1、Service概念及原理SVCKubernetes 中 Service 是 将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的方法也就是提供的业务如何发布出去当pod进行自动伸缩时如何让新增的pod被自动的发现,并且进行自动的负载均衡如何自动形成负载均衡kubernetes “Service”定义了这样一种抽象一个pod的逻辑分组一种可以访问他们的策略——通常称为微服务。这一组pod能被Service访问到通常是通过“Label Selector”​看上图用户通过https外部端点443访问Frontend Service被负载到了多个Frontend Pod也就是前端pod上看另一边有一个Frontend Deployment由控制器来创建三个pod。而Service则负责将三个pod以负载均衡的方式提供给用户访问。通过Service的标签选择器来选择同类项可以看到标签app、role都是相同的。​解析上图这是一个k8s集群内部通过两个Deployment控制器创建了两套业务nginx和tomcat。通过nginx的pod反向代理到tomcat的pod上。如果不用Service实现负载均衡的话肯定是在nginx配置中添加upstream区在区域中写三个pod的ip然后再添加proxy_pass设置反向代理。假设某天某个pod死了控制器发现死了一个肯定会在创建一个新的pod来满足三个pod的需求这时新创建的Pod IP发生了改变需要将新pod的ip添加进nginx的upstream区并且删除旧ip但是这样太复杂了。这个时候Service登场了见下图​如果加上了Service之后tomcat pod依然被创建每个pod上都有对应的标签这时创建一个Service他的标签选择器键值就是apptomcat这时pod准备就绪、标签可以被svc的标签选择器匹配到这两个条件达成的话pod就会被svc抓取放在一个负载均衡的集群里。不管后端的pod如何变更nginx只要将proxy_pass反向代理到Service本身的ip就可以实现负载均衡的方式在k8s集群中每个node运行一个“kube-proxy”进程。这个进程负责为Service实现一种VIP的形式在k8s 1.0版本中代理完全在userspace。在k8s 1.1版本新增了iptables代理但并不是默认的运行模式。从k8s 1.2起默认是iptables代理。在k8s 1.8-beta.0中添加了ipvs代理userspace负载均衡的方式1.27版本被移除​每个节点上的kube-proxy组件都要去监听kube-apiServer组件监听当前负载均衡规则的变化也就是Service对象。通过图片看到有一个客户端的pod和多个server端的pod客户端想要访问server端的话要先访问本地的防火墙规则防火墙规则再将流量转发至当前节点或别的节点的kube-proxykube-proxy再去代理server返回给client在这里kube-proxy有两个工作一个是监听kube-apiServer第二个是代理server的请求返回给clientiptables负载均衡的方式k8s默认​还是这两个组件kube-proxy、kube-apiServer但是很显然proxy监听apiServer后只是将监听结果写入本机的防火墙规则client的访问完全由防火墙转发给后面几个server。kube-proxy不在参与代理功能。相对于userspacekube-proxy功能解耦压力较小ipvs负载均衡的方式​和iptables差不多唯一变化就是将当前Service的负载均衡集群的信息转换为ipvs规则落在本机。然后将client访问负载到后方server。因为ipvs负载均衡是专业的只谈四层负载均衡的话性能是强于iptables的修改默认负载均衡方式为ipvs官方是推荐kube-proxy负载方式为ipvs的但是因为ipvs这个内核模块需要在系统中加载。k8s官方害怕当前的机器由于没有启用ipvs规则导致如果默认用ipvs规则的话k8s没有办法正常工作kubectl edit configmap -n kube-system kube-proxy找到mode一行默认是空的加上ipvs即可保存退出#杀死命名空间kube-system中的proxyPod进行重建 kubectl delete po -n kube-system -l k8s-appkube-proxyipvsadm -Ln 这时候再查会发现​2、Service服务类型及使用对一些应用的某些部分如前端可能希望将其公开于某外部 IP 地址 也就是可以从集群外部访问的某个地址。Kubernetes Service 类型允许指定你所需要的 Service 类型service类型分为四类四类理解为四个圈大圈套小圈从内到外分层级ClusterIPNodePortLoadBalancerExternalName并且每个层级都包含上个层级的功能。并在上个层级的功能之上进行功能的扩展ClusterIP默认类型自动分配一个仅Cluster内部可以访问的虚拟ipapiVersion: apps/v1 kind: Deployment metadata: name: deploy-demo spec: replicas: 1 selector: matchLabels: svc: cluster template: metadata: labels: svc: cluster spec: containers: - name: nginx-container image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.27.0 imagePullPolicy: IfNotPresent readinessProbe: httpGet: path: /index.html port: 80 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3 --- apiVersion: v1 kind: Service metadata: name: deploy-demo-clusterip spec: type: ClusterIP selector: svc: cluster ports: - port: 80 targetPort: 80 --- apiVersion: v1 kind: Pod metadata: name: pod-1 labels: svc: cluster spec: containers: - name: nginx-container image: my-nginx:v1 imagePullPolicy: IfNotPresent readinessProbe: httpGet: path: /index.html port: 80 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3 --- apiVersion: v1 kind: Pod metadata: name: pod-2 labels: svc: cluster spec: containers: - name: nginx-container image: my-nginx:v2 imagePullPolicy: IfNotPresent readinessProbe: httpGet: path: /index.html port: 80 initialDelaySeconds: 5 periodSeconds: 10 failureThreshold: 3 #最后两个pod是新加的,懒得写控制器了,标签一样就能被svc匹配到,新增pod为了等下的实验用域名访问SVC访问Service有两种方式一个是Service的ip也就是curl访问的方式。另一个就是域名。每个Service创建之后都会有一个DNS的域名保存在dns插件中解析的结果就是创建Service分配的IP试一下#先查询dns插件的podIp kubectl get po -n kube-system -o wide |grep dns #dig命令在bind-utils中ip是dns pod的IP。 dig -t A deploy-demo-clusterip.default.svc.cluster.local. dnsPodIp 说下这个域名怎来的 #service-name.namespace.svc.cluster.local这是域名构成 #service-name服务的名称即你在创建 Kubernetes 服务时定义的名称。 #namespace:服务所在的 Kubernetes 命名空间 #svc固定 #cluster.local这是默认的集群域名用于标识 Kubernetes 集群内的所有服务这个域名是用来给集群内部的pod访问用的在集群外部是无法访问该域名的。就是说只能在服务器内部访问web访问不行。可以在集群内的节点都访问一下试试有了结果等下可以做另一个实验SVC对内访问策略可以设置.spec.internalTrafficPolicy字段来控制来自内部源的流量如何被路由可以将svc输出为yaml文件查看​访问模式分两种模式Cluster默认流量可以路由到任何节点上的 Pod不管请求从哪个节点进来Local流量只路由到本节点上的 Pod如果本节点没有 Pod就丢弃请求。刚才试了集群内任意节点访问pod现在试试Local模式。控制器创建的pod副本全在node1和node2上将svc改为local在继续访问改成local后curl访问就失败了并且是在master节点失败。但是域名访问还是正常的为什么因为Local 模式限制的是流量转发路径不是DNS 解析。DNS只负责将域名解析为 IP不关心流量策略而且master节点由于设计机制导致master节点不会有pod进行创建后面会学到现在回到local模式访问方才说了local模式只能在存在pod的节点进行访问先观察下pod都分布在哪些节点pod-1是nginx:v1版本在node1节点pod-2和deploy-demo是nginx:v2版本和默认版本现在分别在三个节点进行访问看看结果对比看出区别了么master节点没有pod无法访问node1节点只有nginx:v1版本所以只能访问到v1node2节点有nginx:v2版本和默认版本所以只能访问这两个这就是Local模式会话亲和性或者叫持久化连接、会话保持底层使用ipvs去实现service.spec.sessionAffinityConfig想象一个场景正在使用某宝每次你访问网站的不同页面时浏览器需要向服务器发送 HTTP 请求。如果每次请求都需要重新建立连接即每次请求后都关闭连接那每次请求的时间会大大增加。比如参与秒杀活动的话只能在一个界面干等一旦刷新就要等待这就很糟糕而如果使用持久化连接那么浏览器和服务器在第一次请求时建立了连接这个连接就会保持活跃后续访问其他页面时浏览器和服务器可以复用这个连接而无需重新建立连接。这样可以显著减少网络延迟提高页面加载速度持久化连接默认是none关闭状态用kubectl edit修改改成ClientIP后保存会自动出现下面的字段。svc.spec.sessionAffinityConfig.clientIP.timeoutSeconds​时间默认值是10800秒也就是三个小时。持久化时间必须大于0并且小小于86400秒也就是24小时默认值就挺久了看实际需求持久化连接的时间会随着每一次的访问恢复到设置的值​并且用ipvsadm -Ln查看也会佐证当前的svc开启了持久化连接​然后访问svc在有限的时间内访问就被固定在了一台机器上记得把访问模式改回ClusterIP​无头服务Headless Services要配合StatefulSet控制器使用后面到存储PV/PVC再回来补充NodePort在CluserIP基础上为Service在每台机器上绑定一个端口这样就可以通过NodeIP:NodePort来访问该服务。在之前的实验中说的都是用户怎么怎么访问pod但是忽略了一个事实实验一直都是在集群内部访问的。pod的ip是一个扁平化的虚拟IP外部用户是直接访问不到的这时候NodePort就出现了。它让服务不仅在集群内部暴露还通过每个节点上的一个固定端口在集群外部暴露这个端口默认大于3000也可自定义。如下图​kind: Deployment metadata: name: deploy-demo-nodeport spec: replicas: 5 selector: matchLabels: svc: nodeport template: metadata: labels: svc: nodeport spec: containers: - name: nodeport-container image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:1.27.0 imagePullPolicy: IfNotPresent readinessProbe: httpGet: path: /index.html port: 80 --- apiVersion: v1 kind: Service metadata: name: deploy-demo-nodeport spec: type: NodePort selector: svc: nodeport ports: - port: 80 targetPort: 80 nodePort: 30001 #当通过外部 IP 访问服务时流量会先经过节点的 nodePort然后被转发到集群内的 port最终转发到目标 Pod 的 targetPort。如果知道集群的任意节点的 IP 地址http://node-ip:30010 就可以访问这个服务 port服务暴露给集群内部的端口。 targetPortPod 上实际处理请求的端口。 nodePort服务暴露给集群外部的端口用于从外部访问服务。 这三者的作用可以这样理解 外部用户通过 nodePort 访问服务。 服务将流量通过 port 转发给 Pod。 Pod 在 targetPort 上处理实际请求。在浏览器输入任意节点的ipport即可访问到后端真实服务器。ipvsadm -Ln查一下各个节点可以看到网卡上绑定了30001端口并且每个节点上的物理ip都做成了负载均衡IP所以如果机器有多个网卡的话他会把当前机器的每一块可用网卡的地址都形成一个ipvs的集群​SVC对外访问策略.spec.externalTrafficPolicy也分为两种Cluster默认提供集群范围内的外部访问能力。外部流量可以通过集群中任何一个节点的nodePort端口访问服务并且流量会被负载均衡到后端的PodLoacl只有集群内部才能访问它限制了nodePort的流量只在本地节点上进行处理。欧克上面试了Cluster模式现在试试Local模式​最开始说过每个层级是从内到外进行嵌套的将externalTrafficPolicy改为Local这时候从web访问只能访问到部署pod的节点master是不参与pod部署的所以master节点就无法访问了但是集群内部还是可以访问的​LoadBalancer在NodePort基础上借助cloud provider创建一个外部负载均衡器并将请求转发到NodeIP:NodePort。LoadBalancer 和 nodePort 其实是同一种方式。区别在于 loadBalancer 比 nodePort 多了一步就是可以调用 cloud provider 去创建 LB 来创建集群的负载均衡。负载均衡器是异步创建的被提供的负载均衡器的信息将会通过 Service 的 status.loadBalancer 字段被发布出去如下图​这个没办法做实验要结合云厂商比如阿里云。可以向阿里云申请工单让厂商提供资源清单就好ExternalNamesvc的一种特殊类型它不代理到 Pod而是将服务映射到外部的 DNS 域名。它不会创建 ClusterIP也不会做负载均衡它只是在集群的 DNS 中创建一个CNAME别名指向外部域名。​看图假设集群外有个数据库其域名为mysql.user.com你希望在Kubernetes集群内部能够通过一个内部名称来访问这个服务。这时创建一个名为tomcat-pod的ExternalName。它将自己CNAME别名到mysql.user.com.因此集群内的Pod可以通过tomcat-pod.dev.svc.cluster.local访问mysql.user.com现在做个实验自己写个CNAME别名让他指向百度apiVersion: v1 kind: Pod metadata: name: external-pod labels: svc: external spec: containers: - name: external-contianer image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/busybox:1.28 imagePullPolicy: IfNotPresent command: - sh - -c - sleep 3600 --- apiVersion: v1 kind: Service metadata: name: external-pod spec: type: ExternalName externalName: www.baidu.com很特殊的一个服务类型哈连IP都莫得。​进入pod内部ping一下service的域名ping命令解析出了IP。​打开浏览器访问一下解析出的IP 103.235.46.115 跳转到了百度。是不是突然就蒙了为什么ping的域名是svc的但是突然就跳转到百度了这就是CNAME别名的作用让集群内部可以通过这种方式来访问外部的服务把www.baidu.com换成mysql.user.com是不是好理解一些​说一下为什么要在pod内部进行ping因为集群内部的pod的dns是指向到集群中的dns插件上的这个dns只对pod提供解析服务。在pod外ping svc域名的话是服务器本身在解析这样是解析不出来的3、Service的底层模型-Endpoints因为在完善更新过程中k8s集群版本也从1.29变成了1.33在1.33版本endpoints/v1这个API已经被废弃了变成了v1/EndpointSlice不过原理一样kubernetes中的service他定义了一组pods的逻辑集合和一个用于访问他们的策略。一个service的目标pod集合通常由Label selector来决定的。Endpoins是一组实际服务的端点集合。一个endpoint是一个可被访问的服务端点即一个状态为running的pod的可访问端点。一般pod都不是一个独立存在所以一组pod的端点合在一起成为endpoints。只有servier selector匹配选中并且状态为running的才会被加入到和service同名的endpoints中​看图有个叫MyService的svc这个svc的标签选择器匹配的标签是appMyApp。最下面有多个对应的后端pod并且都有标签appMyApp那么这些pod会被Service匹配到。匹配完成后会自动创建一个叫Endpoints的对象这个对象称之为端点。并且和svc同名这个对象内所保存的信息就是当前svc的标签选择器匹配到的pod的ip和port4、Service关联体系在 Kubernetes 中自动关联体系和手动关联体系是两种不同的方式来建立 Service 与 Pods 之间的关系。它们的主要区别在于是否依赖于 Kubernetes 的自动化机制来管理关联自动关联体系配置selector看图最上面k8s内部通过Service发起请求然后请求会被导向到对应分配的pod分配的逻辑原理就是基于ipvs。怎么确定ipvs能够创建成功呢很简单当Service被定义了一个标签选择器后选择器会动态监测当前符合需求的pod并且将这些pod的ip抓取到新的资源对象中也就是Endpoint。因为Endpoint对象是和Service同名的。并且是在Service创建的同时自动被创建出来的。这样kubelet只需要将当前的Endpoints维护到当前节点的ipvs规则里即可。做个实验apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: default spec: replicas: 3 selector: matchLabels: app: myapp svc: clusterip template: metadata: labels: app: myapp svc: clusterip spec: containers: - name: myapp-container image: nginx:v1 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: myapp-service spec: type: ClusterIP selector: app: myapp ports: - name: http port: 80 targetPort: 80​手动关联体系无配置selector​解析上图k8s通过Service发起访问Service依然是通过ipvs将请求分散到后端的服务可以是pod也可以是集群外的服务。既然没有了标签选择器要怎么将对应的ipvs规则创建出来这时候就需要管理员进行手动创建endpoints必须与Service同名。这种方式灵活性非常高前面说了自动关联方式被svc匹配的条件有两个pod就绪和满足子集匹配但是手动关联方式svc匹配的条件是由管理员手动在endpoint中填写的端点信息做个实验apiVersion: v1 kind: Service metadata: name: nginx spec: ports: - protocol: TCP port: 6666 targetPort: 80 #先创建svc apiVersion: v1 kind: Endpoints metadata: name: nginx subsets: - addresses: - ip: 192.168.176.102 #指定了服务的地址 ports: - port: 80可以看到没有标签选择器的svc是没有同步创建endpoint的。并且查询ipvsadm看负载均衡集群下是没有真实服务器的​在继续创建endpoint可以看到负载均衡集群中后端的真实服务器ip被添加进了规则中​这时候去ip对应的节点docker启动一个nginx容器进行访问测试不然的话节点上是没有80端口的然后回到mater节点进行访问​从实验看出Service后面的端点是由管理员手动来进行控制的。如果通过官方标准有标签选择器的svc来匹配nginx的话是不可能匹配的到的这就是灵活性的体现5、未就绪匹配spec.publishNotReadyAddresses前面说过svc匹配pod必须满足两个条件pod是就绪的并且满足子集匹配。但是总有需求要匹配未就绪的pod做个实验apiVersion: apps/v1 kind: Deployment metadata: name: myapp namespace: default spec: replicas: 3 selector: matchLabels: app: myapp svc: clusterip template: metadata: labels: app: myapp svc: clusterip spec: containers: - name: myapp-container image: nginx:v1 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 readinessProbe: exec: command: - test - -e - /tmp/live initialDelaySeconds: 1 periodSeconds: 3 --- apiVersion: v1 kind: Service metadata: name: myapp-service spec: type: ClusterIP selector: app: myapp ports: - name: http port: 80 targetPort: 80就绪探测未通过无法进行访问​kubectl edit svc svcName 在spec下加这么一行就行了也可以用打补丁命令也可以修改资源清单随便​修改之后访问就没问题了​