目次
- 1. Getting started
- 2. サンプルアプリケーション
- 3. アプリケーションの更新
- 4. アプリケーションの共有
- 5. DBの永続化
- 6. バインドマウント
- 7. マルチコンテナアプリケーション
- 8. Docker Composeを使う
- 9. Image-buildingのベストプラクティス
- 前の記事
1. Getting started
チュートリアルを始める
Docker ダッシュボード
コンテナとは
コンテナとは、ホストマシン上の他の全てのプロセスから隔離された、マシン上のサンドボックス化されたプロセスのこと。
この隔離は、カーネルの名前空間とcgroup
という、Linuxに古くからある機能を利用している。
要約すると、コンテナとは、
- イメージの実行可能なインスタンス。DockerAPIやCLIを使って、コンテナの作成、起動、停止、移動、削除を行うことができる。
- ローカルマシン、仮想マシンまたはクラウドにデプロイして実行することができる。
- 移植性がある(どのOSでも実行できる)
- コンテナは互いに分離されており、独自のソフトウェア、バイナリ、および設定を実行する。
コンテナイメージとは
- コンテナを実行するとき、コンテナは分離されたファイルシステムを使用する。このカスタムファイルシステムは、コンテナイメージによって提供される。
- イメージにはコンテナのファイルシステムが含まれるため、アプリケーションの実行に必要な全てのもの(全ての依存関係、設定、スクリプト、バイナリなど)が含まれていなければならない。
- イメージには、環境変数、実行するデフォルトコマンド、その他のメタデータなど、コンテナのその他の設定も含まれる。
2. サンプルアプリケーション
Node.jsで動作するシンプルなTodoリストマネージャを扱う
アプリの取得
アプリのコンテナイメージのビルド
- アプリケーションを構築するためには、
Dockerfile
を使用する必要がある。 Dockerfile
とは、コンテナイメージを作成するためのテキストベースの指示スクリプトのこと。Dockerfile
のファイル拡張子が.txt
でないことに注意。
# syntax=docker/dockerfile:1 # node:12-alpineイメージから開始するようにビルダーに指示 FROM node:12-alpha RUN apk add --no-cache python2 g++ make # アプリケーションをコピー WORKDIR /app COPY . . # yarnを使ってアプリケーションの依存関係のインストール RUN yarn install --production # このイメージからコンテナを起動する際に実行するデフォルトのコマンドを指定 CMD ["node", "src/index.js"] EXPOSE 3000
Dockerfile
を使用して新しいコンテナイメージを構築している。# Dockerfileがあるディレクトリで実行すると、コンテナイメージを構築 docker build -t getting-started .
-t
フラグでイメージにタグをつける。- コマンドの最後にある
.
は、Dockerに対して、カレントディレクトリにあるDockerfile
を探すように指示している。
アプリケーションコンテナの起動
docker run
コマンドを使用する
3. アプリケーションの更新
- アプリケーションを更新した場合、イメージを再ビルドし、起動する必要がある(ビルド、起動方法は前項に同じ)。
- しかし、前回同じポートで起動していた場合、ポートには一つだけしか
listen
できないためエラーが吐かれる。
古いコンテナを入れ替える
- コンテナを削除するには、まずコンテナを停止する必要がある。停止した後に、削除することができる。
- 古いコンテナを削除するには2つの方法がある。
4. アプリケーションの共有
- Dockerイメージを共有するには、Dockerレジストリを使用する必要がある。
リポジトリを作成する
- Docker Hubでサインインし、リポジトリを作成する
イメージのプッシュ
Docker Hubにログインする
docker login -u YOUR-USER-NAME
docker tagコマンドを使用して、イメージに新しい名前をつける(新しくイメージが作成される)。
YOUR-USER-NAME
は、Docker IDに必ず書き換える。docker tag getting-started YOUR-USER-NAME/getting-started
push
する。docker push YOUR-USER-NAME/getting-started
新しいインスタンスでイメージを起動する
5. DBの永続化
コンテナのファイルシステム
- コンテナが実行されると、イメージのさまざまなレイヤーをファイルシステムに使用する。
- また、各コンテナには、ファイルを作成/更新/削除するための独自の「スクラッチベース」が用意されている。たとえ同じイメージを使用していても、変更は他のコンテナには表示されない。
コンテナボリューム
- 各コンテナは、起動のたびにイメージ定義から開始する。
- コンテナでは、ファイルの作成/更新/削除が可能だが、コンテナが削除されるとこれらの変更は失われ、全ての変更はそのコンテナに隔離される。
ボリュームを使用すると、この全てを変更することができる。
ボリュームは、コンテナの特定のファイルシステム・パスをホストマシンに戻して接続する機能を提供する。
- コンテナ内のディレクトリがマウントされている場合、そのディレクトリの変更はホストマシンにも反映される。コンテナの再起動にまたがって同じディレクトリをマウントすれば、同じファイルが表示されることになる。
- ボリュームには、主に2つのタイプがある。
- 名前付きボリューム
- Dockerはディスク上の物理的な場所を維持するので、ボリューム名だけを覚えていれば良い。
- 名前付きボリューム
ボリュームを作成し、データが格納されているディレクトリにアタッチ(マウント)することで、データを永続化することができる。
docker run -dp 3000:3000 -v {ボリューム名}:/etc/todos {イメージ名}
- ボリュームマウントを指定するために
-v
フラグを追加する。 - ここでは、名前付きボリュームを使用して、
/etc/todos
にマウントし、そのパスで作成された全てのファイルを取り込む。
- ボリュームマウントを指定するために
Dive into the ボリューム
名前付きボリュームのデータを確認する
docker volume inspect {ボリューム名} // [ { "CreatedAt": "2022-06-05T07:25:52Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", "Name": "todo-db", "Options": {}, "Scope": "local" } ]
Mountpoint
が、データが保存されているディスク上の実際の場所。- ホストからこのディレクトリにアクセスするためにルートアクセスが必要。
Docker Desktopで実行している間、Dockerコマンドは実際にマシン上の小さなVM内部で実行されている。 もしマウントポイントディレクトリの実際の中身を見たい場合は、まずVMの中に入る必要がある。
6. バインドマウント
- 名前付きボリュームは、データがどこに保存されているかを気にする必要がないため、単にデータを保存したい場合に最適。
- バインドマウントを使えば、ホスト上の正確なマウントポイントを制御できる。この機能はデータの永続化に使えるが、多くの場合、コンテナに追加データを提供するために使用される。
- アプリケーションで作業する場合、バインドマウントを使ってソースコードをコンテナにマウントし、コンテナにコードの変更を表示させ、応答し、すぐに変更を表示させることができるようにする。
ボリュームタイプの比較
- バインドマウントと名前付きボリュームは、Dockerエンジンに付属している2つの主要なタイプのボリューム。
- しかし、他のユースケース(SFTP, Ceph, NetApp, S3)をサポートするために、追加のボリュームドライバが用意されている。
ボリュームタイプ | 名前付きボリューム | バインドマウント |
---|---|---|
Host Location | Docker chooses | You control |
マウント例(-v) | my-volume:/usr/lcal/data | /path/to/data:/usr/local/data |
コンテナの中身を新しいボリュームに入れる | Yes | No |
ボリュームドライバのサポート | Yes | No |
devモードコンテナを起動する
- 開発ワークフローをサポートするためのコンテナを実行するために、次のことを行う。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ node:12-alpine \ sh -c "yarn install && yarn run dev"
-w /app
:作業ディレクトリまたはコマンドが実行される現在のディレクトリを設定する。-v “$(pwd):/app”
:コンテナ内のホストから/app
ディレクトリにカレントディレクトリをバインドマウントするnode:12-alpine
:使用するイメージ。Dockerfile
に記載されているアプリのベースイメージ。sh -c “yarn install && yarn run dev”
:sh
を使ってシェルを起動(alpine
にはbash
がない)し、yarn ~
を実行している。バインドマウントの使用は、ローカル開発のセットアップでは非常に一般的。その利点は、開発マシンに全てのビルドツールと環境をインストールする必要がないこと。
docker run
コマンドを1つ実行することで、開発環境が引き出され、すぐに使用できるようになる。
7. マルチコンテナアプリケーション
- 一般的に、各コンテナは1つのことを行い、以下の理由により、それをうまく実行する必要がある。
- データベースとは異なるAPIやフロントエンドのスケーリングが必要になる可能性がある。
- コンテナを分けることで、バージョンアップやアップデートを個別に行うことができる。
- ローカルでは、データベース用のコンテナを使うかもしれないが、本番ではデータベース用のマネージドサービスを使いたいと思うかもしれない。その場合、データベースエンジンをアプリと一緒に配布したくない。
- 複数のプロセスを実行するにはプロセスマネージャが必要になり(コンテナは1つのプロセスしか起動しない)、コンテナのスタートアップ/シャットダウンが複雑になる。
コンテナネットワーキング
- デフォルトでは、コンテナは独立して実行され、同じマシン上の他のプロセスやコンテナについては何も知らない。
- あるコンテナが別のコンテナと接続するには、ネットワークを使う。
MySQLを始める
- ネットワーク上にコンテナを置くには2つの方法がある。
- 起動時に割り当てる
- 既存のコンテナに接続する
- とりあえず、先にネットワークを作成し、起動時にMySQLコンテナをアタッチすることにする。
- ネットワークを作る
docker network create todo-app
MySQLコンテナを起動し、ネットワークに接続する。 (データベースの初期化に使用するいくつかの環境変数を定義している)
docker run -d \ --network todo-app --network-alias mysql \ --platform "linux/amd64" \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7
データベースが稼働しているか確認するため、データベースに接続し、接続を確認する
docker exec -it <mysql-container-id> mysql -u root -p
MySQLに接続する
MySQLアプリケーションを起動する
8. Docker Composeを使う
- Docker Composeは、マルチコンテナアプリケーションの定義と共有を支援するために開発されたツール。
- Composeを使うと、サービスを定義するYAMLファイルを作成し、コマンド1つで全てをスピンアップしたり、ティアダウンしたりすることができるようになる。
- Composeを使う大きなメリットは、アプリケーションスタックをファイルに定義し、それをプロジェクトリポジトリのルートに置き(バージョン管理されている)、他の人が簡単にプロジェクトに貢献できる。
Composeファイルを作る
プロジェクトのルートに
docker-compose.yml
を作成する。# スキーマのバージョンを定義 version: '3.7' # アプリケーションの一部として実行したいサービスのリストを定義する services: # サービスエントリとコンテナ用の画像を定義する # サービスの名前は自由に決めることができ、この名前は自動的にネットワークエイリアスになるので、 # MySQLサービスを定義するときに便利 app: image: node:12-alpine # コマンドはimage定義の近くに記載されるが、順序については特に指定はない command: sh -c "yarn install && yarn run dev" # サービス用のポートを定義。より詳細な長い構文もある ports: - 3000:3000 # 作業ディレクトリの移行 working_dir: /app # ボリュームマッピングの移行。より詳細な長い構文もある # Docker Composeのボリューム定義の利点は、カレントディレクトリからの相対パスを使用できる。 volumes: - ./:/app # 環境キーを使って環境変数の定義 environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todos
MySQLサービスを定義する
version: '3.7' services: app: # 上記で定義認め割愛 # ネットワークエイリアスを自動的に取得できるようにmysqlという名前にする。 mysql: image: mysql:5.7 # ボリューム名だけを指定すると、デフォルトのオプションが使用される。 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos # Composeで実行した場合は、ボリュームは自動的に作られない。 # トップレベルのvolumesセクションでボリュームを定義し、service configでマウントポイントを指定する必要がある。 volumes: todo-mysql-data:
アプリケーションスタックを起動する
docker-compose up
コマンドでアプリケーションスタックを立ち上げる- デフォルトでは、Docker Composeは自動的にアプリケーションスタック専用のネットワークを作成する。そのため、コンポーズファイルでネットワークを定義しなくて良い。
docker-compose logs -f
コマンドでログを見れる。特定のサービスのログを表示したい場合は、log
コマンドの末尾にサービス名を追加する。
停止する
docker-compose down
を実行すると、コンテナが停止し、ネットワークが削除される。
9. Image-buildingのベストプラクティス
Security scanning
- イメージを構築したら、docker scanコマンドを使用してセキュリティ脆弱性をスキャンするのが良い。
- 出力には、脆弱性の種類、詳細を知るためのURL、そして、関連するライブラリのどのバージョンが脆弱性を修正するのかが列挙されている。他にもいくつかのオプションがある。
- コマンドラインで新しくビルドしたイメージをスキャンするだけでなく、新しくプッシュしたイメージを全て自動的にスキャンするようにDocker Hubを設定することもでき、Docker HubとDocker Desktopの両方で結果を見ることができる。
Image layering
docker image history
コマンドを使えば、イメージ内の各レイヤーを作成するために使用されたコマンドを見ることができる。- 各行はイメージ内のレイヤーを表す。これを使えば、各レイヤーの大きさもすぐにわかるので、大きなイメージの診断にも役立つ。
Layer caching
- コンテナイメージのビルド時間を短縮するために
Dockerfile
の各コマンドがイメージの新しいレイヤーとなっている。- イメージに変更を加えた時、yarnの依存関係を再インストールする必要があった。ビルドするたびに同じ依存関係をインストールするのは時間がかかる。
- これを解決するには、
Dockerfile
を再構築して、依存関係のキャッシュをサポートする必要がある。Node
ベースのアプリケーションでは、まず、package.json
だけをコピーして、依存関係をインストールし、それから他の全てをコピーすることで、package.json
に変更があった場合のみ、yarn
の依存関係を再作成する。package.json
をコピーし、依存関係をインストールし、それから他の全てをコピーするようにする。# syntax=docker/dockerfile:1 FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]
ルートディレクトリに
.dockerignore
ファイルを作成する。.dockerignoreファイルは、イメージに関連するファイルのみを選択的にコピーする。 この場合、3つ目のCOPY
ステップでnode_modules
フォルダを省略する必要がある。そうしないと、RUN
ステップでコマンドによって作成されたファイルを上書きしてしまう可能性がある。Node.jsアプリケーションにこの方法を推奨する理由・その他ベストプラクティス```docker node_modules ```
新しいイメージをビルドすると、全てのレイヤーが再構築され、全てキャッシュを使用するようになり、ビルドが非常に高速になる。
Multi-stage builds
- マルチステージビルドは、複数のステージを使用してイメージを作成するのに役立つ、非常に強力なツール。
- ビルド時の依存関係を実行時の依存関係から分離する
- アプリの実行に必要なものだけを配布することで、イメージ全体のサイズを小さくすることができる。
Maven/Tomcat
Java
ベースのアプリケーションを構築する場合、ソースコードをJava
バイトコードにコンパイルするためにJDK
が必要。しかし、その
JDK
は本番では必要ない。また、アプリのビルドをサポートするために、Maven
やGradle
などのツールを使用している場合もある。それらも、最終的なイメージでは必要ない。# syntax=docker/dockerfile:1 FROM maven AS build WORKDIR /app COPY . . RUN mvn package FROM tomcat COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
- この例では、1つのステージ(
build
)で、Maven
を使用して実際のJava
ビルドを実行する。 - 第2ステージ(
FROM tomcat
)では、ビルドステージからファイルをコピーしている。 - 具体的なイメージは、最後のステージが作成されるだけ(
—-target
フラグを使用して、オーバーライドできる)。
- この例では、1つのステージ(
React
React
アプリケーションを構築する場合、JSコード、SASSスタイルシートなどを具体的なHTML
,JS
,CSS
にコンパイルするために、Node
環境が必要。 サーバーサイドのレンダリングを行わないのであれば、本番ビルドにNode環境は必要ない (静的リソースを静的なnginxコンテナで配布する)
# syntax=docker/dockerfile:1 FROM node:12 AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install COPY public ./public COPY src ./src RUN yarn run build FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html
- ここでは、
node:12
イメージを使ってビルドを実行し(レイヤーキャッシングを最大化)、その出力をnginx
コンテナにコピーしている。