# 第五章 服务:让客户端发现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