TerraformでAzureにGPTモデルとAI Searchを使ったアプリをデプロイしてみた
今回は前回の続きで、Terraformでアプリをデプロイする方法について詳細に説明していきます。
terraformのフォルダ階層がこちら。
terraform
├ main.tf
├ param.tfvars
├ variables.tf
└ flaskr.zip
variables.tfは変数の定義でクレデンシャルの形式やデフォルトのリージョンを指定しています。
variables.tf
variable "provider_credentials" {
type = object({
subscription_id = string
tenant_id = string
sp_client_id = string
sp_client_secret = string
})
}
variable "location" {
type = string
default = "japaneast"
}
param.tfvarsはクレデンシャルの値を入力します。
param.tfvars
provider_credentials = {
subscription_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
sp_client_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
sp_client_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
terraformのコマンドは簡単で、azureにログイン後、initしてplanしてapplyするだけです。
az login
terraform init
terraform plan -var-file=param.tfvars
terraform apply -var-file=param.tfvars
動作確認後、作業を終える際は忘れずにdestroyしましょう。
terraform destroy -var-file=param.tfvars
apply後、ユーザデータは手動でアップロードします。
ストレージアカウント > コンテナー > documents
メタデータとしてtitle : ファイル名称 を入力し保存します。
Webアプリ > 参照 でUI画面を開くことができます。
ログを見るには、ログストリームがリアルタイムで追記されるため便利です。
最後にmain.tfを掲載します。お読み頂きありがとうざいました。
main.tf
provider "azurerm" {
subscription_id = var.provider_credentials.subscription_id
tenant_id = var.provider_credentials.tenant_id
client_id = var.provider_credentials.sp_client_id
client_secret = var.provider_credentials.sp_client_secret
features {}
}
# Generate a random integer to create a globally unique name
resource "random_integer" "ri" {
min = 10000
max = 99999
}
locals {
resource_group_name = "kraftResourceGroup-${random_integer.ri.result}"
storage_account_name = "kraftstorageaccount${random_integer.ri.result}"
openai_account_name = "kraftopenaiaccount${random_integer.ri.result}"
subdomain_name = "subdomain${random_integer.ri.result}"
}
# リソースグループ
resource "azurerm_resource_group" "rg" {
name = local.resource_group_name
location = var.location
}
# ストレージアカウント
resource "azurerm_storage_account" "kraftstorage" {
name = local.storage_account_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
}
# デプロイ資材用コンテナの作成
resource "azurerm_storage_container" "kraft-container" {
name = "kraft-container"
storage_account_id = azurerm_storage_account.kraftstorage.id
container_access_type = "blob"
}
# ユーザデータ用コンテナの作成
resource "azurerm_storage_container" "search-container" {
name = "documents"
storage_account_id = azurerm_storage_account.kraftstorage.id
container_access_type = "blob"
}
# デプロイ資材のアップロード
resource "azurerm_storage_blob" "app_service_blob" {
name = "flaskr.zip"
storage_account_name = azurerm_storage_account.kraftstorage.name
storage_container_name = azurerm_storage_container.kraft-container.name
type = "Block"
source = "${path.module}/flaskr.zip"
}
# Azure OpenAI Service Account
resource "azurerm_cognitive_account" "kraft-openai_account" {
name = local.openai_account_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
kind = "OpenAI"
sku_name = "S0"
custom_subdomain_name = local.subdomain_name
network_acls {
default_action = "Allow"
}
}
# Azure OpenAI Service Deployment
resource "azurerm_cognitive_deployment" "kraft-chat" {
name = "my-chat-model"
cognitive_account_id = azurerm_cognitive_account.kraft-openai_account.id
model {
format = "OpenAI"
name = "gpt-35-turbo"
version = "0613"
}
sku {
name = "Standard"
}
}
# Azure Cognitive Search
resource "azurerm_search_service" "kraft_search_service" {
name = "mysearchservice-${random_integer.ri.result}"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
sku = "free"
replica_count = 1
partition_count = 1
}
# データソース作成のためのnull_resource
resource "null_resource" "search_data_source" {
provisioner "local-exec" {
command = <<EOT
curl -X POST "https://${azurerm_search_service.kraft_search_service.name}.search.windows.net/datasources?api-version=2021-04-30-Preview" \
-H "Content-Type: application/json" \
-H "api-key: ${azurerm_search_service.kraft_search_service.primary_key}" \
-d '{
"name": "blob-data-source",
"type": "azureblob",
"credentials": {
"connectionString": "${azurerm_storage_account.kraftstorage.primary_connection_string}"
},
"container": {
"name": "${azurerm_storage_container.search-container.name}"
}
}'
EOT
}
depends_on = [
azurerm_search_service.kraft_search_service,
azurerm_storage_account.kraftstorage,
azurerm_storage_container.search-container
]
}
# インデックスの作成
resource "null_resource" "search_index" {
provisioner "local-exec" {
command = <<EOT
curl -X PUT "https://${azurerm_search_service.kraft_search_service.name}.search.windows.net/indexes/blob-index?api-version=2021-04-30-Preview" \
-H "Content-Type: application/json" \
-H "api-key: ${azurerm_search_service.kraft_search_service.primary_key}" \
-d '{
"name": "blob-index",
"fields": [
{"name": "id", "type": "Edm.String", "key": true},
{"name": "content", "type": "Edm.String", "searchable": true},
{"name": "title", "type": "Edm.String", "searchable": true},
{"name": "metadata", "type": "Edm.String", "searchable": true},
{"name": "author", "type": "Edm.String", "searchable": true},
{"name": "created_date", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true},
{"name": "updated_date", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true},
{"name": "keywords", "type": "Collection(Edm.String)", "searchable": true},
{"name": "summary", "type": "Edm.String", "searchable": true},
{"name": "url", "type": "Edm.String", "retrievable": true}
]
}'
EOT
}
depends_on = [null_resource.search_data_source]
}
# インデクサの作成
resource "null_resource" "search_indexer" {
provisioner "local-exec" {
command = <<EOT
curl -X PUT "https://${azurerm_search_service.kraft_search_service.name}.search.windows.net/indexers/blob-indexer?api-version=2021-04-30-Preview" \
-H "Content-Type: application/json" \
-H "api-key: ${azurerm_search_service.kraft_search_service.primary_key}" \
-d '{
"name": "blob-indexer",
"dataSourceName": "blob-data-source",
"targetIndexName": "blob-index",
"schedule": {
"interval": "PT1H"
},
"fieldMappings": [
{
"sourceFieldName": "metadata_storage_path",
"targetFieldName": "url"
},
{
"sourceFieldName": "metadata_storage_name",
"targetFieldName": "title"
}
]
}'
EOT
}
depends_on = [
null_resource.search_data_source,
null_resource.search_index
]
}
# App Service Plan
resource "azurerm_service_plan" "app_service_plan" {
name = "myAppServicePlan-${random_integer.ri.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
os_type = "Linux"
sku_name = "B1"
}
# Azure Linux Web App
resource "azurerm_linux_web_app" "linux_web_app" {
name = "myChatApp-${random_integer.ri.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.app_service_plan.id
site_config {
application_stack {
python_version = "3.9"
}
app_command_line = "pip install -r /home/site/wwwroot/requirements.txt && gunicorn --bind=0.0.0.0:8000 --timeout 600 flaskr.wsgi:app"
}
app_settings = {
"WEBSITE_RUN_FROM_PACKAGE" = "https://${azurerm_storage_account.kraftstorage.name}.blob.core.windows.net/${azurerm_storage_container.kraft-container.name}/flaskr.zip"
"SEARCH_ENDPOINT" = "https://${azurerm_search_service.kraft_search_service.name}.search.windows.net"
"SEARCH_API_KEY" = azurerm_search_service.kraft_search_service.query_keys[0].key
"OPENAI_API_BASE" = "https://${local.subdomain_name}.openai.azure.com/"
"OPENAI_API_KEY" = azurerm_cognitive_account.kraft-openai_account.primary_access_key
"WEBSITE_PORT" = "8000"
}
}