Skip to content

Setup GCP Management Cluster

This guide walks through installing the HyperShift operator on a GKE Autopilot cluster with GCP platform support.

Prerequisites

  • A GCP project for the management cluster
  • gcloud CLI installed and authenticated
  • kubectl or oc configured to access the GKE cluster
  • The hypershift CLI built from the repository
  • A pull secret from console.redhat.com

Enable Required APIs

Enable the GCP APIs needed for the management cluster:

gcloud services enable \
  container.googleapis.com \
  compute.googleapis.com \
  dns.googleapis.com \
  cloudresourcemanager.googleapis.com \
  --project=<control-plane-project-id>

Create GKE Autopilot Cluster

If you don't already have a GKE cluster, create one:

gcloud container clusters create-auto <cluster-name> \
  --project=<control-plane-project-id> \
  --region=<region> \
  --release-channel=stable \
  --quiet

Configure kubectl to use the new cluster:

gcloud container clusters get-credentials <cluster-name> \
  --project=<control-plane-project-id> \
  --region=<region>

Create PSC Subnet

Private Service Connect (PSC) provides private connectivity between the hosted cluster worker nodes and the control plane API server. Each hosted cluster requires its own dedicated PSC subnet, so you will need as many PSC subnets as the maximum number of hosted clusters you plan to run on the management cluster.

The HyperShift operator automatically discovers available PSC subnets in the region and assigns an unused one to each new hosted cluster — you do not need to specify which subnet to use. Just make sure to pre-create enough subnets in the same VPC and region as the GKE cluster.

Create PSC subnets in the same VPC as the GKE cluster:

# Get the VPC name used by the GKE cluster
VPC_NAME=$(gcloud container clusters describe <cluster-name> \
  --project=<control-plane-project-id> \
  --region=<region> \
  --format='value(networkConfig.network)' | xargs basename)

# Create PSC subnets (one per hosted cluster you plan to support)
# Use unique names and non-overlapping CIDR ranges for each subnet
gcloud compute networks subnets create <psc-subnet-001> \
  --project=<control-plane-project-id> \
  --region=<region> \
  --network="${VPC_NAME}" \
  --range=10.3.0.0/24 \
  --purpose=PRIVATE_SERVICE_CONNECT \
  --quiet

DNS Zone Configuration

Before creating HostedClusters, you need to set up a Cloud DNS zone for ExternalDNS to manage API server and OAuth endpoint DNS records.

You can either use an existing DNS zone in a shared project, or create a new one for testing.

Create a Cloud DNS Zone

DNS_PROJECT_ID=<dns-project-id>
DNS_ZONE_NAME=<zone-name>
DNS_DOMAIN=<your-dns-domain>

# Enable DNS API if not already enabled
gcloud services enable dns.googleapis.com --project="${DNS_PROJECT_ID}"

# Create the DNS zone
gcloud dns managed-zones create "${DNS_ZONE_NAME}" \
  --project="${DNS_PROJECT_ID}" \
  --dns-name="${DNS_DOMAIN}." \
  --description="DNS zone for HyperShift hosted clusters" \
  --visibility=public \
  --quiet

Same Project for Dev/Test

For development or testing, you can create the DNS zone in the same project as the management cluster (DNS_PROJECT_ID=<control-plane-project-id>). This avoids cross-project IAM configuration for ExternalDNS.

Delegate DNS from Parent Zone (Optional)

If your DNS domain is a subdomain of an existing zone, delegate it by adding NS records to the parent zone:

PARENT_DNS_PROJECT=<parent-dns-project-id>
PARENT_DNS_ZONE=<parent-zone-name>
PARENT_DNS_DOMAIN=<parent-domain>
SUBDOMAIN_NAME=<subdomain>

# Get name servers from your new zone
NS_SERVERS=$(gcloud dns managed-zones describe "${DNS_ZONE_NAME}" \
  --project="${DNS_PROJECT_ID}" \
  --format="value(nameServers)" | tr ';' '\n')

# Add NS records to parent zone
for ns in ${NS_SERVERS}; do
  gcloud dns record-sets transaction start \
    --zone="${PARENT_DNS_ZONE}" \
    --project="${PARENT_DNS_PROJECT}" 2>/dev/null || true
  gcloud dns record-sets transaction add "${ns}" \
    --zone="${PARENT_DNS_ZONE}" \
    --project="${PARENT_DNS_PROJECT}" \
    --name="${SUBDOMAIN_NAME}.${PARENT_DNS_DOMAIN}." \
    --type=NS \
    --ttl=300
  gcloud dns record-sets transaction execute \
    --zone="${PARENT_DNS_ZONE}" \
    --project="${PARENT_DNS_PROJECT}"
done

Create ExternalDNS Service Account

Create a GCP service account for ExternalDNS with DNS admin permissions:

gcloud iam service-accounts create external-dns \
  --project="${DNS_PROJECT_ID}" \
  --display-name="ExternalDNS Service Account"

gcloud projects add-iam-policy-binding "${DNS_PROJECT_ID}" \
  --member="serviceAccount:external-dns@${DNS_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/dns.admin" \
  --quiet

Note the DNS project ID, DNS domain, and ExternalDNS service account email — you will need them when installing the operator and configuring ExternalDNS WIF.

Install Required CRDs

GKE does not include OpenShift CRDs. Install the CRDs that the HyperShift operator expects:

# Prometheus operator CRDs
oc apply -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/main/example/prometheus-operator-crd/monitoring.coreos.com_servicemonitors.yaml
oc apply -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/main/example/prometheus-operator-crd/monitoring.coreos.com_prometheusrules.yaml
oc apply -f https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/main/example/prometheus-operator-crd/monitoring.coreos.com_podmonitors.yaml

# OpenShift Route CRD
oc apply -f https://raw.githubusercontent.com/openshift/api/6bababe9164ea6c78274fd79c94a3f951f8d5ab2/route/v1/zz_generated.crd-manifests/routes.crd.yaml

# DNSEndpoint CRD (for ExternalDNS)
oc apply -f https://raw.githubusercontent.com/kubernetes-sigs/external-dns/v0.15.0/docs/contributing/crd-source/crd-manifest.yaml

Install HyperShift Operator

Install the operator with GCP platform support:

hypershift install \
  --tech-preview-no-upgrade \
  --enable-conversion-webhook=false \
  --external-dns-provider=google \
  --external-dns-domain-filter=<your-dns-domain> \
  --external-dns-google-project=<dns-project-id> \
  --private-platform=GCP \
  --gcp-project=<control-plane-project-id> \
  --gcp-region=<region> \
  --pull-secret=<path-to-pull-secret> \
  --limit-crd-install=GCP \
  --wait-until-available

Custom HyperShift Image

Add --hypershift-image quay.io/hypershift/hypershift:TAG if using a custom operator image.

Configure Operator Workload Identity

The HyperShift operator needs a GCP service account with PSC permissions to manage Private Service Connect resources.

Create GCP Service Account

CP_PROJECT_ID=<control-plane-project-id>

gcloud iam service-accounts create hypershift-operator \
  --project="${CP_PROJECT_ID}" \
  --display-name="HyperShift Operator"

Create Custom IAM Role

Create a role with minimal PSC permissions:

gcloud iam roles create hypershiftPSCOperator \
  --project="${CP_PROJECT_ID}" \
  --title="HyperShift PSC Operator" \
  --permissions=compute.forwardingRules.list,compute.forwardingRules.use,compute.serviceAttachments.create,compute.serviceAttachments.delete,compute.serviceAttachments.get,compute.serviceAttachments.list,compute.subnetworks.list,compute.subnetworks.use,compute.regionOperations.get

Bind Role and Configure WIF

# Bind the role to the service account
gcloud projects add-iam-policy-binding "${CP_PROJECT_ID}" \
  --member="serviceAccount:hypershift-operator@${CP_PROJECT_ID}.iam.gserviceaccount.com" \
  --role="projects/${CP_PROJECT_ID}/roles/hypershiftPSCOperator"

# Configure Workload Identity binding
gcloud iam service-accounts add-iam-policy-binding \
  "hypershift-operator@${CP_PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${CP_PROJECT_ID}" \
  --member="serviceAccount:${CP_PROJECT_ID}.svc.id.goog[hypershift/operator]" \
  --role="roles/iam.workloadIdentityUser" \
  --condition=None \
  --quiet

# Annotate the Kubernetes service account
oc annotate serviceaccount operator -n hypershift \
  iam.gke.io/gcp-service-account=hypershift-operator@${CP_PROJECT_ID}.iam.gserviceaccount.com \
  --overwrite

# Restart the operator to pick up WIF credentials
oc rollout restart deployment operator -n hypershift
oc rollout status deployment operator -n hypershift --timeout=120s

Configure ExternalDNS Workload Identity

ExternalDNS manages DNS records for hosted cluster API endpoints. It needs WIF access to impersonate the ExternalDNS GCP service account created in the DNS Zone Configuration section.

DNS_PROJECT_ID=<dns-project-id>
EXTERNAL_DNS_SA=external-dns@${DNS_PROJECT_ID}.iam.gserviceaccount.com

# Allow ExternalDNS K8s SA to impersonate the DNS service account
# Cross-project WIF requires both workloadIdentityUser and serviceAccountTokenCreator
gcloud iam service-accounts add-iam-policy-binding "${EXTERNAL_DNS_SA}" \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:${CP_PROJECT_ID}.svc.id.goog[hypershift/external-dns]" \
  --project="${DNS_PROJECT_ID}" \
  --condition=None \
  --quiet

gcloud iam service-accounts add-iam-policy-binding "${EXTERNAL_DNS_SA}" \
  --role=roles/iam.serviceAccountTokenCreator \
  --member="serviceAccount:${CP_PROJECT_ID}.svc.id.goog[hypershift/external-dns]" \
  --project="${DNS_PROJECT_ID}" \
  --condition=None \
  --quiet

# Annotate ExternalDNS K8s SA and restart
oc annotate serviceaccount external-dns -n hypershift \
  iam.gke.io/gcp-service-account=${EXTERNAL_DNS_SA} \
  --overwrite

oc rollout restart deployment/external-dns -n hypershift
oc rollout status deployment/external-dns -n hypershift --timeout=120s

Verification

Verify the operator and ExternalDNS are running:

oc get deployment -n hypershift
oc get pods -n hypershift

Next Steps