DevOps

Kubernetes in 2026: Autoscaling, HPA, and Resource Management for Go Services

Ruslan Ismailov Published 14 min read
K

Introduction: Why Go Services Need Proper Resource Management in Kubernetes

Go has become the de facto language for building high-performance microservices. Its compact runtime, low memory footprint, and fast startup make Go applications ideal candidates for orchestration in Kubernetes. However, this very "lightness" of Go often becomes a trap: developers underestimate the importance of correct resource configuration, leaving the cluster either overloaded or idle with over-provisioned CPU and memory.

In 2026, Kubernetes continues to evolve: the Gateway API has reached stable status, Sidecar Containers have gone GA, and autoscaling capabilities have grown significantly richer. In this article, we walk through the full cycle — from setting proper requests/limits to configuring HPA with Prometheus metrics and choosing a deployment strategy — with concrete manifests for a typical Go REST API.

Configuring Requests and Limits for Go Applications: Common Mistakes and Best Practices

Kubernetes uses two parameters for pod resource management: requests (guaranteed resources that affect scheduling) and limits (a hard ceiling). For Go services, it is critical to understand how the runtime manages memory and goroutines.

Common Mistakes

  • High limits with low requests. The scheduler places a pod on a node based on requests, but under peak load the pod consumes up to its limits — this causes resource contention and instability for neighboring pods.
  • No memory limits. Go's garbage collector can temporarily retain large amounts of memory. Without a limit, a pod will consume all available memory on the node.
  • CPU limits with multi-core goroutines. A hard CPU limit causes CPU throttling — the Go runtime cannot schedule goroutines efficiently. In 2026, it is recommended to use cpuThrottlingPercent from cAdvisor metrics to monitor this issue.
  • Ignoring GOMAXPROCS. By default, Go sees all vCPUs on the node, not just the allocated ones. Use the go.uber.org/automaxprocs library to align GOMAXPROCS with the CPU limit.

Best Practices

  • Start with real load tests (k6, vegeta) and collect baseline metrics via pprof.
  • Set requests to approximately 70–80% of average consumption under load, and limits to approximately 2× requests for CPU and 1.5× for memory.
  • Use the Burstable QoS class for most Go services and Guaranteed for latency-sensitive components.
resources:\n  requests:\n    cpu: "250m"\n    memory: "128Mi"\n  limits:\n    cpu: "500m"\n    memory: "256Mi"

Horizontal Pod Autoscaler: How It Works, CPU Metrics, and Custom Metrics

HPA (Horizontal Pod Autoscaler) is the primary autoscaling tool in Kubernetes for Go microservices. It periodically polls the Metrics Server (or an external adapter) and adjusts the number of Deployment replicas according to target metrics.

How the HPA Algorithm Works

The HPA controller calculates the desired replica count every 15 seconds (by default) using the formula:

desiredReplicas = ceil(currentReplicas × (currentMetricValue / desiredMetricValue))

To prevent flapping, stabilizationWindowSeconds is used — a stabilization window (default: 300 seconds for scale-down, 0 for scale-up).

HPA Based on CPU

A basic HPA v2 manifest for a Go REST API:

apiVersion: autoscaling/v2\nkind: HorizontalPodAutoscaler\nmetadata:\n  name: go-api-hpa\n  namespace: production\nspec:\n  scaleTargetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: go-api\n  minReplicas: 2\n  maxReplicas: 20\n  metrics:\n    - type: Resource\n      resource:\n        name: cpu\n        target:\n          type: Utilization\n          averageUtilization: 60\n  behavior:\n    scaleDown:\n      stabilizationWindowSeconds: 120\n      policies:\n        - type: Percent\n          value: 25\n          periodSeconds: 60\n    scaleUp:\n      stabilizationWindowSeconds: 0\n      policies:\n        - type: Pods\n          value: 4\n          periodSeconds: 30

Custom Metrics via Prometheus

For Go services, it is often more useful to scale based on business metrics: RPS, queue depth, p99 latency. The stack is: Prometheusprometheus-adapterCustom Metrics API.

Example of exporting a metric in Go:

var httpRequestsTotal = prometheus.NewCounterVec(\n    prometheus.CounterOpts{\n        Name: "http_requests_total",\n        Help: "Total HTTP requests",\n    },\n    []string{"method", "path", "status"},\n)\n\nfunc init() {\n    prometheus.MustRegister(httpRequestsTotal)\n}

prometheus-adapter configuration for exposing the RPS metric:

rules:\n  - seriesQuery: 'http_requests_total{namespace!="",pod!=""}'\n    resources:\n      overrides:\n        namespace: {resource: "namespace"}\n        pod: {resource: "pod"}\n    name:\n      matches: "http_requests_total"\n      as: "http_requests_per_second"\n    metricsQuery: 'rate(http_requests_total{<<.LabelMatchers>>}[2m])'

HPA based on a custom metric:

metrics:\n  - type: Pods\n    pods:\n      metric:\n        name: http_requests_per_second\n      target:\n        type: AverageValue\n        averageValue: "500"

Vertical Pod Autoscaler and When to Use It Instead of HPA

VPA (Vertical Pod Autoscaler) automatically recommends or sets optimal requests/limits based on historical consumption. Unlike HPA, it does not add pods — it adjusts the resources of existing ones.

VPA Operating Modes

  • Off — recommendations only, no automatic changes. Ideal for initial profiling.
  • Initial — sets resources only when a pod is created.
  • Auto — recreates pods with new resources (causes downtime; incompatible with PodDisruptionBudget when only 1 replica exists).

When to Use VPA for Go Services

  • Batch jobs and CronJobs with unpredictable memory consumption.
  • Services with monotonically growing memory usage (leaks, caches).
  • Initial stage: use VPA in Off mode to collect recommendations, then hard-code the values in the manifest.

Important: Using HPA and VPA simultaneously for CPU is not recommended — it causes conflicts. Combine them instead: HPA based on RPS + VPA for memory in Initial mode.

apiVersion: autoscaling.k8s.io/v1\nkind: VerticalPodAutoscaler\nmetadata:\n  name: go-api-vpa\nspec:\n  targetRef:\n    apiVersion: apps/v1\n    kind: Deployment\n    name: go-api\n  updatePolicy:\n    updateMode: "Off"\n  resourcePolicy:\n    containerPolicies:\n      - containerName: go-api\n        minAllowed:\n          memory: "64Mi"\n        maxAllowed:\n          memory: "512Mi"

Deployment Strategies: Rolling Update, Blue-Green, and Canary for Go Services

Rolling Update

The default strategy in Kubernetes. Pods are updated gradually with no full downtime. For Go services, it is important to configure a proper preStop hook and terminationGracePeriodSeconds to finish processing in-flight requests.

spec:\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxSurge: 25%\n      maxUnavailable: 0\n  template:\n    spec:\n      terminationGracePeriodSeconds: 30\n      containers:\n        - name: go-api\n          lifecycle:\n            preStop:\n              exec:\n                command: ["/bin/sh", "-c", "sleep 5"]

Blue-Green

Two identical environments (blue — current, green — new). Traffic is switched instantly by changing the Service selector. Requires twice the resources, but provides instant rollback.

# Switch traffic to green\nkubectl patch service go-api-svc \\\n  -p '{"spec":{"selector":{"version":"green"}}}'

Canary

Gradual traffic shifting to the new version. In 2026, for Go microservices it is recommended to use Argo Rollouts or Flagger with automatic Prometheus metric analysis to decide whether to proceed or roll back.

apiVersion: argoproj.io/v1alpha1\nkind: Rollout\nmetadata:\n  name: go-api-rollout\nspec:\n  strategy:\n    canary:\n      steps:\n        - setWeight: 10\n        - pause: {duration: 5m}\n        - setWeight: 30\n        - pause: {duration: 10m}\n        - setWeight: 100\n      analysis:\n        templates:\n          - templateName: error-rate\n        startingStep: 1

Practical Example: Complete Kubernetes Manifests for a Go REST API

Deployment

apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: go-api\n  namespace: production\n  labels:\n    app: go-api\n    version: v1.5.0\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: go-api\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxSurge: 1\n      maxUnavailable: 0\n  template:\n    metadata:\n      labels:\n        app: go-api\n        version: v1.5.0\n      annotations:\n        prometheus.io/scrape: "true"\n        prometheus.io/port: "8080"\n        prometheus.io/path: "/metrics"\n    spec:\n      terminationGracePeriodSeconds: 30\n      containers:\n        - name: go-api\n          image: registry.example.com/go-api:v1.5.0\n          ports:\n            - containerPort: 8080\n          env:\n            - name: GOMAXPROCS\n              valueFrom:\n                resourceFieldRef:\n                  resource: limits.cpu\n          resources:\n            requests:\n              cpu: "250m"\n              memory: "128Mi"\n            limits:\n              cpu: "500m"\n              memory: "256Mi"\n          readinessProbe:\n            httpGet:\n              path: /healthz/ready\n              port: 8080\n            initialDelaySeconds: 5\n            periodSeconds: 10\n            failureThreshold: 3\n          livenessProbe:\n            httpGet:\n              path: /healthz/live\n              port: 8080\n            initialDelaySeconds: 15\n            periodSeconds: 20\n          lifecycle:\n            preStop:\n              exec:\n                command: ["/bin/sh", "-c", "sleep 5"]

Service and PodDisruptionBudget

apiVersion: v1\nkind: Service\nmetadata:\n  name: go-api-svc\n  namespace: production\nspec:\n  selector:\n    app: go-api\n  ports:\n    - port: 80\n      targetPort: 8080\n  type: ClusterIP\n---\napiVersion: policy/v1\nkind: PodDisruptionBudget\nmetadata:\n  name: go-api-pdb\n  namespace: production\nspec:\n  minAvailable: 2\n  selector:\n    matchLabels:\n      app: go-api

Monitoring and Debugging: kubectl top, Metrics, and Logging

Basic Diagnostic Commands

# Current resource consumption by pods\nkubectl top pods -n production --sort-by=memory\n\n# Current resource consumption by nodes\nkubectl top nodes\n\n# View HPA events\nkubectl describe hpa go-api-hpa -n production\n\n# Scaling history\nkubectl get events -n production --field-selector reason=SuccessfulRescale\n\n# Detailed HPA metric output\nkubectl get hpa go-api-hpa -n production -o yaml

Key Metrics for Go Services in Prometheus

  • container_cpu_throttled_seconds_total — CPU throttling (critical; should be close to 0).
  • container_memory_working_set_bytes — actual memory consumption (used for OOM decisions).
  • go_goroutines — goroutine count (goroutine leaks show up as a rising value).
  • go_gc_duration_seconds — GC pause duration.
  • process_resident_memory_bytes — process RSS memory.

Structured Logging in Go

For efficient logging in Kubernetes, use a structured JSON format with log levels (slog from the Go 1.21+ standard library):

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{\n    Level: slog.LevelInfo,\n}))\nlogger.Info("request processed",\n    "method", r.Method,\n    "path", r.URL.Path,\n    "duration_ms", time.Since(start).Milliseconds(),\n    "status", statusCode,\n)

This format is automatically parsed by Loki, Elasticsearch, and most modern log aggregators without additional configuration.

Conclusion and Checklist

Proper resource management and autoscaling are not a one-time setup but an iterative process. Go services offer a significant performance advantage, but only when the Kubernetes cluster is configured correctly. In 2026, the tooling stack has stabilized: HPA v2 with custom metrics, Argo Rollouts for Canary deployments, and VPA for profiling are all proven practices.

Pre-Deployment Checklist for Go Services in Kubernetes

  1. requests and limits are set based on load tests, not guesswork.
  2. automaxprocs is used to correctly set GOMAXPROCS according to the CPU limit.
  3. readinessProbe and livenessProbe are configured with realistic thresholds.
  4. A preStop hook is added for graceful shutdown.
  5. A PodDisruptionBudget with minAvailable ≥ 1 is created.
  6. HPA is configured with the correct stabilizationWindowSeconds for scale-down.
  7. Go runtime metrics and business metrics are exported to Prometheus.
  8. Alerting is set up for CPU throttling and OOMKilled events.
  9. A deployment strategy (Rolling/Canary/Blue-Green) has been chosen and tested.
  10. Logs are structured in JSON format for efficient indexing.

Technologies

Tags

Ruslan Ismailov

Senior Web / Backend Developer. Senior web/backend developer with 9 years of experience. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservices, CI/CD. More about me →