通过Service可以实现访问Kubernetes集群中的一组Pod
及实现负载均衡,但Service
中的负载均衡都是基于IP和端口的四层负载均衡。而要想实现七层负载均衡,则需要引入Ingress
。Ingress
是对集群中服务的外部访问进行管理的API对象,典型的访问方式是HTTP。Ingress
可以提供负载均衡、SSL和基于主机名的访问。
本文主要参考官方文档对Kubernetes的Ingress进行一个简单总结。
本文所使用的环境如下:
Ingress公开了从集群外部到集群内的HTTP和HTTPS路由。流量路由由Ingress资源上定义的规则控制。
internet
|
[ Ingress ]
--|-----|--
[ Services ]
可以给Ingress配置为服务提供外部可访问的URL、负载均衡流量、SSL/TLS以及提供基于名称的虚拟主机等。Ingress控制器(Ingress Controller)通常负责通过负载均衡器来实现Ingress,尽管它也可以配置边缘路由器或其他前端来帮助处理流量。Ingress不会公开任意端口或协议。将HTTP和HTTPS以外的服务公开到Internet时,通常使用Service.Type=NodePort
或Service.Type=LoadBalancer
类型的服务。
Ingress资源自身并不能进行“流量穿透”,它仅是一组路由规则的集合,这些规则要想真正发挥作用还需要其他功能的辅助,如监听某套接字,然后根据这些规则的匹配机制路由请求流量。这种能够为Ingress资源监听套接字并转发流量的组件称为Ingress控制器(Ingress Controller)。
为了让Ingress资源工作,集群必须有一个正在运行的Ingress控制器。与作为kube-controller-manager
可执行文件的一部分运行的其他类型的控制器不同,Ingress控制器不是随集群自动启动的,需要在集群上单独部署。Kubernetes项目目前支持和维护GCE和nginx控制器。至于其他控制器可以参考Ingress控制器官方文档。
可以在集群中部署任意数量的ingress控制器,创建ingress时,应该使用适当的ingress.class
注解每个Ingress以表明在集群中如果有多个Ingress控制器时,应该使用哪个Ingress控制器。如果不定义ingress.class
,云提供商可能使用默认的Ingress控制器。
必须具有Ingress控制器才能满足Ingress的要求。仅创建Ingress资源本身没有任何效果。使用Ingress可能需要部署一个例如ingress-nginx的Ingress控制器。至于使用哪个Ingress控制器,可以从许多Ingress控制器中进行选择。理想情况下,所有Ingress控制器都应符合参考规范。但实际上,不同的Ingress控制器操作略有不同。
如下图所示,流量首先到达外部负载均衡器,这个负载均衡器是由我们自己部署的,然后转发到Ingress控制器的Service
上,然后再转发到Ingress控制器的Pod
上,通过Ingress控制器基于Ingress
资源定义的规则将客户端请求流量直接转发至与Service
对应的后端Pod
上。这种转发机制会绕过Service
,从而省去了由kube-proxy
实现的端口代理开销,Ingress规则需要由一个Service
资源对象辅助识别相关的所有Pod
资源。
Ingress控制器自身是运行于Pod
中的容器应用,一般是例如Nginx这种具有代理及负载均衡功能的守护进程,它监视着来自API Server
的Ingress
对象状态,并根据规则生成相应的应用程序专有格式的配置文件并通过重载或重启守护进程而使新配置生效。Ingress控制器其实就是托管于Kubernetes系统之上的用于实现在应用层发布服务的Pod
资源,跟踪Ingress
资源并实时生成配置规则。
Ingress控制器可以通过两种方式部署以接入外部请求流量。第一种方式是通过Deployment
控制器管理Ingress控制器的Pod
资源,通过NodePort
或LoadBalancer
类型的Service
对象为其接入集群外部的请求流量,所有这种方式在定义一个Ingress控制器时必须在其前端定义一个专用的Service
资源。如下图所示:
第二种方式是通过DaemonSet
控制器确保集群的所有或部分工作节点中每个节点上只运行Ingress控制器的一个Pod
资源,并配置这类Pod
对象以HostPort
或HostNetwork
的方式在当前节点接入外部流量。如下图所示:
进入用于存放k8s相关文件的目录,这里是/usr/local/k8s/
,创建一个用于ingress-nginx的目录并进入:
cd /usr/local/k8s/
mkdir -p ingress-controller/ingress-nginx
cd ingress-controller/ingress-nginx/
下面就开始安装ingress-nginx,可以参考ingress-nginx官方文档进行安装,官方提供了多种平台的安装方式。由于这里用的是自己使用kubeadm搭建的k8s环境,所以选择Bare-metal的安装配置文件进行安装。下载v0.34.1
版的ingress-nginx的配置清单文件并创建ingress-nginx:
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml
kubectl apply -f deploy.yaml
注意:通过
v0.34.1
版的ingress-nginx的配置清单文件进行部署ingress-nginx控制器的时候需要拉取us.gcr.io/k8s-artifacts-prod/ingress-nginx/controller:v0.34.1
镜像,由于国内网络原因,访问不到us.gcr.io
,需要使用科学上网或者在Docker Hub上用户仓库找到该镜像提前拉取下来。
ingress-nginx控制器的所有资源都在ingress-nginx
名称空间下,查看ingress-nginx
名称空间中的Pod
及ingress-nginx控制器的Pod
的详细信息,这里只截取了部分信息:
查看ingress-nginx
名称空间中的Service
及ingress-nginx-controller
的详细信息:
可知创建了一个NodePort
类型的Service
,名为ingress-nginx-controller
,并随机分配http
的NodePort
为31681
,https
的NodePort
的为31620
,对外暴露服务。
一个最小的Ingress资源示例:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: test-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
serviceName: test
servicePort: 80
与所有其他Kubernetes资源一样,Ingress也需要使用apiVersion
、kind
和metadata
字段。Ingress提供了配置负载均衡器或者代理服务器所需的所有信息,其中包含与所有传入请求匹配的规则列表。Ingress资源仅支持用于转发HTTP流量的规则。
IngressSpec
下的rules
用于配置Ingress的主机规则列表,如果未指定规则或未匹配到任何规则,则所有流量都会被转发到默认后端。每个HTTP规则都包含以下信息:
host
):在此示例中,未指定主机,因此该规则适用于通过指定IP地址的所有入站HTTP通信。如果提供了主机,例如 foo.bar.com
,则规则适用于该主机。path
):例如/testpath
,每个路径都有一个由serviceName
和servicePort
定义的关联后端。在负载均衡器将流量定向到引用的服务之前,主机和路径都必须匹配传入的请求。backend
):是服务和服务端口的组合。与规则的主机和路径匹配的对Ingress的HTTP(和HTTPS )请求将发送到列出的后端。没有定义规则的Ingress将所有流量发送到同一个默认后端。默认后端通常是Ingress控制器的配置选项,并且未在Ingress资源中指定。如果主机或路径都没有与Ingress对象中的HTTP请求匹配,则流量将路由到默认后端。
Ingress中的每个路径都有对应的路径类型。当前支持以下三种路径类型:
ImplementationSpecific
(默认):对于这种类型,匹配取决于IngressClass。具体实现可以将其作为单独的pathType
处理或者与Prefix
或Exact
类型作相同处理。Exact
:精确匹配URL路径,且对大小写敏感。Prefix
:基于以/
分隔的URL路径前缀匹配。匹配对大小写敏感,并且对路径中的元素逐个完成。路径元素指的是由/
分隔符分隔的路径中的标签列表。注意:
- 如果路径的最后一个元素是请求路径中最后一个元素的子字符串,则不会匹配 (例如:
/foo/bar
匹配/foo/bar/baz
,但不匹配/foo/barbaz
)。- 如果Ingress中的多条路径匹配同一个请求。这种情况下最长的匹配路径优先。 如果仍然有两条同等的匹配路径,则
Exact
路径类型优先于Prefix
路径类型。
Kubernetes允许暴露单个Service
,本文使用ingress-nginx控制器暴露服务,示意图如下:
先创建一个名为ingress-test1
的名称空间,方便管理。创建namespace-ingress-test1.yaml
:
apiVersion: v1
kind: Namespace
metadata:
name: ingress-test1
labels:
env: ingress-test1
创建名称空间ingress-test1
:
kubectl apply -f namespace-ingress-test1.yaml
查看ingress-test1
名称空间:
kubectl get namespace ingress-test1
部署nginx
实例,通过Deployment
控制器在ingress-test1
名称空间中部署nginx
的Pod
,通过名为mynginx-service
的service
资源的88
端口暴露容器的80
端口。先创建deploy-svc-test1.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mynginx-deployment
namespace: ingress-test1
labels:
app: MyNginx
version: v1
spec:
replicas: 3
selector:
matchLabels:
app: MyNginx
version: v1
template:
metadata:
labels:
app: MyNginx
version: v1
spec:
containers:
- name: nginx
image: nginx:alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: mynginx-service
namespace: ingress-test1
spec:
type: ClusterIP
selector:
app: MyNginx
version: v1
ports:
- name: http
port: 88
targetPort: 80
创建Deployment
和Service
:
kubectl apply -f deploy-svc-test1.yaml
查看名称空间ingress-test1
中的Deployment
、Service
和Pod
信息:
kubectl get deployment -n ingress-test1
kubectl get pod -n ingress-test1 -o wide
kubectl get svc -n ingress-test1
kubectl describe svc mynginx-service -n ingress-test1
下面创建Ingress
资源对象,匹配mynginx-service
并将该Service
的88
端口暴露,这个Ingress的规则将域名myapp.nginx.com
的根目录映射到mynginx-service
。先创建single-svc-ingress.yaml
:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: single-svc-ingress
namespace: ingress-test1
spec:
rules:
- host: myapp.nginx.com
http:
paths:
- path: /
backend:
serviceName: mynginx-service
servicePort: 88
创建名为single-svc-ingress
的Ingress
:
kubectl apply -f single-svc-ingress.yaml
查看名称空间ingress-test1
中的Ingress
及single-svc-ingress
详细信息:
kubectl get ingress -n ingress-test1
kubectl describe ingress single-svc-ingress -n ingress-test1
查看ingress-nginx控制器Pod中生成的nginx
配置文件:
kubectl exec ingress-nginx-controller- -n ingress-nginx -it -- /bin/sh
cat /etc/nginx/nginx.conf
这里截取了部分内容:
在本地和虚拟机hosts
文件中添加域名解析列表,添加集群任意一个节点IP与域名对应即可:
192.168.1.16 myapp.nginx.com
192.168.1.17 myapp.nginx.com
192.168.1.18 myapp.nginx.com
访问myapp.nginx.com:31681
,31681
为服务ingress-nginx-controller
对外暴露的http
访问的NodePort
:
验证是否调度到后端的Pod
资源,查看日志:
基于名称的虚拟主机支持将针对多个主机名的HTTP流量路由到同一IP地址上,即将不同的host名映射到不同的Service
。如下图所示:
先创建一个名为ingress-test2
的名称空间,方便管理。创建namespace-ingress-test2.yaml
:
apiVersion: v1
kind: Namespace
metadata:
name: ingress-test2
labels:
env: ingress-test2
创建并查看名称空间ingress-test2
:
然后创建一组Nginx
和Tomcat
实例,先创建资源清单文件deploy-svc-test2.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mynginx-deployment
namespace: ingress-test2
labels:
app: MyNginx
version: v1
spec:
replicas: 3
selector:
matchLabels:
app: MyNginx
version: v1
template:
metadata:
labels:
app: MyNginx
version: v1
spec:
containers:
- name: nginx
image: nginx:alpine
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: mynginx-service
namespace: ingress-test2
spec:
type: ClusterIP
selector:
app: MyNginx
version: v1
ports:
- name: http
port: 88
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mytomcat-deployment
namespace: ingress-test2
labels:
app: MyTomcat
version: v1
spec:
replicas: 3
selector:
matchLabels:
app: MyTomcat
version: v1
template:
metadata:
labels:
app: MyTomcat
version: v1
spec:
containers:
- name: tomcat
image: tomcat:8.0.53
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
- name: ajp
containerPort: 8009
---
apiVersion: v1
kind: Service
metadata:
name: mytomcat-service
namespace: ingress-test2
spec:
selector:
app: MyTomcat
version: v1
ports:
- name: http
port: 8080
targetPort: 8080
- name: ajp
port: 8009
targetPort: 8009
Tomcat镜像用的较老的8.0.53
版本,因为较新版本的Tomcat没有index页面,为了方便测试,选择了低版本。创建资源清单中定义的资源对象并查看:
创建Ingress
资源对象,先创建name-virtual-host-ingress.yaml
:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: name-virtual-host-ingress
namespace: ingress-test2
spec:
rules:
- host: mynginx.test.com
http:
paths:
- path: /
backend:
serviceName: mynginx-service
servicePort: 88
- host: mytomcat.test.com
http:
paths:
- path: /
backend:
serviceName: mytomcat-service
servicePort: 8080
创建Ingress
并查看:
在本地和虚拟机hosts
文件中添加域名解析列表,添加集群任意一个节点IP与域名对应即可:
192.168.1.16 mynginx.test.com mytomcat.test.com
192.168.1.17 mynginx.test.com mytomcat.test.com
192.168.1.18 mynginx.test.com mytomcat.test.com
访问mynginx.test.com:31681
:
访问mytomcat.test.com:31681
:
根据请求的HTTP URI可以将流量从单个IP地址路由到多个服务。即根据请求URL的路径将不同路径的请求发送到不同的服务,客户端可以通过一个Ingress控制器的IP地址访问到多个服务。如下图所示:
注意:
Ingress
中path
的要与后端Service
提供的Path
一致,否则将被转发到一个不存在的path
上,引起错误。
一个将不同的路径发送到不同服务的Ingress
资源如下:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: simple-fanout-ingress
namespace: ingress-test2
spec:
rules:
- host: myapp.test.com
http:
paths:
- path: /nginx
backend:
serviceName: mynginx-service
servicePort: 88
- path: /tomcat
backend:
serviceName: mytomcat-service
servicePort: 8080
可以通过设定包含TLS私钥和证书的Secret
来保护Ingress
。目前,Ingress
只支持单个TLS端口443
。这里使用openssl
生成自签名证书:
#生成mynginx.test.com域名的证书
openssl genrsa -out mynginx.test.com.key 4096
openssl req -x509 -new -key mynginx.test.com.key -days 3650 -out mynginx.test.com.crt -subj /C=CN/ST=ChongQing/L=ChongQing/O=RtxTitanV/CN=mynginx.test.com
#生成mytomcat.test.com域名的证书
openssl genrsa -out mytomcat.test.com.key 4096
openssl req -x509 -new -key mytomcat.test.com.key -days 3650 -out mytomcat.test.com.crt -subj /C=CN/ST=ChongQing/L=ChongQing/O=RtxTitanV/CN=mytomcat.test.com
创建Secret
:
#创建mynginx.test.com域名的secret
kubectl create secret tls mynginx-ingress-secret --cert=mynginx.test.com.crt --key=mynginx.test.com.key -n ingress-test2
#创建mytomcat.test.com域名的secret
kubectl create secret tls mytomcat-ingress-secret --cert=mytomcat.test.com.crt --key=mytomcat.test.com.key -n ingress-test2
查看Secret
:
kubectl get secret -n ingress-test2
创建带TLS的Ingress
资源对象,先创建tls-ingress.yaml
:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: tls-ingress
namespace: ingress-test2
spec:
tls:
- hosts:
- mynginx.test.com
secretName: mynginx-ingress-secret
- hosts:
- mytomcat.test.com
secretName: mytomcat-ingress-secret
rules:
- host: mynginx.test.com
http:
paths:
- path: /
backend:
serviceName: mynginx-service
servicePort: 88
- host: mytomcat.test.com
http:
paths:
- path: /
backend:
serviceName: mytomcat-service
servicePort: 8080
创建ingress
并查看:
进行https
访问测试,通过ingress-nginx控制器的前端的Service
资源的对外暴露的NodePort
来访问此服务,而ingress-nginx控制器的Service
的443
端口对应的NodePort
为31620
,下面访问https://mynginx.test.com:31620/
:
访问https://mytomcat.test.com:31620/
:
提示不安全是因为证书是自签名证书,不用管它。
首先安装httpd:
yum install -y httpd
创建认证文件auth
,并根据auth
文件存储到k8s的secret
中:
htpasswd -c auth rtxtitanv
kubectl create secret generic basic-auth --from-file=auth -n ingress-test1
创建basicauth-ingress.yaml
以添加需要认证的主机名:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: basicauth-ingress
namespace: ingress-test1
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - rtxtitanv'
spec:
rules:
- host: bashauth.nginx.com
http:
paths:
- path: /
backend:
serviceName: mynginx-service
servicePort: 88
创建ingress
并查看:
下面进行访问测试,先在hosts
文件新增域名解析,使bashauth.nginx.com
映射到k8s任意节点IP,然后访问bashauth.nginx.com:31681
,需要先输入刚才设置的用户名和密码:
Nginx重写主要有以下几种,都在annotations
下声明:
名称 | 描述 | 值 |
---|---|---|
nginx.ingress.kubernetes.io/rewrite-target | 必须重定向流量的目标URL | 字符串 |
nginx.ingress.kubernetes.io/ssl-redirect | 指示位置部分是否仅可访问SSL(当Ingress包含证书时默认为True)布尔 | 布尔 |
nginx.ingress.kubernetes.io/force-ssl-redirect | 即使lngress未启用TLS,也强制重定向到HTTPS | 布尔 |
nginx.ingress.kubernetes.io/app-root | 定义Controller必须重定向的应用程序根,如果它在‘/’上下文中 | 字符串 |
nginx.ingress.kubernetes.io/use-regex | 指示lngress上定义的路径是否使用正则表达式 | 布尔 |
创建Ingress
资源对象,使访问re.mynginx.com
的请求都将被重定向到myapp.nginx.com
。先创建rewrite-ingress.yaml
:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: rewrite-ingress
namespace: ingress-test1
annotations:
nginx.ingress.kubernetes.io/rewrite-target: http://myapp.nginx.com:31681
spec:
rules:
- host: re.mynginx.com
http:
paths:
- path: /
backend:
serviceName: mynginx-service
servicePort: 88
创建ingress
并查看:
下面进行访问测试,先在hosts
文件新增域名解析,使re.mynginx.com
映射到k8s任意节点IP,然后访问re.mynginx.com:31681
,会跳转到myapp.nginx.com:31681
:
如果要更新现有的Ingress
以添加新的Host,可以通过编辑Ingress
资源来对其进行更新。先查看名称空间ingress-test1
中single-svc-ingress
的详细信息:
kubectl describe ingress single-svc-ingress -n ingress-test1
在更新之前先在ingress-test1
名称空间中创建Tomcat
的Deployment
和Service
,创建过程参考上文,这里就省略了。然后执行以下命令修改配置以更新Ingress
:
kubectl edit ingress single-svc-ingress -n ingress-test1
这一命令将打开编辑器,如下修改配置来增加新的主机:
spec:
rules:
- host: myapp.nginx.com
http:
paths:
- backend:
serviceName: mynginx-service
servicePort: 88
path: /
pathType: ImplementationSpecific
- host: myapp.tomcat.com
http:
paths:
- backend:
serviceName: mytomcat-service
servicePort: 8080
path: /
pathType: ImplementationSpecific
保存更改后,kubectl 将更新API服务器中的资源,该资源将告诉Ingress控制器重新配置负载均衡器。验证:
kubectl describe ingress single-svc-ingress -n ingress-test1
single-svc-ingress
的详细信息可知已经新增了一条Ingress
规则。下面进行访问测试,先在hosts
文件新增域名解析,使myapp.tomcat.com
映射到k8s任意节点IP,然后访问myapp.tomcat.com:31681
,成功访问到Tomcat欢迎页面,说明Ingress
更新成功:
Ingress可以由不同的控制器实现,通常使用不同的配置。每个Ingress应当指定一个Class,也就是一个对IngressClass资源的引用。 IngressClass资源包含额外的配置,其中包括应当实现该类的控制器名称。例如:
apiVersion: networking.k8s.io/v1beta1
kind: IngressClass
metadata:
name: external-lb
spec:
controller: example.com/ingress-controller
parameters:
apiGroup: k8s.example.com/v1alpha
kind: IngressParameters
name: external-lb
IngressClass资源包含一个可选的参数字段parameters
,可用于为该Class引用额外配置。
在Kubernetes 1.18版本引入IngressClass资源和ingressClassName
字段之前,IngressClass是通过Ingress中的一个kubernetes.io/ingress.class
注解来指定的。这个注解从未被正式定义过,但是得到了Ingress控制器的广泛支持。Ingress中新的 ingressClassName
字段用来替代该注解,但并非完全等价。该注解通常用于引用实现该Ingress的控制器的名称,而这个新的字段则是对一个包含额外Ingress配置的IngressClass资源的引用,包括Ingress控制器的名称。
可以将一个特定的IngressClass标记为集群默认选项。将一个IngressClass资源的ingressclass.kubernetes.io/is-default-class
注解设置为true
将确保新的未指定ingressClassName
字段的Ingress能够分配为这个默认的IngressClass。注意,如果集群中有多个IngressClass被标记为默认,则准入控制器将阻止创建新的未指定ingressClassName
的Ingress对象。解决这个问题只需确保集群中最多只能有一个IngressClass被标记为默认。