Chapter 3. Creating a Red Hat OpenShift Service on AWS cluster with Terraform


Create a Red Hat OpenShift Service on AWS cluster with a Terraform cluster template that is configured with the default cluster options.

The following process for creating a cluster uses a Terraform configuration that prepares a Red Hat OpenShift Service on AWS cluster with these resources:

  • An OpenID Connect (OIDC) provider with a managed oidc-config configuration
  • Prerequisite IAM Operator roles with associated AWS Managed Red Hat OpenShift Service on AWS Policies
  • IAM account roles with associated AWS Managed Red Hat OpenShift Service on AWS Policies
  • All other AWS resources required to create a Red Hat OpenShift Service on AWS cluster

3.1.1. Overview of Terraform

Terraform is an infrastructure-as-code tool that provides a way to configure your resources once and replicate those resources as desired. Terraform accomplishes the creation tasks by using declarative language. You declare what you want the final state of the infrastructure resource to be, and Terraform creates these resources to your specifications.

3.1.2. Prerequisites

To use the Red Hat Cloud Services provider inside your Terraform configuration, you must meet the following prerequisites:

  • You have installed the rosa CLI tool.
  • You have your offline Red Hat OpenShift Cluster Manager token.
  • You have installed Terraform version 1.4.6 or newer.
  • You have created the ocm-role IAM role.
  • You have created your AWS account-wide IAM roles.

    The specific account-wide IAM roles and policies provide the STS permissions required for Red Hat OpenShift Service on AWS support, installation, control plane, and compute functionality. This includes account-wide Operator policies. See the Additional resources for more information on the AWS account roles.

  • You have an AWS account and associated credentials that allow you to create resources. The credentials are configured for the AWS provider. See the Authentication and Configuration section in AWS Terraform provider documentation.
  • You have, at minimum, the following permissions in your AWS IAM role policy that is operating Terraform. Check for these permissions in the AWS console.

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
            "iam:GetPolicyVersion",
            "iam:DeletePolicyVersion",
            "iam:CreatePolicyVersion",
            "iam:UpdateAssumeRolePolicy",
            "secretsmanager:DescribeSecret",
            "iam:ListRoleTags",
            "secretsmanager:PutSecretValue",
            "secretsmanager:CreateSecret",
            "iam:TagRole",
            "secretsmanager:DeleteSecret",
            "iam:UpdateOpenIDConnectProviderThumbprint",
            "iam:DeletePolicy",
            "iam:CreateRole",
            "iam:AttachRolePolicy",
            "iam:ListInstanceProfilesForRole",
            "secretsmanager:GetSecretValue",
            "iam:DetachRolePolicy",
            "iam:ListAttachedRolePolicies",
            "iam:ListPolicyTags",
            "iam:ListRolePolicies",
            "iam:DeleteOpenIDConnectProvider",
            "iam:DeleteInstanceProfile",
            "iam:GetRole",
            "iam:GetPolicy",
            "iam:ListEntitiesForPolicy",
            "iam:DeleteRole",
            "iam:TagPolicy",
            "iam:CreateOpenIDConnectProvider",
            "iam:CreatePolicy",
            "secretsmanager:GetResourcePolicy",
            "iam:ListPolicyVersions",
            "iam:UpdateRole",
            "iam:GetOpenIDConnectProvider",
            "iam:TagOpenIDConnectProvider",
            "secretsmanager:TagResource",
            "sts:AssumeRoleWithWebIdentity",
            "iam:ListRoles"
          ],
          "Resource": [
            "arn:aws:secretsmanager:*:<ACCOUNT_ID>:secret:*",
            "arn:aws:iam::<ACCOUNT_ID>:instance-profile/*",
            "arn:aws:iam::<ACCOUNT_ID>:role/*",
            "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/*",
            "arn:aws:iam::<ACCOUNT_ID>:policy/*"
          ]
        },
        {
          "Sid": "VisualEditor1",
          "Effect": "Allow",
          "Action": [
            "s3:*"
            ],
          "Resource": "*"
        }
      ]
    }

3.1.3. Considerations when using Terraform

In general, using Terraform to manage cloud resources should be done with the expectation that any changes should be done using the Terraform methodology. Use caution when using tools outside of Terraform, such as the AWS console or Red Hat console, to modify cloud resources created by Terraform. Using tools outside Terraform to manage cloud resources that are already managed by Terraform introduces configuration drift from your declared Terraform configuration.

For example, if you upgrade your Terraform-created cluster by using the Red Hat Hybrid Cloud Console, you need to reconcile your Terraform state before applying any forthcoming configuration changes.

You can quickly create a Red Hat OpenShift Service on AWS cluster by using the default installation options.

Table 3.1. Default Red Hat OpenShift Service on AWS cluster specifications
ComponentDefault specifications

Accounts and roles

  • Default IAM role prefix: rosa-<6-digit-alphanumeric-string>

Cluster settings

  • Default cluster version: 4.14
  • Cluster name: rosa-<6-digit-alphanumeric-string>
  • Default AWS region for installations using the Red Hat OpenShift Cluster Manager Hybrid Cloud Console: us-east-2 (US East, Ohio)
  • Availability: Multi zone for the data plane
  • EC2 Instance Metadata Service (IMDS) is enabled and allows the use of IMDSv1 or IMDSv2 (token optional)
  • Availability: Single zone for the data plane
  • No cluster admin role created

Compute node machine pool

  • Compute node instance type: m5.xlarge (4 vCPU 16, GiB RAM)
  • Compute node count: 3
  • Autoscaling: Not enabled
  • No additional node labels

Networking configuration

  • Cluster privacy: public or private
  • You can choose to create a new VPC during the Terraform cluster creation process.
  • No cluster-wide proxy is configured

Classless Inter-Domain Routing (CIDR) ranges

  • Machine CIDR: 10.0.0.0/16
  • Service CIDR: 172.30.0.0/16
  • Pod CIDR: 10.128.0.0/14
  • Host prefix: /23

    Note

    The static IP address 172.20.0.1 is reserved for the internal Kubernetes API address. The machine, pod, and service CIDRs ranges must not conflict with this IP address.

Cluster roles and policies

  • Mode used to create the Operator roles and the OpenID Connect (OIDC) provider: auto
  • A configured ocm-role, which is required for all Red Hat OpenShift Service on AWS clusters.

    Note

    For installations that use OpenShift Cluster Manager on the Hybrid Cloud Console, the auto mode requires an admin-privileged OpenShift Cluster Manager role (ocm-role).

  • Default Operator role prefix: rosa-<6-digit-alphanumeric-string>

Storage

  • Node volumes:

    • Type: AWS EBS GP3
    • Default size: 300GiB (adjustable at creation time)
  • Workload persistent volumes:

    • Default StorageClass: gp3-csi
    • Provisioner: ebs.csi.aws.com
    • Dynamic persistent volume provisioning

Cluster update strategy

  • Individual updates
  • 1 hour grace period for node draining

The cluster creation process outlined below shows how to use Terraform to create your account-wide IAM roles and a Red Hat OpenShift Service on AWS cluster with a managed OIDC configuration.

3.1.5.1. Prepare your environment for Terraform

Before you can create your Red Hat OpenShift Service on AWS cluster by using Terraform, you must export your offline Red Hat OpenShift Cluster Manager token to grant Terraform permission to access your account.

Procedure

  1. Optional: Because the Terraform files get created in your current directory during this procedure, you can create a new directory to store these files and navigate into it by running the following command:

    $ mkdir terraform-cluster && cd terraform-cluster
  2. Grant permissions to your account by using an offline Red Hat OpenShift Cluster Manager token.
  3. Copy your offline token, and set the token as an environmental variable by running the following command:

    $ export RHCS_TOKEN=<your_offline_token>
    Note

    This environmental variable resets at the end of each session, such as restarting your machine or closing the terminal.

Verification

  • After you export your token, verify the value by running the following command:

    $ echo $RHCS_TOKEN

3.1.5.2. Creating your Terraform files locally

After you configure your offline Red Hat OpenShift Cluster Manager token, you need to create the Terraform files locally to build your cluster. You can create these files by using the following code templates.

Procedure

  1. Create the main.tf file by running the following command:

    $ cat<<-EOF>main.tf
    #
    # Copyright (c) 2023 Red Hat, Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #   http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    terraform {
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = ">= 4.21.0"
        }
        rhcs = {
          version = ">= 1.6.3"
          source  = "terraform-redhat/rhcs"
        }
      }
    }
    
    # Export token using the RHCS_TOKEN environment variable
    provider "rhcs" {}
    
    provider "aws" {
      region = var.aws_region
      ignore_tags {
        key_prefixes = ["kubernetes.io/"]
      }
      default_tags {
        tags = var.default_aws_tags
      }
    }
    
    data "aws_availability_zones" "available" {}
    
    locals {
      # Extract availability zone names for the specified region, limit it to 3 if multi az or 1 if single
      region_azs = var.multi_az ? slice([for zone in data.aws_availability_zones.available.names : format("%s", zone)], 0, 3) : slice([for zone in data.aws_availability_zones.available.names : format("%s", zone)], 0, 1)
    }
    
    resource "random_string" "random_name" {
      length  = 6
      special = false
      upper   = false
    }
    
    locals {
      worker_node_replicas = var.multi_az ? 3 : 2
      # If cluster_name is not null, use that, otherwise generate a random cluster name
      cluster_name = coalesce(var.cluster_name, "rosa-\${random_string.random_name.result}")
    }
    
    # The network validator requires an additional 60 seconds to validate Terraform clusters.
    resource "time_sleep" "wait_60_seconds" {
      count = var.create_vpc ? 1 : 0
      depends_on = [module.vpc]
      create_duration = "60s"
    }
    
    module "rosa-hcp" {
      source                 = "terraform-redhat/rosa-hcp/rhcs"
      version                = "1.6.3"
      cluster_name           = local.cluster_name
      openshift_version      = var.openshift_version
      account_role_prefix    = local.cluster_name
      operator_role_prefix   = local.cluster_name
      replicas               = local.worker_node_replicas
      aws_availability_zones = local.region_azs
      create_oidc            = true
      private                = var.private_cluster
      aws_subnet_ids         = var.create_vpc ? var.private_cluster ? module.vpc[0].private_subnets : concat(module.vpc[0].public_subnets, module.vpc[0].private_subnets) : var.aws_subnet_ids
      create_account_roles   = true
      create_operator_roles  = true
    # Optional: Configure a cluster administrator user
    #
    # Option 1: Default cluster-admin user
    # Create an administrator user (cluster-admin) and automatically
    # generate a password by uncommenting the following parameter:
    #  create_admin_user = true
    # Generated administrator credentials are displayed in terminal output.
    #
    # Option 2: Specify administrator username and password
    # Create an administrator user and define your own password
    # by uncommenting and editing the values of the following parameters:
    #  admin_credentials_username = <username>
    #  admin_credentials_password = <password>
    
    # Optional: Enable cluster deletion protection to prevent accidental deletion.
    # To enable, uncomment the following parameter:
    #  delete_protection = true
    # By default, clusters are created with deletion protection disabled.
    
      depends_on = [time_sleep.wait_60_seconds]
    }
    EOF
    • If you want to create an administrator user during cluster creation, uncomment the appropriate parameters in the Optional: Configure a cluster administrator user section and edit their values.
    • If you want to enable deletion protection during cluster creation, uncomment the corresponding parameter in the Optional: Enable cluster delete protection section. When enabled, deletion protection blocks the terraform destroy command until you set delete_protection = false and apply the change by running terraform apply.

      Deletion protection only blocks deletion requests made through Terraform. It does not set any AWS-level resource protections, and you can still delete cluster resources directly in AWS, risking data loss or cluster outage. Additionally, deletion protection does not override the Red Hat Deletion policy. Red Hat reserves the right to delete clusters that violate service requirements.

  2. Create the variables.tf file by running the following command:

    Note

    Copy and edit this file before running the command to build your cluster.

    $ cat<<-EOF>variables.tf
    #
    # Copyright (c) 2023 Red Hat, Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #   http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    variable "openshift_version" {
      type        = string
      default     = "4.14.20"
      description = "Desired version of OpenShift for the cluster, for example '4.14.20'. If version is greater than the currently running version, an upgrade will be scheduled."
    }
    
    variable "create_vpc" {
      type        = bool
      description = "If you would like to create a new VPC, set this value to 'true'. If you do not want to create a new VPC, set this value to 'false'."
    }
    
    # Red Hat OpenShift Service on AWS cluster info
    variable "cluster_name" {
      default     = null
      type        = string
      description = "The name of the Red Hat OpenShift Service on AWS cluster to create"
    }
    
    variable "additional_tags" {
      default = {
        Terraform   = "true"
        Environment = "dev"
      }
      description = "Additional AWS resource tags"
      type        = map(string)
    }
    
    variable "multi_az" {
      type        = bool
      description = "Multi AZ Cluster for High Availability"
      default     = true
    }
    
    variable "worker_node_replicas" {
      default     = 3
      description = "Number of worker nodes to provision. Single zone clusters need at least 2 nodes, multizone clusters need at least 3 nodes"
      type        = number
    }
    
    variable "aws_subnet_ids" {
      type        = list(any)
      description = "A list of either the public or public + private subnet IDs to use for the cluster blocks to use for the cluster"
      default     = ["subnet-01234567890abcdef", "subnet-01234567890abcdef", "subnet-01234567890abcdef"]
    }
    
    variable "private_cluster" {
      type        = bool
      description = "If you want to create a private cluster, set this value to 'true'. If you want a publicly available cluster, set this value to 'false'."
    }
    
    #VPC Info
    variable "vpc_name" {
      type        = string
      description = "VPC Name"
      default     = "tf-qs-vpc"
    }
    
    variable "vpc_cidr_block" {
      type        = string
      description = "value of the CIDR block to use for the VPC"
      default     = "10.0.0.0/16"
    }
    
    variable "private_subnet_cidrs" {
      type        = list(any)
      description = "The CIDR blocks to use for the private subnets"
      default     = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
    }
    
    variable "public_subnet_cidrs" {
      type        = list(any)
      description = "The CIDR blocks to use for the public subnets"
      default     = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
    }
    
    variable "single_nat_gateway" {
      type        = bool
      description = "Single NAT or per NAT for subnet"
      default     = false
    }
    
    #AWS Info
    variable "aws_region" {
      type    = string
      default = "us-east-2"
    }
    
    variable "default_aws_tags" {
      type        = map(string)
      description = "Default tags for AWS"
      default     = {}
    }
    EOF
  3. Create the vpc.tf file by running the following command:

    $ cat<<-EOF>vpc.tf
    #
    # Copyright (c) 2023 Red Hat, Inc.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #   http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    module "vpc" {
      source  = "terraform-aws-modules/vpc/aws"
      version = "5.1.2"
    
      count = var.create_vpc ? 1 : 0
      name  = var.vpc_name
      cidr  = var.vpc_cidr_block
    
      azs             = local.region_azs
      private_subnets = var.multi_az ? var.private_subnet_cidrs : [var.private_subnet_cidrs[0]]
      public_subnets  = var.multi_az ? var.public_subnet_cidrs : [var.public_subnet_cidrs[0]]
    
      enable_nat_gateway   = true
      single_nat_gateway   = var.single_nat_gateway
      enable_dns_hostnames = true
      enable_dns_support   = true
    
      tags = var.additional_tags
    }
    EOF

    You are ready to initiate Terraform.

After you create the Terraform files, you must initiate Terraform to provide all of the required dependencies. Then apply the Terraform plan.

Important

Do not modify Terraform state files.

Procedure

  1. Configure Terraform to create your resources based on your Terraform files, run the following command:

    $ terraform init
  2. Optional: Verify that the Terraform you copied is correct by running the following command:

    $ terraform validate

    Example output

    Success! The configuration is valid.

  3. Create your cluster with Terraform by running the following command:

    $ terraform apply

    The Terraform interface asks two questions to create your cluster, similar to the following:

    var.create_vpc
      If you would like to create a new VPC, set this value to 'true'. If you do not want to create a new VPC, set this value to 'false'.
    
      Enter a value:
    
    var.private_cluster
      If you want to create a private cluster, set this value to 'true'. If you want a publicly available cluster, set this value to 'false'.
    
      Enter a value:
  4. Enter yes to proceed or no to cancel when the Terraform interface lists the resources to be created or changed and prompts for confirmation:

    Plan: 63 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.

    If you enter yes, your Terraform plan starts, creating your AWS account roles, Operator roles, and your Red Hat OpenShift Service on AWS cluster.

Verification

  1. Verify that your cluster was created by running the following command:

    $ rosa list clusters

    This example shows a cluster in the ready state:

    ID                                NAME          STATE  TOPOLOGY
    27c3snjsupa9obua74ba8se5kcj11269  rosa-tf-demo  ready  Hosted CP
  2. Verify that your account roles were created by running the following command:

    $ rosa list account-roles

    This example shows the account roles that were created:

    I: Fetching account roles
    ROLE NAME                                   ROLE TYPE      ROLE ARN                                                           OPENSHIFT VERSION  AWS Managed
    ROSA-demo-Installer-Role                    Installer      arn:aws:iam::<ID>:role/ROSA-demo-Installer-Role                    4.14               No
    ROSA-demo-Support-Role                      Support        arn:aws:iam::<ID>:role/ROSA-demo-Support-Role                      4.14               No
    ROSA-demo-Worker-Role                       Worker         arn:aws:iam::<ID>:role/ROSA-demo-Worker-Role                       4.14               No
  3. Verify that your Operator roles were created by running the following command:

    $ rosa list operator-roles

    This example shows the Terraform-created Operator roles:

    I: Fetching operator roles
    ROLE PREFIX    AMOUNT IN BUNDLE
    rosa-demo      8

After creating your cluster with Terraform, you can permit users access to your cluster by using an htpasswd identity provider (IDP) with the Terraform tool.

Prerequisites

  • You have installed and configured the latest version of the rosa CLI.
  • You have installed and configured the latest version of Terraform.

Procedure

  1. Create the htpasswd_idp.tf file by running one of the following commands:

    • Option 1: To create a user with a generated, randomized password, run:

      $ cat<<-EOF>htpasswd_idp.tf
        module "htpasswd_idp" {
          source = "terraform-redhat/rosa-hcp/rhcs//modules/idp"
          version = "1.6.2"
      
          cluster_id         = "2odpb9p344hnkfvpkluo00qmgkika78l"
          name               = "htpasswd-idp-tf-1"
          idp_type           = "htpasswd"
          htpasswd_idp_users = [{ username = "pej-user-d1", password = random_password.password.result }]
        }
      
        resource "aws_secretsmanager_secret" "idp_password" {
        name        = "idp-password-secret"
        description = "Any description here"
        }
      
        resource "random_password" "password" {
            length           = 16
            lower            = true
            special          = true
            override_special = "!#$%&*()-_=+[]{}<>:?"
        }
      
        # If you need to output the password, mark it as sensitive to hide from CLI logs
        output "password_output" {
            value     = random_password.password.result
            sensitive = true
        }
      
        # This section sends your credentials to your AWS Secrets Manager to enable you to log in to your cluster.
        resource "aws_secretsmanager_secret_version" "idp_password_val" {
        secret_id     = aws_secretsmanager_secret.idp_password.id
        secret_string = random_password.password.result
        }
      EOF

      You must replace the <cluster_id> placeholder with the 32-digit ID for your cluster. To find that value, run rosa list clusters | awk '{print $1}'. You also must replace the <user_name> placeholder with the username you want to create. The randomized password is then stored in your AWS Secrets manager to be used when logging in to the cluster.

      • Run the following command to view your password after setting it:

        $ terraform output password_output

        The CLI returns your generated password in plain text.

    • Option 2: To specify your passwords when creating a user, run:

      $ cat<<-EOF>htpasswd_idp.tf
        module "htpasswd_idp" {
          source = "terraform-redhat/rosa-hcp/rhcs//modules/idp"
          version = "1.6.2"
      
          cluster_id         = "<cluster_id>"
          name               = "htpasswd-idp"
          idp_type           = "htpasswd"
          htpasswd_idp_users = [{ username="<user_name>",password="<password>"}]
        }
      EOF

      You must replace the <cluster_id> placeholder with the 32-digit ID for your cluster. To find that value, run rosa list clusters | awk '{print $1}'. You also must replace the <user_name> placeholder with the username you want to create and a password for the <password> placeholder.

  2. Run the following command to configure Terraform to create your resources based on your Terraform files:

    $ terraform init
  3. Verify that the Terraform you copied is correct by running the following command:

    $ terraform validate

    Example output

    Success! The configuration is valid.

  4. Create your cluster with Terraform by running the following command:

    $ terraform apply
  5. Enter yes to proceed or no to cancel when the Terraform interface lists the resources to be created or changed and prompts for confirmation:

    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

    You see a confirmation that your IDP has been created.

    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
    Note

    If you used the randomized password template, then the generated password is stored in your AWS Secrets manager.

Use the terraform destroy command to remove all resources you create with the terraform apply command.

Note

Keep your Terraform .tf files unchanged before destroying your resources. These variables are matched to resources to delete.

Important

If deletion protection is enabled on the cluster, disable it by setting delete_protection = false in the main.tf file, then run terraform apply to apply the change.

Procedure

  1. In the directory where you ran the terraform apply command to create your cluster, run the following command to delete the cluster:

    $ terraform destroy

    The Terraform interface prompts you for two variables. These should match the answers you provided when creating a cluster:

    var.create_vpc
      If you would like to create a new VPC, set this value to 'true.' If you do not want to create a new VPC, set this value to 'false.'
    
      Enter a value:
    
    var.private_cluster
      If you want to create a private cluster, set this value to 'true.' If you want a publicly available cluster, set this value to 'false.'
    
      Enter a value:
  2. Enter yes to start the role and cluster deletion:

    Example output

    Plan: 0 to add, 0 to change, 63 to destroy.
    
    Do you really want to destroy all resources?
      Terraform will destroy all your managed infrastructure, as shown above.
      There is no undo. Only 'yes' will be accepted to confirm.
    
      Enter a value: yes

Verification

  1. Verify that your cluster was destroyed by running the following command:

    $ rosa list clusters

    Example output showing no cluster

    I: No clusters available

  2. Verify that the account roles were destroyed by running the following command:

    $ rosa list account-roles

    Example output showing no Terraform-created account roles

    I: Fetching account roles
    I: No account roles available

  3. Verify that the Operator roles were destroyed by running the following command:

    $ rosa list operator-roles

    Example output showing no Terraform-created Operator roles

    I: Fetching operator roles
    I: No operator roles available

Create a Red Hat OpenShift Service on AWS cluster or update an existing one with Terraform that is configured for AWS Spot instances.

3.2.1. Prerequisites

Before configuring Spot instances for Red Hat OpenShift Service on AWS clusters, you must meet the following prerequisites:

  • You have installed Terraform version 1.4.6 or newer.
  • You have one of the following Terraform providers:

    • Red Hat Cloud Services: terraform-provider-rhcs
    • AWS: terraform-provider-aws

      This is for Enhanced mode Simple Queue Service (SQS) and EventBridge resources

  • You have the required sqs:ReceiveMessage and sqs:DeleteMessage IAM permissions. These are included in the standard ROSANodePoolManagementPolicy.

    You do not need any additional IAM role changes. The managed policy scopes access to SQS queues tagged with red-hat=true.

  • (Optional): You have AWS CLI access so that you can retrieve the NodePoolManagement role ARN, if you do not already know it.

AWS Spot instances allow you to use spare EC2 capacity at significantly reduced costs compared to on-demand instances. Red Hat OpenShift Service on AWS supports Spot instances for worker node machine pools, enabling cost optimization for fault-tolerant and stateless workloads.

Red Hat OpenShift Service on AWS provides two operational modes for Spot instance termination handling:

ModeDescriptionAWS infrastructure requiredGraceful drain

Simple

Spot instances work with no additional AWS setup. When AWS reclaims a Spot instance, the node is terminated abruptly. A MachineHealthCheck detects the missing node and provisions a replacement.

None

No

Enhanced

Requires a customer-configured SQS queue and EventBridge rules. The AWS Node Termination Handler receives an approximately 2m warning before interruption and gracefully drains pods, respecting PodDisruptionBudgets.

SQS queue, EventBridge rules, SQS resource policy

Yes

Enhanced Spot termination handling requires three AWS components:

  • A Simple Queue Service (SQS) queue to receive interruption events.
  • EventBridge rules to route EC2 Spot events to the queue.
  • An SQS queue resource policy that grants access to EventBridge and the cluster’s NodePoolManagement role.
Note

Use enhanced mode for any workload that benefits from graceful shutdown before Spot interruptions.

3.2.2.1. Create the SQS queue

Set up the SQS queue for graceful interruption events.

Procedure

  • Add the following resource to your Terraform configuration:

    resource "aws_sqs_queue" "spot_termination" {
      name = "rosa-<cluster-name>-spot"
    
      tags = {
        "red-hat" = "true"
      }
    }
    Important

    The red-hat=true tag is required. The ROSANodePoolManagementPolicy uses an IAM condition, aws:ResourceTag/red-hat: "true", to scope SQS permissions. Omitting this tag causes the AWS Node Termination Handler to fail when polling the queue.

3.2.2.2. Create your EventBridge rules

Use EventBridge rules to route EC2 Spot events to the queue.

Procedure

  • Create two EventBridge rules to capture Spot instance events and route them to the SQS queue:

    # Rule 1: Spot Instance Interruption Warning
    resource "aws_cloudwatch_event_rule" "spot_interruption" {
      name        = "rosa-<cluster-name>-spot-interruption"
      description = "Capture EC2 Spot Instance Interruption Warnings"
    
      event_pattern = jsonencode({
        source      = ["aws.ec2"]
        detail-type = ["EC2 Spot Instance Interruption Warning"]
      })
    }
    
    # Rule 2: Instance Rebalance Recommendation
    resource "aws_cloudwatch_event_rule" "rebalance_recommendation" {
      name        = "rosa-<cluster-name>-rebalance-recommendation"
      description = "Capture EC2 Instance Rebalance Recommendations"
    
      event_pattern = jsonencode({
        source      = ["aws.ec2"]
        detail-type = ["EC2 Instance Rebalance Recommendation"]
      })
    }
    
    # Route both rules to the SQS queue
    resource "aws_cloudwatch_event_target" "spot_interruption_target" {
      rule      = aws_cloudwatch_event_rule.spot_interruption.name
      target_id = "SendToSQS"
      arn       = aws_sqs_queue.spot_termination.arn
    }
    
    resource "aws_cloudwatch_event_target" "rebalance_recommendation_target" {
      rule      = aws_cloudwatch_event_rule.rebalance_recommendation.name
      target_id = "SendToSQS"
      arn       = aws_sqs_queue.spot_termination.arn
    }

3.2.2.3. Configure the SQS queue resource policy

Set up the SQS resource policy to grant access to EventBridge and the cluster’s NodePoolManagement role

Procedure

  • Apply a resource policy to the SQS queue:

    resource "aws_sqs_queue_policy" "spot_termination" {
      queue_url = aws_sqs_queue.spot_termination.id
    
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Sid    = "AllowNodePoolManagementRole"
            Effect = "Allow"
            Principal = {
              AWS = var.nodepool_management_role_arn
            }
            Action = [
              "sqs:DeleteMessage",
              "sqs:ReceiveMessage"
            ]
            Resource = aws_sqs_queue.spot_termination.arn
          },
          {
            Sid    = "AllowEventBridgeToSendMessages"
            Effect = "Allow"
            Principal = {
              Service = "events.amazonaws.com"
            }
            Action   = "sqs:SendMessage"
            Resource = aws_sqs_queue.spot_termination.arn
          }
        ]
      })
    }

    This policy grants access to two principals:

    EventBridge
    sends interruption events to the queue.
    NodePoolManagement role

    receives and deletes messages from the queue.

    Note

    This resource policy provides defense-in-depth alongside the managed IAM policy. The IAM policy restricts access by the tag red-hat=true. The resource policy restricts access by principal ARN.

3.2.2.4. Define your required Terraform variables

Update you local Terraform variables file with the NodePoolManagement role ARN.

Procedure

  1. (Optional): Retrieve the NodePoolManagement role ARN from an existing cluster:

    $ aws iam list-roles --query 'Roles[?contains(RoleName, `kube-system-capa-controller-manager`)].Arn' --output text
  2. Add the following variables to your variables.tf file:

    variable "cluster_name" {
      description = "Name of the ROSA HCP cluster"
      type        = string
    }
    
    variable "nodepool_management_role_arn" {
      description = "ARN of the NodePoolManagement IAM role for the cluster"
      type        = string
    }

    The NodePoolManagement role ARN typically follows this pattern:

    arn:aws:iam::<account-id>:role/ManagedOpenShift-HCP-ROSA-NodePoolManagement-Role

After you create or update your Terrform variables file, you can add the cluster resource to your main file and initiate Terraform. The process for simple mode and enhanced mode differ by the omission or inclusion respectively of the termination_handler_queue_url attribute. The machine pool configuration is identical for both simple and enhanced modes.

Important

Each cluster must use a dedicated Spot termination queue. Do not reuse the same SQS queue URL for multiple clusters. Create a separate queue for each cluster.

Procedure

  1. Add the cluster resouce to your main.tf file:

    resource "rhcs_cluster_rosa_hcp" "cluster" {
      name                          = var.cluster_name
      cloud_region                  = var.aws_region
      aws_account_id                = var.aws_account_id
      operator_role_arn             = var.operator_role_arn
      termination_handler_queue_url = aws_sqs_queue.spot_termination.url # Enhanced mode only
    }
  2. Add a Spot machine pool resource to your main.tf file:

    resource "rhcs_hcp_machine_pool" "spot_pool" {
      cluster = rhcs_cluster_rosa_hcp.cluster.id
      name    = "spot-pool"
    
      aws_node_pool = {
        instance_type      = "m5.xlarge"
        use_spot_instances = true
        max_spot_price     = 2.0
      }
    
      subnet_id   = var.private_subnet_id
      replicas    = 2
      auto_repair = true
      # ...
    }
  3. Apply the configuration:

    $ terraform init && terraform plan && terraform apply

    The AWS Node Termination Handler is automatically created in the hosted control plane namespace once the cluster is ready and at least one Spot NodePool exists.

You can add Spot machine pools to an existing Red Hat OpenShift Service on AWS cluster managed by Terraform.

Procedure

  1. Add a new rhcs_hcp_machine_pool resource to your existing Terraform configuration:

    resource "rhcs_hcp_machine_pool" "spot_pool" {
      cluster = rhcs_cluster_rosa_hcp.cluster.id
      name    = "spot-pool"
    
      aws_node_pool = {
        instance_type      = "m5.xlarge"
        use_spot_instances = true
        max_spot_price     = 2.0
      }
    
      subnet_id   = var.private_subnet_id
      replicas    = 2
      auto_repair = true
      # ...
    }
  2. Apply the configuration:

    $ terraform plan && terraform apply
    Warning

    If the cluster does not have the termination_handler_queue_url attribute configured, the Spot machine pool operates in simple mode. This means nodes will not be gracefully drained upon interruption.

You can upgrade an existing simple-mode cluster to enhanced mode by adding the AWS infrastructure resources and associating the SQS queue URL with the cluster.

Prerequisites

  1. You have SQS queue configured.
  2. You have your EventBridge rules configured.
  3. You have the associated resource policy configured.

Procedure

  1. Add the termination_handler_queue_url attribute to your existing cluster resource:

    resource "rhcs_cluster_rosa_hcp" "cluster" {
      name              = var.cluster_name
      cloud_region      = var.aws_region
      aws_account_id    = var.aws_account_id
      operator_role_arn = var.operator_role_arn
    
      # Add this line to upgrade from simple to enhanced mode
      termination_handler_queue_url = aws_sqs_queue.spot_termination.url
    }
  2. Apply the configuration:

    $ terraform plan && terraform apply

    Terraform performs a patch update on the cluster. Existing Spot machine pools immediately benefit from graceful termination handling.

Red Hat logoGithubRedditYoutubeTwitter

Learn

Try, buy, & sell

Communities

About Red Hat

We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.

Making open source more inclusive

Red Hat is committed to replacing problematic language in our code, documentation, and web properties. For more details, see the Red Hat Blog.

About Red Hat Documentation

Legal Notice

Theme

© 2026 Red Hat
Back to top