RKE2 is a CNCF-conformant Kubernetes distribution that emphasizes security, minimalism, and ease of operation. Provisioning RKE2 clusters at scale with repeatable, declarative workflows is where Terraform becomes essential. Terraform allows infrastructure for RKE2 control planes, worker node pools, load balancers, and supporting components to be defined as code, reducing manual toil and enabling both airgapped on-prem environments such as Harvester and cloud-native AWS deployments.
The reference implementations demonstrate two distinct patterns: an airgapped RKE2 and Rancher bootstrap on Harvester using prebuilt VM images and an AWS-focused module set that provisions RKE2 servers, a Network Load Balancer control plane, and autoscaling agent node pools. Both patterns rely on Terraform for stateful, declarative provisioning and share common concerns around image distribution, kubeconfig handling, and secure bootstrapping.
Airgapped RKE2 Provisioning with Terraform
Airgapping is a security posture where a Kubernetes environment has no direct connectivity to the public internet. This is usually called generically an Image Store. This location is a centralized source of truth that your RKE2 cluster, Rancher Cluster Manager, and all downstream clusters in this environment can pull their container images from as opposed to the public cloud.
In order to do this, you'll need to pull down all of these images onto a local workstation and then either copy them into your soft-airgap via jumpbox or bring into your hard airgap using physical media like BluRay or a USB key drive. Be aware that the total image size is >30Gbi! The instructions for this process and some very helpful scripts are provided here and details around them defined in the manual install doc.
Provisioning with Terraform is an infrastructure provisioning tool that we can use to spin up airgapped RKE2 and Rancher instances in a more automated way. This also provides the benefits of defining these components in a declaractive, stateful way. This greatly reduces toil and mostly eliminates all of the steps in the previous section.
We can also find a shortcut in this process speed-wise by creating prebuilt VM images that already contain every necessary component on them and them import those VM images into our airgap as well.
Harvester OS Image Preparation
Prep Harvester's Ubuntu OS Image. Please see the doc located in the vm image doc to dive into how I created the OS images here.
The airgap workflow for Harvester begins with a prebuilt Ubuntu OS image that contains RKE2 and all required airgap components. Terraform then imports the image and creates virtual machines from it. The variable configuration for the image is typically defined in terraform.tfvars.
The final terraform.tfvars file example for Harvester is:
hcl
ubuntu_image_name = "ubuntu-rke2-airgap-harvester"
rke2_image_url = "http://10.10.0.248:9900/ubuntu-rke2-airgap-harvester.img"
rke2_registry = "harbor.sienarfleet.systems"
rancher_server_dns = "rancher.home.sienarfleet.systems"
master_vip = "10.10.5.4"
Due to Terraform's cloud-connected nature, we need to prepopulate our terraform directory with everything we need to run within our environment and then copy everything into our airgap:
bash
cd terraform
terraform init
terraform plan
Terraform Tour. One of the powerful features of Terraform is it allows us to templatize things like cloud-init where we can write a generic expression that then can work for all similar cases with simple variable tweaks. We're not crazy here, but I've extracted a few hardcoded values into a variables.tf file that allows us to define some defaults and potentially override them later using the above-defined terraform.tfvars file if we want to try different options out.
Internally kube-vip is used to define an additional static IP for my control-plane node to support HA control-planes and generally keep things simpler. Part of that is defining the IP I want, which is a variable called master_vip.
Terraform Execution Flow for Harvester
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
...
Plan: 9 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
Example resource creation output:
harvester_image.ubuntu-rke2: Creating...
tls_private_key.rsa_key: Creating...
tls_private_key.rsa_key: Creation complete after 1s [id=1159e974801abcb6659d1f966364998a2afb60c4]
harvester_ssh_key.rke2-key: Creating...
harvester_ssh_key.rke2-key: Creation complete after 0s [id=default/rke2-key]
harvester_image.ubuntu-rke2: Still creating... [10s elapsed]
harvester_image.ubuntu-rke2: Still creating... [20s elapsed]
harvester_image.ubuntu-rke2: Still creating... [30s elapsed]
harvester_image.ubuntu-rke2: Creation complete after 30s [id=default/ubuntu-rke2-airgap-harvester]
harvester_virtualmachine.cp-node: Creating...
...
harvester_virtualmachine.cp-node: Still creating... [2m0s elapsed]
harvester_virtualmachine.cp-node (remote-exec): Completed cloud-init!
harvester_virtualmachine.cp-node: Creation complete after 2m10s [id=default/rke2-airgap-cp]
...
harvester_virtualmachine.worker-node: Still creating..
In this repo, we're going to cover the creation of a RKE2 cluster into Harvester using an airgapped process aka Bootstrap! We'll follow that up by installing Rancher on top of RKE2. We're going to do all of this using Terraform. Terraform allows us to declaratively define our environment in code and then create that environment in a straight-forward and sustainable way.
Watch the Video. Learn the ins and outs of airgap-installations for RKE2 and Rancher Multi-Cluster Manager MCM onto Harvester with RGS Field Engineer, Brian Durden watch the video to learn more.
TL;DR. You're here and you want to go REAL fast? There's a couple things you'll need to ensure are setup before you're done. All of that is defined here and within the manual install doc.
Kubeconfig Retrieval and Correction
A repository that creates a RKE2 kubernetes cluster using Terraform.
In the file terraform.tfvars you can set the password. The default is "softica".
To create a new password, run the following command and give it the password when prompted:
bash
mkpasswd --method=SHA-512 --rounds=4096
or
bash
echo "your-password" | mkpasswd --method=SHA-512 --rounds=4096 -
The password is needed by terraform, so we keep a version of it in plain text as well. So remember to change the variable kubernetesnodesshpasswordplain as well.
SSH into the first master node and run the following command
bash
cat /etc/rancher/rke2/rke2.yaml
or get it to a local file like this with the ssh username and ssh password you have chosen.
The command will overwrite your current config file !
bash
ssh [email protected] cat /etc/rancher/rke2/rke2.yaml > ~/.kube/config
In the config file, it refers to the conrolplane at 127.0.0.1 which doesnt work for us. So edit the file and change 127.0.0.1 with the ip of the control plane node default 10.18.3.10.
AWS RKE2 Terraform Modules
The AWS implementation provides two terraform modules intended for user consumption for provisioning RKE2 server nodes and controlplane load balancer and provisioning Auto Scaling Group of agents to auto-join cluster.
The deployment model of this repository is designed to feel very similar to the major cloud providers kubernetes distributions. It revolves around provisioning the following:
- Nodepools: Self-bootstrapping and auto-joining groups of EC2 instances
- AWS NLB: Controlplane static address
This iac leverages the ease of use of rke2 to provide a simple sshless bootstrapping process for sets of cluster nodes, known as.
Module Usage
Provision rke2 server(s) and controlplane loadbalancer
hcl
module "rke2" {
source = "git::https://github.com/rancherfederal/rke2-aws-tf.git"
name = "quickstart"
vpc_id = "vpc-###"
subnets = ["subnet-###"]
ami = "ami-###"
}
Provision Auto Scaling Group of agents to auto-join cluster
hcl
module "rke2_agents" {
source = "git::https://github.com/rancherfederal/rke2-aws-tf.git//modules/agent-nodepool"
name = "generic"
vpc_id = "vpc-###"
subnets = ["subnet-###"]
ami = "ami-###"
# Required input sourced from parent rke2 module, contains configuration that agents use to join existing cluster
cluster_data = module.rke2.cluster_data
}
For more complete options, fully functioning examples are provided in the examples/ folder to meet the various use cases of rke2 clusters on AWS, ranging from:
examples/quickstart
: bare minimum rke2 server/agent cluster, start here!
examples/cloud-enabled
: aws cloud aware rke2 cluster
The AWS Cluster Autoscaler will configure rke2 to autoscale based off kubernetes resource requests.
Provider and Module Requirements
| Name | Version |
|---|---|
| terraform | >= 1.3 |
| aws | >= 5.32 |
| cloudinit | >= 2 |
| random | >= 3 |
| Name | Version |
|---|---|
| aws | >= 5.32 |
| cloudinit | >= 2 |
| random | >= 3 |
Module Components
| Name | Source | Version |
|---|---|---|
| cp_lb | ./modules/nlb | n/a |
| iam | ./modules/policies | n/a |
| init | ./modules/userdata | n/a |
| servers | ./modules/nodepool | n/a |
| statestore | ./modules/statestore | n/a |
Resources and Data Sources
| Name | Type |
|---|---|
| awsiamrolepolicy.awsautoscaler | resource |
| awsiamrolepolicy.awsccm | resource |
| awsiamrolepolicy.awsrequired | resource |
| awsiamrolepolicy.gettoken | resource |
| awsiamrolepolicy.putkubeconfig | resource |
| awssecuritygroup.cluster | resource |
| awssecuritygroup.server | resource |
| awssecuritygrouprule.clusteregress | resource |
| awssecuritygrouprule.clustershared | resource |
| awssecuritygrouprule.servercp | resource |
| awssecuritygrouprule.servercp_supervisor | resource |
| random_password.token | resource |
| random_string.uid | resource |
| awsiampolicydocument.awsautoscaler | data source |
| awsiampolicydocument.awsccm | data source |
| awsiampolicydocument.awsrequired | data source |
| awsiamrole.provided | data source |
| cloudinit_config.this | data source |
Input Variables
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| ami | Server pool ami | string | n/a | yes |
| associatepublicip_address | n/a | bool | ... |
When setting the 'asg' variable, you now must also set the 'termination_policy' value.
Operational Considerations
Terraform's declarative nature ensures that RKE2 clusters are reproducible. For airgapped environments, the workflow emphasizes pre-staging artifacts: container images, VM images, and Terraform providers must be present locally before init and plan. The image store becomes the single source of truth for all RKE2 and Rancher components.
In cloud environments, the module set abstracts node pool provisioning, NLB creation, IAM policies for AWS autoscaler and CCM, and state store configuration. The sshless bootstrapping relies on cloud-init templates generated by Terraform, with random tokens and user data handling secrets securely.
Password handling for SSH access in the Harvester example remains a manual variable override. The default password is softica and can be changed using mkpasswd with SHA-512 and 4096 rounds. Both the hashed password in terraform.tfvars and the plain text variable kubernetesnodesshpasswordplain must be updated together.
Kubeconfig generated on the first master node references 127.0.0.1 for the control plane. This must be edited to the actual control plane IP, such as 10.18.3.10, for external access. The master_vip variable using kube-vip allows HA control planes with a stable virtual IP.
Conclusion
RKE2 Terraform automation delivers consistent, repeatable provisioning across both airgapped on-prem Harvester deployments and AWS cloud environments. The Harvester pattern demonstrates how Terraform can orchestrate prebuilt Ubuntu OS images, Harvester VM resources, TLS keys, SSH keys, and kube-vip VIPs to deliver a fully airgapped RKE2 and Rancher stack with minimal manual intervention. Image store management, physical media transfer, and >30Gbi image sizes are handled via declarative pipelines and helper scripts.
The AWS pattern shows how modular Terraform can provision RKE2 server node pools with NLB control plane endpoints and autoscaling agent node pools that self-bootstrap and auto-join. Provider constraints, IAM policy documents, security groups, and cloud-init data sources compose a cloud-aware RKE2 cluster that mirrors managed Kubernetes offerings while retaining RKE2's lightweight operational model.
Together these approaches illustrate the core strengths of Terraform with RKE2: declarative state, reusable modules, prebuilt images for airgap efficiency, and sshless node bootstrapping for cloud scale. Proper variable management for passwords, master VIPs, registry endpoints, and image URLs ensures environments remain secure, portable, and reproducible.