Building an automated deployment pipeline is crucial when working on large, scalable applications, it absolutely ensures streamlined automated processes when deploying codes.
In this tutorial guide, we will learn how to build and test a DevOps Automated Deployment Pipeline using Flask, Aptible, CircleCI, and Prometheus for an efficient deployment pipeline.
To efficiently build an automated deployment pipeline using Flask, Aptible, CircleCI and Prometheus, the following prerequisites are needed:
Open your terminal to create a new project folder for your application and also a .venv
folder within:
> mkdir flask-app
> cd flask-app
> python3 -m venv .venv
Activate your flask environment using this command below:
> .venv\Scripts\activate
After running that command your terminal changes to show the name of the activated environment.
Activating a virtual environment in most cases when working on python projects or frameworks, is important for isolating your project's dependencies and ensuring that the environment runs in a consistent pattern.
In that activated environment install flask using the following command:
pip install flask
Now our flask is installed in our python environment.
Note: This tutorial assumes that you already have pip installed on your local machine.
In that activated environment, create an app directory.
mkdir app
The app
directory will serve as the main directory for your Flask application.
cd app
Now we have navigated to our app directory, we need to create this files below:
__init__.py
file in your app directory.automate.py
file in your app directory.
Now, in the __init__.py
file follow the code instructions below:
1. Import Flask:
from flask import Flask
This line imports the Flask
class from the Flask module.
2. Create a Flask App Instance:
app = Flask(__name__)
Here in this code the instance of the flask class is created.
3. Define a Route:
@app.route('/')
def default_route():
return 'Automated Deployment'
This code defines a route for the root URL /
. when you load your browser the default URL is what is been created.
4. Run the Application:
if __name__ == '__main__':
app.run(debug=True)
This conditional statement automatically checks whether the script is being executed directly.
5. Entire Code:
# app/__init__.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def default_route():
return 'Automated Deployment'
if __name__ == '__main__':
app.run(debug=True)
Now, in the automate.py
file copy and paste the code below:
# Simple Automation: Addition Function
# Define a function named 'add'
def add(number_One, number_Two):
# Calculate the sum of the two numbers
result = number_One + number_Two
# Return the result
return result
result = add(3, 3)
print("The sum is:", result)
The automate.py
code explains a simple arithmetic function that add two different numbers.
Now that you have successfully create automate.py
function follow the instructions below to import it into the __init__.py
file:
import the automate.py
function:
from .automate import add
This import the automate.py
function into the __init__.py
file.
Define the route:
@app.route('/addition')
def addition():
# Call the add function
sum_result = add(5, 3)
return f'The sum of the Automated Deployment is: {sum_result}'
This code defines a route for the automate function /addition
so that it can be accessible on the browser.
Entire code:
# app/__init__.py
from flask import Flask
from .automate import add
app = Flask(__name__)
@app.route('/addition')
def addition():
# Call the add function
sum_result = add(5, 3)
return f'The sum of the Automated Deployment is: {sum_result}'
@app.route('/')
def default_route():
return 'Automated Deployment'
if __name__ == '__main__':
app.run(debug=True)
Now, create a run.py in the root directory of our projects and paste this code below:
# run.py
from app import app
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
This run.py
file serves as the entry point for running your Flask application. It imports the app instance from the app package and, when executed directly, launches the Flask development server with the specified host port.
Still on the activated environment, run this command on your terminal to start the server:
python run.py
Our app is now running locally..
Copy and paste that url - http://127.0.0.1:5000 on your browser:
Check your browser and see what’s the output..
Still on the activated environment install pytest using the following command:
pip install -U pytest
Now our pytest is installed in our python environment.
In that activated environment, create a tests directory.
mkdir tests
The tests
directory will serve as our tests for our entire application.
cd tests
__init__.py
file in your app directory.test_app.py
file in your app directory.
Now, in the __init__.py
paste this highlighted code:
# tests/__init__.py
# You can put initialization code here if needed.
# This file can also define what should be imported when the package is imported.
The __init__.py
file is used to indicate that the directory should be considered a Python package.
Now, in the test_app.py
follow the code instructions below:
1. Import Flask App Instance:
from run import app
This line imports the Flask application instance app
from the run
module.
2. Import automate
Module:
from app import automate
This line imports the automate
module from the app
package.
3. Define a Test Function:
def test_add():
pass
This code defines a test function named test_add
. The pass
statement is a placeholder, indicating that this test doesn't have any actual testing logic yet.
4. Entire Code:
from run import app
from app import automate
def test_add():
pass
5. Run The Tests:
pytest test_app.py
Now our test ran successfully..
Still on the activated environment install prometheus using the following command:
pip install prometheus-flask-exporter
Prometheus is now successfully installed on our python environment.
Integrate Prometheus Into Our Flask Application:
To integrate prometheus into our flask application, we need to update our Flask application (__init__.py
) to include Prometheus metrics:
# app/__init__.py
from flask import Flask
from prometheus_flask_exporter import PrometheusMetrics
from .automate import add
app = Flask(__name__)
metrics = PrometheusMetrics(app)
@app.route('/addition')
def addition():
# Call the add function
sum_result = add(5, 3)
return f'The sum of the Automated Deployment is: {sum_result}'
@app.route('/')
def default_route():
return 'Automated Deployment'
if __name__ == '__main__':
app.run(debug=True)
Test the metrics Analysis:
To see the metrics navigate to this URL /metrics
you will see a response like this on the browser as a metrics:
# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 232.0
python_gc_objects_collected_total{generation="1"} 71.0
python_gc_objects_collected_total{generation="2"} 0.0
# HELP python_gc_objects_uncollectable_total Uncollectable objects found during GC
# TYPE python_gc_objects_uncollectable_total counter
python_gc_objects_uncollectable_total{generation="0"} 0.0
python_gc_objects_uncollectable_total{generation="1"} 0.0
python_gc_objects_uncollectable_total{generation="2"} 0.0
# HELP python_gc_collections_total Number of times this generation was collected
# TYPE python_gc_collections_total counter
python_gc_collections_total{generation="0"} 68.0
python_gc_collections_total{generation="1"} 6.0
python_gc_collections_total{generation="2"} 0.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="12",patchlevel="0",version="3.12.0"} 1.0
# HELP flask_exporter_info Information about the Prometheus Flask exporter
# TYPE flask_exporter_info gauge
flask_exporter_info{version="0.23.0"} 1.0
# HELP flask_http_request_duration_seconds Flask HTTP request duration in seconds
# TYPE flask_http_request_duration_seconds histogram
flask_http_request_duration_seconds_bucket{le="0.005",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.01",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.025",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.05",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.075",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.1",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.25",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.5",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="0.75",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="1.0",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="2.5",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="5.0",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="7.5",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="10.0",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_bucket{le="+Inf",method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_count{method="GET",path="/",status="200"} 1.0
flask_http_request_duration_seconds_sum{method="GET",path="/",status="200"} 0.00026679993607103825
flask_http_request_duration_seconds_bucket{le="0.005",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.01",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.025",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.05",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.075",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.1",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.25",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.5",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="0.75",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="1.0",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="2.5",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="5.0",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="7.5",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="10.0",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_bucket{le="+Inf",method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_count{method="GET",path="/favicon.ico",status="404"} 1.0
flask_http_request_duration_seconds_sum{method="GET",path="/favicon.ico",status="404"} 0.0006099999882280827
# HELP flask_http_request_duration_seconds_created Flask HTTP request duration in seconds
# TYPE flask_http_request_duration_seconds_created gauge
flask_http_request_duration_seconds_created{method="GET",path="/",status="200"} 1.706334968886746e+09
flask_http_request_duration_seconds_created{method="GET",path="/favicon.ico",status="404"} 1.7063349712633328e+09
# HELP flask_http_request_total Total number of HTTP requests
# TYPE flask_http_request_total counter
flask_http_request_total{method="GET",status="200"} 1.0
flask_http_request_total{method="GET",status="404"} 1.0
# HELP flask_http_request_created Total number of HTTP requests
# TYPE flask_http_request_created gauge
flask_http_request_created{method="GET",status="200"} 1.706334968886746e+09
flask_http_request_created{method="GET",status="404"} 1.7063349712633328e+09
# HELP flask_http_request_exceptions_total Total number of HTTP requests which resulted in an exception
# TYPE flask_http_request_exceptions_total counter
To set up Aptible, sign up for an account and create a new environment for your flask application.
Note: In this tutorial, am not running Docker directly from my local machine, it will be configured directly with CircleCI CI/CD(continuous integration and continuous delivery). Ensure you follow every steps below:
Dockerfile
in the root directory of your Flask application.# Use the official Alpine image with Python 3.8
FROM python:3.8-alpine
# Set the working directory to /app
WORKDIR /app
# Install dependencies
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
pip3 install --upgrade pip && \
pip3 install Flask prometheus-flask-exporter
# Copy the current directory contents into the container at /app
COPY . /app
# Expose port 5000
EXPOSE 5000
# Define the command to run your application
CMD ["python3", "run.py"]
Create a circleci directory for your Flask app:
.circleci
folder in your root directory and.mkdir .circleci
This .circleci
directory will serve as a file that will configure and interact CircleCI into our application.
cd .circleci
Now we have navigated to our .circleci directory, we need to create this file below:
config.yml
file in your .circleci directory.
Now, in the config.yml
file copy and paste this code snippet below:
version: 2.1
jobs:
build:
docker:
- image: docker:19.03.12
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
- run:
name: Build Docker Image
command: |
docker build -t flask-app:latest .
docker images
workflows:
version: 2
build_and_deploy:
jobs:
- build
Initialize your Git repository for version control and set up branches for development, testing, and production.
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin githubprojectUrl
git push origin main
Note: This tutorial assumes that you know how to efficiently work with github and you have already setup a github account.
Set Up Account For CircleCI:
Setup a free account on CircleCI and navigate to the projects in the CircleCI dashboard and create a new project.
Choose a suitable deploy container for your code:
Now, when you click on the create project this page automatically appears to choose provider for storing and managing our code. Click on Github to continue. We go ahead and choose Github, which is what we’ll be using through out the tutorial.
To connect with Github using CircleCI follow this steps below:
To generate an SSH key, open your terminal and use this command below:
ssh-keygen -t ed25519 -f ~/.ssh/project_key -C "[email protected]"
This command automatically generate two keys- private and a public key.
To easily navigate to the generated keys use the type
command together with the path where the SSH keys are been saved. Copy and paste this command below to see the generated public key:
type "C:\Users\EMMANUEL UMEH\.ssh\project_key.pub"
After you have copy that key go to your Github, navigate to the github project we pushed our code to < Enter Settings < navigate to Deploy keys:
To easily navigate to the generated keys for private key use the type
command together with the path where the SSH keys are been saved.
type "C:\Users\EMMANUEL UMEH\.ssh\project_key"
Copy and paste the Key and paste in the CircleCI form box that request for private key.
To not easily get confused, please just go to your local hard drive and locate where the .shh folder is been stored. Open with a code editor either vscode or any code editor and copy the keys. Mostly private keys when you open the code editor are mostly save project_key
while public is saved as project_key.pub
.
Click on the Repository and finally click on the Create Project to start deploying to CircleCI.
Note: Do not share any of this keys especially your private key this is for security reasons.
After it is done it automatically builds the repository and our project is now on CircleCI. Here is the response it gives when you click on the - Build Docker Image:
Sending build context to Docker daemon 40.45kB
Step 1/6 : FROM python:3.8-alpine
3.8-alpine: Pulling from library/python
661ff4d9561e: Pull complete
44cda88cd45d: Pull complete
588652b0a3bf: Pull complete
a01b0f6dede8: Pull complete
06be91cd02e1: Pull complete
Digest: sha256:aeb77f60b4b197c265fc02b305753343da6155b065faa2e60671be83fc830d46
Status: Downloaded newer image for python:3.8-alpine
---> 654f089483df
Step 2/6 : WORKDIR /app
---> Running in 2a1331abce5a
Removing intermediate container 2a1331abce5a
---> 8230e4cf500a
Step 3/6 : RUN apk add --no-cache python3 && python3 -m ensurepip && pip3 install --upgrade pip && pip3 install Flask prometheus-flask-exporter
---> Running in 37570b975c16
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
(1/7) Installing libgcc (13.2.1_git20231014-r0)
(2/7) Installing libstdc++ (13.2.1_git20231014-r0)
(3/7) Installing mpdecimal (2.5.1-r2)
(4/7) Installing python3 (3.11.6-r1)
(5/7) Installing python3-pycache-pyc0 (3.11.6-r1)
(6/7) Installing pyc (3.11.6-r1)
(7/7) Installing python3-pyc (3.11.6-r1)
Executing busybox-1.36.1-r15.trigger
OK: 57 MiB in 45 packages
Looking in links: /tmp/tmp_6yka1up
Requirement already satisfied: setuptools in /usr/local/lib/python3.8/site-packages (57.5.0)
Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
Collecting pip
Downloading pip-23.3.2-py3-none-any.whl (2.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 68.8 MB/s eta 0:00:00
Installing collected packages: pip
Attempting uninstall: pip
Found existing installation: pip 23.0.1
Uninstalling pip-23.0.1:
Successfully uninstalled pip-23.0.1
Successfully installed pip-23.3.2
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Collecting Flask
Downloading flask-3.0.1-py3-none-any.whl.metadata (3.6 kB)
Collecting prometheus-flask-exporter
Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl.metadata (19 kB)
Collecting Werkzeug>=3.0.0 (from Flask)
Downloading werkzeug-3.0.1-py3-none-any.whl.metadata (4.1 kB)
Collecting Jinja2>=3.1.2 (from Flask)
Downloading Jinja2-3.1.3-py3-none-any.whl.metadata (3.3 kB)
Collecting itsdangerous>=2.1.2 (from Flask)
Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting click>=8.1.3 (from Flask)
Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
Collecting blinker>=1.6.2 (from Flask)
Downloading blinker-1.7.0-py3-none-any.whl.metadata (1.9 kB)
Collecting importlib-metadata>=3.6.0 (from Flask)
Downloading importlib_metadata-7.0.1-py3-none-any.whl.metadata (4.9 kB)
Collecting prometheus-client (from prometheus-flask-exporter)
Downloading prometheus_client-0.19.0-py3-none-any.whl.metadata (1.8 kB)
Collecting zipp>=0.5 (from importlib-metadata>=3.6.0->Flask)
Downloading zipp-3.17.0-py3-none-any.whl.metadata (3.7 kB)
Collecting MarkupSafe>=2.0 (from Jinja2>=3.1.2->Flask)
Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl.metadata (3.0 kB)
Downloading flask-3.0.1-py3-none-any.whl (101 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.2/101.2 kB 9.0 MB/s eta 0:00:00
Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl (18 kB)
Downloading blinker-1.7.0-py3-none-any.whl (13 kB)
Downloading click-8.1.7-py3-none-any.whl (97 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 8.9 MB/s eta 0:00:00
Downloading importlib_metadata-7.0.1-py3-none-any.whl (23 kB)
Downloading Jinja2-3.1.3-py3-none-any.whl (133 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.2/133.2 kB 11.6 MB/s eta 0:00:00
Downloading werkzeug-3.0.1-py3-none-any.whl (226 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 226.7/226.7 kB 18.0 MB/s eta 0:00:00
Downloading prometheus_client-0.19.0-py3-none-any.whl (54 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.2/54.2 kB 5.5 MB/s eta 0:00:00
Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl (29 kB)
Downloading zipp-3.17.0-py3-none-any.whl (7.4 kB)
Installing collected packages: zipp, prometheus-client, MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, importlib-metadata, Flask, prometheus-flask-exporter
Successfully installed Flask-3.0.1 Jinja2-3.1.3 MarkupSafe-2.1.4 Werkzeug-3.0.1 blinker-1.7.0 click-8.1.7 importlib-metadata-7.0.1 itsdangerous-2.1.2 prometheus-client-0.19.0 prometheus-flask-exporter-0.23.0 zipp-3.17.0
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
Removing intermediate container 37570b975c16
---> 1e7f53ea0501
Step 4/6 : COPY . /app
---> c4e65ff9f064
Step 5/6 : EXPOSE 5000
---> Running in 8a0e7bf09b92
Removing intermediate container 8a0e7bf09b92
---> 21cbafd1e043
Step 6/6 : CMD ["python3", "run.py"]
---> Running in 88ea77be74aa
Removing intermediate container 88ea77be74aa
---> 93f1e8a979f1
Successfully built 93f1e8a979f1
Successfully tagged flask-app:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-app latest 93f1e8a979f1 Less than a second ago 112MB
python 3.8-alpine 654f089483df 5 weeks ago 49.7MB
public.ecr.aws/eks-distro/kubernetes/pause 3.6 3c69a9ca2c95 3 months ago 6.62MB
docker 19.03.12 81f5749c9058 3 years ago 211MB
To automatically configure Aptible with CircleCI follow these steps below:
APTIBLE_APP
and in the input value box put the name of your Aptible name.
Also add another environment variable named- APTIBLE_ENVIRONMENT and in the input value box put the name of your Aptible environment.
Now all these are successfully setted up, let update our circleci config file to deploy to aptible.
On the config.yml
file update the code below:
version: 2.1
jobs:
build:
docker:
- image: docker:19.03.12
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
- run:
name: Build Docker Image
command: |
docker build -t flask-app:latest .
docker images
deploy:
docker:
- image: docker:19.03.12
steps:
- checkout
- setup_remote_docker:
version: 19.03.12
- run:
name: Install Git and OpenSSH
command: |
apk add --no-cache git openssh-client
- run:
name: Configure SSH for Aptible
command: |
ssh-keyscan beta.aptible.com >> ~/.ssh/known_hosts
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- run:
name: Add Aptible Git Remote
command: |
git remote add aptible [email protected]:$APTIBLE_ENVIRONMENT/$APTIBLE_APP.git
- run:
name: Push to Aptible
command: |
git push aptible $CIRCLE_SHA1:refs/heads/main
workflows:
version: 2
build_and_deploy:
jobs:
- build
- deploy:
requires:
- build
This code will automatically build and deploy to aptible.
Check your aptible deployment on the dashboard and see that it is connected to the circleci.
This is response that the aptible logs when it is deployed successfully:
17:06:09 INFO: Starting App deploy operation with ID: 63423982
17:06:19 INFO: Deploying with git commit: 1f17ec8d8db188e9a71e2389e1f7bde6c71277fb
17:06:26 INFO: Writing .aptible.env file...
17:06:26 INFO: Building app image from Dockerfile...
17:06:26 INFO: Step 1/6 : FROM python:3.8-alpine
17:06:26 INFO:
17:06:27 INFO: Pulling from library/python
17:06:27 INFO: Digest: sha256:aeb77f60b4b197c265fc02b305753343da6155b065faa2e60671be83fc830d46
17:06:27 INFO: Status: Image is up to date for python:3.8-alpine
17:06:27 INFO: ---> 654f089483df
17:06:27 INFO: Step 2/6 : WORKDIR /app
17:06:27 INFO:
17:06:27 INFO: ---> Using cache
17:06:27 INFO: ---> 5abee9094d97
17:06:27 INFO: Step 3/6 : RUN apk add --no-cache python3 && python3 -m ensurepip && pip3 install --upgrade pip && pip3 install Flask prometheus-flask-exporter
17:06:27 INFO:
17:06:27 INFO: ---> Running in 301e637d03a9
17:06:27 INFO: fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
17:06:28 INFO: fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
17:06:28 INFO: (1/7) Installing libgcc (13.2.1_git20231014-r0)
17:06:28 INFO: (2/7) Installing libstdc++ (13.2.1_git20231014-r0)
17:06:28 INFO: (3/7) Installing mpdecimal (2.5.1-r2)
17:06:28 INFO: (4/7) Installing python3 (3.11.6-r1)
17:06:28 INFO: (5/7) Installing python3-pycache-pyc0 (3.11.6-r1)
17:06:28 INFO: (6/7) Installing pyc (3.11.6-r1)
17:06:28 INFO: (7/7) Installing python3-pyc (3.11.6-r1)
17:06:28 INFO: Executing busybox-1.36.1-r15.trigger
17:06:28 INFO: OK: 57 MiB in 45 packages
17:06:31 INFO: Looking in links: /tmp/tmpj9vuta0p
17:06:31 INFO: Requirement already satisfied: setuptools in /usr/local/lib/python3.8/site-packages (57.5.0)
17:06:31 INFO: Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
17:06:31 INFO: [91mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
[0m
17:06:32 INFO: Requirement already satisfied: pip in /usr/local/lib/python3.8/site-packages (23.0.1)
17:06:32 INFO: Collecting pip
17:06:32 INFO: Downloading pip-23.3.2-py3-none-any.whl (2.1 MB)
17:06:32 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 46.5 MB/s eta 0:00:00
17:06:32 INFO:
17:06:32 INFO: Installing collected packages: pip
17:06:32 INFO: Attempting uninstall: pip
17:06:32 INFO: Found existing installation: pip 23.0.1
17:06:32 INFO: Uninstalling pip-23.0.1:
17:06:33 INFO: Successfully uninstalled pip-23.0.1
17:06:34 INFO: Successfully installed pip-23.3.2
17:06:34 INFO: [91mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
[0m
17:06:35 INFO: Collecting Flask
17:06:35 INFO: Downloading flask-3.0.1-py3-none-any.whl.metadata (3.6 kB)
17:06:35 INFO: Collecting prometheus-flask-exporter
17:06:35 INFO: Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl.metadata (19 kB)
17:06:35 INFO: Collecting Werkzeug>=3.0.0 (from Flask)
17:06:35 INFO: Downloading werkzeug-3.0.1-py3-none-any.whl.metadata (4.1 kB)
17:06:35 INFO: Collecting Jinja2>=3.1.2 (from Flask)
17:06:35 INFO: Downloading Jinja2-3.1.3-py3-none-any.whl.metadata (3.3 kB)
17:06:35 INFO: Collecting itsdangerous>=2.1.2 (from Flask)
17:06:35 INFO: Downloading itsdangerous-2.1.2-py3-none-any.whl (15 kB)
17:06:35 INFO: Collecting click>=8.1.3 (from Flask)
17:06:35 INFO: Downloading click-8.1.7-py3-none-any.whl.metadata (3.0 kB)
17:06:35 INFO: Collecting blinker>=1.6.2 (from Flask)
17:06:35 INFO: Downloading blinker-1.7.0-py3-none-any.whl.metadata (1.9 kB)
17:06:35 INFO: Collecting importlib-metadata>=3.6.0 (from Flask)
17:06:35 INFO: Downloading importlib_metadata-7.0.1-py3-none-any.whl.metadata (4.9 kB)
17:06:35 INFO: Collecting prometheus-client (from prometheus-flask-exporter)
17:06:35 INFO: Downloading prometheus_client-0.19.0-py3-none-any.whl.metadata (1.8 kB)
17:06:35 INFO: Collecting zipp>=0.5 (from importlib-metadata>=3.6.0->Flask)
17:06:35 INFO: Downloading zipp-3.17.0-py3-none-any.whl.metadata (3.7 kB)
17:06:35 INFO: Collecting MarkupSafe>=2.0 (from Jinja2>=3.1.2->Flask)
17:06:35 INFO: Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl.metadata (3.0 kB)
17:06:35 INFO: Downloading flask-3.0.1-py3-none-any.whl (101 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.2/101.2 kB 18.3 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading prometheus_flask_exporter-0.23.0-py3-none-any.whl (18 kB)
17:06:35 INFO: Downloading blinker-1.7.0-py3-none-any.whl (13 kB)
17:06:35 INFO: Downloading click-8.1.7-py3-none-any.whl (97 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 97.9/97.9 kB 14.8 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading importlib_metadata-7.0.1-py3-none-any.whl (23 kB)
17:06:35 INFO: Downloading Jinja2-3.1.3-py3-none-any.whl (133 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.2/133.2 kB 24.7 MB/s eta 0:00:00
17:06:35 INFO: Downloading werkzeug-3.0.1-py3-none-any.whl (226 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 226.7/226.7 kB 33.1 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading prometheus_client-0.19.0-py3-none-any.whl (54 kB)
17:06:35 INFO: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 54.2/54.2 kB 8.8 MB/s eta 0:00:00
17:06:35 INFO:
17:06:35 INFO: Downloading MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl (29 kB)
17:06:35 INFO: Downloading zipp-3.17.0-py3-none-any.whl (7.4 kB)
17:06:35 INFO: Installing collected packages: zipp, prometheus-client, MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, importlib-metadata, Flask, prometheus-flask-exporter
17:06:36 INFO: Successfully installed Flask-3.0.1 Jinja2-3.1.3 MarkupSafe-2.1.4 Werkzeug-3.0.1 blinker-1.7.0 click-8.1.7 importlib-metadata-7.0.1 itsdangerous-2.1.2 prometheus-client-0.19.0 prometheus-flask-exporter-0.23.0 zipp-3.17.0
17:06:36 INFO: [91mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
[0m
17:06:37 INFO: Removing intermediate container 301e637d03a9
17:06:37 INFO: ---> b7da707a0f81
17:06:37 INFO: Step 4/6 : COPY . /app
17:06:37 INFO:
17:06:38 INFO: ---> 49d87db774f2
17:06:38 INFO: Step 5/6 : EXPOSE 5000
17:06:38 INFO:
17:06:38 INFO: ---> Running in 5d70b7d7f835
17:06:38 INFO: Removing intermediate container 5d70b7d7f835
17:06:38 INFO: ---> 4773ef4d4e6e
17:06:38 INFO: Step 6/6 : CMD ["python3", "run.py"]
17:06:38 INFO:
17:06:38 INFO: ---> Running in 17ab74c4f3ba
17:06:38 INFO: Removing intermediate container 17ab74c4f3ba
17:06:38 INFO: ---> 9a93b7aeb34a
17:06:38 INFO: Successfully built 9a93b7aeb34a
17:06:38 INFO: No Procfile found in git directory or /.aptible/Procfile found in Docker image: using Docker image CMD
17:06:38 INFO: No .aptible.yml found in git directory or /.aptible/.aptible.yml found in Docker image: no before_release commands will run
17:06:38 INFO: Pushing image dualstack-v2-registry-i-0a95e4f42c277b7d6.aptible.in:46022/app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d:latest to private Docker registry...
17:06:38 INFO: The push refers to repository [dualstack-v2-registry-i-0a95e4f42c277b7d6.aptible.in:46022/app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d]
17:06:38 INFO: 2014143cee52: Pushing: 521 KB / 59.3 MB
17:06:39 INFO: e6035e0a4780: Pushed
17:06:40 INFO: 2014143cee52: Pushing: 30.6 MB / 59.3 MB
17:06:44 INFO: 2014143cee52: Pushed
17:06:45 INFO: latest: digest: sha256:70e44dc2f539188b8e05952db0283e646a17103a6d29b876f25ec7a368f053d5 size: 1995
17:06:45 INFO: Pulling from app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d
17:06:45 INFO: Digest: sha256:70e44dc2f539188b8e05952db0283e646a17103a6d29b876f25ec7a368f053d5
17:06:45 INFO: Status: Image is up to date for dualstack-v2-registry-i-0a95e4f42c277b7d6.aptible.in:46022/app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d:latest
17:06:45 INFO: Image app-67132/7edac14d-6667-4cdb-a6e8-fcd6aea9cb4d successfully pushed to registry.
17:06:47 INFO: STARTING: Register service cmd in API
17:06:47 INFO: COMPLETED (after 0.0s): Register service cmd in API
17:06:47 INFO: STARTING: Extract configuration for endpoint app-67132.on-aptible.com
17:06:47 INFO: COMPLETED (after 0.0s): Extract configuration for endpoint app-67132.on-aptible.com
17:06:47 INFO: STARTING: Import certificate for endpoint app-67132.on-aptible.com
17:06:48 INFO: COMPLETED (after 0.0s): Import certificate for endpoint app-67132.on-aptible.com
17:06:48 INFO: STARTING: Derive placement policy for service cmd
17:06:48 INFO: COMPLETED (after 0.13s): Derive placement policy for service cmd
17:06:48 INFO: STARTING: Create new release for service cmd
17:06:48 INFO: COMPLETED (after 0.26s): Create new release for service cmd
17:06:48 INFO: STARTING: Ensure ALB exists for endpoint app-67132.on-aptible.com
17:06:48 INFO: COMPLETED (after 0.05s): Ensure ALB exists for endpoint app-67132.on-aptible.com
17:06:48 INFO: STARTING: Schedule service cmd
17:07:02 INFO: COMPLETED (after 14.11s): Schedule service cmd
17:07:02 INFO: STARTING: Start app containers for service cmd
17:07:03 INFO: STARTING: Configure IP filtering for endpoint app-67132.on-aptible.com
17:07:03 INFO: COMPLETED (after 0.04s): Configure IP filtering for endpoint app-67132.on-aptible.com
17:07:03 INFO: STARTING: Configure SSL Certificate for endpoint app-67132.on-aptible.com
17:07:03 INFO: COMPLETED (after 0.05s): Configure SSL Certificate for endpoint app-67132.on-aptible.com
17:07:03 INFO: STARTING: Configure SSL Policy for endpoint app-67132.on-aptible.com
17:07:03 INFO: COMPLETED (after 0.06s): Configure SSL Policy for endpoint app-67132.on-aptible.com
17:07:03 INFO: WAITING FOR: Start app containers for service cmd
17:07:05 INFO: COMPLETED (after 2.65s): Start app containers for service cmd
17:07:05 INFO: STARTING: Wait up to 180s for containers in service cmd to respond to HTTP health checks
17:07:05 INFO: STARTING: Start proxy containers for endpoint app-67132.on-aptible.com
17:07:05 INFO: WAITING FOR: Start proxy containers for endpoint app-67132.on-aptible.com, Wait up to 180s for containers in service cmd to respond to HTTP health checks
17:07:08 INFO: COMPLETED (after 2.54s): Wait up to 180s for containers in service cmd to respond to HTTP health checks
17:07:08 INFO: WAITING FOR: Start proxy containers for endpoint app-67132.on-aptible.com
17:07:12 INFO: COMPLETED (after 6.16s): Start proxy containers for endpoint app-67132.on-aptible.com
17:07:12 INFO: STARTING: Register new http targets with endpoint app-67132.on-aptible.com
17:07:12 INFO: STARTING: Register new https targets with endpoint app-67132.on-aptible.com
17:07:12 INFO: WAITING FOR: Register new http targets with endpoint app-67132.on-aptible.com, Register new https targets with endpoint app-67132.on-aptible.com
17:07:24 INFO: COMPLETED (after 12.92s): Register new http targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: COMPLETED (after 12.74s): Register new https targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: STARTING: Deregister old http targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: STARTING: Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:25 INFO: WAITING FOR: Deregister old http targets with endpoint app-67132.on-aptible.com, Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:37 INFO: WAITING FOR: Deregister old http targets with endpoint app-67132.on-aptible.com, Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:41 INFO: COMPLETED (after 16.9s): Deregister old http targets with endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 16.78s): Deregister old https targets with endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Wait for Route 53 health to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.0s): Wait for Route 53 health to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Create ALIAS for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.56s): Create ALIAS for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Create default domain for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.32s): Create default domain for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Wait for DNS to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: COMPLETED (after 0.0s): Wait for DNS to sync for endpoint app-67132.on-aptible.com
17:07:42 INFO: STARTING: Stop old proxy containers for endpoint app-67132.on-aptible.com
17:07:42 INFO: WAITING FOR: Stop old proxy containers for endpoint app-67132.on-aptible.com
17:07:44 INFO: COMPLETED (after 2.01s): Stop old proxy containers for endpoint app-67132.on-aptible.com
17:07:44 INFO: STARTING: Stop old app containers for service cmd
17:07:44 INFO: WAITING FOR: Stop old app containers for service cmd
17:07:55 INFO: WAITING FOR: Stop old app containers for service cmd
17:07:57 INFO: COMPLETED (after 12.24s): Stop old app containers for service cmd
17:07:57 INFO: STARTING: Delete old containers for service cmd in API
17:07:57 INFO: COMPLETED (after 0.22s): Delete old containers for service cmd in API
17:07:57 INFO: STARTING: Commit proxy containers in API for endpoint app-67132.on-aptible.com
17:07:57 INFO: COMPLETED (after 0.27s): Commit proxy containers in API for endpoint app-67132.on-aptible.com
17:07:57 INFO: STARTING: Delete old proxy containers for endpoint app-67132.on-aptible.com in API
17:07:58 INFO: COMPLETED (after 0.2s): Delete old proxy containers for endpoint app-67132.on-aptible.com in API
17:07:58 INFO: STARTING: Commit app containers in API for service cmd
17:07:58 INFO: COMPLETED (after 0.27s): Commit app containers in API for service cmd
17:07:58 INFO: STARTING: Commit service cmd in API
17:07:58 INFO: COMPLETED (after 0.16s): Commit service cmd in API
17:07:58 INFO: STARTING: Cache maintenance page
17:07:59 INFO: COMPLETED (after 0.3s): Cache maintenance page
17:07:59 INFO: STARTING: Commit app in API
17:07:59 INFO: COMPLETED (after 0.18s): Commit app in API
17:08:00 INFO: App deploy successful.
Automating deployment pipeline using DevOps best practices ensures a streamlined process for a well secure and robust software application.