さわって学ぶクラウドインフラ docker 基礎からのコンテナ構築
仕事でCI/CD環境の構築を行うにあたって、各ツールをdockerコンテナ上に構築します。
dockerについての基礎知識を補うため、参考書を購入して学んでいきたいと思います。
本ページは、学んだことの備忘録として残していきたいと思います。
購入した参考書
以下の参考書を購入しました。
さわって学ぶクラウドインフラ docker基礎からのコンテナ構築 [ 大澤 文孝 ]
理論だけでなく、実際に自分で作業を行いながら学べそうということで購入しました。
Dockerとは
隔離環境でプログラム一式を実行できる仕組みをコンテナ技術と言います。
「Docker」はコンテナ技術の中でも最も使われるシステムとなっています。
Dockerの利点としては、
・環境が隔離されているため、互いに影響を及ぼさずに実行できる(競合問題などが起きない)
・コンテナ内のプログラムを実行した際に、コンテナ外の何かが参照されることはないので容易に持ち出し等が可能(ポータビリティ性)
Dockerの欠点としては、
・Linuxシステムでしか動かない
・完全な分離ではない(ハードウェアをエミュレートしているわけではない)
Dockerコンテナを利用するためには、LinuxにDocker Engineをインストールする必要があります。
Dockerエンジンをインストールしたコンピュータのことを「Dockerホスト」と呼びますが、構成としては以下のような形になります。
Dockerコンテナは、Dockerイメージから作成することができます。
Dockerイメージは必要なファイルをすべて固めたアーカイブパッケージとなっていて、Dockerレジストリと呼ばれるDockerイメージを管理するリポジトリに登録することができます。Dockerイメージには①基本的なLinuxディストリビューションだけのDockerイメージと②アプリケーション入りDockerイメージの2種類があります。Dockerイメージはカスタマイズすることもでき、①の基本Dockerイメージから各種アプリや設定ファイルなどを追加してオリジナルのDockerイメージの作成が可能です。(カスタムのDockerイメージを作成する時は、その一連の処理を記載したDockerfileと呼ばれる設定ファイルを用いることが一般的です。)
環境構築
それでは実際にDockerを触っていきたいと思います。
本書では、AWS上でEC2を使用したDocker環境を用いて説明しています。
AWS EC2インスタンスの準備
AWSの仮想サーバーサービスであるEC2を利用してDocker環境を作成していきたいと思います。
AWSの詳細に関しては別途書籍を購入しているので、改めて記載したいと思います。
今回は、無料1年枠を利用して「t2.micro」のインスタンスを利用します。
(本書8章までは、上記インスタンスで動作させることができます。)
ssh接続クライアントソフトは、TeraTermでもPuttyでも良いですがなぜか私の環境ではTeraTermで接続できなかったので、一旦Puttyを利用して接続したいと思います。Puttyで必要な鍵ファイルは.ppkの拡張子を持ったファイルになるのでインスタンス作成時に生成し、ローカルに保存しておきます。
sshでインスタンス接続時に生成した.ppkファイルを指定して接続します。
Docker Engineのインストール
Docker Engineのインストールは、基本的にはこちらの公式ページを参考にしてコマンドを打てばできます。
インストールが終了したら、Ubuntuの場合、デフォルトではrootユーザーしかdockerを利用できないので、ubuntuユーザーでもdockerを利用できるように下記コマンドで、dockerグループにubuntuユーザーを追加します。
sudo gpasswd -a ubuntu docker
以上で、Docker Engineのインストールは完了です。
Webサーバーの構築
まずは、実際に公式に提供されているDockerイメージを使用してWebサーバーを構築していきたいと思います。ここで基本的なDocker操作の流れを習得していきます。
Dockerイメージは、Docker社が運用しているDockerレジストリのDockerHubに大多数あります。
今回利用するのは、apatheがインストールされたイメージでDockerの公式イメージとなります。
利用方法で、Dockerfileを利用しないで直接起動する方法として下記コマンドが記載されています。
docker run -dit --name my-apache-app -p 8080:80 -v /home/ubuntu:/usr/local/apache2/htdocs/ httpd:2.4
実際に上記コマンド実行後にmy-apache-appコンテナが実行されていることがわかります。
~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4edae3b1dac9 httpd:2.4 "httpd-foreground" 43 seconds ago Up 42 seconds 0.0.0.0:8080->80/tcp, :::8080->80/tcp my-apache-app
「http://EC2インスタンスのIPアドレス:8080/」をWebブラウザのアドレスに入力すると、ApachのWebページが表示されます。ここでは、docker runを実行した時のディレクトリ内容(/usr/local/apache2/htdocs/)が表示されています。
上記ディレクトリのマウント先としてDockerHostのhomeディレクトリ(/home/ubuntu)を指定しているため、/home/ubuntuのディレクトリに下記のようなindex.htmlを作成すると、
<html>
<body>
<div>Hello Container!!</div>
</body>
</html>
ブラウザでは、「Hello Container!!」が出力されます。
このように、コンテナ技術を利用すると自分でアプリのインストールや設定を行う必要がなく容易に環境を立ち上げることができます。
(ハマりポイント)
・初めブラウザに表示させた時は、「Forbidden You don’t have permission to access this resources」のようなエラーが表示されました。/usr/local/apache2/htdocsのディレクトリに777のアクセス権を与えることでエラー無く表示することができました。
Docker基本操作
dockerのコマンドについては、公式ページに詳細が記載されています。
本章では、Dockerの基本コマンドの説明と利用の流れを説明したいと思います。
コンテナ起動から終了までの流れは、下記の図のようになります。
次にdockerオプションで特に重要で、よく使用するオプションを説明します。
-pオプション
-pオプションは、ポート番号をマッピングするものです。
前章でWebサーバーを構築した際に、「-p 8080:80」と指定しましたが、これはDockerホストのTCPポート8080番を、コンテナの80番に結び付けるという意味です。下図参照。
(ちなみに、pはportの略ではなくpublish(公開)の略です。コンテナのポートの一部をホストから公開して外から見えるようにするという意味合いになります。)
-vオプション
-vオプションはコンテナの特定のディレクトリに、ホストのディレクトリをマウントする設定です。
マウントというとイメージが付きにくい方もいるかもしれませんが、下図に示すようにマウントをするとコンテナからホストのディレクトリを見れるようになります。これは、コンテナ内で生成したものをホスト側に残したり、逆にホストのデータをコンテナ内で活用する際に必要な処理です。
デタッチとアタッチ
稼働中のコンテナは何らかのコマンドが実行中です。
本来であれば、実行が終了するまでコマンドをさらに入力することはできません。
しかし、前章で見た通りコマンドを入力することができます。
これは、"-dit"のオプションを付けることでコンテナをバックグラウンドで動かすことができるからです。
以下、各オプションの意味を見ていきます。
オプション | 意味 |
---|---|
-d | デタッチモード。端末と切り離した状態でバックグラウンドで実行する。 |
-i | インタラクティブモード。標準入出力および標準エラー出力をコンテナに連結する |
-t | 疑似端末(pseudo-tty)を割り当てる。疑似端末とは、カーソルの移動や文字の削除などの文字入力をサポートする端末のこと。 |
上の表からもわかる通り、-dを指定するとデタッチモードとなり端末とコンテナを切り離すことができます。
-dを指定しないとアタッチモードでフォアグラウンド実行となり、端末上にコンテナのログが出力されます。
デタッチモードとアタッチモードは切り替えが可能です。
アタッチモード→デタッチモードは、「Ctrl」+「P」,「Ctrl」+「Q」を順に押すと切り替わります。
デタッチモード→アタッチモードは、"docker attach コンテナ名orID"で切り替えることができます。
※-iオプションは標準入出力およびエラー出力をコンテナに対して結び付けるので、これを指定しないとキー入力がコンテナに伝わりません。
-tオプションは疑似端末を有効にする設定です。疑似端末はカーソルキーやエスケープキー、「Ctrl」キーなどで操作するためのものです。
コンテナ内操作
動作中や停止中のコンテナ内に入ってファイルを確認編集したりしたい時の操作方法を記載したいと思います。
・動作中のコンテナの場合
動作中のコンテナに入りたい場合は以下のコマンドを実行します。
docker exec -it my-apache-app /bin/bash
このコマンドを実行することで、シェルが起動してコンテナに入ることができます。
・停止中のコンテナの場合
docker run --name my-apache-app -it httpd:2.4 /bin/bash
このコマンドを実行することで、シェルが起動してコンテナに入ることができます。
このコマンドの意味はあくまでコンテナ内に格納されている/bin/bashを実行しているにすぎません。
そのため、コンテナ内に/bin/bashが格納されていなければ実行することはできません。
Go言語コンパイル環境
dockerコンテナのユースケースの一つとして、開発環境を各アプリケーション開発者に配布するということがあります。例えばコンパイル環境です。
ここでは、実際にGo言語コンパイル環境を用いてコンパイルしてみたいと思います。
まずは作業ディレクトリでサンプルのソースを作成します。
package main
import "fmt"
func main(){
fmt.Printf("Hello World\n")
}
そしてgoのコンパイル環境のdocker image(golang:1.13)を用いて"hello.go"をコンパイルします。
docker run --rm -v {host-dir}:/usr/src/myapp -w /usr/src/myapp golang:1.13 go build -v
host-dir:hello.goを作成したディレクトリ
–rm:実行が完了したときに、このコンテナを破棄
-w:go build -vを実行するときの作業ディレクトリ
上記コマンドを実行すると、{host-dir}にはmyappというバイナリが作成されます。
そして、myappを実行すると"Hell World"が表示されます。
コンテナ内のファイルと永続化
コンテナとファイルの独立性
“docker rm"コマンドでコンテナを破棄してしまうと、コンテナ内に作成されたファイルなども同時に破棄されてしまいます。以下その例を示します。
まず、web01というコンテナを作成します。
docker run -dit --name web01 -p 8080:80 httpd:2.4
dockerホスト側の/tmpディレクトリ配下にindex.htmlファイルを作成します。
Tips
通常ディレクトリを移動するときは"cd"コマンドを利用して移動しますが、"pushd"コマンドを利用すると、カレントディレクトリが保存されて、もとに戻るときに"popd"コマンドで簡単に戻ることができます。
pushd /tmp
/tmp ~ ## カレントディレクトリが保存された状態で/tmpディレクトリへ移動します
popd
~ ## /tmpディレクトリからもとにいた~ディレクトリへ戻ることができます
<html>
<body>
<div>It's web01!</div>
</body>
</html>
次に、作成したdocker host側の/tmp/index.htmlファイルを、コンテナ側の/usr/local/apache2/htdocs/配下にコピーします。
※"docker exec"コマンドでコンテナ内に入ってファイルを作成することも可能ですが、今回作成したコンテナ内にはnanoなどのediterが入っていないので、インストールしたりする必要があり面倒です。その場合は、今回のように"docker cp"コマンドでdockerホストの内容をコンテナ内にコピーするのが適切です。
docker cp /tmp/index.html web01:/usr/local/apache2/htdocs/
このコマンドにより、web01のコンテナ内にindex.htmlがコピーされ、"http://localhost:8080″をwebブラウザで表示させると、"It’s web01!"の出力を見ることができます。
しかし、コンテナを破棄してしまうとコンテナ内にコピーされたindex.htmlのファイルも同時に破棄されてしまいます。(再度web01コンテナを作成しても、"It’s web01!"が表示されるindex.htmlは存在しません。)
そのため、"docker rm"コマンドは慎重に実行する必要があります。
バインドマウントとボリュームマウント
コンテナを破棄しても、データを永続化させる手法として"マウント"があります。
WEBサーバーの構築の章でも使ってきましたが、Dockerホストにあらかじめディレクトリを作っておき、それをマウントする方法を「バインドマウント」と言います。
一方で、ホスト上のディレクトリではなく、Docker Engine上で確保した領域をマウントする方法を「ボリュームマウント」と言います。
ボリュームマウントを使う利点としては、ボリュームの保存場所がDocker Engineで管理されるため、その物理的位置を意識する必要がなくなるという点です。
では、「バインドマウント」と「ボリュームマウント」はどのように使い分ければ良いのでしょうか。
①バインドマウントの方が良い場面
・Dockerホスト上に設定ファイルを置いたディレクトリを用意して、それをコンテナから参照したい場合
・作業ディレクトリの変更を即座にDockerコンテナから参照したい場合
②ボリュームマウントの方が良い場面
ボリュームマウントが向く場面としては、例えばデータベースを構成するコンテナにおいて、データベースのデータを保存する場所として用いる場面が挙げられます。
単純にDockerコンテナが扱うデータをブラックボックスとして扱い、コンテナを破棄してもデータが残るようにしたい場面です。
このようにDockerホストから不用意にデータを書き換えたくない場面では、ボリュームマウントが向きます。
データのバックアップ
前章でボリュームマウントを利用するユースケースとして、データベースのデータの保存を例として挙げました。この章では、実際にデータベースコンテナを立ち上げ、さらにデータをバックアップする方法について述べたいと思います。
まずは、ボリュームを作成します。
$ docker volume create mysqlvolume
$ docker volume ls
DRIVER VOLUME NAME
local mysqlvolume
次に、MySQL5.7のコンテナを起動します。
-vでマウントする際にボリュームマウントの場合はvolume nameを使用します。
また、-eオプションを用いることで環境変数として引き渡すことができます。
$ docker run --name db01 -dit -v mysqlvolume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mypassword mysql:5.7
起動したコンテナ内部に入り、mysqlコマンドを実行します。(passwordは環境変数として渡した"mypassword"になります。)
$ docker exec -it db01 /bin/bash
bash-4.2# mysql -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.40 MySQL Community Server (GPL)
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
データベースとテーブルを作成し、データを挿入します。
mysql> CREATE DATABASE exampledb;
Query OK, 1 row affected (0.01 sec)
mysql> use exampledb;
Database changed
mysql> CREATE TABLE exampletable (id INT NOT NULL AUTO_INCREMENT, name VARCHAR(50), PRIMARY KEY(id));
Query OK, 0 rows affected (0.04 sec)
mysql> INSERT INTO exampletable (name) VALUES ('user01');
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO exampletable (name) VALUES ('user02');
Query OK, 1 row affected (0.01 sec)
挿入したデータを確認すると、2件のレコードが表示されます。
mysql> SELECT * FROM exampletable;
+----+--------+
| id | name |
+----+--------+
| 1 | user01 |
| 2 | user02 |
+----+--------+
2 rows in set (0.00 sec)
ボリュームの詳細情報を確認してみます。
$ docker volume inspect mysqlvolume
[
{
"CreatedAt": "2022-12-28T21:36:03+09:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/mysqlvolume/_data",
"Name": "mysqlvolume",
"Options": {},
"Scope": "local"
}
]
では、このdocker volumeのバックアップを取るにはどうすればよいでしょうか?
Dockerでボリュームをバックアップするときは、適当なコンテナに割り当てて、そのコンテナを使ってバックアップを取るようにします。
具体的には、適当なlinuxシステムが入ったコンテナを一つ別に起動します。
そしてその/tmpなどのディレクトリにバックアップ対象のコンテナをマウントし、tarでバックアップを作ります。そのバックアップをDockerホストで取り出せば、バックアップは完了します。
では、実際にやってみましょう。実行コマンドは下記のとおりです。
$ docker run --rm -v mysqlvolume:/src -v "$PWD":/dest busybox tar czvf /dest/backup.tar.gz -C /src .
このコマンドで実際に行っていることを下図に示します。
上記コマンドを実行することで、dockerホスト側のカレントディレクトリに"backup.tar.gz"ファイルが作成されます。
次にリストアしてみます。
まずは、ボリュームを削除したのちに、新しくボリュームを作成します。
$ docker volume rm mysqlvolume
$ docker volume create mysqlvolume
そして、リストアのコマンドを実行します。
$ docker run --rm -v mysqlvolume:/dest -v "$PWD":/src busybox tar xzf /src/backup.tar.gz -C /dest
バックアップを取得した処理と逆の処理が実行されます。
再びボリュームマウントをしてmysqlコンテナを立ち上げデータを確認すると、適切に復元されていることがわかります。
コンテナのネットワーク
bridgeネットワーク
Dockerでは様々な仮想的なネットワークを作り、Dockerホストとコンテナ、もしくはコンテナ側で通信するように構成することができます。
Dockerで管理するネットワークは、"docker network ls"コマンドで確認することができます。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
840e3148ba13 bridge bridge local
08b04cbb1692 host host local
a1e1d4d668e3 none null local
既に備わっているネットワークとしては、「bridge」「host」「null」がありそれぞれ以下のような特徴を持っています。
①bridge
一番よく使用されるnetworkで、docker createもしくはdocker runをするときに、ネットワークのオプションを指定しない場合は、このネットワークが使用されます。Linuxのブリッジ機能を利用していて、外部のネットワークと接続されます。また、他のコンテナがこのbridgeネットワークに接続された状態では、互いに通信が可能です。
②host
ホスト側のネットワークインターフェースを共有するときに使用します。ホストと同じIPアドレスが割り当てられます。
③null
Dockerコンテナにネットワークインターフェースを持たせたくない場合(セキュリティなどを高めたいなどの場合)に使用します。外部との通信は一切できません。
それではコンテナを起動して、実際にネットワークの情報をみていきましょう。
$ docker run -dit --name web01 -p 8080:80 httpd:2.4
$ docker run -dit --name web02 -p 8081:80 httpd:2.4
二つのコンテナを立ち上げて、IPアドレスを"docker container inspect"コマンドで見てみます。
このコマンドはネットワーク情報の一覧が出てくるので、必要な情報のみをソートします。
$ docker inspect --format="{{.NetworkSettings.IPAddress}}" web01
172.17.0.2
$ docker inspect --format="{{.NetworkSettings.IPAddress}}" web02
172.17.0.3
また、Dockerホスト側に割り当てられているIPアドレスは、以下のようになります。
$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
inet6 fe80::42:7eff:fe2d:f4a9 prefixlen 64 scopeid 0x20<link>
ether 02:42:7e:2d:f4:a9 txqueuelen 0 (Ethernet)
RX packets 95 bytes 12387 (12.3 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 138 bytes 24647 (24.6 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
DockerEngineをインストールしたLinux環境には、docker0というネットワークインターフェースが作られ、そのIPアドレスは172.17.0.1であることがわかります。
bridgeネットワークは、IPマスカレードを使って構成されており、そのポート転送設定は下記のコマンドで確認することができます。
$ sudo iptables --list -t nat -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80
MASQUERADE tcp -- 172.17.0.3 172.17.0.3 tcp dpt:80
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8081 to:172.17.0.3:80
コンテナ間通信
それでは、Dockerコンテナ間の通信を試みます。
まずは、疎通確認用の第3のコンテナを立ち上げます。
$ docker run --rm -it ubuntu /bin/bash
「ip」,「ping」,「curl」コマンドを用いるため、aptコマンドでインストールします。
# apt update
# apt -y upgrade
# apt install -y iprout2 iputils-ping curl
ipコマンドで自身のコンテナのIPアドレスを確認します。
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
62: eth0@if63: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
自身のコンテナのIPアドレスは、172.17.0.4でした。
続いて、IPアドレス「172.17.0.2」と「172.17.0.3」に対して、pingを送信して疎通確認をします。
# ping -c 4 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.609 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.166 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.153 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.118 ms
--- 172.17.0.2 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3104ms
rtt min/avg/max/mdev = 0.118/0.261/0.609/0.201 ms
# ping -c 4 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.337 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.120 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.117 ms
64 bytes from 172.17.0.3: icmp_seq=4 ttl=64 time=0.114 ms
--- 172.17.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3115ms
rtt min/avg/max/mdev = 0.114/0.172/0.337/0.095 ms
どちらも疎通を確認することができました。
ついでに、curlでwebコンテンツを取得できることも確認します。
# curl http://172.17.0.2/
<html><body><h1>It works!</h1></body></html>
# curl http://172.17.0.3/
<html><body><h1>It works!</h1></body></html>
既存のbridgeネットワークを用いると、IPアドレスの代わりにコンテナ名を使って通信することはできません。
IPアドレスだけでの通信だと、一回一回IPアドレスを確認する必要があったり、Dockerコンテナを立ち上げるたびにIPアドレスが変わってしまったりとなかなか不便です。
コンテナ名で通信すると都合が良いですが、そのためには新規にDockerネットワークを立ち上げる必要があります。
$ docker network create mydockernet
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
840e3148ba13 bridge bridge local
08b04cbb1692 host host local
16c1b6a02c06 mydockernet bridge local
a1e1d4d668e3 none null local
$ docker network inspect mydockernet
[
{
"Name": "mydockernet",
"Id": "16c1b6a02c068b9f82c4f04e6347161e4afa6acd567cf24e5e58bf3690d9bd29",
"Created": "2022-12-29T18:25:08.686981309+09:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
新たに「172.18.0.0/16」のIPアドレスでmydockernetというネットワークが作成されました。
この新規networkにweb01とweb02のコンテナを接続させます。
$ docker run -dit --name web01 -p 8080:80 --net mydockernet httpd:2.4
$ docker run -dit --name web02 -p 8081:80 --net mydockernet httpd:2.4
$ docker network inspect mydockernet
[
{
"Name": "mydockernet",
"Id": "16c1b6a02c068b9f82c4f04e6347161e4afa6acd567cf24e5e58bf3690d9bd29",
"Created": "2022-12-29T18:25:08.686981309+09:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"35e5894b6f2e574fd8ac8226b10c76e61c6874e68fcf9c369b789b0671746d53": {
"Name": "web02",
"EndpointID": "e3d6fe0c620a5dedc2c18ef05e2e29314c7fb5400d1761c822e9ec233976269a",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"e12827d67503a2075bd215e902bb9f41b1daad649ad7525d16d24728143159a5": {
"Name": "web01",
"EndpointID": "a70b5cd8563cf33c7c62fc8f6bd94da4a5ab76ed044726501ca53c23ce969bc5",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
web01とweb02がこのネットワークに接続されたことが確認できます。
このネットワーク上で再び疎通確認用の第3のコンテナを立ち上げると、コンテナ名で疎通を確認することが可能となります。
Docker Compose
WordPressの立ち上げ
複数のコンテナを組み合わせて、一つのサービス・システムを構成することがあります。
その場合に、コンテナをまとめて起動したり停止したりする仕組みがDocker Composeです。
WordPressを例に、Docker Composeの仕組みをみていきましょう。
WordPressは、WordPressシステム本体であるWordPressコンテナとデータベースのMySQLコンテナからなります。MySQLコンテナはボリュームマウントをしてデータを永続化します。
それでは、上図の環境を実際に作っていきましょう。
手順としては、Dockerネットワークの作成、ボリュームの作成、MySQLコンテナの作成、WordPressコンテナの作成の順で行っていきます。
$ docker network create wordpressnet
$ docker volume create wordpress_db_volume
$ docker run --name wordpress-db -dit -v wordpress_db_volume:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=myrootpassword -e M
YSQL_DATABASE=wordpressdb -e MYSQL_USER=wordpressuser -e MYSQL_PASSWORD=wordpresspass --net wordpressnet mysql:5.7
$ docker run --name wordpress-app -dit -p 8080:80 -e WORDPRESS_DB_HOST=wordpress-db -e WORDPRESS_DB_NAME=wordpressdb -
e WORDPRESS_DB_USER=wordpressuser -e WORDPRESS_DB_PASSWORD=wordpresspass --net wordpressnet wordpress
ブラウザから「http://localhost:8080/」を開くと、Wordpressの初期設定画面が表示されます。
一連の設定を行いインストールが完了するとセットアップ完了です。
Docker Composeの活用
複数のコンテナを起動して連携させるのは不可能ではないですが、設定や起動コマンドなどが煩雑です。
Docker Composeを用いることで、コンテナの起動方法やネットワーク構成などを定義ファイルでまとめて管理し、実行することができます。
(Dokcer composeは、Dockerを補佐するPython製のツールなので、別途インストールする必要があります。)
$ sudo apt install -y python3 python3-pip
$ sudo pip3 install docker-compose
それでは実際にDocker Composeを使用していきます。
Docker Composeを使用するには、作業ディレクトリを用意してそこに定義ファイルであるdocker-compose.ymlファイルを準備します。
docker-compose.ymlは、「サービス」、「ネットワーク」、「ボリューム」の3つの定義からなります。
①サービス
全体を構成する1つ1つのコンテナのことです。サービスの中で、各コンテナが利用するネットワーク、ボリューム、ポート設定、環境変数などを定義します。
②ネットワーク
サービスが参加するネットワークを定義します。
③ボリューム
サービスが利用するボリュームを定義します。
それでは、docker-compose.ymlの例を示します。(主要なオプションについては、公式ページを参照してください。)
version: "3"
services:
wordpress-db:
image: mysql:5.7
networks:
- wordpressnet
volumes:
- wordpress_db_volume:/var/lib/mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: myrootpassword
MYSQL_DATABASE: wordpressdb
MYSQL_USER: wordpressuser
MYSQL_PASSWORD: wordpresspass
wordpress-app:
depends_on:
- wordpress-db
image: wordpress
networks:
- wordpressnet
ports:
- 8080:80
restart: always
environment:
WORDPRESS_DB_HOST: wordpress-db
WORDPRESS_DB_NAME: wordpressdb
WORDPRESS_DB_USER: wordpressuser
WORDPRESS_DB_PASSWORD: wordpresspass
networks:
wordpressnet:
volumes:
wordpress_db_volume:
“docker-compose.yml"ファイルが置いてあるディレクトリ上で、下記コマンドを実行します。
$ docker-compose up -d
Creating network "wordpress_wordpressnet" with the default driver
Creating volume "wordpress_wordpress_db_volume" with default driver
Creating wordpress_wordpress-db_1 ... done
Creating wordpress_wordpress-app_1 ... done
“docker-compose.yml"ファイルに記載してある定義に従って、コンテナが立ち上がります。
“http://localhost:8080/"にアクセスするとWordPressの初期設定画面が表示されます。
$ docker-compose down
Stopping wordpress_wordpress-app_1 ... done
Stopping wordpress_wordpress-db_1 ... done
Removing wordpress_wordpress-app_1 ... done
Removing wordpress_wordpress-db_1 ... done
Removing network wordpress_wordpressnet
上記コマンドでコンテナを停止し、破棄します。
ただし、ボリュームだけ残ることに注意してください。
Dockerイメージの自作
今までは、公開されているイメージを利用して、サービスやシステムを利用してきました。
しかし、インフラエンジニアとしてはカスタムなイメージを作成する機会も多いと思います。
本章では、カスタムイメージの作り方について学んでいきたいと思います。
カスタムなイメージの作り方としては、「コンテナからの作成」と「Dockerfileからの作成」の2通りがあります。
①コンテナからの作成
ベースとなるイメージからコンテナを起動して、そのコンテナに対して、docker execでシェルで操作したり、docker cpでファイルをコピーしたりして調整を加えます。それから、docker commitコマンドを使ってイメージ化します。
例えば、コンテナをそのまま別のコンピューターに移動したい場合などはコンテナのアーカイブとして使用できるこの方法が良いでしょう。
②Docker fileからの作成
ベースとなるイメージと、そのイメージに対してどのような操作をするのかを記載したDockerfileおよびファイル群などを用意し、docker buildコマンドを使ってイメージ化します。
この方法の最大のメリットは、ベースイメージに対してどのような変更を行うかをファイルに定義するので、管理がしやすいことが挙げられます。誰かにイメージを配布する場合はこの方法が一番使われます。
Tips レイヤーの考え方
Dockerのイメージは、データのサイズを抑えるために差分しか記録しない方式を取っています。そのためイメージは変更箇所が階層化された構造になっていて、この階層のことを「レイヤー」と言います。
実際にmysqlのレイヤーを確認すると下記のようになります。
docker pullをしたときに表示されるHash値の値一つ一つがレイヤーに相当します。
docker historyコマンドで過去の変更箇所を表示させることが可能です。
$ docker pull mysql:5.7
5.7: Pulling from library/mysql
d26998a7c52d: Pull complete
4a9d8a3567e3: Pull complete
bfee1f0f349e: Pull complete
71ff8dfb9b12: Pull complete
bf56cbebc916: Pull complete
2e747e5e37d7: Pull complete
711a06e512da: Pull complete
3288d68e4e9e: Pull complete
49271f2d6d15: Pull complete
f782f6cac69c: Pull complete
701dea355691: Pull complete
Digest: sha256:6306f106a056e24b3a2582a59a4c84cd199907f826eff27df36406f227cd9a7d
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
$ docker history mysql:5.7
IMAGE CREATED CREATED BY SIZE COMMENT
d410f4167eea 3 weeks ago /bin/sh -c #(nop) CMD ["mysqld"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) EXPOSE 3306 33060 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 3 weeks ago /bin/sh -c ln -s usr/local/bin/docker-entryp… 34B
<missing> 3 weeks ago /bin/sh -c #(nop) COPY file:e9c22353a1133b89… 14.2kB
<missing> 3 weeks ago /bin/sh -c #(nop) VOLUME [/var/lib/mysql] 0B
<missing> 3 weeks ago /bin/sh -c set -eux; yum install -y --setop… 264MB
<missing> 3 weeks ago /bin/sh -c #(nop) ENV MYSQL_SHELL_VERSION=8… 0B
<missing> 3 weeks ago /bin/sh -c set -eu; . /etc/os-release; { … 215B
<missing> 3 weeks ago /bin/sh -c set -eux; yum install -y --setop… 79.3MB
<missing> 3 weeks ago /bin/sh -c set -eu; . /etc/os-release; { … 225B
<missing> 3 weeks ago /bin/sh -c #(nop) ENV MYSQL_VERSION=5.7.40-… 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ENV MYSQL_MAJOR=5.7 0B
<missing> 3 weeks ago /bin/sh -c set -eux; key='859BE8D7C586F5384… 3.21kB
<missing> 3 weeks ago /bin/sh -c set -eux; yum install -y --setop… 13.6MB
<missing> 3 weeks ago /bin/sh -c set -eux; arch="$(uname -m)"; c… 2.31MB
<missing> 3 weeks ago /bin/sh -c #(nop) ENV GOSU_VERSION=1.14 0B
<missing> 3 weeks ago /bin/sh -c set -eux; groupadd --system --gi… 2.72kB
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:3853624d773c4bf6f… 135MB
実際にDocker imageを作成する際は、Dockerの流儀に従って作成していくことが重要です。
Docker社は公式で、「Dockerfileのベストプラクティス」というページを公開しています。
要点をまとめると以下の通りとなります。
①1つのコンテナは1つの処理しかしない
②利用するポートを明確にする
③永続化すべき場所を明確にする
④設定は環境変数で渡す
⑤ログは標準出力に書き出す
⑥メインのプログラムが終了するとコンテナが終了することを忘れない
コンテナからカスタムイメージを作成
それでは実際にカスタムイメージを作成していきます。
ここでは、コンテンツ(index.html)が格納されているimageを作成します。
まずは、ベースイメージを取ってきて、index.htmlをコンテナ内に配置します。
$ docker run -dit --name webcontent -p 8080:80 httpd:2.4
$ docker cp /tmp/index.html webcontent:/usr/local/apache2/htdocs
次にdocker commitコマンドを用いて、"mycustom_httpd"という名前でコンテナをイメージ化します。
$ docker commit webcontent mycustom_httpd
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mycustom_httpd latest 8ce589118441 6 seconds ago 145MB
作成した"mycustom_httpd"というイメージをもとにdocker runします。
そして、docker execでコンテナ内に入るとindex.htmlが意図通りに格納されていることが確認できます。
“http://localhost:8081/"にブラウザでアクセスすると、"Docker Contents"が表示されます。
$ docker run -dit --name webcontent_new -p 8081:80 mycustom_httpd
$ docker exec -it webcontent_new /bin/bash
root@8c57f40325c6:/usr/local/apache2# cat htdocs/index.html
<html>
<body>
<div>Docker Contents</div>
</body>
</html>
Dockerfileからカスタムイメージを作成
次にDockerfileからイメージを作成していく手法を試します。
作業用ディレクトリに、index.htmlファイルとDockerfileを作成します。
index.htmlファイルは、前章でも作成したとおり"Docker contents"と表示されるようにします。
Dockerfileは下記のように記載します。
FROM httpd
COPY index.html /usr/local/apache2/htdocs/
これはベースイメージとして"httpd"を利用し、index.htmlを"/usr/local/apache2/htdocs/"に追加するという定義を記したものです。
コピー元は、Dockerfileが置かれているディレクトリからの相対パスで、コピー先はWORKDIR命令で指定したパスからの相対パスとなります。
ファイルの準備が完了しましたら、docker buildコマンドでビルドします。
$ docker build -t myimage01 .
Sending build context to Docker daemon 3.072kB
Step 1/2 : FROM httpd
latest: Pulling from library/httpd
Digest: sha256:f8c7bdfa89fb4448c95856c6145359f67dd447134018247609e7a23e5c5ec03a
Status: Downloaded newer image for httpd:latest
---> 73c10eb9266e
Step 2/2 : COPY index.html /usr/local/apache2/htdocs/
---> 6721b8978fd4
Successfully built 6721b8978fd4
Successfully tagged myimage01:latest
※docker buildコマンドの詳しいオプションについては、公式ページを参照ください。
docker historyで作成したイメージの詳細情報を確認すると、COPY fileというレイヤーが存在することがわかります。
$ docker history myimage01:latest
IMAGE CREATED CREATED BY SIZE COMMENT
6721b8978fd4 14 minutes ago /bin/sh -c #(nop) COPY file:e26c7fad769b780c… 57B
新たに作成したmyimage01をもとにコンテナを起動させると、確かに"Docker contents"と表示させるindex.htmlが存在することが確認できます。
Tips Dockerfile書式
Dockerfileの書式は、公式ページに記載されていますので詳細を知りたい方はそちらを参照ください。
今回は特に私が重要だと思う命令について記載したいと思います。
コマンドの実行命令の「RUN」と「CMD,ENTRYPOINT」です。
①RUN
RUNコマンドは、docker buildするタイミングに実行します。
ソフトウェアパッケージのインストールやファイルのコピー、変更処理などのイメージの時点で実行しておきたいコマンドを記載します。
RUN命令を使うときは、できるだけ一つのRUNコマンドで済ませるべきです。(&&でつないで記載する。)
複数のRUNコマンドを使用してしまうと、その分レイヤーが作られてしまって効率が悪いからです。
②CMD,ENTRYPOINT
CMDとENTRYPOINTは、コンテナを起動したときのタイミング(docker start,docker runするときのタイミング)で、コンテナの中で実行するコマンドを指定するものです。例えば、アプリを起動する実行コマンドを記載すれば、docker runをするだけでコンテナが立ち上がると同時にアプリも起動します。
ENTRYPOINTは、コマンドの指定を強要します。ENTRYPOINTを指定した場合、docker runの最後に指定するコマンドは、このENTRYPOINTで指定したコマンドへの引数となります。
CMDは、docker runの際に指定する、最後のコマンドのデフォルト値を変更します。
実際の既存イメージのDockerfileを参照することで、理解を深めていきましょう。
httpdイメージのソースをご覧ください。
Dockerfileからhttpdイメージは、下記のとおりビルドされていることがわかります。
・debian:buster-slimをベースにしている。
・RUN命令で必要なパッケージをインストールしている。
・ポート80をEXPOSEしている。
・既定のコマンドはhttpd-foreground
(service httpd startにしてしまうと、コマンド終了と同時にコンテナも終了してしまうので、フォアグラウンド実行されるようなプログラムを動かしています。)
もう少し複雑なイメージを作成してみたいと思います。
ここでは、debianイメージをベースに、PHP入りのapacheを作ります。
まずは、DockerfileとサンプルPHPを準備します。アクセスすると自分のIPアドレスを表示するindex.phpをコンテナに含めます。
<html>
<body>
Your IP <?php echo $_SERVER['REMOTE_ADDR'] ?>。
</body>
</html>
次にDockerfileを準備します。
FROM debian
EXPOSE 80
RUN apt update \
&& apt install -y apache2 php libapache2-mod-php \
&& apt clean \
&& rm -rf /var/lib/apt/lists/* \
&& rm /var/www/html/index.html
COPY index.php /var/www/html/
CMD /usr/sbin/apachectl -DFOREGROUND
・FROM:debianイメージをベースイメージとして選択しています。
・EXPOSE:ポート80で通信することを明示しています。
・RUN:ApacheとPHPをインストールします。apt cleanとrm -rfでの削除は、パッケージの中間ファイルを消すことで、Dockerイメージのサイズを小さくしています。
また、index.htmlを削除しているのは、同階層のディレクトリにindex.htmlとindex.phpがあるときにindex.htmlが優先されて表示されてしまうためです。
・COPY:index.phpをコンテナ内にコピーしています。
・CMD:Apacheをフォアグラウンドで実行するようにします。
ファイルの準備ができたら、イメージをビルドします。
$ docker build . -t myphpimage
※WSL2の実行環境でdocker buildをしたときに下記のエラーが出力されました。
E: Release file for http://deb.debian.org/debian/dists/bullseye-updates/InRelease is not valid yet (invalid for another 5h 24min 22s). Updates for this repository will not be applied.
これはWSL2側のシステム時刻がずれていることが原因で起こるエラーのようです。以下コマンドで、システムクロックをハードウェアクロックに合わせると、エラーが解消されます。
$ sudo hwclock --hctosys
作成した"myphpimage"からコンテナを立ち上げます。
$ docker run -dit --name myphp -p 8080:80 myphpimage
“http://localhost:8080/"にアクセスすると、自分のIPアドレスが表示されることが確認できます。
イメージの保存と読み込み
作成したイメージは、ファイル化して取り出すことが可能です。
ファイル化するには"docker save"コマンドを、ファイルから取り出してイメージを使えるようにするには、"docker load"コマンドを使います。
それでは前章で作成したmyphpimageをもとに実践してみましょう。
まずは、myphpimageをsaved.tarというファイルとして保存します。
$ docker save -o saved.tar myphpimage
次にdocker loadをしてimageを読み込みます。(docker image rmで既存のdocker imageを削除します。)
$ docker load -i saved.tar
f16901cf4acc: Loading layer [==================================================>] 133.5MB/133.5MB
9ee3f74f729c: Loading layer [==================================================>] 3.584kB/3.584kB
Loaded image: myphpimage:latest
loadしたimageをもとにコンテナを立ち上げると、先ほどと同様にコンテンツが表示されることが確認できます。
Docker Hub
複数のユーザーに作成したimageを使ってもらうには、前章のdocker saveしてファイルを共有するというやり方よりも、Dockerレジストリに登録して、そこからdocker pullしてもらい使ってもらうというやり方が一般的です。
DockerHubで提供しているリポジトリは、無料プランの場合は1アカウントにつき1つのプライベートリポジトリしか利用できませんが、パブリックリポジトリの場合は無制限に使用することが可能です。
以前はまったところなのですが、Dockerイメージをレジストリにpushする前にDockerイメージ名をDockerHubのリポジトリ名に合わせる必要があります。例えば、リポジトリ名が「myphpimage」であれば、次のようにタグ付けをします。(ビルドする際に名前を合わせておくことが望ましいです。)
$ docker tag myphpimage 自分のDockerID/Dockerリポジトリ名
その後、docker loginした後に、docker pushでイメージを登録します。
$ docker login ## public repositoryの場合は必要ありません
$ docker push 自分のDockerID/Dockerリポジトリ名
Docker Hubの自分のページでpushされたイメージを確認することができます。
使用するときは、以下のようにdocker pull(もしくはdocker run)コマンドでローカル環境に取得することができます。
$ docker pull 自分のDockerID/Dockerリポジトリ名
参考
・Docker Desktopなしでwsl2を利用してDockerをインストール
・Docker Engineインストール(Ubuntu向け) 公式ページ
・GithubActionsを利用したDocker Imageの登録自動化
ディスカッション
コメント一覧
まだ、コメントがありません