├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── kubernetes ├── deployment.yml └── service.yml ├── requirements.txt ├── src ├── __pycache__ │ └── app.cpython-310.pyc ├── app.py └── templates │ └── index.html └── webapp ├── .helmignore ├── Chart.yaml ├── templates ├── NOTES.txt ├── _helpers.tpl ├── deployment.yaml └── service.yaml └── values.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | tutorial-env -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tutorial-env -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.0-alpine3.15 2 | WORKDIR /app 3 | COPY requirements.txt . 4 | RUN pip install -r requirements.txt 5 | COPY src src 6 | EXPOSE 5000 7 | HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=5 \ 8 | CMD curl -f http://localhost:5000/health || exit 1 9 | ENTRYPOINT ["python", "./src/app.py"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microservices-in-python 2 | microservices-in-python 3 | - Installing Python 3.X 4 | - Creating Python Virtual Environments 5 | - Installing Python VS Code Extension 6 | - Sample Flask Application 7 | - Jinja templating for Dynamic Web Pages 8 | - Using Pip to Freeze Python Dependencies 9 | - Building the docker image using Dockerfile 10 | - Writing Docker Compose file 11 | - Writing Kubernetes Manifest files for the application 12 | - Creating Helm Chart 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" # optional since v1.27.0 2 | services: 3 | web: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | image: webapp:2.0 8 | ports: 9 | - "80:5000" 10 | restart: always 11 | networks: 12 | - webnet 13 | 14 | networks: 15 | webnet: -------------------------------------------------------------------------------- /kubernetes/deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: python-webapp 5 | labels: 6 | app: web 7 | spec: 8 | replicas: 2 9 | selector: 10 | matchLabels: 11 | app: web 12 | template: 13 | metadata: 14 | labels: 15 | app: web 16 | spec: 17 | containers: 18 | - name: webapp 19 | image: webapp:1.0 20 | ports: 21 | - containerPort: 5000 -------------------------------------------------------------------------------- /kubernetes/service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: web-service 5 | spec: 6 | type: NodePort 7 | selector: 8 | app: web 9 | ports: 10 | - port: 80 11 | targetPort: 5000 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==8.0.3 2 | colorama==0.4.4 3 | Flask==2.0.2 4 | itsdangerous==2.0.1 5 | Jinja2==3.0.3 6 | MarkupSafe==2.0.1 7 | Werkzeug==2.0.2 8 | -------------------------------------------------------------------------------- /src/__pycache__/app.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kunchalavikram1427/microservices-in-python/710d45a42ddb0bf73812f10c08c678efbb47bf23/src/__pycache__/app.cpython-310.pyc -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, render_template 2 | import socket 3 | app = Flask(__name__) 4 | 5 | # Function to fetch hostname and ip 6 | def fetchDetails(): 7 | hostname = socket.gethostname() 8 | host_ip = socket.gethostbyname(hostname) 9 | return str(hostname), str(host_ip) 10 | 11 | @app.route("/") 12 | def hello_world(): 13 | return "

Hello, World!

" 14 | 15 | @app.route("/health") 16 | def health(): 17 | return jsonify( 18 | status="UP" 19 | ) 20 | @app.route("/details") 21 | def details(): 22 | hostname, ip = fetchDetails() 23 | return render_template('index.html', HOSTNAME=hostname, IP=ip) 24 | 25 | if __name__ == '__main__': 26 | app.run(host='0.0.0.0', port=5000) -------------------------------------------------------------------------------- /src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dockerizing a Python Web Application 5 | 6 | 7 | 8 |

Simple Web Application using Python Flask Framework

9 |

Response from host {{ HOSTNAME }} with IP Address {{ IP }}

10 | 11 | -------------------------------------------------------------------------------- /webapp/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /webapp/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: webapp 3 | description: A Helm chart for Kubernetes 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.0 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /webapp/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "webapp.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "webapp.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "webapp.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "webapp.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /webapp/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "webapp.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "webapp.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "webapp.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "webapp.labels" -}} 37 | helm.sh/chart: {{ include "webapp.chart" . }} 38 | {{ include "webapp.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "webapp.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "webapp.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "webapp.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "webapp.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | -------------------------------------------------------------------------------- /webapp/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "webapp.fullname" . }} 5 | labels: 6 | {{- include "webapp.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "webapp.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | labels: 15 | {{- include "webapp.selectorLabels" . | nindent 8 }} 16 | spec: 17 | containers: 18 | - name: {{ .Chart.Name }} 19 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" 20 | imagePullPolicy: {{ .Values.image.pullPolicy }} 21 | ports: 22 | - name: http 23 | containerPort: {{ .Values.service.targetPort }} 24 | protocol: TCP -------------------------------------------------------------------------------- /webapp/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "webapp.fullname" . }} 5 | labels: 6 | {{- include "webapp.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: {{ .Values.service.targetPort }} 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "webapp.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /webapp/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for webapp. 2 | # This is a YAML-formatted file. 3 | # Declare variables to be passed into your templates. 4 | 5 | replicaCount: 2 6 | 7 | image: 8 | repository: webapp 9 | pullPolicy: IfNotPresent 10 | # Overrides the image tag whose default is the chart appVersion. 11 | tag: "1.0" 12 | 13 | imagePullSecrets: [] 14 | nameOverride: "" 15 | fullnameOverride: "" 16 | 17 | serviceAccount: 18 | # Specifies whether a service account should be created 19 | create: true 20 | # Annotations to add to the service account 21 | annotations: {} 22 | # The name of the service account to use. 23 | # If not set and create is true, a name is generated using the fullname template 24 | name: "" 25 | 26 | podAnnotations: {} 27 | 28 | podSecurityContext: {} 29 | # fsGroup: 2000 30 | 31 | securityContext: {} 32 | # capabilities: 33 | # drop: 34 | # - ALL 35 | # readOnlyRootFilesystem: true 36 | # runAsNonRoot: true 37 | # runAsUser: 1000 38 | 39 | service: 40 | type: NodePort 41 | port: 80 42 | targetPort: 5000 43 | 44 | ingress: 45 | enabled: false 46 | className: "" 47 | annotations: {} 48 | # kubernetes.io/ingress.class: nginx 49 | # kubernetes.io/tls-acme: "true" 50 | hosts: 51 | - host: chart-example.local 52 | paths: 53 | - path: / 54 | pathType: ImplementationSpecific 55 | tls: [] 56 | # - secretName: chart-example-tls 57 | # hosts: 58 | # - chart-example.local 59 | 60 | resources: {} 61 | # We usually recommend not to specify default resources and to leave this as a conscious 62 | # choice for the user. This also increases chances charts run on environments with little 63 | # resources, such as Minikube. If you do want to specify resources, uncomment the following 64 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'. 65 | # limits: 66 | # cpu: 100m 67 | # memory: 128Mi 68 | # requests: 69 | # cpu: 100m 70 | # memory: 128Mi 71 | 72 | autoscaling: 73 | enabled: false 74 | minReplicas: 1 75 | maxReplicas: 100 76 | targetCPUUtilizationPercentage: 80 77 | # targetMemoryUtilizationPercentage: 80 78 | 79 | nodeSelector: {} 80 | 81 | tolerations: [] 82 | 83 | affinity: {} 84 | --------------------------------------------------------------------------------