0%

k8s 中的 7 层调度 -- Ingress

关于 Kubernetes 系列其他文章的传送门

  1. 《 部署 Kubernetes 也能如此简单吗? 》

  2. 《 初识 k8s,新时代的宠儿 》

  3. 《 深度学习 Pod 》

  4. 《 学会这 5 种 Pod 控制器,搞定发布、更新、回滚,从此告别人肉运维!! 》

  5. 《 k8s 中的网络入口 – Service 资源管理 》

  6. 《 k8s 中的 7 层调度 – Ingress 》     您当前所在位置

  7. 《 k8s 中的存储系统 – Volumes 》

  8. 《 k8s 中强大的配置中心 – ConfigMap 》

  9. 《 k8s 中的部署神器 – Statefulset 控制器 》

  10. 《 k8s 中的安全守护神 – 你不知道不代表它不在 》

  11. 《 k8s 中的图形接口 – Dashboard 》

  12. 《 k8s 中的网络插件 – flannel 和 Calico 》

  13. 《 k8s 中的调度器 – 亲和性、污点和容忍性 》

  14. 《 k8s 中对资源的监控 – 资源指标、Prometheus、弹性伸缩器 HPA 》

  15. 《 k8s 中的云原生应用管理利器 – Helm 》

  16. 《 理解 k8s 高可用,让你的集群稳如泰山 》



logo

一、Ingress 是什么?

在使用 Service 时,无论是 iptables 还是 ipvs 都是四层调度,这就代表它们都无法作为 ssl 会话卸载器使用,这就要引入另一个资源来使用 7 层的调度。

Ingress 可以想象成是一种七层的路由规则,对于 http 和 https 的流量,能够把来自客户端的对某个 URL 的访问,转换为对内部某个特定 Pod 的访问。

Ingress 能够让你用一种 通用配置格式 来配置规则,这个语法不是 Nginx 格式,也不是 Apache 格式。但是写好通用配置后,它能够自动转为 Nginx 或 Apache 等程序的配置格式。所以你只需要告诉它,需要把哪个 Url 代理到哪个 Url 就完了,至于怎么转换,那是自动进行的。

负责 入站,在 7 层把外部的访问代理到 Pod 中。

Ingress Controller 是什么?

Ingress 只是一种规则,而这个规则需要一个实实在在存在的进程,这个进程能够理解 Ingress 定义的配置信息(规则),并将 Ingress 规则转为 Nginx、HAProxy 等程序能够识别的配置文件,这就是 Ingress Controller

注意

  • Ingress Controller 并不是所谓的控制器,它只是一个 Pod,被称为 Ingress Controller 而已。
  • 这个 Pod 的作用是专门用来确保 Ingress 中定义的配置,能够实时加载并生效的。他与后端 Pod 是一对多的,后边可能有无数个 Pod。
  • 这个控制器没有被打包进 Controller-manager 中统一管理,它既可以运行为一个系统级的守护进程,也可以运行在 Pod 中。但是因为它很复杂,还经常需要变动,为了便于配置,所以不得不将它运行为 Pod。
  • Ingress Controller 也是 Pod,它也应该有自己的 Pod Controller,或许使用 DeployMent 类型来维持多个副本,或许使用 DaemonSet 类型,在每个节点上都运行一份。

综上所述,如果要在 k8s 集群中使用 ssl 会话卸载器之类的 7 层代理功能,就需要使用以下两个组件,而不是用 Service。

  • Ingress Controller:用来把客户端的访问代理到其他的 Pod 上;
  • Ingress:用来定义代理规则。

但是 Ingress 并不支持使用标签选择器来挑选后端的 Pod,所以当后端有多个 Pod 时,它需要由 Service 辅助完成此类功能。在这里 Service 还有另一个功能,就是当 Pod 发生变化时,比如数量增加了一个,也需要通过 Service 来 帮助 Ingress 感知的。

  • 注意:这时 Service 仅仅为 Ingress 提供标签选择器的功能,并不代理网络流量,它不是 Ingress 真正的后端,客户端的访问不会到达 Service,而是从 Ingress 直接到达 Pod。

Igress

二、Ingress 在生产环境中的使用方法

方案一

这是标准方法之一。

假设目前有多个后端 Pod 需要 7 层的代理服务,这时启动一个 Ingress Controller 来代理对后端 Pod 的访问。

这个 Ingress Controller 也是一个 Pod,为这个 Pod 添加一个 NodePort 类型的 Service,这样任何一台 Node 都可以作为引入外部流量的入口。

但是缺点是 NodePort 一般情况下使用的不是标准端口,于是就需要在节点外再加一个代理服务器,来将非标准端口转换成 80443 之类的标准端口。

大致流程如下:

客户端 -> 代理节点(监听 80 标准端口) -> Node 节点 -> Service(Nodeport 监听着非标准端口) -> Ingress Controller 的 Pod -> 后端 Pod

这个流程太繁琐了,一点都不让人省心,所以为了简化这个过程,就有了下面的方案二

方案二

在集群中准备几个特殊的 node,这几个 node 不仅有私网地址,还有公网地址。

然后为这几个节点打上污点或使用标签(ds 支持节点标签选择器),通过 ds 控制器让 Ingress Controller 的 Pod 只在这几个有公网地址的节点上运行,并为 Pod 添加一个 hostPort 类型的 Service,直接映射到物理节点的某个端口。

如果为这几个 Node 节点预留了 80443 这样的标准端口,这样当客户端访问这几个特殊节点中某个节点上的某个公网端口时,就等于访问当前节点中的 Ingress Controller 的端口,而后 Ingress Controller 就可以把访问代理至后端的 Pod 上了。

大致流程如下:

客户端 -> Node 节点 -> Service(hostPort) -> Ingress Controller 的 Pod -> 后端 Pod

方案三

还有一个更简化的方案。

在方案二的基础上,不使用 hostPort,而是让 Pod 共享节点的网络名称空间(hostNetwork)。

实现了无需代理,一次直达 Pod。

大致流程如下:

客户端 -> Node 节点(hostNetwork) == 访问 Ingress Controller 的 Pod -> 后端 Pod

Ingress 版 Nginx

现在有一个问题摆在眼前,现有的 Nginx、Haproxy 等都不是云原生的程序,所以并不知道如何从 API Server 中读取 Ingress 的规则,也无法看懂 Ingress 中定义的规则。这可怎么办呢?

好在 k8s 官方把 Nginx 改造,变成一个自带 Ingress Controller 功能的 Nginx。所以要在 k8s 中使用的话,需要使用经过改造的 Nginx。

k8s 官方经过改造的 Ingress-Nginx项目地址。

不过,虽然经过改造,但是他们本身并非云原生应用,不太适合这种动态的规则不断变化的场景。因此产生了很多专门用来运行在 k8s 上云原生的 Ingress Controller,比如 traefik、Kong、voyager、contour 等,对比如图:

logo

更多详细的比较信息可以看这篇:
Kubernetes 的 Ingress 控制器比较

不过对于中小企业,Ingress-nginx 也够用了,这里介绍部署方式。

Ingress-Nginx 的部署示例

在 Ingress-nginx 项目的 deploy/static/ 目录下有 4 个部署的配置文件,其中 mandatory.yaml 是这 4 个配置文件的集合,使用此配置文件可直接在集群的 master 节点上进行部署。

裸格式的地址为:https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

直接使用在线文件,创建 Ingress-Nginx:

1
~]# kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

创建好后,所有的资源都在 ingress-nginx 这个名称空间下:

1
~]# kubectl get pods -n ingress-nginx

至此这个自带 Ingress Controller 功能的 Nginx 就安装好了,但是默认情况下,这只是一个普通的 Deployment 类型的 Pod,没有对外开放任何访问入口。

如果想让 k8s 之外的客户端访问这个 Ingress Controller,接下来就需要为其添加一个 Service,可以用的三个网络模型有:

  • NodePort
  • hostPort
  • hostNetwork

Service 配置示例:方案一,使用 NodePort

这里演示方案一,使用 NodePort 网络模型,这也是最 Low 的哪种 😜

网络逻辑图

Igress-nginx

这里演示的映射关系很复杂,可以简化一点的方法就是让 Ingress-nginx 的 Service 直接使用节点的网络名称空间(hostNetwork),也就是方案三中描述的那样。不过这里就不演示方案三了,因为只要理解方案一、二,方案三理解起来也不是问题了。

为 Ingress-Nginx 创建 Service

先查一下刚才部署的 ingress-nginx 的 Pod 标签,用于给 Service 连接到此 Pod 时使用:

1
2
3
~]# kubectl get pods -n ingress-nginx --show-labels
NAME READY STATUS RESTARTS AGE LABELS
nginx-ingress-controller-568867bf56-j68dp 1/1 Running 0 76m app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx,pod-template-hash=568867bf56

创建 Service 配置文件:vim ingress-nginx-svc.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadate:
name: ingress
namespace: ingress-nginx
spec:
selector: # 定义为刚才查到的标签
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
ports:
- name: http
port: 80
# nodePort: XXXXX # 指定把 80 映射到 Node 节点的哪个端口,不指定则随机
- name: https
port: 443
type: NodePort # Service 类型为 NodePort

创建 Service:

1
kubectl apply -f ingress-nginx-svc.yaml

查看刚才创建的 svc,可以看到映射的端口了,这里随机到了 3131532329

1
2
3
4
root@master [12:49:54 PM] [~] 
-> # kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress NodePort 10.108.224.148 <none> 80:30315/TCP,443:32329/TCP 13s

不过随机的端口号很不好记,如果不冲突,可以指定让 NodePort 使用一个好记的端口号:

  • 比如 3008030443,方法是把配置文件改成下面这样就行了,不过这里就先不改了:
1
2
3
4
5
6
7
8
9
...
ports:
- name: http
port: 80
nodePort: 30080 # 指定映射到 Node 节点的 30080
- name: https
port: 443
nodePort: 30443 # 指定映射到 Node 节点的 30443
type: NodePort

因为使用的是 Nodeport,所以可以通过任意一台 Node 节点的 IP,加 31315 或者 32329 端口来访问它。只不过目前这个 Ingress-nginx 还没有网页文件,会提示 404,但是如果能看到 Nginx 的信息即可。

至此这个自带 Ingress Controller 功能的 Nginx 就部署好了。

不过就如方案一中说的那样,有一个缺点,因为自己没有使用通用端口,所以还需要在集群外额外部署负载均衡器来代理监听 80443 端口,这里就不做演示了。

开头说过,Ingress Controller 的功能只是把通用规则转换为 Nginx 等程序能够识别的配置文件,但是目前都还没有定义任何 Ingress 规则,所以接下来需要配置 Ingress 规则。

定义 Ingress 规则

Ingress 规则本身也是 k8s 中的一种资源,它的 API 版本是 extensions/v1beta1

注意:一个集群中可能运行着不止一个 Ingress Controller,比如同时部署了 Ingress-Nginx 和 Ingress-HAProxy,那么该由哪个来转换规则呢?

Ingress 支持在 annotations 中定义一些规则,那么在这里就可以定义由谁来转换的规则:

  • kubernetes.io/ingress.class: "nginx":让 Ingress-Nginx 来转换;
  • kubernetes.io/ingress.class: "haproxy":让 Ingress-HAProxy 来转换。

Ingress-Nginx 其实还支持很多转换规则,可以定义的很细节,在这里可以查看全部:

定义 Ingress 通用规则,下面结合示例,也顺便讲下 Ingress 的语法:

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
~]# vim myapp-ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myapp
namespace: default
annotations: # 在这里可以定制各种规则
nginx.ingress.kubernetes.io/rewrite-target: / # 此处可以定义是否进行重定向
kubernetes.io/ingress.class: "nginx" # 当集群中运行多个 Ingress Controller 时,指定由谁来解析
spec:
rules:
- host: www.monster.com # 对应 Nginx 中的 ServerName,不写则代表使用默认虚拟主机(Nginx 或 Apache 中的默认虚拟主机)
# 注意客户端需要能解析此域名到 Ingress Controller 上才能正常访问
http:
paths: # url 的列表,包含多个 “- path”,每个 “- path” 代表 “接入的入口” 和 “对应一组后端被代理服务器” 的配置集合
- path: / # 对应 Nginx 中的 location,不写默认为 /
backend: # 对应 Nginx 中的 upstraem{}
serviceName: myapp # 指定后端被代理 Pod 的名字。 # 在后面演示时会创建
servicePort: 80 # 指定后端要映射的端口
# 如果有多个规则,定义多个 “- path” 即可
- path: /foo
baskend:
serviceName: myapp
servicePort: 443
# - host: www.test.com # 如果再定义一个 host,就对应 Nginx 中基于域名的虚拟主机

根据配置文件创建 Ingress 规则:

1
2
3
4
5
-> # kubectl apply -f myapp-ingress.yaml

-> # kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
myapp master.monster.com 80 31s

定义好 Ingress 规则后,去 ingress-nginx 验证一下是否已经更新到配置:

1
2
3
4
5
6
7
8
9
10
11
# 先查看 ingress-nginx 的 Pod 名是什么:
-> # kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-568867bf56-j68dp 1/1 Running 0 6h12m

# 然后进入这个 Pod 的交互式接口:
-> # kubectl exec -it nginx-ingress-controller-568867bf56-j68dp -n ingress-nginx -- /bin/sh
$ grep "master.monster.com" /etc/nginx/nginx.conf --color
server_name master.monster.com ;

# 能看到确实有我们配置的配置参数即可。

创建后端的 Pod

这两个是后端的业务 Pod,由前边的 Ingress-Nginx 来反向代理。

后端 Pod 的配置文件:backend-pod-example.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
28
29
30
31
32
33
34
35
36
37
# 定义后端 Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: default
spec:
replicas: 2 # 启动两个 Pod
selector:
matchLabels:
app: myapp
template:
metadata:
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1

---

# 定义此 Pod 要绑定的 svc
# 这个 Service 不负责网络,只负责帮 Ingress-Nginx 来引路、调度到这两个 Pod
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 80

创建并查看:

1
2
3
4
5
6
7
8
-> # kubectl apply -f backend-pod-example.yaml
deployment.apps/myapp created
service/myapp created

-> # kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myapp-76c7bcdb86-xm2kn 1/1 Running 0 20s 10.244.1.30 node1.monster.com <none> <none>
myapp-76c7bcdb86-zvczc 1/1 Running 0 20s 10.244.2.31 node2.monster.com <none> <none>

验证

结合拓扑图来看下:

Igress-nginx

接下来在外网访问 Ingress-Nginx 的 Service 地址,也就是 Node 节点 IP + 3031532329 端口。

  • 这个 Service 是之前使用 NodePort 方式创建,并绑定在 Ingress-Nginx 上,使用 kubectl get svc -n ingress-nginx -o wide 可以看到。

注意:因为在 Ingress 规则中显式定义了 - host: www.monster.com,这代表使用基于域名的虚拟主机,所以不要通过 IP 访问,否则会调度到默认的虚拟主机上去

1
2
3
# 我这里通过内网 DNS 将域名解析到了 Node 节点
-> # curl master.monster.com:30315
Hello MyApp | Version: v1 | <a href="hostname.html">Pod Name</a>

而且当前副本数是 2,访问时应该有负载均衡效果:

1
2
3
4
5
6
7
8
9
10
11
12
-> # curl master.monster.com:30315/hostname.html
myapp-76c7bcdb86-zvczc
-> # curl master.monster.com:30315/hostname.html
myapp-76c7bcdb86-xm2kn
-> # curl master.monster.com:30315/hostname.html
myapp-76c7bcdb86-zvczc
-> # curl master.monster.com:30315/hostname.html
myapp-76c7bcdb86-xm2kn
-> # curl master.monster.com:30315/hostname.html
myapp-76c7bcdb86-zvczc
-> # curl master.monster.com:30315/hostname.html
myapp-76c7bcdb86-xm2kn

Service 配置示例:方案二,使用 hostPort

在生产环境中,其实方案二是比较妥当的方法,因为预留了 80443 等端口,所以前端不需要部署负载均衡器来转换端口了。

先把 nginx-ingress 的配置文件下载到本地:

1
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml

进行一些修改:~]# vim mandatory.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#- 在 name: nginx-ingress-controller 配置内,最少应该更改的地方有 3 处

#------------------- 第 1 处 -------------------#
kind: Deployment # 把控制器类型从这个
# ↓
kind: DaemonSet # 改成这个

#------------------- 第 2 处 -------------------#
replicas: 1 # Daemon 类型的控制器不需要定义副本数,删除这行

#------------------- 第 3 处--------------------#
#- 在 spec 内添加以下配置段
spec:
nodeSelector: # 在 Pod 的配置段中,定义节点标签选择器,指定让 Pod 运行在哪个节点上
matchLabels:
ingress: nginx # 指定 Pod 只运行在标签为 ingress 且值为 nginx 的节点上。
# PS:节点的标签要提前设置好

之后再创建 Ingress 规则、后端 Pod 等操作就好了,这里就不赘述了。

三、其他 Ingress 使用技巧

SSL 会话卸载器

创建证书:

1
2
3
~]# openssl genrsa -out myapp.key 2048

~]# openssl req -new -x509 -key myapp.key -out myapp.crt -subj /C=CN/ST=Beijing/L=Beijing/O=Ops/CN=ssltest.monster.com -days 365

导入证书到 k8s:

1
~]# kubectl create secret tls ssltest-cert --cert=myapp.crt --key=myapp.key

创建一个新的 Ingress 规则,并指明使用 tls:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
~]# vim myapp-tls-ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myapp-tls
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls: # 指明使用 tls
- hosts:
- ssltest.monster.com # 一定要和证书中定义的域名一致
secretName: ssltest-cert # 指明使用刚才创建的 ssltest-cert 证书,注意两者要在同一个名称空间内,否则看不见
rules:
- host: ssltest.monster.com
http:
paths:
- path: /
backend:
serviceName: myapp # 指明要代理到哪个 Pod
servicePort: 80

查看刚才创建的:

1
2
3
4
-> # kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
myapp master.monster.com 80 77m
myapp-tls ssltest.monster.com 80, 443 27s

访问 https 连接:https://ssltest.monster.com:32329,就会被代理到 myapp 的 80 端口。

访问流程:
_ https://ssltest.monster.com:32329 (NodePort) -> Ingress-Nginx 的 Service :443 -> 由 Ingress-Nginx Pod 代理至 -> myapp :80

使用 Ingress 结合 Tomcat 配置示例

创建一个 tomcat 的后端 Pod

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
# 创建一个新的 ns
~]# vim tomcat.yaml

apiVersion: v1
kind: Namespace
metadata:
name: tomcat

---

# 定义后端 Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat
namespace: tomcat
spec:
replicas: 2
selector:
matchLabels:
app: tomcat
template:
metadata:
namespace: tomcat
labels:
app: tomcat
spec:
containers:
- name: tomcat
image: tomcat:alpine

---

# 定义此 Pod 要绑定的 svc
apiVersion: v1
kind: Service
metadata:
name: tomcat
namespace: tomcat
spec:
selector:
app: tomcat
ports:
- name: http
port: 8080
targetPort: 8080
- name: ajp
port: 8009
targetPort: 8009

开始创建:

1
2
3
4
-> # kubectl apply -f tomcat.yaml
namespace/tomcat created
deployment.apps/tomcat created
service/tomcat created

查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
-> # kubectl get all -n tomcat
NAME READY STATUS RESTARTS AGE
pod/tomcat-5c9bc6d8fd-jmshp 1/1 Running 0 91s
pod/tomcat-5c9bc6d8fd-kk6b4 1/1 Running 0 91s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/tomcat ClusterIP 10.98.94.254 <none> 8080/TCP,8009/TCP 92s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/tomcat 2/2 2 2 92s

NAME DESIRED CURRENT READY AGE
replicaset.apps/tomcat-5c9bc6d8fd 2 2 2 92s

创建 Ingress 规则

创建配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-> # vim tomcat-ingress.yaml 

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: tomcat
namespace: tomcat
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: master.monster.com
http:
paths:
- path: /tomcat
backend:
serviceName: tomcat
servicePort: 8080

创建 Ingress 规则,并查看应该有如下信息:

1
2
3
4
-> # kubectl apply -f tomcat-ingress.yaml 

-> # kubectl describe ingress tomcat -n tomcat|grep 'tomcat:8080'
/tomcat tomcat:8080 (10.244.2.32:8080,10.244.3.34:8080)

之后在外部网络访问即可:

1
curl https://master.monster.com:32329/tomcat