Paranys de Terraform

Paranys de Terraform
Destaquem alguns inconvenients, inclosos els relacionats amb bucles, declaracions if i tècniques de desplegament, així com problemes més generals que afecten Terraform en general:

  • els paràmetres count i for_each tenen limitacions;
  • limitar zero desplegaments de temps d'inactivitat;
  • fins i tot un bon pla pot fracassar;
  • la refactorització pot tenir els seus inconvenients;
  • la coherència diferida és coherent... amb l'ajornament.

Els paràmetres count i for_each tenen limitacions

Els exemples d'aquest capítol fan un ús extensiu del paràmetre count i de l'expressió for_each en bucles i lògica condicional. Tenen un bon rendiment, però tenen dues limitacions importants que cal tenir en compte.

  • Count i for_each no poden fer referència a cap variable de sortida de recurs.
  • count i for_each no es poden utilitzar a la configuració del mòdul.

count i for_each no poden fer referència a cap variable de sortida de recurs

Imagineu que necessiteu desplegar diversos servidors EC2 i, per algun motiu, no voleu utilitzar ASG. El vostre codi podria ser així:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Mirem-los un per un.

Com que el paràmetre count està establert en un valor estàtic, aquest codi funcionarà sense problemes: quan executeu l'ordre d'aplicació, crearà tres servidors EC2. Però, què passa si volguéssiu desplegar un servidor a cada zona de disponibilitat (AZ) a la vostra regió AWS actual? Podeu fer que el vostre codi carregui una llista de zones des de la font de dades aws_availability_zones i, a continuació, recorreu cadascuna i hi creï un servidor EC2 mitjançant el paràmetre de recompte i l'accés a l'índex de matriu:

resource "aws_instance" "example_2" {
   count                   = length(data.aws_availability_zones.all.names)
   availability_zone   = data.aws_availability_zones.all.names[count.index]
   ami                     = "ami-0c55b159cbfafe1f0"
   instance_type       = "t2.micro"
}

data "aws_availability_zones" "all" {}

Aquest codi també funcionarà bé, ja que el paràmetre de recompte pot fer referència a fonts de dades sense cap problema. Però què passa si el nombre de servidors que necessiteu crear depèn de la sortida d'algun recurs? Per demostrar-ho, la manera més senzilla és utilitzar el recurs random_integer, que, com el seu nom indica, retorna un nombre enter aleatori:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

Aquest codi genera un nombre aleatori entre 1 i 3. Vegem què passa si intentem utilitzar la sortida d'aquest recurs al paràmetre count del recurs aws_instance:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Si executeu el pla terraform en aquest codi, obtindreu el següent error:

Error: Invalid count argument

   on main.tf line 30, in resource "aws_instance" "example_3":
   30: count = random_integer.num_instances.result

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

Terraform requereix que count i for_each es calculin durant la fase de planificació, abans de crear o modificar recursos. Això vol dir que count i for_each poden fer referència a literals, variables, fonts de dades i fins i tot llistes de recursos (sempre que la seva longitud es pugui determinar en el moment de la programació), però no a variables de sortida de recursos calculades.

count i for_each no es poden utilitzar a la configuració del mòdul

Algun dia podríeu tenir la temptació d'afegir un paràmetre de recompte a la configuració del vostre mòdul:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Aquest codi intenta utilitzar el recompte dins d'un mòdul per crear tres còpies del recurs del clúster del servidor web. O potser voldreu fer que la connexió d'un mòdul sigui opcional en funció d'alguna condició booleana establint el seu paràmetre de recompte a 0. Pot semblar un codi raonable, però obtindreu aquest error quan executeu el pla de terraform:

Error: Reserved argument name in module block

   on main.tf line 13, in module "count_example":
   13: count = 3

The name "count" is reserved for use in a future version of Terraform.

Malauradament, a partir de Terraform 0.12.6, l'ús de count o for_each en un recurs de mòdul no és compatible. Segons les notes de la versió de Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp té previst afegir aquesta capacitat en el futur, de manera que, depenent de quan llegiu aquest llibre, és possible que ja estigui disponible. Per saber-ho amb seguretat, llegiu el registre de canvis de Terraform aquí.

Limitacions dels desplegaments de temps d'inactivitat zero

L'ús del bloc create_before_destroy en combinació amb ASG és una gran solució per a desplegaments sense temps d'inactivitat, tret d'una advertència: les regles d'escala automàtica no són compatibles. O, per ser més precisos, això restableix la mida ASG a min_size en cada desplegament, cosa que podria ser un problema si utilitzeu regles d'escala automàtica per augmentar el nombre de servidors en execució.

Per exemple, el mòdul webserver-cluster conté un parell de recursos aws_autoscaling_schedule, que a les 9 del matí augmenta el nombre de servidors del clúster de dos a deu. Si us desplegueu, per exemple, a les 11 a.m., el nou ASG s'iniciarà amb només dos servidors en lloc de deu i romandrà així fins a les 9 a.m. de l'endemà.

Aquesta limitació es pot eludir de diverses maneres.

  • Canvieu el paràmetre de recurrència a aws_autoscaling_schedule de 0 9 * * * ("executa a les 9 del matí") a alguna cosa com 0-59 9-17 * * * ("executa cada minut de 9 a.m. a 5 de la tarda"). Si ASG ja té deu servidors, tornar a executar aquesta regla d'escala automàtica no canviarà res, que és el que volem. Però si l'ASG fa poc que s'ha desplegat, aquesta regla garantirà que en un minut màxim el nombre dels seus servidors arribarà a deu. Aquest no és un enfocament del tot elegant, i grans salts de deu a dos servidors i enrere també poden causar problemes als usuaris.
  • Creeu un script personalitzat que utilitzi l'API d'AWS per determinar el nombre de servidors actius a l'ASG, truqueu-lo mitjançant una font de dades externa (vegeu "Font de dades externa" a la pàgina 249) i configureu el paràmetre de capacitat desitjada de l'ASG al valor retornat per el guió. D'aquesta manera, cada nova instància ASG sempre s'executarà amb la mateixa capacitat que el codi Terraform existent i fa que sigui més difícil de mantenir.

Per descomptat, l'ideal seria que Terraform tingués suport integrat per a desplegaments sense temps d'inactivitat, però a partir de maig de 2019, l'equip de HashiCorp no tenia previst afegir aquesta funcionalitat (detalls - aquí).

El pla correcte pot ser implementat sense èxit

De vegades, l'ordre plan produeix un pla de desplegament perfectament correcte, però l'ordre apply retorna un error. Proveu, per exemple, d'afegir el recurs aws_iam_user amb el mateix nom que vau utilitzar per a l'usuari IAM que vau crear anteriorment al capítol 2:

resource "aws_iam_user" "existing_user" {
   # Подставьте сюда имя уже существующего пользователя IAM,
   # чтобы попрактиковаться в использовании команды terraform import
   name = "yevgeniy.brikman"
}

Ara, si executeu l'ordre del pla, Terraform generarà un pla de desplegament aparentment raonable:

Terraform will perform the following actions:

   # aws_iam_user.existing_user will be created
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (known after apply)
         + force_destroy   = false
         + id                    = (known after apply)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (known after apply)
      }

Plan: 1 to add, 0 to change, 0 to destroy.

Si executeu l'ordre d'aplicació, obtindreu el següent error:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.

   on main.tf line 10, in resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

El problema, per descomptat, és que ja existeix un usuari d'IAM amb aquest nom. I això pot passar no només als usuaris d'IAM, sinó a gairebé qualsevol recurs. És possible que algú hagi creat aquest recurs manualment o utilitzant la línia d'ordres, però de qualsevol manera, la concordança d'identificadors provoca conflictes. Hi ha moltes variacions d'aquest error que sovint sorprenen els nouvinguts a Terraform.

El punt clau és que l'ordre terraform plan només té en compte aquells recursos que s'especifiquen al fitxer d'estat de Terraform. Si els recursos es creen d'una altra manera (per exemple, manualment fent clic a la consola AWS), no acabaran al fitxer d'estat i, per tant, Terraform no els tindrà en compte a l'hora d'executar l'ordre del pla. Com a resultat, un pla que a primera vista sembla correcte no tindrà èxit.

Hi ha dues lliçons per aprendre d'això.

  • Si ja heu començat a treballar amb Terraform, no feu servir res més. Si una part de la vostra infraestructura es gestiona amb Terraform, ja no podeu modificar-la manualment. En cas contrari, no només correu el risc d'errors estranys de Terraform, sinó que també negueu molts dels avantatges d'IaC, ja que el codi ja no serà una representació precisa de la vostra infraestructura.
  • Si ja teniu alguna infraestructura, utilitzeu l'ordre d'importació. Si esteu començant a utilitzar Terraform amb una infraestructura existent, podeu afegir-la al fitxer d'estat mitjançant l'ordre d'importació de terraform. D'aquesta manera Terraform sabrà quina infraestructura s'ha de gestionar. L'ordre d'import pren dos arguments. El primer és l'adreça del recurs als fitxers de configuració. La sintaxi aquí és la mateixa que per als enllaços de recursos: _. (com aws_iam_user.existing_user). El segon argument és l'ID del recurs que s'ha d'importar. Suposem que l'ID de recurs aws_iam_user és el nom d'usuari (per exemple, yevgeniy.brikman) i l'ID de recurs aws_instance és l'ID del servidor EC2 (com i-190e22e5). Com importar un recurs normalment s'indica a la documentació al final de la seva pàgina.

    A continuació es mostra una ordre d'importació que sincronitza el recurs aws_iam_user que heu afegit a la vostra configuració de Terraform juntament amb l'usuari IAM al capítol 2 (substituint el vostre nom per yevgeniy.brikman, és clar):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform trucarà a l'API d'AWS per trobar el vostre usuari IAM i crear una associació de fitxers d'estat entre aquest i el recurs aws_iam_user.existing_user a la vostra configuració de Terraform. A partir d'ara, quan executeu l'ordre del pla, Terraform sabrà que l'usuari IAM ja existeix i no intentarà crear-lo de nou.

    Val la pena assenyalar que si ja teniu molts recursos que voleu importar a Terraform, escriure el codi manualment i importar-ne cadascun a la vegada pot ser una molèstia. Per tant, val la pena buscar una eina com Terraforming (http://terraforming.dtan4.net/), que pot importar automàticament codi i estat des del vostre compte d'AWS.

    La refactorització pot tenir els seus inconvenients

    Refactorització és una pràctica habitual en programació on es canvia l'estructura interna del codi sense canviar el comportament extern. Això és per fer que el codi sigui més clar, net i fàcil de mantenir. La refactorització és una tècnica indispensable que s'ha d'utilitzar amb regularitat. Però quan es tracta de Terraform o qualsevol altra eina d'IaC, heu de tenir molt de compte amb què enteneu per "comportament extern" d'una peça de codi, en cas contrari sorgiran problemes inesperats.

    Per exemple, un tipus comú de refactorització és substituir els noms de variables o funcions per altres de més comprensibles. Molts IDE tenen suport integrat per a la refactorització i poden canviar el nom automàticament de variables i funcions al llarg del projecte. En els llenguatges de programació de propòsit general, aquest és un procediment trivial en el qual potser no penseu, però a Terraform heu de ser extremadament curosos amb això, en cas contrari podríeu patir interrupcions.

    Per exemple, el mòdul del clúster del servidor web té una variable d'entrada nom_clúster:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    Imagineu que vau començar a utilitzar aquest mòdul per implementar un microservei anomenat foo. Més tard, voleu canviar el nom del vostre servei a barra. Aquest canvi pot semblar trivial, però en realitat pot provocar interrupcions del servei.

    El fet és que el mòdul del clúster del servidor web utilitza la variable cluster_name en una sèrie de recursos, inclòs el paràmetre de nom de dos grups de seguretat i l'ALB:

    resource "aws_lb" "example" {
       name                    = var.cluster_name
       load_balancer_type = "application"
       subnets = data.aws_subnet_ids.default.ids
       security_groups      = [aws_security_group.alb.id]
    }

    Si canvieu el paràmetre de nom d'un recurs, Terraform suprimirà la versió antiga d'aquest recurs i en crearà una de nova al seu lloc. Però si aquest recurs és un ALB, entre l'eliminació i la baixada d'una nova versió, no tindreu cap mecanisme per redirigir el trànsit al vostre servidor web. De la mateixa manera, si s'elimina un grup de seguretat, els vostres servidors començaran a rebutjar qualsevol trànsit de xarxa fins que es creï un grup nou.

    Un altre tipus de refactorització que us pot interessar és canviar l'ID de Terraform. Prenem com a exemple el recurs aws_security_group del mòdul del clúster del servidor web:

    resource "aws_security_group" "instance" {
      # (...)
    }

    L'identificador d'aquest recurs s'anomena instància. Imagineu que durant la refactorització heu decidit canviar-lo per un nom més entenedor (segons la vostra opinió) cluster_instance:

    resource "aws_security_group" "cluster_instance" {
       # (...)
    }

    Què passarà al final? És cert: una interrupció.

    Terraform associa cada ID de recurs amb l'ID del proveïdor de núvol. Per exemple, iam_user s'associa amb l'ID d'usuari d'AWS IAM i aws_instance s'associa amb l'ID del servidor AWS EC2. Si canvieu l'ID del recurs (per exemple, d'instància a cluster_instance, com és el cas amb aws_security_group), a Terraform apareixerà com si haguéssiu suprimit el recurs antic i n'haveu afegit un de nou. Si apliqueu aquests canvis, Terraform suprimirà el grup de seguretat antic i en crearà un de nou, mentre els vostres servidors comencen a rebutjar qualsevol trànsit de xarxa.

    Aquí teniu quatre lliçons clau que hauríeu de treure d'aquesta discussió.

    • Feu servir sempre l'ordre del pla. Pot revelar tots aquests inconvenients. Reviseu la seva sortida amb atenció i presteu atenció a les situacions en què Terraform planeja suprimir recursos que probablement no s'han d'eliminar.
    • Crea abans de suprimir. Si voleu substituir un recurs, penseu bé si heu de crear un reemplaçament abans de suprimir l'original. Si la resposta és afirmativa, create_before_destroy pot ajudar. El mateix resultat es pot aconseguir manualment fent dos passos: primer afegiu un recurs nou a la configuració i executeu l'ordre d'aplicació, i després elimineu el recurs antic de la configuració i torneu a utilitzar l'ordre d'aplicació.
    • El canvi d'identificador requereix un canvi d'estat. Si voleu canviar l'identificador associat a un recurs (per exemple, canviar el nom de aws_security_group d'instància a cluster_instance) sense suprimir el recurs i crear-ne una nova versió, heu d'actualitzar el fitxer d'estat de Terraform en conseqüència. No ho feu mai manualment; feu servir l'ordre terraform state. Quan canvieu el nom dels identificadors, hauríeu d'executar l'ordre terraform state mv, que té la sintaxi següent:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE és una expressió que fa referència al recurs en la seva forma actual, i NEW_REFERENCE és on el voleu moure. Per exemple, quan canvieu el nom del grup aws_security_group d'instància a cluster_instance, heu d'executar l'ordre següent:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Això indica a Terraform que l'estat que abans estava associat amb aws_security_group.instance ara s'hauria d'associar amb aws_security_group.cluster_instance. Si després de canviar el nom i executar aquest pla de terraform d'ordres no mostra cap canvi, ho heu fet tot correctament.

    • Alguns paràmetres no es poden canviar. Els paràmetres de molts recursos són inalterables. Si intenteu canviar-los, Terraform suprimirà el recurs antic i en crearà un de nou al seu lloc. Cada pàgina de recursos indicarà normalment què passa quan canvieu una configuració concreta, així que assegureu-vos de consultar la documentació. Utilitzeu sempre l'ordre del pla i considereu utilitzar l'estratègia create_before_destroy.

    La consistència ajornada és coherent... amb l'ajornament

    Les API d'alguns proveïdors de núvol, com AWS, són asíncrones i tenen una consistència retardada. L'asynchrony significa que la interfície pot retornar immediatament una resposta sense esperar que finalitzi l'acció sol·licitada. La consistència retardada significa que els canvis poden trigar un temps a propagar-se per tot el sistema; mentre això succeeix, les vostres respostes poden ser inconsistents i dependre de la rèplica de la font de dades que respon a les vostres trucades d'API.

    Imagineu, per exemple, que feu una trucada d'API a AWS per demanar-li que creï un servidor EC2. L'API retornarà una resposta "èxit" (201 creat) gairebé a l'instant, sense esperar que es creï el propi servidor. Si intenteu connectar-vos-hi immediatament, gairebé segur que fallarà perquè en aquell moment AWS encara està inicialitzant recursos o, alternativament, el servidor encara no s'ha iniciat. A més, si feu una altra trucada per obtenir informació sobre aquest servidor, és possible que rebeu un error (404 Not Found). El cas és que la informació sobre aquest servidor EC2 encara es pot propagar per AWS abans que estigui disponible a tot arreu, haureu d'esperar uns segons.

    Sempre que utilitzeu una API asíncrona amb consistència mandrosa, heu de tornar a provar periòdicament la vostra sol·licitud fins que l'acció es completi i es propagui pel sistema. Malauradament, l'AWS SDK no ofereix cap eina bona per a això, i el projecte Terraform solia patir molts errors com el 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    The subnet ID 'subnet-xxxxxxx' does not exist

    En altres paraules, creeu un recurs (com una subxarxa) i després intenteu obtenir informació sobre ell (com l'ID de la subxarxa acabada de crear) i Terraform no el pot trobar. La majoria d'aquests errors (inclòs el 6813) s'han corregit, però encara apareixen de tant en tant, especialment quan Terraform afegeix suport per a un nou tipus de recurs. Això és molest, però en la majoria dels casos no causa cap dany. Quan torneu a executar terraform apply, tot hauria de funcionar, ja que en aquest moment la informació ja s'haurà estès per tot el sistema.

    Aquest fragment es presenta del llibre d'Evgeniy Brikman "Terraform: infraestructura a nivell de codi".

Font: www.habr.com

Afegeix comentari