test3
cccccccccccc
test2
test
Webhook test.
bbbbbbbbbbbb
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の構成が固まったらまとめようと思います。
最小構成ではじめる GKE + Terraform
なぜ書いたか
現在はGKEの構成管理に Terraform Resource を直接記述しているのですが、公式の terraform-google-kubernetes-engine
への移行を検討しています。
そこで、検証のために最小構成からはじめて徐々にプロダクションレディなクラスタに近づけていく試みの第一歩にできたらと思っています。
tl;dr
良いからコードを見せろという方は以下のレポジトリを参照してください。
本編
動作環境
terraform
:v0.13.5
- modules: ソースコードを参照
とりあえず動かしてみる
作るものたち
- VPC
- 今回は説明を割愛
- GKE cluster
$ mkdir minimum_public_01 $ cd minimum_public_01
main.tf
を作成
// common locals { project_id = "<YOUR_PROJECT_ID>" 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" } module "vpc" { source = "terraform-google-modules/network/google" version = "~> 2.5" project_id = local.project_id network_name = local.network_name subnets = [ { subnet_name = local.subnet_name subnet_ip = "10.0.0.0/17" subnet_region = local.region }, ] 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" } ] } } module "gke" { source = "terraform-google-modules/kubernetes-engine/google" version = "v12.1.0" project_id = local.project_id name = "minimum-configuration-cluster" region = local.region network = module.vpc.network_name subnetwork = local.subnet_name ip_range_pods = local.ip_range_pods_name ip_range_services = local.ip_range_services_name }
required な変数以外ほとんど何も設定していないので特筆すべきことは少ないですが、GCPが推奨している Alias IP ranges を使用しています。 Alias IP ranges の主なメリットに関しては以下を参照してください。
これだけで terraform コマンドでデプロイできます。
$ terraform init ... $ terraform apply ...
かんたん!!
もう少し実用的に
もう少し実際の開発を想定して以下の変更を加えます。
- 環境(
dev
,prod
etc.)を分けられるように - GCS に state file を置くように
- クラスタ作成時に自動で作成される default node pool を自動で削除
ディレクトリ構成
上記の変更のためにディレクトリ構成は以下のようにしました。
. ├── README.md ├── create_tf_bucket.sh ├── dev │ └── main.tf ├── modules │ ├── gke.tf │ ├── network.tf │ └── variables.tf └── prod └── main.tf
変更内容
複数環境(GCPプロジェクト)にデプロイできるように module を利用します。
具体的には以下の3ファイルからなる module を作成
modules/gke.tf
module "gke" { source = "terraform-google-modules/kubernetes-engine/google" 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 = "minimum-public-cluster-${var.env}" 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 }
grant_registry_access
もちゃっかり有効化されていますが、これを無効にしてしまうとGKEクラスタがホスティングされているGCPプロジェクトの Container Registry からイメージをプルできなくなってしまいます。
remove_default_node_pool
を有効にして、自動で作成される default node pool を削除し、明示的に node pool を作成する方法が推奨されているのでそちらに合わせています。
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.0.0.0/17" subnet_region = local.region }, ] }
modules/variables.tf
// common variable "project_id" {} 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" }
最初の構成から内容自体は変わっていませんが、 ファイルを分割して、 project_id
, env
を変数化しました。
このモジュールを各環境のディレクトリから呼び出します。
dev/main.tf
terraform { backend "gcs" { # tfstate-${GCP_PROJECT_ID} bucket = "tfstate-<YOUR_PROJECT_ID>" } } module "public-cluster" { source = "../modules" env = "dev" project_id = "<YOUR_PROJECT_ID>" }
dev/main.tf
では module の呼び出しと、 state を格納するGCSバケットを指定しています。
<YOUR_PROJECT_ID>
は自分の GCPプロジェクトのIDに置き換えてください。
state を格納するGCSバケット自体は手動で作成する必要があるので、 create_tf_bucket.sh
を用意します(スクリプト化しておかないと忘れる)。
create_tf_bucket.sh
#!/bin/sh -e if [ $# -ne 1 ]; then cat << EOF Usage: ${0} <GCP_PROJECT_ID> EOF exit 1 fi readonly GCP_PROJECT=$1 gsutil mb -p "${GCP_PROJECT}" -c multi_regional -l asia "gs://tfstate-${GCP_PROJECT}/"
ファイルが揃ったので開発環境(想定のGCPプロジェクト)にデプロイしてみます。
$ ./create_tf_bucket.sh <YOUR_PROJECT_ID> $ cd dev $ terraform init ... $ terraform apply ...
これで terraform-google-kubernetes-engine
モジュールを使った最少設定のクラスタが出来上がりました。
terraform-google-kubernetes-engine
モジュールには他にもたくさんのパラメータがあるので、本番環境で GKE を運用するためにはどのような設定にすべきかをここから検討してけたらと思います。
CKAD を受けてきた。
昨日 Certified Kubernetes Application Developer (CKAD) を取得しました! n番煎じですが、やったことを振り返るという意味でもCKADの受験記を書いておきます。
背景
k8sはそこそこ触っていたのでコンセプトはざっくり理解していたおり、ググりながらなら典型的な操作はできる状態でした。
一方で、典型的な REST API や Batch Job ばかり作ってきたので、より網羅的に k8s を勉強するモチベーションの一つとして受けました。
試験までにやったこと
- Kubernetes完全ガイドをざっくり2周
- よくつかう Workload などは確認的な読み方
- 理解が甘い・実践で使ったことがないセクションを中心に
- dgkanatsios/CKAD-exercises を3周
- CKAD Practice Questions - DEV
- 1周した。
CKAD-exercises
ちゃんとやってればほぼ復習
- 1周した。
- Udemy の Kubernetes Certified Application Developer (CKAD) Training
Mock Exam - 1
のみ- これも
CKAD-exercises
ちゃんとやってればほぼ復習
意識したこと
- できるだけ
YAMLを生成 => kubectl apply
でリソースを作成するkubectl edit
したくなる部分もYAMLを取得 => 編集 => kubectl apply
- YAMLはできるだけ
kubectl
コマンドで生成する- 以下のようなコマンドを多用(ゲシュタルト崩壊した)
kubectl run nginx --image=nginx --restart=Never --dry-run=client -o yaml > pod.yaml
kubectl run bb --image=busybox --restart=Never --dry-run=client -o yaml -- /bin/sh -c 'sleep 3600' > pod.yaml
- コマンドで生成できないやつは参考になる公式Docのページをブックマークしておく
- ConfigMap, Secret を env, volume として参照するサンプル
- Declare Network Policy | Kubernetes
- Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
- Configure a Security Context for a Pod or Container | Kubernetes
- 以下のようなコマンドを多用(ゲシュタルト崩壊した)
↑を意識しつつあとはひたすら演習した。
結果
66% が合格ラインで僕のスコアは 80% でした。
感想
正直もう少し良いスコアが取れると思っていました。
Mock Exam
や CKAD-exercises
よりも fluentd などのミドルウェアに関する指定などが細かい問題があり、手間取ってしまったことが響いたと思われます…。
スコアは少し残念でしたが、全体を通して普段GKEがやってくれているログに関する操作や、PersistentVolumeなどのあまり使ってこなかったリソースに関して理解を深めることができたのは個人的に良かったです。
次は CKA かな!
Terraform で GCP のサービスアカウントを管理する
なぜ書いたか
Terraform Google Provider のIAM周りのリソースはたくさんある。
google_project_iam_policy
google_project_iam_binding
google_project_iam_member
google_service_account_iam_policy
google_service_account_iam_binding
google_service_account_iam_member
google_cloud_run_service_iam_policy
- ...
たくさんある。
「手動でぽちぽちサービスアカウントを作るのも嫌だけど、Terraform のIAMも怖いし…」という気持ちになったので、これを機に整理してみます。もし間違いなどがありましたら、指摘していただけるととても喜びます。
やりたいこと
Terraform で任意の権限を付与した GCP のサービスアカウントを作成(管理)したい。
tl;dr
google_service_account
でサービスアカウントを作成- 特定サービスの全リソースに対する権限を付与したい場合は
google_project_iam_member
を使う - 特定サービスの特定リソースに対しての権限を付与したい場合は
google_*_iam_member
を使う- e.g.
- Cloud Run:
google_cloud_run_service_iam_member
- Cloud Storage:
google_storage_bucket_iam_member
- Cloud Run:
- e.g.
特定サービス(の全リソース)に対する権限を付与したい
例として、 sample
というサービスアカウントに Cloud Run管理者(roles/run.admin)
の権限を付与します。
resource "google_service_account" "sample" { project = "<YOUR_PROJECT_ID>" account_id = "sample" display_name = "Sample Service Account" } resource "google_project_iam_member" "sample" { project = "<YOUR_PROJECT_ID>" role = "roles/run.admin" member = "serviceAccount:${google_service_account.sample.email}" }
想定通りのサービスアカウントができました。
このように 特定サービスの全リソースに対する権限を付与したい場合は google_project_iam_member
を使います。
特定サービスの特定リソースに対しての権限を付与したい
例として、 sample
というサービスアカウントに sample-bucket-kaito2
というバケットの管理者権限を付与します。
resource "google_storage_bucket" "sample" { project = "<YOUR_PROJECT_ID>" name = "sample-bucket-kaito2" } resource "google_service_account" "sample" { project = "<YOUR_PROJECT_ID>" account_id = "sample" display_name = "Sample Service Account" } resource "google_storage_bucket_iam_member" "sample" { bucket = google_storage_bucket.sample.name role = "roles/storage.admin" member = "serviceAccount:${google_service_account.sample.email}" }
コンソールから INFO PANEL を確認すると sample-bucket-kaito2
バケットのみに対して想定通りの権限が付与されています。
このようにサービス内の特定のリソースに対してのみ権限を付与したい場合は、 google_*_iam_member
を使用します。
今回は GCS のため、 google_storage_bucket_iam_member
でした。
まとめ
google_service_account
でサービスアカウントを作成- 特定サービスの全リソースに対する権限を付与したい場合はgoogle_project_iam_memberを使う
- 特定サービスの特定リソースに対しての権限を付与したい場合は
google_*_iam_member
を使う- e.g.
- Cloud Run:
google_cloud_run_service_iam_member
- Cloud Storage:
google_storage_bucket_iam_member
- Cloud Run:
- e.g.
おまけ
google_service_account_iam_member
がややこしい
google_service_account_iam_member
は 特定サービスの特定リソースに対しての権限を付与したい で説明した google_*_iam_member
に該当します。つまり、「特定のサービスアカウントに対しての権限を別のサービスアカウントに付与する」という目的で使います。ややこしいので注意してください。
なぜ *_iam_member
を使うのか
IAM policy for projects や IAM policy for Cloud Storage Bucketを見ると、権限を付与するためのリソースとして、
*_iam_policy
*_iam_binding
*_iam_member
のバリエーションがあります。
その中で *_iam_member
以外のリソースは "Authoritative" と記載されており、これらは明示的に設定していないものをApply時に削除するという動作をします。
実際にドキュメントにも以下のような記述があるため 、 "Non-Authoritative" な *_iam_member
を使うほうが安全です。
Authoritative. Sets the IAM policy for the project and replaces any existing policy already attached.
参考
Terraform(google provider) で Service Account に Role をバインドするときの罠 - Qiita