Kubernetes1.22安装使用ingress-nginx

运维 系统运维
我们知道 Kubernetes 控制器使用控制循环模式来检查控制器中所需的状态是否已更新或是否需要变更,所以 ingress-nginx 需要使用集群中的不同对象来构建模型。

[[441487]]

我们已经了解了 Ingress 资源对象只是一个路由请求描述配置文件,要让其真正生效还需要对应的 Ingress 控制器才行,Ingress 控制器有很多,这里我们先介绍使用最多的 ingress-nginx,它是基于 Nginx 的 Ingress 控制器。

运行原理

ingress-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 进行重新加载显然会造成大量的性能消耗,所以要尽可能减少出现重新加载的场景。

安装

由于 ingress-nginx 所在的节点需要能够访问外网(不是强制的),这样域名可以解析到这些节点上直接使用,所以需要让 ingress-nginx 绑定节点的 80 和 443 端口,所以可以使用 hostPort 来进行访问,当然对于线上环境来说为了保证高可用,一般是需要运行多个 ·ingress-nginx 实例的,然后可以用一个 nginx/haproxy 作为入口,通过 keepalived 来访问边缘节点的 vip 地址。

!!! info "边缘节点" 所谓的边缘节点即集群内部用来向集群外暴露服务能力的节点,集群外部的服务通过该节点来调用集群内部的服务,边缘节点是集群内外交流的一个 Endpoint。

这里我们使用 Helm Chart(后面会详细讲解)的方式来进行安装:

  1. # 如果你不喜欢使用 helm chart 进行安装也可以使用下面的命令一键安装 
  2. # kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml 
  3. ➜ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 
  4. ➜ helm repo update 
  5. ➜ helm fetch ingress-nginx/ingress-nginx 
  6. ➜ tar -xvf ingress-nginx-4.0.13.tgz && cd ingress-nginx 
  7. ➜ tree . 
  8. ├── CHANGELOG.md 
  9. ├── Chart.yaml 
  10. ├── OWNERS 
  11. ├── README.md 
  12. ├── ci 
  13. │   ├── controller-custom-ingressclass-flags.yaml 
  14. │   ├── daemonset-customconfig-values.yaml 
  15. │   ├── daemonset-customnodeport-values.yaml 
  16. │   ├── daemonset-headers-values.yaml 
  17. │   ├── daemonset-internal-lb-values.yaml 
  18. │   ├── daemonset-nodeport-values.yaml 
  19. │   ├── daemonset-podannotations-values.yaml 
  20. │   ├── daemonset-tcp-udp-configMapNamespace-values.yaml 
  21. │   ├── daemonset-tcp-udp-values.yaml 
  22. │   ├── daemonset-tcp-values.yaml 
  23. │   ├── deamonset-default-values.yaml 
  24. │   ├── deamonset-metrics-values.yaml 
  25. │   ├── deamonset-psp-values.yaml 
  26. │   ├── deamonset-webhook-and-psp-values.yaml 
  27. │   ├── deamonset-webhook-values.yaml 
  28. │   ├── deployment-autoscaling-behavior-values.yaml 
  29. │   ├── deployment-autoscaling-values.yaml 
  30. │   ├── deployment-customconfig-values.yaml 
  31. │   ├── deployment-customnodeport-values.yaml 
  32. │   ├── deployment-default-values.yaml 
  33. │   ├── deployment-headers-values.yaml 
  34. │   ├── deployment-internal-lb-values.yaml 
  35. │   ├── deployment-metrics-values.yaml 
  36. │   ├── deployment-nodeport-values.yaml 
  37. │   ├── deployment-podannotations-values.yaml 
  38. │   ├── deployment-psp-values.yaml 
  39. │   ├── deployment-tcp-udp-configMapNamespace-values.yaml 
  40. │   ├── deployment-tcp-udp-values.yaml 
  41. │   ├── deployment-tcp-values.yaml 
  42. │   ├── deployment-webhook-and-psp-values.yaml 
  43. │   ├── deployment-webhook-resources-values.yaml 
  44. │   └── deployment-webhook-values.yaml 
  45. ├── templates 
  46. │   ├── NOTES.txt 
  47. │   ├── _helpers.tpl 
  48. │   ├── _params.tpl 
  49. │   ├── admission-webhooks 
  50. │   │   ├── job-patch 
  51. │   │   │   ├── clusterrole.yaml 
  52. │   │   │   ├── clusterrolebinding.yaml 
  53. │   │   │   ├── job-createSecret.yaml 
  54. │   │   │   ├── job-patchWebhook.yaml 
  55. │   │   │   ├── psp.yaml 
  56. │   │   │   ├── role.yaml 
  57. │   │   │   ├── rolebinding.yaml 
  58. │   │   │   └── serviceaccount.yaml 
  59. │   │   └── validating-webhook.yaml 
  60. │   ├── clusterrole.yaml 
  61. │   ├── clusterrolebinding.yaml 
  62. │   ├── controller-configmap-addheaders.yaml 
  63. │   ├── controller-configmap-proxyheaders.yaml 
  64. │   ├── controller-configmap-tcp.yaml 
  65. │   ├── controller-configmap-udp.yaml 
  66. │   ├── controller-configmap.yaml 
  67. │   ├── controller-daemonset.yaml 
  68. │   ├── controller-deployment.yaml 
  69. │   ├── controller-hpa.yaml 
  70. │   ├── controller-ingressclass.yaml 
  71. │   ├── controller-keda.yaml 
  72. │   ├── controller-poddisruptionbudget.yaml 
  73. │   ├── controller-prometheusrules.yaml 
  74. │   ├── controller-psp.yaml 
  75. │   ├── controller-role.yaml 
  76. │   ├── controller-rolebinding.yaml 
  77. │   ├── controller-service-internal.yaml 
  78. │   ├── controller-service-metrics.yaml 
  79. │   ├── controller-service-webhook.yaml 
  80. │   ├── controller-service.yaml 
  81. │   ├── controller-serviceaccount.yaml 
  82. │   ├── controller-servicemonitor.yaml 
  83. │   ├── default-backend-deployment.yaml 
  84. │   ├── default-backend-hpa.yaml 
  85. │   ├── default-backend-poddisruptionbudget.yaml 
  86. │   ├── default-backend-psp.yaml 
  87. │   ├── default-backend-role.yaml 
  88. │   ├── default-backend-rolebinding.yaml 
  89. │   ├── default-backend-service.yaml 
  90. │   ├── default-backend-serviceaccount.yaml 
  91. │   └── dh-param-secret.yaml 
  92. └── values.yaml 
  93.  
  94. 4 directories, 81 files 

Helm Chart 包下载下来后解压就可以看到里面包含的模板文件,其中的 ci 目录中就包含了各种场景下面安装的 Values 配置文件,values.yaml 文件中包含的是所有可配置的默认值,我们可以对这些默认值进行覆盖,我们这里测试环境就将 master1 节点看成边缘节点,所以我们就直接将 ingress-nginx 固定到 master1 节点上,采用 hostNetwork 模式(生产环境可以使用 LB + DaemonSet hostNetwork 模式),为了避免创建的错误 Ingress 等资源对象影响控制器重新加载,所以我们也强烈建议大家开启准入控制器,ingess-nginx 中会提供一个用于校验资源对象的 Admission Webhook,我们可以通过 Values 文件进行开启。然后新建一个名为 ci/daemonset-prod.yaml 的 Values 文件,用来覆盖 ingress-nginx 默认的 Values 值。

对应的 Values 配置文件如下所示:

  1. # ci/daemonset-prod.yaml 
  2. controller: 
  3.   name: controller 
  4.   image: 
  5.     repository: cnych/ingress-nginx 
  6.     tag: "v1.1.0" 
  7.     digest: 
  8.  
  9.   dnsPolicy: ClusterFirstWithHostNet 
  10.  
  11.   hostNetwork: true 
  12.  
  13.   publishService:  # hostNetwork 模式下设置为false,通过节点IP地址上报ingress status数据 
  14.     enabled: false 
  15.  
  16.   # 是否需要处理不带 ingressClass 注解或者 ingressClassName 属性的 Ingress 对象 
  17.   # 设置为 true 会在控制器启动参数中新增一个 --watch-ingress-without-class 标注 
  18.   watchIngressWithoutClass: false 
  19.  
  20.   kind: DaemonSet 
  21.  
  22.   tolerations:   # kubeadm 安装的集群默认情况下master是有污点,需要容忍这个污点才可以部署 
  23.   - key"node-role.kubernetes.io/master" 
  24.     operator: "Equal" 
  25.     effect: "NoSchedule" 
  26.  
  27.   nodeSelector:   # 固定到master1节点 
  28.     kubernetes.io/hostname: master1 
  29.  
  30.   service:  # HostNetwork 模式不需要创建service 
  31.     enabled: false 
  32.  
  33.   admissionWebhooks: # 强烈建议开启 admission webhook 
  34.     enabled: true 
  35.     createSecretJob: 
  36.       resources: 
  37.         limits: 
  38.           cpu: 10m 
  39.           memory: 20Mi 
  40.         requests: 
  41.           cpu: 10m 
  42.           memory: 20Mi 
  43.     patchWebhookJob: 
  44.       resources: 
  45.         limits: 
  46.           cpu: 10m 
  47.           memory: 20Mi 
  48.         requests: 
  49.           cpu: 10m 
  50.           memory: 20Mi 
  51.     patch: 
  52.       enabled: true 
  53.       image: 
  54.         repository: cnych/ingress-nginx-webhook-certgen 
  55.         tag: v1.1.1 
  56.         digest: 
  57.  
  58. defaultBackend:  # 配置默认后端 
  59.   enabled: true 
  60.   name: defaultbackend 
  61.   image: 
  62.     repository: cnych/ingress-nginx-defaultbackend 
  63.     tag: "1.5" 

然后使用如下命令安装 ingress-nginx 应用到 ingress-nginx 的命名空间中:

  1. ➜ kubectl create ns ingress-nginx 
  2. ➜ helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx 
  3. Release "ingress-nginx" does not exist. Installing it now. 
  4.  
  5. NAME: ingress-nginx 
  6. LAST DEPLOYED: Thu Dec 16 16:47:20 2021 
  7. NAMESPACE: ingress-nginx 
  8. STATUS: deployed 
  9. REVISION: 1 
  10. TEST SUITE: None 
  11. NOTES: 
  12. The ingress-nginx controller has been installed. 
  13. It may take a few minutes for the LoadBalancer IP to be available. 
  14. You can watch the status by running 'kubectl --namespace ingress-nginx get services -o wide -w ingress-nginx-controller' 
  15.  
  16. An example Ingress that makes use of the controller: 
  17.   apiVersion: networking.k8s.io/v1 
  18.   kind: Ingress 
  19.   metadata: 
  20.     name: example 
  21.     namespace: foo 
  22.   spec: 
  23.     ingressClassName: nginx 
  24.     rules: 
  25.       - host: www.example.com 
  26.         http: 
  27.           paths: 
  28.             - backend: 
  29.                 service: 
  30.                   name: exampleService 
  31.                   port: 
  32.                     number: 80 
  33.               path: / 
  34.     # This section is only required if TLS is to be enabled for the Ingress 
  35.     tls: 
  36.       - hosts: 
  37.         - www.example.com 
  38.         secretName: example-tls 
  39.  
  40. If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided: 
  41.  
  42.   apiVersion: v1 
  43.   kind: Secret 
  44.   metadata: 
  45.     name: example-tls 
  46.     namespace: foo 
  47.   data: 
  48.     tls.crt: <base64 encoded cert> 
  49.     tls.key: <base64 encoded key
  50.   type: kubernetes.io/tls 

部署完成后查看 Pod 的运行状态:

  1. ➜ kubectl get svc -n ingress-nginx 
  2. NAME                                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE 
  3. ingress-nginx-controller-admission   ClusterIP   10.96.15.99     <none>        443/TCP   11m 
  4. ingress-nginx-defaultbackend         ClusterIP   10.97.250.253   <none>        80/TCP    11m 
  5. ➜ kubectl get pods -n ingress-nginx 
  6. NAME                                            READY   STATUS    RESTARTS   AGE 
  7. ingress-nginx-controller-5dfdd4659c-9g7c2       1/1     Running   0          11m 
  8. ingress-nginx-defaultbackend-84854cd6cb-xb7rv   1/1     Running   0          11m 
  9. ➜ POD_NAME=$(kubectl get pods -l app.kubernetes.io/name=ingress-nginx -n ingress-nginx -o jsonpath='{.items[0].metadata.name}'
  10. ➜ kubectl exec -it $POD_NAME -n ingress-nginx -- /nginx-ingress-controller --version 
  11. kubectl logs -f ingress-nginx-controller-5dfdd4659c-9g7c2 -n ingress-nginxW1216 08:51:22.179213       7 client_config.go:615] Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work. 
  12. I1216 08:51:22.179525       7 main.go:223] "Creating API client" host="https://10.96.0.1:443" 
  13. ------------------------------------------------------------------------------- 
  14. NGINX Ingress controller 
  15.   Release:       v1.1.0 
  16.   Build:         cacbee86b6ccc45bde8ffc184521bed3022e7dee 
  17.   Repository:    https://github.com/kubernetes/ingress-nginx 
  18.   nginx version: nginx/1.19.9 
  19.  
  20. ------------------------------------------------------------------------------- 
  21.  
  22. I1216 08:51:22.198221       7 main.go:267] "Running in Kubernetes cluster" major="1" minor="22" git="v1.22.2" state="clean" commit="8b5a19147530eaac9476b0ab82980b4088bbc1b2" platform="linux/amd64" 
  23. I1216 08:51:22.200478       7 main.go:86] "Valid default backend" service="ingress-nginx/ingress-nginx-defaultbackend" 
  24. I1216 08:51:22.611100       7 main.go:104] "SSL fake certificate created" file="/etc/ingress-controller/ssl/default-fake-certificate.pem" 
  25. I1216 08:51:22.627386       7 ssl.go:531] "loading tls certificate" path="/usr/local/certificates/cert" key="/usr/local/certificates/key" 
  26. I1216 08:51:22.651187       7 nginx.go:255] "Starting NGINX Ingress controller" 

当看到上面的信息证明 ingress-nginx 部署成功了,这里我们安装的是最新版本的控制器,安装完成后会自动创建一个 名为 nginx 的 IngressClass 对象:

  1. ➜ kubectl get ingressclass 
  2. NAME    CONTROLLER             PARAMETERS   AGE 
  3. nginx   k8s.io/ingress-nginx   <none>       18m 
  4. ➜ kubectl get ingressclass nginx -o yaml 
  5. apiVersion: networking.k8s.io/v1 
  6. kind: IngressClass 
  7. metadata: 
  8.   ...... 
  9.   name: nginx 
  10.   resourceVersion: "1513966" 
  11.   uid: 70340e62-cab6-4a11-9982-2108f1db786b 
  12. spec: 
  13.   controller: k8s.io/ingress-nginx 

过这里我们只提供了一个 controller 属性,如果还需要配置一些额外的参数,则可以在安装的 values 文件中进行配置。

第一个示例

安装成功后,现在我们来为一个 nginx 应用创建一个 Ingress 资源,如下所示:

  1. # my-nginx.yaml 
  2. apiVersion: apps/v1 
  3. kind: Deployment 
  4. metadata: 
  5.   name: my-nginx 
  6. spec: 
  7.   selector: 
  8.     matchLabels: 
  9.       app: my-nginx 
  10.   template: 
  11.     metadata: 
  12.       labels: 
  13.         app: my-nginx 
  14.     spec: 
  15.       containers: 
  16.       - name: my-nginx 
  17.         image: nginx 
  18.         ports: 
  19.         - containerPort: 80 
  20. --- 
  21. apiVersion: v1 
  22. kind: Service 
  23. metadata: 
  24.   name: my-nginx 
  25.   labels: 
  26.     app: my-nginx 
  27. spec: 
  28.   ports: 
  29.   - port: 80 
  30.     protocol: TCP 
  31.     name: http 
  32.   selector: 
  33.     app: my-nginx 
  34. --- 
  35. apiVersion: networking.k8s.io/v1 
  36. kind: Ingress 
  37. metadata: 
  38.   name: my-nginx 
  39.   namespace: default 
  40. spec: 
  41.   ingressClassName: nginx  # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器) 
  42.   rules: 
  43.   - host: ngdemo.qikqiak.com  # 将域名映射到 my-nginx 服务 
  44.     http: 
  45.       paths: 
  46.       - path: / 
  47.         pathType: Prefix 
  48.         backend: 
  49.           service:  # 将所有请求发送到 my-nginx 服务的 80 端口 
  50.             name: my-nginx 
  51.             port: 
  52.               number: 80 
  53. # 不过需要注意大部分Ingress控制器都不是直接转发到Service 
  54. # 而是只是通过Service来获取后端的Endpoints列表,直接转发到Pod,这样可以减少网络跳转,提高性能 

直接创建上面的资源对象:

  1. ➜ kubectl apply -f my-nginx.yaml 
  2. deployment.apps/my-nginx created 
  3. service/my-nginx created 
  4. ingress.networking.k8s.io/my-nginx created 
  5. ➜ kubectl get ingress 
  6. NAME           CLASS    HOSTS                ADDRESS         PORTS   AGE 
  7. my-nginx       nginx    ngdemo.qikqiak.com   192.168.31.31   80      30m 

在上面的 Ingress 资源对象中我们使用配置 ingressClassName: nginx 指定让我们安装的 ingress-nginx 这个控制器来处理我们的 Ingress 资源,配置的匹配路径类型为前缀的方式去匹配 /,将来自域名 ngdemo.qikqiak.com 的所有请求转发到 my-nginx 服务的后端 Endpoints 中去。

上面资源创建成功后,然后我们可以将域名 ngdemo.qikqiak.com 解析到 ingress-nginx 所在的边缘节点中的任意一个,当然也可以在本地 /etc/hosts 中添加对应的映射也可以,然后就可以通过域名进行访问了。

 

 下图显示了客户端是如何通过 Ingress 控制器连接到其中一个 Pod 的流程,客户端首先对 ngdemo.qikqiak.com 执行 DNS 解析,得到 Ingress 控制器所在节点的 IP,然后客户端向 Ingress 控制器发送 HTTP 请求,然后根据 Ingress 对象里面的描述匹配域名,找到对应的 Service 对象,并获取关联的 Endpoints 列表,将客户端的请求转发给其中一个 Pod。

 

前面我们也提到了 ingress-nginx 控制器的核心原理就是将我们的 Ingress 这些资源对象映射翻译成 Nginx 配置文件 nginx.conf,我们可以通过查看控制器中的配置文件来验证这点:

  1. ➜ kubectl exec -it $POD_NAME -n ingress-nginx -- cat /etc/nginx/nginx.conf 
  2.  
  3. ...... 
  4. upstream upstream_balancer { 
  5.         server 0.0.0.1; # placeholder 
  6.         balancer_by_lua_block { 
  7.                 balancer.balance() 
  8.         } 
  9.         keepalive 320; 
  10.         keepalive_timeout  60s; 
  11.         keepalive_requests 10000; 
  12.  
  13. ...... 
  14. ## start server ngdemo.qikqiak.com 
  15. server { 
  16.         server_name ngdemo.qikqiak.com ; 
  17.  
  18.         listen 80  ; 
  19.         listen [::]:80  ; 
  20.         listen 443  ssl http2 ; 
  21.         listen [::]:443  ssl http2 ; 
  22.  
  23.         set $proxy_upstream_name "-"
  24.  
  25.         ssl_certificate_by_lua_block { 
  26.                 certificate.call() 
  27.         } 
  28.  
  29.         location / { 
  30.  
  31.                 set $namespace      "default"
  32.                 set $ingress_name   "my-nginx"
  33.                 set $service_name   "my-nginx"
  34.                 set $service_port   "80"
  35.                 set $location_path  "/"
  36.                 set $global_rate_limit_exceeding n; 
  37.                 ...... 
  38.                 proxy_next_upstream_timeout             0; 
  39.                 proxy_next_upstream_tries               3; 
  40.  
  41.                 proxy_pass http://upstream_balancer; 
  42.  
  43.                 proxy_redirect                          off
  44.  
  45.         } 
  46.  
  47. ## end server ngdemo.qikqiak.com 
  48. ...... 

我们可以在 nginx.conf 配置文件中看到上面我们新增的 Ingress 资源对象的相关配置信息,不过需要注意的是现在并不会为每个 backend 后端都创建一个 upstream 配置块,现在是使用 Lua 程序进行动态处理的,所以我们没有直接看到后端的 Endpoints 相关配置数据。

Nginx 配置

如果我们还想进行一些自定义配置,则有几种方式可以实现:使用 Configmap 在 Nginx 中设置全局配置、通过 Ingress 的 Annotations 设置特定 Ingress 的规则、自定义模板。接下来我们重点给大家介绍使用注解来对 Ingress 对象进行自定义。

Basic Auth

我们可以在 Ingress 对象上配置一些基本的 Auth 认证,比如 Basic Auth,可以用 htpasswd 生成一个密码文件来验证身份验证。

  1. ➜ htpasswd -c auth foo 
  2. New password
  3. Re-type new password
  4. Adding password for user foo 

然后根据上面的 auth 文件创建一个 secret 对象:

  1. ➜ kubectl create secret generic basic-auth --from-file=auth 
  2. secret/basic-auth created 
  3. ➜ kubectl get secret basic-auth -o yaml 
  4. apiVersion: v1 
  5. data: 
  6.   auth: Zm9vOiRhcHIxJFUxYlFZTFVoJHdIZUZQQ1dyZTlGRFZONTQ0dXVQdC4K 
  7. kind: Secret 
  8. metadata: 
  9.   name: basic-auth 
  10.   namespace: default 
  11. type: Opaque 

然后对上面的 my-nginx 应用创建一个具有 Basic Auth 的 Ingress 对象:

  1. apiVersion: networking.k8s.io/v1 
  2. kind: Ingress 
  3. metadata: 
  4.   name: ingress-with-auth 
  5.   namespace: default 
  6.   annotations: 
  7.     nginx.ingress.kubernetes.io/auth-type: basic  # 认证类型 
  8.     nginx.ingress.kubernetes.io/auth-secret: basic-auth  # 包含 user/password 定义的 secret 对象名 
  9.     nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo'  # 要显示的带有适当上下文的消息,说明需要身份验证的原因 
  10. spec: 
  11.   ingressClassName: nginx  # 使用 nginx 的 IngressClass(关联的 ingress-nginx 控制器) 
  12.   rules: 
  13.   - host: bauth.qikqiak.com  # 将域名映射到 my-nginx 服务 
  14.     http: 
  15.       paths: 
  16.       - path: / 
  17.         pathType: Prefix 
  18.         backend: 
  19.           service:  # 将所有请求发送到 my-nginx 服务的 80 端口 
  20.             name: my-nginx 
  21.             port: 
  22.               number: 80 

直接创建上面的资源对象,然后通过下面的命令或者在浏览器中直接打开配置的域名:

  1. ➜ kubectl get ingress 
  2. NAME                CLASS    HOSTS                        ADDRESS         PORTS   AGE 
  3. ingress-with-auth   nginx    bauth.qikqiak.com            192.168.31.31   80      6m55s 
  4. ➜ curl -v http://192.168.31.31 -H 'Host: bauth.qikqiak.com' 
  5. *   Trying 192.168.31.31... 
  6. * TCP_NODELAY set 
  7. * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0) 
  8. > GET / HTTP/1.1 
  9. > Host: bauth.qikqiak.com 
  10. User-Agent: curl/7.64.1 
  11. > Accept: */* 
  12. < HTTP/1.1 401 Unauthorized 
  13. Date: Thu, 16 Dec 2021 10:49:03 GMT 
  14. < Content-Type: text/html 
  15. < Content-Length: 172 
  16. Connection: keep-alive 
  17. < WWW-Authenticate: Basic realm="Authentication Required - foo" 
  18. <html> 
  19. <head><title>401 Authorization Required</title></head> 
  20. <body> 
  21. <center><h1>401 Authorization Required</h1></center> 
  22. <hr><center>nginx</center> 
  23. </body> 
  24. </html> 
  25. Connection #0 to host 192.168.31.31 left intact 
  26. * Closing connection 0 

我们可以看到出现了 401 认证失败错误,然后带上我们配置的用户名和密码进行认证:

  1. ➜ curl -v http://192.168.31.31 -H 'Host: bauth.qikqiak.com' -u 'foo:foo' 
  2. *   Trying 192.168.31.31... 
  3. * TCP_NODELAY set 
  4. * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0) 
  5. * Server auth using Basic with user 'foo' 
  6. > GET / HTTP/1.1 
  7. > Host: bauth.qikqiak.com 
  8. Authorization: Basic Zm9vOmZvbw== 
  9. User-Agent: curl/7.64.1 
  10. > Accept: */* 
  11. < HTTP/1.1 200 OK 
  12. Date: Thu, 16 Dec 2021 10:49:38 GMT 
  13. < Content-Type: text/html 
  14. < Content-Length: 615 
  15. Connection: keep-alive 
  16. Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT 
  17. < ETag: "61814ff2-267" 
  18. < Accept-Ranges: bytes 
  19. <!DOCTYPE html> 
  20. <html> 
  21. <head> 
  22. <title>Welcome to nginx!</title> 
  23. <style> 
  24. html { color-scheme: light dark; } 
  25. body { width: 35em; margin: 0 auto; 
  26. font-family: Tahoma, Verdana, Arial, sans-serif; } 
  27. </style> 
  28. </head> 
  29. <body> 
  30. <h1>Welcome to nginx!</h1> 
  31. <p>If you see this page, the nginx web server is successfully installed and 
  32. working. Further configuration is required.</p> 
  33.  
  34. <p>For online documentation and support please refer to 
  35. <a href="http://nginx.org/">nginx.org</a>.<br/> 
  36. Commercial support is available at 
  37. <a href="http://nginx.com/">nginx.com</a>.</p> 
  38.  
  39. <p><em>Thank you for using nginx.</em></p> 
  40. </body> 
  41. </html> 
  42. Connection #0 to host 192.168.31.31 left intact 
  43. * Closing connection 0 

 可以看到已经认证成功了。除了可以使用我们自己在本地集群创建的 Auth 信息之外,还可以使用外部的 Basic Auth 认证信息,比如我们使用 https://httpbin.org 的外部 Basic Auth 认证,创建如下所示的 Ingress 资源对象:

  1. apiVersion: networking.k8s.io/v1 
  2. kind: Ingress 
  3. metadata: 
  4.   annotations: 
  5.     # 配置外部认证服务地址 
  6.     nginx.ingress.kubernetes.io/auth-url: https://httpbin.org/basic-auth/user/passwd 
  7.   name: external-auth 
  8.   namespace: default 
  9. spec: 
  10.   ingressClassName: nginx 
  11.   rules: 
  12.   - host: external-bauth.qikqiak.com 
  13.     http: 
  14.       paths: 
  15.       - path: / 
  16.         pathType: Prefix 
  17.         backend: 
  18.           service: 
  19.             name: my-nginx 
  20.             port: 
  21.               number: 80 

上面的资源对象创建完成后,再进行简单的测试:

  1. ➜ kubectl get ingress 
  2. NAME                CLASS    HOSTS                        ADDRESS         PORTS   AGE 
  3. external-auth       <none>   external-bauth.qikqiak.com                   80      72s 
  4. ➜ curl -k http://192.168.31.31 -v -H 'Host: external-bauth.qikqiak.com' 
  5. *   Trying 192.168.31.31... 
  6. * TCP_NODELAY set 
  7. * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0) 
  8. > GET / HTTP/1.1 
  9. > Host: external-bauth.qikqiak.com 
  10. User-Agent: curl/7.64.1 
  11. > Accept: */* 
  12. < HTTP/1.1 401 Unauthorized 
  13. Date: Thu, 16 Dec 2021 10:57:25 GMT 
  14. < Content-Type: text/html 
  15. < Content-Length: 172 
  16. Connection: keep-alive 
  17. < WWW-Authenticate: Basic realm="Fake Realm" 
  18. <html> 
  19. <head><title>401 Authorization Required</title></head> 
  20. <body> 
  21. <center><h1>401 Authorization Required</h1></center> 
  22. <hr><center>nginx</center> 
  23. </body> 
  24. </html> 
  25. Connection #0 to host 192.168.31.31 left intact 
  26. * Closing connection 0 

 然后使用正确的用户名和密码测试: 

  1. ➜ curl -k http://192.168.31.31 -v -H 'Host: external-bauth.qikqiak.com' -u 'user:passwd' 
  2. *   Trying 192.168.31.31... 
  3. * TCP_NODELAY set 
  4. * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0) 
  5. * Server auth using Basic with user 'user' 
  6. > GET / HTTP/1.1 
  7. > Host: external-bauth.qikqiak.com 
  8. Authorization: Basic dXNlcjpwYXNzd2Q= 
  9. User-Agent: curl/7.64.1 
  10. > Accept: */* 
  11. < HTTP/1.1 200 OK 
  12. Date: Thu, 16 Dec 2021 10:58:31 GMT 
  13. < Content-Type: text/html 
  14. < Content-Length: 615 
  15. Connection: keep-alive 
  16. Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT 
  17. < ETag: "61814ff2-267" 
  18. < Accept-Ranges: bytes 
  19. <!DOCTYPE html> 
  20. <html> 
  21. <head> 
  22. <title>Welcome to nginx!</title> 
  23. <style> 
  24. html { color-scheme: light dark; } 
  25. body { width: 35em; margin: 0 auto; 
  26. font-family: Tahoma, Verdana, Arial, sans-serif; } 
  27. </style> 
  28. </head> 
  29. <body> 
  30. <h1>Welcome to nginx!</h1> 
  31. <p>If you see this page, the nginx web server is successfully installed and 
  32. working. Further configuration is required.</p> 
  33.  
  34. <p>For online documentation and support please refer to 
  35. <a href="http://nginx.org/">nginx.org</a>.<br/> 
  36. Commercial support is available at 
  37. <a href="http://nginx.com/">nginx.com</a>.</p> 
  38.  
  39. <p><em>Thank you for using nginx.</em></p> 
  40. </body> 
  41. </html> 
  42. Connection #0 to host 192.168.31.31 left intact 
  43. * Closing connection 0 

 如果用户名或者密码错误则同样会出现401的状态码:

  1. ➜ curl -k http://192.168.31.31 -v -H 'Host: external-bauth.qikqiak.com' -u 'user:passwd123' 
  2. *   Trying 192.168.31.31... 
  3. * TCP_NODELAY set 
  4. * Connected to 192.168.31.31 (192.168.31.31) port 80 (#0) 
  5. * Server auth using Basic with user 'user' 
  6. > GET / HTTP/1.1 
  7. > Host: external-bauth.qikqiak.com 
  8. Authorization: Basic dXNlcjpwYXNzd2QxMjM= 
  9. User-Agent: curl/7.64.1 
  10. > Accept: */* 
  11. < HTTP/1.1 401 Unauthorized 
  12. Date: Thu, 16 Dec 2021 10:59:18 GMT 
  13. < Content-Type: text/html 
  14. < Content-Length: 172 
  15. Connection: keep-alive 
  16. * Authentication problem. Ignoring this. 
  17. < WWW-Authenticate: Basic realm="Fake Realm" 
  18. <html> 
  19. <head><title>401 Authorization Required</title></head> 
  20. <body> 
  21. <center><h1>401 Authorization Required</h1></center> 
  22. <hr><center>nginx</center> 
  23. </body> 
  24. </html> 
  25. Connection #0 to host 192.168.31.31 left intact 
  26. * Closing connection 0 

 当然除了 Basic Auth 这一种简单的认证方式之外,ingress-nginx 还支持一些其他高级的认证,比如我们可以使用 GitHub OAuth 来认证 Kubernetes 的 Dashboard。

URL Rewrite

ingress-nginx 很多高级的用法可以通过 Ingress 对象的 annotation 进行配置,比如常用的 URL Rewrite 功能。很多时候我们会将 ingress-nginx 当成网关使用,比如对访问的服务加上 /app 这样的前缀,在 nginx 的配置里面我们知道有一个 proxy_pass 指令可以实现:

  1. location /app/ { 
  2.   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 的前缀,关于 Rewrite 的操作在 ingress-nginx 官方文档中也给出对应的说明:

按照要求我们需要在 path 中匹配前缀 gateway,然后通过 rewrite-target 指定目标,Ingress 对象如下所示:

  1. apiVersion: networking.k8s.io/v1 
  2. kind: Ingress 
  3. metadata: 
  4.   name: rewrite 
  5.   annotations: 
  6.     nginx.ingress.kubernetes.io/rewrite-target: /$2 
  7. spec: 
  8.   ingressClassName: nginx 
  9.   rules: 
  10.   - host: rewrite.qikqiak.com 
  11.     http: 
  12.       paths: 
  13.       - path: /gateway(/|$)(.*) 
  14.         pathType: Prefix 
  15.         backend: 
  16.           service: 
  17.             name: my-nginx 
  18.             port: 
  19.               number: 80 

更新后,我们可以预见到直接访问域名肯定是不行了,因为我们没有匹配 / 的 path 路径:

  1. ➜ curl rewrite.qikqiak.com 
  2. default backend - 404 

但是我们带上 gateway 的前缀再去访问:

 我们可以看到已经可以访问到了,这是因为我们在 path 中通过正则表达式 /gateway(/|$)(.*) 将匹配的路径设置成了 rewrite-target 的目标路径了,所以我们访问 rewite.qikqiak.com/gateway/ 的时候实际上相当于访问的就是后端服务的 / 路径。

要解决我们访问主域名出现 404 的问题,我们可以给应用设置一个 app-root 的注解,这样当我们访问主域名的时候会自动跳转到我们指定的 app-root 目录下面,如下所示:

  1. apiVersion: networking.k8s.io/v1 
  2. kind: Ingress 
  3. metadata: 
  4.   name: rewrite 
  5.   annotations: 
  6.     nginx.ingress.kubernetes.io/app-root: /gateway/ 
  7.     nginx.ingress.kubernetes.io/rewrite-target: /$2 
  8. spec: 
  9.   ingressClassName: nginx 
  10.   rules: 
  11.   - host: rewrite.qikqiak.com 
  12.     http: 
  13.       paths: 
  14.       - path: /gateway(/|$)(.*) 
  15.         pathType: Prefix 
  16.         backend: 
  17.           service: 
  18.             name: my-nginx 
  19.             port: 
  20.               number: 80 

这个时候我们更新应用后访问主域名 rewrite.qikqiak.com 就会自动跳转到 rewrite.qikqiak.com/gateway/ 路径下面去了。但是还有一个问题是我们的 path 路径其实也匹配了 /app 这样的路径,可能我们更加希望我们的应用在最后添加一个 / 这样的 slash,同样我们可以通过 configuration-snippet 配置来完成,如下 Ingress 对象:

  1. apiVersion: networking.k8s.io/v1 
  2. kind: Ingress 
  3. metadata: 
  4.   name: rewrite 
  5.   annotations: 
  6.     nginx.ingress.kubernetes.io/app-root: /gateway/ 
  7.     nginx.ingress.kubernetes.io/rewrite-target: /$2 
  8.     nginx.ingress.kubernetes.io/configuration-snippet: | 
  9.       rewrite ^(/gateway)$ $1/ redirect; 
  10. spec: 
  11.   ingressClassName: nginx 
  12.   rules: 
  13.   - host: rewrite.qikqiak.com 
  14.     http: 
  15.       paths: 
  16.       - path: /gateway(/|$)(.*) 
  17.         pathType: Prefix 
  18.         backend: 
  19.           service: 
  20.             name: my-nginx 
  21.             port: 
  22.               number: 80 

更新后我们的应用就都会以 / 这样的 slash 结尾了。这样就完成了我们的需求,如果你原本对 nginx 的配置就非常熟悉的话应该可以很快就能理解这种配置方式了。

灰度发布

在日常工作中我们经常需要对服务进行版本更新升级,所以我们经常会使用到滚动升级、蓝绿发布、灰度发布等不同的发布操作。而 ingress-nginx 支持通过 Annotations 配置来实现不同场景下的灰度发布和测试,可以满足金丝雀发布、蓝绿部署与 A/B 测试等业务场景。

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 的值,必须与上一个 annotation (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-by-header - > canary-by-cookie - > canary-weight

总的来说可以把以上的四个 annotation 规则划分为以下两类:

基于权重的 Canary 规则

 基于用户请求的 Canary 规则

 

 下面我们通过一个示例应用来对灰度发布功能进行说明。

第一步. 部署 Production 应用

首先创建一个 production 环境的应用资源清单:

  1. # production.yaml 
  2. apiVersion: apps/v1 
  3. kind: Deployment 
  4. metadata: 
  5.   name: production 
  6.   labels: 
  7.     app: production 
  8. spec: 
  9.   selector: 
  10.     matchLabels: 
  11.       app: production 
  12.   template: 
  13.     metadata: 
  14.       labels: 
  15.         app: production 
  16.     spec: 
  17.       containers: 
  18.       - name: production 
  19.         image: cnych/echoserver 
  20.         ports: 
  21.         - containerPort: 8080 
  22.         env: 
  23.           - name: NODE_NAME 
  24.             valueFrom: 
  25.               fieldRef: 
  26.                 fieldPath: spec.nodeName 
  27.           - name: POD_NAME 
  28.             valueFrom: 
  29.               fieldRef: 
  30.                 fieldPath: metadata.name 
  31.           - name: POD_NAMESPACE 
  32.             valueFrom: 
  33.               fieldRef: 
  34.                 fieldPath: metadata.namespace 
  35.           - name: POD_IP 
  36.             valueFrom: 
  37.               fieldRef: 
  38.                 fieldPath: status.podIP 
  39. --- 
  40. apiVersion: v1 
  41. kind: Service 
  42. metadata: 
  43.   name: production 
  44.   labels: 
  45.     app: production 
  46. spec: 
  47.   ports: 
  48.   - port: 80 
  49.     targetPort: 8080 
  50.     name: http 
  51.   selector: 
  52.     app: production 

然后创建一个用于 production 环境访问的 Ingress 资源对象:

  1. # production-ingress.yaml 
  2. apiVersion: networking.k8s.io/v1 
  3. kind: Ingress 
  4. metadata: 
  5.   name: production 
  6. spec: 
  7.   ingressClassName: nginx 
  8.   rules: 
  9.   - host: echo.qikqiak.com 
  10.     http: 
  11.       paths: 
  12.       - path: / 
  13.         pathType: Prefix 
  14.         backend: 
  15.           service: 
  16.             name: production 
  17.             port: 
  18.               number: 80 

直接创建上面的几个资源对象:

  1. ➜ kubectl apply -f production.yaml 
  2. ➜ kubectl apply -f production-ingress.yaml 
  3. ➜ kubectl get pods -l app=production 
  4. NAME                         READY   STATUS    RESTARTS   AGE 
  5. production-856d5fb99-d6bds   1/1     Running   0          2m50s 
  6. ➜ kubectl get ingress 
  7. NAME         CLASS    HOSTS                ADDRESS        PORTS   AGE 
  8. production   <none>   echo.qikqiak.com     10.151.30.11   80      90s 

应用部署成功后,将域名 echo.qikqiak.com 映射到 master1 节点(ingress-nginx 所在的节点)的 IP即可正常访问应用:

  1. ➜ curl http://echo.qikqiak.com 
  2.  
  3. Hostname: production-856d5fb99-d6bds 
  4.  
  5. Pod Information: 
  6.  node name: node1 
  7.  pod name: production-856d5fb99-d6bds 
  8.  pod namespace: default 
  9.  pod IP: 10.244.1.111 
  10.  
  11. Server values
  12.  server_version=nginx: 1.13.3 - lua: 10008 
  13.  
  14. Request Information: 
  15.  client_address=10.244.0.0 
  16.  method=GET 
  17.  real path=/ 
  18.  query= 
  19.  request_version=1.1 
  20.  request_scheme=http 
  21.  request_uri=http://echo.qikqiak.com:8080/ 
  22.  
  23. Request Headers: 
  24.  accept=*/* 
  25.  host=echo.qikqiak.com 
  26.  user-agent=curl/7.64.1 
  27.  x-forwarded-for=171.223.99.184 
  28.  x-forwarded-host=echo.qikqiak.com 
  29.  x-forwarded-port=80 
  30.  x-forwarded-proto=http 
  31.  x-real-ip=171.223.99.184 
  32.  x-request-id=e680453640169a7ea21afba8eba9e116 
  33.  x-scheme=http 
  34.  
  35. Request Body: 
  36.  -no body in request- 

第二步. 创建 Canary 版本

参考将上述 Production 版本的 production.yaml 文件,再创建一个 Canary 版本的应用。

  1. # canary.yaml 
  2. apiVersion: apps/v1 
  3. kind: Deployment 
  4. metadata: 
  5.   name: canary 
  6.   labels: 
  7.     app: canary 
  8. spec: 
  9.   selector: 
  10.     matchLabels: 
  11.       app: canary 
  12.   template: 
  13.     metadata: 
  14.       labels: 
  15.         app: canary 
  16.     spec: 
  17.       containers: 
  18.       - name: canary 
  19.         image: cnych/echoserver 
  20.         ports: 
  21.         - containerPort: 8080 
  22.         env: 
  23.           - name: NODE_NAME 
  24.             valueFrom: 
  25.               fieldRef: 
  26.                 fieldPath: spec.nodeName 
  27.           - name: POD_NAME 
  28.             valueFrom: 
  29.               fieldRef: 
  30.                 fieldPath: metadata.name 
  31.           - name: POD_NAMESPACE 
  32.             valueFrom: 
  33.               fieldRef: 
  34.                 fieldPath: metadata.namespace 
  35.           - name: POD_IP 
  36.             valueFrom: 
  37.               fieldRef: 
  38.                 fieldPath: status.podIP 
  39. --- 
  40. apiVersion: v1 
  41. kind: Service 
  42. metadata: 
  43.   name: canary 
  44.   labels: 
  45.     app: canary 
  46. spec: 
  47.   ports: 
  48.   - port: 80 
  49.     targetPort: 8080 
  50.     name: http 
  51.   selector: 
  52.     app: canary 

接下来就可以通过配置 Annotation 规则进行流量切分了。

第三步. Annotation 规则配置

1. 基于权重:基于权重的流量切分的典型应用场景就是蓝绿部署,可通过将权重设置为 0 或 100 来实现。例如,可将 Green 版本设置为主要部分,并将 Blue 版本的入口配置为 Canary。最初,将权重设置为 0,因此不会将流量代理到 Blue 版本。一旦新版本测试和验证都成功后,即可将 Blue 版本的权重设置为 100,即所有流量从 Green 版本转向 Blue。

创建一个基于权重的 Canary 版本的应用路由 Ingress 对象。

  1. # canary-ingress.yaml 
  2. apiVersion: networking.k8s.io/v1 
  3. kind: Ingress 
  4. metadata: 
  5.   name: canary 
  6.   annotations: 
  7.     nginx.ingress.kubernetes.io/canary: "true"   # 要开启灰度发布机制,首先需要启用 Canary 
  8.     nginx.ingress.kubernetes.io/canary-weight: "30"  # 分配30%流量到当前Canary版本 
  9. spec: 
  10.   ingressClassName: nginx 
  11.   rules: 
  12.   - host: echo.qikqiak.com 
  13.     http: 
  14.       paths: 
  15.       - path: / 
  16.         pathType: Prefix 
  17.         backend: 
  18.           service: 
  19.             name: canary 
  20.             port: 
  21.               number: 80 

直接创建上面的资源对象即可:

  1. ➜ kubectl apply -f canary.yaml 
  2. ➜ kubectl apply -f canary-ingress.yaml 
  3. ➜ kubectl get pods 
  4. NAME                         READY   STATUS    RESTARTS   AGE 
  5. canary-66cb497b7f-48zx4      1/1     Running   0          7m48s 
  6. production-856d5fb99-d6bds   1/1     Running   0          21m 
  7. ...... 
  8. ➜ kubectl get svc 
  9. NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE 
  10. canary                     ClusterIP   10.106.91.106    <none>        80/TCP                       8m23s 
  11. production                 ClusterIP   10.105.182.15    <none>        80/TCP                       22m 
  12. ...... 
  13. ➜ kubectl get ingress 
  14. NAME         CLASS    HOSTS                ADDRESS        PORTS   AGE 
  15. canary       <none>   echo.qikqiak.com     10.151.30.11   80      108s 
  16. production   <none>   echo.qikqiak.com     10.151.30.11   80      22m 

Canary 版本应用创建成功后,接下来我们在命令行终端中来不断访问这个应用,观察 Hostname 变化:

  1. ➜ for i in $(seq 1 10); do curl -s echo.qikqiak.com | grep "Hostname"; done 
  2. Hostname: production-856d5fb99-d6bds 
  3. Hostname: canary-66cb497b7f-48zx4 
  4. Hostname: production-856d5fb99-d6bds 
  5. Hostname: production-856d5fb99-d6bds 
  6. Hostname: production-856d5fb99-d6bds 
  7. Hostname: production-856d5fb99-d6bds 
  8. Hostname: production-856d5fb99-d6bds 
  9. Hostname: canary-66cb497b7f-48zx4 
  10. Hostname: canary-66cb497b7f-48zx4 
  11. Hostname: production-856d5fb99-d6bds 

由于我们给 Canary 版本应用分配了 30% 左右权重的流量,所以上面我们访问10次有3次访问到了 Canary 版本的应用,符合我们的预期。

2. 基于 Request Header: 基于 Request Header 进行流量切分的典型应用场景即灰度发布或 A/B 测试场景。

在上面的 Canary 版本的 Ingress 对象中新增一条 annotation 配置 nginx.ingress.kubernetes.io/canary-by-header: canary(这里的 value 可以是任意值),使当前的 Ingress 实现基于 Request Header 进行流量切分,由于 canary-by-header 的优先级大于 canary-weight,所以会忽略原有的 canary-weight 的规则。

  1. annotations: 
  2.   nginx.ingress.kubernetes.io/canary: "true"   # 要开启灰度发布机制,首先需要启用 Canary 
  3.   nginx.ingress.kubernetes.io/canary-by-header: canary  # 基于header的流量切分 
  4.   nginx.ingress.kubernetes.io/canary-weight: "30"  # 会被忽略,因为配置了 canary-by-headerCanary版本 

更新上面的 Ingress 资源对象后,我们在请求中加入不同的 Header 值,再次访问应用的域名。

注意:当 Request Header 设置为 never 或 always 时,请求将不会或一直被发送到 Canary 版本,对于任何其他 Header 值,将忽略 Header,并通过优先级将请求与其他 Canary 规则进行优先级的比较。

  1. ➜ for i in $(seq 1 10); do curl -s -H "canary: never" echo.qikqiak.com | grep "Hostname"; done 
  2. Hostname: production-856d5fb99-d6bds 
  3. Hostname: production-856d5fb99-d6bds 
  4. Hostname: production-856d5fb99-d6bds 
  5. Hostname: production-856d5fb99-d6bds 
  6. Hostname: production-856d5fb99-d6bds 
  7. Hostname: production-856d5fb99-d6bds 
  8. Hostname: production-856d5fb99-d6bds 
  9. Hostname: production-856d5fb99-d6bds 
  10. Hostname: production-856d5fb99-d6bds 
  11. Hostname: production-856d5fb99-d6bds 

这里我们在请求的时候设置了 canary: never 这个 Header 值,所以请求没有发送到 Canary 应用中去。如果设置为其他值呢:

  1. ➜ for i in $(seq 1 10); do curl -s -H "canary: other-value" echo.qikqiak.com | grep "Hostname"; done 
  2. Hostname: production-856d5fb99-d6bds 
  3. Hostname: production-856d5fb99-d6bds 
  4. Hostname: canary-66cb497b7f-48zx4 
  5. Hostname: production-856d5fb99-d6bds 
  6. Hostname: production-856d5fb99-d6bds 
  7. Hostname: production-856d5fb99-d6bds 
  8. Hostname: production-856d5fb99-d6bds 
  9. Hostname: canary-66cb497b7f-48zx4 
  10. Hostname: production-856d5fb99-d6bds 
  11. Hostname: canary-66cb497b7f-48zx4 

由于我们请求设置的 Header 值为 canary: other-value,所以 ingress-nginx 会通过优先级将请求与其他 Canary 规则进行优先级的比较,我们这里也就会进入 canary-weight: "30" 这个规则去。

这个时候我们可以在上一个 annotation (即 canary-by-header)的基础上添加一条 nginx.ingress.kubernetes.io/canary-by-header-value: user-value 这样的规则,就可以将请求路由到 Canary Ingress 中指定的服务了。

  1. annotations: 
  2.   nginx.ingress.kubernetes.io/canary: "true"   # 要开启灰度发布机制,首先需要启用 Canary 
  3.   nginx.ingress.kubernetes.io/canary-by-header-value: user-value 
  4.   nginx.ingress.kubernetes.io/canary-by-header: canary  # 基于header的流量切分 
  5.   nginx.ingress.kubernetes.io/canary-weight: "30"  # 分配30%流量到当前Canary版本 

同样更新 Ingress 对象后,重新访问应用,当 Request Header 满足 canary: user-value时,所有请求就会被路由到 Canary 版本:

  1. ➜ for i in $(seq 1 10); do curl -s -H "canary: user-value" echo.qikqiak.com | grep "Hostname"; done 
  2. Hostname: canary-66cb497b7f-48zx4 
  3. Hostname: canary-66cb497b7f-48zx4 
  4. Hostname: canary-66cb497b7f-48zx4 
  5. Hostname: canary-66cb497b7f-48zx4 
  6. Hostname: canary-66cb497b7f-48zx4 
  7. Hostname: canary-66cb497b7f-48zx4 
  8. Hostname: canary-66cb497b7f-48zx4 
  9. Hostname: canary-66cb497b7f-48zx4 
  10. Hostname: canary-66cb497b7f-48zx4 
  11. Hostname: canary-66cb497b7f-48zx4 

3. 基于 Cookie:与基于 Request Header 的 annotation 用法规则类似。例如在 A/B 测试场景下,需要让地域为北京的用户访问 Canary 版本。那么当 cookie 的 annotation 设置为 nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing",此时后台可对登录的用户请求进行检查,如果该用户访问源来自北京则设置 cookie users_from_Beijing 的值为 always,这样就可以确保北京的用户仅访问 Canary 版本。

同样我们更新 Canary 版本的 Ingress 资源对象,采用基于 Cookie 来进行流量切分,

  1. annotations: 
  2.   nginx.ingress.kubernetes.io/canary: "true"   # 要开启灰度发布机制,首先需要启用 Canary 
  3.   nginx.ingress.kubernetes.io/canary-by-cookie: "users_from_Beijing"  # 基于 cookie 
  4.   nginx.ingress.kubernetes.io/canary-weight: "30"  # 会被忽略,因为配置了 canary-by-cookie 

更新上面的 Ingress 资源对象后,我们在请求中设置一个 users_from_Beijing=always 的 Cookie 值,再次访问应用的域名。

  1. ➜ for i in $(seq 1 10); do curl -s -b "users_from_Beijing=always" echo.qikqiak.com | grep "Hostname"; done 
  2. Hostname: canary-66cb497b7f-48zx4 
  3. Hostname: canary-66cb497b7f-48zx4 
  4. Hostname: canary-66cb497b7f-48zx4 
  5. Hostname: canary-66cb497b7f-48zx4 
  6. Hostname: canary-66cb497b7f-48zx4 
  7. Hostname: canary-66cb497b7f-48zx4 
  8. Hostname: canary-66cb497b7f-48zx4 
  9. Hostname: canary-66cb497b7f-48zx4 
  10. Hostname: canary-66cb497b7f-48zx4 
  11. Hostname: canary-66cb497b7f-48zx4 

我们可以看到应用都被路由到了 Canary 版本的应用中去了,如果我们将这个 Cookie 值设置为 never,则不会路由到 Canary 应用中。

HTTPS

如果我们需要用 HTTPS 来访问我们这个应用的话,就需要监听 443 端口了,同样用 HTTPS 访问应用必然就需要证书,这里我们用 openssl 来创建一个自签名的证书:

  1. ➜ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=foo.bar.com" 

然后通过 Secret 对象来引用证书文件:

  1. # 要注意证书文件名称必须是 tls.crt 和 tls.key 
  2. ➜ kubectl create secret tls foo-tls --cert=tls.crt --key=tls.key 
  3. secret/who-tls created 

这个时候我们就可以创建一个 HTTPS 访问应用的:

  1. apiVersion: networking.k8s.io/v1 
  2. kind: Ingress 
  3. metadata: 
  4.   name: ingress-with-auth 
  5.   annotations: 
  6.     # 认证类型 
  7.     nginx.ingress.kubernetes.io/auth-type: basic 
  8.     # 包含 user/password 定义的 secret 对象名 
  9.     nginx.ingress.kubernetes.io/auth-secret: basic-auth 
  10.     # 要显示的带有适当上下文的消息,说明需要身份验证的原因 
  11.     nginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - foo' 
  12. spec: 
  13.   ingressClassName: nginx 
  14.   tls:  # 配置 tls 证书 
  15.   - hosts: 
  16.     - foo.bar.com 
  17.     secretName: foo-tls 
  18.   rules: 
  19.   - host: foo.bar.com 
  20.     http: 
  21.       paths: 
  22.       - path: / 
  23.         pathType: Prefix 
  24.         backend: 
  25.           service: 
  26.             name: my-nginx 
  27.             port: 
  28.               number: 80 

除了自签名证书或者购买正规机构的 CA 证书之外,我们还可以通过一些工具来自动生成合法的证书,cert-manager 是一个云原生证书管理开源项目,可以用于在 Kubernetes 集群中提供 HTTPS 证书并自动续期,支持 Let's Encrypt/HashiCorp/Vault 这些免费证书的签发。在 Kubernetes 中,可以通过 Kubernetes Ingress 和 Let's Encrypt 实现外部服务的自动化 HTTPS。

TCP与UDP

由于在 Ingress 资源对象中没有直接对 TCP 或 UDP 服务的支持,要在 ingress-nginx 中提供支持,需要在控制器启动参数中添加 --tcp-services-configmap 和 --udp-services-configmap 标志指向一个 ConfigMap,其中的 key 是要使用的外部端口,value 值是使用格式 ::[PROXY]:[PROXY] 暴露的服务,端口可以使用端口号或者端口名称,最后两个字段是可选的,用于配置 PROXY 代理。

比如现在我们要通过 ingress-nginx 来暴露一个 MongoDB 服务,首先创建如下的应用:

  1. # mongo.yaml 
  2. apiVersion: apps/v1 
  3. kind: Deployment 
  4. metadata: 
  5.   name: mongo 
  6.   labels: 
  7.     app: mongo 
  8. spec: 
  9.   selector: 
  10.     matchLabels: 
  11.       app: mongo 
  12.   template: 
  13.     metadata: 
  14.       labels: 
  15.         app: mongo 
  16.     spec: 
  17.       volumes: 
  18.       - name: data 
  19.         emptyDir: {} 
  20.       containers: 
  21.       - name: mongo 
  22.         image: mongo:4.0 
  23.         ports: 
  24.         - containerPort: 27017 
  25.         volumeMounts: 
  26.         - name: data 
  27.           mountPath: /data/db 
  28. --- 
  29. apiVersion: v1 
  30. kind: Service 
  31. metadata: 
  32.   name: mongo 
  33. spec: 
  34.   selector: 
  35.     app: mongo 
  36.   ports: 
  37.   - port: 27017 

直接创建上面的资源对象:

  1. ➜ kubectl apply -f mongo.yaml 
  2. ➜ kubectl get svc 
  3. NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)     AGE 
  4. mongo           ClusterIP   10.98.117.228    <none>        27017/TCP   2m26s 
  5. ➜ kubectl get pods -l app=mongo 
  6. NAME                     READY   STATUS    RESTARTS   AGE 
  7. mongo-84c587f547-gd7pv   1/1     Running   0          2m5s 

现在我们要通过 ingress-nginx 来暴露上面的 MongoDB 服务,我们需要创建一个如下所示的 ConfigMap:

  1. apiVersion: v1 
  2. kind: ConfigMap 
  3. metadata: 
  4.   name: tcp-services 
  5.   namespace: ingress-nginx 
  6. data: 
  7.   "27017"default/mongo:27017 

然后在 ingress-nginx 的启动参数中添加 --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp 这样的配置即可,由于我们这里使用的是 Helm Chart 进行安装的,我们只需要去覆盖 Values 值重新安装即可,修改 ci/daemonset-prod.yaml 文件:

  1. # ci/daemonset-prod.yaml 
  2. # ...... 其他部分省略,和之前的保持一致 
  3.  
  4. tcp:  # 配置 tcp 服务 
  5.   27017: "default/mongo:27017"  # 使用 27017 端口去映射 mongo 服务 
  6.   # 9000: "default/test:8080"   # 如果还需要暴露其他 TCP 服务,继续添加即可 

配置完成后重新更新当前的 ingress-nginx:

  1. ➜ helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx 

重新部署完成后会自动生成一个名为 ingress-nginx-tcp 的 ConfigMap 对象,如下所示:

  1. ➜ kubectl get configmap -n ingress-nginx ingress-nginx-tcp -o yaml 
  2. apiVersion: v1 
  3. data: 
  4.   "27017"default/mongo:27017 
  5. kind: ConfigMap 
  6. metadata: 
  7.   ...... 
  8.   name: ingress-nginx-tcp 
  9.   namespace: ingress-nginx 

在 ingress-nginx 的启动参数中也添加上 --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp 这样的配置:

  1. ➜ kubectl get pods -n ingress-nginx 
  2. NAME                                            READY   STATUS    RESTARTS        AGE 
  3. ingress-nginx-controller-gc582                  1/1     Running   0               5m17s 
  4. ➜ kubectl get pod ingress-nginx-controller-gc582 -n ingress-nginx -o yaml 
  5. apiVersion: v1 
  6. kind: Pod 
  7. ...... 
  8.   containers: 
  9.   - args: 
  10.     - /nginx-ingress-controller 
  11.     - --default-backend-service=$(POD_NAMESPACE)/ingress-nginx-defaultbackend 
  12.     - --election-id=ingress-controller-leader 
  13.     - --controller-class=k8s.io/ingress-nginx 
  14.     - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller 
  15.     - --tcp-services-configmap=$(POD_NAMESPACE)/ingress-nginx-tcp  # tcp 配置参数 
  16.     - --validating-webhook=:8443 
  17.     - --validating-webhook-certificate=/usr/local/certificates/cert 
  18.     - --validating-webhook-key=/usr/local/certificates/key 
  19. ...... 
  20.     ports: 
  21. ...... 
  22.     - containerPort: 27017 
  23.       hostPort: 27017 
  24.       name: 27017-tcp 
  25.       protocol: TCP 
  26. ...... 

现在我们就可以通过 ingress-nginx 暴露的 27017 端口去访问 Mongo 服务了:

  1. ➜ mongo --host 192.168.31.31 --port 27017 
  2. MongoDB shell version v4.0.3 
  3. connecting to: mongodb://192.168.31.31:27017/ 
  4. Implicit session: session { "id" : UUID("10f462eb-32b8-443b-ad85-99820db1aaa0") } 
  5. MongoDB server version: 4.0.27 
  6. ...... 
  7.  
  8. > show dbs 
  9. admin   0.000GB 
  10. config  0.000GB 
  11. local   0.000GB 

同样的我们也可以去查看最终生成的 nginx.conf 配置文件:

  1. ➜ kubectl exec -it ingress-nginx-controller-gc582 -n ingress-nginx -- cat /etc/nginx/nginx.conf 
  2. ...... 
  3. stream { 
  4.     ...... 
  5.     # TCP services 
  6.     server { 
  7.             preread_by_lua_block { 
  8.                     ngx.var.proxy_upstream_name="tcp-default-mongo-27017"
  9.             } 
  10.             listen                  27017; 
  11.             listen                  [::]:27017; 
  12.             proxy_timeout           600s; 
  13.             proxy_next_upstream     on
  14.             proxy_next_upstream_timeout 600s; 
  15.             proxy_next_upstream_tries   3; 
  16.             proxy_pass              upstream_balancer; 
  17.     } 
  18.     # UDP services 

TCP 相关的配置位于 stream 配置块下面。从 Nginx 1.9.13 版本开始提供 UDP 负载均衡,同样我们也可以在 ingress-nginx 中来代理 UDP 服务,比如我们可以去暴露 kube-dns 的服务,同样需要创建一个如下所示的 ConfigMap:

  1. apiVersion: v1 
  2. kind: ConfigMap 
  3. metadata: 
  4.   name: udp-services 
  5.   namespace: ingress-nginx 
  6. data: 
  7.   53: "kube-system/kube-dns:53" 

然后需要在 ingress-nginx 参数中添加一个 - --udp-services-configmap=$(POD_NAMESPACE)/udp-services 这样的配置,当然我们这里只需要去修改 Values 文件值即可,修改 ci/daemonset-prod.yaml 文件:

  1. # ci/daemonset-prod.yaml 
  2. # ...... 其他部分省略,和之前的保持一致 
  3.  
  4. tcp:  # 配置 tcp 服务 
  5.   27017: "default/mongo:27017"  # 使用 27017 端口去映射 mongo 服务 
  6.   # 9000: "default/test:8080"   # 如果还需要暴露其他 TCP 服务,继续添加即可 
  7.  
  8. udp:  # 配置 udp 服务 
  9.   53: "kube-system/kube-dns:53" 

然后重新更新即可。

全局配置

除了可以通过 annotations 对指定的 Ingress 进行定制之外,我们还可以配置 ingress-nginx 的全局配置,在控制器启动参数中通过标志 --configmap 指定了一个全局的 ConfigMap 对象,我们可以将全局的一些配置直接定义在该对象中即可:

  1. containers: 
  2.   - args: 
  3.     - /nginx-ingress-controller 
  4.     - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller 
  5.     ...... 

比如这里我们用于全局配置的 ConfigMap 名为 ingress-nginx-controller:

  1. ➜ kubectl get configmap -n ingress-nginx 
  2. NAME                        DATA   AGE 
  3. ingress-nginx-controller    1      5d2h 

比如我们可以添加如下所示的一些常用配置:

  1. ➜ kubectl edit configmap ingress-nginx-controller -n ingress-nginx 
  2. apiVersion: v1 
  3. data: 
  4.   allow-snippet-annotations: "true" 
  5.   client-header-buffer-size: 32k  # 注意不是下划线 
  6.   client-max-body-size: 5m 
  7.   use-gzip: "true" 
  8.   gzip-level"7" 
  9.   large-client-header-buffers: 4 32k 
  10.   proxy-connect-timeout: 11s 
  11.   proxy-read-timeout: 12s 
  12.   keep-alive: "75"   # 启用keep-alive,连接复用,提高QPS 
  13.   keep-alive-requests: "100" 
  14.   upstream-keepalive-connections: "10000" 
  15.   upstream-keepalive-requests: "100" 
  16.   upstream-keepalive-timeout: "60" 
  17.   disable-ipv6: "true" 
  18.   disable-ipv6-dns: "true" 
  19.   max-worker-connections: "65535" 
  20.   max-worker-open-files: "10240" 
  21. kind: ConfigMap 
  22. ...... 

修改完成后 Nginx 配置会自动重载生效,我们可以查看 nginx.conf 配置文件进行验证:

  1. ➜ kubectl exec -it ingress-nginx-controller-gc582 -n ingress-nginx -- cat /etc/nginx/nginx.conf |grep large_client_header_buffers 
  2.         large_client_header_buffers     4 32k; 

由于我们这里是 Helm Chart 安装的,为了保证重新部署后配置还在,我们同样需要通过 Values 进行全局配置:

  1. # ci/daemonset-prod.yaml 
  2. controller: 
  3.   config: 
  4.     allow-snippet-annotations: "true" 
  5.     client-header-buffer-size: 32k  # 注意不是下划线 
  6.     client-max-body-size: 5m 
  7.     use-gzip: "true" 
  8.     gzip-level"7" 
  9.     large-client-header-buffers: 4 32k 
  10.     proxy-connect-timeout: 11s 
  11.     proxy-read-timeout: 12s 
  12.     keep-alive: "75"   # 启用keep-alive,连接复用,提高QPS 
  13.     keep-alive-requests: "100" 
  14.     upstream-keepalive-connections: "10000" 
  15.     upstream-keepalive-requests: "100" 
  16.     upstream-keepalive-timeout: "60" 
  17.     disable-ipv6: "true" 
  18.     disable-ipv6-dns: "true" 
  19.     max-worker-connections: "65535" 
  20.     max-worker-open-files: "10240" 
  21.  
  22. # 其他省略 

此外往往我们还需要对 ingress-nginx 部署的节点进行性能优化,修改一些内核参数,使得适配 Nginx 的使用场景,一般我们是直接去修改节点上的内核参数,为了能够统一管理,我们可以使用 initContainers 来进行配置:

  1. initContainers: 
  2. - command: 
  3.   - /bin/sh 
  4.   - -c 
  5.   - | 
  6.     mount -o remount rw /proc/sys 
  7.     sysctl -w net.core.somaxconn=65535  # 具体的配置视具体情况而定 
  8.     sysctl -w net.ipv4.tcp_tw_reuse=1 
  9.     sysctl -w net.ipv4.ip_local_port_range="1024 65535" 
  10.     sysctl -w fs.file-max=1048576 
  11.     sysctl -w fs.inotify.max_user_instances=16384 
  12.     sysctl -w fs.inotify.max_user_watches=524288 
  13.     sysctl -w fs.inotify.max_queued_events=16384 
  14. image: busybox 
  15. imagePullPolicy: IfNotPresent 
  16. name: init-sysctl 
  17. securityContext: 
  18.   capabilities: 
  19.     add
  20.     - SYS_ADMIN 
  21.     drop
  22.     - ALL 
  23. ...... 

由于我们这里使用的是 Helm Chart 安装的 ingress-nginx,同样只需要去配置 Values 值即可,模板中提供了对 initContainers 的支持,配置如下所示:

  1. controller: 
  2.   # 其他省略,配置 initContainers 
  3.   extraInitContainers: 
  4.   - name: init-sysctl 
  5.     image: busybox 
  6.     securityContext: 
  7.       capabilities: 
  8.         add
  9.         - SYS_ADMIN 
  10.         drop
  11.         - ALL 
  12.     command: 
  13.     - /bin/sh 
  14.     - -c 
  15.     - | 
  16.       mount -o remount rw /proc/sys 
  17.       sysctl -w net.core.somaxconn=65535  # socket监听的backlog上限 
  18.       sysctl -w net.ipv4.tcp_tw_reuse=1  # 开启重用,允许将 TIME-WAIT sockets 重新用于新的TCP连接 
  19.       sysctl -w net.ipv4.ip_local_port_range="1024 65535" 
  20.       sysctl -w fs.file-max=1048576 
  21.       sysctl -w fs.inotify.max_user_instances=16384 
  22.       sysctl -w fs.inotify.max_user_watches=524288 
  23.       sysctl -w fs.inotify.max_queued_events=16384 

同样重新部署即可:

  1. ➜ helm upgrade --install ingress-nginx . -f ./ci/daemonset-prod.yaml --namespace ingress-nginx 

部署完成后通过 initContainers 就可以修改节点内核参数了,生产环境建议对节点内核参数进行相应的优化。

 

责任编辑:姜华 来源: k8s技术圈
相关推荐

2021-03-18 09:07:20

Nginx原理实践

2022-02-15 14:22:46

灰度发布互联网业务

2022-03-15 08:36:34

NginxKubernetesIngress

2022-09-09 20:55:38

LinkerdKubernetes

2022-09-07 08:34:19

kuberneteshttps

2024-01-30 07:58:41

KubernetesGAMMA网关

2023-03-30 09:17:42

KubesprayKubernetesLinux

2023-03-03 11:12:34

Kubernetes控制器后端

2020-11-16 10:50:27

KubernetesIngressLinux

2022-03-03 08:42:10

NodePortServiceKubernetes

2021-06-07 13:53:38

ServiceIngressKubernetes

2020-02-24 20:45:33

控制器技术选型技巧

2022-01-19 22:14:36

Apache APIAPI 网关插件

2024-01-30 18:29:29

微服务架构Ingress

2023-11-08 07:50:41

KubernetesIngress

2021-08-10 07:57:57

k8s Nginx IngrNginx

2010-03-29 16:00:19

Nginx 虚拟机

2011-11-28 14:47:58

Nginx 安装

2022-01-12 08:10:40

APISIXIngress Url Rewrite

2010-03-30 09:46:01

Nginx JSP
点赞
收藏

51CTO技术栈公众号