Dockerで始めるDevOps入門:コンテナ化の基礎と実践

1. はじめにとインストール

現代のマイクロサービスの世界では、Java、Python、JavaScriptなど様々な言語で構築された数百ものマイクロサービスが存在します。これらのアプリケーションにはそれぞれ異なるデプロイ環境が必要であり、デプロイプロセスもアプリケーションごとに異なる場合があります。サーバーの自動プロビジョニングとマイクロサービスの設定を行う場合、異なる設定とスクリプトが必要となり、これらの環境の維持は大きな課題となります。すべてのマイクロサービスに同じデプロイ環境とプロセスを提供する方法はあるのでしょうか?これがDockerが提供する解決策です。

任意のアプリケーションやマイクロサービスがあれば、それを含むDockerイメージを構築できます。一度Dockerイメージを構築すれば、Pythonアプリケーション用か他のプログラム用かにかかわらず、同じ方法で実行できます。Dockerイメージの実行に必要なソフトウェアは、イメージの内容に関係なく同じです。コンテナを使用すると、コードのインフラストラクチャが非常にシンプルになり、コンテナランタイムがインストールされたサーバークラスターを構成し、どの言語で書かれたプログラムであるかを気にすることなく、直接これらのサーバーにコンテナをデプロイできます。コンテナ化はDevOpsを非常に簡単にします。

Dockerは、アプリケーションのデプロイ、スケーリング、管理を自動化するためのオープンソースプラットフォームです。コンテナ化技術を通じて、アプリケーションとその依存関係を1つの独立したポータブルコンテナにパッケージ化し、異なる環境で一貫して実行できるようにします。主な利点は以下の通りです:

  • 環境の一貫性:Dockerは異なる環境でのアプリケーション実行の一貫性を確保し、問題を減らします
  • 迅速なデプロイ:Dockerは異なる環境へのアプリケーションデプロイを迅速化します
  • 継続的インテグレーションと継続的デプロイ(CI/CD):DockerはCIツールと統合し、アプリケーションのビルド、テスト、デプロイを自動化します
  • リソース利用率:Dockerは同じホスト上で複数のコンテナを実行でき、ハードウェアリソースの利用率を向上させます
  • スケーラビリティ:Dockerはアプリケーションの水平スケーリングを簡素化します
  • バージョン管理:Dockerイメージはバージョン管理をサポートし、異なるアプリケーションバージョンの管理を容易にします
  • 分離性:Dockerは良好なコンテナ分離を提供し、アプリケーション間の競合を防ぎ、システムの安定性を向上させます

ここではDockerをインストールし、いくつかの基本的なコマンドを実行して、Dockerの理解を深めます。Dockerのインストール手順については、多くのオンラインチュートリアルが利用可能なので、ここでは割愛します。

次に、非常に有用なユースケースを見てみましょう。あなたがDevOpsチームの一員で、Java、Python、JavaScriptの3つの異なる言語でAPIを構築する新しいプロジェクトに取り組んでいるとします。これらのアプリケーションの基本バージョンはすでに準備されており、できるだけ早く環境にデプロイしたいと考えています。あなたのチームメンバーが、どのようにアプリケーションを環境にデプロイするのか知りたいと思っています。二人でテーブルに座り、ターミナルを起動して、次のコマンドを入力します:

docker --version

Dockerのバージョンが表示され、準備が整いました。まずPythonアプリケーションをデプロイするために、簡単なコマンドを実行します:

docker run -p 5000:5000 sample/hello-python:1.0.0

このコマンドを実行すると、まず「unable to find image」と表示され、Dockerレジストリからイメージをプルし始めます。ダウンロードが完了すると、数秒以内にアプリケーションが起動して実行されていることがわかります。次に、ブラウザで以下のURLにアクセスします:

http://localhost:5000

プログラムが実行されていることが確認できます。数秒でプログラムをデプロイして実行できたことに、あなたのチームメンバーは驚きます。「以前は開発チームから、ハードウェア、OSの特定バージョン、ソフトウェアの特定バージョン、デプロイするアプリケーションの特定バージョンを記述したドキュメントを受け取っていました。手動で指示に従ってサーバーを作成し、OSをインストールし、Tomcatやプログラム実行に必要なツールをインストールし、JavaやPythonなどの正しい言語をインストールしてから、アプリケーションをダウンロードしてデプロイしていました。手動で環境を設定する際、多くの間違いを犯していました」と彼は言います。

あなたは彼にこう伝えます:「Dockerがあれば、アプリケーションを実行する詳細を気にする必要はありません。アプリケーションがどの言語で構成されているかを心配する必要もありません。Javaのバージョンやアプリケーションを構築するために使用されたフレームワーク、プログラムを実行するために必要なソフトウェアについても心配する必要はありません。」開発者はDockerイメージを作成するだけで、Dockerランタイムがインストールされている場所ならどこでも、同じ方法でイメージを実行できます。イメージにJava、Python、Node.jsが含まれているかどうかにかかわらず、同じ方法で実行できます。

次にJavaプログラムを実行してみましょう。まず「Ctrl+C」を使用して実行中のPythonコンテナを停止します。

停止後にウェブサイトを更新すると、アクセスできなくなります。新しいコマンドを入力します:

docker run -p 5000:5000 sample/hello-java:1.0.0

PythonをJavaに変更するだけで、他の内容を変更する必要はなく、Javaをインストールしたり、特定の要件、フレームワーク、プログラムを実行するツールをインストールしたりすることなく、Javaプログラムを迅速に起動して実行できます:

Javaアプリケーションのダウンロードには少し時間がかかります。最後の行から、このアプリケーションの起動に約6.928秒かかったことがわかります。次に、ウェブサイトを更新すると、Javaプログラムが正常に実行されていることが確認できます:

次にNode.jsプログラムをテストします。同様に「Ctrl+C」を使用して現在のJavaアプリケーションを停止します。次のコマンドを入力します:

docker run -p 5000:5000 sample/hello-nodejs:1.0.0

ブラウザで確認すると、JavaScriptプログラムが正常に実行されていることがわかります:

このように、Dockerイメージがあれば、Python、Java、JavaScriptのいずれであっても、同じDockerコマンドを使用して実行できます。マニュアルに従って環境を設定する必要はなく、プログラムを実行するために簡単なコマンドを1つ実行するだけで済みます。

2. Docker関連の概念

次に、前のコマンドを実行したときにバックグラウンドで何が起こっているかを見てみましょう。コマンドを再確認してみましょう:

docker run -p 5000:5000 sample/hello-nodejs:1.0.0

「sample/hello-nodejs:1.0.0」はDockerイメージのパスで、Docker Hubという名前のレジストリに保存されています。Docker Hubは「hub.docker.com」のURLを持つパブリックなDockerレジストリです。

Dockerレジストリには、さまざまなアプリケーションの多くのバージョンのリポジトリが含まれています。Docker Hubはパブリックなレジストリで、誰でもアクセスできます。企業で働いている場合、通常はプライベートリポジトリを使用し、アプリケーションイメージへのアクセス権を持つ人だけがアクセスできるようにします。特定のイメージがどこに保存されているか知りたい場合、パスをコピーしてウェブサイトにアクセスできます:

イメージのページにアクセスできます:

イメージのパスを指定する際には、2つのことを指定する必要があります:

  • sample/hello-nodejs:Dockerリポジトリ
  • 1.0.0:イメージバージョン

タグをクリックすると1.0.0バージョンが表示され、この特定のイメージには特定のアプリケーションを実行するために必要なすべてのもの(実行に必要なソフトウェア、ライブラリ、依存関係など)が含まれています。同じリポジトリに対して複数のタグを使用できます。図の「1.0.0」と「1.0.1」は、複数の異なるバージョンのアプリケーションに使用されています。

このコマンドを実行すると、まずローカルでイメージが見つからないと表示されます。これは、Dockerが最初にイメージがローカルに存在するかどうかを確認するためです。見つからない場合、Docker Hubに接続し、リポジトリからイメージをプルしてローカルコンピュータにダウンロードします。イメージがダウンロードされると、Dockerはその特定のイメージを実行します。実行中のイメージはコンテナと呼ばれます。イメージは静的で、リポジトリ内にあるときは単なるバイトの集合(約33MB)です。イメージがローカルマシンにダウンロードされても、単なるバイトの集合がダウンロードされるだけです。イメージが実行されているとき、それはコンテナと呼ばれます。後のレッスンで、1つのイメージに対して多くの実行中のコンテナがあることがわかります。

イメージ(image)は、コード、ランタイム、システムツール、ライブラリ、設定など、アプリケーションを実行するために必要なすべてを含む軽量で独立した実行可能ソフトウェアパッケージです。イメージは静的な定義で、ソフトウェアのインストールパッケージに似ています。コンテナ(Container)はイメージに基づいて実行されるエンティティで、イメージの実行時インスタンスです。コンテナはイメージと実行中のアプリケーションを含みます。コンテナは分離された実行環境を提供し、アプリケーションとその依存関係が異なる環境で一貫性とポータビリティを持つことを保証します。

同じイメージを使用して複数のコンテナインスタンスを起動できます。これは、同じマシンで同じアプリケーションの複数のコピーを実行でき、各コピーが独立したコンテナであることを意味します。各コンテナは相互に分離されており、独自のファイルシステム、ネットワーク空間、プロセス空間を持っています。それらは同じホスト上で実行でき、互いに影響を与えません。複数のコンテナは必要に応じて拡張または縮小でき、負荷要件に応じてコンテナの数を増減することで水平スケーリングを実現できます。

最後に、なぜ「-p 5000」を使用するのかというと、コンテナを実行するたびに、それはブリッジネットワークと呼ばれる内部ドッカーネットワークの一部になるためです。したがって、デフォルトでは、すべてのコンテナはブリッジネットワーク内で実行され、コンテナ間は相互に通信できますが、ポートが外部に公開されていない限り、コンテナにアクセスすることはできません。ここで行っているのは、コンテナのポート5000をホストのポート5000にマッピングすることです。これを実現する操作が「-p」です。コンテナ内部のポート5000をホストのポート5000にマッピングすることで、ホストのポート5000を介してコンテナ内で実行されているアプリケーションにアクセスできます。

この例では、Node.jsアプリケーションはコンテナ内のポート5000で実行されており、ホストのポート5000からアクセスしたいので、「-p 5000:5000」を使用します。前がローカルポート、後がコンテナポートです。

3. コンテナの実行

異なるバージョンのリリースが表示され、hub.docker.comはレジストリ、「sample/hello-rest-api」はリポジトリ、「1.0.0」はタグです。

では、Python、Java、JavaScriptの3つのプログラムを同時に実行したい場合、どうすればよいでしょうか?

方法は別のターミナルウィンドウを開くことですが、Node.jsがすでに5000ポートを占有しているため、実行に失敗します。

ポートを変更し、5000の代わりに5001を使用して実行する必要があります。次のコマンドは、コンテナ内の5000をホストの5001ポートにマッピングします。

docker run -p 5001:5000 sample/hello-python:1.0.0

localhost:通常、ローカルコンピュータのループバックアドレスである127.0.0.1を指します。このアドレスは、ローカルマシン上のネットワークテストと通信に使用される特別なアドレスです。アプリケーションがlocalhostでポートにバインドすると、ローカルマシンからの接続のみをリッスンします。

0.0.0.0:これは特別なアドレスで、ローカルマシンだけでなく、すべてのネットワークインターフェース上の接続をリッスンすることを示します。アプリケーションが0.0.0.0でポートにバインドすると、ローカルネットワークと外部ネットワークを含む、任意のネットワークインターフェースからの接続を受け入れます。

Dockerでは、コンテナ内のアプリケーションが0.0.0.0にバインドされている場合、コンテナ外部からの接続を受け入れることができます。したがって、Dockerで「-p 5000:5000」を使用してコンテナのポートをホストのポートにマッピングする場合、実際にはコンテナ内のアプリケーションで0.0.0.0アドレスを使用し、ホストのネットワークインターフェースを含む任意のネットワークインターフェースからの接続を受け入れるようにしています。

Pythonプログラムが5001ポートで実行されていることがわかります。同様の操作でJavaプログラムを実行できます。別のターミナルウィンドウを開きますが、5002ポートを使用します。

docker run -p 5002:5000 sample/hello-java:1.0.0

ブラウザで確認すると、Javaプログラムが実行されていることがわかります。

これで、3つの異なるポートで3つの異なるアプリケーションを実行しています。それらを迅速かつ簡単に起動でき、必要なのは3つの簡単なコマンドを実行することだけです。さらに興味深いのは、同じアプリケーションの複数のインスタンスを実行できることです。必要であれば、5003ポートで別のPythonプログラムインスタンスを起動できます。

docker run -p 5003:5000 sample/hello-python:1.0.0

ここで、Pythonプログラムが正常に実行されていることがわかります。コンテナの使用は非常に柔軟です。現在、各コンテナを起動するために別々のターミナルウィンドウを使用していますが、これには問題があります。時間が経つと、別々のウィンドウですべてを起動するのが非常に面倒になり、どのウィンドウでどのコンテナを起動したかわからなくなります。では、同じウィンドウからすべてのコンテナを起動するにはどうすればよいでしょうか。次に、この問題に取り組みます。

4. 分離モードでのコンテナ実行

次に、いくつかのウィンドウを閉じて、「Ctrl+C」でプログラムを終了します。Node.jsのウィンドウのみを残しますが、同様に「Ctrl+C」操作を実行し、現在実行中のコンテナをすべて停止します。同じウィンドウからすべてのコンテナを起動したい場合、コンテナがターミナルにバインドされているため、プログラムのすべてのログを直接ターミナルで確認できます。ここで行うことは、分離モードでコンテナを起動し、ターミナルとコンテナをバインドしないことです。これを実現するには、「-d」(detached)というオプションを追加します。

docker run -d -p 5000:5000 sample/hello-nodejs:1.0.0

これで、このコンテナのログは表示されなくなります。ブラウザを開くと、プログラムが正常に実行されていることがわかります。

これで、分離モードでコンテナを正常に起動できました。Pythonアプリケーションを起動したい場合、新しいウィンドウを開く必要はなく、既存のウィンドウで次のコマンドを直接入力します:

docker run -d -p 5001:5000 sample/hello-python:1.0.0

ブラウザを更新すると、プログラムが正常に実行されていることがわかります。

分離モードで実行する素晴らしい点は、すべてがバックグラウンドで発生し、すべてのコンテナがバックグラウンドで実行されることです。ただし、ターミナル内には以前のような実行ログが表示されません。ログを確認したい場合も非常に簡単です。コマンドの下にある文字列はコンテナのIDです。各コンテナは起動時にIDを割り当てられ、このIDを使用してコンテナのログを確認できます。

docker logs [コンテナID]

これで、2つのコンテナのログを確認できます。また、Dockerには興味深い点があり、完全なIDを入力する必要はなく、一部の部分文字列だけで十分です。

特定のアプリケーションのログを継続的に確認したい場合は、「-f」を使用して実現できます:

docker logs -f [コンテナID]

この状態でウェブページを更新すると、ログが継続的に更新されます。このとき、ログはターミナルにバインドされているため、「Ctrl+C」を実行するとログへの接続が切断されますが、プログラムは正常に実行され続けます。

5. コンテナ管理コマンド

次に、実行中のコンテナとローカルのイメージを確認するために、いくつかのコマンドを使用します。小技として、「clear」を使用してターミナル画面をクリアできます。

ターミナルで実行:

docker images

ローカルのイメージが表示されます。以前のコマンドを実行すると、イメージがローカルにダウンロードされます:

次のコマンドを実行して、実行中のコンテナを確認します。lsはlistの略です:

docker container ls

コンテナのID、コンテナを起動するために使用されたイメージ、起動に使用されたコマンド、作成時間、実行状態(upは起動して実行中を意味します)、公開されたポートが表示されます。また、コンテナの名前も表示されます。コマンドを実行したとき、コンテナにデフォルトの名前を割り当てていないため、Dockerは各コンテナにデフォルトの名前を割り当てます。

lsは現在実行中のコンテナのみを表示します。以前に停止したコンテナを含むすべてのコンテナを確認したい場合は、次のコマンドを使用します:

docker container ls -a

では、docker container lsを実行して、現在実行中のコンテナを確認してみましょう。

コンテナを停止したい場合は、どうすればよいでしょうか。ここではIDの部分文字列しか表示されていません。以前に見たIDは非常に長い文字列でした。ここで表示されているIDの部分文字列を使用するか、最初の数文字を使用して、次のコマンドを実行して最初のコンテナを停止できます:

docker container stop 238f

次に、現在実行中のコンテナを確認します:

停止されたコンテナがアクティブリストからなくなっていることがわかります。次に、対応するポートを確認します:

アクセスできない状態になっているため、コンテナを正常に停止できたことがわかります。

6. Dockerアーキテクチャ

次に、Dockerのアーキテクチャについて理解しましょう。Docker Desktopをインストールするとき、実際には2つのもの、Docker Client(クライアント)とDocker Daemon(デーモン)をインストールしています。Dockerのローカルインストールでさえ、クライアントサーバーアーキテクチャに似たものを使用します。Dockerクライアントはクライアントで、Dockerデーモンはサーバーに似ています。Dockerクライアントでコマンドを実行し、DockerクライアントはそれらをDockerデーモンに送信して実行します。

例えば、「docker run image」コマンドを実行すると、DockerクライアントはこのコマンドをDockerデーモンに送信し、Dockerデーモンはその特定のコマンドの実行を担当するデーモンです。Dockerデーモンは多くのことを担当し、コンテナの管理、ローカルイメージの管理、主にイメージリポジトリからのイメージのプルを担当します。ローカルコンピュータでDockerイメージを作成する場合、Dockerデーモンはこれらのイメージをイメージリポジトリにプッシュする責任も負います。

「docker images」を実行すると、何が起こるでしょうか?

DockerクライアントはコマンドをDockerデーモンに送信し、Dockerデーモンは存在するローカルイメージを確認し、結果を返します。これがここに表示されている結果です:

「Docker container ls」を実行する場合も同様で、Dockerクライアントでコマンドを実行し、DockerクライアントはそれをDockerデーモンに送信し、現在実行中のコンテナのリストを返します。「-a」を追加すると、Dockerデーモンはすべての状態のコンテナのリストを返します。

コンテナを実行する際も、DockerクライアントとDockerデーモンの間で多くの通信が行われます。いくつかの例を見てみましょう。以前にプログラムを実行したコマンドは次の通りです:

docker run -p 5000:5000 sample/hello-nodejs:1.0.0

このプログラムを実行すると、コンテナが起動することがわかります。これは実際にはDockerクライアントがコマンドラインをDockerデーモンに送信し、Dockerデーモンがローカルでイメージが利用可能かどうかを確認し、必要なイメージが利用可能かどうかを確認するために「Docker images」のようなアクションを実行します。対応するイメージがローカルで利用可能であることがわかるため、Dockerレジストリには行かず、既存のローカルイメージを直接使用します。しかし、実際にローカルにないアプリケーションを実行する場合、Dockerデーモンはコマンドを受け取った後、イメージがローカルに存在しないことを検出し、Dockerレジストリと通信してイメージのダウンロードを試み、その後実行を試みます。

次に、ログを通じてアプリケーションが起動したかどうかを確認してみましょう:

プログラムが正常に起動したことがわかります。次に、ウェブサイトでプログラムが正常に実行されているか確認しますが、プログラムが正常に実行されていないことがわかります。これはなぜでしょうか?

原因はログからわかります。プログラムは5001ポートではなく8080ポートで実行されています。コンテナの実行状態を確認してみましょう:

REST APIが実行中であることがわかります。このコンテナを停止し、再起動を試みます。このとき、コンテナのポートを8080に変更します:

docker run -p 5000:8080 sample/hello-nodejs:1.0.0

プログラムが正常に実行されます。したがって、コンテナを実行する際は常に、コンテナがどのポートで実行されているかを知る必要があります。また、再度docker runを実行すると、イメージが再度ダウンロードされないことがわかります。これは、イメージがローカルにすでにダウンロードされており、Dockerデーモンがイメージがローカルで利用可能であることを知っているため、Docker Hubからダウンロードせずに直接実行するためです。

7. Dockerが人気の理由

Dockerがなぜこれほど人気があるのか、その理由を見てみましょう。最初の理由は標準化されたアプリケーションパッケージング、2番目の理由はマルチプラットフォームサポート、3番目の非常に重要な理由はDockerコンテナが軽量であり、互いに分離されていることです。

ここでは、それぞれの理由について詳しく説明します。最初の理由、標準化されたアプリケーションパッケージングから始めましょう。Dockerの興味深い点は、アプリケーションパッケージングが標準化されていることです。Javaプログラムを構築する場合でも他の言語のプログラムを構築する場合でも、Dockerイメージを構築し、Dockerイメージにはアプリケーションを実行するために必要なすべてが含まれます。これにより、このイメージをどこでも実行できるようになります。このイメージに何が含まれているかを心配する必要はありません。一度イメージがあれば、どこでも実行できます。これがマルチプラットフォームサポートの役立ちます。Dockerエンジンをインストールすれば、ローカルコンピュータ、データセンター、またはクラウドでDockerイメージを実行できます。各クラウドプロバイダーは、AWS、Azure、Google Cloudなど、コンテナの実行と編成のためのKubernetes実装を提供するなど、Dockerに驚異的なサポートを提供しています。

したがって、理解しておくべき点は、Dockerイメージを構築した後は、どのタイプのプログラム向けであっても、クラウド、データセンター、またはローカルコンピュータでそのイメージを実行できるということです。

最後の理由は、Dockerコンテナが軽量であり、互いに分離されていることです。ここでの軽量とは何を意味するのでしょうか?Dockerの前は、仮想化の方法は仮想マシン(VM)を使用することでした。

上の図は典型的な仮想マシンアーキテクチャです。仮想マシンを使用すると、ハードウェア、ホストOSがあり、次に仮想化を導入するハイパーバイザーと呼ばれるものがあります。その上層に仮想マシンがインストールされ、各仮想マシンにはゲストOSがあります。したがって、仮想マシンアーキテクチャでは、ゲストOSとホストOSの2つのオペレーティングシステムがあり、仮想マシンはヘビーウェイトです。仮想化を行う際、ハードウェアの全機能を真に活用することはできません。これがDockerの役立ちます。

Dockerを使用する場合、コンテナを実行するにはDockerエンジンだけが必要です。このエンジンをインストールすれば、その上で任意のタイプのコンテナを実行できます。イメージにはコンテナを実行するために必要なすべて(アプリケーションとそれらが必要とするすべてのソフトウェア)が含まれているため、実際には他に何も必要ありません。したがって、Dockerコンテナは軽量であり、Dockerのアーキテクチャ全体が非常に効率的です。Dockerコンテナは軽量であるだけでなく、互いにも分離されています。各コンテナに特定のメモリと特定の量のCPUを割り当てることができます。例えば、コンテナに20%のCPUを割り当てると、どの時点でも20%以上のCPUを使用できなくなります。同様に、メモリにも同様の制限を設定できます。さらに、Dockerエンジンは1つのコンテナの障害が他のコンテナに影響を与えないことを保証し、1つのコンテナの問題が他のコンテナに波及せず、環境の残りの部分が安定したままになることを保証します。

タグ: Docker コンテナ DevOps 仮想化 マイクロサービス

7月30日 07:23 投稿