This is a guideline on how to setup a CI-CD pipeline using Jenkins to deploy a FastAPI application on develop/staging/production environments on AWS. The source code is stored at jenkins-aws-for-web-app
- The repo has 2 branches:
dev
andmaster
- Directory structure:
. ├── app │ └── main.py ├── jenkins_setup | ├── docker-compose.yml │ └── Dockerfile ├── Jenkinsfile ├── Dockerfile ├── docker-compose.yml ├── load-balancer.yaml ├── .flake8 ├── .gitignore ├── images ├── README.md └── requirements.txt
- The repo includes:
-
FastAPI application source code.
# In ./app/main.py from fastapi import FastAPI app = FastAPI() @app.get('/') async def root(): return {'greeting': 'Hello from root function'} @app.get('/{name}') async def hello(name: str): return {'greeting': f'Hello {name}!'}
-
Jenskinfile: located in the root directory of the git repository
./Jenkinsfile
-
Dockerfile and requirements.txt for the FastAPI application.
FROM python:3.9-slim WORKDIR /code RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl\ && apt-get autoremove -yqq --purge \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* COPY ./requirements.txt /code/requirements.txt RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt COPY ./app /code/app CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
-
jenkins_setup: Dockerfile and docker-compose.yml for running jenkins server using containers:
- Dockerfile:
FROM jenkins/jenkins:2.401.1 USER root RUN apt-get update && apt-get install -y lsb-release RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \ https://download.docker.com/linux/debian/gpg RUN echo "deb [arch=$(dpkg --print-architecture) \ signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \ https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list RUN apt-get update && apt-get install -y docker-ce-cli RUN apt-get install -y --no-install-recommends \ python3-pip python3-venv\ && apt-get autoremove -yqq --purge \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* RUN pip install virtualenv RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && unzip awscliv2.zip && ./aws/install USER jenkins RUN jenkins-plugin-cli --plugins "blueocean docker-workflow"
- docker-compose.yml:
networks: jenkins: driver: bridge services: jenkins-docker: image: docker:dind container_name: jenkins-docker privileged: true restart: always networks: jenkins: aliases: - docker environment: - DOCKER_TLS_CERTDIR=/certs volumes: - ./jenkins-docker-certs:/certs/client - ./jenkins-data:/var/jenkins_home ports: - 2376:2376 command: --storage-driver=overlay2 jenkins-blueocean: image: jenkins-blueocean:2.401.1-1 build: . container_name: jenkins-blueocean networks: - jenkins environment: - DOCKER_HOST=tcp://docker:2376 - DOCKER_CERT_PATH=/certs/client - DOCKER_TLS_VERIFY=1 volumes: - ./jenkins-docker-certs:/certs/client:ro - ./jenkins-data:/var/jenkins_home ports: - 8080:8080 - 50000:50000 restart: always
-
-
Create a task definition for each environment: Below is the example for creating a task definition for dev environment. Create task definitions for other environments similarly and names, image uri should be changed. Configuration not mentioned ought to be left default.
-
Create a cluster for each environment: Below is the example for creating a cluster for dev environment. Create clusters for other environments similarly with other names.
-
Modify Cloudformation template for elastic load balancer:
TargetGroup/VpcId
,LoadBalancer/Subnets
,LoadBalancer/SecurityGroups
should be changed to meet your requirements.AWSTemplateFormatVersion: '2010-09-09' Description: Create a public load balancer, listener and target group. Resources: TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: HealthCheckIntervalSeconds: 10 HealthCheckPath: / HealthCheckProtocol: HTTP HealthCheckTimeoutSeconds: 5 HealthyThresholdCount: 2 TargetType: ip Name: !Join [ "-", [!Ref AWS::StackName, 'tg'] ] Port: 80 Protocol: HTTP UnhealthyThresholdCount: 5 VpcId: vpc-046ad6577b5ca5cfe Listener: Type: AWS::ElasticLoadBalancingV2::Listener DependsOn: - TargetGroup - LoadBalancer Properties: DefaultActions: - Type: forward TargetGroupArn: !Ref TargetGroup LoadBalancerArn: !Ref LoadBalancer Port: 80 Protocol: HTTP LoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: IpAddressType: ipv4 Name: !Join [ "-", [!Ref AWS::StackName, 'bl'] ] Scheme: internet-facing SecurityGroups: - sg-04741693a6494256c Subnets: - subnet-0344d44130e11b79c - subnet-0de56d1e9c2d97060 - subnet-02bbfe46ba29ce20a Outputs: LoadBalancerDNS: Description: The DNS of the Elastic Load Balancer of the web app Value: !GetAtt LoadBalancer.DNSName LoadBalancerFullName: Description: The LoadBalancerFullName of the Elastic Load Balancer of the web app Value: !GetAtt LoadBalancer.LoadBalancerFullName TargetGroupArn: Description: The TargetGroupArn of the Target group Value: !GetAtt TargetGroup.TargetGroupArn
-
Modify
Jenkinsfile
in source repository: Look for all lines that contain the--network-configuration
term and modify its to meet your requirements like below:--network-configuration "awsvpcConfiguration={subnets=[{YOUR_SUBNET_ID_1},{YOUR_SUBNET_ID_2},{YOUR_SUBNET_ID_3},{...}],securityGroups=[{YOUR_SECURITY_GROUP_ID_1},{...}],assignPublicIp=ENABLED}" \
-
Take a look at
parameters
section in theJenkinsfile
and modify parameters' default values so that it meets your needs:- AWS account id: AWS_ACCOUNT_ID
- AWS default region: AWS_REGION
- Created ECR repo names: *_ECR_REPO
- Created ECS cluster names: *_CLUSTER
- Created ECS task definition names: *_TASK_DEFINITION
- Defined container names in the created task definitions: *_CONTAINER
- Container port in the created task definitions: CONTAINER_PORT
- ECS service names for deployment: *_SERVICE
- Elastic load balancer stack names which will be created during the deployment process: *_LOAD_BALANCER_STACK
-
Launch an EC2 instance: Remember to expose port 8080 for accessing Jenkins web server
-
Copy
./jenkins_setup
folder to the instance. SSH to the EC2 instance, change the working directory to it and run this command:# Inside jenkins_setup folder curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh mkdir jenkins-data jenkins-docker-certs sudo docker compose up
-
Access Jenkins web server using the instance IP with port 8080 using the initial password shown in the output of the above commands
-
Install plugins:
-
Create an admin user:
-
Assign Jenkins url:
-
Install additional plugins: Manage Jenkins -> Plugins -> Available Plugins -> Search
Pipeline: AWS Steps
and check it -> Install without restart -> Go back to the top page -
Add AWS credentials: Manage Jenkins -> Credentials -> System -> Global credentials (unrestricted) -> Add Credentials:
IMPORTANT: Replace 'duongpd7-aws-credentials' in
Jenkinsfile
of the source repository with your ID of the credentials created below -
Set up Jenkins pipeline:
-
Click Open Blue Ocean:
-
Create a new Pipeline:
-
Configure source repository for the pipeline: choose
Git
, configure your gitlab repository URL, add your credential information and clickCreate pipeline
:IMPORTANT: Assign your Gitlab Personal access token (not your Gitlab password) to the Password field. Refer to the section
Create a personal access token
at Personal access tokens -
After that, the pipeline will be triggered automatically for 2 branches:
master
anddev
. Both of them should run successfully!
Note: You need to click Proceed to continue to the production deployment process
-
-
Install Gitlab plugin: Manage Jenkins -> Plugins -> Available Plugins -> Search
Gitlab
and check it -> Install without restart -> Go back to the top page -
Acess your Gitlab repository and Click webhook button in the setting section
-
Create a webhook like the example below. Replace YOUR_JENKINS_URL with your jenkins server url.
-
Test your webhook and the success notification should appear
-
Now Jenkins will trigger the pipeline whenever there is a push to the Gitlab repository