はじめに
この記事は、Terraform連載2024の10記事目です。
みなさんこんにちは。TIG所属の大岩潤矢( @920OJ ) です。
本記事ではCloudflareで管理しているドメインのDNS設定やCloudflare Pages等のサービスの設定を、Terraform管理に移行した際の手順などを記載します。
背景
私は個人開発や自身のポートフォリオWebサイトの公開のために、 920oj.net
というドメインを所持しています。このドメインはGoogle Domainsで取得し、ネームサーバを変更してCloudflare上でDNSレコードを管理しています。
また、自身の ポートフォリオサイトWebサイト はCloudflare Pagesでホスティングしています。転送量無制限で月500ビルドまで無料というのはお財布に優しくありがたいです。
一方で、Webの管理画面からCloudflareでのドメイン管理や各種設定を変更する際、バージョン履歴を表示する機能がないため、一度変更してしまったものは元に戻すことができません。例えばDNS設定を更新する際、間違えた値に更新してしまった場合、元の値を記憶していない限りは戻せなくなってしまいます。バージョニング機能はあるものの、Enterpriseプランのみでしか使えないようです。
そこで利用したいと思い立ったのがTerraformです。CloudflareをTerrafromで構築し、そのコードをGit等で管理することにより、変更をバージョン管理できます。記事タイトルにある通り、「Infrastructure as Codeする」ということです。
一方でCloudflareはすでに利用中であるため、これまで手作業で実施してきた設定をTerraformへimportして管理することになります。
今回移行するものは、各ドメインのDNSレコードと、Cloudflare Pagesでホスティングしているプロジェクトの2つとします。
CloudflareをTerraformで管理するための前準備
早速、既存リソースをTerraformで管理するための手順を紹介します。まずは下準備として、以下を実施します。
- tfstate管理用のR2バケットを作成
- APIトークンの発行
- 環境変数の設定
Cloudflare R2へtfstate管理用のバケットを作成する
CloudflareにはR2というS3互換のストレージサービスがあり、tfstateはこのR2の中で管理する方針とします。R2でtfstateを管理する方法については、すでにこのテックブログで記事があるので、これを参考にします。
https://future-architect.github.io/articles/20231016a/
まずはCloudflareの管理画面にログインし、R2を選択→「Add R2 subscription to my account」を押下します。
「Create bucket」 を押下します。
バケット名を入力し、Locationは「Automatic」を選択します。最後に「Create bucket」を押下すれば、バケットが出来上がります。
APIトークンを発行する
CloudflareをTerraform管理、すなわちAPIで操作する場合、APIトークンの発行が必須です。
右上ユーザアイコンより「My Profile」を押下→左メニューからAPI Tokensを選び、「Create Token」を押下します。
Create Custom Tokenの「Get started」を押下します。
各種設定値を入力します。
- Token name: 任意の名前を入力
- Permissions: Terraform経由で操作するサービスを選び、それぞれEditの権限を指定する
- どのサービスで何の権限が必要かはドキュメントにまとまっているので参照のこと
- Account Resources: 自分が権限を持っているアカウント(メールアドレス)を選択可能。ここではAll accountsとしたが、複数のアカウントがある場合はここで絞っておくことが好ましい
- Zone Resources: アカウントの中のドメインを選択できる。ここではAll zonesとしたが、操作できるドメインを絞りたいときはここで指定する
- Client IP Address Filtering: 仮に操作されるIPアドレスが決まっている場合はここで指定する。何も入力しなければ、すべてのIPアドレスからアクセスを許容する
- すべて入力できたら「Continue to summary」を押下
設定内容が表示されるので、問題なければ「Create Token」を押下します。
APIトークンが表示されますが、このままではR2のAccess KeyおよびSecretが表示されないため、再度作り直します。このページでのコピーは不要です。
R2の管理ページを開き、右側メニューより「Manage R2 API Tokens」を選びます。
先ほど作成したトークンの「・・・」を押下し、「Roll」を選択。注意書きを読み、「Roll」を押下します。
「API Token」「Access Key ID」「Secret Access Key」「R2のエンドポイント」が表示されるので、これらをすべてコピーしておきましょう。
環境変数の設定
ここからは操作するPCでの作業となります。まずはターミナルを開き、環境変数をセットします。
先ほどコピーしたAPIトークン等認証情報を、環境変数としてセットします。セットするキーと値は以下のとおりです。
No. | 環境変数名 | 値 |
---|---|---|
1 | AWS_ACCOUNT_ID | CloudflareのアカウントID |
2 | AWS_ACCESS_KEY_ID | APIトークンで払い出したアクセスキーID |
3 | AWS_SECRET_ACCESS_KEY | APIトークンで払い出したシークレットアクセスキー |
4 | CLOUDFLARE_ACCOUNT_ID | CloudflareのアカウントID |
5 | CLOUDFLARE_API_TOKEN | アクセスキー、シークレットアクセスキーと共に払い出したトークン |
1、4については管理画面より確認できます。2、3、5については前項でコピーしたものをセットしましょう。
以下のようにシェルスクリプトにまとめて、 source set-env.sh
のコマンドで設定できるようにすると楽です。
export AWS_ACCOUNT_ID=xxxxxxxxxxxxxxx |
CloudflareをTerraform管理する
ここからが本題で、いよいよCloudflare上にあるリソースをTerraform管理にするため、インポート等の作業を実施していきます。
ディレクトリ構成
任意の場所にCloudflareのTerraform管理用のディレクトリを作成します。これをgit管理とし、その配下のディレクトリ構造・ファイル構成は以下の形とします。
. |
ドメイン(ゾーン)管理は domains/
配下で実施し、利用するドメインごとにフォルダを切り、それぞれでtfstateを分ける形とします。
Cloudflare Pagesはアカウントでグローバルに管理するため、 global/
配下で管理し、 pages
ディレクトリを切り、さらにプロジェクトごとにディレクトリを分ける形式とします。
各ディレクトリのセットアップ
Terraformのバージョン情報やプロパイダの設定、tfstateの配置場所等の初期設定に必要なファイルは、 setup.tf
にまとめて、各ディレクトリに配置します。
key
はディレクトリごとに分けておき、tfstateが分けられるようにします。自分は以下のようなルールで運用しています。
- ドメイン:
domains/ドメイン名.tfstate
- Pages:
global/pages/プロジェクト名.tfstate
- Workers:
global/workers/プロジェクト名.tfstate
terraform { |
backend の設定で、 skip_credentials_validation
と skip_requesting_account_id
、 skip_s3_checksum
の3つを true
にする必要があります。
ドメインのDNSレコードをimportする
Cloudflareのimportには、Terraform公式で用意されているimportコマンドを利用するほか、Cloudflareが独自に提供している cf-terraforming というツールを利用できます。
cf-terraformingについては、これまた伊藤さんが書かれている記事があるので、こちらも読んでみてください。
https://future-architect.github.io/articles/20230502a/
実際に現在の設定をimportしてみましょう。
generate
まずは現在の設定をTerraformの記述に落とし込んでくれる generate
コマンドを試します。
cf-terraforming generate --resource-type "cloudflare_record" --zone "ゾーンID" |
--resource-type
オプションで取得したいリソースを指定します。今回はDNS設定を取得してみるので、cloudflare_record
を指定します。- 取得できるリソース一覧はドキュメントに無かったので、ソースコードを参照します。
--zone
オプションで取得したいzoneのIDを指定します。
実行してみたところ、以下のエラーが出ました。
FATA[0000] --account and --zone are mutually exclusive, support for both is deprecated |
どうやら先程セットした環境変数 CLOUDFLARE_ACCOUNT_ID
がセットされていると正常に動いてくれなさそうなので、一旦 unset CLOUDFLARE_ACCOUNT_ID
コマンドで環境変数を外しておきます。
% cf-terraforming generate --resource-type "cloudflare_record" --zone "ゾーンID" |
問題なければ、先程のコマンドの末尾に >> record.tf
をつけてファイルに書き出しましょう。
このままだとリソース名がランダムなものになっているので、わかりやすいように名前を変えると管理しやすいです。
- 例: ルートドメインのCNAMEレコード:
cname_root
- 例:
hoge
という名前のAレコード:a_hoge
また、zone_idやルートドメイン名は何度か記述することになるので、local変数に定義しておくとミスが減ります。
locals { |
resource "cloudflare_record" "cname_root" { |
import
このままでは新規追加した分がそのまま新規として認識されてしまうので、すでに作成されているリソースについてはimportしてtfstateへ反映させる必要があります。
importするためのコマンドはcf-terraformingを利用して出力できます。ただし今回はリソース名をわかりやすく変更したため、コマンドを修正します。
まずはcf-terraformingを利用してコマンドを出力してみましょう。
% cf-terraforming import --resource-type "cloudflare_record" --zone "ゾーンID" |
出力されたコマンドをもとに、リソース名を変更した上で、シェルスクリプトファイルとして保存します。
terraform import cloudflare_record.terraform_managed_resource_cname_root ゾーンID/xxxxxxxxxx |
これを実行してみましょう。 Import successful! と表示されれば、インポート完了です。
% ./import.sh |
ここで terraform plan
を実行してみましょう。先ほどimportしたものが表示され、最後にNo chanegsと表示されれば、無事反映に成功しています。
% terraform plan |
Cloudflare Pagesのimport
続いてはCloudflare PagesをTerraform管理下となるよう設定します。
Pagesはドメイン(ゾーン)単位でなくアカウント単位での管理となるため、先ほどunsetした CLOUDFLARE_ACCOUNT_ID
を再セットします。
また、Cloudflare Pagesはcf-terraformingが対応していないため、Terraform v1.5から追加されたImportブロックを利用してインポートします。
importブロックの作成
自分の場合、 920oj.net
のドメインで、プロジェクト 920oj-net
を設定しています。これをインポートしてみましょう。
Cloudflare Pagesは、 cloudflare_pages_domain
リソースと cloudflarepages_project
リソースから構築されます。
まずは import.tf
を作成し、importブロックを記載します。 local.account_id
でアカウントIDが呼び出せるようにしています。
cloudflare_pages_domain
のインポートでは、to
にはimportする対象のリソース名を、 id
には <アカウントID>/<プロジェクト名>/<設定しているドメイン名>
を記載します。
cloudflare_pages_project
のインポートでは、 to
にはimportする対象のリソース名を、 id
には <アカウントID>/<プロジェクト名>
を記載します。
# cloudflare_pages_domain のインポート |
HCLコードの自動生成
この状態で terraform plan -generate-config-out=generate.tf
コマンドを実行します。
% terraform plan -generate-config-out=generate.tf |
設定値が読み取られ、出力されています。また、指定したファイル generate.tf
に同様の設定値が記載されています!
内容が正しいか確認するのと、コメントを消したり、local変数に置き換えたりして体裁を整えましょう。また、ファイルもリソースごとに分けておきましょう。
resource "cloudflare_pages_domain" "domain-920oj-net" { |
resource "cloudflare_pages_project" "project-920oj-net" { |
importの実行
コードの記載は済んだので、tfstateへ取り込みましょう。
先ほどのimportブロックは残したままで、 terraform plan
を実行します。 Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
が出ていれば、インポートの準備ができていることがわかります。
次に、 terraform apply
を実行します。差分がないことを確認して、yesとタイプしましょう。
Plan: 2 to import, 0 to add, 0 to change, 0 to destroy. |
Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.
が出たらOKです。
このあと terraform plan
を実行してみて、差分が出ていなければ問題なしです。
先ほどインポートに利用したimport.tfは削除しても構いません。
% terraform plan |
これにて、Cloudflareで管理しているドメインのDNSレコードとCloudflare Pagesのリソースを、Terraformにて管理できるようになりました!
おわりに
これでCloudflareを操作する際の不安が軽減できるようになり、自分の個人開発モチベも(わずかながら)高まった気がします。また、Terraformのエコシステムや本体の機能の充実さも改めて実感しました。
ぜひ皆さんもCloudflareをTerraform管理してみましょう!