在本章中,您将学习如何定义集群,部署所有交互的微服务,以及如何在本地进行开发。我们将在前一章中介绍的概念的基础上进行构建,我们将描述如何在 Kubernetes 中以实用的术语配置整个系统,部署多个微服务,以及如何使其在您自己的本地计算机上作为一个整体工作。
在这里,我们将介绍另外两个微服务:前端和用户后端。它们在第 1 章、行动——设计、计划和执行中,在打破巨石部分的战略规划中进行了讨论。我们将在本章中看到它们需要如何配置才能在 Kubernetes 中工作。这是对第 2 章、中介绍的用 Python 创建 REST 服务的思想后端的补充; 第 3 章、使用 Docker 构建、运行和测试您的服务,以及第 4 章、创建管道和工作流。我们将讨论如何正确配置这三个组件,并添加一些其他选项,以确保它们在生产环境中部署后能够顺利运行。
本章将涵盖以下主题:
- 实现多种服务
- 配置服务
- 在本地部署整个系统
到本章结束时,您将拥有一个工作正常的本地 Kubernetes 系统,三个微服务作为一个整体进行部署和工作。您将了解不同元素如何工作,以及如何配置和调整它们。
对于本章,您需要运行一个本地 Kubernetes 实例,如前一章所述。记得安装入口控制器。
您可以查看我们将在 GitHub 存储库中使用的完整代码(https://GitHub . com/PacktPublishing/动手 Docker-for-micro service-with-Python/tree/master/chapter 06)。
在 GitHub repo 中,您可以找到我们将在本章中使用的三种微服务。它们基于第 1 章、提出的整体——设计、计划和执行,分为三个要素:
- 思想后端:如前一章所述,这处理思想的存储和对思想的搜索。
- 用户后端:存储用户,允许用户登录。根据认证方法的描述,这将创建一个可用于针对其他系统进行认证的令牌。
- 前端:这来自单块,但是它不是直接访问数据库,而是向用户和想法后端发出复制功能的请求。
Note that the static files are still being served by the Frontend, even though we described the final stage of the cluster serving them independently. This is done for simplicity and to avoid having an extra service.
上述服务以类似于思想后端在第 3 章、中使用 Docker 构建、运行和测试您的服务的方式进行了 Docker 化。让我们看看其他微服务的一些细节。
用户后端的代码可以在https://github . com/packt publishing/动手操作 Python 微服务/tree/master/chapter 06/Users _ 后端中找到。该结构与思想后端非常相似,思想后端是一个 Flask-RESTPlus 应用,它与 PostgreSQL 数据库通信。
它有两个端点,正如在它的斯瓦格界面中看到的:
端点如下:
| | 终点 | 输入 | 返回 |
| POST
| /api/login
| {username: <username>, password: <password>}
| {Authorized: <token header>}
|
| POST
| /admin/users
| {username: <username>, password: <password>}
| <new_user>
|
admin
端点允许您创建新用户,登录 API 返回一个有效的头,可以用于思想后端。
用户以下列模式存储在数据库中:
| 场 | 格式 | 评论 |
| id
| Integer
| 主关键字 |
| username
| String (50)
| 用户名 |
| password
| String(50)
| 密码以纯文本存储,这是一个坏主意,但简化了示例 |
| creation
| Datetime
| 创建用户的时间 |
在 SQLAlchemy 模型定义中,使用以下代码描述该模式:
class UserModel(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50))
# DO NOT EVER STORE PLAIN PASSWORDS IN DATABASES
# THIS IS AN EXAMPLE!!!!!
password = db.Column(db.String(50))
creation = db.Column(db.DateTime, server_default=func.now())
Note that the creation date gets stored automatically. Also, note that we store the password in plain text. This is a terrible, terrible idea in a production service. You can check out an article called How to store a password in the database? (https://www.geeksforgeeks.org/store-password-database/) to get general ideas for encrypting passwords with a salt seed. You can use a package such as pyscrypt
(https://github.com/ricmoo/pyscrypt) to implement this kind of structure in Python.
用户布鲁斯和斯蒂芬被添加到db
示例中,作为获取示例数据的一种方式。
前端代码可以在 GitHub repo 中找到。它基于在第 1 章、采取行动-设计、计划和执行中介绍的 Django 单块(https://github . com/packt publishing/动手-Python 微服务 Docker/tree/master/Chapter 01/单块)。
与整块的主要区别在于数据库不被访问。因此,Django ORM 没有任何用途。它们被对其他后端的 HTTP 请求所取代。为了发出请求,我们使用了神奇的requests
库。
例如,search.py
文件被转换成以下代码,该代码将搜索委托给思想后端微服务。注意客户的请求是如何转换成对GET /api/thoughts
端点的内部应用编程接口调用的。结果在 JSON 中解码,并在模板中呈现:
import requests
def search(request):
username = get_username_from_session(request)
search_param = request.GET.get('search')
url = settings.THOUGHTS_BACKEND + '/api/thoughts/'
params = {
'search': search_param,
}
result = requests.get(url, params=params)
results = result.json()
context = {
'thoughts': results,
'username': username,
}
return render(request, 'search.html', context)
整块等价代码可以在 repo 的Chapter01
子目录中进行比较。
Note how we make a get
request through the requests
library to the defined search endpoint, which results in the json
format being returned and rendered.
THOUGTHS_BACKEND
根网址来自设置,以通常的姜戈方式。
这个例子很简单,因为不涉及认证。从用户界面捕获参数,然后将其发送到后端。请求在后端和获得结果后都得到正确的格式化,然后被呈现。这是两个微服务协同工作的核心。
一个更有趣的案例是list_thought
(https://github . com/PacktPublishing/hand-On-Docker-for-micro-service-with-Python/blob/master/chapter 06/front/myth things/ideas . py # L18)视图。以下代码列出了登录用户的想法:
def list_thoughts(request):
username = get_username_from_session(request)
if not username:
return redirect('login')
url = settings.THOUGHTS_BACKEND + '/api/me/thoughts/'
headers = {
'Authorization': request.COOKIES.get('session'),
}
result = requests.get(url, headers=headers)
if result.status_code != http.client.OK:
return redirect('login')
context = {
'thoughts': result.json(),
'username': username,
}
return render(request, 'list_thoughts.html', context)
这里,在做任何事情之前,我们需要检查用户是否登录。这是在get_username_from_session
呼叫中完成的,如果他们没有登录,将返回username
或None
。如果他们没有登录,返回将被重定向到登录屏幕。
由于该端点需要认证,我们需要将用户的会话添加到请求的Authorization
头中。用户的会话可以从request.COOKIES
字典中获得。
作为保障,我们需要检查从后端返回的状态代码是否正确。对于这个调用,任何不是 200 (HTTP 调用正确)的结果状态代码都会产生一个到登录页面的重定向。
For simplicity and clarity, our example services are not handling different error cases. In a production system, there should be a differentiation between errors where the issue is that either the user is not logged in or there's another kind of user error (a 400 error), or the backend service is not available (a 500 status code).
Error handling, when done properly, is difficult, but worth doing well, especially if the error helps users to understand what happened.
get_username_from_session
函数封装了对validate_token_header
的调用,与上一章介绍的相同:
def get_username_from_session(request):
cookie_session = request.COOKIES.get('session')
username = validate_token_header(cookie_session,
settings.TOKENS_PUBLIC_KEY)
if not username:
return None
return username
settings
文件包含解码令牌所需的公钥。
In this chapter, for simplicity, we copied the key directly into the settings
file. This is not the way to go for a production environment. Any secret should be obtained through the Kubernetes environment configuration. We will see how to do this in the following chapters.
环境文件需要指定用户后端和想法后端的基本网址在哪里,以便能够连接到它们。
仅通过docker-compose
就可以测试服务的协同工作。检查用户后端和思想后端中的docker-compose.yaml
文件是否对外公开了不同的端口。
思想后端公开端口8000
,用户后端公开端口8001
。这允许前端连接到它们(并暴露端口8002
)。此图显示了该系统的工作原理:
您可以看到这三个服务是如何隔离的,因为docker-compose
将创建自己的网络供它们连接。两个后端都有自己的容器,充当数据库。
前端服务需要连接到其他服务。服务的网址应添加到environment.env
文件中,并应使用计算机的 IP 指示服务。
An internal IP such as localhost or 127.0.0.1
does not work, as it gets interpreted **inside the container. **You can obtain the local IP by running ifconfig
.
例如,如果您的本地 IP 是10.0.10.3
,则environment.env
文件应包含以下内容:
THOUGHTS_BACKEND_URL=http://10.0.10.3:8000
USER_BACKEND_URL=http://10.0.10.3:8001
如果您在浏览器中访问前端服务,它应该会连接到其他服务。
A possibility could be to generate a bigger docker-compose
file that includes everything. This could make sense if all the microservices are in the same Git repo, a technique known as monorepo (https://gomonorepo.org/). Possible problems include keeping both the internal docker-compose
to work with a single system and the general one in sync so that the automated tests should detect any problems.
这个结构有点麻烦,所以我们可以把它转换成一个合适的 Kubernetes 集群,以本地开发为目标。
要在 Kubernetes 中配置应用,我们需要为每个应用定义以下 Kubernetes 对象:
- 部署:部署将控制吊舱的创建,因此它们将始终可用。它还将根据映像创建它们,并在需要时添加配置。吊舱运行应用。
- 服务:该服务将使 RESTful 请求在集群内可用,名称简短。这将请求路由到任何可用的 pod。
- 入口:这使得服务在集群外可用,所以我们可以从集群外访问应用。
在这一节中,我们将详细地以思想后端配置为例。稍后,我们将看到不同部分是如何连接的。我们创建了一个 Kubernetes 子目录(https://github . com/PacktPublishing/hand-On-Docker-for-micro-service-with-Python/tree/master/chapter 06/thinks _ 后端/kubernetes )来存储带有每个定义的.yaml
文件。
我们将使用example
命名空间,因此请确保它已创建:
$ kubectl create namespace example
让我们从第一个 Kubernetes 对象开始。
对于思想后端部署,我们将部署一个包含两个容器的 pod,一个包含数据库,另一个包含应用。这种配置使得在本地工作变得很容易,但是请记住,重新创建 pod 将重新启动两个容器。
这里的配置文件是完全可用的(https://github . com/PacktPublishing/hand-On-Docker-for-micro-service-with-Python/blob/master/chapter 06/thinks _ 后端/kubernetes/deployment.yaml ),所以我们来看看它的不同部分。第一个元素描述了它是什么及其名称,以及它所在的命名空间:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: thoughts-backend
labels:
app: thoughts-backend
namespace: example
然后,我们生成spec
。它包含了我们应该保留多少个豆荚以及每个豆荚的模板。selector
定义监控哪些标签,应该与模板中的labels
匹配:
spec:
replicas: 1
selector:
matchLabels:
app: thoughts-backend
template
部分在自己的spec
部分定义容器:
template:
metadata:
labels:
app: thoughts-backend
spec:
containers:
- name: thoughts-backend-service
...
- name: thoughts-backend-db
...
thoughts-backend-db
比较简单。唯一需要的元素是定义容器和映像的名称。我们需要将拉取策略定义为Never
,以指示映像在本地 Docker repo 中可用,并且没有必要从远程注册表中拉取它:
- name: thoughts-backend-db
image: thoughts_backend_db:latest
imagePullPolicy: Never
thoughts-backend-service
需要定义服务的公开端口以及环境变量。变量值是我们之前在创建数据库时使用的值,除了POSTGRES_HOST
,在这里我们有一个优势,即同一容器中的所有容器共享相同的 IP:
- name: thoughts-backend-service
image: thoughts_server:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
env:
- name: DATABASE_ENGINE
value: POSTGRESQL
- name: POSTGRES_DB
value: thoughts
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: somepassword
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_HOST
value: "127.0.0.1"
要在 Kubernetes 中进行部署,您需要应用该文件,如下所示:
$ kubectl apply -f thoughts_backend/kubernetes/deployment.yaml
deployment "thoughts-backend" created
部署现在在集群中创建:
$ kubectl get deployments -n example
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
thoughts-backend 1 1 1 1 20s
这将自动创建吊舱。如果 pod 被删除或崩溃,部署将以不同的名称重新启动它:
$ kubectl get pods -n example
NAME READY STATUS RESTARTS AGE
thoughts-backend-6dd57f5486-l9tgg 2/2 Running 0 1m
部署正在跟踪最新的映像,但是它不会创建新的 pod,除非它被删除。要进行更改,请确保手动删除 pod,之后将重新创建它:
$ kubectl delete pod thoughts-backend-6dd57f5486-l9tgg -n example
pod "thoughts-backend-6dd57f5486-l9tgg" deleted
$ kubectl get pods -n example
NAME READY STATUS RESTARTS AGE
thoughts-backend-6dd57f5486-nf2ds 2/2 Running 0 28s
该应用在集群中仍然不可发现,除了通过其特定的 pod 名称来引用它之外,pod 名称可以更改,因此我们需要为此创建一个服务。
我们创建一个 Kubernetes 服务,为创建的部署公开的应用创建一个名称。服务可以在service.yaml
文件中查看。让我们来看看:
---
apiVersion: v1
kind: Service
metadata:
namespace: example
labels:
app: thoughts-service
name: thoughts-service
spec:
ports:
- name: thoughts-backend
port: 80
targetPort: 8000
selector:
app: thoughts-backend
type: NodePort
初始数据类似于部署。spec
部分定义了开放端口,将对端口80
上的服务的访问路由到位于thoughts-backend
中的容器中的端口8000
,这是部署的名称。selector
部分将所有请求路由到任何匹配的 pod。
类型为NodePort
允许从集群外部访问。一旦我们找到外部暴露的 IP,这允许我们检查它是否正常工作:
$ kubectl apply -f kubernetes/service.yaml
service "thoughts-service" configured
$ kubectl get service -n example
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
thoughts-service 10.100.252.250 <nodes> 80:31600/TCP 1m
我们可以通过使用描述的窗格访问本地主机来访问思想后端。在这种情况下,http://127.0.0.1:31600
:
该服务为我们提供了一个内部名称,但是如果我们想要控制它如何暴露在外部,我们需要配置一个入口。
最后,我们在ingress.yaml
(https://github . com/PacktPublishing/hand-On-Docker-for-micro-service-with-Python/blob/master/chapter 06/thinks _ 后端/kubernetes/ingress.yaml )中描述了入口。文件复制到这里。注意我们如何将元数据设置在适当的名称空间中:
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: thoughts-backend-ingress
namespace: example
spec:
rules:
- host: thoughts.example.local
http:
paths:
- backend:
serviceName: thoughts-service
servicePort: 80
path: /
该入口将使服务暴露给端口80
上的节点。由于多个服务可以在同一个节点上公开,它们可以通过主机名来区分,在本例中为thoughts.example.local
。
The Ingress controller we are using only allows exposing ports 80
(HTTP) and 443
(HTTPS) in servicePort
.
应用该服务后,我们可以尝试访问该页面,但是,除非我们将呼叫定向到正确的主机,否则我们将得到 404 错误:
$ kubectl apply -f kubernetes/ingress.yaml
ingress "thoughts-backend-ingress" created
$ kubectl get ingress -n example
NAME HOSTS ADDRESS PORTS AGE
thoughts-backend-ingress thoughts.example.local localhost 80 1m
$ curl http://localhost
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.15.8</center>
</body>
</html>
我们需要能够将任何请求指向我们的本地主机。在 Linux 和 macOS 中,最简单的方法是将您的/etc/hosts
文件更改为包含以下行:
127.0.0.1 thoughts.example.local
然后,我们可以使用浏览器检查我们的应用,这次在http://thoughts.example.local
(和端口80
)中:
定义不同的主机条目允许我们从外部访问所有的服务,能够调整它们并调试问题。我们将以同样的方式定义其余的组件。
If you get a Connection refused
error and the word localhost
does not appear when running kubectl get ingress -n example
, your Kubernetes installation does not have the Ingress controller installed. Double-check the installation documentation at https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md.
所以现在我们有了一个部署在 Kubernetes 本地的工作应用!
我们的每个微服务都独立工作,但是为了让整个系统工作,我们需要部署其中的三个(思想后端、用户后端和前端),并将它们相互连接。特别是前端,需要另外两个微服务启动并运行。有了 Kubernetes,我们可以在本地部署它。
要部署整个系统,我们需要先部署用户后端,然后部署前端。我们将描述这些系统中的每一个,将它们与已经部署的思想后端相关联,我们之前已经看到了如何部署。
用户后端文件非常类似于思想后端。你可以在 GitHub repo 中查看它们。确保deployment.yaml
值中的环境设置正确:
$ kubectl apply -f users_backend/kubernetes/deployment.yaml
deployment "users-backend" created
$ kubectl apply -f users_backend/kubernetes/service.yaml
service "users-service" created
$ kubectl apply -f users_backend/kubernetes/ingress.yaml
ingress "users-backend-ingress" created
记住一定要在/etc/hosts
中包含新的主机名:
127.0.0.1 users.example.local
您可以在http://users.example.local
中访问用户后端。
前端服务和入口与前面的非常相似。部署略有不同。让我们分三组来看一下配置:
- 首先,我们添加关于
namespace
、name
和kind
(部署)的元数据,如下代码所示:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: frontend
namespace: example
- 然后,我们用模板和
replicas
的数量定义spec
。对于本地系统来说,只有一个副本是合适的:
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
- 最后,我们
spec
用容器定义出模板:
spec:
containers:
- name: frontend-service
image: thoughts_frontend:latest
imagePullPolicy: Never
ports:
- containerPort: 8000
env:
- name: THOUGHTS_BACKEND_URL
value: http://thoughts-service
- name: USER_BACKEND_URL
value: http://users-service
与之前定义的思想后端部署的主要区别在于有一个容器,并且其上的环境更简单。
我们将后端 URL 环境定义为服务端点。这些端点在集群中是可用的,因此它们将被定向到适当的容器。
Remember that the *.example.local
addresses are only available in your computer, as they only live in /etc/hosts
. Inside the container, they won't be available.
This is suitable for local development, but an alternative is to have a DNS domain that can be redirected to 127.0.0.1
or similar.
我们应该在/etc/hosts
文件中添加一个新的域名:
127.0.0.1 frontend.example.local
Django 要求您设置ALLOWED_HOSTS
设置的值,以允许它接受主机名,因为默认情况下,它只允许来自 localhost 的连接。更多信息请参见 Django 文档(https://docs . Django project . com/en/2.2/ref/settings/# allowed-hosts)。为了简化事情,我们可以允许任何主机使用'*'
。查看 GitHub 上的代码。
In production, it's good practice to limit the hosts to the Fully Qualified Domain Name (FQDN), the full DNS name of a host, but the Kubernetes Ingress will check the host header and reject it if it's not correct.
前端应用的部署与我们之前所做的一样:
$ kubectl apply -f frontend/kubernetes/deployment.yaml
deployment "frontend" created
$ kubectl apply -f frontend/kubernetes/service.yaml
service "frontend-service" created
$ kubectl apply -f frontend/kubernetes/ingress.yaml
ingress "frontend-ingress" created
然后我们可以访问整个系统,登录,搜索等等。
Remember that there are two users, bruce
and stephen
. Their passwords are the same as their usernames. You don't need to be logged in to search.
在浏览器中,转到http://frontend.example.local/
:
恭喜你!您有一个工作正常的 Kubernetes 系统,包括不同部署的微服务。您可以独立访问每个微服务来调试它或者执行一些操作,比如创建新用户等等。
如果需要部署新版本,使用docker-compose
构建适当的容器,并删除容器,以强制重新创建容器。
在本章中,我们看到了如何在 Kubernetes 本地集群中部署我们的微服务,以允许本地开发和测试。将整个系统部署在本地计算机上可以大大简化新功能的开发或系统行为的调试。生产环境会很相似,所以这也为它奠定了基础。
我们首先描述了缺失的两个微服务。用户后端处理用户的认证,前端是第 1 章、移动-设计、计划和执行中的整体的修改版本,它连接到两个后端。我们展示了如何以一种docker-compose
方式构建和运行它们。
之后,我们描述了如何在 Kubernetes 中设置.yaml
文件的组合来正确配置应用。每个微服务都有自己的部署来定义可用的吊舱,有一个服务来定义稳定的接入点,还有一个入口来允许外部访问。我们详细描述了它们,然后将它们应用于所有的微服务。
在下一章中,我们将看到如何从本地部署转移到部署准备投入生产的 Kubernetes 集群。
- 我们正在部署的三种微服务是什么?
- 哪个微服务需要另外两个可用?
- 为什么我们在
docker-compose
运行时需要使用外部 IPs 连接微服务? - 每个应用需要哪些主要的 Kubernetes 对象?
- 是否不需要任何对象?
- 如果我们将任何微服务扩展到多个 pod,您能看到任何问题吗?
- 我们为什么使用
/etc/hosts
文件?
你可以在Kubernetes for Developers(https://www . packtpub . com/eu/virtualization-and-cloud/Kubernetes-Developers)和 Kubernetes Cookbook -第二版(https://www . packtpub . com/in/virtualization-and-cloud/Kubernetes-Cookbook-第二版)中了解更多关于 Kubernetes 的信息。