GKE Integration — Service-to-Service Task Dispatch
Tại Sao Quan Trọng
Cloud Tasks targets không phải chỉ external endpoints. Bạn có thể dispatch tasks tới GKE services inside cluster:
Application → Cloud Tasks Queue → GKE Service
(create task) (dispatch) (handler pod)Lợi ích:
- Rate control — don't overwhelm GKE backend service
- Retry semantics — automatic retry, exponential backoff
- Deduplication — prevent duplicate work inside cluster
- Observability — Cloud Tasks logging, metrics
Architecture
Typical Pattern
┌──────────────────────────────────────────────────────────────┐
│ GCP Project │
├──────────────────────────────────────────────────────────────┤
│ │
│ Application Cloud Tasks GKE │
│ ┌─────────────────┐ ┌────────────┐ ┌──────────┐ │
│ │ │ │ │ │ │ │
│ │ API Handler │◄─────►│ Queue │──────►│ Service │ │
│ │ │ │ │ │ Handler │ │
│ │ CreateTask() │ │ │ │ │ │
│ │ │ │ │ └──────────┘ │
│ └─────────────────┘ └────────────┘ │
│ (http) (rate) (rate-limited)│
│ │
└──────────────────────────────────────────────────────────────┘Network Flow
1. Application (Cloud Run / App Engine)
→ CreateTask(url="https://gke-service.internal/webhook")
2. Cloud Tasks Queue (managed)
→ Waits for schedule_time
3. Dispatch Engine (Google-managed)
→ GET ID token for service account
→ Make HTTP request to GKE service
4. GKE Service (inside cluster)
→ Load Balancer routes to pod
→ Pod receives request (with auth headers)
→ Process task
5. Pod Response
→ Return 200-299 (success)
→ Cloud Tasks marks task completed
→ Task deletedSetup: Service Account & Workload Identity
Step 1: Create Kubernetes Service Account (KSA)
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: cloud-tasks-handler
namespace: defaultbash
kubectl apply -f sa.yamlStep 2: Create Google Service Account (GSA)
bash
gcloud iam service-accounts create gke-tasks-handler \
--project=my-project \
--display-name="GKE Tasks Handler"Step 3: Workload Identity Binding
bash
# Allow KSA to impersonate GSA
gcloud iam service-accounts add-iam-policy-binding \
gke-tasks-handler@my-project.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member=serviceAccount:my-project.svc.id.goog[default/cloud-tasks-handler]Step 4: Annotate KSA
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: cloud-tasks-handler
namespace: default
annotations:
iam.gke.io/gcp-service-account: gke-tasks-handler@my-project.iam.gserviceaccount.comStep 5: Configure Deployment
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: task-handler
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: task-handler
template:
metadata:
labels:
app: task-handler
spec:
serviceAccountName: cloud-tasks-handler
containers:
- name: handler
image: gcr.io/my-project/task-handler:v1
ports:
- containerPort: 8080Create Tasks Targeting GKE Service
Service Endpoint
bash
# Service inside GKE cluster
kubectl get svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP
# task-handler LoadBalancer 10.0.1.100 35.123.45.67For Cloud Tasks to reach it, service needs public IP (LoadBalancer type).
CreateTask Code
go
import cloudtasks "cloud.google.com/go/cloudtasks/apiv2"
func DispatchTaskToGKE(ctx context.Context, paymentID string) error {
client := cloudtasks.NewClient(ctx)
defer client.Close()
req := &taskspb.CreateTaskRequest{
Parent: fmt.Sprintf(
"projects/%s/locations/%s/queues/%s",
"my-project",
"us-central1",
"payment-tasks",
),
Task: &taskspb.Task{
HttpRequest: &taskspb.HttpRequest{
Url: "https://task-handler-gke-service.example.com/process",
HttpMethod: taskspb.HttpMethod_POST,
Headers: map[string]string{
"Content-Type": "application/json",
},
Body: []byte(fmt.Sprintf(`{"payment_id": "%s"}`, paymentID)),
OidcToken: &taskspb.OidcToken{
ServiceAccountEmail: "gke-tasks-handler@my-project.iam.gserviceaccount.com",
Audience: "https://task-handler-gke-service.example.com",
},
},
},
}
_, err := client.CreateTask(ctx, req)
return err
}Handler Implementation (GKE Pod)
Receive & Validate Task
go
package main
import (
"context"
"encoding/json"
"net/http"
"strings"
"google.golang.org/api/idtoken"
)
func TaskHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Extract auth token
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing authorization", http.StatusUnauthorized)
return
}
bearerToken := strings.TrimPrefix(authHeader, "Bearer ")
// Validate OIDC token
payload, err := idtoken.Validate(ctx, bearerToken, "https://task-handler-gke-service.example.com")
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Verify service account
if payload.Claims["email"] != "gke-tasks-handler@my-project.iam.gserviceaccount.com" {
http.Error(w, "Unauthorized service account", http.StatusForbidden)
return
}
// Extract task metadata
taskName := r.Header.Get("X-CloudTasks-TaskName")
retryCount := r.Header.Get("X-CloudTasks-TaskRetryCount")
// Parse payload
var taskReq struct {
PaymentID string `json:"payment_id"`
}
json.NewDecoder(r.Body).Decode(&taskReq)
// Log with context
log.Printf("Processing task: %s (retry: %s, payment_id: %s)", taskName, retryCount, taskReq.PaymentID)
// Process
err = processPayment(ctx, taskReq.PaymentID)
if err != nil {
log.Printf("Error processing payment: %v", err)
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
}
func main() {
http.HandleFunc("/process", TaskHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}Service-to-Service Patterns
Pattern 1: Request-Response with Rate Limiting
Service A (fast) Service B (slow)
Process 100 events Process 10 events/sec
↓
Create 100 tasks
↓
Cloud Tasks Queue (rate limit: 10 QPS)
↓
Dispatch 10/sec to Service BBenefit: Service B doesn't get overwhelmed by Service A spikes.
go
// Service A creates tasks
for i := 0; i < 100; i++ {
dispatchTaskToGKE(ctx, event{...})
}
// Service B handles 10 QPS (rate-limited)
// Service A doesn't need to know Service B capacityPattern 2: Workflow Orchestration
Order → Payment Task → Email Task → Inventory Task
A (rate-limited)
↓
Error?
↓
Retry Task
Each step is explicit task, can be rate-limited independentlygo
// Step 1: Charge payment
taskID := fmt.Sprintf("order-%s-charge", orderID)
dispatchTaskToGKE(ctx, "payment-service", taskID, orderData)
// Step 2 (executed after Step 1 completes): Send email
// Configured in Task 1's handler to create Task 2
func PaymentHandler(w http.ResponseWriter, r *http.Request) {
err := chargePayment()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
// Create next task
dispatchTaskToGKE(ctx, "email-service", taskID2, emailData)
w.WriteHeader(http.StatusOK)
}Pattern 3: Fanout from External Service
Cloud Function Cloud Tasks GKE Services
(HTTP trigger) (fan-out) (parallel)
↓
Image uploaded
↓
Create 3 tasks:
- Compress
- Generate thumbnail
- Index metadata
↓
Dispatch parallel
↓
Service 1, 2, 3 all runningNetwork Setup
GKE Service Exposure
yaml
# Internal Service (not exposed)
apiVersion: v1
kind: Service
metadata:
name: task-handler
spec:
type: ClusterIP # Only internal
ports:
- port: 8080
# OR: External Service (exposed to Cloud Tasks)
apiVersion: v1
kind: Service
metadata:
name: task-handler
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
annotations:
cloud.google.com/load-balancer-type: "External"Network Security
yaml
# Only allow traffic from Cloud Tasks
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-cloud-tasks
spec:
podSelector:
matchLabels:
app: task-handler
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: cloud-tasks-sources # Tag for Cloud Tasks IPs
ports:
- protocol: TCP
port: 8080Firewall Rules
bash
# Allow Cloud Tasks to reach GKE load balancer
gcloud compute firewall-rules create allow-cloud-tasks \
--direction=INGRESS \
--priority=1000 \
--source-ranges=0.0.0.0/0 \
--allow=tcp:443 \
--target-tags=gke-nodesTroubleshooting
Task Not Reaching Handler
Checklist:
1. Service has public LoadBalancer IP?
kubectl get svc task-handler
2. OIDC token valid?
Check service account setup
3. Network connectivity?
gcloud compute security-policies rules describe ...
4. Handler listening?
kubectl logs -f deployment/task-handlerHigh Latency
Likely causes:
1. Pod too slow → add replicas
2. Network latency (inter-zone) → use regional service
3. Rate limiting → check Cloud Tasks queue config
Solution:
- kubectl scale deployment task-handler --replicas=10
- kubectl get hpa (check autoscaling)
- Increase queue max_dispatches_per_secondAuthentication Failures
Errors:
"Invalid token" → OIDC token validation failed
→ Check idtoken.Validate logic
→ Verify audience matches
"Unauthorized service account" → Wrong GSA
→ Check service account email in request
→ Verify workload identity binding
Debug:
kubectl describe pod <pod-name>
→ Check if GOOGLE_APPLICATION_CREDENTIALS set
→ Check if workload identity properly configuredPerformance Considerations
Handler Capacity
GKE Node: 4 CPU, 16 GB RAM
Pod: 1 CPU, 2 GB RAM
Pod throughput:
- 10 simple requests/sec
- 5 complex requests/sec
Config Cloud Tasks:
max_dispatches_per_second: 5 (conservative)
max_concurrent_dispatches: 20 (buffer for latency)
max_burst_size: 5Scaling Handler
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: task-handler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: task-handler
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