Istio: обзор и запуск service mesh в Kubernetes

Автор: | 03/24/2021
 

Istio- одна из реализацией концепии Service Mesh, позволяющая реализовать Service Discovery, Load Balancing, контроль над трафиком, canary rollouts и blue-green deployments, мониторить трафик между приложениями.

Мы будем использовать Istio в AWS Elastic Kubernetes Service для мониторинга трафика, в роли API gateway, разграничения трафика и, возможно, для реализации различных deployment strategies.

В этом посте рассмотрим что такое Service mesh вообще, общую архитектуру и компоненты Istio, его установку и настройку тестового приложения.

Перед тем, как рассматривать Istio — давайте разберёмся, что такое Service Mesh.

Service Mesh

По сути, это менеджер прокси-сервисов, таких как NGINX, HAProxy или Envoy, система, работающая на Layer 7 модели OSI и позволяющая динамически управлять трафиком и настраивать коммуникацию между приложениями.

Service mesh занимается обнаружением новых сервисов/приложений, балансировкой нагрузки, аутентификацией и шифрованием трафика.

Для контроля трафика в service mesh для каждого приложения, или в случае с Kubernetes — для каждого пода, запускается прокси-сервис называемый sidecar, и эти sidecar-сервисы проксируют и управляют трафиком.

Вместе эти sidecar-контейнеры представляют собой Data Plane.

Для их конфигурирования и управления существует другая группа процессов или сервисов, называемых Control Plane. Они занимаются обнаружением новых приложений, обеспечивают управление ключами шифрования, сбором метрик и т.д.

Схематично сервис меш можно отобразить так:

Среди Service mesh решений можно выделить:

Почитать по теме:

Архитектура Istio

Итак, Istio как service mesh состоит из двух основных частей — Data plane и Control plane:

  • Data plane («уровень данных»): состоит из набора прокси-сервисов, sidecar-контейнеров в каждом поде, для которых использует расширенную версию прокси-сервера Envoy. Эти сайдакры связывают и контролируют трафик между приложениями, собирают и отправляют метрики
  • Control plane («уровень управления»): управляет и настраивает сайдкары, управляет метриками, TLS-сертификатами

В целом схема Istio выглядит так:

Или другая схема:

Control Plane

Istio включает в себя четыре основных компонента:

  • Pilot: центральный контроллер, отвечающий за коммуникацию с сайдкарами используя Envoy API. Он считывает правила, описанные в манифестах istio, и отправляет их в Envoy для настройки прокси-сервисов. Отвечает за service discovery, управление трафиком, роутинг, устойчивостью сети с помощью таймаутов и circuit breaking.
  • Citadel: Identity and Access management — шифрование трафика, аутентификация сервисов и пользователей, управление ключами шифрования. См. Istio Security.
  • Galley: управление конфигурацией — валдиация, обработка новых настроек, отправка их остальным компонентам системы
  • Mixer: мониторинг, метрики, логи, контроль трафика

В версии 1.9.1 все они, кроме Pilot, который работает в отдельном Docker-контейнере, объединены в отдельный бинарный файл istiod, плюс Ingress и Egress Controllers — контроллеры входящего и исходящего трафика

Data Plane

Состоит из sidecar-контейнеров, которые в Kubernetes запускаются внутри подов через kube-inject, см. Installing the Sidecar.

Контейнеры являются инстансами Envoy-прокси, которые позволяют:

Почитать по теме:

Модель сети Istio

Перед тем, как приступать к запуску и настройке приложения и Istio — кратко рассмотрим ресурсы, которые участвуют в управлении трафиком.

При установке Istio создаёт ресурс Ingress Gateway (и Egress Gateway, если было определено при установке) — новый объект в Kubernetes, который описывается в Kubernetes CRD при установке Istio.

В AWS при настройках по-умолчанию Ingress Gateway создаёт AWS Classic LoadBalancer, т.к. Ingress Gateway является Kubernetes-объектом Ingress с типом LoadBalancer.

«За» Ingress Gateway создаётся ресурс Gateway, который также описывается в Kubernetes CRD во время установки Istio и описывает хосты и порты, на которые будет отправляться трафик через этот Gateway.

Далее следует ещё один ресурс — VirtualService, который описывает маршрутизацию трафика, прошёдшего через Gateway, и отправляет его на Kubernetes Services следуя заданным Destination rules.

Ingress Gateway состоит из двух компонентов — Kubernetes Pod с инстансом Envoy, который управляет входящим трафиком, и Kubernetes Ingress, который принимает подключения.

В свою очередь Gateway и VirtualService управляют конфигурацией Envoy, который является Ingress Gateway controller.

Т.е. в целом схема прохождения пакета выглядит так:

  1. пакет попадает на порт внешнего LoadBalancer, который шлёт его на порт Kubernetes WorkerNode
  2. там пакет попадает к Istio IngressGateway Service
  3. перенаправляется на Istio IngressGateway Pod
  4. этот под настраивается через Gateway и VirtualService
    1. Gateway описывает порты, протоколы, SSL-сертификаты
    2. VirtualService описывает роутинг пакета к нужному Kubernetes Service нашего приложения
  5. Istio IngressGateway Pod отправляет пакет на Service приложения
  6. сервис приложения отправляет пакет на под с приложением

Почитать по теме:

Запуск Istio в Kubernetes

Istio поддерживает различные Deployment models. В нашем случае используется один EKS-кластер, а поды в нём работают в общей VPC сети.

Кроме того, Istio поддерживает различные Config Profiles, из которых нам может быть интересен default — установка istiod и istio-ingressgateway, demo — аналогично, но с запуском istio-egressgateway и preview — для исследования новых возможностей, не включенных пока в основной релиз Istio.

Установить Istio можно разными способами — с помощью istioctl из манифест-файлов, из Helm-чарта, либо с помощью Ansible.

Также, стоит обратить внимание на Mutual TLS (mTLS) — см. Permissive mode. Если кратко, то по-умолчанию Istio устанавливается в Permissive mode, что позволяет уже существующим приложениям продолжать коммуникацию используя plaintext-трафик. При этом новые подключения через Envoy-контейнеры, sidecars, уже будут выполняться с TLS-шированием.

Пока выполним установку руками, а на Дев и Прод кластера Кубера скорее всего будем инсталить через Helm.

См. Install with Istioctl и Installation Guides.

Загружаем Istio:

curl -L https://istio.io/downloadIstio | sh -
cd istio-1.9.1/

istioctl использует файл ~/.kube/config, который можно переопределить с помощью --kubeconfig или указать нужный контекст через --context.

В загруженном только что каталоге он находится в папке bin — добавляем его в $PATH:

export PATH=$PWD/bin:$PATH

Проверяем:

istioctl version
no running Istio pods in "istio-system"
1.9.1

Генерируем kubeconfig для нового тестового кластера (для AWS EKS, если используется Minikube — он при старте сгенерирует конфиг сам):

aws eks update-kubeconfig --name bttrm-eks-test-1-18 --alias iam-bttrm-eks-root-role-kubectl@bttrm-eks-test-1-18
Added new context iam-bttrm-eks-root-role-kubectl@bttrm-eks-test-1-18 to /home/setevoy/.kube/config

Устанавливаем Istio с профилем default:

istioctl install --set profile=default -y
✔ Istio core installed
✔ Istiod installed
✔ Ingress gateways installed
✔ Installation complete

Проверяем версию ещё раз:

istioctl version
client version: 1.9.1
control plane version: 1.9.1
data plane version: 1.9.1 (1 proxies)

Поды Istio:

kubectl -n istio-system get pod
NAME                                   READY   STATUS    RESTARTS   AGE
istio-ingressgateway-d45fb4b48-jsz9z   1/1     Running   0          64s
istiod-7475457497-6xskm                1/1     Running   0          77s

Далее задеплоим тестовое приложение, и настроим роутинг через Istio Ingress Gateway.

Тестовое приложение

Не будем использовать Bookinfo приложение из Istio Gettings Started, а напишем свой велосипед создадим свой Namespace, Deployment с одним подом с NGINX, и к нему Service — эмулируем миграцию уже имеющихся у нас приложений под управление Istio.

Кроме того, сейчас специально не будем настраваить добавление sidecar-контейнеров в приложение — вернёмся к этому вопросу чуть позже.

Манифест сейчас выглядит так:

---
apiVersion: v1
kind: Namespace
metadata:
  name: test-namespace
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-deployment
  namespace: test-namespace
  labels:
    app: test
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
        version: v1
    spec:
      containers:
      - name: web
        image: nginx
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 100Mi
        readinessProbe:
          httpGet:
            path: /
            port: 80
      nodeSelector:
        role: common-workers
---
apiVersion: v1
kind: Service
metadata:
  name: test-svc
  namespace: test-namespace
spec:
  selector:
    app: test
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

Рекомендуется использовать тег version в приложениях — позже это позволит использовать canary и blue-green deployment, см . How To Do Canary Deployments With Istio and Kubernetes и Traffic Management.

Деплоим:

kubectl apply -f test-istio.yaml
namespace/test-namespace created
deployment.apps/test-deployment created
service/test-svc created

Проверим контейнеры пода:

kk -n tkk -n test-namespace get pod -o jsonpath={.items[*].spec.containers[*].name}
web

Один контейнер, как и описывали.

Через kubectl port-forward подключимся к сервису:

kubectl -n test-namespace port-forward services/test-svc 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

И проверим работу приложения в поде:

curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Окей — тут всё, кроме Envoy-контейнеров, работает.

Istio Ingress Gateway

Итак, у нас есть Istio Ingress Gateway созданный во время установки Istio, который представляет собой AWS Classic LoadBalancer:

Если обратиться к его URL сейчас — то получим ошибку подключения т.к. шлюз не знает, куда ему направлять трафик:

curl a6f***037.eu-west-3.elb.amazonaws.com
curl: (52) Empty reply from server

Что бы разрешить трафик — опишем Istio Gateway.

Gateway

Gateway описывает настройки Istio Ingress Gateway — на какие порты и какой принимать трафик. Плюс тут же можно выполнить SSL termination (но в нашем случае это будет AWS LoadBalancer).

Добавляем в наш файл test-istio.yaml ресурс Gateway:

---
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: test-gateway
  namespace: test-namespace
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

Тут в spec.selector.istio указываем Istio Ingress Gateway, к которому будет применён этот манифест.

Обратите внимание, что наше приложение живёт в отдельном Namespace, поэтому и Gateway и VirtualService (см. ниже) создаём в нём.

Создаём его:

kubectl apply -f test-istio.yaml
namespace/test-namespace unchanged
deployment.apps/test-deployment unchanged
service/test-svc unchanged
gateway.networking.istio.io/test-gateway created

Проверяем:

kubectl -n test-namespace get gateways
NAME           AGE
test-gateway   17s

VirtualService

Теперь добавляем VirtualService, в котором опишем «бекенд», на который требуется слать трафик.

В роли бекенда тут выступает уже обычный Service нашего приложения — test-svc:

kk -n test-namespace get svc
NAME       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
test-svc   NodePort   172.20.195.107   <none>        80:31581/TCP   15h

Описываем VirtualService в том же namespace, где ижвёт наше приложение и в котором создавали Gateway:

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: test-virtualservice
  namespace: test-namespace
spec:
  hosts:
  - "*"
  gateways:
  - test-gateway
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: test-svc
        port:
          number: 80

Создаём его:

kubectl apply -f test-istio.yaml
namespace/test-namespace unchanged
deployment.apps/test-deployment unchanged
service/test-svc unchanged
gateway.networking.istio.io/test-gateway unchanged
virtualservice.networking.istio.io/test-virtualservice created

Проверяем:

kubectl -n test-namespace get virtualservice
NAME                  GATEWAYS         HOSTS   AGE
test-virtualservice   [test-gateway]   [*]     42s

И ещё раз выполняем запрос к URL лоад-балансера Istio Ingress Gateway:

curl a6f***037.eu-west-3.elb.amazonaws.com
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

Заметьте, что мы всё ещё работаем без Envoy-прокси в поде нашего приложения:

kk -n tkk -n test-namespace get pod -o jsonpath={.items[*].spec.containers[*].name}
web

Чуть позже разберёмся почему всё работает.

Kiali — наблюдение за трафиком

К Istio имеется множество аддонов — Prometheus для сбора метрик, Grafana для визуализации графиков, Jaeger для трассировки вызовов, и Kiali для построения карты сети и отображения карты трафика, см. Integrations.

Устанавливаем все аддоны:

kubectl apply -f samples/addons

И выполняем istioctl dashboard kiali — Kiali откроется в дефолтном браузере:

Но если мы перейдём в Applications, то увидим, что у нашего приложения в Details указано «Missing Sidecar«:

А в Graph нет карты сервисов:

Sidecar — Envoy-прокси

Итак, у нас в поде нет контейнеров с Envoy, хотя сеть работает, т.к. сейчас Istio через цепочку правил iptables шлёт трафик напрямую в контейнер с NGINX, см. Traffic flow from application container to sidecar proxy.

Правила iptalbes настраиваются дополнительным InitContainer — istio-init во время запуска пода, а сейчас они дефолтные, настроенные kube-proxy во время деплоя нашего тестового приложения, см. Kubernetes: Service, балансировка нагрузки, kube-proxy и iptables

В следующем посте мы копнём поглубже в работу сети в Istio, а пока просто добавим автоматическую вставку sidecar в поды неймспейса и сравним цепочки iptables до и после.

Istio и iptables

Посмотрим правила iptables до вставки sidecar и istio-init.

По SSH подключаемся на WorkerNode, находим контйнер с nginx:

[root@ip-10-22-35-66 ec2-user]# docker ps | grep nginx
22d64b132490        nginx                                                                   "/docker-entrypoint.…"   3 minutes ago       Up 3 minutes                            k8s_web_test-deployment-6864c5bf84-mk98r_test-namespace_8b88caf0-237a-4c94-ac71-186f8e701a7c_0

Находим PID процесса в этом контейнере:

[root@ip-10-22-35-66 ec2-user]# docker top k8s_web_test-deployment-6864c5bf84-mk98r_test-namespace_8b88caf0-237a-4c94-ac71-186f8e701a7c_0
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                31548               31517               0                   10:36               ?                   00:00:00            nginx: master process nginx -g daemon off;
101                 31591               31548               0                   10:36               ?                   00:00:00            nginx: worker process

Используя nsenter — проверяем правила iptables в network-неймспейсе процесса 31548 — пока тут ничего необычного, весь трафик идёт на наш контейнер:

[root@ip-10-22-35-66 ec2-user]# nsenter -t 31548 -n iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

Запуск Sidecar

Документация — Installing the Sidecar.

Для автоматической вставки Envoy-контейнеров — выполняем:

kubectl label namespace test-namespace istio-injection=enabled
namespace/test-namespace labeled

Проверяем:

kubectl get namespace test-namespace --show-labels
NAME             STATUS   AGE   LABELS
test-namespace   Active   11m   istio-injection=enabled

Но контейнеры будут добавлены только для новых ресурсов в этом неймспейсе.

Можем добавить сайдкар вручную в запущенный под, обновив его манифест с помощью kube-inject, либо просто пересоздать под:

kubectl  -n test-namespace scale deployment test-deployment --replicas=0
deployment.apps/test-deployment scaled
kubectl  -n test-namespace scale deployment test-deployment --replicas=1
deployment.apps/test-deployment scaled

Проверяем контейнеры ещё раз:

kubectl -n test-namespace get pod -o jsonpath={.items[*].spec.containers[*].name}
web istio-proxy

Тут istio-proxy — и есть sidecar-контейнер с Envoy.

Проверяем initContainers нашего пода:

kk -n test-namespace get pod -o jsonpath={.items[*].spec.initContainers[*].name}
istio-init

И проверим правила iptables снова — ещё раз находим контейнер и его PID, смотрим:

[root@ip-10-22-35-66 ec2-user]# nsenter -t 4194 -n iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
ISTIO_INBOUND  tcp  --  anywhere             anywhere
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
ISTIO_OUTPUT  tcp  --  anywhere             anywhere
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
Chain ISTIO_INBOUND (1 references)
target     prot opt source               destination
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15008
RETURN     tcp  --  anywhere             anywhere             tcp dpt:ssh
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15090
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15021
RETURN     tcp  --  anywhere             anywhere             tcp dpt:15020
ISTIO_IN_REDIRECT  tcp  --  anywhere             anywhere
Chain ISTIO_IN_REDIRECT (3 references)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             anywhere             redir ports 15006
Chain ISTIO_OUTPUT (1 references)
target     prot opt source               destination
RETURN     all  --  ip-127-0-0-6.eu-west-3.compute.internal  anywhere
ISTIO_IN_REDIRECT  all  --  anywhere            !localhost            owner UID match 1337
RETURN     all  --  anywhere             anywhere             ! owner UID match 1337
RETURN     all  --  anywhere             anywhere             owner UID match 1337
ISTIO_IN_REDIRECT  all  --  anywhere            !localhost            owner GID match 1337
RETURN     all  --  anywhere             anywhere             ! owner GID match 1337
RETURN     all  --  anywhere             anywhere             owner GID match 1337
RETURN     all  --  anywhere             localhost
ISTIO_REDIRECT  all  --  anywhere             anywhere
Chain ISTIO_REDIRECT (1 references)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             anywhere             redir ports 15001

Теперь тут появились дополнительные цепочки, созданные Istio, которые отправлят трафик в контейнер с Envoy и от него в контейнер с приложением, и обратно.

Возвращаемся к Kiali, проверяем карту:

И трассировку вызовов:

Traces доступны через Jaeger, который тоже можно открыть через istioctl dashboard jaeger:

Также с помощью istioctl dashboard prometheus можно запустить Prometheus, и посмотреть его метрики, см. Querying Metrics from Prometheus:

Собственно, на этом пока всё.

Далее будем знакомиться с интеграцией AWS Application LoadBalncer, деплоем и настройкой Istio в AWS Elastic Kubernetes Service, настройкой Gateway и VirtualServices.

Ссылки по теме