Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Azure Kubernetes Service (AKS) manages your hosted Kubernetes environment. AKS makes it quick and easy to deploy and manage containerized applications without container orchestration expertise. AKS also eliminates the burden of taking applications offline for operational and maintenance tasks. With AKS, you can provision, upgrade, and scale resources on-demand.
Azure Application Gateway provides Application Gateway Ingress Controller (AGIC). AGIC enables various features for Kubernetes services, including reverse proxy, configurable traffic routing, and TLS termination. Kubernetes Ingress resources help configure the ingress rules for individual Kubernetes services. An ingress controller allows a single IP address to route traffic to multiple services in a Kubernetes cluster.
Terraform enables the definition, preview, and deployment of cloud infrastructure. Using Terraform, you create configuration files using HCL syntax. The HCL syntax allows you to specify the cloud provider - such as Azure - and the elements that make up your cloud infrastructure. After you create your configuration files, you create an execution plan that allows you to preview your infrastructure changes before they're deployed. Once you verify the changes, you apply the execution plan to deploy the infrastructure.
In this article, you learn how to:
- Create a random value for the Azure resource group name using random_pet.
- Create an Azure resource group using azurerm_resource_group.
- Create a User Assigned Identity using azurerm_user_assigned_identity.
- Create a virtual network (VNet) using azurerm_virtual_network.
- Create a subnet using azurerm_subnet.
- Create a public IP using azurerm_public_ip.
- Create a Application Gateway using azurerm_application_gateway.
- Create a Kubernetes cluster using azurerm_kubernetes_cluster.
- Install and run a sample web app to test the availability of the Kubernetes cluster you create.
Before you get started, you need to install and configure the following tools:
Note
You can find the sample code from this article in the Azure Terraform GitHub repo. You can view the log file containing the test results from current and previous versions of Terraform.
For more information, see articles and sample code showing how to use Terraform to manage Azure resources.
- Create a directory to test sample Terraform code and make it your working directory.
- Create a file named
providers.tf
and copy in the following code:
terraform {
required_version = ">=1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
}
}
provider "azurerm" {
features {}
}
Create a file named
main.tf
and copy in the following code:resource "random_pet" "rg_name" { prefix = var.resource_group_name_prefix } resource "azurerm_resource_group" "rg" { name = random_pet.rg_name.id location = var.resource_group_location } # Locals block for hardcoded names locals { backend_address_pool_name = "${azurerm_virtual_network.vnet.name}-beap" frontend_port_name = "${azurerm_virtual_network.vnet.name}-feport" frontend_ip_configuration_name = "${azurerm_virtual_network.vnet.name}-feip" http_setting_name = "${azurerm_virtual_network.vnet.name}-be-htst" listener_name = "${azurerm_virtual_network.vnet.name}-httplstn" request_routing_rule_name = "${azurerm_virtual_network.vnet.name}-rqrt" } # Subnets data "azurerm_subnet" "kubesubnet" { name = var.aks_subnet_name virtual_network_name = azurerm_virtual_network.vnet.name resource_group_name = azurerm_resource_group.rg.name } data "azurerm_subnet" "appgwsubnet" { name = var.appgw_subnet_name virtual_network_name = azurerm_virtual_network.vnet.name resource_group_name = azurerm_resource_group.rg.name } data "azurerm_user_assigned_identity" "ingress" { name = "ingressapplicationgateway-${azurerm_kubernetes_cluster.aks.name}" resource_group_name = azurerm_kubernetes_cluster.aks.node_resource_group } # Virtual network (vnet) resource "azurerm_virtual_network" "vnet" { name = var.virtual_network_name location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name address_space = [var.virtual_network_address_prefix] subnet { name = var.aks_subnet_name address_prefix = var.aks_subnet_address_prefix } subnet { name = var.appgw_subnet_name address_prefix = var.app_gateway_subnet_address_prefix } } resource "azurerm_user_assigned_identity" "aks" { name = "aks-${var.aks_cluster_name}" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location } # AKS cluster resource "azurerm_kubernetes_cluster" "aks" { name = var.aks_cluster_name location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name dns_prefix = var.aks_cluster_name private_cluster_enabled = var.aks_private_cluster role_based_access_control_enabled = var.aks_enable_rbac sku_tier = var.aks_sku_tier default_node_pool { name = "agentpool" node_count = var.aks_node_count vm_size = var.aks_vm_size os_disk_size_gb = var.aks_os_disk_size max_pods = 100 vnet_subnet_id = data.azurerm_subnet.kubesubnet.id } identity { type = "UserAssigned" identity_ids = [azurerm_user_assigned_identity.aks.id] } network_profile { network_plugin = "azure" dns_service_ip = var.aks_dns_service_ip service_cidr = var.aks_service_cidr } ingress_application_gateway { gateway_id = azurerm_application_gateway.appgw.id } depends_on = [ azurerm_application_gateway.appgw ] } resource "azurerm_public_ip" "pip" { name = "appgw-pip" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location allocation_method = "Static" sku = "Standard" } resource "azurerm_application_gateway" "appgw" { name = var.app_gateway_name resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location sku { name = var.app_gateway_tier tier = var.app_gateway_tier capacity = 1 } gateway_ip_configuration { name = "appGatewayIpConfig" subnet_id = data.azurerm_subnet.appgwsubnet.id } frontend_port { name = local.frontend_port_name port = 80 } frontend_ip_configuration { name = local.frontend_ip_configuration_name public_ip_address_id = azurerm_public_ip.pip.id } backend_address_pool { name = local.backend_address_pool_name } backend_http_settings { name = local.http_setting_name cookie_based_affinity = "Disabled" port = 80 protocol = "Http" request_timeout = 1 } http_listener { name = local.listener_name frontend_ip_configuration_name = local.frontend_ip_configuration_name frontend_port_name = local.frontend_port_name protocol = "Http" } request_routing_rule { name = local.request_routing_rule_name priority = 1 rule_type = "Basic" http_listener_name = local.listener_name backend_address_pool_name = local.backend_address_pool_name backend_http_settings_name = local.http_setting_name } # Since this sample is creating an Application Gateway # that is later managed by an Ingress Controller, there is no need # to create a backend address pool (BEP). However, the BEP is still # required by the resource. Therefore, "lifecycle:ignore_changes" is # used to prevent TF from managing the gateway. lifecycle { ignore_changes = [ tags, backend_address_pool, backend_http_settings, http_listener, probe, request_routing_rule, ] } } # Role assignments resource "azurerm_role_assignment" "ra1" { scope = azurerm_resource_group.rg.id role_definition_name = "Reader" principal_id = data.azurerm_user_assigned_identity.ingress.principal_id } resource "azurerm_role_assignment" "ra2" { scope = azurerm_virtual_network.vnet.id role_definition_name = "Network Contributor" principal_id = data.azurerm_user_assigned_identity.ingress.principal_id } resource "azurerm_role_assignment" "ra3" { scope = azurerm_application_gateway.appgw.id role_definition_name = "Contributor" principal_id = data.azurerm_user_assigned_identity.ingress.principal_id }
Create a file named
variables.tf
and copy in the following code:variable "resource_group_location" { type = string default = "eastus" description = "Location for all resources." } variable "resource_group_name_prefix" { type = string default = "rg" description = "Prefix of the resource group name that's combined with a random value so name is unique in your Azure subscription." } variable "virtual_network_name" { type = string description = "Virtual network name." default = "aksVirtualNetwork" } variable "virtual_network_address_prefix" { type = string description = "VNET address prefix." default = "10.1.0.0/18" } variable "aks_subnet_name" { type = string description = "Name of the subset." default = "akssubnet" } variable "appgw_subnet_name" { type = string description = "Name of the subset." default = "appgwsubnet" } variable "aks_cluster_name" { type = string description = "The name of the Managed Kubernetes Cluster to create." default = "aks-cluster" } variable "aks_os_disk_size" { type = number description = "(Optional) The size of the OS Disk which should be used for each agent in the Node Pool." default = 50 } variable "aks_node_count" { type = number description = "(Optional) The initial number of nodes which should exist in this Node Pool." default = 3 } variable "aks_sku_tier" { type = string description = "(Optional) The SKU tier that should be used for this Kubernetes Cluster. Possible values are Free and Paid (which includes the Uptime SLA)." default = "Free" validation { condition = contains(["Free", "Paid"], var.aks_sku_tier) error_message = "Invalid SKU tier. The value should be one of the following: 'Free','Paid'." } } variable "aks_vm_size" { type = string description = "The size of the virtual machine." default = "Standard_D3_v2" } variable "kubernetes_version" { type = string description = "(Optional) Version of Kubernetes specified when creating the AKS managed cluster." default = "1.19.11" } variable "aks_service_cidr" { type = string description = "(Optional) The Network Range used by the Kubernetes service." default = "192.168.0.0/20" } variable "aks_dns_service_ip" { type = string description = "(Optional) IP address within the Kubernetes service address range that will be used by cluster service discovery (kube-dns)." default = "192.168.0.10" } variable "aks_private_cluster" { type = bool description = "(Optional) Should this Kubernetes Cluster have its API server only exposed on internal IP addresses? This provides a Private IP Address for the Kubernetes API on the Virtual Network where the Kubernetes Cluster is located." default = false } variable "aks_subnet_address_prefix" { description = "Subnet address prefix." type = string default = "10.1.0.0/22" } variable "app_gateway_subnet_address_prefix" { type = string description = "Subnet address prefix." default = "10.1.4.0/24" } variable "app_gateway_name" { description = "Name of the Application Gateway" type = string default = "ApplicationGateway1" } variable "app_gateway_tier" { description = "Tier of the Application Gateway tier." type = string default = "Standard_v2" } variable "aks_enable_rbac" { description = "(Optional) Is Role Based Access Control based on Azure AD enabled?" type = bool default = false }
Create a file named
outputs.tf
and copy in the following code:output "resource_group_name" { value = azurerm_resource_group.rg.name } output "aks_cluster_name" { value = azurerm_kubernetes_cluster.aks.name } output "application_gateway_name" { value = azurerm_application_gateway.appgw.name } output "identity_name" { value = azurerm_user_assigned_identity.aks.name } output "identity_resource_id" { value = azurerm_user_assigned_identity.aks.id } output "identity_client_id" { value = azurerm_user_assigned_identity.aks.client_id } output "application_ip_address" { value = azurerm_public_ip.pip.ip_address } output "client_key" { value = azurerm_kubernetes_cluster.aks.kube_config.0.client_key sensitive = true } output "client_certificate" { value = azurerm_kubernetes_cluster.aks.kube_config.0.client_certificate sensitive = true } output "cluster_ca_certificate" { value = azurerm_kubernetes_cluster.aks.kube_config.0.cluster_ca_certificate sensitive = true } output "cluster_username" { value = azurerm_kubernetes_cluster.aks.kube_config.0.username sensitive = true } output "cluster_password" { value = azurerm_kubernetes_cluster.aks.kube_config.0.password sensitive = true } output "kube_config" { value = azurerm_kubernetes_cluster.aks.kube_config_raw sensitive = true } output "host" { value = azurerm_kubernetes_cluster.aks.kube_config.0.host sensitive = true }
Run terraform init to initialize the Terraform deployment. This command downloads the Azure provider required to manage your Azure resources.
terraform init -upgrade
Key points:
- The
-upgrade
parameter upgrades the necessary provider plugins to the newest version that complies with the configuration's version constraints.
Run terraform plan to create an execution plan.
terraform plan -out main.tfplan
Key points:
- The
terraform plan
command creates an execution plan, but doesn't execute it. Instead, it determines what actions are necessary to create the configuration specified in your configuration files. This pattern allows you to verify whether the execution plan matches your expectations before making any changes to actual resources. - The optional
-out
parameter allows you to specify an output file for the plan. Using the-out
parameter ensures that the plan you reviewed is exactly what is applied. - To read more about persisting execution plans and security, see the security warning section.
Run terraform apply to apply the execution plan to your cloud infrastructure.
terraform apply main.tfplan
Key points:
- The example
terraform apply
command assumes you previously ranterraform plan -out main.tfplan
. - If you specified a different filename for the
-out
parameter, use that same filename in the call toterraform apply
. - If you didn't use the
-out
parameter, callterraform apply
without any parameters.
Get the Azure resource group name.
resource_group_name=$(terraform output -raw resource_group_name)
Get the AKS cluster name.
aks_cluster_name=$(terraform output -raw aks_cluster_name)
Get the Kubernetes configuration and access credentials for the cluster using the
az aks get-credentials
command.az aks get-credentials \ --name $aks_cluster_name \ --resource-group $resource_group_name \ --overwrite-existing
Verify the health of the cluster using the
kubectl get
command.kubectl get nodes
Key points:
- The details of your worker nodes display with a status of Ready.
Azure Active Directory (Azure AD) Pod Identity provides token-based access to Azure Resource Manager.
Azure AD Pod Identity adds the following components to your Kubernetes cluster:
- Kubernetes CRDs:
AzureIdentity
,AzureAssignedIdentity
,AzureIdentityBinding
- Managed Identity Controller (MIC) component
- Node Managed Identity (NMI) component
To install Azure AD Pod Identity on your cluster, you need to know if RBAC is enabled or disabled. RBAC is disabled by default for this demo. Enabling or disabling RBAC is done in the variables.tf
file via the aks_enable_rbac
block's default
value.
If RBAC is enabled, run the following
kubectl create
command.kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml
If RBAC is disabled, run the following
kubectl create
command.kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment.yaml
Add the AGIC Helm repo using the
helm repo add
command.helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.chinacloudapi.cn/ingress-azure-helm-package/
Update the AGIC Helm repo using the
helm repo update
command.helm repo update
Download
helm-config.yaml
to configure AGIC using thewget
command.wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/sample-helm-config.yaml -O helm-config.yaml
Open
helm-config.yaml
in a text editor.Enter the following value for the top level keys:
verbosityLevel
: Specify the verbosity level of the AGIC logging infrastructure. For more information about logging levels, see logging Levels section of Application Gateway Kubernetes Ingress.
Enter the following values for the
appgw
block:appgw.subscriptionId
: Specify the Azure subscription ID used to create the App Gateway.appgw.resourceGroup
: Get the resource group name using theecho "$(terraform output -raw resource_group_name)"
command.appgw.name
: Get the Application Gateway name using theecho "$(terraform output -raw application_gateway_name)"
command.appgw.shared
: This boolean flag defaults tofalse
. Set it totrue
if you need a Shared App Gateway.
Enter the following value for the
kubernetes
block:kubernetes.watchNamespace
: Specify the name space, which AGIC should watch. The namespace can be a single string value or a comma-separated list of namespaces. Leaving this variable commented out or setting it to a blank or an empty string results in the Ingress controller observing all accessible namespaces.
Enter the following values for the
armAuth
block:If you specify
armAuth.type
asaadPodIdentity
:armAuth.identityResourceID
: Get the Identity resource ID by runningecho "$(terraform output -raw identity_resource_id)"
.armAuth.identityClientId
: Get the Identity client ID by runningecho "$(terraform output -raw identity_client_id)"
.
If you specify
armAuth.type
asservicePrincipal
, see Using a service principal.
Install the AGIC package using the
helm install
command.helm install -f helm-config.yaml application-gateway-kubernetes-ingress/ingress-azure --generate-name
Get the Azure resource group name.
resource_group_name=$(terraform output -raw resource_group_name)
Get the identity name.
identity_name=$(terraform output -raw identity_name)
Get the key values from your identity using the
az identity show
command.az identity show -g $resource_group_name -n $identity_name
Download the YAML file using the
curl
command.curl https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/aspnetapp.yaml -o aspnetapp.yaml
Apply the YAML file using the
kubectl apply
command.kubectl apply -f aspnetapp.yaml
Get the app IP address.
echo "$(terraform output -raw application_ip_address)"
In a browser, navigate to the IP address from the output of the previous step.
When you no longer need the resources created via Terraform, do the following steps:
Run terraform plan and specify the
destroy
flag.terraform plan -destroy -out main.destroy.tfplan
Key points:
- The
terraform plan
command creates an execution plan, but doesn't execute it. Instead, it determines what actions are necessary to create the configuration specified in your configuration files. This pattern allows you to verify whether the execution plan matches your expectations before making any changes to actual resources. - The optional
-out
parameter allows you to specify an output file for the plan. Using the-out
parameter ensures that the plan you reviewed is exactly what is applied. - To read more about persisting execution plans and security, see the security warning section.
- The
Run terraform apply to apply the execution plan.
terraform apply main.destroy.tfplan