本文字数:4543字
阅读时间:16分钟
![b3ea0aff3ff7c1ddb992d4e9beb2a2c2.png](https://i-blog.csdnimg.cn/blog_migrate/6cabd572d2a60b2aac109e3d2c4d3730.jpeg)
接:Kubernetes 网络如何进行工作(第一部分)
嘿!准备好了吗?我希望你已经准备好了!欢迎来到Kubernetes网络系列文章的第二部分。
在前一篇文章也就是这个系列的第一部分中,我们讨论了容器和pod。我们来回忆一下要点。首先,我们了解了Kubernetes如何管理路由设备和路由规则组成的虚拟网络。然后,我们了解了在某个集群上运行的pod如何与在其他集群上运行的pod通信。只有当发送方知道接收方的pod网络IP地址时,通信才有可能实现。没有读过第一部分的同学,请先阅读第一部分!只有阅读完第一部分,你才会理解pods是如何通信的。
Pod网络集群非常整洁。但问题是,因为Pod的寿命不长,它没有足够的能力创建一个持久的系统。例如:pod IP地址可以用作端点,但没有人能保证这个地址是永久的,因为在pod重新创建期间,地址会发生改变。而pod的重新创建的发生有很多原因,所以很有可能会产生一个新的地址。
那么如何解决这个问题呢?
传统解决方案是反向代理负载均衡器。具体怎么操作呢?首先,客户端会连接到代理,代理有一个服务器列表,代理能将客户端请求转发到列表中的任何服务器。所以代理就是这个解决方案中的生命线。但显然,这个代理必须是持久和安全的,它应该有一个可用服务器的大列表,并且要足够智能,能够识别健康的服务器。这也是一个问题,但是Kubernetes的设计者解决了这个问题,他们提出了一个基于平台基本功能的解决方案,在这个方案中所有的要求都得到了满足。这个解决方案的基础是从资源类型开始的,也就是所谓的服务。而本文的重点就是服务,下面我们就进入主题
如何提供服务
我们必须从理论和实践两方面理解服务的概念。你还记得吗?在Kubernetes网络系列的第一篇文章中,我们讨论过假设有两个服务,两个Pod跨Node的通讯方式。现在我想再进一步用一个新的例子来讨论它。我们将讨论在Kubernetes中,Service 是如何实现Pod之间的负载均衡的, 从而使客户端的容器能够独立,持久化的运行。首先,我先以 Deployment 创建服务端的Pod。
下面是创建服务器Pod的代码:
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: service-demo
spec:
replicas: 2
selector:
matchLabels:
app: service_demo_pod
template:
metadata:
labels:
app: service_demo_pod
spec:
containers:
- name: simple-http
image: python:2.7
imagePullPolicy: IfNotPresent
command: ["/bin/bash"]
args: ["-c", "echo \"
Hi from $(hostname)
\" > index.html; python -m SimpleHTTPServer 8080"]
ports:
- name: http
containerPort: 8080
你看到的代码, deployment 将创建两个http服务器Pod,它们在端口8080上用pod的主机名进行响应。而 deployment 则是使用kubectl apply创建的。你还可以看到集群中运行的Pod。现在让我们通过查询来验证pod网络地址。
现在是展示正在运行的pod网络的时候了。我们将创建一个简单的客户机pod来发出请求,然后查看输出。
apiVersion: v1
kind: Pod
metadata:
name: service-demo-client1
spec:
restartPolicy: Never
containers:
- name: demo-client1
image: alpine
command: ["/bin/sh"]
args: ["-c", "echo 'GET / HTTP/1.1\r\n\r\n' | nc 10.0.2.2 8080"]
pod已经创建好了。现在,我们可以运行命令来完成它。所有命令都输入完以后,pod将以完成状态输入。
我们可以在kubectl日志的帮助下检索输出。下面我们来试着检索一下。
在这段代码中,没有显示是在哪个节点上创建的client pod。但有趣的是,无论它在集群中的何处运行,它仍然能够到达服务器pod并返回响应。这种情况是可以解释通的,因为pod网络。
kind: Service
apiVersion: v1
metadata:
name: service-demo
spec:
selector:
app: service_demo_pod
ports:
- port: 80
targetPort: http
所以到底什么才是服务呢?
服务是Kubernetes资源的一种类型,它可以配置代理。上述代码中,该服务被配置为“将请求转发到一组pod”,选择器通过匹配标签(Label)来选择具体哪组Pod能够接受请求,然后流量会打到选择器选择的一组Pod上。服务一旦创建,就会立即被分配一个IP地址,然后它将开始接收端口80上的请求。
我们可以直接将请求发送到服务IP,但最好使用主机名,主机名会被解析为IP地址。方便的是Kubernetes会给我们提供了一个内部集群DNS用于解析服务名称。
通过稍微改变client pod,我们可以使用这个集群。来了解一下具体操作吧。
apiVersion: v1
kind: Pod
metadata:
name: service-demo-client2
spec:
restartPolicy: Never
containers:
- name: demo-client2
image: alpine
command: ["/bin/sh"]
args: ["-c", "echo 'GET / HTTP/1.1\r\n\r\n' | nc service-demo 80"]
这个Pod执行完成后,输出描述服务会将请求转发到一个服务器Pod。
现在,你可以运行client pod并查看来自两个server pod的输出,两个服务器都得到50%的请求。你希望真正理解这个概念吗?如果是,那么我建议你从分配给我们服务的IP地址开始了解。
接下来,要说的是服务网络的工作。
服务网络的网络化
在下面的图中,分配给每个测试服务的IP表示一个地址。上面提到的地址在网络上,但是网络不一样。你注意到了吗?Pod打开了。
如果你将上述网络与私有网络进行比较,就会发现它们是不同的。节点再次打开。pod网络地址范围没有通过kubectl公开,需要使用特定于提供程序的命令来检索集群属性。这同样也适用于服务网络地址范围。对于谷歌容器引擎,可以执行以下操作:
这个地址空间指定的网络称为服务网络,就是我们正在讨论的服务网络。集群IP下的服务将在这个网络上分配一个IP地址。除此之外,还有其他类型的服务,我们将在本系列文章的第三部分中讨论它们。现在,请理解集群IP是默认的。这意味着当服务被分配了一个IP地址,该地址就可以从集群中的任何pod被访问。
要查看服务的类型,请执行kubectl命令+服务名称
如果我们比较pod网络和服务网络,会发现它们都是虚拟的,但它们也有所不同。例如,如果将pod网络范围设为10.0.0.0/14,然后搜索组成节点的主机,你会怎么做?显然,你将会列出网桥和接口,然后在集群中搜索它们。完成搜索之后,你将看到实际设备已经配置了这个网络上的地址。那些地址是什么?它们是每个pod的虚拟以太网接口。除此之外,还有一些网桥将Pod彼此连接起来,并与外部世界相连。
接下来,我们关注一下服务网络10.3.240.0/20。为了达到目的,请执行“ifconfig”,我保证您不会在这个网络上找到任何配置了地址的设备。另外,检查网关(连接所有节点的那个)上的路由规则。但即使在那里,也找不到这个网络的任何路由。因为服务网络根本不存在,至少不是作为连接接口存在。但有趣的是,当我们向这个网络上的IP发出请求时,请求到达了我们的server pod!你会发现它在pod网络上运行,这怎么可能呢?让我们追踪这个包裹并解开这个谜团。
假设上述命令真的创建了pod,那么它们在测试集群中会是什么样子呢?
图片来源: https://cdn-images-1.medium.com/max/800/1*7Txw4Xy_...
我们有两个节点,一个用于连接节点的网关和三个Pod。网关具有Pod网络的路由规则;节点1有一个client pod 和一个server pod;节点2有另一个server pod。首先,客户端使用DNS名称服务测试向服务发出HTTP请求。请注意集群DNS,它将该名称解析为服务集群,IP地址是10.3.241.152。client pod 最终创建一个HTTP请求,这将导致在目标字段中使用该IP发送数据包。
IP网络的一个特殊或独特的特性是在意外情况下的数据包转发。因此,当一个接口由于目标地址的原因不能发送数据包时,数据包就被转发到它的上游网关。目标地址的问题通常发生在本地设备不具有指定的地址的情况下。在我们的示例中,虚拟以太网接口是在client pod中遇到数据包的第一个接口。接口位于pod网络10.0.0.0/14上。它无法查找到任何地址为10.3.241.152的设备,因此它将简单地将数据包转发到它的网关桥接cbr0,网桥不会进行验证,它们允许任何数据通过。
因此,正如我们预期的那样,网桥将把数据包发送到主机/节点的以太网接口。
图片来源: https://cdn-images-1.medium.com/max/800/1*1f98Bf_2...
你能从上图中找到主机/节点以太网接口吗?可以试着找一找。它位于网络10.100.0.0/24上,并且无法查找到任何地址为10.3.241.152的设备。因此,数据包将进入接口的网关。在图中,它是顶层路由器。从这里,数据包被重定向到一个活动的server pod。这个数据包像个流浪者一样在旅行,不是吗?
图片来源: https://cdn-images-1.medium.com/max/800/1*UetnYP8u...
整个过程似乎很神奇。由于未知的原因,客户端在不需要考虑任何接口的情况下,就能够连接到一个地址。此外,数据包会在集群中的准确到达目标地址。但这不是魔法,这是逻辑。这个逻辑是由一个称为Kube-proxy的软件提供的。
关于Kube-proxy
Kube-proxy是一个能影响集群中多个组件的配置和运行的神奇软件。根据“Kube-proxy”这个名称,我们就可以对它有一个基本的了解。proxy,代理。代理是如何工作的呢?顾名思义,代理替代被代理的事物去完成某件事情。但是它与典型的*反向代理(reverse-proxy)*有很大的不同。
代理在客户机和服务器之间传递通信。客户机将入站连接到服务器,代理将客户机出站连接到某个服务器。代理在用户空间中运行,这意味着数据包在用户空间中移动,然后返回内核空间。而所有这些,都通过代理来进行。Kube-proxy作为用户空间代理启动,但它需要一个用于监听客户机连接和连接到后端服务器的接口,而节点上可用的接口是主机接口和虚拟以太网接口。
那么为什么不在这些网络上使用地址呢?因为这不是一个好主意。为什么我这样说?因为服务需要一个稳定的、不冲突的网络空间,还需要一个虚拟IP系统。但是这个网络上没有任何实际的设备,虽然我们可以在路由规则等方面使用虚拟网络,但是他们却没有提供监听端口或通过接口打开连接的功能。
那么,Kubernetes能找到什么办法呢?办法就是使用称为Netfilter的特性和称为IPTables的用户空间接口。Netfilter是一种包处理引擎,它有自己的规则并在内核空间中运行,每个包都在不同的点被检查,数据包根据规则进行验证,并根据规则执行操作。其中一个操作是将包重定向到其他目的地。你明白了吗?Netfilter是内核空间代理的一种类型!
让我们借助下面这幅图来理解Netfilter的作用。
图片来源: https://cdn-images-1.medium.com/max/800/1*iezVQZMV...
在这幅图上,Kube-proxy在localhost接口上打开一个端口,用于侦听来自测试服务的请求。插入Netfilter规则是为了将数据包从服务IP路由到它自己的端口,然后将请求转发到端口8080上的pod。因此,10.3.241.152:80的请求神奇地变成了10.0.2.2:8080的请求。
但不仅仅是这样。由于要封送数据包,用户空间代理非常昂贵。因此,在Kubernetes 1.2中,Kube-proxy被配置为以IPTables模式运行。因此,Kube-proxy通常不再是集群间连接的代理,单调乏味的数据包检测和重定向工作会被委托给Netfilter,而所有这些委托都发生在内核空间中。因此,Kube-proxy角色被自动限制为保持Netfilter规则同步。
图片来源: https://cdn-images-1.medium.com/max/800/1*4XyIJ5Td...
到这里,我们所有的需求都被满足了,这可以帮助我们建立一个可靠的代理。但是我们还要再进行一些检查。
服务代理系统是持久的:默认情况下,Kube-proxy会作为一个系统单元运行,如果它失败,将重新启动。用户空间代理Kube-proxy表示单个点上的连接失败。因此,在IPTables模式下运行时,就尝试连接的本地pod而言,系统非常耐用。
服务代理可以检测server pod是否正常:也就是说它可以处理请求。Kube-proxy侦听master API Server,以获取集群中的任何更改,并使用IPTables来保持Netfilter规则的同步。对于新服务,Kube-proxy获取通知并创建规则,如果服务被删除,则规则将被删除。
结论
总的来说,我们讨论的所有内容都有助于创建一个高可用性的集群范围的持久工具。注意要小心处理pod之间的代理请求。但是没有一个系统是完美的,它也有一些缺点。比如,它只根据规则手册或来自集群内部的请求的描述工作。还有一个问题是关于Netfilter的,来自集群外部的请求使原始IP复杂化,目前还没有一个合适的解决方案。但是我相信开发人员和贡献者很快就会想出更好的东西!到这里,Kubernetes网络的第二部分就已经结束了。你可以在评论中分享你对这篇文章的看法。Kubernetes网络系列文章的最后一部分内容,也敬请期待。
点击