Learn Chef Rally 學習筆記 part7 — 除錯及定期執行 chef-client

Luyo
verybuy-dev
Published in
28 min readAug 24, 2017

本文記錄 Learn Chef Rally 以下章節的學習歷程:

[Resolve a failed chef-client run]

這個章節要來學怎麼除錯。

設定網頁擁有者

現在要來修改 learn_chef_httpd 這個 cookbook 預設的 recipe,讓 /var/www/html/index.html 的擁有者為 web_admin 這個 user,讓它可以被 web_admin 讀寫,而其他帳號只能唯讀。

先回顧一下這個 recipe 長什麼樣子:

$ cat recipes/default.rb
#
# Cookbook Name:: learn_chef_httpd
# Recipe:: default
#
# Copyright (c) 2016 The Authors, All Rights Reserved.
package 'httpd'
service 'httpd' do
action [:enable, :start]
end
template '/var/www/html/index.html' do # ~FC033
source 'index.html.erb'
end

我們可用 template resourcemode , owner , group 屬性來設定檔案權限,像這樣:

package 'httpd'service 'httpd' do
action [:enable, :start]
end
template '/var/www/html/index.html' do
source 'index.html.erb'
mode '0644'
owner 'web_admin'
group 'web_admin'

end

套用變更到 node 上

接下來要做前面的章節學過的流程來套用新的變更到 node 上:

  1. 更新版號
  2. 上傳到 Chef server
  3. 執行 chef-client 指令

先來修改 metadata.rb 裡的版號為 0.3.0:

name 'learn_chef_httpd'
maintainer 'The Authors'
maintainer_email 'you@example.com'
license 'all_rights'
description 'Installs/Configures learn_chef_httpd'
long_description 'Installs/Configures learn_chef_httpd'
version '0.3.0'
issues_url 'https://github.com/learn-chef/learn_chef_httpd/issues' if respond_to?(:issues_url)
source_url 'https://github.com/learn-chef/learn_chef_httpd' if respond_to?(:source_url)

再來是上傳到 Chef server:

$ knife cookbook upload learn_chef_httpd
Uploading learn_chef_httpd [0.3.0]
Uploaded 1 cookbook.

最後透過 knife ssh 讓 node 去下 chef-client 的指令:

$ knife ssh 'name:node1-centos' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
172.31.21.70 Starting Chef Client, version 13.3.42
172.31.21.70 resolving cookbooks for run list: ["learn_chef_httpd"]
172.31.21.70 Synchronizing Cookbooks:
(...略)
172.31.21.70 * cannot determine user id for 'web_admin', does the user exist on this system?
172.31.21.70 ================================================================================
172.31.21.70 Error executing action `create` on resource 'template[/var/www/html/index.html]'
172.31.21.70 ================================================================================
172.31.21.70
172.31.21.70 Chef::Exceptions::UserIDNotFound
172.31.21.70 --------------------------------
172.31.21.70 cannot determine user id for 'web_admin', does the user exist on this system?
172.31.21.70
(...略)
172.31.21.70 Running handlers:
172.31.21.70 [2017-08-24T16:30:10+00:00] ERROR: Running exception handlers
172.31.21.70 Running handlers complete
172.31.21.70 [2017-08-24T16:30:10+00:00] ERROR: Exception handlers complete
172.31.21.70 Chef Client failed. 0 resources updated in 13 seconds
172.31.21.70 [2017-08-24T16:30:11+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
172.31.21.70 [2017-08-24T16:30:11+00:00] FATAL: Please provide the contents of the stacktrace.out file if you file a bug report
172.31.21.70 [2017-08-24T16:30:11+00:00] ERROR: template[/var/www/html/index.html] (learn_chef_httpd::default line 12) had an error: Chef::Exceptions::UserIDNotFound: cannot determine user id for 'web_admin', does the user exist on this system?
172.31.21.70 [2017-08-24T16:30:11+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)

中間的很明顯會看到錯誤訊息,說沒有 web_admin 這個 user。

除錯

我們必須先在 node 上新增 web_admin 這個帳號和群組,然後再重新跑一次前一節的流程。新增帳號及群組的方式當然也是定義在 recipe 上,而不是直接連進去開帳號。

群組和帳號的 resources 分別是 groupuser

package 'httpd'

service 'httpd' do
action [:enable, :start]
end

group 'web_admin'

user 'web_admin' do
group 'web_admin'
system true
shell '/bin/bash'
end


template '/var/www/html/index.html' do
source 'index.html.erb'
mode '0644'
owner 'web_admin'
group 'web_admin'
end

記得一定要先定義 group 再定義 user !這部份跟平常開帳號的思維不太一樣。

接下來重跑一次更新流程,先更新版號,因為這算是修 bug,所以版號可訂為 0.3.1:

name 'learn_chef_httpd'
maintainer 'The Authors'
maintainer_email 'you@example.com'
license 'all_rights'
description 'Installs/Configures learn_chef_httpd'
long_description 'Installs/Configures learn_chef_httpd'
version '0.3.1'
issues_url 'https://github.com/learn-chef/learn_chef_httpd/issues' if respond_to?(:issues_url)
source_url 'https://github.com/learn-chef/learn_chef_httpd' if respond_to?(:source_url)

然後上傳:

$ knife cookbook upload learn_chef_httpd
Uploading learn_chef_httpd [0.3.1]
Uploaded 1 cookbook.

最後透過 knife ssh 讓 node 執行 chef-client

$ knife ssh 'name:node1-centos' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
172.31.21.70 Starting Chef Client, version 13.3.42
172.31.21.70 resolving cookbooks for run list: ["learn_chef_httpd"]
172.31.21.70 Synchronizing Cookbooks:
172.31.21.70 - learn_chef_httpd (0.3.1)
172.31.21.70 Installing Cookbook Gems:
172.31.21.70 Compiling Cookbooks...
172.31.21.70 Converging 5 resources
172.31.21.70 Recipe: learn_chef_httpd::default
172.31.21.70 * yum_package[httpd] action install (up to date)
172.31.21.70 * service[httpd] action enable (up to date)
172.31.21.70 * service[httpd] action start (up to date)
172.31.21.70 * group[web_admin] action create
172.31.21.70 - create group web_admin
172.31.21.70 * linux_user[web_admin] action create
172.31.21.70 - create user web_admin
172.31.21.70 * template[/var/www/html/index.html] action create
172.31.21.70 - change owner from 'root' to 'web_admin'
172.31.21.70 - change group from 'root' to 'web_admin'
172.31.21.70 - restore selinux security context
172.31.21.70
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 3/6 resources updated in 11 seconds

更新 node 成功了,最後來 curl 確認一下網頁無誤:

$ curl 172.31.21.70
<html>
<body>
<h1>hello from ip-172-31-21-70.ap-southeast-1.compute.internal</h1>
</body>
</html>

測驗

True or False: When a change fails on your node, you must manually patch your system before you can run chef-client again.

  • True.
  • False.

According to Semantic Versioning, which component typically relates to bug fixes?

  • MAJOR
  • MINOR
  • PATCH

答案是 2, 3。

這個章節雖然到這邊結束了,但我有個疑問,因為 recipe 是定義一個 node 應該要長得什麼樣子,那照這樣說來,如果 node 上本來有的帳號,在執行 chef-client 之後是不是應該要被砍掉?

聽起來有點怪,來實驗一下,先 ssh 到 node 裡新增一個帳號 test

$ sudo useradd test

然後回到工作站,執行 knife ssh

$ knife ssh 'name:node1-centos' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
172.31.21.70 Starting Chef Client, version 13.3.42
172.31.21.70 resolving cookbooks for run list: ["learn_chef_httpd"]
172.31.21.70 Synchronizing Cookbooks:
172.31.21.70 - learn_chef_httpd (0.3.1)
172.31.21.70 Installing Cookbook Gems:
172.31.21.70 Compiling Cookbooks...
172.31.21.70 Converging 5 resources
172.31.21.70 Recipe: learn_chef_httpd::default
172.31.21.70 * yum_package[httpd] action install (up to date)
172.31.21.70 * service[httpd] action enable (up to date)
172.31.21.70 * service[httpd] action start (up to date)
172.31.21.70 * group[web_admin] action create (up to date)
172.31.21.70 * linux_user[web_admin] action create (up to date)
172.31.21.70 * template[/var/www/html/index.html] action create (up to date)
172.31.21.70
172.31.21.70 Running handlers:
172.31.21.70 Running handlers complete
172.31.21.70 Chef Client finished, 0/6 resources updated in 11 seconds

看起來不像有把 test 帳號砍掉的動作,連進 node 去確認:

$ id test
uid=1001(test) gid=1002(test) groups=1002(test)

帳號確實還在,所以確認了這件事:Chef 不會把既有 user 及 group 砍掉。

[Run chef-client periodically]

前面我們學了怎麼用 knife 來更新 node 的設定,我們可以有需要的時候自己下 knife 指令,也可以使用持續交付 (continuous delivery 簡稱 CI)系統如 Chef Automate,讓系統在特定條件發生時自動觸發更新的動作,例如版本控制系統有更新的時候。

另外你可能也會希望定時跑 chef-client 以避免機器的設定發生非預期的更動。

本章節會利用 chef-client cookbook 去讓 node 定期跑 chef-client 指令。我們將會學到:

  • 使用 Chef Supermarket 上的 community cookbooks
  • 使用 Berkshelf 解決 cookbook 的相依套件問題
  • 使用 role 定義 node 的屬性和它要執行的 run-list

這是本 module 的最後一個章節了,所以也會學到如何從 Chef server 上移除 cookbook 及 node 來清空環境。

可以在 Deploy infrastructure changes with Chef Automate 這個 module 學到 Chef Automate的工作流程

取得 chef-client 這個 cookbook

要定期執行 chef-client 指令,最簡單的想法就是設定 cron job,但這方法已經太遜了,這一節我們要從 Chef Supermarket 抓一個叫 chef-client 的 cookbook 下來。

我們可以直接用 knife supermarket 指令取得 cookbook,但 knife supermarket 不會幫我們解決套件相依的問題,因此我們要用的是 Berkshelf 這個套件管理工具來安裝。

首先要進到 ~/learn-chef 資料夾下:

$ cd ~/learn-chef

接下來要產生一個設定檔告訴 Berkshelf 你想要哪些 cookbooks 以及去哪裡抓。新增檔案 ~/learn-chef/Berksfile 內容如下:

source 'https://supermarket.chef.io'
cookbook 'chef-client'

https://supermarket.chef.io 是 Chef 公開的 supermarket 站點,你也可以自架一個自己的 private Chef Supermarket server。

接下來執行 berks install 下載 cookbook 以及其相依套件:

$ berks install
Resolving cookbook dependencies...
Fetching cookbook index from https://supermarket.chef.io...
Installing compat_resource (12.19.0)
Installing chef-client (8.1.8)
Installing cron (4.1.3)
Installing logrotate (2.2.0)
Installing ohai (5.2.0)
Installing windows (3.1.2)

看起來真不錯,Chef 已經幫我們內建了套件管理工具,要灌什麼應該都會很方便。

Berkshelf 會下載 chef-client 這個 cookbook 以及相關的 cookbook 到 ~/.bershelf/cookbooks 這個資料夾底下:

$ ls ~/.berkshelf/cookbooks/
chef-client-8.1.8 compat_resource-12.19.0 cron-4.1.3 logrotate-2.2.0 ohai-5.2.0 windows-3.1.2

接下來上傳 chef-client 及其相依的 cookbooks 到 Chef server 上,雖然我們也可以用之前學的 knife cookbook upload 但會比較麻煩,取而代之我們可以用 berks upload 指令:

$ berks upload

看起來 Berksfile 只是一個定義檔,並不會下載 cookbooks 到 ~/learn-chef 資料夾下,而是下載到家目錄的 ~/.berkshelf 底下,這樣就可以節省 repository 的大小,這讓我想到 Dockerfile 的做法也有點類似。

產生 role

現在 chef-client cookbook 已經傳上 Chef server 了,接下來要將它加入 node 的 run-list 之中,另外還要設定執行 chef-client 的頻率。我們將會用 role 來搞定這兩件事情。

有兩個屬性用來控制 chef-client 的頻率 (source code):

  • node['chef_client']['interval'] – 每隔幾秒要跑一次 chef-client。預設值為 1,800 秒 (30 分鐘)。
  • node['chef_client']['splay'] – 這個屬性是為了避免 Chef server 在同一時間被所有 node 一起操爆而生的,它會在設定的時間內隨機挑一個時間點去執行 chef-client 指令。預設值是 300 秒 (5 分鐘)。

由以上兩個設定可以推知預設每 30–35 分鐘會在 node 上執行 chef-client ,實際上的間隔當然就還是要看個人需求。這邊的範例中我們會用 interval = 300 秒、splay = 60 秒,所以每 5–6 分鐘會跑一次 chef-client

我們要用 role 來完成接下來的設定。Role 這個東西可以把特定的機能打包起來變成一個設定檔,例如你可以有 web server role、database role、load balancer role 等等。現在我們要來新增的 role 會叫 web

Roles 會以物件的型式被儲存在 Chef server 上。雖然可以直接以 knife role create 指令去產生 role,但這邊我們要用的方式是撰寫一個 JSON 檔,然後執行 knife role from file 指令去產生這個 role,這樣才利於版本控制。

先開一個資料夾 ~/learn-chef/roles

$ mkdir ~/learn-chef/roles

然後新增一個檔案 ~/learn-chef/roles/web.json 內容如下:

{
"name": "web",
"description": "Web server role.",
"json_class": "Chef::Role",
"default_attributes": {
"chef_client": {
"interval": 300,
"splay": 60
}
},
"override_attributes": {
},
"chef_type": "role",
"run_list": ["recipe[chef-client::default]",
"recipe[chef-client::delete_validation]",
"recipe[learn_chef_httpd::default]"
],
"env_run_lists": {
}
}

可以看到這個檔案設定了 interval 及 splay 這兩個屬性,也設定了 chef-clientlearn_chef_httpd 這兩個 cookbook 在 run-list 中。

有沒有發現一個不知道是幹嘛的 recipe chef-client::delete_validation ?它是用來刪掉在 bootstrap 程序期間會產生的用來認證的檔案,之後不會再用到了,所以可以用這個方式將它刪掉。

再來執行 knife role from file 指令:

$ knife role from file roles/web.json
Updated Role web

確認一下 Chef server 上有哪些 roles:

$ knife role list
web

列出 web 這個 role 的詳細資訊:

$ knife role show web
chef_type: role
default_attributes:
chef_client:
interval: 300
splay: 60
description: Web server role.
env_run_lists:
json_class: Chef::Role
name: web
override_attributes:
run_list:
recipe[chef-client::default]
recipe[chef-client::delete_validation]
recipe[learn_chef_httpd::default]

最後用 knife node run_list set 將 role 套用到 node 上:

$ knife node run_list set node1-centos "role[web]"
node1-centos:
run_list: role[web]

觀察上面這道指令,可以發現 role 這個東西是基於 run-list 去打包的,也就是多個 recipe 的集合體,不知道這樣理解有沒有問題。

執行 chef-client

現在我們要把 knife ssh 裡本來的 name:node1-centos 換成 role:web ,所以如果你有很多個 node 的 role 都是成 web 的話就一個指令搞定了:

$ knife ssh 'role:web' 'sudo chef-client' --ssh-user centos --identity-file ~/.ssh/test.pem --attribute ipaddress
172.31.21.70 Starting Chef Client, version 13.3.42
172.31.21.70 resolving cookbooks for run list: ["chef-client::default", "chef-client::delete_validation", "learn_chef_httpd::default"]
(...略)
172.31.21.70 * service[chef-client] action enable
172.31.21.70 - enable service service[chef-client]
172.31.21.70 * service[chef-client] action start
172.31.21.70 - start service service[chef-client]
172.31.21.70 * systemd_unit[chef-client.timer] action stop (up to date)
172.31.21.70 * systemd_unit[chef-client.timer] action disable (up to date)
172.31.21.70 * systemd_unit[chef-client.timer] action delete (up to date)
172.31.21.70 Recipe: chef-client::delete_validation
172.31.21.70 * file[/etc/chef/validation.pem] action delete (up to date)
(...略)

可以看到 chef-client 這個 cookbook 幫我們將 chef-client 設定成了一個 service。

最後我們可以用 knife status 來確認最近有哪些 node 成功執行了 chef-client

$ knife status 'role:web' --run-list
2 minutes ago, node1-centos, ["role[web]"], centos 7.3.1611.
$ knife status 'role:web' --run-list
6 minutes ago, node1-centos, ["role[web]"], centos 7.3.1611.
$ knife status 'role:web' --run-list
0 minutes ago, node1-centos, ["role[web]"], centos 7.3.1611.

因為我們前面設定是每 5–6 分鐘會跑一次,我特地跑了好幾次這道指令,第 二次顯示 6 分鐘,再下一次顯示 0 分鐘,表示 chef-client server 是有正常運作的。

後續

現在我們已經有一個會定期自動跑 chef-client 的系統了,接著可以自己實驗一下這些事:

  1. 修改 index.html.erb 然後上傳到 Chef server,看看幾分鐘之後更新這個網頁看有沒有產生對應的變化。
  2. 連到 node 裡將 /var/www/html/index.html 砍掉,看看幾分鐘之後有沒有被還原。
  3. 撰寫自己的 cookbook,看 Chef Supermarket 上有沒有能用的,可以自己拿來改。

如果對管理 Chef server 有興趣,可以去看看 knife opc 指令,它可以讓你從工作站管理 Chef server 上的組織 (organizations) 及使用者。

將 node 從 Chef server 上移除

在工作站上將 node 從 Chef server 移除的需要下兩道指令:

$ knife node delete node1-centos --yes
Deleted node[node1-centos]

以及

$ knife client delete node1-centos --yes
Deleted client[node1-centos]

第一道指令是將 Chef server 上記錄該 node 的 metadata 做移除;第二道指令是將 Chef server 用來連線進 node 的 API client 資訊做移除 (包括 public RSA key)。

移除 Chef server 上的 cookbook

$ knife cookbook delete learn_chef_httpd --all --yes
Deleted cookbook[learn_chef_httpd][0.3.1]
Deleted cookbook[learn_chef_httpd][0.3.0]
Deleted cookbook[learn_chef_httpd][0.2.0]
Deleted cookbook[learn_chef_httpd][0.1.0]

因為我們加了 --all 這個參數,所以全部的版本都部刪光光了,如果不給這個參數的話程式就會要求你輸入指定的版本 (不早說,現在全部都被刪光沒得實驗了)。

移除 Chef server 上的 role

$ knife role delete web --yes
Deleted role[web]

移除 node 上的 RSA 私鑰

在 bootstrap 期間,RSA 私鑰會被產生並存放在 node 上,公鑰會被傳到 Chef server,Chef server 就可以跟 node 建立連線,預設的私鑰路徑是 /etc/chef/client.pem

如果有重新做 bootstrap 的需求的話,就要連線進 node 把舊的私鑰刪掉:

$ sudo rm /etc/chef/client.pem

最後,機器不用了記得關掉,Chef server 是沒辦法幫你做這件事的。

結論

  • 開一個 Chef server
  • 對一個 node 做 bootstrap
  • 將更新過的 cookbook 套用在 node 上
  • 定期在 node 上自動執行 chef-client 指令
  • template 可以吃到每一個 node 自己的屬性
  • role 來打包多個 run-list 以定義某些 server 的機能

測驗

What is Chef Supermarket?

  • A community forum where you can discuss all things Chef.
  • A site where the community shares cookbooks.
  • A site where you can purchase Chef gear.

Where do you tell Berkshelf which cookbooks you want and where to find them?

  • In the Berksfile.
  • In the metadata.rb file.
  • You call Berkshelf from within the cookbook.

What is a role?

  • A way to give a user-friendly name to a node.
  • A way to focus on the overall functionality of a node.
  • A way to define user privileges.

答案是 2, 1, 2。

呼,到這裡總算收集三個印章了。

--

--