Kubernetes之Ingress

  从前面的学习,我们可以了解到Kubernetes暴露服务的方式目前只有三种:LoadBlancer Service、ExternalName、NodePort Service、Ingress;而我们需要将集群内服务提供外界访问就会产生以下几个问题:

Pod 漂移问题:

  Kubernetes 具有强大的副本控制能力,能保证在任意副本(Pod)挂掉时自动从其他机器启动一个新的,还可以动态扩容等,通俗地说,这个 Pod 可能在任何时刻出现在任何节点上,也可能在任何时刻死在任何节点上;那么自然随着 Pod 的创建和销毁,Pod IP 肯定会动态变化;那么如何把这个动态的 Pod IP 暴露出去?这里借助于 Kubernetes 的 Service 机制,Service 可以以标签的形式选定一组带有指定标签的 Pod,并监控和自动负载他们的 Pod IP,那么我们向外暴露只暴露 Service IP 就行了;这就是 NodePort 模式:即在每个节点上开起一个端口,然后转发到内部 Pod IP 上,如下图所示:

ingress-1

端口管理问题:

  采用 NodePort 方式暴露服务面临问题是,服务一旦多起来,NodePort 在每个节点上开启的端口会及其庞大,而且难以维护。这时,我们可以使用一个Nginx直接对内进行转发呢?众所周知的是,Pod与Pod之间是可以互相通信的,而Pod是可以共享宿主机的网络名称空间的,也就是说当在共享网络名称空间时,Pod上所监听的就是Node上的端口。那么这又该如何实现呢?简单的实现就是使用 DaemonSet 在每个 Node 上监听 80,然后写好规则,因为 Nginx 外面绑定了宿主机 80 端口(就像 NodePort),本身又在集群内,那么向后直接转发到相应 Service IP 就行了,如下图所示:

ingress-2

域名分配及动态更新问题:

  从上面的方法,采用 Nginx-Pod 似乎已经解决了问题,但是其实这里面有一个很大缺陷:当每次有新服务加入又该如何修改 Nginx 配置呢??我们知道使用Nginx可以通过虚拟主机域名进行区分不同的服务,而每个服务通过upstream进行定义不同的负载均衡池,再加上location进行负载均衡的反向代理,在日常使用中只需要修改nginx.conf即可实现,那在K8S中又该如何实现这种方式的调度呢???

  假设后端的服务初始服务只有ecshop,后面增加了bbs和member服务,那么又该如何将这2个服务加入到Nginx-Pod进行调度呢?总不能每次手动改或者Rolling Update 前端 Nginx Pod 吧!!此时 Ingress 出现了,如果不算上面的Nginx,Ingress 包含两大组件:Ingress Controller 和 Ingress。

ingress-3

  Ingress 简单的理解就是你原来需要改 Nginx 配置,然后配置各种域名对应哪个 Service,现在把这个动作抽象出来,变成一个 Ingress 对象,你可以用 yaml 创建,每次不要去改 Nginx 了,直接改 yaml 然后创建/更新就行了;那么问题来了:”Nginx 该怎么处理?”

  Ingress Controller 这东西就是解决 “Nginx 的处理方式” 的;Ingress Controoler 通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,然后读取他,按照他自己模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下,工作流程如下图:

ingress-4

Ingress Controller工作架构如下,借用traefik官方的图:

ingress-5
  你可以将api.domain.com进来的流量路由到集群里api的pod上,你可以将backoffice.domain.com流量路由到backoffice的一组pod上(注意:ingress nginx是怎么识别那些Pod是那个应该加入那个upstream呢?这里就用到了service使用service的labels将一组服务关联起来,虽然创建了service但是ingress controller在解析service的是时候实际是解析到后面的Pod上。)

ingress-6

看起来按照nginx来理解转发是client——>nginx——>svc——>pod;
实际上转发是client—–>nginx——>pod,是直接负载到svc后面的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
#例如上面trafik图里的Ingress大致就是下面这样:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
rules:
- host: api.mydomain.com
http:
paths:
- backend:
serviceName: api
servicePort: 80
- host: domain.com
http:
paths:
- path: /web/*
backend:
serviceName: web
servicePort: 8080
- host: backoffice.domain.com
http:
paths:
- backend:
serviceName: backoffice
servicePort: 8080

#只要创建了上面的Ingress后,ingress controller里会监听到从而生成对应的配置段后动态reload配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#另外ingress也能多路径,如下:

spec:
rules:
- host: xxxx.xxxx.xxx
http:
paths:
- backend:
serviceName: service-index
servicePort: 80
path: /
- backend:
serviceName: service-test-api
servicePort: 80
path: /api/

部署Ingress Nginx:

使用Ingress功能步骤:(注意先后顺序,如果先执行了mandatory.yaml文件在执行service-nodeport.yaml文件使用kubectl logs -f 看ingress-controller的pod的日志会有很多报错信息)

  • 下载Ingress-controller相关的YAML文件,并给Ingress-controller创建独立的名称空间命名为ingress-nginx;
  • 创建Ingress-controller的service,以实现接入集群外部流量;
  • 部署Ingress-controller;
  • 部署后端的服务,如tomcat,并通过service进行暴露;
  • 部署Ingress,进行定义规则,使Ingress-controller和后端服务的Pod组进行关联。

1、创建名称空间:

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

2、创建Ingress-controller的service(使用Nodeport的方式实现的接入集群外部流量)

下载YAML文件:

1
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.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
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
nodePort: 30080
- name: https
port: 443
targetPort: 443
protocol: TCP
nodePort: 30443
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx


---


4、执行部署Ingress Nginx YAML文件

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

5、创建后端服务

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
#创建后端服务的service

kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # tells deployment to run 2 pods matching the template
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80

---
#创建后端服务的pod

kind: Service
apiVersion: v1
metadata:
name: nginx-service
namespace: default
spec:
type: ClusterIP
ports:
- protocol: TCP
port: 8080
targetPort: 80
selector:
app: nginx

如何创建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
27
28
29
30
31
32
33
34
35

[root@k8s-master ~]# kubectl explain ingress
KIND: Ingress
VERSION: extensions/v1beta1

DESCRIPTION:
Ingress is a collection of rules that allow inbound connections to reach
the endpoints defined by a backend. An Ingress can be configured to give
services externally-reachable urls, load balance traffic, terminate SSL,
offer name based virtual hosting etc.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

spec <Object>
Spec is the desired state of the Ingress. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

status <Object>
Status is the current state of the Ingress. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
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
[root@k8s-master ingress-nginx]# kubectl explain ingress.spec
KIND: Ingress
VERSION: extensions/v1beta1

RESOURCE: spec <Object>

DESCRIPTION:
Spec is the desired state of the Ingress. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

IngressSpec describes the Ingress the user wishes to exist.

FIELDS:
backend <Object> #定义后端有哪几个主机
A default backend capable of servicing requests that don't match any rule.
At least one of 'backend' or 'rules' must be specified. This field is
optional to allow the loadbalancer controller or defaulting logic to
specify a global default.

rules <[]Object> #定义规则
A list of host rules used to configure the Ingress. If unspecified, or no
rule matches, all traffic is sent to the default backend.

tls <[]Object> #HTTPS服务
TLS configuration. Currently the Ingress only supports a single TLS port,
1. If multiple members of this list specify different hosts, they will be
multiplexed on the same port according to the hostname specified through
the SNI TLS extension, if the ingress controller fulfilling the ingress
supports SNI.

编写ingress的配置清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master ingress]# vim ingress-myapp.yaml
apiVersion: extensions/v1beta1 #api版本
kind: Ingress #清单类型
metadata: #元数据
name: ingress-myapp #ingress的名称
namespace: default #所属名称空间
annotations: #注解信息
kubernetes.io/ingress.class: "nginx"
spec: #规格
rules: #定义后端转发的规则
- host: nginx.testdomain.com #通过域名进行转发
http:
paths:
- path: #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
backend: #配置后端服务
serviceName: nginx-service
servicePort: 8080

找到ingress nginx的pod名字后通过命令查看里面nginx配置文件能找到有对应的配置段生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ kubectl -n ingress-nginx exec nginx-ingress-controller-6cdcfd8ff9-t5sxl -- cat /etc/nginx/nginx.conf
...
## start server nginx.testdomain.com
server {
server_name nginx.testdomain.com ;

listen 80;

set $proxy_upstream_name "-";

location / {

set $namespace "default";
set $ingress_name "nginx-ingress";
set $service_name "nginx";
set $service_port "8080";
set $location_path "/";
........
## end server nginx.testdomain.com
...

找一台非集群的机器,设置hosts文件把域名nginx.testdomain.com设置到对svc的那个node的ip上,打开浏览器访问nginx.testdomain.com即可发现集群内的nginx已经暴露在集群外。

配置域名解析(Node节点IP)

1
2
192.168.91.22 nginx.testdomain.com
192.168.91.22 tomcat.testdomain.com

多个Ingress controllers:

如果您的环境中运行多个Ingress controllers,则需要kubernetes.io/ingress.class: "nginx"指定将ingress对象加入到那个Ingress controllers中

例如:

1
2
3
4
metadata:
name: foo
annotations:
kubernetes.io/ingress.class: "gce"

将以GCE控制器作为目标,迫使它忽略nginx控制器。

1
2
3
4
metadata:
name: foo
annotations:
kubernetes.io/ingress.class: "nginx"

将以nginx控制器作为目标,迫使它忽略GCE控制器。

Rewrite(重写):

必须条件:当您集群中有多个Ingress controller时您必须通过指定Ingress.class注释来确保您的Ingress只针对一个Ingress controller生效,并且您的集群中必须运行着这个Ingress controller。

ingress可以使用以下annotations标签控制重写:

名称 描述
nginx.ingress.kubernetes.io/rewrite-target 必须重定向流量的目标URI string
nginx.ingress.kubernetes.io/ssl-redirect 指示位置部分是否仅可访问SSL(当Ingress包含证书时默认为True) bool
nginx.ingress.kubernetes.io/force-ssl-redirect 即使Ingress未启用TLS,也强制重定向到HTTPS bool
nginx.ingress.kubernetes.io/app-root 定义Controller必须重定向的应用程序根,如果它在’/‘上下文中 string
nginx.ingress.kubernetes.io/use-regex 指示Ingress上定义的路径是否使用正则表达式 bool

配置无host字段ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: andmu-open-web
namespace: sh-andmuopen
annotations:
nginx.ingress.kubernetes.io/server-alias: "open.andmu.cn"
kubernetes.io/ingress.class: "sh-andmuopen"
nginx.ingress.kubernetes.io/use-regex: "true" #支持nginx的rui的正则匹配
spec:
rules:
- http:
paths:
- path: /(.*)
backend:
serviceName: andmuopen-web
servicePort: 80
---

在配置中我们没有host字段,这将在ingress-nginx配置中会以如下形式展示,此种方式我们可以用IP或域名的方式访问,因为我们还配置了nginx.ingress.kubernetes.io/server-alias: "open.andmu.cn",所以当我们用域名访问时用open.andmu.cn访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
## start server _
server {
server_name _ open.andmu.cn;

listen 80 default_server reuseport backlog=65535;

listen [::]:80 default_server reuseport backlog=65535;

set $proxy_upstream_name "-";
set $pass_access_scheme $scheme;
set $pass_server_port $server_port;
set $best_http_host $http_host;
set $pass_port $pass_server_port;

构建TLS站点:(注:必须是标准的证书格式不能key+server+ca放在一个pem文件中)

(1)准备证书

1
2
3
4
5
[root@k8s-master ingress]# openssl genrsa -out tls.key 2048 
Generating RSA private key, 2048 bit long modulus
.......+++
.......................+++
e is 65537 (0x10001)
1
[root@k8s-master ingress]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=tomcat.magedu.com

(2)生成secret

1
2
[root@k8s-master ingress]# kubectl create secret tls tomcat-ingress-secret --cert=tls.crt --key=tls.key
secret/tomcat-ingress-secret created
1
2
3
4
[root@k8s-master ingress]# kubectl get secret
NAME TYPE DATA AGE
default-token-j5pf5 kubernetes.io/service-account-token 3 39d
tomcat-ingress-secret kubernetes.io/tls 2 9s
1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master ingress]# kubectl describe secret tomcat-ingress-secret
Name: tomcat-ingress-secret
Namespace: default
Labels: <none>
Annotations: <none>

Type: kubernetes.io/tls

Data
====
tls.crt: 1294 bytes
tls.key: 1679 bytes

(3)创建ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master ingress]# cp ingress-tomcat.yaml ingress-tomcat-tls.yaml
[root@k8s-master ingress]# vim ingress-tomcat-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-tomcat-tls
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- tomcat.magedu.com
secretName: tomcat-ingress-secret
rules:
- host: tomcat.magedu.com
http:
paths:
- path:
backend:
serviceName: tomcat
servicePort: 8080
1
2
[root@k8s-master ingress]# kubectl apply -f ingress-tomcat-tls.yaml 
ingress.extensions/ingress-tomcat-tls created
1
2
3
4
5
[root@k8s-master ingress]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
ingress-myapp myapp.magedu.com 80 4h
ingress-tomcat-tls tomcat.magedu.com 80, 443 5s
tomcat tomcat.magedu.com 80 1h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master ingress]# kubectl describe ingress ingress-tomcat-tls
Name: ingress-tomcat-tls
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
TLS:
tomcat-ingress-secret terminates tomcat.magedu.com
Rules:
Host Path Backends
---- ---- --------
tomcat.magedu.com
tomcat:8080 (<none>)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-tomcat-tls","namespace":"default"},"spec":{"rules":[{"host":"tomcat.magedu.com","http":{"paths":[{"backend":{"serviceName":"tomcat","servicePort":8080},"path":null}]}}],"tls":[{"hosts":["tomcat.magedu.com"],"secretName":"tomcat-ingress-secret"}]}}

kubernetes.io/ingress.class: nginx
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 20s nginx-ingress-controller Ingress default/ingress-tomcat-tls

(4)访问测试:https://tomcat.magedu.com:30443

ingress-7

Ingress Controller高可用:

&emsp;&emsp;这里来讨论下Ingress Controller的Nginx pod高可用。上面的例子里svc我使用的nodePort,Nodeport端口不是web端口(但是可以修改Nodeport的范围改成web端口),如果当流量进来负载到某个node上的时候因为Ingress Controller的pod不在这个node上,会走这个node的kube-proxy转发到Ingress Controller的pod上,多走一趟路。(例如:我们Ingress Controller的pod在192.168.91.22上,但是你域名解析的是192.168.91.21 nginx.testdomain.com这样,这个时候192.168.91.21这个node上的kube-proxy会把请求转发到192.168.91.22上)。故建议使用daemonset+nodeSelector的方式来ingress controller的Pod负载(每个Node节点一个ingress controller的Pod)。

&emsp;&emsp;不创建svc,效率最高。如果我们使用Nodeport的方式,流量是NodeIP——->svc—-ingress-controller(pod)这样的话会多走一层svc层,不管svc层是使用iptables还是lvs都会降低效率。如果使用hostNetwork的方式就是直接走Node节点的主机网络,唯一要注意的是hostNetwork下pod会继承宿主机的网络协议,也就是使用了主机的dns,会导致svc的请求直接走宿主机的上到公网的dns服务器而非集群里的dns server,需要设置pod的dnsPolicy: ClusterFirstWithHostNet即可解决

Ingress Controller部署方式没多大区别开心就好:

  • daemonSet + nodeSeletor (一个Node节点运行一个Ingress Controller的Pod,当有多个Node的时候可以使用污点或者通过指定nodeSelector来指定部分主机来运行Ingress Controller的Pod)
  • deploy的方式部署多个ingress controller:设置replicas数量(不能大于node节点数) + nodeSeletor + pod互斥

1.1、使用 daemonSet + nodeSeletor 方式部署

&emsp;&emsp;首先我们要创建一个ingress-nginx的svc不然ingress nginx的log里会一直刷找不到ingress-nginx的svc不处理的话会狂刷log导致机器load过高,创建一个同名的svc即可解决,例如创建一个不带选择器clusterip为None的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: ClusterIP
clusterIP: "None"
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: https
port: 443
targetPort: 443
protocol: TCP
---
1
2
3
[root@k8s-m1 ~]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx ClusterIP None <none> 80/TCP,443/TCP 14h

1.2、修改ingress-controller mandatory.yaml文件

1
2
3
4
5
6
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
1
2
3
4
5
6
7
8
9
10
11
12
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: nginx-ingress-serviceaccount
1
2
3
4
[root@k8s-m1 ~]# kubectl  get pod -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ingress-controller-m64rj 1/1 Running 0 19m 10.15.1.6 k8s-n1 <none> <none>
nginx-ingress-controller-w5ts8 1/1 Running 0 19m 10.15.1.7 k8s-n2 <none> <none>

&emsp;&emsp;修改部署方式为DaemonSet以及删除replicas副本集参数,新增 hostNetwork: truednsPolicy: ClusterFirstWithHostNet参数。我们可以看到Pod的IP就直接是Node节点的IP。


Kubernetes之Ingress
https://system51.github.io/2019/08/26/Kubernetes-Ingress/
作者
Mr.Ye
发布于
2019年8月26日
许可协议