既存のGCPインフラをCDKTFにImportするぞ
こんにちはこんにちはTERASSのこうさかです。
この記事はterraform Advent Calendar 2023の10日目の記事です。
構築済みのGCPインフラをCDKTFに取り込む機会があったので、実際に取り組んだ手順をまとめます。
背景
会社ドメインのDNSレコードをGCP Cloud DNSで管理しておりレコードが大量にあり、最早なにがどのレコードか分からない状態になっている。なのでこれをなんとかしてコメント追加やセグメント分けして分かりやすく管理できる状態にしたい。特にterraform管理しているわけではなくて、レコードの追加依頼をもらったら手動でポチポチ追加するというオペレーションで回してました。
ゴール
- DNSゾーン, DNSレコードをCDKTF配下で管理できるようになること
やりかた
config-driven import使い、コードの生成, コードへの取り込み, ステートへの取り込みという流れでやってみました。
-
generateConfigForImport
メソッドを各リソースに対して呼び(cdktf plan)CDKTFのコードを生成する - 生成されたコードを転記しコード上のCDKTFのスタックに追加
- 転記したリソースの
importFrom
メソッドを呼び(cdktf plan)、実インフラとの差分を確認する -
cdktf deploy
してステートファイルへ取り込み -
importFrom
メソッドをコードから削除
この手順でやると、最初からコード書かずにある程度生成にまかせてインポートすることができ楽でした。
参考:
やってみる
generateConfigForImport
メソッドを各リソースに対して呼び(cdktf plan)CDKTFのコードを生成する
1. 今回は DnSManagedZone
のインポートをやっていきます。
まず、GCPのManagedZone コード生成するためのコードをCDKTF配下のmain.ts
に書きます。
DnsManagedZone.generateConfigForImports(this, <resorce_id>)
上記のように欲しいリソースに対して generateConfigForImports
を呼び出すことでcdktf plan
した際に標準出力に実際のインフラのコードが出力されます。 resource_id
の指定はインポートしようとしているリソースごとに異なりますが、このように terraformのリファレンスにIdの形式が書いてあるのでそれに従って指定します。
cdktf plan
してみます。
$ cdktf plan
....
new DnsManagedZone(this, "terass-dns", {
cloud_logging_config: [
{
enable_logging: false,
},
],
description: [null],
dns_name: "*****",
....
})
....
がちゃがちゃ長いログが出ますが、上記のようにTypeScriptのコードが出力されます。
2. 生成されたコードを転記しコード上のCDKTFのスタックに追加
上記で出力されたソースに貼り付け修正してきます。
今回修正は
- 特に設定していない項目がデフォルト値入で出てくるのでそこの削除
- スネークケースをキャメルケースに変更
- terraform上でのidの変更
を行いました。特にデフォルト値の項目が大量に入っていると大変ややこしいので必要な項目に絞りコード上に反映します。また、プロパティがスネークケースになっておりTypeエラーが出るのでフィールド名変更を行いました。
importFrom
メソッドを呼び(cdktf plan)、実インフラとの差分を見つつ取り込む
3. 転記したリソースの転記して修正したコードに対し、下記のようにimportFrom
メソッドを叩きます。
new DnsRecordSnew DnsManagedZone(this, "terass-dev", {
cloudLoggingConfig: {
enableLogging: false,
},
dnsName: "....",
name: "terass-dns",
visibility: "public",
}).importFrom(<resourceId>)
この状態で cdktf plan
を叩きます。
$ cdktf plan
....
Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
....
planの結果 1 to import
で 0 change
なので既存のGCP側の設定と変更なく取り込めそうです。
cdktf deploy
してステートファイルへ取り込み
4. $ cdktf deploy
terass-dns *** (terass-dev): Importing... [id=<resource_id>]
*** (terass-dev): Import complete [id=<resource_id>]
叩くとterraformステートにとりこまれました。これでソースファイルとterraform ステート両方にとりこまれたことになります
importFrom
メソッドをコードから削除
5.importFrom
を書いたままだとdeploy
のたびに取り込むことになるので、削除してソースとステートが揃ってる状態にして取り込み完了です。
new DnsRecordSnew DnsManagedZone(this, "terass-dev", {
cloudLoggingConfig: {
enableLogging: false,
},
dnsName: "....",
name: "terass-dns",
visibility: "public",
})
DNS recordの取り込み
ここからはCDKTFのインポートの方法と直接関わりが無いですが、アプローチとしてはありだと思うので興味ない人は読み飛ばしてください。
DNSレコードも上記と同じ方法で取り込めるのですが、レコード一つ一つがリソースとして独立しているので、100レコードあれば100リソースを取り込む必要があります。手動で書いていくのは大変めんどくさいし労力がかかります。幸いrecordの定義自体はかなりシンプルなので、recordをエクスポートしてそこからコード生成することにしました。
レコードセット自体は
gcloud dns record-sets export records.yaml --zone=<zone-name>
// https://cloud.google.com/sdk/gcloud/reference/dns/record-sets/export
このようなコマンドで吐き出せます。yaml形式でゾーン内のレコードを全て吐き出せます。
このyamlをもとにcdktfのコードを吐き出して対応することにします。
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(<zone_name>, name, type, ttl, rrdatas))
}
上記のコードでyamlを読み取ってレコードのリソースを定義しました。
generateConfigForImports
のかわりにコード生成を用いました。生成するコードに importFrom
をつけておいてインポート用の記述として出力。 出力したコードを含んだ状態でcdktf plan
,cdktf deploy
して、その後 importFrom
分を削除した記述に差し替えて置くことでインポートしました。手作業でやるよりはだいぶ楽になりました。
もしかしたらterrform import
を用いて全てインポートした後コンバートする事もできるかもしれませんが、試してないです。もっといい方法があったら教えてほしい
所管
config-driven import
の仕組みを使い、部分的にソースとステートを同期することができました。
実インフラとステート, コードそれぞれの差分を確認しながら作業できるのである程度安心して作業できたと思います。特にimportFrom
メソッドは差分があればコードとインフラの違いが出力されるのでわかりやすかったです。config-driven import
素晴らしい!!
作業のログ
参考までに
Discussion