在本文中,我们将使用Flask和JavaScript编写的、带有MongoDB数据库的TODO应用程序,并学习如何将其部署到Kubernetes上。这篇文章是针对初学者的,如果你之前没有深度接触过Kubernetes集群,也不要担心!
我们将使用K3s,这是一个轻量级的Kubernetes发行版,非常适合快速入门。但首先让我们谈谈我们想要实现的目标。
首先,我将介绍示例应用程序。这其实已经简化了许多细节,但它说明了常见的用例。然后我们将熟悉了解容器化应用程序的过程。在我们继续之前,我会讨论我们如何使用容器来让我们的开发更加轻松,特别是如果我们在一个团队中工作,或者是当我们在一个新的环境中工作时,希望减轻开发人员的负担。
一旦我们将应用程序容器化,下一步就是将它们部署到Kubernetes上。虽然我们可以手动创建服务、Ingress和网关,但我们可以使用Knative以在任何时候都支持我们的应用程序。
设置应用程序
我们将使用一个简单的TODO应用程序来演示前端、REST API后端和MongoDB协同工作。这要归功于Prashant Shahi提出的这个例子。我做了一些小改动,纯粹是为了教学的目的:
https://github.com/prashant-shahi
首先,git clone代码库:
git clone https://github.com/benjamintanweihao/Flask-MongoDB-K3s-KNative-TodoApp
接下来,我们将检查目录,了解情况:
cd Flask-MongoDB-K3s-KNative-TodoApp
tree
该文件夹结构是一个典型的Flask应用程序。Entry point是app.py,它还包含REST APIs。Templates文件夹包含了将被渲染成HTML的文件:
打开 app.py,我们可以看到所有的主要部分:
├── app.py
├── requirements.txt
├── static
│ ├── assets
│ │ ├── style.css
│ │ ├── twemoji.js
│ │ └── twemoji.min.js
└── templates
├── index.html
└── update.html
从上面的代码段,您可以看到应用程序需要MongoDB作为数据库。使用lists()方法,您可以看到如何定义路由(即@ app.route(“/ list”))、如何从MongoDB获取数据,以及模板是如何呈现的示例。
mongodb_host = os.environ.get('MONGO_HOST', 'localhost')
mongodb_port = int(os.environ.get('MONGO_PORT', '27017'))
client = MongoClient(mongodb_host, mongodb_port)
db = client.camp2016
todos = db.todo
app = Flask(__name__)
title = "TODO with Flask"
@app.route("/list")
def lists ():
#Display the all Tasks
todos_l = todos.find()
a1="active"
return render_template('index.html',a1=a1,todos=todos_l,t=title,h=heading)
if __name__ == "__main__":
env = os.environ.get('APP_ENV', 'development')
port = int(os.environ.get('PORT', 5000))
debug = False if env == 'production' else True
app.run(host='0.0.0.0', port=port, debug=debug)
这里需要注意的另一件事是使用了MONGO_HOST和MONGO_PORT的环境变量和Flask相关的环境变量。其中,最重要的是debug。当变量设置为True时,Flask服务器会在检测到和发生更改时自动重新加载。这在开发过程中特别方便,也是我们要充分利用的特性。
用Docker容器开发
在处理应用程序时,我曾经花费大量时间设置环境并安装所有依赖项。在那之后,我可以通过添加新功能来启动和运行。然而,这仅仅描述了一个理想的场景,对吗?
你有多少次回到你已经开发的应用程序(比如六个月前),却发现自己正在慢慢陷入依赖项地狱?依赖项通常是一个灵活的目标,除非您采取措施锁定对象,否则您的应用程序可能无法正常工作。解决这个问题的方法之一是将所有依赖项打包到Docker容器中。
Docker带来的另一件特性是自动化。这意味着不再需要复制和粘贴命令,也不再需要设置数据库之类的东西。
Docker化 Flask程序
以下是Dockerfile:
FROM alpine:3.7
COPY . /app
WORKDIR /app
RUN apk add --no-cache bash git nginx uwsgi uwsgi-python py2-pip \
&& pip2 install --upgrade pip \
&& pip2 install -r requirements.txt \
&& rm -rf /var/cache/apk/*
EXPOSE 5000
ENTRYPOINT ["python"]
我们从一个最小的(在大小和功能方面)基础镜像开始。然后,应用程序的内容进入容器中的/app目录。接下来,我们执行一系列命令来安装Python、Nginx web server和Flask应用程序的所有需求。这些正是在新系统上设置应用程序所需的步骤。
您可以这样构建Docker容器:
% docker build -t /todo-app .
你将看到这样如下输出:
# ...
Successfully built c650af8b7942
Successfully tagged benjamintanweihao/todo-app:latest
那 MongoDB 呢?
您是否应该经历为MongoDB创建Dockerfile的相同过程?在此之前,已经有人做过这样的尝试,具体演示请查看案例链接:https://hub.docker.com/_/mongo.不过现在您有两个容器,其中Flask容器依赖于MongoDB容器。
一种方法是先启动MongoDB容器,然后启动Flask容器。但是,假设您想添加缓存并决定引入Redis容器。那么启动每个容器的过程会很快变枯燥繁琐。解决方案是Docker Compose,这是一个允许您定义和运行多个Docker容器的工具,正符合我们当前面临的情况。
Docker Compose
以下是Docker compose文件,docker-compose.yaml:
services:
flaskapp:
build: .
image: benjamintanweihao/todo-app:latest
ports:
- 5000:5000
container_name: flask-app
environment:
- MONGO_HOST=mongo
- MONGO_PORT=27017
networks:
- todo-net
depends_on:
- mongo
volumes:
- .:/app # <---
mongo:
image: mvertes/alpine-mongo
ports:
- 27017:27017
networks:
- todo-net
networks:
todo-net:
driver: bridge
即使您不熟悉Docker Compose,这里的YAML文件也并不复杂。让我们看一下重要的部分。
在最开头,这个文件定义了由flaskapp和mongo组成的服务,以及指定桥接连接的网络。这将创建一个网络连接,以便服务中定义的容器可以相互通信。
每个服务都定义镜像、端口映射和前面定义的网络。在flaskapp中也定义了环境变量(请查看app.py,看看它们是否确实是相同的)。
我想提醒您注意flask应用程序中指定的volume。我们在这里所做的是将主机的当前目录(应该是包含app.py的项目目录)映射到容器的/app目录 我们为什么要这样做?回想一下,在Dockerfile中,我们将app复制到/app目录中,如下所示:
COPY . /app
假设你想对应用程序做一个更改。你不可能轻易改变容器中的app.py。通过对本地目录的映射,你基本上是在用你目录中的本地副本覆盖容器中的app.py。因此,假设Flask应用程序处于调试模式(如果你在这一点上没有改变任何东西的话,它就是调试模式),当你启动容器并做出改变时,渲染的输出会反映出这个改变。
但是,重要的是要意识到容器中的app.py仍然是旧版本,您仍然需要记住构建新镜像(希望您已将CI/CD设置为自动执行此操作)
让我们看看这是怎么回事。运行以下命令:
docker-compose up
接下来你将看到:
Creating network "flask-mongodb-k3s-knative-todoapp_my-net" with driver "bridge"
Creating flask-mongodb-k3s-knative-todoapp_mongo_1 ... done
Creating flask-app ... done
Attaching to flask-mongodb-k3s-knative-todoapp_mongo_1, flask-app
# ... more output truncated
flask-app | * Serving Flask app "app" (lazy loading)
flask-app | * Environment: production
flask-app | WARNING: Do not use the development server in a production environment.
flask-app | Use a production WSGI server instead.
flask-app | * Debug mode: on
flask-app | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
flask-app | * Restarting with stat
mongo_1 | 2021-05-15T15:41:37.993+0000 I NETWORK [listener] connection accepted from 172.23.0.1:48844 #2 (2 connections now open)
mongo_1 | 2021-05-15T15:41:37.993+0000 I NETWORK [conn2] received client metadata from 172.23.0.1:48844 conn2: { driver: { name: "PyMongo", version: "3.11.4" }, os: { type: "Linux", name: "", architecture: "x86_64", version: "5.8.0-53-generic" }, platform: "CPython 2.7.15.final.0" }
flask-app | * Debugger is active!
flask-app | * Debugger PIN: 183-021-098
现在开始在浏览器中访问:http://localhost:5000
如果你看到这个,恭喜你!Flask和Mongo在一起正常工作了。您可以随意使用应用程序来感受它。
现在让我们对应用程序标题中的app.py做一个小小的改动:
index d322672..1c447ba 100644
--- a/app.py
+++ b/app.py
-heading = "tOdO Reminder"
+heading = "TODO Reminder!!!!!"
保存文件并重新加载应用程序:
完成后,您可以输入以下命令:
docker-compose down
将应用程序部署到Kubernetes上
截至目前,我们已将我们的应用程序及其支持服务(现在只是MongoDB)容器化。我们如何开始将我们的应用程序部署到Kubernetes?
在此之前,让我们安装Kubernetes。为此,我选择了K3s,因为它是安装Kubernetes和启动和运行的最简单方法。
% curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --no-deploy=traefik" sh -s -
过一会儿,你就可以安装 Kubernetes了:
[INFO] Finding release for channel stable
[INFO] Using v1.20.6+k3s1 as release
[INFO] Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.20.6+k3s1/sha256sum-amd64.txt
# truncated ...
[INFO] systemd: Starting k3s
验证是否已正确设置K3s:
% kubectl get no
NAME STATUS ROLES AGE VERSION
artemis Ready control-plane,master 2m53s v1.20.6+k3s1
MongoDB
有多种方法可以完成这一操作。您可以使用我们创建的镜像,MongoDB operator或Helm:
helm install mongodb-release bitnami/mongodb --set architecture=standalone --set auth.enabled=false
Please be patient while the chart is being deployed
MongoDB(R) can be accessed on the following DNS name(s) and ports from within your cluster:
mongodb-release.default.svc.cluster.local
To connect to your database, create a MongoDB(R) client container:
kubectl run --namespace default mongodb-release-client --rm --tty -i --restart='Never' --env="MONGODB_ROOT_PASSWORD=$MONGODB_ROOT_PASSWORD" --image docker.io/bitnami/mongodb:4.4.6-debian-10-r0 --command -- bash
Then, run the following command:
mongo admin --host "mongodb-release"
To connect to your database from outside the cluster execute the following commands:
kubectl port-forward --namespace default svc/mongodb-release 27017:27017 &
mongo --host 127.0.0.1
安装Knative和Istio
在本文中,我们将使用Knative。Knative构建在Kubernetes之上,使得开发人员可以很容易地部署和运行应用程序,而不必知道Kubernetes的很多细节。
Knative由两部分组成:Serving和Eventing。在本节中,我们将讨论Serving部分。使用Knative Serving,您可以在几秒钟内创建可弹性伸缩的、安全的和无状态的服务,这就是我们需要对TODO应用程序做的!在此之前,我们先安装Knative:
以下说明基于:
https://knative.dev/docs/install/install-serving-with-yaml/
kubectl apply -f https://github.com/knative/serving/releases/download/v0.22.0/serving-crds.yaml
kubectl apply -f https://github.com/knative/serving/releases/download/v0.22.0/serving-core.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.22.0/istio.yaml
kubectl apply -f https://github.com/knative/net-istio/releases/download/v0.22.0/net-istio.yaml
这设置了Knative和istio。你可能想知道为什么我们需要Istio。原因是Knative需要一个Ingress Controller,使其可以执行流量分发(例如, Todo应用程序的版本1和版本2需要同时运行)和自动HTTP请求重试。
Istio有替代方案吗?或许可以考虑Gloo(https://docs.solo.io/gloo-edge/master/installation/knative/)。但当前不支持Traefik,这就是为什么我们在安装K3s时必须禁用它。由于Istio是默认且最受支持的,我们将使用它。
现在等待所有的knative-serving 的pod运行:
kubectl get pods --namespace knative-serving -w
NAME READY STATUS RESTARTS AGE
controller-57956677cf-2rqqd 1/1 Running 0 3m39s
webhook-ff79fddb7-mkcrv 1/1 Running 0 3m39s
autoscaler-75895c6c95-2vv5b 1/1 Running 0 3m39s
activator-799bbf59dc-t6v8k 1/1 Running 0 3m39s
istio-webhook-5f876d5c85-2hnvc 1/1 Running 0 44s
networking-istio-6bbc6b9664-shtd2 1/1 Running 0 44s
设置自定义域
默认情况下,Knative Serving使用example.com作为默认域。如果您按照说明设置了K3s,则应该安装负载均衡器。这意味着通过一些设置,您可以使用sslip.io之类的DNS服务创建自定义域。
sslip.io是一种服务,当使用带有嵌入式IP地址的主机名进行查询时,它会返回该IP地址。例如,192.168.0.1.sslip.io等URL将指向192.168.0.1。这是极好的服务,你不必去买你自己的域名。
继续并应用以下manifest:
kubectl apply -f https://storage.googleapis.com/knative-nightly/serving/latest/serving-default-domain.yaml
如果您打开 serving-default-domain. yaml,您需要在 spec 中注意到以下内容:
# other parts truncated
spec:
serviceAccountName: controller
containers:
- name: default-doma
image: ko://knative.dev/serving/cmd/default-domain
args: ["-magic-dns=sslip.io"]
这将启用您将在下一步中需要使用的DNS。
测试是否一切正常
下载kn二进制文件。您可以查阅链接:https://knative.dev/development/client/install-kn/。一定要重命名二进制文件 kn然后把它放在$PATH的某个地方。一旦解决了这个问题,就继续创建示例Hello World服务。我已经将benjamintanweihao/helloworld python镜像推送到Docker Hub:
% kn service create helloworld-python --image=docker.io/benjamintanweihao/helloworld-python --env TARGET="Python Sample v1"
这将产生以下输出:
Creating service 'helloworld-python' in namespace 'default':
0.037s The Route is still working to reflect the latest desired specification.
0.099s Configuration "helloworld-python" is waiting for a Revision to become ready.
29.277s ...
29.314s Ingress has not yet been reconciled.
29.446s Waiting for load balancer to be ready
29.605s Ready to serve.
Service 'helloworld-python' created to latest revision 'helloworld-python-00001' is available at URL:
http://helloworld-python.default.192.168.86.26.sslip.io
输入以下代码即可列出所有命名空间中所有已部署的Knative服务:
% kn service list -A
如果有kubectl,这就变成:
% kubectl get ksvc -A
要删除服务,只需执行以下操作:
kn service delete helloworld-python # or kubectl delete ksvc helloworld-python
如果您还没有这样做,请确保TODO应用程序镜像已推送到DockerHub。记住用DockerHub ID替换{username}:
% docker push {username}/todo-app:latest
推送镜像后,可以使用kn命令创建TODO服务。记住用DockerHub ID替换{username}:
kn service create todo-app --image=docker.io/{username}/todo-app --env MONGO_HOST="mongodb-release.default.svc.cluster.local"
如果一切运行顺利,你将看到:
Creating service 'todo-app' in namespace 'default':
0.022s The Route is still working to reflect the latest desired specification.
0.085s Configuration "todo-app" is waiting for a Revision to become ready.
4.586s ...
4.608s Ingress has not yet been reconciled.
4.675s Waiting for load balancer to be ready
4.974s Ready to serve.
Service 'todo-app' created to latest revision 'todo-app-00001' is available at URL:
http://todo-app.default.192.168.86.26.sslip.io
现在访问http://todo-app.default.192.168.86.26.sslip.io (或者在上一个输出的最后一行的内容)您应该可以看到应用程序!现在我们来看看Knative为你做了什么。KNative仅需一行命令就可以为您启动一个服务,并且为您提供了一个可以从集群访问的URL。
我对Knative的了解仅仅停留于表面,但我希望这个教程可以激励你更多地了解它!当我开始看Knative的时候,我不太明白它做了什么。希望这个例子能让我们看到Knative的惊人之处和它的便利性。
结 论
在本文中,我们简要介绍了使用Python构建的web应用程序并需要MongoDB,并学习了如何:
- 使用Docker容器化TODO应用程序
- 使用Docker减轻依赖项
- 使用Docker进行开发
- 使用Docker Compose打包多个容器
- 安装K3s
- 安装Knative(Serving)和Istio
- 使用Helm署MongoDB
- 使用Knative部署TODO应用程序
虽然将应用程序迁移到 Kubernetes 当然不是一项简单的任务,但是将应用程序容器化通常会让你成功一半。当然,本文还有很多东西没有涉及,比如安全性和可扩展性。
K3s 是轻量级的Kubernetes发行版,可以极其轻松地使用笔记本/台式机测试和运行 Kubernetes 工作负载,对个人开发者来说十分友好。同时,也十分适合企业在边缘端大规模部署集群。
当我开始研究 Knative 的时候,我并不十分理解它的作用。希望这个例子能够帮助我们理解 Knative 的魅力及其带来的便利。实际上,Knative 的亮点之一就是“在几秒钟之内就能启动一个可扩展的、安全的、无状态的服务”。
我将在以后的文章中更多地介绍 Knative,并更深入地探讨其核心特征。我希望你能通过这篇教程,将它们应用到你的应用程序中!