Hands-on GitLab CI/CD Docker Container to Azure App Service

Leo Liao | 廖鴻林
Leo Liao
Published in
17 min readMay 5, 2021

Purpose

最近都在忙Side Project和新技術的學習,恰巧有機會可以把Docker虛擬化環境和CI/CD Flow在Side Project上做結合,實作一個DevOps的情境,將詳細步驟整理分享給大家,以便未來可以幫助應用於各種場景上。

這次的實作全部都是以免費的額度去使用,GitLab提供每個月400分鐘的CI/CD Pipeline時數,Azure App Service提供免費F1額度使用。

大概簡述實作流程,我們會建立一個Gitlab專案,將應用程式(Angular Web App)經過GitLab CI Pipeline建置和測試後,用GitLab Schedule在自訂時間經過GitLab CD Pipeline push Docker Image到GitLab Container Registry,並Trigger Azure App Service取得Latest version Docker Image重新部署應用程式。

如果你還不知道CI/CD Flow是做什麼用,可以參考我上一篇文章:

或是參考下面這篇文章:

在開始之前,列幾個先備知識,不然可以會看得有點吃力。

  1. Git Flow
  2. CI/CD Pipeline
  3. Docker Environment
  4. Pass(Azure)

然後再列幾個你要先註冊的平台、服務或CLI

  1. Azure帳戶Azure CLI
  2. Docker
  3. Node.js(NPM)
  4. Angular CLI
  5. GitLab帳戶

Agenda

  1. 使用Angular CLI建立Angular App
  2. 建立一個GitLab Project
  3. 撰寫Dockerfile與.dockerignore
  4. 建立Azure App Service
  5. 取得Gitlab PAT(Personal Access Token)
  6. 建立Azure Active Directory(Azure AD)
  7. 設定GitLab Variables
  8. 撰寫CI/CD Pipeline YAML檔
  9. 建立Pull Request
  10. 建立GitLab Schedule

Hands-on

我是Mac環境,有些Bash指令會不太一樣,我就不特別列出來。

使用Angular CLI建立Angular App

cd Desktop
ng new testProject

我們Focus在Flow上,所以Routing那些就可裝可不裝,完成後來試試看Web App是否正常啟用吧!

cd testProject
npm start

Angular Web App Default使用4200 port,打開瀏覽器輸入http://localhost:4200 確認看看。

建立一個GitLab Project

直接選一個建立一個空白的專案。

將資料填好後,直接建立即可。

建立之後,網頁下方有像Github一樣給你command直接複製,由於我們Angular Project在建立之後會自動有一個initial commit,所以直接擷取其中幾段來push到remote就可以了。

git remote add origin https://gitlab.com/HongLinLiao/testproject.git
git push -u origin master

這樣就建立好了,到這個步驟都不太會有什麼大問題。

撰寫Dockerfile與.dockerignore

我們需要將Docker Image Push到GitLab Container Registry之前,要先用Docker build Dockerfile產出Docker Image。

先來處理.dockerignore檔案,我自己的做法是將.gitignore裡的內容全部複製貼上到.dockerignore檔案裡(這是自己建立的,檔案名稱需一致,並且無副檔名)放到專案根目錄,再加上下面這兩行,原因是Docker Image裡面不需要有兩個docker build的設定檔。

.dockerignoreDockerfile

我們先將Angular CLI加進NPM開發相依。

npm i @anuglar/cli -D

接著來處理Dockerfile,這裡直接用一個我寫好的範本說明,只要將內容複製下來,將檔名設定成Dockerfile(這也是自己建立的,檔名就叫Dockerfile,沒有任何副檔名)放到專案根目錄即可。

# Stage 1# pull node base imageFROM node:14.16.1-alpine3.10 as build-step# set working directoryWORKDIR /app# add file and will exclude .dockerIgnore list fileADD . /app# npm installRUN npm install# add `/app/node_modules/.bin` to $PATHENV PATH /app/node_modules/.bin:$PATH# build codeRUN npm run build# Stage 2# pull nginx base imageFROM nginx:1.17.1-alpine# build file to nginxCOPY --from=build-step /app/dist/testProject /usr/share/nginx/html# portEXPOSE 80

這個Dockerfile分成兩個Stage,Stage1主要就是下載相依NPM套件,並Build出Production Code,這裡不做任何test或lint,原因是之後觸發這個docker build是在CD階段,test和lint會在CI階段完成。

Stage2會使用Nginx架起一個Web Server,EXPOSE 80是一個提示,可以讓後人知道這個Image是走80 Port。

不確定後面的Azure App Service Run起Container時對接的Port是直接抓這裡的80,還是直接抓Default 80 Port,這個可能要測試一下,知道可以留個言跟我分享^^。

接著來測試一下這個Dockerfile,可以從GitLab Container Registry找到要push的command。

docker login registry.gitlab.com
docker build -t registry.gitlab.com/honglinliao/testproject .
docker push registry.gitlab.com/honglinliao/testproject

完成後就可以看到Docker Image已經成功push到GitLab Registry!

建立Azure App Service

從Azure portal中找到Azure App Service(中文版叫應用程式服務),按下新增。

選擇好這個Service的Subscription和Source Group完後,要將這個Service選為Docker Container,接著選用Linux Base,Service位置可以自行調整,再來是很重要的環境配置,記得選F1免費1G RAM,不然你過一個月收到帳單不要跑來找我…。

接著換下一步Docker配置,Image Source由於我們是串接GitLab Registry,所以要選擇私人登錄,伺服器URL填上GitLab的Registry位置https://registry.gitlab.com,由於GitLab Repo是Public,所以使用者名稱和密碼可以不填,映像及標籤則為專案的位置honglinliao/testproject:latest,必須全部小寫,latest為指定為最新版本,接著就可以點下建立。

等待部署完成後,前往資源看到URL在右方,點進去確認Docker Container運作沒問題!

取得Gitlab PAT(Personal Access Token)

有沒有發現,在Azure App Service串接GitLab Container Registry時,如果Repo是Private,我們就必須填寫密碼,或是在CI/CD Pipeline時,我們想要Push Docker Image更新GitLab Container Registry,就需要提供我們GitLab Account的密碼,那是不是團隊成員都能看見我的密碼!
我們可以用GitLab PAT來進行登入,等於是用另一個Token取代掉原本的密碼,讓你的密碼不會外流。

在GitLab User Setting的左邊有個Access Setting的選項,我們可以從這裡建立一個PAT,另外這個PAT是可以被設立操作權限與過期時間的!

建立後可以發現,頁面上方可以看到產生的Token,可以先記下來。

建立Azure Active Directory(Azure AD)

由於Azure雲端服務可以依據各種不同資源與帳戶做管理,所以在身份驗證上會比較嚴謹,沒有辦法像GitLab一樣用帳戶與Token直接進行登入授權,Azure 需要透過Azure AD,創建一個Service Principle,可以詳見這篇

用Azure CLI登入,會開一個Browser讓你登入Azure。

az login

登入完成後就可以用CLI建一個Service Principle, — name後面為這個Service Principle name

az ad sp create-for-rbac --name testProject

完成後會產生一個回傳這樣格式的資料

{
"appId": "xxxxxxxxxxxxxxxxxxx",
"displayName": "testProject",
"name": "http://testProject",
"password": "xxxxxxxxxxxxxxxxxxx",
"tenant": "xxxxxxxxxxxxxxxxxxx"
}

接著我們需要用到appId、password與tenant來進行登入,將這三個參數帶入$appId、$password與$tenant,確認可以正常登入。

az login --service-principal -u $appId --password $password --tenant $tenant

再來確認這個權限下可以重啟Azure App Service, — name為這個Azure App Service的名稱,我們設置的是testDocker123, — resource-group後面放的是Resource Group Name,請將這兩個參數帶入下方Command的$appName與$rsName,只要該指令沒有Error就成功了。

az webapp restart --name $appName --resource-group $rsName

如果重啟時出現Resource Group Not Found,可能是因為你有多個Subscription,將Service Principle建到與App service的Resource Group不同的Subscription,只要將重新az login,接著az account set — subscription <subscription-id>換到該subscription下,再重新走一次產生Service Principle與之後的流程即可解決,詳細切換Subscription請看這篇

設定GitLab Variables

GitLab除了除了可以拿到原本的環境變數外,還可以設定Coustom Variable。

從GitLab進入Project後,點選左方Settings的CI/CD選項。

接著可以找到Variables區塊,選取Expands展開,點選Add variable開始建立變數。

可以將幾個重要的Key、Secrete或Token加在這裡,作法如下。

我們將GitLab PAT、Azure Login AppId、Password與Tenant共四個變數加進來。

撰寫CI/CD Pipeline YAML檔

終於到了最關鍵的步驟,大概解說一下我想要做到的Flow。

  • 分四個階段(Stage): Build、Test、Deployment、Trigger。
  • Build(CI Pipeline): 建置程式碼。
  • Test(CI Pipeline): 執行Test或Lint。
  • Deployment(CD Pipeline): Build Docker Image然後Push至GitLab Container Registry。
  • Trigger(CD Pipeline): 觸發Azure App Service重啟,做到重新抓取Docker Image run Docker Container。
  • 只有在發Pull Request時Target Branch是master(default)才觸發CI Pipeline。
  • Pull Request完成後會執行Merge into master再一次觸發Pipeline,這時不要執行任何Pipeline。
  • 設置GitLab Schedule在每日晚上18點進行CD Pipeline。

簡單來說,就是我想節省GitLab Pipeline提供的每個月400分鐘額度,所以不希望在每次Merge into master時觸發CD Pipeline。

Gitlab CI/CD Pipeline的建置是利用YAML檔,會讀取專案根目錄叫.gitlab-ci.yml的檔案,接著用寫好的範例來說明。

用$CI_PIPELINE_SOURCE來區分是哪種Pipeline。

用$CI_MERGE_REQUEST_TARGET_BRANCH_NAME來區分是否這個Pull Request的Target Branch是不是Master。

另外可以用17、18行來確認某檔案夾內是否有異動檔案,有的專案前後端會放在同個Repo,如果沒有更動某一端的檔案,可以不用跑到該Stage。

Build Stage用node alpine為Base Image,先執行npm install下載相依套件,再建置程式碼,最後將node_module的相依套件push至cache,這樣在Test階段時即可不用再重新npm install。

Test Stage也是用node alpine為Base Image,先從cache pull node_module相依套件,接著跑tslint檢查typescript style。

Deployment Stage用docker stable為Base Image,先用docker login登入才能Push至GitLab Container Registry,接著build出Docker Image,再push至GitLab Registry。

Trigger Stage是用mcr.microsoft.com/azure-cli為Base Image,可以在這個環境下直接使用Azure CLI,首先用建立好的Service Principle login,再restart Azure App Service。

建立Pull Request

將異動後的程式碼Push至遠端分支,接著到GitLab進入Project後,於左方選單點選Merge Request,進入後可以看到剛剛push的分支提醒,直接點擊Create Merge Request。

建立完後可以發現Pipeline有讀取到,且確實只有Build與Test Stage。

建立GitLab Schedule

透過建立GitLab Schedule,可以在自訂的一或多個時間,自動執行Pipeline。從左側CI/CD選單中點選Schedule項目,並新增一個Schedule。

Interval Pattern是採用通用的Cron格式,時區根據所在地區做調整,Target Branch與Variables進階的做法是可以依據不同環境,送環境變數給YAML檔做到不同環境的部署。

送出後,如果想要手動執行這個Schedule可以點擊右方的Play鈕。

確定Schedule觸發的也是正確的Deployment與Trigger Stage,之後Pipeline跑完記得確認Azure App Service確實更新後,就大功告成啦!

Conclusion

這一篇實在是非常的長,但為了清楚解說,只好硬著頭皮寫得詳細一點了。發現GitLab功能其實蠻齊全的,但免費的400分鐘Pipeline實在是有點不夠,如果每天小專案定期定量的PR應該是會爆掉的,而且在一些PR的Policy上也不是很好用,好像都是要錢花下去才會比較多Plus的功能,相較於Azure DevOps來說,Azure DevOps會更彈性一點,但協作只要超過五個人,收費也是蠻驚人的就是了。(有機會可以分享一下Azure DevOps,但水也是很深就是了…)

上面code的寫法或流程都還有優化的空間,但為了研究完這一套就花了不少時間,特別在Azure與GitLab Pipeline上踩了不少雷,希望這篇多少有一點幫助,也歡迎各位大大能不吝嗇地跟我交流或指教,謝謝你看完這一篇!

--

--

Leo Liao | 廖鴻林
Leo Liao

Frontend Engineer | Web Developer,覺得分享經驗就跟潛水一樣,不知不覺在每段旅途中多認識了自己一點