Hands-on GitLab CI/CD Docker Container to Azure App Service
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是做什麼用,可以參考我上一篇文章:
或是參考下面這篇文章:
在開始之前,列幾個先備知識,不然可以會看得有點吃力。
- Git Flow
- CI/CD Pipeline
- Docker Environment
- Pass(Azure)
然後再列幾個你要先註冊的平台、服務或CLI
- Azure帳戶、Azure CLI
- Docker
- Node.js(NPM)
- Angular CLI
- GitLab帳戶
Agenda
- 使用Angular CLI建立Angular App
- 建立一個GitLab Project
- 撰寫Dockerfile與.dockerignore
- 建立Azure App Service
- 取得Gitlab PAT(Personal Access Token)
- 建立Azure Active Directory(Azure AD)
- 設定GitLab Variables
- 撰寫CI/CD Pipeline YAML檔
- 建立Pull Request
- 建立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上踩了不少雷,希望這篇多少有一點幫助,也歡迎各位大大能不吝嗇地跟我交流或指教,謝謝你看完這一篇!