Ebook連載:『5分x10回で学ぶ 開発者のためのDocker コンテナ入門』第10章 ー Webアプリをコンテナサービスとして構築する #docker #DX #Mirantis
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
コンテナ化はDXの第一歩に最適な取り組みです。本シリーズでは10回にわたってMirantis社が発行するEbook「Learn Containers 5 Minutes at a Time An introductory guide for developers」 (Eric Gregory著)を翻訳・編集し、チャプターごとにCL LABで公開しています。本稿はその第9章です。皆さまのクラウドネイティブジャーニーやアプリケーションのモダナイズの一助になれば幸いです。
- 第1章 コンテナとは?
- 第2章 コンテナの作成・調査・削除
- 第3章 Dockerfile でのコンテナイメージのビルド
- 第4章 イメージレジストリ
- 第5章 ボリュームと永続的なストレージ
- 第6章 コンテナネットワークとコンテナのポートの開設
- 第7章 コンテナ化アプリの実行
- 第8章 ユーザ定義ネットワーク上のマルチコンテナアプリケーション
- 第9章 Docker Compose
- 第10章 Webアプリをコンテナサービスとして構築する
第10章: Webアプリをコンテナサービスとして構築する
この最終章では、Node.js・React・MySQL を使用して構築した Web アプリケーションを、コンテナサービスとしてデプロイします。 目標は、フロントエンド・バックエンド・データベースのアプリケーションをすべて各コンテナ内に構築し、最終的に Kubernetes または Docker Swarm にデプロイできるようにすることです。 コンテナ入門の学びの締めくくりとして、これまでに説明したすべての概念を活用します。
MySQL は既に馴染みがあると思います。ここではさらに別の2つのツールを利用します。
- Node.js は、リアルタイムでイベントドリブンなウェブアプリケーションを実行するために設計された JavaScript のオープンソースのサーバ再度実装です。 ここからは、単に "Node "と呼ぶことにします。Node の詳細については、次をご参照ください。 https://nodejs.org/
- React は、ユーザーインターフェースの作成を容易にするために設計された、オープンソースの JavaScript ライブラリです。Reactは、Meta社(旧Facebook社)とオープンソースコミュニティによって管理されています。React の詳細については、次をご参照ください。https://reactjs.org/
この章では、これまでに学んだすべてのことを組み合わせて独自のアプリケーションを構築します。5分より少し長くなりますが、30分以上はかからないでしょう。コーヒーでも飲みながら、さっそく始めましょう。
まずはコンテナが DNS で互いに通信できるユーザー定義の bridge ネットワークを作成することから始めましょう。このプロジェクトは本来なら他のプロジェクトを構築するためのテンプレートですが、テストアプリケーションとして使用するのでネットワークにもそれらしい名前を付けます。
docker network create -d bridge test-net
MySQL データベースを実行する永続的なストレージのボリュームも必要となります。コンテナ自体はエフェメラルなものですが、データベースの設定とアプリケーションのデータは、特定のコンテナの寿命を超えて存続させる方法の一つとしてボリュームを使用します。後ほど別の方法も紹介しますが、ここではボリュームを作成しましょう。
% docker volume create test-data
ネットワークとストレージが揃いました。このリソースを利用するためのコンテナを作成しましょう。最初のコンテナで MySQL データベースを実行します。
% docker run --name test-mysql --network=test-net -v test-data:/var/lib/mysql -d -e MYSQL_ROOT_PASSWORD=octoberfest mysql
次に順を追ってコマンドを説明します。
- test-mysql という名前のコンテナを docker run コマンドで新しく起動する
- --network の引数で、コンテナがユーザー定義の test-net ネットワークを使用することを明示する
- -v の引数で、コンテナが新しいボリュームを使用し、永続データを保存するように、ボリュームを MySQL コンテナ内のディレクトリに関連付ける
- -d の引数で、コンテナを「detached」モードで実行し、コンテナのプロセスが現在のターミナルセッションを占有しなくなるため、実行状況の確認以外の作業を可能にする
- -e の引数で、環境変数を指定しデータベースの root ユーザーのパスワード「octoberfest」を設定する
- 最後にMySQL の公式 Docker Hub イメージからビルドする
これでコンテナ化されたデータベースが稼働するようになりました。あと2つサービスの作成がありますが、どちらも Node 上でビルドします。バックエンドはシンプルな Express サーバーを使用し、フロントエンドは React ライブラリを使用します。
今回の設定では、ローカルマシン上で Visual Studio Code や Sublime Text などのコードエディタを使えるようにしたいのですが、コンテナから Node を実行したいと思います。ホストマシンにプロジェクトディレクトリを設定してみましょう。 test-demo というプロジェクト全体のディレクトリを作成することにします。このディレクトリがこれから作業するディレクトリとなります。
% mkdir test-demo
プロジェクト全体のディレクトリ test-demo の中に、バックエンドアプリケーション用のディレクトリ test-app を作成します。
% mkdir test-app
次に、npm パッケージ マネージャーを使用して、Node でプロジェクトを初期化し、コアの設定を作成し、アプリケーションに必要なパッケージをダウンロードします。 ローカルマシン上で動作する Node で行うことも可能ですが、今回はこの方法をとらず、Node コンテナから設定を実行することで、ワークフローを美しく一貫したものにします。
次のコマンドでDocker Hub にある Official Container Image を基盤にして Node コンテナを立ち上げます。
% docker run --name node-setup -it --mount type=bind,source="$(pwd)",target=/usr/src/app -w /usr/src/app –rm node bash
ここで注意しておきたいことがあります。
- --mount の引数で、コンテナのディレクトリをハードディスクに直接接続し、test-demo プロジェクトの作業ディレクトリでマウントを開始するよう指示し、そのディレクトリをコンテナファイルシステムの usr/src/app にマップするように Docker に指示する(※訳注:これが前述の「volumeに変わる永続的なデータ保存の方法」です)。
- -w の引数で、コンテナ内の作業ディレクトリを定義し、bash を実行した際そのディレクトリで実行するように設定する
コンテナ内で ls コマンドを使用すると、test-app ディレクトリが表示されるはずです。これはコピーではなく実際のディレクトリなので、ここで行ったことはすべてホストマシンに反映されます。test-app ディレクトリに移動してみましょう。
% cd test-app
Node コンテナ内にいるので、コンテナの npm のインスタンスでプロジェクトを初期化することができます。
% npm init -y
これで package.json ファイルが設定されます。バックエンドアプリケーションがデータベースに接続できるように、mysql2 ドライバもインストールする必要があります。 ここでは mysql2 ドライバを使用していることに注意してください。純粋な MySQL ドライバを使用するとエラーが発生します。
% npm install mysql2
Express Web フレームワークを使用するために Express をインストールします。Express は Node のコードの共通モジュールで、Web サーバーと API の開発を簡略化します。
% npm install express
バックエンド側で必要な設定は以上となります。全体のプロジェクトディレクトリに移動します。
% npx create-react-app test-client
npxコマンドで依存関係の解決エラーが出る場合は、事前に次のコマンドを実行してみてください。
% npm config set legacy-peer-deps true
test-client という新しいディレクトリが作成され、React ベースのフロントエンドサービス用のファイルがそのディレクトリに配置されます。
以上で初期設定は完了です。exit と入力し、コンテナのシェルセッションを終了します。ホストシステムを通してプロジェクトのディレクトリをチェックすると、作成したすべての新しいファイルが表示されます。
いよいよアプリケーションの基盤を構築します。まずはバックエンドからです。index.js ファイルをtest-app ディレクトリに作成します。
% cd test-app % touch index.js
次のようなコードをindex.jsファイルに書き込みます。
// Dependencies const mysql = require("mysql2"); const express = require("express"); // MySQL config - be careful of passwords! const db = mysql.createConnection({ host: "test-mysql", user: "root", password: "octoberfest", }); db.connect((err) => { if (err) { console.log("Error!", err); } else { dbStatus = "Connected to MySQL"; console.log(`${dbStatus}`); } });
本演習はサービスのコンテナ化についてのものとなりますので、アプリケーションのコード自体にはあまり触れませんが、ここで実際に行われていることについて、大まかに説明します。コードの最初のブロックでは、依存関係である MySQL ドライバを宣言しました。2番目のブロックでは、データベース接続と認証情報を定義しています。
注意: DNS を介して適切に解決されるので、test-mysql コンテナのホスト名を使用することができます。コンテナオーケストレータにデプロイする際に、簡単にスケールできる素晴らしいモデルです。ここでは説明のためにシンプルな運用にしていますが、実際の運用では、機密性の高いパスワードが安全を保障されていない git リポジトリで公開されていないことを確認する必要があります。そのため実際のデプロイでは、コンテナオーケストレータの Secrets 機能を使用する必要があります。
最後のブロックのコードは、データベースに接続し、すべてが正しく動作しているかどうかを確認します。接続に成功した場合は、コンソールに確認メッセージが表示されます。接続に失敗した場合は、エラーが出力されます。実際にテストして確認してみましょう。
% docker run --name test-app --network=test-net --mount type=bind,source="$(pwd)"/test-app,target=/usr/sr c/app -w /usr/src/app --rm node node index.js
このコマンド文で、再び Node イメージを基盤としたコンテナを実行します。このコンテナはtest-net ネットワーク上で実行し、前回の演習と同様にハードディスクをマウントしています。 また、--rm フラグを追加し、コンテナが停止したら自動的に削除するようにしています。そして index.jsを実行するために Node を使用しています。
コンテナ化されたアプリケーションとデータベースが接続され、すべてがうまく機能しています。しかし、このアプリケーションをもう少し発展させて、テンプレートにフロントエンドを取り込みたいと思います。別のターミナルタブで、test-app コンテナを停止します。
% docker container stop test-app
index.js に書いたコードの後に次のコードを追加して、 バックエンドにもう少しロジックを追加してみましょう。
// Express config const app = express(); const PORT = process.env.PORT || 3001; app.get("/api", (req, res) => { res.json({ message: `${dbStatus}`}); }); app.listen(PORT, () => { console.log(`Server listening on port ${PORT}`) });
Express フレームワークを依存関係として追加しました。Express を使用して、トラフィック用に 3001 番ポートをオープンし、API の基本的な部分を作成しました。データベース接続チェックの結果を JavaScript Object Notation(JSON)形式でレンダリングして、API の呼び出しに応答するようにしました。
更新を保存して、もう一度コンテナ上でバックエンドを実行してみましょう。今回は detached フラグといくつかのポートマッピングを付けます。
% docker run -d --name test-app --network=test-net -p 3001:3001 --mount type=bind,source="$(pwd)"/test-app,target=/usr/sr c/app -w /usr/src/app node node index.js
コンテナの 3001 番ポートを localhost にマッピングしているので、localhost:3001/api/にあるAPIを確認できるはずです。
JSONメッセージが表示されます。
これで2つのサービスの設定が終わりました。フロントエンドクライアントに目を向けてみましょう。
% docker run --name test-client --network=test-net -p 3000:3000 --mount type=bind,source="$(pwd)"/test-client,target=/usr /src/app -w /usr/src/app node npm start
React フロントエンドは 3000 番ポートで公開されており、localhost:3000 にマッピングしています。このため、ブラウザで確認することができます。
デフォルトのランディングページに書いてあるように、クライアントディレクトリのソースフォルダにある App.js を編集したいと思います。App.js を開いて変更してみましょう。ファイルの内容を削除して、以下のコードに置き換えます。
import React from "react"; import logo from "./logo.svg"; import "./App.css"; function App() { const [data, setData] = React.useState(null); React.useEffect(() => { fetch("/api") .then((res) => res.json()) .then((data) => setData(data.message)); }, []); return ( <div><header><img src="{logo}" alt="logo" /> {!data ? "Checking connection..." :data} </header></div> ); } export default App;
ここではあまり大きな変更はしていません。フロントエンドが APIから JSON メッセージを取得し、それをフロントページに渡す方法を追加しています。特記事項として、このコードをフロントエンドのサービス専用とするため、 API を実行していません。これに対処するために、クライアント用の package.json ファイルを開き、test-app の 3001 番ポートでプロキシを実行する行を追加します。
"proxy": "http://test-app:3001"
これですべて揃いました。もう一度、クライアントコンテナを保存して実行してみましょう。
% docker run --name test-client --network=test-net -p 3000:3000 --mount type=bind,source="$(pwd)"/test-client,target=/usr/src/app -w /usr/src/app --rm -d node npm start
3つのコンテナ化したサービスが連携しており、作成するアプリケーションの基盤の準備が整いました。これは明らかにアプリケーションの骨組みに過ぎず、質を高める為にさまざまな改良が必要です。しかし、1つだけ絶対に話しておくべき重要な効率化があります。Docker Compose です。デプロイのたびにサービス毎に多くの面倒な設定をしてデプロイしたくはないですよね。幸いなことに、Docker Compose ファイルを作成することで、全て行ってくれます。
現在のコンテナの状態をイメージに保存しておきましょう。
% docker commit test-app test-app % docker commit test-client test-client % docker commit test-mysql test-mysql
イメージをローカルに保存した状態で、現在のコンテナを停止させれば、コンテナは自動的に削除されるはずです。
% docker stop $(docker ps -a -q)
test-demo ディレクトリに、各コンテナで使用したすべての設定を含む YAML ファイルを記述します。 新しいファイルの名前を docker-compose.yml にして次の設定を行います。
services: test-app: image: test-app hostname: test-app networks: - test-net expose: - "3001" ports: - "3001:3001" volumes: - ./test-app:/usr/src/app working_dir: /usr/src/app command: node index.js test-client: image: test-client hostname: test-client networks: - test-net expose: - "3000" ports: - "3000:3000" volumes: - ./test-client:/usr/src/app working_dir: /usr/src/app command: npm start test-mysql: image: test-mysql hostname: test-mysql networks: - test-net restart: always volumes: - test-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: octoberfest volumes: test-data: external: true name: test-data networks: test-net: external: true name: test-net
ここで何が起きているか見ていきましょう。最後の2つのセクションでは、これまで使用してきたボリュームとユーザ定義ネットワークを設定しています。ここでは「external」と定義していますが、これは単にDocker Composeファイル自体の範囲外であることを意味しています。 上記では、各コンテナに対して、これまでと同じ設定をしています。 YAML ファイル内のパスワードに注意しましょう。実際のデプロイメントでは、コンテナオーケストレーターの Secrets 機能を使用し、機密情報を含む YAML ファイルを安全が保証されてないリポジトリに投稿しないように注意しましょう。プロジェクトディレクトリから次のコマンド文を実行しましょう。
% docker-compose up
シンプルなdocker-compose upコマンドで複数のアプリケーション・コンテナを立ち上げることが出来ます。これで、このアプリケーションをどのような時でも構築する準備が整いました。
注意:
- この章で使用したコードは、次のサイトで入手できます。 https://github.com/ericgregory/test-demo
- 記載されていますdocker compose V1 のコマンドは2023年6月末からサポートされません。7月以降からは、Compose V2に移行してご使用ください。 詳しくは、https://docs.docker.com/compose/compose-v2/ をご参照ください。
1章では、Dockerが唯一のコンテナシステムではないことに言及しました。Dockerは15年近く業界標準として使われてきましたが、代替エンジンやランタイムの人気が高まっており、多くの場合、独自の明確なユースケースを備えています。
- Podman は、個人開発者に適したフリーでオープンソースのコンテナエンジンを提供しています。詳しくは次のURLをご参照ください。 https://podman.io/
- Mirantis Container Runtime (MCR) は、セキュリティ要件への準拠をサポートする高度な暗号化機能、および Windows と Linux のネイティブサポートを備えたコンテナランタイムを企業に提供します。Kubernetes のコンテナランタイムとして機能することも可能です。詳しくは次の URL をご参照ください。https://www.creationline.com/mirantis/mirantis-container-runtime
- containerd はDocker Engineで使用されるオープンソースのランタイムです。独自にスピンアウトしたオープンソースのエンジンブロックで、Cloud Native Computing Foundation(CNCF)の下で管理されています。containerd は、ユーザとの直接のやり取りを最小限に抑えたコンポーネントとなるよう焦点が当てられており、多くのクラウドプラットフォームやコンテナオーケストレータで利用可能なコンテナランタイムを提供しています。詳しくはhttps://containerd.io/
幸いなことにこれらの代替ランタイムやエンジンの台頭によって、コンテナ市場が劇的に分断されることはありませんでした。2015年、Docker社.は、コンテナランタイムとイメージのオープンで標準的な仕様の確立を支援するため、Open Container Initiative(OCI)を設立しました。これによりOCIに準拠したコンテナイメージは、OCIに準拠したランタイム環境で相互運用が可能となりました。そのためランタイムに関する知識を他のランタイムに大きく移行させることができます。例えば、Podmanのホームページでは、コマンドラインエイリアスを使用して、「docker」コマンドを「Podman」で実行させることを推奨しています。それ以外は本書ですでに学んだものと同じコマンドのほとんどを使用できます。
コンテナプラットフォームは、積み木のようなイメージへのアクセスや、分離され素早いプロビジョニングが可能な環境など開発を加速させる方法を提供します。さらに、コンテナのエフェメラルな性質と効率性は、コンテナ化したサービスを統制するためのシステムなど、新しいソフトウェア展開パターンとアプリケーションを生み出しました。コンテナオーケストレータの Kubernetes や Docker Swarm などです。
Docker Composeが複雑な楽曲の作曲家のようなものだとすれば、Swarm と Kubernetes は、コンテナ化されたアプリケーションとサービスの壮大なオーケストラの指揮者のようなものです。アプリケーションをコンテナの形で提供することにより、コンテナベースのデプロイを迅速に複製でき、システムは高可用性と回復力を実現できます。
今日、私たちは異なる、しかし時には重なるユースケースによって一連のコンテナツールを選択できます。
- Docker を含むユーザ向けコンテナエンジン:コンテナの作成と実行のために設計されており、開発環境において1つのホストで単一コンテナのアプリケーションを迅速にデプロイすることに適しています。
- Docker Compose: マルチコンテナアプリのデプロイを簡略化するDockerツールで、単一ホストでの開発環境にも適しています。
- Docker Swarm: Dockerに組み込まれたコンテナオーケストレータで、本番用の複数のホストにアプリケーションを展開することに適しています。
- Kubernetes: Googleが開発したオープンソースのコンテナオーケストレータで、多数の異なるホスト、アプリケーション、またはサービスによる非常に大規模なデプロイメントに適しており、多くのノードでサービスを拡張できることから企業ユーザの間で人気が高まっています。
Docker SwarmとKubernetesでは、さらに多くの複雑なことが可能となりますが、クラウドネイティブなプロジェクトに適したツールは、ユースケースによって使い方が異なります。コンテナ化の基本を理解することで、組織が目指す方向にむけ強固な基礎を獲得することができます。本書で学んだことは、隆盛なオープンソースコミュニティとエコシステムへの第一歩となることでしょう。
※Docker Enterprise事業は2019年11月14日にMirantis社によって買収されました。
当時のDocker Enterprise製品は、現在は下記のように名称変更されています。
- 「Docker Engine」→「Mirantis Container Runtime (MCR)」
- 「エンタープライズ版Docker」→「Mirantis Kubernetes Engine (MKE)」
- 「Docker Trusted Registry」→「Mirantis Secure Registry (MSR)」
Mirantis製品に関するお問い合わせはこちら。
Mirantisについて
Mirantisは、Fortune 1000 企業の 2/3 以上にコンテナやマルチクラウドの導入を加速させ、データセンター運用のストレスを取り除く支援をしています。日本では、クリエーションラインと提携し、Kubernetes、OpenStack、その他のオープンクラウドテクノロジーを、現地語でのサポートやサービスとともに提供しています。Mirantis Kubernetes Engine(旧Docker Enterprise)および関連製品を含む、深い技術的専門知識とベンダーにとらわれない柔軟なプラットフォームが、お客様から選ばれています。
詳細は www.mirantis.com でご確認ください。