--- title: MSSQL on vds-kzntsv (Express 2022 Linux) type: concept tags: [vds, mssql, migration, ops] related: [[../entities/vds-kzntsv]], [[recovery-architecture-snapshot]], [[traefik-tcp-passthrough-vs-starttls]], [[db-tls-self-signed-via-traefik-raw-tcp]] updated: 2026-06-12 --- # MSSQL on vds-kzntsv Запущен 2026-05-22 как часть `mssql-vds-migration`. SPOF с windows-recovery-host снят для CMS DB park. ## Architecture ``` IIS snolla site (web.config) → mssql.kzntsv.site:1433 (LE/DNS A → 89.253.255.94) → VDS traefik :1433 (TCP entrypoint, HostSNI(*)) → mssql container :1433 (self-signed TLS terminated по TDS) → /var/opt/mssql/data/{5 prod DBs} ``` Stack: `/opt/stacks/databases/mssql/{docker-compose.yml,.env,data,backups}`. Networks `proxy` only. **Image:** `mcr.microsoft.com/mssql/server:2022-latest` (RTM-CU25, build 16.0.4255.1, on Ubuntu 22.04.5 LTS). **Edition:** Express (`MSSQL_PID=Express`) — production licensing OK, 10 GB/DB cap (max .mdf сейчас `stostayer` 968 MB → 10× запас). **Memory:** `MSSQL_MEMORY_LIMIT_MB=2048` env (Express auto-caps на 1410 в любом случае). **TLS:** self-signed cert, mssql generates automatically. Client connects with `TrustServerCertificate=True`. Traefik forwards raw TCP — TLS terminates at mssql. ## Migration recipe (one-shot, executed 2026-05-22) Источник: `mcr.microsoft.com/mssql/server:2019-latest` Developer на windows-recovery-host (volume `mssql_mssql_data` 26.6 GB; real data ~2.4 GB; .ldf logs 12 GB inflated). ```sql -- 1. Source: BACKUP DATABASE for each prod DB (5 total) BACKUP DATABASE [] TO DISK='/var/opt/mssql/backups/.bak' WITH COMPRESSION, COPY_ONLY, INIT, STATS=50; -- Compressed total: 486 MB (10× от .mdf size — built-in compression на UTF-16/varchar pads) ``` ```bash # 2. Extract from container → scp → VDS bind docker cp mssql:/var/opt/mssql/backups/. C:/tmp/mssql-backups/ scp C:/tmp/mssql-backups/*.bak vitya@vds.kzntsv.site:/tmp/mssql-bak-staging/ ssh vitya@vds.kzntsv.site 'sudo mv /tmp/mssql-bak-staging/*.bak /opt/stacks/databases/mssql/backups/ sudo chown 10001:0 /opt/stacks/databases/mssql/backups/*.bak' ``` ```yaml # 3. compose + .env (см. /opt/stacks/databases/mssql/) # MSSQL_SA_PASSWORD из pass show mssql-vds/sa-password (32-char alnum, no special chars) ``` ```yaml # 4. Traefik: добавить TCP entrypoint в /opt/stacks/traefik/data/traefik.yml entryPoints: mssql: address: ":1433" # + port 1433 в /opt/stacks/traefik/docker-compose.yml ports list # + ufw allow 1433/tcp # + sudo docker compose up -d (RECREATE, не restart — port mapping) ``` ```sql -- 5. RESTORE per DB RESTORE DATABASE [] FROM DISK='/var/opt/mssql/backups/.bak' WITH REPLACE, STATS=50; -- 2019 → 2022 schema upgrade автоматический (versions 953→957). DBCC CHECKDB PHYSICAL_ONLY clean. ``` ```sql -- 6. CRITICAL: server-level logins НЕ переезжают в .bak (sysadmin/master DB only). -- Создать manually + fix orphan users в каждой DB: CREATE LOGIN snolla WITH PASSWORD = '', CHECK_POLICY = OFF; USE MoreThenCms; ALTER USER snolla WITH LOGIN = snolla; -- Repeat for каждой DB где web.config упоминает данного user'а. ``` ## Gotchas 1. **Logins vs users**: .bak восстанавливает DB-level users (с их role memberships + permissions), но НЕ server logins. Sites с `User Id=X` в conn-string ловят `Login failed for user 'X'` после restore, пока не выполнить `CREATE LOGIN` + `ALTER USER ... WITH LOGIN`. 2. **Traefik recreate, не restart**: добавление port mapping в `docker-compose.yml` требует **recreate** контейнера (`docker compose up -d`), не `restart`. `restart` оставит старые published ports. 3. **PowerShell + `pass` (bash script)**: `pass show` в PowerShell возвращает NULL (PS не запускает bash-скрипты напрямую). Нужно `bash -c "pass show ..."` или literal. Симптом: `Login failed for user 'sa'` несмотря на правильный пароль на VDS. 4. **`MSSQL_SA_PASSWORD` спец-символы**: rotation password generator должен excludeавать `+/=` (Base64) и shell-meta (`$`, `` ` ``, `"`, `'`, `\`). Иначе env-var passing через ssh heredoc или sed конструкторы ломаются. Пример безопасного: `[Convert]::ToBase64String($bytes) -replace '[+/=]','x'`. 5. **MSSQL Linux user uid**: `mssql` user = uid 10001. Bind-mount должен быть `chown 10001:0` чтобы контейнер read/write. Parent dir owns `vitya:vitya` чтобы можно было редактировать compose без sudo. ## Backup integration Реализовано 2026-06-11 в `/opt/stacks/backup/scripts/run.sh` (см. [[vds-backup-rsync-kreknin]]). Блок между Step 1 (DB dumps) и Step 2 (rsync): ```bash source /opt/stacks/databases/mssql/.env # injects MSSQL_SA_PASSWORD for DB in MoreThenCms StayerCalculator StayerPrice TireService stostayer; do docker exec mssql /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P "$MSSQL_SA_PASSWORD" -C -b \ -Q "BACKUP DATABASE [$DB] TO DISK='/var/opt/mssql/backups/${DB}.bak' WITH COPY_ONLY, FORMAT" mv /opt/stacks/databases/mssql/backups/${DB}.bak "$DUMP_DIR/mssql-${DB}.bak" done unset MSSQL_SA_PASSWORD ``` **Gotcha #1 — Express не умеет COMPRESSION:** Express Edition не поддерживает `WITH COMPRESSION`. .bak файлы создаются в bind-mount `/var/opt/mssql/backups/` (uid 10001:10001); root mv их в `$DUMP_DIR` для rsync на kreknin. sqlcmd path: `/opt/mssql-tools18/bin/sqlcmd`. **Gotcha #2 — `FORMAT`, не `INIT` (incident 2026-06-12):** первая версия блока (added 2026-06-11) использовала `WITH COPY_ONLY, INIT` и **молча падала первым же cron-запуском**. Причина: в `/var/opt/mssql/backups/` лежали миграционные `.bak` от 22.05, созданные `WITH COMPRESSION` на Developer-источнике. `INIT` переиспользует существующий media-set header (компрессованный) → Express не может в него писать → `Msg 1844 "BACKUP ... WITH COMPRESSION is not supported on Express"` (хотя в команде COMPRESSION нет!). `NO_COMPRESSION` тоже не спасает → `Msg 3098 "media formatted with an incompatible structure"`. **Fix: `WITH COPY_ONLY, FORMAT`** — всегда создаёт новый media set, иммунно к остаткам. Урок: при бэкапе на Express в каталог с возможными чужими/старыми `.bak` — только `FORMAT`, не `INIT`. **Backup gap:** между 22.05 (миграция) и 12.06 у 5 боевых баз **не было ни одной offsite-копии** — raw datadir намеренно не rsync'ится (lock), а dump-ветка падала с первого дня. Закрыто 2026-06-12. ## Rollback recipe 1. Revert IIS web.config'ы (см. `.bak-pre-vds-cutover-20260522` файлы). 2. `iisreset /restart` (или ждать auto-recycle ~30 sec). 3. Убедиться windows-host MSSQL контейнер всё ещё running (acceptance 48ч uptime до decommission). 4. Если уже decommissioned: `docker start mssql` (volume сохранён ещё неделю по acceptance).