Microsoft SQL Server on Linux 2017 の Docker を作成する

# 前置き

SQL Server on Linux 2017 が公開されたので、早速 Docker を作成していきます。
intra-mart の検証用として利用したいので、Dockerfile の最後で検証用ユーザの作成も行います。
（[権限の付与や、intra-mart が要求する照合順序も設定します](https://www.intra-mart.jp/download/product/iap/setup/iap_setup_guide/texts/install/common/database.html#microsoft-sql-server)）
これによって、MS 公式の Docker イメージと異なり、docker run 後何も考えずにすぐに検証用として利用できるような SQL Server の環境が作れるようになります。

# 注意

SQL Server on Linux は DTC に対応していないので DTC が必要となる分散トランザクションは利用できません。
[SQL Server Linux: Distributed transactions requiring the Microsoft Distributed Transaction Coordinator service are not supported on SQL Server running on Linux. SQL Server to SQL Server distributed transactions are supported. – CLARIFIED!!!](https://blogs.msdn.microsoft.com/bobsql/2017/12/11/sql-server-linux-distributed-transactions-requiring-the-microsoft-distributed-transaction-coordinator-service-are-not-supported-on-sql-server-running-on-linux-sql-server-to-sql-server-distributed-tr/)


# タイムゾーンに関する脱線

Postfix の Docker の記事で述べましたが自分が Docker を作成した段階ではタイムゾーンの設定方法により SQL Server のタイムゾーンに差異がでました。（やり方によっては Asia/Tokyo にならなかった）
詳しくは[こちら](https://qiita.com/manymanyuni/items/9ccf1c4ea76813c806a3#%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%BE%E3%83%BC%E3%83%B3%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%84%B1%E7%B7%9A)を参照してください。
本記事ではちゃんと Asia/Tokyo になるように Dockerfile を作成します。

# Docker ベースイメージ(CentOS 7.4)

いつものようにベースイメージから作ります。
本記事執筆時点ではベースは CentOS のバージョン 7.4 です。
OS のバージョンを固定したければ centos:7.4.1708 などのタグがあるのでそちらを使う手もあります。

```dockerfile:Dockerfile
FROM centos:centos7

EXPOSE 22

ENV DEBIAN_FRONTEND noninteractive

# yum
RUN yum -y update && yum clean all
RUN yum -y install dbus systemd

# locale
RUN yum reinstall -y glibc-common
RUN localedef -i ja_JP -f UTF-8 ja_JP.utf8
RUN touch /etc/sysconfig/i18n
RUN echo 'LANG="ja_JP.UTF-8"' >> /etc/sysconfig/i18n
ENV LANG ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja

# timezone
RUN yum install -y tzdata
RUN echo 'ZONE="Asia/Tokyo"' > /etc/sysconfig/clock
RUN echo 'UTC=false' >> /etc/sysconfig/clock
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# tools
RUN yum groupinstall -y 'Development Tools'
RUN yum install -y --enablerepo centosplus wget curl vim emacs tar unzip mlocate perl ssh openssh-server openssl-devel

# root passwd
RUN bash -c 'echo "root:password" | chpasswd'

# ssh
RUN sed -i -e "s/#PasswordAuthentication yes/PasswordAuthentication yes/g" /etc/ssh/sshd_config
RUN sed -i -e "s/#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
RUN sed -i -e "s/UsePAM yes/UsePAM no/g" /etc/ssh/sshd_config

RUN updatedb

CMD /sbin/init
```

locate コマンドが便利なので Dockerfile の最後に必ず入れるようにしてます。
やってるのはロケールとタイムゾーンと ssh を設定しているだけです。

# Docker SQL Server on Linux 2017

さて次は SQL Server on Linux 2017 の Docker です。
先ほどの CentOS 7 をベースイメージとして利用します。

Dockerfile 以外にも以下のファイルを用意します。

* create_user.sh
* create_user.sql
* Dockerfile
* drop_user.sh
* drop_user.sql
* run_server.sh
* setup_sqlserver.sh

まずは Dockerfile から。

```dockerfile:Dockerfile
FROM mycentos:7.4

RUN yum -y update && yum clean all
RUN yum -y upgrade && yum -y update && yum clean all
RUN yum -y install --setopt=protected_multilib=false epel-release
RUN yum -y install sudo multitail ncurses-devel ncurses-static ncurses-term

ADD create_user.sql /create_user.sql
ADD drop_user.sql /drop_user.sql

ADD create_user.sh /create_user.sh
ADD drop_user.sh /drop_user.sh
RUN chmod +x /create_user.sh
RUN chmod +x /drop_user.sh
RUN ln -s /create_user.sh /root/create_user.sh
RUN ln -s /drop_user.sh /root/drop_user.sh

ADD run_server.sh /run_server.sh
RUN chmod +x /run_server.sh

# SQL Server on 2017 セットアップ
ADD setup_sqlserver.sh /setup_sqlserver.sh
RUN chmod +x /setup_sqlserver.sh
RUN /setup_sqlserver.sh
RUN rm -f /setup_sqlserver.sh

RUN updatedb

CMD /run_server.sh
```

SQL Server のセットアップ等、複雑な物はシェルスクリプトに逃がし、Dockerfile からはそれを実行するようにします（Dockerfile がシンプルになる、かつイメージ容量の削減にもつながります）
ログを tail するのに multitail を利用するため、yum install しています。

続いて、SQL Server のセットアップです。
セットアップ手順やシェルスクリプト内で設定している変数は[こちら](https://docs.microsoft.com/ja-jp/sql/linux/sql-server-linux-configure-environment-variables)を参考にしました。
docker build 内では systemctl が使えないので pkill でプロセスを殺したりと苦労してます。
いい方法ないですかね・・・。

```shell:setup_sqlserver.sh
#!/bin/sh

curl -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/7/prod.repo
curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2017.repo
yum update -y && yum clean all
yum install -y mssql-server

# サイレントインストールするためにいくつか設定
# ライセンス同意
export ACCEPT_EULA='Y'
# sa ユーザのパスワード
export MSSQL_SA_PASSWORD='password'
# Developer エディション
export MSSQL_PID='Developer'
# 言語は日本語
export MSSQL_LCID=1041
# デフォルトの照合順序
export MSSQL_COLLATION='Japanese_XJIS_100_CS_AS_KS_WS'
# メモリ上限
export MSSQL_MEMORY_LIMIT_MB=2048
# 使用するポート
export MSSQL_TCP_PORT=1433
# リッスンアドレス
export MSSQL_IP_ADDRESS='0.0.0.0'
# 可用性グループを有効
export MSSQL_ENABLE_HADR=1
# SQL Server エージェントを利用
export MSSQL_AGENT_ENABLED=true
/opt/mssql/bin/mssql-conf setup
# カスタマーフィードバックを OFF
/opt/mssql/bin/mssql-conf set telemetry.customerfeedback false

# systemctl restart mssql-server.service が使えないので pkill と /bin/sqlserver で代用
pkill sqlservr
sudo -EH -u mssql /bin/bash -c "/opt/mssql/bin/sqlservr >> /var/opt/mssql/log/sqlservr.log 2>&1 &"

yum remove unixODBC-utf16 unixODBC-utf16-devel
export ACCEPT_EULA='Y'
yum install -y mssql-tools unixODBC-devel
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> /root/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> /root/.bashrc
source /root/.bashrc

# intra-mart 検証用の DB, ユーザを作成。不要であればコメントアウトするだけで OK です
/create_user.sh
# systemctl stop mssql-server.service が使えないので pkill　で代用
pkill sqlservr
```

続いて docker run 時に実行する SQL Server on Linux 2017 を起動するためのスクリプトです。
愚直に docker run した場合 systemctl が使えないため、sshd や SQL Server の起動を直接行っています。

```shell:run_server.sh
# systemctl restart sshd.service
# systemctl restart mssql-server.service
ssh-keygen -A
/usr/sbin/sshd
sudo -EH -u mssql /bin/bash -c "/opt/mssql/bin/sqlservr >> /var/opt/mssql/log/sqlservr.log 2>&1 &"

touch /var/opt/mssql/log/sqlservr.log
touch /var/opt/mssql/log/errorlog

multitail -M 0 --follow-all --retry-all /var/opt/mssql/log/sqlservr.log -I /var/opt/mssql/log/errorlog
# multitail を利用したくない場合、以下で代用可
# tail -F /var/opt/mssql/log/sqlservr.log /var/opt/mssql/log/errorlog
```

続いて、intra-mart 用 DB とユーザの作成です。
これを docker build 時に行っておくことで、docker run 後にすぐ利用できるようになります。

以下のユーザを作成しています。
ユーザ名：imart
パスワード：imart

以下の DB を作成しています。

* imart
* iap_db
* default

すべての DB の照合順序は [Japanese_XJIS_100_CS_AS_KS_WS](https://www.intra-mart.jp/download/product/iap/setup/iap_setup_guide/texts/install/common/database.html#microsoft-sql-server) です。
すべての DB に対して [acceldocuments](https://www.intra-mart.jp/download/product/iad/im_acceldocuments_setup_guide/texts/setup_guide/database/index.html#microsoft-sql-server) スキーマも作成しています。
すべての DB に対して [READ_COMMITTED_SNAPSHOT](https://www.intra-mart.jp/download/product/iap/setup/iap_setup_guide/texts/install/common/database.html#microsoft-sql-server) を設定しています。

```shell:create_user.sh
#!/bin/sh

sqlcmd -S localhost -U sa -P 'password' -i /create_user.sql
```

```sql:create_user.sql
-- imart user
USE [master]
GO
CREATE LOGIN [imart] WITH PASSWORD=N'imart', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO

-- imart
CREATE DATABASE [imart]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'imart', FILENAME = N'/var/opt/mssql/data/imart.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
 LOG ON 
( NAME = N'imart_log', FILENAME = N'/var/opt/mssql/data/imart_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
 COLLATE Japanese_XJIS_100_CS_AS_KS_WS
GO
ALTER DATABASE [imart] SET READ_COMMITTED_SNAPSHOT ON;
GO

USE [imart]
GO
EXEC dbo.sp_changedbowner @loginame = N'imart', @map = false
GO

-- iap_db
CREATE DATABASE [iap_db]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'iap_db', FILENAME = N'/var/opt/mssql/data/iap_db.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
 LOG ON 
( NAME = N'iap_db_log', FILENAME = N'/var/opt/mssql/data/iap_db_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
 COLLATE Japanese_XJIS_100_CS_AS_KS_WS
GO
ALTER DATABASE [iap_db] SET READ_COMMITTED_SNAPSHOT ON;
GO

USE [iap_db]
GO
EXEC dbo.sp_changedbowner @loginame = N'imart', @map = false
GO

-- imart default database
ALTER LOGIN [imart] WITH DEFAULT_DATABASE=[iap_db], DEFAULT_LANGUAGE=[日本語], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO

-- default
CREATE DATABASE [default]
 CONTAINMENT = NONE
 ON  PRIMARY 
( NAME = N'default', FILENAME = N'/var/opt/mssql/data/default.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
 LOG ON 
( NAME = N'default_log', FILENAME = N'/var/opt/mssql/data/default_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )
 COLLATE Japanese_XJIS_100_CS_AS_KS_WS
GO
ALTER DATABASE [default] SET READ_COMMITTED_SNAPSHOT ON;
GO

USE [default]
GO
EXEC dbo.sp_changedbowner @loginame = N'imart', @map = false
GO

-- acceldocuments
USE [imart]
GO
CREATE SCHEMA [acceldocuments] AUTHORIZATION [db_owner]
GO

USE [iap_db]
GO
CREATE SCHEMA [acceldocuments] AUTHORIZATION [db_owner]
GO

USE [default]
GO
CREATE SCHEMA [acceldocuments] AUTHORIZATION [db_owner]
GO
```

続いて、ユーザを drop する処理です。
docker run し直せばいい話ですが、docker は起動したままでユーザを作り替えたいという場合にご利用ください。

```shell:drop_user.sh
#!/bin/sh

sqlcmd -S localhost -U sa -P 'password' -i /drop_user.sql
```

```sql:drop_user.sql
USE [master]
GO

DROP DATABASE [default]
GO
DROP DATABASE [iap_db]
GO
DROP DATABASE [imart]
GO
```

# 起動

docker run -it -p 0.0.0.0:1433:1433 -p 0.0.0.0:2222:22 mysqlserver_on_linux:2017

# データ永続化

データを永続化したい場合、docker volume を使えば OK です。
named volume であれば docker run 時に docker コンテナ内のデータが docker volume にコピーされてマウントされるため、次のように起動することで永続化が可能です。

docker run -v sqlserver:/var/opt/mssql/data -it -p 0.0.0.0:1433:1433 -p 0.0.0.0:2222:22 mysqlserver_on_linux:2017

# SQL Server Management Studio

ユーザ sa/password で接続可能です。
接続先は docker run しているマシンの IP アドレスです。

というわけで簡単にですが SQL Server on Linux 2017 の Docker についての紹介でした。