0x00 前言简述
Service - 服务介绍
描述: K8s中的Service实际上是微服务框架中的微服务,Service定义了一个服务的访问入口,可以通过该入口访问其背后一组的有Pod副本组成的集群实例;
Q: 什么是Service服务?
答: kubernetes 通过Labels(标签)选择的方式来匹配一组pod,然后提供对外访问的一种机制一组pod可以对应到多个svc的, 每一个service(svc)都可以理解为一个微服务
Service有且只有一个算法 RB 轮询, 它能够提供负载均衡的能力但是在使用上有以下限制:
- 提供4层负载均衡能力【只能基于ip地址和端口进行转发】
- 提供7层功能【不能通过主机名及域名的方案去进行负载均衡】,但有时我们可能需要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的;
Q: Service 如何与后端Pod关联?
答: SVC 与其后端的Pod多个副本集群通过Lable Selector进行关联(采用等式或者集合进行过滤
), 而构建这些Pod的RS、Deployment、StatefulSet等控制器是保证Pod数量满足预选定义值;
关联图示如下:
frontend pod -> Service(SVC) -> -> Pod `lable:app=backend`
Label Selector -> pod `lable:app=backend`
Controller -> -> pod `lable:app=backend`
Q: 微服务给我们带来来什么好处?
答: 复杂的系统微服务服务化,由多个提供不同业务服务而彼此独立的微服务(Kubernetes Service)组成
,服务之间通过TCP/IP协议进行通信,便拥有了强大的分布式、水平弹性扩展能力
;
Q: 什么是EndPoint?
答: 它实际上就是我们所说的端点,默认情况下每个Pod将提供一个独立端点供SVC或者其它Pod进行访问,而端点实际上就是Pod IP + Container Port
的组合;
$ kubectl get ep -o wide
NAME ENDPOINTS AGE
deploy-blog-svc 10.244.0.209:80,10.244.1.196:80,10.244.2.99:80 # 对应则三个Pod 34d
deploy-maven-svc 10.244.0.236:8080,10.244.1.221:8080,10.244.2.124:8080 32d
kubernetes 10.10.107.202:6443 83d
Q: 采用Lable关联Controller创建的Pod后如何进行EndPoint访问?
答: 传统做法是前端部署一个负载均衡器(比如Nginx)并为该组开放一个对外端口例如8080,并将这些Pod的EndPoint转发到Nginx上,之后客户端将可以通过Nginx负载均衡(LB)对外的IP和Port来访问服务, 而对于Client的请求最终会被分发到那个Pod是由负载均衡器算法决定的;
$ kubectl get pod -o wide --show-labels
NAME READY STATUS AGE IP NODE LABELS # 关键点
deploy-blog-html-0 1/1 Running 34d 10.244.0.209 weiyigeek-ubuntu app=blog-html,controller-revision-hash=deploy-blog-html-b56f6cf65,release=stabel,statefulset.kubernetes.io/pod-name=deploy-blog-html-0
deploy-blog-html-1 1/1 Running 34d 10.244.1.196 k8s-node-4 app=blog-html,controller-revision-hash=deploy-blog-html-b56f6cf65,release=stabel,statefulset.kubernetes.io/pod-name=deploy-blog-html-1
deploy-blog-html-2 1/1 Running 34d 10.244.2.99 k8s-node-5 app=blog-html,controller-revision-hash=deploy-blog-html-b56f6cf65,release=stabel,statefulset.kubernetes.io/pod-name=deploy-blog-html-2
$ kubectl get svc -o wide --show-labels
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR # 关键点 LABELS
deploy-blog-svc NodePort 10.104.74.36 <none> 80:30088/TCP 34d app=blog-html,release=stabel <none>
Q: k8S 如何实现负载均衡?
答: 在K8s架构中我们提到过Kube-Proxy组件它实际上实现了一个软件负载均衡器的作用
,负责把Service的请求转发到某一个具体后端Pod实例上,并且在内部实现了回话保持的机制;
Tips : Pod 的EndPoint点可能会随着Pod的销毁或重新创建而发生改变,K8s为每个Service都分配了一个Cluster IP的全局虚拟IP
,一旦Service被创建其生命周期内该IP会一直保持不变, 所以只要Service Name 与 Service 的 Cluster IP 做一个DNS域名映射就可以轻松的解决服务发现的问题;
--
0x01 服务发现
描述: 谈到微服务的服务发现,那么久离不开服务发现这个棘手的问题,前面说了Service与Cluster IP关联原理,但随之而来的是K8S如何做到四层/七层服务发现的呢?
四层服务发现
描述: 四层服务发现主要有两种方式环境变量或者DNS
;
环境变量-environment
说明: 在K8s早期采用了Linux环境变量的方式,即每个Service生成一些对应的Linux环境变量,并在Pod启动时自动注入这些变量;
kubectl exec -it deploy-blog-html-0 env
# PATH=/usr/local/nginx/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# HOSTNAME=deploy-blog-html-0
# TERM=xterm
# DEPLOY_BLOG_SVC_SERVICE_PORT=80
# DEPLOY_BLOG_SVC_PORT_80_TCP_ADDR=10.104.74.36
# KUBERNETES_SERVICE_PORT=443
# KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
# KUBERNETES_PORT_443_TCP_PROTO=tcp
# DEPLOY_BLOG_SVC_SERVICE_HOST=10.104.74.36
# DEPLOY_BLOG_SVC_PORT_80_TCP=tcp://10.104.74.36:80
# DEPLOY_BLOG_SVC_PORT_80_TCP_PORT=80
# KUBERNETES_SERVICE_PORT_HTTPS=443
# KUBERNETES_PORT=tcp://10.96.0.1:443
# DEPLOY_BLOG_SVC_PORT=tcp://10.104.74.36:80
# DEPLOY_BLOG_SVC_PORT_80_TCP_PROTO=tcp
# KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
# DEPLOY_BLOG_SVC_SERVICE_PORT_HTTP=80
# KUBERNETES_SERVICE_HOST=10.96.0.1
# KUBERNETES_PORT_443_TCP_PORT=443
# NGINX_VERSION=1.19.4
# NJS_VERSION=0.4.4
# PKG_RELEASE=1~buster
# IMAGE_VERSION=3.0
# HOME=/root
Tips : 然而采用环境变量注入的方式有很大的局限性,环境变量信息只能注入到后于该Service启动的Pod,而先启动的Pod则是查询不到后启动的Service环境变量即
DNS
描述: 鉴于环境变量的方式的局限性以及SVC的Cluster IP
的可读性差等问题引入了DNS方式进行服务发现,利用Service Name做为DNS域名,应用程序或者集群中的其它服务可以通过域名+Port形式直接访问服务;
资源清单示例:
cat > services-dns-test.yaml <<'END'
apiVersion: v1
kind: Pod
metadata:
name: busybox-dns-test
namespace: default
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: "IfNotPresent"
command: ["sleep","3000"]
restartPolicy: Never
END
操作实践:
~/K8s/Day13$ kubectl create -f services-dns-test.yaml
# pod/busybox-dns-test created
$ kubectl get pod -o wide | grep "dns"
# busybox-dns-test 1/1 Running 44s 10.244.0.237 weiyigeek-ubuntu
# Service
$ kubectl get svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# deploy-blog-svc NodePort 10.104.74.36 <none> 80:30088/TCP 34d
# 通过Nslookup命令查看前面创建的 deploy-blog-svc
~/K8s/Day13$ kubectl exec -it busybox-dns-test -- sh -c "nslookup -type=a deploy-blog-svc"
# Server: 10.244.0.185
# Address: 10.244.0.185:53
# Name: deploy-blog-svc.default.svc.cluster.local
# Address: 10.104.74.36
七层服务发现
描述: 在实际的应用场景中一定是由某些服务要暴露给用户或者集群外部访问的比如网站服务, 而此时前面所提的四层服务发现仅仅限于K8s集群内部访问, 通过需要我们的前端代理来进行实现,例如采用Nginx-Ingress 或者 Traefik-Ingress
进行实现;
Q: K8s集群 Pod 间互访的原理
答: Pod IP 是由Docker Daemon(Docker Engine) 根据 docker0
网桥IP地址分配的或者是网络插件Flannel实现的, 即Pod间的通信是通过Pod IP所在虚拟二层网络通信的,而真实的TCP/IP流量是通过Node的网卡流出;
$ ip addr | grep flannel -B 3
# 6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
# link/ether 2e:d6:4c:68:a3:24 brd ff:ff:ff:ff:ff:ff
# inet 10.244.0.0/32 brd 10.244.0.0 scope global flannel.1
# 7: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
# link/ether 2e:5d:21:a6:1e:c2 brd ff:ff:ff:ff:ff:ff
# inet 10.244.0.1/24 brd 10.244.0.255 scope global cni0
kubectl get pod -o wide
# NAME READY STATUS AGE IP # 关键点 NODE
# busybox-dns-test 0/1 Completed 119m 10.244.0.237 weiyigeek-ubuntu
Tips: Service 的 Cluster IP 与 Pod IP 类似属于集群的内部地址属于虚拟IP,无法直接被其他K8S集群所访问;
Tips: Cluster IP 是通过集群的内部IP暴露服务,选择该值服务只能够在集群内部可以访问即默认为ServiceType = ClusterIP
Tips : 下面是几个常用于外部访问内部服务的方式:
- NodePort : 采用Kube-Proxy组件每个节点将为其Service开放一个30000~32000的外部端口
- LoadBalancer : 支持使用外部负载均衡的云提供商服务比如GCE或者AWS, lB是异步创建其信息将会通过Service的status.loadBalance字段发布出去;
- ExternalName : 通过返回CNAME 和 其值,可以将服务映射到ExternalName字段内
例如foo.example.com
(注意没有任何可u下代理被创建/只有Kubernetes V1.7或者更高版本的Kube-DNS支持)
代理实现原理
描述: 访问k8s集群中创建的内部Pod端口流程示意图, 其中Pod中的容器端口需要加入到EndPoints端点控制器里面;
作用解析:
- apiServer: 监听服务和端点通过kube-proxy去监控,以及通过监控kube-proxy去实现服务端点信息的发现;
- kube-proxy: 通过选择标签去监控对应的pod并写入到iptable规则里面去;
- client: 访问服务时通过iptables中的规则被定向到pod的地址信息(客户端访问pod是通过iptables去实现的);
- iptables : 规则是通过kube-proxy去写入的;
k8s代理模式的分类
描述: 在Kubernetes集群中,每个Node 运行一个kube-proxy 进程。kube-proxy负责为service 实现了一种VIP(虚拟IP)的形式【可以在集群内部直接访问】,而不是ExternalName【返回集群外部的地址信息】 的形式。
- 在Kubernetes v1.0 版本,代理完全由userspace实现。
- 在Kubernetes v1.1 版本,新增了iptables代理,但并不是默认的运行模式。
- 在Kubernetes v1.2 版本起,默认就是 iptables 代理。
- 在Kubernetes v1.14 版本起,默认使用 ipvs 代理但是缺省还是IPtables。
Userspace - 代理模式
描述: 客户端首先访问 iptables,然后通过 iptables 访问到 kube-proxy
之后访问到具体的pod上,同时kube-apiserver也会监控kube-proxy服务更新及端点的维护;
从上面描述过程中我们知道每次访问的时候都需要 Kube-proxy
进行一次代理, 这会导致 kube-proxy 压力是非常大的;
IPtables - 代理模式
描述: 它在Userspace代理模式下进行改变,即所有的访问直接通过IPtables而不需要Kube-Proxy去调度访问; 此时 kube-apiserver 依然通过监控kube-proxy去实现iptables的端口的更新维护
优点: 访问速度大大增加以及Kube-Proxy稳定性会提高,并且承受的压力将会减少很多;
缺点: 性能方面还有待提高;
IPvs - 代理模式
描述: IPVS模式实际是将iptables代理模式中iptables变更为ipvs, 即把原本是通过iptables进行服务定向转发变成了通过IPVS模块去实现负载均衡以及流量导向,其他的方面与IPtables代理模式相同所有的访问也是不经过Kube-Proxy的;
该模式 kube-proxy 会监视 Kubernetes service 对象和 Endpoints,调用netlink 接口以相应地创建ipvs 规则并定期与Kubernetes service 对象和 Endpoints 对象同步 ipvs规则,以确保ipvs状态与期望一致。当访问服务时流量将被重定向到其中一个后端 Pod;
优点:ipvs基于Netfilter的hook功能,在内核空间中使用哈希表作为底层数据结构,使得 ipvs 可以更快地重定向流量,并且在同步代理规则时具有更好的性能,此外ipvs为负载均衡算法提供了更多的选项;
PS : 如果操作系统没有提前预安装ipvs模块以及其依赖需求不满足时,K8S将会默认使用iptables的代理模式;
假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。
例如当 kube-proxy 以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装则kube-proxy将回退到iptables代理模式
IPVS 负载均衡算法:
- ·rr:轮询调度
- ·1c:最小连接数
- ·dh:目标哈希
- ·sh:源哈希
- ·sed: 最短期望延迟
- ·nq:不排队调度
负载均衡
- 在Kubernetes v1.0 版本,service 是“4层”(TCP/UDP over IP)即只能通过主机和端口进行负载概念。
- 在Kubernetes v1.1 版本,新增了IngressAPI(beta版),用来表示“7层”(HTTP)服务!可以进行7层的负载均衡。正是因为有了Ingress的API接口,我们才有了7层调度的功能。
Q: 为何不使用 round-robin DNS?
答: k8s不管是历史还是现在都没有使用过DNS,不采用DNS负载均衡集群,最大最有意义的一点就是DNS会在很多的客户端里进行缓存,很对服务访问DNS进行域名解析的时候解析完成以后得到地址以后很多的服务都不会对DNS的缓存进行清除,也就意味着只要缓存存在服务在下次访问的时候还是这个地址信息,因此也就达不到我们负载均衡的要求了,因此DNS一般仅仅作为负载均衡的一种辅助手段;
Tips : 注意此处的四与七层不是OSI模型中的概念而是负载均衡中的概念, 你可以简单通过以下两个例子进行了解;
- 四层负载均衡原理: 在接受到客户端请求后,通过修改数据包得
目的/源地址信息与端口号(ip+端口号)
将流量转发到应用服务器; - 七层负载均衡原理: 可以对同一个Web服务器进行负载,它除了根据IP加端口进行负载外,还可根据
http协议中的URL/浏览器类别/语言
来决定是否要进行负载均衡;
0x02 服务发现类型
描述: 在K8s集群中Service服务发现方式有以下四种类型ServiceType
;
- ClusterIP :默认类型,自动分配一个仅Cluster内部可以访问的虚拟IP(
常常由 flannel/Calico 网络插件进行管理
)【service创建一个仅集群内部可访问的ip,集群内部其他的pod可以通过该服务访问到其监控下的pod】
- 注意: 为了防止某一个Node节点down掉建议将集群中所有Node节点地址+端口都设置进入负载均衡中;
- NodePort :在ClusterlP基础上为Service在每台机器上绑定一个端口,这样就可以通过 NodePort来访问该服务【在service及各个node节点上开启端口,外部的应用程序或客户端访问node的端口将会转发到service的端口,而service将会依据负载均衡随机将请求转发到某一个pod的端口上。一般暴露服务常用的类型】
- LoadBalancer :在NodePort的基础上,借助 cloud provider 创建一个外部负载均衡器(在云主机构建K8s基础上),并将请求转发到:NodePort【在NodePort基础之上,即各个节点前加入了负载均衡器实现了真正的高可用,一般云供应商提供的k8s集群就是这种,即本身自带负载均衡器】
- ExternalName : 把集群外部的服务引入到集群内部来在集群内部直接使用。没有任何类型代理被创建,这只有kubernetes 1.7 或更高版本的kube-dns 才支持【当我们的集群服务需要访问k8s之外的集群时,可以选择这种类型,然后把外部服务的IP及端口写入到k8s服务中来,k8s的代理将会帮助我们访问到外部的集群服务】
ClusterIP - Service
描述: 它主要在每个 Node 节点使用 iptables【新版本默认是ipvs代理模式,并且笔者安装的K8s集群时也采用的IPVS模块因此此处为ipvs,代理模式不同所使用的底层方案也是不一致的
】,将发向clusterlP对应端口的数据,转发到kube-proxy中。
kube-proxy自己内部实现有负载均衡的方法,并可以查询到这个service下对应pod的地址和端口,进而把数据转发给对应的pod的地址和端口;
Tips : 采用IPVS模块替代了IPtables,其实还是采用IPtables中类似于netfilter的Hook功能进行实现的;
为了实现图上的功能,主要需要以下几个组件进行协同工作:
- (1) ApiServer : 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中
- (2) Kube-proxy : 每个节点中都有一个叫做kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables规则中
- (3) IPtables : 使用NAT等技术将virtuallP的流量转至endpoint中
ClusterIP 资源清单示例(注意此处采用的是IPVS负载均衡技术与Flannel网络插件):
cat > ClusterIP-demo.yaml <<'EOF'
# Namespace :后面的 Service 演示的资源清单都将放入在该名称空间下
kind: Namespace
apiVersion: v1
metadata:
name: service-test
labels:
keys: service-test
---
# Deployment 资源控制器
apiVersion: apps/v1
kind: Deployment
metadata:
name: clusterip-deploy
namespace: service-test
spec:
replicas: 3
selector: # 选择器
matchLabels:
app: nginx-clusterip # 匹配的Pod标签非常重要
release: stabel
template:
metadata:
labels:
app: nginx-clusterip # 模板标签
release: stabel
env: test
spec:
containers:
- name: nginx
image: harbor.weiyigeek.top/test/nginx:v2.0
imagePullPolicy: IfNotPresent
ports:
- name: http # 此端口在服务中的名称
containerPort: 80 # 容器暴露的端口
---
# Services 服务发现
apiVersion: v1
kind: Service
metadata:
name: clusterip-deploy
namespace: service-test
spec:
type: ClusterIP # Service 类型
selector:
app: nginx-clusterip # 【注意】与deployment资源控制器创建的Pod标签进行绑定;
release: stabel # Service 服务发现不能缺少Pod标签,有了Pod标签才能与之SVC对应
ports: # 映射端口
- name: http
port: 80 # cluster 访问端口
targetPort: 80 # Pod 容器内的服务端口
EOF
操作流程:
# (1) 资源清单的部署
~/K8s/Day6$ kubectl create -f ClusterIP-demo.yaml
# namespace/service-test created
# deployment.apps/clusterip-deploy created
# service/clusterip-deploy created
# (2) 查看资源控制器管理的pod
$ kubectl get ns | grep "service-test"
# service-test Active 5m23s
$ kubectl get deploy -n service-test -o wide --show-labels # deployment 控制器
# NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR LABELS
# clusterip-deploy 3/3 3 3 6m5s nginx harbor.weiyigeek.top/test/nginx:v2.0 app=nginx-clusterip,release=stabel <none>
$ kubectl get rs -n service-test -o wide --show-labels # 受到 deployment 资源控制器管理,但是由ReplicaSet资源控制器创建Pod
# NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR LABELS
# clusterip-deploy-76b69d5c5b 3 3 3 6m53s nginx harbor.weiyigeek.top/test/nginx:v2.0 app=nginx-clusterip,pod-template-hash=76b69d5c5b,release=stabel app=nginx-clusterip,env=test,pod-template-hash=76b69d5c5b,release=stabel
$ kubectl get pod -n service-test -o wide --show-labels # pod 相关信息
# NAME READY STATUS RESTARTS AGE IP NODE LABELS
# clusterip-deploy-76b69d5c5b-cnfrm 1/1 Running 0 8m33s 10.244.1.108 k8s-node-4 app=nginx-clusterip,env=test,pod-template-hash=76b69d5c5b,release=stabel
# clusterip-deploy-76b69d5c5b-cx8r6 1/1 Running 0 8m33s 10.244.1.107 k8s-node-4 app=nginx-clusterip,env=test,pod-template-hash=76b69d5c5b,release=stabel
# clusterip-deploy-76b69d5c5b-qkwjs 1/1 Running 0 8m33s 10.244.1.109 k8s-node-4 app=nginx-clusterip,env=test,pod-template-hash=76b69d5c5b,release=stabel
# (3) 查看 Service 资源控制器
~/K8s/Day6$ kubectl get svc -n service-test -o wide
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# clusterip-deploy ClusterIP 10.108.129.19 <none> 80/TCP 10m app=nginx-clusterip,release=stabel
# (4) 访问 & 结果验证
~/K8s/Day6$ curl http://10.108.129.19/host.html
Hostname: clusterip-deploy-76b69d5c5b-cx8r6 <br> # 基于 IPVS 负载轮询机制(Round-Robin)
Image Version: <u> 2.0 </u>
Nginx Version: 1.19.4
~/K8s/Day6$ curl http://10.108.129.19/host.html
Hostname: clusterip-deploy-76b69d5c5b-cnfrm <br>
Image Version: <u> 2.0 </u>
Nginx Version: 1.19.4
~/K8s/Day6$ curl http://10.108.129.19/host.html
Hostname: clusterip-deploy-76b69d5c5b-qkwjs <br>
Image Version: <u> 2.0 </u>
Nginx Version: 1.19.4
$ sudo ipvsadm -Ln | grep "10.108.129.19" -A 3 # ipvs 负载均衡
# IP Virtual Server version 1.2.1 (size=4096)
# Prot LocalAddress:Port Scheduler Flags
# -> RemoteAddress:Port Forward Weight ActiveConn InActConn
# TCP 10.108.129.19:80 rr
# -> 10.244.1.107:80 Masq 1 # 权重 0 # 当前连接数 1 # 命中数
# -> 10.244.1.108:80 Masq 1 0 1
# -> 10.244.1.109:80 Masq 1 0 1
Headless - Service
描述:Headless Service 即无头服务,它也是一种特殊的Cluster IP, 当某应用不需要、不想要负载均衡(暂时不需要访问
)以及单独的Service IP时;
简单的说: 即为了更好的转发性能, 我们希望可以自己控制负载均衡策略来替代K8s默认的负载策略
, 或者一个应用期望知道同组服务的其他实例。
特点: 通过无头服务的方式去解决 hostname 和 portname 的变化问题也就是通过它去进行绑定;
配置: 通过指定 ClusterIP (.spec.clusterIP) 的值为 None
来创建 Headless Service。
PS : 这类 Service 并不会分配 Cluster IP 并且 kube-proxy 不会处理它们,所以平台也不会为它们进行负载均衡和路由;
Headless 资源清单示例:
cat > headless-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
namespace: service-test
spec:
# type: Headless # Service 类型
clusterIP: "None" # 改变点实际上就是基于ClsterIP实现的
selector:
app: nginx-clusterip # 选择上面创建的Pod标签
release: stabel
ports:
- port: 80
targetPort: 80
EOF
操作流程:
# (1) 创建 svc 服务
~/K8s/Day6$ kubectl apply -f headless-demo.yaml
# service/myapp-headless created
# (2) 查看
~/K8s/Day6$ kubectl get svc -n service-test
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# clusterip-deploy ClusterIP 10.108.129.19 <none> 80/TCP 18h # app=nginx-clusterip,release=stabel
# myapp-headless ClusterIP None #(观察点) <none> 80/TCP 15s # app=nginx-clusterip,release=stabel
$ kubectl get pod -n service-test -o wide
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# clusterip-deploy-76b69d5c5b-cnfrm 1/1 Running 0 24h 10.244.1.108 k8s-node-4 <none> <none>
# clusterip-deploy-76b69d5c5b-cx8r6 1/1 Running 0 24h 10.244.1.107 k8s-node-4 <none> <none>
# clusterip-deploy-76b69d5c5b-qkwjs 1/1 Running 0 24h 10.244.1.109 k8s-node-4 <none> <none>
# (3) 在SVC中一旦创建成功后,他将写入到coreDNS中去,并且会有一个主机名被写入到coreDNS中;
# 写入格式 : svc的名称+命名空间的名称+当前集群的域名
~/K8s/Day6$ kubectl get pod -n kube-system -o wide | grep "coredns"
# coredns-6c76c8bb89-8cgjz 1/1 Running 1 7d18h 10.244.0.5 ubuntu <none> <none>
# coredns-6c76c8bb89-wgbs9 1/1 Running 1 7d18h 10.244.0.4 ubuntu <none> <none>
# (4) 意味着在无头服务中虽然它没有ip了,但可以通过访问域名的方案依然可以访问服务下的pod;
# 需要将 10.244.0.5 CoreDNS 设置到 /etc/resolv.conf
$ cat /etc/resolv.conf
# nameserver 127.0.0.53
# nameserver 10.244.0.5
# options edns0
# apt -y install bind-utils
dig -t A myapp-headless.service-test.svc.cluster.local. @10.244.0.5
# ;; ANSWER SECTION:
# myapp-headless.service-test.svc.cluster.local. 30 IN A 10.244.1.109 # 无头还是域名的方案进行访问
# myapp-headless.service-test.svc.cluster.local. 30 IN A 10.244.1.108
# myapp-headless.service-test.svc.cluster.local. 30 IN A 10.244.1.107
# ;; Query time: 92 msec
# ;; SERVER: 10.244.0.5#53(10.244.0.5)
# ;; WHEN: Fri Nov 13 16:14:59 CST 2020
# ;; MSG SIZE rcvd: 269
# (5) 验证
~/K8s$ curl http://myapp-headless.service-test.svc.cluster.local/host.html
# Hostname: clusterip-deploy-76b69d5c5b-cx8r6 <br>
# Image Version: <u> 2.0 </u>
# Nginx Version: 1.19.4
~/K8s$ curl http://myapp-headless.service-test.svc.cluster.local/host.html
# Hostname: clusterip-deploy-76b69d5c5b-cnfrm <br>
# Image Version: <u> 2.0 </u>
# Nginx Version: 1.19.4
# (6)可以看见访问没有走负载均衡的
$ sudo ipvsadm -Ln
# IP Virtual Server version 1.2.1 (size=4096)
# Prot LocalAddress:Port Scheduler Flags
# -> RemoteAddress:Port Forward Weight ActiveConn InActConn
# TCP 10.96.0.1:443 rr
# -> 10.10.107.202:6443 Masq 1 3 0
# TCP 10.96.0.10:53 rr
# -> 10.244.0.4:53 Masq 1 0 0
# -> 10.244.0.5:53 Masq 1 0 0
# TCP 10.96.0.10:9153 rr
# -> 10.244.0.4:9153 Masq 1 0 0
# -> 10.244.0.5:9153 Masq 1 0 0
# TCP 10.99.135.33:80 rr
# TCP 10.108.129.19:80 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 0
# -> 10.244.1.109:80 Masq 1 0 0
# TCP 10.109.65.22:80 rr
# UDP 10.96.0.10:53 rr
# -> 10.244.0.4:53 Masq 1 0 0
# -> 10.244.0.5:53 Masq 1 0 0
# (7) 删除创建的SVC
~/K8s/Day6$ kubectl delete -f headless-demo.yaml
NodePort - Service
描述: nodePort的原理是在node上开放一个端口(30000~32000)间的随机端口,当客户端访问该SVC则将向该端口的流量导入到kube-proxy
, 然后由 kube-proxy 进一步的根据SVC绑定的Pod标签
将请求转发给对应的Pod容器;
NodePort 资源清单示例:
cat > nodeport-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: nodeport-demo
namespace: service-test
spec:
type: NodePort # 关键点: Service 类型
selector:
app: nginx-clusterip # 关键点: 利用标签绑定 Pod
release: stabel
ports:
- name: http # 此端口在服务中的名称这必须是一个DNS_LABEL所有ServiceSpec内的端口必须有唯一的名称
port: 80 # 集群访问端口
targetPort: 80 # Pod 保留的端口
nodePort: 32306 # 节点访问的端口,(注意如果不指定该字段属性) 端口范围30000~32000
protocol: TCP # 协议类型
EOF
操作流程:
# (1) Service 资源控制器部署
~/K8s/Day6$ kubectl apply -f nodeport-demo.yaml
# service "nodeport-demo" create
# (2) 查看创建的NodePort,其保留的端口 80 (集群IP访问的端口):32306(node节点访问的端口)/TCP
~/K8s/Day6$ kubectl get svc -n service-test -o wide
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# clusterip-deploy ClusterIP 10.108.129.19 <none> 80/TCP 24h app=nginx-clusterip,release=stabel
# myapp-headless ClusterIP None <none> 80/TCP 6h1m app=nginx-clusterip
# nodeport-demo NodePort 10.98.144.122 <none> 80:32306/TCP 40s app=nginx-clusterip,release=stabel
# (3) 验证两种方式访问
~/K8s/Day6$ curl http://10.98.144.122/host.html
# Hostname: clusterip-deploy-76b69d5c5b-qkwjs <br>
# Image Version: <u> 2.0 </u>
# Nginx Version: 1.19.4
~/K8s/Day6$ curl http://10.10.107.214:32306/host.html
# Hostname: clusterip-deploy-76b69d5c5b-qkwjs <br>
# Image Version: <u> 2.0 </u>
# Nginx Version: 1.19.4
~/K8s/Day6$ curl http://10.10.107.202:32306/host.html
# Hostname: clusterip-deploy-76b69d5c5b-qkwjs <br>
# Image Version: <u> 2.0 </u>
# Nginx Version: 1.19.4
$ sudo ipvsadm -Ln
# Prot LocalAddress:Port Scheduler Flags
# -> RemoteAddress:Port Forward Weight ActiveConn InActConn
# --- 常规(Node节点与集群节点)
# TCP 10.10.107.202:32306 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 1
# -> 10.244.1.109:80 Masq 1 0 0
# TCP 10.98.144.122:80 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 1
# -> 10.244.1.109:80 Masq 1 0 0
# --- 特殊(也可以访问)
# TCP 172.17.0.1:32306 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 0
# -> 10.244.1.109:80 Masq 1 0 0
# TCP 172.18.0.1:32306 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 0
# -> 10.244.1.109:80 Masq 1 0 0
# TCP 10.244.0.0:32306 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 0
# -> 10.244.1.109:80 Masq 1 0 0
# TCP 10.244.0.1:32306 rr
# -> 10.244.1.107:80 Masq 1 0 0
# -> 10.244.1.108:80 Masq 1 0 0
# -> 10.244.1.109:80 Masq 1 0 0
# (4) 删除创建的service
~/K8s/Day6$ kubectl delete -f nodeport-demo.yaml
# service "nodeport-demo" deleted
PS : 采用IPtables作为负载均衡时候可以利用iptables -t nat -nvL KUBE-NODEPORTS
看见NAT转发链;
LoadBalancer - Service
描述:loadBalancer 和 nodePort 其实是同一种方式, 只是前者运行在云厂商服务器中的; 其两则区别在于 loadBalancer 就是可以调用 cloud provider【云供应商】去 创建 LB【负载均衡】来向节点导流, 但是这会增加额外的预算成本;
ExternalName - Service
描述: 该类型的 Service 通过返回 CNAME和它的值,可以将服务映射到externalName字段的内容(例如:hub.weiyigeek.top)。它是 Service 的特例它没有 selector 也没有定义任何的端口和Endpoint
。相反的对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务;
PS : 其目标是为了将外部流量引入到集群内部(在正式的生产环境中在两个集群中可以利用该方式进行访问)。
ExternalName 资源清单示例:
cat > externalname-demo.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: externalname-demo
namespace: service-test
spec:
type: ExternalName
externalName: s.weiyigeek.top
EOF
操作流程:
# (1) 创建
~/K8s/Day6$ kubectl apply -f externalname-demo.yaml
# service/externalname-demo created
# (2) 查看
~/K8s/Day6$ kubectl get svc -n service-test -o wide
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
# externalname-demo ExternalName <none> self.weiyigeek.top <none> 25s <none>
# (3) 查询主机DNS解析集群的DNS服务器将返回一个值my.database.example.com的CNAME记录;
# 访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在DNS层,而且不会进行代理或转发。
~/K8s/Day6$ dig -t A externalname-demo.service-test.svc.cluster.local. @10.244.0.5
# ;; ANSWER SECTION:
# externalname-demo.service-test.svc.cluster.local. 30 IN CNAME s.weiyigeek.top.
# s.weiyigeek.top. 30 IN A 125.32.244.252
~/K8s/Day6$ telnet externalname-demo.service-test.svc.cluster.local. 80
# Trying 125.32.244.252...
# Connected to s.weiyigeek.top.
# Escape character is '^]'.
0x03 Proxy - 代理转发
1.使用port-forward访问集群中的应用程序
描述:在实际进行Debug时使用 kubectl port-forward
访问 Kubernetes 集群中的 Redis Server进行调试;
Step1.分别为Redis创建Deployment和Service
Deployment
cat > redis-master-deployment.yaml<<'END'
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-master-deployment
labels:
app: redis
spec:
selector:
matchLabels:
app: redis
role: master
tier: backend
replicas: 1
template:
metadata:
labels:
app: redis
role: master
tier: backend
spec:
containers:
- name: master
image: redis
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 6379
END
Service
cat > redis-master-service.yaml<<'END'
apiVersion: v1
kind: Service
metadata:
name: redis-master-service
labels:
app: redis
role: master
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
role: master
tier: backend
END
Step2.执行apply命令以创建 Redis Deployment与Service:
kubectl apply -f redis-master-deployment.yaml
# deployment.apps/redis-master-deployment created
kubectl apply -f redis-master-service.yaml
# service/redis-master-service created
Step3.分别查看deployment与service部署情况:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# redis-master-deployment-7d557b94bb-bzw2v 1/1 Running 0 3m2s
#查看 Deployment状态
kubectl get deployment
# NAME READY UP-TO-DATE AVAILABLE AGE
# redis-master-deployment 1/1 1 1 3m49s
# 查看 ReplicaSet(副本) 状态
kubectl get rs
# NAME DESIRED CURRENT READY AGE
# redis-master-deployment-7d557b94bb 1 1 1 4m46s
# 检查 Service 创建结果
kubectl get svc -o wide | grep redis
# redis-master-service ClusterIP 10.99.192.159 <none> 6379/TCP 9m27s app=redis,role=master,tier=backend
# 验证 Redis Service已经运行并监听了 6379 端口
kubectl get pods redis-master-deployment-7d557b94bb-bzw2v --template='{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'
# 6379
Step4.使用kubectl port-forward
命令转发本地端口到Pod的端口,用户可以使用资源的名称来进行端口转发
#(1)下面的命令中的任意一行,都可以实现端口转发的效果:
kubectl port-forward redis-master-deployment-7d557b94bb-bzw2v 7000:6379
kubectl port-forward pods/redis-master-deployment-7d557b94bb-bzw2v 7000:6379 # kubectl get pods 获取资源名称
kubectl port-forward deployment/redis-master-deployment 7000:6379 # kubectl get deployments 获取资源名称
kubectl port-forward svc/redis-master-service 7000:6379 # kubectl get svc 获取资源名称
kubectl port-forward rs/redis-master-deployment-7d557b94bb 7000:6379 # kubectl get rs 获取资源名称
#(2)以上命令的输出结果类似:
[root@master-01 ~]$ kubectl port-forward --address 127.0.0.1,10.10.107.191 redis-master-deployment-7d557b94bb-bzw2v 7000:6379
# Forwarding from 127.0.0.1:7000 -> 6379
# Handling connection for 7000 (连接到此端口反应)
redis-cli -h 10.10.107.191 -p 7000
# 10.10.107.191:7000> ping
# PONG
Step5.总结本机 7000 端口的连接被转发到集群中 Redis Server 所在 Pod 的 6379 端口。利用该命令可以方便开发或者运维人员进行Debug调试;
注意事项:
- 由于已知的限制,目前的端口转发仅适用于 TCP 协议。在 issue 47862 中正在跟踪对 UDP 协议的支持。