Google Kubernetes Engine部屬全紀錄 (Part 1. Cloud Build篇)

Kevin Cheng
Cow Say
Published in
10 min readAug 16, 2018

本文記錄著這麼一段心路歷程, 如果你也有同樣的疑惑, 希望這篇文章能帶給你一點頭緒。

相關圖片

我有一個容器化的Nodejs應用, 用Dockerfile定義並建置了映像檔(image), 然而這個應用以往都是手動運行docker run指令啟動的, 最理想的狀況應該是能夠在特定條件下(例如: git push到特定分支)自動重新建置映像檔並更新正在運行中的容器。

整體來說, 我需要一個CI/CD的自動化流程, 流程大致如下:

  1. git push 推送程式碼至遠端的存儲區(Repository), 如GitHub或Bitbucket等服務。
  2. CI階段執行測試, 若成功, 則前往(3), 若失敗, 前往(4)。
  3. CD階段會建置映像檔, 推送至容器倉儲區(Container registry)。
  4. 新建映像檔這個事件會觸發相關容器更新並重新運行。
  5. 通知我並結束流程。

目標運行平台為Google Kubernetes Engine (GKE), 想多了解一點GKE, k8s 或是Kubernetes, 可以參考這篇文章

能夠滿足上述需求的解決方案, 核心於是落在CI/CD平台的選擇, 我研究了兩個CI/CD平台如下。 由於篇幅限制, 本文將會介紹Cloud Build的部分, Codeship則會留給下一篇。

會選擇這個平台是因為他是Google自家的服務, 希望能夠省去認證帶來的額外負擔(其他平台都需要 gcloud auth命令先跟GCP認證), 並享有更方便快速的整合。

這個平台也有蠻多人在使用, 會挑選他是因為它Codeship Basic (免費)方案的環境中已經有預先安裝好的 gcloud 命令列工具可以使用。

準備階段

https://github.com/cowsay-blog/Example-GKE-Deploy

createServer((req, res) => {    const { query } = parse(req.url)    const queryObj = parseQuery(query || '')    const you = queryObj.me || 'World'    res.writeHead(200)    res.end(`Hello ${you}!\n`)})    .listen(80, () => {        console.log('Server is listening...')    })

這邊提供本文範例將會使用的Nodejs App, 這是一個很簡單的HTTP伺服器, 每當有請求的時候, 總是回應「Hello World!」, 若有GET參數 me 的話則會將該值作為對象名字, 回應「Hello <名字>!」。

FROM node:8-alpine
WORKDIR ~
COPY package.json .RUN npm iCOPY src ./src
CMD [ "npm", "start" ]

根目錄下配置一個Dockerfile提供建置映像檔的步驟清單, 採用Node 8.11.3 alpine的版本, 接著依照package.json安裝依賴並將原始碼放入容器, 使用 npm start 作為起始命令。

想當然要部屬到GKE, 你還需要一個Google專案, 如果沒有的話也請創建一個。

進到GKE的部分, 首先創建一個叢集, 叢集底下包含多個VM, 每個VM裡面會有一模一樣的容器部屬, VM可以分散在不同地理區域, 叢集是GKE最大的單位。

區域、機器類型配置、節點數量請自行斟酌, 可以搭配使用Google的計價計算機進行評估。

接著建立一個新的部屬, 裡面可能包含多個容器。 這邊所謂pod和部屬的關係其實我還沒研究得透徹, 待日後有機會再好好惡補Kubernates的底層架構了。

在這邊可以直接使用默認映象檔, 待會觸發我們的CI/CD流程後, 便會更新這個pod的映像。

部屬建置完成。

Cloud Build

本段落將依照這篇文件的步驟進行, 並提供更具體詳細的說明。

在Cloud Build中有兩種方式可以制定流程, 一種是直接使用Dockerfile, 另一種是使用YAML/JSON的設定檔。 (延伸閱讀)

這兩種方式比較起來, YAML/JSON設定檔比Dockerfile來得有彈性。

Dockerfile的方式比較像是使用默認的建置流程, Cloud Build會依照Dockerfile將你的映像檔建置起來, 並自動推送至Container Registry

Container Registry也是Google提供的服務, 作為私有容器的倉儲。

YAML/JSON設定檔則可以自訂詳細的流程、參數, 當然也可以在這邊定義怎麼呼叫 docker build 進行建置, 並額外加入單元測試、整合測試等流程, 最後都沒有問題才送上Container Registry。

我們的範例將使用YAML進行設定。

YAML設定檔中最主要的結構便是 steps 下所條列的步驟, 每個步驟的 name 都是一個docker映像, Cloud Build事先提供了許多常用的工具, 這些映像皆以gcr.io/cloud-builders/* 開頭命名, 可以在這裡查看所有工具。

第一步會先安裝Nodejs App的依賴。

- name: 'gcr.io/cloud-builders/npm'args: [ 'install' ]

接著測試我們的App。

- name: 'gcr.io/cloud-builders/npm'args: ['test']

測試通過之後, 則可以開始建置映像。

- name: 'gcr.io/cloud-builders/docker'
args: ["build", "-t", "gcr.io/$PROJECT_ID/${_IMAGE}:$BUILD_ID", "."]

使用docker將映像推送至Container Registery。

設定中, $開頭的字是變數, 可在 substitutions 這個段落下設定, 也有一些預先設定好的變數可以使用, 請參考這裡

- name: 'gcr.io/cloud-builders/docker'args: ["push", "gcr.io/$PROJECT_ID/${_IMAGE}:$BUILD_ID"]

推送之後的映象就會存在我們的私有倉儲中, 最後一步是要更新pod下的所有容器使用新的映象。

kubectl 這個工具是Kubernetes的管理工具, 我們將更改 $DEPLOYMENT 這個變數所指定的部屬, 讓他使用新的映像。

- name: 'gcr.io/cloud-builders/kubectl'args:- 'set'- 'image'- 'deployment/${_DEPLOYMENT}'- '${_CONTAINER}=gcr.io/$PROJECT_ID/${_IMAGE}:$BUILD_ID'env:- 'CLOUDSDK_COMPUTE_ZONE=${_ZONE}'- 'CLOUDSDK_CONTAINER_CLUSTER=${_CLUSTER}'

設定檔完成後, 前往Cloud Build設定觸發。

選擇你的Repository位置之後, 並同意授權給GCP。

最後設定觸發條件, 這邊設定為每當推送到主線時進行建置。

然後使用我們所配置的YAML設定檔, 活用參數替換的話, 可以讓一個YAML設定檔同時給很多個專案使用, 只需要在替代變數這邊修改設定就好了。

(※註: 圖片未更新, 事後補了一個環境變數_CONTAINER=nginx, 這可能是因為我們一開始使用nginx的映像初始化, 容器命名則依照映像的名字)

這樣就完成觸發的設定了。

可以到GKE的頁面確認正在執行的pod為一開始預設的nginx, nginx:nginx:latest 前面的nginx是容器的名稱, 後面才是使用的映象檔。

接著就可以做個git commit推送到Repository試試看成果了!

沒有事情是第一次就會成功的...

去GKE頁面也可以觀察到映像檔順利更新了!

還有容器的日誌可以查看。

基本上到這邊就算是大功告成了。

另外還可以了解一下使用kubectl設定負載平衡, 並將容器服務發佈到公開網路, 可參考這篇文章

容器順利運行確認。

總結

  1. 我們將一個Nodejs的App打包在一個docker容器中。
  2. Cloud Build可透過git push觸發建置, 為我們的Nodejs App執行測試並建置映像檔。
  3. Container Registry儲存了所有建置後的映像檔。
  4. 透過kubectl這個命令列工具將新建置的映像檔更新至指定容器中。

以後, 每每推送新的程式上到遠端repository, 部屬那端就會同時自己更新, 減少許多手動部屬的麻煩!

要說Cloud Build缺點的話, 目前遇到的不便之處就是跟slack的整合, 由於沒有現成的Slack App可以用, 必須透過Google Pub/Sub那邊的事件推送消息給自訂的Slack App了。

以上紀錄, 謝謝觀看。

--

--

Kevin Cheng
Cow Say
Editor for

貓奴 / 後端工程師 / 人生最重要的四件事:溫柔、勇敢、自由、浪漫