Previous Section: DevOps: What? Why? How?
Terraform, developed by HashiCorp, is an open-source tool that allows you to manage infrastructure as code (IaC). Picture having the power to set up an entire cloud environment with just a few lines of code and then being able to adjust, scale, or tear it down with ease. That’s Terraform in a nutshell. It empowers you to handle cloud resources, like virtual machines, databases, and networks, consistently and efficiently across multiple providers.
Why do we love Terraform? Because it makes infrastructure management feel almost magical. You write your configuration files, apply them, and watch as your cloud resources come to life. Best of all, you can version control your infrastructure changes just like you would with code, making collaboration, updates, and rollbacks a breeze.
Before diving into setting up Terraform for CVMFS and DevOps, it’s crucial to have a strong foundational understanding of several tools and concepts. This knowledge will help you work efficiently through the steps in this guide and troubleshoot as needed.
.tfvars files for setting values. Familiarity with commands like terraform init, terraform apply, and terraform destroy will help you run the configurations smoothly..env files and passing variables to Docker containers.With these prerequisites in place, you’ll be well-equipped to follow this guide, implement the Terraform configuration, and set up a consistent, automated environment for managing your CVMFS and DevOps infrastructure. We will assume that you have read the previous section as well that talks about setting up a development environment in Docker.
In this project, we’re using Terraform to set up an AWS EC2 instance that will host our MicroK8s Kubernetes cluster. Here’s how Terraform will simplify our work:
With our Docker container up and running, it’s time to SSH into it and start setting up our Terraform configuration. Our goal is to automate the deployment of an EC2 instance that’s perfectly tailored for our MicroK8s cluster, complete with the necessary security settings and access controls.
Run this command to access the container:
docker exec -it devops-workspace bash
This command allows you to SSH into the container and observe that any local changes you make are reflected inside the container, keeping everything synchronized. Now, let’s dive into configuring Terraform to deploy our AWS EC2 instance that will host our MicroK8s cluster. We’ll also create a script to provision this instance with everything needed for our Kubernetes setup.
main.tfFirst, we create a main.tf file to define our AWS provider, set the region, and fetch our IP address dynamically for secure SSH access. We’ll use Ubuntu 20.04 as our base AMI.
# main.tf
# Configure the AWS provider to manage resources in the specified region.
provider "aws" {
region = "us-east-1" # Set the AWS region to US East (N. Virginia).
}
# Retrieve the public IP of the current machine using an external HTTP data source.
# This is useful for configuring access rules, allowing the local IP to access certain AWS resources.
data "http" "myip" {
url = "https://ipv4.icanhazip.com" # Service that returns the public IP address.
}
# Look up the latest Ubuntu 20.04 AMI (Amazon Machine Image) in the AWS region.
# This data source dynamically selects the most recent AMI, ensuring that instances use the latest Ubuntu version.
data "aws_ami" "ubuntu" {
most_recent = true # Ensures the AMI is the latest available version.
# Filter the AMI selection by name pattern, specifying the Ubuntu 20.04 focal version with HVM (hardware virtual machine) and SSD support.
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] # Match AMIs with this name pattern.
}
# Define the owner of the AMI to limit the selection to official Ubuntu images.
owners = ["099720109477"] # Canonical's AWS account ID, which owns the official Ubuntu AMIs.
}
Next, we define a security group to control access to our EC2 instance. It will restrict SSH and Kubernetes communication to our local machine’s IP.
# main.tf continued
# Define a security group for the MicroK8s EC2 instance.
# This security group:
# - Allows outbound (egress) traffic to any destination.
# - Restricts inbound (ingress) traffic to SSH and Kubernetes API access from the user's public IP.
resource "aws_security_group" "microk8s_sg" {
name = "microk8s-sg" # Name of the security group.
description = "Security group for MicroK8s EC2 instance"
vpc_id = var.vpc_id # Specify the VPC where this security group will be applied.
# Egress rule allowing all outbound traffic.
egress {
from_port = 0 # Allow traffic on all ports.
to_port = 0
protocol = "-1" # -1 specifies all protocols.
cidr_blocks = ["0.0.0.0/0"] # Allow traffic to any IP address.
}
# Ingress rule to allow SSH access (port 22) from the user's public IP.
ingress {
from_port = 22 # Port for SSH.
to_port = 22
protocol = "tcp" # TCP protocol for SSH access.
cidr_blocks = ["${chomp(data.http.myip.response_body)}/32"] # Allow access only from the user’s current public IP.
description = "Allow SSH access from your public IP" # Description for easier identification in AWS.
}
# Ingress rule to allow access to the Kubernetes API (port 16443) from the user's public IP.
ingress {
from_port = 16443 # Port for Kubernetes API server.
to_port = 16443
protocol = "tcp" # TCP protocol for Kubernetes API access.
cidr_blocks = ["${chomp(data.http.myip.response_body)}/32"] # Restrict access to the user’s current public IP.
description = "Allow K8s access from your public IP" # Description for the security rule.
}
}
Now we’ll create the EC2 instance, configure it, and transfer necessary setup files. We’ll run a script to prepare the instance for MicroK8s. Do not worry about the files we are transferring over to provision just yet! We will be going into detail about them in the next part of this blog!
# Define an EC2 instance to host MicroK8s, configured with a security group, storage, and setup scripts.
resource "aws_instance" "microk8s_instance" {
ami = data.aws_ami.ubuntu.id # Use the latest Ubuntu 20.04 AMI for the instance.
instance_type = var.instance_type # Set instance type dynamically from a variable.
subnet_id = var.subnet_id # Specify the subnet to launch the instance in.
vpc_security_group_ids = [aws_security_group.microk8s_sg.id] # Attach the previously created security group.
key_name = var.key_name # Specify SSH key name for secure access to the instance.
# Configure the root block device to provide storage for the instance.
root_block_device {
volume_size = 500 # Set root volume size to 500 GB.
volume_type = "gp2" # Use general-purpose SSD storage.
}
# Connection configuration for SSH access to the instance, allowing provisioners to connect.
connection {
type = "ssh"
user = "ubuntu" # Default Ubuntu user for SSH access.
private_key = file(var.private_key_path) # Path to the private SSH key for authentication.
host = self.public_ip # Connect to the instance's public IP.
}
# Provisioner to upload the main setup script to the instance, which automates MicroK8s configuration.
provisioner "file" {
source = "${path.module}/setup.sh" # Local setup script.
destination = "/home/ubuntu/setup.sh" # Remote path on the instance.
}
# Provisioner to upload the CVMFS values file, containing configurations specific to CVMFS setup.
provisioner "file" {
source = "${path.module}/cvmfs-values.yaml" # Local configuration for CVMFS.
destination = "/home/ubuntu/cvmfs-values.yaml" # Remote path for use in setup.
}
# Provisioner to upload the PersistentVolumeClaim YAML file, defining the CVMFS storage claim.
provisioner "file" {
source = "${path.module}/cvmfs-pvc.yaml" # Local PVC YAML file.
destination = "/home/ubuntu/cvmfs-pvc.yaml" # Remote path on the instance.
}
# Provisioner to upload the test pod YAML file, used for validating the CVMFS setup.
provisioner "file" {
source = "${path.module}/cvmfs-test-pod.yaml" # Local YAML file defining a test pod.
destination = "/home/ubuntu/cvmfs-test-pod.yaml" # Remote path on the instance.
}
# Remote-exec provisioner to execute the setup script on the instance after file uploads.
provisioner "remote-exec" {
inline = [
"sudo bash /home/ubuntu/setup.sh" # Execute setup script with sudo privileges.
]
}
# Add tags for easy identification of the instance within AWS.
tags = {
Name = "microk8s-dev-instance" # Tag to identify the instance as a MicroK8s development environment.
}
}
To simplify our configuration and make it easier to maintain, we use a variables.tf file to manage all our dynamic values.
# variables.tf
# Specify the AWS region for resource deployment.
# Default is set to "us-east-1", but this can be modified as needed.
variable "aws_region" {
description = "The AWS region where the resources will be deployed"
default = "us-east-1" # Sets a default region for resource creation.
}
# Define the instance type for the EC2 instance.
# Default is "t2.2xlarge", which provides a balance of CPU and memory for development.
variable "instance_type" {
description = "The instance type to use for the EC2 instance"
default = "t2.2xlarge" # Instance type can be changed as needed.
}
# Define the VPC ID for the EC2 instance deployment.
# This VPC will host the instance, and its ID must be specified for correct placement.
variable "vpc_id" {
description = "The VPC ID where the instance will be deployed"
}
# Define the Subnet ID for the EC2 instance.
# The subnet provides the network configuration for the instance within the specified VPC.
variable "subnet_id" {
description = "The Subnet ID where the instance will be deployed"
}
# Define the SSH key name for secure access to the EC2 instance.
# This key pair must already exist in the specified AWS region and will be used to SSH into the instance.
variable "key_name" {
description = "The key pair name for SSH access to the EC2 instance"
}
# Define the name of the AMI (Amazon Machine Image) for the Ubuntu base image.
# This variable allows flexibility in selecting different AMIs for future instances.
variable "ami_name" {
description = "The AMI name to use for the EC2 instance"
}
# Define the path to the private key file for SSH access.
# This path points to the local private key file that Terraform uses to connect to the instance.
variable "private_key_path" {
description = "The path to the private key file for SSH access"
}
terraform.tfvarsOnce we’ve defined our variables, we need to supply them with specific values for our deployment. The terraform.tfvars file is where we do this, effectively overriding any default values and customizing the setup for our environment.
Example terraform.tfvars:
# terraform.tfvars
# AWS region for deployment
aws_region = "us-east-1"
# The VPC ID where the instance will be deployed
vpc_id = "VPC-VALUE"
# The Subnet ID where the instance will be deployed
subnet_id = "SUBNET-VALUE"
# The key pair name for SSH access
key_name = "KEYPAIR-NAME"
# AMI name for the Ubuntu base image
ami_name = "AMI-NAME"
# The path to the private key file for SSH connection
private_key_path = "/path/to/your/YOUR_KEY_PAIR.pem"
At this point in the guide, here’s what our project structure should look like
├── devops/
├── .env
├── configure-aws.sh
|── YOUR_KEY_PAIR.pem
|── Dockerfile
|── docker-compose.yml
|── provision.sh
├── cvmfs-full-setup-basic/
├── main.tf # Terraform configuration
├── terraform.tfvars # Specific variables for terraform
└── variables.tf # Generic Variables for Terraform
In this section, we’ve walked through setting up a Terraform configuration to automate the deployment of an AWS EC2 instance for our MicroK8s Kubernetes cluster. We covered defining the AWS provider, setting up security groups, creating the EC2 instance, and using variables to make our setup flexible and maintainable.
In the next part of our series, we’ll dive into what’s inside the setup.sh script. We’ll cover everything from installing essential drivers to configuring MicroK8s and other necessary tools to get our Kubernetes cluster up and running. Stay tuned. There’s plenty more to come!
Next Section: Kubernetes and Helm: The Beauty of Orchestration