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的共性是什麼? 我能不能說那四個問題就是它們的共性,答案是我也不知道。

最後,我申明:除了上述工具,我沒有將「思路」應用到其它工具。但願應用到的同窗給我反饋。謝謝。

相關文章
相關標籤/搜索