第4章 イメージの作成
使用可能な事前にビルドされたイメージを使用して独自のコンテナーイメージを作成する方法について確認します。このプロセスには、イメージの作成、イメージのメタデータの定義、イメージのテストおよびカスタムビルダーワークフローを使用した Red Hat OpenShift Service on AWS で使用するイメージの作成のベストプラクティスを理解することが含まれます。
4.1. コンテナーのベストプラクティスについて
Red Hat OpenShift Service on AWS で実行するコンテナーイメージを作成する場合には、イメージの作成者は、イメージの使いやすさの点で数多くのベストプラクティスを考慮する必要があります。イメージは変更不可で、そのままの状態で使用されることが意図されているため、以下のガイドラインは、イメージを使用しやすく、Red Hat OpenShift Service on AWS で簡単に使用できるようにするのに役立ちます。
4.1.1. コンテナーイメージの一般的なガイドライン
以下のガイドラインは、イメージが Red Hat OpenShift Service on AWS で使用されるかどうかにかかわらず、コンテナーイメージの作成時に一般的に適用されます。
イメージの再利用
可能な場合は、FROM
ステートメントを使用し、適切なアップストリームイメージをベースとしてイメージを設定します。これにより、依存関係を直接更新する必要なく、イメージが更新時にアップストリームイメージからセキュリティー修正を簡単に取得できるようになります。
さらに、FROM
命令 (例: rhel:rhel7
) のタグを使用して、お使いのイメージがどのバージョンのイメージをベースとしているかを明確にします。アップストリームイメージの latest
バージョンを使用すると互換性に影響のある変更が組み込まれる可能性があるため、latest
以外のタグを使用することができます。
タグ内の互換性の維持
独自のイメージにタグを付ける場合には、タグ内で後方互換性が維持されるようにします。たとえば、image
という名前のイメージがあり、現時点でバージョン 1.0
が含まれている場合には、image:v1
のタグを指定します。イメージの更新時には、元のイメージとの互換性がある限り、新しいイメージに image:v1
のタグを付けることができ、このタグのダウンストリームのコンシューマーは、互換性に関する影響を被ることなく更新を取得できるようになります。
互換性のない更新を後にリリースした場合には、image:v2
などの新しいタグに切り替えます。これにより、ダウンストリームのコンシューマーはいつでも新しいバージョンに移行できますが、意図せずにこの互換性のない新規イメージによる影響を受けることはありません。image:latest
を使用するダウンストリームコンシューマーには、互換性のない変更が導入されるリスクがあります。
複数プロセスの回避
データベースや SSHD
など複数のサービスを 1 つのコンテナー内で起動しないようにしてください。コンテナーは軽量で、複数のプロセスをオーケストレーションするために簡単にリンクできるので、複数プロセスの実行は不要です。Red Hat OpenShift Service on AWS では、関連のあるイメージを 1 つの Pod にグループ化して、簡単に共存させ、共同管理することができます。
このように共存させることで、コンテナーはネットワークの namespace とストレージを通信用に共有できるようになります。また、イメージの更新頻度が低く、個別に更新されるので、更新による中断の可能性が低くなります。シグナル処理フローは、複数の起動したプロセスへのルーティングシグナルを管理する必要がないので、単一プロセスによって明確になります。
ラッパースクリプトでの exec
の使用
多くのイメージはラッパースクリプトを使用して、実行されるソフトウェアのプロセスを開始する前にいくつかの設定を行います。イメージがこのようなスクリプトを使用する場合、そのスクリプトは、スクリプトのプロセスがソフトウェアによって置き換えられるように exec
を使用します。exec
を使用しない場合、コンテナーランタイムによって送信されるシグナルが、ソフトウェアのプロセスではなくラッパースクリプトに送られます。これは望ましい動作ではありません。
一部のサーバーのプロセスを開始するラッパースクリプトがあるとします。podman run -i
などを使用してコンテナーを起動すると、それによりラッパースクリプトが実行され、次にプロセスが開始されます。CTRL+C
でコンテナーを閉じる必要があるとします。ラッパースクリプトがサーバープロセスを開始するために exec
を使用している場合、podman
は SIGINT をサーバープロセスに送信し、すべてが予想通りに機能します。ラッパースクリプトで exec
を使用しなかった場合、podman
はラッパースクリプトのプロセスに SIGINT を送信し、プロセスは何も生じなかったかのように実行し続けます。
また、コンテナー内で実行されると、プロセスは PID 1
として実行される点に留意してください。つまり、主なプロセスが中断された場合には、コンテナー全体が停止され、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
操作も再実行する必要があります。
重要なポートのマーク付け
EXPOSE 命令は、ホストシステムで利用できるコンテナーおよび他のコンテナーにポートを作成します。ポートを podman run
の起動で公開されるように指定できますが、Dockerfile
で EXPOSE 命令を使用すると、ソフトウェアが実行する必要のあるポートを明示的に宣言することで、人間とソフトウェアの両方がイメージをより簡単に使用できるようになります。
-
公開されるポートは、イメージから作成されるコンテナーに関連付けられる
podman ps
の下に表示されます。 -
公開されるポートは、
podman inspect
によって返されるイメージのメタデータに表示されます。 - 公開されるポートは、1 つのコンテナーを別のコンテナーにリンクする際にリンクされます。
環境変数の設定
ENV
命令で環境変数を設定することが適切です。一例として、プロジェクトのバージョンを設定するなどが挙げられます。バージョンを設定することで、Dockerfile
を確認せずにバージョンを簡単に見つけ出すことができます。別の例としては、JAVA_HOME
など、別のプロセスで使用可能なシステムでパスを公開する場合などです。
デフォルトのパスワードの回避
デフォルトのパスワードは設定しないようにしてください。イメージを拡張して、デフォルトのパスワードを削除または変更するのを忘れることが多くあります。これは、実稼働環境で使用するユーザーに誰でも知っているパスワードが割り当てられると、セキュリティーの問題に発展する可能性があります。パスワードは、環境変数を使用して設定できます。
デフォルトのパスワードを設定することにした場合には、コンテナーの起動時に適切な警告メッセージが表示されるようにしてください。メッセージはデフォルトパスワードの値をユーザーに通知し、環境変数の設定など、パスワードの変更方法を説明するものである必要があります。
SSHD の回避
イメージで sshd
を実行しないようにしてください。ローカルホストで実行中のコンテナーにアクセスするには、podman exec
または docker exec
コマンドを使用できます。または、oc exec
コマンドまたは oc rsh
コマンドを使用して、Red Hat OpenShift Service on AWS クラスターで実行中のコンテナーにアクセスできます。イメージで sshd
をインストールし、実行すると、攻撃の経路が増え、セキュリティー修正が必要になります。
永続データ向けのボリュームの使用
イメージは、永続データ用に ボリューム を使用する必要があります。こうすることで、Red Hat OpenShift Service on AWS により、コンテナーを実行するノードにネットワークストレージがマウントされ、コンテナーが新しいノードに移動した場合に、ストレージはそのノードに再度割り当てられます。永続ストレージのすべての要件に対応するようにボリュームを使用することで、コンテナーが再起動されたり、移動されたりしても、コンテンツは保存されます。イメージがコンテナー内の任意の場所にデータを書き込む場合には、コンテンツは保存されない可能性があります。
コンテナーが破棄された後も保存する必要のあるデータはすべて、ボリュームに書き込む必要があります。コンテナーエンジンはコンテナーの readonly
フラグをサポートしており、このフラグを使用して、コンテナーの一時ストレージにデータが決して記述されないようにすることができます。イメージをこの機能に基づいて設計すると、この機能を後に利用することがより簡単になります。
Dockerfile
でボリュームを明示的に定義すると、イメージの利用者がイメージの実行時に定義する必要のあるボリュームがどれかを簡単に理解できるようになります。
Red Hat OpenShift Service on AWS でのボリュームの使用方法の詳細は、Kubernetes ドキュメント を参照してください。
永続ボリュームでも、イメージの各インスタンスには独自のボリュームがあり、ファイルシステムはインスタンス間で共有されません。つまり、ボリュームを使用してクラスターの状態を共有できません。
4.1.2. Red Hat OpenShift Service on AWS 固有のガイドライン
以下は、Red Hat OpenShift Service on AWS で使用するためのコンテナーイメージの作成時に適用されるガイドラインです。
4.1.2.1. Source-To-Image (S2I) 向けのイメージの有効化
開発者が提供した Ruby コードを実行するように設計された Ruby イメージなど、サードパーティー提供のアプリケーションコードを実行することが目的のイメージの場合には、イメージを Source-to-Image (S2I) ビルドツールと連携できるようにすることができます。S2I は、インプットとして、アプリケーションのソースコードを受け入れるイメージを簡単に記述でき、アセンブルされたアプリケーションをアウトプットとして実行する新規イメージを簡単に生成することができるフレームワークです。
4.1.2.2. 任意のユーザー ID のサポート
デフォルトでは Red Hat OpenShift Service on AWS は、任意に割り当てられたユーザー ID を使用してコンテナーを実行します。こうすることで、コンテナーエンジンの脆弱性が原因でコンテナーから出ていくプロセスに対して追加のセキュリティーを設定でき、ホストノードでパーミッションのエスカレーションが可能になります。
イメージが任意ユーザーとしての実行をサポートできるように、イメージ内のプロセスで記述されるディレクトリーやファイルは、root グループが所有し、このグループに対して読み取り/書き込みの権限を割り当てる必要があります。実行予定のファイルには、グループの実行権限も必要です。
以下を Dockerfile に追加すると、root グループのユーザーがビルドされたイメージでアクセスできるように、ディレクトリーおよびファイルのパーミッションが設定されます。
RUN chgrp -R 0 /some/directory && \ chmod -R g=u /some/directory
コンテナーユーザーは常に root グループのメンバーであるため、コンテナーユーザーはこれらのファイルに対する読み取り、書き込みが可能です。
コンテナーの機密領域のディレクトリーとファイルパーミッションを変更する場合は注意が必要です。/etc/passwd
ファイルなどの機密領域に変更を適用すると、意図しないユーザーによるこれらのファイルの変更が許可され、コンテナーまたはホストがセキュリティーリスクにさらされる可能性があります。CRI-O は、コンテナーの /etc/passwd
ファイルへに対する任意のユーザー ID の挿入をサポートしています。そのため、パーミッションを変更する必要はありません。
また、いずれのコンテナーイメージにも /etc/passwd
ファイルが存在しないはずです。存在する場合、CRI-O コンテナーランタイムはランダムな UID を /etc/passwd
ファイルに挿入できません。このような場合、コンテナーがアクティブな UID を解決する際に問題が発生する可能性があります。この要件を満たさない場合、特定のコンテナー化されたアプリケーションの機能に影響が及ぶ可能性があります。
さらに、コンテナーで実行中のプロセスは、特権のあるユーザーとして実行されていないので、特権のあるポート (1024 未満のポート) をリッスンできません。
4.1.2.3. イメージ間通信でのサービスの使用
データの保存や取得のためにデータベースイメージにアクセスする必要のある Web フロントエンドイメージなど、別のイメージが提供するサービスとイメージが通信する場合には、イメージは Red Hat OpenShift Service on AWS サービスを使用します。サービスは、コンテナーが停止、開始、または移動しても変更されない静的アクセスエンドポイントを提供します。さらに、サービスにより、要求が負荷分散されます。
4.1.2.4. 共通のライブラリーの提供
サードパーティーが提供するアプリケーションコードの実行を目的とするイメージの場合は、プラットフォーム用として共通に使用されるライブラリーをイメージに含めるようにしてください。とくに、プラットフォームで使用する共通のデータベース用のデータベースドライバーを設定してください。たとえば、Java フレームワークイメージを作成する場合に、MySQL や PostgreSQL には JDBC ドライバーを設定します。このように設定することで、アプリケーションのアセンブリー時に共通の依存関係をダウンロードする必要がなくなり、アプリケーションイメージのビルドがスピードアップします。また、すべての依存関係の要件を満たすためのアプリケーション開発者の作業が簡素化されます。
4.1.2.5. 設定での環境変数の使用
イメージのユーザーは、ダウンストリームイメージをイメージに基づいて作成しなくても、イメージを設定できます。つまり、ランタイム設定は環境変数を使用して処理されます。単純な設定の場合、実行中のプロセスは環境変数を直接使用できます。より複雑な設定や、これをサポートしないランタイムの場合、起動時に処理されるテンプレート設定ファイルを定義してランタイムを設定します。このプロセス時に、環境変数を使用して渡される値は設定ファイルで置き換えることも、この値を使用して、設定ファイルに指定するオプションを決定することもできます。
環境変数を使用して、コンテナーに証明書やキーなどのシークレットを渡すこともでき、これは推奨されています。環境変数を使用することで、シークレット値がイメージにコミットされたり、コンテナーイメージレジストリーに漏洩されることはありません。
環境変数を指定することで、イメージの利用者は、イメージ上に新しいレイヤーを作成することなく、データベースの設定、パスワード、パフォーマンスチューニングなどの動作をカスタマイズできます。Pod の定義時に環境変数の値を定義するだけで、イメージの再ビルドなしに設定を変更できます。
非常に複雑なシナリオの場合、ランタイム時にコンテナーにマウントされるボリュームを使用して設定を指定することも可能です。ただし、この方法を使用する場合には、必要なボリュームや設定が存在しない場合に明確なエラーメッセージが起動時に表示されるように、イメージが設定されている必要があります。
サービスエンドポイントの情報を渡す環境変数としてデータソースなどの設定を定義される点で、これはイメージ間の通信でのサービスの使用に関するトピックと関連しています。これにより、アプリケーションは、アプリケーションイメージを変更せずに、Red Hat OpenShift Service on AWS 環境に定義されているデータソースサービスを動的に使用できます。
さらに、コンテナーの cgroups
設定を確認して、調整します。これにより、イメージは利用可能なメモリー、CPU、他のリソースに合わせてチューニングが可能になります。たとえば、Java ベースのイメージは、制限を超えず、メモリー不足のエラーが表示されないように、cgroup
の最大メモリーパラメーターを基にヒープをチューニングします。
4.1.2.6. イメージのメタデータ設定
イメージのメタデータを定義することで、Red Hat OpenShift Service on AWS によるコンテナーイメージの使用が改善され、開発者が Red Hat OpenShift Service on AWS でイメージを使用しやすくなります。たとえば、メタデータを追加して、イメージに関する役立つ情報を提供したり、必要とされる他のイメージを提案したりできます。
4.1.2.7. クラスタリング
イメージの複数のインスタンスを実行するとはどういうことかを十分に理解しておく必要があります。最も単純な例では、サービスの負荷分散機能は、イメージのすべてのインスタンスにトラフィックをルーティングします。ただし、セッションの複製などで、リーダーの選択やフェイルオーバーの状態を実行するには、多くのフレームワークが情報を共有する必要があります。
Red Hat OpenShift Service on AWS での実行時に、インスタンスでこのような通信を実現する方法を検討します。Pod 同士は直接通信できますが、Pod が起動、停止、移動するたびに IP アドレスが変更されます。そのため、クラスタリングスキームを動的にしておくことが重要です。
4.1.2.8. ロギング
すべてのロギングを標準出力に送信することが推奨されます。Red Hat OpenShift Service on AWS はコンテナーから標準出力を収集し、表示が可能な中央ロギングサービスに送信します。ログコンテンツを分離する必要がある場合には、出力の接頭辞に適切なキーワードを指定して、メッセージをフィルタリングできるようにしてください。
お使いのイメージがファイルにロギングをする場合には、手動で実行中のコンテナーに入り、ログファイルを取得または表示する必要があります。
4.1.2.9. Liveness および Readiness プローブ
イメージで使用可能な liveness および readiness プローブの例をまとめます。これらのプローブにより、処理の準備ができるまでトラフィックがコンテナーにルーティングされず、プロセスが正常でない状態になる場合にコンテナーが再起動されるので、ユーザーはイメージを安全にデプロイできます。
4.1.2.10. テンプレート
イメージと共にテンプレートサンプルを提供することも検討してください。テンプレートがあると、ユーザーは、正しく機能する設定を指定してイメージをすばやく簡単にデプロイできるようになります。完全を期すため、テンプレートには、イメージに関連して記述した liveness および readiness プローブを含めるようにしてください。