前言

在查詢 Golang 的 CICD 的時候有滿多使用 Docker 部署的解決方案。

但想要嘗試看看如果不使用 Dokcer 的情況下要如何達到類似的目的呢?

流程

  1. 在 GithubAction 上 build golang application
  2. 在 Server 中建立 .env file (透過 scp )
  3. 將 application 上傳至 Server (透過 scp )
  4. 使用 Systemctl 來啟動服務

GithubAction

在使用 GithubAction 前先來了解一下 GithubAction 可以為我們提供什麼幫助。

以下為官網資訊的極簡短介紹

GithubAction 是個使用 event-driven 驅動的程式。

所以我們可以透過設置某些特定的條件讓 GithubAction 會啟動我們寫的 workflow 。

接下來介紹一下 GithubAction .yml 的結構

Event

一個 Event 就是觸發的條件

最常見的就是,當有人 push commit 到 main branch 之後就觸發。

on:
  push:
    branches: [ main ]

Jobs

Jobs 是指一系列的 Step,這裡要特別注意的是在同一個 Job 裡面的 Steps 會依序執行

但如果你一個 workflow 內有兩個 Job 。一旦 event 被 trigger ,系統是預設兩個 Job 會同步執行。

當然你也可以透過設定讓 JobB 在 JobA 執行成功後再執行。

Steps

Step 是指一個單一的 task ,一個 task 可以是別人做好的 action 或是 shell command

如果要執行 action 就使用關鍵字 uses:xxxxxxxx

執行 shell command 就使用關鍵字 run: {{your command}}

來看看一個簡單的例子

jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/[email protected]
      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
                    ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."

SSH 連線

從前面的流程中我們知道,我們會需要在 Server 上建立 .env file 與上傳 application 。

這免不了需要透過 SSH 來進行連線。這裡我們可以想像一下 GithubAction 的 Job Runner 都是一個微小的 OS 系統,他跟我們常用的 OS 沒有什麼差別,所以我們要告訴他如何透過 SSH 連上我們的 Server 。

你可以找找看有沒有人寫好的 action 或是使用 step 的 run 自己寫 command 。

我這邊透過使用 run 的方式來達到這個目的。

在使用這個 step 之前,千萬別忘了將你的 private key 加入 github secret 內。才能夠確保正確運作唷。

- name: Add SSH key
      env:
        SSH_PRIVATE_KEY: ${{ secrets.AWS_PRIVATE_KEY }}
        SERVER_USER: ${{ secrets.AWS_USER }}
        SERVER_HOST_NAME: ${{ secrets.AWS_HOST_NAME }}
      run: |
        mkdir -p ~/.ssh/
        # put ssh key
        echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
        chmod 600 ~/.ssh/id_rsa
        # write ssh config
        printf "Host server\n\tHostname ${SERVER_HOST_NAME}\n\tuser ${SERVER_USER}" >> ~/.ssh/config
        # test ssh connection
        ssh -tt -o StrictHostKeyChecking=no server "echo ssh successful"        

對於 command 不熟的朋友這邊稍微解釋一下
這裡透過 echo "xxx" > ~/.ssh/id_rsa ,將 private key 寫入 id_rsa 裡
chmod 600 為改變權限該檔案的使用權限(知道更多關於檔案權限)
printf "Host server\n\tHostname ${SERVER_HOST_NAME}\n\tuser ${SERVER_USER}" >> ~/.ssh/config 這是將我們 hostname 等資訊加入 config 中,之後我們就可以透過 ssh server 來連線。

建立 .env file 並上傳至 Server

一個服務免不了需要使用到 env file ,當然有許多其他的解決方式,我這裡使用的是建立一個 env file 後傳上去 Server

在這裡你一樣可以選擇使用找到的 action 或是自己寫 command 的方式來執行。我這裡同樣採取自己寫 command 的方式。

- name: scp .env
      run: |
        cat << EOF > ~/config.yaml
        WEB:
          PORT: "${{ env.PORT }}"
        DB:
          MONGO: "${{ env.MONGO_URI }}"
          MONGO_DATABASE_NAME: "${{ env.MONGO_DATABASE_NAME }}"
        EOF
        scp ~/config.yaml server:<your folder location>/config.yaml        

上傳 application & 啟動服務

終於來到最後一個步驟。

- name: scp app & Deploy
      run: |
        scp ~/app server:<your location>/app
        ssh -tt server \
          "
            sudo systemctl stop <your service>
            sudo mv <your location>/app <your destination>/app
            sudo systemctl restart <your service>
          "        

完整 workflow 範例

name: Example

on:
  push:
    branches: [ main ]
    
env:
  PORT: ${{ secrets.PORT }}

jobs:

  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/[email protected]

    - name: Set up Go
      uses: actions/[email protected]
      with:
        go-version: 1.16.2

    - name: Build
      run: |
        cd cmd/
        go build -o ~/app
                
    - name: Add SSH key
      env:
        SSH_PRIVATE_KEY: ${{ secrets.AWS_PRIVATE_KEY }}
        SERVER_USER: ${{ secrets.AWS_USER }}
        SERVER_HOST_NAME: ${{ secrets.AWS_HOST_NAME }}
      run: |
        mkdir -p ~/.ssh/
        # put ssh key
        echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
        chmod 600 ~/.ssh/id_rsa
        # write ssh config
        printf "Host server\n\tHostname ${SERVER_HOST_NAME}\n\tuser ${SERVER_USER}" >> ~/.ssh/config
        # test ssh connection
        ssh -tt -o StrictHostKeyChecking=no server "echo ssh successful"
                
    - name: scp .env
      run: |
        cat << EOF > ~/config.yaml
        WEB:
          PORT: "${{ env.PORT }}"
        EOF
        scp ~/config.yaml server:<your destination>/config.yaml
                
    - name: scp app & Deploy
      run: |
        scp ~/app server:/tmp/release/app
        ssh -tt server \
          "
            sudo systemctl stop <your service>
            sudo mv <your location>/app <your destination>/app
            sudo systemctl restart <your service>
          "        

結語

可以達成目的的方式有很多種,這只是其中一個解決方案。

如果其中有哪些錯誤或是有更好的方法,歡迎留言告知。

特別感謝友人 Louis 先生大力幫助。

我們下次見