服务器之家:专注于服务器技术及软件下载分享
分类导航

云服务器|WEB服务器|FTP服务器|邮件服务器|虚拟主机|服务器安全|DNS服务器|服务器知识|Nginx|IIS|Tomcat|

服务器之家 - 服务器技术 - 服务器知识 - Kubernetes1.22安装使用ingress-nginx

Kubernetes1.22安装使用ingress-nginx

2021-12-22 22:13k8s技术圈阳明 服务器知识

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

Kubernetes1.22安装使用ingress-nginx

我们已经了解了 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. .
  9. ├── CHANGELOG.md
  10. ├── Chart.yaml
  11. ├── OWNERS
  12. ├── README.md
  13. ├── ci
  14. │ ├── controller-custom-ingressclass-flags.yaml
  15. │ ├── daemonset-customconfig-values.yaml
  16. │ ├── daemonset-customnodeport-values.yaml
  17. │ ├── daemonset-headers-values.yaml
  18. │ ├── daemonset-internal-lb-values.yaml
  19. │ ├── daemonset-nodeport-values.yaml
  20. │ ├── daemonset-podannotations-values.yaml
  21. │ ├── daemonset-tcp-udp-configMapNamespace-values.yaml
  22. │ ├── daemonset-tcp-udp-values.yaml
  23. │ ├── daemonset-tcp-values.yaml
  24. │ ├── deamonset-default-values.yaml
  25. │ ├── deamonset-metrics-values.yaml
  26. │ ├── deamonset-psp-values.yaml
  27. │ ├── deamonset-webhook-and-psp-values.yaml
  28. │ ├── deamonset-webhook-values.yaml
  29. │ ├── deployment-autoscaling-behavior-values.yaml
  30. │ ├── deployment-autoscaling-values.yaml
  31. │ ├── deployment-customconfig-values.yaml
  32. │ ├── deployment-customnodeport-values.yaml
  33. │ ├── deployment-default-values.yaml
  34. │ ├── deployment-headers-values.yaml
  35. │ ├── deployment-internal-lb-values.yaml
  36. │ ├── deployment-metrics-values.yaml
  37. │ ├── deployment-nodeport-values.yaml
  38. │ ├── deployment-podannotations-values.yaml
  39. │ ├── deployment-psp-values.yaml
  40. │ ├── deployment-tcp-udp-configMapNamespace-values.yaml
  41. │ ├── deployment-tcp-udp-values.yaml
  42. │ ├── deployment-tcp-values.yaml
  43. │ ├── deployment-webhook-and-psp-values.yaml
  44. │ ├── deployment-webhook-resources-values.yaml
  45. │ └── deployment-webhook-values.yaml
  46. ├── templates
  47. │ ├── NOTES.txt
  48. │ ├── _helpers.tpl
  49. │ ├── _params.tpl
  50. │ ├── admission-webhooks
  51. │ │ ├── job-patch
  52. │ │ │ ├── clusterrole.yaml
  53. │ │ │ ├── clusterrolebinding.yaml
  54. │ │ │ ├── job-createSecret.yaml
  55. │ │ │ ├── job-patchWebhook.yaml
  56. │ │ │ ├── psp.yaml
  57. │ │ │ ├── role.yaml
  58. │ │ │ ├── rolebinding.yaml
  59. │ │ │ └── serviceaccount.yaml
  60. │ │ └── validating-webhook.yaml
  61. │ ├── clusterrole.yaml
  62. │ ├── clusterrolebinding.yaml
  63. │ ├── controller-configmap-addheaders.yaml
  64. │ ├── controller-configmap-proxyheaders.yaml
  65. │ ├── controller-configmap-tcp.yaml
  66. │ ├── controller-configmap-udp.yaml
  67. │ ├── controller-configmap.yaml
  68. │ ├── controller-daemonset.yaml
  69. │ ├── controller-deployment.yaml
  70. │ ├── controller-hpa.yaml
  71. │ ├── controller-ingressclass.yaml
  72. │ ├── controller-keda.yaml
  73. │ ├── controller-poddisruptionbudget.yaml
  74. │ ├── controller-prometheusrules.yaml
  75. │ ├── controller-psp.yaml
  76. │ ├── controller-role.yaml
  77. │ ├── controller-rolebinding.yaml
  78. │ ├── controller-service-internal.yaml
  79. │ ├── controller-service-metrics.yaml
  80. │ ├── controller-service-webhook.yaml
  81. │ ├── controller-service.yaml
  82. │ ├── controller-serviceaccount.yaml
  83. │ ├── controller-servicemonitor.yaml
  84. │ ├── default-backend-deployment.yaml
  85. │ ├── default-backend-hpa.yaml
  86. │ ├── default-backend-poddisruptionbudget.yaml
  87. │ ├── default-backend-psp.yaml
  88. │ ├── default-backend-role.yaml
  89. │ ├── default-backend-rolebinding.yaml
  90. │ ├── default-backend-service.yaml
  91. │ ├── default-backend-serviceaccount.yaml
  92. │ └── dh-param-secret.yaml
  93. └── values.yaml
  94.  
  95. 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 值。

Kubernetes1.22安装使用ingress-nginx

对应的 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:
  49. tls.key: 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 443/TCP 11m
  4. ingress-nginx-defaultbackend ClusterIP 10.97.250.253 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 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 中添加对应的映射也可以,然后就可以通过域名进行访问了。

Kubernetes1.22安装使用ingress-nginx

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

Kubernetes1.22安装使用ingress-nginx

前面我们也提到了 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. ......
  15. ## start server ngdemo.qikqiak.com
  16. server {
  17. server_name ngdemo.qikqiak.com ;
  18.  
  19. listen 80 ;
  20. listen [::]:80 ;
  21. listen 443 ssl http2 ;
  22. listen [::]:443 ssl http2 ;
  23.  
  24. set $proxy_upstream_name "-";
  25.  
  26. ssl_certificate_by_lua_block {
  27. certificate.call()
  28. }
  29.  
  30. location / {
  31.  
  32. set $namespace "default";
  33. set $ingress_name "my-nginx";
  34. set $service_name "my-nginx";
  35. set $service_port "80";
  36. set $location_path "/";
  37. set $global_rate_limit_exceeding n;
  38. ......
  39. proxy_next_upstream_timeout 0;
  40. proxy_next_upstream_tries 3;
  41.  
  42. proxy_pass http://upstream_balancer;
  43.  
  44. proxy_redirect off;
  45.  
  46. }
  47.  
  48. }
  49. ## end server ngdemo.qikqiak.com
  50. ......

我们可以在 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. >
  13. < HTTP/1.1 401 Unauthorized
  14. < Date: Thu, 16 Dec 2021 10:49:03 GMT
  15. < Content-Type: text/html
  16. < Content-Length: 172
  17. < Connection: keep-alive
  18. < WWW-Authenticate: Basic realm="Authentication Required - foo"
  19. <
  20.  
  21.  
  22.  
  23. 401 Authorization Required


  24. nginx
  25.  
  26.  
  27. * Connection #0 to host 192.168.31.31 left intact
  28. * 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. >
  12. < HTTP/1.1 200 OK
  13. < Date: Thu, 16 Dec 2021 10:49:38 GMT
  14. < Content-Type: text/html
  15. < Content-Length: 615
  16. < Connection: keep-alive
  17. < Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT
  18. < ETag: "61814ff2-267"
  19. < Accept-Ranges: bytes
  20. <
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27. Welcome to nginx!

  28. If you see this page, the nginx web server is successfully installed and

  29. working. Further configuration is required.

     

  30.  
  31. For online documentation and support please refer to

  32. "http://nginx.org/">nginx.org.
  33. Commercial support is available at
  34. "http://nginx.com/">nginx.com.

     

  35.  
  36. Thank you for using nginx.

  37.  
  38.  
  39. * Connection #0 to host 192.168.31.31 left intact
  40. * 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 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. >
  13. < HTTP/1.1 401 Unauthorized
  14. < Date: Thu, 16 Dec 2021 10:57:25 GMT
  15. < Content-Type: text/html
  16. < Content-Length: 172
  17. < Connection: keep-alive
  18. < WWW-Authenticate: Basic realm="Fake Realm"
  19. <
  20.  
  21.  
  22.  
  23. 401 Authorization Required


  24. nginx
  25.  
  26.  
  27. * Connection #0 to host 192.168.31.31 left intact
  28. * 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. >
  12. < HTTP/1.1 200 OK
  13. < Date: Thu, 16 Dec 2021 10:58:31 GMT
  14. < Content-Type: text/html
  15. < Content-Length: 615
  16. < Connection: keep-alive
  17. < Last-Modified: Tue, 02 Nov 2021 14:49:22 GMT
  18. < ETag: "61814ff2-267"
  19. < Accept-Ranges: bytes
  20. <
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27. Welcome to nginx!

  28. If you see this page, the nginx web server is successfully installed and

  29. working. Further configuration is required.

     

  30.  
  31. For online documentation and support please refer to

  32. "http://nginx.org/">nginx.org.
  33. Commercial support is available at
  34. "http://nginx.com/">nginx.com.

     

  35.  
  36. Thank you for using nginx.

  37.  
  38.  
  39. * Connection #0 to host 192.168.31.31 left intact
  40. * 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. >
  12. < HTTP/1.1 401 Unauthorized
  13. < Date: Thu, 16 Dec 2021 10:59:18 GMT
  14. < Content-Type: text/html
  15. < Content-Length: 172
  16. < Connection: keep-alive
  17. * Authentication problem. Ignoring this.
  18. < WWW-Authenticate: Basic realm="Fake Realm"
  19. <
  20.  
  21.  
  22.  
  23. 401 Authorization Required


  24. nginx
  25.  
  26.  
  27. * Connection #0 to host 192.168.31.31 left intact
  28. * 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/;
  3. }

proxy_pass 后面加了 /remote 这个路径,此时会将匹配到该规则路径中的 /app 用 /remote 替换掉,相当于截掉路径中的 /app。同样的在 Kubernetes 中使用 ingress-nginx 又该如何来实现呢?我们可以使用 rewrite-target 的注解来实现这个需求,比如现在我们想要通过 rewrite.qikqiak.com/gateway/ 来访问到 Nginx 服务,则我们需要对访问的 URL 路径做一个 Rewrite,在 PATH 中添加一个 gateway 的前缀,关于 Rewrite 的操作在 ingress-nginx 官方文档中也给出对应的说明:

Kubernetes1.22安装使用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 的前缀再去访问:

Kubernetes1.22安装使用ingress-nginx

我们可以看到已经可以访问到了,这是因为我们在 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 规则

Kubernetes1.22安装使用ingress-nginx

基于用户请求的 Canary 规则

Kubernetes1.22安装使用ingress-nginx

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

第一步. 部署 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 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 80/TCP 8m23s
  11. production ClusterIP 10.105.182.15 80/TCP 22m
  12. ......
  13. ➜ kubectl get ingress
  14. NAME CLASS HOSTS ADDRESS PORTS AGE
  15. canary echo.qikqiak.com 10.151.30.11 80 108s
  16. production 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 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
  12. >

同样的我们也可以去查看最终生成的 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
  19. }

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 就可以修改节点内核参数了,生产环境建议对节点内核参数进行相应的优化。

原文链接:https://mp.weixin.qq.com/s/XoUaW-vBtQFxwNM3UdD8QA

延伸 · 阅读

精彩推荐