[toc]
0x00 前言简述 Service - 服务介绍 描述: K8s中的Service实际上是微服务框架中的微服务,Service定义了一个服务的访问入口,可以通过该入口访问其背后一组的有Pod副本组成的集群实例;
Q: 什么是Service服务?
答: kubernetes 通过Labels(标签)选择的方式来匹配一组pod,然后提供对外访问的一种机制,一组pod可以对应到多个svc的, 每一个service(svc)都可以理解为一个微服务
Service有且只有一个算法 RB 轮询, 它能够提供负载均衡的能力但是在使用上有以下限制:
提供4层负载均衡能力【只能基于ip地址和端口进行转发】
提供7层功能【不能通过主机名及域名的方案去进行负载均衡】,但有时我们可能需要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的;
weiyigeek.top-Service服务连接图
Q: Service 如何与后端Pod关联?
答: SVC 与其后端的Pod多个副本集群通过Lable Selector进行关联(采用等式或者集合进行过滤
), 而构建这些Pod的RS、Deployment、StatefulSet等控制器是保证Pod数量满足预选定义值;
关联图示如下:1 2 3 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
的组合;1 2 3 4 5 $ 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 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是由负载均衡器算法决定的;
1 2 3 4 5 6 7 8 9 $ 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 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启动时自动注入这些变量;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 kubectl exec -it deploy-blog-html-0 env
Tips : 然而采用环境变量注入的方式有很大的局限性,环境变量信息只能注入到后于该Service启动的Pod,而先启动的Pod则是查询不到后启动的Service环境变量即
DNS 描述: 鉴于环境变量的方式的局限性以及SVC的Cluster IP
的可读性差等问题引入了DNS方式进行服务发现,利用Service Name做为DNS域名,应用程序或者集群中的其它服务可以通过域名+Port形式直接访问服务;
资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
操作实践:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ~/K8s/Day13$ kubectl create -f services-dns-test.yaml $ kubectl get pod -o wide | grep "dns" $ kubectl get svc ~/K8s/Day13$ kubectl exec -it busybox-dns-test -- sh -c "nslookup -type=a deploy-blog-svc"
七层服务发现 描述: 在实际的应用场景中一定是由某些服务要暴露给用户或者集群外部访问的比如网站服务, 而此时前面所提的四层服务发现仅仅限于K8s集群内部访问, 通过需要我们的前端代理来进行实现,例如采用Nginx-Ingress 或者 Traefik-Ingress
进行实现;
Q: K8s集群 Pod 间互访的原理
答: Pod IP 是由Docker Daemon(Docker Engine) 根据 docker0
网桥IP地址分配的或者是网络插件Flannel实现的, 即Pod间的通信是通过Pod IP所在虚拟二层网络通信的,而真实的TCP/IP流量是通过Node的网卡流出;1 2 3 4 5 6 7 8 9 10 11 12 $ ip addr | grep flannel -B 3 kubectl get pod -o wide
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端点控制器里面;
weiyigeek.top-访问内部Pod端口流程示意图
作用解析:
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 压力是非常大的;
weiyigeek.top-userspace-Proxy模式
IPtables - 代理模式 描述: 它在Userspace代理模式下进行改变,即所有的访问直接通过IPtables而不需要Kube-Proxy去调度访问; 此时 kube-apiserver 依然通过监控kube-proxy去实现iptables的端口的更新维护
优点: 访问速度大大增加以及Kube-Proxy稳定性会提高,并且承受的压力将会减少很多; 缺点: 性能方面还有待提高;
weiyigeek.top-IPtables-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:不排队调度
weiyigeek.top-ipvs-Proxy模式
负载均衡
在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
;
1) ClusterIP : 默认类型,自动分配一个仅Cluster内部可以访问的虚拟IP(常常由 flannel/Calico 网络插件进行管理
)【service创建一个仅集群内部可访问的ip,集群内部其他的pod可以通过该服务访问到其监控下的pod】
2) NodePort :在ClusterlP基础上为Service在每台机器上绑定一个端口,这样就可以通过 NodePort来访问该服务【在service及各个node节点上开启端口,外部的应用程序或客户端访问node的端口将会转发到service的端口,而service将会依据负载均衡随机将请求转发到某一个pod的端口上。一般暴露服务常用的类型】
注意: 为了防止某一个Node节点down掉建议将集群中所有Node节点地址+端口都设置进入负载均衡中;
3) LoadBalancer :在NodePort的基础上,借助 cloud provider 创建一个外部负载均衡器(在云主机构建K8s基础上),并将请求转发到:NodePort【在NodePort基础之上,即各个节点前加入了负载均衡器实现了真正的高可用,一般云供应商提供的k8s集群就是这种,即本身自带负载均衡器】
4) 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网络插件):1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 cat > ClusterIP-demo.yaml <<'EOF' # Namespace :后面的 Service 演示的资源清单都将放入在该名称空间下 kind: Namespace apiVersion: v1 metadata: name: service-test labels: keys: service-test --- apiVersion: apps/v1 kind: Deployment metadata: name: clusterip-deploy namespace: service-test spec: replicas: 3 selector: matchLabels: app: nginx-clusterip 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 --- apiVersion: v1 kind: Service metadata: name: clusterip-deploy namespace: service-test spec: type: ClusterIP selector: app: nginx-clusterip release: stabel ports: - name: http port: 80 targetPort: 80 EOF
操作流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 ~/K8s/Day6$ kubectl create -f ClusterIP-demo.yaml $ kubectl get ns | grep "service-test" $ kubectl get deploy -n service-test -o wide --show-labels $ kubectl get rs -n service-test -o wide --show-labels $ kubectl get pod -n service-test -o wide --show-labels ~/K8s/Day6$ kubectl get svc -n service-test -o wide ~/K8s/Day6$ curl http://10.108.129.19/host.html Hostname: clusterip-deploy-76b69d5c5b-cx8r6 <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-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
Headless - Service 描述:Headless Service 即无头服务,它也是一种特殊的Cluster IP, 当某应用不需要、不想要负载均衡(暂时不需要访问
)以及单独的Service IP时;
简单的说: 即为了更好的转发性能, 我们希望可以自己控制负载均衡策略来替代K8s默认的负载策略
, 或者一个应用期望知道同组服务的其他实例。
特点: 通过无头服务的方式去解决 hostname 和 portname 的变化问题也就是通过它去进行绑定;
配置: 通过指定 ClusterIP (.spec.clusterIP) 的值为 None
来创建 Headless Service。 PS : 这类 Service 并不会分配 Cluster IP 并且 kube-proxy 不会处理它们,所以平台也不会为它们进行负载均衡和路由;
Headless 资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 cat > headless-demo.yaml <<'EOF' apiVersion: v1 kind: Service metadata: name: myapp-headless namespace: service-test spec: clusterIP: "None" selector: app: nginx-clusterip release: stabel ports: - port: 80 targetPort: 80 EOF
操作流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 ~/K8s/Day6$ kubectl apply -f headless-demo.yaml ~/K8s/Day6$ kubectl get svc -n service-test $ kubectl get pod -n service-test -o wide ~/K8s/Day6$ kubectl get pod -n kube-system -o wide | grep "coredns" $ cat /etc/resolv.conf dig -t A myapp-headless.service-test.svc.cluster.local. @10.244.0.5 ~/K8s$ curl http://myapp-headless.service-test.svc.cluster.local/host.html ~/K8s$ curl http://myapp-headless.service-test.svc.cluster.local/host.html $ sudo ipvsadm -Ln ~/K8s/Day6$ kubectl delete -f headless-demo.yaml
NodePort - Service 描述: nodePort的原理是在node上开放一个端口(30000~32000)间的随机端口,当客户端访问该SVC则将向该端口的流量导入到kube-proxy
, 然后由 kube-proxy 进一步的根据SVC绑定的Pod标签
将请求转发给对应的Pod容器;
NodePort 资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cat > nodeport-demo.yaml <<'EOF' apiVersion: v1 kind: Service metadata: name: nodeport-demo namespace: service-test spec: type: NodePort selector: app: nginx-clusterip release: stabel ports: - name: http port: 80 targetPort: 80 nodePort: 32306 protocol: TCP EOF
操作流程:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 ~/K8s/Day6$ kubectl apply -f nodeport-demo.yaml ~/K8s/Day6$ kubectl get svc -n service-test -o wide ~/K8s/Day6$ curl http://10.98.144.122/host.html ~/K8s/Day6$ curl http://10.10.107.214:32306/host.html ~/K8s/Day6$ curl http://10.10.107.202:32306/host.html $ sudo ipvsadm -Ln ~/K8s/Day6$ kubectl delete -f nodeport-demo.yaml
weiyigeek.top-NodePort节点访问
PS : 采用IPtables作为负载均衡时候可以利用iptables -t nat -nvL KUBE-NODEPORTS
看见NAT转发链;
LoadBalancer - Service 描述:loadBalancer 和 nodePort 其实是同一种方式, 只是前者运行在云厂商服务器中的; 其两则区别在于 loadBalancer 就是可以调用 cloud provider【云供应商】去 创建 LB【负载均衡】来向节点导流, 但是这会增加额外的预算成本;
weiyigeek.top-LoadBalancer示意图
ExternalName - Service 描述: 该类型的 Service 通过返回 CNAME和它的值,可以将服务映射到externalName字段的内容(例如:hub.weiyigeek.top)。
它是 Service 的特例它没有 selector 也没有定义任何的端口和Endpoint
, 相反的对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务, 所以其作用类似软链或者快捷方式;
PS : 其目标是为了将外部流量引入到集群内部(在正式的生产环境中在两个集群中可以利用该方式进行访问)。
Tips: 说 ExternalName 接受 IPv4 地址字符串,但作为包含数字的 DNS 名称,而不是 IP 地址。 类似于 IPv4 地址的外部名称不能由 CoreDNS 或 ingress-nginx 解析,因为外部名称旨在指定规范的 DNS 名称。 要对 IP 地址进行硬编码,请考虑使用 headless Services 。
ExternalName 资源清单示例:1 2 3 4 5 6 7 8 9 10 cat > externalname-demo.yaml <<'EOF' apiVersion: v1 kind: Service metadata: name: externalname-demo namespace: service-test spec: type: ExternalName externalName: s.weiyigeek.top EOF
Tips: 当查找主机 externalname-demo.service-test.svc.cluster.local
时,群集DNS服务返回 CNAME 记录,其值为 s.weiyigeek.top。 访问 my-service 的方式与其他服务的方式相同,但主要区别在于重定向发生在 DNS 级别而不是通过代理或转发
。 如果以后您决定将数据库移到群集中,则可以启动其 Pod,添加适当的选择器或端点以及更改服务的类型。
操作流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ~/K8s/Day6$ kubectl apply -f externalname-demo.yaml ~/K8s/Day6$ kubectl get svc -n service-test -o wide ~/K8s/Day6$ dig -t A externalname-demo.service-test.svc.cluster.local. @10.244.0.5 ~/K8s/Day6$ telnet externalname-demo.service-test.svc.cluster.local. 80
externalIPs 资源清单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 tee svc-ExternalIP.yaml << 'EOF' kind: Service apiVersion: v1 metadata: name: extip namespace: test spec: externalIPs: - "61.128.1.26" ports: - name: http port: 8081 protocol: TCP targetPort: 80 selector: app: nginx-demo EOF
Tips: externalIPs 字段一般设置为外部网卡地址。 Tips: 注意externalIPs
字段千万不要使用kubernetes master
的网卡地址,否则无法管理k8s集群。
操作流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 kubectl create deployment -n test --image=nginx:latest --replicas 1 nginx-demo $ kubectl apply -f svc-ExternalIP.yam $ kubectl describe svc/extip -n test Name: extip Namespace: test Labels: <none> Annotations: <none> Selector: app=nginx-demo Type: ClusterIP IP: 10.108.92.122 External IPs: 61.128.1.26 Port: http 8081/TCP TargetPort: 80/TCP Endpoints: 172.16.100.120:80 Session Affinity: None Events: <none> $ kubectl get svc -n test blog1 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE extip ClusterIP 10.108.92.122 61.128.1.26 80/TCP 10m $ curl extip.test.svc:8081 $ curl 61.128.1.26:8081 Kubernetes Services Demo , WeiyiGeek $ netstat -tlnp | grep "61.128.1.26:8081" tcp 0 0 61.128.1.26:8081 0.0.0.0:* LISTEN -
0x03 Proxy - 代理转发 1.使用port-forward访问集群中的应用程序 描述:在实际进行Debug时使用 kubectl port-forward
访问 Kubernetes 集群中的 Redis Server进行调试;
Step1.分别为Redis创建Deployment和ServiceDeployment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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: 100 m memory: 100 Mi ports: - containerPort: 6379 END
Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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:1 2 3 4 5 kubectl apply -f redis-master-deployment.yaml kubectl apply -f redis-master-service.yaml
Step3.分别查看deployment与service部署情况:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 kubectl get pods kubectl get deployment kubectl get rs kubectl get svc -o wide | grep redis kubectl get pods redis-master-deployment-7d557b94bb-bzw2v --template='{{(index (index .spec.containers 0).ports 0).containerPort}}{{"\n"}}'
Step4.使用kubectl port-forward
命令转发本地端口到Pod的端口,用户可以使用资源的名称来进行端口转发1 2 3 4 5 6 7 8 9 10 11 12 13 14 kubectl port-forward redis-master-deployment-7d557b94bb-bzw2v 7000:6379 kubectl port-forward pods/redis-master-deployment-7d557b94bb-bzw2v 7000:6379 kubectl port-forward deployment/redis-master-deployment 7000:6379 kubectl port-forward svc/redis-master-service 7000:6379 kubectl port-forward rs/redis-master-deployment-7d557b94bb 7000:6379 [root@master-01 ~]$ kubectl port-forward --address 127.0.0.1,10.10.107.191 redis-master-deployment-7d557b94bb-bzw2v 7000:6379 redis-cli -h 10.10.107.191 -p 7000
Step5.总结本机 7000 端口的连接被转发到集群中 Redis Server 所在 Pod 的 6379 端口。利用该命令可以方便开发或者运维人员进行Debug调试;
注意事项:
1) 由于已知的限制,目前的端口转发仅适用于 TCP 协议。 在 issue 47862 中正在跟踪对 UDP 协议的支持。
0x04 映射外部服务到集群内 场景 1.集群外的数据库映射到集群内部(IP地址) 描述: 如果您在 Kubernetes 内部和外部分别运行一些服务应用,此时应用如果分别依赖集群内部和外部应用时,通过采用将集群外部服务映射到K8s集群内部。
希望未来某个时候您可以将所有服务都移入集群内,但在此之前将是“内外混用”的状态。幸运的是您可以使用静态 Kubernetes 服务来缓解上述痛点。
在本例中,假如有一个集群外的 MySQL 服务器, 由于此服务器在与 Kubernetes 集群相同的网络(或 VPC)中创建,因此可以使用高性能的内部 IP 地址映射到集群内部以供Pod访问。
第一步,我们创建一个将从此服务接收流量的 Endpoints 对象并将该对象与Service进行绑定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 tee mapping-svc-ep.yaml << 'EOF' kind: Endpoints apiVersion: v1 metadata: name: mysqldb namespace: test subsets: - addresses: - ip: 192.168.12.50 ports: - port: 3306 protocol: TCP --- kind: Service apiVersion: v1 metadata: name: mysqldb namespace: test spec: type : ClusterIP ports: - port: 13306 protocol: TCP targetPort: 3306 EOF
第二步,创建Service和Endpoints,然后您可以看到 Endpoints 手动定义了数据库的 IP 地址,并且使用的名称与服务名称相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ kubectl apply -f mapping-svc-ep.yaml $ kubectl describe svc/mysql -n test $ kubectl get svc -n test mysqldb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE mysql ClusterIP 10.102.172.40 <none> 13306/TCP 23s
第三步,集群内部Pod访问映射的MySQL服务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 $ telnet 10.102.172.40 13306 ~$ kubectl run busybox-demo -n test --image=busybox:latest --command -- sleep 3600 ~$ kubectl get pod -n test $ kubectl exec -it -n test busybox-demo -- sh ping mysql.test.svc -c 1 telnet mysql.test.svc 13306 telnet mysql 13306 Connected to mysql .... 5.5.5-10.4.18-MariaDB-1:10.4.18+maria~bionic-log ...
Kubernetes 将 Endpoints 中定义的所有 IP 地址视为与常规 Kubernetes Pod 一样。现在您可以用一个简单的连接字符串访问数据库:mysql://mysql.test.svc
或者 mysql://mysql
根本不需要在代码中使用 IP 地址!如果以后 IP 地址发生变化,您可以为端点更新 IP 地址,而应用无需进行任何更改。
场景 2.具有 URI 的远程服务映射到集群内部 描述: 如果您使用的是来自第三方的托管网站,它们可能会为您提供可用于连接的统一资源标识符 (URI)。 如果它们为您提供 IP 地址,则可以使用场景 1 中的方法。
在本例中,我在 集群外部创建了一个网站,而我想在集群内部进行重定向访问。
第一步,编写部署的资源清单。1 2 3 4 5 6 7 8 9 10 cat > mapping-svc-ExternalName.yaml <<'EOF' apiVersion: v1 kind: Service metadata: name: myweb namespace: test spec: type: ExternalName externalName: www.weiyigeek.top EOF
第二步,部署ExternalName类型的services, 我们创建一个 “ExternalName” Kubernetes 服务,此服务为您提供将流量重定向到外部服务的静态 Kubernetes 服务。此服务在内核级别执行简单的 CNAME 重定向,因此对性能的影响非常小。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ~$ kubectl apply -f mapping-svc-ExternalName.yaml ~$ kubectl describe svc/myweb -n test ~$ kubectl get svc -n test myweb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE myweb ExternalName <none> www.weiyigeek.top <none> 48s
第三步,进入Pod中进行访问测试1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ~$ kubectl exec -it -n test busybox-demo -- sh ping myweb wget myweb/api.json cat api.json wget --spider myweb.test.svc/api.json
Tips: 非常注意,由于 “ExternalName” 使用 CNAME 重定向,因此无法执行端口重映射我们无法使用port
指定集群内部访问端口字段。
场景 3.具有 URI 和端口重映射功能的远程托管数据库 描述: CNAME 重定向对于每个环境均使用相同端口的服务非常有效,但如果每个环境的不同端点使用不同的端口,CNAME 重定向就略显不足,此时我们可以
幸运的是我们可以使用一些基本工具来解决这个问题,手动创建无头服务及endpoint,引入外部数据库,然后通过k8s集群中的域名解析服务访问,访问的主机名格式为[svc_name].[namespace_name].svc.cluster.local
。
在本例中,我在其它K8S集群外部创建了一个appspring的应用,而我想在当前集群通过集群services进行访问调用。
第一步,资源清单的创建,此处使用无头服务,对应的svc及endpoint配置文件应该如下。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 tee svc-ep-clusterIP.yaml << 'EOF' kind: Endpoints apiVersion: v1 metadata: name: appspring namespace: test labels: app: appspring subsets: - addresses: - ip: 10.41.40.21 - ip: 10.41.40.22 - ip: 10.41.40.23 ports: - name: http port: 32179 protocol: TCP --- kind: Service apiVersion: v1 metadata: name: appspring namespace: test labels: app: appspring spec: clusterIP: None ports: - name: http port: 80 protocol: TCP targetPort: 32179 type : ClusterIP EOF
第二步,创建service及endpoint1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ kubectl apply -f svc-ep-clusterIP.yaml $ kubectl describe svc/appspring -n test $ kubectl get svc -n test
第三步,在k8s集群中启动一个alpine:latest
容器进行验证:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 kubectl run alpine-demo -n test --image=alpine:latest --command -- sleep 3600 kubectl exec -it -n test alpine-demo -- sh / PING appspring (10.41.40.22): 56 data bytes 64 bytes from 10.41.40.22: seq=0 ttl=60 time=1.196 ms / PING appspring (10.41.40.21): 56 data bytes 64 bytes from 10.41.40.21: seq=0 ttl=60 time=1.273 ms / PING appspring (10.41.40.23): 56 data bytes 64 bytes from 10.41.40.23: seq=0 ttl=60 time=1.304 ms / Connecting to appspring:32179 (10.41.40.22:32179) Connecting to appspring:32179 (10.41.40.21:32179) saving to 'index.html' index.html 100% |*************************************************************************| 5275 0:00:00 ETA 'index.html' saved/ / -rw-r--r-- 1 root root 5.2K Dec 6 14:58 index.htm
注:URI 可以使用 DNS 在多个 IP 地址之间进行负载平衡,因此,如果 IP 地址发生变化,这个方法可能会有风险!如果您通过上述命令获取多个 IP 地址,则可以将所有这些地址都包含在 Endpoints YAML 中,并且 Kubernetes 会在所有 IP 地址之间进行流量的负载平衡。
映射总结: 将外部服务映射到内部服务可让您未来灵活地将这些服务纳入集群,同时最大限度地减少重构工作。即使您今天不打算将服务加入集群,以后可能也会这样做!而且,这样一来,您可以更轻松地管理和了解组织所使用的外部服务。
如果外部服务具有有效域名,并且您不需要重新映射端口,那么使用 “ExternalName” 服务类型将外部服务映射到内部服务十分简便、快捷。如果您没有域名或需要执行端口重映射,只需将 IP 地址添加到端点并使用即可。
至此从K8s集群中引入外部服务实践完成。