Kubernetes(6)service访问pod

文章目录

  • Kubernetes(6)service访问pod
    • 作用和概念
    • 三种IP
    • Service 类型
    • 创建Service
      • Cluster IP底层实现
    • DNS访问Service
    • 外网如何访问Service
      • 实践NodePort

Kubernetes(6)service访问pod

作用和概念

我们不应该期望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

服务,是一个虚拟概念,逻辑上代理后端pod。众所周知,pod生命周期短,状态不稳定,pod异常后新生成的pod ip会发生变化,之前pod的访问方式均不可达。通过service对pod做代理,service有固定的ip和port,ip:port组合自动关联后端pod,即使pod发生改变,kubernetes内部更新这组关联关系,使得service能够匹配到新的pod。这样,通过service提供的固定ip,用户再也不用关心需要访问哪个pod,以及pod是否发生改变,大大提高了服务质量。如果pod使用rc创建了多个副本,那么service就能代理多个相同的pod,通过kube-proxy,实现负载均衡Kubernetes(6)service访问pod_第1张图片

三种IP

Node IP:Node节点的IP地址
Pod IP:Pod的IP地址
Cluster IP:Service的IP地址
首先,Node IP是Kubernetes集群中节点的物理网卡IP地址(一般为内网),所有属于这个网络的服务器之间都可以直接通信,所以Kubernetes集群外要想访问Kubernetes集群内部的某个节点或者服务,肯定得通过Node IP进行通信(这个时候一般是通过外网IP了)
然后Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的(我们这里使用的是flannel这种网络插件保证所有节点的Pod IP不会冲突)
最后Cluster IP是一个虚拟的IP,仅仅作用于Kubernetes Service这个对象,由Kubernetes自己来进行管理和分配地址,当然我们也无法ping这个地址,他没有一个真正的实体对象来响应,他只能结合Service Port来组成一个可以通信的服务。

Service 类型

ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求 :,可以从集群的外部访问一个 NodePort 服务。
LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

创建Service

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

来看个例子,创建下面的这个Deployment
[k8s@server1 ~]$ 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服务必须是好的,而且虚拟机要能上网
[k8s@server1 ~]$ kubectl apply -f httpd.yml 
deployment.apps/httpd created
[k8s@server1 ~]$ 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中的容器和节点访问

[k8s@server1 ~]$ curl 10.244.2.22
<html><body><h1>It works!</h1></body></html>
[k8s@server1 ~]$ curl 10.244.1.24
<html><body><h1>It works!</h1></body></html>
[k8s@server1 ~]$ curl 10.244.1.23
<html><body><h1>It works!</h1></body></html>

# 前提:之前的httpd pod 不能关闭和删除

接下来创建Service
[k8s@server1 ~]$ cat httpd-svc.yml 
apiVersion: v1
kind: Service
metadata:
 name: httpd-svc
spec:
   selector:
    run: httpd
   ports:
     - protocol: TCP
       port: 8080
       targetPort: 80
"""
1 v1是Service的apiVersion
2 指明当前资源的类型为Service
3 Service的名字为httpd-svc
4 selector指明挑选那些label为run: httpd的Pod作为Service的后端
5 将Service的8080端口映射到Pod的80端口,使用TCP协议
"""
执行kubectl apply创建Service httpd-svc
[k8s@server1 ~]$ kubectl apply -f httpd-svc.yml 
service/httpd-svc created
[k8s@server1 ~]$ 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
[k8s@server1 ~]$ curl 10.104.247.136:8080
<html><body><h1>It works!</h1></body></html>
[k8s@server1 ~]$ curl 10.104.247.136:8080
<html><body><h1>It works!</h1></body></html>

# 通过kubectl describe可以查看httpd-svc与Pod的对应关系
[k8s@server1 ~]$ 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:                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:            
"""


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 10.99.229.179相关的信息

# 注意用户的切换:此处使用root用户
[root@server1 ~]# iptables-save |grep httpd-svc
-A KUBE-SERVICES ! -s 10.244.0.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.0.0/16)要访问httpd-svc,则允许
2)其他源地址访问httpd-svc,跳转到规则KUBE-SVC-
RL3JAE4GN7VOG DGP。KUBE-SVC-RL3JAE4GN7VOGDGP规则

[root@server1 ~]# 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

# 如果要访问其他namespace中的Service,就必须带上namesapce了。kubectl get namespace查看已有的namespace
[k8s@server1 ~]$ kubectl get namespace
NAME              STATUS   AGE
default           Active   23h
kube-node-lease   Active   23h
kube-public       Active   23h
kube-system       Active   23h

# kube-dns是一个DNS服务器。每当有新的Service被创建,kube-dns会添加该Service的DNS记录
[k8s@server1 ~]$ 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


# 在kube-public中部署Service httpd2-svc
# 通过namespace: kube-public指定资源所属的namespace。多个资源可以在一个YAML文件中定义,用“---”分割。
#执行kubectl
[k8s@server1 ~]$ 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
       
[k8s@server1 ~]$ kubectl apply -f httpd2.yml 
deployment.apps/httpd2 created
service/httpd2-svc created
[k8s@server1 ~]$ 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才能访问到
[k8s@server1 ~]$ 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

[k8s@server1 ~]$ 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
[k8s@server1 ~]$ kubectl apply -f httpd.yml 
deployment.apps/httpd created
[k8s@server1 ~]$ kubectl apply -f httpd-svc.yml 
service/httpd-svc created

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



[k8s@server1 ~]$ 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
[k8s@server1 ~]$ 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
[k8s@server1 ~]$ curl 172.25.0.2:30295
<html><body><h1>It works!</h1></body></html>
[k8s@server1 ~]$ curl 172.25.0.3:30295
<html><body><h1>It works!</h1></body></html>
接下来我们深入探讨一个问题:Kubernetes是如何将<NodeIP>:<NodePort>映射到Pod的呢?
与ClusterIP一样,也是借助了iptables。与ClusterIP相比,每个节点的iptables中都增加了下面两条规则

[root@server1 ~]# 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

[root@server1 ~]# 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

"""
规则的含义是:访问当前节点30295端口的请求会应用规则  KUBE-SVC-RL3JAE4GN7VOGDGP
其作用就是负载均衡到每一个Pod

"""

NodePort默认的是随机选择,不过我们可以用nodePort指定某个特定端口
[k8s@server1 ~]$ 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
"""
现在配置文件中就有三个Port了
nodePort是节点上监听的端口
port是ClusterIP上监听的端口
targetPort是Pod监听的端口

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

[k8s@server1 ~]$ kubectl apply -f httpd-svc.yml 
service/httpd-svc created
[k8s@server1 ~]$ 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
[k8s@server1 ~]$ curl 172.25.0.3:30000
<html><body><h1>It works!</h1></body></html>
[k8s@server1 ~]$ curl 172.25.0.2:30000
<html><body><h1>It works!</h1></body></html>

你可能感兴趣的:(docker&k8s)