一、背景介绍
我们这里准备三台机器,一台master,两台node,采用kubeadm的方式进行安装的,安装过程大家可以参照我之前的博文。
IP | 角色 | 版本 |
---|---|---|
192.168.1.200 | master | kubeadm v1.13.0 |
192.168.1.201 | node01 | kubeadm v1.13.0 |
192.168.1.202 | node02 | kubeadm v1.13.0 |
我们不应该期望 Kubernetes Pod 是健壮的,而是要假设 Pod 中的容器很可能因为各种原因发生故障而死掉。Deployment 等 controller 会通过动态创建和销毁 Pod 来保证应用整体的健壮性。换句话说,Pod 是脆弱的,但应用是健壮的。
每个 Pod 都有自己的 IP 地址。当 controller 用新 Pod 替代发生故障的 Pod 时,新 Pod 会分配到新的 IP 地址。这样就产生了一个问题:
如果一组 Pod 对外提供服务(比如 HTTP),它们的 IP 很有可能发生变化,那么客户端如何找到并访问这个服务呢?
Kubernetes 给出的解决方案是 Service。
二、创建 Service
Kubernetes Service 从逻辑上代表了一组 Pod,具体是哪些 Pod 则是由 label 来挑选。Service 有自己 IP,而且这个 IP 是不变的。客户端只需要访问 Service 的 IP,Kubernetes 则负责建立和维护 Service 与 Pod 的映射关系。无论后端 Pod 如何变化,对客户端不会有任何影响,因为 Service 没有变。
1、创建 Deployment
创建文件mytest-deploy.yaml
文件,增加如下内容:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mytest
spec:
replicas: 3
template:
metadata:
labels:
run: mytest
spec:
containers:
- name: mytest
image: wangzan18/mytest:v1
ports:
- containerPort: 80
创建我们的 Pod。
[root@master ~]# kubectl apply -f mytest-deploy.yaml
deployment.extensions/mytest created
[root@master ~]#
[root@master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mytest-88d46bf99-cd4zk 1/1 Running 0 70s 10.244.2.2 node02 <none> <none>
mytest-88d46bf99-fsmcj 1/1 Running 0 70s 10.244.1.3 node01 <none> <none>
mytest-88d46bf99-ntd5n 1/1 Running 0 70s 10.244.1.2 node01 <none> <none>
Pod 分配了各自的 IP,这些 IP 只能被 Kubernetes Cluster 中的容器和节点访问。
2、创建 Service
创建文件mytest-svc.yaml
,新增如下内容:
apiVersion: v1
kind: Service
metadata:
name: mytest-svc
spec:
selector:
run: mytest
ports:
- port: 80
targetPort: 8080
创建service。
[root@master ~]# kubectl apply -f mytest-svc.yaml
service/mytest-svc created
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 33m
mytest-svc ClusterIP 10.100.77.149 <none> 80/TCP 8s
mytest-svc
分配到一个 CLUSTER-IP 10.100.77.149
。可以通过该 IP 访问后端的 mytest Pod。
[root@master ~]# curl 10.100.77.149
Hello Kubernetes bootcamp! | Running on: mytest-88d46bf99-ntd5n | v=1
通过 kubectl describe
可以查看 mytest-svc
与 Pod 的对应关系。
[root@master ~]# kubectl describe svc mytest-svc
Name: mytest-svc
Namespace: default
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"mytest-svc","namespace":"default"},"spec":{"ports":[{"port":80,"t...
Selector: run=mytest
Type: ClusterIP
IP: 10.100.77.149
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 10.244.1.2:8080,10.244.1.3:8080,10.244.2.2:8080
Session Affinity: None
Events: <none>
Endpoints 罗列了三个 Pod 的 IP 和端口。我们知道 Pod 的 IP 是在容器中配置的,那么 Service 的 Cluster IP 又是配置在哪里的呢?CLUSTER-IP 又是如何映射到 Pod IP 的呢?
三、Cluster IP 底层实现
Service Cluster IP 是一个虚拟 IP,是由 Kubernetes 节点上的 iptables 规则管理的。
可以通过 iptables-save
命令打印出当前节点的 iptables 规则,因为输出较多,这里只截取与 httpd-svc
Cluster IP 10.100.77.149
相关的信息:
[root@master ~]# iptables-save |grep 10.100.77.149
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.100.77.149/32 -p tcp -m comment --comment "default/mytest-svc: cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.100.77.149/32 -p tcp -m comment --comment "default/mytest-svc: cluster IP" -m tcp --dport 80 -j KUBE-SVC-XKNZ3BN47GCYFIPJ
这两条规则的含义是:
- 如果 Cluster 内的 Pod(源地址来自 10.244.0.0/16)要访问 mytest-svc,则允许。
- 其他源地址访问
mytest-svc
,跳转到规则KUBE-SVC-XKNZ3BN47GCYFIPJ
。
那我们查看一下KUBE-SVC-XKNZ3BN47GCYFIPJ
规则是什么,内容如下:
-A KUBE-SVC-XKNZ3BN47GCYFIPJ -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-6VUP2B3YLPPLYJJV
-A KUBE-SVC-XKNZ3BN47GCYFIPJ -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ENVKJLELDEHDNVGK
-A KUBE-SVC-XKNZ3BN47GCYFIPJ -j KUBE-SEP-IZPSUB6K7QCCEPS3
- 1/3 的概率跳转到规则
KUBE-SEP-6VUP2B3YLPPLYJJV
。 - 1/3 的概率(剩下 2/3 的一半)跳转到规则
KUBE-SEP-ENVKJLELDEHDNVGK
。 - 1/3 的概率跳转到规则
KUBE-SEP-IZPSUB6K7QCCEPS3
。
上面的三条规则内容分别如下:
转发到Pod 10.244.1.2。
-A KUBE-SEP-6VUP2B3YLPPLYJJV -s 10.244.1.2/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-6VUP2B3YLPPLYJJV -p tcp -m tcp -j DNAT --to-destination 10.244.1.2:8080
转发到Pod 10.244.1.3。
-A KUBE-SEP-ENVKJLELDEHDNVGK -s 10.244.1.3/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-ENVKJLELDEHDNVGK -p tcp -m tcp -j DNAT --to-destination 10.244.1.3:8080
转发到Pod 10.244.2.2。
-A KUBE-SEP-IZPSUB6K7QCCEPS3 -s 10.244.2.2/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-IZPSUB6K7QCCEPS3 -p tcp -m tcp -j DNAT --to-destination 10.244.2.2:8080
可以看到讲请求分别转发到了后端的三个 Pod。由此我们可以看出 iptables
将访问 Service 的流量转发到后端 Pod,而且使用类似轮询的负载均衡策略。
Cluster 的每一个节点都配置了相同的 iptables 规则,这样就确保了整个 Cluster 都能够通过 Service 的 Cluster IP 访问 Service。