Node.js требует больше ресурсов (память, CPU) → Go потребляет в 5-10x меньше
Node.js стартует секунды → Go стартует миллисекунды (быстрее rolling update)
npm run start → один бинарник, никаких node_modules
Одинаковые K8s-манифесты (Deployment, Service, ConfigMap)
Go-приложение не требует readiness-задержки (initialDelaySeconds можно 0!)
mkdir go-k8s && cd go-k8s
go mod init go-k8s
# Создаём простое приложение с health-чеками
mkdir -p cmd/server
mkdir -p k8s
cmd/server/main.gopackage main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
var (
startTime = time.Now()
isReady = false // Меняем на true когда готовы принимать трафик
)
func main() {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
log.Println("🚀 Запуск Go-приложения в Kubernetes...")
// Имитация инициализации (подключение к БД, прогрев кэша)
go func() {
log.Println("⏳ Инициализация (2 секунды)...")
time.Sleep(2 * time.Second)
isReady = true
log.Println("✅ Приложение готово к работе")
}()
mux := http.NewServeMux()
// Liveness probe — жив ли процесс?
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
// Readiness probe — готов ли принимать запросы?
mux.HandleFunc("GET /readyz", func(w http.ResponseWriter, r *http.Request) {
if isReady {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Ready"))
} else {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("Not Ready"))
}
})
// Startup probe — запустился ли? (для медленных приложений)
mux.HandleFunc("GET /startupz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("Started"))
})
// Информация о поде
mux.HandleFunc("GET /info", func(w http.ResponseWriter, r *http.Request) {
hostname, _ := os.Hostname()
info := map[string]any{
"hostname": hostname,
"version": "1.0.0",
"uptime": time.Since(startTime).String(),
"go_version": runtime.Version(),
"env": os.Getenv("APP_ENV"),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(info)
})
// Имитация нагрузки (для тестирования HPA)
mux.HandleFunc("GET /cpu-load", func(w http.ResponseWriter, r *http.Request) {
// Нагружаем CPU на 100ms
done := make(chan struct{})
go func() {
for i := 0; i < 10000000; i++ {
_ = i * i
}
close(done)
}()
<-done
w.Write([]byte("CPU load completed"))
})
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
server := &http.Server{
Addr: ":" + port,
Handler: mux,
}
// Graceful shutdown (важно для K8s!)
go func() {
log.Printf("✅ Сервер на :%s", port)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Сервер: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
sig := <-quit
log.Printf("📡 Сигнал %v — выключаемся...", sig)
// K8s даёт terminationGracePeriodSeconds на завершение
isReady = false // Сразу перестаём принимать новый трафик
log.Println("⏳ Ждём завершения активных запросов (5 секунд)...")
time.Sleep(5 * time.Second) // Имитация drain
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(ctx)
log.Println("👋 Завершено")
}
import "runtime"
DockerfileFROM golang:1.22-alpine AS builder
RUN apk add --no-cache git ca-certificates
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-w -s" -o /server ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
USER nonroot
ENTRYPOINT ["/server"]
k8s/deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: go-app
labels:
app: go-app
spec:
replicas: 3 # 3 пода для отказоустойчивости
selector:
matchLabels:
app: go-app
# Стратегия обновления
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Максимум дополнительных подов при обновлении
maxUnavailable: 0 # Минимум 0 недоступных (всегда доступны!)
template:
metadata:
labels:
app: go-app
version: "1.0.0"
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
# Graceful shutdown: даём 15 секунд на завершение
terminationGracePeriodSeconds: 15
containers:
- name: go-app
image: go-k8s-app:1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
protocol: TCP
env:
# Конфигурация из ConfigMap
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: go-app-config
key: app.env
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: go-app-config
key: log.level
# Секреты из Secret
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: go-app-secrets
key: database-url
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: go-app-secrets
key: redis-url
# ╔══════════════════════════════════════════════════╗
# ║ PROBES (проверки) ║
# ╚══════════════════════════════════════════════════╝
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 1 # Go стартует быстро
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 0 # Проверяем сразу
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
startupProbe:
httpGet:
path: /startupz
port: 8080
initialDelaySeconds: 0
periodSeconds: 1
failureThreshold: 30 # До 30 секунд на старт
# ╔══════════════════════════════════════════════════╗
# ║ RESOURCES (Go потребляет МАЛО!) ║
# ╚══════════════════════════════════════════════════╝
resources:
requests:
cpu: "50m" # 0.05 ядра
memory: "32Mi" # 32 MB
limits:
cpu: "200m" # 0.2 ядра
memory: "128Mi" # 128 MB
k8s/service.yamlapiVersion: v1
kind: Service
metadata:
name: go-app-service
labels:
app: go-app
spec:
type: ClusterIP # Внутренний доступ
selector:
app: go-app
ports:
- port: 80 # Порт сервиса
targetPort: 8080 # Порт контейнера
protocol: TCP
name: http
sessionAffinity: None
k8s/configmap.yamlapiVersion: v1
kind: ConfigMap
metadata:
name: go-app-config
data:
app.env: "production"
log.level: "info"
app.name: "go-k8s-demo"
# Можно хранить целые файлы
config.yaml: |
server:
port: 8080
read_timeout: 5s
features:
enable_cache: true
enable_metrics: true
k8s/secret.yamlapiVersion: v1
kind: Secret
metadata:
name: go-app-secrets
type: Opaque
stringData: # stringData автоматически кодируется в base64
database-url: "postgres://user:password@postgres:5432/appdb?sslmode=disable"
redis-url: "redis://redis:6379/0"
jwt-secret: "your-super-secret-key-here"
k8s/hpa.yamlapiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: go-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: go-app
minReplicas: 2 # Минимум 2 пода
maxReplicas: 10 # Максимум 10 подов
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # Масштабируем при 70% CPU
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # Масштабируем при 80% памяти
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # Ждём 5 минут перед уменьшением
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0 # Увеличиваем мгновенно
policies:
- type: Percent
value: 100
periodSeconds: 15
k8s/ingress.yamlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: go-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: go-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: go-app-service
port:
number: 80
# Собираем Docker-образ
docker build -t go-k8s-app:1.0.0 .
# Если используете minikube
minikube start
minikube image load go-k8s-app:1.0.0
# Применяем манифесты
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/hpa.yaml
# Проверяем
kubectl get pods
kubectl get svc
kubectl get hpa
# Логи
kubectl logs -f deployment/go-app
# Проброс порта (если нет Ingress)
kubectl port-forward svc/go-app-service 8080:80
# Тестирование
curl http://localhost:8080/healthz
curl http://localhost:8080/info
# Нагрузочное тестирование HPA
kubectl run -it --rm load-generator --image=busybox -- /bin/sh
# Внутри контейнера:
while true; do wget -q -O- http://go-app-service/cpu-load; done
| Probe | Назначение | При провале | Go-особенности |
|---|---|---|---|
| Liveness | Жив ли процесс? | Перезапуск контейнера | initialDelaySeconds: 1 (быстрый старт) |
| Readiness | Готов принимать трафик? | Убрать из Service | initialDelaySeconds: 0 (можно сразу) |
| Startup | Запустился ли? | Блокирует Liveness/Readiness | Нужен если есть долгая инициализация |
💡 Best practices от сеньоров:
💡 Для Node.js разработчика: