1.0变更
因本地原因,k8s系列之二集群环境搭建以及插件安装 中的虚拟机更换为以下3个节点,后续文章均在此三个节点上实践:
节点 | ip |
---|---|
k8s-master | 192.168.200.128 |
k8s-node1 | 192.168.200.129 |
k8s-node2 | 192.168.200.130 |
假设Pod中的容器很可能因为各种原因发生故障而死掉。Deployment等Controller会通过动态创建和销毁Pod来保证应用整体的健壮性。换句话说,Pod是脆弱的,但应用是健壮的。
每个Pod都有自己的IP地址。当Controller用新Pod替代发生故障的Pod时,新Pod会分配到新的IP地址。这样就产生了⼀个问题:如果⼀组Pod对外提供服务(比如HTTP),它们的IP很有可能发⽣变化,那么客户端如何找到并访问这个服务呢?Kubernetes给出的解决方案是Service。
1.1 Service是什么
由于Pod的动态性,Service解决了一个重要问题:如果一组Pod(称为“后端”)为其他Pod(称为“前端”)提供服务,前端如何找到并连接到后端的IP地址。Kubernetes中的Service为此提供了解决方案,通过提供稳定的虚拟IP和DNS,使服务发现变得简单而可靠。
1.2 Service的几种类型
(1)ClusterIP
ClusterIP 服务是 Kubernetes 的默认服务。它给你一个集群内的服务,集群内的其它应用都可以访问该服务,但是集群外部无法访问它。
(2)NodePort
除了只在内部访问的服务,我们总有很多是需要暴露出来公开访问的服务吧。在ClusterIP基础上为Service在每台机器上绑定一个端口,这样就可以通过:NodePort来访问这些服务。
(3)LoadBalancer
LoadBalancer 服务是暴露服务到 internet 的标准方式,它借助Cloud Provider创建一个外部的负载均衡器,并将请求转发到:NodePort(向节点导流)。
1.3 ClusterIP
先创建Deployment。
vi httpd.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 3
selector: # 添加这一行,定义标签选择器
matchLabels:
run: httpd
template:
metadata:
labels:
run: httpd
spec:
containers:
- name: httpd
image: httpd
ports:
- containerPort: 80
我们启动了三个Pod,运行httpd镜像,label是run: httpd,Service将会用这个label来挑选Pod。
kubectl get pod -o wide
Pod分配了各自的IP,这些IP只能被KubernetesCluster中的容器和节点访问,如图所示。
[root@k8s-master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
httpd-ff8d77b9b-dpp79 1/1 Running 1 43h 10.244.2.104 k8s-node2 <none> <none>
httpd-ff8d77b9b-n5w6x 1/1 Running 1 43h 10.244.2.103 k8s-node2 <none> <none>
httpd-ff8d77b9b-t6jhg 1/1 Running 1 43h 10.244.2.105
[root@k8s-master ~]# curl 10.244.2.103
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 10.244.2.104
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 10.244.2.105
<html><body><h1>It works!</h1></body></html>
接下来创建Service,其配置文件如下。
vi httpdservice.yaml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
selector:
run: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
执⾏kubectl apply创建Service httpd-svc,查看service
kubectl get service
[root@k8s-master ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc ClusterIP 10.1.216.8 <none> 8080/TCP 43h
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 10d
httpd-svc分配到⼀个CLUSTER-IP 10.1.216.8。可以通过该IP访问后端的httpd Pod,如图。
[root@k8s-master ~]# curl 10.1.216.8:8080
<html><body><h1>It works!</h1></body></html>
通过kubectl describe可以查看httpd-svc与Pod的对应关系,如图
[root@k8s-master ~]# kubectl describe service httpd-svc
Name: httpd-svc
Namespace: default
Labels: <none>
Annotations: Selector: run=httpd
Type: ClusterIP
IP: 10.1.216.8
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.244.2.103:80,10.244.2.104:80,10.244.2.105:80
Session Affinity: None
Events: <none>
Endpoints罗列了三个Pod的IP和端口。我们知道Pod的IP是在容器中配置的,那么Service的Cluster IP又是配置在哪⾥的呢?CLUSTER-IP又是如何映射到Pod IP的呢?
答案是iptables。
1.4 Cluster IP底层实现
ClusterIP是⼀个虚拟IP,是由Kubernetes节点上的iptables规则管理的。可以通过iptables-save命令打印出当前节点的iptables规则,因为输出较多,这里只截取与httpd-svcClusterIP10.1.216.8相关的信息,如下。
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.216.8/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 808 0 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.216.8/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-RL3JAE4GN7VOGDGP
这两条规则的含义是:
(1)如果Cluster内的Pod(源地址来⾃10.244.0.0/16)要访问
httpd-svc,则允许。
(2)其他源地址访问httpd-svc,跳转到规则KUBE-SVC-
RL3JAE4GN7VOGDGP。KUBE-SVC-RL3JAE4GN7VOGDGP规则如下。
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.33333333349 -j KU BE-SEP-EQHMOIHNIUPMH73P
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.50000000000 -j KU BE-SEP-7VYTM2IXTFTHGXZQ
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -j KUBE-SEP-6OZMKNJKUPLE2HZF
(1)1/3的概率跳转到规则KUBE-SEP-EQHMOIHNIUPMH73P。
(2)1/3的概率(剩下2/3的⼀半)跳转到规则KUBE-SEP-7VYTM2IXTFTHGXZQ。
(3)1/3的概率跳转到规则KUBE-SEP-6OZMKNJKUPLE2HZF。
-A KUBE-SEP-EQHMOIHNIUPMH73P -s 10.244.2.106/32 -m comment --comment "default/httpd-svc:" -j KUBE-MARK-MASQ
-A KUBE-SEP-EQHMOIHNIUPMH73P -p tcp -m comment --comment "default/httpd-svc:" -m tcp -j DNAT --to-destination 10.244.2.106:80
-A KUBE-SEP-7VYTM2IXTFTHGXZQ -s 10.244.2.107/32 -m comment --comment "default/httpd-svc:" -j KUBE-MARK-MASQ
-A KUBE-SEP-7VYTM2IXTFTHGXZQ -p tcp -m comment --comment "default/httpd-svc:" -m tcp -j DNAT --to-destination 10.244.2.107:80
-A KUBE-SEP-6OZMKNJKUPLE2HZF -s 10.244.2.108/32 -m comment --comment "default/httpd-svc:" -j KUBE-MARK-MASQ
-A KUBE-SEP-6OZMKNJKUPLE2HZF -p tcp -m comment --comment "default/httpd-svc:" -m tcp -j DNAT --to-destination 10.244.2.108:80
可以看到是将请求分别转发到后端的三个Pod。
通过上面的分析,我们得到结论:iptables将访问Service的流量转发到后端Pod,而且使用类似轮询的负载均衡策略。
1.5 DNS访问Service
kubeadm部署时会默认安装kube-dns组件。
[root@k8s-master ~]# kubectl get deployment --namespace=kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 12d
metrics-server 1/1 1 1 12d
kube-dns是⼀个DNS服务器。每当有新的Service被创建,kube-dns会添加该Service的DNS记录。Cluster中的Pod可以通过<SERVICE_NAME>.<NAMESPACE_NAME>访问Service。比如可以用httpd-svc.default访问Servicehttpd-svc。
[root@k8s-master ~]# kubectl run busybox --rm -ti --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # curl httpd-svc.default:8080
/bin/sh: curl: not found
/ # wget httpd-svc.default:8080
Connecting to httpd-svc.default:8080 (10.1.216.8:8080)
saving to 'index.html'
index.html 100% |******************************************************************************************| 45 0:00:00 ETA
'index.html' saved
如果要访问其他namespace中的Service,就必须带上namesapce了。在kube-public中部署Service httpd2-svc。
vi httpd2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd2
namespace: kube-public
spec:
replicas: 1
selector:
matchLabels:
run: httpd2
template:
metadata:
labels:
run: httpd2
spec:
containers:
- name: httpd2
image: httpd
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpd2-svc
namespace: kube-public
spec:
selector:
run: httpd2
ports:
- protocol: TCP
port: 8080
targetPort: 80
通过namespace:kube-public指定资源所属的namespace。多个资源可以在⼀个YAML⽂件中定义,⽤“—”分割。执⾏kubectlapply创建资源。
[root@k8s-master ~]# kubectl apply -f httpd2.yaml
deployment.apps/httpd2 created
service/httpd2-svc unchanged
[root@k8s-master ~]# kubectl get service --namespace=kube-public
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd2-svc ClusterIP 10.1.19.55 <none> 8080/TCP 9m15s
[root@k8s-master ~]# kubectl run busybox --rm -ti --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd2-svc.kube-public:8080
Connecting to httpd2-svc.kube-public:8080 (10.1.19.55:8080)
saving to 'index.html'
index.html 100% |******************************************************************************************| 45 0:00:00 ETA
'index.html' saved
1.5 NodePort
Service通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过:访问Service。
下面我们来实践NodePort,Service httpd-svc的配置文件修改。
vi httpdservice.yaml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
添加type: NodePort,重新创建httpd-svc
[root@k8s-master ~]# vi httpdservice.yaml
[root@k8s-master ~]# kubectl apply -f httpdservice.yaml
service/httpd-svc configured
[root@k8s-master ~]# kubectl get service httpd-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc NodePort 10.1.216.8 <none> 8080:30560/TCP 2d23h
Kubernetes依然会为httpd-svc分配⼀个ClusterIP,不同的是:
(1)EXTERNAL-IP为nodes,表示可通过Cluster每个节点自身的IP访问Service。
(2)PORT(S)为8080:30560。8080是ClusterIP监听的端口,30560则是节点上监听的端口。Kubernetes会从30000〜32767中分配⼀个可用的端口,每个节点都会监听此端⼝并将请求转发给Service。
测试NodePort是否正常工作,通过三个节点IP+30560端口都能够访问httpd-svc。:
[root@k8s-master ~]# curl 192.168.200.128:30560
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 192.168.200.129:30560
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 192.168.200.130:30560
<html><body><h1>It works!</h1></body></html>
Kubernetes是如何将:映射到Pod的呢?执行iptables-save
与ClusterIP⼀样,也是借助了iptables。与ClusterIP相比,每个节点的iptables中都增加了下面两条规则。
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30560 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30560 -j KUBE-SVC-RL3JAE4GN7VOGDGP
规则的含义是:访问当前节点32312端口的请求会应用规则KUBE-
SVC-RL3JAE4GN7VOGDGP,内容如下
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-EQHMOIHNIUPMH73P
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-7VYTM2IXTFTHGXZQ
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m comment --comment "default/httpd-svc:" -j KUBE-SEP-6OZMKNJKUPLE2HZF
其作用就是负载均衡到每⼀个Pod。
NodePort默认的是随机选择,不过我们可以用nodePort指定某个特定端口。
vi httpdservice.yaml
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
nodePort: 31000
port: 8080
targetPort: 80
现在配置文件中就有三个Port了:
- nodePort是节点上监听的端口。
- port是ClusterIP上监听的端口。
- targetPort是Pod监听的端口。
[root@k8s-master ~]# vi httpdservice.yaml
[root@k8s-master ~]# kubectl apply -f httpdservice.yaml
service/httpd-svc configured
[root@k8s-master ~]# curl 192.168.200.130:31000
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 192.168.200.128:31000
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 192.168.200.129:31000
<html><body><h1>It works!</h1></body></html>
最终,Node和ClusterIP在各自端口上接收到的请求都会通过 iptables转发到Pod的targetPort。