最近の砂場活動その18: GKEの設定をTerraformで行なう

前提

というわけで、モジュール分割とかあれこれイケてない部分があるけど、石を投げないでください...。

backendを指定する

Terraformは現在どこまで適用したか、手元のコードと差分はどこにあるかを計算するためにstateファイル(tfstate)というものをbackendに持っている。stateは手元に持っておいてもよいが、PC変えたりrmでミスって消したりすると悲しいので、クラウドに置いておくとよい。S3やDynamoDBも指定できるようだが、今はGCPでリソースをあれこれ作っているので、GCSをbackendに指定する。

このGCSは画面から手でポチポチ作りました。

この辺の情報を元に、main.tfに書いていきます。

provider "google" {
  credentials = file("~/.config/gcloud/terraform-my-project@my-project.json")
  project     = "my-project"
  region      = "us-west1"
  zone        = "us-west1-a"
}

terraform {
  backend "gcs" {
    bucket = "my-project-terrarom-state-file"
  }
}

Terraformを実行する

Terraformを実行する際にはGCPのAPIをあれこれ叩くことになるので、サービスアカウントを発行します。自分でやるなら何でもいいけど、チームでやるんだったら個人のアカウントじゃないほうがいいはず。

% gcloud iam service-accounts create terraform-my-project \
  display-name "Account for Terraform"
% gcloud projects add-iam-policy-binding my-project \
  --member serviceAccount:terraform-my-project@my-project.iam.gserviceaccount.com \
  --role roles/owner

Makefileで便利コマンドを用意する

Terraformのコマンドを毎回覚えきれないので、Makefileでよく叩くコマンドを用意しておきます。production環境やstaging環境など、いくつか環境がある場合には-var-file=で環境毎に切り替えれるようにしておくと便利だけど、お財布の関係で趣味プロジェクトには一つしか環境はないです。

REMOTE_BUCKET_NAME := my-project-terrarom-state-file

create_credentials:
    gcloud iam service-accounts keys create ~/.config/gcloud/terraform-my-project@my-project.json --iam-account=terraform-my-project@my-project.iam.gserviceaccount.com

init:
    terraform fmt --recursive
    terraform init -backend-config="bucket=$(REMOTE_BUCKET_NAME)" -reconfigure

plan: init
    terraform plan

apply: init
    terraform apply

destroy:
    terraform destroy

make planで「意図してないリソースの削除とかはなさそうね、よし!」と確認して、make apply。CFnと同じ感じですね。updateだと思っていたら、delete & createとかが時々あるので注意する。

GKEの設定をTerraformで行なう

実際にやりたかったのはここ。とはいえ、概ねここと同じことを書いてあげればよい。

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster

node_count = 1かつpreemptible = trueなのは「いつクラスタが落ちても許します」ということなので、あまりよろしくはない。安く済ませようと思うと、preemptibleな f1-microのマシンを3台並べるのがよさそうだが、機械学習のタスク的にメモリはある程度欲しいので、この構成にしている(バッチなので、こけてもまあ即死はしない)。

esource "google_container_cluster" "my-project" {
  name                     = "my-project"
  remove_default_node_pool = true
  initial_node_count       = 1
}

resource "google_container_node_pool" "primary_preemptible_nodes" {
  name       = "my-node-pool"
  cluster    = google_container_cluster.my-project.name
  node_count = 1
  node_config {
    oauth_scopes = [
      "https://www.googleapis.com/auth/devstorage.read_only",
      "https://www.googleapis.com/auth/logging.write",
      "https://www.googleapis.com/auth/monitoring",
      "https://www.googleapis.com/auth/service.management.readonly",
      "https://www.googleapis.com/auth/servicecontrol",
      "https://www.googleapis.com/auth/trace.append"
    ]
    machine_type    = "n1-standard-1"
    preemptible     = true
    service_account = "default"
  }
  timeouts {
    create = "30m"
    update = "20m"
  }
}

クラスタに紐付いているservice_account = "default"というのが非常に厄介(?)で、かなり強い権限を持っている。GKEのpodを通じてあれこれできすぎてしまってよくない。この辺をWorkload Identityを使ってもっと安全にしていきましょう、というのを次のエントリで書きます。