Microsoft SQL сервер
в Docker-compose окружении

Актуально на октябрь 2022 г. (Проект на GitHub)

Давайте определимся, что мы выигрываем от переноса MS SQL сервера в docker.
1) Возможность быстрого запуска под свежепереустановленной системой
2) Работа приложения оказывается в изолированном пространстве, что всегда безопаснее чем работа на сервере с дюжиной сервисов разной важности
3) Запуск делать будем под Linux окружением
4) Запуск по настоящему прост и лаконичен. Не надо учитывать особенности окружающей ОС, бояться позже сдвинуть какие-то настройки, нарушить версии вспомогательных библиотек.
5) Относительно легко контролировать расход памяти и процессора

Есть и недостатки... (наверно). Но я о них не знаю. )))
Этапы работ
Этап 0. Подготовка рабочей среды.
Этап 1. Установка Docker и Docker compose
Этап 2. Первый упрощенный запуск MS SQL сервера в контейнере
Этап 3. Детальная настройка сервера в контейнере
Этап 4. Вынос пароля в отдельный файл Secret
Этап 5. Монтирование каталога с данными на хостовую систему
Этап 0. Подготовка рабочей среды (развернуть).
Я бы рекомендовал установить MS Visual Code на вашем основном рабочем компьютере и через ssh подключиться к хостовой операционной системе, на которой вы будете запускать docker.
В моем случае это выглядит так. Основной компьютер с Windows pro. В Hyper-V машине установлен linux Debian или Ubuntu (без разницы), в котором уже и запускаются docker контейнеры. .
Подключение к рабочей папке на Ubuntu делаю через расширение SSH FS. Не поленитесь з разберитесь как это работает. Вы наверняка еще 100500 раз примените эту схему в других работах.
Основное преимущество такой схемы
  • Работаем на привычной нам ОС Windows/Linux/Mac OS с уже готовым инструментарием для бэкапов.
  • Используем удобную расширяемую IDE благодаря чему в одном окне видим как структуру каталогов, имеем возможность редактировать каждый файл (я ставлю автозапись при потере фокуса, но это уже вкусовщика). И тут же терминал (или несколько), для запуска команд управления. Принцип "одного окна" подкупает!
  • По необходимости мы подключаем расширения "линтеры" для проверки синтаксиса docker и docker-compose.yml, как впрочем и любых других файлов. Можем просматривать и редактировать большое количество типов файлов.
  • Встроенный терминал уже поддерживает подсветку синтаксиса
Я проповал настроить аналогичную схему через Pycharm IDE, но их модуль подключения по SSH по состоянию на октябрь 2022 года был еще в стадии Beta и сразу же засыпал меня кучей нервно открывающихся и закрывающихся окон и ошибок.... Отчего был решительно отвергнут. Да и помним, что он платный, как только вы захотите использовать чуть больше чем "стандартный" набор commu
Этап 1. Установка Docker и Docker compose (развернуть)
Этот этап хорошо описан на сайте docker.com.
От себя лишь добавлю несколько нюансов.
  • Если хостовая система Linux - то важно проделать этап PostInstall (добавление локального пользователя в группу docker)
  • При установке docker-compose существуют две версии - 1.6 и 2.0. Первая будет работать по команде docker-compose. А вторая - docker compose (два отдельных слова без дефиса). Рекомендую, конечно версию 2.0
  • При использовании docker compose 2.0 сборка (Build) будет сопровождаться сжатым текстом, что в большинсве случаев удобно. Но если вам нужно выяснить обстоятельства процесса подробнее - используйте ключ build ЗДЕСЬ КЛЮЧ
  • Если ваша хостовая ситема на Windows - то, вероятно, вам придется забыть о пробросе тома с базаой данных на хостовую машину. Насколько я понимаю, в таком случае docker назначит права доступа внутри контейнера 755, что сделает невозможным запись (перезапись) в этот каталог данных. Так что использование Windows по сути нам закрыто...
Этап 2. Первый упрощенный запуск MS SQL сервера в контейнере
Готовим docker-compose.yml следующего содержания
# docker-compose.yml
version: "3"
services:
  sql-server:
    image: mcr.microsoft.com/mssql/server:2019-latest
    ports:
      - "1433:1433"
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=MyPass123
      - MSSQL_PID=Express
Этап 3. Детальная настройка сервера в контейнере
Усложним настройку.
Подготовим новую структуру решения.
Во-первых радом c docker-compose.yml создадим папку src_msSQL где будут хранится следующие файлы: Dockerfile, entrypoint.sh и init-db.sql
Также рядом с docker-compose.yml расположим файл .env
каталог с решением
----------------------------------------
Docker-compose.yml
.env
src_msSQL
    Dockerfile
    entrypoint.sh
    init-db.sql

В файле .env можно указывать произвольные переменные, так же как это делается в раздели Environment файла Docker-compose.yml. Использовать его можно, например для хранения паролей, и запретить выгрузку этого файла в git
# .env

    COMPOSE_PROJECT_NAME=sql_server_compose
В файле Dockerfile укажем алгоритм сборки нашего контейнера
# Dockerfile

FROM mcr.microsoft.com/mssql/server:2019-latest
EXPOSE 1433
WORKDIR /app
COPY ./entrypoint.sh ./
COPY ./init-db.sql ./
USER root
RUN chmod +x ./entrypoint.sh
RUN mkdir -p /var/opt/mssql/data && chown mssql /var/opt/mssql/data
RUN mkdir -p /var/opt/mssql/log && chown mssql /var/opt/mssql/log
RUN mkdir -p /var/opt/mssql/backup && chown mssql /var/opt/mssql/backup
USER mssql
ENTRYPOINT ["./entrypoint.sh"]
Cкрипт entrypoint будет вызываться при старте нашего контейнера. В него добавлена процедура init_db единственный смысл которой - убедиться что сервер запущен и успешно функционирует
# entrypoint.sh

#!/bin/bash

set -e # Exit immediately if a command exits with a non-zero status.

function echo-red     { COLOR='\033[31m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }
function echo-green   { COLOR='\033[32m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }
function echo-yellow  { COLOR='\033[33m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }
function echo-blue    { COLOR='\033[34m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }

echo-yellow "[ INFO ] started entrypoint.sh script"

init_db () {
  echo-yellow "[ INFO ] started init_db function"

  PASS_TO_INIT=$SA_PASSWORD

  INPUT_SQL_FILE="init-db.sql"
  until /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U sa -P "$PASS_TO_INIT" -i $INPUT_SQL_FILE > /dev/null 2>&1
  do
    echo-red "SQL server is unavailable - password=$PASS_TO_INIT- sleeping 1 sec...."
    sleep 1 # Sleep for a second....
  done
  
  echo-green "Success initialized db"
}

init_db & /opt/mssql/bin/sqlservr
И наконец, docker-compose.yml будет расширен дополнительными настройками, суть которых, надеюсь понятна из самого файла.
#docker-compose.yml

version: "3.7"
services:
  sql2019server:
    build:
      context: ./src_msSQL
      dockerfile: Dockerfile
    #image: mcr.microsoft.com/mssql/server:2019-latest
    image: ${COMPOSE_PROJECT_NAME:?err}-mssql-2019-latest
    container_name: ${COMPOSE_PROJECT_NAME:?err}_container
    restart: unless-stopped #always
    #hostname: sql-server
    
    ports:
      - "1433:1433"
    environment:
      - "ACCEPT_EULA=Y"
      - "MSSQL_USER=SA"
      - "SA_PASSWORD=MyPass123"
      - "MSSQL_PID=Express"
      - "MSSQL_LCID=1049" # русский
      - "MSSQL_COLLATION=1049"
      - "MSSQL_MEMORY_LIMIT_MB=3500" #задает максимальный объем памяти (в МБ), который можно использовать SQL Server. По умолчанию он составляет 80% от общего объема физической памяти.
      - "MSSQL_TCP_PORT=1433"
#      - "MSSQL_IP_ADDRESS=0.0.0.0"
      - "MSSQL_BACKUP_DIR=/var/opt/mssql/backup" #Задайте расположение каталога резервного копирования по умолчанию.
      - "MSSQL_DATA_DIR=/var/opt/mssql/data" #Перейдите в каталог, где создаются новые базы данных файлы данных SQL Server (.mdf).
      - "MSSQL_LOG_DIR=/var/opt/mssql/log" #где создаются новые файлы журналов (LDF) базы данных SQL Server.
      - "MSSQL_DUMP_DIR=/var/opt/mssql/dumps" #где SQL Server будет Депонировать дампы памяти и другие файлы для устранения неполадок по умолчанию.
      - "MSSQL_ENABLE_HADR=0" #Включение группы доступности. Например "1" включена и отключена "0" - репликация
#      - "MSSQL_AGENT_ENABLED=true" #Включите агент SQL Server. Например «true» включен, и «false» отключена. По умолчанию агент отключен.
#      - "MSSQL_MASTER_DATA_FILE" #Задает расположение файла данных базы данных master.
#      - "MSSQL_ERROR_LOG_FILE" #Задает расположение файла журнала базы данных master.
#      - "MSSQL_MASTER_LOG_FILE" #Задает расположение файлов журнала ошибок.

    deploy:
      resources:
        limits:    
          memory: 4G
          cpus: '2.0'

Запускаем снова контейнер и проверяем что сервер снова работает!!!
Важно!!! Изменив настройки контейнера - новый запуск делать с ключем --build
docker compose up --build
Хранить пароль в файле Docker-compose.yml большой грех. Давайте это исправим...
Как раньше говорил, пароль можно перенести в файл .env и в дальнейшем следить что бы он не выгружался в git. Но это не очень красивое решение.
Покажу как пароль подключать через специальный механизм хранения секретов.

Этап 4. Вынос пароля в отдельный файл Secret
в Docker-compose вносим следующие изменения
  • в переменных окружения environments убираем (комментируем) переменную SA_PASSWORD и добавляем переменную MSSQL_SA_PASSWORD_FILE
- "MSSQL_SA_PASSWORD_FILE=/run/secrets/sa_password"

  • в описании сервиса sql2019server, например перед разделом environments добавляем радел secrets
secrets:
- sa_password

  • в глобальном разделе (например в конце файла сообщаем об использовании механизма секретов, хранимых в файле
#глобальный раздел
secrets:
sa_password:
file: ./sa_password.secret # Add this file in .gitignore to ignore from a repository

  • в каталоге решения рядом с docker-compose.yml создаем файл sa_password.secret в котором только одна строка с паролем для SQL сервера
MyPass123FromSecret

  • в скрипте entrypoint.sh в процедуре init_db указывает на необходимость прочитать пароль из секрета проброшенного в контейнер
......
init_db () {
echo-yellow "[ INFO ] started init_db function"

PATH_TO_SECRET_sa_password=/run/secrets/sa_password
if [ ! -z ${PATH_TO_SECRET_sa_password+x} ]
then
PASS_TO_INIT=$(cat ${PATH_TO_SECRET_sa_password})
echo-green "[ SUCCESS ] FINDED IN SECRETS PASSWORD='$PASS_TO_INIT'"
else
echo-red "[ ERROR ] DID NOT FINDED SECRET sa_password"
PASS_TO_INIT=""
fi

INPUT_SQL_FILE="init-db.sql"
....


Этап 5. Монтирование каталога с данными на хостовую систему
Для того что бы наши базы данных не стирались каждый раз при чистке томов docker важно сделать проброс каталога с данными на хостовую машину.
Делается это относительно просто, добавляем раздел volumes
volumes:
- ./_vol_msSQL/data:/var/opt/mssql/data
- ./_vol_msSQL/log:/var/opt/mssql/log
- ./_vol_msSQL/secrets:/var/opt/mssql/secrets
- ./_vol_msSQL/backup:/var/opt/mssql/backup

Вот только попробовав запустить такой контейнер вы обнаружите, что сервер отваливается с ошибкой "Отказано в доступе" к данным из проброшенных каталогов.
Это происходит из-за того что в папка _vol_msSQL создается с правами 755 под текущим пользователем, а SQL сервер в нашем контейнере запускается согласно Dockerfile под именем mssql.
Я приведу одно из решений - предоставить на созданный каталог права на чтение и запись всем пользователям для простоты отладки. В продакшн версии вам надо будет более скупо назначать права...
Перед повторным запуском контейнера в хостовой системе запустим команду
sudo chmod 777 -R ./_vol_msSQL

Также, на всякий случай напоминаю, что проброс папок с помощью volumes предназначен для передачи данных из хостовой системы в контейнер. Если же уже успели создать базы и нашем контейнере и хотите их получить - то вам нужно либо до проброса их скачать, подключившись docker exec -it name_of_container /bin/bash либо удалить(переименовать) пробрасываемый каталог что бы создался новый и в него были помещены все данные из контейнера (затем повторить команду chmod 777)

Приведу итоговые файлы нашего контейнера (Проект на GitHub)
#docker-compose.yml
-----------------------------------------

version: "3.7"
services:
  sql2019server:
    build:
      context: ./src_msSQL
      dockerfile: Dockerfile
    #image: mcr.microsoft.com/mssql/server:2019-latest
    image: ${COMPOSE_PROJECT_NAME:?err}-mssql-2019-latest
    container_name: ${COMPOSE_PROJECT_NAME:?err}_container
    restart: unless-stopped #always
    #hostname: sql-server
    
    ports:
      - "1433:1433"
    
    secrets:
      - sa_password

    environment:
      - "ACCEPT_EULA=Y"
      - "MSSQL_USER=SA"
     # - "SA_PASSWORD=MyPass123"
      - "MSSQL_SA_PASSWORD_FILE=/run/secrets/sa_password"
      - "MSSQL_PID=Express"
      - "MSSQL_LCID=1049" # русский
      - "MSSQL_COLLATION=1049"
      - "MSSQL_MEMORY_LIMIT_MB=3500" #задает максимальный объем памяти (в МБ), который можно использовать SQL Server. По умолчанию он составляет 80% от общего объема физической памяти.
      - "MSSQL_TCP_PORT=1433"
#      - "MSSQL_IP_ADDRESS=0.0.0.0"
#      - "MSSQL_BACKUP_DIR=/var/opt/mssql/backup" #Задайте расположение каталога резервного копирования по умолчанию.
#      - "MSSQL_DATA_DIR=/var/opt/mssql/data" #Перейдите в каталог, где создаются новые базы данных файлы данных SQL Server (.mdf).
#      - "MSSQL_LOG_DIR=/var/opt/mssql/log" #где создаются новые файлы журналов (LDF) базы данных SQL Server.
      - "MSSQL_DUMP_DIR=/var/opt/mssql/dumps" #где SQL Server будет Депонировать дампы памяти и другие файлы для устранения неполадок по умолчанию.
      - "MSSQL_ENABLE_HADR=0" #Включение группы доступности. Например "1" включена и отключена "0" - репликация
#      - "MSSQL_AGENT_ENABLED=true" #Включите агент SQL Server. Например «true» включен, и «false» отключена. По умолчанию агент отключен.
#      - "MSSQL_MASTER_DATA_FILE" #Задает расположение файла данных базы данных master.
#      - "MSSQL_ERROR_LOG_FILE" #Задает расположение файла журнала базы данных master.
#      - "MSSQL_MASTER_LOG_FILE" #Задает расположение файлов журнала ошибок.


    deploy:
      resources:
        limits:    
          memory: 4G
          cpus: '2.0'

    volumes:
      - ./_vol_msSQL/data:/var/opt/mssql/data
      - ./_vol_msSQL/log:/var/opt/mssql/log
      - ./_vol_msSQL/secrets:/var/opt/mssql/secrets
      - ./_vol_msSQL/backup:/var/opt/mssql/backup

#глобальный раздел
secrets:
  sa_password:
    file: ./sa_password.secret # Add this file in .gitignore to ignore from a repository
# .env
-----------------------------------------

    COMPOSE_PROJECT_NAME=sql_server_compose
# sa_password.secret
-----------------------------------------

MyPass123FromSecret
# src_msSQL/Dockerfile
-----------------------------------------

FROM mcr.microsoft.com/mssql/server:2019-latest
EXPOSE 1433
WORKDIR /app
COPY ./entrypoint.sh ./
COPY ./init-db.sql ./
USER root
RUN chmod +x ./entrypoint.sh
RUN mkdir -p /var/opt/mssql/data && chown mssql /var/opt/mssql/data
RUN mkdir -p /var/opt/mssql/log && chown mssql /var/opt/mssql/log
RUN mkdir -p /var/opt/mssql/backup && chown mssql /var/opt/mssql/backup
USER mssql
ENTRYPOINT ["./entrypoint.sh"]
# src_msSQL/entrypoint.sh
-----------------------------------------

#!/bin/bash

set -e # Exit immediately if a command exits with a non-zero status.

function echo-red     { COLOR='\033[31m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }
function echo-green   { COLOR='\033[32m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }
function echo-yellow  { COLOR='\033[33m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }
function echo-blue    { COLOR='\033[34m' ; NORMAL='\033[0m' ; echo -e "${COLOR}$1${NORMAL}"; }

echo-yellow "[ INFO ] started entrypoint.sh script"

init_db () {
  echo-yellow "[ INFO ] started init_db function"

  PATH_TO_SECRET_sa_password=/run/secrets/sa_password
  if [ ! -z ${PATH_TO_SECRET_sa_password+x} ]
  then
    PASS_TO_INIT=$(cat ${PATH_TO_SECRET_sa_password})
    echo-green "[ SUCCESS ] FINDED IN SECRETS PASSWORD='$PASS_TO_INIT'"
  else
    echo-red "[ ERROR ] DID NOT FINDED SECRET sa_password"
    PASS_TO_INIT=""
  fi


  INPUT_SQL_FILE="init-db.sql"
  until /opt/mssql-tools/bin/sqlcmd -S localhost,1433 -U sa -P "$PASS_TO_INIT" -i $INPUT_SQL_FILE > /dev/null 2>&1
  do
    echo-red "SQL server is unavailable - password=$PASS_TO_INIT - sleeping 1 sec...."
    sleep 1 # Sleep for a second....
  done
  
  echo-green "Success initialized db"
}

init_db & /opt/mssql/bin/sqlservr
# src_msSQL/init-db.sql
-----------------------------------------

/* init-db.sql */
CREATE DATABASE [my-db];
USE [my-db];
CREATE TABLE [User] (
  Id INT NOT NULL IDENTITY(1,1),
  FirstName VARCHAR(50) NOT null,
  LastName VARCHAR(50) NOT NULL,
  DateOfBirth DATETIME NOT NULL
  CONSTRAINT PK_User_Id PRIMARY KEY (Id ASC)
);
INSERT INTO [User] VALUES ('Jose', 'Realman', '2018-01-01');
Made on
Tilda