Introdução
O Hashicorp Configuration Language (HCL), usado pelo Terraform, oferece muitas estruturas e capacidades úteis que estão presentes em outras linguagens de programação. A utilização de laços em seu código de infraestrutura pode reduzir significativamente a duplicação de código e aumentar a legibilidade, permitindo uma refatoração futura mais fácil e maior flexibilidade. O HCL também fornece algumas estruturas de dados comuns, como listas e mapas (também chamadas de arrays e dicionários respectivamente em outras linguagens), bem como condicionais para divisão de caminhos de execução.
Exclusivo do Terraform é a capacidade de especificar manualmente os recursos em que um depende. Embora o grafo de execução que ele constróa quando executando seu código já contenha as ligações detectadas (que são corretas na maioria dos casos), você pode encontrar-se precisando de forçar uma relação de dependência que o Terraform não conseguiu detetar.
Neste artigo, nós revisaremos as estruturas de dados fornecidas pelo HCL, suas funcionalidades de loop para recursos (a chave count
, for_each
e for
), condicionais para tratar valores conhecidos e desconhecidos, e relações de dependência entre recursos.
Pré-requisitos
- Uma Chave de Acesso Personalizada da DigitalOceano, que você pode criar via painel de controle da DigitalOceano. Você encontrará instruções no documento de produtos da DigitalOceano, Como Criar uma Chave de Acesso Personalizada.
- Terraform instalado na sua máquina local e um projeto configurado com o provedor DigitalOcean. Complete Passo 1 e Passo 2 do tutorial Como Usar Terraform com DigitalOcean, e seja certo de nomear a pasta do projeto
terraform-flexibility
, ao invés deloadbalance
. Durante Passo 2, você não precisa incluir variávelpvt_key
e recurso de chave SSH quando configure o provedor.
Observação: Este tutorial foi testado especificamente com Terraform 1.0.2
.
Tipos de Dados em HCL
Antes de você aprender mais sobre laços e outras funcionalidades do HCL que tornam seu código mais flexível, vamos revisitar os tipos de dados disponíveis e suas utilizações.
O Linguagem de Configuração do Hashicorp suporta tipos primitivos e tipos complexos. Os tipos primitivos são strings, números e valores booleans, que são os tipos básicos que não podem ser derivados de outros. As duas classes de valores complexos, por outro lado, agrupam vários valores em um único. Os dois tipos de valores complexos são tipos estruturais e colecionais. Os dois primeiros tipos de valores complexos são as definições de recursos que você usa para especificar o que sua infraestrutura irá parecer. Em contraste com os tipos estruturais, as tipos de valores complexos também agrupam valores de diferentes tipos. Os três tipos de valores de coleção disponíveis no HCL que nos interessamos são listas, mapas e conjuntos.
Os conjuntos permitem que valores de tipos diferentes sejam agrupados juntos. O exemplo principal é as definições de recursos que você usa para especificar o que sua infraestrutura terá a semelhança. Em comparação com os tipos estruturais, as colecionas também agrupam valores, mas apenas com valores de mesmo tipo. Existem três tipos de coleções disponíveis no HCL que temos um interesse especial: listas, mapas e conjuntos.
Listas
As listas são similares às arrays em outras linguagens de programação. Eles contêm um número conhecido de elementos de mesma função, que pode ser acessado usando notação de índice de array ([]
) por meio de índices inteiros, começando de 0. Aqui está um exemplo de uma declaração de variável de lista que contém nomes de Droplet que você irá implantar nas próximas etapas:
Para o tipo
, você especificou que é uma lista cujo elemento é uma string, e então forneceu seu valor padrão
. Na HCL, valores enumerados entre parênteses signifiquem uma lista.
Mapas
Mapas são coleções de pares chave-valor, em que cada valor é acessado usando sua chave de tipo string
. Há duas maneiras de especificar mapas dentro de parênteses curvas: usando colones (:
) ou iguais (=
) para especificar valores. Em ambos os casos, o valor deve ser envolvido com aspas. Quando se usa uma coluna, a chave também deve ser envolvida.
A definição da mapa a seguir que contém nomes de gotas para diferentes ambientes é escrita usando o igual:
Se a chave começar com um número, você deve usar a sintaxe de colunas:
Conjuntos
Os conjuntos não suportam ordem de acesso, significando que percorrer conjuntos não garante que eles seja feito na mesma ordem cada vez e que seus elementos não podem ser acessados de maneira algummente objetivada. Eles contêm elementos únicos repetidos exatamente uma vez e especificar o mesmo elemento múltiplas vezes resultará em eles sendo agregados com apenas uma instância presente no conjunto.
Declarar um conjunto é semelhante à declaração de uma lista, a única diferença sendo o tipo da variável:
Após ter aprendido sobre as estruturas de dados HCL disponíveis e revisitado a sintaxe de listas, mapas e conjuntos, que você usará durante todo este tutorial, você irá tentar algumas maneiras flexíveis de deployar várias instâncias do mesmo recurso no Terraform.
Configurando o Número de Recursos Usando a Chave count
Nesta seção, você criará múltiplas instâncias do mesmo recurso usando a chave count
. A chave count
está disponível em todos os recursos e especifica quantas instâncias criar.
Veja como funciona escrevendo um recurso de Droplets que você armazenará no arquivo droplets.tf
na pasta do projeto que você criou como parte das pré-requisitos. Crie e abra para editar executando:
Adicione as linhas seguintes:
Este código define um recurso de Droplets chamado test_droplet
, com Ubuntu 20.04, 1GB de RAM e uma única vCPU.
Notem que o valor da chave count
é definido como 3
, o que significa que o Terraform tentará criar três instâncias idênticas do mesmo recurso. Quando você estiver pronto, salve e fecha o arquivo.
Você pode planejar o projeto para ver quais ações o Terraform iria fazer executando:
A saída será semelhante a esta:
Detalhes da saída que o Terraform criará três instâncias de test_droplet
, todas com o mesmo nome web
. Embora possível, não é a opção preferida, então vamos modificar a definição do Droplet para tornar o nome de cada instância único. Abra droplets.tf
para edição:
Modifique a linha destacada:
Salve e feche o arquivo.
O objeto count
fornece o parâmetro index
, que contém o índice da iteração atual, começando de 0. O índice atual é substituído no nome do Droplet usando interpolação de string, que permite construir dinamicamente uma string substituindo variáveis. Você pode planejar o projeto novamente para ver as mudanças:
A saída será semelhante a esta:
Desta vez, as três instâncias de test_droplet
terão seu índice em seus nomes, facilitando o rastreamento.
Agora você sabe como criar múltiplas instâncias de um recurso usando a chave count
, bem como buscar e usar o índice de uma instância durante a provisionamento. Próximo, você aprenderá como buscar o nome do Droplet de uma lista.
Obter Nomes de Gotas De uma Lista
Em situações em que múltiplos instâncias do mesmo recurso precisam ter nomes personalizados, você pode obter-los dinamicamente de uma variável de lista que definiu. Durante o resto da guia de instruções, veremos várias maneiras de automatizar a deploy de gotas de um arquivo de nomes personalizados, promovendo flexibilidade e facilidade de uso.
Você primeiro precisa definir uma lista contendo os nomes das gotas. Abra um arquivo chamado variables.tf
e edite-o:
Adicione as seguintes linhas:
Salve e fecha o arquivo. Este código define uma lista chamada droplet_names
, que contém os string first
, second
, third
, e fourth
.
Abra droplets.tf
para editar:
Modifique as linhas destacadas:
Para melhorar a flexibilidade, ao invés de especificar manualmente o número de elementos, você passa o parâmetro count
para o número de elementos na lista droplet_names
, que sempre retornará o número de elementos na lista. Para o nome, você faz fetch no elemento da lista posicionado no parâmetro count.index
, usando notação de índice de arrays. Salve e fecha o arquivo quando terminar.
Tente planejar o projeto novamente. Você receberá saída semelhante a esta:
Com base nestas modificações, quatro Droplet seriam implantados, respectivamente nomeados com base nos elementos da lista droplet_names
.
Você aprendeu sobre count
, suas funcionalidades e sintaxe, e como usá-lo juntamente com uma lista para modificar as instâncias dos recursos. Agora você verá os desvantagens dele, e como superá-los.
Entendendo os Desvantagens de count
Agora que você sabe como o count é usado, vamos examinar seus desvantagens quando modificamos a lista com a qual ele é usado.
Vamos tentar implantar os Droplet na nuvem:
Digite yes
quando solicitado. O final de seu output será semelhante a isto:
OutputApply complete! Resources: 4 added, 0 changed, 0 destroyed.
Agora vamos criar mais um instância de Droplet aumentando a lista droplet_names
. Abra variables.tf
para edição:
Adicione um novo elemento no início da lista:
Depois que você estiver pronto, salve e feche o arquivo.
Planejar o projeto:
Você receberá um resultado como este:
O que mostra é que o Terraform renomeará os quatro primeiros Droplet e criará um quinto chamado quarto
, porque ele considera as instâncias como uma lista ordenada e identifica os elementos (Droplet) pelo número de índice na lista. Assim é como o Terraform inicialmente considera as quatro Droplet:
Index Number | 0 | 1 | 2 | 3 |
---|---|---|---|---|
Droplet Name | first | second | third | fourth |
Quando o novo Droplet zero
é adicionado ao início, sua representação interna parece assim:
Index Number | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
Droplet Name | zero | first | second | third | fourth |
Os quatro Droplet iniciais agora são deslizados para a direita um lugar. O Terraform então compara as duas estratégias de estado representadas em tabelas: na posição 0
, o Droplet era chamado primeiro, e porque é diferente na segunda tabela, planeja uma ação de atualização. Isso continua até a posição 4
, que não tem um elemento comparável na primeira tabela, e em vez disso, uma ação de provisionamento de Droplet é planeada.
Essa informação significa que adicionar um novo elemento à lista em qualquer lugar além do último resultaria em recursos sendo modificados quando não houveram de ser. Ações semelhantes de atualização seriam planeadas se um elemento da lista droplet_names
fosse removido.
A falha principal de usar count
para implantar uma quantidade dinâmica de instâncias diferentes do mesmo recurso é o rastreio de recursos incompleto. Para uma quantidade constante de instâncias constantes do mesmo recurso, count
é uma solução simples que funciona bem. Em situações como esta, porém, quando algum atributo está sendo retirado de uma variável, o loop for_each
, que você aprenderá mais tarde no tutorial, é uma escolha muito melhor.
Referência ao Recurso Atual (self
)
Outro aspecto negativo de count
é que não é possível referir-se a uma instância arbitrária de um recurso pelo índice na maioria das vezes.
O exemplo principal é os provisorias de tempo de destruição, que são executadas quando o recurso é planejado para ser destruído. O motivo é que a instância requisitada pode não existir (já foi destruída) ou criaria um ciclo de dependências mutuais. Nesses casos, ao invés de referir-se ao objeto através da lista de instâncias, você pode acessar somente o recurso atual através do operador self
.
Para demonstrar sua utilização, você agora adicionará um provisor de tempo de destruição local à definição do test_droplet
, o que mostrará uma mensagem quando executado. Abra droplets.tf
para editar:
Adicione as linhas destacadas abaixo:
Salve e fechte o arquivo.
O provisor local-exec
executa uma comanda na máquina em que o Terraform está sendo executado. Porque o parâmetro when
é setado para destroy
, ele irá rodar somente quando o recurso será destruído. A comanda que roda escreve uma string no stdout
, que substitui o nome da instância atual usando self.name
.
Porque você criará os Dropletos de maneira diferente na próxima seção, destruir as atuais implantadas executando o seguinte comando:
Digite sim
quando solicitado. Você receberá a execução do provisor local-exec
quatro vezes:
Neste passo, você aprendeu os desvantagens do count
. Agora você aprenderá sobre o construto de laço for_each
, que supera eles e funciona com um maior array de tipos de variáveis.
Looping Usando for_each
Nesta seção, você considerará o laço for_each
, sua sintaxe, e como ele ajuda a flexibilidade ao definir recursos com várias instâncias.
for_each
é um parâmetro disponível em cada recurso, mas diferentemente de count
, que requer um número de instâncias para criar, for_each
aceita um mapa ou um conjunto. Cada elemento da coleção fornecida é percorrido uma vez e é criada uma instância para ele. O for_each
faz com que a chave e o valor estejam disponíveis sob a palavra-chave each
como atributos (a chave e o valor do par como each.key
e each.value
, respectivamente). Quando um conjunto é fornecido, a chave e o valor serão os mesmos.
Como ele fornece o elemento atual no objeto each
, você não precisará acessar manualmente o elemento desejado, como fez com as listas. No caso de conjuntos, isso nem sequer é possível, pois internamente não tem uma ordem observável. As listas também podem ser passadas, mas elas devem primeiro ser convertidas em um conjunto usando a função toset
.
O principal benefício de usar for_each
, além de ser capaz de enumeração de todos os três tipos de dados de coleção, é que apenas os elementos afetados serão modificados, criados ou excluídos. Se você mudar a ordem dos elementos na entrada, nenhuma ação será planejada, e se você adicionar, remover ou modificar um elemento da entrada, ações apropriadas serão planejadas apenas para esse elemento.
Vamos converter o recurso Droplet de count
para for_each
e ver como funciona na prática. Abra droplets.tf
para edição executando:
Modifique as linhas destacadas:
Você pode remover o provisionador local-exec
. Quando terminar, salve e feche o arquivo.
A primeira linha substitui count
e chama for_each
, passando a lista droplet_names
na forma de um conjunto usando a função toset
, que converte automaticamente o input fornecido. Para o nome de Droplet, você especifica each.value
, que mantém o valor do elemento atual do conjunto de nomes de Droplet.
Planeje o projeto executando:
A saída detalhará as etapas que o Terraform faria:
Em contraste com o uso de count
, o Terraform agora considera cada instância individualmente, e não como elementos de uma lista ordenada. Cada instância está ligada a um elemento do conjunto fornecido, conforme indicado pela string de elemento mostrada dentro dos parênteses adjacentes a cada recurso que será criado.
Aplique o plano ao cloud executando:
Digite yes
quando solicitado. Quando terminar, você removerá um elemento da lista droplet_names
para mostrar que outras instâncias não serão afetadas. Abra variables.tf
para edição:
Modifique a lista para parecer assim:
Salvar e fechar o arquivo.
Planeje o projeto novamente, e você receberá o seguinte saída:
Este vez, o Terraform destruiria apenas a instância removida (zero
), e não tocaria nenhuma das outras instâncias, o que é o comportamento correto.
Neste passo, você aprendeu sobre o for_each
, como usar e as vantagens sobre o count
. A próxima, você aprenderá sobre a estrutura de laço for
, sua sintaxe e uso, e quando pode ser utilizado para automatizar determinadas tarefas.
Looping Usando for
O loop for
funciona em coleções e cria uma nova coleção aplicando uma transformação a cada elemento da entrada. O tipo exato do resultado dependerá se o loop está encerrado em parênteses ([]
) ou colchetes ({}
), o que dá um lista ou um mapa, respectivamente. Assim, é adequado para consultar recursos e formar saídas estruturadas para processamento posterior.
A sintaxe geral do loop for
é:
Semelhante às outras linguagens de programação, você primeiro nomeia a variável de travessão (element
) e especifica o collection
para enumerar. O corpo do loop é o passo de transformação, e a clausula opcional if
pode ser usada para filtrar a coleção de entrada.
Você agora trabalhará com alguns exemplos usando saídas. Você irá armazenar elas em um arquivo chamado outputs.tf
. Crie-lo para edição executando o seguinte comando:
Adicione as linhas seguintes para salvar os parâmetros de pares de nomes das instâncias Droplet implantadas e suas adresses IP:
Este código especifica uma saída chamada ip_addresses
, e especifica um loop que percorre sobre as instâncias da resource_test_droplet
que você personalizou nas etapas anteriores. Porque o loop está encerrado entre parênteses, sua saída será um mapa. A etapa de transformação para mapas é semelhante a funções lambda em outras linguagens de programação, e aqui ele cria um par chave-valor combinando o nome da instância como chave e sua IP privada como valor.
Salve e fechar o arquivo, então atualize o estado do Terraform para contar com a saída nova de infraestrutura executando:
O comando refresh
do Terraform atualiza o estado local com o estado real da infraestrutura no cloud.
Então, verifique os conteudos dos outputs:
O que o Terraform mostrou é o conteúdo do resultado ip_addresses
, que é um mapa construído pela loop for
. A ordem das entradas pode ser diferente para você. O loop funciona sem interrupções para cada número de entradas—seu Droplet será criado sem qualquer input manual adicional e também aparecerá automaticamente no resultado deste loop.
Envolvendo o loop em chaves quadradas, você pode fazer o output ser uma lista. Por exemplo, se voce quiser exibir apenas os endereços IP dos Droplet, isso é útil para software externo que possa estar analisando dados. O código teria um aspecto assim:
Aqui, o passo de transformação seleciona o atributo IP da instância. Ele iria dar o seguinte output:
Como foi notado antes, você também pode filtrar a coleção de entrada usando o cláusula if
. Aqui é como você escreveria o loop para filtrar por a região fra1
:
No HCL, o operador ==
verifica a igualdade dos valores das duas partes—aqui ele checa se instance.region
é igual a fra1
. Se sim, a verificação passa e a instância é transformada e adicionada à saída, caso contrário ela é ignorada. O output deste código seria o mesmo que o exemplo anterior, pois todas as instâncias de Droplet definidas na resource "digitalocean_droplet" "test_droplet"
estão na região fra1
, conforme a definição do recurso. A condição if
também é útil quando você deseja filtrar a coleção de entrada para outros valores em seu projeto, como o tamanho ou distribuição.
Porque você irá criar recursos de maneira diferente na próxima seção, destrua os atuais implantados executando o comando seguinte:
Digite sim
quando solicitado para concluir o processo.
Veremos mais adiante sobre o loop for
, sua sintaxe e exemplos de uso em saídas. Agora você aprenderá sobre condicionais e como eles podem ser usados juntamente com count
.
Diretivas e Condicionais
Na seção anterior, você viu a chave count
e como funciona. Agora você aprenderá sobre operadores condicionais ternários, que pode ser usado em outras partes do seu código Terraform e como eles podem ser usados com count
.
A sintaxe do operador ternário é:
condição
é uma expressão que computa para um booleano (verdadeiro ou falso). Se a condição for verdadeira, então a expressão valida value_if_true
. No entanto, se a condição for falsa, o resultado será value_if_false
.
O principal uso dos operadores ternários é permitir ou desabilitar a criação única de recursos de acordo com o conteúdo de uma variável. Isso pode ser feito passando o resultado da comparação (seja 1
ou 0
) para a chave count
no desejado recurso.
Na ocasião em que você estiver usando o operador ternário para obter um único elemento de uma lista ou conjunto, você pode usar a função one
. Se a coleção for vazia, ela retorna null
. Caso contrário, ela retorna o único elemento na coleção, ou lança um erro se houver múltiplos.
Abra o arquivo variables.tf
para editar:
Adicione as linhas destacadas:
Este código define a variável create_droplet
, que controlará se um Droplet será criado. Primeiro, abra o arquivo droplets.tf
para editar:
Adicione as linhas destacadas:
Este código define a variável create_droplet
do tipo bool
. Salve e fecha o arquivo.
Então, para modificar a declaração do Droplet, abra o arquivo droplets.tf
para editar executando:
Modifique seu arquivo como o seguinte:
Para count
, você usa um operador ternário para retornar 1
se a variável create_droplet
for verdadeira, ou 0
se falso, o que resultará em nenhum Droplet sendo provisionado. Salve e fecha o arquivo quando terminar.
Planeje a execução do plano de projeto com a variável definida como falsa executando:create_droplet
foi passada com o valor de false
, o count
de instâncias é 0
, e não haverá Droplets criados, portanto, não haverá IPs para exibir.
Você já revisou como usar o operador condicional ternário junto com a chave count
para habilitar uma flexibilidade maior na escolha de quais recursos desejar deployar. A seguir, você aprenderá sobre explicitamente definir dependências de recursos para os seus recursos.
Definindo Dependências Explícitas de Recursos
Enquanto criando o plano de execução do seu projeto, o Terraform deteta as cadeias de dependências entre recursos e implicamente ordena-los de maneira que serem construídos em ordem apropriada. Na maioria dos casos, é capaz de detectar relacionamentos por meio de todas as expressões em recursos e construir um gráfico.
No entanto, quando um recurso precisa de configurações de controle de acesso para ser provisionado no provedor de nuvem, não há sinal clara para o Terraform que eles sejam relacionados behavioralmente. Em vez disso, o Terraform não saberá que eles são dependentes de outros comportamentos. Nesses casos, a dependência deve ser especificada manualmente usando o argumento depends_on
.
O campo depends_on
está disponível em cada recurso e é usado para especificar as ligações de dependências ocultas entre recursos específicos. As dependências ocultas surgem quando um recurso depende de outro sem usar nenhuma informação dele na sua declaração, o que faria o Terraform conectá-los de uma maneira diferente.
Aqui é um exemplo de como depends_on
é especificado em código:
Ela aceita uma lista de referências a outros recursos e não aceita expressões arbitrárias.
depends_on
deve ser usado com moderação e somente quando todas as outras opções forem exaustadas. Seu uso significa que você está tentando declarar algo que está passando dos limites do sistema de detecção automática de dependências do Terraform; pode significar que o recurso depende explicitamente de mais recursos do que necessita.
Você aprendera agora sobre a definição explícita de dependências adicionais para um recurso usando a chave depends_on
e quando deve ser usada.
Conclusão
Neste artigo, passamos pelas funcionalidades do HCL que melhoram a flexibilidade e a escalabilidade do seu código, como o count
para especificar o número de instâncias de recursos a serem deployados e o for_each
como uma maneira avançada de percorrer tipos de dados de coleção e personalizar instâncias. Quando usados corretamente, eles reduzem significativamente a duplicação de código e o sobrecarga de operação de gerenciamento da infraestrutura deployada.
Você também aprendera sobre condicionais e operadores ternários e como eles podem ser usados para controlar se um recurso será deployado. Embora o sistema de análise automática de dependências do Terraform seja capaz, existem casos em que você precisará especificar dependências de recursos manualmente usando a chave depends_on
.
Esse tutorial faz parte da série Como Gerenciar Infraestrutura com Terraform. A série cobre uma variedade de tópicos sobre Terraform, desde a instalação do Terraform pela primeira vez até o gerenciamento de projetos complexos.