見出し画像

TerraformでAzureにGPTモデルとAI Searchを使ったアプリをデプロイしてみた

今回は前回の続きで、Terraformでアプリをデプロイする方法について詳細に説明していきます。

terraformのフォルダ階層がこちら。

terraformmain.tfparam.tfvarsvariables.tfflaskr.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

写真1.ユーザデータのアップロード

メタデータとしてtitle : ファイル名称 を入力し保存します。

写真2.タイトルの入力

Webアプリ > 参照 でUI画面を開くことができます。

写真3.Webアプリ画面
写真4.UI画面

ログを見るには、ログストリームがリアルタイムで追記されるため便利です。

写真5.ログストリーム

最後に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"
  }
}


いいなと思ったら応援しよう!