四. Kubernetes Service

一. 应用访问与Kubernetes 中的 Service

1. Service是什么

  1. 上面通过k8s部署了一个应用, 执行"kubectl get deploy,pod -o wide" 可以查看k8s中安装的pod详细信息,内部包含pod的ip地址,但是这个地址现在只能通过内网访问,在使用docker是"docker run -p" 其中的-p就把访问地址暴露给外网了,k8s中怎么设置
  2. 再考虑一个问题:, 分布式架构中一个服务多个节点,每个节点运行在不同的pod上,上层怎么访问,k8s是如何负载选择对应节点的
  3. 进而提出了 Service,Service 称为负载均衡服务,封装Service目的是通过选择器选中代理一组pod,通过探针使被代理的这组pod实现服务发现功能,
  4. 即使Service中的无头服务类型,虽然需要使用ingress网络定义所有的访问逻辑才能被外部访问,但是内部service名字作为域名被pod解析,所以Pod之间可以使用Service名字访问
    在这里插入图片描述

2. 标签相关操作命令

  1. 因为封装Service是根据labels标签选择一组pod,进行封装的所以先看一下标签相关操作
  2. 打标签命令"kubectl lable 资源类型 资源名 设置方式"
//针对k8s节点设置标签
kubectl lable node k8s-node1-节点名称 node-role.kubernetes.io/worker=''
//针对pod设置标签
kubectl lable pod my-nginx2-pod名称 标签名

在这里插入图片描述
3. 删除标签命令

//对应标签上设置一个"-"即可
kubectl lable node k8s-node1(节点名称) node-role.kubernetes.io/worker-

3. 命令行方式封装删除service示例

  1. 示例
//解释:
//kubectl expose: 暴露,成一个负载均衡网络
//deployment: 需要暴露的部署应用的标签名
//--port:访问service的端口 8912(可以理解为堆外外暴露的端口)
//--target-port: pod容器的端口 8080(可以理解为与--port暴露出的端口映射的内部端口,例如通过--port暴露了8912,外面访问8912时,实际会打到pod内部的8080上)
//--type: 有多种类型
//--nodePort: 每个机器开发的端口 30403
kubectl expose deployment tomcat6 --port=8912 --target-port=8080 --type=NodePort
//deployment 等于 deploy
kubectl expose deploy tomcat6 --port=8912 --target-port=8080 --type=NodePort
 
 ## 进行验证
 kubectl get svc 
 curl ip:port
 
 ## kubectl exec 进去pod修改,并测试负载均衡
  1. 删除 Service 命令
kubectl delete service/my-nginx2

4. yaml 方式封装 Service 示例

  1. yaml方式封装service示例(只是示例下方yaml不能真实使用)
apiVersion: v1  #版本号
kind: Service #类型
metadata:  #元数据
  name: service   #名称
  namespace:  dev #所属命名空间
spec:  #详情描述
  selector: #选择器,用于确定当前service代理哪些pod
    app: nginx
  type: #Service 类型,指定service的访问方式
  clusterIP:  #虚拟服务的ip地址
  sessionAffinity: #session亲和性,支持ClientIP、None两个选项
  ports: #端口信息
  	- name: abc
      port: 3017 #service端口
      targetPort: 3005 #pod端口
      nodePort: 31123 #主机端口
    - name: redis
      port: 99#service端口
      targetPort: 6378 #pod端口
      nodePort: 31122 #主机端口 

4. Service 中的 type 属性详解

  1. type: 有多种类型,
  1. ClusterIP: 以集群ip方式暴露,多副本时带负载均衡.该方式下暴露的地址只允许集群内访问
  2. NodePort: 以节点端口方式暴露,每一台机器都会为这个Service随机分配一个指定的端口,.多副本时带负载均衡,该方式允许公网以"公网ip+暴露的端口"访问
  3. LoadBalancer:是可以实现集群外部访问服务的另外一种解决方案,并不是所有的k8s集群都会支持,大多是在公有云托管集群中会支持该类型, 负载均衡器是异步创建的,在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到< NodeIP>:NodePort,此模式只能在云服务器上使用。
  4. ExternalName:将服务通过 DNS CNAME记录方式转发到指定的域名,通过service代理外部域名
type = ClusterIP
  1. ClusterIP: 以集群ip方式暴露,多副本时带负载均衡.该方式下暴露的地址只允许集群内访问,在分配ip时默认会在指定网段中随机分配,也可以通过"spec.clusterIP"指定合法ip
  2. 命令行方式封装Service,类型为ClusterIP示例:
 kubectl expose deploy my-nginx2 --port=8912 --target-port=8080 --type=ClusterIP
  1. yaml 方式封装类型为ClusterIP的Service示例
apiVersion: v1
kind: Service
metadata:
  name: cluster-service-test
  namespace: default
spec:
  selector:
    app: canary-nginx
  type: ClusterIP #ClusterIP:指当前Service在集群内可以被所有人发现,k8s中默认给这个service分配一个集群内的网络
  #clusterIP: 10.96.88.99 #指定分配的ip地址(如果不知道k8s会默认给service分配一个集群内的网络地址)
  #clusterIPs: 指定一组
  ports:
  - name: abc
    port: 80  ## 访问当前service 的 80
    targetPort: 80  ## 派发到Pod的8080
  - name: redis
    port: 99#service端口
    targetPort: 6378 #pod端口
    nodePort: 31122 #主机端口 
  1. 然后执行"kubectl get all" 可以看到封装的service, 可以获取到封装后分配的ip地址,通过ip+映射端口号可以集群内部访问
    在这里插入图片描述
  2. 注意点: 封装Service时spec下有一个专门的clusterIP属性,如果不指定该属性值k8s会默认给service分配一个集群内的网络地址,我们也可以通过clusterIP属性指定一个ip
  3. 封装service后,在service中拿到的访问ip是哪来的, 再次复习初始化k8s命令,内部存在一个"service-cidr"命令, 就是通过该命令设置service网段范围的
kubeadm init \
--apiserver-advertise-address=10.170.11.8 \
--image-repository registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images \
--kubernetes-version v1.21.0 \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=192.168.0.0/16
type = NodePort
  1. NodePort: 以节点端口方式暴露,每一台机器都会为这个Service随机分配一个指定的端口,.多副本时带负载均衡,该方式允许公网以"公网ip+暴露的端口"访问,在使用NodePort类型时"ports"下存在一个专门的"nodePort"属性,也可以使用该属性设置分配指定端口

注意NodePort方式,端口是通过kubeProxy开在机器上的,使用"机器ip+暴露的指定端口"可以访问,使用"serviceIp+暴露的指定端口"是不可以访问的

  1. 命令行方式封装NodePort类型Service示例
 kubectl expose deploy my-nginx2 --port=8888 --target-port=8080 --type=NodePort
  1. yaml封装示例
apiVersion: v1
kind: Service
metadata:
  name: cluster-service-222
  namespace: default
spec:
  selector:
    app: canary-nginx
  type: NodePort  #每一台机器都为这个service随机分配一个指定的端口
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
    #nodePort: 31110 #不指定默认会在30000-32765这个范围内随机分配
  1. 执行"kubectl get all"命令,再次查看service中的信息发现,在port位置多了一个端口号, 结合上面封装service命令与下图获取到的信息解释:
  1. 使用NodePort方式时, k8s集群中的所有节点的都会开放一个相同的端口,例如此处开放的就是30959
  2. 执行逻辑是: 获取公网ip+暴露的端口号进行访问,暴露的端口会打到pod的端口上当前也就是8888, 而pod上的8888又与服务的80端口映射, 此时就可以访问到实际的服务
  3. 并且如果是集群中访问,使用内网ip+pod端口号此处是8888访问即可
    在这里插入图片描述
type = ExternalName
  1. ExternalName:将服务通过 DNS CNAME记录方式转发到指定的域名,通过service代理外部域名,如下,如果访问下方的Service时会将请求代理到指定域名上
apiVersion: v1
kind: Service
metadata:
  name: cluster-service-222
  namespace: default
spec:
  type: ExternalName #外部域名方式
  externalName: baidu.com #指定域名 
type = LoadBalancer
  1. LoadBalancer:是可以实现集群外部访问服务的另外一种解决方案,并不是所有的k8s集群都会支持,大多是在公有云托管集群中会支持该类型, 负载均衡器是异步创建的,在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到< NodeIP>:NodePort,此模式只能在云服务器上使用
  2. 注意该方式的负载均衡指的不是Service内部的,是给云平台开放的,该方式下相当于设置云平台,通过云平台创建一个负载均衡器,访问service时,service通过云平台创建的负载均衡器负载调用
  3. 当type设置为LoadBalancer时, 还可以设置一个loadBalancerSourceRanges区间,意思是指定哪些地址可以访问当前通过LoadBalancer创建出的负载均衡器,相当于指定了一个白名单
  4. loadBalancerIP: 使用LoadBalancer类型是通过该属性指定ip
  5. loadBalancerClass: 指定创建的外部负载均衡器类型
  6. healthCheckNodePort: 健康检查,检查创建的外部负载均衡器
  7. allocateLoadBalancerNodePorts: 负载到service的指定端口,所有机器是否全部开启这个端口

5. spec.clusterIP 与无头服务

  1. 通过"spec.clusterIP “给service分配指定ip,如果不设置k8s会默认给service分配一个集群内的网络地址,也可以指定为"None” 表示不分配ip,当前的Service是一个无头服务 “clusterIP: None” 不分配ip
  2. 在封装Service负载均衡网络时,有一种情况,不使用Service分配的ip,访问ip由代理的pod自己控制
  3. 编写Service yaml时部提供了一个专门的clusterIP属性,该属性设置为"None"就表示不分配ClusterIP,headless等
  4. 注意点这种service通常与StatefulSet有状态部署配合使用, 在StatefulSet有状态部署中存在一个serviceName属性,该属性值要与当前service下的metadata.name相同
  5. 这种Service称为无头服务,如果想要外部访问时需要使用ingress网络定义所有的访问逻辑,但是Pod之间可以用service名作为域名来访问,这种情况下拥有负载均衡功能,
  6. 也可以通过"podName.serviceName.namespace.svc.cluster.local"访问指定pod
apiVersion: v1
kind: Service
metadata:
  name: nginx-aaa ## 与使用该service的 yaml中的serviceName属性值相同
  namespace: default
spec:
  selector:
    app: ss-nginx
  clusterIP: None  ## 不要分配ClusterIP,headless service,整个集群的Pod能直接访问
  # 浏览器目前不能访问,mysql\mq 继续使用ingress网络定义所有的访问逻辑
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80
  1. StatefulSet部署yaml
apiVersion: apps/v1
kind: StatefulSet  ### 有状态副本集
metadata:
  name: stateful-nginx
  namespace: default
spec:
  selector:
    matchLabels:
      app: ss-nginx # has to match .spec.template.metadata.labels
  serviceName: "nginx-aaa"  ## 服务名,指定加到那个service里面
  replicas: 3 # 三个副本
  template: ## Pod模板
    metadata:
      labels:
        app: ss-nginx # has to match .spec.selector.matchLabels
    spec:
      containers:
      - name: nginx
        image: nginx
  1. 最终实现效果以mysql为例,通过StatefulSet 部署mysql服务,设置副本数,由于使用StatefulSet部署,当前mysql服务是有状态的,即使宕机重启访问ip不会变,然后通过clusterIP=None的service代理这些mysql服务,内部通过"podName.serviceName.namespace.svc.cluster.local"访问指定pod(外部浏览器访问时需要配合其他设置,如果想要外部访问时需要使用ingress网络定义所有的访问逻辑,也可以直接访问这个无头服务的service,通过service负载均衡调用指定pod)

6. spec.externalIPs 设置白名单

在封装service中,externalIPs可以与任何类型的spec.type一块使用,指定哪些外部ip可以访问当前service,相当于创建了一个白名单

7. Service 中的其它属性

  1. publishNotReadyAddresses: 是否发布未就绪的,默认false
  2. ipFamilyPolicy: 指定ip簇策略,例如SingleStack
  3. ipFamilies: 指定使用ipv4还是ipv6,默认ipv4
  4. internalTrafficPolicy: 内部流量策略,内部访问时流量分发策略,有Cluster负载均衡策略(默认)与Local优先节点两种
  1. 使用Cluster时,访问会到达Service,由Service负载均衡选择调用
  2. 使用Local时,优先选择当前节点中的进行调用
  1. externalTrafficPolicy: 外部流量策略,外部访问时流量分发策略,有Cluster与Local两种
  1. 使用Local时,不隐藏客户端原ip
  2. 使用Cluster,隐藏客户端原ip,type=NodePort时的默认值
  3. 是否隐藏客户端原ip会影响网络中的第二跳"second hop",会影响负载均衡效果,请求访问podA,由podA通过Service请求访问到podB,如果不隐藏ip,podB中可以拿到原请求podA的ip,如果隐藏了,podB的请求是由Service过来的拿到的就可能是由Service重写的ip,推荐使用Cluster
  1. sessionAffinity: session亲和会话保持,有ClientIP亲和和None不亲和两个值

在配置为ClientIP时,使用同一会话,进来将同一用户的多次请求打到同一个pod上

二. EndPoint 与 k8s服务发现原理

  1. 针对一组应用服务封装Service,由Service代理Pod,选择指定pod调用,服务发现踢除是怎么实现的
  2. endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址
  3. endpoint 是对应service的,在封装service时需要配置selector选择器,endpointController会根据选择器选中指定pod,创建对应的endpoint对象,内部保存了多个pod的ip地址,端口号等信息,假设选择器选中了三个满足条件的pod,那么创建的endpoint中它包含了这三个 pod 的 IP 地址和端口号(如果设置了副本数,那ip数=pod*对应的副本数)
  4. 后续如果就绪探针或存活探针探测失败时,Kubernetes 会将该 Pod 从对应的 Endpoint 中移除,以确保负载均衡只选择就绪的 Pod,后续会通过kube-proxy监听这个Endpoint
  5. endpointController: 是k8s集群控制器的其中一个组件功能如下:
  1. 负责生成和维护所有endpoint对象的控制器
  2. 负责监听service和对应pod的变化
  3. 监听到service被删除,则删除和该service同名的endpoint对象
  4. 监听到新的service被创建,则根据新建service信息获取相关pod列表,然后创建对应endpoint对象
  5. 监听到service被更新,则根据更新后的service信息获取相关pod列表,然后更新对应endpoint对象
  6. 监听到pod事件,则更新对应的service的endpoint对象,将podIp记录到endpoint中

无选择器Service 与 EndPoint

  1. 使用场景: 多台部署到云外部的机器地址的管理,举例:
  1. 例如多台mysql,部署在云外部
  2. 编写一个无选择器Service,编写一个对应的Endpoints
  3. 通过Endpoints管理多台mysql服务器访问地址,结合Service通过Endpoints实现服务发现踢除功能
  4. 后续mysql地址有变动时只需要修改Endpoints中管理的地址即可
#无选择器Service
apiVersion: v1
kind: Service
metadata:
  name: cluster-service-no-selector
  namespace: default
spec:
  ## 不选中Pod而在下面手动定义可以访问的EndPoint
  type: ClusterIP 
  ports:
  - name: abc
    port: 80  ## 访问当前service 的 80
    targetPort: 80  ## 派发到Pod的 80

---
apiVersion: v1
kind: Endpoints
metadata:
  name: cluster-service-no-selector  ## 和service同名
  namespace: default
subsets:
- addresses:
  - ip: 192.168.169.184
  - ip: 192.168.169.165
  - ip: 39.156.69.79
  ports:
  - name: abc  ## ep和service要是一样的
    port: 80
    protocol: TCP

三. 滚动升级

  1. 什么是滚动升级: 例如发布新版本,重新发布服务,升级服务等等
  2. 在k8s中通过分装service堆外暴露统一的访问地址, 然后通过Service控制负载均衡控制访问指定的机器,并且在针对封装了service的pod进行扩容时, 扩容出的pod会自动加入service中,针对这个特性,当某个服务需要升级时,
  1. 不操作原服务, 直接部署升级的服务,并设置用来封装service的标签
  2. 新部署的升级后的服务会自动加入到原来的service负载均衡网络中,一块对外提供服务
  3. 当新升级后的服务启动完毕后,将原服务下掉即可,
  4. 可以做到不停机升级,对外是无感知的
  1. 如何实现滚动升级:
  1. 首先要了解升级,升级的是镜像, 要明确升级哪个服务, 获取到对应升级镜像
  2. 升级的镜像对应现在运行中的哪个部署,获取到部署名,
  3. 获取升级的服务在pod中运行的容器名称
//1.获取部署信息, 拿到部署名
kubectl get depoloy
//2.获取pod信息,拿到需要升级的服务所在的pod名称
kubectl get pod
//3.通过pod名称,获取到pod中运行的Container容器名称,也就是实际运行的服务
kubectl get pod 容器名称 -p ymal|grep name

在这里插入图片描述

  1. 升级命令
kubectl set image deploy 部署名称 container容器名称=升级的新镜像

在这里插入图片描述

  1. 当执行完升级命令后,会先下载镜像,多节点情况下根据升级后的镜像默认会先启动新的服务,下掉一个老服务,如果新服务启动成功才会杀掉其它剩余的老服务,启动新服务替换,如果在下载新镜像或启动新服务失败,则会保留剩余原来的老服务对外提供服务,内部有一个保证可用的阈值

例如四个服务节点,升级服务时,会先基于新的镜像创建出两个新服务节点启动运行,同时会杀死一个旧服务,如果中间两个新服务启动失败,还会有三台旧服务对外提供服务

kubectl rollout 命令相关

  1. 有这样一种情况,更新版本成功后,发现代码异常需要回退到上一个版本该如何操作,执行"kubectl rollout",会获取到一下几个子集指令
  1. history : 查看历史
  2. undo: 默认回退到上次,可以添加"–to-revision=指定版本"回退
    在这里插入图片描述
  1. 执行"kubectl rollout history deploy 部署名称" 命令查看部署历史,如下图发现my-nginx2部署中存在三次历史,但是升级历史中的信息为< none >
    在这里插入图片描述
  2. 执行升级命令时通过"–record"记录升级信息,后续通过"kubectl rollout history deploy 部署名称" 查看历史时,就可以获取到升级的记录信息
//--record表示记录升级信息
kubectl set image deploy 部署名称 container容器名称=升级的新镜像 --record

在这里插入图片描述
4. 回退版本命令

kubectl rollout undo deployment.apps/部署名 --to-revision=指定版本

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值