Create a Low Cost Cluster
gcloud container clusters create "medium-cost-cluster" \
--zone "us-central1-a" \
--num-nodes=1 \
--machine-type "e2-medium" \
--spot \
--disk-type "pd-standard" \
--disk-size "30GB" \
--release-channel "stable"Create a New Artifact Registry Repository
gcloud artifacts repositories create my-simple-repo \
--repository-format=docker \
--location=us-central1Create a Workload Identity Pool
gcloud iam workload-identity-pools create "github-pool" --location="global"Create a Workload Identity Provider
gcloud iam workload-identity-pools providers create-oidc "my-repo-provider" \
--location="global" \
--workload-identity-pool="github-pool" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="attribute.repository_owner == 'alancj731'"Create a Service Account
gcloud iam service-accounts create "github-actions-sa" \
--display-name="GitHub Actions Service Account"gcloud iam workload-identity-pools list \
--project="plasma-set-485717-s0" \
--location="global"GitHub Actions Secrets and Variables
Set the following values in:
Repository → Settings → Secrets and variables → Actions
GCP_PROJECT_ID:
use this commmand to show your project id and number
gcloud projects listGCP_SA_EMAIL:
Format:
{SERVICE_ACCOUNT_NAME}@{PROJECT_ID}.iam.gserviceaccount.com
GCP_WORKLOAD_IDENTITY_PROVIDER:
Format:
projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{POOL_ID}/providers/{PROVIDER_ID}
You can also use this command to show your providers
gcloud iam workload-identity-pools providers list \
--location="global" \
--workload-identity-pool="YOUR_POOL_ID" \
--format="value(name)"GitHub Actions Workflow
File:.github/workflows/deployment.yaml
name: Deploy to GKE
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: "read"
id-token: "write"
steps:
- name: Checkout code
uses: actions/checkout@v4
# 1. Authenticate to Google Cloud
- id: "auth"
uses: "google-github-actions/auth@v2"
with:
workload_identity_provider: "${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}"
service_account: "${{ secrets.GCP_SA_EMAIL }}"
# 2. Get GKE Credentials
- name: Set up GKE credentials
uses: "google-github-actions/get-gke-credentials@v2"
with:
cluster_name: "medium-cost-cluster"
location: "us-central1-a"
# 3. Build and Push Image (Using Artifact Registry)
- name: Build and Push Docker Image
run: |
gcloud auth configure-docker us-central1-docker.pkg.dev
docker build -t us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/my-simple-repo/my-app:${{ github.sha }} .
docker push us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/my-simple-repo/my-app:${{ github.sha }}
# 4. Deploy to Cluster
- name: Deploy to GKE
run: |
# This replaces the 'IMAGE_PLACEHOLDER' in your deployment.yaml with the real image path
sed -i "s|IMAGE_PLACEHOLDER|us-central1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/my-simple-repo/my-app:${{ github.sha }}|g" k8s/deployment.yaml
kubectl apply -f k8s/Kubernetes Deployment
File: k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-web-app-service
spec:
replicas: 1
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: web-container
image: IMAGE_PLACEHOLDER # The CI/CD script will replace this
resources:
requests:
cpu: "10m" # Minimal reservation to fit on cheap nodes
memory: "32Mi"
limits:
cpu: "500m" # High limit so it doesn't get slow
memory: "256Mi" # High limit so it doesn't crash
---
apiVersion: v1
kind: Service
metadata:
name: my-web-app-service
spec:
selector:
app: hello-world
ports:
- protocol: TCP
port: 80
targetPort: 80
Kubernetes Service (LoadBalancer)
File: k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-web-app-service
annotations:
# This line tells GKE to look for a Standard Tier IP
cloud.google.com/network-tier: "Standard"
spec:
type: LoadBalancer
loadBalancerIP: "35.208.88.209"
selector:
app: hello-world
ports:
- protocol: TCP
port: 80
targetPort: 80Kubernetes Ingress
File: k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-web-app-ingress
annotations:
# Use the NAME of your GLOBAL static IP
kubernetes.io/ingress.global-static-ip-name: "my-cheap-ip"
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-web-app-service
port:
number: 80