OpenShiftで独自ドメインを使ったWeb アプリを公開する

# 1.はじめに

`OpenShift` で独自ドメインのアプリを公開してみます。

この手順では、シンプルな HTTP アプリケーション(`Apache` のデモアプリ) を公開してみます。

# 2. OpenShift のドメインでアプリをデプロイする

まずは普通に、`OpenShift` が持っているドメインを使って、アプリをデプロイしてみます。
(その後、独自ドメインに変更します)

この `OpenShift` 環境は
`rosa-cluster.3fof.p1.openshiftapps.com`

というベースのドメインを使用しています。
`OpenShift` は、アプリケーション用には`apps` というドメインを自動的に足し、

`*.apps.rosa-cluster.3fof.p1.openshiftapps.com`

というサブ・ドメインが、OpenShift 上にデプロイしたアプリケーションに使われるようになります。
`*`の所は作成されるアプリ毎に変わります。

`*`の部分はアプリケーション毎に異なるのですが、IPアドレスは同じものが使われます。
同じIPアドレスで一旦受けて、後述する`Hostヘッダー`でどこ行きのトラフィックかを見分けて、`Pod`にトラフィックを振り分けています。


## 2.1 Project を作成する

作業用の `project` を作成します。(`Kubernetes`の`namespace`を拡張したリソースです。)

```
oc new-project myapp1
```

これで作業用の`myapp1` という `project` ができました。

ちなみにこの`project`というリソースは、`kubectl get namespace` でも `oc get project` でもどちらでも表示されます。

`OpenShift` の `project`は、`kubernetes`の`namespace`としても取り扱う事ができるコンバーチブルな`Kubernetes`リソースです。

## 2.2 Apache アプリを作成する

`centos/httpd-24-centos8` という`CentOS8`の `S2I builder image` に `https://github.com/sclorg/httpd-ex` という`GitHub`上のソースコードを足して新しいイメージをビルドします。

`OpenShift`独自の方法ですが、`Dockerfile`無しでカスタマイズされたコンテナイメージを作成できます。

・`S2I`とは、`Source to Image`の略です。
・`S2I builder image`とは、`ベースイメージ`の中に、コンテナのビルド処理をする`S2Iスクリプト`が中に入ったイメージです。


コマンドは以下のようになります。これで`https://github.com/sclorg/httpd-ex`というソースコード (単純な html ファイルです）を使った新しいイメージがビルドされ、クラスタ内で`ClusterIP`サービスとして公開されます。

```
$ oc new-app centos/httpd-24-centos8~https://github.com/sclorg/httpd-ex

# GitHub 上にある 「S2I builder image」 の 「centos/httpd-24-centos8」  に
# 「https://github.com/sclorg/httpd-ex」をコピーして
#  新しいイメージをビルドしている。
```

できた`ClusterIP`サービスを確認してみます。

`ClusterIP`は、`Cluster`の別の`Pod`からアクセスできるIPアドレスなので、この時点では、このサービスは、`OpenShift`クラスタの内部にしか公開されていません。

```
$ oc get svc
NAME       TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
httpd-ex   ClusterIP   172.30.139.58   <none>        8080/TCP,8443/TCP   75s
```

この`ClusterIP`サービスである`httpd-ex` を今度は、Clusterの外の世界に公開`expose`します。

```
$ oc expose service httpd-ex
route.route.openshift.io/httpd-ex exposed
```

`expose`により、クラスター外部からアクセスするための`route`というリソースが作成されます。
これで `OpenShfit`クラスター外部に、`ClusterIP`サービスが公開されました。

作成された `route` リソースを確認すると、アクセス`URL`が分かります。

```
$ oc get route
NAME       HOST/PORT                                                     PATH   SERVICES   PORT       TERMINATION   WILDCARD
httpd-ex   httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com          httpd-ex   8080-tcp                 None
```


## 2.3 アクセス確認

このサービス `httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com` にアクセスできるかブラウザで確認しみてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/dbd86778-2b49-3c36-5a98-443f89e17807.png)

無事アクセスできました。

# 3.独自ドメインに変更する

さて、このアプリを独自ドメインに変更してみます。
## 3.1 Route を編集する

`oc edit route` で、この Webアプリの `Route` リソースである `httpd-ex` を編集します。

独自ドメインは `myapp1.ocp4.work` とします。

`spec.host` を `httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com` から `myapp1.ocp4.work` に変更します。

```
$  oc edit route httpd-ex
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  annotations:
    openshift.io/host.generated: "true"
  creationTimestamp: "2021-06-14T09:33:13Z"
  labels:
    app: httpd-ex
    app.kubernetes.io/component: httpd-ex
    app.kubernetes.io/instance: httpd-ex
  name: httpd-ex
  namespace: myapp1
  resourceVersion: "66041"
  uid: 7372eb12-4cb2-4ec6-a38c-c91ce505e8c4
spec:
  host: httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com →　 myapp1.ocp4.work に変更                                            
  port:
    targetPort: 8080-tcp
  to:
    kind: Service
    name: httpd-ex
    weight: 100
  wildcardPolicy: None

# 保存して exit

# 設定を確認します。
$ oc get route/httpd-ex -o jsonpath='{.spec.host}{"\n"}'
myapp1.ocp4.work
$ 

```

## 3.2 CNAMEを行う

独自ドメインの `ocp4.work` ゾーンを管理している `DNS` 権威サーバーにアクセスします。

独自ドメインの `myapp1.ocp4.work` から、元元のドメインの `httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com` に `CNAME`をしてあげます。

この例は `Route 53` を使用しましたが、以下のような感じの`DNS`レコードを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/5f893d40-ea0d-9351-c2d9-aa00025a046a.png)

`CNAME`の `C`は、`Canonical`の意味で、`正規の`等と訳されます。 `myapp1.ocp4.work` の正規の名前は`httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com`と指定するのが`CNAME`です。

以上で独自ドメイン化の作業としては完了です。

この `CNAME`という作業をすると、クライアントから `myapp1.ocp4.work`の IPアドレスの解決の問い合わせが来た時に、「実は、私の正規の名前は`httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com` です。IPアドレスは、そちらにおたずね下さい。」とクライアントに応答が返されます。

その応答を受け取った、IPアドレスの問い合わせをしに来たクライアントは、「なるほど」と、今度は、`httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com` の `DNS`レコードを管理する`DNS`サーバーに IPアドレスを尋ねにいきます。

`httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com`のドメインを管理している`DNS`サーバーは、IPアドレスを持っているので、そのIPアドレスを問い合わせ元のクライアントに返却します。以上でドメインの名前解決が完了する。という動きになります。

結果として、`myapp1.ocp4.work`の`IP`アドレスは、`httpd-ex-myapp1.apps.rosa-cluster.3fof.p1.openshiftapps.com`の`IP`アドレスが使用される事になります。

## 3.3 アクセス確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/2a30771e-194b-f5b7-1598-154410a145d6.png)

無事にアクセスできました。

# 4.OpenShift の アプリケーション用のドメイン

ここでクラスター外部から来たトラフィックが、どうやってアプリケーションの`Pod`まで到達するのかを、ここまで行った作業を交えながら考えて見ます。

今回の例では、`AWS`の上に構築した、標準的な`OpenShift`を利用しました。

`OpenShift`を構築した場合、外部に公開するアプリケーションに対しては、`*.apps.<クラスター名>.<OpenShift 作成時に与えたドメイン名>` のドメイン名が自動的に割り当てられます。

今回の例ではアプリケーションドメイン名は下の図の赤枠の部分で示している`*.apps.rosa-cluster.3fof.p1.openshiftapps.com`でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/c4a82200-9d77-3d66-04a2-f44f8092deb4.png)

外部に公開するアプリケーションは、`*.apps.rosa-cluster.3fof.p1.openshiftapps.com`のワイルドカード部分を変更したドメイン名が割り当てられます。

今回の例では、`*`の部分が`httpd-ex-myapp1`でした。apps.rosa-cluster.3fof.p1.openshiftapps.com

複数のアプリを公開した場合でも`*`の部分が別名に替わるだけで、同じIPアドレスに誘導されます。

今回のケースの場合、誘導されたIPアドレスは`AWS`環境なのでちょっと特殊な記述方法になっていますが、`a8fa5687ee7a34cfc900bb242f25747e-368521378.ap-northeast-1.elb.amazonaws.com.` という`ELB(Elastic Load Balancer)`の`IP`になっています。(通常の`DNS`は、ここには数字の`IP`アドレスが記載されます)

## 4.1 Host ヘッダー

全てのアプリケーションが同じ`IP`アドレス（この例の場合は`ELB`の`IP`）に解決されるのに、どうやって`OpenShift`は、それぞれの`Pod`にトラフィックを誘導しているのでしょうか。

これは、全ての`HTTP`リクエストは、その中に`HTTP Host ヘッダー`と呼ばれる、`ヘッダー`を持っていて、その`Hostヘッダー`の値が利用されています。ブラウザーの`URL`としてドメインを打ち込むと、URLの中のFQDN が `Host ヘッダー`の値としてセットされます。

ですので、今回の場合は、`myapp1.ocp4.work`が`Hostヘッダー`にセットされています。

`HTTP`リクエスト時にどんな `Hostヘッダー`が送られているかは、ブラウザーの`Developerツール`で確認する事ができます。(以下の`Host:`の部分)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/860e56ac-5892-ec1c-937a-3a94ea5b756f.png)


`OpenShift`の標準構成では、`HTTP`アプリケーションへのリクエストは`Router`と呼ばれる `Pod`が受け取ります。

以下は、2つの`Router` `Pod`が稼働している例です。インターネットから、外部のロードバランサー(`ELB`)に到達したトラフィックは、この2つの`Pod`に割り振られます。(`Router`の数は、負荷を考えて増やす事ができるので、3つの構成もあります）

```
$ oc get pods -n openshift-ingress
NAME                              READY   STATUS    RESTARTS   AGE
router-default-5577468466-cjmm2   1/1     Running   0          3h19m     ### router-default-xxxx という名前になります。
router-default-5577468466-gdd7w   1/1     Running   0          3h19m     ### これらの router は通常 Node に分散されて配置されています。
$ 
```

この`Router`が`Hostヘッダー`の値を見て、どの `Pod`に来たリクエストなのか。を判別して割り振っています。

この時にどの`Hostヘッダー`の値が、どの`Pod`に行くべきか。を判断するのに参照するのが、[3.1 Route を編集する](#31-route-を編集する)で編集した`Route`になります。

ここまでの話を図示すると以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/a36742d6-35c8-db59-cfef-fa53ae16f598.png)


`Router`と`Route`と似たような名前があるので、混乱しますが、`Router`が実際に動いている`Pod`、`Route`は`Router`が参照している設定ファイルのように考えると良いと思います。

一つの`Router`は、複数の`Route`を取り扱う事ができるので、一つの`IP`で複数のドメインを運用できます。

今回は`AWS`環境なので上の図の`Load Balancer`は、`ELB`になっていますが、オンプレ環境では`ELB`の部分がオンプレで用意した`Load Balancer`になります。

# 5.参考資料

カスタムドメインの仕組みについて P.24から解説があります。
<script async class="speakerdeck-embed" data-slide="24" data-id="337f76aa396a4424a2ad5a4f1cbddd6e?slide=24" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script>

メモ：標準で生成されるembed 用の script タグを使っても P.24から表示しないので data-idの最後に?slide=24を追加。data-slideが無視されているように見える。