赞
踩
Service 存在的意义?
引入 Service 主要是解决 Pod 的动态变化,通过创建 Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。
若提供服务的容器应用是分布式,所以存在多个 pod 副本,而 Pod 副本数量可能在运行过程中动态改变,比如水平扩缩容,或者服务器发生故障 Pod 的 IP 地址也有可能发生变化。当 pod 的地址端口发生改变后,客户端再想连接访问应用就得人工干预,很麻烦,这时就可以通过 service 来解决问题。
概念:
Service 主要用于提供网络服务,通过 Service 的定义,能够为客户端应用提供稳定的访问地址(域名或 IP 地址)和负载均衡功能,以及屏蔽后端 Endpoint 的变化,是 K8s 实现微服务的核心资源。
svc 特点:
服务发现,防止阴滚动升级等因素导致 Pod IP 发生改变而失联,找到提供同一个服务的 Pod。
负载均衡,定义一组 Pod 的访问策略。
svc 与 pod 关系:
pod 在创建时,与资源没有明确关联,通过 service 标签和 pod 标签相匹配来以此关联。
可以通过 endpoints 来查看关联的 pod。
命令 | 说明 |
---|---|
kubectl create service clusterip web --tcp=80:80 | 命令创建 svc |
kubectl apply -f service.yaml | 定义 yaml 文件创建 svc |
kubectl expose deployment webapp | 命令创建 svc,需要提前创建 deploy |
kubectl get service | 查看 svc |
参数 | 释义 | 是否必选 |
---|---|---|
version | 版本号,例如 v1 | 必选 |
kind | 资源对象类型,例如 pod、deployment、service | 必选 |
metadata | 元数据 | 必选 |
metadata.name | 对象名称 | 必选 |
metadata.namespace | 对象所属的命名空间,默认值为 default | 必选 |
metadata.labels | 自定义标签列表 | |
metadata.annotation | 自定义注解列表 | |
spec | 详细定义描述 | 必选 |
spec.selector | Label Selector 配置,将选择具有指定 Label 标签的 Pod 作为管擦绡啷谎理范围 | 必选 |
spec.type | svc 类型,指定 svc 访问方式,默认为 ClusterIP。 1、ClusterP:虚拟服务 IP 地址,用于 K8s 集群内部的 Pod 访问,在 Node 上 kube-proxy 通过设置的 iptables 规则进行转发。 2、NodePort:对外部客户端提供访问应用使用。 3、LoadBalancer:使用外接负载均衡器完成到服务的负载分发,用于公有云环境。 | 必选 |
spec.clusterlP | 虚拟服务的 IP 地址。 当 type=ClusterIP 时,若不指定,则系统进行自动分配,也可以手工指定; 当 type=LoadBalancer 时,需要指定。 | |
spec.sessionAffinity | 是否支持 Session,可选值为 ClientP,默认值为 None。 ClientIP 表示将同一个客户端 (根据客户端的 IP 地址决定) 的访问请求都转发到同一个后端 Pod。 | |
spec.ports | 定义端口设置列表。 | |
spec.ports.name | 端口名称 | |
spec.ports.protocol | 端口协议,支持 TCP、HTTP、UDP、SCTP 等等,默认值为 TCP | |
Spec.ports.port | svc 端口 | |
spec.ports.targetPort | 容器端口 | |
spec.ports.nodePort | 当 spec.type=NodePort 时,指定映射到宿主机的端口号 | |
Status | 当 spec.ype=lodBalaner 时,设置外部负均衡器的地址,用于公有云环境 | |
status.loadBalancer | 外部负载均衡器 | |
status.loadBalancer.ingress | 外部负载均衡器 | |
status.loadBalancer.ingress.ip | 外部负载均衡器 IP | |
status.loadBalancer.ingress.hostname | 外部负载均衡器的主机名 | |
spec.selector | 指定关联 Pod 的标签 | 必选 |
\1. 创建一个 service,名称为 seride-demo,指定类型为 clusterip,第一个 80 是 svc 端口,第二个 80 是容器端口。
[root@k8s-master bck]# kubectl create service clusterip service-demo --tcp=80:80
\2. 查看 svc
\1. 先通过 kubectl expose 方式创建,先创建一个 deployment,此时的 pod 名称为 demo1,标签为 app=demo1。
\2. 创建一个 svc,名为 demo1,看是否能和 demo1 的 pod 关联上。
[root@k8s-master bck]# kubectl create svc clusterip demo1 --tcp=80:80
\3. 此时查看 demo1 的 svc 和 demo1 的 pod 关联上了,是因为创建的 svc 标签是和 deployment 创建的 pod 标签一样。
我们创建一个 svc 后,会随机分配和 cluster-ip,配合 svc 端口,可以在任何一个节点上访问其他应用。
\1. 创建一个 pod,配合上面创建的 svc demo1 来模拟前端访问后端。这里的 bs 为前端,demo1 为后端。进入 bs 容器,通过 clusterip:svc 端口来访问 demo1 前端网页。
将一个 Kubernetes 集群外部的已知服务定义为 Kubernetes 内的一个 Service,供集群内的其他应用访问。
应用场景:
已部署的一个集群外服务,例如数据库服务、缓存服务等。
其他 Kubernetes 集群的某个服务。
迁移过程中对某个服务进行 Kubernetes 内的服务名访问机制的验证。
\1. 准备一个集群外的服务,我这里在 192.168.130.147 上部署一个 nginx 服务。
[root@k8s-node2 ~]# docker run -d --name nginx1 -p 80:80 nginx
\2. 此时我想将这个 nginx 服务定义成 K8s 集群内部的 svc,供集群内的其他应用访问。
[root@k8s-master ~]# cat svc.yaml --- apiVersion: v1 kind: Service metadata: name: qingjun1 spec: ports: - protocol: TCP port: 80 targetPort: 80 --- apiVersion: v1 kind: Endpoints metadata: name: qingjun1 ##与svc名称一致。 subsets: - addresses: - ip: 192.168.130.147 ##外部服务地址 ports: - port: 80 ##外部服务访问端口 [root@k8s-master ~]# kubectl apply -f svc.yaml
\3. 查看,此时就会会创建一个 svc 并分配 clusterip,同时 endpoint 将其与外部服务关联。
\4. 此时进入测试容器验证,可以访问到外部服务。
[root@k8s-master ~]# kubectl run bs --image=busybox -- sleep 24h
多端口 Service 定义:对于某些服务,需要公开多个端口,Service 也需要配置多个端口定义,通过端口名称区分。
\1. 先创建 1 个 pod 内有两个容器,一个是 Nginx,一个是 tomcat。现在需要通过访问 80 端口到达 nginx,访问 svc 的 8080 端口到达 tomcat。
\2. 创建 svc,编辑 yaml 文件,添加多个端口。
[root@k8s-master bck]# cat svc.yaml apiVersion: v1 kind: Service metadata: name: web-multi spec: ports: # nginx - name: 80-80 port: 80 ##代理nginx80端口。 protocol: TCP targetPort: 80 # tomcat - name: 88-8080 port: 88 ##代理tomcat8080端口。 protocol: TCP targetPort: 8080 selector: app: web-multi type: ClusterIP
\3. 通过另外一个 pod 验证,进入 bs 容器,访问 web-multi 里的容器端口。访问 88 端口,则返回 tomcat;访问 80 端口,则返回 nginx。
ep 相当于 svc 的小弟,就相当于 rs 是 deployment 的小弟,帮忙连接多个 pod。
类型 | 描述 |
---|---|
ClusterIP | 集群内部使用(Pod) |
NodePort | 对外暴露应用(浏览器) |
LoadBalancer | 对外暴露应用,将 Service 映射到一个已存在的负载均衡器的 IP 地址上,适用公有云 |
ExternalName | 将 Service 映射为一个外部域名地址,通过 externalName 字段进行设置。 |
作用:默认分配一个稳定的虚拟 IP,即 VIP,仅可被集群内部的客户端应用访问。
原理图:
作用:在每个节点上启用一个端口来暴露服务,可以在集群外部访问。也会分配一个稳定内部集群 IP 地址。 基本常识:
访问地址:<任意 NodeIP>:
端口范围:30000-32767
默认情况下,Node 的 kube-proxy 会在全部网卡(0.0.0.0)上绑定 NodePort 端口号。也可以通过配置启动参数 “–nodeport-addresses” 指定需要绑定的网卡 IP 地址,多个地址之间使用逗号分隔。
原理图:
注意事项:
NodePort 会在每台 Node 上监听端口接收用户流量,在实际情况下,对用户暴露的只会有一个 IP 和端口,那这么多台 Node 该使用哪台让用户访问呢?
这时就需要前面加一个公网负载均衡器为项目提供统一访问入口了。比如在公网机器上部署 nginx 做负载均衡,配置配置文件 upstream ——> 网站 A 端口 30001,upstream2——> 应用服务 B 端口 30002。
yaml 文件配置模板
spec: type: NodePort ##修改此处 ports: - port: 80 protocol: TCP targetPort: 80 nodePort: 30009 ##添加此行,指定端口,也可以不指定,使用随机端口。 selector: app: web
\1. 配置 svc 的 yaml 文件,指定 NodePort 类型,使用随机端口。
##这里没有指定端口,后面是随机生成一个端口。 [root@k8s-master bck]# vim svc.yaml apiVersion: v1 kind: Service metadata: name: web-demo1 spec: ports: # nginx - name: 80-80 port: 80 protocol: TCP targetPort: 80 selector: app: demo1 type: NodePort ##指定类型。
\2. 导入 yaml 文件,查看 svc 随机端口为 32580。
\3. 浏览器访问验证。
\4. 使用指定端口。
[root@k8s-master bck]# cat svc.yaml apiVersion: v1 kind: Service metadata: name: web-demo1 spec: ports: # nginx - name: 80-80 port: 80 protocol: TCP targetPort: 80 nodePort: 30003 ##指定端口。 selector: app: demo1 type: NodePort ##指定类型。
作用:与 NodePort 类似,在每个节点上启用一个端口来暴露服务。除此之外,Kubernetes 会请求底层云平台(例如阿里云、腾讯云、AWS 等)上的负载均衡器,将每个 Node([NodeIP]:[NodePort])作为后端添加进去。
原理图:
协议 | 是否支持 |
---|---|
TCP | 默认网络协议,支持所有类型的 svc。 |
UDP | 可用于大多数类型的 svc,LoadBalancer 类型取决于云服务商对 UDP 的支持。 |
HTTP | 取决于云服务商是否支持 HTTP 和实现机制。 |
PROXY | 取决于云服务商是否支持 HTTP 和实现机制。 |
SCTP | 现已默认启用,如需关闭该特性,则需要设置 kube-apiserver 的启动参数–feature-gates=SCTPSupport=false 进行关闭。 |
定义 AppProtocol 字段,用于标识后端服务在某个端口号上提供的应用层协议类型,例如 HTTP、HTTPS、SSL、DNS 等。需要设置 kube-apiserver 的启动参数–feature-gates=ServiceAppProtocol=true 进行开启,然后在 Service 或 Endpoint 的定义中设置 AppProtocol 字段指定应用层协议的类型。
实现原理:
当一个 Service 对象在 K8s 集群中被定义出来时,集群内的客户端应用就可以通过服务 IP 访问到具体的 Pod 容器提供的服务了。从服务 IP 到后端 Pod 的负载均衡机制,则是由每个 Node 上的 kube-proxy 负责实现的。
实现负载均衡的 2 种方式:
kube-proxy 的代理模式,通过启动参数–proxy-mode 设置。
会话保持机制。设置 sessionAffinity 实现基于客户端 IP 的会话保持机制,即首次将某个客户端来源 IP 发起的请求转发到后端的某个 Pod 上,之后从相同的客户端 IP 发起的请求都将被转发到相同的后端 Pod 上。
kube-proxy 的代理模式分类:
userspace 模式:用户空间模式,由 kube-proxy 完成代理的实现,效率最低,不推荐。
iptables 模式:kube-proxy 通过设置 Linux Kernel 的 iptables 规则,实现从 Service 到后端 Endpoint 列表的负载分发规则,效率较高。用此模式时需要给 pod 设置健康检查,这样可以避免某个后端 Endpoint 不可用导致客户端请求失败。
ipvs 模式:1.11 版本推出,kube-proxy 通过设置 Linux Kernel 的 netlink 接口设置 IPVS 规则,转发效率和支持的吞吐率达到最高。ipvs 模式要求 Linux Kernel 启用 IPVS 模块,如果操作系统未启用 IPVS 内核模块,kube-proxy 则会自动切换至 iptables 模式。支持负载均衡策略如下:
rr:round-robin,轮询。
lc:least connection,最小连接数。
dh:destination hashing,目的地址哈希。
sh:source hashing,源地址哈希。
sed:shortest expected delay,最短期望延时。
nq:never queue,永不排队。
kernelspace 模式:Windows Server 上的代理模式。
Iptables 模式和 IPVS 模式对比:
iptables:灵活、功能强大;规则遍历匹配和更新,呈线性时延
IPVS:工作在内核态,有更好的性能;调度算法丰富:rr,wrr,lc,wlc,ip hash…
概念图:
数据包传输流程:
客户端 ->NodePort/ClusterIP(iptables/Ipvs 负载均衡规则) -> 分布在各节点 Pod
查看负载均衡规则:
iptables 模式:iptables-save |grep <SERVICE-NAME>
ipvs 模式:ipvsadm -L -n
项目流程:
项目 a:用户——> LB(基于域名分流 a.com) ——> service nodeport 30001 ——> 一组 pod(多副本)(相当于项目本身)
项目 b:用户——> LB(基于域名分流 b.com)——> service nodeport 30002 ——> 一组 pod(多副本)(相当于项目本身)
项目 c:用户——> LB(基于域名分流 c.com)——> service nodeport 30003 ——> 一组 pod(多副本)(相当于项目本身)
提问:
而这里我们就要看看,我们浏览器访问 svc(http://192.168.130.146:30003)是怎么转发到容器内的?
\1. 根据 svc 名称,在工作节点上查看 iptables 规则。
\2. 将两条规则复制出来分析。(第一步:流量入口)
第一步:浏览器访问 30003 端口,转发到服务器上的网络协议栈,再转发到该服务器的 iptables 来处理,根据 iptables 规则一条条处理。
-A KUBE-NODEPORTS -p tcp -m comment --comment “default/web-demo1:80-80” -m tcp --dport 30003 -j KUBE-EXT-72T25B5TU2KEWYA6
第二步:处理完第一条规则,再处理第二条,这里就是访问 cluster IP,访问 pod。
-A KUBE-SERVICES -d 10.104.171.31/32 -p tcp -m comment --comment “default/web-demo1:80-80 cluster IP” -m tcp --dport 80 -j KUBE-SVC-72T25B5TU2KEWYA6
\3. 这里给 demo1pod 扩容增加 2 个副本,好看出效果。
[root@k8s-master bck]# kubectl scale deployment demo1 --replicas=3
\4. 根据 iptables 规则链过滤查看负载均衡结果。(第二步:负载均衡)
1.第一个请求第一个规则的权重概率为33%。 -A KUBE-SVC-72T25B5TU2KEWYA6 -m comment --comment "default/web-demo1:80-80 -> 10.244.169.143:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-WNIKZVEWSTDE4VLZ 2.第二个请求第二个规则权重为50%,因为第一个规则已经请求了,就剩第二、第三个请求了。 -A KUBE-SVC-72T25B5TU2KEWYA6 -m comment --comment "default/web-demo1:80-80 -> 10.244.36.87:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-ZM2JCM2TG2VXDQG3 3.从第三个请求第三个规则权重为100%,因为此时就剩下最后一条规则,所以请求概率为100%。 -A KUBE-SVC-72T25B5TU2KEWYA6 -m comment --comment "default/web-demo1:80-80 -> 10.244.36.89:80" -j KUBE-SEP-CSYMMWI2AY6NBPWB
\5. 查看其中一个规则链,最后转发到对应容器里。(第三步:转发到容器里)
使用此种模式需要手动去修改,因为默认是 iptables 模式。不同的 k8s 集群搭建方式,其修改方式不同。kubeadm 方式修改 ipvs 模式:第一步:修改配置文kube-proxy件参数。 kubectl edit configmap kube-proxy -n kube-system ... mode: “ipvs“ ... 第二步:删除该组Pod,重建所有节点。 kubectl delete pod kube-proxy-btz4p -n kube-system
注意事项:kube-proxy 配置文件以 configmap 方式挂载存储。如果让所有节点生效,需要重建所有节点 kube-proxy pod二进制方式修改 ipvs 模式:第一步:编辑修改配置文件。 vi kube-proxy-config.yml mode: ipvs ipvs: scheduler: "rr“ 第二步:重启kube-proxy。 systemctl restart kube-proxy
\1. 查看配置文件位置。
[root@k8s-master bck]# kubectl get configmap -n kube-system
\2. 在线编辑 kube-proxy 文件,修改参数为 ipvs 模式。
[root@k8s-master bck]# kubectl edit configmaps kube-proxy -n kube-system
\3. 此时需要删除这组 pod 重建,相当于重启。我这里只删除了一个节点,是为了验证对比效果,正常情况下是要重启所有节点的。
[root@k8s-master bck]# kubectl delete pod kube-proxy-vkfkh -n kube-system
\4. 在删除的 kube-proxy 容器所在节点上验证,我这里删除的是 node2 节点上的 kube-proxy,重启后 ipvs 模式生效;node1 节点没有删除,也就没有重启,依然还是 iptables 模式。使用 ipvs 模式验证需要安装 ipvsadm。
yum -y install ipvsadm
查看 node1 节点没有显示,是因为还是采用的 iptables 模式。
查看 node2 节点,此时为 ipvs 模式就显示出路由数据。当访问 node2 节点 IP:30003 时,最后路由到列出来的对应容器里。
访问 cluster ip 时,就转发到对应的容器里。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。