Infrastructure

This provisions the cloud infrastructure Confident AI runs on: a GKE cluster, a Cloud SQL PostgreSQL database, GCS buckets, and the keyless identity wiring. When it finishes you will have a running cluster and a set of outputs to feed into the Helm chart on the next page.

Set your project and region once, and enable the APIs the module uses:

$export PROJECT=my-gcp-project
$export REGION=us-central1
$gcloud config set project $PROJECT
$
$gcloud services enable \
> container.googleapis.com sqladmin.googleapis.com \
> servicenetworking.googleapis.com compute.googleapis.com \
> artifactregistry.googleapis.com run.googleapis.com \
> iamcredentials.googleapis.com
1

Create the network (optional)

Skip this if you already have a VPC network with a subnet that has two secondary ranges (pods and services) and Cloud NAT for its private nodes. Use your existing names in the next step.

GKE needs one subnet with two secondary ranges, one for pods and one for services, plus Cloud NAT so the private nodes can pull images. Run the blocks in order.

Create the VPC network in custom subnet mode so you define the subnet yourself:

$gcloud compute networks create confident-prod-vpc --subnet-mode=custom

Create the subnet with the two secondary ranges GKE needs:

$gcloud compute networks subnets create confident-prod-subnet \
> --network=confident-prod-vpc --region=$REGION \
> --range=10.30.0.0/20 \
> --secondary-range confident-pods=10.30.32.0/19,confident-services=10.30.16.0/20

ClickHouse runs cleanly on the IPv4-only GKE cluster this module builds: the chart pins the operator’s pods to listen on IPv4 (0.0.0.0), so there is no address-family error and no dual-stack setup is required. A dual-stack subnet alone would not change this, the nodes stay IPv4-only unless the cluster itself is created dual-stack.

Add a Cloud Router and NAT so the private nodes reach the internet for outbound pulls, with no inbound exposure:

$gcloud compute routers create confident-prod-router \
> --network=confident-prod-vpc --region=$REGION
$gcloud compute routers nats create confident-prod-nat \
> --router=confident-prod-router --region=$REGION \
> --nat-all-subnet-ip-ranges --auto-allocate-nat-external-ips
2

Mirror the code sandbox image (required)

Code-based and transformer metrics run in a sandboxed Cloud Run service, which runs the confident-code-sandbox-gcp image. It must live in your own Artifact Registry before Terraform creates the service, so mirror the public image.

Create an Artifact Registry repository and let Docker authenticate to it:

$gcloud artifacts repositories create confident \
> --repository-format=docker --location=$REGION
$gcloud auth configure-docker $REGION-docker.pkg.dev --quiet

Pull the public image and push it to your repository:

$docker pull confidentai/confident-code-sandbox-gcp:latest
$docker tag confidentai/confident-code-sandbox-gcp:latest \
> $REGION-docker.pkg.dev/$PROJECT/confident/confident-code-sandbox-gcp:latest
$docker push $REGION-docker.pkg.dev/$PROJECT/confident/confident-code-sandbox-gcp:latest
3

Write the Terraform config

Create a main.tf that references the published module, then fill in your project and the network names from the earlier steps. What each variable does:

  • Project and network: confident_gcp_project_id, confident_gcp_region, and the network names from the network step.
  • Naming: confident_environment and confident_environment_code stamp the prod naming convention onto every resource.
  • Access: confident_public_gke exposes the cluster API to your machine; set it false for a private-only endpoint.
  • Database: confident_psql_password sets your own PostgreSQL password; omit it to auto-generate one.
  • Managed services: confident_create_secret_manager and confident_managed_redis_enabled turn on the recommended secret store and Redis.
  • Code executor: confident_ar_repository_name is the Artifact Registry repo holding the image you just mirrored.
1provider "google" {
2 project = "my-gcp-project"
3 region = "us-central1"
4}
5
6module "confident_ai" {
7 source = "confident-ai/confident-ai/google"
8 version = "~> 0.1"
9
10 confident_gcp_project_id = "my-gcp-project"
11 confident_gcp_region = "us-central1"
12
13 confident_network_name = "confident-prod-vpc"
14 confident_network_id = "projects/my-gcp-project/global/networks/confident-prod-vpc"
15 confident_subnetwork_name = "confident-prod-subnet"
16 confident_ip_range_pods = "confident-pods"
17 confident_ip_range_services = "confident-services"
18
19 confident_environment = "prod"
20 confident_environment_code = "p"
21
22 confident_public_gke = true
23
24 confident_psql_password = "choose-a-strong-password"
25
26 confident_create_secret_manager = true
27 confident_managed_redis_enabled = true
28
29 confident_code_executor_enabled = true
30 confident_ar_repository_name = "confident"
31}
32
33output "helm_values" {
34 value = module.confident_ai.helm_values
35 sensitive = true
36}

Prefer to work inside the repo? Clone terraform-google-confident-ai and put the same variables in a terraform.tfvars file instead of a module block.

4

Configure remote state (optional)

Skip this to use local state. For a team or a real environment, keep state in a GCS bucket.

Create a backend.tf:

1terraform {
2 backend "gcs" {
3 bucket = "confident-tfstate"
4 prefix = "confident-ai/gcp"
5 }
6}
5

Apply

$terraform init
$terraform plan
$terraform apply

Creating the GKE cluster and Cloud SQL takes roughly 15 to 20 minutes.

6

Connect to the cluster

$eval "$(terraform output -raw configure_kubectl)"
$kubectl get nodes

GKE has a default storage class (standard-rwo), so the in-cluster ClickHouse and Redis disks work out of the box.

7

Read the outputs

The Helm chart on the next page needs these values. terraform output helm_values prints a ready-to-paste snippet, or read them individually. The comment on each line is the Helm value it feeds:

$terraform output -raw database_url # secrets.data.DATABASE_URL
$terraform output test_cases_bucket # storage.testCasesBucket
$terraform output payloads_bucket # storage.payloadsBucket
$terraform output -raw app_service_account_email # serviceAccount annotation
$terraform output -raw code_executor_function_url # codeExecutor.gcp.functionUrl

The module block above provisions both. The Deploy page installs the External Secrets Operator and wires them into the chart:

  • Secret Manager + External Secrets Operator (confident_create_secret_manager = true): Terraform creates the secret and a Workload-Identity-bound service account for ESO.
  • Memorystore for Redis (confident_managed_redis_enabled = true): managed Redis instead of the in-cluster one. terraform output -raw redis_url gives the value for redis.externalUrl.

Prefer a simpler footprint? Set both to false to hold secrets in a Kubernetes Secret and run Redis in the cluster. See Simpler option on the Deploy page.

Inputs reference

The variables you are most likely to set. For the complete, always-current list, see the module inputs on the Terraform Registry.

Required

VariableDescription
confident_gcp_project_idGCP project to deploy into.
confident_network_name / confident_network_idExisting VPC network name and self-link.
confident_subnetwork_nameExisting subnet for the GKE nodes.
confident_ip_range_pods / confident_ip_range_servicesExisting secondary range names (pods and services).

Commonly set (optional, with production defaults)

VariableDefaultDescription
confident_gcp_regionus-central1Region for the cluster and data plane.
confident_environment / confident_environment_codestage / sNaming convention stamped on resources (use prod / p).
confident_public_gkefalseExpose the cluster API endpoint.
confident_psql_passwordgeneratedSet your own PostgreSQL password, or leave unset to auto-generate one.
confident_node_machine_type / confident_node_group_desired_sizen2-standard-8 / 4Node pool sizing.
confident_code_executor_enabled / confident_ar_repository_nametrue / ""Code executor Cloud Run service and the Artifact Registry repo holding its image.
confident_create_secret_managerfalseSecret Manager secret and ESO Workload Identity.
confident_managed_redis_enabledfalseMemorystore instead of in-cluster Redis.

Next step