Docker超入門~通常レベルまで Part3
1. 本記事について
本記事では、Dockerfileの書き方、マルチビルドについて、Dockerコンテナのネットワークについて、説明します。
2. Dockerfile
Dockerfileとは、自分好みにカスタムしたImageを作成するための設計書です。
dockerhubに公開されているイメージを持ってきて、コンテナを起動という流れになると思います。
ただ、開発をしていくと、既存で用意されているイメージでは自分の思い描くコンテナが作れないことが多々あります。そこで、Dockerfileを用いて、オリジナルのImageを作成するのです。
オリジナルと言っても、根本的なシステムやサービスは、dockerhubから持ってきます。
では、いくつか、Dockerfileを作ってみます。
ubuntu
From ubuntu:20.04
RUN apt update && apt upgrade
Imageを作成
docker build -t myubuntu:1 .
コンテナを起動して、コンテナ内へ
docker run -it myubuntu:1 bash
コンテナ内
root@0asdfasdv:/# ls
bin boot dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
無事にubuntuのコンテナが作成されていることがわかります。
COPYコマンド
Dockerfileがあるディレクトリで、hello.txtというファイルを作ってください。
hello.txtの中身は、helloとしてください。以下のコマンドでも作成できます。
echo "hello" >> hello.txt
COPYコマンドを使うために、Dockerfileを以下のように書き換えてみてください。
From ubuntu:20.04
RUN apt update && apt upgrade
# 追加
COPY ./hello.txt ./hello/hello.txt.
COPY コピーしたいファイルやフォルダ(コピー元) dockerコンテナ内のディレクトリ(コピー先)
では、先ほどと同じコマンドを使って、Imageを作成して、コンテナを起動
root@0asdfasdv:/# ls
bin boot dev etc hello home lib media mnt opt proc root run sbin srv sys tmp usr var
コンテナ内にhelloというフォルダが追加されていることがわかります。
COPYコマンドは、コピーすることはもちろんですが、フォルダの作成もやってくれます。
root@0asdfasdv:/# cd hello
root@0asdfasdv:/hello# ls
hello.txt
root@0asdfasdv:/hello# cat hello.txt
hello
hello/hello.txtがちゃんとコピーされていることがわかり、さらに、hello.txtの中身がhelloとなっていることも確認できます。
WORKDIR
このコマンドは簡単です。dockerコンテナを起動した時、作業ディレクトリをどこにするのかということです。例えば、以下のように記述すると
From ubuntu:20.04
RUN apt update && apt upgrade
COPY ./hello.txt ./hello/hello.txt
# 追加
WORKDIR /hello
コンテナ起動して、コンテナの中に入ると、最初から、/helloディレクトリにいます。
% docker run -it myubuntu:1 bash ✔ 00:47:40
root@567bca7c2537:/hello# ls
hello.txt
このような形で、Imageをカスタマイズすることが可能です。
3. マルチステージビルド
マルチステージビルドとは
公式ほうに以下のようにして記述されていました。
マルチステージビルドとは、Dockerfile の読みやすさやメンテナンスのしやすさを維持するよう、最適化に悩まされている全ての人に役立ちます。
https://docs.docker.jp/develop/develop-images/multistage-build.html
マルチステージビルドのメリット
イメージをビルドする際に取り組むことといえば、ほとんどがそのイメージサイズを小さく抑えることです。
https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/multistage-build/
使い方
具体的に、どのような場面で使うのか。
c言語で作成した、Hello.cというファイルをLinuxの画面で表示したい時について以下の画像を見てください。必要な処理として、
- Hello.cをコンパイラによりHello.outへコンパイルする
- Hello.outをLinuxで読み取り出力する。
これをDockerで表現すると、コンパイラ用のフェーズと、Linux用のフェーズがあります。
これをDockerfile、DockerImage、Dockerコンテナで表現すると、常にコンパイラとLinuxは存在していることになりますよね。この一連の処理に二つ必要なので。
つまり、容量も二つ分必要ということになります。
でも、よく考えてください。最後のLinuxでの処理は、Hello.outとLinuxさえあれば、良いことを。
https://docs.docker.jp/build/guide/multi-stage.html
- 構築ステップを並列に実行できるため、構築パイプラインをより速くより効率的にできる。
- プログラムを実行するために必要なものだけを含む、形跡が最小の最終イメージを作成できる。
二つ目の文章で、最小の最終イメージを作成できる。というのは、画像に当てはめて、説明するとコンパイラも作成されるが、最後の残るのは、Linuxのイメージである。ということです。つまり、最終的なイメージサイズが小さくなることを意味しています。
試してみよう
まずは、gccのみで構成してみましょう
.
├── Dockerfile
└── Hello.c
FROM gcc:12.2.0
WORKDIR /app
COPY ./Hello.c ./Hello.c
RUN gcc -o Hello.out Hello.c
CMD ["./Hello.out"]
#include <stdio.h>
void main() {
printf("Hello World!");
}
Dockerfileの説明をします。
FROM gcc:12.2.0は、DockerhubにあるDockerイメージのgccかつバージョンが12.2.0を指定しています。これが、コンパイラです。
WORKDIR /appは、コンテナ内の作業ディレクトリを/appに指定しています。以後の操作は、すべて、/appで始まります。
COPY ./Hello.c ./Hello.cは、自分のローカルPC上から、Dockerコンテナ上へ指定したファイルをコピーしています。細かく説明すると、最初の./Hello.cを自分のPCのカレントディレクトリ、二つ目の./Hello.cが、Dockerコンテナ上のカレントディレクトリとなります。ここでは、意味的には、/app/Hello.cとなります。
RUN gcc -o Hello.out Hello.c
CMD [“./Hello.out”]
これは、コンパイラの実行コマンドと、出力されたファイルを、実行するコマンドです。コンパイルは、gcc Hello.cでできますが、出力のファイルが、a.outとなってしまうので、今回は、-oオプションをつけて出力ファイル名を指定しました。CMDは、実行コマンドです。
では、Dockerfileからイメージを作成して、イメージをもとにコンテナを起動してみましょう。
% docker build -t single-stage .
または
% docker image build -t single-stage .
% docker run single-stage
または
% docker container run single-stage ✔ 09:49:53
Hello World!%
Hello World!が出力されました。
では、マルチステージをやってみましょう。
先ほど同様にディレクトリ構成を以下のようにしてください。
.
├── Dockerfile
└── Hello.c
Dockerfileは、以下のようにしてください。
FROM gcc:12.2.0
WORKDIR /app
COPY ./Hello.c ./Hello.c
RUN gcc -o Hello.out Hello.c
FROM ubuntu:20.04
WORKDIR /app
COPY --from=0 ./app/Hello.out ./Hello.out
CMD ["./Hello.out"]
基本的には、先ほどとなにも変わりませんが、違う点は、FROMが二つある点です。
これが、マルチステージビルドです。初めに実行されるのが、gccの部分です。次に実行されるのが、ubuntuの部分です。
先ほどとさほど内容は変わりませんが、一点説明すると、COPY –from=0 ./app/Hello.out ./Hello.outこちらのコードは初めて見ると思います。先ほど、COPY ローカルディレクトリ コンテナディレクトリのように説明しましたが、–from=0とは何のことでしょうか。
これは、一つ目のFROMを指しています。その後の ./app/Hello.out ./Hello.outは、コピー元からコピー先という流れは変わりません。この–fromをつけることで、コピー元の指定を変えているのです。コピー元をローカルPCではなく、gccコンテナ内から取得を指定します。
% docker build -t multi-stage .
または
% docker image build -t multi-stage .
% docker run multi-stage
または
% docker container run multi-stage
これを実行すると同様にHello World!%が出力されるはずです。
ちょっとコードが増えたけど、つまりなにが言いたいの?です。。
% docker image ls ✔ 10:02:31
REPOSITORY TAG IMAGE ID CREATED SIZE
multi-stage latest 14f92b08c99c 7 minutes ago 65.7MB
single-stage latest 9ccd0fceb6c6 26 minutes ago 1.18GB
以上のコマンドを実行してみてください。single-stageとmulti-stageで、SIZEが違うことが確認できると思います。single-stageは、マルチステージビルドをしていない場合のimageサイズです。このように、マルチステージビルドにするとイメージサイズが軽くなるのです。
コマンドの補足説明
CMDとRUNコマンドについて
大きな違いとしては、実行タイミングと実行回数です。
RUN
実行タイミング: Dockerビルド時
実行回数:何回でも
CMD
実行タイミング: コンテナ実行時のデフォルトを提供します
実行回数: CMD
命令を一度だけ指定できます。複数の CMD
がある場合、最も後ろの CMD
のみ有効です。
参考:https://docs.docker.jp/engine/reference/builder.html
おまけ
おまけといっても、詳しくは書きませんが、マルチステージビルドの並列処理とイメージの軽量化を用いて、環境を分けることができます。
例えば、本番環境と開発環境、クライアントサイドとサーバーサイドなど。
参考:https://docs.docker.jp/build/guide/multi-stage.html
4. Dockerコンテナのポートとネットワーク
PCからサーバーのアプリケーションに対してアクセスするとき、ランダムで接続されると困るので、接続の窓口を設定する必要があります。それがポートで、80番の窓口にきた方はApp1へ案内し、8080番窓口に来た方は、App2へ案内するといったようにポートというものがあります。
ホストOSとコンテナのポートを繋げる
コンテナのポートの前に一つ余談として、以下のようなことをしたことはありませんか?
自分で開発したものを起動して、localhostでブラウザから接続し、開発を進めていく。
これも実は、ポートが指定されていて、開発したものが画面上で見えている分けなんですね。
でも、この画像はまだ不完全。
ホストマシンと4000番ポートで接続しているけど、ホストマシンの中に入ったら、実は接続先がわからないんです。4000番に接続したからといってreactへの道は用意されていません。ここでもポートが必要なんです。
では、ここからコンテナを交えて説明していきます。
画像のように、コンテナをブラウザやコンテナ外部から接続する場合には、ポートを設定する必要があります。
実は、part2のDockerfileで、ポートができてきているんです😁
docker container run -p 3030:80 --name phpcontainer01 phpimage
ここで大事なのは、-p 3030:80の部分です。-pは、portオプションで紐付けを意味します。3030:80は、ホストOS側のポートが3030、コンテナ側のポートが80という意味です。
docker container run -p {ホスト側のポート}:{コンテナ側のポート} {イメージ}
Dockerfileを作成しなくても、dockerhubからイメージを指定すれば、コンテナを起動することができるので、色々コマンドを叩いてみましょう
nginx
docker container run -p 3000:80 nginx
nginxのイメージ:https://hub.docker.com/_/nginx
apache
ホストOS側のポートがかぶるのでapacheはホストOSを4000番ポートで指定します。
docker container run -p 4000:80 httpd
公式によるとhttps://hub.docker.com/_/httpd。apacheは、httpdというイメージですね。
試しに、以下のコマンドを実行してみてください。
% docker container ls
実行結果
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
10e9f02d414c httpd "httpd-foreground" 4 seconds ago Up 3 seconds 0.0.0.0:4000->80/tcp strange_gauss
f017d19d0882 nginx "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:3000->80/tcp mystifying_edison
このように実行結果が返ってくると思います。
PORTを見るとIPアドレス:ポート->ポートとなっていることがわかると思います。先ほどの図と同じように接続できました。
コンテナネットワークについて
次にコンテナのネットワークについて説明します。
dockerには、ネットワークという概念が存在し、同一ネットワーク内なら簡単に通信することができます。逆に同一ネットワークではない場合、通信することができません。設定を行えばできます。
networkと言われても何のことかわからないと思いますので、一度、コマンドを実行してみましょう。
% docker network ls
NETWORK ID NAME DRIVER SCOPE
02c9bdce3dda bridge bridge local
0f73ade7d877 docker_gwbridge bridge local
749cf2e94a82 host host local
knlv1ytebgyb ingress overlay swarm
850297abe447 none null local
docker networkについての公式ドキュメントhttps://docs.docker.jp/engine/userguide/networking/dockernetworks.html
ちなみに、ネットワークを指定しない場合は、bridgeというネットワークにグルーピングされます。
同じネットワーク上のコンテナ同士は通信が可能です。
ブリッジネットワークについて知りたい方は、公式ドキュメントを読んでみてください。
以下のコマンドを実行するとbridgeネットワークの詳細が表示されます。
docker network inspect bridge
では、bridgeネットワーク間で通信をしてみましょう。
まずは、二つのコンテナを起動します。以下のコマンドを実行してみてください。
docker run -itd --name=container1 busybox
docker run -itd --name=container2 busybox
2つのコンテナが起動したことを確認したら、以下のコマンドを実行してください。
docker network inspect bridge
以下のように、Containersという項目に、container1とcontainer2が追加されていることが確認できます。
"Containers": {
"6ec9de58b2b2abb20acbf4de226946a6f43f8fb9fea2ce4efe59d021ec9a2060": {
"Name": "container2",
"EndpointID": "431eb3cce07b4cb87b3464012d4c9b15a7cae1b7d35a248d047ef7397b39a4ca",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
},
"c8ccfa54bcf9c736b18292e67e497406079e815dedbb18f1f3ac4b7b7e9b607f": {
"Name": "container1",
"EndpointID": "99cfe88f80868ebebd6bed11cc51a78bc56b7b84a0ee7726f409b8e863f0529d",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
Container1へアタッチしてそこからpingコマンドを用いてContainer2へ接続してみると画像のように返ってきます。
docker attach container1
ping 172.17.0.3
または
ping -w3 172.17.0.3
また、以下のコマンドを実行してみてください
しっかりとContainer2への接続ができていることが確認できます
/ # cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 c8ccfa54bcf9
ネットワーク分野は知識を要するので説明はここまでとします。
興味のある方は、pingコマンドやtcp / ip、ポート、ネットワークとはなど調べてみるのもいいかもしれません。
5. 終わりに
part3はここまでになります。
次回は、いよいよ超入門〜普通編ラストpart4となります。
part4では、docker composeを使って、複数のコンテナを作成する方法について説明します。
コメントを送信