更新时间:2023-09-06 gmt 08:00

service-凯发k8国际娱乐官网入口

直接访问pod的问题

pod创建完成后,如何访问pod呢?直接访问pod会有如下几个问题:

  • pod会随时被deployment这样的控制器删除重建,那访问pod的结果就会变得不可预知。
  • pod的ip地址是在pod启动后才被分配,在启动前并不知道pod的ip地址。
  • 应用往往都是由多个运行相同镜像的一组pod组成,逐个访问pod也变得不现实。

举个例子,假设有这样一个应用程序,使用deployment创建了前台和后台,前台会调用后台做一些计算处理,如图1所示。后台运行了3个pod,这些pod是相互独立且可被替换的,当pod出现状况被重建时,新建的pod的ip地址是新ip,前台的pod无法直接感知。

图1 pod间访问

使用service解决pod的访问问题

kubernetes中的service对象就是用来解决上述pod访问问题的。service有一个固定ip地址(在创建cce集群时有一个服务网段的设置,这个网段专门用于给service分配ip地址),service将访问它的流量转发给pod,具体转发给哪些pod通过label来选择,而且service可以给这些pod做负载均衡。

那么对于上面的例子,为后台添加一个service,通过service来访问pod,这样前台pod就无需感知后台pod的变化,如图2所示。

图2 通过service访问pod

创建后台pod

首先创建一个3副本的deployment,即3个pod,且pod上带有标签“app: nginx”,具体如下所示。

apiversion: apps/v1      
kind: deployment         
metadata:
  name: nginx            
spec:
  replicas: 3                    
  selector:              
    matchlabels:
      app: nginx
  template:              
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:latest
        name: container-0
        resources:
          limits:
            cpu: 100m
            memory: 200mi
          requests:
            cpu: 100m
            memory: 200mi
      imagepullsecrets:
      - name: default-secret

创建service

下面示例创建一个名为“nginx”的service,通过selector选择到标签“app:nginx”的pod,目标pod的端口为80,service对外暴露的端口为8080。

访问服务只需要通过“服务名称:对外暴露的端口”接口,对应本例即“nginx:8080”。这样,在其他pod中,只需要通过“nginx:8080”就可以访问到“nginx”关联的pod。

apiversion: v1
kind: service
metadata:
  name: nginx        # service的名称
spec:
  selector:          # label selector,选择包含app=nginx标签的pod
    app: nginx
  ports:
  - name: service0
    targetport: 80   # pod的端口
    port: 8080       # service对外暴露的端口
    protocol: tcp    # 转发协议类型,支持tcp和udp
  type: clusterip    # service的类型

将上面service的定义保存到nginx-svc.yaml文件中,使用kubectl创建这个service。

$ kubectl create -f nginx-svc.yaml
service/nginx created
$ kubectl get svc
name         type        cluster-ip       external-ip   port(s)    age
kubernetes   clusterip   10.247.0.1               443/tcp    7h19m
nginx        clusterip   10.247.124.252           8080/tcp   5h48m

您可以看到service有个cluster ip,这个ip是固定不变的,除非service被删除,所以您也可以使用clusterip在集群内部访问service。

下面创建一个pod并进入容器,使用clusterip访问pod,可以看到能直接返回内容。

$ kubectl run -i --tty --image nginx:alpine test --rm /bin/sh
if you don't see a command prompt, try pressing enter.
/ # curl 10.247.124.252:8080
welcome to nginx!
...

使用servicename访问service

通过dns进行域名解析后,可以使用“servicename:port”访问service,这也是kubernetes中最常用的一种使用方式。在创建cce集群的时候,会默认要求安装coredns插件,在kube-system命名空间下可以查看到coredns的pod。

$ kubectl get po --namespace=kube-system
name                                      ready   status    restarts   age
coredns-7689f8bdf-295rk                   1/1     running   0          9m11s
coredns-7689f8bdf-h7n68                   1/1     running   0          11m

coredns安装成功后会成为dns服务器,当创建service后,coredns会将service的名称与ip记录起来,这样pod就可以通过向coredns查询service的名称获得service的ip地址。

访问时通过nginx..svc.cluster.local访问,其中nginx为service的名称,为命名空间名称,svc.cluster.local为域名后缀,在实际使用中,在同一个命名空间下可以省略.svc.cluster.local,直接使用servicename即可。

例如上面创建的名为nginx的service,直接通过“nginx:8080”就可以访问到service,进而访问后台pod。

使用servicename的方式有个主要的优点就是可以在开发应用程序时可以将servicename写在程序中,这样无需感知具体service的ip地址。

下面创建一个pod并进入容器,查询nginx域名的地址,可以发现是解析出nginx这个service的ip地址10.247.124.252;同时访问pod的域名,可以看到能直接返回内容。

$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=never --rm /bin/sh
if you don't see a command prompt, try pressing enter.
/ # nslookup nginx
server:		10.247.3.10
address:	10.247.3.10#53
name:	nginx.default.svc.cluster.local
address: 10.247.124.252
/ # curl nginx:8080
welcome to nginx!
...

service是如何做到服务发现的

前面说到有了service后,无论pod如何变化,service都能够发现到pod。

如果调用kubectl describe命令查看service的信息,您会看到如下信息。

$ kubectl describe svc nginx
name:              nginx
......
endpoints:         172.16.2.132:80,172.16.3.6:80,172.16.3.7:80
......

可以看到一个endpoints,endpoints同样也是kubernetes的一种资源对象,可以查询得到。kubernetes正是通过endpoints监控到pod的ip,从而让service能够发现pod。

$ kubectl get endpoints
name         endpoints                                     age
nginx        172.16.2.132:80,172.16.3.6:80,172.16.3.7:80   5h48m

这里的172.16.2.132:80是pod的ip:port,通过如下命令可以查看到pod的ip,与上面的ip一致。

$ kubectl get po -o wide
name                     ready   status    restarts   age     ip             node         
nginx-869759589d-dnknn   1/1     running   0          5h40m   172.16.3.7     192.168.0.212
nginx-869759589d-fcxhh   1/1     running   0          5h40m   172.16.3.6     192.168.0.212
nginx-869759589d-r69kh   1/1     running   0          5h40m   172.16.2.132   192.168.0.94

如果删除一个pod,deployment会将pod重建,新的pod ip会发生变化。

$ kubectl delete po nginx-869759589d-dnknn
pod "nginx-869759589d-dnknn" deleted
$ kubectl get po -o wide
name                     ready   status    restarts   age     ip             node         
nginx-869759589d-fcxhh   1/1     running   0          5h41m   172.16.3.6     192.168.0.212
nginx-869759589d-r69kh   1/1     running   0          5h41m   172.16.2.132   192.168.0.94 
nginx-869759589d-w98wg   1/1     running   0          7s      172.16.3.10    192.168.0.212

再次查看endpoints,会发现endpoints的内容随着pod发生了变化。

$ kubectl get endpoints
name         endpoints                                      age
kubernetes   192.168.0.127:5444                             7h20m
nginx        172.16.2.132:80,172.16.3.10:80,172.16.3.6:80   5h49m

下面进一步了解这又是如何实现的。

kubernetes集群架构中介绍过node节点上的kube-proxy,实际上service相关的事情都由节点上的kube-proxy处理。在service创建时kubernetes会分配ip给service,同时通过api server通知所有kube-proxy有新service创建了,kube-proxy收到通知后通过iptables记录service对应的ip和端口,从而让service在节点上可以被查询到。

下图是一个实际访问service的图示,pod x访问service(10.247.124.252:8080),在往外发数据包时,在节点上根据iptables规则目的ip:port被随机替换为pod1的ip:port,从而通过service访问到实际的pod。

除了记录service对应的ip和端口,kube-proxy还会监控service和endpoint的变化,从而保证pod重建后仍然能通过service访问到pod。

图3 pod x访问service的过程

service的类型与使用场景

service的类型除了clusterip还有nodeport、loadbalancer和headless service,这几种类型的service有着不同的用途。

  • clusterip:用于在集群内部互相访问的场景,通过clusterip访问service。
  • nodeport:用于从集群外部访问的场景,通过节点上的端口访问service,详细介绍请参见nodeport类型的service
  • loadbalancer:用于从集群外部访问的场景,其实是nodeport的扩展,通过一个特定的loadbalancer访问service,这个loadbalancer将请求转发到节点的nodeport,而外部只需要访问loadbalancer,详细介绍请参见loadbalancer类型的service
  • headless service:用于pod间的互相发现,该类型的service并不会分配单独的clusterip, 而且集群也不会为它们进行负载均衡和路由。您可通过指定spec.clusterip字段的值为“none”来创建headless service,详细介绍请参见headless service

nodeport类型的service

nodeport类型的service可以让kubernetes集群每个节点上保留一个相同的端口, 外部访问连接首先访问节点ip:port,然后将这些连接转发给服务对应的pod。如下图所示。

图4 nodeport service
下面是一个创建nodeport类型的service。创建完成后,可以通过节点的ip:port访问到后台pod。
apiversion: v1
kind: service
metadata:
  name: nodeport-service
spec:
  type: nodeport
  ports:
  - port: 8080
    targetport: 80
    nodeport: 30120
  selector:
    app: nginx

创建并查看,可以看到port这一列为8080:30120/tcp,说明service的8080端口是映射到节点的30120端口。

$ kubectl create -f nodeport.yaml 
service/nodeport-service created
$ kubectl get svc -o wide
name               type        cluster-ip       external-ip   port(s)          age    selector
kubernetes         clusterip   10.247.0.1               443/tcp          107m   
nginx              clusterip   10.247.124.252           8080/tcp         16m    app=nginx
nodeport-service   nodeport    10.247.210.174   <none>        8080:30120/tcp   17s    app=nginx

此时,通过节点ip:端口访问service可以访问到pod,如下所示。

$ kubectl run -i --tty --image nginx:alpine test --rm /bin/sh
if you don't see a command prompt, try pressing enter.
/ # curl 192.168.0.212:30120
welcome to nginx!
......

loadbalancer类型的service

loadbalancer类型的service其实是nodeport类型service的扩展,通过一个特定的loadbalancer访问service,这个loadbalancer将请求转发到节点的nodeport。

loadbalancer本身不是属于kubernetes的组件,这部分通常是由具体厂商(云服务提供商)提供,不同厂商的kubernetes集群与loadbalancer的对接实现各不相同,例如cce对接了elb。这就导致了创建loadbalancer类型的service有不同的实现。

图5 loadbalancer service
下面是一个创建loadbalancer类型的service。创建完成后,可以通过elb的ip:port访问到后台pod。
apiversion: v1 
kind: service 
metadata: 
  annotations:   
    kubernetes.io/elb.id: 3c7caa5a-a641-4bff-801a-feace27424b6
  labels: 
    app: nginx 
  name: nginx 
spec: 
  loadbalancerip: 10.78.42.242     #  elb实例的ip地址
  ports: 
  - name: service0 
    port: 80
    protocol: tcp 
    targetport: 80
    nodeport: 30120
  selector: 
    app: nginx 
  type: loadbalancer    # 类型为loadbalancer 

上面metadata.annotations里的参数配置是cce的loadbalancer类型service需要配置的参数,表示这个service绑定哪个elb实例。cce还支持创建loadbalancer类型service时新建elb实例,详细的内容请参见。

headless service

前面讲的service解决了pod的内外部访问问题,允许客户端连接到service关联的某个pod。但还有下面这些问题没解决。

  • 同时访问所有pod
  • 一个service内部的pod互相访问

为了解决以上问题,kubernetes提供了另一种较为特殊的service类型,称为headless service。对于其他service来说,客户端在访问服务时,dns查询时只会返回service的clusterip地址,具体访问到哪个pod是由集群转发规则(ipvs或iptables)决定的。而headless service并不会分配单独的clusterip,在进行dns查询时会返回所有pod的dns记录,这样就可查询到每个pod的ip地址。中statefulset正是使用headless service解决pod间互相访问的问题。

apiversion: v1
kind: service       # 对象类型为service
metadata:
  name: nginx-headless
  labels:
    app: nginx
spec:
  ports:
    - name: nginx     # pod间通信的端口名称
      port: 80        # pod间通信的端口号
  selector:
    app: nginx        # 选择标签为app:nginx的pod
  clusterip: none     # 必须设置为none,表示headless service

执行如下命令创建headless service。

# kubectl create -f headless.yaml 
service/nginx-headless created

创建完成后可以查询service。

# kubectl get svc
name             type        cluster-ip   external-ip   port(s)   age
nginx-headless   clusterip   none                 80/tcp    5s

创建一个pod来查询dns,可以看到能返回所有pod的记录,这就解决了访问所有pod的问题了。

$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=never --rm /bin/sh
if you don't see a command prompt, try pressing enter.
/ # nslookup nginx-headless
server:         10.247.3.10
address:        10.247.3.10#53
name:   nginx-headless.default.svc.cluster.local
address: 172.16.0.31
name:   nginx-headless.default.svc.cluster.local
address: 172.16.0.18
name:   nginx-headless.default.svc.cluster.local
address: 172.16.0.19
分享:
网站地图