什么是Ingress
Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
Ingress 公开从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源上定义的规则控制。
下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例:
Ingress 其实就是从 Kuberenets 集群外部访问集群的一个入口,将外部的请求转发到集群内不同的 Service 上,其实就相当于 nginx、haproxy 等负载均衡代理服务器,可能你会觉得我们直接使用 nginx 就实现了,但是只使用 nginx 这种方式有很大缺陷,每次有新服务加入的时候怎么改 Nginx 配置?不可能让我们去手动更改或者滚动更新前端的 Nginx Pod 吧?那我们再加上一个服务发现的工具比如 consul 如何?貌似是可以,对吧?Ingress 实际上就是这样实现的,只是服务发现的功能自己实现了,不需要使用第三方的服务了,然后再加上一个域名规则定义,路由信息的刷新依靠 Ingress Controller 来提供。
Ingress Controller 可以理解为一个监听器,通过不断地监听 kube-apiserver,实时的感知后端 Service、Pod 的变化,当得到这些信息变化后,Ingress Controller 再结合 Ingress 的配置,更新反向代理负载均衡器,达到服务发现的作用。其实这点和服务发现工具 consul、 consul-template 非常类似。
现在可以供大家使用的 Ingress Controller 有很多,比如
traefik
、nginx-controller
、Kubernetes Ingress Controller
for Kong、HAProxy Ingress controller
,当然你也可以自己实现一个 Ingress Controller,现在普遍用得较多的是 traefik 和 nginx-controller,traefik 的性能较 nginx-controller 差,但是配置使用要简单许多,我们这里会重点给大家介绍 nginx-controller 以及 traefik 的使用。
安装NGINX Ingress Controller
NGINX Ingress Controller 是使用 Kubernetes Ingress 资源对象构建的,用 ConfigMap 来存储 Nginx 配置的一种 Ingress Controller 实现。
由于 nginx-ingress 所在的节点需要能够访问外网,这样域名可以解析到这些节点上直接使用,所以需要让 nginx-ingress 绑定节点的 80 和 443 端口,所以可以使用 hostPort 来进行访问。
查看当前Ingress-Nginx适用的kubernetes版本
Ingress-NGINX version | k8s supported version | Alpine Version | Nginx Version |
---|---|---|---|
v1.6.4 | 1.26, 1.25, 1.24, 1.23 | 3.17.0 | 1.21.6 |
v1.5.1 | 1.25, 1.24, 1.23 | 3.16.2 | 1.21.6 |
v1.4.0 | 1.25, 1.24, 1.23, 1.22 | 3.16.2 | 1.19.10† |
v1.3.1 | 1.24, 1.23, 1.22, 1.21, 1.20 | 3.16.2 | 1.19.10† |
v1.3.0 | 1.24, 1.23, 1.22, 1.21, 1.20 | 3.16.0 | 1.19.10† |
v1.2.1 | 1.23, 1.22, 1.21, 1.20, 1.19 | 3.14.6 | 1.19.10† |
v1.1.3 | 1.23, 1.22, 1.21, 1.20, 1.19 | 3.14.4 | 1.19.10† |
v1.1.2 | 1.23, 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.1.1 | 1.23, 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.1.0 | 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.0.5 | 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.0.4 | 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.0.3 | 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.0.2 | 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.0.1 | 1.22, 1.21, 1.20, 1.19 | 3.14.2 | 1.19.9† |
v1.0.0 | 1.22, 1.21, 1.20, 1.19 | 3.13.5 | 1.20.1 |
- 使用
Helm
进行部署nginx-ingress-controller
1 | helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx |
- 新建一个
value-test.yaml
的配置文件
dnsPolicy
:因为处于hostNetwork:true
的状态下,Pod默认使用宿主机的DNS解析,这样会导致如果你使用ServiceName
的方式来访问Pod的话会出现无法解析的情况。所以修改为ClusterFirstWithHostNet
- 请将
webhook
的镜像修改为registry.cn-beijing.aliyuncs.com/polymerization/kube-webhook-certgen:v20220916-gd32f8c343
1 | controller: |
- 部署
ingress-controller
1 | # 安装 |
Ingress的基本使用
创建一个ingress资源对象
一个最小的 Ingress 资源示例
1 | apiVersion: networking.k8s.io/v1 |
Ingress 需要指定 apiVersion
、kind
、 metadata
和 spec
字段。 Ingress 对象的命名必须是合法的 DNS 子域名名称。 关于如何使用配置文件,请参见部署应用、 配置容器、 管理资源。 Ingress 经常使用注解(annotations)来配置一些选项,具体取决于 Ingress 控制器,例如重写目标注解。 不同的 Ingress 控制器支持不同的注解。 查看你所选的 Ingress 控制器的文档,以了解其支持哪些注解。
如果
ingressClassName
被省略,那么你应该定义一个默认Ingress 类
,否则无法转发服务
创建一个默认的ingressClass
1 | apiVersion: networking.k8s.io/v1 |
有一些 Ingress 控制器不需要定义默认的 IngressClass
。比如:Ingress-NGINX 控制器可以通过参数 --watch-ingress-without-class
来配置。 不过仍然推荐创建默认的ingressClass
可以看一下简单地ingress-controller请求流程
- 客户端首先对
ngdemo.qikqiak.com
执行 DNS 解析,得到 Ingress Controller 所在节点的 IP - 后客户端向 Ingress Controller 发送 HTTP 请求
- 根据 Ingress 对象里面的描述匹配域名,找到对应的 Service 对象,并获取关联的 Endpoints 列表,将客户端的请求转发给其中一个 Pod
创建Todo-app测试(暂时废弃)
- 首先部署
MongoDB
1 | apiVersion: apps/v1 |
- 创建Todo
1 | apiVersion: apps/v1 |
URL Rewrite
Rewrite的Ingress注解
nginx.ingress.kubernetes.io/rewrite-target | Target URI where the traffic must be redirected | string |
---|---|---|
nginx.ingress.kubernetes.io/ssl-redirect | Indicates if the location section is only accessible via SSL (defaults to True when Ingress contains a Certificate) | bool |
nginx.ingress.kubernetes.io/force-ssl-redirect | Forces the redirection to HTTPS even if the Ingress is not TLS Enabled | bool |
nginx.ingress.kubernetes.io/app-root | Defines the Application Root that the Controller must redirect if it’s in / context |
string |
nginx.ingress.kubernetes.io/use-regex | Indicates if the paths defined on an Ingress use regular expressions | bool |
现在我们需要对访问的 URL 路径做一个 Rewrite,比如在 PATH 中添加一个 app 的前缀,关于 Rewrite 的操作在 ingress-nginx 官方文档中也给出对应的说明。
nginx.ingress.kubernetes.io/rewrite-target
: 流量必须重定向的目标URI(Target URI where the traffic must be redirected)
1 | apiVersion: networking.k8s.io/v1 |
在此入口定义中,捕获的任何字符都(.*)
将分配给占位符$2
,然后将其用作注释中的参数rewrite-target
。
例如,上面的入口定义将导致以下重写:
nginx.qingyang.com/something
改写为nginx.qingyang.com/
nginx.qingyang.com/something/
改写为nginx.qingyang.com/
nginx.qingyang.com/something/new
改写为nginx.qingyang.com/new
使用此方法可能会导致部分
css、js
等内容无法找到,可以使用以下方法实现
- 通过
configuration-snippet
注解
1 | apiVersion: networking.k8s.io/v1 |
Basic Auth
们还可以在 Ingress Controller 上面配置一些基本的 Auth 认证,比如 Basic Auth,可以用 htpasswd 生成一个密码文件来验证身份验证。
1 | [root@Online-Beijing-master1 ~]# htpasswd -c auth admin |
创建一个secret
1 | [root@Online-Beijing-master1 ~]# kubectl create secret generic basic-auth --from-file=authBasic Auth 的 Ingress 对象: |
创建一个具有 Basic Auth
的 Ingress 对象
1 | apiVersion: networking.k8s.io/v1 |
正常会弹出来认证窗口,进行认证就行。
灰度应用
在日常工作中我们经常需要对服务进行版本更新升级,所以我们经常会使用到滚动升级、蓝绿发布、灰度发布等不同的发布操作。而 ingress-nginx
支持通过 Annotations 配置来实现不同场景下的灰度发布和测试,可以满足金丝雀发布、蓝绿部署与 A/B 测试等业务场景。
在某些情况下,您可能希望通过向与生产服务不同的服务发送少量请求来金丝雀
一组新的更改。Canary 注释使 Ingress 规范可以充当请求路由到的替代服务,具体取决于应用的规则。
ingress-nginx 的 Annotations 支持以下 4 种 Canary 规则:
nginx.ingress.kubernetes.io/canary-by-header
: 基于 Request Header 的流量切分,适用于灰度发布以及 A/B 测试。当 Request Header 设置为 always 时,请求将会被一直发送到 Canary 版本;当 Request Header 设置为 never时,请求不会被发送到 Canary 入口;对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他金丝雀规则进行优先级的比较。nginx.ingress.kubernetes.io/canary-by-header-value
: 要匹配的 Request Header 的值,用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务。当 Request Header 设置为此值时,它将被路由到 Canary 入口。该规则允许用户自定义 Request Header 的值。此注释必须与 一起使用nginx.ingress.kubernetes.io/canary-by-header
。如果未定义canary-by-header
,那么该注解没有任何效果。nginx.ingress.kubernetes.io/canary-weight
: 基于服务权重的流量切分,适用于蓝绿部署,权重范围 0 - 100 按百分比将请求路由到 Canary Ingress 中指定的服务。权重为 0 意味着该金丝雀规则不会向 Canary 入口的服务发送任何请求,权重为 100 意味着所有请求都将被发送到 Canary 入口。nginx.ingress.kubernetes.io/canary-by-cookie
: 基于 cookie 的流量切分,适用于灰度发布与 A/B 测试。用于通知 Ingress 将请求路由到 Canary Ingress 中指定的服务的cookie。当 cookie 值设置为 always 时,它将被路由到 Canary 入口;当 cookie 值设置为 never 时,请求不会被发送到 Canary 入口;对于任何其他值,将忽略 cookie 并将请求与其他金丝雀规则进行优先级的比较。
Canary 规则按优先顺序进行评估。优先级如下:
canary-by-header -> canary-by-cookie -> canary-weight
把以上的四个 annotation 规则可以总体划分为以下两类:
基于权重的的Canary规则
基于用户请求的Canary规则
灰度验证
- 首先我们先创建一个基于
Producation
版本的应用
1 | apiVersion: apps/v1 |
- 创建Production 版本的应用路由 (Ingress)。
1 | apiVersion: networking.k8s.io/v1 |
- 创建Canary版本的应用上线
1 | apiVersion: apps/v1 |
基于权重的Canary规则
基于权重的流量切分的典型应用场景就是蓝绿部署
,可通过将权重设置为 0 或 100 来实现。例如,可将 Green 版本设置为主要部分,并将 Blue 版本的入口配置为 Canary。最初,将权重设置为 0,因此不会将流量代理到 Blue 版本。一旦新版本测试和验证都成功后,即可将 Blue 版本的权重设置为 100,即所有流量从 Green 版本转向 Blue。
以下 Ingress 示例的 Canary 版本使用了基于权重进行流量切分的 annotation 规则,将分配 30% 的流量请求发送至 Canary 版本。
1 | apiVersion: networking.k8s.io/v1 |
应用的 Canary 版本基于权重 (30%) 进行流量切分后,访问到 Canary 版本的概率接近 30%,流量比例可能会有小范围的浮动。
基于 Request Header
基于 Request Header 进行流量切分的典型应用场景即灰度发布或 A/B 测试场景
。参考以下截图,在 KubeSphere 给 Canary 版本的应用路由 (Ingress) 新增一条 annotation nginx.ingress.kubernetes.io/canary-by-header: canary
(这里的 annotation 的 value 可以是任意值),使当前的 Ingress 实现基于 Request Header 进行流量切分。
金丝雀规则按优先顺序
canary-by-header - > canary-by-cookie - > canary-weight
进行如下排序,因此以下情况将忽略原有 canary-weight 的规则。
1 | apiVersion: networking.k8s.io/v1 |
我们尝试访问一下
1 | [root@Online-Beijing-master1 ~]# for i in $(seq 1 10); do curl -s prod.qingyang.com | grep "Hostname"; done |
尝试加入请求头访问
- 如果你的
canary-header
的值为never
则表示请求永远不会请求到当前版本,如果你的canary-header
的值设置为always
的话则表示永远请求当前版本
1 | [root@Online-Beijing-master1 ~]# for i in $(seq 1 10); do curl -s -H "canary-header: never" prod.qingyang.com | grep "Hostname"; done |
如果你想让用户请求到指定的服务上可以添加ginx.ingress.kubernetes.io/canary-by-header-value: user-value
,当请求访问携带canary-header: user-value
的时候,那么该请求会被转发到canary
版本。
1 | apiVersion: networking.k8s.io/v1 |
基于 Cookie的canary
与基于 Request Header 的 annotation 用法规则类似。例如在 A/B 测试场景
下,需要让地域为北京的用户访问 Canary 版本。那么当 cookie 的 annotation 设置为 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_beijing"
,此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置 cookie users_from_beijing
的值为 always
,这样就可以确保北京的用户仅访问 Canary 版本
1 | apiVersion: networking.k8s.io/v1 |
自签HTTPS
如果我们需要用 HTTPS 来访问我们这个应用的话,就需要监听 443 端口了,同样用 HTTPS 访问应用必然就需要证书,这里我们用 openssl
来创建一个自签名的证书:
1 | openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=prod.qingyang.com" |
创建tls
类型的secret
1 | kubectl create secret tls self-sign-nginx --cert=tls.crt --key=tls.key |
创建带tls
的ingress
1 | apiVersion: networking.k8s.io/v1 |
CertManager 自动 HTTPS
cert-manager 将证书和证书颁发者作为资源类型添加到 Kubernetes 集群中,并简化了这些证书的获取、更新和使用过程。
它可以从各种受支持的来源颁发证书,包括 Let’s Encrypt、HashiCorp Vault和Venafi以及私有 PKI。
它将确保证书有效且最新,并尝试在到期前的配置时间更新证书。
它大致基于 kube-lego的工作,并借鉴了其他类似项目(例如 kube-cert-manager)的一些智慧。
Issuers
: 代表的是证书颁发者,可以定义各种提供者的证书颁发者,当前支持基于Let's Encrypt/HashiCorp/Vault
和 CA 的证书颁发者,还可以定义不同环境下的证书颁发者。Certificates
: 代表的是生成证书的请求.
部署cert-manager
好像这个quay.io
能拉下来了…
1 | kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.11.0/cert-manager.yaml |
正常部署完成可以看到Pod正在运行
1 | [root@Online-Beijing-master1 ~]# kubectl get pods -n cert-manager |
我们可以通过下面的测试来验证下是否可以签发基本的证书类型,创建一个 Issuer 资源对象来测试 webhook 工作是否正常(在开始签发证书之前,必须在群集中至少配置一个 Issuer 或 ClusterIssuer 资源)
1 | apiVersion: v1 |
自动HTTPS
Let's Encrypt
使用 ACME
协议来校验域名是否真的属于你,校验成功后就可以自动颁发免费证书,证书有效期只有 90 天,在到期前需要再校验一次来实现续期,而 cert-manager 是可以自动续期的,所以事实上并不用担心证书过期的问题。目前主要有 HTTP 和 DNS 两种校验方式。
HTTP-01 校验
HTTP-01
的校验是通过给你域名指向的 HTTP 服务增加一个临时 location,在校验的时候 Let's Encrypt
会发送 http 请求到 http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>
,其中 YOUR_DOMAIN
就是被校验的域名,TOKEN
是 cert-manager 生成的一个路径,它通过修改 Ingress 规则来增加这个临时校验路径并指向提供 TOKEN 的服务。Let's Encrypt
会对比 TOKEN 是否符合预期,校验成功后就会颁发证书了,不过这种方法不支持泛域名证书。
使用 HTTP 校验这种方式,首先需要将域名解析配置好,也就是需要保证 ACME 服务端可以正常访问到你的 HTTP 服务。这里我们以上面的 TODO 应用为例,我们已经将 demo.qingyang.com
域名做好了正确的解析。
由于 Let’s Encrypt 的生产环境有着严格的接口调用限制,所以一般我们需要先在 staging 环境测试通过后,再切换到生产环境。首先我们创建一个全局范围 staging 环境使用的 HTTP-01 校验方式的证书颁发机构:
1 | apiVersion: cert-manager.io/v1 |
接下来我们就可以生成免费证书了,cert-manager 给我们提供了 Certificate
这个用于生成证书的自定义资源对象,不过这个对象需要在一个具体的命名空间下使用,证书最终会在这个命名空间下以 Secret 的资源对象存储。我们这里是要结合 ingress-nginx 一起使用,实际上我们只需要修改 Ingress 对象,添加上 cert-manager 的相关注解即可,不需要手动创建 Certificate 对象了。
1 | apiVersion: networking.k8s.io/v1 |
创建完成后会多出一个ingress
对象,主要是为了让acme
可以访问到当前的的token
1 | [root@Online-Beijing-master1 ~]# kubectl get ingress |
可以查看当前的acme
认证,其中/.well-known/acme-challenge/pVY-ihomZPdlWDMt44cV9qZUwMQScjHvd3Zkf_FDLRI
是被验证对象
1 | [root@Online-Beijing-master1 ~]# kubectl describe ingress cm-acme-http-solver-xpxm7 |
你可以尝试访问https://demo.qingyang.com/.well-known/acme-challenge/pVY-ihomZPdlWDMt44cV9qZUwMQScjHvd3Zkf_FDLRI
。正常会出现具体的验证密钥
即成功.
由于我的是本地自己搭建的kubernetes集群,没有外部解析的访问权限所以这个地方就没办法给大家演示了。
DNS-01 校验
NS-01
的校验是通过 DNS 提供商的 API 拿到你的 DNS 控制权限, 在 Let's Encrypt
为 cert-manager 提供 TOKEN 后,cert-manager 将创建从该 TOKEN 和你的帐户密钥派生的 TXT
记录,并将该记录放在 _acme-challenge.<YOUR_DOMAIN>
。然后 Let's Encrypt
将向 DNS 系统查询该记录,如果找到匹配项,就可以颁发证书,这种方法是支持泛域名证书的。
DNS-01 支持多种不同的服务提供商,直接在 Issuer 或者 ClusterIssuer 中可以直接配置,对于一些不支持的 DNS 服务提供商可以使用外部 webhook 来提供支持,比如阿里云的 DNS 解析默认情况下是不支持的,我们可以使用阿里云这个 webhook 来提供支持。
- 安装
alidns-webhook
1 | kubectl apply -f https://raw.githubusercontent.com/pragkent/alidns-webhook/master/deploy/bundle.yaml |
- 接着创建一个包含访问阿里云 DNS 认证密钥信息的 Secret 对象,对应的
accessk-key
和secret-key
1 | kubectl create secret generic alidns-secret --from-literal=access-key=YOUR_ACCESS_KEY --from-literal=secret-key=YOUR_SECRET_KEY -n cert-manager |
- 接下来同样首先创建一个 staging 环境的 DNS 类型的证书机构资源对象
1 | apiVersion: cert-manager.io/v1 |
接下来我们就可以使用上面的 ClusterIssuer 对象来或者证书数据了,创建如下所示的 Certificate 资源对象
1 | apiVersion: cert-manager.io/v1 |
后我们就可以直接在 Ingress 资源对象中使用上面的 Secret 对象了
1 | apiVersion: networking.k8s.io/v1 |