Istio 网络:深入了解流量和架构

2021-12-10 10:08:55 浏览数 (1)

作者 | Kasun Talwatta 译者 | Luga Lee 策划 | Luga Lee

像 Istio 这样的服务网格项目为我们的架构引入了许多功能和优势,包括更安全地管理集群微服务之间的流量、服务发现、请求路由以及服务之间的可靠通信。

尽管 Istio 是中立平台,但其已成为与 Kubernetes 一起使用的更流行的服务网格之一。尽管如此受欢迎,但对于服务网格新手来说,理解 Istio 的网络和核心机制可能很复杂,例如:

1、Envoy Sidecar 代理注入

2、Sidecar 如何拦截和路由流量

3、下发流量管理配置

4、流量规则如何在数据平面上生效

在通过分析 Istio 的架构和实现机制来解释这些机制的系列博客的第一篇文章中,我们将介绍 Istio 的网络基础知识、数据平面和控制平面、网络以及使用 Envoy 代理的 Sidecar 注入。使用演示环境,我们将能够看到 Istio 如何注入 Init 和 Sidecar 容器以及这些容器在 Pod 模板中的配置。

Istio 网络基础

Istio 的概述已在官方文档中进行了广泛的介绍,但在进一步进行之前,我们将先重点介绍关键组件。

Istio 由两个主要部分组成:数据平面和控制平面。

数据平面:数据平面或数据层由一组代理服务组成,这些代理服务表示为每个 Kubernetes Pod 中的边车容器,使用扩展的 Envoy 代理服务器。这些边车调解和控制微服务之间的所有网络通信,同时还收集和报告有用的遥测数据。

控制平面:控制平面或控制层由一个名为 istiod 的二进制文件组成,负责将高级路由规则和流量控制行为转换为 Envoy 特定的配置,然后在运行时将它们传播到 Sidecar。此外,控制平面提供安全措施,通过内置身份和凭证管理实现强大的服务到服务和最终用户身份验证,同时根据服务身份实施安全策略。

Istio 环境演示

在继续之前,让我们创建一个本地沙箱环境。这将确保我们在 Kubernetes 中部署了 Istio 服务网格,并在网格中运行了一个示例应用程序。

需要的工具如下:

  • minikube
  • istioctl (Installed with curl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.11.4 sh)

部署 Istio 服务网格步骤:

1、使用 hyperkit 驱动程序在本地创建 1.22.2 版本的 Kubernetes 集群。如果使用的是非 Mac OS X 机器,则需要安装 virtualbox。

代码语言:javascript复制
[administrator@JavaLangOutOfMemory ~ ] % minikube start --memory=4096 --cpus=2 --disk-size='20gb' --kubernetes-version=1.22.2 --driver=hyperkit -p istio-demo

2、集群完全启动后,执行以下命令设置 Istio。

代码语言:javascript复制
# Deploy Istio operator
[administrator@JavaLangOutOfMemory ~ ] % istioctl operator init

# Inject operator configuration
[administrator@JavaLangOutOfMemory ~ ] % cat << EOF | kubectl apply -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-control-plane
  namespace: istio-system
spec:
  profile: minimal
  meshConfig:
    accessLogFile: /dev/stdout
    enableAutoMtls: true
    defaultConfig:
      proxyMetadata:
        # Enable basic DNS proxying
        ISTIO_META_DNS_CAPTURE: 'true'
        # Enable automatic address allocation
        ISTIO_META_DNS_AUTO_ALLOCATE: 'true'
EOF

3、部署示例应用程序。

代码语言:javascript复制
# Create apps namespace
[administrator@JavaLangOutOfMemory ~ ] % kubectl create ns apps
# Label apps namespace for sidecar auto injection
[administrator@JavaLangOutOfMemory ~ ] % kubectl label ns apps istio-injection=enabled

# Deploy a unprivileged sleep application
[administrator@JavaLangOutOfMemory ~ ] % cat << EOF | kubectl apply -n apps -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sleep
---
apiVersion: v1
kind: Service
metadata:
  name: sleep
  labels:
    app: sleep
    service: sleep
spec:
  ports:
  - name: http
    port: 80
  selector:
    app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  selector:
    matchLabels:
      app: sleep
  template:
    metadata:
      labels:
        app: sleep
    spec:
      terminationGracePeriodSeconds: 0
      serviceAccountName: sleep
      containers:
      - name: sleep
        image: curlimages/curl
        command: ["/bin/sleep", "3650d"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: secret-volume
          mountPath: /etc/sleep/tls
      volumes:
      - name: secret-volume
        secret:
          secretName: sleep-secret
          optional: true
EOF

4、验证 istio-init 和 istio-proxy 容器都准备就绪并运行。

代码语言:javascript复制
[administrator@JavaLangOutOfMemory ~ ] % kubectl get po -l app=sleep -n apps -o jsonpath='{range .items[*]}{range @.status.containerStatuses[*]}{.name},{"ready="}{.ready},{"started="}{.started}{"n"}{end}{range @.status.initContainerStatuses[*]}{.name},{"ready="}{.ready},{"terminated="}{.state.terminated.reason}{end}' | sort

其结果显示如下:

代码语言:javascript复制
istio-init,ready=true,terminated=Completed
istio-proxy,ready=true,started=true

Istio Sidecar 容器和 Envoy 代理

Sidecar 注入是 Istio 中的关键功能之一,它简化了添加和运行附加容器作为 Pod 模板的一部分的过程。作为此注入过程的一部分,还提供了两个额外的容器:

1、istio-init – 此容器在应用程序 Pod 中配置 iptables,以便 Envoy 代理(作为单独的容器运行)可以拦截入站和出站流量。在任何其他容器启动之前,Kubernetes 会将其作为 Init 容器运行以初始化 Pod 中的网络。请注意,允许 istio-init 操作内核空间中的 iptables 确实需要升级 Kubernetes 权限。一旦成功完成任务,此容器将自动终止。在此之前,Pod 不会准备就绪。请注意,为了在部署此容器时消除任何安全问题和操作挑战,Istio 引入了 CNI 插件,因此它可以直接与底层 Kubernetes CNI 集成,而无需操作 iptables。

2、istio-proxy – 打包为上游 Envoy 代理的扩展版本。有关支持的扩展列表,请参阅官方文档。

对 Sidecar 清单的深入检查

让我们看一下我们之前部署的应用程序 Pod 中这两个容器的 YAML 清单。

代码语言:javascript复制
[administrator@JavaLangOutOfMemory ~ ] % kubectl get po -l app=sleep -n apps -o yaml

我们将查看 istio-init 和 istio-proxy 容器的摘录。

istio-init Container:

代码语言:javascript复制
initContainers:
- name: istio-init
  image: docker.io/istio/proxyv2:1.11.4
  imagePullPolicy: IfNotPresent
  args:
  - istio-iptables
  - -p
  - "15001"
  - -z
  - "15006"
  - -u
  - "1337"
  - -m
  - REDIRECT
  - -i
  - '*'
  - -x
  - ""
  - -b
  - '*'
  - -d
  - 15090,15021,15020
  env:
  - name: ISTIO_META_DNS_AUTO_ALLOCATE
    value: "true"
  - name: ISTIO_META_DNS_CAPTURE
    value: "true"
  resources:
    limits:
      cpu: "2"
      memory: 1Gi
    requests:
      cpu: 100m
      memory: 128Mi
  securityContext:
    allowPrivilegeEscalation: false
    capabilities:
      add:
      - NET_ADMIN
      - NET_RAW
      drop:
      - ALL
    privileged: false
    readOnlyRootFilesystem: false
    runAsGroup: 0
    runAsNonRoot: false
    runAsUser: 0

istio-proxy Container:

代码语言:javascript复制
containers:
- name: istio-proxy
  image: docker.io/istio/proxyv2:1.11.4
  imagePullPolicy: IfNotPresent
  args:
  - proxy
  - sidecar
  - --domain
  - $(POD_NAMESPACE).svc.cluster.local
  - --proxyLogLevel=warning
  - --proxyComponentLogLevel=misc:error
  - --log_output_level=default:info
  - --concurrency
  - "2"
  ports:
  - name: http-envoy-prom
    containerPort: 15090
    protocol: TCP
  readinessProbe:
    httpGet:
      path: /healthz/ready
      port: 15021
      scheme: HTTP
    failureThreshold: 30
    initialDelaySeconds: 1
    periodSeconds: 2
    successThreshold: 1
    timeoutSeconds: 3
  securityContext:
    allowPrivilegeEscalation: false
    capabilities:
      drop:
      - ALL
    privileged: false
    readOnlyRootFilesystem: true
    runAsGroup: 1337
    runAsNonRoot: true
    runAsUser: 1337
  env:
  - name: PROXY_CONFIG
    value: |
      {"proxyMetadata":{"ISTIO_META_DNS_AUTO_ALLOCATE":"true","ISTIO_META_DNS_CAPTURE":"true"}}
  - name: ISTIO_META_DNS_AUTO_ALLOCATE
    value: "true"
  - name: ISTIO_META_DNS_CAPTURE
    value: "true"
  ...

在这些附加参数中有一些有趣的关键点需要注意:

两个容器都由同一个镜像提供服务:docker.io/istio/proxyv2:1.11。这是什么意思,它是如何工作的?istio-iptables 和 proxy(在 args 下)命令被装载到镜像中的 Pilot-agent 二进制文件中。因此,如果我们在 istio-proxy 容器中运行 Pilot-agent 二进制文件,将看到以下内容:

代码语言:javascript复制
kubectl exec $(kubectl get po -l app=sleep -n apps -o jsonpath="{.items[0]. metadata.name}") -n apps -c istio-proxy --pilot-agent
代码语言:javascript复制
Istio Pilot agent runs in the sidecar or gateway container and bootstraps Envoy.

Usage:
  pilot-agent [command]

Available Commands:
  completion           generate the autocompletion script for the specified shell
  help                 Help about any command
  istio-clean-iptables Clean up iptables rules for Istio Sidecar
  istio-iptables       Set up iptables rules for Istio Sidecar
  proxy                XDS proxy agent
  request              Makes an HTTP request to the Envoy admin API
  version              Prints out build version information
  wait                 Waits until the Envoy proxy is ready

为了最小化攻击面,istio-init 容器中的 securityContext 节(它是 PodSecurityContext 对象的一部分)表示容器以 root 权限(runAsUser: 0)运行,但是除了 NET_ADMIN 和 NET_RAW 功能。这些功能为 istio-init Init 容器提供了运行时权限以重写应用程序 Pod 的 iptables。这在 Istio 文档中有更详细的说明。

代码语言:javascript复制
allowPrivilegeEscalation: false
    capabilities:
      add:
      - NET_ADMIN
      - NET_RAW
      drop:
      - ALL
    privileged: false
    readOnlyRootFilesystem: false
    runAsGroup: 0
    runAsNonRoot: false
    runAsUser: 0

另一方面, istio-proxy 容器以 1337 用户的受限权限运行。由于这是保留的,因此应用程序工作负载的 UID(用户 ID)必须不同且不得与 1337 冲突。1337 UID 已被任意选择 Istio 团队绕过流量重定向到 istio-proxy 容器。我们还可以看到 1337 在初始化 iptables 时用作 istio-iptables 的参数。由于此容器与应用程序工作负载一起积极运行,因此 Istio 还确保如果它受到威胁,它只能对根文件系统进行只读访问。

代码语言:javascript复制
allowPrivilegeEscalation: false
    capabilities:
      drop:
      - ALL
    privileged: false
    readOnlyRootFilesystem: true
    runAsGroup: 1337
    runAsNonRoot: true
    runAsUser: 1337

istio-proxy 容器与如下所示的就绪探针一起运行。Kubernetes 中的 Kubelet 使用此就绪探针来确定 istio-proxy 是否已准备好接受流量。只有当 istio-proxy 容器和所有相应的应用程序容器都处于运行状态并且健康探测已成功执行时,Kubelet 才会识别出 Pod 处于就绪状态。如果服务器路径的 /healthz/ready 处理程序(在pilot-agent 源代码中定义)返回成功的返回码,Kubelet 将假定容器是活动的且健康的。failureThreshold 配置指定了在容器被标记为未就绪之前此就绪探测可以失败的连续次数。

代码语言:javascript复制
readinessProbe:
    httpGet:
      path: /healthz/ready
      port: 15021
      scheme: HTTP
    initialDelaySeconds: 1
    failureThreshold: 30
    periodSeconds: 2
    successThreshold: 1
    timeoutSeconds: 3

Sidecar 注入分析

Istio 采用了两种不同的方式将 Sidecar 代理注入应用程序工作负载:手动和自动。这两种方法都遵循相同的注入原则,给定“一些”应用程序工作负载(这可以定义为更高级别的 Kubernetes 资源,如部署、Statefulset、DaemonSet 甚至 Pod),允许 Kubernetes 使用 Sidecar 注入 Sidecar 容器 模板和配置参数(istio-sidecar-injector configmap)。

Istio 中的手动边车注入

在这两种方法中,这是最容易理解的。手动注入是通过 istioctl 命令使用 kube-inject 参数完成的。我们可以使用以下任一格式进行注入:

代码语言:javascript复制
[administrator@JavaLangOutOfMemory ~ ] % istioctl kube-inject -f application.yaml | kubectl apply -f -

代码语言:javascript复制
[administrator@JavaLangOutOfMemory ~ ] % kubectl apply -f <(istioctl kube-inject -f application.yaml)

当 istioctl kube-inject 用于注入 Sidecar 时,默认情况下它将使用编写为 istio-sidecar-injector Kubernetes configmap 的集群内配置。提供了许多我们可以指定以自定义此行为的标志:

代码语言:javascript复制
--injectConfigFile string    Injection configuration filename. Cannot be used with --injectConfigMapName
--meshConfigFile string      Mesh configuration filename. Takes precedence over --meshConfigMapName if set
--meshConfigMapName string   ConfigMap name for Istio mesh configuration, key should be "mesh" (default "istio")
--injectConfigMapNam string  ConfigMap name for Istio sidecar injection, key should be "config" (default "istio-sidecar-injector")

请注意,--injectConfigMapNam 是 istioctl kube-inject 中的一个隐藏标志,它允许我们覆盖集群内 Sidecar 注入配置。或者,可以使用配置的本地副本和上面的标志来完成注入:

代码语言:javascript复制
[administrator@JavaLangOutOfMemory ~ ] % kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.config}' > inject-config.yaml
[administrator@JavaLangOutOfMemory ~ ] % kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' > inject-values.yaml
[administrator@JavaLangOutOfMemory ~ ] % kubectl -n istio-system get configmap istio -o=jsonpath='{.data.mesh}' > mesh-config.yaml
[administrator@JavaLangOutOfMemory ~ ] % istioctl kube-inject 
    --injectConfigFile inject-config.yaml 
    --meshConfigFile mesh-config.yaml 
    --valuesFile inject-values.yaml 
    --filename application.yaml 
    | kubectl apply -f -

手动注入时必须注意不要破坏 Sidecar,尤其是在使用自定义配置时。

Istio 中的自动边车注入

这被认为是在 Istio 中注入边车的事实上的方法。与手动方法相比,这涉及更少的配置步骤;但是,这取决于底层 Kubernetes 发行版是否启用了对准入控制器的支持。为此,Istio 利用了一个变异的 webhook 准入控制器。

以下是 Kubernetes 变异准入控制器在 Sidecar 注入中处理的过程:

1、首先,在 Istio 安装过程中注入的 istio-sidecar-injector mutating 配置(如下所示)向 istiod 控制器发送带有所有 Pod 信息的 webhook 请求。

2、接下来,控制器在运行时修改 Pod 规范,将 Init 和 Sidecar 容器代理引入实际的 Pod 规范。

3、然后,控制器将修改后的对象返回给准入 Webhook 进行对象验证。

4、最后经过验证,修改后的 Pod 规范与所有 Sidecar 容器一起部署。

完整配置请看 kubectl get mutatingwebhookconfiguration istio-sidecar-injector -o yaml。为简洁起见,以下摘录中仅给出了四种 webhook 配置中的两种:

代码语言:javascript复制
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: istio-sidecar-injector
webhooks:
- admissionReviewVersions:
  - v1beta1
  - v1
  clientConfig:
    caBundle: cert
    service:
      name: istiod
      namespace: istio-system
      path: /inject
      port: 443
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: namespace.sidecar-injector.istio.io
  namespaceSelector:
    matchExpressions:
    - key: istio-injection
      operator: In
      values:
      - enabled
  objectSelector:
    matchExpressions:
    - key: sidecar.istio.io/inject
      operator: NotIn
      values:
      - "false"
  reinvocationPolicy: Never
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10
- admissionReviewVersions:
  - v1beta1
  - v1
  clientConfig:
    caBundle: cert
    service:
      name: istiod
      namespace: istio-system
      path: /inject
      port: 443
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: namespace.sidecar-injector.istio.io
  namespaceSelector:
    matchExpressions:
    - key: istio-injection
      operator: In
      values:
      - enabled
  objectSelector:
    matchExpressions:
    - key: sidecar.istio.io/inject
      operator: NotIn
      values:
      - "false"
  reinvocationPolicy: Never
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10
- admissionReviewVersions:
  - v1beta1
  - v1
  clientConfig:
    caBundle: cert
    service:
      name: istiod
      namespace: istio-system
      path: /inject
      port: 443
  failurePolicy: Fail
  matchPolicy: Equivalent
  name: object.sidecar-injector.istio.io
  namespaceSelector:
    matchExpressions:
    - key: istio-injection
      operator: DoesNotExist
    - key: istio.io/rev
      operator: DoesNotExist
  objectSelector:
    matchExpressions:
    - key: sidecar.istio.io/inject
      operator: In
      values:
      - "true"
    - key: istio.io/rev
      operator: DoesNotExist
  reinvocationPolicy: Never
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: '*'
  sideEffects: None
  timeoutSeconds: 10

此配置告诉 Kubernetes 变异控制器在 HTTPS 端口上安全地将请求发送到 istiod 服务的 /inject 端点。在调用 mutating webhook 之前,Kubernetes 会检查发出请求的用户是否有权发出请求。在 Istio 中,webhook 作为 istiod 二进制文件的一部分实现。

可以使用命名空间级别的标签(istio-injection=enabled)或在对象级别作为注释(sidecar.istio.io/inject="true")触发注入。每个 webhook 配置在 namespaceSelector 和 objectSelector 中定义了这些触发器的匹配规则。当注入基于命名空间级别定义的标签时,命名空间中创建的任何部署对象(Deployment、StatefulSet、DaemonSet)都将使用 Sidecar 代理进行变异。以下是匹配规则的摘要:

命名空间标签

对象注解

Sidecar注入(Y or N)

istio-injection=enabled

sidecar.istio.io/inject="true"

istio-injection=enabled

sidecar.istio.io/inject="true"

istio-injection=enabled

sidecar.istio.io/inject="false"

istio-injection=disabled

sidecar.istio.io/inject="true"

istio-injection=disabled

sidecar.istio.io/inject="false"

当 Pod 清单被注入时,也可以直接改变 Pod 对象(如果命名空间还没有标签)。Pod 清单必须有一个标签 sidecar.istio.io/inject="true"。例如:

代码语言:javascript复制
apiVersion: v1
kind: Pod
metadata:
  name: sleep
  namespace: apps
  labels:
    app: sleep
    sidecar.istio.io/inject: "true"
...

到目前为止,我们已经了解了 Istio 的网络基础知识、数据平面和控制平面、网络和使用 Envoy 代理的 Sidecar 注入,以及 Istio 如何使用演示注入 Init 和 Sidecar 容器以及这些容器在 Pod 模板中的配置 环境。在下一篇博客中,我们将分析 iptables 是如何配置和管理的。

在 Slack 上的 Solo.io 上与我们联系,以了解有关 Istio 和我们产品的更多信息。我们还提供了许多关于 Istio 的网络研讨会和研讨会,因此请随时注册以了解更多信息。

原文:https://www.solo.io/blog/istios-networking-in-depth/?utm_source=thenewstack&utm_medium=website&utm_campaign=platform

0 人点赞