如果我们写一个程序A,使用Pod部署10个副本组成集群,我们知道这个Pod是动态的,列如说某个Pod重启了,它的地址就变了。前一会你用10.244.234.80这个地址访问,过一会就变成10.244.234.90了,这样谁也无法接受。
怎么办呢?我们就想到,弄一个地址不变的总接口,然后让这个接口把流量均衡到各个Pod上面,这样既兼顾了集群,又兼顾了统一,如下所示:

这就是service的由来。
(1)部署
my-go-server-deploy-1.0.yml
第一开启10个my-go-server的副本(程序监听端口9900),使用Deployment来部署。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: my-go-server
name: my-go-server
spec:
replicas: 10 #部署10个副本,可以自定义
selector:
matchLabels:
app: my-go-server
template:
metadata:
labels:
app: my-go-server
spec:
containers:
- image: crpi-gj5arn39i861t1eu.cn-hangzhou.personal.cr.aliyuncs.com/dev-team-sz/my-go-server:1.0
name: my-go-serverk8s执行
kubectl apply -f my-go-server-deploy-1.0.yml(2)部署service,文件:
my-go-server-service-clusterIP.yml
apiVersion: v1
kind: Service
metadata:
labels:
app: my-go-server
name: my-go-server
spec:
selector: #选择pod
app: my-go-server
ports:
- port: 9905
protocol: TCP
targetPort: 9900这个service使用的是简易形式,先将问题简化,后面再一步步复杂。service的拓扑如下:

流程:用户请求service的9905端口,然后service将请求随机的转发到后方的某个Pod。
k8s部署service
kubectl apply -f my-go-server-service-clusterIP.yml
service部署之后,入口就有了,那么第一就需要为这个入口分配一个IP。k8s会从集群的IP池中取一个分配给service,用于集群内通信,这个IP就称为ClusterIP。
我们查看一下实际分配的结果:
# master节点上执行
kubectl get svc my-go-server -o wide
#结果
my-go-server ClusterIP 10.96.96.199 ...可以看到是10.96.96.199(由于作者的实验常常重启,这个地址也会变化,只需知道是10.96.xx的样式即可),我们观察到,它和Pod的网络段不一样。拓扑图如下:

和以往的操作一样,这个ClusterIP也是个虚拟IP,依靠NAT进行逻辑上的转发,下面是NAT的流程图:

观察这个流程,发现也比较简易。这个NAT规则在每个节点机上都有一份。现以node2节点上执行
http://10.96.96.199:9905/user来说明大致的流程:
通俗的说就是:每部署一个service,NAT就固定写一个到此IP的规则,如果有10个服务,就写10条固定的规则。其次,集群每个节点机上都有一套这样的规则,那么就实现了每台机器上都可以访问service。
我们还记得每个Pod在集群内都有唯一的IP地址,因此调度的时候也不会混乱,直接转发就可以了。
下面看下各个链是怎么写的。
下面的规则就是每增加一个服务,就增加一条这样的固定规则。
-A KUBE-SERVICES -d 10.96.200.158/32 -p tcp -m comment
--comment "default/my-go-server cluster IP"
-m tcp --dport 9905 -j KUBE-SVC-MTSFM4LIWSGO5GQC解释:
完整意思是:如果目标IP为10.96.200.158/32且端口等于9905,那么就跳转到KUBE-SVC-MTSFM4LIWSGO5GQC处理。
再看我们发出的请求:
http://10.96.96.199:9905/user,匹配这个规则因此命中。
这条链主要添加调度链,由于我们有10个副本,因此加了十条。
-A KUBE-SVC-MTSFM4LIWSGO5GQC
-m comment --comment "default/my-go-server"
-m statistic --mode random --probability 0.10000000009
-j KUBE-SEP-GZLE4PE5OB5BAAXS完整意思是:有10%的概率会命中KUBE-SEP-GZLE4PE5OB5BAAXS链进行处理。
下面再看下KUBE-SEP-GZLE4PE5OB5BAAXS链。
主要有两条链组成:
#第一条
-A KUBE-SEP-GZLE4PE5OB5BAAXS
-s 10.244.169.183/32
-m comment --comment "default/my-go-server"
-j KUBE-MARK-MASQ
#第二条
-A KUBE-SEP-GZLE4PE5OB5BAAXS
-p tcp -m comment --comment "default/my-go-server"
-m tcp -j DNAT --to-destination 10.244.169.183:9900由于和前面的规则很类似,这里简化写一下:
第一条:如果是从10.244.169.183/32里面发出的,就将它标记一下,后续进行SNAT转换。
第二条:将地址进行DNAT转换,目标地址改为10.244.169.183:9900(最终目标达成!)。
为什么要进行SNAT转换呢?看下拓扑图:

如图所示的小红人,如果我们就在容器内部执行:
http://10.96.96.199:9905/user,那么就是Pod的IP,因此要进行SNAT转换,不然数据回不来了。
像上面样的规则一共有10对,由于我们有10个副本。
在工作节点中,我们前面写了kube-node容器,另外还有一个就是kube-proxy容器。

kube-proxy会监控service资源的变化,进而生成iptables的规则。列如删除service时,kube-proxy会清理NAT表中的相关规则。这里就不再详写。
#删除service
kubectl delete svc my-go-server
#删除deploy
kubectl delete deploy my-go-server
#删除部署文件
rm -rf my-go-server-service-clusterIP.yml这篇详细分析了services实现的细节,下面是整个过程:
需要注意的是,ClusterIP是集群内部IP,只在集群内部使用。