手元で試してから,
Azure へ小さく導入する
このページでは,ACME Conductor をまず 1 台のマシン上で動かして仕組みを確かめ, 次に Azure Container Apps へステージング環境としてデプロイするまでを,実際の コマンドと設定例に沿って説明します.どちらもステージング CA と 1 つのホスト名 から始めることを前提にしています.
必要なもの
手元で試す場合
- Go のビルド環境(
make buildでソースからビルドする場合),または GHCR の公開イメージを取得できるネットワーク bin/acme-conductorとbin/acme-runnerを同じホストに置けること(Conductor のコンテナイメージには Runner が含まれません)- Let's Encrypt ステージング の ACME ディレクトリへの通信と,テスト用の DNS ゾーン(本ガイドの例は Azure DNS)
curlとjq(REST API を試すため)
Azure にデプロイする場合
- デプロイ用の Azure サブスクリプションと,リソースグループを作成する権限
- DNS ゾーン(Azure DNS)と RBAC 権限モデルの Key Vault — デプロイと同じサブスクリプションに置くこと(カスタムロールの割り当て可能スコープの制約)
- そのサブスクリプションにロール定義とロール割り当てを作成する権利(Owner または User Access Administrator 相当)
- OIDC プロバイダ(例: Microsoft Entra ID)に,API 用と GUI 用の 2 つのアプリ登録を作成できること
acme-conductor keygenとacme-runner keygenで作る,交換用共有の往復それぞれに 1 組ずつの Ed25519 署名鍵ペア(合計 2 組)
手元で試す
開発用の認証(localhost-dev)と,Runner を子プロセスとして起動する
local-process ランチャー,ファイルシステムの Certificate Store を使い,
1 台のマシンの上に Conductor と Runner の両方を動かします.ACME は Let's Encrypt の
ステージング を使うため,本物の証明書は発行されません.
passthroughEnv),単一ユーザーの開発ホスト以外では使わない構成です.1. ビルドする
make build # ./bin/acme-conductor と ./bin/acme-runner
./bin/acme-conductor --version
./bin/acme-runner --version
GHCR のイメージ(イメージとリリース)から始めても構いませんが,Conductor のイメージには Runner が含まれないため,local-process ランチャーを試すにはソースからビルドするか,両方のバイナリを含む独自イメージを用意します.
2. 署名鍵を作る
手元での試用では省略しても動きますが(jobSigning のないローカルランチャーは生の JobSpec を渡します),練習を兼ねて作っておくと Azure デプロイの手順に直結します.
./bin/acme-conductor keygen --private job-signing.pem --public job-signing.pub
3. 設定を書く
deploy/examples/conductor-config.example.json
と
deploy/examples/runner-config.example.json
をコピーして,パスを手元の環境に合わせます.バインディング名(letsencrypt-staging,azure-dns-staging,filesystem-dev)は両方の設定で一致していなければなりません.Conductor はバインディング名しか知らず,実体は Runner 側の設定が持ちます.次は Conductor 側の設定の抜粋です(apiVersion,server,database,scheduler などは例のファイルを参照).
{
"executionBindings": {
"local": {
"type": "local-process",
"config": {
"runnerBinary": "/path/to/bin/acme-runner",
"runnerConfig": "/path/to/runner-config.json",
"workDir": "/path/to/conductor-runs"
}
}
},
"acmeBindings": ["letsencrypt-staging"],
"dnsBindings": ["azure-dns-staging"],
"storeBindings": ["filesystem-dev"]
}
Runner 側は storeBindings.filesystem-dev を type: filesystem でファイルシステムの directory に向け,DNS の資格情報が必要なら dnsBindings.azure-dns-staging.passthroughEnv に環境変数名を列挙し,Conductor を起動する側の環境でその変数を export します.
4. Conductor を起動する
./bin/acme-conductor serve --config /path/to/conductor-config.json
既定では 127.0.0.1:8080 で REST API と GUI(/ui/)が動きます.localhost-dev モードでは,ループバックに接続できるローカルユーザーは誰でも管理者として扱われます.
5. ポリシーと target を登録する
docs/conductor.md のセッション例 に沿って,ポリシーを作成し,それを参照する target を作成します.
C=http://127.0.0.1:8080/api/v1alpha1
P=$(curl -s -H 'Content-Type: application/json' \
-d '{"allowedDnsSuffixes":["example.ac.jp"],"acmeBinding":"letsencrypt-staging","renewBeforeDays":30,"keyType":"ec256"}' \
$C/policies | jq -r .id)
T=$(curl -s -H 'Content-Type: application/json' \
-d "{\"fqdn\":\"wiki.example.ac.jp\",\"owner\":\"web-team\",\"policyRef\":\"$P\",\"executionBinding\":\"local\",\"dnsBinding\":\"azure-dns-staging\",\"storeBinding\":\"filesystem-dev\"}" \
$C/targets | jq -r .id)
target の作成でスケジューラが即座に起こされ,証明書がまだない target は待機中の run になります.次で進み具合を見ます.
6. run を見守る
curl -s $C/targets/$T | jq .lastRun
curl -s "$C/audit?targetId=$T" | jq '.items[].action'
lastRun.status が succeeded になれば,filesystem-dev の
directory の下に証明書と秘密鍵が書き込まれています.手動で今すぐ run を要求したい
場合は curl -s -X POST $C/targets/$T/runs を使います(アクティブな run がある間は
409).GUI(http://127.0.0.1:8080/ui/)でも同じ内容を確認できます.
Azure にデプロイする
同梱の Bicep テンプレート(deploy/azure)は, Container Apps 環境の上に HTTPS ingress を持つ Conductor の Container App と,毎分スケジュール実行される Runner の Container Apps Job,権限の重ならない 2 つのユーザー割り当てマネージド ID,3 つの Azure Files 共有, DNS ゾーンと Key Vault へのロール割り当て,Log Analytics を一括でプロビジョニングします.
staging/ から pending/ への移動も,pending/ から claimed/ への移動も 1 回のディレクトリの rename です.Conductor は pollIntervalSeconds(既定 10 秒)ごとに実行の状態を読み,終端状態になるまで待ちます.
まずはステージング CA と 1 つのホスト名から始めてください.
このテンプレートは 1 つの組織のサブスクリプションでの staging デプロイでは端から端まで
確認できていますが(同時実行時のリネームの原子性や SMB 上の flock など未確認の項目が残っています.新しい環境へのデプロイは,最初から最後まで見守ってください.
手順
- 署名鍵ペアを 2 組作る — 交換用共有の往復それぞれに 1 組.
各コマンドが表示するacme-conductor keygen --private job-signing.pem --public job-signing.pub acme-runner keygen --private result-signing.pem --public result-signing.pubpublicKey:の 1 行を,次の手順の設定に貼り付けます. - Runner の設定を書く — deploy/examples/runner-config.aca.example.json を出発点にします.
lego.stateDirは/state,lego.workDirは/workのままにし,DNS バインディングはマネージド ID で認証するようにします(AZURE_AUTH_METHODは設定しません.passthroughEnvにAZURE_CLIENT_ID,IDENTITY_ENDPOINT,IDENTITY_HEADERを列挙).jobSigning.publicKeysは Bicep テンプレートが自動で追加するため,ここには書きません. main.bicepparamをコピーして値を埋める —deploy/azureディレクトリ内にコピーします(runnerConfigJsonのloadTextContentが相対パスで解決するため).イメージのダイジェスト,バインディング名,DNS ゾーン,Key Vault,公開鍵,OIDC の値を記入します.
cd deploy/azure cp main.bicepparam my.bicepparam- 秘密鍵を環境変数として export してデプロイする — 秘密鍵は
readEnvironmentVariableで読まれ,パラメータファイルには書きません.
イメージはダイジェストで固定して GHCR から直接 pull されるため,ローカルでのビルドや GHCR の資格情報は不要です.export ACME_JOB_SIGNING_PRIVATE_KEY_PEM="$(cat job-signing.pem)" export ACME_RESULT_SIGNING_PRIVATE_KEY_PEM="$(cat result-signing.pem)" az deployment group create --resource-group rg-acme \ --template-file main.bicep --parameters my.bicepparam - 最初の実行を確認する — Runner の設定はデプロイ時には検証されず,各実行の開始時に読み込まれます.待機中のジョブがなくても実行は毎分動き,
no pending job; nothing to doでSucceededになるのが正常です.
az containerapp job execution list -g rg-acme -n <prefix>-runner \ --query "[0:3].{name:name,status:properties.status}" -o table az containerapp job logs show -g rg-acme -n <prefix>-runner \ --execution <execution name> --container runner --format text - GUI のリダイレクト URI を登録する — デプロイ後に出力される
conductorGuiRedirectUri(https://<app fqdn>/ui/)を,OIDC プロバイダ側の GUI 用アプリ登録(シングルページアプリケーション)のリダイレクト URI に設定します.アプリの FQDN は初回デプロイ後にはじめて判明するため,これだけを後から登録すればよく,再デプロイは不要です.
実際に組織のサブスクリプションへ 1 つずつコマンドを実行した記録は deploy/azure/walkthrough.md にあります.命名や権限の確認,Entra ID のアプリ登録の作り方まで,順を追って書かれています. 生成 AI(Claude Code など)に作業を手伝わせたい場合は deploy/azure/ai-assisted-deploy.md に,何を AI に任せてよく何を人が判断すべきかがまとめられています.
デプロイ後の運用
| 項目 | 内容 |
|---|---|
| 到達方法 | API と GUI には conductorUrl(デプロイの出力)で到達します.HTTPS のみで,/api/ 配下のすべてのリクエストと GUI の操作には admin または viewer ロールを持つベアラートークンが必要です./healthz,/readyz,GUI の静的ファイルだけが認証不要です. |
| バックアップ(Conductor) | conductor-state 共有上の conductor.db です.共有のスナップショットを取るか,アプリをゼロにスケールした状態でファイルをコピーします.runner-state 共有(ACME アカウント鍵)もバックアップ対象です.exchange 共有には永続的なものはありません. |
| 復元 | プロセス(またはアプリ)を止めてファイルを戻します.新しいバイナリは起動時にスキーマを前方に移行しますが,古いバイナリは新しいスキーマのデータベースを拒否するため,ロールバックには対応するバックアップの復元が伴います.バックアップ時点で実行中だった run は次の起動時に「結果不明」として失敗扱いになります. |
| ロールバック | 以前のテンプレートとパラメータで az deployment group create を実行するか,以前のイメージダイジェストを設定し,対応する conductor.db も復元します. |
| 署名鍵のローテーション | まず検証側に新しい公開鍵を追加し(Runner の jobSigning.publicKeys,または Conductor の resultSigningPublicKey.どちらも複数保持できます),デプロイし,次に署名側を新しい秘密鍵に切り替え,最後に古い公開鍵を取り除きます. |
| 残留した run ディレクトリ | 実行の終了が確認できなかったとき,Conductor は exchange 共有上に claimed/run-<runId>/ を残し「run directory kept」とログに記録します.az containerapp job execution list で実行の終了を確認したうえで,手で削除します. |
イメージとリリース
バージョンタグを打つと,両方のイメージが SBOM と provenance を添えて
ghcr.io/cits-nue/acme-conductor と ghcr.io/cits-nue/acme-runner
に linux/amd64 と linux/arm64 向けに公開されます.固定する前に
リリースを検証し,そのダイジェストで固定して使います.
gh attestation verify oci://ghcr.io/cits-nue/acme-conductor:<version> --owner CITS-NUE
gh attestation verify oci://ghcr.io/cits-nue/acme-runner:<version> --owner CITS-NUE
docker buildx imagetools inspect ghcr.io/cits-nue/acme-conductor:<version>
latest タグも便宜上付きますが,デプロイが固定する対象にはしません.GHCR の公開
イメージにレジストリの資格情報は不要ですが,新しいパッケージ(名前を変えたイメージ,新しい
バイナリ)を資格情報なしで pull できるようにするには,組織のオーナーが一度だけ公開設定にする
必要があります.