Капстоун, шаг 1: планирование пакета Firefly III

Уровень: expert ~55 мин Практика: Firefly III (AGPL-3.0)

Что нужно знать перед уроком

  • M10.10 — Troubleshooting по статусной модели — весь курс M0–M10 пройден — капстоун применяет накопленные знания самостоятельно, без новой теории
  • M5.01 — Анатомия пакета Application целиком — нужна структура пакета как отправная точка планирования

Что нужно знать перед уроком

Это первый из пяти уроков капстоуна — итогового модуля курса. В отличие от всех предыдущих уроков, где новая концепция вводилась и тут же закреплялась практикой, капстоун не вводит новых концепций — он требует самостоятельно спланировать и собрать production-ready Application-пакет, применяя весь стек знаний M0–M10 к новому приложению — Firefly III, self-hosted менеджер личных финансов (PHP/Laravel + MySQL/MariaDB, AGPL-3.0). Перед этим уроком нужно уверенно ориентироваться в: анатомии пакета (M5 урок 1), полях package.yaml (M5 урок 2), контракте настроек (M5 урок 3), ограничениях Application (M5 урок 5) и модели requirements.modules.mandatory (M6 урок 1).

Теория

Почему именно Firefly III для капстоуна

На протяжении курса каждый урок использовал новое self-hosted приложение возрастающей сложности — от тривиального статического контейнера Excalidraw (M1 урок 1) до полноценного сквозного примера Umami (M5M10). Firefly III выбран для финального капстоуна по трём причинам, делающим его показательным mid-size приложением:

  1. PHP/Laravel-стек — ни разу не встречался в курсе (Vikunja — Go, Umami — Node.js), что вынуждает самостоятельно перенести уже усвоенные паттерны (werf-сборка, Nelm-шаблоны, миграции) на незнакомый рантайм, а не копировать готовое решение.
  2. Жёсткая зависимость от реляционной БД — Firefly III не может работать без MySQL/MariaDB (или PostgreSQL, но капстоун фиксирует MySQL для симметрии с уже пройденным managed-postgres в M6), что требует полноценного применения requirements.modules.mandatory с новым модулем — managed-mysql.
  3. Одноразовый секрет уровня приложения (APP_KEY, аналог Laravel APP_KEY — 32-байтный ключ шифрования, генерируемый один раз и не должный меняться между обновлениями) — заставляет применить паттерн «ресурс только при первой установке» из M7 урока 8 (use case 6, deploy-on: install + ownership: anyone) в реальном сценарии, а не в изолированном примере.

Формальный план работы: пять шагов капстоуна

flowchart LR S1["Шаг 1 (этот урок)\nПланирование + анатомия"] --> S2["Шаг 2\nСборка образа (werf)"] S2 --> S3["Шаг 3\nApplication-чарт + managed-mysql"] S3 --> S4["Шаг 4\nCI/CD: build/verify/publish"] S4 --> S5["Шаг 5\nУстановка + чеклист приёмки"]

Это прямое повторение маршрута всего курса в миниатюре: M3 (сборка) → M4–M7 (Nelm-чарт и зависимости) → M8–M9 (CI/CD и публикация) → M10 (эксплуатационная готовность), только все решения принимает сам разработчик, а не берёт готовые ответы из теории урока.

Шаг планирования 1 — анализ приложения перед написанием кода

Прежде чем создавать структуру пакета командой d8 package bootstrap (M5 урок 1), необходимо ответить на вопросы, которые в предыдущих модулях уже были даны в готовом виде для Umami/Vikunja:

ВопросОтвет для Firefly III
Какой рантайм и какой официальный образ существует?PHP 8.x + Apache/FPM, официальный образ fireflyiii/core на Docker Hub — используется как референс для собственной Stapel-сборки (M3 урок 3), а не копируется напрямую
От какого Module зависит функционально?managed-mysql (mandatory) — без базы данных приложение не проходит даже первичную инициализацию Laravel
Нужен ли постоянный том?Да — каталог storage/upload (вложения транзакций) и storage/logs; данные критичны — требуется полная модель защиты из M6 урока 7
Нужна ли Job-миграция?Да — php artisan migrate --force перед запуском основного контейнера, паттерн из M6 урока 4
Нужен ли одноразовый секрет?Да — APP_KEY (php artisan key:generate), паттерн deploy-on: install + ownership: anyone из M7 урока 8
Какая внешняя точка входа?HTTP через Ingress/HTTPRoute (M1 уроки 8–9)
Какой уровень сложности проб?startupProbe обязателен (миграция и прогрев Laravel-кэша конфигурации занимают заметное время при первом старте, аналогично мотивации startupProbe в M1 уроке 7 на примере Node-RED)

Этот вопросник — не формальность, а систематизация того самого анализа, который в модулях M1–M10 курс проводил за студента при выборе очередного OSS-приложения; теперь эта работа выполняется самостоятельно.

package.yaml для Firefly III

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: v1
type: "Application"
name: firefly-iii
descriptions:
  ru: "Личные финансы: учёт доходов, расходов, бюджетов и целей"
  en: "Personal finance manager: budgets, transactions, and goals"
version: "0.1.0"
stage: "Preview"
category: "Finance"
requirements:
  deckhouse:
    constraint: ">= 1.60.0"
  kubernetes:
    constraint: ">= 1.31.0"
  modules:
    mandatory:
      - name: managed-mysql
        constraint: ">= 1.0.0"

Прямое применение полей, разобранных в M5 уроке 2apiVersion/type/stage/category как обязательные поля манифеста, name (≤ 24 символов, соблюдено), descriptions на обоих языках (M9 урок 7 требует их для приёмки), объектный формат requirements.deckhouse.constraint/requirements.kubernetes.constraint (а не плоская строка), и ключевая для капстоуна секция requirements.modules.mandatory, зафиксированная в M6 уроке 1 как единственно допустимый способ для Application задекларировать жёсткую зависимость от платформенного компонента.

Контракт настроек: config-values.yaml для Firefly III

Проектируется по правилам M5 урока 3 — пользователю экспонируются только осмысленные для конфигурации параметры, внутренние детали (сгенерированный APP_KEY, реквизиты managed-mysql) остаются в internal.* и никогда не попадают в config-values.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
x-config-version: 1
type: object
properties:
  hostname:
    type: string
    description: "FQDN, на котором будет доступен Firefly III"
  defaultCurrency:
    type: string
    default: "USD"
    description: "Код валюты по умолчанию (ISO 4217)"
  timezone:
    type: string
    default: "UTC"
  storageSize:
    type: string
    default: "5Gi"
    description: "Размер PVC для вложений транзакций и логов"
required:
  - hostname

Частые ошибки и подводные камни

  • Начинать с написания шаблонов Nelm до завершения планирования зависимостей. Как показывает вопросник выше, решение «какой Module обязателен» напрямую влияет на структуру templates/ (потребуется Secret/ConfigMap с реквизитами подключения к БД, разобранный принцип получения данных о зависимом Module — M6 урок 2) — планирование зависимостей должно предшествовать написанию манифестов, а не следовать за ним.
  • Копировать официальный docker-entrypoint.sh образа fireflyiii/core дословно, включая логику авто-миграции при старте контейнера. Официальный образ мигрирует схему БД автоматически при каждом запуске — это удобно для одиночного docker run, но противоречит паттерну курса «явный Job-миграция перед Deployment» (M6 урок 4), обеспечивающему предсказуемый, наблюдаемый и не гоняющийся параллельно с несколькими репликами процесс миграции.
  • Забыть зафиксировать вопросник анализа приложения в документации пакета. Ответы на вопросы из таблицы (почему выбран именно managed-mysql, почему нужен startupProbe) стоит явно задокументировать в docs/README.md пакета — это упрощает онбординг следующего разработчика, продолжающего сопровождение пакета.

Linux / Сети / Docker

Firefly III в официальном образе объединяет PHP-FPM и веб-сервер (Apache или nginx+php-fpm, в зависимости от варианта тега образа) в одном контейнере — это классический паттерн «multi-process container», отличающийся от связки из двух отдельных образов/контейнеров (например, отдельно nginx и отдельно php-fpm как sidecar), которую студент мог видеть в других PHP-проектах. Собственная Stapel-сборка капстоуна (следующий урок) должна явно решить, следовать ли этому паттерну (один процесс-менеджер вроде supervisord внутри контейнера) или разделить на два контейнера в одном Pod — оба подхода валидны, различие в компромиссах, разобранных в M1 уроке 1 (совместное размещение процессов в одном Pod оправдано только при тесной связи жизненного цикла).

Практика в кластере

🧩 Практика урока: Firefly III · лицензия AGPL-3.0

Практики установки в кластере в этом уроке нет — она начнётся в M11 уроке 5, после завершения сборки образа (шаг 2) и Application-чарта (шаг 3). На этом шаге — только подготовка окружения:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
d8 k get moduleconfig managed-mysql -o jsonpath='{.spec.enabled}'; echo
# если модуль ещё не включён в вашем практическом кластере — включите его сейчас, он понадобится в M11 уроке 3
d8 k apply -f - <<'EOF'
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: managed-mysql
spec:
  enabled: true
EOF

Практика разработки

  1. Изучите официальный репозиторий Firefly III, обратив внимание на .env.example (полный список переменных окружения — основа для будущего openapi/values.yaml) и docker/entrypoint.sh (что именно происходит при старте — для сравнения с собственным явным паттерном миграции):
1
2
git clone --depth 1 https://github.com/firefly-iii/firefly-iii.git firefly-iii-src
grep -E '^(DB_|APP_KEY|APP_URL|TZ)' firefly-iii-src/.env.example
  1. Инициализируйте скелет пакета:
1
2
d8 package bootstrap application firefly-iii --hooks --werf
cd firefly-iii
  1. Заполните package.yaml и openapi/config-values.yaml по образцам из теории урока, зафиксируйте их в git:
1
2
3
git init
git add .
git commit -m "chore: планирование пакета firefly-iii, package.yaml и config-values.yaml"

Шпаргалка команд урока

1
2
d8 package bootstrap application <name> [--hooks] [--extended] [--werf] [-o <path>]
d8 k get moduleconfig <name> -o jsonpath='{.spec.enabled}'

Полная шпаргалка d8: 📋 d8 .

Вопросы для самопроверки

Почему для капстоуна выбран стек, ни разу не встречавшийся в курсе (PHP/Laravel)?

Капстоун проверяет перенос общих принципов, а не запоминание конкретного стека.

Источник: Рекомендуемая литература

К какому Module объявляется mandatory-зависимость пакета firefly-iii?

Firefly III зависит от реляционной СУБД; капстоун фиксирует MySQL/MariaDB через managed-mysql для симметрии с managed-postgres, уже разобранным в M6.

Источник: Рекомендуемая литература

Почему официальную логику авто-миграции docker-entrypoint.sh образа fireflyiii/core не стоит копировать дословно в Application-пакет курса?

M6 урок 4 явно разбирает, почему миграция должна быть отдельным управляемым Job, а не неявной частью старта контейнера.

Источник: Рекомендуемая литература

Куда должны попадать внутренние детали вроде сгенерированного APP_KEY — в config-values.yaml или во internal.*?

Это прямое применение разграничения config-values.yaml/internal.*, разобранного в M5 уроке 3.

Источник: Рекомендуемая литература

Зачем нужен startupProbe для Firefly III, а не просто readinessProbe?

Та же мотивация, что и для Node-RED в M1 уроке 7 — заметная задержка старта требует отдельного startupProbe.

Источник: Рекомендуемая литература

Рекомендуемая литература

Официальная документация

Статьи и блоги

Книги

  • Gene Kim, Jez Humble, Patrick Debois, John Willis. The DevOps Handbook. 2nd ed., 2021. ISBN 978-1942788003.

Связанные материалы