[DevOps實作] 使用 Google Cloud 全代管服務達成 CI/CD

  • Post author:
  • Post category:GCP
[DevOps實作] 使用 Google Cloud 全代管服務達成 CI/CD
blog banner

上回我們介紹了使用 「GitLab部署asp.Net Core到 Kubernetes Engine 達成 CI/CD」,用的還是自己辛苦打造的 GitLab Server,地雷之多的…….

這次要輕鬆一點,把整個DevOps流程都放在全代管 GCP 服務上,不要再自己建Server了。除此之外,會再善用 Git 分版本來達成 GitOps,把整個環境用 Code 來處理,也就是所謂 “environments-as-code”。

今天的實作是參考 “GitOps-style continuous delivery with Cloud Build” 來實作的,各位有疑問可以深入研究這篇文章。

整個 DevOps 環境如下:

DevOps (GitOps) Process
DevOps (GitOps) Process

過程中會建2個 Git Repositories,首先程式碼Push到 app repository,Cloud Build 會跑測試、建image然後push到 Container Registry。接下來 Cloud Build 又會更新 Deployment 設定檔,並且把設定檔push到 env repository,這個動作會觸發另一個 Cloud Build pipeline去套用到 GKE Cluster,如果成功,會把設定檔存在env repository的另一個Git分支。

我們會讓 app 和 env repo 分開,因為它們的用途不同。App repo 的使用者是「真人」,env 的使用者是「假人」?咦不是啦!是自動化系統如 Cloud Build 和其他會用到它的應用程式。而且這 env repo有幾個分支分別對應到特定的環境,也會參考特定的 container image,而 app repo 則沒有。

看完之後,你應該會有一個系統能做到:

  • 在 Cloud Build 的history區分出成功和失敗的deployment
  • 在env repo的production分支存取當前的設定檔
  • 重新執行對應的 build 動作回滾到先前的版本

整個 DevOps 流程如下:

DevOps (GitOps) Pipeline
DevOps (GitOps) Pipeline

基本環境建立

建立 GKE Cluster

gcloud container clusters create hello-cloudbuild –num-nodes 1 –zone us-central1-b

因為要等幾分鐘讓Cluster開好,等不及可以開另一個 Cloud Shell 登入 Git

git config –global user.email “xxx@xxx.com”

git config –global user.name “Aaron Lee”

git config
git config

執行 credential helper,它讓你的帳號連到 Cloud Source Repositories

gcloud credential helper

接著建立2個 repo,app和env

Create 2 repos in Source Repository

然後去

https://github.com/GoogleCloudPlatform/gke-gitops-tutorial-cloudbuild

把範例程式碼拉下來

clone sample code
clone sample code

接下來設環境變數,一定要設好,後面就不用每次key source repo那個長長的網址:

cd ~/hello-cloudbuild-app
PROJECT_ID=$(gcloud config get-value project)
git remote add google \   “https://source.developers.google.com/p/${PROJECT_ID}/r/hello-cloudbuild-app”

設定環境變數
設定環境變數

進app.py稍微改一下名字,證明自己真的有做過~

vim app.py

app.py
app.py

來看一下Dockerfile

app.py Dockerfile
app.py Dockerfile

我們會用 Cloud Build 建image並儲存在 Container Registry。

然後我們要先把 commit id取出來,之後建image會用到它。

COMMIT_ID=”$(git rev-parse –short=7 HEAD)”

建立 Container Image

現在我們要建 image了!

加 tag 包含 commit id (這樣放到 GCR也可以識別)

gcloud builds submit –tag=”gcr.io/${PROJECT_ID}/hello-cloudbuild:${COMMIT_ID}” .

gcloud build tag
gcloud build tag

完成!

gcloud build tag successfully
gcloud build tag successfully

建立 CI pipeline (就是建trigger)

直接去 Cloud Build 的頁面點擊 Create Trigger

create trigger
create trigger

取名 hello-cloudbuild

edit cloud build trigger
edit cloud build trigger

在 Event 選擇 Push to a branch

cloud build trigger event
cloud build trigger event

在 Source 部分選擇 hello-cloudbuild-app ,以及 ^master$ 做為 Branch

cloud build source repository
cloud build source repository
cloud build source branch
cloud build source branch

Build Configuration選擇 Cloud Build configuration file

檔名取 cloudbuild.yaml

CI cloudbuild.yaml
CI cloudbuild.yaml

OK的話就按 Create。

看一下這個檔案內容,這是 cloudbuild.yaml第一版的檔,只做CI 

CI cloudbuild.yaml details
CI cloudbuild.yaml details

Trigger建好了,那我們來push code看看

create trigger successfully
create trigger successfully

但是因為 code 沒有變動,所以沒有觸發 Cloud Build 的Trigger來建image

那我故意再改一下:

Modify app.py
Modify app.py

送出了:

commit app.py
commit app.py

結果有問題

commit app.py and error
commit app.py and error

測試失敗?

commit app.py and error
commit app.py and error

我們仔細看一下 Log:

Step #0 – “Test”:     self.assertEqual(hello(), “Hello World!\n”)
Step #0 – “Test”: AssertionError: “Hello World! I’m GKE Master Aaron Lee 1228\n” != ‘Hello World!\n’
Step #0 – “Test”: – Hello World! I’m GKE Master Aaron Lee 1228 Step #0 – “Test”: + Hello World!

原來是它檢查我的句子是不是”Hello World!”,只不要是都算錯。

那我還是乖乖改回來:

modify app.py
modify app.py

再commit並push出去:

commit app.py again
commit app.py again

快去 Cloud Build 看看:

check cloud build
check cloud build

果然成功了!

所以那個測試,是有寫一個規則在裡面嗎?

我找到 test_app.py ,來看看裡面有什麼:

test_app.py details
test_app.py details

原來就是檢查句子是不是 “Hello World!”

確認Image上來了:

confirm image created
confirm image created

建立 CD Pipeline

接下來開始CD流程。

Cloud build 也可以用在CD pipeline, 每次commit push到 candidate分支就會開始跑。這個pipeline也會套用新的設定檔到GKE,如果成功的話,它會copy設定檔到production分支。整個流程有一些特性如下:

  • Candidate分支會放所有要部署的歷史記錄
  • Production分支則是只有成功的部署
  • 你會在 Cloud Build 看到所有成功和失敗部署的細節
  • 你可以重新執行 build 用來回滾到先前的版本,同時回滾也會更新production分支來真實反應deployment的歷史記錄。

接下來要注意的地方是,你會改掉前面建的 CI Pipeline,去更新candidate分支,觸發 CD pipeline。

授權 Cloud Build 來存取 GKE,像上次授權GitLab存取GCR和GKE是一樣的意思。

grant permissions to Cloud Build service account
grant permissions to Cloud Build service account

接下來初始化 hello-cloudbuild-env repo,做2個分支

cd ~
gcloud source repos clone hello-cloudbuild-env
cd ~/hello-cloudbuild-env
git checkout -b production      
“-b” 代表我建了一個分支叫 production,並且切換到這個分支

create another branch production
create another branch production

目前在production分支  把app repo的cloudbuild-delivery.yaml copy過來 變成cloudbuild.yaml 只是把檔案copy一份,然後就commit上去

cd ~/hello-cloudbuild-env
cp ~/hello-cloudbuild-app/cloudbuild-delivery.yaml ~/hello-cloudbuild-env/cloudbuild.yaml

目前沒有trigger,不會有任何影響。

這個檔的內容主要是部署設定檔到GKE,成功的話再把檔案放到production分支。

cloudbuild.yaml details
cloudbuild.yaml details

git add
.git commit -m “Create cloudbuild.yaml for deployment”

commit cloudbuild.yaml
commit cloudbuild.yaml

再建一個 candidate 分支,並且切換過去:

git checkout -b candidate

把這兩個都push,注意 candidate 是空的,沒錯喔不要懷疑。

git push origin production
git push origin candidate

然後授權 Source Repository Writer角色權限給 Cloud Build service account:

PROJECT_NUMBER="$(gcloud projects describe ${PROJECT_ID} \
    --format='get(projectNumber)')"
cat >/tmp/hello-cloudbuild-env-policy.yaml <<EOF
bindings:
- members:
  - serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com
  role: roles/source.writer
EOF
gcloud source repos set-iam-policy \
    hello-cloudbuild-env /tmp/hello-cloudbuild-env-policy.yaml
grant Source Repository Writer role Cloud Build service account
grant Source Repository Writer role Cloud Build service account

接下來建立第二個Trigger:

create trigger hello-cloudbuild-deploy
create trigger hello-cloudbuild-deploy
trigger event
trigger event
cloud build trigger source repository
cloud build trigger source repository
cloud build trigger source branch
cloud build trigger source branch

它的意思是,只要在env這個repo裡,有push到candidate這個分支,就執行cloudbuild.yaml (主要會部署 GKE, 並且在production 分支產生k8s.yaml做一個commit)
那這支 cloudbuild.yaml 目前還沒產生,接下來才會出現。(感覺在鋪一個很長的梗……)

更改 CI pipeline 去觸發 CD

現在要改檔名,把 cloudbuild-trigger-cd.yaml 改成 cloudbuild.yaml

就像上面說的,它會部署 GKE 並且copy到 production

我們看一下這個檔案,因為內容很多又雜,我分段貼出來並且加了一些註解,方便大家看懂(閱讀課開始……):

cloudbuild-trigger-cd.yaml details part1
cloudbuild-trigger-cd.yaml details part1

後面還有…

cloudbuild-trigger-cd.yaml details part2
cloudbuild-trigger-cd.yaml details part2

還有…

cloudbuild-trigger-cd.yaml details part3
cloudbuild-trigger-cd.yaml details part3

而我們的另一個Trigger,是在 ENV 裡提交 Candidate 之後所觸發執行的工作,來看一下內容(第二堂閱讀課……):

another trigger cloudbuild.yaml part1
another trigger cloudbuild.yaml part1

後面還有…

another trigger cloudbuild.yaml part2
another trigger cloudbuild.yaml part2

再忍耐一下,快看完了…

another trigger cloudbuild.yaml part3
another trigger cloudbuild.yaml part3

然後把這個push到 app 自己的 repo: 

cd ~/hello-cloudbuild-app

git add cloudbuild.yaml

git commit -m “Trigger CD pipeline”

git push google master

這樣把app自己的檔案commit出來,就會Trigger 原本app裡的cloudbuild.yaml (只有CI),以及Env裡的Candidate Trigger (CD: 部署到GKE):

commit cloudbuild.yaml to trigger CICD pipeline
commit cloudbuild.yaml to trigger CICD pipeline

接下來看到 Cloud Build 開始跑了:

Cloud Build running
Cloud Build running

過幾秒之後看到成功:

Cloud Build running successfully
Cloud Build running successfully

後來發現 Cluster 忘記建了,快來建一下。

(忘記是小事,因為前面就可以花好幾天的時間確認流程,太早建Cluster就只能放在那邊浪費錢,就是等到它提示再建就好了)

create GKE cluster
create GKE cluster

等開好,來按一下retry (貼心的功能不用再重新commit或push)

retry cloud build
retry cloud build

成功了!

retry cloud build successfully
retry cloud build successfully

來看一下Service,看到 Load Balancer 有建起來,並且有一個外部IP:

confirm GKE service
confirm GKE service

點擊下去看到網頁,終於成功了!

confirm GKE service successfully
confirm GKE service successfully

以上就是完整的CI/CD流程,過程中有任何錯誤都可以看得到,因為在 GCP 上多了 DevOps 提到的「透明化」原則,而且都是用代管式的 GCP 服務來達成,其中比較複雜的是 Google 又加入了 Git 管理 (所以在過程中看起來有點亂),又形成了 GitOps,透過Infra structure as a cod 提高了自動化,並減少大量手工(Toil)出錯的風險,對我們未來的產品開發速度大有幫助。

如果還不習慣全代管,要使用 GitLab 的話,可以回來看這篇文章:

[DevOps實作] GitLab部署asp.Net Core到 Kubernetes Engine 達成 CI/CD

Aaron Lee

超過7年的 Google Cloud 經驗,服務過上百家 Google Workspace 與 GCP 客戶,擔任多次研討會主講人與教育訓練講師,提供架構諮詢與技術支援,幫助各大企業上雲。