この記事では、Dockerの概要とDocker Composeについて大まかにまとめました。
初めての投稿なので体裁が整っていない部分もありますが、温かい目で見ていただけると幸いです。。。
Dockerとは?
Dockerとは「コンテナ」という技術を使用して、アプリケーション単位でパッケージ化する技術の事です。
例えば、ローカルでアプリAとアプリBを開発したいとします。
その際、「アプリA = Rails6」「アプリB = Rails7」といった環境で動作したい場合は、純粋なローカル環境ではRailsのバージョンを入れ替える必要があると思います。
しかし、DockerではRailsのバージョンをコンテナ単位で区切る為、Railsのバージョンが違っても問題なく同じ端末で開発を進めることが出来ます。
参考:https://qiita.com/Sicut_study/items/4f301d000ecee98e78c9
Dockerを使用するメリット
アプリのスケールが行いやすい
開発中のアプリケーションを「新しいメンバーの端末」や「新たなPC」で動作したい場合、Dockerを使用すると迅速に開発環境を用意することが出来ます。
どういった方法で行うかというと、既存のGitHubのリポジトリからローカル環境にcloneし、その中に含まれるDockerfileとdocker-compose.ymlを使用することで迅速に環境を構築できます。
各アプリが独立している
各アプリはコンテナ単位で独立しているため、相互に悪影響を与えあうことがありません。
環境由来のバグを減らすことが出来るのも1つのメリットです。
参考:https://qiita.com/Sicut_study/items/4f301d000ecee98e78c9
Docker Composeとは?
Docker Composeとは、アプリの構成要素となるサービスを定義し、一括で操作を行うためのツールです。
アプリケーションを動作するために必要となる、DB, Webなどのイメージをまとめて作成し、コンテナとして動作することができます。
$ docker run -p 3000:3000 --name my-node-app my-node-app
$ docker run --name postgres -e POSTGRES_PASSWORD=mysecretpassword -d postgres
例えば、上記のようにmy-node-appコンテナとpostgresコンテナを実行したとします。
この場合には、各コンテナを長いコマンドで作成&実行するので、非常に手間がかかります。
しかし、下記のようにdocker-compose.ymlでwebやdbといったサービス単位でイメージを定義することで、まとめて管理や操作を行うことが出来ます。
version: '3'
services:
web:
build: .
container_name: my-node-app
working_dir: /usr/src/app
volumes:
- .:/usr/src/app
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:13
container_name: postgres-db
environment:
POSTGRES_PASSWORD: mysecretpassword
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
※サービスとイメージの違い:
サービスとは、イメージを「どういった仕様で作成するか?」といったものをまとめたもの。
ゲームソフトで例えるのであれば、ゲーム会社は設計図を基にマリオカートを大量生産できます。
これは、設計図を基に作成された不変的なものなので、どの任天堂の子会社が作成しても同じものが大量にできます。
この設計図こそがサービスで、それによって作成された実態こそがイメージです。
もっといえば、マリオカートを小学生が起動して遊ぶ。
こういった、イメージを稼働してデータを保存したり、変化を加えられるようになったものをコンテナと呼びます。
docker-compose.ymlのサービスからイメージを作成し、コンテナを起動する流れ。
- サービスでコンテナの仕様を作成(※Dockerfileも作成)
- docker compose buildでDockerfileと共にイメージを作成
- docker compose upでコンテナを起動
- アプリ開発を開始
参考:
https://qiita.com/Sicut_study/items/4f301d000ecee98e78c9
https://docs.docker.jp/get-started/08_using_compose.html#define-the-app-service
docker-compose.ymlのキーについて
services:稼働するサービスをまとめたものがservicesです。
servicesをdocker-compose.ymlに記述し、ネストする形でwebやdbなど、実際のサービスを記述していきます。
web db:サービスは「実際に稼働するコンテナ」です。
各サービスで動作するイメージは1つですが、この場所に動作方法等も記述します。
ポートは何番を使用するのか、データの永続化をどこで行うのか等です。
image:各コンテナを動かすための設計図のようなもの。
※個人的には、設計図というより「ゲームソフト」の感覚が近い気もしました。
mysqlやphpの特定のバージョンという「ゲームソフト」をインストールすることで、コンテナ上で遊べるというようなイメージです。
(実際に遊ぶには、永続化や環境変数などの細かい設定も必要です。)
イメージだけを使用してコンテナは作成可能か?
可能です。
※Docker Composeを使用せずにアプリ開発を始める場合は、必要なイメージを個別に取得し、それぞれのコンテナを手動で起動する必要があります。
手順としては下記となります。
1. 「Docker Hub」というアプリストアからゲーム単体(イメージ)を取得します。
$ docker pull <イメージ名>
2. 取得したイメージを元に、ポートをマッピングしてコンテナを作成&実行します。
$ docker run -d -p 8080:80 <イメージ名>
3. 作成したコンテナを停止&削除します。
$ docker stop <コンテナIDまたはコンテナ名>
4. 不要になったイメージを削除します。
docker rmi <イメージ名>
ports:「ホスト:コンテナ」の形式でポートを指定する部分。
81:80といった形で記載し、ホストマシンでは81番からアクセスを行います。
※ポートが60以下だと.ymlが60進数で誤認してエラーを吐くようです。
なぜ、わざわざ「ホスト:コンテナ」の形式で記述する必要があるのか?
前提として、dockerコンテナとホストマシンは隔離されているので、ホスト側からは直接アクセスができません。
例えば「ホスト:コンテナ」といった形式でマッピングを行わなかった場合、dockerのmyapp_web_1コンテナが3000番でポートを公開していても、ホスト側からはlocalhost:3000で試みてもアクセスができません。
なので、ホストとコンテナは「ホストがdockerに接続するポート:dockerコンテナのポート」といった形式でマッピングする必要があります。
逆に言えば、「3001:3000」といった形式でポートを設定した場合には、localhost:3001で3000番のコンテナに接続することができます。
volumes:ホストのファイルシステムの一部としてデータを保存するストレージであり、Dockerによって管理されている。
Dockerボリュームを利用すると、永続データを保存・管理でき、コンテナ削除や更新後にもデータを永続化できます。
余談ですが、Dockerのストレージは3種類存在しており、ボリューム、バインドマウント、tmpfsがあるようです。
参考:
https://docs.docker.jp/v17.06/get-started/part3.html
https://qiita.com/gounx2/items/23b0dc8b8b95cc629f32
https://docs.docker.jp/compose/compose-file/compose-file-v3.html
https://qiita.com/zembutsu/items/9e9d80e05e36e882caaa
https://qiita.com/nimo1/items/8f4cff8bba65274b6475
https://qiita.com/aki_55p/items/63c47214cab7bcb027e0
https://matsuand.github.io/docs.docker.jp.onthefly/storage/
docker-composeコマンドについて
docker-compose.ymlで定義したサービスを元にイメージを作成します。
$ docker-compose build
作成したイメージを元に、コンテナの作成と起動を行います。
$ docker-compose up
upした起動中のコンテナを停止します。
また、コンテナと共にネットワークの削除も行ってくれるようです。
$ docker-compose down
docker-compose down --rmi allだと、コンテナとネットワークに加えてイメージも削除してくれます。
また、それに加える形で--volumesオプションを付けるとボリュームも一緒に削除されます。
$ docker-compose down --rmi all --volumes
現在稼働中のコンテナの状態を確認できます。
どのコンテナが稼働していないのか、ポートの使用状況などを見ることが出来ます。
$ docker ps
build時に作成したイメージを一覧で表示することができます。
$ docker images
コンテナ内に入って作業を行いたい時に使用します。
新たなライブラリをインストールする際や、migrateを実行する際などです。
※下記は例です
$ docker compose -p myapp exec web bash
docker compose exec web bash
execと同様、コンテナ内で作業を行えるコマンドです。
しかし、docker attachでは基本的に標準出力を確認するためのものなので、execのようにコンテナ内で自由に操作することはできません。
使用例としては、ログや過去のリクエストの内容を確認する等です。
※docker attachでコンテナ内に入った場合、ctrl + cで抜けるとコンテナが停止します。
コンテナを起動したまま抜けたい場合は、ctrl + p, ctrl + q の両方を実行してください。
標準入出力とは?
前提として、コンピュータには入力・出力という概念があります。 例としては下記です。入力 = touch example.txt
出力 = 成果物としてのexampleファイル
コンピュータは、「何かを受け取って → 処理して → 結果を返す」 という仕組みで動いており、今回のケースだと「example.txtを作成してください」という明示的な指示に対して、「example.txt」という明示的な成果物が作成されました。
つまり、「明示的な指示 = 入力」であり、「明示的な成果物 = 出力」という事となります。
暗黙の入力・出力
これとは別に、暗黙の入力・出力というものもあります。
$ cp -v file1 file2
'file1' -> 'file2'
上記では、「file1をfile2にリネームしてください。」という入力に対して、リネーム後の「file2」が出力されます。
しかし、-vオプションにより、「file1をfile2にリネームしました。」といった旨のメッセージ(出力)が表示されています。
このメッセージは、わざわざ「○○というターミナルにリネームのメッセージを表示して」という入力を行ったわけではありません。
※例えば、ターミナルA, ターミナルB, ターミナルC...どちらで行った場合でも、各ターミナルで「'file1' -> 'file2'」が表示されます。
「暗黙の出力」とは、まさにこういった「明示的ではない成果物」の事であり「実行過程・ログ・メッセージ」等がこちらに該当します。
これが標準出力です。
また、標準出力には「標準出力」「標準エラー」の2系統が存在し、警告や確認メッセージ、エラーなどが「標準エラー」に該当します。
$ cp -v -i file1 file2
cp: overwrite 'file2'? y
'file1' -> 'file2'
例をお見せします。
先ほどのコマンドに-aを加え、上記では「上書き確認」を実施するよう指示しました。
この場合の「上書き確認」が「標準エラー」となります。
また、ここでは「y/n」の入力を求められており、ユーザーは「y」もしくは「n」といった形で、標準エラー時に何かしらの入力を行います。
これを「標準入力」といいます。
※docker attachは、コンテナで動作中のプロセスに直接接続し、ログなどの標準出力を確認しつつ、入力が可能な場合には標準入力を介して操作することもできるため、デバッグなどに役立ちます。
$ docker attach (コンテナのID)
作成したコンテナを削除できます。
$ docker rm <コンテナ名orコンテナID>
取得したイメージを削除できます。
$ docker rmi <イメージID>
参考:
https://qiita.com/nimo1/items/8f4cff8bba65274b6475
https://qiita.com/souichirou/items/6e701f6469822a641bdd
https://zenn.dev/hyoil/articles/a9e619aec9efce
https://docs.docker.jp/engine/reference/commandline/attach.html