Впровадження та переваги Istio Ambient Mesh. Оптимізація ресурсів та підвищення безпеки у мікросервісних середовищах

💡 Усі статті, обговорення, новини про DevOps — в одному місці. Приєднуйтесь до DevOps спільноти!

Привіт! Мене звати Нікіта, я DevOps-інженер у команді професіоналів на платформі FinOps Uniskai від Profisea Labs. Нещодавно наша команда зіткнулася з викликом захисту та додаткового контролю над трафіком мікросервісів. Для цього вирішили впровадити Istio Ambient Mesh.

Istio виділяється як платформа з відкритим кодом, призначена для оптимізації та зміцнення зв’язку між мікросервісами. Вона спрощує складність керування трафіком у Kubernetes в багатьох хмарних середовищах, пропонуючи рішення для масштабованості та безпеки.

За своєю суттю Istio складається з двох ключових елементів:

Data plane. Охоплює мережу proxy-серверів Envoy, які обслуговують зв’язок між мікросервісами. Envoy діє як легкий proxy-сервер. Розгорнутий разом із кожною службою, він перехоплює та регулює потік трафіку. Надає основні функції: виявлення служб, покращення безпеки, mTLS, інструменти для стійкості мережі та її спостережуваності.

Control plane. Виконуючи функцію оркестратора, площина керування централізує завдання керування та налаштування для proxy-серверів у площині даних. Раніше він складався з кількох компонентів, тепер його спрощено в один ресурс під назвою Istiod. Istiod займається виявленням служб, конфігурацією та керуванням сертифікатами, перетворюючи правила маршрутизації високого рівня в конкретні конфігурації для Envoy.

Service Mesh діє як проміжний рівень у кластерній мережі. Він захищає канали зв’язку між призначеними контейнерами, працюючи поруч із основними контейнерами застосунків, як-от sidecar-контейнер. Це налаштування забезпечує підключення до мережі через proxy-сервер із бездоганною інтеграцією між ним та основною програмою через локальну мережу.

Але у версії Istio 1.19.0 зʼявився Ambient mode. Ambient mesh створено для спрощення Service mesh, з особливим акцентом на гнучкість архітектури, безпеку та продуктивність. В нашій команді вирішили спробувати Istio й майже одразу ми стикнулись з проблемою: Istio не додавав init-container в mesh й тому вони не могли закінчити свою роботу. У ході розгляду цієї проблеми було відкрито issue на GitHub де розробники розібралися в проблемі й вирішили її. Хочу скористатися можливістю та виразити подяку за це.

Переваги Istio Ambient Mesh

Зменшення ресурсів і накладних витрат

  • Ambient Mesh має модульну архітектуру, де можливості L4 і L7 розподілені між двома компонентами, що знижує потребу у ресурсах порівняно з sidecar-proxy.
  • Відсутність sidecar-proxy дозволяє уникнути зайвого використання ресурсів, спрощує роботу команд DevOps та покращує загальну ефективність кластерів.
  • Ztunnels, які обробляють функції з zero trust/mTLS і L4 на рівні вузлів, є легкими та менш ресурсомісткими. Waypoint proxy автоматично масштабуються відповідно реального трафіку, що економить ресурси й кошти.

Zero trust і краща безпека мережі

  • Відсутність sidecar-proxy, інтегрованих із застосунками, забезпечує кращу ізоляцію між ними та компонентами мережі, що підвищує загальну безпеку.
  • Ztunnels і waypoint proxy можуть застосовувати політики authn/z.
  • Керування життєвим циклом ztunnels і proxy здійснюється без перезапуску робочих навантажень, що забезпечує нульовий час простою.

Підвищення продуктивності та ефективності роботи

  • Менша кількість компонентів для керування спрощує роботу команд DevOps, зменшує затримку та підвищує загальну продуктивність мережі.
  • Гнучкість архітектури дозволяє поступово впроваджувати Istio. Починаючи з базових функцій, як-от mTLS і L4, і поступово додаючи розширені функції L7.

Режим Ambient у Istio відкриває нові можливості для оптимізації роботи мікросервісів, знижуючи складність та витрати, забезпечуючи при цьому високий рівень безпеки та продуктивності.

Розгортання Istio Ambient з використанням Terraform & Helm

Перш за все раджу зпулити й розташувати імейджі в вашому приватному репозиторії. На прикладі AWS це буде ECR. Оскільки при перших спробах розгортання ресурсів Istio їхня конфігурація зазнавала змін і в списку ресурсів є не тільки deployment, а й daemonset, ми вкрай швидко зіткнулись з проблемою обмеження кількості завантажень з DockerHub. Це блокувало роботу всього кластеру.

Щоб запобігти проблемі, вирішили використовувати внутрішній репозиторій. А ще тому, що в наших кластерах використовуються node з різними архітектурами x86 й arm. Більше про це у статті «Як оптимізувати хмарну інфраструктуру: шлях DevOps-інженера від процесорів x86 до ARM (Graviton) архітектури».

Щоб мати docker manifest, котрий посилається на імейджі під різні архітектури, необхідно спочатку отримати доступ до власного репозиторію, зпулити імейджі з DockerHub й запушити їх до нього з різними тагами. Після створити docker manifest зі списком цих імейджів й також запушити його в репозиторій. Такі дії необхідно повторити для всіх імейджів Istio, котрі ви будете використовувати.

aws ecr get-login-password --region <aws_region> | docker login --username AWS --password-stdin <account_id>.dkr.ecr.<aws_region>.amazonaws.com
docker pull istio/pilot:1.22.0 --platform linux/amd64
docker tag istio/pilot:1.22.0 <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0-amd64
docker push <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0-amd64
docker pull istio/pilot:1.22.0 --platform linux/arm64
docker tag istio/pilot:1.22.0 <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0-arm64
docker push <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0-arm64
docker manifest create <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0 <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0-arm64 <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0-amd64
docker manifest push <account_id>.dkr.ecr.<aws_region>.amazonaws.com/istio:pilot-1.22.0

Для деплою Istio в кластер з використанням Terraform кращим рішенням буде створити окремий модуль. Щоб в ньому описати все, що стосується конфігурації Istio. Стандартний deployment Istio дає змогу лише керувати трафіком всередині кластеру, тому до Istio Base & Istiod helm chart необхідно додати й сконфігурувати Istio Gateway, завдяки якому ми зможемо керувати й направляти трафік ззовні.

resource "helm_release" "istio_base" {
 name             = "istio-base"
 namespace        = "istio-system"
 chart            = "base"
 repository       = "https://istio-release.storage.googleapis.com/charts"
 create_namespace = true
 version          = "1.22.0"
}
resource "helm_release" "istiod" {
 name             = "istiod"
 namespace        = "istio-system"
 chart            = "istiod"
 repository       = "https://istio-release.storage.googleapis.com/charts"
 create_namespace = true
 version          = "1.22.0"
 wait             = true
 values           = [file("values/istiod.yaml")]
 depends_on = [helm_release.istio_base]
}
resource "helm_release" "istio_gateway" {
 name             = "istio-gateway"
 namespace        = "istio-system"
 chart            = "gateway"
 repository       = "https://istio-release.storage.googleapis.com/charts"
 create_namespace = true
 version          = "1.22.0"
 values           = [file("values/istio-gateway.yaml")]
 depends_on = [helm_release.istiod]
}

Коли ресурси в Terrafrom створені, необхідно додати модифіковані values.yaml файли для кожного helm chart, оскільки необхідно вказати місце зберігання імейджів (власного репозиторію), проставити ресурси й увімкнути Ambient mode. В нашому випадку фронтенд-сервіси додатково використовують протокол HTTP/1.1, тому в змінних Istiod треба прописати наступне значення. Протестуйте у своєму середовищі: якщо використовується остання версія протоколу, ці зміни не потрібні.

pilot:
 hub: "<account_id>.dkr.ecr.<aws_region>.amazonaws.com"
 tag: "pilot-1.22.0"
 image: "istio"
 traceSampling: 100.0
 env:
   PILOT_HTTP10: "1"
   PILOT_ENABLE_AMBIENT: true
 resources:
   requests:
     cpu: 300m
     memory: 700Mi
   limits:
     cpu: 300m
     memory: 700Mi
istio_cni:
 enabled: true
 chained: true

Своєю чергою конфігурація Istio Gateway є більш комплексною, оскільки Helm chart, котрий називається «istio-gateway» створює тільки pod й service в кластері. Окрім цього, chart має два типи gateway для керування вхідним трафіком.

Перший — стандартний Istio Gateway, котрий є частиною CRD networking.istio.io й розгортається завдяки helm chart. Він розглядається в цьому прикладі. Інший — Gateway API. Належить до CRD gateway.networking.k8s.io, котрий розгортається завдяки Kubernetes manifest. Розберемо розбіжності цих рішень у наступному пункті, а поки зосередимось на Istio gateway.

podAnnotations:
 karpenter.sh/do-not-disrupt: "true"
autoscaling:
 enabled: true
 minReplicas: 2
resources:
 requests:
   cpu: 100m
   memory: 150Mi
 limits:
   cpu: 100m
   memory: 150Mi
service:
 ports:
 - name: status-port
   port: 15021
   protocol: TCP
   targetPort: 15021
 - name: http2
   port: 80
   protocol: TCP
   targetPort: 80
 - name: https
   port: 443
   protocol: TCP
   targetPort: 443
 annotations:
   service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "instance"
   service.beta.kubernetes.io/aws-load-balancer-internal: "true"
   service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "30"
   service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "10"

В цьому файлі описується масштабування цього deployment. Рекомендую ставити від двох реплік, щоб у разі відмови одного pod трафік до ваших сервісів йшов через інший. Стандартно цей сервіс створює Network LoadBalancer, який конфігурується завдяки анотаціям.

Якщо ви використовуєте Application LoadBalancer в інфраструктурі, можете зіткнутись з перешкодами. Наприклад, з унеможливленням використовування WAF з NLB. Щоб розвʼязати проблему, можна скористатися AWS Application Load Balancer (ALB), який посилається на service Istio Gateway. Для досягнення цієї мети ми використовуємо аналогічний до минулого прикладу підхід, але з деякими модифікаціями:

podAnnotations:
 karpenter.sh/do-not-disrupt: "true"
autoscaling:
 enabled: true
 minReplicas: 2
resources:
 requests:
   cpu: 100m
   memory: 150Mi
 limits:
   cpu: 100m
   memory: 150Mi
service:
 type: NodePort
 ports:
 - name: status-port
   protocol: TCP
   port: 15021
   targetPort: 15021
   nodePort: 30586
 - name: http2
   protocol: TCP
   port: 80
   targetPort: 80
 - name: https
   protocol: TCP
   port: 443
   targetPort: 443
 annotations:
   alb.ingress.kubernetes.io/healthcheck-path: /healthz/ready
   alb.ingress.kubernetes.io/healthcheck-port: "30586"
   service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "30"
   service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "10"

В цьому файлі ми змінили тип service з Load Balancer на Node Port. Далі постає питання — як виконувати health checks на AWS Application LoadBalancer? Адже в Istio Gateway використовуються різні порти — 80 для вхідного трафіку та 15021 для перевірки статусу. Якщо в Ingress вказати анотацію «alb.ingress.kubernetes.io/healthcheck-port», то ALB Ingress Controller не реагує на це налаштування, й Ingress не створюється для AWS LoadBalancer.

Рішення полягає в тому, щоб перемістити анотації, пов’язані з health check, в service самого Istio Gateway. Для цього редагуємо service Istio Gateway. Щоб анотації, пов’язані з health check, були застосовані до цього service.

Також може виникнути проблема з health check. Якщо таке сталось — збільшіть інтервали й таймаути. В нашому випадку вистачило виставити інтервали в 30 секунд й таймаут в 10. В різних середовищах й випадках поведінка може відрізнятись, тому треба підібрати ці значення.

У варіанті з використанням Gateway API необхідно додати CRD в кластер. Дістати їх можна в офіційному репозиторії по шляху «config/crd/standard». Для зручності чотири завантажених файли можна обʼєднати в один файл, розділивши маніфести «---». Коли створили спільний файл, що містить опис всіх ресурсів, зберігаємо у зручному місці й створюємо gateway.

Щоб трафік був захищений через цей gateway, використаємо cert-manager. Є можливість встановити CRDʼs, використовуючи Helm Chart й переписавши у значеннях «installCRDs: true». Або таким чином, як з Gateway API — встановити з репозиторію й обʼєднати маніфести. В цьому прикладі показано конфігурацію ClusterIssuer для використання Amazon Route53 для отримання DNS01 ACME (детальніше описано в документації).

//Cert-manager module
data "kubectl_file_documents" "cert_manager_crd" {
 content = file("files/manifests/cert-manager.crds.yaml")
}
resource "kubectl_manifest" "cert_manager_manifest" {
 for_each  = data.kubectl_file_documents.cert_manager_crd.manifests
 yaml_body = each.value
}
data "aws_iam_policy_document" "cert_manager_webidentity" {
 statement {
   effect = "Allow"
   actions = [
     "sts:AssumeRoleWithWebIdentity"
   ]
   condition {
    test     = "StringEquals"
    variable = "${replace(module.eks.cluster_oidc_issuer_url, "https://", "")}:aud"
    values = [
      "sts.amazonaws.com"
    ]
  }
   condition {
     test     = "StringEquals"
     variable = "${replace(module.eks.cluster_oidc_issuer_url, "https://", "")}:sub"
     values = [
       "system:serviceaccount:cert-manager:cert-manager"
     ]
   }
   principals {
    type = "Federated"
    identifiers = [
      module.eks.oidc_provider_arn
     ]
  }
 }
}
resource "aws_iam_role" "cert_manager_role" {
 name = "cert-manager"
 assume_role_policy = data.aws_iam_policy_document.cert_manager_webidentity.json
}
resource "aws_iam_policy" "cert_manager_policy" {
 name        = "cert-manager-policy"
 description = "Policy for cert-manager IAM role"
 policy = <<EOF
{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Effect": "Allow",
     "Resource": "${var.cert_manager_route53_role}",
     "Action": "sts:AssumeRole"
   }
 ]
}
EOF
}
resource "aws_iam_role_policy_attachment" "cert_manager_attachment" {
 role       = aws_iam_role.cert_manager_role.name
 policy_arn = aws_iam_policy.cert_manager_policy.arn
}
resource "kubernetes_manifest" "route53_cluster_issuer" {
manifest = {
  apiVersion = "cert-manager.io/v1"
  kind       = "ClusterIssuer"
  metadata = {
    name      = "route53-cluster-issuer"
  }
  spec = {
    acme = {
    server        = "https://acme-v02.api.letsencrypt.org/directory"
      privateKeySecretRef = {
       name      = "route53-cluster-issuer"
      }
     solvers = [
        {
         selector = {
           dnsZones = ["${var.site}"]
          }
          dns01 = {
            route53 = {
              region = "us-east-1"
              role   = "${var.cert_manager_route53_role}"
           }
         }
        }
      ]
    }
  }
}
 depends_on = [kubectl_manifest.cert_manager_manifest]
}
//Istio module
data "kubectl_file_documents" "gateway_api_crd" {
 content = file("files/manifests/gateway-api.crds.yaml")
}
resource "kubectl_manifest" "gateway_api_manifest" {
 for_each  = data.kubectl_file_documents.gateway_api_crd.manifests
 yaml_body = each.value
}
//Istio Gateway (CRD resource)
resource "kubernetes_manifest" "istio_ingress_gateway" {
 manifest = {
   apiVersion = "networking.istio.io/v1beta1"
   kind       = "Gateway"
   metadata = {
     name      = "ingress-gateway"
     namespace = "istio-system"
   }
   spec = {
     selector = {
       app = "istio-gateway"
    }
     servers = [
       {
        port = {
          number   = 80
          name     = "http"
          protocol = "HTTP"
         }
         tls = {
           httpsRedirect = true
         }
         hosts = ["*.test.com"]
       },
       {
         port = {
           number   = 443
           name     = "https"
           protocol = "HTTPS"
         }
         tls = {
          mode            = "SIMPLE"
          credentialName  = "ingress-gateway-cert"
         }
         hosts = ["*.test.com"]
       }
     ]
   }
 }
 depends_on = [kubernetes_manifest.route53_cluster_issuer]
}
//Gateway API "Gateway" (CRD resource)
resource "kubernetes_manifest" "ingress_gateway" {
 manifest = {
   apiVersion = "gateway.networking.k8s.io/v1beta1"
   kind       = "Gateway"
   metadata = {
     name = "ingress-gateway"
     namespace = "istio-system"
     annotations = {
       "cert-manager.io/cluster-issuer" = "route53-cluster-issuer"
       "service.beta.kubernetes.io/aws-load-balancer-internal" = "true"
       "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type" = "ip"
     }
     labels = {
       app = "ingress-gateway"
     }
   }
  spec = {
    gatewayClassName = "istio"
    listeners = [
      {
        name        = "http"
        protocol    = "HTTP"
        port        = 80
        hostname    = "*.test.com"
      },
      {
        name        = "https"
        protocol    = "HTTPS"
        port        = 443
        hostname    = "*.test.com"
        tls = {
          mode = "Terminate"
          certificateRefs = [
             {
              kind   = "Secret"
               group  = ""
               name   = "ingress-gateway-cert"
             }
          ]
        }
        allowedRoutes = {
          namespaces = {
            from = "All"
          }
       }
      }
    ]
  }
}
 depends_on = [kubernetes_manifest.route53_cluster_issuer]
}

Gateway API помітно схожий на API Istio. А саме у використанні ресурсів Gateway і VirtualService, які служать аналогічним цілям. Мета розробки нових Gateway API — уніфікувати інформацію, зібрану з різних вхідних реалізацій Kubernetes, включно з Istio, щоб сформулювати стандартизований API, позбавлений упередженості постачальника. Хоча ці API першочергово служать тим же цілям, що й Gateway та VirtualService від Istio, існують деякі характерні відмінності.

В API Istio Gateway складається з двох частин: helm chart й сам gateway, де Istio керує вже розгорнутими ресурсами. І навпаки, в рамках Gateway API ресурс налаштовує та розгортає gateway. Хоча у VirtualService від Istio всі протоколи налаштовані в одному ресурсі, Gateway API використовує інший підхід, згідно з яким кожен тип протоколу має свій окремий ресурс. Наприклад, HTTPRoute і TCPRoute.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
 name: example-vs
 namespace: example
spec:
 gateways:
   - istio-system/ingress-gateway #here you should write namespace & gateway name (Istio CRD)
 hosts:
   - example.test.com
 http:
  - match:
      - uri:
          prefix: /
    name: example-svc
    route:
      - destination:
          host: example-svc
          port:
            number: 80
 tcp:
 - match:
   - port: 27017
   route:
   - destination:
       host: example.svc.cluster.local
       port:
         number: 5555
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-example-route
spec:
parentRefs:
- name: ingress-gateway #here you should write gateway name (Gateway API CRD)
hostnames:
- "example.test.com"
rules:
- backendRefs:
  - name: example-svc
    port: 80
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TCPRoute
metadata:
 name: tcp-example-route
spec:
 parentRefs:
 - name: ingress-gateway #here you should write gateway name (Gateway API CRD)
   sectionName: foo
 rules:
 - backendRefs:
   - name: example-svc
     port: 5555

Попри те, що Gateway API пропонує низку складних функцій маршрутизації, Gateway API поки що не охоплює повного набору функцій Istio. Адже багато людей вважають Gateway API перспективним проєктом, але не можуть його використовувати через несумісність з ALB. Саме тому ми вирішили використовувати звичайний функціонал Istio.

У випадку використання Service Mesh після того, як основні ресурси Istio розгорнуті й налаштовані, треба додати мітку «istio-injection=enabled» в namespace, котрі повинні бути в Istio Service Mesh й перезавантажити pod, щоб sidecar proxy зʼявились у кожному екземплярі в namespace. Основний недолік Service Mesh — його копія є у кожному pod кожного namespace, котрий ви додали в Service Mesh, додавши label «istio-injection=enabled».

Стандартні значення sidecar-контейнеру — (requests cpu: 100m, memory: 128Mi & limits cpu: 2000m, memory: 1024Mi). На практиці у тестовому середовищі використання ресурсів було на рівні 150m й 240Мі, що є досить суттєвим. Оскільки в нашому Dev-кластері в основному namespace, котрий містить самі мікросервіси аплікації, налічується біля 40 pod (залежить від навантаження). Якщо підрахувати, на один namespace знадобляться додаткові 6000m (6vCPU) й 9600Mi (9,6Gb).

Важливо відзначити — в нашому кластері використовується Karpenter, тому значення requests & limits виставленні однаковими. Для того, щоб Karpenter підбирав оптимальні node.

Своєю чергою Istio Ambient Mesh для роботи потребує лише два daemonset — це Istio CNI й Ztunnel. Також кожен namespace, котрий ви хочете додати до mesh, повинен мати label «istio.io/dataplane-mode=ambient». Istio CNI використовується для налаштування перенаправлення трафіку для модулів у Istio mesh. Він працює як daemonset: на кожному вузлі, з підвищеними привілеями.

Агент вузла CNI використовується обома режимами даних Istio, Ztunnel (Zero trust tunnel). Це спеціально створений proxy для кожного вузла для навколишньої Istio mesh. Він відповідає за безпечне підключення та автентифікацію робочих навантажень в Istio mesh зовнішнього середовища.

На Dev-кластері ми маємо 24 node, з чого виходить, що розгорнуті по 24 pod для кожного демонсету. Підібрані значення виділених ресурсів для контейнеру Istio CNI — (requests/limits cpu: 100m, memory: 100Mi), Ztunnel — (requests/limits cpu: 150m, memory: 150Mi), що сумарно складає 6000m (6vCPU) й 6000Mi (6Gb). З однією вагомою різницею — це на весь кластер, у порівнянні з Service Mesh для одного namespace.

Як раніше було сказано, для Ambient mesh треба розгорнути daemonset Istio CNI й Ztunnel.

resource "helm_release" "istio_cni" {
 name             = "istio-cni"
 namespace        = "istio-system"
 chart            = "cni"
 repository       = "https://istio-release.storage.googleapis.com/charts"
 create_namespace = true
 version          = "1.22.0"
 values           = [file("values/istio-cni.yaml")]
 depends_on = [helm_release.istio_base]
}
resource "helm_release" "ztunnel" {
 name             = "ztunnel"
 namespace        = "istio-system"
 chart            = "ztunnel"
 repository       = "https://istio-release.storage.googleapis.com/charts"
 create_namespace = true
 version          = "1.22.0"
 values           = [file("values/istio-ztunnel.yaml")]
 depends_on = [helm_release.istio_base]
}

Де в values.yaml Istio CNI ми можемо вказати namespace, котрі ми не хочемо додавати в Ambient mesh з деяких причин: як обмеження Istio ми зобовʼязані внести в цей список «istio-system» й «kube-system», вибагливі вимоги до продуктивності. Istio, як додатковий компонент в системі, дещо уповільнює роботу. Два proxy додаються приблизно 0,182 мс і 0,248 мс до 90-го і 99-го персентиля, що не є критичним в більшості випадків, але завжди є виключення.

Також можуть бути проблеми з окремими застосунками, на нашому прикладі це «kube-prometheus-stack», котрий при додаванні в mesh переводить node в стан «not ready» й викликає ланцюгову реакцію для всіх інших node в кластері. Для запобігання такого були додані в список namespace «monitoring», «karpenter».

cni:
 hub: "<account_id>.dkr.ecr.<aws_region>.amazonaws.com"
 tag: "cni-1.22.0"
 image: "istio"
 ambient:
   enabled: true
 resources:
   requests:
     cpu: 100m
     memory: 100Mi
   limits:
     cpu: 100m
     memory: 100Mi
 privileged: true
 excludeNamespaces:
   - kube-public
   - kube-system
   - karpenter
   - istio-system
   - monitoring 

В values.yaml файлі Ztunnel нам необхідно тільки вказати ресурси й репозиторій з імейджем.

defaults:
 hub: "<account_id>.dkr.ecr.<aws_region>.amazonaws.com"
 tag: "ztunnel-1.22.0"
 image: "istio"
 resources:
   requests:
     cpu: 150m
     memory: 150Mi
   limits:
     cpu: 150m
     memory: 150Mi

Коли всі ресурси сконфігуровані й розташовані в кластері, необхідно проставити label «istio.io/dataplane-mode=ambient», після чого перезапустити node, оскільки Istio CNI для роботи повинен ініціалізуватись при старті сервера. Впевнившись, що все працює як заплановано, необхідно перенаправити вхідний трафік до Istio.

resource "kubectl_manifest" "sentry_ingress" {
 count = var.environment == "dev" ? 0 : 1
 yaml_body = <<YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: "${helm_release.sentry.name}"
 namespace: "istio-system"
 annotations:
   alb.ingress.kubernetes.io/group.name: internal
   alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
   alb.ingress.kubernetes.io/scheme: internal
   alb.ingress.kubernetes.io/ssl-redirect: '443'
   alb.ingress.kubernetes.io/target-type: ip
   kubernetes.io/ingress.class: alb
spec:
 rules:
   - ${var.environment == "prod" ? "host: \"sentry.${var.dns_domain}\"" : var.environment == "stage" ? "host: \"sentry-${var.dns_domain}\"" : "host: \"sentry-${var.environment}.${var.dns_domain}\""}
     http:
       paths:
         - path: /
           pathType: Prefix
           backend:
             service:
               name: "istio-internal-gateway"
               port:
                 number: 80
YAML
 depends_on = [helm_release.sentry]
}
resource "kubectl_manifest" "virtual_service_sentry" {
 yaml_body = <<YAML
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
 name: "${helm_release.sentry.name}"
 namespace: "${kubernetes_namespace_v1.sentry.metadata.0.name}"
spec:
 gateways:
   - ${var.environment == "dev" ? "istio-system/ingress-gateway" : "istio-system/istio-internal-gateway"}
 hosts:
   - ${var.environment == "prod" ? "sentry.${var.dns_domain}" : var.environment == "stage" ? "sentry-${var.dns_domain}" : "sentry-${var.environment}.${var.dns_domain}"}
 http:
 - match:
   - uri:
       prefix: /api/0/
   route:
   - destination:
       port:
         number: 9000
       host: sentry-web
 - match:
   - uri:
       prefix: /api/
   route:
   - destination:
       port:
         number: 3000
       host: sentry-relay
 - match:
   - uri:
       prefix: /
   route:
   - destination:
       port:
         number: 9000
       host: sentry-web
     name: sentry-web
YAML
 depends_on = [helm_release.sentry]
}
resource "kubernetes_namespace_v1" "sentry" {
 metadata {
   name = "sentry"
   labels = {
     name = "sentry"
     "istio.io/dataplane-mode" = "ambient"
   }
 }
}
resource "kubectl_manifest" "sentry_istio_waypoint" {
 yaml_body = <<YAML
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
 name: "${kubernetes_namespace_v1.sentry.metadata.0.name}-istio-waypoint"
 namespace: "${kubernetes_namespace_v1.sentry.metadata.0.name}"
spec:
 gatewayClassName: istio-waypoint
 listeners:
 - name: mesh
   port: 15008
   protocol: HBONE
YAML
 depends_on = [kubernetes_namespace_v1.sentry]
}

Перенаправлення трафіку до Istio на прикладі Sentry полягає у створенні namespace замість використання вбудованого функціоналу Terrafrom провайдера Helm. Так як нам треба проставити мітки, за необхідності створити Istio waypoint для обробки L7 робочих навантажень. Створити ingress, котрий використовує наш ALB й перенаправляє трафік не прямо до сервісу Sentry, а до Istio Gateway в namespace «istio-system», розташування Virtual Service в namespace Sentry, котрий перенаправляє трафік з Istio Gateway до сервісу Sentry. Такі маніпуляції необхідно виконати з кожним застосунком, який ви хочете внести до Istio mesh.

Висновок

Впровадження Istio Ambient Mesh дозволило нашій команді ефективно розвʼязати питання захисту та контролю трафіку між мікросервісами. Використовуючи його, ми значно заощадили у витратах на обчислювальні ресурси, збільшили гнучкість архітектури, покращили безпеку та підвищили надійність наших сервісів (у порівнянні з Service mesh). А також змогли полегшити процеси керування мережею, забезпечивши ефективне управління трафіком з мінімальними накладними витратами.

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Istio CNI — (requests/limits cpu: 100m, memory: 100Mi), Ztunnel — (requests/limits cpu: 150m, memory: 150Mi)

а це не призводить до проблем з летенсі через тротлінг по CPU?

Звичайно призводить. Вмикати ліміти cpu то доведено постріл в ногу.

Дякую за питання! З нашого досвіду, протягом усього часу використання Istio в наших кластерах, проблем з тротлінгом не виникало. На різних середовищах значення ресурсів можуть відрізнятися. В нашому випадку на дев серидовищі (як в статті) Ztunnel зазвичай навантажений до 30% по CPU та пам’яті. Istio CNI в свою чергу споживає незначну кількість CPU після старту пода (1-5%), а по пам’яті досягає максимуму до 60%.
Ці дані свідчать про те, що налаштовані ресурси достатні для забезпечення стабільної роботи без суттєвого впливу на летенсі. Проте, у кожному випадку треба підбирати індивідуальні значееня й якщо у вас є специфічні вимоги або спостерігаються відхилення, завжди корисно періодично переглядати і коригувати ресурси відповідно до навантаження.

Те що споживання маленьке не означає, що ваш контейнер не голодує по CPU. Раніше з цим зовсім була біда, з того часу щось там підфіксали в ядрі, додали налаштування для k8s, які дозволяють зменшити період планування до 10мс, але я майже впевнений, що проблема лишилась.

The CPU limit defines a hard ceiling on how much CPU time that the container can use. During each scheduling interval (time slice), the Linux kernel checks to see if this limit is exceeded; if so, the kernel waits before allowing that cgroup to resume execution.

Дякую за статтю. А чому обрали саме Istio, які його фічі використовуете (окрім банального балансування і mTLS) ? Наприклад той же Linkerd по ресурсам значно ефективніший і простіший у конфігурації.

Ну, Істіо — це комбайн «все-в-одному». В Лінкерді той-же інгрес контроллер потрібно ставити окремо. Але Істіо по трохи відходить в минуле, особливо з тим, як довго вони релізили свій ембіент. Зараз по повній програмі набирає оборотів Cilium, який по функціоналу уже обходить Істіо.

До того ж Лінкерд тепер платний для великих команд.

Дякую за коментар! Наша команда обрала Istio через його широкий набір можливостей і розширюваність, що дозволяє використовувати його як універсальний інструмент для управління трафіком у складних мікросервісних архітектурах, також Istio проявив себе надійним інструментом й в інших проектах. Окрім mTLS та балансування, також використовуємо такі фічі як rate limiting, authorization & authentication policies й редірект трафіку

Можна використати aws ecr pull through cache, замість перетягування імейджів руками. При оновленні на наступну версію istio, не треба буде знову займатись цим
docs.aws.amazon.com/...​e/pull-through-cache.html

Дякую за коментар і корисну підказку! Це виглядає як цікаве рішення, яке допоможе зекономити час і зменшити кількість ручних дій. Обов’язково вивчу це питання глибше.

Підписатись на коментарі