是故不应取法,不应取非法。以是义故,如来常说:汝等比丘,知我说法,如筏喻者,法尚应舍何况非法。 ———-《金刚经》
写在前面
分享一些 k8s 中服务如何访问集群外服务的笔记
博文内容涉及:
如何访问集群外服务
创建外部服务代理 SVC
(IP+PORT情况)
Endponts/EndpointSlice
实现 Demo
外部服务为 单体/集群
的访问 Demo
创建 ExternalName
类型 SVC
(域名的情况)
理解不足小伙伴帮忙指正
是故不应取法,不应取非法。以是义故,如来常说:汝等比丘,知我说法,如筏喻者,法尚应舍何况非法。 ———-《金刚经》
如何访问集群外服务 在 K8s 中,考虑某些稳定性问题,希望把数据库部署到 物理机或者虚机上,或许系统正在一点点迁移到 K8s 平台,某些服务在非 k8s 集群部署,或者上游系统是别人的,和我们没有直接关系。那么我们如何实现 K8s 集群上的服务访问 这些外部服务。
外部服务是IP端口的方式 在 K8s 中,我们可以定义一个没有 lable Selector
的 Service
来代替 非当前集群的服务。通过 IP 端口映射的方式把外部服务映射到内部集群中。
这样可以正常接入外部服务的同时,添加了一个类似外部服务的代理服务。之后如果外部服务发生 IP 端口变更,只需要修改映射关系即可,不需要修改应用相关的配置。同时对访问他的pod 隐藏了实际的IP端口,以后如果服务移入集群内,则不需要更改任何代码。
外部服务是域名的方式 当 外部服务提供的方式是域名的时候,我们可以创建一个 Service 类型为 ExternalName
的SVC,同样没有lable Selector
, 类型为 ExternalName
的服务将外部服务域名映射到集群内部服务的 DNS 名称,而不是对应的 Pod 。
创建外部服务代理服务 适用于外部服务为 IP:Port的方式,定义一个没有 选择器的 Service
,对应这样的 Service
,k8s
不会自动创建对应的 Endpoint
、 EndpointSlice
,其他的和正常的 Service 没有区别,所以我们需要提供 Service 对应的 endpoint
或者是 EndpointSlice
来对外部服务做映射。类似与通过 iptables
做了 DNAT
的映射,实现 IP 端口转发。
资源文件的定义
1 2 3 4 5 6 7 8 9 10 11 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$cat not-service.yaml apiVersion: v1 kind: Serviceca metadata: name: external-service spec: ports: - protocol: TCP port: 30056 targetPort: 3306
定义了一个普通的 Service ,没有选择器,在 Service 内部做了转发,暴露的端口为 30056
转发到端口 3306
, 这里的 3306
为代理的外部服务的端口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl describe svc external-service Name: external-service Namespace: awx Labels: <none> Annotations: <none> Selector: <none> Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.103.93.20 IPs: 10.103.93.20 Port: <unset > 30056/TCP TargetPort: 3306/TCP Endpoints: <none> Session Affinity: None Events: <none>
可以看到当前的 Service
类型为 ClusterIP
, 对应的集群 IP 为 :10.103.93.20
,Endpoints
为 None。
对于这样的 Service 系统不会自动创建 Endpoint
和 EndpointSlice
,因此需要手动创建一个和该 Service 同名的Endpoint
或者带序号的 EndpointSlice
对象 ,用于指向实际的 后端访问地址
。
1.21 版本之前的只能通过创建 Endponits
的方式,创建 Endpoint 的配置文件内容如下:
Endponits 方式 如果外部服务为单体服务,那么我们只定义一个 IP就可以
1 2 3 4 5 6 7 8 9 10 11 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$cat external-service.yaml kind: Endpoints apiVersion: v1 metadata: name: external-service subsets: - addresses: - ip: 192.168 .26 .81 ports: - port: 3306
这里定义 集群外的服务 IP 为 192.168.26.81
,端口为 3306
, 这个 endpoint 即表示集群外的服务,生产环境中,我们需要打通相关的网络。
1 2 3 4 5 6 7 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl apply -f external-service.yaml endpoints/external-service created ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl get endpoints external-service NAME ENDPOINTS AGE external-service 192.168.26.81:3306 57s
在集群外通过 python 模块发布一个 简单的 http 服务,暴露端口 3306
,做简单测试。
1 2 3 4 5 6 7 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$coproc python -m SimpleHTTPServer 3306 [2] 109525 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl get svc external-service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE external-service ClusterIP 10.103.93.20 <none> 30056/TCP 26m
通过访问集群服务,实现对集群外部服务的访问
1 2 3 4 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$curl 10.103.93.20:30056 -s -w "%{http_code}\n" -o /dev/null 192.168.26.81 - - [10/Dec/2022 03:26:30] "GET / HTTP/1.1" 200 - 200
EndpointSlice 方式 对于 1.21 版本及之后的版本来讲,我们可以通过 EndpointSlice
来实现,资源文件的定义,这里如果外部服务为集群,可以定义多个 IP 地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$cat external-service-1.yaml apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: external-service-1 labels: kubernetes.io/service-name: external-service addressType: IPv4 ports: - name: '' appProtocol: http protocol: TCP port: 3306 endpoints: - addresses: - "192.168.26.82" - addresses: - "192.168.26.81"
这里我们提供了两个 ip ,来模拟外部服务集群的情况。
1 2 3 4 5 6 7 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl apply -f external-service-1.yaml endpointslice.discovery.k8s.io/external-service-1 created ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl get endpointslices.discovery.k8s.io external-service-1 NAME ADDRESSTYPE PORTS ENDPOINTS AGE external-service-1 IPv4 3306 192.168.26.81,192.168.26.82 20s
在集群外 81,82 两台机器通过 python 模块发布一个 简单的 http 服务,暴露端口 3306
,做简单测试。
1 2 3 4 5 ┌──[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 上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ┌──[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;done 192.168.26.81 - - [10/Dec/2022 03:56:52] "GET / HTTP/1.1" 200 - 200 200 200 192.168.26.81 - - [10/Dec/2022 03:56:58] "GET / HTTP/1.1" 200 - 200 200 192.168.26.81 - - [10/Dec/2022 03:57:02] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 03:57:04] "GET / HTTP/1.1" 200 - 200 200 192.168.26.81 - - [10/Dec/2022 03:57:08] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 03:57:10] "GET / HTTP/1.1" 200 - 200
这里我们修改一下,修改为会话保持 sessionAffinity: ClientIP
1 2 3 4 5 6 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl edit svc external-service service/external-service edited ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl get svc external-service -o json | jq .spec.sessionAffinity "ClientIP"
可以看到当前 访问只到 81 上面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ┌──[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;done 192.168.26.81 - - [10/Dec/2022 04:00:56] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 04:00:58] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 04:01:00] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 04:01:02] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 04:01:04] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 04:01:06] "GET / HTTP/1.1" 200 - 200 192.168.26.81 - - [10/Dec/2022 04:01:08] "GET / HTTP/1.1" 200 - 200
DNS 解析测试,可以看到 对于没有选择器的服务来讲,同样可以通过 服务名对应的域名来解析到对应的 集群 IP 地址,这与 有选择器的相同。
1 2 3 4 5 6 7 8 9 10 11 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl run pod-test -it --rm --image=yauritux/busybox-curl --image-pull-policy=IfNotPresent If you don't see a command prompt, try pressing enter. /home # nslookup external-service.awx.svc.cluster.local. Server: 10.96.0.10 Address 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 running pod "pod-test" deleted
域名的方式:ExternalName 这里假设 集群外的服务为 我的 个人主页 https://liruilongs.github.io/
创建一个 ExternalName
类型的 SVC,当然也可以设置端口,这里我们不需要。
1 2 3 4 5 6 7 8 9 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$cat external-service.yaml apiVersion: v1 kind: Service metadata: name: external-service spec: type: ExternalName externalName: liruilongs.github.io
查看详细信息,CLUSTER-IP
为none
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl apply -f external-service.yaml service/external-service created ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE external-service ExternalName <none> liruilongs.github.io <none> 5s ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl describe svc external-service Name: external-service Namespace: liruilong-deploy-create Labels: <none> Annotations: <none> Selector: <none> Type: ExternalName IP Families: <none> IP: IPs: <none> External Name: liruilongs.github.io Session Affinity: None Events: <none>
解析域名测试,可以发现,external-service
经过 k8s 的内部 DNS 记录为 liruilongs.github.io
,解析获得的 ipv4和ipv6 完全相同。所以pod 可以通过域名连接到外部服务,而不是使用服务的实际 FQDN
。
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 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl run pod-test -it --rm --image=yauritux/busybox-curl --image-pull-policy=IfNotPresent If you don'' t see a command prompt, try pressing enter. /home Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: external-service Address 1: 2606:50c0:8001::153 Address 2: 2606:50c0:8003::153 Address 3: 2606:50c0:8000::153 Address 4: 2606:50c0:8002::153 Address 5: 185.199.111.153 cdn-185-199-111-153.github.com Address 6: 185.199.109.153 cdn-185-199-109-153.github.com Address 7: 185.199.110.153 cdn-185-199-110-153.github.com Address 8: 185.199.108.153 cdn-185-199-108-153.github.com /home Server: 10.96.0.10 Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local Name: liruilongs.github.io Address 1: 2606:50c0:8003::153 Address 2: 2606:50c0:8001::153 Address 3: 2606:50c0:8000::153 Address 4: 2606:50c0:8002::153 Address 5: 185.199.111.153 cdn-185-199-111-153.github.com Address 6: 185.199.110.153 cdn-185-199-110-153.github.com Address 7: 185.199.108.153 cdn-185-199-108-153.github.com Address 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 实战》