初期化済みSQL ServerのDockerイメージの作り方
はじめに
たとえばテストなどにおいて、最初からテストデータが用意されているDBを用意しておきたいというのは、ありがちな要求なのではないでしょうか?
SQL Serverでそんなイメージを用意したいと思ったのですが、公式で紹介されている方法は
初期化済みのSQL Serverイメージの作り方
ではなく
起動時に初期化するSQL Serverイメージの作り方
になっていて、ちょっと欲しいものと違います。
テストで何度も繰り返し実行されるので、最初から初期化しておいてすぐ使いたいのです。たとえばSQL ServerのサンプルDB「AdventureWorks」の初期化には30秒程度かかりますので、これを可能な限り0にしたい訳です。
というわけで、本稿ではその方法を解説したいと思います。
概略
マルチステージビルドを利用して初期化済みのDBファイルを作成し、イメージ内にコピーしておく。
起動時は初期化済みDBファイルをアタッチのみする。
下記に動くものを公開しているので参考にどうぞ。
Dockerファイル
FROM mcr.microsoft.com/mssql/server:2022-latest as Builder
WORKDIR /work/
COPY build/ .
RUN /bin/bash ./setup.sh
FROM mcr.microsoft.com/mssql/server:2022-latest
WORKDIR /var/opt/mssql/data/
COPY /var/opt/mssql/data/AdventureWorks.mdf .
COPY /var/opt/mssql/data/AdventureWorks_log.ldf .
COPY run/ /opt/adventureworks
ENTRYPOINT ["/bin/bash", "/opt/adventureworks/entrypoint.sh"]
ざっと説明すると、最終的なベースイメージ「mcr.microsoft.com/mssql/server:2022-latest」を2回起動して、1回目でDBファイルを作成し、2回目のほうでDBファイルをコピーしてイメージ化しています。
DBの初期化
#start SQL Server, start the script to create the DB and import the data, start the app
export ACCEPT_EULA=Y
export SA_PASSWORD=P@ssw0rd!
/opt/mssql/bin/sqlservr &
#run the setup script to create the DB and the schema in the DB
#do this in a loop because the timing for when the SQL instance is ready is indeterminate
for i in {1..50};
do
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P P@ssw0rd! -d master -i ./instawdb.sql
if [ $? -eq 0 ]
then
echo "instawdb.sql completed"
break
else
echo "not ready yet..."
sleep 1
fi
done
それほど難しいことはしていません。
まずSQL Serverを起動します。
/opt/mssql/bin/sqlservr &
その後、forの中でsqlcmdをつかってinstawdb.sqlをつかってDBの作成と初期データの投入をしています。instawdb.sqlはAdventureWorksで提供しているクエリーで、実際には任意の初期化クエリーを利用します。
forで回しているのは、SQL Serverが起動するまでエラーになるので、成功するまでリトライするためです。
イメージの起動処理
エントリーポイントはつぎのようにシェルスクリプトが指定されています。
ENTRYPOINT ["/bin/bash", "/opt/adventureworks/entrypoint.sh"]
中身は下記のとおりです。
#!/bin/bash
/opt/adventureworks/setup-database.sh & /opt/mssql/bin/sqlservr
setup-database.shの実行と、SQL Serverの起動を行っています。SQL Serverの起動を右側に書いておかないと、setup-database.sh終了後にコンテナーが停止してしまうので注意してください。
setup-database.shの中身は下記のとおり。
#run the setup script to create the DB and the schema in the DB
#do this in a loop because the timing for when the SQL instance is ready is indeterminate
for i in {1..50};
do
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -d master -i /opt/adventureworks/setup-database.sql
if [ $? -eq 0 ]
then
echo "setup-database.sql completed"
break
else
echo "not ready yet..."
sleep 1
fi
done
sqlcmdでsetup-database.sqlを実行しています。
そしてsetup-database.sqlの中身はつぎのとおり。
USE [master]
GO
EXEC sp_attach_db [AdventureWorks], N'/var/opt/mssql/data/AdventureWorks.mdf', N'/var/opt/mssql/data/AdventureWorks_log.ldf'
GO
初期化済みのDBファイルをアタッチしているだけです。これで素早く起動できますね。
おまけ
なんでこの流れに行きついてかというと、マルチステージにしないでSQL Server起動して初期化してイメージを作ると、正しく動作せずDBに接続できなかったからです。
これはおそらく、シャットダウンを適正にできていないからなんじゃないか?という気がするんですが・・・
- SQL Server Linux版の終了処理は、Docker版では動かなかった
- Dockerのsqlservrコマンドには終了オプションが少なくともヘルプやドキュメントにはない
ドキュメント非公開のとこを探ればできる気がするんですが、いつ動かなくなるか分からないものを使うのも・・・ということで、その方向は諦めました。
じゃぁこの方法で作ったDBファイルは正常なのか?というと、DBを停止してデタッチ処理してからコピー&アタッチしているので、Docker上でも認められている正規の方法なので大丈夫だろうと判断しました。
おしまい!
Discussion
この記事を参考に自分のDBを構築してみました。
その時ハマったポイントにsetup.shなどの改行コードがCRLRだとスクリプト実行時にエラーが出るようです。
VSCODEなどでLFのみの改行コードにしたらうまく行きました。