Observação:

Ativar Bloqueio de Arquivo de Estado do Terraform com Backend Compatível com Amazon S3 no OCI

Introdução

No mundo dinâmico da computação em nuvem, a Infraestrutura como Código (IaC) surgiu como uma abordagem crucial para as organizações que buscam gerenciar efetivamente sua infraestrutura. A principal vantagem da IaC está em sua capacidade de promover consistência, automação, controle de versão e colaboração, tornando-a um elemento indispensável das estratégias de TI nativas da nuvem.

O Terraform se destaca como uma ferramenta IaC proeminente, ele armazena uma representação de seus objetos de infraestrutura e suas dependências em um arquivo de configuração chamado terraform.tfstate. Em um ambiente colaborativo em que vários membros da equipe gerenciam a infraestrutura de nuvem, armazenar o terraform.tfstate localmente se torna um desafio. Para resolver isso, o Terraform oferece um recurso chamado "Backend Remoto" para ativar o armazenamento do arquivo de estado em um local compartilhado. Alguns dos backends suportam bloqueio tfstate enquanto as operações plan ou apply são executadas para garantir a integridade dos dados e evitar conflitos.

Este tutorial se concentrará em como configurar o backend compatível com S3 e a API compatível com DynamoDB do ScyllaDB para ativar o bloqueio de arquivos de estado.

Objetivos

Pré-requisitos

Abordagem 1: Implantação Automática

Clique abaixo em Implantar no Oracle Cloud, informe os detalhes necessários e clique em Aplicar.

Implantar no OCI

Abordagem 2: Implantação Manual

Tarefa 1: Configurar ScyllaDB e ativar a API compatível com DynamoDB

Criaremos uma instância baseada em ARM, instalaremos o Docker e configuraremos e executaremos o ScyllaDB.

Tarefa 1.1: Provisionar uma nova instância

  1. Navegue até a página Instâncias na Console do OCI e clique em Criar Instância.

  2. Informe os parâmetros de configuração necessários considerando as recomendações abaixo.

    • Imagem: Oracle Linux 8

    • Forma: VM.Standard.A1.Flex (1 OCPU, 6 GB RAM)

    • VNIC Principal: Public Subnet & Assign Public IPv4 Address (será usado para conectividade SSH)

    Anote o endereço IP público e privado da instância.

Tarefa 1.2: Instalar o Docker

  1. Conecte-se à Instância via SSH.

    $ ssh opc@<public-ip-address-of-the-instance>
    
  2. Instale o Docker Engine, containerd e o Docker Compose.

    sudo yum install -y yum-utils
    sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    sudo systemctl start docker.service
    sudo systemctl enable docker.service
    sudo usermod -aG docker opc
    
  3. Reconecte a sessão SSH e valide a instalação bem-sucedida do Docker.

    docker run hello-world
    
    # Unable to find image 'hello-world:latest' locally
    # latest: Pulling from library/hello-world
    # 70f5ac315c5a: Pull complete
    # Digest: sha256:3155e04f30ad5e4629fac67d6789f8809d74fea22d4e9a82f757d28cee79e0c5
    # Status: Downloaded newer image for hello-world:latest
    
    # Hello from Docker!
    # This message shows that your installation appears to be working correctly.
    

Tarefa 1.3: Gerar Chave Secreta do Cliente

A chave secreta do cliente é necessária para acessar o OCI Object Storage usando a API Compatível com S3.

  1. Navegue até a página de perfil do usuário na Console do OCI e selecione Chaves secretas do cliente.

  2. Gere uma nova chave, copie o valor da chave secreta e clique em Fechar.

  3. Copie o valor da chave de acesso (segunda coluna nesta lista com as chaves secretas do cliente).

Tarefa 1.4: Configurar e iniciar ScyllaDB

  1. Crie o diretório de implantação s3-lock.

    mkdir s3-lock
    cd s3-lock
    
  2. Crie o .env usando o comando a seguir.

    AWS_ACCESS_KEY_ID='<ACCESS_KEY>'
    AWS_SECRET_ACCESS_KEY='<SECRET_KEY>'
    TF_STATE_TABLE='s3-locking-demo'
    

    Observação: o arquivo .env será usado pelo Docker para configurar ScyllaDB.

  3. Crie um arquivo scylladb.Dockerfile no diretório scylladb usando o comando a seguir.

    FROM scylladb/scylla:latest
    RUN echo "alternator_enforce_authorization: true" >> /etc/scylla/scylla.yaml
    ENTRYPOINT ["/docker-entrypoint.py"]
    
  4. Crie o arquivo docker-compose.yaml no diretório s3-lock usando o comando a seguir.

    version: "3.3"
    
    services:
      scylladb:
        build:
          dockerfile: scylladb.Dockerfile
          context: ./scylladb
        image: "local-scylla:latest"
        container_name: "scylladb"
        restart: always
        command: ["--alternator-port=8000", "--alternator-write-isolation=always"]
        ports:
          - "8000:8000"
          - "9042:9042"
    
      scylladb-load-user:
        image: "scylladb/scylla:latest"
        container_name: "scylladb-load-user"
        depends_on:
          - scylladb
        entrypoint: /bin/bash -c "sleep 60 && echo loading cassandra keyspace && cqlsh scylladb -u cassandra -p cassandra \
                                  -e \"INSERT INTO system_auth.roles (role,can_login,is_superuser,member_of,salted_hash) \
                                  VALUES ('${AWS_ACCESS_KEY_ID}',True,False,null,'${AWS_SECRET_ACCESS_KEY}');\""
    
      scylladb-create-table:
        image: "amazon/aws-cli"
        container_name: "create_table"
        depends_on:
          - scylladb
        env_file: .env
        entrypoint: /bin/sh -c "sleep 70 && aws dynamodb create-table --table-name ${TF_STATE_TABLE} \
                                --attribute-definitions AttributeName=LockID,AttributeType=S \
                                --key-schema AttributeName=LockID,KeyType=HASH --billing-mode=PAY_PER_REQUEST \
                                --region 'None' --endpoint-url=http://scylladb:8000"
    
  5. Revise a estrutura de diretório.

    $ tree -a .
    .
    ├── docker-compose.yaml
    ├── .env
    └── scylladb
        └── scylladb.Dockerfile
    
    1 directory, 3 files
    
  6. Inicie o serviço ScyllaDB.

    docker compose up -d
    
  7. Permita conexões de entrada com a porta 8000.

    sudo firewall-cmd --add-port 8000/tcp --permanent
    
  8. Valide a conexão com ScyllaDB.

    • Instale os pacotes python3-pip e boto3.

      sudo yum install -y python3-pip
      python3 -m pip install --user boto3
      
    • Crie o arquivo script.py usando o comando a seguir.

      import boto3
      
      endpoint_url = 'http://localhost:8000'
      aws_access_key_id = '<ACCESS_KEY>'
      aws_secret_access_key = '<SECRET_KEY>'
      table_name = "s3-locking-demo"
      
      client = boto3.client('dynamodb', endpoint_url=endpoint_url, region_name="None",
                      aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key)
      
      response = client.describe_table(TableName=table_name)
      
      print(response["Table"]["TableName"], response["Table"]["TableStatus"])
      
    • Execute o script usando o comando a seguir.

      python3 script.py
      

    Se a execução do script retornar s3-locking-demo ACTIVE, ela estará funcionando conforme esperado.

Tarefa 2: Configurar o Gateway de API do OCI para proteger conexões com a API compatível com ScyllaDB DynamoDB

Nesta tarefa, configuraremos o Gateway de API do OCI para aproveitar a criptografia TLS entre os usuários e o ScyllaDB.

Tarefa 2.1: Criação de um novo Gateway de API

  1. Navegue até a página API Gateway na Console do OCI e clique em Criar Gateway.

    • Nome: s3-locking

    • Tipo: public

    • Rede: A mesma VCN e Sub-rede da instância ScyllaDB

    • Certificado: Default (*.oci.oci-customer.com)

  2. Anote o nome do host associado ao novo Gateway de API criado. O nome do host é exibido na guia Informações do gateway quando você clica no novo recurso do Gateway de API criado.

    Por exemplo: fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com

Tarefa 2.2: Criação de uma nova implantação do Gateway de API

  1. Criar uma nova implantação.

    1. Clique no novo Gateway de API criado e, no menu esquerdo, clique em Implantações em Recursos.

    2. Clique em Criar Implantação e crie uma nova implantação usando as informações a seguir.

      • Informações Básicas

        • Nome: default

        • Prefixo do caminho: /

      • Autenticação: Sem Autenticação

      • Rotas

        • Caminho: /{requested_path*}

        • Métodos: ANY

        • Tipo de Backend: HTTP

        • URL: http://<private_ip_address_of_the_instance>:8000/${request.path[requested_path]}

    3. Vá para Políticas de Solicitação de Roteamento, Transformações de Cabeçalho e clique em Adicionar.

      • Ação: Set

      • Comportamento: Overwrite

      • Header Name: Host

      • Valores: <API Gateway hostname> (Por exemplo: fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com)

    4. Verifique os detalhes da nova implantação e clique em Criar.

  2. Configure a lista de segurança de sub-rede para permitir conexão de entrada e saída com a porta 8000.

    1. Obtenha o bloco CIDR alocado para a sub-rede em uso, usando as etapas a seguir.

    2. Identifique a lista de segurança associada à sub-rede usada pela instância usando as etapas a seguir.

    3. Clique na lista de segurança padrão já associada à sub-rede e adicione as regras a seguir.

      • Entrada

        • CIDR de Origem: 0.0.0.0/0

        • Protocolo: TCP

        • Intervalo de Portas de Destino: 443

        • Descrição: Ingress Access to the API Gateway

      • Entrada

        • CIDR de Origem: <subnet CIDR>

        • Protocolo: TCP

        • Faixa de Portas de Destino: 8000

        • Descrição: Ingress connection to the ScyllaDB

      • Saída

        • CIDR de Destino: <subnet CIDR>

        • Protocolo: TCP

        • Faixa de Portas de Destino: 8000

        • Descrição: Egress connection from the API Gateway backend to ScyllaDB

Tarefa 2.3: Validar a conexão com o ScyllaDB por meio do Gateway de API

  1. Atualize o endpoint_url no arquivo script.py para usar o nome de host do Gateway de API. Por exemplo: endpoint_url = "https://fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com"

  2. Execute o script para testar a conexão com o ponto final público.

    python3 script.py
    s3-locking-demo ACTIVE
    

Tarefa 3: Testar o bloqueio do arquivo terraform.tfstate ao usar a API Compatível com S3

Executaremos as etapas a seguir na instância em que queremos executar o código terraform.

  1. Copie as linhas a seguir e crie o arquivo main.tf.

    resource "null_resource" "hello_world" {
      provisioner "local-exec" {
        command = "echo Hello World"
      }
    
      provisioner "local-exec" {
        command = "echo 'sleeping for 30 seconds';sleep 30;echo 'done';"
      }
    
      triggers = {
        run_always = "${timestamp()}"
      }
    }
    
  2. Configure o backend S3.

    terraform {
      backend "s3" {
        bucket = "<bucket-name>" # e.g.: bucket = "sample-bucket"
        region = "<oci-region>"  # e.g.: region = "eu-frankfurt-1"
    
        skip_region_validation      = true
        skip_credentials_validation = true
        skip_metadata_api_check     = true
        # skip_requesting_account_id  = true
        # skip_s3_checksum            = true
    
        force_path_style = true
        # use_path_style = true
        # insecure       = true
    
        # For best practice on how to set credentials access: https://developer.hashicorp.com/terraform/language/settings/backends/s3#access_key
    
        access_key = "<ACCESS_KEY>"
        secret_key = "<SECRET_KEY>"
    
        # endpoints = {
        #   # To determine <objectostrage_namespace> access: https://docs.oracle.com/en-us/iaas/Content/Object/Tasks/understandingnamespaces.htm
        #   s3       = "https://<objectstorage_namespace>.compat.objectstorage.<oci-region>.oraclecloud.com"
        #   # e.g.: s3 = https://axaxnpcrorw5.compat.objectstorage.eu-frankfurt-1.oraclecloud.com
    
        #   # ScyllaDB TLS endpoint, configured using the API Gateway:
        #   dynamodb = "https://<API_Gateway_hostname>"
        #   # e.g.: dynamodb = "https://fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com"
        # }
    
        # ScyllaDB TLS endpoint, configured using the API Gateway:
        dynamodb_endpoint = "https://<API_Gateway_hostname>"
        # e.g.: dynamodb_endpoint = "https://fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com"
        key            = "demo.tfstate" # the name of the tfstate file
        dynamodb_table = "s3-locking-demo" # the name of the table in the ScyllaDB
      }
    }
    
  3. Inicialize o diretório de trabalho com o comando terraform init.

    $ terraform init
    
    Initializing the backend...
    
    Successfully configured the backend "s3"! Terraform will automatically
    use this backend unless the backend configuration changes.
    
    Initializing provider plugins...
    - Finding latest version of hashicorp/null...
    - Installing hashicorp/null v3.2.2...
    - Installed hashicorp/null v3.2.2 (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!
    
  4. Execute terraform apply.

    $ terraform apply
    
    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:
    
      # null_resource.hello_world will be created
      + resource "null_resource" "hello_world" {
          + id       = (known after apply)
          + triggers = {
              + "run_always" = (known after apply)
            }
        }
    
    Plan: 1 to add, 0 to change, 0 to destroy.
    
    Do you want to perform these actions?
      Terraform will perform the actions described above.
      Only 'yes' will be accepted to approve.
    
      Enter a value: yes
    
    null_resource.hello_world: Creating...
    null_resource.hello_world: Provisioning with 'local-exec'...
    null_resource.hello_world (local-exec): Executing: ["/bin/sh" "-c" "echo Hello World"]
    null_resource.hello_world (local-exec): Hello World
    null_resource.hello_world: Provisioning with 'local-exec'...
    null_resource.hello_world (local-exec): Executing: ["/bin/sh" "-c" "echo 'sleeping for 30 seconds';sleep 30;echo 'done';"]
    null_resource.hello_world (local-exec): sleeping for 30 seconds
    null_resource.hello_world: Still creating... [10s elapsed]
    null_resource.hello_world: Still creating... [20s elapsed]
    null_resource.hello_world: Still creating... [30s elapsed]
    null_resource.hello_world (local-exec): done
    null_resource.hello_world: Creation complete after 30s [id=5722520729023050684]
    
    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
    
  5. Teste o bloqueio do arquivo terraform.tfstate. Se você tentar executar terraform plan ou terraform apply durante a execução da tarefa 3.4, sua solicitação será rejeitada.

    $ terraform apply
    ╷
    │ Error: Error acquiring the state lock
    │
    │ Error message: operation error DynamoDB: PutItem, https response error StatusCode: 400, RequestID: ,
    │ ConditionalCheckFailedException: Failed condition.
    │ Lock Info:
    │   ID:        69309f13-d9fc-8c6b-9fbe-73639b340539
    │   Path:      sample-bucket/demo.tfstate
    │   Operation: OperationTypeApply
    │   Who:       use
    │   Version:   1.6.4
    │   Created:   2023-12-14 11:31:30.291168816 +0000 UTC
    │   Info:
    │
    │
    │ Terraform acquires a state lock to protect the state from being written
    │ by multiple users at the same time. Please resolve the issue above and try
    │ again. For most commands, you can disable locking with the "-lock=false"
    │ flag, but this is not recommended.
    
  6. Gerencie entradas nas tabelas ScyllaDB usando a API DynamoDB. Se você precisar listar as entradas na tabela DynamoDB ou remover manualmente as entradas, poderá adicionar as seguintes linhas no final do arquivo script.py.

    scan_response = client.scan(
        TableName=table_name,
    )
    
    print(scan_response)
    
    entry_to_delete = input("what is the LockID value you would like to delete? ")
    
    delete_response = client.delete_item(
        Key={
            'LockID': {
                'S': f'{entry_to_delete}',
            },
        },
        TableName=table_name
        )
    
    print(delete_response)
    

    Observação: mais recursos estão disponíveis neste script.

Agradecimentos

Mais Recursos de Aprendizagem

Explore outros laboratórios em docs.oracle.com/learn ou acesse mais conteúdo de aprendizado gratuito no canal Oracle Learning YouTube. Além disso, visite education.oracle.com/learning-explorer para se tornar um Oracle Learning Explorer.

Para obter a documentação do produto, visite o Oracle Help Center.