1153 lines
41 KiB
Markdown
1153 lines
41 KiB
Markdown
# Исследование: Flux CD для GitOps-развёртывания из GitLab
|
||
|
||
## Задача
|
||
|
||
Обеспечить автоматическое развёртывание приложений из проектов внешнего GitLab-сервера
|
||
(`https://gitlab.gigacoms.info`) в кластер Kubernetes (k8s-master-01 / k8s-worker-01).
|
||
|
||
Flux работает совместно с уже установленными в кластере компонентами:
|
||
- **Longhorn** — постоянное блочное хранилище (StorageClass `longhorn`). Стандарт использования: `roles/longhorn/README.md`
|
||
- **Traefik** — HTTP-прокси для публикации сервисов наружу (порты 10001–10999). Стандарт использования: `roles/traefik/README.md`
|
||
|
||
---
|
||
|
||
## Что такое Flux CD
|
||
|
||
Flux CD — GitOps-инструмент для Kubernetes. Работает как набор контроллеров внутри кластера,
|
||
которые непрерывно синхронизируют состояние кластера с Git-репозиторием. Поддерживает GitLab
|
||
(включая self-hosted), GitHub, Bitbucket, S3-совместимые хранилища.
|
||
|
||
Официальный сайт: https://fluxcd.io
|
||
|
||
---
|
||
|
||
## Архитектура компонентов
|
||
|
||
Flux состоит из нескольких независимых контроллеров, каждый управляет своим набором CRD.
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ GitLab: gitlab.gigacoms.info │
|
||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||
│ │ fleet repo │ │ app repo A │ │ app repo B │ │
|
||
│ │ (flux config)│ │ (Helm/k8s) │ │ (kustomize) │ │
|
||
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
|
||
└─────────┼────────────────┼──────────────────-─┼────────────┘
|
||
│ pull │ pull │ pull
|
||
┌─────────▼────────────────▼─────────────────────▼───────────┐
|
||
│ Kubernetes cluster │
|
||
│ │
|
||
│ source-controller — следит за Git/Helm-источниками │
|
||
│ kustomize-controller — применяет Kustomization манифесты │
|
||
│ helm-controller — управляет HelmRelease │
|
||
│ notification-controller— алерты, webhooks │
|
||
│ image-reflector — сканирует container registry │
|
||
│ image-automation — обновляет теги образов в Git │
|
||
│ │
|
||
│ ──── уже установлено Ansible ──────────────────────────── │
|
||
│ Longhorn (longhorn-system) — StorageClass: longhorn │
|
||
│ Traefik (traefik) — порт-прокси 10001–10999 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Основные CRD
|
||
|
||
| CRD | Контроллер | Назначение |
|
||
|---|---|---|
|
||
| `GitRepository` | source-controller | Отслеживает Git-репозиторий, создаёт artifact |
|
||
| `HelmRepository` | source-controller | Отслеживает Helm chart repository |
|
||
| `HelmRelease` | helm-controller | Устанавливает/обновляет Helm chart |
|
||
| `Kustomization` | kustomize-controller | Применяет kustomize-манифесты из artifact |
|
||
| `ImageRepository` | image-reflector | Сканирует container registry |
|
||
| `ImagePolicy` | image-reflector | Выбирает тег по SemVer/паттерну |
|
||
| `ImageUpdateAutomation` | image-automation | Коммитит новый тег в Git |
|
||
|
||
---
|
||
|
||
## Модели развёртывания
|
||
|
||
### Модель 1: Один fleet-репозиторий (рекомендуется)
|
||
|
||
Один специальный Git-репозиторий (`k8s-fleet`) хранит всю конфигурацию Flux.
|
||
Приложения описываются через `GitRepository` + `Kustomization` или `HelmRelease`.
|
||
|
||
```
|
||
k8s-fleet/
|
||
clusters/
|
||
production/
|
||
flux-system/ ← bootstrap-манифесты (авто-генерируются)
|
||
apps.yaml ← Flux Kustomization → apps/production/
|
||
apps/
|
||
base/
|
||
my-app/
|
||
deployment.yaml
|
||
service.yaml
|
||
pvc.yaml ← storageClassName: longhorn
|
||
ingressroute.yaml ← IngressRoute для Traefik
|
||
kustomization.yaml
|
||
production/
|
||
my-app/
|
||
kustomization.yaml
|
||
patch-image.yaml
|
||
staging/
|
||
my-app/
|
||
kustomization.yaml
|
||
patch-image.yaml
|
||
```
|
||
|
||
### Модель 2: GitOps per-проект
|
||
|
||
Каждый проект в GitLab хранит свои k8s-манифесты. Flux смотрит на каждый из них.
|
||
Подходит при большом количестве независимых команд.
|
||
|
||
---
|
||
|
||
## Установка Flux CLI
|
||
|
||
```bash
|
||
# На k8s-manager-01 (operator workstation)
|
||
curl -s https://fluxcd.io/install.sh | sudo bash
|
||
|
||
# Проверка совместимости с кластером
|
||
flux check --pre
|
||
```
|
||
|
||
---
|
||
|
||
## Bootstrap: подключение к GitLab
|
||
|
||
Bootstrap — однократная процедура, после которой Flux сам себя обслуживает через Git.
|
||
|
||
### Требования
|
||
|
||
- **GitLab Personal Access Token** со scope `api` (или `read_repository` + `write_repository`)
|
||
- Права **Owner** на проект или **Maintainer** на группу в GitLab
|
||
- `kubectl` с доступом к кластеру (kubeconfig на k8s-manager-01)
|
||
- Flux CLI установлен
|
||
|
||
### Команда bootstrap для self-hosted GitLab
|
||
|
||
```bash
|
||
export GITLAB_TOKEN=<personal-access-token>
|
||
|
||
flux bootstrap gitlab \
|
||
--hostname=gitlab.gigacoms.info \
|
||
--owner=<gitlab-group-or-username> \
|
||
--repository=k8s-fleet \
|
||
--branch=main \
|
||
--path=clusters/production \
|
||
--personal # если owner — пользователь, не группа
|
||
```
|
||
|
||
Параметры:
|
||
|
||
| Флаг | Описание |
|
||
|---|---|
|
||
| `--hostname` | Хост self-hosted GitLab |
|
||
| `--owner` | GitLab group или username |
|
||
| `--repository` | Имя репозитория (создастся если нет) |
|
||
| `--branch` | Ветка для Flux-конфига |
|
||
| `--path` | Путь внутри репо для этого кластера |
|
||
| `--personal` | Если owner — личный аккаунт, не группа |
|
||
| `--private` | Создать репо как приватный (по умолчанию `true`) |
|
||
|
||
Bootstrap выполнит:
|
||
1. Создаст репозиторий `k8s-fleet` в GitLab (если не существует)
|
||
2. Сгенерирует Deploy Key и добавит в репозиторий
|
||
3. Установит Flux-контроллеры в namespace `flux-system`
|
||
4. Запушит начальные манифесты в `clusters/production/flux-system/`
|
||
|
||
---
|
||
|
||
## Подключение приложения из GitLab-проекта
|
||
|
||
После bootstrap, чтобы Flux следил за конкретным проектом:
|
||
|
||
### Вариант A: Kustomize (plain YAML / kustomize)
|
||
|
||
```yaml
|
||
# clusters/production/apps/my-app.yaml
|
||
|
||
apiVersion: source.toolkit.fluxcd.io/v1
|
||
kind: GitRepository
|
||
metadata:
|
||
name: my-app
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 1m
|
||
url: https://gitlab.gigacoms.info/mygroup/my-app.git
|
||
ref:
|
||
branch: main
|
||
secretRef:
|
||
name: my-app-gitlab-token # Secret с токеном доступа
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: my-app
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
path: ./deploy/k8s
|
||
prune: true
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: my-app
|
||
targetNamespace: my-app
|
||
```
|
||
|
||
### Вариант B: Helm chart из GitLab
|
||
|
||
```yaml
|
||
apiVersion: source.toolkit.fluxcd.io/v1
|
||
kind: HelmRepository
|
||
metadata:
|
||
name: mygroup-charts
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 10m
|
||
url: https://gitlab.gigacoms.info/api/v4/projects/<project-id>/packages/helm/stable
|
||
secretRef:
|
||
name: gitlab-helm-token
|
||
---
|
||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||
kind: HelmRelease
|
||
metadata:
|
||
name: my-app
|
||
namespace: my-app
|
||
spec:
|
||
interval: 5m
|
||
chart:
|
||
spec:
|
||
chart: my-app
|
||
version: ">=1.0.0"
|
||
sourceRef:
|
||
kind: HelmRepository
|
||
name: mygroup-charts
|
||
namespace: flux-system
|
||
values:
|
||
replicaCount: 2
|
||
```
|
||
|
||
### Secret для доступа к приватному репозиторию
|
||
|
||
```bash
|
||
# Создать Secret с GitLab токеном (HTTPS)
|
||
kubectl create secret generic my-app-gitlab-token \
|
||
--namespace=flux-system \
|
||
--from-literal=username=oauth2 \
|
||
--from-literal=password=<gitlab-token>
|
||
```
|
||
|
||
Или через SSH deploy key:
|
||
|
||
```bash
|
||
flux create secret git my-app-gitlab-ssh \
|
||
--url=ssh://git@gitlab.gigacoms.info/mygroup/my-app.git \
|
||
--namespace=flux-system
|
||
# Публичный ключ добавить в GitLab → Project → Settings → Repository → Deploy Keys
|
||
```
|
||
|
||
---
|
||
|
||
## Интеграция с Longhorn
|
||
|
||
> Полный стандарт работы с Longhorn: `roles/longhorn/README.md`
|
||
|
||
Longhorn установлен Ansible и предоставляет StorageClass `longhorn` (default).
|
||
Flux-управляемые приложения используют его напрямую через PVC — никаких дополнительных
|
||
настроек в fleet-repo не требуется.
|
||
|
||
### Правила использования хранилища в fleet-repo
|
||
|
||
- Всегда указывать `storageClassName: longhorn` явно — не полагаться на default
|
||
- Для одного Pod — `ReadWriteOnce`, `replicas: 1` в Deployment
|
||
- Для StatefulSet с несколькими репликами — `volumeClaimTemplates` (не `volumes + PVC`)
|
||
- Для PostgreSQL: `PGDATA` в подпапку, `fsGroup: 999` в `securityContext`
|
||
- `ReadWriteMany` — только если нескольким Pod на разных нодах нужен общий том
|
||
|
||
### Пример A: простое приложение с постоянным хранилищем
|
||
|
||
```yaml
|
||
# apps/base/file-processor/pvc.yaml
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: file-processor-data
|
||
namespace: file-processor
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteOnce
|
||
storageClassName: longhorn
|
||
resources:
|
||
requests:
|
||
storage: 20Gi
|
||
---
|
||
# apps/base/file-processor/deployment.yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: file-processor
|
||
namespace: file-processor
|
||
spec:
|
||
replicas: 1 # RWO — только 1 реплика
|
||
selector:
|
||
matchLabels:
|
||
app: file-processor
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: file-processor
|
||
spec:
|
||
containers:
|
||
- name: file-processor
|
||
image: registry.gigacoms.info/myteam/file-processor:latest
|
||
volumeMounts:
|
||
- name: data
|
||
mountPath: /app/data
|
||
resources:
|
||
requests:
|
||
cpu: 100m
|
||
memory: 256Mi
|
||
limits:
|
||
cpu: 500m
|
||
memory: 512Mi
|
||
volumes:
|
||
- name: data
|
||
persistentVolumeClaim:
|
||
claimName: file-processor-data
|
||
```
|
||
|
||
### Пример B: StatefulSet с Longhorn (база данных PostgreSQL)
|
||
|
||
```yaml
|
||
# apps/base/shop/postgres/statefulset.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:
|
||
securityContext:
|
||
fsGroup: 999 # postgres GID
|
||
containers:
|
||
- name: postgres
|
||
image: postgres:16-alpine
|
||
env:
|
||
- name: POSTGRES_DB
|
||
value: shopdb
|
||
- name: POSTGRES_USER
|
||
value: shopuser
|
||
- name: POSTGRES_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: shop-postgres-secret
|
||
key: password
|
||
- name: PGDATA
|
||
value: /var/lib/postgresql/data/pgdata # подпапка! не корень тома
|
||
volumeMounts:
|
||
- name: data
|
||
mountPath: /var/lib/postgresql/data
|
||
resources:
|
||
requests:
|
||
cpu: 200m
|
||
memory: 512Mi
|
||
limits:
|
||
cpu: 1000m
|
||
memory: 2Gi
|
||
readinessProbe:
|
||
exec:
|
||
command: [pg_isready, -U, shopuser, -d, shopdb]
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 5
|
||
volumeClaimTemplates:
|
||
- metadata:
|
||
name: data
|
||
spec:
|
||
accessModes: ["ReadWriteOnce"]
|
||
storageClassName: longhorn # явно указывать всегда
|
||
resources:
|
||
requests:
|
||
storage: 50Gi
|
||
```
|
||
|
||
### Пример C: два компонента с общим томом (ReadWriteMany)
|
||
|
||
Сценарий: CMS пишет медиафайлы, CDN-прокси их раздаёт с нескольких Pod.
|
||
|
||
```yaml
|
||
# apps/base/cms/pvc-shared-media.yaml
|
||
apiVersion: v1
|
||
kind: PersistentVolumeClaim
|
||
metadata:
|
||
name: shared-media
|
||
namespace: cms
|
||
spec:
|
||
accessModes:
|
||
- ReadWriteMany # Longhorn NFS-шлюз, v1.5+
|
||
storageClassName: longhorn
|
||
resources:
|
||
requests:
|
||
storage: 100Gi
|
||
---
|
||
# apps/base/cms/deployment-cms.yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: cms
|
||
namespace: cms
|
||
spec:
|
||
replicas: 1
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: cms
|
||
image: registry.gigacoms.info/myteam/cms:latest
|
||
volumeMounts:
|
||
- name: media
|
||
mountPath: /app/media
|
||
volumes:
|
||
- name: media
|
||
persistentVolumeClaim:
|
||
claimName: shared-media
|
||
---
|
||
# apps/base/cms/deployment-cdn.yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: cdn-proxy
|
||
namespace: cms
|
||
spec:
|
||
replicas: 3 # несколько реплик читают один RWX-том
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: nginx
|
||
image: nginx:1.27-alpine
|
||
volumeMounts:
|
||
- name: media
|
||
mountPath: /usr/share/nginx/html/media
|
||
readOnly: true
|
||
volumes:
|
||
- name: media
|
||
persistentVolumeClaim:
|
||
claimName: shared-media
|
||
```
|
||
|
||
---
|
||
|
||
## Интеграция с Traefik
|
||
|
||
> Полный стандарт конфигурации Traefik: `roles/traefik/README.md`
|
||
|
||
Traefik установлен Ansible и слушает порты 10001–10999 как DaemonSet на worker-нодах.
|
||
Для публикации сервиса через Traefik есть **два подхода** — выбор зависит от характера сервиса.
|
||
|
||
### Подход 1: IngressRoute в fleet-repo (рекомендуется для Flux-управляемых приложений)
|
||
|
||
Flux-управляемое приложение само описывает свой маршрут в fleet-repo.
|
||
`allowCrossNamespace: true` уже включён в Traefik — IngressRoute из namespace `traefik`
|
||
может ссылаться на Service в любом другом namespace.
|
||
|
||
Этот подход даёт полный GitOps-цикл: добавил приложение в fleet-repo — оно само появилось
|
||
и снаружи, без отдельного запуска Ansible.
|
||
|
||
**Ограничение:** порт под IngressRoute должен быть заранее добавлен в Traefik через
|
||
`traefik_port_map` (Ansible). EntryPoint-ы в Traefik — это статическая конфигурация,
|
||
она не обновляется через CRD. Поэтому порядок действий:
|
||
|
||
```
|
||
1. Зарезервировать порт → добавить в traefik_port_map → запустить setup_traefik.yml
|
||
2. Закоммитить IngressRoute в fleet-repo → Flux применит его
|
||
```
|
||
|
||
Пример IngressRoute в fleet-repo (приложение `shop`, порт `10005` зарезервирован в traefik_port_map):
|
||
|
||
```yaml
|
||
# apps/base/shop/ingressroute.yaml
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: IngressRoute
|
||
metadata:
|
||
name: shop-ui
|
||
namespace: traefik # всегда в namespace traefik
|
||
spec:
|
||
entryPoints:
|
||
- shop-ui # имя из traefik_port_map (≤15 символов)
|
||
routes:
|
||
- match: PathPrefix(`/`)
|
||
kind: Rule
|
||
services:
|
||
- name: frontend # Service в namespace shop-production
|
||
namespace: shop-production
|
||
port: 80
|
||
scheme: http
|
||
```
|
||
|
||
Для BasicAuth:
|
||
|
||
```yaml
|
||
# apps/base/shop/ingressroute-admin.yaml
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: IngressRoute
|
||
metadata:
|
||
name: shop-admin
|
||
namespace: traefik
|
||
spec:
|
||
entryPoints:
|
||
- shop-admin # порт 10006 в traefik_port_map
|
||
routes:
|
||
- match: PathPrefix(`/`)
|
||
kind: Rule
|
||
services:
|
||
- name: backend-admin
|
||
namespace: shop-production
|
||
port: 8080
|
||
scheme: http
|
||
middlewares:
|
||
- name: basicauth-shop-admin
|
||
namespace: traefik
|
||
---
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: Middleware
|
||
metadata:
|
||
name: basicauth-shop-admin
|
||
namespace: traefik
|
||
spec:
|
||
basicAuth:
|
||
secret: traefik-auth-shop-admin # создать вручную перед деплоем
|
||
removeHeader: true
|
||
```
|
||
|
||
> BasicAuth Secret создаётся вручную (не в fleet-repo):
|
||
> ```bash
|
||
> kubectl create secret generic traefik-auth-shop-admin \
|
||
> --from-literal=users="$(openssl passwd -apr1 'Password' | xargs -I{} echo 'admin:{}')" \
|
||
> -n traefik
|
||
> ```
|
||
|
||
### Подход 2: traefik_port_map в Ansible (для инфраструктурных сервисов)
|
||
|
||
Подходит для сервисов, которые не управляются Flux: мониторинг, дашборды, сервисы,
|
||
установленные Ansible. Добавить запись в `inventory/prod/group_vars/traefik.yml` и
|
||
запустить `setup_traefik.yml`.
|
||
|
||
Подробно: `roles/traefik/README.md`, раздел «Единственный способ добавить сервис».
|
||
|
||
### Полный пример приложения с Longhorn + Traefik в fleet-repo
|
||
|
||
**Сценарий:** веб-приложение `shop` с PostgreSQL на Longhorn, UI опубликован через Traefik.
|
||
|
||
Предварительные требования (выполнить один раз до первого деплоя через Flux):
|
||
|
||
```bash
|
||
# 1. Зарезервировать порты в traefik_port_map и запустить Ansible:
|
||
# port 10005: name=shop-ui → frontend:80 (без BasicAuth)
|
||
# port 10006: name=shop-admin → backend:8080 (с BasicAuth)
|
||
ansible-playbook -i inventory/prod playbooks/setup_traefik.yml
|
||
|
||
# 2. Создать секреты вручную (не хранятся в Git)
|
||
kubectl create secret generic shop-postgres-secret \
|
||
--from-literal=password='StrongDbPassword' \
|
||
-n shop-production
|
||
|
||
kubectl create secret generic traefik-auth-shop-admin \
|
||
--from-literal=users="$(openssl passwd -apr1 'AdminPass' | xargs -I{} echo 'admin:{}')" \
|
||
-n traefik
|
||
```
|
||
|
||
Структура fleet-repo для приложения:
|
||
|
||
```
|
||
apps/
|
||
├── base/
|
||
│ └── shop/
|
||
│ ├── namespace.yaml
|
||
│ ├── configmap.yaml
|
||
│ ├── postgres/
|
||
│ │ ├── statefulset.yaml ← volumeClaimTemplates: storageClassName: longhorn
|
||
│ │ └── service.yaml
|
||
│ ├── backend/
|
||
│ │ ├── deployment.yaml
|
||
│ │ └── service.yaml
|
||
│ ├── frontend/
|
||
│ │ ├── deployment.yaml
|
||
│ │ └── service.yaml
|
||
│ ├── ingressroute-ui.yaml ← IngressRoute: entryPoints: [shop-ui]
|
||
│ ├── ingressroute-admin.yaml ← IngressRoute: entryPoints: [shop-admin] + BasicAuth
|
||
│ ├── middleware-auth.yaml ← Middleware: basicAuth (ссылается на secret)
|
||
│ └── kustomization.yaml
|
||
└── production/
|
||
└── shop/
|
||
├── kustomization.yaml
|
||
├── patch-postgres.yaml ← образ postgres:16-alpine (стабильный)
|
||
├── patch-frontend.yaml ← image: registry/shop-frontend:v2.1.0
|
||
└── patch-backend.yaml ← image: registry/shop-backend:v2.1.0
|
||
```
|
||
|
||
Ключевые манифесты:
|
||
|
||
```yaml
|
||
# apps/base/shop/postgres/statefulset.yaml — Longhorn storage
|
||
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:
|
||
securityContext:
|
||
fsGroup: 999
|
||
containers:
|
||
- name: postgres
|
||
image: postgres:16-alpine
|
||
env:
|
||
- name: POSTGRES_DB
|
||
value: shopdb
|
||
- name: POSTGRES_USER
|
||
value: shopuser
|
||
- name: POSTGRES_PASSWORD
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: shop-postgres-secret
|
||
key: password
|
||
- name: PGDATA
|
||
value: /var/lib/postgresql/data/pgdata
|
||
volumeMounts:
|
||
- name: data
|
||
mountPath: /var/lib/postgresql/data
|
||
resources:
|
||
requests:
|
||
cpu: 200m
|
||
memory: 512Mi
|
||
limits:
|
||
cpu: 1000m
|
||
memory: 2Gi
|
||
readinessProbe:
|
||
exec:
|
||
command: [pg_isready, -U, shopuser, -d, shopdb]
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 5
|
||
volumeClaimTemplates:
|
||
- metadata:
|
||
name: data
|
||
spec:
|
||
accessModes: ["ReadWriteOnce"]
|
||
storageClassName: longhorn
|
||
resources:
|
||
requests:
|
||
storage: 50Gi
|
||
---
|
||
# apps/base/shop/ingressroute-ui.yaml — Traefik публикация
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: IngressRoute
|
||
metadata:
|
||
name: shop-ui
|
||
namespace: traefik
|
||
spec:
|
||
entryPoints:
|
||
- shop-ui
|
||
routes:
|
||
- match: PathPrefix(`/`)
|
||
kind: Rule
|
||
services:
|
||
- name: frontend
|
||
namespace: shop-production
|
||
port: 80
|
||
scheme: http
|
||
---
|
||
# apps/base/shop/ingressroute-admin.yaml — Traefik с BasicAuth
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: IngressRoute
|
||
metadata:
|
||
name: shop-admin
|
||
namespace: traefik
|
||
spec:
|
||
entryPoints:
|
||
- shop-admin
|
||
routes:
|
||
- match: PathPrefix(`/`)
|
||
kind: Rule
|
||
services:
|
||
- name: backend
|
||
namespace: shop-production
|
||
port: 8080
|
||
scheme: http
|
||
middlewares:
|
||
- name: basicauth-shop-admin
|
||
namespace: traefik
|
||
---
|
||
# apps/base/shop/middleware-auth.yaml
|
||
apiVersion: traefik.io/v1alpha1
|
||
kind: Middleware
|
||
metadata:
|
||
name: basicauth-shop-admin
|
||
namespace: traefik
|
||
spec:
|
||
basicAuth:
|
||
secret: traefik-auth-shop-admin
|
||
removeHeader: true
|
||
```
|
||
|
||
Flux Kustomization с порядком деплоя (postgres → backend → frontend):
|
||
|
||
```yaml
|
||
# clusters/production/apps.yaml
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: shop-postgres
|
||
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
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/shop/backend
|
||
prune: true
|
||
dependsOn:
|
||
- name: shop-postgres
|
||
healthChecks:
|
||
- apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: backend
|
||
namespace: shop-production
|
||
---
|
||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||
kind: Kustomization
|
||
metadata:
|
||
name: shop-frontend-and-routes
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 5m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
path: ./apps/production/shop/frontend
|
||
prune: true
|
||
dependsOn:
|
||
- name: shop-backend
|
||
# IngressRoute и Middleware включены в этот же Kustomization:
|
||
# frontend/deployment.yaml, frontend/service.yaml,
|
||
# ../ingressroute-ui.yaml, ../ingressroute-admin.yaml,
|
||
# ../middleware-auth.yaml
|
||
```
|
||
|
||
---
|
||
|
||
## Image Automation (автообновление образов)
|
||
|
||
Flux умеет самостоятельно обновлять тег образа в Git при появлении нового в registry.
|
||
|
||
```
|
||
GitLab CI билдит образ → пушит в GitLab Container Registry
|
||
↓
|
||
image-reflector замечает новый тег
|
||
↓
|
||
image-automation коммитит обновлённый тег в fleet-репозиторий
|
||
↓
|
||
kustomize/helm-controller деплоит обновлённую версию
|
||
```
|
||
|
||
Конфигурация:
|
||
|
||
```yaml
|
||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||
kind: ImageRepository
|
||
metadata:
|
||
name: my-app
|
||
namespace: flux-system
|
||
spec:
|
||
image: registry.gitlab.gigacoms.info/mygroup/my-app
|
||
interval: 1m
|
||
secretRef:
|
||
name: gitlab-registry-token
|
||
---
|
||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||
kind: ImagePolicy
|
||
metadata:
|
||
name: my-app
|
||
namespace: flux-system
|
||
spec:
|
||
imageRepositoryRef:
|
||
name: my-app
|
||
policy:
|
||
semver:
|
||
range: ">=1.0.0"
|
||
---
|
||
apiVersion: image.toolkit.fluxcd.io/v1beta2
|
||
kind: ImageUpdateAutomation
|
||
metadata:
|
||
name: fleet
|
||
namespace: flux-system
|
||
spec:
|
||
interval: 1m
|
||
sourceRef:
|
||
kind: GitRepository
|
||
name: flux-system
|
||
git:
|
||
checkout:
|
||
ref:
|
||
branch: main
|
||
commit:
|
||
author:
|
||
email: fluxbot@gigacoms.info
|
||
name: FluxBot
|
||
push:
|
||
branch: main
|
||
update:
|
||
path: ./clusters/production
|
||
strategy: Setters
|
||
```
|
||
|
||
---
|
||
|
||
## Интеграция с существующим стеком
|
||
|
||
### Что меняется в текущей инфраструктуре
|
||
|
||
| Компонент | Сейчас | С Flux |
|
||
|---|---|---|
|
||
| Деплой инфраструктуры | Ansible playbooks (вручную/CI) | Без изменений — Ansible остаётся |
|
||
| Деплой приложений | Нет автоматизации | Flux следит за GitLab-проектами |
|
||
| Longhorn (хранилище) | Ansible устанавливает, `storageClassName: longhorn` — доступен всем | Приложения используют через PVC без изменений |
|
||
| Traefik (прокси) | Ansible управляет `traefik_port_map` и EntryPoint-ами | EntryPoint — по-прежнему Ansible; IngressRoute — опционально Flux |
|
||
| Обновление образов | Нет | Image Automation или CI-скрипт в fleet-repo |
|
||
|
||
Ansible и Flux не конкурируют: Ansible управляет **OS и кластером**, Flux управляет
|
||
**приложениями внутри кластера**.
|
||
|
||
### Разделение ответственности: Ansible vs Flux
|
||
|
||
```
|
||
Ansible (roles/longhorn, roles/traefik):
|
||
├── Устанавливает Longhorn, создаёт StorageClass longhorn
|
||
├── Устанавливает Traefik, настраивает EntryPoint-ы (traefik_port_map)
|
||
└── Открывает firewalld-порты
|
||
|
||
Flux (fleet-repo):
|
||
├── Деплоит приложения (Deployment, StatefulSet, Service)
|
||
├── Создаёт PVC с storageClassName: longhorn
|
||
├── Создаёт IngressRoute/Middleware в namespace traefik
|
||
└── Управляет тегами образов
|
||
```
|
||
|
||
### Сетевые требования
|
||
|
||
Flux-контроллеры работают внутри кластера и сами инициируют соединения наружу:
|
||
|
||
- `source-controller` → `gitlab.gigacoms.info:443` (HTTPS или SSH)
|
||
- `image-reflector` → `registry.gitlab.gigacoms.info:443` (если используется)
|
||
- Firewalld на узлах не требует изменений (egress трафик не блокируется)
|
||
|
||
---
|
||
|
||
## Ansible-роль для установки Flux
|
||
|
||
Роль `roles/flux/` уже реализована в проекте. Стандарт GitOps-структуры fleet-repo: `roles/flux/README.md`.
|
||
|
||
```
|
||
roles/flux/
|
||
defaults/main.yml — flux_version, fleet_repo, gitlab_hostname, gitlab_owner
|
||
tasks/main.yml — include_tasks: install → bootstrap
|
||
tasks/install.yml — скачать flux CLI на k8s-manager-01
|
||
tasks/bootstrap.yml — запустить flux bootstrap gitlab
|
||
```
|
||
|
||
Playbook `playbooks/setup_flux.yml` запускается на `manager_nodes`.
|
||
|
||
GitLab CI job:
|
||
|
||
```yaml
|
||
setup:flux:
|
||
stage: setup
|
||
script:
|
||
- ansible-playbook -i $INVENTORY playbooks/setup_flux.yml
|
||
environment:
|
||
name: production
|
||
rules:
|
||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||
when: manual
|
||
resource_group: production
|
||
```
|
||
|
||
Дополнительная CI/CD-переменная: `GITLAB_FLUX_TOKEN` — GitLab PAT для bootstrap.
|
||
|
||
---
|
||
|
||
## Мониторинг и отладка
|
||
|
||
```bash
|
||
# Статус всех Flux-объектов
|
||
flux get all -A
|
||
|
||
# Принудительная синхронизация
|
||
flux reconcile source git flux-system
|
||
flux reconcile kustomization flux-system
|
||
|
||
# Конкретное приложение
|
||
flux reconcile kustomization shop-backend --with-source
|
||
|
||
# Логи контроллеров
|
||
kubectl logs -n flux-system deploy/source-controller
|
||
kubectl logs -n flux-system deploy/kustomize-controller
|
||
|
||
# Подробности о конкретном ресурсе
|
||
flux get kustomization shop-backend --watch
|
||
|
||
# Проверить что IngressRoute применился
|
||
kubectl get ingressroute -n traefik
|
||
|
||
# Проверить что PVC создан и привязан
|
||
kubectl get pvc -n shop-production
|
||
|
||
# Проверить том в Longhorn
|
||
kubectl get volumes.longhorn.io -n longhorn-system
|
||
```
|
||
|
||
---
|
||
|
||
## GitLab Agent для Kubernetes (agentk)
|
||
|
||
GitLab Agent (agentk) — официальный способ подключить кластер Kubernetes к GitLab.
|
||
GitLab рекомендует использовать **Flux + agentk вместе**: Flux синхронизирует состояние
|
||
кластера с Git, agentk даёт видимость в GitLab UI и управление доступом.
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ GitLab: gitlab.gigacoms.info │
|
||
│ GitLab UI → Operate → Kubernetes clusters │
|
||
│ GitLab UI → Operate → Environments (k8s dashboard) │
|
||
└──────────────────────┬──────────────────────────────┘
|
||
│ исходящее соединение (WebSocket)
|
||
│ агент сам подключается к GitLab KAS
|
||
┌──────────────────────▼──────────────────────────────┐
|
||
│ Kubernetes cluster │
|
||
│ namespace: gitlab-agent │
|
||
│ agentk pod ─────────────────────────────────→ │
|
||
│ kubectl / Flux API │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
> **Важно:** agentk сам инициирует соединение **наружу** к GitLab KAS
|
||
> (Kubernetes Agent Server) по WebSocket/gRPC. Входящих портов открывать не нужно.
|
||
> Работает за NAT и firewall — подходит для текущей инфраструктуры.
|
||
|
||
### Что даёт agentk
|
||
|
||
| Возможность | Описание |
|
||
|---|---|
|
||
| Kubernetes dashboard в GitLab UI | Обзор подов, деплойментов, namespace-ов прямо в GitLab |
|
||
| Статус Flux-объектов | Видимость `Kustomization` и `HelmRelease` в окружениях |
|
||
| Ручная синхронизация | Suspend/resume Flux reconciliation из GitLab UI |
|
||
| Логи подов | Просмотр логов контейнеров в GitLab → Environments |
|
||
| CI/CD-доступ к кластеру | `kubectl` в GitLab CI без прямого доступа к kubeconfig |
|
||
| RBAC-управление | Ограничение доступа к namespace-ам для разных проектов |
|
||
|
||
### Установка
|
||
|
||
#### Шаг 1 — Создать конфигурацию агента в GitLab
|
||
|
||
В fleet-репозитории создать файл:
|
||
|
||
```
|
||
.gitlab/agents/production/config.yaml
|
||
```
|
||
|
||
Минимальная конфигурация для Flux + видимости в UI:
|
||
|
||
```yaml
|
||
# .gitlab/agents/production/config.yaml
|
||
|
||
gitops:
|
||
reconcile_timeout: 3600s
|
||
|
||
observability:
|
||
logging:
|
||
level: info
|
||
|
||
ci_access:
|
||
# Разрешить GitLab CI-пайплайнам этого проекта использовать агент
|
||
projects:
|
||
- id: mygroup/my-app
|
||
- id: mygroup/shop
|
||
# Или разрешить всей группе:
|
||
# groups:
|
||
# - id: mygroup
|
||
```
|
||
|
||
#### Шаг 2 — Зарегистрировать агент и получить токен
|
||
|
||
В GitLab: **Infrastructure → Kubernetes clusters → Connect a cluster** (или через `glab`):
|
||
|
||
```bash
|
||
# Через GitLab CLI
|
||
glab cluster agent bootstrap production \
|
||
--repo mygroup/k8s-fleet
|
||
```
|
||
|
||
Или вручную: GitLab → проект → **Operate → Kubernetes clusters → Connect a cluster** →
|
||
ввести имя `production` → скопировать токен.
|
||
|
||
#### Шаг 3 — Установить agentk в кластер через Helm
|
||
|
||
```bash
|
||
helm repo add gitlab https://charts.gitlab.io
|
||
helm repo update
|
||
|
||
helm upgrade --install gitlab-agent gitlab/gitlab-agent \
|
||
--namespace gitlab-agent \
|
||
--create-namespace \
|
||
--set config.token=<agent-token> \
|
||
--set config.kasAddress=wss://gitlab.gigacoms.info/-/kubernetes-agent/
|
||
```
|
||
|
||
Параметры:
|
||
|
||
| Параметр | Значение для вашего стека |
|
||
|---|---|
|
||
| `config.token` | Токен из GitLab (шаг 2) |
|
||
| `config.kasAddress` | `wss://gitlab.gigacoms.info/-/kubernetes-agent/` |
|
||
| `namespace` | `gitlab-agent` |
|
||
|
||
> KAS (Kubernetes Agent Server) уже встроен в GitLab начиная с версии 14.4.
|
||
> Для self-hosted GitLab он доступен по пути `/-/kubernetes-agent/`.
|
||
|
||
#### Установка через Ansible-роль
|
||
|
||
Роль `roles/gitlab_agent/` уже реализована в проекте.
|
||
Playbook: `playbooks/setup_gitlab_agent.yml`.
|
||
CI-переменная: `GITLAB_AGENT_TOKEN`.
|
||
|
||
### Просмотр кластера в GitLab UI
|
||
|
||
После установки agentk в GitLab появится:
|
||
|
||
- **Operate → Kubernetes clusters** — список подключённых кластеров
|
||
- **Operate → Environments** → выбрать окружение → вкладка **Kubernetes** — статус подов
|
||
- Flux-объекты (`Kustomization`, `HelmRelease`) — в окружении после настройки namespace
|
||
|
||
Для отображения Flux-объектов в окружении указать namespace в настройках environment:
|
||
|
||
```yaml
|
||
# .gitlab-ci.yml — окружение с привязкой к namespace
|
||
deploy:
|
||
environment:
|
||
name: production
|
||
kubernetes:
|
||
namespace: flux-system
|
||
```
|
||
|
||
### Связка Flux + agentk
|
||
|
||
Рекомендованная GitLab архитектура:
|
||
|
||
```
|
||
Разработчик пушит в GitLab-проект
|
||
↓
|
||
GitLab CI: сборка образа → пуш в GitLab Registry
|
||
↓
|
||
CI обновляет тег образа в fleet-repo (patch-image.yaml)
|
||
↓
|
||
Flux kustomize-controller применяет изменение:
|
||
- StatefulSet postgres → том из Longhorn (storageClassName: longhorn)
|
||
- Deployment frontend/backend → контейнеры обновлены
|
||
- IngressRoute → маршрут в Traefik (порт 10005)
|
||
↓
|
||
agentk транслирует статус обратно в GitLab UI
|
||
```
|
||
|
||
Flux отвечает за **деплой**, agentk — за **видимость и доступ**.
|
||
|
||
### Сетевые требования
|
||
|
||
Agentk устанавливает исходящее соединение, дополнительных firewalld-правил не требуется:
|
||
|
||
- Кластер → `gitlab.gigacoms.info:443` (WebSocket upgrade) — уже открыто
|
||
|
||
---
|
||
|
||
## Альтернативы
|
||
|
||
| Инструмент | Плюсы | Минусы |
|
||
|---|---|---|
|
||
| **Flux CD** | Нативный GitOps, много CRD, image automation | Сложнее начать, несколько контроллеров |
|
||
| **ArgoCD** | Удобный UI, проще для начала | Один монолитный процесс, нет image automation |
|
||
| **GitLab Agent (agentk)** | Нативная интеграция с GitLab UI | Требует постоянного соединения с GitLab |
|
||
|
||
GitLab официально рекомендует связку **Flux + agentk**: Flux синхронизирует состояние,
|
||
agentk обеспечивает видимость в GitLab UI и управление доступом.
|
||
|
||
---
|
||
|
||
## Рекомендуемый план внедрения
|
||
|
||
1. **Создать fleet-репозиторий** `k8s-fleet` в GitLab-группе на `gitlab.gigacoms.info`
|
||
2. **Ansible-роль `flux`** уже готова — запустить `playbooks/setup_flux.yml`
|
||
3. **Добавить первое приложение** через `GitRepository` + `Kustomization`:
|
||
- PVC с `storageClassName: longhorn` если нужно хранилище
|
||
- IngressRoute в namespace `traefik` если нужен внешний доступ (порт предварительно зарезервировать в `traefik_port_map`)
|
||
4. **Ознакомиться со стандартами** компонентов:
|
||
- `roles/flux/README.md` — структура fleet-repo, многоветочный деплой, все примеры
|
||
- `roles/longhorn/README.md` — PVC, StatefulSet, реплики, диагностика
|
||
- `roles/traefik/README.md` — port_map, IngressRoute, BasicAuth, таблица портов
|
||
5. **Опционально**: настроить Image Automation для автодеплоя по новому тегу образа
|
||
|
||
---
|
||
|
||
## Источники
|
||
|
||
- [Flux — официальный сайт](https://fluxcd.io/)
|
||
- [Flux bootstrap для GitLab](https://fluxcd.io/flux/installation/bootstrap/gitlab/)
|
||
- [flux bootstrap gitlab — справка по команде](https://fluxcd.io/flux/cmd/flux_bootstrap_gitlab/)
|
||
- [GitOps компоненты Flux](https://fluxcd.io/flux/components/)
|
||
- [HelmRelease CRD](https://fluxcd.io/flux/components/helm/helmreleases/)
|
||
- [Kustomization CRD](https://fluxcd.io/flux/components/kustomize/kustomizations/)
|
||
- [GitLab: Using GitOps with a Kubernetes cluster](https://docs.gitlab.com/user/clusters/agent/gitops/)
|
||
- [GitLab: интеграция с Flux CD](https://about.gitlab.com/blog/why-did-we-choose-to-integrate-fluxcd-with-gitlab/)
|
||
- [Пример flux2-kustomize-helm](https://github.com/fluxcd/flux2-kustomize-helm-example)
|
||
- Стандарт хранилища кластера: `roles/longhorn/README.md`
|
||
- Стандарт публикации сервисов: `roles/traefik/README.md`
|
||
- Стандарт GitOps-структуры fleet-repo: `roles/flux/README.md`
|