注意:

在 OCI 中使用 Amazon S3 兼容后端启用 Terraform 状态文件锁定

简介

在云计算的动态世界中,基础设施即代码 (IaC) 已成为寻求有效管理基础设施的组织的关键方法。IaC 的主要优势在于它能够促进一致性、自动化、版本控制和协作,使其成为云原生 IT 战略不可或缺的要素。

Terraform 作为一个突出的 IaC 工具脱颖而出,它将基础结构对象及其依赖项的描述存储在名为 terraform.tfstate 的配置文件中。在多个团队成员管理云基础设施的协作环境中,将 terraform.tfstate 存储在本地变得非常具有挑战性。为了解决此问题,Terraform 提供了一种名为“远程后端”的功能,可用于在共享位置存储状态文件。某些后端在运行 planapply 操作时支持 tfstate 锁定,以确保数据完整性并防止冲突。

本教程将重点介绍如何设置 S3 兼容后端ScyllaDB 的 DynamoDB 兼容 API 以启用状态文件锁定。

目标

先决条件

方法 1:自动部署

单击下面的部署到 Oracle Cloud ,输入所需详细信息并单击应用

部署到 OCI

方法 2:手动部署

任务 1:设置 ScyllaDB 并启用与 DynamoDB 兼容的 API

我们将创建一个基于 ARM 的实例,安装 Docker,并配置和运行 ScyllaDB。

任务 1.1:预配新实例

  1. 导航到 OCI 控制台中的实例页,然后单击创建实例

  2. 根据以下建议输入所需的配置参数。

    • 图像Oracle Linux 8

    • 配置VM.Standard.A1.Flex (1 OCPU, 6 GB RAM)

    • 主要 VNICPublic Subnet & Assign Public IPv4 Address(将用于 SSH 连接)

    记下实例的公共和专用 IP 地址。

任务 1.2:安装 Docker

  1. 通过 SSH 连接到实例。

    $ ssh opc@<public-ip-address-of-the-instance>
    
  2. 安装 Docker 引擎、containerd 和 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. 重新连接 SSH 会话并验证 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.
    

任务 1.3:生成客户密钥

要使用与 S3 兼容的 API 访问 OCI 对象存储,需要提供客户密钥。

  1. 在 OCI 控制台中导航到您的用户概要信息页,然后选择客户密钥

  2. 生成新密钥,复制密钥值,然后单击关闭

  3. 复制访问键值(此列表中的第二列带有客户密钥)。

任务 1.4:配置并启动 ScyllaDB

  1. 创建部署目录 s3-lock

    mkdir s3-lock
    cd s3-lock
    
  2. 使用以下命令创建 .env

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

    :Docker 编写用来设置 ScyllaDB 的 .env 文件。

  3. 使用以下命令在目录 scylladb 中创建文件 scylladb.Dockerfile

    FROM scylladb/scylla:latest
    RUN echo "alternator_enforce_authorization: true" >> /etc/scylla/scylla.yaml
    ENTRYPOINT ["/docker-entrypoint.py"]
    
  4. 使用以下命令在 s3-lock 目录中创建 docker-compose.yaml 文件。

    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. 查看目录结构。

    $ tree -a .
    .
    ├── docker-compose.yaml
    ├── .env
    └── scylladb
        └── scylladb.Dockerfile
    
    1 directory, 3 files
    
  6. 启动 ScyllaDB 服务。

    docker compose up -d
    
  7. 允许与端口 8000 建立入站连接。

    sudo firewall-cmd --add-port 8000/tcp --permanent
    
  8. 验证与 ScyllaDB 的连接。

    • 安装 python3-pipboto3 软件包。

      sudo yum install -y python3-pip
      python3 -m pip install --user boto3
      
    • 使用以下命令创建文件 script.py

      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"])
      
    • 使用以下命令执行该脚本。

      python3 script.py
      

    如果脚本执行返回 s3-locking-demo ACTIVE,它将按预期工作。

任务 2:配置 OCI API 网关以保护与 ScyllaDB DynamoDB 兼容 API 的连接

在此任务中,我们将配置 OCI API 网关,以利用用户与 ScyllaDB 之间的 TLS 加密。

任务 2.1:创建新的 API 网关

  1. 导航到 OCI 控制台中的 API 网关页面,然后单击创建网关

    • 名称s3-locking

    • 类型public

    • 网络:与 ScyllaDB 实例相同的 VCN 和子网

    • 证书Default (*.oci.oci-customer.com)

  2. 写下与新创建的 API 网关关联的主机名。单击新建的 API 网关资源时,主机名将显示在网关信息选项卡中。

    例如fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com

任务 2.2:创建新的 API 网关部署

  1. 创建新部署。

    1. 单击新建的 API 网关,然后在左侧菜单中,单击资源下的部署

    2. 单击创建部署并使用以下信息创建新部署。

      • 基本信息

        • 名称default

        • 路径前缀/

      • 验证:无验证

      • 路由

        • 路径/{requested_path*}

        • 方法ANY

        • 后端类型HTTP

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

    3. 转到路由请求策略标头转换,然后单击添加

      • 操作Set

      • 行为Overwrite

      • 标题名称Host

      • <API Gateway hostname>(例如:fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com

    4. 查看新部署的详细信息,然后单击创建

  2. 设置子网安全列表以允许与端口 8000 建立入站和出站连接。

    1. 使用以下步骤获取分配给正在使用的子网的 CIDR 块。

    2. 使用以下步骤标识与实例使用的子网关联的安全列表。

    3. 单击已与子网关联的默认安全列表并添加以下规则。

      • 入站

        • 源 CIDR0.0.0.0/0

        • 协议TCP

        • 目标端口范围443

        • 说明Ingress Access to the API Gateway

      • 入站

        • 源 CIDR<subnet CIDR>

        • 协议TCP

        • 目标端口范围8000

        • 说明Ingress connection to the ScyllaDB

      • 出站

        • 目标 CIDR<subnet CIDR>

        • 协议TCP

        • 目标端口范围8000

        • 说明Egress connection from the API Gateway backend to ScyllaDB

任务 2.3:通过 API 网关验证与 ScyllaDB 的连接

  1. 更新文件 script.py 中的 endpoint_url 以使用 API 网关主机名。例如:endpoint_url = "https://fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com"

  2. 运行脚本以测试与公共端点的连接。

    python3 script.py
    s3-locking-demo ACTIVE
    

任务 3:使用 S3 兼容 API 时测试 terraform.tfstate 文件锁定

我们将对要运行 terraform 代码的实例执行以下步骤。

  1. 复制以下行并创建文件 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. 配置 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. 使用 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. 执行 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. 测试 terraform.tfstate 文件锁定。如果在执行任务 3.4 期间尝试执行 terraform planterraform apply,您的请求将被拒绝。

    $ 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. 使用 DynamoDB API 管理 ScyllaDB 表中的条目。如果需要列出 DynamoDB 表中的条目或手动删除条目,可以在 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)
    

    注意脚本中提供了更多功能。

确认

更多学习资源

浏览 docs.oracle.com/learn 上的其他实验室,或者通过 Oracle Learning YouTube 频道访问更多免费学习内容。此外,请访问 education.oracle.com/learning-explorer 以成为 Oracle Learning Explorer。

有关产品文档,请访问 Oracle 帮助中心