DUICUO

2019年になりましたが、まだDockerをご存知ですか?この10分ガイドで、初心者からエキスパートまで、Dockerの使い方をマスターしましょう。

Dockerの概要

DockerはOS仮想化技術であり、オープンソースのアプリケーションコンテナエンジンです。開発者はDockerを使用することで、アプリケーションをほぼすべてのLinuxシステムで実行可能なポータブルコンテナにパッケージ化できます(Windows 10もネイティブでサポートされていますが、Windows 10以前のバージョンでは組み込みの仮想マシンが必要でした)。これはまさに「一度パッケージ化すれば、どこでも実行可能」というコンセプトを体現しています。

Dockerコンテナは完全にサンドボックス内で動作し、コンテナ間でのやり取りはありません(手動でクラスターを連結しない限り)。ネットワーク、ストレージ、プロセスリソースは、公開ポートを手動でマッピングしたりストレージボリュームをマウントしたりしない限り、異なるコンテナ間だけでなく、ホストマシンとコンテナ間でも分離されます。

多くの人は Docker と仮想マシンの違いを理解していません。

これら2つの図を見ると、Dockerは仮想マシンのオペレーティングシステム層を必要としないことがわかります。DockerアプリケーションはDockerエンジン上で直接実行されます。仮想マシンはオペレーティングシステムを必要とするため、サイズが非常に大きくなり、通常は数ギガバイトから数十ギガバイトに及びます。さらに、1台の仮想マシンは通常複数のアプリケーションをホストするため、仮想クラスタ全体の管理効率が低下し、リソースを柔軟に割り当てることが難しくなります。これに対し、Dockerイメージのサイズは通常数十メガバイトから数百メガバイトです。一般的に、イメージには1つのアプリケーションのみがパッケージ化されており、複数のイメージで1つのプロジェクトが完成します。また、イメージは簡単にコピーでき、複数のプラットフォームで実行できるため、プロジェクトの展開と管理の柔軟性が向上します。そのため、Dockerはリソース消費、管理、使いやすさの点で仮想マシンを上回っています。では、なぜこのコンテナ化技術を使用しないのでしょうか?

コンテナ化技術の学習は複雑で難易度の高い分野です。基本的なイメージとコンテナの操作から、イメージのパッケージ化とコンテナのデプロイ、そしてエンタープライズレベルのコンテナクラスタ管理技術(DockerのSwarmやGoogleのKubernetes)に至るまで、その膨大な内容はほとんどの技術者にとって手の届かないものです。しかし、本番環境レベルのクラスタ管理の難しさを除けば、学習と使用の観点から見ると、他の側面は実は非常にシンプルです。しかも、Kubernetesは一般の開発者がめったに目にすることのない技術です。

この時点では、多くの人はまだこれが企業レベルまたは運用レベルの操作であると考えており、一般の開発者にとって Docker が何を意味するのか、そしてそれが私たちにどのようなメリットをもたらすのかを理解していないかもしれません。

  • 複数の作業環境をワンクリックでデプロイ。オフィスと自宅にそれぞれ開発環境があると想像してみてください。オフィス環境が変化すると、自宅環境も更新する必要があります。Dockerを使えば、Redis、ZooKeeper、MySQLといった依存アプリケーションをDockerコンテナにパッケージ化できます。コンテンツの変更箇所に関係なく、実行時にイメージを更新するだけで変更が適用されるため、手動でのインストールや調整は不要です。
  • 統合テストは他者に依存せずに実行できます。バックエンドの外部インターフェースが完成すると、バックエンドアプリケーションはDockerにパッケージ化されます。これにより、フロントエンドチームとテストチームは、バックエンド環境を段階的に手動で構築することなく、いつでもどこでもコンテナを起動して統合テストを行うことができます。
  • ...

以下に、通常の開発に必要な Docker の知識を段階的に説明します。

コンセプトの紹介

Docker を学習するには、まずいくつかの基本的な概念を理解する必要があります。

  • ホスト マシンは、Docker が実行される物理マシンであり、Docker が動作するシステム環境です。
  • イメージはプログラムのテンプレートのようなもので、そこから多くの類似したコンテナを生成できます。Javaのクラスとして理解できます。イメージ自身は実行できませんが、オブジェクトの抽象テンプレートです。各イメージは複数のバージョンを持つことができ、タグで区別されます。イメージはDockerfileを使用してビルドできます。
  • コンテナはDockerにおけるオブジェクトの最小単位です。イメージからインスタンス化された実行可能なオブジェクトです。コンテナへの変更はイメージにコミットされ、コンテナのテンプレートが更新されます。
  • リポジトリとは、Gitがコードリポジトリを管理するのと同様に、イメージを保存および管理するために使用されるリポジトリです。複数のバージョンのイメージを管理できます。

イメージ、コンテナ、リポジトリの関係は次のとおりです。

つまり、リポジトリからイメージをプルし、それらのイメージを使用してコンテナを作成します。

基本操作

Dockerの基本概念を理解した上で、基本的な操作を学び始めましょう。ここではDockerのインストール手順全体を省略しますが、公式サイトからダウンロードできます。インストールは非常に簡単です。

画像をプル

コマンド `docker pull ${image_uri}:${image_tag}` を使用して、リモート リポジトリ (デフォルトでは Docker Hub) から必要なイメージをプルできます。

必要なイメージとバージョンは、Docker Hubウェブサイトで検索できます。例えば、Ubuntuの複数のバージョンが利用可能です。

Ubuntu 16.04 のイメージをプルしてみましょう。そして、`docker images` コマンドを使って、新しい Ubuntu イメージがローカルイメージに追加されたことを確認します。

コンテナの作成、起動、シャットダウン、ログイン

イメージを入手したら、`docker run -it ${image_id}` を使用してコンテナを作成して起動できます。

image_id はイメージ ID であり、docker images を使用して表示できます。または、イメージ名 (REPOSITORY:TAG) にすることもできます。

`-it` オプションを使用すると、起動後にコンテナのターミナルに接続できます。接続後は、コンテナの内容を自由に操作できます。

`exit` を使ってコンテナを終了すると、コンテナは自動的に停止します。ただし、コンテナ自体はまだ存在しており、「シャットダウン」されただけです。(コンテナを閉じなくても、Ctrl+P または Ctrl+Q で終了して再度ログインできます。)

`docker ps -a` を使用すると、コンテナが終了したことを確認できます。

`docker start ${container_id}` を使用してコンテナを再起動します。`docker ps`(現在実行されていないコンテナを表示するには `* -a` を使用)を使用すると、コンテナのステータスが UP* であることがわかります

同様に、`docker stop ${container_id}` を使用してコンテナを停止できます。

`docker start` コマンドを使用する際、`-a` パラメータを指定しないと、デフォルトではコンテナに接続されません。ただし、`start` の後に `docker attach ${container_id}` を実行することでコンテナにログインできます。

上記の基本的な手順で、Dockerを仮想マシンとして使用できます。コンテナと仮想マシンのネットワークとストレージを統合したい場合は、ネットワークやボリュームのマウントなどのコンテナ設定に関する情報をオンラインで検索してください。

画像を更新

上記の例では、Ubuntuの生のイメージのみをプルし、追加コンテンツはあまり含まれていませんでした。次に、このイメージのコンテナ内にJDKをインストールします。

これにより、コンテナ内にJDKが組み込まれます。ただし、この元のUbuntuイメージを使用して別のコンテナを作成する場合、このJDKは使用されません。そのため、コンテナの内容をイメージにコミットする必要があります。これは「docker commit ${container_id} ${repository}:${tag}」コマンドを使用して実行します。このコマンドは、コンテナの内容をローカルにイメージにコミットします。これで、JDKが組み込まれたUbuntuイメージが作成されます。

次に、このイメージを使ってJDKを含むコンテナを生成します。上記の更新はローカルイメージのみに適用されます。コンテナをクラウドにプッシュするには、`docker push`コマンドを使用する必要があります。この操作を行うには、リポジトリにログインし、必要な権限を持っている必要があります。

ミラーリポジトリ

前述の通り、リポジトリはデフォルトでDocker Hubです。pullとpushの操作はDocker Hub上で行いますが、イメージが社内プライベート利用のためのものであれば、ネットワーク速度が遅いこと、そしてプライバシーとセキュリティの問題から、Docker Hubを使用する必要はありません。

上記の問題に対処するには、2つの解決策があります。1つは独自のプライベートサービスを構築すること、もう1つはクラウドサービスのイメージ管理プラットフォーム(Alibaba Cloudの「Container Image Service」など)を利用することです。前者はほとんどの開発者にとって不要であり、認証が必要となるため、やや面倒です。そのため、ここでは詳細には触れません。以下では、Alibaba Cloudサービスを独自のプライベートリポジトリとして利用する方法について説明します。

まず、docker login を使用して Alibaba Cloud サービスにログインします。

次に、上記の JDK イメージにタグを付けます (これは実際にはリポジトリ ソースを変更するプロセスです)。

***イメージを Alibaba Cloud にプッシュするだけです。

イメージがプッシュされると、Alibaba Cloud のリポジトリで確認できるようになります。

プライベート リポジトリを設定することで、ホスト マシン環境を完全にバイパスし、イメージを構築して、どこからでも実行できるようになります。

Dockerfileビルドイメージ

上記の導入では、イメージをプルし、コンテナの内容を変更し、コミットすることで必要なイメージを構築する方法を学びました。しかし、これらの操作でイメージを構築するのは煩雑で分かりにくく、イメージの構成を直感的に理解することが困難です。

Dockerfileはこの問題を効果的に解決します。ビルドプロセスを記述することで、イメージをワンステップでビルドできます。以下では、UbuntuをベースイメージとしてJDKをインストールし、新しいイメージをビルドする例を用いて、Dockerfileの記述方法を説明します。

次に、`docker build -t registry.cn-shenzhen.aliyuncs.com/zackku/jdk2:1.0 .` を実行してイメージをビルドします。

高度なDockerfileテクニック

上記はDockerfileの基本的な使い方を説明したものですが、実際には上記の方法(というか、これだけではありません)でイメージを構築するわけではありません。以下に、実装においてよく使われる2つの原則を示します。

階層化構築。実際、Docker イメージは階層構造になっています。リモート リポジトリへのプッシュの例を振り返ってみましょう。

赤いボックスは、イメージのコミットプロセスをレイヤーごとに示しています。レイヤーがすでにローカルでビルドされている場合は、再度ビルドする必要はありません。同様に、レイヤーがリモートにすでに存在する場合は、プッシュする必要はありません。このレイヤー化は、異なるイメージ間で共有できます。たとえば、異なる Java プロジェクトが JDK ランタイム環境に依存している可能性があるため、JDK レイヤーのイメージコンテンツを共有できます。したがって、この特性に基づいて、共通性を抽象化してレイヤーでイメージを構築する必要があります。実際には、イメージを 2 段階で構築できます。まず、Java プロジェクトの基本的なランタイム環境など、さまざまなイメージの基礎となるレイヤーのベースイメージを構築します。次に、ベースイメージを使用して、開発レイヤーのアプリケーションイメージをビルドします。基本的に、これはアプリケーションをパッケージ化し、開発レイヤーに配置します。このレイヤーは、Docker にプログラムの実行方法を指示します。

Dubbo、Redis、Netty、ZooKeeper、Spring Cloud、分散システム、高並行性、その他のアーキテクチャ技術

可能な限り、ベースレイヤーは小さく構築してください。Dockerを使用する際は、イメージサイズが重要な要素となります。大きなイメージは、アップデートやプル時に非効率性につながるためです。これは特にベースレイヤーに当てはまります。プログラムが多すぎる、大きく肥大化したベースレイヤーは、ファイルサイズが増加するだけでなく、CPUとネットワークリソースを過剰に消費します。通常、Dockerでは、コンテナには1つのアプリケーションプロジェクトのみが含まれます。このアプリケーションの監視とヘルス管理は、Kubernetesなどのクラスタ管理ツールを通じて外部で処理されます。そのため、コンテナ自体は、アプリケーションの実行を保証するだけで済みます。

UbuntuをベースOSとして使用するのはやや冗長です。プログラムのベースイメージにはAplineを使用することをお勧めします。Aplineは軽量なLinuxディストリビューションで、システムサイズとリソース消費量が少なく、Dockerコンテナに最適です。Aplineをベースに、TomcatやJDKなどの必要な環境を追加してベースイメージを構築します。

上記のベースイメージは、必ずしも独自のDockerfileを作成する必要はありません。Dockerは、GitHubのDockerライブラリで、様々な言語と環境向けのベースイメージを直接提供しています。チームのランタイム環境に特別な要件がある場合は、このDockerfileを追加または変更したり、追加のレイヤーとして抽象化したりすることも可能です。

Dockfile の書き方や構文については、オンラインで詳細な説明が多数あるため、スペースの制限によりここでは詳しく説明しません。

docker-compose でクラスターを起動する

単一のコンテナの構築と起動方法については既に説明しましたが、私たちのプロジェクトでは複数のコンテナが使用されることが多く、すべてのアプリケーションを単一のコンテナにパッケージ化するのは適切なアプローチではありません。そのため、多数のコンテナを管理および起動することは必須のスキルです。エンタープライズレベルでは、KubernetesやSwarmといったコンテナオーケストレーションおよび管理ツールがありますが、これらはやや複雑であり、個人での使用にはあまり必要ありません。

ここでは、Docker公式のdocker-composeを使用することをお勧めします。docker-composeは、すべてのコンテナオーケストレーションを1つのファイルに記述し、docker-compose upコマンドを使用して、オーケストレーションに従ってコンテナセットを起動できます。

この例では、 `services` セクションに各コンテナの設定が含まれています。Redis と MongoDB はデフォルトのイメージと設定を使用し、`myproject` は独自のプロジェクトです。このオーケストレーションにより、プロジェクトは Redis と MongoDB に接続できます。***`docker-compose up` を実行すると、イメージが自動的にプルされ、オーケストレーションが実行されます。

具体的な構文についてはここでは触れませんが、コンテナボリュームのマウント、ネットワーク設定、ポートの公開、コンテナの依存関係などがポイントになります。使い始めると徐々に理解できるようになりますが、重要なのは実際に自分で試してみることです。