Kubernets(k8s) 网络原理二:Pod访问外网

上一篇文章中,我们介绍了pod与宿主机通信,并且通过network namespace模拟了通信过程。回顾整个流程,无非就涉及到两个东西,通信设备和路由规则。

本文要讲的,也离不开这两个东西,只不过需要对容器IP进行额外处理。

pod访问外网

在展开讲述之前,先明确一个问题,外网的范围是什么?

这个外网范围可大可小,可能是同一个局域网的另外一台主机,也可能是局域网外的一台网路设备或主机。

讲到这里,可能会混淆,是不是扯到了集群容器间通信,毕竟集群的容器也是分散在不同主机上的。

因此为了避免混淆,我们这里先对这些主机做两个约定

  1. 两个主机间可以通过物理网络通信。
  2. 仅能修改源主机配置

试想,当我们访问百度时,你肯定只是配置自己的主机,你会去配置百度的网关吗?

好,基于两个约定,展开本文要讨论的话题。

同样,我们还是先建模,网络拓扑图如下:

注:这里用network泛指中间的网路设备

如上图,host A和host B可以通过物理网络连接,并且为host A和host B分配了IP 196.128.1.2和196.128.2.2,并且通过CIDR地址可以看到,他们分别归宿与两个不同的网段。

同时当主机A和主机B通信时,host A先将报文将给默认网关196.128.1.1,默认网关将报文转发至network,最终该报文会被交给196.128.2.1,即host B的默认网关,然后转交给host B。

试想一下,如果主机A上有一个pod-1,其IP为10.10.1.2/24,它的报文可以发送给主机B吗?

当然可以!!!因为pod-1发送的报文的目标IP是host B的IP,这个IP是可以在network中路由的。

那为什么我们说他们之间不能通信呢?

那是因为pod-1能发送报文,但是却收不到响应报文。

那这个响应报文被谁丢了呢?

如果回答是host B的网关,那证明你的基础网络知识还是比较过关,没错这里就是被host B的默认网关把报文丢了。

我们来看看这个过程发生了什么

  1. pod-1发起IP报文,源IP为10.10.1.2,目标IP为196.128.2.2
  2. host A具备转发功能,将报文转发给自己的默认网关196.128.1.1
  3. host A默认网关将报文转发至network
  4. network最终将报文交付给196.128.2.1,即host B的默认网关
  5. host B默认网关将报文转交给host B
  6. host B协议栈处理完报文准备响应
  7. host B将IP调换,发送响应报文,此时,源IP为196.128.2.2,目标IP为10.10.1.2
  8. host B上无10.10.1.2的直接路由,于是将报文交给它的默认网关
  9. host B默认网关收到报文,查找路由信息,发现无任何匹配,丢弃该报文。

最终pod-1无法收到响应报文。

因此在整个过程中,无法发送响应报文的核心的问题是:

host B的网关以及整个network中的网络设备都不认识10.10.1.2

讲到这里,可能你已经想到办法了。

既然不认识,我们让它认识不就完了吗?

确实可以这样做,但是却存在以下两个问题

  • 你需要配置整个网络中参与路由的所有设备
  • 10.10.1.2是我们虚拟的ip,它在我们虚拟的子网10.10.1.0/24这个域内不会产生冲突,但是保不齐其他域中也存在10.10.1.2这个IP。

因为存在这个问题,所以这个方案非常复杂并且几乎不可能实现。

既然中间网络设备不让修改,那我们就只能从IP入手了.

值得庆幸的是,pod-1所在的host A的IP在整个网络中是可以路由的。

即上面流程中第8条,如果host B收到的报文的目标IP是host  A的IP不就可以了吗?

那要如何修改IP呢?答案就是NAT

NAT

顾名思义,NAT就是网络地址转换,简单理解就是一种将私网地址转成公网地址的技术。

关于NAT技术的详情,不在这篇文章的讨论范围,感兴趣的可以自行查阅

既然有了NAT,我们将刚在的网络图改造以下,可以得到:

有了上一篇文章的基础,这张图看起来应该没有压力。

整个传输过程如下:

  1. host A中pod协议栈发起报文,源IP为10.10.1.2,目的IP为196.128.2.2
  2. 报文通过veth pair到达主机协议栈
  3. host A协议栈iptables中存在规则:源地址为10.10.1.2/24的报文需要做nat
  4. host A协议栈将原始报文的源IP替换为主机的IP地址,并记录该报文被snat过
  5. host A将报文从默认网关发出(图中已忽略)
  6. 报文经过网络传输,到达host B的默认网关(图中已忽略)
  7. host B的默认网关将报文转交给host B
  8. host B协议栈处理报文
  9. host B协议栈处理报文完毕将响应报文发送给默认网关
  10. host B默认网关根据规则将报文传输给下一跳
  11. 响应报文通过网络到达host A
  12. host A协议栈发现该报文应该做dnat(过程4中存有记录)
  13. host A将目标IP还原为10.10.1.2,并转发给veth 1
  14. host A中pod协议栈收到响应报文。

其它的流程都很熟悉,我们重点看看第3,4,12。

先看4和12,这里有snat和dnat,即将源地址转换和目标地址转换(上图IP首部)。至于内核转换细节,这里不讨论。

我们再看第3条,iptable规则,是一条什么样的规则完成了这个转换呢?

要回答这个问题,必须要了解iptable的工作原理,因为在后续的文章中,例如分析K8S service时我们会用到这个iptable,所以这里也不展开详细的讲述。

我们通过一个小实验来看看是不是和我们说的一样

实验

在这个实验中,我们仍然以network  namespace代指pod

因为实验环境的主机IP已经配置了,所以下面实验中的IP可能会和上图中分配的IP对不上,不过不影响我们做整个实验

在实验中,我们用192.167.11.126代指上图中host A,用192.167.11.127代指上图中host B

首先我们在host A中创建一个network namespace,并完成相关配置工作,因为这部分在第一篇文章中已经有讲述,所以这里就不展开讲每一条命令的作用

# ip netns add pod-1

# ip link add eth0 type veth peer name veth1

# ip link set eth0 netns pod-1

# ip netns exec pod-1 ip addr add 10.10.1.10 dev eth0

# ip netns exec pod-1 ip link set dev eth0 up

# ip netns exec pod-1 ip route add 169.254.1.1 dev eth0

# ip netns exec pod-1 ip route add default via 169.254.1.1 dev eth0

# ip link set dev veth1 up 

# echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp

# echo 1 > /proc/sys/net/ipv4/ip_forward

此时我们尝试在pod-1中ping host B

# ip netns exec pod-1 ping -c 1 192.168.11.127
PING 192.168.11.127 (192.168.11.127) 56(84) bytes of data.

--- 192.168.11.127 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

不出意外,ping 不通。

我们在host B上抓包看看。

# tcpdump -n -i ens33 -p icmp
05:46:42.231478 IP 10.10.1.10 > 192.168.11.127: ICMP echo request, id 37196, seq 1, length 64
05:46:42.231505 IP 192.168.11.127 > 10.10.1.10: ICMP echo reply, id 37196, seq 1, length 64

可以看到host B是正确响应了报文的,和我们刚才的分析一致

此时,我们在主机上添加iptables规则

 # iptables -A POSTROUTING -t nat -s 10.10.1.0/24  -j MASQUERADE

这个规则的意思是所有来自10.10.1.0/24这个网段的IP做一次snat

配置好规则之后,这时候就可以ping通了,我们再次尝试在host B抓包

# tcpdump -n -i ens33 -p icmp
07:08:51.945389 IP 192.168.11.126 > 192.168.11.127: ICMP echo request, id 59199, seq 1, length 64
07:08:51.945405 IP 192.168.11.127 > 192.168.11.126: ICMP echo reply, id 59199, seq 1, length 64

和刚才抓包的结果对比,你会发现,源IP不再是容器IP,而是host A的IP

如果你的宿主机能访问百度,你这时候在pod-1内访问百度,也是可以的

# ip netns exec pod-1 ping -c 1 www.baidu.com
PING www.a.shifen.com (183.2.172.185) 56(84) bytes of data.
64 bytes from 183.2.172.185 (183.2.172.185): icmp_seq=1 ttl=127 time=36.2 ms

--- www.a.shifen.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 36.291/36.291/36.291/0.000 ms

总结

本文介绍了宿主内的容器,如何访问外网,严格来说,他不是容器组网的关键。因为它解决的不是两个容器相互通信,而是pod访问其他主机这个单点问题。

关于容器组网的内容,将在下一篇文章中分析。

  • 32
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要使外部网络能够访问 Kubernetes 集群中的 Pod,通常可以使用 Kubernetes Service 和 Ingress Controller。 1. 首先,创建一个 Service 对象来公开 Pod。Service 可以将流量从集群外部路由到 Pod 内部。可以通过以下 YAML 示例创建一个 Service: ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: my-app ports: - protocol: TCP port: 80 targetPort: 8080 ``` 上述示例将名为 `my-service` 的 Service 创建为 TCP 协议的端口映射,将集群外部的流量路由到具有标签 `app: my-app` 的 Pod 上的端口 8080。 2. 安装和配置 Ingress Controller。Ingress Controller 是负责将外部流量路由到 Service 的组件。常见的 Ingress Controller 有 Nginx Ingress Controller、Traefik、HAProxy 等。 3. 创建一个 Ingress 资源对象,用于定义请求的入口点和路由规则。以下是一个示例 Ingress YAML: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress spec: rules: - host: myapp.example.com http: paths: - pathType: Prefix path: / backend: service: name: my-service port: number: 80 ``` 上述示例将 Ingress 创建为将流量路由到名为 `my-service` 的 Service 上的规则。可以根据需要自定义 Host、Path 和其他路由规则。 4. 配置 DNS,将 Ingress 路由的域名解析到 Kubernetes 集群的外部 IP 地址或负载均衡器上。 完成上述步骤后,外部网络就可以通过访问 Ingress 定义的域名,从而访问Kubernetes 集群内部的 Pod。请注意,具体的实现方式可能因集群环境和网络架构而有所不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值