На главную

Контейнеры и CI/CD

docker-compose → Kubernetes

Переводит docker-compose.yml в манифесты Kubernetes: Deployment или StatefulSet, Service, ConfigMap, Secret, PVC и Ingress. Всё, что перенести нельзя, — отдельным списком с объяснением, что делать вместо этого.

Параметры кластера

Пусто — манифесты без namespace

Ingress

Что получится

  • ConfigMap/web-env
  • ConfigMap/db-env
  • Secret/web-secret
  • Secret/db-secret
  • Service/web
  • Service/db
  • Service/redis
  • Deployment/web
  • StatefulSet/db
  • StatefulSet/redis
  • Ingress/web
  • PersistentVolumeClaim/pgdata-db-0 (создаст StatefulSet)
  • PersistentVolumeClaim/redisdata-redis-0 (создаст StatefulSet)

k8s.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: web-env
  labels:
    app.kubernetes.io/name: web
data:
  NODE_ENV: production
  DATABASE_URL: postgres://shop@db:5432/shop
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: db-env
  labels:
    app.kubernetes.io/name: db
data:
  POSTGRES_DB: shop
  POSTGRES_USER: shop
---
apiVersion: v1
kind: Secret
metadata:
  name: web-secret
  labels:
    app.kubernetes.io/name: web
type: Opaque
stringData:
  SESSION_SECRET: change-me-please
---
apiVersion: v1
kind: Secret
metadata:
  name: db-secret
  labels:
    app.kubernetes.io/name: db
type: Opaque
stringData:
  POSTGRES_PASSWORD: s3cret
---
apiVersion: v1
kind: Service
metadata:
  name: web
  labels:
    app.kubernetes.io/name: web
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: web
  ports:
  - name: tcp-8080
    port: 8080
    targetPort: 3000
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: db
  labels:
    app.kubernetes.io/name: db
spec:
  clusterIP: None
  selector:
    app.kubernetes.io/name: db
---
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    app.kubernetes.io/name: redis
spec:
  clusterIP: None
  selector:
    app.kubernetes.io/name: redis
  ports:
  - name: tcp-6379
    port: 6379
    targetPort: 6379
    protocol: TCP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app.kubernetes.io/name: web
    tier: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: web
  template:
    metadata:
      labels:
        app.kubernetes.io/name: web
        tier: frontend
    spec:
      containers:
      - name: web
        image: ghcr.io/example/shop-web:1.4.2
        imagePullPolicy: IfNotPresent
        args:
        - npm
        - run
        - start
        ports:
        - containerPort: 3000
          protocol: TCP
        envFrom:
        - configMapRef:
            name: web-env
        - secretRef:
            name: web-secret
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 128Mi
        readinessProbe:
          exec:
            command:
            - wget
            - -qO-
            - http://localhost:3000/healthz
          initialDelaySeconds: 90
          periodSeconds: 30
          timeoutSeconds: 5
          failureThreshold: 3
        livenessProbe:
          exec:
            command:
            - wget
            - -qO-
            - http://localhost:3000/healthz
          initialDelaySeconds: 90
          periodSeconds: 30
          timeoutSeconds: 5
          failureThreshold: 3
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
  labels:
    app.kubernetes.io/name: db
spec:
  serviceName: db
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: db
  template:
    metadata:
      labels:
        app.kubernetes.io/name: db
    spec:
      containers:
      - name: db
        image: postgres:16
        imagePullPolicy: IfNotPresent
        envFrom:
        - configMapRef:
            name: db-env
        - secretRef:
            name: db-secret
        readinessProbe:
          exec:
            command:
            - sh
            - -c
            - pg_isready -U shop
          initialDelaySeconds: 0
          periodSeconds: 10
          timeoutSeconds: 30
          failureThreshold: 5
        livenessProbe:
          exec:
            command:
            - sh
            - -c
            - pg_isready -U shop
          initialDelaySeconds: 0
          periodSeconds: 10
          timeoutSeconds: 30
          failureThreshold: 5
        volumeMounts:
        - name: pgdata
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: pgdata
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  labels:
    app.kubernetes.io/name: redis
spec:
  serviceName: redis
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: redis
  template:
    metadata:
      labels:
        app.kubernetes.io/name: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        imagePullPolicy: IfNotPresent
        args:
        - redis-server
        - --appendonly
        - 'yes'
        ports:
        - containerPort: 6379
          protocol: TCP
        volumeMounts:
        - name: redisdata
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redisdata
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - shop.example.com
    secretName: web-tls
  rules:
  - host: shop.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web
            port:
              number: 8080

Что не перенесено и почему

  • Предупреждениеweb

    SESSION_SECRET вынесен в Secret. Secret — это base64, а не шифрование: не коммитьте файл с реальными значениями, используйте Sealed Secrets, SOPS или External Secrets.

  • Предупреждениеweb

    env_file (.env) не читается: создайте из файла Secret — kubectl create secret generic web-file-env --from-env-file=.env — и подключите через envFrom.

  • Предупреждениеweb

    Bind-mount «./uploads:/app/uploads» не перенесён: hostPath привязывает под к конкретному узлу и открывает доступ к его файловой системе. Файлы конфигурации положите в ConfigMap, данные — в PVC.

  • Предупреждениеdb

    POSTGRES_PASSWORD вынесен в Secret. Secret — это base64, а не шифрование: не коммитьте файл с реальными значениями, используйте Sealed Secrets, SOPS или External Secrets.

  • Подсказкаweb

    healthcheck перенесён в readinessProbe и livenessProbe. Для liveness лучше проверка попроще: при сбое базы liveness перезапустит здоровый под.

  • Подсказкаweb

    restart: unless-stopped — в Kubernetes так ведёт себя любой под Deployment, ничего делать не нужно.

  • Подсказкаweb

    depends_on (db, redis) не переносится: Kubernetes запускает поды одновременно. Приложение должно переживать недоступность зависимостей; если нет — добавьте initContainer, ждущий порт (until nc -z db <порт>; do sleep 2; done), и readinessProbe у зависимости.

  • Подсказкаdb

    Нет deploy.resources — у пода не будет requests/limits, планировщик разместит его вслепую.

  • Подсказкаdb

    healthcheck перенесён в readinessProbe и livenessProbe. Для liveness лучше проверка попроще: при сбое базы liveness перезапустит здоровый под.

  • Подсказкаdb

    Service «db» headless (clusterIP: None): его требует StatefulSet, а поды получают стабильные имена вида db-0.db.

  • Подсказкаdb

    restart: always — в Kubernetes так ведёт себя любой под Deployment, ничего делать не нужно.

  • Подсказкаredis

    Нет deploy.resources — у пода не будет requests/limits, планировщик разместит его вслепую.

  • Подсказкаredis

    Service «redis» headless (clusterIP: None): его требует StatefulSet, а поды получают стабильные имена вида redis-0.redis.

Применить

kubectl apply --dry-run=server -f k8s.yaml
kubectl apply -f k8s.yaml
kubectl get pods,svc,pvc
kubectl get events --sort-by=.lastTimestamp