Chef X Docker X Packer – Give Me Golden Image

背景介紹

最近開始花比較多的時間在研究 Docker ,而這篇文章把重點擺在 Image 的製作上。在大 Docker 時代來臨前,被使用最多的應該是 VM,而身為一個 DevOps,身上帶有榔頭也是非常合理的通常要幫忙準備好一個可以讓 Application 正常運行的環境,而為了省下寶貴的時間當然會用已經製作好的 Image,每個人當然都希望有 Golden Image 可以給全部的 Application 使用,不過想也知道不可能!!不然還有需要繼續往下看嗎XD 我以下圖來表達一個 Image 通常會需要具有哪些咚咚

Image Content

  1. OS: 這一層當然是最少去更動的,可能一年做個大版本更新就不錯了
  2. Environment: 這一層改動的幅度就相對大一點,譬如可能 Nginx 出了新版,多了甚麼你想要的功能,或是你想要改動 MySQL 的 Configuration
  3. Application: 假如你的公司使用了 Agile,而且採用 Continuous Delivery,那我想每天出個幾包 Build 應該不為過吧

而在 Configuration Management 出現後,開始有了 Provision 這個詞,指在 Server 開機完後的一連串安裝及設定動作,讓 Server 最終可以開始服務使用者的過程,老實說看到一台甚麼都沒有的 Ubuntu 被 Provision 之後,自動化裝了一堆東西會有莫名的成就感XD 不過這其中是有一些缺點的 1. 費時太久 2. 安裝外部套件可能出問題,這樣一來 Auto-Scale 便會失敗;那假如準備好一個全部東西都安裝好的 Image, 只需要把它開機就可以直接使用,這樣不就好了(得意貌),但是你的 Application 總是會有變動,環境也有可能改變,只是幅度比較小,所以這樣的做法靈活性會相對低,正所謂魚與熊掌不可兼得。因此準備一個已經安裝好不太會變動底層架構的 Image 加上小幅度的 Provision 是我到後來覺得可以接受的作法~

大 Docker 時代

自從使用 Docker 後,頭腦變好了考試都考一百分人變帥了也交到女朋友了 Image 變得很小,而且因為 Docker MicroService 的特性,Configuration Management 強大的功能漸漸派不上用場 (Docker 對 CM 說道: 你已經死了…),Deployment 的方式也必須要改變,目前看到兩種方式:

  1. 把 Application Build 包在 Docker Image 裡面,利用 Docker Registry 去做版控

  2. 在 Docker Container 跑起來的時候掛載一個空間 (-v 本機路徑:Container路徑),然後把 Build Deploy 本機路徑,讓多個 Container 可以共享

好像扯得有點太遠,把剛剛如何準備 Golden Image 主軸拉回來,在使用 Docker 之後,問題依舊存在,因此我開始想方法來解決這個問題,就在這個時候我看到了一個叫做 Packer 的東西

Packer 介紹

Packer 跟 Vagrant 系出同門,都是 HashiCorp 所推出的作品,是一個專門用來準備 Image的工具,而他的 Template 為 JSON 格式, 有幾個比較主要的 Module 分別如下 (更詳細的內容可以參考官方網站,而官方網站也準備了很詳細的第一次使用就上手文件,有興趣的人可以來這邊試試看):

  • User Variables: 使用者自訂參數,就像平常寫程式定義函式傳入參數的感覺
  • Builders: Packer 要建立新的 Image 時,所要參考的基底 Images 的來源,譬如 Amazon EC2(AMI) 或是 Docker
  • Provisioners: 就是進行 Provision 的方式,譬如最簡單的Shell,以及各種 CM Tool 都已經有支援了(不過我等等要講的 Chef Client 有 Bug TT)
  • Post-Processors: 就是 Provision 完的 Image 要發布去甚麼地方,譬如再次存成 AMI,或是 Push 到 Docker Hub 上面去

從這邊應該可以漸漸發現準備一個 Image 的過程已經被定義成一個 JSON 檔案,每次產生出的 Image 都是一模一樣的,而且是可以進行版控的,不會再有任何"工人智慧"介入其中,進而減少人為疏失。因為有支援一大堆的 Provisioners 的存在,可發現要對一個 Docker Image 做 Provision 是輕而易舉的事情,而 Packer 為了在 Provision 時可以更靈活使用各家工具,因此並不使用 Dockerfiles,剛好讓我那一堆 Chef Cookbook 可以再度派上用場XD

Chef X Docker X Packer

接下來直接說明如何用 Chef-Client 來當 Packer 的 Provisioner,而且是在 Builders 為 Docker 時,因此你的 JSON Template 將會包含下面三個主要的 Module: Builders, Provisioners 以及 Post-Processors

Builders:

"builders":[{
  "type": "docker",
  "image": "填入要進行 Provision 的基底 Image, 例如: Ubuntu",
  "pull": true,
  "commit": true
}]

Provisioners:

"provisioners":[
  {
    "type": "shell",
      "inline": [
      "apt-get update",
      "apt-get install -y curl",
      "sudo mkdir /tmp/packer-chef-client",
      "sudo ln -s /tmp/packer-chef-client /etc/chef"
    ]
  },
  {
    "type": "chef-client",
    "chef_environment": "Chef 的環境名稱, 例如: Nginx_Dev",
    "run_list": [ "放入你想要執行的 Recipe 或是 Role" ],
    "server_url": "輸入你的 Chef Server 的網址",
    "validation_client_name": "輸入你的 Chef Server 的 validator 的名字",
    "validation_key_path": "你的 validator pem 檔案在本機的路徑"
  }
]

因為 Packer 使用 Chef-Client 有 Bug (Packer 會把 Chef 的 Configuraiton 放到 /tmp/packer-chef-client,但是 Chef-Client 跑到預設路徑 /etc/chef/ 去找 ),所以必須要插一段 shell,把兩個資料夾連結在一起,不然你就會看到類似底下的 Error:

docker: WARN: Failed to read the private key /tmp/packer-chef-client/client.pem: #
docker: ERROR: Your private key could not be loaded from /tmp/packer-chef-client/client.pem

Post-Processors:

"post-processors": [
  [
    {
      "type": "docker-tag",
      "repository": "Image 儲存的 Repository 名稱",
      "tag": "Image 的 Tag 名稱"
    },
    "docker-push"
  ]
]

下完 packer build 應該就可以看到 provision 好的 Image 被 Push 到 Repository了,不過假如你的 Chef Server 的 Certificate 不被 Docker Container 認得,可能會出現下面的錯誤

docker: [2015-11-01T00:59:44+00:00] ERROR: SSL Validation failure connecting to host: XXX.XXX.XXX - SSL_connect returned=1 errno=0 state=error: certificate verify failed

這時候就必須要使用自行製作的 Chef client.rb Configuration Template

log_level :info
log_location STDOUT
verify_api_cert false
{{if ne .SslVerifyMode ""}}
ssl_verify_mode :{{.SslVerifyMode}}
{{end}}
chef_server_url "{{.ServerUrl}}"
{{if ne .ValidationClientName ""}}
validation_client_name "{{.ValidationClientName}}"
{{else}}
validation_client_name "chef-validator"
{{end}}
{{if ne .ValidationKeyPath ""}}
validation_key "{{.ValidationKeyPath}}"
{{end}}
{{if ne .NodeName ""}}
node_name "{{.NodeName}}"
{{end}}

記得要在 Chef-Client 的 Provisioners 裡面加上底下兩個參數,第一行同時也會被 knife.rb 用到

"ssl_verify_mode": "verify_none",
"config_template": "你客製化好的 client.rb 在本機端的路徑"

整體架構

確定好 Chef + Docker + Packer 可以運行之後,就開始來考量把整個放到 CI 的流程裡,我大概把每個 Image 裡面拆成兩個部分

  • Base: 就是在 VM 時的 OS 加 Environment,這個部分很少變動,交給 Chef 的 Cookbook 去做管理,當有需要做變動的時候直接改 Cookbook,然後 Push 到 GitLab,Jenkins 收到 GitLab Webhook 就會透過 Pakcer 自動把 Image 建立好並且 Push 到 Docker Registry
  • App: 當然就是最寶貴的 Application 啦,只要程式碼修改完畢發佈到 GitLab ,Jenkins 也是馬上收到 GitLab 的 Webhook 後開始執行 Packer 建置 Image,不過並不是從零開始,而是從剛剛已經完成的 Base Image 再往上疊,所以耗費的時間很短

Docker_Build_Image

而圖中除了 Jenkins 比較不好取代之外,其餘像是 GitLab 可以改成其他 Git Protocol 的服務,Chef 也可以替換成自己慣用的 CM (Puppet, Ansible, SaltStack …)

當然在 Docker Image 發佈到 Docker Registry 之前都要通過測試,這樣才能確保繼續往下整合的 Jenkins Deployment Job 不會把有 Bug 的 Image Deploy 到 Production 上面去,不過關於 Deployment 的部分就留到下一篇了…

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

w

連結到 %s