Kubernetes 网络模型
谈到 Kubernetes 的网络模型, 就不能不提它著名的 "单 Pod 单 IP" 模型, 即每个 Pod 都有一个独立的 IP,Pod 内所有容器共享网络 namespace(同一个网络协议栈和 IP)."单 Pod 单 IP" 网络模型为我们勾勒了一个 Kubernetes 扁平网络的蓝图, 在这个网络世界里: 容器之间直接通信, 不需要额外的 NAT(网络地址转换);Node 与容器之间, 同样不需要额外的 NAT; 在其他容器和容器自身看到的 IP 也一样的. 扁平化网络的优点在于: 没有 NAT 的性能损耗, 可追溯源地址进而为后面的网络策略做铺垫, 易排错等.
总体而言, 如果集群内要访问 Pod, 走 Service, 至于集群外要访问 Pod, 走的是 Ingress.Service 和 Ingress 是 Kubernetes 专门的抽象出来的和服务发现相关的概念, 后面会做详细讨论.
类似于 CRI 之于 Kubernetes 的 Runtime,Kubernetes 使用 CNI(Container Network Interface)作为 Pod 网络配置的标准接口. 需要注意的是, CNI 并不支持 Docker 网络, 也就是说 docker0 网桥会被 CNI 的各类插件 "视而不见".
上图描绘了当用户在 Kubernetes 里创建了一个 Pod 后, CRI 和 CNI 协同创建所有容器并为他们初始化网络栈的全过程.
具体过程如下: 当用户在 Kubernetes 的 Master 那边创建了一个 Pod 后, Kubelet 观察到新 Pod 的创建, 于是首先调用 CRI(后面的 Runtime 实现, 比如: dockershim,containerd 等)创建 Pod 内的若干个容器. 在这些容器里面, 第一个被创建的 Pause 容器是比较特殊的, 这是 Kubernetes 系统 "赠送" 的容器, 里面跑着一个功能十分简单的 Go 语言程序, 具体逻辑是一启动就去 select 一个空的 Go 语言 channel, 自然就永远阻塞在那里了. 一个永远阻塞而且没有实际业务逻辑的 pause 容器到底有什么用呢? 用处大了. 我们知道容器的隔离功能利用的是 Linux 内核的 namespace 机制, 而只要是一个进程, 不管这个进程是否处于运行状态(挂起亦可), 它都能 "占" 着一个 namespace. 因此, 每个 Pod 内的第一个系统容器 Pause 的作用就是为占用一个 Linux 的 network namespace, 而 Pod 内其他用户容器通过加入到这个 network namespace 的方式来共享同一个 network namespace. 用户容器和 Pause 容器之间的关系有点类似于寄居蟹和海螺的关系.
因此, Container Runtime 创建 Pod 内所有容器时, 调用的都是同一个命令:
$ docker run --net=none
意思是只创建一个 network namespace, 而不初始化网络协议栈. 如果这个时候通过 nsenter 方式进入到容器, 会看到里面只有一个本地回环设备 lo. 那么容器的 eth0 是怎么创建出来的呢? 答案是 CNI.
CNI 主要负责容器的网络设备初始化工作. Kubelet 目前支持两个网络驱动, 分别是: kubenet 和 CNI.
Kubenet 是一个历史产物, 即将废弃, 因此这里也不准备过多介绍. CNI 有多个实现, 官方自带的插件就有 p2p,bridge 等, 这些插件负责初始化 Pause 容器的网络设备, 也就是给 eth0 分配 IP 等, 到时候 Pod 内其他容器就用这个 IP 与外界通信. Flanne,Calico 这些第三方插件解决 Pod 之间的跨机通信问题. 容器之间的跨机通信, 可以通过 bridge 网络或 overlay 网络来完成.
上图是一个 bridge 网络的示意图. Node1 上 Pod 的网段是 10.1.1.0/24, 接的 Linux 网桥是 10.1.1.1,Node2 上 Pod 的网段是 10.1.2.0/24, 接的 Linux 网桥是 10.1.2.1, 接在同一个网桥上的 Pod 通过局域网广播通信. 我们发现, Node1 上的路由表的第二条是:
10.1.1.0/24 dev cni0
意思是所有目的地址是本机上 Pod 的网络包, 都发到 cni0 这个 Linux 网桥去, 进而广播给 Pod.
注意看第三条路由规则:
10.1.2.0/24 via 192.168.1.101
10.1.2.0/24 是 Node2 上 Pod 的网段, 192.168.1.101 又恰好是 Node2 的 IP. 意思是, 目的地址是 10.1.2.0/24 的网络包, 发到 Node2 上.
这时候我们观察 Node2 上面的第二条路由信息:
10.1.2.0/24 dev cni0
就会知道这个包会被接着发给 Node2 上的 Linux 网桥 cni0, 然后再广播给目标 Pod. 回程报文同理走一条逆向的路径. 因此, 我们可以得出一个小小的结论: bridge 网络本身不解决容器的跨机通信问题, 需要显式地书写主机路由表, 映射目标容器网段和主机 IP 的关系, 集群内如果有 N 个主机, 需要 N-1 条路由表项.
至于 overlay 网络, 它是构建在物理网络之上的一个虚拟网络, 其中 VXLAN 是当前最主流的 overlay 标准. VXLAN 就是用 UDP 包头封装二层帧, 即所谓的 Mac in UDP.
上图即一个典型 overlay 网络的拓扑图. 和 bridge 网路类似, Pod 同样接在 Linux 网桥上, 目的地址是本机 Pod 的网络包同样发给 Linux 网桥 cni0. 不一样的是, 目的 Pod 在其他节点上的路由表规则, 例如:
10.1.0.0/16 dev tun0
这次是直接发给本机的 TAP 设备 tun0, 而 tun0 就是 overlay 隧道网络的入口. 我们注意到, 集群内所有机器都只需要这么一条路由表, 而不需要像 bridge 网络那样, 写 N-1 条路由表项. 那如何才能将网络包正确地传递到目标主机的隧道口另一端呢? 一般情况下, 例如 flannel 的实现, 会借助一个分布式的数据库, 用于记录目的容器 IP 与所在主机的 IP 的映射关系, 而且每个节点上都会运行一个 agent, 例如 flanneld, 会监听在 tun0 上, 进行封包和解包操作. 例如: Node1 上的容器发包给 Node2 上的容器, flanneld 会在 tun0 处将一个目的地址是 192.168.1.101:8472 的 UDP 包头 (校验和置成 0) 封装到这个包的外层, 然后接着主机网络的东风顺利到达 Node2. 监听在 Node2 的 tun0 上的 flanneld 捕获这个特殊的 UDP 包(检验和为 0), 知道这是一个 overlay 的封包, 于是解开 UDP 包头, 将它发给本机的 Linux 网桥 cni0, 进而广播给目的容器.
什么是 CNI
CNI 是 Container Network Interface 的缩写, 是容器网络的标准化, 试图通过 JSON 来描述一个容器网络配置.
从上图可以看出, CNI 是 Kubernetes 与底层网络插件之间的一个抽象层, 为 Kubernetes 屏蔽了底层网络实现的复杂度, 同时也解耦了 Kubernetes 的具体网络插件实现.
CNI 主要有两类接口, 分别是在创建容器时调用的配置网络接口: AddNetwork.NET NetworkConfig, rt RuntimeConf) (types.Result,error)和删除容器时调用的清理网络接口: DelNetwork.NET NetworkConfig, rt RuntimeConf).
不论是配置网络接口还是删除网络接口, 都有两个入参, 分别是网络配置和 runtime 配置. 网络配置很好理解, Rumtime 配置则主要是容器运行时传入的网络 namespace 信息. 符合 CNI 标准的默认 / 第三方网络插件有:
其中 CNI-Genie 是一个开源的多网络的容器解决方案, 感兴趣的读者可以自行去 GitHub 上搜索.
下面我们将举几个 CNI 网络插件的例子.
上图是一个 host-local + bridge 插件组合的例子, 在这么一个 JSON 文件中, 我们定义了一个名为 mynet 的网络, 是一个 bridge 模型, 而 IP 地址管理 (ipam) 使用的是 host-local(在本地用一个文件记录已经分配的容器 IP 地址)且可供分配的容器网段是 10.10.0.0/16. 至于 Kubernetes 如何使用它们? Kubelet 和 CNI 约好了两个默认的文件系统路径, 分别是 / etc/cni.NET.d 用来存储 CNI 配置文件和 / opt/cni/bin 目录用来存放 CNI 插件的二进制文件, 在我们这个例子中, 至少要提前放好 bridge 和 host-local 这两个插件的二进制, 以及 10-mynet.conf 配置文件(叫什么名字随意, Kubelet 只解析 *.conf 文件). 由于主流的网络插件都集成了 bridge 插件并提供了各自的 ipam 功能, 因此在实际 Kubernetes 使用过程中我们并不需要操心上面过程, 也无需做额外配置.
再来看一个最近 Kubernetes V1.11 版本合到社区主干的带宽控制插件的使用方法. 当我们书写了以下 Pod 配置时:
Kubernetes 就会自动为我们这个 Pod 分别限制上传和下载的带宽为最高 10Mb/s. 注意, 由于这个特性较新, 我们需要自己在 / etc/cni.NET.d 目录下写一个配置文件, 例如 my.NET.conf:
- {
- "type": "bandwidth",
- "capabilities": {
- "bandwidth": true
- }
- }
这个配置文件会告诉 Kubelet 去调用 CNI 的默认 bandwidth 插件, 然后根据 Pod annotation 里面关于带宽的 ingress/egress 值进行容器上行 / 下行带宽的限制. 当然, CNI 插件最后调用的还是 Linux tc 工具.
Kubernetes Service 机制
容器网络模型讲完后, 我们再看下 Kubernetes 集群内访问的 Service 机制. 先从一个简单的例子讲起, 客户端访问容器应用, 最简单的方式莫过于直接容器 IP + 端口了.
但, 简单的生活总是暂时的.
当有多个后端实例, 如何做到负载均衡? 如何保持会话亲和性? 容器迁移, IP 发生变化如何访问? 健康检查怎么做? 怎么通过域名访问?
Kubernetes 提供的解决方案是在客户端和后端 Pod 之间引入一个抽象层: Service. 什么是 Kubernetes 的 Service 呢?
Kubernetes 的 Service 有时候也称为 Kubernetes 的微服务, 代表的是 Kubernetes 后端服务的入口, 它注意包含服务的访问 IP(虚 IP)和端口, 因此工作在 L4. 既然 Service 只存储服务入口信息, 那如何关联后端 Pod 呢? Service 通过 Label Selector 选择与之匹配的 Pod. 那么被 Service 选中的 Pod, 当它们 Running 且 Ready 后, Kubernetes 的 Endpoints Controller 会生成一个新的 Endpoints 对象, 记录 Pod 的 IP 和端口, 这就解决了前文提到的后端实例健康检查问题. 另外, Service 的访问 IP 和 Endpoint/Pod IP 都会在 Kubernetes 的 DNS 服务器里存储域名和 IP 的映射关系, 因此用户可以在集群内通过域名的方式访问 Service 和 Pod.
Kubernetes Service 的定义如下所示:
其中, spec.ClusterIP 就是 Service 的访问 IP, 俗称虚 IP,spec.ports[].port 是 Service 的访问端口, 而与之对应的 spec.ports[].targetPort 是后端 Pod 的端口, Kubernetes 内部会自动做一次映射.
Kubernetes Endpoints 的定义如下所示:
其中, subsets[].addresses[].ip 是后端 Pod 的 IP,subsets[].ports 是后端 Pod 的端口, 与 Service 的 targetPort 对应.
下面我们来看下 Kubernetes Service 的工作原理.
如上图所示, 当用户创建 Service 和对应后端 Pod 时, Endpoints Controller 会观察 Pod 的状态变化, 当 Pod 处于 Running 且 Ready 状态时, Endpoints Controller 会生成 Endpoints 对象. 运行在每个节点上的 Kube-proxy 会观察 Service 和 Endpoints 的更新, 并调用其 load balancer 在主机上模块刷新转发规则. 当前主流的 load balancer 实现有 iptables 和 IPVS,iptables 因为扩展性和性能不好, 越来越多的厂商正开始使用 IPVS 模式.
Kubernetes Service 有这么几种类型: ClusterIP,NodePort 和 Load Balancer. 其中, ClusterIP 是默认类型, 自动分配集群内部可以访问的虚 IP--Cluster IP.NodePort 为 Service 在 Kubernetes 集群的每个 Node 上分配一个端口, 即 NodePort, 集群内 / 外部可基于任何一个 NodeIP:NodePort 的形式来访问 Service. 因此 NodePort 也成为 "乞丐版" 的 Load Balancer, 对于那些没有打通容器网络和主机网络的用户, NodePort 成了他们从外部访问 Service 的首选. LoadBalancer 类型的 Service 需要 Cloud Provider 的支持, 因为 Service Controller 会自动为之创建一个外部 LB 并配置安全组, Kubernetes 原生支持的 Cloud Provider 就那么几个: GCE,AWS. 除了 "外用",Load Balancer 还可以 "内服", 即如果要在集群内访问 Load Balancer 类型的 Service,kube-proxy 用 iptables 或 ipvs 实现了云服务提供商 LB(一般都是 L7 的)的部分功能: L4 转发, 安全组规则等.
Kubernetes Service 创建好了, 那么如何使用, 即如何进行服务发现呢? Kubernetes 提供了两种方式: 环境变量和域名.
环境变量即 Kubelet 为每个 Pod 注入所有 Service 的环境变量信息, 形如:
这种方式的缺点是: 容易环境变量洪泛, Docker 启动参数过长影响性能甚至直接导致容器启动失败.
域名的方式是, 假设 Service(my-svc)在 namespace(my-ns)中, 暴露名为 http 的 TCP 端口, 那么在 Kubernetes 的 DNS 服务器会生成两种记录, 分别是 A 记录: 域名 (my-svc.my-ns) 到 Cluster IP 的映射和 SRV 记录, 例如:_http._tcp.my-svc.my-ns 到一个 http 端口号的映射. 我们会在下文 Kube-dns 一节做更详细的介绍.
前文提到, Service 的 load balancer 模块有 iptables 和 IPVS 实现. 下面会一一进行分析.
Iptables 是用户空间应用程序, 通过配置 Netfilter 规则表 ( Xtables ) 来构建 Linux 内核防火墙. 下面就是 Kubernetes 利用 iptables 的 DNAT 模块, 实现了 Service 入口地址 (10.20.30.40:80) 到 Pod 实际地址 (172.17.0.2:8080) 的转换.
IPVS 是 LVS 的负载均衡模块, 亦基于 netfilter, 但比 iptables 性能更高, 具备更好的可扩展性.
如上所示, 一旦创建一个 Service 和 Endpoints,Kube-proxy 的 IPVS 模式会做三样事情:
确保一块 dummy 网卡 (kube-ipvs0) 存在. 至于为什么要创建 dummy 网卡, 因为 IPVS 的 netfilter 钩子挂载 INPUT chain, 我们需要把 Service 的访问 IP 绑定在 dummy 网卡上让内核 "觉得" 虚 IP 就是本机 IP, 进而进入到 INPUT chain.
把 Service 的访问 IP 绑定在 dummy 网卡上.
通过 socket 调用, 创建 IPVS 的 virtual server 和 real server, 分别对应 Kubernetes 的 Service 和 Endpoints.
好了, 都说 IPVS 性能要好于 iptables, 无图无真相, 上实测数据!
通过上图我们可以发现, IPVS 刷新规则的时延明显要低 iptables 几个数量级.
从上图我们又可以发现, IPVS 相较于 iptables, 端到端的吞吐率和平均时延均由不小的优化. 注意, 这是端到端的数据, 包含了底层容器网络的 RTT, 还能有 30% 左右的性能提升.
上图是 iptables 和 IPVS 在资源消耗方面的对比, 孰优孰劣, 不言而喻.
最后, 问个开放性的问题. 如何从集群外访问 Kubernetes Service?
前文已经提到, 可以使用 NodePort 类型的 Service, 但这种 "屌丝" 的做法除了要求集群内 Node 有对外访问 IP 外, 还有一些已知的性能问题(具体请参考本公众号另外一篇干货文章《 记一次 Docker/Kubernetes 上无法解释的超时原因探寻之旅 https://mp.weixin.qq.com/s/y1b7hh8w5tdnjqF6gtVL-g 》). 使用 LoadBalancer 类型的 Service? 它又要求在特定的云服务上跑 Kubernetes. 而且 Service 只提供 L4 负载均衡功能, 而没有 L7 功能, 一些高级的, L7 的转发功能, 比如: 基于 HTTP header,cookie,URL 的转发就做不了.
在 Kubernetes 中, L7 的转发功能, 集群外访问 Service, 这些功能是专门交给 Ingress 的.
Kubernetes Ingress
何谓 Ingress? 从字面意思解读, 就是 "入站流量".Kubernetes 的 Ingress 资源对象是指授权入站连接到达集群内服务的规则集合. 具体含义看下面这个例子便一目了然:
通常情况下, Service 和 Pod 仅可在集群内部网络中通过 IP 地址访问. 所有到达边界路由的流量或被丢弃或被转发到其他地方. Ingress 就是在边界路由处开个口子, 放你进来. 因此, Ingress 是建立在 Service 之上的 L7 访问入口, 它支持通过 URL 的方式将 Service 暴露到 Kubernetes 集群外; 支持自定义 Service 的访问策略; 提供按域名访问的虚拟主机功能; 支持 TLS 通信.
在上面这个例子, Ingress 就可以基于客户端请求的 URL 来做流量分发, 转发给不同的 Service 后端.
我们来看下 Ingress 资源对象的 API 定义:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: test-ingress
- spec:
- tls:
- - secretName: testsecret
- backend:
- serviceName: testsvc
- servicePort: 80
把上面这个 Ingress 对象创建起来后, kubectl get 一把, 会看到:
其中, ADDRESS 即 Ingress 的访问入口地址, 由 Ingress Controller 分配, 一般是 Ingress 的底层实现 LB 的 IP 地址, 例如: Ingress,GCE LB,F5 等; BACKEND 是 Ingress 对接的后端 Kubernetes Service IP + Port;RULE 是自定义的访问策略, 主要是基于 URL 的转发策略, 若为空, 则访问 ADDRESS 的所有流量都转发给 BACKEND.
下面给出一个 Ingress 的 rules 不为空的例子.
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: test
- spec:
- rules:
- - host: foo.bar.com
- http:
- paths:
- - path: /foo
- backend:
- serviceName: s1
- servicePort: 80
- - path: /bar
- backend:
- serviceName: s2
- servicePort: 80
这个例子和上面那个最明显的区别在于, rules 定义了 path 分别为 / foo 和 / bar 的分发规则, 分别转发给 s1:80 和 s2:80.Kubectl get 一把一目了然:
需要注意的是, 当底层 LB 准备就绪时, Ingress Controller 把 LB 的 IP 填充到 ADDRESS 字段. 而在我们的例子中, 这个 LB 显然还未 ready.
Ingress 是一个非常 "极客" 和需要 DIY 的产物, Kubernetes 只负责提供一个 API 定义, 具体的 Ingress Controller 需要用户自己实现! 官方倒是提供了 Nginx 和 GCE 的 Ingress Controller 示例供开发者参考. 实现一个 Ingress Controller 的大致框架无非是, List/Watch Kubernetes 的 Service,Endpoints,Ingress 对象, 刷新外部 LB 的规则和配置.
这还不算, 如果想要通过域名访问 Ingress? 需要用户自己配置域名和 Ingress IP 的映射关系, 比如: host 文件, 自己的 DNS(不是 kube-dns). 下文会讲到,"高冷" 的 kube-dns 只会负责集群内的域名解析, 集群外的一概不管.
Kubernetes DNS
Kubernetes DNS 说, 刚刚谁念叨起本宫了?
一言以蔽之, Kubernetes 的 DNS, 就是用来解析 Kubernetes 集群内的 Pod 和 Service 域名的, 而且一般是供 Pod 内的进程使用的! 血统高贵, 一般不给外人使用. 那可能会有人好奇问一句, Pod 到底怎么使用 Kubernetes DNS 呢? 原来, kubelet 配置 --cluster-dns 把 DNS 的静态 IP 传递给每个容器. Kubernetes DNS 一般通过插件方式部署到 Kubernetes 上, 并为之绑定一个 Service, 而 Service 的 Cluster IP 往往是固定的. Kubernetes DNS 目前有两个实现, 分别是 kube-dns 和 CoreDNS.
对于 Service,Kubernetes DNS 服务器会生成两类 DNS 记录, 分别是: A 记录和 SRV 记录. 而 A 记录又对普通 Service 和 headless Service 有所区别. 普通 Service 的 A 记录是: {service name}.{service namespace}.svc.cluster.local -> Cluster IP 的映射关系. 后面域名后面一串子域名: svc.cluster.local 是 Kubelet 通过 --cluster-domain 配置的伪域名.
Headless Service 的 A 记录是: {service name}.{service namespace}.svc.cluster.local -> 后端 Pod IP 列表 的映射关系.
至于 SRV 记录, 则是按照一个约定俗称的规定:
_{port name}._{port protocol}.{service name}.{service namespace}.svc.cluster.local -> Service Port
实现了对服务端口的查询.
对于 Pod,A 记录是:
{pod-ip}.{pod namespace}.pod.cluster.local -> Pod IP
如果 Pod IP 是 1.2.3.4, 上面的 {pod-ip} 即 1-2-3-4.Pod 的 A 记录其实没什么用, 因为如果都知道 Pod IP 了, 还用查 DNS 吗?
如果在 Pod Spec 指定 hostname 和 subdomain, 那么 Kubernetes DNS 会额外生成 Pod 的 A 记录就是:
{hostname}.{subdomain}.{pod namespace}.pod.cluster.local -> Pod IP
同样, 后面那一串子域名 pod.cluster.local 是 kubelet 配置的伪域名.
让我们看下 kube-dns 的架构吧.
如上图所示, kube-dns 是 "三进程" 架构.
kubedns:List/Watch Kubernetes Service 和 Endpoints 变化. 接入 SkyDNS, 在内存中维护 DNS 记录, 是 dnsmasq 的上游.
dnsmasq:DNS 配置工具, 监听 53 端口, 为集群提供 DNS 查询服务. 提供 DNS 缓存, 降低 kubedns 压力.
exechealthz: 健康检查, 检查 kube-dns 和 dnsmasq 的健康.
需要注意的是, dnsmasq 是个 C++ 写的一个小程序, 有内存泄露的 "老毛病".
虽然 kube-dns 血统纯正, 而且早早地进入到 Kubernetes 的 "后宫", 也早有 "名分", 但近来 CoreDNS 却独得 Kubernetes SIG Network 的圣宠. CoreDNS 是个 DNS 服务器, 原生支持 Kubernetes, 而且居然还是一个 CNCF 的项目!
与 kube-dns 的三进程架构不同, CoreDNS 就一个进程, 运维起来更加简单. 而且采用 Go 语言编写, 内存安全, 高性能. 值得称道的是, CoreDNS 采用的是 "插件链" 架构, 每个插件挂载一个 DNS 功能, 保证了功能的灵活, 易扩展. 尽管资历不深, 但却 "集万千宠爱于一身", 自然是有两把刷子的.
值得一提的是, 以上性能测试数据是不带 cache 情况下取得的, 明显要高于 kube-dns. 那么为什么建议使用 CoreDNS 呢? Kubernetes 官方已经将 CoreDNS 扶正, 成为了默认模式. 除了性能好以外, 还有什么其他优势吗? CoreDNS 修复了 kube-dns 的一些 "令人讨厌" 的 "老生常谈" 的问题:
- dns#55 - Allow custom DNS entries for kube-dns
- dns#116 - Missing 'A' records for headless service with pods sharing hostname
- dns#131 - ExternalName not using stubDomains settings
- dns#167 - Enable round robin A/AAAA records
- dns#190 - kube-dns cannot run as non-root user
- dns#232 - Use pod's name instead of pod's hostname in DNS SRV records
同时, 还有一些吸引人的特性:
- Zone transfers - list all records, or copy records to another server
- Namespace and label filtering - expose a limited set of services
- Adjustable TTL - adjust up/down default service record TTL
- Negative Caching - By default caches negative responses (e.g. NXDOMAIN)
其中, 原生支持基于 namespace 隔离和过滤 Service 和 Pod 的 DNS 记录这一条特性, 在多租户场景下格外有用.
Network Policy
Kubernetes 默认情况下, 底层网络是 "全连通" 的. 但如果我们需要实现以下愿景:
即, 只允许访问 default namespace 的 Label 是 App=web 的 Pod,default namespace 的其他 Pod 都不允许外面访问. 这个隔离需求在多租户的场景下十分普遍. Kubernetes 的解决方案是 Network Policy.
Network Policy 说白了就是基于 Pod 源 IP(所以 Kubernetes 网络不能随随便便做 SNAT 啊!)的访问控制列表, 限制 Pod 的进 / 出流量, 用白名单实现了一个访问控制列表(ACL).Network Policy 作为 Pod 网络隔离的一层抽象, 允许使用 Label Selector,namespace selector, 端口, CIDR 这四个维度限制 Pod 的流量进出. 和 Ingress 一副德行的是, Kubernetes 对 Netowrk Policy 还是只提供了 API 定义, 不负责实现!
一般情况下, Policy Controller 是由网络插件提供的. 支持 Network Policy 的网络插件有 Calico,Cilium,Weave.NET,Kube-router,Romana. 需要注意的是, flannel 不在这个名单之列, 似乎又找到了一个不用 flannel 的理由?
让我们先来见识几个默认网络策略:
注:{}代表允许所有,[]代表拒绝所有.
如果要拒绝所有流量进入呢? 比如, 场景长这样:
那么 Network Policy 对象应该定义成:
如果要限制部分流量进入呢? 比如, 场景长这样:
那么 Network Policy 对象应该定义成:
如果只允许特定 namespace 的 Pod 流量进入呢? 比如, 场景长这样:
那么 Network Policy 对象应该定义成:
如果限制流量从指定端口进入呢? 比如, 场景长这样:
那么, Network Policy 对象应该定义成:
未来工作
最后, 畅想下 Kubernetes 网络后面的发展趋势.
首先, kubenet 会被废弃. Kubenet 本来就是一个特定时期的产物, 那时候 CNI 尚未成熟, 让 Kubernetes 亲自去干底层网络这种 "苦差事", 尽管 Kubernetes 是有一万个不愿意, 但如果完全没有网络连通方案, 又会让用户诟病 "过于高冷","易用性差", 甚至会让那时的竞争对手 docker swarm 有机可图. 因此 Kubernetes 写了一个简单的网络插件, 即 kubenet, 一个 bridge 模型的容器连通性解决方案. 但随着 CNI 强势崛起, 以及 kubenet 并不支持网络策略等硬伤, 社区已经没了继续维护 kubenet 的热情, 因此废弃 kubenet 也就被提上了议程.
IPv4/IPv6 双栈支持. 经过大半年社区开发者的齐心协力, Kubernetes 总算支持了 IPv6. 但目前的支持比较初级, IPv6 还不能和 IPv4 混用. IPv4/IPv6 的双栈支持, 势在必行.
Pod Ready++.Pod 有 Ready 和非 Ready 状态之分, 为何还要搞出个 Ready++ 这种 "量子化" 的模糊界限呢? 原因在于, 一个 Pod 能否真的对外提供服务, 除了依赖容器内进程 ready(我们会放置探针, 检查进程状态)这类内部条件外, 还依赖诸如: Ingress,Service, 网络策略, 外部 LB 等一系列外部因素. Pod Ready++ 的提出, 就是为了将外部因素一齐纳入 Pod 状态的考量.
多网络. 也许是 Kubernetes 的 "单 Pod 单 IP" 的网络模型过于深入人心了, 以至于在实现过程中都谨遵这一 "金科玉律". 但我们知道, 网络的世界纷繁复杂, 一块网卡怎么可能 cover 所有场景呢? 据最简单的例子, 一般我们会为一个 IO 密集型的作业配两块网络, 一块网卡作为数据信道, 另一块网卡则作为控制信道. 从单网络到多网络的迁移, 道路并不平坦, 甚至是处处荆棘和沼泽, 且不说网络插件, Service,DNS,Ingress 等实现要大改, 光 API 兼容性就让你头疼. 好消息是经过整整两年的拉锯, 社区 Network Plumbing WG 终于取得了阶段性成果, 如不出意外的话, 应该是 CRD + 多网路插件的形式支持 Kubernetes 的多网络, 保持 Kubernetes 原生 API 的稳定. 支持多网络的 CNI 插件有几个, 但真真落到生产的没几个, CNI-genie 是其中一个有众多粉丝基础和经过生产环境检验的 Kubernetes 多网络插件, 了解一下?
最后, 谈下 Service Mesh. 严格说来, Service Mesh 并不在 Kubernetes 核心的范围之内. 但是, 在 Kubernetes 的帮助下, 应用上云后, 还面临着服务治理的难题. 现在大多数云原生的应用都是微服务架构, 微服务的注册, 服务之间的相互调用关系, 服务异常后的熔断, 降级, 调用链的跟踪, 分析等待一系列现实问题摆在各机构面前. Service Mesh(服务网络)就是解决这类微服务发现和治理问题的一个概念. 在我看来, Service Mesh 之于微服务架构就像 TCP 协议之于 Web 应用. 我们大部分人写 Web 应用, 关心的是 RESTful,HTTP 等上层协议, 很少需要我们自己操心网络报文超时重传, 分割组装, 内容校验等底层细节. 正是因为有了 Service Mesh, 企业在云原生和微服务架构的实践道路上只需根据自己业务做适当的微服务拆分即可, 无需过多关注底层服务发现和治理的复杂度. 而 Istio 的出现, 使得有些 "学院派" 的 Service Mesh 概念真正得到了落地, 并且提供了真正可供操作, 非侵入式的方案, 让诸如 Spring Cloud,Dubbo 这些 "老古董" 第一次有了被淘汰的危机感.
来源: http://www.tuicool.com/articles/nU32myr