前言

補完上次的 Github Action 系列文

這篇會簡單敘述如何讓 Github Actions 搭配 AWS 的服務做前後端的部署(CD - Continuous Deployment),讓 AWS Live URL 的狀態跟 Repo 保持一致

我們是利用 AWS EC2 web service 在公開網路上執行網頁服務來 run side project

此篇是假設你已經有了 AWS EC2 的知識的前提下,該如何利用 Github Action 來串最後的 Deploy

對如何使用 AWS EC2 有興趣的人可以參考免費的官方文件或是各類線上教學

AWS CodeDeploy

利用 AWS 現成的服務 CodeDeploy 就能跟 Github Actions 完成自動部署的腳本

AWS 自己有在 Github Actions Market 出了官方套件給開發者整合 AWS Key,也有相關的說明文件

AWS IAM Role

首先得在 IAM 服務內新增一個 IAM rule 給要搭配 Github Action 部署用的 CodeDeploy 使用去產生對應的 Access Key Pair

IAM -> Access management -> Users

新增一個 User 取名叫 CodeDeploy,Policy 加入 AWS 已經打包好 policy package

Add Inline policy -> Import managed policy

搜尋 “AWSCodeDeploy” 就會出現一些已經打包好的 policy 集合,選擇 “AWSCodeDeployDeployerAccess”

在建立好的 IAM User 選單內選擇 Security credentials -> Create access key 就會得到一組 Access key,這會需要放在在 Github repo 中的 Secrets 內供 Actions 腳本取用,建議先複製到記事本方便等等貼上

Note: Secret Key 務必自己紀錄好,一但建立完畢,就無法再看到,AWS 官方建議管理員時不時就應該換新的 Access Key 來確保系統安全

For your protection, you should never share your secret keys with anyone. As a best practice, we recommend frequent key rotation. If you lose or forget your secret key, you cannot retrieve it. Instead, create a new access key and make the old key inactive.

Github Secrets

到需要做 CD 的 Github Repo 中的 Settings -> Secrets 內建立新的 Secrets

把剛剛建立好的 CodeDeploy User 的

  • AWS Access Secret Key 放到 Value 欄位內,取名為 AWS_SECRET_ACCESS_KEY
  • AWS Access Key ID 放到 Value 欄位內,取名為 AWS_ACCESS_KEY_ID

CD pipeline

前置準備都差不多了,就加入以下的 yml 檔案

workflow_dispatch 是 Github Actions 支援手動 trigger 的語法

deploy.yml example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
name: Deploy
on:
workflow_dispatch:
inputs:
commitId:
description: 'git commit Id to deploy'
required: true

jobs:
deployBackend:
name: CD - AWS codedeploy
runs-on: ubuntu-latest
strategy:
matrix:
appname: ['Your_Web_Service_Folder']
deploy-group: ['Your_Deployment_Group_Name']
repository: ['Your_Github_Repo']
aws-region: ['us-east-1']

steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ matrix.aws-region }}
- name: CommitId to be deployed
run: |
echo "Deploying code commit: ${{ github.event.inputs.commitId }}"
- name: AWS Create Deployment
run: |
aws deploy create-deployment \
--application-name ${{ matrix.appname }} \
--deployment-config-name CodeDeployDefault.OneAtATime \
--deployment-group-name ${{ matrix.deploy-group }} \
--file-exists-behavior OVERWRITE \
--github-location repository=${{ matrix.repository }},commitId=${{ github.event.inputs.commitId }}

手動執行這個 workflow 就會把目前的 Github Repo Deploy 到你的 EC2 上,在 CodeDeploy service 中的 Deployments 就能看到歷史紀錄,以及每筆 Deploy 的狀態

appspec.yml

這是 AWS CodeDeploy 的描述檔

是指定 source (目前 repo 中的檔案) 與 destination (在 EC2 上要放的位置) 的重要檔案

也是在 Deployed 到 EC2 後,能運用 Life cycle Hook 執行不同 script 的檔案

比如我們需要停止目前的 web service,重新 launch,或是重新 build sources

  • BeforeInstall
  • AfterInstall
  • ApplicationStart
  • ApplicationStop

我們在 appsepc 檔加的 hook 邏輯算是比較簡略的,畢竟不是太大的應用,是閒暇時做興趣的 side project 😬

放在 aws-scripts 內的 .sh 就是供 AWS CodeDeploy 在不同 Life Cycle 執行用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: 0.0
os: linux
files:
- source: /
destination: Your_Destination_Folder_on_EC2

# Life cycle hooks once deployment is executed to EC2 instance
hooks:
AfterInstall:
- location: aws-scripts/after_install.sh
runas: root
ApplicationStart:
- location: aws-scripts/docker_up.sh
runas: root

Frontend CD

前端的 CD 獨立出來紀錄是因為這包 Side project 是前後端放在一起,避免檔案複雜度,所以只放 codes,並不會放 frontend build 完後的 web sources

換句話說,我們需要有個機制可以讓 Github Actions 搭配 AWS 能 Build + Deploy 前端

在我們的 Case 中,因為是使用免費的 AWS,在各類資源不超過限制用量的考量下,我選擇將前端獨立拆開來做 CD preparation

S3 bucket

嘗試了一些方法後,我的方式就是將 frontend build 上傳到 S3 bucket,在 CodeDeploy AfterInstall 中把 S3 bucket 的東西 sync 到 EC2 指定的位置,最後再重新 Launch web service

基本上不需要改預設的選項,一路下一步下去就可以,存取的方式一樣透過 AWS Access Key,不需要特別設置成 Public bucket

建立好要存放資源的 S3 bucket 後,就到 IAM service 中 CodeDeploy User,Import 另一個打包好的 Policy “AmazonS3FullAccess”,我選擇用同一把 AWS Access Key 來執行上傳到 S3 的 Github Actions workflow

這裡設置成手動執行,因為免費的 AWS 帳號有限制 S3 每月的上傳次數XD,不希望是每推一次 commit 就跑一次,腳本內選擇將 frontend build 先打包成 zip 再上傳到 S3 也是一樣的原因…因為傳一個檔案就算一次唷 😂

upload.yml example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
name: Upload Frontend to S3

on:
workflow_dispatch:
inputs:
tags:
description: 'tag - please input date MM/DD/YYYY'
required: true

jobs:
deployFrontend:
name: Upload - Frontend
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['13.14.x']
aws-region: ['us-east-1']
s3-bucket: ['Your_S3_Bucket_Name']
web-source: ['out.zip']

steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
uses: actions/checkout@v2

# Set up required dev env
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

# Building
- name: Install dependencies
working-directory: frontend
run: |
echo "Tag: ${{ github.event.inputs.tags }}"
npm install --save-exact
- name: Building Frontend
working-directory: frontend
run: |
npm run build
zip -r ${{ matrix.web-source }} your_web_build_folder
ls ${{ matrix.web-source }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ matrix.aws-region }}

- name: Uploading to S3
run: aws s3 cp frontend/${{ matrix.web-source }} s3://${{ matrix.s3-bucket }}/frontend/ --acl bucket-owner-full-control

AWS S3 指令可以直接參考官方文件,來看你需要怎麼樣的指令,我是用 aws s3 cp 來上傳

AfterInstall hook

執行完上傳 frontend build 到 S3 bucket 後,我們在 CodeDeploy 完成後的第一步要做的事就是把 frontend build 從 S3 bucket 抓下來,才有辦法重新執行 web service 去反應最新的狀態

想法其實直覺的,不過實務上是測試了好幾次,確定有照預定的路線走下去…😅

after_install.sh example

1
2
3
echo "[AfterInstall] downloading frontend sources from S3"
aws s3 cp s3://Your_S3_Bucket_Name/Your_zip_file /Target_folder_on_EC2/
unzip -o /Target_folder_on_EC2/Your_zip_file -d /Target_folder_on_EC2/

所以 CodeDeploy 不止可以搭配 EC2,有 hook 的幫助,指令能做的事情基本上都能夠做到,要注意的就是 AWS IAM 權限,當初因為不熟一直碰壁,要多玩個幾次才比較有感覺

ApplicationStart hook

這邊我們做的事情就是到 destination folder 把前後端都重新 build 一次,然後讓整個 web service launch 起來

Note: hook 執行的位置在 EC2 root 底下,跟 appspec.yml 中定義的 destination 一樣

1
2
cd Your_Destination_Folder_on_EC2 
## Execute your building process and launch the application

後記

這邊就不特別談 Docker 的部分,因為是負責後端的朋友把 Docker build 引入 side project 的,應該很多 web service 不一定需要跑 Docker build

總而言之就是把需要啟動網頁應用的步驟變成在 AWS service 上來執行,並透過 Github Actions 的腳本來幫我們一鍵到位 🤟