Docker とは
Docker とは、機能を配布する手段です。
「Docker イメージ」というパッケージを作成し、これを起動することにより機能を達成します。
多くのことは (無茶をしない限り) できません。一つの Docker イメージにつき、一つの機能を持たせます。
複数の機能が必要なときは、複数の Docker イメージを用意します。
普通のサーバ構成とは逆の考え方をします。
Docker は仮想化技術のひとつである「コンテナ機能」を利用しています。
× ひとつの OS を用意して複数の機能をインストールする。 ← ふつうはこっち ◯ 機能ごとに OS を用意し、複数の OS を走らせる。 ← Docker はこっち
親 OS に仮想化機能が無い場合は Docker を使用できません。
(LXC のようなコンテナの中からも起動できるらしいけど、リソースの開放が必須では意味がない)
また、内蔵する機能は一つだけなので、別の機能である sshd や telnetd を混在できません。
このため、通常の OS で日常的に使用する ps や ping を Docker にログインして実行することはできません。
(半分うそ。/bin/bash を指定して run させればコマンド実行できるし、無茶をすれば複数の機能を詰め込める)
パッケージの配布と利用を最優先とし、インストールした機能をメンテナンスすることなく運用するのが Docker の特徴と言えます。
これが Docker の使い方です。この使い方ができないものは Docker に向いていません。
- 不要になったら削除する。
- 古くなったら (新しいものができたら) まるごと入れ替える。
- 複数の機能を詰め込まない。複雑な機能を実現するには複数のパッケージを組み合わせる。
1. 動作環境の用意
1-1. Docker エンジンをインストールする。
Docker エンジンとは下記 a~c を含んだセットを指し、Docker デーモンの上で Docker イメージを動作させる。1-2. サンプルで動作確認する。
Docker イメージは LXC の中から起動させない。Docker エンジンは実機や KVM 等のコンテナ以外の OS にインストールする。
$ su
- Docker デーモン
- API
- docker コマンド (CLI)
# dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# systemctl enable docker --now
docker コマンドは、デーモンやローカルリポジトリ*1に直接アクセスするため、管理者権限が必要。1-3. プロセスを確認する。
*1公式には「レジストリ(保管場所)」と「リポジトリ(イメージの集合)」が区別されるが、
本稿ではローカルのイメージ保存領域を便宜上「ローカルリポジトリ」と呼称する。
# docker run hello-world
初回は hello-world のダウンロードが実行される。
Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 17eec7bbc9d7: Pull complete ea52d2000f90: Download complete Digest: sha256:ef54e839ef541993b4e87f25e752f7cf4238fa55f017957c2eb44077083d7a6a Status: Downloaded newer image for hello-world:latest
hello-world は表示するとすぐに終了するため、プロセスが見えない。
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES --- hello-world のプロセスは表示されない ---
終了したプロセスを確認
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bc4c49cb10c4 hello-world "/hello" 19 seconds ago Exited (0) 18 seconds ago condescending_jones
# exit
$
2. Docker イメージの作成と動作確認
現在時刻を返すサーバ機能を作成してみる。
標準的な OS のコマンドだけを使用するため、プログラムは作成しない。
2-1. Docker イメージを作成する。
2-1-1. 定義ファイルを作成する。
$ mkdir daytime2-1-2. Docker イメージをビルドする。
$ cat > ./daytime/Dockerfile << EOF
EOF
FROM almalinux:9-minimal # ncat をインストール RUN microdnf install -y nmap-ncat && microdnf clean all # リッスンポート (省略可。Docker desktop では必須) EXPOSE 130 # ポート130で待ち受け、接続されたら現在時刻を返して終了する設定 # -lk: 待機を続ける, 130: ポート番号, -e: 接続時に実行するコマンド CMD ["ncat", "-lk", "130", "-e", "/usr/bin/date"]
$ su
# docker build -t daytime ./daytime/
[+] Building 2.5s (6/6) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 422B 0.0s => [internal] load metadata for docker.io/library/almalinux:9-minimal 2.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [1/2] FROM docker.io/library/almalinux:9-minimal@sha256:5ad6bf379aa6b 0.0s => => resolve docker.io/library/almalinux:9-minimal@sha256:5ad6bf379aa6b 0.0s => CACHED [2/2] RUN microdnf install -y nmap-ncat && microdnf clean all 0.0s => exporting to image 0.3s => => exporting layers 0.0s => => exporting manifest sha256:e33c58c4dd5ee8c6a5647386ba84a018391a8d32 0.0s => => exporting config sha256:dfd1ba639f5a22e930276b206122d3107561c1e636 0.0s => => exporting attestation manifest sha256:9bcd48a21a2ff7f467ef4db6335e 0.0s => => exporting manifest list sha256:ae77ea3ee1394d00dd758812b831445f645 0.0s => => naming to docker.io/library/daytime:latest 0.0s => => unpacking to docker.io/library/daytime:latest 0.1s
# docker images daytime
イメージが実行中の場合は EXTRA の列に U が表示される。
i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA daytime:latest 8df6b92ba1dd 146MB 38.6MB U
Docker イメージのリポジトリは下記コマンドにより確認できるが、実際の格納場所を確認することは出来ない。
# docker info | egrep '(Docker Root Dir|Storage Driver)'
Storage Driver: overlayfs Docker Root Dir: /var/lib/docker
# exit
$
2-2. 動作確認をする。
2-2-1. コンテナを起動する。
ここでは、自ホストの 1300 番ポートを経由してコンテナにアクセスする。
また、テストのためフォアグラウンドで実行する。(-d を付けない)
$ su
# docker run -p 1300:130 --rm --init --name daytime-0 daytime
確認 (下記 2-2-2) ができたら、 で停止させる。
# exit*2大抵は、ターゲットポートと呼ばれる。
-p : ホストポートとコンテナポート*2を指定する。 --rm : 実行時のワークを削除する。(一時ファイルやログ) --init : 終了シグナルの受け口を設けて正常終了を可能にする。(Ctrl + C を使用する場合は必須) --name : 実行インスタンスの名称を指定する。(ひとつのイメージから複数のインスタンスを実行可能)
$
2-2-2. 別の端末から確認する。(同じコンピュータ)
$ ncat localhost 1300 -i 0.1s
Tue Mar 3 12:50:53 UTC 2026 Ncat: Idle timeout expired (100 ms).
コンテナが停止しない場合はこちら。
$ su
# docker stop daytime-0
daytime-0
# exit
$
2-3. Docker イメージの削除
2-3-1. イメージの存在を確認する。
$ su2-3-2. イメージを削除する。
# docker images daytime
i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA daytime:latest 8df6b92ba1dd 146MB 38.6MB U
# docker image rm daytime2-3-3. イメージの削除を確認する。
Untagged: daytime:latest Deleted: sha256:25fcd04435568dc92bb60ee6008c608d104e60da7ebb0a37d3a46ca58d356797
# docker images daytime2-3-4. ワークを削除する。
i Info → U In Use IMAGE ID DISK USAGE CONTENT SIZE EXTRA
既に終了したコンテナのワークをすべて削除する。(一時ファイルやログ)
# docker container prune
WARNING! This will remove all stopped containers. Are you sure you want to continue? [y/N] y ⏎ Deleted Containers: cb0a8215a0a7d916e3a2bc1b753a3dd50fe460fb050ceb235efa0efc7707d344 bc4c49cb10c49e20984f9eb7d3edcb1f03d6b88153d5f7f37b78315de16c7ee1 Total reclaimed space: 0B
# exit
$
3. じゃんけんシステムの作成
3 つのじゃんけんサーバを動作させ、それぞれの勝敗をカウントする。
じゃんけんサーバはグー・チョキ・パーを返すだけとし、進行役コンテナが勝敗を取りまとめる。
それらを相互接続するために、ユーザ定義ブリッジネットワークを作成する。
プログラムは Python を使用し、Flask を使ったじゃんけんサーバ 1 つ*3と、MC クライアント*4を作成する。
*3 1 つのイメージを、名前を変えて 3 つ起動する。
*4 MC: 司会進行役のこと。イベントを進行させたり、盛り上げたりする。
3-1. じゃんけんサーバを作成する。
3-1-1. プレイヤーのプログラムを作成する。
$ mkdir ./janken/3-1-2. プレイヤーの Dockerfile として "player" を作成する。
$ cat > ./janken/player.py << EOF
EOF
#!/usr/bin/env python3 from flask import Flask, jsonify import os, random MY_NAME = os.getenv('MY_NAME', 'unknown') app = Flask(__name__) @app.route('/hand') def get_hand(): # ランダムに手を返す hand = random.choice([ 'R', # Rock グー 'P', # Paper パー 'S', # Scissors チョキ ]) return jsonify({"name": MY_NAME, "hand": hand}) if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)
$ cat > ./janken/player << EOF3-1-3. プレイヤーをビルドする。
EOF
# 材料(OS)の指定 FROM almalinux:9 # 作業ディレクトリの指定 WORKDIR /app # 道具のインストール(ビルド時にネットワークが必要) RUN dnf install -y python3 python3-pip RUN pip3 install flask # リッスンポート (省略可。Docker desktop では必須) EXPOSE 8080 # プログラムをコンテナの中へ入れる COPY player.py /app/player.py # 起動時に実行するコマンド (AUTOEXEC) ENTRYPOINT ["python3"] CMD ["player.py"]
$ su3-1-4. プレイヤーの動作を確認する。
# docker build -t janken-player -f ./janken/player ./janken/
-d でバックグラウンド動作させる。
# docker run -d -p 8080:8080 --name jp1 -e MY_NAME=jp1 janken-player
5af15fa686ef575c0685f90e501e0e68de5b2e857c408845fe58518d888567f2 ← インスタンス ID (毎回異なる)
# curl localhost:8080/hand ; curl localhost:8080/hand
{"hand":"R","name":"jp1"} {"hand":"S","name":"jp1"}
# docker stop jp1
# exit
$
3-2. MC クライアントを作成する。
3-2-1. クライアントのプログラムを作成する。
$ cat > ./janken/mc.py << EOF3-2-2. MC クライアントの Dockerfile として "mc" を作成する。
EOF
#!/usr/bin/env python3 import requests, time time.sleep(3) # プレイヤーの起動を待つ nodes = [ 'http://jp1:8080/hand', 'http://jp2:8080/hand', 'http://jp3:8080/hand', ] results = {'jp1': 0, 'jp2': 0, 'jp3': 0} def judge(h_a, h_b, h_c): hands = set([h_a, h_b, h_c]) # 3種類出た、または1種類だけなら「あいこ」 if len(hands) == 3: return [] if len(hands) == 1: return [] # 勝敗が決まる場合(2種類のみ存在) win_map = { 'R': 'S', # グー : チョキ 'S': 'P', # チョキ : パー 'P': 'R', # パー : グー } # どの手が勝っているか特定 h1, h2 = list(hands) winner_hand = h1 if win_map[h1] == h2 else h2 # 勝った手を出した人をリストで返す winners = [] if h_a == winner_hand: winners.append('jp1') if h_b == winner_hand: winners.append('jp2') if h_c == winner_hand: winners.append('jp3') return winners for i in range(10): print(f'Round {i+1: >2}:', end='') try: # 3人から手を取得 h_a = requests.get(nodes[0]).json()['hand'] h_b = requests.get(nodes[1]).json()['hand'] h_c = requests.get(nodes[2]).json()['hand'] winners = judge(h_a, h_b, h_c) for w in winners: results[w] += 1 print(f'jp1:{h_a}, jp2:{h_b}, jp3:{h_c} -> 勝ち: {winners}') except Exception as e: print(f'Error: {e}') print('-----') print(f'結果: {results}')
$ cat > ./janken/mc << EOF3-2-3. MC クライアントをビルドする。
EOF
# 材料 (OS) の指定 FROM almalinux:9 # 作業ディレクトリを設定 WORKDIR /app # Python と HTTPリクエスト用のライブラリをインストール # (ビルド時にネットワークが必要) RUN dnf install -y python3 python3-pip && \ pip3 install requests # MC のプログラムをコンテナ内にコピー COPY mc.py /app/mc.py # 起動時に実行するコマンド (AUTOEXEC) ENTRYPOINT ["python3", "mc.py"]
$ su
# docker build -t janken-mc -f ./janken/mc ./janken/
3-3. 動作を確認する。
3-3-1. ユーザ定義ブリッジネットワークを janken-net の名称で作成する。
# docker network create janken-net3-3-2. じゃんけんプレイヤーを 3 つ起動する。(janken-net に接続)
3e13c3a3f0eeba0cbc21d5cf7b61528749b67c2b7cbcf54e1350c6225d767b75 ← インスタンス ID (毎回異なる)
# docker run -d --rm --init --name jp1 --network janken-net -e MY_NAME=jp1 janken-player3-3-3. MC クライアントを実行する。(janken-net に接続)
# docker run -d --rm --init --name jp2 --network janken-net -e MY_NAME=jp2 janken-player
# docker run -d --rm --init --name jp3 --network janken-net -e MY_NAME=jp3 janken-player
# docker run --rm --init --name jmc --network janken-net janken-mc3-3-4. じゃんけんプレイヤーを 3 つ停止させる。
Round 1:jp1:S, jp2:S, jp3:S -> 勝ち: [] Round 2:jp1:P, jp2:S, jp3:S -> 勝ち: ['jp2', 'jp3'] Round 3:jp1:R, jp2:S, jp3:R -> 勝ち: ['jp1', 'jp3'] Round 4:jp1:P, jp2:R, jp3:P -> 勝ち: ['jp1', 'jp3'] Round 5:jp1:R, jp2:P, jp3:S -> 勝ち: [] Round 6:jp1:S, jp2:R, jp3:R -> 勝ち: ['jp2', 'jp3'] Round 7:jp1:R, jp2:P, jp3:P -> 勝ち: ['jp2', 'jp3'] Round 8:jp1:P, jp2:S, jp3:R -> 勝ち: [] Round 9:jp1:R, jp2:R, jp3:R -> 勝ち: [] Round 10:jp1:P, jp2:R, jp3:S -> 勝ち: [] ----- 結果: {'jp1': 2, 'jp2': 3, 'jp3': 5}
# docker stop jp1 jp2 jp33-3-5. ユーザ定義ブリッジネットワーク janken-net を削除する。
# docker network rm janken-net
# exit
$
4. Docker イメージの受け渡し
Docker イメージはローカルリポジトリを基本として動作するため、他の環境へ移すにはエクスポート・インポートの作業が必要になる。
tar 形式だが、エクスポート時の圧縮は不要。
Docker イメージは、ビルド直後に圧縮してローカルリポジトリに格納されている。
(docker run は自動的に解凍して実行される)
4-1. イメージをまとめてエクスポートする。
$ su4-2. イメージをまとめてインポートする。
# docker save janken-player janken-mc > janken.tar
# exit
$ tar -xOf janken.tar manifest.json | jq -r '.[].RepoTags[]'
janken-player:latest janken-mc:latest
$ su
# docker load < janken.tar
Loaded image: janken-player:latest Loaded image: janken-mc:latest
# exit
$