本文讲的是使用Systemd运行Docker容器, 【编者的话】现今Docker的每个主要发行版都在转移到Systemd上,使用Systemd运行Docker更有便于主机系统初始化以及进程管理等。但Systemd在监控容器上有一弊端,它不监控容器,而监控的是客户端,导致了若客户端与容器脱离联系后无论容器是否运行正常Systemd均会将该容器停掉等问题。但可以通过Systemd-Docker来解决。




你可以通过Shell脚本或者Docker Compose(目前它还不能用于生产环境)来运行Docker容器,但在一些用户场景下,你也可以使用主机系统初始化/进程管理。现在看起来,好像主流的Linux发行版本都准备使用Systemd,所以接下来我将深入介绍下Docker和Systemd。

如果你的一个非容器化的服务依赖于容器,那这个时候使用Systemd就可能是最好的解决方案。更有趣的是,纯容器应用的开发者也注意到Systemd的价值。最后提醒下大家,CoreOS就建立在Systemd和Docker之上。

『官方的Docker文档之使用Systemd』 中,你可以看到官方推荐手动创建Docker容器,并只在service文件中使用docker start以及docker stop。我不赞同这个建议,因为这使得主机之间的迁移配置或使用一个新容器重启服务更加困难----如果service文件包含了所有的依赖组件会更好。这是通过CoreOS的方法,我将在这博客展示一个案例。

我们以一个基于『Systemd』的Docker化的Redis为例来进行说明。例子中我将使用CentOS 7,我想其它的发行版也应该类似。你几乎需要以下所有的service file:

[Unit]
Description=Redis Container
After=Docker.service
Requires=Docker.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/Docker stop redis
ExecStartPre=-/usr/bin/Docker rm redis
ExecStartPre=/usr/bin/Docker pull redis
ExecStart=/usr/bin/Docker run --rm --name %n redis
[Install]
WantedBy=multi-user.target



这里需要注意的事情有:


  • 很明显,容器依赖于Docker的运行,因此Requires 这行。After这行也需要注意避免竞争条件。
  • 在我们启动容器之前,我们首先停掉并移除任何存在相同名称的容器,然后拉取最新的版本的镜像。以”-”开头的意味着如果命令执行失败,系统不会终止。
  • 这意味着我们的容器将每次都从头开始。如果你希望保存数据,你需要使用volumes或volume 容器,亦或改变code(如果存在),重启旧的容器。
  • 我们使用TimeoutStartSec=0实现永不超时,例如Docker pull可能需要一段时间。


如果你保存这文件到/etc/Systemd/system/Docker.redis.service并且执行systemctl start Docker.redis,Systemd将启动Redis 容器(记住,如果它需要拉取redis镜像这可能需要花一些时间)。那么我们就可以手动访问它,或另设一个依赖于它的服务。例如,如果我们有一个是运行在一个容器并且依赖于redis服务的应用foo,我们就可以使用以下服务文件:

[Unit]
Description=Foo Service
After=Docker.service
Requires=Docker.service
After=Docker.redis.service
Requires=Docker.redis.service
[Service]
TimeoutStartSec=0
Restart=always
ExecStartPre=-/usr/bin/Docker stop foo
ExecStartPre=-/usr/bin/Docker rm foo
ExecStartPre=/usr/bin/Docker pull foo
ExecStart=/usr/bin/Docker run --name foo \
--link Docker.redis.ervice:redis --rm\
foo
[Install]
WantedBy=multi-user.target


如果redis容器发生故障,Systemd将自动重启redis service和依赖foo的服务。



这配置工作花费大部分时间,这有一个主要问题。Systemd不会监控自身的容器,它监控的是客户端。无论出于何种原因如果客户端与容器脱离(比如网络问题),Systemd将停掉这个容器,即使它可能正常运行。相反,如果容器挂了但客户端仍正常运行,Systemd不会做任何操作。我们真正需要监控的是容器,而不是客户端。正好这有一个解决方法---Systemd-Docker。



System-Docker的工作原理是当它启动时通过包装Docker命令和移动容器进程到Systemd服务单元的cgroup上。我们的redis实例看起来是这样的:

[Unit]
Description=Redis
After=Docker.service
Requires=Docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=/usr/bin/Docker pull redis
ExecStart=/usr/local/bin/Systemd-Docker --cgroups name=Systemd run --rm --name %n redis
Restart=always
RestartSec=10s
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target

这是简单的,但有一个问题是,Systemd-Docker命令隐瞒事实,那就是具有相同名字的停止了的容器将被删除。我不得不添加--cgroups name=system 参数到存在这一问题的CentOS7和cgroups(其他发行版应该不需要这个参数)。如果你想尝试Systemd-Docker,注意,你当前必须从头构建,由于在我写本博文时,“go get”功能是不完整的提供给了Docker的一个依赖。



总之,如果你想使用Systemd,显然你需要在用之前思考得全面点。如果你只是使用一些工具不需要运行时超可靠的,这篇博文的的配置规则就可以了。否则你需要使用Systemd-Docker或解决监控容器的其他方法。



[1].在这种情况下运行,我们可以参照一个不需要修改或第三方工具的快速解决方案,请参阅下面问题的更多信息: 


• 

https://github.com/Docker/Docker/issues/7245

 

• 

https://github.com/Docker/Docker/pull/10427

  https://github.com/ibuildthecl ... es/25