Terraform で GKE private cluster をたてる
なぜ書いたか
GKE を本番運用しているなかで、検討しきれていない設定であったり構成を調査する試みの一環です。 最小構成ではじめる GKE + Terraform - Eng (なりたい) を先に読んでいただけるとより理解しやすいと思います。
tl;dr
良いからコードを見せろという方は以下のレポジトリを参照してください。
本編
概要
今回は以前作成した最小構成クラスタに以下の変更をしてみます。
examples/safer_cluster_iap_bastion を参考にしています。
- private cluster にする
- Node に External IP が付与されないようになる
- 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
まとめ
- private cluster にした
- Node に External IP が付与されないようになった
- 外部IPの料金を節約できる
- セキュリティ向上
- インターネットへアクセスするためには Cloud NAT が必要
- Node に External IP が付与されないようになった
- master authorized networks を設定する
- 指定されたネットワークからのみ Master にアクセスできるようになる
- セキュリティ向上
- IAP踏み台サーバーが必要
- 指定されたネットワークからのみ Master にアクセスできるようになる
より詳しい仕組みに関しては、もう少しGKEの構成が固まったらまとめようと思います。