启动Docker容器超过100个后报错问题

环境信息

AMD EPYC 7551 32-Core Processor 64bit / 452GB / 4.0TB
Kylin Linux Advanced Server V10 (Sword)
Linux-419.90-24.4.v2101.ky10.x86 64-x86 64-with-kylin-10-Sword
Docker 19.03.9

问题现象

Docker容器超过一定数量后无法启动新的容器。

排查和解决

先检查目前正常运行的容器数量,有134个:

> docker ps| wc-l
134

查看了系统可用CPU和内存资源,发现可用16个CPU线程和24GB内存,可用资源还比较宽裕。

考虑到目前运行的容器已经比较多,首先怀疑是打开文件数量(open files)达到上限,检查系统设置值:

> ulimit -n
1024

因为其他类似环境里一般都设置为65535,所以用ulimit -n 65536将打开文件数上限改大,但问题并没有解决。

既然启动新的容器失败,可能docker daemon日志会有线索,查了一下果然发现有错误信息如下:

>  journalctl -ru docker.service
...
04月 26 14:39:35 k2box-211 dockerd[3016]: time="2023-04-26T14:39:35.212798766+08:00" level=error msg="stream copy error: reading from a closed fifo"
04月 26 14:39:35 k2box-211 dockerd[3016]: time="2023-04-26T14:39:35.212674036+08:00" level=error msg="stream copy error: reading from a closed fifo"
04月 26 14:39:26 k2box-211 dockerd[3016]: time="2023-04-26T14:39:26.731792766+08:00" level=warning msg="Health check for container 443a24419f9864ca13a6772c35bb7fe9eabcf317d670d7c73c0383e5473cb397 error: context deadline exceeded"
04月 26 14:39:02 k2box-211 dockerd[3016]: time="2023-04-26T14:39:02.706135654+08:00" level=warning msg="Health check for container d18b85908134d2c45869f68daf825d0c7f6541e77673e8c83a03d34a1c0a3ad9 error: OCI runtime exec failed: exec failed: container_linux.go:318: starting container process caused \"exec: \\\"curl\\\": executable file not found in $PATH\": unknown"
04月 26 14:39:02 k2box-211 dockerd[3016]: time="2023-04-26T14:39:02.699741119+08:00" level=error msg="stream copy error: reading from a closed fifo"
04月 26 14:39:02 k2box-211 dockerd[3016]: time="2023-04-26T14:39:02.699578169+08:00" level=error msg="stream copy error: reading from a closed fifo"
...

日志提示context deadline exceeded,这个错误信息虽然比较模糊,但从时间上看与尝试创建新容器的时间是吻合的,并且应该是某个阈值的限制。在网上google了一下没有找到相同的问题。

尝试停止了一个正在运行的容器,此时启动新容器能正常成功1个,再启动则会失败,因此确认了是容器数量达到了某个阈值,但为什么是134个?

经过仔细分析,发现这134个容器的启动方式有区别,其中34个是通过docker run命令行启动的,另外100个是通过docker-java以程序方式启动的(http接口),查看相关代码发现后者果然有限制:

DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
    .dockerHost(config.getDockerHost())
    .sslConfig(config.getSSLConfig())
    .maxConnections(100)  // 这里对连接数量做了限制
    .connectionTimeout(Duration.ofSeconds(30))
    .responseTimeout(Duration.ofSeconds(45))
    .build();

至此定位到问题原因,解决方法是将maxConnections(100)改为maxConnections(1024)并打包更新到问题环境。

使用Docker部署PostgreSql高可用集群(RepMgr方案)

本文记录了某项目中使用docker部署PostgreSQL集群的步骤和注意事项,使用的镜像是bitnami/postgresql-repmgr,其中与用户身份有关的内容在其他bitnami打包的镜像中也适用。

一、环境信息

CentOS 7.9
PostgreSQL 16

二、部署方案

PostgreSQL集群有多种方案,这里我们使用在项目中多次使用比较稳定的repmgr方案,repmgr能够在集群环境管理每个PostgreSQL节点的主从状态,官网介绍如下:

repmgr是一个开源的工具套件,用于管理PostgreSQL服务器集群中的复制和故障转移。它通过提供设置备用服务器、监控复制和执行故障转移或手动切换操作等管理任务的工具,增强了PostgreSQL内置的热备功能。
repmgr自从PostgreSQL 9.0引入内置的复制机制后,就提供了高级的支持。当前的repmgr系列,repmgr 5,支持了PostgreSQL 9.3引入的最新的复制功能,如级联复制、时间线切换和通过复制协议进行基础备份。

bitnami提供的postgresql-repmgr镜像是将PostgreSQL与repmgr打包在一起,形成一个集成的解决方案,以便用户能够快速搭建PostgreSQL集群服务,用户可以从dockerhub获取到这个镜像。

这个PostgreSQL集群解决方案包括PostgreSQL复制管理器,一个用于管理PostgreSQL集群上的复制和故障转移的开源工具。

三、部署步骤

首先确认集群环境各个服务器节点的状态:

关闭防火墙(如果docker已启动需要重启docker服务,否则关闭防火墙后启动容器会报iptables命令错):

systemctl stop firewalld 
systemctl disable firewalld

确保服务器时钟准确:

ntpdate cn.ntp.org.cn

安装和启动docker服务和docker-compose工具:

yum install -y epel-release
yum install -y docker
yum install -y docker-compose
systemctl enable docker
systemctl start docker

安装postgresql-repmgr镜像:

docker pull bitnami/postgresql-repmgr

将各个服务器名称写入hosts文件,vi /etc/hosts添加下面内容:

10.102.9.80 pg-0
10.102.9.81 pg-1
10.102.9.82 pg-2

四、配置

数据目录

bitnami的镜像使用非root用户身份,即容器里的root用户映射到宿主机的非root用户,此用户是ID为1001的无名称用户。要让容器的数据能够持久化到宿主机,需要准备一个数据目录(此例中为/mnt/sda/bitnami/postgresql)并映射到容器内,此目录的owner是1001:

mkdir /mnt/sda/bitnami
mkdir /mnt/sda/bitnami/postgresql
chown 1001:root /mnt/sda/bitnami -R

网络

创建docker网络以便节点间能够通信:

docker network create --subnet=172.25.0.0/24 --gateway=172.25.0.1 pg-network

配置文件

在任意目录创建pg.yml文件,内容如下:

version: '2'
networks:
  default:
    external:
      name: pg-network
services:
  pg:
    container_name: "pg"
    image: bitnami/postgresql-repmgr:latest
    networks:
      default:
        ipv4_address: 172.25.0.110
    ports:
      - "5432:5432"
    restart: always
    volumes:
      - /mnt/sda/bitnami/postgresql:/bitnami/postgresql
      - /etc/hosts:/etc/hosts
    environment:
      - POSTGRESQL_POSTGRES_PASSWORD=adminpassword
      - POSTGRESQL_USERNAME=myuser
      - POSTGRESQL_PASSWORD=mypassword
      - POSTGRESQL_DATABASE=mydatabase
      - REPMGR_PASSWORD=adminpassword
      - REPMGR_PRIMARY_HOST=pg-0
      - REPMGR_PRIMARY_PORT=5432
      - REPMGR_PARTNER_NODES=pg-0,pg-1,pg-2:5432
      - REPMGR_NODE_NAME=pg-0
      - REPMGR_NODE_NETWORK_NAME=pg-0
      - REPMGR_PORT_NUMBER=5432

在第二个节点类似创建pg.yml文件,修改其中的部分内容(ip地址、以及2处节点名称,见代码中的标注)如下所示:

version: '2'
networks:
  default:
    external:
      name: pg-network
services:
  pg:
    container_name: "pg"
    image: bitnami/postgresql-repmgr:latest
    networks:
      default:
        ipv4_address: 172.25.0.111    <-- 修改了这里
    ports:
      - "5432:5432"
    restart: always
    volumes:
      - /mnt/sda/bitnami/postgresql:/bitnami/postgresql
      - /etc/hosts:/etc/hosts
    environment:
      - POSTGRESQL_POSTGRES_PASSWORD=adminpassword
      - POSTGRESQL_USERNAME=myuser
      - POSTGRESQL_PASSWORD=mypassword
      - POSTGRESQL_DATABASE=mydatabase
      - REPMGR_PASSWORD=adminpassword
      - REPMGR_PRIMARY_HOST=pg-0
      - REPMGR_PRIMARY_PORT=5432
      - REPMGR_PARTNER_NODES=pg-0,pg-1,pg-2:5432
      - REPMGR_NODE_NAME=pg-1    <-- 修改了这里
      - REPMGR_NODE_NETWORK_NAME=pg-1    <-- 修改了这里
      - REPMGR_PORT_NUMBER=5432

第三个节点的情况类似,为节约篇幅这里不再贴配置文件内容。

启动服务

在每个节点分别使用docker-compose命令启动服务:

docker-compose -f /root/pg-ha/pg.yml up -d

查看repmgr状态,例如当前primary节点是哪一个:

docker exec -ti pg /opt/bitnami/scripts/postgresql-repmgr/entrypoint.sh repmgr -f /opt/bitnami/repmgr/conf/repmgr.conf service status

 ID | Name | Role    | Status    | Upstream | repmgrd | PID | Paused? | Upstream last seen
----+------+---------+-----------+----------+---------+-----+---------+--------------------
 1000 | pg-0 | standby |   running | pg-1     | running | 1   | no      | 1 second(s) ago
 1001 | pg-1 | primary | * running |          | running | 1   | no      | n/a
 1002 | pg-2 | standby |   running | pg-1     | running | 1   | no      | 0 second(s) ago

尝试连接数据库,验证服务是否正常(-U参数很重要):

docker exec -ti pg psql -U myuser -d mydatabase

若需要手工切换standby节点为primary执行下面的命令,需要节点之间配置过免密:

docker exec -it pg /opt/bitnami/scripts/postgresql-repmgr/entrypoint.sh repmgr -f /opt/bitnami/repmgr/conf/repmgr.conf standby switchover