第11章 一帆风顺——生产环境中的Docker以及运维上的考量

    本章主要内容

    • 记录容器日志输出的选项
    • 如何监控运行中的容器
    • 管理容器的资源使用
    • 使用Docker的能力来帮助管理传统系统管理员的任务

    本章中我们会讨论生产环境中会遇到的一些话题。在生产环境中运行Docker是一个很大的主题,而且Docker在生产中的用法还是一个发展中的领域。很多主要的工具还处于开发的早期阶段,就在我们写这本书的过程中都还在变化。例如,我们写本章的时候,Kubernetes更新到了1.0版本,Docker也发布了一个改版了的注册中心(registry)。

    本章中我们重点关注一些从动荡环境迁移到稳定环境时应该考虑的关键的事情。

    在生产环境中运行Docker首先要考虑的一点就是,如何追踪及测量容器所倚靠的事物。本节中我们会学到如何从运维的角度来考虑运行中的容器的日志活动及其性能。

    这是Docker生态系统中仍在发展的一个方面,但是某些工具和技术正在变得比其他的更为主流。我们将会谈及把应用程序的日志重定向到宿主机的syslog,把docker logs命令的输出重定向到一个集中的地方,以及Google公司的容器性能监控工具——cAdvisor。

    Linux发行版通常会运行一个syslog守护进程。这个守护进程是系统日志记录功能的服务器端——应用程序给这个守护进程发送消息,以及一些类似消息重要性这样的元数据,这个守护进程就会决定存储消息的场所(如果要存储的话)。很多应用程序,从网络连接管理器到内核本身,都在使用这个功能来在遇到错误的时候转储信息。

    正因为syslog是如此可靠且广泛使用,用户自己写的程序也应在它那里记录日志。但是,一旦用户对自己的程序进行了容器化,这种方法就行不通了(因为容器中默认没有syslog守护进程)。如果用户确实决定要在自己的所有容器中启动syslog守护进程,就要自己去每个容器里获取日志。

    问题

    想要在Docker宿主机上集中获取各个syslog。

    解决方案

    在宿主机上创建一个中心syslog守护进程容器,然后把syslog绑定挂载到一个集中的地方。

    讨论

    本技巧的基本思想就是要运行一个运行了syslog守护进程的服务容器,然后把日志接触点(/dev/log)通过宿主机的文件系统共享。日志本身可以通过查询syslog Docker容器来获得,并且存储在卷上。图11-1对此进行了说明。

    11

    图11-1 Docker容器中心化系统日志概览

    图11-1说明了宿主机文件系统上的/tmp/syslogdev是如何被运行其上的所有容器用作syslog活动的接触点的。日志容器挂载到该位置,并把syslog写入该位置。系统日志容器就收集全部的输入。

    syslog 守护进程是什么 syslog守护进程是运行在服务器上的一种进程,它会收集并管理发送到一个中心文件(通常是一个Unix域套接字)的所有消息。它一般会使用/dev/log作为接收日志消息的文件,并且把日志记录到/var/log/syslog。

    系统日志容器可以通过下面这个简单明了的Dockerfile来创建:

    1. FROM ubuntu:14.04
    2. RUN apt-get update && apt-get install rsyslog 1) 
    3. VOLUME /dev 2
    4. VOLUME /var/log 3)    
    5. CMD rsyslogd n 4

    (1)安装rsyslog包,以使rsyslogd守护进程程序可用。“r”代表reliable(可靠的)

    (2)创建/dev卷来与其他容器共享

    (3)创建/var/log卷来允许syslog文件持久保存

    (4)在启动时运行rsyslogd进程

    接下来构建容器,为其打上syslogger标签,并且运行它:

    1. docker build t syslogger
    2. docker run name syslogger d v /tmp/syslogdev:/dev syslogger

    把容器的/dev文件夹绑定挂载到宿主机的/tmp/syslogdev文件夹,然后就可以把/dev/log套接字作为一个卷挂载到每个容器上,马上就会看到效果了。这个容器会在后台继续运行,读取所有来自/dev/log文件的信息并且处理之。

    在宿主机上现在可以看到系统日志容器上的/dev 文件夹已经挂载到了宿主机的/tmp/syslogdev文件夹:

    1. $ ls -1 /tmp/syslogdev/
    2. fd
    3. full
    4. fuse
    5. kcore
    6. log
    7. null
    8. ptmx
    9. random
    10. stderr
    11. stdin
    12. stdout
    13. tty
    14. urandom
    15. zero

    在这个演示中,将要启动100个守护进程容器,它们会把自己的启动顺序使用logger命令按从0到100记录到syslog。然后就能在宿主机上运行docker exec来检查系统日志容器的syslog文件以查看这些消息了。

    首先,启动容器:

    1. for d in {1..100}
    2. do
    3.   docker run -d -v /tmp/syslogdev/log:/dev/log ubuntu logger hello$d
    4. done

    上述的卷挂载操作把容器的syslog端点(/dev/log)链接到了宿主机的/tmp/syslogdev文件,而该文件转而映射到了系统日志容器的/dev/log文件。通过这样的搭线,所有的syslog输出都会送到同一个文件。

    该操作完成的时候,我们会看到类似于下面这样的输出(本输出经过编辑):

    1. $ docker exec -ti syslogger tail -f /var/log/syslog
    2. May 25 11:51:25 f4fb5d829699 logger: hello
    3. May 25 11:55:15 f4fb5d829699 logger: hello_1
    4. May 25 11:55:15 f4fb5d829699 logger: hello_2
    5. May 25 11:55:16 f4fb5d829699 logger: hello_3
    6. […]
    7. May 25 11:57:38 f4fb5d829699 logger: hello_97
    8. May 25 11:57:38 f4fb5d829699 logger: hello_98
    9. May 25 11:57:39 f4fb5d829699 logger: hello_99

    如果用户希望,可以通过修改exec命令来归档这些syslog。例如,可以运行下面的命令来获取所有5月25日11时的文档并归档到一个压缩文件里:

    1. $ docker exec syslogger bash -c cat /var/log/syslog | grep ‘^May 25 11’” | \
    2. xz - > /var/log/archive/May25_11.log.xz

    应用程序必须记录日志到syslog 为了让这些消息在中心系统日志容器中出现,程序需要记录日志到syslog。我们在这里通过运行logger命令来确保这一点,然而你的应用程序也要执行一样的操作来使之工作。大多数现代的记录日志方法都有写入本地可见的syslog的手段。

    读者可能想知道,通过本技巧如何区分不同容器的日志消息。这里有两种选择:一是可以把应用程序的日志信息改为输出容器的宿主机名,二是可以了解下一个技巧让Docker来做这个粗重活。

    syslog驱动程序是另一回事 本技巧看起来和下一个使用Docker syslog驱动程序的技巧类似,但是它们不是一码事。本技巧让容器运行进程的输出作为docker logs命令的输出,但下一个技巧接管了logs命令,使本技巧显得多余。

    正如所见,Docker 提供了基本的日志系统来获取用户的容器的启动命令的输出。如果你是一名在不止一个宿主机上运行着多个服务的系统管理员,那么手工轮流在每个容器上执行docker logs命令来跟踪并获取日志操作起来可能很是烦琐。

    在本技巧中,我们会讲解一下Docker的日志驱动程序特性。这让我们能够使用标准的日志系统来追踪某个(乃至跨多个)宿主机上的很多服务。

    问题

    想要在Docker宿主机上集中获取docker logs的输出。

    解决方案

    启动一个日志驱动程序来在其他地方获取docker logs的输出。

    讨论

    在默认情况下,Docker日志是在Docker守护进程内部被捕获的,可以通过docker logs命令来查看。读者可能已经注意到了,这展示的是容器主进程的输出。

    编写本书时,Docker为重定向该输出提供了若干选择,包括:

    • syslog;
    • journald;
    • json-file。

    默认设置是json-file,但是其他两项也可以通过—log-driver命令来选择。syslog和journald选项会把日志输出发送到各自同名的守护进程。在https://docs.docker.com/engine/reference/logging/可以找到所有可用的日志驱动程序的官方文档。

    版本依赖 本技巧需要Docker 1.6.1或更高版本。

    syslog守护进程是运行在服务器上的一个进程,它会收集并管理发送到一个中心文件(通常是一个Unix域套接字)的所有消息。它一般会使用/dev/log作为接收日志消息的文件,并且把日志记录到/var/log/syslog。

    journald是一个收集并存储日志数据的系统服务。它为不同来源的日志创建并维护一个结构严密的索引。这些日志可以通过journalctl命令来查询。

    1.记录日志到syslog

    为了把输出定向到syslog,需要使用—log-driver标志:

    1. docker run log-driver=syslog ubuntu echo outputting to syslog
    2. outputting to syslog

    这会将输出记录在syslog文件中。如果有访问该文件的权限,可以通过标准Unix工具来检查这些日志:

    1. $ grep outputting to syslog /var/log/syslog
    2. Jun 23 20:37:50 myhost docker/6239418882b6[2559]: outputting to sysl
    2.记录日志到journald

    输出到journal守护进程看起来类似这样:

    1. $ docker run log-driver=journald ubuntu echo outputting to journald
    2. outputting to syslog
    3. $ journalctl | grep outputting to journald

    运行journal守护进程? 确保在运行前面的命令之前宿主机上有一个journal守护进程在运行。

    3.对所有的容器应用这些命令

    为宿主机上所有的容器应用这一参数可能会十分费力,因此设置Docker守护进程默认输出日志到这些受支持的机制。

    更改守护进程的/etc/default/docker,或者/etc/sysconfig/docker,或者用户自己的发行版设置的Docker配置文件。激活DOCKER_OPTS=””这一行,添加—log-driver标志。例如,如果这一行是

    1. DOCKER_OPTS=”—dns 8.8.8.8 dns 8.8.4.4

    就变为

    1. DOCKER_OPTS=”—dns 8.8.8.8 dns 8.8.4.4 log-driver syslog

    Docker中的配置变化 参见附录B以了解如何在宿主机上改变Docker守护进程的配置。

    如果重启了Docker守护进程,容器会记录日志到相关服务。

    另一个在这种情境下值得一提的通常做法(在这里未做讲解)是可以使用容器实现一个ELK(Elasticsearch, Logstash, Kibana)日志基础设施。

    破坏Docker日志命令 把守护进程的设置改成json-flie或者journald之外的任何东西都将意味着默认情况下标准的docker logs命令将不起作用了。此Docker守护进程的用户可能并不喜欢这个变化,尤其是/var/log/syslog文件(被syslog驱动程序使用)通常对非root用户来说是无法访问的。

    一旦在生产环境中有一系列的容器运行,那么用户可能会想要像多个进程运行在宿主机上一样能够监控它们的资源利用和性能。

    监控领域中(包括总体上来讲,以及考虑Docker)有众多候选的热门地带。之所以选择cAdvisor是因为它受众很广。它是一个由Google公司开源的快速流行起来的项目。如果之前用过传统的宿主机监控工具,如Zabbix或者Sysdig,那么值得看一下它们是否已经提供了所需的功能——编写本书时很多工具都在添加面向容器的功能。

    问题

    想要监控容器的性能。

    解决方案

    使用cAdvisor。

    讨论

    cAdvisor 是一个由Google公司开发的用来监控容器的工具。它在GitHub上开源:https:// github.com/google/cadvisor.。

    cAdvisor作为一个收集运行中的容器的性能数据的守护进程运行。在其他事情中,它会监测:

    • 资源隔离参数;
    • 历史资源使用;
    • 网络统计数据。

    cAdvisor可以在宿主机上原生安装或者作为Docker容器运行:

    1.  $ docker run \
    2.  —volume /:/rootfs:ro \ 1)   
    3.  —volume /var/run:/var/run:rw \ 2)    
    4.  —volume /sys:/sys:ro \ 3
    5.  volume /var/lib/docker/:/var/lib/docker:ro \ 4
    6.  -p 8080:8080 -d name cadvisor \ 5)    
    7.  —restart on-failure:10 google/cadvisor 6

    (1)给予cAdvisor对root文件系统的只读权限,以便它检测宿主机的信息

    (2)以读写权限挂载/var/run文件夹。大多数情况下,每个宿主机上应当运行一个cAdvisor实例

    (3)给予cAdvisor对宿主机的/sys文件夹的只读权限,其中包含内核子系统和挂载到宿主机的设备的信息

    (4)给予cAdvisor对Docker宿主机目录的只读权限

    (5)cAdvisor的网络接口在端口8080提供服务,我们在宿主机上也在同一端口发布。用来在后台运行容器并给容器取名的标准的Docker参数也会被使用

    (6)在失败时重启容器最多十次。这个镜像是用Google的账户存储在Docker Hub上的

    一旦启动了镜像,就可以通过浏览器访问http://localhost:8080以检查数据输出。这里有一些宿主机的信息,但是点击主页顶部的Docker Containers链接,可以通过点击Subcontainers标题下列出的容器检查CPU、内存和其他历史数据。

    容器运行时数据被收集并在内存中保存。在GitHub页面上有一个在InfluxDB里保存数据的文档。那个GitHub仓库里还有REST API的细节以及一个用Go语言编写的样例客户端程序。

    什么是InfluxDB InfluxDB是一个设计用来处理时间序列数据的轨迹的开源数据库。因此它对记录和分析实时提供的监控信息来说很理想。

    在生产环境里运行服务的一个中心问题是公平有效的资源分配。在底层,Docker使用了核心操作系统的cgroup概念来管理容器的资源使用。容器进行资源竞争的时候,默认使用简单均分算法。但是有时候这还不够。出于运维上的考虑,用户可能想保留或者限制某个容器或者某类容器的资源。

    本节中,我们会学习如何对容器的CPU和内存使用进行调优。

    默认情况下,Docker可以在机器的任意内核上运行。只有一个进程和线程的容器明显最多只能耗尽一个内核,但是容器中的多线程程序(或者多个单线程程序)可以使用 CPU 上所有的内核。如果有一个容器比其他容器都重要,用户可能想对这个行为进行修改——面向客户的程序每次都要在内部日常报告系统运行的时候争抢CPU可不太好。本技巧还可以用于防止失控的容器把用户挡在服务器的SSH之外。

    问题

    想要让容器有最小CPU分配额,对CPU消耗有硬性的限制,或者想要限制可以运行容器的内核。

    解决方案

    使用—cpuset-cpus选项来保留内核为容器所用。

    讨论

    在多核计算机上用户需要遵循本技巧以合理探索—cpuset-cpus选项。如果使用的是云机器可能遇不到这种情况。

    重命名了的标志—cpuset 老版本的Docker使用的是现在已经弃用的—cpuset标志。如果—cpuset-cpus不工作,可以试一下—cpuset

    我们将使用htop命令来看一下—cpuset-cpus选项的作用,这条命令会给出计算机内核使用的有用图形。请在继续之前确保这个命令已经装好——通常从系统包管理器以htop包的形式提供。或者,可以在一个以—pid=host选项启动的Ubuntu容器里安装它,这样就可以把宿主机的信息暴露给容器。

    如果现在运行htop,大概不会看到任何内核处于忙碌状态。在两个不同的终端里运行以下命令,以模拟多个容器内部的负载:

    1. docker run ubuntu:14.04 sh -c cat /dev/zero >/dev/null

    现在回头来看htop,可以看到有两个内核显示出 100%使用率。为了限制到一个内核上,docker kill之前的容器,然后在两个终端里运行以下命令:

    1. docker run cpuset-cpus=0 ubuntu:14.04 sh -c cat /dev/zero >/dev/null

    现在htop会显示出这些容器只使用了第一个内核。

    cpuset-cpus选项允许通过逗号分隔的列表(0,1,2)、范围(0-2)或者两者结合的方式(0-1,3)来指定多个内核。因此为宿主机保留CPU就是在给容器选范围的时候排除一个内核的事儿了。

    这个功能可以以多种方式使用。例如,可以通过不断分配剩余CPU给运行中的容器的方式,来为宿主机进程保留特定的CPU;也可以限制特定的容器运行在各自独立的CPU上,从而防止它们干扰其他容器所用的计算。

    宿主机上的容器一般在竞争的时候平分CPU使用。读者已经了解了如何做出绝对监管或者限制,但是那些有点儿太不灵活了。如果想让一个进程比其他进程使用更多的 CPU,一直为它保留整个内核就太浪费了。如果只有几个内核,这样做难免受限制。

    对于想把应用程序部署到共享服务器的用户,Docker 创造了多租户的便利条件。这可能会导致在有经验的虚拟机用户中有名的“吵闹的邻居”问题,一个用户耗尽了所有的资源,影响了在同一硬件上工作的其他虚拟机。

    一个具体的例子是,在写本书时,我们不得不采用这个功能来减少一个尤其贪得无厌的Postgres应用程序的资源占用,它耗尽了CPU周期,让网络服务器无法为终端用户服务。

    问题

    想要给重要的容器更多的CPU份额,或者把一些容器标记为不那么重要。

    解决方案

    docker run命令添加-c/—cpu-shares参数以定义CPU相对使用份额。

    讨论

    当一个容器启动起来的时候,它会得到一个 CPU 份额的数值(默认是 1024)。当只有一个进程在运行的时候,如果有必要它对CPU可以有100%的使用权,不管它有多少的CPU份额。只有当和其他容器竞争CPU的时候,这个数值才有用。

    设想我们有3台容器(A、B和C)同时都在试图使用所有可用的CPU资源:

    • 如果它们的CPU份额相等,那么每个都可以分配到1/3的CPU;
    • 如果A和B拿到512,C拿到1024,那么C获得CPU的一半,A和B各得1/4;
    • 如果A拿到10,B拿到100,C拿到1000,A拿到可用CPU资源的1%不到,并且只有在B和C空闲的时候才能做一些资源消耗较大的事。

    以上所有都假设容器可以使用机器上的所有内核(或者只有一个内核)。Docker会尽量把来自容器的负载分配到所有的内核上。如果有两个容器在一个双核机器上运行着单线程应用程序,那么明显无法在应用相对权重的同时最大化使用可用资源。每个容器都会分配到一个在其上执行的内核,不管权重是多少。

    如果想试一下,运行下面的命令:

    1. docker run cpuset-cpus=0 -c 10000 ubuntu:14.04 \
    2. sh -c cat /dev/zero > /dev/null &
    3. docker run cpuset-cpus=0 -c 1 -it ubuntu:14.04 bash

    现在看看在bash里执行操作是多么缓慢。注意,这些数值是相对的,可以把它们全部乘10(举例来说),它们代表的意思仍旧完全相同。然而,默认得到的仍然是 1024,所以当修改这些数值的时候,应当考虑一下在同一套CPU上运行一个没有指定份额的进程会怎么样。

    选择设置 为用例选择合适的CPU层级是一门艺术。值得看一下top和vmstat一类程序的输出,看看什么在用CPU时间。使用top的时候,按“1”键展示每个CPU各自在做什么尤其有用。

    运行容器的时候,Docker 会允许它从宿主机分配尽可能多的内存。通常这是我们想要的效果(也是与虚拟机相比的巨大优势,虚拟机分配内存的方式并不灵活)。但是有时候应用程序可能会脱离控制,分配了太多内存,当机器开始交换内存的时候就会死机。这很烦人,我们以前也发生了很多次。我们想要一种能限制容器内存消耗的方式来防止这件事。

    问题

    想要限制容器的内存消耗。

    解决方案

    docker run使用-m/—memory参数。

    讨论

    如果正在运行Ubuntu,很可能内存限制能力默认并没有启用。运行docker info来检查。如果输出中有一行在警告No swap limit support,就要先做些准备工作。注意,这些改变可能对机器上所有的应用程序都有性能影响,参见Ubuntu安装文档,获取更多信息(http://docs. docker.com/engine/installation/ubuntulinux/#adjust-memory-and-swap-accounting)。

    简单来说,需要在启动时就告诉内核,希望这些限制可用。为了达到这个目的,需要如下修改/etc/default/grub。如果GRUB_CMDLINE_LINUX已经有值了,在末尾加上新的:

    1. -GRUB_CMDLINE_LINUX=””
    2. +GRUB_CMDLINE_LINUX=”cgroup_enable=memory swapaccount=1

    现在需要运行sudo update-grub并重启计算机。运行docker info应该不会再得到警告了,现在可以继续正题了。

    首先,简单粗暴地展示一下使用4 MB的最低可能内存限制确实有用:

    1. $ docker run -it -m 4m ubuntu:14.04 bash 1)  
    2. root@cffc126297e2:/# \
    3. python3 -c open(“/dev/zero”).read(1010241024)’ 2
    4. Killed 3
    5. root@e9f13cacd42f:/# \
    6. A=$(dd if=/dev/zero bs=1M count=10 | base64) 4
    7. $ 5
    8. $ echo $? 6)    
    9. 137 7

    (1)以4 MB内存限制运行容器

    (2)试着把10 MB加载到内存

    (3)进程消耗了太多内存,因此被杀死

    (4)试着把10 MB的内存直接加载bash

    (5)bash也被杀死了,容器因而退出

    (6)检查退出码

    (7)退出码非零,表明容器因错退出

    这种限制有一个缺陷。为了展示这一点,我们要使用jess/stress镜像,这一镜像里面包含有stress,一个用来测试系统限制的工具。

    轻松压力测试 jess/stress是一个用来测试在容器上施加的资源限制的有用镜像。想要实验更多的话,用这个镜像试一下之前的技巧吧。

    如果运行下面的命令,你会惊讶地发现它立刻就不存在了:

    1. docker run -m 100m jess/stress vm 1 vm-bytes 150M vm-hang 0

    你已经让Docker限制容器到100 MB了,已经让stress占用150 MB了。可以通过运行下面的命令检查stress正在如期运行:

    1. docker top <container_id> -eo pid,size,args

    大小(size)那一栏是以KB为单位显示的,展示出容器确实占用了150 MB内存……问题来了,为什么它还没有被杀死呢!原来是Docker双重保留了内存,一半给物理内存一半用于内存交换。如果使用下面的命令,容器会立刻终止:

    1. docker run -m 100m jess/stress vm 1 vm-bytes 250M vm-hang 0

    这种双重保留机制是默认设置,可以通过—memory-swap参数来控制,该参数指定了总体虚拟内存的大小(内存+交换)。例如,想要完全消除交换内存的使用,应当把—memory—memory-swap设定为同一大小。在Docker运行参考中可以找到更多的例子:https://docs.docker. com/engine/reference/run/#user-memory-constraints。

    本节中我们要看一下Docker的一些令人惊奇的用法。尽管第一眼看上去可能会觉得奇怪,但Docker确实可以让cron作业管理更加轻松,还可以用作一种备份工具。

    什么是cron作业 cron作业是一个定时定期运行的命令,它由一个几乎所有Linux系统都作为服务包含的守护进程运行。每个用户可以指定自己要运行的命令的周期。它通常被一些系统管理员重度使用来运行一些周期性任务,如清理日志文件或者运行备份。

    我们在这里不可能穷尽它的可能用法,但是可以让读者对Docker的灵活性有一点儿感觉,并对其特性能够如何被用在意想不到的地方有所了解。

    如果读者在多个宿主机上管理过cron作业,那么可能遇到过这样的难题,要在不同地方部署同一套软件,还要确保crontab本身正确调用想要运行的程序。

    尽管这个问题有其他的解决方案(例如,使用Chef、Puppe、Ansible或者其他配置管理工具来管理跨宿主机的软件部署),但有一个选项是可以用Docker注册中心来存储正确的调用。

    虽然对于这个问题这并不总是最好的解决方案,但是它极好地展示出了有一个对应用程序运行时配置隔离又便携的存储是多么好。如果已经在用Docker了这还是免费的。

    问题

    想要cron作业能被集中管理和自动更新。

    解决方案

    把cron作业脚本作为Docker容器来拉取和运行。

    讨论

    如果有很多机器要定期运行作业,一般会使用crontab并手动配置(没错,还有人这么干),或者用一个类似Puppet或者Chef这样的配置管理工具。更新其配置脚本可以确保当机器的配置管理控制程序下次运行的时候这些变化都会应用到crontab上,为下次运行做好准备。

    什么是crontab crontab文件是一种由用户维护的特殊文件,它指定了脚本应该运行的次数。通常都是些维护任务,如压缩和归档日志文件,但是也可能是商业上很重要的应用程序,如信用卡付款结算程序。

    本技巧中,我们会展示如何通过从Docker注册中心中拉取镜像的方式取代这些把戏。

    如图11-2展示的一般情况,维护者更新配置管理工具,接下来在有代理(agent)运行的时候这些工具就被分发到服务器上。同时,系统更新时,cron作业运行着新旧代码。

    11

    图11-2 在CM代理定期运行中每个服务器都更新cron脚本

    在图11-3描绘的Docker场景下,在cron作业运行前每个服务器都去拉取了最新版的代码。

    到此为止读者可能会纳闷,如果已经有了能工作的解决方案,为什么要这么麻烦呢。把Docker作为交付机制有以下一些好处:

    • 当作业运行的时候,它会从中央地点把自己更新到最新版;
    • crontab文件变得更加简单,因为脚本和代码都在Docker镜像里封装了;
    • 对于更加庞大复杂的变化,只有Docker镜像变化中的差值需要拉取,加速了交付和更新的速度;
    • 不用在机器本身上维护代码或者二进制文件;
    • 可以把Docker和其他技术结合,如把输出定向到syslog,以简化并集中化对这些管理服务的管理。

    11

    图11-3 在每个cron作业运行前,每个服务器拉取最新的镜像

    在这个例子中,我们要使用在技巧40中创建的log_cleaner镜像。回忆一下,这个镜像封装了一个脚本,该脚本会清理服务器上的日志文件并且接收一个指定清理多少天的日志的参数。使用Docker作为交付机制的crontab大概看起来像下面这样:

    1. 00**\ 1) 
    2. docker pull dockerinpractice/log_cleaner && \ 2) 
    3. docker run \ 3
    4. -v /var/log/myapplogs:/log_dir dockerinpractice/log_cleaner 1

    (1)每天午夜运行

    (2)先拉取最新版本的镜像

    (3)运行日志清理程序清理一天的日志文件

    不熟悉 cron? 如果对 cron 不是很熟悉,想要了解如何编辑crontab的话,可以运行crontab -e。每行都让一条命令在行首的5个值指定的时间运行。在crontab的帮助手册可以找到更多信息。

    如果有故障发生,那么应当触发发送邮件的标准的cron机制。如果不信任这个,就用or操作符添加一条命令。接下来的例子中,我们假设读者定制的报警命令是my_alert_command

    1. 0 0 (docker pull dockerinpractice/log_cleaner && \
    2. docker run -d -v /var/log/myapplogs:/log_dir \
    3. dockerinpractice/log_cleaner 1) \
    4. || my_alert_command log_cleaner failed

    什么是or操作符 (本例中是双竖线||)确保了至少两边的命令有一条得到执行。如果第一个命令失败了(本例中,是在cron对时间的指定0 0 *之后括号中由与操作符&&连接起来的两条命令中的一条),那么第二条就会执行。

    ||操作符确保了如果日志清理作业的任一部分失败了,都会触发报警命令。

    如果读者运行过交易系统,那么就会知道,当有差错的时候,在出问题的时候推断系统状态的能力对于源头分析至关重要。

    通常它是通过一些方法的结合来达成的:

    • 应用程序日志分析;
    • 数据库法医学(确定在给定时间点的数据状态);
    • 构建历史分析(确定在给定的时间点,什么样的代码和配置在服务上运行过);
    • 生产系统分析(例如,有没有人登录并改动过什么东西)。

    对这样重要的系统,可以采取简单有效的备份Docker服务容器的办法。尽管可能数据库是和Docker基础设施分离的,配置、代码和日志的状态都可以通过几个简单的命令存储在注册中心里。

    问题

    想要保存Docker容器的备份。

    解决

    在容器运行的时候提交,然后作为一个单独的仓库推送。

    讨论

    遵循Docker最佳实践,利用一些Docker特性可以不用存储容器备份。例如,使用技巧90中的日志驱动程序而不是把日志记录到容器的文件系统里,意味着不用从容器备份里获取日志。

    但是,有时候用户不得不被迫做一些不喜欢做的事情,必须看一下容器是什么样的。接下来的命令展示了提交和推送备份容器的整个过程:

    1. DATE=$(date +%Y%m%d%H%M%S) (1)  
    2. TAG=”yourlog_registry:5000/live_pmt_svr_backup:$(hostname -s)${DATE}” (2)     
    3. docker commit -m=”${DATE}” -a=”Backup Admin live_pmt_svr $TAG 3)  
    4. docker push $TAG 4

    (1)产生一个记录到秒的时间戳

    (2)通过带有宿主机名和日期的标签产生一个指向你的注册中心URL的标签

    (3)以日期为消息,以Backup Admin为作者,提交容器

    (4)把容器推送到注册中心

    停止容器服务 本技巧会在容器运行时将它暂停,高效地停止它的服务。你的服务要么可以忍受中断,要么你应该以负载均衡的方式保持有其他可以处理请求的节点。

    如果在各个宿主机上交错执行这些操作,就会有一个高效的备份系统,并且有一个为支持工程师复原尽可能清晰的状态的方法。

    图11-4描述了这个过程的简化视图。

    11

    图11-4 双宿主机服务备份

    备份只会推送基础镜像和备份时容器的状态之间的差异,备份是交错进行的以确保至少有一台宿主机上的服务是运行的。注册中心服务器只存储基础镜像和每次提交时的差异,节省磁盘空间。

    和凤凰式部署结合

    用户可以更进一步把这个技巧和凤凰式部署模型结合起来。凤凰式部署是一个强调尽可能替换系统而不是就地升级部署的部署模型。它是很多Docker工具的中心原则。

    本例中,比起提交容器然后让它继续运行,可以按照如下做法:

    • 从注册中心拉取最新的镜像的副本;
    • 停止运行中的容器;
    • 开启新容器;
    • 提交,打标签,然后把旧的容器推送到registry。

    和这种方法结合,让你更加确定生产系统没有偏离源镜像。作者中的一位用这种方法来管理家庭服务器上的一个生产系统。

    本章中展示了很多在生产环境运行Docker会遇到的问题。和Docker的很多部分一样,这是个变化很快的领域,尤其是因为各类组织迁移工作负担的时候,还在不断发现在生产环境使用Docker的用例和痛点。

    我们讨论过的主要内容有:

    • 把容器中的日志捕获到宿主机的syslog守护进程;
    • 把Docker日志的输出捕获到宿主机级别的服务;
    • 使用cAdvisor监控容器的性能;
    • 限制容器对CPU、内核和内存资源的使用;
    • 一些Docker的奇妙用法,如cron交付工具和备份系统。

    现在我们讨论过了Docker在生产环境中可以做什么,应该做什么,接下来我们看一下当出错的时候如何调试Docker。