Streamline Network Observability on AKS: A Step-by-Step Guide to enable the AKS add-on with Terraform
Have you ever had to troubleshoot network issues in your Kubernetes clusters? If so, you know how challenging it can be to identify and resolve problems.
To troubleshoot network issues you probably had to use a combination of tools like kubectl, tcpdump, wireshark, and netstat. The list goes on and on… While these tools are great for debugging and capturing network logs and traces, they don’t provide a holistic view of your cluster’s network traffic.
Monitoring Azure Container Apps With Azure Managed Grafana
The Azure Monitor team has announced the general availability of the Azure Managed Grafana (AMG) service. As part of the announcement, they also announced the availability of curated Grafana dashboards for various Azure services including Azure Container Apps 🎉
Grafana is very popular within the Cloud Native community and it seems natural to use it for Azure Container Apps (ACA) observability.
In this post, I will walk you through provisioning the ACA and AMG resources using the Terraform AzAPI provider and show you how easy it is to import the ACA dashboards into your AMG instance.
Continue readingTerraform: Azure Container Apps with Azure Managed Grafana using the AzAPI provider
Code snippet for the Monitoring Azure Container Apps With Azure Managed Grafana article.
main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">=3.0.0"
}
azapi = {
source = "azure/azapi"
version = ">=0.5.0"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
key_vault {
purge_soft_delete_on_destroy = false
}
}
}
resource "random_pet" "aca" {
length = 2
separator = ""
}
resource "random_integer" "aca" {
min = 000
max = 999
}
resource "random_string" "aca" {
length = 5
lower = true
upper = false
numeric = true
special = false
keepers = {
# Generate a new random_string on every run to avoid a conflict with the previous revision
none = timestamp()
}
}
locals {
resource_name = format("%s", random_pet.aca.id)
resource_name_unique = format("%s%s", random_pet.aca.id, random_integer.aca.result)
location = "eastus"
}
resource "azurerm_resource_group" "aca" {
name = "rg-${local.resource_name}"
location = local.location
}
resource "azurerm_log_analytics_workspace" "aca" {
name = "law-${local.resource_name_unique}"
resource_group_name = azurerm_resource_group.aca.name
location = azurerm_resource_group.aca.location
sku = "PerGB2018"
retention_in_days = 30
}
# https://docs.microsoft.com//azure/templates/microsoft.app/2022-03-01/managedenvironments?tabs=bicep&pivots=deployment-language-terraform
resource "azapi_resource" "env" {
type = "Microsoft.App/managedEnvironments@2022-03-01"
name = "env-${local.resource_name}"
parent_id = azurerm_resource_group.aca.id
location = azurerm_resource_group.aca.location
body = jsonencode({
properties = {
appLogsConfiguration = {
destination = "log-analytics"
logAnalyticsConfiguration = {
customerId = azurerm_log_analytics_workspace.aca.workspace_id
sharedKey = azurerm_log_analytics_workspace.aca.primary_shared_key
}
}
}
})
}
resource "azapi_resource" "helloworld" {
type = "Microsoft.App/containerApps@2022-03-01"
name = "helloworld"
parent_id = azurerm_resource_group.aca.id
location = azurerm_resource_group.aca.location
body = jsonencode({
properties = {
managedEnvironmentId = azapi_resource.env.id
configuration = {
ingress = {
allowInsecure = false
external = true
targetPort = 80
traffic = [
{
label = "dev"
latestRevision = true
weight = 100
}
]
}
}
template = {
containers = [
{
name = "helloworld"
image = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
resources = {
cpu = 0.5
memory = "1.0Gi"
}
}
]
revisionSuffix = random_string.aca.result
scale = {
minReplicas = 0
maxReplicas = 30
rules = [
{
name = "http-rule"
http = {
metadata = {
concurrentRequests = "100"
}
}
}
]
}
}
}
})
# this tells azapi to pull out properties and stuff into the output attribute for the object
response_export_values = ["properties.configuration.ingress.fqdn"]
}
data "azurerm_subscription" "aca" {}
data "azurerm_client_config" "aca" {}
resource "azapi_resource" "amg" {
type = "Microsoft.Dashboard/grafana@2022-08-01"
name = "amg-${local.resource_name}"
parent_id = azurerm_resource_group.aca.id
location = azurerm_resource_group.aca.location
identity {
type = "SystemAssigned"
}
body = jsonencode({
properties = {
apiKey = "Enabled"
autoGeneratedDomainNameLabelScope = "TenantReuse"
deterministicOutboundIP = "Enabled"
publicNetworkAccess = "Enabled"
zoneRedundancy = "Disabled"
}
sku = {
name = "Standard"
}
})
# this tells azapi to pull out properties and stuff into the output attribute for the object
response_export_values = ["identity.principalId"]
}
resource "azurerm_role_assignment" "amg_reader" {
scope = data.azurerm_subscription.aca.id
role_definition_name = "Monitoring Reader"
principal_id = jsondecode(azapi_resource.amg.output).identity.principalId
}
resource "azurerm_role_assignment" "amg_admin" {
scope = azapi_resource.amg.id
role_definition_name = "Grafana Admin"
principal_id = data.azurerm_client_config.aca.object_id
}
output "helloworld_ingress_url" {
value = format("%s%s", "https://", jsondecode(azapi_resource.helloworld.output).properties.configuration.ingress.fqdn)
}
# optional to automate the dashboard import process
resource "null_resource" "aca" {
provisioner "local-exec" {
command = <<-EOT
az config set extension.use_dynamic_install=yes_without_prompt
az grafana dashboard import -g ${azurerm_resource_group.aca.name} -n ${azapi_resource.amg.name} --definition 16592
EOT
}
}