# 第五章 服务:让客户端发现pod并与之通信 ##

1.什么是服务

简单来说服务就是为一组功能相同的pod提供单一不变的接入点的资源:
1.因为Pod是不稳定的,所以不能固定一个Pod的IP,但是服务是稳定的,服务的IP固定,通过服务来连接Pod也是稳定的
2.服务只是连通了集群内部所有IP访问pod的问题,但是不解决外部访问pod问题

2.创建服务

1.创建一个rc控制器,启动三个pod,暴露容器端口8080

apiVersion: v1
kind: ReplicationController
metadata:
  name: kubia
spec:
  replicas: 3
  selector:
    app: kubia
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia
        ports:
        - containerPort: 8080

2.创建一个服务

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

创建的服务会根据selector标签来选择pod,凡是pod标签和服务标签匹配的都属于同一个服务,上面服务会占用80端口,服务会将链接转发到容器的8080端口

3.检测新服务

[ ~]# k get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP   5d3h
kubia        ClusterIP   10.96.51.176   <none>        80/TCP    5s

4.检测服务是否代理到对应的pod

随机进入一个pod,执行请求命令
[ ~]# k get pods
NAME                         READY   STATUS      RESTARTS   AGE
batch-job-1576940400-jwkrn   0/1     Completed   0          8m6s
kubia-dzhlj                  1/1     Running     0          5m16s
kubia-mg8d7                  1/1     Running     0          5m16s
kubia-vccvt                  1/1     Running     0          5m16s

[ ~]# kubectl exec kubia-vccvt -- curl -s http://10.96.51.176
You've hit kubia-dzhlj
[ ~]# kubectl exec kubia-vccvt -- curl -s http://10.96.51.176
You've hit kubia-dzhlj
[ ~]# kubectl exec kubia-vccvt -- curl -s http://10.96.51.176
You've hit kubia-mg8d7
[ ~]# kubectl exec kubia-vccvt -- curl -s http://10.96.51.176
You've hit kubia-vccvt
[ ~]# curl http://10.96.51.176
You've hit kubia-vccvt

# kubectl exec 功能等同于ssh,另外 --(两个横杠) 的作用代表kubectl exec 命令结束,进入pod内部执行命令

5.以上svc代理的全过程

1.首先svc接收到请求,会随机分配到属于其代理范畴的pod中(负载均衡)
2.pod内的接收到服务代理的请求,返回pod自身的名称
3.curl命令向标准输出打印返回值

6.配置服务上的会话亲和性

如果多次执行相同的命令,每次调用执行应该在不同的pod上,因为svc会将每个链接随机分配到某个pod上,即使请求来自同一个客户端。

如果希望特定的客户端产生的请求指向同一个pod,可以设置服务的sessionAffinity属性为ClientIP(而不是None,默认为None)

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  sessionAffinity: ClientIP
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: kubia

验证:

[ ~]# k exec kubia-lmhd5 -- curl -s http://10.96.50.191
You've hit kubia-vdksm
[ ~]# k exec kubia-lmhd5 -- curl -s http://10.96.50.191
You've hit kubia-vdksm
[ ~]# k exec kubia-lmhd5 -- curl -s http://10.96.50.191
You've hit kubia-vdksm

7.同一个服务暴露多个端口

创建的服务可以暴露多个端口,通过一个集群IP,使用一个服务就可以将多个端口全部暴露出来

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: https
    port: 443
    targetPort: 8443
  selector:
    app: kubia

注意: 标签选择器应用于整个服务,不能对某个pod做单独配置,如果不同的pod需要暴露的端口不同,那么需要创建两个或者多个服务

8.使用命名的端口

为什 么要采用 命名端口的方式?最大 的 好处就是即使更换端口号 也无须更改服务 spec 。 你的 po d 现在对 http 服务用的是 8080 ,但是假设过段时间你决定将端口更换为 80 呢 ?

如果采用命名方式的话,只需要更改pod中的端口就可以了,svc不用做任何改变

kind: Pod
spec:
  containers:
  -name :kubia
    ports:
    -name: http
      containerPort: 8080
    -name: https
      containerPort: 8443
kind: Service
spec:
  - name: http
    port: 80
    targetPort: http
  - name: https
    port: 443
    targetPort: https

3.服务发现

当前端pod需要访问后端pod时是怎么发现后端服务pod呢?

1.第一种方式,根据环境变量

早期K8S通过环境变量来解决,每个Service生成对应的环境变量,并在POD启动时注入这些变量。

当创建一个Pod的时候,kubelet会在该Pod中注入集群内所有Service的相关环境变量。需要注意的是,要想一个Pod中注入某个Service的环境变量,则必须Service要先比该Pod创建。这一点,几乎使得这种方式进行服务发现不可用。
k exec kubia-vdksm env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubia-vdksm
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBIA_SERVICE_HOST=10.96.51.176
KUBIA_PORT_80_TCP_PROTO=tcp
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBIA_PORT_80_TCP_ADDR=10.96.51.176
KUBERNETES_PORT_443_TCP_PORT=443
KUBIA_PORT_80_TCP=tcp://10.96.51.176:80
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBIA_SERVICE_PORT=80
KUBIA_PORT=tcp://10.96.51.176:80
KUBIA_PORT_80_TCP_PORT=80
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=7.9.0
YARN_VERSION=0.22.0
HOME=/root

其中的UBIA_SERVICE_HOST=10.96.51.176和KUBIA_SERVICE_PORT=80就是环境变量中对应的服务IP和服务PORT,只要前端pod获取到这两个信息就可以实现数据库的交互

2.通过DNS发现服务

kubernetes 根据CoreDNS来提供DNS服务,如下coredns运行者dns服务,在集群中的其他pod都被配置成使用它作为dns服务(ubemetes 通过修改每个容器的/ etc/resolv. conf 文件 实现))。也就是说每个pod在resolv.conf中都存在着和coredns相同的DNS规则。运行在pod进程的DNS查询都会被Kubernetes自身的DNS服务器响应,该服务器知道系统中运行的所有服务

kube-system   coredns-9d85f5447-kthw8              1/1     Running   8          6d17h
kube-system   coredns-9d85f5447-pqvcl              1/1     Running   8          6d17h

注意:

pod 是否使用内部的DNS服务器是根据pod中的spec的dnsPlicy属性来决定的

每个服务从内部 DNS 服务器中获得 一 个 DNS 条目, 客户端 的 pod 在知道服务名称的情况下可以通过全限定域名 CFQDN )来访问,而不是诉诸于环境变量。

通过FQDN连接服务:

前端pod可以通过打开以下的FQDN的连接来访问后端数据库服务:
backend database.default svc.cluster. l ocal

backend-database对应服务名称,default表示服务在其中定义的命名空间,而svc.cluster.local是在所有集群本地服务名称中使用的可配置集群域后缀,但是前端仍然必须知道服务的端口号,如果服务使用标准端 口 号(例如,HTTP 的 80 端 口 或 Postgres 的5432端口),这样是没问题的 。 如果并不是标准端口,客户端可以从环境变量中获取端口号 。

如果前端pod和后端pod在同一个namespace下,可以省略svc.cluster.local后缀,甚至命名空间。因此可以使用backend-database来指代服务
如下:
[ ~]# k get po
NAME          READY   STATUS    RESTARTS   AGE
kubia-b24bc   1/1     Running   0          79m
kubia-nqvst   1/1     Running   0          79m
kubia-svsln   1/1     Running   0          79m
[ ~]# k exec kubia-b24bc sh
[ ~]# k exec -it  kubia-b24bc sh
# curl kubia
You've hit kubia-b24bc

3.连接集群外的服务

1.介绍endpoint

Endpoint是一种介于服务和pod之间的资源,因此服务和pod不是直接连接的。

[ ~]# k get endpoints kubia
NAME    ENDPOINTS                                      AGE
kubia   10.38.0.1:8080,10.38.0.2:8080,10.40.0.3:8080   45h

Endpoint资源就是暴露一个服务的IP地址和端口的列表,svc只有定义了selector之后,才会自动生成Endpoints资源

2.创建没有选择器的服务

要想创建没有选择器的服务,就需要手动创建服务和Endpoints资源

首先创建rc/rs/pod,选择先前的kubia rc

然后创建svc

apiVersion: v1
kind: Service
metadata:
  name: external-service
spec:
  ports:
  - port: 80

最后创建ep(注意要和svc有相同的名称,同时address要对应pod()地址)

apiVersion: v1
kind: Endpoints
metadata:
  name: external-service
subsets:
  - addresses:
    - ip: 10.38.0.1
    - ip: 10.38.0.2
    - ip: 10.40.0.3
    ports:
    - port: 8080

然后验证是否将svc和pod关联到

[ ~]# k get ep
NAME               ENDPOINTS                                      AGE
external-service   10.38.0.1:8080,10.38.0.2:8080,10.40.0.3:8080   15s
kubernetes         10.0.2.6:6443                                  7d2h

[ ~]# k exec -it kubia-svsln sh
# curl kubia
curl: (6) Could not resolve host: kubia
# curl external-service
You've hit kubia-nqvst

如果想要把外部的服务迁移到pod中,只需要为服务添加选择器关联到pod,从而对Endpoint进行自动管理

2.为外部服务创建别名

spec:
   type: ExternalName
   externalName: someapi.somecompany.com
   ports:
   - port: 80

3.将服务暴露给外部客户端

有几种方式可以通过外部访问服务:

1.将服务类型设置为NodePort: 
    此种方式下,每个集群节点都会在自身节点上打开一个特定的端口,并将在该端口的接收的流量重定向到基础服务,该服务仅在内部集群IP和端口上才可以访问,但是也可以通过所有节点的专用端口访问
2.将服务类型设置为LoadBalance,:
    NodePort类型的一种扩展 -- 使得服务可以通过一个专用的负载均衡器来访问,这是由K8s中正在运行的云基础设施提供的。负载均衡器将流量重定向到跨所有节点的节点端口。客户端通过负载均衡器的 IP 连接到服务。
3.创建一个Ingress资源,这是一个    完全不同的机制,通过一个IP地址公开多个服务, 它是运行在HTTP层(网络协议的7层)上,因此提供比工作在4层服务更多的功能

3.1使用NodePort

apiVersion: v1
kind: Service
metadata:
  name: kubia-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30123
  selector:
    app: kubia

其中port为服务集群的Ip端口号,targetPort为背后pod的目标端口,nodePort可指定也可不指定,如果不制定那就会分配随机端口号

验证:

通过NodePort服务的方式,可以通过以下两种方式访问pod服务

①通过服务IP

②通过NodeIp+端口号

[ ~]# k get nodes -o wide
NAME         STATUS   ROLES    AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION               CONTAINER-RUNTIME
master.k8s   Ready    master   7d3h   v1.17.0   10.0.2.6      <none>        CentOS Linux 7 (Core)   3.10.0-1062.9.1.el7.x86_64   docker://19.3.5
node1.k8s    Ready    <none>   7d3h   v1.17.0   10.0.2.15     <none>        CentOS Linux 7 (Core)   3.10.0-1062.9.1.el7.x86_64   docker://19.3.5
node2.k8s    Ready    <none>   7d3h   v1.17.0   10.0.2.4      <none>        CentOS Linux 7 (Core)   3.10.0-1062.9.1.el7.x86_64   docker://19.3.5
[ ~]# curl 10.0.2.6:30123
You've hit kubia-b24bc
[ ~]# curl 10.0.2.15:30123
You've hit kubia-svsln
[ ~]# curl 10.0.2.4:30123
You've hit kubia-nqvst
[ ~]# k exec kubia-nqvst -it sh
# curl kubia
^C
# curl 10.38.0.1
curl: (7) Failed to connect to 10.38.0.1 port 80: Connection refused
# curl 10.0.2.15:30123
You've hit kubia-nqvst
# curl 10.96.123.159
You've hit kubia-svsln

现在如果你的集群IP是公开的,那么在互联网的任意地方,任意IP+30123端口即可访问你的服务,但是如果节点发生故障,但是此时客户端正在请求该节点,那么就会造成宕机,所以需要在请求前加一个负载均衡器,以确保请求的是一个健康的节点,那么就有了Load Badancer,Load Badancer会自动创建负载均衡器

3.2使用LoadBalance

LoadBanlance,负载均衡器有独一无二的可公开访问的IP地址,并将所有连接重定向到服务,可以通过负载均衡器的IP地址访问服务()

如果Kubernetes在不支持Load Bancer服务的环境的环境中运行,则不会调配负载均衡器,但该服务仍将表现得想一个NodePort服务,这是因为LoadBanlance是NodePort的一个扩展

创建LoadBanlance:

和创建NodePort一样,只是type类型为LoadBalancer

3.3防止不必要的网络跳数

正常情况下,外部客户端通过节点端口连接到服务时,会通过Service层做负载均衡,所以最终的处理请求的pod不一定会落在接收请求的节点上,这会带来额外的网络开销,这不符合预期
可以通过将服务配置将接收请求的节点和将提供服务的pod绑定在一起,通过在spec中配置:
spec:
  externalTrafficPolicy: Local
  如果服务定义包含此设置, 并且通过服务的节点端口打开外部连接, 则服务代理将选择本地运行的pod。 如果没有本地pod存在, 则连接将挂起(它不会像不使用注解那样, 将其转发到随机的全局pod)。 因此, 需要确保负载平衡器将连接转发给至少具有 一 个pod的节点
 
 缺点:
 ①使用local外部流量策略的服务可能会导致跨pod的负载分布不均衡
 ②追踪不到访问者的IP

相关推荐