Skip to main content

Overview

This guide covers deploying ClickHouse Private on Google Cloud Platform (GCP) using Google Kubernetes Engine (GKE). It adapts the standard AWS deployment process for GCP-specific services including Artifact Registry, GCS with S3-compatible API, and Workload Identity. This guide supports both standard and FIPS 140-2 compliant deployments. FIPS-specific steps and configuration are marked with (FIPS) throughout. Set ENABLE_FIPS=true in 00-env.sh to enable FIPS mode.

Prerequisites

  • GCP Project with billing enabled
  • gcloud CLI installed and authenticated
  • kubectl installed
  • helm v3.x installed
  • skopeo installed (for copying container images)
  • AWS credentials with read access to ClickHouse ECR (for image copying)
  • Bastion host in the VPC for accessing the private cluster
  • Deployer service account with appropriate permissions (see below)
  • (FIPS) Access to Red Hat UBI8 container image (pulled via GKE for certificate generation)

Creating a Service Account for ClickHouse Deployment

In FedRAMP environments where direct user authentication is restricted, create a service account for impersonation.

Option 1: Via GCP Console

  1. Go to IAM & AdminService Accounts
  2. Click + CREATE SERVICE ACCOUNT
  3. Enter:
    • Name: clickhouse-deployer
    • ID: clickhouse-deployer
    • Description: Service account for deploying ClickHouse infrastructure
  4. Click CREATE AND CONTINUE
  5. Add the following roles:
  1. Click CONTINUEDONE
Grant Token Creator Permission:
  1. Click on the newly created clickhouse-deployer service account
  2. Go to PERMISSIONS tab
  3. Click GRANT ACCESS
  4. Add principal: <COMPUTE_SERVICE_ACCOUNT>@developer.gserviceaccount.com (your bastion’s service account)
  5. Assign role: roles/iam.serviceAccountTokenCreator
  6. Click SAVE

Option 2: Via CLI (requires IAM admin access)

Configure Impersonation on Bastion

Add this to your 00-env.sh or run before executing scripts:
To clear impersonation later:

Set up Tasks

GCP Resources

  1. Set Environment Variables - 00-env.sh
  2. Create Artifact Registry Repository - 01-artifact-registry.sh
  3. Copy Container Artifacts - 02-copy-images.sh
  4. Configure VPC and GKE Subnet - 03-vpc-subnet.sh
  5. Create Cloud NAT - 04-cloud-nat.sh
  6. Create GCP Service Account - 05-service-account.sh
  7. Create GCS Bucket - 06-gcs-bucket.sh
  8. Create GKE Cluster - 07-gke-cluster.sh
    • Enable Private Nodes (--enable-private-nodes)
    • Enable Private Endpoint (--enable-private-endpoint) - No public API access (FedRAMP)
    • Enable Workload Identity (--workload-pool=$PROJECT_ID.svc.id.goog)
  9. Create Node Pools - 08-node-pools.sh
  10. Deploy Local SSD DaemonSet - 09-local-ssd-daemonset.sh
  11. Add Topology Labels to Nodes - 10-topology-labels.sh

Kubernetes Resources

  1. Install VolumeSnapshot CRDs - 11-volumesnapshot-crds.sh
  2. Set up Workload Identity - 12-workload-identity.sh
  3. Install StorageClass - 13-storageclass.sh (optional - can use built-in premium-rwo)

FIPS-Specific Steps (skip if ENABLE_FIPS=false)

  1. (FIPS) Create FIPS-Compliant Certificates - 14-create-fips-certs.sh
  2. (FIPS) Create Kubernetes Certificate Secrets - 15-create-cert-secrets.sh

Deploy ClickHouse

  1. Install Operator via Helm - 16-install-operator.sh
  2. Create ClickHouseCluster Resource - 17-install-cluster.sh
  3. Bind Workload Identity to Helm SA - 18-bind-helm-workload-identity.sh
  4. Verify Installation

00. Environment Variables

File: 00-env.sh
Important: Source this file before running other scripts:

01. Create Artifact Registry

File: 01-artifact-registry.sh

02. Copy Container Images

File: 02-copy-images.sh Requires skopeo to retain all architectures.

03. Configure VPC and Subnet

File: 03-vpc-subnet.sh This script assumes you have an existing VPC with a bastion host. It creates or updates the GKE subnet with required secondary ranges. Important: CIDR Boundary Requirements
  • /14 blocks: Third octet must be divisible by 4 (e.g., 10.244.0.0, NOT 10.245.0.0)
  • /20 blocks: Fourth octet must be 0, third octet on 16-boundary (e.g., 10.252.0.0)

04. Create Cloud NAT

File: 04-cloud-nat.sh Private GKE nodes require Cloud NAT for outbound internet access (pulling images, etc.).

05. Create GCP Service Account

File: 05-service-account.sh

06. Create GCS Bucket

File: 06-gcs-bucket.sh

07. Create GKE Cluster

File: 07-gke-cluster.sh Creates a fully private GKE cluster for FedRAMP compliance:
  • Worker nodes have only internal IPs
  • Control plane has only a private endpoint
  • All access must come from within the VPC (via bastion)
Private Cluster Settings:

Accessing the Private Cluster

With --enable-private-endpoint, the cluster API is only accessible from within the VPC. From the Bastion Host:

08. Create Node Pools

File: 08-node-pools.sh

09. Local SSD DaemonSet

File: 09-local-ssd-daemonset.sh GKE Container-Optimized OS (COS) has a read-only root filesystem, so we cannot create /nvme. Instead, we mount the local SSD at /mnt/disks/ssd0 and configure ClickHouse to use that path. Note: GKE does not allow --metadata-from-file=startup-script on node pools (reserved key), so a DaemonSet is required to format and mount the local SSD.
Important Notes:
  • GKE Container-Optimized OS has a read-only root filesystem - cannot create /nvme
  • Local SSD is mounted at /mnt/disks/ssd0
  • Uses busybox:1.36 image (no internet required for apt-get)
  • Uses nsenter to run commands on the host filesystem
  • DaemonSet must be deployed before the ClickHouse cluster to avoid race conditions

10. Add Topology Labels

File: 10-topology-labels.sh

11. Install VolumeSnapshot CRDs

File: 11-volumesnapshot-crds.sh

12. Workload Identity Setup

File: 12-workload-identity.sh
Workload Identity Alignment:

13. Install StorageClass

File: 13-storageclass.sh (Optional) Note: GKE uses topology.gke.io/zone instead of topology.kubernetes.io/zone.

14. Create FIPS Certificates (FIPS only)

File: 14-create-fips-certs.sh — Skip this step if ENABLE_FIPS=false. FIPS-compliant certificates must be generated using FIPS-approved algorithms in a FIPS-enabled environment. This script runs a Red Hat UBI8 pod on the GKE cluster with FIPS crypto policy enabled, then copies the generated certificates back to the bastion via kubectl cp. Requirements:
  • RSA 3072-bit minimum key size
  • SHA-256 or higher signature algorithm
  • Subject Alternative Names (SANs) must include cluster DNS names
  • kubectl access to the GKE cluster (no docker required)
Certificate Types: The script performs these steps:
  1. Creates a ConfigMap with the cert generation script
  2. Runs a UBI8 pod with an initContainer that installs OpenSSL, sets FIPS crypto policy via update-crypto-policies --set FIPS, and generates all certificates
  3. Copies the certificates back to the bastion via kubectl cp
  4. Cleans up the pod and ConfigMap
See fips/14-create-fips-certs.sh for the full script. Important Notes:
  • Certificates expire after 365 days by default — adjust CERT_VALIDITY as needed
  • CA certificate expires after 3650 days (10 years)
  • Uses RSA 3072-bit keys (FIPS-compliant minimum)
  • Keep private keys (*.key files) secure and never commit to version control

15. Create Certificate Secrets (FIPS only)

File: 15-create-cert-secrets.sh — Skip this step if ENABLE_FIPS=false. Create Kubernetes secrets containing the FIPS-compliant certificates. Secrets must be in the same namespace as the ClickHouse cluster.
Secret Structure:

16. Install Operator

File: 16-install-operator.sh

17. Install ClickHouse Cluster

File: 17-install-cluster.sh
Important GCP-specific settings:
  • storageClass.create=false: Don’t create AWS StorageClass
  • storageClass.name="premium-rwo": Use GCP’s built-in SSD StorageClass
  • server.storage.s3.endpoint: https://storage.googleapis.com for GCS S3-compatible API
  • server.storage.s3.useEnvironmentCredentials=true: Required for Workload Identity
  • keeper.storage.s3.useEnvironmentCredentials=true: Must also be set for keeper!
(FIPS) OpenSSL settings (automatically included when ENABLE_FIPS=true):

18. Bind Workload Identity to Helm SA

File: 18-bind-helm-workload-identity.sh The helm chart creates its own service account (ch-${CLUSTER_NAME}-sa). You must bind Workload Identity to this SA for GCS access. Important: The keeper and server pods created during step 17 start before this Workload Identity binding exists, so they will fail with GCS 403 Access Denied errors. This script automatically restarts the pods after binding so they pick up the new credentials.

19. Verify Installation

Standard mode (no TLS):
(FIPS) TLS mode — With openSSL.required=true, ClickHouse only accepts connections on secure ports. Use port 9440 (native TLS) instead of 9000:
The --accept-invalid-certificate flag is needed because we use self-signed certificates. For production with proper CA-signed certs, specify the CA certificate explicitly:
Run a test query:
Expected output:
Important (FIPS): Port 9000 (plaintext native) and 8123 (plaintext HTTP) will refuse connections when openSSL.required=true. Use port 9440 (native TLS) or 8443 (HTTPS) instead.

Appendix: AWS to GCP Component Mapping


Appendix: FIPS-Specific Considerations

Note: This section only applies when ENABLE_FIPS=true is set in 00-env.sh.

Certificate Requirements

FIPS compliance requires:
  • Minimum RSA 3072-bit keys
  • FIPS-approved signature algorithms (SHA-256 or higher)
  • Certificates generated in FIPS-enabled environment (Red Hat UBI8)
  • Proper certificate chain validation

OpenSSL Configuration

Both server and keeper must have:
  • openSSL.enabled=true - Enable TLS
  • openSSL.required=true - Enforce TLS (reject non-TLS connections)
  • openSSL.selfSigned=true - For self-signed certificates (set false if using proper CA)

Key Differences from Standard GCP Installation


Appendix: Troubleshooting

Certificate Verification Failures (FIPS)

If you see errors like “certificate verify failed”:
  1. Verify CA certificate is correct:
  1. Verify server certificate SANs:
  1. Check secret contents:

FIPS Mode Verification

Verify FIPS compliance of generated certificates:
Should show:
  • Private key: 3072-bit
  • Signature Algorithm: sha256WithRSAEncryption (or higher)

Pods stuck in Pending - Node Selector Issues

Private Nodes Cannot Pull Images

Verify Cloud NAT is configured:

GCS Access Denied Errors (403)

The most common cause is a Workload Identity race condition: pods start during the Helm install (Step 17) before the Helm-created SA (ch-${CLUSTER_NAME}-sa) has its Workload Identity binding applied in Step 18. Step 18 now handles this automatically by restarting pods after binding. If you still see 403 errors:

Cannot Access Cluster from Bastion

Verify bastion subnet is in authorized networks:

Connection Refused on Port 9000 (FIPS)

With FIPS TLS enabled (openSSL.required=true), ClickHouse does not accept plaintext connections. Port 9000 (native) and 8123 (HTTP) will refuse connections. Use the secure ports instead:

Helm Registry Login Fails

helm registry login does not accept https:// prefix:

Validate SSD Cache script

validate-ssd-cache.sh

Query cache status

query-cache-status.sh

Security Best Practices

  1. Certificate Management (FIPS)
    • Rotate certificates before expiration (default: 365 days)
    • Store private keys securely (use secrets management)
    • Never commit certificates to version control
    • Use proper CA-signed certificates in production
  2. Network Security
    • Use private GKE cluster with no public endpoint
    • Restrict master-authorized-networks to bastion and GKE subnets
    • Enable VPC flow logs
    • Use Cloud NAT for controlled outbound access
  3. Access Control
    • Use Workload Identity instead of static credentials
    • Enable Kubernetes RBAC
    • Audit access logs regularly
    • Use service account impersonation for deployment
  4. Monitoring
    • Monitor certificate expiration dates (FIPS)
    • Set up alerts for failed TLS connections (FIPS)
    • Monitor GCS access patterns
    • Enable Cloud Audit Logging