---
title: "Rails 部署實踐 - 使用 GitHub Actions 自動化建置"
date: 2022-06-10T00:00:00+08:00
publishDate: 2022-06-10T00:00:00Z
lastmod: 2023-09-03T17:41:34+08:00
tags: ["Rails","教學","部署","實作","Rails 部署實踐","GitHub"]
series: "rails-deployment-in-practice"
toc: true
permalink: "https://blog.aotoki.me/posts/2022/06/10/rails-deployment-in-practice-auto-build-with-github-actions/"
language: "zh-tw"
---


GitHub Actions 跟 GitLab CI 有著不少差異，雖然在這類工具中不外乎就是生產線（Pipeline）和任務（Task）的搭配使用，然而每套系統都還是有著不同的設計可以使用。

因為我比較常使用 GitLab CI 因此有著完整的[樣板專案](https://github.com/elct9620/ruby-gitlab-ci)可以使用，目前還在建置 GitHub Actions 的樣板，這篇文章主要是我在 GitHub 上面的專案所彙整出來的使用技巧。

<!--more-->

## GitHub Actions 基礎概念{#github-actions-concept}

跟 GitLab CI 不同的地方在於，整個 GitHub Actions 允許有多個工作流程（Ｗorkflows）的存在，我們可以理解為允許多個生產線（也就是 `.gitlab-ci.yml` 設定）的狀態，基於這樣的前提我們就能把不同的任務拆分出來處理。

> GitLab CI 其實也能夠利用 [Child Pipeline](https://docs.gitlab.com/ee/ci/pipelines/parent_child_pipelines.html) 的方式來實現這件事情。

### 基本結構{#basic-structure}

我們只需要在 `.github/workflows/` 目錄下增加像是 `containerize.yml` 的設定檔即可。

```yaml
name: Containerize # 名稱

on: # 啟動條件
  push:
    branches:
      - main

jobs: # 任務
  assets:
	runs-on: ubuntu-latest # 運行環境
	steps:
	  # ...
```

基本結構大致上是類似的，然而我們沒有了階段（Stage）的設定，每個工作流程基本上是會同步的進行，但是可以利用 `on` 的觸發來設定要在什麼情況下才運行。

至於 Job（任務）的使用基本上是類似的，只不過在 GitHub Actions 中不是使用容器而是直接使用 Runner 來運行。

## 自動建置 Rails 鏡像{#auto-build-rails-image}

想在 GitHub Actions 自動建置鏡像某方面來說比在 GitLab CI 來說是更容易的，因為有許多現成的 Action（動作）模組可以使用，然而每個模組的作者在文件上的撰寫品質不一，因此也不一定容易使用，這邊我們使用的基本上都是官方的套件，因此還不算難使用。

### Assets Precompile

同樣的，我們希望在容器化之前就先把素材預先編譯完畢，因此第一個加入的就是 Assets Precompile 的任務。

```yaml
name: Containerize

on:
  push:
    branches:
      - main

env: # 共通環境變數
  RUBY_VERSION: '2.7.5'
  NODE_VERSION: '16'
  RAILS_ENV: production
  RAILS_MASTER_KEY: ${{ secrets.RAILS_SECRET_KEY }}

jobs:
  assets:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2 # 預先做成腳本的動作，不需要自己下命令
    - name: Set up Ruby
      uses: ruby/setup-ruby@v1
      with: # 預先動作的額外設定，這邊是設定 Ruby 版本
        ruby-version: ${{ env.RUBY_VERSION }}
        bundler-cache: true
    - name: Set up Node
      uses: actions/setup-node@v2
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'yarn'
    - run: yarn install # 直接執行命令的情況
    - run: bundle exec rake assets:precompile
    - name: Archive precompiled assets
      uses: actions/upload-artifact@v3
      with:
        name: assets
        path: public/
        retention-days: 7
```

在上面這段大多是使用 GitHub、Ruby、Node.js 官方預先準備好的動作來處理，假設是一些主流的語言、工具大多都已經有先寫好的動作，可以直接當作是模組載入使用。

在這個階段我們基本上就是把 Ruby、Node.js 都安裝一遍，然後執行 `rake assets:precompile` 預先編譯素材，最後使用 Artifacts（生成物）功能將編譯好的檔案上傳。

## 製作容器{#build-container}

接下來我們要進行容器化的處理，基本上大部分的命令都已經有預先製作好的版本，我們只需要套用正確的設定即可。雖然 Docker 可以使用 Buildx（Buildkit 的封裝）然而卻無法使用 Registry 版本的封裝，這會讓容器化的速度稍微變慢，不過還在可以接受的範圍之內。

```yaml
# ...

env:
  # ...
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ...
  build-and-push-image:
    runs-on: ubuntu-latest
    needs: assets
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Download precompiled assets
        uses: actions/download-artifact@v3
        with:
          name: assets
          path: public/

      - name: Log in to the Container registry
        uses: docker/login-action@v1
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=ref,event=branch
      - name: Build and push Docker image
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:main
          cache-to: type=inline
```

因為 GitHub 有也提供 Registry 的伺服器，因此我們可以直接使用自動設定好的帳號密碼登入來上傳，跟 GitLab CI 版本不太一樣的地方是我們的鏡像標籤。

在 GitLab CI 的版本中，我們會產生類似 `registry.gitlab.com/example/example:c5e6528` 的鏡像標籤，然而在 GitHub Actions 預先製作好的命令中，產生的是 `sha-c5e6528` 這樣的標籤，然而這不影響我們使用以及控制版本來進行部署。

在這裡我們對 `tags` 做了三個選想，分別是 `sha` 為基礎的標籤、`1.0.0` 和 `1.0` 以版本為主的標籤，以及以 Branch 名稱的標籤，這個規則基本上是對應大部分 Docker 鏡像的規則，可以依照自己的需求調整。

完成這些設定之後，我們就可以在 GitHub 上面自動的產生 Rails 的容器鏡像，如果是公開的專案就能夠直接透過 GitHub 提供的 Registry 來直接使用。

---

如果想在第一時間收到更新，歡迎[訂閱弦而時習之](https://mailchi.mp/aotoki/rails-deployment-in-practice)在這系列文章更新時收到通知，如果有希望了解的知識，可以利用[Rails 部署實踐回饋表單](https://us4.list-manage.com/survey?u=dd3d68032c0510041f1302539&id=f25e0dc43e&attribution=false)告訴我。

