Creación de un controlador de entrada de Application Gateway en Azure Kubernetes Service

Azure Kubernetes Service (AKS) administra el entorno hospedado de Kubernetes. AKS facilita y acelera la implementación y administración de aplicaciones en contenedores sin necesidad de tener conocimientos de orquestación de contenedores. AKS también elimina la carga de dejar las aplicaciones sin conexión para las tareas funcionales y de mantenimiento. Con AKS, estas tareas, incluidos el aprovisionamiento, la actualización y el escalado de recursos, se pueden realizar a petición.

Un controlador de entrada proporciona varias características para los servicios de Kubernetes. Estas características incluyen el proxy inverso, el enrutamiento de tráfico configurable y la terminación de TLS. Los recursos de entrada de Kubernetes se usan para configurar las reglas de entrada de los distintos servicios de Kubernetes. Mediante reglas de entrada y un controlador de entrada, una sola dirección IP puede enrutar el tráfico a varios servicios en un clúster de Kubernetes. Azure Application Gateway proporciona todas las funcionalidades anteriores, lo que convierte este servicio en un controlador de entrada perfecto para Kubernetes en Azure.

En este artículo, aprenderá a:

  • Crear un clúster de Kubernetes con AKS y Application Gateway como controlador de entrada
  • Definición de un clúster de Kubernetes
  • Crear un recurso de Application Gateway
  • Creación de un clúster de Kubernetes
  • Probar la disponibilidad de un clúster de Kubernetes

1. Configurar su entorno

  • Suscripción de Azure: Si no tiene una suscripción a Azure, cree una cuenta gratuita antes de empezar.
  • Entidad de servicio de Azure: si aún no tiene una entidad de servicio, cree una. Anote los valores de appId, displayName, password y tenant.

  • Identificador de objeto de entidad deservicio: ejecute el siguiente comando para obtener el identificador de objeto de la entidad de servicio:

2. Configuración de una cuenta de almacenamiento de Azure para almacenar el estado de Terraform

Terraform realiza un seguimiento del estado de manera local a través del archivo terraform.tfstate. Este patrón funciona bien en un entorno de persona único. Sin embargo, en un entorno más práctico de varias personas, debe realizar el seguimiento del estado en el servidor mediante Azure Storage. En esta sección aprenderá a recuperar la información de la cuenta de almacenamiento necesaria y a crear un contenedor de almacenamiento. La información de estado Terraform se almacena en ese contenedor.

  1. Vaya a Azure Portal.

  2. En Servicios de Azure, seleccione Cuentas de almacenamiento. (Si la opción Cuentas de almacenamiento no está visible en la página principal, seleccione Más servicios).

  3. En la página Cuentas de almacenamiento, seleccione el nombre de la cuenta de almacenamiento en la que Terraform va a almacenar el estado. Por ejemplo, puede utilizar la cuenta de almacenamiento creada cuando abrió Cloud Shell por primera vez. El nombre de cuenta de almacenamiento creada por Cloud Shell normalmente comienza por cs seguido de una cadena aleatoria de números y letras.

  4. Anote el nombre de la cuenta de almacenamiento seleccionada.

  5. En la página Cuenta de almacenamiento, seleccione Claves de acceso.

    Menú de la cuenta de almacenamiento

  6. Anote el valor de clave key1. (Al seleccionar el icono a la derecha de la clave, se copia el valor al portapapeles).

    Claves de acceso de cuenta de almacenamiento

  7. Cree un contenedor en su cuenta de Azure Storage. Reemplace los marcadores de posición por los valores adecuados para su cuenta de Azure Storage.

    az storage container create -n tfstate --account-name <storage_account_name> --account-key <storage_account_key>
    

3. Implementación del código de Terraform

  1. Cree un directorio en el que probar el código de ejemplo de Terraform y conviértalo en el directorio actual.

  2. Cree un archivo llamado main.tf e inserte el siguiente código:

    terraform {
    
      required_version = ">=0.12"
    
      required_providers {
        azurerm = {
          source = "hashicorp/azurerm"
          version = "~>2.0"
        }
      }
      backend "azurerm" {
        resource_group_name = var.resource_group_name
        storage_account_name = var.storage_account_name
        container_name = "tfstate"
        key = "codelab.microsoft.tfstate"
      }
      }
    
      provider "azurerm" {
      features {}
    }
    
  3. Cree un archivo llamado variables.tf que contenga las variables del proyecto e inserte el código siguiente:

    variable "resource_group_name" {
        description = "Name of the resource group."
    }
    
    variable "location" {
        description = "Location of the cluster."
    }
    
    variable "aks_service_principal_app_id" {
        description = "Application ID/Client ID  of the service principal. Used by AKS to manage AKS related resources on Azure like vms, subnets."
    }
    
    variable "aks_service_principal_client_secret" {
        description = "Secret of the service principal. Used by AKS to manage Azure."
    }
    
    variable "aks_service_principal_object_id" {
        description = "Object ID of the service principal."
    }
    
    variable "virtual_network_name" {
        description = "Virtual network name"
        default     = "aksVirtualNetwork"
    }
    
    variable "virtual_network_address_prefix" {
        description = "VNET address prefix"
        default     = "15.0.0.0/8"
    }
    
    variable "aks_subnet_name" {
        description = "Subnet Name."
        default     = "kubesubnet"
    }
    
    variable "aks_subnet_address_prefix" {
        description = "Subnet address prefix."
        default     = "15.0.0.0/16"
    }
    
    variable "app_gateway_subnet_address_prefix" {
        description = "Subnet server IP address."
        default     = "15.1.0.0/16"
    }
    
    variable "app_gateway_name" {
        description = "Name of the Application Gateway"
        default = "ApplicationGateway1"
    }
    
    variable "app_gateway_sku" {
        description = "Name of the Application Gateway SKU"
        default = "Standard_v2"
    }
    
    variable "app_gateway_tier" {
        description = "Tier of the Application Gateway tier"
        default = "Standard_v2"
    }
    
    variable "aks_name" {
        description = "AKS cluster name"
        default     = "aks-cluster1"
    }
    variable "aks_dns_prefix" {
        description = "Optional DNS prefix to use with hosted Kubernetes API server FQDN."
        default     = "aks"
    }
    
    variable "aks_agent_os_disk_size" {
        description = "Disk size (in GB) to provision for each of the agent pool nodes. This value ranges from 0 to 1023. Specifying 0 applies the default disk size for that agentVMSize."
        default     = 40
    }
    
    variable "aks_agent_count" {
        description = "The number of agent nodes for the cluster."
        default     = 3
    }
    
    variable "aks_agent_vm_size" {
        description = "VM size"
        default     = "Standard_D3_v2"
    }
    
    variable "kubernetes_version" {
        description = "Kubernetes version"
        default     = "1.11.5"
    }
    
    variable "aks_service_cidr" {
        description = "CIDR notation IP range from which to assign service cluster IPs"
        default     = "10.0.0.0/16"
    }
    
    variable "aks_dns_service_ip" {
        description = "DNS server IP address"
        default     = "10.0.0.10"
    }
    
    variable "aks_docker_bridge_cidr" {
        description = "CIDR notation IP for Docker bridge."
        default     = "172.17.0.1/16"
    }
    
    variable "aks_enable_rbac" {
        description = "Enable RBAC on the AKS cluster. Defaults to false."
        default     = "false"
    }
    
    variable "vm_user_name" {
        description = "User name for the VM"
        default     = "vmuser1"
    }
    
    variable "public_ssh_key_path" {
        description = "Public key path for SSH."
        default     = "~/.ssh/id_rsa.pub"
    }
    
    variable "tags" {
        type = map(string)
    
        default = {
        source = "terraform"
        }
    }
    
    variable "storage_account_name" {
        description = "Name of storage account"
    }
    
  4. Cree un archivo llamado resources.tf e inserte el siguiente código.

    # # Locals block for hardcoded names. 
    locals {
        backend_address_pool_name      = "${azurerm_virtual_network.test.name}-beap"
        frontend_port_name             = "${azurerm_virtual_network.test.name}-feport"
        frontend_ip_configuration_name = "${azurerm_virtual_network.test.name}-feip"
        http_setting_name              = "${azurerm_virtual_network.test.name}-be-htst"
        listener_name                  = "${azurerm_virtual_network.test.name}-httplstn"
        request_routing_rule_name      = "${azurerm_virtual_network.test.name}-rqrt"
        app_gateway_subnet_name = "appgwsubnet"
    }
    
    data "azurerm_resource_group" "rg" {
        name = var.resource_group_name
    }
    
    # User Assigned Identities 
    resource "azurerm_user_assigned_identity" "testIdentity" {
        resource_group_name = data.azurerm_resource_group.rg.name
        location            = data.azurerm_resource_group.rg.location
    
        name = "identity1"
    
        tags = var.tags
    }
    
    resource "azurerm_virtual_network" "test" {
        name                = var.virtual_network_name
        location            = data.azurerm_resource_group.rg.location
        resource_group_name = data.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           = "appgwsubnet"
        address_prefix = var.app_gateway_subnet_address_prefix
        }
    
        tags = var.tags
    }
    
    data "azurerm_subnet" "kubesubnet" {
        name                 = var.aks_subnet_name
        virtual_network_name = azurerm_virtual_network.test.name
        resource_group_name  = data.azurerm_resource_group.rg.name
        depends_on = [azurerm_virtual_network.test]
    }
    
    data "azurerm_subnet" "appgwsubnet" {
        name                 = "appgwsubnet"
        virtual_network_name = azurerm_virtual_network.test.name
        resource_group_name  = data.azurerm_resource_group.rg.name
        depends_on = [azurerm_virtual_network.test]
    }
    
    # Public Ip 
    resource "azurerm_public_ip" "test" {
        name                         = "publicIp1"
        location                     = data.azurerm_resource_group.rg.location
        resource_group_name          = data.azurerm_resource_group.rg.name
        allocation_method            = "Static"
        sku                          = "Standard"
    
        tags = var.tags
    }
    
    resource "azurerm_application_gateway" "network" {
        name                = var.app_gateway_name
        resource_group_name = data.azurerm_resource_group.rg.name
        location            = data.azurerm_resource_group.rg.location
    
        sku {
        name     = var.app_gateway_sku
        tier     = "Standard_v2"
        capacity = 2
        }
    
        gateway_ip_configuration {
        name      = "appGatewayIpConfig"
        subnet_id = data.azurerm_subnet.appgwsubnet.id
        }
    
        frontend_port {
        name = local.frontend_port_name
        port = 80
        }
    
        frontend_port {
        name = "httpsPort"
        port = 443
        }
    
        frontend_ip_configuration {
        name                 = local.frontend_ip_configuration_name
        public_ip_address_id = azurerm_public_ip.test.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
        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
        }
    
        tags = var.tags
    
        depends_on = [azurerm_virtual_network.test, azurerm_public_ip.test]
    }
    
    resource "azurerm_role_assignment" "ra1" {
        scope                = data.azurerm_subnet.kubesubnet.id
        role_definition_name = "Network Contributor"
        principal_id         = var.aks_service_principal_object_id 
    
        depends_on = [azurerm_virtual_network.test]
    }
    
    resource "azurerm_role_assignment" "ra2" {
        scope                = azurerm_user_assigned_identity.testIdentity.id
        role_definition_name = "Managed Identity Operator"
        principal_id         = var.aks_service_principal_object_id
        depends_on           = [azurerm_user_assigned_identity.testIdentity]
    }
    
    resource "azurerm_role_assignment" "ra3" {
        scope                = azurerm_application_gateway.network.id
        role_definition_name = "Contributor"
        principal_id         = azurerm_user_assigned_identity.testIdentity.principal_id
        depends_on           = [azurerm_user_assigned_identity.testIdentity, azurerm_application_gateway.network]
    }
    
    resource "azurerm_role_assignment" "ra4" {
        scope                = data.azurerm_resource_group.rg.id
        role_definition_name = "Reader"
        principal_id         = azurerm_user_assigned_identity.testIdentity.principal_id
        depends_on           = [azurerm_user_assigned_identity.testIdentity, azurerm_application_gateway.network]
    }
    
    resource "azurerm_kubernetes_cluster" "k8s" {
        name       = var.aks_name
        location   = data.azurerm_resource_group.rg.location
        dns_prefix = var.aks_dns_prefix
    
        resource_group_name = data.azurerm_resource_group.rg.name
    
        linux_profile {
        admin_username = var.vm_user_name
    
        ssh_key {
            key_data = file(var.public_ssh_key_path)
        }
        }
    
        addon_profile {
        http_application_routing {
            enabled = false
        }
        }
    
        default_node_pool {
        name            = "agentpool"
        node_count      = var.aks_agent_count
        vm_size         = var.aks_agent_vm_size
        os_disk_size_gb = var.aks_agent_os_disk_size
        vnet_subnet_id  = data.azurerm_subnet.kubesubnet.id
        }
    
        service_principal {
        client_id     = var.aks_service_principal_app_id
        client_secret = var.aks_service_principal_client_secret
        }
    
        network_profile {
        network_plugin     = "azure"
        dns_service_ip     = var.aks_dns_service_ip
        docker_bridge_cidr = var.aks_docker_bridge_cidr
        service_cidr       = var.aks_service_cidr
        }
    
        role_based_access_control {
        enabled = var.aks_enable_rbac
        }
    
        depends_on = [azurerm_virtual_network.test, azurerm_application_gateway.network]
        tags       = var.tags
    }
    

    Puntos clave:

    • El código del archivo resources.tf establece el nombre del clúster, la ubicación y el nombre del grupo de recursos.
    • Se establece el valor dns_prefix, que forma parte del nombre de dominio completo (FQDN) utilizado para acceder al clúster.
    • El registro linux_profile permite configurar las opciones que permiten iniciar sesión en los nodos de trabajo mediante SSH.
    • Con AKS, solo se paga por los nodos de trabajo.
    • El registro agent_pool_profile configura los detalles de estos nodos de trabajo.
    • El registro agent_pool_profile record incluye el número de nodos de trabajo que se van a crear y el tipo de estos.
    • Si necesita ampliar o reducir verticalmente el clúster en el futuro, modifique el valor de count en el registro agent_pool_profile record.
  5. Cree un archivo llamado output.tf e inserte el siguiente código.

    output "client_key" {
        value = azurerm_kubernetes_cluster.k8s.kube_config.0.client_key
    }
    
    output "client_certificate" {
        value = azurerm_kubernetes_cluster.k8s.kube_config.0.client_certificate
    }
    
    output "cluster_ca_certificate" {
        value = azurerm_kubernetes_cluster.k8s.kube_config.0.cluster_ca_certificate
    }
    
    output "cluster_username" {
        value = azurerm_kubernetes_cluster.k8s.kube_config.0.username
    }
    
    output "cluster_password" {
        value = azurerm_kubernetes_cluster.k8s.kube_config.0.password
    }
    
    output "kube_config" {
        value = azurerm_kubernetes_cluster.k8s.kube_config_raw
        sensitive = true
    }
    
    output "host" {
        value = azurerm_kubernetes_cluster.k8s.kube_config.0.host
    }
    
    output "identity_resource_id" {
        value = azurerm_user_assigned_identity.testIdentity.id
    }
    
    output "identity_client_id" {
        value = azurerm_user_assigned_identity.testIdentity.client_id
    }
    

    Puntos clave:

    • Las salidas de Terraform permiten definir valores que se resaltan al usuario cuando Terraform aplica un plan.
    • Estos valores pueden consultarse con el comando terraform output.
    • En esta sección, va a crear un archivo de salida que permita el acceso al clúster con kubectl.
  6. Cree un archivo llamado terraform.tfvars e inserte el siguiente código.

    resource_group_name = "<Name of the Resource Group already created>"
    
    location = "<Location of the Resource Group>"
        
    aks_service_principal_app_id = "<Service Principal AppId>"
        
    aks_service_principal_client_secret = "<Service Principal Client Secret>"
        
    aks_service_principal_object_id = "<Service Principal Object Id>"
    

    Puntos clave:

    • Para obtener el valor de ubicación del entorno, ejecute az account list-locations.

4. Prueba del clúster de Kubernetes

Las herramientas de Kubernetes se pueden usar para comprobar el clúster recién creado.

  1. Ejecute az aks get-credentials para obtener la configuración de Kubernetes y acceder a las credenciales desde Azure.

    az aks get-credentials --name <aks_cluster_name>  --resource-group <resource_group_name>
    
  2. Compruebe el mantenimiento del clúster.

    kubectl get nodes
    

    Puntos clave:

    • Los detalles de los nodos de trabajo se muestran con el estado Ready (Listo).

    La herramienta kubectl le permite comprobar el mantenimiento de su clúster de Kubernetes

5. Instalación de Azure AD Pod Identity

Azure Active Directory Pod Identity proporciona acceso basado en token a Azure Resource Manager.

Azure AD Pod Identity agrega los siguientes componentes al clúster de Kubernetes:

Si RBAC está Habilitado, ejecute el siguiente comando para instalar Azure AD Pod Identity en el clúster:

kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml

Si RBAC está Deshabilitado, ejecute el siguiente comando para instalar Azure AD Pod Identity en el clúster:

kubectl create -f https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment.yaml

6. Instalación de Helm 6

El código de esta sección usa Helm (administrador de paquetes de Kubernetes) para instalar el paquete:

Ejecute los siguientes comandos de Helm para agregar el repositorio de Helm de AGIC:

helm repo add application-gateway-kubernetes-ingress https://appgwingress.blob.core.windows.net/ingress-azure-helm-package/
helm repo update

7. Instalación del gráfico de Helm del controlador de entrada

  1. Descargue helm-config.yaml para configurar AGIC:

    wget https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/sample-helm-config.yaml -O helm-config.yaml
    
  2. Edite helm-config.yaml y escriba los valores adecuados para las secciones appgw y armAuth.

    code helm-config.yaml
    

    Los valores se describen de la siguiente manera:

    • verbosityLevel: establece el nivel de detalle de la infraestructura de registro de AGIC. Consulte Niveles de registro para los valores posibles.
    • appgw.subscriptionId: identificador de suscripción de Azure para la instancia de Application Gateway. Ejemplo: a123b234-a3b4-557d-b2df-a0bc12de1234
    • appgw.resourceGroup: nombre del grupo de recursos de Azure donde se creó Application Gateway.
    • appgw.name: nombre de la instancia de Application Gateway. Ejemplo: applicationgateway1.
    • appgw.shared: se debe establecer el valor predeterminado de esta marca booleana en false. Establezca en true si necesita un truecompartido.
    • kubernetes.watchNamespace: especifica el espacio de nombres que debe ver AGIC. El espacio de nombres puede ser un valor de cadena único o una lista separada por comas de espacios de nombres. Si marca esta variable como comentario o la establece como una cadena en blanco o vacía, el controlador de entrada que observa todos los espacios de nombres accesibles.
    • armAuth.type: valor de aadPodIdentity o servicePrincipal.
    • armAuth.identityResourceID: identificador de recurso de la identidad administrada.
    • armAuth.identityClientId: identificador de cliente de la identidad.
    • armAuth.secretJSON: solo es necesario cuando se elige el tipo de secreto de entidad de servicio (cuando se establece armAuth.type en servicePrincipal).

    Puntos clave:

    • El valor identityResourceID se crea en el script de Terraform y se encuentra al ejecutar: echo "$(terraform output identity_resource_id)".
    • El valor identityClientID se crea en el script de Terraform y se encuentra al ejecutar: echo "$(terraform output identity_client_id)".
    • El valor <resource-group> es el grupo de recursos de la instancia de Application Gateway.
    • El valor <identity-name> es el nombre de la identidad creada.
    • Todas las identidades de una suscripción determinada se pueden enumerar mediante: az identity list.
  3. Instale el paquete del controlador de entrada de Application Gateway:

    helm install -f helm-config.yaml application-gateway-kubernetes-ingress/ingress-azure --generate-name
    

8. Instalación de una aplicación de ejemplo

Una vez que haya instalado Application Gateway, AKS y AGIC, puede instalar una aplicación de ejemplo.

  1. Use el comando curl para descargar el archivo YAML:

    curl https://raw.githubusercontent.com/Azure/application-gateway-kubernetes-ingress/master/docs/examples/aspnetapp.yaml -o aspnetapp.yaml
    
  2. Aplique el archivo YAML:

    kubectl apply -f aspnetapp.yaml
    

9. Limpieza de recursos

Cuando ya no necesite los recursos creados a través de Terraform, realice los pasos siguientes:

  1. Ejecute el plan de Terraform y especifique la marca.

    terraform plan -destroy -out main.destroy.tfplan
    

    Puntos clave:

    • El comando terraform plan crea un plan de ejecución, pero no lo ejecuta. En su lugar, determina qué acciones son necesarias para crear la configuración especificada en los archivos de configuración. Este patrón le permite comprobar si el plan de ejecución coincide con sus expectativas antes de realizar cambios en los recursos reales.
    • El parámetro -out opcional permite especificar un archivo de salida para el plan. El uso del parámetro -out garantiza que el plan que ha revisado es exactamente lo que se aplica.
    • Para más información acerca de la seguridad y conservar los planes de ejecución, consulte la sección Advertencia de seguridad.
  2. Ejecute terraform apply para aplicar el plan de ejecución.

    terraform apply main.destroy.tfplan
    

Solución de problemas de Terraform en Azure

Solución de problemas comunes al usar Terraform en Azure

Pasos siguientes