1.0变更

因本地原因,k8s系列之二集群环境搭建以及插件安装 中的虚拟机更换为以下3个节点,后续文章均在此三个节点上实践:

节点ip
k8s-master192.168.200.128
k8s-node1192.168.200.129
k8s-node2192.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。

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐