nginx ingress最后的倔强: admission webhook

背景

k8s中大多使用nginx-ingress-controller来实现ingress, 但是脆弱的nginx-controller通过ingress解析出nginx配置, 对于某些annotation会reload nignx配置失败, 然后controller就卡死了, 不断重启, 除非删除对应的ingress.

问题复现

创建有问题的ingress

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "false"
    nginx.ingress.kubernetes.io/auth-tls-verify-client: optional
    nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"
    nginx.ingress.kubernetes.io/configuration-snippet: |
      proxy_set_header Host $targethost;
      proxy_buffering     off;
      proxy_pass          http://$targetbackend;
      proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
      proxy_redirect      off;
      proxy_set_header    X-SSL-Client-Verify $ssl_client_verify;
      proxy_set_header    X-SSL-Client-DN $ssl_client_s_dn;
      proxy_set_header    X-Real-IP       $remote_addr;
      proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
  creationTimestamp: "2020-03-23T04:57:22Z"
  generation: 1
  name: example-ingress
  namespace: kube-system
  resourceVersion: "57681168"
  selfLink: /apis/extensions/v1beta1/namespaces/kube-system/ingresses/example-ingress
  uid: c7f66385-6cc2-11ea-b6a8-246e96d4b538
spec:
  rules:
  - host: example.com
    http:
      paths:
      - backend:
          serviceName: example-svc
          servicePort: 8008
        path: /
  tls:
  - hosts:
    - example.com
    secretName: example-tls
status:
  loadBalancer: {}

查看nginx-ingress-controller状态全部为CrashLoopBackOff

# kubectl get po -n kube-system -owide |grep ingress
nginx-ingress-controller-ftfbg                        1/2     CrashLoopBackOff   6          8m27s
nginx-ingress-controller-hp4pf                        1/2     CrashLoopBackOff   11         24m  
nginx-ingress-controller-qlb4l                        1/2     CrashLoopBackOff   11         24m   

查看nginx-ingress-controller日志, 显示reload失败"proxy_pass" directive is duplicate in /tmp/nginx-cfg911768424:822

-------------------------------------------------------------------------------
W0403 10:26:14.716246       1 queue.go:130] requeuing kube-system/nginx-ingress-controller-4txfk, err 
-------------------------------------------------------------------------------
Error: exit status 1
2020/04/03 10:26:14 [notice] 137#137: ModSecurity-nginx v1.0.0
2020/04/03 10:26:14 [warn] 137#137: duplicate value "error" in /tmp/nginx-cfg911768424:815
nginx: [warn] duplicate value "error" in /tmp/nginx-cfg911768424:815
2020/04/03 10:26:14 [warn] 137#137: duplicate value "timeout" in /tmp/nginx-cfg911768424:815
nginx: [warn] duplicate value "timeout" in /tmp/nginx-cfg911768424:815
2020/04/03 10:26:14 [emerg] 137#137: "proxy_pass" directive is duplicate in /tmp/nginx-cfg911768424:822
nginx: [emerg] "proxy_pass" directive is duplicate in /tmp/nginx-cfg911768424:822
nginx: configuration file /tmp/nginx-cfg911768424 test failed

-------------------------------------------------------------------------------
W0403 10:26:16.998897       1 nginx_status.go:207] unexpected error obtaining nginx status info: unexpected error scraping nginx status page: unexpected error scraping nginx : Get http://0.0.0.0:18080/nginx_status: dial tcp 0.0.0.0:18080: connect: connection refused
I0403 10:26:17.526801       1 main.go:167] Received SIGTERM, shutting down
I0403 10:26:17.526827       1 nginx.go:364] Shutting down controller queues
I0403 10:26:17.526845       1 status.go:200] updating status of Ingress rules (remove)
I0403 10:26:17.537511       1 status.go:219] removing address from ingress status ([])
I0403 10:26:17.537593       1 nginx.go:372] Stopping NGINX process
2020/04/03 10:26:17 [notice] 141#141: signal process started
I0403 10:26:20.547669       1 nginx.go:385] NGINX process has stopped
I0403 10:26:20.547692       1 main.go:175] Handled quit, awaiting Pod deletion
I0403 10:26:30.547824       1 main.go:178] Exiting with 0

解决方案

创建一个有问题的ingress, 会影响所有新创建的ingress规则, 又一个集群级别的Bug诞生了.那么有没有办法, 提前检验ingress配置, 有问题就不去reload. 那验证步骤肯定要在请求到达nginx-controller之前来做, 是不是想到了k8s-admission-webhook, 可以在apiserver持久化对象前拦截请求, 去实现自定义的验证规则. 好在新版本的nginx-ingress-controller(v0.25.0+)已经实现了相关的功能, 只需开启对应配置就行.

ApiServer配置

Apiserver开启webhook相关配置, 必须包含MutatingAdmissionWebhookValidatingAdmissionWebhook

--admission-control=MutatingAdmissionWebhook,ValidatingAdmissionWebhook

创建webhook相关配置

启用ValidatingAdmissionWebhook必须使用https, 需要配置对应证书

  • 手动生成:
    openssl req -x509 -newkey rsa:2048 -keyout certificate.pem -out key.pem -days 365 -nodes -subj "/CN=ingress-validation-webhook.ingress-nginx.svc"
    
  • CertificateSigningRequest
    通过k8s CertificateSigningRequest来创建(controller-manager需要开启--cluster-signing-cert-file--cluster-signing-key-file)
    可通过如下脚本创建, namespace与service替换成自己的
    SERVICE_NAME=ingress-nginx
    NAMESPACE=ingress-nginx
    
    TEMP_DIRECTORY=$(mktemp -d)
    echo "creating certs in directory ${TEMP_DIRECTORY}"
    
    cat <<EOF >> ${TEMP_DIRECTORY}/csr.conf
    [req]
    req_extensions = v3_req
    distinguished_name = req_distinguished_name
    [req_distinguished_name]
    [ v3_req ]
    basicConstraints = CA:FALSE
    keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    extendedKeyUsage = serverAuth
    subjectAltName = @alt_names
    [alt_names]
    DNS.1 = ${SERVICE_NAME}
    DNS.2 = ${SERVICE_NAME}.${NAMESPACE}
    DNS.3 = ${SERVICE_NAME}.${NAMESPACE}.svc
    EOF
    
    openssl genrsa -out ${TEMP_DIRECTORY}/server-key.pem 2048
    openssl req -new -key ${TEMP_DIRECTORY}/server-key.pem \
        -subj "/CN=${SERVICE_NAME}.${NAMESPACE}.svc" \
        -out ${TEMP_DIRECTORY}/server.csr \
        -config ${TEMP_DIRECTORY}/csr.conf
    
    cat <<EOF | kubectl create -f -
    apiVersion: certificates.k8s.io/v1beta1
    kind: CertificateSigningRequest
    metadata:
      name: ${SERVICE_NAME}.${NAMESPACE}.svc
    spec:
      request: $(cat ${TEMP_DIRECTORY}/server.csr | base64 | tr -d '\n')
      usages:
      - digital signature
      - key encipherment
      - server auth
    EOF
    
    kubectl certificate approve ${SERVICE_NAME}.${NAMESPACE}.svc
    
    for x in $(seq 10); do
        SERVER_CERT=$(kubectl get csr ${SERVICE_NAME}.${NAMESPACE}.svc -o jsonpath='{.status.certificate}')
        if [[ ${SERVER_CERT} != '' ]]; then
            break
        fi
        sleep 1
    done
    if [[ ${SERVER_CERT} == '' ]]; then
        echo "ERROR: After approving csr ${SERVICE_NAME}.${NAMESPACE}.svc, the signed certificate did not appear on the resource. Giving up after 10 attempts." >&2
        exit 1
    fi
    echo ${SERVER_CERT} | openssl base64 -d -A -out ${TEMP_DIRECTORY}/server-cert.pem
    
    kubectl create secret generic ingress-nginx.svc \
        --from-file=key.pem=${TEMP_DIRECTORY}/server-key.pem \
        --from-file=cert.pem=${TEMP_DIRECTORY}/server-cert.pem \
        -n ${NAMESPACE}
    

配置ingress controller

ingress controller需要启用如下参数, 挂载需要的tls证书

flagdescriptionexample usage
--validating-webhookadmission webhook的地址:8080
--validating-webhook-certificatewebhook证书/usr/local/certificates/validating-webhook.pem
--validating-webhook-keywebhook私钥/usr/local/certificates/validating-webhook-key.pem

验证

更新后, 创建有问题的ingress则会拦截, 符合预期

# kubectl apply -f ing.yaml
Error from server: error when creating "ing.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: 
-------------------------------------------------------------------------------
Error: exit status 1
2020/04/02 10:26:04 [emerg] 331#331: directive "proxy_pass" is not terminated by ";" in /tmp/nginx-cfg461116913:2165
nginx: [emerg] directive "proxy_pass" is not terminated by ";" in /tmp/nginx-cfg461116913:2165
nginx: configuration file /tmp/nginx-cfg461116913 test failed

引用

  • https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/
  • https://kubernetes.github.io/ingress-nginx/deploy/validating-webhook/
  • https://qingwave.github.io/ingress-nginx-controller-admission-webhook/

更多文章见 Qinng’s Blog

<think>嗯,用户遇到了Kubernetes Ingress的问题,执行kubectl create -f laplop.yaml的时候报错了。错误信息是关于admission webhook拒绝了请求,因为使用了nginx.ingress.kubernetes.io/server-snippet注解,但管理员禁用了这个功能。我需要先理解这个错误的根本原因。 首先,我应该回忆一下Nginx Ingress Controller的配置。管理员可以设置是否允许使用snippet相关的注解,比如server-snippet或者configuration-snippet。如果这些被禁用,用户尝试在Ingress资源中使用这些注解就会被拒绝。用户可能是在YAML文件中添加了server-snippet的注解,导致了这个错误。 接下来,我需要考虑用户为什么会使用server-snippet。通常,这是为了自定义Nginx的配置,比如添加特定的响应头或者复杂的路由规则。但管理员可能出于安全或一致性的考虑禁用了这个功能,防止用户插入可能影响全局配置的代码。 然后,解决方案可能包括几个方面。首先,检查YAML文件中的注解,确认是否有server-snippet的存在。如果有,用户可能需要删除或替换这部分配置。其次,如果确实需要自定义配置,可能需要联系集群管理员,询问是否有其他允许的方法,或者是否可以临时启用snippet功能。另外,管理员可能需要修改Ingress Controller的启动参数,启用snippet相关的选项,比如在部署时添加--enable-snippets标志。 还需要考虑用户是否有权限修改Ingress Controller的配置,或者是否需要通过其他方式实现需求,比如使用ConfigMap来传递全局配置,或者使用其他允许的注解来实现相同的功能。此外,用户可能需要了解集群的策略,是否完全禁止了snippet,或者只是部分限制,这样他们可以调整自己的配置来符合规范。 最后,应该建议用户检查文档或与管理员沟通,确认允许的配置方式,并调整他们的YAML文件。同时,提醒用户在修改配置时注意语法和合规性,避免类似错误再次发生。</think>以下是逐步解决该问题的分析及建议: ### 错误原因分析 ```bash Error from server (BadRequest): error when creating "laplop.yaml": admission webhook "validate.nginx.ingress.kubernetes.io" denied the request: nginx.ingress.kubernetes.io/server-snippet annotation cannot be used. Snippet directives are disabled by the Ingress administrator ``` 1. **准入控制拦截**:Kubernetes集群启用了 `validate.nginx.ingress.kubernetes.io` 这个Admission Webhook 2. **禁用Snippet指令**:Nginx Ingress Controller管理员通过配置参数 `--enable-snippet=false` 禁用了代码片段功能 3. **违规注解**:您的YAML文件中使用了被禁止的注解 `nginx.ingress.kubernetes.io/server-snippet` ### 解决方案步骤 #### 第一步:检查YAML文件 ```yaml # 示例问题片段 metadata: annotations: nginx.ingress.kubernetes.io/server-snippet: | # 违规注解 add_header Custom-Header "Blocked"; ``` #### 第二步:替代方案选择 | 方案 | 操作 | 适用场景 | |------|------|----------| | 1. 删除snippet | 移除所有`server-snippet`相关注解 | 不需要自定义Nginx配置时 | | 2. 使用ConfigMap | 通过`nginx-configuration` ConfigMap全局配置 | 需要统一Nginx配置时 | | 3. 联系管理员 | 请求临时开启snippet功能 | 必须使用snippet的特殊需求 | #### 第三步:配置修正示例 ```yaml # 修正后的安全配置示例 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress annotations: nginx.ingress.kubernetes.io/proxy-connect-timeout: "60" # 使用合法注解 spec: rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80 ``` ### 管理员操作指南 如需启用snippet功能,管理员需要: ```bash # 修改Ingress Controller部署参数 kubectl edit deploy nginx-ingress-controller -n ingress-nginx ``` ```yaml # 在spec.template.spec.containers.args中添加 - --enable-snippets=true ``` ### 补充建议 1. 使用`kubectl describe ingress <name>`检查现有配置 2. 通过`kubectl get validatingwebhookconfiguration`查看其他准入控制规则 3. 考虑使用Open Policy Agent(OPA)实现更精细的策略控制 该错误表明集群执行了严格的安全策略,建议优先通过标准注解实现需求。如需深度定制Nginx配置,建议与基础设施团队协作设计合规方案。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值