Cozystack Руководство по разработке
Как это работает
Cozystack — платформа на основе операторов. Начальная загрузка и текущее управление осуществляются набором контроллеров, работающих внутри кластера. Высокоуровневый поток выглядит так:
Инсталлятор чарта (
packages/core/installer) применяется черезhelm install. Он разворачивает Deploymentcozystack-operatorв пространстве имёнcozy-system.cozystack-operator запускается и выполняет однократную начальную загрузку:
- Устанавливает CRD Cozystack (
Package,PackageSource) из встроенных манифестов (internal/crdinstall). - Устанавливает компоненты Flux (source-controller, helm-controller,
source-watcher) из встроенных манифестов (
internal/fluxinstall). - Создаёт начальный OCIRepository (
cozystack-platform) из значенийplatformSourceUrlиplatformSourceRef, заданных в инсталляторе. - Создаёт
PackageSource, ссылающийся на начальный OCIRepository.
- Устанавливает CRD Cozystack (
Цикл согласования берёт управление. Оператор следит за CRD
PackageSourceиPackageи преобразует их в объекты FluxHelmRelease. Flux затем устанавливает реальные Helm-чарты и управляет ими.Platform chart (
packages/core/platform) разворачивается как обычный Package. Он читает конфигурацию кластера из ресурсаcozystack.cozystack-platformPackage и создаёт шаблоны манифестов пакетов, определяющих, какие системные компоненты должны быть установлены.Platform chart также создаёт вторичный OCIRepository (
cozystack-packages), копируя спецификацию из начального OCIRepository. Все PackageSource ссылаются на этот вторичный репозиторий. При обновлениях platform chart выполняет миграции какpre-upgradeхуки перед созданием или обновлением HelmRelease компонентов.FluxCD — движок выполнения, он согласует объекты
HelmRelease, созданные оператором, загружает артефакты чартов из ресурсовExternalArtifactи применяет их к кластеру.
Полную цепочку согласования (PackageSource → ArtifactGenerator → ExternalArtifact → Package → HelmRelease → Pods), разрешение зависимостей, потоки обновления и отката, а также CLI cozypkg см. в разделе Ключевые концепции.
OCIRepository и поток миграции
Cozystack использует два ресурса OCIRepository для управления обновлениями платформы:
| OCIRepository | Создаётся | Ссылается на |
|---|---|---|
cozystack-platform | cozystack-operator | Настраивается через значения инсталлятора (platformSourceUrl, platformSourceRef) |
cozystack-packages | Platform chart (repository.yaml) | Копирует спецификацию из cozystack-platform |
Все PackageSource в packages/core/platform/sources/ ссылаются на cozystack-packages.
Выполнение миграций
Миграции выполняются как Helm pre-upgrade хуки в platform chart:
# packages/core/platform/templates/migration-hook.yaml
metadata:
name: cozystack-migration-hook
annotations:
helm.sh/hook: pre-upgrade,pre-install
helm.sh/hook-weight: "1"
Контейнер миграции считывает текущую версию из ConfigMap cozystack-version и выполняет скрипты миграции последовательно от CURRENT_VERSION до TARGET_VERSION - 1. Каждая миграция обновляет ConfigMap при успехе, обеспечивая идемпотентность миграций и возможность возобновления после сбоев.
Зачем два репозитория?
Разделение гарантирует, что:
- Начальный OCIRepository управляется оператором (через значения инсталлятора).
- Все PackageSource имеют согласованную ссылку (
cozystack-packages), а не указывают напрямую на источник, управляемый оператором. - Platform chart может выполнять миграции перед созданием вторичного OCIRepository, гарантируя выполнение миграций до обновления компонентов.
Ключевые бинарные файлы
| Бинарный файл | Источник | Роль |
|---|---|---|
| cozystack-operator | cmd/cozystack-operator | Начальная загрузка (CRD, Flux, источник платформы), согласование PackageSource и Package, репликация секрета cozystack-values. |
| cozystack-controller | cmd/cozystack-controller | Согласование рабочих нагрузок и ApplicationDefinition, управление дашбордами. |
| cozystack-api | cmd/cozystack-api | Уровень агрегации API Kubernetes для групп API apps.cozystack.io и core.cozystack.io. |
| cozypkg | cmd/cozypkg | CLI-инструмент для управления пакетами — визуализация зависимостей, интерактивная установка и удаление. |
Структура репозитория
Основная структура репозитория cozystack:
.
├── api # Go-типы для CRD Cozystack (Package, PackageSource и т.д.)
├── cmd # Точки входа для всех бинарных файлов
│ ├── cozystack-operator # Основной оператор платформы
│ ├── cozystack-controller # Контроллеры рабочих нагрузок и приложений
│ ├── cozystack-api # Агрегированный API-сервер
│ └── cozypkg # CLI для управления пакетами
├── internal # Реализации контроллеров и согласователей
│ ├── operator # Согласователи PackageSource и Package
│ ├── controller # Контроллеры рабочих нагрузок и ApplicationDefinition
│ ├── fluxinstall # Встроенные манифесты Flux и инсталлятор
│ ├── crdinstall # Встроенные манифесты CRD и инсталлятор
│ └── cozyvaluesreplicator # Логика репликации секретов
├── packages # Helm-чарты, организованные по слоям
│ ├── core # Начальная загрузка и конфигурация платформы
│ ├── system # Инфраструктурные операторы и upstream-чарты
│ ├── apps # Пользовательские чарты приложений
│ └── extra # Чарты приложений для конкретных тенантов
├── pkg # Общие Go-библиотеки
├── dashboards # Дашборды Grafana
├── hack # Вспомогательные скрипты для локальной разработки
└── docs # Журналы изменений и примечания к релизам
Разработку можно вести локально, изменяя и обновляя файлы в этом репозитории.
Пакеты
Cozystack по своей сути — это провайдер управляемых сервисов. Подобно управляемым предложениям AWS или Google Cloud, пользователь приходит, чтобы заказать конечную сущность — базу данных PostgreSQL, очередь Kafka, S3-бакет, кластер Kubernetes, виртуальную машину — а не для того, чтобы самостоятельно собирать инфраструктуру под ними. Каждая из этих сущностей является объектом первого класса в API Cozystack (apps.cozystack.io): пользователь декларирует, что он хочет, а платформа сама обеспечивает провижининг и работу реализации под капотом. Пользователь получает эндпоинт и учётные данные и никогда не должен знать — или даже видеть — как и где на самом деле работает сервис.
Четыре категории пакетов напрямую следуют из этой модели:
core— как платформа загружается и настраивает саму себя.system— операторы и апстрим-чарты, которые фактически запускают рабочие нагрузки.apps— управляемые сервисы первого класса, которые пользователь заказывает напрямую.extra— модули-«включатели», которые арендатор (tenant) активирует, и которые обеспечивают работу этих сервисов «под капотом», но сами не заказываются как самостоятельные сервисы.
Разделение между apps и extra понимается неправильно чаще всего, поэтому оно подробно разъясняется ниже.
core
Core-пакеты отвечают за начальную загрузку и конфигурацию на уровне платформы.
installer
Helm-чарт, разворачивающий Deployment cozystack-operator. Он создаёт
пространство имён cozy-system, ServiceAccount с правами cluster-admin и
Deployment оператора с флагами, инициирующими установку CRD и Flux при запуске.
Образ оператора и URL источника платформы задаются во время сборки.
platform
Helm-чарт, разворачиваемый как обычный Package (не применяется напрямую). Он читает
конфигурацию кластера из ресурса cozystack.cozystack-platform
Package
и создаёт шаблоны манифестов согласно указанному
варианту и
настройкам компонентов, определяя, какие системные компоненты должны быть установлены.
flux-aio
Компоненты Flux, упакованные для развёртывания оператором.
talos
Конфигурационные ресурсы Talos OS.
helm template . | kubectl apply -f -.system
System-пакеты настраивают систему для управления и развёртывания пользовательских приложений. Необходимые системные компоненты указываются в конфигурации пакета.
System-пакеты включают два вида компонентов:
- Операторы (например,
postgres-operator,kafka-operator,redis-operator): Контроллеры, умеющие управлять полным жизненным циклом конкретного приложения, включая операции второго дня. - Upstream Helm-чарты для приложений без выделенного оператора (например,
nats,ingress-nginx): Эти чарты размещаются в system, чтобы пакеты apps и extra могли разворачивать их через FluxHelmReleaseCR, фактически используя FluxCD в качестве оператора.
apps
apps — это управляемые сервисы первого класса, которые пользователь заказывает напрямую. Каждый из них представляет собой конечную сущность, отображаемую в каталоге дашборда и доступную через API apps.cozystack.io: apps/postgres («Managed PostgreSQL service»), apps/kubernetes («Managed Kubernetes service»), apps/kafka, apps/bucket (S3-бакет), apps/vm-instance и так далее.
Чарт приложения — это высокоуровневый API, а не рецепт развёртывания. Он определяет только те параметры, которые должны быть доступны пользователю и валидироваться через values.schema.json, сохраняя интерфейс минималистичным и безопасным — например, пользователь выбирает версию Postgres, но не может переопределить образ контейнера. Сам чарт не содержит бизнес-логики запуска приложения; вместо этого он делегирует выполнение оператору или FluxCD. Такой тонкий API-слой поверх «сырого» оператора нужен для того, чтобы платформа сохраняла полный контроль над всеми входными параметрами (безопасность) и предоставляла пользователю готовый к использованию конечный сервис (UX).
В зависимости от наличия выделенного оператора приложения apps следуют одному из двух паттернов:
Паттерн на основе оператора
Когда для приложения есть выделенный оператор (например, PostgreSQL, MongoDB, Redis, Kafka), чарт app создаёт экземпляры CRD, которыми управляет оператор:
packages/system/postgres-operator/ # Helm-чарт оператора
packages/apps/postgres/ # App-чарт создаёт postgresql.cnpg.io/v1.Cluster CR
Оператор обрабатывает все детали развёртывания и операции второго дня (масштабирование, резервное копирование, переключение при сбое). App-чарт просто создаёт соответствующий CRD со значениями, полученными из пользовательского ввода.
Паттерн на основе HelmRelease
Когда для приложения нет выделенного оператора и стандартным методом развёртывания является Helm-чарт,
upstream-чарт размещается в system/, а app-чарт создаёт
Flux HelmRelease CR, указывающий на него:
packages/system/nats/ # Upstream Helm-чарт NATS
packages/apps/nats/ # App-чарт создаёт helm.toolkit.fluxcd.io/v2.HelmRelease
В этом случае FluxCD выступает оператором, управляя жизненным циклом Helm-релиза. App-чарт контролирует, какие upstream-значения открываются пользователю, обеспечивая дополнительный уровень безопасности — пользователи не могут обойти валидацию для развёртывания чарта с произвольными значениями.
Другие примеры этого паттерна: extra/ingress, extra/seaweedfs, extra/monitoring.
extra
Пакеты extra — это модули-активаторы, а не полноценные сервисы. Пользователь никогда не заказывает их как конечную сущность; вместо этого они включаются как опции тенанта, и после включения предоставляют возможности, на которых строятся сервисы apps, работая «под капотом». По этой причине они не отображаются в каталоге приложений (вместо этого они появляются в разделе Администрирование → Модули) и могут быть установлены только в рамках тенанта. Поскольку модуль extra включается на уровне тенанта, он становится общим для дочерних (нижестоящих) тенантов, вложенных в пространство имён этого тенанта — развёрнутым один раз и используемым ими совместно (например, дочерний тенант без собственного monitoring отправляет свои метрики в стек мониторинга родительского тенанта, а не разворачивает вторую копию).
Наиболее наглядный пример — объектное хранилище:
extra/seaweedfsразворачивает кластер SeaweedFS и регистрирует ресурсыBucketClassдля тенанта.apps/bucket(«S3-совместимое хранилище») — это то, что пользователь фактически заказывает: создаётсяBucketClaimна один из этихBucketClass.
Таким образом, администратор тенанта включает модуль SeaweedFS один раз, и с этого момента пользователи могут заказывать S3-бакеты как полноценный сервис. Пользователь потребляет бакет; он никогда не видит, не заказывает и не управляет самим SeaweedFS — это деталь реализации «S3-бакета». Другие модули extra предоставляют инфраструктуру уровня тенанта, а не заказываемые сервисы: extra/ingress (NGINX Ingress Controller), extra/monitoring (стек мониторинга тенанта) и extra/gateway (Gateway API на уровне тенанта на основе Cilium; только переключатель — не отображается в дашборде и включается автоматически для тенантов с производным apex-доменом).
Подробнее о Системе тенантов читайте на странице основных концепций.
В одном пространстве имён тенанта можно использовать только один тип приложения.
Extra-пакеты следуют тем же двум архитектурным паттернам, что и apps (на основе оператора или HelmRelease).
Выбор между apps, extra и встроенной зависимостью
При добавлении новой возможности решите, куда она относится, задав вопрос: кто её потребляет?
Заказывает ли её пользователь напрямую как конечный сервис? Тогда это полноценный управляемый сервис → apps, отображаемый в каталоге (например, apps/postgres, apps/bucket).
Является ли это общей зависимостью — используемой несколькими приложениями или разделяемой между тенантами? Тогда это компонент, который платформа/тенант включает один раз, а на нём строятся многие другие вещи → extra (например, extra/seaweedfs служит основой для каждого apps/bucket; extra/monitoring собирает метрики для всех приложений в тенанте).
Является ли это единственной, приватной зависимостью одного приложения, не разделяемой ни с кем? Тогда это вообще не пакет — она встраивается внутрь потребляющего чарта и разворачивается вместе с ним, невидимая для пользователя. Например, стек monitoring поставляет собственную базу PostgreSQL для Alerta как часть своего релиза (бывшее приложение ferretdb аналогично поставляло свою PostgreSQL внутри чарта); пользователь не видит эти внутренние базы данных и не имеет к ним доступа.
Зависимости также существуют между полноценными сервисами. Когда зависимость сама по себе является чем-то, что пользователь создаёт, хранит и управляет самостоятельно, она остаётся сервисом apps, а другие приложения просто ссылаются на неё, а не встраивают её. Например, apps/vm-disk («диск виртуальной машины») заказывается отдельно, а apps/vm-instance подключает один или несколько существующих дисков по имени (в дашборде отображается список доступных дисков для выбора). У диска есть собственный жизненный цикл — он может существовать дольше инстанса, отключаться и подключаться заново или объединяться с несколькими дисками на одной ВМ — поэтому это самостоятельный сервис, а не что-то скрытое внутри vm-instance.
Два вопроса решают большинство случаев: кто заказывает (пользователь → apps; платформа или тенант → extra) и имеет ли это ценность само по себе (да → отдельный сервис apps, на который ссылаются другие; нет → встраивается и скрывается внутри потребляющего чарта). Совместное использование смещает чашу весов в сторону extra: зависимость, которую нужно развернуть один раз и использовать в нескольких приложениях или тенантах, становится модулем, а не пакетом на каждый экземпляр.
Структура пакета
Каждый пакет — это типичный Helm-чарт, содержащий все необходимые образы и манифесты
для платформы. Мы следуем логике umbrella-чарта, размещая upstream-чарты в
директории ./charts и переопределяя values.yaml в корне приложения.
Такая структура упрощает обновление upstream-чартов.
.
├── Chart.yaml # Определение Helm-чарта и описание параметров
├── Makefile # Общие цели для упрощения локальной разработки
├── charts # Директория для upstream-чартов
├── images # Директория для Docker-образов
├── patches # Опциональная директория для патчей upstream-чартов
├── templates # Дополнительные манифесты для upstream Helm-чарта
├── templates/dashboard-resourcemap.yaml # Роль для отображения ресурсов k8s в дашборде
├── values.yaml # Значения переопределения для upstream Helm-чарта
└── values.schema.json # JSON-схема для валидации входных значений и отрисовки элементов UI в дашборде
Используйте
cozyvalues-gen для генерации таблицы параметров в README.md и схемы values.schema.json на основе аннотаций в values.yaml.
Установите бинарный файл cozyvalues-gen со страницы релизов и выполните make generate в директории пакета. Помимо схемы и README, эта команда также обновляет ApplicationDefinition пакета (spec.application.openAPISchema и spec.dashboard.keysOrder) через hack/update-crd.sh.
Принципы разработки Helm-чартов
Структура пакетов и рабочий процесс разработки в Cozystack основаны на следующих принципах:
Простое обновление upstream-чартов
Оригинальный upstream-чарт должен быть легко обновляемым, переопределяемым и изменяемым. Мы используем паттерн umbrella-чарта — upstream-чарты находятся в директории ./charts и хранятся в оригинальном виде. Кастомизации вносятся через переопределения values.yaml и дополнительные templates/, а структурные изменения в upstream-чарте применяются через patches/. Такое разделение гарантирует простое обновление до новой upstream-версии: выполните make update, просмотрите diff и при необходимости повторно примените патчи.
Локальные артефакты
Патчи и образы контейнеров хранятся локально и являются частью пакета. Директория patches/ содержит любые изменения upstream-чарта, а директория images/ — Dockerfile для сборки всех необходимых образов. Это обеспечивает полную воспроизводимость — всё необходимое для сборки и развёртывания пакета самодостаточно внутри репозитория.
Рабочий процесс локальной разработки и тестирования
Каждый пакет должен быть легко обновляемым и тестируемым локально на реальном кластере без использования CI. Стандартные цели make (make image, make diff, make apply) обеспечивают быструю обратную связь: сборка образов, сравнение отрендеренных манифестов с живым кластером и применение изменений — всё с рабочей станции разработчика.
Без внешних зависимостей
Пакеты не должны зависеть от внешних ресурсов во время выполнения. Все чарты, образы и патчи включены в репозиторий. Это гарантирует детерминированность сборок и развёртываний, а также отсутствие сбоев из-за недоступности upstream-реестров, удалённых тегов или проблем с сетью.
Разработка
Настройка Buildx
Для сборки образов необходимо установить и настроить плагин
docker buildx.
Вместо встроенного сборщика можно
настроить дополнительные, которые могут быть удалёнными или поддерживать несколько архитектур.
В этом примере показано, как создать сборщик с драйвером kubernetes, позволяющим собирать образы непосредственно в кластере Kubernetes:
docker buildx create \
--bootstrap \
--name=buildkit \
--driver=kubernetes \
--driver-opt=namespace=tenant-kvaps,replicas=2 \
--platform=linux/amd64 \
--platform=linux/arm64 \
--use
Либо опустите параметры –driver*, чтобы настроить среду сборки в локальном Docker-окружении.
Управление пакетами
Каждое приложение включает Makefile для упрощения процесса разработки. Для каждого пакета мы следуем такой логике:
make update # Обновить Helm-чарт и версии из upstream-источника
make image # Собрать Docker-образы, используемые в пакете
make show # Показать вывод отрендеренных шаблонов
make diff # Сравнить Helm-релиз с объектами в кластере Kubernetes
make apply # Применить Helm-релиз к кластеру Kubernetes
Например, для обновления cilium:
cd packages/system/cilium # Перейти в директорию приложения
make update # Загрузить новую версию из upstream
make image # Собрать образ cilium
git diff . # Показать diff с изменёнными манифестами
make diff # Показать diff с применёнными манифестами кластера
make apply # Применить изменённые манифесты к кластеру
kubectl get pod -n cozy-cilium # Проверить корректность работы
git commit -m "Update cilium" # Зафиксировать изменения в ветке
Для сборки контейнера cozystack с обновлённым чартом:
cd packages/core/installer # Перейти в директорию пакета cozystack
make image-packages # Собрать образ пакетов
make apply # Применить к кластеру
kubectl get pod -n cozy-system # Проверить корректность работы
kubectl get hr -A # Проверить объекты HelmRelease
При пересборке образов указывайте переменную окружения REGISTRY, указывающую на ваш Docker-реестр.
Не стесняйтесь заглядывать в каждый Makefile, чтобы лучше понять логику.
Тестирование
Платформа включает скрипт
e2e.sh, выполняющий следующие задачи:
- Запускает три виртуальные машины QEMU
- Настраивает Talos Linux
- Устанавливает Cozystack
- Ожидает установки всех HelmRelease
- Выполняет дополнительные проверки работоспособности компонентов
Скрипт e2e.sh можно запускать как локально, так и непосредственно в контейнере Kubernetes.
Для запуска тестов в кластере Kubernetes перейдите в директорию packages/core/testing и выполните следующие команды:
make apply # Создать тестовую песочницу в кластере Kubernetes
make test # Запустить end-to-end тесты в существующей песочнице
make delete # Удалить тестовую песочницу из кластера Kubernetes
⚠️ Для запуска e2e-тестов в кластере Kubernetes узлы должны иметь достаточно свободных ресурсов для создания 3 ВМ и хранения данных развёртываемых приложений.
Рекомендуется использовать bare-metal узлы родительского кластера Cozystack.
Динамическая среда разработки
Если вы предпочитаете разрабатывать Cozystack в виртуальных машинах вместо изменения существующего кластера, можно использовать ту же песочницу из тестовой среды. Makefile в packages/core/testing включает дополнительные опции:
make exec # Открывает интерактивную оболочку в контейнере песочницы.
make login # Загружает kubeconfig во временную директорию и запускает оболочку с окружением песочницы; требуется установленный mirrord.
make proxy # Включает SOCKS5 прокси-сервер; требуются mirrord и gost.
Прокси Socks5 можно настроить в браузере для доступа к сервисам кластера, работающего в песочнице. В Firefox есть удобное расширение для переключения прокси: