kubernetesのServiceとIngressをカンチガイしていた話

# やりたかったこと

アプリ開発中、バックエンドで使用する2種類のAPIサーバをkubernetesにデプロイし、Ingress経由で公開しようとしました。
そのときのエンドポイントを下のように設定しようとしました。

* /server1 → APIサーバ１にアクセス
* /server2 → APIサーバ２にアクセス

使用しているkubernetes環境は以下の通りです。

* ホスティングサービス: IBM Cloud Kubernetes Service
* バージョン: 1.9.10_1523 (ちょい古め)

# 起きたこと

どちらのエンドポイントにアクセスしても、両方のサーバに均等に到達してしまう…:fearful:

> 例：　/server1　に３回アクセス → APIサーバ１に１回、APIサーバ２に２回到達

# やらかした原因

例えば次のようなアプリをデプロイしていたとします。

| name | port binding |
|:----:|:-----|
| server1 | 8000:8000 |
| server2 | 8080:8080 |


このとき、ServiceとIngressを下のように書いていました。

```yaml:service.yml
apiVersion: v1
kind: Service
metadata:
  name: all-in-one-service
  labels:
    project: current-project
spec:
  type: LoadBalancer
  loadBalancerIP: <available IP>
  selector:
    project: current-project
  ports:
  - port: 8000
    targetPort: 8000
    name: server1
  - port: 8080
    targetPort: 8080
    name: server2
```

```yaml:ingress.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  labels:
    project: current-project
spec:
  rules:
  - host: <available hostname>
    http:
      paths:
      - path: /service1
        backend:
          serviceName: all-in-one-service
          servicePort: 8000
      - path: /service2
        backend:
          serviceName: all-in-one-service
          servicePort: 8080
```

これでうまく行くはず！:sunglasses:…と思ってたのですが。うまく行かない。
[IBM Cloud Kubernetes Serviceのドキュメント](https://console.bluemix.net/docs/containers/cs_troubleshoot_debug_ingress.html#cs_troubleshoot_debug_ingress)を見ながらデバッグしました。

以下、実際にデバッグしたときの手順です。

1. ``kubectl get po -n kube-system | grep alb``で、nginx-ingressが動いているpodの名前を探す
2. ``kubectl logs <podname> -n kube-system nginx-ingress``で、nginx-ingressのログを取得する
3. ``kubectl exec -it <podname> bash -c nginx-ingress -n kube-system``で、nginx-ingressのコンテナにログインする
4. nginxの設定ファイルを探し出す。今回は``/etc/nginx/conf.d/``以下にありました。

nginxの設定ファイルを見ると、下のような部分がありました。

```text:my-ingress.conf(抜粋)
  location /service1 {
    ...
    proxy_pass http://my-cluster-url.eu-central.containers.mybluemix.net-all-in-one-service;
    ...
  }

  location /service2 {
    ...
    proxy_pass http://my-cluster-url.eu-central.containers.mybluemix.net-all-in-one-service;
    ...
  }
```

**ん？同じURL…？**

IngressにはserviceNameとservicePortを両方書くけれど、servicePortは関係なく、両方のパスに対して同じServiceを紐づけているだけということが分かりました。
だからうまく行かなかったんですね。

# どう直したか

シンプルに、ServiceをDeploymentごとに分けました。
Ingressに指定するパスごとに別々のServiceが割りあたるようにしました。

```yaml:svc-server1.yml
apiVersion: v1
kind: Service
metadata:
  name: service-for-server1
  labels:
    project: current-project
spec:
  selector:
    app: server1
    project: current-project
  ports:
  - port: 8000
    targetPort: 8000
```

```yaml:svc-server2.yml
apiVersion: v1
kind: Service
metadata:
  name: service-for-server2
  labels:
    project: current-project
spec:
  selector:
    app: server2
    project: current-project
  ports:
  - port: 8080
    targetPort: 8080
```

```yaml:ingress.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-ingress
  labels:
    project: current-project
spec:
  rules:
  - host: <available hostname>
    http:
      paths:
      - path: /service1
        backend:
          serviceName: service-for-server1
          servicePort: 8000
      - path: /service2
        backend:
          serviceName: service-for-server2
          servicePort: 8080
```

これで今度こそ解決:sunglasses:

# おわりに

実はIngressのルーティング問題の他にも、気づいて直したやらかしがいくつかあります。

* ServiceでLoadBalancerIPを指定しているので、名前空間変えてデプロイしたりできなかった。
* そもそもIngressに紐付けるだけならLoadBalancerでなくても良かった。
    * Serviceのtypeを指定せずにClusterIPになっててもIngressに登録できるみたいです。

他にもこんなとこやらかしてるよ、というツッコミお待ちしております。k8sむずかしい。