2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Azure OpenAIを例にAKSのワークロードID環境をterraformで構築・設定する

Last updated at Posted at 2023-09-28

はじめに

皆さまAKS上で動作させるアプリケーションと各Azureサービスとの認証はどうされていますでしょうか?
特にGitOpsを構成していたりすると、キーやパスワードを利用すると漏洩リスクが高くなるので、厄介だと思います。
AzureではAKS(Azure Kubernetes Service)について、23年4月にこれを解決するため「ワークロードID (workload identity)」がGAされました。

そこで今回、このワークロードIDを利用するAzure環境をIaCツールであるterraformを用いて構築したいと思います。
主には以下のMicrosoftドキュメントの手順について、terraformを用いて実装したのものになります。

またワークロードIDの特徴については、以下のGA時の記事をご参照ください。

構築する環境

今回は以下のような簡単な環境をterraformを用いて構築します。
デプロイするリソースの名称は、後程掲載するterraformのローカル変数を修正ください。
詳細については「認証フローについて」にて記載いたします。

デプロイリソース

環境構築手順

ここではAzure CloudShell(bash)を利用します。CloudShellにはデフォルトでterraformkubectlがインストールされています。
CloudShellで以下に記載の通りコマンドを実行し、terraformコードについてもコピーしてください。
またコマンド中のコメントに記載の通り、1度目のterraform applyは失敗するので、再度実行し、AKSにマニフェストを反映させてください。

# terraformフォルダを作成し移動
mkdir terraform && cd terraform

# terraformの内容を貼り付けて保存
vi aks-workload-id.tf

# terraformを初期化し、デプロイ実行
terraform init && terraform apply

# 1回目ではKubernetes Configが存在せず失敗するので再実行
terraform apply

コピーするterraformコードは以下の通りです。

aks-workload-id.tf
# 必要な外部プロバイダーの定義 ※今回はAzApiを利用します。
terraform {
  required_providers {
    azapi = {
      source = "azure/azapi"
    }
  }
}
# 各プロバイダーの設定
provider "azurerm" {
  features {}
}
provider "kubernetes" {
  config_path = "~/.kube/config"
}
provider "azapi" {
}

# ワークロードID関連で利用するローカル変数を定義
locals {
  resource_group_name                = "example"
  resource_group_locaiton            = "Japan East"

  aks_name                           = "example-aks"
  aks_dns_prefix                     = "exampleaks"

  service_account_name               = "workload-identity-sa"
  service_account_namespace          = "default"
  user_assigned_identity_name        = "myIdentity"
  federated_identity_credential_name = "myFedIdentity"

  kubernetes_pod_name                = "test-pod"
  kubernetes_container_name          = "test"
  
  openai_account_name                = "example-openai"
  gpt_35_turbo_deploy_name           = "chatgpt"
  gpt_35_turbo_model_version         = "0613"
}

# リソースグループを作成
resource "azurerm_resource_group" "example" {
  name     = local.resource_group_name
  location = local.resource_group_locaiton
}

# AKSの作成
resource "azurerm_kubernetes_cluster" "example" {
  name                      = local.aks_name
  location                  = azurerm_resource_group.example.location
  resource_group_name       = azurerm_resource_group.example.name
  dns_prefix                = local.aks_dns_prefix
  oidc_issuer_enabled       = true # OIDC issuerを有効化 
  workload_identity_enabled = true # ワークロードIDを有効化

  default_node_pool {
    name       = "default"
    node_count = 1
    vm_size    = "Standard_D2_v2"
  }

  identity {
    type = "SystemAssigned"
  }

  # デプロイ後、Kubernetes Configを作成
  provisioner "local-exec" {
    command = "az aks get-credentials -n ${self.name} -g ${azurerm_resource_group.example.name}"
  }
}

# ユーザ割当マネージドIDの作成
resource "azurerm_user_assigned_identity" "example" {
  name                = local.user_assigned_identity_name
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

# Kubernetesのサービスアカウントを作成
resource "kubernetes_service_account" "example" {
  depends_on = [azurerm_kubernetes_cluster.example]

  metadata {
    name      = local.service_account_name
    namespace = local.service_account_namespace
    annotations = {
      "azure.workload.identity/client-id" = azurerm_user_assigned_identity.example.client_id
    }
  }
}

# フェデレーション ID 資格情報を設定
resource "azurerm_federated_identity_credential" "example" {
  depends_on = [kubernetes_service_account.example]

  name                = local.federated_identity_credential_name
  parent_id           = azurerm_user_assigned_identity.example.id
  resource_group_name = azurerm_resource_group.example.name
  audience            = ["api://AzureADTokenExchange"]
  issuer              = azurerm_kubernetes_cluster.example.oidc_issuer_url
  subject             = "system:serviceaccount:${local.service_account_namespace}:${local.service_account_name}"
}

# テスト用のKubernetes Podを作成
resource "kubernetes_pod" "example" {
  depends_on = [azurerm_federated_identity_credential.example]

  metadata {
    name      = local.kubernetes_pod_name
    namespace = local.service_account_namespace
    labels = {
      "azure.workload.identity/use" = "true"
    }
  }

  spec {
    service_account_name = local.service_account_name
    container {
      name    = local.kubernetes_container_name
      image   = "python:3.11"
      command = ["sleep", "3600"]
    }
  }
}

# OpenAIアカウントを作成
resource "azurerm_cognitive_account" "example" {
  name                  = local.openai_account_name
  location              = azurerm_resource_group.example.location
  resource_group_name   = azurerm_resource_group.example.name
  custom_subdomain_name = local.openai_account_name

  kind     = "OpenAI"
  sku_name = "S0"

  identity {
    type = "SystemAssigned"
  }
}

# gpt-35-turbo(ChatGPT)モデルをデプロイ
resource "azapi_resource" "chatgpt" {
  type      = "Microsoft.CognitiveServices/accounts/deployments@2023-05-01"
  name      = local.gpt_35_turbo_deploy_name
  parent_id = azurerm_cognitive_account.example.id

  body = jsonencode({
    properties = {
      model = {
        format  = "OpenAI"
        name    = "gpt-35-turbo"
        version = local.gpt_35_turbo_model_version
      }
      versionUpgradeOption = "NoAutoUpgrade"
    }
    sku = {
      capacity = 120
      name     = "Standard"
    }
  })
}

# ユーザ割当マネージドIDにアクセス権を付与
resource "azurerm_role_assignment" "openai_role_user" {
  scope                = azurerm_cognitive_account.example.id
  role_definition_name = "Cognitive Services OpenAI User"
  principal_id         = azurerm_user_assigned_identity.example.principal_id
}

ワークロードIDに関する設定について

Azure上の設定

terraformでワークロードIDを有効化するにはazurerm_kubernetes_clusteroidc_issuer_enabledworkload_identity_enabledtrueに設定してください。
次に必要な設定としては、フェデレーションID資格情報azurerm_federated_identity_credentialになります。
この設定により、ユーザ割当マネージドIDとAKS、サービスアカウントの紐づけがされます。

Kubernetesリソースの設定

Kubernetesリソースとしてはサービスアカウント(sa)にユーザ割当マネージドIDのクライアントIDを設定する点と、そのsaとPodを紐づけ、
ラベルazure.workload.identity/usetrueを設定することが必要です。
これによりPodデプロイ時にワークロードIDを用いた認証に必要な設定がInjectされます。
詳細については後述の「認証フローについて」を参照ください。

サンプルアプリによるワークロードIDでの認証例

次にAzure CloudShellで以下のコマンドを実行し、Podに入ってください。

kubectl exec -it test-pod -- /bin/bash

Podに入った後は、pipを用いて必要なライブラリと、aptでエディタ(vi等)をインストールしてください。

pip install azure-identity openai
apt install vi

次にインストールしたエディタで、以下のPythonを保存してください。
この際、terraformでOpenAIのリソース名を変更した場合は、上から6行目のexample-openaiを変更したリソース名に、OpenAIのデプロイ名を変更した場合は12行目についてengine="<デプロイ名>"に修正してください。
このPythonを実行すると、自動的にワークロードIDを用いて認証され、Azure OpenAI Serviceから応答が返ってくるかと思います。
見て頂けたら分かる通り、特にコード上ではワークロードIDに関する記述はなく、全てDefaultAzureCredentialが吸収してくれています。

import openai
from azure.identity import DefaultAzureCredential

default_credential = DefaultAzureCredential()

openai.api_base = "https://example-openai.openai.azure.com/"
openai.api_version = "2023-05-15"
openai.api_type = "azure_ad"
openai.api_key = default_credential.get_token("https://cognitiveservices.azure.com/.default").token

response = openai.ChatCompletion.create(
    engine="chatgpt",
    messages=[
        {"role": "user", "content": "こんにちは!"},
    ],
)

print(response.choices[0].message.content)

認証フローについて

ワークロードID利用時の認証の流れは以下の通りです。
前述のoidc_issuer_enabledtrueにすると、OIDC Provider URLが公開され、AzureADと連携できるようになります。
OIDC Issuerは、一度有効化すると無効化することはできません。無効化するにはクラスタを再作成する必要があります。
またworkload_identity_enabledtrueにすると、ラベルにazure.workload.identity/usetrueとなっているPodに対して、ワークロードIDに必要な設定がInjectされます。
InjectはPodの作成時にのみ行われるようなので、修正するにはPod自身を再作成してください。

ワークロードID 認証概要

認証フローの詳細については、以下の公式ドキュメントを参照ください。

また上記の図を作成するにあたり、以下のサイトも参照しています。

終わりに

如何でしたでしょうか。公式ドキュメントだと認証フローから記載されているため取っ付きにくいと思いますが、一度設定が分かってしまえば、そこまで難しくないと思います。
また認証についてもAzure IdentityのDefaultAzureCredentialを利用していれば、既存コードから変更することなくワークロードIDに移行できることが分かったかと思います。
特に冒頭にも記載さセていただいた通り、日増しにキーやパスワードの漏洩リスクが高まってきているのを感じているため、もしキーやパスワードを利用されている場合は是非移行についてご検討ください。

参考記事

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?