k8s/research/flux.md
2026-07-15 11:14:21 +03:00

1153 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Исследование: 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-прокси для публикации сервисов наружу (порты 1000110999). Стандарт использования: `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) — порт-прокси 1000110999 │
└─────────────────────────────────────────────────────────────┘
```
### Основные 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 и слушает порты 1000110999 как 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`