k8s 网络部署汇总——解决集群外部服务访问问题

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 子网内的性能。


配置vxlan跨子网的方法

名词解释

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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容