部署的模式(一):基础设施即代码

什么是基础设施


在IT领域,当我们谈论基础设施时,我们都在谈论什么呢?一般来讲,我们会直觉的认为服务器就是基础设施,甚至在Infrastructure as Code的wiki页面也是这么举例的,不过我不是很认同,我觉得基础设施应该包括提供给业务相关的应用所有基础保障的服务和设施,比如:

  1. DNS/CDN
  2. 防火墙/Load Balancer
  3. 应用服务器、数据库(物理机/虚拟机)
  4. 日志、监控、报警服务

基础设施管理面临的挑战


业务的快速发展要求基础设施的灵活性,更快的部署速度,更快的上线时间,自恢复的系统,但是传统的IT运维的方式在基础设施管理面前给我们带来的很多的挑战:

  1. 服务器蔓延(Server Sprawl)。在单块架构下,服务器的数量和需要配置的种类都比较少,然而随着业务发展,或者微服务拆分等,服务器数量,所需配置的种类可能会爆炸式增长,沿用传统的管理方式挑战很大,而且对于相同的服务器可能会导致配置的差异。
  2. 配置漂移(Configuration Drift)。服务器的配置可能会随着时间增加。比如有人为了解决一个特定用户的问题,修改了其中一台服务器的配置,这样他们之间就存在了差异。 很有可能会发生,只有在某个环境里面的台服务器上,应用才能正常运行的情况。
  3. 雪花服务器(Snowflake Servers)。雪花服务器的意思是该服务器和你的网络中任意其它的服务器都不同,特殊到无法复制。比如,在别的服务器上升级ruby语言后,应用可以运行,但是在某台机器上就是不可以。
  4. 脆弱的基础设施(Fragile Infrastructure)。总有一些服务器,在你on-call的时候,你需要对着它们拜一拜,祈祷它们不要出问题。
  5. 自动化恐惧症(Automation Fear)。缺乏对自动化的信心因为我的服务器配置不是一致的。我的服务器不一致是因为我没有频繁和一致的运行自动化。
  6. 侵蚀(Erosion)。侵蚀就是问题随着时间的推移蔓延到正在运行的系统的意思。比如,服务器磁盘被日志文件塞满,操作系统升级,内核补丁,以及基础设施软件(如Apache,MySQL,SSH,OpenSSL)升级去修复安全漏洞等。

基础设施即代码定义及原则


针对以上的问题,解决的办法是将基础设施作为代码,版本管理起来。基础设施即代码是基于从软件开发实践的基础设施自动化的方法。它强调配置和改变系统及其配置的一致性,可重复的程序。变更转化为定义,然后通过包括彻底的验证的无人值守过程应用到系统中。其原则如下:

  1. 容易重现的系统。能够毫不费力且可靠地重建基础设施中的任何元素。
  2. 可任意处理系统。可以轻松创建、销毁、替换、更改以及移动资源。
  3. 一致的系统。假设两个基础设施元素提供相似的服务,比如同一个集群中有两个应用程序服务器。这些服务器应该几乎完全相同。它们的系统软件和配置应该是一样的,除了一丁点配置(比如IP地址)用于区分彼此。
  4. 可重复的过程。基于可再生原则,对基础设施执行的任何行为都是可以重复的。也就是说Duang了之后,对于所有人的效果应该是一样的。
  5. 变化的设计。确保系统能够安全地改变,迅速的频繁做出变化。

基础设施即代码的实践


  1. 使用定义文件。如Chef cookbook, Ansible Playbook等。
  2. 一切版本化。所有的配置管理文件都用CVS工具如git管理起来。
  3. 持续测试系统和流程。自动化测试和持续交付/部署流水线。
  4. 小的变更而不是批量变更。小的变更,测试和回退的难度更小。
  5. 让服务持续可用。通过冗余/DR等方式保持服务的可用性。

基础设施即代码的工具


市面上的基础设施、配置管理的工具很多,我用过的有chefpuppetansible以及cloudformation。其中chef是很多年前用于管理测试环境的工具,puppet用于管理数据中心遗留系统的工具,ansible用于少量的系统如日志系统Splunk的工具,cloudformation是我们在AWS上统一的配置管理、部署工具。
我在github做了关于这几个工具的小demo,大家可以感受下几种工具的差别。它们完成的事情都是在一个Ubuntu14.04的虚拟机上,配置和安装docker,并且用Docker运行一个简单的应用,访问时可以返回Ciao mondo.(意大利语,世界你好的意思)。
直接运行的效果就是这样:

docker run -d -p 8080:80 iambowen/ciao:alpine

curl localhost:8080

让我们分别看看用chefpuppetansible来实现的过程。

Chef


Chef 是用基于Ruby实现的自动化配置管理工具。它有两种运行模式,Server-Client以及Chef-Solo的模式。其中Server-Client模式中,必须有chef的agent驻守在node上,并将节点注册在Chef Server中,同时同步node上相关的cookbook,并编译应用到节点上。另一种是以Chef Solo的方式,将所需的Cookbook等配置文件下载/上传到node,然后编译运行,不依赖Chef Server。

arch

这里我们使用chef solo的方式,将所需的docker相关的cookbook放在固定的路径下,由vagrant来完成chef client的安装,之后再用Chef根据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应用的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实现,所以写Chef的脚本,比起写Ruby的脚本要稍微简单一点点。Chef还提供了一些其他的工具,比如Knife,公司的虚拟化解决方案用的是VMware VShpere,曾经使用Knife来管理虚拟机(创建/销毁)。我们使用是在几年前在AWS上构建端到端的测试环境时使用Chef来做不同应用/服务器的配置管理和部署的,规模接近500台。

Puppet


Puppet是另一个自动化配置管理工具。和Chef类似,也是两种运行模式,Master-Agent模式,以及Standalone模式。Master-Agent模式里面,agent需要创建client side certificate和Puppet Master通过SSL通信,获取该节点的catalog/manifests,然后编译运行。Puppet Master以前是用Ruby实现的,好像后来用Scala重写了,但是它的配置文件的格式还是类似于Ruby的DSL。Puppetlabs每年还有年度DevOps报告,内容不错,咨询师或者需要和领导吹比的可以看看。

puppet arch

这里是利用puppet配置和部署的代码。因为这个ubuntu14.04的基础镜像不包括puppet,同时我想偷懒,直接用vagrant运行inline的shell脚本将puppet docker module安装,之后再用puppet的agent去应用配置。

  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"
}

因为我们的数据中心的遗留系统都是在用puppet去做管理,所以接触的稍微多些,也踩过一些坑,比如这个,这样的工具在方便你使用的同时也隐藏了具体的实现,一旦出现问题,debug的成本比较高。我们用puppet管理两个数据中心大约在2000-3000台的服务器。

Ansible


前面提到Chef和Puppet都是需要在节点/服务器上安装代理(chef client/puppet agent),以这种pull的模式去获取配置文件,应用。这就意味着你的节点服务器上会存在额外的依赖,举个例子,如果你的应用基于Ruby2.3,但是提供部署的puppet agent只能运行在ruby1.9.3下面,你就得在同一套环境下准备两个ruby的环境,有没有觉得很膈应,管理的难度也加大了。至少在几年前,对我们造成了比较大的伤害,当时在开发时流行用rvm或者rbenv去管理ruby的环境,但是在生产环境的服务器用这些东西是比较奇怪和不靠谱的。
相比之下,Ansible做的事情非常简单,写yaml格式的配置文件,ssh到应用服务器,应用具体的配置。服务器上只需要有Python的环境以及一些相关的包。

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,会发现它多做了很多事情,比如添加额外的docker源,安装docker等,比起Chef和Puppet脚本要长些,但是它们的格式都是yaml,而且对应的文档都可以通过ansible-doc获取到,学习的成本比起Chef和Puppet来讲要低很多,同时,也省去了Chef Server和Puppet Master维护的成本,比较省心。因为我们的基础设施以及全面向AWS移植,AWS提供了更好的配置管理和部署工具Cloudformation,所以我们只有在很少的情况下使用了Ansible,比如管理基础镜像、日志服务器更新等。

基础镜像 + 包(RPM/Deb) + Config Service


回顾上面的几种工具,他们在配置管理时做的事情,大致有两种,首先是依赖管理,如安装应用运行所需的依赖,第二是配置管理,根据具体的环境(staging/production)应用不同的配置文件。如果服务器通过基础镜像生成,基础镜像中包含了运行基础设施相关的组件,如日志客户端、监控客户端等等,应用代码可以通过RPM/Deb打包(依赖自包含),安装时yum/aptitude会帮你安装依赖。而应用启动的配置,可以用过环境变量传入,不同环境依赖的配置,可以连接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/

基于基础镜像,RPM包和配置服务器的示例图:

rpm_config_service.png

这样做的好处在于:

  1. 打包的脚本和配置文件可以和生产代码放在一起,开发人员对于生产环境拥有了可见性,同时具体的配置值放在config service中,对大部分人是不可见的
  2. 免去了自动化配置工具如Chef、Puppet等的学习、维护的成本,同时这种方式配置部署,其代码库对于大部分人是不可见的
  3. 对于系统的安全补丁,只需要更新基础镜像既可,之后重新部署即可,维护的成本大大降低

不好的地方在于有额外的依赖,比如需要维护yum源,我们使用Koji去维护内部的yum源,每次新的rpm包push到koji时,koji需要重新index,更新metadata,花费的时间会比较多,无法在持续交付流水线上立即部署。其次维护config service也会有额外的成本。当然现在也可以在打包rpm的时候生成metadata,同步到s3上作为YUM源。

Cloudformation


前面提到的三种工具,看上去都是对服务器做配置管理和部署,但是实际上,对于其他的基础设施,比如网络配置等也可以做到代码化。这里我们以AWS的cloudformation为例来介绍。
AWS的cloudformation可以让你通过json或者yaml格式的模板,来管理AWS几乎所有的基础设施资源,同时对于应用提供了immutable deployment的零宕机时间部署。
这里是我用来生成自己的VPC网络的cloudformation模板(感觉钱包在滴血-_-!),

比如下面的模板片段,在我新建的VPC中,会包含两个private subnet,两个public subnet,分别对应两个az(数据中心)的网络,以及其它的一些网络配置,nat、网络的acl等。如果我有新的配置修改,比如acl的变更等,可以直接通过cloudformation模板更新,不需要在aws console上做任何手动的修改,数据中心的网络直接就被代码化了。

{
    "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格式的配置,比如例子中的配置。如果我通过命令行调用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模板中对应的配置,运行下update stack操作即可. 如 aws cloudformation update-stack --stack-name ciao --template-body file://aws/cloudformation/template/ciao.yml --capabilities CAPABILITY_IAM

我们对于AWS的环境中的资源,几乎都通过cloudformation生成,这种基础设施即代码的程度,大大降低了我们维护、迁移的成本。

总结


文中介绍的几种工具都比较成熟,对于配置管理、部署等,都没有太大问题,选择的时候根据具体的情况,比如虚拟化解决方案等。下一节我将介绍部署的几种模式,如蓝绿部署、红黑部署、immutable部署等。

Reference


  1. 文中部分内容参考了我参与翻译的《Infrastructure as Code: Managing Servers in the Cloud》 以及 《DevOps实践》
  2. 基础设施即代码的工具部分的代码链接
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容