K8S-Ingress进阶

K8S-Ingress进阶

在前面Service章节其实已经介绍过Ingress,但是针对于生产环境下的Ingress配置会更加多样化。针对不同的应用场景,在本章节会详细介绍Ingress的不同使用方法。如果读者之前有接触过Nginx,其实会发现跟Nginx的功能点非常类似。

在开始前,为读者介绍两个概念,分别是hostNetwork和Annotations。

HostNetwork

hostNetwork 是 Kubernetes 中 Pod 配置的一个字段,用于指定 Pod 是否使用主机的网络命名空间。在 Kubernetes 中,Pod 通常会被分配一个虚拟网络接口,所有容器都在这个虚拟网络接口中进行通信。而设置 hostNetwork: true 时,Pod 会直接使用宿主机的网络接口,意味着该 Pod 共享宿主机的 IP 地址和网络堆栈。

hostNetwork 的工作原理:默认情况下,Pod 使用自己的网络命名空间,也就是每个 Pod 和宿主机有不同的网络接口和 IP 地址。启用 hostNetwork后,Pod 使用宿主机的网络接口。Pod 将不再有独立的 IP 地址,而是直接使用宿主机的 IP 地址。Pod 内的容器共享宿主机的网络栈,包括所有网络端口和防火墙规则。

hostNetwork 的使用场景:

1、高性能网络应用:某些网络密集型应用(如网络代理、DNS 服务、负载均衡器等)可能需要与宿主机的网络栈紧密集成,使用 hostNetwork 可以减少网络延迟和带宽消耗。

2、需要访问宿主机的特定网络接口:当需要访问宿主机的某些特定网络设备时(如物理网卡或内网 IP),可以设置 hostNetwork。

3、网络插件和操作系统级别的配置:例如,CNI 插件(如 Calico、Flannel)或系统级网络配置可能需要在主机网络空间中运行 Pod。

hostNetwork 的优点:

1、直接访问宿主机网络:Pod 可以直接访问宿主机的网络接口,这对于某些需要低延迟或高带宽的应用非常重要。

2、支持特殊网络配置:对于需要特殊网络配置(例如某些硬件接口、特定子网等)的应用,使用 hostNetwork 可以简化网络设置。

hostNetwork 的缺点:

1、端口冲突:如果多个 Pod 使用相同的端口并设置了 hostNetwork: true,它们将共享宿主机的端口,这可能导致端口冲突。因此,需要非常小心地分配端口。

2、限制灵活性:使用宿主机的网络命名空间限制了 Pod 在网络配置上的灵活性。Pod 可能无法在多个网络中独立工作。

3、安全性问题:Pod 直接使用宿主机网络可能增加安全风险,因为容器可以访问宿主机的网络接口,绕过一些网络隔离。

Ingress中的Annotations

在 Kubernetes 中,Annotations(注解) 确实可以翻译为“注释”,但是在 Kubernetes 的资源对象中,Annotations 并不仅仅用于存储注释信息。它们是一个可以存储 非标识性、非结构化数据 的键值对,允许附加额外的信息和配置信息。虽然它的名字是“注释”,但它在 Kubernetes 中的作用远远超出了普通的注释,实际上是用来扩展资源功能的。

Annotations 与 Labels 的区别:

1、Labels:用于标识和选择 Kubernetes 资源,通常用于资源的筛选、查询、分组等操作,且具有结构化的键值对。

2、Annotations:用于存储任意的非结构化元数据(如配置、文档、说明、状态等),它的内容可以是长文本,并且通常不会直接影响对象的选择或调度。

对于 Ingress 对象,Annotations 字段用来扩展 Ingress Controller 的功能,允许用户为 Ingress 资源配置额外的行为、优化、功能开关等。这些注解不会影响 Kubernetes 对象本身的选择或调度,而是通过 Ingress Controller(如 NGINX Ingress Controller)来解读和执行。

Ingress Controller 在启动时会解析 Ingress 资源的 annotations 字段,基于这些键值对来动态地调整其行为和配置。例如:配置负载均衡策略(如轮询、最小连接数等)、启用/禁用某些功能(如 SSL 重定向、路径重写、请求限流等)、配置超时、请求体大小限制等(如 proxy body size)。这些功能的生效并不依赖 Kubernetes 内部的调度或选择机制,而是通过解析注解并修改 Ingress Controller 的行为来实现的。

Annotations的优点:

1、灵活性和扩展性:Annotations 提供了一种非侵入式的方式来扩展 Kubernetes 对象的功能,而不需要改变资源本身的结构。

2、避免改变标准 API:Kubernetes 本身的 API 设计是通用的,Annotations 允许在不更改核心 API 的情况下,实现不同的功能扩展和个性化需求。

3、向后兼容:Ingress Controller 可以检查是否存在特定的注解来决定是否启用某些功能,因此可以方便地进行版本控制和逐步过渡。

本次实验与之前的章节进行区分,使用Helm进行安装。(Helm的安装此处省略,详情请参看之前的Helm&Operate章节)

添加Ingress Nginx Controller仓库,并拉取对应的安装包

[root@master-01 ~]# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
"ingress-nginx" has been added to your repositories
[root@master-01 ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "ingress-nginx" chart repository
Update Complete. ⎈Happy Helming!⎈
[root@master-01 ~]# helm pull ingress-nginx/ingress-nginx --version 4.11.3

解压软件包,切换到ingress-nginx目录下,修改对应配置

[root@master-01 ingress-nginx]# vim values.yaml

1、修改拉取的镜像地址,此处不再赘述了。可以选择代理拉取、本地克隆或者直接寻找国内镜像(如果拉取国内镜像的,需要将镜像的digest值注释)

2、hostNetwork字段的值设为true

hostNetwork: true

3、修改dnsPolicy字段的值为ClusterFirstWithHostNet

dnsPolicy: ClusterFirstWithHostNet

4、添加节点选择的标签 ingress: true(添加双引号,变成字符串)

nodeSelector:
  kubernetes.io/os: linux
  ingress: "true"

5、容器部署类型修改为DaemonSet

  # -- Use a `DaemonSet` or `Deployment`
  kind: DaemonSet

6、修改Service类型为NodePort

service:
...省略部分输出...
    labels: {}
    # -- Type of the external controller service.
    # Ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
    type: NodePort

7、 将ingress nginx 设置为默认的 ingressClass

  ingressClassResource:
    # -- Name of the IngressClass
    name: nginx
    # -- Create the IngressClass or not
    enabled: true
    # -- If true, Ingresses without `ingressClassName` get assigned to this IngressClass on creation.
    # Ingress creation gets rejected if there are multiple default IngressClasses.
    # Ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#default-ingress-class
    default: true  #原本为false
    # -- Annotations to be added to the IngressClass resource.
    annotations: {}
    # -- Controller of the IngressClass. An Ingress Controller looks for IngressClasses it should reconcile by this value.
    # This value is also being set as the `--controller-class` argument of this Ingress Controller.
    # Ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-class
    controllerValue: k8s.io/ingress-nginx

修改完成后,对部署的节点添加标签

[root@master-01 ingress-nginx]# kubectl label nodes node-02 ingress=true
node/node-02 labeled

创建对应的命名空间后,创建ingress-nginx

[root@master-01 ingress-nginx]# kubectl create ns ingress-nginx
[root@master-01 ingress-nginx]# helm install ingress-nginx . -n ingress-nginx
NAME: ingress-nginx
LAST DEPLOYED: Wed Dec  4 14:35:10 2024
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
Get the application URL by running these commands:
  export HTTP_NODE_PORT=$(kubectl get service --namespace ingress-nginx ingress-nginx-controller --output jsonpath="{.spec.ports[0].nodePort}")
  export HTTPS_NODE_PORT=$(kubectl get service --namespace ingress-nginx ingress-nginx-controller --output jsonpath="{.spec.ports[1].nodePort}")
  export NODE_IP="$(kubectl get nodes --output jsonpath="{.items[0].status.addresses[1].address}")"

  echo "Visit http://${NODE_IP}:${HTTP_NODE_PORT} to access your application via HTTP."
  echo "Visit https://${NODE_IP}:${HTTPS_NODE_PORT} to access your application via HTTPS."

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

注意:作者在部署时,遇到了报错,此处与读者分享

在使用境外机器下载镜像到国内时,下载没有出现问题,但是推送到阿里云镜像仓库时,出现报错

ctr: content digest sha256:e1fc6d49e0e3225374e9fe43a4ebbdd791e9b0d821b2f4d4da2b9f1af3b57736: not found

解决方法:在拉取镜像时,添加--all-platforms参数。所以建议读者使用Github的方法进行克隆

[root@master-03 ~]# ctr images pull registry.k8s.io/ingress-nginx/controller:v1.11.3 --all-platforms

使用Helm install安装时报错如下

[root@master-01 ingress-nginx]# helm install ingress-nginx -n ingress-study .
Error: INSTALLATION FAILED: Unable to continue with install: ClusterRole "ingress-nginx" in namespace "" exists and cannot be imported into the current release: invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "ingress-nginx"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ingress-study"

根据报错,可以观察到有ClusterRole资源已经存在了,原因是在之前实验时,没有清理环境,将对应的ClusterRole资源删除,后面报错提示有些KV不对应,原因也是旧的资源没有清理

[root@master-01 ~]# kubectl delete -f ingress.yaml
namespace "ingress-nginx" deleted
serviceaccount "ingress-nginx" deleted
serviceaccount "ingress-nginx-admission" deleted
role.rbac.authorization.k8s.io "ingress-nginx" deleted
role.rbac.authorization.k8s.io "ingress-nginx-admission" deleted
clusterrole.rbac.authorization.k8s.io "ingress-nginx" deleted
clusterrole.rbac.authorization.k8s.io "ingress-nginx-admission" deleted
rolebinding.rbac.authorization.k8s.io "ingress-nginx" deleted
...省略部分输出...

继续安装,仍然提示等待前置资源超时

[root@master-01 ingress-nginx]# helm install ingress-nginx -n ingress-nginx .
Error: INSTALLATION FAILED: failed pre-install: 1 error occurred:
        * timed out waiting for the condition

解决思路和方法:查看在ingress-nginx命名空间下创建的镜像,可以观察到Pod拉取镜像失败

[root@master-01 ingress-nginx]# kubectl get pods -n ingress-nginx
NAME                                   READY   STATUS             RESTARTS   AGE
ingress-nginx-admission-create-zxj49   0/1     ImagePullBackOff   0          2m

通过describe命令查看具体的镜像,手动拉取镜像,或者是修改values.yaml文件内镜像的下载地址

 Warning  Failed     26s (x3 over 111s)   kubelet            Error: ErrImagePull
  Normal   BackOff    0s (x4 over 111s)    kubelet            Back-off pulling image "registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4"
  Warning  Failed     0s (x4 over 111s)    kubelet            Error: ImagePullBackOff

创建实验使用的命名空间

[root@master-01 ~]# kubectl create namespace study-ingress
namespace/study-ingress created

创建Nginx容器模拟Web服务,并且暴露该容器的80端口

root@master-01 ~]# kubectl create deploy nginx --image=registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:1.15.12 -n study-ingress
deployment.apps/nginx created
[root@master-01 ~]# kubectl expose deployment nginx --port 80 -n study-ingress
service/nginx exposed

创建Ingress服务指向Service

[root@master-01 ~]# vim web-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: study-ingress
spec:
  rules:
    - host: nginx.test.com
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              service:
                name: nginx
                port:
                  number: 80
[root@master-01 ~]# kubectl create -f web-ingress.yaml
ingress.networking.k8s.io/nginx-ingress created
[root@master-01 ~]# kubectl get -f web-ingress.yaml
NAME            CLASS   HOSTS            ADDRESS   PORTS   AGE
nginx-ingress   nginx   nginx.test.com             80      5s

切换到node-02,读者需要注意,由于使用了hostnetwork,所以Ingress将直接绑定到宿主机的网络接口,监听来自外部的流量。如果 Service 是类型为 ClusterIP 或 NodePort,它的端口不会直接暴露到物理机上,而是仍然只在集群内部可访问。只有当你使用 LoadBalancer 类型的 Service,或者通过 Ingress 控制器的配置将流量路由到特定的服务时,流量才会通过宿主机端口暴露(作者后面将Ingress的Service修改为了LoadBalancer)

[root@master-01 ~]# kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.96.32.35     <pending>     80:30826/TCP,443:32622/TCP   11h
[root@node-02 ~]# ss -lntp | grep 80
LISTEN     0      16384        *:80                       *:*                   users:(("nginx",pid=36012,fd=15),("nginx",pid=35183,fd=15))
LISTEN     0      16384        *:80                       *:*                   users:(("nginx",pid=36012,fd=7),("nginx",pid=35182,fd=7))
LISTEN     0      16384     [::]:80                    [::]:*                   users:(("nginx",pid=36012,fd=8),("nginx",pid=35182,fd=8))
LISTEN     0      16384     [::]:80                    [::]:*                   users:(("nginx",pid=36012,fd=16),("nginx",pid=35183,fd=16))
[root@node-02 ~]#  ss -lntp | grep 443
LISTEN     0      16384        *:443                      *:*                   users:(("nginx",pid=36012,fd=17),("nginx",pid=35183,fd=17))
LISTEN     0      16384        *:443                      *:*                   users:(("nginx",pid=36012,fd=9),("nginx",pid=35182,fd=9))
LISTEN     0      16384     [::]:8443                  [::]:*                   users:(("nginx-ingress-c",pid=35846,fd=12))
LISTEN     0      16384     [::]:443                   [::]:*                   users:(("nginx",pid=36012,fd=10),("nginx",pid=35182,fd=10))
LISTEN     0      16384     [::]:443                   [::]:*                   users:(("nginx",pid=36012,fd=18),("nginx",pid=35183,fd=18))

修改物理机上的hosts文件,添加映射,通过浏览器直接访问域名(注意:LoadBalancer类型访问不需要加端口)

192.168.132.172 nginx.test.com

nginx.test.com

Ingress Nginx 域名重定向 Redirect

在 Nginx 作为代理服务器时,Redirect 可用于域名的重定向,比如访问 old.com 被重定向到new.com。Ingress 可以更简单的实现 Redirect 功能,接下来用 nginx.redirect.com 作为旧域名,blog.caijxlinux.work 作为新域名进行演示

[root@master-01 ~]# vim redirect.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-redirect
  namespace: study-ingress
  annotations:
    nginx.ingress.kubernetes.io/permanent-redirect: https://blog.caijxlinux.work
spec:
  rules:
  - host: nginx.redirect.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx
            port:
              number: 80
[root@master-01 ~]# kubectl create -f redirect.yaml
ingress.networking.k8s.io/nginx-redirect created

通过curl命令访问域名nginx.redirect.com,可以看到 301(请求被重定向的返回值)

[root@master-01 ~]# curl -I -H "Host:nginx.redirect.com" 192.168.132.173
HTTP/1.1 301 Moved Permanently
Date: Wed, 04 Dec 2024 18:40:02 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://blog.caijxlinux.work

Ingress Nginx 前后端分离 Rewrite

前后端分离常常涉及到将请求从前端(通常是静态资源)和后端(通常是 API 服务)进行分离和路由。Rewrite 规则可以用于将请求路径改写为后端服务需要的路径,以便让前端和后端在不同的路径上运行,且能够无缝协作

案例:假设有一个后端服务,托管在根路径下,而前端 API 服务托管在 /api-a 路径下。你希望用户通过访问 nginx.test.com/api-a 来访问后端。

在这种情况下,Ingress Nginx 的配置可以使用 Rewrite-target 注解来实现路径的重写

创建一个Nginx容器模拟后端服务并暴露80端口,该容器只有根路径,并没有/api-a路径的对应页面

[root@master-01 ~]# kubectl create deploy backend-api --image=registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:backend-api -n study-ingress
deployment.apps/backend-api created
[root@master-01 ~]# kubectl expose deploy backend-api --port 80 -n study-ingress
service/backend-api exposed

验证路径

[root@master-01 ~]# kubectl get svc -n study-ingress
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
backend-api   ClusterIP   10.96.17.221    <none>        80/TCP    9s
nginx         ClusterIP   10.96.135.131   <none>        80/TCP    108m
[root@master-01 ~]# curl 10.96.17.221/api-a
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.15.12</center>
</body>
</html>
[root@master-01 ~]# curl 10.96.17.221
<h1> backend for ingress rewrite </h1>

<h2> Path: /api-a </h2>

<a href="http://gaoxin.kubeasy.com"> Kubeasy </a>

配置Ingress rewrite功能,将/api-a 重写为"/"。

1、nginx.ingress.kubernetes.io/rewrite-target: /$2:这个注解将请求路径中的第二个捕获组(即 /api-a/ 后面的部分)作为新路径传递给后端服务。比如,访问 /api-a/foo 会被重写为 /foo。
2、path: /api-a(/|$)(.*):使用正则表达式匹配 /api-a 后面的路径。它会捕获 /api-a/ 后面的所有内容,并通过 $2 传递给后端服务。
[root@master-01 ~]# vim rewrite.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: backend-api
  namespace: study-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  rules:
  - host: nginx.test.com
    http:
      paths:
      - path: /api-a(/|$)(.*)
        pathType: ImplementationSpecific
        backend:
          service:
            name: backend-api
            port:
              number: 80
[root@master-01 ~]# kubectl create -f rewrite.yaml
ingress.networking.k8s.io/backend-api created

修改任意主机的/etc/hosts文件,增加映射

[root@node-02 ~]# vim /etc/hosts
192.168.132.173 nginx.test.com

通过curl命令访问,可以观察到/api-a路径被重定向到根目录下

[root@node-02 ~]# curl nginx.test.com/api-a
<h1> backend for ingress rewrite </h1>

<h2> Path: /api-a </h2>

Ingress Nginx 错误代码重定向

当访问某个服务时遇到 404 或 500 错误,可以将用户重定向到一个自定义的错误页面或其他 URL

创建具有错误代码重定向功能的Ingress

[root@master-01 ~]# cat error.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: error-redirect-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/error-pages: "404,500"
    nginx.ingress.kubernetes.io/custom-http-errors: "404,500"
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/permanent-redirect: "https://blog.caijxlinux.work"
spec:
  rules:
  - host: nginx.test.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              number: 80
参数 解析
nginx.ingress.kubernetes.io/error-pages: "404,500" 定义了在发生错误时要捕获的 HTTP 错误代码,这里是 404 和 500 错误
nginx.ingress.kubernetes.io/custom-http-errors: "404,500" Nginx Ingress Controller 如果返回 404 或 500 错误,应该进行自定义处理
nginx.ingress.kubernetes.io/permanent-redirect: "https://blog.caijxlinux.work" 当错误发生时,用户会被重定向到指定的 URL(这里是"https://blog.caijxlinux.work")。这个 URL 可以是一个外部错误页面,或者集群内部的服务页面。
nginx.ingress.kubernetes.io/rewrite-target: / 确保请求的路径会被重写成 /,让错误页面可以处理该路径
[root@master-01 ~]# kubectl create -f error.yaml
ingress.networking.k8s.io/error-redirect-ingress created

修改/etc/hosts文件,增加映射,尝试访问一个不存在的界面,可以观察到错误界面被重定向

[root@master-01 ~]# curl -I http://nginx.test.com/test
HTTP/1.1 301 Moved Permanently
Date: Wed, 04 Dec 2024 20:00:32 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://blog.caijxlinux.work

第二种方法是修改values.yaml文件,启用defaultbackend功能(把上面方法的ingress删除掉才能使用第二种方法)

[root@master-01 ingress-nginx]# vim values.yaml
defaultBackend:
  ##
  enabled: true
  name: defaultbackend
  image:
    registry: registry.cn-guangzhou.aliyuncs.com
    image: caijxlinux/defaultbackend-amd64
    ## for backwards compatibility consider setting the full image url via the repository value below
    ## use *either* current default registry/image or repository format or installing chart by providing the values.yaml will fail
    ## repository:
    tag: "1.5"
    pullPolicy: IfNotPresent

修改ingress-nginx-controller的ConfigMap

[root@master-01 ingress-nginx]# kubectl edit cm -n ingress-nginx ingress-nginx-controller
data:
  allow-snippet-annotations: "false"
  apiVersion: v1
  client_max_body_size: 20m
  custom-http-errors: 404,503

更新Release

[root@master-01 ingress-nginx]#  helm upgrade ingress-nginx -n ingress-nginx .
Release "ingress-nginx" has been upgraded. Happy Helming!
...省略部分输出...

访问不存在的界面,已经重定向到了自定义的错误界面

[root@master-01 ingress-nginx]# curl nginx.test.com/caijx
default backend - 404

Ingress Nginx SSL

使用 Nginx Ingress Controller 配置 SSL(HTTPS)是一个常见的需求,尤其是在生产环境中需要为应用程序提供加密的安全通信。可以通过配置 Ingress 资源来启用 SSL,并使用 Kubernetes 的 Secret 存储 SSL 证书和私钥

使用 OpenSSL 生成一个测试证书

[root@master-01 ~]# openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=nginx.test.com"
Generating a 2048 bit RSA private key
................................+++
.........................................................................................................................................................+++
writing new private key to 'tls.key'
-----

生成secret

[root@master-01 ~]# kubectl create secret tls ca-secret --cert=tls.crt --key=tls.key -n study-ingress
secret/ca-secret created

配置 Ingress 添加 TLS 配置

[root@master-01 ~]# vim ingress-ssl.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: study-ingress
spec:
  ingressClassName: nginx  # 指定使用 nginx Ingress Controller
  rules:
  - host: nginx.test.com  # 定义主机名
    http:
      paths:
      - path: /  # 路径匹配,匹配根路径
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx  # 后端服务名
            port:
              number: 80  # 后端服务端口
  tls:
  - hosts:
    - nginx.test.com  # 定义使用 SSL 的主机名
    secretName: ca-secret  # SSL 证书存储的 Secret 名称
[root@master-01 ~]# kubectl create -f ingress-ssl.yaml
ingress.networking.k8s.io/nginx-ingress created

访问http://nginx.test.com,可以观察到已经重定向到https://nginx.test.com

[root@master-01 ~]# curl -I http://nginx.test.com
HTTP/1.1 308 Permanent Redirect
Date: Wed, 04 Dec 2024 20:43:02 GMT
Content-Type: text/html
Content-Length: 164
Connection: keep-alive
Location: https://nginx.test.com

Ingress Nginx 匹配请求头

假设有一个 Web 应用,其中需要根据不同的请求头来将流量路由到不同的后端服务。具体来说,类似某些网页,针对手机端和PC端会有不同的显示和优化,Ingress可以对User-Agent字段进行区分,将流量分发到不同的后端

部署面向移动端设备的后端,并暴露80端口

[root@master-01 ~]# kubectl create deployment phone --image=registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:phone -n study-ingress
deployment.apps/phone created
[root@master-01 ~]# kubectl expose deploy phone --port=80 -n study-ingress
service/phone exposed

创建一个名为 phone 的 Ingress 资源。配置 Ingress 规则,将来自 m.test.com 域名下所有路径 (/*) 的请求转发到名为 phone 的服务的 80 端口

[root@master-01 ~]# kubectl create ingress phone --rule=m.test.com/*=phone:80 -n study-ingress
ingress.networking.k8s.io/phone created

部署面向PC端设备的后端,并暴露80端口

[root@master-01 ~]# kubectl create deployment laptop --image=registry.cn-guangzhou.aliyuncs.com/caijxlinux/nginx:laptop -n study-ingress
deployment.apps/laptop created
[root@master-01 ~]# kubectl expose deploy laptop --port=80 -n study-ingress
service/laptop exposed

创建Ingress,基于请求头中的 User-Agent 来判断请求的客户端设备类型,如果是移动设备(如 Android 或 iPhone 等),则会将请求重定向到 m.test.com,否则正常路由到名为 laptop 的服务

[root@master-01 ~]# vim laptop-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/server-snippet: |
      set $agentflag 0;
      if ($http_user_agent ~* "(Android|iPhone|Windows Phone|UC|Kindle)") {
        set $agentflag 1;
      }
      if ($agentflag = 1) {
        return 301 http://m.test.com;
      }
  name: laptop
  namespace: study-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: test.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: laptop
            port:
              number: 80

创建资源,会提示报错

[root@master-01 ~]# kubectl create -f laptop-ingress.yaml
Error from server (BadRequest): error when creating "laptop-ingress.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: nginx.ingress.kubernetes.io/server-snippet annotation cannot be used. Snippet directives are disabled by the Ingress administrator

需要修改ingress-controller的ConfigMap,启用该注解。修改完成后重启Ingress

[root@master-01 ~]# kubectl edit configmap -n ingress-nginx ingress-nginx-controller
data:
  allow-snippet-annotations: "true"
configmap/ingress-nginx-controller edited
[root@master-01 ~]# kubectl rollout restart daemonset -n ingress-nginx ingress-nginx-controller
daemonset.apps/ingress-nginx-controller restarted

成功创建Ingress资源

[root@master-01 ~]# kubectl create -f laptop-ingress.yaml
ingress.networking.k8s.io/laptop created

在/etc/hosts文件添加对应映射,通过不同的User-Agent进行访问,可以观察到如果对应的用户头为移动端设置,会自动跳转到m.test.com

[root@master-01 ~]# curl -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)" http://test.com/
<h1> Access From Laptop </h1>
[root@master-01 ~]# curl -H "User-Agent: iPhone" http://test.com/ -I
HTTP/1.1 301 Moved Permanently
Date: Wed, 04 Dec 2024 21:17:03 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: http://m.test.com

Ingress Nginx 基本认证

有些网站可能需要通过密码来访问,对于这类网站可以使用 Nginx 的 basic-auth 设置密码访 问,具体方法如下,由于需要使用 htpasswd 工具,所以需要安装 httpd

使用 htpasswd 创建 foo 用户的密码

[root@master-01 ~]# htpasswd -c auth foo
New password:
Re-type new password:
Adding password for user foo

基于生成的密码文件生成Secret

[root@master-01 ~]# kubectl create secret generic basic-auth --from-file auth -n study-ingress
secret/basic-auth created

创建具有认证功能的Ingress

[root@master-01 ~]# vim ingrees-auth.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-with-auth
  namespace: study-ingress
  annotations:
    # kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/auth-realm: "Please Input Your Username and Password"
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-type: basic
spec:
  ingressClassName: nginx  # for k8s >= 1.22+
  rules:
  - host: auth.test.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx
            port:
              number: 80

在任意节点修改/etc/hosts文件,添加映射,此处以master-03为例

[root@master-03 ~]# vim /etc/hosts
192.168.132.173 node-02 auth.test.com

访问时不添加账号和密码,访问报错

[root@master-03 ~]# curl -v -H "Host: auth.test.com" http://192.168.132.173
* About to connect() to 192.168.132.173 port 80 (#0)
*   Trying 192.168.132.173...
* Connected to 192.168.132.173 (192.168.132.173) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Accept: */*
> Host: auth.test.com
>
< HTTP/1.1 401 Unauthorized
< Date: Thu, 05 Dec 2024 16:40:05 GMT
< Content-Type: text/html
< Content-Length: 172
< Connection: keep-alive
< WWW-Authenticate: Basic realm="Please Input Your Username and Password"
<
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host 192.168.132.173 left intact

添加正确的账号和密码连接Ingress,正常访问界面

[root@master-03 ~]# curl -v -H "Host: auth.test.com" http://192.168.132.173 -u 'foo:123'
* About to connect() to 192.168.132.173 port 80 (#0)
*   Trying 192.168.132.173...
* Connected to 192.168.132.173 (192.168.132.173) port 80 (#0)
* Server auth using Basic with user 'foo'
> GET / HTTP/1.1
> Authorization: Basic Zm9vOjEyMw==
> User-Agent: curl/7.29.0
> Accept: */*
> Host: auth.test.com
>
< HTTP/1.1 200 OK
< Date: Thu, 05 Dec 2024 16:42:34 GMT
< Content-Type: text/html
< Content-Length: 612
< Connection: keep-alive
< Last-Modified: Tue, 16 Apr 2019 13:08:19 GMT
< ETag: "5cb5d3c3-264"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<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>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host 192.168.132.173 left intact

Ingress Nginx 黑/白名单

在 Kubernetes 中使用 Ingress 配置 Nginx 进行 IP 黑/白名单控制,可以通过 nginx.ingress.kubernetes.io/whitelist-source-range 和 nginx.ingress.kubernetes.io/deny 注解来实现

配置黑名单禁止某一个或某一段 IP,需要在 Nginx Ingress 的 ConfigMap 中配置,比如将192.168.132.169(多个配置逗号分隔)添加至黑名单

[root@master-01 ~]# kubectl edit configmaps -n ingress-nginx ingress-nginx-controller
data:
  block-cidrs: 192.168.132.169
configmap/ingress-nginx-controller edited

重启Ingress

[root@master-01 ~]# kubectl rollout restart daemonset -n ingress-nginx ingress-nginx-controller
daemonset.apps/ingress-nginx-controller restarted

在master-01节点的/etc/hosts文件添加映射,尝试访问,提示403 Forbidden

[root@master-01 ~]# curl -I auth.test.com
HTTP/1.1 403 Forbidden
Date: Thu, 05 Dec 2024 18:09:20 GMT
Content-Type: text/html
Content-Length: 146
Connection: keep-alive
[root@master-01 ~]# curl m.test.com
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>

在master-03节点可以正常访问界面(401是因为没有输入账号和密码)

[root@master-03 ~]# curl -I auth.test.com
HTTP/1.1 401 Unauthorized
Date: Thu, 05 Dec 2024 18:09:36 GMT
Content-Type: text/html
Content-Length: 172
Connection: keep-alive
WWW-Authenticate: Basic realm="Please Input Your Username and Password"

白名单表示只允许某个 IP 可以访问,直接在 yaml 文件中配置即可(也可以通过 ConfigMap 配置),比如只允许 192.168.132.170 访问,只需要添加一个 nginx.ingress.kubernetes.io/whitelist- source-range 注释即可

重写ingress-auth.yaml配置文件

[root@master-01 ~]# vim ingrees-auth.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-with-auth
  namespace: study-ingress
  annotations:
    kubernetes.io/ingress.class: nginx  # K8s >= 1.22+ 使用 ingressClassName 替代
    nginx.ingress.kubernetes.io/auth-realm: "Please Input Your Username and Password"
    nginx.ingress.kubernetes.io/auth-secret: basic-auth
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/whitelist-source-range: "192.168.132.170"
spec:
  rules:
  - host: auth.test.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx
            port:
              number: 80

更新Ingress配置文件

[root@master-01 ~]# kubectl replace -f ingrees-auth.yaml
ingress.networking.k8s.io/ingress-with-auth replaced

在master-02的/etc/hosts文件添加映射,测试访问,可以正常访问

[root@master-02 ~]# curl -I auth.test.com
HTTP/1.1 401 Unauthorized
Date: Thu, 05 Dec 2024 18:17:43 GMT
Content-Type: text/html
Content-Length: 172
Connection: keep-alive
WWW-Authenticate: Basic realm="Please Input Your Username and Password"

在master-01和master-03节点访问,访问失败

[root@master-01 ~]# curl -I auth.test.com
HTTP/1.1 403 Forbidden
Date: Thu, 05 Dec 2024 18:18:44 GMT
Content-Type: text/html
Content-Length: 146
Connection: keep-alive
[root@master-03 ~]# curl -I auth.test.com
HTTP/1.1 403 Forbidden
Date: Thu, 05 Dec 2024 18:18:36 GMT
Content-Type: text/html
Content-Length: 146
Connection: keep-alive

Ingress Nginx 速率限制

速率限制常用于防止恶意用户对应用程序发起暴力破解攻击、保护暴露的 API,避免滥用和过度请求、防止 DDoS(分布式拒绝服务)攻击的一个有效手段、对于一些资源消耗较大的接口(如数据处理、报告生成等),可以使用速率限制来确保系统负载不会过大,避免某个请求或某类请求耗尽资源,影响整体服务稳定性。速率限制是一项重要的安全功能,帮助管理系统负载、保护应用免受恶意攻击、以及保障公平性和系统稳定性。通过 Nginx Ingress Controller 提供的注解,可以轻松地对不同的请求设置合适的速率限制,确保系统在高流量环境下的可用性和响应能力

首先没有加速率限制,使用 ab 进行访问,Failed 为 0(由于上面的实验限制,所以在master-02上测试)

[root@master-02 ~]# ab -c 10 -n 100 http://auth.test.com/ | grep requests
Complete requests:      100
Failed requests:        0
Time per request:       0.730 [ms] (mean, across all concurrent requests)

删除旧的有白名单限制的Ingress配置,添加新的具有速率限制功能的Ingress

[root@master-01 ~]# vim rate-limit.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: customer-api-ingress
  namespace: study-ingress
  annotations:
    #nginx.ingress.kubernetes.io/limit-rps: "1"  # 每秒最多 1 个请求
    #nginx.ingress.kubernetes.io/limit-burst-multiplier: "1"  # 突发流量最大 1 个请求
    nginx.ingress.kubernetes.io/limit-connections: "1"  # 每个客户最大连接数为 1
spec:
  rules:
  - host: auth.test.com
    http:
      paths:
      - path: /
        pathType: ImplementationSpecific
        backend:
          service:
            name: nginx
            port:
              number: 80

构建Ingress

[root@master-01 ~]# kubectl create -f rate-limit.yaml
ingress.networking.k8s.io/customer-api-ingress created

master-02进行验证,可以观察到访问已经被限制(注意:此处有一个问题,只要replace文件后,限制就会失败)

[root@master-02 ~]# ab -c 10 -n 100 http://auth.test.com/ | grep requests
Complete requests:      100
Failed requests:        77
Time per request:       1.145 [ms] (mean, across all concurrent requests)

Ingress Nginx 实现灰度/金丝雀发布

灰度发布(Grey Release)和金丝雀发布(Canary Release)都是软件部署中常见的策略,旨在通过逐步推出新版本,减少系统风险,确保发布的新版本不会对整个系统造成负面影响。这两种策略在 Kubernetes 环境下,尤其是在结合 Ingress 和 Kubernetes Deployments 时,常被用于控制流量的分配和逐步升级。

灰度发布(Grey Release):灰度发布是指将新版本的功能逐渐推送到一部分用户或流量中,而不是一次性更新所有用户。在灰度发布中,可以根据不同的用户群体、地区或者其他标识进行分阶段发布。常见的做法是,先将新版本发布给一部分用户,然后根据反馈决定是否扩大范围。

金丝雀发布(Canary Release):金丝雀发布是一种逐步部署新版本的策略,通常将一小部分流量(例如 5% 或 10%)引导到新版本,观察新版本的稳定性和性能表现。如果没有问题,再将更多流量逐步引导到新版本,最终完成全面发布。

创建模拟 Production(生产)环境的 Namespace 和服务

[root@master-01 ~]# kubectl create ns production
namespace/production created
[root@master-01 ~]# kubectl create deployment canary-v1 --image=registry.cn-guangzhou.aliyuncs.com/caijxlinux/canary:v1 -n production
deployment.apps/canary-v1 created
[root@master-01 ~]# kubectl expose deployment -n production canary-v1 --port 8080
service/canary-v1 exposed
[root@master-01 ~]# kubectl create ingress canary-v1 --rule=canary.com/*=canary-v1:8080 -n production
ingress.networking.k8s.io/canary-v1 created

检测网页状态(记得把黑名单删掉,否则在master-01节点无法测试)

[root@master-01 ~]# curl canary.com
<h1>Canary v1</h1>

创建 v2 版本,充当灰度环境

[root@master-01 ~]# kubectl create ns canary
namespace/canary created
[root@master-01 ~]# kubectl create deployment canary-v2 --image=registry.cn-
guangzhou.aliyuncs.com/caijxlinux/canary:v2 -n canary
[root@master-01 ~]# kubectl expose deploy canary-v2 --port 8080 -n canary

容器启动完成后,通过 Service 访问该服务,会返回 Canary v2

[root@master-01 ~]# kubectl get svc -n canary
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
backend-api   ClusterIP   10.96.17.223    <none>        8080/TCP    9s
[root@master-01 ~]# curl 10.96.17.223:8080
<h1>Canary v2</h1>

创建具有金丝雀功能的Ingress,创建 v2 版本的 Ingress 时,需要添加两个注释,一个是 nginx.ingress.kubernetes.io/canary,表明是灰度环境,nginx.ingress.kubernetes.io/canary-weight 表明切多少流量到该环境,本示例为10%

[root@master-01 ~]# vim canary-v2.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: canary-v2
  namespace: canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"  # 启用金丝雀发布
    nginx.ingress.kubernetes.io/canary-weight: "10"  # 10% 的流量到新版本
spec:
  ingressClassName: nginx  # 对应 Nginx Ingress Controller,K8s >= 1.22+
  rules:
  - host: canary.com
    http:
      paths:
      - path: /  # 默认路由路径
        pathType: ImplementationSpecific
        backend:
          service:
            name: canary-v2  # 指向 canary-v2 服务
            port:
              number: 8080  # 服务端口
[root@master-01 ~]# Kubectl create -f canary-v2.yaml
ingress.networking.k8s.io/canary-v2 created

使用Shell脚本进行测试,此脚本会输出 v1 和 v2 的访问次数比值

[root@master-01 ~]# vim test-canary.sh
#!/bin/bash

# 初始化计数器
v1_count=0
v2_count=0

# 循环访问网站 100 次
for i in {1..100}
do
  # 获取网站内容
  output=$(curl -s canary.com | awk -F '[ <]' '{prinf $3}')

  # 检查是否包含 v1 或 v2
  if [[ "$output" == *"v1"* ]]; then
    ((v1_count++))
  elif [[ "$output" == *"v2"* ]]; then
    ((v2_count++))
  fi
done

# 打印结果
echo "v1 count: $v1_count"
echo "v2 count: $v2_count"
[root@master-01 ~]# chmod +x test-canary.sh
[root@master-01 ~]# sh test-canary.sh
v1 count: 91
v2 count: 9