Skip to content

Commit 55f82d2

Browse files
authored
.
.
1 parent 95231d1 commit 55f82d2

16 files changed

+668
-2
lines changed

.github/workflows/build.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Build
2+
on:
3+
push:
4+
branches:
5+
- master
6+
- develop
7+
pull_request:
8+
types: [opened, synchronize, reopened]
9+
jobs:
10+
build:
11+
name: Build
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
with:
16+
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
17+
- name: Set up JDK 11
18+
uses: actions/setup-java@v1
19+
with:
20+
java-version: 11
21+
- name: Cache SonarCloud packages
22+
uses: actions/cache@v1
23+
with:
24+
path: ~/.sonar/cache
25+
key: ${{ runner.os }}-sonar
26+
restore-keys: ${{ runner.os }}-sonar
27+
- name: Cache Gradle packages
28+
uses: actions/cache@v1
29+
with:
30+
path: ~/.gradle/caches
31+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
32+
restore-keys: ${{ runner.os }}-gradle
33+
- name: Build and analyze
34+
env:
35+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
36+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
37+
run: ./gradlew build sonarqube --info

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
build
2+
.gradle
3+
.vscode
4+
5+
*.log
6+
*.jar
7+
*.war
8+
*.nar
9+
*.ear
10+
*.zip
11+
*.tar.gz
12+
*.rar
13+
14+
# MAC
15+
**/.DS_Store
16+
17+
# internal
18+
*internal*
19+
20+
# GKE
21+
service-account.json
22+
23+
# k8s
24+
python-ping-api.yaml
25+
126
# Byte-compiled / optimized / DLL files
227
__pycache__/
328
*.py[cod]

README.md

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,126 @@
1-
# python-gradle
2-
Python, pytest, Gradle, Sonarqbue
1+
# Python sample project for GKE
2+
3+
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=DevSecOpsSamples_gke-python-api&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=DevSecOpsSamples_gke-python-api) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=DevSecOpsSamples_gke-python-api&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=DevSecOpsSamples_gke-python-api)
4+
5+
The sample project to deploy Python REST API application, Service, HorizontalPodAutoscaler, Ingress, and BackendConfig on GKE.
6+
7+
- [app.py](app/app.py)
8+
- [python-ping-api-template.yaml](app/python-ping-api-template.yaml)
9+
10+
---
11+
12+
## Prerequisites
13+
14+
### Installation
15+
16+
- [Install the gcloud CLI](https://cloud.google.com/sdk/docs/install)
17+
- [Install kubectl and configure cluster access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl)
18+
19+
### Set environment variables
20+
21+
```bash
22+
PROJECT_ID="sample-project" # replace with your project
23+
COMPUTE_ZONE="us-central1"
24+
```
25+
26+
### Set GCP project
27+
28+
```bash
29+
gcloud config set project ${PROJECT_ID}
30+
gcloud config set compute/zone ${COMPUTE_ZONE}
31+
```
32+
33+
---
34+
35+
## Create a GKE cluster
36+
37+
Create an Autopilot GKE cluster. It may take around 9 minutes.
38+
39+
```bash
40+
gcloud container clusters create-auto sample-cluster --region=${COMPUTE_ZONE}
41+
gcloud container clusters get-credentials sample-cluster
42+
```
43+
44+
---
45+
46+
## Deploy python-ping-api
47+
48+
Build and push to GCR:
49+
50+
```bash
51+
cd ../app
52+
docker build -t python-ping-api . --platform linux/amd64
53+
docker tag python-ping-api:latest gcr.io/${PROJECT_ID}/python-ping-api:latest
54+
55+
gcloud auth configure-docker
56+
docker push gcr.io/${PROJECT_ID}/python-ping-api:latest
57+
```
58+
59+
Create and deploy K8s Deployment, Service, HorizontalPodAutoscaler, Ingress, and GKE BackendConfig using the [python-ping-api-template.yaml](app/python-ping-api-template.yaml) template file.
60+
61+
```bash
62+
sed -e "s|<project-id>|${PROJECT_ID}|g" python-ping-api-template.yaml > python-ping-api.yaml
63+
cat python-ping-api.yaml
64+
65+
kubectl apply -f python-ping-api.yaml
66+
```
67+
68+
It may take around 5 minutes to create a load balancer, including health checking.
69+
70+
Confirm that pod configuration and logs after deployment:
71+
72+
```bash
73+
kubectl logs -l app=python-ping-api
74+
75+
kubectl describe pods
76+
77+
kubectl get ingress python-ping-api-ingress
78+
```
79+
80+
Confirm that response of `/ping` API.
81+
82+
```bash
83+
LB_IP_ADDRESS=$(gcloud compute forwarding-rules list | grep python-ping-api | awk '{ print $2 }')
84+
echo ${LB_IP_ADDRESS}
85+
```
86+
87+
```bash
88+
curl http://${LB_IP_ADDRESS}/ping
89+
```
90+
91+
```json
92+
{
93+
"host": "<your-ingress-endpoint-ip>",
94+
"message": "ping-api",
95+
"method": "GET",
96+
"url": "http://<your-ingress-endpoint-ip>/ping"
97+
}
98+
```
99+
100+
---
101+
102+
## Screenshots
103+
104+
- Loadbalncer
105+
106+
![loadbalancer](./screenshots/loadbalancer.png?raw=true)
107+
108+
- Loadbalncer Details
109+
110+
![loadbalancer-details](./screenshots/loadbalancer-details.png?raw=true)
111+
112+
---
113+
114+
## Cleanup
115+
116+
```bash
117+
kubectl delete -f app/python-ping-api.yaml
118+
119+
gcloud container clusters delete sample-cluster
120+
```
121+
122+
## References
123+
124+
- [Cloud SDK > Documentation > Reference > gcloud container clusters](https://cloud.google.com/sdk/gcloud/reference/container/clusters)
125+
126+
- [Google Kubernetes Engine (GKE) > Documentation > Guides > GKE Ingress for HTTP(S) Load Balancing](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress)

app/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM python:3.9-alpine
2+
3+
VOLUME ./:app/
4+
5+
COPY requirements.txt requirements.txt
6+
RUN pip install -r requirements.txt
7+
8+
COPY . /app/
9+
10+
WORKDIR /app
11+
12+
EXPOSE 8000
13+
14+
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

app/app.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from flask import Flask
2+
from flask import request
3+
from flask import json
4+
from werkzeug.exceptions import HTTPException
5+
6+
app = Flask(__name__)
7+
8+
@app.route("/")
9+
def ping_root():
10+
return ping()
11+
12+
@app.route("/<string:path1>")
13+
def ping_path1(path1):
14+
return ping()
15+
16+
def ping():
17+
return {
18+
"host": request.host,
19+
"url": request.url,
20+
"method": request.method,
21+
"message": "ping-api"
22+
}
23+
24+
@app.errorhandler(HTTPException)
25+
def handle_exception(e):
26+
response = e.get_response()
27+
response.data = json.dumps({
28+
"code": e.code,
29+
"name": e.name,
30+
"description": e.description,
31+
})
32+
response.content_type = "application/json"
33+
return response
34+
35+
if __name__ == '__main__':
36+
app.debug = True
37+
app.run(host='0.0.0.0', port=8000)

app/deploy-test.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "PROJECT_ID: ${PROJECT_ID}"
5+
6+
docker build -t python-ping-api . --platform linux/amd64
7+
docker tag python-ping-api:latest gcr.io/${PROJECT_ID}/python-ping-api:latest
8+
9+
gcloud auth configure-docker
10+
docker push gcr.io/${PROJECT_ID}/python-ping-api:latest
11+
12+
kubectl scale deployment python-ping-api --replicas=0
13+
kubectl scale deployment python-ping-api --replicas=3
14+
sleep 3
15+
kubectl get pods

app/local-test.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
set -e
3+
4+
echo "PROJECT_ID: ${PROJECT_ID}"
5+
6+
docker build -t python-ping-api . --platform linux/amd64
7+
docker tag python-ping-api:latest gcr.io/${PROJECT_ID}/python-ping-api:latest
8+
docker push gcr.io/${PROJECT_ID}/python-ping-api:latest
9+
10+
docker run -it -p 8000:8000 gcr.io/${PROJECT_ID}/python-ping-api:latest
11+
12+
curl http://127.0.0.1:8000

app/python-ping-api-template.yaml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
apiVersion: apps/v1
3+
kind: Deployment
4+
metadata:
5+
name: python-ping-api
6+
annotations:
7+
app: python-ping-api
8+
spec:
9+
replicas: 2
10+
selector:
11+
matchLabels:
12+
app: python-ping-api
13+
template:
14+
metadata:
15+
labels:
16+
app: python-ping-api
17+
spec:
18+
containers:
19+
- name: python-ping-api
20+
image: gcr.io/<project-id>/python-ping-api:latest
21+
imagePullPolicy: Always
22+
ports:
23+
- containerPort: 8000
24+
resources:
25+
requests:
26+
cpu: "0.25"
27+
memory: "256Mi"
28+
---
29+
apiVersion: v1
30+
kind: Service
31+
metadata:
32+
name: python-ping-api
33+
annotations:
34+
app: python-ping-api
35+
cloud.google.com/backend-config: '{"default": "python-ping-api-backend-config"}'
36+
spec:
37+
selector:
38+
app: python-ping-api
39+
type: ClusterIP
40+
ports:
41+
- port: 8000
42+
targetPort: 8000
43+
protocol: TCP
44+
---
45+
apiVersion: networking.k8s.io/v1
46+
kind: Ingress
47+
metadata:
48+
name: python-ping-api-ingress
49+
annotations:
50+
app: python-ping-api
51+
kubernetes.io/ingress.class: gce
52+
spec:
53+
rules:
54+
- http:
55+
paths:
56+
- path: /*
57+
pathType: ImplementationSpecific
58+
backend:
59+
service:
60+
name: python-ping-api
61+
port:
62+
number: 8000
63+
---
64+
apiVersion: cloud.google.com/v1
65+
kind: BackendConfig
66+
metadata:
67+
name: python-ping-api-backend-config
68+
spec:
69+
healthCheck:
70+
checkIntervalSec: 10
71+
timeoutSec: 10
72+
healthyThreshold: 1
73+
unhealthyThreshold: 3
74+
port: 8000
75+
type: HTTP
76+
requestPath: /ping
77+
---
78+
apiVersion: autoscaling/v2beta2
79+
kind: HorizontalPodAutoscaler
80+
metadata:
81+
name: 'python-ping-api-hpa'
82+
spec:
83+
scaleTargetRef:
84+
apiVersion: apps/v1
85+
kind: Deployment
86+
name: 'python-ping-api'
87+
minReplicas: 2
88+
maxReplicas: 10
89+
metrics:
90+
- type: Resource
91+
resource:
92+
name: cpu
93+
target:
94+
type: Utilization
95+
averageUtilization: 50

app/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Flask==2.1.1
2+
gunicorn==20.1.0

0 commit comments

Comments
 (0)