【Rails】Capistrano × GitHub Actionsで自動デプロイ環境を整える

# はじめに
RailsアプリをCapistranoでEC2（Docker使ってない）にデプロイしています。
普段ローカルで`bundle exec cap staging deploy`を実行してEC2にデプロイしていたのですが、GitHub Actionsでデプロイできるようにしました。
本記事では以下のシチュエーションを想定しています。
* developブランチで修正する
* ローカルでCapistranoデプロイが既にできている
* ステージングサーバーにデプロイする
* Macを使用している

# 前提
CapstranoはSSH接続ができることを前提としています。
ローカルでデプロイできるということは、すでにローカル環境がリモートサーバーにSSH接続ができるように設定されているからです。
しかしGitHub Actionsのランナーは当然設定してあげないと、対象のリモートサーバーとはSSH接続ができません。
ランナーがSSH接続できるようになれば、あとはcapコマンドでいい感じに自動でデプロイしてくれるようになります。

つまり、**ランナーがリモートサーバーとSSH接続できるようにしてあげること**が一番の鍵となります。

そしてSSHの接続設定を簡単に行えるのが、[shimataro/ssh-key-action@v2](https://github.com/marketplace/actions/install-ssh-key)です。
ワークフロー内で秘密鍵をSecretsからコピーする工程があるのですが、最後に.sshディレクトリごと削除してくれます。

# ざっくり流れ
1. デプロイに必要なファイルの作成・修正
1. ローカルでSSHのキーペアを作成する
1. 公開鍵をサーバーに登録する
1. GitHub Secretsに秘密鍵を登録
1. GitHub SecretsにKNOWN_HOSTSを登録
1. Actionsを実行





# 1. デプロイに必要なファイルの作成・修正
ファイル名は自由ですが、.github/workflowsディレクトリを作成してその中にファイルを置いてください。


```yml:.github/workflows/staging_deploy.yml
name: Staging Deploy

on:
  push:
    branches:
      - develop
jobs:
  deploy:
    name: Capistrano Deploy to Staging
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Install SSH key
        uses: shimataro/ssh-key-action@v2
        with:
          key: ${{ secrets.STAGING_SSH_KEY }}
          name: github-actions # optional
          known_hosts: ${{ secrets.STAGING_KNOWN_HOSTS }}
          if_key_exists: fail # replace / ignore / fail; optional (defaults to fail)

      - uses: actions/checkout@v3
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true # runs 'bundle install' and caches installed gems automatically

      - name: Deploy to EC2
        run: |
          eval "$(ssh-agent -s)"
          ssh-add ~/.ssh/github-actions
          BRANCH=develop bundle exec cap staging deploy

```


以下の2行はsshエージェントを起動し、指定した秘密鍵を登録します。
`config/deploy/staging.rb`の`ssh_options`で使用する秘密鍵のパスを設定しているので不要かもしれませんが、念の為記述しておきます。

```yml
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/github-actions
```

新しく`~/.ssh/github-actions`という秘密鍵を作成するため、そちらを参照するように修正してください。

```rn:config/deploy/staging.rb
set :ssh_options, {
  keys: [
    # 以下に変更
    File.expand_path('~/.ssh/github-actions')
  ],
}
```

必要なファイルの修正が済んだので、いよいよSSHのキーペアの作成をしていきます。
          


# 2. ローカルでSSHのキーペアを作成する
使い回しは良くないのでローカルで普段デプロイ時に利用しているキーペアとは別に、Actions用の公開鍵と秘密鍵を作成します。
Macのターミナルで以下を実行してください。

* `-C`...コメント。任意ですが、後々のことを考えて分かりやすい文言を設定しておくと良いです。
* `-f`...生成されるキーのファイル名とパスを指定します。こちらも任意ですが、.sshディレクトリで簡単に識別できるよう、分かりやすい名前にしました。

その他オプションに関しては[こちら](https://atmarkit.itmedia.co.jp/ait/articles/1908/02/news015.html)をご確認ください。


```sh:ローカル
ssh-keygen -t rsa -b 4096 -C "StagingのGitHub Actions" -f ~/.ssh/stg_github_actions
```
上記を実行後、.sshディレクトリに`stg_github_actions`（秘密鍵）と`stg_github_actions.pub`（公開鍵）が生成されることを確認してください。
**なお、当然ですが秘密鍵はGit管理などしないよう慎重に扱ってください。**

#  3. 公開鍵をサーバーに登録する
サーバーの`~/.ssh/authorized_keys`に公開鍵を登録します。
Macのターミナルでリモートユーザー名とホスト名を各自置き換え、以下を実行してください。

```sh:ローカル
ssh-copy-id -i ~/.ssh/stg_github_actions.pub [リモートユーザー]@[リモートサーバーのホスト名]
```
サーバーにssh接続し、authorized_keysに先ほど作成した公開鍵が記入されてることを確認します。

```sh:サーバー
# ローカルでcat ~/.ssh/stg_github_actions.pubを実行した結果と同じ値が記入されていたらOK
cat ~/.ssh/authorized_keys
```

もし上記`ssh-copy-id`コマンドがうまくいかない場合は、手動でサーバーの`~/.ssh/authorized_keys`に公開鍵を記入すれば大丈夫です。


<details><summary>手動で公開鍵をサーバーに登録する手順（必要な人だけクリックで表示）</summary><div>

先ほど作成した公開鍵をの中身をcatで表示し、値をコピーしてください。

```sh:ローカル
cat ~/.ssh/stg_github_actions.pub
```

sshでリモートサーバーに接続し、`~/.ssh/authorized_keys`の最後にペーストします。

```sh:サーバー
vi ~/.ssh/authorized_keys
```

```sh:サーバー
# ローカルでcat ~/.ssh/stg_github_actions.pubを実行した結果と同じ値が記入されていたらOK
cat ~/.ssh/authorized_keys
```


</div></details>

# 4. GitHub Secretsに秘密鍵を登録

先ほど作成した秘密鍵の中身をコピーします。

```sh:ローカル
cat ~/.ssh/stg_github_actions
```

GitHubの対象リポジトリ＞Settings＞Secrets and variables＞Actions＞Repository secrets＞New repository secretsで新しいsecretを作成します。

* Name...`STAGING_SSH_KEY`
* Value...秘密鍵の値


# 5. GitHub Secretsにknown_hostsを登録

リモートサーバーにssh接続をすでにしたことがある人は、ローカルのknown_hostsファイルにリモートサーバーの公開鍵情報が登録されていると思います。
known_hostsの中身を表示し、対象のリモートサーバーのホスト名（example.com）かIPアドレスで検索してみてください。

```sh:ローカル
cat ~/.ssh/known_hosts
```

ヒットした行全てをそのままコピーします。

もし検索がうまくいかない場合は、[ssh-keyscan](https://unicorn.limited/jp/rd/linux/20211216-knownhosts.html)というコマンドの結果をコピーしても大丈夫そうです（試したことがないので保証はしません）

known_hostsをコピーしたら、GitHubの対象リポジトリで新しいsecretを作成します。
* Name...`STAGING_KNOWN_HOSTS`
* Value...コピーした値

これで必要なSecretsは揃いました！

# 6. Actions実行
ymlに以下のトリガーを指定したため、例えばdevelopブランチがpushされたタイミングで実行されます。
トリガーは自由に設定していただいて大丈夫です。
Actions実行後、正常にデプロイできたか確認してください。
```
on:
  push:
    branches:
      - develop
```

以上です！


# エラー集
私が遭遇したエラーと解決策を載せておくので、参考にしていただければと思います。


## Permission denied (publickey). fatal: Could not read from remote repository.

```sh
00:02 git:check
      01 git ls-remote git@github.com:hoge/repo.git HEAD
      01 Permission denied (publickey).
      01 fatal: Could not read from remote repository.
      01 
      01 Please make sure you have the correct access rights
      01 and the repository exists.

```

EC2がGitHubにSSH接続できないことが原因でした。
実際リモートサーバー内で`git ls-remote git@github.com:hoge/repo.git HEAD`をすると、Permissionエラーが出ました。
以下の記事を参考に、リモートサーバー上でSSHキーペアを作成→GitHubのリポジトリのSettings＞Deploy keysに公開鍵を登録してください。

## OpenSSH keys only supported if ED25519 is available (NotImplementedError)



```sh
OpenSSH keys only supported if ED25519 is available (NotImplementedError)
net-ssh requires the following gems for ed25519 support:

 * ed25519 (>= 1.2, < 2.0)
 * bcrypt_pbkdf (>= 1.0, < 2.0)
 ```

[こちら](https://qiita.com/Tosh39/items/7bac004d821299cc4f6b)を参考に、以下をGemfileに記述して`bundle install`を実行してください。

 ```rb:Gemfile
group :development do
  gem 'ed25519', '>= 1.2', '< 2.0'
  gem 'bcrypt_pbkdf', '>= 1.0', '< 2.0'
end
```

# 参考記事
* https://qiita.com/shimataro999/items/b05a251c93fe6843cc16
* https://github.com/LightningFF/deploy/wiki/Deploy-with-Capistrano-&-Github-Actions-on-Git-merge
* https://qiita.com/kazokmr/items/754169cfa996b24fcbf5