はじめに
オンプレ環境(自宅ラボ)からプライベート(VPN越しでクラスタはグローバルアクセスしない) GKE の設定を実施した
構成や内容など実施した内容を整理するため、まとめたものを記載する
構築には terraform を使用する
実施環境
前回 terraform で GCP 環境の構築を実施したので、今回は Kubernetes の実施環境を構築する
環境
自宅ラボ、個人契約GoogleCloud(ドメイン取得して組織構築済)、両環境がVPN接続済み (前回など参照)
全体概要は下の方の構成概要図
に俯瞰図記載
前提:構築済み項目
- 組織
- ホストプロジェクト
- 共有 VPC
- Cloud VPN
- Cloud DNS (Private Google Access)
- サービスプロジェクト(今回 GKE 作成環境)
- gcloud/terraform 実施環境
今回の実施内容
- Private GKE Cluster の構築
- GKE Master Node のある Google 管理のプロジェクトとの VPC Peering
- GKE Master Subnet のオンプレ(自宅ラボ)向けの経路広報
- VPC SC への Kubernetes 追加
- kubectl アクセス確認 / LB構築試験 / ip-masq-agent 設定
構成概要図
全体の構成概要は下記の通り
今回は、GKE に関係する範囲を実施する
GKE 設定概要
- ネットワークモード
- VPC ネイティブクラスタ
- プライベートクラスタ
- Public endpoint
- Private endpoint
- Master Authorized Network
- プライベート IP を許可する
- Node pool
- Node Image
- Container-Optimized OS (Default)
- Workload Identity
- 利用
- autoscaling
- 実施
- Node Image
-
Storage Class
- 今回特に触れない
- デフォルトを使用 [参照]
- 動的プロビジョニングされる
- ReadWriteMany, ReadOnlyMany に対応するには NFS などの準備が必要そう
-
GKE 通信制御は複数あり、上記に書いた項目を含む下記項目を実施
- Firewall
- Private Endpoint
- Master Authorized Network
- VPC Service Controls
Workload Identity
GKE の Pod で Google Cloud のサービスを利用するために Workload Identity を使用できる [ドキュメント]
- Google Cloud のサービス (API) を使用するには認証が必要
- Kubernetes サービス アカウントを構成して Google サービス アカウントとして使用する
- Kubernetes サービス アカウントとして実行される Pod は、Google Cloud APIs にアクセスする際に、Google サービス アカウントとして自動的に認証される
サービスアカウントは下記となる
serviceAccount:PROJECT_ID.svc.id.goog[K8S_NAMESPACE/KSA_NAME]
- PROJECT_ID.svc.id.goog : クラスタで設定された Workload Identity プール
- KSA_NAME : リクエストを行っている Kubernetes サービス アカウントの名前
- K8S_NAMESPACE : Kubernetes サービス アカウントが定義されている Kubernetes 名前空間
Workload Identity 以外での認証 [参照]
- サービスアカウントキーをエクスポートして、Kubernetes の Secret として保存し使用する
- ノードのサービスアカウントを使用する。サービスアカウントはノードの全てのワークロードで共有される
細かく管理しないなら、ノードのサービスアカウントを使用するでもいい気もするが、
今回は Workload Identity を有効化してみた
実施内容
- Private GKE Cluster 構築
- 1. パラメータ(var)準備
- 2. クラスタ用サブネットの作成
- 3. クラスタ、ノードプールの作成
- 4. VPC Peering
- 5. VPC Service Controls (VPC SC) 設定
- 6. On-prem 向け経路広報追加
- kubectl アクセス
- Loadbalancer 構築
- Workload Identity
- IP マスカレード エージェント
Private GKE Cluster 構築
VPN で接続したプライベートの Op-prem 環境(自宅ラボ)からプライベートに利用できる GKE クラスタを構築する
1. パラメータ(var)準備
下記が今回使用するサブネット・パラメータ(var)
パラメータ | 内容 | 今回の環境でのパラメータ例 |
---|---|---|
master_ipv4 | マスターノード用のIP. /28 でアサイン | "172.18.2.0/28" |
master_authorized_subnets | マスターノードへのアクセスを許可するセグメント群 | private1 = "192.168.0.0/16", private2 = "172.16.0.0/12" |
primary_ipv4 | ワーカーノードの用のIP. 拡張できないため将来を含めた想定ワーカーノード数が収まるサイズでアサイン | "172.18.1.0/24" |
secondary_ipv4 | Pod と Service のサブネットをアサイン. 拡張できないため将来を含めた想定 Pod, Service数が収まるサイズでアサイン | pod = "10.2.0.0/20", service = "10.2.16.0/20" |
loadbalancer_ipv4 | LB 用のサブネットをアサイン。 LB 作成時に annotation で指定可能 | lb1 = "172.18.3.0/24" |
variable "service1_gke" {
type = object({
master_ipv4 = string
master_authorized_subnets = map(string)
primary_ipv4 = string
secondary_ipv4 = map(string)
loadbalancer_ipv4 = map(string)
})
default = {
master_ipv4 = "172.18.2.0/28"
master_authorized_subnets = {
private1 = "192.168.0.0/16",
private2 = "172.16.0.0/12"
}
primary_ipv4 = "172.18.1.0/24"
secondary_ipv4 = {
pod = "10.2.0.0/20",
service = "10.2.16.0/20"
}
loadbalancer_ipv4 = {
lb1 = "172.18.3.0/24"
}
}
}
その他、使用している terraform パラメータ
google_project.host_project: 構築済みのホストプロジェクト
google_compute_network.host_sharedvpc: 構築済みのホストの共有VPC
google_project.service1: 構築済みのサービスプロジェクト
var.service1_pj.service_name: 構築済みのサービスプロジェクト名
2. クラスタ用サブネットの作成
GKE クラスタには1つのサブネットと2つのセカンダリサブネットが必要になる
セカンダリサブネットは、Pod 用と Service 用のサブネットになり、これは拡張できないため十分なサイズで準備する必要がある
resource "google_compute_subnetwork" "service1-gke-subnet" {
name = "gke-${var.service1_pj.service_name}-primary"
project = google_project.host_project.name
network = google_compute_network.host_sharedvpc.id
ip_cidr_range = var.service1_gke.primary_ipv4
region = "us-west1"
private_ip_google_access = true
dynamic "secondary_ip_range" {
for_each = var.service1_gke.secondary_ipv4
content {
range_name = "${secondary_ip_range.key}-secondary"
ip_cidr_range = secondary_ip_range.value
}
}
}
共有 VPC で GKE を構築する場合は、IAM の権限付与が複数必要になる
ドキュメントを参照して、下記の通り付与する
# 共有 VPC で GKE を作成する際に必要な権限のアサイン
## https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc
resource "google_compute_subnetwork_iam_binding" "service1-gke-subnet" {
project = google_compute_subnetwork.service1-gke-subnet.project
region = google_compute_subnetwork.service1-gke-subnet.region
subnetwork = google_compute_subnetwork.service1-gke-subnet.name
role = "roles/compute.networkUser"
members = [
+ join(":", ["serviceAccount", "${google_project.service1.number}@cloudservices.gserviceaccount.com"]),
+ join(":", ["serviceAccount", "service-${google_project.service1.number}@container-engine-robot.iam.gserviceaccount.com"]),
]
}
# 共有 VPC で GKE を作成する際に必要な権限のアサイン
## https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc
resource "google_project_iam_binding" "host_hostServiceAgentUser" {
project = google_project.host_project.id
role = "roles/container.hostServiceAgentUser"
members = [
+ join(":", ["serviceAccount", "service-${google_project.service1.number}@container-engine-robot.iam.gserviceaccount.com"]),
]
}
3. クラスタ、ノードプールの作成
クラスタ・ノードプールの作成を実施する
下記、使用する terraform リソース
resource | 内容 |
---|---|
google_container_cluster | GKE クラスタ設定 |
google_container_node_pool | GKE クラスタのノードプール設定 |
下記、パラメータで特記箇所
argument | 概要 | 内容 | 今回のパラメータ |
---|---|---|---|
initial_node_count | GKE クラスタのノード数指定 | The initial number of nodes for the pool. In regional or multi-zonal clusters, this is the number of nodes per zone.1 とすると、us-west1-a , us-west1-b ,us-west1-c , があるので 3 ノードとなるdefault node pool を使用せずに node_pool を作成して指定する場合は "1" にする必要がある[参照] |
1 |
release_channel | リリースチャネルを指定 |
Channel の種類 Rapid : アップストリーム オープンソースの一般提供から数週間 Regular (default) : Rapid でのリリースから 2~3 か月後 Stable : Regular でのリリースから 2~3 か月後 |
"STABLE" |
remove_default_node_pool | デフォルトノードプールの削除有無 | ノードプールを作成する場合は true にする | true |
ip_allocation_policy | VPC Native クラスタ設定 | この設定をすると VPC Native クラスタの設定となる subnetwork(ノードサブネット)のセカンダリアドレスの中から Pod の IP となる cluster_secondary_range_nameと Serviceの IP となる services_secondary_range_name を指定する |
google_compute_subnetwork.service1-gke-subnet.secondary_ip_range[0].range_name, google_compute_subnetwork.service1-gke-subnet.secondary_ip_range[1].range_name |
private_cluster_config | プライベートクラスタの設定 | 3つのパラメータを設定する enable_private_nodes: プライベートクラスタの有効化 enable_private_endpoint: プライベートエンドポイントを有効化して、パブリックエンドポイントを無効化する master_ipv4_cidr_block: グーグル VPC に構築されるプライベートクラスタのマスターノードのサブネットの IP を /28 でアサインして指定する |
enable_private_endpoint = "true" enable_private_nodes = "true" master_ipv4_cidr_block = [マスターノードの IP サブネット/28] |
master_authorized_networks_config | マスターノードへのアクセス許可 IP 設定 | kubectl などでマスターへの API アクセスを許可する IP を書く | オンプレ・GCP 内で利用する Private サブネット |
workload_identity_config | WorkloadIdentity を設定 | WorkloadIdentity の設定。Namespace を設定する。現在プロジェクトの default namespace のみサポートらしい[参照] | identity_namespace = "${data.google_project.project.project_id}.svc.id.goog" |
node_count | ノード数の指定 | ゾーン数分作成される。今回は 3 ノード作成される | 1 |
autoscaling | オートスケール設定 | 最小ノード数の最大ノード数を指定できる | min_node_count = 1 max_node_count = 2 |
upgrade_settings | アップグレード設定 | max_surge: アップグレード時にノードプールに追加する最大ノード数 max_unavailable: アップグレード時に利用できなくなる最大ノード数 |
max_surge = 1 max_unavailable = 0 |
node_config | ワーカーノードのスペックを設定 | 使用したいノードのスペックをマシンタイプで指定する machine_type: GCE のマシンタイプ (デフォルト:"e2-medium") |
"e2-medium" |
resource "google_container_cluster" "servie1_gke" {
# provider = google-beta
# networking_mode = "VPC_NATIVE"
name = join("-", [var.service1_pj.service_name, "gke-cluster"])
location = var.gcp_common.region
project = google_project.service1.name
network = google_compute_network.host_sharedvpc.id
subnetwork = google_compute_subnetwork.service1-gke-subnet.id
release_channel {
channel = "STABLE"
}
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#initial_node_count
## google_container_node_pool objects with no default node pool, you'll need to set this to a value of at least 1, alongside setting remove_default_node_pool to true.
remove_default_node_pool = true
initial_node_count = 1
ip_allocation_policy {
cluster_secondary_range_name = google_compute_subnetwork.service1-gke-subnet.secondary_ip_range[0].range_name
services_secondary_range_name = google_compute_subnetwork.service1-gke-subnet.secondary_ip_range[1].range_name
}
private_cluster_config {
enable_private_endpoint = "true"
enable_private_nodes = "true"
master_ipv4_cidr_block = var.service1_gke.master_ipv4
}
master_authorized_networks_config {
dynamic "cidr_blocks" {
for_each = var.service1_gke.master_authorized_subnets
content {
cidr_block = cidr_blocks.value
display_name = cidr_blocks.key
}
}
}
workload_identity_config {
identity_namespace = "${google_project.service1.name}.svc.id.goog"
}
}
resource "google_container_node_pool" "servie1_gke_nodes" {
name = join("-", [var.service1_pj.service_name, "gke-node-pool"])
location = var.gcp_common.region
project = google_project.service1.name
cluster = google_container_cluster.servie1_gke.name
# ゾーンごとに作成されるノード数 (1だと zone で x3 で全体だと 3ノード)
node_count = 1
# Auto Scaling
autoscaling {
min_node_count = 1
max_node_count = 2
}
upgrade_settings {
max_surge = 1 # default
max_unavailable = 0 # default
}
node_config {
preemptible = true
machine_type = "e2-medium"
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform"
]
}
}
4. VPC Peering
マスターノードへの外部経路(On-premのアドレスなど)からアクセスを可能にするため、Google 側の VPC と Peering の設定を実施する
外部経路(custom_routes)をマスターノードの VPC へ経路交換をして疎通を可能にする
resource "google_compute_network_peering_routes_config" "service1_peering_gke_routes" {
peering = google_container_cluster.servie1_gke.private_cluster_config[0].peering_name
network = google_compute_network.host_sharedvpc.name
project = google_project.host_project.name
import_custom_routes = true
exportt_custom_routes = true
}
5. VPC Service Controls (VPC SC) 設定
VPC SC の Primiter (境界線) へ GKE を追加する(ついでに使用するであろう GCR なども追加している)
resource "google_access_context_manager_service_perimeters" "service-perimeter" {
# ~中略~
service_perimeters {
# ~中略~
status {
restricted_services = [
"storage.googleapis.com",
+ "container.googleapis.com", # GKE
+ "containerregistry.googleapis.com", # Container Registory
+ "artifactregistry.googleapis.com", # Artifact Registry
]
6. On-prem 向け経路広報追加
マスターノードへ割り当てたサブネットは、自動では BGP での外部経路広報がされない
On-prem 向け Cloud Router の BGP の advertised_ip_ranges 設定にカスタム経路としてマスターノードのサブネットを追加で広報設定する
resource "google_compute_router" "ha_vpn_router1" {
# ~中略~
bgp {
# ~中略~
advertise_mode = "CUSTOM"
advertised_groups = ["ALL_SUBNETS"]
dynamic "advertised_ip_ranges" {
for_each = {
privategoogle = "199.36.153.8/30"
restrictedgoogle = "199.36.153.4/30"
+ service1gkemaster = var.service1_gke.master_ipv4
以上で、Private GKE Cluster とオンプレから利用するための設定完了
kubectl アクセス
kubeconfig のエントリ作成は下記コマンドで実施可能
下記を実施すると、kubeconfig (.kube/config) が自動で追記され、コンテキストも指定したクラスタに切り替わる
gcloud --project=[service-project-name] container clusters get-credentials [cluster-name] --region=[GKE-cluster-region]
権限でエラーが出る場合は、gcloud のアカウントにroles/container.developer
の IAM ロールを追加する
アクセス試験を実施すると下記の通り
$ kubectl get node
NAME STATUS ROLES AGE VERSION
gke-service1-gke-c-service1-gke-n-5eec1cbc-tqpz Ready <none> 14m v1.18.17-gke.100
gke-service1-gke-c-service1-gke-n-dd60e720-54wh Ready <none> 14m v1.18.17-gke.100
gke-service1-gke-c-service1-gke-n-fdbc04bb-bqmm Ready <none> 14m v1.18.17-gke.100
Loadbalancer 構築
kind:Service
で type:Loadbalancer
を Google Cloud の内部ロードバランサで作成・指定したサブネットを使用したい場合は、
下記の 2 つの annotation を使用する
annotation | 内容 |
---|---|
networking.gke.io/load-balancer-type: "Internal" | 内部ロードバランサで構築する |
networking.gke.io/internal-load-balancer-subnet: "指定するサブネット名" | 指定したサブネットで VIP を作成する。指定しないとノードのサブネットと同じサブネット帯でアサインされる。ここではノードのキャパシティ設計と VIP が競合することも考え分離できる方法をとる |
※一部ドキュメントなどではcloud.google.com/load-balancer-type: "Internal"
表記があるが、これは GKE 1.7 以前の指定。日本語ドキュメントには記載が現状(2021.05.08)見当たらないが、英語ドキュメントに記載あり。変だなと思ったら英語版を見るあるある。
下記は、実施例。nginxコンテナを立てて、Loadbalancerを構築して外部(On-prem)から叩けることを確認する。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: ilb-service
annotations:
networking.gke.io/load-balancer-type: "Internal"
networking.gke.io/internal-load-balancer-subnet: "service1-gke-lb1-subnet"
labels:
app: nginx
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP
構築したtype:LoadBalancer
が指定サブネットからEXTERNAL-IP
がアサインされたことを確認する
※内部ロードバランサ作成などから少し時間がかかる
% kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ilb-service LoadBalancer 10.2.18.120 172.18.3.7 80:32039/TCP 11m
kubernetes ClusterIP 10.2.16.1 <none> 443/TCP 126m
アサインされたEXTERNAL-IP
へOn-prem(自宅ラボ)からcurl
で返答を確認する
% curl 172.18.3.7
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
疎通できない場合は、Firewall Rule で TCP/80
を ターゲット "${service-project-num}-compute@developer.gserviceaccount.com"
に対して許可する
※${service-project-num}
はgcloud projects list
のPROJECT_NUMBER
Workload Identity
Workload Identity の試験を実施する [参考]
パラメータは下記の通りで、namespace
とgoogle service account (google_sa)
と kubernetes service account(kubernetes_sa)
を指定する
variable "service1_gke_workloadidentity" {
type = object({
namespace = string
gsa_name = string
ksa_name = string
})
default = {
namespace = "default"
gsa_name = "workloadidentity-sa"
ksa_name = "workloadidentity-sa"
}
}
google service account を作成して、serviceAccount:PROJECT_ID.svc.id.goog[K8S_NAMESPACE/KSA_NAME]
をメンバーとして、roles/iam.workloadIdentityUser
を IAM Role バインディングする
resource "google_service_account" "service1_workloadidentity_sa_account" {
account_id = var.service1_gke_workloadidentity.gsa_name
display_name = "workload identity serviceAccount"
project = google_project.service1.name
}
resource "google_service_account_iam_binding" "service1_gke_workloadidentity" {
service_account_id = google_service_account.service1_workloadidentity_sa_account.name
role = "roles/iam.workloadIdentityUser"
members = [
"serviceAccount:${google_project.service1.name}.svc.id.goog[${var.service1_gke_workloadidentity.namespace}/${var.service1_gke_workloadidentity.ksa_name}]"
]
}
上記を terraform apply する
$ terraform apply
上記で出力されたアノテーションを最後の行に入れて、Kubernetes ServiceAccount を作成する
- KSA_NAME: Kubernetes の ServiceAcconut 名(今回は
workloadidentity-sa
) - GSA_NAME: Google Cloud の ServiceAcconut 名(今回は
workloadidentity-sa
) - GSA_PROJECT_ID: 実行環境のサービスプロジェクトのプロジェクト名
apiVersion: v1
kind: ServiceAccount
metadata:
name: KSA_NAME
namespace: default
annotations:
iam.gke.io/gcp-service-account: "GSA_NAME@GSA_PROJECT_ID.iam.gserviceaccount.com"
kubectl apply -f workloadidentity_sa.yaml
上記で、サービスアカウントの準備は完了
下記、準備したサービスアカウントを使用して ServiceAccount の紐付け確認をする
cloud-sdk 用のコンテナを起動して作成したサービスアカウント(KSA_NAME:ここではworkloadidentity-saで実施している)
kubectl run -it \
--image gcr.io/google.com/cloudsdktool/cloud-sdk:slim \
--serviceaccount KSA_NAME \
--namespace default \
workload-identity-test
コンテナ内でgcloud auth list
を実施すると、
指定した Google Cloud サービスアカウントが紐づいてコンテナが起動していることを確認できる
root@workload-identity-test:/# gcloud auth list
Credentialed Accounts
ACTIVE ACCOUNT
* workloadidentity-sa@suzuyu-service1-dev.iam.gserviceaccount.com
To set the active account, run:
$ gcloud config set account `ACCOUNT`
以上、Workload Identity の紐付け確認まで完了
IP マスカレード エージェント
Pod がクラスタ外へアクセスする際に Pod の CIDR が10.0.0.0/8
の場合、IPマスカレードが有効化されない (細かい条件)
Pod の CIDR を10.0.0.0/8
の中に含まれる場合は、IPマスカレードのdaemonSetとマスカレードしない宛先のConfigMapを作成する(ドキュメント)
作成は下記の通りで、ドキュメントを参照
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ip-masq-agent
namespace: kube-system
data:
config: |
nonMasquerageCIDRs:
- 10.0.0.0/8
resyncInterval: 60s
masqLinkLocal: true
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: ip-masq-agent
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: ip-masq-agent
template:
metadata:
labels:
k8s-app: ip-masq-agent
spec:
hostNetwork: true
containers:
- name: ip-masq-agent
image: k8s.gcr.io/networking/ip-masq-agent-amd64:v2.6.0
args:
- --masq-chain=IP-MASQ
# To non-masquerade reserved IP ranges by default, uncomment the line below.
# - --nomasq-all-reserved-ranges
securityContext:
privileged: true
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
# Note this ConfigMap must be created in the same namespace as the
# daemon pods - this spec uses kube-system
name: ip-masq-agent
optional: true
items:
# The daemon looks for its config in a YAML file at /etc/config/ip-masq-agent
- key: config
path: ip-masq-agent
tolerations:
- effect: NoSchedule
operator: Exists
- effect: NoExecute
operator: Exists
- key: "CriticalAddonsOnly"
operator: "Exists"
kubectl apply -f ip-masq-agent.yaml
DaemonSet で ip-masq-agent が worker-node 数分動作していることが確認できる
% kubectl get ds -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
ip-masq-agent 3 3 3 3 3 <none> 16m
おわりに
プライベート利用のための GKE クラスタの構築とその周辺設定を実施した
Autopilot も terraform で対応されたので、今後試したい
Private Service Connect が 2021.04 に GA されたので、プライベート接続についてはこちらを検討していきたい
Storage 動作なども今後試す予定 (RWXなどは別途対応が必要そう)
参考
Workload Identity の使用
https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity
限定公開クラスタ > クラスタ エンドポイントへのアクセス
https://cloud.google.com/kubernetes-engine/docs/concepts/private-cluster-concept?hl=ja#overview
kubectl 用のクラスタ アクセスの構成
https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl?hl=ja
内部 TCP / UDP ロードバランサの使用
https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing?hl=ja#service_parameters
IP マスカレード エージェントの使用
https://cloud.google.com/kubernetes-engine/docs/how-to/ip-masquerade-agent
- Terraform ドキュメント
- google_container_cluster
- google_container_node_pool