--- tags: DevOps --- # GitLab CI / CD 實踐 > 本篇文章將使用「GitLab-CI」作為自動化的工具,並撰寫 GitLab-CI 腳本,在每一次 Push Commit 時觸發 CI 事件,自動完成網頁服務的建置與部署。 --- <div style="text-align: center;"> <span style="font-size: 50px;">概念篇</span> </div> --- ## 前言 在軟體開發過程中,難免會需要不斷地進行測試以及程式的更新,但在需求不斷變更,甚至要開發新功能的狀況下,要如何讓工程師能更專注在「coding」,節省更多測試和人工部署的時間呢?因此這幾年軟體業界出現了「CI / CD」的概念,透過以「自動化」的方式節省開發過程所消耗的成本,減輕人工負擔。 ## 何謂 CI / CD? * CI (Continuous Integration) 稱作「**持續整合**」,意指軟體開發的過程中,針對新版本進行持續性的程式建置與測試。由於每個工程師的開發環境與套件版本未必相同,因此為了確保程式上線後能正常運行,在每一次 push commit 之後,透過 CI 工具自動模擬測試 / 正式主機的環境,確保 Service 運行後沒有異常;當環境建置完成後,會在模擬環境進行測試,如單元測試...等,驗證功能執行後是否異常。 * CD (Continuous Deployment / Continuous Delivery) 稱作「**持續部署** / **持續交付**」,意指通過前面的 CI 流程之後,自動/手動發行程式版本與部署服務,並進行系統監控以及異常通知,最終將軟體交付給客戶使用。 說白一點,就是將需要被重複執行的工作 (如測試、更新) 交給自動化工具幫我們執行,~~工程師只要負責專心當碼農就好~~,當程式 push 到版控平台之後,就會觸發自動化事件,只要檢查到程式有問題,工程師就能夠即時處理,確保上線的品質,而最終順利將程式上線。 說到這裡,各位~~碼農~~們有沒有看見一道曙光呢?終於不用再人工做檢查、測試,不用再反覆用 SSH 連到主機做 git pull 囉!我就廢,我就懶嘛~ ## 常見的 CI 工具 這幾年自動化工具可以說是百家爭鳴,各有各的優缺點,但這邊先不多做說明,僅列出常見的工具: * Jenkins * Travis CI * Circle CI * Drone CI * GitLab CI (GitLab 內建) * Pipelines (BitBucket 內建) ## CI / CD 流程簡介 下圖為 CI / CD 的示意圖,團隊會搭配版控平台進行程式的開發,開發人員會從**主要分支**開創一個**功能分支**進行開發,而開發完成後將**功能分支** push 至版控平台,接著會觸發 CI 事件幫我們進行程式建置與測試,若測試過程中遇到錯誤,工程師可以即時修復問題,並再次 push 程式,若測試沒有問題,則發 Pull Request 進行 Code Review,最後將**功能分支**進行 merge,此時會再次觸發 CI 事件,確保合併後的程式碼無誤,最後才會觸發 CD 事件,將程式自動部署至伺服器。 筆者認為,CI / CD 其中一項概念,是希望用更小的單位進行開發,用快速的方式進行驗證與更新,並期望以最短的時間取得使用者回饋,找出系統問題並加以改善,這點和「敏捷式開發 (Agile)」有些相似。而這個章節暫時不討論 DevOps 與 Agile 的議題,僅介紹 CI / CD 的常見流程。 另外,筆者也認為,CI / CD 在導入前,更重要的是團隊必須定義開發流程的規範,所有人員都必須遵循它,例如 Git Flow、分支命名規則、PR 與 merge 的時機、分支的用途...等,像筆者習慣以 develop branch 作為測試環境 (Staging) 專用的分支,而 master branch 作為正式環境 (Production) 專用的分支。  ## 參考連結 https://hackmd.io/@ncnu-opensource/r10ikTDFI --- <div style="text-align: center;"> <span style="font-size: 50px;">工作坊</span> </div> --- ## 實作目標 在這次的工作坊中,我們將使用 GitLab-CI 自動觸發 CI / CD 的管線化流程 (pipeline),依序執行建置、測試、部署3個階段,並使用 Docker 快速部署網頁服務。 然而 CI / CD 流程,涉及到「測試程式」的撰寫,考量到每個語言、框架的測試工具皆不相同,且撰寫測試也是另一門學問,因此這邊先不示範測試的撰寫。 另外,我們在伺服器端將會使用 Docker 與 Docker-Compose 進行網頁的服務部署,倘若讀者尚未具備 Docker 的技術,建議可先研究相關的知識,例如 container、image、Dockerfile、volume、docker-compose ... 等。 ## 你將會使用到哪些工具? [GitLab](https://gitlab.com/) (版本控制) GitLab-CI (自動化工具) Docker, Docker-Compose (服務部署) Shell Script, YAML (執行腳本與部署檔) ## GitLab-CI 簡介 GitLab-CI 是 GitLab 內建的自動化工具,開發者可在專案根目錄新增 .gitlab-ci.yml,並定義 Stage、Job、Job 的 docker image,以及要執行的 script。在 push commit 後,GitLab 會自動呼叫 GitLab-CI Server,透過「CI Runner」進行一系列的自動化事件。CI Server 在執行的過程中,會將過程即時顯示在 GitLab Pipelines 的頁面,讓團隊可以查看自動化事件的執行狀況。 而 CI Runner 所扮演的角色,是幫 CI Server 執行自動化任務的「執行個體」,它會依據 .gitlab-ci.yml 的定義,在每一個 CI Job 生成獨立的 docker container,因此每一個 Job 都是獨立的 docker 環境,Job 之間不會互相影響,當 Job 結束後,會自動銷毀 container。 ## 前置準備 請先備好一台具有對外 IP 的主機,作業系統為 Ubuntu 18.04,並允許 http 80 port 對外連線。 ※ 可使用 AWS, GCP, Azure, OpenStack 或其他公、私有雲主機 --- ## 實作 part I - 本地端建立專案 > 首先我們需要建立新專案,新增簡易的 html 檔,並推送至 GitLab #### 1. 請至 GitLab 建立空白 Repository,名稱以「web_cicd」作為示範 #### 2. 建立專案目錄 ``` $ mkdir web_cicd $ cd web_cicd/ ``` #### 3. 新增 index.html ``` $ touch index.html ``` #### 4. 編輯 index.html ```htmlembedded= <h1>Hello world!</h1> ``` #### 5. 提交並推送至 GitLab ``` $ git init $ git add . $ git commit -m "initial commit" $ git remote add origin <your-repository-url> $ git push -u origin master ``` --- ## 實作 part II - 伺服器端 Docker 環境建置 > 由於這次的實作我們將使用 Docker 與 Docker-Compose 進行部署,所以伺服器必須先安裝 Docker, Docker-Compose, Git 與 vim #### 1. 安裝相關服務與工具 ``` $ apt update $ apt install -y docker.io docker-compose git vim ``` --- ## 實作 part III - 伺服器端新增用戶與 GitLab 相關設定 > 伺服器建立一個供「部署專用」的用戶,為了方便後續 Git 的操作,同時也將其 SSH Key 添加至 GitLab,這樣後續就能不輸入帳號密碼,直接操作 Git #### 1. 新增部署專用的用戶 ``` $ sudo adduser deployer ``` #### 2. 賦予新用戶讀寫權限 ``` $ sudo usermod -aG www-data deployer ``` #### 3. 設定權限 我們通常需要將 deployer 用戶權限分別設置為創建文件 644 與目錄 755,這樣一來,deployer 用戶可以讀寫,但是組與其它用戶只能讀 ``` # 切换到 deployer $ su deployer $ echo "umask 022" >> ~/.bashrc $ exit ``` #### 4. 將 deployer 加入 Sudoers ``` $ sudo vim /etc/sudoers # 於最後一行加入以下設定,儲存後退出 deployer ALL=(ALL) NOPASSWD: ALL ``` #### 5. 設定 Web 目錄擁有者 ``` $ sudo mkdir /var/www $ sudo chown deployer:www-data /var/www ``` #### 6. 為 deployer 設定 Web 目錄操作權限 ``` $ sudo chmod g+s /var/www ``` #### 7. 建立 deployer 的 SSH Key ``` # 切換至 deployer 用戶 $ su - deployer $ ssh-keygen -t rsa -b 4096 -C "deployer" $ cat ~/.ssh/id_rsa.pub ``` #### 8. cat 打印出公鑰之後,將其添加至 GitLab Deploy Keys 此步驟是為了讓伺服器不用輸入 Git 帳號密碼,改為透過 SSH 與 GitLab 進行 clone、pull、fetch 等動作,所以必須將伺服器的 public key 儲存到 GitLab Repository,讓 GitLab 進行解密,若無進行此步驟,後續進行自動部署時,會因為沒有足夠權限拉取程式,而部署失敗 ※ GitLab -> Your project -> Settings -> Repository -> Deploy keys #### 9. 將 deployer 公鑰添加至伺服器的 authorized_keys (此處會因各家雲端主機不同,而有所差異) ``` $ cd ~/.ssh $ touch authorized_keys $ chmod 600 authorized_keys $ cat id_rsa.pub >> authorized_keys ``` #### 10. 將 deployer 私鑰添加至 GitLab CI/CD 的環境變數,並命名為「SSH_PRIVATE_KEY」 ``` # 打印私鑰 $ cat ~/.ssh/id_rsa ``` 複製私鑰的值之後,到 GitLab -> Your Project -> Settings -> CI/CD -> Variables,點擊「Add variable」新增一個變數,Key 為 SSH_PRIVATE_KEY,Value 為私鑰的內容 #### 11. 測試伺服器是否能操作 Git Repository 在此步驟,先測試能否透過無密碼的方式,clone 你的 GitLab Repository,請記得 clone 的網址要選擇「SSH」,若可以成功操作,則可將目錄移除 ``` $ git clone <git@gitlab.com:...> $ rm -rf web_cicd/ ``` #### 12. 於「本機」確認能否透過無密碼方式,連線至伺服器 (此步驟於本機操作) 請將剛才複製的 deployer 「私鑰」保存下來,存放在本機電腦,並使用該私鑰連線至伺服器 Linux or MacOS: ``` $ touch ~/.ssh/deployer_private_key $ vim ~/.ssh/deployer_private_key # 貼上私鑰後,儲存並離開 ssh deployer@<your-server-ip> -i ~/.ssh/deployer_private_key ``` Windows: 將私鑰儲存為txt檔之後,進行 SSH 操作 ``` ssh deployer@<your-server-ip> -i <txt檔路徑> ``` --- ## 實作 part IV - 撰寫部署腳本 > 在專案底下,新增部署腳本 (shell script) 、GitLab-CI Yaml 與 Docker-Compose Yaml,讓專案推送至 GitLab 後觸發自動化建置與部署 #### 1. Git Repository 添加伺服器 IP 的環境變數 ※ GitLab -> Your Project -> Settings -> CI/CD -> Variables ※ 新增「DEPLOY_SERVER」變數,Value 為伺服器 IP (不需要加 http) #### 2. 專案根目錄新增部署的 shell script 檔,並編輯 ``` $ touch deploy.sh ``` 這支 shell script 檔,將會在 CI 腳本中連線至伺服器執行,其內容為 1. 判斷專案目錄是否存在,若存在則進行 pull 更新;若不存在則 clone 專案 2. 執行 docker-compose 啟動服務,並於背景執行 ※ 請注意 GIT_REMOTE 要存成 SSH 的形式,並替換<your-name>的文字 ```shell= #!/bin/sh PROJECT_NAME="web_cicd" GIT_REMOTE="git@gitlab.com:<your-name>/web_cicd.git" cd /var/www if [ -d $PROJECT_NAME ]; then cd $PROJECT_NAME && git pull $GIT_REMOTE master else git clone $GIT_REMOTE cd $PROJECT_NAME fi docker-compose up -d ``` #### 3. 專案根目錄新增 GitLab-CI Yaml,並編輯 ``` $ touch .gitlab-ci.yml ``` .gitlab-ci.yml 定義自動化事件的內容,依照順序執行 build、test,最後在 deploy 時會使用「Variables」的「SSH_PRIVATE_KEY」,連線至伺服器,執行 shell script。 補充一下,CI Yaml 會因 CI 工具的不同而格式有所差異,以下為 GitLab-CI 的格式: 1. image 為這支 CI Yaml 的預設 docker image,所有 Job 皆以該 image 產生獨立的 container,此外 Job 也可自定義其他 image 2. stages 定義了 CI 事件的流程,每一個 stage 皆會按照順序執行,若 stage 執行失敗,則後續的 stage 就不會被執行 3. job 為獨立的 container 環境,其依附在所屬的 stage 底下,一個 stage 可以擁有多個 jobs,且同一個 stage 的 Jobs 會同時執行 4. script 為 job 實際執行的指令,這些動作皆會在 CI Runner 產生的 container 中執行 5. only 為限定此 job 只在特定分支進行,例如只在 master branch 才進行自動部署的任務 更多內容請參考 GitLab 官方文件 https://docs.gitlab.com/ee/ci/ ```yaml= image: ubuntu:18.04 stages: - build - test - deploy build_job: stage: build script: - apt update - apt install -y git nginx vim test_job: stage: test script: - echo "Test successfully" deploy_job: stage: deploy before_script: - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' - mkdir -p ~/.ssh - eval $(ssh-agent -s) - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - ssh-add <(echo "$SSH_PRIVATE_KEY") script: - ssh -o StrictHostKeyChecking=no deployer@$DEPLOY_SERVER "bash -s" < ./deploy.sh environment: name: production url: http://$DEPLOY_SERVER only: - master ``` #### 4. 專案根目錄新增 docker-compose.yml,並編輯 ``` $ touch docker-compose.yml ``` docker-compose.yml 可定義所有需要使用的服務,每一個服務皆為獨立的 container,在本範例中將使用 Nginx 作為 Web Server 架設站台,所以需要建立一個名為「cicd-nginx」的 Nginx 容器,並將容器內部的 80 port 映射到實體機的 80 port (下方ports,冒號左方為實體機,右方為容器),同時將站台設定檔掛載至容器內部。 更多相關內容,請參考 Docker 官方文件 https://docs.docker.com/compose/compose-file/compose-file-v3/ ```yaml= version: '3.5' services: cicd-nginx: image: nginx container_name: cicd-nginx restart: always tty: true ports: - '80:80' volumes: - ./docker/nginx/conf.d/:/etc/nginx/conf.d/ - /var/www:/var/www networks: - app-network networks: app-network: driver: bridge ``` #### 5. 在專案底下建立 docker 目錄,存放 docker container 欲掛載的 Nginx 站台設定檔 ``` $ mkdir docker/ $ mkdir docker/nginx/ $ mkdir docker/nginx/conf.d/ $ touch docker/nginx/conf.d/app.conf ``` #### 6. app.conf 內容 ``` $ vim docker/nginx/conf.d/app.conf ``` ```nginx= server { listen 80; index index.php index.html; error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; root /var/www/web_cicd; location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; } location / { try_files $uri $uri/ /index.php?$query_string; gzip_static on; } } ``` #### 7. 提交並推送至 GitLab,開始進行自動化流程 ``` $ git add . $ git commit -m "Add CI/CD scripts" $ git push ``` #### 8. 觀看 CI/CD Pipeline 執行狀況 ※ GitLab -> Your Project -> CI/CD -> Pipelines #### 9. 自動部署成功,可造訪網頁,確認內容是否已更新 網址:http://<your-server-ip>  #### 10. 至伺服器查看容器執行狀況 本機連線至伺服器 ``` $ ssh deployer@<your-server-ip> -i ~/.ssh/deployer_private_key ``` 伺服器端 ``` $ cd /var/www/web_cicd $ docker-compose ps ``` 此時可以看到 docker-compose.yml 已經幫我們建立容器了  --- <div style="text-align: center;"> <span style="font-size: 50px; font-weight: bold;">大功告成!</span> </div> ---
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up