Створення та розгортання конфігурації
Конфігурація Kandra розгортається як один застосунок ASP.NET Core: проєкт Acme.WebApi
хостить API і обслуговує Blazor WebAssembly-клієнт з того самого джерела (origin). У цій
документації немає жодного Kandra-специфічного інструментарію для розгортання й жодного рецепта
хостингу для конкретної платформи (Azure, Docker, IIS…) — далі йде опис того, що насправді видає
dotnet publish, як він поводиться, коли запущений у Production, і перелік речей, які типові
налаштування не зроблять безпечно за вас.
Типові налаштування підлаштовані під розробку. Щойно опублікований інстанс, запущений у
Production, усе одно створює наперед відомі облікові записи, обслуговує публічну документацію API
та приймає cross-origin запити з будь-якого сайту. Дивіться
Перед тим, як відкривати доступ.
Створення нової конфігурації "з нуля"
Це описано крок за кроком у Як почати — коротка версія:
dotnet new install Kandra.ConfigTemplate --add-source https://nugets.kandra.tech/index.json
dotnet new kandra-config -n Acme --IncludeSqlite
або, з установленим інтерактивним майстром
(dotnet tool install --global Kandra.ConfigCreateTool --add-source https://nugets.kandra.tech/index.json):
kandra-new-config
Kandra.* надається з публічного фіда Kandra, https://nugets.kandra.tech/index.json, який анонімний:
токен не потрібен ні на вашій машині, ні на build-сервері. Скафолд постачає nuget.config, що
мапить Kandra.* на цей фід, а все інше — на nuget.org (packageSourceMapping, який не дає пакету з
такою самою назвою з іншого джерела виграти відновлення). Тримайте цей файл у репозиторії, адже
dotnet restore на build-сервері читає його теж; повний файл і пояснення — у
Як почати.
Публікація
dotnet publish src/Acme.WebApi/Acme.WebApi.csproj -c Release -o ./publish
Acme.WebApi посилається на проєкт Blazor-клієнта, тож одна публікація видає обидві половини.
Вихід публікації еталонної конфігурації (KandraWms.WebApi; ваша матиме таку саму форму) склав
приблизно 190 МБ у Debug-публікації й містив:
| У виводі | Що це таке |
|---|---|
Acme.WebApi.exe, Acme.WebApi.dll, *.runtimeconfig.json, *.deps.json | Серверний хост |
Kandra.*.dll, Acme.*.dll, збірки третіх сторін | Рушій, ваша конфігурація, залежності |
wwwroot/_framework/ | Blazor WebAssembly-клієнт (кілька сотень файлів, кожен також попередньо стиснутий як .br і .gz) |
wwwroot/css, _content/ | Статичні ресурси, включно з ресурсами бібліотеки UI |
appsettings.json (та appsettings.Development.json) | Конфігурація — дивіться нижче |
db/ | Де лежатиме файл SQLite-бази даних за замовчуванням (порожня папка у виводі; дивіться Папка даних) |
web.config | Присутній для хостингу в IIS |
мовні папки (uk, de, …) | Супутні ресурси із залежностей |
Release-публікація уникає лише-для-debug частин (BlazorDebugProxy, файли .pdb), які захопила
вище показана Debug-публікація. Папка db/ порожня, доки застосунок не запуститься вперше;
папки сховища файлів, ключів і резервних копій створюються під час виконання всередині папки даних.
Запуск
cd publish
ASPNETCORE_ENVIRONMENT=Production ASPNETCORE_URLS=http://localhost:5299 ./Acme.WebApi.exe
Під час старту застосунок застосовує будь-які незастосовані міграції й засіває дані
(MigrateAndSeedAsync), тож перший запуск створює базу даних. Еталонний хост, запущений у
Production, відповів на вказаній вище URL. API (/api/v1/…), інтерактивна документація API та
вхід у систему підтверджено робочими; клієнт обслуговується тим самим хостом
(UseBlazorFrameworkFiles плюс запасний варіант на index.html).
ASPNETCORE_ENVIRONMENT=Production обирає перевизначення з appsettings.Production.json, якщо ви
його надаєте, і вмикає обробник винятків та HSTS; він не вимикає жоден із пунктів переліку
нижче.
Ендпоінти стану
Хост надає два анонімні ендпоінти-проби для healthcheck контейнерів, реверс-проксі та проб Kubernetes/Azure:
| Ендпоінт | Відповідає 200, коли |
|---|---|
/health/live | процес працює (інші перевірки не виконуються) |
/health/ready | база даних доступна і не має невиконаних міграцій |
Обидва повертають лише слово Healthy або Unhealthy (503) — жодних подробиць; причина збою потрапляє в
журнал застосунку. Вони доступні і через звичайний HTTP: /health/* виключено з перенаправлення на HTTPS,
бо проби не вміють його виконувати.
У chiseled/distroless-образах немає curl, тож командний Docker HEALTHCHECK там недоступний; націльте HTTP-пробу
оркестратора (livenessProbe/readinessProbe у Kubernetes, health probe в Azure) на ці шляхи.
Конфігурація
Хост використовує стандартну конфігурацію ASP.NET Core, тож будь-яке налаштування в
appsettings.json можна перевизначити змінною середовища, використовуючи __ для вкладеності —
звичний спосіб тримати секрети поза опублікованими файлами:
| Налаштування | Змінна середовища | Значення |
|---|---|---|
Provider | Provider | Який провайдер бази даних працює: Sqlite, SqlServer чи PostgreSql. Інші ключі провайдера у файлі (_Provider, __Provider) — це просто закоментовані альтернативи. |
ConnectionStrings:<Provider> | ConnectionStrings__PostgreSql | Рядок підключення для обраного провайдера |
Kandra:DataPath | Kandra__DataPath | Єдина папка, у якій лежить увесь локальний стан — дивіться Папка даних |
JwtOptions:Secret | JwtOptions__Secret | Ключ підпису для токенів входу. Не задавайте його — і кожна інсталяція згенерує власний (дивіться Ключ підпису JWT) |
JwtOptions:Issuer / Audience | JwtOptions__Issuer / JwtOptions__Audience | Перевіряються при кожному запиті |
JwtOptions:Expires / RefreshExpires | Тривалість життя токенів (за замовчуванням 1 день / 14 днів) | |
Kandra:Database:* | Kandra__Database__BackupsToKeep … | Резервні копії SQLite та відновлення — дивіться Резервні копії перед міграціями |
BlobStorage:* | BlobStorage__LocalDiskRootPath … | Сховище вкладень — дивіться Файлове сховище та вкладення |
FeatureManagement:* | Прапорці функцій (Feature flags) | |
ReportResultCache:* | TTL і розмір кешу результатів звітів | |
Licensing:LicenseFilePath / LicenseValue | Licensing__LicenseFilePath | Звідки зчитується підписана ліцензія |
Перед тим, як відкривати доступ
Пункти 2, 3, 4 і 6 перевірено, запустивши опублікований еталонний хост у Production на тестовій
базі даних. Пункти 5 і 7 зчитано з постачених конфігурації та коду конвеєра.
-
Знайте, звідки береться ключ підпису.
appsettings.jsonпостачається безJwtOptions:Secret: під час першого запуску кожна інсталяція генерує власний ключ у папкуkeys/всередині папки даних, тож дві інсталяції однієї збірки ніколи не ділять один ключ. Будь-хто, хто має ключ, може випускати дійсні токени для будь-якого користувача цієї інсталяції, тому тримайте папку даних закритою та задайте ваші реальніIssuerтаAudience. Кілька вузлів або потрібен ключ під вашим контролем? ЗадайтеJwtOptions__Secretсамі — дивіться Ключ підпису JWT. -
Змініть або вилучіть наперед створені облікові записи. Під час старту сідер створює будь-який з цих записів, якого бракує,
admin— із фіксованим паролем, що є частиною платформи, решту — з випадковими:Користувач Пароль Примітки adminAdmin!1Адміністратор mcp-agent(випадковий) Обліковий запис нелюдського викликача; автентифікується API-ключем, а не паролем jobs-scheduler(випадковий) Назавжди заблокований для інтерактивного входу, тож увійти під ним неможливо — існує, щоб атрибутувати заплановані задачі Відомий пароль має лише
admin;mcp-agentіjobs-schedulerотримують випадковий, якого ніхто не бачить. Звичайна інсталяція не має зразкових користувачів: вони з'являються лише в демоінсталяції, зі списку демокористувачів вашої конфігурації (див. Імпорт даних і демодані). База даних, створена до цієї зміни, зберігає наявні облікові записиuser0…user7, тож перевірте їх на оновленому інстансі. Негайно змініть парольadminпісля першого старту. Оскільки сідер створює користувача, лише якщо його бракує, зміна пароля лишається чинною — але видалення наперед створеного користувача призводить лише до того, що його створять знову при наступному старті (admin— з паролем за замовчуванням). Замість видалення блокуйте чи вимикайте непотрібні вам облікові записи й ніколи не покладайтесь на їхню відсутність. -
Вирішіть, чи має документація API бути публічною. Swagger UI та
swagger.jsonувімкнені за замовчуванням у кожному середовищі, включно зProduction, і відповідають без автентифікації. Вимкніть їх черезKandra__Swagger__Enabled=falseабо обмежте на реверс-проксі. -
Затягніть CORS, якщо на іншому origin розміщено щось іще. За замовчуванням жоден міжджерельний виклик не дозволений. Клієнт обслуговується з того самого origin, що й API, тож CORS йому не потрібен. Щоб дозволити інший origin, перелічіть його в
Kandra:Cors:AllowedOrigins(змінна середовищаKandra__Cors__AllowedOrigins__0,__1, …); credentials отримують лише перелічені origin, а*ігнорується. -
Правильно термінуйте TLS. Конвеєр викликає
UseHttpsRedirection()і, поза розробкою,UseHsts(), але застосунок не конфігурує сертифікат. Використовуйте реверс-проксі або сконфігуруйте endpoint Kestrel, і переконайтесь, що застосунок бачить оригінальну схему та порт HTTPS за проксі, інакше редиректу нема куди йти. -
Знайте, які endpoint анонімні.
GET /api/v1/About(ідентифікатор інсталяції, версія, статус ліцензії) потребує входу користувача. Endpoint входу та текст EULA лишаються анонімними. -
Оберіть базу даних, яку ви справді будете використовувати. SQLite — щоденний провайдер для розробки, і він працює для одного невеликого інстансу. Міграції SQL Server і PostgreSQL згенеровані, але в самій роботі Kandra рідко перевіряються на живому сервері, тож перш ніж покладатися на такий провайдер, пройдіть увесь шлях на ньому — міграції, засівання, ваші дані. Дивіться Міграції бази даних.
Папка даних
Увесь стан, який працюючий інстанс тримає на власному диску, лежить в одній папці, тож для
повторного розгортання, оновлення чи перезапуску контейнера є рівно одна річ, яку треба зберегти.
Задайте її через Kandra:DataPath (в appsettings.json, змінною середовища Kandra__DataPath або
в командному рядку). Якщо не задано, це папка самого застосунку (content root): dotnet run використовує
папку проєкту, а опублікований застосунок — папку публікації, незалежно від того, з якої папки його
запущено. Хост, який знає краще, може задати власне значення за замовчуванням через
Kandra:HostDataPath (так робить десктопний хост — папка даних користувача). Відносне значення
розміщується в папці застосунку.
| У папці даних | Що це |
|---|---|
db/ | База SQLite — відносний Data Source у рядку підключення (за замовчуванням ./db/<name>.db) розміщується тут, а не в поточній папці процесу. Абсолютний Data Source використовується як є. Не застосовується до SQL Server і PostgreSQL. |
blobs/ | Завантажені вкладення (BlobStorage:LocalDiskRootPath, за замовчуванням blobs; відносне значення розміщується тут; інсталяція, що вже має App_Data/blobs і не має blobs/, далі користується нею). Втрата цієї папки залишає без пари кожне посилання на файл у базі даних. |
keys/ | Згенерований ключ підпису JWT. |
backups/ | Резервні копії SQLite, зроблені перед міграціями, і запис про скидання (нижче). |
Визначена папка записується в лог під час старту (Kandra data path: …). Для контейнера змонтуйте один
том і спрямуйте налаштування на нього: Kandra__DataPath=/data.
Program.cs хоста має створювати білдер через KandraWebApplication.CreateBuilder(args) замість
WebApplication.CreateBuilder(args). ASP.NET Core бере папку застосунку з робочої папки процесу, тож
опублікований застосунок, запущений з іншої папки (ярлик, менеджер служб), інакше працює без свого
appsettings.json. Варіант Kandra переходить у власну папку застосунку, якщо в робочій папці немає
appsettings.json, а в папці застосунку він є.
Також бережіть ліцензію, якщо використовуєте LicenseFilePath. Вона навмисно тримається поза
базою даних, тож відновлення резервної копії на іншій машині не переносить разом із нею продуктивну
ліцензію. З SQL Server чи PostgreSQL сама база лежить на сервері, і резервні копії робляться там.
Ключ підпису JWT
Якщо JwtOptions:Secret не задано, перший запуск генерує випадковий ключ і зберігає його в
keys/jwt-signing.key у папці даних (читається лише користувачем застосунку, де це дозволяє
операційна система). Той самий ключ використовується при кожному наступному запуску, тож користувачі,
що увійшли, переживають перезапуск. Видаліть файл, щоб анулювати всі випущені токени.
Задане значення завжди має пріоритет: задайте JwtOptions__Secret довгим випадковим рядком, коли
кілька вузлів мають приймати токени одне одного (інакше кожен згенерує свій ключ), або коли секрети
керуються централізовано. Ніколи не комітьте його.
Резервні копії перед міграціями
При кожному запуску з провайдером SQLite, якщо база вже існує і має незастосовані міграції,
застосунок спершу записує узгоджену копію в backups/kandra-<мітка часу>-before-<міграція>.db (через
онлайн-резервне копіювання SQLite, тож зміни, що ще лежать у журналі WAL, включені) і логує шлях. Він
зберігає п'ять найновіших (Kandra:Database:BackupsToKeep; 0 вимикає копії). Нова база, або база без
незастосованих міграцій, копії не отримує.
Відновлення: зупиніть застосунок, скопіюйте резервну копію поверх файлу бази (і видаліть поруч
kandra.db-wal / kandra.db-shm), запустіть попередню версію застосунку. Інші провайдери: власні
засоби резервного копіювання сервера.
Після релізу історія міграцій — це контракт
Щойно в інсталяцій з'являються справжні бази, історія міграцій, яку ви постачаєте, і є тим, що їх оновлює. Не склеюйте (squash) міграції після публічного релізу — база, що записала старі міграції, не може продовжити з переписаною історією. Під час старту така SQLite-база виявляється (вона містить міграції, яких збірка не знає), а застосунок відмовляється стартувати й пояснює чому, замість того, щоб зламатись посеред міграції. Нічого не змінюється.
Якщо ви все ж склеїли — зазвичай на тестовому чи демо-інстансі, дані якого не потрібні, — скиньте
SQLite-базу, задавши одне з цих налаштувань там, де працює застосунок. Це звичайні налаштування,
тож вони працюють і для контейнера, до якого вже не дістатися (Azure Application settings,
compose-файл, -e):
| Налаштування | Дія |
|---|---|
Kandra__Database__ResetToken=<будь-яке раніше не використане значення> | Одноразово. При наступному запуску базу зберігають у backups/, видаляють і створюють заново; значення запам'ятовується в backups/reset-tokens.txt, тож залишене налаштування при подальших перезапусках нічого не робить. Нове значення — нове скидання. Працює незалежно від того, сумісна база чи ні. |
Kandra__Database__IncompatibleHistory=Reset | Постійна політика. Щоразу, коли база містить міграції, яких збірка не знає, її зберігають у backups/, видаляють і створюють заново. Приберіть налаштування після відновлення, якщо не хочете цього при кожному squash. |
У будь-якому разі стара база лишається в backups/ (зберігаються найновіші копії; скопіюйте її
звідти, якщо вона потрібна надовго), а папки файлів і ключів не чіпаються. Обидва налаштування діють
лише для SQLite і ніколи не спрацьовують для SQL Server чи PostgreSQL.
Ліцензування
Без налаштованої ліцензії інстанс працює як Free: усе працює, у межах вбудованих у компіляцію
лімітів використання (проєктний документ задає 5 користувачів, 1000 документів на місяць, 200
рядків на документ). Надайте підписану ліцензію через Licensing__LicenseFilePath (шлях до файлу,
що містить рядок ліцензії) або Licensing__LicenseValue (сам рядок, для середовищ, де файл
незручний); шлях до файлу має пріоритет, якщо задано обидва. GET /api/v1/About показує поточний
статус ("status":"free" у перевіреному запуску). Адміністратор також може керувати ліцензією зі
сторінки Admin → License.
Запуск більш ніж одного інстансу
Запускайте один. Кілька частин стану прив'язані до процесу:
- планувальник задач тримає свій розклад у пам'яті й запускав би кожну задачу на кожному інстансі (Планувальник задач);
- кеш результатів звітів — це кеш у пам'яті;
- файлове сховище за замовчуванням використовує локальний диск, який інші інстанси не бачать;
- ключ підпису JWT генерується для кожної інсталяції, якщо ви не задасте
JwtOptions__Secret— вузли мають ділити один.
Платформа поки що не має історії багатовузлової підтримки. Масштабуйтесь вертикально, перш ніж масштабуватись горизонтально.
Що тут навмисно не задокументовано
Навмисно: покрокові інструкції для Azure/AWS/IIS, конвеєри CI/CD та конфігурація реверс-проксі. Ніщо в Kandra не специфічне для жодного з них — це стандартний застосунок ASP.NET Core — і вигадувати рецепт, який ніхто не прогнав, було б гірше, ніж чесно про це сказати. Коли такий рецепт з'явиться і буде прогнаний від початку до кінця, йому місце на цій сторінці.
Дивіться також
- Як почати — створення "з нуля" та перший запуск.
- Міграції бази даних — як база даних отримує свою схему, і
MigrateAndSeedAsync. - Файлове сховище та вкладення та Планувальник задач — частини стану, про які варто подбати заздалегідь.
- Прапорці функцій (Feature flags) — вмикання й вимикання можливостей за середовищем.
- Контейнеризація конфігурації — Dockerfile і compose-файли у створеній конфігурації.