暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

mac 上学习k8s系列(9)nginx-ingress lua

        本篇将在mac 上学习k8s系列(8)external auth的基础上基于nginx-ingress 的access_by_lua_block +redis 来实现一个全局的rate limiter:用nginx lua连接redis,用redis计数来做集群粒度的rate-limit

          ngx_lua 模块提供了配置指令 access_by_lua,用于在 access 请求处理阶段插入用户 Lua 代码。这条指令运行于 access 阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段。一般我们通过 access_by_lua 在 ngx_access 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的请求验证操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限。

        首先,我们启动一个简单的redis服务,一般redis、mysql等有状态的服务,我们一般通过pv使用nfs服务存储,并且使用使用StatefulSet创建redis-cluster集群节点和headless service,保证每个pod的ip不变。本文重点介绍如何使用access_by_lua_block,所以搭建一个简单的无状态的redis。

        创建一个ConfigMap来保存redis的配置:

    ---
    kind: ConfigMap
    apiVersion: v1
    metadata:
    name: redis-config
    labels:
    app: redis
    data:
    redis.conf: |-
    dir /srv
    port 6379
    bind 0.0.0.0
    appendonly yes
    daemonize no
    #protected-mode no
    requirepass 123456
    pidfile /srv/redis-6379.pid

    然后通过DeployMent启动redis的pod

      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
      name: redis
      labels:
      app: redis
      spec:
      replicas: 1
      selector:
      matchLabels:
      app: redis
      template:
      metadata:
      labels:
      app: redis
      spec:
      containers:
      - name: redis
      image: redis:latest
      command:
      - "sh"
      - "-c"
      - "redis-server usr/local/redis/redis.conf"
      ports:
      - containerPort: 6379
      resources:
      limits:
      cpu: 1000m
      memory: 1024Mi
      requests:
      cpu: 1000m
      memory: 1024Mi
      livenessProbe:
      tcpSocket:
      port: 6379
      initialDelaySeconds: 300
      timeoutSeconds: 1
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 3
      readinessProbe:
      tcpSocket:
      port: 6379
      initialDelaySeconds: 5
      timeoutSeconds: 1
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 3
      volumeMounts:
      - name: config
      mountPath: /usr/local/redis/redis.conf
      subPath: redis.conf
      volumes:
      - name: config
      configMap:
      name: redis-config

      启动redis服务

        apiVersion: v1
        kind: Service
        metadata:
        name: redis
        labels:
        app: redis
        spec:
        type: NodePort
        ports:
        - name: tcp
        port: 6379
        nodePort: 30379
        selector:
        app: redis

        测试下redis服务是否正常启动了:

           %redis-cli -h 127.0.0.1 -p 30379 -a 123456
          Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
          127.0.0.1:30379>

          接着,我们创建Ingress

            apiVersion: networking.k8s.io/v1
            kind: Ingress
            metadata:
            name: ingress-with-auth
            annotations:
            nginx.ingress.kubernetes.io/server-snippet: |
            access_by_lua_block {
            local header = ngx.req.get_headers()
            if header.token then
            local LIMIT = 100
            local DELAY = 10


            local red = require "resty.redis"
            local redis = red:new()
            redis:set_timeout(1000)
            local ok, err = redis:connect("redis.default.svc.cluster.local", 6379)
            if not ok then
            ngx.status = 500
            ngx.say("<h1>系统开小差了</h1>",err)
            return
            end


            local res, err = redis:auth("123456")
            if not res then
            ngx.status = 500
            ngx.say("<h1>系统开小差了</h1>")
            return
            end


            local now = ngx.now()
            local ok, err = redis:eval('local oldest = redis.call("lindex", ARGV[1], -1);if oldest then if redis.call("llen", ARGV[1]) >= tonumber(KEYS[1]) then if (ARGV[2] - oldest) < tonumber(KEYS[2]) then return nil end end end;redis.call("lpush", ARGV[1], ARGV[2]);redis.call("expire", ARGV[1], KEYS[1]);redis.call("ltrim", ARGV[1], 0, KEYS[1]); return 1', 2, LIMIT-1, DELAY, "limit:"..ngx.md5(header.token), now)


            if ok ~= 1 then
            ngx.status = 519
            ngx.say("<h1>系统繁忙</h1>")
            redis:set_keepalive(10000, 100)
            return
            end


            redis:set_keepalive(10000, 100)
            end
            }
            nginx.ingress.kubernetes.io/rewrite-target:
            spec:
            rules:
            - http:
            paths:
            - pathType: Prefix
            path:
            backend:
            service:
            name: apple-service
            port:
            number: 5678

            我们在注解的nginx.ingress.kubernetes.io/server-snippet: 段插入了lua代码:access_by_lua_block,通过openresty的redis 包来连接redis:

              local red = require "resty.redis"
              local redis = red:new()
              redis:set_timeout(1000)
              local ok, err = redis:connect("redis.default.svc.cluster.local"6379)

              通过执行redis lua脚本来进行限流

                local now = ngx.now()
                local ok, err = redis:eval('
                local oldest = redis.call("lindex"ARGV[1], -1);
                if oldest 
                then if redis.call("llen"ARGV[1]) >= tonumber(KEYS[1]) 
                then if (ARGV[2] - oldest) < tonumber(KEYS[2]) 
                then return nil 
                      end
                  end 
                  end;
                 redis.call("lpush"ARGV[1], ARGV[2]);
                 redis.call("expire"ARGV[1], KEYS[1]);
                 redis.call("ltrim"ARGV[1], 0KEYS[1]); 
                 return 1',
                2LIMIT-1DELAY"limit:"..ngx.md5(header.token), now)

                通过滑动窗口算法,做用户token维度的限流。

                首先我们测试下

                  kubectl apply -f ingress.yaml 
                  curl -H "token:12344" 127.0.0.1/apple       
                  /apple

                  一个请求的情况下正常工作了,我们来压测下

                    % ab -n 1000 -c 100 -H "token:12344" 127.0.0.1/apple   
                    Percentage of the requests served within a certain time (ms)
                    50% 54
                    66% 77
                    75% 90
                    80% 99
                    90% 122
                    95% 145
                    98% 189
                    99% 212
                    100% 258 (longest request)

                    100个并发是正常工作的,我们换成200个呢

                      %ab -n 1000 -c 200 -H "token:12344" 127.0.0.1/apple       
                      This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
                      Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
                      Licensed to The Apache Software Foundation, http://www.apache.org/


                      Benchmarking 127.0.0.1 (be patient)
                      apr_socket_recv: Connection reset by peer (54)
                      Total of 1 requests completed

                      限流量,至此我们完成了access_by_lua_block +redis 全局的rate limiter的功能。其中也遇到了各种问题,下一节给大家介绍分析思路。


                      文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                      评论