Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terraform: faz infraestrutura ser gerenciada por código #208

Merged
merged 2 commits into from
Mar 7, 2022

Conversation

filipedeschamps
Copy link
Owner

@filipedeschamps filipedeschamps commented Mar 4, 2022

Depois quero fazer um vídeo ou escrever no diário de desenvolvimento sobre a abordagem de usar o Terraform para ter os ambientes de Staging e Production similares (e não idênticos, porque terá diferenças como tamanho do banco, retenção de backups e várias outras características dos recursos). Mas, a topologia se mantém a mesma, o que é muito massa 👍

[edit] como eu altero o .env na parte dos dados default para criação do banco local, após o merge desse PR você precisará deletar o container e o estado do banco no seu Docker 🤝

@vercel
Copy link

vercel bot commented Mar 4, 2022

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/tabnews/tabnews/45LQn8K3iH5z8gpNSr8JA6mLxSaF
✅ Preview: https://tabnews-git-terraform-tabnews.vercel.app

@tcarreira
Copy link

tcarreira commented Mar 5, 2022

Estou agradavelmente surpreendido pelo código que vi, parabéns 👏
Se vc nunca trabalhou com terraform, ou teve boa ajuda ou vc é mesmo bom

Tenho 2 perguntas:

  • como é que o bucket s3 é criado dentro do modulo state e usado ao mesmo tempo como backend? Julgava que isso era impossível, já que o terraform precisa verificar o estado antes de poder rodar.

  • talvez seja boa ideia usar prevent_destroy em resources sensíveis, como redes ou ACL.
    isso previne erros humanos, como alterar o nome de uma resource, que se traduziria em remover a resource para criar ela novamente.
    Mas não sei se vc quer deixar assim até o projeto estar mesmo em produção?

Nota final: não trabalho com AWS há alguns anos, não tenho as boas práticas tão presentes, vou deixar alguém comentar sobre isso

Copy link

@huogerac huogerac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @filipedeschamps ficou bem legal a estrutura. Divisão por ambiente e recursos.
Eu estou aprovando, eu consegui rodar o terraform init e terraform plan.
Não dei o apply, pq não queria gastar umas doletas...sorry

@huogerac
Copy link

huogerac commented Mar 5, 2022

Requisitos

  • instalar terraform
  • instalar aws cli
  • criar o bucket no S3 (precisa ter um bucket criado)
  • ter usuário com par de chaves (access & secret key) com acesso ao S3

Configurar AWS CLI

$ aws configure
   AWS Access Key ID [None]: ********
   AWS Secret Access Key [None]: **********
   Default region name [None]: us-east-1
   Default output format [None]: json

Rodar terraform

cd infra/provisioning/production
terraform init
terraform plan
terraform apply (não rodei este)

Resultado

(todolist) ➜  production git:(terraform) ✗ terraform init
Initializing modules...

Initializing the backend...
bucket
  The name of the S3 bucket

  Enter a value: mytabnews-br-test1


Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding hashicorp/aws versions matching "4.2.0"...
- Finding latest version of hashicorp/random...
- Installing hashicorp/aws v4.2.0...
- Installed hashicorp/aws v4.2.0 (signed by HashiCorp)
- Installing hashicorp/random v3.1.0...
- Installed hashicorp/random v3.1.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
(todolist) ➜  production git:(terraform) ✗ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.database.aws_db_instance.postgres will be created
  + resource "aws_db_instance" "postgres" {
      + address                               = (known after apply)
      + allocated_storage                     = 20
      + allow_major_version_upgrade           = true
      + apply_immediately                     = true
      + arn                                   = (known after apply)
      + auto_minor_version_upgrade            = true
      + availability_zone                     = (known after apply)
      + backup_retention_period               = 7
      + backup_window                         = "04:00-05:00"
      + ca_cert_identifier                    = (known after apply)
      + character_set_name                    = (known after apply)
      + copy_tags_to_snapshot                 = true
      + db_name                               = "tabnews"
      + db_subnet_group_name                  = "production-postgres-subnet-group"
      + delete_automated_backups              = false
      + deletion_protection                   = true
      + endpoint                              = (known after apply)
      + engine                                = "postgres"
      + engine_version                        = "14.1"
      + engine_version_actual                 = (known after apply)
      + final_snapshot_identifier             = (known after apply)
      + hosted_zone_id                        = (known after apply)
      + id                                    = (known after apply)
      + identifier                            = (known after apply)
      + identifier_prefix                     = (known after apply)
      + instance_class                        = "db.t4g.micro"
      + kms_key_id                            = (known after apply)
      + latest_restorable_time                = (known after apply)
      + license_model                         = (known after apply)
      + maintenance_window                    = "wed:06:00-wed:07:00"
      + max_allocated_storage                 = 100
      + monitoring_interval                   = 0
      + monitoring_role_arn                   = (known after apply)
      + multi_az                              = false
      + name                                  = (known after apply)
      + nchar_character_set_name              = (known after apply)
      + option_group_name                     = (known after apply)
      + parameter_group_name                  = (known after apply)
      + password                              = (sensitive value)
      + performance_insights_enabled          = false
      + performance_insights_kms_key_id       = (known after apply)
      + performance_insights_retention_period = (known after apply)
      + port                                  = (known after apply)
      + publicly_accessible                   = true
      + replica_mode                          = (known after apply)
      + replicas                              = (known after apply)
      + resource_id                           = (known after apply)
      + skip_final_snapshot                   = false
      + snapshot_identifier                   = (known after apply)
      + status                                = (known after apply)
      + storage_type                          = "gp2"
      + tags                                  = {
          + "Environment" = "production"
          + "Name"        = "production-postgres"
        }
      + tags_all                              = {
          + "Environment" = "production"
          + "Name"        = "production-postgres"
        }
      + timezone                              = (known after apply)
      + username                              = (known after apply)
      + vpc_security_group_ids                = (known after apply)
    }

  # module.database.aws_db_subnet_group.postgres_subnet_group will be created
  + resource "aws_db_subnet_group" "postgres_subnet_group" {
      + arn         = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "production-postgres-subnet-group"
      + name_prefix = (known after apply)
      + subnet_ids  = (known after apply)
      + tags        = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-subnet-group"
        }
      + tags_all    = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-subnet-group"
        }
    }

  # module.database.aws_route_table_association.postgres_route_table_association[0] will be created
  + resource "aws_route_table_association" "postgres_route_table_association" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.database.aws_route_table_association.postgres_route_table_association[1] will be created
  + resource "aws_route_table_association" "postgres_route_table_association" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.database.aws_security_group.postgres_security_group will be created
  + resource "aws_security_group" "postgres_security_group" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = [
                  + "::/0",
                ]
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = (known after apply)
              + ipv6_cidr_blocks = [
                  + "::/0",
                ]
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = (known after apply)
            },
        ]
      + name                   = "production-postgres-security-group"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = true
      + tags                   = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-security-group"
        }
      + tags_all               = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-security-group"
        }
      + vpc_id                 = (known after apply)
    }

  # module.database.aws_subnet.postgres_subnet[0] will be created
  + resource "aws_subnet" "postgres_subnet" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "sa-east-1a"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "10.0.0.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = true
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-subnet-0"
        }
      + tags_all                                       = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-subnet-0"
        }
      + vpc_id                                         = (known after apply)
    }

  # module.database.aws_subnet.postgres_subnet[1] will be created
  + resource "aws_subnet" "postgres_subnet" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = "sa-east-1b"
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "10.0.1.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = true
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-subnet-1"
        }
      + tags_all                                       = {
          + "Environment" = "production"
          + "Name"        = "production-postgres-subnet-1"
        }
      + vpc_id                                         = (known after apply)
    }

  # module.database.random_integer.postgres_port will be created
  + resource "random_integer" "postgres_port" {
      + id     = (known after apply)
      + max    = 49151
      + min    = 1024
      + result = (known after apply)
    }

  # module.database.random_integer.postgres_random_identifier_suffix will be created
  + resource "random_integer" "postgres_random_identifier_suffix" {
      + id     = (known after apply)
      + max    = 999999999999
      + min    = 0
      + result = (known after apply)
    }

  # module.database.random_password.postgres_password will be created
  + resource "random_password" "postgres_password" {
      + id               = (known after apply)
      + length           = 99
      + lower            = true
      + min_lower        = 0
      + min_numeric      = 0
      + min_special      = 0
      + min_upper        = 0
      + number           = true
      + override_special = "-+=_^~,."
      + result           = (sensitive value)
      + special          = true
      + upper            = true
    }

  # module.database.random_string.postgres_username will be created
  + resource "random_string" "postgres_username" {
      + id          = (known after apply)
      + length      = 62
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + result      = (known after apply)
      + special     = false
      + upper       = true
    }

  # module.state.aws_kms_alias.kms_s3_key_alias will be created
  + resource "aws_kms_alias" "kms_s3_key_alias" {
      + arn            = (known after apply)
      + id             = (known after apply)
      + name           = "alias/production-terraform-state"
      + name_prefix    = (known after apply)
      + target_key_arn = (known after apply)
      + target_key_id  = (known after apply)
    }

  # module.state.aws_kms_key.s3_key will be created
  + resource "aws_kms_key" "s3_key" {
      + arn                                = (known after apply)
      + bypass_policy_lockout_safety_check = false
      + customer_master_key_spec           = "SYMMETRIC_DEFAULT"
      + deletion_window_in_days            = 10
      + description                        = "production-s3-key"
      + enable_key_rotation                = true
      + id                                 = (known after apply)
      + is_enabled                         = true
      + key_id                             = (known after apply)
      + key_usage                          = "ENCRYPT_DECRYPT"
      + multi_region                       = (known after apply)
      + policy                             = (known after apply)
      + tags                               = {
          + "Environment" = "production"
          + "Name"        = "production-s3-key"
        }
      + tags_all                           = {
          + "Environment" = "production"
          + "Name"        = "production-s3-key"
        }
    }

  # module.state.aws_s3_bucket.terraform_state will be created
  + resource "aws_s3_bucket" "terraform_state" {
      + acceleration_status                  = (known after apply)
      + acl                                  = (known after apply)
      + arn                                  = (known after apply)
      + bucket                               = (known after apply)
      + bucket_domain_name                   = (known after apply)
      + bucket_regional_domain_name          = (known after apply)
      + cors_rule                            = (known after apply)
      + force_destroy                        = true
      + grant                                = (known after apply)
      + hosted_zone_id                       = (known after apply)
      + id                                   = (known after apply)
      + lifecycle_rule                       = (known after apply)
      + logging                              = (known after apply)
      + policy                               = (known after apply)
      + region                               = (known after apply)
      + replication_configuration            = (known after apply)
      + request_payer                        = (known after apply)
      + server_side_encryption_configuration = (known after apply)
      + tags                                 = {
          + "Environment" = "production"
          + "Name"        = "production-terraform-state"
        }
      + tags_all                             = {
          + "Environment" = "production"
          + "Name"        = "production-terraform-state"
        }
      + versioning                           = (known after apply)
      + website                              = (known after apply)
      + website_domain                       = (known after apply)
      + website_endpoint                     = (known after apply)

      + object_lock_configuration {
          + object_lock_enabled = (known after apply)
          + rule                = (known after apply)
        }
    }

  # module.state.aws_s3_bucket_acl.acl will be created
  + resource "aws_s3_bucket_acl" "acl" {
      + acl    = "private"
      + bucket = (known after apply)
      + id     = (known after apply)

      + access_control_policy {
          + grant {
              + permission = (known after apply)

              + grantee {
                  + display_name  = (known after apply)
                  + email_address = (known after apply)
                  + id            = (known after apply)
                  + type          = (known after apply)
                  + uri           = (known after apply)
                }
            }

          + owner {
              + display_name = (known after apply)
              + id           = (known after apply)
            }
        }
    }

  # module.state.aws_s3_bucket_public_access_block.public_access will be created
  + resource "aws_s3_bucket_public_access_block" "public_access" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

  # module.state.aws_s3_bucket_server_side_encryption_configuration.encryption will be created
  + resource "aws_s3_bucket_server_side_encryption_configuration" "encryption" {
      + bucket = (known after apply)
      + id     = (known after apply)

      + rule {
          + apply_server_side_encryption_by_default {
              + kms_master_key_id = (known after apply)
              + sse_algorithm     = "aws:kms"
            }
        }
    }

  # module.state.aws_s3_bucket_versioning.versioning will be created
  + resource "aws_s3_bucket_versioning" "versioning" {
      + bucket = (known after apply)
      + id     = (known after apply)

      + versioning_configuration {
          + mfa_delete = (known after apply)
          + status     = "Enabled"
        }
    }

  # module.state.random_string.s3_bucket_postfix will be created
  + resource "random_string" "s3_bucket_postfix" {
      + id          = (known after apply)
      + length      = 40
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + result      = (known after apply)
      + special     = false
      + upper       = false
    }

  # module.vpc.aws_internet_gateway.internet_gateway will be created
  + resource "aws_internet_gateway" "internet_gateway" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Environment" = "production"
          + "Name"        = "production-igw"
        }
      + tags_all = {
          + "Environment" = "production"
          + "Name"        = "production-igw"
        }
      + vpc_id   = (known after apply)
    }

  # module.vpc.aws_route_table.route_table will be created
  + resource "aws_route_table" "route_table" {
      + arn              = (known after apply)
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + carrier_gateway_id         = ""
              + cidr_block                 = "0.0.0.0/0"
              + destination_prefix_list_id = ""
              + egress_only_gateway_id     = ""
              + gateway_id                 = (known after apply)
              + instance_id                = ""
              + ipv6_cidr_block            = ""
              + local_gateway_id           = ""
              + nat_gateway_id             = ""
              + network_interface_id       = ""
              + transit_gateway_id         = ""
              + vpc_endpoint_id            = ""
              + vpc_peering_connection_id  = ""
            },
        ]
      + tags             = {
          + "Environment" = "production"
          + "Name"        = "production-rt"
        }
      + tags_all         = {
          + "Environment" = "production"
          + "Name"        = "production-rt"
        }
      + vpc_id           = (known after apply)
    }

  # module.vpc.aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Environment" = "production"
          + "Name"        = "production-vpc"
        }
      + tags_all                             = {
          + "Environment" = "production"
          + "Name"        = "production-vpc"
        }
    }

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

Changes to Outputs:
  + database_address  = (known after apply)
  + database_db_name  = "tabnews"
  + database_password = (sensitive value)
  + database_port     = (known after apply)
  + database_username = (known after apply)

@filipedeschamps
Copy link
Owner Author

@tcarreira e @huogerac que honra meus caros, muuuito obrigado por comentar nesse PR 🤝 👍

Estou agradavelmente surpreendido pelo código que vi, parabéns 👏 Se vc nunca trabalhou com terraform, ou teve boa ajuda ou vc é mesmo bom

Repasso os créditos para as toneladas de artigos e documentação que li, principalmente documentação no final para fazer tudo na v4 do Terraform. Outra pessoa que foi muito legal trocar ideia foi o @hposca e o maior desafio não foi o Terraform em sí, e mais entender as peças que a AWS fornece para você trabalhar.

  • como é que o bucket s3 é criado dentro do modulo state e usado ao mesmo tempo como backend? Julgava que isso era impossível, já que o terraform precisa verificar o estado antes de poder rodar.

Para resolver isso eu primeiro usei o Terraform (com state local) para criar o bucket, e depois configurei o backend nele e repassei o state para o bucket.

  • talvez seja boa ideia usar prevent_destroy em resources sensíveis, como redes ou ACL.
    isso previne erros humanos, como alterar o nome de uma resource, que se traduziria em remover a resource para criar ela novamente.

Bom ponto! Não sei se estou errado, mas esses recursos não tem state la na AWS, correto? Por state digo, eles não tem dados como um banco de dados como o Postgres no RDS. No módulo dele usado no ambiente de produção eu seto a proteção de delete como true:

deletion_protection = var.deletion_protection

De qualquer forma, as duas infras já estão deployadas, segue a de produção: https://www.tabnews.com.br/api/v1/status

As migrations não foram rodadas ainda, devo fazer hoje ainda e daí já vai dar para criar contas 👍

Então vou fazer merge nesse PR e vamos continuar a evoluir!

E @huogerac muito obrigado pelo teste meu caro!!!! 🎉 👍 🤝

@filipedeschamps filipedeschamps merged commit 0e26d15 into main Mar 7, 2022
@filipedeschamps filipedeschamps deleted the terraform branch March 7, 2022 16:28
@liverday
Copy link
Contributor

liverday commented Mar 7, 2022

Sensacional essa feature, aqui na empresa nós tambem usamos terraform mas confesso que não sei nada sobre isso. Onde foi que você "aprendeu" tão rápido? Me ensina

@filipedeschamps
Copy link
Owner Author

@liverday o ponto inicial foi esse vídeo aqui: https://youtu.be/SLB_c_ayRMo

Depois fui pesquisando artigos sobre boas práticas e ficando aflito porque todos eles estavam na versão 3 😂 e toda vez que o Terraform me dava uma rasteira eu chorava com o @hposca e ao explicar o que tava acontecendo, automaticamente me ajudava a entender melhor alguns detalhes que eu não percebia.

E o mais importante é encarar de fato o Terraform como código e abstrações assim como estamos fazendo no código do TabNews. Então eu primeiro criei toda a infra num arquivo só todo maluco pra conseguir ver e sentir o que precisava ser abstraído e onde. Daí aprendi sobre módulos (que são basicamente funções) e ficou fácil abstrair tudo que precisava no state, depois vpc e database e declarar isso nos dois ambientes de staging e production.

Eu também acho que aprendi rápido, mas isso tá longe de ser experiência. Com certeza essa rapidez vai me picar a bunda logo logo por falta de experiência 😂 vamos torcer para que os próximos PRs aqui serem revisados por pessoas mais experientes e para o ambiente de Staging segurar erros da vida real 🤝 👍

@tcarreira
Copy link

tcarreira commented Mar 7, 2022

para fazer tudo na v4 do Terraform

eu ignorei no início, mas quando vc fala em v4 do Terraform, eu acho que vc quer dizer v4 do provider da AWS, certo 😄 ? O terraform está na versão 1.1.7 😛


Para resolver isso eu primeiro usei o Terraform (com state local) para criar o bucket, e depois configurei o backend nele e repassei o state para o bucket.

Mas então porque o bucket tem um valor random? bucket = "${var.environment}-tfstate-${random_string.s3_bucket_postfix.result}"


  • talvez seja boa ideia usar prevent_destroy em resources sensíveis, como redes ou ACL.

Bom ponto! Não sei se estou errado, mas esses recursos não tem state la na AWS, correto?

Acho que todos os recursos que este código tem estão já salvaguardados de alguma forma (eg: o deletion_protection que existe no aws_db_instance como vc mencionou).

Mas o que estou mencionando, é alguma operação que faz vc trancar a porta com a chave dentro. Alguma operação que faz o terraform deixar de ter acessos, por exemplo. Pode ocorrer em situações em que vc acha que ele vai fazer alguma alteração in-place, mas que remove para voltar a criar depois.

Eu acho seguro o código como está.
Podemos assumir que talvez exista alguma operação altamente estranha, que iria remover acessos ao terraform. Se isso acontecer na realidade, aí se faz um processo de recuperação de credenciais manualmente e se corrige o erro. Não acho que vai ter uma forma de dar um delete acidental de dados.

@filipedeschamps
Copy link
Owner Author

eu ignorei no início, mas quando vc fala em v4 do Terraform, eu acho que vc quer dizer v4 do provider da AWS, certo 😄 ? O terraform está na versão 1.1.7 😛

😂 correto 👍

Mas então porque o bucket tem um valor random? bucket = "${var.environment}-tfstate-${random_string.s3_bucket_postfix.result}"

Excelente pergunta: como o arquivo .tf está sendo commitado aqui e vai ficar público, eu não queria que ninguém soubesse qual o caminho correto das coisas. Isso começou com o banco de dados (que precisa ser público atualmente por conta da Vercel) e todos os dados deles são randomizados na hora da criação. O nome do bucket que guarda state seguiu o mesmo raciocínio, onde apesar dele ter os acessos públicos desabilitados, optei por randomizar o nome dele para evitar que as pessoas saibam o caminho exato do bucket e fiquem testando e aguardando por algum descuido de política de acesso aos arquivos.

Tanto que na hora de declarar o backend no arquivo raiz de cada ambiente, eu não especifico o nome dele propositalmente. Ele é fornecido uma única vez no prompt que aparece ao fazer o terraform init

Mas o que estou mencionando, é alguma operação que faz vc trancar a porta com a chave dentro. Alguma operação que faz o terraform deixar de ter acessos, por exemplo. Pode ocorrer em situações em que vc acha que ele vai fazer alguma alteração in-place, mas que remove para voltar a criar depois.

Ahh entendi perfeitamente! Faz sentido situações 👍 nesse caso, por enquanto estou usando o acesso root da conta. Mais pra frente seria legal definir e usar acessos menos privilegiados.

@tcarreira
Copy link

Excelente pergunta: como o arquivo .tf está sendo commitado aqui e vai ficar público, eu não queria que ninguém soubesse qual o caminho correto das coisas.

nesse caso, sugiro adicionar uma variable no código do deploy, como vc faz com region e environment.
Na minha opinião fica mais claro o que está acontecendo, e vc trata esses valores como qualquer outro secret.

variable "bucket_name" {
  type        = string
  description = "Bucket name (you should override this on the real deployment)"
  sensitive   = true
  default     = "production-tfstate-abcdefghijklmnopqrstuvwxyz0123456789"
}

aí, na hora de rodar o plan/apply vc declara qual o valor do bucket (com -var 'bucket_name=bar' ou com -var-file=secrets-file.tfvars).

@filipedeschamps
Copy link
Owner Author

Show! Mas please me ajuda a clarear algumas coisas: O valor default ali não poderia estar preenchido, correto? Para não commitar ele e revelar o bucket.

@tcarreira
Copy link

tcarreira commented Mar 8, 2022

O valor default ali não poderia estar preenchido, correto? Para não commitar ele e revelar o bucket.

O valor é isso mesmo, um default, tal como chamar uma função com parâmetros default 😉 .
Assim, quem quiser subir o projeto, pode apenas usar os defaults.

E vc pode continuar a usar o mesmo código, mas na hora de rodar contra o ambiente real, basta sobrescrever a variável.
Assim, usando aquela variável que mostrei acima, vc pode usar ela no plan/apply:

terraform apply -var 'bucket_name=production-tfstate-xxxxxxxxxxxxxxxxxxxxx' 

No futuro, se vc tiver mais variáveis que vc não quer commitar, vc pode até manter um arquivo secrets.tfvars (no .gitignore) e usar ele da mesma forma que o .env:

terraform plan -var-file secrets.tfvars

Isto até permite configurar um passo ContinuousIntegration (eg: com o github actions) e botar o valor nos secrets do projeto. Aí o código vai rodar com o valor certo, mesmo usando o código que não tem o valor real.

Ficou mais claro? Se não ficou, é só falar 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants