2.2.6.2 LoadBalancer类型
通常在公有云环境中设置Service
的类型为 “LoadBalancer‘” ,可以将Service
映射到公有云提供的某个负载均衡器的IP
地址上,客户端通过负载均衡器的IP
和Service
的端口号就可以访问到具体的服务,无须再通过kube-proxy
提供的负载均衡机制进行流量转发。公有云提供的LoadBalancer可以直接将流量转发到后端Pod上,而负载分发机制依赖于公有云服务商的具体实现。
举例:
apiVersion: v1 kind: Service metadata: name: my-service spec: type: LoadBalancer selector: app: MyApp ports: - protocol: TCP port: 80 targetPort: 9376 clusterIP: 10.0.171.239
在服务创建成功之后,云服务商会在Service
的定义中补充LoadBalancer
的IP
地址(status字段):
status: loadBalancer: ingress: - ip:192.0.2.127
2.2.6.3 ExternalName类型
ExternalName类型的服务用于将集群外的服务定义为Kubernetes
的集群的Service
,并且通过externalName
字段指定外部服务的地址,可以使用域名或IP
格式,集群内的客户端应用通过访问这个Service
就能访问外部服务了。
这种类型的Service没有后端Pod,所以无须设置Label Selector。例如:
apiVersion: v1 kind: Service metadata: name: my-service namespace: prod spec: type: ExternalName externalName: my.database.example.com
在本例中设置的服务名为my-service,所在namespace为prod,客户端访问服务地址
my-service.prod.svc.cluster.local
时,系统将自动指向外部域名my.database.example.com
。
2.2.7 Service支持的网络协议
目前Service支持的网络协议如下.
类型 | 描述 |
TCP | Service的默认网络协议,可用于所有类型的Service |
UDP | 可用于大多数类型的Service,LoadBalancer类型取决于云服务商对UDP的支持 |
HTTP | 取决于云服务商是否支持HTTP和实现机制 |
PROXY | 取决于云服务商是否支持HTTP和实现机制 |
SCTP | 从Kubernetes1.12版本引入,到1.19版本时达到Beta阶段,默认启用,如需关闭该特性,则需要设置kube-apiserver的启动参数–feature- gates=-SCTPSupport=-false进行关闭 |
2.2.8 k8s的服务发现机制
2.2.8.1 环境变量的方式
在一个Pod运行起来的时候,系统会自动为其容器运行环境注入所有集群中有效Service的信息。
Service的相关信息包括服务IP、服务端口号、各端口号相关的协议等,通过
{SVCNAME_SERVICE_HOST}
和{SVCNAME_SERVICE_PORT}
格式进行设置。
以webapp服务为例:
apiVersion: v1 kind: Service metadata: name: webapp spec: ports: - protocol: TCP port: 8080 targetPort: 8080 selector: app: webapp
在一个新创建的Pod(客户端应用)中,可以看到系统自动设置的环境变量如下:
WEBAPP_SERVICE_HOST=169.169.81.175 WEBAPP_SERVICE_PORT=8080 WEBAPP_P0RT=tcp://169.169.81.175:8080 WEBAPP_P0RT_8080_TCP=tcp://169.169.81.175:8080 WEBAPP_PORT_8080_TCP_PROTO=tcp WEBAPP_PORT_8080_TCP_PORT=8080 WEBAPP_PORT_8080_TCP_ADDR=169.169.81.175
然后,客户端应用就能够根据Service
相关环境变量的命名规则,从环境变量中获取需要访问的目标服务的地址了,例如:
curl http://${WEBAPP_SERVICE_HOST}:${WEBAPP_SERVICE_HOST}
2.2.8.2 DNS的方式
Service在Kubernetes系统中遵循DNS命名规范,Service的DNS域名表示方法为<servicename>.<namespace>.svc.<clusterdomain>
,其中:
字段 | 解析 |
servicename | 服务的名称 |
namespace | 所在namespace的名称 |
clusterdomain | Kubernetes集群设置的域名后缀(例如cluster.local),服务名称的命名规则遵循RFC 1123规范的要求 |
以webapp
服务为例,将其端口号命名为“http
”:
apiversion: v1 kind: Service metadata: name: webapp spec: ports: - protocol: TCP port: 8080 targetPort: 8080 name: http selector: app: webapp
解析名为 “http” 端口的DNS SRV
记录
“_http._tcp.webapp.default.svc.cluster.local'
”,可以查询到其端口号的值为8080
。
2.2.9 headless service
在某些应用场景中,客户端应用不需要通过Kubernetes内置Service实现的负载均衡功能,需要自行完成对服务后端各实例的服务发现机制,或者需要自行实现负载均衡功能,此时可以通过创建一种特殊的名为 “Headless‘”的服务来实现。
服务名(
DNS
域名)的解析机制取决于该Headless Service
是否设置了Label Selector
。
2.2.9.1 已设置Label Selector
如果Headless Service设置了Label Selector,Kubernetes
则将根据Label Selector
查询后端Pod
列表,自动创建Endpoint
列表,将服务名(DNS
域名)的解析机制设置为:当客户端访问该服务名时,得到的是全部Endpoint
列表(而不是一个确定的IP
地址)。
以下面的Headless Service为例,其设置了Label Selector:
apiversion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 clusterIP: None selector: app: nginx
使用kubectl create
命令创建完之后,可以查看该Headless Service
的详细信息,可以看到后端的Endpoint
列表:
当客户端通过
DNS
服务名 “nginx"和服务端口号访问该Headless服务(URL=nginx:80)时,将得到Service后端Endpoint列表"10.0.95.12:80,10.0.9513:80,10.0.95.14:80
”,然后由客户端程序自行决定如何操作,例如:通过轮询机制访问各个Endpoint。
2.2.9.2 未设置Label Selector
如果headless Service没有设置Label Selector,则Kubernetes将不会自动创建对应的Endpoint列表。
DNS系统会根据下列条件尝试对该服务名设置DNS记录:
- 如果Service的类型为ExternalName,则对服务名的访问将直接被DNS系统转换为Service设置的外部名称(externalName);
- 如果系统中存在与Service同名的Endpoint定义,则服务名将被解析为Endpoint定义中的列表,适用于非ExternalName类型的Service。
2.2.10 端点分片和服务拓扑
2.2.10.1 端点分片
EndpointSlice通过对Endpoint进行分片管理来实现降低Master和各Node之间的网络传输数据量及提高整体性能的目标。对于Deployment的滚动升级,可以实现仅更新部分Node上的Endpoint信息,Master与Node之间的数据传输量可以减少100倍左右,能够大大提高管理效率。
EndpointSlice根据Endpoint
所在Node
的拓扑信息进行分片管理,如图所示:
Endpoint Slices
要实现的第2个目标是为基于Node拓扑的服务路由提供支持,这需要与服务拓扑(Service Topology)机制共同实现。
2.2.10.2 服务拓扑
在默认情况下,发送到一个Service的流量会被均匀转发到每个后端Endpoint,但无法根据更复杂的拓扑信息设置复杂的路由策略。服务拓扑机制的引入就是为了实现基于Node拓扑的服务路由,允许Service创建者根据来源Node和目标Node的标签来定义流量路由策略。
通过对来源Node和目标Node标签的匹配,用户可以根据业务需求对Node进行分组,设置有意义的指标值来标识 “较近” 或者 “较远” 的属性:
例如:对于公有云环境来说,通常有区域(Zone或Region)的划分,云平台倾向于把服务流量限制在同一个区域内,这通常是因为跨区域网络流量会收取额外的费用。另一个例子是把流量路由到由DaemonSet管理的当前Node的Pod 上。又如希望把流量保持在相同机架内的Node上,以获得更低的网络延时。
2.3 dns服务搭建和配置
2.3.1 dns在k8s服务的发展
发展:SkyDNS => KubeDNS =>CoreDNS
CoreDNS架构图如下:
描述:
- 它是由go语言实现的一套高性能、插件式,易于扩展的DNS服务端;
- 解决了KubeDNS的一些问题, 例如dnsmasq的安全漏洞、externalName不能使用stubDomains进行设置等等;
- 支持自定义DNS记录及配置upstream DNS Server,可以统一管理Kubernetes基于服务的内部DNS和数据中心的物理DNS;
- 它没有使用多个容器的架构,只用一个容器便实现了KubeDNS内3个容器的全部功能。
2.3.2 coredns搭建
① 修改每个Node上kubelet的启动参数,在其中加上以下两个参数:
--cluster-dns=169.169.0.100
:为DNS服务的ClusterIP地址。--cluster-domain=cluster.local
:为在DNS服务中设置的域名。
② 重启kubelet服务
③ 部署:需要创建3个资源对象:1个ConfigMap、1个Deployment和1个Service。
apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system labels: addonmanager.kubernetes.io/mode: EnsureExists data: Corefile: | cluster.local{ errors health{ lameduck 5s } ready kubernetes cluster.local 169.169.0.0/16{ fallthrough in-addr.arpa ip6.arpa } prometheus: 9153 forward ./etc/resolv.conf cache 30 loop reload loadbalance } . { cache 30 loadbalance forward /etc/resolv.conf } --- apiversion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/name: "CoreDNS" spec: replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns spec: priorityClassName: system-cluster-critical tolerations: - key: "CriticalAddonsonly" operator: "Exists" nodeSelector: kubernetes.io/os: linux affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: k8s-app operator: In values: ["kube-dns"] topologyKey: kubernetes.io/hostname containers: - name: coredns image: coredns/coredns:1.7.0 imagePullPolicy: IfNotPresent resources: limits: memory: 170Mi requests: cpu: 100m memory: 70Mi args: ["-conf","/etc/coredns/Corefile" ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - a11 readOnlyRootFilesystem: true livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/port: "9153" prometheus.io/scrape: "true" labels: k8s-app: kube-dns kubernetes.io/cluster-service: "true" kubernetes.io/name: "CoreDNS" spec: selector: k8s-app: kube-dns clusterIP: 169.169.0.100 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP - name: metrics port: 9153 protocol: TCP
使用kubectl create
命令依次把资源对象创建,然后可以看到创建成功:
2.3.3 配置解析
CoreDNS的主要功能是通过插件系统实现的,CoreDNS实现了一种链式插件结构,将DNS的逻辑抽象成了一个个插件,能够灵活组合使用。常用的插件如下:
插件 | 描述 |
loadbalance | 提供基于DNS的负载均衡功能 |
loop | 检测在DNS解析过程中出现的简单循环问题 |
cache | 提供前端缓存功能 |
health | 对Endpoint进行健康检查 |
kubernetes | 从Kubernetes中读取zone数据 |
etcd | 从etcd中读取zone数据,可用于自定义域名记录 |
file | 从RFC 1035格式文件中读取zone数据 |
hosts | 使用/etc/hosts文件或者其他文件读取zone数据,可用于自定义域名记录 |
auto | 从磁盘中自动加载区域文件 |
reload | 定时自动重新加载Corefile配置文件的内容 |
forward | 转发域名查询到上游DNS服务器上 |
prometheus | 为Prometheus系统提供采集性能指标数据的URL |
pprof | 在URL路径/debug/pprof下提供运行时的性能数据 |
log | 对DNS查询进行日志记录 |
errors | 对错误信息进行日志记录 |
2.3.4 举例(自定义域名)
etcd和hosts插件都可以用于用户自定义域名记录。
下面是使用etcd
插件的配置示例,将以“.com
”结尾的域名记录配置为从etcd中获取,并将域名记录保存在/skydns
路径下:
{ etcd com{ path /skydns endpoint http://192.168.18.3:2379 upstream /etc/resolv.conf } cache 160 com loadbalance proxy /etc/resolv.conf }
如果用户在etcd中插入一条“10.1.1.1 mycompany” DNS记录:
$ ETCDCTL_API=3 etcdctl put "/skydns/com/mycompany" '["host":"10.1.1.","ttl":60]'
客户端应用就能访问域名"mycompany.com"了:
$ nslookup mycompany.com Server: 169.169.0.100 Address: 169.169.0.100#53 Name: mycompany.com Address: 10.1.1.1
2.4 node本地dns缓存
Node本地DNS缓存(NodeLocal DNSCache)的工作流程如图所示,客户端Pod首先会通过本地DNS缓存进行域名解析,当缓存中不存在域名时,会将请求转发到集群DS服务进行解析。
部署Node本地DNS缓存工具,主要包括 ServiceAccount、Daemonset、ConfigMap和Service 几个资源对象,详情看原文。
2.5 pod的dns域名
对Pod来说,Kubernetes会为其设置一个<pod-ip>.<namespace>.pod.<cluster-domain>
格式的DNS域名,其中Pod IP部分需要用 “-
” 替换 “.
” 符号,例如下面Pod的IP地址为10.0.95.63
:
系统为这个Pod设置的DNS域名为10-0-95-63.default.pod.cluster.local
,用 nslookup
进行验证,便可以成功解析该域名的IP地址为10.0.95.63
:
其它与Service差不多,详情参考原文。
2.6 ingress 7层路由机制
Kubernetes 使用了一个Ingress策略定义和一个具体提供转发服务的Ingress Controller,两者结合,实现了基于灵活Ingress策略定义的服务路由功能。
如果是对Kubernetes集群外部的客户端提供服务,那么Ingress Controller实现的是类似于边缘路由器(Edge Router)的功能。
需要注意的是,Ingress
只能以HTTP
和 HTTPS
提供服务,对于使用其他网络协议的服务,可以通过设置Service
的类型 type
为NodePort
或LoadBalancer
对集群外部的客户端提供服务。
举例:使用Ingress进行服务路由时,Ingress Controller基于Ingress规则将客户端请求直接转发到Service对应的后端Endpoint(Pod)上,这样会跳过kube-proxy设置的路由转发规则,以提高网络转发效率,下图是一个典型的HTTP层路由的例子:
其中:
- 对http:/mywebsite.com/api的访问将被路由到后端名为api的Service上;
- 对http:/mywebsite.com/web的访问将被路由到后端名为web的Service上;
- 对http:/mywebsite.com/docs的访问将被路由到后端名为docs的Service上。
主要流程:部署ingress controller -> 创建ingress策略 -> 客户端通过ingress controller访问后端web-app服务。
关系图大致如下:(图片来自:https://www.cnblogs.com/rancherlabs/p/12034075.html)
03 疑问
如果还是对service的一些概念不清晰,比如:kube-proxy与ingress的关系,可以参考阅读:《Using a Network Load Balancer (NLB) to Expose an Application Outside the Kubernetes Cluster》。
暴露k8s的服务的三种方式:
- NodePort
- Network Load Balancer (NLB)
- Ingress (application load balancer, ALB)
原理图如下,以便于理解:
04 文末
本文主要总结k8s关于Service的一些学习笔记,以便后续的回顾,谢谢大家的阅读,本文完!