作者 :Henrique Souza
原文 :how-to-dockerize-any-application

具有一定基础的同学阅读起来风味更佳😀

1. 挑选基础镜像

Docker 镜像仓库中有许多特定技术的基础镜像:

  • https://hub.docker.com/java
  • https://hub.docker.com/python
  • https://hub.docker.com/nginx

如果以上都运行不起来,那么你需要从基础系统镜像开始,自行安装所有东西。

大部分网上的教程都会以 Ubuntu 开始(比如 Ubuntu16.04),这也没什么错。

但我建议用 Alpine 镜像:

https://hub.docker.com/alpine

相比其他,Alpine 的镜像要小得多(可以小到5MB)。

注意:Alpine 不支持 apt-get 命令。Alpine 有自己的包仓库和包管理工具。详情可移步:

https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management
https://pkgs.alpinelinux.org/packages
# Alpine 镜像中使用 apk 来管理包
apk update
apk upgrade
apk search openssh
apk add openssh
apk del openssh
...

2. 安装必要的系统包

这通常是一些琐碎的东西,以致你可能忽略一些细节:

a)应该把 apt-get update 和 apt-get install 写在一行里(在 Alpine 中用 apk 也一样)。这不仅仅是一个惯例,而是应该这样做,否则 apt-get update 临时镜像(层)会被缓存起来,可能不会马上更新你接下来要用到的包信息。

https://forums.docker.com/t/dockerfile-run-apt-get-install-all-packages-at-once-or-one-by-one/17191

我们知道,Dockerfile 的构建过程是针对每一个指令构建一个容器,然后提交后形成一个新的镜像(层),然后运行新的镜像(层)产生一个容器,在容器上执行下一个指令,再提交生成一个新的镜像(层),而临时容器被销毁,直至得到最终的镜像。因为一个 RUN 指令会生成一个中间容器,所以分成多个 RUN 指令会导致生成多个中间容器,而增加清除时的开销,并且 apt 指令启动也是要耗时的。如果一个 apt-get 指令会频繁发生变化,那么把它分出来,单独执行就是可接受的,因为如没有设置清除临时镜像(层),那么前面的指令生成的镜像缓存就会得到复用,从而提高构建效率。

b)再次确认你 仅仅 安装了真正需要的东西(如果你要在生产环境使用 docker)。我见过人们把 vim开发工具 都塞到了镜像里。

必要的话,针对构建/debug/开发创建不同的 Dockerfile。这不仅是镜像大小的问题,还涉及安全、可维护性等等。

3. 自行添加的文件

有一些线索有助于改进你的 Dockerfiles:

a)理解 COPY 和 ADD 的区别:

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy

COPY 仅支持原样复制本地文件到容器中,而 ADD 特殊一点,如果复制的是压缩文件,会自动解压,同时 ADD 也可以获取 URL 指向的文件。

b)尽量遵循文件系统的常例来放置文件:

http://www.pathname.com/fhs/

比如,对于解释性语言(Go,Python),就用 /usr/src 路径。

c)检查你要添加的文件的参数。如果只是要执行权限,那没必要在镜像上加一层(RUN chmod +x …)。仅仅需要在本地代码里改好初始参数就行。

就算你用的是 Windows 系统,也没有理由这样做,看这个👇:

https://stackoverflow.com/questions/21691202/how-to-create-file-execute-mode-permissions-in-git-on-windows

4. 设定运行用户

首先,休息一下,然后读读下面这篇精彩的文章:

https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf

容器运行时,内部用户的 uid/gid 会映射到外部相同的 uid/gid 上,导致容器获得了宿主机上相同 uid/gid 用户的权限。

读完之后,你将理解到:

a)如果应用程序需要访问用户或组表(/etc/passwd 或 /etc/group),则只需要使用特定(固定ID)用户运行容器。

b)尽量避免用 root 来运行容器。

不幸的是,很多流行的应用要求你用特定的 id 组合来运行它们(比如,Elastic Search 要求 uid:gid = 1000:1000)。

骚年,别跟着这样做…

5. 设定对外端口

这是一个微不足道的问题,恳请你不要仅仅因为需要对外暴露一个特权级的小号端口(比如80),就以 root 来运行容器。你完全可以对外暴露一个非特权端口(比如 8080),然后在运行容器的时候把它映射到80端口就行。

这个区别由来已久:

https://www.w3.org/Daemon/User/Installation/PrivilegedPorts.html

小于1024的端口都是特殊的端口,不允许普通用户基于该端口跑服务。

6. 设定接入点

一般的方式:立即运行你的可执行文件。

更好的方式:创建一个 “docker-entrypoint.sh” 脚本,这样你可以钩住一些东西,像用环境变量的配置(更多的内容可以看下面):

这是一种非常通用的做法,一些例子如下:

https://github.com/elastic/elasticsearch-docker/tree/master/build/elasticsearch/bin
https://github.com/docker-library/postgres/tree/de8ba87d50de466a1e05e111927d2bc30c2db36d/10

7. 设置配置方法

每个应用都需要某种形式的参数化。你可以在以下两种中选:

1)应用特定配置文件:用这些配置文件记录格式、域、路径等等(当你的环境很复杂,各应用的技术栈差别很大的时候就不适合了)。

2)(操作系统)环境变量:简单而有效。

如果认为这些方法不现代或者是不受推崇,记住,这是 12因素的一部分。

https://12factor.net/config

这不是叫你扔掉你的配置文件,重构应用的配置机制。

只需要使用一个简单的 envsubst 命令替换掉配置模板(位于 docker-entrypoint.sh 中,因为是在运行时被执行)就行。

例子:

https://docs.docker.com/samples/library/nginx/#using-environment-variables-in-nginx-configuration

这就封装了应用的特定配置文件,将细节放到了容器中。

8. 外部化数据

记住黄金法则:不要在容器中存放持久化数据

容器的文件系统通常都是临时的,存活时间有限。所以用户产生的内容、数据文件、程序输出都应该保存到 容器卷绑定挂载 (就是将宿主机上的文件夹关联到容器中)中。

老实讲,对容器卷,我没有太多的使用经验,通常我采用 绑定挂载 方式来保存数据。绑定挂载中用到的文件路径是通过 配置管理工具 (如 Salt Stack)在前面经过 谨慎定义 后创建的。

提到 谨慎定义,我意思如下:

  1. 在基础系统上创建的用户和组为非特权的。
  2. 所有的绑定文件夹(-v)都以这个用户创建,并为其所有。
  3. 相应地给权限(只有这个用户和组有权限,其他用户没有)。
  4. 由这个用户来运行容器。
  5. 你能够完全掌控。

9. 确定能很好地处置日志

我意识到前面说的 “持久化数据” 远不是一个严瑾的定义,有些时候日志就这样掉进了灰色地带。你该如何处置它们?

如果你创建了一个应用,并且希望它严格遵守 docker 惯例,那么任何日志 文件 都不应该产生。应用应该把 stdoutstderr 作为 事件流 来输出。就像推荐你使用环境变量,这也来源于 12因素

https://12factor.net/logs

Docker 会截获所有你发到 stdout 的输出,然后可通过 “docker logs” 命令来访问:

https://docs.docker.com/engine/reference/commandline/logs/

尽管如此,还是有些实际情形处理起来会比较困难。假设你在运行 nginx 容器,会有两种类型的日志文件:

  • HTTP Access Logs
  • Error Logs

对于不同的结构,配置和现有的实现,在标准输出上管理它们可能并不是微不足道的。

在这里,就按上一节的描述来处置日志就行,另外确认你设置了日志轮替。

10. 轮替日志和其他追加型文件

如果你的应用可以无限地写日志或追加文件,那么你要操心一下文件轮替的问题。

注意设置数据的 存放 策略,这是避免服务器 磁盘空间 耗尽的关键(也是是否符合 GDPR 及其他数据规范的关键)。

如果使用绑定挂载,可以从基础操作系统获取一些帮助,用和本地轮替配置一样的工具,如 logrotate操作手册)。

下面是我最近找到的一个简单但是完整的例子:

https://www.aerospike.com/docs/operations/configure/log/logrotate.html

另一个比较好的:

https://www.digitalocean.com/community/tutorials/how-to-manage-logfiles-with-logrotate-on-ubuntu-16-04

作者在文中提到的每个链接都值得你打开来读一读。

分类: CODE

0 条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注