Kubernetes——服务发现方式
服务发现方式
微服务意味着存在更多的独立服务,但它们并非独立的个体,而是存在着复杂的依赖关系且彼此之间通常需要进行非常频繁地交互和通信的群体。然而,建立通信之前,服务和服务之间该如何获知彼此的地址呢?
在 Kubernetes 系统上,Service 为 Pod 中的服务类应用提供了一个稳定的入口,但 Pod 客户端中的应用如何得知某个特定 Service 资源的 IP 和端口呢? 这个时候就需要引入服务发现(Service Discovery)的机制。
一、服务发现概述
服务发现简单的来说,就是服务或者应用之间的互相定位的过程。
不过服务发现并发新概念,传统的单体应用架构时代也会用到,只不过单体应用的动态性不强,更新和重新发布的频度较低,通常以月甚至以年计,基本上不会进行自动伸缩,因此服务发现的概念无须显性强调。在传统的单体应用网络位置发生变化时,由IT运维手动更新一下相关的配置文件基本上就能解决问题。
但在微服务应用场景中,应用被拆分成众多的小服务,它们按需创建且变动频繁,配置信息基本无法事先写入配置文件中并及时跟踪和反映动态变化,因此服务发现的重要性便随之突显。
根据服务发现过程的实现方式,服务发现可以分为两种类型:
-
- 客户端发现:由客户端到服务注册中心发现其依赖到的服务的相关信息,因此,它需要内置特定的服务发现程序和发现逻辑。
- 服务端发现:这种方式需要额外用到一个称为中央路由器或服务均衡器的组件;服务消费者将请求发往中央路由器或者负载均衡器,由它们负载查询服务注册中心获取服务提供者的位置信息,并将服务消费者的请求转发给服务提供者。
可见,服务注册中心是服务发现的一落地的核心组件。
事实上,DNS 可以算是最为原始的服务发现系统之一,不过在服务的动态性很强的业务场景中,DNS记录的传播速度可能会跟不上服务的变更速度,因此它并不适用于微服务环境。微服务中常见的服务注册中心,比如 zookeeper、eureka、nacos 和 etcd 等分布式键值存储系统。
尽管传统 DNS 系统不适于微服务环境中的服务发现,但 SkyDNS 项目(后来成为 kubedns)却是一个有趣的实现,它结合了古老的 DNS 技术和时髦的 Go 语言、Raft 算法,并构建于 etcd 存储系统之上,为 Kubernetes 系统实现了一种服务发现机制。Servie 资源为 Kubernetes 提供了一个较为稳定的抽象层,这点类似于服务端发现的方式,于是也就不存在 DNS 服务的时间窗口的问题了。
Kubernetes 自 1.3 版本开始,其用于服务发现的 DNS 更新为了 kubeDNS,而类似的另一个基于较新的 DNS 服务发现项目是 CNCF(Cloud Native Computing Foundaton)孵化的 CoreDNS,它基于 Go 语言开发,通过串接一组实现 DNS 功能的插件的插件链进行工作。自 Kubernetes 1.11 版本起,CoreDNS 取代 kubeDNS 成为默认的 DNS 福建。不过 Kubernetes 依然支持使用环境变量进行服务发现。
二、服务发现方式一:环境变量
创建 Pod 资源时,kubelet 会将其所属名称空间的每个活动的 Service 对象以一系列环境变量的形式注入其中。它支持使用 Kubernetes Service 环境变量以及与 Docker 的 links 兼容的变量。
(1)Kubernetes Servcie 环境变量
Kubernetes 为每个 Service 资源生成包括以下形式的环境变量在内的一系列环境变量,在同一名称空间中创建的 Pod 对象都会自动拥有这些变量。
-
- {SVCNAME}_SERVICE_HOST
- {SVCNAME}_SERVICE_PORT
(2)Docker Link 形式的环境变量
Docker 使用 --link 选项实现容器连接时所设置的环境变量形式,具体使用方式请参考 Docker 官网文档。
基于环境变量的服务发现其功能简单、易用,但存在一定的局限,例如,仅有那些与创建的 Pod 对象在同一名称空间中且事先存在的 Service 对象的信息才会以环境变量的形式注入,那些非同一名称空间,或者是在 Pod 资源创建之后才创建的 Service 对象的相关环境变量则不会添加。基于 DNS 的发现机制并不存在此类限制。
三、ClusterDNS 和服务发现
Kubernetes 系统之上用于名称解析和服务发现的 ClusterDNS 是集群的核心附件之一,集群中创建的每个 Service 对象,都会由其自动生成相关的资源记录。默认情况下,集群内各 Pod 资源会自动配置其作为名称解析器,并在其 DNS 搜索列表中包含它所属名称空间的域名后缀。
(1)创建一个coredns 服务:
[root@mh-k8s-master-prd-243-24 ~]# kubectl get deployment/coredns -n kube-system -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2022-04-11T09:49:54Z"
generation: 1
labels:
k8s-app: kube-dns
managedFields:
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:labels:
.: {}
f:k8s-app: {}
f:spec:
f:progressDeadlineSeconds: {}
f:replicas: {}
f:revisionHistoryLimit: {}
f:selector:
f:matchLabels:
.: {}
f:k8s-app: {}
f:strategy:
f:rollingUpdate:
.: {}
f:maxSurge: {}
f:maxUnavailable: {}
f:type: {}
f:template:
f:metadata:
f:labels:
.: {}
f:k8s-app: {}
f:spec:
f:containers:
k:{"name":"coredns"}:
.: {}
f:args: {}
f:image: {}
f:imagePullPolicy: {}
f:livenessProbe:
.: {}
f:failureThreshold: {}
f:httpGet:
.: {}
f:path: {}
f:port: {}
f:scheme: {}
f:initialDelaySeconds: {}
f:periodSeconds: {}
f:successThreshold: {}
f:timeoutSeconds: {}
f:name: {}
f:ports:
.: {}
k:{"containerPort":53,"protocol":"TCP"}:
.: {}
f:containerPort: {}
f:name: {}
f:protocol: {}
k:{"containerPort":53,"protocol":"UDP"}:
.: {}
f:containerPort: {}
f:name: {}
f:protocol: {}
k:{"containerPort":9153,"protocol":"TCP"}:
.: {}
f:containerPort: {}
f:name: {}
f:protocol: {}
f:readinessProbe:
.: {}
f:failureThreshold: {}
f:httpGet:
.: {}
f:path: {}
f:port: {}
f:scheme: {}
f:periodSeconds: {}
f:successThreshold: {}
f:timeoutSeconds: {}
f:resources:
.: {}
f:limits:
.: {}
f:memory: {}
f:requests:
.: {}
f:cpu: {}
f:memory: {}
f:securityContext:
.: {}
f:allowPrivilegeEscalation: {}
f:capabilities:
.: {}
f:add: {}
f:drop: {}
f:readOnlyRootFilesystem: {}
f:terminationMessagePath: {}
f:terminationMessagePolicy: {}
f:volumeMounts:
.: {}
k:{"mountPath":"/etc/coredns"}:
.: {}
f:mountPath: {}
f:name: {}
f:readOnly: {}
f:dnsPolicy: {}
f:nodeSelector:
.: {}
f:kubernetes.io/os: {}
f:priorityClassName: {}
f:restartPolicy: {}
f:schedulerName: {}
f:securityContext: {}
f:serviceAccount: {}
f:serviceAccountName: {}
f:terminationGracePeriodSeconds: {}
f:tolerations: {}
f:volumes:
.: {}
k:{"name":"config-volume"}:
.: {}
f:configMap:
.: {}
f:defaultMode: {}
f:items: {}
f:name: {}
f:name: {}
manager: kubeadm
operation: Update
time: "2022-04-11T09:49:54Z"
- apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:deployment.kubernetes.io/revision: {}
f:status:
f:availableReplicas: {}
f:conditions:
.: {}
k:{"type":"Available"}:
.: {}
f:lastTransitionTime: {}
f:lastUpdateTime: {}
f:message: {}
f:reason: {}
f:status: {}
f:type: {}
k:{"type":"Progressing"}:
.: {}
f:lastTransitionTime: {}
f:lastUpdateTime: {}
f:message: {}
f:reason: {}
f:status: {}
f:type: {}
f:observedGeneration: {}
f:readyReplicas: {}
f:replicas: {}
f:updatedReplicas: {}
manager: kube-controller-manager
operation: Update
time: "2022-05-23T22:13:18Z"
name: coredns
namespace: kube-system
resourceVersion: "44798610"
selfLink: /apis/apps/v1/namespaces/kube-system/deployments/coredns
uid: c65ae3e2-c843-43fd-8ac2-9b1c14d5c9a8
spec:
progressDeadlineSeconds: 600
replicas: 2
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-app: kube-dns
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
k8s-app: kube-dns
spec:
containers:
- args:
- -conf
- /etc/coredns/Corefile
image: registry.cn-beijing.aliyuncs.com/kubesphereio/coredns:1.6.9
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 5
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 5
name: coredns
ports:
- containerPort: 53
name: dns
protocol: UDP
- containerPort: 53
name: dns-tcp
protocol: TCP
- containerPort: 9153
name: metrics
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /ready
port: 8181
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
resources:
limits:
memory: 170Mi
requests:
cpu: 100m
memory: 70Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_BIND_SERVICE
drop:
- all
readOnlyRootFilesystem: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /etc/coredns
name: config-volume
readOnly: true
dnsPolicy: Default
nodeSelector:
kubernetes.io/os: linux
priorityClassName: system-cluster-critical
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
serviceAccount: coredns
serviceAccountName: coredns
terminationGracePeriodSeconds: 30
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- effect: NoSchedule
key: node-role.kubernetes.io/master
volumes:
- configMap:
defaultMode: 420
items:
- key: Corefile
path: Corefile
name: coredns
name: config-volume
status:
availableReplicas: 2
conditions:
- lastTransitionTime: "2022-04-11T09:50:43Z"
lastUpdateTime: "2022-04-11T09:50:43Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: "2022-04-11T09:50:10Z"
lastUpdateTime: "2022-04-11T09:50:48Z"
message: ReplicaSet "coredns-674d655c65" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
observedGeneration: 1
readyReplicas: 2
replicas: 2
updatedReplicas: 2
cm配置:
[root@mh-k8s-master-prd-243-24 ~]# kubectl describe cm/coredns -n kube-system
Name: coredns
Namespace: kube-system
Labels: <none>
Annotations: <none>
Data
====
Corefile:
----
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
Events: <none>
[root@mh-k8s-master-prd-243-24 ~]#
ServiceAccount 配置:
[root@mh-k8s-master-prd-243-24 ~]# kubectl describe ServiceAccount/coredns -n kube-system
Name: coredns
Namespace: kube-system
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: coredns-token-s8sqx
Tokens: coredns-token-s8sqx
Events: <none>
[root@mh-k8s-master-prd-243-24 ~]# kubectl get ServiceAccount/coredns -n kube-system
NAME SECRETS AGE
coredns 1 70d
[root@mh-k8s-master-prd-243-24 ~]#
rbac 配置:
[root@mh-k8s-master-prd-243-24 ~]# kubectl describe ClusterRole/system:coredns -n kube-system
Name: system:coredns
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
nodes [] [] [get]
endpoints [] [] [list watch]
namespaces [] [] [list watch]
pods [] [] [list watch]
services [] [] [list watch]
[root@mh-k8s-master-prd-243-24 ~]# kubectl describe ClusterRoleBinding/system:coredns -n kube-system
Name: system:coredns
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: system:coredns
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount coredns kube-system
[root@mh-k8s-master-prd-243-24 ~]#
svc 配置:
[root@mh-k8s-master-prd-243-24 ~]# kubectl get svc/coredns -n kube-system -o yaml
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{"prometheus.io/port":"9153","prometheus.io/scrape":"true"},"labels":{"addonmanager.kubernetes.io/mode":"Reconcile","k8s-app":"kube-dns","kubernetes.io/cluster-service":"true","kubernetes.io/name":"coredns"},"name":"coredns","namespace":"kube-system"},"spec":{"clusterIP":"10.233.0.3","ports":[{"name":"dns","port":53,"protocol":"UDP"},{"name":"dns-tcp","port":53,"protocol":"TCP"},{"name":"metrics","port":9153,"protocol":"TCP"}],"selector":{"k8s-app":"kube-dns"}}}
prometheus.io/port: "9153"
prometheus.io/scrape: "true"
creationTimestamp: "2022-04-11T09:50:05Z"
labels:
addonmanager.kubernetes.io/mode: Reconcile
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name: coredns
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
f:prometheus.io/port: {}
f:prometheus.io/scrape: {}
f:labels:
.: {}
f:addonmanager.kubernetes.io/mode: {}
f:k8s-app: {}
f:kubernetes.io/cluster-service: {}
f:kubernetes.io/name: {}
f:spec:
f:clusterIP: {}
f:ports:
.: {}
k:{"port":53,"protocol":"TCP"}:
.: {}
f:name: {}
f:port: {}
f:protocol: {}
f:targetPort: {}
k:{"port":53,"protocol":"UDP"}:
.: {}
f:name: {}
f:port: {}
f:protocol: {}
f:targetPort: {}
k:{"port":9153,"protocol":"TCP"}:
.: {}
f:name: {}
f:port: {}
f:protocol: {}
f:targetPort: {}
f:selector:
.: {}
f:k8s-app: {}
f:sessionAffinity: {}
f:type: {}
manager: kubectl
operation: Update
time: "2022-04-11T09:50:05Z"
name: coredns
namespace: kube-system
resourceVersion: "323"
selfLink: /api/v1/namespaces/kube-system/services/coredns
uid: 739362ef-f448-4ccc-a569-d4c6280d9ebe
spec:
clusterIP: 10.233.0.3
ports:
- name: dns
port: 53
protocol: UDP
targetPort: 53
- name: dns-tcp
port: 53
protocol: TCP
targetPort: 53
- name: metrics
port: 9153
protocol: TCP
targetPort: 9153
selector:
k8s-app: kube-dns
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
[root@mh-k8s-master-prd-243-24 ~]#
(2)apply后结果运行
[root@mh-k8s-master-prd-243-24 ~]# kubectl get deployment/coredns -n kube-system
NAME READY UP-TO-DATE AVAILABLE AGE
coredns 2/2 2 2 70d
[root@mh-k8s-master-prd-243-24 ~]# kubectl get pods -n kube-system |grep coredns
coredns-674d655c65-5scd4 1/1 Running 1 70d
coredns-674d655c65-wdgll 1/1 Running 1 70d
[root@mh-k8s-master-prd-243-24 ~]#
(3)测试 CoreDNS:
[root@mh-k8s-master-prd-243-24 ~]# kubectl get svc -o wide -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
coredns ClusterIP 10.233.0.3 <none> 53/UDP,53/TCP,9153/TCP 70d k8s-app=kube-dns
etcd ClusterIP None <none> 2379/TCP 69d <none>
kube-controller-manager-svc ClusterIP None <none> 10257/TCP 69d component=kube-controller-manager
kube-scheduler-svc ClusterIP None <none> 10259/TCP 69d component=kube-scheduler
kubelet ClusterIP None <none> 10250/TCP,10255/TCP,4194/TCP 69d <none>
metrics-server ClusterIP 10.233.47.168 <none> 443/TCP 69d k8s-app=metrics-server
[root@mh-k8s-master-prd-243-24 ~]# dig -t A www.baidu.com @10.233.0.3 +short
www.a.shifen.com.
14.215.177.38
14.215.177.39
[root@mh-k8s-master-prd-243-24 ~]#
(4)配置解析
CoreDNS 提供基于 DNS 的服务发现解决方案会负责解析以下资源记录(Resource Record)类型以实现服务发现。
(1)拥有ClusterIP的Service资源,需要具有以下类型的资源记录
A记录:<service>.<ns>.svc.<zone>.<ttl> IN a <cluster-ip>
SRV记录:_<port>._<proto>.<service>.<ns>.svc.<zone>.<ttl> IN SRV <weight><priority><port-number><serivce>.<ns>.svc.<zone>
PTR记录:<d>.<c>.<b>.<a>.in-addr-arpa.<ttl> IN PTR <service>.<ns>.svc.<zone>
(2)Headless类型的Service资源,需要具有以下类型的资源记录。
A记录:<service>.<ns>.svc.<zone>.<ttl> IN a <endpoint-ip>
SRV记录:_<port>._<proto>.<service>.<ns>.svc.<zone>.<ttl> IN SRV <weight><priority><port-number><hostname>.<serivce>.<ns>.svc.<zone>
PTR记录:<d>.<c>.<b>.<a>.in-addr-arpa.<ttl> IN PTR <hostname>.<service>.<ns>.svc.<zone>
(3)ExternalName类型的service资源,需要具有CNAME类型的资源记录
CNAME记录:<serivce>.<ns>.svc.<zone>.<ttl> IN CNAME <extname>
名称解析和服务发现是 Kubernetes 系统许多功能得以实现的基础服务,它通常是集群安装完成后应该立即部署的附加组件。使用 kubeadm 初始化一个集群时,它甚至会自动进行部署。
四、服务发现方式:DNS
创建 Service 资源对象时,ClusterDNS 会为它自动创建资源记录用于名称解析和服务注册,于是,Pod 资源科直接使用标准的 DNS 名称来访问这些 Service 资源。每个 Service 对象相关的 DNS 记录包括如下两个:
- {SVCNAME}.{NAMESPACE}.{CLUSTER_DOMAIN}
- {SVCNAME}.{NAMESPACE}.svc.{CLUSTER_DOMAIN}
另外,在部署 Kubernetes 时,"--cluster-dns" 指定了集群 DNS 服务的工作地址,"--cluster-domain" 定义了集群使用的本地域名,因此,系统初始化时默认会将 "cluster.local." 和 主机所在的域 "xxx.com“ 作为 DNS 的本地域使用,这些信息会在 Pod 创建时以 DNS 配置的相关信息注入它的 /etc/resolv.conf 配置文件中。
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local xxx.com
上述 search 参数中指定的 DNS 各搜索域,是以次序指定的几个域名后缀,具体如下所示:
-
- {NAMESPACE}.svc{CLUSTER_DOMAIN}: 如 default.svc.cluster.lcoal。
- svc.{CLUSTER_DOMAIN}: 如 svc.cluster.local。
- {CLUSTER_DOMAIN}: 如 cluster.local。
- {WORK_NODE_DOMAIN}: xxx.com