Google Cloudの環境構築をする際、Terraformのコードを作ると何度も作成・削除したり、同じ環境を複製したりと便利です。
一方、TerraformやGoogle Cloudに関する知識がないととっつきづらいかもしれません。
今回はGemini CLIを使ってTerraformコードを生成してみました。
結論として、とても有用だと思いましたが、人間によるレビュー・手直しの重要さも再認識しました。
私が試した内容を追体験できるように記事にまとめたので、参考になれば幸いです。
お試しした環境
Google CloudのCloudShellで試しました。
CloudShellには最初からGemini CLIがインストールされているのでセットアップは楽です。
今回はサンドボックス用Google Cloudプロジェクトにて、Owner権限を持つユーザで試しています。
本来はOwner権限など強い権限を持つユーザを作らないほうがセキュリティ上望ましいです。
理由は認証情報が流出した際にGoogle Cloudプロジェクトを自由にされてしまうためです。
最小権限での運用に加え、二要素認証や(可能なら)VPCSCのアクセス元制限を用いて、ご自身のGoogle Cloudプロジェクトが悪意のある第三者に利用されないように注意することをお勧めします。
Gemini CLIのセットアップ
Gemini CLIのお供
Gemini CLIが以下の二つを使える状態にすることで、Google CloudとHashicorpが推奨するコードに近づけたいと思います。
| No. | 分類 | 名称 | 説明 |
|---|---|---|---|
| 1 | MCP Server & Skills | Gemini Cloud Assist | Google Cloudが推奨する構成を設計・構築する能力を持ちます。Google Cloudが提供しています。 |
| 2 | Skills | Terraform Skills | Hashicorpが推奨するTerraformコードの作成・リファクタリングをする能力を持ちます。Hashicorpが提供しています。 |
2026/5/1時点でGemini Cloud Assistはプレビュー段階です。
Gemini Cloud Assistのセットアップ
以下のコマンドを実行してGemini CLIに拡張をインストールします。
gemini extensions install https://github.com/GoogleCloudPlatform/gemini-cloud-assist-mcp
続いて、~/.gemini/settings.jsonのMCP Serverの設定に以下の二つを追加します。
"mcpServers": {
"gemini_cloud_assist": {
"httpUrl": "https://geminicloudassist.googleapis.com/mcp",
"authProviderType": "google_credentials",
"oauth": {
"scopes": ["https://www.googleapis.com/auth/cloud-platform"]
},
"timeout": 600000
},
"application_design_center": {
"httpUrl": "https://designcenter.googleapis.com/mcp",
"authProviderType": "google_credentials",
"oauth": {
"scopes": ["https://www.googleapis.com/auth/cloud-platform"]
},
"timeout": 600000
}
}
Terraform Skillsのセットアップ
下記リポジトリのREADMEを参考に今回必要そうなskillを抜粋してインストールします。
# Code generation
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-style-guide
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-test
npx skills add hashicorp/agent-skills/terraform/code-generation/skills/terraform-search-import
# Module generation
npx skills add hashicorp/agent-skills/terraform/module-generation/skills/refactor-module
npx skills add hashicorp/agent-skills/terraform/module-generation/skills/terraform-stacks
おまけ:AGENT.md
こちらの記事の日本語版CLAUDE.mdの内容をAGENT.mdとして利用させていただきました。
構築する環境の説明
GKE Autopilot上でサンプルアプリを稼働させてみます。
セキュリティ上のリスクを低減するために、GKEはプライベートクラスタ(外部IPを持たないクラスタ)で構築し、内部LBをエンドポイントとします。
内部LBに対してリクエストするには、IAP経由でSSH接続するGCEを介してポートフォワードしてアクセスします。
これにより、IAPに接続する権限を持つアカウントしかアクセスできない環境を作ります。
初版のTerraformコードを生成する
Gemini CLIを起動し、以下のプロンプトを渡してみました。
Google Cloud上にプライベートなWebアプリケーションを構成するためのTerraformコードとGKEマニフェストファイルを作成してください。
IaCのapply以外に手順が必要になる場合は構築手順をMarkdown形式で作成してください。
## 構成
* プライベートクラスタのGKE Autopilotにアプリケーションをホストする
* ホストするアプリケーションは`us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0`とする
* hello-appのPod数は2、1PodあたりのCPUは500m・メモリは256MiBとする
* hello-appはSpot Podで起動する
* 内部アプリケーションロードバランサでエンドポイントを公開する
* ローカルPCからの動作確認は以下の経路とする
* IAPを経由して踏み台サーバにSSHする
* 踏み台サーバから内部ロードバランサにポートフォワードする
* 新しいVPC・サブネットを作成し、不要な通信経路はすべてブロックする
すると以下のプランを生成してくれました。
良さそうなので生成を進めてもらいます。
途中、MCP ServerやSkillsの利用許可を求められたり、構成に対する確認事項にいくつか答え、最終的に以下のまとめと共に生成が完了しました。
TerraformはGoogle Cloudの大まかな構成要素単位にファイル分割して生成されました。
しかし、変数の共通化やモジュール化等は特にされておらず、このままだと使いづらい状態です。
生成に利用したMCP ServerとSkillsを聞いてみたところ以下の通りでした。
初版の生成にはGemini Cloud Assistのみ使用し、Terraform Skillsは使用していませんでした。
vpc.tfの中身を確認する
vpc.tfには以下の4種類のリソースが定義されていました。
- VPC
- サブネット
- Cloud Router
- Cloud NAT
Cloud RouterとCloud NATはGKEノードからインターネット上のDockerレジストリへアウトバウンド通信するために作成しています。
resource "google_compute_network" "vpc_x9y2z" {
name = "vpc-x9y2z"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "gke_subnet_x9y2z" {
name = "gke-subnet-x9y2z"
ip_cidr_range = "10.0.1.0/24"
region = "us-central1"
network = google_compute_network.vpc_x9y2z.id
private_ip_google_access = true
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.1.0.0/16"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.2.0.0/20"
}
}
resource "google_compute_subnetwork" "proxy_only_subnet_x9y2z" {
name = "proxy-only-subnet-x9y2z"
ip_cidr_range = "10.0.2.0/24"
region = "us-central1"
network = google_compute_network.vpc_x9y2z.id
purpose = "REGIONAL_MANAGED_PROXY"
role = "ACTIVE"
}
resource "google_compute_router" "router_x9y2z" {
name = "router-x9y2z"
region = "us-central1"
network = google_compute_network.vpc_x9y2z.id
}
resource "google_compute_router_nat" "nat_x9y2z" {
name = "nat-x9y2z"
router = google_compute_router.router_x9y2z.name
region = "us-central1"
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
}
Google Cloudの構成自体はよさそうですね。
GKEに割り当てるIPレンジを確保するため、サブネットにpod用とService用のセカンダリIPレンジを設定してくれています。
proxy-only-subnet-x9y2zサブネットはpurpose=REGIONAL_MANAGED_PROXYが設定されているため、LB用に用意されたものですね。
一方、Terraformとしては以下の点が気になります。
- 同じ文字列を複数個所でべた書きしている
- region ※プロンプトで指定するのを忘れたのでアメリカになっていますね
- リソース名のサフィックス ※一意な名称をつけるためにサフィックスをつけるのは良いのですが、
x9y2zがいたるところにあるので変更したいときに手間がかかります
gke.tfの中身を確認する
GKE Autopilotのクラスタを定義してありました。
resource "google_container_cluster" "gke_x9y2z" {
name = "gke-cluster-x9y2z"
location = "us-central1"
enable_autopilot = true
network = google_compute_network.vpc_x9y2z.id
subnetwork = google_compute_subnetwork.gke_subnet_x9y2z.id
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false
master_ipv4_cidr_block = "172.16.0.0/28"
}
master_authorized_networks_config {
cidr_blocks {
cidr_block = "10.0.1.0/24"
display_name = "bastion-subnet"
}
}
gateway_api_config {
channel = "CHANNEL_STANDARD"
}
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
deletion_protection = false
}
ちゃんとサブネットで定義したpod用・service用のセカンダリIPレンジを割り当てています。
master_authorized_networks_configでGKEのコントロールプレーンにアクセスできるクライアントを踏み台サーバ用のサブネットに制限しようとしている点は良いですね
bastion.tfの中身を確認する
GCE(踏み台サーバ)とファイアウォールルールの定義がありました。
resource "google_compute_instance" "bastion_x9y2z" {
name = "bastion-x9y2z"
machine_type = "e2-micro"
zone = "us-central1-a"
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
network = google_compute_network.vpc_x9y2z.id
subnetwork = google_compute_subnetwork.gke_subnet_x9y2z.id
}
service_account {
scopes = ["cloud-platform"]
}
metadata = {
enable-oslogin = "TRUE"
}
tags = ["bastion"]
}
resource "google_compute_firewall" "allow_iap_ssh_x9y2z" {
name = "allow-iap-ssh-x9y2z"
network = google_compute_network.vpc_x9y2z.id
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["35.235.240.0/20"]
target_tags = ["bastion"]
}
# 内部ロードバランサへのアクセスを許可するファイアウォールルール (プロキシサブネットからノードへのトラフィック)
resource "google_compute_firewall" "allow_proxy_to_gke_x9y2z" {
name = "allow-proxy-to-gke-x9y2z"
network = google_compute_network.vpc_x9y2z.id
allow {
protocol = "tcp"
ports = ["8080"]
}
source_ranges = ["10.0.2.0/24"]
}
踏み台サーバへのSSH接続をIAP経由のみに限定するファイアウォールルールは問題なさそうです。
一方、踏み台サーバから内部LBに対するHTTPリクエストを許可するファイアウォールルールが不足していそうです。
GKEのマニフェストファイル hello-app.yaml の中身を見る
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-app
spec:
replicas: 2
selector:
matchLabels:
app: hello-app
template:
metadata:
labels:
app: hello-app
spec:
nodeSelector:
cloud.google.com/gke-spot: "true"
containers:
- name: hello-app
image: us-docker.pkg.dev/google-samples/containers/gke/hello-app:1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 500m
memory: 256Mi
limits:
cpu: 500m
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: hello-app-service
spec:
type: ClusterIP
selector:
app: hello-app
ports:
- port: 80
targetPort: 8080
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: internal-http
spec:
gatewayClassName: gke-l7-rilb
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: hello-app-route
spec:
parentRefs:
- name: internal-http
rules:
- backendRefs:
- name: hello-app-service
port: 80
こちらは問題なさそうです。
README.mdの中身を見る
READMEには構築内容と構築手順、疎通確認手順がまとまっていました。
こういったドキュメントを自動で作ってくれるのはありがたいですね。
# GKE Private Gateway Architecture (suffix: x9y2z)
このプロジェクトは、Google Cloud上にプライベートなGKE Autopilot環境を構築し、Gateway APIを使用して内部ロードバランサを構成します。
## 構成内容
- **VPC**: `vpc-x9y2z`
- **GKE**: Autopilotプライベートクラスタ (Gateway API有効)
- **Bastion**: IAP経由でのみアクセス可能な踏み台サーバ
- **Application**: `hello-app:1.0` (Spot Pod, 2 Replicas)
- **Load Balancer**: Gateway API (`gke-l7-rilb`)
## 構築手順
### 1. インフラの構築 (Terraform)
\```bash
cd infra
terraform init
terraform apply
\```
### 2. GKEクラスタへの接続
踏み台サーバ(Bastion)を経由してGKEクラスタに接続するための認証情報を取得します。
\```bash
# プロジェクトIDの設定
export PROJECT_ID=$(gcloud config get-value project)
# GKE認証情報の取得
gcloud container clusters get-credentials gke-cluster-x9y2z --region us-central1 --project $PROJECT_ID
\```
※Master Authorized Networksにより、Bastionからのアクセスが許可されています。
### 3. アプリケーションのデプロイ
\```bash
cd ../manifests
kubectl apply -f hello-app.yaml
\```
### 4. 動作確認
内部ロードバランサ (Gateway) のIPアドレスを確認します。
\```bash
kubectl get gateway internal-http
\```
取得したIPアドレス(例: `10.0.1.X`)に対して、ローカルPCからBastionを経由してポートフォワーディングを行います。
#### ローカルPCのターミナル 1 (SSHトンネルの作成)
\```bash
# ILB_IP は上記で確認したIPに置き換えてください
gcloud compute ssh bastion-x9y2z \
--tunnel-through-iap \
--zone us-central1-a \
-- -L 8080:[ILB_IP]:80
\```
#### ローカルPCのブラウザ または ターミナル 2
ブラウザで `http://localhost:8080` にアクセスするか、以下のコマンドを実行します。
\```bash
curl http://localhost:8080
\```
## 注意点
- **Gateway API**: GKE Autopilotではデフォルトで有効ですが、Terraformで明示的に有効化しています。
- **Spot Pods**: `nodeSelector` によりSpotインスタンスで実行されます。
- **Firewall**: IAPのIP帯域 (`35.235.240.0/20`) からのSSHのみを許可しています。
初版の問題点まとめ
- Terraformの書き方に関する問題点
- 変数定義がなく、同じ文字列が繰り返しべた書きされている
- モジュール化されておらず、類似環境を構築する際に再利用できない
- Google Cloudの構成に関する問題
- 踏み台サーバから内部LBへのHTTP通信を許可するファイアウォールルールが不足している
Terraform Skillsでリファクタリングする
Gemini CLIから以下のプロンプトでリファクタリングしてもらいました。
HashiCorp社の推奨する手法に倣って、カレントディレクトリ配下のTerraformのコードをリファクタリングしてください
すると以下のようにモジュール化するプランを作ってくれました。
上記プランで進めてもらった結果、以下のメッセージと共に作業が完了しました。
Hashicorpが推奨するファイル体系にしたうえで、モジュール化もしてくれました。
移行用にmigration.tfも作成し、movedでリソース名称変更を解決してくれてました。
まだterraform applyしていないため、migration.tfは不要なので削除します。
tfstateのバックアップを取ったうえで、terraform initとterraform planで動作確認をしてくれました。
なお、provider.tfのシンタックスエラーは、私が意図せず変な文字列を混入させてしまっていました。
modules/bastion/main.tfの中身を見る
modulesを代表してbastionだけ紹介します。
gkeとvpcのモジュールもbastionと同様だったので紹介は割愛します。
resource "google_compute_instance" "bastion" {
name = "bastion-${var.prefix}"
machine_type = "e2-micro"
zone = var.zone
boot_disk {
initialize_params {
image = "debian-cloud/debian-12"
}
}
network_interface {
network = var.network_id
subnetwork = var.subnetwork_id
}
service_account {
scopes = ["cloud-platform"]
}
metadata = {
enable-oslogin = "TRUE"
}
tags = ["bastion"]
}
resource "google_compute_firewall" "allow_iap_ssh" {
name = "allow-iap-ssh-${var.prefix}"
network = var.network_id
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = ["35.235.240.0/20"]
target_tags = ["bastion"]
}
# 内部ロードバランサへのアクセスを許可するファイアウォールルール (プロキシサブネットからノードへのトラフィック)
resource "google_compute_firewall" "allow_proxy_to_gke" {
name = "allow-proxy-to-gke-${var.prefix}"
network = var.network_id
allow {
protocol = "tcp"
ports = ["8080"]
}
source_ranges = [var.proxy_subnet_cidr]
}
サフィックスとゾーンが変数化されました。
ただ、サフィックスの変数名がprefixになっている点は残念ですね…
サフィックスはリソース名から除外されたことで読みやすくなりました。
また、モジュール化に伴って別のモジュールで定義されている値も変数化されています。
トップレベルのmain.tfの中身を見る
module "vpc" {
source = "./modules/vpc"
project_id = var.project_id
region = var.region
prefix = var.prefix
}
module "gke" {
source = "./modules/gke"
prefix = var.prefix
region = var.region
network_id = module.vpc.network_id
subnetwork_id = module.vpc.gke_subnet_id
}
module "bastion" {
source = "./modules/bastion"
prefix = var.prefix
zone = var.zone
network_id = module.vpc.network_id
subnetwork_id = module.vpc.gke_subnet_id
proxy_subnet_cidr = module.vpc.proxy_subnet_cidr
}
各モジュールに変数を渡して構成しています。
モジュール間の依存関係もとても分かりやすくなりました。
トップレベルのvariables.tfの中身を見る
variable "project_id" {
description = "The ID of the project in which to create resources."
type = string
default = "" # 手で消しました
}
variable "region" {
description = "The region in which to create resources."
type = string
default = "us-central1"
}
variable "zone" {
description = "The zone in which to create resources."
type = string
default = "us-central1-a"
}
variable "prefix" {
description = "A prefix to use for resource names."
type = string
default = "x9y2z"
}
リファクタリング前は多数に散らばっていたリージョン・ゾーン・サフィックス(prefix)が一か所で定義されるようになりました。
これで環境を複製したい場合はprefixを変え、新しいtfstateでapplyすればすぐに構築できます。
また、東京リージョンへの変更も容易になりました。(一か所のみなので手で修正しました)
これで、前述のTerraformコードの問題点は解消です。
Google Cloudの構成不備を修正する
Gemini CLIに指摘して修正してもらいます。
踏み台サーバから内部LBにHTTP通信を許可するFirewall Ruleがありませんが、通信可能でしょうか?
以下の返答がありました。
プロンプトで追加を依頼して修正してもらいました。
実際に追加されたファイアウォールルールは以下の通りです。
# 踏み台サーバから内部ロードバランサへのアクセスを許可するファイアウォールルール
resource "google_compute_firewall" "allow_bastion_to_ilb" {
name = "allow-bastion-to-ilb-${var.prefix}"
network = var.network_id
allow {
protocol = "tcp"
ports = ["80"]
}
source_ranges = [var.bastion_subnet_cidr]
}
READMEの手順に従って構築・疎通確認をする
terraform applyして構築する
terraform planまではGemini CLIが確認済みなので、applyしてみます。
実際に構築するにはGKEのAPI等、必要なAPIを有効化しておく必要があります。
同じように試してみてエラーが出た方は、エラーメッセージに従ってAPIを有効化して再実施してみてください。
applyしてしばらく待つと、以下のメッセージが表示され無事構築が完了しました。
Apply complete! Resources: 10 added, 0 changed, 0 destroyed.
Outputs:
bastion_name = "bastion-x9y2z"
gke_cluster_name = "gke-cluster-x9y2z"
vpc_name = "vpc-x9y2z"
CloudShellからGKEのコントロールプレーンに接続できないことを確認する。
あえてCloudShellからREADMEの以下のコマンドを実行してみます。
# プロジェクトIDの設定
export PROJECT_ID=$(gcloud config get-value project)
# GKE認証情報の取得
gcloud container clusters get-credentials gke-cluster-x9y2z --region asia-northeast1 --project $PROJECT_ID
すると以下のように403エラーになりました。
Fetching cluster endpoint and auth data.
ERROR: (gcloud.container.clusters.get-credentials) ResponseError: code=403, message=Permission denied on 'locations/asia-notheast1' (or it may not exist). This command is authenticated as xxxx@gmail.com which is the active account specified by the [core/account] property.
これは、GKEクラスタのmaster_authorized_networks_configで接続元をGKEと同一サブネットに限定しているためです。
踏み台サーバにSSH接続する
CloudShellから以下のコマンドを用いてIAP経由のSSH接続をします。
gcloud compute ssh --zone "asia-northeast1-a" "bastion-x9y2z" --tunnel-through-iap --project "プロジェクトID"
READMEにはSSH接続の手順がなかったのは少し残念ですね。
踏み台サーバからGKEのマニフェストファイルをapplyする
接続できたらvi hello-app.yamlでサーバ上にGKEのマニフェストファイルを作成します。
中身はGemini CLIが作成したのもをコピペします。
踏み台サーバからGKEクラスタへの認証情報を取得する
そのまま踏み台サーバで以下のコマンドを実行し、GKEクラスタへの認証情報を取得します。
# プロジェクトIDの設定
export PROJECT_ID=$(gcloud config get-value project)
# GKE認証情報の取得
gcloud container clusters get-credentials gke-cluster-x9y2z --region asia-northeast1 --project $PROJECT_ID
踏み台サーバでは成功すると思いきや、以下のように403エラーになりました。
Fetching cluster endpoint and auth data.
ERROR: (gcloud.container.clusters.get-credentials) ResponseError: code=403, message=Required "container.clusters.get" permission(s) for "projects/xxxxxx/locations/asia-northeast1/clusters/gke-cluster-x9y2z". This command is authenticated as xxxxxx-compute@developer.gserviceaccount.com which is the active account specified by the [core/account] property.
踏み台サーバのサービスアカウントに権限がないようです。
ここでは、gcloud auth loginをしてユーザの権限で作業を進めます。
ログイン後に再度GKE認証情報の取得をすると以下のエラーになりました。
WARNING: Accessing a Kubernetes Engine cluster requires the kubernetes commandline
client [kubectl]. To install, run
$ gcloud components install kubectl
Fetching cluster endpoint and auth data.
CRITICAL: ACTION REQUIRED: gke-gcloud-auth-plugin, which is needed for continued use of kubectl, was not found or is not executable. Install gke-gcloud-auth-plugin for use with kubectl by following https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl#install_plugin
kubeconfig entry generated for gke-cluster-x9y2z.
上記は二つの問題があることを示しています。
-
kubectlがインストールされていないこと -
gke-gcloud-auth-pluginがインストールされていないこと
問題解決:踏み台サーバにkubectlをインストールする
kubectlを踏み台サーバにインストールする必要があるようです。
エラーメッセージの指示に従い以下を実行します。
gcloud components install kubectl
すると以下のエラーが表示されました。
ERROR: (gcloud.components.install)
You cannot perform this action because the Google Cloud CLI component manager
is disabled for this installation. You can run the following command
to achieve the same result for this installation:
sudo apt-get install kubectl
メッセージに従いapt-getならkubectlをインストールできました。
問題解決:踏み台サーバにgke-gcloud-auth-pluginをインストールする
に従い、以下のコマンドを実行してみます。
gcloud components install gke-gcloud-auth-plugin
すると、kubectlの時と同様にapt-getでインストールするよう促すエラーメッセージが表示されました。
ERROR: (gcloud.components.install)
You cannot perform this action because the Google Cloud CLI component manager
is disabled for this installation. You can run the following command
to achieve the same result for this installation:
sudo apt-get install google-cloud-cli-gke-gcloud-auth-plugin
メッセージに従い、apt-getで無事インストールできました。
改めて踏み台サーバからGKEクラスタへの認証情報を取得する
再度、以下のコマンドを実行してみます。
gcloud container clusters get-credentials gke-cluster-x9y2z --region asia-northeast1 --project $PROJECT_ID
すると、以下のメッセージが表示され、無事GKEクラスタの認証情報を取得できました。
Fetching cluster endpoint and auth data.
kubeconfig entry generated for gke-cluster-x9y2z.
GKEクラスタにアプリケーションをデプロイする
README.mdに従い、以下のコマンドを踏み台サーバで実行します。
kubectl apply -f hello-app.yaml
すると、以下のエラーメッセージが表示されてapplyに失敗しました。
error: error validating "hello-app.yaml": error validating data: failed to download openapi: Get "https://34.xx.xx.xx/openapi/v2?timeout=32s": dial tcp 34.xx.xx.xx:443: i/o timeout; if you choose to ignore these errors, turn validation off with --validate=false
パブリックIPアドレスは記事上マスクしています。
どうやら通信エラーが発生しているようです。
Google CloudコンソールからGKEクラスタの情報を見ると34.xx.xx.xxはコントロールプレーンのパブリックエンドポイントでした。
また、プライベートエンドポイントは172.16.0.2でした。
ここから2点問題がわかります。
- パブリックエンドポイントはセキュリティ上望ましくないが作成されている
- 踏み台サーバからGKEコントロールプレーンへのプライベートエンドポイントに対する通信許可設定がファイアウォールルールにない
いずれもTerraformのコードからわかる問題でしたが見逃していました。
問題解決:不要なパブリックエンドポイントを削除する
TerraformコードのGKE定義部分で、enable_private_endpointがfalseになっていたため、trueに変更します。
# 略
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false # ここをtrueにする
master_ipv4_cidr_block = "172.16.0.0/28"
}
# 略
問題解決:DNSベースのエンドポイントを構成する
GKEのコントロールプレーンに接続する方法は大きく分けて以下の2種類あります。
- IPベースのエンドポイント
- DNSベースのエンドポイント
IPベースのエンドポイントは、その名の通りIPアドレス指定でアクセスする方法です。
一方、DNSベースのエンドポイントは、Google APIを介してアクセスします。
そのため、DNSベースのエンドポイントはIAMとVPCSCで保護できるため、よりセキュアな方法です。
今回は、DNSベースのエンドポイントを構成するようTerraformのコードを修正します。
GKEクラスタにDNSベースのエンドポイントを構成するための設定を加える
GKEの定義部分にcontrol_plane_endpoints_configを追加し、DNSベースのエンドポイントに必要な設定を有効化します。
また、IPベースのエンドポイントは使用しないため削除します。
resource "google_container_cluster" "gke" {
# 略
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = true
master_ipv4_cidr_block = "172.16.0.0/28" # ここを削除
}
# 以下のブロックを追加
control_plane_endpoints_config {
dns_endpoint_config {
enable_k8s_certs_via_dns = true
enable_k8s_tokens_via_dns = true
}
ip_endpoints_config {
enabled = false
}
}
}
プライベートGoogleアクセスを構成する
踏み台サーバからGoogle APIに接続する必要があるため、プライベートGoogleアクセスをするための定義を追加します。
プライベートGoogleアクセスのためには以下を行う必要があります。
- VPCでプライベートGoogleアクセスを有効化する
-
googleapis.comの限定公開DNSゾーンを作成し、private.googleapis.comまたはrestricted.googleapis.comの名前解決をするためのレコードを追加する - Google APIにアクセスするためのルーティング設定をする(デフォルトルートで疎通可能)
- Google APIにアクセスするための通信許可設定をファイアウォールルールに追加する
ここはGemini CLIに設定を追加してもらうことにします。
以下のプロンプトで修正指示を出しました。
gke_subnetからプライベートGoogleアクセスできるようにしたいです。
必要な設定を調べて追加してください。
なお、プライベートGoogleアクセス用のドメインは`restricted.googleapis.com`を利用してください。
すると、以下のように完了メッセージが表示されました。
vpcの定義ファイルを見ると以下のコードが追加されていました。
# Cloud DNS Private Zone for googleapis.com
resource "google_dns_managed_zone" "googleapis_com" {
name = "googleapis-com"
dns_name = "googleapis.com."
description = "Private DNS zone for googleapis.com to use restricted.googleapis.com"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.vpc.id
}
}
}
resource "google_dns_record_set" "restricted_googleapis_com_a" {
name = "restricted.googleapis.com."
managed_zone = google_dns_managed_zone.googleapis_com.name
type = "A"
ttl = 300
rrdatas = ["199.36.153.4", "199.36.153.5", "199.36.153.6", "199.36.153.7"]
}
resource "google_dns_record_set" "googleapis_com_cname" {
name = "*.googleapis.com."
managed_zone = google_dns_managed_zone.googleapis_com.name
type = "CNAME"
ttl = 300
rrdatas = ["restricted.googleapis.com."]
}
# Cloud DNS Private Zone for gcr.io
resource "google_dns_managed_zone" "gcr_io" {
name = "gcr-io"
dns_name = "gcr.io."
description = "Private DNS zone for gcr.io to use restricted.googleapis.com"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.vpc.id
}
}
}
resource "google_dns_record_set" "gcr_io_cname" {
name = "*.gcr.io."
managed_zone = google_dns_managed_zone.gcr_io.name
type = "CNAME"
ttl = 300
rrdatas = ["restricted.googleapis.com."]
}
# Cloud DNS Private Zone for pkg.dev
resource "google_dns_managed_zone" "pkg_dev" {
name = "pkg-dev"
dns_name = "pkg.dev."
description = "Private DNS zone for pkg.dev to use restricted.googleapis.com"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.vpc.id
}
}
}
resource "google_dns_record_set" "pkg_dev_cname" {
name = "*.pkg.dev."
managed_zone = google_dns_managed_zone.pkg_dev.name
type = "CNAME"
ttl = 300
rrdatas = ["restricted.googleapis.com."]
}
# Route for restricted.googleapis.com VIP
resource "google_compute_route" "restricted_google_apis" {
name = "restricted-google-apis"
dest_range = "199.36.153.4/30"
network = google_compute_network.vpc.name
next_hop_gateway = "default-internet-gateway"
priority = 1000
}
# Firewall rule to allow egress to restricted Google API VIPs
resource "google_compute_firewall" "allow_restricted_google_apis" {
name = "allow-restricted-google-apis"
network = google_compute_network.vpc.name
allow {
protocol = "tcp"
ports = ["443"]
}
direction = "EGRESS"
priority = 1000
destination_ranges = ["199.36.153.4/30"]
}
restricted_google_apisに関しては、デフォルトルートでルーティング可能なため、削除しました。
前述の下記が設定されていることが確認できました。
- VPCでプライベートGoogleアクセスを有効化する
-
googleapis.comの限定公開DNSゾーンを作成し、private.googleapis.comまたはrestricted.googleapis.comの名前解決をするためのレコードを追加する - Google APIにアクセスするための通信許可設定をファイアウォールルールに追加する
修正したTerraformコードをapplyする
ここでの記載は割愛しますが、planをして確認したところ、意図した追加設定のみが表示されました。
applyすると無事構成変更が完了しました。
改めてGKEクラスタにアプリケーションをデプロイする
GKEクラスタへの接続をDNSベースのエンドポイントに変更したため、CloudShellからでもデプロイできるようになりました。
CloudShellから以下のコマンドで認証情報を取得します。
# プロジェクトIDの設定
export PROJECT_ID=$(gcloud config get-value project)
# GKE認証情報の取得
gcloud container clusters get-credentials gke-cluster-x9y2z --region asia-northeast1 --project $PROJECT_ID --dns-endpoint
続いて以下のコマンドでデプロイします。
kubectl apply -f hello-app.yaml
以下のメッセージが表示されて無事applyコマンドは正常終了しました。
Warning: autopilot-workload-defaulter:Autopilot added tolerations matching: cloud.google.com/gke-spot
Warning: autopilot-default-resources-mutator:The max supported TerminationGracePeriodSeconds is 25 seconds when using toleration of cloud.google.com/gke-spot=true:NoSchedule. Defaulting down from configured 30 seconds to 25 seconds.
deployment.apps/hello-app created
service/hello-app-service created
gateway.gateway.networking.k8s.io/internal-http created
httproute.gateway.networking.k8s.io/hello-app-route created
WarningはSpot Podを利用しているためです。
コンソールからGKEのワークロードを確認すると無事Podの起動が成功していました!
アプリケーションの疎通確認をする
踏み台サーバから疎通確認する
まずは踏み台サーバからcurlで疎通確認します。
そのために、内部LBのプライベートIPアドレスを取得します。
kubectl get gateway internal-http
以下のように表示されました。
NAME CLASS ADDRESS PROGRAMMED AGE
internal-http gke-l7-rilb 10.0.1.6 True 3m16s
踏み台サーバからcurlでアクセスしてみます。
curl http://10.0.1.6
以下のメッセージが表示され無事疎通確認できました。
Hello, world!
Version: 1.0.0
Hostname: hello-app-76f95fcc69-xnv66
CloudShellから疎通を試してみる
CloudShellからも疎通を試してみます。
CloudShellから内部LBへは通信許可するファイアウォールルールを設定していません。
そのため、想定通り通信できないことが確認できました。
ローカルPCから疎通確認する
最後にローカルPCから踏み台サーバにSSH接続し、ポートフォワードすることでローカルのブラウザからアクセスしてみます。
READMEの以下の手順をローカルPCのターミナルから実行します。
gcloud auth login
gcloud compute ssh bastion-x9y2z \
--tunnel-through-iap \
--zone asia-northeast1-a \
-- -L 8080:10.0.1.6:80
上記コマンドを実行した状態で、ブラウザからhttp://localhost:8080にアクセスすると以下のように疎通が取れました。
これでプライベートクラスタに起動し、内部LBで公開しているアプリケーションに踏み台サーバ経由でアクセスできました。
環境削除
お試しは完了したので今回作った環境を以下のコマンドで削除します。
なお、実行はCloudShellのTerraformコードがあるディレクトリから行う必要があります。
terraform destroy
無事、環境は削除できました。
不要になった環境は放っておくと無駄な課金が発生するだけでなく、攻撃を受けて悪用される恐れがあります。
そのため、速やかに削除することをお勧めします。
まとめ
公式のSkills/MCPで武装したGemini CLIは戦力になるか
かなり戦力になると思いました。
多くのコードは生成したものをそのまま使っています。
そのため、かなりの時間短縮になると思いました。
また、Gemini Cloud AssistはGoogle Cloudの設計をしてくれ、Terraform SkillsはTerraformコードの保守性を高めてくれました。
Google Cloud向けのTerraformコードを生成するには、この二つを組み合わせるのは有効に感じました。
ただし、本記事に記載した通り、生成したコードそのままではうまくいかない箇所も何点かありました。
そのため、人間によるレビューと修正は重要だと思います。
なぜAIに直接環境を構築させるのではなく、Terraformのコードを作らせるのか
前回の記事のようにAIに直接デプロイさせることも可能です。
しかし、私はインフラ構築に関してはIaCの生成にとどめ、人間がIaCを使って構築したほうが良いと思いました。
理由は以下の通りです。
- AIに直接構築させるとリスクのある環境を構築する恐れがある
- 無駄にリソースを消費する環境を作って、高額請求を受けるかもしれません
- 重大な脆弱性のある構成をして、悪意のある第三者に悪用されてしまうかもしれません
- 環境構築は再現性が欲しい
- 同じあるいは似た環境を複数作りたいときに、AIに依頼すると再現性の担保が難しいと思います
- 不要になった環境は手軽に削除したい
- IaCなら
terraform destroyのように管理しているリソースを一括で削除できます
- IaCなら
1のリスクが低い状況で、2,3の要望が小さいケースにおいては、AIに直接環境構築させることも有用だと思います。
しかし、1,2,3いずれかが許容できないケースでは、AIにIaCを生成させ、人間がレビュー・手直しをしたうえで、構築するのが現時点では良いと思いました。








