iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

Docker基礎の基礎

こんにちは。

iimonでエンジニアをしているかねにわです。

業務の中でバックエンド側プロジェクトを実行する際など、時折Dockerに触れることがあるのですが、門外漢も甚だしく、おざなりな内容理解に留まっている状態でした。

ここで、以下の書籍が大変分かりやすく参考になりましたので、自学を兼ねて、本書をベースにDocker基礎について記事の形でまとめさせていただきました。

Docker&仮想サーバー完全入門

目次

Dockerとコンテナ

Dockerはコンテナ型の仮想環境を作成、配布、実行できるプラットフォームです。

コンテナは、それぞれの単位で隔離されたプロセスであり、一つの物理マシン上で独立した複数の仮想サーバを構築することが可能となります。

このコンテナを作成・実行するソフトウェアがDockerです。

コンテナと仮想マシン

Dockerと似た仮想化技術に仮想マシンがあります。

仮想マシンでは、内部にホストOSと区別されたゲストOSをもち、ハードウェア層まで含めたマシン全体を仮想化するのに対し、 コンテナはあくまでソフトウェア層のみを仮想化します。コンテナではゲストOSを含まず、ホストOSのカーネルを利用するため、 処理にかかるコストが非常に軽く、環境を簡単に複製したり使い捨てることが可能となります。

コンテナを使うメリット

コンテナを使わずにアプリの環境構築をしようとすると、通常は諸々ソフトウェアをインストールして設定を変更して・・といった いくつもの手順を踏む必要があり、他の人が同じ環境を作るのにも都度手間がかかります。

これに対しDockerであれば、どんなコンテナを作るかというイメージをまとめたファイル(後述するDockerfile、compose.yaml)をもとにコンテナを作成するので、そのファイルさえあれば誰でも同様の環境を容易に作成できるという利点があります。また、ローカルマシンを汚さずに環境構築ができるのも大きなメリットでしょう。

コンテナの設計図であるコンテナイメージ

コンテナはイメージから作られます。

イメージとはコンテナを作るためのテンプレートのようなもので、基本的なアプリやソフトウェアに加え、コンテナの動作に必要なファイルシステムや実行コマンドなど、コンテナに必要なファイルの集合体の形をとります。

プロセスであるコンテナと違い、配布・共有が可能です。

イメージは自分で作成することもできますが、公式レジストリであるDocker Hubからも提供されています。

このイメージを構築するには、後述するDockerfileというテキストファイルを使います(Dockerfileからイメージを構築することをビルド(build)とも呼ぶ)。

イメージをレジストリから取得するにはdocker image pullコマンドを使います。

イメージのバージョンはタグとして付与されており、バージョンも指定したい場合はdocker image pull イメージ:タグ名と記述します(タグを指定しない場合、タグがlatestのイメージがpullされます)。

タグの内訳はDocker HubのTagsから確認できます。

コンテナのライフサイクル

イメージから作成したコンテナは、実行することでコンテナ上のアプリを動作させることができます。 また、不要になればコンテナの停止および削除を行うというように、コンテナはいくつかの状態をもち、これをコンテナのライフサイクルと呼びます。

作成:イメージからコンテナを作成した状態。単に作成しただけなので、コンテナ上のアプリは動作しておらず、コンテナ内へのアクセスも不可。

実行:コンテナを動作させた状態。

停止:実行したコンテナを停止させた状態。

削除:コンテナを削除した状態。コンテナを再実行するには、コンテナの再作成が必要。

コンテナの状態を変化させるにはdockerコマンドを使います(docker container create docker container runなど)。

ここで留意すべきが、コンテナを削除すると、コンテナ内のデータも併せて削除されるという点です。これを避け、データを永続化するには、後述するボリュームまたはバインドマウントという機能を使います。

Dockerのアーキテクチャ

Dockerはクライアント・サーバシステムを採用しています。

ターミナル等からdockerコマンドを介してDockerが動作するサーバに対してリクエストを送ります。 このとき、Dockerデーモンというソフトウェアがクライアントからのリクエストを受付け、コンテナの作成や実行、イメージのプルなどを管理しています。

Docker Desktop

Docker Desktopと呼ばれるソフトウェアをインストールすることで、Dockerを使うことができます。 Docker DesktopにはDockerクライアントやデーモンが付属しており、GUIでの操作も可能なので気軽に導入、利用することができます。

画面上からは、取得および作成済のコンテナ、イメージ、ボリューム(後述)がタブごとに一覧で表示され、GUI上から諸々の操作をすることが可能です (とはいえコマンドの方が操作の多岐性において有利なので、本記事ではコマンド操作メインで進めていきます)

実際にコンテナを作成してみる

さて、以上の基本を踏まえた上で、実際にコンテナを作ってみます。

ここでは、Docker Hubからプルするイメージをもとに、オープンソースのWebサーバソフトであるApacheのコンテナを作成してみましょう。 イメージは以下のものを使います。

https://hub.docker.com/_/httpd

以下のコマンドを実行してみましょう。

docker container run --name apache01 -p 8080:80 -d httpd

これにより、イメージのプルと併せてコンテナが作成・実行されます。 (上記コマンドは、Apacheのイメージ(httpd)をもとに、ホストマシンのポート番号8080でアクセス可能なapache01という名前のApacheサーバコンテナを作成すると言う意味です)

Docker Desktop上からも、コンテナが作成、実行されたことが確認できます。

この状態でhttp://localhost:8080/にアクセスし、以下のようなApacheのデフォルトページにアクセスできれば成功です。

コンテナを停止させるには、docker container stop コンテナ名コマンドを実行しましょう。

さらにdocker container rm コンテナ名コマンドで、コンテナを削除することができます。

以上がコンテナの作成・停止・削除の一連の流れです。

しかしながら、複雑な設定を付与したい場合や複数コンテナを作りたい場合など、多様なユースケースに応じて都度docker container runコマンドを打ち込むのは、記述も長くなり手間になります。

ここで、必要なコマンドやオプションを一つのファイルにまとめ、簡単にコンテナを作成可能なDocker Composeをご紹介します。

Docker Composeとは

Docker Desktopに同梱されているDocker Composeというソフトウェアにより、一度に複数のコンテナを作成・実行することが可能です。

YAML形式のcompose.yamlファイルにコンテナ定義を記述して利用します。

例えば、先述したApacheコンテナをYAMLファイルで定義するとこうなります。

compose.yaml

services: #コンテナ定義を記述。services≒コンテナ
  web: #コンテナ名
    image: httpd:2.4 #イメージ名
    container_name: apache01
    ports: #ポート番号のマッピング。ホストのポート番号:コンテナのポート番号
      - "8080:80"

このyamlファイルを配置したディレクトリで、コンテナの作成と実行を行うコマンドである docker compose up -dを実行してみます(-dオプションをつけないとコンテナがフォアグラウンドで実行され、ログがターミナル上に出続けるので付与しておきます)

docker container lsコマンドでコンテナが作成、実行されているか確認してみます。

先ほどと同様、http://localhost:8080/からApacheのデフォルトページにアクセスできれば成功です。 

コンテナの停止には docker compose stopコマンドを、起動には docker compose startコマンドで作成済コンテナを起動できます。startコマンドは作成済コンテナが存在することが前提で、新規作成は行いません。そのためイメージのビルドとコンテナ作成を併せて実施したい場合、docker compose up -dを使います。

このように、ホストマシンに直接インストールをせずともソフトウェアを簡単に試せるのはコンテナの大きなメリットの一つですが、作成が容易であるがゆえについたくさんのコンテナを作ってしまいがちです。。コンテナやイメージが増えすぎるとストレージ圧迫の原因にもなるので、不要になったコンテナはどんどん削除してしまいましょう。

docker compose downコマンドで、コンテナを削除できます。

消えた。

コンテナ内にアクセスしてみる

次はもう少し踏み込んで、コンテナ内部にアクセスして操作を行ってみましょう。

https://hub.docker.com/_/mysql

上記イメージをもとにMySQLのコンテナを立ち上げ、コンテナ内部からテーブルの作成、データ追加を行ってみます。

以下のようにcomposeファイルを記述し、docker compose up -dを実行します。

services:
  db:
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: test
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: test

コンテナが起動したら、docker compose exec db bash を実行し、コンテナ内でシェルを立ち上げます。

MySQLクライアントを立ち上げ、composeファイルで環境変数に設定したパスワードを入力してアクセスしていきます。

アクセスできたら、データベースに接続し、testというテーブルを作って以下のようにデータを投入してみます。

コンテナ内からMySQLクライアントにアクセスし、DBに対する操作を行うことができました。

exitコマンドでシェルを終了し、コンテナから抜けましょう。

また、ここで一旦docker compose downでコンテナも削除しておきます。

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

ここで、さきほど削除したコンテナを再びupコマンドで立ち上げ、MySQLコンテナにアクセスしてみましょう。

さきほどtestデータベース内にtestというテーブルを作成したので、そのデータにアクセスしようとすると・・

あれっ、作成したはずのtestテーブルがデータ諸共消えてしまっています。

「コンテナのライフサイクル」の項でも少し述べましたが、コンテナを削除した場合、通常はコンテナ内のデータも併せて削除されてしまいます。今回のようなデータベースコンテナの場合、データが毎回削除されてしまっては困ります。

コンテナ内のデータを残したいというような場合、ボリューム、またはバインドマウントという機能を使って、データの永続化を行うことができます。

概略は以下の通りです。

ボリューム:Dockerが管理する記憶領域にデータを永続化させる。バインドマウントよりもデータの移行やバックアップが容易。 composeファイルに以下のようにvolumesを記述するとボリュームが作成される。

services:
  コンテナ名:
    image: イメージ名
    volumes:
      - volumesに定義したボリューム名:コンテナ内のパス
volumes:
  ボリューム名

バインドマウント:ホストOSのディレクトリやファイルをマウント。データを変更する際はホストOSのファイルを直接変更することで、コンテナ内にも自動で反映される。 composeファイルのservices下に、volumesを記述し、ホストOSのディレクトリを記述。

services:
  コンテナ名:
    image: イメージ名
    volumes:
      - ホストOSのディレクトリ:コンテナ内のパス

ここでは、ボリュームを使ってMySQLコンテナのデータを永続化してみます。

composeファイルに以下のように追記し、コンテナを起動、先ほどと同様の流れでMySQLクライアントにアクセスします。

services:
  db:
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: test
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: test
    volumes:
      - db-store:/var/lib/mysql 
volumes: 
  db-store:

↑composeファイルに定義した通りdb-storeという名前でボリュームが作成されているのが確認できる

nameがWoodyのレコードを持つtestテーブルを作成し、コンテナを抜けます。

ここでコンテナを削除→再度立ち上げて先ほど追加したデータを参照してみると・・

データがしっかり保持されていることが確認できます!

このように、永続化の手法を使うことでコンテナ内のデータを保持させることができます。

ちなみに、筆者はボリューム設定追加後にdocker compose up -dしようとしたところ、compose.yaml: volumes must be a mappingというエラーが出てしまいしばらく途方に暮れていたのですが、原因は最終行のvolumes: db-storeのあとにコロン:をつけ忘れていたことが原因でした。。

【蛇足】 ボリュームに設定したコンテナパスである/var/lib/mysql ですが、これはMySQLにおいてデータファイルが格納されるディレクトリです。データベースのテーブルデータやログファイル等が保持されています。

試しに、コンテナ内の指定ディレクトリをホストマシンの現在のディレクトリ上にコピーできるdocker cp mysql-test-db-1:/var/lib/mysql ./mysql_cp_dirコマンドを実行してディレクトリ構成を確認してみましょう。

作成したtestテーブルに対応するデータファイルができていることが確認できますね。

Dockerfile

これまではcomposeファイル上から、公式レジストリよりダウンロードしたイメージをそのまま指定していましたが、インストールするソフトウェアの種類や依存関係など、より詳細なイメージを指定、構築したい場合、通常Dockerfileを使います。

Dockerfileを配置しておくことで、dockerコマンドやdocker composeコマンドでイメージのビルドを行う際に、このファイルに基づいたイメージを作成することができます。

Dockerfileに記述する基本的な内容は以下の通りです。

FROM:もとになるイメージを指定する

WORKDIR:作業ディレクトリを指定。指定ディレクトリが存在しない場合はディレクトリ作成

RUN:イメージのビルド時に実行するコマンド

COPY:イメージにファイルやフォルダをコピー

ADD:イメージにファイルやフォルダをコピー。tarファイルの展開が可能などCOPYより多機能

CMD:コンテナ起動時に実行するデフォルトコマンド

ENTRYPOINT:コンテナ起動時に実行するコマンド

試しに、DjangoMySQLを組み合わせた環境をDockerfileを使って構築していきます。

まず、以下の3つのファイルを作成します。

requirements.txt

Django
mysqlclient

Dockerfile

FROM python:3.10
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt

compose.yaml

services:
  web:
    build: . # Dockerfileからイメージ作成
    command: python manage.py runserver 0.0.0.0:8000
    depends_on:
      - db
    ports:
      - "8000:8000"
    volumes:
      - .:/code
  db:
    image: mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: test
      MYSQL_DATABASE: test
      MYSQL_USER: test
      MYSQL_PASSWORD: test
    volumes:
      - db-data:/var/lib/mysql
volumes:
  db-data:

composeファイルのbuildにカレントディクレトリを指定することで、この階層のDockerfileからイメージが作成されます。

作成後、以下のコマンドを実行してmyprojectという名前のDjangoプロジェクトを作成します。

docker compose run --rm web django-admin startproject myproject .

そうすると↑のようにプロジェクトが作成されるので、次にsettings.pyのDATABASESを以下のように変更し、Djangoの接続先DBをデフォルトのSQLiteからMySQLに向けます。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',
        'USER':'test',
        'PASSWORD':'test',
        'HOST':'db',
        'PORT':'3306'
    }
}

compose.yamlのenvironmentに記述した値で設定します。

ここでdocker compose up -dを実行します。

http://localhost:8000/へアクセスすると、無事Djangoのトップページが表示されました!

注:docker compose up -dはイメージが既に存在する場合、イメージをビルドをし直すことなく既存のイメージを使います。そのため、Dockerfileの内容を書き換えた場合はdocker compose buildコマンドでイメージの再ビルドを行い、変更内容を反映させましょう。また、docker compose up -d --buildのように、upコマンドに続けて--buildオプションを付与することで、イメージの再ビルドおよびコンテナ起動を同時に実行することができます。

まとめ

以上、Dockerの基礎的な内容について確認してきました。

本記事を書くまでは、Dockerへの理解がかなり不明瞭な状態でしたが、Dockerのメリットや構成ファイルの役割、記述方法とその意味など大きく知見を広げることができたと感じます。

コンテナのネットワークなど、まだこの記事で触れられていない部分は多くありますが、Docker基礎としては概略を掴むことができたのではないかと思います。また機会があれば何か記事にできればと存じます。

ここまでお読みいただきありがとうございます。弊社ではエンジニアを募集しております。もし興味を持って下さった方がいれば、ぜひカジュアル面談でお話しできればと思います!

Wantedly / Green

参考資料

Docker&仮想サーバー完全入門

MySQL :: MySQL 8.0 リファレンスマニュアル :: MySQL 用語集

【初心者】Docker+Django+MySQLで環境構築 #備忘録 - Qiita

DockerのVolumeを使用してMySQLデータを永続化する #volume - Qiita

Dockerfileとdocker-compose.ymlとは?役割と違いをわかりやすく解説 #入門 - Qiita

MySQLがデータを保管する場所 〜 データディレクトリ - 日常メモ