🗂

ec2上で動かすUBIコンテナから、RHELのリポジトリを利用する方法

2022/03/22に公開

Red Hat Universal Base Images (以下UBI)というコンテナイメージで、RHELのパッケージを利用したコンテナを動作させることができます。
ただし、これをec2上で利用するには若干注意が必要です。

Red Hat Universal Base Image

UBIについては、以下のページをご覧ください。
https://rheb.hatenablog.com/entry/2020/06/11/RHUBI-subscription

この記事で触れることの関連ポイントは以下の2点です。

RHEL 7および RHEL 8の基本的なコンテナイメージと、さらにRHELの約1/3のrpmパッケージをEULA契約のみで無償で入手でき、さらに自由に利用・再配布できる

RHEL上でUBIを利用すると 自動的に RHELのリポジトリを利用できるようになります

逆の捉え方をすると、RHEL上での利用でなければ、RHELのリポジトリは利用できず、1/3のパッケージのみしか利用できません。
1/3に含まれないものとしては、例えば、上記リンク先の記事にあるようなpostgresqlは含まれません。私が利用しようとして気付いたところでは、squidやstraceなども利用できません。結構利用できないものは多い印象です。

そして、ec2上で利用する場合、RHEL上での利用であっても、上記リンク先のように簡単にはRHELのリポジトリを利用できないケースがあります。以下、それぞれの場合について、記載します。

時間課金のRHELインスタンスで利用する場合

ec2上でRHELを利用する場合、これが最もお手軽な利用方法かと思います。このインスタンス上でUBIを利用する場合、上記記事のように自動的にRHELのリポジトリを利用できるわけではなく、subscription-managerを利用して登録されたRHEL上の場合にのみ、自動的に利用できるようになります。
従って、自動的に利用するためには、サブスクリプション代金が利用料に含まれたec2料金を支払っているにも関わらず、サブスクリプションの登録が必要となります。なお、標準AMIから起動した時間課金のRHELインスタンスでは、subscription-manager registerコマンドで登録するだけでなく、/etc/rhsm/rhsm.confのmanage_reposを0から1に変更する必要もあります。
なお、RHELインスタンス上のUBIコンテナではサブスクリプションを登録することはできません。実行すると以下のように表示されます。

[root@4aeb092c0afd /]# subscription-manager register
subscription-manager is disabled when running inside a container. Please refer to your host system for subscription management.

ただし、もちろん二重で課金されるような方法ではなく、時間課金のインスタンスであれば、ホスト側で利用できるAWSが提供するリポジトリがUBI側でも利用できて良いはずです。これをするためには、ホスト上でdnf download rh-amazon-rhui-clientコマンドでRPMパッケージをダウンロードし、UBIコンテナ側にインストールします。
インストールするとAWSが提供するリポジトリがリポジトリリストに追加されますが、この状態でdnf/yumコマンド実行しても、以下の表示で応答無し状態となります。

Updating Subscription Management repositories.
Unable to read consumer identity

This system is not registered with an entitlement server. You can use subscription-manager to register.

この理由は以下の2点です。

  • rh-amazon-rhui-clientにより導入されるdnf pluginでは、インスタンスメタデータから情報を取得する
  • コンテナ環境では、ホップ制限が1の場合、IMDSv2は応答しない

コンテナ環境では、ホップ制限が 1 の場合、コンテナへの到達は余分なネットワークホップと見なされるため、IMDSv2 応答は返されません。IMDSv1 へのフォールバックプロセスとその結果として生じる遅延を回避するために、コンテナ環境でホップ制限を 2 に設定することをお勧めします。

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html

したがって、インスタンス起動時の設定で、メタデータトークンレスポンスのホップ制限をデフォルトの1から、2以上の値にする必要があります。この設定は、インスタンス起動後には変更できません。

こうして起動したインスタンス上で、UBIコンテナの中から、AWSのリポジトリが利用できるようになります。

BYOSのRHELインスタンスで利用する場合

短時間の利用ではなく、長期間一定ののリソースで利用する場合には、ライセンスを別途調達し、BYOSで利用した方が低コストとなります。エンタープライズ用途であれば、こちらのケースも多いでしょう。
BYOSでの利用については下記ページをご覧ください。
https://project-flora.net/2021/02/12/awsrhelを使う時、サブスクリプションモデルは見落と/

ポイントは、記事にも記載の通り、AMI名にAccess2と入ったイメージを利用することです。

RHEL-8.3.0_HVM-20201031-x86_64-0-Access2-GP2

ただし、何もしていないAWSアカウントではこのAMIイメージを見つけることはできません。
これは有効なサブスクリプションを有した状態で、Red Hat Customer PortalのCloud Accessから利用するAWSアカウントを登録することによって、プライベートイメージから参照できるようになります。

このイメージを利用する場合には、ホストOSで、subscription-manager registerで登録することで、RHELのリポジトリが利用できるようになり、この状態で起動したUBIコンテナは冒頭の記事の通り、自動でRHELリポジトリが利用できる状態となります。UBIコンテナでRHELのリポジトリを使う上では最も簡単な方法です。

RHEL以外のLinuxでUBIを利用する場合

ec2であれば開発や検証用途としては、Amazon Linuxを利用する場合も多いのではないかと思います。一方で、本番向けのコンテナイメージとしては、UBIを使うことを想定し、Amazon Linux上でUBIを使いたいケースもあるのではないでしょうか。この場合、2つの利用パターンがあります。

Red Hatのリポジトリを利用する場合

上で、「RHELインスタンス上のUBIコンテナではサブスクリプションを登録することはできません」と記載しましたが、Amazon Linux上で動作させるUBIコンテナでは、通常通りsubscription-manager registerで登録することができます。これによりRHEL提供のリポジトリが利用可能となります。

AWSのリポジトリを利用する場合

これはライセンス上やって良いのか不明ですし、後述の通りこれをやる利点はほぼありません。

時間課金のRHELインスタンスでリポジトリが利用できる仕組みについては、下記のページをご覧ください。
https://aws.typepad.com/aws_partner_sa/2015/01/rhel_on_aws_update_server.html

rh-amazon-rhui-clientというRPMパッケージで導入されるdnf/yumのプラグインがdnf/yumの実行時に動作して、インスタンスメタデータから取得した情報をアップデートサーバに送信して、パッケージが取得できます。
この処理が実装されているのが、/usr/lib/python3.6/site-packages/dnf-plugins/amazon-id.pyです。

送信している情報は、インスタンスメタデータから取得する以下2つの情報です。取得内容は下記のコマンドで確認できます。

TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/dynamic/instance-identity/document
TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
&& curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/dynamic/instance-identity/signature

まずは、「時間課金のRHELインスタンスで利用する場合」での記載と同様に、rh-amazon-rhui-clientパッケージをUBIコンテナ上にインストールします。
次に、別に動作しているRHELインスタンス上で下記のコマンドを実行して、X-RHUI-IDとX-RHUI-SIGNATUREの値を取得します。

echo "X-RHUI-ID=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document|base64|tr -d '\n')"
echo "X-RHUI-SIGNATURE=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/signature|base64|tr -d '\n')"

取得した情報を用いて、UBIコンテナ上のamazon-id.pyのコードを書き換えます。不要な箇所はがっつり削除しています。
下記のコード中で、環境ごとに書き換える箇所は以下の3点です。

  • ID_DOC_HEADER_VALUE
  • ID_DOC_SIG_HEADER_VALUE
  • AWS_REGION
amazon-id.py
import dnf

ID_DOC_HEADER = "X-RHUI-ID"
ID_DOC_HEADER_VALUE = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
ID_SIG_HEADER = "X-RHUI-SIGNATURE"
ID_SIG_HEADER_VALUE = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
AWS_REGION = "us-west-2"

# Instance from one region will be redirected to another region's CDS for content
REDIRECT_MAP = {'us-gov-west-1': 'us-west-2', 'us-gov-east-1': 'us-east-2'}

class AmazonID(dnf.Plugin):
    name = "amazon-id"

    def config(self):
        headers = [
            "{header}: {value}".format(header=ID_DOC_HEADER, value=ID_DOC_HEADER_VALUE),
            "{header}: {value}".format(header=ID_SIG_HEADER, value=ID_SIG_HEADER_VALUE)
        ]

        # Add the headers to all RHUI repos
        for repo in self._rhui_repos():
            repo._repo.setHttpHeaders(headers)
            self._fix_rhui_url_template(repo, AWS_REGION)

    def _fix_rhui_url_template(self, repo, region):
        if region in REDIRECT_MAP:
            region = REDIRECT_MAP[region]

        if repo.baseurl:
            repo.baseurl = tuple(
                url.replace('REGION', region, 1) for url in repo.baseurl
            )
        elif repo.mirrorlist:
            repo.mirrorlist = repo.mirrorlist.replace('REGION', region, 1)
        else:
            raise dnf.exceptions.RepoError("RHUI repository %s does not have an url" % repo.name)

    def _rhui_repos(self):
        for repo_name, repo in self.base.repos.items():
            if 'rhui-' in repo_name:
                yield repo

本来は、インスタンスメタデータから取得する処理ですが、上記のように静的に埋め込み、インスタンスメタデータへのリクエスト部を削除してしまえば、「時間課金のRHELインスタンスで利用する場合」に記載したように、メタデータトークンレスポンスのホップ制限の設定を変更する必要もなく、AWSのリポジトリがUBIコンテナから利用できるようになります。

開発用途のRHELサブスクリプションは16システムまで無償

下記の記事の通り、開発用途のRHELサブスクリプションは無償で利用可能です。
「お客様の開発チーム向け無料 RHEL」の続報が見つけられませんでしたが、サポートが必要な大規模な本番環境用途でなければ無償と考えて良いのではないでしょうか。
https://www.redhat.com/ja/blog/new-year-new-red-hat-enterprise-linux-programs-easier-ways-access-rhel

小規模本番環境用途や検証/開発用途でCentOSを使用したり、時間課金ののec2インスタンスを使うのはやめましょう。上の施策自体がCentOSの方針変更を受けて、より気軽にRHELが利用できるように導入されたものです。

私は検証用途では基本的にオレゴンのスポットインスタンスを使います。残念ながらRHELではnanoが選択できないので、t3a.microが最小課金となります。
これは、Linuxでは1時間当たり0.0028ドル≒0.336円程度です。AWS課金のRHELにすると、1時間当たり0.0628ドル≒7.536円程度です。
その比率実に22倍以上。これが無駄遣いでなくして、何が無駄遣いと呼べるでしょうか。

本題からは外れますが重要なことなので、繰り返します。
「Hourly2」という文字を含むAMIを使ってはいけません。
パブリックイメージのRHELを使ってはいけません。
Red Hat Enterprise Linux 開発者プログラムに登録し、BYOSのRHELを使用しましょう。
https://access.redhat.com/products/Red_Hat_Enterprise_Linux/Developer/

結論

ec2上で動かすUBIコンテナからRHELのリポジトリを利用する最も良い方法は、「BYOSのRHELインスタンスで利用する」ことです。
サブスクリプションは開発者プログラムで無償で取得しましょう。
それ以外の方法に特段のメリットは感じません。

Discussion