概要 #
GKEプライベートクラスタと踏み台を作った後、複数のアプリケーションをクラスタに同居させたくなる場面があります。リクエストをアプリごとに振り分けたい、という場面です。GKEのServiceだけではVPC内部からの経路が用意されません。Ingressを使わない場合、Podへの入り口をどう作るかが課題になります。
Internal HTTP LB(INTERNAL_MANAGED)は、GKEのPodをNetwork Endpoint Group(NEG)というバックエンドの形で直接受け取れます。Terraformが手動で作るNEGとは異なる点です。GKEのServiceにcloud.google.com/negというアノテーションを付けるだけです。GKEコントローラがNEGを自動的に作成します。
Terraformの基本操作と、前回のGKEプライベートクラスタ+踏み台構成は前提としています。
Session Affinity(Cookie)は本記事では扱いません。外部ALB向けの構成例がterraform-gcp-examplesの10-regional-alb-session-affinityにあります(Terraformコードのみ)。Ingress(GKEのL7 LBコントローラ)や外部公開も扱いません。
検証すること #
- GKEのServiceアノテーションから、Network Endpoint Group(NEG)が自動作成されること
- Internal HTTP LBで、共有VIPの3つのポート(:81/:82/:83)を、それぞれ異なるバックエンドへ振り分けられること
- 踏み台からVIPの3ポートへ実際に接続し、意図したバックエンドが返ること(LB作成の確認とは別)
前提環境 #
- Google Cloud CLI、Terraform、Docker
- Billingが有効な検証用Google Cloud Project
- GKE、Compute Engine、IAMを操作できるGoogleアカウント
- 検証時のバージョン:Terraform 1.14.3、google 7.46.0
今回の構成 #
GKEのServiceにcloud.google.com/negアノテーションを付けると、GKEコントローラはクラスタが配置されているゾーンごとにNEGを作成します。この構成では3ゾーンにノードプールを配置しているので、1つのServiceにつき3つのNEG(ゾーンごと1つ)が対応します。
flowchart TB
subgraph GCP["Google Cloud"]
subgraph Project["Project"]
subgraph Region["asia-northeast1"]
subgraph VPC["VPC"]
subgraph GkeSubnet["GKE Subnet"]
PodA["Pod(app-a)"]
PodB["Pod(app-b)"]
PodC["Pod(app-c)"]
end
Proxy["proxy-only Subnet
/23"]
VIP["共有VIP
:81 / :82 / :83"]
end
end
end
end
VIP -->|":81"| PodA
VIP -->|":82"| PodB
VIP -->|":83"| PodC
Proxy -.->|"Envoyプロキシ処理"| VIP
VIPは1つですが、ポートごとに異なるURL Map・Target HTTP Proxy・Backend Serviceを割り当てています。結果として、異なるバックエンドへ振り分けられます。
sequenceDiagram
participant Bastion as 踏み台
participant VIP as ILB VIP
participant PodA as Pod(app-a)
Bastion->>VIP: curl http://VIP:81/
VIP->>PodA: :81のBackend Serviceへ転送
PodA-->>VIP: "backend-a"
VIP-->>Bastion: "backend-a"
使用するTerraformコード #
GKEクラスタをリージョナル・3ゾーンにする #
NEGはゾーンごとに作られるため、前回のゾーナル・1ノード構成のままでは複数ゾーンのバックエンドを確認できません。今回はGKEクラスタをリージョナルにし、3ゾーンにゾーンごと1ノードずつ配置します。
resource "google_container_cluster" "primary" {
name = var.cluster_name
location = var.region # 前回はvar.zone(ゾーナル)
# ...
}
resource "google_container_node_pool" "primary" {
name = "${var.cluster_name}-np"
location = var.region
cluster = google_container_cluster.primary.name
node_count = 1 # ゾーンごとに適用される値
node_locations = var.gke_zones # 3ゾーンで合計3ノード
# ...
}
ノード数が前回の3倍になるため、コストも高くなります。確認後はすぐにdestroyする前提の構成です。
GKEのServiceからNEGを自動作成する #
apiVersion: v1
kind: Service
metadata:
name: app-a
namespace: default
annotations:
cloud.google.com/neg: '{"exposed_ports":{"8080":{"name":"neg-app-a"}}}'
spec:
type: ClusterIP
selector:
app: app-a
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP
app-b・app-cも同じ形で、名前とNEG名だけを変えて用意します。バックエンドのアプリにはhashicorp/http-echoを使い、それぞれbackend-a・backend-b・backend-cという固定文字列を返すだけにしています。
Internal HTTP LB(NEGを参照する側) #
resource "google_compute_region_backend_service" "bs_app_a" {
name = "${var.cluster_name}-bs-app-a"
region = var.region
load_balancing_scheme = "INTERNAL_MANAGED"
protocol = "HTTP"
health_checks = [google_compute_region_health_check.hc_app_a[0].id]
dynamic "backend" {
for_each = toset(var.gke_zones)
content {
group = "https://www.googleapis.com/compute/v1/projects/${var.project_id}/zones/${backend.key}/networkEndpointGroups/neg-app-a"
balancing_mode = "RATE"
max_rate_per_endpoint = 100
capacity_scaler = 1.0
}
}
}
NEGはTerraformが作るリソースではなく、GKEコントローラが作る側のリソースです。Backend Serviceは名前を組み立てたURL文字列でNEGを参照しているだけです。Terraformの計画時点ではNEGの実在を確認できません。存在しないNEGを参照したままapplyすると、Terraformの計画エラーではなく、実際のAPI呼び出し時に404で失敗します。
設定と実行(2段階apply) #
NEGはPodやServiceを作った後にしか存在しないため、applyを2段階に分けます。
cd Advanced-Examples/14-gke-bastion-ilb-multi-neg
cp terraform.tfvars.example terraform.tfvars
project_id = "YOUR_PROJECT_ID"
iap_member = "user:YOUR_ACCOUNT"
terraform init
terraform fmt -check
terraform validate
terraform plan
terraform apply
1段階目(enable_ilb=false、デフォルト)はVPC・踏み台・GKEクラスタ(3ゾーン)を作ります。25リソースです。
Apply complete! Resources: 25 added, 0 changed, 0 destroyed.
続けて、踏み台へapp-a/b/cのマニフェストを転送し、kubectl applyします。
for f in nginx-deployment.yaml ilb-app-a.yaml ilb-app-b.yaml ilb-app-c.yaml; do
gcloud compute scp "k8s/$f" \
"$(terraform output -raw bastion_name):/tmp/$f" \
--zone="$(terraform output -raw zone)" --tunnel-through-iap \
--project="$(terraform output -raw project_id)"
done
# 踏み台の中で実行
gcloud container clusters get-credentials "$(terraform output -raw cluster_name)" \
--region="$(terraform output -raw region)" --project="$(terraform output -raw project_id)"
kubectl apply -f /tmp/ilb-app-a.yaml -f /tmp/ilb-app-b.yaml -f /tmp/ilb-app-c.yaml
NEGの作成を1〜2分待ってから、ローカルで3ゾーン分できているか確認します。
gcloud compute network-endpoint-groups list --filter="name~neg-app"
NAME LOCATION ENDPOINT_TYPE SIZE
neg-app-a asia-northeast1-a GCE_VM_IP_PORT 1
neg-app-b asia-northeast1-a GCE_VM_IP_PORT 1
neg-app-c asia-northeast1-a GCE_VM_IP_PORT 1
neg-app-a asia-northeast1-b GCE_VM_IP_PORT 0
neg-app-b asia-northeast1-b GCE_VM_IP_PORT 0
neg-app-c asia-northeast1-b GCE_VM_IP_PORT 0
neg-app-a asia-northeast1-c GCE_VM_IP_PORT 0
neg-app-b asia-northeast1-c GCE_VM_IP_PORT 0
neg-app-c asia-northeast1-c GCE_VM_IP_PORT 0
Podはレプリカ数1のため実際には1ゾーンにしか配置されませんでしたが、NEG自体はクラスタが持つ3ゾーンすべてに作られていました。Pod未配置のゾーンはSIZE=0の空のNEGとして存在します。3ゾーン分そろったところで、2段階目のapplyへ進みます。
terraform apply -var="enable_ilb=true"
Apply complete! Resources: 19 added, 0 changed, 0 destroyed.
Outputs:
ilb_vip = "10.40.0.250"
踏み台からVIPの3ポートへ実際に接続できることを確認する #
ILBが作成できたことと、意図したバックエンドへ実際に振り分けられることは別です。
# 踏み台の中で実行
VIP=$(terraform output -raw ilb_vip)
curl -s "http://$VIP:81/"
curl -s "http://$VIP:82/"
curl -s "http://$VIP:83/"
backend-a
backend-b
backend-c
同じVIPの異なるポートに対して、それぞれ意図したバックエンドの応答が返ってきました。1つのVIPを複数ポートで使い分ける構成が、実際に機能していることを確認できました。
後片付け #
terraform destroy
Destroy complete! Resources: 44 destroyed.
3ゾーンのGKEクラスタとILBは利用中に料金が発生します。11〜13より単価が高い構成のため、検証後は必ずすぐにdestroyします。
まとめ #
- GKEのServiceに
cloud.google.com/negアノテーションを付けると、GKEコントローラがゾーンごとにNEGを自動作成する - NEGはTerraform管理外のリソースのため、Backend ServiceはURL文字列で参照する。存在しないNEGへの参照は、plan時ではなくapply時のAPI呼び出しで404になる
- NEGを使う構成では、Podが実際に配置されていないゾーンにも空のNEGが作られる
- 1つのVIPでも、ポートごとに異なるURL Map・Backend Serviceを割り当てれば、複数のバックエンドへ振り分けられる
- LB作成の確認とは別に、意図したポート・バックエンドの組み合わせで実際に応答が返ることまで確認できる
参考資料 #
- Google Cloud: Internal HTTP(S) Load Balancing overview
- Google Cloud: Standalone NEGs (GKE)
- Terraform Registry: google_compute_region_backend_service