PART 1: INFRASTRUCTURE — Building a Scalable App Environment with Infrastructure and Deployment

Using .NET, Angular, Kubernetes, Azure/Devops, Terraform, Eventhubs and other Azure resources.

image by author

Introduction

What resources do we need?

Terraform

Prerequisites

Terraform

Azure CLI

Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi; Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'; rm .\AzureCLI.msi
az login

Start creating

Initial Terraform configuration

# Define the required provider by terraform.
provider "azurerm" {
features {
}
version = "=2.33.0"
skip_provider_registration = "true"
}
provider "helm" {version = "= 2.0.2"kubernetes {host = azurerm_kubernetes_cluster.aks.kube_config.0.hostclient_key = base64decode(azurerm_kubernetes_cluster.aks.kube_config.0.client_key)client_certificate = base64decode(azurerm_kubernetes_cluster.aks.kube_config.0.client_certificate)cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.aks.kube_config.0.cluster_ca_certificate)}}# Defines our first resource - the resource group in which we create other resources.
resource "azurerm_resource_group" "rg" {
name = "rg_notifier_example"
// The resource group name in azure.
location = "West Europe"
}
terraform {
backend "local" {}
}
terrafrom init
terraform plan
terraform apply
# Creates our acceptance workspace
terraform workspace new acc
# Creates our production workspace
terraform workspace new prd
# Show all worspaces (the star in the list marks the current workspace)
terraform workspace list
# Select a workspace which will be used inside the terraform code
terraform workspace select acc
resource_group_name: notifier-resource-group-acc
resource_group_name: notifier-resource-group-prd
# Define the required provider by terraform.
provider "azurerm" {
features {
}
version = "=2.33.0"
skip_provider_registration = "true"
}
provider "helm" {version = "= 2.0.2"kubernetes {host = azurerm_kubernetes_cluster.aks.kube_config.0.hostclient_key = base64decode(azurerm_kubernetes_cluster.aks.kube_config.0.client_key)client_certificate = base64decode(azurerm_kubernetes_cluster.aks.kube_config.0.client_certificate)cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.aks.kube_config.0.cluster_ca_certificate)}}# Here we define settings which will be used when creating the resources.
locals {
default_tfsettings = {
} commonSettingsFile = "./settings/common.yaml"
commonSettingsFileContent = fileexists(local.commonSettingsFile) ? file(local.commonSettingsFile) : "NoTFCommonSettingsFileFound: true"
commonSettings = yamldecode(local.commonSettingsFileContent)
workspaceSettingsFile = "./settings/${terraform.workspace}.yaml"
workspaceSettingsFileContent = fileexists(local.workspaceSettingsFile) ? file(local.workspaceSettingsFile) : "NoTFWorkspaceSettingsFileFound: true"
workspaceSettings = yamldecode(local.workspaceSettingsFileContent)
settings = merge(local.default_tfsettings, local.commonSettings, local.workspaceSettings)
}
# Defines our first resource - the resource group in which we create other resources.
resource "azurerm_resource_group" "rg" {
name = local.settings.resource_group_name
// The resource group name in azure.
location = "West Europe"
}

Adding further resources

Application Insights

resource "azurerm_application_insights" "ai" {
name = local.settings.application_insights_name // Name of the resource defined in the settings file.
location = azurerm_resource_group.rg.location // Use resource group location.
resource_group_name = azurerm_resource_group.rg.name // Use our resource group from the current workspace.
application_type = "other" // The type of application. We use "other" here, so it is not so specific like "web", "java", etc.
retention_in_days = 90 // The default retention used here.
sampling_percentage = 100 // To get the most accurate results without so many loose of data.
}
application_insights_name: notifier-application-insights-acc
application_insights_name: notifier-application-insights-prd
terraform plan
terraform apply

Container Registry

resource "azurerm_container_registry" "acr" {
name = "notifiercontainerregistry" // Name of the resource.
location = azurerm_resource_group.rg.location // Use resource group location.
resource_group_name = azurerm_resource_group.rg.name // Use our resource group from the current workspace.
sku = "Basic" // We will use the not so expensive one for this demo.
}
container_registry_name: notifierContainerregistryacc # Some resources can only use alphanumeric names.
container_registry_name: notifiercontainerregistryprd # Some resources can only use alphanumeric names.
terraform plan
terraform apply

Kubernetes Service (AKS)

resource "azurerm_kubernetes_cluster" "aks" {
name = local.settings.aks_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
dns_prefix = local.settings.aks_dns_prefix
default_node_pool {
name = "default"
node_count = 1
vm_size = "Standard_A2_v2"
}
identity {
type = "SystemAssigned"
}
tags = {
Environment = local.settings.aks_tag_environment
}
}
output "client_certificate" {
value = azurerm_kubernetes_cluster.aks.kube_config.0.client_certificate
}
output "kube_config" {
value = azurerm_kubernetes_cluster.aks.kube_config_raw
}
resource "helm_release" "ingress" {name = local.settings.ingress_namerepository = "https://charts.bitnami.com/bitnami"chart = "nginx-ingress-controller"set {name = "rbac.create"value = "true"}}
aks_name: notifier-aks-acc
aks_dns_prefix: notifieraksacc
aks_tag_environment: Acceptance
aks_name: notifier-aks-prd
aks_dns_prefix: notifieraksprd
aks_tag_environment: Production
terraform plan
terraform apply

Event Hubs

resource "azurerm_eventhub_namespace" "ehns" {
name = local.settings.eventhub_namespace.name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
sku = "Standard"
capacity = local.settings.eventhub_namespace.capacity
auto_inflate_enabled = true
maximum_throughput_units = local.settings.eventhub_namespace.maximum_throughput_units
network_rulesets = [{
default_action = "Deny"
ip_rule = []
virtual_network_rule = []
}]
tags = {
"creator" = "markus herkommer"
"environment" = terraform.workspace
}
}
# Define the eventhub
resource "azurerm_eventhub" "notifications" {
name = "notifications"
namespace_name = azurerm_eventhub_namespace.ehns.name
resource_group_name = azurerm_resource_group.rg.name
partition_count = local.settings.eventhub.notifications.partition_count
message_retention = local.settings.eventhub.notifications.message_retention
}
# Define eventhub consumers
resource "azurerm_eventhub_consumer_group" "notifications_notifier_appinsights" {
name = "appinsights"
namespace_name = azurerm_eventhub_namespace.ehns.name
eventhub_name = azurerm_eventhub.notifications.name
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_eventhub_consumer_group" "notifications_notifier_email" {
name = "email"
namespace_name = azurerm_eventhub_namespace.ehns.name
eventhub_name = azurerm_eventhub.notifications.name
resource_group_name = azurerm_resource_group.rg.name
}
# Define eventhub authorization rules
resource "azurerm_eventhub_authorization_rule" "notifications_notifier_send" {
name = "send"
namespace_name = azurerm_eventhub_namespace.ehns.name
eventhub_name = azurerm_eventhub.notifications.name
resource_group_name = azurerm_resource_group.rg.name
listen = false
send = true
manage = false
}
resource "azurerm_eventhub_authorization_rule" "notifications_notifier_listen" {
name = "listen"
namespace_name = azurerm_eventhub_namespace.ehns.name
eventhub_name = azurerm_eventhub.notifications.name
resource_group_name = azurerm_resource_group.rg.name
listen = true
send = false
manage = false
}
eventhub_namespace:
name: eventhubs-acc
capacity: 1
maximum_throughput_units: 10
eventhub:
notifications:
partition_count: 2
message_retention: 7
eventhub_namespace:
name: eventhubs-prd
capacity: 1
maximum_throughput_units: 10
eventhub:
notifications:
partition_count: 4
message_retention: 7
terraform plan
terraform apply

Table Storage

resource "azurerm_storage_account" "sa" {
name = local.settings.storage_account_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_kind = "StorageV2"
account_replication_type = "LRS"
}
storage_account_name: notifierstoreacc
storage_account_name: notifierstoreprd
terraform plan
terraform apply

Key Vaults

tenant_id: YOUR_TENANT_IDkv_allow:
notifier-devs:
object_id: CURRENT_LOGGED_IN_USER_OBJECT_ID
secret_permissions: ["get", "list", "delete", "set", "recover", "backup", "restore"]
keyvault_webapi_name: kv-webapi-acc
keyvault_worker_appinsights_name: kv-worker-insights-acc
keyvault_worker_email_name: kv-worker-email-acc
keyvault_webapi_name: keyvault-webapi-prd
keyvault_worker_appinsights_name: keyvault-worker-appinsights-prd
keyvault_worker_email_name: keyvault-worker-email-prd
# Key vault definition
resource "azurerm_key_vault" "kv_webapi" {
name = local.settings.keyvault_webapi_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
enabled_for_disk_encryption = false
enabled_for_template_deployment = true
tenant_id = local.settings.tenant_id
soft_delete_enabled = true
soft_delete_retention_days = 7
purge_protection_enabled = false
sku_name = "standard"
}
# Access policy
resource "azurerm_key_vault_access_policy" "ap_webapi_admin" {
for_each = local.settings.kv_allow
key_vault_id = azurerm_key_vault.kv_webapi.id
tenant_id = local.settings.tenant_id
object_id = each.value.object_id
secret_permissions = each.value.secret_permissions
}
# Key vault entries
resource "azurerm_key_vault_secret" "kvs_webapi_appinsights" {
name = "ApplicationInsights--InstrumentationKey"
value = azurerm_application_insights.ai.instrumentation_key
key_vault_id = azurerm_key_vault.kv_webapi.id
depends_on = [azurerm_key_vault_access_policy.ap_webapi_admin]
}
resource "azurerm_key_vault_secret" "kvs_webapi_storage" {
name = "StorageSettings--ConnectionString"
value = azurerm_storage_account.sa.primary_connection_string
key_vault_id = azurerm_key_vault.kv_webapi.id
depends_on = [azurerm_key_vault_access_policy.ap_webapi_admin]
}
resource "azurerm_key_vault_secret" "kvs_webapi_eventhub" {
name = "EventHubSettings--ConnectionString"
value = azurerm_eventhub_authorization_rule.notifications_notifier_send.primary_connection_string
key_vault_id = azurerm_key_vault.kv_webapi.id
depends_on = [azurerm_key_vault_access_policy.ap_webapi_admin]
}
# Key vault definition
resource "azurerm_key_vault" "kv_worker_appinsights" {
name = local.settings.keyvault_worker_appinsights_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
enabled_for_disk_encryption = false
tenant_id = data.azurerm_client_config.cc.tenant_id
sku_name = "standard"
}
resource "azurerm_key_vault_access_policy" "ap_worker_appinsights_admin" {
key_vault_id = azurerm_key_vault.kv_worker_appinsights.id
tenant_id = data.azurerm_client_config.cc.tenant_id
object_id = data.azurerm_client_config.cc.object_id
secret_permissions = [
"get",
"list",
"set",
"delete",
"recover",
"backup",
"restore"
]
}
# Key vault entries
resource "azurerm_key_vault_secret" "kvs_worker_appinsights_appinsights" {
name = "ApplicationInsights--InstrumentationKey"
value = azurerm_application_insights.ai.instrumentation_key
key_vault_id = azurerm_key_vault.kv_worker_appinsights.id
depends_on = [azurerm_key_vault_access_policy.ap_worker_appinsights_admin]
}
resource "azurerm_key_vault_secret" "kvs_worker_appinsights_eventhub" {
name = "EventHubSettings--ConnectionString"
value = azurerm_eventhub_authorization_rule.notifications_notifier_listen.primary_connection_string
key_vault_id = azurerm_key_vault.kv_worker_appinsights.id
depends_on = [azurerm_key_vault_access_policy.ap_worker_appinsights_admin]
}
# Key vault definition
resource "azurerm_key_vault" "kv_worker_email" {
name = local.settings.keyvault_worker_email_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
enabled_for_disk_encryption = false
tenant_id = data.azurerm_client_config.cc.tenant_id
sku_name = "standard"
}
resource "azurerm_key_vault_access_policy" "ap_worker_email_admin" {
key_vault_id = azurerm_key_vault.kv_worker_email.id
tenant_id = data.azurerm_client_config.cc.tenant_id
object_id = data.azurerm_client_config.cc.object_id
secret_permissions = [
"get",
"list",
"set",
"delete",
"recover",
"backup",
"restore"
]
}
# Key vault entries
resource "azurerm_key_vault_secret" "kvs_worker_email_appinsights" {
name = "ApplicationInsights--InstrumentationKey"
value = azurerm_application_insights.ai.instrumentation_key
key_vault_id = azurerm_key_vault.kv_worker_email.id
depends_on = [azurerm_key_vault_access_policy.ap_worker_email_admin]
}
resource "azurerm_key_vault_secret" "kvs_worker_email_eventhub" {
name = "EventHubSettings--ConnectionString"
value = azurerm_eventhub_authorization_rule.notifications_notifier_listen.primary_connection_string
key_vault_id = azurerm_key_vault.kv_worker_email.id
depends_on = [azurerm_key_vault_access_policy.ap_worker_email_admin]
}
terraform plan
terraform apply
image by author
image by author

Conclusion

Preview

like to create

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store