Setting up a Jenkins build server

Introduction

Using Python and the CDK to setup a Jenkins build server.

2 stacks were created for this example.

These stacks help define the following AWS components:

1. Network Stack

  • VPC
  • Public Subnets
  • Routing table
  • Internet gateway

2. EC2 Stack

  • Auto Scaling Group
  • Security Groups

Ansible was used to install Jenkins on the EC2 instance.

app.py

#!/usr/bin/env python3

from aws_cdk import core
from EC2Stack import EC2Stack
from NetworkStack import NetworkStack

props = {
    'namespace':'JenkinsBuildServer',
    'vpc_name':'devops',
    'ec2_instance_name':'jenkins-build-server',
    'wan_ip':'your_ip_address',
    'ec2_instance_type' : 't2.micro',
    'key_pair':'your_key_pair',
}
env = core.Environment(account="your-aws-account-id",region="your-region")

app = core.App()
network_stack = NetworkStack(app,f"{props['namespace']}-network",props,env=env)

ec2_stack = EC2Stack(app, f"{props['namespace']}-ec2",network_stack.output_props,env=env)
ec2_stack.add_dependency(network_stack)

app.synth()

NetworkStack.py

from aws_cdk import (
    core,
    aws_ec2 as ec2
)

class NetworkStack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, props, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # The code that defines your stack goes here

        # Create VPC

        # Create VPC with a single Public Subnet

        vpc = ec2.CfnVPC(
            self,
            "VPC",
            enable_dns_hostnames=True,
            enable_dns_support=True,
            cidr_block="10.0.0.0/16"

        )

        vpc.tags.set_tag(key="Name",value=props['vpc_name'])

        # Create Routing table for public subnet
        route_table_public = ec2.CfnRouteTable(
            self,
            "RtbPublic",
            vpc_id=vpc.ref
        )

        route_table_public.tags.set_tag(key="Name",value="EC2 Public Routing Table")

        # Create public subnet

        public_subnet = ec2.CfnSubnet(
            self,
            "PublicSubnet",
            cidr_block="10.0.0.0/24",
            vpc_id=vpc.ref,
            map_public_ip_on_launch=True,
        )

        public_subnet.tags.set_tag(key="Name",value="devops-public-subnet")

        # Create internet gateway

        inet_gateway = ec2.CfnInternetGateway(
            self,
            "DevOpsIgw",
            tags=[core.CfnTag(key="Name",value="devops-igw")]
        )

        ec2.CfnVPCGatewayAttachment(
            self,
            "IgwAttachment",
            vpc_id=vpc.ref,
            internet_gateway_id=inet_gateway.ref
        )

        # Add gw to route to routing table

        ec2.CfnRoute(
            self,
            "RouteInetGateway",
            route_table_id=route_table_public.ref,
            destination_cidr_block="0.0.0.0/0",
            gateway_id=inet_gateway.ref

        )

       
        # Routing table association with public subnet
        ec2.CfnSubnetRouteTableAssociation(
            self,
            "RtbAssocPublic",
            route_table_id=route_table_public.ref,
            subnet_id=public_subnet.ref
        )

        self.output_props = props.copy()
        self.output_props['vpc_id']  = vpc.ref

    @property
    def outputs(self):
        return self.output_props

EC2Stack.py

from aws_cdk import (
    core,
    aws_ec2 as ec2,
    aws_autoscaling as autoscaling
)

class EC2Stack(core.Stack):

    def __init__(self, scope: core.Construct, id: str, props, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # The code that defines your stack goes here

        # EC2 security group

        sg_ec2 = ec2.CfnSecurityGroup(
            self,
            "ec2-sec-group",
            group_description="EC2 Security Group",
            vpc_id=props['vpc_id']
            
        )

        sg_ec2.tags.set_tag(key="Name",value="sg-devops-ec2")

        # add rules to sg
        ec2.CfnSecurityGroupIngress(
            self,
            "sg-http-ingress",
            ip_protocol="tcp",
            from_port=80,
            to_port=80,
            cidr_ip="0.0.0.0/0",
            group_id=sg_ec2.ref
        )

        ec2.CfnSecurityGroupIngress(
            self,
            "sg-https-ingress",
            ip_protocol="tcp",
            from_port=443,
            to_port=443,
            cidr_ip="0.0.0.0/0",
            group_id=sg_ec2.ref
        )

        ec2.CfnSecurityGroupIngress(
            self,
            "sg-ssh-ingress",
            ip_protocol="tcp",
            cidr_ip=props['wan_ip']+"/32",
            from_port=22,
            to_port=22,
            group_id=sg_ec2.ref
        )

        # define machine image ami

        amzn_linux_ami = ec2.MachineImage.latest_amazon_linux(
            edition=ec2.AmazonLinuxEdition.STANDARD,
            generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
            virtualization=ec2.AmazonLinuxVirt.HVM,
            storage=ec2.AmazonLinuxStorage.GENERAL_PURPOSE
        )

        # create autoscaling group for ec2 instance

        asg = autoscaling.AutoScalingGroup(
            self,
            "AutoScalingGroup",
            instance_type=ec2.InstanceType(props['ec2_instance_type']),
            machine_image=amzn_linux_ami,
            associate_public_ip_address=True,
            update_type=autoscaling.UpdateType.REPLACING_UPDATE,
            desired_capacity=1,
            vpc=ec2.Vpc.from_lookup(self,"VPCLookup",vpc_name=props['vpc_name']),
            vpc_subnets={'subnet_type':ec2.SubnetType.PUBLIC},
            security_group=ec2.SecurityGroup.from_security_group_id(self,"LookupSG",security_group_id=sg_ec2.ref),
            key_name=props['key_pair']
        )

Ansible

A playbook was created to setup Jenkins on the EC2 instance.

---
# tasks file for jenkins-build-server

- name: Run yum upgrade
  yum:
    name: "*"
    state: latest

- name: Install openjdk 8
  yum:
    name: java-1.8.0-openjdk-devel.x86_64
    state: present
  
- name: Add the Jenkins repo
  get_url: 
    url: http://pkg.jenkins-ci.org/redhat/jenkins.repo
    dest: /etc/yum.repos.d/jenkins.repo

- name: Import a key file from Jenkins-CI to enable installation from the package
  rpm_key:
    state: present
    key: https://pkg.jenkins.io/redhat/jenkins.io.key

- name: Install Jenkins
  yum:
    name: jenkins
    state: latest

- name: Start Jenkins as a service
  service:
    name: jenkins
    state: started

Inventory

[jenkins]
EC2_INSTANCE_IP_ADDRESS ansible_ssh_user=ec2-user ansible_ssh_private_key_file=PATH_TO_PEM_FILE

Playbook

 hosts: jenkins
  become: yes
  become_user: root
 
  roles:
    - jenkins-build-server

Running the playbook

ansible-playbook -i inventory playbook.yml
Last updated on 21 Aug 2020
Published on 21 Aug 2020