lock 機構のための GitHub Action を作った
lock 機構を実現するための GitHub Action を作ったので紹介します。
背景
lock 機構によって複数のプロセスが同時に実行されるのを防ぐことが出来ます。
例えば GitHub Flow で Pull Request (以下 PR) をマージしたらデプロイを実行する場合に、デプロイ直前に lock を取り完了後に unlock することで、デプロイ中に他の PR がマージされてデプロイが複数同時に実行されるようなことを防ぐことが出来ます。
また、 Terraform で State の分割作業を行う際に、作業前に lock を取り完了後に unlock することで作業中に terraform plan, apply が実行されるのを防ぐことが出来ます。
今回は GitHub Actions で簡単に lock 機構を実現するための Action を開発しました。
特徴
今回の action の特徴です。
- AWS や GCP といった外部サービスには依存しません
- JavaScript Action として実装され shell や Git のような外部コマンドにも依存しません
- lock の状態管理のために GitHub Repository の branch を利用
- branch の操作は全て GitHub API で実行されるため、事前に actions/checkout でリポジトリをチェックアウトする必要もありません
- lock / unlock の履歴を残せます
使い方
基本は次のように action を実行するだけです。
jobs:
deploy-foo:
runs-on: ubuntu-24.04
permissions:
contents: write # lock / unlock するのに必要
steps:
- uses: suzuki-shunsuke/lock-action@v0.1.1
id: lock
with:
mode: lock # 必須 lock / unlock / check
key: foo # 必須
post_unlock: "true" # 任意。 post step で unlock する
- run: bash deploy.sh
key
と mode
が必須です。
key
は必須で、 lock を一意に識別する文字列です。
サービスや環境を含めることで、サービスや環境毎に lock を管理できます。
単一の key しかないのであれば default
などてきとうな文字列を指定してください。
mode
には lock
, unlock
, check
のいずれかを指定ください。
- lock: Lock します
- unlock: Unlock します
- check: Lock 状態をチェックします
デフォルトでは lock を取るだけで unlock しないので job が終わったあとも lock は残ります。
post_unlock: "true"
を指定することで post step で unlock をすることが出来ます。
lock を取ったつもりが unlock されていたというのが怖いので、デフォルトでは unlock しないようにしています。
また lock action 実行時に既に lock が取られていた場合、 step は失敗します。
既に lock が取られていた場合でも処理を続行したい場合、 ignore_already_locked_error
で処理を続行することが出来ます。
- uses: suzuki-shunsuke/lock-action@v0.1.1
id: lock
with:
key: foo
mode: lock
ignore_already_locked_error: "true"
- run: echo "already locked"
if: steps.lock.outputs.already_locked == 'true' # output で結果を参照できる
lock を取れてないのに気づかずそのまま処理が続行されるのは危険なため、デフォルトでは lock が既に取られていたら失敗するようにしています。
post_unlock
を使わずに unlock することも出来ます。
- uses: suzuki-shunsuke/lock-action@v0.1.1
with:
key: foo
mode: unlock # unlock する
lock が取られているかどうか check することも出来ます。
- uses: suzuki-shunsuke/lock-action@v0.1.1
id: check
with:
key: foo
mode: check # check する
- run: echo "already locked"
if: steps.check.outputs.already_locked == 'true' # output で結果を参照できる
仕組み
lock 機構を実現するには lock の状態をどこかしらに永続化する必要があります。
色々やり方はありますが、今回は GitHub Repository の branch を使っています。
action は lock を取る際に ${key_prefix}${key}
という branch を作成します。
key_prefix
はデフォルトで lock__
ですが、 input で変更できます。
branch の commit message によって lock 状態を管理します。
unlock by suzuki-shunsuke: test
{
"message": "test",
"state": "unlock",
"actor": "suzuki-shunsuke",
"github_actions_workflow_run_url": "https://github.com/suzuki-shunsuke/test-github-action/actions/runs/11545637203?pr=237",
"pull_request_number": 237
}
このように commit message を見ればどの workflow run で誰がいつ lock / unlock をしたか分かります。
branch の history を見ればいつ誰が lock / unlock したのか分かります。
余談ですが、 commit message に PR へのリンクを載せると以下のように PR のタイムライン上に表示されて鬱陶しいのでリンクを含めないようにしています。
Example
workflow_dispatch を用いた簡単なサンプル workflow を提供しています。
この workflow を適当なリポジトリにコピーして workflow_dispatch を実行することで lock 機構を試すことが出来ます。
この workflow では 選択する mode によって動作が変わります。
このサンプルでは terraform plan, apply を題材にしていますがあくまでサンプルなので実際に terraform を実行したりはしません。
-
lock
: lock を取ります。 workflow 完了後も lock されたままになります -
unlock
: lock を解除します -
check
: lock が取られているか確認し、結果を出力します -
terraform_plan
: lock が取られているか確認しterraform plan
を実行したふりをします。 lock が既に取られていたら失敗します -
terraform_apply
: lock を取りterraform apply
を実行したふりをします。 120 秒ほど lock を取ります。 lock が既に取られていたら失敗します。終わったら lock を解放します
まず lock が取られていない状態で、 terraform_plan
を選択して workflow を実行すると普通に成功するはずです。
terraform_apply
を選択して実行すると 2 分ほど lock が取られ、最後に lock が解放されます。
その状態で terraform_plan
や terraform_apply
を選択して実行すると失敗するはずです。
terraform_plan
は lock を取らないので terraform_plan
を並列実行しても問題ありませんし、 terraform_plan
実行中に terraform_apply
を実行することも出来ます。
terraform state の分割などで一定期間 lock を取ったままにしておきたい場合、まず lock
を選択して実行して lock を取ります。
そうすると workflow が終わったあとも lock されたままになります。
作業完了後 unlock
を選択して実行すると lock が解放されます。
key_prefix, key の制約
この action は ${key_prefix}${key}
という branch を作成するため、 ${key_prefix}${key}
は GitHub 及び Git のブランチ名の制約を受けます。
Alternatives
この action 以外にも lock 機構を実現する action は幾つかあります。
公式の action ですが、単純に lock を管理するだけではなく色々複雑なことをやっています。
自分の求めているものとは違うと感じたので試してもいません。
GitHub API で branch を操作している点は今回の action と同様です。
- Docker action なためオーバーヘッドが大きい
- shell script で実装されていて Git に依存している
- 恐らく必ず post step で unlock するようになっているため、 Terraform State の分割作業中は lock を取るといった使い方には向かない (workflow を実行しっぱなしにすれば可能ではあるが)
Discussion