ROSA の CDO(Custom Domain Operator) を使用する

# CDO (Custom Domain Operator) とは

:::note warn
また RSOA 4.14 (2024年10月GA) からは、OpenShift 標準の IngressController によってユーザーの独自ドメイン用の IngressController を作成するようになりました。 

また、この記事は ROSA Classic と呼ばれるタイプのアーキテクチャーの ROSA の記事です。ROSA HCP は OCP 4.14 以降を採用していますので、CDO (Custom Domain Operator)は存在していません。

CDOの代わりに[標準のIngressControllerを使って、同様の事をする記事をこちらに](https://qiita.com/Yuhkih/items/67c61418db4dac463ace)書きました。
:::


ROSA では、2つのタイプの ingress (ユーザーのHTTP/S トラフィックを Service まで導く仕組み) が提供されています。

`ingress` と言うと、Kubernetes のリソースの定義としての `kind: Ingress` の意味もありますが、ここでは HTTP/S トラフィックを誘導する機能としての ingress という意味で、頭を小文字にして `igress` という言葉を使用します。

**ROSA デフォルトのingress**
1つめの Ingress は、単純に "Ingress"と呼ばれていて、特別な呼び名はありません。Cluster作成時に最低でも1つがデフォルトで存在し"default" という名前が付いてます。もう一つ別の名前を付けて追加する事ができます。これらはSREが管理しているオブジェクトです。

`*.apps.<ユーザーが付けたROSAクラスター名>.<ランダム文字>.<ランダム文字>.openshiftapps.com` 
例：*.apps.mycluster.dzfa.p1.openshiftapps.com

のようなフォーマットになります。`openshiftapps.com` は、Red Hat が取得管理しているドメイン名で、そのサブドメインを ROSAサービスを購入しているユーザーに割り当てている形になります。

ユーザーアプリを外部公開する時は、この `*` の部分を好きな文字列にして提供できます。DNS (Route53)上に `*`で登録されているのでユーザーは個別の DNSレコードを登録する必要はありません。

OpenShift のコンソールや、OAuth サーバーなどの基本機能もこのドメインで提供されます。

このデフォルトの ingress は、`rosa` コマンドで以下のように表示する事ができます。（CDOはできません）

```
$ rosa list ingress -c rosa-cluster
ID    APPLICATION ROUTER                                         PRIVATE    DEFAULT    ROUTE SELECTORS
z9l9  https://apps.rosa-cluster.70d7.p1.openshiftapps.com       no         yes
$
```

**CDO(Custom Domain Operator)によるingress**
2つ目の Ingressは、CDO (Custom Domain Operator)と呼ばれています。ドキュメント上は[アプリケーションのカスタムドメインの設定](https://access.redhat.com/documentation/ja-jp/red_hat_openshift_service_on_aws/4/html-single/application_development/index#osd-config-custom-domains-applications)で言及されている部分になります。

この ingress のドメイン名は、`*.example.com` のように独自ドメイン（ユーザーが取得したドメイン)が使われます。
ここで使用するドメイン名はユーザーが取得したものを使用する前提になります。

ユーザーが作成し、外部に公開するアプリは `test1`.example.com、`test2`.example.com などのドメインになります。同じベースドメインで複数のアプリを公開する時に便利です。ワイルドカード証明書を使用するため、アプリ毎に証明書を管理しなくて良いというメリットもあります。

CDOを使用するに当たって以下が必要になります。

1. `example.com` のような独自ドメインの取得
2. 上記の独自ドメインに対する証明書
3. 作業用の dedicated-admin 権限を持つユーザーアカウント


`dedicated-admin` 権限は ROSAクラスターの管理者権限です。

この作業では、管理者が提供する独自ドメインを選定し、そのドメインを * (ワイルドカード) で使用できるようにした ingress 環境を作成します。アプリケーション開発者は、*の部分を好きな名前にしたアプリをHTTPSで提供できるようになります。

ちょっと細かいですが、ROSA の default ingress と、CDO(Custom Domain Operator) による ingress の違いを図示したものが以下になります。薄い緑色の枠で塗っている部分がユーザーが何らかの作成作業をしなければいけない部分を示しています。それ以外の部分は自動で生成される部分です(オレンジは AWSリソース、薄い青は ROSAのリソース)。
![rosa-cdo-ingress2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/6f0b0bf6-9675-389d-727e-85bce17233cd.png)

上図では、`*.apps.<ユーザーが付けたROSAクラスター名>.<ランダム文字>.<ランダム文字>.openshiftapps.com` は、長いので `*.apps.<cluster-domain>`と省略しています。

:::note info
2023/03/29 追記： 現在 CDO の内部で使用する ELB の種類として CLB の他に NLB が選択できるようになりました。 CDOの使い勝手を広げる機能追加なので別途、解説記事を書ければ...と思っています。
:::

# CDO のインストール

ROSA では、CDO(Custom Domain Operator) はデフォルトでインストールされているため、デフォルトでは Operator 自体のインストール作業は必要ありません。

ただし、ingress オブジェクトを生成するには、`CustomDomain` という `kind` の Custom Resrouce を作成する必要があります。

Custom Domain Operator がこの `CustomDomain` リソースの作成を検知して、実際の ingress 関連のオブジェクトを作成してくれる仕組みになっています。



# 独自ドメインの取得

`example.com` は取得する事はできないので、ここでは、`ocp4.work` というドメインを お名前.com で取得しました。そのため、取得したドメインは、最初は、お名前.com の DNSサーバーによって管理されています。

そのままでも良いのですが、Route53ユーザーが多いと思うので、お名前.com から Route53 にドメイン(Zone) の管理を移管します。

まず、AWS Route53 に Zone を作成してホストする事にします。詳細な手順は省略しますが、Route53 上に取得した自分のドメインの Zone を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/7296c3f6-7100-0f44-3758-c4cd4ae535b2.png)



Route53上でゾーン(ドメインの入れ物) を作成すると、画面に、そのゾーンを管理する NSレコード (DNSサーバーのアドレス) が表示されます。上記の表示の `ns-711.awsdns-24.net` `ns-1066.awsdns-05.org` .... がそれです。可用性を保つために複数のDNSサーバーが提供されます。


Route53で提供された DNSサーバー名をお名前.com 側の管理ツールに入力して、暫く待ちます。
以下は お名前.com の DNSサーバーの設定変更画面です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/68f64d94-c53e-bdc2-c7ea-05c5c7716f97.png)

以下のコマンドを指定して、NSレコードの状態を確認できます。

```
$ dig <取得したドメイン名> ns +short
```

指定した DNSサーバーに変更されるまで待ちます。

親となるドメインの複数のDNSサーバーに対して情報更新を行う(しかもその親は複数のドメインの親である）ので、暫く時間がかかる事があります。私の場合は1時間ほどで更新されました。

```
$ dig ocp4.work ns +short
01.dnsv.jp.                   # 初期状態。ドメイン取得元(お名前.com) の NS サーバーが表示されている。
03.dnsv.jp.
02.dnsv.jp.
04.dnsv.jp.
$

<お名前.com 側の Interface で、Route53から提供された DNSサーバー名を入力>
<待つ事およそ一時間 (管理コンソールのメッセージによると24時間くらいかかる事もあるとの事) >

$ dig ocp4.work ns +short
ns-1781.awsdns-30.co.uk.  # AWSが提供する DNSサーバーが表示されるようになる。
ns-711.awsdns-24.net.
ns-51.awsdns-06.com.
ns-1066.awsdns-05.org.
$ 
```

Route53 の DNSサーバーの値が NS レコードとして返ってくれば Route53での取得ドメインのホストが開始されています。

`dig` コマンドの `+short` というオプションは出力を簡略化してくれるオプションなので覚えて置くと便利です。



# 証明書の取得

今回は、Let's Encrypt で証明書を作成します。

:::note warn
AWS環境だと ACM (AWS Certificate Manager) で、証明書が発行をしたくなりますが、ACMは、AWSのサービスである ELBやCloudFront 等のサービスに対してのみ発行できる仕組みです。EC2上等で稼働する一般のアプリケーションに対して証明書を発行(証明書と秘密鍵をファイルとして Export する）機能は持っていません。
:::


**作業環境を整える**

後のコマンドがコピペで済むようにするため、必要な情報を環境変数にセットします。
この例では、`ocp4.work` という独自ドメインを取得したので、それを使用します。

```
$ CERT_DIR=~/mycert
$ mkdir -p $CERT_DIR
$ EMAIL="myemail@test.com"             # 有効な email アドレス
$ DOMAIN="ocp4.work"                   # 取得した独自ドメイン
```


**Let's Enrypt証明書の取得に必要なソフトウェアのインストール**

certbot と呼ばれる Let's Encrypt のモジュールを適当な環境にインストールします。インターネットに接続できる環境である必要があります。作業手順を考えると oc コマンドが実行でき ROSAクラスターにアクセスできるようにセットアップされた環境が便利です。

私の環境は Amazon Linux 2 の環境なので、[こちらの公式ガイド](https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/SSL-on-amazon-linux-2.html#letsencrypt)を見ながら以下のコマンドを実行しました。

```
$ sudo wget -r --no-parent -A 'epel-release-*.rpm' https://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/
$ sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm
$ sudo amazon-linux-extras install epel -y
$ sudo yum install -y certbot python2-certbot-apache 
```


**証明書取得の開始(certbot コマンドの実行)**

certbot の実行。証明書は `*.` でワイルドカード証明書を取得しています。

```
certbot certonly --manual \
   --preferred-challenges=dns \
   --email $EMAIL \
   --agree-tos \
   --config-dir "$CERT_DIR/config" \
   --work-dir "$CERT_DIR/work" \
   --logs-dir "$CERT_DIR/logs" \
   -d "*.$DOMAIN" 
```

:::note warn
<details><summary>テストで何度も証明書を発行を繰り返す可能性がある場合の注意</summary>


一週間に5回以上、同じリクエストをすると Rate Limit にひっかかり、以下のようなエラーがでる事があります。

```
too many certificates (5) already issued for this exact set of domains in the
last 168 hours: example.com login.example.com: see https://letsencrypt.org/docs/duplicate-certificate-limit
```

この事象については、[こちらのブログ](https://letsencrypt.org/docs/duplicate-certificate-limit/) に記載があります。

発行対象の名前を変更する方法もありますが、回避策の一つとして、テスト用に何度も発行する事が想定する時は、` --test-cert` オプションを付けておく方法があります。

```
certbot certonly --manual \
   --preferred-challenges=dns \
   --email $EMAIL \
   --agree-tos \
   --config-dir "$CERT_DIR/config" \
   --work-dir "$CERT_DIR/work" \
   --logs-dir "$CERT_DIR/logs" \
   -d "*.$DOMAIN" \
   --test-cert
```

ただし、この場合の Root CA は通常のブラウザなどには含まれてないものになりますので、curl だと -k オプションを使用するか、Root CA の証明書をダウンロードしておく必要があります。[こちらの記事](https://letsencrypt.org/docs/staging-environment/) に詳細の解説があります。

</details>
:::

certbot コマンドを実行すると以下のような出力が出てきます。**慌てて Enter を押さず、一回止まって指示を良く読みます。**

```
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y　　　　　　　　　# ここは お好みで。
Account registered.
Saving debug log to /home/ec2-user/mycert/logs/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Requesting a certificate for *.ocp4.work
Performing the following challenges:                     # Challenge を実行して下さいと指示をしている。
dns-01 challenge for ocp4.work

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name            # DNS TXT レコードを設定しなさいと指示が出ている。
_acme-challenge.ocp4.work with the following value:      # _acme-challenge.ocp4.work という TXT レコードに

L_BVE3S_cAeHEnodB2xwLCcZYxStOeUutxlTevhX2NQ              # L_BVE3S_cAeHEnodB2xwLCcZYxStOeUutxlTevhX2NQ  という値を設定

Before continuing, verify the record is deployed.        # これ以上進む前に、TXTレコードがデプロイされた確認する。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
```

上記のメッセージを解読すると DNS 上に `_acme-challenge.ocp4.work` という TXT レコードを作成し、そのレコードに`L_BVE3S_cAeHEnodB2xwLCcZYxStOeUutxlTevhX2NQ`  という値を設定しな、レコードがインターネット上から解決できる状態になったら、Enter をクリックして下さい。と言っています。

:::note info
ドメインを管理している DNS に、指定された値を追加する事で、このドメインの編集権限がある持ち主である事を証明しています。証明書の発行母体(この場合 Let's Encrypt)から与えられるこの課題を `Challenge` と呼びます。証明書を発行してもらうには、この`Challenge`に応える必要があります。

独自ドメインをホストするDNSサーバーは、闇で立てたDNSサーバーのようなものは使う事はできず、ドメインを購入したレジストラ等で「このDNSサーバーでホストします」登録する必要があります。[example.com]を取得した場合は、上位の[.com] の DNS権威サーバーに、子ゾーンである [example.com] の DNS権威サーバーのアドレスを登録する必要があります。

今回の例では、お名前.com(レジストラ）の設定画面で、Route53 の DNSサーバー名を登録した作業がこれに当たります。[ocp4.work]というドメインを取得しましたが、[.work]の権威DNSサーバーに[ocp4.work]をホストする権威DNSサーバーとして、Route53のDNSサーバーを登録した事になります。
:::

**DNSへのレコードの登録**

今回の場合は、独自ドメイン`ocp4.work`の権威DNSサーバーを、Route53にしているので、Route53の該当画面に飛び、指定された TXT レコードを登録します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/5acc7db4-e6f6-708b-8557-9462cbac9232.png)

レコードを登録したら、以下のコマンドで値が返ってくるまで待ちます。

```
dig (_acme-challenge.ではじまる指定されたドメイン名)  TXT +short 
```

どのくらいでレコードが反映されるかは、事業者毎によって違います。[Route53の場合は、通常1分程度](https://aws.amazon.com/jp/route53/faqs/) で反映されるようです。

```
$ dig _acme-challenge.ocp4.work TXT +short      # TXT レコードを指定
$                                               # 何も出てこないので暫く待つ。
<暫く待つ>
$ dig _acme-challenge.ocp4.work TXT +short       
"45D1lL4FxRuV__fBdgApFH9uNtNixDiSLzSs0duC0Lk"   # 登録した TXT レコードが表示される (状況によりますが、今回は1分程度で出てきました）
```


**certbot コマンドの実行画面に戻る**

TXT レコードが表示されるようになったら、certbotコマンドを実行していた画面で Enter を実行します。
端末によって DNSレコードの反映に少し時間差がある場合もあるので、あまり急いでテキパキと操作をしない方が良いです。(challenge に失敗すると certbot の実行からやり直しになり、DNSへの登録もやり直しになります)

```
<省略>
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name            # DNS TXT レコードを設定しなさいと指示が出ている。
_acme-challenge.ocp4.work with the following value:      # _acme-challenge.ocp4.work という TXT レコードに

vl-6-7guKT4ShQS0OtNLBp1VtNgHQdiu1VpbFVeVC6I              # vl-6-7guKT4ShQS0OtNLBp1VtNgHQdiu1VpbFVeVC6I  という値を設定

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Waiting for verification...
Cleaning up challenges
Non-standard path(s), might not work with crontab installed by your operating system package manager

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:      # Challenge 成功です。
   /home/ec2-user/mycert/config/live/ocp4.work/fullchain.pem            # 証明書は左のパスに保存されています。
   Your key file has been saved at:
   /home/ec2-user/mycert/config/live/ocp4.work/privkey.pem　　　　       # 秘密鍵は左のパスに保存されています。
   Your certificate will expire on 2023-03-27. To obtain a new or
   tweaked version of this certificate in the future, simply run
   certbot again. To non-interactively renew *all* of your
   certificates, run "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

$ 
```

これで証明書(=公開鍵を含んだ物体)と秘密鍵が生成されました。
念のため、生成された書かれているディレクトリを確認してみます。

```
$ ls $CERT_DIR/config/live/$DOMAIN
README  cert.pem  chain.pem  fullchain.pem  privkey.pem
$
```

それっぽいものが生成されているのがわかります。

ここのファイルが何かを解説するには、Webの証明書の仕組みを解説する必要があるので、説明は省略しますが、私達が注目すべきは、`fullcahin.pem` と `privkey.pem` の2つのファイルです。

# CustomDomain オブジェクトの作成

**作業環境を整える**

ドメインと証明書が準備できたので、いよいよ CustomDomain の作成に入ります。まずは、作業用に `my-custom-domain` という新しいプロジェクトを作成します。

```
$ oc new-project my-custom-domain
```

後々のコマンドを楽にするために、変数に証明書があるパスを設定しておきます。

```
$ MY_CERT=$CERT_DIR/config/live/$DOMAIN
```

**Secretの作成**

証明書を入れた Secret を作成します。

```
$ oc create secret tls my-tls --cert=$MY_CERT/fullchain.pem --key=$MY_CERT/privkey.pem -n  my-custom-domain
secret/my-tls created
$ 
```

**CustomDomain のオブジェクトの作成**

作成した Secret を内部に指定する形で、Kind : `CusutomDomain` のリソースのオブジェクト作成します。

ここでは、`CusutomDomain` オブジェクトの名前は `acme` と付けました。
(ACME（Automatic Certificate Management Environment) は、Let's Encrypt の証明書発行するための環境を指す言葉です。)

ヒアドキュメントで書いた定義をそのまま apply してしまいます。

```
cat << EOF | oc apply -f -
apiVersion: managed.openshift.io/v1alpha1
kind: CustomDomain
metadata:
  name:  acme
spec:
  domain: $DOMAIN
  certificate:
    name: my-tls
    namespace: my-custom-domain
EOF
```

暫く待つと以下のように `Ready` になります。以上で完了です。

```
$ oc get customdomains
NAME   ENDPOINT                                         DOMAIN      STATUS
acme   qgeenq.acme.singleaz.dzfa.p1.openshiftapps.com   ocp4.work   Ready
$ 
```

## CustomDomain リソースの作成で生成されたものを確認してみる

すこしアドバンスな内容なのではじめのうちは気にしなくて良いですが、CustomDomain オブジェクトを作成すると、裏でいろいろなオブジェクトが作成されています。

どのようなオブジェクトが生成されているのかリストしてみました。内部構造を知りたくなった時にこの部分を参照して見て下さい。

<details><summary>CustomDomain リソース作成により、生成されたオブジェクト群</summary>

**Service**

まずは、どのような Service リソースのオブジェクトが作成されたか確認してみます。

`openshift-ingress` プロジェクト内に、`Service type=LoadBalancer` と、`Server type=ClusterIP` のオブジェクトが作成されています。(NAME に -acme と入っているのがそれです。それ以外は元から存在するものですが、環境によって異なります）

`Service type=LoadBalancer` の EXTERNAL-IP部分は、`afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com`　と AWS の LoadBalancer のドメインが紐付いてる事がわかります。この LoadBalancer (CLB) も CustomDomain リソースの作成で生成されたものです。

```
$  oc get svc  -n openshift-ingress
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP                                                                             PORT(S)                      AGE
router-acme               LoadBalancer   172.30.85.219    afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com             80:32618/TCP,443:31941/TCP   2m28s   # 新規に作成された
router-default            LoadBalancer   172.30.237.168   internal-a93f5c9ec983f4c87aca4abbedf0ba3b-1240512026.ap-northeast-1.elb.amazonaws.com   80:31404/TCP,443:32229/TCP   7h47m
router-internal-acme      ClusterIP      172.30.205.43    <none>                                                                                  80/TCP,443/TCP,1936/TCP      2m28s    # 新規に作成された
router-internal-default   ClusterIP      172.30.25.151    <none>                                                                                  80/TCP,443/TCP,1936/TCP      7h47m
$ 
```

**Pod**

以下のPod は OpenShift 用語で router Pod と呼ばれる、ingress の Pod です。
新規に2つの Pod が作成されています。-acme- と NAME 欄に入っているもがそれです。それ以外の router Pod はデフォルトで作成される router pod です。


```
$ oc get pods -n openshift-ingress
NAME                             READY   STATUS    RESTARTS   AGE
router-acme-857b888458-5nfxb     1/1     Running   0          8m24s　　 # 新規に作成されたもの
router-acme-857b888458-9sv7f     1/1     Running   0          8m24s　　 # 新規に作成されたもの
router-default-b8cfbc448-t928m   1/1     Running   0          7h　　　　　　　　　　　　　　
router-default-b8cfbc448-zggxj   1/1     Running   0          7h3m
$ 
```

**ingresscontroller**

こちらは ingresscontroller オブジェクトです。NAME の所に acme と書かれたものが新しく作成されたものです。OpenShift の場合は、実際にトラフィックを処理する Ingerss Pod(=Router Pod)とそれを管理する Ingresscontroller にオブジェクトが別れています。実装によっては Ingresscontroller が Podとして直接トラフィックを処理するものもあります。

```
$ oc get ingresscontroller -A
NAMESPACE                    NAME      AGE
openshift-ingress-operator   acme      9m12s     # 新規に作成されたもの
openshift-ingress-operator   default   8h
$ 
```

**DNSレコード**

黄色のハイライト部分が新規にレコードとして追加されています。`*.acme.<cluster domain名>` のエイリアス先は、CLB のドメイン、`afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com` になっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/80667d3e-2d00-d2a0-f480-5e6b38350729.png)

**CLB(Classic Load Balancer)**

CLB(Classic Load Blanacer)が新規に追加されています。これは前述のDNSレコードに登録されているものです。
真ん中の行の DNS名　`afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com` の部分がそれにあたります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/b1b8aaa9-2253-7400-41b7-361b4fc9d997.png)

</details>

# 独自ドメインをCDOで生成された CLB のドメインに CNAMEする

`CustomDomain` オブジェクトに `acme` という名前を付けたので、`acme` という名前が入った `kind:Service Type=LoadBalancer` が生成されているはずです。LoadBalancer で使われているドメイン名を取得します。
この Service オブジェクトは、openshift-ingress プロジェクト内に作成されています。

```
$  oc get svc  -n openshift-ingress | grep acme | grep  LoadBalancer
router-acme               LoadBalancer   172.30.85.219    afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com             80:32618/TCP,443:31941/TCP   17h
$ 
```

CLB ドメインが `afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com` である事がわかります。

**情報の整理**
この作業で使うドメインは以下の2つです。

[独自ドメイン]： `ocp4.work`  お名前.com で取得し、Route53 に管理を移管したドメイン。
[CDOによって作られたドメイン(CLB名)]: `afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com`

**CNAME**
独自ドメイン `*.ocp4.work` へのアクセスを、CDOによって作られたCLBのドメイン　`afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com`　に誘導するには CNAMEという作業をします。

Route53 の `ocp4.work` ゾーンに以下の黄色でハイライトしている CNAMEレコードを追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/24d1341a-0263-1a60-91d7-36a2d6fe123c.png)

:::note info
Route53 の独自機能でエイリアスという機能があり、それを使用しても上記の CNAMEと同様な事が実現可能です。が、ここでは RFCで定められた標準的な CNAMEを使って設定を行っています。
:::

1分程度したら、*.ocp4.work の * の所を適当な文字列にして、dig で状況を確認してみます。DNS上にレコードとして`*`で登録しているので、どんな文字列でもDNSレコードにマッチして解決されるはずです。(abc **.** def **.** ocp4.work のようなものは **.** が2つあるのでマッチしません)

ここでは、`test.ocp4.work` に対して dig を実行してみます。

```
$ dig test.ocp4.work 

<省略>

;; ANSWER SECTION:
test.ocp4.work.         291     IN      CNAME   afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com.
afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com. 51 IN A 13.113.63.29
afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com. 51 IN A 18.176.74.91

<省略>
```

`test.ocp4.work` は、先ほど設定した CNAME により `afb4ab5e4fcb8450e9705d2f4b4588c1-143380427.ap-northeast-1.elb.amazonaws.com` (CLBのドメイン名) に誘導され、さらに CLBドメイン名がロードバランシング用の2つのIPアドレスに解決されている事がわかります。

この CLIの画面からはわかりませんが、CLBの2つのIPに到達したトラフィックは、さらに OpenShift 上の Router Pod (= Ingress Pod : 実際のHTTP/Sトラフィックの処理を行うPod) にフォワードされるように設定されています。

**Router Podでの処理**

前述のように`[独自ドメイン]->[CLBドメイン]->(CLB IPアドレス) -> (Router Pod IPアドレス)`と言う流れでトラフィックの宛先が変わっていきます。

CustomDomain オブジェクトを作成した時に、証明書の Secret を指定しましたが、この証明書は、Router Pod に渡されています。そのため Router Pod はHTTPSを復号化する事ができます。

Route Pod は、HTTP/Sのリクエストと一緒にやってきた宛先のドメインの情報を見て、どのユーザーの Service (Kbuerentes で定義されているリソース) にフォワードするか決定します。そのためには、ドメイン名とユーザーのService 名の紐付けの設定が書かれたオブジェクトが、アプリケーション毎に別途必要です。これを OpenShift では、`Route`リソースと呼んでいます。 

`Route`リソースは、Kuberentes の `Ingerss` リソースの元になったもので、YAMLの記述内容も Ingress とかなり似たものになっています。

OpenShiftでも`Ingress`リソースが使えるのですが、歴史的に `Route` が先に作成された事もあり、OpenShift の各種ドキュメントは、今でも基本`Route`が前提とした書き方になっています。そのため、敢えて変えるのも混乱するので`Route`をそのまま使うのがおすすめです。

この`Route`は、ユーザーアプリ(PodやService)と一緒に、ユーザーが作成する必要があるリソースになります。次のステップでは実際にサンプルアプリと一緒に`Route`を作成します。

:::note info
ここで Ingress と呼んでいるのは、kind: Ingress (Ingress リソース/リソースの定義) の事で、L7(HTTP/S)トラフィックを取り扱う機能としての ingress の事ではない事に注意して下さい。

Ingress リソースは、ベンダー毎に様々な機能拡張がされ、同じ Ingress リソース (kind: Ingress) もできる事が違っているというのが実態です。もはや Ingress という標準化は意味をなしておらず、個人的には無理に Route を Ingress に置き換える意味もほぼ無いだろうと思っています。OpenShift が Ingress リソースをサポートしつつも、未だに Route をメインに据えているのもこの辺りの背景があるのかもしれません。

また様々な形に派生してしまった Ingress リソースをそのまま発展させていくのも難しいため、それに変わるものとして [Gateway API](https://gateway-api.sigs.k8s.io/) というものが Kubernetes の SIG(Special Interest Group) で開発されており、[様々なベンダーがその実装を公開](https://gateway-api.sigs.k8s.io/implementations/)しはじめています。

:::


# テスト用のサンプルアプリをデプロイしてアクセスを確認する

アクセスすると "Hello OpenShift!" とテキストを返すシンプルな HTTPS アプリをデプロイしてみます。
イメージは、`docker.io/openshift/hello-openshift`  に置かれています。

**Projectの作成**

テスト用に新しい `test-app` という新しい Project を作成します。

```
$ oc new-project test-app   
``` 

**hello-openshift アプリのデプロイとクラスター内への公開**

`oc new-app` コマンドに、イメージ名を指定して、テスト用のコンテナ (hello-openshift)を Pod としてデプロイします。
イメージ名を指定するだけで、 `Service` を作成してクラスター内にコンテナを公開する所までやってくれます。

```
oc newa-app --image=<イメージ名>
```

実際のコマンドは以下の通りです。

```
$ oc new-app --image=docker.io/openshift/hello-openshift
$ oc get svc
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
hello-openshift   ClusterIP   172.30.80.122   <none>        8080/TCP,8888/TCP   4m18s
$    
```

:::note info
通常コンテナイメージを Kubernetes (OpenShift)上にデプロイするには、Pod 化するために Deployment のYAMLを書いたり、Service を作成する必要がありますが、oc new-app は、[イメージか自動的にデプロイに必要なオブジェクトを作成し](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.11/html-single/building_applications/index#applications-create-using-cli-modify_creating-applications-using-cli) Service として公開してくれます。
:::

**hello-openshift アプリのクラスター外への公開**

上記の Service `hello-openshit` を、クラスター外部に公開します。OpenShift では、`oc create route edge` に Service 名を引数指定する事で公開できます。

`route`の主な役割の一つは、Router Pod がHTTP/Sトラフィックを受けた取った時に、受け取ったドメイン名に対して、紐付けされている Service を教える事です。

```
oc create route edge --service=<サービス名> <作成するルート名> --hostname <公開ホスト名>
```
`--hostname` オプションを使用してクラスター外部に公開するドメイン名を指定します。ここに独自ドメインを使ったホスト名を指定します。

今回の例では `*.ocp4.work` にマッチするドメイン名であれば、`--hostname` に、どんな値も指定できます。ここでは、サービス名と同じ `hello-openshift` を使用して `hello-openshift.ocp4.work` というドメイン名で公開する事にします。

:::note info
ここでさりげなく `edge` と指定していますが、これは Ingress Pod (=Router Pod)で HTTPSを終端しそこから先は暗号化しない方法です。この手順の中で作成して Secret に格納した証明書は、内部的に Router Pod に渡されており、Router Pod はそれを使用してHTTPSを終端します。

Router Pod と、ユーザーのアプリ(Pod)の間はクラスター内のプライベートネットワークなので外部に公開されないネットワークです。もし、Route Pod から先も暗号化したい場合は、個別のユーザーの Pod に証明書を入れて別途管理する必要があります。

[edge](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.11/html/networking/nw-ingress-creating-an-edge-route-with-a-custom-certificate_secured-routes#doc-wrapper) 以外にも、[re-encrypt](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.11/html/networking/configuring-default-certificate#nw-ingress-creating-a-reencrypt-route-with-a-custom-certificate_secured-routes) や [passthrough](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.11/html/networking/nw-ingress-creating-a-passthrough-route_secured-routes) というオプションがあります。これらの方法は、いずれもユーザーPod側で別途証明書を組み込む必要があります。
:::

実際のコマンドの実行結果は以下のようになります。

```
$ oc create route edge --service=hello-openshift hello-openshift-tls --hostname hello.$DOMAIN
route.route.openshift.io/hello-openshift-tls created
$ oc get route
NAME                  HOST/PORT                    PATH   SERVICES          PORT       TERMINATION   WILDCARD
hello-openshift-tls   hello.ocp4.work ... 1 more          hello-openshift   8080-tcp   edge          None
$ 
```


**独自ドメイン名でのアクセス確認**

curl で、 `hello-openshift.ocp4.work` にアクセスして確認してみます。

```
$ curl  https://hello.$DOMAIN
Hello OpenShift!
$
```

無事、独自ドメイン名で HTTPS を使用してサンプルアプリにアクセスできました。

サンプルアプリでは、`hello`.ocp4.work という名前にしていますが、証明書は `*` で取得していて、DNSへのドメイン登録も`*`で登録されているので、`*`.ocp4.work にマッチすればどんな名前でも使用可能です。


**サンプルアプリの削除**


プロジェクト毎そこに含まれるサンプルアプリを廃棄するのが一番簡単です。以下のコマンドを実行します。

```
oc delete project <プロジェクト名>
```

実際のコマンドの実行結果は以下のようになります。

```
$ oc delete project  my-app
project.project.openshift.io "my-app" deleted
$ 
```

# routeSelector と namespaceSelector 

2022/10 月頃に routeSelector と namespaceSelector が、kind: CustomDomain に実装されていました。
これにより CDO で作られた ingress の対象となる Route オブジェクトや、namespace を選択できるようになりました。


```
apiVersion: managed.openshift.io/v1alpha1
kind: CustomDomain
metadata:
  name: <company_name>
spec:
  domain: apps.companyname.io 
  scope: External
  certificate:
    name: <name>-tls 
    namespace: <my_project> 
  routeSelector:
    matchLabels:
      route: acme   # このラベルにマッチする Route を拾う
```

この機能は、ingress を経由するユーザーアプリへのトラフィックの向け先を制御できるので sharding (負荷分散) に使用するために使用する事ができます。(尚、routeSelector をテストしてみたのですが、作成していた CustomDomain オブジェクトを一度削除して再作成する必要がありました)

:::note warn
構成によっては ROSA上に、インターネット向けのドメインを持ったアプリだけでなく、プライベートネットワーク向けのドメインを持ったアプリも同時に作成する事もできます。

router pod は、特に何も指定しない場合、ROSA (OpenShift) 上の全ての Route オブジェクトを検索して、外部からのリクエストにマッチするドメイン名を持った Route オブジェクトを探します。

このような場合、「Spoofing」と呼ばれる手法を使って、インターネットから、内向けのドメインへのアクセスを探る事が技術的に可能です。

プライベートネットワーク向けのドメイン名をある程度把握してないと内部向けのドメインを突き止める事は困難ですが、セキュリティリスクにならないように、インターネットに公開されている ingress には、特定の Route (インターネット公開ドメインを持つ Route)だけにしかアクセスできないように routeSelector を使って制御する事をおすすめします。
:::


# 証明書に関する運用上の注意

この記事の中では触れていませんが、証明書には期限があります。そのため独自の証明書を使用した場合は、その証明書を更新し続ける必要があります。

ROSA標準の ingress で使用されている証明書は、SREにより自動的に証明書が期限前に更新されますが、CDOで作成された ingressの証明書はユーザーが取得したものなので、ユーザーが更新する必要があります。

証明書が含まれている Secret を自動更新する Certification Manager というOperator が存在しているので、それについては[別記事](https://qiita.com/Yuhkih/items/0d6c9f42e7dc6b924625)で解説しています。