写点什么

关于 Kubernetes 中如何访问集群外服务的一些笔记

作者:山河已无恙
  • 2022-12-19
    内蒙古
  • 本文字数:5591 字

    阅读完需:约 18 分钟

写在前面



  • 分享一些 k8s 中服务如何访问集群外服务的笔记

  • 博文内容涉及:

  • 如何访问集群外服务

  • 创建外部服务代理 SVC(IP+PORT 情况)

  • Endponts/EndpointSlice 实现 Demo

  • 外部服务为 单体/集群 的访问 Demo

  • 创建 ExternalName 类型 SVC(域名的情况)

  • 理解不足小伙伴帮忙指正


是故不应取法,不应取非法。以是义故,如来常说:汝等比丘,知我说法,如筏喻者,法尚应舍何况非法。 ----------《金刚经》



如何访问集群外服务

在 K8s 中,考虑某些稳定性问题,希望把数据库部署到 物理机或者虚机上,或许系统正在一点点迁移到 K8s 平台,某些服务在非 k8s 集群部署,或者上游系统是别人的,和我们没有直接关系。那么我们如何实现 K8s 集群上的服务访问 这些外部服务。

外部服务是 IP 端口的方式

在 K8s 中,我们可以定义一个没有 lable SelectorService 来代替 非当前集群的服务。通过 IP 端口映射的方式把外部服务映射到内部集群中。


这样可以正常接入外部服务的同时,添加了一个类似外部服务的代理服务。之后如果外部服务发生 IP 端口变更,只需要修改映射关系即可,不需要修改应用相关的配置。同时对访问他的 pod 隐藏了实际的 IP 端口,以后如果服务移入集群内,则不需要更改任何代码。

外部服务是域名的方式

当 外部服务提供的方式是域名的时候,我们可以创建一个 Service 类型为 ExternalName 的 SVC,同样没有lable Selector, 类型为 ExternalName 的服务将外部服务域名映射到集群内部服务的 DNS 名称,而不是对应的 Pod 。

创建外部服务代理服务

适用于外部服务为 IP:Port 的方式,定义一个没有 选择器的 Service ,对应这样的 Servicek8s 不会自动创建对应的 EndpointEndpointSlice ,其他的和正常的 Service 没有区别,所以我们需要提供 Service 对应的 endpoint 或者是 EndpointSlice 来对外部服务做映射。类似与通过 iptables 做了 DNAT 的映射,实现 IP 端口转发。


资源文件的定义


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$cat not-service.yamlapiVersion: v1kind: Servicecametadata:  name: external-servicespec:  ports:    - protocol: TCP      port: 30056      targetPort: 3306
复制代码


定义了一个普通的 Service ,没有选择器,在 Service 内部做了转发,暴露的端口为 30056 转发到端口 3306, 这里的 3306 为代理的外部服务的端口。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl describe svc external-serviceName:              external-serviceNamespace:         awxLabels:            <none>Annotations:       <none>Selector:          <none>Type:              ClusterIPIP Family Policy:  SingleStackIP Families:       IPv4IP:                10.103.93.20IPs:               10.103.93.20Port:              <unset>  30056/TCPTargetPort:        3306/TCPEndpoints:         <none>Session Affinity:  NoneEvents:            <none>
复制代码


可以看到当前的 Service 类型为 ClusterIP, 对应的集群 IP 为 :10.103.93.20,Endpoints 为 None。


对于这样的 Service 系统不会自动创建 EndpointEndpointSlice,因此需要手动创建一个和该 Service 同名的Endpoint 或者带序号的 EndpointSlice 对象 ,用于指向实际的 后端访问地址


1.21 版本之前的只能通过创建 Endponits 的方式,创建 Endpoint 的配置文件内容如下:

Endponits 方式

如果外部服务为单体服务,那么我们只定义一个 IP 就可以


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$cat external-service.yamlkind: EndpointsapiVersion: v1metadata:  name: external-servicesubsets:- addresses:  - ip: 192.168.26.81  ports:  - port: 3306
复制代码


这里定义 集群外的服务 IP 为 192.168.26.81,端口为 3306, 这个 endpoint 即表示集群外的服务,生产环境中,我们需要打通相关的网络。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl apply  -f external-service.yamlendpoints/external-service created┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl get endpoints external-serviceNAME               ENDPOINTS            AGEexternal-service   192.168.26.81:3306   57s
复制代码


在集群外通过 python 模块发布一个 简单的 http 服务,暴露端口 3306,做简单测试。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$coproc python -m SimpleHTTPServer 3306[2] 109525┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl get svc external-serviceNAME               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)     AGEexternal-service   ClusterIP   10.103.93.20   <none>        30056/TCP   26m
复制代码


通过访问集群服务,实现对集群外部服务的访问


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$curl 10.103.93.20:30056 -s -w "%{http_code}\n" -o /dev/null192.168.26.81 - - [10/Dec/2022 03:26:30] "GET / HTTP/1.1" 200 -200
复制代码

EndpointSlice 方式

对于 1.21 版本及之后的版本来讲,我们可以通过 EndpointSlice 来实现,资源文件的定义,这里如果外部服务为集群,可以定义多个 IP 地址


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$cat external-service-1.yamlapiVersion: discovery.k8s.io/v1kind: EndpointSlicemetadata:  name: external-service-1 # 按惯例将服务的名称用作 EndpointSlice 名称的前缀  labels:    # 你应设置 "kubernetes.io/service-name" 标签。    # 设置其值以匹配服务的名称    kubernetes.io/service-name: external-serviceaddressType: IPv4ports:  - name: '' # 留空,因为 port 9376 未被 IANA 分配为已注册端口    appProtocol: http    protocol: TCP    port: 3306endpoints:  - addresses:      - "192.168.26.82" # 此列表中的 IP 地址可以按任何顺序显示  - addresses:      - "192.168.26.81"
复制代码


这里我们提供了两个 ip ,来模拟外部服务集群的情况。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl apply  -f external-service-1.yamlendpointslice.discovery.k8s.io/external-service-1 created┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl get endpointslices.discovery.k8s.io  external-service-1NAME                 ADDRESSTYPE   PORTS   ENDPOINTS                     AGEexternal-service-1   IPv4          3306    192.168.26.81,192.168.26.82   20s
复制代码


在集群外 81,82 两台机器通过 python 模块发布一个 简单的 http 服务,暴露端口 3306,做简单测试。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$coproc python -m SimpleHTTPServer 3306[2] 9084┌──[root@vms82.liruilongs.github.io]-[~]└─$coproc python -m SimpleHTTPServer 3306
复制代码


测试可以看到在 81 和 82 两个外部服务轮询访问,默认情况下 Serviec 的 负载均衡策略为,sessionAffinity: None ,即 RoundRobin 将客户端请求代理到合适的后端合适的 Pod 上


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$while true;do curl 10.103.93.20:30056 -s -w "%{http_code}\n" -o /dev/null ;sleep 2;done192.168.26.81 - - [10/Dec/2022 03:56:52] "GET / HTTP/1.1" 200 -200200200192.168.26.81 - - [10/Dec/2022 03:56:58] "GET / HTTP/1.1" 200 -200200192.168.26.81 - - [10/Dec/2022 03:57:02] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 03:57:04] "GET / HTTP/1.1" 200 -200200192.168.26.81 - - [10/Dec/2022 03:57:08] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 03:57:10] "GET / HTTP/1.1" 200 -200
复制代码


这里我们修改一下,修改为会话保持 sessionAffinity: ClientIP


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl edit svc external-serviceservice/external-service edited┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl get svc external-service  -o json | jq .spec.sessionAffinity"ClientIP"
复制代码


可以看到当前 访问只到 81 上面


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$while true;do curl 10.103.93.20:30056 -s -w "%{http_code}\n" -o /dev/null ;sleep 2;done192.168.26.81 - - [10/Dec/2022 04:00:56] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 04:00:58] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 04:01:00] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 04:01:02] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 04:01:04] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 04:01:06] "GET / HTTP/1.1" 200 -200192.168.26.81 - - [10/Dec/2022 04:01:08] "GET / HTTP/1.1" 200 -200
复制代码


DNS 解析测试,可以看到 对于没有选择器的服务来讲,同样可以通过 服务名对应的域名来解析到对应的 集群 IP 地址,这与 有选择器的相同。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl run pod-test -it --rm --image=yauritux/busybox-curl  --image-pull-policy=IfNotPresentIf you don't see a command prompt, try pressing enter./home # nslookup external-service.awx.svc.cluster.local.Server:    10.96.0.10Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: external-service.awx.svc.cluster.local.Address 1: 10.103.93.20 external-service.awx.svc.cluster.local/home # Session ended, resume using 'kubectl attach pod-test -c pod-test -i -t' command when the pod is runningpod "pod-test" deleted
复制代码

域名的方式:ExternalName

这里假设 集群外的服务为 我的 个人主页 https://liruilongs.github.io/


创建一个 ExternalName 类型的 SVC,当然也可以设置端口,这里我们不需要。


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$cat external-service.yamlapiVersion: v1kind: Servicemetadata:  name: external-servicespec:  type: ExternalName  externalName: liruilongs.github.io
复制代码


查看详细信息,CLUSTER-IP 为 none


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl apply  -f external-service.yamlservice/external-service created┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl get svcNAME               TYPE           CLUSTER-IP   EXTERNAL-IP            PORT(S)   AGEexternal-service   ExternalName   <none>       liruilongs.github.io   <none>    5s┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl describe svc external-serviceName:              external-serviceNamespace:         liruilong-deploy-createLabels:            <none>Annotations:       <none>Selector:          <none>Type:              ExternalNameIP Families:       <none>IP:IPs:               <none>External Name:     liruilongs.github.ioSession Affinity:  NoneEvents:            <none>
复制代码


解析域名测试,可以发现,external-service 经过 k8s 的内部 DNS 记录为 liruilongs.github.io,解析获得的 ipv4 和 ipv6 完全相同。所以 pod 可以通过域名连接到外部服务,而不是使用服务的实际 FQDN


┌──[root@vms81.liruilongs.github.io]-[~/ansible]└─$kubectl run pod-test -it --rm --image=yauritux/busybox-curl  --image-pull-policy=IfNotPresentIf you don''t see a command prompt, try pressing enter./home # nslookup external-serviceServer:    10.96.0.10Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: external-serviceAddress 1: 2606:50c0:8001::153Address 2: 2606:50c0:8003::153Address 3: 2606:50c0:8000::153Address 4: 2606:50c0:8002::153Address 5: 185.199.111.153 cdn-185-199-111-153.github.comAddress 6: 185.199.109.153 cdn-185-199-109-153.github.comAddress 7: 185.199.110.153 cdn-185-199-110-153.github.comAddress 8: 185.199.108.153 cdn-185-199-108-153.github.com/home # nslookup liruilongs.github.ioServer: 10.96.0.10Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: liruilongs.github.ioAddress 1: 2606:50c0:8003::153Address 2: 2606:50c0:8001::153Address 3: 2606:50c0:8000::153Address 4: 2606:50c0:8002::153Address 5: 185.199.111.153 cdn-185-199-111-153.github.comAddress 6: 185.199.110.153 cdn-185-199-110-153.github.comAddress 7: 185.199.108.153 cdn-185-199-108-153.github.comAddress 8: 185.199.109.153 cdn-185-199-109-153.github.com
复制代码


ExternalName 服务仅在 DNS 级别实现,为该服务创建一个简单的 CNAME DNS 记录。因此,连接到服务的客户端将直接连接到外部服务,完全绕过服务代理。出于这个原因,这些类型的服务甚至没有获得集群 IP。所以对于域名的解析,实际上是依赖于 节点机器。

博文参考



https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/


https://stackoverflow.com/questions/74795408/clean-way-to-connect-to-services-running-on-the-same-host-as-the-kubernetes-clus


《Kubernetes 实战》


发布于: 刚刚阅读数: 4
用户头像

InfoQ写作平台签约作者,RHCE、CKA认证 2022-01-04 加入

Java 后端一枚,技术不高,前端、Shell、Python 也可以写一点.纯种屌丝,不热爱生活,热爱学习,热爱工作,喜欢一直忙,不闲着。喜欢篆刻,喜欢吃好吃的,喜欢吃饱了晒太阳。

评论

发布
暂无评论
关于Kubernetes中如何访问集群外服务的一些笔记_12月月更_山河已无恙_InfoQ写作社区