1.Nginx Ingress简介
在Kubernetes集群中,Ingress作为集群内服务对外暴露的访问接入点,其几乎承载着集群内服务访问的所有流量。Ingress是Kubernetes中的一个资源对象,用来管理集群外部访问集群内部服务的方式。您可以通过Ingress资源来配置不同的转发规则,从而达到根据不同的规则设置访问集群内不同的Service所对应的后端Pod
2.Nginx Ingress Controller 工作原理
为了使得Nginx Ingress资源正常工作,集群中必须要有个Nginx Ingress Controller来解析Nginx Ingress的转发规则。Nginx Ingress Controller收到请求,匹配Nginx Ingress转发规则转发到后端Service所对应的Pod,由Pod处理请求。Kubernetes中Service、Nginx Ingress与Nginx Ingress Controller有着以下关系:
- Service是后端真实服务的抽象,一个Service可以代表多个相同的后端服务。
- Nginx Ingress是反向代理规则,用来规定HTTP/HTTPS请求应该被转发到哪个Service所对应的Pod上。例如根据请求中不同的Host和URL路径,让请求落到不同Service所对应的Pod上。
- Nginx Ingress Controller是一个反向代理程序,负责解析Nginx Ingress的反向代理规则。如果Nginx Ingress有增删改的变动,Nginx Ingress Controller会及时更新自己相应的转发规则,当Nginx Ingress Controller收到请求后就会根据这些规则将请求转发到对应Service的Pod上。
Nginx Ingress Controller通过API Server获取Ingress资源的变化,动态地生成Load Balancer(例如Nginx)所需的配置文件(例如nginx.conf),然后重新加载Load Balancer(例如执行nginx -s load重新加载Nginx)来生成新的路由转发规则。
ngress-nginx控制器主要是用来组装一个 nginx.conf的配置文件,当配置文件发生任何变动的时候就需要重新加载 Nginx 来生效,但是并不会只在影响 upstream配置的变更后就重新加载 Nginx,控制器内部会使用一个 lua-nginx-module来实现该功能。
我们知道 Kubernetes 控制器使用控制循环模式来检查控制器中所需的状态是否已更新或是否需要变更,所以 ingress-nginx需要使用集群中的不同对象来构建模型,比如 Ingress、Service、Endpoints、Secret、ConfigMap 等可以生成反映集群状态的配置文件的对象,控制器需要一直 Watch 这些资源对象的变化,但是并没有办法知道特定的更改是否会影响到最终生成的 nginx.conf配置文件,所以一旦 Watch 到了任何变化控制器都必须根据集群的状态重建一个新的模型,并将其与当前的模型进行比较,如果模型相同则就可以避免生成新的 Nginx 配置并触发重新加载,否则还需要检查模型的差异是否只和端点有关,如果是这样,则然后需要使用 HTTP POST 请求将新的端点列表发送到在 Nginx 内运行的 Lua 处理程序,并再次避免生成新的 Nginx 配置并触发重新加载,如果运行和新模型之间的差异不仅仅是端点,那么就会基于新模型创建一个新的 Nginx 配置了,这样构建模型最大的一个好处就是在状态没有变化时避免不必要的重新加载,可以节省大量 Nginx 重新加载。
下面简单描述了需要重新加载的一些场景:
- 创建了新的 Ingress 资源
- TLS 添加到现有 Ingress
- 从 Ingress 中添加或删除 path 路径
- Ingress、Service、Secret 被删除了
- Ingress 的一些缺失引用对象变可用了,例如 Service 或 Secret
- 更新了一个 Secret
对于集群规模较大的场景下频繁的对 Nginx 进行重新加载显然会造成大量的性能消耗,所以要尽可能减少出现重新加载的场景
3.添加 ingress-nginx 官方 helm 仓库
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
4.下载 chart 包
# 查找所有的版本
helm search repo ingress-nginx/ingress-nginx -l
# 下载
$ helm fetch ingress-nginx/ingress-nginx --version 4.7.1
# 解压缩
$ tar -zxvf ingress-nginx-4.7.1.tgz
$ cd ingress-nginx
5.修改 values.yaml 文件
5.1 修改 ingress-nginx-contorller,注释掉 digest
官方提供的镜像无法拉取,改成阿里云镜像
5.2 修改 hostNetwork 的值为 true
5.3 修改 dnsPolicy 的值为 ClusterFirstWithHostNet
5.4 nodeSelector 添加标签: ingress: "true",用于部署 ingress-controller 到指定节点
5.5 修改 kind 类型为 DaemonSet
5.6 修改 publishService 为false
publishService: # hostNetwork 模式下设置为false,通过节点IP地址上报ingress status数据
enabled: false
5.7 修改 watchIngressWithoutClass 为false
# 是否需要处理不带 ingressClass 注解或者 ingressClassName 属性的 Ingress 对象
# 设置为 true 会在控制器启动参数中新增一个 --watch-ingress-without-class 标注
watchIngressWithoutClass: false
5.8 修改 service 为false
service: # HostNetwork 模式不需要创建service
enabled: false
5.9 修改 kube-webhook-certgen 的镜像地址为国内仓库
registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v1.5.1
5.10 修改 service 类型为 NodePort
6.安装ingress-nginx
# 创建命名空间
kubectl create ns ingress-nginx
# helm安装
helm install ingress-nginx -n ingress-nginx .
NAME: ingress-nginx
LAST DEPLOYED: Thu Nov 24 17:12:22 2022
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status by running 'kubectl --namespace ingress-nginx get services -o wide -w ingress-nginx-controller'
An example Ingress that makes use of the controller:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example
namespace: foo
spec:
ingressClassName: nginx
rules:
- host: www.example.com
http:
paths:
- pathType: Prefix
backend:
service:
name: exampleService
port:
number: 80
path: /
# This section is only required if TLS is to be enabled for the Ingress
tls:
- hosts:
- www.example.com
secretName: example-tls
If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:
apiVersion: v1
kind: Secret
metadata:
name: example-tls
namespace: foo
data:
tls.crt: <base64 encoded cert>
tls.key: <base64 encoded key>
type: kubernetes.io/tls
安装完成后,需要给节点打上刚刚设置的标签ingress=true
,让 Pod 调度到指定的节点
# 查看节点
kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master231 Ready control-plane,master 10d v1.21.10
k8s-master232 Ready control-plane,master 10d v1.21.10
k8s-node233 Ready <none> 10d v1.21.10
k8s-node234 Ready <none> 10d v1.21.10
k8s-node235 Ready <none> 10d v1.21.10
k8s-node236 Ready <none> 9d v1.21.10
# 设置标签
kubectl label node k8s-node233 ingress=true
kubectl label node k8s-node234 ingress=true
kubectl label node k8s-node235 ingress=true
kubectl label node k8s-node236 ingress=true
执行完成之后,就可以看到 ingress-nginx 部署到节点
kubectl get all -n ingress-nginx
NAME READY STATUS RESTARTS AGE
pod/ingress-nginx-controller-72p5z 1/1 Running 0 25m
pod/ingress-nginx-controller-lxt5g 1/1 Running 0 23m
pod/ingress-nginx-controller-ndnb7 1/1 Running 0 25m
pod/ingress-nginx-controller-w5gkp 1/1 Running 0 23m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ingress-nginx-controller LoadBalancer 10.97.111.98 <pending> 80:31595/TCP,443:32140/TCP 25m
service/ingress-nginx-controller-admission ClusterIP 10.108.103.137 <none> 443/TCP 25m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/ingress-nginx-controller 4 4 4 4 4 ingress=true,kubernetes.io/os=linux 25m
helm upgrade ingress-nginx -n ingress-nginx .
7.示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
app: my-nginx
template:
metadata:
labels:
app: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
app: my-nginx
spec:
ports:
- port: 80
protocol: TCP
name: http
selector:
app: my-nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-nginx
namespace: default
spec:
ingressClassName: nginx # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器)
rules:
- host: k8s.minio.cn # 将域名映射到 my-nginx 服务
http:
paths:
- path: /
pathType: Prefix
backend:
service: # 将所有请求发送到 my-nginx 服务的 80 端口
name: my-nginx
port:
number: 80
在主机中 /etc/hosts 添加域名DNS,然后在主机中测试:(或者在浏览器上访问liehooo.k8s.minio.cn)
curl k8s.minio.cn
8.为ingress-nginx配置Basic Auth认证
1.用htpasswd生成一个密码文件(用户名:foo 密码:foo)
htpasswd -c auth foo
2.k8s生成secret
kubectl create secret generic basic-auth --from-file=auth
3.在ingress中添加secret信息
annotations:
nginx.ingress.kubernetes.io/auth-type: basic # 认证类型
nginx.ingress.kubernetes.io/auth-secret: basic-auth # 包含 user/password 定义的 secret 对象名
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo' # 要显示的带有适当上下文的消息,说明需要身份验证的原因
在浏览器中再次访问 k8s.minio.cn ,此时会跳出弹窗,输入正确的用户名密码就可以访问.
9.为ingress-nginx配置https
1.创建一个自签名的证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout k8s.minio.cn.key -out k8s.minio.cn.crt -subj "/CN=k8s.minio.cn"
2.通过 Secret 对象来引用证书文件
kubectl create secret tls foo-tls --cert=k8s.minio.cn.crt --key=k8s.minio.cn.key
3.在ingress中配置证书
spec:
tls: # 配置 tls 证书
- hosts:
- k8s.minio.cn
secretName: foo-tls
在浏览器中再次访问 k8s.minio.cn
10.URL Rewrite
ingress-nginx很多高级的用法可以通过 Ingress 对象的 annotation进行配置,比如常用的 URL Rewrite 功能。很多时候我们会将 ingress-nginx当成网关使用,比如对访问的服务加上 /app这样的前缀,在 nginx的配置里面我们知道有一个 proxy_pass指令可以实现:
location /app/ {
proxy_pass http://127.0.0.1/remote/;
}
proxy_pass后面加了 /remote这个路径,此时会将匹配到该规则路径中的 /app用 /remote替换掉,相当于截掉路径中的 /app。同样的在 Kubernetes 中使用 ingress-nginx又该如何来实现呢?我们可以使用 rewrite-target的注解来实现这个需求,比如现在我们想要通过 rewrite.qikqiak.com/gateway/来访问到 Nginx 服务,则我们需要对访问的 URL 路径做一个 Rewrite,在 PATH 中添加一个 gateway 的前缀.
按照要求我们需要在 path中匹配前缀 gateway,然后通过 rewrite-target指定目标,Ingress 对象如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rewrite
annotations:
nginx.ingress.kubernetes.io/app-root: /gateway/
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/configuration-snippet: |
rewrite ^(/gateway)$ $1/ redirect;
spec:
ingressClassName: nginx
rules:
- host: k8s.minio.cn
http:
paths:
- path: /gateway(/|$)(.*)
pathType: Prefix
backend:
service:
name: my-nginx
port:
number: 80
更新后,我们可以预见到直接访问域名肯定是不行了,因为我们没有匹配 / 的 path 路径,但是我们带上 /gateway 的前缀再去访问就可以正常访问。
要解决我们访问主域名出现 404 的问题,我们可以给应用设置一个 app-root的注解,这样当我们访问主域名的时候会自动跳转到我们指定的 app-root。这个时候我们更新应用后访问主域名 k8s.minio.cn就会自动跳转到 k8s.minio.cn/gateway/路径下面去了。
但是还有一个问题是我们的 path 路径其实也匹配了 /app这样的路径,可能我们更加希望我们的应用在最后添加一个 /这样的 slash,同样我们可以通过 configuration-snippet配置来完成