Dockerハンドブック
Dockerの概念や仕組みまではなんとなく理解できるもののDockerfileを書こうとするとスムーズに書けなかったり、そもそものDockerの基礎、あるいはコンテナ技術というものの基礎が抜け落ちていてDocker環境に移行できていないところも多いのではと思い、この記事を翻訳しました。
Source:The Docker Handbook by Farhan Hasin Chowdhury(@Twitter)
本記事は、原著者の許諾のもとに翻訳・掲載しております。
コンテナ化の概念自体はかなり古いですが、2013年にDocker Engineが登場したことで、アプリケーションのコンテナ化がはるかに簡単になりました。
Stack Overflow Developer Survey-2020によると、 Dockerは#1 最も望まれるプラットフォーム、#2 最も愛されるプラットフォーム、および#3 最も人気のあるプラットフォームとなりました。
必要に応じて、最初に始めるときは少し不安があるかもしれません。したがって、この記事では、コンテナ化の基本レベルから中級レベルまでのすべてを学習します。記事全体を読み終えると、次のことができるようになります。
- (ほぼ)すべてのアプリケーションのコンテナ化
- Docker HubにカスタムDockerイメージをアップロードする
- Docker Composeを使用して複数のコンテナを操作する
前提条件
- Linuxターミナルの知識
- JavaScriptの知識(続くプロジェクトの一部はJavaScriptを使用)
プロジェクトコード
サンプルプロジェクトのコードは、次のリポジトリにあります。
https://github.com/fhsinchy/docker-handbook-projects
コードの完成形はcontainerized
ブランチにあります。
目次
- コンテナ化とDockerの概要
- Dockerのインストール
- DockerでのHello World
- コンテナの操作
- コンテナ分離のデモ
- カスタムイメージの作成
- Docker Composeを使用したマルチコンテナアプリケーションの操作
- まとめ
コンテナ化とDockerの概要
コンテナ化とは、ソフトウェアコードとそのすべての依存関係を単一のパッケージ内にカプセル化して、どこでも一貫して実行できるようにするプロセスです。
Dockerはオープンソースのコンテナ化プラットフォームです。コンテナと呼ばれる隔離された環境でアプリケーションを実行する機能を提供します。
コンテナは、ハイパーバイザーを必要とせずにホストオペレーティングシステムのカーネルで直接実行できる非常に軽量な仮想マシンのようなものです。その結果、複数のコンテナを同時に実行できます。
各コンテナには、アプリケーションとその依存関係のすべてが含まれており、他の依存関係から分離されています。開発者は、これらのコンテナをレジストリを通じてイメージとして交換でき、サーバーに直接デプロイすることもできます。
仮想マシンとコンテナ
仮想マシンは、仮想CPU、メモリ、ストレージ、およびオペレーティングシステムを備えた物理コンピューターシステムと同じようなエミュレートされたものです。
ハイパーバイザーと呼ばれるプログラムは、仮想マシンを作成して実行します。ハイパーバイザーが実行されている物理コンピューターはホストシステムと呼ばれ、仮想マシンはゲストシステムと呼ばれます。
ハイパーバイザーはCPU、メモリ、およびストレージのようなリソースを簡単に既存のゲスト仮想マシン間で再割り当てすることができるプールとして扱います。
ハイパーバイザーには次の2つのタイプがあります。
- Type 1(「ネイティブ」または「ベアメタル」)ハイパーバイザ(VMware vSphere、KVM、Microsoft Hyper-V)
- Type 2(「ホスト」)ハイパーバイザ(Oracle VM VirtualBox、VMware Workstation Pro/VMware Fusion)
コンテナは、コードと依存関係を一緒にパッケージ化するアプリケーション層の抽象概念です。物理マシン全体を仮想化する代わりに、コンテナはホストオペレーティングシステムのみを仮想化します。
コンテナは、物理マシンとそのオペレーティングシステムの上に配置されます。各コンテナは、ホストオペレーティングシステムのカーネルと、通常はバイナリとライブラリも共有します。
Dockerのインストール
Docker Desktopのダウンロードページに移動し、ドロップダウンからオペレーティングシステムを選択します。
Macバージョンのインストールプロセスを示しますが、他のオペレーティングシステムへのインストールも同じように簡単なはずです。
Macのインストールプロセスには2つのステップがあります。
- ダウンロードしたDocker.dmgファイルをマウントします。
- DockerをApplicationディレクトリにドラッグアンドドロップします。
次に、アプリケーションディレクトリに移動し、ダブルクリックしてDockerを開きます。daemonが実行され、メニューバー(ウィンドウのタスクバー)にアイコンが表示されます。
このアイコンからDockerダッシュボードにアクセスできます。
現時点では少し退屈に見えるかもしれませんが、いくつかのコンテナを実行すると、これはより興味深いものになります。
DockerでのHello World
Dockerをマシンで実行する準備ができたので、次は最初のコンテナを実行します。ターミナル(Windowsのコマンドプロンプト)を開き、次のコマンドを実行します。
docker run hello-world
すべてがうまくいくと、次のような出力が表示されます。
hello-worldイメージは、Dockerでの最小限のコンテナ化の例です。ターミナルに表示されているメッセージを出力するためのhello.cファイルが1つあります。
ほとんどすべてのイメージにはデフォルトのコマンドが含まれています。hello-worldイメージの場合、デフォルトのコマンドは、前述のCコードからコンパイルされたhelloバイナリを実行します。
ダッシュボードをもう一度開くと、hello-worldコンテナが表示されています。
ステータスはEXITED(0)
で、コンテナが実行され、正常に終了したことを示しています。Logs 、 Stats(CPU/memory/disk/network usage)またはInspect(environment/port mappings)が確認できます。
何が起こったのかを理解するには、Dockerのアーキテクチャ、イメージとコンテナ、レジストリについて理解する必要があります。
Dockerアーキテクチャ
Dockerはクライアントサーバーアーキテクチャを使用します。エンジンは3つの主要コンポーネントで構成されています。
Dockerデーモン: デーモンは、クライアントが発行したコマンドをリッスンし、バックグラウンドで動作し続ける長時間実行アプリケーションです。イメージ、コンテナ、ネットワーク、ボリュームなどのDockerオブジェクトを管理できます。
Dockerクライアント: クライアントは、
docker
コマンドでアクセスできるCUIプログラムです。このクライアントはサーバーに何をすべきかを伝えます。docker run hello-world
のようなコマンドを実行すると、クライアントはデーモンにタスクを実行するように指示します。REST API: デーモンとクライアント間の通信は、UNIXソケットまたはネットワークインターフェイスを介してREST APIを使用して行われます。
Dockerの公式ドキュメントには、アーキテクチャのわかりやすい図があります。
いま複雑に見えても心配しないでください。次のサブセクションでは、すべてがより明確になります。
イメージとコンテナ
イメージは、コンテナを作成するために必要な指示を含む、多層の自己完結型ファイルです。レジストリを介してイメージを交換できます。他の人が作成したイメージを使用することも、新しい指示を追加して変更することもできます。
scratchからもイメージを作成できます。イメージのベースレイヤーは読み取り専用です。Dockerfileを編集して再構築すると、変更された部分のみが最上位レイヤーに再構築されます。
コンテナは、イメージの実行可能なインスタンスです。hello-worldのようなイメージをプルして実行すると、イメージに含まれているプログラムの実行に適した隔離された環境が作成されます。この隔離された環境がコンテナです。イメージをOOPのクラスと比較すると、コンテナがオブジェクトになります。
レジストリ
レジストリは、Dockerイメージのストレージです。Docker Hubは、イメージを保存するためのデフォルトのパブリックレジストリです。docker run
やdocker pull
などのコマンドを実行すると、デーモンは通常ハブからイメージをフェッチします。誰でもdocker push
コマンドを使用してハブに画像をアップロードできます。ハブに移動して、他のWebサイトと同様にイメージを検索できます。
アカウントを作成すると、カスタムイメージもアップロードできるようになります。アップロードした画像は、https://hub.docker.com/u/fhsinchyページで誰でも利用できます。
全体像
アーキテクチャ、イメージ、コンテナ、レジストリに精通したので、docker run hello-world
コマンドを実行したときに何が起こったかを理解する準備が整いました。プロセスの図は次のとおりです。
プロセス全体は5つのステップで行われます。
1.docker run hello-world
コマンドを実行します。
2. Dockerクライアントは、hello-worldイメージを使用してコンテナを実行することをデーモンに通知します。
3. Dockerデーモンは、最新バージョンのイメージをレジストリから取得します。
4. イメージからコンテナを作成します。
5. 新しく作成されたコンテナを実行します。
これは、ローカルに存在しないハブ内のイメージを検索するDockerデーモンのデフォルトの動作です。ただし、イメージがフェッチされると、ローカルキャッシュに残ります。したがって、コマンドを再度実行しても、下記のような出力はされません。
Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 0e03bdcc26d7: Pull complete Digest: sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9 Status: Downloaded newer image for hello-world:latest
利用可能なイメージの新しいバージョンがある場合、デーモンはイメージを再度フェッチします。その:latest
はタグです。イメージには通常、バージョンまたはビルドを示す意味のタグがあります。これについては、後のセクションで詳しく説明します。
コンテナの操作
前のセクションでは、Dockerクライアントについて簡単に触れました。Dockerデーモンにコマンドを渡すのはコマンドラインインターフェースプログラム(CUI)です。このセクションでは、Dockerでのより高度なコンテナ操作について学習します。
コンテナを実行する
前のセクションでは、docker run
を使用して、hello-worldイメージを使用するコンテナを作成および実行しました。このコマンドの一般的な構文は次のとおりです。
docker run <イメージ名>
ここでイメージ名
は、Docker Hubまたはローカルマシンからの任意のイメージを指定することができます。実行だけでなく、作成し実行と言っていることにお気づきかと思います。その背後にある理由は、docker run
コマンドが実際には2つの別個のdockerコマンドの役割を果たしているためです。それが次のとおりです。
1.docker create <イメージ名>
- 指定されたイメージからコンテナを作成し、コンテナIDを返します。
2.docker start <コンテナID>
- 既に作成されたコンテナの指定されたIDよりコンテナを起動します。
hello-worldイメージからコンテナを作成するには、次のコマンドを実行します。
docker create hello-world
コマンドはこのような長い文字列出力します。cb2d384726da40545d5a203bdb25db1a8c6e6722e5ae03a573d717cd93342f61
これがコンテナIDです。このIDは、ビルドされたコンテナを開始するために使用されます。
コンテナを識別するには、コンテナIDの最初の12文字で十分です。文字列全体を使用する代わりに、cb2d384726da
を使用するのが適切です。
このコンテナを起動するには、次のコマンドを実行します。
docker start cb2d384726da
コンテナIDを出力として返す必要があります。コンテナが適切に実行されていないと思われるかもしれません。しかし、ダッシュボードを確認すると、コンテナが実行され、正常に終了したことがわかります。
ここで起こったことは、ターミナルをコンテナの出力ストリームに接続しなかったということです。UNIXおよびLINUXコマンドは、通常、実行時に3つのI/Oストリーム、つまりSTDIN
、STDOUT
、およびSTDERR
を開きます。
詳細については、このトピックに関するすばらしい記事があります。
ターミナルをコンテナの出力ストリームに接続するには、-a
または--attach
オプションを使用する必要があります。
docker start -a cb2d384726da
すべてが正常に完了すると、次の出力が表示されます。
start
コマンドを使用して、まだ実行されていないコンテナを実行できます。run
コマンドを使用すると、毎回新しいコンテナが作成されます。
コンテナの一覧表示
前のセクションで、ダッシュボードを使用してコンテナを簡単に確認できることを覚えているかもしれません。
これは、個々のコンテナを確認するためにはかなり便利なツールですが、コンテナの単純なリストを表示するには多すぎます。これが、より簡単な方法がある理由です。ターミナルで次のコマンドを実行します。
docker ps -a
そして、ターミナル上のすべてのコンテナの一覧が表示されます。
-a
または--all
オプションは、実行中のコンテナだけでなく、停止中のコンテナも表示します。-a
オプションなしでps
を実行すると、実行中のコンテナのみが一覧表示されます。
コンテナの再起動
コンテナを実行するためにはstart
コマンドを使用しました。restart
というコンテナを開始するための別のコマンドがあります。コマンドは表面的には同じ目的を果たしているように見えますが、若干の違いがあります。
start
コマンドは実行されていないコンテナを起動します。ただし、restart
コマンドは実行中のコンテナを強制終了し、それを再度起動します。停止したコンテナでrestart
を使用すると、start
コマンドと同じように機能します。
ダングリングコンテナのクリーンアップ
すでに終了したコンテナはシステムに残ります。これらのダングリングまたは不要なコンテナはスペースを占有し、後で問題が発生する可能性さえあります。
コンテナをクリーンアップする方法はいくつかあります。特定のコンテナを削除したい場合は、rm
コマンドを使用できます。このコマンドの一般的な構文は次のとおりです。
docker rm <コンテナID>
IDがe210d4695c51
のコンテナを削除するには、次のコマンドを実行します。
docker rm e210d4695c51
そして、削除されたコンテナのIDを出力として取得する必要があります。すべてのDockerオブジェクト(イメージ、コンテナ、ネットワーク、ビルドキャッシュ)をクリーンアップする場合は、次のコマンドを使用できます。
docker system prune
Dockerは確認してきます。-f
または--force
オプションを使用して、この確認手順をスキップできます。このコマンドは、実行が正常に終了したときに削減された容量を表示します。
インタラクティブモードでのコンテナの実行
これまでのところ、hello-worldイメージから構築されたコンテナのみを実行しています。hello-worldイメージのデフォルトのコマンドは、イメージに付属する単一のhello.cプログラムを実行することです。
すべてのイメージはそれほど単純ではありません。イメージは、オペレーティングシステム全体をカプセル化できます。Ubuntu、Fedora、DebianなどのLinuxディストリビューションには、すべてDocker Hubで利用可能な公式のDockerイメージがあります。
公式のubuntuイメージを使用して、コンテナ内でUbuntuを実行できます。docker run ubuntu
コマンドを実行してUbuntuコンテナを実行しようとしても、何も起こりません。ただし、次のように-it
オプションを指定してコマンドを実行すると、
docker run -it ubuntu
Ubuntuコンテナ内のbashに直接アクセスする必要があります。このbashウィンドウでは、普段のUbuntuターミナルで実行するようなタスクを実行できます。標準のcat / etc / os-release
コマンドを実行して、OSの詳細を出力しました。
この-it
オプションが必要な理由は、Ubuntuイメージが起動時にbashを開始するように構成されているためです。bashはインタラクティブプログラムです。つまり、何のコマンドも入力しければ、bashは何もしないということです。
コンテナ内のプログラムとやりとりするには、インタラクティブセッションが必要であることをコンテナに明示的に知らせる必要があります。
-it
オプションは、コンテナ内のインタラクティブなプログラムとやりとりするための場を設定します。このオプションは、実際には2つの個別のオプションを組み合わせたものです。
*-i
オプションはコンテナの入力ストリームに接続するため、入力をbashに送信できます。
*-t
オプションは、いくつかの優れたフォーマットとネイティブターミナルのような体験を確実にします。
インタラクティブモードでコンテナを実行するときはいつでも、-it
オプションを使用する必要があります。docker run -it node
またはdocker run -it python
を実行すると、nodeまたはpythonのREPLプログラムに直接到達するはずです。
インタラクティブモードでランダムにコンテナを実行することはできません。インタラクティブモードで実行できるようにするには、起動時にインタラクティブプログラムを起動するようにコンテナを設定する必要があります。Shell、REPL、CLIなどは、いくつかのインタラクティブプログラムの例です。
実行可能イメージを使用してコンテナを作成する
これまで、Dockerイメージには、自動的に実行されるデフォルトのコマンドがあると言ってきました。すべてのイメージがそうではありません。一部のイメージは、コマンド(CMD
)ではなくエントリポイント(ENTRYPOINT
)で構成されています。
エントリポイントを使用すると、実行可能ファイルとして実行されるコンテナを構成できます。他の通常の実行可能ファイルと同様に、これらのコンテナに引数を渡すことができます。実行可能コンテナに引数を渡すための一般的な構文は次のとおりです。
docker run <イメージ名> <引数>
Ubuntuイメージは実行可能ファイルであり、イメージのエントリポイントはbashです。実行可能コンテナに渡される引数は、エントリポイントプログラムに直接渡されます。つまり、Ubuntuイメージに渡す引数はすべてbashに直接渡されます。
Ubuntuコンテナ内のすべてのディレクトリのリストを表示するには、ls
コマンドを引数として渡します。
docker run ubuntu ls
次のようなディレクトリのリストが表示されます。
-it
オプションを使用していないことに注意してください。bashとやりとりしようとするのではなく、出力が必要なだけだからです。有効なbashコマンドを引数として渡すことができます。pwd
コマンドを引数として渡すと、現在の作業ディレクトリが返されます。
有効な引数のリストは通常、エントリポイントプログラム自体によって異なります。コンテナがシェルをエントリーポイントとして使用する場合、任意の有効なシェルコマンドを引数として渡すことができます。コンテナが他のプログラムをエントリーポイントとして使用する場合、その特定のプログラムに有効な引数をコンテナに渡すことができます。
デタッチモードでコンテナを実行する
コンピューターでRedisサーバーを実行するとします。Redisは非常に高速なインメモリデータベースシステムであり、さまざまなアプリケーションでキャッシュとしてよく使用されます。公式のredisイメージを使用してRedisサーバーを実行できます。これを行うには、次のコマンドを実行します。
docker run redis
ハブからイメージを取得するのにしばらく時間がかかる場合があります。その後、ターミナルにテキストの壁が表示されるはずです。
ご覧のとおり、Redisサーバーは実行中であり、接続を受け入れる準備ができています。サーバーを実行し続けるには、このターミナルウィンドウを開いたままにする必要があります(これは私の考えでは面倒です)。
これらの種類のコンテナは、デタッチモードで実行できます。デタッチモードで実行されているコンテナは、サービスのようにバックグラウンドで実行されます。コンテナをデタッチするには、-d
または--detach
オプションを使用できます。コンテナをデタッチモードで実行するには、次のコマンドを実行します。
docker run -d redis
コンテナIDを出力として取得する必要があります。
Redisサーバーがバックグラウンドで実行されています。ダッシュボードを使用するか、ps
コマンドを使用して確認できます。
実行中のコンテナ内でコマンドを実行する
Redisサーバーがバックグラウンドで実行されたので、redis-cli
ツールを使用していくつかの操作を実行するとします。先に進んでdocker run redis redis-cli
を実行することはできません。コンテナは既に実行中です。
このような状況では、実行中のコンテナ内にexec
と呼ばれる他のコマンドを実行するコマンドがあり、このコマンドの一般的な構文は次のとおりです。
docker exec <コンテナID> <コマンド>
RedisコンテナのIDが5531133af6a1
の場合、コマンドは次のようになります。
docker exec -it 5531133af6a1 redis-cli
そして、redis-cli
プログラムに入り込みます。
これはインタラクティブセッションになるため、-it
オプションを使用していることに注意してください。このウィンドウではどの有効なRedisコマンドでも実行でき、データはサーバーに保持されます。
ctrl + c
を押すか、ターミナルウィンドウを閉じるだけで終了できます。ただし、CLIプログラムを終了しても、サーバーはバックグラウンドで実行され続けることに注意してください。
実行中のコンテナ内でシェルを起動する
なんらかの理由で、実行中のコンテナ内でシェルを使用したいとします。これは、次のコマンドのように、 実行可能であるsh
を添えてexec
コマンドを実行してできます。
docker exec -it <コンテナID> sh
redisコンテナのIDが5531133af6a1
の場合、次のコマンドを実行してコンテナ内のシェルを起動します。
docker run exec -it 5531133af6a1 sh
コンテナ内のシェルに入れるはずです。
ここで有効なシェルコマンドを実行できます。
実行中のコンテナからログにアクセスする
コンテナからのログを表示する場合は、ダッシュボードが非常に役立ちます。
また、logs
コマンドを使用して、実行中のコンテナからログを取得することもできます。コマンドの一般的な構文は次のとおりです。
docker logs <コンテナID>
RedisコンテナのIDが5531133af6a1
の場合、次のコマンドを実行してコンテナのログにアクセスします。
docker logs 5531133af6a1
ターミナルウィンドウにテキストの壁が表示されるはずです。
これはログ出力の一部にすぎません。-f
または--follow
オプションを使用すれば、コンテナの出力ストリームにフックして、リアルタイムでログを取得できます。
それ以降のログは、ctrl + c
を押すかウィンドウを閉じて終了しない限り、即座にターミナルに表示されます。ログウィンドウを終了しても、コンテナは実行を続けます。
実行中のコンテナを停止または強制終了する
ターミナルで実行中のコンテナは、ターミナルウィンドウを閉じるか、ctrl + c
を押すだけで停止できます。ただし、バックグラウンドで実行されているコンテナを同じ方法で停止することはできません。
実行中のコンテナを停止するには、2つのコマンドがあります。
*docker stop <コンテナID>
-SIGTERM
シグナルをコンテナに送信して、コンテナを正常に停止しようとします。コンテナが制限時間内に停止しない場合、SIGKILL
シグナルが送信されます。
*docker kill <コンテナID>
-SIGKILL
シグナルを送信してコンテナを即座に停止します。SIGKILL
シグナルを受け取ったら無視することはできません。
IDがbb7fadc33178
のコンテナを停止するには、docker stop bb7fadc33178
コマンドを実行します。docker kill bb7fadc33178
を使用すると、クリーンアップする機会を与えずにコンテナが即座に終了します。
ポートのマッピング
人気のWebサーバーであるNginxのインスタンスを実行するとします。これは、公式のnginxイメージを使用して行うことができます。次のコマンドを実行してコンテナを実行します。
docker run nginx
Nginxは実行し続けることを意図しているため、-d
または--detach
オプションを使用することもできます。デフォルトでは、Nginxはポート80番で実行されます。しかし、http://localhost:80
にアクセスしようとすると、次のように表示されます。
これは、Nginxがコンテナ内のポート80番で実行されているためです。コンテナは分離された環境であり、ホストシステムはコンテナ内で何が行われているのか何も知りません。
コンテナ内のポートにアクセスするには、そのポートをホストシステムのポートにマップする必要があります。これを行うには、docker run
コマンドで-p
または--port
オプションを使用します。このオプションの一般的な構文は次のとおりです。
docker run -p <ホストポート:コンテナポート> nginx
docker run -p 80:80 nginx
を実行すると、ホストマシンのポート80番がコンテナのポート80番にマッピングされます。次に、http://localhost:80
アドレスにアクセスしてみます。
80:80
の代わりにdocker run -p 8080:80 nginx
を実行すると、ホストマシンのポート8080番でNginxサーバーが利用可能になります。しばらくしてポート番号を忘れた場合は、ダッシュボードを使用して確認できます。
Inspectタブには、ポートマッピングに関する情報が含まれています。ご覧のとおり、ポート80番をコンテナからホストシステムのポート8080番にマッピングしました。
コンテナ分離のデモ
コンテナの概念を紹介したときから、コンテナは分離された環境であると言ってきました。隔離されているとは、ホストシステムだけでなく、他のコンテナからも意味します。
このセクションでは、この分離について理解するために少し実験を行います。2つのターミナルウィンドウを開き、次のコマンドを使用して2つのUbuntuコンテナインスタンスを実行します。
docker run -it ubuntu
ダッシュボードを開くと、2つのUbuntuコンテナが実行されていることがわかります。
上のウィンドウで、次のコマンドを実行します。
mkdir hello-world
mkdir
コマンドは新しいディレクトリを作成します。両方のコンテナのディレクトリのリストを表示するには、両方のコンテナ内でls
コマンドを実行します。
ご覧のように、hello-world
ディレクトリは、下部のウィンドウではなく、上部のターミナルウィンドウで開いているコンテナ内に存在します。同じイメージから作成されたコンテナが互いに分離されていることを証明しています。
これは理解しておくべき重要なことです。しばらくコンテナ内で作業しているシナリオを想定します。次に、コンテナを「停止」し、翌日にもう一度docker run -it ubuntu
を実行します。行った動作すべてが消失したことがわかります。
前のサブセクションから覚えていると思いますが、run
コマンドは毎回新しいコンテナを作成して開始します。そのため、run
コマンドではなく、start
コマンドを使用して、以前に作成したコンテナを起動することを忘れないでください。
カスタムイメージの作成
Dockerクライアントを使用してコンテナを操作する多くの方法をしっかり理解したところで、今度はカスタムイメージの作成方法を学習します。
このセクションでは、イメージの構築、イメージからのコンテナの作成、および他のユーザーとの共有に関する多くの重要な概念を学びます。
以降のサブセクションに進む前にVisual Studio Codeと共に公式のDocker Extensionをインストールすることをお勧めします。
イメージ作成の基本
このサブセクションでは、Dockerfileの構造と一般的な手順に焦点を当てます。Dockerfileはテキストドキュメントであり、Dockerデーモンがイメージを作成してビルドするための一連の指示が含まれています。
イメージ作成の基本を理解するために、非常にシンプルなカスタムNodeイメージを作成します。始める前に、公式のnodeイメージがどのように機能するかを紹介します。次のコマンドを実行してコンテナを実行します。
docker run -it node
Nodeイメージは、起動時にNode REPLを開始するように構成されています。REPLはインタラクティブプログラムなので、-it
フラグを使用します。
ここで有効なJavaScriptコードを実行できます。そのように機能するカスタムNodeイメージを作成します。
まず、コンピュータの任意の場所に新しいディレクトリを作成し、その中にDockerfile
という名前の新しいファイルを作成します。コードエディター内でプロジェクトフォルダを開き、次のコードをDockerfile
に配置します。
FROM ubuntu RUN apt-get update RUN apt-get install nodejs -y CMD [ "node" ]
前のサブセクションから、イメージに複数のレイヤーがあることを思い出してください。Dockerfile
の各行は命令であり、各命令は新しいレイヤーを作成します。
Dockerfile
を1行ずつ詳しく説明します。
FROM ubuntu
すべての有効なDockerfile
は、FROM
命令で始まる必要があります。この命令は、新しいビルドステージを開始し、ベースイメージを設定します。ubuntu
をベースイメージとして設定することにより、Ubuntuイメージのすべての機能をイメージ内で使用できるようにする必要があります。
イメージでUbuntu機能を使用できるようになったので、Ubuntuパッケージマネージャー(apt-get
)を使用してNodeをインストールできます。
RUN apt-get update RUN apt-get install nodejs -y
RUN
命令は、現在のイメージ上の新しいレイヤーでコマンドを実行し、結果を保持します。したがって、次の手順では、Nodeを参照できます。これはこの手順でNodeをインストールしたためです。
CMD [ "node" ]
CMD
命令の目的は、実行中のコンテナにデフォルトを提供することです。これらのデフォルトには実行可能ファイルを含めることも、実行可能ファイルを省略することもできます。その場合は、ENTRYPOINT
命令を指定する必要があります。Dockerfileに含めることができるCMD
命令は1つだけです。また、シングルクォーテーションは無効です。
このDockerfile
からイメージをビルドするために、build
コマンドを使用します。コマンドの一般的な構文は次のとおりです。
docker build <ビルドコンテキスト>
build
コマンドにはDockerfileとビルドコンテキストが必要です。コンテキストは、指定された場所にあるファイルとディレクトリのセットです。Dockerはコンテキスト内でDockerfile
を探し、それを使用してイメージを構築します。
そのディレクトリ内のターミナルウィンドウを開き、次のコマンドを実行します。
docker build .
現在のディレクトリを意味するビルドコンテキストとして.
を渡しています。/src/Dockerfile
のような別のディレクトリ内にDockerfile
を置くと、コンテキストは./src
になります。
ビルドプロセスが完了するまでに時間がかかる場合があります。完了すると、ターミナルにテキストの壁が表示されます。
すべてがうまくいくと、最後にSuccessfully built d901e4d15263
のようなものが表示されます。このランダムな文字列はイメージIDであり、コンテナIDではありません。このイメージIDでrun
コマンドを実行して、新しいコンテナを作成して立ち上げることができます。
docker run -it d901e4d15263
Node REPLはインタラクティブプログラムであるため、-it
オプションが必要であることを忘れないでください。コマンドを実行したらNode REPLにアクセスする必要があります。
ここでは、公式のNodeイメージと同じように、任意の有効なJavaScriptコードを実行できます。
実行可能イメージを作成する
前のサブセクションの実行可能イメージの概念を思い出してください。イメージは通常の実行可能ファイルと同じように追加の引数を取ることができます。このサブセクションでは、作成方法を学習します。
カスタムbashイメージを作成し、前のサブセクションでUbuntuイメージで行ったように引数を渡します。空のディレクトリ内にDockerfile
を作成することから始め、次のコードをその中に配置します。
FROM alpine RUN apk add --update bash ENTRYPOINT [ "bash" ]
alpineイメージをベースとして使用しています。Alpine Linuxは、セキュリティ指向で軽量なLinuxディストリビューションです。
Alpineにはデフォルトでbashが付属していません。したがって、2行目では、Alpineパッケージマネージャーであるapk
を使用してbashをインストールします。Alpineのapk
はUbuntuでいうapt-get
です。最後の命令は、このイメージのエントリポイントとしてbash
を設定します。ご覧のとおり、ENTRYPOINT
命令はCMD
命令と同じです。
イメージをビルドするには、次のコマンドを実行します。
docker build .
ビルドプロセスには時間がかかる場合があります。完了したら、新しく作成されたイメージIDを取得する必要があります。
作成されたイメージからコンテナを実行するには、run
コマンドを使用します。このイメージにはインタラクティブなエントリポイントがあるため、必ず-it
オプションを使用してください。
これで、Ubuntuコンテナで行ったように、このコンテナに任意の引数を渡すことができます。すべてのファイルとディレクトリのリストを表示するには、次のコマンドを実行できます。
docker run 66e867a1504d -c ls
-c ls
オプションはbashに直接渡され、コンテナ内のディレクトリのリストを返す必要があります。
-c
オプションはDockerクライアントとは何の関係もありません。これはbashコマンドラインオプションです。後続の文字列からコマンドを読み取ります。
Expressアプリケーションのコンテナ化
これまでは追加のファイルを含まないイメージのみを作成しました。このサブセクションでは、ソースファイルを含むプロジェクトをコンテナ化する方法を学習します。
プロジェクトコードリポジトリのクローンを作成した場合は、express-api
ディレクトリ内に移動します。これは、ポート3000番で実行され、ヒットすると単純なJSONペイロードを返すREST APIです。
このアプリケーションを実行するには、次の手順を実行する必要があります。
npm install
を実行して、必要な依存関係をインストールします。npm run start
を実行してアプリケーションを起動します。
Dockerfileの手順を使用して上記のプロセスを複製するには、次の手順を実行する必要があります。
- Nodeアプリケーションを実行できるベースイメージを使用します。
package.json
ファイルをコピーし、npm run install
を実行して依存関係をインストールします。- 必要なプロジェクトファイルをすべてコピーします。
npm run start
を実行してアプリケーションを起動します。
次に、プロジェクトディレクトリ内に新しいDockerfile
を作成し、次の内容をファイル中に配置します。
FROM node WORKDIR /usr/app COPY ./package.json ./ RUN npm install COPY . . CMD [ "npm", "run", "start" ]
ベースイメージとしてNodeを使用しています。WORKDIR
は、WORKDIR
のあとのRUN
、CMD
、ENTRYPOINT
、COPY
、およびADD
のすべての命令の作業ディレクトリを設定します。これは、ディレクトリにcd
するようなものです。
COPY
は./package.json
を作業ディレクトリにコピーします。前の行で作業ディレクトリを設定したので、.
はコンテナ内の/usr/app
を参照します。package.json
がコピーされたら、RUN
を使用して必要な依存関係をすべてインストールします。
CMD
では、npm
を実行可能ファイルとして設定し、run
とstart
を引数として渡します。命令はコンテナ内でnpm run start
として解釈されます。
ここで、docker build .
を使用してイメージをビルドし、結果としてのイメージIDを使用して新しいコンテナを実行します。アプリケーションはコンテナ内のポート3000番で実行されるため、マッピングすることを忘れないでください。
コンテナが正常に実行されたら、http://127.0.0.1:3000
にアクセスすると、シンプルなJSONがレスポンスとして表示されます。ホストシステムから他のポートを使用した場合は、3000を書き換えてください。
ボリュームの操作
このサブセクションでは、非常に一般的なシナリオを紹介します。ReactまたはVueを使用して、モダンなフロントエンドアプリケーションで作業していると想定します。プロジェクトコードリポジトリをクローンしたら、vite-counter
ディレクトリ内に移動します。これは、npm init vite-app
コマンドで初期化されたシンプルなVueアプリケーションです。
このアプリケーションを開発モードで実行するには、次の手順を実行する必要があります。
npm install
を実行して、必要な依存関係をインストールします。npm run dev
を実行して、アプリケーションを開発モードで起動します。
Dockerfileの手順を使用して上記のプロセスを複製するには、次の手順を実行する必要があります。
- Nodeアプリケーションを実行できるベースイメージを使用します。
package.json
ファイルをコピーし、npm run install
を実行して依存関係をインストールします。- 必要なプロジェクトファイルをすべてコピーします。
npm run dev
を実行して、アプリケーションを開発モードで起動します。
そこに新しくDockerfile.dev
を作成し、以下の内容を記述します。
FROM node WORKDIR /usr/app COPY ./package.json ./ RUN npm install COPY . . CMD [ "npm", "run", "dev" ]
空想ではなく、実際にpackage.json
ファイルをコピーし、依存関係をインストールして、プロジェクトファイルをコピーし、npm run dev
を実行して開発サーバーを起動しました。
次のコマンドを実行してイメージをビルドします。
docker build -f Dockerfile.dev .
Dockerは、ビルドのコンテキスト内でDockerfile
を探すようにプログラムされています。しかし、ファイルにDockerfile.dev
という名前を付けたので、-f
または--file
オプションを使用して、Dockerにファイル名を知らせる必要があります。最後の.
は、以前と同様にコンテキストを示します。
開発サーバーはコンテナ内のポート3000番で実行されるため、コンテナを作成して起動するときにポートをマップするようにしてください。私の方ではhttp://127.0.0.1:3000
にアクセスして、アプリケーションにアクセスできます。
これは、新しいViteアプリケーションに付属するデフォルトのコンポーネントです。ボタンを押してカウントを増やすことができます。
すべての主要なフロントエンドフレームワークには、ホットリロード機能が付属しています。開発サーバーで実行中にコードに変更を加えた場合、変更はブラウザにすぐに反映されますが、一足先にこのプロジェクトのコードに変更を加えようとしても、ブラウザには何の変更も表示されません。
まあ、その理由はかなり簡単です。コードを変更する場合は、コンテナ内のコピーではなく、ホストシステムのコードを変更します。
この問題の解決策があります。コンテナ内にソースコードのコピーを作成する代わりに、コンテナにホストから直接ファイルにアクセスさせることができます。
そうするために、Dockerには、run
コマンド用の-v
または--volume
というオプションがあります。volume
オプションの一般的な構文は次のとおりです。
docker run -v <ホストディレクトリへの絶対パス>:<コンテナの作業ディレクトリへの絶対パス> <イメージID>
pwd
シェルコマンドを使用して、現在のディレクトリの絶対パスを取得できます。私のホストディレクトリパスは/Users/farhan/repos/docker/docker-handbook-projects/vite-counter
、コンテナアプリケーションの作業ディレクトリパスは/usr/app
、イメージIDは8b632faffb17
です。だから私のコマンドは次のようになります。
docker run -p 3000:3000 -v /Users/farhan/repos/docker/docker-handbook-projects/vite-counter:/usr/app 8b632faffb17
上記のコマンドを実行すると、sh: 1: vite: not found
というエラーが表示され、依存関係がコンテナ内に存在しないことを意味します。
このようなエラーが発生しないようにするには、ホストシステムに依存関係がインストールされている必要があります。ローカルシステムのnode_modules
フォルダを削除して、再試行してください。
しかし、Dockerfile.dev
の4行目を見ると、RUN npm install
が明確に記述されています。
これがなぜ起こっているのかを説明しましょう。ボリュームを使用する場合、コンテナはホストシステムから直接ソースコードにアクセスします。ご存じのとおり、ホストシステムには依存関係をインストールしていません。
依存関係をインストールすると問題を解決できますが、まったく理想的ではありません。一部の依存関係は、インストールするたびにソースからコンパイルされるためです。また、OSとしてWindowsまたはMacを使用している場合、OS用にビルドされたバイナリは、Linuxを実行しているコンテナ内では機能しません。
この問題を解決するには、Dockerが持っている2種類のボリュームについて知っておく必要があります。
- 名前付きボリューム:これらのボリュームには、コンテナ外部からの特定のソースがあります。たとえば、
-v ($PWD):/usr/app
です。 - 匿名ボリューム:これらのボリュームには特定のソースがありません(例:
-v/usr/app/node_modules
)。コンテナが削除されると、匿名ボリュームは手動でクリーンアップするまで残ります。
node_modules
ディレクトリが上書きされないようにするには、匿名ボリューム内に配置する必要があります。これを行うには、前のコマンドを次のように変更します。
docker run -p 3000:3000 -v /usr/app/node_modules -v /Users/farhan/repos/docker/docker-handbook-projects/vite-counter:/usr/app 8b632faffb17
ここでは、新しい匿名ボリュームの追加のみを変更しました。ここでコマンドを実行すると、アプリケーションが実行されていることがわかります。何か変更したら、ブラウザですぐに変更を確認できます。デフォルトのヘッダーを少し変更しました。
このコマンドは、繰り返し実行するには少し長すぎます。長いソースディレクトリの絶対パスの代わりにシェルコマンドで代用できます。
docker run -p 3000:3000 -v /usr/app/node_modules -v $(pwd):/usr/app 8b632faffb17
$(pwd)
ビットは、現在のディレクトリへの絶対パスに置き換えられます。そのため、プロジェクトフォルダ内のターミナルウィンドウが開いていることを確認してください。
マルチステージビルド
Docker v17.05で導入されたマルチステージビルドは素晴らしい機能です。このサブセクションでは、vite-counter
アプリケーションを再び使用します。
前のサブセクションでは、明確に開発サーバーを実行するためのDockerfile.dev
ファイルを作成しました。VueまたはReactアプリケーションのプロダクションビルドを作成することは、多段階ビルドプロセスの完璧な例です。
最初に、次の図でプロダクションビルドがどのように機能するかを示します。
図からわかるように、ビルドプロセスには2つのステップまたはステージがあります。それらは次のとおりです。
npm run build
を実行すると、アプリケーションがひとくくりのJavaScript、CSS、およびindex.html
ファイルにコンパイルされます。プロダクションビルドは、プロジェクトルートの/dist
ディレクトリ内で利用できます。ただし、開発バージョンとは異なり、プロダクションビルドには手の込んだサーバーが付属していません。- プロダクションファイルでサーバーを立ち上げるには、Nginxを使用する必要があります。ステージ1でビルドされたファイルをNginxのデフォルトのドキュメントルートにコピーし、利用可能にします。
前の2つのプロジェクトで行ったような手順を確認したい場合は、次のようになります。
- Nodeアプリケーションを実行できるベースイメージ(node)を使用します。
package.json
ファイルをコピーし、npm run install
を実行して依存関係をインストールします。- 必要なプロジェクトファイルをすべてコピーします。
npm run build
を実行してプロダクションビルドを作成します。- 本番ファイルの実行を可能にする別のベースイメージ(nginx)を使用します。
- 本番ファイルを
/dist
ディレクトリからデフォルトのドキュメントルート(/usr/share/nginx/html
)にコピーします。
さあ、作業に取り掛かりましょう。vite-counter
プロジェクトディレクトリ内に新しいDockerfile
を作成します。Dockerfile
の内容は次のとおりです。
FROM node as builder WORKDIR /usr/app COPY ./package.json ./ RUN npm install COPY . . RUN npm run build FROM nginx COPY --from=builder /usr/app/dist /usr/share/nginx/html
まず複数のFROM
があることに気づいたかもしれません。マルチステージビルドプロセスでは、複数のFROM
を使用することができます。最初のFROM
は、node
をベースイメージとして設定し、依存関係をインストールし、すべてのプロジェクトファイルをコピーして、npm run build
を実行します。最初のステージをbuilder
と命名します。
次に、第2段階では、ベースイメージとしてnginx
を使用します。第1ステージで構築された/usr/app/dist
ディレクトリから第2ステージのusr/share/nginx/html
ディレクトリにすべてのファイルをコピーします。COPY
の--from
オプションを使用すると、ステージ間でファイルをコピーできます。
イメージをビルドするには、次のコマンドを実行します。
docker build .
今回はDockerfile
という名前のファイルを使用しているため、ファイル名を明示的に宣言する必要はありません。ビルドプロセスが完了したら、イメージIDを使用して新しいコンテナを実行します。Nginxはデフォルトでポート80番で実行されるため、マッピングすることを忘れないでください。
コンテナを正常に起動したら、http://127.0.0.1:80
にアクセスすると、カウンターアプリが実行されているのがわかります。ホストシステムから他のポートを使用した場合は、80を書き換えてください。
このマルチステージビルドプロセスからの出力イメージは、ビルドされたファイルのみを含み、追加のデータを含まないNginxベースのイメージです。その結果、サイズが最適化され、軽量になっています。
ビルドされたイメージをDocker Hubにアップロードする
あなたはすでにかなり多くの画像を構築しました。このサブセクションでは、画像のタグ付けとDocker Hubへのアップロードについて学びます。先に進んで、無料のアカウントDocker Hubにサインアップしてください。
アカウントを作成したら、Dockerメニューを使用してログインします。
または、ターミナルからコマンドを使用してログインできます。コマンドの一般的な構文は次のとおりです。
docker login -u <DockerのID> --password <Dockerのパスワード>
ログインが成功すると、ターミナルにLogin Succeeded
のようなものが表示されます。
これでイメージをアップロードする準備が整いました。イメージをアップロードするには、まずそれらにタグを付ける必要があります。プロジェクトコードリポジトリのクローンを作成した場合は、vite-counter
プロジェクトフォルダー内のターミナルウィンドウを開きます。
build
コマンドで-t
または--tag
オプションを使用してイメージにタグを付けることができます。このオプションの一般的な構文は次のとおりです。
docker build -t <タグ> <ビルドのコンテキスト>
タグの一般的な規則は次のとおりです。
<DockerのID>/<イメージ名>:<イメージのバージョン>
私のDocker IDはfhsinchy
なので、画像にvite-counter
という名前を付けたい場合、コマンドは次のようになります。
docker build -t fhsinchy/vite-controller:1.0 .
コロンの後にバージョンを定義しない場合、latest
が自動的に使用されます。すべてがうまくいけば、ターミナルにSuccessfully tagged fhsinchy/vite-controller:1.0
のようなものが表示されるはずです。私の場合、バージョンを定義していません。
このイメージをHubにアップロードするには、push
コマンドを使用できます。コマンドの一般的な構文は次のとおりです。
docker push <DockerのID>/<イメージタグとバージョン>
fhsinchy/vite-counter
イメージをアップロードするには、コマンドは次のようにする必要があります。
docker push fhsinchy/vite-counter
プッシュが完了すると、次のようなテキストが表示されます。
Hubのイメージは誰でも見ることができます。
このイメージからコンテナを実行するための一般的な構文は次のとおりです。
docker run <DockerのID>/<イメージタグとバージョン>
このアップロードされたイメージを使用してvite-counter
アプリケーションを実行するには、次のコマンドを実行します。
docker run -p 80:80 fhsinchy/vite-counter
そして、あなたはvite-counter
アプリケーションが以前と同じように実行されているのを見るはずです。
任意のアプリケーションをコンテナ化し、Docker Hubまたはその他のレジストリを介してそれらを配布して、実行またはデプロイをとても簡単にすることができます。
Docker Composeを使用したマルチコンテナアプリケーションの操作
これまでは、1つのコンテナのみで構成されるアプリケーションのみを扱ってきました。 次に、複数のコンテナを持つアプリケーションを想定します。おそらく、データベースサービスが適切に機能するために必要なAPI、またはバックエンドAPIとフロントエンドアプリケーションを一緒に使用する必要があるフルスタックアプリケーションなどがあります。
このセクションでは、Docker Composeというツールを使用して、このようなアプリケーションを操作する方法について学びます。
Dockerのドキュメントによると下記のように記述されています。
Composeは、マルチコンテナのDockerアプリケーションを定義して実行するためのツールです。Composeでは、YAMLファイルを使用してアプリケーションサービスを設定します。次に、1つのコマンドで、構成からすべてのサービスを作成して開始します。
Composeはすべての環境で機能しますが、開発とテストにより重点を置いています。本番環境でComposeを使用することはお勧めしません。
Docker Composeの基本
プロジェクトコードリポジトリのクローンを作成した場合は、notes-api
ディレクトリ内に移動します。これは、ノートを作成、読み取り、更新、および削除できる単純なCRUD APIです。アプリケーションは、データベースシステムとしてPostgreSQLを使用します。
プロジェクトにはすでにDockerfile.dev
ファイルがありました。ファイルの内容は次のとおりです。
FROM node:lts WORKDIR /usr/app COPY ./package.json . RUN npm install COPY . . CMD [ "npm", "run", "dev" ]
前のセクションで書いたものと同じです。package.json
ファイルをコピーし、依存関係をインストールして、プロジェクトファイルをコピーし、npm run dev
を実行して開発サーバーを起動します。
Composeの使用は、基本的に3つのステップのプロセスです。
Dockerfile
を使用し、アプリを定義するので、どこでも再現できます。- アプリを構成するサービスを
docker-compose.yml
で定義して、隔離された環境で一緒に実行できるようにします。 docker-compose up
を実行すると、Composeが起動し、アプリ全体が実行されます。
サービスは基本的に、いくつかの追加要素を持つコンテナです。最初のYMLファイルを一緒に書き始める前に、このアプリケーションの実行に必要なサービスをリストアップしましょう。2つしかありません。
api
- プロジェクトルートのDockerfile.dev
ファイルを使用して実行されるExpressアプリケーションコンテナ。db
- 公式のpostgresイメージを使用して実行されるPostgreSQLインスタンス。
プロジェクトルートで新しいdocker-compose.yml
ファイルを作成し、最初のサービスを一緒に定義しましょう。.yml
または.yaml
拡張子を使用できます。どちらも問題なく動作します。最初にコードを記述してから、コードを1行ずつ詳しく説明します。db
サービスのコードは次のとおりです。
version: "3.8" services: db: image: postgres:12 volumes: - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d environment: POSTGRES_PASSWORD: 63eaQB9wtLqmNBpg POSTGRES_DB: notesdb
すべての有効なdocker-compose.yml
ファイルは、ファイルバージョンを定義することから始まります。執筆時点では、3.8
が最新バージョンです。最新バージョンはこちらで調べることができます。
YMLファイル内のブロックは、インデントによって定義されます。私は各ブロックを通して、それらが何をするかを説明します。
services
ブロックは、アプリケーション内の各サービスまたはコンテナの定義を保持します。db
はservicesブロック内のサービスです。
db
ブロックはアプリケーションの新しいサービスを定義し、コンテナを開始するために必要な情報を保持します。コンテナを実行するには、すべてのサービスに事前に構築されたイメージまたはDockerfileが必要です。db
サービスには、公式のPostgreSQLイメージを使用しています。
プロジェクトルートのdocker-entrypoint-initdb.d
ディレクトリには、データベースのテーブルをセットアップするためのSQLファイルが含まれています。このディレクトリは、初期化スクリプトを保持するためのものです。docker-compose.yml
ファイル内のディレクトリをコピーする方法はないため、ボリュームを使用する必要があります。
environment
ブロックは環境変数を保持します。有効な環境変数のリストは、Docker Hubのpostgres image pageにあります。POSTGRES_PASSWORD
変数はサーバーのデフォルトのパスワードを設定し、POSTGRES_DB
は指定された名前で新しいデータベースを作成します。
次に、api
サービスを追加します。次のコードをファイルに追加します。インデントとdb
サービスを一致させるように注意してください。
## ## インデントを調整してください ## api: build: context: . dockerfile: Dockerfile.dev volumes: - /usr/app/node_modules - ./:/usr/app ports: - 3000:3000 environment: DB_CONNECTION: pg DB_HOST: db ## データベースサービスと同じ名前を指定 DB_PORT: 5432 DB_USER: postgres DB_DATABASE: notesdb DB_PASSWORD: 63eaQB9wtLqmNBpg
api
サービス用のビルド済みイメージはありませんが、Dockerfile.dev
ファイルがあります。build
ブロックはビルドのcontext
と使用するDockerfileのfilename
を定義します。ファイルの名前がDockerfile
のみの場合、filename
は不要です。
ボリュームのマッピングは、前のセクションで見たものと同じです。node_modules
ディレクトリ用の1つの匿名ボリュームとプロジェクトルート用の1つの名前付きボリュームです。
ポートマッピングも前のセクションと同じように機能します。構文は<ホストシステムポート>:<コンテナポート>
です。コンテナのポート3000番をホストシステムのポート3000番にマッピングしています。
environment
ブロックでは、データベース接続のセットアップに必要な情報を定義しています。アプリケーションはKnex.jsをORMとして使用して、データベースに接続するためにこれらの情報を必要とします。DB_PORT: 5432
とDB_USER: postgres
はすべてのPostgreSQLサーバーのデフォルトです。DB_DATABASE: notesdb
およびDB_PASSWORD: 63eaQB9wtLqmNBpg
は、db
サービスの値と一致する必要があります。DB_CONNECTION: pg
は、PostgreSQLを使用していることをORMに示します。
docker-compose.yml
ファイルで定義されているすべてのサービスは、サービス名を使用してホストとして使用できます。そのため、api
サービスは、127.0.0.1
のような値ではなくホストとして扱うことで、実際にdb
サービスに接続できます。そのため、DB_HOST
の値をdb
に設定しています。
docker-compose.yml
ファイルが完成したので、アプリケーションを起動します。Composeアプリケーションは、dokcer-compose
と呼ばれるCLIツールを使用してアクセスできます。Compose用のdocker-compose
CLIは、Dockerでいうdocker
CLIです。サービスを開始するには、次のコマンドを実行します。
docker compose up
コマンドを実行すると、docker-compose.yml
ファイルを読み込み、各サービスのコンテナを作成して開始します。先に進んで、コマンドを実行してください。サービスの数によっては、起動プロセスに時間がかかる場合があります。
完了すると、ターミナルウィンドウのすべてのサービスからのログが表示されます。
アプリケーションはhttp://127.0.0.1:3000
アドレスで実行されている必要があり、アクセスすると、次のようなJSONがレスポンスとして表示されます。
APIには完全なCRUD機能が実装されています。エンドポイントについて知りたい場合は、/tests/e2e/api/routes/notes.test.js
ファイルを参照してください。
up
コマンドは、サービスのイメージが存在しない場合に自動的に作成します。イメージの再構築を強制したい場合は、up
コマンドで--build
オプションを使用できます。ターミナルウィンドウを閉じるか、ctrl + c
を押すと、サービスを停止できます。
デタッチモードでサービスを実行する
すでに述べたように、サービスはコンテナであり、他のコンテナと同様に、サービスはバックグラウンドで実行できます。デタッチモードでサービスを実行するには、up
コマンドで-d
または--detach
オプションを使用できます。
現在のアプリケーションをデタッチモードで起動するには、次のコマンドを実行します。
docker-compose up -d
今回は、前のサブセクションで見た長いテキストの壁は表示されません。
なおhttp://127.0.0.1:3000
でAPIにアクセスできるはずです。
サービスの一覧表示
docker ps
コマンドと同様に、Composeには独自のps
コマンドがあります。主な違いは、docker-compose ps
コマンドは特定のアプリケーションのコンテナ部分のみを一覧表示することです。notes-api
アプリケーションの一部として実行されているすべてのコンテナを一覧表示するには、プロジェクトルートで次のコマンドを実行します。
docker-compose ps
プロジェクトディレクトリ内でコマンドを実行することが重要です。それ以外の場合は実行されません。コマンドからの出力は次のようになります。
Composeのps
コマンドはデフォルトで任意の状態のサービスを表示します。-a
や--all
などのオプションを使用する必要はありません。
実行中のサービス内でコマンドを実行する
notes-api
アプリケーションが実行中で、db
サービス内のpsql
CLIアプリケーションにアクセスするとします。それを行うためにexec
と呼ばれるコマンドがあります。コマンドの一般的な構文は次のとおりです。
docker-compose exec <サービス名> <コマンド>
サービス名はdocker-compose.yml
ファイルにあります。psql
CLIアプリケーションを起動するための一般的な構文は次のとおりです。
psql <データベース> <ユーザー名>
データベース名がnotesdb
で、ユーザーがpsql
であるdb
サービス内でpsql
アプリケーションを開始するには、次のコマンドを実行する必要があります。
docker-compose exec db psql notesdb postgres
psql
アプリケーションに直接アクセスする必要があります。
ここで有効なpostgresコマンドを実行できます。プログラムを終了するには、\q
と入力してEnterキーを押します。
実行中のサービス内でシェルを開始する
exec
コマンドを使用して、実行中のコンテナ内でシェルを起動することもできます。コマンドの一般的な構文は次のとおりです。
docker-compose exec <サービス名> sh
コンテナに付属している場合は、sh
の代わりにbash
を使用できます。api
サービス内でシェルを開始するには、コマンドは次のようにする必要があります。
docker-compose exec api sh
これにより、api
サービス内のシェルに直接アクセスできます。
そこで、任意の有効なシェルコマンドを実行できます。exit
コマンドを実行することで終了できます。
実行中のサービスからログにアクセスする
コンテナからログを表示する場合は、ダッシュボードが非常に役立ちます。
また、logs
コマンドを使用して、実行中のサービスからログを取得することもできます。コマンドの一般的な構文は次のとおりです。
docker-compose logs <サービス名>
api
サービスからログにアクセスするには、次のコマンドを実行します。
docker-compose logs api
ターミナルウィンドウにテキストの壁が表示されるはずです。
これはログ出力の一部にすぎません。-f
または--follow
オプションを使用すると、サービスの出力ストリームにフックしてリアルタイムでログを取得できます。以降のログは、ctrl + c
を押すかウィンドウを閉じて終了しない限り、ターミナルに即座に表示されます。ログウィンドウを終了しても、コンテナは実行を続けます。
実行中のサービスを停止する
ターミナル上で実行されているサービスは、ターミナルウィンドウを閉じるか、ctrl + c
を押すことで停止できます。バックグラウンドでサービスを停止するには、いくつかのコマンドを使用できます。一つ一つ説明していきます。
docker-compose stop
-SIGTERM
シグナルを送信することにより、実行中のサービスを適切に停止しようとします。サービスが制限時間内に停止しない場合、SIGKILL
シグナルが送信されます。docker-compose kill
-SIGKILL
シグナルを送信して実行中のサービスを即座に停止します。SIGKILL
シグナルは受信者が無視することはできません。docker-compose down
-SIGTERM
シグナルを送信して実行中のサービスを適切に停止しようとし、その後コンテナを削除します。
サービスのコンテナを保持したい場合は、stop
コマンドを使用できます。コンテナも削除する場合は、down
コマンドを使用します。
フルスタックアプリケーションの作成
このサブセクションでは、ノートAPIにフロントエンドアプリケーションを追加し、それを完全なアプリケーションに変換します。このサブセクションのDockerfile.dev
ファイル(nginx
サービスのファイルを除く)は、前のサブセクションですでに見た他のファイルと同一であるため、説明しません。
プロジェクトコードリポジトリのクローンを作成した場合は、fullstack-notes-application
ディレクトリ内に移動します。プロジェクトルート内の各ディレクトリには、各サービスのコードと対応するDockerfileが含まれています。
docker-compose.yml
ファイルから始める前に、アプリケーションがどのように機能するかを示す図を見てみましょう。
以前のように直接リクエストを受け入れる代わりに、このアプリケーションでは、すべてのリクエストが最初にNginxサーバーによって受信されます。Nginxは、リクエストされたエンドポイントに/api
が含まれているかどうかを確認します。はいの場合、Nginxはリクエストをバックエンドにルーティングします。そうでない場合、Nginxはリクエストをフロントエンドにルーティングします。
これを行う理由は、フロントエンドアプリケーションを実行すると、コンテナ内では実行されないためです。コンテナから提供されるブラウザで実行されます。その結果、Composeネットワークが期待どおりに機能せず、フロントエンドアプリケーションがapi
サービスを見つけることができません。
一方、nginxはコンテナ内で実行され、アプリケーション全体でさまざまなサービスと通信できます。
ここではNginxの構成については触れません。そのトピックは、この記事の範囲外ですが興味があれば/nginx/default.conf
ファイルを読んでみてください。/nginx/Dockerfile.dev
のコードは次のとおりです。
FROM nginx:stable COPY ./default.conf /etc/nginx/conf.d/default.conf
コンテナ内の設定ファイルを/etc/nginx/conf.d/default.conf
にコピーするだけです。
使い慣れたサービスを定義して、docker-compose.yml
ファイルの記述をしてみましょう。db
およびapi
サービス。プロジェクトルートにdocker-compose.yml
ファイルを作成し、そこに次のコードを配置します。
version: "3.8" services: db: image: postgres:12 volumes: - ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d environment: POSTGRES_PASSWORD: 63eaQB9wtLqmNBpg POSTGRES_DB: notesdb api: build: context: ./api dockerfile: Dockerfile.dev volumes: - /usr/app/node_modules - ./api:/usr/app environment: DB_CONNECTION: pg DB_HOST: db ## データベースサービスと同じ名前を指定 DB_PORT: 5432 DB_USER: postgres DB_DATABASE: notesdb DB_PASSWORD: 63eaQB9wtLqmNBpg
ご覧のとおり、これら2つのサービスは前のサブセクションとほぼ同じです。唯一の違いは、api
サービスのcontext
です。これは、そのアプリケーションのコードがapi
という名前の専用ディレクトリ内にあるためです。また、サービスを直接公開したくないため、ポートマッピングはありません。
次に定義するサービスはclient
サービスです。次のコードを構成ファイルに追加します。
## ## インデントは調整してください ## client: build: context: ./client dockerfile: Dockerfile.dev volumes: - /usr/app/node_modules - ./client:/usr/app environment: VUE_APP_API_URL: /api
サービスにclient
という名前を付けます。build
ブロック内では、/client
ディレクトリをcontext
として設定し、それにDockerfile名を付けています。
ボリュームのマッピングは、前のセクションで見たものと同じです。node_modules
ディレクトリ用の1つの匿名ボリュームとプロジェクトルート用の1つの名前付きボリュームです。
environtment
内のVUE_APP_API_URL
変数の値は、client
からapi
サービスへの各リクエストに追加されます。このようにして、Nginxは異なるリクエストを区別し、それらを適切に再ルーティングすることができます。
api
サービスと同様に、このサービスも公開したくないため、ここにはポートマッピングはありません。
アプリケーションの最後のサービスはnginx
サービスです。これを定義するには、次のコードを構成ファイルに追加します。
## ## インデントを調整してください ## nginx: build: context: ./nginx dockerfile: Dockerfile.dev ports: - 80:80
Dockerfile.dev
の内容はすでに話しました。サービスにはnginx
という名前を付けます。build
ブロック内では、/nginx
ディレクトリをcontext
として設定し、それにDockerfile名を付けています。
すでに図で示したように、このnginx
サービスはすべてのリクエストを処理します。したがって、それを公開する必要があります。Nginxはデフォルトでポート80番で実行されます。コンテナ内のポート80番をホストシステムのポート80番にマッピングしています。
docker-compose.yml
ファイルが完成したので、サービスを実行します。次のコマンドを実行して、すべてのサービスを開始します。
docker-compose up
ここでhttp://localhost:80
にアクセスしてみましょう!
ノートを追加および削除して、アプリケーションが正しく動作するかどうかを確認してください。マルチコンテナアプリケーションはこれよりもはるかに複雑になる可能性がありますが、この記事ではこれで十分です。
まとめ
この記事を読んでくださった皆さんに心から感謝します。あなたがこの時間を楽しんで、Dockerのすべての本質を学んだことを願っています。
今後の最新記事をキャッチアップするなら、フォローしてください。@frhnhsin✌
筆者紹介
Farhan Hasin Chowdhury
レストラン管理システムを開発するFoodQoでテックリードを務める傍ら、NPOで運営されるプログラミング情報コミュニティ「freeCodeCamp」にてライター活動も行っている。
翻訳者紹介
satoru
大学を半年で中退後、Webエンジニアとして就職。チームラボやダイヤモンドメディアを経てシリコンバレーを訪問。世界的な企業でエンジニアリングを学び、現在は教会にコントリビュートするエンジニアとしてフリーランスで活動している。このブログの運営者でもある。