Service 对外暴露与应用
Service
Kubernetes Service定义了这样一种抽象:逻辑上的一组 Pod,一种能够访问它们的策略 —— 一般被称为微服务。这一组 Pod 可以被 Service 访问到,一般是经过 selector实现的。
举例:考虑一个图片处理 backend,它运行了3个副本。这些副本是可互换的 —— frontend 不需要关心它们调用了哪一个 backend 副本。 然而组成这一组 backend 程序的 Pod 实际上可能会发生变化,frontend 客户端不必知道,并且也不需要跟踪这一组 backend 的状态。Service 定义的抽象可以解耦这种关联。
Service能够提供负载均衡的能力,可是使用上存在以下限制:
- 只能提供4层负载均衡能力,而没有7层功能。有时咱们可能须要更多的匹配规则来转发请求,这点上4层负载均衡是不支持的、
如web访问的service服务示例图:
VIP和Service代理
在 Kubernetes 集群中,每一个 Node 运行一个 kube-proxy 进程。kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。后端
从Kubernetes v1.0开始,已经可使用 userspace代理模式。Kubernetes v1.1添加了 iptables 代理模式,在 Kubernetes v1.2 中kube-proxy 的 iptables 模式成为默认设置。Kubernetes v1.8添加了 ipvs 代理模式。
为何不使用 DNS 轮询?
缘由以下:
-
DNS 实现的历史由来已久,它不遵照记录 TTL,而且在名称查找到结果后会对其进行缓存。
-
有些应用程序仅执行一次 DNS 查找,并没有限期地缓存结果。
-
即便应用和库进行了适当的从新解析,DNS 记录上的 TTL 值低或为零也可能会给 DNS 带来高负载,从而使管理变得困难。
-
由于有缓存,所以不合适。
userspace代理模式
这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每一个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何链接到“代理端口”的请求,都会被代理到 Service 的backend Pods 中的某个上面(如 Endpoints 所报告的同样)。 使用哪一个 backend Pod,是 kube-proxy 基于 SessionAffinity 来肯定的。网络
最后,它配置 iptables 规则,捕获到达该 Service 的 clusterIP(是虚拟 IP)和 Port 的请求,并重定向到代理端口,代理端口再代理请求到 backend Pod。
默认状况下,userspace模式下的kube-proxy经过循环算法选择后端。
默认的策略是,经过 round-robin 算法来选择 backend Pod。
iptables 代理模式
这种模式,kube-proxy 会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。 对每一个 Service,它会配置 iptables 规则,从而捕获到达该 Service 的 clusterIP 和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。对于每一个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个 backend 组合。
默认的策略是,kube-proxy 在 iptables 模式下随机选择一个 backend。
使用 iptables 处理流量具备较低的系统开销,由于流量由 Linux netfilter 处理,而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
若是 kube-proxy 在 iptables模式下运行,而且所选的第一个 Pod 没有响应,则链接失败。 这与userspace模式不一样:在这种状况下,kube-proxy 将检测到与第一个 Pod 的链接已失败,并会自动使用其余后端 Pod 重试。
咱们可使用 Pod readiness 探测器 验证后端 Pod 是否能够正常工做,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。这样作意味着能够避免将流量经过 kube-proxy 发送到已知已失败的Pod。
代理模式
在 ipvs 模式下,kube-proxy监视Kubernetes服务(Service)和端点(Endpoints),调用 netlink 接口相应地建立 IPVS 规则, 并按期将 IPVS 规则与 Kubernetes服务(Service)和端点(Endpoints)同步。该控制循环可确保 IPVS 状态与所需状态匹配。访问服务(Service)时,IPVS 将流量定向到后端Pod之一。
IPVS代理模式基于相似于 iptables 模式的 netfilter 挂钩函数,可是使用哈希表做为基础数据结构,而且在内核空间中工做。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通讯的延迟要短,而且在同步代理规则时具备更好的性能。与其余代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS提供了更多选项来平衡后端Pod的流量。分别是:
- rr: round-robin
- lc: least connection (smallest number of open connections)
- dh: destination hashing
- sh: source hashing
- sed: shortest expected delay
- nq: never queue
注意:要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 以前使 IPVS Linux 在节点上可用。 当 kube-proxy 以 IPVS 代理模式启动时,它将验证 IPVS 内核模块是否可用。 若是未检测到 IPVS 内核模块,则 kube-proxy 将退回到以 iptables 代理模式运行。
Service服务类型
Kubernetes 中Service有如下4中类型:
ClusterIP:默认类型,自动分配一个仅Cluster内部能够访问的虚拟IP
-
NodePort:经过每一个 Node 上的 IP 和静态端口(NodePort)暴露服务。以ClusterIP为基础,NodePort 服务会路由到 ClusterIP 服务。经过请求 :,能够从集群的外部访问一个集群内部的 NodePort 服务。
-
LoadBalancer:使用云提供商的负载均衡器,能够向外部暴露服务。外部的负载均衡器能够路由到 NodePort 服务和 ClusterIP 服务。
-
ExternalName:经过返回 CNAME 和它的值,能够将服务映射到 externalName 字段的内容(例如,foo.bar.example.com)。没有任何类型代理被建立。
须要注意的是:Service 可以将一个接收 port 映射到任意的 targetPort。默认状况下,targetPort 将被设置为与 port 字段相同的值。
Service域名格式: ( s e r v i c e n a m e ) . (service name). (servicename).(namespace).svc.cluster.local,其中 cluster.local 为指定的集群的域名
ClusterIP:
默认类型,自动分配一个仅Cluster内部能够访问的虚拟IP
[root@k8s-master ~]# vi network.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: v1
template:
metadata:
labels:
app: myapp
release: v1
spec:
containers:
- name: myapp
image: xxkk/httpd:v1.0
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
namespace: default
spec:
type: ClusterIP
selector:
app: myapp
release: v1
ports:
- name: httpd
port: 80
targetPort: 80
[root@k8s-master ~]# kubectl apply -f network.yaml
deployment.apps/myapp-deploy created
service/myapp-nodeport created
[root@k8s-master ~]# kubectl get pods,svc
NAME READY STATUS RESTARTS AGE
pod/myapp-deploy-6c95b4d844-ns54r 1/1 Running 0 76s
pod/myapp-deploy-6c95b4d844-p8lh2 1/1 Running 0 76s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d21h
service/myapp-nodeport ClusterIP 10.101.27.9 <none> 80/TCP 77s
[root@k8s-master ~]# iptables -t nat -nvL | grep 'myapp-nodeport'
0 0 KUBE-MARK-MASQ tcp -- * * !10.244.0.0/16 10.101.27.9 /* default/myapp-nodeport:httpd cluster IP */ tcp dpt:80
0 0 KUBE-SVC-UJB7KAYNZVW7NK6H tcp -- * * 0.0.0.0/0 10.101.27.9 /* default/myapp-nodeport:httpd cluster IP */ tcp dpt:80
0 0 KUBE-SEP-J4MHLFV6KB4EYBDQ all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-QT46DDMNERPRMJZS all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */
0 0 KUBE-MARK-MASQ all -- * * 10.244.1.52 0.0.0.0/0 /* default/myapp-nodeport:httpd */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ tcp to:10.244.1.52:80
0 0 KUBE-MARK-MASQ all -- * * 10.244.1.53 0.0.0.0/0 /* default/myapp-nodeport:httpd */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ tcp to:10.244.1.53:80
[root@k8s-master ~]# curl 10.101.27.9
This is V1!
NodePort
nodePort的原理在于在node上开了一个端口,将向该端口的流量导入到kube-proxy,然后由 kube-proxy进一步到给对应的pod
[root@k8s-master ~]# vi network.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: v1
template:
metadata:
labels:
app: myapp
release: v1
spec:
containers:
- name: myapp
image: xxkk/httpd:v1.0
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
namespace: default
spec:
type: NodePort # 指定NodePort类型
selector:
app: myapp
release: v1
ports:
- name: httpd
port: 80
targetPort: 80
nodePort: 31250 # 指定对外端口
[root@k8s-master ~]# kubectl apply -f network.yaml
deployment.apps/myapp-deploy unchanged
service/myapp-nodeport configured
[root@k8s-master ~]# kubectl get pods,svc
NAME READY STATUS RESTARTS AGE
pod/myapp-deploy-6c95b4d844-ns54r 1/1 Running 0 5m24s
pod/myapp-deploy-6c95b4d844-p8lh2 1/1 Running 0 5m24s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d21h
service/myapp-nodeport NodePort 10.101.27.9 <none> 80:31250/TCP 5m25s
[root@k8s-master ~]# iptables -t nat -nvL | grep 'myapp-nodeport'
0 0 KUBE-MARK-MASQ tcp -- * * !10.244.0.0/16 10.101.27.9 /* default/myapp-nodeport:httpd cluster IP */ tcp dpt:80
0 0 KUBE-SVC-UJB7KAYNZVW7NK6H tcp -- * * 0.0.0.0/0 10.101.27.9 /* default/myapp-nodeport:httpd cluster IP */ tcp dpt:80
0 0 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ tcp dpt:31250
0 0 KUBE-SVC-UJB7KAYNZVW7NK6H tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ tcp dpt:31250
0 0 KUBE-SEP-J4MHLFV6KB4EYBDQ all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-QT46DDMNERPRMJZS all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */
0 0 KUBE-MARK-MASQ all -- * * 10.244.1.52 0.0.0.0/0 /* default/myapp-nodeport:httpd */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ tcp to:10.244.1.52:80
0 0 KUBE-MARK-MASQ all -- * * 10.244.1.53 0.0.0.0/0 /* default/myapp-nodeport:httpd */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/myapp-nodeport:httpd */ tcp to:10.244.1.53:80
[root@k8s-master ~]# curl 192.168.72.142:31250
This is V1!
ExternalName类型示例
这种类型的Service经过返回CNAME和它的值,能够将服务映射到externalName字段的内容(例如:my.k8s.example.com;能够实现跨namespace名称空间访问)。ExternalName Service是Service的特例,它没有selector,也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它经过返回该外部服务的别名这种方式提供服务。
[root@k8s-master ~]# vi network.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: v1
template:
metadata:
labels:
app: myapp
release: v1
spec:
containers:
- name: myapp
image: xxkk/httpd:v0.2 #此处将镜像,更换为基于contos编译的httpd镜像
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: myapp-nodeport
namespace: default
spec:
type: ExternalName #指定类型
externalName: www.k8s.example.com
[root@k8s-master ~]# kubectl apply -f network.yaml
deployment.apps/myapp-deploy configured
service/myapp-nodeport configured
[root@k8s-master ~]# kubectl get pods,svcNAME READY STATUS RESTARTS AGE
pod/myapp-deploy-6c95b4d844-ns54r 1/1 Running 0 10m
pod/myapp-deploy-6c95b4d844-p8lh2 1/1 Running 0 10m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d21h
service/myapp-nodeport ExternalName <none> www.k8s.example.com <none> 10m
# 此处Type,改为 ExternalName模式了
宿主机dig命令安装
root@k8s-master ~]# kubectl exec -it myapp-deploy-5bc8b6987d-2bz5q /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
sh-4.4# ls
apr-1.7.0 apr-util-1.6.1 debug httpd-2.4.48 kernels
sh-4.4# yum -y install bind-utils
CentOS Linux 8 - AppStream 1.0 kB/s | 4.3 kB 00:04
CentOS Linux 0% [ ] 2.1 kB/s | 0 B 00:0
coredns记录信息如下
sh-4.4# nslookup myapp-externalname.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
** server can't find myapp-externalname.default.svc.cluster.local: NXDOMAIN
# 其中 10.96.0.10 为 coredns IP
# myapp-externalname.default.svc.cluster.local 为Service域名。格式为:$(service name).$(namespace).svc.cluster.local,其中 cluster.local 指定的集群的域名
sh-4.4# dig -t A myapp-externalname.default.svc.cluster.local.@10.96.0.10
; <<>> DiG 9.11.26-RedHat-9.11.26-6.el8 <<>> -t A myapp-externalname.default.svc.cluster.local.@10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 18284
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 9eb0b6eb7260ab5b (echoed)
;; QUESTION SECTION:
;myapp-externalname.default.svc.cluster.local.\@10.96.0.10. IN A
;; Query time: 1014 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Dec 26 01:29:47 UTC 2021
;; MSG SIZE rcvd: 97
ExternalIP示例
若是外部的 IP 路由到集群中一个或多个 Node 上,Kubernetes Service 会被暴露给这些 externalIPs。经过外部 IP(做为目的 IP 地址)进入到集群,打到 Service 端口上的流量,将会被路由到 Service 的 Endpoint 上。
externalIPs 不会被 Kubernetes 管理,它属于集群管理员的职责范畴。
根据 Service 的规定,externalIPs 能够同任意的 ServiceType 来一块儿指定。在下面的例子中,my-service 能够在【模拟外网IP】“10.0.0.240”
[root@k8s-master ~]# vi network.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: myapp
release: v1
template:
metadata:
labels:
app: myapp
release: v1
spec:
containers:
- name: httpd
image: xxkk/httpd:v0.2
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: myapp-externalip
namespace: default
spec:
selector:
app: myapp
release: v1
ports:
- name: httpd
port: 80
targetPort: 80
externalIPs:
- 10.0.0.240
[root@k8s-master ~]# kubectl apply -f network.yaml
deployment.apps/myapp-deploy configured
service/myapp-externalip created
[root@k8s-master ~]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d21h <none>
myapp-externalip ClusterIP 10.106.57.50 10.0.0.240 80/TCP 58s app=myapp,release=v1
myapp-nodeport ExternalName <none> www.k8s.example.com <none> 19m <none>
[root@k8s-master ~]# curl 10.106.57.50
<html><body><h1>It works!</h1></body></html>
[root@k8s-master ~]# curl 10.0.0.240
<html><body><h1>It works!</h1></body></html>