Стадия жизненного цикла модуля: General Availability

Circuit Breaker

Для выявления проблемных эндпоинтов используются настройки outlierDetection в кастомном ресурсе DestinationRule. Более подробно алгоритм Outlier Detection описан в документации Envoy.

Пример:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: reviews-cb-policy
spec:
  host: reviews.prod.svc.cluster.local
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100 # Максимальное число соединений в сторону host, суммарно для всех эндпоинтов.
      http:
        maxRequestsPerConnection: 10 # Каждые 10 запросов, соединение будет пересоздаваться.
    outlierDetection:
      consecutive5xxErrors: 7 # Допустимо 7 ошибок (включая пятисотые, TCP-таймауты и HTTP-таймауты)
      interval: 5m            # в течение пяти минут,
      baseEjectionTime: 15m   # после которых эндпоинт будет исключен из балансировки на 15 минут.

А также для настройки HTTP-таймаутов используется ресурс VirtualService. Эти таймауты также учитываются при подсчете статистики ошибок на эндпоинтах.

Пример:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: my-productpage-rule
  namespace: myns
spec:
  hosts:
  - productpage
  http:
  - timeout: 5s
    route:
    - destination:
        host: productpage

Балансировка gRPC

Чтобы балансировка gRPC-сервисов заработала автоматически, присвойте имя с префиксом или значением grpc для порта в соответствующем сервисе.

Locality Failover

При необходимости ознакомьтесь с основной документацией.

Istio позволяет настроить приоритетный географический failover между эндпоинтами. Для определения зоны Istio использует лейблы узлов с соответствующей иерархией:

  • topology.istio.io/subzone;
  • topology.kubernetes.io/zone;
  • topology.kubernetes.io/region.

Это полезно для межкластерного failover при использовании совместно с мультикластером.

Для включения Locality Failover используется ресурс DestinationRule, в котором также необходимо настроить outlierDetection.

Пример:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: helloworld
spec:
  host: helloworld
  trafficPolicy:
    loadBalancer:
      localityLbSetting:
        enabled: true # Включили Locality Failover.
    outlierDetection: # outlierDetection включить обязательно.
      consecutive5xxErrors: 1
      interval: 1s
      baseEjectionTime: 1m

Retry

С помощью ресурса VirtualService можно настроить Retry для запросов.

По умолчанию при возникновении ошибок все запросы (включая POST-запросы) выполняются повторно до трех раз.

Пример:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: ratings-route
spec:
  hosts:
  - ratings.prod.svc.cluster.local
  http:
  - route:
    - destination:
        host: ratings.prod.svc.cluster.local
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: gateway-error,connect-failure,refused-stream

Canary

Istio отвечает лишь за гибкую маршрутизацию запросов, которая опирается на спецзаголовки запросов (например, cookie) или просто на случайность. За настройку этой маршрутизации и «переключение» между канареечными версиями отвечает CI/CD-система.

Подразумевается, что в одном неймспейсе развёрнуты два Deployment с разными версиями приложения. У подов разных версий разные лейблы (version: v1 и version: v2).

Требуется настроить два кастомных ресурса:

  • DestinationRule с описанием, как идентифицировать разные версии вашего приложения (subset);
  • VirtualService с описанием, как распределять трафик между разными версиями приложения.

Пример:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: productpage-canary
spec:
  host: productpage
  # subset доступны только при обращении к хосту через VirtualService из пода под управлением Istio.
  # Эти subset должны быть указаны в маршрутах.
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: productpage-canary
spec:
  hosts:
  - productpage
  http:
  - match:
    - headers:
       cookie:
         regex: "^(.*;?)?(canary=yes)(;.*)?"
    route:
    - destination:
        host: productpage
        subset: v2 # Ссылка на subset из DestinationRule.
  - route:
    - destination:
        host: productpage
        subset: v1

Распределение по вероятности

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: productpage-canary
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: productpage
        subset: v1 # Ссылка на subset из DestinationRule.
      weight: 90 # Процент трафика, который получат поды с лейблом version: v1.
  - route:
    - destination:
        host: productpage
        subset: v2
      weight: 10

Ingress для публикации приложений

Istio Ingress Gateway

Пример:

apiVersion: deckhouse.io/v1alpha1
kind: IngressIstioController
metadata:
 name: main
spec:
  # ingressGatewayClass содержит значение селектора лейблов, используемое при создании ресурса Gateway.
  ingressGatewayClass: istio-hp
  inlet: HostPort
  hostPort:
    httpPort: 80
    httpsPort: 443
  nodeSelector:
    node-role.deckhouse.io/frontend: ""
  tolerations:
    - effect: NoExecute
      key: dedicated.deckhouse.io
      operator: Equal
      value: frontend
  resourcesRequests:
    mode: VPA
apiVersion: v1
kind: Secret
metadata:
  name: app-tls-secret
  namespace: d8-ingress-istio # Обратите внимание, неймспейс не является app-ns.
type: kubernetes.io/tls
data:
  tls.crt: |
    <tls.crt data>
  tls.key: |
    <tls.key data>
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: gateway-app
  namespace: app-ns
spec:
  selector:
    # Селектор лейблов для использования Istio Ingress Gateway main-hp.
    istio.deckhouse.io/ingress-gateway-class: istio-hp
  servers:
    - port:
        # Стандартный шаблон для использования протокола HTTP.
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - app.example.com
    - port:
        # Стандартный шаблон для использования протокола HTTPS.
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        # Ресурс Secret с сертификатом и ключом, который должен быть создан в нейсмпейсе d8-ingress-istio.
        # Поддерживаемые форматы ресурсов Secret можно посмотреть по ссылке https://istio.io/latest/docs/tasks/traffic-management/ingress/secure-ingress/#key-formats.
        credentialName: app-tls-secret
      hosts:
        - app.example.com
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs-app
  namespace: app-ns
spec:
  gateways:
    - gateway-app
  hosts:
    - app.example.com
  http:
    - route:
        - destination:
            host: app-svc

Ingress NGINX

Для работы с Ingress NGINX требуется подготовить:

  • Ingress-контроллер, добавив к нему сайдкар от Istio. В нашем случае — включить параметр enableIstioSidecar в кастомном ресурсе IngressNginxController модуля ingress-nginx.
  • Ingress-ресурс, который ссылается на Service. Обязательные аннотации для Ingress-ресурса:
    • nginx.ingress.kubernetes.io/service-upstream: "true" — с этой аннотацией Ingress-контроллер будет отправлять запросы на ClusterIP сервиса (из диапазона Service CIDR) вместо того, чтобы слать их напрямую в поды приложения. Sidecar-контейнер istio-proxy перехватывает трафик только в сторону диапазона Service CIDR, остальные запросы отправляются напрямую;
    • nginx.ingress.kubernetes.io/upstream-vhost: myservice.myns.svc — с данной аннотацией сайдкар сможет идентифицировать прикладной сервис, для которого предназначен запрос.

Примеры:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: productpage
  namespace: bookinfo
  annotations:
    # Просим nginx проксировать трафик на ClusterIP вместо собственных IP подов.
    nginx.ingress.kubernetes.io/service-upstream: "true"
    # В Istio вся маршрутизация осуществляется на основе `Host:` заголовка запросов.
    # Чтобы не сообщать Istio о существовании внешнего домена `productpage.example.com`,
    # мы просто используем внутренний домен, о котором Istio осведомлен.
    nginx.ingress.kubernetes.io/upstream-vhost: productpage.bookinfo.svc
spec:
  rules:
    - host: productpage.example.com
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: productpage
              port:
                number: 9080
apiVersion: v1
kind: Service
metadata:
  name: productpage
  namespace: bookinfo
spec:
  ports:
  - name: http
    port: 9080
  selector:
    app: productpage
  type: ClusterIP

Примеры настройки авторизации

Алгоритм принятия решения

Как только для приложения создается AuthorizationPolicy, начинает работать следующий алгоритм принятия решения о судьбе запроса:

  • Если запрос попадает под политику DENY — запретить запрос.
  • Если для данного приложения нет политик ALLOW — разрешить запрос.
  • Если запрос попадает под политику ALLOW — разрешить запрос.
  • Все остальные запросы — запретить.

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

Для работы политик, основанных на высокоуровневых параметрах, таких как неймспейсы или principal, необходимо, чтобы все вовлеченные сервисы работали под управлением Istio. Также между приложениями должен быть организован Mutual TLS.

Примеры:

  • Запретим POST-запросы для приложения myapp. Отныне, так как для приложения появилась политика, согласно алгоритму выше будут запрещены только POST-запросы к приложению.

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: deny-post-requests
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: myapp
      action: DENY
      rules:
      - to:
        - operation:
            methods: ["POST"]
    
  • Для приложения создана разрешающая политика ALLOW. При ней будут разрешены только запросы из неймспейса bar, остальные запрещены.

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: deny-all
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: myapp
      action: ALLOW # default, можно не указывать.
      rules:
      - from:
        - source:
            namespaces: ["bar"]
    
  • Здесь для приложения создана политика ALLOW. При этом она не содержит ни одного правила, вследствие чего ни один запрос не попадает под критерии фильтрации. Однако сам факт наличия политики активирует алгоритм проверки, где всё, что не разрешено явно, считается запрещенным. В данном случае блокируются абсолютно все запросы

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: deny-all
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: myapp
      action: ALLOW # default, можно не указывать.
      rules: []
    
  • Здесь для приложения создана политика ALLOW (это default) и одно пустое правило. Под это правило попадает любой запрос и автоматически получает разрешение.

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: allow-all
      namespace: foo
    spec:
      selector:
        matchLabels:
          app: myapp
      rules:
      - {}
    

Запретить все действия в рамках неймспейса foo

Два способа:

  • Запретить явно. Здесь мы создаем политику DENY с единственным универсальным фильтром {}, под который попадают все запросы:

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: deny-all
      namespace: foo
    spec:
      action: DENY
      rules:
      - {}
    
  • Неявно. Здесь мы создаем политику ALLOW (по умолчанию), но не создаем ни одного фильтра, так что ни один запрос под нее не попадет и будет автоматически запрещен.

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: deny-all
      namespace: foo
    spec: {}
    

Запретить доступ только из неймспейса foo

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: deny-from-ns-foo
 namespace: myns
spec:
 action: DENY
 rules:
 - from:
   - source:
       namespaces: ["foo"]

Разрешить запросы только в рамках нашего неймспейса foo

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: allow-intra-namespace-only
 namespace: foo
spec:
 action: ALLOW
 rules:
 - from:
   - source:
       namespaces: ["foo"]

Разрешить из любого места в нашем кластере

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: allow-all-from-my-cluster
 namespace: myns
spec:
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["mycluster.local/*"]

Разрешить любые запросы только кластеров foo или bar

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: allow-all-from-foo-or-bar-clusters-to-ns-baz
 namespace: baz
spec:
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["foo.local/*", "bar.local/*"]

Разрешить любые запросы только от сущностей из неймспейса baz кластеров foo или bar

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: allow-all-from-foo-or-bar-clusters-to-ns-baz
 namespace: baz
spec:
 action: ALLOW
 rules:
 - from:
   - source: # Правила ниже логически перемножаются.
       namespaces: ["baz"]
       principals: ["foo.local/*", "bar.local/*"]

Разрешить из любого кластера (по mTLS)

Если есть запрещающие правила, у них будет приоритет. Подробнее можно посмотреть алгоритм.

Пример:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: allow-all-from-any-cluster-with-mtls
 namespace: myns
spec:
 action: ALLOW
 rules:
 - from:
   - source:
       principals: ["*"] # To set mTLS mandatory.

Разрешить вообще откуда угодно (в том числе без mTLS)

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
 name: allow-all-from-any
 namespace: myns
spec:
 action: ALLOW
 rules: [{}]

Устройство федерации из двух кластеров с помощью кастомного ресурса IstioFederation

Доступно в редакциях Enterprise Edition и Certified Security Edition Pro.

Cluster A:

apiVersion: deckhouse.io/v1alpha1
kind: IstioFederation
metadata:
  name: cluster-b
spec:
  metadataEndpoint: https://istio.k8s-b.example.com/metadata/
  trustDomain: cluster-b.local

Cluster B:

apiVersion: deckhouse.io/v1alpha1
kind: IstioFederation
metadata:
  name: cluster-a
spec:
  metadataEndpoint: https://istio.k8s-a.example.com/metadata/
  trustDomain: cluster-a.local

Устройство мультикластера из двух кластеров с помощью ресурса IstioMulticluster

Доступно только в редакции Enterprise Edition.

Cluster A:

apiVersion: deckhouse.io/v1alpha1
kind: IstioMulticluster
metadata:
  name: cluster-b
spec:
  metadataEndpoint: https://istio.k8s-b.example.com/metadata/

Cluster B:

apiVersion: deckhouse.io/v1alpha1
kind: IstioMulticluster
metadata:
  name: cluster-a
spec:
  metadataEndpoint: https://istio.k8s-a.example.com/metadata/

Управление поведением data plane

Предотвращение завершения работы istio-proxy до завершения соединений основного приложения

По умолчанию в процессе остановки пода все контейнеры, включая istio-proxy, получают сигнал SIGTERM одновременно. Но некоторым приложениям для правильного завершения работы необходимо время и иногда дополнительная сетевая активность. Это невозможно, если istio-proxy завершился раньше.

Решение — добавить в istio-proxy preStop-хук для оценки активности прикладных контейнеров, а единственный доступный метод — это выявление сетевых сокетов приложения, и если таковых нет, тогда можно останавливать контейнер.

Аннотация ниже добавляет описанный выше preStop-хук в контейнер istio-proxy прикладного пода:

annotations:
  inject.istio.io/templates: "sidecar,d8-hold-istio-proxy-termination-until-application-stops"

Ограничения режима перенаправления прикладного трафика CNIPlugin

В отличие от режима InitContainer, настройка перенаправления осуществляется в момент создании пода, а не в момент срабатывания init-контейнера istio-init. Это значит, что прикладные init-контейнеры не смогут взаимодействовать с остальными сервисами так как весь трафик будет перенаправлен на обработку в sidecar-контейнер istio-proxy, который ещё не запущен. Обходные пути:

  • Запустить прикладной init-контейнер от пользователя с uid 1337. Запросы данного пользователя не перехватываются под управление Istio.
  • Исключить IP-адрес или порт сервиса из-под контроля Istio с помощью аннотаций traffic.sidecar.istio.io/excludeOutboundIPRanges или traffic.sidecar.istio.io/excludeOutboundPorts.

Каждый из обходных вариантов выводит трафик из-под контроля Istio, что в свою очередь убирает шифрование трафика между прикладными сервисами.

Обновление Istio

Обновление control plane Istio

  • Deckhouse Kubernetes Platform позволяет установить несколько версий control plane одновременно:
    • Одна глобальная, обслуживает неймспейсы или поды без явного указания версии (лейбл у неймспейсов istio-injection: enabled). Настраивается параметром globalVersion.
    • Остальные — дополнительные, обслуживают неймспейсы или поды с явным указанием версии (лейбл у неймспейса или пода istio.io/rev: v1x21). Настраиваются параметром additionalVersions.
  • Istio заявляет обратную совместимость между data plane и control plane в диапазоне двух минорных версий: Istio data-plane and control-plane compatibility
  • Алгоритм обновления (для примера, с версии 1.21 на версию 1.25):
    • Добавить желаемую версию в параметр модуля additionalVersions (additionalVersions: ["1.25"]).
  • Дождаться появления соответствующего пода istiod-v1x25-xxx-yyy в неймспейсе d8-istio.
  • Для каждого прикладного неймспейса, где включен Istio:
    • поменять лейбл istio-injection: enabled на istio.io/rev: v1x25;
    • по очереди пересоздать поды в неймспейсе, параллельно контролируя работоспособность приложения.
  • Поменять настройку globalVersion на 1.25 и удалить additionalVersions.
  • Убедиться, что старый под istiod удалился.
  • Поменять лейблы прикладных неймспейсов на istio-injection: enabled.

Чтобы найти все поды под управлением старой ревизии Istio (в примере — версия 21), выполните команду:

d8 k get pods -A -o json | jq --arg revision "v1x21" \
  '.items[] | select(.metadata.annotations."sidecar.istio.io/status" // "{}" | fromjson |
   .revision == $revision) | .metadata.namespace + "/" + .metadata.name'

Обновление до версии Istio 1.25 возможно только с версии 1.21.

Автоматическое обновление data plane Istio

Доступно в редакциях Enterprise Edition и Certified Security Edition Pro.

Для автоматизации обновления istio-сайдкаров установите лейбл istio.deckhouse.io/auto-upgrade="true" на Namespace либо на отдельный ресурс — Deployment, DaemonSet или StatefulSet.

Настройка ресурсов istio-proxy sidecar

Для переопределения глобальных ограничений ресурсов для сайдкара istio-proxy в отдельных рабочих нагрузках используются аннотации. Поддерживаются следующие аннотации:

Поддерживаемые аннотации

Аннотация Описание Пример значения
sidecar.istio.io/proxyCPU Запрос CPU для сайдкара 200m
sidecar.istio.io/proxyCPULimit Лимит CPU для сайдкара "1"
sidecar.istio.io/proxyMemory Запрос памяти для сайдкара 128Mi
sidecar.istio.io/proxyMemoryLimit Лимит памяти для сайдкара 512Mi

Примеры конфигурации

Для Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
# ...
spec:
  template:
    metadata:
      annotations:
          sidecar.istio.io/proxyCPU: 200m
          sidecar.istio.io/proxyCPULimit: "1"
          sidecar.istio.io/proxyMemory: 128Mi
          sidecar.istio.io/proxyMemoryLimit: 512Mi
# ... остальная часть манифеста

Для ReplicaSet:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
# ...
spec:
  template:
    metadata:
      annotations:
          sidecar.istio.io/proxyCPU: 200m
          sidecar.istio.io/proxyCPULimit: "1"
          sidecar.istio.io/proxyMemory: 128Mi
          sidecar.istio.io/proxyMemoryLimit: 512Mi
# ... остальная часть манифеста

Для Pod:

apiVersion: v1
kind: Pod
metadata:
  annotations:
    sidecar.istio.io/proxyCPU: 200m
    sidecar.istio.io/proxyCPULimit: "1"
    sidecar.istio.io/proxyMemory: 128Mi
    sidecar.istio.io/proxyMemoryLimit: 512Mi
# ... остальная часть манифеста

Все четыре параметра должны быть указаны вместе — sidecar.istio.io/proxyCPU, sidecar.istio.io/proxyCPULimit, sidecar.istio.io/proxyMemory и sidecar.istio.io/proxyMemoryLimit. Частичная конфигурация не поддерживается.