Ingressでクロスネームスペースでのロードバランシング

TL;DR

複数のnamespaceのpodsたちにむけてを1つのロードバランサで振り分けるのはめんどくさいって話です。
ExternalNameを使えばできる。

# ことの発端

そんなにトラフィックはないけど機能ごとにnamespaceを分けておこうかなと思って、バッチやAPPのPodsやServiceのnamespaceを分けてデプロイしようかなと思った。
いざ、Ingressの設定をしようと思ったら、backendにnamespace書くところないじゃんとなった。

このspec.rules.http.pathごとにnamespaceを切っていた。

```yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: lb
  annotations:
    #nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-origin: "*"
spec:
  rules:
    - host: api.192.168.99.107.nip.io
      http:
        paths:
          - path: /dapps
            backend:
              serviceName: dapps-svc
              servicePort: 80
          - path: /gas
            backend:
              serviceName: gas-svc
              servicePort: 80
          - path: /miner
            backend:
              serviceName: miner-svc
              servicePort: 80
```

もしかしてkubens的な感じで書けばいけるのかなと思ったけど、serviceNameに「.」を入れるとエラーを吐いてくれてデプロイすらできない。（dapps-svc.namespace1とかってしても無駄。）

```
* spec.rules[0].http.backend.serviceName: Invalid value: "dapps-svc.namespace1": a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')
```


# ググるマン

とりあえず公式のドキュメントを見ると、
https://kubernetes.io/docs/concepts/services-networking/ingress/
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/#ingress-v1beta1-networking-k8s-io

その辺の記載はない。
とりあえず今回は切ったnamespaceを全部Ingress側に寄せて(default)デプロイして回避することにした。


# そんな馬鹿な納得できない。

ちゃんと検証してみようと思ってこんな感じに環境を構築した。http HeaderのServerとかで振り分け確認をすればいいや。
（minikubeでやりました。）


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56925/7d765d92-eb86-2eb7-c4a1-f341b9fc8ffd.png)




# conf

とりあえず全部同一namespaceで動くものを。

```yaml

# namespace
apiVersion: v1
kind: Namespace
metadata:
  name: ns-test
---
# ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: lb
  namespace: ns-test
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - host: ns-test.192.168.99.107.nip.io
      http:
        paths:
          - path: /apache
            backend:
              serviceName: apache-svc
              servicePort: 80
          - path: /nginx
            backend:
              serviceName: nginx-svc
              servicePort: 80
          - path: /
            backend:
              serviceName: blackhole
              servicePort: 80
---
# apache
apiVersion: v1
kind: Service
metadata:
  name: apache-svc
  namespace: ns-test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: httpd
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd
  namespace: ns-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - image: httpd:alpine
        name: httpd
        ports:
        - containerPort: 80
---
# nginx
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: ns-test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: ns-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80

```

デフォルトnamespaceを切り替えて

```
❯ kubens ns-test
Context "vm" modified.
Active namespace is "ns-test".
```

こんな感じ

```
❯ kubectl get all,ingress -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
pod/httpd-55c79f4cb-zrzdc    1/1     Running   0          29s   172.17.0.2   minikube   <none>           <none>
pod/nginx-66d89c74cb-l7gdv   1/1     Running   0          29s   172.17.0.3   minikube   <none>           <none>

NAME                 TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE   SELECTOR
service/apache-svc   NodePort   10.101.246.95   <none>        80:32141/TCP   29s   app=httpd
service/nginx-svc    NodePort   10.97.137.34    <none>        80:30783/TCP   29s   app=nginx

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES         SELECTOR
deployment.apps/httpd   1/1     1            1           29s   httpd        httpd:alpine   app=httpd
deployment.apps/nginx   1/1     1            1           29s   nginx        nginx:alpine   app=nginx

NAME                               DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES         SELECTOR
replicaset.apps/httpd-55c79f4cb    1         1         1       29s   httpd        httpd:alpine   app=httpd,pod-template-hash=55c79f4cb
replicaset.apps/nginx-66d89c74cb   1         1         1       29s   nginx        nginx:alpine   app=nginx,pod-template-hash=66d89c74cb

NAME                    HOSTS                           ADDRESS   PORTS   AGE
ingress.extensions/lb   ns-test.192.168.99.107.nip.io             80      29s
```

動いてるかな？

```
❯ curl http://ns-test.192.168.99.107.nip.io/apache
<html><body><h1>It works!</h1></body></html>

~
❯ curl http://ns-test.192.168.99.107.nip.io/nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
```

問題なさそう。本当はヘッダーで判別したかったけど、Ingressのnginxで上書きされてしまっていたのでbodyで。

# namespace変えてみる。

podsのapacheのnamespaceをns-test-podsとしてみよう。
IngressのTargetは変えていないので404になるはず。
`kubectl delete all --all` とかで一回きれいにしてからやらないと前のnamespaceのpodsが残るよ。

namespace追加とServiceとPod変更

```diff

6a7,12
> # namespace
> apiVersion: v1
> kind: Namespace
> metadata:
>   name: ns-test-pods
> ---
39c45
<   namespace: ns-test
---
>   namespace: ns-test-pods
53c59
<   namespace: ns-test
---
>   namespace: ns-test-pods
```


```
❯ curl http://ns-test.192.168.99.107.nip.io/nginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {

~snip;

❯ curl http://ns-test.192.168.99.107.nip.io/apache
<html>
<head><title>503 Service Temporarily Unavailable</title></head>

~snip;

❯ curl http://ns-test.192.168.99.107.nip.io/apache -I
HTTP/1.1 503 Service Temporarily Unavailable
Server: nginx/1.15.9
Date: Fri, 07 Jun 2019 04:18:20 GMT
Content-Type: text/html
Content-Length: 197
Connection: keep-alive

```

503になった、リバプロ(Ingress)が吐いてるから正しいね。

# serviceName変えてみる。


```
29c29
<               serviceName: apache-svc
---
>               serviceName: apache-svc.ns-test-pods

```

違うだろボケナス！って言われる。

```
The Ingress "lb" is invalid: spec.rules[0].http.backend.serviceName: Invalid value: "apache-svc.ns-test-pods": a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic c
haracter, and end with an alphanumeric character (e.g. 'my-name',  or 'abc-123', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')
```


# ExternalNameの登場

ExternalNameっていうtypeのServiceがあって、別名をつけることができる。

そもそも、k8sのpodsやらserviceやらは名前がついていて、<名前>.<ネームスペース>.svc.cluster.local で名前解決できるようになっている。
ここで別のnamespaceのapache-svcをこのServiceで紐づけてやる。

わかりやすい？ように、Ingress側のターゲットを `serviceName: apache-svc-koitu` として下のServiceを挟む。


```
apiVersion: v1
kind: Service
metadata:
  name: apache-svc-koitu
  namespace: ns-test
spec:
  type: ExternalName
  externalName: apache-svc.ns-test-pods.svc.cluster.local
```


こうすると、

```
❯ curl http://ns-test.192.168.99.107.nip.io/apache
<html><body><h1>It works!</h1></body></html>
```

ちゃんとアクセスできたまん。


最終的にこんな感じになった。

```yaml
# namespace
apiVersion: v1
kind: Namespace
metadata:
  name: ns-test
---
# namespace
apiVersion: v1
kind: Namespace
metadata:
  name: ns-test-pods
---
# ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: lb
  namespace: ns-test
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
    - host: ns-test.192.168.99.107.nip.io
      http:
        paths:
          - path: /apache
            backend:
              serviceName: apache-svc-koitu
              servicePort: 80
          - path: /nginx
            backend:
              serviceName: nginx-svc
              servicePort: 80
          - path: /
            backend:
              serviceName: blackhole
              servicePort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: apache-svc-koitu
  namespace: ns-test
spec:
  type: ExternalName
  externalName: apache-svc.ns-test-pods.svc.cluster.local
---
# apache
apiVersion: v1
kind: Service
metadata:
  name: apache-svc
  namespace: ns-test-pods
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: httpd
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpd
  namespace: ns-test-pods
spec:
  replicas: 1
  selector:
    matchLabels:
      app: httpd
  template:
    metadata:
      labels:
        app: httpd
    spec:
      containers:
      - image: httpd:alpine
        name: httpd
        ports:
        - containerPort: 80
---
# nginx
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: ns-test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: ns-test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80
```

こんなイメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/56925/5165bced-31bd-937b-4ed5-643cd9a76f91.png)

目的は果たせた。

# まとめ

ALBを1個増やすとさんぜんえんだからね！ExternalName使っていって良いと思います。
ただし、namespaceを分けすぎるのは良くない。kubectl get xxxで見るときにツライことになる。
本当はIngressまで分けたほうがいいけど、そこまでしなくても・・・みたいなときに使うようにしたいと思います。

参考: ) 
https://github.com/kubernetes/ingress-nginx/issues/2971
https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/
https://qiita.com/ryicoh/items/24e5cf4ffdb6cc8dbbdf （この記事そのまんまであった）







# 追記

EKSでは試せていませんが、オンプレのv1.19.2で↑のマニフェストのままでExternalNameの動作が確認できました。

```
murata:~ $ kubectl version
Client Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.2", GitCommit:"f5743093fd1c663cb0cbc89748f730662345d44d", GitTreeState:"clean", BuildDate:"2020-09-16T13:41:02Z", GoVersion:"go1.15", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"19", GitVersion:"v1.19.2", GitCommit:"f5743093fd1c663cb0cbc89748f730662345d44d", GitTreeState:"clean", BuildDate:"2020-09-16T13:32:58Z", GoVersion:"go1.15", Compiler:"gc", Platform:"linux/amd64"}
```

下記のような構成になりました。

```
murata:~ $ kubectl get pod,svc,ing -A
Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
NAMESPACE        NAME                                                    READY   STATUS      RESTARTS   AGE
ingress-nginx    pod/ingress-nginx-admission-create-jkrr6                0/1     Completed   0          9m6s
ingress-nginx    pod/ingress-nginx-admission-patch-hr6z4                 0/1     Completed   1          9m6s
ingress-nginx    pod/ingress-nginx-controller-59859f77c7-26tfl           1/1     Running     0          9m6s
kube-system      pod/calico-kube-controllers-c9784d67d-cztwl             1/1     Running     0          19m
kube-system      pod/canal-788gd                                         2/2     Running     0          19m
kube-system      pod/coredns-f9fd979d6-4zzmr                             1/1     Running     0          19m
kube-system      pod/coredns-f9fd979d6-sd68h                             1/1     Running     0          19m
kube-system      pod/etcd-k8s-tmp.dev.deroris.local                      1/1     Running     0          19m
kube-system      pod/kube-apiserver-k8s-tmp.dev.deroris.local            1/1     Running     0          19m
kube-system      pod/kube-controller-manager-k8s-tmp.dev.deroris.local   1/1     Running     0          19m
kube-system      pod/kube-proxy-r7qtv                                    1/1     Running     0          19m
kube-system      pod/kube-scheduler-k8s-tmp.dev.deroris.local            1/1     Running     0          19m
metallb-system   pod/controller-fb659dc8-dbxrw                           1/1     Running     0          18m
metallb-system   pod/speaker-shz4n                                       1/1     Running     0          18m
ns-test-pods     pod/httpd-6cd65b68c6-7phmm                              1/1     Running     0          9m37s
ns-test          pod/nginx-7fb7fd49b4-c5df7                              1/1     Running     0          9m37s

NAMESPACE       NAME                                         TYPE           CLUSTER-IP       EXTERNAL-IP                                 PORT(S)                      AGE
default         service/kubernetes                           ClusterIP      10.96.0.1        <none>                                      443/TCP                      19m
ingress-nginx   service/ingress-nginx-controller             LoadBalancer   10.97.180.87     172.30.203.204                              80:32003/TCP,443:32626/TCP   9m6s
ingress-nginx   service/ingress-nginx-controller-admission   ClusterIP      10.110.169.132   <none>                                      443/TCP                      9m6s
kube-system     service/kube-dns                             ClusterIP      10.96.0.10       <none>                                      53/UDP,53/TCP,9153/TCP       19m
ns-test-pods    service/apache-svc                           NodePort       10.96.121.175    <none>                                      80:31181/TCP                 9m37s
ns-test         service/apache-svc-koitu                     ExternalName   <none>           apache-svc.ns-test-pods.svc.cluster.local   <none>                       9m37s
ns-test         service/nginx-svc                            NodePort       10.111.149.9     <none>                                      80:31453/TCP                 9m37s

NAMESPACE   NAME                    CLASS    HOSTS                           ADDRESS          PORTS   AGE
ns-test     ingress.extensions/lb   <none>   ns-test.172.30.203.204.nip.io   172.30.203.204   80      9m37s
```