在 Minikube 中直接上手学习 K8S Service
K8S 为什么需要 Service?
- Pod 的 IP 不是固定的;
- 一组 Pod 实例之间有负载均衡的需求;
上机操作
以下使用 minikube
作实例:
# 安装
brew install minikube
# 启动
minikube start
# 创建 ~/.kube/config 请注意先备份
minikube update-context
# 进入宿主机
minikube ssh
集群启动后,创建 Deployment 和 Service
apiVersion: v1
kind: Service
metadata:
namespace: default
name: nginx-svc
spec:
type: NodePort
selector:
app: nginx
ports:
- name: default
protocol: TCP
port: 80
targetPort: 80
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: default
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
protocol: TCP
这个 Service 使用了 selector 字段来声明这个 Service 只代理携带了 app=nginx 标签的 Pod。并且,这个 Service 的 80 端口,代理的是 Pod 的 8080 端口。
> kubectl get endpoints nginx-svc
NAME ENDPOINTS AGE
nginx-svc 10.244.0.13:80,10.244.0.14:80,10.244.0.15:80 4m29s
只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。并且,当某一个 Pod 出现问题时,Kubernetes 会自动把它从 Service 里摘除掉。
> kubectl get svc nginx-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc NodePort 10.110.83.228 <none> 80:32597/TCP 4m37s
这个ip是 k8s 自动为 Service 分配的。Service 提供的是 Round Robin 方式的负载均衡。可以使用以下方式进行测试
> minikube ip
192.168.49.2
> minikube ssh
curl http://192.168.49.2:32597
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Service 工作原理
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。
- 凡是目的地址是 10.110.83.228、目的端口是 80 的 IP 包,都应该跳转到另外一条名叫 KUBE-SVC-KBEH26U7YV6VBLSX 的 iptables 链进行处理。使用 iptables-save 查看
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-svc:default" -m tcp --dport 32597 -j KUBE-EXT-KBEH26U7YV6VBLSX
-A KUBE-SERVICES -d 10.110.83.228/32 -p tcp -m comment --comment "default/nginx-svc:default cluster IP" -m tcp --dport 80 -j KUBE-SVC-KBEH26U7YV6VBLSX
- KUBE-SVC-KBEH26U7YV6VBLSX 是一组规则,这三个目的地就是Service 代理的 3 个pod。
-A KUBE-SVC-KBEH26U7YV6VBLSX -m comment --comment "default/nginx-svc:default -> 10.244.0.13:80" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-E36YYYCTO5EK2IJ4
-A KUBE-SVC-KBEH26U7YV6VBLSX -m comment --comment "default/nginx-svc:default -> 10.244.0.14:80" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-INUGID7JOCIXUMGA
-A KUBE-SVC-KBEH26U7YV6VBLSX -m comment --comment "default/nginx-svc:default -> 10.244.0.15:80" -j KUBE-SEP-H22OXDXNEK3GBZK4
iptables 规则的匹配是从上到下逐条进行的,所以为了保证上述三条规则每条被选中的概率都相同,我们应该将它们的 probability 字段的值分别设置为 1/3(0.333…)、1/2 和 1。这种设计思路,在我们日常开发中,也是值的借鉴的。Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的。缺点:宿主机有大量Pod时,大量 iptables 规则不断被刷新,占用该宿主机 CPU 资源。
IPVS 模式
IPVS 模式的工作原理,其实跟 iptables 模式类似。当我们创建了前面的 Service 之后,kube-proxy 首先会在宿主机上创建一个虚拟网卡(叫作:kube-ipvs0),并为它分配 Service VIP 作为 IP 地址按如文档方法开启 ipvs。Minikube 操作开启 IPVS。
# 安装 ipvsadm 工具
sudo apt-get update -y
sudo apt-get install -y ipvsadm
准备就绪后,重新创建 nginx-svc。kube-proxy 就会通过 Linux 的 IPVS 模块,为这个 IP 地址设置三个 IPVS 虚拟主机,并设置这三个虚拟主机之间使用轮询模式 (rr) 来作为负载均衡策略。
kubectl get svc nginx-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc NodePort 10.97.127.252 <none> 80:31379/TCP 3m3s
ipvsadm -ln
TCP 10.97.127.252:80 rr
-> 10.244.0.13:80 Masq 1 0 0
-> 10.244.0.14:80 Masq 1 0 0
-> 10.244.0.15:80 Masq 1 0 0
相比于 iptables,IPVS 在内核中的实现其实也是基于 Netfilter 的 NAT 模式,所以在转发这一层上,理论上 IPVS 并没有显著的性能提升。但是,IPVS 并不需要在宿主机上为每个 Pod 设置 iptables 规则,而是把对这些“规则”的处理放到了内核态,从而极大地降低了维护这些规则的代价。“将重要操作放入内核态”是提高性能的重要手段。不过需要注意的是,IPVS 模块只负责上述的负载均衡和代理功能。而一个完整的 Service 流程正常工作所需要的包过滤、SNAT 等操作,还是要靠 iptables 来实现。只不过,这些辅助性的 iptables 规则数量有限,也不会随着 Pod 数量的增加而增加。所以,在大规模集群里,我非常建议你为 kube-proxy 设置–proxy-mode=ipvs 来开启这个功能。它为 Kubernetes 集群规模带来的提升,还是非常巨大的。
Service 与 DNS
在 Kubernetes 中,Service 和 Pod 都会被分配对应的 DNS A 记录(从域名解析 IP 的记录)。再次创建了 ClusterIP 模式 service(type: ClusterIP) 和 Headless Service(.spec.clusterIP: None)
> kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc NodePort 10.97.127.252 <none> 80:31379/TCP 11h
nginx-svc-cluster-ip ClusterIP 10.106.66.77 <none> 80/TCP 23m
nginx-svc-headless ClusterIP None <none> 80/TCP 19m
对于 CLUSTER-IP 有值的,不管是 NodePort 还是 ClusterIP 类型,Service 的 A记录格式都是: ..svc.cluster.local
而对于指定了 clusterIP=None 的 Headless Service 来说,它的 A 记录的格式也是:..svc.cluster.local。但是,当你访问这条 A 记录的时候,它返回的是所有被代理的 Pod 的 IP 地址的集合。我们登上一个 pod 看一看,首先需要安装 nslookup
kubectl exec nginx-7c5ddbdf54-gg5ql -it /bin/bash
# 安装 nslookup
apt update && apt install -y dnsutils
> nslookup nginx-svc
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-svc.default.svc.cluster.local
Address: 10.97.127.252
> nslookup nginx-svc-cluster-ip
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-svc-cluster-ip.default.svc.cluster.local
Address: 10.106.66.77
> nslookup nginx-svc-headless
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx-svc-headless.default.svc.cluster.local
Address: 10.244.0.13
Name: nginx-svc-headless.default.svc.cluster.local
Address: 10.244.0.14
Name: nginx-svc-headless.default.svc.cluster.local
Address: 10.244.0.15
附录 Service 类型
在 Kubernetes 中,Service
类型用于将一组 Pod 暴露为网络服务。Service
类型有四种:
- ClusterIP: 这是默认的
Service
类型。ClusterIP 服务具有内部 IP 地址,只能由集群内部的其他 Pod 访问。 - NodePort: NodePort 将服务暴露在每个节点的外部 IP 地址上,并使用指定的端口。任何人都可以使用该端口从集群外部访问服务。
- LoadBalancer: LoadBalancer 服务使用云提供商的负载均衡器将服务暴露在互联网上。
- ExternalName: ExternalName 服务指向外部 DNS 名称。
ClusterIP 和 NodePort 的主要区别在于 可访问性:
- ClusterIP: 仅限于集群内部访问,外部无法访问。适合内部服务或测试环境。
- NodePort: 可以从集群外部通过 Node 的 IP 地址和端口访问。适合需要从外部访问的服务,例如 Web 服务或 API。
默认值:默认情况下,Kubernetes 服务的类型为 ClusterIP。这意味着如果您不指定 service.spec.type
字段,则将创建 ClusterIP 服务。