一、Service定义

通过创建 Service ,可以为一组具有相同功能的容器应用提供一个统一的入口地址,井且将请求负载分发到后端的各个容器应用上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion : vl         #Required
kind: Service #Required
metadata: #Required
name : string #Required
namespace : string #Required
labels:
- name: string
annotations:
- name: string
spec : #Required
selector : [] #Required
type : string #Required
clusterIP: string
sessionAffinity: string
ports:
- name: string
protocol: string
port: int
targetPort : int
nodePort: int
status: #外部负载均衡器
loadBalancer :
ingress:
ip: string
hostname: string

二、Service基本用法

对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简便的方式就是通过 TCP/IP 机制及监听 和端口号来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#webapp-rc.yaml
apiVersion : v1
kind: ReplicationController
metadata:
name : webapp
spec:
replicas: 2
template:
metadata:
name: webapp
labels:
app: webapp
spec:
containers:
- name: webapp
image : tomcat
ports:
- containerPort: 8080

创建:

1
kubectl create -f webapp-rc.yaml

快速创建service ,通过kubectl expose:

1
2
3
4
kubectl expose rc webapp

#查看
kubectl get svc

通过配置文件创建

1
2
3
4
5
6
7
8
9
10
apiVersion : v1  
kind: Service
metadata:
name: webapp
spec:
ports:
- port: 8081
targetPort: 8080
selector:
app: webapp

Service 定义中的关键字段是 port和selector ports 定义部分指定了 Service 所需的虚拟端口号为 8081,由于与 Pod 容器端口号8080 不一样,所以需要再通过 targetPort 来指定后Pod端口号。 selector 定义部分设置的是后端 Pod 所拥有的 label: app=webapp

目前有2种负载分发策略:

  • RoundRobin:轮询模式,即轮询将请求转发到后端的各个 Pod 上。默认
  • SessionAffinity :基于客户端IP地址进行会话保持的模式,即第一次将某个客户端发起的请求转发到后端的某个 Pod 上,之后从相同的客户端发起的请求都将被转发到后端相同的 Pod 上。

Service 还可以进行其他类型的设置,例如设置多个端口号、直接设置为集群外部服务,或实现为无头服务( Headless )模式

多端口Service

有时一个容器应用也可能提供多个端口的服务,那么在 Service 的定义中也可以相应地设置为将多个端口对应到多个应用服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion : v1  
kind: Service
metadata:
name: webapp
spec:
ports:
- port: 8080
targetPort: 8080
name: web
- port: 8005
targetPort: 8005
name: management
selector:
app: webapp

两个端口号使用了不同的4层协议一一TCP和UDP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion : v1  
kind: Service
metadata:
name: kube-dns
namespace: kube-system
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name : "KubeDNS"
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

外部服务Service

应用系统需要将一个外部数据库作为后端服务进行连接,或将另一个集群或Namespace 中的服务作为服务的后端,这时可以通过创建一个无 Label Selector的Service实现

1
2
3
4
5
6
7
8
9
apiVersion : v1  
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol : TCP
port : 80
targetPort: 80

通过该定义创建的是一个不带标签选择器的 Service 即无法选择后端的 Pod, 系统不会自动创建 Endpoint ,因此需要手动创建一个和该 Service 同名的 Endpoint ,用于指向实际的后端访问地址。

1
2
3
4
5
6
7
8
9
kind: Endpoints
apiVersion : v1
metadata:
name: my-service
subsets:
- addresses:
- IP: 1.2.3.4
ports:
- port: 80

访问没有标签选择器的 Service 带有标签选择器的 Service 请求将会被路由到由用户手动定义的后端 Endpoint。

image-20211103221514595

三、Headless Service

在某些应用场景中,开发人员希望自己控制负载均衡策略不使用 Service 提供的默认负载均衡的功能,或者应用程序希望知道属于同组服务的其他实例

Kubernetes 提供了 Headless Service (无头服务)来实现这种功能,即不为 Service 设置 ClusterIP 入口地址 ,仅通Label Selector 将后端的 Pod 列表返回给调用的客户端。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion : v1 
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
clusterIP : None
selector:
app: nginx

Service 就不再具有一个特定 ClusterIP 对其进行访问将获得包含 Labelapp=nginx 的 全部 Pod 然后客户端程序自行决定如何处理这个 Pod 列表。 如, StatefulSet就是使用 Headless Service 为客户端返回多个服务地址

对于去中心化类 集群 Headless Service 将非常有用。

四、集群外部访问 Pod Service

由于 Pod和Service是Kubernetes 集群范围内的虚 概念,所以集群外的客户端系统无法通过Pod的IP地址或者 Service 的虚拟IP地址和虚拟端口号访问到它们。为了让外部客户端可以访问这些服务,可以将 Pod Service 的端口号映射到宿主机,以使得客户端应用能够通过物理机访问容器应用。

1.容器端口好映射到物理机

  1. 通过设置容器级别的 hostPort 将容器应用的端口号映射到物理机上:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pod-hostport.yaml 
apiVersion: v1
kind : Pod
metadata:
name: webapp
labels :
app : webapp
spec :
containers:
- name : webapp
image : tomcat
ports:
- containerPort: 8080
hostPort: 8081

通过物理机的 地址和 8081 端口号访问 Pod 内的容器服务

  1. 通过设置 Pod 级别的 hostNetwork=true ,该 Pod 中所有容器的端口号都将被直接映射到物理机上。需要注意 ,在容器的 ports 定义部分如果不指定 hostPort,则默认 hostPort 等于 containerPort如果指定了 hostPort ,则 hostPort 必须等于 containerPort的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pod-hostnetwork.yaml 
apiVersion: v1
kind : Pod
metadata:
name: webapp
labels :
app : webapp
spec :
hostNetwork : true
containers:
- name : webapp
image : tomcat
imagePullPolicy : Never
ports:
- containerPort: 8080

通过物理 地址 8080 端口号访问 Pod 的容器服务

2.将Service的端口号映射到物理机

  1. 通过设置 nodePort映射到物理机 ,同时设置 Service 的类型为 NodePort:
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion : v1 
kind: Service
metadata:
name: webapp
spec:
type: NodePort
ports:
- port: 8080
targetPort : 8080
nodePort: 8081
selector:
app: webapp

通过物理机的 地址和 nodePort 8081 端口号访问服务

五、DNS服务搭建指南

在集群内需要能够通过服务名对服务进行访问,这就需要一个集群范围的 DNS 服务来完成服务名到 ClusterIP 的解析

Kubernetes 提供的虚拟 DNS 服务名为 sky ,由 个组件组成。

  1. etcd: DNS 存储

  2. kube2sky :将 Kubernetes Master 中的 Service (服务)注册到 etcd

  3. skyDNS :提供 DNS 域名解析服务

  4. healthz :提供对skydns 服务的健康检查功能

image-20211103224120176

六、自定义 DNS 和上游 DNS 服务器

七、Ingress: HTTP 层路由机制

Service 的表现形式为 IP:Port,即工作在 TCP/IP层。而对于基于 HTTP 的服务来说,不同的 URL 地址经常对应到不同的后端服务或者虚拟服务器( Virtual Host ),这些应用层的转发机制仅通过 Kubernetes Service 机制是无法实现的。

Ingress 资源对象,用于将不同 URL 的访问请求转发到后端不同Service ,以实现 HTTP 层的业务路由机制。 Kubernetes 使用一个 Ingress 策略定义和一个具体Ingress Controller ,两者结合并实现了 一个完整 Ingress 负载均衡器。

使用 Ingress 进行负载分发时, Ingress Controller 将基于 Ingress 规则将客户端请求直接转发到 Service 对应的后端 Endpoint (即 Pod )上,这样会跳过 kube-proxy 的转发功能, kube-proxy不再起作用。

IngressService的统一网关入口

image-20211103230218688
  • http://mywebsite.com/api 的访问将被路由到后端名为“api”的 Service
  • http://mywebsite.com/web 的访问将被路由到后端名为“web”的 Service
  • http://mywebsite.com/doc 的访问将被路由到后端名为“doc”的 Service

为使用 Ingress ,需要创建 Ingress Controller (带一个默认 backend 服务)和 Ingress 策略设置来共同完成

1.创建 Ingress Controller 和默认的 backend 服务

先部署 Ingress Controller ,以实现为所有后端 Service 提供一个统一的入口。 Ingress Controller 需要实现基于不同 HTTP URL 向后转发的负载分发规则,并可以灵活设置7层的负载分发策略。如果公有云服务商能够提供该类型 Hπp 路由 Load.Balancer,则也可设置其为 Ingress Controller.

Ingress Controller 将以 Pod 的形式运行,监控 apiserver 的/ingress 接口后端的 backend services ,如果 service 发生变化,则 Ingress Controller 应自动更新其转发规则

使用ingress实现一个Ingress Controller

  • 监昕 apiserver ,获取全部 ingress 的定义。
  • 基于 ingress 的定义, 生成 Nginx 需的配置文件/etc/nginx/nginx.conf
  • 执行 nginx -s reload 命令, 重新加载 nginx.conf 配置文件的内容,

Go语言核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
for { 
rateLimiter.Accept()
ingresses, err : = ingClient.List(labels.Everything(), fields.Everything())
if err != nil || reflect.DeepEqual(ingresses.Items, known.Items) {
continue
}
if w, err :=os.Create ("/etc/nginx/nginx.conf"); err != nil {
log.Fatalf ("Failed to open %v: %v", nginxConf, err)
} else if err := tmpl.Execute (w, ingresses); err != nil {
log.Fatalf ("Failed to write template", err)
}
shellOut ("nginx -s reload")
}

使用谷歌提供的 nginx-ingress-controller 镜像来创建 Ingress Controller 以daemonset 的形式进行创建,在每个 Node 上都将启动一个 Nginx 服务。

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.47.0/deploy/static/provider/baremetal/deploy.yaml

#修改镜像
vi deploy.yaml
#将image的值改为如下值:
registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/ingress-nginx-controller:v0.46.0

#应用
kubectl apply -f ingress-deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
configmap/ingress-nginx-controller created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
service/ingress-nginx-controller-admission created
service/ingress-nginx-controller created
deployment.apps/ingress-nginx-controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
serviceaccount/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created

# 检查安装的结果
kubectl get pod,svc -n ingress-nginx

# 最后别忘记把svc暴露的端口要放行
ingress-nginx ingress-nginx-controller NodePort 10.96.218.45 <none> 80:31993/TCP,443:31515/TCP 85m
ingress-nginx ingress-nginx-controller-admission ClusterIP 10.96.194.114 <none> 443/TCP 85m

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-server
spec:
replicas: 2
selector:
matchLabels:
app: hello-server
template:
metadata:
labels:
app: hello-server
spec:
containers:
- name: hello-server
image: registry.cn-hangzhou.aliyuncs.com/lfy_k8s_images/hello-server
ports:
- containerPort: 9000
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-demo
name: nginx-demo
spec:
replicas: 2
selector:
matchLabels:
app: nginx-demo
template:
metadata:
labels:
app: nginx-demo
spec:
containers:
- image: nginx
name: nginx
---
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx-demo
name: nginx-demo
spec:
selector:
app: nginx-demo
ports:
- port: 8000
protocol: TCP
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
labels:
app: hello-server
name: hello-server
spec:
selector:
app: hello-server
ports:
- port: 8000
protocol: TCP
targetPort: 9000
1
kubectl apply -f test.yaml

域名访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-host-bar
spec:
ingressClassName: nginx
rules:
- host: "hello.lq.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hello-server
port:
number: 8000
- host: "demo.lq.com"
http:
paths:
- pathType: Prefix
path: "/nginx" # 把请求会转给下面的服务,下面的服务一定要能处理这个路径,不能处理就是404
backend:
service:
name: nginx-demo ## java,比如使用路径重写,去掉前缀nginx
port:
number: 8000

对浏览器配置域名映射

1
2
192.168.0.100  hello.lq.com
192.168.0.100 demo.lq.com

浏览器访问

1
2
http://hello.lq.com:31993/
http://demo.lq.com:31993/

路径重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations: #加注解
nginx.ingress.kubernetes.io/rewrite-target: /$2
name: ingress-host-bar
spec:
ingressClassName: nginx
rules:
- host: "hello.lq.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hello-server
port:
number: 8000
- host: "demo.lq.com"
http:
paths:
- pathType: Prefix
path: "/nginx(/|$)(.*)" # 把请求会转给下面的服务,下面的服务一定要能处理这个路径,不能处理就是404
backend:
service:
name: nginx-demo ## java,比如使用路径重写,去掉前缀nginx
port:
number: 8000

流量限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-limit-rate
annotations:
nginx.ingress.kubernetes.io/limit-rps: "1"
spec:
ingressClassName: nginx
rules:
- host: "haha.lq.com"
http:
paths:
- pathType: Exact
path: "/"
backend:
service:
name: nginx-demo
port:
number: 8000