Skip to content
Merged

. #1

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build
on:
push:
branches:
- master
- develop
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle packages
uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew build sonarqube --info
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
build
.gradle
.vscode

*.log
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# MAC
**/.DS_Store

# internal
*internal*

# k8s
python-ping-api.yaml

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
104 changes: 102 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,102 @@
# _eks-python-api
Python REST API sample for EKS
# Python sample project for EKS

[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=DevSecOpsSamples_eks-python-api&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=DevSecOpsSamples_eks-python-api) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=DevSecOpsSamples_eks-python-api&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=DevSecOpsSamples_eks-python-api)

The sample project to deploy Python REST API application, Service, HorizontalPodAutoscaler, Ingress on EKS.

- [app.py](app/app.py)
- [Dockerfile](app/Dockerfile)
- [python-ping-api-template.yaml](app/python-ping-api-template.yaml)

---

## Prerequisites

### Installation

- [Installing or updating the latest version of the AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)

### Set AWS configurations

Set AWS configurations with `aws configure` for default region and keys:

```bash
aws configure
```

```bash
AWS Access Key ID [None]: <enter-your-access-key>
AWS Secret Access Key [None]: <enter-your-secret-key>
Default region name [None]: us-east-1
Default output format [None]:
```

```bash
aws configure get default.region
us-east-1
```

---

## Create an EKS cluster and deploy AWS Load Balancer Controller

Refer to the https://github.com/DevSecOpsSamples/eks-eksctl page.

## Set environment variables

```bash
REGION=$(aws configure get default.region)
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
CLUSTER_NAME=$(kubectl config current-context | cut -d '@' -f2 | cut -d '.' -f1)
echo "REGION: ${REGION}, ACCOUNT_ID: ${ACCOUNT_ID}, CLUSTER_NAME: ${CLUSTER_NAME}"
```

## Deploy python-ping-api

Build and push to ECR:

```bash
cd ../app
docker build -t python-ping-api . --platform linux/amd64

docker tag python-ping-api:latest ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/python-ping-api:latest

aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com

docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/python-ping-api:latest
```

Create and deploy K8s Deployment, Service, HorizontalPodAutoscaler, Ingress using the [python-ping-api-template.yaml](app/python-ping-api-template.yaml) template file.

```bash

sed -e "s|<account-id>|${ACCOUNT_ID}|g" python-ping-api-template.yaml | sed -e "s|<region>|${REGION}|g" > python-ping-api.yaml
cat python-ping-api.yaml

kubectl apply -f python-ping-api.yaml
```

It may take around 5 minutes to create a load balancer, including health checking.

Confirm that Pod and ALB logs.

```bash
kubectl logs -l app=python-ping-api

kubectl describe pods

kubectl logs -f $(kubectl get po -n kube-system | egrep -o 'aws-load-balancer-controller-[A-Za-z0-9-]+') -n kube-system
```

---

## Cleanup

```bash
kubectl delete -f app/python-ping-api.yaml

```

## References

- [Application load balancing on Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html)
14 changes: 14 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.9-alpine

VOLUME ./:app/

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

COPY . /app/

WORKDIR /app

EXPOSE 8000

CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]
37 changes: 37 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from flask import Flask
from flask import request
from flask import json
from werkzeug.exceptions import HTTPException

app = Flask(__name__)

@app.route("/")
def ping_root():
return ping()

@app.route("/<string:path1>")
def ping_path1(path1):
return ping()

def ping():
return {
"host": request.host,
"url": request.url,
"method": request.method,
"message": "ping-api"
}

@app.errorhandler(HTTPException)
def handle_exception(e):
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response

if __name__ == '__main__':
app.debug = True
app.run(host='0.0.0.0', port=8000)
17 changes: 17 additions & 0 deletions app/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
set -e

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=$(aws configure get default.region)

echo "ACCOUNT_ID: $ACCOUNT_ID"
echo "REGION: $REGION"
sleep 1

docker build -t python-ping-api . --platform linux/amd64

docker tag python-ping-api:latest ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/python-ping-api:latest

aws ecr get-login-password --region ${REGION} | docker login --username AWS --password-stdin ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com

docker push ${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/python-ping-api:latest
2 changes: 2 additions & 0 deletions app/gunicorn.config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import multiprocessing
workers = multiprocessing.cpu_count() * 2 + 1
101 changes: 101 additions & 0 deletions app/python-ping-api-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-ping-api
annotations:
app: 'python-ping-api'
spec:
replicas: 2
selector:
matchLabels:
app: python-ping-api
template:
metadata:
labels:
app: python-ping-api
spec:
containers:
- name: python-ping-api
image: <account-id>.dkr.ecr.<region>.amazonaws.com/python-ping-api:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
resources:
requests:
cpu: "0.25"
memory: "256Mi"
limits:
cpu: "0.25"
memory: "256Mi"
env:
- name: env
value: "dev"
---
apiVersion: v1
kind: Service
metadata:
name: python-ping-api
annotations:
app: 'python-ping-api'
alb.ingress.kubernetes.io/healthcheck-path: "/ping"
spec:
selector:
app: python-ping-api
type: NodePort
ports:
- port: 8000
targetPort: 8000
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: "python-ping-api-ingress"
annotations:
app: 'python-ping-api'
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/load-balancer-name: python-ping-api
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=30
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=10
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}]'
# alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
# alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:<region>:<account-id>:certificate/<certificate-id>
# alb.ingress.kubernetes.io/ssl-redirect: '443'
alb.ingress.kubernetes.io/tags: env=dev
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '16'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '15'
alb.ingress.kubernetes.io/healthy-threshold-count: '2'
alb.ingress.kubernetes.io/unhealthy-threshold-count: '5'
spec:
rules:
- http:
paths:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: python-ping-api
port:
number: 8000
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: 'python-ping-api-hpa'
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: 'python-ping-api'
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
2 changes: 2 additions & 0 deletions app/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask==2.1.1
gunicorn==20.1.0
26 changes: 26 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
id 'base'
id "org.sonarqube" version "3.4.0.2513"
}

repositories {
mavenCentral()
}

sonarqube {
properties {
property "sonar.projectName", "eks-python-api"
property "sonar.projectKey", "DevSecOpsSamples_eks-python-api"
property "sonar.organization", "devsecopssamples"
// property "sonar.host.url", "http://127.0.0.1:9000"
property "sonar.host.url", "https://sonarcloud.io"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.sources", "."
property "sonar.python.version", "3.9"
property "sonar.exclusions", "**/node_modules/**, **/cdk.out/**"
property "sonar.issue.ignore.multicriteria", "e1"
property "sonar.issue.ignore.multicriteria.e1.ruleKey", "typescript:S1848"
property "sonar.issue.ignore.multicriteria.e1.resourceKey", "**/*.ts"
property "sonar.links.ci", "https://github.com/DevSecOpsSamples/eks-python-api"
}
}
Binary file added gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
5 changes: 5 additions & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading