GabrielCYOU_logo

部署流程自動化 GitHub CD w/ Flask & Deploy Keys

每次寫完程式碼 Push 上 GitHub 後,就要手動部署一次(我是部署在 GCP),所以今天來處理 CD (Continuous Deployment) 的部分,使用 GitHub Webhhok 到我的 Server,使用 Flask 接收並執行 Bash Script。

Reference: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys#set-up-deploy-keys

簡單說明達成 CD 的邏輯

這篇適合已經熟悉 Nginx 或 Apache 反向代理以及 Systemd,知道一點 Python 和 SSH 的人類。

最基礎(暴力)的 Deploy 就是 SSH Server 然後 git clone 之後運行他,因為我也不是很 Senior,這個應該非常適合我,這個方法會遇到的唯一困難就是遇到自己的 private repo 要設定 SSH Key,讓你的 Server 在 git clone 時不會遇到權限問題,稍微整理一下總共有以下幾個步驟:

  1. 製作 SSH Key 放上 GitHub(僅適用 private repository) & 試試看 git clone
  2. 寫 Flask 接收 GitHub Webhook
  3. 寫部署的 Bash Script

製作 SSH Key 放上 GitHub(僅適用 private repository)& 試試看 git clone

可以參考這個 GitHub 頁面:Generating a new SSH key。使用你的 GitHub Email,然後他會詢問 passphrase,你要使用空白或是自己設定都可以。完成之後他會存在 ~/.ssh 資料夾裡面。

ssh-keygen -t ed25519 -C "[email protected]"

cat ~/.ssh/id_ed25519.pub 把你的「public key」顯示出來之後複製貼上到你的 repository >> Settings >> Deploy keys

GitHub Settings/Deploy keys

好酷!直接使用 SSH clone,之前都是使用 https 的方法複製的。

git clone [email protected]:GITHUB_USER/YOUR_REPO.git

好的我確定他有 clone 到我的 server,下一步驟就是寫 Flask 聆聽 GitHub Webhook。

寫 Flask 接收 GitHub Webhook

請出 ChatGPT 然後修修修修修,確認程式沒有問題,然後用原本就有的 Nginx 或 Apache 網頁伺服器反向代理到設定的 port 5055。

# Python Flask @~/cd.py
import subprocess
import hmac
import hashlib
import os
from flask import Flask, request, abort
from dotenv import load_dotenv
load_dotenv()

app = Flask(__name__)

secret = os.getenv('GITHUB_WEBHOOK_SECRET')
if not secret:
    raise RuntimeError("GITHUB_WEBHOOK_SECRET not found")

@app.route('/', methods=['POST'])
def webhook():
    signature_sha256 = request.headers.get('X-Hub-Signature-256')
    data = request.data
    mac = hmac.new(secret.encode(), msg=data, digestmod=hashlib.sha256)
    valid = hmac.compare_digest('sha256=' + mac.hexdigest(), signature_sha256)

    if signature_sha256 is None or not valid:
        print(f"Invalid signature: {signature_sha256}")
        abort(403)
    try:
        result = subprocess.run(['/home/my_user/app/cd.sh'], check=True, text=True, capture_output=True)
        return result.stdout, 200
    except subprocess.CalledProcessError as e:
        print("Error:", e.stderr)
        return f"Error: {e.stderr}", 500
    except Exception as e:
        error_message = f"Failed to execute script: {str(e)}"
        return error_message, 500

if __name__ == '__main__':
    app.run(port=5055)

寫部署的 Bash Script

這邊就是寫你 push 完會在 server 執行的一些東西,例如 git pull、restart systemd。

#!/usr/bin/bash
cd /home/user/app
git pull origin main
pip install -r requirements.txt
sudo systemctl restart app

完成後記得要讓使用者有權限使用,透過 python 開啟時才不會遇到 Failed to execute script: [Errno 13] Permission denied: /path/to/sh

chmod +x /home/my_user/app/cd.sh

這邊很蠢的跟你們說,我一開始在 Bash Script 裡面寫了 systemctl 重新開啟我的 CD 程式,所以 GitHub 在傳送 Webhook 時還沒接收到我的 Python return 就會被我的 Script 重開,造成 502 網頁伺服器錯誤(因為重開 Apache 就無法回傳反向代理的 Python 的回傳)害我搞了 3 個小時…,不知道在笨幾點嗚。

同場加應 GitHub 回傳狀態 & Tools Config

沒啦我也要記錄一下我東西怎麼設定的,不然這種太少碰到會忘 ><。

Systemd cd.service 設定

Apache 反向代理設定

GitHub Webhooks 結果