GitHub Actions を使った Rails アプリの自動デプロイを10分で理解する

こんにちは、駆け出しエンジニアの[よしこ @k2_yoshikouki](https://twitter.com/k2_yoshikouki)です

CI/CDってカッコいいですよね。なんかこう、カッコいいんですよね（語彙力）

しかし「どうやって実装したらいいのかワケワカメ＼(^o^)／」というエンジニアの方は多いと思います。でも実際はとても簡単なんだよということを知ってほしかったので、10分で読める記事で CI/CD のうちのCD（自動デプロイ）を実装していきたいと思います。

実作業時間は詰まらなかったら30分かからないくらいです。

# ゴール

CI/CD をやってみたいがやったことがないし何から手を付けたら良いか分からないエンジニア向けに、Rails アプリ (6系) で自動デプロイを最速で実装する。

結果、GitHub Actions を使ったCI/CDの大枠を把握できて自力で実装できるようになる



# 実装フローを見ていく

1. Rails アプリを作る
2. Heroku に手動デプロイして動作確認
3. GitHub Actions の初期設定
    - `./.github/workflows/{好きな名前}.yml` の作成
    - 必要な鍵などの取得・GitHubへ設定

すでにアプリケーションがある方は **3** からです。たった2ステップで自動デプロイが実装できます。早速やっていきましょう

# 手順

### Railsアプリの準備

- `rails new sample-rails-with-gha`

    ```bash
    ~/tmp/sample-rails-with-gha
    ❯ rails -v
    Rails 6.0.3.4

    ~/tmp/sample-rails-with-gha
    ❯ ruby -v
    ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19]
    # 後から2.7.2にアップデートします（デバックのため）
    ```

    ```bash
    bundle install
    rails server
    ```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/7b50190d-1094-2bcf-94d1-1f8e4cc712d7.png)


### リポジトリの準備

- GitHub上にリポジトリを作る  
[https://github.com/new](https://github.com/new)
- ローカルにリポジトリを登録

    ```
git remote add origin https://github.com/yoshikouki/sample-rails-with-gha.git
```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/a05f6e82-1bf5-761c-e271-8ce9b19d8dba.png)


    - アップロード

        ```bash
        git add -A
        git commit -m "rails new"
        git push -u origin master
        ```

### デプロイの初期設定と初回デプロイ

- 今回はデプロイ先に Heroku を使う（簡単なので）

    ```bash
    ~/tmp/sample-rails-with-gha master
    ❯ heroku --version

    zsh: command not found: heroku
    ```

- Heroku 入ってなかった・・・

    ```bash
    brew tap heroku/brew && brew install heroku
    # https://devcenter.heroku.com/articles/heroku-cli
    ```

- Heroku アカウントを作っておく
[https://signup.heroku.com/](https://signup.heroku.com/)
- Heroku にログイン（ターミナル上）

    ```bash
    heroku login --interactive
    ```

- Heroku 用のアプリケーションを作る（アップロードする場所を作る）

    ```bash
    heroku create
    # URL を控えておく
    # https://sleepy-beyond-32826.herokuapp.com/
    # `$ heroku open` でも開ける
    ```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/a486a465-7906-2291-466c-4fabab7379b1.png)


- Heroku は sqlite3 に対応していないため、`./Gemfile` を操作する

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/5ac0da2d-8ca1-a318-885e-d599c1ec58f2.png)


- Heroku へデプロイする

    ```bash
    git push heroku master
    ```

- あらら

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/3dca3698-c84b-1a14-e229-1bf49403b976.png)


    - エラーログ

    ```bash
    $ heroku logs
    2020-11-22T14:39:05.610528+00:00 app[web.1]: I, [2020-11-22T14:39:05.610419 #4]  INFO -- : [a6b71e5b-f13a-4224-a857-9b791da3ecc4] Started GET "/" for 133.32.232.41 at 2020-11-22 14:39:05 +0000
    2020-11-22T14:39:05.611222+00:00 app[web.1]: F, [2020-11-22T14:39:05.611152 #4] FATAL -- : [a6b71e5b-f13a-4224-a857-9b791da3ecc4]
    2020-11-22T14:39:05.611223+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] ActionController::RoutingError (No route matches [GET] "/"):
    2020-11-22T14:39:05.611224+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4]
    2020-11-22T14:39:05.611225+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call'
    2020-11-22T14:39:05.611225+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
    2020-11-22T14:39:05.611226+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/rack/logger.rb:37:in `call_app'
    2020-11-22T14:39:05.611226+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/rack/logger.rb:26:in `block in call'
    2020-11-22T14:39:05.611226+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/tagged_logging.rb:80:in `block in tagged'
    2020-11-22T14:39:05.611227+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/tagged_logging.rb:28:in `tagged'
    2020-11-22T14:39:05.611227+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/tagged_logging.rb:80:in `tagged'
    2020-11-22T14:39:05.611228+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/rack/logger.rb:26:in `call'
    2020-11-22T14:39:05.611228+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
    2020-11-22T14:39:05.611228+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/request_id.rb:27:in `call'
    2020-11-22T14:39:05.611229+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] rack (2.2.3) lib/rack/method_override.rb:24:in `call'
    2020-11-22T14:39:05.611229+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] rack (2.2.3) lib/rack/runtime.rb:22:in `call'
    2020-11-22T14:39:05.611230+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
    2020-11-22T14:39:05.611230+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/executor.rb:14:in `call'
    2020-11-22T14:39:05.611231+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/static.rb:126:in `call'
    2020-11-22T14:39:05.611231+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] rack (2.2.3) lib/rack/sendfile.rb:110:in `call'
    2020-11-22T14:39:05.611232+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/host_authorization.rb:76:in `call'
    2020-11-22T14:39:05.611232+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/engine.rb:527:in `call'
    2020-11-22T14:39:05.611232+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/configuration.rb:228:in `call'
    2020-11-22T14:39:05.611233+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/server.rb:713:in `handle_request'
    2020-11-22T14:39:05.611233+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/server.rb:472:in `process_client'
    2020-11-22T14:39:05.611233+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/server.rb:328:in `block in run'
    2020-11-22T14:39:05.611234+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/thread_pool.rb:134:in `block in spawn_thread'
    2020-11-22T14:39:05.612663+00:00 heroku[router]: at=info method=GET path="/" host=sleepy-beyond-32826.herokuapp.com request_id=a6b71e5b-f13a-4224-a857-9b791da3ecc4 fwd="133.32.232.41" dyno=web.1 connect=1ms service=4ms status=404 bytes=1902 protocol=https
    2020-11-22T14:39:05.858618+00:00 heroku[router]: at=info method=GET path="/favicon.ico" host=sleepy-beyond-32826.herokuapp.com request_id=831fe7f9-bc01-496d-a9ac-380c9c89dd3c fwd="133.32.232.41" dyno=web.1 connect=1ms service=2ms status=200 bytes=143 protocol=https
    ```



    - なるほどねえ（分かっていない）
      `ActionController::RoutingError (No route matches [GET] "/"):`

    ```ruby
        # ./config/routes
        Rails.application.routes.draw do
          # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
        end
    ```

    - ないものは作ればいいんだよ（ドヤ顔）

        ```shell
        rails generate controller Home index
        ```

    - ルーティング `./config/routes` もイジる

        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/822b329b-5fc3-78d8-63ab-e23b90f961a4.png)

    - よしよし（手動デプロイで動作を確認）

        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/898a48f8-b764-ef59-9e5d-4d741001adf0.png)


### GitHub Actions を設定していく

- 空の設定ファイルを作る

    ```bash
    mkdir -p ./.github/workflows
    tocuh -p ./.github/workflows/sample_cd.yml
    ```

- 設定していく
    - 設定ファイル

        ```yaml
        name: Deploy to sample-rails-with-gha

        on:
          # master ブランチにプッシュされた場合に動作する
          push:
            branches:
              - master

        jobs:
          deploy:
            runs-on: ubuntu-latest
            steps:
              # GitHub リポジトリからコードを引っ張ってくる
              - name: Checkout
                uses: actions/checkout@v2
              # 前回のコンテナキャッシュがあれば使用する。なければキャッシュを作る
              - name: Cache multiple paths
                uses: actions/cache@v2
                with:
                  path: vendor/bundle
                  key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
                  restore-keys: |
                    ${{ runner.os }}-gems-
              # Ruby をインストールする
              - name: Set up Ruby 2.7
                uses: actions/setup-ruby@v1
                with:
                  ruby-version: 2.7
              # バンドラーをインストールし、初期化する
              - name: Bundle install
                run: |
                  gem install bundler
                  bundle config path vendor/bundle
                  bundle install --jobs 4 --retry 3
              # ヘロクにデプロイする
              - uses: akhileshns/heroku-deploy@v3.6.8 # This is the action
                with:
                  heroku_api_key: ${{secrets.HEROKU_API_KEY}}
                  heroku_app_name: "sleepy-beyond-32826"
                  heroku_email: "yoshikouki@gmail.com"
        ```

    - 簡単な設定ファイルなので説明はファイル内のコメントを参考にしてください。
        - アクションという Ruby でいう Gem のような機能を使用しています。
    - ドキュメント
        - [actions/cache](https://github.com/actions/cache/blob/main/examples.md#ruby---bundler)
        [https://github.com/actions/cache/blob/main/examples.md#ruby---bundler](https://github.com/actions/cache/blob/main/examples.md#ruby---bundler)
        - [actions/setup-ruby
        https://github.com/actions/setup-ruby#setup-ruby](https://github.com/actions/setup-ruby#setup-ruby)
        - [actions/deploy-to-heroku](https://github.com/marketplace/actions/deploy-to-heroku#getting-started)
        [https://github.com/marketplace/actions/deploy-to-heroku#getting-started](https://github.com/marketplace/actions/deploy-to-heroku#getting-started)
    - `secrets.HEROKU_API_KEY` が必要なので Heroku で取得する
        - [https://dashboard.heroku.com/account](https://dashboard.heroku.com/account)
        もしくは `自分のアイコン` > `Account settings`
        - 下部 `API Key`
        ここのKeyをコピーする
    - 取得した Key をGitHub で使用できるように設定する
        - `sample-rails-with-gha` のリポジトリ
        > `Settings` > `Secrets`
        - GitHub Actions で使用する非公開環境変数の設定画面になる
        > `New repository secret`
        - Name に `HEROKU_API_KEY`
        Value に先程Herokuで取得したキーをセット
        > `Add secret`
- 準備は整ったので、master/main ブランチにプッシュしてみる
    - **そして順調にエラーになった**
    - エラーになった画面  
      https://github.com/yoshikouki/sample-rails-with-gha/runs/1438564960?check_suite_focus=true

    ### デバッグ

    - [actions/cache](https://github.com/actions/cache/blob/main/examples.md#ruby---bundler) のドキュメントは bundle がインストールしてある前提だったので `gem install bundler` を追加した
    - Gemfile に記述されている ruby-version と GitHub Actions のコンテナ内のRubyを一致させること

        ```yaml
        uses: actions/setup-ruby@v1
        with:
          ruby-version: 2.7
        ```

        - GitHub Actions [setup-ruby](https://github.com/actions/setup-ruby#setup-ruby) ではマイナーバージョンまでしか指定できないみたいなので、手元Rubyのメンテナンス (ビルド) バージョンをあげることになるかもしれない
        - マイナーバージョンが上がるわけではないので、素直に上げておけばいいと思う

### そして...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/ecf876af-8ac6-1c9c-1cbb-3233b3208103.png)


- 最後の push を済ませれば・・・

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/d9defb72-be29-2768-7ace-96a44ed07415.png)


- 感極まる😂

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532774/79f7079c-5784-5c06-5096-fd8c593b1da6.png)


# まとめ

以上で Rails アプリ (6系) で自動デプロイまでを最速で実装しました。

以後は、master (main) ブランチへ push するだけで heroku へ自動デプロイされます。これで2〜3コマンド分の実行の手間が省けたことになりますし、あなたはもう完全に CI/CD を習得したエンジニアです。

（実際のところ、今回はCI部分を何も扱っていませんし、デプロイに関してもデータベースを触っていないので中途半端な状態ではあるのですが、最低限は実装できたので良しとしましょう。してください。）

# 参考URL

https://github.co.jp/features/actions

https://docs.github.com/ja/free-pro-team@latest/actions