한국어 | English | 日本語
Webアプリケーションエンジニア (経験8.8年)
技術・開発
engineering
ウェブフロントエンドと バックエンド開発を扱います

Docker基本概念をサッと見てみる

サービス環境構築のたびに直面する複雑な設定やリソース無駄の問題を解決するために登場したDockerの核心的な哲学を探ります。仮想マシン(VM)より軽量なコンテナ技術が、どのようにアプリケーションのデプロイと管理を革新したのか、その構造的な違いを掘り下げて解説します。
単一ホストOS上で論理的にリソースを分配するコンテナの概念をVMと比較しながら詳細に学習します。Dockerイメージの定義からコンテナとの関係、そして実際のサービスデプロイのためのDockerfileの作成方法と主要コマンド(FROM, ENV, COPY, EXPOSEなど)を実務例を通して理解します。

Dockerをなぜ使用するのか?

1つのサーバー上で多様なアプリケーションを稼働させる場合、複数のVMを構築し、それぞれのアプリケーションにVMを割り当てる方法もあります。しかし、Dockerは各アプリケーションをVMよりも軽量なコンテナ単位でパッケージングおよび管理することを可能にします。

コンテナとは何か?

VM vs コンテナ

VMの概念は、単一のホストOS上に複数のゲストOSを置き、それぞれのアプリケーションを単一のゲストOSにマッピングするものです。

[ ホストOS - [ VM: ゲストOS - ライブラリ - アプリ ] ]

コンテナは、単一のホストOS上で複数のアプリケーションを直接稼働させることができる、VMよりも軽量な単位です。ホストOSとコンテナ間のポートフォワーディングやファイルシステム(ディレクトリ)連携などは、後述するイメージ設定によって可能です。

[ ホストOS - [ コンテナ: ライブラリ - アプリ ] ]

VMはハイパーバイザーによって物理リソースが管理されるのに対し、コンテナはDockerによって論理的にリソースが分配されます。

かつて学部時代にデモ実行のため、マルチノードHadoop構成時に使用経験のあるLXC(Linux Container)の概念がDockerの初期バージョンの実装だったとされていますが、その後Dockerは独自のコンテナを使用するようになったとのことです。

イメージとコンテナ

Dockerに初めて触れた際に明確に区別できなかった概念があります。「Image」と「Container」です。ImageはVMにおける概念と同じなので、比較的簡単に理解できるでしょう。

コンテナをなぜ使用するのか?

アプリケーション単位での管理

アプリケーション単位でのパッケージングを可能にすることで、開発時の役割と責任(R&R)を分離できます。ウェブサービスを開発する際、1つのサーバーインスタンスに多様な役割が含まれていますが、これらをそれぞれ独立したコンテナとして分離することが可能です。

つまり、上記の例のように、1つのサーバーインスタンス上で合計5つのコンテナを稼働させることができます。

もしフロントエンドに提供するAPIサーバーだけでなく、外部から直接呼び出せるAPIサーバーを追加したい場合、tomcatコンテナをもう1つ追加することで、合計2つのtomcatを1つのサーバーインスタンスに置いて使用することができます。JavaベースのtomcatをPythonベースのDjangoに置き換えることも可能です。フロントエンドを提供するnginxサーバーはそのままに、APIサーバーだけが置き換えられた形になります。

各アプリケーションをレゴブロックのように管理できることは、デプロイにおいても大きな利点があります。単一のコンテナバージョンのみを更新したい場合、そのコンテナのイメージを再度取得して再デプロイを行うだけで済みます。これにより、各コンテナごとに個別のバージョン管理が可能になります。

アプリケーションをレゴブロックのように管理できるという利点はVMも持っていますが、それよりもコンテナが好まれる理由は、仮想化のレベルが上位であるため軽量であり(コンテナ = 軽量VM)、前述のようにバージョンおよびデプロイ管理がイメージで行われるため、①イメージ設定と②デプロイが分離されており、プロセスの自動化が容易だからです。性能面でも、コンテナ間のI/Oおよびネットワーク処理において高速です。 仮想化のレベルが低いVMは、セキュリティ面でのカプセル化がコンテナよりも優れていると言われていますが、現在の技術では両者の間にどれほどの大きな違いがあるのか気になるところです。

このように、Dockerではアプリケーションが稼働する環境と稼働させるイメージを設定します。アプリケーションそれぞれの自己設定はDockerとは別にプロジェクト内部に設定しておけばよいでしょう。これは責任の分離と言えます。

Docker使用時に遭遇する用語

Docker Engine

① Image生成 および ② Container起動の両方を担当するエンジンであり、その構成は以下の通りです。

Image生成

コンテナはImageに基づいて起動されるため、希望するコンテナを起動する前に、まず希望するImageを作成する必要があります。Imageの生成から最終的なコンテナの起動までは、3つのステップで構成されます。

Dockerfileを使って、希望するImage生成に関する設定(生成ルール)を複数のコマンドで記述します。この設定に基づいてImageが生成され、生成されたImageを基に後にコンテナとして起動することになります。以下は簡単なコマンド集です。

FROM: 基本となるベースイメージを定義します。取得するイメージのURLを記述します。
ENV: イメージ内の環境変数を設定します。LinuxターミナルでのSET_VALUE=3 & echo $SET_VALUEをイメージしてください。

RUN: 実行するシェルコマンドを明記すると、イメージビルド時にそのコマンドを実行します。
CMD: 実行するシェルコマンドを明記すると、イメージビルド完了後にコンテナが正常に実行されたときにそのコマンドを実行します。

EXPOSE: 外部に公開したいポートを設定します。コンテナポートと実際のホストで公開するポートを接続します。
WORKDIR, ENTRYPOINT: RUN/CMDで指定したシェルを実行するディレクトリ位置を指定します。
ADD, COPY: ホストのディレクトリやファイルをイメージにコミットします。
VOLUME: ホストのディレクトリやファイルをイメージにコミットせず、コンテナディレクトリに接続します。

… その他のコマンドと詳細な説明は、公式Dockerドキュメントを参照してください。

docker buildコマンドを実行すると、まず作成済みのDockerfileがDocker Daemonに渡されます。その後、Dockerfileスクリプト内の各コマンドごとに実行するためのコンテナを起動し、コマンドが正常に実行されれば、そのスナップショットからイメージを生成します。以下の例で確認できるdocker buildの実行ログを見ると、DockerはDockerfile内の各コマンドが実行されるコンテナのIDと、実行が完了したコンテナのスナップショットから生成されたイメージIDの両方を返すことがわかります。

もしコマンド実行中に失敗した場合、そのコマンドが実行されているコンテナIDにシェルを通じてアクセスし、ログを確認することができます。このように、途中で返されるコンテナIDを通じてdocker buildのデバッグが可能です。つまり、Dockerfileスクリプトの最終行が実行完了したコンテナのスナップショットが、最終的に我々が生成するイメージとなるわけです。

Docker Daemonは、DockerfileのFROMコマンドで指定された、新しく生成するイメージのベースとなるイメージを取得します。

$ docker build .

Sending build context to Docker daemon 10240 bytes

Step 1/3 : FROM base-image:1.7.2
Pulling repository base-image:1.7.2
 ---> e9aa60c60128/1.000 MB (100%) endpoint: https://my-own.docker-registry.com/v1/ // [!code highlight]

個人Docker Registryであるhttps://my-own.docker-registry.com/v1/からbase-image:1.7.2イメージが取得されました。最終行のe9aa60c60128は、ダウンロードされたベースイメージにDockerが割り当てたIDです。次に実行されるコマンドは、このイメージをベースとして中間イメージを作成します。

Step 2/3 : WORKDIR /instance
 ---> Running in 9c9e81692ae9
Removing intermediate container 9c9e81692ae9
 ---> b35f4035db3f

直前に実行したFROMコマンドの結果としてe9aa60c60128中間イメージが生成されました。このイメージで新しいコンテナ9c9e81692ae9を起動し、その内部でWORKDIR /instanceコマンドを実行した後、実行完了したコンテナを削除し、そのスナップショットをb35f4035db3fイメージとして返したことがわかります。

Step 3/3 : CMD echo Hello world
 ---> Running in 02071fceb21b
Removing intermediate container 02071fceb21b
 ---> f52f38b7823e

Successfully built f52f38b7823e

私たちが得る最終イメージ名(ID)をf52f38b7823eではなく、任意の名前を付けたい場合、tagオプションを通じて名前を付けることができます。例えば、base-image:1.7.2で新しいイメージを作成したので、custom-image:1.7.2と名付けることができます。

コンテナ起動

生成された最終Imageで、Docker Daemon上でContainerを起動します。

Dockerイメージ設定の例

商品情報の保存/照会サービスを提供するため、フロントエンドサーバーはnginx(React.js)で、バックエンドサーバーはtomcat(Java)でサービスを提供しようとしています。これら2つのアプリケーションをそれぞれコンテナとして、合計2つのコンテナを1つのAWS EC2サーバーインスタンス上で稼働させます。

nginx用Dockerfileの例

まず、nginxイメージの設定を見てみましょう。nginxの起動にはシェルスクリプトを実行しますが、自作のreplace-hosts-and-run.shシェルをイメージに注入し、適切な環境変数とともに実行して、最終的にnginxサーバーを立ち上げることを目標とします。

# 1. 基本ベースイメージを取得します。フロントエンドサーバー用のnginx基本イメージをプルします。
FROM http://docker-hub.aaronryu.com/nginx:1.8.0

# 2. nginxウェブサーバーで多言語サポートのためのgettextをインストールします。
RUN apk --no-cache add gettext

# 3. 現在のプロジェクトディレクトリ内のfiles/, build/, およびシェルスクリプトを、イメージ内の指定したディレクトリに追加/コピーします。
ADD files/ /instance/program/nginx/conf
ADD build/ /instance/service/webroot/ui
ADD replace-hosts-and-run.sh /instance/program/nginx/replace-hosts-and-run.sh

# 4. 上記シェルスクリプト(replace-hosts-and-run.sh)で使用するホスト名環境変数を設定します。
ENV NGINX_HOST aaronryu.frontend.com

# 5. ロギングなどのため、nginxコンテナ内の以下のディレクトリをホストのディレクトリに接続します。
# (コンテナが以下のディレクトリに対して行う操作は、実際のホストディレクトリに反映されます。)
VOLUME ["/instance/logs/nginx"]

# 6. 「イメージ完了後」に、先ほどコピーしておいた以下のシェルスクリプトを、上記の環境変数とともに実行(CMD)します。
CMD /instance/program/nginx/replace-hosts-and-run.sh

tomcat用Dockerfileの例

nginxサーバーのSPA静的ページから照会および保存を行うには、それに対応するAPIが必要です。これらのAPIを提供するためのtomcatサーバーを起動します。Javaサーバーであるため、JVMの設定を追加し、外部からこのサーバーの状態を照会できるように12345番ポートを開放します。

# 1. 基本ベースイメージを取得します。バックエンドサーバー用のtomcat基本イメージをプルします。
FROM http://docker-hub.aaronryu.com/tomcat:8.0.0-jdk8

# 2. tomcatの実装はSpring Bootで行われています。起動時にproductionプロファイルオプションを指定します。
ENV SPRING_PROFILE production

# 3. tomcatはJavaベースのサーバーであるため、JVMメモリオプションを追加します。
ENV JVM_MEMORY -Xms2g -Xmx2g -XX:PermSize=512m -XX:MAxPermSize=512m

# 4. 現在のプロジェクトディレクトリ内に保存されているsetenv.shを、イメージ内のtomcat実行シェルファイルに追加/コピーします。
ADD setenv.sh ${CATALINA_HOME}/bin/setenv.sh

# 5. 現在のプロジェクトビルドが完了した後、生成されたすべてのwarファイルをtomcat実行のwebappsに追加/コピーします。
COPY build/libs/*.war "${CATALINA_HOME}" /webapps/ROOT.war

# 6. 設定したtomcatサーバーポート8080をホストの12345ポートに接続し、外部に公開します。
EXPOSE 8080 12345

# 7. ロギングなどのため、tomcatコンテナ内の以下のディレクトリをホストのディレクトリに接続します。
# (コンテナが以下のディレクトリに対して行う操作は、実際のホストディレクトリに反映されます。)
VOLUME ["/instance/logs/tomcat", "/instance/logs/tomcat/catalina_log", "/instance/logs/tomcat/gc"]

上記の例で見てきた各Dockerfileは、それぞれnginxとtomcatプロジェクト内に配置されます。これら2つのコンテナを1つのインスタンスに同時に起動させるには、Docker Compose設定(例:.yml)で各コンテナのイメージをまとめて明記すればよいでしょう。


Docker基本概念をサッと見てみる
Author
Aaron
Posted on
Licensed Under
CC BY-NC-SA 4.0
CC BY-NC-SA 4.0
同じカテゴリーの関連記事
最新記事
LLMフィルターが奪う会話の筋肉とコミュニケーション様式
会話における無礼さを濾過し、洗練された回答を生成するLLMツールが日常化した現代において、私たちは本当に思慮深い会話をしているのだろうか?リアルタイムのコミュニケーションにおける数多くの失敗を通じて磨かれるべき会話能力が、外部ツールに依存することで退化している現象と、それがもたらす社会的な不安や世代間の行動様式の変化について考察する。
シニア採用における年俸交渉の最適なタイミングと戦略
年俸交渉は単なる数字の交換ではなく、心理的な駆け引きとタイミングが重要です。本稿では、企業側にとって、候補者が計算的な態度を取りがちな最終合格後よりも、採用プロセスの初期段階から段階的に交渉を進めることが、なぜより効率的であり、率直な情報の共有に繋がるのかを考察します。
法治主義の限界と人間の多様性
全ての人間の行為を単一の法体系で規制できるという信念は、傲慢であるかもしれない。この記事は、中世の階層的な統制から脱却し、現代の無限の自由を手に入れた人類が直面する法治主義の逆説と、多様性という名のもとに深化する社会的強制力と他者への悪魔化現象を鋭く分析する。
토스트 예시 메세지