Skip to main content

Overview

Google Kubernetes Engine (GKE) is a managed Kubernetes service that provides enterprise-grade features including Autopilot mode, Workload Identity, and tight integration with Google Cloud services.
This guide covers deploying to GKE with production-ready configuration including Workload Identity for secure service authentication, Cloud SQL for PostgreSQL, and Memorystore for Redis.

Prerequisites

  • Google Cloud account with billing enabled
  • gcloud CLI installed and configured
  • kubectl installed
  • Project with required APIs enabled

Enable Required APIs

## Set project
export PROJECT_ID=your-project-id
gcloud config set project $PROJECT_ID

## Enable APIs
gcloud services enable \
  container.googleapis.com \
  compute.googleapis.com \
  sql-component.googleapis.com \
  redis.googleapis.com \
  secretmanager.googleapis.com \
  cloudkms.googleapis.com \
  servicenetworking.googleapis.com

Create GKE Cluster

GKE Autopilot manages the cluster infrastructure for you:
## Create Autopilot cluster
gcloud container clusters create-auto langgraph-cluster \
  --region=us-central1 \
  --release-channel=regular \
  --enable-cloud-logging \
  --enable-cloud-monitoring \
  --workload-pool=$PROJECT_ID.svc.id.goog

## Get credentials
gcloud container clusters get-credentials langgraph-cluster \
  --region=us-central1

Standard Cluster

For more control over node configuration:
## Create standard cluster
gcloud container clusters create langgraph-cluster \
  --region=us-central1 \
  --num-nodes=3 \
  --machine-type=n2-standard-4 \
  --disk-size=50 \
  --disk-type=pd-ssd \
  --enable-autoscaling \
  --min-nodes=3 \
  --max-nodes=10 \
  --enable-autorepair \
  --enable-autoupgrade \
  --release-channel=regular \
  --workload-pool=$PROJECT_ID.svc.id.goog \
  --enable-ip-alias \
  --network=default \
  --subnetwork=default \
  --enable-cloud-logging \
  --enable-cloud-monitoring

## Get credentials
gcloud container clusters get-credentials langgraph-cluster \
  --region=us-central1

Setup Workload Identity

Workload Identity allows pods to authenticate as Google Cloud service accounts:
## Create Google Service Account
gcloud iam service-accounts create mcp-server-langgraph \
  --display-name="LangGraph Agent Service Account"

## Grant permissions
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:mcp-server-langgraph@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:mcp-server-langgraph@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/cloudsql.client"

## Create Kubernetes service account
kubectl create namespace mcp-server-langgraph

kubectl create serviceaccount mcp-server-langgraph \
  --namespace mcp-server-langgraph

## Bind Kubernetes SA to Google SA
gcloud iam service-accounts add-iam-policy-binding \
  mcp-server-langgraph@$PROJECT_ID.iam.gserviceaccount.com \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:$PROJECT_ID.svc.id.goog[mcp-server-langgraph/mcp-server-langgraph]"

kubectl annotate serviceaccount mcp-server-langgraph \
  --namespace mcp-server-langgraph \
  iam.gke.io/gcp-service-account=mcp-server-langgraph@$PROJECT_ID.iam.gserviceaccount.com

Cloud SQL for PostgreSQL

Create Cloud SQL Instance

## Create Cloud SQL instance
gcloud sql instances create langgraph-postgres \
  --database-version=POSTGRES_15 \
  --tier=db-custom-2-8192 \
  --region=us-central1 \
  --network=default \
  --enable-google-private-path \
  --backup-start-time=03:00 \
  --backup-location=us \
  --maintenance-window-day=SUN \
  --maintenance-window-hour=4 \
  --database-flags=max_connections=200

## Create databases (Keycloak, OpenFGA, GDPR compliance)
gcloud sql databases create keycloak \
  --instance=langgraph-postgres

gcloud sql databases create openfga \
  --instance=langgraph-postgres

## NEW: GDPR compliance database (ADR-0041)
gcloud sql databases create gdpr \
  --instance=langgraph-postgres

## Create users
gcloud sql users create keycloak \
  --instance=langgraph-postgres \
  --password=$(openssl rand -base64 32)

gcloud sql users create gdpr_user \
  --instance=langgraph-postgres \
  --password=$(openssl rand -base64 32)
Three databases required: Keycloak (identity), OpenFGA (authorization), and GDPR (compliance data storage per ADR-0041).

Initialize GDPR Schema

After creating the databases, initialize the GDPR schema:
## Get Cloud SQL instance connection name
INSTANCE_CONNECTION_NAME=$(gcloud sql instances describe langgraph-postgres \
  --format="value(connectionName)")

## Connect via Cloud SQL Proxy and apply schema
cloud_sql_proxy -instances=$INSTANCE_CONNECTION_NAME=tcp:5432 &
PROXY_PID=$!

## Apply GDPR schema (5 tables: user_profiles, user_preferences, consent_records, conversations, audit_logs)
PGPASSWORD=your-gdpr-password psql \
  -h 127.0.0.1 \
  -U gdpr_user \
  -d gdpr \
  -f deployments/base/postgres-gdpr-schema.sql

## Stop proxy
kill $PROXY_PID
Schema Details:
  • user_profiles: User profile data (GDPR Article 15, 16, 17)
  • user_preferences: User preferences (GDPR Article 16, 17)
  • consent_records: Consent audit trail, 7-year retention (GDPR Article 21, Article 7)
  • conversations: Conversation history, 90-day retention (GDPR Article 15, 20)
  • audit_logs: Compliance audit trail, 7-year retention (HIPAA §164.316(b)(2)(i), SOC2 CC6.6)
See GDPR Storage Configuration for retention policies and backup procedures.

Configure Cloud SQL Proxy

Option 1: Cloud SQL Auth Proxy Sidecar
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  namespace: mcp-server-langgraph
spec:
  template:
    spec:
      serviceAccountName: mcp-server-langgraph
      containers:
      - name: keycloak
        image: quay.io/keycloak/keycloak:23.0
        env:
        - name: KC_DB
          value: postgres
        - name: KC_DB_URL
          value: jdbc:postgresql://127.0.0.1:5432/keycloak
        - name: KC_DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: cloudsql-db-credentials
              key: username
        - name: KC_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: cloudsql-db-credentials
              key: password

      # Cloud SQL Proxy sidecar
      - name: cloud-sql-proxy
        image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.8.0
        args:
        - "--structured-logs"
        - "--port=5432"
        - "$PROJECT_ID:us-central1:langgraph-postgres"
        securityContext:
          runAsNonRoot: true
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
Option 2: Private IP (Recommended for Production)
## Enable private IP for Cloud SQL
gcloud sql instances patch langgraph-postgres \
  --network=projects/$PROJECT_ID/global/networks/default \
  --enable-google-private-path

## Get private IP
CLOUDSQL_PRIVATE_IP=$(gcloud sql instances describe langgraph-postgres \
  --format='get(ipAddresses[0].ipAddress)')

## Use private IP directly
KC_DB_URL=jdbc:postgresql://$CLOUDSQL_PRIVATE_IP:5432/keycloak

Memorystore for Redis

Create Redis Instance

## Create Redis instance
gcloud redis instances create langgraph-redis \
  --size=5 \
  --region=us-central1 \
  --tier=standard \
  --redis-version=redis_7_0 \
  --network=default \
  --connect-mode=private-service-access \
  --enable-auth

## Get Redis host and auth string
REDIS_HOST=$(gcloud redis instances describe langgraph-redis \
  --region=us-central1 \
  --format='get(host)')

REDIS_AUTH=$(gcloud redis instances get-auth-string langgraph-redis \
  --region=us-central1)

## Create secret
kubectl create secret generic redis-credentials \
  --namespace=mcp-server-langgraph \
  --from-literal=host=$REDIS_HOST \
  --from-literal=password=$REDIS_AUTH

Configure Application

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
spec:
  template:
    spec:
      containers:
      - name: agent
        env:
        - name: REDIS_URL
          value: redis://:$(REDIS_PASSWORD)@$(REDIS_HOST):6379
        - name: REDIS_HOST
          valueFrom:
            secretKeyRef:
              name: redis-credentials
              key: host
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: redis-credentials
              key: password

Secret Manager Integration

Store Secrets in Secret Manager

## Create secrets
echo -n "sk-ant-your-key" | gcloud secrets create anthropic-api-key \
  --data-file=- \
  --replication-policy=automatic

echo -n "your-jwt-secret" | gcloud secrets create jwt-secret \
  --data-file=- \
  --replication-policy=automatic

## Grant access to service account
gcloud secrets add-iam-policy-binding anthropic-api-key \
  --member="serviceAccount:mcp-server-langgraph@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"

Use External Secrets Operator

## Install External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets \
  external-secrets/external-secrets \
  --namespace external-secrets-system \
  --create-namespace

## Create SecretStore
cat <<'EOF' | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: gcpsm-secret-store
  namespace: mcp-server-langgraph
spec:
  provider:
    gcpsm:
      projectID: "$PROJECT_ID"
      auth:
        workloadIdentity:
          clusterLocation: us-central1
          clusterName: langgraph-cluster
          serviceAccountRef:
            name: mcp-server-langgraph
EOF

## Create ExternalSecret
cat <<'EOF' | kubectl apply -f -
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: langgraph-secrets
  namespace: mcp-server-langgraph
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: gcpsm-secret-store
    kind: SecretStore
  target:
    name: mcp-server-langgraph-secrets
    creationPolicy: Owner
  data:
  - secretKey: ANTHROPIC_API_KEY
    remoteRef:
      key: anthropic-api-key
  - secretKey: JWT_SECRET
    remoteRef:
      key: jwt-secret
EOF

Google Container Registry (GCR)

Build and Push Images

## Configure Docker for GCR
gcloud auth configure-docker

## Build image
docker build -t gcr.io/$PROJECT_ID/mcp-server-langgraph:latest .

## Push to GCR
docker push gcr.io/$PROJECT_ID/mcp-server-langgraph:latest

## Use in Kubernetes
cat << 'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
spec:
  template:
    spec:
      containers:
      - name: agent
        image: gcr.io/$PROJECT_ID/mcp-server-langgraph:latest
        imagePullPolicy: Always
EOF
## Create Artifact Registry repository
gcloud artifacts repositories create langgraph \
  --repository-format=docker \
  --location=us-central1 \
  --description="LangGraph Agent images"

## Configure Docker
gcloud auth configure-docker us-central1-docker.pkg.dev

## Build and push
docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/langgraph/agent:latest .
docker push us-central1-docker.pkg.dev/$PROJECT_ID/langgraph/agent:latest

Load Balancer and Ingress

Install NGINX Ingress Controller

## Install NGINX Ingress
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.service.type=LoadBalancer

Configure Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: langgraph-ingress
  namespace: mcp-server-langgraph
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - api.yourdomain.com
    secretName: langgraph-tls
  rules:
  - host: api.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: mcp-server-langgraph
            port:
              number: 8000

Google Cloud Load Balancer

For GCP-native load balancing:
apiVersion: v1
kind: Service
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
  annotations:
    cloud.google.com/neg: '{"ingress": true}'
    cloud.google.com/backend-config: '{"default": "langgraph-backend-config"}'
spec:
  type: LoadBalancer
  loadBalancerIP: 34.xxx.xxx.xxx  # Reserve static IP
  ports:
  - port: 80
    targetPort: 8000
    protocol: TCP
  selector:
    app: mcp-server-langgraph
---
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: langgraph-backend-config
  namespace: mcp-server-langgraph
spec:
  healthCheck:
    checkIntervalSec: 15
    port: 8000
    type: HTTP
    requestPath: /health/ready
  timeoutSec: 30
  connectionDraining:
    drainingTimeoutSec: 60
  cdn:
    enabled: false

Monitoring with Cloud Operations

Enable Google Cloud Monitoring

## Install Google Cloud Ops agent
kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.8.0/manifests/setup.yaml

kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/prometheus-engine/v0.8.0/manifests/operator.yaml

Configure Pod Monitoring

apiVersion: monitoring.googleapis.com/v1
kind: PodMonitoring
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
spec:
  selector:
    matchLabels:
      app: mcp-server-langgraph
  endpoints:
  - port: metrics
    interval: 30s

Cloud Logging

Configure structured logging to Cloud Logging:
## Install dependencies
# uv add google-cloud-logging

## Configure logging
import google.cloud.logging
from google.cloud.logging.handlers import CloudLoggingHandler

client = google.cloud.logging.Client()
handler = CloudLoggingHandler(client)

## Use with structlog
import structlog

structlog.configure(
    processors=[
        structlog.processors.JSONRenderer()
    ],
    wrapper_class=structlog.stdlib.BoundLogger,
    logger_factory=structlog.stdlib.LoggerFactory(),
)

Auto-Scaling

Horizontal Pod Autoscaler

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: mcp-server-langgraph
  namespace: mcp-server-langgraph
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: mcp-server-langgraph
  minReplicas: 3
  maxReplicas: 50
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 30
      - type: Pods
        value: 5
        periodSeconds: 60
      selectPolicy: Max

Cluster Autoscaler

GKE Autopilot handles node scaling automatically. For Standard clusters:
## Enable cluster autoscaling
gcloud container clusters update langgraph-cluster \
  --enable-autoscaling \
  --min-nodes=3 \
  --max-nodes=20 \
  --region=us-central1

Complete Deployment

## Clone repository
git clone https://github.com/vishnu2kmohan/mcp-server-langgraph
cd mcp-server-langgraph

## Build and push image
docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/langgraph/agent:v1.0.0 .
docker push us-central1-docker.pkg.dev/$PROJECT_ID/langgraph/agent:v1.0.0

## Create namespace
kubectl create namespace mcp-server-langgraph

## Deploy with Helm
helm install mcp-server-langgraph ./helm/mcp-server-langgraph \
  --namespace mcp-server-langgraph \
  --set image.repository=us-central1-docker.pkg.dev/$PROJECT_ID/langgraph/agent \
  --set image.tag=v1.0.0 \
  --set workloadIdentity.enabled=true \
  --set workloadIdentity.serviceAccount=mcp-server-langgraph@$PROJECT_ID.iam.gserviceaccount.com \
  --set cloudsql.instance=$PROJECT_ID:us-central1:langgraph-postgres \
  --set redis.host=$REDIS_HOST

## Verify deployment
kubectl get pods -n mcp-server-langgraph
kubectl get svc -n mcp-server-langgraph
kubectl get ingress -n mcp-server-langgraph

Cost Optimization

GKE Autopilot optimizes resource usage:
  • Pay only for pod resources
  • Automatic resource optimization
  • No node management overhead
  • Built-in security best practices
For non-production workloads:
gcloud container node-pools create preemptible-pool \
  --cluster=langgraph-cluster \
  --region=us-central1 \
  --preemptible \
  --num-nodes=3 \
  --machine-type=n2-standard-4
Use node affinity to schedule on preemptible nodes:
affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 100
      preference:
        matchExpressions:
        - key: cloud.google.com/gke-preemptible
          operator: In
          values:
          - "true"
Use GKE usage metering to analyze resource usage:
# Enable usage metering
gcloud container clusters update langgraph-cluster \
  --resource-usage-bigquery-dataset=cluster_usage \
  --enable-network-egress-metering \
  --enable-resource-consumption-metering \
  --region=us-central1
For production workloads:
  • 1-year commitment: 37% discount
  • 3-year commitment: 55% discount
gcloud compute commitments create \
  --plan=12-month \
  --resources=vcpu=100,memory=400GB \
  --region=us-central1

Security Best Practices

gcloud container clusters update langgraph-cluster \
  --enable-shielded-nodes \
  --region=us-central1
Ensure only trusted images are deployed:
# Enable Binary Authorization
gcloud container clusters update langgraph-cluster \
  --enable-binauthz \
  --region=us-central1
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-agent-egress
  namespace: mcp-server-langgraph
spec:
  podSelector:
    matchLabels:
      app: mcp-server-langgraph
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: keycloak
    ports:
    - protocol: TCP
      port: 8080
  - to:
    - podSelector:
        matchLabels:
          app: openfga
    ports:
    - protocol: TCP
      port: 8080

Troubleshooting

Check connectivity:
# Test Cloud SQL Proxy
kubectl run -it --rm debug \
  --image=gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest \
  --restart=Never \
  -- $PROJECT_ID:us-central1:langgraph-postgres
Verify setup:
# Check annotation
kubectl describe sa mcp-server-langgraph -n mcp-server-langgraph

# Test from pod
kubectl run -it --rm test \
  --image=google/cloud-sdk:slim \
  --serviceaccount=mcp-server-langgraph \
  --namespace=mcp-server-langgraph \
  -- gcloud auth list

Next Steps


GKE Deployment Ready: Production-grade deployment on Google Kubernetes Engine!