Kubernetesクラスター外のIPアドレスに対してルーティングしてくれるServiceを作る

これはなに
==========
タイトルのとおり、Kubernetesクラスター外のIPアドレスに対してルーティングしてくれるServiceオブジェクトを作る方法を書きます。

KubernetesのServiceにはExternalNameというタイプがありまして、これを使うと、外部のサービスにDNS名でアクセスすることができます。これに対して、このエントリーでやりたいことは、IPアドレスを直に指定してアクセスするということです。

そんなことやりたいときなんてあります？と思われるかもしれませんが、自分の場合たまたまあったんです…。


TL;DR
=====
Selectorの無いClusterIPタイプのServiceを作って、さらにEndpointオブジェクトでアクセス先のサービスのIPを指定すれば良いです。

（もっといいやり方があったら教えてください）

### 2018/09/06 追記: 
ExternalNameでもIPを扱えるとのご指摘を頂いていますので、まずはそちらの方法を試したほうが良いと思います。ご指摘いただいている内容については本記事のコメント欄を参照ください。

やってみる
==========
例えばクラスターの外部に、123.123.123.123というIPアドレスでMySQLが立っているとします。この場合、以下のような内容のmanifestで、ServiceとEndpointを作成します。
後でも書きますが、ServiceにSelectorが指定されていないところがポイントです。

```yaml:mysql-external.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysqldb
spec:
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 3306
---
apiVersion: v1
kind: Endpoints
metadata:
  name: mysqldb
subsets:
  - addresses:
    # FIXME: use the right IP
    - ip: 123.123.123.123
    ports:
      - protocol: TCP
        port: 3306
```

このmanifestをクラスターに``kubectl apply``します。

```bash
% kubectl apply -f ./mysql-external.yaml
service/mysqldb created
endpoints/mysqldb created
```

結果を確認すると、こんな感じになります。

```Bash
% kubectl get service,endpoints
NAME                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    4m
service/mysqldb      ClusterIP   10.100.129.108   <none>        3306/TCP   2m

NAME                   ENDPOINTS              AGE
endpoints/kubernetes   192.168.39.97:8443     4m
endpoints/mysqldb      123.123.123.123:3306   2m
```

Endpointsというオブジェクトは、Serviceに対して実際のリクエストの送信先を決めているオブジェクトで、Selecterを指定してServiceを作ると（多くの場合そうしていると思います）自動で生成されているものです。
今回は、Endpointsの定義を明示して、指定したIPアドレスが送信先となるようにしています。こうすることで外部のIPアドレスにリクエストを届けてくれるServiceオブジェクトができあがります。

試しにService詳細情報を表示してみます。

```bash
% kubectl describe service mysqldb
Name:              mysqldb
Namespace:         default
Labels:            <none>
Annotations:       kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"mysqldb","namespace":"default"},"spec":{"ports":[{"port":3306,"protocol":"TCP"...
Selector:          <none>
Type:              ClusterIP
IP:                10.100.129.108
Port:              <unset>  3306/TCP
TargetPort:        3306/TCP
Endpoints:         123.123.123.123:3306
Session Affinity:  None
Events:            <none>
```

Endpointというところに、上で指定したIPアドレスが設定されていることがわかります。

では、このServiceを使って、クラスターの中からMySQLに接続してみます。
以下のコマンドで、クラスター内にmysql-clientが入ったコンテナ(Pod)を上げて、DBに接続してみます。

```Bash
% kubectl run -it --rm --image=mysql:8.0.3 --restart=Never mysql-client -- mysql -hmysqldb -uroot -ppassword
If you don't see a command prompt, try pressing enter.
...（中略）...
mysql> 
```

この例は、ユーザーはroot、パスワードはpasswordとなっている想定です。また、-hはホスト名やIPアドレスを指定するオプションですが、上で作成したServiceの名前を指定すればOKです。ServiceがDBリクエストを送ってくれていることになります。

クライアントのコンテナから見ると、Serviceオブジェクト経由でアクセスするだけなので、クラスター内の他のPodにアクセスするのとやることは全く同じです。クライアントは何も変更せずに、Serviceオブジェクトの変更によって接続先を切り替えられるので、開発環境ではクラスター内にコンテナ化されたDBを配置してそこにつなぐなど、色々な使い方が考えられそうです。

クライアントが上がった後は、好きなようにクエリできます。

```Bash
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
5 rows in set (0.27 sec)
```

以上で、Kubernetesクラスター外のIPアドレスに対してルーティングしてくれるServiceを作り、それを利用して外部のDBにアクセすることが出来ました。

繰り返しになりますが、他にいい方法があったらぜひコメントください。


参考リンク
==========

- 公式ドキュメントのSelectorのないServiceオブジェクトに関する記述: 
    * [https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors](https://kubernetes.io/docs/concepts/services-networking/service/#services-without-selectors)