1502 lines
48 KiB
Markdown
1502 lines
48 KiB
Markdown
# Flux CD — GitOps-эталон для команды разработчиков
|
||
|
||
Этот документ описывает стандарт организации GitOps-деплоя приложений в Kubernetes через Flux CD.
|
||
Является эталоном для всех команд, разворачивающих сервисы в кластере `gitlab.gigacoms.info / k8s`.
|
||
|
||
---
|
||
|
||
## Содержание
|
||
|
||
1. [Концепция и термины](#1-концепция-и-термины)
|
||
2. [Двухрепозиторная модель: app-repo и fleet-repo](#2-двухрепозиторная-модель-app-repo-и-fleet-repo)
|
||
3. [Структура fleet-репозитория](#3-структура-fleet-репозитория)
|
||
4. [Многоветочный деплой: main → production, dev → staging](#4-многоветочный-деплой-main--production-dev--staging)
|
||
5. [Пример 1: Одно приложение, один Pod](#5-пример-1-одно-приложение-один-pod)
|
||
6. [Пример 2: Одно приложение, несколько Pod (Deployment + HPA)](#6-пример-2-одно-приложение-несколько-pod-deployment--hpa)
|
||
7. [Пример 3: Несколько компонентов одного приложения (frontend + backend + БД)](#7-пример-3-несколько-компонентов-одного-приложения-frontend--backend--бд)
|
||
8. [Пример 4: Взаимодействующие независимые приложения (микросервисы)](#8-пример-4-взаимодействующие-независимые-приложения-микросервисы)
|
||
9. [CI/CD в app-repo: автоматическое обновление образа](#9-cicd-в-app-repo-автоматическое-обновление-образа)
|
||
10. [Secrets: безопасная передача секретов через Flux](#10-secrets-безопасная-передача-секретов-через-flux)
|
||
11. [Диагностика и типичные ошибки](#11-диагностика-и-типичные-ошибки)
|
||
12. [Чеклист перед деплоем нового приложения](#12-чеклист-перед-деплоем-нового-приложения)
|
||
|
||
---
|
||
|
||
## 1. Концепция и термины
|
||
|
||
**GitOps** — практика, при которой Git-репозиторий является единственным источником истины о состоянии кластера. Flux CD периодически (по умолчанию каждые 1–5 минут) сравнивает желаемое состояние (Git) с фактическим (кластер) и приводит их в соответствие.
|
||
|
||
| Термин | Описание |
|
||
|---|---|
|
||
| **fleet-repo** | Git-репозиторий с манифестами Kubernetes. Flux следит за ним и применяет изменения в кластер. В нашем случае: `gitlab.gigacoms.info/k8s/k8s-fleet` |
|
||
| **app-repo** | Репозиторий с исходным кодом приложения. Содержит `Dockerfile` и CI/CD пайплайн, который собирает образ и обновляет тег в fleet-repo |
|
||
| **Kustomization** | CRD Flux, описывающий: откуда брать манифесты, в каком namespace применять, от чего зависеть |
|
||
| **GitRepository** | CRD Flux, описывающий: какой Git-репозиторий наблюдать, какую ветку/тег/коммит |
|
||
| **HelmRelease** | CRD Flux для деплоя Helm-чарта с управлением версией и values |
|
||
| **ImageRepository** | CRD Flux для наблюдения за образами в Container Registry |
|
||
| **ImageUpdateAutomation** | CRD Flux для автоматического обновления тега образа в fleet-repo |
|
||
| **Overlay** | Kustomize-слой поверх базовой конфигурации — позволяет менять значения для разных окружений без дублирования |
|
||
|
||
### Принцип работы
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ Developer │
|
||
│ │ │
|
||
│ ├─ git push → app-repo (main или dev) │
|
||
│ │ │
|
||
│ │ CI/CD в app-repo: │
|
||
│ │ 1. docker build + push → registry (тег: sha/version) │
|
||
│ │ 2. git commit в fleet-repo → обновить тег образа │
|
||
│ │ │
|
||
│ └─ fleet-repo (k8s-fleet) │
|
||
│ │ │
|
||
│ │ Flux (в кластере) каждые N минут: │
|
||
│ │ 1. git pull fleet-repo │
|
||
│ │ 2. сравнить с кластером │
|
||
│ │ 3. kubectl apply изменений │
|
||
│ ▼ │
|
||
│ Kubernetes Cluster │
|
||
│ ├── namespace: production ← из ветки main fleet-repo │
|
||
│ └── namespace: staging ← из ветки main fleet-repo │
|
||
│ (другой path в том же repo) │
|
||
└─────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
> **Важно:** Flux читает **один fleet-repo**, но может следить за разными **путями** внутри него для разных окружений. Ветки `main` и `dev` app-repo влияют на разные директории fleet-repo — staging и production.
|
||
|
||
---
|
||
|
||
## 2. Двухрепозиторная модель: app-repo и fleet-repo
|
||
|
||
### app-repo (репозиторий приложения)
|
||
|
||
Содержит:
|
||
- Исходный код приложения
|
||
- `Dockerfile`
|
||
- `.gitlab-ci.yml` — сборка образа и **обновление fleet-repo**
|
||
- (опционально) базовые Kustomize-манифесты, которые копируются в fleet-repo при инициализации
|
||
|
||
**Не содержит:**
|
||
- Финальных Kubernetes-манифестов с тегами образов
|
||
- Секретов
|
||
|
||
### fleet-repo (репозиторий конфигурации кластера)
|
||
|
||
Единственный источник истины для Flux. Содержит:
|
||
- Манифесты всех приложений и инфраструктурных компонентов
|
||
- Kustomize-оверлеи для каждого окружения
|
||
- HelmRelease-объекты
|
||
- Ссылки на секреты (но не сами секреты в открытом виде)
|
||
|
||
**Не содержит:**
|
||
- Исходного кода приложений
|
||
- `Dockerfile`
|
||
- Секретов в открытом виде
|
||
|
||
---
|
||
|
||
## 3. Структура fleet-репозитория
|
||
|
||
Стандартная структура fleet-repo для нашего кластера:
|
||
|
||
```
|
||
k8s-fleet/
|
||
├── clusters/
|
||
│ └── production/ # flux bootstrap --path=clusters/production
|
||
│ ├── flux-system/ # авто-генерируется flux bootstrap, не трогать вручную
|
||
│ │ ├── gotk-components.yaml
|
||
│ │ ├── gotk-sync.yaml
|
||
│ │ └── kustomization.yaml
|
||
│ ├── apps.yaml # Flux Kustomization → apps/production/
|
||
│ └── infrastructure.yaml # Flux Kustomization → infrastructure/production/
|
||
│
|
||
├── apps/
|
||
│ ├── base/ # базовые манифесты (без env-специфики)
|
||
│ │ ├── myapp/
|
||
│ │ │ ├── namespace.yaml
|
||
│ │ │ ├── deployment.yaml
|
||
│ │ │ ├── service.yaml
|
||
│ │ │ └── kustomization.yaml # Kustomize kustomization.yaml (не Flux CRD)
|
||
│ │ └── another-app/
|
||
│ │ └── ...
|
||
│ ├── production/ # production оверлей
|
||
│ │ ├── kustomization.yaml # Flux Kustomization CRD
|
||
│ │ └── myapp/
|
||
│ │ ├── kustomization.yaml # Kustomize: patch поверх base
|
||
│ │ └── patch-image.yaml # тег образа для production
|
||
│ └── staging/ # staging оверлей (из dev ветки app-repo)
|
||
│ ├── kustomization.yaml # Flux Kustomization CRD
|
||
│ └── myapp/
|
||
│ ├── kustomization.yaml
|
||
│ └── patch-image.yaml # тег образа для staging
|
||
│
|
||
└── infrastructure/
|
||
├── base/
|
||
│ ├── traefik/
|
||
│ └── longhorn/
|
||
├── production/
|
||
│ └── kustomization.yaml
|
||
└── staging/
|
||
└── kustomization.yaml
|
||
```
|
||
|
||
### Точка входа Flux: clusters/production/apps.yaml
|
||
|
||
```yaml
|
||
# clusters/production/apps.yaml
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: apps
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system # fleet-repo, созданный flux bootstrap
|
||
path: ./apps/production # Flux следит за этой директорией
|
||
prune: true # удалять из кластера то, чего нет в Git
|
||
wait: true # ждать готовности перед следующим шагом
|
||
timeout: 5m
|
||
```
|
||
|
||
> `prune: true` — критически важный параметр. Без него удалённые из Git ресурсы останутся в кластере.
|
||
|
||
---
|
||
|
||
## 4. Многоветочный деплой: main → production, dev → staging
|
||
|
||
### Концепция
|
||
|
||
Оба окружения (production и staging) существуют в **одном кластере** в **разных namespace**.
|
||
Flux следит за **одним fleet-repo** (ветка `main`), но за **разными путями** внутри него:
|
||
|
||
- `apps/production/` → namespace `production` (образы из ветки `main` app-repo)
|
||
- `apps/staging/` → namespace `staging` (образы из ветки `dev` app-repo)
|
||
|
||
CI/CD в app-repo при пуше в `main` обновляет тег в `apps/production/myapp/patch-image.yaml`.
|
||
CI/CD в app-repo при пуше в `dev` обновляет тег в `apps/staging/myapp/patch-image.yaml`.
|
||
|
||
### Настройка двух окружений в clusters/production/
|
||
|
||
```yaml
|
||
# clusters/production/apps.yaml
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: apps-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production
|
||
prune: true
|
||
wait: true
|
||
timeout: 5m
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: apps-staging
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/staging
|
||
prune: true
|
||
wait: true
|
||
timeout: 5m
|
||
```
|
||
|
||
### Зависимости между окружениями (dependsOn)
|
||
|
||
Если staging должен деплоиться только после успешного production (нетипично, но возможно):
|
||
|
||
```yaml
|
||
spec:
|
||
dependsOn:
|
||
- name: apps-production
|
||
```
|
||
|
||
### Схема взаимодействия веток и окружений
|
||
|
||
```
|
||
app-repo fleet-repo (k8s-fleet, ветка main)
|
||
─────────────────────────────── ──────────────────────────────────────
|
||
branch: main apps/production/myapp/patch-image.yaml
|
||
│ image: registry/myapp:v1.5.0
|
||
│ CI push → обновляет тег ──────►
|
||
│
|
||
branch: dev apps/staging/myapp/patch-image.yaml
|
||
│ image: registry/myapp:dev-abc1234
|
||
│ CI push → обновляет тег ──────►
|
||
│
|
||
Flux (каждые 5 минут):
|
||
git pull fleet-repo
|
||
apply apps/production/ → ns: production
|
||
apply apps/staging/ → ns: staging
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Пример 1: Одно приложение, один Pod
|
||
|
||
**Сценарий:** простой HTTP-сервис (например, API на Go), одна реплика.
|
||
|
||
### Структура в fleet-repo
|
||
|
||
```
|
||
apps/
|
||
├── base/
|
||
│ └── simple-api/
|
||
│ ├── namespace.yaml
|
||
│ ├── deployment.yaml
|
||
│ ├── service.yaml
|
||
│ └── kustomization.yaml
|
||
├── production/
|
||
│ ├── kustomization.yaml
|
||
│ └── simple-api/
|
||
│ ├── kustomization.yaml
|
||
│ └── patch-image.yaml
|
||
└── staging/
|
||
├── kustomization.yaml
|
||
└── simple-api/
|
||
├── kustomization.yaml
|
||
└── patch-image.yaml
|
||
```
|
||
|
||
### apps/base/simple-api/namespace.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Namespace
|
||
metadata:
|
||
name: production # для base используем production; staging переопределит через patch
|
||
```
|
||
|
||
### apps/base/simple-api/deployment.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: simple-api
|
||
namespace: production
|
||
spec:
|
||
replicas: 1
|
||
selector:
|
||
matchLabels:
|
||
app: simple-api
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: simple-api
|
||
version: "1.0.0"
|
||
spec:
|
||
containers:
|
||
- name: simple-api
|
||
image: registry.gigacoms.info/myteam/simple-api:latest # тег заменяется патчем
|
||
ports:
|
||
- containerPort: 8080
|
||
env:
|
||
- name: APP_ENV
|
||
value: production
|
||
resources:
|
||
requests:
|
||
cpu: 50m
|
||
memory: 64Mi
|
||
limits:
|
||
cpu: 200m
|
||
memory: 128Mi
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 8080
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 8080
|
||
initialDelaySeconds: 15
|
||
periodSeconds: 20
|
||
```
|
||
|
||
### apps/base/simple-api/service.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: simple-api
|
||
namespace: production
|
||
spec:
|
||
selector:
|
||
app: simple-api
|
||
ports:
|
||
- port: 80
|
||
targetPort: 8080
|
||
type: ClusterIP
|
||
```
|
||
|
||
### apps/base/simple-api/kustomization.yaml
|
||
|
||
```yaml
|
||
# Это Kustomize kustomization.yaml (не Flux CRD)
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
resources:
|
||
- namespace.yaml
|
||
- deployment.yaml
|
||
- service.yaml
|
||
```
|
||
|
||
### apps/production/simple-api/patch-image.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: simple-api
|
||
namespace: production
|
||
spec:
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: simple-api
|
||
image: registry.gigacoms.info/myteam/simple-api:v1.5.2 # обновляет CI/CD
|
||
```
|
||
|
||
### apps/production/simple-api/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
namespace: production
|
||
resources:
|
||
- ../../base/simple-api
|
||
patches:
|
||
- path: patch-image.yaml
|
||
```
|
||
|
||
### apps/staging/simple-api/patch-image.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: simple-api
|
||
namespace: staging
|
||
spec:
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: simple-api
|
||
image: registry.gigacoms.info/myteam/simple-api:dev-abc1234f
|
||
env:
|
||
- name: APP_ENV
|
||
value: staging
|
||
```
|
||
|
||
### apps/staging/simple-api/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
namespace: staging # переопределяет namespace для всех ресурсов из base
|
||
resources:
|
||
- ../../base/simple-api
|
||
patches:
|
||
- path: patch-image.yaml
|
||
```
|
||
|
||
### apps/production/kustomization.yaml (Flux Kustomization CRD)
|
||
|
||
```yaml
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: simple-api-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/simple-api
|
||
prune: true
|
||
targetNamespace: production
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Пример 2: Одно приложение, несколько Pod (Deployment + HPA)
|
||
|
||
**Сценарий:** нагруженный HTTP-сервис с горизонтальным масштабированием.
|
||
|
||
### apps/base/web-service/deployment.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: web-service
|
||
namespace: production
|
||
spec:
|
||
replicas: 2 # минимальное количество; HPA управляет масштабом
|
||
selector:
|
||
matchLabels:
|
||
app: web-service
|
||
strategy:
|
||
type: RollingUpdate
|
||
rollingUpdate:
|
||
maxSurge: 1
|
||
maxUnavailable: 0 # zero-downtime deploy
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: web-service
|
||
spec:
|
||
affinity:
|
||
podAntiAffinity: # разносить Pod по разным нодам
|
||
preferredDuringSchedulingIgnoredDuringExecution:
|
||
- weight: 100
|
||
podAffinityTerm:
|
||
labelSelector:
|
||
matchLabels:
|
||
app: web-service
|
||
topologyKey: kubernetes.io/hostname
|
||
containers:
|
||
- name: web-service
|
||
image: registry.gigacoms.info/myteam/web-service:latest
|
||
ports:
|
||
- containerPort: 8080
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 128Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 512Mi
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /ready
|
||
port: 8080
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 5
|
||
failureThreshold: 3
|
||
```
|
||
|
||
### apps/base/web-service/hpa.yaml
|
||
|
||
```yaml
|
||
apiVersion: autoscaling/v2
|
||
kind: HorizontalPodAutoscaler
|
||
metadata:
|
||
name: web-service
|
||
namespace: production
|
||
spec:
|
||
scaleTargetRef:
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: web-service
|
||
minReplicas: 2
|
||
maxReplicas: 10
|
||
metrics:
|
||
- type: Resource
|
||
resource:
|
||
name: cpu
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 70
|
||
- type: Resource
|
||
resource:
|
||
name: memory
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 80
|
||
behavior:
|
||
scaleDown:
|
||
stabilizationWindowSeconds: 300 # не уменьшать быстрее чем раз в 5 минут
|
||
policies:
|
||
- type: Pods
|
||
value: 1
|
||
periodSeconds: 60
|
||
scaleUp:
|
||
stabilizationWindowSeconds: 30
|
||
policies:
|
||
- type: Pods
|
||
value: 2
|
||
periodSeconds: 60
|
||
```
|
||
|
||
### apps/base/web-service/pdb.yaml
|
||
|
||
```yaml
|
||
# PodDisruptionBudget — не допускать недоступности при обновлении нод
|
||
apiVersion: policy/v1
|
||
kind: PodDisruptionBudget
|
||
metadata:
|
||
name: web-service
|
||
namespace: production
|
||
spec:
|
||
minAvailable: 1
|
||
selector:
|
||
matchLabels:
|
||
app: web-service
|
||
```
|
||
|
||
### apps/base/web-service/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
resources:
|
||
- namespace.yaml
|
||
- deployment.yaml
|
||
- service.yaml
|
||
- hpa.yaml
|
||
- pdb.yaml
|
||
```
|
||
|
||
### apps/staging/web-service/patch-replicas.yaml
|
||
|
||
В staging HPA и PDB не нужны — экономим ресурсы:
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: web-service
|
||
namespace: staging
|
||
spec:
|
||
replicas: 1 # staging всегда 1 реплика
|
||
```
|
||
|
||
### apps/staging/web-service/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
namespace: staging
|
||
resources:
|
||
- ../../base/web-service
|
||
patches:
|
||
- path: patch-image.yaml
|
||
- path: patch-replicas.yaml
|
||
# исключаем HPA и PDB из staging
|
||
components: []
|
||
```
|
||
|
||
Или через `kustomization.yaml` с явным списком resources (без hpa.yaml и pdb.yaml):
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
namespace: staging
|
||
resources:
|
||
- ../../base/web-service/namespace.yaml
|
||
- ../../base/web-service/deployment.yaml
|
||
- ../../base/web-service/service.yaml
|
||
patches:
|
||
- path: patch-image.yaml
|
||
- path: patch-replicas.yaml
|
||
```
|
||
|
||
---
|
||
|
||
## 7. Пример 3: Несколько компонентов одного приложения (frontend + backend + БД)
|
||
|
||
**Сценарий:** веб-приложение из трёх компонентов в одном namespace. Все три деплоятся вместе, версионируются независимо.
|
||
|
||
### Структура
|
||
|
||
```
|
||
apps/
|
||
├── base/
|
||
│ └── shop/ # одно логическое приложение "shop"
|
||
│ ├── namespace.yaml
|
||
│ ├── frontend/
|
||
│ │ ├── deployment.yaml
|
||
│ │ └── service.yaml
|
||
│ ├── backend/
|
||
│ │ ├── deployment.yaml
|
||
│ │ ├── service.yaml
|
||
│ │ └── configmap.yaml
|
||
│ ├── postgres/
|
||
│ │ ├── statefulset.yaml
|
||
│ │ ├── service.yaml
|
||
│ │ └── pvc.yaml
|
||
│ └── kustomization.yaml
|
||
├── production/
|
||
│ └── shop/
|
||
│ ├── kustomization.yaml
|
||
│ ├── patch-frontend.yaml
|
||
│ ├── patch-backend.yaml
|
||
│ └── patch-postgres.yaml
|
||
└── staging/
|
||
└── shop/
|
||
├── kustomization.yaml
|
||
├── patch-frontend.yaml
|
||
├── patch-backend.yaml
|
||
└── patch-postgres.yaml
|
||
```
|
||
|
||
### apps/base/shop/namespace.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Namespace
|
||
metadata:
|
||
name: shop-production
|
||
labels:
|
||
environment: production
|
||
app.kubernetes.io/part-of: shop
|
||
```
|
||
|
||
### apps/base/shop/backend/configmap.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: backend-config
|
||
namespace: shop-production
|
||
data:
|
||
DB_HOST: postgres # имя Service внутри namespace
|
||
DB_PORT: "5432"
|
||
DB_NAME: shopdb
|
||
FRONTEND_URL: http://frontend
|
||
LOG_LEVEL: info
|
||
```
|
||
|
||
### apps/base/shop/backend/deployment.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: backend
|
||
namespace: shop-production
|
||
spec:
|
||
replicas: 2
|
||
selector:
|
||
matchLabels:
|
||
app: shop
|
||
component: backend
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: shop
|
||
component: backend
|
||
spec:
|
||
initContainers:
|
||
- name: wait-for-postgres # ждём БД перед стартом
|
||
image: busybox:1.36
|
||
command:
|
||
- sh
|
||
- -c
|
||
- |
|
||
until nc -z postgres 5432; do
|
||
echo "Waiting for postgres..."
|
||
sleep 2
|
||
done
|
||
containers:
|
||
- name: backend
|
||
image: registry.gigacoms.info/myteam/shop-backend:latest
|
||
ports:
|
||
- containerPort: 3000
|
||
envFrom:
|
||
- configMapRef:
|
||
name: backend-config
|
||
env:
|
||
- name: DB_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: shop-postgres-secret
|
||
key: password
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 256Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 512Mi
|
||
```
|
||
|
||
### apps/base/shop/backend/service.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: backend
|
||
namespace: shop-production
|
||
spec:
|
||
selector:
|
||
app: shop
|
||
component: backend
|
||
ports:
|
||
- port: 80
|
||
targetPort: 3000
|
||
type: ClusterIP
|
||
```
|
||
|
||
### apps/base/shop/frontend/deployment.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: frontend
|
||
namespace: shop-production
|
||
spec:
|
||
replicas: 2
|
||
selector:
|
||
matchLabels:
|
||
app: shop
|
||
component: frontend
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: shop
|
||
component: frontend
|
||
spec:
|
||
containers:
|
||
- name: frontend
|
||
image: registry.gigacoms.info/myteam/shop-frontend:latest
|
||
ports:
|
||
- containerPort: 80
|
||
env:
|
||
- name: BACKEND_URL
|
||
value: http://backend # DNS-имя Service в том же namespace
|
||
resources:
|
||
requests:
|
||
cpu: 50m
|
||
memory: 64Mi
|
||
limits:
|
||
cpu: 200m
|
||
memory: 128Mi
|
||
```
|
||
|
||
### apps/base/shop/postgres/statefulset.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: postgres
|
||
namespace: shop-production
|
||
spec:
|
||
serviceName: postgres
|
||
replicas: 1
|
||
selector:
|
||
matchLabels:
|
||
app: shop
|
||
component: postgres
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: shop
|
||
component: postgres
|
||
spec:
|
||
containers:
|
||
- name: postgres
|
||
image: postgres:16-alpine
|
||
ports:
|
||
- containerPort: 5432
|
||
env:
|
||
- name: POSTGRES_DB
|
||
value: shopdb
|
||
- name: POSTGRES_USER
|
||
value: shopuser
|
||
- name: POSTGRES_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: shop-postgres-secret
|
||
key: password
|
||
volumeMounts:
|
||
- name: data
|
||
mountPath: /var/lib/postgresql/data
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 256Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 1Gi
|
||
volumeClaimTemplates:
|
||
- metadata:
|
||
name: data
|
||
spec:
|
||
accessModes: ["ReadWriteOnce"]
|
||
storageClassName: longhorn
|
||
resources:
|
||
requests:
|
||
storage: 10Gi
|
||
```
|
||
|
||
### apps/base/shop/postgres/service.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: postgres
|
||
namespace: shop-production
|
||
spec:
|
||
selector:
|
||
app: shop
|
||
component: postgres
|
||
ports:
|
||
- port: 5432
|
||
targetPort: 5432
|
||
type: ClusterIP
|
||
clusterIP: None # Headless service для StatefulSet
|
||
```
|
||
|
||
### apps/base/shop/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
resources:
|
||
- namespace.yaml
|
||
- backend/configmap.yaml
|
||
- backend/deployment.yaml
|
||
- backend/service.yaml
|
||
- frontend/deployment.yaml
|
||
- frontend/service.yaml
|
||
- postgres/statefulset.yaml
|
||
- postgres/service.yaml
|
||
```
|
||
|
||
### apps/production/shop/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
namespace: shop-production
|
||
resources:
|
||
- ../../base/shop
|
||
patches:
|
||
- path: patch-frontend.yaml
|
||
- path: patch-backend.yaml
|
||
- path: patch-postgres.yaml
|
||
```
|
||
|
||
### apps/production/shop/patch-frontend.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: frontend
|
||
namespace: shop-production
|
||
spec:
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: frontend
|
||
image: registry.gigacoms.info/myteam/shop-frontend:v2.1.0
|
||
```
|
||
|
||
### apps/staging/shop/kustomization.yaml
|
||
|
||
```yaml
|
||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||
kind: Kustomization
|
||
namespace: shop-staging # staging в отдельном namespace
|
||
resources:
|
||
- ../../base/shop
|
||
namePrefix: "" # не добавлять префикс
|
||
patches:
|
||
- path: patch-frontend.yaml
|
||
- path: patch-backend.yaml
|
||
- path: patch-postgres.yaml
|
||
- path: patch-staging-replicas.yaml # все компоненты = 1 реплика
|
||
```
|
||
|
||
### apps/staging/shop/patch-staging-replicas.yaml
|
||
|
||
```yaml
|
||
---
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: frontend
|
||
namespace: shop-staging
|
||
spec:
|
||
replicas: 1
|
||
---
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: backend
|
||
namespace: shop-staging
|
||
spec:
|
||
replicas: 1
|
||
---
|
||
apiVersion: apps/v1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: postgres
|
||
namespace: shop-staging
|
||
spec:
|
||
replicas: 1
|
||
```
|
||
|
||
### Порядок деплоя (dependsOn в Flux Kustomization)
|
||
|
||
Когда postgres должен быть готов до backend:
|
||
|
||
```yaml
|
||
# clusters/production/apps.yaml
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: shop-postgres-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/shop/postgres
|
||
prune: true
|
||
healthChecks:
|
||
- apiVersion: apps/v1
|
||
kind: StatefulSet
|
||
name: postgres
|
||
namespace: shop-production
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: shop-backend-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/shop/backend
|
||
prune: true
|
||
dependsOn:
|
||
- name: shop-postgres-production # backend стартует только после healthy postgres
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: shop-frontend-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/shop/frontend
|
||
prune: true
|
||
dependsOn:
|
||
- name: shop-backend-production
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Пример 4: Взаимодействующие независимые приложения (микросервисы)
|
||
|
||
**Сценарий:** несколько независимых сервисов в разных namespace, которые вызывают друг друга по HTTP.
|
||
|
||
### Топология
|
||
|
||
```
|
||
namespace: auth-production
|
||
└── auth-service (порт 8080)
|
||
|
||
namespace: orders-production
|
||
└── orders-service (порт 8080) → вызывает auth-service через cross-namespace DNS
|
||
|
||
namespace: notifications-production
|
||
└── notifications-service (порт 8080) → вызывает orders-service
|
||
```
|
||
|
||
### DNS-имена для cross-namespace обращения
|
||
|
||
Внутри Kubernetes полное DNS-имя Service:
|
||
|
||
```
|
||
<service-name>.<namespace>.svc.cluster.local
|
||
```
|
||
|
||
Примеры:
|
||
- `auth-service.auth-production.svc.cluster.local:8080`
|
||
- `orders-service.orders-production.svc.cluster.local:8080`
|
||
|
||
### Структура fleet-repo для микросервисов
|
||
|
||
```
|
||
apps/
|
||
├── base/
|
||
│ ├── auth-service/
|
||
│ │ ├── namespace.yaml
|
||
│ │ ├── deployment.yaml
|
||
│ │ └── service.yaml
|
||
│ ├── orders-service/
|
||
│ │ ├── namespace.yaml
|
||
│ │ ├── deployment.yaml
|
||
│ │ ├── service.yaml
|
||
│ │ └── configmap.yaml # URL других сервисов
|
||
│ └── notifications-service/
|
||
│ ├── namespace.yaml
|
||
│ ├── deployment.yaml
|
||
│ ├── service.yaml
|
||
│ └── configmap.yaml
|
||
├── production/
|
||
│ ├── kustomization.yaml # включает все три сервиса
|
||
│ ├── auth-service/
|
||
│ │ ├── kustomization.yaml
|
||
│ │ └── patch-image.yaml
|
||
│ ├── orders-service/
|
||
│ │ ├── kustomization.yaml
|
||
│ │ └── patch-image.yaml
|
||
│ └── notifications-service/
|
||
│ ├── kustomization.yaml
|
||
│ └── patch-image.yaml
|
||
└── staging/
|
||
└── ... # аналогично, namespace: *-staging
|
||
```
|
||
|
||
### apps/base/orders-service/configmap.yaml
|
||
|
||
```yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: orders-config
|
||
namespace: orders-production
|
||
data:
|
||
# cross-namespace DNS — полный FQDN обязателен
|
||
AUTH_SERVICE_URL: http://auth-service.auth-production.svc.cluster.local:80
|
||
NOTIFICATIONS_SERVICE_URL: http://notifications-service.notifications-production.svc.cluster.local:80
|
||
LOG_LEVEL: info
|
||
```
|
||
|
||
### apps/base/orders-service/deployment.yaml
|
||
|
||
```yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: orders-service
|
||
namespace: orders-production
|
||
spec:
|
||
replicas: 2
|
||
selector:
|
||
matchLabels:
|
||
app: orders-service
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: orders-service
|
||
spec:
|
||
containers:
|
||
- name: orders-service
|
||
image: registry.gigacoms.info/myteam/orders-service:latest
|
||
ports:
|
||
- containerPort: 8080
|
||
envFrom:
|
||
- configMapRef:
|
||
name: orders-config
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 128Mi
|
||
limits:
|
||
cpu: 300m
|
||
memory: 256Mi
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 8080
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 5
|
||
```
|
||
|
||
### Staging: cross-namespace URL тоже меняется
|
||
|
||
```yaml
|
||
# apps/staging/orders-service/patch-config.yaml
|
||
apiVersion: v1
|
||
kind: ConfigMap
|
||
metadata:
|
||
name: orders-config
|
||
namespace: orders-staging
|
||
data:
|
||
AUTH_SERVICE_URL: http://auth-service.auth-staging.svc.cluster.local:80
|
||
NOTIFICATIONS_SERVICE_URL: http://notifications-service.notifications-staging.svc.cluster.local:80
|
||
LOG_LEVEL: debug # staging = verbose logging
|
||
```
|
||
|
||
### NetworkPolicy: разрешить inter-namespace трафик явно
|
||
|
||
По умолчанию в Kubernetes трафик между namespace разрешён. Если в кластере включён запретительный NetworkPolicy, нужно явно разрешить:
|
||
|
||
```yaml
|
||
# apps/base/auth-service/networkpolicy.yaml
|
||
apiVersion: networking.k8s.io/v1
|
||
kind: NetworkPolicy
|
||
metadata:
|
||
name: allow-from-orders
|
||
namespace: auth-production
|
||
spec:
|
||
podSelector:
|
||
matchLabels:
|
||
app: auth-service
|
||
ingress:
|
||
- from:
|
||
- namespaceSelector:
|
||
matchLabels:
|
||
kubernetes.io/metadata.name: orders-production
|
||
podSelector:
|
||
matchLabels:
|
||
app: orders-service
|
||
ports:
|
||
- port: 8080
|
||
```
|
||
|
||
### Порядок деплоя микросервисов (dependsOn)
|
||
|
||
```yaml
|
||
# clusters/production/apps.yaml
|
||
---
|
||
# auth деплоится первым (ни от чего не зависит)
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: auth-service-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/auth-service
|
||
prune: true
|
||
healthChecks:
|
||
- apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: auth-service
|
||
namespace: auth-production
|
||
---
|
||
# orders ждёт auth
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: orders-service-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/orders-service
|
||
prune: true
|
||
dependsOn:
|
||
- name: auth-service-production
|
||
healthChecks:
|
||
- apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: orders-service
|
||
namespace: orders-production
|
||
---
|
||
# notifications ждёт orders
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: notifications-service-production
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/notifications-service
|
||
prune: true
|
||
dependsOn:
|
||
- name: orders-service-production
|
||
```
|
||
|
||
---
|
||
|
||
## 9. CI/CD в app-repo: автоматическое обновление образа
|
||
|
||
### Принцип работы
|
||
|
||
1. Разработчик делает `git push` в ветку `main` (или `dev`)
|
||
2. CI собирает Docker-образ, тегирует его (`v1.5.2` или `dev-abc1234f`)
|
||
3. CI пушит образ в Container Registry
|
||
4. CI клонирует fleet-repo, обновляет тег образа в нужном оверлее, делает commit + push
|
||
5. Flux обнаруживает изменение в fleet-repo и применяет его в кластер
|
||
|
||
### .gitlab-ci.yml в app-repo (стандартный шаблон)
|
||
|
||
```yaml
|
||
variables:
|
||
REGISTRY: registry.gigacoms.info
|
||
IMAGE_NAME: $REGISTRY/myteam/simple-api
|
||
FLEET_REPO: https://oauth2:${GITLAB_FLEET_TOKEN}@gitlab.gigacoms.info/k8s/k8s-fleet.git
|
||
|
||
stages:
|
||
- build
|
||
- deploy
|
||
|
||
build:
|
||
stage: build
|
||
image: docker:27
|
||
services:
|
||
- docker:27-dind
|
||
script:
|
||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $REGISTRY
|
||
- |
|
||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||
TAG="v${CI_COMMIT_SHORT_SHA}"
|
||
OVERLAY="production"
|
||
else
|
||
TAG="dev-${CI_COMMIT_SHORT_SHA}"
|
||
OVERLAY="staging"
|
||
fi
|
||
- docker build -t $IMAGE_NAME:$TAG .
|
||
- docker push $IMAGE_NAME:$TAG
|
||
- echo "TAG=$TAG" >> build.env
|
||
- echo "OVERLAY=$OVERLAY" >> build.env
|
||
artifacts:
|
||
reports:
|
||
dotenv: build.env
|
||
rules:
|
||
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "dev"
|
||
|
||
deploy-to-fleet:
|
||
stage: deploy
|
||
image: alpine/git:latest
|
||
needs:
|
||
- job: build
|
||
artifacts: true
|
||
script:
|
||
- git config --global user.email "ci@gitlab.gigacoms.info"
|
||
- git config --global user.name "GitLab CI"
|
||
- git clone --depth=1 $FLEET_REPO /tmp/fleet
|
||
- |
|
||
PATCH_FILE="/tmp/fleet/apps/${OVERLAY}/simple-api/patch-image.yaml"
|
||
cat > $PATCH_FILE << EOF
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: simple-api
|
||
namespace: simple-api-${OVERLAY}
|
||
spec:
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: simple-api
|
||
image: ${IMAGE_NAME}:${TAG}
|
||
EOF
|
||
- cd /tmp/fleet
|
||
- git add apps/${OVERLAY}/simple-api/patch-image.yaml
|
||
- git diff --cached --quiet || git commit -m "ci: update simple-api ${OVERLAY} to ${TAG}"
|
||
- git push origin main
|
||
rules:
|
||
- if: $CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "dev"
|
||
```
|
||
|
||
> **Важно:** `GITLAB_FLEET_TOKEN` — отдельный токен с правами `write_repository` на fleet-repo. Хранить в CI/CD Variables проекта app-repo. Не использовать персональные токены.
|
||
|
||
### Соглашения по тегам образов
|
||
|
||
| Окружение | Ветка app-repo | Формат тега | Пример |
|
||
|---|---|---|---|
|
||
| production | `main` | `v<SHA-7>` или semver | `v1.5.2`, `vabc1234f` |
|
||
| staging | `dev` | `dev-<SHA-7>` | `dev-abc1234f` |
|
||
|
||
Никогда не использовать тег `latest` в fleet-repo — он неиммутабелен и Flux не сможет отследить изменение.
|
||
|
||
---
|
||
|
||
## 10. Secrets: безопасная передача секретов через Flux
|
||
|
||
### Принцип
|
||
|
||
Секреты **никогда не хранятся в Git в открытом виде**. Допустимые подходы:
|
||
|
||
1. **Ручное создание** (используем сейчас) — `kubectl create secret` вручную один раз
|
||
2. **Sealed Secrets** — шифрование секрета публичным ключом кластера, зашифрованный yaml коммитится в Git
|
||
3. **External Secrets Operator** — секрет хранится во внешнем хранилище (Vault, AWS SM), ESO синхронизирует в кластер
|
||
|
||
### Подход 1: ручное создание (текущий стандарт)
|
||
|
||
```bash
|
||
# Создать секрет вручную — один раз, не через Ansible и не через Git
|
||
kubectl create secret generic shop-postgres-secret \
|
||
--from-literal=password='StrongPassword123!' \
|
||
-n shop-production
|
||
|
||
# Для staging отдельно
|
||
kubectl create secret generic shop-postgres-secret \
|
||
--from-literal=password='StagingPassword456!' \
|
||
-n shop-staging
|
||
```
|
||
|
||
В манифесте деплоя ссылаться на секрет через `secretKeyRef` (как в примерах выше).
|
||
|
||
Если Flux удалит namespace (через `prune: true`), секрет исчезнет вместе с ним. Документировать секреты в `secrets/README.md` внутри fleet-repo (без значений!):
|
||
|
||
```markdown
|
||
# Требуемые секреты (создавать вручную перед первым деплоем)
|
||
|
||
## shop-production
|
||
kubectl create secret generic shop-postgres-secret \
|
||
--from-literal=password='<ASK_OPS>' \
|
||
-n shop-production
|
||
```
|
||
|
||
### Подход 2: Sealed Secrets (рекомендуется при масштабировании)
|
||
|
||
```bash
|
||
# Установить kubeseal
|
||
brew install kubeseal # или скачать бинарь
|
||
|
||
# Зашифровать секрет публичным ключом кластера
|
||
kubectl create secret generic shop-postgres-secret \
|
||
--from-literal=password='StrongPassword123!' \
|
||
--dry-run=client -o yaml | \
|
||
kubeseal --controller-namespace flux-system \
|
||
--controller-name sealed-secrets \
|
||
--format yaml > apps/base/shop/postgres/sealed-secret.yaml
|
||
|
||
# Полученный sealed-secret.yaml безопасно коммитить в Git
|
||
git add apps/base/shop/postgres/sealed-secret.yaml
|
||
git commit -m "feat: add shop postgres sealed secret"
|
||
```
|
||
|
||
---
|
||
|
||
## 11. Диагностика и типичные ошибки
|
||
|
||
### Основные команды
|
||
|
||
```bash
|
||
# Состояние всех Flux-объектов
|
||
flux get all -A
|
||
|
||
# Состояние конкретного Kustomization
|
||
flux get kustomization apps-production -n flux-system
|
||
|
||
# Принудительная синхронизация (не ждать interval)
|
||
flux reconcile kustomization apps-production --with-source
|
||
|
||
# Логи flux-контроллеров
|
||
kubectl logs -n flux-system -l app=kustomize-controller --tail=50
|
||
kubectl logs -n flux-system -l app=source-controller --tail=50
|
||
|
||
# Просмотр событий в namespace
|
||
kubectl get events -n production --sort-by='.lastTimestamp'
|
||
|
||
# Детали конкретного Kustomization
|
||
kubectl describe kustomization apps-production -n flux-system
|
||
```
|
||
|
||
### Типичные ошибки и решения
|
||
|
||
| Ошибка | Причина | Решение |
|
||
|---|---|---|
|
||
| `Health check failed` | Pod не стал Ready в течение timeout | Проверить `kubectl describe pod`, `kubectl logs` |
|
||
| `kustomize build failed` | Синтаксическая ошибка в yaml | `kustomize build apps/production/myapp` локально |
|
||
| `object not found` | Ресурс удалён из Git но остался в кластере без `prune: true` | Добавить `prune: true` или удалить вручную |
|
||
| `no such file or directory` | Неверный path в Kustomization | Проверить path в CRD, путь относительный от корня repo |
|
||
| `resource conflict` | Два Kustomization управляют одним ресурсом | Разнести по разным namespace или убрать дублирование |
|
||
| Flux не видит изменений | Интервал не истёк | `flux reconcile kustomization <name> --with-source` |
|
||
| Deployment завис на 0/2 | imagePullError | Проверить тег образа и доступность registry |
|
||
|
||
### Проверка до деплоя (dry-run)
|
||
|
||
```bash
|
||
# Локальная проверка Kustomize-сборки
|
||
kustomize build apps/production/myapp
|
||
|
||
# Проверить что Flux увидит после изменения
|
||
flux diff kustomization apps-production --path ./apps/production
|
||
|
||
# Синтаксическая проверка всех yaml
|
||
find apps/ -name '*.yaml' | xargs kubectl apply --dry-run=client -f
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Чеклист перед деплоем нового приложения
|
||
|
||
### В fleet-repo
|
||
|
||
- [ ] Создана директория `apps/base/<app-name>/` с namespace, deployment, service, kustomization.yaml
|
||
- [ ] Создана директория `apps/production/<app-name>/` с patch-image.yaml и kustomization.yaml
|
||
- [ ] Создана директория `apps/staging/<app-name>/` с patch-image.yaml и kustomization.yaml
|
||
- [ ] Добавлен Flux Kustomization CRD в `clusters/production/apps.yaml`
|
||
- [ ] Настроены `dependsOn` если приложение зависит от других компонентов
|
||
- [ ] Настроены `healthChecks` если другие компоненты зависят от этого приложения
|
||
- [ ] `prune: true` установлен во всех Kustomization CRD
|
||
- [ ] Ресурсы `requests`/`limits` заданы для всех контейнеров
|
||
- [ ] `readinessProbe` настроен для всех контейнеров
|
||
- [ ] `kustomize build apps/production/<app-name>` выполняется без ошибок локально
|
||
|
||
### В app-repo
|
||
|
||
- [ ] `.gitlab-ci.yml` содержит шаг `deploy-to-fleet`
|
||
- [ ] Используются иммутабельные теги образов (не `latest`)
|
||
- [ ] `GITLAB_FLEET_TOKEN` добавлен в CI/CD Variables проекта
|
||
- [ ] Образ успешно собирается и пушится в registry
|
||
|
||
### В кластере
|
||
|
||
- [ ] Secrets созданы вручную в нужных namespace перед первым деплоем (documented в secrets/README.md)
|
||
- [ ] `flux get kustomization <name>` показывает `Ready=True`
|
||
- [ ] Pod в состоянии `Running`, все контейнеры `Ready`
|
||
- [ ] Сервис доступен внутри кластера: `kubectl exec -it <pod> -- curl http://<service>`
|
||
- [ ] (если нужен внешний доступ) IngressRoute добавлен в `inventory/prod/group_vars/traefik.yml`
|
||
|
||
---
|
||
|
||
## Приложение: соглашения по именованию
|
||
|
||
| Сущность | Формат | Пример |
|
||
|---|---|---|
|
||
| Namespace production | `<app>-production` | `shop-production` |
|
||
| Namespace staging | `<app>-staging` | `shop-staging` |
|
||
| Flux Kustomization | `<app>-<env>` | `shop-production` |
|
||
| Docker-образ production | `registry/team/<app>:v<sha>` | `registry.gigacoms.info/myteam/shop-backend:vabc1234f` |
|
||
| Docker-образ staging | `registry/team/<app>:dev-<sha>` | `registry.gigacoms.info/myteam/shop-backend:dev-abc1234f` |
|
||
| Secret | `<app>-<purpose>-secret` | `shop-postgres-secret` |
|
||
| ConfigMap | `<component>-config` | `backend-config` |
|
||
| Service (внутр.) | `<component>` | `backend`, `postgres` |
|
||
|
||
## Приложение: минимальный ресурсный профиль
|
||
|
||
Использовать как отправную точку, корректировать под реальную нагрузку:
|
||
|
||
```yaml
|
||
resources:
|
||
requests:
|
||
cpu: 50m # гарантированное CPU
|
||
memory: 64Mi # гарантированная память
|
||
limits:
|
||
cpu: 200m # максимальное CPU
|
||
memory: 256Mi # максимальная память (OOMKill при превышении)
|
||
```
|
||
|
||
> При выборе `limits.memory`: установить в 2–4x от `requests.memory`. Слишком маленький лимит приведёт к постоянным OOMKill. Слишком большой — к неэффективному использованию узла.
|