プライベートなAKSにAzure Relay経由で安価でセキュアに接続する

# はじめに

皆さま日頃どのようにプライベートなAKSに接続されておりますでしょうか？  
わざわざAKSのためだけに、ExpressRouteやVPNを引っ張っている方は少ないかと思います。  
そこで、ここではクラウド上のプライベート環境に安価にリモートから接続する方法を紹介します。

## プライベートなAKSへの接続方法紹介

Azureにて、ローカルからプライベートなAKSに接続する方法は以下の3つが挙げられます。

1. Azure Bastion(Standard)を利用し、ネイティブ クライアント機能を用いて接続する。
2. Azure ArcにAKSクラスタを追加し、`az connectedk8s proxy`で接続する。
3. OSSのAzure Relay Bridgeにより、Azure Relay経由で接続する (本記事)。

項1が一般的かと思いますが、月額費用がそれなりにかかり、仮にずっとBastionをデプロイしていた場合は約423ドルかかってしまいます。
次に項2ですが、マルチクラウドソリューションであるAzure ArcにオンプレにあるKubernetesへ接続する機能があり、これを利用するというものになります。
こちらは(恐らく)費用は掛かりませんが、Azure上に2重にクラスタが登録され不具合が出る可能性があり、またArc関連のコンテナがデプロイされリソースを圧迫するというデメリットがあります。
最後に、今回紹介する項3ですが、コンテナを自分でビルド・デプロイする必要がありますが、比較的シンプルに利用することが可能です。認証についてもユーザ毎にRBACで設定することが可能です。
また値段も比較的安く、1接続あたり1月あたり最大で$9.78で、特に接続しない場合はこれより安くなります。

## 作成する環境

今回構築する環境概要図は以下の通りとなります。
VNetやSubnet等について省略しています。また通信については後述します。

![環境概要図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695104/d50ecb6e-c2ca-d933-20ab-d996abd60beb.png)

## 設定手順

次にAzure CLIを用いて本環境を構築していく手順を紹介します。  
以下のコマンドは、Windows10上のPowerShellで実行する想定です。

### AKS, ACRの作成

以下のコマンドを実行し、ACRとAKSについて作成します。  

``` powershell
# リソースグループの作成
az group create -n myResourceGroup -l japaneast

# ACRの作成
$MYACR="mycontainerregistry"
az acr create -n $MYACR -g myResourceGroup --sku basic

# プライベートAKSの作成
az aks create -g myResourceGroup -n myAKSCluster `
  --enable-managed-identity --node-count 1  --generate-ssh-keys `
  --load-balancer-sku standard --enable-private-cluster `
  --attach-acr $MYACR

# kubectlをローカルにインストール (※任意)
az aks install-cli
```

### Azure Relay Serviceの作成と設定

次にAzure Relay Serviceを作成し、権限を設定します。  

``` powershell
# Azure Relay Serviceの作成
az relay namespace create -g myResourceGroup --name my-relay-ns

# ハイブリッド接続の作成
az relay hyco create -g myResourceGroup --namespace-name my-relay-ns --name aks

# 現在のユーザにアクセス権を設定
az role assignment create `
    --assignee $(az ad signed-in-user show --query "id" --output tsv) `
    --role "Azure Relay Owner" `
    --scope $(az relay namespace show -g myResourceGroup --name my-relay-ns --query "id" --output tsv)
```

### Azure Relay Bridgeのコンテナ作成

次にACR Taskを利用してAzure Relay Bridgeのコンテナイメージをビルドします。

``` powershell
# GitHubのAzure Relay BridgeをClone
git clone https://github.com/Azure/azure-relay-bridge

# ソースコードをACRにアップし、ACR上でビルド
$MYACR="mycontainerregistry"
cd azure-relay-bridge
az acr build --registry $MYACR --image relay-bridge:v1 .
```

### AKSへAzure Relay Bridgeコンテナのデプロイ

次にこのビルドしたイメージをAKSへデプロイします。
またAzure Relay Serviceの名称(`my-relay-ns`)について、`AzureRelayConnectionString`の当該箇所を変更してください。

``` powershell
# Azure Relay Bridgeの設定ファイル用ConfigMap
echo @"
apiVersion: v1
kind: ConfigMap
metadata:
  name: config.yaml
data:
  relay_bridge_config.yaml: |
    ---
    GatewayPorts : no
    AzureRelayConnectionString: $(az relay namespace authorization-rule keys list -g myResourceGroup --namespace-name my-relay-ns --name RootManageSharedAccessKey --query "primaryConnectionString" --output tsv)
    RemoteForward:
      - RelayName: aks
        PortName: kubernetes
        Host: kubernetes.default.svc
        HostPort: 443
"@ > config.yaml

# Azure Relay Bridgeのデプロイメント
echo @"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: relay-bridge-deployment
  labels:
    app: relay-bridge
spec:
  replicas: 1
  selector:
    matchLabels:
      app: relay-bridge
  template:
    metadata:
      labels:
        app: relay-bridge
    spec:
      containers:
      - name: relay-bridgee-pod
        image: $MYACR.azurecr.io/relay-bridge:v1
        command: ["/app/azbridge"]
        args:
          - "-f"
          - "/app/config/relay_bridge_config.yaml"
        imagePullPolicy: Always
        volumeMounts:
          - name: config-volume
            mountPath: /app/config
      volumes:
        - name: config-volume
          configMap:
            name: config.yaml
"@ > deployment.yaml

# `command invoke`を利用してAKSにマニフェストを適用する。
az aks command invoke -g myResourceGroup -n myAKSCluster --command "kubectl create ns relay-bridge"
az aks command invoke -g myResourceGroup -n myAKSCluster --command "kubectl apply -n relay-bridge -f config.yaml" --file config.yaml
az aks command invoke -g myResourceGroup -n myAKSCluster --command "kubectl apply -n relay-bridge -f deployment.yaml" --file deployment.yaml
```

### Kubernetes Configファイルの作成と修正

以下のコマンドを実行し、Kubernetes Configファイルを生成します。

``` powershell
# Kubernetes Configファイルを作成する
az aks get-credentials -g myResourceGroup -n myAKSCluster
```

作成された`C:\Users\<ユーザ名>\.kube\config`を開き、該当する`cluster`について以下の通り修正してください。  

- `certificate-authority-data`フィールドを削除
- `insecure-skip-tls-verify`を`true`に設定
- `server`のアドレスをAzure Relay Bridge実行時に指定するアドレス、ポートを設定

**変更前**

``` yaml:config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data:  <certificate>
    server: <apiサーバーへのアドレス>
  name: myAKSCluster
contexts:
(~~~省略~~~)
```

**変更後**

``` yaml:config
apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://127.3.2.1:443
  name: myAKSCluster
contexts:
(~~~省略~~~)
```

### Azure Relay Bridgeの起動とAKSへの接続

以下の手順でAzure Relay Bridgeをローカルにインストールし、AKSへ接続します。

- 該当する環境のAzure Relay Bridgeプログラムを[GitHub](https://github.com/Azure/azure-relay-bridge/releases)よりダウンロード
- ダウンロードしたアーカイブを任意のフォルダに展開してください。
- 以下の通りAzure Relay Bridgeを実行し、ポート転送を開始します。  
  IPアドレス(`127.3.2.1`)、ポートは、Kubenetes Configの`server`にて設定した値を指定してください。  
- Azure Relay Serviceの名称(`my-relay-ns`)について、作成した名称に合わせて修正してください。

``` powershell
./azbridge.exe -L "127.3.2.1:443/kubernetes:aks" -e sb://my-relay-ns.servicebus.windows.net
```

- 新しくコンソールを開き`kubectl`コマンドを実行してください。Pod一覧等が取得できると思います。
  取得できない場合は、`azbridge`コマンド実行時に`-v`オプションを付けログ表示を有効にしエラー内容を確認してください。  
  またアクセスユーザにAKSへアクセスロールが付与されているか確認してください。

## 接続・設定内容の解説

上記の設定も含めた構成図は以下の通りとなります。  
それぞれ対応する設定に矢印で示しています。  

![構成図(設定込)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695104/9dc388ba-cdce-709b-a0d8-ce8627d92258.png)

Azure Relay Bridgeで指定するRelayNameは、ハイブリッド接続の名称を表し、リモート、ローカル側でそれぞれ設定します。  
こちらは必ずリモートとローカルが1対1となるように設定してください。
次にPortNameは、どこにどう通信を転送するかを紐づけるための設定名称となり、リモートとローカル側で同じ値を設定します。
このように設定することで、ローカル側でBindAdress:Portにアクセスすると、リモートのホストへ通信が転送されるようになります。  

また説明を読んで頂ければ分かるかと思いますが、こちらの方法はAKSだけでなく、VMや他のプライベート環境への接続、IoT機器との連携といった様々な用途で活用可能です。  
実際にAzure Architecture Centerでも以下の通り紹介がされております。

- [Azure Relay を使用した安全なトンネリング - Azure Architecture Center](https://learn.microsoft.com/ja-jp/azure/architecture/example-scenario/networking/secure-tunneling-azure-relay)

もし複数のホストに転送したい場合は以下の通り設定することが可能です。  
ここでは環境概要図のオプションで記載した開発用コンテナ(`dev-container-svc`)に、`ssh`というポート名称で接続する想定で設定しています。  

- Azure Relay Bridgeコンテナの設定(該当箇所抜粋)

``` yaml
RemoteForward:
  - RelayName: aks
    Bindings:
    - PortName: kubernetes
      Host: kubernetes.default.svc
      HostPort: 443
    - PortName: ssh
      Host: dev-container-svc.default.svc
      HostPort: 2222
```

- 起動オプションの設定

``` powershell
./azbridge.exe -L "127.3.2.1:443/kubernetes;127.3.2.1:2222/ssh:aks" -e sb://my-relay-ns.servicebus.windows.net
```

## 注意事項

### 同時接続や複数ユーザからの接続について

複数のクライアントから同時に接続する場合は、その接続数分だけ、ハイブリッド接続を作成し、設定を追加してください。
複数クライアントから同じハイブリッド接続に接続した場合、一見動作しているように見えますが、上手くデータが届いていなかったりしますので注意してください。

### 接続設定ファイルの反映について

Kubernetesのコンフィグにより設定ファイルをマウントした場合、自動で設定が更新されないので、コンテナを再起動等する必要があります。
これを自動化する場合は、OSSの`reloader`等を利用することで、自動でコンテナを再起動するよう設定することができます。

### セキュリティについて

紹介した手順は、プライベートなAKSへ接続するための最小限の手順となっており、セキュリティはあまり考慮していません。  
それぞれの環境に合わせて接続先を制限する、ACR、Azure Relayについてもプライベートエンドポイントを作成する等検討ください。

## 終わりに

いかがでしたでしょうか。Azure Relayという少しマイナーなサービスを使って接続いたしました。  
多少設定等が複雑ですが、SSHのポートフォワーディング等に慣れていれば少しイメージしやすいかと思います。  
また実用的なの？と疑問に持たれる方もいらっしゃるかと思いますが、この環境で約半年ほど利用していますが多少ラグ等あるものの普通に利用できていますので一度お試しください。

## 参考サイト

- [クイック スタート: Azure CLI を使用して Azure Kubernetes Service (AKS) クラスターをデプロイする - Azure Kubernetes Service](https://learn.microsoft.com/ja-jp/azure/aks/learn/quick-kubernetes-deploy-cli)
- [Azure Container Registry と Azure Kubernetes Service (AKS) を統合する - Azure Kubernetes Service](https://learn.microsoft.com/ja-jp/azure/aks/cluster-container-registry-integration)
- [GitHub - Azure/azure-relay-bridge: Azure Relay Bridge - A cross-platform command line tool to create VPN-less TCP tunnels from and to anywhere](https://github.com/Azure/azure-relay-bridge)