fix, feat, doc: massive build system refactorion, bug fixes and apex documentation (#19)
This commit is contained in:
1 parent
e8b42c60c2
commit
91f5245823
165 files changed
+8598
-1205
No files matched your search
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Руководства
|
||||
description: Практические руководства по сборке репозиториев узлов, действий и композиций.
|
||||
weight: 20
|
||||
---
|
||||
|
||||
Практические руководства для повседневной работы с apex. Каждое руководство решает одну повторяющуюся задачу: сборка репозитория [узла](/apex/glossary#node), разработка [действий](/apex/glossary#action) для [движка](/apex/glossary#engine), определение [композиций](/apex/glossary#compositions) сервисов и эксплуатация лежащего в основе хоста.
|
||||
|
||||
## Доступные руководства {#available-guides}
|
||||
|
||||
{{< cards >}}
|
||||
{{< card link="/apex/guides/node-repository" title="Репозиторий узла" subtitle="Соберите репозиторий узла, который объявляет идентичность узла, его конфигурации и композиции." >}}
|
||||
{{< card link="/apex/guides/action-development" title="Разработка действий" subtitle="Пишите действия, которые движок диспетчеризует на узле." >}}
|
||||
{{< card link="/apex/guides/service-composition" title="Композиция сервисов" subtitle="Определяйте композиции проектов и разворачивайте их вместе с базовой композицией." >}}
|
||||
{{< card link="/apex/guides/host-operations" title="Операции с хостом" subtitle="Выполняйте рутинные операции над работающим хостом через движок." >}}
|
||||
{{< /cards >}}
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Docs: Getting started](/apex/getting-started)
|
||||
- [Docs: Concepts](/apex/concepts)
|
||||
- [GitHub: apex.ermnvldmr.com](https://github.com/deytenit/apex.ermnvldmr.com)
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: Разработка действий
|
||||
description: Пишите, переопределяйте и тестируйте действия движка в commons или proprietaries.
|
||||
weight: 22
|
||||
---
|
||||
|
||||
**Контекст:** вся автоматизация поставляется в виде [действий](/apex/glossary#action) — по одному Python-модулю на каждое, диспетчеризуемых по пути. Общее поведение размещается в [commons](/apex/glossary#commons) `actions/` («commons» — общий репозиторий); всё, что специфично для узла, размещается в `proprietaries/actions/` этого узла. Оба используют идентичный контракт, а запускатель помещает оба дерева в `sys.path`, поэтому проприетарное действие импортирует `engine.*` точно так же, как и действие из commons.
|
||||
|
||||
## Минимальное действие {#minimal}
|
||||
|
||||
```python {filename="proprietaries/actions/utils/example.py"}
|
||||
"""Однострочный docstring модуля, описывающий действие."""
|
||||
import os
|
||||
from engine.descriptor import Meta, Arg, Flag
|
||||
|
||||
METADATA = Meta(
|
||||
summary="Скопировать отрендеренную конфигурацию на место.",
|
||||
args=[Arg("target_dir", "Каталог назначения"),
|
||||
Flag("--dry-run", "Только предпросмотр.")],
|
||||
)
|
||||
|
||||
def run(ctx, args):
|
||||
if not os.path.isdir(args.target_dir):
|
||||
ctx.log.error(f"Нет такого каталога: {args.target_dir}")
|
||||
raise SystemExit(66)
|
||||
content = ctx.tpl.render("example/config", ctx.vars())
|
||||
ctx.host.write_file(os.path.join(args.target_dir, "config"), content,
|
||||
dry_run=args.dry_run, sudo=True)
|
||||
ctx.log.success("Конфигурация развёрнута.")
|
||||
```
|
||||
|
||||
Путь к файлу — это его имя: модуль выше диспетчеризуется как `apex utils/example`. Верните `0`/`None` для успеха, целое число для конкретного кода выхода или возбудите `SystemExit(code)`; [соглашение о кодах выхода](/apex/concepts/actions-and-overlay#exit-codes) таково: `64` — ошибка использования, `65` — ошибка данных, `66` — отсутствующий ввод, `1` — общая ошибка.
|
||||
|
||||
## Соглашения для действий без надзора {#unattended}
|
||||
|
||||
Действия, запускаемые из cron, следуют двум правилам, чтобы сбои приводили к оповещению, а не исчезали бесследно:
|
||||
|
||||
- **Оберните тело** в `try/except Exception` и отправьте `ctx.notify.error(...)` перед повторным возбуждением как `SystemExit(1)` — Python-эквивалент shell-ловушки ERR.
|
||||
- **Привяжите код выхода к финальному уведомлению**: `ctx.notify.*` возвращает `False` при неудачной доставке; завершённый запуск, чьё статусное сообщение так и не пришло, всё равно должен завершиться с кодом `1`.
|
||||
|
||||
```python
|
||||
try:
|
||||
...работа...
|
||||
log.success("Готово.")
|
||||
if not ctx.notify.success(TITLE, url, "Готово.", node):
|
||||
raise SystemExit(1)
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception as e:
|
||||
log.error(f"{type(e).__name__}: {e}")
|
||||
ctx.notify.error(TITLE, url, f"Критическая ошибка: {e}", node)
|
||||
raise SystemExit(1)
|
||||
```
|
||||
|
||||
## Переопределение, обёртывание, отключение {#override}
|
||||
|
||||
Проприетарный файл с тем же относительным путём, что и действие из commons, полностью затеняет его. Внутри затеняющего модуля `ctx.commons.run(args)` вызывает затенённую реализацию — паттерн обёртывания:
|
||||
|
||||
```python {filename="proprietaries/actions/sync/packages.py"}
|
||||
from engine.descriptor import Meta, Arg
|
||||
|
||||
METADATA = Meta(summary="sync/packages с локальным для узла предварительным шагом.",
|
||||
args=[Arg("telegram_bot_url", "Telegram Bot URL")])
|
||||
|
||||
def run(ctx, args):
|
||||
ctx.log.info("Локальная для узла подготовка...")
|
||||
return ctx.commons.run(args) # делегирование реализации из commons
|
||||
```
|
||||
|
||||
Чтобы вывести действие из эксплуатации на одном узле, присвойте `DISABLED = "reason"` на верхнем уровне модуля: диспетчеризация отклонит его (код выхода `1`), а в списке отобразится `[DISABLED: reason]`.
|
||||
|
||||
## Тестирование {#testing}
|
||||
|
||||
Набор тестов commons использует stdlib `unittest`:
|
||||
|
||||
```bash
|
||||
./tests/run.sh # python3 -m unittest discover -s tests -p 'test_*.py' -v
|
||||
```
|
||||
|
||||
Изменения движка должны сохранять набор тестов «зелёным». Дешёвая smoke-проверка для любого действия — из commons или проприетарного — это его парсер: `apex <name> --help` должен завершиться с кодом `0`, что доказывает, что модуль разбирается, его `METADATA` корректно сформирован, и его видит механизм обнаружения.
|
||||
|
||||
Pre-commit-хук commons (подключаемый `init.sh` через `core.hooksPath`) запускает `apex utils/lint-docker-compose --hook` для staged-файлов compose, поэтому изменения композиций линтуются в момент коммита.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Concept: Actions and the overlay](/apex/concepts/actions-and-overlay)
|
||||
- [Concept: The execution context](/apex/concepts/execution-context)
|
||||
- [Reference: Engine API](/apex/reference/engine-api)
|
||||
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Эксплуатация узла
|
||||
description: "Операции day-2: конфигурирование, композиции, резервные копии и синхронизация репозитория."
|
||||
weight: 24
|
||||
---
|
||||
|
||||
**Контекст:** после первоначальной настройки [узел](/apex/glossary#node) эксплуатируется полностью через [движок](/apex/glossary#engine). Это руководство охватывает повторяющиеся операции и то, что каждая из них фактически делает на хосте.
|
||||
|
||||
## Конфигурирование хоста {#configure}
|
||||
|
||||
`apex configure` запускает полный набор конфигурирования в фиксированном порядке; каждый шаг вызывается индивидуально, когда изменился только один слой:
|
||||
|
||||
| Шаг | Эффект |
|
||||
|---|---|
|
||||
| `configure/base` | Обеспечивает базу Debian: обновление индекса пакетов, необходимые пакеты (`curl`, `wget`, `git`, `rsyslog`), запущенный rsyslog, установленный `ufw` и вспомогательный скрипт `ufw-docker` в `~/.local/bin/ufw-docker`. |
|
||||
| `configure/ufw` | Применяет правила хоста, внедряет управляемые блоки правил уровня ядра, применяет правила контейнеров через `ufw-docker`. Поддерживает `--dry-run`. |
|
||||
| `configure/crowdsec` | Устанавливает CrowdSec, если он отсутствует, разворачивает `configs/crowdsec/` в `/etc/crowdsec/`, устанавливает коллекции и firewall bouncer, если он настроен. Поддерживает `--dry-run`. |
|
||||
| `configure/cron` | Рендерит `configs/cron/crontab` через слой шаблонов и устанавливает его — с предпросмотром, отказом при пустом рендере (код выхода `65`) и откатом при неудачной установке. Поддерживает `--dry-run`. |
|
||||
| `configure/systemd` | Рендерит файлы верхнего уровня из `configs/systemd/` в `/etc/systemd/system/`, затем выполняет daemon-reload и включает каждый `.service`. Поддерживает `--dry-run`. |
|
||||
| `configure/routing` | Включает IP-форвардинг и устанавливает правило и таблицу policy-routing по fwmark, используемые границей анклава; идемпотентно. |
|
||||
|
||||
После `configure/ufw` намеренно перезапустите `ufw` (`sudo systemctl restart ufw`) — действие напоминает об этом и предупреждает, что порт SSH уже должен быть разрешён.
|
||||
|
||||
## Жизненный цикл композиции {#compose}
|
||||
|
||||
```bash
|
||||
apex compose up # сначала core, затем сервисы по алфавиту
|
||||
apex compose down # обратный порядок
|
||||
apex compose restart
|
||||
apex compose up --dry-run # только напечатать команды
|
||||
```
|
||||
|
||||
Дополнительные аргументы передаются в `docker compose` для каждого проекта. Файлы `.env` и `apex.env` каждого проекта подставляются автоматически, если они присутствуют.
|
||||
|
||||
## Резервные копии {#backups}
|
||||
|
||||
```bash
|
||||
apex backup/run "$(cat <path-to-telegram-url-file>)"
|
||||
```
|
||||
|
||||
Запускает [resticontainer](https://github.com/deytenit/resticontainer) в режиме one-shot под профилем compose `manual`: init-if-needed, затем управляемое метками резервное копирование, которое определяет намерение каждого сервиса из его меток compose `restic.*` (`restic.enable`, `restic.backup.paths`, …), делает снимок объединения разрешённых путей хоста в одном запуске restic (`--host` = `APEX_NODE_HOST`, `--compression max`), применяет политику хранения (`forget --prune`) и уведомляет. Он отказывается сообщать об успехе, если ни один сервис с меткой `restic.*` не создал нового снимка. Полный процесс описан в [Concept: Tiered storage](/apex/concepts/tiered-storage#backups). Запланируйте его в `configs/cron/crontab`, чтобы запуск и его результат в Telegram проходили без надзора.
|
||||
|
||||
## Синхронизация репозитория (capture-up) {#sync}
|
||||
|
||||
```bash
|
||||
apex sync/repository <telegram-bot-url>
|
||||
```
|
||||
|
||||
Коммитит рабочую копию в `sync/<node>`, выполняет rebase на `origin/main` и force-push с lease; CI сворачивает ветку обратно в `main`. «capture-up» — механизм захвата состояния узла обратно в репозиторий. Действие отказывается выполняться — с постраничной ошибкой — когда сабмодуль commons «грязный» или дерево `compositions/` отсутствует, и аккуратно прерывается при конфликте rebase (требуется ручное вмешательство). См. [Concept: Framework architecture](/apex/concepts/architecture#capture-up) для описания модели.
|
||||
|
||||
## Осведомлённость об обновлениях {#updates}
|
||||
|
||||
```bash
|
||||
apex sync/packages <telegram-bot-url>
|
||||
```
|
||||
|
||||
Сообщает об ожидающих apt-обновлениях и устаревших образах контейнеров (локальные дайджесты сравниваются с реестром через `skopeo`) в едином дайджесте Telegram. Он ничего не изменяет на хосте — это плановая проверка осведомлённости.
|
||||
|
||||
## Извлечение сертификатов {#certs}
|
||||
|
||||
```bash
|
||||
apex utils/extract-traefik-certs <acme.json> <dest-dir> [telegram-bot-url]
|
||||
```
|
||||
|
||||
Выгружает хранилище ACME граничного прокси в пары `fullchain.pem`/`privkey.pem` по каждому домену (через контейнеризованный dumper), ограничивает права на ключи до `600` и переназначает владельца вывода вызывающему пользователю — для сервисов, которым нужен «сырой» PEM-материал вместо TLS, терминируемого прокси.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Getting started with an apex node](/apex/getting-started)
|
||||
- [Reference: Actions](/apex/reference/actions)
|
||||
- [Docs: Troubleshooting](/apex/troubleshooting)
|
||||
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Сборка репозитория узла
|
||||
description: Соберите четыре опоры, зафиксируйте commons и подключите поток capture-up.
|
||||
weight: 21
|
||||
---
|
||||
|
||||
**Контекст:** каждый [узел](/apex/glossary#node) получает ровно один репозиторий. Выбирайте любое имя репозитория — [идентичность](/apex/glossary#node-identity) объявляется явно в `node.env`, поэтому имя репозитория — это соглашение, а не функциональный ввод. Это руководство собирает такой репозиторий с нуля.
|
||||
|
||||
{{% steps %}}
|
||||
|
||||
### Инициализируйте репозиторий и зафиксируйте commons
|
||||
|
||||
```bash
|
||||
git init <node-repo> && cd <node-repo>
|
||||
git submodule add <commons-url> commons
|
||||
( cd commons && git fetch --tags && git checkout <release-tag> )
|
||||
git add .gitmodules commons
|
||||
mkdir -p proprietaries/actions proprietaries/lib configs compositions/apex .github/workflows
|
||||
```
|
||||
|
||||
Gitlink фиксирует точную версию движка, которую запускает узел. Обновление в дальнейшем означает checkout более нового тега внутри `commons/` и коммит перемещённого указателя.
|
||||
|
||||
### Объявите идентичность
|
||||
|
||||
```ini {filename="node.env"}
|
||||
APEX_NODE_FQDN=node1.example.com
|
||||
APEX_NODE_HOST=node1.example.com
|
||||
APEX_SUBNET=198.18.16.0/24
|
||||
APEX_GIT_AUTHOR_NAME=apex [bot]
|
||||
APEX_GIT_AUTHOR_EMAIL=apex@localhost
|
||||
```
|
||||
|
||||
`node.env` объявляет идентичность узла явно — не предполагается никакой формы домена. `APEX_NODE_FQDN` — это реальный FQDN, используемый для маршрутизации traefik по `Host()` (fallback: имя хоста ОС, с предупреждением). `APEX_NODE_HOST` — это публичное имя хоста, используемое для `--host` резервного копирования и метки `instance` наблюдаемости/уведомлений (по умолчанию: `APEX_NODE_FQDN`); короткое `name` узла — это его первая метка. `APEX_SUBNET` — это /24 узла для сети `direct` и является **обязательным** — отсутствующее значение приводит к выходу с кодом `66` (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)). `APEX_GIT_AUTHOR_NAME`/`APEX_GIT_AUTHOR_EMAIL` — это идентичность бота capture-up (общие значения по умолчанию, если не заданы).
|
||||
|
||||
### Напишите базовую обёртку и её env-файлы
|
||||
|
||||
```yaml {filename="compositions/apex/docker-compose.yml"}
|
||||
name: apex
|
||||
include:
|
||||
- ../../commons/compositions/apex/docker-compose.yml
|
||||
```
|
||||
|
||||
```ini {filename="compositions/apex/apex.env"}
|
||||
APEX_TRAEFIK_DIRECT_IP=198.18.16.10
|
||||
APEX_RESTIC_REPOSITORY=s3:<endpoint>/<bucket>/<node>
|
||||
COMPOSE_PROFILES=adguard,xray,alloy
|
||||
# цели push-наблюдаемости (только когда включён профиль alloy)
|
||||
APEX_PROM_PUSH_URL=https://<central-host>/api/v1/push
|
||||
APEX_LOKI_PUSH_URL=https://<central-host>/loki/api/v1/push
|
||||
APEX_OBS_BASIC_AUTH_USER=<node-user>
|
||||
# опциональные скаляры со значениями compose по умолчанию:
|
||||
# TZ=Etc/UTC
|
||||
# APEX_RESTIC_COMPRESSION=max
|
||||
# APEX_RESTIC_AWS_REGION=us-east-1 # задайте для любого не-AWS S3-хранилища
|
||||
# APEX_RESTIC_AWS_ENDPOINT= # напр., URL S3-совместимого эндпоинта
|
||||
```
|
||||
|
||||
`apex.env` содержит только коммитируемые несекретные скаляры. `COMPOSE_PROFILES` выбирает, какие опциональные базовые сервисы запускает этот узел — `adguard`, `xray`, `alloy` (агент push-наблюдаемости) и `manual` (одноразовые резервные копии) — и может быть пустым. Задокументируйте некоммитируемые секретные ключи в `compositions/apex/SECRETS.md`, чтобы оператор на свежем хосте знал, что заполнить в `.env` (`APEX_RESTIC_PASSWORD`, ключи доступа S3, `APEX_TRAEFIK_CF_DNS_API_TOKEN`, `APEX_TRAEFIK_ACME_EMAIL` и `APEX_OBS_BASIC_AUTH_PASS`, когда наблюдаемость включена).
|
||||
|
||||
### Игнорируйте то, чему никогда не место в git
|
||||
|
||||
```gitignore {filename=".gitignore"}
|
||||
**/@tier1
|
||||
**/@tier2
|
||||
**/@tier3
|
||||
**/.env
|
||||
!compositions/apex/apex.env
|
||||
__pycache__/
|
||||
*.pyc
|
||||
```
|
||||
|
||||
### Добавьте bootstrap-скрипт
|
||||
|
||||
`init.sh` подготавливает свежий клон на хосте: `git submodule update --init --recursive`, `git config core.hooksPath commons/githooks`, `git config --global --add safe.directory <repo>` и добавляет `<repo>/commons` в начало `PATH` в login-shell, чтобы `apex` разрешался. Пометьте его исполняемым.
|
||||
|
||||
### Заведите реестр адресации
|
||||
|
||||
Создайте `compositions/ADDRESSING.md` с подсетью узла (`APEX_SUBNET`) и зарезервированными диапазонами (`.10`–`.19` — core, `.20`+ — сервисы). Каждый [якорный IP](/apex/glossary#anchor-ip), выделяемый позднее, записывается здесь.
|
||||
|
||||
### Подключите CI capture-up
|
||||
|
||||
Добавьте плановый workflow, который сливает `sync/<node>` в `main`, когда ветка существует, коммитя под идентичностью бота. Выполняйте checkout **без** сабмодулей — слияние лишь перемещает указатель commons, а submodule-URL по SSH не извлекается с CI-токеном по умолчанию. См. [Concept: Framework architecture](/apex/concepts/architecture) для полного цикла [capture-up](/apex/glossary#capture-up).
|
||||
|
||||
### Проверьте
|
||||
|
||||
```bash
|
||||
./commons/apex --help
|
||||
```
|
||||
|
||||
В списке должно отображаться каждое действие из commons (плюс ваши proprietaries, после их добавления). Вне хоста — до заполнения `node.env` — ожидайте безобидное предупреждение о том, что `APEX_NODE_FQDN` не задан (движок откатывается к имени хоста ОС); `APEX_SUBNET` при этом всё равно должен быть задан, иначе движок завершится с кодом `66`. Закоммитьте собранный репозиторий в `main`.
|
||||
|
||||
{{% /steps %}}
|
||||
|
||||
## Заметки
|
||||
|
||||
- Опора `proprietaries/` может начинаться пустой (сохраняйте её присутствие с помощью файлов `.gitkeep`); движок корректно обрабатывает отсутствующий оверлей.
|
||||
- Переносите источники конфигурации хоста в `configs/` (`ufw/`, `cron/crontab`, `systemd/`, `crowdsec/`) по мере того как узел принимает каждый шаг `configure/*` — действия потребляют `configs/` дословно через слой шаблонов.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Getting started with an apex node](/apex/getting-started)
|
||||
- [Concept: Framework architecture](/apex/concepts/architecture)
|
||||
- [Guide: Add a service composition](/apex/guides/service-composition)
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
title: Добавление композиции сервисов
|
||||
description: "Подключите новый проект к узлу: якорный IP, сети, уровни и линтинг."
|
||||
weight: 23
|
||||
---
|
||||
|
||||
**Контекст:** проект присоединяется к [узлу](/apex/glossary#node) как один каталог в `compositions/`, содержащий написанный вручную файл compose. Контракт фреймворка для этого файла невелик: статический [якорный IP](/apex/glossary#anchor-ip) в сети `direct`, ссылки на внешние сети по ключу и пути к данным относительно уровней.
|
||||
|
||||
{{% steps %}}
|
||||
|
||||
### Выделите якорный адрес
|
||||
|
||||
Выберите следующий свободный адрес `.20` или выше в /24 узла и запишите его в `compositions/ADDRESSING.md`. Адреса `.10`–`.19` зарезервированы для [базовой композиции](/apex/glossary#core-composition) (см. [Concept: Fleet addressing scheme](/apex/concepts/addressing)).
|
||||
|
||||
### Напишите файл compose
|
||||
|
||||
```yaml {filename="compositions/<project>/docker-compose.yml"}
|
||||
name: <project>
|
||||
services:
|
||||
<anchor>:
|
||||
image: ...
|
||||
container_name: <project>-<anchor>
|
||||
user: "${APEX_UID?error}:${APEX_GID?error}"
|
||||
group_add:
|
||||
- "${APEX_SHARED_GID?error}"
|
||||
volumes:
|
||||
- ./@tier1/<anchor>/config:/config
|
||||
- ./@tier2/<anchor>/data:/data
|
||||
networks:
|
||||
direct:
|
||||
ipv4_address: 198.18.16.20
|
||||
enclave: {}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.<project>.rule=Host(`...`)"
|
||||
- "traefik.http.services.<project>.loadbalancer.server.port=8080"
|
||||
# намерение резервного копирования (resticontainer, определяется из меток restic.*)
|
||||
- "restic.enable=true"
|
||||
- "restic.backup.paths=/config"
|
||||
restart: unless-stopped
|
||||
|
||||
<sidecar>:
|
||||
image: ...
|
||||
network_mode: "service:<anchor>"
|
||||
|
||||
networks:
|
||||
direct:
|
||||
external: true
|
||||
enclave:
|
||||
external: true
|
||||
```
|
||||
|
||||
Применяемые соглашения:
|
||||
|
||||
- **Один якорь на проект** держит статический `ipv4_address` в `direct`; sidecar-контейнеры разделяют его сетевое пространство имён через `network_mode: service:<anchor>`.
|
||||
- **На сети ссылаются по ключу** (`direct`, `enclave`, `socket`) с `external: true` — их определения принадлежат базовой композиции.
|
||||
- **Членство в `enclave` плюс метки traefik** публикуют сервис через граничный прокси; прокси обнаруживает его через socket-прокси, никогда через опубликованные порты.
|
||||
- **Пути к данным относительны уровням** (`./@tierN/...`) и разрешаются через симлинки, создаваемые `tiers/link`.
|
||||
- **Резервное копирование включается по выбору через метки `restic.*`.** `apex backup/run` управляет [resticontainer](https://github.com/deytenit/resticontainer), который определяет, что снимать, из меток compose: `restic.enable=true` помечает сервис, `restic.backup.paths=<container-path>` называет монтирования для резервного копирования (resticontainer разрешает их в пути хоста). Сервис без меток `restic.*` просто не резервируется. Резервируйте конфигурацию только там, где данные восстановимы.
|
||||
- Тройка `${APEX_UID...}` берётся из `.env` проекта, поддерживаемого `tiers/useradd`.
|
||||
|
||||
### Обеспечьте хранилище и идентичность
|
||||
|
||||
```bash
|
||||
apex tiers/link <tier1> <tier2> <tier3>
|
||||
apex tiers/useradd
|
||||
apex tiers/chown
|
||||
```
|
||||
|
||||
Повторный запуск тройки идемпотентен; он подхватывает новый проект, создаёт его каталоги уровней и `.env`, обеспечивает его пользователя `noroot-<project>` и применяет матрицу владения.
|
||||
|
||||
### Линтинг
|
||||
|
||||
```bash
|
||||
apex utils/lint-docker-compose
|
||||
```
|
||||
|
||||
Проверяет каждый якорный IP (корректность формы, уникальность, нахождение внутри подсети узла) и запускает линтер compose по репозиторию. Pre-commit-хук обеспечивает тот же линтинг для staged-файлов compose.
|
||||
|
||||
### Разверните
|
||||
|
||||
```bash
|
||||
apex compose up
|
||||
```
|
||||
|
||||
Базовая композиция уже поднята; новый проект стартует в своём алфавитном слоте. Проверьте с помощью `docker ps` и, если сервис проксируется, через его маршрут на границе.
|
||||
|
||||
{{% /steps %}}
|
||||
|
||||
## Заметки
|
||||
|
||||
- Внутренние секреты сервиса (например, пароль базы данных) размещаются в собственном `.env` проекта в хранилище уровня 1, рядом с автоматически внедряемым блоком UID.
|
||||
- Никогда не определяйте `direct`/`enclave`/`socket` внутри файла проекта — второе определение будет конкурировать с базовой композицией за владение сетью.
|
||||
|
||||
---
|
||||
|
||||
**См. также:**
|
||||
|
||||
- [Concept: Compositions and the shared core](/apex/concepts/compositions)
|
||||
- [Concept: Tiered storage and privilege isolation](/apex/concepts/tiered-storage)
|
||||
- [Concept: Fleet addressing scheme](/apex/concepts/addressing)
|
||||
Reference in new issue
Block a user