Kubernetes是一个广受欢迎的容器编排系统,它可以帮助开发人员自动化部署、扩展和管理容器化应用程序。在Kubernetes中,服务是一种重要的概念,它提供了一种方式来暴露应用程序并使其可访问。在本文中,我们将深入了解Kubernetes支持的不同服务类型,并讨论每种类型的优缺点以及何时使用它们。
一、Service类型
Service的资源清单文件:
kind: Service # 资源类型
apiVersion: v1 # 资源版本
metadata: # 元数据
name: service # 资源名称
namespace: dev # 命名空间
spec: # 描述
selector: # 标签选择器,用于确定当前service代理哪些pod
app: nginx
type: # Service类型,指定service的访问方式
clusterIP: # 虚拟服务的ip地址
sessionAffinity: # session亲和性,支持ClientIP、None两个选项
ports: # 端口信息
- protocol: TCP
port: 3017 # service端口
targetPort: 5003 # pod端口
nodePort: 31122 # 主机端口
- ClusterIP:默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问
- NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务。(在每个Node上分配一个端口作为外部访问入口)
- LoadBalancer:工作在特定的Cloud Provider上,例如Google Cloud,AWS,OpenStack
- ExternalName: 集群外部的服务引入到集群内部直接使用
二、ClusterIP
ClusterIP是Kubernetes默认的服务类型。当您创建一个Service资源对象而没有指定服务类型时,默认会被设置为ClusterIP类型。这种类型的服务仅在Kubernetes集群内部可见,并通过ClusterIP暴露应用程序。ClusterIP只能通过内部DNS进行访问,因此它适合处理内部流量。
ClusterIP根据是否生成ClusterIP又可分为普通Service和Headless Service
Service两类:
- 普通Service:
为Kubernetes的Service分配一个集群内部可访问的固定虚拟IP(Cluster IP), 实现集群内的访问。
- Headless Service:
该服务不会分配Cluster IP, 也不通过kube-proxy做反向代理和负载均衡。而是通过DNS提供稳定的网络ID来访问,DNS会将headless service的后端直接解析为pod IP列表。
2.1 普通 Service使用案例
2.1.0 创建Deployment类型的应用
[root@master01 ~]# cat 01_create_deployment_app_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-server1
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: c1
image: nginx:1.15-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
- 应用资源清单文件
[root@master01 ~]# kubectl apply -f 01_create_deployment_app_nginx.yaml
- 验证Deployment类型的创建情况
[root@master01 ~]# kubectl get deployment.apps
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-server1 2/2 2 2 13s
2.1.1 命令方式创建ClusterIP类型service与Deployment类型应用关联
命令创建service
[root@master01 ~]# kubectl expose deployment.apps nginx-server1 --type=ClusterIP --target-port=80 --port=80
输出
service/nginx-server1 exposed
说明
expose 创建service
deployment.apps 控制器类型
nginx-server1 应用名称,也是service名称
--type=ClusterIP 指定service类型
--target-port=80 指定Pod中容器端口
--port=80 指定service端口
2.1.2 通过资源清单文件创建Service
[root@master01 ~]# cat 02_create_deployment_app_nginx_with_service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-server1
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-smart
image: nginx:1.15-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 80
selector:
app: nginx
[root@master01 ~]# kubectl apply -f 02_create_deployment_app_nginx_with_service.yaml
- 验证
查看service
[root@master01 ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4d15h
nginx-svc ClusterIP 10.101.153.50 <none> 80/TCP 3s
查看endpoints
[root@master01 ~]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.122.30:6443 4d15h
nginx-svc 172.16.189.74:80,172.16.235.150:80 8s
查看Pod
[root@master01 ~]# kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-server1-77d4c485d8-gsrmq 1/1 Running 0 12s
nginx-server1-77d4c485d8-mmc52 1/1 Running 0 12s
- 访问
[root@master01 ~]# curl http://10.101.153.50:80
<title>Welcome to nginx!</title>
2.1.3 两个pod里做成不同的主页方便测试负载均衡
[root@master01 ~]# kubectl exec -it nginx-server1-77d4c485d8-gsrmq -- /bin/bash
root@deployment-nginx-6fcfb67547-nv7dn:/# cd /usr/share/nginx/html/
root@deployment-nginx-6fcfb67547-nv7dn:/usr/share/nginx/html# echo web1 > index.html
root@deployment-nginx-6fcfb67547-nv7dn:/usr/share/nginx/html# exit
exit
[root@master01 ~]# kubectl exec -it nginx-server1-77d4c485d8-mmc52 -- /bin/bash
root@deployment-nginx-6fcfb67547-rqrcw:/# cd /usr/share/nginx/html/
root@deployment-nginx-6fcfb67547-rqrcw:/usr/share/nginx/html# echo web2 > index.html
root@deployment-nginx-6fcfb67547-rqrcw:/usr/share/nginx/html# exit
exit
[root@master01 ~]# curl 10.101.153.50
或
[root@master01 ~]# while true;do curl 10.101.153.50;sleep 1; done
2.2 Headless Service
当创建 Kubernetes Service 时,可以指定 Service 的类型。其中一种类型是 ClusterIP,该类型会为 Service 创建一个虚拟 IP(VIP),并将此 VIP 分配给 Service。然后,通过这个 VIP 可以访问到 Service 中运行的 Pod。
除了 ClusterIP 类型,还有一种类型是 Headless Service,也叫做无头服务。与 ClusterIP 不同的是,Headless Service 不会为 Service 创建 VIP,而是直接将 DNS 解析返回到 Service 下的所有 Pod。
- 普通的ClusterIP service是service name解析为cluster ip,然后cluster ip对应到后面的pod ip
- Headless service是指service name 直接解析为后面的pod ip
使用 Headless Service 可以实现以下几个优点:
- 直接连接 Pod:由于没有 VIP,因此在使用 Headless Service 时,客户端可以直接连接到 Service 下的所有 Pod。
- 动态扩展:使用 Headless Service 可以更轻松地动态扩展 Pod,因为不需要重新分配 VIP。
- 简化负载均衡器:当使用 Headless Service 时,负载均衡器不再需要管理 VIP,这可以简化部署和维护。
下面是一个 Headless Service 的示例 YAML 文件:
apiVersion: v1
kind: Service
metadata:
name: headless-service
spec:
clusterIP: None
selector:
app: my-app
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
在上面的示例中,我们通过 clusterIP: None
明确指定了创建 Headless Service。selector 字段定义了应该选择哪些 Pod 来提供服务,(pod文件此处没有举例)。ports 则定义了 Service 所使用的端口和目标端口。
使用 Headless Service 后,可以通过以下方式连接到 Service 下的所有 Pod:
$ dig(curl) headless-service.default.svc.cluster.local
在上面的命令中,我们使用 dig
命令查询了 Service 的 DNS 记录,它将返回所有 Pod 的 IP 地址列表。 (命名方式 = serviceName+nameSpace+.svc.cluster.local)
2.2.0 编写用于创建Deployment控制器类型的资源清单文件
[root@master01 ~]# cat 03_create_deployment_app_nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-server1
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-smart
image: nginx:1.15-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
2.2.1 通过资源清单文件创建headless Service
编写YAML文件命令
[root@master ~]# vim 04_headless-service.yml
apiVersion: v1
kind: Service
metadata:
name: headless-service
namespace: default
spec:
type: ClusterIP # ClusterIP类型,也是默认类型
clusterIP: None # None就代表是无头service
ports: # 指定service 端口及容器端口
- port: 80 # service ip中的端口
protocol: TCP
targetPort: 80 # pod中的端口
selector: # 指定后端pod标签
app: nginx # 可通过kubectl get pod -l app=nginx查看哪些pod在使用此标签
2.2.2 应用资源清单文件创建headless Service
命令
[root@master ~]# kubectl apply -f 04_headless_service.yml
输出
service/headless-service created
2.2.3 查看已创建的headless Service
命令
[root@master ~]# kubectl get svc
输出
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless-service ClusterIP None <none> 80/TCP 2m18s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 5d9h
可以看到headless-service没有CLUSTER-IP,用None表示
2.2.4 DNS
DNS服务监视Kubernetes API,为每一个Service创建DNS记录用于域名解析
headless service需要DNS来解决访问问题
DNS记录格式为: ..svc.cluster.local.
2.2.4.1 查看kube-dns服务的IP
命令
[root@master1 ~]# kubectl get svc -n kube-system
输出
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.2 <none> 53/UDP,53/TCP,9153/TCP 5d9h
metrics-server ClusterIP 10.105.219.44 <none> 443/TCP 45h
查看到coreDNS的服务地址是 10.96.0.2
2.2.4.2 在集群主机通过DNS服务地址查找无头服务的dns解析
命令
[root@master01 ~]# dig -t A headless-service.default.svc.cluster.local. @10.96.0.2
输出
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-16.P2.el7_8.2 <<>> -t A headless-service.default.svc.cluster.local. @10.96.0.2
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31371
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;headless-service.default.svc.cluster.local. IN A #被解析域名
;; ANSWER SECTION:
headless-service.default.svc.cluster.local. 30 IN A 10.224.235.147 #注意这里IP
;; Query time: 0 msec
;; SERVER: 10.96.0.10#53(10.96.0.2)
;; WHEN: Sun May 17 10:58:50 CST 2020
;; MSG SIZE rcvd: 129
2.2.4.3 验证pod的IP
命令
[root@master ~]# kubectl get pod -o wide
输出
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-56bf6c9c8c-jmk7r 1/1 Running 0 35m 10.224.235.147 worker1 <none> <none>
2.2.4.4 在集群中创建一个pod验证
创建一个镜像为busyboxplus:curl的pod,pod名称为bb2,用来解析域名
命令
[root@master01 ~]# kubectl run bbp --image=busyboxplus:curl -it
或
[root@master01 ~]# kubectl run bbp --image=1.28 -it
输出
If you don't see a command prompt, try pressing enter.
解析域名
nslookup headless-service.default.svc.cluster.local.
访问命令
[ root@bbp:/ ]$ curl http://headless-service.default.svc.cluster.local.
输出
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
</html>
[ root@bbp:/ ]$ exit
Session ended, resume using 'kubectl attach bbp -c bbp -i -t' command when the pod is running
三、NodePort
如果您想在集群外部访问服务,则可以使用NodePort类型。NodePort服务类型映射到每个节点的静态端口,并且可以通过该端口向服务发送请求。
例如,假设您的NodePort服务正在使用TCP端口30000,并且有三个节点:node1、node2和node3。要访问该服务,可以使用任何一个节点的IP地址,并将端口设置为30000。请求会被路由到正在运行该服务的Pod。
NodePort有一些限制,例如您需要手动打开防火墙来允许外部流量进入集群,并且端口范围是30000到32767之间。此外,如果您更改了分配的端口号,则可能需要更新所有使用该服务的客户端。
- 创建资源清单文件
[root@master01 ~]# cat 05_create_nodeport_service_app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
labels:
app: nginx-app
spec:
replicas: 2
selector:
matchLabels:
app: nginx-app
template:
metadata:
labels:
app: nginx-app
spec:
containers:
- name: c1
image: nginx:1.15-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-app
spec:
type: NodePort
selector:
app: nginx-app
ports:
- protocol: TCP
nodePort: 30001
port: 8060
targetPort: 80
- 应用资源清单文件
[root@master01 ~]# kubectl apply -f 05_create_nodeport_service_app.yaml
deployment.apps/nginx-app created
service/nginx-app created
- 验证service创建
[root@master01 ~]# kubectl get deployment.apps
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-app 2/2 2 2 26s
[root@master01 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d22h
nginx-app NodePort 10.104.157.20 <none> 8060:30001/TCP 36s
[root@master01 ~]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.122.10:6443 2d22h
nginx-app 172.16.1.24:80,172.16.2.20:80 2m10s
[root@master01 ~]# ss -anput | grep ":30001"
tcp LISTEN 0 128 :::30001 :::* users:(("kube-proxy",pid=5826,fd=9))
[root@worker01 ~]# ss -anput | grep ":30001"
tcp LISTEN 0 128 :::30001 :::* users:(("kube-proxy",pid=4937,fd=11))
[root@worker02 ~]# ss -anput | grep ":30001"
tcp LISTEN 0 128 :::30001 :::* users:(("kube-proxy",pid=5253,fd=11))
[root@master01 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-app-ffd5ccc78-cnwbx 1/1 Running 0 8m59s
nginx-app-ffd5ccc78-mz77g 1/1 Running 0 8m59s
[root@master01 ~]# kubectl exec -it nginx-app-ffd5ccc78-cnwbx -- bash
root@nginx-app-ffd5ccc78-cnwbx:/# echo "nginx-app-1" > /usr/share/nginx/html/index.html
root@nginx-app-ffd5ccc78-cnwbx:/# exit
exit
[root@master01 ~]# kubectl exec -it nginx-app-ffd5ccc78-mz77g -- bash
root@nginx-app-ffd5ccc78-mz77g:/# echo "nginx-app-2" > /usr/share/nginx/html/index.html
root@nginx-app-ffd5ccc78-mz77g:/# exit
exit
- 在与kubernetes 节点同一网络主机中访问k8s集群内service
[root@bogon ~]# curl http://192.168.10.12:30001
nginx-app-2
[root@bogon ~]# curl http://192.168.10.13:30001
nginx-app-1
[root@bogon ~]# curl http://192.168.10.14:30001
nginx-app-1
[root@bogon ~]# curl http://192.168.10.15:30001
nginx-app-2
四、LoadBalancer
(工作在特定的Cloud Provider上,例如Google Cloud,AWS,OpenStack)
LoadBalancer服务类型是一种Kubernetes扩展,它可以通过云提供商支持的负载均衡器来暴露服务。这种类型的服务适用于处理公共流量,并且需要在外部进行访问。
当您创建一个LoadBalancer服务时,Kubernetes会自动向云提供商发送请求以创建一个负载均衡器,并将其配置为将流量路由到您的服务。负载均衡器可能需要时间来启动,并且可能会产生额外的费用。
Kubernetes 中的 LoadBalancer Service 类型是一种用于暴露应用程序服务到公共互联网上的方式。当您创建一个 LoadBalancer Service 时,Kubernetes 会自动为该服务创建一个负载均衡器,并将流量路由到后端 Pod 上。这使得您可以使用 Kubernetes 内部的服务名称来访问外部服务,并且可以实现自动化负载均衡和高可靠性。
下面是一个 LoadBalancer Service 的例子:
yamlCopy Code
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer-service
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 8080
selector:
app: my-app
这个 Service 对象定义了一个名为 my-loadbalancer-service
的 LoadBalancer Service,该服务映射到后端 Pod 的标签为 app=my-app
的所有 Pod 上,并将流量路由到目标端口 8080
上。当您创建这个 Service 时,Kubernetes 会自动为该服务创建一个负载均衡器,并分配一个公共 IP 地址,以便您可以从公共互联网上访问该服务。您可以使用 kubectl get services
命令查看 LoadBalancer Service 的 IP 地址和端口号。
好的,下面是一个完整的 LoadBalancer Service 的例子:
- 创建一个 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
selector:
matchLabels:
app: my-app
replicas: 2
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app-container
image: nginx:latest
ports:
- containerPort: 80
这个 Deployment 对象定义了一个名为 my-app
的 Deployment,它使用 Nginx 镜像创建了两个 Pod,并将标签 app=my-app
分配给这些 Pod。
- 创建一个 LoadBalancer Service:
apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer-service
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 80
selector:
app: my-app
这个 Service 对象定义了一个名为 my-loadbalancer-service
的 LoadBalancer Service,该服务映射到后端 Pod 的标签为 app=my-app
的所有 Pod 上,并将流量路由到目标端口 80
上。当您创建这个 Service 时,Kubernetes 会自动为该服务创建一个负载均衡器,并分配一个公共 IP 地址。
- 查看 Service 的状态:
使用 kubectl get svc
命令查看 LoadBalancer Service 的状态,例如:
$ kubectl get svc my-loadbalancer-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-loadbalancer-service LoadBalancer 10.100.200.100 203.0.113.10 80:30123/TCP 1m
注意到 EXTERNAL-IP
列中分配了一个公共 IP 地址。
LoadBalancer 类型的 Service 在 Kubernetes 集群中会为后端 Pod 分配一个虚拟 IP 地址,这个虚拟 IP 地址就是 CLUSTER-IP
列所列出的 IP 地址。该 IP 地址只能在集群内部使用,用于内部微服务之间的通信。
同时,LoadBalancer 类型的 Service 还可以将 Service 暴露到 Internet 上,并使用云提供商的负载均衡器(例如 AWS ELB、Google Cloud Load Balancer 或 Azure Load Balancer)将流量路由到后端 Pod 上进行处理。这个公共 IP 地址就是 EXTERNAL-IP
列所列出的 IP 地址。但是,在负载均衡器还没有分配公共 IP 地址之前,EXTERNAL-IP
的值将显示为 <pending>
。
因此,对于 LoadBalancer 类型的 Service,CLUSTER-IP
用于在 Kubernetes 集群内部进行通信,而 EXTERNAL-IP
用于将 Service 暴露给外部访问者
- 请求 Service:
假设您的 Kubernetes 集群部署在 example.com
域名下,您可以通过 http://<EXTERNAL-IP>
访问该服务。例如,如果 EXTERNAL-IP
分配为 203.0.113.10
,则可以使用 http://203.0.113.10
访问该服务。Kubernetes 的负载均衡器将会自动将请求路由到后端 Pod 上进行处理,从而实现自动化负载均衡和高可用性。
五、ExternalName
(集群外部的服务引入到集群内部直接使用)
Kubernetes Service 的 externalName 类型是一种特殊的服务类型,它通常用于将 Kubernetes 集群内部的服务映射到集群外部的服务。当您创建一个 externalName Service 时,Kubernetes 不会为该服务创建任何负载均衡器或代理,而是仅仅将该服务的名称映射到指定的 DNS 名称上。这使得您可以使用 Kubernetes 内部的服务名称来访问外部服务,同时也允许您在不修改应用程序代码的情况下更改服务的后端地址。
例如,如果您有一个MySQL数据库托管在AWS RDS中,并且希望从Kubernetes集群中的应用程序访问该数据库,则可以创建一个ExternalName服务并指向数据库的DNS别名。此外,ExternalName服务类型不具有选择器,因为它不暴露任何Pod。
下面是一个 externalName Service 的例子:
apiVersion: v1
kind: Service
metadata:
name: my-external-service
spec:
type: ExternalName
externalName: my.external.service.com
这个 Service 对象定义了一个名为 my-external-service
的 externalName Service,该服务映射到 my.external.service.com
这个 DNS 名称上。当 Pod 访问 my-external-service
时,Kubernetes 将会把请求发送到 my.external.service.com
上。
六、Endpoints
Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址,它是根据service配置文件中selector描述产生的。
一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换句话说,service和pod之间的联系是通过endpoints实现的。
负载分发策略
对Service的访问被分发到了后端的Pod上去,目前kubernetes提供了两种负载分发策略:
-
如果不定义,默认使用kube-proxy的策略,比如随机、轮询
-
基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上
此模式可以使在spec中添加
sessionAffinity:ClientIP
选项
总结:在本文中,我们探讨了Kubernetes支持的四种不同服务类型:ClusterIP、NodePort、LoadBalancer和ExternalName。每种类型都适用于不同的用例和需求,因此需要根据您的应用程序需求仔细选择服务类型。通过正确使用服务类型,您可以更好地管理和扩展容器化应用程序,并提高性能和可靠性。