安装部署

安装配置介绍及注意事项

本章节主要介绍和开源容器 docker 安装相关的重要配置。


基本安装配置

配置daemon参数

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

cat /etc/docker/daemon.json 
{        
    "debug": true,        
    "storage-driver": "overlay2",        
    "storage-opts": ["overlay2.override_kernel_check=true"] 
}


daemon运行目录配置

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

须知:

用户指定的目录或文件应为docker专用,避免冲突导致的文件属性变化带来安全问题。

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

须知:

从 docker-17.05 开始,--graph 参数被标记为 Deprecated,用新的参数 --data-root 替代。


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 create/run 也配置了 native.umask 参数,则以 docker create/run 中的配置为准。

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


daemon启动时间

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

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

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

  • 容器编排层定期清理不需要的容器,尤其是 exited 的容器。
  • 结合解决方案的性能要求场景,调整编排层的清理周期和 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

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

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


关联组件 iptables

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

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) 
... 
0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0 
... 
0     0 DROP       all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0
...

在 Chain FORWARD 中,DROP 上面多出了一条 ACCEP icmp 的规则,造成加了 --icc=false后,容器之间也能 ping 能通,但 udp/tcp 仍然是不通的。

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


关联组件audit

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

-w /var/lib/docker -k docker 
-w /etc/docker -k docker 
-w /usr/lib/systemd/system/docker.service -k docker 
-w /usr/lib/systemd/system/docker.socket -k docker 
-w /etc/sysconfig/docker -k docker 
-w /usr/bin/docker-containerd -k docker 
-w /usr/bin/docker-runc -k docker 
-w /etc/docker/daemon.json -k docker

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

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

[root@localhost signal]# cat /etc/audit/rules.d/audit.rules | grep docker -w /var/lib/docker/  -k docker 
[root@localhost signal]# auditctl -R /etc/audit/rules.d/audit.rules | grep docker 
[root@localhost signal]# auditctl -l | grep docker -w /var/lib/docker/ -p rwxa -k docker

说明:

-p [r|w|x|a] 和 -w 一起使用,观察用户对这个目录的读、写、执行或者属性变化(如时间戳变化)。这样的话,在/var/lib/docker目录下的任何文件、目录操作,都会打印日志到 audit.log中,从而会有太多的日志往 audit.log 中记录,会严重地影响 auditd, 比如内存、cpu 占用等,进而影响 os 的运行。例如:每次执行 ”ls /var/lib/docker/containers” 都会有类似如下日志记录到 /var/log/audit/audit.log 中。

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,示例如下:

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” 文件对应的值决定,如下所示:

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

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

可配置的最大值请参考内核的最大能力,如下:

[root@localhost ~]# sysctl -a | grep pid_max 
kernel.pid_max = 32768



存储驱动配置

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


配置 overlay2 存储驱动

配置方法

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

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

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

    cat /etc/sysconfig/docker-storage 
    DOCKER_STORAGE_OPTIONS="--storage-driver=overlay2"
    
注意事项
  • 部分容器生命周期管理的操作会报找不到相应的 rootfs 或者相关的可执行文件。

  • 如果容器的健康检查配置的是执行容器内的可执行文件,也会报错,导致容器的健康检查失败。

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

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

    • 内核版本

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

    • Copy-UP 性能问题

      修改 lower 层文件会触发文件复制到 uppe r层,其中数据块复制和 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 的挂载点:

[root@localhost ~]# mount -l | grep overlay 
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) 
/dev/mapper/dm-root on /var/lib/docker/overlay2 type ext4 (rw,relatime,seclabel,data=ordered)

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

[root@localhost ~]# docker rm 1348136d32
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:

      docker info | grep "Storage Driver"
      
    2. 查询当前的挂载点:

      Devicemapper: mount -l | grep devicemapper 
      Overlay2: mount -l | grep overlay2
      

      输出格式为: A on B type C (D)

      • A:块设备名称或overlay
      • B:挂载点
      • C:文件系统类型
      • D:挂载属性
    3. 从下往上逐一 umount 这些挂载点 B。

    4. 然后全部 docker restart 这些容器,或者删除所有容器。

    5. 重启 docker。

      systemctl restart docker
      
  • 修复方案二

    1. 业务迁移
    2. 节点重启


配置 devicemapper 存储驱动

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

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

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

    cat /etc/sysconfig/docker-storage 
    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。

    activation {   
      thin_pool_autoextend_threshold=80   
      thin_pool_autoextend_percent=20 
    }
    
  • 使用 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 服务会重启失败。



强杀 docker 相关后台进程的影响

docker 的调用链很长,强杀 docker 相关后台进程可能会导致一些数据状态不一致,本章节列举一些强杀可能导致的问题。


信号量残留

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

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

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

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

    $ cat /proc/sys/kernel/sem 
    250     32000   32      128
    

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

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

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

    $ dmsetup udevcomplete_all 1 
    This operation will destroy all semaphores older than 1 minutes with keys that have a prefix 3405 (0xd4d). 
    Do you really want to continue? [y/n]: y 
    0 semaphores with keys prefixed by 3405 (0xd4d) destroyed. 0 skipped.
    


网卡残留

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


重启容器失败

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

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 中保存的元数据。

      说明:

      用户若选择采用手动清理恢复环境的方式,可通过配置环境变量 “DISABLE_CRASH_FILES_DELETE=true” 屏蔽 daemon 掉电重启时 db 文件清理功能。

results matching ""

    No results matching ""