zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Ingress-Nginx进阶学习扩展实践

扩展学习Nginx 实践 进阶 ingress
2023-06-13 09:13:35 时间

[TOC]

0x00 Ingress-Nginx 快速安装配置实践

描述: 此节,作为上一章的扩展补充,主要因为ingress-nginx迭代较快,加入了很多新得特性导致原来某些配置被弃用,当前时间节点【2022年3月8日 17:24:28】针对现有Ingress-nginx版本(v1.1.1)进行快速安装配置,与上一章中的安装是存在一定的不同,安装时都可以作为参考。

知识复习 Q: 什么是Ingress? A: Ingress 是管理对集群中服务的提供外部访问的 API 对象,Ingress 控制器负责实现 Ingress,通常使用负载均衡器,但它也可以配置边缘路由器或其他前端来帮助处理流量,它可以将来自集群外部的 HTTP 和 HTTPS 路由转发到集群内部的 Service 中。

Ingress 只是一个统称,其由 Ingress 和 Ingress Controller 两部分组成。

  • Ingress 用作将原来需要手动配置的规则抽象成一个 Ingress 对象,使用 YAML 格式的文件来创建和管理。
  • Ingress Controller 用作通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化。

使用 Ingress 控制器可以轻松实现外部URL访问集群内部服务、负载均衡、代理转发、支持配置SSL/TLS并提供基于名称的虚拟主机,值得注意的是 Ingress 不会暴露任意端口或协议,通过使用 Service.Type=NodePortService.Type=LoadBalancer类型的服务向向 Internet 公开 HTTP 和 HTTPS 的访问服务

Q: 常用 Ingress 控制器有那些? 其它更多适用于Kubernetes的ingress控制器可以参考地址[https://kubernetes.io/zh/docs/concepts/services-networking/ingress-controllers/#%E5%85%B6%E4%BB%96%E6%8E%A7%E5%88%B6%E5%99%A8]

温馨提示: 理想情况下所有 Ingress 控制器都应符合参考规范。实际上各种 Ingress 控制器的操作略有不同,请参考相应Ingress的控制器官方文档。

如下图所示的一个简单的示例,客户端请求访问外部URL地址, Ingress 将其所有流量发送到一个Service中, 后端 Pod 提供服务端响应通过路由进行返回给客户端。

WeiyiGeek.Ingress

Q: Ingress 规则有哪些?

  • host : 虚拟主机名称, 主机名通配符主机可以是精确匹配(例如”foo.bar.com”)或通配符(例如“ *.foo.com”)
  • paths : URL访问路径。
  • pathType : Ingress 中的每个路径都需要有对应的路径类型(Path Type)
  • backend : 是 Service 文档中所述的服务和端口名称的组合与规则的 host 和 path 匹配的对 Ingress 的 HTTP(和 HTTPS )请求将发送到列出的 backend, 一般情况可以单独为路径设置Backend以及未匹配的url默认访问的后端defaultBackend。

Ingress 中的每个路径都需要有对应的路径类型(Path Type),未明确设置 pathType 的路径无法通过合法性检查,当前支持的路径类型有三种:

  • Exact:精确匹配 URL 路径,且区分大小写。
  • Prefix:基于以/分隔的URL路径前缀匹配, 且区分大小写,并且对路径中的元素逐个完成。
  • ImplementationSpecific:此路径类型匹配方法取决于 IngressClass, 具体实现可以将其作为单独的 pathType 处理或者与 Prefix 、 Exact 类型作相同处理。

说明: 如果路径的最后一个元素是请求路径中最后一个元素的子字符串,则不会匹配 (例如:/foo/bar 匹配 /foo/bar/baz, 但不匹配 /foo/barbaz)。

温馨提示: defaultBackend 通常在 Ingress 控制器中配置,以服务与规范中的路径不匹配的任何请求。

tee > test.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  defaultBackend:
    service:
      name: test
      port:
        number: 80
EOF
kubectl apply -f

注意, 入口控制器和负载平衡器可能需要一两分钟才能分配 IP 地址。 在此之前,你通常会看到地址字段的值被设定为<pending>

1.快速安装

# 1.下载 v1.1.1 版本的ingress YAML资源清单到本地。
$ wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.1.1/deploy/static/provider/cloud/deploy.yaml

# 2.为了加快国内环境拉取镜像的速度,此处将k8s.gcr.io替换为阿里云提供的镜像源。
$ sed -i -r 's#k8s.gcr.io/ingress-nginx/controller:(.*)@.*$#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:\1#g' ingress-nginx-deploy.yaml
$ sed -i -r 's#k8s.gcr.io/ingress-nginx/(.*)@.*$#registry.cn-hangzhou.aliyuncs.com/google_containers/\1#g' ingress-nginx-deploy.yaml
#  或者使用skopeo同步到私有仓库
# ./skopeo copy --insecure-policy --src-tls-verify=false --dest-tls-verify=false --dest-authfile /root/.docker/config.json docker://registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.2.0 docker://harbor.weiyigeek.top/devops/nginx-ingress-controller:v1.2.0


# 3.在ingress-nginx命名空间中,部署 YAML 清单中ingress相关的控制器资源
$ kubectl apply -f ingress-nginx-deploy.yaml

# 4.指定条件等待Ingress相关资源部署完成以及查看namespace中部署ingress的相关Pod及其状态
$ kubectl wait --namespace ingress-nginx \
  --for=condition=ready pod \
  --selector=app.kubernetes.io/component=controller \
  --timeout=120s
$ kubectl get pods --namespace=ingress-nginx

# 5.创建的默认后端的资源名称一定要与ingress-nginx-controller中的 --default-backend-service 字段值相对应
  # 使用如下命令利用deployment 资源控制器快速创建default-backend 对象
$ kubectl create deployment default-backend --image=nginx:latest --namespace ingress-nginx
  # 为default-backend Pod 创建clusterip 类型的服务
$ kubectl create service clusterip default-backend --namespace ingress-nginx --tcp=80

  # 或者资源清单的方式
tee default-backend.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
  annotations:
    prometheus.io/path: /metrics
    prometheus.io/port: "80"
    prometheus.io/scheme: http
  labels:
    app: weiyigeek.top
    ref: default-backend
  name: default-backend
  namespace: ingress-nginx
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: weiyigeek.top
    ref: default-backend
  type: ClusterIP
EOF

# 6.查看Ingress的ClassName,此处名称为nginx
$ kubectl get ingressclasses.networking.k8s.io
  # NAME      CONTROLLER             PARAMETERS        AGE
  # nginx     k8s.io/ingress-nginx        <none>       10d

# 将ingress controler服务Pod运行在所有Work节点上,有几台机器就可以设置几个副本,当然master要去除污点后使用
~$ kubectl scale deployment -n ingress-nginx ingress-nginx-controller --replicas=4

2.服务验证

描述: 创建使用指定的名称Ingress入口,此处可以采用两种方式进行创建,第一种方式是通过命令行方式,第二种是通过Ingress资源清单方式。

# 方式1,创建一个名为demo-myweb-blog的入口,控制器名称为nginx,规则是将访问demo.weiyigeek.top请求转发到后端myweb-blog:80 服务之上
kubectl create ingress demo-myweb-blog --class=nginx --rule="demo.weiyigeek.top/=web-blog:80" --namespace devtest
# kubectl create ingress test-app -n devtest --rule="test.app.weiyigeek.top/oa(/|\$)(.*)=weiyigeek-oa-prod:8080" --annotation nginx.ingress.kubernetes.io/rewrite-target=/\$2

# 方式2.Ingress资源清单如下所示,其中重点是ingressClassName与rules字段
cat > www-myweb-blog.ingress <<'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: www-blog
  namespace: devtest
  annotations:
    kubernetes.io/ingress.class: "nginx"              # 方式1.根据该注解指定发现 Ingress
    ingressclass.kubernetes.io/is-default-class: true # 如未设置IngressClassName名称则采用默认Ingress
    nginx.ingress.kubernetes.io/ssl-redirect : false  # 禁用强制跳转
    nginx.ingress.kubernetes.io/rewrite-target: /
  labels:
    app: blog
spec:
  ingressClassName: nginx  # 方式2.根据该字段指定发现 Ingress
  defaultBackend:
    service:
      name: default-http
      port:
        number: 80
  rules:
  - host: "www.weiyigeek.top"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: www-blog
            port:
              number: 80
  - host: "web.weiyigeek.top"
    http:
      paths:
      - pathType: ImplementationSpecific
        path: "/"
        backend:
          service:
            name: web-blog
            port:
              number: 80 
  tls:
  - hosts:
      - www.weiyigeek.top
    secretName: testsecret-tls   
EOF
# 部署资源清单
kubectl apply -f www-myweb-blog.ingress

# 查看上面部署的ingress入口信息及其规则默认后端信息。
kubectl get ingress -n devtest
kubectl describe ingress -n devtest

# 调用修改后的 Ingress yaml 文件。
kubectl replace -f www-myweb-blog.ingress

# 通过本机的hosts文件,手动将www.weiyigeek.top、demo.weiyigeek.top域名绑定到K8S中Node节点IP地址上,即可通过浏览器访问。
192.168.12.222 www.weiyigeek.top demo.weiyigeek.top

WeiyiGeek.describe ingress

3.配置虚拟主机证书

描述: 通过设定包含 TLS 私钥和证书的 Secret 来保护 Ingress, TLS Secret 必须包含名为 tls.crt (证书)tls.key (私钥) 的键名

# 方式1.资源清单方式
apiVersion: v1
kind: Secret
metadata:
  name: www.weiyigeek.top
  namespace: devtest
data:
  tls.crt: `base64 编码的 cert`
  tls.key: `base64 编码的 key`
type: kubernetes.io/tls

# 方式2.kubectl 命令行方式
kubectl create secret tls www.weiyigeek.top --cert=server.crt --key=server.key -n devtest

# 使用示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
      - www.weiyigeek.top 
    secretName: testsecret-tls

温馨提示: 在 Ingress 中引用此 Secret 将会告诉 Ingress 控制器使用 TLS 加密从客户端到负载均衡器的通道。

4.使用hostNetwork网络方式

描述: 定义后Ingress-controller的IP就与宿主机IP一样。

步骤 01.编辑 Ingress-controller 的 deploy 资源控制器内容,添加hostNetWork为true的键值对,。

$ kubectl edit deployments.apps -n ingress-nginx ingress-nginx-controller
    spec:
      hostNetwork: true
      containers:
      - args:
        - /nginx-ingress-controller

步骤 02.查看Pod的IP地址,此时你会发现其地址为节点主机地址

~$ kubectl get pod -n ingress-nginx  -o wide | grep "ingress-nginx-controller"
ingress-nginx-controller-6b8bd48548-dfnvc   1/1     Running     0          4m26s   192.168.12.224   weiyigeek-224
ingress-nginx-controller-6b8bd48548-r9kpl   1/1     Running     0          5m      192.168.12.226   weiyigeek-226
ingress-nginx-controller-6b8bd48548-x9w9j   1/1     Running     0          5m33s   192.168.12.225   weiyigeek-225
ingress-nginx-controller-6b8bd48548-xqpcc   1/1     Running     0          5m33s   192.168.12.223   weiyigeek-223 

~$ curl 192.168.12.225
Hello ingress-Nginx , Index /

步骤 03.让Pod均衡的分别运行在work节点,我们需要执行以下两步,

# 设置工作节点标签
~$ kubectl label nodes weiyigeek-223 weiyigeek-224 weiyigeek-225 weiyigeek-226 node=work
  # node/weiyigeek-223 labeled
  # node/weiyigeek-224 labeled
  # node/weiyigeek-225 labeled
  # node/weiyigeek-226 labeled


# 设置节点硬亲和、pod软亲和
~$ kubectl edit deployments.apps -n ingress-nginx ingress-nginx-controller
    spec: 
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node
                operator: In
                values:
                - work
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: ingress
                  operator: In
                  values:
                  - nginx
              topologyKey: kubernetes.io/hostname
            weight: 100

5.使用externalIP网络方式

描述: 前面我们使用了 hostNetwork 方式,知道了可以使用 controller.hostNetwork=true 参数进行设置, 此处我们采用采用 externalIP 的方式进行服务暴露。

温馨提示:默认的控制器类型是Deployment,不过为其稳定性建议使用 DaemonSet 类型的控制器, 设置 hostNetwork 为 true、设置 dnsPolicy 为 ClusterFirstWithHostNet,也建议为要部署到的节点上打上ingress标签, 然后使用NodeSelector添加ingress: "true"部署至指定节点。

安装部署

# 启用 RBAC 支持
$ helm install --name nginx-ingress --set "rbac.create=true,controller.service.externalIPs[0]=192.168.12.211,controller.service.externalIPs[1]=192.168.12.212,controller.service.externalIPs[2]=192.168.12.213" stable/nginx-ingress

部署完成后我们可以看到 Kubernetes 服务中增加了 nginx-ingress-controllernginx-ingress-default-backend 两个服务。nginx-ingress-controller 为 Ingress Controller,主要做为一个七层的负载均衡器来提供 HTTP 路由、粘性会话、SSL 终止、SSL直通、TCP 和 UDP 负载平衡等功能。nginx-ingress-default-backend 为默认的后端,当集群外部的请求通过 Ingress 进入到集群内部时,如果无法负载到相应后端的 Service 上时,这种未知的请求将会被负载到这个默认的后端上。

由于我们采用了 externalIP 方式对外暴露服务, 所以 nginx-ingress-controller 会在 192.168.12.211、192.168.12.212、192.168.12.213 三台节点宿主机上的 暴露 80/443 端口。

$ kubectl get svc | grep "nginx-ingress-controller"
nginx-ingress-controller        LoadBalancer   10.254.84.72     192.168.12.211,192.168.12.212,192.168.12.213   80:8410/TCP,443:8948/TCP   46s

访问 Nginx Ingress Controller , 我们可以使用以下命令来获取 Nginx 的 HTTP 和 HTTPS 地址。

$ kubectl --namespace default get services -o wide -w nginx-ingress-controller

访问验证:

# 返回 200
$ curl -I  http://192.168.12.211
$ curl -I --insecure http://192.168.12.211

在几台节点宿主机上查看,我们可以看到 ExternalIP 的 Service 是通过 Kube-Proxy对外暴露的,这里的 192.168.12.211、192.168.12.212、192.168.12.213 是三个内网 IP。 实际生产应用中是需要通过边缘路由器或全局统一接入层的负载均衡器将到达公网 IP 的外网流量转发到这几个内网 IP 上,外部用户再通过域名访问集群中以 Ingress 暴露的所有服务。

$ sudo netstat -tlunp|grep kube-proxy|grep -E '80|443'
tcp        0      0 192.168.12.211:80      0.0.0.0:*               LISTEN      714/kube-proxy
tcp        0      0 192.168.12.211:443     0.0.0.0:*               LISTEN      714/kube-proxy

$ sudo netstat -tlunp|grep kube-proxy|grep -E '80|443'
tcp        0      0 192.168.12.212:80      0.0.0.0:*               LISTEN      690/kube-proxy
tcp        0      0 192.168.12.212:443     0.0.0.0:*               LISTEN      690/kube-proxy

$ sudo netstat -tlunp|grep kube-proxy|grep -E '80|443'
tcp        0      0 192.168.12.213:80      0.0.0.0:*               LISTEN      748/kube-proxy
tcp        0      0 192.168.12.213:443     0.0.0.0:*               LISTEN      748/kube-proxy

6.对外暴露端口配置

描述: 默认的 Ingress 配置是暴露 http(80) 、https(443) 端口, 但需要注意的是创建的 Ingress 必须要和对外暴露的 Service 在同一命名空间下!

除此之外其实还可通过 Ingress controller 来实现TCP 和 UDP 服务端口的暴露,下面进行一一讲解。

1) http/https端口 描述: 缺少ingress支持http与https协议的后端应用。

cat '
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2
  name: rewrite
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: rewrite.weiyigeek.top
    http:
      paths:
      - path: /something(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: http-svc
            port: 
              number: 80
' | kubectl create -f -

2) 暴露TCP/UDP端口 描述: 我们可以通过 tcp-services-configmap.yaml 设置映射tcp, 通过 udp-services-configmap.yaml 映射udp

# tcp-services-configmap.yaml
cat > tcp-services-configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: tcp-services
  namespace: ingress-nginx
data:
  2181: "kafka/kafka-zookeeper:2181"
  50000: "devops/jenkins:50000"
EOF

cat  tcp-services-configmap.yaml | kubectl create -f -

# udp-services-configmap.yaml
tee udp-services-configmap.yaml <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
  name: udp-services
  namespace: ingress-nginx
data:
  53: "kube-system/kube-dns:53"
EOF

cat udp-services-configmap.yaml | kubectl create -f -

查看 ConfigMap 资源

~$ kubectl get cm -n ingress-nginx  | grep "services"
tcp-services                2      15h
udp-services                1      15h

验证暴露的 TCP/UDP 服务:

~$ telnet 192.168.12.225 50000
Trying 192.168.12.225...
Connected to 192.168.12.225.
Escape character is '^]'.

~$ dig -x jenkins.devops.svc @192.168.12.225

3) 通过helm更新公开端口 描述: 通过helm chart图表中的deployment-tcp-udp-values.yaml或者deployment-tcp-udp-configMapNamespace-values.yaml文件进行暴露TCP/UDP (如果你使用采用helm方式部署的ingress,此种方式是推荐的。), 注意此种方式

# ngress-nginx/ci/deployment-tcp-udp-values.yaml
controller:
  image:
    repository: ingress-controller/controller
    tag: 1.0.0-dev
    digest: null
  admissionWebhooks:
    enabled: false
  service:
    type: ClusterIP
  
tcp:
  9000: "default/test:8080"
udp:
  9001: "default/test:8080"

# 执行更新
helm upgrade nginx-ingress stable/nginx-ingress \
-f ./ci/deployment-tcp-udp-values.yaml

7.节点与Pod亲和性设置

描述: 为 deployments.apps 资源控制器部署的 ingress 设置节点亲和。

sepc:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: node
            operator: In
            values:
            - app
            - ali
            - www
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: kubernetes.io/os
              operator: In
              values:
              - linux
          topologyKey: kubernetes.io/hostname
        weight: 100
  nodeSelector:
    k8s.io/ingress-nginx: "true"

8.基于 auth-url 模块认证方式实践

1.使用auth-url模块配置ldap登录 描述: 在 K8s 中我们可以使用 nginx Ingressauth-url 模块配置ldap登录验证。

前置需求已经有部署自己的ldap server, 然后还要在要配置ldap验证的服务ingress 上添加以下annotation:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/auth-url: https://{server_domain}/auth/ldap/$remote_user/$http_authorization

温馨提示: 此处 {server_domain} 是你的 ldap-auth-server 域名或IP地址, 其实现原理也是非常简单的即用你的程序去ldap server校验用户名密码是否正确即可。

# app.py
# -*- coding: utf-8 -*-
# description: ldap 服务用户账号密码验证
from ldap3 import Server, Connection, ALL, SUBTREE, ALL_ATTRIBUTES, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE
from ldap3.utils.hashed import hashed
from ldap3 import (
    HASHED_SALTED_SHA, MODIFY_REPLACE
)
import json,os
import base64
from flask import (
    Flask,
    Response,
    request,
    render_template,
    redirect,
    jsonify,
    make_response,
    url_for,
    abort,
)

from helpers import (
    status_code
)

app = Flask(__name__)

class LdapUtils(object):
    def __init__(self, ldap_host=None, port=None, base_dn=None, user=None, password=None):
        self.base_dn = base_dn
        try:
            server = Server(ldap_host, port, get_info=ALL)
            self.ldapconn = Connection(server, user=None, password=None,
                                       auto_bind='NONE', version=3, authentication='SIMPLE',
                                       client_strategy='SYNC',
                                       auto_referrals=True, check_names=True, read_only=False, lazy=False,
                                       raise_exceptions=False)
            self.ldapconn.rebind(user=user, password=password)
        except Exception as e:
            print(e)

    def ldap_search_dn(self, uid=None):
        obj = self.ldapconn
        search_base = self.base_dn
        search_scope = SUBTREE
        search_filter = "(cn={0})".format(uid)
        try:
            obj.search(search_base, search_filter, search_scope, attributes=['cn'], paged_size=1)
            if len(obj.response) == 1:
                return obj.response[0]['dn']
            else:
                return None
        except Exception as e:
            print(e)

    def ldap_update_pass(self, uid=None, oldpass=None, newpass=None):
        target_cn = self.ldap_search_dn(uid)
        try:
            hashed_password = hashed(HASHED_SALTED_SHA, newpass)
            print("password:" + hashed_password)
            changes = {
                'userPassword': [(MODIFY_REPLACE, [hashed_password])]
            }
            return self.ldapconn.modify(target_cn, changes=changes)
        except Exception as e:
            print(e)
            return False

    def ldap_get_vaild(self, uid=None, passwd=None):
        if not uid or not passwd:
            return False
        obj = self.ldapconn
        # 这里注意修改成自己ldap上定义的user dn 
        dn = "uid={0},ou=Users,dc=xxxxx,dc=com".format(uid)
        try:
            if obj.rebind(dn, passwd):
                return True
            else:
                return False
        except Exception as e:
            print('e:' + str(e))

# 这里我是通过docker部署服务,这些配置在环境变量传入,你可以根据需要选择直接填写对应值
ldap_host = os.getenv("host")
port = os.getenv("port")
base_dn = os.getenv("base_dn")
user = os.getenv("admin_user")
password = os.getenv("password")

ldap = LdapUtils(ldap_host, int(port), base_dn, user,password)  

@app.route('/auth/<user>/<passwd>')
def auth_user(user="user", passwd="passwd"):
    if not ldap.ldap_get_vaild(uid=user,passwd=passwd):
        return status_code(401)
    return jsonify(authenticated=True, user=user)
    
@app.route('/auth/<qop>/<user>/<passwd>')
def auth_qop_user(qop=None, user="user", passwd="passwd"):
    temp = base64.b64decode(passwd.split(" ")[1]).decode()
    if not ldap.ldap_get_vaild(uid=user,passwd=temp.split(":")[1]):
        return status_code(401)
    return jsonify(authenticated=True, user=user)

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
    auth = request.headers.get('Authorization')
    if auth is not None:
        uid = base64.b64decode(auth.split(" ")[1]).decode().split(":")[0]
        passwd = base64.b64decode(auth.split(" ")[1]).decode().split(":")[1]
        if not ldap.ldap_get_vaild(uid=uid,passwd=passwd):
            return status_code(401)
        return render_template('password.html',uid=uid)
    return status_code(401)

if __name__ == '__main__':
   app.run(host = '0.0.0.0')

0x01 入坑出坑

问题1.K8s Nginx Ingress Controller 转发 X-Forwarded-Proto 请求头的问题

问题说明: 在 Kubernetes 集群上部署了 Nginx Ingress Controller 最前端用的是阿里云七层负载均衡,部署后发现不能正确转发 X-Forwarded-Proto 请求头,造成 http 重定向到 https 无法正常工作,请问如何解决?

# 问题补充:
# 用下面的命令进入 nginx-ingress 容器
kubectl exec -it daemonset/nginx-ingress -n nginx-ingress cat /etc/nginx/conf.d/production-cnblogs-ingress.conf

# 发现问题是下面的配置引起的
proxy_set_header X-Forwarded-Proto $scheme;

问题解决: 终于在 Nginx Ingress Controller 的官方帮助文档 Advanced Configuration with Annotations 中找到一个注解(annotation)解决了这个问题,它就是 nginx.org/redirect-to-https: "true"

Annotation: nginx.org/redirect-to-https
ConfigMap Key redirect-to-https
Description Sets the 301 redirect rule based on the value of the http_x_forwarded_proto header on the server block to force incoming traffic to be over HTTPS. Useful when terminating SSL in a load balancer in front of the Ingress controller
# 根据服务器块上http_x_forwarded_proto报头的值设置301重定向规则,以强制传入的流量使用HTTPS。在入口控制器前的负载均衡器中终止SSL时非常有用;

操作步骤: 1)在 cnblogs-ingress.yaml 中 annotations 下面添加 nginx.org/redirect-to-https: “true”

apiVersion: extensions/v1beta1 
kind: Ingress
metadata:
  name: cnblogs-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.org/redirect-to-https: "true"
spec:
  rules:
  - host: q.cnblogs.com
    http:
      paths:
        - backend:
            serviceName: q-web
            servicePort: 80

2) 更新 ingress 配置

kubectl apply -f cnblogs-ingress.yaml

3) 更新 nginx-ingress

kubectl rollout restart daemonset/nginx-ingress -n nginx-ingress && \
kubectl rollout status daemonset/nginx-ingress -n nginx-ingress

4)查看 inginx 容器中的配置

kubectl exec -it daemonset/nginx-ingress -n nginx-ingress cat /etc/nginx/conf.d/production-cnblogs-ingress.conf

# 发现 proxy_set_header X-Forwarded-Proto $scheme; 变成了 proxy_set_header X-Forwarded-Proto https; ,并且增加了下面的 http 重定向 https 的配置。

if ($http_x_forwarded_proto = 'http') {
    return 301 https://$host$request_uri;
}

nginx-ingress 自己完成了基于X-Forwarded-Proto 的 http 重定向到 https 的操作,应用都不需要自己处理了。

Tips :建议采用 kubernetes/ingress-nginx 而非 nginxinc/kubernetes-ingress : https://github.com/kubernetes/ingress-nginx

问题2:extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress

解决办法:根据当前 Kubernetes 版本中资源组与版本进行选择即可;

~/K8s/Day7/demo1$ kubectl api-resources | grep "ingresses"
ingresses                         ing          extensions                     true         Ingress
ingresses                         ing          networking.k8s.io              true         Ingress

# Warning: extensions/v1beta1 Ingress is deprecated in v1.14+, unavailable in v1.22+; use networking.k8s.io/v1 Ingress
apiVersion: networking.k8s.io/v1 # 注意点否则将报上面的预警

问题3.Error from server (InternalError): error when applying patch:

错误信息:

$ kubectl apply -f demo-ingress.yaml
# 错误1
{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"networking.k8s.io/v1\",\"kind\":\"Ingress\",\"metadata\":{\"annotations\":{\"kubernetes.io/ingress.class\":\"nginx\"},\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"rules\":[{\"host\":\"nginx.ieasou.cn\",\"http\":{\"paths\":[{\"backend\":{\"service\":{\"name\":\"nginx\",\"port\":{\"number\":80}}},\"path\":\"/\",\"pathType\":\"Prefix\"}]}}]}}\n","kubernetes.io/ingress.class":"nginx"}},"spec":{"rules":[{"host":"nginx.ieasou.cn","http":{"paths":[{"backend":{"service":{"name":"nginx","port":{"number":80}}},"path":"/","pathType":"Prefix"}]}}]}}
to:
Resource: "networking.k8s.io/v1, Resource=ingresses", GroupVersionKind: "networking.k8s.io/v1, Kind=Ingress"
Name: "nginx", Namespace: "default"
for: "demo-ingress.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": Post "https://ingress-nginx-controller-admission.ingress-nginx.svc:443/extensions/v1beta1/ingresses?timeout=30s": x509: certificate is valid for k8s-master002, kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local, not 
ingress-nginx-controller-admission.ingress-nginx.svc

# 错误2
kubectl create -f ingress-nginx-http-v1.yaml
  # Error from server (InternalError): error when creating "ingress-nginx-http-v1.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io": an error on the server ("") has prevented the request from succeeding

解决办法: Webhook 删除后重新构建 ingress-nginx-http-v1.yaml 资源清单即可

weiyigeek@ubuntu:~/K8s/Day7/demo2$ kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
  # validatingwebhookconfiguration.admissionregistration.k8s.io "ingress-nginx-admission" deleted

weiyigeek@ubuntu:~/K8s/Day7/demo2$ kubectl create -f ingress-nginx-http-v1.yaml  
  # ingress.networking.k8s.io/nginx-ingress-http created  # 未其他报错正常创建

问题4.查看ingress规则时提示kubernetes之ingress error: endpoints "default-http-backend" not found警告

  • 问题信息:
$ kubectl describe ing
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
  • 问题原因: 注意:根据您正在使用的Ingress控制器,您可能需要创建一个default-http-backend服务。没有规则的入口将所有流量发送到一个默认后端。默认后端通常是Ingress控制器的一个配置选项,在您的Ingress资源中没有指定。如果Ingress对象中的主机或路径都不匹配HTTP请求,则流量将被路由到默认后端。
  • 解决办法:
# Ingress 控制器
spec:
  defaultbackend:
    service:
      name: nginx-ingress-v1-svc
      port:
        number: 80

问题5.设置了ingress无法正常显示页面,由于没有在资源清单中指定设置ingressclass名称

问题描述: 由于没有给ingress规则设置默认的ClassName,此时带有虚拟主机头的响应为默认default-http后端。

# 1.查看部署的ingress-nginx控制器名称k8s.io/ingress-nginx。
~$ kubectl get deployments.apps -n ingress-nginx ingress-nginx-controller -o yaml | grep " --controller-class"
        - --controller-class=k8s.io/ingress-nginx

# 2.查看ingressclass的Name信息
~$ kubectl get ingressclasses.networking.k8s.io
  # NAME    CONTROLLER             PARAMETERS   AGE
  # nginx   k8s.io/ingress-nginx   <none>       12d

# 3.编辑ingressclass
~$ kubectl get ingressclasses.networking.k8s.io
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/version: 1.1.1
    helm.sh/chart: ingress-nginx-4.0.15
spec:
  controller: k8s.io/ingress-nginx
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: external-lb
    namespace: external-configuration
    scope: Namespace

# 4.为ingress规则指定默认的ingresclass的名称。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: blog
  namespace: devtest
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    # kubernetes.io/ingress.class: nginx         #  当使用多个 Ingress 控制器时进行配置,如果不定义 ingress.class,云提供商可能使用默认的 Ingress 控制器。
    ingressclass.kubernetes.io/is-default-class: true
spec:
  ingressClassName: nginx                        #  推荐
  rules:
  - host: "www.weiyigeek.top"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: myweb-blog
            port:
              number: 80

温馨提示: ingressclass API 资源对象中, 在 sepc.parameters 字段中有一个 scope 和 namespace 字段,可用来引用特定于名字空间的资源,对 Ingress 类进行配置。(FEATURE STATE: Kubernetes v1.22 [beta])

  • scope 字段默认为 Cluster,表示默认是集群作用域的资源。
  • scope 设置为 Namespace 并设置 namespace 字段就可以引用某特定名字空间中的参数资源。

温馨提示: 当前 ingress 1.1.1 版本中使用 ingressClassName 字段来替代kubernetes.io/ingress.class 注解, 两则区别在于该注解通常用于引用实现该 Ingress 的控制器的名称, 而这个新的字段则是对一个包含额外 Ingress 配置的 IngressClass 资源的引用, 包括 Ingress 控制器的名称。

温馨提示: 我们也可以将一个 IngressClass 资源的 ingressclass.kubernetes.io/is-default-class 注解设置为 true ,以确保在未指定 ingressClassName 字段的情况下,Ingress 也能够分配为这个默认的 IngressClass.