合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 第10章 配置管理 过去的10年里,配置管理(Configuration Management,CM)已然成为基础设施工具链中一个被广泛应用的工具,像Chef、Puppet和CFEngine这样的配置管理平台也成为了绝大多数服务器上的必备软件。伴随着企业的基础设施变得更加动态地去适配不同的像亚马逊或者Rackspace这些厂商提供的云服务,配置管理工具的地位也显得越加重要。一般来说,配置管理工具的目标是完成新上线服务器的配置和现有线上服务配置的更新等任务的标准化和自动化。配置管理的应用场景小到增加一个新用户,大到新建一个复杂的运行大数据服务的计算机集群。这些平台需要处理种类繁多的操作系统以及不同的系统版本、应用服务和各式各样的用例。随着时间的推移,这些配置管理工具在数据中心的地位变得越来越重要,基于它们实现的自动化配置任务也变得越来越复杂。从某种程度上来讲,容器技术的一大魅力正是在于它承诺可以通过面向容器的管理来避免配置管理带来的复杂性。 相对于物理机或者虚拟机(virtual machine,VM),容器化基础设施的配置管理则要简单得多。当尝试以配置变更发生频率的角度去看待配置管理本身的时候,不难发现这里面实际上存在着3种不同层级的配置变更,分别对应着不同等级的熵[\[1\]](part0016.xhtml#anchor101)。 最上面的一层是作为系统一部分的宿主机的数量,和宿主机的容量、配置以及它们之间的相互关系等。这类配置通常很少会发生变更,除非有硬件/虚拟机故障等突发情况,其余最常见的变更就是替换故障元件这样的日常事宜。 第二层便是宿主机本身的配置。这包括安装软件包、打补丁和编写配置文件。这类配置变更通常要比上一层频繁许多。 最后,更加常见的一层应该是运行在宿主机上的应用相关的配置变更。这里面包括bug的修复、性能调优、新功能的发布、新版本的迭代以及新的软件配置等。一般来说,现代基础设施绝大部分的配置变化均集中于此。 容器技术的应用会使得这3个层级之间变更频率的差异变得更大。在这种情况下,运行容器的所有宿主机看上去并没有什么变化,而且它们实际上也很少变动,反而是运行在每台宿主机上的容器会经常发生变化。 由于配置管理擅长的是装配宿主机,因而它们在新的容器化的世界里并没有多少用武之地。它们的作用也从负责配置整个系统转变成只是负责配置运行这些应用服务的基础设施,这包括从Docker宿主机的配置到Mesos集群的搭建等。 组建容器化的基础设施实际上是一项非常固定的、通用和重复的工作,以至于用户不得不重新考虑采用传统配置管理工具去做这些事情是否真的值得。毕竟这些配置管理工具本身是相当复杂的,而伴随着这种复杂性而来的是配置管理工具本身的学习成本、运维成本以及企业内部个性化定制的开销,如今因为Docker的出现,它们当初设计时的目标也已经远远超出现有实际的需求。 此外,由于绝大部分所需的配置变更均转移到了Docker容器这一层,传统的配置管理也变得没有那么关键。如果基础设施层面没有发生变更,用户便无需再借助配置管理工具来推送这些服务的新版本或者新配置。 尽管配置管理在容器环境下已经变得不那么核心,但它仍然是十分重要的,而更关键的问题在于用户很有可能无法工作在一个全容器的环境里,这便导致用户仍然需要配置管理,因此将容器整合进配置管理就变得十分有必要。 配置管理在以下3个方面能帮到Docker用户。 (1)配置和维护Docker宿主机。这包括从带有基础操作系统的新硬件的上线到Docker服务的安装和配置,以及确保这些宿主机上安装了最新的安全补丁。这样一来就使得用户能够快速地配置新宿主机来扩容,同时以集中式的管理方式维护Docker主机的容器集群。 (2)Docker容器和Docker镜像的管理。这涵盖了从容器镜像的整个生命周期(镜像的创建、推送等)到实际运行这些镜像的容器的运行和管理。 (3)构建镜像。虽然Docker提供了通过Dockerfile来构建镜像的简单方式,但是很多时候在容器里运行的软件的大部分还是需要通过配置管理指令集来安装和配置的。用户可以绕过Dockerfile,直接使用在虚拟机上安装这些软件的方式在Docker容器上安装这些软件。 在配置管理工具提供的3个功能中,最为Docker用户所熟知的是第一个——配置Docker宿主机。对事先没有建立一套完备的配置管理的用户而言,另外两个也许并没有多大的吸引力,毕竟它们跟Docker原生提供的相差无几。 在本节中,我们将探讨如何使用一些主流的配置管理系统来集成Docker的支持。 Chef官方为我们提供了专门的[docker-chef cookbook](https://github.com/chef-cookbooks/docker)来完成Docker的宿主机安装和镜像及容器的管理。该cookbook对于不同的Docker宿主机配置参数(存储引擎和守护进程的启动参数等)以及各种宿主机操作系统的支持已经相当完备。 使用Chef在宿主机上安装Docker是一件再轻松不过的事情,只需要在你的cookbook里增加下面这行代码: `include_recipe 'docker'`基于Chef的镜像和容器的管理同样简洁明了,还可以非常简便地映射到对应的`docker`命令。例如,拉取一个镜像可以通过如下的资源轻松搞定: `docker_image 'nginx'`上述指令将完成最新Nginx镜像的下载。如果想拉取一个指定版本的镜像,可以这样做: ``` docker_image 'nginx' do tag '1.9.1' end ``` 使用Chef从镜像实例化容器同样也不是一件难事。例如,以下命令即实现了发起一个运行前面的`nginx`镜像的容器实例,开放对应80端口的访问权限并且挂载一个宿主机本地目录到对应容器内部的`/www`: ``` docker_container 'nginx' do detach true port '80:80' volume '/mnt/docker:/www' end ``` 最后,还可以使用与原生Docker完全相同的方式来操作容器。如下指令即等同于运行一个`commit`命令来将现有的容器提交为一个新镜像,并且命名为`my-company/nginx:my-new-version`: ``` docker_container 'nginx' do repository 'my-company' tag 'my-new-version' action :commit end ``` 不只是上述提到的这些,实际上,docker-chef cookbook还提供了更多对Docker功能方面的支持,它们分别对应着现有Docker的一些原生功能,像push、cp和export等,而一般它们的设计主旨也和原生Docker的基本保持一致,甚至于说它们可以非常工整地对应到原生Docker提供的各种功能。 Chef还支持通过标准的配置基础设施的方式来构建镜像。它们所提供的就是[Chef容器](https://github.com/chef/chef-container),一个安装了Chef客户端并且将镜像本身配置运行在一个以`runit`作为进程管理程序的完整操作系统里的Docker镜像。运行这个镜像的容器将会连接到Chef服务器上并根据事先定义好的cookbook来完成自身的配置。这样一来便提供了一个很好的从虚拟机/物理机到Docker的过渡,它允许用户使用一套相同的cookbook来配置不同的虚拟机、容器或是物理宿主机。 Chef社区还有很多其他的cookbook用来完成Docker生态圈的其他组件的自动化配置,包括服务发现(`etcd`、`consul`等)、调度器和像Mesos及Kubernetes这样的资源管理组件。Docker官方还提供了一个Ruby版本的docker-api,这使得Chef的recipe可以通过它的API直接与Docker守护进程交互。 综上所述,如果你已经是Chef用户,你也许会想探索一下Chef究竟能为容器做些什么。Chef可以提供的是一个简单直接的从现有基础设施到容器的迁移方案,而一旦进入Docker世界,如何使用Chef则很大程度上取决于用户所处的环境(例如,用户所在的企业有许多其他的用户),以及用户接受Docker哲学的程度,Docker的理念正是推崇从虚拟化迁移到单进程容器为主的微服务架构。 与Chef类似,Ansible同样为Docker提供了从宿主机配置到镜像和容器管理的支持。 Ansible本身在配置管理方面和Docker十分契合,它们都推崇简单、直接的做事方式。因此,当Chef和Puppet纷纷选择“客户端运行在主机(及容器)上并主动向服务器拉取自己所需的配置变更”这样一套主从架构(当然,它们也正在尝试支持独立客户端)时,Ansible采取了一种更为直接的方式,即通过SSH将配置从远程主机推送到目标机器上。坦白讲,这种方法和之前的客户端模型相比,与Docker的契合度更高。 使用Ansible在宿主机上安装Docker同样是一件非常简单的事情。尽管Ansible官方不直接提供专用的playbook,但读者可以参考Paul Durivage发布在GitHub中的用来在Ubuntu上安装[Docker的docker.ubuntu](https://github.com/angstwad/docker.ubuntu)的做法。例如,添加如下指令到你的Ansible playbook,即可实现在宿主机上安装Docker并且监听7890端口: ``` - name: Install Docker on Port hosts: all roles: - role: angstwad.docker_ubuntu docker_opts: "-H tcp://0.0.0.0:7890" kernel_pkg_state: present ``` Ansible同样提供了对Docker宿主机管理的官方支持——[Docker模块](http://docs.ansible.com/docker_module.html)。通过调用这个模块,可以实现对Docker镜像的管理以及对容器的创建、启动、停止和销毁,这同直接使用Docker几乎没什么两样。当涉及容器方面的操作时,用户也可以设置一些重启策略,这样一来,在容器发生故障时,Ansible便知道该如何去应对。Ansible的Docker模块在多容器管理方面同样提供了不错的支持。例如,用户可以轻松定位到所有运行同一镜像的容器然后通过Ansible来完成一键重启。 用户也可以直接在自己的playbook里编写对应的Docker操作。例如,用户可以在自己的playbook里追加如下指令,如此一来,Ansible便会先下载好`myimage:1.2.3`镜像,然后再创建一个名为`mycontainer`的容器,接着它会在`/usr/data`里挂载一个卷,并运行这个下载好的镜像: ``` - name: data container docker: name: mycontainer image: myimage:1.2.3 state: present volumes: - /usr/data command: myservice --myparam myvalue state: started expose: - 1234 ``` 这里面其他参数的含义是显而易见的。容器将会在启动时运行一个`myservice`的命令,配上对应的运行参数`--myparam myvalue`。然后该容器还会对外公开`1234`端口。 用户也可以重启一台主机上现有运行同一镜像的容器。例如,可以通过下面这些指令去重启所有运行`myimage:1.2.3`镜像的容器实例: ``` - name: restart myimage:1.2.3 docker: image: myimage:1.2.3 state: restarted ``` 另外,Ansible对于用户的Docker基础设施的另一大助益便是用户可以借助其playbook来完成镜像的构建。为了达成这个目的,用户需要编写一个Dockerfile,它会在本地安装Ansible并且将需要运行的playbook复制到容器里然后运行: ``` FROM ubuntu # 安装 Ansible RUN apt-get -y update RUN apt-get install -y python-yaml python-jinja2 git RUN git clone http://github.com/ansible/ansible.git /usr/lib/ansible WORKDIR /usr/lib/ansible ENV PATH /usr/lib/ansible/bin:/sbin:/usr/sbin:/usr/bin ENV ANSIBLE_LIBRARY /usr/lib/ansible/library ENV PYTHONPATH /usr/lib/ansible/lib:$PYTHON_PATH # 下载并复制 playbooks 和 hosts ADD playbooks /usr/lib/ansible-playbooks ADD inventory /etc/ansible/hosts WORKDIR /usr/lib/ansible-playbooks # 运行 playbook ,用 Ansible 配置镜像 RUN ansible-playbook my-playbook.yml -c local # 其他Docker配置 EXPOSE 22 4000 ENTRYPOINT [“myservice”] ``` 在上述Dockerfile里,Ansible将去执行一个事先已经复制到镜像的指定playbook(这里是my-playbook),用户可以把所有的配置任务都放到这里面来,Ansible会负责接下来的所有事情。如果在用户的基础设施里已经有了很多现成的playbook,用户完全可以很方便地继续使用它们来配置相应的容器而无需再烦恼是否应该直接用Dockerfile来重新实现这些自动化配置任务。 Ansible官方的Docker模块同样也提供了镜像管理方面的支持,如上传镜像、拉取镜像、删除和下载镜像等。值得一提的是,上述绝大部分的功能也可以很轻松地通过Ansible直接运行Docker命令行来实现。 Salt Stack于`2014.7.0`版本完成了对Docker的功能支持,这其中不仅包括常见的Docker操作,还加入了从Docker获取信息到Salt Mine的支持。 Salt通过`DOCKERIO`模块来管理Docker容器。有了它,用户可以很方便地定义一些自己所需的Docker State,而Salt Minion将会负责和Docker交互,并实现用户期许的配置状态。例如,如果用户想实现这样的一个配置状态:创建一个运行`myorg/myimage:1.2.3`镜像,名字叫做`mycontainer`的容器。那么可以根据所需定义如下的state: ``` my_service: docker.running: - container: mycontainer - image: myorg/myimage:1.2.3 - port_bindings: "5000/tcp": HostIp: "" HostPort: "5000" ``` 其他的Docker操作与上述类似。与Ansible类似,用户同样也可以非常简便地使用Salt Stack调用Docker的命令行来操作Docker。例如,如下内容大致等价于前面的state: ``` my_service: cmd.run: - name: docker run -p5000 --name mycontainer myorg/myimage:1.2.3 ``` 用户可以使用Puppet的一个由Gareth Rushgrove提供的已然相当完备的[Docker模块](https://forge.puppetlabs.com/garethr/docker/readme)来完成Docker宿主机、镜像以及容器的安装和管理等工作。如果用户已经在用Puppet,那么通过这个模块来操作Docker将是一件再简单不过的事情。 使用该模块安装Docker非常简单,这一功能初步在Ubuntu 12.04和14.04以及Centos6.6和7.0测试通过,当然它也可能未经修改便可以直接在其他Debian或者RHEL派系的Linux发行版上正常运行。以下便是如何在宿主机上安装最新版Docker的详细指令: ``` include 'docker' class { 'docker': version => 'latest', } ``` 该安装类提供了很多参数选项。例如,用户可以修改Docker监听的端口,或者去定义它的套接字具体的路径: ``` class { 'docker': version => 'latest', tcp_bind => 'tcp://127.0.0.1:4243', socket_bind => 'unix:///var/run/docker.sock', } ``` 一旦完成了Docker的安装,管理它的镜像及容器自然也是水到渠成的事情。例如,我们可以通过如下指令来拉取所需的镜像: ``` docker::image { 'myorg/myimage': image_tag => '1.2.3' } ``` 还可以轻松地将它删除: ``` docker::image { 'myorg/myimage': ensure => 'absent', image_tag => '1.2.3' } ``` 从镜像运行一个容器也不再是一件困难的事情: ``` docker::run { 'myservice': image => 'myorg/myimage:1.2.3', command => 'myservice --myparam myvalue', } ``` `docker::run`提供的很多配置选项都能够很好地直接对应到原生Docker `run`的选项,像公开的服务端口、环境变量、重启策略等。另外,它还补充了一些Docker原生所不具备的、额外的配置参数,像容器的依赖关系等。这些依赖关系会被编码到initd或者systemd[\[2\]](part0016.xhtml#anchor102)。 最后,这个模块还提供了一个很贴心的功能,那便是用户可以直接在正在运行的容器里执行`exec`命令: ``` docker::exec { 'myservice-ls': detach => true, container => 'myservice', command => 'ls', tty => true, } ``` 时下的配置管理工具提供了对Docker一定层面上的支持。然而,这些功能是否足够满足需求又或者说使用这些工具所带来的价值是否抵得上它们的投入成本都取决于用户所处的具体环境。当然,这些工具也为已经使用它们来管理虚拟机的企业提供了一些过渡到容器的解决方案。 Docker的存储引擎为其提供了优质的性能体验,第11章我们将就此展开讨论。 - - - - - - [\[1\]](part0016.xhtml#ac101) 这里的熵指的是基础设施的配置变化程度的度量。——译者注 [\[2\]](part0016.xhtml#ac102) 即容器启动时便会根据这个依赖关系顺序启动。——译者注