nabeo がピーしているブログ (仮)

どーも、nabeop です

cdktf に入門した話

本記事ははてなエンジニア Advent Calendar 2020 の2日目の記事です。昨日は id:miki_bene さんのページのコンテンツから離れたタイミングのブラウザイベントの選び方 - ドキュメントを見たほうが早いでした

qiita.com

現職になってからクラウドプロバイダーは AWS が中心だったので、TypeScript で AWS CDK をつかっていました。最近になって Google Cloud にも興味が出てきたので、Terraform も選択肢として入ってくるようになりました。ただ、TypeScript でインフラを構成することになれていると HCL では表現力が足りないなーという課題感も感じていました。

そんなときに CDK の Terraform 版である cdktf の存在を知ったので、Terraform に入門するついでにお試しで cdktf にも入門してみました。

cdktf はこのエントリの執筆時点で v0.0.18 でまだまだ開発版という位置付けで正式版になったらいろいろと変わると思います。以下は v0.0.18 での知見です。

cdktf の始め方

HashiCorp のドキュメントに始め方があります。

learn.hashicorp.com

CDK と同じように TypeScript 向けのプロジェクトは以下のように初期化してテンプレートが生成されます。

cdktf init --template=typescript --local

cdktf init した直後のディレクトリは CDK と同じような感じです。

$ ls -a
./                 .gitignore         main.d.ts          node_modules/      tsconfig.json
../                cdktf.json         main.js            package-lock.json
.gen/              help               main.ts            package.json
$

唯一、 .gen というディレクトリだけが見慣れない感じだったので中身を確認したところ、初期状態では cdktf.jsonterraformProvidersaws が指定されている関係で Terraform の AWS プロバイダ向けと思われる ts が .gen/providers/aws 以下に配置されていました。

あと、help というファイルに cdktf での開発に必要なコマンドが書かれていて便利でした。

Google Cloud 向けに環境を整える

初期状態では AWS を想定されていますが、今回は Google Cloud を対象としたいので Terraform プロバイダを変更する必要があります。Terraform プロバイダ設定は cdktf.json で以下のように書き換えます。

{
  "language": "typescript",
  "app": "npm run --silent compile && node main.js",
  "terraformProviders": [
    "google@~> 3.0"
  ]
}

このままでは .gen 以下には AWS 向けのプロバイダが入っているので help にかいてあるとおり Google Cloud のプロバイダを導入します。

npm i -a @cdktf/provider-google
npm run get

cdktf のある生活

Google Cloud 向けのクレデンシャル情報を用意したりと Terraform / Google Cloud としてのお作法はありますが、だいたい AWS CDK と同じように開発ができます。

つまり、

  • ts ファイルで記述して
  • npm run compile して、ts ファイルから js ファイルと d.ts ファイルを生成して、
  • npm run diff で差分を確認して、
  • npm run deploy して適用する

という感じです。

npm run diffterraform plannpm run deployterraform apply に相当しています。

また、package.json の内容などを特に変更しなければ cdktf.out が Terraform としての working directory という扱いになっていそうです。

$ ls -a cdktf.out
./           ../          .terraform/  cdk.tf.json  plan
$

たとえば、cdktf.out/planterraform plan -out plan したときに生成されるファイルと同じ感じでした。つまり、cdktf.out ディレクトリに移動したら terrafom show plan で plan 時の情報を確認できたりします。cdktf.out/plan を使って差分を確認する様子は別エントリで書いています。

nabeop.hatenablog.com

Terraform プロバイダーとの対応関係

.gen/providers/google の中にプロバイダーのリソースやデータソースに対応するクラスが定義されています。

また、各クラスの実装はプロバイダーのスキーマから生成されており、以下のような対応関係になっているみたいです。

  • リソース
    • リソース名についているプロバイダーの文字列を削除して、アッパーキャメルケースに変換
  • データソース
    • データソース名に Data プレフィックスをつけて、アッパーキャメルケースに変換

つまり、

というような感じです。

snapshot テストしたい

せっかく TypeScript で IaC をしているので、テストも書いておきたいところです。AWS CDK を使っている時は snapshot テストだけは整えるようにして、バージョンアップによる不意の変更がわかるようにしています。cdktf でも同じようにしたいので調べてみたところ、cdktf のなかに Testing クラスが定義されていました

試しに jest 周りを整えて、main.test.ts を書いてみたら snapshot テストができました。

import { SandboxStack } from './main';
import { Testing } from 'cdktf';

describe ("sandbox-project", () => {
  test("snaphost test", () => {
    const app = Testing.app();
    const stack = new SandboxStack(app, 'test-stack', {
      credentials: '{}',
    });
    const results = Testing.synth(stack);
    expect(results).toMatchSnapshot();
  });
});

cdktf を触ってみた感想

まだまだ開発版なので足りていない機能はあるという前提ですが、以下のような感想です。

  • TypeScript の資産が活かせるので開発はすんなり始められそう
    • Terraform プロバイダーから半ば機械的に生成された ts ファイルをベースにしているので、HCL を書いている感はある
  • AWS CDK に慣れた身では cdktf diff の出力内容だけだと、どのような変更が行われるかわからず不安になる
    • 変更があるリソースはわかるけど、変更内容まではわからないというところが厳しかった

cdktf のレポジトリをみると活発に開発されていて、プロジェクトのロードマップもあるので正式版のリリースを気長に待とうかという気持ちです。

明日は id:dekokun さんです。

terraform で for_each でリソースを作りつつ、リソース内に Optional な引数を作りたい時

terraform の Google Cloud プロバイダーの google_compute_subnetwork リソースを定義している module で、

  • 1つのサブネットで1つ以上のセカンダリ IP レンジを持つことができる
  • Production 環境と Staging 環境など似た構成で複数のサブネットを作りたい
  • ただし、あるネットワークではセカンダリ IP レンジを持たないサブネットもある

というようなことが想定される場合はこんなふうに書いておくと良さそう。

# modules/foo/variables.tf
variable "subnets" {
  type = map(object({
    ip_cidr_range = string
  }))
}
variable "secondary_ip_ranges" {
  type = map(list(object({
    range_name    = string
    ip_cidr_range = string
  })))
  default = {}
}

サブネットそのもの情報と、サブネット内でオプショナルとなるセカンダリ IP レンジの情報を別の変数として定義しておく。ミソは map として作っているところ。また、secondary_ip_ranges は存在しない場合があるので default = {} として空の map をデフォルト値としている。

# moduels/foo/main.tf
resource "google_compute_subnetwork" "ubnetwork" {
  for_each = var.subnets

...

  ip_cidr_range = each.value.ip_cidr_range

  dynamic secondary_ip_range {
    for_each = contains(keys(var.secondary_ip_ranges), each.key) ? var.secondary_ip_ranges[each.key] : []
    content {
      range_name    = lookup(secondary_ip_range.value, "range_name", null)
      ip_cidr_range = lookup(secondary_ip_range.value, "ip_cidr_range", null)
    }
  }
}

dynamic secodary_ip_range の中で var.secondary_ip_ranges のキーと var.subnet のキーを突き合わせて存在する場合は secondary_ip_range を設定するようにしている。

実際に使う時は以下のようにする。

# main.tf
# セカンダリ IP レンジが存在するサブネット
module "hoge" {
  source = "./modules/foo"

...

  subnets = {
    "production" = {
      ip_cidr_range = "10.244.0.0/20"
    }
    "staging" = {
      ip_cidr_range = "10.244.16.0/20"
    }
  }

  secondary_ip_ranges = {
    "production" = [
      {
        range_name    = "subnet-01"
        ip_cidr_range = "10.244.192.0/20"
      }
      {
        range_name    = "subnet-02"
        ip_cidr_range = "10.244.208.0/20"
      }
    ]
    "staging" = [
      {
        range_name    = "subnet-01"
        ip_cidr_range = "10.244.224.0/20"
      }
      {
        range_name    = "subnet-02"
        ip_cidr_range = "10.244.240.0/20"
      }
    ]
  }
}

# セカンダリ IP レンジが存在しないサブネット
module "fuga" {
  source = "./modules/foo"

...

  subnets = {
    "production" = {
      ip_cidr_range = "10.245.0.0/20"
    }
    "staging" = {
      ip_cidr_range = "10.245.16.0/20"
    }
  }
} 

cdktf diff の内容を詳しく調べる

最近は Terraform を触っているんだけど、AWS-CDK に慣れた身としては HCL を直接書くのは厳しいので、cdktf を試している。cdktf とは AWS-CDK のように Terraform の HCL を Typescript などで記述できるツールです。

github.com

cdktf を使うにあたり厳しさを感じていたのが cdktf diff で変更されるリソースの内容がわかりにくいという問題があった。cdktf の使い方とかは別エントリで書こうと思っていたんだけど、terraform と cdktf を行き来するなかで現状における解決策を見つけたのでメモしておく。

まず、cdktf diff したら cdktf.out ディレクトリが作成される。このディレクトリは terraform init して terraform plan -out plan した内容が出力されている。cdktf.out の中身は以下のようになっている。

cdktf.out
├── .terraform
│   └── plugins
│       ├── registry.terraform.io
│       │   └── hashicorp
│       │       └── google
│       │           └── 3.42.0
│       │               └── darwin_amd64
│       └── selections.json
├── cdk.tf.json
└── plan

cdktf.out/cdk.tf.json は謎だけど、 cdktf.out/plan が今回の本丸。

cdktf.out ディレクトリを working ディレクトリとして、terraform show plan すると terraform plan したときの出力が得られる。

$ cd cdktf.out
$ terraform show plan

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy.
$

本当は AWS-CDK 並にいい感じに cdktf diff が出力して欲しいところだけど、正式リリース前なのでこんなもんでしょという感じではある。

Terraform で Google Beta API をつかう

2行でまとめると

  • プロバイダは googlegoogle-beta の両方を定義する
  • 必要な時だけ google-beta プロバイダをつかうと宣言する

という感じ。

モノによっては Google Beta API をつかう必要がある。Terraform の Google Cloud 向けプロバイダでは API の種別ごとに googlegoogle-beta というプロバイダが用意されている。googlegoogle-beta については Google Provider Versions - Terraform by HashiCorp で説明されている。

具体的な例は以下のとおり。

2つのプロバイダを定義する。

provider "google" {
  credentials = file("account.json")
  project     = "sample"
  region      = "asia-northeast1"
}

provider "google-beta" {
  credentials = file("account.json")
  project     = "sample"
  region      = "asia-northeast1"
}

shared-vpc-host-projectというプロジェクトの定義では google プロバイダを使用するが、shared-vpc-host-project を共有 VPC として有効にするときは google-beta プロバイダを利用する。

resource "google_project" "shared_vpc_host_project" {
  name       = "shared VPC Host Project"
  project_id = "shared-vpc-host-project"
}

resource "google_compute_shared_vpc_host_project" "shared_vpc_host_project" {
 # フォルダ単位でロールをつけているので beta API をつかう必要がある
  provider = google-beta
  project  = google_project.shared_vpc_host_project.project_id
}

gnupg の鍵サーバで hkps://hkps.pool.sks-keyservers.net を使いたい

homebrew で gnupg をインストールした直後だと以下のように失敗してしまう。

% gpg --keyserver hkps://hkps.pool.sks-keyservers.net --send-keys C08813444158EAD96E4D8226F6B09CB92E3DF175
gpg: sending key F6B09CB92E3DF175 to hkps://hkps.pool.sks-keyservers.net
gpg: keyserver send failed: General error
gpg: keyserver send failed: General error
[1]    53395 exit 2     gpg --send-keys C08813444158EAD96E4D8226F6B09CB92E3DF175
%

TLS 無しで試してみると成功する。

% gpg --keyserver hkp://hkps.pool.sks-keyservers.net --send-keys C08813444158EAD96E4D8226F6B09CB92E3DF175
gpg: sending key F6B09CB92E3DF175 to hkp://hkps.pool.sks-keyservers.net
%

サーバ証明書の検証で失敗しているぽい。こういうときはサーバ証明書を教えてあげる必要がある。

まずは、dirmngrhkps.pool.sks-keyservers.net で使っている証明書を明示させる。

homebrew で gnupg をインストールしたら $( brew --prefix )/share/gnupg/sks-keyservers.netCA.pemサーバ証明書が存在するはず。このファイルを dirmngr が鍵サーバの証明書として扱えるようにする。

具体的には ~/.gnupg/dirmngr.conf に以下を記述して、 gpgconf --reload dirmngr で設定の再読み込みをさせるだけで良い。

hkp-cacert /usr/local/share/gnupg/sks-keyservers.netCA.pem

あとは gpg --keyserver hkps://hkps.pool.sks-keyservers.net --send-keys C08813444158EAD96E4D8226F6B09CB92E3DF175 が成功することを確認したら、gnupg の設定ファイル(~/.gnupg/gpg.conf) で以下のように鍵サーバとして hkps://hkps.pool.sks-keyservers.net を指定する。

keyserver hkps://hkps.pool.sks-keyservers.net

GoBGP で受け取った経路を Quagga (zebra) でカーネル経路に落とし込む

vagrant で k8s クラスタを作った記録では以下のように書いていました。

gobgpd と quagga を使った BGP ルータの構築も https://github.com/nabeo/vagrant-k8s-metallb で構築していますが、ここでは詳しくは触れません。

ということで、今回は gobgpd で受けた BGP 経路を Quagga で kernel の経路表に落とし込んで別サブネットに存在しているクライアントが k8 で構築しているサービスにアクセスできるようにしている部分の解説をしようと思います。Quagga でも BGP を受けれることは承知していますが、GoBGP を使ってみたかったのでこんな構成にしています。

続きを読む