整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ
或許你該懂一下 CI/CD?用 CD 部署到 Render.com
前言
身為一名工程師來講,其實稍微懂一點 CI/CD 是滿有幫助的,畢竟如果想要做一點小專案之類的,那麼 CI/CD 是可以派上一些用場,就算你只是一名前端工程師,也可以善加利用 CI/CD 來做一些事情。
CI/CD 是什麼?
首先我們要先認識一下什麼是 CI 與 CD,CI 其實是 Continuous integration 的縮寫,而中文是持續整合,當然也有可能看到持續集成,而 CD 則是 Continuous deployment 的縮寫,中文是持續部署。
ok,如果不出意外的話,現在已經出意外了,因為你應該感覺到滿頭問號中,你應該想著…
「到底持續整合跟持續部署是在持續什麼東西呢?」
所以這邊我們先進入一段想像畫面…
當我們想要將專案給部署到正式開發環境時,通常會先輸入一些指令,例如 npm run test 先跑一下測試在執行打包指令 npm run build,然後再將打包好的檔案給部署到正式環境,這樣的流程就是一般的開發流程。
雖然看是沒有什麼太大問題,但是身為人類的我們,總是會有一些疏忽,例如忘記跑測試、忘記打包等等,尤其是忘記跑測試這件事情是非常嚴重的,輕則上線後立刻修正,重則上線後立刻爆炸 on call 修正。
而這一段就是 CI 要幫我們做的事情,他會自動幫我們持續的去整合這些我們應該本身要做的事情,例如測試、打包等等。
CD 呢?其實 CD 就跟部署有關了,簡單來講 CD 就是幫我們自動部署,這樣的好處就是我們可以省下一些時間,而且也可以避免一些人為的疏忽。
講了那麼多 CI/CD 的基本概念後,接下來就準備要來使用 CI/CD 了,但是我們要用哪一套了?市面上的 CI/CD 工具我大概列下有以下
- Travis CI
- Circle CI
- Jenkins
- GitLab CI
- GitHub Actions
以上是我大概知道的 CI/CD 工具,但這一篇我會以 GitHub Actions 作為教學範例,畢竟 GitHub 是我們常用的平台,而且 GitHub Actions 也是 GitHub 官方提供的,所以我們就以 GitHub Actions 來做教學哩~
YAML 語法
開始玩 GitHub Actions 之前,我們必須先學習一個東西,也就是 .yml 檔,這個檔案是 GitHub Actions 的設定檔,我們可以在這邊設定我們要做什麼事情,例如測試、打包、部署等等之類,另一種檔案格式是 .yaml,其實 .yml 跟 .yaml 是一樣的,只是副檔名不同而已。
那麼 .yml 該怎麼寫呢?基本上 .yml 有以下幾個特性
- 以縮排來區分層級
#代表註解- 主要是以
key: value的方式來寫 - 用
-區分多個值,也可以用[]來區分多個值,其實-跟[]是一樣的,只是[]可以讓你的 .yml 檔案看起來比較整齊而已,就是類似陣列一種,如果是物件的話也是使用key: value的方式來寫 |與>代表多行文字,但是前者會保留換行,後者則會將換行去除{{ }}代表變數,可以用來取代一些值,例如{{ secrets.GITHUB_TOKEN }},這邊的secrets.GITHUB_TOKEN是 GitHub Actions 提供的一個變數,可以用來取得 GitHub 的 token,這邊的secrets是一個物件,而GITHUB_TOKEN則是物件的 key,所以我們可以這樣取得secrets.GITHUB_TOKEN的值
本質上來講 .yml 其實是 JSON 的一種格式,所以如果你有 JSON 的基礎,那麼 .yml 應該不難理解,底下我也寫一下 .yml 跟 JSON 的對照表
註解
1 | |
key: value
1 | |
1 | |
多行文字
第一種寫法(保留換行):
1 | |
1 | |
第二種寫法(去除換行):
1 | |
1 | |
陣列
第一種寫法:
1 | |
1 | |
第二種寫法:
1 | |
1 | |
變數
1 | |
1 | |
物件
1 | |
1 | |
陣列物件:
1 | |
1 | |
以上差不多就是 .yml 的基本概念。
GitHub Actions 基礎
前面認識了基本的 YAML 之後就可以準備來玩 GitHub Actions 囉!
那麼 GitHub Actions 會有介面可以直接設定嗎?不,沒有,主要你必須使用 .yml 設定,這個檔案又稱為 workflow,而 workflow 會放在 .github/workflows 資料夾下,在官方文件上有提供一張圖

在這張圖我們可以看到以下東西
- Event
- Runner 1
- Job 1
- Step 1: Run action
- Step 2: Run action
- Step 3: Run action
- Step 4: Run action
- Job 1
- Runner 2
- Job 2
- Step 1: Run action
- Step 2: Run action
- Step 3: Run action
- Job 2
基本上所有的 workflow 檔案必定會有 Event(事件),而 Event 會觸發 Runner,而 Runner 會執行 Job,而 Job 會依照 Step 順序執行。
在這邊官方有提供一個範例,我們先來看一下
1 | |
不用太緊張,這邊我會逐行去解釋。
1 | |
首先第一行 name: learn-github-actions 代表著這個 workflow 的名稱,而這會影響到 GitHub Actions 呈現的名稱,接著是 run-name: ${{ github.actor }} is learning GitHub Actions,這個是 workflow 的描述,而這邊的 github.actor 是一個變數,代表著 GitHub 的使用者名稱,所以這邊的意思就是說「使用者名稱正在學習 GitHub Actions」。
1 | |
接著比較核心重點在於 on: [push],這一行主要是告知 GitHub Actions 這個 workflow 是要在什麼時候觸發,而這邊的 push 代表著當你將程式碼推到 GitHub 時就會觸發這個 workflow。
1 | |
最後就是 jobs,這邊主要是描述了當我們觸發這個事件後要做的哪些任務行為,接下來你會看到 check-bats-version 這個屬性,這個屬性是在描述這個任務的名稱,然後 runs-on: ubuntu-latest 是在描述我們要在什麼樣的環境下執行這個任務,接著 steps 就是這個任務有哪些步驟。
接下來的 - uses: 有一點特別了,相信應該是滿多人會感到混亂的地方,因為後面寫著 actions/checkout@v3,那麼這是什麼意思呢?而且這又是哪裡來的東西呢?
首先其實 actions/checkout@v3 跟下一行的 actions/setup-node@v3 都是來自 GitHub Actions 的官方庫,在 GitHub 官方有一個組織叫做 Actions

所以其實這一段的白話文意思是這樣的
「我要使用 actions 組織下的 checkout 儲存庫,並且版本是 v3 版本」
那 checkout 這個儲存庫用途是幹嘛的呢?簡單來講就是讓你的 Workflow 可以取得你的程式碼,而 setup-node 則是讓你可以設定 Node.js 的版本,這邊的 v3 則是版本號。
相信應該滿多人對於 uses: actions/checkout@v3、actions/setup-node@v3 這兩個東西終於有點概念了。
而後面 actions/setup-node@v3 有一個 with 屬性,這個屬性是用來設定一些值,例如設定 node-version: '14',這邊的 node-version 就是設定 Node.js 的版本,而 14 則是版本號,當然 setup-node 它不只有這個屬性,只是大多都只需要設定版本就可以了,如果你想了解更多可以點擊 setup-node 這個儲存庫來觀看。
最後就是 - run: npm install -g bats、- run: bats -v,這兩個非常簡單,就是你本地電腦執行 npm install -g bats、bats -v 的意思,這邊的 - run: 代表著執行指令。
最後來看一下 GitHub Actions 文件所提供的可視化圖

相信你應該有一些概念了,那麼以上就是官方 GitHub Actions 的範例解釋哩~
GitHub Actions 實戰
前面學了很多基本觀念,如 YAML 語法、GitHub Actions 基礎,所以接下來就準備來寫一下自己的 GitHub Actions 囉~
首先一開始我們先使用 Express 建立一個初始化專案,這邊我會使用我先前準備好的空白專案
可以先 Fork 並 Clone 下來準備練習。
以上準備好後,就打開這個專案,然後在根目錄下面建立一個 .github/workflows 資料夾,因為 GitHub Actions 的設定檔案必須放在這個資料夾下,接著在這個資料夾下建立一個 main.yml 檔案,這個檔案就是我們的 workflow 檔案,所以目前你的結構應該會長這樣

那麼底下的 .yml 檔案名稱沒有特別限制一定要怎樣,只是為了方便管理,所以我們就以 main.yml 來命名(後續這邊我更改成 main-pr.yml,所以你也可以直接使用 main-pr.yml 來命名)。
接下來我們要來做一些事情,首先我們會期望原本我們常常在做的幾個行為自動化
- ESLint 掃描
- 測試
- 打包
- 部署
那麼前三者基本上都是在發起 Pull Requests 之後就會自動執行,而部署則是在合併到主要分支後才會自動執行,所以我們就會來撰寫兩個 workflow 檔案,一個是在發起 Pull Requests 後就會自動執行的,另一個則是在合併到主要分支後才會自動執行的。
因此這邊基本上我會建立兩個檔案,分別是
.github/workflows/main-pr.yml.github/workflows/main-deploy.yml
Pull Requests
首先先打開 .github/workflows/main-pr.yml 檔案,接著我們這邊會需要監聽 pull_request 事件,所以我們就會在 on 屬性下面加入 pull_request,代表著當有人發起 Pull Requests 時就會觸發這個 workflow
1 | |
那麼 pull_request 你會發現我是用物件形式撰寫,代表著它底下其實有超多屬性可以使用,詳細你可以觀看「pull_request」這邊的官方文件,所以這邊我只會示範一個最常用的屬性,也就是 branches,意旨當有人發起 Pull Requests 時,我們會監聽哪些分支,這邊我們會監聽 main 分支,所以我們就會這樣寫
1 | |
代表著只要有人發起 Pull Requests 到 main 分支時就會觸發這個 workflow。
如果你覺得這樣寫很難懂,你也可以改成陣列的寫法
1 | |
當然也有萬用的寫法,就是 *,代表著監聽特定名稱的分支,例如 Git Flow 的 feature/*、release/*、hotfix/* 等等
1 | |
但這邊我們只需要監聽 main 分支就好。
接著呢?接著我們已經撰寫了 Event 事件後就是要寫 Job 了,這邊我們會寫一個 Job,而這個 Job 會有三個步驟,分別是剛剛所提到的 ESLint 掃描、測試、打包,那麼如果要能夠掃描 ESLint 跟測試的話,專案就必須要有相對應的套件,所以我們先來安裝一下
1 | |
接下來你可以替這個專案初始化 ESLint 跟撰寫一點基本的測試檔案,我這邊就不額外說明與介紹這一塊了,只是由於我們需要讓 GitHub Actions 可以執行 ESLint 跟測試,所以我們就需要在 package.json 裡面加入一些指令
1 | |
這邊我們加入了 lint 跟 test 兩個指令,分別是執行 ESLint 跟測試,以上準備好之後,就可以繼續回來寫 Job 了囉。
一開始我們會針對 ESLint 跑一下掃描,所以 Job 就會這樣寫
1 | |
到目前為止來講,你只要 ESLint 有發生錯誤,那麼這個 Job 就會失敗,而且會顯示在 GitHub Actions 上面,如下圖

只有你修正了該 ESLint 錯誤才能夠繼續往下執行

測試跟打包呢?其實測試跟打包都是一樣差不多的行為,只是因為我們是 Express 專案並不需要打包的行為,所以我底下程式碼會忽略掉打包的行為
1 | |
這邊你會發現一件很有趣的事情,也就是類似的行為 runs-on: ubuntu-latest、uses: actions/checkout@v3 跟 uses: actions/setup-node@v3 這些行為都是重複的,為什麼呢?因為每個 Job 都是獨立的,所以每個 Job 都必須要有這些行為,這邊你可以想像成每個 Job 都是一個獨立的 Container,所以每個 Job 都必須要有這些行為,這樣才能夠執行哩。
那麼這邊有一件事情很特別,因為 GitHub Actions 在執行 Job 時的順序是不固定的,所以有可能是 ESLint 先跑完又或者是測試先跑完,那麼如果你期望順序是先跑完 ESLint 再跑測試,那麼你就必須要使用 needs 這個屬性,這個屬性可以讓你指定 Job 的執行順序,所以這邊我們就會這樣寫
1 | |
否則的話你應該會在 GitHub Actions 的可視化圖形長這樣子

當你使用了 needs 這個屬性後,你會發現可視化圖形會變成這樣

Note
可視化圖形觀看位置位於專案的 Actions 頁面。
到目前為止我們的 PR workflow 就完成了,接下來就是要來寫部署的 workflow 囉~
Deploy
接下來打開 .github/workflows/main-deploy.yml 檔案,這邊我們會監聽 push 事件,所以我們就會在 on 屬性下面加入 push,代表 Pull Requests 合併到主要分支後就會觸發這個 workflow,並且我們只監聽 main 這個分支
1 | |
只需要做到這樣子,你就可以監聽並執行部屬行為了,很簡單吧?
Deploy to Render.com
那麼我們要部署到哪裡呢?這邊我會示範部署到 Render.com,在 Render.com 的官方文件中,有一個 Deploy Hook 可以使用。
那你可能會想說為什麼還要自己用 CD 部署?不是可以直接自己監聽 Branch 分支,當有新的分支讓 Render.com 自己部署就好了嗎?但是實際上來講,我們有些時候會遇到一些特殊的狀況是必須要先跑完自己的 CI/CD 之後才能部署的,所以這邊才會額外介紹如何用 CI/CD 自己部署到 Render.com,而不是直接讓 Render.com 自己監聽 Branch 分支。
但是是如果要做到這個需求的話,是必須要做一些前置動作的,所以接下來就跟著我一起來設定吧!
首先在你建立好 Render 專案時,你要記得把 Auth Deploy 給關閉,否則你會發現 CI/CD 還沒跑完,專案就已經被部署到 Render 上面

如果是已經建立好的專案,那麼你可以到專案的設定頁面關閉 Auth Deploy

那麼剛好你也會看到 Deploy Hook 的欄位,請把這個網址複製起來,因為我們會使用這個網址來部署,請小心不要把這個網址外流出去,因為這個網址可以讓任何人都可以部署你的專案,所以請小心保管。
接下來我們必須要到個人設定中取得 API Key,請你點一下自己頭像找到「Account Setting」

接著找到 API Keys,並點一下「Create API Key」

名稱基本上隨便你取,我這邊是取名為 Example,按下「Create API Key」之後,你會取得一組 Key,請把那一組 Key 保存下來,因為我們會使用到這一組 Key

接下來讓我們回到 Job 的部分,那麼部署之前你可能會想說,雖然 Pull Requests 已經通過了,但是我還是想要再跑一次 ESLint 跟測試,這樣才能夠確保我們的專案是沒有問題的,所以可能就會這樣寫
1 | |
(其實就只是把 Pull Requests 的 workflow 複製過來而已)
再來就是要來撰寫部署了,在部署之前我們會先把剛剛複製的 Deploy Hook 跟 API Key 貼到 GitHub 的 Secrets 裡面,位置會在該儲存庫的 Setting 可以找到

接著點一下「new repository secret」,然後輸入 RENDER_DEPLOY_HOOK 跟 RENDER_TOKEN 並貼入相對應的值,最後點一下「Add secret」就完成了~
都完成以上後回來到 main-deploy.yml 繼續撰寫部署的部分,基本上都跟剛剛差不多,只是我們必須跑完 ESLint 跟測試後才能夠部署,所以我們就會使用 needs 這個屬性,這邊我們就會這樣寫
1 | |
接下來呢?接下來這邊稍微有一點特別了,如果要部署到 Render.com 的話將會使用到 curl 的方式,因此就會變成以下
1 | |
接下來你就可以試著發起 PR 並且合併到 main 分支,你會發現 GitHub Actions 會自動執行,並且會自動部署到 Render.com 上面囉~



最後這邊我也附上我寫完的範本給予參考哩
參考文獻
整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ