👌

今更ながらdockerの基礎を自分なりにまとめる

2023/09/16に公開

日々の開発にdockerは使ってはいますが、せいぜい下記のようなコマンドを打つ程度です。

$ docker-compose up -d
$ docker-compose ps -a
$ docker-compose down

dockerについての理解は浅く、「いい加減基本的なことだけは理解しなければ...」と思っていました。

私なりにdockerについて整理したことをまとめます。

dockerとdocker-composeの使い分け

  • 複数のコンテナを管理する場合にはdocker-composeが便利です。
    • アプリケーション全体が一目で把握できるのが利点
    • 1つのコマンドで全てのコンテナを起動・停止できるのも利点
  • イメージをカスタマイズして使う場合は、DockerFileを使います
    • docker hubにあるイメージをそのまま使う場合はdocker-compose.ymlに直接imageを指定すれば良いため、DockerFileはなくても問題ありません。

DockerFileの書き方

# ベースとなるイメージを指定する
FROM node:14.15.4-alpine3.10

# 以降のコマンドは、ここで指定したディレクトリで実行される
WORKDIR /usr/src/app

# ホストのファイルやディレクトリをコンテナにコピーする
# 詳細に言うと、ホストのファイルやディレクトリがイメージの新しいレイヤーにコピーされる。そのイメージからコンテナが起動すると、コンテナにも指定したホストファイルやディレクトリが存在する
COPY ./package.json ./
COPY ./yarn.lock ./

# シェルコマンドの実行
RUN yarn install

# hostのserverディレクトリを"/usr/src/app"にコピーする
## パッケージインストールをした後にコピーすること
COPY . /usr/src/app

# コンテナのポートの宣言
# これを指定してもポートが公開されるわけではなく、ポートを公開するためには`docker run -p`や`docker-compose.yml`での指定が必要
# あくまでもドキュメントとしての役割
EXPOSE 8080

# コンテナが起動したときに実行されるコマンド
# docker run -it image /bin/bashなどで実行すると、CMDのコマンドは上書きされる
CMD [ "yarn", "start" ]

ENTRYPOINT

ポートマッピング

ポートマッピングとは、dockerホスト(DockerEngineが動作しているマシン)のポートとコンテナのポートをマッピングすることです。これにより、外部からDockerコンテナにアクセスできるようになります。

docker-compose.ymlで記述すると以下のように指定します。

version: '3'

services:
  nginx:
    image: nginx:latest
    # ホスト側で公開するポート番号 : コンテナのポート番号
    ports:
      - '8080:80'

8080はホストマシンのポートで、80がコンテナ内のアプリケーション(この例ではnginx)がリッスンしているポートになります。

Dockerコマンドで実行する場合は、docker run -p 8080:80 ...など-pオプションで指定可能です。

http://ホストのIPアドレス:8080へのアクセスがあった時の動作を見ていきます。

  • http://ホストのIPアドレス:8080へアクセスします(ブラウザやcurlから)
  • ホストマシンは8080番ポートでトラフィックを受け取り、そのトラフィックをコンテナの80番ポートに転送します
  • コンテナ内では80番ポートでnginxがリッスンしているため、レスポンスを返します。今回の例であれば、nginxがデフォルトページを返却します。

通常はインターネットなどの外部からコンテナ内のアプリケーションにアクセスできませんが、ポートマッピングをすることで、インターネットからコンテナへのアクセスが可能となります。

ボリュームマウントとバインドマウント

大前提

コンテナ内のデータは、コンテナが削除されると失われます。

そのため、何かしらの方法でデータを永続化する必要があります。その手段が以下のvolumeです。

dockerにおけるvolumeとは

永続的なデータ保持や複数コンテナでのデータ共有のためのデータストレージのことです。

コンテナとはライフサイクルが異なるため、コンテナが削除されても、volume内のデータは残り続けます。

(デフォルトでは)volumeは、dockerホストの特定のディレクトリに保存されています。

ボリュームマウント

上に記載したdocker engineの管理下にあるvolumeをコンテナにマウント(取り付ける)するのがボリュームマウントです。

繰り返しになりますが、volumeはコンテナと異なるライフサイクルのため、コンテナが削除されてもボリュームは削除されません。そのため、volumeに永続化したいデータを保存することで、データの永続化を実現できます。

services:
  db:
    volumes:
      # MySQLのデータを永続化する(ボリュームマウント)
      # コンテナ外のDockerVMのvolumeのdb-dataという領域にデータが保持される
      - 'db-data:/var/lib/mysql'
      - './docker/db/config/my.cnf:/etc/mysql/conf.d/mysql.cnf'
      - './docker/db/initial_db:/docker-entrypoint-initdb.d'
volumes:
  db-data:

バインドマウント

ローカルPCなど、docker engineが管理していないディレクトリやファイルにコンテナをマウントするやり方です。

  client:
    volumes:
      - ./client/:/usr/src/app
  api:
    volumes:
      - ./server:/usr/src/app

無名ボリューム

ホストのディレクトリを指定しない方法を無名ボリュームと呼びます。

無名ボリュームでは、指定したディレクトリやファイルをホストのファイルシステムではなく、docker engineが管理するvolumeにマウントします。

バインドマウントを使ってアプリケーションをコンテナにマウントするとき、特定のディレクトリを除外したいケースに使うことが多いです。

    volumes:
      # バインドマウント
      - ./server:/usr/src/app
      # 無名ボリューム
      - /usr/src/app/node_modules

上の例では、/usr/src/app配下のディレクトリとファイルはホストの./serverの内容と全く同じになりますが、/usr/src/app/node_modulesだけはコンテナ側の(volumeに保存されている)データを参照します。

ブリッジネットワーク

  • Dockerのネットワークモードの1つです。
  • デフォルトでは、コンテナはこのネットワークに接続されています。
  • ホスト上のネットワークとは別物のプライベートなネットワークです。
    • ポートマッピングをすることで、ホスト側からアクセスが可能となります。
  • ネットワーク内部の各コンテナは、それぞれ疎通が可能です。
    • ブリッジネットワークでは、他のホスト上で起動するコンテナと通信することはできません。

dockerにおける名前解決

networkの設定を明示的に実施しない場合、docker-compose.ymlを1つにつき、1つのブリッジネットワークが作成されます。

このネットワーク環境では、名前解決機能が提供されていて、コンテナはお互いをIPアドレスではなく、コンテナ名(apiやdb)で参照することができます。

services:
  api:
    image: node:14.15.4-alpine3.10
  db:
    image: mysql:5.7

例えば、上のdocker-compose.ymlファイルで定義される2つのコンテナは、互いのサービス名(api,db)で通信できます。

app.get('/db', async (req, res) => {
  try {
    // apiコンテナ内で、dbというコンテナ名で通信できる(同じブリッジネットワークだから)
    const response = await axios.get('http://db:3306');
    res.send(response.data);
  } catch (error) {
    res.status(500).send('Failed to fetch from database service');
  }
});

カスタムブリッジネットワーク

docker-compose.ymlを複数に分けてるような場合(マイクロサービス的な設計の時にあるかも)は、カスタムブリッジネットワークを使うこともあります。

通常、異なるdocker-compose.ymlで作ったコンテナはネットワークが異なるため通信できません。しかし、カスタムブリッジネットワークを作るとそれが可能になります。

services:
  api:
    image: node:14.15.4-alpine3.10
    networks:
      - sample_bridge
  db:
    image: mysql:5.7
    networks:
      - sample_bridge
networks:
  sample_bridge:
    driver: bridge

dockerにおける他のネットワークの種類

  • Host:ホスト
    • コンテナがHost上のネットワークにそのまま接続する
    • ホストのポートをそのまま使うため、衝突することがある
    • NATを介さないため、高速
  • None
    • ネットワーク接続不可

Discussion