# Исследование: 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= flux bootstrap gitlab \ --hostname=gitlab.gigacoms.info \ --owner= \ --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//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= ``` Или через 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= \ --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`