443 k8s配置开启nginx_Kubernetes Ingress — NGINX

5b1ae1ac70682c619aa1a523561b99dd.png

在 Kubernetes 中,Service 是一种抽象的概念,它定义了每一组 Pod 的逻辑集合和访问方式,并提供一个统一的入口,将请求进行负载分发到后端的各个 Pod 上。Service 默认类型是 ClusterIP,集群内部的应用服务可以相互访问,但集群外部的应用服务无法访问。为此 Kubernetes 提供了 NodePorts,LoadBalancer 和 Ingress 三种外部访问 Kubernetes 集群的方式。

Ingress 是 Kubernetes 中的一个 API 对象(在1.19版本GA),它提供路由规则来管理外部用户对 Kubernetes 集群中服务的访问。Ingress Controller 是 Ingress API 的实际实现,通过和 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,将外部流量路由到 Kubernetes 集群,同时提供负载平衡,并负责L4-L7网络服务。

ec162860f438e348cd9b56bd19a150da.png

目前社区上的 Ingress Controller 有十几种,如 Nginx Ingress、Kong、Traefik、Istio Ingress、APISIX 等,可根据自己的功能需求选型。

01

Nginx Ingress

Nginx Ingress 是由 Kubernetes SIGs 小组开发的。顾名思义,它基于 nginx,并补充了一组用于实现额外功能的 Lua 插件。由于 nginx 的普及以及在用作控制器时对其进行的最小改动,对于大部分人来说,它可能是最简单,最直接的选择。

2fd35bb71dc071c4f2cf127340208e1b.png

Nginx Ingress 安装非常简单,在裸机上部署的 kubernetes 集群,使用 NodePort

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.42.0/deploy/static/provider/baremetal/deploy.yaml

其他环境查看社区文档。

[root@k8s-test01 ~]# kubectl get po,svc -n ingress-nginx 
NAME                                            READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-xtjfl        0/1     Completed   0          97m
pod/ingress-nginx-admission-patch-wx2jt         0/1     Completed   0          97m
pod/ingress-nginx-controller-848bfcb64d-6spj4   1/1     Running     0          97m

NAME                                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                 AGE
service/ingress-nginx-controller             NodePort    10.254.14.4             80:80/TCP,443:443/TCP   97m
service/ingress-nginx-controller-admission   ClusterIP   10.254.94.128           443/TCP                 97m
[root@k8s-test01 ~]# 

创建一个 ingress 示例

[root@k8s-test01 ~]# cat ingress-nginx-demo.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: "foo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx
            port:
              number: 80
[root@k8s-test01 ~]# kubectl describe ingress ingress-wildcard-host 
Name:             ingress-wildcard-host
Namespace:        default
Address:          172.31.9.226
Default backend:  default-http-backend:80 ()
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com  
               /   nginx:80 (192.168.47.155:80)
Annotations:   kubernetes.io/ingress.class: nginx
Events:
  Type    Reason  Age                  From                      Message
  ----    ------  ----                 ----                      -------
  Normal  Sync    103s (x2 over 111s)  nginx-ingress-controller  Scheduled for sync
[root@k8s-test01 ~]# kubectl get po -o wide | grep nginx
nginx-546585459c-zxfmh   1/1     Running     0          94m   192.168.47.155   k8s-test02              
[root@k8s-test01 ~]# 

e8835ec9fa3fe370164ca027f9b7acfc.png

NGINX Ingress Controller 提供了有三种方式配置 NGINX :

ConfigMap:使用 ConfigMap 在 NGINX 中设置全局配置。

Annotations:在特定 Ingress 规则的特定配置。

Custom template:当需要更具体的设置(如打开文件缓存)时,可以使用自定义 nginx 模板。

02

配置 SSL

通过 Annotations 来配置某一个 ingress 使用 SSL。

创建secret

kubectl create secret tls ingress-cert --key=fullchain.com.key --cert=fullchain.cer 

创建Ingress

[root@k8s-test01 cert]# cat ingress-nginx-demo.yaml
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
  annotations:
    kubernetes.io/ingress.class: nginx
    ingress.kubernetes.io/force-ssl-redirect: "true"
    kubernetes.io/tls-acme: "true"
spec:
  rules:
  - host: "foo.xxx.com"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx
            port:
              number: 80
  tls:
   - secretName: ingress-cert
[root@k8s-test01 cert]# kubectl create -f  ingress-nginx-demo.yaml
ingress.networking.k8s.io/ingress-wildcard-host created

fa1220eaf34054589622645b19e7e7f6.png

03

配置ModSecurity防火墙与OWASP规则

ModSecurity是一个免费、开源的 Apache 模块,用于入侵探测与拦截,目前已经支持 Nginx,可以充当 Web 应用防火墙(WAF),旨在增强 Web 应用程序的安全性和避免遭受来自已知与未知的攻击。而 OWASP 是安全社区开发和维护的一套免费的应用程序保护规则,是 MoodSecurity 的核心规则集。Nginx-ingress 集成了 ModSecurity 模块和 OWASP 规则,默认没有开启。

测试简单 XSS 攻击,没开启 Modsecurity 之前:

54e0be458080b782175d9bdad540f01b.png

状态200,没有拦截。

开启 Modsecurity 模块

[root@k8s-test01 ~]# cat ingress-nginx-demo.yaml 
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/enable-modsecurity: "true"
    nginx.ingress.kubernetes.io/enable-owasp-modsecurity-crs: "true"
    nginx.ingress.kubernetes.io/modsecurity-snippet: |
      SecRuleEngine On
      SecRequestBodyAccess On
      SecAuditEngine RelevantOnly
      SecAuditLogParts ABIJDEFHZ
      SecAuditLog /var/log/nginx/modsec_audit.log
      Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
spec:
  rules:
  - host: "foo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: nginx
            port:
              number: 80
[root@k8s-test01 ~]#

再进行 XSS 攻击测试

2b652344e1d1e08de5656378bffc6297.png

状态403,已拦截。

查看 Nginx 拦截日志

2020/12/27 09:37:56 [error] 3001#3001: *213189 [client 47.242.91.20] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5' against variable `TX:ANOMALY_SCORE' (Value: `5' ) [file "/etc/nginx/owasp-modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "80"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "192.168.123.79"] [uri "/"] [unique_id "160906187674.566940"] [ref ""], client: 47.242.91.20, server: foo.bar.com, request: "HEAD /?search=alert(xss); HTTP/1.1", host: "foo.bar.com"

04

获取客户端真实IP

Nginx Ingress 部署使用 nodePort 模式, 在讲获取客户端真实 IP 之前,我们大概了解下 nodePort 模式的链路。从 Kubernetes 1.5 开始,NodePort 类型的 Services 的数据包默认进行源地址 NAT。

假设某一个 Pod 运行在 node1 节点,客户端访问 node2:nodeport,过程如下:

          client
\ ^
\ \
v \
node 1 | ^ SNAT
| | --->
v |
endpoint

1、客户端发送数据包到 node2:nodePort

2、node2 使用它自己的 IP 地址替换数据包的源 IP 地址(SNAT)

3、node2 使用 pod IP 地址替换数据包的目的 IP 地址

4、数据包被路由到 node1,然后交给 endpoint

5、Pod 的回复被路由回 node2

6、Pod 的回复被发送回给客户端

所以 nodePort 模式下源地址被转换了(SNAT),服务端获取的并不是正确的客户端 IP,它们是集群的内部 IP。为什么会这样呢?原因是为了支持从任一节点IP+NodePort 都可以访问应用,而不得不做的 SNAT。

当然,Kubernetes 也提供了一个特性来保留客户端的源 IP 地址,通过设置 externalTrafficPolicy 的值为 Local,请求就只会被代理到本地 endpoints 而不会被转发到其它节点。这样就保留了最初的源 IP 地址。

---
# Source: ingress-nginx/templates/controller-service.yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:
    helm.sh/chart: ingress-nginx-3.17.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.42.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  externalTrafficPolicy: Local
  type: NodePort
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
      nodePort: 80
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
      nodePort: 443
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/component: controller

 但是,如果没有本地 endpoints,发送到这个节点的数据包将会被丢弃。即请求 node2:nodePort , 但 node2 上没有运行 Pod , 故本地没有 endpoints ,所以请 node2:nodePort 是失败的,node1:nodePort 正常。

        client
^ / \
/ / \
/ v X
node 1 node 2
^ |
| |
| v
endpoint

所以 Nginx Ingress如需获取客户端真实 IP 需要设置 externalTrafficPolicy 或设置容器网络使用主机模式 hostNetwork: true ,然后 daemonset 部署,或通过亲和性把 Pod 固定在某些节点,客户端访问 Pod 所在的节点,源 IP 地址便不会 SNAT。

Nginx Ingress 获取客户端真实 IP 的配置如下:

---
# Source: ingress-nginx/templates/controller-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    helm.sh/chart: ingress-nginx-3.17.0
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.42.0
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  server-tokens: "false"
  forwarded-for-header: "X-Forwarded-For"
  use-forwarded-headers: "true"
  compute-full-forwarded-for: "true"

还可以通过http-snippet 获取 客户端真实 IP

data:
  server-tokens: "false"
  http-snippet: |
    real_ip_header X-Forwarded-For;
    set_real_ip_from 0.0.0.0/0;

通过日志可以看到已经获取到真实的IP

5c6fae35660f918896f35dadfe4c01ce.png

当 Nginx Ingress 在转发请求时会通过 X-Forwarded-For 和 X-Real-IP 字段来记录客户端源 IP,后端可以通过此字段获得客户端真实源 IP,可以写一个简单的程序来验证下

package main

import (
    "log"
    "net"
    "net/http"
    "strings"
)

func myHandle(w http.ResponseWriter, r *http.Request) {
    _, _ = w.Write([]byte(remoteIP(r)))
}

func main() {

    serveMux := http.NewServeMux()
    serveMux.HandleFunc("/", myHandle)

    err := http.ListenAndServe("0.0.0.0:80", serveMux)
    if err != nil {
        log.Printf("http.ListenAndServe():%v\n", err)
        return
    }
}
func remoteIP(r *http.Request) string {
    ip := r.Header.Get("X-Original-Forwarded-For")
    log.Printf("X-Original-Forwarded-For : %s", r.Header.Get("X-Original-Forwarded-For"))
    if ip != "" {
        return ip
    }

    ip = r.Header.Get("X-Forwarded-For")
    log.Printf("X-Forwarded-For: %s", r.Header.Get("X-Forwarded-For"))
    if ip != "" {
        return ip
    }

    ip = r.Header.Get("X-Real-Ip")
    log.Printf("X-Real-Ip : %s", r.Header.Get("X-Real-Ip"))
    if ip != "" {
        return ip
    }

    if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil {
        return ip
    }

    return ""
}

程序运行日志

[root@k8s-test01 ~]# kubectl cp app nginx-d8d5f47c9-n9bnm:/
[root@k8s-test01 ~]# kubectl exec -it nginx-d8d5f47c9-n9bnm -- bash
root@nginx-d8d5f47c9-n9bnm:/# ./app 
2020/12/27 10:40:57 X-Original-Forwarded-For : 
2020/12/27 10:40:57 X-Forwarded-For: 47.112.119.36

05

路由配置

通过 iris 框架写一个简单的测试程序

func NewAPP() *iris.Application {
    // 创建app结构体对象
    app := iris.New()
    // 配置字符编码
    app.Configure(iris.WithConfiguration(iris.Configuration{
        Charset: "UTF-8",
    }))

    // 配置日志
    customLogger := logger.New(logger.Config{
        //状态显示状态代码
        Status: true,
        // IP显示请求的远程地址
        IP: true,
        //显示http方法
        Method: true,
        // Path显示请求路径
        Path: true,
        // Query将url查询附加到Path。
        Query: true,
        //Columns:true,
        // 如果不为空然后它的内容来自`ctx.Values(),Get("logger_message")
        //将添加到日志中。
        MessageContextKeys: []string{"logger_message"},
        //如果不为空然后它的内容来自`ctx.GetHeader(“User-Agent”)
        MessageHeaderKeys: []string{"User-Agent"},
    })
    // 捕获所有http错误:
    app.OnAnyErrorCode(customLogger, func(ctx iris.Context) {
        switch ctx.GetStatusCode() {
        case 404:
            ctx.Values().Set("logger_message", "a dynamic message passed to the logs")
            ctx.Writef("My Custom 404 error page")
        default:
            ctx.Values().Set("logger_message", "a dynamic message passed to the logs")
            ctx.Writef("%v unknown error page", ctx.GetStatusCode())
        }
    })
    app.Use(customLogger)

    // favicons
    app.Favicon("./static/favicons/favicon.ico")

    // static
    app.HandleDir("/", "static")

    return app
}

入口程序

func main() {
    app := config.NewAPP()
    resAPI := app.Party("/api/v1")
    resAPI.Get("/namespaces",handle.GetNameSpace)
    resAPI.Get("/ip",handle.GetIP)
    resAPI.Get("/hostname",handle.GetHostname)
    app.Run(iris.Addr("0.0.0.0:8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithOptimizations)
}

创建Ingress

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/enable-modsecurity: "true"
    nginx.ingress.kubernetes.io/enable-owasp-modsecurity-crs: "true"
    nginx.ingress.kubernetes.io/modsecurity-snippet: |
      SecRuleEngine On
      SecRequestBodyAccess On
      SecAuditEngine RelevantOnly
      SecAuditLogParts ABIJDEFHZ
      SecAuditLog /var/log/nginx/modsec_audit.log
      Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
spec:
  rules:
  - host: "demo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/api/v1"
        backend:
          service:
            name: demo
            port:
              number: 8080
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: demo
            port:
              number: 8080

b219bf93a7b2e2c464b15220acd0094b.png

静态资源加一级路由测试,重写路径

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/enable-modsecurity: "true"
    nginx.ingress.kubernetes.io/enable-owasp-modsecurity-crs: "true"
    nginx.ingress.kubernetes.io/modsecurity-snippet: |
      SecRuleEngine On
      SecRequestBodyAccess On
      SecAuditEngine RelevantOnly
      SecAuditLogParts ABIJDEFHZ
      SecAuditLog /var/log/nginx/modsec_audit.log
      Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf
    nginx.ingress.kubernetes.io/app-root: /test/
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/configuration-snippet: |
      rewrite ^/css/(.*)$ /test/css/$1 redirect;
      rewrite ^/js/(.*)$ /test/js/$1 redirect;
      rewrite ^/img/(.*)$ /test/img/$1 redirect;
spec:
  rules:
  - host: "demo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/api/v1"
        backend:
          service:
            name: demo
            port:
              number: 8080
      - pathType: Prefix
        path: "/test(/|$)(.*)"
        backend:
          service:
            name: demo
            port:
              number: 8080

显示正常。(这里的前端样式文件用的是相对路径)

a6e13880f62284708f9147d50fd64ad9.png

06

开启压缩

开启压缩前 

b9d3259015586c8a431c886167e1d473.png

开启压缩

    nginx.ingress.kubernetes.io/server-snippet: |
        gzip on;
        gzip_disable "MSIE [1-6]\.";
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 5;
        gzip_min_length 512;
        gzip_buffers 16 128k;
        gzip_http_version 1.1;
        gzip_types
            application/json
            application/javascript
            application/xml
            application/x-javascript
            application/vnd.api+json
            application/json
            application/x-font-ttf
            text/javascrip
            text/css
            text/plain
            image/jpeg
            image/png
            image/jpg
            image/svg+xml
            image/x-icon;

673c0f0915120a676a542cc5ee167327.png

06

其他

Nginx Ingress 的配置参数与 Nginx 相差无几,更多配置请参考官方文档:

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值