On-prem からプライベートに利用する Private GKE Cluster の作成

## はじめに
オンプレ環境(自宅ラボ)からプライベート(VPN越しでクラスタはグローバルアクセスしない) GKE の設定を実施した
構成や内容など実施した内容を整理するため、まとめたものを記載する
構築には terraform を使用する

## 実施環境
[前回 terraform で GCP 環境の構築](https://qiita.com/suzuyui/items/879dfa5d837a19c39a54)を実施したので、今回は Kubernetes の実施環境を構築する

### 環境
自宅ラボ、個人契約GoogleCloud(ドメイン取得して組織構築済)、両環境がVPN接続済み ([前回](https://qiita.com/suzuyui/items/879dfa5d837a19c39a54)など参照)
全体概要は下の方の`構成概要図`に俯瞰図記載

### 前提：構築済み項目
- 組織
- ホストプロジェクト
- 共有 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 に関係する範囲を実施する
![MyGCP_Overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/e7c2e27f-a304-da4c-4b51-89d4e24289ba.png)


## GKE 設定概要

- ネットワークモード
    - VPC ネイティブクラスタ
- プライベートクラスタ
    - Public endpoint
    - Private endpoint
    - Master Authorized Network
        - プライベート IP を許可する
- Node pool
    - Node Image
        - Container-Optimized OS (Default)
    - Workload Identity
        - 利用 
    - autoscaling
        - 実施
- Storage Class
    - 今回特に触れない
    - デフォルトを使用 [[参照](https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes?hl=ja#storageclasses)]
    - 動的プロビジョニングされる
    - ReadWriteMany, ReadOnlyMany に対応するには NFS などの準備が必要そう


- GKE 通信制御は複数あり、上記に書いた項目を含む下記項目を実施
    - Firewall
    - Private Endpoint 
    - Master Authorized Network
    - VPC Service Controls 


#### Workload Identity
GKE の Pod で Google Cloud のサービスを利用するために Workload Identity を使用できる [[ドキュメント](https://cloud.google.com/kubernetes-engine/docs/how-to/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 以外での認証 [[参照](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#alternatives)]

1. サービスアカウントキーをエクスポートして、Kubernetes の Secret として保存し使用する
2. ノードのサービスアカウントを使用する。サービスアカウントはノードの全てのワークロードで共有される

細かく管理しないなら、ノードのサービスアカウントを使用するでもいい気もするが、
今回は 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",<br>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",<br>service = "10.2.16.0/20" |
| loadbalancer_ipv4 | LB 用のサブネットをアサイン。 LB 作成時に annotation で指定可能| lb1 = "172.18.3.0/24" |

```terraform
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 クラスタには１つのサブネットと2つのセカンダリサブネットが必要になる
セカンダリサブネットは、Pod 用と Service 用のサブネットになり、これは拡張できないため十分なサイズで準備する必要がある

```terraform
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 の権限付与が複数必要になる
[ドキュメント](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-shared-vpc)を参照して、下記の通り付与する

```diff_terraform:作成したGKE用サブネットへのIAMの追加(roles/compute.networkUser)
# 共有 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"]),
  ]
}
```

```diff_terraform:ホストプロジェクトIAMの追加(roles/container.hostServiceAgentUser)
# 共有 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](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster) | GKE クラスタ設定 |
| [google_container_node_pool](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool) | GKE クラスタのノードプール設定 |

下記、パラメータで特記箇所


| argument | 概要 | 内容 | 今回のパラメータ |
|:--|:--|:--|:--|
| [initial_node_count](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#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.<br>`1` とすると、`us-west1-a`, `us-west1-b`,`us-west1-c`, があるので `3`ノードとなる<br>default node pool を使用せずに node_pool を作成して指定する場合は "1" にする必要がある[[参照](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#initial_node_count)] | 1 | 
| [release_channel](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#release_channel) | リリースチャネルを指定 | [Channel の種類](https://cloud.google.com/kubernetes-engine/docs/concepts/release-channels#channels) <br> Rapid : アップストリーム オープンソースの一般提供から数週間 <br>Regular (default) : Rapid でのリリースから 2～3 か月後 <br> Stable : Regular でのリリースから 2～3 か月後 | "STABLE"| 
| [remove_default_node_pool](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#remove_default_node_pool) | デフォルトノードプールの削除有無 | ノードプールを作成する場合は true にする | true |
| [ip_allocation_policy](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#ip_allocation_policy) | VPC Native クラスタ設定 | この設定をすると VPC Native クラスタの設定となる<br>subnetwork(ノードサブネット)のセカンダリアドレスの中から<br>Pod の IP となる [cluster_secondary_range_name](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#cluster_secondary_range_name)と<br> Serviceの IP となる [services_secondary_range_name](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#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](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#private_cluster_config) | プライベートクラスタの設定 | 3つのパラメータを設定する <br>enable_private_nodes: プライベートクラスタの有効化 <br>enable_private_endpoint: プライベートエンドポイントを有効化して、パブリックエンドポイントを無効化する <br>master_ipv4_cidr_block: グーグル VPC に構築されるプライベートクラスタのマスターノードのサブネットの IP を /28 でアサインして指定する | enable_private_endpoint = "true" <br> enable_private_nodes    = "true" <br> master_ipv4_cidr_block  = [マスターノードの IP サブネット/28]|
| [master_authorized_networks_config](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#master_authorized_networks_config) | マスターノードへのアクセス許可 IP 設定 | kubectl などでマスターへの API アクセスを許可する IP を書く | オンプレ・GCP 内で利用する Private サブネット |
| [workload_identity_config](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#master_authorized_networks_config) | WorkloadIdentity を設定 | WorkloadIdentity の設定。Namespace を設定する。現在プロジェクトの default namespace のみサポートらしい[[参照](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#identity_namespace)] | identity_namespace = "${data.google_project.project.project_id}.svc.id.goog" |
| [node_count](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#node_count) | ノード数の指定 |  ゾーン数分作成される。今回は 3 ノード作成される | 1 |
| [autoscaling](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#autoscaling) | オートスケール設定 | 最小ノード数の最大ノード数を指定できる | min_node_count = 1 <br> max_node_count = 2|
| [upgrade_settings](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool#upgrade_settings) | アップグレード設定 | max_surge: アップグレード時にノードプールに追加する最大ノード数 <br>max_unavailable: アップグレード時に利用できなくなる最大ノード数 | max_surge       = 1 <br>max_unavailable = 0 |
| [node_config](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#node_config) | ワーカーノードのスペックを設定 | 使用したいノードのスペックをマシンタイプで指定する<br>machine_type: GCE のマシンタイプ<br>(デフォルト："e2-medium") | "e2-medium" |

```tf
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 へ経路交換をして疎通を可能にする

```tf:
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 なども追加している)

```diff_terraform
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 設定にカスタム経路としてマスターノードのサブネットを追加で広報設定する

```diff_terraform
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) が自動で追記され、コンテキストも指定したクラスタに切り替わる

```sh:cluster-nameは作成したクラスタの名称
gcloud --project=[service-project-name] container clusters get-credentials [cluster-name] --region=[GKE-cluster-region]
```

権限でエラーが出る場合は、gcloud のアカウントに`roles/container.developer`の IAM ロールを追加する

アクセス試験を実施すると下記の通り

```sh:アクセス試験(get_node)表示例
$ 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)から叩けることを確認する。

```yaml
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`がアサインされたことを確認する
※内部ロードバランサ作成などから少し時間がかかる

```sh
% 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`で返答を確認する

```sh
% 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 の試験を実施する [[参考](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#authenticating_to)]

パラメータは下記の通りで、`namespace`と`google service account (google_sa)` と `kubernetes service account(kubernetes_sa)`を指定する

```terraform
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 バインディングする

```terraform
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 する

```sh
$ terraform apply
```

上記で出力されたアノテーションを最後の行に入れて、**Kubernetes ServiceAccount** を作成する

- KSA_NAME: Kubernetes の ServiceAcconut 名(今回は`workloadidentity-sa`)
- GSA_NAME: Google Cloud の ServiceAcconut 名(今回は`workloadidentity-sa`)
- GSA_PROJECT_ID: 実行環境のサービスプロジェクトのプロジェクト名

```yaml:workloadidentity_sa.yaml
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"
```

```shell
kubectl apply -f workloadidentity_sa.yaml
```

上記で、サービスアカウントの準備は完了

下記、準備したサービスアカウントを使用して ServiceAccount の紐付け確認をする
cloud-sdk 用のコンテナを起動して作成したサービスアカウント(KSA_NAME:ここではworkloadidentity-saで実施している)

```shell
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 サービスアカウントが紐づいてコンテナが起動していることを確認できる

```shell
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マスカレードが有効化されない ([細かい条件](https://cloud.google.com/kubernetes-engine/docs/how-to/ip-masquerade-agent#when-ip-masq-included))
Pod の CIDR を`10.0.0.0/8`の中に含まれる場合は、IPマスカレードのdaemonSetとマスカレードしない宛先のConfigMapを作成する([ドキュメント](https://cloud.google.com/kubernetes-engine/docs/how-to/ip-masquerade-agent#specify-non-masq-cidrs))

作成は下記の通りで、[ドキュメント](https://cloud.google.com/kubernetes-engine/docs/how-to/ip-masquerade-agent#create_manual)を参照

```yaml:ip-masq-agent.yaml
---
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](https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-overview?hl=ja) も terraform で[対応](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster#enable_autopilot)されたので、今後試したい
[Private Service Connect](https://cloud.google.com/vpc/docs/private-service-connect?hl=ja) が 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
        - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_cluster
    - google_container_node_pool
        - https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/container_node_pool