概要 #
GKEプライベートクラスタと踏み台の構成は、コントロールプレーンへのパブリックエンドポイントを完全に無効化していました。踏み台がないとkubectlが一切できません。開発初期の検証など、許可した自分のIPからは踏み台を経由せず直接繋ぎたい場面もあります。
GKEプライベートクラスタは、プライベートエンドポイントとパブリックエンドポイントを併用できます。パブリック側は許可IPだけに絞れるので、「VPC内は無条件、VPC外は限定IPのみ」という2段構えのアクセス制御になります。
Terraformの基本操作と、前々回のGKEプライベートクラスタ+踏み台構成は前提としています。
Cloud VPN・Interconnect経由のオンプレミス接続は扱いません。IAM/RBACの認可設計も扱わず、接続経路の確認に絞ります。
検証すること #
- ローカルPCから、踏み台を経由せずパブリックエンドポイント経由で直接
kubectlできること - 許可していないIPからは接続できないこと(apply成功とは別の確認)
- 踏み台からは、プライベートエンドポイント経由で引き続き接続できること
前提環境 #
- Google Cloud CLI、Terraform、Docker
- Billingが有効な検証用Google Cloud Project
- GKE、Compute Engineを操作できるGoogleアカウント
- 検証時のバージョン:Terraform 1.14.3、google 7.46.0
使用するTerraformコード #
一次情報での裏取り:enable_private_endpointの挙動
#
今回のコードは、別環境で検証したコードを土台にしています。ただし、そのコードはprivate_cluster_config.enable_private_endpoint = trueのままパブリックエンドポイントも使える、という前提で書かれていました。
terraform providers schema -jsonでプロバイダのフィールド説明を確認すると、次のように書かれています。
enable_private_endpoint -> When true, the cluster's private endpoint is
used as the cluster endpoint and access through the public endpoint is
disabled. When false, either endpoint can be used.
trueは「パブリックエンドポイント経由のアクセスを無効化する」フラグです。master_authorized_networksにどれだけパブリックIPを追加しても、enable_private_endpoint = trueのままではパブリックエンドポイント自体が無効なので到達できません。本記事のコードはfalseに修正してあります。
今回の構成 #
master_authorized_networks_configに、踏み台Subnetの範囲と管理者の公開IPの両方を許可リストとして渡します。
flowchart TB
subgraph GCP["Google Cloud"]
subgraph Project["Project"]
subgraph Region["asia-northeast1"]
subgraph VPC["VPC"]
Bastion["踏み台VM
(外部IPなし)"]
end
end
CP["GKEコントロールプレーン
Private Endpoint + Public Endpoint"]
end
end
Admin["管理者のローカルPC
(admin_public_cidr)"]
Bastion -->|"プライベートIP経由
(VPC内、無条件)"| CP
Admin -->|"パブリックIP経由
(許可IPのみ)"| CP
GKEクラスタの設定 #
resource "google_container_cluster" "primary" {
# ...
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false # 11はtrue(パブリック無効)
master_ipv4_cidr_block = var.master_ipv4_cidr_block
}
master_authorized_networks_config {
cidr_blocks {
cidr_block = var.bastion_subnet_cidr
display_name = "bastion-subnet"
}
cidr_blocks {
cidr_block = var.admin_public_cidr
display_name = "admin-public"
}
}
}
admin_public_cidrにはデフォルト値を設定していません。誤って緩いCIDRのままapplyしてしまう事故を避けるためです。
設定と実行 #
cd Advanced-Examples/15-gke-dual-endpoint
cp terraform.tfvars.example terraform.tfvars
curl -4 -s https://ifconfig.me # 表示されたIPを admin_public_cidr に /32 で設定
project_id = "YOUR_PROJECT_ID"
iap_member = "user:YOUR_ACCOUNT"
admin_public_cidr = "YOUR_PUBLIC_IP/32"
ifconfig.meは、IPv6が使える環境ではIPv6アドレスを返すことがあります。GKEの許可ネットワークはIPv4を想定しているため、-4を付けてIPv4を明示的に取得します(この違いに気づかず、実際にIPv6アドレスが返ってきて確認し直しました)。
terraform init
terraform fmt -check
terraform validate
terraform plan
terraform apply
Apply complete! Resources: 25 added, 0 changed, 0 destroyed.
Outputs:
private_endpoint = "172.16.4.2"
public_endpoint = "35.187.205.172"
ローカルPCから直接接続できることを確認する #
クラスタが作成できたことと、実際にパブリックエンドポイント経由で接続できることは別です。踏み台を経由せず、ローカルから直接確認します。
gcloud container clusters get-credentials "$(terraform output -raw cluster_name)" \
--zone="$(terraform output -raw zone)" --project="$(terraform output -raw project_id)"
kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-tf-adv-gke-duale-tf-adv-gke-duale-f1510c85-0f18 Ready <none> 23s v1.35.7-gke.1027000
踏み台なしで、ローカルから直接ノードの一覧が取得できました。
許可していないIPからは接続できないことを確認する #
admin_public_cidrが正しく機能しているかは、正しいIPで繋がることだけでは確認できません。ドキュメント用の予約アドレス(203.0.113.1/32、RFC 5737で予約された、実在の割り当て先が存在しないアドレス)を一時的に設定し、接続が拒否されることを確認します。
terraform apply -var="admin_public_cidr=203.0.113.1/32"
kubectl get nodes --request-timeout=15s
Unable to connect to the server: dial tcp 35.187.205.172:443: i/o timeout
タイムアウトしました。許可リストに存在しないIPからは、コネクション自体が確立できません。正しいIPへ戻すと、接続が復帰します。
terraform apply -var="admin_public_cidr=$(curl -4 -s https://ifconfig.me)/32"
kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-tf-adv-gke-duale-tf-adv-gke-duale-f1510c85-0f18 Ready <none> 2m2s v1.35.7-gke.1027000
master_authorized_networks_configの更新はクラスタやノードの再作成を伴わないため、数秒で反映されます。
踏み台からの接続には--internal-ipが必要
#
前々回の完全プライベート構成では、gcloud container clusters get-credentialsをそのまま実行すればプライベートエンドポイントに繋がりました。エンドポイントが1つしか存在しないため、選びようがなかったからです。
Dual Endpointにすると事情が変わります。enable_private_endpoint = falseのとき、get-credentialsはVPC内(踏み台)から実行してもパブリックエンドポイントのIPを書き込みます。
公式の選択順が外部IPを先に見るためです。
# 踏み台の中で実行(--internal-ipなし)
gcloud container clusters get-credentials "$(terraform output -raw cluster_name)" \
--zone="$(terraform output -raw zone)" --project="$(terraform output -raw project_id)"
kubectl get nodes
E0901 03:52:14.094629 memcache.go:265] "Unhandled Error" err="couldn't get current server API group list: Get \"https://35.187.205.172/api?timeout=32s\": dial tcp 35.187.205.172:443: i/o timeout"
踏み台のCloud NAT出口IPはadmin_public_cidrに含まれていないため、パブリックエンドポイント経由の接続はタイムアウトします。プライベートエンドポイントを明示的に使うには、--internal-ipが必要です。
gcloud container clusters get-credentials "$(terraform output -raw cluster_name)" \
--internal-ip --zone="$(terraform output -raw zone)" --project="$(terraform output -raw project_id)"
kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-tf-adv-gke-duale-tf-adv-gke-duale-f1510c85-0f18 Ready <none> 4m27s v1.35.7-gke.1027000
--internal-ipを付けて実行し直すと、プライベートエンドポイント経由で接続できました。同じ「踏み台からget-credentials」でも、パブリックエンドポイントが有効になったことで、明示的にプライベート側を選ぶ操作が必要になったことになります。
後片付け #
terraform destroy
Destroy complete! Resources: 25 destroyed.
GKE Clusterと踏み台VMは利用中に料金が発生します。検証後は必ずdestroyします。
まとめ #
enable_private_endpoint = trueは「パブリックエンドポイントを無効化する」フラグ。Dual Endpointにするにはfalseにするmaster_authorized_networks_configに踏み台Subnetと管理者の公開IPの両方を許可すると、VPC内外の両方から接続できる- 許可していないIPからの接続はタイムアウトする。正しいIPに戻すとクラスタの再作成なしに復帰する
- Dual Endpointにすると、VPC内から実行する
get-credentialsもデフォルトでパブリックエンドポイントを選ぶ。プライベートエンドポイントを使うには--internal-ipが必要