Skip to content

Commit

Permalink
add terraform for provision an AWS EKS cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
FernandoArteaga committed Feb 12, 2024
1 parent 5833ed4 commit 63570f0
Show file tree
Hide file tree
Showing 13 changed files with 568 additions and 3 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/check-terraform-files.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: "Check Terraform files"
on:
pull_request: {}

jobs:
check:
name: "Run Terraform fmt and validate"
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v4

- name: "Setup Terraform"
uses: hashicorp/setup-terraform@v3

- name: "Run terraform fmt"
run: terraform fmt -check -recursive
working-directory: terraform

- name: "Run terraform validate"
run: terraform validate
if: always()
working-directory: terraform
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Here you can find several examples and guides on some fundamental DevOps concept

* [Docker](./my-awesome-app/README.md)
* [Docker compose](./my-awesome-app/README.md#docker-compose)
* Kubernetes Manifests
* Terraform
* [Kubernetes Manifests](./kubernetes/README.md)
* [Terraform](./terraform/README.md)
* Helm Charts
* [CI/CD](./.github/workflows/README.md)
3 changes: 2 additions & 1 deletion kubernetes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ and an [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress
### Create the resources

Before creating the resources, we need to have the `kubectl` command-line tool installed and a Kubernetes cluster running.
For this demo, you can choose to use a [local Kubernetes cluster](#local-kubernetes-cluster) or a cloud provider like [AWS (EKS)](https://aws.amazon.com/eks/).
For this demo, you can choose to use a [local Kubernetes cluster](#local-kubernetes-cluster) or a [cloud provider](../terraform/README.md#provision-the-infrastructure)
like AWS or GCP.

Once you have the Kubernetes cluster running, you can create the resources by following the steps below.
1. Create a namespace named `learn-devops`, where we will deploy the resources
Expand Down
55 changes: 55 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Terraform

[Terraform](https://www.terraform.io/) is an open-source infrastructure as code software tool created by HashiCorp. It enables users to define and
provision cloud infrastructure using a high-level configuration language known as Hashicorp Configuration Language (HCL).

The main advantage of using Terraform is that it allows you to define your infrastructure as code once and then deploy it
in multiple environments, having always the same result.

We will provision a simple infrastructure that we can use to deploy the [my-awesome-app](../kubernetes/README.md#kubernetes-manifests)
example. This infrastructure will consist of a VPC, a public subnet, an internet gateway, a security group, and an EKS instance.

Requirements:
- Make sure you have the [terraform CLI](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) installed

## AWS

To provision the infrastructure in AWS, we will use the [AWS Terraform provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs).
You can find the Terraform code in the [aws](./aws) directory.

Requirements:
- Make sure you have the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed
- You need to have an AWS account and the [AWS CLI configured](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html)

## Provision the infrastructure

1. Select the cloud provider you want to use and follow the instructions in the respective directory. In this example,
we will use AWS.
```sh
cd aws
```
2. Adjust the values in the [_config.tf](./aws/_config.tfvars) file to match your environment.
3. Initialize the Terraform working directory and download the AWS provider plugin.
```sh
terraform init
```
4. Create an execution plan.
```sh
terraform plan -out tfplan
```
5. Apply the changes to the infrastructure.
```sh
terraform apply tfplan
```

Once the infrastructure is provisioned, you can update your `kubeconfig` file to connect to the EKS cluster by running the following command:
```sh
aws eks --region us-east-1 update-kubeconfig --name learn-devops
```

## Clean up

Once you are done with the infrastructure, you can destroy it by running the following command:
```sh
terraform destroy
```
25 changes: 25 additions & 0 deletions terraform/aws/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions terraform/aws/_config.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
region = "us-east-1"
k8s_cluster_name = "learn-devops"
87 changes: 87 additions & 0 deletions terraform/aws/eks.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
resource "aws_eks_cluster" "main" {
name = var.k8s_cluster_name
role_arn = aws_iam_role.eks_cluster.arn

vpc_config {
endpoint_private_access = true
endpoint_public_access = true
subnet_ids = concat(
aws_subnet.public_subnet[*].id,
aws_subnet.private_subnet[*].id
)
}

# Ensure that IAM Role permissions are created before and deleted after EKS Cluster handling.
# Otherwise, EKS will not be able to properly delete EKS managed EC2 infrastructure such as Security Groups.
depends_on = [
aws_iam_role_policy_attachment.aws_eks_cluster_policy,
aws_subnet.public_subnet,
aws_subnet.private_subnet,
]
}

# Nodes in private subnets
resource "aws_eks_node_group" "private" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "private-node-group"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = aws_subnet.private_subnet[*].id

instance_types = ["t2.micro"]
capacity_type = "SPOT"

scaling_config {
desired_size = 1
max_size = 2
min_size = 1
}

taint {
key = "vm-size"
value = "tiny"
effect = "NO_SCHEDULE"
}

tags = merge(var.tags, { subnet = "private" })

# Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling.
# Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces.
depends_on = [
aws_iam_role_policy_attachment.aws_eks_worker_node_policy,
aws_iam_role_policy_attachment.aws_eks_cni_policy,
aws_iam_role_policy_attachment.ec2_read_only,
]
}

# Nodes in public subnet
resource "aws_eks_node_group" "public" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "public-node-group"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = aws_subnet.public_subnet[*].id

instance_types = ["t2.micro"]
capacity_type = "SPOT"

scaling_config {
desired_size = 1
max_size = 2
min_size = 1
}

taint {
key = "vm-size"
value = "tiny"
effect = "NO_SCHEDULE"
}

tags = merge(var.tags, { subnet = "public" })

# Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling.
# Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces.
depends_on = [
aws_iam_role_policy_attachment.aws_eks_worker_node_policy,
aws_iam_role_policy_attachment.aws_eks_cni_policy,
aws_iam_role_policy_attachment.ec2_read_only,
]
}
61 changes: 61 additions & 0 deletions terraform/aws/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html

resource "aws_iam_role" "eks_cluster" {
name = "${var.k8s_cluster_name}-cluster"

assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Service" : "eks.amazonaws.com"
},
"Action" : "sts:AssumeRole"
}
]
})

tags = var.tags
}

resource "aws_iam_role_policy_attachment" "aws_eks_cluster_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.eks_cluster.name
}

#https://docs.aws.amazon.com/eks/latest/userguide/worker_node_IAM_role.html

resource "aws_iam_role" "eks_nodes" {
name = "${var.k8s_cluster_name}-worker"

assume_role_policy = data.aws_iam_policy_document.assume_workers.json
}

data "aws_iam_policy_document" "assume_workers" {
statement {
effect = "Allow"

actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}

resource "aws_iam_role_policy_attachment" "aws_eks_worker_node_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
role = aws_iam_role.eks_nodes.name
}

resource "aws_iam_role_policy_attachment" "aws_eks_cni_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
role = aws_iam_role.eks_nodes.name
}

resource "aws_iam_role_policy_attachment" "ec2_read_only" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
role = aws_iam_role.eks_nodes.name
}
13 changes: 13 additions & 0 deletions terraform/aws/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

# Configure the AWS Provider
provider "aws" {
region = var.region
}
99 changes: 99 additions & 0 deletions terraform/aws/network.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Create a VPC with a /16 (65,536 IP addresses) IPv4 CIDR block
resource "aws_vpc" "main" {
assign_generated_ipv6_cidr_block = false
cidr_block = var.vpc_cidr_block
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags = var.tags
}

# Create a private subnet with a /24 (256 IP addresses) IPv4 CIDR block
resource "aws_subnet" "private_subnet" {
count = length(var.availability_zones)
assign_ipv6_address_on_creation = false
availability_zone = element(var.availability_zones, count.index)
cidr_block = element(var.private_subnet_cidr_blocks, count.index)
map_public_ip_on_launch = false
tags = var.tags
vpc_id = aws_vpc.main.id
}

# Create a public subnet with a /24 (256 IP addresses) IPv4 CIDR block
resource "aws_subnet" "public_subnet" {
count = length(var.availability_zones)
availability_zone = element(var.availability_zones, count.index)
cidr_block = element(var.public_subnet_cidr_blocks, count.index)
map_public_ip_on_launch = true
tags = var.tags
vpc_id = aws_vpc.main.id
}

# Create Internet Gateway for the public subnets
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = var.tags

depends_on = [
aws_vpc.main,
]
}

# Create Elastic IP
resource "aws_eip" "elastic_ip" {
public_ipv4_pool = "amazon"
domain = "vpc"
tags = var.tags
}

# Create NAT Gateway
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.elastic_ip.id
subnet_id = aws_subnet.public_subnet[0].id

tags = var.tags
depends_on = [
aws_eip.elastic_ip,
aws_subnet.public_subnet,
]
}


# Create a route table for the VPC with a single route that sends all traffic to the Internet Gateway
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}

tags = var.tags
depends_on = [
aws_internet_gateway.igw,
]
}

# Associate the public subnet with the public route table
resource "aws_route_table_association" "internet_access" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public_subnet[count.index].id
route_table_id = aws_route_table.public.id

depends_on = [
aws_subnet.public_subnet,
aws_route_table.public,
]
}

# Add route to route table
resource "aws_route" "main" {
route_table_id = aws_vpc.main.default_route_table_id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id

depends_on = [
aws_vpc.main,
aws_internet_gateway.igw,
]
}
Loading

0 comments on commit 63570f0

Please sign in to comment.