Docker使用小贴士
程序员文章站
2022-03-19 14:27:43
...
摘要:来自Tutum的John Regan在本地Linux系统和云平台中都安装使用过Docker,在使用1年多的时间中,他总结了5条非常实用的经验,包括Docker经验镜像制作、Dockerfile、子进程以及容器和进程的对应关系等。
使用Docker已经一年多了,在本地Linux系统和云平台上都安装使用过。在那个时候就学会了很多关于管理镜像、灵活地为任何平台创建镜像的方法,学会写一些和Docker不相关的自己的程序。我试着把自己的经验总结为以下五点,为那些刚开始使用Docker的人提供参考。
制作镜像时需要特别明确
我尝试不用root用户运行我的应用。大多数Linux发行版都有一个优点:当你安装一个服务时,操作系统会通常会为你创建一个相对应的系统用户。例如,当Apache安装的时候,几乎每个发行版本都会新建某类型的http,apache或者www-data用户。
我打算通过源代码来构建 一个ejabberd镜像,并且在build脚本中会创建xmpp系统用户。和大多数人一样,我使用了Docker的自动构建服务,并设置当源镜像Ubuntu更新时系统会自动重建我的镜像。
不过我在自己的镜像上犯了一个错误:镜像源部分被我设置成了Ubuntu而不是Ubuntu:12.04。但是有一天镜像自动更新到了最新的Ubuntu14.04,还增加了一个新的默认系统用户。这导致了我的应用程序的UID加1。我取回最新build的ejabberd镜像,但它在启动时失败了,因为我用volume存储ejabbers文件,而ejabberd用户没有该文件的读取权限。
现在我在创建镜像时会做两件事情:
1. 在build每一个镜像时,都加上具体的版本号;
2. 为所有的应用写启动脚本。
这些启动脚本通常以root用户执行,做如下的操作:首先最重要的是确认所需的配置文件存在——因为这些配置文件有可能会因为使用volume而根本不存在。然后把配置文件和数据文件的所有者设置为我运行应用的用户。
这会节省很多时间,我非常清楚我的镜像是基于什么,我不仅仅使用我的主程序当作镜像的入口点,我还写了一些脚本确保环境是合理的。
我无从知道某个人的系统会有哪些功能
直到最近,我才在Ubuntu系统上运行了最新版本的Docker。当在一些云平台上运行时,发现以下问题:
我改变了制作Docker镜像的习惯:我再也不这样写说明:“你要用—volumes-from运行一个容器”或者“这需要link一个名为DB的容器”。我不知道其他用户如何使用我的镜像,我试着让我的镜像尽可能灵活。比如,如果镜像需要Mysql数据库,我会用另一个container,然后和它link起来,或着一个环境变量设置链接地址等等。
虽然这增加了工作量,但是我认为这非常值得。现在,我可以做各种“古怪”的事情,比如运行一个代理MySQL容器连接到一个实际的MySQL数据库,并在连接时使用特定的主机名。这样非常整洁。
刚开始使用DockerFile时可能比较痛苦,时间长了你会爱上它们
有两种方法创建镜像。一种方式很简单:生成一个容器并安装所需要的程序,一旦这个容器按照预想的方式运行,把该容器提交成一个镜像。
另一种方式是Dockerfile,操作起来可能会有点痛苦。当安装包提示我输入信息的时候我该怎么办?我怎么非交互式的修改文件?这是一个棘手的过程,因为没有办法使得其中的一部分是交互的,所有步骤必须全都是全自动完成的。
尽管如此,但我仍觉得使用Dockerfile会利大于弊。通过第一种方式,很多时候我并不能确定程序是否在按照我的期望运行,而且不能确切的记得我对这个镜像到底做过了什么。而通过Dockerfile这种方式,能够记录我的所有操作步骤,更妙的是我还可以使用版本控制软件对其进行管理。虽然使用Dockerfile建立镜像时需要更多的工作,但是它是我现在建立镜像唯一使用的方法。
生成子进程时要慎重(不管是不是在使用Docker)
一个应用程序创建一个子进程是很常见的,我自己一直也是这么用的。在绝大多数的系统中,我可以创建一个子进程,读取它的输出,退出时检查返回值等等,然后待程序结束后,交由init进程进行资源回收。这么多年来,我都一直这么写程序,从来没考虑过其他的情况。
但是对于Docker容器来说,却不是这么简单。在多数情况下,任何创建的子进程都将会成为僵尸进程消耗系统资源。我知道在我自己的程序中如何正确的监控并销毁一个子进程,所以其他人把我程序打包在镜像中没有任何问题。但是如果一个没有正确销毁的子进程的程序被打包成镜像,就会造成很多麻烦。
每个任务对应一个容器不意味着一个容器只有一个进程
这一点上存在一些争论。大多数人都同意每个容器应该只执行一个任务,但是对于一个容器是否只对应一个进程,大家都有不同的看法。
我倾向于只要我决定了运行什么任务,就运行一个监督进程(像supervidord,runit,s6),这对于web应用非常有意义。
例如我有一个web应用需要PHP-FPM,Nginx, Cron,和mysql,我会在一个容器内运行PHP-FPM+Nginx+Cron,在另外一个容器中运行Mysql。同时保证一个主要进程退出或者崩溃时,监督进程也要随着退出。当主进程退出时可以保留Docker正常的退出行为。
使用Docker已经一年多了,在本地Linux系统和云平台上都安装使用过。在那个时候就学会了很多关于管理镜像、灵活地为任何平台创建镜像的方法,学会写一些和Docker不相关的自己的程序。我试着把自己的经验总结为以下五点,为那些刚开始使用Docker的人提供参考。
制作镜像时需要特别明确
我尝试不用root用户运行我的应用。大多数Linux发行版都有一个优点:当你安装一个服务时,操作系统会通常会为你创建一个相对应的系统用户。例如,当Apache安装的时候,几乎每个发行版本都会新建某类型的http,apache或者www-data用户。
我打算通过源代码来构建 一个ejabberd镜像,并且在build脚本中会创建xmpp系统用户。和大多数人一样,我使用了Docker的自动构建服务,并设置当源镜像Ubuntu更新时系统会自动重建我的镜像。
不过我在自己的镜像上犯了一个错误:镜像源部分被我设置成了Ubuntu而不是Ubuntu:12.04。但是有一天镜像自动更新到了最新的Ubuntu14.04,还增加了一个新的默认系统用户。这导致了我的应用程序的UID加1。我取回最新build的ejabberd镜像,但它在启动时失败了,因为我用volume存储ejabbers文件,而ejabberd用户没有该文件的读取权限。
现在我在创建镜像时会做两件事情:
1. 在build每一个镜像时,都加上具体的版本号;
2. 为所有的应用写启动脚本。
这些启动脚本通常以root用户执行,做如下的操作:首先最重要的是确认所需的配置文件存在——因为这些配置文件有可能会因为使用volume而根本不存在。然后把配置文件和数据文件的所有者设置为我运行应用的用户。
这会节省很多时间,我非常清楚我的镜像是基于什么,我不仅仅使用我的主程序当作镜像的入口点,我还写了一些脚本确保环境是合理的。
我无从知道某个人的系统会有哪些功能
直到最近,我才在Ubuntu系统上运行了最新版本的Docker。当在一些云平台上运行时,发现以下问题:
- 有些人可能没有运行最新的Docker;
- 有些人可能没有开启Docker的所有功能;
- 有些人没有正在运行Docker的系统的root用户权限;
我改变了制作Docker镜像的习惯:我再也不这样写说明:“你要用—volumes-from运行一个容器”或者“这需要link一个名为DB的容器”。我不知道其他用户如何使用我的镜像,我试着让我的镜像尽可能灵活。比如,如果镜像需要Mysql数据库,我会用另一个container,然后和它link起来,或着一个环境变量设置链接地址等等。
虽然这增加了工作量,但是我认为这非常值得。现在,我可以做各种“古怪”的事情,比如运行一个代理MySQL容器连接到一个实际的MySQL数据库,并在连接时使用特定的主机名。这样非常整洁。
刚开始使用DockerFile时可能比较痛苦,时间长了你会爱上它们
有两种方法创建镜像。一种方式很简单:生成一个容器并安装所需要的程序,一旦这个容器按照预想的方式运行,把该容器提交成一个镜像。
另一种方式是Dockerfile,操作起来可能会有点痛苦。当安装包提示我输入信息的时候我该怎么办?我怎么非交互式的修改文件?这是一个棘手的过程,因为没有办法使得其中的一部分是交互的,所有步骤必须全都是全自动完成的。
尽管如此,但我仍觉得使用Dockerfile会利大于弊。通过第一种方式,很多时候我并不能确定程序是否在按照我的期望运行,而且不能确切的记得我对这个镜像到底做过了什么。而通过Dockerfile这种方式,能够记录我的所有操作步骤,更妙的是我还可以使用版本控制软件对其进行管理。虽然使用Dockerfile建立镜像时需要更多的工作,但是它是我现在建立镜像唯一使用的方法。
生成子进程时要慎重(不管是不是在使用Docker)
一个应用程序创建一个子进程是很常见的,我自己一直也是这么用的。在绝大多数的系统中,我可以创建一个子进程,读取它的输出,退出时检查返回值等等,然后待程序结束后,交由init进程进行资源回收。这么多年来,我都一直这么写程序,从来没考虑过其他的情况。
但是对于Docker容器来说,却不是这么简单。在多数情况下,任何创建的子进程都将会成为僵尸进程消耗系统资源。我知道在我自己的程序中如何正确的监控并销毁一个子进程,所以其他人把我程序打包在镜像中没有任何问题。但是如果一个没有正确销毁的子进程的程序被打包成镜像,就会造成很多麻烦。
每个任务对应一个容器不意味着一个容器只有一个进程
这一点上存在一些争论。大多数人都同意每个容器应该只执行一个任务,但是对于一个容器是否只对应一个进程,大家都有不同的看法。
我倾向于只要我决定了运行什么任务,就运行一个监督进程(像supervidord,runit,s6),这对于web应用非常有意义。
例如我有一个web应用需要PHP-FPM,Nginx, Cron,和mysql,我会在一个容器内运行PHP-FPM+Nginx+Cron,在另外一个容器中运行Mysql。同时保证一个主要进程退出或者崩溃时,监督进程也要随着退出。当主进程退出时可以保留Docker正常的退出行为。
上一篇: dubbo 配置中心源码分析(一)