Private Linkを用いたAKS + Azure Database for MySQLの構築

初めに

AKSとAzure Database for MySQL間の通信をセキュアにするために、
両者をPrivate Linkを用いて接続することで、両者間の通信をMicrosoftバックボーンネットワーク内で完結できるようにします。

また、構築後に、Djangoチュートリアルで紹介されている質問アプリをコンテナ化したものを用いて動作確認を行います。
※詳細はページ下部の参考資料リンクからどうぞ


目次

0.構成図

1.AKSとAzure Database for MySQLをAzure上に作成

2.Private Linkを用いてAKSとAzure Database for MYSQLを接続

3.FQDNを用いてDBに接続できるようにする

4.WebアプリをAKS上にデプロイ

5.ブラウザ経由でWebアプリへアクセスして質問を登録

6.MySQLクライアントを用いてDBへの変更内容を確認 

7.クリーンアップ


0.構成図

f:id:p1k42:20210623141452p:plain
構成図

1.AKSとAzure Database for MySQLをAzure上に作成

#リソースグループの作成
az group create -g test -l japaneast
 

#VNetとSubnetの作成
az network vnet create -g test -l japaneast -n test-vnet \
--address-prefixes 10.1.0.0/16 --subnet-name test-vnet-subnet \
--subnet-prefixes 10.1.0.0/24
 

#Subnetの設定を更新
#PrivateEndpointをSubnetにデプロイするために、PrivateEndpointNetworkPolicyを無効化
az network vnet subnet update -g test --vnet-name test-vnet \
-n test-vnet-subnet --disable-private-endpoint-network-policies true
 

#SubnetのResourceIDを変数に入れる
SUBNET_ID=$(az network vnet subnet show -g test --vnet-name test-vnet \
-n test-vnet-subnet --query id -otsv)
 

#AKSをVNet上に作成
#"--network-plugin azure"を指定することで、 Azure CNI networkingを有効化され、
#VNet Subnet上にAKSを作成することが可能になる
#コスト削減のため、ノード数を1、ノードVMのサイズをStandard_B2sに変更
az aks create -g test -n test-aks --network-plugin azure \
--vnet-subnet-id $SUBNET_ID --node-count 1 --node-vm-size Standard_B2s
 

#Azure Database for MySQLを作成
#"--ssl-enforcement Disabled"を指定してSSL無効化(WebアプリがSSL接続できなかったため)
#注1:"-n"で指定しているDB名は全世界で一意である必要がある
#注2:"-l"でregionを指定しないと、AKSとは異なるregionに作成されてしまい後々困る
az mysql server create -g test -l japaneast -n test-mysqlXXX \
--admin-user user --admin-password 1qA2wS3eD \
--ssl-enforcement Disabled


2.Private Linkを用いてAKSとAzure Database for MYSQLを接続

#DBのResourceIDを変数に入れる
DB_ID=$(az mysql server show -g test -n test-mysqlXXX --query id -otsv)

#今回作成したDBをサポートしているGroupIDを変数に入れる
GROUP_ID=$(az network private-link-resource list -g test --id $DB_ID \
--query [].properties.groupId -otsv)

#Private Endpointの作成
az network private-endpoint create -g test --vnet-name test-vnet \
--subnet test-vnet-subnet -n test-privateendpoint \
--private-connection-resource-id $DB_ID --group-id $GROUP_ID \
--connection-name test-connection


2.(番外編)

#ちなみに、先程作成したPriavteEndpointに対して設定されているのは、10.1.XXX.XXX(プライベートIPアドレス)
az network private-endpoint show -g test -n test-privateendpoint --query customDnsConfigs[0].ipAddresses[0]

"10.1.XXX.XXX"

#AKS上のPodから、このプライベートIPアドレスを用いてDBに接続することは可能
mysql -h 10.1.XXX.XXX -u user@test-mysqlXXX -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is ...

#けれども、DBのFQDNを調べて
az mysql server show -g test --name test-mysqlXXX --query fullyQualifiedDomainName

"test-mysqlXXX.mysql.database.azure.com"

#AKS上のPodから、FQDNを用いた接続を試みると、エラーを吐く
mysql -h test-mysqlXXX.mysql.database.azure.com -u user@test-mysqlXXX -p
Enter password:
ERROR 9000 (HY000): Client with IP address '20.44.XXX.XXX' is not allowed to connect to this MySQL server.

#試しに、AKS上のPodから、このFQDNに対して名前解決を行うと、パブリックIPアドレス(40.79...)が返ってくる
nslookup test-mysqlXXX.mysql.database.azure.com

Server:         10.0.0.10
Address:        10.0.0.10#53

Non-authoritative answer:
test-mysqlXXX.mysql.database.azure.com  canonical name = cr6.japaneast1-a.control.database.windows.net.
Name:   cr6.japaneast1-a.control.database.windows.net
Address: 40.79.XXX.XXX

現時点で、AKS側からプライベートIPアドレスを用いてDBにセキュアに接続することは可能。
しかし、FQDNで接続しようとするとプライベートIPアドレスではなく、パブリックIPアドレスを用いて接続しようとしてしまう。


3.FQDNを用いてDBに接続できるようにする

※この状態でも、プライベートIPアドレスを用いて、DBにPrivateLink経由で接続できるが、 今回はFQDNで接続したいので、FQDNとプライベートIPアドレスの関連付けを行います。

#プライベートDNSゾーンを作成
az network private-dns zone create -g test \
-n privatelink.mysql.database.azure.com

#DNSゾーンへのVnetリンクを作成
az network private-dns link vnet create -g test \
--zone-name privatelink.mysql.database.azure.com --name test-dnslink \
--virtual-network test-vnet --registration-enabled false

#DNSゾーングループを作成
az network private-endpoint dns-zone-group create -g test \
--endpoint-name test-privateendpoint --name test-zonegroup \
--private-dns-zone privatelink.mysql.database.azure.com --zone-name test-zone


4.WebアプリをAKS上にデプロイ

#AKSにkubectlで接続するための認証情報を入手
az aks get-credentials -g test -n test-aks

#Webアプリをデプロイ、環境変数で接続先DBを指定
kubectl run test-django --image dekabitasp/test-django:v2.3 \
--env DATABASE_URL=mysql://user@test-mysqlXXX:1qA2wS3eD@test-mysqlXXX.mysql.database.azure.com:3306/defaultdb \
--port 8000

#Webアプリにインターネット経由でアクセスできるように
kubectl expose po test-django --port 8000 --type LoadBalancer

#今までk8s上に作った物の確認
kubectl get all
 
NAME              READY   STATUS              RESTARTS   AGE
pod/test-django   0/1     ContainerCreating   0          26s

NAME                  TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/kubernetes    ClusterIP      10.0.0.1               443/TCP          12m
service/test-django   LoadBalancer   10.0.191.177   20.89.80.13   8000:32167/TCP   21s


5.ブラウザ経由でWebアプリへアクセスして質問を登録

#Webアプリにログインする際に必要なUserを作成
kubectl exec test-django -it -- python manage.py createsuperuser

#こんな感じで入力していく(メアドは不要)
Username (leave blank to use 'root'):
Email address:
Password:
Password (again):
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully. 

#Webアプリに接続するためのIPアドレスを確認(今回は20.89.80.13)
kubectl get svc
#又は
kubectl get svc test-django -ojsonpath={.status.loadBalancer.ingress[0].ip}

#ブラウザで"20.89.80.13:8000/admin"にアクセス


ログイン画面が出てくるので先程登録したユーザ名とパスワードを入力してログインする

f:id:p1k42:20210623153909p:plain
ログイン画面


一番下のQuestionsのaddボタンを押す

f:id:p1k42:20210622213742p:plain
ログイン後


こんな感じで適当に質問、日付時間を登録して、保存する
(画像では、日付時間を入力せずに保存しようとして怒られてます)

f:id:p1k42:20210622214035p:plain
質問登録


6.MySQLクライアントを用いてDBへの変更内容を確認

#DBのホスト名を変数に入れる
DB_HOSTNAME=$(az mysql server show -g test -n test-mysqlXXX \
--query fullyQualifiedDomainName -o tsv)

#mysqlclient用のPodをデプロイ
kubectl run mysqlclient --image mysql -- sleep infinity

#先程のPodを用いてDBに接続する
#DB作成時に指定したユーザ名を指定する
kubectl exec -it mysqlclient -- mysql -u user@test-mysqlXXX \
-h $DB_HOSTNAME -p

#DB作成時に指定したパスワードを入力してログイン
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 64240
Server version: 5.6.47.0 MySQL Community Server (GPL)

Copyright (c) 20002021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
#全DBを表示
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| defaultdb          |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.05 sec)
 
#操作対象のDBを指定
mysql> use defaultdb
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
 
#全Tableを表示
mysql> show tables;
+----------------------------+
| Tables_in_defaultdb        |
+----------------------------+
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
| polls_choice               |
| polls_question             |
+----------------------------+
12 rows in set (0.01 sec)
 
#polls_question Tableの中身を確認
#ブラウザから登録した質問がちゃんとDBに保存されてるのを確認
mysql> select * from polls_question;
+----+---------------+----------------------------+
| id | question_text | pub_date                   |
+----+---------------+----------------------------+
|  1 | sample | 2021-06-03 11:18:21.000000 |
+----+---------------+----------------------------+
1 row in set (0.00 sec)

mysql> exit
Bye


7.クリーンアップ

#現在存在しているResourceGroupを全部表示する
az group list -otable

#test ResourceGroupを削除
#--no-waitを指定すると、削除中でもターミナルが使うことが可能
az group delete -g test --no-wait -y

#VNet作成時に自動で作成されたResourceGruopも削除
az group delete -g NetworkWatcherRG --no-wait -y

#全てのResourceGroupが削除中になっているか確認
az group list -otable


参考資料

What is Azure Private Link? | Microsoft Docs

Quickstart - Create an Azure Private Endpoint using Azure CLI | Microsoft Docs

Disable network policies for private endpoints in Azure | Microsoft Docs

Azure プライベート エンドポイントの DNS 構成 | Microsoft Docs

Quickstart - Create an Azure private DNS zone using the Azure CLI | Microsoft Docs

az network private-endpoint | Microsoft Docs

Writing your first Django app, part 1 | Django documentation | Django