輕鬆建置 Gitlab CI/CD + Docker + GCP Compute Engine + React + Nodejs 網頁前後端自動化整合部署 — Part 2

Hongwei
13 min readJun 1, 2021

--

大家好我是Kevin,最近專案需要在Gitlab上建置CI/CD的環境,剛好有機會從頭到尾實作一個基本的CI/CD流程,因此整理下來所有詳細的步驟分享給大家,希望對初學者或是有需要DevOps情境的人有幫助 。

此篇是在 Web情境中使用 React+Express,透過建立 Docker container 啟動前後端服務,主要針對 CI/CD Flow去做介紹,所以大家可以作為參考,再依據個人環境去修改調整。

👇 若還未建立專案環境的可以先參考我寫的 Part 1 👇

👇首先我們一定要先了解什麼是CI/CD,若不清楚的可以查看下方這篇👇

👇此外也必須了解Docker的基本運作原理,可以參考下方連結👇

Steps

  1. 撰寫前端 Dockerfile 並在本地端測試
  2. 撰寫後端 Dockerfile 並在本地端測試
  3. 撰寫 Gitlab CI/CD 檔案 (.gitlab-ci.yml)
  4. 運行 CI/CD 和查看狀態
  5. 確認前後端服務正常運行

1. 撰寫前端 Dockerfile 並在本地端測試

✔️ 我們要來建立前端 Docker Image,首先必須在前端專案目錄底下創建一個名為 Dockerfile 的檔案,這邊已經準備好一份 Dockerfile其中描述了容器內所運行的指令和安裝環境,以下大概介紹一下依序做了哪些事情。

  • 取得 node image 建立 node 環境
  • 複製整個專案檔案至 container 內的 /app 資料夾底下
  • 安裝所有專案套件依賴
  • 打包成build靜態檔案
  • 取得 nginx image 建立 web server 環境
  • build 放入 container nginx 的網站目錄位置
  • 將預設 nginx.conf 設定檔移除換成我們的設定檔(下一步會創建)

✔️ 此外,在專案根目錄底下也可以創建一個 .dockerignore ,此檔案是讓 Docker Build 的時候,可以忽略某些檔案或是資料夾,以下提供我使用的內容,可以依照自己需求去做新增修改。

✔️ 接下來我們同樣在前端專案根目錄,創建一個 nginx.conf 檔案,會需要這個檔案主要是為了使用 React Router 而進行額外設定,將所有請求都轉向index.html,並監聽80作為前端網頁Port,以下附上 nginx.conf 設定檔的內容。

Dockerfilenginx.conf.dockerignore都已經創建完成後,我們可以透過 Docker CLI 來建立我們的 Docker Image,若尚未在本地端安裝 Docker也可以透過 Docker官方進行下載

✔️ 在前端專案跟目錄底下,輸入以下指令可以建立 Docker Image,使用-t 目的可以為此 Image 設定 Tag,可以方便我們辨認所創建的 Image。

docker build -t frontend-example .

💡 建立完成後透過輸入 docker images 指令可以看到我們所創的 Image

✔️ 接著輸入以下指令,可以依據我們所創建的 Image 來啟動 Docker Container,使用 -d 讓程序運行於背景、 --name 來設定container名稱、-p [本地port]:[容器內port] 進行Port綁定。

docker run -d --name frontend-example -p 80:80 frontend-example

執行成功後,可以透過docker ps -a 查看所有的容器,並打開本地瀏覽器在網址列輸入 localhost (預設走80)即可看到你的 React App 成功運行代表我們的 Dockerfile 沒問題囉~

2. 撰寫後端 Dockerfile 並在本地端測試

✔️ 我們要來建立後端 Docker Image,首先必須在後端專案目錄底下創建一個名為 Dockerfile 的檔案,這邊已經準備好一份 Dockerfile其中描述了容器內所運行的指令和安裝環境,以下大概介紹一下依序做了哪些事情。

  • 取得 node image 建立 node 環境
  • 複製整個專案檔案至 container 內的 /app 資料夾底下
  • 安裝所有專案套件依賴
  • 啟動後端服務

✔️ 此外,在專案根目錄底下也可以創建一個 .dockerignore ,此檔案是讓 Docker Build 的時候,可以忽略某些檔案或是資料夾,以下提供我使用的內容,可以依照自己需求去做新增修改。

Dockerfile.dockerignore都已經創建完成後,我們就可以透過 Docker CLI 來建立我們的 Docker Image。

✔️ 在後端專案跟目錄底下,輸入以下指令可以建立 Docker Image,使用-t 目的可以為此 Image 設定 Tag,可以方便我們辨認所創建的 Image。

docker build -t backend-example .

建立完成後透過輸入 docker images 指令可以看到我們所創的 Image

✔️ 接著輸入以下指令,可以依據我們所創建的 Image 來啟動 Docker Container,使用 -d 讓程序運行於背景、 --name 來設定container名稱、-p [本地port]:[容器內port] 進行Port綁定(這邊綁定3000)。

docker run -d --name backend-example -p 3000:3000 backend-example

執行成功後,可以透過docker ps -a 查看所有的容器,並打開本地瀏覽器在網址列輸入 localhost:3000 即可看到 Hello World 字樣就代表我們的Dockerfile沒問題可以正常運行後端服務~

3. 撰寫 Gitlab CI/CD 檔案 (.gitlab-ci.yml)

終於到了本篇核心的部分,我們需要在前後端各建立一個 .gitlab-ci.ymlGitlab 會去依據專案下的這個檔案去執行我們的 CI/CD Flow,我這邊已經撰寫好兩份.gitlab-ci.yml 給大家參考,比較特別的是我們多寫了一份 clear-docker.sh腳本用來清除多餘的 Image Docker,以下概述一下我們想做的事情。

我們的 CI/CD Flow 主要有四個 Stages

  • Build(CI Pipeline): 負責進行npm install和建置程式碼生成build
  • Test(CI Pipeline): 透過react-scriptsjest進行測試
  • Docker(CD Pipeline): 建置 Image 然後 Push GitLab Container Registry
  • Deploy(CD Pipeline): 更新 GCE 遠端 Docker Images 版本來完成部署

✔️ 前端.gitlab-ci.yml (在前端專案跟目錄中):

✔️ 後端.gitlab-ci.yml(在後端專案跟目錄中):

✔️ 伺服器端clear-docker.sh(放在伺服器用戶根目錄中~/):

💡 Git Flow: 我的範例以最單純的 Git Flow 展示,主要是只允許 master 進行 CD Pipeline,而 develop 僅進行 CI Pipeline再需要部署的時候透過 PR(Pull Request)master進行合併,詳細 Git Flow 介紹可以參考以下連結。

💡 Gitlab Variables: 在我的情境中前後端的 CI/CD Flow 大致相同,因此在這邊統一解釋 .gitlab-ci.yml 裡的關鍵參數的意義:

Gitlab CI/CD 本身有預設的一些預設變數,以下是我們用到的:

  • $CI_REGISTRY_USER : 將容器推送到專案的 Gitlab 容器註冊表的用戶名
  • $CI_REGISTRY : GitLab 容器註冊位址
  • $CI_REGISTRY_IMAGE : 專案的容器註冊的地址
  • $CI_COMMIT_REF_NAME : 專案建立的分支標記名稱

我們也可以自己設定所需要的變數,因為透過 Gitlab Group 管理兩個專案,所以可以在 Group Level 設定以下參數讓底下專案都吃的到,如下圖是我設定的參數:

  • $DOCKER_REGISTRY_TOKEN : 登入Gitlab Docker Registry 所使用的 Token
  • $GCP_VM_HOST : GCE 伺服器位址
  • $GCE_LOGIN_USER : 登入GCE 的使用者帳戶
  • $SSH_PRIVATE_KEY_GCE : 登入GCE 所使用的 ssh key 私鑰

💡 Scripts: 這邊較為重要的是 Docker Deploy 這兩個 CD Pipeline,所以解釋一下這兩階段所做的事情。

Docker Stage:

此階段主要目的是要將我們前後端的 Dockerfile 建置為 Image 然後 Push GitLab Container Registry,所以第一行必須先登入 Gitlab 容器註冊位址;第二行是建置我們的Image;最後一行是 Push 至所登入的容器註冊位址。

docker login -u $CI_REGISTRY_USER -p $DOCKER_REGISTRY_TOKEN $CI_REGISTRYdocker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME .docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME

Deploy Stage:

在部署階段主要是透過 openssh-client這個工具連線遠端主機,所以我們必須將之前設定好的 $SSH_PRIVATE_KEY_GCE 存進 deploy.pem 這個檔案中,在透過 ssh-add 加入就可以註冊到我們的私鑰透過ssh進行登入。

echo "${SSH_PRIVATE_KEY_GCE}" | tr -d '\r' > deploy.pemssh-add deploy.pem

接下來就是透過 ssh 連線遠端主機操作的部分,我們先把主機上目前所有的Images Containers 列出來(方便我看Log而已),接著我們執行 clear-docker.sh(注意要在~/ 底下創建這支檔案),最後就直接透過docker run 跑起來,這邊 Docker 會自動檢查本地 Image 是否存在,若不存在它會拉取最新版的,這邊前後端不同的部分是 Port 的設定,因為我們在同一台主機上部署,在沒有透過 Traefik 等其他工具的狀況下,會造成Port相衝所以這邊前端和後端要依據自己需求去做修改。

ssh ${GCE_LOGIN_USER}@${GCP_VM_HOST} "docker images && docker ps -a"ssh ${GCE_LOGIN_USER}@${GCP_VM_HOST} "bash ~/clear-docker.sh frontend-example $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"ssh ${GCE_LOGIN_USER}@${GCP_VM_HOST} "docker run -d --name frontend-example -p 80:80 $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME"

4. 運行 CI/CD 和查看狀態

現在所有檔案和環境都準備好了,可以開始讓 CI/CD 跑起來囉,我們分別在前後端專案內輸入以下指令,可以將程式碼 Push 上去 Gitlab 專案中,就可以透過 Gitlab 頁面看到運行中的狀態了,相同直觀方便呢😄

✔️ 進入Gitlab 前端或後端專案 → CI/CD → Pipeline

正在運行中的工作
完成所有的工作

✔️ 此外可以看到每個 Job 運作過程中的日誌,透過點擊 Gitlab 前端或後端專案 → CI/CD → Jobs → 選擇 Job

測試工作成功(CI Pipeline)
啟動Docker容器完成部署(CD Pipeline)

從以上可以看到我們的 CI Pipeline CD Pipeline 都成功(以前端為例),之後在develop 上開發就會自動測試,合併進 master 就會自動部署啦😄

5. 確認前後端服務正常運行

當確定 CI/CD Flow 都正確且運行成功後,就代表我們完成自動整合部署了,因此我們可以上去你的伺服器網址查看,看前後端回應是否正常,以我的網址為例:

Perfect! 前端有顯示 React 的範例網站,後端顯示我們的 Hello World API,這樣就完成了一個基本的 CI/CD 建置流程,大家可以再依據自己使用的前後端框架和專案需求去設計自己的 CI/CD Flow~

Conclusion

此篇內容我花了蠻多時間撰寫的,感謝大家有耐心地看完,主要目的是想分享給想學雲端自動化部署但可能沒有頭緒的人一個方向,順便我也可以紀錄一下我的學習歷程,也讓我釐清很多觀念,因為這篇情境其實很單純,不論是Git Flow、Pipeline設計等,在實務上可能都會更複雜,不過我相信任何事情都是起頭難,當能做出簡單的東西後再下去延伸學習,就會更得心意手!之後有機會在分享使用 Docker Compose 來建立可以做到方便的功能。

我是Kevin,覺得不錯的可以幫我拍個手,如果有任何問題都可以提出或是聯絡我Email,希望不管剛接觸的還是高手能不吝嗇跟我交流或指教,祝大家學習的路上都能順利😆

--

--