Kubernetes （Azure AKS）とローカルネットワークをOpenVPN（443/tcp）で接続する

# はじめに
皆さん[Kubernetes](https://kubernetes.io/)使ってますか？サーバーサイドのインフラやネットワーク構成を斟酌せず、システムを構成するために必要なコンテナ群を宣言的に記述するだけで、ナンカイイカンジにサーバーサイドを組み立ててくれるカワイイヤツです。

一方そのKubernetes自体を構築し維持管理するのは大変なので、Kubernetes自体はクラウドに面倒を見てもらいたくなるのは世の常です。

しかし世の中は全てクラウドだけで完結するわけではなく、ローカルシステムと生のパケットをやり取りせざるを得ないことも、ままあります。ローカルネットワークとクラウドの仮想ネットワークをVPN接続するソリューションは、大体どのクラウドでも提供されていますが、要件によってはうまくハマらない場合があります。
> 例えば[Azureのポイント対サイトVPN](https://docs.microsoft.com/ja-jp/azure/vpn-gateway/point-to-site-about)はクライアントOSがWindowsとMacしかダメとか・・・

そこで今回は、[Kubernetes](https://kubernetes.io/) ([Azure AKS](https://azure.microsoft.com/ja-jp/services/container-service/))上にOpenVPNサーバを構築し、ローカルネットワーク（を模した別サイトの仮想ネットワーク）と接続してみます。最終的には、次のような構成になります。

![kubernetes-openvpn.png](https://qiita-image-store.s3.amazonaws.com/0/37648/0a373b99-f5c7-5bd0-d672-f42c0f2abef8.png)

# 検証した環境

|Kubernetesクラスタ|バージョン|
|:--|:--|
|AKS|Microsoft AKS 米国中部|
|Kubernetes|1.8.10|

|ローカルシステム|バージョン|
|:--|:--|
|クラウド|Microsoft Azure 東日本|
|OS|Ubuntu 16.04 LTS|

|kubectl端末|バージョン|
|:--|:--|
|azure cli|2.0.31|
|kubectl|1.10.0|
|OS|macOS Sierra 10.12.6|

# AKSの起動
Azure CLIを使って、米国中央リージョンにAKSを起動します。

注意） AKSを起動するためには、Service Principalを作成できる権限が必要です。もし403エラーが発生するようでしたら、Azureアカウントの管理者に相談してください。

## Azure CLIのログイン

```bash:login_cli
mac:~$ az login
```

oauth2で認証するためのキーコードが表示されますので、 https://microsoft.com/devicelogin にブラウザでアクセスしてキーコードを入力し、Azure CLIを認証してください。

## AKS用のリソースグループを作成

```bash:create_aks_resourcegroup
mac:~$ az group create --name aks --location centralus
```

米国中央リージョンにAKS用のリソースグループを作ります。

## AKS起動

```bash:start_aks
mac:~$ az aks create --resource-group aks --name aksCluster --node-count 1 --ssh-key-value $HOME/.ssh/azure.pub
```

米国中央リージョンにAKSを起動します。

## kubectlを設定

```bash:configure_kubectl
mac:~$ az aks get-credentials --resource-group aks --name aksCluster
```

起動したAKSに接続するように、kubectlを設定します。

```bash:get_nodes
mac:~$ kubectl get nodes
NAME                       STATUS    ROLES     AGE       VERSION
aks-nodepool1-37708792-0   Ready     agent     16m       v1.8.10
```

上記のように、`aks-...`というnodeがリストされていれば、AKSに接続しています。

## （オプション）Kubernetes dashboardの表示

```bash:open_dashboard
mac:~$ az aks browse --resource-group aks --name aksCluster --disable-browser
```

別のコンソールから`az aks browse`コマンドを実行すると、AKSのManagerとのトンネルを開設します。トンネルが開設されている状態で http://127.0.0.1:8001/ にアクセスすると、Kubernetes dashboardを参照することができます。

# Kubernetes上にOpenVPNサーバを構築
AKSの準備ができたので、AKSのKubernetes上に443/tcpでlistenするOpenVPNサーバを構築します。

## スクリプト類の取得

```bash:
mac:~$ git clone https://github.com/nmatsui/kubernetes-openvpn.git
mac:~$ cd kubernetes-openvpn
```

Kubernetes上にOpenVPNサーバを構築するためのスクリプト類を取得します。
> cloneした[nmatsui/kubernetes-openvpn](https://github.com/nmatsui/kubernetes-openvpn)のスクリプトやyamlは、[kube-openvpn](https://github.com/pieterlange/kube-openvpn)をベースに443/tcpでVPNをlistenするようにアレンジしたものです。

## PKI鍵生成

```bash:create_pki
mac:kubernetes-openvpn$ docker run --user=$(id -u) -e OVPN_SERVER_URL=tcp://vpn.example.com:443 -v $PWD:/etc/openvpn:z -ti ptlange/openvpn ovpn_initpki
mac:kubernetes-openvpn$ docker run --user=$(id -u) -e EASYRSA_CRL_DAYS=180 -v $PWD:/etc/openvpn:z -ti ptlange/openvpn easyrsa gen-crl
```

kube-openvpnのコンテナを用いて、VPN接続で用いるPKI鍵を生成します。FQDNとして `vpn.example.com` という存在しないドメインを指定していますが、このFQDNは最終的にはOpenVPNクライアントの `/etc/hosts` で名前解決します。

> ここで指定するFQDNは、Kubernetes上に構築されるOpenVPN PODのラベル名にも用いられます。 `vpn.example.com` 以外のFQDNを使用する場合、 `testproxy-1.yaml` と `testproxy-2.yaml` のSelectorも合わせて変更してください。

## KubernetesのService用ネットワークとPod用ネットワークのCIDR取得

```bash:get_service_cidr
mac:kubernetes-openvpn$ kubectl cluster-info dump | grep "service-cluster-ip-range"
```
```bash:get_pod_cidr
mac:kubernetes-openvpn$ kubectl cluster-info dump | grep "cluster-cidr"
```

OpenVPNはクライアントのルーティングテーブルにOpenVPNサーバへのルーティングをpushすることができます。そのために、KubernetesのServiceとPodのCIDRを取得します。

ただしAzure AKSでは、ServiceのCIDRがうまく取れません（もしかしたら、`az aks`とかで取れるのかも？？）
見る限りAzure AKSは `10.0.0.0/16` でServiceのIPを振っているようですので、ローカルシステム側の仮想ネットワークのCIDRをかぶらないようにしておけば、とりあえず `10.0.0.0/8` を全部Kubernetesにまわしてしまえば問題なさげな感じです。

## OpenVPNサーバ起動

```bash:start_openvpn_server
mac:kubernetes-openvpn$ ./kube-openvpn/deploy.sh default tcp://vpn.example.com:443 10.0.0.0/8 10.244.0.0/16
```

`./kube-openvpn/deploy.sh` を用いて、Kubernetes上にOpenVPNサーバを起動します。内部的にはざっくり次のようなことをしています。

* namespaceやFQDN、ポート、指定されたCIDRを元にOpenVPNの各種設定値を生成してconfigmapに設定
* 生成したPKI鍵をsecretに登録
* `./kube-openvpn/deployment.yaml`を用いてOpenVPN PODとServiceを起動

## OpenVPNクライアント用の設定ファイルを生成

```bash:create_ovpn
mac:kubernetes-openvpn$ docker run --user=$(id -u) -v $PWD:/etc/openvpn:z -ti ptlange/openvpn easyrsa build-client-full client nopass
mac:kubernetes-openvpn$ docker run --user=$(id -u) -e OVPN_SERVER_URL=tcp://vpn.example.com:443 -v $PWD:/etc/openvpn:z --rm ptlange/openvpn ovpn_getclient client > client.ovpn
```

OpenVPNクライアントがOpenVPNサーバに接続する際の設定ファイルを生成しておきます（後で検証用のVMにscpします）。

# 接続検証用のローカルシステムの起動
米国中央のAKS上にOpenVPNサーバが起動しましたので、次はローカルシステムを模した仮想ネットワークとクライアントVM（2台）を東日本リージョンに起動します。

## ローカルシステム用のリソースグループを作成

```bash:create_client_resourcegroup
mac:~$ az group create --name client --location japaneast
```

東日本リージョンにローカルシステム用のリソースグループを作ります。

## ローカルシステム用の仮想ネットワークを作成

1. vnetとsubnetを作成

    ```bash:create_vnet
    mac:~$ az network vnet create --resource-group client --name client-vnet --address-prefix 192.168.0.0/16 --subnet-name public --subnet-prefix 192.168.0.0/24
    ```
1. 2台のクライアント用にpublic ipを二つ作成

    ```bash:create_publicip_1
    mac:~$ az network public-ip create --resource-group client --name client-publicip-1 --dns-name client-1
    ```
    ```bash:create_publicip_2
    mac:~$ az network public-ip create --resource-group client --name client-publicip-2 --dns-name client-2
    ```
1. セキュリティグループを作成

    ```bash:create_nsg
    mac:~$ az network nsg create --resource-group client --name client-nsg
    ```
1. kubectl端末からのSSHを許可

    ```bash:add_ingress_rule
    mac:~$ az network nsg rule create --resource-group client --nsg-name client-nsg --name AllowSSHInBound --protocol tcp --destination-port-range 22 --source-address-prefixes WWW.XXX.YYY.ZZZ/32 --access allow --priority 1000
    ```
1. セキュリティグループとpublic ipを割り当てた仮想NICを二つ作成

    ```bash:create_nic_1
    mac:~$ az network nic create --resource-group client --name client-nic-1 --vnet-name client-vnet --subnet public --public-ip-address client-publicip-1 --network-security-group client-nsg
    ```
    ```bash:create_nic_2
    mac:~$ az network nic create --resource-group client --name client-nic-2 --vnet-name client-vnet --subnet public --public-ip-address client-publicip-2 --network-security-group client-nsg
    ```

KubernetesにルーティングするCIDR（`10.0.0.0/8`）とかぶらないように、 `192.168.0.0/16` の仮想ネットワーク上に `192.168.0.0/24` のサブネットを構築します。
またInternetからSSHの受信を許可したセキュリティグループを作成し（デフォルトルールにより、仮想ネットワーク内の送受信、及び仮想ネットワークからInternetへの送信は許可されます）、セキュリティグループとpublic ipを割り当てた仮想NICを作成します。

## ローカルシステム用の可用性セットを作成

```bash:create_as
mac:~$ az vm availability-set create --resource-group client --name client-as
```

今回は検証なので不要ではありますが、とりあえず可用性セットも作っておきます。

## クライアントVMを2台起動

```bash:start_vm_1
mac:~$ az vm create --resource-group client --name ubuntu-1 --location japaneast --availability-set client-as --nics client-nic-1 --image UbuntuLTS --size Standard_A1 --admin-username ubuntu --ssh-key-value $HOME/.ssh/azure.pub
```
```bash:start_vm_2
mac:~$ az vm create --resource-group client --name ubuntu-2 --location japaneast --availability-set client-as --nics client-nic-2 --image UbuntuLTS --size Standard_A1 --admin-username ubuntu --ssh-key-value $HOME/.ssh/azure.pub
```

作成した仮想NICを指定して、Ubuntu 16.04 LTSのVMを2台起動します。

# ローカルシステムからKubernetesへVPNで接続
では準備ができましたので、ローカルシステム上の検証用VM（ubuntu-1）からKubernetesへ443/tcpでVPN接続してみます。

## OpenVPNクライアント用の設定ファイルをubuntu-1にSCP

[OpenVPNクライアント用の設定ファイルを生成](#openvpnクライアント用の設定ファイルを生成)で作成された `client.ovpn` をubuntu-1にSCPします。

## OpenVPN ServiceのExternal IP確認と名前解決

```bash:get_externalip
mac:kubernetes-openvpn$ kubectl get service -l openvpn=vpn.example.com
NAME      TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)         AGE
openvpn   LoadBalancer   10.0.206.64   WW.XX.YY.ZZ    443:32041/TCP   8m
```

Kubernetes上に起動したOpenVPN Serviceのexternal ipを確認し、OpenVPNサーバ構築時に指定した `vpn.example.com` で名前解決できるようにします。とりあえず今回は、ubuntu-1の `/etc/hosts` に設定しておきます。

```bash:/etc/hosts
ubuntu-1:~$ cat /etc/hosts
127.0.0.1 localhost
WW.XX.YY.ZZ vpn.example.com

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
```

## ubuntu-1からKubernetesへVPN接続の確認
1. OpenVPNパッケージをインストール

    ```bash:install_openvpn_on_ubuntu-1
    ubuntu-1:~$ sudo apt update && sudo apt install openvpn -y
    ```
1. KubernetesのOpenVPNサーバへ接続

    ```bash:connect_openvpn
    ubuntu-1:~$ sudo openvpn ./client.ovpn
    ...
    Tue Apr 17 04:01:24 2018 Initialization Sequence Completed
    ```

Kubernetes上のOpenVPNサーバへ接続します。 `Initialization Sequence Completed` と表示されれば、接続成功です。

## eth0とtun0のIPアドレス確認

```bash:eth0
ubuntu-1:~$ ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0d:3a:50:85:26 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.4/24 brd 192.168.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::20d:3aff:fe50:8526/64 scope link
       valid_lft forever preferred_lft forever
```
```bash:tun0
ubuntu-1:~$ ip addr show dev tun0
3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 100
    link/none
    inet 10.140.0.2/24 brd 10.140.0.255 scope global tun0
       valid_lft forever preferred_lft forever
    inet6 fe80::7821:bd24:7fde:a992/64 scope link flags 800
       valid_lft forever preferred_lft forever
```

VPN接続をしているコンソールとは別のコンソールを開き、ubuntu-1のeth0（ローカルシステムの仮想ネットワークに接続したデバイス）のIPアドレスと、tun0（OpenVPNによって開設されたVPNトンネルに接続したデバイス）のIPアドレスを確認します。

今回は、eth0に `192.168.0.4` tun0に `10.140.0.2` が割り当てられました。

## ルーティングテーブル確認

```bash:show_route
ubuntu-1:~$ ip route show
default via 192.168.0.1 dev eth0
10.0.0.0/8 via 10.140.0.1 dev tun0
10.140.0.0/24 dev tun0  proto kernel  scope link  src 10.140.0.2
10.244.0.0/16 via 10.140.0.1 dev tun0
168.63.129.16 via 192.168.0.1 dev eth0
169.254.169.254 via 192.168.0.1 dev eth0
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.4
```

ついでにルーティングテーブルも確認しておきます。OpenVPNサーバ起動時に指定した`10.0.0.0/8`と`10.244.0.0/16`が、tun0経由で`10.140.0.1`にルーティングされることがわかります（`10.140.0.1`は、Kubernetes上のOpenVPNサーバのVPNトンネル出口のIPアドレスです）。

## VPN接続を停止

openvpnクライアントからの接続をCtrl-Cで停止し、一旦VPN接続を解除しておきます。

# 接続検証
ここまでで、Kubernetes上のOpenVPNサーバとローカルシステムがOpenVPNによってネットワーク的に繋がったことがわかりました。ただしこれだけだとつまらないので、実際にローカルシステムからKubernetesのHTTPサービスを、逆にKubernetes上のPODからローカルシステムのHTTPデーモンに接続してみましょう。

## Kubernetes側に検証用のPODやServiceを起動しVPN再接続
### OpenVPNサーバにローカルシステムへのNAT設定を追加

1. OpenVPNクライアントの設定

    ```bash:add_ccd
    mac:kubernetes-openvpn$ kubectl edit configmap openvpn-ccd
    ```
    ```diff:diff
    --- /tmp/openvpn-ccd.org	2018-04-17 13:23:32.000000000 +0900
    +++ /tmp/openvpn-ccd	2018-04-17 13:24:14.000000000 +0900
    @@ -5,6 +5,7 @@
     apiVersion: v1
     data:
       example: ifconfig-push 10.140.0.5 255.255.255.0
    +  ubuntu-1: ifconfig-push 10.140.0.2 255.255.255.0
     kind: ConfigMap
     metadata:
       annotations:
    ```
1. OpenVPNクライアントに対するPort mappingの設定

    ```bash:add_portmapping
    mac:kubernetes-openvpn$ kubectl edit configmap openvpn-portmapping
    ```
    ```diff:diff
    --- /tmp/openvpn-portmapping.org	2018-04-17 13:28:40.000000000 +0900
    +++ /tmp/openvpn-portmapping	2018-04-17 13:29:10.000000000 +0900
    @@ -5,6 +5,8 @@
     apiVersion: v1
     data:
       "20080": example:80
    +  "8081": ubuntu-1:8081
    +  "8082": ubuntu-1:8082
     kind: ConfigMap
     metadata:
       annotations:
    ```

Kubernetes上のOpenVPNに、NAT設定を追加します（configmap経由で設定できるようになっています）。

OpenVPNサーバの `8081` 及び `8082` ポートに到達したパケットは、VPNトンネルを通って `10.140.0.2`（ローカルシステムのubuntu-1のtun0のIPアドレス）の対応するポートに転送されます。

### OpenVPNサーバのProxyServiceをKubernetes上に起動

```bash:start_testproxy
mac:kubernetes-openvpn$ kubectl apply -f testproxy-1.yaml
mac:kubernetes-openvpn$ kubectl apply -f testproxy-2.yaml
```

Kubernetesの仕組み上、OpenVPNサーバのPODと他のPODが直接通信するのではなく、OpenVPNサーバにつながるServiceと他のPODが通信する形になります。そのため、8081（や8082）をlistenするServiceを起動してOpenVPNサーバのPODと接続します。

```yaml:testproxy-1.yaml
apiVersion: v1
kind: Service
metadata:
  name: testproxy-1
  labels:
    app: testproxy-1
spec:
  type: ClusterIP
  selector:
    openvpn: vpn.example.com
  ports:
  - port: 8081
    targetPort: 8081
```

### 検証用のHTTPサービスをKubernetes上に起動

```bash:start_testservice
mac:kubernetes-openvpn$ kubectl apply -f testservice.yaml
```

ローカルシステム側からKuberntesへの到達検証用に、どのようなパスにアクセスしても `{"message":"hello world!"}` というjsonを返す検証用のServiceをポート3000でKubernetes上に起動します。

```yaml:testservice.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: testserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: testserver
  template:
    metadata:
      labels:
        app: testserver
    spec:
      containers:
      - name: testserver
        image: nmatsui/hello-world-api:latest
        ports:
        - containerPort: 3000
          name: testserver
---
apiVersion: v1
kind: Service
metadata:
  name: testservice
  labels:
    app: testservice
spec:
  type: ClusterIP
  selector:
    app: testserver
  ports:
  - port: 3000
```

このtestserviceのKubernetes内部IPアドレスを確認しておきます。

```bash:get_testservice
mac:kubernetes-openvpn$ kubectl get services -l app=testservice

NAME          TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
testservice   ClusterIP   10.0.19.96   <none>        3000/TCP   24m
```

今回は`10.0.19.96`が割り当てられました。

### ubuntu-1でVPN接続を再開

```bash:reconnect_openvpn
ubuntu-1:~$ sudo openvpn ./client.ovpn
```

ubuntu-1でVPN接続を再開します。再接続することにより、configmapで設定したportmappingが有効になります。

## ubuntu-1とKuberntesの接続検証
それでは、ubuntu-1とKuberntesが双方向に接続できることを確認します。

### ubuntu-1 ⇒ Kubernetes上のtestserviceの疎通確認

```bash:from_ubuntu-1_to_testservice
ubuntu-1:~$ curl -i http://10.0.19.96:3000/
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 17 Apr 2018 04:47:19 GMT
Connection: keep-alive
Transfer-Encoding: chunked

{"message":"hello world!"}
```

ローカルシステムのubuntu-1からKubernetesのtestserviceのIPアドレスへ、VPNトンネルを通ってHTTP接続できることが確認できました。

![ubuntu-1=>testservice.png](https://qiita-image-store.s3.amazonaws.com/0/37648/4f3bb489-03d7-0b27-440e-ebfcc112895e.png)

### Kubernetes上のPOD ⇒ ubuntu-1のHTTPデーモンの疎通確認

1. ubuntu-1にindex.htmlを準備

    ```html:index.html
    <html>
      <head><title>TEST PAGE</title></head>
      <body>ubuntu-1</body>
    </html>
    ```
1. ubuntu-1で8081ポートでHTTPデーモン起動

    ```bash:start_httpd
    ubuntu-1:~$ python -m SimpleHTTPServer 8081
    ```
1. Kubernetes上にcurlが使えるPODを起動

    ```bash:start_pod
    mac:kubernetes-openvpn$ kubectl run testpod --rm -it --image=yauritux/busybox-curl
    ```
1. PODからtestproxyの8081ポートへアクセス

    ```bash:from_pod_to_ubuntu-1
    pod:~$ curl -i http://testproxy-1:8081/
    HTTP/1.0 200 OK
    Server: SimpleHTTP/0.6 Python/2.7.12
    Date: Tue, 17 Apr 2018 05:02:06 GMT
    Content-type: text/html
    Content-Length: 79
    Last-Modified: Tue, 17 Apr 2018 04:57:22 GMT

    <html>
      <head><title>TEST PAGE</title></head>
      <body>ubuntu-1</body>
    </html>
    ```

KubernetesのPODから、OpenVPNサーバのNATを経由して、ubuntu-1のHTTPデーモンにHTTP接続できることが確認できました。

![testpod=>ubuntu-1.png](https://qiita-image-store.s3.amazonaws.com/0/37648/74947986-f1d8-696b-fd1b-a8525e697cc6.png)


## ubuntu-2とKubernetesの接続検証
最後に、Kubernetesとは直接接続されていないubuntu-2とKubernetesが、ubuntu-1でNATすることで双方向に接続できることを確認します。

### ubuntu-1にNATを設定

1. ubuntu-1でパケットのforwardを許可

    ```bash:enable_ip_forward
    ubuntu-1:~$ sudo sysctl -w net.ipv4.ip_forward=1
    ```
1. ubuntu-1のeth0(`192.168.0.4`)の3000ポート ⇒ Kubernetesのtestserviceの3000ポートへforward

    ```bash:from_localvnet_to_testservice
    ubuntu-1:~$ sudo iptables -t nat -A PREROUTING -m tcp -p tcp --dst 192.168.0.4 --dport 3000 -j DNAT --to-destination 10.0.19.96:3000
    ubuntu-1:~$ sudo iptables -t nat -A POSTROUTING -m tcp -p tcp --dst 10.0.19.96 --dport 3000 -j SNAT --to-source 10.140.0.2
    ```
1. ubuntu-1のtun0(`10.140.0.2`)の8082ポート ⇒ ubuntu-2の8082ポートへforward

    ```bash:from_vpn_to_ubuntu-2
    ubuntu-1:~$ sudo iptables -t nat -A PREROUTING -m tcp -p tcp --dst 10.140.0.2 --dport 8082 -j DNAT --to-destination 192.168.0.5:8082
    ubuntu-1:~$ sudo iptables -t nat -A POSTROUTING -m tcp -p tcp --dst 192.168.0.5 --dport 8082 -j SNAT --to-source 192.168.0.4
    ```
1. NATテーブルを確認

    ```bash:show_nat
    ubuntu-1:~$ sudo iptables -L -t nat
    Chain PREROUTING (policy ACCEPT)
    target     prot opt source               destination
    DNAT       tcp  --  anywhere             192.168.0.4          tcp dpt:3000 to:10.0.19.96:3000
    DNAT       tcp  --  anywhere             10.140.0.2           tcp dpt:8082 to:192.168.0.5:8082

    Chain INPUT (policy ACCEPT)
    target     prot opt source               destination

    Chain OUTPUT (policy ACCEPT)
    target     prot opt source               destination

    Chain POSTROUTING (policy ACCEPT)
    target     prot opt source               destination
    SNAT       tcp  --  anywhere             10.0.19.96           tcp dpt:3000 to:10.140.0.2
    SNAT       tcp  --  anywhere             192.168.0.5          tcp dpt:8082 to:192.168.0.4
    ```
1. filterテーブルを確認
    * 本来はFORWARDフィルタも設定しなければなりませんが、AzureのVMはデフォルトのフィルタルールが全てACCEPTなので割愛します。

    ```bash:show_filter
    ubuntu-1:~$ sudo iptables -L -t filter
    Chain INPUT (policy ACCEPT)
    target     prot opt source               destination

    Chain FORWARD (policy ACCEPT)
    target     prot opt source               destination

    Chain OUTPUT (policy ACCEPT)
    target     prot opt source               destination
    ```
1. (option)FORWARDフィルタを追加
    * もしFORWARDをデフォルトでDROPするように設定している場合は、次のようにFORWARDフィルタを追加してください。

    ```bash:forward_rule
    ubuntu-1:~$ sudo iptables -A FORWARD -m tcp -p tcp --dst 10.0.19.96 --dport 3000 -j ACCEPT
    ubuntu-1:~$ sudo iptables -A FORWARD -m tcp -p tcp --dst 192.168.0.5 --dport 8082 -j ACCEPT
    ubuntu-1:~$ sudo iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
    ```
    ```bash:show_forward_filter
    ubuntu-1:~$ sudo iptables -nL FORWARD
    Chain FORWARD (policy DROP)
    target     prot opt source               destination
    ACCEPT     tcp  --  0.0.0.0/0            10.0.19.96           tcp dpt:3000
    ACCEPT     tcp  --  0.0.0.0/0            192.168.0.5          tcp dpt:8082
    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            state RELATED,ESTABLISHED
    ```

ubuntu-1のiptablesへ、ローカルシステムの仮想ネットワークからtestservice（`10.0.19.96`）へポート3000を転送するNAT定義と、VPNネットワークからubuntu-2（`192.168.0.5`）へポート8082を転送するNAT定義を設定します。

### ubuntu-2 ⇒ Kubernetes上のtestserviceの疎通確認

```bash:from_ubuntu-2_to_testservice
ubuntu-2:~$ curl -i http://192.168.0.4:3000
HTTP/1.1 200 OK
Content-Type: application/json
Date: Tue, 17 Apr 2018 05:28:33 GMT
Connection: keep-alive
Transfer-Encoding: chunked

{"message":"hello world!"}
```

ubuntu-2はKubernetesと接続されておらず、特段のルーティング設定もしていないため、ubuntu-2からtestserviceのIPアドレス `10.0.19.96` へ直接パケットを投げることはできません。しかしubuntu-1（`192.168.0.4`）がポート3000をtestservice（`10.0.19.96`）へNATするため、ubuntu-1経由でKubernetesのtestserviceへHTTP接続できます。

![ubuntu-2=>testservice.png](https://qiita-image-store.s3.amazonaws.com/0/37648/9b6cfe8c-2693-945a-8a57-545bf2230b37.png)

### Kubernetes上のPOD ⇒ ubuntu-2のHTTPデーモンの疎通確認

1. ubuntu-2にindex.htmlを準備

    ```html:index.html
    <html>
      <head><title>TEST PAGE</title></head>
      <body>ubuntu-2</body>
    </html>
    ```
1. ubuntu-2で8082ポートでHTTPデーモンを起動

    ```bash:start_httpd
    ubuntu-2:~$ python -m SimpleHTTPServer 8082
    ```
1. Kubernetes上にcurlが使えるPODを起動

    ```bash:start_pod
    mac:kubernetes-openvpn$ kubectl run testpod --rm -it --image=yauritux/busybox-curl
    ```
1. PODからtestproxyの8082ポートへアクセス

    ```bash:from_pod_to_ubuntu-2
    pod:~$ curl -i http://testproxy-2:8082/
    HTTP/1.0 200 OK
    Server: SimpleHTTP/0.6 Python/2.7.12
    Date: Tue, 17 Apr 2018 05:45:23 GMT
    Content-type: text/html
    Content-Length: 79
    Last-Modified: Tue, 17 Apr 2018 05:35:00 GMT

    <html>
      <head><title>TEST PAGE</title></head>
      <body>ubuntu-2</body>
    </html>
    ```

KubernetesのPODから、OpenVPNサーバのNATとubuntu-1のNATを経由して、ubuntu-2のHTTPデーモンにHTTP接続できることが確認できました。

![testpod=>ubuntu-2.png](https://qiita-image-store.s3.amazonaws.com/0/37648/a824aa95-b18b-a184-0410-f909e53d9ee9.png)

# さいごに
ということで、Kubernetes上に構築したOpenVPNサーバを経由してKubernetesとローカルシステムの仮想ネットワークを接続することができました(現時点では、OpenVPN Podがspofですが・・・）。
OpenVPNクライアントVMのNAT周りの設定が面倒ですが、ローカルシステム側の他のVMは特段の設定をせずともKubernetesと連携できるようになるため、ここは我慢のしどころということで。
上手く活用して、幸せなKubernetesライフを過ごしましょう！