Капстоун, шаг 3: Application-чарт и зависимость от managed-mysql

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

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

  • M11.02 — Сборка образа через werf — нужны готовые образы app/web перед написанием шаблонов, ссылающихся на них
  • M6.01 — requirements.modules.mandatory целиком — нужна модель проверки mandatory-зависимости перед объявлением её для managed-mysql
  • M6.04 — Паттерн «Job-миграция перед Deployment» — нужен паттерн для миграции схемы БД Firefly III
  • M7.08 — Семь готовых use-case'ов — нужен use case 6 (deploy-on: install + ownership: anyone) для одноразового APP_KEY

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

M6 урок 1 — синтаксис и семантика requirements.modules.mandatory. M6 урок 4 — паттерн Job-миграции с werf.io/delete-policy: before-creation + werf.io/deploy-dependency-*. M7 урок 8 — use case 6, одноразовый ресурс через deploy-on: install + ownership: anyone. Этот урок собирает все три паттерна в одном реальном Application-чарте.

Теория

Полная схема связей чарта Firefly III

flowchart TB MC["managed-mysql (Module)"] -->|"requirements.modules.mandatory"| PKG["package.yaml firefly-iii"] SEC["Secret app-key\ndeploy-on: install\nownership: anyone"] -->|"envFrom"| DEP["Deployment app\n(PHP-FPM)"] JOB["Job db-migrate\ndelete-policy: before-creation"] -->|"deploy-dependency"| DEP MC -->|"ConfigMap/Secret с реквизитами\n(имя/хост/порт/пароль БД)"| JOB MC -->|"тот же Secret реквизитов"| DEP DEP -->|"import public/"| WEB["Deployment web (nginx)"] PVC["PVC storage-upload\nresource-policy: keep"] --> DEP

Каждая стрелка этой схемы — уже пройденный в курсе паттерн: зависимость от Module (M6 урок 1), реквизиты подключения через ресурсы, публикуемые самим Module (M6 урок 2, там — на примере managed-postgres), Job-миграция перед Deployment (M6 урок 4), одноразовый секрет (M7 урок 8) и защита PVC (M6 урок 7). Капстоун не добавляет ни одного нового механизма — он требует правильно скомпоновать уже известные механизмы для нового набора требований.

Реквизиты подключения к managed-mysql

По аналогии с managed-postgres из M6 урока 2, Module managed-mysql публикует реквизиты подключения через Secret, создаваемый в namespace инстанса при первой установке (конкретное имя ресурса и его поля документируются самим Module — в чарте Application они читаются шаблонной функцией доступа к данным зависимости, а не жёстко прописываются):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
apiVersion: v1
kind: Secret
metadata:
  name: {{ .Application.Instance.Name }}-db-credentials
type: Opaque
stringData:
  DB_HOST: {{ .Values.managedMysql.host | quote }}
  DB_PORT: "3306"
  DB_DATABASE: {{ .Values.managedMysql.database | quote }}
  DB_USERNAME: {{ .Values.managedMysql.username | quote }}
  DB_PASSWORD: {{ .Values.managedMysql.password | quote }}

Job-миграция перед Deployment

Прямое применение паттерна из M6 урока 4 к php artisan migrate --force вместо ./migrate up из примера с Umami:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Application.Instance.Name }}-db-migrate
  annotations:
    werf.io/delete-policy: before-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: {{ .Application.Package.Images "app" }}
          command: ["php", "artisan", "migrate", "--force"]
          envFrom:
            - secretRef:
                name: {{ .Application.Instance.Name }}-db-credentials
            - secretRef:
                name: {{ .Application.Instance.Name }}-app-key
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Application.Instance.Name }}-app
  annotations:
    werf.io/deploy-dependency-migrate: state=ready,kind=Job,name={{ .Application.Instance.Name }}-db-migrate
spec:
  template:
    spec:
      containers:
        - name: app
          image: {{ .Application.Package.Images "app" }}
          envFrom:
            - secretRef:
                name: {{ .Application.Instance.Name }}-db-credentials
            - secretRef:
                name: {{ .Application.Instance.Name }}-app-key
          startupProbe:
            tcpSocket:
              port: 9000
            failureThreshold: 30
            periodSeconds: 5

--force в команде миграции необходим специфично для Laravel: без этого флага artisan migrate в production-окружении (APP_ENV=production) запрашивает интерактивное подтверждение в консоли, что несовместимо с автоматическим выполнением внутри Job без TTY — важная деталь, специфичная для Laravel-экосистемы, не встречавшаяся при работе с Go/Node.js-миграторами Vikunja/Umami.

APP_KEY — одноразовый секрет по use case 6

APP_KEY — 32-байтный ключ, используемый Laravel для симметричного шифрования (сессии, cookie, зашифрованные поля БД). Требования к нему прямо противоположны реквизитам БД: он должен быть сгенерирован один раз при первой установке и никогда не меняться при последующих обновлениях (смена APP_KEY на уже работающем инстансе делает нечитаемыми все ранее зашифрованные данные — необратимая потеря доступа, а не просто неудобство). Это ровно use case 6 из M7 урока 8:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Secret
metadata:
  name: {{ .Application.Instance.Name }}-app-key
  annotations:
    werf.io/deploy-on: install
    werf.io/ownership: anyone   # КРИТИЧНО — без этого Secret будет удалён при следующем upgrade
type: Opaque
stringData:
  APP_KEY: "base64:{{ randAlphaNum 32 | b64enc }}"

Как разобрано в M7 уроке 9 (подводный камень 3, недетерминированные функции), randAlphaNum в обычных условиях произвёл бы разное значение при каждом рендере — но здесь это ровно нужное поведение однократно, при первой установке, а werf.io/deploy-on: install гарантирует, что при последующих upgrade этот ресурс вообще не рендерится и не переприменяется повторно, значит, недетерминированность randAlphaNum при обновлениях уже не имеет значения — ресурс просто не участвует в процессе upgrade. werf.io/ownership: anyone не даёт Nelm удалить этот Secret как «отсутствующий в текущем рендере upgrade-релиза» (потому что deploy-on: install-ресурсы действительно не присутствуют в рендере фазы upgrade, и без ownership: anyone Nelm по умолчанию удалил бы их, посчитав «убранными из чарта»).

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

  • Не передать --force в php artisan migrate внутри Job. Без TTY команда зависает в ожидании интерактивного подтверждения, Job никогда не завершается — Deployment, зависящий от него через deploy-dependency-migrate, вечно ждёт state=ready и в итоге прерывается по no-activity-timeout (M7 урок 4).
  • Забыть werf.io/ownership: anyone на Secret с APP_KEY. Это тот же критичный подводный камень, что явно отмечен в M7 уроке 8 — без него Nelm удалит секрет при первом же upgrade, полностью нарушив шифрование существующих данных пользователей.
  • Использовать now/недетерминированные функции для значений, которые должны быть стабильны на протяжении всех фаз релиза (включая install). Здесь randAlphaNum безопасен именно благодаря сочетанию с deploy-on: install — без этой аннотации та же конструкция была бы прямым нарушением подводного камня 3 из M7 урока 9.
  • Указывать в requirements.modules.mandatory слишком мягкий constraint для managed-mysql. Как и в примере из M6 урока 1, constraint: ">= 0.0.0" фактически отключает защиту от несовместимых версий модуля.

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

🧩 Практика урока: Firefly III · лицензия AGPL-3.0
  1. Убедитесь, что managed-mysql включён (см. M11 урок 1), и создайте базу/пользователя для Firefly III средствами модуля (конкретная процедура — по документации managed-mysql вашего кластера, аналогично managed-postgres из M6 урока 2).

  2. Задеплойте чарт локально через Nelm и понаблюдайте за строгим порядком:

1
2
nelm release install -n course-m11-firefly -r firefly-demo . --auto-rollback
d8 k get jobs,deployments,secrets -n course-m11-firefly -w

Убедитесь, что firefly-demo-db-migrate переходит в Completed до появления Pod’ов Deployment, а Secret firefly-demo-app-key создан ровно один раз.

  1. Выполните повторный upgrade (например, изменив resources.requests в values.yaml) и убедитесь, что Secret с APP_KEY не изменился:
1
2
3
4
d8 k get secret firefly-demo-app-key -n course-m11-firefly -o jsonpath='{.data.APP_KEY}' > /tmp/key-before
nelm release install -n course-m11-firefly -r firefly-demo . --auto-rollback
d8 k get secret firefly-demo-app-key -n course-m11-firefly -o jsonpath='{.data.APP_KEY}' > /tmp/key-after
diff /tmp/key-before /tmp/key-after   # ожидаемо: пусто, ключ не изменился

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

Соберите полный набор файлов templates/ (Secret реквизитов, Job-миграция, Secret app-key, Deployment app, Deployment web, Service, PVC с helm.sh/resource-policy: keep для storage/upload) и openapi/values.yaml с полным контрактом (включая internal.managedMysql.* для реквизитов, получаемых от зависимого Module) — зафиксируйте всё в git как единый коммит перед переходом к CI/CD в M11 уроке 4.

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

1
2
3
4
werf.io/deploy-on: install # ресурс рендерится и применяется только при install
werf.io/ownership: anyone # защита от удаления при upgrade для ресурсов вне обычного жизненного цикла
werf.io/delete-policy: before-creation
werf.io/deploy-dependency-<id>: state=ready,kind=Job,name=<имя>

Полный справочник аннотаций — M7, команды Nelm — 📋 nelm .

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

Зачем нужен флаг --force у php artisan migrate внутри Job?

Специфика Laravel — APP_ENV=production требует явного --force для неинтерактивного запуска.

Источник: Laravel — Artisan migrate

Почему randAlphaNum безопасен для генерации APP_KEY, хотя M7 урок 9 называет недетерминированные функции подводным камнем?

Ровно use case 6 из M7 урока 8 — deploy-on: install делает недетерминированность безопасной именно для одноразовых секретов.

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

Что произойдёт при upgrade-релизе, если забыть werf.io/ownership: anyone на Secret с APP_KEY?

deploy-on: install ресурсы не присутствуют в рендере upgrade — без ownership: anyone Nelm интерпретирует это как удаление ресурса из чарта.

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

В каком порядке должны быть применены Job-миграция и Deployment согласно паттерну этого урока?

Без явной аннотации зависимости порядок не гарантирован — Nelm может применить их параллельно.

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

Откуда Application-чарт Firefly III получает хост/порт/пароль для подключения к БД?

Это стандартная модель получения реквизитов зависимого Module, а не изобретение нового механизма для капстоуна.

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

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

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

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

Книги

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

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