🚀

GitHub Actions self-hosted runnerで自宅サーバのデプロイをしたら最高だった

2021/12/21に公開

モチベーション

僕は自宅のサーバでいくつかの自宅用サービスを動かしています。全てのサービスはコンテナ化してあり、ホストマシン自体にはdockerとgitといくつかのツールぐらいしか追加していません。そんなサーバを前提にした話です。

前々から自宅サーバで動かしているサービスの継続的デリバリー( 以下CD )をしたいと思っていましたが、最初に思いついた幾つかの方法はどれも「これだ!」って感じがしませんでした。以下、思いついた方法です。

  • CircleCIにSSH鍵を登録してCircleCIから自宅サーバにSSHしてデプロイコマンドを叩く
    • 外部に自宅サーバのSSH鍵を渡すのは怖い
    • SSHのポート開くのもちょっと怖い
  • githubからのwebhookを受け取ってデプロイコマンドを叩く
    • webhookを受け取ってデプロイコマンドを叩くプログラム作らないといけない?
    • CDのログどうやって見る?
    • 自宅サーバにwebhook受け取る用のポートを開放しないといけない(これは許容範囲内だけど欲を言えばポート開けたくない)
  • githubのリポジトリをポーリングし、新しいコミットやタグがあるかどうかなどの情報を取得して必要であればデプロイコマンドを叩く
    • ポーリングしてデプロイコマンドを叩くプログラム作らないといけない?
    • CDのログどうやって見る?
    • pushしてからできるだけ早くデプロイして欲しいけど、ポーリング間隔はあまり短くできない

そんなわけで今まで手動デプロイで凌いでいました。

最近業務でgithub actionsを使うようになったので試しに自宅サーバでgithub actions self-hosted runnerを使ったら上記の問題が全て解決して最高になったので記事にまとめます。

self-hosted runnerを自宅サーバに立てる

self-hosted runnerを動かすためのDockerイメージがあったのでありがたく使わせてもらうことにしました。

myoung34/github-runner

docker-compose.yaml
version: '3.9'

services:
  my_runner:
    restart: unless-stopped
    image: myoung34/github-runner
    environment:
      RUNNER_NAME: my-runner
      RUNNER_SCOPE: repo
      REPO_URL: https://github.com/hoge/piyo
      LABELS: my-runner
      ACCESS_TOKEN: { githubのpersonal access token }
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

docker daemonのソケットをボリュームマウントしているのがポイントです。
これによってself-hosted runner内で docker コマンドを叩くことでホスト側のdockerを操作できます。いわゆるDooD( Docker outside of Docker )というやつですね。

これをサーバに置いて docker compose up しておきます。

ところで1つのself-hosted runnerで1つのリポジトリしか対象にできないんですかね?
でもself-hosted runnerコンテナを1個立てても平常時は50MBぐらいしかメモリを食っていないようなので50個とか100個とかにならない限りはリポジトリごとにコンテナを立てても良いかなぁと思ってます。

GitHub Actionsのワークフロー

続いてgithub actionsのワークフローを書きます。

イメージのビルドとプッシュをしてから、self-hosted runnerの上で docker-compose up -d しています。 build_and_push jobをgithub-hosted runnerで動かしているのに深い意味はないです。こっちもself-hosted runnerにしても良いと思います。

GitLabのコンテナレジストリにpushしていますが、ECRでもDockerHubでも同じ感じでいけると思います。GCRではWorkload Identity Federationを使った方が良いと思いますが、その話はまた別の機会に…

.github/workflows/cd.yaml
name: CD

on:
  push:
    tags:
      - v*

jobs:
  build_and_push:
    name: Build and Push
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Login GitLab
        run: |
          echo ${{ secrets.GITLAB_TOKEN }} | docker login registry.gitlab.com --username=hoge --password-stdin

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: registry.gitlab.com/hoge/piyo
          flavor: |
            latest=false
          tags: |
            type=semver,pattern={{version}}

      - name: Build and Push
        uses: docker/build-push-action@v2
        with:
          context: ./
          file: ./docker/deploy/Dockerfile
          tags: ${{ steps.meta.outputs.tags }}
          push: true

  deploy:
    name: Deploy
    needs:
      - build_and_push
    runs-on: [self-hosted, my-runner]
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v1

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v1

      - name: Login GitLab
        run: |
          echo ${{ secrets.GITLAB_TOKEN }} | docker login registry.gitlab.com --username=hoge --password-stdin

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v3
        with:
          images: registry.gitlab.com/hoge/piyo
          flavor: |
            latest=false
          tags: |
            type=semver,pattern={{version}}

      - name: Deploy
        env:
          VERSION: ${{ steps.meta.outputs.version }}
        run: |
	  docker-compose up -d

今回は docker-compose up だけすれば良いケースの記述ですが、DBがある場合はこの前後でマイグレーションのコマンドを叩いたりしてます。

アプリの docker-compose.yaml

アプリ用の docker-compose.yaml はこんな感じです。

docker-compose.yaml
version: '3.9'

services:
  app:
    restart: unless-stopped
    image: registry.gitlab.com/hoge/piyo:${VERSION}

ここでは最低限しか書いていませんが、実際にはもっと色々な記述が必要だと思います。ポートフォワードしたりボリュームマウントしたり。

動かしてみた

自宅サーバへデプロイが行われる様子をきれいなUIで見れて最高です!

所感

デプロイフローは全てリポジトリ側に書いてあり、一度self-hosted runnerを立てればそちらを触ることは多くなさそう。また、もしデプロイフローが変わってもリポジトリ内のyamlファイルを書いていくだけなので楽そう。

github actionsは業務でも使っているため馴染みがあり、同じ感覚で自宅サーバにもデプロイできるのが非常に便利です。

GITLAB_TOKEN のような秘匿情報をGitHubに設定するのに抵抗があるのであれば、self-hosted runner側の環境変数に設定すれば良さそう?(未検証)(ログに表示されないように気を付ける)
↑追記 github actions self-hosted runnerを動かす docker-compose.yamlenvironment に追加することで出来ました。

まとめ

自宅サーバでGitHub Actions self-hosted runnerを動かすことで良い感じに自宅用サービスをCDできて最高だった。

Discussion