И снова здравствуйте! Я – Евгений Симигин, занимаюсь внедрением DevOps-практик в Центре компетенций по разработке облачных и интернет-решений МТС Digital. Как я и обещал – это вторая часть нашего обзора Argo Rollouts, из которой вы узнаете о том, как в процессе выкатки нового релиза организовать еще и тестирование. Первая часть статьи – по ссылке.

Рассмотренные в первой половине статьи механизмы послужат нам существенным подспорьем, однако итоговое решение – прошло все успешно или нет – принимается на основе звонков от разгневанных пользователей алертов от системы мониторинга. Тут на помощь нам спешат analysis и experiments.Они позволят вклиниться в шаги выкатки и выставить критерии для принятия решения: идем мы дальше или откатываем ревизию.

Прежде всего мы задаем «правила игры» – то, что мы будем анализировать и критерии «что такое хорошо, что такое плохо». Для этого служат AnalysisTemplate ClusterAnalysisTemplate

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: error-rate
spec:
  args:
  - name: service-name
  - name: api-token
    valueFrom:
      secretKeyRef:
        name: token-secret
        key: apiToken
  - name: api-url
    value: http://example/measure
  - name: host
  metrics:
  - name: error-rate
    interval: 5m
    successCondition: result[0] <= 0.95
    failureLimit: 3
    provider:
      prometheus:
        address: http://prometheus.example.com:9090
        query: |
          sum(irate(
            istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code=~"5.*"}[5m]
          )) /
          sum(irate(
            istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m]
          ))
  - name: webmetric
    successCondition: result == 'true'
    provider:
      web:
        # placeholders are resolved when an AnalysisRun is created
        url: "{{ args.api-url }}?service={{ args.service-name }}"
        headers:
          - key: Authorization
            value: "Bearer {{ args.api-token }}"
        jsonPath: "{$.results.ok}"
  - name: http-benchmark
    failureLimit: 100
    interval: 5s
    provider:
      job:
        spec:
          template:
            spec:
              containers:
              - name: load-tester
                image: argoproj/load-tester:latest
                command: [sh, -xec]
                args:
                - |
                  wrk -t1 -c1 -d5 -s report.lua http://{{args.host}}/color
                  jq -e '.errors_ratio <= 0.05' report.json
              restartPolicy: Never
          backoffLimit: 0

Прошу прощения за огромный шаблон (его пришлось склеить из трех), но это сделано для максимальной наглядности.

  1. args: – можно задать переменные для шаблона и при запуске экземпляра анализа их можно переопределить. Значения можно взять из секретов

  2. metrics: – массив тестов. Метрики поддерживают разные провайдеры. В примере показаны istio, web и job.

  3. Если что-то не прошло нашу проверку – откатываем изменения (поведение можно менять)

  4. Для метрики http-benchmark запускается отдельный контейнер (по ссылке вы можете посмотреть файл report.lua, который формирует структуру отчета). Провайдер job позволяет нам подпихнуть любой контейнер с нашими автотестами и прогнать их без переключение реального трафика (для этого используется experiment).

  5. Шаблонизация очень удобна, если ваши сервисы поставляются разными командами: так вы задаете единые критерии для всех. Особенно это помогает с теми, у кого нет времени на написание автотестов.

Подключаем шаблон к Rollout. Сначала рассмотрим поведение при canary-стратегии:

  strategy:
    canary:
      analysis:
        templates:
        - templateName: error-rate # имя шаблона
        - templateName: Mytemplate # можно подключать несколько шаблонов сразу
          clusterScope: true #это служит для подключения ClusterAnalysisTemplate
        startingStep: 2 # Ждём когда rollout дойдёт до 2 шага (40%) и стартуем
        args:
        - name: service-name
          value: guestbook-svc.default.svc.cluster.local
      steps:
      - setWeight: 20
      - pause: {duration: 10m}
      - setWeight: 40
      - pause: {duration: 10m}
      - setWeight: 60
      - pause: {duration: 10m}
      - setWeight: 80
      - pause: {duration: 10m}

В данном манифесте показан пример анализа в фоне: Rollout потихоньку подменяет поды, а инстанс анализа делает своё дело до тех пор, пока не завершится накат новой ревизии или же возникнет ошибка и тогда нас ждёт Rollback. Есть другой вариант применения – Inline Analysis. Он позволяет нам вклиниться в steps, анализироваться столько сколько хочется и только потом переходить на другие шаги.

      steps:
      - setWeight: 20
      - pause: {duration: 5m}
      - analysis:
          templates:
          - templateName: success-rate
          args:
          - name: service-name
            value: guestbook-svc.default.svc.cluster.local

Тут будет необходим тюнинг шаблона анализа, без interval и count ваш тест выполнится один раз

  metrics:
  - name: success-rate
    successCondition: result[0] >= 0.95
    interval: 60s
    count: 5
    provider:
      prometheus:
        address: http://prometheus.example.com:9090
        query: ...

Тут уже в зависимости от количества новых подов, мы можем выбрать различные наборы и продолжительность наших тестов. А теперь рассмотрим BlueGreen – стратегию: в данном случае мы можем проводить проверку до переключения трафика и после:

  strategy:
    blueGreen:
      activeService: active-svc
      previewService: preview-svc
      prePromotionAnalysis: # прогоняем тесты и переключем
        templates:
        - templateName: smoke-tests
#...или...
      scaleDownDelaySeconds: 600 # сколько живёт текущая реплика после переключения
      postPromotionAnalysis: # прогоняем тесты после переключения
        templates:
        - templateName: smoke-tests

В первом случае контроллер просто не переключит трафик, во втором будет производиться откат, поэтому рекомендуется увеличить (в разумных пределах) scaleDownDelaySeconds, чтобы контроллер не уничтожал сразу предыдущую реплику. В случае проблем он быстро перекинет лейблы и мы не будем ожидать старта контейнеров.

Как всегда есть исключения из правил: метрика вроде бы есть, но если не выполняется – давайте не будем из-за нее останавливать релиз. Для этого служит механизм dry-run: можно пометить метрики и выкатка не зафейлится, если все плохо.

Пример dry-run метрик
#В манифесте AnalysisTemplate
...
  dryRun:
  - metricName: .*
  metrics:
  - name: total-5xx-errors
# в манифестах rollout
...
  steps:
  - analysis:
      templates:
      - templateName: random-fail
      - templateName: always-pass
      dryRun:
      - metricName: .*

Еще есть состояние inconclusive или, говоря по-русски, «нипанятна» – состояние, когда заданы success и failed границы, но мы не попали. В таком случае Rollout встает на паузу и ждет ручного вмешательства. На мой взгляд, лучше не допускать дырки в диапазонах и однозначно задавать критерии.

  metrics:
  - name: success-rate
    successCondition: result[0] >= 0.90
    failureCondition: result[0] < 0.50

Мы рассмотрели Analysis, который позволял нам подстелить соломку в случае плохого релиза, а теперь пора перейти к целому «стогу» – experiments. Согласно документации, в первом случае мы переключаем реальный клиентский трафик (хотя в случае BlueGreen prePromotionAnalysis ничего не переключается). Experiments позволяет нам развернуть произвольное количество replicaset в с разными контейнерами, прогнать по ним тесты, сравнить и принять решение о судьбе релиза.

длинная портянка из документации
apiVersion: argoproj.io/v1alpha1
kind: Experiment
metadata:
  name: example-experiment
spec:
  # Duration of the experiment, beginning from when all ReplicaSets became healthy (optional)
  # If omitted, will run indefinitely until terminated, or until all analyses which were marked
  # `requiredForCompletion` have completed.
  duration: 20m

  # Deadline in seconds in which a ReplicaSet should make progress towards becoming available.
  # If exceeded, the Experiment will fail.
  progressDeadlineSeconds: 30

  # List of pod template specs to run in the experiment as ReplicaSets
  templates:
  - name: purple
    # Number of replicas to run (optional). If omitted, will run a single replica
    replicas: 1
    selector:
      matchLabels:
        app: canary-demo
        color: purple
    template:
      metadata:
        labels:
          app: canary-demo
          color: purple
      spec:
        containers:
        - name: rollouts-demo
          image: argoproj/rollouts-demo:purple
          imagePullPolicy: Always
          ports:
          - name: http
            containerPort: 8080
            protocol: TCP
  - name: orange
    replicas: 1
    minReadySeconds: 10
    selector:
      matchLabels:
        app: canary-demo
        color: orange
    template:
      metadata:
        labels:
          app: canary-demo
          color: orange
      spec:
        containers:
        - name: rollouts-demo
          image: argoproj/rollouts-demo:orange
          imagePullPolicy: Always
          ports:
          - name: http
            containerPort: 8080
            protocol: TCP

  # List of AnalysisTemplate references to perform during the experiment
  analyses:
  - name: purple
    templateName: http-benchmark
    args:
    - name: host
      value: purple
  - name: orange
    templateName: http-benchmark
    args:
    - name: host
      value: orange
  - name: compare-results
    templateName: compare
    # If requiredForCompletion is true for an analysis reference, the Experiment will not complete
    # until this analysis has completed.
    requiredForCompletion: true
    args:
    - name: host
      value: purple

Также можно запускать автономные эксперименты не привязанных к процессу выкатки. Это может быть востребовано у тестировщиков – запустил/забыл/вспомнил/сравнил. Вот так можно подглядывать за экспериментом:

Способ подключения из rollout похож на AnalysisTemplate:

  strategy:
    canary: 
      steps:
      - experiment:
          duration: 1h
          templates:
          - name: baseline
            specRef: stable
          - name: canary
            specRef: canary
          analyses:
          - name : mann-whitney
            templateName: mann-whitney
            args:
            - name: baseline-hash
              value: "{{templates.baseline.podTemplateHash}}"
            - name: canary-hash
              value: "{{templates.canary.podTemplateHash}}"

Можно сделать еще интереснее: развернуть N-экспериментальных replicaset, переключить какой-то процент боевого трафика на них и посмотреть, что из этого получится (работает только на SMI, ALB, Istio):

    steps:
      - experiment:
          duration: 1h
          templates:
            - name: experiment-baseline
              specRef: stable
              weight: 5
            - name: experiment-canary
              specRef: canary
              weight: 5

Есть еще удобная функция – не писать весь spec в манифесте rollout, а прицепиться к существующему deployment:

# манифест rollout
spec:
  replicas: 5
  selector:
    matchLabels:
      app: rollout-ref-deployment
  workloadRef:
    apiVersion: apps/v1
    kind: Deployment
    name: rollout-ref-deployment
  strategy:
#...

Обратите внимание, что rollout не управляет количеством реплик deployment. После наката, у вас будет задвоение (но это позволяет избежать простоя) и если все прошло успешно, вы уменьшаете число реплик у deployment до нуля. С этого момента deployment служит по сути шаблоном spec: все дальнейшие изменения вы делаете в нем (но число реплик всегда должно быть 0), а rollout контроллер отслеживает изменения и порождает replicaset.

Я собрал для вас основные выдержки из документации, которые интересны для специалистов по автоматизации и они помогут вам быстро вникнуть в решение и начать применять его на практике. Примеры из документации можете посмотреть здесь и здесь (тут встречаются старые апи), есть гайд по миграции.

Если у вас есть замечания, благодарности, или вы хотите поделиться опытом – добро пожаловать в комментарии.

Всем удачных релизов!

Комментарии (0)