こんにちは、クラウド事業部の山中です。
今回はDatadog OperatorをAKSにデプロイし、
Azure Database for PostgreSQL のフレキシブル サーバーのメトリクスを収集してみました。
手順は基本的にこちらに沿って進めます。
Azure側の準備
まずはAzure側のリソースを作成します。
検証用に以下のterraformコードを書きました。
terraformコード
# provider
terraform {
required_version = "=1.13.3"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=4.73.0"
}
azuread = {
source = "hashicorp/azuread"
version = "=3.8.0"
}
}
}
provider "azurerm" {
resource_provider_registrations = "none"
features {}
tenant_id = var.tenant_id
subscription_id = var.subscription_id
}
# variable
variable "tenant_id" {
type = string
}
variable "subscription_id" {
type = string
}
variable "resource_group_name" {
type = string
}
variable "name" {
type = string
}
variable "tags" {
type = map(string)
}
variable "datadog_api_key" {
type = string
}
variable "postgres_admin_password" {
type = string
}
variable "postgres_datadog_password" {
type = string
}
# data
data "azurerm_resource_group" "rg" {
name = var.resource_group_name
}
# resource
resource "azurerm_virtual_network" "vnet" {
name = "dev-vnet"
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
address_space = ["10.2.0.0/16"]
}
resource "azurerm_subnet" "subnets" {
for_each = {
"aks-subnet" = {
address_prefixes = ["10.2.1.0/24"]
}
"db-subnet" = {
address_prefixes = ["10.2.2.0/28"]
service_endpoints = ["Microsoft.Storage"]
delegation = {
name = "db-subnet-delegation"
service_delegation = {
name = "Microsoft.DBforPostgreSQL/flexibleServers"
actions = ["Microsoft.Network/virtualNetworks/subnets/join/action"]
}
}
}
"keyvault-subnet" = {
address_prefixes = ["10.2.2.16/28"]
}
}
name = each.key
resource_group_name = data.azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = each.value.address_prefixes
service_endpoints = lookup(each.value, "service_endpoints", null) != null ? each.value.service_endpoints : null
private_endpoint_network_policies = "NetworkSecurityGroupEnabled"
dynamic "delegation" {
for_each = lookup(each.value, "delegation", null) != null ? [each.value.delegation] : []
content {
name = delegation.value.name
service_delegation {
name = delegation.value.service_delegation.name
actions = delegation.value.service_delegation.actions
}
}
}
}
resource "azurerm_private_dns_zone" "private_dns_zones" {
for_each = {
postgres_db = "privatelink.postgres.database.azure.com"
keyvault = "privatelink.vaultcore.azure.net"
}
name = each.value
resource_group_name = data.azurerm_resource_group.rg.name
tags = var.tags
}
resource "azurerm_private_dns_zone_virtual_network_link" "dns_link" {
for_each = {
"dev-vnet_postgres_db" = {
private_dns_zone_name = "privatelink.postgres.database.azure.com"
virtual_network_id = azurerm_virtual_network.vnet.id
}
"dev-vnet_keyvault" = {
private_dns_zone_name = "privatelink.vaultcore.azure.net"
virtual_network_id = azurerm_virtual_network.vnet.id
}
}
name = each.key
resource_group_name = data.azurerm_resource_group.rg.name
private_dns_zone_name = each.value.private_dns_zone_name
virtual_network_id = each.value.virtual_network_id
tags = var.tags
depends_on = [azurerm_virtual_network.vnet, azurerm_private_dns_zone.private_dns_zones]
}
resource "azurerm_private_endpoint" "private_endpoints" {
for_each = {
"keyvault-pe" = {
private_connection_resource_id = azurerm_key_vault.keyvault.id
subnet_id = azurerm_subnet.subnets["keyvault-subnet"].id
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zones["keyvault"].id]
subresource_names = ["vault"]
}
}
name = each.key
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
subnet_id = each.value.subnet_id
private_dns_zone_group {
name = "${each.key}-pdzg"
private_dns_zone_ids = each.value.private_dns_zone_ids
}
private_service_connection {
name = "${each.key}-psc"
private_connection_resource_id = each.value.private_connection_resource_id
subresource_names = each.value.subresource_names
is_manual_connection = false
}
tags = var.tags
depends_on = [
azurerm_virtual_network.vnet,
azurerm_private_dns_zone.private_dns_zones,
azurerm_key_vault.keyvault
]
}
resource "azurerm_user_assigned_identity" "users" {
for_each = toset([
"aks-user",
"datadog"
])
name = each.value
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
tags = var.tags
}
resource "azurerm_key_vault" "keyvault" {
name = var.name
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
tenant_id = var.tenant_id
tags = var.tags
sku_name = "standard"
#public_network_access_enabled = false
rbac_authorization_enabled = true
}
resource "azurerm_key_vault_secret" "keyvault-secrets" {
for_each = {
datadog-api-key = var.datadog_api_key
postgres-datadog-password = var.postgres_datadog_password
}
name = each.key
value = each.value
key_vault_id = azurerm_key_vault.keyvault.id
depends_on = [azurerm_key_vault.keyvault]
}
resource "azurerm_postgresql_flexible_server" "db" {
name = var.name
resource_group_name = data.azurerm_resource_group.rg.name
location = data.azurerm_resource_group.rg.location
delegated_subnet_id = azurerm_subnet.subnets["db-subnet"].id
sku_name = "B_Standard_B1ms"
administrator_login = "psqladmin"
administrator_password = var.postgres_admin_password
version = "16"
private_dns_zone_id = azurerm_private_dns_zone.private_dns_zones["postgres_db"].id
public_network_access_enabled = false
tags = var.tags
lifecycle {
ignore_changes = [zone]
}
}
resource "azurerm_role_assignment" "roles" {
for_each = {
"datadog-keyvault" = {
scope = azurerm_key_vault.keyvault.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_user_assigned_identity.users["datadog"].principal_id
}
}
scope = each.value.scope
role_definition_name = each.value.role_definition_name
principal_id = each.value.principal_id
depends_on = [azurerm_user_assigned_identity.users, azurerm_key_vault.keyvault]
}
resource "azurerm_kubernetes_cluster" "k8s" {
name = var.name
dns_prefix = var.name
location = data.azurerm_resource_group.rg.location
resource_group_name = data.azurerm_resource_group.rg.name
kubernetes_version = "1.34"
sku_tier = "Free"
automatic_upgrade_channel = "patch"
node_os_upgrade_channel = "NodeImage"
role_based_access_control_enabled = true
oidc_issuer_enabled = true
workload_identity_enabled = true
tags = var.tags
#private_cluster_enabled = true
default_node_pool {
name = "default"
vm_size = "Standard_B4ms"
node_count = "1"
vnet_subnet_id = azurerm_subnet.subnets["aks-subnet"].id
auto_scaling_enabled = "false"
orchestrator_version = "1.34"
temporary_name_for_rotation = "tmpdefault"
upgrade_settings {
drain_timeout_in_minutes = 0
max_surge = "33%"
node_soak_duration_in_minutes = 0
}
}
identity {
type = "UserAssigned"
identity_ids = [
azurerm_user_assigned_identity.users["aks-user"].id
]
}
# azure_active_directory_role_based_access_control {
# azure_rbac_enabled = true
# }
network_profile {
network_plugin = "azure"
network_policy = "cilium"
network_data_plane = "cilium"
#network_plugin_mode = "overlay"
load_balancer_sku = "standard"
service_cidr = "172.16.0.0/24"
dns_service_ip = "172.16.0.254"
}
# key_vault_secrets_provider {
# secret_rotation_enabled = true
# }
depends_on = [azurerm_subnet.subnets, azurerm_user_assigned_identity.users]
}
resource "azurerm_kubernetes_cluster_node_pool" "nodepool-user" {
kubernetes_cluster_id = azurerm_kubernetes_cluster.k8s.id
name = "user"
vm_size = "Standard_B4ms"
node_count = 1
vnet_subnet_id = azurerm_subnet.subnets["aks-subnet"].id
temporary_name_for_rotation = "tmpuser"
tags = var.tags
upgrade_settings {
drain_timeout_in_minutes = 0
max_surge = "33%"
node_soak_duration_in_minutes = 0
}
depends_on = [azurerm_kubernetes_cluster.k8s]
}
resource "azurerm_federated_identity_credential" "aks-workload_identity-federations" {
for_each = {
"datadog-agent-federation" = {
user_assigned_identity_id = azurerm_user_assigned_identity.users["datadog"].id
subject = "system:serviceaccount:datadog:datadog-agent"
}
"datadog-cluster-agent-federation" = {
user_assigned_identity_id = azurerm_user_assigned_identity.users["datadog"].id
subject = "system:serviceaccount:datadog:datadog-cluster-agent"
}
"datadog-cluster-checks-runner-federation" = {
user_assigned_identity_id = azurerm_user_assigned_identity.users["datadog"].id
subject = "system:serviceaccount:datadog:datadog-cluster-checks-runner"
}
}
name = each.key
audience = ["api://AzureADTokenExchange"]
issuer = azurerm_kubernetes_cluster.k8s.oidc_issuer_url
user_assigned_identity_id = each.value.user_assigned_identity_id
subject = each.value.subject
depends_on = [azurerm_kubernetes_cluster.k8s, azurerm_user_assigned_identity.users]
}
作成済みリソースグループに対してvnetとsubnetを作成し、
AKS、AzureKeyVault、Azure Database for PostgreSQL のフレキシブル サーバーをデプロイし、リソースにアクセスするための各種設定をしています。
実行する場合は以下の点にご注意ください。
- 変数になっている部分は
terraform.tfvarsなどを準備してご自身の環境に合わせて設定してください - リソースグループに対して
共同作成者、キー コンテナー管理者、ユーザー アクセス管理者のロールがあれば実行できると思います - VPNなどは手間なのでパブリックアクセスを許可しています
- 料金がかかります
terraform applyが完了したら、以下のコマンドで作成したAKSクラスタにkubectlが実行できるようにしてください。
az aks get-credentials --resource-group <var.resource_group_name> --name <var.name> --overwrite-existing
Postgres 設定を構成する
こちらを参考に、データベース側の設定を変更します。
検証用のためオプションもすべて有効化します。
以下のようなコマンドで設定できると思います。
az postgres flexible-server parameter set --resource-group <var.resource_group_name> --server-name <var.name> --name azure.extensions --value PG_STAT_STATEMENTS az postgres flexible-server parameter set --resource-group <var.resource_group_name> --server-name <var.name> --name track_activity_query_size --value 4096 az postgres flexible-server parameter set --resource-group <var.resource_group_name> --server-name <var.name> --name pg_stat_statements.track --value ALL az postgres flexible-server parameter set --resource-group <var.resource_group_name> --server-name <var.name> --name pg_stat_statements.max --value 10000 az postgres flexible-server parameter set --resource-group <var.resource_group_name> --server-name <var.name> --name pg_stat_statements.track_utility --value off az postgres flexible-server parameter set --resource-group <var.resource_group_name> --server-name <var.name> --name track_io_timing --value on az postgres flexible-server restart --resource-group <var.resource_group_name> --name <var.name>
クエリはAKSに一時的にpgadminを立てて、そこから実行することにしました。
以下のようなmanifestを準備しました。
ホスト名などはご自身の環境に書き換えていただく必要があります。
pgadmin.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: pgadmin
name: pgadmin
namespace: default
spec:
containers:
- image: dpage/pgadmin4:latest
name: pgadmin
env:
- name: PGADMIN_DEFAULT_EMAIL
value: "user@domain.com"
- name: PGADMIN_DEFAULT_PASSWORD
value: "SuperSecret"
- name: PGADMIN_CONFIG_SERVER_MODE
value: "False"
- name: PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED
value: "False"
- name: TZ
value: "Asia/Tokyo"
ports:
- name: http
containerPort: 80
resources: {}
volumeMounts:
- name: pgadmin-config
mountPath: /pgadmin4/servers.json
subPath: servers.json
readOnly: true
volumes:
- name: pgadmin-config
configMap:
name: pgadmin-config
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
---
apiVersion: v1
kind: Service
metadata:
name: pgadmin
spec:
selector:
run: pgadmin
ports:
- port: 80
targetPort: 80
name: http
type: ClusterIP
---
apiVersion: v1
kind: ConfigMap
metadata:
name: pgadmin-config
data:
servers.json: |
{
"Servers": {
"1": {
"Name": "<var.name>",
"Group": "Servers",
"Host": "<var.name>.postgres.database.azure.com",
"Port": 5432,
"MaintenanceDB": "postgres",
"Username": "psqladmin",
"SSLMode": "prefer"
}
}
}
デプロイします。
kubectl apply -f .\pgadmin.yaml
ポートフォワードでアクセスします。
kubectl port-forward pgadmin 8080:80
無事起動できていたらブラウザでhttp://localhost:8080/にアクセスすればログインできると思います。
terraform実行時に設定したadministrator_passwordでDBに接続したら、
ドキュメント通りのクエリを実行してdatadogユーザー作成などを行ってください。

Agent のインストールと構成
こちらを参考にhelmでDatadog Operatorをデプロイします。
helm repo add datadog https://helm.datadoghq.com helm install datadog-operator datadog/datadog-operator -n datadog --create-namespace
次にDatadogAgentのカスタムリソースをデプロイします。
以下のようなmanifestを準備しました。
一部ご自身の環境に書き換えてください。
datadog-agent.yaml
apiVersion: datadoghq.com/v2alpha1
kind: DatadogAgent
metadata:
name: datadog
namespace: datadog
spec:
global:
clusterName: <var.name>
site: datadoghq.com
tags:
- "env:<var.name>"
credentials:
apiKey: ENC[datadog-api-key]
features:
clusterChecks:
enabled: true
useClusterChecksRunners: true
override:
nodeAgent:
env:
- name: DD_SECRET_BACKEND_TYPE
value: "azure.keyvault"
- name: DD_SECRET_BACKEND_CONFIG
value: '{"keyvaulturl":"https://<var.name>.vault.azure.net/"}'
serviceAccountAnnotations:
azure.workload.identity/client-id: "<Managed ID clientID>"
labels:
azure.workload.identity/use: "true"
clusterChecksRunner:
serviceAccountAnnotations:
azure.workload.identity/client-id: "<Managed ID clientID>"
labels:
azure.workload.identity/use: "true"
env:
- name: DD_SECRET_BACKEND_TYPE
value: "azure.keyvault"
- name: DD_SECRET_BACKEND_CONFIG
value: '{"keyvaulturl":"https://<var.name>.vault.azure.net/"}'
clusterAgent:
extraConfd:
configDataMap:
postgres.yaml: |-
cluster_check: true
init_config:
instances:
- dbm: true
host: <var.name>.postgres.database.azure.com
port: 5432
username: datadog
password: ENC[postgres-datadog-password]
dbname: postgres
collect_function_metrics: false
ssl: required
tags:
- "service:<var.name>"
azure:
deployment_type: flexible_server
fully_qualified_domain_name: <var.name>.postgres.database.azure.com
podが無事に起動すればOKです。
kubectl get pod -n datadog NAME READY STATUS RESTARTS AGE datadog-agent-jnq64 2/2 Running 0 50s datadog-agent-r5g99 2/2 Running 0 51s datadog-cluster-agent-544b7d8c9b-sxgz5 1/1 Running 0 51s datadog-cluster-checks-runner-59bf6cc75c-gwsq4 1/1 Running 0 51s datadog-operator-64f5cdbb4c-jcdxp 1/1 Running 0 4m46s
secret管理について
datadog API Keysなどsecret情報はAzure KeyVaultからworkload identityを使って取得しています。
設定方法は こちらのネイティブ Agent サポートを使用します。
Operatorでの設定方法はAWS Secretsにしか書いてないですが、Azure KeyVaultでも使えました。
以下の設定部分です。
override:
nodeAgent:
env:
- name: DD_SECRET_BACKEND_TYPE
value: "azure.keyvault"
- name: DD_SECRET_BACKEND_CONFIG
value: '{"keyvaulturl":"https://<var.name>.vault.azure.net/"}'
Azure側ではdatadogというマネージドIDを作成し、
Key Vault Secrets Userロールをアサインし、Datadog Operatorで自動的に作成される
datadog用のKubernetes Service Accountのフェデレーション設定をしています。
terraformコードをご確認いただければと思います。
そしてAKS側でpodのラベルにazure.workload.identity/use: "true"を追加、
Service Accountのアノテーションにazure.workload.identity/client-id: "<Managed ID clientID>"を追加してマネージドIDと紐づけます。
Datadog OperatorではDatadogAgentのカスタムリソースでoverrideで設定できました。
以下の部分ですね。
override:
nodeAgent:
serviceAccountAnnotations:
azure.workload.identity/client-id: "<Managed ID clientID>"
labels:
azure.workload.identity/use: "true"
マネージドIDのクライアントIDは以下のコマンドで取得できると思います。
az identity show -g <var.resource_group_name> -n datadog --query clientId -o tsv
Datadog AgentとDB Monitoringを実行するclusterChecksRunnerで設定が必要でした。
Workload Identityについては以下も参考になると思うのでご確認いただければと思います。
Datadog でメトリクス確認
こちらにあるようにpostgresql.から始まるメトリクスが収集できるようです。
無事収集できていれば、Metrics Summaryで確認できると思います。

こちらのメトリクスは
Postgresのインテグレーション
を有効化すると追加されるPostgres - Metricsのダッシュボードで確認できるので試してみてもらえればと思います。
