Skip to content

Automate CI/CD pipelines using Jenkins, AWS EFS, CloudFormation, and Terraform. This project streamlines deployment and infrastructure management for efficient development workflows.

Notifications You must be signed in to change notification settings

roeeelnekave/jenkins-efs-vpc-ecs-part-1

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Jenkins with ECS with Presistent Volume using EFS system.

How to deploy this project using CloudFormation

Prerequisites

  1. AWS Account
  2. Basic Understanding of AWS services (VPC, EFS, ECS, IAM)
  3. Docker and Docker Compose
  4. Basic understanding of the CloudFormation template
  5. AWS CLI

STEPS

  • We need a Dockerfile to customize our Jenkins image. Create a Dockerfile:

  • Create a folder for your project and inside that folder create a file named Dockerfile and paste the following into the Dockerfile:

FROM amazonlinux:2023 RUN yum install -y
python3
python3-pip
git
zip
unzip
tar
gzip
wget
jq
which
findutils
python3-pip &&
python3 -m pip install awscli &&
python3 -m pip install boto3 &&
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo &&
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key &&
yum upgrade -y &&
yum install -y fontconfig &&
dnf install java-17-amazon-corretto -y &&
yum install -y jenkins &&
python3 -m pip install ansible &&
yum clean all EXPOSE 8080 CMD ["java", "-jar", "/usr/share/java/jenkins.war"]


- **Create a CloudFormation file for deploying our infrastructure:**
- Create a file named `main.yaml` and paste the following into the YAML template one by one:

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template for VPC with 3 public and 3 private subnets'
  • Create a Parameter for Image URL of jenkins image
Parameters:
  ImageURL:
    Type: String
    Description: 'Image URL for the ECR repo'
    Default: 'image-uri'
  • We then create a VPC in the Resources section:

    Resources:
        VPC:
            Type: AWS::EC2::VPC
            Properties:
                CidrBlock: 10.0.0.0/16
                EnableDnsHostnames: true
                EnableDnsSupport: true
                InstanceTenancy: default
                Tags:
                    - Key: Name
                      Value: project-vpc
  • We then create an Internet Gateway and Internet Gateway attachment:

        InternetGateway:
            Type: AWS::EC2::InternetGateway
        InternetGatewayAttachment:
            Type: AWS::EC2::VPCGatewayAttachment
            Properties:
                VpcId: !Ref VPC
                InternetGatewayId: !Ref InternetGateway
  • We create 3 Public Subnets and 3 Private Subnets for the VPC for EFS and ECS to be available in different AZs:

        PublicSubnet1:
            Type: AWS::EC2::Subnet
            Properties:
                VpcId: !Ref VPC
                AvailabilityZone: !Select [ 0, !GetAZs '' ]
                CidrBlock: 10.0.1.0/24
                MapPublicIpOnLaunch: true
                Tags:
                    - Key: Name
                      Value: project-subnet-public1-us-east-1a
        PublicSubnet2:
            Type: AWS::EC2::Subnet
            Properties:
                VpcId: !Ref VPC
                AvailabilityZone: !Select [ 1, !GetAZs '' ]
                CidrBlock: 10.0.2.0/24
                MapPublicIpOnLaunch: true
                Tags:
                    - Key: Name
                      Value: project-subnet-public2-us-east-1b
        PublicSubnet3:
            Type: AWS::EC2::Subnet
            Properties:
                VpcId: !Ref VPC
                AvailabilityZone: !Select [ 2, !GetAZs '' ]
                CidrBlock: 10.0.3.0/24
                MapPublicIpOnLaunch: true
                Tags:
                    - Key: Name
                      Value: project-subnet-public3-us-east-1c
        PrivateSubnet1:
            Type: AWS::EC2::Subnet
            Properties:
                VpcId: !Ref VPC
                AvailabilityZone: !Select [ 0, !GetAZs '' ]
                CidrBlock: 10.0.4.0/24
                MapPublicIpOnLaunch: false
                Tags:
                    - Key: Name
                      Value: project-subnet-private1-us-east-1a
        PrivateSubnet2:
            Type: AWS::EC2::Subnet
            Properties:
                VpcId: !Ref VPC
                AvailabilityZone: !Select [ 1, !GetAZs '' ]
                CidrBlock: 10.0.5.0/24
                MapPublicIpOnLaunch: false
                Tags:
                    - Key: Name
                      Value: project-subnet-private2-us-east-1b
        PrivateSubnet3:
            Type: AWS::EC2::Subnet
            Properties:
                VpcId: !Ref VPC
                AvailabilityZone: !Select [ 2, !GetAZs '' ]
                CidrBlock: 10.0.6.0/24
                MapPublicIpOnLaunch: false
                Tags:
                    - Key: Name
                      Value: project-subnet-private3-us-east-1c
  • We are creating a Public Route Table and its association for the Public Subnet:

        PublicRouteTable:
            Type: AWS::EC2::RouteTable
            Properties:
                VpcId: !Ref VPC
                Tags:
                    - Key: Name
                      Value: project-rtb-public
        DefaultPublicRoute:
            Type: AWS::EC2::Route
            DependsOn: InternetGatewayAttachment
            Properties:
                RouteTableId: !Ref PublicRouteTable
                DestinationCidrBlock: 0.0.0.0/0
                GatewayId: !Ref InternetGateway
        PublicSubnet1RouteTableAssociation:
            Type: AWS::EC2::SubnetRouteTableAssociation
            Properties:
                RouteTableId: !Ref PublicRouteTable
                SubnetId: !Ref PublicSubnet1
        PublicSubnet2RouteTableAssociation:
            Type: AWS::EC2::SubnetRouteTableAssociation
            Properties:
                RouteTableId: !Ref PublicRouteTable
                SubnetId: !Ref PublicSubnet2
        PublicSubnet3RouteTableAssociation:
            Type: AWS::EC2::SubnetRouteTableAssociation
            Properties:
                RouteTableId: !Ref PublicRouteTable
                SubnetId: !Ref PublicSubnet3
  • We create a NAT Gateway for the EIP and its association for the public subnet and Internet Gateway:

        NATGateway1:
            Type: AWS::EC2::NatGateway
            Properties:
                AllocationId: !GetAtt NATGateway1EIP.AllocationId
                SubnetId: !Ref PublicSubnet1
        NATGateway1EIP:
            Type: AWS::EC2::EIP
            DependsOn: InternetGatewayAttachment
            Properties:
                Domain: vpc
  • Now we create a NAT Gateway and its association for the Private Subnet:

        PrivateRouteTable1:
            Type: AWS::EC2::RouteTable
            Properties:
                VpcId: !Ref VPC
                Tags:
                    - Key: Name
                      Value: project-rtb-private1-us-east-1a
        DefaultPrivateRoute1:
            Type: AWS::EC2::Route
            Properties:
                RouteTableId: !Ref PrivateRouteTable1
                DestinationCidrBlock: 0.0.0.0/0
                NatGatewayId: !Ref NATGateway1
        PrivateSubnet1RouteTableAssociation:
            Type: AWS::EC2::SubnetRouteTableAssociation
            Properties:
                RouteTableId: !Ref PrivateRouteTable1
                SubnetId: !Ref PrivateSubnet1

    (Repeat similar blocks for PrivateRouteTable2 and PrivateRouteTable3)

  • We will be creating the ECS and EFS security group and associate it with the VPC that we have just created. We will be opening port 8080 on the ECS security group for Jenkins and 2049 for the ECS Security group for the NFS file system of EFS, allowing ALL traffic in outbound:

        ECSSecurityGroup:
            Type: AWS::EC2::SecurityGroup
            Properties:
                GroupDescription: "ECS Security Group"
                VpcId: !Ref VPC
                SecurityGroupIngress:
                    - IpProtocol: tcp
                      FromPort: 8080
                      ToPort: 8080
                      CidrIp: 0.0.0.0/0
                SecurityGroupEgress:
                    - IpProtocol: "-1"
                      CidrIp: 0.0.0.0/0
        EFSSecurityGroup:
            Type: AWS::EC2::SecurityGroup
            Properties:
                GroupDescription: "EFS Security Group"
                VpcId: !Ref VPC
                SecurityGroupIngress:
                    - IpProtocol: tcp
                      FromPort: 2049
                      ToPort: 2049
                      SourceSecurityGroupId: !Ref ECSSecurityGroup
                SecurityGroupEgress:
                    - IpProtocol: "-1"
                      CidrIp: 0.0.0.0/0
  • We will be creating IAM policies and role for the ecs to execute and efs mount

  # Policy for ECS Task
  ECSTaskPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ecstaskpolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - ecr:GetAuthorizationToken
              - ecr:BatchCheckLayerAvailability
              - ecr:GetDownloadUrlForLayer
              - ecr:BatchGetImage
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: "*"
      Roles:
        - !Ref ECSEFSmountTaskRole

  # Policy for EFS Mount
  EFSMountPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: efsmountpolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowDescribe
            Effect: Allow
            Action:
              - elasticfilesystem:DescribeAccessPoints
              - elasticfilesystem:DescribeFileSystems
              - elasticfilesystem:DescribeMountTargets
              - ec2:DescribeAvailabilityZones
            Resource: "*"
          - Sid: AllowCreateAccessPoint
            Effect: Allow
            Action:
              - elasticfilesystem:CreateAccessPoint
            Resource: "*"
            Condition:
              Null:
                aws:RequestTag/efs.csi.aws.com/cluster: false
              ForAllValues:StringEquals:
                aws:TagKeys: efs.csi.aws.com/cluster
          - Sid: AllowTagNewAccessPoints
            Effect: Allow
            Action:
              - elasticfilesystem:TagResource
            Resource: "*"
            Condition:
              StringEquals:
                elasticfilesystem:CreateAction: CreateAccessPoint
              Null:
                aws:RequestTag/efs.csi.aws.com/cluster: false
              ForAllValues:StringEquals:
                aws:TagKeys: efs.csi.aws.com/cluster
          - Sid: AllowDeleteAccessPoint
            Effect: Allow
            Action: elasticfilesystem:DeleteAccessPoint
            Resource: "*"
            Condition:
              Null:
                aws:ResourceTag/efs.csi.aws.com/cluster: false
      Roles:
        - !Ref ECSEFSmountTaskRole

  # IAM Role for ECS Tasks
  ECSEFSmountTaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ECSEFSmountTaskRole
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ECSTaskPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*" 
  • We will be creating the EFS file system:

        EFSSystem:
            Type: AWS::EFS::FileSystem
            Properties:
                Encrypted: true
                FileSystemTags:
                    - Key: Name
                      Value: JenkinsEFS
  • We need to mount the target for the EFS. For that, we will be using the public subnets 1, 2, 3 that we have just created:

        JenkinsHomeVolume1:
            Type: AWS::EFS::MountTarget
            Properties:
                FileSystemId: !Ref EFSSystem
                SubnetId: !Ref PublicSubnet1
                SecurityGroups:
                    - !Ref EFSSecurityGroup
        JenkinsHomeVolume2:
            Type: AWS::EFS::MountTarget
            Properties:
                FileSystemId: !Ref EFSSystem
                SubnetId: !Ref PublicSubnet2
                SecurityGroups:
                    - !Ref EFSSecurityGroup
        JenkinsHomeVolume3:
            Type: AWS::EFS::MountTarget
            Properties:
                FileSystemId: !Ref EFSSystem
                SubnetId: !Ref PublicSubnet3
                SecurityGroups:
                    - !Ref EFSSecurityGroup
  • We create an ECS cluster for our application:

        ECSCluster:
            Type: AWS::ECS::Cluster
            Properties:
                ClusterName: JenkinsCluster
                CapacityProviders:
                    - FARGATE
                    - FARGATE_SPOT
                DefaultCapacityProviderStrategy:
                    - CapacityProvider: FARGATE
                      Weight: 1
                    - CapacityProvider: FARGATE_SPOT
                      Weight: 1
                Configuration:
                    ExecuteCommandConfiguration:
                        Logging: DEFAULT
  • Create a Log group to fetch the log stream of EFS:

        ECSLogGroup:
            Type: AWS::Logs::LogGroup
            Properties:
                LogGroupName: !Sub "/ecs/test-${AWS::StackName}"
                RetentionInDays: 7
  • Now we are creating the ECS task definition. Comment the CpuArchitecture if you are using intel or amd chip (64-bit)

    ECSTaskDefinition:
     Type: AWS::ECS::TaskDefinition
     Properties:
       ExecutionRoleArn: !Ref ECSEFSmountTaskRole
       TaskRoleArn: !Ref ECSEFSmountTaskRole
       NetworkMode: awsvpc
       RequiresCompatibilities:
         - FARGATE
       RuntimePlatform:
         OperatingSystemFamily: LINUX
         CpuArchitecture: ARM64
       Family: my-jenkins-task-00
       Cpu: "1024"
       Memory: "2048"
       ContainerDefinitions:
         - Name: jenkins
           Image: !Ref ImageURL
           Cpu: 1024
           Memory: 2048
           MemoryReservation: 1024
           Essential: true
           PortMappings:
             - ContainerPort: 8080
               Protocol: tcp
           LinuxParameters:
             InitProcessEnabled: true
           MountPoints:
             - SourceVolume: efs-volume
               ContainerPath: /root/.jenkins
           LogConfiguration:
             LogDriver: awslogs
             Options:
               mode: non-blocking
               max-buffer-size: 25m
               awslogs-group: !Ref ECSLogGroup
               awslogs-region: us-east-1
               awslogs-create-group: "true"
               awslogs-stream-prefix: efs-task
       Volumes:
         - Name: efs-volume
           EFSVolumeConfiguration:
             FilesystemId: !Ref EFSSystem
             RootDirectory: /
             TransitEncryption: ENABLED
    
  • Create a ecs service

  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster  
      TaskDefinition: !Ref ECSTaskDefinition
      LaunchType: FARGATE
      ServiceName: ebs
      SchedulingStrategy: REPLICA
      DesiredCount: 1
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups: 
            - !Ref ECSSecurityGroup
          Subnets:
            - !Ref PublicSubnet1
            - !Ref PublicSubnet2
            - !Ref PublicSubnet3
      PlatformVersion: LATEST
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
        DeploymentCircuitBreaker:
          Enable: true
          Rollback: true
      DeploymentController:
        Type: ECS
      Tags: []
      EnableECSManagedTags: true

Create the bash script and update the Repository Name, aws region and stack name if you desire and required

#!/bin/bash

# update the stack name
STACK_NAME="jenkins-efs-ecs"
# update to your desired aws region
AWS_REGION="us-east-1"

# Set or update the repository name
REPOSITORY_NAME="jenkins"

# Set the image tag
IMAGE_TAG="latest"

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output --region $AWS_REGION)
# Create the ECR repository
aws ecr describe-repositories --repository-names "${REPOSITORY_NAME}" --region $AWS_REGION > /dev/null 2>&1
if [ $? -ne 0 ]
then
    aws ecr create-repository --repository-name "${REPOSITORY_NAME}" --region $AWS_REGION > /dev/null
fi


# Build the Docker image
docker build -t $REPOSITORY_NAME:$IMAGE_TAG .

# Get the ECR login command
LOGIN_COMMAND=$(aws ecr get-login-password | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com)
# Push the image to ECR
docker tag $REPOSITORY_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_NAME:$IMAGE_TAG
docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_NAME:$IMAGE_TAG

# Export the image URI as an environment variable
IMAGE_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$REPOSITORY_NAME:$IMAGE_TAG

aws cloudformation create-stack \
  --stack-name $STACK_NAME \
  --template-body file://main.yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides ImageURL=$IMAGE_URI
  • Go to the AWS Console Dashboard navigate to Cloudformation and click on jenkins-ecs-efs see the creation process after stack creation is complete go to ecs click on service and click on task access jenkins with the public ip:8080

About

Automate CI/CD pipelines using Jenkins, AWS EFS, CloudFormation, and Terraform. This project streamlines deployment and infrastructure management for efficient development workflows.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published