Puppet,Chef,Ansible的共性

本文试图找到相似Puppet、Chef、Ansible这样自动化配置管理工具的共性,以不至于迷失在杂乱的尘世中。总会有各类人为各类目的造概念,来让世界更复杂。java

本文一样适用于没有运维经验的人。由于我就是一个没有运维经验的人。欢迎斧正。node

与这仨之间的历史python

本人接触自动化运维的时间比较晚,也就一年前才知道Puppet及自动化运维(只限于知道),而Chef、Ansible就更晚了。然而在学习它们以前,我对运维要作哪些事情并无概念。这就对我学习Puppet,Chef和Ansible形成的障碍。由于不知道这三个工具在运维领域的位置,解决运维过程当中的哪些问题。我对这三个工具的最初印象就是有了它们,不用我手工的SSH上服务器,而后一条条命令去执行安装软件,不用SCP war包上服务器等,对服务器的操做均可以自动化了。mysql

这个最初印象也就是我跟它们的历史。为何要说这些呢?就是由于这个最初印象,让我以为它们是有共性。所谓共性就是存在一些共通的概念或原理之类的东西,掌握这些“东西”,我就能够站在一个更高的高度去思考。Puppet, Chef, Ansible都是工具,对于工具来讲,共性指的是它们共同要解决的问题。nginx

可是当我翻了很多文章后依然没有结果。因此,我决定本身去找它们的共性,并记录下来。程序员

自制一个自动化运维工具web

可是要从多个类似的东西中找到共性,彷佛须要同时很熟悉它们。可是我没有那么多时间。因此,我选择了另外一种方法:在大脑中预想本身去实现一套自动化运维工具。但问题是,我都不知道“自动化运维工具要实现哪些功能。sql

那我就先把目标下降一些,把问题简化一下:将我最初的“印象”实现自动化了。我能想到的就是写一个bash脚本:编程

ssh ....json

apt-get install -y java

apt-get -y nginx

scp ..

好,如今将问题难度加大:对多台服务器进行一样的操做。我能到想就是将全部的服务器的IP放在一个数组里,而后用for循环执行。问题来了,若是我对服务器已经执行了一次命令时可能会失败,我再想执行第二次怎么办呢?这时,咱们能够在bash脚本里加上if语句,若是安装了java就不安装第二次了。

显然现实中还会有不少问题,如:

  • 反向配置问题,这时,咱们应该另写一个bash脚原本解决这个反向的问题?若是采用这种方案,咱们bash脚本数量上升到必定程度,如何管理这些脚本及它们之间的关系,这个方案带来的新的复杂性将会成为咱们的新问题;

  • 服务器上操做系统的兼容性问题:不一样的操做系统,咱们的bash命令会不一样;

  • 软件的版本升级或降级问题等等。

面对这些问题,咱们是能够每次都用bash解决,可是这样始终不是个办法。由于,bash对于创建一个自动化运维工具过于底层。说到这句话,你应该很容易就想到设计一种DSL。到这个点,我以为咱们的方向已经明朗。Puppet, Chef和Ansible都分别采用不一样的DSL。而这种DSL是须要编译成服务器可执行的东西的。什么东西是可执行的?目前,咱们假设这个东西是bash脚本。

可是这个DSL是放在哪里编译呢?放在受控机器端,仍是主控机器上?因此,我认为全部的自动化运维工具都会遇到这个问题。什么是受控机器与主控机器?你就理解成一台机器只发命令,另外一台机器只执行命令。

咱们刚刚谈的是设计DSL。可是,要设计一个完整的自动化运维工具,咱们最早应该考虑是主控机器如何与受控机器通讯。这个问题让我很长时间感到很无力,由于无从下手。后来醒悟,原来你不能单独考虑这个问题,通讯方式还与你设计的DSL及编译DSL的方式有关。同时,受控机器的执行结果这个问题,也影响着咱们设计DSL。

上面咱们彷佛忘记了一个事实:自动化运维工具应对的每每不是一台机器,而是不少台机器。当面对多台机器时,就会产生一个新的问题:如何组织它们?由于不一样的机器的职责不一样,所对应的配置也就不一样。

我想咱们已经知道自动化运维工具都要解决的问题了:

1. 如何与受控机器通讯

2. 如何组织成百上千台机器

3. DSL的设计与编译

4. 如何获得执行结果

我不肯定咱们的思路与Puppet,Chef和Ansible的做者同样。也不必定彻底正确。可是至少,咱们大概知道自动化运维工具要解决哪些问题了。而它们是否是自动化运维工具的共性?我不肯定。

可是没有关系,咱们假设它们就是全部配置管理工具都要解决的问题,它们的共性,接下来咱们来看看它们分别是怎么解决这些问题的。

这仨的背景

在进入学习以前,咱们先看看它们的背景:

Puppet

 

如何与受控机器通讯

Puppet的主控机器(Server)称为Puppet master,受控机器(client)称为Puppet agent。它们使用HTTPS进行通讯。在安装puppet以前,须要分别在主控机器和受控上设置的hostname。

由于是C/S架构的,意味着你须要在主控机器上安装Puppet master,(安装的过程或翻阅其它教程的时候,请注意教程所使用的Puppet的版本,Puppet版本之间是有差别的)咱们以Ubuntu为例:

          sudo apt-get install -y puppetmaster

在受控机器上安装Puppet agent:

    sudo apt-get install -y puppet

安装完成后,受控机器须要设置在/etc/puppet/puppet.conf文件的[main]节点下加入server=<master’s hostname>,同时运行sudo puppet agent —test向主控机器申请认证。主控机器执行sudo puppet cert sign <agent’s hostname>认证。

在生产环境并不必定采起这样的手工认证。

DSL的设计与编译1

Puppet的DSL称为Manifest ,以.pp为文件扩展名。像其它编程语言同样,它也有一个程序的入口,它默认放在:/etc/puppet/manifests/site.pp

 

  #vim /etc/puppet/manifests/site.pp   

node 'mysqlserver.example.com' {

        $mysql_password = 123456
        package {
               ['mysql-common', 'mysql-client', 'mysql-server']:
                 ensure => present
         }

         service {
           'mysql':
             enable => true,
             ensure => running,
             require => Package['mysql-server']
         }

         exec {
           'set-root-password':
             path   => "/usr/bin:/usr/sbin:/bin",
             subscribe => [Package['mysql-common'], Package['mysql-client'], Package['mysql-server']],
             refreshonly => true,
             unless => "mysqladmin -uroot -p$mysql_password status",
             command => "mysqladmin -uroot password $mysql_password",
         }


         file {
           '/etc/mysql/my.cnf':
             content => template('my.cnf.erb'),
             require => Package['mysql-server'],
             notify => Service['mysql']
         }
    }

 

Manifest的基本格式:

node NODENAME{
        RESOURCE { NAME:
            ATTRIBUTE => VALUE,
            ...
        }     

    }

Nodename实际上就是节点的hostname。这里有个提问:为何不直接使用IP呢?Resource表明的是资源,也就是Puppet将节点内全部的东西都看成资源。具体细节,咱们稍后会讲到。

如何组织成百上千台机器

若是我要控制1000台机器是否是意味着我要在site.pp中写1000个 节点 node NODENAME{…}呢?答案是确定的。

DSL的设计与编译2

咱们注意到`node`节点中所包含的全部内容,本质上都是在描述这个node的状态,如:

service { 'mysql':            

     enable => true,

     ensure => running       

}

指的是`service mysql`这个Resource的状态是可用的且正在运行的。Puppet中内建了很多Resource供给咱们使用,如:file,exec, package等。可是当Puppet内建的Resource不够用了呢?因此,它应该支持resource的扩展。以此类推,全部的自动化运维工具都应该支持此类扩展。

同时,Puppet提供了模板机制。Puppet的模板模式使用的是Ruby的ERB。你将`.erb`的文件放在`/etc/puppet/templates`后,就能够在puppet的代码中使用`template('my.cnf.erb’)`:

 #vim /etc/puppet/templates/my.cnf.erb
    [mysql]
    ...
    ...

既然有模板,怎么少得了变量?变量的定义:$VAR_NAME = VALUE。有变量的地方,必定会引用做用域概念,不论它是静态做用域仍是全局做用域。能够告诉你Puppet是静态做用域。猜猜Puppet的变量的做用域分几级?实际上,Puppet,Chef,Ansible的变量都是静态做用域概念,它们的变量做用域分又都分红几级?多问一句:这也是它们的共性吗?

如今咱们了解了Manifest及其基本格式、node、resource概念、模板和变量。同时,咱们并不该该把全部的内容都写到一个site.pp文件中,这就像咱们不该该把全部的逻辑都写在C语言中的main函数中同样。那Puppet的DSL是如何解决这个问题的:如何让开发者更方便的组织代码?

如今咱们来看相对模块化一些的Puppet代码:
#vim /etc/puppet/manifests/site.pp
 

class mysql($mysql_password = "123456") {
    package {
           ['mysql-common', 'mysql-client', 'mysql-server']:
             ensure => present
     }

     service {
       'mysql':
         enable => true,
         ensure => running,
         require => Package['mysql-server']
     }

     exec {
       'set-root-password':
         path   => "/usr/bin:/usr/sbin:/bin",
         subscribe => [Package['mysql-common'], Package['mysql-client'], Package['mysql-server']],
         refreshonly => true,
         unless => "mysqladmin -uroot -p$mysql_password status",
         command => "mysqladmin -uroot password $mysql_password",
     }


     file {
       '/etc/mysql/my.cnf':
         content => template('my.cnf.erb'),
         require => Package['mysql-server'],
         notify => Service['mysql']
     }

}
node 'agent' {
    include mysql

    class { 'mysql':

           mysql_password => '456789'
    }
}

这个版本的site.pp咱们用到了class概念。你能够定义了一个class,而后将职责相同的逻辑放在其中。最后在其它地方引用。可是引用的时候要分状况,这个class有没有带参数, 将影响使用这个class的方式。

可是就算这样,咱们的site.pp的代码可维护性同样不高。因此,Puppet还提供一种module概念。实际上,用了你就知道,puppet就是将class从site.pp移出去了的另外一种说法。咱们来看使用了module的第三版:

咱们将mysql class抽到mysql module中:

#vim /etc/puppet/modules/mysql/manifests/init.pp
class mysql($mysql_password = "123456") {
    package {
       ['mysql-common', 'mysql-client', 'mysql-server']:
                 ensure => present
     }

     service {
       'mysql':
         enable => true,
         ensure => running,
         require => Package['mysql-server']
     }

     exec {
       'set-root-password':
         path   => "/usr/bin:/usr/sbin:/bin",
         subscribe => [Package['mysql-common'], Package['mysql-client'], Package['mysql-server']],
         refreshonly => true,
         unless => "mysqladmin -uroot -p$mysql_password status",
         command => "mysqladmin -uroot password $mysql_password",
     }

     file {
       '/etc/mysql/my.cnf':
         content => template('mysql/my.cnf.erb'),
         require => Package['mysql-server'],
         notify => Service['mysql']
     }

}

在使用module以前,咱们的文件结构是这样的:

|-- auth.conf

|-- environments

|-- files

|-- manifests

|   `-- site.pp

|-- puppet.conf

|-- templates

|  `—my.cnf.erb

使用module后:

|-- auth.conf

|-- environments

|-- files

|-- manifests

|   `-- site.pp

|-- modules

|   `-- mysql

|       |-- manifests

|       |   `-- init.pp

|       `-- templates

|           `-- my.cnf.erb

|-- puppet.conf

`-- templates

定义了module,那怎么用呢?

#vim /etc/puppet/manifests/site.pp
node 'agent' {
    include mysql
}

到这里,我相信你知道大概怎么写Puppet,可是我以为是不够的。我不理解它为何这样设计。咱们为何不是:

node 'agent_hostname'{

    install package [‘mysql’,’mysqlserver’]

    start service ‘mysql'

    create template mysql.cnf

}

个人意思是为何是以名词导向的描述性语言,而不是以动词为导向。而后,我就去找《配置管理最佳实践》。醒悟了,原来配置管理不是一两个工具就能够搞定。它是一个系统,包括六个核心职能:

1. 源代码管理

2. 构建工程

3. 环境配置

4. 变动控制

5. 发布工程

6. 部署

因此,我之前一直不理解自动化运维和自动化配置管理之间的区别。同时我看到了一些文章里:配置管理实际上就是状态管理,不管是服务器状态仍是软件状态。

这下终于感受明白了,也难怪Puppet,Chef,Ansible的DSL都是描述性的语言。

可是,Puppet如何编译,在哪里编译咱们写好的manifest呢?在主控机器与受控机器认证成功后,受控机器会每隔一段时间就向主控机器发请求,这个请求将会把本身(受控机器)的信息告诉主控机器。主控机器拿到这些信息后与manifest连接编译,最后生成一份受控机器(puppet客户端)可执行的catalog。受控机器在执行的过程当中,将执行状况反馈给主控主机。这就是Puppet中主控机器如何获得受控机器的命令执行结果的。

到此,咱们看到了Puppet已经回答了咱们以前的四个问题。

小结

1. 如何与受控机器通讯

     采用C/S架构,使用HTTPS,agent向master申请证书。

2. 如何组织成百上千台机器

     在manifest中使用`node`关键字定义。

3. DSL的设计与编译

     * 组织代码的方式

          Puppet在manifest文件中定义node(受控节点),将全部node中的构件抽象为resource,咱们能够给这个resource的attribute设置值。node下能够包含多个resource,这些resource共同构成了这个node的状态。可是不可能将全部的resource都写在一个文件中,再说一个manifest文件一般不止一个node。因此,因此,Puppet提供一种module和class机制,让你能将一些共同起到同一职责的resource打包到一块儿。class与module有什么不一样呢?class能够直接写到manifest文件中,而module必须另外新建一个目录结构。这就是Puppet组织代码的方式。

          深刻学习:若是处理resource之间的关系问题,它们颇有可能有依赖关系。class及module也会有一样的问题。if else及for呢?

     * 变量定义: $VAR_NAME = VALUE。 

          深刻学习:了解变量的做用域

     * 模板:使用ruby的erb文件

4. 如何获得执行结果

     受控机器主动将执行结果发送给主控机器。

它真的必定要有master才能用吗?不是的。Puppet提供了单机版的使用方法。具体请google: puppet apply

Chef

Chef的中文意思是厨师。因此它将全部的受控机器看做“菜肴”。可是若是咱们不给告诉它菜谱(Cookbook),它是不会给咱们作菜的。菜谱上都写着什么呢?是配方(Recipe)。因此,咱们把recipe一个个的写到Cookbook中,最后交给Chef。

Chef一样是C/S架构,C与S也是使用HTTPS进行通讯的。一样的,正由于这样,咱们可能重用学习Puppet的pattern来学习Chef。可是由于Chef的C/S模式的投资回报率过低了,因此,我坚持一段时间后,就放弃了。和Puppet同样,Chef也提供了单机版:Chef-solo。

Ansible

Ansible说是agentles(去客户端)的。可是实际上,它要求受控机器上装有SSH及Python,而且装了python-simplejson包。实际上,咱们如今的机器基本上默认都已经安装这些。因此,在使用Ansible时,你不须要特地准备一台机器作为主控服务器。只要你想,任何机器随时均可以变成主控机器。

关于Ansible的安装看文档就行了。与Chef和Puppet不一样的是,Ansible组织受控机器的那部分逻辑抽来单独放,叫Inventory。它是一个ini格式的文件,如hosts:

[web]
192.168.33.10

[db]
192.168.33.11

文件名和路径都任意,可是建议使用表意的名字及合适的路径。

Puppet和Chef都本身作了一套DSL,而后再本身写编译器,可是Ansible使用的是yaml格式。我以为这是很是聪明的设计:一是你们都熟悉yaml格式比熟悉自定义的DSL来得简单,二是不须要本身设计DSL了,三是不用本身写编译器了。因此,我我的学习过程当中,发现它是相对Puppet,Chef简单不少。

了解yaml文件格式后,接着就是理解Ansible的隐喻了。Ansible是导演,将全部的受控机器理解为演员。而咱们开发者则是编剧。咱们只要把剧本(playbook)写好,Ansible拿剧本再与Invenstory一对上号,演员只会按照剧本上的如实发挥,不会有任何的我的发挥。

好,咱们就来写初版本的playbook.yml(路径和名字均可自定义):

---
- hosts: web
  tasks:
    - name: install nginx
      apt: name=nginx state=latest

- hosts: db
  vars:
    mysql_password: '123465'
  tasks:
    - name: install mysql
      yum: name={{item}}
      with_items:
        - 'mysql-common'
        - 'mysql-client'
        - 'mysql-server'

    - name: configurate mysql-server
      template: src=my.cnf.j2 dest=/etc/mysql/my.cnf

    - name: start service
      service: name=mysql state=started
#vim templates/my.cnf.j2
[mysql]
...
passowrd={{mysql_password}}

咱们的剧本包括两个演员:web,db。它们都对应哪些受控机器呢?看Invenstory文件就知道了。那这些演员都要作哪些事情呢?看tasks,它下面跟的是一个列表。像`yum`,`apt`,`template`,`service`,被称为module,相似于Puppet的resource和Chef的recipe。Ansible自己提供了很多module,可是想都不用想,必定不能知足全部项目的需求,因此,你能够开发本身的module。

一样的,Ansible提供变量和模板(Jinja2)机制。问题来了,Ansible的做用域分为几级呢?

一样的,咱们能够不能容忍把全部的task都写在一个文件里。Ansible是如何组织代码的呢?Ansible提出role的概念。是的,扮演共同职责的task,咱们把它们归到同一个role中。因此,咱们文件结构也变了,由原来的只有两个文本文件,到如今须要新建目录了:

├── hosts

├── playbook.yml

└── roles

    └── mysql

        ├── tasks

        │   └── main.yml

        └── templcates

            └── my.cnf.j2

这时,playbook.yml:

    ---
    - hosts: web
      tasks:
        - name: install nginx
          apt: name=nginx state=latest

    - hosts: db
      var:
        mysql_password: '123456'
      roles:
        - mysql

而main.yml:

- name: install mysql
  yum: name={{item}}
  with_items:
    - 'mysql-common'
    - 'mysql-client'
    - 'mysql-server'

- name: configurate mysql-server
  template: src=my.cnf.j2 dest=/etc/mysql/my.cnf

- name: start service
  service: name=mysql state=started

就是task和role之间的关系。那task之间的关系,role之间的关系呢?

Ansible的DSL就是这样组织代码的。最后一个问题如何获得执行结果呢?这就要说到Ansible的原理:Ansible将本地的yml文件编译成python代码,而后传到受控机器,受控机器执行结果以Json格式返回。

Ansible的入门很是简单。

小结

1. 如何与受控机器通讯

     只要主控机器与受控机器双方将有SSH

2. 如何组织成百上千台机器

     使用Invenstory管理

3. DSL的设计与编译

     * 组织代码的方式

          Ansible Language的入口就是playbook。你能够直接在playbook里加`tasks`。很天然,咱们想到的这个tasks里是一批小task。事实的确是这样,可是在Ansible中,它叫module。可是,咱们不但愿全部的task写在同一个文件中,这时,Ansible的role机制就起做用了。你能够把完成同一职责的一批放在一个role中就行了。

          深刻学习:module之间的依赖问题,if-else问题

     * 变量定义:不一样的级别有不一样的定义方法 

          深刻学习:了解变量的做用域

     * 模板:使用Jinja2

4. 如何获得执行结果

     受控机器主动将执行结果发送给主控机器。

总结

面对层出不穷的新编程语言、新框架、新概念,咱们程序员老是学不完。诚然,我假设你们都爱学习,可是,咱们更须要问:学到的这些东西,到底属于解决域仍是问题域。因此,来了新东西,我老是要问这东西解决了什么问题?为何它能解决,依据是什么?咱们须要的是问题的本质和问题的解决模型,而不是别人葫芦里的药。

说远了。实际上,除了上述的仨自动化配置管理工具,市面还有不少别的。总的来,我以为均可以用以上思路去学习。回到咱们最开始的问题:Puppet,Chef,Ansible的共性是什么? 我能不能说那四个问题就是它们的共性,答案是我也不知道。

最后,我申明:除了上述工具,我没有将“思路”应用到其它工具。但愿应用到的同窗给我反馈。谢谢。

相关文章
相关标签/搜索