はじめに
皆さま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にはデフォルトでterraform
とkubectl
がインストールされています。
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
コードは以下の通りです。
# 必要な外部プロバイダーの定義 ※今回は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_cluster
のoidc_issuer_enabled
とworkload_identity_enabled
をtrue
に設定してください。
次に必要な設定としては、フェデレーションID資格情報azurerm_federated_identity_credential
になります。
この設定により、ユーザ割当マネージドIDとAKS、サービスアカウントの紐づけがされます。
Kubernetesリソースの設定
Kubernetesリソースとしてはサービスアカウント(sa)にユーザ割当マネージドIDのクライアントIDを設定する点と、そのsaとPodを紐づけ、
ラベルazure.workload.identity/use
にtrue
を設定することが必要です。
これにより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_enabled
をtrue
にすると、OIDC Provider URLが公開され、AzureADと連携できるようになります。
OIDC Issuerは、一度有効化すると無効化することはできません。無効化するにはクラスタを再作成する必要があります。
またworkload_identity_enabled
をtrue
にすると、ラベルにazure.workload.identity/use
がtrue
となっているPodに対して、ワークロードIDに必要な設定がInjectされます。
InjectはPodの作成時にのみ行われるようなので、修正するにはPod自身を再作成してください。
認証フローの詳細については、以下の公式ドキュメントを参照ください。
また上記の図を作成するにあたり、以下のサイトも参照しています。
- Lab - Workload Identity
- Connect your Kubernetes application to your database without any credentials (and securely) - Alexis Plantin - Blog
終わりに
如何でしたでしょうか。公式ドキュメントだと認証フローから記載されているため取っ付きにくいと思いますが、一度設定が分かってしまえば、そこまで難しくないと思います。
また認証についてもAzure IdentityのDefaultAzureCredential
を利用していれば、既存コードから変更することなくワークロードIDに移行できることが分かったかと思います。
特に冒頭にも記載さセていただいた通り、日増しにキーやパスワードの漏洩リスクが高まってきているのを感じているため、もしキーやパスワードを利用されている場合は是非移行についてご検討ください。