pod 分类

Pod的分类
Pod 是 kubernetes 系统的基础单元,是由用户创建或部署的最小组件,也是 kubernetes 系统上运行容器化应用的资源对象。Kubernetes 集群中其他资源对象都是为 pod 这个资源对象做支撑来实现 kubernetes 管理应用服务的目的。

自主式pod
自我管理的pod,创建以后仍然需要提交给apiserver,由apiserver接收以后借助于调度器将其调度至指定的node节点,由node启动此pod
如果此pod出现故障,需要重启容器则由kubelet来完成
如果node节点故障了,那么此pod将会消失。其无法实现全局调度。所以不推荐使用此种pod

控制器管理pod
常见的pod控制器

ReplicationController(副本控制器)
当启动一个pod时,这个pod如果不够用可以再启一个副本,而后由控制器来管理同一类pod的各种副本与对象。一旦副本少了就会自动增加。采取多退少补的规则,精确符合我们所定义的期望

支持滚动更新

ReplicaSet(复制集)
由一个名叫Deployment的声明式更新的控制器来管理

Deployment(部署)
Deployment只能管理无状态的应用

StateFulSet(状态集)
有状态副本集,可以管理有状态的应用

DaemonSet(守护程序)

如果需要在每个node上运行一个副本的时候可以用DaemonSet

job(任务)

Cronjob(定时任务)

以上每种控制器都是用来实现一钟独特的应用管理的

Kubernetes 集群组件主要包括主节点组件API Server、Controller Manager、Scheduler 以及子节点组件 kubelet、container Runtime(如docker)、kube-proxy 等。从与集群各组件交互角度讲述 pod 的创建、运行、销毁等生命周期,Pod 生命周期中的几种不同状态包括pending(待办)、running(正在运行)、succeeded(完成)、failed(失败)、Unknown(未知)。

核心组件协作流程

至此,Kubernetes中主要的组件我们都有了大致的了解。接下来,我们梳理一下在Kubernetes的全局视图下,当执行一些指令时这些组件之间是如何协作的,这样的流程解析对于读者将来对Kubernetes进行调试、排错和二次开发都是非常有帮助的。

创建pod

如图所示,当客户端发起一个创建pod的请求后,kubectl向APIServer的/pods端点发送一个HTTP POST请求,请求的内容即客户端提供的pod资源配置文件。

APIServer收到该REST API请求后会进行一系列的验证操作,包括用户认证、授权和资源配额控制等。验证通过后,APIServer调用etcd的存储接口在后台数据库中创建一个pod对象。

scheduler使用APIServer的API,定期从etcd获取/监测系统中可用的工作节点列表和待调度pod,并使用调度策略为pod选择一个运行的工作节点,这个过程也就是绑定(bind)。

绑定成功后,scheduler会调用APIServer的API在etcd中创建一个 binding对象,描述在一个工作节点上绑定运行的所有pod信息。同时kubelet会监听APIServer上pod的更新,如果发现有pod更新信息,则会自动在podWorker的同步周期中更新对应的pod。

这正是Kubernetes实现中“一切皆资源”的体现,即所有实体对象,消息等都是作为etcd里保存起来的一种资源来对待,其他所有组件间协作都通过基于APIServer的数据交换,组件间一种松耦合的状态。

创建replication controller

如图所示,当客户端发起一个创建replication controller的请求后,kubectl向APIServer的/controllers端点发送一个HTTP POST请求,请求的内容即客户端提供的replication controller资源配置文件。

与创建pod类似,APIServer收到该REST API请求后会进行一系列的验证操作。验证通过后,APIServer调用etcd的存储接口在后台数据库中创建一个replication controller对象。

controller manager会定期调用APIServer的API获取期望replication controller对象列表。再遍历期望RC对象列表,对每个RC,调用APIServer的API获取对应的pod集的实际状态信息。然后,同步replication controller的pod期望值与pod的实际状态值,创建指定副本数的pod。

创建service

如图所示,当客户端发起一个创建service的请求后,kubectl向APIServer的/services端点发送一个HTTP POST请求,请求的内容即客户端提供的service资源配置文件。

同样,APIServer收到该REST API请求后会进行一系列的验证操作。验证通过后,APIServer调用etcd的存储接口在后台数据库中创建一个service对象。

kube-proxy会定期调用APIServer的API获取期望service对象列表,然后再遍历期望service对象列表。对每个service,调用APIServer的API获取对应的pod集的信息,并从pod信息列表中提取pod IP和容器端口号封装成endpoint对象,然后调用APIServer的API在etcd中创建该对象。

  • userspace kube-proxy

    对每个新建的service, kube-proxy会为其在本地分配一个随机端口号,并相应地创建一个ProxySocket,随后使用iptablesl具在宿主机上建立一条从ServiceProxy到ProxySocket的链路。同时,kube-prxoy后台启动一个协程监听ProxySocket上的数据并根据endpoint实例的信息(例如IP,port和session affinity属性等)将来自客户端的请求转发给相应的service后端pod。

  • iptables kube-proxy

    对于每个新建的service,kube-proxy会为其创建对应的iptables。来自客户端的请求将由内核态iptables负责转发给service后端pod完成。

    最后,kube-proxy会定期调用APIServer的API获取期望service和endpoint列表并与本地的service和endpoint实例同步。

server是什么

service
假如有2个pod,pod有其生命周期,万一pod所在的节点宕机了,那么此pod将应该要在其他的节点上重建,而重建完的pod与原来的pod已经不是同一个pod了,只是两者都是运行的同一个服务而已。且每个容器都有其IP地址,重建的pod中的容器其IP地址与之前的pod中容器的IP地址是不一样的,如此一来就会存在一个问题,客户端如何访问这些pod中的容器呢?

服务发现

pod是有生命周期的,一个pod随时都有可能离去,随时都有可能会有其他pod加进来,假如它们提供的是同一种服务,客户端是无法通过固定的手段来访问这些pod的,因为pod本身是不固定的,它们随时可能被替换掉,无论使用主机名还是P地址,都随时会被替换掉。

为了尽可能的降低客户端与pod问协调的复杂度,k8s为每-组提供同类服务的pod和其客户端之间添加了一个中间层,这个中间层是固定的,这+中间层就叫service

service只要不被删除,其地址与名称皆是固定的,当客户端需要在其配置文件中写上访问某个服务时,它不再需要自动发现,只需要在配置文件中写明service的名称即可,而这个service是个调度器,其不但能够提供一个稳定的访问入口,还可以做反向代理,当service接收到客户端的请求后,会将其代理到后端的pod之上,一旦pod宕机了会立即新建一个pod,这个新建的pod会立即被service关联上,作为service后端的可用pod之一

客户端程序访问服务都是通过IP+端口或者主机名+端口的方式来实现的。而service关联后端的pod不是靠它的IP和主机名,而是靠pod的标签选择器。只要创建的pod的label是统一的,无论IP地址和主机如何改变,其都能被service所识别。如此一来,只要pod属于标签选择器,只要其在service的管理范围之内,则其就会被关联到service中,当这个动态的pod关联到service中之后,再进行动态的探测此pod的IP地址、端口,再将其作为自己后端可调度的可用服务器主机对象。因此,客户端的请求发送到service,然后由service代理到后端真实的pod中的容器进行响应。

service不是一个程序,也不是一个组件,它只是一个iptables的dnat规则

service作为k8s的对象,有其自身的名称,而service的名称相当于服务的名称,而这个名称可以被解析
addOns附件

dns pod

装完k8s后第一件事就需要在k8s集群上部署一个dns pod,以确保各service的名称能够被解析

可以动态改变,包括动态创建、动态删除、动态修改

比如把service的名称改了,dnspod会自动触发,将dns解析记录中的名称也给改掉;假如我们手动把service的ip地址给改了,改完以后会自动触发,将dns服务中的解析记录给改掉。

如此一来,客户端去访问pod资源的时候可以直接访问service的名称,然后由集群中专用的dns服务来负责解析。

这种pod是k8s自身的服务就需要用到的pod,所以我们把它称为基础性的系统架构级的pod对象,而且它们也被称为集群附件

Kubernetes 网络模型

Kubernetes 对 Pod 的联网方式做出了固执的选择。特别是,Kubernetes 规定了对任何网络实现的以下要求:

  • 所有 Pod 都可以与所有其他 Pod 通信,而无需使用网络地址转换(NAT)。
  • 所有节点都可以在没有 NAT 的情况下与所有 Pod 通信。
  • Pod 认为自己的 IP 与其他人认为的 IP 相同。

鉴于这些限制,我们需要解决四个不同的网络问题:

  1. 容器到容器网络
  2. Pod 到 Pod 网络
  3. Pod-to-Service 网络
  4. 互联网到服务网络

本指南的其余部分将依次讨论每个问题及其解决方案。

1 容器到容器网络

通常,我们将虚拟机中的网络通信视为直接与以太网设备交互,如图 1 所示。

eth0.png

图 1. 以太网设备的理想化视图。

实际上,情况比这更微妙。在 Linux 中,每个正在运行的进程都在一个网络命名空间内进行通信,该命名空间提供了一个具有自己的路由、防火墙规则和网络设备的逻辑网络堆栈。本质上,网络命名空间为命名空间内的所有进程提供了一个全新的网络堆栈。

作为 Linux 用户,可以使用该ip命令创建网络命名空间。例如,以下命令将创建一个名为ns1.

$ ip netns add ns1

创建命名空间时,会在 下为其创建一个挂载点, /var/run/netns即使没有附加进程,命名空间也能持久存在。

您可以通过列出 下的所有挂载点/var/run/netns或使用ip命令来列出可用的命名空间 。

$ ls /var/run/netns
ns1
$ ip netns
ns1

默认情况下,Linux 将每个进程分配给根网络命名空间以提供对外部世界的访问,如图 2 所示。

根命名空间.png

图 2. 根网络命名空间。

就 Docker 构造而言,Pod 被建模为一组共享网络命名空间的 Docker 容器。Pod 内的容器都具有通过分配给 Pod 的网络命名空间分配的相同 IP 地址和端口空间,并且可以通过 localhost 找到彼此,因为它们驻留在相同的命名空间中。我们可以为虚拟机上的每个 Pod 创建一个网络命名空间。这是使用 Docker 实现的,作为一个“Pod 容器”,它保持网络命名空间打开,而“应用容器”(用户指定的东西)使用 Docker 的 –net=container 加入该命名空间:功能。图 3 显示了每个 Pod 如何由ctr*共享命名空间内的多个 Docker 容器 ( ) 组成。

pod-namespace.png

图 3. 每个 Pod 的网络命名空间。

Pod 中的应用程序还可以访问共享卷,这些卷被定义为 Pod 的一部分,并且可以挂载到每个应用程序的文件系统中。

2Pod 到 Pod 网络

在 Kubernetes 中,每个 Pod 都有一个真实的 IP 地址,每个 Pod 使用该 IP 地址与其他 Pod 通信。手头的任务是了解 Kubernetes 如何使用真实 IP 实现 Pod 到 Pod 通信,无论 Pod 部署在同一物理节点上还是集群中的不同节点上。我们通过考虑驻留在同一台机器上的 Pod 来开始这个讨论,以避免通过内部网络跨节点通信的复杂性。

从 Pod 的角度来看,它存在于自己的以太网命名空间中,需要与同一节点上的其他网络命名空间进行通信。值得庆幸的是,命名空间可以使用 Linux虚拟以太网设备或 由两个虚拟接口组成的veth 对连接,这些虚拟接口可以分布在多个命名空间中。为了连接 Pod 命名空间,我们可以将 veth 对的一侧分配给根网络命名空间,另一侧分配给 Pod 的网络命名空间。每对 veth 都像跳线一样工作,连接两侧并允许流量在它们之间流动。可以为机器上的尽可能多的 Pod 复制此设置。图 4 显示了将 VM 上的每个 Pod 连接到根命名空间的 veth 对。

pod-veth-pairs.png

图 4. 每个 Pod 的 veth 对。

在这一点上,我们已经将 Pod 设置为每个都有自己的网络命名空间,以便它们相信自己拥有自己的以太网设备和 IP 地址,并且它们连接到节点的根命名空间。现在,我们希望 Pod 通过根命名空间相互通信,为此我们使用网桥

Linux 以太网桥是一个虚拟的第 2 层网络设备,用于将两个或多个网段联合起来,透明地将两个网络连接在一起。网桥通过检查通过它的数据包的目的地并决定是否将数据包传递到连接到网桥的其他网段来维护源和目的地之间的转发表。桥接代码通过查看网络中每个以太网设备唯一的 MAC 地址来决定是桥接数据还是丢弃数据。

网桥实现 ARP协议以发现与给定 IP 地址关联的链路层 MAC 地址。当网桥接收到数据帧时,网桥将帧广播到所有连接的设备(原始发送方除外),响应该帧的设备存储在查找表中。具有相同 IP 地址的未来流量使用查找表来发现要将数据包转发到的正确 MAC 地址。

pods-connected-by-bridge.png

图 5. 使用桥连接命名空间。

2.1 数据包的生命周期:Pod-to-Pod,同一个节点

给定将每个 Pod 与其自己的网络堆栈隔离的网络命名空间、将每个命名空间连接到根命名空间的虚拟以太网设备以及将命名空间连接在一起的网桥,我们终于准备好在同一节点上的 Pod 之间发送流量。如图 6 所示。

pod-to-pod-same-node.gif

图 6. 数据包在同一节点上的 Pod 之间移动。

在图 6 中,Pod 1 向其自己的以太网设备发送一个数据包,该设备eth0可用作 Pod 的默认设备。对于 Pod 1,eth0通过虚拟以太网设备连接到根命名空间veth0 (1)。网桥cbr0配置了veth0一个附加到它的网段。一旦数据包到达网桥,网桥就会解析正确的网段以将数据包发送到 -veth1使用 ARP 协议 (3)。当数据包到达虚拟设备时veth1,它被直接转发到 Pod 2 的命名空间和该eth0命名空间内的设备 (4)。在整个交通流,每个吊舱只与沟通eth0localhost并且流量被路由到正确的 Pod。使用网络的开发体验是开发人员所期望的默认行为。

Kubernetes 的网络模型规定 Pod 必须可以通过其 IP 地址节点访问。也就是说,一个 Pod 的 IP 地址始终对网络中的其他 Pod 可见,并且每个 Pod 看到自己的 IP 地址与其他 Pod 看到它的方式相同。我们现在转向在不同节点上的 Pod 之间路由流量的问题。

2.2 数据包的生命周期:Pod-to-Pod,跨节点

确定了如何在同一节点上的 Pod 之间路由数据包后,我们继续在不同节点上的 Pod 之间路由流量。Kubernetes 网络模型要求 Pod IP 可以通过网络访问,但它没有指定必须如何完成。在实践中,这是特定于网络的,但已经建立了一些模式来使这更容易。

通常,集群中的每个节点都会分配一个 CIDR 块,指定可用于该节点上运行的 Pod 的 IP 地址。一旦以 CIDR 块为目的地的流量到达节点,节点就有责任将流量转发到正确的 Pod。图 7 说明了两个节点之间的流量流,假设网络可以将 CIDR 块中的流量路由到正确的节点

pod-to-pod-不同节点.gif

图 7. 在不同节点上的 Pod 之间移动的数据包。

图 7 以与图 6 中相同的请求开始,除了这次,目标 Pod(以绿色突出显示)与源 Pod(以蓝色突出显示)位于不同的节点上。数据包首先通过 Pod 1 的以太网设备发送,该设备与根命名空间 (1) 中的虚拟以太网设备配对。最终,数据包最终到达根命名空间的网桥 (2)。ARP 将在网桥上失败,因为没有设备连接到具有数据包正确 MAC 地址的网桥。失败时,网桥将数据包发送到默认路由——根命名空间的eth0设备。此时路由离开节点并进入网络(3)。我们现在假设网络可以根据分配给节点的 CIDR 块将数据包路由到正确的节点 (4)。数据包进入目标节点的根命名空间(eth0在 VM 2 上),在那里它通过网桥路由到正确的虚拟以太网设备 (5)。最后,路由通过位于 Pod 4 命名空间 (6) 内的虚拟以太网设备对完成。一般来说,每个节点都知道如何将数据包传送到运行在其中的 Pod。一旦数据包到达目标节点,数据包的流动方式与它们在同一节点上的 Pod 之间路由流量的方式相同。

我们方便地回避了如何配置网络以将 Pod IP 的流量路由到负责这些 IP 的正确节点。这是特定于网络的,但查看特定示例将有助于深入了解所涉及的问题。例如,对于 AWS,亚马逊为 Kubernetes 维护了一个容器网络插件,允许节点到节点网络使用 [容器网络接口 (CNI) 插件] ( https://github.com/aws/amazon -vpc-cni-k8s))。

容器网络接口 (CNI) 提供了用于将容器连接到外部网络的通用 API。作为开发人员,我们想知道 Pod 可以使用 IP 地址与网络通信,并且我们希望此操作的机制是透明的。AWS 开发的 CNI 插件试图满足这些需求,同时通过 AWS 提供的现有 VPC、IAM 和安全组功能提供安全且可管理的环境。解决方案是使用弹性网络接口。

在 EC2 中,每个实例都绑定到一个弹性网络接口 (ENI),并且所有 ENI 都连接在一个 VPC 内——ENI 无需额外工作即可相互访问。默认情况下,每个 EC2 实例都使用单个 ENI 部署,但您可以自由创建多个 ENI 并将它们部署到您认为合适的 EC2 实例。适用于 Kubernetes 的 AWS CNI 插件通过为部署到节点的每个 Pod 创建新的 ENI 来利用这种灵活性。由于 VPC 内的 ENI 已经在现有 AWS 基础设施内连接,这允许每个 Pod 的 IP 地址在 VPC 内本地可寻址。当 CNI 插件部署到集群时,每个节点(EC2 实例)创建多个弹性网络接口并为这些实例分配 IP 地址,为每个节点形成一个 CIDR 块。部署 Pod 时,kubelet过程。这个二进制文件从节点的可用 ENI 池中选择一个可用的 IP 地址,并通过在 Linux 内核中连接虚拟以太网设备和桥接器将其分配给 Pod,如在同一节点内联网 Pod 时所述。有了这个,Pod 流量就可以在集群内的节点之间路由。

3 Pod-to-Service 网络

我们已经展示了如何在 Pod 及其关联的 IP 地址之间路由流量。这很有效,直到我们需要应对变化。Pod IP 地址不是持久的,并且会随着扩展或缩小、应用程序崩溃或节点重新启动而出现和消失。这些事件中的每一个都可以在没有警告的情况下使 Pod IP 地址发生变化。服务被内置到Kubernetes来解决这个问题。

Kubernetes服务管理一组 Pod 的状态,允许您跟踪一组随时间动态变化的 Pod IP 地址。服务充当 Pod 的抽象,并将单个虚拟 IP 地址分配给一组 Pod IP 地址。任何寻址到服务的虚拟 IP 的流量都将路由到与虚拟 IP 关联的一组 Pod。这允许与 Service 关联的一组 Pod 随时更改——客户端只需要知道 Service 的虚拟 IP,它不会更改。

创建新的 Kubernetes 服务时,会代表您创建一个新的虚拟 IP(也称为集群 IP)。在集群内的任何地方,寻址到虚拟 IP 的流量都将负载平衡到与服务关联的一组支持 Pod。实际上,Kubernetes 会自动创建和维护一个分布式集群内负载均衡器,该负载均衡器将流量分配到服务相关的健康 Pod。让我们仔细看看这是如何工作的。

3.1 netfilter 和 iptables

为了在集群内执行负载均衡,Kubernetes 依赖于 Linux 内置的网络框架 — netfilter. Netfilter 是 Linux 提供的一个框架,它允许以自定义处理程序的形式实现各种与网络相关的操作。Netfilter 为数据包过滤、网络地址转换和端口转换提供了各种功能和操作,这些功能和操作提供了引导数据包通过网络所需的功能,以及提供禁止数据包到达计算机网络内敏感位置的能力。

iptables是一个用户空间程序,提供基于表的系统,用于定义使用 netfilter 框架操作和转换数据包的规则。在 Kubernetes 中,iptables 规则由 kube-proxy 控制器配置,该控制器监视 Kubernetes API 服务器的变化。当对 Service 或 Pod 的更改更新 Service 的虚拟 IP 地址或 Pod 的 IP 地址时,iptables 规则会更新以正确地将指向 Service 的流量路由到支持 Pod。iptables 规则监视发往 Service 虚拟 IP 的流量,并在匹配时从可用 Pod 集中选择一个随机 Pod IP 地址,iptables 规则将数据包的目标 IP 地址从 Service 的虚拟 IP 更改为选定的 Pod。随着 Pod 的上升或下降,iptables 规则集被更新以反映集群的变化状态。换句话说,iptables 已经在机器上进行了负载平衡,以将定向到服务 IP 的流量转移到实际 pod 的 IP。

在返回路径上,IP 地址来自目标 Pod。在这种情况下,iptables 再次重写 IP 标头,用服务的 IP 替换 Pod IP,以便 Pod 相信它一直只与服务的 IP 通信。

3.2 IPVS

Kubernetes 的最新版本 (1.11) 包括用于集群内负载平衡的第二个选项:IPVS。IPVS(IP 虚拟服务器)也建立在 netfilter 之上,并作为 Linux 内核的一部分实现传输层负载平衡。IPVS 被整合到 LVS(Linux 虚拟服务器)中,它在主机上运行并充当真实服务器集群前面的负载平衡器。IPVS 可以将基于 TCP 和 UDP 的服务请求定向到真实服务器,并使真实服务器的服务在单个 IP 地址上表现为虚拟服务。这使得 IPVS 非常适合 Kubernetes 服务。

在声明 Kubernetes Service 时,您可以指定是否希望使用 iptables 或 IPVS 完成集群内负载平衡。IPVS 专为负载平衡而设计,并使用更高效的数据结构(哈希表),与 iptables 相比几乎可以无限扩展。创建一个使用 IPVS 负载均衡的 Service 时,会发生三件事:在 Node 上创建一个虚拟 IPVS 接口,Service 的 IP 地址绑定到虚拟 IPVS 接口,并为每个 Service IP 地址创建 IPVS 服务器。

未来,预计 IPVS 将成为集群内负载均衡的默认方法。此更改仅影响集群内负载平衡,并且在本指南的其余部分中,您可以安全地将 iptables 替换为 IPVS 以进行集群内负载平衡,而不会影响其余讨论。现在让我们通过集群内负载平衡服务来看看数据包的生命周期

3.3 数据包的生命周期:Pod 到 Service

pod-to-service.gif

图 8. 在 Pod 和服务之间移动的数据包。

在 Pod 和 Service 之间路由数据包时,旅程以与以前相同的方式开始。数据包首先通过eth0 连接到 Pod 网络命名空间 (1)的接口离开 Pod 。然后它穿过虚拟以太网设备到达网桥 (2)。在网桥上运行的 ARP 协议不知道服务,因此它通过默认路由将数据包传输出去 - eth0(3)。在这里,发生了一些不同的事情。在被接受之前eth0,数据包通过iptables过滤。iptables 收到数据包后,使用 kube-proxy 安装在 Node 上的规则响应 Service 或 Pod 事件,将数据包的目的地从 Service IP 重写为特定的 Pod IP(4)。数据包现在注定要到达 Pod 4,而不是服务的虚拟 IP。conntrackiptables 利用Linux 内核的实用程序来记住所做的 Pod 选择,以便将来的流量路由到同一个 Pod(除非发生任何扩展事件)。本质上,iptables 已经直接在 Node 上做了集群内负载均衡。然后流量使用我们已经检查过的 Pod 到 Pod 路由流到 Pod (5)。

3.4 数据包的生命周期:服务到 Pod

服务到 pod.gif

图 9. 在服务和 Pod 之间移动的数据包。

接收此数据包的 Pod 将做出响应,将源 IP 标识为自己的 IP,将目标 IP 标识为最初发送数据包的 Pod (1)。进入节点后,数据包流经 iptables,iptablesconntrack用于记住它之前所做的选择,并将数据包的源重写为服务的 IP,而不是 Pod 的 IP (2)。从这里,数据包通过网桥流向与 Pod 的命名空间配对的虚拟以太网设备 (3),以及我们之前看到的 Pod 的以太网设备 (4)。

3.5 使用 DNS

Kubernetes 可以选择使用 DNS 来避免将服务的集群 IP 地址硬编码到您的应用程序中。Kubernetes DNS 作为在集群上调度的常规 Kubernetes 服务运行。它配置 kubelets在每个节点上运行,以便容器使用 DNS 服务的 IP 来解析 DNS 名称。集群中定义的每个服务(包括 DNS 服务器本身)都分配了一个 DNS 名称。DNS 记录将 DNS 名称解析为服务的集群 IP 或 POD 的 IP,具体取决于您的需要。SRV 记录用于指定用于运行服务的特定命名端口。

DNS Pod 由三个独立的容器组成:

  • kubedns: 监视 Kubernetes 主节点的服务和端点的变化,并维护内存中的查找结构来为 DNS 请求提供服务。
  • dnsmasq: 添加 DNS 缓存以提高性能。
  • sidecar: 提供一个单一的健康检查端点来为dnsmasq和执行健康检查kubedns

DNS Pod 本身作为 Kubernetes 服务公开,并在启动时将静态集群 IP 传递给每个正在运行的容器,以便每个容器都可以解析 DNS 条目。DNS 条目通过kubedns维护内存中 DNS 表示的系统进行解析。etcd 是集群状态的后端存储系统,并kubedns使用一个库将etcd键值存储转换为 DNS 整体,以在必要时重建内存中 DNS 查找结构的状态。

CoreDNS 的工作原理与此类似,kubedns但采用插件架构构建,使其更加灵活。从 Kubernetes 1.11 开始,CoreDNS 是 Kubernetes 的默认 DNS 实现。

4 互联网到服务网络

到目前为止,我们已经了解了如何Kubernetes 集群中路由流量。这一切都很好,但不幸的是,将您的应用程序与外部世界隔离开将无助于实现任何销售目标——在某些时候,您可能希望将您的服务暴露给外部流量。这种需求突出了两个相关的问题:(1) 将流量从 Kubernetes Service 获取到 Internet,以及 (2) 从 Internet 获取流量到您的 Kubernetes Service。本节依次处理这些问题中的每一个。

4.1 出口——将流量路由到互联网

将流量从节点路由到公共 Internet 是特定于网络的,并且实际上取决于您的网络如何配置以发布流量。为了使本节更加具体,我将使用 AWS VPC 来讨论任何具体细节。

在 AWS 中,Kubernetes 集群在 VPC 内运行,其中每个节点都分配有一个私有 IP 地址,该地址可从 Kubernetes 集群内部访问。为了使流量可以从集群外部访问,您可以将 Internet 网关附加到您的 VPC。Internet 网关有两个用途:在 VPC 路由表中为可路由到 Internet 的流量提供目标,以及为已分配公共 IP 地址的任何实例执行网络地址转换 (NAT)。NAT 转换负责将节点的集群私有的内部 IP 地址更改为公共 Internet 中可用的外部 IP 地址。

有了 Internet 网关,VM 就可以自由地将流量路由到 Internet。不幸的是,有一个小问题。Pod 有自己的 IP 地址,这与托管 Pod 的节点的 IP 地址不同,Internet 网关处的 NAT 转换仅适用于 VM IP 地址,因为它不知道 Pod 在什么上运行哪些虚拟机——网关不知道容器。让我们再看看 Kubernetes 如何使用 iptables 解决这个问题。

4.1.1 数据包的生命周期:节点到互联网

在下图中,数据包源自 Pod 的命名空间 (1),并穿过连接到根命名空间 (2) 的 veth 对。一旦进入根命名空间,数据包就会从网桥移动到默认设备,因为数据包上的 IP 与连接到网桥的任何网段都不匹配。在到达根命名空间的以太网设备 (3) 之前,iptables 会破坏数据包 (3)。在这种情况下,数据包的源 IP 地址是一个 Pod,如果我们将源保留为 Pod,Internet 网关将拒绝它,因为网关 NAT 只识别连接到 VM 的 IP 地址。解决方案是让 iptables 执行源 NAT - 更改数据包源 - 使数据包看起来来自 VM 而不是 Pod。使用正确的源 IP,数据包现在可以离开 VM (4) 并到达 Internet 网关 (5)。Internet 网关将执行另一个 NAT,将源 IP 从 VM 内部 IP 重写为外部 IP。最后,数据包将到达公共互联网 (6)。在返回的路上,数据包遵循相同的路径,并取消任何源 IP 修改,以便系统的每一层都收到它理解的 IP 地址:节点或 VM 级别的 VM 内部,以及 Pod 内部的 Pod IP命名空间。

pod-to-internet.gif

图 10. 将数据包从 Pod 路由到 Internet。

4.2 Ingress——将互联网流量路由到Kubernetes

Ingress——让流量进入你的集群——是一个非常难解决的问题。同样,这特定于您正在运行的网络,但一般来说,Ingress 分为两种解决方案,分别适用于网络堆栈的不同部分:(1) Service LoadBalancer 和 (2) Ingress 控制器。

4.2.1 第 4 层 Ingress:负载均衡器

创建 Kubernetes 服务时,您可以选择指定一个 LoadBalancer 来使用它。LoadBalancer 的实现由知道如何为您的服务创建负载均衡器的云控制器提供。创建您的服务后,它将为负载均衡器通告 IP 地址。作为最终用户,您可以开始将流量定向到负载均衡器以开始与您的服务进行通信。

借助 AWS,负载均衡器可以了解其目标组中的节点,并将平衡集群中所有节点的流量。一旦流量到达节点,之前在整个集群中为您的服务安装的 iptables 规则将确保流量到达您感兴趣的服务的 Pod。

4.2.2 数据包的生命周期:LoadBalancer 到 Service

让我们看看这在实践中是如何工作的。部署服务后,您正在使用的云提供商将为您创建一个新的负载均衡器 (1)。因为负载均衡器不知道容器,所以一旦流量到达负载均衡器,它就会分布在组成集群的虚拟机中 (2)。每个 VM 上的 iptables 规则会将传入流量从负载均衡器引导到正确的 Pod (3) — 这些是在服务创建期间放置并在前面讨论过的相同 IP 表规则。从 Pod 到客户端的响应将返回 Pod 的 IP,但客户端需要有负载均衡器的 IP 地址。iptables 并conntrack用于在返回路径上正确重写 IP,正如我们之前看到的。

下图显示了托管 Pod 的三个 VM 前面的网络负载平衡器。传入流量 (1) 被定向到您的服务的负载均衡器。一旦负载均衡器接收到数据包 (2),它就会随机选择一个 VM。在这种情况下,我们病态地选择了没有运行 Pod 的 VM:VM 2 (3)。在这里,运行在 VM 上的 iptables 规则将使用 kube-proxy 安装到集群中的内部负载平衡规则将数据包定向到正确的 Pod。iptables 执行正确的 NAT 并将数据包转发到正确的 Pod (4)。

互联网到服务.gif

图 11. 从 Internet 发送到服务的数据包。

4.2.3 七层入口:入口控制器

第 7 层网络 Ingress 在网络堆栈的 HTTP/HTTPS 协议范围内运行,并建立在服务之上。启用 Ingress 的第一步是使用NodePortKubernetes 中的服务类型在您的服务上打开一个端口。如果您将 Service 的 type 字段设置为 NodePort,Kubernetes 主节点将从您指定的范围中分配一个端口,并且每个节点都会将该端口(每个节点上的相同端口号)代理到您的服务中。也就是说,任何定向到节点端口的流量都将使用 iptables 规则转发到服务。服务到 Pod 的路由遵循我们在将流量从服务路由到 Pod 时已经讨论过的相同内部集群负载平衡模式。

要向 Internet 公开节点的端口,您可以使用 Ingress 对象。Ingress 是一个更高级别的 HTTP 负载均衡器,它将 HTTP 请求映射到 Kubernetes 服务。Ingress 方法将根据 Kubernetes 云提供商控制器的实现方式而有所不同。HTTP 负载均衡器,如第 4 层网络负载均衡器,只了解节点 IP(而非 Pod IP),因此流量路由类似地利用 kube-proxy 安装在每个节点上的 iptables 规则提供的内部负载均衡。

在 AWS 环境中,ALB Ingress Controller 使用 Amazon 的第 7 层应用程序负载均衡器提供 Kubernetes Ingress。下图详细说明了此控制器创建的 AWS 组件。它还演示了入口流量从 ALB 到 Kubernetes 集群的路由。

入口控制器设计.png

图 12. 入口控制器的设计。

创建时,(1) 入口控制器监视来自 Kubernetes API 服务器的入口事件。当它找到满足其要求的 Ingress 资源时,它就会开始创建 AWS 资源。AWS 对入口资源使用应用程序负载均衡器 (ALB) (2)。负载均衡器与用于将请求路由到一个或多个注册节点的目标组协同工作。(3) 在 AWS 中为 Ingress 资源描述的每个唯一 Kubernetes 服务创建目标组。(4) 侦听器是一个 ALB 进程,它使用您配置的协议和端口检查连接请求。侦听器由 Ingress 控制器为 Ingress 资源注释中详述的每个端口创建。最后,为 Ingress 资源中指定的每个路径创建目标组规则。

4.2.4 数据包的生命周期:入口到服务

流经 Ingress 的数据包的生命周期与 LoadBalancer 的生命周期非常相似。主要区别在于 Ingress 知道 URL 的路径(允许并可以根据其路径将流量路由到服务),并且 Ingress 和节点之间的初始连接是通过节点上为每个服务公开的端口。

让我们看看这在实践中是如何工作的。部署服务后,您正在使用的云提供商将为您创建一个新的 Ingress 负载均衡器 (1)。因为负载均衡器不知道容器,所以一旦流量到达负载均衡器,它就会通过服务的通告端口分布在组成集群 (2) 的虚拟机中。每个 VM 上的 iptables 规则会将传入流量从负载均衡器定向到正确的 Pod (3) — 正如我们之前所见。从 Pod 到客户端的响应将返回 Pod 的 IP,但客户端需要有负载均衡器的 IP 地址。iptables 并 conntrack用于在返回路径上正确重写 IP,正如我们之前看到的。

入口到服务.gif

图 13. 从入口发送到服务的数据包。

第 7 层负载平衡器的一个好处是它们可以识别 HTTP,因此它们知道 URL 和路径。这允许您按 URL 路径分割服务流量。它们通常还在X-Forwarded-ForHTTP 请求的标头中提供原始客户端的 IP 地址。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐