目录
一、概念介绍
介绍Nginx Ingress Controller之前,需要先理解三个概念。
- Ingress:对集群中服务的外部访问进行管理的 API 对象,可以理解为kubernetes中一种定义从集群外部到集群内服务的HTTP/HTTPS 路由的资源类型;
- Ingress Controller:负责通过负载均衡器(如nginx/haproxy)来实现 Ingress中定义的路由,通常以pod形式运行;
- IngressClass:一种定义某个具体Ingress Controller的资源类型,kubernetes 1.18-1.21中支持,Ingress通过IngressClass来指定使用哪一种Ingrss Controller。
关于Ingress的详细介绍可参看官方文档:Ingress | Kubernetes。
为什么需要使用Ingress暴露服务?Kubernetes还支持通过NodePort、LoadBalancer向集群外部暴露内部服务,与这两种方式相比,ingress可以通过单一IP为多个服务提供访问,可以通过主机名和路径将流量路由到特定服务,使得服务访问更加灵活,同时还可以用来提供https服务。
Nginx Ingress Controller即是Nginx公司使用nginx实现的一种Ingress Controller。(注意区别于kubernetes社区开发的ingress-nginx,两者均是Ingress Controller,功能略有差异,Nginx Ingress Controller额外定义了两种资源类型VirtualServer和VirtualServerRoute,实现了一些Ingress不支持的的功能。)
Nginx Ingress Controller:nginxinc/kubernetes-ingress: NGINX and NGINX Plus Ingress Controllers for Kubernetes (github.com)
Kubernetes Ingress Nginx:kubernetes/ingress-nginx: NGINX Ingress Controller for Kubernetes (github.com)
功能差异:Which Ingress Controller Do I Need? | NGINX Ingress Controller
二、安装Nginx Ingress Controller
1、添加helm仓库:
$ helm repo add nginx-stable https://helm.nginx.com/stable
$ helm repo update
2、拉取helm chart到本地,并编辑配置文件values.yaml:
$ helm pull nginx-stable/nginx-ingress
$ tar -zxvf nginx-ingress-0.10.0.tgz
# 根据实际生产环境及需要修改配置文件
$ vi nginx-ingress/values.yaml
3、使用本地修改好的chart安装nginx ingress:
$ kubectl create namespace nginx-ingress
$ helm install cmiot ./nginx-ingress -n nginx-ingress
至此即完成了Nginx Ingress Controller的安装。注意目前的最新版本,即helm chart 0.10.0、github v1.12.0版本仅支持k8s 1.21及以下版本,不支持最新的1.22版本,详情见:Kubernetes v1.22 · Issue #1832 · nginxinc/kubernetes-ingress (github.com)
下面介绍一些我们测试环境在安装第二步中修改的values.yaml的几个地方:
- controller.kind: daemonset,使用daemonset方式安装controller。
- controller.hostNetwork: true,controller的pod直接使用主机的网络接口,即直接使用主机网路接口暴露controller pod。
- controller.nodeSelector: kubernetes.io/hostname: wx-tky-ops-136,将controller pod绑定在hostname为wx-tky-ops-136的节点上;配合hostNetwork: true,可直接通过主机wx-tky-ops-136的IP地址从集群外部访问到内部服务。
- controller.service.type: ClusterIP,通过ClusterIP类型的service暴露controller,供集群内部使用。因为已经通过hostNetwork: true向集群外部暴露controller,所以此处不再需要使用NodePort/LoadBalancer。
- controller.ingressClass: nginx,定义该controller对应的IngressClass资源的名称为nginx。
安装完成后,可以看到如下一些资源:
# kubectl get po -n nginx-ingress
NAME READY STATUS RESTARTS AGE
cmiot-nginx-ingress-8585f89c5b-jbjpz 1/1 Running 0 4h15m
# kubectl get IngressClass
NAME CONTROLLER PARAMETERS AGE
nginx nginx.org/ingress-controller <none> 19h
三、使用实例
配置两个deployment和两个service:
# kubectl apply -f nginx_ingress_example.yaml
# nginx_ingress_example.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-1
labels:
app: nginx-1
spec:
replicas: 1
selector:
matchLabels:
app: nginx-1
template:
metadata:
labels:
app: nginx-1
spec:
containers:
- name: nginx
image: zhangrongjie/test-nginx-1:v1
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment-2
labels:
app: nginx-2
spec:
replicas: 1
selector:
matchLabels:
app: nginx-2
template:
metadata:
labels:
app: nginx-2
spec:
containers:
- name: nginx
image: zhangrongjie/test-nginx-2:v1
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx1-svc
labels:
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: nginx-1
---
apiVersion: v1
kind: Service
metadata:
name: nginx2-svc
labels:
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: nginx-2
访问测试:
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23h
nginx1-svc ClusterIP 10.101.144.127 <none> 80/TCP 2m34s
nginx2-svc ClusterIP 10.100.202.70 <none> 80/TCP 2m34s
# curl http://10.101.144.127/nginx1/
nginx1
# curl http://10.100.202.70/nginx2/
nginx2
创建ingress:
# kubectl apply -f test-ingress.yaml
# test-ingress.yaml
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
# 1.18及以后版本需要通过ingressClassName指定使用的controller,或者在ingressClass中设置默认controller,否则ingress不生效
ingressClassName: nginx
rules:
- host: testnginx.com
http:
paths:
- path: /nginx1/
backend:
serviceName: nginx1-svc
servicePort: 80
- path: /nginx2/
backend:
serviceName: nginx2-svc
servicePort: 80
# kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
test-ingress nginx testnginx.com 80 52m
访问测试:
查看nginx ingress controller pod所在的节点IP:
# kubectl get po -n nginx-ingress -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
cmiot-nginx-ingress-8585f89c5b-jbjpz 1/1 Running 2 8h 192.168.0.12 *.* <none> <none>
因为已经通过hostNetwork暴露该pod,所以在192.168.0.12这个node上可以看到80/443/8080端口已经开放,所以直接在本地写入相应的域名解析记录即可访问(测试时用):
# echo '192.168.0.12 testnginx.com' >> /etc/hosts
此时访问不同的路径即可路由到相应的内部服务上:
# curl http://testnginx.com/nginx1/
nginx1
# curl http://testnginx.com/nginx2/
nginx2
四、原理解析
现在进入nginx ingress controller的pod:
# kubectl get po -n nginx-ingress
NAME READY STATUS RESTARTS AGE
cmiot-nginx-ingress-8585f89c5b-jbjpz 1/1 Running 2 8h
# kubectl exec -it cmiot-nginx-ingress-8585f89c5b-jbjpz -n nginx-ingress -- /bin/bash
nginx@worker1:/$ cd /etc/nginx/conf.d/
nginx@worker1:/etc/nginx/conf.d$ ls
default-test-ingress.conf
nginx@worker1:/etc/nginx/conf.d$ cat default-test-ingress.conf
# configuration for default/test-ingress
upstream default-test-ingress-testnginx.com-nginx1-svc-80 {
zone default-test-ingress-testnginx.com-nginx1-svc-80 256k;
random two least_conn;
server 10.0.0.140:80 max_fails=1 fail_timeout=10s max_conns=0;
}
upstream default-test-ingress-testnginx.com-nginx2-svc-80 {
zone default-test-ingress-testnginx.com-nginx2-svc-80 256k;
random two least_conn;
server 10.0.0.146:80 max_fails=1 fail_timeout=10s max_conns=0;
}
server {
listen 80;
server_tokens on;
server_name testnginx.com;
set $resource_type "ingress";
set $resource_name "test-ingress";
set $resource_namespace "default";
location /nginx1/ {
set $service "nginx1-svc";
proxy_http_version 1.1;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
client_max_body_size 1m;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering on;
proxy_pass http://default-test-ingress-testnginx.com-nginx1-svc-80;
}
location /nginx2/ {
set $service "nginx2-svc";
proxy_http_version 1.1;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
client_max_body_size 1m;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering on;
proxy_pass http://default-test-ingress-testnginx.com-nginx2-svc-80;
}
}
可以看到, nginx ingress controller 程序已经将ingress定义的路由规则转化为相应的nginx配置文件,因此我们访问nginx ingress controller才能实现相应的路由功能。从上面的nginx配置文件也可看出, nginx ingress 控制器并非直接将请求转发给相应服务,而是通过http请求头部信息确定客户端尝试访问哪个服务,通过与该服务关联的Endpoint对象查看pod IP,并将客户端的请求转发给其中的一个pod。
关于 nginx ingress controller的详细工作原理,可以参看:How NGINX Ingress Controller Works | NGINX Ingress Controller。总结如下:
nginx ingress controller pod由单一容器组成,该容器中有三类进程:IC(Ingress Controller)、Nginx master、Nginx worker。IC进程通过Kubernetes API读取ingress等资源的最新内容,通过容器中的模板文件(在容器根目录下)创建对应的nginx配置文件,写入容器的/etc/nginx目录,然后IC运行nginx -s reload命令重载nginx配置使其生效。
如何实现高可用?
可以通过keepalived+VIP+nodeselector实现,controller pod所在的节点上运行ps -ef可以看到nginx-ingress进程(还可以看到nginx进程,但实际上这些进行并不直接运行的主机系统上),可依此判断pod是否正常运行。