k8s apiserver中的限流策略源码解读

k8s零基础入门运维课程

k8s纯源码解读教程(3个课程内容合成一个大课程)

k8s运维进阶调优课程

k8s管理运维平台实战

k8s二次开发课程

cicd 课程

prometheus全组件的教程

go语言课程

直播答疑sre职业发展规划

k8s支持多种限流配置

为了防止突发流量影响apiserver可用性,k8s支持多种限流配置,包括:
  • MaxInFlightLimit,server级别整体限流
  • Client限流
  • EventRateLimit, 限制event
  • APF,更细力度的限制配置

MaxInFlightLimit限流

  • apiserver默认可设置最大并发量(集群级别,区分只读与修改操作)
  • 通过参数--max-requests-inflight代表只读请求
  • --max-mutating-requests-inflight代表修改请求
  • 可以简单实现限流。

源码解读

  • 入口 GenericAPIServer.New中的添加hook
  • 位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\server\config.go

      if c.FlowControl != nil {
          const priorityAndFairnessFilterHookName = "priority-and-fairness-filter"
          if !s.isPostStartHookRegistered(priorityAndFairnessFilterHookName) {
              err := s.AddPostStartHook(priorityAndFairnessFilterHookName, func(context PostStartHookContext) error {
                  genericfilters.StartPriorityAndFairnessWatermarkMaintenance(context.StopCh)
                  return nil
              })
              if err != nil {
                  return nil, err
              }
          }
      } else {
          const maxInFlightFilterHookName = "max-in-flight-filter"
          if !s.isPostStartHookRegistered(maxInFlightFilterHookName) {
              err := s.AddPostStartHook(maxInFlightFilterHookName, func(context PostStartHookContext) error {
                  genericfilters.StartMaxInFlightWatermarkMaintenance(context.StopCh)
                  return nil
              })
              if err != nil {
                  return nil, err
              }
          }
      }
  • 意思是FlowControl为nil ,代表未启用 APF,API 服务器中的整体并发量将受到 kube-apiserver 的参数 --max-requests-inflight 和 --max-mutating-requests-inflight 的限制。
  • 启动metrics观测的函数

    func startWatermarkMaintenance(watermark *requestWatermark, stopCh <-chan struct{}) {
      // Periodically update the inflight usage metric.
      go wait.Until(func() {
          watermark.lock.Lock()
          readOnlyWatermark := watermark.readOnlyWatermark
          mutatingWatermark := watermark.mutatingWatermark
          watermark.readOnlyWatermark = 0
          watermark.mutatingWatermark = 0
          watermark.lock.Unlock()
    
          metrics.UpdateInflightRequestMetrics(watermark.phase, readOnlyWatermark, mutatingWatermark)
      }, inflightUsageMetricUpdatePeriod, stopCh)
    
      // Periodically observe the watermarks. This is done to ensure that they do not fall too far behind. When they do
      // fall too far behind, then there is a long delay in responding to the next request received while the observer
      // catches back up.
      go wait.Until(func() {
          watermark.readOnlyObserver.Add(0)
          watermark.mutatingObserver.Add(0)
      }, observationMaintenancePeriod, stopCh)
    }

    WithMaxInFlightLimit代表限流处理函数

  • 调用的入口在 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\server\config.go
  • DefaultBuildHandlerChain中,还是判断FlowControl为nil就开启WithMaxInFlightLimit

      if c.FlowControl != nil {
          requestWorkEstimator := flowcontrolrequest.NewWorkEstimator(c.StorageObjectCountTracker.Get)
          handler = filterlatency.TrackCompleted(handler)
          handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl, requestWorkEstimator)
          handler = filterlatency.TrackStarted(handler, "priorityandfairness")
      } else {
          handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
      }
    
    解读
  • 如果limit num为0就不开启限流了

      if nonMutatingLimit == 0 && mutatingLimit == 0 {
          return handler
      }
  • 构造限流的chan,类型为长度=limit的 bool chan

      var nonMutatingChan chan bool
      var mutatingChan chan bool
      if nonMutatingLimit != 0 {
          nonMutatingChan = make(chan bool, nonMutatingLimit)
          watermark.readOnlyObserver.SetX1(float64(nonMutatingLimit))
      }
      if mutatingLimit != 0 {
          mutatingChan = make(chan bool, mutatingLimit)
          watermark.mutatingObserver.SetX1(float64(mutatingLimit))
      }
    
    检查是否是长时间运行的请求
          // Skip tracking long running events.
          if longRunningRequestCheck != nil && longRunningRequestCheck(r, requestInfo) {
              handler.ServeHTTP(w, r)
              return
          }
    
  • 使用BasicLongRunningRequestCheck检查是否是watch或者pprof debug等长时间运行的请求,因为这些请求不受限制,位置 D:\go_path\src\github.com\kubernetes\kubernetes\staging\src\k8s.io\apiserver\pkg\server\filters\longrunning.go

    // BasicLongRunningRequestCheck returns true if the given request has one of the specified verbs or one of the specified subresources, or is a profiler request.
    func BasicLongRunningRequestCheck(longRunningVerbs, longRunningSubresources sets.String) apirequest.LongRunningRequestCheck {
      return func(r *http.Request, requestInfo *apirequest.RequestInfo) bool {
          if longRunningVerbs.Has(requestInfo.Verb) {
              return true
          }
          if requestInfo.IsResourceRequest && longRunningSubresources.Has(requestInfo.Subresource) {
              return true
          }
          if !requestInfo.IsResourceRequest && strings.HasPrefix(requestInfo.Path, "/debug/pprof/") {
              return true
          }
          return false
      }
    }
    
检查是只读操作还是修改操作,决定使用哪个chan限制
        var c chan bool
        isMutatingRequest := !nonMutatingRequestVerbs.Has(requestInfo.Verb)
        if isMutatingRequest {
            c = mutatingChan
        } else {
            c = nonMutatingChan
        }
如果队列未满,有空的位置,则更新下排队数字
  • 使用select 向c中写入true,如果能写入到说明队列未满
  • 记录下对应的指标

              select {
              case c <- true:
                  // We note the concurrency level both while the
                  // request is being served and after it is done being
                  // served, because both states contribute to the
                  // sampled stats on concurrency.
                  if isMutatingRequest {
                      watermark.recordMutating(len(c))
                  } else {
                      watermark.recordReadOnly(len(c))
                  }
                  defer func() {
                      <-c
                      if isMutatingRequest {
                          watermark.recordMutating(len(c))
                      } else {
                          watermark.recordReadOnly(len(c))
                      }
                  }()
                  handler.ServeHTTP(w, r)
    
default代表队列已满,但是如果请求的group中含有 system:masters,则放行
  • 因为apiserver认为这个组是很重要的请求,不能被限流

                  if currUser, ok := apirequest.UserFrom(ctx); ok {
                      for _, group := range currUser.GetGroups() {
                          if group == user.SystemPrivilegedGroup {
                              handler.ServeHTTP(w, r)
                              return
                          }
                      }
                  }
  • group=system:masters 对应的clusterRole 为cluster-admin
队列已满,如果请求的group中没有 system:masters,则返回http 429错误
  • http 429 代表当前有太多请求了,请重试
  • 并设置 response 的header Retry-After =1

                  if isMutatingRequest {
                      metrics.DroppedRequests.WithContext(ctx).WithLabelValues(metrics.MutatingKind).Inc()
                  } else {
                      metrics.DroppedRequests.WithContext(ctx).WithLabelValues(metrics.ReadOnlyKind).Inc()
                  }
                  metrics.RecordRequestTermination(r, requestInfo, metrics.APIServerComponent, http.StatusTooManyRequests)
                  tooManyRequests(r, w)

Client限流

  • 例如client-go默认的qps为5,但是只支持客户端限流,只能由各个发起端限制
  • 集群管理员无法控制用户行为。

EventRateLimit

  • EventRateLimit在1.13之后支持,只限制event请求
  • 集成在apiserver内部webhoook中
  • 可配置某个用户、namespace、server等event操作限制,通过webhook形式实现。
和文档一起学习
原理
  • 具体原理可以参考提案,每个eventratelimit 配置使用一个单独的令牌桶限速器
  • 每次event操作,遍历每个匹配的限速器检查是否能获取令牌,如果可以允许请求,否则返回429。
优点
  • 实现简单,允许一定量的并发
  • 可支持server/namespace/user等级别的限流

    缺点
  • 仅支持event,通过webhook实现只能拦截修改类请求
  • 所有namespace的限流相同,没有优先级

APF,更细力度的限制配置

  • API 优先级和公平性(APF)是MaxInFlightLimit限流的一种替代方案,设计文档见提案。
  • API 优先级和公平性(1.15以上,alpha版本), 以更细粒度(byUser,byNamespace)对请求进行分类和隔离。 支持突发流量,通过使用公平排队技术从队列中分发请求从而避免饥饿。
  • APF限流通过两种资源,PriorityLevelConfigurations定义隔离类型和可处理的并发预算量,还可以调整排队行为。 FlowSchemas用于对每个入站请求进行分类,并与一个 PriorityLevelConfigurations相匹配。
  • 可对用户或用户组或全局进行某些资源某些请求的限制,如限制default namespace写services put/patch请求。
优点
  • 考虑情况较全面,支持优先级,白名单等
  • 可支持server/namespace/user/resource等细粒度级别的限流

    缺点
  • 配置复杂,不直观,需要对APF原理深入了解
  • 功能较新,缺少生产环境验证

    文档地址
  • https://kubernetes.io/zh/docs...

你可能感兴趣的:(kubernetesgo运维)