既存のGCP Cloud DNSをCDKTFで管理したい
背景
- CloudDNSで会社ドメインのゾーンを作っている
- メールの認証やらSaasやらのドメイン認証系のレコードが雑多に存在してて管理しきれないのでIacしたい
ゴール
- CDKTFでDNSレコードの管理ができている状態
- メンバーがPRを投げてDNSレコードを変更できるような状態を目指す
とりあえずcdktfの初期化をする
cdktf init
言語はTypescriptを選択, デフォルトだとnpmベースなので諸々整えてpnpmへ移行
prebuildなプロバイダーを使う
"dependencies": {
"cdktf": "0.19.1",
"constructs": "10.3.0",
"@cdktf/provider-google": "12.0.3",
"@cdktf/provider-google-beta": "12.0.3"
},
GCPバックエンド用のバケットを作る
gcloud --project=*** storage buckets create gs://***** --location=asia-northeast1 --versioning
とりあえず、バックエンド向けの設定を書きつつストレージ作ってみる
import { GoogleProvider } from "@cdktf/provider-google/lib/provider"
import { StorageBucket } from "@cdktf/provider-google/lib/storage-bucket"
import { App, TerraformStack, GcsBackend } from "cdktf"
import { Construct } from "constructs"
class DNS extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id)
new GcsBackend(this, {
bucket: "terraform-state",
prefix: "terraform/state",
})
new GoogleProvider(this, "google", {
project: "*****",
})
new StorageBucket(this, "bucket", {
name: "****",
location: "asia-northeast1",
})
}
}
const app = new App()
new DNS(app, "dns")
app.synth()
pnpm cdktf plan
なんかいい感じにバックエンドの初期化が行われつつDiffが出てくる。
デプロイしてみる
pnpm cdktf deploy
がちゃがちゃ出力されつつ作成GCSが作成された
new StorageBucket(this, "bucket", {
name: "****",
location: "asia-northeast1",
})
ここを削除してもっかい deploy
して消しとく
これを試してみようと思う。
おっかなすぎるので、バックアップはとっとく
gcloud dns record-sets export records.yaml --zone=terass***
generateConfigForImport
を使い。stateファイルには取り込まずにcdktfのconfigを吐かせてみる。適当にGCS作ってそいつをimportしてみる。tera-one-adsfa
ってのをコンソールから作成し、
StorageBucket.generateConfigForImport(
this,
"gcs-import-sample",
"tera-one-adsfa",
)
こんな感じで、定義する。
pnpm cdktf plan
するとつらつら出てくるが、
import { Construct } from "constructs"
/*
* Provider bindings are generated by running `cdktf get`.
* See https://cdk.tf/provider-generation for more details.
*/
import { StorageBucket } from "./.gen/providers/google/storage-bucket"
class MyConvertedCode extends Construct {
constructor(scope: Construct, name: string) {
super(scope, name)
/*The following providers are missing schema information and might need manual adjustments to synthesize correctly: google.
For a more precise conversion please use the --provider flag in convert.*/
new StorageBucket(this, "gcs-import-sample", {
default_event_based_hold: false,
enable_object_retention: false,
force_destroy: false,
labels: [{}],
location: "ASIA-NORTHEAST1",
name: "tera-one-adsfa",
project: "****",
public_access_prevention: "enforced",
requester_pays: false,
storage_class: "STANDARD",
timeouts: [
{
create: [null],
read: [null],
update: [null],
},
],
uniform_bucket_level_access: true,
})
}
}
それっぽいのが出てきた。
プロバイダー云々言われているが、下記のようにProvider指定しても出力は変わらなかった。
const provider = new GoogleProvider(this, "google", {
project: "***",
})
StorageBucket.generateConfigForImport(
this,
"gcs-import-sample",
"tera-one-adsfa",
provider,
)
prebuildのものではなくビルドすべきなのだろうか?分からないが多分こんなもんなんだろうと思う。
今回はこのまま進めてみる
おためしで、これをそのままつかって取り込んでみる。
上記コードをmain.tfのスタック中にいれ、importFrom
を StorageBucket
のインスタスに対してコールしてやるとよさそう。
Though at this point, your resource has not been imported. To import, first add the new generated configuration to your project, then remove the initial call of generateConfigForImport. Finally, follow the steps outlined in the section "How To Import" above. On apply, your resource will be imported, then becoming managed by CDKTF.
https://developer.hashicorp.com/terraform/cdktf/concepts/resources#importing-resources
new StorageBucket(this, "gcs-import-sample", {
location: "ASIA-NORTHEAST1",
name: "tera-one-adsfa",
project: "***",
uniformBucketLevelAccess: true,
}).importFrom("tera-one-adsfa")
いらないところを削除しつつ、main.tsに記述 & generateConfigForImport文を削除
この状態で plan
してみる
$ pnpm cdktf plan
...
tera-one-dns # google_storage_bucket.gcs-import-sample (gcs-import-sample) will be imported
resource "google_storage_bucket" "gcs-import-sample" {
default_event_based_hold = false
effective_labels = {}
enable_object_retention = false
force_destroy = false
id = "tera-one-adsfa"
labels = {}
location = "ASIA-NORTHEAST1"
name = "tera-one-adsfa"
project = "***"
public_access_prevention = "enforced"
requester_pays = false
self_link = "https://www.googleapis.com/storage/v1/b/tera-one-adsfa"
storage_class = "STANDARD"
terraform_labels = {}
uniform_bucket_level_access = true
url = "gs://tera-one-adsfa"
timeouts {}
}
Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
...
cdktf deploy(terraform apply)すれば上記の状態にterraform stateが更新されるようだ。
やってみる
$ pnpm cdktf deploy
...
tera-one-dns google_storage_bucket.gcs-import-sample (gcs-import-sample): Importing... [id=tera-one-adsfa]
google_storage_bucket.gcs-import-sample (gcs-import-sample): Import complete [id=tera-one-adsfa]
...
インポートされたっぽい。この状態で importFrom("tera-one-adsfa")
という記述を削除して、もう一度cdktf plan
してみる
[kosaka]% pnpm cdktf plan
tera-one-dns Initializing the backend...
tera-one-dns Initializing provider plugins...
tera-one-dns - Reusing previous version of hashicorp/google from the dependency lock file
tera-one-dns - Using previously-installed hashicorp/google v5.6.0
tera-one-dns Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
tera-one-dns google_storage_bucket.gcs-import-sample (gcs-import-sample): Refreshing state... [id=tera-one-adsfa]
tera-one-dns No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
良さそう。StorageBucket
の記述をしつつ、変更がないってことはstateに無事反映されたということだ
じゃあ、cloud dnsをやっていく
基本的な構成を調べる
dns_managed_zone, dns_record_set当たりを使えば良さそう。
dns_managed_zoneをインポートしてみる
DnsManagedZone.generateConfigForImports(this, ....)
$ pnpm cdktf plan
.....
new DnsManagedZone(this, "terass-dev", {
cloud_logging_config: [
{
enable_logging: false,
},
],
description: [null],
dns_name: "......",
....
StorageBucket
と同じ流れて、上記アウトプットを必要な部分だけ抜き出してmain.ts
に書いてimportFrom
する
new DnsManagedZone(this, "terass-dev", {
cloudLoggingConfig: {
enableLogging: false,
},
dnsName: "....",
name: "tera-one-dev",
visibility: "public",
}).importFrom("tera-one-dev")
これを今度はcdktf deploy
して、importFrom
を消してやる
$ pnpm cdktf deploy
.....
# importFrom を削除して
$ pnpm cdktf plan
....
tera-one-dns No changes. Your infrastructure matches the configuration.
ということで、zoneをterraform stateファイルとtypescriptのソース両方に取り込めた
record_setが問題だ。
importしようにも数が多い。リソースとしてそれぞれ定義する形になるため一つづつimportする必要がある。今回はとりあえず開発用のテストzoneなので数がないからいいとしても、本番どうしよう? コード生成 or 手動で書きつつ抜けの内容にテストを書いてみる or その他もっとイケてる方法...
バックアップのところでexportしたテキストファイルもあるし、数の多いAレコード, CNAME, TXTレコードあたりだけでも生成できると楽かもしれない
とりあえず、数の問題は忘れて一旦適当にimport してみるか
exportしたファイル読んで生成することにする
.importFrom付きとそうじゃないバージョンを出力できるようにして、import時の確認ができるように作ってみる
import fs from "fs"
import { parseAllDocuments } from "yaml"
const records = parseAllDocuments(
fs.readFileSync("./records.yaml").toString(),
).map((doc) => doc.toJS())
const genRecord = (
zone: string,
name: string,
type: string,
ttl: number,
rrdatas: string[],
) => {
return `
new DnsRecordSet(this, "${type}-${name
.split(".")
.filter((c) => c)
.join("-")}", {
managedZone: "${zone}",
name: "${name}",
rrdatas: [${rrdatas
.map((r) => (r.includes(" ") && type === "TXT" ? `"\\"${r}\\""` : `"${r}"`))
.join(",")}],
ttl: ${ttl},
type: "${type}",
}).importFrom("${zone}/${name}/${type}")`
}
for (const record of records) {
const name = record.name
const type = record.type
const ttl = record.ttl
const rrdatas = record.rrdatas
console.log(genRecord("tera-one-dev", name, type, ttl, rrdatas))
}
こんな感じか?
$ pnpm ts-node recordSetGen.ts
....
new DnsRecordSet(this, "A-****dev", {
managedZone: "tera-one-dev",
name: "import****",
rrdatas: ["192.168.0.1"],
ttl: 300,
type: "A",
}).importFrom("tera-one-dev/*****/A")
....
出力できたので、これをmain.ts
に突っ込んでplanしてみる。
$ pnpm cdktf plan
...
Plan: 7 to import, 0 to add, 0 to change, 0 to destroy.
...
ズラッと出ますが、結果レコードがimportされて0 changeなので実際のインフラとの差分はなさそうです。変更があると変更箇所と変更数が結果にでるが、今回はでてないので大丈夫そう。
インポートするぞ
$ pnpm cdktf deploy
...
Apply complete! Resources: 7 imported, 0 added, 0 changed, 0 destroyed.
...
大丈夫そう。importFrom
の記述を削除してplanしてみる
$ pnpm cdktf plan
...
tera-one-dns No changes. Your infrastructure matches the configuration.
...
レコードインポートできた。
main.tsに全部書いてくのは量も増えるし管理しにくいので分離できるようにしてみる
コンストラクタの中でthis
を引数で渡しつつ関数呼び出しすればおk