在Kubernetes集群中,Nginx Ingress对集群服务(Service)中外部可访问的API对象进行管理,提供七层负载均衡能力。您可以给Nginx Ingress配置提供外部可访问的URL、Rewrite配置、HTTPS服务、以及灰度发布功能等。本文介绍如何配置安全的路由服务、HTTPS双向认证、域名支持正则化及泛化,申请免费的HTTPS证书等功能。
前提条件
- 已创建Kubernetes集群。具体操作,请参见 创建Kubernetes托管版集群 。
- 集群中的Nginx Ingress Controller运行正常。
- 已通过kubectl连接Kubernetes集群。具体操作,请参见 步骤二:选择集群凭证类型 。
- 已创建示例Deployment和Service。具体操作,请参见 Kubectl操作指导 。
配置说明
针对Nginx Ingress Controller,阿里云容器服务团队采用与社区完全兼容的配置方式。关于所有的配置说明,请参见 NGINX Configuration 。
- 基于Annotation的方式:在每个Nginx Ingress YAML的Annotation里配置,只对本Nginx Ingress生效。更多信息,请参见 Annotations 。
- 基于ConfigMap的方式:通过kube-system/nginx-configuration configmap的配置,是一个全局的配置,对所有的Nginx Ingress生效。更多信息,请参见 ConfigMaps 。
- 自定义NGINX Template模板的方式:对Nginx Ingress Controller内部的NGINX Template有特殊配置要求,且当前通过Annotation和ConfigMap方式都无法满足诉求的情况下采用该方式。更多信息,请参见 Custom NGINX template 。
配置URL重定向的路由服务
当您在使用Nginx Ingress Controller的时候,Nginx会将路径完整转发到后端(如,从Ingress访问的
/service1/api
路径会直接转发到后端Pod的
/service1/api/
路径)。如果您后端的服务路径为
/api
,则会出现路径错误,导致404的情况。该情况下,您可以通过配置
rewrite-target
的方式,来将路径重写至需要的目录。
-
部署以下模板,创建Nginx Ingress。
cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: foo.bar.com namespace: default annotations: #URL重定向。 nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - host: foo.bar.com http: paths: # 在Ingress Controller的版本≥0.22.0之后,path中需要使用正则表达式定义路径,并在rewrite-target中结合捕获组一起使用。 - path: /svc(/|$)(.*) backend: serviceName: web1-service servicePort: 80
cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: foo.bar.com namespace: default annotations: #URL重定向。 nginx.ingress.kubernetes.io/rewrite-target: /$2 spec: rules: - host: foo.bar.com http: paths: # 在Ingress Controller的版本≥0.22.0之后,path中需要使用正则表达式定义路径,并在rewrite-target中结合捕获组一起使用。 - path: /svc(/|$)(.*) backend: service: name: web1-service port: number: 80 pathType: ImplementationSpecific
-
执行以下命令,访问Nginx服务。
替换 IP_ADDRESS 为Ingress对应的IP,可通过
kubectl get ing
获取。curl -k -H "Host: foo.bar.com" http://<IP_ADDRESS>/svc/foo
预期输出:
web1: /foo
Rewrite配置
使用
nginx.ingress.kubernetes.io/rewrite-target
注解支持基本的Rewrite配置,对于一些复杂高级的Rewrite需求,可以通过如下注解来实现:
-
nginx.ingress.kubernetes.io/server-snippet
:扩展配置到Server章节。 -
nginx.ingress.kubernetes.io/configuration-snippet
:扩展配置到Location章节。
配置示例:
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
rewrite ^/v4/(.*)/card/query http://foo.bar.com/v5/#!/card/query permanent;
nginx.ingress.kubernetes.io/configuration-snippet: |
rewrite ^/v6/(.*)/card/query http://foo.bar.com/v7/#!/card/query permanent;
示例配置生成的 nginx.conf 如下所示。
## start server foo.bar.com
server {
server_name foo.bar.com ;
listen 80;
listen [::]:80;
set $proxy_upstream_name "-";
### server-snippet配置。
rewrite ^/v4/(.*)/card/query http://foo.bar.com/v5/#!/card/query permanent;
### configuration-snippet配置。
rewrite ^/v6/(.*)/card/query http://foo.bar.com/v7/#!/card/query permanent;
## end server foo.bar.com
同时,
snippet
也支持一些全局配置。详细信息,请参见
server-snippet
。
关于
rewrite
指令的具体用法,请参照
Nginx官方文档对该指令的描述
。
为路由规则配置HTTPS证书
您可以通过Ingress提供的原生语义来为网站配置 HTTPS 证书。
-
准备您的服务证书。
说明 域名需要与您所配置的Host一致,否则将无法被Nginx Ingress Controller正确加载。
-
执行以下命令,创建Ingress资源,通过tls字段引用上个步骤中创建的Secret。
cat <<EOF | kubectl create -f - apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: test-test-ingress spec: #引用tls证书。 - hosts: - foo.bar.com #证书所对应的域名。 secretName: tls-test-ingress rules: - host: tls-test-ingress.com http: paths: - path: /foo backend: serviceName: web1-svc servicePort: 80
cat <<EOF | kubectl create -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test-test-ingress spec: #引用tls证书。 - hosts: - foo.bar.com #证书所对应的域名。 secretName: tls-test-ingress rules: - host: tls-test-ingress.com http: paths: - path: /foo backend: service: name: web1-svc port: number: 80 pathType: ImplementationSpecific
-
配置
hosts
文件或者设置真实域名来访问该TLS服务。您可以通过https://tls-test-ingress.com/foo
访问到web1-svc
服务。
配置HTTPS双向认证
在某些情况下,您可能需要配置服务器与客户端之间的双向HTTPS认证来保证连接的安全性,Nginx Ingress Controller支持通过annotation的方式配置该功能。
-
执行以下命令,创建自签的CA证书。
openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 356 -nodes -subj '/CN=Fern Cert Authority'
预期输出:
Generating a 4096 bit RSA private key .............................................................................................................++ .....................................................................................++ writing new private key to 'ca.key'
-
执行以下命令,创建Server端证书。
-
执行以下命令,创建Client端证书。
-
执行以下命令,检查创建的证书。
ls
预期输出:
ca.crt ca.key client.crt client.csr client.key server.crt server.csr server.key
-
执行以下命令,创建CA证书的Secret。
kubectl create secret generic ca-secret --from-file=ca.crt=ca.crt
预期输出:
secret/ca-secret created
-
执行以下命令,创建Server证书的Secret。
kubectl create secret generic tls-secret --from-file=tls.crt=server.crt --from-file=tls.key=server.key
预期输出:
secret/tls-secret created
-
执行以下命令,创建测试用的Nginx Ingress用例。
cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: annotations: nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret" nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" name: nginx-test namespace: default spec: rules: - host: foo.bar.com http: paths: - backend: serviceName: http-svc servicePort: 80 path: / - hosts: - foo.bar.com secretName: tls-secret
cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: nginx.ingress.kubernetes.io/auth-tls-verify-client: "on" nginx.ingress.kubernetes.io/auth-tls-secret: "default/ca-secret" nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1" nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true" name: nginx-test namespace: default spec: rules: - host: foo.bar.com http: paths: - backend: service: name: http-svc port: number: 80 path: / pathType: ImplementationSpecific - hosts: - foo.bar.com secretName: tls-secret
预期输出:
ingress.networking.k8s.io/nginx-test configured
-
执行以下命令,查看Ingress的IP地址。
kubectl get ing
预期输出如下,ADDRESS字段对应的IP地址即为Ingress的IP地址。
NAME HOSTS ADDRESS PORTS AGE nginx-test foo.bar.com 39.102.XX.XX 80, 443 4h42m
-
执行以下命令,更新Hosts文件,替换下面的IP地址为真实获取的Ingress的IP地址。
echo "39.102.XX.XX foo.bar.com" | sudo tee -a /etc/hosts
结果验证:-
客户端不传证书访问
curl --cacert ./ca.crt https://foo.bar.com
预期输出:
<html> <head><title>400 No required SSL certificate was sent</title></head> <center><h1>400 Bad Request</h1></center> <center>No required SSL certificate was sent</center> <hr><center>nginx/1.19.0</center> </body> </html>
-
客户端传证书访问
curl --cacert ./ca.crt --cert ./client.crt --key ./client.key https://foo.bar.com
预期输出:
<!DOCTYPE html> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; </style> </head> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
-
客户端不传证书访问
配置HTTPS服务转发到后端容器为HTTPS协议
Nginx Ingress Controller默认使用HTTP协议转发请求到后端业务容器。当您的业务容器为HTTPS协议时,可以通过使用注解
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
来使得Nginx Ingress Controller使用HTTPS协议转发请求到后端业务容器。
Nginx Ingress配置示例如下:
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: backend-https annotations: #注意这里:必须指定后端服务为HTTPS服务。 nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" spec: - hosts: - <your-host-name> secretName: <your-secret-cert-name> rules: - host: <your-host-name> http: paths: - path: / backend: serviceName: <your-service-name> servicePort: <your-service-port>
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: backend-https annotations: #注意这里:必须指定后端服务为HTTPS服务。 nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" spec: - hosts: - <your-host-name> secretName: <your-secret-cert-name> rules: - host: <your-host-name> http: paths: - path: / backend: service: name: <your-service-name> port: number: <your-service-port> pathType: ImplementationSpecific
配置域名支持正则化
在Kubernetes集群中,Ingress资源不支持对域名配置正则表达式,但是可以通过
nginx.ingress.kubernetes.io/server-alias
注解来实现。
-
创建Nginx Ingress,以正则表达式
~^www\.\d+\.example\.com
为例。cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: ingress-regex namespace: default annotations: nginx.ingress.kubernetes.io/server-alias: '~^www\.\d+\.example\.com$, abc.example.com' spec: rules: - host: foo.bar.com http: paths: - path: /foo backend: serviceName: http-svc1 servicePort: 80
cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-regex namespace: default annotations: nginx.ingress.kubernetes.io/server-alias: '~^www\.\d+\.example\.com$, abc.example.com' spec: rules: - host: foo.bar.com http: paths: - path: /foo backend: service: name: http-svc1 port: number: 80 pathType: ImplementationSpecific
-
执行以下命令,查看对应Nginx Ingress Controller的配置。
-
执行以下命令,获取Ingress对应的IP。
kubectl get ing
预期输出:
NAME HOSTS ADDRESS PORTS AGE ingress-regex foo.bar.com 101.37.XX.XX 80 11s
-
执行以下命令,进行不同规则下的服务访问测试。
配置以下 IP_ADDRESS 为上一步获取的IP地址。
-
执行以下命令,通过
Host: foo.bar.com
访问服务。curl -H "Host: foo.bar.com" <IP_ADDRESS>/foo
预期输出:
/foo
-
执行以下命令,通过
Host: www.123.example.com
访问服务。curl -H "Host: www.123.example.com" <IP_ADDRESS>/foo
预期输出:
/foo
-
执行以下命令,通过
Host: www.321.example.com
访问服务。curl -H "Host: www.321.example.com" <IP_ADDRESS>/foo
预期输出:
/foo
-
执行以下命令,通过
配置域名支持泛化
在Kubernetes集群中,Nginx Ingress资源支持对域名配置泛域名,例如,可配置
*. ingress-regex.com
泛域名。
-
部署以下模板,创建Nginx Ingress。
$ cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: ingress-regex namespace: default spec: rules: - host: *.ingress-regex.com http: paths: - path: /foo backend: serviceName: http-svc1 servicePort: 80
cat <<-EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-regex namespace: default spec: rules: - host: *.ingress-regex.com http: paths: - path: /foo backend: service: name: http-svc1 port: number: 80 pathType: ImplementationSpecific
-
执行以下命令,查看对应Nginx Ingress Controller的配置,可以发现生效的配置(Server_Name字段)。
kubectl exec -n kube-system <ningx-ingress-pod-name> cat /etc/nginx/nginx.conf | grep -C3 "*.ingress-regex.com"
说明 替换 ningx-ingress-pod-name 为实际环境的nginx-ingress pod。预期输出:
## start server *.ingress-regex.com server { server_name *.ingress-regex.com ; listen 80; listen [::]:80; ## end server *.ingress-regex.com
在较新版本Nginx Ingress Controller中的预期输出:
## start server *.ingress-regex.com server { server_name ~^(?<subdomain>[\w-]+)\.ingress-regex\.com$ ; listen 80; listen [::]:80; ## end server *.ingress-regex.com
-
执行以下命令,获取Ingress对应的IP。
kubectl get ing
预期输出:
NAME HOSTS ADDRESS PORTS AGE ingress-regex *.ingress-regex.com 101.37.XX.XX 80 11s
-
执行以下命令,进行不同规则下的服务访问测试。
配置以下 IP_ADDRESS 为上一步获取的IP地址。
-
执行以下命令,通过
Host: abc.ingress-regex.com
访问服务。curl -H "Host: abc.ingress-regex.com" <IP_ADDRESS>/foo
预期输出:
/foo
-
执行以下命令,通过
Host: 123.ingress-regex.com
访问服务。curl -H "Host: 123.ingress-regex.com" <IP_ADDRESS>/foo
预期输出:
/foo
-
执行以下命令,通过
Host: a1b1.ingress-regex.com
访问服务。curl -H "Host: a1b1.ingress-regex.com" <IP_ADDRESS>/foo
预期输出:
/foo
-
执行以下命令,通过
通过注解实现灰度发布
nginx.ingress.kubernetes.io/canary: "true"
,通过不同注解可以实现不同的灰度发布功能:
-
nginx.ingress.kubernetes.io/canary-weight
:设置请求到指定服务的百分比(值为0~100的整数)。 -
nginx.ingress.kubernetes.io/canary-by-header
:基于Request Header的流量切分,当配置的hearder
值为always
时,请求流量会被分配到灰度服务入口;当hearder
值为never
时,请求流量不会分配到灰度服务;将忽略其他hearder
值,并通过灰度优先级将请求流量分配到其他规则设置的灰度服务。 -
nginx.ingress.kubernetes.io/canary-by-header-value
和nginx.ingress.kubernetes.io/canary-by-header
:当请求中的hearder
和header-value
与设置的值匹配时,请求流量会被分配到灰度服务入口;将忽略其他hearder
值,并通过灰度优先级将请求流量分配到其他规则设置的灰度服务。 -
nginx.ingress.kubernetes.io/canary-by-cookie
:基于Cookie的流量切分,当配置的cookie
值为always
时,请求流量将被分配到灰度服务入口;当配置的cookie
值为never
时,请求流量将不会分配到灰度服务入口。
-
基于权重灰度:配置灰度服务的权重为20%。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "20"
-
基于Header灰度:请求Header为
ack:always
时将访问灰度服务;请求Header为ack:never
时将不访问灰度服务;其它Header将根据灰度权重将流量分配给灰度服务。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "50" nginx.ingress.kubernetes.io/canary-by-header: "ack"
-
基于Header灰度(自定义header值):当请求Header为
ack: alibaba
时将访问灰度服务;其它Header将根据灰度权重将流量分配给灰度服务。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "20" nginx.ingress.kubernetes.io/canary-by-header: "ack" nginx.ingress.kubernetes.io/canary-by-header-value: "alibaba"
-
基于Cookie灰度:当Header不匹配时,请求的Cookie为
hangzhou_region=always
时将访问灰度服务。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "20" nginx.ingress.kubernetes.io/canary-by-header: "ack" nginx.ingress.kubernetes.io/canary-by-header-value: "alibaba" nginx.ingress.kubernetes.io/canary-by-cookie: "hangzhou_region"
-
基于Cookie的灰度不支持设置自定义,只有
always
和never
。 - 灰度优先级顺序:基于Header > 基于Cookie > 基于权重(从高到低)。
使用cert-manager申请免费的HTTPS证书
cert-manager是一个云原生证书管理开源工具,用于在Kubernetes集群中提供HTTPS证书并自动续期。以下示例介绍了如何使用cert-manager申请免费证书并自动续期。
-
执行以下命令,部署cert-manager。
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
说明 上述命令中的YAML示例仅适用于在ACK集群中部署cert-manager。关于如何在ASK集群中部署cert-manager,请参见 使用cert-manager申请免费的HTTPS证书 。 -
执行以下命令,查看Pod状态。
kubectl get pods -n cert-manager
预期输出:
NAME READY STATUS RESTARTS AGE cert-manager-1 1/1 Running 0 2m11s cert-manager-cainjector 1/1 Running 0 2m11s cert-manager-webhook 1/1 Running 0 2m10s
-
执行以下命令,创建ClusterIssuer。
cat <<EOF | kubectl apply -f - apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod-http01 spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: <your_email_name@gmail.com> #替换为您的邮箱名。 privateKeySecretRef: name: letsencrypt-http01 solvers: - http01: ingress: class: nginx EOF
-
执行以下命令,查看ClusterIssuer。
kubectl get clusterissuer
预期输出:
NAME READY AGE letsencrypt-prod-http01 True 17s
-
执行以下命令,创建Nginx Ingress资源对象。
cat <<EOF | kubectl apply -f - apiVersion: extensions/v1beta1 kind: Ingress metadata: name: ingress-tls annotations: kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: "letsencrypt-prod-http01" spec: - hosts: - <your_domain_name> # 替换为您的域名。 secretName: ingress-tls rules: - host: <your_domain_name> # 替换为您的域名。 http: paths: - path: / backend: serviceName: <your_service_name> # 替换为您的后端服务名。 servicePort: <your_service_port> # 替换为您的服务端口。
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-tls annotations: kubernetes.io/ingress.class: "nginx" cert-manager.io/cluster-issuer: "letsencrypt-prod-http01" spec: - hosts: - <your_domain_name> # 替换为您的域名。 secretName: ingress-tls rules: - host: <your_domain_name> # 替换为您的域名。 http: paths: - path: / backend: service: name: <your_service_name> # 替换为您的后端服务名。 port: number: <your_service_port> # 替换为您的服务端口。 pathType: ImplementationSpecific
说明 替换的域名your_domain_name必须符合以下条件:- 域名不能超过64个字符。
- 不支持泛域名。
- 在公网以HTTP协议可正常访问。
-
执行以下命令,查看证书。
kubectl get cert
预期输出:
NAME READY SECRET AGE ingress-tls True ingress-tls 52m
说明 如果 READY 状态不为 True ,可通过kubectl describe cert ingress-tls
查看证书处理过程。 -
执行以下命令,查看Secret。
kubectl get secret ingress-tls
预期输出:
NAME TYPE DATA AGE ingress-tls kubernetes.io/tls 2 2m
-
通过Web浏览器输入
https:[网站域名]
访问设置的域名。