strip cluster bootstraps
All checks were successful
AI Code Review / ai-review (pull_request) Successful in 59s

This commit is contained in:
2026-04-27 21:34:11 +02:00
parent 0353803d4f
commit 96dde22884
42 changed files with 65 additions and 2338 deletions

View File

@@ -12,30 +12,6 @@ resource "google_project_service" "container" {
disable_on_destroy = false
}
resource "google_project_service" "sqladmin" {
project = var.project_id
service = "sqladmin.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "servicenetworking" {
project = var.project_id
service = "servicenetworking.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "redis" {
project = var.project_id
service = "redis.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "iam" {
project = var.project_id
service = "iam.googleapis.com"
disable_on_destroy = false
}
# ─── Networking ───────────────────────────────────────────────────────
resource "google_compute_network" "main" {
@@ -65,31 +41,11 @@ resource "google_compute_subnetwork" "main" {
}
}
# Private IP range for Cloud SQL VPC peering
resource "google_compute_global_address" "private_ip_range" {
project = var.project_id
name = "${var.prefix}-private-ip-range"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 20
network = google_compute_network.main.id
depends_on = [google_project_service.compute]
}
resource "google_service_networking_connection" "private_vpc_connection" {
network = google_compute_network.main.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_range.name]
depends_on = [google_project_service.servicenetworking]
}
# ─── GKE Cluster ──────────────────────────────────────────────────────
#
# Regional cluster (3 control-plane replicas) for HA.
# Workload Identity enabled — allows K8s service accounts to impersonate
# Google Service Accounts for keyless GCS access.
# Google Service Accounts for keyless access to GCP services.
resource "google_container_cluster" "main" {
project = var.project_id
@@ -157,285 +113,3 @@ resource "google_container_node_pool" "main" {
auto_upgrade = true
}
}
# ─── Cloud SQL PostgreSQL ─────────────────────────────────────────────
#
# Private IP only — reachable from GKE via VPC peering.
# NOTE: Cloud SQL doesn't support Terraform-managed local user creation.
# Users (keycloak, gitlab) must be created post-provision via psql.
# Use: kubectl run pg-init --rm -it --image=postgres:16 -- psql -h <private_ip> -U pgadmin
resource "random_password" "pg_admin" {
length = 32
special = false
}
resource "random_password" "pg_keycloak" {
length = 32
special = false
}
resource "random_password" "pg_gitlab" {
length = 32
special = false
}
resource "google_sql_database_instance" "main" {
project = var.project_id
name = "${var.prefix}-postgresql"
region = var.region
database_version = var.pg_database_version
settings {
tier = var.pg_tier
availability_type = var.pg_availability_type
disk_size = var.pg_disk_size_gb
disk_autoresize = true
ip_configuration {
ipv4_enabled = false # private IP only
private_network = google_compute_network.main.id
enable_private_path_for_google_cloud_services = true
}
backup_configuration {
enabled = var.pg_backup_enabled
}
database_flags {
name = "max_connections"
value = "200"
}
}
deletion_protection = var.pg_deletion_protection
depends_on = [google_service_networking_connection.private_vpc_connection]
}
resource "google_sql_user" "pg_admin" {
project = var.project_id
name = "pgadmin"
instance = google_sql_database_instance.main.name
password = random_password.pg_admin.result
}
resource "google_sql_database" "keycloak" {
project = var.project_id
name = "keycloak"
instance = google_sql_database_instance.main.name
}
resource "google_sql_database" "gitlab" {
project = var.project_id
name = "gitlabhq_production"
instance = google_sql_database_instance.main.name
}
# ─── Cloud Memorystore (Redis) ────────────────────────────────────────
#
# Private IP within VPC. Auth enabled (password via AUTH command).
# The auth_string is output and must be stored in a K8s secret for GitLab.
resource "google_redis_instance" "main" {
project = var.project_id
name = "${var.prefix}-redis"
region = var.region
tier = var.redis_tier
memory_size_gb = var.redis_memory_size_gb
authorized_network = google_compute_network.main.id
# Redis AUTH password — keyless access is not supported by Memorystore
auth_enabled = true
labels = var.labels
depends_on = [google_project_service.redis]
}
# ─── GCS Buckets (GitLab Object Storage) ─────────────────────────────
#
# GitLab supports GCS natively via the Fog/Google provider.
# Workload Identity is used for keyless access — no access key required.
# NOTE: GCS bucket names are globally unique. If "${prefix}-gitlab-*" conflicts,
# adjust var.prefix to include a project-specific component.
locals {
gcs_bucket_prefix = "${var.prefix}-gitlab"
}
resource "google_storage_bucket" "gitlab_artifacts" {
project = var.project_id
name = "${local.gcs_bucket_prefix}-artifacts"
location = var.region
storage_class = var.gcs_storage_class
force_destroy = true
uniform_bucket_level_access = true
labels = var.labels
}
resource "google_storage_bucket" "gitlab_uploads" {
project = var.project_id
name = "${local.gcs_bucket_prefix}-uploads"
location = var.region
storage_class = var.gcs_storage_class
force_destroy = true
uniform_bucket_level_access = true
labels = var.labels
}
resource "google_storage_bucket" "gitlab_packages" {
project = var.project_id
name = "${local.gcs_bucket_prefix}-packages"
location = var.region
storage_class = var.gcs_storage_class
force_destroy = true
uniform_bucket_level_access = true
labels = var.labels
}
resource "google_storage_bucket" "gitlab_lfs" {
project = var.project_id
name = "${local.gcs_bucket_prefix}-lfs"
location = var.region
storage_class = var.gcs_storage_class
force_destroy = true
uniform_bucket_level_access = true
labels = var.labels
}
resource "google_storage_bucket" "gitlab_registry" {
project = var.project_id
name = "${local.gcs_bucket_prefix}-registry"
location = var.region
storage_class = var.gcs_storage_class
force_destroy = true
uniform_bucket_level_access = true
labels = var.labels
}
resource "google_storage_bucket" "gitlab_backups" {
project = var.project_id
name = "${local.gcs_bucket_prefix}-backups"
location = var.region
storage_class = var.gcs_storage_class
force_destroy = true
uniform_bucket_level_access = true
labels = var.labels
}
# ─── Google Identity Provider for Keycloak ────────────────────────────
#
# Keycloak federates with Google — users authenticate via "Sign in with Google"
# through Keycloak, which remains the single OIDC issuer for all services.
#
# IMPORTANT: The Google OAuth 2.0 client (Web Application type) must be
# created MANUALLY in Google Cloud Console:
# APIs & Services → Credentials → Create OAuth client ID → Web application
# Authorized redirect URIs: https://keycloak.<domain>/realms/devops/broker/google/endpoint
#
# After creation, fill in k8s/scripts/gcp-{dev,prod}/gcp-idp.env:
# GOOGLE_IDP_CLIENT_ID=<client-id>
# GOOGLE_IDP_CLIENT_SECRET=<client-secret>
#
# Then run: ./setup-keycloak.sh --env gcp-dev idp
# Enable Google Identity Platform API for documentation purposes
resource "google_project_service" "oauth2" {
project = var.project_id
service = "oauth2.googleapis.com"
disable_on_destroy = false
}
# ─── Workload Identity for GitLab ─────────────────────────────────────
#
# Allows GitLab pods (webservice, sidekiq) to access GCS buckets without
# a service account key. The K8s service account "gitlab" in the "gitlab"
# namespace exchanges its projected OIDC token for a Google token.
#
# GKE must have workload_identity_config set (done above).
resource "google_service_account" "gitlab" {
project = var.project_id
account_id = "${var.prefix}-gitlab"
display_name = "GitLab Service Account (Workload Identity)"
depends_on = [google_project_service.iam]
}
# Grant the GSA Object Admin on all GitLab buckets
resource "google_storage_bucket_iam_member" "gitlab_artifacts" {
bucket = google_storage_bucket.gitlab_artifacts.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gitlab.email}"
}
resource "google_storage_bucket_iam_member" "gitlab_uploads" {
bucket = google_storage_bucket.gitlab_uploads.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gitlab.email}"
}
resource "google_storage_bucket_iam_member" "gitlab_packages" {
bucket = google_storage_bucket.gitlab_packages.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gitlab.email}"
}
resource "google_storage_bucket_iam_member" "gitlab_lfs" {
bucket = google_storage_bucket.gitlab_lfs.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gitlab.email}"
}
resource "google_storage_bucket_iam_member" "gitlab_registry" {
bucket = google_storage_bucket.gitlab_registry.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gitlab.email}"
}
resource "google_storage_bucket_iam_member" "gitlab_backups" {
bucket = google_storage_bucket.gitlab_backups.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gitlab.email}"
}
# Bind the K8s service account "gitlab/gitlab" to the GSA via Workload Identity.
# The GitLab Helm chart creates the "gitlab" SA when global.serviceAccount.enabled=true.
resource "google_service_account_iam_member" "gitlab_workload_identity" {
service_account_id = google_service_account.gitlab.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[gitlab/gitlab]"
}
# ─── External-DNS Workload Identity ──────────────────────────────────
# Allows external-dns to manage Cloud DNS records for the cluster's domain.
# The K8s service account "external-dns/external-dns" exchanges its OIDC token
# for a Google token via Workload Identity.
resource "google_service_account" "external_dns" {
project = var.project_id
account_id = "${var.prefix}-external-dns"
display_name = "External-DNS Service Account (Workload Identity)"
depends_on = [google_project_service.iam]
}
resource "google_project_iam_member" "external_dns_dns_admin" {
project = var.project_id
role = "roles/dns.admin"
member = "serviceAccount:${google_service_account.external_dns.email}"
}
resource "google_service_account_iam_member" "external_dns_workload_identity" {
service_account_id = google_service_account.external_dns.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[external-dns/external-dns]"
}