Google Kubernetes Engine部屬全紀錄 (Part 1. Cloud Build篇)
本文記錄著這麼一段心路歷程, 如果你也有同樣的疑惑, 希望這篇文章能帶給你一點頭緒。
我有一個容器化的Nodejs應用, 用Dockerfile定義並建置了映像檔(image), 然而這個應用以往都是手動運行docker run指令啟動的, 最理想的狀況應該是能夠在特定條件下(例如: git push到特定分支)自動重新建置映像檔並更新正在運行中的容器。
整體來說, 我需要一個CI/CD的自動化流程, 流程大致如下:
- git push 推送程式碼至遠端的存儲區(Repository), 如GitHub或Bitbucket等服務。
- CI階段執行測試, 若成功, 則前往(3), 若失敗, 前往(4)。
- CD階段會建置映像檔, 推送至容器倉儲區(Container registry)。
- 新建映像檔這個事件會觸發相關容器更新並重新運行。
- 通知我並結束流程。
目標運行平台為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設定負載平衡, 並將容器服務發佈到公開網路, 可參考這篇文章。
容器順利運行確認。
總結
- 我們將一個Nodejs的App打包在一個docker容器中。
- Cloud Build可透過git push觸發建置, 為我們的Nodejs App執行測試並建置映像檔。
- Container Registry儲存了所有建置後的映像檔。
- 透過kubectl這個命令列工具將新建置的映像檔更新至指定容器中。
以後, 每每推送新的程式上到遠端repository, 部屬那端就會同時自己更新, 減少許多手動部屬的麻煩!
要說Cloud Build缺點的話, 目前遇到的不便之處就是跟slack的整合, 由於沒有現成的Slack App可以用, 必須透過Google Pub/Sub那邊的事件推送消息給自訂的Slack App了。
以上紀錄, 謝謝觀看。