Objective
- Setup Jenkins for automate Terraform process
- Provision highly available 3-tier architecture with Terraform
Pre-requisite :
- AWS & Github Account
- Configured AWS Credentials on local
- AWS CLI Installed on local
First | Create and Setup Jenkins Server
Run EC2 Instance for Jenkins Server and fill the parameter what do you want.
aws ec2 run-instances \
--image-id ami-abcd1234 \
--instance-type t2.medium \
--key-name keypair-name \
--subnet-id subnet-abcd1234 \
--security-group-ids sg-abcd1234 \
--user-data file://setup.sh
This is a content of setup.sh :
#!/bin/bash
#Ubuntu/Debian
# Install Jenkins
sudo apt update && sudo apt install git fontconfig unzip openjdk-17-jre -y
curl -fsSL https://pkg.jenkins.io/debian/jenkins.io-2023.key | sudo tee /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update && sudo apt-get install jenkins -y
sudo systemctl enable jenkins
# Install Terraform
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
After instance running and Jenkins successfuly installed, ensure you have open port 8080 on Security Group because its Jenkins default port. And now setting up Jenkins with access your-instance-ip-or-domain:8080 via browser.
To unlock Jenkins, open terminal and use command below to see the password.
> ssh -i keypair-name user@domain/public-ip-instance 'sudo cat /var/lib/jenkins/secrets/initialAdminPassword'
0587d04623434423a1532785eaf9692a
Second | Generate AWS Credentials
You can skip this step, if you already have Access & Secret Key.
aws iam create-access-key --user-name your-user
output :
{
"AccessKey": {
"UserName": "your-user",
"AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
"Status": "Active",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"CreateDate": "2023-10-054T17:34:16Z"
}
}
Third | Configure AWS Credentials on Jenkins Server
Use command below to configure AWS Credentials.
ssh -i keypair-name jenkins@domain/public-ip-instance '
aws configure set aws_access_key_id "YOUR-ACCESS-KEY" --profile jenkins && \
aws configure set aws_secret_access_key "YOUR-SECRET-KEY" --profile jenkins && \
aws configure set region "us-east-1" --profile jenkins
'
Fourth | Setup Jenkins Pipeline
Click New Item > Enter Pipeline Name.
In Definition choose ‘Pipeline script for SCM’, then fill the repository URL & credentials box with your own and uncheck Lightweight checkout.
And this is a code of Jenkinsfile.
#Jenkinsfile
pipeline {
agent any
stages {
stage("Checkout"){
steps{
echo pwd()
git branch: 'main',
credentialsId: 'auth-jenkins-tf',
url: 'https://github.com/defloriooryn/jenkins-terraform.git'
}
}
stage("TF Init"){
steps{
sh 'export AWS_PROFILE=jenkins'
sh 'terraform init'
}
}
stage("TF Validate"){
steps{
sh 'terraform validate'
}
}
stage("TF Plan"){
steps{
sh 'terraform plan'
}
}
stage("TF Apply"){
steps{
sh 'terraform apply -auto-approve'
}
}
}
}
Fifth | Deploy 3-Tier Architecture
Below is the terraform code for the 3 tier architecture topology above or you can find the complete code in my GitHub account.
version.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
profile = "jenkins"
}
backend.tf
terraform {
backend "s3" {
region = "us-east-1"
bucket = "nizamzam"
key = "terraform.tfstate"
encrypt = true
}
}
networking.tf
resource "aws_vpc" "vpc_example" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc_example.id
}
resource "aws_subnet" "public" {
for_each = var.subnet_public
vpc_id = aws_vpc.vpc_example.id
cidr_block = each.value.cidr
availability_zone = each.value.az
map_public_ip_on_launch = true
}
resource "aws_subnet" "private" {
for_each = var.subnet_private
vpc_id = aws_vpc.vpc_example.id
cidr_block = each.value.cidr
availability_zone = each.value.az
}
resource "aws_subnet" "database" {
for_each = var.subnet_database
vpc_id = aws_vpc.vpc_example.id
cidr_block = each.value.cidr
availability_zone = each.value.az
}
resource "aws_eip" "eip_natgw" {
domain = "vpc"
}
resource "aws_nat_gateway" "nat_gw" {
allocation_id = aws_eip.eip_natgw.id
subnet_id = values(aws_subnet.public)[0].id
}
resource "aws_route_table" "route_public" {
vpc_id = aws_vpc.vpc_example.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
}
resource "aws_route_table" "route_private" {
vpc_id = aws_vpc.vpc_example.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_nat_gateway.nat_gw.id
}
}
resource "aws_route_table_association" "public" {
for_each = aws_subnet.public
subnet_id = each.value.id
route_table_id = aws_route_table.route_public.id
}
resource "aws_route_table_association" "private" {
for_each = aws_subnet.private
subnet_id = each.value.id
route_table_id = aws_route_table.route_private.id
}
resources.tf
# Security Group Application Load Balancer
resource "aws_security_group" "sg-alb" {
name = "sg_alb"
vpc_id = aws_vpc.vpc_example.id
description = "allow http and https"
dynamic "ingress" {
for_each = var.sg_alb_port
iterator = port
content {
from_port = port.value
to_port = port.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
egress {
from_port = 0
to_port = 0
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
# Security Group Instance
resource "aws_security_group" "sg-instance" {
name = "sg_ec2"
vpc_id = aws_vpc.vpc_example.id
description = "allow http from sg-alb"
dynamic "ingress" {
for_each = var.sg_ec2_port
iterator = port
content {
from_port = port.value
to_port = port.value
protocol = "tcp"
security_groups = [aws_security_group.sg-alb.id]
}
}
egress {
from_port = 0
to_port = 0
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
#Security Group RDS
resource "aws_security_group" "sg-rds" {
name = "sg_rds"
vpc_id = aws_vpc.vpc_example.id
description = "allow port 3306 from sg-instance"
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.sg-instance.id]
}
egress {
from_port = 0
to_port = 0
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_launch_template" "launch_template" {
name = "template"
image_id = var.instance_template.ami
instance_type = var.instance_template.type
user_data = base64encode(file("${path.module}/script/nginx.sh"))
network_interfaces {
device_index = 0
security_groups = [aws_security_group.sg-instance.id]
}
}
resource "aws_autoscaling_group" "asg" {
desired_capacity = var.asg.desired
min_size = var.asg.min
max_size = var.asg.max
target_group_arns = [aws_lb_target_group.tg_instance.arn]
vpc_zone_identifier = values(aws_subnet.private)[*].id
launch_template {
id = aws_launch_template.launch_template.id
version = "$Latest"
}
}
resource "aws_lb" "alb" {
name = "external-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.sg-alb.id]
dynamic "subnet_mapping" {
for_each = aws_subnet.public
content {
subnet_id = subnet_mapping.value.id
}
}
}
resource "aws_lb_target_group" "tg_instance" {
name = "nginx"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.vpc_example.id
health_check {
path = "/"
matcher = 200
}
}
resource "aws_lb_listener" "lb_listener_http" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"
default_action {
target_group_arn = aws_lb_target_group.tg_instance.id
type = "forward"
}
}
resource "aws_db_instance" "db" {
allocated_storage = var.db.allocated_storage
db_name = var.db.db_name
db_subnet_group_name = aws_db_subnet_group.sg_db.name
engine = var.db.engine
engine_version = var.db.engine_version
instance_class = var.db.instance_class
username = var.db.username
password = var.db.password
parameter_group_name = var.db.parameter_group_name
skip_final_snapshot = var.db.skip_final_snapshot
availability_zone = var.db.availability_zone
multi_az = var.db.multi_az
}
output.tf
output "alb_dns" {
value = aws_lb.alb.dns_name
}
variables.tf
variable "region" {
type = string
default = "us-east-1"
}
# VPC & Subnet
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "subnet_public" {
type = any
default = {
"public-1" = { cidr = "10.0.0.0/24", az = "us-east-1a" }
"public-2" = { cidr = "10.0.1.0/24", az = "us-east-1b" }
}
}
variable "subnet_private" {
type = map(object({
cidr = string
az = string
}))
default = {
"private-1" = { cidr = "10.0.2.0/24", az = "us-east-1a" }
"private-2" = { cidr = "10.0.3.0/24", az = "us-east-1b" }
}
}
variable "subnet_database" {
type = map(object({
cidr = string
az = string
}))
default = {
"private-3" = { cidr = "10.0.4.0/24", az = "us-east-1a" }
"private-4" = { cidr = "10.0.5.0/24", az = "us-east-1b" }
}
}
# Security Group
variable "sg_alb_port" {
type = list(any)
default = [80, 443]
}
variable "sg_ec2_port" {
type = list(any)
default = [80, 22]
}
variable "sg_rds_port" {
type = number
default = 3306
}
# Auto Scalling Group
variable "instance_template" {
type = map(string)
default = {
type = "t2.micro"
ami = "ami-067d1e60475437da2" #Amazon Linux 2023
}
}
# ASG
variable "asg" {
type = any
default = {
az = ["us-east-1a", "us-east-1b"]
desired = 2
min = 2
max = 4
}
}
# Database
variable "db" {
type = map(string)
default = {
allocated_storage = 10
db_name = "mydb"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
username = "admin"
password = "admin"
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
availability_zone = "us-east-1a"
multi_az = false
}
}
Sixth | Test Pipeline
After the Jenkins setup is complete and you have pushed the code to your repository. Then in your Job, click Build Now to trigger the pipeline.
Additional :
Just like that for notes today, hopefully I can still exist to update this website