欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

k8s——service

程序员文章站 2022-03-12 11:49:49
...

我们不应该期望Kubernetes Pod是健壮的,而是要假设Pod中的容器很可能因为各种原因发生故障而死掉。Deployment等Controller会通过动态创建和销毁Pod来保证应用整体的健壮性。换句话说,Pod是脆弱的,但应用是健壮的

Pod的IP地址是Docker Daemon根据docker0网桥的IP地址段进行分配的,但Service的Cluster IP地址是Kubernetes系统中的虚拟IP地址,由系统动态分配。
Service的Cluster IP地址相对于Pod的IP地址来说相对稳定,Service被创建时即被分配一个IP地址,在销毁该Service之前,这个IP地址都不会再变化了。
而Pod在Kubernetes集群中生命周期较短,可能被ReplicationContrller销毁、再次创建,新创建的Pod将会分配一个新的IP地址

每个Pod都有自己的IP地址。当Controller用新Pod替代发生故障的Pod时,新Pod会分配到新的IP地址,这样就产生了一个问题:如果一组Pod对外提供服务(比如HTTP),它们的IP很有可能发生变化,那么客户端如何找到并访问这个服务呢?

Kubernetes给出的解决方案是Service

Service从逻辑上代表了一组Pod,具体是哪些Pod则是由label来挑选的。Service有自己的IP,而且这个IP是不变的。客户端只需要访问Service的IP,Kubernetes则负责建立和维护Service与Pod的映射关系。无论后端Pod如何变化,对客户端不会有任何影响,因为Service没有变

创建下面的这个Deployment

[[email protected] ~]$ cat httpd.yml 
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

# 注意的是:node节点上的docker服务必须是好的,而且虚拟机要能上网
[[email protected] ~]$ kubectl apply -f httpd.yml 
deployment.apps/httpd created
[[email protected] ~]$ kubectl get pod -o wide  #注意:ip的出现是需要时间的(容器的准备也是需要时间的)
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES
httpd-69cb5b9fdd-cwqk2   1/1     Running   0          14s   10.244.2.22   server3   <none>           <none>
httpd-69cb5b9fdd-wprs9   1/1     Running   0          14s   10.244.1.24   server2   <none>           <none>
httpd-69cb5b9fdd-xj9mp   1/1     Running   0          14s   10.244.1.23   server2   <none>           <none>

我们可以看到的是:Pod分配了各自的IP,这些IP只能被Kubernetes Cluster中的容器和节点访问

[[email protected] ~]$ curl 10.244.2.22
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 10.244.1.24
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 10.244.1.23
<html><body><h1>It works!</h1></body></html>

接下来创建Service

# 前提:之前的httpd pod 不能关闭和删除
[[email protected] ~]$ cat httpd-svc.yml 
apiVersion: v1
kind: Service
metadata:
 name: httpd-svc
spec:
   selector:
    run: httpd
   ports:
     - protocol: TCP
       port: 8080
       targetPort: 80
v1 是Service的apiVersion
kind 指明当前资源的类型为Service
name Service的名字为httpd-svc
selector 指明挑选那些label为run: httpd的Pod作为Service的后端
ports 将Service的8080端口映射到Pod的80端口,使用TCP协议

执行kubectl apply创建Service httpd-svc

[[email protected] ~]$ kubectl apply -f httpd-svc.yml 
service/httpd-svc created
[[email protected] ~]$ kubectl get service
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
httpd-svc    ClusterIP   10.104.247.136   <none>        8080/TCP   9s
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP    22h


# httpd-svc分配到一个CLUSTER-IP

# 以通过该IP 访问后端的httpd Pod
[[email protected] ~]$ curl 10.104.247.136:8080
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 10.104.247.136:8080
<html><body><h1>It works!</h1></body></html>

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

[[email protected] ~]$ kubectl describe service httpd-svc 
Name:              httpd-svc
Namespace:         default
Labels:            <none>
Annotations:       kubectl.kubernetes.io/last-applied-configuration:
                     {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"httpd-svc","namespace":"default"},"spec":{"ports":[{"port":8080,"...
Selector:          run=httpd
Type:              ClusterIP
IP:                10.104.247.136
Port:              <unset>  8080/TCP
TargetPort:        80/TCP
Endpoints:         10.244.1.25:80,10.244.2.23:80,10.244.2.24:80
Session Affinity:  None
Events:            <none>

Cluster IP底层实现

Endpoints罗列了三个Pod的IP和端口。我们知道Pod的IP是在容器中配置的,那么Service的Cluster IP又是配置在哪里的呢?CLUSTER-
IP又是如何映射到Pod IP的呢?

答案是iptables

Cluster IP是一个虚拟IP,是由Kubernetes节点上的iptables规则管理的,可以通过iptables-save命令打印出当前节点的iptables规则

因为输出较多,这里只截取与httpd-svc Cluster IP 相关的信息

# 注意用户的切换:此处使用root用户
[[email protected] ~]# iptables-save |grep httpd-svc
-A KUBE-SERVICES ! -s 10.244.34.0/16 -d 10.104.247.136/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.104.247.136/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-RL3JAE4GN7VOGDGP

解析:

  • (1)如果Cluster内的Pod(源地址来自10.244.34.0/16)要访问httpd-svc,则允许
  • (2)其他源地址访问httpd-svc,跳转到规则KUBE-SVC-
    RL3JAE4GN7VOG DGP。KUBE-SVC-RL3JAE4GN7VOGDGP规则
[[email protected] ~]# iptables-save |grep KUBE-SVC-RL3JAE4GN7VOGDGP
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-N5PH4BBCFTMHGLV6
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UEWSDQT52CMKBNNL
-A KUBE-SVC-RL3JAE4GN7VOGDGP -j KUBE-SEP-BSPNJWUG33XKKPE5

解析:
1/3的概率跳转到规则 KUBE-SEP-N5PH4BBCFTMHGLV6
1/3的概率(剩下2/3的一半)跳转到规则 KUBE-SEP-UEWSDQT52CMKBNNL
1/3的概率跳转到规则 KUBE-SEP-BSPNJWUG33XKKPE5
即将请求分别转发到后端的三个Pod。

通过上面的分析,我们得到结论:iptables将访问Service的流量转发到后端Pod,而且使用类似轮询的负载均衡策略
另外,需要补充一点:Cluster的每一个节点都配置了相同的
iptables规则,这样就确保了整个Cluster都能够通过Service的Cluster IP访问Service

DNS访问Service

  • 在Cluster中,除了可以通过Cluster IP访问Service,Kubernetes还提供了更为方便的DNS访问,kubeadm部署时会默认安装kube-dns组件
  • kube-dns是一个DNS服务器。每当有新的Service被创建,kube-dns会添加该Service的DNS记录。Cluster中的Pod可以通过<SERVICE_NAME>.<NAMESPACE_NAME>访问Service

kubectl get namespace查看已有的namespace

[[email protected] ~]$ kubectl get namespace
NAME              STATUS   AGE
default           Active   23h
kube-node-lease   Active   23h
kube-public       Active   23h
kube-system       Active   23h

每当有新的Service被创建,kube-dns会添加该Service的DNS记录

[[email protected] ~]$ kubectl run busybox --rm -it --image=busybox /bin/sh
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc.default:8080
Connecting to httpd-svc.default:8080 (10.99.215.181:8080)
saving to 'index.html'
index.html           100% |********************************|    45  0:00:00 ETA
'index.html' saved

# 如上所示,我们在一个临时的busyboxPod中验证了DNS的有效性。另外,由于这个Pod与httpd-svc同属于default namespace,因此可以省略default直接用httpd-svc访问Service
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.215.181:8080)
wget: can't open 'index.html': File exists
/ # ls
bin         etc         index.html  root        tmp         var
dev         home        proc        sys         usr
/ # rm -rf index.html 
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.215.181:8080)
saving to 'index.html'
index.html           100% |********************************|    45  0:00:00 ETA
'index.html' saved
/ # ls
bin         etc         index.html  root        tmp         var
dev         home        proc        sys         usr

通过namespace: kube-public 指定资源所属的namespace。多个资源可以在一个YAML文件中定义,用“—”分割。执行kubectl

[[email protected] ~]$ cat httpd2.yml 
apiVersion: apps/v1
kind: Deployment
metadata:
 name: httpd2
 namespace: kube-public
spec:
 replicas: 3
 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
[[email protected] ~]$ kubectl apply -f httpd2.yml 
deployment.apps/httpd2 created
service/httpd2-svc created
[[email protected] ~]$ kubectl get service --namespace=kube-public 
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
httpd2-svc   ClusterIP   10.104.125.247   <none>        8080/TCP   19s

# 在busybox Pod中访问httpd2-svc
# 因为不属于同一个namespace,所以必须使用httpd2-svc.kube-public才能访问到
[[email protected] ~]$ kubectl run busybox --rm -it --image=busybox /bin/sh
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
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.104.125.247:8080)
saving to 'index.html'
index.html           100% |******|    45  0:00:00 ETA
'index.html' saved

注意:Pod与httpd-svc同属于default namespace,因此可以省略default直接用httpd-svc访问Service

外网如何访问Service

除了Cluster内部可以访问Service,很多情况下我们也希望应用的Service能够暴露给Cluster外部。Kubernetes提供了多种类型的Service,默认是ClusterIP

  • (1)ClusterIP
    Service通过Cluster内部的IP对外提供服务,只有Cluster内的节点和Pod可访问,这是默认的Service类型,前面实验中的Service都是ClusterIP

  • (2)NodePort
    Service通过Cluster节点的静态端口对外提供服务。Cluster外部可以通过:访问Service

  • (3)LoadBalancer
    Service利用cloud provider特有的load balancer对外提供服务,cloud provider负责将load balancer的流量导向Service 目前支持的 cloud provider有GCP、AWS、Azur等

下面我们来实践NodePort

[[email protected] ~]$ cat httpd-svc.yml 
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
# 注意点是:要先运行pod 然后将它们打包成一个整体-service
[[email protected] ~]$ kubectl apply -f httpd.yml 
deployment.apps/httpd created
[[email protected] ~]$ kubectl apply -f httpd-svc.yml 
service/httpd-svc created

# Kubernetes依然会为httpd-svc分配一个ClusterIP,不同的是
# PORT(S)为8080:30295 8080是ClusterIP监听的端口,30295则是节点上监听的端口。Kubernetes会从30000~32767中分配一个可用的端口,每个节点都会监听此端口并将请求转发给Service



[[email protected] ~]$ kubectl get service httpd-svc 
NAME        TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
httpd-svc   NodePort   10.98.81.225   <none>        8080:30295/TCP   4s
[[email protected] ~]$ kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES
httpd-69cb5b9fdd-7b6gq   1/1     Running   0          28s   10.244.2.27   server3   <none>           <none>
httpd-69cb5b9fdd-9k6c8   1/1     Running   0          28s   10.244.1.30   server2   <none>           <none>
httpd-69cb5b9fdd-c5rxz   1/1     Running   0          28s   10.244.1.29   server2   <none>           <none>

测试

# 通过三个节点IP+PORT 口都能够访问httpd-svc
[[email protected] ~]$ curl 172.25.0.2:30295
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 172.25.0.3:30295
<html><body><h1>It works!</h1></body></html>

接下来我们深入探讨一个问题:Kubernetes是如何将:映射到Pod的呢?
与ClusterIP一样,也是借助了iptables。与ClusterIP相比,每个节点的iptables中都增加了下面两条规则

[[email protected] ~]# iptables-save |grep 30295
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30295 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30295 -j KUBE-SVC-RL3JAE4GN7VOGDGP
规则的含义是:访问当前节点30295端口的请求会应用规则  KUBE-SVC-RL3JAE4GN7VOGDGP 其作用就是负载均衡到每一个Pod


[[email protected] ~]# iptables-save |grep KUBE-SVC-RL3JAE4GN7VOGDGP
:KUBE-SVC-RL3JAE4GN7VOGDGP - [0:0]
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/httpd-svc:" -m tcp --dport 30295 -j KUBE-SVC-RL3JAE4GN7VOGDGP
-A KUBE-SERVICES -d 10.98.81.225/32 -p tcp -m comment --comment "default/httpd-svc: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-RL3JAE4GN7VOGDGP
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-Y5EOPQEI6R2HKSKZ
-A KUBE-SVC-RL3JAE4GN7VOGDGP -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SACXFJATP2WVU5HD
-A KUBE-SVC-RL3JAE4GN7VOGDGP -j KUBE-SEP-MZTDTCCSTQYQRVK2

NodePort默认的是随机选择,不过我们可以用nodePort指定某个特定端口

[[email protected] ~]$ cat httpd-svc.yml 
apiVersion: v1
kind: Service
metadata:
 name: httpd-svc
spec:
   type: NodePort
   selector:
    run: httpd
   ports:
     - protocol: TCP
       nodePort: 30000
       port: 8080
       targetPort: 80
nodePort 节点上监听的端口
port ClusterIP上监听的端口
targetPort Pod监听的端口

最终,Node和ClusterIP在各自端口上接收到的请求都会通过iptables转发到Pod的targetPort

[[email protected] ~]$ kubectl apply -f httpd-svc.yml 
service/httpd-svc created
[[email protected] ~]$ kubectl get service httpd-svc 
NAME        TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
httpd-svc   NodePort   10.98.239.4   <none>        8080:30000/TCP   10s
[[email protected] ~]$ curl 172.25.0.3:30000
<html><body><h1>It works!</h1></body></html>
[[email protected] ~]$ curl 172.25.0.2:30000
<html><body><h1>It works!</h1></body></html>