docs(.wiki): ingest NAS recovery session 2026-05-18/19

15-hour cross-hypervisor recovery from WD40EFAX SMR RAID5
cascade failure. 5 entities + 7 concepts + 1 source documenting:

- Root cause: WD40EFAX SMR cascade in 3-disk RAID 5
- Hyper Backup .hbk structure + SFTP-jail / ACL workarounds
- OVA from Synology VMM (KVM) → VirtualBox: SCSI→SATA,
  Hyper-V driver disable, paravirt=kvm, GA install, NAT switch
- MSSQL restore via named volume + chown, sqlcmd -x, mssql-conf
- Web.config UTF-16 vs UTF-8 BOM IIS 500.19 trap
- Traefik on Windows DD: configFile, named volume for acme.json,
  file-provider as docker.sock workaround
- Snapshot of current recovery architecture + SPOF list
- Placeholder for future resilient-architecture work

Plus .tasks/nas-recovery.md and STATUS.md updates closing the task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 11:07:38 +03:00
parent a130bd61d3
commit 19422352ba
8 changed files with 1005 additions and 0 deletions

View File

@@ -0,0 +1,151 @@
---
title: MSSQL контейнер с восстановленными production data — паттерн
type: concept
tags: [mssql, docker, recovery, named-volume, chown]
sources: [../sources/nas-recovery-session-2026-05-18.md]
updated: 2026-05-19
---
# MSSQL container с production data
## Проблема
Восстановить MSSQL базу в Docker-контейнере на Windows-хосте из:
- Готовых `.mdf/.ldf` файлов production (взяты из tar `/var/opt/mssql/` source-контейнера)
- + 5 user-databases + системные (master/model/msdb/tempdb)
## Что НЕ работает: bind-mount production data
```yaml
volumes:
- ./production-data/mssql:/var/opt/mssql
```
**Не работает** на Windows Docker Desktop. MSSQL-контейнер требует:
- `master.mdf` owned by `mssql` user (UID 10001) или root
- `chmod` на файлах внутри `/var/opt/mssql/log/` (логи, .xel, .trc)
На Windows DD bind-mount через WSL2-слой не позволяет:
- Файлы видятся как owned by root or other UID — MSSQL отказывается: `Your master database file is owned by root.`
- `chmod` внутри контейнера фейлится: `Operation not permitted`
Симптом: MSSQL стартует, в логах ругается на chmod, потом стартует ещё раз и зависает.
## Что работает: named volume + chown через temp container
Идея: создать named volume, скопировать данные внутрь с правильным chown, потом примонтировать к MSSQL.
```yaml
# docker-compose.yml — финальная версия
services:
mssql:
image: mcr.microsoft.com/mssql/server:2019-latest
container_name: mssql
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: ${SA_PASSWORD}
MSSQL_PID: Developer
ports:
- "1433:1433"
volumes:
- mssql_data:/var/opt/mssql
networks:
- proxy
restart: unless-stopped
volumes:
mssql_data:
networks:
proxy:
external: true
```
Заполнение volume (один раз перед `docker compose up -d`):
```powershell
docker volume create mssql_mssql_data
docker run --rm \
-v mssql_mssql_data:/dest \
-v <local-path-to-production-data>/mssql:/src:ro \
alpine cp -a /src/. /dest/
docker run --rm -v mssql_mssql_data:/dest \
alpine chown -R 10001:0 /dest
docker compose up -d
```
После этого MSSQL стартует с production data, делает crash recovery в master.mdf, поднимается за 30-60 секунд (если CU контейнера == CU source) или 5-30 минут (если CU source старше — script upgrade mode).
## sqlcmd "$( var-substitution" pitfall
Альтернативный путь — restore из `.sql` script (export через "Generate Scripts" в SSMS). Файл 547 MB с CREATE+INSERT'ами всей БД.
**Не запустить через `sqlcmd -i file.sql`** без флага `-x`. Потому что:
- В данных встречаются jQuery JS-сниппеты типа `INSERT ... VALUES (N'$(function(){ ... })')`
- sqlcmd по умолчанию интерпретирует `$(varname)` как переменную для подстановки.
- На `$(function(){` парсер ломается → "Syntax error near command '('" в середине файла.
Фикс: `sqlcmd -x` (disable variable substitution).
```
docker exec mssql /opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P '<pw>' -C -x \
-i /var/opt/mssql/backup/MoreThenCms.sql
```
## SA-аккаунт disabled в production
Production SQL Server конфигурации часто **отключают `sa`** (security best practice). Пароль может быть прав, но account disabled → `Login failed for user 'sa'. Reason: The account is disabled.`
Фикс: **`mssql-conf set-sa-password`** в offline-mode (server stopped) под root:
```
# Stop running container
docker compose stop
# Run offline mssql-conf in temp container с тем же volume
docker run --rm --user 0:0 \
-e ACCEPT_EULA=Y \
-e MSSQL_SA_PASSWORD='<new-pw>' \
-v mssql_mssql_data:/var/opt/mssql \
mcr.microsoft.com/mssql/server:2019-latest \
/opt/mssql/bin/mssql-conf set-sa-password
# Запуск нормального container
docker compose start
```
`mssql-conf set-sa-password` сбрасывает пароль И **enables sa**. Если env-pw совпадает с тем что ожидает приложение — приложение продолжает работать.
## Production passwords reuse
Замеченный паттерн: у пользователя один пароль `fXkH4@8O%3pc` используется как:
- SA password (MSSQL)
- Connection string password для user `snolla` (CMS DB user)
- Возможно где-то ещё
Лучше: rotate after recovery, разделить.
## CU mismatch warning
Если master.mdf был из старой CU (например 2019 CU14), а контейнер `2019-latest` (например CU32-GDR), MSSQL после старта войдёт в **script upgrade mode** на 10-30 минут:
```
Server is in script upgrade mode. Only administrator can connect at this time.
```
Видно в логах сотни строк `spid9s ... Deleting AlwaysOnAgReplicas...` — это нормальный msdb-upgrade. Подождать. Один раз. После завершения — никаких задержек на следующих стартах.
## Healthcheck path для 2019-latest image
В современных image SQL Server инструменты лежат в `/opt/mssql-tools18/bin/sqlcmd` (с TLS-флагом `-C`), а не `/opt/mssql-tools/bin/sqlcmd`:
```yaml
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -C -Q 'SELECT 1' -b -o /dev/null || exit 1"]
```
Связано: [[snolla-recovery-vm]] (Web.config указывает на `10.0.2.2:1433` = host из VM-NAT), [[windows-recovery-host]].