ノート:

Enable Terraform State File Locking with Amazon S3 Compatible Backend in OCI

イントロダクション

クラウド・コンピューティングの動的な世界において、Infrastructure as Code(IaC)は、インフラストラクチャを効果的に管理しようとする組織にとって重要なアプローチとして出現しました。IaC’s key advantage lies in its ability to promote consistency, automation, version control, and collaboration, making it an indispensable element of cloud-native IT strategies.

Terraformは、著名なIaCツールとして際立っており、インフラストラクチャ・オブジェクトとその依存関係の描写をterraform.tfstateという名前の構成ファイルに格納します。複数のチーム・メンバーがクラウド・インフラストラクチャを管理しているコラボレーション環境では、terraform.tfstateをローカルに格納することは困難になります。これに対処するために、Terraformには「リモート・バックエンド」と呼ばれる機能があり、共有の場所に状態ファイルを格納できます。一部のバックエンドはtfstateロックをサポートしていますが、planまたはapply操作はデータの整合性を確保し、競合を回避するために実行されます。

This tutorial will focus on how to set up the S3 compatible backend and ScyllaDB’s DynamoDB-compatible API to enable state file locking.

目的

前提条件

アプローチ1: 自動デプロイメント

下の「Oracle Cloudにデプロイ」をクリックし、必要な詳細を入力して「適用」をクリックします。

Deploy to OCI

アプローチ2: 手動デプロイメント

Task 1: Setup ScyllaDB and enable DynamoDB-compatible API

We will create an ARM based instance, install Docker, and configure and run ScyllaDB.

タスク1.1: 新規インスタンスのプロビジョニング

  1. Navigate to the Instances page in the OCI Console and click Create Instance.

  2. 次の推奨事項を考慮して、必要な構成パラメータを入力します。

    • イメージ: Oracle Linux 8

    • シェイプ: VM.Standard.A1.Flex (1 OCPU, 6 GB RAM)

    • Primary VNIC: Public Subnet & Assign Public IPv4 Address (will be used for SSH connectivity)

    Note down the public and private IP address of the instance.

タスク1.2: Dockerのインストール

  1. Connect to the Instance via SSH.

    $ ssh opc@<public-ip-address-of-the-instance>
    
  2. Docker Engine、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. Reconnect the SSH session and validate the successful installation of 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: 顧客秘密キーの生成

The customer secret key is required to access OCI Object Storage using the S3-Compatible API.

  1. Navigate to your user profile page in the OCI Console and select Customer secret keys.

  2. 新しいキーを生成し、秘密キー値をコピーして、「閉じる」をクリックします。

  3. アクセス・キー値(このリストの2番目の列と顧客秘密キー)をコピーします。

タスク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'
    

    ノート: .envファイルは、Docker構成でScyllaDBを設定するために使用されます。

  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-pipおよびboto3パッケージをインストールします。

      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が返された場合、期待どおりに動作しています。

Task 2: Configure OCI API Gateway to secure connections to the ScyllaDB DynamoDB-compatible API

In this task, we will configure OCI API Gateway to take advantage of the TLS encryption between the users and the ScyllaDB.

Task 2.1: Create a new API Gateway

  1. Navigate to the API Gateway page in the OCI Console and click Create Gateway.

    • 名前: s3-locking

    • タイプ: public

    • Network: Same VCN and Subnet as the ScyllaDB instance

    • 証明書: Default (*.oci.oci-customer.com)

  2. Write down the hostname associated with the new created API Gateway. The hostname is displayed in the Gateway information tab when you click the new created API Gateway resource.

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

Task 2.2: Create a new API Gateway deployment

  1. 新しいデプロイメントを作成します。

    1. Click the new created API Gateway, and in the left-side menu, click Deployments under Resources.

    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. Get the CIDR block allocated to the subnet in use, using the following steps.

    2. 次のステップを使用して、インスタンスで使用されるサブネットに関連付けられているセキュリティ・リストを識別します。

    3. サブネットにすでに関連付けられているデフォルトのセキュリティ・リストをクリックし、次のルールを追加します。

      • イングレス

        • Source CIDR: 0.0.0.0/0

        • プロトコル: TCP

        • 宛先ポート範囲: 443

        • 説明: Ingress Access to the API Gateway

      • イングレス

        • Source CIDR: <subnet CIDR>

        • プロトコル: TCP

        • 宛先ポート範囲: 8000

        • 説明: Ingress connection to the ScyllaDB

      • 送信

        • Destination CIDR: <subnet CIDR>

        • プロトコル: TCP

        • 宛先ポート範囲: 8000

        • 説明: Egress connection from the API Gateway backend to ScyllaDB

Task 2.3: Validate the connection to the ScyllaDB via the API Gateway

  1. Update the endpoint_url in the file script.py to use the API Gateway hostname.たとえば: endpoint_url = "https://fj4etyuvz3s57jdsadsadsadsa.apigateway.eu-frankfurt-1.oci.customer-oci.com"

  2. スクリプトを実行して、パブリック・エンドポイントへの接続をテストします。

    python3 script.py
    s3-locking-demo ACTIVE
    

Task 3: Test terraform.tfstate file locking when using S3-Compatible API

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. Configure the S3 backend.

    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 planまたはterraform 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. Manage entries in the ScyllaDB tables using DynamoDB API.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 Help Centerを参照してください。