Container Engine for Kubernetes (OKE) で Ingress を設定する

# はじめに
OKEでIngressを使用する際の設定方法を確認したいと思います。マニュアルに記載はあるのですがこの通りではできなかったので、参考にしながら確認しました。

https://docs.oracle.com/ja-jp/iaas/Content/ContEng/Tasks/contengsettingupingresscontroller.htm

Kubernetesクラスタは構築済みです。

```
$ kubectl get node
NAME          STATUS   ROLES   AGE     VERSION
10.0.10.152   Ready    node    5d17h   v1.21.5
10.0.10.187   Ready    node    5d17h   v1.21.5
10.0.10.253   Ready    node    5d17h   v1.21.5
```

# Ingress コントローラーの設定
## ClusterRoleBindingsの設定

利用するOCIユーザがテナンシー管理者ではない場合、cluster-admin の clusterrole をユーザに与えます。

### OCIDの確認
ユーザのOCIDを確認します。
OCIコンソールの「ユーザの詳細」からOCIDをコピーします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/fabafffe-9287-60a1-1335-bf8fcc0b0026.png)


### ClusterRoleBindingsの作成

ここでは、ClusterRoleBindingsの名前を「ingress-adm」として設定します。

```
$ kubectl create clusterrolebinding ingress-adm --clusterrole=cluster-admin --user=ocid1.user.oc1..aaaaaaaa・・・pgasfa
clusterrolebinding.rbac.authorization.k8s.io/ingress-adm created
$ kubectl describe clusterrolebindings ingress-adm
Name:         ingress-adm
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind  Name                                                    Namespace
  ----  ----                                                    ---------
  User  ocid1.user.oc1..aaaaaaaa4・・・asfa  
```

## Ingressコントローラーのデプロイ
GitHubリポジトリに公開されているマニフェストを使用してデプロイします。最新は[こちら](https://github.com/kubernetes/ingress-nginx/blob/main/docs/deploy/index.md#oracle-cloud-infrastructure)にあります。
ただ、このままではデプロイされるLoadBalancer のヘルスチェックがエラーになったので、直接 kubectl で apply しないで、ダウンロード後一部編集してから apply します。

```
$ wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml
--2022-01-05 07:06:33--  https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.108.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 19299 (19K) [text/plain]
Saving to: ‘deploy.yaml’

100%[===============================================================================================================================================================================================>] 19,299      --.-K/s   in 0s      

2022-01-05 07:06:33 (74.0 MB/s) - ‘deploy.yaml’ saved [19299/19299]
$ vim deploy.yaml
```

編集したマニフェストを以下に抜粋します。

```deploy.yaml
・・・
---
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
### フレキシブル・シェイプを使用するために追記（任意） ###
    service.beta.kubernetes.io/oci-load-balancer-shape: "flexible"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-min: "10"
    service.beta.kubernetes.io/oci-load-balancer-shape-flex-max: "50"
### ここまで ###
  labels:
    helm.sh/chart: ingress-nginx-4.0.10
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 1.1.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: LoadBalancer
### コメントアウト ###
#  externalTrafficPolicy: Local
### ここまで ###
  ipFamilyPolicy: SingleStack
  ipFamilies:
    - IPv4
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
      appProtocol: http
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
      appProtocol: https
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/component: controller
---
・・・
```
フレキシブル・シェイプを使用するために追記した3行は任意です。
「externalTrafficPolicy: Local」はコメントアウト（もしくは削除）しました。

IngressコントローラーのGitHubリポジトリには以下の記載があります。
これをLocalに設定することでホップを節約できるのですが、OCIのロードバランサーは対応してないのかな？Localのままでデプロイすると、ロードバランサーのヘルスチェックがエラーになりました。

> If the load balancers of your cloud provider do active healthchecks on their backends (most do), you can change the externalTrafficPolicy of the ingress controller Service to Local (instead of the default Cluster) to save an extra hop in some cases. If you're installing with Helm, this can be done by adding --set controller.service.externalTrafficPolicy=Local to the helm install or helm upgrade command.

編集したマニフェストを apply します。

```
$ kubectl apply -f deploy.yaml 
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
```

確認します。

```
$ kubectl get all -n ingress-nginx
NAME                                          READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-j2sdk      0/1     Completed   0          7m12s
pod/ingress-nginx-admission-patch-sbp8d       0/1     Completed   0          7m12s
pod/ingress-nginx-controller-54bfb9bb-7w8wm   1/1     Running     0          7m12s

NAME                                         TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.96.202.169   129.159.68.246   80:32178/TCP,443:32206/TCP   7m12s
service/ingress-nginx-controller-admission   ClusterIP      10.96.205.154   <none>           443/TCP                      7m12s

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           7m12s

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-54bfb9bb   1         1         1       7m12s

NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           1s         7m12s
job.batch/ingress-nginx-admission-patch    1/1           2s         7m12s
```

OCIのコンソールでもロードバランサーがデプロイされていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/2a4dcb3d-2e4f-834e-0b1c-78181220ea3c.png)

# バックエンドの設定
Ingressの動作を確認するために、サンプルのDeploymentとServiceをデプロイします。

## Deployment（Pod）のデプロイ
L7でのバランシングを確認するために、3つのDeploymentをデプロイします。

```deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx0
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-dep0
  template:
    metadata:
      labels:
        app: nginx-dep0
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-dep1
  template:
    metadata:
      labels:
        app: nginx-dep1
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-dep2
  template:
    metadata:
      labels:
        app: nginx-dep2
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
```

```
$ kubectl apply -f deployment.yaml 
deployment.apps/nginx0 created
deployment.apps/nginx1 created
deployment.apps/nginx2 created
$ kubectl get pod -o wide -L app
NAME                      READY   STATUS    RESTARTS   AGE    IP             NODE          NOMINATED NODE   READINESS GATES   APP
nginx0-7f59f499f8-gwjzt   1/1     Running   0          8m6s   10.244.0.168   10.0.10.187   <none>           <none>            nginx-dep0
nginx0-7f59f499f8-xq6zh   1/1     Running   0          8m6s   10.244.0.32    10.0.10.253   <none>           <none>            nginx-dep0
nginx1-76fccb476f-hn7lg   1/1     Running   0          8m6s   10.244.0.169   10.0.10.187   <none>           <none>            nginx-dep1
nginx1-76fccb476f-tj4s6   1/1     Running   0          8m6s   10.244.1.20    10.0.10.152   <none>           <none>            nginx-dep1
nginx2-f7cf4975b-988s5    1/1     Running   0          8m6s   10.244.0.170   10.0.10.187   <none>           <none>            nginx-dep2
nginx2-f7cf4975b-rzsnj    1/1     Running   0          8m6s   10.244.0.33    10.0.10.253   <none>           <none>            nginx-dep2
```

## ClusterIPのデプロイ
IngressのバックエンドとなるClusterIPをデプロイします。各Deploymentごとに3つのClusterIPを作成します。

```ClusterIP.yaml
apiVersion: v1
kind: Service
metadata:
  name: clusterip0
spec:
  type: ClusterIP
  ports:
    - name: clusterip
      protocol: TCP
      port: 8080
      targetPort: 80
  selector:
    app: nginx-dep0
---
apiVersion: v1
kind: Service
metadata:
  name: clusterip1
spec:
  type: ClusterIP
  ports:
    - name: clusterip
      protocol: TCP
      port: 8080
      targetPort: 80
  selector:
    app: nginx-dep1
---
apiVersion: v1
kind: Service
metadata:
  name: clusterip2
spec:
  type: ClusterIP
  ports:
    - name: clusterip
      protocol: TCP
      port: 8080
      targetPort: 80
  selector:
    app: nginx-dep2
```

```
$ kubectl apply -f ClusterIP.yaml 
service/clusterip0 created
service/clusterip1 created
service/clusterip2 created
$ kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
clusterip0   ClusterIP   10.96.48.239    <none>        8080/TCP   12s
clusterip1   ClusterIP   10.96.118.101   <none>        8080/TCP   12s
clusterip2   ClusterIP   10.96.111.0     <none>        8080/TCP   12s
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    5d18h
```

各ClusterIPの「Endpoints」に、selectorに合ったPodのIPアドレスが表示されているか確認します。

```
$ kubectl describe svc clusterip0
Name:              clusterip0
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx-dep0
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.96.48.239
IPs:               10.96.48.239
Port:              clusterip  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.244.0.168:80,10.244.0.32:80
Session Affinity:  None
Events:            <none>
$ kubectl describe svc clusterip1
・・・省略・・・
$ kubectl describe svc clusterip2
・・・省略・・・
```

# Ingressの設定
## TLS Secret の作成
IngressでHTTPSを利用する場合には、事前に証明書をSecretとして作成します。
今回は自己証明書を使用します。

```
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginxsvc/O=nginxsvc"
Generating a 2048 bit RSA private key
*********************************************************************************************************+++++
************************************************************************+++++
writing new private key to 'tls.key'
-----
$ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret/tls-secret created
```

## Ingressのデプロイ

今回は「/path1」を指定した場合は「clusterip1」に、「/path2」を指定した場合は「clusterip2」にルーティングして、それ以外の場合は「clusterip0」にルーティングするようにします。

```sampleIngress-ga.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: sample-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - http:
      paths:
      - path: /path1
        pathType: Prefix
        backend:
          service:
            name: clusterip1
            port:
             number: 8080
      - path: /path2
        pathType: Prefix
        backend:
          service:
            name: clusterip2
            port: 
             number: 8080
  defaultBackend:
    service:
      name: clusterip0
      port:
        number: 8080
  tls:
  - secretName: tls-secret
```

```
$ kubectl apply -f sampleIngress-ga.yaml 
ingress.networking.k8s.io/sample-ingress created
$ kubectl get ingress
NAME             CLASS    HOSTS   ADDRESS   PORTS     AGE
sample-ingress   <none>   *                 80, 443   8s
```

デプロイした直後は「ADDRESS」が空白になっていますが、数分待つとLoadBalancerのIPアドレスが表示されます。

```
$ kubectl get ingress
NAME             CLASS    HOSTS   ADDRESS          PORTS     AGE
sample-ingress   <none>   *       129.159.68.246   80, 443   42s
```

詳細も確認しておきます。

```
$ kubectl describe ingress sample-ingress
Name:             sample-ingress
Namespace:        default
Address:          129.159.68.246
Default backend:  clusterip0:8080 (10.244.0.168:80,10.244.0.32:80)
TLS:
  tls-secret terminates 
Rules:
  Host        Path  Backends
  ----        ----  --------
  *           
              /path1   clusterip1:8080 (10.244.0.169:80,10.244.1.20:80)
              /path2   clusterip2:8080 (10.244.0.170:80,10.244.0.33:80)
Annotations:  kubernetes.io/ingress.class: nginx
Events:
  Type    Reason  Age                From                      Message
  ----    ------  ----               ----                      -------
  Normal  Sync    10m (x2 over 10m)  nginx-ingress-controller  Scheduled for sync
```

# 動作確認
## index.htmlファイルの準備
Ingress経由でリクエストを送ってレスポンスを確認します。ただ、このままだとどのPodにバランシングしているかわからないので、各Podのindex.htmlファイルにホスト名を記載しておきます。
また、nginx1とnginx2はそれぞれ「path1」「path2」配下にindex.htmlファイルを用意します。

```
$ kubectl exec -it nginx0-7f59f499f8-gwjzt -- /bin/bash
root@nginx0-7f59f499f8-gwjzt:/# echo `hostname` > /usr/share/nginx/html/index.html 
root@nginx0-7f59f499f8-gwjzt:/# exit
exit

$ kubectl exec -it nginx1-76fccb476f-tj4s6 -- /bin/bash
root@nginx1-76fccb476f-tj4s6:/# mkdir /usr/share/nginx/html/path1
root@nginx1-76fccb476f-tj4s6:/# echo `hostname` > /usr/share/nginx/html/path1/index.html
root@nginx1-76fccb476f-tj4s6:/# exit
exit

・・・そのほかは省略・・・
```

## 動作確認

Ingress経由でHTTPリクエストをそれぞれ5回送って、動作を確認します。

```
$ for i in 1 2 3 4 5; do curl http://129.159.68.246/ ; done
nginx0-7f59f499f8-xq6zh
nginx0-7f59f499f8-gwjzt
nginx0-7f59f499f8-gwjzt
nginx0-7f59f499f8-xq6zh
nginx0-7f59f499f8-xq6zh
$ for i in 1 2 3 4 5; do curl http://129.159.68.246/path1/ ; done
nginx1-76fccb476f-tj4s6
nginx1-76fccb476f-hn7lg
nginx1-76fccb476f-tj4s6
nginx1-76fccb476f-hn7lg
nginx1-76fccb476f-tj4s6
$ for i in 1 2 3 4 5; do curl http://129.159.68.246/path2/ ; done
nginx2-f7cf4975b-988s5
nginx2-f7cf4975b-rzsnj
nginx2-f7cf4975b-rzsnj
nginx2-f7cf4975b-988s5
nginx2-f7cf4975b-988s5
```

HTTPSでも確認します。

```
$ for i in 1 2 3 4 5; do curl -k https://129.159.68.246/ ; done
nginx0-7f59f499f8-gwjzt
nginx0-7f59f499f8-xq6zh
nginx0-7f59f499f8-xq6zh
nginx0-7f59f499f8-gwjzt
nginx0-7f59f499f8-xq6zh
$ for i in 1 2 3 4 5; do curl -k https://129.159.68.246/path1/ ; done
nginx1-76fccb476f-hn7lg
nginx1-76fccb476f-tj4s6
nginx1-76fccb476f-hn7lg
nginx1-76fccb476f-tj4s6
nginx1-76fccb476f-tj4s6
$ for i in 1 2 3 4 5; do curl -k https://129.159.68.246/path2/ ; done
nginx2-f7cf4975b-rzsnj
nginx2-f7cf4975b-rzsnj
nginx2-f7cf4975b-988s5
nginx2-f7cf4975b-rzsnj
nginx2-f7cf4975b-988s5
```

それぞれ期待通りの動作であることが確認できます。