Red Hat Training

A Red Hat training course is available for OpenShift Container Platform

第2章 ガイドライン

2.1. 概要

OpenShift Container Platform で実行するためにコンテナーイメージを作成する場合には、使用者がイメージを快適に使用できるように、イメージの作成者として考慮するべきベストプラクティスが複数あります。イメージは、変更できず、そのままの状態で使用するように作成されているので、以下のガイドラインでは、イメージを OpenShift Container Platform で簡単に使用でき、消費しやすくなるように、サポートします。

2.2. コンテナーイメージの一般的なガイドライン

以下のガイドラインは、一般的なコンテナーイメージの作成時に適用され、イメージが OpenShift Container Platform で使用されるかどうかは関係ありません。

イメージの再利用

できる限り、FROM ステートメントを使用して、適切なアップストリームイメージをベースに、お使いのイメージを設定することを推奨します。こうすることで、依存関係を直接更新する必要なく、更新があるとイメージが簡単にセキュリティー修正を取得できるようになります。

さらに、FROM 命令 (例: rhel:rhel7) のタグを使用して、お使いのイメージがどのバージョンのイメージを使用しているのかを明確にします。アップストリームの latest バージョンに壊れた変更が組み込まれている可能性があり、latest 以外のタグを使用することで、そのような変更の影響を受けないようにします。

タグ内の互換性の維持

独自のイメージにタグを付ける場合には、タグ内で後方互換性が維持されるようにすることを推奨します。たとえば、foo という名前のイメージがあり、現在バージョン 1.0 が含まれている場合には、タグに foo:v1 を指定します。イメージの更新時には、オリジナルのイメージと互換性があれば、新しいイメージに foo:v1 のタグを付けて、このタグのダウンストリームのコンシューマーは、イメージを破壊することなく更新を取得できるようになります。

互換性のない更新を後にリリースした場合には、foo:v2 などの新しいタグに切り替える必要があります。これにより、ダウンストリームのコンシューマーはいつでも新しいバージョンに移行できますが、不注意で互換性のない新規イメージで破壊してしまうことはありません。foo:latest を使用するダウンストリームコンシューマーの方には、互換性のない変更が導入される危険性があります。

複数プロセスの回避

データベースや SSHD など複数のサービスを 1 つのコンテナー内で起動しないようにしてください。コンテナーは軽量で、簡単にリンクして複数のプロセスをオーケストレーションできるので、これは必要ありません。OpenShift Container Platform では、関連のあるイメージを 1 つの pod にグループ化して、簡単に共存、共同管理できるようになります。

このように共存させることで、コンテナーがネットワークの namespace とストレージを通信用に共有できるようになります。また、イメージの更新頻度が低く、個別に更新されるので、更新の破壊レベルが低くなります。シグナル処理のフローは、起動したプロセスへのルーティングシグナルを管理する必要がないので、単一プロセスのほうが明確です。

ラッパースクリプトでの exec の使用

詳細は、『Project Atomic documentation』の「Always exec in Wrapper Scripts」のセクションを参照してください。

また、コンテナー内で実行すると、プロセスは PID 1 として実行されている点に留意してください。つまり、主なプロセスが中断された場合には、コンテナー全体が停止され、PID 1 プロセスから起動した子プロセスが終了します。

その他関連のある内容は、「Docker and the PID 1 zombie reaping problem」 のブログ記事を参照してください。また、PID 1 や init システムの詳細は、「Demystifying the init system (PID 1)」のブログ記事を参照してください。

一時ファイルの消去

ビルドプロセスで作成される一時ファイルはすべて削除する必要があります。これには、ADD コマンドで追加したファイルも含まれます。たとえば、yum install の操作を実行してから、yum clean コマンドを実行することを強く推奨します。

yum キャッシュがイメージ層に残らないように、以下のように、RUN ステートメントを作成します。

RUN yum -y install mypackage && yum -y install myotherpackage && yum clean all -y

以下のように記述した場合には注意してください。

RUN yum -y install mypackage
RUN yum -y install myotherpackage && yum clean all -y

上記のように記述すると、最初の yum 呼び出しにより、対象の層に追加のファイルが残り、yum clean 操作を後に実行してもこれらのファイルは削除できません。これらの追加ファイルは最終イメージでは確認できませんが、下層には存在します。

現在の Docker ビルドプロセスでは、前の層で何かが削除された場合でも、後の層でコマンドを実行してイメージが使用する容量を縮小できません。ただし、これについては今後変更される可能性はあります。後の層で rm コマンドを実行しても、ファイルが表示されていなくても、ダウンロードする全体のイメージサイズは減少しません。そのため、yum clean の例のように、層に書き込まれないように、できるだけ同じコマンドでファイルを削除することが一番です。

また、単一の RUN ステートメントで複数のコマンドを実行すると、イメージの階層数が減り、ダウンロードと実行時間が短縮されます。

正しい順序での命令の指定

Docker は Dockerfile を読み取り、トップダウンで命令を実行します。命令が正常に実行されると、同じイメージが次回ビルドされるときや、別のイメージがビルドされる時に再利用することができる階層が作成されます。Dockerfile の上部でほぼ変更されない命令を配置することが非常に重要です。こうすることで、上層で加えられた変更により、キャッシュが無効にならないので、同じイメージが次回、非常にすばやくビルドされるようになります。

たとえば、反復するファイルをインストールするための ADD コマンドと、パッケージを yum install する RUN コマンドが含まれる Dockerfile で作業を行う場合には、ADD コマンドを最後に配置することがベストです。

FROM foo
RUN yum -y install mypackage && yum clean all -y
ADD myfile /test/myfile

こうすることで、myfile を編集して docker build を返すたびに、システムは yum コマンドのキャッシュ階層を再利用し、ADD 操作に対してのみ、新規階層を生成します。

以下のように Dockerfile を記述した場合:

FROM foo
ADD myfile /test/myfile
RUN yum -y install mypackage && yum clean all -y

次に、myfile を変更して、docker build を再実行するたびに、ADD 操作は RUN 階層キャッシュを無効にするので、yum 操作も再実行する必要があります。

重要なポートのマーク

詳細は、『Project Atomic documentation』の「Always EXPOSE Important Ports」のセクションを参照してください。

環境変数の設定

ENV 命令で環境変数を設定することが適切です。一例として、プロジェクトのバージョンを設定するなどが挙げられます。バージョンを設定して、Dockerfile を確認せずにバージョンを簡単に見つけ出すことができます。別の例としては、JAVA_HOME など、別のプロセスで使用可能なシステムでパスを広告する場合などです。

デフォルトパスワードの回避

デフォルトのパスワードは設定しないことが一番です。イメージを拡張して、デフォルトのパスワードを削除または変更するのを忘れることが多く、実稼働環境で使用するユーザーに誰でも知っているパスワードが割り当てられると、セキュリティーの問題につながります。パスワードは、環境変数を使用して設定できるようにする必要があります。詳細は、「設定での環境変数の使用」 のトピックを参照してください。

デフォルトのパスワードを設定することにした場合には、コンテナーの起動時に適切な警告メッセージが表示されるようにしてください。メッセージで、デフォルトパスワードの値をユーザーに通知し、環境変数の設定など、パスワードの変更方法を説明する必要があります。

SSHD の回避

イメージで SSHD の実行を回避するようにしてください。ローカルホストで実行中のコンテナーにアクセスするには、docker exec コマンドを使用できます。または、oc exec コマンドまたは oc rsh コマンドを使用して、OpenShift Container Platform クラスターで実行中のコンテナーにアクセスできます。イメージで SSHD をインストール、実行すると、攻撃の経路が増え、セキュリティー修正が必要になります。

永続データ向けのボリュームの使用

イメージは、永続データ用に Docker ボリューム を使用する必要があります。こうすることで、OpenShift Container Platform により、コンテナーを実行するノードにネットワークストレージがマウントされ、コンテナーが新しいノードに移動した場合に、ストレージはそのノードにアタッチしなおされます。永続ストレージの全要件にボリュームを使用することで、コンテナーが再起動されたり、移動されたりしても、コンテンツは保存されます。イメージがコンテナー内の任意の場所にデータを書き込む場合には、コンテンツは保存されない可能性があります。

コンテナーが破棄された後でさえも保存する必要のあるデータはすべて、ボリュームに書き込む必要があります。Docker 1.5 では、コンテナーに readonly フラグがあり、このフラグを使用して、コンテナーの一時ストレージにデータが絶対に記述されないように強制することができます。その機能をもとにイメージを設計すると、後でこの機能をより簡単に活用できるようになります。

さらに、Dockerfile でボリュームを明示的に定義すると、イメージの消費者が簡単に、イメージの実行時に定義する必要のあるボリュームがどれかを理解できるようになります。

OpenShift Container Platform でボリュームがどのように使用されるかについては、「Kubernetes ドキュメント」を参照してください。

注記

永続ボリュームでも、イメージの各インストールには独自のボリュームがあり、ファイルシステムはインスタンス間で共有されません。つまり、ボリュームを使用してクラスターの状態を共有できません。

外部のガイドライン

他のガイドラインについては、以下の資料を参照してください。

2.3. OpenShift Container Platform 固有のガイドライン

以下は、OpenShift Container Platform で使用するためのコンテナーイメージを作成時に適用されるガイドラインです。

Source-To-Image (S2I) のイメージの有効化

開発者が提供した Ruby コードを実行するように設計された Ruby イメージなど、サードパーティー提供のアプリケーションコードを実行することが目的のイメージの場合には、イメージを有効化して Source-to-Image (S2I) ビルドツールと連携できるようにします。S2I は、インプットとして、アプリケーションのソースコードを受け入れるイメージを簡単に記述でき、アウトプットとして、組み立てられたアプリケーションを実行する新規イメージを簡単に生成することができるフレームワークです。

たとえば、この Python イメージ は S2I スクリプトを定義して、Python アプリケーションのさまざまなバージョンをビルドします。

イメージ用に S2I スクリプトを記述する方法については、「S2I 要件」のトピックを参照してください。

任意の ID のサポート

デフォルトでは OpenShift Container Platform は、任意に割り当てられたユーザー ID を使用してコンテナーを実行します。こうすることで、コンテナーエンジンの脆弱性が原因でコンテナーから出ていくプロセスに対して追加のセキュリティーを設定できるので、ホストノードでパーミッションのエスカレーションが可能になります。

イメージが任意ユーザーとしての実行をサポートできるように、イメージ内のプロセスで記述される可能性のあるディレクトリーやファイルは、root グループが所有し、このグループに対して読み取り/書き込みの権限を割り当てる必要があります。実行予定のファイルには、グループの実行権限も必要です。

以下を Dockerfile に追加すると、root グループのユーザーがビルドイメージにアクセスできるように、ディレクトリーおよびファイルのパーミッションが設定されます。

RUN chgrp -R 0 /some/directory && \
    chmod -R g=u /some/directory

コンテナーユーザーは常に、root グループのメンバーとして所属しているので、コンテナーユーザーはこれらのファイルに対する読み取り、書き込みが可能です。root グループには、(root ユーザーのように) 特別なパーミッションがないので、この設定ではセキュリティーに関する懸念点はありません。さらに、コンテナーで実行中のプロセスは、特権のあるユーザーとして実行されていないので、特権のあるポート (1024 未満のポート) をリッスンしてはいけません。

コンテナーのユーザー ID が動的に生成されるので、/etc/passwd に関連のあるエントリーがありません。これが原因で、ユーザー ID を検索する必要のあるアプリケーションで問題が発生する可能性があります。この問題に対応するには、イメージの起動スクリプトの一部として、コンテナーのユーザー ID を指定した passwd のファイルエントリーを動的に作成します。Dockerfile の内容は、以下のようになります。

RUN chmod g=u /etc/passwd
ENTRYPOINT [ "uid_entrypoint" ]
USER 1001

uid_entrypoint には、以下を含めてください。

if ! whoami &> /dev/null; then
  if [ -w /etc/passwd ]; then
    echo "${USER_NAME:-default}:x:$(id -u):0:${USER_NAME:-default} user:${HOME}:/sbin/nologin" >> /etc/passwd
  fi
fi

上記の完全な例は、この Dockerfile を参照してください。

最後に、Dockerfile の最後の USER 宣言は、ユーザー名ではなく、ユーザー ID (数値) を指定するようにしてください。こうすることで、OpenShift Container Platform が、イメージが実行しようとしている認証情報を検証して、root でイメージを実行できないようにします。理由は、特権のあるユーザーにより セキュリティー上の欠陥 が生み出される可能性があるためです。イメージで USER が指定されていない場合は、親イメージの USER が継承されます。

重要

S2I イメージに、ユーザーを数値で指定した USER 宣言が含まれない場合には、デフォルトで、ビルドが失敗するようになっています。名前が指定されたユーザーや root (0) ユーザーを使用するイメージを使用できるようにするには、プロジェクトのサービスアカウント (system:serviceaccount:<your-project>:builder) を 特権付き SCC (security context constraint) に追加してください。または、すべてのイメージを どのユーザーでも実行できるようにしてください

イメージ内の通信でのサービスの使用

データの保存や取得にデータベースイメージにアクセスする必要のある Web フロントエンドイメージなど、別のイメージが提供するサービスとイメージが通信する場合には、イメージは OpenShift Container Platform サービス を使用する必要があります。サービスは、コンテナーが停止、開始、または移動されても、変更しない静的アクセスエンドポイントを提供します。さらに、サービスにより、要求が負荷分散されます。

共通のライブラリーの提供

サードパーティーが提供するアプリケーションコードの実行を目的とするイメージの場合は、プラットフォーム用に共通で使用されるライブラリーをイメージに含めるようにしてください。特に、プラットフォームで使用する共通のデータベース用のデータベースドライバーを設定してください。たとえば、Java フレームワークイメージを作成する場合に、MySQL や PostgreSQL には JDBC ドライバーを設定します。このように設定することで、アプリケーションの組み立て時に共通の依存関係をダウンロードする必要がありません。また、全依存関係が提供され、アプリケーション開発者の作業が簡素化されます。

設定での環境変数の使用

イメージのユーザーは、イメージをもとにダウンストリームイメージを作成する必要なしに、設定が行えるようにしてください。つまり、ランタイム設定は環境変数を使用して処理してください。単純な設定の場合、実行中のプロセスは直接環境変数を使用します。より複雑な設定や、これをサポートしないランタイムの場合は、起動時に処理されるテンプレート設定ファイルを定義してランタイムを設定します。このプロセス時に、環境変数を使用して渡される値を、設定ファイルに置き換えることも、この値を使用して、設定ファイルに指定するオプションを決定することもできます。

環境変数を使用して、コンテナーに証明書や鍵などのシークレットを渡すこともでき、これを推奨しています。環境変数を使用することで、シークレット値がイメージにコミットされたり、Docker レジストリーに漏洩してしまったりしないようにします。

環境変数を指定して、イメージのコンシューマーは、イメージの上に新しい階層を作成することなく、データベースの設定、パスワード、パフォーマンスチューニングなどの動作をカスタマイズできます。代わりに、pod の定義時に環境変数の値を定義するだけで、イメージのリビルドなしに設定が変更されます。

非常に複雑なシナリオでは、ランタイム時にコンテナーにマウントされるボリュームを使用して設定を提供することも可能です。ただし、この方法を使用する場合には、必要なボリュームや設定が存在しない時に明確なエラーメッセージが起動時に表示されるように、イメージが設定されている必要があります。

このトピックは、サービスエンドポイントの情報を渡す環境変数としてデータソースなどの設定は定義する必要がある点で、「イメージ間の通信でのサービスの使用」のトピックに関連しています。環境変数で設定を定義することで、アプリケーションは、アプリケーションイメージを変更せずに、OpenShift Container Platform 環境に定義されているデータソースサービスを動的に使用できます。

さらに、コンテナーの cgroups 設定を確認して、調整を行う必要があります。これにより、イメージは利用可能なメモリー、CPU、他のリソースに合わせてチューニングが可能になります。たとえば、Java ベースのイメージは、制限を超えずに、メモリー不足のエラーが表示されないように、cgroup の最大メモリーパラメーターをもとにヒープをチューニングする必要があります。

Docker コンテナーの cgroup クォータを管理する方法については、以下の資料を参照してください。

イメージのメタデータの設定

イメージのメタデータを定義することで、OpenShift Container Platform によるコンテナーイメージの消費が改善され、イメージを使用する開発者が OpenShift Container Platform をより快適に使用できるようになります。たとえば、メタデータを追加して、イメージに関する役立つ情報を提供したり、必要とされる可能性のある他のイメージを提案したりできます。

サポートされるメタデータや、定義の方法に関する詳細は、「イメージのメタデータ」のトピックを参照してください。

クラスタリング

イメージのインスタンスを複数実行する意味を完全に理解する必要があります。最もシンプルな例では、サービスの負荷分散機能は、イメージの全インスタンスにトラフィックをルーティングします。ただし、セッションの複製などで、リーダーの選択やフェイルオーバーの状態を実行するには、多くのフレームワークが情報を共有する必要があります。

OpenShift Container Platform で実行時に、インスタンスでこのような通信を実現する方法を検討します。pod 同士で直接通信できますが、pod が起動、停止、移動するたびに、IP アドレスが変更されるので、クラスタリングスキームを動的にしておくことが重要です。

ロギング

すべてのロギングを標準出力に送信することが最適です。OpenShift Container Platform はコンテナーから標準出力を収集し、表示が可能な中央ロギングサービスに送信します。別個のログコンテンツが必要な場合には、出力のプレフィックスに適切なキーワードを指定して、メッセージをフィルタリングできるようにしてください。

お使いのイメージがファイルにロギングをする場合には、手動で実行中のコンテナーに入り、ログファイルを取得または表示する必要があります。

Liveness および Readiness プローブ

イメージ使用可能な 「liveness および readiness プローブ」 の例をまとめます。これらのプローブで、処理の準備ができるまでトラフィックがルーティングされず、コンテナーのヘルス状態が良くない場合にコンテナーが再起動されるので、ユーザーは安心してイメージをデプロイできます。

テンプレート

イメージの テンプレート 例を提供することも検討してください。テンプレートがあると、ユーザーは、正しく機能する設定を指定してイメージをすばやく簡単にデプロイ できるようになります。完全を期すため、テンプレートには、イメージにドキュメントとして追加した liveness および readiness プローブ を含めるようにしてください。

2.4. 外部の参考資料