Контейнеры и 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