KubernetesAzure - Implementando arquitectura de contenedores en la nube.

Eduardo estrada
Computación en la nube
La evolución de la tecnología, la descentralización de los datos, la velocidad de la innovación, adaptación a los cambio

KubernetesAzure

 

Implementando arquitectura de contenedores en la nube.

En este articulo deployaremos una aplicación en DotNet Core 3.1 dentro de un contenedor de Docker a Container Registry en Azure, creando un Cluster de Kubernetes Cluster con Terraform y Helm para el routeo interno y la administración de los certificados de open authority integrado con nuestro dominio e integrando con Azure DevOps

Introducción:

La evolución de la tecnología, la descentralización de los datos, la velocidad de la innovación, adaptación a los cambios, nos ha llevado a evolucionar en la forma en que desarrollamos el software y lo aprovisionamos.

EL paradigma de la nube nos ha llevado a pensar si la forma en que usamos los recursos es la correcta.

La arquitectura de contenedores nos permite tener muchas pequeñas instancia de los componentes de software que asumiendo están desarrollados de forma desacoplada, tenemos que desarrollar las metodologías para la correcta orquestación, para administrarlos, desplegarlos, gestionar los costos, optimizar el performance, etc.

En este tutorial veremos la forma de aprovisionar en uno de los más grandes providers de cómputo en la nube como lo es Microsoft Azure una infraestructura de contenedores con Terraform.

1. Requerimientos

En este escenario vamos a trabajar en un ambiente local de Windows usando Visual Studio Code con PowerShell como consola

Visual Studio Code https://code.visualstudio.com/

DotNet Core SDK 3.1 https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.201-windows-x64-installer

Git https://github.com/git-for-windows/git/releases/download/v2.26.2.windows.1/Git-2.26.2-64-bit.exe

Docker

https://docs.docker.com/docker-for-windows/install/

  • Docker requiere que se habilite Hypertreading en sus máquinas; verifica que Docker ese corriendo

Azure Cli

https://docs.microsoft.com/es-es/cli/azure/install-azure-cli-windows?view=azure-cli-latest#install-or-update

Kubernetes

Install-Script -Name install-kubectl -Scope CurrentUser -Force install-kubectl.ps1

Choco, para instalar Terraform

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

Terraform $ choco install terraform

2. Crear el proyecto

Crea una nueva carpeta donde instalaremos el proyecto. mkdir myproject cd myproject

Ahora crearemos una nueva aplicación usando DotNet Core y especificando la plantilla "Web App", esto les creará una carpeta con todo lo necesario para correr nuestra primera aplicación. dotnet new webapp

Ahora restauraremos la aplicación para obtener las dependencias que esta requiere. dotnet restore ./

Construimos la aplicación. dotnet build ./

Publicamos la solución, nos creará una carpeta con los binarios dentro de nuestra solución bin/Release/netcoreapp3.1/publish/ dotnet publish -c Release

SI deseamos ver nuestra solución corriendo ejecutaremos el siguiente comando y abriremos el navegador indicando la URL de localhost y el puerto requerido. dotnet run ./

3. Montamos este dentro de un contenedor de Docker en nuestra maquina local

Dentro de nuestro proyecto crearemos un archivo llamado Dockerfile New-Item -Path './Dockerfile' -ItemType File

Abrimos el archivo con Visual Studio Code code ./Dockerfile

Ahora copiaremos el siguiente código remplazando la línea de ENTRYPOINT con la dll principal de nuestro proyecto.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1

 

COPY bin/Release/netcoreapp3.1/publish/ App/

WORKDIR /App

ENTRYPOINT ["dotnet", "myproject.dll"]

 

En el código anterior la primera línea nos dice que vamos a tomar la imagen pública de Microsoft para proyectos ASPNet en DotNet Core 3.1 En el siguiente bloque de líneas especificaremos que vamos a copiar el contenido de la carpeta de publicación de nuestro proyecto, que son los binarios a la carpeta llamada App/ y finalmente ponemos ENTRYPOINT para correr "dotnet myproject.dll" al iniciar la imagen.

Ahora regresamos a la consola en la misma ruta donde se encuentra nuestro archivo Dockerfile, construiremos y etiquetaremos con la versión uno. docker build ./ --tag "myproject:1"

Con la siguiente línea veremos las imágenes que ha construido Docker docker images

Para ejecutar la imagen ejecutamos la siguiente línea: docker run -it -p 80:80 myproject:1

En la línea anterior estamos implementando la imagen dentro de un contenedor, donde el argumento -p especifica que el puerto 80 de nuestra maquina enrutara las peticiones al puerto 80 del contenedor.

Para verlo corriendo, abriremos la URL de nuestro navegador en localhost, debemos de asegurarnos que otra aplicación no este usando el puerto 80, de cualquier forma, podríamos especificar otro puerto.

Si deseamos ver los contenedores corriendo ejecutamos docker ps

4.- Azure ACR (Azure Container Registry)

Ahora veremos como conectar Azure desde nuestra terminal para crear un grupo de recursos, un Azure Container Registry (ACR) y subir nuestra imagen de Docker.

Iniciamos la sesión corriendo az login Esto nos abrirá una nueva ventana en nuestro navegador y nos solicitará el usuario y la contraseña, una vez validado podremos regresar a la terminal.

Nos mostrará las subscripciones a las que tenemos acceso, para ver la subscripción predeterminada lo podemos hacer con: az account show

Si esta no es con la que trabajaremos podemos especificarla en una variable (buena práctica) y cambiarnos estableciéndola como argumento de la siguiente forma: $subscription = "My Subscription" az account set --subscription $subscription

Ahora obtendremos el nombre, id y tenant de nuestra subscripción y la asignaremos dentro de variables porque las necesitaremos más adelante.

$subscription = az account show --query name -o tsv

$subscriptionId = az account show --query id -o tsv

$tenant = az account show --query homeTenantId -o tsv

Ahora crearemos en las variables cual será el nombre de nuestro grupo de recursos con el que vamos a trabajar, de igual forma establecemos en otra variable la ubicación geográfica como lo define Azure en este ejemplo usaremos "eastus"

$nameGrp = "myResourceGroup"

$location = "eastus"

Una vez definida creamos el grupo de recursos. az group create -l $location -n $nameGrp

 

Si entramos a portal podremos ver que se han creado correctamente.

Ahora crearemos el Azure Container Registry, (ACR) para almacenar nuestras imágenes Docker.

Define el nombre en una variable y en otra el SKU de nuestro ACR $acrname = "myacr" $acrSKU = "Standard"

Lo creamos az acr create --name $acrName --resource-group $groupName --sku $acrSKU Habilotamos la administración, esto nos permitirá conectarnos desde nuestra terminal para subir las imagenes. az acr update --name $acrName --admin-enabled true

Obtenemos la URL para hacer login y la asignamos a una variable $acrURL = $(az acr show --name $acrName --resource-group $groupName --query loginServer -o tsv)

Ahora obtenemos en distintas variables el usuario la contraseña. $acrusername = az acr credential show -n $acrname --query username $acrpassword = az acr credential show -n $acrname --query passwords[0].value

Ahora podemos hacer login o "HandShake" entre nuestra maquina y el ACR esto nos permitirá subir nuestra imagen con "push" az acr login --name $acrname --username $acrusername --password $acrpassword

Antes de hacer push debemos re-etiquetar nuestra imagen local con el nombre del ACR que la almacenará.

Recordaremos que estamos guardando todos los argumentos en variables para tenerlas disponibles durante nuestra sesión de trabajo en nuestra terminal.

$imageName = "myproject" $imageNameTag = "$imageName:1"

Ahora construiremos como debe de ir la URL del ACR más "/" más el nombre de nuestra imagen y esta será nuestra etiqueta. $imageUrl = $acrURL + "/" + $imageNameTag Etiquetamos con Docker docker tag $imageNameTag $imageUrl Hacemos Push a la imágen docker push $containerurl

Ahora podemos ver la imagen dentro del portal de Azure.

5.- Terraform

En este apartado crearemos el cluster de Kubernetes usando Terraform

Nos ubicamos en la raíz de nuestra solución y creamos una nueva carpeta donde definiremos nuestro proyecto de Terraform. cd ../ mkdir terraform cd .\terraform\

Creamos el archivo donde comenzaremos con las instrucciones de Terraform, primero el provider en este caso "azurerm" New-Item -Path './main.tf' -ItemType File

Nota: *El proyecto completo se puede ver dentro de este mismo repositorio, la URL es https://github.com/internetgdl/KubernetesAzure

Para configurar nuestro provider:

provider "azurerm" {

    # The "feature" block is required for AzureRM provider 2.x.

    # If you are using version 1.x, the "features" block is not allowed.

    version = "~>2.0"

      subscription_id = var.aks_service_subscription_id

      client_id       = var.aks_service_principal_app_id

      client_secret   = var.aks_service_principal_client_secret

      tenant_id       = var.aks_service_principal_tenant_id

    features {}

}

 

terraform {

    backend "azurerm" {}

}

Al final del bloque inicializaremos Terraforma y especificaremos nuestro proovedor que es "azurerm" En el bloque del proveedor especificamos, la versión enseguida las variables que usaremos la implementación, son:

  • subscription_id
  • client_id
  • client_secret
  • tenant_id

Estos valores son las credenciales que deben de ser tipo User Principal (SP) que son con las que utilizará el cluster para crear los elementos en Azure.

Ahora crearemos el archivo de las variables. New-Item -Path './variables.tf' -ItemType File Con el contenido

variable "resource_group_name" {

  description = "Name of the resource group."

}

 

variable "location" {

  description = "Location of the cluster."

}

 

variable "aks_service_subscription_id" {

  description = "Subscription ID."

}

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_tenant_id" {

  description = "Tenant 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 = "Containers DNS server IP address."

  default     = "15.0.0.0/8"

}

 

variable "aks_subnet_name" {

  description = "AKS Subnet Name."

  default     = "kubesubnet"

}

 

variable "aks_subnet_address_prefix" {

  description = "Containers DNS server IP address."

  default     = "15.0.0.0/16"

}

 

variable "app_gateway_subnet_address_prefix" {

  description = "Containers DNS server IP address."

  default     = "15.1.0.0/16"

}

 

variable "app_gateway_name" {

  description = "Name of the Application Gateway."

}

 

variable "app_gateway_sku" {

  description = "Name of the Application Gateway SKU."

  default = "Standard_v2"

}

 

variable "app_gateway_tier" {

  description = "Tier of the Application Gateway SKU."

  default = "Standard_v2"

}

 

variable "identity" {

  description = "Managed Identity"

  default     = "identity"

}

 

variable "ip_allocation_method" {

  description = "IP Allocation Method"

  default     = "Static"

}

variable "ip_sku" {

  description = "IP SKU"

  default     = "Standard"

}

 

variable "app_gateway_subnet"{

  description = "App Gateway subnet"

  default     = "appgwsubnet"

}

 

variable "app_gateway_ip_config_name"{

  description = "App Gateway IP Config Name"

  default     = "appGatewayIpConfig"

}

variable "app_gateway_backend_http_settings_cookie_affinity"{

  description = "App Gateway BackEnd Http Settings Cookie Afinity"

  default     = "Disabled"

}

variable "app_gateway_backend_http_settings_port"{

  description = "App Gateway BackEnd Http Settings Port"

  default     = 80

}

 

variable "app_gateway_backend_http_settings_request_timeout"{

  description = "App Gateway BackEnd Http Settings Request Timeout"

  default     = 1

}

 

variable "app_gateway_backend_http_settings_protocol"{

  description = "App Gateway BackEnd Http Settings Protocol"

  default     = "Http"

}

 

variable "app_gateway_http_listener_protocol"{

  description = "App Gateway Http Listener Protocol"

  default     = "Http"

}

 

variable "app_gateway_http_listener_require_sni"{

  description = "App Gateway Http Listener Require SNI"

  default     = true

}

 

variable "app_gateway_request_routing_rule_type"{

  description = "App Gateway Request Routing Rule Type"

  default     = "Basic"

}

 

variable "aks_name" {

  description = "Name of the AKS cluster."

}

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     = 1

}

 

variable "aks_agent_vm_size" {

  description = "The size of the Virtual Machine."

  default     = "Standard_D2_v2"

}

 

variable "kubernetes_version" {

  description = "The version of Kubernetes."

  default     = "1.15.2"

}

 

variable "aks_service_cidr" {

  description = "A CIDR notation IP range from which to assign service cluster IPs."

  default     = "10.0.0.0/16"

}

 

variable "aks_dns_service_ip" {

  description = "Containers DNS server IP address."

  default     = "10.0.0.10"

}

 

variable "aks_docker_bridge_cidr" {

  description = "A 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 "domain_name" {

  description = "Domain Name."

}

variable "tags" {

  type = "map"

 

  default = {

    source = "terraform"

  }

}

Algunas variables no tienen el valor predeterminado especificado, esto es porque se las pasaremos posteriormente como parámetro por temas de seguridad. Algunas otras variables determinan como es que el Cluster será creado.

Por ejemplo, las DNS, GateWay, IP, la Versión de Kubernetes, el tamaño de disco, el número de replicas, etc. En public_ssh_key_path debes de especificar el path del archivo llave que podrás utilizar para hacer handshake con la máquina de Linux que se aprovisionará para el cluster.

Para poder crear la llave ejecutamos ssh-keygen en nuestra terminal (Linux o Putty), Nos preguntara el password que tendremos que especificar cuando nos conectemos. ssh-keygen

La configuración que usamos en la propuesta por Microsoft "creating Kubernetes cluster with terraform" en: https://docs.microsoft.com/en-us/azure/developer/terraform/create-k8s-cluster-with-tf-and-aks

Ahora creamos el archivo donde se definen las características que nuestro cluster debe de tener

New-Item -Path './resources.tf' -ItemType File Con el contenido:

# # 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"

    backend_port_name              = "${azurerm_virtual_network.test.name}-beport"

    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"

}

 

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                = var.identity

 

  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           = var.app_gateway_subnet

    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

}

 

data "azurerm_subnet" "appgwsubnet" {

  name                 = var.app_gateway_subnet

  virtual_network_name = azurerm_virtual_network.test.name

  resource_group_name  = data.azurerm_resource_group.rg.name

}

 

# Public Ip

resource "azurerm_public_ip" "test" {

  name                         = "${var.resource_group_name}-ip"

  location                     = data.azurerm_resource_group.rg.location

  resource_group_name          = data.azurerm_resource_group.rg.name

  allocation_method            = var.ip_allocation_method

  sku                          = var.ip_sku

  domain_name_label            = var.domain_name

  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     = var.app_gateway_tier

    capacity = 2

  }

 

  gateway_ip_configuration {

    name      = var.app_gateway_ip_config_name

    subnet_id = data.azurerm_subnet.appgwsubnet.id

  }

 

  frontend_port {

    name = local.frontend_port_name

    port = 80

  }

 

  frontend_port {

    name = local.backend_port_name

    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 = var.app_gateway_backend_http_settings_cookie_affinity

    port                  = var.app_gateway_backend_http_settings_port

    protocol              = var.app_gateway_backend_http_settings_protocol

    request_timeout       = var.app_gateway_backend_http_settings_request_timeout

  }

 

  http_listener {

    name                           = local.listener_name

    frontend_ip_configuration_name = local.frontend_ip_configuration_name

    frontend_port_name             = local.frontend_port_name

    protocol                       = var.app_gateway_http_listener_protocol

  }

 

  request_routing_rule {

    name                       = local.request_routing_rule_name

    rule_type                  = var.app_gateway_request_routing_rule_type

    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

  }

 

  depends_on = ["azurerm_virtual_network.test", "azurerm_application_gateway.network"]

  tags       = var.tags

}

 

 

Muchas referencias son obtenidas de otros recursos de los cuales dependen y se definen y acceden a ellos como variables u objetos.

Ahora crearemos el archivo output.tf en el que definiremos variables las cuales podremos consultar desde el contexto

New-Item -Path './output.tf' -ItemType File Con el contenido

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

}

 

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

}

Ahora podemos acceder a estas variables desde nuestra terminal para utilizarlas en el deploy o crear otros elementos relacionados a este Cluster.

Ahora ya tenemos el Cluster completo definido.

Inicializaremos el mismo, este proceso analizará la estructura y guardará el estado "state"

Podemos guardar el estado en nuestro equipo local, pero en este ejercicio usaremos Azure Cli para conectarnos a Azure y crearemos un Storage and Container para almacenar el estado.

Definiremos en una variable el nombre de nuestro Storage y en otra el SKU que nuestro recurso deberá tener.

$storageName = "myStorage" $storageSKU = "Standard_LRS"

Lo creamos en Azure az storage account create --name $storageName --resource-group $nameGrp --sku $storageSKU

Una vez creado obtendremos el ConnectioString para crear el contenedor donde estará el estado del Terraform $storageKey=(az storage account keys list -n $storageName -g $nameGrp --query [0].value -o tsv)

Ahora definiremos el nombre de nuestro contenedor y procederemos a crearlo. $containerStateName "stateOfMyTerraform" az storage container create -n $containerStateName --account-name $storageName --account-key $storageKey

Ahora veremos los recursos que se crearán en nuestro portal de Azure.

Ahora como lo especificamos en el provider y antes de crear el Cluster debemos de especificar las credenciales del Service Principal User (SP) con el que Terraform creará los recursos en nuestra subscripción de Azure, este debe de tener privilegios elevados.

Creamos el usuario con Azure Cli

Creamos el usuario y en el momento de la creación estructuramos para que la respuesta se almacene la contraseña en texto plano y nos lo asigne a una variable. En esta instrucción estaremos especificando que el rol debe de ser "Owner". $spPassword = az ad sp create-for-rbac -n $spName --role "Owner" --query password --output tsv

Una vez que el usuario es creado, obtenemos el AppID y el ObjectID y lo asignamos en dos variables.

$spId = az ad sp list --filter "displayname eq '$spName'" --query '[].appId' -o tsv $spObjectId = az ad sp list --filter "displayname eq '$spName'" --query '[].objectId' -o tsv

Adelante, vamos a seguir creando variables para almacenar los valores que podremos estar usando durante el tutorial.

Nombre del cluster $aksName = "myAKS" Definimos el dominio que usaremos, porque tendremos que especificarlo a las DNS posteriormente. $dnsName = "mypersonaldomain123.com"

Define el nombre del Gateway porque lo usaremos después.

$varInitial = '' # empty var so that a subsequent join does not affect us.

$varResourceName = 'resource_group_name={0}' -f $groupName # the resource group to the kubernetes where it will be created

$varSubscriptionId = 'aks_service_subscription_id={0}' -f $subscriptionId # Subscription Id

$varSpUserName = 'aks_service_principal_app_id={0}' -f $spId # Service Principal ID just created

$varSpPassword = 'aks_service_principal_client_secret={0}' -f $spPassword # Client Secret

$varSpObjectId = 'aks_service_principal_object_id={0}' -f $spObjectId # Object ID of the SP

$varTenant = 'aks_service_principal_tenant_id={0}' -f $tenant # Tenant of the subscription

$varLocation = 'location={0}' -f $location # The location

$varAksName = 'aks_name={0}' -f $aksName # The Name of AKS

$varDns = 'domain_name={0}' -f $dnsName.replace(".","") #

The domain without points, to define it as DNS of the Cluster would be something like mypersonaldomain123.eastus.cloudapp.azure.com

Define el nombre del plan en una variable $planName = "plan.out"

Cree todas las variables en un formato donde cada una tenga un prefijo -var (es por eso que la variable se vacía al principio) $planCmdVars = $varInitial, $varResourceName, $varSubscriptionId, $varSpUserName, $varSpPassword, $varSpObjectId, $varTenant, $varLocation, $varAksName, $varDns -join ' -var '

Para crear nuestro clúster, inicie Terraform, ya que dijimos que especificamos la configuración para guardar nuestro BackEnd, enviando a las conexiones el nombre de nuestro almacenamiento y el del contenedor.

terraform init -backend-config="storage_account_name=$storageName" -backend-config="container_name=$containerStateName" -backend-config="access_key=$storageKey" -backend-config="key=codelab.microsoft.tfstate"

Podemos ver que creó una carpeta con la información de nuestro estado y nuestro proveedor, si hubiera habido un problema al principio, Terraform nos diría de una manera muy explícita, corregiría el problema, eliminaría la carpeta e inicializaría nuevamente.

Ahora creamos nuestro plan, pero lo ejecutaremos mediante PowerShell Invoke-Expression debido al hecho de que tiene una estructura de variable de terraforma dentro de una variable de PowerShell.

Invoke-Expression -Command "terraform plan --out $planName -input=false -detailed-exitcode $planCmdVars"

La salida de en el sistema de archivos.

La salida de la consola.

Finalmente aplicamos el plan, esto tomará unos minutos

Al final te mostrará

Viendose desde Azure

Veremos la red virtual, el clúster de Kubernetes, la IP pública y una Identidad creada por Kuberneres más el ACR, el almacenamiento creado previamente en este tutorial.

Como podemos ver en la creación del ALS, Azure creó un nuevo grupo de recursos donde su nombre se estructura de la siguiente manera: MC_myaks_eastus con los recursos que el clúster necesitará para sus operaciones, como un equilibrador de carga y otros recursos que nosotros Necesidad y suministro con la operación de nuestro clúster.

6. Kubernetes

Ahora podemos comenzar a crear implementaciones de Kubernetes, Servicios, etc.

Pero antes de comenzar con eso, expliquemos qué es Kubernetes. Kubernetes es un orquestador de contenedores. Por objeto llamado despliegue, Kubernetes gestiona los pods que tienen los contenedores que tienen nuestras imágenes creadas, este objeto se define en un archivo yaml que debemos crear y aprovisionar con kubernetes.

Dentro del mismo Yaml crearemos el servicio para definir la configuración de red en nuestro clúster que nos permite acceder a esos pods.

En este momento tenemos los conceptos básicos que requiere un clúster de Kubernetes:

  • Pods creados usando una réplica reestructurada definida y administrada por nuestro objeto de implementación
  • Servicios que definen en el clúster qué puertos apuntan a qué implementación

Crearemos nuestro primer despliegue y servicio

Para interactuar con Kubernetes en nuestro Azure (AKS) debemos establecer esa relación de confianza (HandShake)

Por lo que ejecutamos

az aks get-credentials --resource-group $groupName --name $aksName

Vaya a la raíz de nuestra solución y cree una carpeta ca donde colocaremos nuestros yamls e ingresaremos

cd ..

mkdir yaml

cd ./yaml

 

Cree un archivo para definir nuestra implementación y el servicio de esta implementación.

New-Item -Path './Deploymeny-Service.yaml' -ItemType File ` With the content.

apiVersion: apps/v1

kind: Deployment

metadata:

  name: myimage

  labels:

    app: myimage

spec:

  replicas: 1

  selector:

    matchLabels:

      app: myimage

  template:

    metadata:

      labels:

        app: myimage

    spec:

      containers:

        - name: myimage

          image: myacr.azurecr.io/myproject:1

          ports:

            - containerPort: 80

          env:

            - name: ASPNETCORE_ENVIRONMENT

              value: dev

---

apiVersion: v1

kind: Service

metadata:

  annotations:

    service.beta.kubernetes.io/azure-dns-label-name: myimage321

  name: myimage-service

spec:

  type: LoadBalancer

  ports:

   - port: 80

  selector:

    app: myimage

En el bloque de código anterior, el tipo de Implementación define que vamos a comenzar una nueva definición para una Implementación.

Los atributos que vamos a reemplazar son:

  • name: myimage // así es como se identificará dentro del clúster, para continuar con el mismo estándar pondremos el mismo nombre que le ponemos a nuestra ventana acoplable local "myimage" definida en la variable $ imageName.
  • replicas: 1 // va dentro del espectro y es cuántas réplicas podrá tener este servicio, es decir, la cantidad de pods que puede crear para administrar su escalabilidad.
  • image: myacr.azurecr.io/myproject:1 // La url de la imagen dentro del ACR como se definió anteriormente, tenemos esto en la variable.

El indicador "---" especifica que vamos a comenzar con otro objeto; A continuación vamos a crear un servicio definiendo el tipo: Servicio.

Los atributos que vamos a reemplazar son

  • name: myimage-service // definimos cómo se llamará nuestro servicio
  • service.beta.kubernetes.io/azure-dns-label-name: myimage321 // debe ser un nombre único para crear el dns dentro de Azure Region y asociarlo con la IP pública creada
  • app: myimage321 // el nombre del deployment creado

Guardamos el archivo y las aplicaciones con Kubernetes.

kubectl apply -f .\Deploymeny-Service.yaml

Para ver los pods creados desde la terminal, ejecutamos.

kubectl get pods

Para poder ver las implementaciones.

kubectl get deployments

Para ver los servicios

kubectl get services

Aqui podemos ver la IP externa

Del mismo modo, podemos pedirle a Kubernetes que describa cada objeto, que describa el servicio que ejecutamos.

kubectl describe services myimage-service

Si no hay errores, nos mostrará la etiqueta dns y podremos mostrar nuestra aplicación desde el navegador, dejando una url compuesta por el nombre de dns que definimos, la región y el dominio de aplicaciones de Azure "http://myimage321.eastus.cloudapp.azure.com/"

Una herramienta útil para administrar el clúster es el Terraform Board, para iniciarlo lo ejecutamos.

az aks browse --resource-group $groupName --name $aksName Esto no abrirá una ventana del navegador.

7. DNS, Routeo y Certificados

Para poder organizar con Kubernetes una solución que nos permite crear rutas de llamadas a nuestro DNS entre nuestros servicios y administrar certificados de seguridad, se necesitan otra serie de elementos, como enrutamiento, equilibrador de carga, administrador de certificados y el grupo de emisión de certificados .

Estos elementos se administran con Helm, se conocen como gráficos, para entenderlo un poco mejor Helm es como un administrador de paquetes y cada gráfico es un paquete.

Para este ejemplo instalaremos dos paquetes.

  • nginx-ingress
  • cert-manager

La tabla nginx-ingress se encuentra en el espacio de nombres de google apis https://kubernetes-charts.storage.googleapis.com/ y nos permite crear los objetos de ingreso para nuestros servicios, que define la ruta que las solicitudes deben tomar si se encuentran bajo ciertos criterios, por ejemplo, un subdominio o una ruta de dominio interna (eduardo.mydomain.com or mydomain.com/eduardo or * .mydomain.com).

El cuadro de cert-manager está en el repositorio de jetstack https://charts.jetstack.io, esto nos ayudará a crear un clúster de emisión de certificados, cuando la entrada de un servicio especifica que un servicio debe tener un tls, deberá definir su nombre y tipo de emisor, en este ejemplo usaremos letsencrypt, que es un esquema de certificación abierto y gratuito, pero también puede definir certificados emitidos por una entidad certificadora o algún servicio de certificación externo.

La instalación de estos repositorios se realizará dentro de otro espacio de nombres de clúster para que podamos facilitar la operación cuando tengamos una gran cantidad de servicios.

kubectl create namespace ingress-basic

Agregue el repositorio de Google Apis. helm repo add stable https://kubernetes-charts.storage.googleapis.com/

Instalar nginx-ingress. helm install nginx stable/nginx-ingress --namespace ingress-basic --set controller.replicaCount=2 --set controller.nodeSelector."beta\.kubernetes\.io/os"=linux --set defaultBackend.nodeSelector."beta\.kubernetes\.io/os"=linux

Valide y guarde la IP externa del controlador.

kubectl get service -l app=nginx-ingress --namespace ingress-basic

Otro chat que necesitamos es la Definición de recursos personalizados que le permite definir recursos personalizados.

kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.13/deploy/manifests/00-crds.yaml

Ahora debemos deshabilitar la validación de recursos para la entrada. kubectl label namespace ingress-basic cert-manager.io/disable-validation=true

Ahora vamos a instalar el Certificate Manager Cert-Manager.

Agregamos el repositorio Jetstack. helm repo add jetstack https://charts.jetstack.io

Actualizamos. helm repo update

Al igual que con nginx, lo instalamos dentro del espacio de nombres básico de ingreso. helm install cert-manager --namespace ingress-basic --version v0.13.0 jetstack/cert-manager

Valide que tenemos los pods instalados bajo el espacio de nombres básico de ingreso. kubectl get pods --namespace ingress-basic

Cree la zona DNS para nuestro dominio.

az network dns zone create --name $dnsName --resource-group $groupName --zone-type public

Obtenga las URL de NS, para configurar nuestro dominio en el sistema donde compra su dominio.

az network dns zone show --name $dnsName --resource-group $groupName --query "nameServers" --output tsv

Apuntamos nuestro dominio a esos DNS.

Agregue un conjunto de registros "A" al conjunto de registros de la Zona DNS con la IP de nuestro equilibrador.

az network dns record-set a add-record --resource-group $groupName --zone-name "ku2.com.mx" --record-set-name '*' --ipv4-address 52.191.81.204

Podemos ver el área y el registro en el Portal de Azure.

Ahora vamos a trabajar con el Administrador de certificados, primero vamos a crear un clúster emisor, esto no nos permitirá emitir certificados cuando creamos elementos de ingreso para los servicios que publicaremos en Kubernetes.

El clúster creará los certificados utilizando la autoridad de certificación LetsEncrypt.

La forma de crear estos elementos es la misma, creando sus definiciones en formato yaml.

Para el clúster, al estar en nuestra ruta yaml, creamos un archivo llamado clusterissuer.yaml

New-Item -Path './clusterissuer.yaml' -ItemType File

Con el Contenido

apiVersion: cert-manager.io/v1alpha2

kind: ClusterIssuer

metadata:

  name: letsencrypt

spec:

  acme:

    email: your@email.com

    # The ACME server URL

    server: https://acme-v02.api.letsencrypt.org/directory

    privateKeySecretRef:

      name: letsencrypt

    # Enable the HTTP-01 challenge provider

    solvers:

    - http01:

        ingress:

          class: nginx

  • El elemento "nombre" identifica el nombre de nuestro clúster.
  • "correo electrónico" tenemos que definir nuestro correo electrónico.
  • Los demás elementos los dejamos igual.

Aplicamos kubectl apply -f .\clusterissuer.yaml

Ahora eliminaremos el servicio creado anteriormente para volver a crearlo con una modificación que nos permitirá mantener el tráfico solo por el ingreso.

En el servicio tuvimos que definir el tipo de servicio como.

kubectl delete -f .\eduardo.yaml Deploymeny-Service.yaml

En el Servicio cambiamos el tipo: LoadBalancer por tipo: ClusterIP, para que sea el siguiente:

apiVersion: apps/v1

kind: Deployment

metadata:

  name: myimage

  labels:

    app: myimage

spec:

  replicas: 1

  selector:

    matchLabels:

      app: myimage

  template:

    metadata:

      labels:

        app: myimage

    spec:

      containers:

        - name: myimage

          image: myacr.azurecr.io/myproject:1

          ports:

            - containerPort: 80

          env:

            - name: ASPNETCORE_ENVIRONMENT

              value: dev

---

apiVersion: v1

kind: Service

metadata:

  annotations:

    service.beta.kubernetes.io/azure-dns-label-name: myimage321

  name: myimage-service

spec:

  type: ClusterIP

  ports:

   - port: 80

  selector:

    app: myimage

  • type: ClusterIP // This will not create public ip

Aplicamos la implementación y el servicio nuevamente.

kubectl delete -f .\eduardo.yaml Deploymeny-Service.yaml

Ahora creamos el ingreso que no permitirá crear el certificado y dirigir el tráfico.

Creamos un archivo llamado ingreso.

New-Item -Path './clusterissuer.yaml' -ItemType File

Con el contenido:

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

  name: myimage-ingress

  annotations:

    kubernetes.io/ingress.class: nginx

    nginx.ingress.kubernetes.io/rewrite-target: /$2

    cert-manager.io/cluster-issuer: letsencrypt

spec:

  tls:

  - hosts:

    - myimage.mypersonaldomain123.com

    secretName: myimage-secret

  rules:

  - host: myimage.mypersonaldomain123.com

    http:

      paths:

      - backend:

          serviceName: myimage-service

          servicePort: 80

        path: /(.*)

Los elementos importantes son:

  • nombre: myimage-ingress // el nombre del ingress
  • cert-manager.io/cluster-issuer: letsencrypt // el nombre de nuestro cluster de emision
  • Hospedadores:
  • myimage.mypersonaldomain123.com // sobre que llamada se enturará el tráfico y también se especifica en el tls para la creación del dominio
  • secretName: myimage-secret // el nombre del certificado en nuestro administrador de certificados
  • host: myimage.mypersonaldomain123.com // también se define en las reglas
  • backend:
  • serviceName: myimage-service // el nombre que tiene nuestro servicio
  • ruta: /(.*) // la expresión por la cual se aplica el redireccionamiento de tráfico

kubectl apply -f .\ingress.yaml

Validamos y esperamos que se cree el certificado kubectl get certificates --namespace ingress-basic

Ahora podemos ingresar al navegador, abrir nuestra URL y validar el certificado

Con esto hemos terminado de "crear una aplicación con DotNetCore 3.1, ponerla dentro de un Docker, subirla a un ACR, Crear un clúster AKS con Kubernetes, Implementar implementaciones, Servicios. Instalar un controlador de ruta y administrador de certificados nginx Ingress con LetsEncrypt con Helm ".

Entonces, si tiene alguna pregunta, no dude en ponerse en contacto conmigo.

 


Related Posts

Únete a nuestra Newsletter

Lidera la Conversación en la Nube