セキュアな Azure AI Studio (AI Foundry) を Terraform テンプレートから立てる ~Azure Sandbox を使用して~

### Introduction
Azure においてセキュアな開発環境を Portal からテンプレート無しで展開するには冪等性が欠けたり、開発者へのインフラストラクチャを構成する負担が出てきます。
また Azure Landing Zone において Sandbox Subscription 配下に開発環境を払い出すことで今後の本番環境への展開も迅速に行うことが可能です。
でも 0 から 1 でテンプレートを作成することはハードルが高くなります。
そういった需要を満たすために Microsoft では GitHub でリポジトリが提供されています。
このリポジトリには、単一のサブスクリプションで一般的な Azure サービスを実装するための相互依存するクラウドコンピューティング構成のコレクションが含まれています。これらの構成を総合的に使用することで、さまざまなAzureサービスや機能を試すのに役立つ柔軟でコスト効果の高いサンドボックス環境を提供します。

https://github.com/Azure-Samples/azuresandbox/blob/main/README.md 

今回はこの中の AI 開発において使用可能な AI Studio Sandbox 環境を展開していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/66e39493-0a81-17d4-f7ed-6d8151e018f0.png)

::: note info
AI Studio は AI Foundry に名前が変更されましたが、本ブログでは Sandbox レポジトリの表現に従って AI Foundry を AI Studio と以前の名称で表します。
:::

少し長いブログにはなりますがお付き合いください。
**色々と躓くポイントが多い**ので、レポジトリの README.md を確認しながら本ブログの"躓きポイント"を確認してデプロイすることをお勧めします。
では以下の項目に従って AI Studio のデプロイまでしていきます。
* 事前準備
* VNet Shared テンプレートの展開
* VNet App テンプレートの展開
* AI Studio テンプレートの展開

## 事前準備
前提知識として AI Studio テンプレートの展開には VNet Shared → VNet App のテンプレートを順番にデプロイしていく必要があります。
展開の順序のイメージは以下のような形になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/d8bbdc0d-b661-4552-abf7-d46bae9dcc57.png)

まず必要な道具は以下です:
* Microsoft Entra ID テナント
* Azure サブスクリプション
* サブスクリプションに対する Owner ロール
* Service Principle: Azure CLI コマンドを使用するために必要 (後で作ります)
* デプロイツール

デプロイツールに関して、Azure Sandbox の自動スクリプトは Linux Bash と Linux PowerShell で書かれているため、実行するには Linux 環境が必要となります。
Windows ユーザーは WSL を使用して実行する必要があります。

https://learn.microsoft.com/windows/wsl/about

Windows ユーザーは以下のステップに従ってスクリプトの実行環境を整えていきます。
* WSL をインストールします(Ubuntu 24.05 LTS が推奨です)
https://learn.microsoft.com/windows/wsl/install
* Python パッケージのインストールをします
    * https://pip.pypa.io/en/stable/ の Python ライブラリ パッケージ マネージャーと https://pyjwt.readthedocs.io/en/latest/ の Python ライブラリをインストールします。
    * これは、現在サインインしている Azure CLI ユーザーのセキュリティ プリンシパルの ID を確認するために使用されます。
    * 以下のコマンドでインストールをします。
    
```bash
sudo apt update
sudo apt install python3-pip
pip3 show pyjwt
```
また Azure CLI, Terraform、PowerShell on Ubuntu をインストールします。

https://learn.microsoft.com/cli/azure/install-azure-cli-linux?pivots=apt

https://learn.hashicorp.com/tutorials/terraform/install-cli#install-terraform

https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.3

PowerShell をインストールしたらコンフィグするために以下のコマンドを実行します。

```bash
wget https://raw.githubusercontent.com/Azure-Samples/azuresandbox/main/configure-powershell.ps1
chmod 755 configure-powershell.ps1
sudo ./configure-powershell.ps1
```

最後に VS Code の設定をしていきます。
* VS Code を起動
* WSL VS Code Extension をインストール
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl
* HashiCorp Terraform VS Code Extension in WSL をインストール
https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform
* PowerShell VS Code extension in WSL をインストール
https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell

これで事前準備は完了です。


## VNet Shared テンプレートの展開
terraform-azurerm-vnet-shared が含むリソースは以下です:
* resource group : 全ての Sandbox リソースを含む
* key vault : シークレットの管理
* log analytics workspace : ログとメトリックのため
* storage account : BLOB Storage
* automation account : Configuration Management のため
* virtual network : virtual machines のホストの為
* bastion : VM への RDP と SSH アクセスの為
* Windows Server VM: 事前定義済みのドメインとDNS の為の Active Directory Domain Services 

https://github.com/Azure-Samples/azuresandbox/blob/main/terraform-azurerm-vnet-shared/README.md

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/3f52ebb9-5b38-2e8a-e74d-f49fbac17f40.png)

まず 1st ステップとして Git clone をしていきます。
VS Code から WSL に接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/b86a5730-bb28-7c05-b89c-0b334a78c867.png)

Terminal で git clone をします。

```bash
git clone https://github.com/Azure-Samples/azuresandbox
cd azuresandbox
latestTag=$(git describe --tags $(git rev-list --tags --max-count=1))
git checkout $latestTag
```

実行する前に Resource Provider に EncryptionAtHost が登録されていることを確認してください。
でないと bootstrap.sh を実行した際に失敗します。
以下で確認ができます。

```bash
az feature show --subscription $subscription_id --namespace Microsoft.Compute --name EncryptionAtHost 
```

では az login で対象のサブスクリプションにログインします。

```bash
# Log out of Azure and clear cached credentials (skip if using cloudshell)
az logout

# Clear cached credentials (skip if using cloudshell)
az account clear

# Log into Azure (skip if using cloudshell)
az login

# Find and copy subscription id
az account list -o table

# Set default subscription
az account set -s 00000000-0000-0000-0000-000000000000
```

では次に vnet shared ディレクトリに移動します。

```bash
cd ~/azuresandbox/terraform-azurerm-vnet-shared
```

次にテナントに Service Princple を登録します。
Documentには Contributor と記載がありますが、
vnet-app のデプロイの際に Service Principle が ジャンプボックスの仮想マシンに対して BLOB Reader の権限を付与するため、**Owner 権限**を付与させます。

```bash
az ad sp create-for-rbac -n AzureSandboxSPN --role Owner --scopes /subscriptions/<subscription id>
```
後ほど使用するので、アウトプットを控えておきます。
念のため Azure Portal の EntraID/App Registrations からアプリの登録を確認します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/23ecb284-550c-fabc-cdec-205345e2f70d.png)

次に環境変数に先ほど控えた Application のパスワードを環境変数に格納します。
ダブルチェックしてください。Terraform 展開の際のプロバイダーの認証に失敗します。

```bash
export TF_VAR_arm_client_secret=YourServicePrincipalSecret
```

準備が整ったので bootstrap.sh を実行します。

bootstrap.sh スクリプトは、変数の初期化と Terraform 構成の適用に必要なすべての依存関係を確保するために使用されます。多くの実際のプロジェクトでは、既存のリソースを参照する必要があり、循環依存を避けるために事前にリソースをプロビジョニングすることが必要です。
このため、この構成では bootstrap.sh を使用していくつかのリソースを事前にプロビジョニングします。
bootstrap.sh は以下の操作を行います：
* リソースグループの作成: すべての構成で使用されるデフォルト名 rg-sandbox-01 の新しいリソースグループを作成します。
* キーコンテナの作成: ランダムに生成された名前（例: kv-xxxxxxxxxxxxxxx）のキーコンテナを作成します。
* アクセス許可モデルの設定: Vault アクセスポリシーに設定され、Azure ロールベースのアクセス制御は使用されません。(ロールアクセス制御にもできますが、コードの書き換えが必要ですので、アクセスポリシーのまま実行することを時間短縮のためにお勧めします)
* シークレットの作成: すべての構成で使用されるシークレットを作成します。これらのシークレットは静的であり、値が変更された場合は手動で更新する必要があります。
* サービスプリンシパルクライアントシークレット: サービスプリンシパルクライアントIDと同じ名前で、値はクライアントシークレットです。
* ストレージアカウントアクセスキー: ストレージアカウントと同じ名前で、値はアクセスキーです。
* ストレージアカウントケルベロスキー: ストレージアカウントと同じ名前で、値はケルベロスキーです。
* 管理者パスワード: 新しいリソースがプロビジョニングされるときに使用される強力なパスワードです。
* 管理者ユーザー: 新しいリソースが構成されるときに使用されるユーザー名で、デフォルト値は bootstrapadmin です。
* アクセスポリシーの作成: シークレットの管理と取得を可能にするアクセスポリシーを作成します。
* AzureSandboxSPN: シークレットの取得と設定の権限が付与されます。
* サンドボックスユーザー: シークレットの取得、リスト、および設定の権限が付与されます。
* 診断設定の構成: 監査カテゴリのログを Log Analytics ワークスペースに送信するように 020-loganalytics.tf で構成します。
* ストレージアカウントの作成: 
    * スクリプトコンテナの作成: Windows または Linux 用のカスタムスクリプト拡張機能を活用する構成のための新しいスクリプトコンテナを作成します。
    * パブリックネットワークアクセスと共有キーアクセスの無効化: デフォルトで無効にします。
    * ネットワーク例外 AzureServices の有効化: 信頼された Azure サービスへのアクセスを許可します。
* Terraform 計画の生成と適用のための terraform.tfvars ファイルの作成: スクリプトは冪等性があり、Terraform 構成が適用された後でも複数回実行できます。
このように、bootstrap.sh スクリプトは Terraform 構成の準備と依存関係の管理を効率的に行うための重要なツールです。

::: note warn
この後実行する bootstrap.sh ですが、デフォルトのリージョンである centralus ですと Open AI のデプロイメントに失敗しました。
そのため本ブログでは japaneast にしています。
それに伴って VM のサイズを変更する必要があります。

2 コアの B シリーズでは adds に対応していないものがあるため、D2s_v4 としました。
:::

bootstrap.sh を以下の様に書き換えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/18e48ee9-3aa2-29ab-1bb5-3f5664012828.png)

::: note info
save できない場合は、書き込みアクセスが必要ですので所有していない場合は以下のコマンドを実行してください。自分を対象のディレクトリの所有者にします。
sudo chown -R \<USER NAME\>:\<USER NAME\> .
:::

::: note warn
bootstrap.sh をこのまま実行すると後々エラーが出力されるので、Service Princple に delete の権限を与えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/7affee5f-5d6a-1b8e-a62f-5e36fa81f60a.png)
:::

bootstrap.sh を実行すると初期変数として、以下の変数を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/67dc1b0b-6d3f-eac4-6cf0-df08fed74262.png)

アウトプットとして terraform.tfvars が出力されるので確認します。

次に Azure Portal でリソースを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/7172ba15-9ce7-c3b5-10d4-c1da068ac1b8.png)

特にシークレットの情報として以下の5 つが含まれていることを確認してください。
* サービスプリンシパルクライアントシークレット
* ストレージアカウントアクセスキー
* ストレージアカウントケルベロスキー
* 管理者パスワード
* 管理者ユーザー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/8303179c-2dfa-efc2-3435-af0d21bece6a.png)

次に Terraform を実行して残りのリソースを立てていきます。

```bash
# Initialize providers
terraform init

# Check configuration for syntax errors
terraform validate

# Review plan output
terraform plan

# Apply plan
terraform apply
```

デプロイが終了したのでリソースを確認します。
ここではいくつか割愛しますが、Azure Automation Account に関して内容を紹介します。

**Azure Automation Account**:
* Terraform Provisionersを使用して仮想マシンを構成するためにAzure Automation State Configuration (DSC)を広範に利用しています。
* configure-automation.ps1:
    * azurerm_automation_account.automation_account_01リソースのプロビジョナーによって実行されるスクリプトです。
    * Azure Automationの共有リソースを構成します。
        * モジュールをインポートします（例: ActiveDirectoryDsc, DnsServerDsc, PSDscResources, xDSCDomainjoin）。
        * 変数をブートストラップします。
        * 資格情報をブートストラップします。
    * Azure Automation State Configuration (DSC)を構成し、Windows Server仮想マシンを構成します。
    * この構成で使用されるDSC構成をインポートします。
* LabDomainConfig.ps1:
    * Windows Server 仮想マシンを Active Directory Domain Services ドメインコントローラーとして構成します。
    * DSC 構成をコンパイルし、後で State Configuration で管理されるVMに登録できるようにします。
これで、Azure Automation と DSC を使って仮想マシンを効率的に管理する構成にしています。

最後に Automation Account で DSC が Compliant かどうかを次に進む前に確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/454c0ea2-f6dd-e800-bcda-a5e11cbf3eb6.png)

OK です！

## VNet App テンプレートの展開

https://github.com/Azure-Samples/azuresandbox/tree/main/terraform-azurerm-vnet-app

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/d5c03e19-ec09-c04a-af11-eafa3afdbe6a.png)

terraform-azurerm-vnet-app が含むリソースは以下です:
* virtual network : virtual machines と private endpoints
* Virtual network peering:  terraform-azurerm-vnet-shared で自動的に設定済み
* Windows Server: ジャンプボックス
* Linux virtual machine : DevOps agent として使用するため
* Azure Files : Private Endpoint を設定

この構成では、仮想マシン adds1 が実行中で利用可能である必要があります。adds1 が停止したり、プロビジョニング中に利用できなくなったりすると、失敗する可能性があります。adds1 がプロビジョニング後のパッチ適用や再起動を完了するのに十分な時間を確保するために、vnet-shared から vnet-app のプロビジョニングを試みる前に30分待つことをお勧めします。

ではディレクトリを terraform-azurerm-vnet-app に移動します。
そして Service Principle のパスワードがしっかりと格納されていることを確認します。
```bash
echo $TF_VAR_arm_client_secret
```

japaneast で使用できる VM サイズに変更するために Vnet-shared を実行した際と同じように
bootstrap.sh の VM のサイズを変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/61fa54a2-1539-fb44-c5e2-a55393b42a9d.png)

では bootstrap.sh を実行します。
ここでは jampbox の ssh キーの作成と Key vault への格納や各 jampbox に DSC のコンフィグを実行します。

```bash
./bootstrap.sh
```

出力を見て分かる通り、vnet-shared の tfstate を読み取ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/42b917bf-bac3-cb28-85a8-1f7cb7363b5a.png)

次に Terraform を実行して残りのリソースを立てていきます。

```bash
# Initialize providers
terraform init

# Check configuration for syntax errors
terraform validate

# Review plan output
terraform plan

# Apply plan
terraform apply
```

デプロイが終了したのでテストをしていきます。
内容はこちらに記載がある通りです。

https://github.com/Azure-Samples/azuresandbox/blob/main/terraform-azurerm-vnet-app/README.md#jumpbox-smoke-testing

基本的に jampbox に接続してサーバーの中身を確認します。
また SMB の接続のテストとして Azure Files プライベート エンドポイント (PaaS) への SMB 接続と統合をします。
特に以下のようにそれぞれの VM や Storage Account がドメイン 参加しているかの確認などをします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/fb54d0e7-c547-4d6e-bca1-9f635783a7f6.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/8600dd95-e27f-a40a-fb37-a89f653f7183.png)

ちなみに Auzre FIles の Z ドライブへのマウントですが、username と password は Azure Portal の Storage Account の Access keys を確認して入力します。
username = localhost\<Storage Account Name>, password=key となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/b4dbfb8e-c491-fec4-5f1e-99f3e5436313.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/baa6f1b9-72e8-7621-e594-75d581ba20e9.png)

OK です！

## AI Studio テンプレートの展開
以下のリソースを最後に立てていきます。
* ネットワーク分離が構成された Azure AI Studio ハブ。ハブは共有サービスのストレージアカウントとキーボルトに接続されています。
* ハブに接続された Application Insights ワークスペース。
* ハブに接続された Azure Container Registry。
* ハブに接続可能な追加の AI サービスには以下が含まれます：
* Azure AI Services リソース。
* Azure AI Search リソース。

https://github.com/Azure-Samples/azuresandbox/blob/main/extras/terraform-azurerm-aistudio/README.md

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/ed10c3c3-353c-867d-19cc-2b6892394b6e.png)

::: note info
bootstrap.sh を実行するユーザーは、AI Studio を対話的に使用するユーザーと同一であると想定されており、サンドボックスサブスクリプションに対してオーナーの Azure RBAC ロール割り当てを持っている必要があります。同じユーザーは、Azure CLI をサンドボックスサブスクリプションに認証済みである必要もあります。これは、AI Studio ハブが共有サービスのストレージアカウントと統合するための対話的な使用をサポートするために、Azure RBAC ロール割り当てを追加する必要があるためです
:::

ではディレクトリの移動をします。
```bash
cd ../extras/terraform-azurerm-aistudio
```
ディレクトリに対して権限がなければ再度以下を実行します
```bash
sudo chown -R \<USER NAME\>:\<USER NAME\> .
```
ここからはいつもと同じです。
bootstrap.sh では Storage Account に今後使用する Dosument の Upload をします。

```bash
./bootstrap.sh

# Initialize terraform providers
terraform init

# Validate configuration files
terraform validate

# Review plan output
terraform plan

# Apply configuration
terraform apply

```

こんな感じにリソースが展開されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/b700e2c7-2f4c-e3b6-5a05-a158bdba4d4a.png)

デプロイ後のテストとして、以下の URL に従って行ってください。

https://github.com/Azure-Samples/azuresandbox/blob/main/extras/terraform-azurerm-aistudio/README.md#smoke-testing

閉域構成としているため、Bastion でアクセスした Jampbox 内から AI Studio を使用することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/693422/82baadec-655a-7821-e943-6fe99a0032c6.png)

今回はデプロイが目的なので内容解説はしませんが、AI Studio を探索するものになっています。
例えば gpt-4o-2024-08-06 をデプロイし、mp3 ファイルからコールセンターのレスポンスを出力したりします。

### Wrap up
以上で AI 開発における事前定義済みのテンプレートの展開ができました。
正直普通に時間がかかります。。
いずれにしてもこの Sandbox を使用することで開発者に安全な AI 開発環境をリソースグループとして払い出すことが可能です。
その他にも AI Callcenter 用のテンプレートなど Azure Sandbox には備わっているので是非試してみてください。
ではでは。