一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 22 天,点击查看活动详情。
一、 ClusterIp Service
ClusterIp Service
可分为两种:普通 Service
和 Headless Service
.
默认创建的都是普通
Service
ClusterIP Service
:只能用于集群内部,不能提供外部的访问。NodePort Service
:可以让集群中每个Node
都开放一个固定端口,客户端通过连接任意一个NodeIP + NodePort
即可访问集群内部的服务。
ClusterIP
简单理解如图:
(1)Normal Service
Service
完整的 Yaml
配置文件格式如下:
apiVersion: v1
kind: Service
metadata: # 元数据
name: string
namespace: string # 如果不填写的话,默认为 default
labels: # 自定义标签属性列表
- name: string
annotations: # 自定义注解属性列表
- name: string
spec: # 详细描述
selector: [] # 标签选择器
type: string # 可选有:ClusterIP(默认)/NodePort/LoadBalancer(外接负载均衡器时选择这个)
clusterIP: string # 不指定的话系统自动分配 IP,当 type=LoadBalancer 时必须指定
sessionAffinity: string # 是否支持 Session,默认值为空,可选值为 ClientIP,ClientIP 表示将同一个客户端的访问请求都转发到同一个后端 Pod
ports: # 需要暴露的端口列表
- name: string # 端口名称
protocols: string # 端口协议,支持 TCP 和 UDP,默认值为 TCP
port: int # 服务监听的端口号
targetPort: int # 需要转发到后端 Pod 的端口号
nodePort: int # 指定映射到 Node 节点的端口号
status: # 当 spec.type=LoadBalancer 时,设置外部负载均衡器的地址,用于公有云环境
loadBalancer:
ingress: # 外部负载均衡器
ip: string # 外部负载均衡器的 IP 地址
hostname: string # 外部负载均衡器的主机名
服务负载分发策略 & 多端口服务 & 端口命名
在 Kubernetes
集群中有两种负载均衡分发策略,也可以称之为服务的会话亲和性,如下所示:
RoundRobin
:轮询模式,这是默认配置。即定义YAML
文件时不设置spec.sessionAffinity
或设置spec.sessionAffinity=None
。它表示的是将请求分发到后端各个Pod
上,没有固定某个Pod
对请求进行响应。SessionAffinity
:会话保持模式,需要在定义YAML
文件时设置spec.sessionAffinity=ClientIP
。当某个客户端第一次请求转发到后端的某个Pod
上,那么之后这个客户端的请求也一直由相同的Pod
进行响应。
多端口服务:
有的时候
Pod
会开放多个端口,这样的情况下Service
也可以开放多个端口与Pod
的端口形成一一对应的关系。 在集群中使用一个服务将多个端口暴露出来。
Tips
: 创建多端口服务的时候必须给每个端口都命名。
比如 Pod
监听两个端口,其中的 8000 端口用于 HTTP
服务,8888 端口用于 HTTPS
服务,那么 Service
可以使用 80 端口转发到 Pod
的 8000 端口、使用 88 端口转发到 Pod
的 8888 端口,其定义的 YAML
文件如下所示:
apiVersion: v1
kind: Service
metadata:
name: multi-ports-service
spec:
ports:
- name: http
port: 80 # 将 Pod 的 8000 端口映射到 Service 的 80 端口
targetPort: 8000
- name: https
port: 88 # 将 Pod 的 8080 端口映射到 Service 的 88 端口
targetPort: 8080
selector:
app: test
也可以在定义
Pod
端口时直接给这些端口命名,然后在Service
中直接引用这些Pod
名称、而不是端口号,这样的好处在于如果Pod
端口经常变动的情况下,只需要修改Pod
的端口号,而不需要再去修改相关服务的端口号(因为引用的是端口名)。
依然用上面的例子,那么 Pod
的 YAML
文件定义为:
---
kind: Pod
spec:
containers:
- name: test-pod
ports:
- name: http #0 端口被命名为 http 端口
containerPort: 8000
- name: https #8 端口被命名为 https 端口
containerPort: 8888
引用端口命名的 Service
的 YAML
文件定义为:
apiVersion: v1
kind: Service
metadata:
name: multi-ports-service
spec:
ports:
- name: http
port: 80 # 将 Service 的 80 端口映射到 Pod 名为 http 的端口
targetPort: http
- name: https
port: 88 # 将 Service 的 88 端口映射到 Pod 名为 https 的端口
targetPort: https
selector:
app: test
举个栗子
在 /home/shiyanlou
目录下新建 tomcat-deployment.yaml
文件,并向其中写入如下代码:
apiVersion: apps/v1
kind: Deployment
metadata:
name: multi-ports
spec:
selector:
matchLabels:
app: tomcat
replicas: 2
template:
metadata:
labels:
app: tomcat
spec:
containers:
- name: tomcat
image: tomcat
ports:
- name: service # 将 8080 端口命名为 service
containerPort: 8080
- name: shutdown # 将 8005 端口命名为 shutdown
containerPort: 8005
执行创建:
$ kubectl create -f tomcat-deployment.yaml
查看创建的详细情况:
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
multi-ports 2/2 2 2 61s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
multi-ports-797bbf966b-9rh46 1/1 Running 0 94s
multi-ports-797bbf966b-tdrsz 1/1 Running 0 94s
在 /home/shiyanlou
目录下新建 multi-ports-service.yaml
文件,并向其中写入如下代码:
apiVersion: v1
kind: Service
metadata:
name: multi-ports-service
spec:
sessionAffinity: ClientIP # 设置 service 为会话保持模式
ports:
- name: service-port
port: 88 # 将名为 service-port Service 的 88 端口映射到 Pod 名为 service 的端口
targetPort: service
- name: shutdown-port
port: 85 # 将名为 shutdown-port Service 的 85 端口映射到 Pod 名为 shutdown 的端口
targetPort: shutdown
selector:
app: tomcat
执行创建:
# 可以看到 multi-ports-service 服务有两个端口,分别为 tcp 88 端口和 tcp 85 端口
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 85d
multi-ports-service ClusterIP 10.99.212.184 <none> 88/TCP,85/TCP 7s
# 查看 endpoints
$ kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.192.0.2:6443 85d
# 这里省略了一个 IP 地址+端口号 未显示,应该为:10.244.3.3:8080
multi-ports-service 10.244.2.4:8005,10.244.3.3:8005,10.244.2.4:8080 + 1 more... 18s
$ kubectl get endpoints -o yaml
...
subsets:
- addresses:
- ip: 10.244.2.4 # 可以看到 10.244.2.4 的 IP 地址是 kubernetes-worker 节点的
nodeName: kubernetes-worker
targetRef:
kind: Pod
name: multi-ports-797bbf966b-tdrsz
namespace: default
resourceVersion: "2077"
uid: f6106369-b920-4a76-a7cd-f342d287bfd6
- ip: 10.244.3.3 # 可以看到 10.244.3.3 的 IP 地址是 kubernetes-worker2 节点的
nodeName: kubernetes-worker2
targetRef:
kind: Pod
name: multi-ports-797bbf966b-9rh46
namespace: default
resourceVersion: "2069"
uid: 1be2227f-702a-4131-b767-7f1e0d852def
ports:
- name: shutdown-port
port: 8005 # 对应的容器 shutdown 端口号为 8005
protocol: TCP
- name: service-port
port: 8080 # 对应的容器 service 端口号为 8080
protocol: TCP
...
进行验证:
# 任意进入一个 Node 节点
$ docker exec -it kubernetes-worker bash
root@kubernetes-worker:/# curl 10.99.212.184:88
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Apache Tomcat/8.5.47</title>
<link href="favicon.ico" rel="icon" type="image/x-icon" />
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="tomcat.css" rel="stylesheet" type="text/css" />
</head>
...
需要着重理解的是 Service
的 Cluster IP
,是 Kubernetes
集群内部的虚拟 IP
,是伪造的 IP
网络,它有如下 4 个特点:
Cluster IP
只能用于Service
对象,由Kubernetes
从Cluster IP
池中进行分配和管理Cluster IP
不能被ping
通,因为没有“实体网络对象”进行响应,所以在集群内部只能使用curl
而不能使用ping
Cluster IP
只能和Service Port
一并使用构成通信端口,单独的Cluster IP
不具备TCP/IP
通信功能;如果集群外的节点想要访问这个通信端口,还需要额外的操作。- 在集群内部,
Node IP
网络、Pod IP
网络和Cluster IP
之间的通信采用的是Kubernetes
自己设计的独特的路由规则,这和普通的IP
路由不一样。
(2)Headless Service
"Headless Service" 翻译过来就是“无头服务”,它表示的是创建的 Service
没有设置 Cluster IP
。
- 它的创建非常简单,只需要设置
service.spec.clusterIP=None
即可。
它属于一种特殊类型的集群服务,通常应用于以下两种情况中:
- 自定义负载均衡策略,即:不使用
Service
默认的负载均衡策略(默认的策略是通过服务转发连接到符合要求的任一一个Pod
上)。 - 获取属于相同标签选择器下的所有
Pod
列表。
所以通过 Headless Service
可以获取到所有符合相关要求的 Pod
列表,然后可以通过自定义负载均衡器让客户端的连接转发到一个、多个、或是所有的 Pod
上,典型的应用就是:StatefulSet
。
Headless Service
的特点如下:
- 在集群内部没有一个特定的
Cluster IP
地址 kube-proxy
不会处理Headless Service
- 没有负载均衡和路由功能
- 根据服务是否有标签选择器进行
DNS
配置
是否定义标签选择器主要影响 DNS
配置:
- 设置了
Selector:Endpoints Controller
在apiService
中会创建Endpoints
记录,并且修改DNS
配置返回A
记录,这样就可以获取到Headless Service
对应的所有Pod
的 IP 地址。 - 没有设置
Selector
:不会有Endpoints
记录。
所以 Headless Service
自定义负载均衡的实现逻辑是:通过标签选择器获取到所有符合标签的 Pod
的 IP
地址列表,然后自定义服务响应的方式。
举个栗子
在 /home/shiyanlou
目录下新建 nginx-deployment.yaml
文件,并向其中写入如下内容:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 3 # 创建 3 个 nginx Pod 副本
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80 # 指定开放 80 端口
然后创建一个普通的、有 ClusterIP
的服务,在 /home/shiyanlou
目录下新建 normal-service.yaml
文件,并向其中写入如下内容:
apiVersion: v1
kind: Service
metadata:
name: normal-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 8080 # 将服务的 8080 端口映射到 nginx 容器的 80 端口
targetPort: 80
接着创建一个 headless
类型的 Service
,在 /home/shiyanlou
目录下新建 headless-service.yaml
文件,并向其中写入如下内容:
apiVersion: v1
kind: Service
metadata:
name: headless-service
spec:
clusterIP: None # 注意:这里一定要设置为 None
selector:
app: nginx
ports:
- protocol: TCP
port: 8080
targetPort: 80
执行创建:
kubectl create -f nginx-deployment.yaml
kubectl create -f normal-service.yaml
kubectl create -f headless-service.yaml
$ kubectl get all
# 有 3 个新创建的 nginx Pod
NAME READY STATUS RESTARTS AGE
pod/nginx-5754944d6c-5v6wq 1/1 Running 0 2m13s
pod/nginx-5754944d6c-q5mxh 1/1 Running 0 2m13s
pod/nginx-5754944d6c-vzjb9 1/1 Running 0 2m13s
# headless-service 的 CLUSTER-IP 为 None,而 normal-service 有一个固定的 CLUSTER-IP 为 10.107.154.5
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/headless-service ClusterIP None <none> 8080/TCP 90s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 85d
service/normal-service ClusterIP 10.107.154.5 <none> 8080/TCP 97s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 3/3 3 3 2m13s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-5754944d6c 3 3 3 2m13s
为了便于后面的验证,先查询创建的 3 个 Pod
的 IP
地址:
$ kubectl get pods -l app=nginx -o yaml|grep podIP
podIP: 10.244.2.5
podIP: 10.244.2.4
podIP: 10.244.3.3
现在可以进入容器内部通过 DNS
查找 podIP
,这里单独运行一个使用 utils
镜像创建的 pod
执行命令(镜像中已经安装了 dnsutils
):
$ kubectl run --rm utils -it --image registry-vpc.cn-hangzhou.aliyuncs.com/chenshi-kubernetes/utils:latest bash
root@utils:/# nslookup normal-service
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: normal-service.default.svc.cluster.local
Address: 10.107.154.5
root@utils:/# nslookup headless-service
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: headless-service.default.svc.cluster.local
Address: 10.244.3.3
Name: headless-service.default.svc.cluster.local
Address: 10.244.2.4
Name: headless-service.default.svc.cluster.local
Address: 10.244.2.5
通过上面的输出也就证实了,访问 normal-service
获得的是服务在集群中的 ClusterIP
,而访问 headless-service
获取到的是符合标签选择器的所有 Pod
的 IP
地址列表。
Headless Service
可以让我们直接连接到所有的 Pod
,而不需要使用作为负载均衡的 Service
或是 kube-proxy
,这类特殊的服务有其特定的用途,比如经常和 StatefulSet
搭配使用。
(3)无 Selector
的服务
它也属于一种特殊类型的集群服务。在上一节我们提到过如果没有标签选择器,就不会有 Endpoints
记录。但是可以创建 Endpoints
对象,在 Endpoints
对象中手动指定需要映射到的 IP
地址和端口号。
虽然 Service
服务通常都会被用来代理对于 Pod
的访问,但是也可以代理其它的后端类型,只要我们自定义 Endpoints
记录就可以了。这些情况通常包括:
- 一个集群在不同场景下使用不同的数据库。比如在生产环境中使用外部数据库,而在测试环境中使用集群内的数据库;
- 服务被其它命名空间或是其它集群上的服务调用;
- 当迁移应用时,一些后端在集群内部运行,一些后端在集群外部运行。
需要注意的是:如果只定义一个没有标签选择器的服务,那么创建的服务在集群内有 VIP
,只是没有 Endpoints
而已。
举个栗子
在 /home/shiyanlou
目录下新建 no-selector-svc.yaml
文件,并向其中写入如下内容:
apiVersion: v1
kind: Service
metadata:
name: no-selector-svc
spec:
ports:
- protocol: TCP
port: 80 # 指定 ClusterIP 对应的端口为 80
targetPort: 9376 # 指定对应 Pod 的端口为 9376
执行创建:
$ kubectl create -f no-selector-svc.yaml
service/no-selector-svc created
# 定义的 no-selector-svc 依然是有 ClusterIP 的,为:10.100.179.62
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 87d
no-selector-svc ClusterIP 10.100.179.62 <none> 80/TCP 22s
然后在 /home/shiyanlou
目录下新建 test-endpoints.yaml
文件,并向其中写入如下内容:
apiVersion: v1
kind: Endpoints
metadata:
name: no-selector-svc # 这里的名称一定要与需要绑定的 Service 相同
subsets:
- addresses:
- ip: 192.0.2.42 # Endpoints 需要映射到的 IP 地址
ports:
- port: 9376 # Endpoints 映射的 IP 地址对应的端口号
执行创建:
$ kubectl create -f test-endpoints.yaml
endpoints/no-selector-svc created
查看新创建好的 Endpoints
:
# 可以发现自定义的与 no-selector-svc 服务相关联的 Endpoints 映射已经建立好了
$ kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.192.0.2:6443 87d
no-selector-svc 192.0.2.42:9376 24s
在设置 Endpoints
的 subsets.addresses.ip
字段时,需要注意它们不是以下的任意一种:
- 环回地址,比如:
IPv4
的127.0.0.0/8
,IPv6
的::1/128
- 本地连接,比如:
IPv4
的169.254.0.0/16
和224.0.0.0/24
,IPv6
的fe80::/64
Service
的ClusterIP
访问没有 selector
的 Service
,与有标签选择器的服务一样,依然是将请求路由到用户自定义的 Endpoints
,在上面的例子中就是地址 192.0.2.42:9376
。
Endpoints
的缺点是只能指定 IP
,如果想要指定网址可以使用 ExternalName
类型的 Service
。
\