Eng (なりたい)

はやく エンジニア になりたい

Terraform で GKE private cluster をたてる

f:id:kaito2-2:20201202195532p:plain

なぜ書いたか

GKE を本番運用しているなかで、検討しきれていない設定であったり構成を調査する試みの一環です。 最小構成ではじめる GKE + Terraform - Eng (なりたい) を先に読んでいただけるとより理解しやすいと思います。

tl;dr

良いからコードを見せろという方は以下のレポジトリを参照してください。

github.com

本編

概要

今回は以前作成した最小構成クラスタに以下の変更をしてみます。

examples/safer_cluster_iap_bastion を参考にしています。

  1. private cluster にする
    • Node に External IP が付与されないようになる
  2. master authorized networks を設定する
    • 指定されたネットワークからのみ Master にアクセスできるようになる

実際に、本番運用するなら参考にしているのモジュールを使用することになるともいますが、理解のために自分で記述していきます。

変更内容

最終的なディレクトリ構成は以下です。

.
├── README.md
├── create_tf_bucket.sh
├── dev
│   └── main.tf
├── modules
│   ├── bastion.tf
│   ├── gke.tf
│   ├── network.tf
│   └── variables.tf
└── prod
    └── main.tf

modules/gke.tf

module "gke" {
  // Modified
  source  = "terraform-google-modules/kubernetes-engine/google//modules/private-cluster"
  version = "v12.1.0"

  grant_registry_access = true
  ip_range_pods         = local.ip_range_pods_name
  ip_range_services     = local.ip_range_services_name
  name                  = local.cluster_name
  network               = module.vpc.network_name
  node_pools = [
    {
      machine_type = "n2-standard-2"
      name         = "default-node-pool"
    }
  ]
  project_id               = var.project_id
  region                   = local.region
  remove_default_node_pool = true
  subnetwork               = local.subnet_name

  // Added
  enable_private_nodes = true
  master_authorized_networks = [{
    cidr_block   = "${module.bastion.ip_address}/32"
    display_name = "Bastion Host"
  }]
}

module は terraform-google-modules/kubernetes-engine/google//modules/private-cluster を指定します。 private node はベータ機能なので public-cluster module では有効化できないみたいです。

次に master_authorized_networks で後ほど作成する踏み台サーバーの IP アドレスを指定することで、踏み台からのみ Master にアクセス可能になります。

modules/network.tf

module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 2.5"

  network_name = local.network_name
  project_id   = var.project_id
  secondary_ranges = {
    (local.subnet_name) = [
      {
        range_name    = local.ip_range_pods_name
        ip_cidr_range = "10.1.0.0/17"
      },
      {
        range_name    = local.ip_range_services_name
        ip_cidr_range = "192.168.64.0/18"
      },
    ]
  }
  subnets = [
    {
      subnet_name   = local.subnet_name
      subnet_ip     = "10.2.0.0/17"
      subnet_region = local.region
    },
  ]
}

// Added
module "cloud-nat" {
  source  = "terraform-google-modules/cloud-nat/google"
  version = "~> 1.2"

  project_id    = var.project_id
  region        = local.region
  router        = "safer-router"
  network       = module.vpc.network_self_link
  create_router = true
}

Node に External IP が付与されなくなったので クラスタ内からインターネットへアクセスするためには Cloud NAT を使う必要あります。 そこで terraform-google-modules/cloud-nat/google module を定義しています。

bastion.tf

data "template_file" "startup_script" {
  template = <<-EOF
    sudo apt-get update -y
    sudo apt-get install -y tinyproxy
    EOF
}

module "bastion" {
  source  = "terraform-google-modules/bastion-host/google"
  version = "~> 2.0"

  host_project   = var.project_id
  image_family   = "debian-9"
  image_project  = "debian-cloud"
  machine_type   = "g1-small"
  members        = var.bastion_members
  name           = local.bastion_name
  network        = module.vpc.network_self_link
  project        = var.project_id
  shielded_vm    = "false"
  startup_script = data.template_file.startup_script.rendered
  subnet         = module.vpc.subnets_self_links[0]
  zone           = local.bastion_zone
}

GKE Mater へアクセスするための踏み台になる VM を定義しています。 terraform-google-modules/bastion-host/google module を使うことでかんたんに IAP を利用することができ、間接的に GKE Master へのアクセスを IAM ベースで制御できるようになります。特別な要件はないので各項目はほとんど変更していません。

便利なのですが、IAM ベースでのアクセスは踏み台をたてなくてもできるようになってほしい

variables.tf

// common
variable "project_id" {
  type = string
}
variable "env" {
  type = string
}
locals {
  region = "asia-northeast1"
}

// network
locals {
  network_name           = "sample-vpc"
  subnet_name            = "sample-subnet"
  ip_range_pods_name     = "ip-range-pods"
  ip_range_services_name = "ip-range-services"
}

// bastion
variable "bastion_members" {
  type = list(string)
}
locals {
  bastion_name = format("%s-bastion", local.cluster_name)
  bastion_zone = format("%s-b", local.region)
}

// gke
locals {
  cluster_name = format("minimum-private-cluster-%s", var.env)
}

最後にこれまでの変数をまとめたファイルを作成します。

デプロイ

複数環境を想定しているので dev/ ディレクトリを例にデプロイしていきます。

<YOUR_PROJECT_ID> を適宜 GCP プロジェクトに置き換えてください。

まずは state ファイルを格納しておく GCP バケットを作成します。

$ ./create_tf_bucket.sh <YOUR_PROJECT_ID>

次にここまで記述してきた module を呼び出すメインファイルを記述します。

dev/main.tf

<YOUR_EMAIL_ADDRESS> は自身のものに置き換えてください。

terraform {
  backend "gcs" {
    // tfstate-${GCP_PROJECT_ID}
    bucket = "tfstate-<YOUR_PROJECT_ID>"
  }
}

module "public-cluster" {
  source     = "../modules"
  env        = "dev"
  project_id = "<YOUR_PROJECT_ID>"
  bastion_members = [
    "user:<YOUR_EMAIL_ADDRESS>"
  ]
}

terraform コマンドでデプロイ

$ cd dev
$ terraform init
...
$ terraform apply
...

GKE Master への疎通を確認します。

$ export PROJECT_ID=<YOUR_PROJECT_ID>
$ export REGION=asia-northeast1

$ gcloud container clusters get-credentials \
    --project $PROJECT_ID --region $REGION \
    --internal-ip minimum-private-cluster-dev
$ gcloud beta compute ssh minimum-private-cluster-dev-bastion \
    --tunnel-through-iap --project $PROJECT_ID --zone $REGION-b \
    -- -L8888:127.0.0.1:8888
$ HTTPS_PROXY=localhost:8888 kubectl get pods --all-namespaces
...

Pod の一覧が表示されれば完成です。

ちなみに proxy なしだとアクセスにちゃんと失敗します。

$ kubectl get pods --all-namespaces
Unable to connect to the server: net/http: TLS handshake timeout

まとめ

  1. private cluster にした
    • Node に External IP が付与されないようになった
      • 外部IPの料金を節約できる
      • セキュリティ向上
    • インターネットへアクセスするためには Cloud NAT が必要
  2. master authorized networks を設定する
    • 指定されたネットワークからのみ Master にアクセスできるようになる
      • セキュリティ向上
    • IAP踏み台サーバーが必要

より詳しい仕組みに関しては、もう少しGKEの構成が固まったらまとめようと思います。