发布于 2026-01-06 0 阅读
0

将任何 Python 项目部署到 Kubernetes

将任何 Python 项目部署到 Kubernetes

随着项目规模的增长,单个虚拟机或简单的 SaaS 解决方案可能难以应对。您可以切换到更强大的解决方案,例如Kubernetes,来解决这个问题。但是,如果您不熟悉 Kubernetes 的概念或从未用过,这可能会比较复杂。因此,为了帮助您,本文将介绍您需要了解的所有内容,帮助您入门并将Python项目部署到集群上——包括集群设置、所有Kubernetes清单以及一些额外的自动化功能,让您日后的开发工作更加轻松!

这是之前关于“自动化 Python 项目的各个方面”的文章的后续,所以你可能想在阅读本文之前先看看那些文章。

简而言之:这是我的代码仓库,包含完整的源代码和文档:https://github.com/MartinHeinz/python-project-blueprint

舒适的开发环境

为了提高开发效率,你需要一个舒适的本地开发环境。在这种情况下,这意味着在本地部署一个易于使用的Kubernetes 集群,使其与你的真实生产集群高度相似。为此,我们将使用KinD

KinD(Kubernetes-in-Docker)顾名思义,是在Docker容器中运行Kubernetes集群。它是Kubernetes维护者用于Kubernetes v1.11+ 一致性测试的官方工具。它支持多节点集群以及高可用性 (HA) 集群。由于KinD在Docker中运行Kubernetes因此它可以在 Windows、Mac 和 Linux 上运行。也就是说,只要安装了 Docker,您就可以在任何地方运行它。

那么,让我们来安装KinD(在 Linux 系统上 - 如果您使用的是 Windows 系统,请点击此处查看安装信息):

~ $ 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

至此,我们就可以开始搭建集群了。为此,我们需要以下 YAML 文件:

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

此清单描述了我们的集群。它将包含 3 个节点:控制平面节点(role: control-plane)和 2 个工作节点role: worker。我们还添加了一些设置和参数,以便稍后配置 Ingress 控制器,从而在该集群上实现 HTTPS。关于这些设置,您只需了解它们的作用:extraPortMappings指示集群将主机上的端口转发到运行在某个节点上的 Ingress 控制器。

注意:集群和 Python 应用程序的所有清单文件都可以在我的仓库的k8s目录找到。

现在,我们需要运行几个命令来启动它:

$ ~ 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

要创建集群,我们只需运行第一个命令。之后,我们可以通过运行 `git add`cluster-info和 ` get nodesgit checkout` 命令来检查集群是否正常运行。长时间输入这些命令会很麻烦,所以我们Make稍后会简化操作,但目前为止,集群已经启动并运行了。

接下来,我们需要为集群设置入口。为此,我们需要运行一些kubectl命令,使其与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"}]}}}}'

首先,我们部署必要的ingress-nginx组件。在此基础上,我们使用NodePort暴露nginx服务,这就是第二个命令的作用。最后一个命令为入口控制器应用了一些KinD特有的补丁。

定义清单

集群准备就绪后,就可以开始设置和部署我们的应用程序了。为此,我们将使用一个非常简单的Flask应用程序——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)

我选择使用Flask应用而不是命令行工具(或Python包),因为我们需要一个不会像某些Python包那样立即终止的应用。另外,请注意host参数已设置,如果没有此设置,当我们通过Kubernetes0.0.0.0服务和 Ingress暴露应用时,将无法访问该应用

接下来我们需要的是该应用程序的 YAML 清单文件,让我们将其拆分成单独的对象:

  • 命名空间:
apiVersion: v1
kind: Namespace
metadata:
  name: blueprint

没什么特别要说的。我们通常不希望在default命名空间中部署应用程序,所以会使用这个命名空间。

  • 配置映射:
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"

在这里我们可以定义应用程序的变量。这些变量data将作为环境变量注入到应用程序容器中。例如,我添加了 `${value1}`FLASK_ENV和`${value2}`, Flask会在应用程序启动时自动获取FLASK_DEBUG它们。

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

就像我们指定纯文本变量一样,我们可以使用Secret向应用程序添加凭据和密钥等信息。但是,由于此对象包含敏感数据,因此不应将其推送到存储库。我们可以使用以下命令动态创建它:

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

注意:部署应用程序所需的此命令和其他命令已列在存储库的 README 文件中,以及此处清单文件的底部。

  • 部署:
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

现在到了最重要的部分——部署。这里需要重点关注的是spec指定镜像、端口和环境变量的部分。image我们这里指定镜像来自Docker Hub。如果要使用像Artifactory这样的私有镜像仓库,则需要添加imagePullSecret集群凭据以拉取镜像。可以使用以下命令创建此密钥:

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

这展示了如何允许从GitHub Package Registry拉取您的图像,但遗憾的是,由于上述 YAML 中列出的问题,它目前无法与KinD一起使用,但它可以很好地与您的云中的生产集群一起使用(假设它不使用KinD)。

如果您不想每次重新部署应用程序时都将镜像推送到远程注册表,则可以使用以下命令将镜像加载kind load docker-image martinheinz/python-project-blueprint:flask到集群中。

之后image,我们还要指定端口ports。这些是我们的应用程序正在监听的端口,在本例中5000,因为我们的应用程序开始使用app.run(host='0.0.0.0', port=5000)

最后一部分envFrom用于通过在相应的字段中指定名称,将上面显示的ConfigMapSecretRef中的明文变量和密钥注入到该部分中。

  • 服务:
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

现在,我们已经有一个应用程序在监听端口port,我们需要一个服务来将其暴露出来。这个对象定义了这样一个事实:监听端口的应用程序5000应该暴露在集群节点的端口上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

最后一块重要的拼图——Ingress——一个管理集群中服务外部访问的对象。我们先来看这rules部分——在本例中,我们定义主机为localhost。我们还设置path/,这意味着发送到的任何请求localhost/都属于之前显示的Servicebackend的名称及其关联的port

这里还有一部分。这部分通过指定包含 ` <secret> ` 和 `<secret>` 的密钥tls,为列出的内容提供 HTTPS 连接。让我们创建这个密钥hoststls.crttls.key

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

上面的代码片段首先设置了一些变量,然后使用这些变量生成用于 TLS 的证书和密钥文件openssl。最后一个命令创建包含这两个文件的Secret 。

部署应用程序

所有清单文件准备就绪后,我们终于可以部署应用程序了:

$ ~ 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

上面的大部分命令我们之前已经在章节中见过了。新增的内容是,kubectl apply -f app.yaml它会在集群中创建所有必要的对象。创建完成后,我们可以使用 `constructor` 命令检查这些对象是否存在kubectl get all。最后,我们可以使用 `constructor` 命令检查应用程序是否可访问cURL,结果是可以的!至此,我们的应用程序已经在集群上运行了!

Make化繁为简

kind如果你对所有命令还不完全熟悉kubectl,或者像我一样懒,不想全部输入,那么我Make为你准备了几个目标——让你的生活更轻松:

  • 启动集群:
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)

make cluster如果集群尚未准备就绪,该命令将为您设置集群;如果已准备就绪,它将提供所有相关信息。如果您需要检查节点状态并切换到开发命名空间,这将非常有用。

  • 重新部署/重启应用程序:
deploy-local:
    @kubectl rollout restart deployment $(MODULE)

这个功能非常简单——它所做的只是推出新的部署——所以如果有新镜像,它就会部署它;否则,它只会重新启动你的应用程序。

  • 调试:
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)

如果您需要调试应用程序,您可能需要查看与应用程序 pod 相关的近期事件以及最近(过去一小时)的日志。这正是它能make cluster-debug为您提供的功能。

  • 获取远程 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

如果日志不足以解决您可能遇到的一些问题,并且您决定需要查看容器内部,那么您可以运行命令make cluster-rsh来获取远程 shell。

  • 更新清单:
manifest-update:
    @kubectl apply -f ./k8s/app.yaml

我们之前见过这个命令。它只是重新应用 YAML 清单,这在调整Kubernetes对象的某些属性时非常有用。

结论

本文并非Kubernetes教程,但我认为它足以帮助您快速入门并启动运行应用程序。要深入了解Kubernetes,我建议您亲自尝试,在清单文件中进行各种调整和修改,看看会发生什么。在我看来,这是了解其工作原理并熟悉kubectl命令的好方法。如果您有任何问题、建议或遇到任何问题,请随时与我联系或在我的代码仓库中创建 issue 。您还可以在该代码仓库中找到文档以及本文中展示的所有清单文件。

资源

文章来源:https://dev.to/martinheinz/deploy-any-python-project-to-kubernetes-2h2l