Vamos destacar algumas armadilhas, incluindo aquelas relacionadas a ciclos, expressões if e metodologias de implantação, assim como problemas mais gerais que afetam o Terraform como um todo:
- os parâmetros count e for_each têm limitações;
- limitações de implantações com tempo de inatividade zero;
- mesmo um bom plano pode falhar;
- o refatoramento pode ter suas armadilhas;
- a consistência eventual se alinha... com a procrastinação.
Os parâmetros count e for_each têm limitações
Nos exemplos deste capítulo, o parâmetro count e a expressão for_each são usados ativamente em ciclos e lógica condicional. Eles funcionam bem, mas têm duas limitações importantes que você precisa saber.
- Não é possível fazer referência a variáveis de saída de nenhum recurso em count e for_each.
- count e for_each não podem ser usados na configuração do módulo.
Não é possível fazer referência a variáveis de saída de nenhum recurso em count e for_each
Imagine que você precise implantar várias instâncias EC2 e, por algum motivo, não deseja usar ASG. Seu código pode ser assim:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Vamos analisá-los um a um.
Como o parâmetro count recebe um valor estático, esse código funcionará sem problemas: quando você executar o comando apply, ele criará três instâncias EC2. Mas e se você quisesse implantar uma instância em cada zona de disponibilidade (Availability Zone ou AZ) no atual região AWS? Você pode fazer seu código carregar a lista de zonas de uma fonte de dados aws_availability_zones e então passar "ciclamente" por cada uma delas e criar uma instância EC2 nelas, usando o parâmetro count e o acesso ao array pelo índice:
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" {}Esse código também funcionará bem, pois o parâmetro count pode referir-se a fontes de dados sem problemas. Mas o que acontece se o número de instâncias que você precisa criar depende da saída de algum recurso? Para demonstrar isso, é mais fácil pegar o recurso random_integer, que, como você pode imaginar pelo nome, retorna um número inteiro aleatório:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Este código gera um número aleatório de 1 a 3. Vamos ver o que acontece se tentarmos usar a saída result deste recurso no parâmetro count do recurso aws_instance:
resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}Se você executar o terraform plan para este código, obterá o seguinte erro:
Error: Invalid count argument
on main.tf line 30, in resource "aws_instance" "example_3":
30: count = random_integer.num_instances.result
O valor "count" depende de atributos do recurso que não podem ser determinados até a aplicação, então o Terraform não pode prever quantas instâncias serão criadas. Para contornar isso, use o argumento -target para aplicar primeiro apenas os recursos dos quais o count depende.O Terraform exige que count e for_each sejam calculados na fase de planejamento, antes da criação ou modificação de qualquer recurso. Isso significa que count e for_each podem referenciar literais, variáveis, fontes de dados e até listas de recursos (desde que seu comprimento possa ser determinado durante o planejamento), mas não variáveis de saída calculadas do recurso.
count e for_each não podem ser usados na configuração do módulo
Um dia você pode sentir a tentação de adicionar o parâmetro count nas configurações do módulo:
module "count_example" {
source = "..\/..\/..\/..\/modules\/services\/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}Este código tenta usar count dentro do módulo para criar três cópias do recurso webserver-cluster. Ou, talvez, você queira tornar a conexão do módulo opcional com base em alguma condição booleana, atribuindo o valor 0 ao parâmetro count. Esse código pareceria bastante razoável, no entanto, ao executar o terraform plan você receberá o seguinte erro:
Error: Reserved argument name in module block
on main.tf line 13, in module "count_example":
13: count = 3
O nome "count" é reservado para uso em uma versão futura do Terraform.Infelizmente, na versão do Terraform 0.12.6, o uso de count ou for_each no recurso module não é suportado. De acordo com as notas de lançamento do Terraform 0.12 (http://bit.ly/3257bv4), a HashiCorp planeja adicionar esse recurso no futuro, então, dependendo de quando você está lendo este livro, ele já pode estar disponível. Para ter certeza, .
Limitações de implantações com zero tempo de inatividade
O uso do bloco create_before_destroy em conjunto com o ASG é uma ótima solução para organizar implantações com tempo de inatividade zero, exceto por um detalhe: as regras de escalonamento automático não são suportadas. Ou, para ser mais preciso, isso redefine o tamanho do ASG de volta para min_size a cada implantação, o que pode se tornar um problema se você tiver usado regras de escalonamento automático para aumentar a quantidade de servidores em funcionamento.
Por exemplo, o módulo webserver-cluster contém alguns recursos aws_autoscaling_schedule, que às 9 da manhã aumentam o número de servidores no cluster de dois para dez. Se você realizar uma implantação, digamos, às 11 da manhã, o novo grupo ASG não será iniciado com dez, mas com apenas dois servidores e permanecerá assim até às 9 da manhã do dia seguinte.
Essa limitação pode ser contornada de várias maneiras.
- Mudar o parâmetro recurrence no aws_autoscaling_schedule de 0 9 * * * ('executar às 9 da manhã') para algo como 0-59 9-17 * * * ('executar a cada minuto das 9 da manhã às 5 da tarde'). Se já houver dez servidores no ASG, a reexecução dessa regra de escalonamento automático não mudará nada, que é exatamente o que precisamos. Mas se o grupo ASG foi implantado recentemente, essa regra garante que em no máximo um minuto o número de servidores chegue a dez. Essa não é uma abordagem exatamente elegante, e grandes saltos de dez para dois servidores e vice-versa também podem causar problemas para os usuários.
- Criar um script personalizado que utilize a API AWS para determinar o número de servidores ativos no ASG, chamá-lo de uma fonte de dados externa (veja a seção 'Fonte de dados externa' na p. 249) e atribuir ao parâmetro desired_capacity do grupo ASG o valor retornado por esse script. Assim, cada nova instância do ASG será sempre iniciada com a mesma capacidade, o que complica a manutenção do código Terraform.
Claro, idealmente o Terraform deveria ter suporte integrado para implantações com tempo de inatividade zero, mas até maio de 2019 a equipe da HashiCorp não planejava adicionar essa funcionalidade ().
Um plano correto pode ser mal executado
Às vezes, ao executar o comando plan, um plano de implementação bastante correto é gerado, no entanto, o comando apply retorna um erro. Experimente, por exemplo, adicionar o recurso aws_iam_user com o mesmo nome que você usou para o usuário IAM que você criou anteriormente no capítulo 2:
resource "aws_iam_user" "existing_user" {
# Substitua aqui pelo nome de um usuário IAM já existente,
# para praticar o uso do comando terraform import
name = "yevgeniy.brikman"
}Agora, se você executar o comando plan, o Terraform exibirá à primeira vista um plano de implementação bastante razoável:
Terraform executará as seguintes ações:
# aws_iam_user.existing_user será criado
+ resource "aws_iam_user" "existing_user" {
+ arn = (conhecido após aplicar)
+ force_destroy = false
+ id = (conhecido após aplicar)
+ name = "yevgeniy.brikman"
+ path = "/"
+ unique_id = (conhecido após aplicar)
}
Plano: 1 a adicionar, 0 a modificar, 0 a destruir.Se você executar o comando apply, obterá o seguinte erro:
Error: Erro ao criar Usuário IAM yevgeniy.brikman: EntityAlreadyExists:
Usuário com o nome yevgeniy.brikman já existe.
na linha 10 do main.tf, no recurso "aws_iam_user" "existing_user":
10: resource "aws_iam_user" "existing_user" {O problema é que um usuário IAM com esse nome já existe. E isso pode acontecer não apenas com usuários IAM, mas também com quase qualquer recurso. É possível que alguém tenha criado esse recurso manualmente ou através da linha de comando, mas, de qualquer forma, a coincidência de identificadores leva a conflitos. Existem muitas variações desse erro que frequentemente pegam os iniciantes do Terraform desprevenidos.
O ponto chave é que o comando terraform plan considera apenas os recursos que estão no arquivo de estado do Terraform. Se os recursos forem criados de outra forma (por exemplo, manualmente, clicando na console da AWS), eles não aparecerão no arquivo de estado e, portanto, o Terraform não os considerará ao executar o comando plan. Como resultado, um plano que parece correto à primeira vista acabará falhando.
A partir disso, podem-se extrair duas lições.
- Se você já começou a trabalhar com o Terraform, não use nada mais. Se parte da sua infraestrutura é gerida através do Terraform, não é mais possível modificá-la manualmente. Caso contrário, você não apenas corre o risco de obter erros estranhos do Terraform, mas também anula muitos dos benefícios do IaC, já que o código não será mais uma representação precisa da sua infraestrutura.
- Se você já tem alguma infraestrutura, use o comando import. Se você está começando a usar o Terraform com uma infraestrutura existente, ela pode ser adicionada ao arquivo de estado com o comando terraform import. Assim, o Terraform saberá qual infraestrutura deve gerenciar. O comando import aceita dois argumentos. O primeiro é o endereço do recurso em seus arquivos de configuração. Aqui o mesmo formato é usado que nas referências de recursos: _. (como aws_iam_user.existing_user). O segundo argumento é o identificador do recurso que deve ser importado. Por exemplo, no caso do ID do recurso aws_iam_user, o nome de usuário é utilizado (por exemplo, yevgeniy.brikman), enquanto no ID do recurso aws_instance será o identificador do servidor EC2 (como i-190e22e5). Como importar um recurso geralmente é indicado na documentação na parte inferior da sua página.
Abaixo está o comando import que sincroniza o recurso aws_iam_user que você adicionou à sua configuração Terraform junto com o usuário IAM no Capítulo 2 (naturalmente, em vez de yevgeniy.brikman, insira seu nome):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanO Terraform fará uma chamada à API da AWS para encontrar seu usuário IAM e criar uma conexão no arquivo de estado entre ele e o recurso aws_iam_user.existing_user na sua configuração Terraform. A partir desse momento, ao executar o comando plan, o Terraform saberá que o usuário IAM já existe e não tentará criá-lo novamente.
É importante notar que, se você já tem muitos recursos que deseja importar para o Terraform, escrever manualmente o código e importar cada um deles um por um pode ser uma tarefa trabalhosa. Portanto, vale a pena considerar uma ferramenta como o Terraforming (http://terraforming.dtan4.net/), que pode importar automaticamente o código e o estado da sua conta AWS.
O refatoramento pode ter suas pegadinhas
Refatoração — é uma prática comum em programação, onde você altera a estrutura interna do código sem modificar seu comportamento externo. Isso é feito para tornar o código mais legível, limpo e fácil de manter. A refatoração é uma técnica essencial que deve ser aplicada regularmente. Mas, ao falar sobre Terraform ou qualquer outra ferramenta de IaC, é preciso ter extremo cuidado com o que se entende por "comportamento externo" de um trecho de código, ou poderão surgir problemas imprevistos.
Por exemplo, uma forma comum de refatoração é substituir nomes de variáveis ou funções por nomes mais claros. Muitas IDEs têm suporte embutido para refatoração e podem renomear automaticamente variáveis e funções em todo o projeto. Em linguagens de programação de uso geral, isso é uma tarefa trivial, mas no Terraform é preciso ter muito cuidado, caso contrário, você pode enfrentar interrupções.
Por exemplo, o módulo webserver-cluster tem uma variável de entrada chamada cluster_name:
variable "cluster_name" { description = "O nome a ser usado para todos os recursos do cluster" type = string }Imagine que você começou a usar este módulo para implantar um microserviço chamado foo. Mais tarde, você decidiu renomear seu serviço para bar. Essa mudança pode parecer trivial, mas na verdade pode causar interrupções.
O fato é que o módulo webserver-cluster utiliza a variável cluster_name em uma série de recursos, incluindo o parâmetro name de dois grupos de segurança e do 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] }Se você mudar o parâmetro name em algum recurso, o Terraform removerá a versão antiga desse recurso e criará uma nova. Mas se esse recurso for um ALB, no período entre sua remoção e o carregamento da nova versão, você não terá um mecanismo para redirecionar o tráfego para seu servidor web. Da mesma forma, se um grupo de segurança for removido, seus servidores começarão a rejeitar qualquer tráfego de rede até que um novo grupo seja criado.
Outra forma de refatoração que pode lhe interessar é a alteração do identificador do Terraform. Vamos tomar como exemplo o recurso aws_security_group no módulo webserver-cluster:
resource "aws_security_group" "instance" { # (...) }O identificador desse recurso é chamado instance. Suponha que durante a refatoração você decida mudá-lo para um nome mais claro (na sua opinião) cluster_instance:
resource "aws_security_group" "cluster_instance" { # (...) }O que acontecerá, então? Certo: interrupção no serviço.
O Terraform associa o ID de cada recurso ao identificador do provedor de nuvem. Por exemplo, o iam_user é associado ao ID do usuário IAM na AWS, e o aws_instance é vinculado ao ID do servidor AWS EC2. Se você alterar o identificador do recurso (digamos, de instance para cluster_instance, como no caso do aws_security_group), para o Terraform isso parecerá que você removeu o recurso antigo e adicionou um novo. Ao aplicar essas alterações, o Terraform removerá o antigo grupo de segurança e criará outro, enquanto isso, seus servidores começarão a rejeitar qualquer tráfego de rede.
Aqui estão quatro lições-chave que você deve extrair dessa discussão.
- Sempre utilize o comando plan. Com ele, é possível identificar todas essas situações complicadas. Revise cuidadosamente sua saída e preste atenção em situações em que o Terraform planeja excluir recursos que provavelmente não deveriam ser excluídos.
- Crie antes de excluir. Se você deseja substituir um recurso, pense bem se deve criar a substituição antes de remover o original. Se a resposta for sim, o recurso create_before_destroy pode ajudar. O mesmo resultado pode ser alcançado manualmente, seguindo dois passos: primeiro, adicione o novo recurso à configuração e execute o comando apply, em seguida, remova o recurso antigo da configuração e utilize o comando apply novamente.
- Alterar identificadores requer mudanças no estado. Se você deseja alterar o identificador associado a um recurso (por exemplo, renomear aws_security_group de instance para cluster_instance), evitando a exclusão do recurso e a criação de uma nova versão, é necessário atualizar adequadamente o arquivo de estado do Terraform. Nunca faça isso manualmente — utilize o comando terraform state. Ao renomear identificadores, você deve executar o comando terraform state mv, que tem a seguinte sintaxe:
terraform state mvORIGINAL_REFERENCE é uma expressão que se refere ao recurso em sua forma atual, enquanto NEW_REFERENCE é o local para onde você deseja movê-lo. Por exemplo, ao renomear o grupo aws_security_group de instance para cluster_instance, você deve executar o seguinte comando:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceAssim, você informará ao Terraform que o estado anteriormente associado ao aws_security_group.instance agora deve estar vinculado ao aws_security_group.cluster_instance. Se, após renomear e executar este comando, o terraform plan não mostrar nenhuma alteração, significa que você fez tudo certo.
- Algumas configurações não podem ser alteradas. Muitas configurações de recursos são imutáveis. Se você tentar alterá-las, o Terraform excluirá o recurso antigo e criará um novo em seu lugar. Na página de cada recurso, costuma-se indicar o que acontece ao modificar determinada configuração, portanto, não se esqueça de consultar a documentação. Sempre use o comando plan e considere a viabilidade de aplicar a estratégia create_before_destroy.
A consistência eventual é compatível… com a procrastinação
APIs de alguns provedores de nuvem, como AWS, são assíncronas e têm consistência eventual. A assíncrona significa que a interface pode retornar uma resposta imediatamente, sem esperar a conclusão da ação solicitada. A consistência eventual significa que a propagação das alterações por todo o sistema pode levar tempo; enquanto isso acontece, suas respostas podem estar inconsistentes e depender de qual réplica da fonte de dados está respondendo às suas chamadas de API.
Imagine, por exemplo, que você faz uma chamada de API para a AWS solicitando a criação de um servidor EC2. A API retornará uma resposta "bem-sucedida" (201 Created) quase instantaneamente, sem esperar pela criação do próprio servidor. Se você tentar se conectar a ele imediatamente, é quase certo que nada acontecerá, pois nesse momento a AWS ainda está inicializando os recursos ou, alternativamente, o servidor ainda não foi carregado. Além disso, se você fizer outra chamada para obter informações sobre esse servidor, pode receber um erro (404 Not Found). O fato é que as informações sobre esse servidor EC2 ainda podem estar sendo propagadas pela AWS, e pode levar alguns segundos até que se tornem disponíveis em todos os lugares.
Ao usar uma API assíncrona com consistência eventual, você deve repetir seu pedido periodicamente até que a ação seja concluída e propagada pelo sistema. Infelizmente, o AWS SDK não fornece boas ferramentas para isso, e o projeto Terraform anteriormente sofreu com muitos erros como o 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply aws_subnet.private-persistence.2: InvalidSubnetID.NotFound: O ID da sub-rede 'subnet-xxxxxxx' não existeEm outras palavras, você está criando um recurso (como uma sub-rede) e então tenta obter algumas informações sobre ele (como o ID da sub-rede recém-criada), mas o Terraform não consegue encontrá-las. A maioria desses erros (incluindo 6813) já foi corrigida, mas de vez em quando eles ainda aparecem, especialmente quando o Terraform adiciona suporte para um novo tipo de recurso. Isso é irritante, mas na maioria das vezes não causa nenhum dano. Ao executar novamente o terraform apply, tudo deve funcionar, pois até o momento a informação já terá se espalhado pelo sistema.
Este trecho é retirado do livro de Evgeny Brikman .
Fonte: habr.com
