惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
博客园_首页
J
Java Code Geeks
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
TaoSecurity Blog
TaoSecurity Blog
T
Troy Hunt's Blog
Forbes - Security
Forbes - Security
Schneier on Security
Schneier on Security
Hugging Face - Blog
Hugging Face - Blog
PCI Perspectives
PCI Perspectives
O
OpenAI News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hacker News: Ask HN
Hacker News: Ask HN
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Heimdal Security Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 聂微东
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
美团技术团队
V
V2EX
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
Cybersecurity and Infrastructure Security Agency CISA
有赞技术团队
有赞技术团队
腾讯CDC
Cloudbric
Cloudbric
Google DeepMind News
Google DeepMind News
博客园 - 【当耐特】
SecWiki News
SecWiki News
IT之家
IT之家
C
Cisco Blogs
雷峰网
雷峰网
aimingoo的专栏
aimingoo的专栏
B
Blog RSS Feed
S
Schneier on Security
Security Latest
Security Latest
Scott Helme
Scott Helme
H
Help Net Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Palo Alto Networks Blog
L
LINUX DO - 热门话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

Martin Heinz's Blog

A Guide to Python's Weak References Using weakref Module Recent Docker BuildKit Features You're Missing Out On Modern Git Commands and Features You Should Be Using Everything You Can Do with Python's textwrap Module Monitoring Indoor Air Quality with Prometheus, Grafana and a CO2 Sensor Everything You Can Do with Python's bisect Module You Don't Need a Dedicated Cache Service - PostgreSQL as a Cache A Collection of Docker Images To Solve All Your Debugging Needs Weird Python "Features" That Might Catch You By Surprise Lessons Learned From Writing 100 Articles Debugging Crashes and Deadlocks in Python using PyStack Goodbye etcd, Hello PostgreSQL: Running Kubernetes with an SQL Database Remote Interactive Debugging of Python Applications Running in Kubernetes The Right Way to Run Shell Commands From Python Real Multithreading is Coming to Python - Learn How You Can Use It Now Python's Missing Batteries: Essential Libraries You're Missing Out On Kubernetes-Native Synthetic Monitoring with Kuberhealthy Make Your CLI Demos a Breeze with Zero Stress and Zero Mistakes Reduce - The Power of a Single Python Function Why I Will Never Use Alpine Linux Ever Again Cgroups - Deep Dive into Resource Management in Kubernetes Dictionary Dispatch Pattern in Python Boost Your Python Application Performance using Continuous Profiling Lazy Evaluation Using Recursive Python Generators Python Magic Methods You Haven't Heard About Getting Started with Mastodon API in Python Backup-and-Restore of Containers with Kubernetes Checkpointing API Getting Started with Google APIs in Python Python CLI Tricks That Don't Require Any Code Whatsoever All The Ways To Introspect Python Objects at Runtime What is Python's "self" Argument, Anyway? Python List Comprehensions Are More Powerful Than You Might Think You Should Be Using Python's Walrus Operator - Here's Why Recipes and Tricks for Effective Structural Pattern Matching in Python It's Time to Say Goodbye to These Obsolete Python Libraries Advanced Features of Kubernetes' Horizontal Pod Autoscaler Data and System Visualization Tools That Will Boost Your Productivity Stop Messing with Kubernetes Finalizers Automate All the Boring Kubernetes Operations with Python End-to-End Monitoring with Grafana Cloud with Minimal Effort Bitly | bit.ly/3JLmSgA Bitly | bit.ly/3uETfbi Bitly | bit.ly/3MI4Iz0 Bitly | bit.ly/3M30D82 Bitly | bit.ly/3oMJ6qR Bitly | bit.ly/3IRD7IK Bitly | bit.ly/3A3B69t Bitly | bit.ly/31lKCYA Bitly | bit.ly/30uviIM Bitly | bit.ly/3E1X2mw Bitly | bit.ly/3Dv7JxP Bitly | bit.ly/3GG1BEz Bitly | bit.ly/3lLavs4 Bitly | bit.ly/39TqP3m Bitly | bit.ly/3A5Mpx8 Bitly | bit.ly/3kGwPl4 Bitly | bit.ly/3iHtulU Bitly | bit.ly/3xGjtKS Bitly | bit.ly/3h8DZg0 Bitly | bit.ly/2RQn1dG Bitly | bit.ly/3p2B5wW The Easiest Way to Debug Kubernetes Workloads Bitly | bit.ly/2PHVudx Cloud Native CI/CD with Tekton - Building Custom Tasks Bitly | bit.ly/3dg3QR9 Bitly | bit.ly/3qHtSkZ Deep Dive into Docker Internals - Union Filesystem Bitly | bit.ly/3qlRAUN Bitly | bit.ly/3pCUJ26 Bitly | bit.ly/3ifZxYr Bitly | bit.ly/34ZhIMt Bitly | bit.ly/3qSO7h0 Bitly | bit.ly/3muGLOk Bitly | bit.ly/35xN79v Bitly | bit.ly/3mLGshK Bitly | bit.ly/2IvkGQl Bitly | bit.ly/2Sk1KFK Bitly | bit.ly/3iCNIL6 Bitly | bit.ly/3beQPpy Saving Your Linux Machine from Certain Death New Features in Python 3.9 You Should Know About Analyzing Docker Image Security Recursive SQL Queries with PostgreSQL Automating Every Aspect of Your Python Project Tour of Python Itertools Implementing 2D Physics in Javascript Ultimate Setup for Your Next Python Project Making Python Programs Blazingly Fast Security and Cryptography Mistakes You Are Probably Doing All The Time Going Serverless with OpenFaaS and Golang - Building Optimized Templates Going Serverless with OpenFaaS and Golang - The Ultimate Setup and Workflow Setting Up Swagger Docs for Golang API Building RESTful APIs in Golang Pytest Features, That You Need in Your (Testing) Life Setting up GitHub Package Registry with Docker and Golang Ultimate Setup for Your Next Golang Project Python Tips and Trick, You Haven't Already Seen, Part 2. Tricks for Postgres and Docker that will make your life easier Getting The Most Out of Reading Books - Reading The "Professional Way" Python Tips and Trick, You Haven't Already Seen
Deploy Any Python Project to Kubernetes
Martin · 2020-04-16 · via Martin Heinz's Blog

As your project grows, it might get to the point that it becomes too hard to handle with just single VM or some simple SaaS solution. You can solve that by switching to more robust solution like Kubernetes. That might however, be little too complex if you are not familiar with it's concepts or if just never used it before. So, to help you out - in this article - we will go over all you need to get yourself started and have your Python project deployed on cluster - including cluster setup, all the Kubernetes manifests and some extra automation to make your life easier down the road!

This is a follow up to previous article(s) about Automating Every Aspect of Your Python Project, so you might want check that out before reading this one.

TL;DR: Here is my repository with full source code and docs: https://github.com/MartinHeinz/python-project-blueprint

Comfy Development Setup

To be productive in your development process, you need to have comfy local development setup. Which in this case means having simple to use Kubernetes on local, closely mirroring your real, production cluster and for that, we will use KinD:

KinD (Kubernetes-in-Docker), as the name implies, runs Kubernetes clusters in Docker containers. It is the official tool used by Kubernetes maintainers for Kubernetes v1.11+ conformance testing. It supports multi-node clusters as well as HA clusters. Because it runs K8s in Docker, KinD can run on Windows, Mac, and Linux. So, you can run it anywhere, you just need Docker installed.

So, let's install KinD (on Linux - if you are on Windows, see installation info here):


~ $ curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(uname)-amd64
~ $ chmod +x ./kind
~ $ sudo mv ./kind /usr/local/bin/kind
~ $ kind --version
kind version 0.7.0

With that, we are ready to setup our cluster. For that we will need following YAML file:


kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
        authorization-mode: "AlwaysAllow"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
- role: worker
- role: worker

This manifest describes our cluster. It will have 3 nodes - control plane (role: control-plane) and 2 workers role: worker. We are also giving it a few more settings and arguments to make it possible to setup ingress controller later, so that we can have HTTPS on this cluster. All you need to know about those settings, is that extraPortMappings tells cluster to forward ports from the host to an ingress controller running on a node.

Note: All the manifests for both cluster and Python application are available in my repo here in k8s directory.

Now, we need to run few commands to bring it up:


$ ~ kind create cluster --config kind-config.yaml --name cluster
$ ~ kubectl cluster-info --context kind-cluster
Kubernetes master is running at https://127.0.0.1:32769
KubeDNS is running at https://127.0.0.1:32769/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

$ ~ kubectl get nodes
NAME                    STATUS    ROLES     AGE       VERSION
cluster-control-plane   Ready     master    2m39s     v1.17.0
cluster-worker          Ready     <none>    2m3s      v1.17.0
cluster-worker2         Ready     <none>    2m3s      v1.17.0

To create cluster, we just need to run the first command. After that we can check whether it's good to go by running cluster-info and get nodes commands. Typing out these commands gets annoying after some time, so we will Make it simpler later, but with that we have cluster up and running.

Next, we want to setup ingress for our cluster. For that we have to run a few kubectl commands to make it work with KinD:


~ $ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
~ $ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/baremetal/service-nodeport.yaml
~ $ kubectl patch deployments -n ingress-nginx nginx-ingress-controller -p \
  '{"spec":{"template":{"spec":{"containers":[{"name":"nginx-ingress-controller","ports":[{"containerPort":80,"hostPort":80},{"containerPort":443,"hostPort":443}]}],"nodeSelector":{"ingress-ready":"true"},"tolerations":[{"key":"node-role.kubernetes.io/master","operator":"Equal","effect":"NoSchedule"}]}}}}'

First we deploy mandatory ingress-nginx components. On top of that we expose the nginx service using NodePort, which is what the second command does. Last command applies some KinD specific patches for ingress controller.

Defining Manifests

With cluster ready, it's time to setup and deploy our application. For that we will use very simple Flask application - echo server:


# __main__.py

from flask import Flask, request

app = Flask(__name__)


@app.route("/")
def echo():
    return f"You said: {request.args.get('text', '')}\n"


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=5000)

I chose Flask application instead of some CLI tool (or Python package), as we need the application that won't terminate instantaneously, as some Python package would. Also, notice host parameter being set to 0.0.0.0, without this it would not be possible to reach the application when we expose it through Kubernetes service and ingress.

Next thing we need is YAML manifests for this application, let's break it up into separate objects:

- Namespace:


apiVersion: v1
kind: Namespace
metadata:
  name: blueprint

Nothing to really talk about here. We generally don't want to deploy applications in default namespace, so this is the one will use instead.

- ConfigMap:


apiVersion: v1
kind: ConfigMap
metadata:
  name: env-config-blueprint
  namespace: blueprint
data:  # Example vars that will get picked up by Flask application
  FLASK_ENV: development
  FLASK_DEBUG: "1"

This is where we can define variables for the application. These vars from data section will be injected into application container as environment variables. As an example, I included FLASK_ENV and FLASK_DEBUG, which will be picked up by Flask automatically when the application starts.

- Secret:


apiVersion: v1
kind: Secret
metadata:
  name: env-secrets-blueprint
  namespace: blueprint
data:
  VAR: VkFMVUU=  # base64 of "VALUE"

The same way as we specified plaintext vars, we can use Secret to add things like credential and keys to our application. This object should however, not be pushed to your repository as it contains sensitive data. We can create it dynamically with following command:


~ $ kubectl create secret generic env-secrets-blueprint -n blueprint \
    --from-literal=VAR=VALUE \
    --from-literal=VAR2=VALUE2 \
    --dry-run -o yaml >> app.yaml

Note: This and other commands, needed to deploy the application are listed in README in repository as well as at the bottom manifests file here

- Deployment:


apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: blueprint
  name: blueprint
  namespace: blueprint
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: blueprint
    spec:
      containers:
#        - image: docker.pkg.github.com/martinheinz/python-project-blueprint/example:flask
#          ^^^^^  https://github.com/containerd/containerd/issues/3291 https://github.com/kubernetes-sigs/kind/issues/870
        - image: martinheinz/python-project-blueprint:flask
          name: blueprint
          imagePullPolicy: Always
          ports:
            - containerPort: 5000
              protocol: TCP
          envFrom:
            - configMapRef:
                name: env-config-blueprint
            - secretRef:
                name: env-secrets-blueprint
#      imagePullSecrets:  # In case you are using private registry (see kubectl command at the bottom on how to create regcred)
#        - name: regcred
  selector:
    matchLabels:
      app: blueprint

Now for the most important part - the Deployment. The relevant part here is the spec section which specifies image, ports and environment variables. For image we specify image from Docker Hub. In case we wanted to use some private registry like Artifactory, we would have to add imagePullSecret which gives the cluster credential for pulling the image. This secret can be created using following command:


kubectl create secret docker-registry regcred \
    --docker-server=docker.pkg.github.com \
    --docker-username=<USERNAME> --docker-password=<GITHUB_TOKEN> \
    --dry-run -o yaml >> app.yaml

This shows how you would allow pulling of your images from GitHub Package Registry, which unfortunately doesn't work with KinD right now, because of issues listed in the above YAML, but it would work just fine with your production cluster in Cloud (assuming it's not using KinD).

If you want to avoid pushing image to remote registry every time you redeploy your application, then you can load your image into cluster using kind load docker-image martinheinz/python-project-blueprint:flask.

After image, we also specify ports. These are ports on which our application is listening on, in this case 5000, because our app starts using app.run(host='0.0.0.0', port=5000).

Last part, the envFrom section is used to inject plaintext variables and secrets from ConfigMap and Secret shown above, by specifying their names in the respective Ref fields.

- Service:


apiVersion: v1
kind: Service
metadata:
  name: blueprint
  namespace: blueprint
  labels:
    app: blueprint
spec:
  selector:
    app: blueprint
  ports:
    - name: http
      targetPort: 5000  # port the container accepts traffic on
      port: 443  # port other pods use to access the Service
      protocol: TCP

Now, that we have application listening on a port, we need Service that will expose it. All this objects defines is that application listening on port 5000 should be exposed on cluster node on port 443.

- Ingress:


apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: blueprint-ingress
  namespace: blueprint
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
  labels:
    app: blueprint
spec:
  tls:
    - hosts:
      - localhost
      secretName: tls-secret
  rules:
    - host: localhost
      http:
        paths:
          - path: "/"
            backend:
              serviceName: blueprint
              servicePort: 443

Last big piece of puzzle - the Ingress - an object that manages external access to the Services in a cluster. Let's first look at the rules section - in this case we define that our host is localhost. We also set path to / meaning that any request sent to localhost/ belongs to associated backend defined by name of the previously shown Service and its port.

The other section here is tls. This section provides HTTPS for listed hosts by specifying Secret that includes tls.crt and tls.key. Let's create this Secret:


KEY_FILE="blueprint.key"
CERT_FILE="blueprint.crt"
HOST="localhost"
CERT_NAME=tls-secret
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${HOST}/O=${HOST}"
kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} --dry-run -o yaml >> app.yaml

Above snippet first sets a few variables which are then used to generate certificate and key files for TLS using openssl. Last command creates Secret containing these 2 files.

Deploying Application

With all the manifests ready, we can finally deploy our application:


$ ~ kubectl config set-context kind-cluster --namespace blueprint

$ ~ KEY_FILE="blueprint.key"
$ ~ CERT_FILE="blueprint.crt"
$ ~ HOST="localhost"
$ ~ CERT_NAME=tls-secret

$ ~ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj "/CN=${HOST}/O=${HOST}"

$ ~ kubectl create secret generic env-secrets-blueprint --from-literal=VAR=VALUE --dry-run -o yaml >> app.yaml && echo "---" >> app.yaml
$ ~ kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE} --dry-run -o yaml >> app.yaml

$ ~ kubectl apply -f app.yaml
namespace/blueprint unchanged
deployment.apps/blueprint created
configmap/env-config-blueprint unchanged
service/blueprint created
ingress.extensions/blueprint-ingress unchanged
secret/env-secrets-blueprint configured
secret/tls-secret configured

$ ~ kubectl get all
NAME                             READY   STATUS    RESTARTS   AGE
pod/blueprint-5d86484b76-dkw7z   1/1     Running   0          13s

NAME                TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/blueprint   ClusterIP   10.96.253.31   <none>        443/TCP   13s

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/blueprint   1/1     1            1           13s

NAME                                   DESIRED   CURRENT   READY   AGE
replicaset.apps/blueprint-5d86484b76   1         1         1       13s

$ ~ curl -k https://localhost/?text=Hello
You said: Hello

Most of the commands above we've already seen in sections before. What's new is kubectl apply -f app.yaml which creates all the necessary object in our cluster. After it's created we can check presence of those objects using kubectl get all. Finally we can check whether the application is accessible using cURL and it is! So, with that, we have our application running on the cluster!

Make-ing It Simple

If you don't feel fully comfortable with all the kind and kubectl commands yet or you are just lazy like me and you don't want to type it all out, then I have couple of Make targets for you - to make your life easier:

- Bring up the cluster:


cluster:
	@if [ $$(kind get clusters | wc -l) = 0 ]; then \
		kind create cluster --config ./k8s/cluster/kind-config.yaml --name kind; \
	fi
	@kubectl cluster-info --context kind-kind
	@kubectl get nodes
	@kubectl config set-context kind-kind --namespace $(MODULE)

The make cluster command will setup the cluster for you if it's not yet ready and if it is, it will give you all the info about it. This is nice if you need to check status of nodes and switch to your development namespace.

- Redeploy/Restart application:


deploy-local:
	@kubectl rollout restart deployment $(MODULE)

This one is very simple - all it does is roll out new deployment - so in case there is new image, it will deploy it, otherwise it will just restart your application.

- Debugging:


cluster-debug:
	@echo "\n${BLUE}Current Pods${NC}\n"
	@kubectl describe pods
	@echo "\n${BLUE}Recent Logs${NC}\n"
	@kubectl logs --since=1h -lapp=$(MODULE)

In case you need to debug your application, you will probably want to see recent events related to the application pod as well as recent (last hour) logs. That's exactly what make cluster-debug does for you.

- Get remote shell:


cluster-rsh:
	# if your container has bash available
	@kubectl exec -it $$(kubectl get pod -l app=${MODULE} -o jsonpath="{.items[0].metadata.name}") -- /bin/bash

If logs are not enough to solve some issues you might be having and you decide that you need to poke around inside the container, then you can run make cluster-rsh to get remote shell.

- Update manifests:


manifest-update:
	@kubectl apply -f ./k8s/app.yaml

We've seen this command before. It just re-applies YAML manifests, which is handy when you are tweaking some properties of Kubernetes objects.

Conclusion

This article wasn't meant to be Kubernetes tutorial, but I think it shows enough to get you started and have your application up and running pretty quickly. To learn more about Kubernetes, I recommend just playing, tweaking and changing things around in manifests and seeing what happens. That's in my opinion good way to find out how things work and to get comfortable with kubectl commands. If you have any questions, suggestions or issues feel free to reach out to me or create issue in my repository. In this repo, you can also find docs and all the manifests shown in this post.

Resources