This guide deploys a GKE cluster, cert-manager, and the helloworld app using Terraform and Kubernetes manifests.
nenmp/
├── app/helloworld/
│ ├── Dockerfile
│ └── index.html
├── .github/workflows/
│ └── deploy.yml
├── infra/terraform/
│ ├── main.tf
│ ├── gke.tf
│ ├── sa.tf
│ ├── outputs.tf
│ └── variables.tf
└── k8s/
├── app/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── ingress.yaml
└── cert-manager/
├── clusterissuer.yaml
├── certificate.yaml
└── cloudflare-secret.yaml # placeholder only
- Terraform installed
gcloudinstalled and authenticated to your projectkubectlinstalled- Domain
deniskachar.commanaged in Cloudflare - GCP Secret Manager secret
cloudflare-api-tokenalready created
infra/terraform/sa.tf creates identity resources for GitHub Actions:
- Service account:
github-actions@project-e7b71f6e-c56d-438f-a7e.iam.gserviceaccount.com - Workload Identity Pool:
github-pool - Workload Identity Provider:
github-provider - Repo restriction: only
SNKD92/k8scan federate - IAM roles on project:
roles/container.developerroles/container.clusterViewer
- Service account impersonation binding:
roles/iam.workloadIdentityUserfor principal setprincipalSet://iam.googleapis.com/projects/365212276900/locations/global/workloadIdentityPools/github-pool/attribute.repository/SNKD92/k8s
GitHub workflow file: .github/workflows/deploy.yml.
Keep these values in sync between infra/terraform/sa.tf and .github/workflows/deploy.yml:
- Project number:
365212276900 - Workload Identity provider path:
projects/365212276900/locations/global/workloadIdentityPools/github-pool/providers/github-provider - Service account:
github-actions@project-e7b71f6e-c56d-438f-a7e.iam.gserviceaccount.com - Repository scope:
SNKD92/k8s
Required GitHub repository secrets for that workflow:
DOCKER_USERNAME(Docker Hub username)DOCKER_PASSWORD(Docker Hub access token/password) Source of truth: GCP Secret Manager secretprojects/365212276900/secrets/githubactionssecret(latest version).
No static GCP service account key secret is required because the workflow uses OIDC Workload Identity Federation.
cd infra/terraform
terraform init
terraform applyWhen prompted, type:
yes
gcloud container clusters get-credentials small-gke --zone us-central1-a
kubectl get nodesExpected output should show nodes in Ready state.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
kubectl get pods -n cert-managerWait until cert-manager pods are Running.
kubectl create secret generic cloudflare-api-token-secret \
-n cert-manager \
--from-literal=api-token="$(gcloud secrets versions access latest --secret=cloudflare-api-token)" \
--dry-run=client -o yaml | kubectl apply -f -Return to repo root first:
cd ../..
kubectl apply -f k8s/cert-manager/clusterissuer.yaml
kubectl apply -f k8s/cert-manager/certificate.yaml
kubectl get clusterissuerExpected:
letsencrypt-cloudflare True
kubectl apply -f k8s/app/
kubectl get ingress deniskachar-com-ingressCopy the ingress external IP from the output.
Create an A record:
deniskachar.com -> <INGRESS_IP>
kubectl get certificate deniskachar-cert
kubectl get secret deniskachar-com-tlsThen test:
https://deniskachar.com
The Cloudflare API token is stored in GCP Secret Manager and injected into Kubernetes via the Step 4 command.