ロボットやラズパイのカメラ映像をインターネット越しに安全にブラウザで視聴する方法（2/3）

1. [ロボットやラズパイのカメラ映像をインターネット越しに安全にブラウザで視聴する方法（1/3）](https://qiita.com/nmatsui/items/d1f7ceaa84cf805ee636) <br/> 概要編
2. [ロボットやラズパイのカメラ映像をインターネット越しに安全にブラウザで視聴する方法（2/3）](https://qiita.com/nmatsui/items/8bc9dc224fd9d288c56c) <br/> **クラウド側環境構築編** <-- 今回
3. [ロボットやラズパイのカメラ映像をインターネット越しに安全にブラウザで視聴する方法（3/3）](https://qiita.com/nmatsui/items/30adbeebd15b4c3de0ea) <br/> ロボット側環境構築編

# はじめに
前回は、VPNやWebRTCを使ったインターネット越しにカメラ映像を視聴するシステムの概要とネットワーク構成について書きました。今回は、Azure上にクラウド側環境を構築していきます。

なおLet's EncryptでTLS証明書を取得しますので、お名前.com等のドメインレジストラからドメインを一つ調達しておいてください。

# 各種バージョン
* クライアント側

||バージョン|
|:--|:--|
|OS|macOS Sierra 10.12.6|
|azure-cli|2.0.61|
|kubectl|1.14.0|
|docker|18.09.2|
|git|2.4.13|
|envsubst|0.19.8.1|

* クラウド側

||バージョン|
|:--|:--|
|AKS|1.12.6|

# 環境変数の設定
クラウド側環境を構築する際に必要になる設定値を、環境変数として定義しておきます。 `DOMAIN` や `EMAIL` は、自分が所有するものに適切に変更してください。それ以外の環境変数は、特に理由がなければ変更しなくても大丈夫です（既存のAzure上で同じ名前のリソースがある場合など）。

## 個別に変更する必要がある環境変数
```bash:TERMINAL1
kurento-robot-camera-k8s$ export DOMAIN="example.com"
kurento-robot-camera-k8s$ export EMAIL="nobody@example.com"
```
## 利用するAzureリージョン
```bash:TERMINAL1
kurento-robot-camera-k8s$ export REGION="japaneast"
```
## Azureのリソース名
```bash:TERMINAL1
kurento-robot-camera-k8s$ export DNS_ZONE_RG="dns-zone"
kurento-robot-camera-k8s$ export SYSTEM_RG="kurento"
kurento-robot-camera-k8s$ export VNET_NAME="kurentovnet"
kurento-robot-camera-k8s$ export PODSUBNET_NAME="kurentosubnet"
kurento-robot-camera-k8s$ export GW_PUBLICIP="gwpublicip"
kurento-robot-camera-k8s$ export GW_NAME="kurentogw"
kurento-robot-camera-k8s$ export AKS_NAME="kurentoaks"
```
## AKSノードのスペック（ノード数は3以上）
```bash:TERMINAL1
kurento-robot-camera-k8s$ export NODE_COUNT=3
kurento-robot-camera-k8s$ export NODE_VM_SIZE="Standard_D2s_v3"
kurento-robot-camera-k8s$ export NODE_OSDISK_SIZE_GB=64
kurento-robot-camera-k8s$ export K8S_VERSION="1.12.6"
```
> 2019/04/04時点で、東日本リージョンのAKSは `1.12.6` が最新版でした。

## 自動生成するユーザー名やパスワード
```bash:TERMINAL1
kurento-robot-camera-k8s$ export COTURN_CREDENTIAL="$(cat /dev/urandom | \
  LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 8):$(cat /dev/urandom | \
  LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 16)"; echo ${COTURN_CREDENTIAL}
kurento-robot-camera-k8s$ export BASIC_USERNAME="$(cat /dev/urandom | \
  LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 8)"; echo ${BASIC_USERNAME}
kurento-robot-camera-k8s$ export BASIC_PASSWORD="$(cat /dev/urandom | \
  LC_CTYPE=C tr -dc 'a-zA-Z0-9' | head -c 16)"; echo ${BASIC_PASSWORD}
```
> 上記のコマンドはBSD系のMacOS用です。Linuxの場合、 `$(cat /dev/urandom 2>/dev/null | head -n 40 | tr -cd 'a-zA-Z0-9' | head -c 8)` のようなコマンドで代替できると思います。

# Azureにログイン
`az` コマンドを用いてAzureにログインします。会社でAzureを利用している場合など、 "規定のディレクトリ" 以外にテナントが存在する場合は、適切なテナントを指定してください。

```bash:TERMINAL1_規定のディレクトリにログイン
kurento-robot-camera-k8s$ az login
```
```bash:TERMINAL1_テナントを指定してログイン（テナント名を適切に変更）
kurento-robot-camera-k8s$ export TENANT="example.onmicrosoft.com"
kurento-robot-camera-k8s$ az login --tenant ${TENANT}
```

# ソースコードの取得
Githubより、今回構築するシステムのソースコードを取得します。

```bash:TERMINAL1
$ git clone https://github.com/nmatsui/kurento-robot-camera-k8s.git
$ cd kurento-robot-camera-k8s/
```

# Azure Virtual NetworkとAzure VPN Gatewayの構築
[Azure Virtual Network](https://azure.microsoft.com/ja-jp/services/virtual-network/)を用いて、今回のシステムに必要な仮想ネットワークを構築します。またその仮想ネットワーク上に[Azure VPN Gateway](https://azure.microsoft.com/ja-jp/services/vpn-gateway/)を構成し、IKEv2でPoint-to-siteのVPNトンネルを敷設できるようにします。
![fig3.png](https://qiita-image-store.s3.amazonaws.com/0/37648/d3fb04fc-91c3-f3c6-42d4-87cbe1af7add.png)

## 今回のシステム用のリソースグループ作成
```bash:TERMINAL1
kurento-robot-camera-k8s$ az group create --name ${SYSTEM_RG} --location ${REGION}
```
## Azure Virtual Networkを作成
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network vnet create \
  --name ${VNET_NAME} \
  --resource-group ${SYSTEM_RG} \
  --location ${REGION} \
  --address-prefix 10.168.0.0/16 \
  --subnet-name ${PODSUBNET_NAME} \
  --subnet-prefix 10.168.0.0/24
```
## VPN Gateway用subnetを追加
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network vnet subnet create \
  --vnet-name ${VNET_NAME} \
  --name GatewaySubnet \
  --resource-group ${SYSTEM_RG} \
  --address-prefix 10.168.255.0/27
```
Azureの場合、VPN Gateway用subnetの名前は `GatewaySubnet` に固定されています。
## VPN Gateway用Public IPの取得
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network public-ip create \
  --name ${GW_PUBLICIP} \
  --resource-group ${SYSTEM_RG} \
  --allocation-method Dynamic
```
## VPN Gatewayを作成
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network vnet-gateway create \
  --name ${GW_NAME} \
  --resource-group ${SYSTEM_RG} \
  --location ${REGION} \
  --public-ip-address ${GW_PUBLICIP} \
  --vnet ${VNET_NAME} \
  --gateway-type Vpn \
  --sku VpnGw1 \
  --vpn-type RouteBased \
  --address-prefixes 10.255.0.0/24 \
  --client-protocol IkeV2 \
  --no-wait
```
VPN Gatewayの作成にはある程度時間がかかるようです。この記事を書いた際は、20分ぐらいかかりました。（[公式マニュアル](https://docs.microsoft.com/ja-jp/azure/vpn-gateway/create-routebased-vpn-gateway-cli)によれば、45分以上かかる場合もあるそうな）。
## VPN Gatewayの確認
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network vnet-gateway show \
  --name ${GW_NAME} \
  --resource-group ${SYSTEM_RG} \
  --query provisioningState \
  --output tsv
```
構築に成功すれば、 `Succeeded` と表示されます。

# AKS起動
先程作成したAzure Virtual Network上に、Azure AKSを起動します。
![fig4.png](https://qiita-image-store.s3.amazonaws.com/0/37648/1caad891-9fc2-8e88-677d-ab3af04bacfc.png)
実はAzure AKSは、何も指定しなければAKS専用のAzure Virtual Networkを自動的に構成し、その上にKubernetesのデフォルトのネットワークドライバを用いてK8S用の仮想ネットワークを重畳します。K8S単体で動作させるだけならば、これで問題ありません。
しかし今回は、POD間通信で用いられるK8S内部のネットワークをAzure VPN Gateway経由でロボットのLANにまで延伸したいので、K8SのネットワークドライバとしてAzure Virtual Networkを明示的に指定してAKSを起動します。
## pod用subnetのIDを取得
```bash:TERMINAL1
kurento-robot-camera-k8s$ export PODSUBNET_ID=$(az network vnet subnet list \
  --resource-group ${SYSTEM_RG} \
  --vnet-name ${VNET_NAME} \
  --query "[?name=='${PODSUBNET_NAME}'].id" \
  --output tsv)
```
## network pluginとsubnetを指定してAKSを起動
```bash:TERMINAL1
kurento-robot-camera-k8s$ az aks create \
  --name ${AKS_NAME} \
  --resource-group ${SYSTEM_RG} \
  --location ${REGION} \
  --node-count ${NODE_COUNT} \
  --node-vm-size ${NODE_VM_SIZE} \
  --node-osdisk-size ${NODE_OSDISK_SIZE_GB} \
  --network-plugin azure \
  --vnet-subnet-id ${PODSUBNET_ID} \
  --docker-bridge-address 172.17.0.1/16 \
  --dns-service-ip 10.2.0.10 \
  --service-cidr 10.2.0.0/24 \
  --no-ssh-key \
  --kubernetes-version ${K8S_VERSION} \
  --no-wait
```
VPN Gatewayと同様に、AKSの起動にも時間がかかります。この記事を書いた際は、やはり20分ぐらいかかりました。
## AKSの確認
```bash:TERMINAL1
kurento-robot-camera-k8s$ az aks show \
  --name ${AKS_NAME} \
  --resource-group ${SYSTEM_RG} \
  --query provisioningState \
  --output tsv
```
構築に成功すれば、 `Succeeded` と表示されます。
## Credentialの取得
```bash:TERMINAL1
kurento-robot-camera-k8s$ az aks get-credentials \
  --name ${AKS_NAME} \
  --resource-group ${SYSTEM_RG} \
  --overwrite-existing
```
## AKSのnode確認
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get nodes
NAME                       STATUS   ROLES   AGE     VERSION
aks-nodepool1-78167327-0   Ready    agent   8m40s   v1.12.6
aks-nodepool1-78167327-1   Ready    agent   10m     v1.12.6
aks-nodepool1-78167327-2   Ready    agent   10m     v1.12.6
```

# AKS上にSTUN & TURN Server起動
WebRTCでNAT越えをする際には、ブラウザやKurentoのコンテナが "インターネット側から見て" どのようなPublic IPとPortで到達できるのかを知る必要があります。その際に用いられるのが、STUN Serverです。ブラウザやKurentoが用いるLAN内のPrivate IPとPortが、最終的にどのようにNATされインターネット側から見えるのかをSTUN Serverが観測できれば、その観測されたPublic IPとPortを用いてブラウザとKurentoがP2Pで接続できます。
一方、NATやFirewallの構成次第ですが、WebRTCで必要となるPublic IPやPortの情報をSTUN Serverが観測できないことがあります。このような場合でも、ブラウザやKurentoがPulbic IPを持つTURN Serverに接続できるなら、TURN ServerにWebRTCのパケットを中継してもらうことで、映像を流すことは可能になります。

今回はこのSTUN & TURN Serverとして、[coturn](https://github.com/coturn/coturn)というOSSを利用します。
![fig5.png](https://qiita-image-store.s3.amazonaws.com/0/37648/98cff20c-c78b-559a-b7df-2e0de9971b7c.png)
> K8Sの制限により、今回構築するTURN ServerはUDPのみListenします。そのため、80/tcpや443/tcpしか通さないような制約が厳しいネットワークでは、TURN Serverにさえ接続できません。仕様上、TURN ServerはTCPでもTLSでも動作しますので、TCPとTLSをListenするTURN Serverを別途起動すれば、この問題を解決できます。

## coturnのLoadBalancer Serviceを起動
coturnはその仕様上、coturn自身がNATされた状況で動作する場合、どのPublic IPからNATされてくるのかを知っておく必要があります。そこで、coturnのPodへルーティングするLoadBalancer Serviceを最初に起動します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f coturn/coturn-service.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get services -l app=coturn
NAME     TYPE           CLUSTER-IP   EXTERNAL-IP      PORT(S)                                                                                                                                                                        AGE
coturn   LoadBalancer   10.2.0.139   xx.xx.xx.xx      80:32704/UDP,49150:30983/UDP,49151:32439/UDP,49152:32325/UDP,49153:32277/UDP,49154:32220/UDP,49155:31336/UDP,49156:31185/UDP,49157:30756/UDP,49158:31146/UDP,49159:31975/UDP   1m
```
## coturnのPublic IPを確認
```bash:TERMINAL1
kurento-robot-camera-k8s$ COTURN_PUBLIC_IP=$(kubectl get services \
  -l app=coturn \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}'); \
echo ${COTURN_PUBLIC_IP}
```
## coturnの設定ファイルを生成
テンプレートをもとに、NAT元のPublic IPやTURN Serverのcredentialを変更してcoturnの設定ファイル `turnserver.conf` を生成します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ cp coturn/turnserver.conf.template coturn/turnserver.conf
kurento-robot-camera-k8s$ sed -i '' -e "s/^external-ip=.*$/external-ip=${COTURN_PUBLIC_IP}/" \
  coturn/turnserver.conf
kurento-robot-camera-k8s$ sed -i '' -e "s/^user=.*$/user=${COTURN_CREDENTIAL}/" \
  coturn/turnserver.conf
kurento-robot-camera-k8s$ sed -i '' -e "s/^realm=.*$/realm=coturn.${DOMAIN}/" \
  coturn/turnserver.conf
```

## coturnの設定ファイルをsecretに登録
coturnのPodから参照できるように、生成した設定ファイルを `turnserver-conf` という名前のSecretとしてAKSに登録します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl create secret generic turnserver-conf \
  --from-file=coturn/turnserver.conf
```
## coturnのPodを起動
最後に、coturnのPodを起動します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f coturn/coturn-deployment.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get pods -l app=coturn
NAME                      READY   STATUS    RESTARTS   AGE
coturn-7ff4c894cb-dr5bh   1/1     Running   0          108s
```

# AKS上にKurento起動
Kurentoの公式Docker imageを用いて、AKS上にKurentoを起動します。Serviceを経由してNATを増やす意味は無いので、WebRTCのシグナリング時にはPODの生Private IPをそのまま使うべきです。そのため、Headless ServiceとStatefulSetを用いて、Signaling&APPコンテナからPodに直接到達できるようにします。
![fig6.png](https://qiita-image-store.s3.amazonaws.com/0/37648/ac2dabc5-4588-f52d-21f1-21f26add4abd.png)
## KurentoのHeadless Serviceを起動
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f kurento/kurento-service.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get services -l app=kurento
NAME      TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
kurento   ClusterIP   None         <none>        8888/TCP   27s
```

## KurentoのWebRTC用設定ファイルを生成
テンプレートをもとに、STUN ServerとTURN Serverの設定を変更してKurentoのWebRTC用設定ファイル `WebRtcEndpoint.conf.ini` を生成します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ cp kurento/WebRtcEndpoint.conf.ini.template kurento/WebRtcEndpoint.conf.ini
kurento-robot-camera-k8s$ sed -i '' -e "s/^stunServerAddress=.*$/stunServerAddress=${COTURN_PUBLIC_IP}/" \
  kurento/WebRtcEndpoint.conf.ini
kurento-robot-camera-k8s$ sed -i '' -e "s/^turnURL=.*$/turnURL=${COTURN_CREDENTIAL}@${COTURN_PUBLIC_IP}:80/" \
  kurento/WebRtcEndpoint.conf.ini
```

## KurentoのWebRTC用設定ファイルをsecretに登録
KurentoのPodから参照できるように、生成した設定ファイルを `kurento-webrtcendpoint-conf` という名前のSecretとしてAKSに登録します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl create secret generic kurento-webrtcendpoint-conf \
  --from-file=kurento/WebRtcEndpoint.conf.ini
```

## KurentoのPodを起動
最後に、KurentoのPodを起動します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f kurento/kurento-statefulset.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get pods -l app=kurento -o wide
NAME        READY   STATUS    RESTARTS   AGE    IP             NODE                       NOMINATED NODE
kurento-0   1/1     Running   0          110s   10.168.0.132   aks-nodepool1-78167327-1   <none>
```
Kurento PodのPrivate IPが、生成したAzure Virtual NetworkのSubnet内で割り振られていることが確認できます。

# AKS上にAPI Gatewayと認証認可サービス構築
ブラウザがロボットのカメラ映像を視聴する際、最初にKurentoとWebRTCで接続するためのJavascriptを持ったWebページを表示します。このHTTP GET Requestの処理はSignaling&APPコンテナが担当しますが、インターネットからアクセスされる以上、TLSによる経路の暗号化とユーザー認証は必須となります。
このような横断的な機能を各Signaling&APPコンテナに作り込むのは無駄ですので、TLSの終端と認証認可を行うAPI Gatewayをフロントに立て、各Signaling&APPコンテナから責務を切り離すことにします。
![fig7.png](https://qiita-image-store.s3.amazonaws.com/0/37648/20e9bf29-1022-ffcf-cce9-24b7b6cb27ec.png)


## DNSの設定
まずはSignaling&APPコンテナをFQDNで名前解決できるように、自分のドメインのname serverを[Azure DNS](https://azure.microsoft.com/ja-jp/services/dns/)に構築します。
### DNS用リソースグループ作成
```bash:TERMINAL1
kurento-robot-camera-k8s$ az group create --name ${DNS_ZONE_RG} --location ${REGION}
```
### DNS Zone作成
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network dns zone create \
  --resource-group ${DNS_ZONE_RG} \
  --name "${DOMAIN}"
```
### name server確認
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network dns zone show \
  --resource-group ${DNS_ZONE_RG} \
  --name "${DOMAIN}" \
  --query nameServers
```
このコマンドを実行すると、次のようなname serverのリストが帰ってきます。これらをドメインレジストラに登録してください。

```json:コマンド実行結果
[
  "ns1-XX.azure-dns.com.",
  "ns2-XX.azure-dns.net.",
  "ns3-XX.azure-dns.org.",
  "ns4-XX.azure-dns.info."
]
```

## TLS証明書の取得
自分のドメインで名前解決ができるようになれば、[Let's Encrypt](https://letsencrypt.org/)からTLS証明書を取得できます。後々複数のサブドメインで異なるアプリを立ち上げますので、DNSチャレンジを用いてワイルドカード証明書を取得します。

### certbotコンテナを用いてDNSチャレンジを生成
最初に設定したDOMAINやEMAIL等を変数展開しつつ、DNSチャレンジを行うdockerコマンドを生成します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ echo "docker run -it \
-v $(pwd)/secrets:/etc/letsencrypt certbot/certbot certonly \
--manual \
--domain *.${DOMAIN} \
--email ${EMAIL} \
--no-eff-email \
--agree-tos \
--manual-public-ip-logging-ok \
--preferred-challenges dns-01 \
--server https://acme-v02.api.letsencrypt.org/directory"
```
別のターミナルを開き、生成したdockerコマンドを実行すると、自分のドメインのTXTレコードに設定すべき値を表示して入力待ちになります。例えば次のような感じです。

```bash:TERMINAL2
$ docker run -it -v /tmp/kurento-robot-camera-k8s/secrets:/etc/letsencrypt certbot/certbot certonly --manual --domain ........
....
-------------------------------------------------------------------------------
Please deploy a DNS TXT record under the name
_acme-challenge.cloudconductor.jp with the following value:

ABCDEFGHIJKLMNO-PQRST-UVWXYZabcdefghijklmno

Before continuing, verify the record is deployed.
-------------------------------------------------------------------------------
Press Enter to Continue
```
### TXTレコード登録
TERMINAL1で、表示された名前と値を持つTXTレコードをDNS Zoneに作成します。 `DNS_TXT` の値は、実際に表示された値に書き換えてください。

```bash:TERMINAL1
kurento-robot-camera-k8s$ export DNS_TXT="ABCDEFGHIJKLMNO-PQRST-UVWXYZabcdefghijklmno"
kurento-robot-camera-k8s$ az network dns record-set txt add-record \
  --resource-group ${DNS_ZONE_RG} \
  --zone-name "${DOMAIN}" \
  --record-set-name "_acme-challenge" \
  --value "${DNS_TXT}"
```
次のコマンドを実行し、TXTレコードが生成されていることを確認しましょう。

```bash:TERMINAL1
kurento-robot-camera-k8s$ az network dns record-set txt list \
  --resource-group ${DNS_ZONE_RG} \
  --zone-name "${DOMAIN}"
```

### TLS証明書の取得
TXTレコードが登録されていれば、TERMINAL2でエンターキーを押してTLS証明書を取得します。TERMINAL２で次のように表示されれば成功です。

```bash:TERMINAL2
.....
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
.....
```

実際のTLS証明書は、 `secrets` ディレクトリの下に格納されています。 `privkey.pem` と `fullchain.pem` が生成されていることを確認してください。

```bash:TERMINAL1
kurento-robot-camera-k8s$ ls -al secrets/live/${DOMAIN}
```

### 不要なTXTレコードの削除
TLS証明書が取得できれば、先程登録したTXTレコードは削除してしまいましょう。

```bash:TERMINAL1
kurento-robot-camera-k8s$ az network dns record-set txt remove-record \
  --resource-group ${DNS_ZONE_RG} \
  --zone-name "${DOMAIN}" \
  --record-set-name "_acme-challenge" \
  --value "${DNS_TXT}"
```

## API Gateway ServiceとPodを起動
今回は、OSSのK8S API Gatewayである[ambassador](https://www.getambassador.io/)を用いて、TLS終端とsubdomainによるサービスルーティングを行います。

### ambassadorのLoadBalancer Serviceを起動
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f ambassador/ambassador-service.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get services -l app=ambassador
NAME         TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
ambassador   LoadBalancer   10.2.0.19    xx.xx.xx.xx   443:30517/TCP,80:31893/TCP   2m50s
```

### TLS証明書をsecretに登録
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl create secret tls ambassador-certs \
  --cert=$(pwd)/secrets/live/${DOMAIN}/fullchain.pem \
  --key=$(pwd)/secrets/live/${DOMAIN}/privkey.pem
```

### ambassadorのPodを起動
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f ambassador/ambassador-deployment.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get pods -l app=ambassador
NAME                         READY   STATUS    RESTARTS   AGE
ambassador-5c79db557-289pd   1/1     Running   0          2m29s
```

## 認証認可サービスの起動
ambassadorは、別のサービスと連携することでHTTPリクエストの認証を行うことができます。今回は、シンプルなBasic認証を行う自作のサービスと連携させます。

### 認証認可サービスのCluster IP Serviceを起動
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f auth/auth-service.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get services -l app=auth
NAME   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
auth   ClusterIP   10.2.0.237   <none>        3000/TCP   33s
```

### 認証認可サービスの設定ファイルを生成
環境変数として定義したBasic認証のCredentialを元に、設定ファイルを生成します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ cat secrets/auth-tokens.json.template | \
  envsubst > secrets/auth-tokens.json
```

## 認証認可設定ファイルをsecretに登録
認証認可サービスのPodから参照できるように、生成した設定ファイルを `auth-tokens` という名前のSecretとしてAKSに登録します。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl create secret generic auth-tokens \
  --from-file=secrets/auth-tokens.json
```

## 認証認可サービスのPodを起動
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f auth/auth-deployment.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get pods -l app=auth
NAME                    READY   STATUS    RESTARTS   AGE
auth-67ff6bd94b-xt5hf   1/1     Running   0          44s
```

# WebRTC検証用のSignaling&APPコンテナ（loopback）を起動
実際にロボットに接続する前に、WebRTCが正しく動作するかを確認します。Azure AKS上のcoturnやKurentoを利用して、PCのカメラ映像を自分自身へループバックするアプリを作りましたので、それを動作させてみましょう。
![fig8.png](https://qiita-image-store.s3.amazonaws.com/0/37648/48205716-5cab-affd-c54a-d8d6dcf7f37a.png)

## loopbackコンテナのCluster IP Serviceを起動
looopbackコンテナのServiceには、次のようなambassadorのMapping定義が記述されています。

```yaml:loopback-service.yaml
kind: Service
metadata:
  name: loopback
  labels:
    app: loopback
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  loopback-mapping
      prefix: /
      host: "^loopback\\..+$"
      host_regex: true
      service: http://loopback:3000
      use_websocket: true
spec:
  type: ClusterIP
  selector:
    app: loopback
  ports:
  - port: 3000
    targetPort: loopback
    name: loopback
```
ambassadorはこのannotationを検知して、 `loopback.${DOMAIN}` というsubdomainの場合はこのloopback ServiceにHTTP Requestを転送してくれます。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f loopback/loopback-service.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get services -l app=loopback
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
loopback   ClusterIP   10.2.0.125   <none>        3000/TCP   20s
```

## loopbackコンテナの設定情報をSecretに登録
このloopbackコンテナは、起動前に接続するSTUN ServerやTURN Serverの情報を `loopback-secrets` という名前のSecretに設定しておく必要があります。詳細は[ソースコード](https://github.com/nmatsui/kurento-robot-camera-loopback)を確認してください。

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl create secret generic loopback-secrets \
  --from-literal=STUN_LIST=${COTURN_PUBLIC_IP}:80 \
  --from-literal=TURN_LIST=${COTURN_CREDENTIAL}@${COTURN_PUBLIC_IP}:80
```

## loopbackコンテナのPodを起動する

```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl apply -f loopback/loopback-deployment.yaml
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ kubectl get pods -l app=loopback
NAME                        READY   STATUS    RESTARTS   AGE
loopback-5f5798bbfd-xv2b9   1/1     Running   0          2m8s
```

## DNSにloopbackアプリ用のAレコードを登録する

```bash:TERMINAL1
kurento-robot-camera-k8s$ AMBASSADOR_PUBLIC_IP=$(kubectl get services \
  -l app=ambassador \
  -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}'); \
echo ${AMBASSADOR_PUBLIC_IP}
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ az network dns record-set a add-record \
  --resource-group ${DNS_ZONE_RG} \
  --zone-name "${DOMAIN}" \
  --record-set-name "loopback" \
  --ipv4-address "${AMBASSADOR_PUBLIC_IP}"
```
```bash:TERMINAL1
kurento-robot-camera-k8s$ nslookup loopback.${DOMAIN}
```

## WebRTCの動作確認
では最後に、このloopbackコンテナを使ってWebRTCの動作確認を行います。

### loopback Web画面を表示
ChromeやFirefox等のモダンブラウザを立ち上げ、 `https://loopback.${DOMAIN}` にアクセスしてください。API Gatewayと認証認可サービス、及びloopbackコンテナが正しく起動していれば、Basic認証の画面が表示されます。 
![fig9.png](https://qiita-image-store.s3.amazonaws.com/0/37648/653570a2-4700-9871-eb71-a6985a2dea73.png)
`secrets/auth-tokens.json` に定義されている `username` と `password` を使ってログインしてください。loopbackコンテナのWeb画面が表示されます。
<img width="1392" alt="fig10.png" src="https://qiita-image-store.s3.amazonaws.com/0/37648/e7c20bc8-346a-8679-ea8c-835bc6eb4ba3.png">

### シグナリング
`Start` ボタンをクリックすると、loopbackコンテナを中心に、WebSocketを用いてブラウザとKurentoがSDPを交換します（この際に、coturnを使ってNAT越えのための情報を収集します）。
> WebSocketで交換しているSDPの内容等は `Console` に出力されますので、興味があれば確認してください。

![fig11.png](https://qiita-image-store.s3.amazonaws.com/0/37648/27ea81de-738d-2c24-6cc9-dbbdb1cb5428.png)

### WebRTCによるブラウザ - Kurento間の経路確立
STUN & TURN Serverの情報をもとに最適な経路が確定すると、自動的にWebRTCによってSRTPによるMedia Streamが開設されます。
![fig12.png](https://qiita-image-store.s3.amazonaws.com/0/37648/519d395a-d2ab-7318-6ad9-328183df8ebb.png)
> この図は、TURNサーバを経由せずにブラウザとKurentoが直結した場合です。

Web画面の左側の `Local Stream` はカメラ映像そのもので、右側に表示される `Remote Stream` はAzure AKS上のKurentoを経由して表示されています。

### 接続経路の確認
ブラウザの機能を用いて、ブラウザとKurentoが最終的にどのように接続されたのかを確認することができます。TURNサーバ経由で接続されてしまった場合は、TURNサーバのCPUを消費しますので、注意が必要です。

|値|意味|
|:--|:--|
|host|ローカルネットワークのIPとPortで接続|
|prflx|UDPホールパンチング中に発見された接続候補のIPとPortを元に接続|
|srlfx|STUN Serverを利用して入手したIPとPortを元に接続|
|**relay**|**TURN Serverを中継して接続**|


#### Chromeの場合
`chrome://webrtc-internals/` を開くと、結局どのような経路で接続されているのかがわかります。様々な経路候補の中から、最終的に選択された経路が **太字** の **Conn-xxxx-x-x** という項目でまとめられています。
そのうち、`googLocalCandidateType` がブラウザ側から見た接続経路、 `googRemoteCandidateType` がKurento側から見た接続経路になります。
#### Firefoxの場合
`about:webrtc` を開くと、経路候補が表になって表示されます。最終的に選択された経路は、テーブル右端の選択がtrueとなっている経路です。 `ローカル通信情報` がブラウザ側から見た接続経路、 `リモート通信情報` がKurento側から見た接続経路になります。
<img width="1392" alt="fig13.png" src="https://qiita-image-store.s3.amazonaws.com/0/37648/fa6673a7-687a-206e-ca50-f1a95d1b0c70.png">
この記事を書いたネットワーク環境では、UDPホールパンチングで発見した経路を使い、ブラウザとKurentoが直結したようです。

# 次回は
ここまででクラウド側は構築できました。次回はロボット側を構築し、Raspberry PiやROSロボットのカメラ映像をインターネット越しに視聴したいと思います。