基盤¶
Overview¶
基盤は、Cloudflare の zone、Workers の custom domain、Terraform の state を置く R2 のバケットを、Terraform のコードで持つ。
Worker script と静的アセットの deploy は Wrangler が持ち、この文書の対象に入らない。
全体の構成は Design Doc にある。
ADR-0005: Workers と Terraform と Wrangler の分担
Scope¶
持つものは次のとおり。
infra/bootstrap/とinfra/cloudflare/の分け方- R2 の state backend の設定
- deploy と apply の流れ
- ドキュメントサイトの Access を Terraform で持たないことと、custom domain を作る順序
持たないものは次のとおり。
- Terraform と Wrangler の管轄、deploy と apply の条件、credential の置き場。context/operations.md が持つ。
- 手元で apply する手順。infra/README.md が持つ。
.tfのファイル名とディレクトリの規則。context/codebase.md が持つ。
Design¶
deploy と apply の流れ¶
次の図は、Worker の deploy の流れと、運用者が手で起動する apply の流れが、別の credential で分かれていることを示す。
flowchart TD
dmanual["運用者が develop から手動で起動"] --> ci["deploy workflow"]
ci -->|"deploy 用の token"| wd["wrangler deploy"]
wd --> worker["Worker script + assets"]
check["develop の check workflow の成功"] --> cci["deploy-catalog workflow"]
cci -->|"deploy 用の token"| cwd["wrangler deploy(packages/ui)"]
cwd --> catalog["カタログの Worker + assets"]
check --> pci["deploy-playground workflow"]
pci -->|"deploy 用の token"| pwd["wrangler deploy(apps/playground)"]
pwd --> playground["プレイグラウンドの Worker + assets"]
check --> vci["deploy-preview workflow"]
vci -->|"deploy 用の token"| vwd["wrangler deploy --env preview(apps/web)"]
vwd --> preview["プレビューの Worker + assets"]
check --> dci["deploy-docs workflow"]
dci -->|"deploy 用の token"| dwd["wrangler deploy(apps/docs-site)"]
dwd --> docs["ドキュメントサイトの Worker + assets"]
manual["運用者が手動で起動(mode=plan、次に mode=apply)"] --> env["environment protection rules"]
env -->|"apply 用の token"| tf["terraform plan / apply"]
tf --> base["Zone / custom domain"]
ci -. "触らない" .-x base
cci -. "触らない" .-x base
pci -. "触らない" .-x base
vci -. "触らない" .-x base
dci -. "触らない" .-x base
Worker のコードの変更は、通常の pull request に含まれる。
deploy と apply を起動する条件と、apply を plan と apply の 2 回に分けて起動する手順は context/operations.md にある。
deploy の workflow は、apply の workflow と違い、environment の承認を挟まない。
変更を止める手段は、pull request のレビューと自動のテストに頼る。
develop には branch protection を掛けていないので、pull request を通すのは慣行であり、仕組みでは強制しない。
deploy 用の token は Workers Scripts の権限を持つので、custom domain の apex のレコードにも届きうる。deploy の workflow がそれを触らないのは、wrangler deploy の振る舞いによる。
どの Worker もハンドラを持たないので、誤った変更が及ぶ範囲は静的アセットの誤った配信に限られる。
Wrangler の設定ファイルは wrangler.jsonc にする。
Cloudflare は新しいプロジェクトに JSONC を勧めており、一部の新しい機能は JSON の形式でしか使えない。
bootstrap と本体¶
| ディレクトリ | 対象 | state の置き場 | 実行する人 |
|---|---|---|---|
infra/bootstrap/ |
state 用の R2 のバケット | 手元(commit しない) | 運用者が手元で 1 回 |
infra/cloudflare/ |
zone、Workers の custom domain | R2(infra/bootstrap/ が作る) |
apply の workflow か手元 |
infra/bootstrap/ を CI から apply しない。state を手元に置く前提が成り立たなくなる。
CI は infra:check で、fmt の確認、init(backend なし)、validate だけを実行する。
zone は Terraform で作らない。
Cloudflare Registrar がドメインを取得したときに作った zone を、zone.tf の import ブロックが、state にまだないときに取り込む。
cloudflare_zone には prevent_destroy を付ける。
cloudflare_workers_custom_domain が、サイトの apex、カタログの design、プレイグラウンドの play、ドキュメントサイトの docs のレコードを管理するので、DNS のレコードは書かない。
ドキュメントサイトの Access¶
docs.<zone> の Access のアプリケーションとポリシーは、Terraform で持たない。
運営者がダッシュボードで設定する。
ADR-0032: アクセスの認証
- Terraform は、custom domain の
docsだけを持つ。Terraform では、Access と custom domain を作る順序を制御しない。 - custom domain は、Access より先に作ってよい。Access を掛けるまでは、ドキュメントサイトを誰でも開ける。手順は apps/docs-site/README.md にある。
- Access は
docs.<zone>のホスト名にだけ掛かる。Worker のworkers_devとpreview_urlsをfalseにして、ほかの URL を閉じる。
state backend¶
R2 は S3 と互換だが、AWS ではない。
s3 backend には STS、IAM、metadata API を前提にする箇所があるので、その確認を切る。
backend "s3" {
key = "cloudflare/main/terraform.tfstate"
region = "auto"
skip_credentials_validation = true # STS がない
skip_region_validation = true # auto は AWS のリージョンの名前ではない
skip_requesting_account_id = true # IAM、STS、metadata API がない
skip_s3_checksum = true # R2 はチェックサムへの対応が限られる
use_path_style = true
use_lockfile = true # state のロックを S3 の側のオブジェクトで取る
}
bucket の名前と endpoint は account ID を含むので、commit しない。
bucket の名前は -backend-config で、endpoint は AWS_ENDPOINT_URL_S3 で渡す。
state の key に scope を含める(cloudflare/main/terraform.tfstate)。
後から兄弟の state を足すときに、既存の state の名前を変えずに済む。
バケットのバージョニングを設定しない理由と、state を失ったときの戻し方は context/operations.md にある。
Interface¶
置くファイル¶
infra/bootstrap/provider.tf、r2_state_bucket.tf、vars.tf: state 用のバケットinfra/cloudflare/provider.tf: provider と state backendinfra/cloudflare/zone.tf: zone の取り込みinfra/cloudflare/workers_domain.tf: Workers の custom domain。サイトの apex(site)、カタログのdesign.<zone>(catalog)、プレイグラウンドのplay.<zone>(playground)、ドキュメントサイトのdocs.<zone>(docs)infra/cloudflare/vars.tf: 変数。Worker の名前のworker_name、catalog_worker_name、playground_worker_name、docs_worker_nameを持つ。ほかにaccount_id、zone_id、zone_nameを持つ。apps/web/wrangler.jsonc: サイトの Worker の設定。env.previewに、プレビューの Worker(fukuemon-dev-preview)の設定を置く。プレビューの Worker は custom domain を持たず、workers.dev の URL で公開するpackages/ui/wrangler.jsonc: カタログの Worker の設定。workers_devとpreview_urlsを切り、design.<zone>だけで公開するapps/playground/wrangler.jsonc: プレイグラウンドの Worker の設定。workers_devとpreview_urlsを切り、play.<zone>だけで公開するapps/docs-site/wrangler.jsonc: ドキュメントサイトの Worker の設定。workers_devとpreview_urlsを切り、docs.<zone>だけで公開する
変数の値は、手元では各ディレクトリの terraform.tfvars に置き、commit しない。
CI では、apply の workflow が、TF_VAR_account_id、TF_VAR_zone_id、TF_VAR_zone_name を渡す。
値は secret と variable から取る。
worker_name、catalog_worker_name、playground_worker_name、docs_worker_name と、bootstrap の location はデフォルト値を持つ。
実行するコマンドとチェック¶
pnpm run infra:init、infra:plan、infra:apply:infra/cloudflare/を対象にするpnpm run infra:bootstrap:init、infra:bootstrap:apply:infra/bootstrap/を対象にするpnpm run infra:check:terraform fmt -checkとterraform validateを、backend を無効にして実行する。credential は要らないpnpm run check:worker-name: 4 つの Worker の名前が、wrangler.jsoncとvars.tfで一致するかを確認する。apps/web/wrangler.jsoncのnameとworker_nameのデフォルト値を比べるpackages/ui/wrangler.jsoncのnameとcatalog_worker_nameのデフォルト値を比べるapps/playground/wrangler.jsoncのnameとplayground_worker_nameのデフォルト値を比べるapps/docs-site/wrangler.jsoncのnameとdocs_worker_nameのデフォルト値を比べる- プレビューの Worker は custom domain を持たず、Terraform の変数に名前が無いので、比べない
pnpm run check:deploy: サイト、プレビュー、カタログ、プレイグラウンド、ドキュメントサイトのwrangler deploy --dry-runを実行する。プレビューは、サイトの build の出力に noindex を足した複製を読む。カタログはpnpm run build:storybookの出力を、プレイグラウンドはapps/playgroundの build の出力を、ドキュメントサイトはapps/docs-siteの build の出力を読むpnpm run check:closed-urls:apps/docs-site/scripts/check-closed-urls.shを実行する。ドキュメントサイトの Worker のworkers_devとpreview_urlsがfalseであることと、_headersのすべてのパスに noindex があることを確認する。wrangler.jsoncのほかに wrangler の設定ファイル(wrangler.json、.wrangler/deploy/config.json)が無いことと、routeとroutesが無いことも確認する。package.jsonのdeployが、引数の無いwrangler deployであることと、predeployとpostdeployが無いことも確認する。custom domain は Terraform で作る(ADR-0005)。deploy の workflow は apply を通らないので、Access の外の URL を開く変更を、この確認で止める。deploy-docs workflow も、配信の前にこの確認を実行する- check の workflow(
.github/workflows/check.yml)が、infra:checkと、上の 3 つを含むpnpm run checkを実行する - workflow は次の 6 つ
- apply:
.github/workflows/apply.yml - deploy:
.github/workflows/deploy.yml - deploy-catalog:
.github/workflows/deploy-catalog.yml - deploy-playground:
.github/workflows/deploy-playground.yml - deploy-preview:
.github/workflows/deploy-preview.yml - deploy-docs:
.github/workflows/deploy-docs.yml
- apply:
Verification¶
cloudflare_workers_custom_domainが、wrangler deployを済ませた Worker へ結び付くことを、初回の apply で確認した。https://fukuemon.devが Worker を配信した。skip_s3_checksumを含む上の backend の設定で、R2 への state の読み書きとuse_lockfileのロックが通ることを、同じ apply で確認した。- 初回の plan は、
cloudflare_zone.siteについて、account.idを sensitive として扱う差分だけを出した。apply した後に plan を実行すると、差分が残らなかった。 - context/operations.md の token の権限の表のとおりで、初回の deploy と初回の apply が通った。