Red Hat OpenShift on IBM Cloud on VPCへのプライベート接続方式を検証してみた（設定編）

# 目的

Red Hat OpenShift on IBM Cloud（ROKS）はプロビジョン後にすぐにアプリケーションの開発と公開を行うことができますが、パブリックではなくプライベートでアプリケーション公開したい場合があります。例えばVPC内からのアクセスや、専用線、VPN接続などのケースです。

今回はROKS on VPCで、プライベートにアプリケーションを公開するための方法をいくつかのパターンで検証してみます。

# 前提

* ROKSはマルチゾーン構成とします
* ROKSクラスター自体はIBM Cloud ConsoleからアクセスできるOpenShift Web Consoleや外部のコンテナレジストリは利用できた方がよいので、パブリックとのアクセスは引き続き可能とします（パブリックゲートウェイが有効なサブネットを利用）

# プライベートアクセスの計画

公式のドキュメントをよく読んで検討します。

> https://cloud.ibm.com/docs/openshift?topic=openshift-cs_network_planning#private_vpc

現在選択肢は次の4つのようです。

1. プライベートルーター
1. NodePort
1. プライベートVPCロードバランサー
1. プライベートIngressコントローラー

今回はこれらを全て設定します。

# 手順

## テスト用アプリケーションの準備

OpenShiftユーザーならおなじみのruby-exを使用します。

```
$ oc new-app ruby~https://github.com/sclorg/ruby-ex.git
$ oc get pods
NAME               READY   STATUS      RESTARTS   AGE
ruby-ex-1-build    0/1     Completed   0          2m3s
ruby-ex-1-deploy   0/1     Completed   0          30s
ruby-ex-1-vp5sf    1/1     Running     0          23s
```

## プライベートルーター

> https://cloud.ibm.com/docs/openshift?topic=openshift-openshift_routes#private-routes-setup-43

デフォルトではパブリックで利用可能なルーターをプライベート側にもデプロイします。

ルーターはドメイン名を必要とします。自分でドメイン名を取得してDNSを設定して持ち込むこともできますが、VPCのROKSはIBM提供のドメイン名を使えますので、今回はそのまま利用します。現在IBMから提供されているドメイン名を調べます。

```
$ ibmcloud oc nlb-dns ls -c roks-tok
OK
サブドメイン                                                                       ロード・バランサーのホスト名         SSL 証明書の状況   SSL 証明書の秘密名                               秘密の名前空間
roks-tok-********81bb6d6d7afea007d1a8cafd-0000.jp-tok.containers.appdomain.cloud   ****a523-jp-tok.lb.appdomain.cloud   created            roks-tok-********81bb6d6d7afea007d1a8cafd-0000   openshift-ingress
```

途中の0000を``i001``にしたものがプライベート用ドメイン名です。1桁目は一律i、4桁目は0からのカウントアップです。そのドメイン名を指定してIngressControllerを作成します。

```ingresscontroller-i001.yaml
apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  name: private
  namespace: openshift-ingress-operator
spec:
  replicas: 2
  domain: roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud
  endpointPublishingStrategy:
    loadBalancer:
      scope: Internal
    type: LoadBalancerService
```

リソースを作成します。

```
$ oc create -f ingresscontroller-i001.yaml
ingresscontroller.operator.openshift.io/private created
```

router-privateというPodが追加されました。

```
$ oc get pods -n openshift-ingress
NAME                              READY   STATUS    RESTARTS   AGE
router-default-5c4d76b4c-5zj9r    1/1     Running   0          29m
router-default-5c4d76b4c-8gvns    1/1     Running   0          29m
router-private-544cf4bfbf-cw8z2   1/1     Running   0          25s
router-private-544cf4bfbf-trb96   1/1     Running   0          25s
```

このとき裏ではVPCのプライベートロードバランサーが自動生成されています。プライベートロードバランサーのホスト名を調べます。下記のEXTERNAL-IP欄が該当します。

```
$ oc get svc/router-private -n openshift-ingress
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP                          PORT(S)                      AGE
router-private   LoadBalancer   172.21.132.236   ****6e91-jp-tok.lb.appdomain.cloud   80:30379/TCP,443:32396/TCP   62s
```

プライベート用ドメイン名とロードバランサーのホスト名を紐づけてDNS登録します。nlb-dns createする度にi00xをカウントアップしたレコードが自動登録されます。

```
$ ibmcloud oc nlb-dns create vpc-gen2 -c roks-tok --lb-host ****6e91-jp-tok.lb.appdomain.cloud --type private --secret-namespace openshift-ingress
NLB DNS を作成中...
OK
NLB ホスト名が roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud として作成されました。

$ ibmcloud oc nlb-dns ls -c roks-tok
OK
サブドメイン                                                                       ロード・バランサーのホスト名         SSL 証明書の状況   SSL 証明書の秘密名                               秘密の名前空間
roks-tok-********81bb6d6d7afea007d1a8cafd-0000.jp-tok.containers.appdomain.cloud   ****a523-jp-tok.lb.appdomain.cloud   created            roks-tok-********81bb6d6d7afea007d1a8cafd-0000   openshift-ingress
roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud   ****6e91-jp-tok.lb.appdomain.cloud   created            roks-tok-********81bb6d6d7afea007d1a8cafd-i001   openshift-ingress
```

> 注意）IBM Cloudのガイドに従うと--secret-namespaceをつけ忘れてしまいますが、これをつけないとこのドメイン用のTLS証明書がdefault名前空間に作られてしまい、今後TLSを使いたい場合にopenshift-ingress名前空間のルーターPodから証明書を見つけられなくなってしまいます。

名前解決できることを確認します。CNAMEでロードバランサーを指していることがわかります。

```
$ dig roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud
...
roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud. 0 IN CNAME ****6e91-jp-tok.lb.appdomain.cloud.
****6e91-jp-tok.lb.appdomain.cloud. 0 IN A      10.244.66.7
****6e91-jp-tok.lb.appdomain.cloud. 0 IN A      10.244.2.7
```

プロジェクト（ネームスペース）に、プライベートルーターを使用するようラベリングします。

```
$ oc label namespace $(oc project -q) "router=router-private"
```

> このラベリングはガイドに記載されているとおりですが、これをしなくても問題なくプライベートルーターを利用できました。このあたりの仕組みはよくわかっていません。また、このラベルが真に有効ならば、パブリックルーターとプライベートルーターどちらを使うかはプロジェクト単位で分けるべきということになります。

サービスをプライベートルーターで公開します。--hostnameはi001のドメイン名に任意のサブドメイン名を付けます。

```
$ oc expose svc/ruby-ex --name ruby-ex-private --hostname ruby-ex-teruz.roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud
route.route.openshift.io/ruby-ex-private exposed
```

同じアプリケーションに対してパブリックルーターとプライベートルーターの経路は共存できます。

```
$ oc get route
NAME              HOST/PORT                                                                                                   PATH   SERVICES   PORT       TERMINATION   WILDCARD
ruby-ex           ruby-ex-teruz.roks-tok-********81bb6d6d7afea007d1a8cafd-0000.jp-tok.containers.appdomain.cloud ... 1 more          ruby-ex    8080-tcp                 None
ruby-ex-private   ruby-ex-teruz.roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud ... 1 more          ruby-ex    8080-tcp                 None
```

接続の確認ですが、今回は同じVPC内の仮想サーバを利用します。

```
$ curl -s ruby-ex-teruz.roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud | grep title
  <title>Welcome to OpenShift</title>
```

## NodePort

NodePortはKubernetes標準の方法です。サービスをNodePortタイプで作成するだけです。

```
$ oc expose dc/ruby-ex --name ruby-ex-nodeport --type NodePort
service/ruby-ex-nodeport exposed

$ oc get svc
NAME               TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
ruby-ex            ClusterIP   172.21.251.135   <none>        8080/TCP         137m
ruby-ex-nodeport   NodePort    172.21.1.23      <none>        8080:31530/TCP   12s
```

各ノードのアドレスを調べます。

```
$ oc get node
NAME           STATUS   ROLES           AGE    VERSION
10.244.130.4   Ready    master,worker   150m   v1.18.3+fa69cae
10.244.2.4     Ready    master,worker   150m   v1.18.3+fa69cae
10.244.66.4    Ready    master,worker   151m   v1.18.3+fa69cae
```

それぞれのノードのNodePortに直接アクセスします。

```
$ curl -s 10.244.130.4:31530/ | grep title
  <title>Welcome to OpenShift</title>
$ curl -s 10.244.2.4:31530/ | grep title
  <title>Welcome to OpenShift</title>
$ curl -s 10.244.66.4:31530/ | grep title
  <title>Welcome to OpenShift</title>
```

NodePortは楽ですが、アクセス経路の冗長化を考えると、結局のところ別の手段でヘルスチェックとロードバランシングを考えないといけません。そのため、現実には他の選択肢をとることになると考えます。

## プライベートVPCロードバランサー

> https://cloud.ibm.com/docs/openshift?topic=openshift-vpc-lbaas#lbaas_about

サービスをLoadBalancerタイプで作成することでVPCロードバランサーが作成されます。下例のようにマニフェストを用意します。

> コメントアウトしている2か所は注意が必要です。ガイドに従うとこれらを設定してしまいそうになりますが、proxy-protocolを有効にすると通常のWebクライアントからのアクセスが400 Bad Requestになるので、特殊な事情なければ有効にしてはいけません。zoneは特定のゾーンのノードと通信をしたい場合のみ指定します。無指定の場合、全ゾーンのノードが対象になります。

```loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
 name: ruby-ex-loadbalancer
 annotations:
   #service.kubernetes.io/ibm-load-balancer-cloud-provider-enable-features: "proxy-protocol"
   service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: private
   #service.kubernetes.io/ibm-load-balancer-cloud-provider-zone: ""
spec:
  type: LoadBalancer
  selector:
    deploymentconfig: ruby-ex
  ports:
  - name: 8080-tcp
    port: 8080
    protocol: TCP
    targetPort: 8080
```

リソースを作成します。

```
$ oc create -f loadbalancer.yaml
service/ruby-ex-loadbalancer created

$ oc get svc
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                          PORT(S)          AGE
ruby-ex                ClusterIP      172.21.251.135   <none>                               8080/TCP         3h47m
ruby-ex-loadbalancer   LoadBalancer   172.21.187.249   ****cf61-jp-tok.lb.appdomain.cloud   8080:32157/TCP   21m
ruby-ex-nodeport       NodePort       172.21.1.23      <none>                               8080:31530/TCP   90m
```

接続確認します。

```
$ curl -s ****cf61-jp-tok.lb.appdomain.cloud:8080 | grep title
  <title>Welcome to OpenShift</title>
```

## プライベートIngressコントローラー

プライベートルーターもIngressコントローラーの一部なのですが、ここではKubernetesでおなじみのIngressリソースを使用します。ドメイン名は区別のためi002を使用します。

```ingresscontroller-i002.yaml
apiVersion: operator.openshift.io/v1
kind: IngressController
metadata:
  name: private-ingress-controller
  namespace: openshift-ingress-operator
spec:
  replicas: 2
  domain: roks-tok-********81bb6d6d7afea007d1a8cafd-i002.jp-tok.containers.appdomain.cloud
  endpointPublishingStrategy:
    loadBalancer:
      scope: Internal
    type: LoadBalancerService
```

リソースを作成します。

```
$ oc create -f ingresscontroller-i002.yaml
ingresscontroller.operator.openshift.io/private-ingress-controller created

$ oc get svc -n openshift-ingress
NAME                                         TYPE           CLUSTER-IP       EXTERNAL-IP                          PORT(S)                      AGE
router-default                               LoadBalancer   172.21.170.143   ****a523-jp-tok.lb.appdomain.cloud   80:31463/TCP,443:32580/TCP   4h27m
router-internal-default                      ClusterIP      172.21.5.56      <none>                               80/TCP,443/TCP,1936/TCP      4h27m
router-internal-private                      ClusterIP      172.21.76.206    <none>                               80/TCP,443/TCP,1936/TCP      3h45m
router-internal-private-ingress-controller   ClusterIP      172.21.158.189   <none>                               80/TCP,443/TCP,1936/TCP      8m4s
router-private                               LoadBalancer   172.21.132.236   ****6e91-jp-tok.lb.appdomain.cloud   80:30379/TCP,443:32396/TCP   3h45m
router-private-ingress-controller            LoadBalancer   172.21.247.200   ****aad0-jp-tok.lb.appdomain.cloud   80:30050/TCP,443:31945/TCP   8m4s
```

i002ドメインをロードバランサーと紐づけてDNS登録します。

```
$ ibmcloud oc nlb-dns create vpc-gen2 -c roks-tok --lb-host ****aad0-jp-tok.lb.appdomain.cloud --type private --secret-namespace openshift-ingress
NLB DNS を作成中...
OK
NLB ホスト名が roks-tok-********81bb6d6d7afea007d1a8cafd-i002.jp-tok.containers.appdomain.cloud として作成されました。

$ ibmcloud oc nlb-dns ls -c roks-tok
OK
サブドメイン                                                                       ロード・バランサーのホスト名         SSL 証明書の状況   SSL 証明書の秘密名                               秘密の名前空間
roks-tok-********81bb6d6d7afea007d1a8cafd-0000.jp-tok.containers.appdomain.cloud   ****a523-jp-tok.lb.appdomain.cloud   created            roks-tok-********81bb6d6d7afea007d1a8cafd-0000   openshift-ingress
roks-tok-********81bb6d6d7afea007d1a8cafd-i001.jp-tok.containers.appdomain.cloud   ****6e91-jp-tok.lb.appdomain.cloud   created            roks-tok-********81bb6d6d7afea007d1a8cafd-i001   openshift-ingress
roks-tok-********81bb6d6d7afea007d1a8cafd-i002.jp-tok.containers.appdomain.cloud   ****aad0-jp-tok.lb.appdomain.cloud   created            roks-tok-********81bb6d6d7afea007d1a8cafd-i002   openshift-ingress
```

アプリケーションを公開するためのIngressマニフェストを作成します。任意のサブドメインを付与します。

```ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ruby-ex
spec:
  rules:
  - host: ruby-ex-teruz.roks-tok-********81bb6d6d7afea007d1a8cafd-i002.jp-tok.containers.appdomain.cloud
    http:
      paths:
      - path: /
        backend:
          serviceName: ruby-ex
          servicePort: 8080
```

リソースを作成します。

```
$ oc create -f ingress.yaml
ingress.extensions/ruby-ex created
```

接続確認します。

```
$ curl -s ruby-ex-teruz.roks-tok-********81bb6d6d7afea007d1a8cafd-i002.jp-tok.containers.appdomain.cloud | grep title
  <title>Welcome to OpenShift</title>
```

# 結果

いずれの方式もVPC内部からのプライベートアクセスはうまくいきました。次回以降、オンプレミス相当の環境からいくつかのパターンで接続して動作検証をしてみます。

また、NodePortを除くいずれの方式もDNSでの名前解決を前提としていました。つまり、オンプレミス側でもパブリックまたはIBM Cloud内のDNSが参照できる必要があります。

検証編を公開しました。

> https://qiita.com/teruq/items/dca1fdb653f358cf6de3