10k8s集群暴露 Nginx Ingress Controller

0 阅读24分钟

k8s集群暴露 Nginx Ingress Controller

ingress控制器

ingress控制器作用

ingress controller可以为kubernetes集群外用户访问Kubernetes集群内部pod提供代理服务。ingress controller作为所有Service的入口,提供统一的入口,避免多个服务之间需要记录大量的IP或者域名,毕竟IP可能改变,服务太多域名记录不方便

  • 提供全局访问代理
  • 访问流程:用户-->ingress controller-->service-->pod

image.png

image.png

ingress控制器种类

Kubernetes Ingress Controller
  • 参考链接:github.com/nginxinc/ku…
  • 实现:Go/Lua(nginx 是用 C 写的)
  • 许可证:Apache 2.0
  • Kubernetes 的“官方”控制器(之所以称为官方,是想把它区别于 NGINX 公司的控制器)。这是社区开发的控制器,它基于 nginx Web 服务器,并补充了一组用于实现额外功能的 Lua 插件。
  • 由于 NGINX 十分流行,再加上把它用作控制器时所需的修改较少,它对于 K8s 普通工程师来说,可能是最简单和最直接的选择。
NGINX Ingress Controller
  • 参考链接:github.com/kubernetes/…
  • 实现:Go
  • 许可证:Apache 2.0
  • 这是 NGINX 公司开发的官方产品,它也有一个基于 NGINX Plus 的商业版。NGINX 的控制器具有很高的稳定性、持续的向后兼容性,且没有任何第三方模块。
  • 由于消除了 Lua 代码,和官方控制器相比,它保证了较高的速度,但也因此受到较大限制。相较之下,它的付费版本有更广泛的附加功能,如实时指标、JWT 验证、主动健康检查等。
  • NGINX Ingress 重要的优势是对 TCP/UDP 流量的全面支持,最主要缺点是缺乏流量分配功能。
Kong Ingress
  • 参考链接:github.com/Kong/kubern…
  • 实现:Go
  • 许可证:Apache 2.0
  • Kong Ingress 由 Kong Inc 开发,有两个版本:商业版和免费版。它基于 NGINX 构建,并增加了扩展其功能的 Lua 模块。
  • 最初,Kong Ingress 主要用作 API 网关,用于 API 请求的处理和路由。现在,它已经成为成熟的 Ingress 控制器,主要优点是拥有大量易于安装和配置的附加模块、插件(包括第三方插件)。它开启了控制器具备大量附加功能的先河,其内置函数也提供了许多可能性。Kong Ingress 配置是用 CRD 执行的。
  • Kong Ingress 的一个重要特性是它只能在一个环境中运行(而不支持跨命名空间)。这是一个颇有争议的话题:有些人认为这是一个缺点,因为必须为每个环境生成实例;而另一些人认为这是一个特殊特性,因为它是更高级别的隔离,控制器故障的影响仅限于其所在的环境。
Traefik
  • 参考链接:github.com/containous/…
  • 实现:Go
  • 许可证:MIT
  • 最初,这个代理是为微服务请求及其动态环境的路由而创建的,因此具有许多有用的功能:连续更新配置(不重新启动)、支持多种负载均衡算法、Web UI、指标导出、对各种服务的支持协议、REST API、Canary 版本等
  • 支持开箱即用的 Let’s Encrypt 是它的另一个不错的功能,但它的主要缺点也很明显,就是为了控制器的高可用性,你必须安装并连接其 Key-value store。
  • 在 2019 年 9 月发布的 Traefik v2.0 中,虽然它增加许多不错的新功能,如带有 SNI 的 TCP/SSL、金丝雀部署、流量镜像/shadowing 和经过改进的 Web UI,但一些功能(如 WAF 支持)还在策划讨论中。
  • 与新版本同期推出的还有一个名叫 Mesh 的服务网格,它建在 Traefik 之上,对kubernetes内部服务访问做到受控及被监控。
HAProxy Ingress
  • 参考链接:github.com/jcmoraisjr/…
  • 实现:Go(HAProxy 是用 C 写的)
  • 许可证:Apache 2.0
  • HAProxy 是众所周知的代理服务器和负载均衡器。作为 Kubernetes 集群的一部分,它提供了“软”配置更新(无流量损失)、基于 DNS 的服务发现和通过 API 进行动态配置。 HAProxy 还支持完全自定义配置文件模板(通过替换 ConfigMap)以及在其中使用 Spring Boot 函数。
  • 通常,工程师会把重点放在已消耗资源的高速、优化和效率上。而 HAProxy 的优点之一正是支持大量负载均衡算法。值得一提的是,在2020年 6 月发布的 v2.0 中,HAProxy 增加了许多新功能,其即将推出的 v2.1 有望带来更多新功能(包括 OpenTracing 支持)。
Voyager
  • 参考链接:github.com/appscode/vo…
  • 实现:Go
  • 许可证:Apache 2.0
  • Voyager 基于 HAProxy,并作为一个通用的解决方案提供给大量供应商。它最具代表性的功能包括 L7 和 L4 上的流量负载均衡,其中,TCP L4 流量负载均衡称得上是该解决方案最关键的功能之一。
  • 在2020年早些时候,尽管 Voyager 在 v9.0.0 中推出了对 HTTP/2 和 gRPC 协议的全面支持,但总的来看,对证书管理(Let’s Encrypt 证书)的支持仍是 Voyager 集成的最突出的新功能。
Contour
  • 参考链接:github.com/heptio/cont…
  • 实现:Go
  • 许可证:Apache 2.0
  • Contour 和 Envoy 由同一个作者开发,它基于 Envoy。它最特别的功能是可以通过 CRD(IngressRoute)管理 Ingress 资源,对于多团队需要同时使用一个集群的组织来说,这有助于保护相邻环境中的流量,使它们免受 Ingress 资源更改的影响。
  • 它还提供了一组扩展的负载均衡算法(镜像、自动重复、限制请求率等),以及详细的流量和故障监控。对某些工程师而言,它不支持粘滞会话可能是一个严重缺陷。
Istio Ingress
  • 参考链接:istio.io/docs/tasks/…
  • 实现:Go
  • 许可证:Apache 2.0
  • Istio 是 IBM、Google 和 Lyft 的联合开发项目,它是一个全面的服务网格解决方案——不仅可以管理所有传入的外部流量(作为 Ingress 控制器),还可以控制集群内部的所有流量。
  • Istio 将 Envoy 用作每种服务的辅助代理。从本质上讲,它是一个可以执行几乎所有操作的大型处理器,其中心思想是最大程度的控制、可扩展性、安全性和透明性。
  • 通过 Istio Ingress,你可以对流量路由、服务之间的访问授权、均衡、监控、金丝雀发布等进行优化。
Ambassador
  • 参考链接:github.com/datawire/am…
  • 实现:Python
  • 许可证:Apache 2.0
  • Ambassador 也是一个基于 Envoy 的解决方案,它有免费版和商业版两个版本。
  • Ambassador 被称为“Kubernetes 原生 API 微服务网关”,它与 K8s 原语紧密集成,拥有你所期望的从 Ingress controller 获得的功能包,它还可以与各种服务网格解决方案,如 Linkerd、Istio 等一起使用。
  • 顺便提一下,Ambassador 博客日前发布了一份基准测试结果,比较了 Envoy、HAProxy 和 NGINX 的基础性能。
Gloo
  • 参考链接:github.com/solo-io/glo…
  • 实现:Go
  • 许可证:Apache 2.0
  • Gloo 是在 Envoy 之上构建的新软件(于 2018 年 3 月发布),由于它的作者坚持认为“网关应该从功能而不是服务中构建 API”,它也被称为“功能网关”。其“功能级路由”的意思是它可以为后端实现是微服务、无服务器功能和遗留应用的混合应用路由流量。
  • 由于拥有可插拔的体系结构,Gloo 提供了工程师期望的大部分功能,但是其中一些功能仅在其商业版本(Gloo Enterprise)中可用。
Skipper
  • 参考链接:github.com/zalando/ski…
  • 实现:Go
  • 许可证:Apache 2.0
  • Skipper 是 HTTP 路由器和反向代理,因此不支持各种协议。从技术上讲,它使用 Endpoints API(而不是 Kubernetes Services)将流量路由到 Pod。它的优点在于其丰富的过滤器集所提供的高级 HTTP 路由功能,工程师可以借此创建、更新和删除所有 HTTP 数据。
  • Skipper 的路由规则可以在不停机的情况下更新。正如它的作者所述,Skipper 可以很好地与其他解决方案一起使用,比如 AWS ELB。

nginx ingress controller

image.png

参考链接:www.nginx.com/products/ng…

nginx ingress controller部署

项目地址:github.com/kubernetes/…

MetalLB准备,见上面的' '自建Kubernetes的LoadBalancer类型服务方案-MetalLB'

wget https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
kubectl apply -f metallb-native.yaml

metallb-conf.yaml

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.91.90-192.168.91.100 # 是集群节点服务器IP同一段
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
spec:
  ipAddressPools:
  - first-pool
kubectl apply -f metallb-conf.yaml

kubectl describe IPAddressPool first-pool -n metallb-system
...
Spec:
  Addresses:
    192.168.91.90-192.168.91.100
  Auto Assign:       true
  Avoid Buggy I Ps:  false
Events:              <none>

nginx ingress controller

# 下载并修改配置文件
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
sed -i 's/externalTrafficPolicy: Local/externalTrafficPolicy: Cluster/' deploy.yaml
# 替换配置文件中的镜像地址,注意不同时期下载的镜像版本不一样
sed -i -e 's#registry.k8s.io/ingress-nginx/controller:v1.8.2@sha256:74834d3d25b336b62cabeb8bf7f1d788706e2cf1cfd64022de4137ade8881ff2#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.8.2#' -e 's#registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b#registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20230407#' deploy.yaml

kubectl apply -f deploy.yaml

#  验证部署结果,注意镜像较大可能要等一等
kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-vwccj        0/1     Completed   0          4m33s
ingress-nginx-admission-patch-69hlb         0/1     Completed   0          4m33s
ingress-nginx-controller-767b8c48bf-5vfhk   1/1     Running     0          4m33s

kubectl get all -n ingress-nginx
NAME                                            READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-vwccj        0/1     Completed   0          4m48s
pod/ingress-nginx-admission-patch-69hlb         0/1     Completed   0          4m48s
pod/ingress-nginx-controller-767b8c48bf-5vfhk   1/1     Running     0          4m48s
NAME                                         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)                      AGE
service/ingress-nginx-controller             LoadBalancer   10.111.7.213   192.168.91.90   80:30112/TCP,443:31623/TCP   5m35s
service/ingress-nginx-controller-admission   ClusterIP      10.99.92.122   <none>          443/TCP                      5m35s
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           5m35s
NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-767b8c48bf   1         1         1       4m48s
NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           8s         5m36s
job.batch/ingress-nginx-admission-patch    1/1           17s        5m36s

ingress对象应用案例

ingress-http案例

基于名称的负载均衡

创建deployment控制器类型应用

nginx.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: ingress-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: c1
        image: nginx:1.15-alpine
        imagePullPolicy: IfNotPresent
kubectl apply -f nginx.yml

kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-vwccj        0/1     Completed   0          7m15s
ingress-nginx-admission-patch-69hlb         0/1     Completed   0          7m15s
ingress-nginx-controller-767b8c48bf-5vfhk   1/1     Running     0          7m15s
nginx-5cc579878-4v8xd                       1/1     Running     0          12s
nginx-5cc579878-hp7rv                       1/1     Running     0          12s

创建service

nginx-service.yml

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  namespace: ingress-nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx
kubectl apply -f nginx-service.yml

kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.111.7.213     192.168.91.90   80:30112/TCP,443:31623/TCP   8m45s
ingress-nginx-controller-admission   ClusterIP      10.99.92.122     <none>          443/TCP                      8m45s
nginx-service                        ClusterIP      10.107.249.245   <none>          80/TCP                       10s

创建ingress对象

ingress-nginx.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx                    #自定义ingress名称
  namespace: ingress-nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: www.kubedemo.com                   # 自定义域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-service     # 对应上面创建的service名称
            port:
              number: 80
kubectl apply -f ingress-nginx.yaml

# nginx-ingress-controller 对外暴露在192.168.91.90这个ip上
kubectl get ingress -n ingress-nginx
NAME            CLASS    HOSTS              ADDRESS         PORTS   AGE
ingress-nginx   <none>   www.kubedemo.com   192.168.91.90   80      116s

# 查看ingress描述信息
kubectl describe ingress ingress-nginx -n ingress-nginx
Name:             ingress-nginx
Labels:           <none>
Namespace:        ingress-nginx
Address:          192.168.91.90
Ingress Class:    <none>
Default backend:  <default>
Rules:
  Host              Path  Backends
  ----              ----  --------
  www.kubedemo.com
                    /   nginx-service:80 (10.244.30.118:80,10.244.5.1:80)
...

# 可以看到两个pod的IP正好对应ingress域名对应的IP
kubectl get pods -o wide -n ingress-nginx
...
nginx-5cc579878-4v8xd                       1/1     Running     0          6m3s   10.244.30.118   worker02   <none>           <none>
nginx-5cc579878-hp7rv                       1/1     Running     0          6m3s   10.244.5.1      worker01   <none>           <none>

# 修改pod内容器运行的web主页
kubectl exec -it nginx-5cc579878-4v8xd -n ingress-nginx -- /bin/sh -c 'echo "ingress web1" > /usr/share/nginx/html/index.html'
kubectl exec -it nginx-5cc579878-hp7rv -n ingress-nginx -- /bin/sh -c 'echo "ingress web2" > /usr/share/nginx/html/index.html'

模拟客户端访问

# 在集群之外任一主机中添加上述域名与IP地址解析(模拟公网DNS)
# 这里在 192.168.91.101 上测试
cat >> /etc/hosts << EOF
192.168.91.90 www.kubedemo.com
EOF

curl www.kubedemo.com
ingress web1

curl www.kubedemo.com
ingress web2
# 删除
kubectl delete -f ingress-nginx.yaml
kubectl delete -f nginx-service.yml
kubectl delete -f nginx.yml
rm -f ingress-nginx.yaml nginx-service.yml nginx.yml
ingress-http案例扩展

基于URI的负载均衡

创建第一个应用

nginx-uri-1.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-uri-1
  namespace: ingress-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-uri-1
  template:
    metadata:
      labels:
        app: nginx-uri-1
    spec:
      containers:
      - name: c1
        image: nginx:1.15-alpine
        imagePullPolicy: IfNotPresent

nginx-service-uri-1.yml

apiVersion: v1
kind: Service
metadata:
  name: nginx-service-uri-1
  namespace: ingress-nginx
  labels:
    app: nginx-uri-1
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx-uri-1
kubectl apply -f nginx-uri-1.yml
kubectl apply -f nginx-service-uri-1.yml

创建第二个应用

nginx-uri-2.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-uri-2
  namespace: ingress-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-uri-2
  template:
    metadata:
      labels:
        app: nginx-uri-2
    spec:
      containers:
      - name: c1
        image: nginx:1.15-alpine
        imagePullPolicy: IfNotPresent

nginx-service-uri-2.yml

apiVersion: v1
kind: Service
metadata:
  name: nginx-service-uri-2
  namespace: ingress-nginx
  labels:
    app: nginx-uri-2
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx-uri-2
kubectl apply -f nginx-uri-2.yml
kubectl apply -f nginx-service-uri-2.yml

kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.111.7.213     192.168.91.90   80:30112/TCP,443:31623/TCP   59m
ingress-nginx-controller-admission   ClusterIP      10.99.92.122     <none>          443/TCP                      59m
nginx-service-uri-1                  ClusterIP      10.102.152.126   <none>          80/TCP                       80s
nginx-service-uri-2                  ClusterIP      10.101.213.109   <none>          80/TCP                       25s

创建ingress对象

ingress-nginx-uri.yml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-uri
  namespace: ingress-nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: www.kubedemouri.com
    http:
      paths:
      - path: /svc1
        pathType: Prefix
        backend:
          service:
            name: nginx-service-uri-1
            port:
              number: 80
      - path: /svc2
        pathType: Prefix
        backend:
          service:
            name: nginx-service-uri-2
            port:
              number: 80
kubectl apply -f ingress-nginx-uri.yml
# 验证ingress,nginx-ingress-controller 对外暴露在192.168.91.90这个ip上
kubectl get ingress -n ingress-nginx
NAME          CLASS    HOSTS                 ADDRESS         PORTS   AGE
ingress-uri   <none>   www.kubedemouri.com   192.168.91.90   80      46s

# 描述查看ingress信息
kubectl describe ingress ingress-uri -n ingress-nginx
Name:             ingress-uri
Labels:           <none>
Namespace:        ingress-nginx
Address:          192.168.91.90
Ingress Class:    <none>
Default backend:  <default>
Rules:
  Host                 Path  Backends
  ----                 ----  --------
  www.kubedemouri.com
                       /svc1   nginx-service-uri-1:80 (10.244.30.119:80,10.244.5.3:80)
                       /svc2   nginx-service-uri-2:80 (10.244.30.120:80,10.244.5.4:80)
...

kubectl get pods -o wide -n ingress-nginx
...
nginx-uri-1-6894c8b9c-cbs5t                 1/1     Running     0          5m18s   10.244.5.3      worker01   <none>           <none>
nginx-uri-1-6894c8b9c-dvgrg                 1/1     Running     0          5m18s   10.244.30.119   worker02   <none>           <none>
nginx-uri-2-7fb59f7d66-cl6hr                1/1     Running     0          4m22s   10.244.30.120   worker02   <none>           <none>
nginx-uri-2-7fb59f7d66-fnxhc                1/1     Running     0          4m22s   10.244.5.4      worker01   <none>           <none>

# 修改pod内容器运行的web主页
kubectl exec -it nginx-uri-1-6894c8b9c-cbs5t -n ingress-nginx -- /bin/sh -c 'mkdir /usr/share/nginx/html/svc1 && echo sssvc1 > /usr/share/nginx/html/svc1/index.html'
kubectl exec -it nginx-uri-1-6894c8b9c-dvgrg -n ingress-nginx -- /bin/sh -c 'mkdir /usr/share/nginx/html/svc1 && echo sssvc1 > /usr/share/nginx/html/svc1/index.html'
kubectl exec -it nginx-uri-2-7fb59f7d66-cl6hr -n ingress-nginx -- /bin/sh -c 'mkdir /usr/share/nginx/html/svc2 && echo sssvc2 > /usr/share/nginx/html/svc2/index.html'
kubectl exec -it nginx-uri-2-7fb59f7d66-fnxhc -n ingress-nginx -- /bin/sh -c 'mkdir /usr/share/nginx/html/svc2 && echo sssvc2 > /usr/share/nginx/html/svc2/index.html'

模拟客户端访问

# 在集群之外任一主机中添加上述域名与IP地址解析(模拟公网DNS)
# 这里在 192.168.91.101 上测试
cat >> /etc/hosts << EOF
192.168.91.90 www.kubedemouri.com
EOF

curl www.kubedemouri.com/svc1/index.html
sssvc1

curl www.kubedemouri.com/svc2/index.html
sssvc2
# 删除
kubectl delete -f ingress-nginx-uri.yml
kubectl delete -f nginx-service-uri-2.yml
kubectl delete -f nginx-uri-2.yml
kubectl delete -f nginx-service-uri-1.yml
kubectl delete -f nginx-uri-1.yml

rm -f nginx-uri-1.yml nginx-service-uri-1.yml nginx-uri-2.yml nginx-service-uri-2.yml ingress-nginx-uri.yml
ingress-https案例
# 创建自签证书
mkdir ingress-https
cd ingress-https
openssl genrsa -out nginx.key 2048
# 需要输入正确的域名
openssl req -new -x509 -key nginx.key -out nginx.pem -days 365
...
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:GD
Locality Name (eg, city) [Default City]:BJ
Organization Name (eg, company) [Default Company Ltd]:IT
Organizational Unit Name (eg, section) []:it
Common Name (eg, your name or your server's hostname) []:kubedemohost
Email Address []:admin@kubedemohost.com

# 将证书创建成secret
kubectl create secret tls nginx-tls-secret --cert=nginx.pem --key=nginx.key -n ingress-nginx

kubectl get secrets -n ingress-nginx | grep nginx-tls-secret
nginx-tls-secret          kubernetes.io/tls   2      16s

ingress-https.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx2
  namespace: ingress-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx2
  template:
    metadata:
      labels:
        app: nginx2
    spec:
      containers:
      - name: c1
        image: nginx:1.15-alpine
        imagePullPolicy: IfNotPresent
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service2
  namespace: ingress-nginx
  labels:
    app: nginx2
spec:
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 443
  selector:
    app: nginx2
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx2
  namespace: ingress-nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
    kubernetes.io/ingress.class: nginx
spec:
  tls:
  - hosts:
    - www.kubedemohost.com                                                     # 域名
    secretName: nginx-tls-secret                                  # 调用前面创建的secret
  rules:
  - host: www.kubedemohost.com                                                 # 域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-service2                     # 对应服务名
            port:
              number: 80
kubectl apply -f ingress-https.yml

kubectl get ingress -n ingress-nginx
NAME             CLASS    HOSTS                  ADDRESS         PORTS     AGE
ingress-nginx2   <none>   www.kubedemohost.com   192.168.91.90   80, 443   81s

模拟客户端访问

配置hosts,192.168.91.90 www.kubedemohost.com,然后在浏览器中访问

如果需要在互联网中访问kubernetes集群中的服务是可信的,建议使用互联网中申请的SSL证书

image.png

# 删除
kubectl delete -f ingress-https.yml
kubectl delete secret nginx-tls-secret -n ingress-nginx
cd ..
rm -rf ingress-https
ingress+nodeport服务

ingress-nodeport.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx3
  namespace: ingress-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx3
  template:
    metadata:
      labels:
        app: nginx3
    spec:
      containers:
      - name: c1
        image: nginx:1.15-alpine
        imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service3
  namespace: ingress-nginx
  labels:
    app: nginx3
spec:
  type: NodePort						# NodePort类型服务
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx3
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-nginx3
  namespace: ingress-nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: www.kubedemo3.com                                                 # 域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-service3                    # 对应服务名
            port:
              number: 80
kubectl apply -f ingress-nodeport.yml

kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.111.7.213    192.168.91.90   80:30112/TCP,443:31623/TCP   2d16h
ingress-nginx-controller-admission   ClusterIP      10.99.92.122    <none>          443/TCP                      2d16h
nginx-service3                       NodePort       10.109.164.41   <none>          80:32201/TCP                 52s

模拟客户端访问

# 在集群之外任一主机中添加上述域名与IP地址解析(模拟公网DNS)
# 这里在 192.168.91.101 上测试
cat >> /etc/hosts << EOF
192.168.91.90 www.kubedemo3.com
EOF

curl www.kubedemo3.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
# 删除
kubectl delete -f ingress-nodeport.yml
rm -f ingress-nodeport.yml
# 删除 nginx ingress controller
kubectl delete -f deploy.yaml
kubectl delete -f metallb-conf.yaml
kubectl delete -f metallb-native.yaml

rm -f deploy.yaml metallb-conf.yaml metallb-native.yaml

基于Ingress Nginx实现灰度发布系统

工作中,我们会经常对应用进行升级发版,在互联网公司尤为频繁,主要是为了满足业务的快速发展。我们经常用到的发布方式有滚动更新、蓝绿发布、灰度发布。

  • 滚动更新:依次进行新旧替换,直到旧的全部被替换为止。
  • 蓝绿发布:两套独立的系统,对外提供服务的称为绿系统,待上线的服务称为蓝系统,当蓝系统里面的应用测试完成后,用户流量接入蓝系统,蓝系统将称为绿系统,以前的绿系统就可以销毁。
  • 灰度发布:在一套集群中存在稳定和灰度两个版本,灰度版本可以限制只针对部分人员可用,待灰度版本测试完成后,可以将灰度版本升级为稳定版本,旧的稳定版本就可以下线了,我们也称之为金丝雀发布。

通过ingress-nginx实现灰度发布原理

ingress-nginx是Kubernetes官方推荐的ingress controller,它是基于nginx实现的,增加了一组用于实现额外功能的Lua插件。 为了实现灰度发布,ingress-nginx通过定义annotation来实现不同场景的灰度发布,其支持的规则如下:

  • 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

通过ingress-nginx实现灰度发布场景

基于服务权重的流量切分

假如在生产上已经运行了A应用对外提供服务,此时开发修复了一些Bug,需要发布A1版本将其上线,但是我们又不希望直接将所有流量接入到新的A1版本,而是希望将10%的流量进入到A1中,待A1稳定后,才会将所有流量接入进来,再下线原来的A版本。

image.png

实现方法:在canary ingress中添加如下annotation

# 表示开启canary
nginx.ingress.kubernetes.io/canary: "true"
#  表示设置的权重百分比,10为10%的流量
nginx.ingress.kubernetes.io/canary-weight: "10"
基于用户请求头Header的流量切分

由于基于权重的发布场景比较粗糙,它是所有用户中的10%流量,无法限制具体的用户访问行为。我们有时候会有这样的需求,比如我们有北京、上海、深圳这三个地区的用户,并且已经有A版本的应用为这三个地区提供服务,由于更新了需求,我们需要发布A1应用,但是我们不想所有地区都访问A1应用,而是希望只有深圳的用户可以访问,待深圳地区反馈没问题后,才开放其他地区。

image.png

实现方法:在canary ingress中添加如下annotation

# 开启canary
nginx.ingress.kubernetes.io/canary: "true"
# 指定header关键字
nginx.ingress.kubernetes.io/canary-by-header: "Region"
# 指定header关键字对应的value
nginx.ingress.kubernetes.io/canary-by-header-value: "shenzhen"

通过ingress-nginx实现灰度发布实现思路

  1. 在K8S集群中运行2个应用版本,一个是stable版本,一个是canary版本
  2. 定义stable版本ingress,提供运行的应用正常访问;定义canary版本,在metadata中添加annotation实现类度发布,例如根据流量百分比或用户请求header
  3. 经过一定时间的运行,canary版本可正常提供服务后,将其切换为stable版本,并将原stable版本下线即可

通过ingress-nginx实现灰度发布系统

nginx ingress controller部署

修改为ipvs调度方式

参考前面的修改为ipvs调度方式(拓展)

# 修改kube-proxy的配置文件
kubectl edit configmap kube-proxy -n kube-system
...
    kind: KubeProxyConfiguration
    metricsBindAddress: ""
    mode: "ipvs" # 默认""号里为空,加上ipvs
...

# 重启kube-proxy
kubectl rollout restart daemonset kube-proxy -n kube-system

nginx ingress controller部署

参考前面的nginx ingress controller部署

# MetalLB准备
wget https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
kubectl apply -f metallb-native.yaml

cat > metallb-conf.yaml << "EOF"
---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.91.90-192.168.91.100
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
spec:
  ipAddressPools:
  - first-pool
EOF

kubectl apply -f metallb-conf.yaml

kubectl describe IPAddressPool first-pool -n metallb-system
...
Spec:
  Addresses:
    192.168.91.90-192.168.91.100
  Auto Assign:       true
  Avoid Buggy I Ps:  false
Events:              <none>

# nginx ingress controller 部署
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
sed -i 's/externalTrafficPolicy: Local/externalTrafficPolicy: Cluster/' deploy.yaml
sed -i -e 's#registry.k8s.io/ingress-nginx/controller:v1.8.2@sha256:74834d3d25b336b62cabeb8bf7f1d788706e2cf1cfd64022de4137ade8881ff2#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:v1.8.2#' -e 's#registry.k8s.io/ingress-nginx/kube-webhook-certgen:v20230407@sha256:543c40fd093964bc9ab509d3e791f9989963021f1e9e4c9c7b6700b02bfb227b#registry.cn-hangzhou.aliyuncs.com/google_containers/kube-webhook-certgen:v20230407#' deploy.yaml

kubectl apply -f deploy.yaml

kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS      RESTARTS   AGE
ingress-nginx-admission-create-fpf8r        0/1     Completed   0          39s
ingress-nginx-admission-patch-snpqr         0/1     Completed   0          39s
ingress-nginx-controller-767b8c48bf-b9lnk   1/1     Running     0          46s

kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.108.47.131   192.168.91.90   80:31840/TCP,443:31976/TCP   83s
ingress-nginx-controller-admission   ClusterIP      10.108.147.52   <none>          443/TCP                      83s

有必要修改configmap,操作如下:

kubectl edit configmap ingress-nginx-controller -n ingress-nginx
...
apiVersion: v1
data:
  allow-snippet-annotations: "true"
  enable-canary: "true"
...

kubectl rollout restart deployment ingress-nginx-controller -n ingress-nginx
应用服务部署

v1版本部署

01-deploy-nginx-v1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
      version: v1
  template:
    metadata:
      labels:
        app: nginx
        version: v1
    spec:
      containers:
      - name: nginx
        image: "openresty/openresty:centos"
        ports:
        - name: http
          protocol: TCP
          containerPort: 80
        volumeMounts:
        - mountPath: /usr/local/openresty/nginx/conf/nginx.conf
          name: config
          subPath: nginx.conf
      volumes:
      - name: config
        configMap:
          name: nginx-v1

---

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app: nginx
    version: v1
  name: nginx-v1
data:
  nginx.conf: |-
    worker_processes  1;

    events {
        accept_mutex on;
        multi_accept on;
        use epoll;
        worker_connections  1024;
    }

    http {
        ignore_invalid_headers off;
        server {
            listen 80;
            location / {
                access_by_lua '
                    local header_str = ngx.say("nginx-v1")
                ';
            }
        }
    }

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-v1
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: nginx
    version: v1
kubectl apply -f 01-deploy-nginx-v1.yaml

kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
nginx-v1-86dcb755fd-7bvxj   1/1     Running   0          98s

kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   11d
nginx-v1     ClusterIP   10.102.113.186   <none>        80/TCP    2m3s

curl 10.102.113.186
nginx-v1

v2版本部署

02-deploy-nginx-v2.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
      version: v2
  template:
    metadata:
      labels:
        app: nginx
        version: v2
    spec:
      containers:
      - name: nginx
        image: "openresty/openresty:centos"
        ports:
        - name: http
          protocol: TCP
          containerPort: 80
        volumeMounts:
        - mountPath: /usr/local/openresty/nginx/conf/nginx.conf
          name: config
          subPath: nginx.conf
      volumes:
      - name: config
        configMap:
          name: nginx-v2
---

apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app: nginx
    version: v2
  name: nginx-v2
data:
  nginx.conf: |-
    worker_processes  1;

    events {
        accept_mutex on;
        multi_accept on;
        use epoll;
        worker_connections  1024;
    }

    http {
        ignore_invalid_headers off;
        server {
            listen 80;
            location / {
                access_by_lua '
                    local header_str = ngx.say("nginx-v2")
                ';
            }
        }
    }

---

apiVersion: v1
kind: Service
metadata:
  name: nginx-v2
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    name: http
  selector:
    app: nginx
    version: v2
kubectl apply -f 02-deploy-nginx-v2.yaml

kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1        <none>        443/TCP   11d
nginx-v1     ClusterIP   10.102.113.186   <none>        80/TCP    7m9s
nginx-v2     ClusterIP   10.105.133.131   <none>        80/TCP    3m34s

curl 10.105.133.131
nginx-v2
创建stable版本ingress资源对象

03-stable-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-v1                    #自定义ingress名称
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - host: www.kubedemo.com                   # 自定义域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-v1    # 对应上面创建的service名称
            port:
              number: 80
kubectl apply -f 03-stable-ingress.yaml

kubectl get ingress
NAME       CLASS   HOSTS              ADDRESS         PORTS   AGE
nginx-v1   nginx   www.kubedemo.com   192.168.91.90   80      61s

# 验证
cat >> /etc/hosts << EOF
192.168.91.90 www.kubedemo.com
EOF

curl http://www.kubedemo.com
nginx-v1
流量切分
基于服务权重的流量切分

04-canary-ingress-weight.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-v2                    #自定义ingress名称
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
  ingressClassName: nginx
  rules:
  - host: www.kubedemo.com                   # 自定义域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-v2    # 对应上面创建的service名称
            port:
              number: 80
kubectl apply -f 04-canary-ingress-weight.yaml

kubectl get ingress
NAME       CLASS   HOSTS              ADDRESS         PORTS   AGE
nginx-v1   nginx   www.kubedemo.com   192.168.91.90   80      6m53s
nginx-v2   nginx   www.kubedemo.com   192.168.91.90   80      89s

# 会有10%的流量到nginx-v2
for i in {1..10};do curl http://www.kubedemo.com;done
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v2
nginx-v1
nginx-v1
nginx-v1

# 删除
kubectl delete -f 04-canary-ingress-weight.yaml
rm -f 04-canary-ingress-weight.yaml
基于用户请求头Header的流量切分

05-canary-ingress-header.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-v2                    #自定义ingress名称
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "Region"
    nginx.ingress.kubernetes.io/canary-by-header-pattern: "shenzhen"
spec:
  ingressClassName: nginx
  rules:
  - host: www.kubedemo.com                   # 自定义域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-v2    # 对应上面创建的service名称
            port:
              number: 80
kubectl apply -f 05-canary-ingress-header.yaml

kubectl get ingress
NAME       CLASS   HOSTS              ADDRESS         PORTS   AGE
nginx-v1   nginx   www.kubedemo.com   192.168.91.90   80      16m
nginx-v2   nginx   www.kubedemo.com   192.168.91.90   80      66s

curl http://www.kubedemo.com
nginx-v1

curl -H "Region: beijing"  http://www.kubedemo.com
nginx-v1

curl -H "Region: shanghai"  http://www.kubedemo.com
nginx-v1

curl -H "Region: shenzhen"  http://www.kubedemo.com
nginx-v2

# 删除
kubectl delete -f 05-canary-ingress-header.yaml
rm -f 05-canary-ingress-header.yaml
基于Cookie的流量切分

使用 Cookie 则无法自定义 value,以模拟灰度shenzhen地域用户为例,仅将带有名为 user_from_shenzhen 的 Cookie 的请求转发给当前 Canary Ingress

06-canary-ingress-cookie.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-v2                    #自定义ingress名称
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-cookie: "user_from_shenzhen"
spec:
  ingressClassName: nginx
  rules:
  - host: www.kubedemo.com                   # 自定义域名
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx-v2    # 对应上面创建的service名称
            port:
              number: 80
kubectl apply -f 06-canary-ingress-cookie.yaml

kubectl get ingress
NAME       CLASS   HOSTS              ADDRESS         PORTS   AGE
nginx-v1   nginx   www.kubedemo.com   192.168.91.90   80      18m
nginx-v2   nginx   www.kubedemo.com   192.168.91.90   80      69s

# 当仅有 cookie `user_from_shenzhen` 为 `always` 的请求才由 v2 版本的服务响应
curl --cookie "user_from_beijing" http://www.kubedemo.com
nginx-v1

curl --cookie "user_from_shenzhen" http://www.kubedemo.com
nginx-v1

curl --cookie "user_from_shenzhen=always" http://www.kubedemo.com
nginx-v2

# 删除
kubectl apply -f 06-canary-ingress-cookie.yaml
rm -f 06-canary-ingress-cookie.yaml
# 删除资源并恢复集群到默认配置
kubectl delete -f 03-stable-ingress.yaml
kubectl delete -f 02-deploy-nginx-v2.yaml
kubectl delete -f 01-deploy-nginx-v1.yaml

rm -f 03-stable-ingress.yaml 02-deploy-nginx-v2.yaml 01-deploy-nginx-v1.yaml

kubectl edit configmap ingress-nginx-controller -n ingress-nginx
...
apiVersion: v1
data:
  allow-snippet-annotations: "true"
...

kubectl rollout restart deployment ingress-nginx-controller -n ingress-nginx

kubectl delete -f deploy.yaml
kubectl delete -f metallb-conf.yaml
kubectl delete -f metallb-native.yaml

rm -f deploy.yaml metallb-conf.yaml metallb-native.yaml

kubectl edit configmap kube-proxy -n kube-system
...
    kind: KubeProxyConfiguration
    metricsBindAddress: ""
    mode: ""
...

kubectl rollout restart daemonset kube-proxy -n kube-system