服务网格ASM为ACK集群内的服务通信提供了一种非侵入式的生成遥测数据的能力。这种遥测功能提供了服务行为的可观测性,可以帮助运维人员对应用程序进行故障排除、维护和优化,而不会带来任何额外负担。本文介绍如何使用ASM指标实现工作负载的自动弹性伸缩。

前提条件

背景信息

服务网格ASM为ACK集群内的服务通信提供了一种非侵入式的生成遥测数据的能力。这种遥测功能提供了服务行为的可观测性, 可以帮助运维人员对应用程序进行故障排除、维护和优化,而不会带来任何额外负担。根据监控的四个黄金指标维度(延迟、流量、错误和饱和度)服务网格ASM为管理的服务生成一系列指标,具体指标可以参见Istio标准指标

自动伸缩是一种根据资源使用情况进行自动扩缩工作负载的方法。Kubernetes中的自动伸缩具有两个维度:一个是用于处理节点伸缩操作的集群自动伸缩器Cluster Autoscaler(CA), 另外一个是用于自动伸缩应用部署中的Pod的水平自动伸缩器Horizontal Pod Autoscaler(HPA)。

Kubernetes提供的聚合层允许第三方应用程序通过将自身注册为API Addon组件来扩展Kubernetes API。这样的Addon组件可以实现Custom Metrics API,并允许HPA访问任意指标。HPA会定期通过Resource Metrics API查询核心指标(例如CPU /内存)以及通过Custom Metrics API获取特定于应用程序的指标,包括ASM提供的可观测性指标。弹性伸缩

开启采集Prometheus监控指标

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格管理详情页面单击右上角的功能设置
    说明 请确保ASM实例的Istio为v1.6.8.4及以上版本。
  5. 功能设置更新对话框中选中开启采集Prometheus监控指标,然后单击确定

    ASM将自动生成采集Prometheus监控指标相关的EnvoyFilter配置。

部署自定义指标API Adapter

  1. 在ACK集群中安装部署自定义指标API Adapter,使用了如下Helm安装包kube-metrics-adapter
    ## 如果使用helm v3
    helm -n kube-system install asm-custom-metrics ./kube-metrics-adapter  --set prometheus.url=http://prometheus.istio-system.svc:9090

    如果使用helm v2或者其他安装选项,请参考上述Helm包链接文档。

  2. 安装完成后,请确认kube-metrics-adapter已启用。 确保autoscaling/v2beta1显示API组。

    输入以下命令:

    kubectl api-versions |grep "autoscaling/v2beta2"

    系统输出类似如下结果:

    autoscaling/v2beta2
  3. 确保相应的kube-metrics-adapter Pod已部署并处于running状态。

    输入以下命令:

    kubectl get po -n kube-system |grep metrics-adapter

    系统输出类似如下结果:

    asm-custom-metrics-kube-metrics-adapter-85c6d5d865-2cm57          1/1     Running   0          19s
  4. 列出Prometheus适配器提供的自定义外部指标。

    输入以下命令:

    kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq .

    系统输出类似如下结果:

    {
      "kind": "APIResourceList",
      "apiVersion": "v1",
      "groupVersion": "external.metrics.k8s.io/v1beta1",
      "resources": []
    }

部署示例应用

首先创建test命名空间,并启用Sidecar自动注入。然后在该命名空间中创建podinfo部署和服务,将以下YAML内容存为文件podinfo.yaml。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: podinfo
  namespace: test
  labels:
    app: podinfo
spec:
  minReadySeconds: 5
  strategy:
    rollingUpdate:
      maxUnavailable: 0
    type: RollingUpdate
  selector:
    matchLabels:
      app: podinfo
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
      labels:
        app: podinfo
    spec:
      containers:
      - name: podinfod
        image: stefanprodan/podinfo:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9898
          name: http
          protocol: TCP
        command:
        - ./podinfo
        - --port=9898
        - --level=info
        livenessProbe:
          exec:
            command:
            - podcli
            - check
            - http
            - localhost:9898/healthz
          initialDelaySeconds: 5
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command:
            - podcli
            - check
            - http
            - localhost:9898/readyz
          initialDelaySeconds: 5
          timeoutSeconds: 5
        resources:
          limits:
            cpu: 2000m
            memory: 512Mi
          requests:
            cpu: 100m
            memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  name: podinfo
  namespace: test
  labels:
    app: podinfo
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 9898
      targetPort: 9898
      protocol: TCP
  selector:
    app: podinfo

然后执行如下:

kubectl apply -n test -f podinfo.yaml

为了触发自动弹性伸缩,需要一个客户端来触发请求。在命名空间test中部署负载测试服务,将以下YAML内容存为文件loadtester.yaml 。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: loadtester
  namespace: test
  labels:
    app: loadtester
spec:
  selector:
    matchLabels:
      app: loadtester
  template:
    metadata:
      labels:
        app: loadtester
      annotations:
        prometheus.io/scrape: "true"
    spec:
      containers:
        - name: loadtester
          image: weaveworks/flagger-loadtester:0.18.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          command:
            - ./loadtester
            - -port=8080
            - -log-level=info
            - -timeout=1h
          livenessProbe:
            exec:
              command:
                - wget
                - --quiet
                - --tries=1
                - --timeout=4
                - --spider
                - http://localhost:8080/healthz
            timeoutSeconds: 5
          readinessProbe:
            exec:
              command:
                - wget
                - --quiet
                - --tries=1
                - --timeout=4
                - --spider
                - http://localhost:8080/healthz
            timeoutSeconds: 5
          resources:
            limits:
              memory: "512Mi"
              cpu: "1000m"
            requests:
              memory: "32Mi"
              cpu: "10m"
          securityContext:
            readOnlyRootFilesystem: true
            runAsUser: 10001
---
apiVersion: v1
kind: Service
metadata:
  name: loadtester
  namespace: test
  labels:
    app: loadtester
spec:
  type: ClusterIP
  selector:
    app: loadtester
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http

然后执行如下:

kubectl apply -n test -f loadtester.yaml

确认pod启动成功,执行kubectl get pod -n test,输出类似于以下结果:

NAME                          READY   STATUS    RESTARTS   AGE
loadtester-64df4846b9-nxhvv   2/2     Running   0          2m8s
podinfo-6d845cc8fc-26xbq      2/2     Running   0          11m

通过调用podinfo API验证安装。进入负载测试器容器,并使用hey几秒钟来生成负载:

export loadtester=$(kubectl -n test get pod -l "app=loadtester" -o jsonpath='{.items[0].metadata.name}')
kubectl -n test exec -it ${loadtester} -- hey -z 5s -c 10 -q 2 http://podinfo.test:9898

使用ASM指标配置HPA

定义一个HPA,该HPA将根据每秒接收的请求数来扩缩podinfo的工作负载数量。 当平均请求流量负载超过10 req / sec时,以下配置将指示HPA扩大部署。将以下YAML内容存为文件hpa.yaml。

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: podinfo
  namespace: test
  annotations:
    metric-config.external.prometheus-query.prometheus/processed-requests-per-second: |
      sum(
          rate(
              istio_requests_total{
                destination_workload="podinfo",
                destination_workload_namespace="test",
                reporter="destination"
              }[1m]
          )
      ) 
spec:
  maxReplicas: 10
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  metrics:
    - type: External
      external:
        metric:
          name: prometheus-query
          selector:
            matchLabels:
              query-name: processed-requests-per-second
        target:
          type: AverageValue
          averageValue: "10"

然后执行如下:

kubectl apply -f hpa.yaml

再次列出Prometheus适配器提供的自定义外部指标。

kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq .

输出类似于以下结果,应该返回包含自定义的ASM指标的资源列表:

{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "external.metrics.k8s.io/v1beta1",
  "resources": [
    {
      "name": "prometheus-query",
      "singularName": "",
      "namespaced": true,
      "kind": "ExternalMetricValueList",
      "verbs": [
        "get"
      ]
    }
  ]
}

自动弹性伸缩

进入测试器容器并用于hey生成工作负载请求,运行几分钟:

kubectl -n test exec -it ${loadtester} -- sh
~ $ hey -z 5m -c 10 -q 5 http://podinfo.test:9898

一分钟后,HPA将开始扩大工作负载,直到请求/秒降至目标值以下。负载测试完成后,每秒的请求数将降为零,并且HPA将开始缩减工作负载Pod数量,几分钟后副本数将恢复为一个。

执行如下命令可以观察到自动伸缩

输入以下命令:

watch kubectl -n test get hpa/podinfo

系统输出类似如下结果:

NAME      REFERENCE            TARGETS          MINPODS   MAXPODS   REPLICAS   AGE
podinfo   Deployment/podinfo   8308m/10 (avg)   1         10        6          124m

默认情况下,指标每30秒执行一次同步,并且只有在最近3-5分钟内没有重新缩放时,才可以进行放大/缩小。这样,HPA可以防止冲突决策的快速执行,并为集群自动扩展程序腾出时间。