DockerでSQLServerを起動&初期データ登録

SQLServerのDockerコンテナに初期データを投入して起動します。
マイクロソフト公式のDockerイメージを使用します。MySQLだと初期データ投入の仕組みが初めから組み込まれていますが、SQLServerにはそれないのでシェルを作成しました。

Microsoft SQL Server Dockerイメージ（[https://hub.docker.com/_/microsoft-mssql-server](https://hub.docker.com/_/microsoft-mssql-server)）
参考：公式からリンクされているデータ投入サンプル（[https://github.com/twright-msft/mssql-node-docker-demo-app](https://github.com/twright-msft/mssql-node-docker-demo-app)）
[Qiita Docker for Windowsを使って、SQL Server Express を使用する](https://qiita.com/takanemu/items/be99c7445f5832f45064)

# ディレクトリー構成

```
|- docker-compose.yml
|- docker
    |- mssqlserver
        |- Dockerfile
        |- initdb.d
            |- entrypoint.sh
            |- import-data.sh
            |- demo.sql
            |- Demo.dbo.DEMO.csv
        |- data
        |- log
        |- secrets
```

## 各ファイル

### docker-compose.yml

environmentで設定しているSQL Serverの環境変数については[こちら](https://docs.microsoft.com/ja-jp/sql/linux/sql-server-linux-configure-environment-variables?view=sql-server-ver15)を参照。

```yaml:docker-compose.yml
version: '3'

services:
  mssql:
    build:
      context: ./docker/mssqlserver/
    image: mssql
    ports:
      - 1433:1433
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=56CFUVzh # パスワードポリシー(https://docs.microsoft.com/ja-jp/sql/relational-databases/security/password-policy?view=sql-server-ver15)
      - MSSQL_PID=Express # SQL Serverのエディションまたはプロダクトキー
      - MSSQL_LCID=1041 # ロケールID 日本語 (https://www.ryadel.com/en/microsoft-windows-lcid-list-decimal-and-hex-all-locale-codes-ids/)
      - MSSQL_COLLATION=Japanese_CI_AS # 照合順
      - DB_NAME=DEMO # 初回起動時のDB存在判定のために使用します。
    volumes:
      - ./docker/mssqlserver/initdb.d:/docker-entrypoint-initdb.d
      - ./docker/mssqlserver/data:/var/opt/mssql/data
      - ./docker/mssqlserver/log:/var/opt/mssql/log
      - ./docker/mssqlserver/secrets:/var/opt/mssql/sec
```

### Dockerfile

初期データと投入用シェルスクリプトをコピーし、entrypoint.shを呼び出します。
（ちなみに`EXPOSE`はただのドキュメントらしいです。）

```dockerfile:Dockerfile
FROM mcr.microsoft.com/mssql/server:2017-latest

USER root
SHELL ["/bin/bash", "-c"]

WORKDIR /docker-entrypoint-initdb.d
COPY ./initdb.d/ /docker-entrypoint-initdb.d/
RUN chmod -R +x /docker-entrypoint-initdb.d

EXPOSE 1433

ENTRYPOINT ["/bin/bash", "./entrypoint.sh"]
```

### entrypoint.sh

データ登録のシェルとSQLServerを同時起動します。ここで少しはまったのですがSQLServerの起動を先にするとimport-data.sh実行後コンテナが停止してしまいます。SQLServerを後ろにするとコンテナは起動し続けます。~~（つまりDockerは一番最後に実行したプロセスが生きているかどうかでコンテナを停止するのか判断している？）~~
→これは単純にどちらをバックグラウンドで実行するかの違いでした。SQLServerをバックグラウンドで実行してしまっているので、import-data.shが終わるとプロセスID=1のこのシェルが終了します。PID=1のプロセスが停止するとDockerが停止します。詳しくはコメント欄の2件目を参照してください。

Initialize MS SQL in Docker container - create database at startup（[https://www.softwaredeveloper.blog/initialize-mssql-in-docker-container](https://www.softwaredeveloper.blog/initialize-mssql-in-docker-container)）

```sh:entrypoint.sh
#!/bin/bash

LOG_OUT=/var/opt/mssql/log/init-stdout.log
LOG_ERR=/var/opt/mssql/log/init-stderr.log

exec 1>>$LOG_OUT
exec 2>>$LOG_ERR

# SQL Serverが前だとコンテナが停止してしまいます。
#/opt/mssql/bin/sqlservr & /docker-entrypoint-initdb.d/import-data.sh
/docker-entrypoint-initdb.d/import-data.sh & /opt/mssql/bin/sqlservr
```

### import-data.sh

SQLServerと同時起動するので、インスタンスが起動するまで初めに20秒スリープします。また、SQLやCSVインポートを実行してエラーの場合1秒待って再実行するようにしています。

```sh:import-data.sh
#!/bin/bash
sleep 20

if [ `ls -U1 /var/opt/mssql/data | grep $DB_NAME | wc -l` -eq 0 ]; then
    cd /docker-entrypoint-initdb.d
    sql_files=`ls *.sql`

    for file in $sql_files;
    do
        for i in {1..30};
        do
            /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -i $file
            if [ $? -eq 0 ]
            then
                echo "${file} completed."
                break
            else
                echo "${file} failed."
                sleep 1
            fi
        done
    done

    csv_files=`ls *.csv`
    for file in $csv_files;
    do
        for i in {1..30};
        do
            table_name=`basename $file .csv`
            /opt/mssql-tools/bin/bcp $table_name in $file -c -t',' -S localhost -U sa -P $SA_PASSWORD
            if [ $? -eq 0 ]
            then
                echo "${file} completed."
                break
            else
                echo "${file} failed."
                sleep 1
            fi
        done
    done
fi
```