1. Kubernetes 网络基础知识
Kubernetes 网络模型定义了一个“平面”网络,其中:
每个 pod 都有自己的 IP 地址。
任何节点上的 Pod 都可以在没有 NAT 的情况下与所有其他节点上的所有 Pod 通信。
1.1 CNI 插件
CNI(容器网络接口)是一个标准 API,它允许不同的网络实现插入 Kubernetes。每当创建或销毁 pod 时,Kubernetes 都会调用 API。有两种类型的 CNI 插件:
CNI 网络插件:负责在 Kubernetes pod 网络中添加或删除 pod。这包括创建/删除每个 pod 的网络接口以及将其与其余的网络实现连接/断开。
CNI IPAM 插件:负责在创建或删除 Pod 时为其分配和释放 IP 地址。根据插件的不同,这可能包括为每个节点分配一个或多个 IP 地址 (CIDR) 范围,或从底层公共云网络获取 IP 地址以分配给 pod。
1.2 Kubenet
Kubenet 是一个非常基础的网络插件,内置在 Kubernetes 中。它不实现跨节点网络或网络策略。它通常与云提供商集成一起使用,该集成在云提供商网络中设置路由以在节点之间或在单节点环境中进行通信。Kubenet 与 Calico 不兼容。
1.3 覆盖网络(Overlay)
覆盖网络是在另一个网络之上的网络层。在 Kubernetes 的上下文中,覆盖网络可用于处理底层网络之上节点之间的 pod 到 pod 流量。用于kubernetes覆盖网络的两种常见网络协议是 VXLAN 和 IP-in-IP。
使用覆盖网络的主要缺点是:
轻微的性能影响。封装数据包的过程占用少量 CPU,数据包中用于编码封装(VXLAN 或 IP-in-IP 标头)所需的额外字节减少了可以发送的内部数据包的最大大小,进而可以意味着需要为相同数量的总数据发送更多数据包。
pod IP 地址不可在集群外路由。
1.4 跨子网覆盖(Cross-subnet overlays)
除了标准的 VXLAN 或 IP-in-IP 覆盖外,Calico 还支持 VXLAN 和 IP-in-IP 的“跨子网”模式。在这种模式下,在每个子网中,底层网络充当 L2 网络。在单个子网内发送的数据包不会被封装,因此您可以获得非覆盖网络的性能。
1.5 集群外的 Pod IP 可路由性
不可路由
如果 pod IP 地址在集群外部不可路由,那么当 pod 尝试建立到集群外部 IP 地址的网络连接时,Kubernetes 使用一种称为 SNAT(源网络地址转换)的技术来更改源 IP从 pod 的 IP 地址到托管 pod 的节点的 IP 地址。
集群外部的任何东西都不能直接连接到 Pod IP 地址,因为更广泛的网络不知道如何将数据包路由到 Pod IP 地址。
可路由
如果 pod IP 地址可在集群外部路由,则 pod 可以在没有 SNAT 的情况下连接到外部世界,而外部世界可以直接连接到 pod,而无需通过 Kubernetes 服务或 Kubernetes 入口。
可在集群外路由的 Pod IP 地址的主要缺点是 Pod IP 在更广泛的网络中必须是唯一的。
什么决定了可路由性?
如果您为集群使用覆盖网络,则 pod IP 通常无法在集群外部路由。
如果您不使用覆盖网络,那么 pod IP 是否可在集群外部路由取决于正在使用的 CNI 插件、云提供商集成或(对于本地)BGP 与物理网络对等的组合。
1.6 BGP
BGP(边界网关协议)是一种基于标准的网络协议,用于在网络中共享路由。它是互联网的基本组成部分之一,具有出色的扩展特性。
1.7 关于 Calico 网络
Calico CNI 网络插件
Calico CNI 网络插件使用一对虚拟以太网设备(veth pair)将 pod 连接到主机网络命名空间的 L3 路由。这种 L3 架构避免了许多其他 Kubernetes 网络解决方案中的额外 L2 桥的不必要的复杂性和性能开销。
Calico CNI IPAM 插件
Calico CNI IPAM 插件从一个或多个可配置的 IP 地址范围内为 Pod 分配 IP 地址,并根据需要为每个节点动态分配小块 IP。结果是与许多其他 CNI IPAM 插件相比,IP 地址空间使用效率更高,包括用于许多网络解决方案的主机本地 IPAM 插件。
覆盖网络模式(Overlay )
Calico 可以提供 VXLAN 或 IP-in-IP 覆盖网络,包括仅跨子网(cross-subnet only)模式。
非覆盖网络模式(Non-overlay)
Calico 可以提供在任何底层 L2 网络或 L3 网络之上运行的非覆盖网络,该网络既可以是具有适当云提供商集成的公共云网络,也可以是支持 BGP 的网络(通常是具有标准 Top-of 的本地网络-机架路由器)。
Calico 内置了对 BGP 的支持。在本地部署中,这允许 Calico 与物理网络(通常与架顶式路由器)对等以交换路由,从而形成一个非覆盖网络,其中 pod IP 地址可在更广泛的网络中路由,就像附加的任何其他工作负载一样到网络。
网络策略执行
Calico 的网络策略执行引擎实现了 Kubernetes 网络策略的全部功能,以及 Calico 网络策略的扩展功能。这与 Calico 的内置网络模式或任何其他 Calico 兼容的网络插件和云提供商集成结合使用。
2. Calico 网络配置方案
一个比官网讲的更系统的介绍:Calico 介绍、原理与使用
如果使用 Calico 官方的本地部署脚本,则部署的技术结构如下:
Calico on-prem 最常见的网络设置是使用 BGP to peer 与物理网络(通常是架顶路由器)的非覆盖模式,以使 pod IP 可在集群外部路由。(当然,如果需要,您可以配置本地网络的其余部分以限制集群外部 pod IP 路由的范围。)此设置提供了丰富的高级 Calico 功能,包括广播 Kubernetes 服务 IP 的能力(集群 IP 或外部 IP),以及在 pod、命名空间或节点级别控制 IP 地址管理的能力,以支持与现有企业网络和安全要求集成的广泛可能性。
如果无法将 BGP 配对连接到物理网络,如果集群位于单个 L2 网络中,可以在 VXLAN 或 IP-in-IP 覆盖模式下运行 Calico,并使用跨子网覆盖模式来优化每个 L2 子网内的性能。
名词解释
3. 将服务暴露到集群外——Ingress 控制器
k8s 可以通过服务(service)将自己的应用程序暴露到集群外,服务的类型有:
ClusterIP
:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的ServiceType
。[NodePort](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#type-nodeport)
:通过每个节点上的 IP 和静态端口(NodePort
)暴露服务。NodePort
服务会路由到自动创建的ClusterIP
服务。 通过请求<节点 IP>:<节点端口>
,你可以从集群的外部访问一个NodePort
服务。[LoadBalancer](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#loadbalancer)
:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的NodePort
服务和ClusterIP
服务上。[ExternalName](https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#externalname)
:通过返回CNAME
和对应值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。 无需创建任何类型代理。
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
k8s 使用 Ingress 方式提供服务,需要额外安装 Ingress 控制器
以 Ingress-nginx 控制器为例,官方的部署指南提供了多种平台下的部署方式:
Ingress-nginx 控制器在云环境部署时,默认的 Service 类型为 LoadBalancer
, 在裸机(内网)环境中,可以参考 Bare-metal 部署方式(控制器 Service 类型为 NodePort
)
3.1 关于 Ingress 的外部访问
3.1.1 存在的问题
云环境部署 ingress-nginx 时,默认的访问方式由云服务商提供的负载均衡器来负责,从而外部客户端可以访问集群中的 NGINX ingress
裸机环境缺乏负载均衡器,无法给外部用户同样的访问
针对这个问题,官方提供了4种外部访问方法
3.1.2 使用 NodePort 服务端口
裸机的 ingress-nginx 默认使用 NodePort 服务的方式安装部署
在该配置中,nginx 容器与主机网络隔离,因此可以安全的绑定任何端口,包括HTTP的标准端口 80和 443 。然而,由于容器命名空间隔离,集群网络外部的客户端无法直接访问 ingress主机的80和443端口,相反,外部客户端必须附加分配给 ingress-nginx 服务的 NodePort 给 HTTP 请求
例如给 ingress-nginx 服务分配了 NodePort 30100端口
$ kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP PORT(S)
ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP
k8s node 的外部IP地址是203.0.113.2
$ kubectl get node
NAME STATUS ROLES EXTERNAL-IP
host-1 Ready master 203.0.113.1
host-2 Ready node 203.0.113.2
host-3 Ready node 203.0.113.3
客户端需要用 host: myapp.example.com 访问一个 Ingress ,需要访问 http://myapp.example.com:30100,这里 myapp.example.com 子域名解析为 203.0.113.2
3.1.2 使用主机网络
当没有外部负载均衡器,使用NodePorts 也不是一个好的选项时,可以通过配置 ingress-nginx Pods 使用其宿主机网络,而不是专用网络命名空间。这种方法的优点是 NGINX Ingress 控制器可以直接绑定 80端口和443端口到 k8s 节点的网络接口,不需要由 NodePort 服务提供的额外的网络转换
这种方法不利用任何服务对象来暴露 NGINX Ingress 控制器,如果 ingress-nginx 服务存在于目标集群,推荐删除它
通过配置 Pod 的 spec 选项,可以实现
template:
spec:
hostNetwork: true
安全考虑:使用这个方法将会暴露每个系统守护进程给 NGINX Ingress 控制器到任何网络接口,包括主机的 loopback。使用这个方法需要小心的评估这给系统带来的安全影响
例:NGINX Pod 继承主机的IP地址,而不是Pod内部IP
$ kubectl -n ingress-nginx get pod -o wide
NAME READY STATUS IP NODE
default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2
ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3
ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2
这种部署方法有一个限制,就是每个集群节点上只能部署一个 NGINX Ingress 控制器 Pod,因为在同一个网络接口上多次绑定同一个端口在技术上是不可能的,确保仅创建可调度 Pod 的一种方法是将 NGINX Ingress 控制器部署为DaemonSet而不是传统的 Deployment。
DaemonSet 为每个集群节点(包括主节点)准确调度一种 Pod,除非节点配置为排斥这些Pod 。有关更多信息,请参阅DaemonSet。
跟 NodePorts 方法一样,这种方法有几个奇怪的点需要注意:
DNS 解析:配置了
hostNetwork: true
的Pod 不使用内部 DNS 解析器(如 kube-dns 或 CoreDNS),除非其dnsPolicy
spec 字段设置为[ClusterFirstWithHostNet](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy)
。如果希望 NGINX 解析内部名字的话,需要使用这个设置-
Ingress 状态:由于使用主机网络的配置中,没有服务暴露 NGINX Ingress controller ,在标准云设置中默认的
--publish-service
标志将不会应用,并且所有的 Ingress 对象的状态会保持空$ kubectl get ingress NAME HOSTS ADDRESS PORTS test-ingress myapp.example.com 80
并且由于裸机节点通常没有 ExternalIP,节点需要设置
[--report-node-internal-ip-address](https://kubernetes.github.io/ingress-nginx/user-guide/cli-arguments/)
标志,这会设置所有 Ingress 对象的状态地址为所有运行 NGINX Ingress controller 的节点IP地址$ kubectl -n ingress-nginx get pod -o wide NAME READY STATUS IP NODE default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2 ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3 ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2 $ kubectl get ingress -o wide NAME HOSTS ADDRESS PORTS test-ingress myapp.example.com 203.0.113.2,203.0.113.3 80
3.1.4 使用自配置 edge
与云环境类似,这种方法需要一个边界网络组件(网关),给 k8s 集群提供公共进入点(entrypoint)。该边界组件可以是物理的(运营商实现)或软件实现(例如 HAproxy),并且由运营团队在 k8s 之外管理。
这种方法与 NodePort 服务方式的重要区别就是:外部客户端不直接访问集群节点,只有边界组件访问。这种方式适合私有 k8s 集群,其中的节点都没有公共ip 地址。
在边界端,需要预先准备一个公共地址,用于转发所有的 HTTP 流量到 k8s 节点或 master。TCP 80端口和443端口进入的流量转发到目标节点的相关的 HTTP 和 HTTPS NodePort ,图示如下:
[图片上传失败...(image-9e94ca-1668345670017)]
3.1.5 使用 External IP
不建议:这种方法不允许任何方式保留 HTTP 请求的源IP地址,因此尽管简单,但不建议使用这种方法
externalIPs 选项让 kube-proxy
将发送到任意IP地址和服务端口的流量路由到该服务的端点,这些IP地址必须属于目标节点
例:
$ kubectl get node NAME STATUS ROLES EXTERNAL-IP host-1 Ready master 203.0.113.1 host-2 Ready node 203.0.113.2 host-3 Ready node 203.0.113.3 $ kubectl -n ingress-nginx get svc NAME TYPE CLUSTER-IP PORT(S) ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP
配置该服务的 spec, NodePort 和 服务 port 都可访问 NGINX
$ kubectl edit svc ingress-nginx-controller -n ingress-nginx spec: externalIPs: - 203.0.113.2 - 203.0.113.3 $ curl -D- http://myapp.example.com:30100 HTTP/1.1 200 OK Server: nginx/1.15.2 $ curl -D- http://myapp.example.com HTTP/1.1 200 OK Server: nginx/1.15.2