41 KiB
Исследование: 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
# На 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
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 выполнит:
- Создаст репозиторий
k8s-fleetв GitLab (если не существует) - Сгенерирует Deploy Key и добавит в репозиторий
- Установит Flux-контроллеры в namespace
flux-system - Запушит начальные манифесты в
clusters/production/flux-system/
Подключение приложения из GitLab-проекта
После bootstrap, чтобы Flux следил за конкретным проектом:
Вариант A: Kustomize (plain YAML / kustomize)
# 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
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 для доступа к приватному репозиторию
# Создать 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:
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: простое приложение с постоянным хранилищем
# 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)
# 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.
# 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):
# 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:
# 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):
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):
# 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
Ключевые манифесты:
# 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):
# 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 деплоит обновлённую версию
Конфигурация:
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:
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.
Мониторинг и отладка
# Статус всех 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:
# .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):
# Через GitLab CLI
glab cluster agent bootstrap production \
--repo mygroup/k8s-fleet
Или вручную: GitLab → проект → Operate → Kubernetes clusters → Connect a cluster →
ввести имя production → скопировать токен.
Шаг 3 — Установить agentk в кластер через Helm
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:
# .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 и управление доступом.
Рекомендуемый план внедрения
- Создать fleet-репозиторий
k8s-fleetв GitLab-группе наgitlab.gigacoms.info - Ansible-роль
fluxуже готова — запуститьplaybooks/setup_flux.yml - Добавить первое приложение через
GitRepository+Kustomization:- PVC с
storageClassName: longhornесли нужно хранилище - IngressRoute в namespace
traefikесли нужен внешний доступ (порт предварительно зарезервировать вtraefik_port_map)
- PVC с
- Ознакомиться со стандартами компонентов:
roles/flux/README.md— структура fleet-repo, многоветочный деплой, все примерыroles/longhorn/README.md— PVC, StatefulSet, реплики, диагностикаroles/traefik/README.md— port_map, IngressRoute, BasicAuth, таблица портов
- Опционально: настроить Image Automation для автодеплоя по новому тегу образа
Источники
- Flux — официальный сайт
- Flux bootstrap для GitLab
- flux bootstrap gitlab — справка по команде
- GitOps компоненты Flux
- HelmRelease CRD
- Kustomization CRD
- GitLab: Using GitOps with a Kubernetes cluster
- GitLab: интеграция с Flux CD
- Пример flux2-kustomize-helm
- Стандарт хранилища кластера:
roles/longhorn/README.md - Стандарт публикации сервисов:
roles/traefik/README.md - Стандарт GitOps-структуры fleet-repo:
roles/flux/README.md