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
gcloudCLI installed and authenticatedkubectlorocconfigured to access the GKE cluster- The
hypershiftCLI 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
- Create GCP Infrastructure — Create VPC and subnet for hosted clusters
- Create GCP IAM Resources — Create WIF pool and service accounts