IBM Cloud Blog

第12回『コンテナと RHEL と OpenShift – しられざる OpenShift の強み 』

記事をシェアする:

こんにちは、Red Hat のSolution Architect の花田です。

前回は、Red Hat OpenShift と Kubernetes の違い について解説させて頂きましたが、今回は、OpenShift のしられざる(?) 強みという事で、OpenShift の RHEL (Red Hat Enterprise Linux) の関わりについて解説してみたいと思います。

コンテナと言うと、Kubernetes が注目されがちな気がしますが、Kubernetes はコンテナを運用するための基盤の一つにすぎません。ユーザー視点で一番大切なのは、Kubernetes (OpenShift)の上で稼働するアプリケーションだと思います。

最近は Windows のコンテナも注目を浴びつつありますが、現状、殆どのコンテナは Linux で作成されています。まずは、コンテナと Linux の関わりを知るために、コンテナのアーキテクチャーから解説してみたいと思います。

アプリケーションが依存しているもの

コンテナは、コンテナ内のアプリケーションの稼働に必要な、依存関係のある「ミドルウェア」や「ライブラリ」をコンテナ内部に全て含んでいます。

依存関係、つまりアプリケーションが依存するコンポーネントを全て含んでいるので、どの Linux ホストOS上に持っていても問題無く稼働する。というのが「可搬性」の由来になっています。

では、この「依存関係」を理解するために、アプリケーションが依存している「ミドルウェア」や「ライブラリ」について考えて見ます。

ミドルウェアとライブラリ

「ミドルウェア」や「ライブラリ」という言葉は、異なる観点でソフトウェアコンポーネントを表現した言葉で、文脈によっては、これらの表現は同じものを指す場合もあります。

一般的に「ミドルウェア」は、ソフトウェアスタックの中で中間層にあるソフトウェアコンポーネントを指す言葉です。

「ミドルウェア」を具体的にイメージしやすいように幾つか例を挙げると、アプリケーション・サーバーや、データベースサーバーは、代表的な「ミドルウェア」と言えるでしょう。それだけでも単独で稼働するアプリケーションである事が多いと思います。

一方「ライブラリ」は、プログラムに対して何らかの機能を提供するソフトウェアコンポーネントを指します。プログラムから使用できる、いろいろな機能が図書館(ライブラリ)に並べられている様子をイメージするとわかりやすいと思います。

「ライブラリ」の例としては、Java ライブラリや、C言語のライブラリの他にも、OSが提供する各種機能を提供するOSのライブラリがあります。

以下は、C言語で “Hello World” という文字列を出力するプログラムです。

#include <stdio.h>

 int main(void){

  printf("Hello World\n");
  return 0;

}

この例では、一行目で stdio.h というファイルを読み込んでいます。これはC言語のライブラリを読み込むための命令で、これを行う事で、途中に現れる printf() という関数が使えるようになります。

このコードからは見てとれませんが、 printf() 関数は、OS上にある libc.so.x(xは数字) という名前のC言語のライブラリのファイルによって提供されています。libc.so.x には printf() の他にもいろいろ便利な関数が含まれています。

このように、ライブラリはプログラムに対して便利な機能を提供してくれるためのモジュールです。

なお、ライブラリやミドルウェアと言った言葉の使い方は、対象や視点によって変わってくる場合があります。例えば、筆者も「Python」のソフトウェアに言及する時に、ユーザーが書いた Python プログラムに関数を提供する役割を切り出して「ライブラリ」と言ったり、OSとユーザーの書いた Python のコードの間にある存在として「ミドルウェア」と呼ぶ事があります。「ミドルウェア」も「ライブラリ」自分の書いたコードが使用している、もしくは動かすために必要なソフトウェアという感じで抑えて置いて良いと思います。

共有ライブラリと静的ライブラリ

Linux では .so は、「共有ライブラリ」の拡張子を表し .a は「静的ライブラリ」の拡張子になります。

共有ライブラリとは、そのライブラリを使用するプログラムが実行された時に、参照されるタイプのライブラリです。この場合、プログラム本体とライブラリは別ファイルとして存在します。ファイルは一つですが、いろいろなプログラムから共有されて使われるので共有ライブラリと呼びます。

ライブラリのファイルが別なので、ライブラリの機能をアップデートすると、それを参照しているプログラムは自動的にその恩恵を受ける事ができます。一方で互換性の無い変更を行うと、それを参照している全てのプログラムが影響を受ける事になります。つまり、ファイル間のバージョンの依存性が発生するのが共有ライブラリです。

一方、静的ライブラリは、そのライブラリを使用するプログラムを作成する時に、プログラムの実行ファイル内にライブラリを取り込んでしまいます。そのため、プログラム自体は大きくなってしまいますが、ファイルが一つになるので、プログラムの実行のために必要なものを気にする必要がなくなります。バージョンも取り込まれたタイミングのバージョンが維持されます。

図1: 共有ライブラリと静的ライブラリ

OS上にはいろいろな共有ライブラリと、静的ライブラリが存在しており、「C言語のライブラリ」や「Pythonのライブラリ」などの他に「OSのライブラリ」もあります。もともとインストールされているものもありますし、ユーザーが追加インストールして使用するものもあります。

環境によって動かなくなるアプリケーション

これは私自身の経験ですが、海外の工場向けに作成したプログラムをリリースした後、動かないと現地のユーザーから連絡が来た事がありました。私の環境では再現はせず、現地のユーザーとやりとりしながらわかったのが、OSにインストールされている動的ライブラリのバージョンが違い、互換性が無くなっていた。と言うものでした。

こうしたデバッグは時間を要するものですし、リリース時のトラブルはできるだけ避けたかったので、アプリケーションのサイズは大きくなってしまうものの、できるだけ静的ライブラリを使って、環境との依存関係を減らす形でリリースするようにしました。

また、一般的なプロジェクトでは、「本番環境」「開発環境」などのように複数の環境が用意されている場合がありますが、本番環境が10台のサーバーで構成されていても開発環境は予算の都合上1台という事は普通にあります。

本来はこういう縮退しすぎた開発環境を作るべきではないという問題は置いておくと、シェアされている開発環境でOSにインストールされているライブラリを他の人が更新すると、自分は何も変更していないはずなのに、ある日突然これまで動いていたアプリケーションが動かなくなる事があります。こうなると原因調査は困難を極めます。

このように手元の環境では動作する同じアプリケーションのバイナリでも、環境に依存して、動かなくなる場合があります。

アプリケーションの他のコンポーネントへの依存

これまで説明したアプリケーションの依存関係を、ある程度抽象化して図に表すと以下のようになります。

図2: アプリケーションが依存するもの

Windows や Mac や Linux でもこの関係性は同じですので、イメージしやすいように拡張子などは、いろいろなOSのもので思いついた範囲のものを記載しています。

このように一つのアプリケーションは、いろいろなソフトウェアのコンポーネントに依存しており、複雑なアプリケーションほど環境に深く依存する可能性があります。

コンテナの可搬性

依存関係をパッケージする

前述したように、一つのアプリケーションは、ミドルウェアやライブラリと呼ばれるいろいろなレイヤーの様々なソフトウェアコンポーネントに依存して稼働します。

ですので「OSのアップグレードをしたらアプリケーションが動かなくなった」「DBのバージョンを上げたらタイムアウトするようになった」「Javaをアップデートしたらアプリケーションがエラーを出すようになった」という現象に、ソフトウェアエンジニアは頻繁に(?) 遭遇します。

ある Linux 環境で動いていたアプリケーションを、別の Linux 環境に移植した場合、動くかどうかは保証できません。アプリケーションが依存しているミドルウェアやライブラリがインストールされていない、インストールされていてもバージョンが違う。等がありえるためです。

その度にエンジニアは「ここが悪いのかな?」とデバッグを行いながら原因調査に当たりますが、これは一般的に非常に長い時間がかかる作業です。

コンテナではこう言ったアプリケーションの依存関係を全て取り込んだ形でパッケージングが行われます。依存関係のあるものは全てイメージとして固めてしまう。という方法を取ります。

図3: 依存関係のパッケージ

アプリケーションが依存するコンポーネントは、全てコンテナの中に含まれています。そのため、一つの環境で動いたコンテナは、他の環境でも(ほぼ)まちがいなく動作します。これがコンテナの「可搬性」です。

共有ライブラリと静的ライブラリの話を少し前にしましたが、考え方の方向性は静的ライブラリのそれと似ています。依存関係を内包してしまう事で、環境の影響を受けにくくしています。

Linux のカーネルはコンテナに含まれない

OSには「カーネル」という概念があります。カーネルはOSのコア部分を指す言葉で、ユーザーが改変できない部分です。

ユーザーが作ったアプリケーションが稼働する空間を「ユーザー・スペース」と呼ぶのに対して、ユーザーが変更できないカーネルの空間を「カーネル・スペース」と呼びます。

実は、コンテナのパッケージの中には、このLinuxのカーネル部分が含まれていません。Linux のカーネル部分は、Linux ディストリビューション間で共通であり、コンテナを別のLinuxの上に載せても、問題無く動くはずだからです。

ユーザーが作ったアプリケーションも、ミドルウェアも、様々なライブラリも最終的には、カーネルが提供している「システム・コール」と呼ばれるカーネルの機能を使うための Linux API を呼び出します。

図4: システム・コール

Linux API は、Linux カーネルの システム・コール とそれをラップするC言語のラッパー関数で構成されています。Linux は C言語で作られているので、これらはC言語の関数です。

他の言語のライブラリを使っている場合も、最終的にはこれらのシステム・コールを通してカーネルの機能が呼び出されます。

プログラムから Linux API を呼び出す場合は、テキストファイルであるソースコードからの Linux API を呼び出します。この呼び出し方は、Linux ディストリビューションに関わらず共通です。

(ただし、CPU のアーキテクチャーによって若干違う部分があります。前述の システム・コール のリストを読んでいくと、`x86 にはない` `IA-64のみ` のような記述があるのがわかると思います。)

テキストファイルであるプログラムは、コンパイルされバイナリファイル形式に変換されます。この時、バイナリファイルが使用するユーザー・スペースからカーネル・スペースへのインターフェイス(ABI: Application Binary Interface) も、Linux ディストリビューション間で共通です。(ただし、こちらも CPU のアーキテクチャーが違うと互換性は無くなります。)

いづれにしても CPU のアーキテクチャーが同じであれば、どの Linux ディストリビューション間でも Linux Kernel へのアクセス方法は同じになります。

つまり Linux のプログラムが、カーネルにアクセスしようとした時、そのアクセス方法は同じで互換性があり、コンテナの視点で見ると、カーネルはコンテナ内に含まず、デプロイされた環境に存在する Linux カーネルに頼る事ができます。

Linux カーネルの後方互換性

また、このLinuxのカーネル部分は、バージョンアップしても後方互換性がたもたれる。という開発者間での強いルールがあります。そのため、ユーザー・スペースで稼働するユーザーが作ったアプリの挙動を変えてしまうようなカーネルのコード変更は行う事ができません。

何度かユーザー・スペースへの影響が発生しそうな変更が行われそうになった こちらのケースや、 こちらのケース 等がありますが、このような変更は止められる運命にあります。メーリングリストのやりとりを一部、引用します。

We have very clear rules in the kernel: if some change breaks existing setups, it is ABSOLUTELY NEVER the application that is broken.

(私達はとてもはっきりしたルールをカーネルについて持っています。もしカーネルの何らかの変更が、既存の設定を破壊するのであれば、それは決してアプリケーション側の問題ではありません。(カーネル側の変更が問題なのです。))

このルールのおかげで、例え古い Linux のプログラムでも最新のLinuxカーネル上で問題無く動かせるようになっています。

一方で、反対に最新の Linux 環境で作成したコンテナは、最新の機能を参照している可能性があり、昔のカーネルで動かない可能性があるので、そこは注意する必要があります。

コンテナ専用のホストOS

以上のような技術的な背景があり、コンテナはカーネルをコンテナに含める必要がないため仮想マシンよりもサイズを小さくする事ができます。

さらに依存関係もコンテナ内に内包されているので、他のホストOSの上でも稼働するようになっています。

図5:コンテナ専用のOSと共通の Linux Kernel

このアーキテクチャーに基づくと、コンテナを動かすホストOSには、基本的には Linux のカーネル部分だけがあれば良い。という事になります。

コンテナ自身がカーネル以外の依存関係を全て含んでいるので、通常のLinuxディストリビューションそのものをホストOSに使用する技術的理由がありません。

そのような考えから、カーネルとその運用に必要なコンポーネント(例えば `ls` コマンドなどの最低限の管理コマンドやユーザー管理の仕組みは必要でしょう )と、コンテナを動かすためのランタイムに絞ったコンテナ専用のOSと呼ばれるものが登場しています。

コンテナ専用OSでは、コンテナから使用されないツールやライブラリを除外する事で、そのサイズをかなり小さくできます。同時に脆弱性を気にしなければいけない部分も減らす事ができます。これは、攻撃者からのアタックサーフェイスを減少させる事につながります。

OpenShift では、RHELをコンテナのホストOSとしても使用できますが、デフォルトではRHELをベースにした、RHEL CoreOS というコンテナ専用のOSを、コンテナのホストOSとして使用しています。

Windows コンテナ

少し脱線しますが、ユーザー・スペースやカーネル・スペースと言った考え方は、Linux特有のものではなく、Windowsでも同じです。

最近は Windowsのコンテナも話題に出てくるようになってきましたが、Windows のコンテナは Linuxの上では動きません。Windowsコンテナには WindowsのホストOSが必要になります。

OpenShift も Windows のコンテナを使用できますが、Linux (RHEL CoreOS) のノードで構成された OpenShift クラスターに、ユーザー所有の Windowsのノードを追加する BYOH(Bring Your Own Host) 形式 を取ります。  Windows のコンテナは Windows のノードの上だけで稼働する事ができます。

図6:Windows Worker Node の追加

コンテナと Linux ディストリビューション

コンテナの中身は、Linux ディストリビューション毎のライブラリや、ミドルウェアに依存したソフトウェアで構成される事になります。

「コンテナに対するサポート」と言った時、コンテナを動かす、Kubernetes や OpenShift 等のサポートをイメージしがちですが、忘れられがちなのは、コンテナそのものに対するサポートです。

現在、様々な Linux ディストリビューションが、コンテナの「ベース・イメージ」を提供しています。

ベース・イメージは、通常の Linuxディストリビューションそのものではなく、各 Linuxディストリビューションからアプリケーション開発に最低限必要な、コマンドなどのツールや、ライブラリを抜き出したセットをイメージ化したものです。

ベース・イメージは、OSをできるだけ絞り込んだ最小限のセットと言えるでしょう。どこまで絞り込んでいるかは、ベース・イメージによって違います。

あまり絞り込んでいると、結局、ユーザーがいろいろ追加する必要が発生して、使いにくいものになってしまいます。

例えば、ls コマンドを含んでないようなベース・イメージは、極小のコンテナを作りたい人には、嬉しいと思いますが、多くの人にとっては使いにくいものになるでしょう。

コンテナを作成する場合は、普通のアプリケーション作成とほぼ同じ手順を踏む必要があります。ベースのコンテナのイメージを用意して、さらに自分の作成したいアプリケーションに必要なライブラリやミドルウェア、ツールを追加して、自分のコンテナ・イメージを作成します。

図7:コンテナのビルド

コンテナのサポートを考える場合、Linux のディストリビューターが提供するベース・イメージや、追加しなければならないライブラリやミドルウェアのサポートをまず考える必要があります。

多くの場合、無料で使えるベース・イメージが各 Linux ディストリビューターから配布されています。が、エンタープライズ用途で、サポートが必要な場合は有償の契約を結ぶようになっているはずです。

時間の経過とともにソフトウェアの不具合や、脆弱性が発覚していく事は避けられません。そのため、ベース・イメージは、適宜アップデートされていく必要があります。これは、ベース・イメージに追加して使用するライブラリなども同様です。

また、多くのエンタープライズ向けのソフトウェアはサポートされる環境が決まっています。コンテナ内に追加するソフトウェアは、その Linux ディストリビューションのベース・イメージでサポートされるものである必要があります。

つまり、コンテナのベース・イメージの選定は、これから作成するアプリケーションで使用する Linux のディストリビューションの選定と同じで、どのベンダーの Linux が良いのか、自分が使用したいソフトウェアはその Linuxディストリビューションの環境でサポートされるのか。を考慮して選定する必要があります。

さらに Linux のディストリビューター以外のサード・ベンダーが提供しているパッケージ・アプリケーションをコンテナ内で使用したい場合は、Linux のディストリビューションをサポートしているかに加えてコンテナ環境をサポートするのかを確認する必要があります。

UBI (Universal Base Image) – RHEL の ベース・イメージ

Red Hat では、UBI (Universal Base Image) という、RHELのコンテナのベース・イメージを提供しています。UBIと名前がついてるのでイメージしにくいですが、RHELを元にして作られた RHEL のベース・イメージです。登録無しで、無償でダウンロードでき、再配布可能です。

Red Hat Eco System CatalogDocker Hub上で提供されています。

図8:Red Hat Eco System Catalog 上のUBIイメージ

UBI では、含まれるパッケージの違いにより4種類の UBIが提供されています。init (別名 Multi-service) 、 Standard(別名Platform)、Minimal、および Microと呼ばれるものが存在しています。

これらの 4種類のタイプのベース・イメージに対して RHEL7ベースのものと、RHEL8ベースのものがあり、また、原稿執筆時点では、ベータ中ですが、RHEL9 のベース・イメージのコンテナも既に存在しています。

Docker Hub で提供されているのは、現時点で最新版のRHEL8のベース・イメージのみです。

UBIで使用可能な RPMパッケージも提供されており、これらは一般的にアプリケーションの依存関係を満たすように選ばれた RHEL の RPM のサブセットになっています。Red Hatにより管理されており、RHEL の RPMと同じアップデートが行われ、ライフサイクルも同じです。

もちろん RHEL 用の RPM も追加可能です。注意点としてUBIのレポジトリの RPM を超えて、RHELのレポジトリにあるRPMを追加した場合は、再配布ができません。ただし、Red Hat Partner Connect Program に入る等要件を満たす事で、パートナー様のソリューションのコンテナ・イメージとして再配布する事ができるようになっています。

UBIに関しては、 こちらからダウンロードできる ebook がわかり易く、さらに詳しいので、興味のある方はご参照下さい。

Docker Hub にある RHEL8 の UBI イメージの Standard をダウンロードして実行してみます。レポジトリ名は redhat/ubi8 です。

$ docker run -itd --name my-rhel8 redhat/ubi8   
1a864627291e1119d75598730dbf258bfa821e293a37e9ce1ed5cd54d6a7c03a
$ docker ps  
CONTAINER ID   IMAGE         COMMAND       CREATED          STATUS          PORTS     NAMES
1a864627291e   redhat/ubi8   "/bin/bash"   23 seconds ago   Up 22 seconds             my-rhel8
$ docker attach my-rhel8   
[root@1a864627291e /]# cat /etc/redhat-release 
Red Hat Enterprise Linux release 8.5 (Ootpa)
[root@1a864627291e /]#

/etc/redhat-release の内容から RHEL 8.5がベースになっている事がわかります。

4種類のベース・イメージは、含まれているコンポーネントが違うので、それぞれサイズが異なります。

図9:4種類のUBIイメージ

これらの4種類のタイプのベース・イメージを用意する事で、開発者は自分の最適にあったベース・イメージを簡単に選べるようになっています。

UBI のサポート

RHELのコンテナ・イメージであるUBIイメージは、無料でダウンロードでき、再配布も可能ですが、Red Hat からサポートを受けるには、UBI を稼働させる RHEL もしくは、OpenShift の有償のサブスクリプション契約が必要になります。

UBIイメージは、サブスクリプションを購入して頂いている RHELもしくは OpenShift の上で稼働させた時のみに Red Hat のサポートが提供されるようになっています。

また、OpenShift は、ソフトウェアとしての提供だけではなく、IBM Cloud、AWS、Azure や、Red Hat のパートナー様が提供するマネージド・サービスとしても提供が行われていますが、UBIを使用して作成されたRHELコンテナは、もちろんこれらのマネージド・サービス上でもサポートされます。

図10:UBIがサポートされる環境

もちろん、理論的には、UBIを使って作成されたコンテナは、他の Linux ディストリビューションのホストOSの上でも稼働するはずです。

ですが、その場合は、Red Hat からのサポートは提供されない事になります。反対に、他の Linux ディストリビューターのコンテナ・イメージを RHEL もしくは OpenShift (RHEL CoreOS) の上で稼働させた場合も、稼働はするはずですが、そのコンテナは Red Hat のサポート対象ではない事に注意してください。

OpenShift に含まれる RHEL のエンタイトルメント

OpenShift上で稼働させる事のできる UBIイメージを使ったコンテナの数にソフトウェアのエンタイトルメントの観点での制限はありません。

リソースの物理的な上限の観点を別にすれば、幾つでもRHELのコンテナ(UBI)を稼働させる事ができます。もともとコンテナはアプリケーションのプロセスをパッケージしたものです。一つの RHEL ホストOSの上で幾つプロセスを稼働させても、RHEL のエンタイトルメント的には同じである。と考えるとイメージしやすいかもしれません。

OpenShift を使用するにあったっての必要なサブスクリプションは、OpenShift の Worker Node が使用する Core 数でカウントされます。その上で稼働するコンテナ数には依存しません。

また、RHEL には、Application StreamsSoftware Collectionsと言った、アプリケーションの構築に必要な基本ソフトウェアのエンタイトルメントが付属しています。

Application Streams は、RHEL 8 で導入された、RHEL 7 の Software Collections を引きつぐ新しい概念です。これらのエンタイトルメントは OpenShift 上で使用するUBIコンテナにも同様に付属しています。

図11:OpenShift に含まれる Software のエンタイトルメント

Application Streams / Software Collections は、具体的には PHPPythonPerlNode.jsRuby や、 MySQL ,  MariaDB , や Redis 等のソフトウェアコンポーネントのパッケージです。

さらに OpenShift では、RHEL には含まれていない JBoss Web Server と、 Quarkus のエンタイトルメントも含まれています。

OpenShift とその関連製品のサブスクリプションに何が含まれるのかは、こちらの Self-managed Red Hat OpenShift sizing and subscription guide で公開されています。

まとめ

コンテナの用途がエンタープライズ向けにも進出するようになってきて、コンテナ自体の信頼性やサポートもエンタープライズレベルのものが求められるようになってきました。

コンテナというと Kubernetes のディストリビューションをどこにしよう。という事に注目が行きがちですが、一番大事なのは、その上で動くLinux アプリケーションだと思います。

ホストOSから、コンテナのベース・イメージ、そこに追加する各種ソフトウェアモジュールのスタックは、Linux そのものなので、使用する Linux のディストリビューションの選択が必要です。

OpenShift は、Kubernetes のエンタープライズ向けのディストリビューションという役割のソフトウェアというだけではなく、その上で実行する RHEL のコンテナ を作成するために必要なエンタイトルメントとサポートまで全て含まれています。

RHEL は、エンタープライズ視点で求められる実績、信頼性、サポートを提供してきたLinux ディストリビューションです。コンテナ環境でも安心して御使用頂ければと思います。

花田 祐樹
Red Hat Solution Architect / OpenShift Evangelist

 

SIerでの社内システム開発、サービス部隊でのお客様システム構築等の経験を経た後、運用管理製品、仮想化製品のプリセール、インフラのアーキテクトとして幅広い製品の提案活動を行う。コンテナ環境に未来を感じ、現在は、Red Hat社のソリューション・アーキテクトとしてOpenShift製品の普及のために活動中。

 

More IBM Cloud Blog stories

2022年6月21日 15時43分頃に発生したIBM Cloud Internet Servicesの障害について

IBM Cloud Blog, IBM Cloud News

日本時間2022年6月21日 15時43分頃よりIBM Cloud Internet Servicesで障害が発生しました。 これにより同サービスをご利用いただいているお客様では、IBM Cloudに接続できない事象が生 ...続きを読む


IBM Cloud の ISMAP 対応状況について(2022年6月)

IBM Cloud Blog, IBM Cloud News

IBM Cloud の ISMAP * への最新の対応状況をお伝えいたします。 *ISMAP は、政府情報システムのためのセキュリティ評価制度(Information system Security Management ...続きを読む


IBM Cloudにも入ってる「インテルOptane」とは?

IBM Cloud Blog, IBM Cloud News

 ハイブリッドクラウド&AI戦略において、IBMはインテルとのパートナーシップ・アライアンスのもと、オンプレミス、エッジ、パブリッククラウドに至るまで、多くのソリューションを展開しています。今回、ソリューション・ブログ連 ...続きを読む