什么是基礎(chǔ)設(shè)施
在IT領(lǐng)域,當(dāng)我們談?wù)摶A(chǔ)設(shè)施時,我們都在談?wù)撌裁茨??一般來講,我們會直覺的認(rèn)為服務(wù)器就是基礎(chǔ)設(shè)施,甚至在Infrastructure as Code的wiki頁面也是這么舉例的,不過我不是很認(rèn)同,我覺得基礎(chǔ)設(shè)施應(yīng)該包括提供給業(yè)務(wù)相關(guān)的應(yīng)用所有基礎(chǔ)保障的服務(wù)和設(shè)施,比如:
- DNS/CDN
- 防火墻/Load Balancer
- 應(yīng)用服務(wù)器、數(shù)據(jù)庫(物理機/虛擬機)
- 日志、監(jiān)控、報警服務(wù)
基礎(chǔ)設(shè)施管理面臨的挑戰(zhàn)
業(yè)務(wù)的快速發(fā)展要求基礎(chǔ)設(shè)施的靈活性,更快的部署速度,更快的上線時間,自恢復(fù)的系統(tǒng),但是傳統(tǒng)的IT運維的方式在基礎(chǔ)設(shè)施管理面前給我們帶來的很多的挑戰(zhàn):
-
服務(wù)器蔓延(Server Sprawl)。在單塊架構(gòu)下,服務(wù)器的數(shù)量和需要配置的種類都比較少,然而隨著業(yè)務(wù)發(fā)展,或者微服務(wù)拆分等,服務(wù)器數(shù)量,所需配置的種類可能會爆炸式增長,沿用傳統(tǒng)的管理方式挑戰(zhàn)很大,而且對于相同的服務(wù)器可能會導(dǎo)致配置的差異。 -
配置漂移(Configuration Drift)。服務(wù)器的配置可能會隨著時間增加。比如有人為了解決一個特定用戶的問題,修改了其中一臺服務(wù)器的配置,這樣他們之間就存在了差異。 很有可能會發(fā)生,只有在某個環(huán)境里面的臺服務(wù)器上,應(yīng)用才能正常運行的情況。 -
雪花服務(wù)器(Snowflake Servers)。雪花服務(wù)器的意思是該服務(wù)器和你的網(wǎng)絡(luò)中任意其它的服務(wù)器都不同,特殊到無法復(fù)制。比如,在別的服務(wù)器上升級ruby語言后,應(yīng)用可以運行,但是在某臺機器上就是不可以。 -
脆弱的基礎(chǔ)設(shè)施(Fragile Infrastructure)。總有一些服務(wù)器,在你on-call的時候,你需要對著它們拜一拜,祈禱它們不要出問題。 -
自動化恐懼癥(Automation Fear)。缺乏對自動化的信心因為我的服務(wù)器配置不是一致的。我的服務(wù)器不一致是因為我沒有頻繁和一致的運行自動化。 -
侵蝕(Erosion)。侵蝕就是問題隨著時間的推移蔓延到正在運行的系統(tǒng)的意思。比如,服務(wù)器磁盤被日志文件塞滿,操作系統(tǒng)升級,內(nèi)核補丁,以及基礎(chǔ)設(shè)施軟件(如Apache,MySQL,SSH,OpenSSL)升級去修復(fù)安全漏洞等。
基礎(chǔ)設(shè)施即代碼定義及原則
針對以上的問題,解決的辦法是將基礎(chǔ)設(shè)施作為代碼,版本管理起來?;A(chǔ)設(shè)施即代碼是基于從軟件開發(fā)實踐的基礎(chǔ)設(shè)施自動化的方法。它強調(diào)配置和改變系統(tǒng)及其配置的一致性,可重復(fù)的程序。變更轉(zhuǎn)化為定義,然后通過包括徹底的驗證的無人值守過程應(yīng)用到系統(tǒng)中。其原則如下:
-
容易重現(xiàn)的系統(tǒng)。能夠毫不費力且可靠地重建基礎(chǔ)設(shè)施中的任何元素。 -
可任意處理系統(tǒng)??梢暂p松創(chuàng)建、銷毀、替換、更改以及移動資源。 -
一致的系統(tǒng)。假設(shè)兩個基礎(chǔ)設(shè)施元素提供相似的服務(wù),比如同一個集群中有兩個應(yīng)用程序服務(wù)器。這些服務(wù)器應(yīng)該幾乎完全相同。它們的系統(tǒng)軟件和配置應(yīng)該是一樣的,除了一丁點配置(比如IP地址)用于區(qū)分彼此。 -
可重復(fù)的過程。基于可再生原則,對基礎(chǔ)設(shè)施執(zhí)行的任何行為都是可以重復(fù)的。也就是說Duang了之后,對于所有人的效果應(yīng)該是一樣的。 -
變化的設(shè)計。確保系統(tǒng)能夠安全地改變,迅速的頻繁做出變化。
基礎(chǔ)設(shè)施即代碼的實踐
- 使用定義文件。如Chef cookbook, Ansible Playbook等。
- 一切版本化。所有的配置管理文件都用CVS工具如git管理起來。
- 持續(xù)測試系統(tǒng)和流程。自動化測試和持續(xù)交付/部署流水線。
- 小的變更而不是批量變更。小的變更,測試和回退的難度更小。
- 讓服務(wù)持續(xù)可用。通過冗余/DR等方式保持服務(wù)的可用性。
基礎(chǔ)設(shè)施即代碼的工具
市面上的基礎(chǔ)設(shè)施、配置管理的工具很多,我用過的有chef, puppet,ansible以及cloudformation。其中chef是很多年前用于管理測試環(huán)境的工具,puppet用于管理數(shù)據(jù)中心遺留系統(tǒng)的工具,ansible用于少量的系統(tǒng)如日志系統(tǒng)Splunk的工具,cloudformation是我們在AWS上統(tǒng)一的配置管理、部署工具。
我在github做了關(guān)于這幾個工具的小demo,大家可以感受下幾種工具的差別。它們完成的事情都是在一個Ubuntu14.04的虛擬機上,配置和安裝docker,并且用Docker運行一個簡單的應(yīng)用,訪問時可以返回Ciao mondo.(意大利語,世界你好的意思)。
直接運行的效果就是這樣:
docker run -d -p 8080:80 iambowen/ciao:alpine
curl localhost:8080
讓我們分別看看用chef、puppet、ansible來實現(xiàn)的過程。
Chef
Chef 是用基于Ruby實現(xiàn)的自動化配置管理工具。它有兩種運行模式,Server-Client以及Chef-Solo的模式。其中Server-Client模式中,必須有chef的agent駐守在node上,并將節(jié)點注冊在Chef Server中,同時同步node上相關(guān)的cookbook,并編譯應(yīng)用到節(jié)點上。另一種是以Chef Solo的方式,將所需的Cookbook等配置文件下載/上傳到node,然后編譯運行,不依賴Chef Server。

這里我們使用chef solo的方式,將所需的docker相關(guān)的cookbook放在固定的路徑下,由vagrant來完成chef client的安裝,之后再用Chef根據(jù)recipe的配置去做部署,包括docker的安裝、pull鏡像以及運行容器。
vagrantfile的配置
config.vm.provision "chef_solo" do |chef|
chef.cookbooks_path = "cookbooks"
chef.add_recipe "docker"
chef.add_recipe "ciao"
end
ciao應(yīng)用的recipe文件
docker_service 'default' do
action [:create, :start]
end
docker_image 'ciao' do
repo 'iambowen/ciao'
tag 'alpine'
action :pull
end
docker_container 'ciao' do
repo 'iambowen/ciao'
tag 'alpine'
port '8088:80'
end
Chef是基于Ruby的DSL實現(xiàn),所以寫Chef的腳本,比起寫Ruby的腳本要稍微簡單一點點。Chef還提供了一些其他的工具,比如Knife,公司的虛擬化解決方案用的是VMware VShpere,曾經(jīng)使用Knife來管理虛擬機(創(chuàng)建/銷毀)。我們使用是在幾年前在AWS上構(gòu)建端到端的測試環(huán)境時使用Chef來做不同應(yīng)用/服務(wù)器的配置管理和部署的,規(guī)模接近500臺。
Puppet
Puppet是另一個自動化配置管理工具。和Chef類似,也是兩種運行模式,Master-Agent模式,以及Standalone模式。Master-Agent模式里面,agent需要創(chuàng)建client side certificate和Puppet Master通過SSL通信,獲取該節(jié)點的catalog/manifests,然后編譯運行。Puppet Master以前是用Ruby實現(xiàn)的,好像后來用Scala重寫了,但是它的配置文件的格式還是類似于Ruby的DSL。Puppetlabs每年還有年度DevOps報告,內(nèi)容不錯,咨詢師或者需要和領(lǐng)導(dǎo)吹比的可以看看。

這里是利用puppet配置和部署的代碼。因為這個ubuntu14.04的基礎(chǔ)鏡像不包括puppet,同時我想偷懶,直接用vagrant運行inline的shell腳本將puppet docker module安裝,之后再用puppet的agent去應(yīng)用配置。
config.vm.provision "shell", inline: <<-SHELL
apt-get update
gem install puppet -v '3.7.5'
gem install facter
puppet module install --modulepath /etc/puppet/modules garethr-docker
SHELL
config.vm.provision "puppet" do |puppet|
puppet.manifests_path = "puppet/manifests"
puppet.manifest_file = "ciao.pp"
end
實際的puppet腳本是這樣的
include 'docker'
docker::run { 'ciao':
image => 'iambowen/ciao:alpine',
ports => "8088:80"
}
因為我們的數(shù)據(jù)中心的遺留系統(tǒng)都是在用puppet去做管理,所以接觸的稍微多些,也踩過一些坑,比如這個,這樣的工具在方便你使用的同時也隱藏了具體的實現(xiàn),一旦出現(xiàn)問題,debug的成本比較高。我們用puppet管理兩個數(shù)據(jù)中心大約在2000-3000臺的服務(wù)器。
Ansible
前面提到Chef和Puppet都是需要在節(jié)點/服務(wù)器上安裝代理(chef client/puppet agent),以這種pull的模式去獲取配置文件,應(yīng)用。這就意味著你的節(jié)點服務(wù)器上會存在額外的依賴,舉個例子,如果你的應(yīng)用基于Ruby2.3,但是提供部署的puppet agent只能運行在ruby1.9.3下面,你就得在同一套環(huán)境下準(zhǔn)備兩個ruby的環(huán)境,有沒有覺得很膈應(yīng),管理的難度也加大了。至少在幾年前,對我們造成了比較大的傷害,當(dāng)時在開發(fā)時流行用rvm或者rbenv去管理ruby的環(huán)境,但是在生產(chǎn)環(huán)境的服務(wù)器用這些東西是比較奇怪和不靠譜的。
相比之下,Ansible做的事情非常簡單,寫yaml格式的配置文件,ssh到應(yīng)用服務(wù)器,應(yīng)用具體的配置。服務(wù)器上只需要有Python的環(huán)境以及一些相關(guān)的包。

這里是利用ansible配置和部署的代碼。
config.vm.provision "ansible" do |ansible|
ansible.playbook = "playbook.yml"
ansible.limit = "all"
ansible.inventory_path = './inventory'
end
部分的playbook如下:
---
- hosts: all
sudo: yes
user: vagrant
tasks:
- name: install docker-py package
pip:
name: docker-py
state: latest
- name: running ciao app
docker_container:
image: 'iambowen/ciao:alpine'
name: ciao
expose: 80
ports:
- 8088:80
pull: true
如果看完整的playbook,會發(fā)現(xiàn)它多做了很多事情,比如添加額外的docker源,安裝docker等,比起Chef和Puppet腳本要長些,但是它們的格式都是yaml,而且對應(yīng)的文檔都可以通過ansible-doc獲取到,學(xué)習(xí)的成本比起Chef和Puppet來講要低很多,同時,也省去了Chef Server和Puppet Master維護(hù)的成本,比較省心。因為我們的基礎(chǔ)設(shè)施以及全面向AWS移植,AWS提供了更好的配置管理和部署工具Cloudformation,所以我們只有在很少的情況下使用了Ansible,比如管理基礎(chǔ)鏡像、日志服務(wù)器更新等。
基礎(chǔ)鏡像 + 包(RPM/Deb) + Config Service
回顧上面的幾種工具,他們在配置管理時做的事情,大致有兩種,首先是依賴管理,如安裝應(yīng)用運行所需的依賴,第二是配置管理,根據(jù)具體的環(huán)境(staging/production)應(yīng)用不同的配置文件。如果服務(wù)器通過基礎(chǔ)鏡像生成,基礎(chǔ)鏡像中包含了運行基礎(chǔ)設(shè)施相關(guān)的組件,如日志客戶端、監(jiān)控客戶端等等,應(yīng)用代碼可以通過RPM/Deb打包(依賴自包含),安裝時yum/aptitude會幫你安裝依賴。而應(yīng)用啟動的配置,可以用過環(huán)境變量傳入,不同環(huán)境依賴的配置,可以連接config service(zookeeper等)獲取。
rpm 打包的例子:
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch
Requires: java-1.7.0-openjdk tomcat6
%description
This package installs the Resi REST Services with embedded server.
%pre
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/usr/share/tomcat6/webapps/bbs_team_b/
cp -r ../SOURCES/* %{buildroot}/usr/share/tomcat6/webapps/bbs_team_b/
基于基礎(chǔ)鏡像,RPM包和配置服務(wù)器的示例圖:

這樣做的好處在于:
- 打包的腳本和配置文件可以和生產(chǎn)代碼放在一起,開發(fā)人員對于生產(chǎn)環(huán)境擁有了可見性,同時具體的配置值放在config service中,對大部分人是不可見的
- 免去了自動化配置工具如Chef、Puppet等的學(xué)習(xí)、維護(hù)的成本,同時這種方式配置部署,其代碼庫對于大部分人是不可見的
- 對于系統(tǒng)的安全補丁,只需要更新基礎(chǔ)鏡像既可,之后重新部署即可,維護(hù)的成本大大降低
不好的地方在于有額外的依賴,比如需要維護(hù)yum源,我們使用Koji去維護(hù)內(nèi)部的yum源,每次新的rpm包push到koji時,koji需要重新index,更新metadata,花費的時間會比較多,無法在持續(xù)交付流水線上立即部署。其次維護(hù)config service也會有額外的成本。當(dāng)然現(xiàn)在也可以在打包rpm的時候生成metadata,同步到s3上作為YUM源。
Cloudformation
前面提到的三種工具,看上去都是對服務(wù)器做配置管理和部署,但是實際上,對于其他的基礎(chǔ)設(shè)施,比如網(wǎng)絡(luò)配置等也可以做到代碼化。這里我們以AWS的cloudformation為例來介紹。
AWS的cloudformation可以讓你通過json或者yaml格式的模板,來管理AWS幾乎所有的基礎(chǔ)設(shè)施資源,同時對于應(yīng)用提供了immutable deployment的零宕機時間部署。
這里是我用來生成自己的VPC網(wǎng)絡(luò)的cloudformation模板(感覺錢包在滴血-_-!),
比如下面的模板片段,在我新建的VPC中,會包含兩個private subnet,兩個public subnet,分別對應(yīng)兩個az(數(shù)據(jù)中心)的網(wǎng)絡(luò),以及其它的一些網(wǎng)絡(luò)配置,nat、網(wǎng)絡(luò)的acl等。如果我有新的配置修改,比如acl的變更等,可以直接通過cloudformation模板更新,不需要在aws console上做任何手動的修改,數(shù)據(jù)中心的網(wǎng)絡(luò)直接就被代碼化了。
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "A vpc template",
"Parameters": {
},
"Metadata": {
},
"Resources": {
"NetworkAclPrivate": {
"Type": "AWS::EC2::NetworkAcl",
"Properties": {
"VpcId": {
"Ref": "Vpc"
},
"Tags": [
{
"Key": "Name",
"Value": {
"Fn::Join": [
"",
[
{
"Ref": "AWS::StackName"
},
"-acl-private"
]
]
}
}
]
}
}
}
}
cloudformation還支持yaml格式的配置,比如例子中的配置。如果我通過命令行調(diào)用aws cloudformation create-stack --stack-name ciao --template-body file://aws/cloudformation/template/ciao.yml --capabilities CAPABILITY_IAM,那么我可以生成一個ELB,一臺基于Ubuntu的EC2實例,該實例通過一個AutoScaling Group去管理,上面運行了iambowen/ciao:alpine的容器,并且該ELB只接收http請求,該實例綁定的安全組只接收來自ELB的請求。
loadBalancer:
Type: AWS::ElasticLoadBalancing::LoadBalancer
Properties:
Scheme: internet-facing
Subnets:
- subnet-d7c254b3
- subnet-90d866e6
SecurityGroups:
- Ref: loadBalancerSecurityGroup
Listeners:
- Protocol: HTTP
LoadBalancerPort: 80
InstancePort: 80
HealthCheck:
Target: HTTP:80/
HealthyThreshold: 2
UnhealthyThreshold: 4
Interval: 10
Timeout: 8
CrossZone: true
ConnectionDrainingPolicy:
Enabled: true
Timeout: 30
launchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
IamInstanceProfile:
Ref: iamInstanceProfile
ImageId: ami-8ed6eaed
AssociatePublicIpAddress: false
InstanceType: t2.micro
InstanceMonitoring: true
SecurityGroups:
- Ref: instancesSecurityGroup
UserData:
Fn::Base64:
Fn::Join:
- "\n"
- - "#!/bin/bash -eu"
- docker run -d --name ciao -p 80:80 -e MESSAGE='Goodbye!' iambowen/ciao:alpine
- docker ps
- ''
- echo; echo --- SUCCESS
- RESOURCE_STATUS=0
- ''
每次AWS的配置、容器或者配置需要更新時,只需要修改cloudformation模板中對應(yīng)的配置,運行下update stack操作即可. 如 aws cloudformation update-stack --stack-name ciao --template-body file://aws/cloudformation/template/ciao.yml --capabilities CAPABILITY_IAM
我們對于AWS的環(huán)境中的資源,幾乎都通過cloudformation生成,這種基礎(chǔ)設(shè)施即代碼的程度,大大降低了我們維護(hù)、遷移的成本。
總結(jié)
文中介紹的幾種工具都比較成熟,對于配置管理、部署等,都沒有太大問題,選擇的時候根據(jù)具體的情況,比如虛擬化解決方案等。下一節(jié)我將介紹部署的幾種模式,如藍(lán)綠部署、紅黑部署、immutable部署等。
Reference
- 文中部分內(nèi)容參考了我參與翻譯的《Infrastructure as Code: Managing Servers in the Cloud》 以及 《DevOps實踐》
- 基礎(chǔ)設(shè)施即代碼的工具部分的代碼鏈接