安装配置

    • Docker容器的安装需要使用root权限。
    • docker-engine rpm包与containerd rpm包、runc rpm包、podman rpm包不能同时安装。因为docker-engine rpm包中已经包含Docker运行所需的所有组件,其中包括containerd、runc、docker二进制,且containerd、runc和podman rpm包也分别提供了对应的二进制,所以重复安装时会出现软件包冲突。

    可以通过在/etc/docker/daemon.json文件中添加配置项自定义配置参数,相关配置项以及如何使用可以通过dockerd —help查看。配置示例如下:

    daemon运行目录配置

    用户需要明白重新指定各种运行目录和文件(包括—graph、—exec-root等),可能会存在目录冲突,或文件属性变换,对应用的正常使用造成影响。

    • 以—graph为例,当我们使用/new/path/作为daemon新的Root Dir时,如果/new/path/下已经存在文件,且目录或文件名与docker需要使用的目录或文件名冲突(例如: containers、hooks、tmp等目录)时,docker可能会更新原有目录或文件的属性,包括属主、权限等为自己的属主和权限。

    daemon自带网络配置

    • Docker daemon使用—bip参数指定docker0网桥的网段之后,如果在下一次重启的时候去掉—bip参数,docker0网桥会沿用上一次的—bip配置,即使重启之前已经删除docker0网桥。原因是docker会保存网络配置并在下一次重启的时候默认恢复上一次配置。
    • Docker network create 并发创建网络的时候,可以创建具有相同名字的两个网络。原因是docker network是通过id来区分的,name只是个便于识别的别名而已,不保证唯一性。
    • Docker在桥接bridge网络模式下,Docker容器是通过宿主机上的NAT模式,建立与宿主机之外世界的通信。Docker Daemon在启动一个容器时,每在宿主机上映射一个端口都会启动一个docker-proxy进程来实现访问代理。建议用户在使用这种userland-proxy时,只映射必须的端口,减少docker-proxy进行端口映射所消耗的资源。

    daemon-umask配置

    容器主进程和exec进程的默认umask为0022,为了满足安全性需求,避免容器受到攻击,修改runc的实现,将默认umask修改为0027。修改后others群组将无法访问新建文件或目录。

    docker启动容器时的默认umask值为0027,可以在dockerd启动时,使用—exec-opt native.umask=normal参数将容器启动时的umask修改为0022。

    详细的配置见和docker run章节的参数说明。

    daemon启动时间

    Docker服务由systemd管理,systemd对各个服务的启动时间有限制,如果指定时间内docker服务未能成功启动,则可能由以下原因导致:

    • 如果使用devicemapper且为第一次启动,docker daemon需要对该设备做文件系统初始化操作,而该操作会进行大量磁盘IO操作,在磁盘性能不佳或存在大量IO竞争时,很可能会导致docker daemon启动超时。devicemapper设备只需要初始化一次,后续docker daemon启动时不再需要重复初始化。
    • 如果当前系统资源占用太高,导致系统卡顿,系统所有的操作都会变慢,也可能会出现docker服务启动超时的情况。
    • daemon重启过程中,需要遍历并读取docker工作目录下每一个容器的配置文件、容器init层和可写层的配置,如果当前系统存在过多容器(包含created和exited的容器),并且磁盘读写性能受限,也会出现daemon遍历文件过久导致docker服务启动超时的情况。

    出现服务启动超时情况,建议对以下两种情况进行排查调整:

    • 结合解决方案的性能要求场景,调整编排层的清理周期和docker服务的启动时间。

    关联组件journald

    重启systemd-journald后需要重启docker daemon。journald通过pipe获取docker daemon的日志,如果journald服务重启,会导致该pipe被关闭,docker的日志写入操作便会触发SIGPIPE信号,该错误信号会导致docker daemon crash。由于忽略该信号影响严重,可能导致后续docker daemon的日志无法记录,因此建议用户在重启journald服务或者journald 异常后主动去重启docker daemon,保证docker日志能够被正常记录,避免daemon crash导致的状态异常。

    需要在重启或拉起firewalld之后重启docker服务,保证docker服务在firewalld之后启动。

    • firewalld服务启动会清空当前系统的iptables规则,所以在启动docker daemon过程中,重启firewalld可能会导致docker服务插入iptables规则失败,从而导致docker服务启动失败。
    • docker服务启动后重启firewalld服务,或者状态发生了变化(从启动到停止,或者从停止到启动),会导致docker的iptables规则被删除,创建带端口映射的容器失败。

    关联组件iptables

    docker使用—icc=false选项时,可以限制容器之间互通,但若os自带某些规则,可以造成限制容器之间互通失效,例如:

    1. ...
    2. 0 0 ACCEPT icmp -- * * 0.0.0.0/0 0.0.0.0/0
    3. ...
    4. 0 0 DROP all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
    5. ...

    在Chain FORWARD中,DROP上面多出了一条ACCEPT icmp的规则,造成加了—icc=false后,容器之间也能ping通,但容器之间如果使用udp/tcp协议,对端仍然是不可达的。

    因此,在容器os中使用docker,如果需要使用—icc=false选项时,建议先在host上清理一下iptables相关的规则。

    关联组件audit

    docker支持配置audit,但不是强制的。例如:

    1. -w /var/lib/docker -k docker
    2. -w /etc/docker -k docker
    3. -w /usr/lib/systemd/system/docker.service -k docker
    4. -w /usr/lib/systemd/system/docker.socket -k docker
    5. -w /etc/sysconfig/docker -k docker
    6. -w /usr/bin/docker-containerd -k docker
    7. -w /usr/bin/docker-runc -k docker
    8. -w /etc/docker/daemon.json -k docker

    配置docker的audit,好处在于可以记录更多信息便于审计,但从安全角度来看,它对防攻击并没有实质性的作用。另一方面,audit配置会导致严重的效率问题,可能导致系统卡顿,生产环境中请谨慎使用。

    下面以“-w /var/lib/docker -k docker”为例,演示docker audit的配置:

    1. [root@localhost signal]# cat /etc/audit/rules.d/audit.rules | grep docker -w /var/lib/docker/ -k docker
    2. [root@localhost signal]# auditctl -R /etc/audit/rules.d/audit.rules | grep docker
    3. [root@localhost signal]# auditctl -l | grep docker -w /var/lib/docker/ -p rwxa -k docker
    1. type=SYSCALL msg=audit(1517656451.457:8097): arch=c000003e syscall=257 success=yes exit=3 a0=ffffffffffffff9c a1=1b955b0 a2=90800 a3=0 items=1 ppid=17821 pid=1925 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts6 ses=4 comm="ls" exe="/usr/bin/ls" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="docker"type=CWD msg=audit(1517656451.457:8097): cwd="/root"type=PATH msg=audit(1517656451.457:8097): item=0 name="/var/lib/docker/containers" inode=1049112 dev=fd:00 mode=040700 ouid=0 ogid=0 rdev=00:00 obj=unconfined_u:object_r:container_var_lib_t:s0 objtype=NORMAL

    安全配置seccomp

    在做容器网络性能测试时发现,Docker相对于原生内核namespace性能有所下降,经分析开启seccomp后,系统调用(如:sendto)不会通过system_call_fastpath进行,而是调用tracesys,这会带来性能大幅下降。因此,建议在有高性能要求的业务的容器场景下关闭seccomp,示例如下:

    1. docker run -itd --security-opt seccomp=unconfined busybox:latest

    禁止修改docker-daemon的私有目录

    不允许对Docker用的根目录(默认/var/lib/docker)和运行时目录(默认/run/docker)以及其文件作任何修改,包括在该目录下删除文件,添加文件,对目录或者文件做软/硬链接,修改文件的属性/权限,修改文件的内容等,如果确实需要做修改,后果自负。

    普通用户大量部署容器场景下的配置注意事项

    普通用户在OS主机上能创建的进程数的上限,例如:可以在系统中创建配置文件“/etc/security/limits.d/20-nproc.conf”限制;类似的,普通用户在容器里也能创建的进程数的上限,由容器镜像中“/etc/security/limits.d/20-nproc.conf”文件对应的值决定,如下所示:

    1. cat /etc/security/limits.conf
    2. * soft nproc 4096

    当普通用户大量部署容器,导致容器内进程过多资源不够出现报错时,需要把容器镜像“/etc/security/limits.d/20-nproc.conf”文件中如上所示的4096配置值加大。

    本发行版docker支持overlay2和devicemapper两种存储驱动。由于overlay2较devicemapper而言,拥有更好的性能,建议用户在生产环境中优先考虑。

    配置方法

    docker默认为使用overlay2存储驱动,也可以通过如下两种方式显示指定。

    • 编辑/etc/docker/daemon.json,通过storage-driver字段显示指定。

      1. cat /etc/docker/daemon.json
      2. {
      3. "storage-driver": "overlay2"
      4. }
    • 编辑/etc/sysconfig/docker-storage,通过docker deamon启动参数显示指定。

      1. DOCKER_STORAGE_OPTIONS="--storage-driver=overlay2"

    注意事项

    • 部分容器生命周期管理的操作会报找不到相应的rootfs或者相关的可执行文件。
    • 如果容器的健康检查配置的是执行容器内的可执行文件,也会报错,导致容器的健康检查失败。

    • 如果将overlay2作为graphdriver,在容器中第一次修改镜像中的文件时,若该文件的大小大于系统剩余的空间,修改将会失败。因为即使修改很小,也要把这个文件完整的拷贝到上层,剩余空间不足导致失败。

    • overlay2文件系统相比普通文件系统天然存在一些行为差异,归纳如下:

      • 内核版本

        overlay2只兼容原生4.0以上内核,建议配合使用ext4文件系统。

      • Copy-UP性能问题

        修改lower层文件会触发文件复制到upper层,其中数据块复制和fsync比较耗时。

      • rename目录问题

        • 只有源路径和目标路径都在merged层时,才允许rename系统调用,否则rename系统调用会报错-EXDEV。
        • 内核4.10引入了redirect dir特性来修复rename问题,对应内核选项为CONFIG_OVERLAY_FS_REDIRECT_DIR。

          在使用overlay2场景下,对文件系统目录进行重命名时,如果系统配置文件/sys/module/overlay/parameters/redirect_dir中配置的特性开关为关闭状态,则会导致使用失败;如果用户要使用相关特性,需要用户手动设置/sys/module/overlay/parameters/redirect_dir为“Y”。

      • Hard link break问题

        • 当lower层目录中有多个硬链接,在merged层写入数据会触发Copy-UP,导致硬链接断开。
        • 内核4.13引入了index feature来修复这个问题,对应内核选项为 CONFIG_OVERLAY_FS_INDEX。注意这个选项没有前向兼容性,不支持热升级。
      • st_dev和st_ino变化

        触发Copy-UP之后,用户只能看到merged层中的新文件,inode会变化。虽然attr和xattr可以复制,但st_dev和st_ino具有唯一性,不可复制。这会导致stat和ls查看 到相应的变化。

      • fd变化

        Copy-UP之前,以只读模式打开文件得到描述符fd1,Copy-UP之后,打开同名文件得到文件描述符fd2, 二者实际指向不同的文件。向fd2写入的数据不会在fd1中体现。

    异常场景

    容器使用配置了overlay2存储驱动的过程中,可能出现挂载点被覆盖的异常情况。例如

    异常场景-挂载点被覆盖

    挂载关系:在问题容器的挂载点的下面,存在一个/var/lib/docker/overlay2的挂载点:

    1. [root@localhost ~]# mount -l | grep overlay
    2. overlay on /var/lib/docker/overlay2/844fd3bca8e616572935808061f009d106a8748dfd29a0a4025645457fa21785/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/JL5PZQLNDCIBU3ZOG3LPPDBHIJ:/var/lib/docker/overlay2/l/ELRPYU4JJG4FDPRLZJCZZE4UO6,upperdir=/var/lib/docker/overlay2/844fd3bca8e616572935808061f009d106a8748dfd29a0a4025645457fa21785/diff,workdir=/var/lib/docker/overlay2/844fd3bca8e616572935808061f009d106a8748dfd29a0a4025645457fa21785/work)
    3. /dev/mapper/dm-root on /var/lib/docker/overlay2 type ext4 (rw,relatime,seclabel,data=ordered)

    执行部分docker命令会遇到错误,比如:

    1. [root@localhost ~]# docker rm 1348136d32
    2. docker rm: Error response from daemon: driver "overlay2" failed to remove root filesystem for 1348136d32: error while removing /var/lib/docker/overlay2/844fd3bca8e616572935808061f009d106a8748dfd29a0a4025645457fa21785: invalid argument

    此时,在主机侧可以发现对应容器的rootfs找不到,但这并不意味着rootfs丢失,只是被/var/lib/docker/overlay2挂载点覆盖,业务仍然可以正常运行,不受影响。修复方案可以参考如下:

      1. 确定当前docker所使用graphdriver:

        1. docker info | grep "Storage Driver"
    1. 2. 查询当前的挂载点:
    2. ```
    3. Devicemapper: mount -l | grep devicemapper
    4. Overlay2: mount -l | grep overlay2
    5. ```
    6. 输出格式为: A on B type C \(D\)
    7. - A:块设备名称或overlay
    8. - B:挂载点
    9. - C:文件系统类型
    10. - D:挂载属性
    11. 3. 从下往上逐一umount这些挂载点B
    12. 4. 然后全部docker restart这些容器,或者删除所有容器。
    13. 5. 重启docker
    14. ```
    15. systemctl restart docker
    16. ```
    • 修复方案二
      1. 业务迁移
      2. 节点重启

    配置devicemapper存储驱动

    用户如果需要使用devicemapper存储驱动,可以通过如下两种方式显示指定。

    • 编辑/etc/docker/daemon.json,通过storage-driver字段显示指定。

    • 编辑/etc/sysconfig/docker-storage,通过docker deamon启动参数显示指定。

      1. cat /etc/sysconfig/docker-storage
      2. DOCKER_STORAGE_OPTIONS="--storage-driver=devicemapper"

    注意事项

    • 使用devicemapper必须使用devicemapper+direct-lvm的方式,配置的方法可以参考 https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/#configure-direct-lvm-mode-for-production
    • 配置devicemapper时,如果系统上没有足够的空间给thinpool做自动扩容,请禁止自动扩容功能。
    • 禁止把/etc/lvm/profile/docker-thinpool.profile中如下两个值都改成100。

      1. activation {
      2. thin_pool_autoextend_threshold=80
      3. thin_pool_autoextend_percent=20
      4. }
    • 使用devicemapper时推荐加上—storage-opt dm.use_deferred_deletion=true —storage-opt dm.use_deferred_removal=true。

    • 使用devicemapper时,容器文件系统推荐使用ext4,需要在docker daemon的配置参数中加 上—storage-opt dm.fs=ext4。
    • 当graphdriver为devicemapper时,如果metadata文件损坏且不可恢复,需要人工介入恢复。禁止直接操作或篡改daemon存储devicemapper的元数据。
    • 使用devicemapper lvm时,异常掉电导致的devicemapper thinpool损坏,无法保证thinpool损坏后可以修复,也不能保证数据的完整性,需重建thinpool。

    docker daemon开启了user namespace特性,切换devicemapper存储池时的注意事项

    • 一般启动容器时,deviceset-metadata文件为:/var/lib/docker/devicemapper/metadata/deviceset-metadata。
    • 使用了user namespace场景下,deviceset-metadata文件使用的是:/var/lib/docker/{userNSUID.GID}/devicemapper/metadata/deviceset-metadata。
    • 使用devicemapper存储驱动,容器在user namespace场景和普通场景之间切换时,需要将对应deviceset-metadata文件中的BaseDeviceUUID内容清空;针对thinpool扩容或者重建的场景下,也同样的需要将对应deviceset-metadata文件中的BaseDeviceUUID内容清空,否则docker服务会重启失败。

    信号量残留

    使用devicemapper作为graphdriver时,强制退出强制退出可能导致信号量残留。docker在操作dm的过程中会创建信号量,如果在释放信号量前,daemon被强制退出,可能导致该信号量无法释放,一次强制退出最多泄露一个信号量,泄露概率低。而linux系统有信号量上限限制,当信号量泄露次数达到上线值时将无法创建新的信号量,进而导致docker daemon启动失败。排查方法如下:

    1. 首先查看系统上残留的信号量

      1. $ ipcs
      2. ------ Message Queues --------
      3. key msqid owner perms used-bytes messages
      4. ------ Shared Memory Segments --------
      5. key shmid owner perms bytes nattch status
      6. ------ Semaphore Arrays --------
      7. key semid owner perms nsems
      8. 0x0d4d3358 238977024 root 600 1
      9. 0x0d4d0ec9 270172161 root 600 1
      10. 0x0d4dc02e 281640962 root 600 1
    2. 接着用dmsetup查看devicemapper创建的信号量,该信号量集合是上一步中查看到的系统信号量的子集

      1. $ dmsetup udevcookies
    3. 最后查看内核信号量设置上限,第四个值就是当前系统的信号量使用上限

      1. $ cat /proc/sys/kernel/sem
      2. 250 32000 32 128

      如果步骤1中残留的信号量数量与步骤3中看到的信号量上限相等,则是达到上限,此时docker daemon无法正常启动。可以使用下述命令增加信号量使用上限值来让docker恢复启动

      1. $ echo 250 32000 32 1024 > /proc/sys/kernel/sem

      也可以手动清理devicemapper残留的信号量(下面是清理一分钟以前申请的dm相关信号量)

    网卡残留

    使用bridge模式启动容器的过程中,强制退出daemon可能导致网卡残留。使用bridge网络模式,当docker创建容器时,会先在host上创建一对veth,然后再把该网卡信息存到数据库中,如果在创建完成,存到docker的数据库之前,daemon被强制退出,那么该网卡无法被docker关联,下次启动也无法删除(docker本身会清理自己数据库中不用的网卡),从而造成网卡残留。

    重启容器失败

    容器hook耗时较长,且启动阶段遇到containerd被强制退出,再次执行容器start操作可能失败。容器启动阶段遇到containerd被强制退出,docker start操作直接返回错误;containerd被重新拉起后,上次启动可能仍处于runc create执行阶段(执行用户自定义hook,可能耗时较长),此时再次下发docker start命令启动该容器,可能提示以下错误:

    1. Error response from daemon: oci runtime error: container with id exists: xxxxxx

    该错误是由runc create一个已经存在(创建中)的容器导致,等第一次start对应的runc操作结束后再次执行docker start便可以成功。

    由于hook的执行不受docker控制,这种场景下尝试回收该容器有可能导致containerd进程启动卡死(执行未知hook程序),且问题的风险可控(短期影响当前容器的创建):

    • 问题出现后等待第一次操作结束可以再次成功启动该容器。
    • 一般是在容器启动失败后创建新的容器,不复用已经失败的容器。

    综上,该问题暂时作为场景约束。

    服务无法正常重启

    短时间内频繁重启docker服务导致该服务无法正常重启。docker系统服务由systemd负责监控,如果docker服务在10s内重启次数超过5次,systemd服务就会监控到该异常行为,因此会禁止docker服务启动。只有等到下一个10s周期开始后,docker服务才能响应重启命令正常重启。

    主机意外掉电或系统panic等场景下,由于docker daemon的状态无法及时刷新到磁盘,导致重启后docker daemon状态不正常,可能出现的问题有(包括但不限于):

    • 掉电前创建的容器,重启后docker ps -a看不到,该问题是因为该容器的状态文件没有刷新到磁盘,从而导致重启后daemon无法获取到该容器的状态(镜像、卷、网络等也可能会有类似问题)。
    • 掉电前某个文件正处于写入状态,尚未完全写入,重启后daemon重新加载该文件发现文件格式不正常或内容不完整,导致重启加载出错。
    • 针对掉电时会破坏docker DB的情况,在重启节点时会清理data-root下面的db文件。因此重启前创建的如下信息在重启后会被删除:

      • network,用docker network创建的资源会在重启后清除。
      • volume,用 docker volume创建的资源会在重启后删除。
      • containerd保存的元数据,由于启动容器会重建containerd元数据,重启节点会清理containerd中保存的元数据。