อัปเดตล่าสุด Feb 2026

GitLab Runner Autoscaling on Kubernetes

ปรับขนาด GitLab Runner อัตโนมัติบน Kubernetes ด้วย HPA

คู่มือฉบับสมบูรณ์สำหรับตั้งค่า GitLab Runner ปรับขนาดอัตโนมัติ ลดต้นทุน CI/CD และเพิ่มประสิทธิภาพการรัน jobs

GitLab
Kubernetes
DevOps

1. บทนำ

GitLab Runner คือเครื่องมือที่รัน jobs จาก GitLab CI/CD pipeline โดยปกติแล้วเราต้องตั้งค่า Runner ด้วยตนเองและจัดการInfrastructure (VM, Container) เอง แต่เมื่อผสมผสานกับ Kubernetes Autoscaling จะทำให้ GitLab Runner สามารถปรับขนาดได้อัตโนมัติตาม workload ที่เข้ามา

ข้อดีของ GitLab Runner Autoscaling on Kubernetes:

  • ลดต้นทุน: เริ่มต้นด้วย runner 0 ตัว แล้วเพิ่มขึ้นเมื่อมี jobs เข้ามา
  • ประสิทธิภาพ: รองรับ job spikes อัตโนมัติโดยไม่ต้องกังวลว่าจะ runner จะหมด
  • High Availability: Kubernetes ดูแล runner ให้ automatically
  • Isolation: แต่ละ job รันใน pod แยก ปลอดภัยจาก side effects

Architecture (Architecture)

GitLab Server GitLab CI/CD Pipeline Trigger Jobs GitLab Runner Controller (GitLab Executor) HPA Controller Horizontal Pod Autoscaler Kubernetes Cluster Runner Pod Job #1 Runner Pod Job #2 Pending Job #3 Scale Up/Down

2. สิ่งที่ต้องเตรียม (Prerequisites)

Kubernetes Cluster

  • Kubernetes cluster ที่ operational
  • Kubectl ติดตั้ง และเชื่อมต่อได้
  • Cluster ต้องรองรับ Persistent Volumes (สำหรับ caching)

GitLab & Helm

  • GitLab self-hosted หรือ GitLab.com
  • Helm 3.x ติดตั้งแล้ว
  • GitLab Personal Access Token (PAT) หรือ CI/CD token

GitLab Runner Requirements

  • GitLab Runner chart version 0.65.0+ (รองรับ autoscaling)
  • GitLab Runner Kubernetes executor
  • Service account with proper RBAC permissions

Infrastructure

  • Storage class สำหรับ caching (Redis/MinIO หรือ using persistent volumes)
  • Network policies ที่อนุญาตให้ runner ติดต่อกับ Kubernetes API
  • Load balancer (ถ้าใช้ GitLab self-hosted)

3. การติดตั้ง GitLab Runner พร้อม Kubernetes Autoscaling

มี 2 วิธีในการติดตั้ง GitLab Runner บน Kubernetes:

  • วิธีที่ 1: ใช้ Helm chart โดยตรง (แนะนำ)
  • วิธีที่ 2: ใช้ GitLab Kubernetes Agent (สำหรับ GitLab 14.0+)

วิธีที่ 1: ติดตั้งด้วย Helm Chart (แนะนำ)

ขั้นตอนที่ 1: เพิ่ม GitLab Helm Repository

Install GitLab Runner with Helm
# เพิ่ม GitLab Helm repository
helm repo add gitlab https://charts.gitlab.io
helm repo update

# ตรวจสอบเวอร์ชันล่าสุด
helm search repo gitlab/gitlab-runner

ขั้นตอนที่ 2: สร้าง values.yaml สำหรับ autoscaling

values.yaml
# values.yaml - GitLab Runner dengan Kubernetes Autoscaling
gitlabUrl: https://gitlab.example.com/
runnerRegistrationToken: "YOUR_RUNNER_TOKEN"

runnerConfig: |
  [[runners]]
    name = "kubernetes-runner"
    url = "https://gitlab.example.com/"
    token = "TOKEN"
    executor = "kubernetes"
    [runners.kubernetes]
      namespace = "gitlab-runners"
      image = "alpine:latest"
      [runners.kubernetes.pod_security_context]
        run_as_non_root = true
      [runners.kubernetes.env]
        RUNNER_UPDATE_NAME = "true"
      [runners.kubernetes.volumes]
        { name = "cache", mount_path = "/cache", type = "persistent_volume_claim", claim_name = "gitlab-runner-cache" }
      [runners.kubernetes.node_selector]
        kubernetes.io/os = "linux"

    # Autoscaling configuration
    [runners.autoscale]
      replicas = 5
      min = 0
      max = 10
      time_window = "90s"
      scale_down_delay = "30s"
      scale_down_unneeded = "2m"
      scale_down_running = "2m"

[[runners.kubernetes.services]]
  name = "redis"
  alias = "redis-cache"

ขั้นตอนที่ 3: สร้าง Persistent Volume Claim สำหรับ Cache

pv-pvc.yaml
# Create PVC for caching
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlab-runner-cache
  namespace: gitlab-runners
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: standard
  resources:
    requests:
      storage: 10Gi

ขั้นตอนที่ 4: ติดตั้งด้วย Helm

Helm Install Command
# สร้าง namespace
kubectl create namespace gitlab-runners

# Deploy GitLab Runner
helm install gitlab-runner gitlab/gitlab-runner   --namespace gitlab-runners   -f values.yaml   --wait   --timeout 5m
คำอธิบาย Configuration
  • replicas = 5 - จำนวน runner pods ที่จะสร้างเริ่มต้น
  • min = 0 - จำนวน minimum runners (0 หมายถึง scale down ได้ถึง 0)
  • max = 10 - จำนวน maximum runners สูงสุด
  • time_window = "90s" - ช่วงเวลาที่จะตรวจสอบปริมาณ jobs ในการตัดสินใจ scale up
  • scale_down_delay = "30s" - ความล่าช้าก่อน scale down
  • scale_down_unneeded = "2m" - ระยะเวลาที่ runner ไม่ได้ใช้งานก่อน scale down

วิธีที่ 2: ใช้ GitLab Kubernetes Agent

Install via GitLab Kubernetes Agent
# ขั้นตอน
# 1. เปิด GitLab Project → Settings → CI/CD → Kubernetes
# 2. Add Kubernetes cluster → Install GitLab Agent
# 3. Select your cluster
# 4. Agent will be installed with autoscaling enabled by default

วิธีนี้ง่ายกว่าและรองรับ gitOps workflows ได้ดีกว่า โดย GitLab จะจัดการ runner lifecycle ให้โดยอัตโนมัติพร้อม autoscaling

โครงสร้างหลังติดตั้ง

GitLab CI/CD Push Event GitLab Runner Controller ServiceAccount HPA (Horizontal Pod) Autoscaler Kubernetes API Kubernetes Nodes Runner Pod #1 Job A Runner Pod #2 Job B Runner Pod #3 Job C

4. การปรับขนาดอัตโนมัติ (HPA Configuration)

GitLab Runner autoscaling ใช้ HPA (Horizontal Pod Autoscaler) ในการปรับขนาด runner pods ตามจำนวน jobs ที่ค้างอยู่ใน queue

HPA Auto-scale Logic:

Scale Logic
# Scale-up conditions
scale_up:
  if queue_depth > (replicas * 2):  # ถ้า jobs ในคิว > 2x runners
    create new runner pod

# Scale-down conditions  
scale_down:
  if runner_idle_time > 2m:  # runner idle > 2 นาที
    terminate runner pod
Scale Timings
  • scale_up_delay: 10-30 วินาที (รอให้ jobs ค้างก่อน scale up)
  • scale_down_delay: 2-5 นาที (รอให้ jobs จบก่อน scale down)
  • time_window: 90 วินาที (ตรวจสอบ jobs ที่เข้ามาในช่วงนี้)

ตัวอย่าง HPA YAML Manual

hpa.yaml
# ตัวอย่าง HPA configuration
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: gitlab-runner
  namespace: gitlab-runners
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gitlab-runner-gitlab-runner
  minReplicas: 0
  maxReplicas: 10
  
  # Scale based on GitLab queue depth
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
      policies:
        - type: Percent
          value: 100
          periodSeconds: 30
        - type: Pods
          value: 2
          periodSeconds: 30
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
        - type: Pods
          value: 1
          periodSeconds: 60
      selectPolicy: Min

กระบวนการ Autoscaling

Start Job Queued Monitor Queue Depth Decision Scale Up? Yes No Create Runner Pod Start Job Jobs Running Runner Pod Job #1, #2, #3 Idle? (2 min) Scale Down Delete Runner Pod Clean Up Resources End Pod Deleted

5. การปรับแต่งประสิทธิภาพ (Performance Optimization)

1. Custom Cache Configuration

GitLab Runner รองรับหลาย types of cache:

S3 Cache

ใช้ S3-compatible storage (MinIO, AWS S3) สำหรับ cache ที่ shared ระหว่าง runners

Google Cloud Storage

สำหรับโปรเจคที่ host บน GCP ใช้ GCS สำหรับ cache

NFS Cache

For shared cache across runners using NFS

In-Memory Cache

Redis for low-latency cache access

2. Optimized Pod Template

runner-pod-template.yaml
# Optimized Pod Template
apiVersion: v1
kind: Pod
metadata:
  name: gitlab-runner-pod-template
  namespace: gitlab-runners
  labels:
    app: gitlab-runner
spec:
  # Security Context
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 1000
  
  # Prefer low-latency nodes
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: node-type
            operator: In
            values: ["compute-optimized"]
  
  # Containers
  containers:
  - name: gitlab-runner
    image: gitlab/gitlab-runner:latest
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
    
    # Resource limits
    resources:
      requests:
        memory: "256Mi"
        cpu: "100m"
      limits:
        memory: "2Gi"
        cpu: "1000m"
    
    # Environment
    env:
    - name:-ci-runner
      value: "true"
    
    # Volume mounts
    volumeMounts:
    - name: cache
      mountPath: /cache
    - name: config
      mountPath: /etc/gitlab-runner
      readOnly: true
  
  # Volumes
  volumes:
  - name: cache
    emptyDir: {}
  - name: config
    configMap:
      name: gitlab-runner-config

3. Advanced Autoscaling Settings

advanced-hpa.yaml
# Advanced HPA with custom metrics
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: gitlab-runner-advanced
  namespace: gitlab-runners
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: gitlab-runner-gitlab-runner
  minReplicas: 0
  maxReplicas: 20
  
  behavior:
    # Fast scale-up for burst jobs
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Pods
        value: 4
        periodSeconds: 60
      - type: Percent
        value: 100
        periodSeconds: 60
      selectPolicy: Max
    
    # Conservative scale-down
    scaleDown:
      stabilizationWindowSeconds: 300  # 5 minutes
      policies:
      - type: Percent
        value: 25
        periodSeconds: 60
      - type: Pods
        value: 2
        periodSeconds: 60
      selectPolicy: Min
  
  # Metrics based on GitLab job queue
  metrics:
  - type: Pods
    pods:
      metric:
        name: gitlab_jobs_pending
      target:
        type: AverageValue
        averageValue: "1"

6. ตัวอย่างโค้ด (Code Examples)

1. GitLab CI/CD Pipeline ตัวอย่าง

.gitlab-ci.yml
# .gitlab-ci.yml - Pipeline ตัวอย่างสำหรับใช้กับ Autoscaling Runner
stages:
  - build
  - test
  - security
  - deploy

variables:
  # Use Helm to deploy
  DEPLOYMENT_STRATEGY: helm
  # Enable debug logging
  CI_DEBUG_TRACE: "false"
  # Cache settings
  CACHE_KEY: "\${CI_COMMIT_REF_NAME}"
  DOCKER_TLS_CERTDIR: "/certs"

# Build stage
build:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind
  script:
    - docker build -t \${CI_REGISTRY_IMAGE}:\${CI_COMMIT_SHA} .
    - docker push \${CI_REGISTRY_IMAGE}:\${CI_COMMIT_SHA}
  artifacts:
    paths:
      - docker-image.tar
    expire_in: 1 hour

# Test stage
test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test
  cache:
    key: "\${CI_COMMIT_REF_NAME}"
    paths:
      - node_modules/
      - .npm/

# Security scan
security-scan:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy image \${CI_REGISTRY_IMAGE}:\${CI_COMMIT_SHA}
  allow_failure: true

# Deploy to Kubernetes
deploy:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/my-app my-app=\${CI_REGISTRY_IMAGE}:\${CI_COMMIT_SHA}
    - kubectl rollout status deployment/my-app
  environment:
    name: production
    url: https://app.example.com
  only:
    - main

2. GitLab Runner Deployment YAML

runner-deployment.yaml
# GitLab Runner Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab-runner
  namespace: gitlab-runners
  labels:
    app: gitlab-runner
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gitlab-runner
  template:
    metadata:
      labels:
        app: gitlab-runner
    spec:
      serviceAccountName: gitlab-runner
      securityContext:
        runAsNonRoot: true
        fsGroup: 65534
      
      containers:
      - name: gitlab-runner
        image: gitlab/gitlab-runner:latest
        securityContext:
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
        env:
        - name: CI_SERVER_URL
          value: "https://gitlab.example.com/"
        - name: REGISTRATION_TOKEN
          valueFrom:
            secretKeyRef:
              name: gitlab-runner-token
              key: registration-token
        - name: RUNNER_EXECUTOR
          value: "kubernetes"
        resources:
          requests:
            cpu: "100m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "1Gi"
        volumeMounts:
        - name: config
          mountPath: /etc/gitlab-runner
        - name: cache
          mountPath: /cache
        - name: docker-config
          mountPath: /certs/client
      volumes:
      - name: config
        configMap:
          name: gitlab-runner-config
      - name: cache
        emptyDir: {}
      - name: docker-config
        emptyDir: {}

3. Common Jobs Troubleshooting

troubleshooting-examples.yaml
# ตัวอย่าง Jobs ที่พบบ่อยและวิธีแก้ไข

# ปัญหา: Runner ไม่ scale up
# แก้ไข: เพิ่ม time_window และ scale_up_delay
[runners.autoscale]
  time_window = "120s"  # เพิ่มเวลาตรวจสอบ jobs
  scale_up_delay = "30s"  # Delay ก่อน scale up

# ปัญหา: Jobs ค้างใน queue
# แก้ไข: ตรวจสอบ resources ของ runner pods
limits:
  memory: "2Gi"
  cpu: "1000m"
requests:
  memory: "512Mi"
  cpu: "500m"

# ปัญหา: Cache miss บ่อย
# แก้ไข: ใช้ S3 cache แทน default
[runners.cache]
  Type = "s3"
  Path = "gitlab-runner/cache"
  Shared = true
  [runners.cache.s3]
    ServerAddress = "s3.amazonaws.com"
    AccessKey = "YOUR_ACCESS_KEY"
    SecretKey = "YOUR_SECRET_KEY"
    BucketName = "gitlab-cache"

7. แก้ไขปัญหาที่พบบ่อย (Troubleshooting)

ปัญหา: Runner ไม่ scale up

Symptoms: Jobs ค้างใน queue แต่ runner pods ไม่เพิ่มขึ้น

Root Cause:

  • HPA configuration ผิด
  • scale_up_delay ตั้งค่าสูงเกินไป
  • max ต่ำเกินไป

Solution:

  • ตรวจสอบ HPA: kubectl get hpa -n gitlab-runners
  • ลด scale_up_delay: scale_up_delay = "10s"
  • เพิ่ม max runners: max = 20

ปัญหา: Jobs ค้างใน queue

Symptoms: Jobs ไม่รันแม้จะมี runner อยู่

Root Cause:

  • Runner pods ไม่พร้อมใช้งาน (CrashLoopBackOff)
  • RBAC permissions ผิด
  • Kubernetes executor configuration ผิด

Solution:

  • ตรวจสอบ logs: kubectl logs -n gitlab-runners -l app=gitlab-runner
  • ตรวจสอบ pods: kubectl get pods -n gitlab-runners
  • ตรวจสอบ RBAC: kubectl auth can-i create pods --as=system:serviceaccount:gitlab-runners:gitlab-runner

ปัญหา: Out of Memory

Symptoms: Runner pods CrashLoopBackOff หรือ OOMKilled

Root Cause:

  • Resource limits ต่ำเกินไป
  • Jobs ต้องการ memory มาก
  • No memory request ตั้งค่า

Solution:

  • เพิ่ม memory requests: requests.memory: "512Mi"
  • เพิ่ม memory limits: limits.memory: "2Gi"
  • ใช้ smaller image: image: alpine:latest

ปัญหา: Connection timeout

Symptoms: Runner ไม่สามารถเชื่อมต่อ GitLab server

Root Cause:

  • Network policies บล็อค traffic
  • Firewall rules
  • DNS resolution issues

Solution:

  • ตรวจสอบ network policy: kubectl get networkpolicy -n gitlab-runners
  • ตรวจสอบ connectivity: kubectl run test --image=curlimages/curl -it --rm -- curl -v https://gitlab.example.com
  • ตรวจสอบ DNS: kubectl exec -it -n gitlab-runners -l app=gitlab-runner -- nslookup gitlab.example.com

Diagnostic Commands

troubleshooting-commands.sh
# 1. Check HPA status
kubectl get hpa -n gitlab-runners
kubectl describe hpa gitlab-runner -n gitlab-runners

# 2. Check runner pods
kubectl get pods -n gitlab-runners -l app=gitlab-runner
kubectl logs -n gitlab-runners -l app=gitlab-runner --tail=100

# 3. Check deployments
kubectl get deployments -n gitlab-runners
kubectl describe deployment gitlab-runner -n gitlab-runners

# 4. Check services
kubectl get services -n gitlab-runners
kubectl describe service gitlab-runner -n gitlab-runners

# 5. Check secrets
kubectl get secrets -n gitlab-runners
kubectl get secret gitlab-runner-token -n gitlab-runners -o yaml

# 6. Test connectivity
kubectl run test-runner --image=alpine:latest -it --rm --   wget -qO- https://gitlab.example.com/api/v4/version

# 7. Check events
kubectl get events -n gitlab-runners --sort-by='.lastTimestamp'

# 8. Check PVC
kubectl get pvc -n gitlab-runners
kubectl describe pvc gitlab-runner-cache -n gitlab-runners