GitHub ActionsでRailsアプリをロリポップマネージドクラウドにデプロイする

ロリポップ!マネージドクラウド(以下マネクラ)へのRails(Ruby)アプリケーションのデプロイは、Herokuと同じようにgit pushをすることでbundle installやDBのマイグレーションなどが自動で行われるようになっている。このウェブ日記もRailsで作っていて、そのデプロイをGitHub Actionsで自動化したのでそのやり方や試行錯誤の過程を記録しておく。

2020-05-02時点で利用している完全なYAML

「結論ははじめに」ということで、以下ようなYAMLと2つのsecrets(SSH_PRIVATE_KEYとLOLIPOP_REPOSITORY_URL)を用意すれば、masterへのpushをトリガーにしてマネクラにRailsアプリケーションがデプロイされる。

name: deploy

on: 
  push:
    branches:
      - master

jobs:
  deploy_to_lolipop_mc:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 0

    - uses: webfactory/ssh-agent@v0.2.0
      with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
    
    - name: add known_hosts
      run: cat .github/workflows/known_hosts >> ~/.ssh/known_hosts
    
    - name: git remote add
      run: git remote add lolipop ${{ secrets.LOLIPOP_REPOSITORY_URL}}

    - name: git remote update
      run: git remote update

    - name: deploy
      run: git push lolipop HEAD:master

各stepを簡単に解説する。

  • checkout@v2にfetch-depth: 0を設定する。デフォルトでは1となっていてgitの履歴をとってこない設定になっているため、マネクラ側にpushする際にnon-fast-forwardと判定されてしまう。デフォルトのままにしてgit push -fとする手もあると思う。
  • webfactory/ssh-agentを使ってsshの秘密鍵を使えるようにする。(後述)
  • webfactory/ssh-agentはknown_hostsの管理はやってくれないので、リポジトリ上にknown_hostsをコミットしておいてそれを書き込む。(後述)
  • マネクラのgitリポジトリをremoteに追加する。
  • git pushのためには一度fetchしてくる必要があるのでremote updateする。
  • マネクラのgitリポジトリにpushする。

今回は、テストの実行などはステップに含めていない。デプロイだけであればこれくらいのステップ簡単にできる。

webfactory/ssh-agent でsshの秘密鍵をメモリ上で管理する

SSHの鍵認証しか許していないサーバにGitHub Actionsの中からアクセスするためには、GitHub Actionsが動作している環境に秘密鍵を置く必要がある。 GitHub Marketplace · Actions to improve your workflowをsshで検索するといくつかsshができるようにするActionsがあることがわかるが今回はwebfactory/ssh-agentを利用した。

sshとその秘密鍵を利用可能にするActionsは大別すると以下の2種類がある。

  • ssh-agentを実行する(webfactory/ssh-agent)
  • ~/.ssh に秘密鍵を書き込む

GitHub Actionsの実行モデルはよく理解できていないが、実行環境のローカルファイルシステム上に秘密鍵を書き込むよりはメモリ内に留められる方法のほうがよかろうと思い、このActionsを使うことにした。なお、公式のactions/checkoutでも秘密鍵を扱う仕組みはあって、ファイル名をランダムにして書き込むということをしている。

https://github.com/actions/checkout/blob/94c2de77cccf605d74201a8aec6dd8fc0717ad66/src/git-auth-helper.ts#L186-L197

Secretsに登録する秘密鍵とURLを準備する

デプロイに使う秘密鍵は、GitHub Actions専用のものを用意したほうが安全だろう。マネクラではキーペアを簡単に作成・登録できる仕組みがあるので、この機能を使ってデプロイ用のキーペアを作っておく。Ed25519の鍵ができるので強度も安心。

マネクラのコンパネでSSHのキーペアを作成する

ここで作成してダウンロードされた秘密鍵の中身をそのまま、GitHub側に SSH_PRIVATE_KEYというsecretsに登録すればOK。

GitHub側でのsecretsの登録

また、LOLIPOP_REPOSITORY_URLという名前で、マネクラのコンパネに書かれているリポジトリ「ssh://xxxxxx@yyyy.lolipop.jp:port/」を追加しておく。

マネクラでの接続先情報

Actionsの実行に必要な秘匿情報の追加はこれでおわり。

known_hostsをリポジトリに入れる

sshで初めて接続しようとしたときにプロンプトが出てくるアレのこと。何らかの方法でマネクラサーバのホストキーをknown_hostsにいれないとgit pushができないのだ。webfactory/ssh-agentのREADMEでは、actionsの中でssh-keyscanを使って書き込むか、この内容は秘匿情報ではないのでリポジトリにコミットするとよいと書かれている。このファイルの目的は、READMEにもあるとおり接続先のサーバが正しいものか、MITM攻撃にあっていないか、を検証するためのものなので、Actionsの中で毎回取得するよりは同じものを使ったほうが良いだろうと思い、コミットすることにした。

マネクラのコンパネからリモートリポジトリの情報を元に、以下のコマンドで実行することでホストキーを取得することができる。しかし、マネクラ側のSSHコンテナが起動していないと鍵がうまく取れないことがあるので、事前に別のターミナルなどでSSHをつないでおいたほうがいい。

ssh-keyscan -p <your port> -H <your ssh host>

上のコマンドを実行したときに、stderrに以下のような文字列が出てきたら、一度SSHしてから試してみるとうまくいく。

xxxx.mc.lolipop.jp: Connection closed by remote host

この問題のせいで、Actionsの中でssh-keyscanするのはひと手間掛かりそう。

おわりに

GitHub Actionsを使ったマネクラへのデプロイは、sshの秘密鍵をどう管理したらいいかわからずに二の足を踏んでいたのだけど、改めて学習し直して自分なりに安全な方法を考えてみた。マネクラでもこれで継続的なデプロイをGitHubだけで完結させることができるので、かなり便利ではないかと思う。

この方法でも危険な部分などあったら、是非コメントやツイッターなどで教えてください。

おまけ

テックブログでのロリポ関連の記事を読んだことで、この記事を書くモチベーションがあがりました。@antipopと@linyowsに感謝!