在 Production 环境使用 Docker 需要注意的大小事

本文件列举了一些使用 Docker 于生产环境会碰到的问题,欢迎一同贡献、分享!

后续内容会更新在 GitHub Repo。

前言

大家好,我是 Ian。之前发表在 StarBugs 的技术文章(讲述如何避免 Docker 佔用大量的磁碟空间)收到了很不错的迴响,所以我一直有在思考是不是应该开个课程或是继续撰写相关的专栏。

由于本人拖延症的毛病,这些文章草稿被封尘在我的 medium draft(s) 迟迟没有进展。事情的转机来自于前几天看到地震如何影响了花莲的受灾户,碍于个人仅能在救助捐款上尽一点绵薄之力,因此我借鉴 Backend TW 版大开讲座救石虎的方式写了篇文章,聊聊有关在 production 环境中使用 Docker 有可能碰到的坑,如果你有因为这篇文章受益且行有余力的话,不妨考虑小额捐款援助地震灾区。

记忆体管理

Docker 为符合 OCI 定义的实作,它让我们可以撰写简易的配置档案后再使用几行指令启动一个完全隔离的沙箱(Sandbox),社群上将这个沙箱称之为「容器」。
儘管如此,这些被我们用 Docker 建立的 Container 在 Kernel Space 上还是依赖宿主机,这也是为什么 Docker on MacOS/Windows 需要仰赖额外的基础设施[1][2]。

然而,容器直接的仰赖宿主机的 Linux Kernel,这也意味着这些容器同样受到 kernel 的管理,如果要将 Docker 这类的容器化技术使用在生产环境上,OOM 的问题值得我们特别注意!

Out of Memory Killer 由 Linux Kernel 实作,是为了避免 Process 吃光记忆体资源设下的最后一道防线。Lorenzo Stoakes 在他的 linux-vm-notes 专案上对 OOM Killer 进行了详细的解说。如何判断自己是否遇到了 OOM?在应用程式已经有充分的错误处理的情况下,容器还是毫无预兆的发生状态异常。使用 docker inspect 检查 exit code 确认为 exit code (137)docker inspect 有时似乎不太灵光,经验告诉我使用 dmesg 确认也十分有用。若经过评估后,宿主机的硬体对比其所执行的附载不至于造成应用程式崩溃,我们可以合理的怀疑应用程式可能有 memory leak 的状况发生。

磁碟管理

如果在生产环境上大量的使用 Docker,在磁碟空间不充足的前提下其实有可能会遇到很多问题:

笔者先前在 Medium 撰写的文章讲述了如何定期清理无用的 docker 资源。如果机器的数量不大,可以写简易的脚本配合 linux cronjob 定期回收资源。若你需要管理大量的机器,可以考虑使用 Ansible 这类的工具一次性管理所有机器上的未使用资源。

Docker 映像档案最佳化

使用 multiple stage build。在 Dockerfile 中如果使用如 aptsnap 安装了特定的工具,可在安装完毕后清除无用的暂存档案。善用 .dockerignore 避免无用的档案存放于 image。

DevSecOps

不要使用 root 运作你的 container

如果非必要,不要使用 root 运作你的 container,使用 root 运作 container 可能会对系统带来很多隐忧:预设情况下,Docker image 的预设使用者都是 root,建议在 image 建立一个独立的 group 以及 user。尽可能使用 linux capabilities 赋予 container 需要的权利。使用 root 可能造成的危害:如果应用程式有修改档案的行为,可能会无意间修改从外部挂载进来的档案。如果 container 使用 host network,恶意会无意的修改网路设定都有可能瘫痪宿主机的网路。

固定 image 的版本号码

不要在 production 环境使用的 image 指定 latest tag,这会让我们无法保证每个开发者、每一套 Server、每一次透过 CI/CD deliver 的 docker image 为一致的:

建议固定使用某个 stable version。定期使用工具扫描 image 的漏洞,这篇 HackMD 共笔 推荐了许多免费的实用工具。

Rate limit

CI/CD 结合 Docker 非常的方便,但 Docker Hub 的 rate limit 策略可能会让你的 CI/CD 环境大停摆:

对于频繁使用的 image(s),可以考虑自架 container registry 或 image proxy 避免触碰到上限值。如果有创办 Docker Hub 帐号,可以在 CI/CD Runner 上登入那些帐号,登入后的机器将会获得更宽鬆的限制。如果前两者都无法满足需求,建议考虑付费解锁官方的 rate limit。

在 CI/CD 整合 image scanner

将静态的扫描工具(如:Sonar Qube)整合到 CI/CD pipeline 上可以省去很多麻烦,若 Dockerfile 的撰写方式会造成资安疑虑,这类工具也能即时的告知开发人员。使用 syft 对常用的 image 进行扫描,它能够分析当前 image 以及其安装的 packages 是否有资安漏洞或潜在风险。

使用精简化的 container image

社群上有许多精简化的 docker image 供大家使用,例如着名的 Alpine(其精简的实作也不易受到 CVE 影响)。

需要注意的是,有些应用程式因为 dependency 的关係会没办法运作在 Alpine 之上[1]。使用 Distroless 能够避免这个问题(但空间还是较 Alpine 大了一些)。GitHub 上也能找到一些 Alpine with glibc 的 image 範例,如果有人定期维护或是自己有余力维护的话也是一个好选择。

网路议题

Docker 的官方文件 提到了一些与安全性相关的议题,对于使用 Docker 来运作对外的网路服务的案例,我们应该特别注意这些问题。

Iptables

Docker 通过操作 iptables 的规则提供网路层级的隔离(isolation)Docker 客製化了两个 iptalbes 链(chain)存放客製化的规则,分别是 DOCKERDOCKER-USER:不要手动修改 DOCKER 链上的规则,如果你希望为 container 设定特殊规则,请新增到 DOCKER-USER 链上。这两个链属于 FORWARD 链的一部分,确保所有封包会被两条链上的规则检查。当封包进入到 DOCKER-USER 链上时,这些封包都已经被 DNAT 转换 Dst IP 的资讯:因此,若要对指定的封包设定规则,应使用 internal IP。否则,应使用 conntrack(但这会很大程度的影响网路封包的处理效率)。

Firewall

Debian 和 Ubuntu 使用 ufw 作为防火墻的前端,然而,ufw 与 docker 都使用了 iptables 且出于某些原因,你在 ufw 上设立的规则将无法套用到通往 container 的网路封包。

图片取自 wikipedia

ufw 在 INPUT 以及 OUTPUT 链上对封包进行检查。Ingress:经由 NAT 表(table)后通往 container 的网路封包会被发往 FORWARD 链,刚好巧妙的绕过 INPUT 链上的规则。Egress:参考上图,经过 briging decision 且送往 FORWARD 链的封包不会送往 OUTPUT 链。补充:Ric Hincapie 的技术文章 使用图解且搭配案例分析很好地解释了这个问题。

Dataplane

正常情况下,Container 具有独立的 network namespace(Host Network Mode 除外),这也代表:

与 Host 的 Network Stack 是互相隔离的。Network device 是独立的。就算在 container 中新增 tun/tap、veth、vlan 等 device,在 host 上也不可见。

但是涉及 kernel plugin 的部分,即使我们从 container 下手,对 host 仍是可见的:

Container 中载入的 eBPF program(且 container 必须为 privileged mode 才有权限做这件事)。Kernel module。

关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章