# 第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) 即容器啟動時便會根據這個依賴關系順序啟動。——譯者注
- 版權信息
- 版權聲明
- 內容提要
- 對本書的贊譽
- 譯者介紹
- 前言
- 本書面向的讀者
- 誰真的在生產環境中使用Docker
- 為什么使用Docker
- 開發環境與生產環境
- 我們所說的“生產環境”
- 功能內置與組合工具
- 哪些東西不要Docker化
- 技術審稿人
- 第1章 入門
- 1.1 術語
- 1.1.1 鏡像與容器
- 1.1.2 容器與虛擬機
- 1.1.3 持續集成/持續交付
- 1.1.4 宿主機管理
- 1.1.5 編排
- 1.1.6 調度
- 1.1.7 發現
- 1.1.8 配置管理
- 1.2 從開發環境到生產環境
- 1.3 使用Docker的多種方式
- 1.4 可預期的情況
- 為什么Docker在生產環境如此困難
- 第2章 技術棧
- 2.1 構建系統
- 2.2 鏡像倉庫
- 2.3 宿主機管理
- 2.4 配置管理
- 2.5 部署
- 2.6 編排
- 第3章 示例:極簡環境
- 3.1 保持各部分的簡單
- 3.2 保持流程的簡單
- 3.3 系統細節
- 利用systemd
- 3.4 集群范圍的配置、通用配置及本地配置
- 3.5 部署服務
- 3.6 支撐服務
- 3.7 討論
- 3.8 未來
- 3.9 小結
- 第4章 示例:Web環境
- 4.1 編排
- 4.1.1 讓服務器上的Docker進入準備運行容器的狀態
- 4.1.2 讓容器運行
- 4.2 連網
- 4.3 數據存儲
- 4.4 日志
- 4.5 監控
- 4.6 無須擔心新依賴
- 4.7 零停機時間
- 4.8 服務回滾
- 4.9 小結
- 第5章 示例:Beanstalk環境
- 5.1 構建容器的過程
- 部署/更新容器的過程
- 5.2 日志
- 5.3 監控
- 5.4 安全
- 5.5 小結
- 第6章 安全
- 6.1 威脅模型
- 6.2 容器與安全性
- 6.3 內核更新
- 6.4 容器更新
- 6.5 suid及guid二進制文件
- 6.6 容器內的root
- 6.7 權能
- 6.8 seccomp
- 6.9 內核安全框架
- 6.10 資源限制及cgroup
- 6.11 ulimit
- 6.12 用戶命名空間
- 6.13 鏡像驗證
- 6.14 安全地運行Docker守護進程
- 6.15 監控
- 6.16 設備
- 6.17 掛載點
- 6.18 ssh
- 6.19 私鑰分發
- 6.20 位置
- 第7章 構建鏡像
- 7.1 此鏡像非彼鏡像
- 7.1.1 寫時復制與高效的鏡像存儲與分發
- 7.1.2 Docker對寫時復制的使用
- 7.2 鏡像構建基本原理
- 7.2.1 分層的文件系統和空間控管
- 7.2.2 保持鏡像小巧
- 7.2.3 讓鏡像可重用
- 7.2.4 在進程無法被配置時,通過環境變量讓鏡像可配置
- 7.2.5 讓鏡像在Docker變化時對自身進行重新配置
- 7.2.6 信任與鏡像
- 7.2.7 讓鏡像不可變
- 7.3 小結
- 第8章 存儲Docker鏡像
- 8.1 啟動并運行存儲的Docker鏡像
- 8.2 自動化構建
- 8.3 私有倉庫
- 8.4 私有registry的擴展
- 8.4.1 S3
- 8.4.2 本地存儲
- 8.4.3 對registry進行負載均衡
- 8.5 維護
- 8.6 對私有倉庫進行加固
- 8.6.1 SSL
- 8.6.2 認證
- 8.7 保存/載入
- 8.8 最大限度地減小鏡像體積
- 8.9 其他鏡像倉庫方案
- 第9章 CI/CD
- 9.1 讓所有人都進行鏡像構建與推送
- 9.2 在一個構建系統中構建所有鏡像
- 9.3 不要使用或禁止使用非標準做法
- 9.4 使用標準基礎鏡像
- 9.5 使用Docker進行集成測試
- 9.6 小結
- 第10章 配置管理
- 10.1 配置管理與容器
- 10.2 面向容器的配置管理
- 10.2.1 Chef
- 10.2.2 Ansible
- 10.2.3 Salt Stack
- 10.2.4 Puppet
- 10.3 小結
- 第11章 Docker存儲引擎
- 11.1 AUFS
- 11.2 DeviceMapper
- 11.3 BTRFS
- 11.4 OverlayFS
- 11.5 VFS
- 11.6 小結
- 第12章 Docker 網絡實現
- 12.1 網絡基礎知識
- 12.2 IP地址的分配
- 端口的分配
- 12.3 域名解析
- 12.4 服務發現
- 12.5 Docker高級網絡
- 12.6 IPv6
- 12.7 小結
- 第13章 調度
- 13.1 什么是調度
- 13.2 調度策略
- 13.3 Mesos
- 13.4 Kubernetes
- 13.5 OpenShift
- Red Hat公司首席工程師Clayton Coleman的想法
- 第14章 服務發現
- 14.1 DNS服務發現
- DNS服務器的重新發明
- 14.2 Zookeeper
- 14.3 基于Zookeeper的服務發現
- 14.4 etcd
- 基于etcd的服務發現
- 14.5 consul
- 14.5.1 基于consul的服務發現
- 14.5.2 registrator
- 14.6 Eureka
- 基于Eureka的服務發現
- 14.7 Smartstack
- 14.7.1 基于Smartstack的服務發現
- 14.7.2 Nerve
- 14.7.3 Synapse
- 14.8 nsqlookupd
- 14.9 小結
- 第15章 日志和監控
- 15.1 日志
- 15.1.1 Docker原生的日志支持
- 15.1.2 連接到Docker容器
- 15.1.3 將日志導出到宿主機
- 15.1.4 發送日志到集中式的日志平臺
- 15.1.5 在其他容器一側收集日志
- 15.2 監控
- 15.2.1 基于宿主機的監控
- 15.2.2 基于Docker守護進程的監控
- 15.2.3 基于容器的監控
- 15.3 小結
- DockOne社區簡介
- 看完了