近年は、Dockerに代表されるコンテナ技術が、広く普及しています。インフラ構築、クラウドアプリケーション、マイクロサービスアーキテクチャ。いまやシステム開発には欠かせない技術です。
コンテナとか言われても、なんかイメージ湧かないです…
それなら実際やってみよう!
一度使えば分かる便利さ。簡単サンプルで、Dockerの実力を体験してみます。
コンテナ技術を試してみよう
コンテナ技術とは…
コンテナ技術とはなにか?よく出てくるのは、こんな説明。
コンテナ技術とは、1つのOS上で複数の独立したアプリケーションを動作させる技術です。ホストOS上にコンテナと呼ばれる仮想的な区画を作り…
なんのこっちゃ?
案ずるより産むが易し。例題で試してみましょう。
サンプル事例で考えよう
サンプルで考えるのは、HelloWorldアプリケーション。要件はこんな感じ。
- 起動すると、“Hello,World!”と表示するアプリケーション
- Linuxサーバーで動作する
- 開発言語にJavaを使用
まあ、サンプルですからね。単純にいきましょう。
Hello,Worldアプリの開発手順はつぎのとおり。
【開発サーバー作業】
- HelloWolrdを表示するJavaコードを作成する
- Java開発環境を整える
- コードをコンパイルする
【運用サーバー作業】
- コンパイルしたファイルを運用サーバーにコピーする
- 実行環境を整える
- アプリを起動する(Hello,World表示)
Dockerの活躍どころは環境構築
この開発手順で、Dockerが活躍するのは、ここ。
- Java開発環境を整える
- 実行環境を整える
いわゆる「環境構築」とよばれる作業です。こんなことをやりますね。
- 必要なアプリケーションを配置したり
- ライブラリを置いたり
- 設定を変えたり
環境構築って、手順いっぱいあったり、大変ですよね
Dockerを使うと、環境構築が、すごくラクになるよ!
HelloWorldアプリ開発(Dockerなし)
まずは、Dockerのない世界ではどうなるのか、やってみます。
まずはコードを作成
まずは開発サーバーで開発します
Hello,Wolrdを表示するJavaコードを作成しましょう。まあ普通のコードです。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Java開発環境を整える
Javaコードをコンパイルします。コンパイルにはjavacコマンドを使います。
ためしにやってみましょう。
$ javac HelloWorld.java
Command 'javac' not found, but can be installed with:
sudo apt install openjdk-11-jdk-headless # version 11.0.20.1+1-0ubuntu1~22.04, or
sudo apt install default-jdk # version 2:1.11-72build2
sudo apt install openjdk-17-jdk-headless # version 17.0.8.1+1~us1-0ubuntu1~22.04
sudo apt install openjdk-18-jdk-headless # version 18.0.2+9-2~22.04
sudo apt install openjdk-19-jdk-headless # version 19.0.2+7-0ubuntu3~22.04
sudo apt install openjdk-8-jdk-headless # version 8u382-ga-1~22.04.1
sudo apt install ecj # version 3.16.0-1
できないですね。「openjdkをインストールしてね」と言われます。
Javaコードをコンパイルするには、JDK(Java Development Kit)が必要です。Javaの開発環境をひとつにまとめたパッケージですね。
ここでは、OpenJDK17をインストールします。(JDKはほかにもいろいろありますが、使い方はどれも同じです)
$ sudo apt -y install openjdk-17-jdk
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
...
1~2分くらい待つと、インストールが終わります。
あらためて、Javaコードをコンパイルしましょう。HelloWorld.classができました。
$ javac HelloWorld.java
$ ls
HelloWorld.class HelloWorld.java
実行するときは、javaコマンドです。
$ java HelloWorld
Hello, World!
ちゃんと表示されました。HelloWorldアプリに必要なファイル、HelloWorld.classの作成完了です。
運用サーバーの実行環境を整える
つぎは運用サーバーで動かそう
HelloWorld.classを運用サーバーにコピーします。そのままでは動きません。運用サーバーにもOpenJDKをインストールしましょう。
$ sudo apt -y install openjdk-17-jdk
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
...
アプリを起動する
さあ、起動しましょう。
$ java HelloWorld
Hello, World!
無事に動きました。HelloWorldアプリ完成です。
まあ、普通の手順ですよね
つぎは、Dockerを使ってやってみるよ
HelloWorldアプリ開発(Dockerあり)
Dockerを使って、HelloWorldアプリを開発しましょう。
コードは同じ
コードは、さきほどと同じですね。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Java開発環境を整える…はいらない!
次にJava開発環境…ですが、ここでDockerです。
Dockerを使うと、開発環境のインストールが不要!
JDKをインストールする必要はありません。つぎの魔法のコマンドを実行しましょう。(コマンドの内容は後で説明します)
$ docker container run --rm -v "$PWD":/usr/src/work -w /usr/src/work openjdk:17 javac HelloWorld.java
なんか始まります。
Unable to find image 'openjdk:17' locally
17: Pulling from library/openjdk
38a980f2cc8a: Downloading [=========> ] 8.112MB/42.11MB
de849f1cfbe6: Downloading [==================================> ] 9.254MB/13.53MB
a7203ca35e75: Downloading [=====> ] 20.49MB/187.5MB
....
Digest: sha256:xxxxxxxxx....
Status: Downloaded newer image for openjdk:17
これが終わるともう、コンパイルされたHelloWolrd.classができてしまいます。
お試し実行も、魔法のコマンドを使います。
$ docker container run --rm -v "$PWD":/usr/src/work -w /usr/src/work openjdk:17 java HelloWorld
Hello, World!
なんか、環境構築しなくても動きましたね…
運用サーバに持っていくイメージを作る
運用サーバーにファイルを持っていく、のですが、HelloWorld.classファイルを持っていくわけではありません。
ここでもDockerが登場します、「HelloWorld.class+Java実行環境」をまることパッケージにしてしまいます。
まず、パッケージの内容を定義するファイルを作ります。Dockerfileという名前にします。
# OpenJDK 17をベースイメージとして使用
FROM openjdk:17
# 作業ディレクトリを設定
WORKDIR /app
# ローカルのHelloWorld.classをイメージの作業ディレクトリにコピー
COPY ./HelloWorld.class /app
# コンテナが起動した際にHelloWorldプログラムを実行
CMD ["java", "HelloWorld"]
パッケージ定義をもとに、パッケージを作ります。docker buildというコマンドが、さきほどのDockerfileを読み込んでパッケージを作ります。helloworldappという名前のパッケージになります。
$ docker build -t helloworldapp .
[+] Building 0.5s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 374B 0.0s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/openjdk:17 0.0s
=> [1/3] FROM docker.io/library/openjdk:17 0.1s
=> [internal] load build context 0.1s
=> => transferring context: 472B 0.0s
=> [2/3] WORKDIR /app 0.1s
=> [3/3] COPY ./HelloWorld.class /app 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:8b94adce1493b28eb2d641cb5e8b47afe15cf1fcdd5e6e540fd40fbbc6a4ba59 0.0s
=> => naming to docker.io/library/helloworldapp
パッケージができたら、実行してみましょう。
$ docker run helloworldapp
Hello, World!
動きましたね。
このようなパッケージを、Dockerイメージと呼びます。
作られたDockerイメージは、サーバー内部に格納されています。これを運用サーバーにもっていくために、ファイルにしましょう。
$ docker save helloworldapp | gzip > helloworldapp.tar.gz
これで、helloworldapp.tar.gzというファイルができます。
この、helloworldapp.tar.gzって、なにが入ってるんですか?
コンパイルしたファイルや、Java用のファイルが、まるごと詰まってるよ!
運用サーバーの実行環境…もいらない!
できたhelloworldapp.tar.gzを、運用サーバーに持っていきます。
運用サーバーでも、JDKをインストールする必要はありません。
Dockerを使うと、運用環境の構築も不要!
やることは、持っていたファイルを、Dockerイメージとして登録するだけ。
$ docker load < helloworldapp.tar.gz
あっというまにアプリ起動
さあ、これでもう、起動ができてしまいます。
$ docker run helloworldapp
Hello, World!
ファイル持ってきて登録しただけで、動いちゃいました!
実行する環境をまるごと持ってきた、ってとこだね
Dockerありなしの手順を比較
Dockerがある場合とない場合、比較をまとめてみましょう。
手順 | Dockerなし | Dockerあり |
---|---|---|
コード生成 | Javaコード生成 | Javaコード生成 |
Java開発環境 | OpenJDKインストール | 不要! |
コンパイル | コードコンパイル | コードコンパイル Dockerイメージ生成 |
運用サーバー実行環境 | OpenJDKインストール | 不要! |
システム起動 | javaコマンドで実行 | docker runコマンドで実行 |
Dockerを使った場合は、OpenJDKインストールという作業が全く不要、ということが分かりますね。
開発したアプリケーションを、その環境も含めて、まるごと持ち運びできる。これがDockerのメリットです。
Dockerは何してる?(1)~イメージの利用
さて、Dockerの魔法のコマンドをいろいろ使ったわけですが、実際なにをやっているのでしょうか?
コンパイルのときに使ったコマンド
まずは、コンパイルのときに使ったこのコマンド。
$ docker container run --rm -v "$PWD":/usr/src/work -w /usr/src/work openjdk:17 javac HelloWorld.java
このコマンドでは、以下4つのことを、まとめてやっています。
- Dockerイメージ(openjdk)のダウンロード
- openjdkコンテナの起動
- /usr/src/workディレクトリのマウント
- javacコマンド実行
順番に見ていきましょう。
Dockerイメージのダウンロード
まず重要なのは、openjdk:17。
$ docker container run
--rm
-v "$PWD":/usr/src/work -w /usr/src/work
openjdk:17
javac HelloWorld.java
これは「openjdkバージョン17というDockerイメージ」を指します。
Dockerイメージとは、さきほどの説明でも出てきた、まるごとパッケージのこと。実は便利なパッケージが、DockerHubと呼ばれる公式サイトで提供されています。
このコマンドは、openjdk:17という公式イメージをダウンロードして使用する、ということをしています。
いちどダウンロードしたイメージは、サーバー内部に保存されます。次からはダウンロードは発生しません。
Dockerイメージの中身ってどうなってるんですか?
必要なファイルが、ディレクトリ構成ごと入ってる、みたいな感じかな
例えばこのopenjdkイメージ、こんなファイルがディレクトリごと入っています。
/usr/bin
lrwxrwxrwx 1 root root 22 Apr 27 2022 java -> /etc/alternatives/java
lrwxrwxrwx 1 root root 23 Apr 27 2022 javac -> /etc/alternatives/javac
lrwxrwxrwx 1 root root 25 Apr 27 2022 javadoc -> /etc/alternatives/javadoc
/etc/alternatives
lrwxrwxrwx 1 root root 29 Apr 27 2022 java -> /usr/java/openjdk-17/bin/java
lrwxrwxrwx 1 root root 30 Apr 27 2022 javac -> /usr/java/openjdk-17/bin/javac
lrwxrwxrwx 1 root root 32 Apr 27 2022 javadoc -> /usr/java/openjdk-17/bin/javadoc
/usr/java/openjdk-17/bin
-rwxr-xr-x 1 root root 12368 Dec 7 2021 java
-rwxr-xr-x 1 root root 12416 Dec 7 2021 javac
-rwxr-xr-x 1 root root 12424 Dec 7 2021 javadoc
上にあげたのはほんの一部。openjdk Dockerイメージには、Javaに必要なあらゆるファイルが詰まっている、と思ってください。
なので、このイメージさえあればJavaが動かせるわけですね。
独立して存在する箱、コンテナ
このDockerイメージ、実は素材みたいなもので、まだ直接使えません。次に、Dockerイメージを実体化させる必要があります。コマンド実行で、さきほどあげた /usr/bin や、 /etc/alternative という、ディレクトリ/ファイルが実体化されます。
ここで、重要な概念です。
- もともとの開発サーバーにも、/usr/bin というディレクトリがある
- Dockerイメージにも、/usr/bin というディレクトリがある
- でも、Dockerイメージの/usr/bin の中身が、開発サーバーの/usr/bin に上書きされるわけではない
- Dockerイメージの/usr/binは、もとの開発サーバーとまったく独立して実体化される
つまり、不思議ですが、
- 開発サーバーの/usr/bin
- Dockerイメージで実体化された/usr/bin
は、お互いに干渉せず、個別に存在するのです。
この実体化したものを、コンテナと呼びます。
閉じ込められた箱の中にいるから、コンテナ!
そのまま持ち運べるっていうとこも、コンテナって感じですね
Dockerのアイコン「コンテナをのせたクジラ」も、ホストサーバー(クジラ)に乗せた独立空間(コンテナ)を表しています。
Dockerイメージを実体化することを、「コンテナを起動する」といいます。
そう、さきほどのコマンド、docker container run が、それですね。
$ docker container run
--rm
-v "$PWD":/usr/src/work -w /usr/src/work
openjdk:17
javac HelloWorld.java
開発サーバーのディレクトリをマウント
さて、独立して存在するコンテナ。でもそのままだと、何も手出しができません。そのために、次のトリックを使います。
- 開発サーバーのどこかのディレクトリを
- コンテナのどこかのディレクトリにあるかのように、見せる
それがこの部分。
$ docker container run
--rm
-v "$PWD":/usr/src/work -w /usr/src/work
openjdk:17
javac HelloWorld.java
-v [もとサーバーのディレクトリ]:[コンテナのディレクトリ]
という意味です。$PWDは、今のサーバーのカレント(現在いる)ディレクトリ。つまりこういう意味になります。
- 開発サーバーの今いるディレクトリを
- コンテナの中で/usr/src/work に配置する
これでコンテナの中では、あたかも /usr/src/work というディレクトリに、HelloWorld.java があるように見えるのです。このような、ディレクトリを結びつける操作のことを、マウントといいます、
そして、-wで、コンテナ内のカレントディレクトリを、先ほどの/usr/src/workになるように指示します。
コンテナの中でコマンド実行
さて、ここまでで、
- Java用の/usr/binなどが入ったコンテナを起動
- コンテナ内にディレクトリをマウント
までできました。あとは、このコンテナ内でコマンドを実行します。javacコマンドですね。
$ docker container run
--rm
-v "$PWD":/usr/src/work -w /usr/src/work
openjdk:17
javac HelloWorld.java
ここで実行されるコマンドからすると、コンテナ内にあるファイルやディレクトリしか見えません。つまり
- ホスト(開発サーバー)の/usr/bin は見えない
- コンテナの /usr/bin は見える
なので、コンテナ内にあるjavacコマンドが使えるのです。コンパイルが無事成功し、HelloWorld.classができます。
なお、できたファイルは、マウントされているディレクトリと同じところに出力されるので、ホスト(開発サーバー)からも見えるわけです。
Dockerは何してる?(2)~イメージの生成
途中で、Dockerfileってのも作りましたよね?
あれは、新しいDockerイメージを「作る」ための手順だね!
前段で、openjdkというDockerイメージの使い方を説明しました。
実は、Dockerイメージを自分で作ることもできます。そしてそのときは、既存のDockerイメージに、追加分をプラスして作る、というやり方をします。
つまりさきほどの手順は、
- openjdkというDockerイメージに
- HelloWorldアプリケーションを追加した
- 新たなDockerイメージを作る
ということになります。
Dockerイメージの設計書、Dockerfile
自分でDockerイメージを作るとき必要となるのが、Dockerfileです。Dockerfileは、いわばイメージの設計書。
# OpenJDK 17をベースイメージとして使用
FROM openjdk:17
# 作業ディレクトリを設定
WORKDIR /app
# ローカルのHelloWorld.classをイメージの作業ディレクトリにコピー
COPY ./HelloWorld.class /app
# コンテナが起動した際にHelloWorldプログラムを実行
CMD ["java", "HelloWorld"]
イメージを作る手順を、順番に書いていくといった感じです。順番に見てみましょう。
まず真っ先に、ベースとなるDockerイメージを指定します。ここではopenjdkですね。
# OpenJDK 17をベースイメージとして使用
FROM openjdk:17
作業ディレクトリを指定します。以降のコマンドが使うディレクトリですね。場所に決まりはありません。
# 作業ディレクトリを設定
WORKDIR /app
HelloWorldアプリケーションとして必要なclassファイルを、コンテナの中にコピーします。
COPY もとのサーバーのファイル コンテナ内のコピー先ディレクトリ という指定ですね。
# ローカルのHelloWorld.classをイメージの作業ディレクトリにコピー
COPY ./HelloWorld.class /app
最後に、コンテナが起動したときに実行されるコマンドを指定しておきます。
# コンテナが起動した際にHelloWorldプログラムを実行
CMD ["java", "HelloWorld"]
以上が、Dockerイメージを作る手順です。
オリジナルDockerイメージを作る
docker buildで、Dockerfileをもとにして、新たなDockerイメージを生成します。
$ docker build -t helloworldapp .
これで、helloworldappという、オリジナルのDockerイメージが生成できました。
実行してみましょう。コンテナ起動と同時にjavaコマンドが動きます。無事Hello,World!が表示されましたね。
$ docker run helloworldapp
Hello, World!
Dockerfile=環境構築手順
Dockerfileをみると、「ディレクトリを移動して」「コピーして」など、実際にサーバーでやっているような手順をそのまま書いているような雰囲気があります。
実際、ビルドのログを見ると、[1/3][2/3][3/3]と、手順を進めていく様子が分かります。
$ docker build -t helloworldapp .
[+] Building 0.5s (8/8) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 374B 0.0s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/openjdk:17 0.0s
=> [1/3] FROM docker.io/library/openjdk:17 0.1s
=> [internal] load build context 0.1s
=> => transferring context: 472B 0.0s
=> [2/3] WORKDIR /app 0.1s
=> [3/3] COPY ./HelloWorld.class /app 0.0s
=> exporting to image 0.1s
=> => exporting layers 0.1s
=> => writing image sha256:8b94adce1493b28eb2d641cb5e8b47afe15cf1fcdd5e6e540fd40fbbc6a4ba59 0.0s
=> => naming to docker.io/library/helloworldapp
必要な環境構築手順を一通りやった結果、それがDockerイメージ、という言い方もできるでしょう。
全部入りの、自分用Dockerイメージを作れるんですね!
コンテナ技術がもたらすメリット2つ
Dockerに代表されるコンテナ技術は、ひとことでいえば「実行環境ごとパックして持ち運べるアプリケーション」。これがもたらす大きなメリットは、この2つ。
- 環境構築手順を省力化
- クラウドアプリケーション開発を容易化
環境構築手順を省力化
実際に環境構築を行う手順は、もっと複雑。アプリケーションを配置して、ミドルウェアをインストールして、各種設定ファイルを更新して…。
手順を間違えたりすると競合が発生したり、とあるサーバーだけ動かないという不可解な現象に悩まされたり。環境構築にはいろいろなトラブルがつきものです。
環境まるごとパックして、他と独立して動作する。このコンテナの特性が、環境構築における様々な問題を回避してくれます。
必要なのは、環境構築に必要な手順をDockerfileに記載して、Dockerイメージを作ることだけ。それで動作が保証されます。
このような、構築に必要な手順をコードで表現する方法を、
と呼びます。Dockerは、IaCの有力なツールの1つ、といえるでしょう。
環境構築の手間がなくなりますね!
クラウドアプリケーション開発を容易化
環境まるごとパックして、別のサーバーに持っていけば動かせる。この特性が、クラウドにとてもよくマッチします。
つまり、クラウドのどこのサーバーに持っていっても、コンテナを動かせる。アプリケーションの冗長性や拡張性が、格段に向上します。
Amazon AWS、Microsoft Azure、Google Cloud Platformといった、主要クラウドサービスは、いずれもコンテナ実行をサポートしています。
クラウドとコンテナの組み合わせで、マイクロサービスが実現できるよ!
まとめ
Dockerなどのコンテナ技術は、いまやシステム開発に不可欠な技術となっています。しかしながら、特にオンプレミス開発に慣れた人にはなかなかイメージが掴みにくいです。
Dockerを身に着ける一番の近道は、やはり自分でやってみること。最近では仮想環境などですぐDockerを使える環境が整えられます。
手を動かして、いろいろ試してみよう!
管理人が使う参考書:
Dockerの参考書は、その特性上、インフラ技術者寄りのものが多いです。
その中でも「プログラマのためのDocker教科書」は、プログラマ向けの内容が充実しているので、管理人は愛用しています。
コンテナ技術には、さらにこの先、マルチステージビルド、コンテナオーケストレーションといった内容が控えています。アプリケーションエンジニアリングとインフラエンジニアリングの課題を、一挙両得で解決するこれらの技術。基本を押さえつつ継続的に身に着けていきましょう。
コメント