初めに
環境をコンテナ化、各々の開発環境や、開発環境と実行環境の差異をなくす、開発が爆速化など、dockerに関することを目にするのですが、恥ずかしながら私はdockerの取っ掛かりにとても苦労しました。Qiitaの記事、書籍を調べ、ようやっと、なんとなくつかめた気がする、という段階です。もし同様の方が見えて、当記事がほんの少しでも助けになったら、とてもうれしいです。
環境
- Windows10 Home 20H2
- WSL2インストール済み
- Docker Desktop for Windows
- VSCode
当記事でやること
- Node.js用のコンテナ、SQLServer用のコンテナを建てる
- TypeScriptを用いる
- ブラウザからNode.jsコンテナにアクセス
- Node.jsコンテナからSQLServerコンテナに接続し、データを取得しブラウザに表示させる
なぜSQLServerなのか
当方が本業で使っているのがSQLServerであり、慣れているためです。しかし、個人で無料で開発や学習を進めるにはハードルが高いと感じ、これからはPostgreSQLに切り替えようと考えています。話はそれますが、学習のためにSQLServer Azureを建てたのですが、無料期間が切れたことが気づかず、ただのテストデータがあるだけで、1500円ほどクレジットカードから引き落とされたことがあります。今思えば、15万円でも、1万5千円でもなく、1500円でよかったです。
当記事で心がけていること
- 理屈よりも、手順を踏めば、同じように動く
- 体験後は、コンテナを廃棄し、当手順を踏む前の状態に戻す
以上を心がけています。
参考にさせて頂いた記事、サイト
-
【Node.js】 Dockerを用いてNode.js Express MySQLの環境を構築するまでの道のり - Qiita
-
【連載】WSL2、Visual Studio Code、DockerでグッとよくなるWindows開発環境 〜 その1:まずは概要 〜 | SIOS Tech. Lab
-
【連載】世界一わかりみが深いコンテナ & Docker入門 〜 その1:コンテナってなに? 〜 | SIOS Tech. Lab
-
いつから始めるの?Docker、AWS、kubernetes初学者がこっそり1日で先輩に追いつく3つの作戦 - YouTube
-
Docker:SQL Server on Linux 用のコンテナーのインストール - SQL Server | Microsoft Docs
実践
事前の状態の確認
下記コマンドを実行し状態を確認します。
- Dockerイメージを一覧する。
docker images
# 結果
REPOSITORY TAG IMAGE ID CREATED SIZE
- コンテナを一覧する。
docker ps -a
# 結果
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
プロジェクトフォルダを作る
エクスプローラーにて、任意の場所にdocker-sqlserverというフォルダを作成します。
例)D:\data\docker-sqlserver
※フォルダ名は任意で構いません。
docker-compose.yml
(プロジェクトフォルダ)\docker-compose.ymlを作成します。
version: '3'
services:
apply:
# Dockerfileのパスを指定する。
build: .
container_name: webserver
ports:
- '3000:3000'
working_dir: /app
volumes:
- ./src:/app
command: npm start
database:
image: mcr.microsoft.com/mssql/server:2017-latest
container_name: sql2017
ports:
- 1433:1433
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=<YourStrongP@ssw0rd>
- MSSQL_COLLATION=Japanese_CI_AS
volumes:
- ./database/data:/var/opt/mssql/data
- ./database/data:/var/opt/mssql/log
- ./database/secrets:/var/opt/mssql/secrets
注意
字下げ部分、タブ(\t)だと、のちのコマンド実行時エラーが発生しました。
スペース2つを用います。
Dockerfile
docker-compose.ymlから参照されるDockerfileを作成します。
このDockerfileにて、いろいろと指定できるのですが、当方はまだ知識が追い付いておりません。
FROM node:14
Node.jsコンテナに必要なパッケージをインストールする
VSCodeのターミナル画面でコマンドを実行します。
docker compose run --rm apply /bin/bash
VSCodeのターミナルの表示が以下のようになります。
"root@ae71187cc460:"の部分は、異なる表示になると思います。
root@ae71187cc460:/app#
-
イメージ、コンテナの確認(スキップしてもよい手順です)
PowerShellを開き(もしくはVSCodeで別のターミナルを開き)、Dockerイメージ、Dockerコンテナを一覧します。# イメージを一覧するコマンドを実行します。 docker images # 結果 REPOSITORY TAG IMAGE ID CREATED SIZE docker-sqlserver_apply latest 81fe3ebc1b85 4 weeks ago 943MB
# コンテナを一覧するコマンドを実行します。 docker ps # 結果 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 07bd2e4fdc83 docker-sqlserver_apply "docker-entrypoint.s…" 30 minutes ago Up 30 minutes docker-sqlserver_apply_run_ec19ff4eaa38
コンテナに入った状態になり、コマンドを入力し実行すると、ホストであるWindowsではなく、コンテナに対する操作になります。
以下の順でコンテナ内でコマンドを実行します。
# packages.jsonを初期化します。(package.jsonが作成されます。)
npm init -y
# パッケージをインストールします。
npm install --save express
npm install --save tedious
# 開発に必要なパッケージをインストールします。
npm install --save-dev typescript
npm install --save-dev @types/express
npm install --save-dev @types/tedious
npm install --save-dev nodemon
npm install --save-dev ts-loader
npm istanll --save-dev webpack
npm install --save-dev webpack-cli
npm install --save-dev webpack-node-externals
# 以下のように列挙して実行も可能です。
npm install --save express tedious
npm install --save-dev typescript @types/express ......
ホストPC側のプロジェクトフォルダの.srcフォルダにpackage.json、node_modulesフォルダが作成され、必要なファイルがダウンロードされます。
./src/package.jsonを確認します。
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"tedious": "^11.0.9"
},
"devDependencies": {
"@types/express": "^4.17.12",
"@types/tedious": "^4.0.3",
"nodemon": "^2.0.7",
"ts-loader": "^9.2.3",
"typescript": "^4.3.2",
"webpack": "^5.39.0",
"webpack-cli": "^4.7.2",
"webpack-node-externals": "^3.0.0"
}
}
./src/package.jsonのscriptsにコマンドを登録します。
{
...
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "nodemon -L",
"start": "node ./dist/server.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
...
}
TypeScriptをトランスパイルするための環境を整えます。
{
"compilerOptions": {
"sourceMap": true,
"noImplicitAny": true,
"module": "es2015",
"target": "es2017",
"jsx": "react",
"lib": ["es2018", "dom"],
"moduleResolution": "node",
"removeComments": true,
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"strictFunctionTypes": false
}
}
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
mode: 'development',
target: 'node',
externals: [ nodeExternals() ],
devtool: 'eval-source-map',
module: {
rules: [
{
loader: 'ts-loader',
test: /\.ts$/,
exclude: [/node_modules/],
options: { configFile: 'tsconfig.json'}
}
]
},
resolve: {
extensions: ['.ts', '.js', '.json']
},
entry: './src/server.ts',
output: {
filename: 'server.js',
path: path.resolve(__dirname, 'dist')
},
node: {
__dirname: false
}
};
{
"watch": [
"dist"
],
"ext": "ts, js, json",
"exec": "node ./dist/server.js"
}
サーバサイドのメインプログラムをコーディングします。
import * as Express from 'express';
const app = Express();
app.get('/', (req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
res.send('アクセス成功');
});
const portNo = process.env.PORT || 3000;
app.listen(portNo, () => {
console.log(`app running on port ${portNo}.`);
});
export default app;
コンテナ内でトランスパイルを実行します。
# トランスパイル実行
npm run build
# 結果 成功すると以下のような表示になります。
> app@1.0.0 build /app
> webpack --config webpack.config.js
asset server.js 6.08 KiB [emitted] (name: main)
runtime modules 937 bytes 4 modules
built modules 319 bytes [built]
./src/server.ts 277 bytes [built] [code generated]
external "express" 42 bytes [built] [code generated]
webpack 5.39.0 compiled successfully in 17709 ms
トランスパイル実行後、ホストPCのプロジェクトフォルダに、./src/dist/server.jsが生成されています。
コンテナ内でNode.jsサーバの実行をテストします。
# 実行
npm start
# 結果
> app@1.0.0 start /app
> node ./dist/server.js
app running on port 3000.
ブラウザでhttp://localhost:3000にアクセスします。
当方、この段階でブラウザで接続できると思っていたのですが、設定したHost側のport:3000がコンテナ側のport:3000と結びつかないらしく、ブラウザでlocalhost:3000に接続しても、(この段階では)アクセスできませんでした。
Node.js用コンテナに必要な初期ファイルがそろったので、いったんコンテナを抜けます。
当コンテナは削除されます。
exit
-
コンテナ一覧の確認(スキップしてもよい手順です)
# コンテナを一覧するコマンドを実行する。-aは、停止中のコンテナも一覧するオプション。 docker ps -a # 結果 先ほどはリストアップされていたコンテナが削除されています。 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
本番(開発の本番用という意味の本番です)コンテナを作成します。
ホストPCのプロジェクトフォルダのパスで以下のコマンドを実行します。
# コンテナの作成と実行を行います。
docker compose up
# ※-dオプションを使用すると、バックグラウンドで実行します。しかし当方、実行状況が確認しやすいため、-dオプション使用せずに実行するほうが好みです。
docker compose up -d
SQLServerのファイル大きいため、ダウンロードに数分ようすると思います。
また、以降SQLServerのコンテナを開始するときも、当方の環境では2分弱要します。
-
コンテナ一覧の確認(スキップしてもよい手順です)
# 稼働中のコンテナを一覧 docker ps # 結果 webserverというコンテナ名のNode.jsコンテナと、sql2017というコンテナ名のデータベースコンテが作成されています。 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e5efb3c4f84f 78e2d1575743 "/opt/mssql/bin/nonr…" 20 minutes ago Up 20 minutes 0.0.0.0:1433->1433/tcp, :::1433->1433/tcp sql2017 404a4e158144 81fe3ebc1b85 "docker-entrypoint.s…" 20 minutes ago Up 20 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp webserver
SQLServerにデータベースとテーブルを作成します。
SQLServerコンテナに入ります。
docker exec -it sql2017 /bin/bash
# 表示が以下のようになります。"root@e5efb3c4f84f"の部分は各々異なる表示になると思います。
root@e5efb3c4f84f:/#
SQLSserverのCLIツールsqlcmdを起動し、データベースの作成、テーブルの作成、レコードの挿入を行います。
- 作成するデータベース、テーブル
データベース:testdb
テーブル: user
フィールド名 | 型 |
---|---|
id | smallint |
name | nvarchar(20) |
以下はSQLServerコンテナ内でのコマンドです。
# CLIツールを起動します。
/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "<YourStrongP@ssw0rd>"
# 表示が以下のようになります。
1>
# データベースを作成します。
CREATE DATABASE TestDB
GO
# テーブルを作成します。
CREATE TABLE TestDB.dbo.users (id smallint, name nvarchar(20))
GO
# レコードを挿入します。
INSERT INTO TestDB.dbo.users (id, name) VALUES (1, '山田 太郎'), (2, '山田 花子')
GO
# 挿入したレコードを確認します。(スキップしてもよい手順です)
SELECT * FROM TestDB.dbo.users
GO
# 結果 以下のように表示されます。
id name
------ --------------------
1 山田 太郎
2 山田 花子
(2 rows affected)
# CLIツールを終了します。
exit
Node.js側に、SQLServerに接続し、データを取得し、表示させるコードを作成します。
import * as Express from 'express';
import * as Tedious from 'tedious';
const router = Express.Router();
router.get('/', async (req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
const getConnection = (): Promise<Tedious.Connection> => {
return new Promise<Tedious.Connection>((resolve, reject) => {
const config: Tedious.ConnectionConfig = {
server: 'sql2017', //コンテナ名で接続します。
authentication: {
type: 'default',
options: {
userName: 'sa',
password: '<YourStrongP@ssw0rd>'
}
},
options: {
port: 1433,
trustServerCertificate: true
}
};
const conn = new Tedious.Connection(config);
conn.connect();
conn.on('connect', (err) => {
if (err) {
return reject(err);
}
return resolve(conn);
});
});
}
const executeStatement = (conn: Tedious.Connection): Promise<{key: string, value:any}[]> => {
return new Promise((resolve, reject) => {
const request = new Tedious.Request('SELECT * FROM testdb.dbo.users', (err: Error) => {
if(err){
return reject(err);
}
return resolve(rows);
});
const rows: {key: string, value: any}[] = [];
request.on('row', (columns: Tedious.ColumnValue[]) => {
const row:any = {};
columns.forEach(column => {
row[column.metadata.colName] = column.value;
});
rows.push(row);
});
conn.execSql(request);
});
}
try {
const conn = await getConnection();
const result = await executeStatement(conn);
res.send(JSON.stringify(result));
} catch(err) {
res.send(err);
}
});
export default router;
サーバサイドのメインプログラムを修正します。
import * as Express from 'express';
import dbConnect from './routes/db-connect'; //←追記
const app = Express();
app.use('/db-connect', dbConnect); //←追記
app.get('/', (req: Express.Request, res: Express.Response, next: Express.NextFunction) => {
res.send('アクセス成功');
});
//…以下略
Node.jsコンテナでトランスパイルします。
npm run build
ブラウザでhttp://localhost:3000/db-connectにアクセスし、以下のような表示になれば成功です。
[{"id":1,"name":"山田 太郎"},{"id":2,"name":"山田 花子"}]
Node.jsコンテナからSQLServerコンテナに接続し、データを取得し、ブラウザに返しています。
当方、ここまで来たとき、少々感動しました。
後片付け
コンテナの停止
# docker compose up (-dオプションなし)でコンテナを開始していた場合、そのコマンドを実行したターミナルでctrl+cを押すとコンテナが停止します。
# dockuer compose up -d でコマンドを開始した場合、以下のコマンドでコンテナを停止させます。
docker compose stop
コンテナの削除
コマンド "docker rm (コンテナ名もしくはコンテナID)" を実行し、コンテナを削除します。
# Node.jsコンテナを削除します。"webserver"はコンテナ名です。
docker rm webserver
# SQLServerコンテナを削除します。"sql2017"はコンテナ名です。
docker rm sql2017
イメージの削除
コマンド docker rmi (イメージID) を実行し、イメージを削除します。
# イメージIDを確認するため、イメージを一覧します。
docker images
# 削除するイメージIDを確認します。
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-sqlserver_apply latest 81fe3ebc1b85 5 weeks ago 943MB
# イメージを削除します。
docker rmi 81fe3ebc1b85
以上です。
改訂履歴
- 2021/06/23 00:23 Dockerfileの手順が抜けていたので追加。