GitHub Actions self-hosted runnerで自宅サーバのデプロイをしたら最高だった
モチベーション
僕は自宅のサーバでいくつかの自宅用サービスを動かしています。全てのサービスはコンテナ化してあり、ホストマシン自体には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イメージがあったのでありがたく使わせてもらうことにしました。
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を使った方が良いと思いますが、その話はまた別の機会に…
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
はこんな感じです。
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.yaml
の environment
に追加することで出来ました。
まとめ
自宅サーバでGitHub Actions self-hosted runnerを動かすことで良い感じに自宅用サービスをCDできて最高だった。
Discussion