有没有想过,在Kubernetes集群中成千上万的容器是如何相互通信的?
为什么一个Pod能够轻松找到另一个Pod?为什么Service能够自动负载均衡?
今天,一起来看看这个容器编排平台是如何构建了一条条"隐形高速公路",让容器间的通信如丝般顺滑。
K8s网络的"三个基本要求",简单却不简单
在深入了解K8s网络原理之前,我们先要明白K8s对网络的三个基本要求:
- 所有Pod都能相互通信:不管Pod在哪个节点上,都能直接通信,就像在同一个局域网
- 节点能与所有Pod通信:节点上的进程能够与任何Pod通信
- Pod看到的IP是其他Pod看到的IP:没有NAT转换,IP地址保持一致
这三个要求看似简单,实际上却定义了整个K8s网络的架构基础。想象一下,如果你要在一个有1000个节点的集群中实现这三点,难度可想而知。
K8s网络模型:从混沌到有序
K8s的网络模型就像一个精心设计的交通系统,有不同层级的"道路":### 网络的四个层次

- 容器到容器通信:同一个Pod内的容器通过localhost通信
- Pod到Pod通信:CNI插件负责实现跨节点的Pod通信
- Pod到Service通信:kube-proxy负责服务发现和负载均衡
- 外部到Service通信:通过NodePort、LoadBalancer或Ingress暴露服务
每一层都有其独特的职责,共同构成了K8s的网络体系。
深入Pod网络:容器的"专属公寓"
在K8s中,Pod是最小的部署单元。每个Pod都有自己的IP地址,就像每个公寓都有自己的门牌号。但Pod内部是如何组织网络的呢?
Pod网络原理
一个Pod可以包含多个容器,这些容器共享:
- 网络命名空间(Network Namespace)
- IP地址和端口空间
- 网络接口
这就像同一个公寓里的室友,他们共享一个地址,但可以有自己的房间(端口)。
#!/bin/bash
# Pod网络实现原理演示
# 1. 创建网络命名空间(模拟Pod)
echo "=== 创建Pod网络环境 ==="
sudo ip netns add pod1
sudo ip netns add pod2
# 2. 创建veth对(虚拟网卡对)
echo "=== 创建虚拟网卡对 ==="
sudo ip link add veth0 type veth peer name veth1
sudo ip link add veth2 type veth peer name veth3
# 3. 将veth的一端放入Pod命名空间
echo "=== 配置Pod网络接口 ==="
sudo ip link set veth1 netns pod1
sudo ip link set veth3 netns pod2
# 4. 配置Pod内的网络
echo "=== 配置Pod内IP地址 ==="
# Pod1配置
sudo ip netns exec pod1 ip addr add 10.244.1.10/24 dev veth1
sudo ip netns exec pod1 ip link set veth1 up
sudo ip netns exec pod1 ip link set lo up
# Pod2配置
sudo ip netns exec pod2 ip addr add 10.244.1.11/24 dev veth3
sudo ip netns exec pod2 ip link set veth3 up
sudo ip netns exec pod2 ip link set lo up
# 5. 创建网桥(模拟CNI功能)
echo "=== 创建CNI网桥 ==="
sudo ip link add name cni0 type bridge
sudo ip link set cni0 up
sudo ip addr add 10.244.1.1/24 dev cni0
# 6. 将veth的另一端连接到网桥
echo "=== 连接Pod到网桥 ==="
sudo ip link set veth0 master cni0
sudo ip link set veth0 up
sudo ip link set veth2 master cni0
sudo ip link set veth2 up
# 7. 配置Pod的默认路由
echo "=== 配置Pod路由 ==="
sudo ip netns exec pod1 ip route add default via 10.244.1.1
sudo ip netns exec pod2 ip route add default via 10.244.1.1
# 8. 测试Pod间通信
echo "=== 测试Pod间通信 ==="
echo "Pod1 ping Pod2:"
sudo ip netns exec pod1 ping -c 3 10.244.1.11
# 显示网络配置
echo "=== 查看网络配置 ==="
echo "Pod1网络配置:"
sudo ip netns exec pod1 ip addr
echo ""
echo "Pod2网络配置:"
sudo ip netns exec pod2 ip addr
echo ""
echo "主机网桥配置:"
ip addr show cni0
# Go语言实现的简化版CNI插件
cat > simple-cni.go << 'EOF'
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
)
// CNI配置
type NetConf struct {
CNIVersion string `json:"cniVersion"`
Name string `json:"name"`
Type string `json:"type"`
Bridge string `json:"bridge"`
IPAM IPAMConfig `json:"ipam"`
}
type IPAMConfig struct {
Type string `json:"type"`
Subnet string `json:"subnet"`
}
// CNI结果
type Result struct {
CNIVersion string `json:"cniVersion"`
IPs []IPConfig `json:"ips"`
}
type IPConfig struct {
Version string `json:"version"`
Address string `json:"address"`
Gateway string `json:"gateway"`
}
func main() {
// 解析CNI配置
var conf NetConf
json.NewDecoder(os.Stdin).Decode(&conf)
// 获取CNI环境变量
containerID := os.Getenv("CNI_CONTAINERID")
netns := os.Getenv("CNI_NETNS")
ifname := os.Getenv("CNI_IFNAME")
switch os.Getenv("CNI_COMMAND") {
case "ADD":
err := addNetwork(conf, containerID, netns, ifname)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
case "DEL":
err := delNetwork(conf, containerID, netns, ifname)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
}
func addNetwork(conf NetConf, containerID, netns, ifname string) error {
// 1. 创建veth pair
hostVeth := fmt.Sprintf("veth%s", containerID[:8])
cmd := exec.Command("ip", "link", "add", hostVeth, "type", "veth", "peer", "name", ifname)
if err := cmd.Run(); err != nil {
return err
}
// 2. 将容器端接口移到容器网络命名空间
cmd = exec.Command("ip", "link", "set", ifname, "netns", netns)
if err := cmd.Run(); err != nil {
return err
}
// 3. 在容器内配置IP
ipAddr := allocateIP(conf.IPAM.Subnet) // 简化的IP分配
cmd = exec.Command("ip", "netns", "exec", netns, "ip", "addr", "add", ipAddr, "dev", ifname)
if err := cmd.Run(); err != nil {
return err
}
// 4. 启动容器内接口
cmd = exec.Command("ip", "netns", "exec", netns, "ip", "link", "set", ifname, "up")
if err := cmd.Run(); err != nil {
return err
}
// 5. 将主机端接口加入网桥
cmd = exec.Command("ip", "link", "set", hostVeth, "master", conf.Bridge)
if err := cmd.Run(); err != nil {
return err
}
// 6. 启动主机端接口
cmd = exec.Command("ip", "link", "set", hostVeth, "up")
if err := cmd.Run(); err != nil {
return err
}
// 7. 返回结果
result := Result{
CNIVersion: conf.CNIVersion,
IPs: []IPConfig{
{
Version: "4",
Address: ipAddr,
Gateway: getGateway(conf.IPAM.Subnet),
},
},
}
return json.NewEncoder(os.Stdout).Encode(result)
}
func delNetwork(conf NetConf, containerID, netns, ifname string) error {
// 删除主机端veth接口(容器端会自动删除)
hostVeth := fmt.Sprintf("veth%s", containerID[:8])
cmd := exec.Command("ip", "link", "del", hostVeth)
return cmd.Run()
}
func allocateIP(subnet string) string {
// 简化的IP分配逻辑
// 实际应该使用IPAM插件管理IP地址池
return "10.244.1.100/24"
}
func getGateway(subnet string) string {
// 返回网关地址
return "10.244.1.1"
}
EOF
echo "CNI插件示例代码已创建: simple-cni.go"
Service网络魔法:负载均衡的秘密
Service是K8s中的一个抽象概念,它为一组Pod提供稳定的网络访问入口。但Service是如何实现负载均衡的呢?答案就在kube-proxy中。
Service的四种类型
- ClusterIP:集群内部IP,只能在集群内访问
- NodePort:在每个节点上开放一个端口
- LoadBalancer:使用云服务商的负载均衡器
- ExternalName:将服务映射到外部DNS名称
kube-proxy的三种代理模式
package main
import (
"fmt"
"net"
"os/exec"
"strings"
)
// Service定义
type Service struct {
Name string
ClusterIP string
Port int
Endpoints []Endpoint
}
type Endpoint struct {
IP string
Port int
}
// kube-proxy userspace模式(已废弃)
func userspaceProxy(svc Service) {
// 在用户空间监听Service IP和端口
listener, _ := net.Listen("tcp", fmt.Sprintf("%s:%d", svc.ClusterIP, svc.Port))
for {
conn, _ := listener.Accept()
go func(c net.Conn) {
// 选择一个后端
backend := selectBackend(svc.Endpoints)
// 建立到后端的连接
backendConn, _ := net.Dial("tcp", fmt.Sprintf("%s:%d", backend.IP, backend.Port))
// 双向复制数据
go io.Copy(backendConn, c)
io.Copy(c, backendConn)
}(conn)
}
}
// kube-proxy iptables模式
func iptablesProxy(svc Service) {
// 创建Service链
createServiceChain(svc)
// 为每个端点创建规则
for i, ep := range svc.Endpoints {
createEndpointRule(svc, ep, i)
}
// 创建负载均衡规则(轮询)
createLoadBalancingRules(svc)
}
func createServiceChain(svc Service) {
// 创建Service专用链
chainName := fmt.Sprintf("KUBE-SVC-%s", svc.Name)
cmd := exec.Command("iptables", "-t", "nat", "-N", chainName)
cmd.Run()
// 将流量导向Service链
rule := fmt.Sprintf("-d %s/32 -p tcp --dport %d -j %s",
svc.ClusterIP, svc.Port, chainName)
cmd = exec.Command("iptables", "-t", "nat", "-A", "KUBE-SERVICES", rule)
cmd.Run()
}
func createEndpointRule(svc Service, ep Endpoint, index int) {
// 为每个端点创建规则
svcChain := fmt.Sprintf("KUBE-SVC-%s", svc.Name)
epChain := fmt.Sprintf("KUBE-SEP-%s-%d", svc.Name, index)
// 创建端点链
cmd := exec.Command("iptables", "-t", "nat", "-N", epChain)
cmd.Run()
// DNAT到实际的Pod
rule := fmt.Sprintf("-p tcp -j DNAT --to-destination %s:%d", ep.IP, ep.Port)
cmd = exec.Command("iptables", "-t", "nat", "-A", epChain, rule)
cmd.Run()
// 添加概率跳转(负载均衡)
probability := 1.0 / float64(len(svc.Endpoints) - index)
rule = fmt.Sprintf("-m statistic --mode random --probability %.4f -j %s",
probability, epChain)
cmd = exec.Command("iptables", "-t", "nat", "-A", svcChain, rule)
cmd.Run()
}
// kube-proxy IPVS模式
func ipvsProxy(svc Service) {
// 创建IPVS虚拟服务器
createIPVSService(svc)
// 添加真实服务器(后端Pod)
for _, ep := range svc.Endpoints {
addIPVSRealServer(svc, ep)
}
}
func createIPVSService(svc Service) {
// 使用ipvsadm创建虚拟服务器
cmd := exec.Command("ipvsadm", "-A", "-t",
fmt.Sprintf("%s:%d", svc.ClusterIP, svc.Port),
"-s", "rr") // 轮询算法
cmd.Run()
}
func addIPVSRealServer(svc Service, ep Endpoint) {
// 添加真实服务器
cmd := exec.Command("ipvsadm", "-a", "-t",
fmt.Sprintf("%s:%d", svc.ClusterIP, svc.Port),
"-r", fmt.Sprintf("%s:%d", ep.IP, ep.Port),
"-m") // masquerading模式
cmd.Run()
}
// IPVS支持的调度算法
var IPVSSchedulers = []string{
"rr", // Round Robin
"wrr", // Weighted Round Robin
"lc", // Least Connection
"wlc", // Weighted Least Connection
"sh", // Source Hashing
"dh", // Destination Hashing
"sed", // Shortest Expected Delay
"nq", // Never Queue
}
// 性能对比
func comparePerformance() {
fmt.Println("kube-proxy模式性能对比:")
fmt.Println("\nUserspace模式(已废弃):")
fmt.Println("- 性能最差,每个连接都要经过用户空间")
fmt.Println("- 延迟高,CPU占用大")
fmt.Println("- 支持的连接数有限")
fmt.Println("\niptables模式(默认):")
fmt.Println("- 性能中等,使用内核netfilter")
fmt.Println("- 规则数量多时性能下降")
fmt.Println("- 更新规则时有短暂中断")
fmt.Println("\nIPVS模式(推荐):")
fmt.Println("- 性能最好,专为负载均衡设计")
fmt.Println("- 支持多种调度算法")
fmt.Println("- 规则更新更平滑")
fmt.Println("- 内存占用更少")
}
// 查看当前模式
func getCurrentMode() string {
// 检查IPVS
cmd := exec.Command("ipvsadm", "-L", "-n")
if err := cmd.Run(); err == nil {
return "ipvs"
}
// 检查iptables规则
cmd = exec.Command("iptables", "-t", "nat", "-L", "KUBE-SERVICES")
out, _ := cmd.Output()
if strings.Contains(string(out), "KUBE-SVC") {
return "iptables"
}
return "unknown"
}
func main() {
// 示例Service
service := Service{
Name: "my-service",
ClusterIP: "10.96.0.10",
Port: 80,
Endpoints: []Endpoint{
{IP: "10.244.1.10", Port: 8080},
{IP: "10.244.1.11", Port: 8080},
{IP: "10.244.2.10", Port: 8080},
},
}
fmt.Printf("Service: %s (%s:%d)\n", service.Name, service.ClusterIP, service.Port)
fmt.Println("Endpoints:")
for _, ep := range service.Endpoints {
fmt.Printf(" - %s:%d\n", ep.IP, ep.Port)
}
fmt.Printf("\n当前kube-proxy模式: %s\n", getCurrentMode())
// 显示性能对比
comparePerformance()
}
CNI插件生态,网络实现的百花齐放
K8s使用CNI(Container Network Interface)规范来实现网络功能。不同的CNI插件有不同的特性:
主流CNI插件对比
-
Flannel:
- 最简单的CNI插件
- 基于Overlay网络(VXLAN)
- 适合小规模集群
-
Calico:
- 基于BGP路由
- 支持网络策略
- 性能优秀,适合大规模集群
-
Cilium:
- 基于eBPF技术
- 强大的可观测性
- 支持高级网络策略
-
Weave Net:
- 自动网络发现
- 内置加密功能
- 易于安装和使用
CNI工作流程

网络策略,K8s的"防火墙"
NetworkPolicy是K8s提供的网络安全机制,它可以控制Pod之间的网络流量。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-netpolicy
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 3306
这个策略的含义是:
- 应用到标签为
app=web的Pod - 只允许
app=frontend的Pod访问80端口 - 只允许访问
app=database的Pod的3306端口
实际应用案例
案例1:跨节点Pod通信故障排查
当Pod无法跨节点通信时,可以按以下步骤排查:
# 1. 检查Pod IP
kubectl get pods -o wide
# 2. 进入Pod测试连通性
kubectl exec -it pod-a -- ping <pod-b-ip>
# 3. 检查路由表
kubectl exec -it pod-a -- route -n
# 4. 检查CNI配置
cat /etc/cni/net.d/*.conf
# 5. 检查节点间连通性
ping <other-node-ip>
# 6. 检查iptables规则
iptables -t nat -L -n | grep <service-ip>
# 7. 检查CNI日志
journalctl -u kubelet | grep -i cni
案例2:Service负载均衡不均问题
// 测试Service负载均衡
package main
import (
"fmt"
"io/ioutil"
"net/http"
"time"
)
func main() {
serviceURL := "http://my-service.default.svc.cluster.local"
// 统计各个后端的请求分布
backends := make(map[string]int)
for i := 0; i < 1000; i++ {
resp, err := http.Get(serviceURL)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
body, _ := ioutil.ReadAll(resp.Body)
backend := string(body) // 假设后端返回自己的标识
backends[backend]++
resp.Body.Close()
time.Sleep(10 * time.Millisecond)
}
// 打印统计结果
fmt.Println("负载均衡统计:")
for backend, count := range backends {
fmt.Printf("%s: %d (%.2f%%)\n", backend, count, float64(count)/10)
}
}
案例3:网络性能优化
# 1. 使用iperf测试网络带宽
# 在接收端Pod
kubectl exec -it pod-server -- iperf3 -s
# 在发送端Pod
kubectl exec -it pod-client -- iperf3 -c <server-pod-ip>
# 2. 调整CNI插件MTU
# 编辑CNI配置
vim /etc/cni/net.d/10-flannel.conflist
# 添加MTU配置
"mtu": 1450
# 3. 优化kube-proxy模式
# 修改kube-proxy配置为IPVS模式
kubectl edit configmap -n kube-system kube-proxy
# 修改mode: "ipvs"
# 4. 启用网络加速
# 对于Calico,启用eBPF数据平面
kubectl patch felixconfiguration default --type='merge' -p '{"spec":{"bpfEnabled":true}}'
高级话题:Service Mesh与eBPF
Service Mesh:更精细的流量管理
Service Mesh如Istio在K8s网络之上提供了更高级的功能:
# Istio VirtualService示例
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: my-app
spec:
hosts:
- my-app
http:
- match:
- headers:
version:
exact: v2
route:
- destination:
host: my-app
subset: v2
weight: 100
- route:
- destination:
host: my-app
subset: v1
weight: 90
- destination:
host: my-app
subset: v2
weight: 10
eBPF:网络性能的未来
eBPF技术正在革新K8s网络栈:
// eBPF程序示例(简化)
func tcFilter() {
// 在内核中运行的eBPF程序
// 可以直接处理网络包,无需经过iptables
// 解析包头
eth := parseEthernet(skb)
if eth.Proto != ETH_P_IP {
return TC_ACT_OK
}
ip := parseIP(skb)
if ip.Protocol != IPPROTO_TCP {
return TC_ACT_OK
}
// 应用网络策略
if !allowedByPolicy(ip.Src, ip.Dst) {
return TC_ACT_SHOT // 丢弃包
}
// 负载均衡
backend := selectBackend(ip.Dst)
rewriteDestination(skb, backend)
return TC_ACT_REDIRECT
}
面试必备知识点
1. K8s网络模型的核心原则是什么?
答案:
- 每个Pod拥有唯一的IP地址
- Pod之间可以直接通过IP地址通信,无需NAT
- 节点与Pod之间可以相互通信
- Pod看到的自己的IP与其他Pod看到的一致
2. Service的几种类型及其区别?
答案:
- ClusterIP:默认类型,只能在集群内部访问
- NodePort:在每个节点上开放指定端口,端口范围30000-32767
- LoadBalancer:使用云服务商的负载均衡器,适用于云环境
- ExternalName:将服务映射到外部DNS名称,不创建代理
3. kube-proxy的工作模式有哪些?
答案:
- userspace:最早的模式,性能差,已废弃
- iptables:默认模式,使用netfilter规则,规则多时性能下降
- ipvs:性能最好,支持多种负载均衡算法,推荐使用
4. CNI插件的作用是什么?
答案:
CNI(Container Network Interface)是K8s的网络插件接口规范,主要作用:
- 为Pod分配IP地址
- 配置Pod的网络接口
- 设置路由规则
- 实现跨节点Pod通信
5. 如何实现Pod间的网络隔离?
答案:
使用NetworkPolicy实现网络隔离:
- 定义入站(Ingress)和出站(Egress)规则
- 通过标签选择器指定应用范围
- 可以基于命名空间、Pod标签、端口等进行控制
- 需要网络插件支持(如Calico、Cilium)
总结
K8s网络系统是一个精心设计的分层架构,从底层的CNI插件到上层的Service抽象,每一层都有其特定的职责。理解这些概念和原理,不仅能帮助你更好地运维K8s集群,还能在出现网络问题时快速定位和解决。
记住几个关键点:
- K8s网络是扁平的,所有Pod默认可以相互通信
- Service提供了稳定的访问入口和负载均衡
- CNI插件负责具体的网络实现
- NetworkPolicy提供了网络安全隔离
- 新技术如eBPF正在推动K8s网络的演进
希望这篇文章能帮助你理解K8s网络的"黑科技",让你在容器网络的世界里游刃有余!

7254

被折叠的 条评论
为什么被折叠?



