k8s学习-通过Service访问Pod

假设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

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。

bash 复制代码
kubectl get pod -o wide

Pod分配了各自的IP,这些IP只能被KubernetesCluster中的容器和节点访问,如图所示。

bash 复制代码
[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   
bash 复制代码
[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

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

bash 复制代码
kubectl get service
bash 复制代码
[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,如图。

bash 复制代码
[root@k8s-master ~]# curl 10.1.216.8:8080
<html><body><h1>It works!</h1></body></html>

通过kubectl describe可以查看httpd-svc与Pod的对应关系,如图

bash 复制代码
[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相关的信息,如下。

bash 复制代码
-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规则如下。

bash 复制代码
-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。

bash 复制代码
-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组件。

bash 复制代码
[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。

bash 复制代码
[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

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创建资源。

bash 复制代码
[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

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

bash 复制代码
[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。:

bash 复制代码
[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中都增加了下面两条规则。

bash 复制代码
-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,内容如下

bash 复制代码
-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

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监听的端口。
bash 复制代码
[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。

相关推荐
lichenyang4534 天前
Docker 学习笔记(四):Dockerfile,把项目打成自己的镜像
docker·容器
lichenyang4534 天前
Docker 学习笔记(三):Docker 网络、bridge、子网和容器互通
docker·容器
lichenyang4534 天前
Docker 学习笔记(二):docker run 的参数到底在控制什么?
docker·容器
运维开发故事7 天前
基于 Arthas 的多集群在线诊断系统设计与实现
kubernetes
Patrick_Wilson8 天前
从「改个端口」到 502:Next.js on k8s 的容器端口、Service 映射与 env 覆盖
docker·kubernetes·next.js
探索云原生9 天前
K8s 1.36 这个 GA 特性,把 initContainer 拉模型的 hack 干掉了
ai·云原生·kubernetes
云恒要逆袭9 天前
运行你的第一个Docker容器
后端·docker·容器
Java之美10 天前
一次k8s升级引发的DevicePlugin注册失败
云原生·kubernetes
程序员老赵11 天前
10 分钟部署 OpenCode:Docker 一键安装,浏览器打开就能用 AI 写代码(附完整命令与排错)
docker·容器·ai编程