<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>KEDA | 伪架构师</title>
    <link>/tags/keda/</link>
      <atom:link href="/tags/keda/index.xml" rel="self" type="application/rss+xml" />
    <description>KEDA</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Wed, 29 Nov 2023 15:21:04 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>KEDA</title>
      <link>/tags/keda/</link>
    </image>
    
    <item>
      <title>用 KEDA 根据工作负载进行快速扩容</title>
      <link>/post/way-to-keda/</link>
      <pubDate>Wed, 29 Nov 2023 15:21:04 +0800</pubDate>
      <guid>/post/way-to-keda/</guid>
      <description>

&lt;p&gt;&lt;strong&gt;太长不看版&lt;/strong&gt;：用单一指标指导单一工作负载进行扩缩容实在是太低效了。&lt;/p&gt;

&lt;h2 id=&#34;历史问题&#34;&gt;历史问题&lt;/h2&gt;

&lt;p&gt;众所周知，Kubernetes 有个亲生的 HPA 组件，在云原生早期，这个名义上的自动扩缩容的能力给 Kubernetes 赢得了不少掌声。当然现在回头看看，仅仅根据 CPU 和内存这样“贫瘠”的指标，不论是用于判断负载水平，还是用于计算扩容目标，都不是很够用的。这个阶段里，HPA 的扩缩容效率也是广受诟病的一个问题，在一个多级微服务调用的业务场景里，压力是&lt;strong&gt;逐级传递&lt;/strong&gt;的，下图展示了一个常见情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/load-in-turn.png&#34; alt=&#34;turn&#34; /&gt;&lt;/p&gt;

&lt;p&gt;如上图，用户流量进入集群之后：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先在 &lt;code&gt;Deploy A&lt;/code&gt; 造成负载，指标变化迫使 &lt;code&gt;Deploy A&lt;/code&gt; 扩容&lt;/li&gt;
&lt;li&gt;A 扩容之后，吞吐量变大，B 受到压力，再次采集到指标变化，扩容 &lt;code&gt;Deploy B&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;B 吞吐变大，C ..&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个逐级传递的过程不仅缓慢，而且可以说是步步惊心——每一级的扩容都是直接被 CPU 或内存的飙高触发的，被“冲垮”的可能性是普遍存在的。这种被动、滞后的方式，很明显是有问题的。&lt;/p&gt;

&lt;h2 id=&#34;推陈出新&#34;&gt;推陈出新&lt;/h2&gt;

&lt;p&gt;造成 HPA 窘境的原因之一，就是“自扫门前雪”，每个 Pod 都只能根据自身负载情况来进行扩缩容决策。如果能够直接根据业务流量的变化进行决策，并且将流量流经的所有微服务进行扩缩容，看起来情况就会好很多了。
 HPA 的&lt;a href=&#34;https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/&#34; target=&#34;_blank&#34;&gt;自定义指标&lt;/a&gt;支持，给这个问题了一个可行的方案。该能力让 HPA 可以用其它的指标来作为扩缩容的触发器，例如我们可以用 Promethues 采集消息中间件的深度或者负载均衡器的队列长度，作为一个更能如实反映业务流量的指标，直接用来触发相关的多个微服务的扩缩容，如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/prom-adapter.png&#34; alt=&#34;prom-adapter&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在上图中：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prometheus 采集消息队列和负载均衡等更能反映业务流量的指标&lt;/li&gt;
&lt;li&gt;使用 Prometheus Adapter 将 Promethues Metrics 转换为 Kubernetes 的 Aggregated API&lt;/li&gt;
&lt;li&gt;HPA 使用自定义指标，&lt;strong&gt;同时&lt;/strong&gt;对多个应用进行扩缩容。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这中间涉及到的 Prometheus Adapter，通过配置文件完成步骤 2 的转换：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- seriesQuery: &#39;{__name__=~&amp;quot;^container_.*_total&amp;quot;,container!=&amp;quot;POD&amp;quot;,namespace!=&amp;quot;&amp;quot;,pod!=&amp;quot;&amp;quot;}&#39;
  resources:
    overrides:
      namespace: {resource: &amp;quot;namespace&amp;quot;}
      pod: {resource: &amp;quot;pod&amp;quot;}
  seriesFilters:
  # since this is a superset of the query above, we introduce an additional filter here
  - isNot: &amp;quot;^container_.*_seconds_total$&amp;quot;
  name: {matches: &amp;quot;^container_(.*)_total$&amp;quot;}
  metricsQuery: &amp;quot;sum(rate(&amp;lt;&amp;lt;.Series&amp;gt;&amp;gt;{&amp;lt;&amp;lt;.LabelMatchers&amp;gt;&amp;gt;,container!=&amp;quot;POD&amp;quot;}[2m])) by (&amp;lt;&amp;lt;.GroupBy&amp;gt;&amp;gt;)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当然，完全可以自行实现 Aggregated API 来支持这种指标的采集和呈现工作。Prometheus 所提供的大量 Exporter 是吸引我们写这种古怪语法的最大动力。&lt;/p&gt;

&lt;p&gt;那么如果是 KEDA 的话，这个问题又如何呢？KEDA 提供了几十个被称为 Scaler 的东西，其中除了 Promethues 之外，还包括 Kafka、Redis、PostgreSQL 等多种选择。所以在很多场景中，无需 Promethues，也能使用 Scaler 完成对输入指标的读取和判断。下面用 KEDA 为例，看看这种伸缩方法的具体实现。&lt;/p&gt;

&lt;h2 id=&#34;keda&#34;&gt;KEDA&lt;/h2&gt;

&lt;p&gt;假设一个容器化应用由多个工作负载组成：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ingress：负责接收业务流量&lt;/li&gt;
&lt;li&gt;Backend 1、Backend 2：负责处理 Ingress 发来的任务&lt;/li&gt;
&lt;li&gt;Database：数据库&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们希望达成的效果是 —— Ingress、Backend 1、Backend 2、Database，实例数量保持在 &lt;code&gt;1:2:1.5:2&lt;/code&gt; 的关系，Keda 的大致流程如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/sequence.png&#34; alt=&#34;images&#34; /&gt;&lt;/p&gt;

&lt;p&gt;首先使用 Helm 安装 KEDA：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ helm repo add kedacore https://kedacore.github.io/charts
$ helm install keda kedacore/keda --namespace default
NAME: keda
LAST DEPLOYED: Wed Nov 29 18:56:36 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;随便创建几个工作负载，冒充微服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create deploy ingress --image=nginx
deployment.apps/ingress created
$ kubectl create deploy backend1 --image=nginx
deployment.apps/backend1 created
$ kubectl create deploy backend2 --image=nginx
deployment.apps/backend2 created
$ kubectl create deploy database --image=nginx
deployment.apps/database created
$ kubectl get pods | cut -d - -f 1 | grep -v keda | sort
...
backend1
backend2
database
ingress
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行成功后，我们可以看到，四个微服务，每个微服务都有一个实例。&lt;/p&gt;

&lt;p&gt;按照刚才瞎掰的比例，编写一个 &lt;code&gt;ScaleObject&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: bk1
spec:
  scaleTargetRef:
    name: backend1
  triggers:
  - type: kubernetes-workload
    metadata: 
      podSelector: &#39;app=ingress&#39;
      value: &#39;0.5&#39;
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: bk2
spec:
  scaleTargetRef:
    name: backend2
  triggers:
  - type: kubernetes-workload
    metadata: 
      podSelector: &#39;app=ingress&#39;
      value: &#39;0.67&#39;      
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: db
spec:
  scaleTargetRef:
    name: database
  triggers:
  - type: kubernetes-workload
    metadata: 
      podSelector: &#39;app=ingress&#39;
      value: &#39;0.5&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上述代码引入了 &lt;code&gt;kubernetes-workload&lt;/code&gt; 类型的触发器，他会监控 &lt;code&gt;app=ingress&lt;/code&gt; 的容器，并对 &lt;code&gt;scaleTargetRef&lt;/code&gt; 中提到的工作负载数量比例进行扩缩容。&lt;/p&gt;

&lt;p&gt;提交到集群之后，会看到实例数量数量发生了变化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get pods | cut -d - -f 1 | sort | uniq --count
...
   2 backend1
   2 backend2
   2 database
   1 ingress
   3 keda
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们把 Ingress 扩容到 2 实例，再次统计：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl scale deployment ingress --replicas=2
deployment.apps/ingress scaled
$ kubectl get pods | cut -d - -f 1 | sort | uniq --count
...
   4 backend1
   3 backend2
   4 database
   2 ingress
   3 keda
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，的确是按照我们设定的比例，同步产生了缩放。如果缩减 Ingress 服务实例数，几分钟之后，其它工作负载也会随之缩容。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl scale deployment ingress --replicas=1
deployment.apps/ingress scaled
$ kubectl get pods | cut -d - -f 1 | sort | uniq --count                                                         \
...
   2 backend1
   2 backend2
   2 database
   1 ingress
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;结论&#34;&gt;结论&lt;/h2&gt;

&lt;p&gt;虽说云原生架构的复杂性问题越来越被强调，但是这一生态的宗旨应该还是没有变化——用简单的透明的手段解决复杂问题。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
