Kubernetesでのコンポーネント間の通信をまとめる

# はじめに
CaaS(Container as a Service)を使った開発は増えてきていることと思います。
私自身もCaaS環境にてリリースを行うプロダクトを複数持っています。
そしてプロダクトを設計していくときに大事なのが、ネットワークですね。
マイクロサービス化していく中で、サービス同士での通信をする機会というのは増えていくと思います。
なので、今回Caas環境のサービスであるKubernetesの通信をまとめます。

なお、この記事に書くにあたり作成したコードは全て [walk8243/kubernetes-communication](https://github.com/walk8243/kubernetes-communication) にあります。

## まとめる通信
まとめる対象の通信は以下の画像にある通りです。
通信は、緑の四角のコンテナから、青の四角のコンテナに向けて送信します。
![まとめる通信の内容](https://github.com/walk8243/kubernetes-communication/raw/master/qiita%E7%94%A8%E8%B3%87%E6%96%99/%E3%82%B9%E3%83%A9%E3%82%A4%E3%83%891.JPG)

## デプロイファイル
Kubernetesにデプロイする際に使用するコードは全て、 [walk8243/kubernetes-communication/deploy.yaml](https://github.com/walk8243/kubernetes-communication/raw/master/deploy.yaml) にあります。

この記事中では、Kubernetesを扱うことのできる `minikube` を使っています。
`minikube` が既にインストールされていることを前提に進めていきますので、インストールしていない場合は [Minikubeのインストール - Kubernetes](https://kubernetes.io/ja/docs/tasks/tools/install-minikube/) を参考にインストールしてください。

### サーバーの基本設定
```.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: nginx
        image: nginx
```

上の設定をベースとして、ルートにアクセスされた際、 `Hello World!` と書かれた `index.html` を返します。

### クライアントの基本設定
```.yaml
apiVersion: v1
kind: Pod
metadata:
  name: client
spec:
  containers:
  - name: shell
    image: centos
    command:
    - /bin/sh
    - "-c"
    - "-"
    args:
    - "while :; do sleep 1000; done"
```

**shell** というコンテナがあります。このコンテナからサーバーに対してアクセスします。
なお、クライアントコンテナには、以下のようなスクリプトでログインします。

```.sh
kubectl exec -it client -c shell /bin/bash
```

# 実際に通信してみる
使用するコンテナなどをデプロイするためのスクリプトは用意しています。
よろしければGitHubから [walk8243/kubernetes-communication](https://github.com/walk8243/kubernetes-communication.git) をクローンしてお使いください。

```
# Linuxの場合
sh deploy.sh

# Windowsの場合
.\deploy.cmd
```

全てのケースにおいて、 `Hello World!` が返ってくれば通信成功です。

## ケース①コンテナ間（同一ポッド内）
同じPodの中で、異なるContainer同士での通信を行います。
ここでは、ポートとUNIXドメインソケットの2つ方法をご紹介致します。

このケースの中でログインするコンテナは、 **Deployment** によって作成されたコンテナになるので、 **Deployment** を指定してログインします。

```.sh
kubectl exec -it deploy/server -c shell --namespace=k8s-communication /bin/bash
```

### ポートで接続
#### 受信方法
ポートを使ったリクエストの受信には、nginxのDockerイメージで用意してくれている80番ポートをそのまま使います。

#### 送信方法
`curl` コマンドで、リクエストを送信します。

```.sh
curl localhost
```

### UNIXドメインソケットで接続
UNIXドメインソケットを用いた通信に関しては、 [Node.jsでもUNIXドメインソケットを使いたい#unixドメインソケットとは](https://qiita.com/walk8243/items/49ce3fc24500038f126f#unix%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E3%81%A8%E3%81%AF) に書いていますので、是非ご覧ください。

以下にような設定で、通信を行うことができます。

#### 受信方法
受信するためのnginxの設定は以下になります。

```/etc/nginx/conf.d/socket.conf
server {
    listen       unix:/var/sock/nginx.sock;
    root   /usr/share/nginx/html;
    index  index.html index.htm;
}
```

なお、 `/var/sock/nginx.sock` は通信開始時に配置されるので、デプロイ時に用意するのはsockファイルを配置するためのフォルダです。
`/var/sock` はKubernetesの `Volume` を使って作成します。

```.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: server
spec:
  replicas: 1
  template:
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: socket-dir
          mountPath: /var/sock
      volumes:
      - name: socket-dir
        emptyDir: {}
```

#### 送信方法
以下のコマンドを叩くことで、 `/var/sock/nginx.sock` を使ってUNIXドメインソケットによるリクエストの送信ができます。
なお、送信に利用している `nc` は `yum install nc` でインストールしています。

```.sh
echo 'GET /' | nc -U /var/sock/nginx.sock
```

このときの `/var/sock` フォルダは、受信側の `nginx` コンテナと `Volume` で共有しています。

## ケース②ポッド間（同一ネームスペース内）
ネームスペース `k8s-communication` 内にあるサービス `server` を使ってリクエストを送信します。

```.sh
curl server
```

使用するネームサーバーの情報は以下のようになっています。

```/etc/resolv.conf
nameserver 10.96.0.10
search k8s-communication.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
```

この中の `k8s-communication.svc.cluster.local` でドメイン解決が行われています。

## ケース③ポッド間（ネームスペース違い）
ケース②と同じように `curl server` としても、リクエストは届きません。
届かない理由は、ドメイン解決ができないことにあります。
なので、ケース②と使用するネームサーバーを変えます。

今回のようなネームスペースが違う場合で使用するネームサーバーは `svc.cluster.local` か `cluster.local` になります。
`svc.cluster.local` を使うケースは、 **Service** に向かって通信を行う場合です。
今回はケース②と同様にネームスペース `k8s-communication` 内にあるサービス `server` を使ってリクエストを送信したいため、 `svc.cluster.local` を使用します。
先ほどのケース②と同じようにネームサーバー `k8s-communication.svc.cluster.local` を見に行くことができるように、リクエスト送信時のホスト名を少し工夫します。

```.sh
curl server.k8s-communication
```

これで、ネームサーバー `svc.cluster.local` 経由で、ネームサーバー `k8s-communication.svc.cluster.local` を見に行くことが可能になり、無事リクエストが到達できるようになります。

なお、ネームスペース `k8s-communication-sub` でのネームサーバーの情報は以下の通りです。

```/etc/resolv.conf
nameserver 10.96.0.10
search k8s-communication-sub.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
```

## ケース④サーバー間
ここでは、minikubeの外からの通信を想定しています。
Kubernetesの中に作成したServiceを外部からアクセスできるようにするために、以下のコマンドを使用します。
このコマンドは、デプロイスクリプトの中に含まれています。

```.sh
minikube service server --namespace=k8s-communication
```

上記のコマンドを実行後に、以下のコマンドを実行して、リクエスト先のURLを表示してください。
そこで表示されたURLをコピーして、ブラウザなどからアクセスしてください。

```.sh
minikube service server --namespace=k8s-communication --url=true
```

# おわりに
今回、4パターンの場所からサーバーに向けてリクエストを送信してみました。
近い距離の方が簡単に通信できますが、遠くなっても適切に設定してあげればちゃんと通信することは可能です。
また、Kubernetes内でDNSの設備はしっかりとしているので、ルールさえ押さえておけば、固定値で簡単に扱えます。
通信方法に怖がらず、何を作りたいかに合わせてモノを設計していきましょう！！