じぶん対策

日々学んだことをアウトプットして備忘録にしています。

Dockerを雰囲気ではなく理解して使う

はじめに

以前Dockerについてごく簡単にまとめた記事を書きましたが、Webエンジニアになって一年が経とうとしているので改めてDockerについてまとめようと思います。
公式ドキュメントを自分なりに解釈してまとめたいと思います。
Dockerについての入門からみていき、普段業務で使用しているコマンドの中身を理解していきます。

まとめ

  • Dockerを使えばコードを管理するようにインフラも管理できる。
  • イメージとは、Dockerコンテナを作成する命令が入った読み込み専用のテンプレートのこと。
  • Dockerfileは、イメージの作成の際に使用する。
  • コンテナとは、イメージが実行状態となったインスタンス(実体)のこと。
  • コンテナ内でのファイルの変更を保存するには、ボリュームを使用する。
  • 複数のコンテナ同士の接続にはネットワーク機能を使用する

Dockerとは

公式には以下のように説明があります。

Docker はアプリケーションを開発(developing)、移動(shipping)、実行(running)するためのオープンなプラットフォームです。Docker はインフラストラクチャ 1 とアプリケーションを切り離すため、ソフトウェアを短時間で提供できます。Docker があれば、アプリケーションを管理するのと同じ方法で、あなたのインフラも管理できます。Docker 的な手法を最大限活用しますと、テストやコードのデプロイを素早くできますので、コードを書いてから、プロダクション(実行環境)で動かすまでにかかる時間を著しく軽減できます。

Dockerを使用することで、コンテナというものを使ってインフラをGitのようなバージョン管理ツールを用いて管理することができます。
コンテナは隔離された環境です。
ホストコンピュータ上に何がインストールされているかに関係なく、コンテナ上にアプリケーションのパッケージ化、実行が可能です。
コンテナは、アプリケーションの配布とテストをする単位です。
Dockerはこのコンテナという技術のライフサイクルを管理するツールとプラットフォームです。

Dockerが解決する問題

CI/CD

開発するアプリケーションやサービスをローカルのコンテナ内で実現することで、開発者は標準化された環境で作業が進められます。
コンテナを使っての開発はCI/CDのワークフローに適しています。
コンテナは開発者のローカル環境だけではなく、本番環境を含めた様々な環境の組み合わせにおいて実行可能です。
いろんな環境で実行できる可搬性のおかげで、

  • 処理負荷を動的に管理できる
  • スケールアップやサービス終了時に簡単に行える

といったメリットがあります。

同じハードウェア上で負荷の高い処理を実行

以下公式からの引用です。

Docker は軽量かつ高速です。ハイパーバイザ・ベースの仮想マシンに取って変わる、実用的で費用対効果の高いものです。したがってコンピュータ性能をフルに活用してビジネス目標を達成できます。Docker は高度に処理集中する環境に適しており、さらには中小規模の、より少ないリソースの中でのシステム構築にも適しています。

Dockerにおける用語

雰囲気でDockerを使わないために、イメージとコンテナという用語についてどういうものなのかを理解する必要がありそうです。

イメージとは

  • Dockerコンテナを作成する命令が入った読み込み専用のテンプレートのこと
  • 通常、他のイメージをベースにカスタマイズして利用する
  • イメージを自分で作る場合はDockerfileというファイルを生成する

コンテナとは

  • イメージが実行状態となったインスタンス(実体)のこと。
  • 複数のネットワークへの接続、ストレージの追加を行う事ができ、現時点の状態にもとづいた新たなイメージを生成する事もできる。
  • ローカルマシン上や仮想マシン上でも実行でき、クラウドにもデプロイができ、可搬性があります。
  • コンテナを削除すると永続的なストレージに保存されていないものは消失します。

Dockerのアーキテクチャ

具体的なDockerの動作イメージについては下記公式を参考.

https://docs.docker.jp/get-started/overview.html#docker-architecture

Dockerfileとは

アプリケーションを構築するには、Dockerfileを使います。 Dockerfileとは、コンテナイメージの作成で使う命令が書かれたスクリプトです。

以下のようなファイルを用意し、docker buildコマンドを使ってコンテナイメージを構築します。

# syntax=docker/dockerfile:1
FROM node:18-alpine
WORKDIR /app
COPY ..
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000

内容は

  • イメージのダウンロード
  • アプリの依存関係をインストール
  • CMD ... このイメージでコンテナを起動するときにデフォルトで実行するコマンドを指定
$ docker build -t getting-started .

-tタグでイメージにタグをつけることができます。コンテナの実行時にこのイメージ名を指定できます。
最後の.で現在のディレクトリのDockerfileを探します。

コンテナの起動

コンテナの起動には下記の様なコマンドを実行します。

$ docker run -dp 3000:3000 getting-started

-dオプションでバックグラウンドで実行されます。
-pオプションでコンテナのポートとホスト側のポートをマッピングします。

コンテナの停止、削除

$ docker ps

コンテナのIDが出力されます。

$ docker stop <コンテナID>

コンテナが停止されます。

$ docker rm  [-f] <コンテナID>

コンテナを削除します。-fオプションでコンテナと削除を同時に行います。

コンテナのデータの永続化

前提として、各コンテナではコンテナのファイルシステムに対する変更は他のコンテナからは見えません。
動作させて確認する場合は下記公式を参照してください。
https://docs.docker.jp/get-started/05_persisting_data.html

コンテナのボリューム

コンテナはファイルの作成、更新、削除ができますが、コンテナを削除すると、それらの変更は失われます。
ボリュームをコンテナ内にマウントすると、ディレクトリに対する変更はホストマシン上からも見ることができます。
コンテナの再起動の際にも同じディレクトリをマウントしていれば再起動後も同じファイルが見えます。

ボリュームには名前付きボリュームバインドマウントがあります。
Dockerがディスク上で物理的な場所を確保するので、ボリュームの名前を覚えておくだけで利用できます。

$ docker volume create <ボリューム名>

その後、コンテナを起動する際に-vフラグを追加することでボリュームをコンテナにマウントできます。
これでこのパスに生成されたすべてのファイルを保存します。

$ docker run -dp 3000:3000 -v <ボリューム名:マウントするパス> イメージタグ名

ボリュームの実体

公式の例では下記のようにdocker volume inspectコマンドでボリュームの詳細を見ることができます。

$ docker volume inspect todo-db
[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

MountPointにディスク上で実際のデータが保管されます。

バインドマウントの使用

シンプルにデータを保存したい場合は名前付きボリュームが優れていますが、ホスト上でどこにマウントされるかを管理したい場合はバインドマウントという方法があります。

バインドマウントはデータ保持に使えますが、使用時はコンテナに対する追加データの指定が度々必要です。
アプリケーションの動作中でも、バインドマウントを使ってソースコードをコンテナ内にマウントするとコードの変更が見えたり反映したりできるようになります。

名前つきボリュームを利用した場合はホストマシン上に新たなディレクトリが生成され、そこがDockerの保存ディレクトリになりますが、バインドマウントはホストマシンのファイルシステムに依存します。
バインドマウントはDockerの初期のころから存在していて、今後は原則名前付きボリュームのほうが便利そうです。
https://matsuand.github.io/docs.docker.jp.onthefly/storage/bind-mounts/

複数コンテナのアプリ

アプリケーションとは別に例えばMySQLを用意したい場合、通常1つ1つのコンテナが1つのことをしっかりと実行すべきです。
公式には下記の理由が記載されています。

  • データベースとは別に、 API とフロントエンドをスケールする良い機会
  • コンテナを分けると、現在のバージョンと更新したバージョンを分離できる
  • 今はローカルにあるデータベースをコンテナが使っているが、プロダクションではデータベースのマネージド サービスを利用したくなるかもしれない
  • 複数プロセスの実行にはプロセスマネージャが必要であり(コンテナは1つのプロセスのみ起動するため)、コンテナの起動や停止が複雑になる

先述したとおりコンテナは、外部とは隔離された状態で実行されるため、基本的には同じマシン上の他のプロセスやコンテナを一切知りません。
他のコンテナと通信するために、ネットワーク機能と呼ばれる機能を使います。

ネットワーク機能の利用には以下の二種類の方法があります。

  • 起動する前にネットワークに割り当てる
  • 既存のコンテナに接続する

ネットワークを作成するコマンドは下記

$ docker network create <ネットワーク名>

公式のチュートリアルにあるコンテナ起動とネットワーク接続用コマンド(Apple Siliconの場合)が下記です。
todo-appという名前のネットワークに接続しています。

$ 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

--networkで接続するネットワークを指定しています。
--network-aliasmysqlという文字列を指定しているので、IPアドレスを調べる際に使用する事ができます。

$ dig mysql

-vはボリュームの指定、-e環境変数を設定できます。

データベースが実行中であることを確認するには、下記のコマンドを使用します。

$ docker exec -it <mysql-container-id> mysql -u root -p

これでMySQLのコンテナを作成することができました。

あらためてまとめ

  • Dockerを使えばコードを管理するようにインフラも管理できる。
  • イメージとは、Dockerコンテナを作成する命令が入った読み込み専用のテンプレートのこと。
  • Dockerfileは、イメージの作成の際に使用する。
  • コンテナとは、イメージが実行状態となったインスタンス(実体)のこと。
  • コンテナ内でのファイルの変更を保存するには、ボリュームを使用する。
  • 複数のコンテナ同士の接続にはネットワーク機能を使用する

また、アプリケーションの起動に必要なコンテナの作成をより簡単な方法で実現するために、Docker Composeという仕組みがあります。 こちらについてはまたの機会に調べてみることにします!

所感

業務で携わるたいていのプロジェクトではすでに環境構築がされている場合がほとんどかと思います。
ただ、新規プロダクトの立ち上げであったり、より効果的なインフラ構成や開発者体験を求めようとすると避けては通れないものだと思います。
個人的には個人開発で環境構築する際に自分がDockerについて全然わかっていないことを改めて認識しました。
CI/CDとの相性もよく、簡単にデプロイできる環境を構築しておくことは取れる工数の少なくなりがちな個人開発においてもとても有用なものだと思いました。