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 では、関連のあるイメージを単一 Pod にグループ化して、これらを簡単に共存させ、共同管理できるようになります。

この共存により、コンテナーがネットワークの namespace とストレージを通信用に共有できるようになります。また、イメージの更新頻度が低く、個別に更新されるので、更新による中断の可能性が低くなります。シグナル処理のフローは、生成されたプロセスへのルーティングシグナルを管理する必要がないため、単一プロセスの場合により明確になります。

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

詳細は、Project Atomic ドキュメントの「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 操作を後に実行してもこれらのファイルは削除できません。これらの追加ファイルは最終イメージでは確認できませんが、下位レイヤーには存在します。

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

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

適切な順序での命令の指定

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

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

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

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

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

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

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

重要なポートへのマーキング

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

環境変数の設定

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

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

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

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

SSHD の回避

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

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

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

コンテナーが破棄された後も保存する必要のあるデータはすべて、ボリュームに書き込む必要があります。コンテナーエンジンは 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+rwX /some/directory

コンテナーユーザーは常に、root グループのメンバーとして所属しているので、コンテナーユーザーはこれらのファイルに対する読み取り、書き込みが可能です。

警告

コンテナーの慎重に扱うべき分野のディレクトリーおよびファイルパーミッションを変更する場合には注意が必要です(通常のシステムの扱い方と同様です)。

/etc/passwd などの機密の領域に適用される場合、意図しないユーザーがこのようなファイルを変更することを許可し、コンテナーやホストが公開されてしまう可能性があります。CRI-O は、ランダムなユーザー ID のコンテナーの /etc/passwd への挿入をサポートするため、このパーミッションの変更は一切必要ありません。

さらに、コンテナーで実行中のプロセスは、特権のあるユーザーとして実行されていないので、特権のあるポート (1024 未満のポート) をリッスンできません。

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

export USER_ID=$(id -u)
export GROUP_ID=$(id -g)
envsubst < ${HOME}/passwd.template > /tmp/passwd
export LD_PRELOAD=/usr/lib64/libnss_wrapper.so
export NSS_WRAPPER_PASSWD=/tmp/passwd
export NSS_WRAPPER_GROUP=/etc/group

この場合、passwd.template には以下が含まれます。

root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
postgres:x:${USER_ID}:${GROUP_ID}:PostgreSQL Server:${HOME}:/bin/bash

さらに、これが機能するには、nss_wrapper および gettext パッケージをイメージにインストールする必要があります。後者は envsubst コマンドを提供します。たとえば、この行を yum ベースのイメージの Dockerfile に追加できます。

RUN yum -y install nss_wrapper gettext

最後に、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 ドライバーを設定します。このように設定することで、アプリケーションのアセンブリー時に共通の依存関係をダウンロードする必要がありません。また、すべての依存関係の要件を満たすためのアプリケーション開発者の作業が簡素化されます。

設定での環境変数の使用

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

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

環境変数を指定することで、イメージの利用者は、イメージ上に新しいレイヤーを作成することなく、データベースの設定、パスワード、パフォーマンスチューニングなどの動作をカスタマイズできます。Pod の定義時に環境変数の値を定義するだけで、イメージの再ビルドなしに設定を変更できます。

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

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

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

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

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

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

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

クラスタリング

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

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

ロギング

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

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

Liveness および Readiness プローブ

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

Templates (テンプレート)

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

2.4. 外部の参考資料