/

GitHub ActionsでMastodonのdocker buildを自動化する

タイトルの通りで、 GitHub Actions を用いて docker build を自動化しました。

重複するビルド

前回 はデータベースサーバーを分離することで、Webサーバー側を身軽にすることに成功しました。

しかしまだ無駄な部分が残っていました。mstdn.maud.io には公開サービスを提供している本番環境のほかに、アップデート前の人柱を担うステージング環境[^1] が存在しています。

[^1]: staging.mstdn.maud.io というのですが、登録は受け付けていないほか、特にフォローもおすすめしません

今までのアップデートの際には、まずステージング環境で docker-compose build を実行し、サービスを再起動し、動作に問題ないことを確認したのちに本番環境でも同様に docker-compose build し…という手順を踏んでいました。毎回ビルドに時間がかかっており、アップデート開始からデプロイまでの所要時間が読めないのが悩みでした。

独自のカスタムを加えていないプレーンな Mastodon を動かす場合、tootsuite/mastodon - Docker Hub を利用することができますが、弊サーバーではそこそこ独自の改変が加わっているので自前でやる必要がありました。

Docker Registry

せっかくなので自分たち用の docker image を置いて docker-compose pull だけで済むようにしたいなあと思い始めました。

特に自分たち以外で使うこともないだろう[^2] ということでプライベートリポジトリにしようと思っていたのですが、これだけのために自前でサーバーを立てようとはあんまり思えず、今流行りの GitHub Packages は Free だとプライベートリポジトリで利用可能なストレージが 500MB まで で、前述の tootsuite/mastodon を見る限り無理そうだったので流れに逆行して Docker Hub になりました。

[^2]: favicon や apple-touch-icon など弊サーバーに特有のアセットが含まれるため他所で使われることがない

GitHub Actions

やるなら akane-blue/mastodon にpushされたら自動でビルドして Docker Hub に上げてくれると便利ですよね。今なら GitHub Actions を使うのが簡単そうなので試してみました。

以下をリポジトリに .github/workflows/push_docker_image.yml みたいな名前で作ります。

name: Push our docker image
on:
push:
branches: [ hota/master ]
pull_request:
branches: [ hota/master ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

jobs:
push_to_hub:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_TOKEN }}
-
name: Checkout Git repository
uses: actions/checkout@v2
with:
repository: akane-blue/mastodon
path: mastodon
-
name: Build and push to Docker Hub
uses: docker/build-push-action@v2
with:
context: mastodon
push: true
tags: ${{ secrets.REGISTRY_USERNAME }}/${{ secrets.REGISTRY_REPO }}:${{ secrets.REGISTRY_TAG }}

わたしの場合は hota/master ブランチだったのでこんな感じですが、適宜読み替えてください。

あとは GitHub リポジトリ上で Settings -> Secrets から Repository Secrets に以下の内容を設定する必要があります。

secret 中身
REGISTRY_USERNAME Docker Hub のユーザー名
REGISTRY_TOKEN Docker Hub のトークン
REGISTRY_REPO Docker Hub のリポジトリ名
REGISTRY_TAG Docker イメージのタグ名

actions/checkout@v2 の中でリポジトリがわざわざ指定されてたり、ここの pathdocker/build-push-action@v2context を揃えてるのは最初この Actions を別のリポジトリで管理しようとした名残です。結局、別リポジトリのpushをトリガーにするのがまあまあ面倒臭いことが判明したので akane-blue/mastodon 自身になりましたが…。

スリム化

無事に docker image のビルドが自動化され、サーバー側はそれらを pull するだけでよくなり、ソースコードをcloneしておく必要がなくなりました。

結果として、現在は以下のファイルだけで構成されています:

mastodon/
 ├ .env.production
 ├ docker-compose.yml
 ├ public/
 │ ├ announcements.json
 │ └ system/
 └ redis/
   └ dump.rdb

public/system/ 以下もメディアを外部に置いていれば特に増えないはず(まだ空)なので、すごくスッキリしましたね!

追記: 便利にする

  • よく考えたらプルリク来たときも固定のタグに上がってくるの良くないし、そもそも使わない気がした
  • 機能追加や検証時に hota/testing/fix-featurename みたいなブランチ切るのでそういうのもビルドしてタグ分けたい

という気持ちになったので、ちょこちょこ書き直しました

結果がこうで

name: Push our docker image
on:
push:
branches:
- hota/**
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

jobs:
push_to_hub:
name: Push Docker image to Docker Registry
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to Docker Registry
uses: docker/login-action@v1
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_TOKEN }}
-
name: Checkout akane-blue/mastodon
uses: actions/checkout@v2
with:
repository: akane-blue/mastodon
path: mastodon
-
name: Get latest branch name
id: set_tag
run: |
cd mastodon
DOCKER_TAG="$(git rev-parse --abbrev-ref HEAD | sed 's/hota\///g' | sed 's/testing\///g' | sed 's/-/_/g')"
echo "::set-output name=docker_tag::${DOCKER_TAG}"
-
name: Build and push to Docker Registry
uses: docker/build-push-action@v2
with:
context: mastodon
push: true
tags: ${{ secrets.REGISTRY_USERNAME }}/${{ secrets.REGISTRY_REPO }}:${{steps.set_tag.outputs.docker_tag}}
  • hota/** ブランチがpushされたときをトリガーとして
    • hota/master のほかに前述の hota/testing/* があるので
  • ブランチ名を適当に切り出してタグ名を決める

ようになりました。

教訓

ブランチ名を取るときのまあまあアホポイントみて…

  run: |
cd mastodon
- CURRENT_BRANCH="$(git branch --sort=-committerdate | sed -n 1p | sed 's/* //g')"
+ CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
- git switch ${CURRENT_BRANCH}
DOCKER_TAG="$(echo ${CURRENT_BRANCH} | sed 's/hota\///g' | sed 's/testing\///g' | sed 's/-/_/g')"
  • git branch だと * hota/master みたいになるので * を取り除こうとした
    • 1行目だけ切り出してるから当然のことで、実際こうだし…
$ git branch --sort=committerdate

* hota/master
hota/testing/fix-hogehoge
hota/testing/add-fugafuga
  • actions/checkout@v2 、そもそもトリガーになったブランチをcheckoutするので git switch する必要なかった
  • つまり現在のブランチ名を取ってくるだけでよかったので git rev-parse 使えば一発

気づき

書いてて思い出したけどじゃあもう CURRENT_BRANCH 自体要らなくない???

-   CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
- DOCKER_TAG="$(echo ${CURRENT_BRANCH} | sed 's/hota\///g' | sed 's/testing\///g' | sed 's/-/_/g')"
+ DOCKER_TAG="$(git rev-parse --abbrev-ref HEAD | sed 's/hota\///g' | sed 's/testing\///g' | sed 's/-/_/g')"

終わり!!!!!

更に

  • そもそも環境変数 GITHUB_REF でトリガーしたブランチ名が取れると教えてもらった。
  • こっちは refs/heads/<branch_name> で返ってくるのでまた削ることになる

つまりこうで

-   DOCKER_TAG="$(git rev-parse --abbrev-ref HEAD | sed 's/hota\///g' | sed 's/testing\///g' | sed 's/-/_/g')"
+ DOCKER_TAG="$(echo ${GITHUB_REF} | sed 's/refs\/heads\/hota\///g' | sed 's/testing\///g' | sed 's/-/_/g')"

なんと1文字も減ってなくてちょっと笑った。

参考