部署的模式(一):基礎(chǔ)設(shè)施即代碼

什么是基礎(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è)施,比如:

  1. DNS/CDN
  2. 防火墻/Load Balancer
  3. 應(yīng)用服務(wù)器、數(shù)據(jù)庫(物理機/虛擬機)
  4. 日志、監(jiān)控、報警服務(wù)

基礎(chǔ)設(shè)施管理面臨的挑戰(zhàn)


業(yè)務(wù)的快速發(fā)展要求基礎(chǔ)設(shè)施的靈活性,更快的部署速度,更快的上線時間,自恢復(fù)的系統(tǒng),但是傳統(tǒng)的IT運維的方式在基礎(chǔ)設(shè)施管理面前給我們帶來的很多的挑戰(zhàn):

  1. 服務(wù)器蔓延(Server Sprawl)。在單塊架構(gòu)下,服務(wù)器的數(shù)量和需要配置的種類都比較少,然而隨著業(yè)務(wù)發(fā)展,或者微服務(wù)拆分等,服務(wù)器數(shù)量,所需配置的種類可能會爆炸式增長,沿用傳統(tǒng)的管理方式挑戰(zhàn)很大,而且對于相同的服務(wù)器可能會導(dǎo)致配置的差異。
  2. 配置漂移(Configuration Drift)。服務(wù)器的配置可能會隨著時間增加。比如有人為了解決一個特定用戶的問題,修改了其中一臺服務(wù)器的配置,這樣他們之間就存在了差異。 很有可能會發(fā)生,只有在某個環(huán)境里面的臺服務(wù)器上,應(yīng)用才能正常運行的情況。
  3. 雪花服務(wù)器(Snowflake Servers)。雪花服務(wù)器的意思是該服務(wù)器和你的網(wǎng)絡(luò)中任意其它的服務(wù)器都不同,特殊到無法復(fù)制。比如,在別的服務(wù)器上升級ruby語言后,應(yīng)用可以運行,但是在某臺機器上就是不可以。
  4. 脆弱的基礎(chǔ)設(shè)施(Fragile Infrastructure)。總有一些服務(wù)器,在你on-call的時候,你需要對著它們拜一拜,祈禱它們不要出問題。
  5. 自動化恐懼癥(Automation Fear)。缺乏對自動化的信心因為我的服務(wù)器配置不是一致的。我的服務(wù)器不一致是因為我沒有頻繁和一致的運行自動化。
  6. 侵蝕(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)中。其原則如下:

  1. 容易重現(xiàn)的系統(tǒng)。能夠毫不費力且可靠地重建基礎(chǔ)設(shè)施中的任何元素。
  2. 可任意處理系統(tǒng)??梢暂p松創(chuàng)建、銷毀、替換、更改以及移動資源。
  3. 一致的系統(tǒng)。假設(shè)兩個基礎(chǔ)設(shè)施元素提供相似的服務(wù),比如同一個集群中有兩個應(yīng)用程序服務(wù)器。這些服務(wù)器應(yīng)該幾乎完全相同。它們的系統(tǒng)軟件和配置應(yīng)該是一樣的,除了一丁點配置(比如IP地址)用于區(qū)分彼此。
  4. 可重復(fù)的過程。基于可再生原則,對基礎(chǔ)設(shè)施執(zhí)行的任何行為都是可以重復(fù)的。也就是說Duang了之后,對于所有人的效果應(yīng)該是一樣的。
  5. 變化的設(shè)計。確保系統(tǒng)能夠安全地改變,迅速的頻繁做出變化。

基礎(chǔ)設(shè)施即代碼的實踐


  1. 使用定義文件。如Chef cookbook, Ansible Playbook等。
  2. 一切版本化。所有的配置管理文件都用CVS工具如git管理起來。
  3. 持續(xù)測試系統(tǒng)和流程。自動化測試和持續(xù)交付/部署流水線。
  4. 小的變更而不是批量變更。小的變更,測試和回退的難度更小。
  5. 讓服務(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。

arch

這里我們使用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 arch

這里是利用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 arch
ansible arch

這里是利用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ù)器的示例圖:

rpm_config_service.png

這樣做的好處在于:

  1. 打包的腳本和配置文件可以和生產(chǎn)代碼放在一起,開發(fā)人員對于生產(chǎn)環(huán)境擁有了可見性,同時具體的配置值放在config service中,對大部分人是不可見的
  2. 免去了自動化配置工具如Chef、Puppet等的學(xué)習(xí)、維護(hù)的成本,同時這種方式配置部署,其代碼庫對于大部分人是不可見的
  3. 對于系統(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


  1. 文中部分內(nèi)容參考了我參與翻譯的《Infrastructure as Code: Managing Servers in the Cloud》 以及 《DevOps實踐》
  2. 基礎(chǔ)設(shè)施即代碼的工具部分的代碼鏈接
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容