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

41 KiB
Raw Blame History

Исследование: 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

# На 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 выполнит:

  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)

# 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 и слушает порты 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):

# 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-controllergitlab.gigacoms.info:443 (HTTPS или SSH)
  • image-reflectorregistry.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 и управление доступом.


Рекомендуемый план внедрения

  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 для автодеплоя по новому тегу образа

Источники