关于 gateways

gateway 是一个专用的 Envoy 代理部署,配合 Kubernetes Service 使用,运行在服务网格的边缘。它能够对进入或离开网格的流量进行细粒度控制。 在 Alauda Service Mesh 中,gateway 通过gateway 注入进行安装。

目录

关于 gateway 注入

gateway 注入利用与 sidecar 注入相同的机制,将 Envoy 代理部署到 gateway pod 中。部署 gateway 的步骤如下:

  1. 在 Istio 控制平面可见的命名空间中创建 Kubernetes Deployment 和对应的 Service
  2. 为 Deployment 添加注解和标签,使 Istio 控制平面注入配置为 gateway 的 Envoy 代理。
  3. 应用 Istio 的 GatewayVirtualService 资源以控制入口或出口流量。

Linux 内核兼容性通知

对于运行 Linux 内核版本低于 4.11(例如 CentOS 7)的节点,gateway 安装前需要额外配置。

INFO

如果您的内核版本是 4.11 或更高版本,可跳过本节。

前提条件

  • 本地安装 jq,用于处理 JSON。

操作步骤

  1. 创建一个名为 gateway-injection-template.txt 的 YAML 文件,包含 gateway 的默认注入模板。

    点击展开
    gateway-injection-template.txt
    {{- $containers := list }}
    {{- range $index, $container := .Spec.Containers }}{{ if not (eq $container.Name "istio-proxy") }}{{ $containers = append $containers $container.Name }}{{end}}{{- end}}
    metadata:
      labels:
        service.istio.io/canonical-name: {{ index .ObjectMeta.Labels `service.istio.io/canonical-name` | default (index .ObjectMeta.Labels `app.kubernetes.io/name`) | default (index .ObjectMeta.Labels `app`) | default .DeploymentMeta.Name  | quote }}
        service.istio.io/canonical-revision: {{ index .ObjectMeta.Labels `service.istio.io/canonical-revision` | default (index .ObjectMeta.Labels `app.kubernetes.io/version`) | default (index .ObjectMeta.Labels `version`) | default "latest"  | quote }}
      annotations:
        istio.io/rev: {{ .Revision | default "default" | quote }}
        {{- if ge (len $containers) 1 }}
        {{- if not (isset .ObjectMeta.Annotations `kubectl.kubernetes.io/default-logs-container`) }}
        kubectl.kubernetes.io/default-logs-container: "{{ index $containers 0 }}"
        {{- end }}
        {{- if not (isset .ObjectMeta.Annotations `kubectl.kubernetes.io/default-container`) }}
        kubectl.kubernetes.io/default-container: "{{ index $containers 0 }}"
        {{- end }}
        {{- end }}
    spec:
      securityContext:
      {{- if .Values.gateways.securityContext }}
        {{- toYaml .Values.gateways.securityContext | nindent 4 }}
      {{- else }}
        sysctls: []  
        capabilities:
          add: [CAP_NET_BIND_SERVICE]  
      {{- end }}
      containers:
      - name: istio-proxy
      {{- if contains "/" (annotation .ObjectMeta `sidecar.istio.io/proxyImage` .Values.global.proxy.image) }}
        image: "{{ annotation .ObjectMeta `sidecar.istio.io/proxyImage` .Values.global.proxy.image }}"
      {{- else }}
        image: "{{ .ProxyImage }}"
      {{- end }}
        ports:
        - containerPort: 15090
          protocol: TCP
          name: http-envoy-prom
        args:
        - proxy
        - router
        - --domain
        - $(POD_NAMESPACE).svc.{{ .Values.global.proxy.clusterDomain }}
        - --proxyLogLevel={{ annotation .ObjectMeta `sidecar.istio.io/logLevel` .Values.global.proxy.logLevel }}
        - --proxyComponentLogLevel={{ annotation .ObjectMeta `sidecar.istio.io/componentLogLevel` .Values.global.proxy.componentLogLevel }}
        - --log_output_level={{ annotation .ObjectMeta `sidecar.istio.io/agentLogLevel` .Values.global.logging.level }}
      {{- if .Values.global.sts.servicePort }}
        - --stsPort={{ .Values.global.sts.servicePort }}
      {{- end }}
      {{- if .Values.global.logAsJson }}
        - --log_as_json
      {{- end }}
      {{- if .Values.global.proxy.lifecycle }}
        lifecycle:
          {{ toYaml .Values.global.proxy.lifecycle | indent 6 }}
      {{- end }}
        securityContext:
          runAsUser: {{ .ProxyUID | default "1337" }}
          runAsGroup: {{ .ProxyGID | default "1337" }}
        env:
        - name: PILOT_CERT_PROVIDER
          value: {{ .Values.global.pilotCertProvider }}
        - name: CA_ADDR
        {{- if .Values.global.caAddress }}
          value: {{ .Values.global.caAddress }}
        {{- else }}
          value: istiod{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}.{{ .Values.global.istioNamespace }}.svc:15012
        {{- end }}
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: INSTANCE_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName
        - name: HOST_IP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: ISTIO_CPU_LIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu
        - name: PROXY_CONFIG
          value: |
                {{ protoToJSON .ProxyConfig }}
        - name: ISTIO_META_POD_PORTS
          value: |-
            [
            {{- $first := true }}
            {{- range $index1, $c := .Spec.Containers }}
              {{- range $index2, $p := $c.Ports }}
                {{- if (structToJSON $p) }}
                {{if not $first}},{{end}}{{ structToJSON $p }}
                {{- $first = false }}
                {{- end }}
              {{- end}}
            {{- end}}
            ]
        - name: GOMEMLIMIT
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
        - name: GOMAXPROCS
          valueFrom:
            resourceFieldRef:
              resource: limits.cpu
        {{- if .CompliancePolicy }}
        - name: COMPLIANCE_POLICY
          value: "{{ .CompliancePolicy }}"
        {{- end }}
        - name: ISTIO_META_APP_CONTAINERS
          value: "{{ $containers | join "," }}"
        - name: ISTIO_META_CLUSTER_ID
          value: "{{ valueOrDefault .Values.global.multiCluster.clusterName `Kubernetes` }}"
        - name: ISTIO_META_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: ISTIO_META_INTERCEPTION_MODE
          value: "{{ .ProxyConfig.InterceptionMode.String }}"
        {{- if .Values.global.network }}
        - name: ISTIO_META_NETWORK
          value: "{{ .Values.global.network }}"
        {{- end }}
        {{- if .DeploymentMeta.Name }}
        - name: ISTIO_META_WORKLOAD_NAME
          value: "{{ .DeploymentMeta.Name }}"
        {{ end }}
        {{- if and .TypeMeta.APIVersion .DeploymentMeta.Name }}
        - name: ISTIO_META_OWNER
          value: kubernetes://apis/{{ .TypeMeta.APIVersion }}/namespaces/{{ valueOrDefault .DeploymentMeta.Namespace `default` }}/{{ toLower .TypeMeta.Kind}}s/{{ .DeploymentMeta.Name }}
        {{- end}}
        {{- if .Values.global.meshID }}
        - name: ISTIO_META_MESH_ID
          value: "{{ .Values.global.meshID }}"
        {{- else if (valueOrDefault .MeshConfig.TrustDomain .Values.global.trustDomain) }}
        - name: ISTIO_META_MESH_ID
          value: "{{ (valueOrDefault .MeshConfig.TrustDomain .Values.global.trustDomain) }}"
        {{- end }}
        {{- with (valueOrDefault .MeshConfig.TrustDomain .Values.global.trustDomain)  }}
        - name: TRUST_DOMAIN
          value: "{{ . }}"
        {{- end }}
        {{- range $key, $value := .ProxyConfig.ProxyMetadata }}
        - name: {{ $key }}
          value: "{{ $value }}"
        {{- end }}
        {{with .Values.global.imagePullPolicy }}imagePullPolicy: "{{.}}"{{end}}
        readinessProbe:
          httpGet:
            path: /healthz/ready
            port: 15021
          initialDelaySeconds: {{.Values.global.proxy.readinessInitialDelaySeconds }}
          periodSeconds: {{ .Values.global.proxy.readinessPeriodSeconds }}
          timeoutSeconds: 3
          failureThreshold: {{ .Values.global.proxy.readinessFailureThreshold }}
        volumeMounts:
        - name: workload-socket
          mountPath: /var/run/secrets/workload-spiffe-uds
        - name: credential-socket
          mountPath: /var/run/secrets/credential-uds
        {{- if eq .Values.global.caName "GkeWorkloadCertificate" }}
        - name: gke-workload-certificate
          mountPath: /var/run/secrets/workload-spiffe-credentials
          readOnly: true
        {{- else }}
        - name: workload-certs
          mountPath: /var/run/secrets/workload-spiffe-credentials
        {{- end }}
        {{- if eq .Values.global.pilotCertProvider "istiod" }}
        - mountPath: /var/run/secrets/istio
          name: istiod-ca-cert
        {{- end }}
        - mountPath: /var/lib/istio/data
          name: istio-data
        # SDS channel between istioagent and Envoy
        - mountPath: /etc/istio/proxy
          name: istio-envoy
        - mountPath: /var/run/secrets/tokens
          name: istio-token
        {{- if .Values.global.mountMtlsCerts }}
        # Use the key and cert mounted to /etc/certs/ for the in-cluster mTLS communications.
        - mountPath: /etc/certs/
          name: istio-certs
          readOnly: true
        {{- end }}
        - name: istio-podinfo
          mountPath: /etc/istio/pod
      volumes:
      - emptyDir: {}
        name: workload-socket
      - emptyDir: {}
        name: credential-socket
      {{- if eq .Values.global.caName "GkeWorkloadCertificate" }}
      - name: gke-workload-certificate
        csi:
          driver: workloadcertificates.security.cloud.google.com
      {{- else}}
      - emptyDir: {}
        name: workload-certs
      {{- end }}
      # SDS channel between istioagent and Envoy
      - emptyDir:
          medium: Memory
        name: istio-envoy
      - name: istio-data
        emptyDir: {}
      - name: istio-podinfo
        downwardAPI:
          items:
            - path: "labels"
              fieldRef:
                fieldPath: metadata.labels
            - path: "annotations"
              fieldRef:
                fieldPath: metadata.annotations
      - name: istio-token
        projected:
          sources:
          - serviceAccountToken:
              path: istio-token
              expirationSeconds: 43200
              audience: {{ .Values.global.sds.token.aud }}
      {{- if eq .Values.global.pilotCertProvider "istiod" }}
      - name: istiod-ca-cert
      {{- if eq (.Values.pilot.env).ENABLE_CLUSTER_TRUST_BUNDLE_API true }}
        projected:
          sources:
          - clusterTrustBundle:
            name: istio.io:istiod-ca:root-cert
            path: root-cert.pem
      {{- else }}
        configMap:
          name: istio-ca-root-cert
      {{- end }}
      {{- end }}
      {{- if .Values.global.mountMtlsCerts }}
      # Use the key and cert mounted to /etc/certs/ for the in-cluster mTLS communications.
      - name: istio-certs
        secret:
          optional: true
          {{ if eq .Spec.ServiceAccountName "" }}
          secretName: istio.default
          {{ else -}}
          secretName: {{  printf "istio.%s" .Spec.ServiceAccountName }}
          {{  end -}}
      {{- end }}
      {{- if .Values.global.imagePullSecrets }}
      imagePullSecrets:
        {{- range .Values.global.imagePullSecrets }}
        - name: {{ . }}
        {{- end }}
      {{- end }}
    1. 移除 sysctls,因为旧版 Linux 内核不支持 net.ipv4.ip_unprivileged_port_start
    2. 添加 CAP_NET_BIND_SERVICE 能力,允许 gateway 监听 1024 以下端口。
  2. Istio 资源的默认 gateway 注入模板打补丁:

    TEMPLATE_CONTENT=$(cat gateway-injection-template.txt)
    PATCH_DATA=$(jq -n \
      --arg template "${TEMPLATE_CONTENT}" \
      '{
        "spec": {
          "values": {
            "sidecarInjectorWebhook": {
              "templates": {
                "gateway": $template
              }
            }
          }
        }
      }')
    # 最后将补丁应用到名为 default 的 Istio 资源:
    kubectl patch istio default --type=merge -p "${PATCH_DATA}"
  3. 运行以下命令,等待控制平面返回 Ready 状态:

    kubectl wait --for condition=Ready istio/default --timeout=3m

通过 gateway 注入安装 gateway

本操作步骤说明如何通过 gateway 注入安装 gateway。

INFO

以下步骤适用于 ingress 和 egress gateway 部署。

前提条件

  • 已安装 Alauda Service Mesh v2 Operator。
  • 已部署 Istio 控制平面。

操作步骤

  1. 创建 gateway 命名空间:

    kubectl create namespace <gateway_namespace>
    NOTE

    gateway 和 Istio 控制平面应安装在不同命名空间。

    您可以将 gateway 安装在专用的 gateway 命名空间中。 这种方式允许多个不同命名空间中的应用共享该 gateway。 也可以将 gateway 安装在应用命名空间中, 此时 gateway 作为该命名空间内应用的专用 gateway。

  2. 创建名为 secret-reader.yaml 的 YAML 文件,定义 gateway 部署所需的 ServiceAccount、Role 和 RoleBinding,允许 gateway 读取 secret 以获取 TLS 凭证。

    secret-reader.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: secret-reader
      namespace: <gateway_namespace>
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: secret-reader
      namespace: <gateway_namespace>
    rules:
      - apiGroups: [""]
        resources: ["secrets"]
        verbs: ["get", "watch", "list"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name:  secret-reader
      namespace: <gateway_namespace>
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: secret-reader
    subjects:
      - kind: ServiceAccount
        name:  secret-reader
  3. 运行以下命令应用 YAML 文件:

    kubectl apply -f secret-reader.yaml
  4. 创建名为 gateway-deployment.yaml 的 YAML 文件,定义 gateway 的 Kubernetes Deployment 对象。

    gateway-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: <gateway_name>
      namespace: <gateway_namespace>
    spec:
      selector:
        matchLabels:
          istio: <gateway_name>
      template:
        metadata:
          annotations:
            inject.istio.io/templates: gateway
          labels:
            istio: <gateway_name>
            sidecar.istio.io/inject: "true"
        spec:
          containers:
            - name: istio-proxy
              image: auto
              securityContext:
                capabilities:
                  drop:
                    - ALL
                allowPrivilegeEscalation: false
                privileged: false
                readOnlyRootFilesystem: true
                runAsNonRoot: true
              ports:
                - containerPort: 15090
                  protocol: TCP
                  name: http-envoy-prom
              resources:
                limits:
                  cpu: 2000m
                  memory: 1024Mi
                requests:
                  cpu: 100m
                  memory: 128Mi
          serviceAccountName: secret-reader
    1. 表示 Istio 控制平面使用 gateway 注入模板替代默认的 sidecar 模板。
    2. 确保为 gateway 部署设置唯一标签。 唯一标签用于 Istio Gateway 资源选择 gateway 工作负载。
    3. 通过设置 sidecar.istio.io/inject 标签为 true 启用 gateway 注入。 如果 Istio 资源名称不是 default,需使用 istio.io/rev: <istio_revision> 标签,revision 表示 Istio 资源的活动版本。
    4. 将 image 字段设置为 auto,使镜像在每次 pod 启动时自动更新。
    5. serviceAccountName 设置为之前创建的 ServiceAccount 名称。
  5. 运行以下命令应用 YAML 文件:

    kubectl apply -f gateway-deployment.yaml
  6. 运行以下命令验证 gateway Deployment 是否成功滚动更新:

    kubectl rollout status deployment/<gateway_name> -n <gateway_namespace>

    您应看到类似如下输出:

    示例输出

    Waiting for deployment "<gateway_name>" rollout to finish: 0 of 1 updated replicas are available...
    deployment "<gateway_name>" successfully rolled out
  7. 创建名为 gateway-service.yaml 的 YAML 文件,定义 gateway 的 Kubernetes Service 对象。

    gateway-service.yaml
    apiVersion: v1
    kind: Service
    metadata:
      name: <gateway_name>
      namespace: <gateway_namespace>
    spec:
      type: ClusterIP
      selector:
        istio: <gateway_name>
      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
    1. spec.type 设置为 ClusterIP 时,gateway Service 只能在集群内部访问。 若 gateway 需处理来自集群外部的入口流量,应将 spec.type 设置为 LoadBalancer
    2. selector 设置为之前 gateway 部署的 pod 模板中指定的唯一标签或标签集合。
  8. 运行以下命令应用 YAML 文件:

    kubectl apply -f gateway-service.yaml
  9. 运行以下命令验证 gateway 服务是否正确指向 gateway pod 的端点:

    kubectl get endpoints <gateway_name> -n <gateway_namespace>

您应看到类似如下示例输出:

示例输出

NAME              ENDPOINTS                             AGE
<gateway_name>    10.131.0.181:8080,10.131.0.181:8443   1m
  1. 可选:创建名为 gateway-hpa.yaml 的 YAML 文件,定义 gateway 的水平 Pod 自动扩缩器。 以下示例将最小副本数设置为 2,最大副本数设置为 5,当平均 CPU 利用率超过 CPU 资源限制的 80% 时扩容。 该限制在 gateway 部署的 pod 模板中指定。

    gateway-service.yaml
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: <gateway_name>
      namespace: <gateway_namespace>
    spec:
      minReplicas: 2
      maxReplicas: 5
      metrics:
      - resource:
          name: cpu
          target:
            averageUtilization: 80
            type: Utilization
        type: Resource
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: <gateway_name>
    1. spec.scaleTargetRef.name 设置为之前创建的 gateway 部署名称。
  2. 可选:运行以下命令应用 YAML 文件:

    kubectl apply -f gateway-service.yaml
  3. 可选:创建名为 gateway-pdb.yaml 的 YAML 文件,定义 gateway 的 Pod 中断预算。 以下示例允许只有在驱逐后集群中至少还有 1 个健康 gateway pod 时,才允许驱逐 gateway pod。

    gateway-pdb.yaml
    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: <gateway_name>
      namespace: <gateway_namespace>
    spec:
      minAvailable: 1
      selector:
        matchLabels:
          istio: <gateway_name>
    1. spec.selector.matchLabels 设置为之前 gateway 部署的 pod 模板中指定的唯一标签或标签集合。
  4. 可选:运行以下命令应用 YAML 文件:

    kubectl apply -f gateway-pdb.yaml