12.1 Ingress 简介
Ingress 不仅可以让外部客户端访问服务,还可以自定义服务的访问策略。在前面介绍的 LoadBalancer Service 中,我们知道每一个服务都需要定义一个负载均衡器和唯一的共有 IP 地址,如果一个应用可以提供多种服务,那么显然这种方式是不够友好的。而 Ingress 只需要一个公网 IP 地址就可以为多个服务提供自定义的访问。因此,Ingress 是一种非常灵活、得到越来越多厂商支持的集群服务暴露方式,在实际的生产环境中使用得最多,而其他的服务暴露方式更多地适用于服务调试、特殊应用部署等方面。
Ingress 和 Ingress Controller
在 Kubernetes 集群中,Ingress 只是一个统称,具体而言由两部分组成:
- Ingress:Ingress 资源对象,使用 YAML 文件定义负载均衡的策略(规则)
- Ingress Controller:控制器,以插件形式存在于集群中,从 apiServer 中获取 Ingress 的规则变化
在集群中,需要先部署 Ingress Controller,再创建 Ingress 资源对象。Ingress Controller 控制器是一个 docker 容器,容器镜像中包含一个负载均衡器(比如:Nginx 或是 HAProxy)和一个 Ingress Controller。
整个底层的实现是这样的:Ingress Controller 从 Kubernetes apiServer 中(实际上是监控 apiServer 的 /ingress 接口后端的 backend services)获取 Ingress 的配置信息,然后动态生成一个 Nginx 或 HAProxy 配置文件,重启负载均衡器进程使配置生效。所以,虽然 Ingress Controller 是以插件形式集成在环境中,但依然是由 Kubernetes 管理的负载均衡器。
Ingress Controller 容器中的负载均衡器可以是各种主流的负载均衡软件,比如:Nginx、HAProxy、Traefik 等。不管具体的软件类型是什么,官方都统称为 Ingress Controller。
当集群使用 Ingress 进行负载分发时,Ingress Controller 基于 Ingress 规则将客户端请求直接转发到 Service 对应的后端 Endpoint(Pod) 上,所以会跳过 kube-proxy 的转发功能,kube-proxy 在这里并不会起作用。这种方式比 LoadBalancer 更加高效。
部署 nginx-ingress-controller
在 Kubernetes 集群中,Ingress Controller 是以 Pod 形式运行的。根据负载均衡器的不同出现了多种 Ingress Controller,比如:
- Kubernetes Ingress Controller: Kubernetes 官方推荐的控制器,由社区开发,最适合新手。
- NGINX Ingress Controller: Nginx 公司开发的官方产品。有一个基于 NGINX Plus 的商业版。
- Traefik: 为部署微服务更加便捷而诞生的现代 HTTP 反向代理、负载均衡工具。支持多种后台来自动化、动态的应用它的配置文件。
- HAProxy Ingress: 这也是在传统配置时应用非常广泛的负载均衡器。它提供了“软”配置更新(无流量损失)、基于 DNS 的服务发现和通过 API 进行动态配置。
本次实验中将会使用官方推荐的控制器 Kubernetes Ingress Controller,它的创建其实也比较简单,直接使用 YAML 文件创建即可。
需要使用 nginx-ingress-controller.yaml 文件和 ingress-service-nodeport.yaml 文件,文档结尾附内容。
nginx-ingress-controller.yaml 文件中,创建了名为 ingress-nginx 的命名空间(没有使用 kube-system 系统默认空间),并在该命名空间下分别创建了名为 nginx-configuration、tcp-services、udp-services 的 ConfigMap,创建了相关的服务账户、集群角色、集群角色绑定,最后使用 Deployment 部署一个名为 nginx-ingress-controller-xxx 的 Pod 副本,这个 Pod 就是 Ingress Controller。注意在 Deployment.spec.template.spec 中设置 hostNetwork: true,将 Pod 中所有容器的端口号都映射到 Node 节点上,因为是在本机进行测试,直接使用 NodePort:containerPort 就可以直接访问该控制器。同时由于国外的镜像无法下载,这里使用阿里云提供的镜像 registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1,这个容器有两个端口:80 端口提供 http 访问,443 端口提供 https 访问。
nginx-ingress-controller:0.26.1 镜像实现 Ingress Controller 的基本逻辑为:监听 APIServer,获取在 Ingress 中定义的配置信息;基于 Ingress 配置信息,生成 Nginx 所需的配置文件 /etc/nginx/nginx.conf;执行 nginx -s reload 命令重新加载配置文件内容。
ingress-service-nodeport.yaml 文件中,为 Ingress Controller Pod 创建 NodePort 类型的 Service,接收集群外部的流量。
先创建 nginx-ingress-controller Pod:
$ kubectl create -f nginx-ingress-controller.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
deployment.apps/nginx-ingress-controller created
然后为 nginx-ingress-controller Pod 创建 NodePort 类型的 Service:
$ kubectl create -f ingress-service-nodeport.yaml
service/ingress-nginx created
查看 ingress-nginx 命令空间下所有创建好的资源对象:
$ kubectl get all -o wide -n ingress-nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-ingress-controller-5cd8ff9d6f-ggsd5 1/1 Running 0 85s 192.168.0.33 kubesphere03 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/ingress-nginx NodePort 10.96.141.128 <none> 80:32653/TCP,443:32657/TCP 33s app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/nginx-ingress-controller 1/1 1 1 85s nginx-ingress-controller registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1 app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/nginx-ingress-controller-5cd8ff9d6f 1 1 1 85s nginx-ingress-controller registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1 app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx,pod-template-hash=5cd8ff9d6f
部署一个简单的 Nginx 实例
接下来部署一个 Nginx 服务,通过 Ingress 对外暴露 Nginx Service 进行访问,方便大家熟悉 Ingress 的使用流程。
首先使用 Deployment 创建 2 个 Nginx Pod 副本,指定容器端口为 80 端口;然后创建 Nginx Cluster Service,服务端口依然为 80 端口。新建 nginx.yaml 文件并向其中写入如下代码:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: ingress-nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: ingress-nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
执行创建:
$ kubectl create -f nginx.yaml
deployment.apps/nginx-deployment created
service/nginx created
设置 ingress 策略,将服务的域名命名为 www.example.com,将客户端对于根路径的访问转发给后端的 nginx Service 进行响应。新建 ingress.yaml 文件,并向其中写入如下代码:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
rules:
- host: www.example.com # 对外访问的域名为 www.example.com
http:
paths:
- path: / # 转发路径为根路径 /
backend:
serviceName: nginx # 对外暴露 Service 的名称为 nginx
servicePort: 80 # nginx Service 的 ClusterPort 为 80
当外部客户端访问地址 http://www.example.com 时,kubernetes 集群内部会将这个访问请求转发给地址 nginx:80。需要注意的是:创建的 Ingress 必须和提供服务的 Service 处于同一命名空间下。
执行创建:
$ kubectl create -f ingress.yaml
ingress.extensions/ingress-nginx created
查看创建的 ingress:
$ kubectl get ingress -n ingress-nginx
NAME HOSTS ADDRESS PORTS AGE
ingress-nginx www.example.com 80 41s
# 验证 ingress-nginx CLUSTER-IP
$ kubectl get service -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx NodePort 10.96.141.128 <none> 80:32653/TCP,443:32657/TCP 43m
可以看到这里的 ADDRESS 为 10.96.141.128,也就是 ingress-nginx Service 的 ClusterIP。这说明 Nginx 设置好了后端服务的 Endpoint,Ingress Controller 中的转发规则已经配置好。如果 ADDRESS 中没有地址,则表示没有配置好。
登录 nginx-ingress-controller Pod,查看 /etc/nginx/nginx.conf 文件是否正确配置转发规则:
$ kubectl exec -n ingress-nginx -it nginx-ingress-controller-5cd8ff9d6f-ggsd5 sh
$ cat nginx.conf
...
## start server www.example.com
server {
server_name www.example.com ;
listen 80 ;
listen [::]:80 ;
listen 443 ssl http2 ;
listen [::]:443 ssl http2 ;
set $proxy_upstream_name "-";
ssl_certificate_by_lua_block {
certificate.call()
}
location / {
set $namespace "ingress-nginx";
set $ingress_name "ingress-nginx";
set $service_name "nginx";
set $service_port "80";
set $location_path "/";
...
在客户端配置静态解析,公网 IP 是 Ingress Controll Service 的公网 IP(在这里即 NodeIp),域名是 ingress 的 host(在这里即 www.example.com)。
$ echo "192.168.0.33 www.example.com" >> /etc/hosts
$ ping www.example.com
PING www.example.com (192.168.0.33) 56(84) bytes of data.
64 bytes from kubesphere03 (192.168.0.33): icmp_seq=1 ttl=64 time=0.224 ms
64 bytes from kubesphere03 (192.168.0.33): icmp_seq=2 ttl=64 time=0.210 ms
^C
$ curl www.example.com
<!DOCTYPE html>
<html>
...
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</body>
</html>
两个文件内容:
# nginx-ingress-controller.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: tcp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
kind: ConfigMap
apiVersion: v1
metadata:
name: udp-services
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: nginx-ingress-clusterrole
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
- "networking.k8s.io"
resources:
- ingresses/status
verbs:
- update
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: nginx-ingress-role
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: nginx-ingress-role
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: nginx-ingress-clusterrole
subjects:
- kind: ServiceAccount
name: nginx-ingress-serviceaccount
namespace: ingress-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
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:
# wait up to five minutes for the drain of connections
terminationGracePeriodSeconds: 300
hostNetwork: true
serviceAccountName: nginx-ingress-serviceaccount
nodeSelector:
kubernetes.io/os: linux
containers:
- name: nginx-ingress-controller
image: registry.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
args:
- /nginx-ingress-controller
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
- --udp-services-configmap=$(POD_NAMESPACE)/udp-services
- --publish-service=$(POD_NAMESPACE)/ingress-nginx
- --annotations-prefix=nginx.ingress.kubernetes.io
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
# www-data -> 33
runAsUser: 33
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 10
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
---
# ingress-service-nodeport.yaml
kind: Service
apiVersion: v1
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
- name: https
port: 443
targetPort: https
protocol: TCP
externalTrafficPolicy: Cluster #有两个可用选项:Cluster(默认)和 Local。 Cluster 隐藏了客户端源 IP,可能导致第二跳到另一个节点,但具有良好的整体负载分布。 Local 保留客户端源 IP 并避免 LoadBalancer 和 NodePort 类型服务的第二跳, 但存在潜在的不均衡流量传播风险。
12.2 不同的 Ingress 策略配置类型
对于后端服务的访问规则全部都需要在 Ingress 中进行配置。常用的如下几种:
- 不使用域名,转发到单个后端服务
- 不使用域名,不同的 URL 路径被转发到不同的服务
- 同一域名,不同的 URL 路径被转发到不同的服务
- 不同的域名,转发到不同的服务
不使用域名,转发到单个后端服务
这种配置适用的场景是:网站不使用域名,直接通过运行 Ingress Controller 的 Node IP 地址访问后端服务。
Ingress Controller 将来自客户端的请求全部转发到后端的单一服务上,比如把对 Ingress Controller 全部的请求都转发到 nginx:80 服务上:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-nginx
spec:
serviceName: nginx # 对外暴露 Service 的名称为 nginx
servicePort: 80 # nginx Service 的 ClusterPort 为 80
需要注意的是,使用无域名的 Ingress 转发规则会默认禁用非安全 HTTP,强制使用 HTTPS 访问。
不使用域名,不同的 URL 路径被转发到不同的服务
即使不使用域名,也可以配置对于不同 URL 路径的访问请求转发到不同的服务上。
比如对
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-shiyanlou
spec:
rules:
- http:
paths:
- path: /courses
backend:
serviceName: courses
servicePort: 8080
- path: /louplus
backend:
serviceName: louplus
servicePort: 8081
同一域名,不同的 URL 路径被转发到不同的服务
不过通常情况下,还是推荐使用域名的,这样更加方便外部客户端的访问。使用域名访问网站,通过不同的路径提供不同的服务。
比如对 http://www.example.com/courses 的请求转发到后端 courses:8080 服务上;对 http://www.example.com/vip 的请求转发到后端 vip:2000 服务上:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-example
spec:
rules:
- host: www.example.com
http:
paths:
- path: /courses
backend:
serviceName: courses
servicePort: 8080
- path: /vip
backend:
serviceName: vip
servicePort: 2000
不同的域名,转发到不同的服务
同样也可以配置通过不同的域名提供不同的服务。
比如对 courses.example.com 的请求转发到后端 courses:8080 服务上;对 vip.example.com 的请求转发到后端 vip:2000 的服务上:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-example
spec:
rules:
- host: courses.example.com
http:
paths:
- backend:
serviceName: courses
servicePort: 8080
- host: vip.example.com
http:
paths:
- backend:
serviceName: vip
servicePort: 2000
12.3 配置 Ingress 处理 TLS 传输
如果想要使用 HTTPS 访问,则需要配置 Ingress 支持 TLS。
比如在 Kubernetes 集群中的 Pod 上运行的是 web 服务器,在集群内部通信使用的是 HTTP 协议。那么只需要 Ingress Controller 处理 HTTPS 连接的相关内容即可。
具体的操作步骤为:
- 创建自签名的密钥和 SSL 证书文件;
- 将证书保存到 Kubernetes 中的 Secret 资源对象上;
- 将 Secret 对象添加到 Ingress 中。
使用 OpenSSL 工具生成密钥和证书文件:
# CN=www.example.com 用于指定网站域名
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout example.com.key -out example.com.crt -subj "/CN=www.example.com"
Generating a 2048 bit RSA private key
.....................................+++
......................+++
writing new private key to 'example.com.key'
-----
$ ls example.com.*
example.com.crt example.com.key
接下来使用命令 kubectl create secret tls 创建 Secret 资源对象:
$ kubectl create secret tls www.example.com-secret --key example.com.key --cert example.com.crt
secret/www.example.com-secret created
修改前面的 ingress.yaml 文件,将其修改为如下所示:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-nginx
namespace: ingress-nginx
spec:
tls: # 在 tls 字段下进行配置
- hosts:
- www.example.com # 域名
secretName: www.example.com-secret # 对应的 secret
rules:
- host: www.example.com # 对外访问的域名为 www.example.com
http:
paths:
- path: / # 转发路径为根路径 /
backend:
serviceName: nginx # 对外暴露 Service 的名称为 nginx
servicePort: 80 # nginx Service 的 ClusterPort 为 80
更新配置信息:
$ kubectl apply -f ingress.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
ingress.extensions/ingress-nginx configured
# 查看更新后的 ingress,可以看到开放了 443 端口
$ kubectl get ingress -n ingress-nginx
NAME HOSTS ADDRESS PORTS AGE
ingress-nginx www.example.com 10.96.141.128 80, 443 8m14s
通过浏览器访问 https://www.example.com ,浏览器会提示不安全,继续访问如下:

也可以通过 curl 访问:
$ curl -k https://www.example.com
<!DOCTYPE html>
<html>
...
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
</body>
</html>
评论区