1. 程式人生 > >chef之cookbook入門簡明手冊

chef之cookbook入門簡明手冊

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

                本手冊基於markdown編寫,因為論壇不能刪除markdown檔案,壓縮後,由不方便故這裡上傳生成後的pdf(),令附上,markdown內容,方便大家自行修改編輯

===================================================

---
layout: post
title: "chef之cookbook絕對簡明手冊"
date: 2012-12-28 21:09
comments: true
categories: linux
---


### 1:楔子:

從前有個名叫chef的大廚,功夫甚為了得,通過如下幾招:
<pre>
* 1:rpm-Uvh
http://rbel.frameos.org/rbel6

* 2:yum -y install rubygem-chef-server
* 3:setup-chef-server.sh
</pre>
便能在一個叫做centos6.*(可能在其它地方也可以)的地方快速搭建一個內部餐廳,
然後在來招
<pre>
knife configure -i
</pre>
便能把餐廳給裝修了
餐廳好了,該需要客人了,既然是內部客人,就該有自己的內部vip的憑著,於是chef大廚把“/etc/chef/validation.pem"當做內部餐廳的vip憑證,客人只要練這樣一招"curl -L
http://opscode.com/chef/install.sh
| bash",然後在自己的"/etc/chef/client.rb",寫上chef大廚餐廳的地址,自己的名字,自己需要的一些東西,類似這樣:
<pre>
cat /etc/chef/client.rb
log_level :info #整個菜譜執行過程的記錄級別
log_location STDOUT
chef_server_url 'http://172.58.0.61:4000' #chef大廚餐廳的地址
validation_client_name 'chef-validator'
node_name "172-58-0-64" #自己的名字
</pre>
然後在來招:
<pre>
chef-client
</pre>
就成為chef大廚餐廳的免認證,唯一VIP會員了。

這麼複雜的東西,chef大廚也自寫了一套祕籍:knife,來進行管理,knife種有不同的招式,來對不同的方面進行不的處理;使用起來也相當方便

比如給客人新加一道菜(客戶吃什麼也都是chef控制的,東西怎麼做也是chef控制的,太霸道了)
<pre>
knife node  run_list add client1 nova
</pre>
在去掉一道菜:
<pre>
knife node  run_list remove client1 nova
</pre>
這套祕籍還有很多的招式,預知詳情,輸入knife,按回車

不過呢,chef大廚認為自己廚藝比較高,怕餐廳工作都自己搞,人多了忙不過來,就定了個規矩,上菜,只上菜譜,而且不主動給送(就是這麼霸道);客戶要什麼菜,告訴大廚,大廚給你發過去;這樣大廚就比較輕鬆了,客戶累了一點(也就是是說真正幹事的人是客戶自己,客戶在自己的家裡按照菜譜做自己要的菜);不過這樣做的好處就是可以用較低的成本,完成較多的工作;而且呢,客戶也可以是一些小客戶,在地球上沒有獨立或者固定地址的客戶。

既然這樣,也就是說,如果是僅僅是想到不錯的菜,對菜的具體加工過程沒有潔癖的話,只要去研究這個菜譜怎麼搞,就好了;如果有潔癖,想吃很好的菜,那就去研究這個餐廳的工作機制吧,我想要的是吃上不錯的菜,那麼我就去研究這個菜譜怎麼寫;而我是個懶人,實現我的效果即可



### 2:菜譜(cookbook)

先看眼菜譜是什麼樣的

<pre>
tree openssh
openssh
├── attributes
│   └── default.rb
├── files
│   └── default
│       └── mysh
├── metadata.rb
├── recipes
│   ├── default.rb
│   └── pubkeys.rb
└── templates
    └── default
        ├── ssh_config.erb
        └── sshd_config.erb
6 directories, 7 files
</pre>


#### 1:我需要什麼
* 1: 變數(attributes)
  儲存那些會變,用的比較多,而且不常變的東西,也是就說這裡儲存的都是預設配置

  在這個檔案裡定義
penssh/attributes/default.rb

  可以這樣定義:
  <pre>
  default['myssh'] = 'False'
  default['openssh']['server']['port'] = "22222"
  default['openssh']['server']['protocol'] = "2"
  default['openssh']['client']['forward_x11'] = "no"
  default['openssh']['client']['gssapi_authentication'] = "no"
  </pre>
  以default(因為這裡都是預設配置嘛)開頭,然後可以通過已中括號括起的字串(用引號標起來了嘛)一級級的來組織我們的變數

* 2: 模板(templates)
  整體確定,部分內容會有變化的文字檔案;一般這裡儲存的都是配置檔案,所以每個配置檔案都是獨立的模板,存放openssh/templates資料夾下的

  模板檔名字可以自定義,以.erb結尾

  比如:
  <pre>
  cat openssh/templates/default/sshd_config.erb
\<% @settings.each do |key, value| %>
\<%         if value.kind_of? Array %>
\<%                 value.each do |item| %>
\<%=                 "#{key.split("_").map { |w| w.capitalize}.join} #{item}" %>
\<%                 end %>
\<%         else %>
\<%=         "#{key.split("_").map { |w| w.capitalize}.join} #{value}"%>
\<%         end %>
\<% end %>
  Port <%= @%node['openssh']['server']['port']>
  <% if ['openssh']['server']['port'] == 2222 %>
  Protocol <%= @node['openssh']['server']['protocol'] %>
  <% else %>
  Protocol 1 %>
  <% end %>
  </pre>

  這裡實際就是ruby的語法(雖然我看不懂),上面那段比較複雜,看起來是一個迴圈,先不看;看看後面的簡單的,可以對變數進行賦值
  看看後面那2行生成的內容是什麼樣的(哎,由果推因,無知啊)
  <pre>
  Port 2222
  Protocol 2
  </pre>
  原來<%= @var%>,var那裡寫上變數名就行了,這個變數名可以直接從第一步定義的變數中匯入,只需要將那個"變數"中的default,變成node就可以了. 通過這樣,我們可以對配置檔案做基本的操作,只需要我們將變數按我們的思路去處理,也就是在這裡對變數的處理是最主要的,一會就說怎麼讓配置檔案中的變數搞的飛起來

  還有一點,猜對了,如果"變數"中的port那項不是2222的話,protocol就是1了

* 3: 檔案(files)

  100年都不變的各種檔案(\*.av,\*.:,\*.bin...)
  存在這裡(openssh/files/default)的檔案,會原封不懂的給客戶,至於放到客戶的什麼地方,就有後面提到的動作控制了




#### 2:我做什麼

有了上面的準備工作,下面就該操刀實幹了

* 操作(recipes)

  openssh/recipes 下的以.rb結尾的檔案稱為一個個recipe,這裡記錄了每一步該用什麼,怎麼用,做什麼;從前到後依次執行

##### Where there is a shell, there is a way !

shell是如此的強大,但是如果全部用shell來實現我們的需求,那我們剛才花的那幾分鐘豈不是白花了,不能讓他白花;看看shell之外有有那些路可以走。

首先整理下,一般情況下,我們都需要對系統做什麼操作(當然,我們需要的所有東西,基本都可以通過執行一個命令來實現,我認為,從某個角度來說,這就是對shell的另一種實現)

操作上有以下幾種:

模式:
<pre>
模式 “模式名” do
  。。。 #這裡寫你
  。。。 #要做的操
  。。。 #做
end
</pre>

模式只能使用chef定義好的,模式名,可以自定義

* 1:對資料夾進行操作

  - 1:建立一個資料夾
  <pre>
   directory "/root/script" do
     owner "root"
     group "root"
     mode  0755
     action :create
   end
  </pre>

  建立一個/root/script的資料夾,使用者屬於root,使用者組也屬於root,許可權為0755

  - 2:刪除一個資料夾
  <pre>
  directory "/root/script" do
     recursive true  #遞迴刪除
     action :delete
  end
  </pre>

  不要懷疑,剛看起來,對一個資料夾操作都要這麼多行程式碼,真麻煩

* 2:對普通檔案進行操作
  - 1 建立一個檔案
  <pre>
  file "/root/123.txt" do
    owner "root"
    group "root"
    mode 00755
    action :create
  end
  </pre>
  - 2 刪除一個檔案
  <pre>
  file "/root/123.txt" do
    action :delete
  end
  </pre>



* 3:對命令進行操作
  - 2 執行一個命令
   <pre>
   execute "upgrade script" do
     command "shutdown -h 0"
     action :run
   end
   </pre>

  command 決定了執行什麼命令(裡面寫什麼就執行什麼)
  action 怎麼操作做這個命令(這是個通用選項),這裡寫的是執行,也是每次到這裡都會執行這個命令
  這個操作每執行一次,你的系統就關機一次
* 5:對軟體包進行操作
  支援redhat,debian系,忽視yum與apt,這個可以寫出2種系統都相容的命令
  - 1 安裝一個軟體
  <pre>
  package "dstat" do
      action :install
  end
  </pre>

  在模組名出寫上,要操作的包的名字

  action 決定怎麼操作做個包
    * install:安裝
    * upgrade:升級
    * remove:解除安裝(centos系列種,相當於yum remove,刪除了軟體包,但沒有刪除配置檔案)
    * purge:解除安裝包,刪除配置檔案
* 6:對模板進行操作

  個人認為:linux種對各種服務的配置與管理,基本上就是對配置檔案的管理了,這裡要多花精力
  <pre>
  address = '192.168.0.'+node['ipaddress'].split('.')[2]
  port = node['mysqld']['port']
  template "/etc/my.cnf" do
    source "my.cnf.erb"
    owner "mysql"
    group "mysql"
    mode 0755
    variables bindipaddress=>addressport=>port
  end
  </pre>
  在template開始之前,可以對變數做各種處理,以達到我們想要的結果(處理的語法就是ruby的語法),在variables種可以放多個變數;這裡我們也可以使用客戶的一些資訊,比如客戶IP(node['ipaddress'])等,通過對定義的變數和客戶的一些資訊,在加上一些處理,基本就能滿足大部分需求了。
  這個template會去"openssh/templates"檔案下招my.cnf.erb這個檔案,而且這個檔案可能是這樣的
  <pre>
  \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
  bind-address = <%= @address %>
  \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
  \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
  port = <%= @port %>
  </pre>

  而最終系統的/etc/my.cnf就會是這樣:
  <pre>
  \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
  bind-address = 192.168.0.45
  \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
  \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
  port = 3306
  </pre>

* 7:對檔案(1小節種提到的檔案)進行操作
  這個主要是檔案上傳的功能,上傳制定檔案到客戶的制定位置,並給與制定的許可權
  <pre>
  cookbook_file "/root/script/src/mysql-5.1.56.tar.gz" do
    source "mysql-5.1.56.tar.gz"
    mode '0700'
    owner 'root'
    group 'root'
  end
  </pre>
  這個會去openssh/files/default目錄下找名字為mysql-5.1.56.tar.gz的檔案,然後上傳到客戶的/root/script/src/mysql-5.1.56.tar.gz,並切使用者和使用者組都是root,許可權是0700

邏輯上有以下幾種:

* 1:執行一個簡單的操作

  通過上面的各種模式,就可以定義一個簡單的操作了
* 2:執行操作甲後,執行另一個操作乙

  首先相信這樣的需求,如果讓我設計,我會怎麼實現他:

  - 1:首先,操作甲執行完後,要通知乙下,告訴乙做什麼,怎麼做;這樣乙才知道什麼執行;
  - 2:而且操作乙平常是不執行的,否則等甲的通知也每什麼意義了(當然有一種蛋都疼的情況就是,操作乙平常需要執行,操作甲執行後也需要執行)

  看看chef的實現方式:
  <pre>
  template "/etc/nginx/nginx.conf" do
    notifies :run, "execute[nginx_reload]", :immediately
  end
  execute "nginx_reload" do
    command "nginx -s reload"
    action :nothing
  end
  </pre>
  notifies:標記了,什麼時候通知什麼操作,做什麼動作

  官方文件是這樣寫的:
  <pre>
  notifies :action, "resource_type[resource_name]", :notification_timing
  </pre>
  那麼“notifies :run, "execute[nginx_reload]", :immediately”,這句話翻譯成人話,就是這樣的,馬上執行名字為”nginx_reload”的命令;在結合場景翻譯下:在對/etc/nginx/nginx.conf配置完成後,馬上執行名字為”nginx_reload”的命令。

  action: 這裡的nothing,標記了該操作,平時的不作為,如果吧這裡的nothing改為run,那麼就滿足了上面說到的蛋蛋都疼的需求

* 3:這個操作,只在某種條件下執行

  其實這裡應該有2個,該操作在某種條件下執行或者不執行:
  - 1:只有在什麼情況下才做這個操作
  - 2:在這種情況下就不做這個操作

  如果將上面2個需求翻譯成英語就是
  - 1 nly if
  - 2:not if

  翻譯成chef的語法就是:(會英語就是好啊,學技術都這麼快,語法都能猜出來了)
  - 1 nly_if
  - 2:not_if

  only_if和not_if的意義雖然不一樣,但是用法是一樣的

  這種情況下chef的操作程式碼就像是這樣:
  <pre>
  execute 'restart_mysql' do
    command '/etc/init.d/mysqld restart'
    not_if 'netstat -ln |  grep 3306'
  end
  </pre>
  這個翻譯成人話就是執行"netstat -ln|grep 3306",如果有輸出,就不執行這個命令來,否則執行

* 8:必殺技之執行shell指令碼

  既然有shell,就能實現我們的功能,如果上面的那些還滿足不了,或者就喜歡寫shell指令碼,那麼這個就是最好也是唯一的選擇了

  <pre>
  script "install_nginx" do
    interpreter "bash"
    user "root"
    cwd "/usr/local/src"
    code <<-EOH
    wget 'http://nginx.org/download/nginx-1.2.1.tar.gz'
    tar -xzvf nginx-1.2.1.tar.gz -C /usr/local/src/
    git clone https://github.com/yaoweibin/nginx_tcp_proxy_module.git \
    /usr/local/src/nginx_tcp_proxy_module
    cd /usr/local/src/nginx-1.2.1/
    patch -p1 <  /usr/local/src/nginx_tcp_proxy_module/tcp.patch
    ./configure --add-module=/usr/local/src/nginx_tcp_proxy_module
    make
    make install
    EOH
    not_if 'ls /usr/local/src | grep nginx'
  end
</pre>

interpreter:這裡指定了bash,既然需要制定,就說明這裡的選擇不僅僅一個,還應該有其它,還真猜對了,這裡可以寫 bash csh perl ruby python(當然應該是bash用的多些吧,反正如果讓我執行py的話我是不會這樣寫的)

user:指定了在什麼目錄下執行,當然在指令碼的第一行,cd 到某個目錄也是一樣的效果

code:指定了結尾的logo,這裡,從當前行開始,一直到,EOH中間的都是要執行的指令碼

not_if 在這裡加了個判斷,只要在/usr/local/src目錄下沒有名字nginx的檔案時,才執行這個指令碼

有了上面這些東西,就能夠編寫基本的cookbook的編寫(我就僅僅知道這麼多),但有時候,我們需要,對某一個更深入些,那麼就去:[ http://wiki.opscode.com/display/chef/Resources#Resources-Script],就會得到你需要的了


### 3:雜項

* 1:變數

  - 1:針對一個使用者的變數

    在attributes種可以定義預設的變數,但是有些時候,在莫個變數上,有些使用者希望是自己獨有的,這種情況,我們可以針對改使用者做修改
   每個使用者的情況,在chef餐廳裡都有儲存,可以通過"knife node"對使用者進行操作:

    <pre>
   \# knife node
   FATAL: Cannot find sub command for: 'node'
   Available node subcommands: (for details, knife SUB-COMMAND --help)
   \*\* NODE COMMANDS \*\*
   knife node create NODE (options)
   knife node run_list add [NODE] [ENTRY[,ENTRY]] (options)
   knife node list (options)
   knife node edit NODE (options)
   knife node bulk delete REGEX (options)
   knife node show NODE (options)
   knife node from file FILE (options)
   knife node delete NODE (options)
   knife node run_list remove [NODE] [ENTRIES] (options)
   </pre>
  比如要對莫個客戶做調整:
  <pre>
  \#knife node edit client_name
  </pre>
  會開啟一個編輯器,顯示當前使用者的配置檔案(json格式,要注意裡面的東西一定要是json的否則,如果你些了半天,有個標點符號錯,然後儲存了,恭喜,你剛才些的白寫了),
  <pre>
  {
    "name": "client_name",
    "run_list": [
      "recipe[os_init]",
      "recipe[openssh]"
    ],
    "normal": {
      "openssh": {
        "server": {
        },
        "client": {
        }
      },
      "manage": {
        "extra": [
          "user1",
          "user2"
        ]
      },
      "tags": [
      ]
    },
    "chef_environment": "_default"
  }
  </pre>
  在normal的下面有這樣一段:
  <pre>
  "manage": {
    "extra": [
      "user1",
      "user2"
    ]
  },
  </pre>
  在recipe或者template中,如何使用這些資訊呢?
  通過 node['manage']['default'] 這個變數就可以獲取到一個內容為有2個元素('user1','user2')


  - 2:讓一批使用者都可以用的變數

    在attribute中的變數,所有使用者都可以使用,在針對客戶定義的變數,只有這個用才可以使用,但是如果有一批使用者都需要這個變數呢?給這批使用者一個個加多累啊,在說要改這個變數的時候,在一個個改更累,我們需要一個環境級別的變數,使用者需要的時候,匯入這個環境,即可。

        在chef中和cookbook並級的,還以一個environments的資料夾,這裡就時儲存各種變數的配置檔案的
    <pre>
    \# knife environment
    FATAL: Cannot find sub command for: 'environment'
    Available environment subcommands: (for details, knife SUB-COMMAND --help)
    \*\* ENVIRONMENT COMMANDS \*\*
    knife environment list (options)
    knife environment create ENVIRONMENT (options)
    knife environment edit ENVIRONMENT (options)
    knife environment show ENVIRONMENT (options)
    knife environment delete ENVIRONMENT (options)
    knife environment from file FILE (options)
    </pre>

    隨便寫個儲存環境變數的檔案:
    <pre>
    vim environments/new.json
    </pre>
    這個也會開啟一個編輯器,然活可以在裡面寫json格式的資料
    <pre>
    {
      "name": "openstack",
      "description": "",
      "cookbook_versions": {
      },
      "json_class": "Chef::Environment",
      "chef_type": "environment",
      "default_attributes": {
        "mysql": {
          "allow_remote_root": true,
          "root_network_acl": "%"
        }
      },
      "override_attributes": {
        "developer_mode": false,
        "enable_monit": true,
        "manage": {
          "extra": [
            "user3",
            "user4"
         ]
      }
      }
    }
    </pre>
    這個檔案裡面需要注意的有3塊:name default_attirbute override_attribute

    name:當前環境的名字
    default_attribute:該環境中的預設屬性
    override_attribute:該環境中的會覆蓋到預設屬性的屬性

    環境變數定義好後,需要匯入
    <pre>
    \# knife environment from file environments/new.json
    Updated Environment openstack
    </pre>
    環境變數匯入後,在chef中就存在了,客戶在要用的時候,還需要做一個操作:修改客戶的配置:
      <pre>
      #knife node edit client_name
      {
        "name": "client_name",
         \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
        "chef_environment": "openstack"
      }
      </pre>
    將chef_environment對應的值改成剛剛定義的變數名,在然後,在recipe或者template中,通過 node['manage']['default'] 這個變數就可以獲取到一個內容為有2個元素('user3','user3')

* 2:針對特定型別的客戶做不同處理

    比如我們需要對cenotos和ubuntu的系統分別上傳不同的檔案,或者不同的模板。當然我們可以在recipes種通過系統的型別來判斷,做不同的操作。chef還給我們提供了一種方法。

    我們注意到在files和template些都有個default的資料夾,也是時預設從這裡找我們需要的模板和檔案;是不是centos的客戶會首先重centos的資料夾下找呢?對就是這樣的

    我們需要往不同的系統上傳不同的檔案或者模板時,只需要建立相應的資料夾,然後在資料夾種方式和default種同名的檔案,不同的系統的客戶,就會先來該系統的目錄下查詢,找不到,在去default目錄下查詢的
           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述