教程:在 GKE 集群上使用 Cloud Service Mesh 出站流量网关


本教程介绍了如何使用 Cloud Service Mesh 出站流量网关和其他 Google Cloud 控制措施来确保出站流量安全 (出站流量)来自部署在 Google Kubernetes Engine 集群上的工作负载。本教程是在 GKE 集群上使用 Cloud Service Mesh 出站流量网关的最佳实践的配套教程。

本教程的目标受众包括管理由一个或多个软件交付团队使用的 Google Kubernetes Engine 集群的网络、平台和安全工程师。对于必须展示其遵从了法规(例如 GDPRPCI)的组织,此处介绍的控制措施尤为有用。

目标

  • 设置运行 Cloud Service Mesh 的基础架构:
  • 安装 Cloud Service Mesh。
  • 安装在专用节点池上运行的出站流量网关代理。
  • 为通过出站流量网关的外部流量配置多租户路由规则:
    • 命名空间 team-x 中的应用可以连接到 example.com
    • 命名空间 team-y 中的应用可以连接到 httpbin.org
  • 使用 Sidecar 资源限制每个命名空间的 Sidecar 代理出站流量配置的范围。
  • 配置授权政策以强制执行出站规则。
  • 配置出站流量网关,将纯文本 HTTP 请求升级为 TLS(TLS 源)。
  • 配置出站流量网关以直通 TLS 流量。
  • 设置 Kubernetes 网络政策作为额外的出站流量控制措施。
  • 使用专用 Google 访问通道和 Identity and Access Management (IAM) 权限配置对 Google API 的直接访问。

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本教程后,您可以删除所创建的资源以避免持续产生费用。如需了解详情,请参阅清理

准备工作

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  2. Make sure that billing is enabled for your Google Cloud project.

  3. In the Google Cloud console, activate Cloud Shell.

    Activate Cloud Shell

  4. 创建在学习本教程时使用的工作目录:

    mkdir -p ~/WORKING_DIRECTORY
    cd ~/WORKING_DIRECTORY
    
  5. 创建一个 shell 脚本,用于为本教程初始化您的环境。根据您的项目和偏好替换和修改变量。如果 shell 会话过期,使用 source 命令运行此脚本来重新初始化您的环境:

    cat << 'EOF' > ./init-egress-tutorial.sh
    #! /usr/bin/env bash
    PROJECT_ID=YOUR_PROJECT_ID
    REGION=REGION
    ZONE=ZONE
    
    gcloud config set project ${PROJECT_ID}
    gcloud config set compute/region ${REGION}
    gcloud config set compute/zone ${ZONE}
    
    EOF
    
  6. 启用 compute.googleapis.com

    gcloud services enable compute.googleapis.com --project=YOUR_PROJECT_ID
    
  7. 将脚本设为可执行文件,并使用 source 命令运行该脚本来初始化您的环境。如果系统提示您启用 compute.googleapis.com,请选择 Y

    chmod +x ./init-egress-tutorial.sh
    source ./init-egress-tutorial.sh
    

设置基础架构

创建 VPC 网络和子网

  1. 创建新的 VPC 网络:

    gcloud compute networks create vpc-network \
        --subnet-mode custom
    
  2. 创建集群运行的子网,并为 pod 和服务预先分配次要 IP 地址范围。启用专用 Google 访问通道,使仅具有内部 IP 地址的应用可以访问 Google API 和服务:

    gcloud compute networks subnets create subnet-gke \
        --network vpc-network \
        --range 10.0.0.0/24 \
        --secondary-range pods=10.1.0.0/16,services=10.2.0.0/20 \
        --enable-private-ip-google-access
    

配置 Cloud NAT

Cloud NAT 允许没有外部 IP 地址的工作负载连接到互联网上的目标,并接收来自这些目标的入站响应。

  1. 创建 Cloud Router 路由器:

    gcloud compute routers create nat-router \
        --network vpc-network
    
  2. 向路由器添加 NAT 配置:

    gcloud compute routers nats create nat-config \
        --router nat-router \
        --nat-all-subnet-ip-ranges \
        --auto-allocate-nat-external-ips
    

为每个 GKE 节点池创建服务账号

创建两个服务账号供两个 GKE 节点池使用。系统会为每个节点池分配一个单独的服务账号,以便您可以将 VPC 防火墙规则应用于特定节点。

  1. 创建一个供默认节点池中的节点使用的服务账号:

    gcloud iam service-accounts create sa-application-nodes \
        --description="SA for application nodes" \
        --display-name="sa-application-nodes"
    
  2. 创建一个供网关节点池中的节点使用的服务账号:

    gcloud iam service-accounts create sa-gateway-nodes \
        --description="SA for gateway nodes" \
        --display-name="sa-gateway-nodes"
    

向服务账号授予权限

向应用和网关服务账号添加一组最少的 IAM 角色。日志记录、监控以及从 Container Registry 拉取私有容器映像需要这些角色。

    project_roles=(
        roles/logging.logWriter
        roles/monitoring.metricWriter
        roles/monitoring.viewer
        roles/storage.objectViewer
    )
    for role in "${project_roles[@]}"
    do
        gcloud projects add-iam-policy-binding ${PROJECT_ID} \
            --member="serviceAccount:sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com" \
            --role="$role"
        gcloud projects add-iam-policy-binding ${PROJECT_ID} \
            --member="serviceAccount:sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com" \
            --role="$role"
    done

创建防火墙规则

在以下步骤中,您将对 VPC 网络应用防火墙规则,从而默认拒绝所有出站流量。要使集群能够正常运行以及网关节点能够访问 VPC 外部的目标,需要进行特定的连接。一组最低的特定防火墙规则会覆盖默认的全部拒绝规则,以允许必要的连接。

  1. 创建默认(低优先级)防火墙规则,拒绝来自 VPC 网络的所有出站流量:

    gcloud compute firewall-rules create global-deny-egress-all \
        --action DENY \
        --direction EGRESS \
        --rules all \
        --destination-ranges 0.0.0.0/0 \
        --network vpc-network \
        --priority 65535 \
        --description "Default rule to deny all egress from the network."
    
  2. 创建一条规则,仅允许具有网关服务账号的节点访问互联网:

    gcloud compute firewall-rules create gateway-allow-egress-web \
        --action ALLOW \
        --direction EGRESS \
        --rules tcp:80,tcp:443 \
        --target-service-accounts sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com \
        --network vpc-network \
        --priority 1000 \
        --description "Allow the nodes running the egress gateways to connect to the web"
    
  3. 允许节点访问 Kubernetes 控制层面:

    gcloud compute firewall-rules create allow-egress-to-api-server \
        --action ALLOW \
        --direction EGRESS \
        --rules tcp:443,tcp:10250 \
        --target-service-accounts sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com,sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com \
        --destination-ranges 10.5.0.0/28 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow nodes to reach the Kubernetes API server."
    
  4. 可选:如果您使用代管式 Cloud Service Mesh,则不需要此防火墙规则。

    在将 Sidecar 代理注入 Sidecar 代理时,Cloud Service Mesh 会使用 Webhook 工作负载允许 GKE API 服务器调用节点上运行的服务网格控制平面公开的网络钩子:

    gcloud compute firewall-rules create allow-ingress-api-server-to-webhook \
        --action ALLOW \
        --direction INGRESS \
        --rules tcp:15017 \
        --target-service-accounts sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com,sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com \
        --source-ranges 10.5.0.0/28 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow the API server to call the webhooks exposed by istiod discovery"
    
  5. 允许节点与集群上运行的 Pod 之间建立出站流量连接。GKE 会自动创建相应的入站流量规则。Service 连接不需要规则,因为 iptables 路由链始终会将 Service IP 地址转换为 Pod IP 地址。

    gcloud compute firewall-rules create allow-egress-nodes-and-pods \
        --action ALLOW \
        --direction EGRESS \
        --rules all \
        --target-service-accounts sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com,sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com \
        --destination-ranges 10.0.0.0/24,10.1.0.0/16 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow egress to other Nodes and Pods"
    
  6. 允许访问专用 Google 访问通道用于提供 Google API、Container Registry 和其他服务的预留 IP 地址集:

    gcloud compute firewall-rules create allow-egress-gcp-apis \
        --action ALLOW \
        --direction EGRESS \
        --rules tcp \
        --target-service-accounts sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com,sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com \
        --destination-ranges 199.36.153.8/30 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow access to the VIPs used by Google Cloud APIs (Private Google Access)"
    
  7. 允许 Google Cloud 健康检查程序服务访问集群中运行的 pod。如需了解详情,请参阅健康检查

    gcloud compute firewall-rules create allow-ingress-gcp-health-checker \
        --action ALLOW \
        --direction INGRESS \
        --rules tcp:80,tcp:443 \
        --target-service-accounts sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com,sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com \
        --source-ranges 35.191.0.0/16,130.211.0.0/22,209.85.152.0/22,209.85.204.0/22 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow workloads to respond to Google Cloud health checks"
    

配置 Google Cloud API 的专用访问通道

启用专用 Google 访问通道后,只有内部 IP 地址的虚拟机和 pod 可以访问 Google API 和服务。虽然 Google API 和服务是从外部 IP 提供的,但在使用专用 Google 访问通道时,来自节点的流量始终不会离开 Google 网络。

启用 Cloud DNS API:

gcloud services enable dns.googleapis.com

创建专用 DNS 区域、CNAMEA 记录,以便节点和工作负载可以使用专用 Google 访问通道和 private.googleapis.com 主机名连接到 Google API 和服务:

gcloud dns managed-zones create private-google-apis \
    --description "Private DNS zone for Google APIs" \
    --dns-name googleapis.com \
    --visibility private \
    --networks vpc-network

gcloud dns record-sets transaction start --zone private-google-apis

gcloud dns record-sets transaction add private.googleapis.com. \
    --name "*.googleapis.com" \
    --ttl 300 \
    --type CNAME \
    --zone private-google-apis

gcloud dns record-sets transaction add "199.36.153.8" \
"199.36.153.9" "199.36.153.10" "199.36.153.11" \
    --name private.googleapis.com \
    --ttl 300 \
    --type A \
    --zone private-google-apis

gcloud dns record-sets transaction execute --zone private-google-apis

配置 Container Registry 的专用访问通道

创建专用 DNS 区域、CNAMEA 记录,以便节点可以使用专用 Google 访问通道和 gcr.io 主机名连接到 Container Registry:

gcloud dns managed-zones create private-gcr-io \
    --description "private zone for Container Registry" \
    --dns-name gcr.io \
    --visibility private \
    --networks vpc-network

gcloud dns record-sets transaction start --zone private-gcr-io

gcloud dns record-sets transaction add gcr.io. \
    --name "*.gcr.io" \
    --ttl 300 \
    --type CNAME \
    --zone private-gcr-io

gcloud dns record-sets transaction add "199.36.153.8" "199.36.153.9" "199.36.153.10" "199.36.153.11" \
    --name gcr.io \
    --ttl 300 \
    --type A \
    --zone private-gcr-io

gcloud dns record-sets transaction execute --zone private-gcr-io

创建专用 GKE 集群

  1. 找到您的 Cloud Shell 的外部 IP 地址,以便将其添加到允许访问集群的 API 服务器的网络列表:

    SHELL_IP=$(dig TXT -4 +short @ns1.google.com o-o.myaddr.l.google.com)
    

    如果 Cloud Shell 虚拟机有一段时间处于不活动状态,其外部 IP 地址可能会更改。如果发生这种情况,您必须更新集群的授权网络列表。将以下命令添加到初始化脚本中:

    cat << 'EOF' >> ./init-egress-tutorial.sh
    SHELL_IP=$(dig TXT -4 +short @ns1.google.com o-o.myaddr.l.google.com)
    gcloud container clusters update cluster1 \
        --enable-master-authorized-networks \
        --master-authorized-networks ${SHELL_IP//\"}/32
    EOF
    
  2. 启用 Google Kubernetes Engine API:

    gcloud services enable container.googleapis.com
    
  3. 创建专用 GKE 集群:

    gcloud container clusters create cluster1 \
        --enable-ip-alias \
        --enable-private-nodes \
        --release-channel "regular" \
        --enable-master-authorized-networks \
        --master-authorized-networks ${SHELL_IP//\"}/32 \
        --master-ipv4-cidr 10.5.0.0/28 \
        --enable-dataplane-v2 \
        --service-account "sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com" \
        --machine-type "e2-standard-4" \
        --network "vpc-network" \
        --subnetwork "subnet-gke" \
        --cluster-secondary-range-name "pods" \
        --services-secondary-range-name "services" \
        --workload-pool "${PROJECT_ID}.svc.id.goog" \
        --zone ${ZONE}
    

    创建集群可能需要几分钟时间。集群的专用节点具有内部 IP 地址。系统会向 pod 和服务分配您在创建 VPC 子网时定义的已命名次要范围内的 IP。

    具有集群内控制平面的 Cloud Service Mesh 需要集群 设置为使用至少具有 4 个 vCPU 的机器类型。

    Google 建议为集群订阅“常规”发布版本 确保节点运行的 Kubernetes 版本 Cloud Service Mesh 支持。

    如需详细了解使用集群内控制平面运行 Cloud Service Mesh 的前提条件,请参阅集群内前提条件

    如需详细了解运行代管式 Cloud Service Mesh 的要求和限制,请参阅代管式 Cloud Service Mesh 支持的功能

    适用于 GKE 的 Workload Identity Federation 是 集群上启用的 IP 地址Cloud Service Mesh 需要适用于 GKE 的工作负载身份联合,这也是从 GKE 工作负载访问 Google API 的推荐方法。

  4. 创建一个名为 gateway 的节点池。出站流量网关将部署在这个节点池中。将 dedicated=gateway:NoSchedule 污点 添加到网关节点池中的每个节点。

    gcloud container node-pools create "gateway" \
        --cluster "cluster1" \
        --machine-type "e2-standard-4" \
        --node-taints dedicated=gateway:NoSchedule \
        --service-account "sa-gateway-nodes@${PROJECT_ID}.iam.gserviceaccount.com" \
        --num-nodes "1"
    

    Kubernetes 污点和容忍有助于确保网关节点池中的节点上只运行出站流量网关 pod。

  5. 下载凭据,以便您可以使用 kubectl 连接到集群:

    gcloud container clusters get-credentials cluster1
    
  6. 验证网关节点是否具有正确的污点:

    kubectl get nodes -l cloud.google.com/gke-nodepool=gateway -o yaml \
    -o=custom-columns='name:metadata.name,taints:spec.taints[?(@.key=="dedicated")]'
    

    输出类似于以下内容:

    name                                 taints
    gke-cluster1-gateway-9d65b410-cffs   map[effect:NoSchedule key:dedicated value:gateway]
    

安装和设置 Cloud Service Mesh

按照 Cloud Service Mesh 的安装指南之一操作:

安装 Cloud Service Mesh 后,您可以停止并返回本教程,而无需安装入站流量或出站流量网关。

安装出站流量网关

  1. 为出站流量网关创建 Kubernetes 命名空间:

    kubectl create namespace istio-egress
    
  2. 启用用于注入的命名空间。具体步骤取决于您的控制平面实现

    受管 (TD)

    将默认注入标签应用于命名空间:

    kubectl label namespace istio-egress \
        istio.io/rev- istio-injection=enabled --overwrite
    

    受管理(Istiod)

    推荐:运行以下命令可将默认注入标签应用于命名空间:

      kubectl label namespace istio-egress \
          istio.io/rev- istio-injection=enabled --overwrite
    

    如果您是使用 Managed Istiod 控制平面的现有用户: 我们建议您使用默认注入,但基于修订版本的注入 支持。请按照以下说明操作:

    1. 运行以下命令以查找可用的发布渠道:

      kubectl -n istio-system get controlplanerevision
      

      输出类似于以下内容:

      NAME                AGE
      asm-managed-rapid   6d7h
      

      在输出中,NAME 列下的值是与 Cloud Service Mesh 版本的可用发布版本对应的修订版本标签。

    2. 将修订版本标签应用于命名空间:

      kubectl label namespace istio-egress \
          istio-injection- istio.io/rev=REVISION_LABEL --overwrite
      

    集群内

    建议:运行以下命令,将默认注入标签应用于命名空间:

      kubectl label namespace istio-egress \
          istio.io/rev- istio-injection=enabled --overwrite
    

    我们建议您使用默认注入,但也支持基于修订版本的注入:请按照以下说明操作:

    1. 使用以下命令查找 istiod 的修订版本标签:

      kubectl get deploy -n istio-system -l app=istiod -o \
         jsonpath={.items[*].metadata.labels.'istio\.io\/rev'}'{"\n"}'
      
    2. 将修订版本标签应用于命名空间。在以下命令中,REVISION_LABEL 是您在上一步中记下的 istiod 修订版本标签的值。

      kubectl label namespace istio-egress \
          istio-injection- istio.io/rev=REVISION_LABEL --overwrite
      
  3. 创建运算符清单 对于出站流量网关:

    cat << EOF > egressgateway-operator.yaml
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    metadata:
      name: egressgateway-operator
      annotations:
        config.kubernetes.io/local-config: "true"
    spec:
      profile: empty
      revision: REVISION
      components:
        egressGateways:
        - name: istio-egressgateway
          namespace: istio-egress
          enabled: true
      values:
        gateways:
          istio-egressgateway:
            injectionTemplate: gateway
            tolerations:
              - key: "dedicated"
                operator: "Equal"
                value: "gateway"
            nodeSelector:
              cloud.google.com/gke-nodepool: "gateway"
    EOF
    
  4. 下载 istioctl 工具。即使您使用的是 Cloud Service Mesh 1.15 或更低版本,也必须使用 1.16.2-asm.2 或更高版本。请参阅下载正确的 istioctl 版本

  5. 解压缩下载的归档文件后,设置一个环境变量来保存 istioctl 工具的路径,并将其添加到初始化脚本中:

    ISTIOCTL=$(find "$(pwd -P)" -name istioctl)
    echo "ISTIOCTL=\"${ISTIOCTL}\"" >> ./init-egress-tutorial.sh
    
  6. 使用 Operator 清单和 istioctl 创建出站流量网关安装清单:

    ${ISTIOCTL} manifest generate \
        --filename egressgateway-operator.yaml \
        --output egressgateway \
        --cluster-specific
    
  7. 安装出站流量网关:

    kubectl apply --recursive --filename egressgateway/
    
  8. 检查出站流量网关是否在 gateway 节点池中的节点上运行:

    kubectl get pods -n istio-egress -o wide
    
  9. 出站流量网关 pod 对 gateway 节点池中的节点具有 affinity 以及使其在污点网关节点上运行的容忍设置。检查出站流量网关 pod 的节点亲和性和容忍设置:

    kubectl -n istio-egress get pod -l istio=egressgateway \
        -o=custom-columns='name:metadata.name,node-affinity:spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms,tolerations:spec.tolerations[?(@.key=="dedicated")]'
    

    输出内容类似如下:

    name                                   node-affinity                                                                                   tolerations
    istio-egressgateway-754d9684d5-jjkdz   [map[matchExpressions:[map[key:cloud.google.com/gke-nodepool operator:In values:[gateway]]]]]   map[key:dedicated operator:Equal value:gateway]
    

启用 Envoy 访问日志记录

启用 Envoy 访问日志所需的步骤取决于您的 Cloud Service Mesh 类型(代管式或集群内):

准备网格和测试应用

  1. 确保已启用 STRICT 双向 TLS。为 istio-system 命名空间中的网格应用默认的 PeerAuthentication 政策:

    cat <<EOF | kubectl apply -f -
    apiVersion: "security.istio.io/v1beta1"
    kind: "PeerAuthentication"
    metadata:
      name: "default"
      namespace: "istio-system"
    spec:
      mtls:
        mode: STRICT
    EOF
    

    您可以通过在特定命名空间中创建 PeerAuthentication 资源来替换此配置。

  2. 创建用于部署测试工作负载的命名空间。本教程的后续步骤介绍了如何为每个命名空间配置不同的出站流量路由规则。

    kubectl create namespace team-x
    kubectl create namespace team-y
    
  3. 为命名空间添加标签,以便 Kubernetes 网络政策可以进行选择:

    kubectl label namespace team-x team=x
    kubectl label namespace team-y team=y
    
  4. 为了让 Cloud Service Mesh 自动注入代理 Sidecar,您需要 在工作负载命名空间上设置控制平面修订版本标签:

    kubectl label ns team-x istio.io/rev- istio-injection=enabled --overwrite
    kubectl label ns team-y istio.io/rev- istio-injection=enabled --overwrite
    
  5. 创建用于进行测试部署的 YAML 文件:

    cat << 'EOF' > ./test.yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: test
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: test
      labels:
        app: test
    spec:
      ports:
      - port: 80
        name: http
      selector:
        app: test
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: test
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: test
      template:
        metadata:
          labels:
            app: test
        spec:
          serviceAccountName: test
          containers:
          - name: test
            image: gcr.io/google.com/cloudsdktool/cloud-sdk:slim
            command: ["/bin/sleep", "infinity"]
            imagePullPolicy: IfNotPresent
    EOF
    
  6. 将测试应用部署到 team-x 命名空间:

    kubectl -n team-x create -f ./test.yaml
    
  7. 验证测试应用已部署到默认池中的节点,并已注入代理 Sidecar 容器。重复以下命令,直到 pod 的状态为 Running

    kubectl -n team-x get po -l app=test -o wide
    

    输出内容类似如下:

    NAME                   READY   STATUS    RESTARTS   AGE   IP          NODE                                      NOMINATED NODE   READINESS GATES
    test-d5bdf6f4f-9nxfv   2/2     Running   0          19h   10.1.1.25   gke-cluster1-default-pool-f6c7a51f-wbzj
    

    两个容器均为 Running 状态。一个容器是测试应用,另一个是代理 Sidecar。

    pod 在默认节点池中的节点上运行。

  8. 验证是否无法从测试容器向外部网站发出 HTTP 请求:

    kubectl -n team-x exec -it \
        $(kubectl -n team-x get pod -l app=test -o jsonpath={.items..metadata.name}) \
        -c test -- curl -v http://example.com
    

    由于 global-deny-egress-all 防火墙规则拒绝上游连接,因此边车代理会生成错误消息。

使用 Sidecar 资源限制 Sidecar 代理配置的范围

您可以使用 Sidecar 资源来限制为 Sidecar 代理配置的出站流量监听器的范围。为了减少配置臃肿和内存用量,最好为每个命名空间应用默认的 Sidecar 资源。

Cloud Service Mesh 在 Sidecar 中运行的代理是 Envoy。在 Envoy 术语中,cluster 是一组逻辑上相似的上游端点,用作负载均衡的目标。

  1. 运行 istioctl proxy-config 命令,检查 Envoy Sidecar 代理中为测试 pod 配置的出站集群:

    ${ISTIOCTL} pc c $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}).team-x --direction outbound
    

    列表中大约有 11 个 Envoy 集群,包括一些用于出站流量网关的集群。

  2. 将代理配置限制为在出站流量和 team-x 命名空间中使用服务条目明确定义的出站路由。将 Sidecar 资源应用于 team-x 命名空间:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: Sidecar
    metadata:
      name: default
      namespace: team-x
    spec:
      outboundTrafficPolicy:
        mode: REGISTRY_ONLY
      egress:
      - hosts:
        - 'istio-egress/*'
        - 'team-x/*'
    EOF
    

    将出站流量政策模式设置为 REGISTRY_ONLY 会限制代理配置,使其仅包含通过定义服务条目明确添加到网格服务注册表的外部主机。

    设置 egress.hosts 会指定边车代理仅选择使用 exportTo 属性提供的出站流量命名空间中的路由。“team-x/*”部分包含在 team-x 命名空间中本地配置的任何路由。

  3. 查看在 Envoy Sidecar 代理中配置的出站集群,并将它们与应用 Sidecar 资源之前配置的集群列表进行比较:

    ${ISTIOCTL} pc c $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}).team-x --direction outbound
    

    您会看到用于出站流量网关的集群和一个用于测试 pod 本身的集群。

配置 Cloud Service Mesh 以通过出站流量网关路由流量

  1. 为端口 80 上的 HTTP 流量配置 GatewayGateway 用于选择您部署到出站流量命名空间的出站流量网关代理。Gateway 配置会应用于出站流量命名空间并处理任何主机的流量。

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
      name: egress-gateway
      namespace: istio-egress
    spec:
      selector:
        istio: egressgateway
      servers:
      - port:
          number: 80
          name: https
          protocol: HTTPS
        hosts:
          - '*'
        tls:
          mode: ISTIO_MUTUAL
    EOF
    
  2. 使用双向 TLS 为出站流量网关创建 DestinationRule 以进行身份验证和加密。对所有外部主机使用一个共享目标规则。

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: target-egress-gateway
      namespace: istio-egress
    spec:
      host: istio-egressgateway.istio-egress.svc.cluster.local
      subsets:
      - name: target-egress-gateway-mTLS
        trafficPolicy:
          tls:
            mode: ISTIO_MUTUAL
    EOF
    
  3. 在出站流量命名空间中创建一个 ServiceEntry,以明确在网格的服务注册表中为 team-x 命名空间注册 example.com:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: ServiceEntry
    metadata:
      name: example-com-ext
      namespace: istio-egress
      labels:
        # Show this service and its telemetry in the Cloud Service Mesh page of the Google Cloud console
        service.istio.io/canonical-name: example.com
    spec:
      hosts:
      - example.com
      ports:
      - number: 80
        name: http
        protocol: HTTP
      - number: 443
        name: tls
        protocol: TLS
      resolution: DNS
      location: MESH_EXTERNAL
      exportTo:
      - 'team-x'
      - 'istio-egress'
    EOF
    
  4. 创建 VirtualService,通过出站流量网关将流量路由到 example.com。有两个匹配条件:第一个条件将流量定向到出站流量网关,第二个条件将流量从出站流量网关定向到目标主机。exportTo 属性控制哪些命名空间可以使用虚拟服务。

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: example-com-through-egress-gateway
      namespace: istio-egress
    spec:
      hosts:
      - example.com
      gateways:
      - istio-egress/egress-gateway
      - mesh
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-mTLS
            port:
              number: 80
          weight: 100
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 80
        route:
        - destination:
            host: example.com
            port:
              number: 80
          weight: 100
      exportTo:
      - 'istio-egress'
      - 'team-x'
    EOF
    
  5. 运行 istioctl analyze 检查是否存在配置错误:

    ${ISTIOCTL} analyze -n istio-egress --revision REVISION
    

    输出内容类似如下:

    ✔ No validation issues found when analyzing namespace: istio-egress.
    
  6. 通过出站流量网关向外部网站发送多个请求:

    for i in {1..4}
    do
        kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
            -o jsonpath={.items..metadata.name}) -c test -- \
        curl -s -o /dev/null -w "%{http_code}\n" http://example.com
    done
    

    您将看到全部 4 个响应的状态代码均为 200

  7. 检查代理访问日志,以验证请求是否通过出站流量网关定向。首先查看随测试应用部署的代理 Sidecar 的访问日志:

    kubectl -n team-x logs -f $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) istio-proxy
    

    对于您发送的每个请求,您将看到类似于以下内容的日志条目:

    [2020-09-14T17:37:08.045Z] "HEAD / HTTP/1.1" 200 - "-" "-" 0 0 5 4 "-" "curl/7.67.0" "d57ea5ad-90e9-46d9-8b55-8e6e404a8f9b" "example.com" "10.1.4.12:8080" outbound|80||istio-egressgateway.istio-egress.svc.cluster.local 10.1.0.17:42140 93.184.216.34:80 10.1.0.17:60326 - -
    
  8. 查看出站流量网关访问日志:

    kubectl -n istio-egress logs -f $(kubectl -n istio-egress get pod -l istio=egressgateway \
        -o jsonpath="{.items[0].metadata.name}") istio-proxy
    

    对于您发送的每个请求,您将看到类似于以下内容的出站流量网关访问日志条目:

    [2020-09-14T17:37:08.045Z] "HEAD / HTTP/2" 200 - "-" "-" 0 0 4 3 "10.1.0.17" "curl/7.67.0" "095711e6-64ef-4de0-983e-59158e3c55e7" "example.com" "93.184.216.34:80" outbound|80||example.com 10.1.4.12:37636 10.1.4.12:8080 10.1.0.17:44404 outbound_.80_.target-egress-gateway-mTLS_.istio-egressgateway.istio-egress.svc.cluster.local -
    

为第二个命名空间配置不同的路由

为第二个外部主机配置路由,以了解如何为不同的团队配置不同的外部连接。

  1. team-y 命名空间创建 Sidecar 资源:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: Sidecar
    metadata:
      name: default
      namespace: team-y
    spec:
      outboundTrafficPolicy:
        mode: REGISTRY_ONLY
      egress:
      - hosts:
        - 'istio-egress/*'
        - 'team-y/*'
    EOF
    
  2. 将测试应用部署到 team-y 命名空间:

    kubectl -n team-y create -f ./test.yaml
    
  3. 注册第二个外部主机,并将其导出到 team-xteam-y 命名空间:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: ServiceEntry
    metadata:
      name: httpbin-org-ext
      namespace: istio-egress
      labels:
        # Show this service and its telemetry in the Cloud Service Mesh page of the Google Cloud console
        service.istio.io/canonical-name: httpbin.org
    spec:
      hosts:
      - httpbin.org
      ports:
      - number: 80
        name: http
        protocol: HTTP
      - number: 443
        name: tls
        protocol: TLS
      resolution: DNS
      location: MESH_EXTERNAL
      exportTo:
      - 'istio-egress'
      - 'team-x'
      - 'team-y'
    EOF
    
  4. 创建虚拟服务,通过出站流量网关将流量路由到 httpbin.org:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: httpbin-org-through-egress-gateway
      namespace: istio-egress
    spec:
      hosts:
      - httpbin.org
      gateways:
      - istio-egress/egress-gateway
      - mesh
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-mTLS
            port:
              number: 80
          weight: 100
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 80
        route:
        - destination:
            host: httpbin.org
            port:
              number: 80
          weight: 100
      exportTo:
      - 'istio-egress'
      - 'team-x'
      - 'team-y'
    EOF
    
  5. 运行 istioctl analyze 检查是否存在配置错误:

    ${ISTIOCTL} analyze -n istio-egress --revision REVISION
    

    您可以看到以下信息:

    ✔ No validation issues found when analyzing namespace: istio-egress.
    
  6. team-y 测试应用向 httpbin.org 发出请求:

    kubectl -n team-y exec -it $(kubectl -n team-y get pod -l app=test -o \
        jsonpath={.items..metadata.name}) -c test -- curl -I http://httpbin.org
    

    您会看到 200 OK 响应。

  7. team-x 测试应用向 httpbin.org 发出请求:

    kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -I http://httpbin.org
    

    您会看到 200 OK 响应。

  8. 尝试从 team-y 命名空间向 example.com 发出请求:

    kubectl -n team-y exec -it $(kubectl -n team-y get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -I http://example.com
    

    由于没有为 example.com 主机配置出站路由,因此请求失败。

使用授权政策进一步控制流量

在本教程中,出站流量网关的授权政策是在 istio-egress 命名空间中创建的。您可以配置 Kubernetes RBAC,使得只有网络管理员才能访问 istio-egress 命名空间。

  1. 创建 AuthorizationPolicy,使 team-x 命名空间中的应用在使用端口 80 发送请求时,可以连接到 example.com,但不能连接到其他外部主机。出站流量网关 pod 上的对应 targetPort 是 8080。

    cat <<EOF | kubectl apply -f -
    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: egress-team-x-to-example-com
      namespace: istio-egress
    spec:
      action: ALLOW
      rules:
        - from:
          - source:
              namespaces:
              - 'team-x'
          to:
          - operation:
              hosts:
                - 'example.com'
          when:
          - key: destination.port
            values: ["8080"]
    EOF
    
  2. 验证 team-x 命名空间中的测试应用能否向 example.com 发出请求:

    kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -I http://example.com
    

    您会看到 200 OK 响应。

  3. 尝试从 team-x 命名空间中的测试应用向 httpbin.org 发出请求:

    kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -s -w " %{http_code}\n" \
        http://httpbin.org
    

    请求失败并显示 RBAC: access denied 消息和 403 禁止访问状态代码。您可能需要等待几秒钟,因为授权政策通常要延迟一会儿才能生效。

  4. 通过授权政策,您可以方便地控制要允许或拒绝哪些流量。应用以下授权政策,允许 team-y 命名空间中的应用在使用端口 80 向 httpbin.org 发送请求时,使用一个特定的网址路径。出站流量网关 pod 上的对应 targetPort 是 8080。

    cat <<EOF | kubectl apply -f -
    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: egress-team-y-to-httpbin-teapot
      namespace: istio-egress
    spec:
      action: ALLOW
      rules:
        - from:
          - source:
              namespaces:
              - 'team-y'
          to:
          - operation:
              hosts:
              - httpbin.org
              paths: ['/status/418']
          when:
          - key: destination.port
            values: ["8080"]
    EOF
    
  5. 尝试从 team-y 命名空间中的测试应用连接到 httpbin.org:

    kubectl -n team-y exec -it $(kubectl -n team-y get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -s -w " %{http_code}\n" \
        http://httpbin.org
    

    请求失败并显示 RBAC: access denied 消息和 403 禁止访问状态代码。

  6. 现在,从同一个应用向 httpbin.org/status/418 发出请求:

    kubectl -n team-y exec -it $(kubectl -n team-y get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl http://httpbin.org/status/418
    

    由于路径与授权政策中的格式匹配,因此请求成功。输出内容类似如下:

       -=[ teapot ]=-
          _...._
        .'  _ _ `.
       | ."` ^ `". _,
       \_;`"---"`|//
         |       ;/
         \_     _/
           `"""`
    

出站流量网关上的 TLS 源

您可以将出站流量网关配置为 upgrade(发起)对 TLS 或双向 TLS 的纯文本 HTTP 请求。与 Istio 双向 TLS 和 TLS 发源结合使用时,允许应用发出纯文本 HTTP 请求有几个好处。如需了解详情,请参阅最佳实践指南

出站流量网关上的 TLS 源

  1. 创建 DestinationRule. The DestinationRule,指定网关发起到 example.com 的 TLS 连接。

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: DestinationRule
    metadata:
      name: example-com-originate-tls
      namespace: istio-egress
    spec:
      host: example.com
      subsets:
        - name: example-com-originate-TLS
          trafficPolicy:
            portLevelSettings:
            - port:
                number: 443
              tls:
                mode: SIMPLE
                sni: example.com
    EOF
    
  2. 更新 example.com 的虚拟服务,以使对网关上的端口 80 的请求在发送到目标主机时 upgraded 为端口 443 上的 TLS:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: example-com-through-egress-gateway
      namespace: istio-egress
    spec:
      hosts:
      - example.com
      gateways:
      - mesh
      - istio-egress/egress-gateway
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-mTLS
            port:
              number: 80
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 80
        route:
        - destination:
            host: example.com
            port:
              number: 443
            subset: example-com-originate-TLS
          weight: 100
    EOF
    
  3. team-x 命名空间中的测试应用向 example.com 发出多个请求:

    for i in {1..4}
    do
        kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
            -o jsonpath={.items..metadata.name}) -c test -- curl -I http://example.com
    done
    

    和之前一样,请求成功并返回 200 OK 响应。

  4. 查看出站流量网关日志,验证网关通过发起 TLS 连接将请求路由到目标主机:

    kubectl -n istio-egress logs -f $(kubectl -n istio-egress get pod -l istio=egressgateway \
        -o jsonpath="    {.items[0].metadata.name}") istio-proxy
    

    输出内容类似如下:

    [2020-09-24T17:58:02.548Z] "HEAD / HTTP/2" 200 - "-" "-" 0 0 6 5 "10.1.1.15" "curl/7.67.0" "83a77acb-d994-424d-83da-dd8eac902dc8" "example.com" "93.184.216.34:443" outbound|443|example-com-originate-TLS|example.com 10.1.4.31:49866 10.1.4.31:8080 10.1.1.15:37334 outbound_.80_.target-egress-gateway-mTLS_.istio-egressgateway.istio-egress.svc.cluster.local -
    

    代理 Sidecar 使用端口 80 将请求发送到网关,使用端口 443 上发起的 TLS 将请求发送到目标主机。

HTTPS/TLS 连接的直通

在与外部服务通信时,您的现有应用可能已经在使用 TLS 连接。您可以将出站流量网关配置为直通 TLS 连接,而不进行解密。

tls 直通

  1. 修改配置,以便出站流量网关对到端口 443 的连接使用 TLS 直通:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
      name: egress-gateway
      namespace: istio-egress
    spec:
      selector:
        istio: egressgateway
      servers:
      - port:
          number: 80
          name: https
          protocol: HTTPS
        hosts:
          - '*'
        tls:
          mode: ISTIO_MUTUAL
      - port:
          number: 443
          name: tls
          protocol: TLS
        hosts:
        - '*'
        tls:
          mode: PASSTHROUGH
    EOF
    
  2. 更新指向出站流量网关的 DestinationRule,以便为网关上的端口 443 添加第二个子集。这个新子集不使用双向 TLS。TLS 连接的直通不支持 Istio 双向 TLS。端口 80 上的连接仍使用 mTLS:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: target-egress-gateway
      namespace: istio-egress
    spec:
      host: istio-egressgateway.istio-egress.svc.cluster.local
      subsets:
      - name: target-egress-gateway-mTLS
        trafficPolicy:
          portLevelSettings:
          - port:
              number: 80
            tls:
              mode: ISTIO_MUTUAL
      - name: target-egress-gateway-TLS-passthrough
    EOF
    
  3. 更新 example.com 的虚拟服务,使端口 443 上的 TLS 流量直通网关:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: example-com-through-egress-gateway
      namespace: istio-egress
    spec:
      hosts:
      - example.com
      gateways:
      - mesh
      - istio-egress/egress-gateway
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-mTLS
            port:
              number: 80
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 80
        route:
        - destination:
            host: example.com
            port:
              number: 443
            subset: example-com-originate-TLS
          weight: 100
      tls:
      - match:
        - gateways:
          - mesh
          port: 443
          sniHosts:
          - example.com
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-TLS-passthrough
            port:
              number: 443
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 443
          sniHosts:
          - example.com
        route:
        - destination:
            host: example.com
            port:
              number: 443
          weight: 100
      exportTo:
      - 'istio-egress'
      - 'team-x'
    EOF
    
  4. 更新 httpbin.org 的虚拟服务,使端口 443 上的 TLS 流量直通网关:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: VirtualService
    metadata:
      name: httpbin-org-through-egress-gateway
      namespace: istio-egress
    spec:
      hosts:
      - httpbin.org
      gateways:
      - istio-egress/egress-gateway
      - mesh
      http:
      - match:
        - gateways:
          - mesh
          port: 80
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-mTLS
            port:
              number: 80
          weight: 100
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 80
        route:
        - destination:
            host: httpbin.org
            port:
              number: 80
          weight: 100
      tls:
      - match:
        - gateways:
          - mesh
          port: 443
          sniHosts:
          - httpbin.org
        route:
        - destination:
            host: istio-egressgateway.istio-egress.svc.cluster.local
            subset: target-egress-gateway-TLS-passthrough
            port:
              number: 443
      - match:
        - gateways:
          - istio-egress/egress-gateway
          port: 443
          sniHosts:
          - httpbin.org
        route:
        - destination:
            host: httpbin.org
            port:
              number: 443
          weight: 100
      exportTo:
      - 'istio-egress'
      - 'team-x'
      - 'team-y'
    EOF
    
  5. 添加授权政策,以接受发送到出站流量网关服务的端口 443 的任何类型的流量。网关 pod 上的对应 targetPort 是 8443。

    cat <<EOF | kubectl apply -f -
    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: egress-all-443
      namespace: istio-egress
    spec:
      action: ALLOW
      rules:
        - when:
          - key: destination.port
            values: ["8443"]
    EOF
    
  6. 运行 istioctl analyze 检查是否存在配置错误:

    ${ISTIOCTL} analyze -n istio-egress --revision REVISION
    

    您可以看到以下信息:

    ✔ No validation issues found when analyzing namespace: istio-egress.
    
  7. team-x 命名空间中的测试应用向 example.com 发出纯文本 HTTP 请求:

    kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -I http://example.com
    

    请求成功并返回 200 OK 响应。

  8. 现在,从 team-x 命名空间中的测试应用发出多个 TLS (HTTPS) 请求:

    for i in {1..4}
    do
        kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
            -o jsonpath={.items..metadata.name}) -c test -- curl -s -o /dev/null \
            -w "%{http_code}\n" \
            https://example.com
    done
    

    您会看到 200 响应。

  9. 再次查看出站流量网关日志:

    kubectl -n istio-egress logs -f $(kubectl -n istio-egress get pod -l istio=egressgateway \
        -o jsonpath="{.items[0].metadata.name}") istio-proxy
    

    您会看到类似如下内容的日志条目:

    [2020-09-24T18:04:38.608Z] "- - -" 0 - "-" "-" 1363 5539 10 - "-" "-" "-" "-" "93.184.216.34:443" outbound|443||example.com 10.1.4.31:51098 10.1.4.31:8443 10.1.1.15:57030 example.com -
    

    HTTPS 请求被视为 TCP 流量,通过网关直通到目标主机,因此日志中不包含 HTTP 信息。

将 Kubernetes NetworkPolicy 用作额外控制措施

在很多情况下,应用可以绕过 Sidecar 代理。您可以使用 Kubernetes NetworkPolicy 额外指定工作负载可以建立的连接。应用单个网络政策后,未明确允许的所有连接都会被拒绝。

本教程仅考虑网络政策的出站连接和出站流量选择器。如果您在自己的集群上使用网络政策控制入站流量,则必须创建与出站政策对应的入站政策。例如,如果您允许从 team-x 命名空间中的工作负载到 team-y 命名空间的出站流量,则还必须允许从 team-x 命名空间到 team-y 命名空间的入站流量。

  1. 允许部署在 team-x 命名空间中的工作负载和代理连接到 istiod 和出站流量网关:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-egress-to-control-plane
      namespace: team-x
    spec:
      podSelector: {}
      policyTypes:
        - Egress
      egress:
      - to:
        - namespaceSelector:
            matchLabels:
              "kubernetes.io/metadata.name": istio-system
          podSelector:
            matchLabels:
              istio: istiod
        - namespaceSelector:
            matchLabels:
              "kubernetes.io/metadata.name": istio-egress
          podSelector:
            matchLabels:
              istio: egressgateway
    EOF
    
  2. 允许工作负载和代理查询 DNS:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-egress-to-dns
      namespace: team-x
    spec:
      podSelector: {}
      policyTypes:
        - Egress
      egress:
      - to:
        - namespaceSelector:
            matchLabels:
              "kubernetes.io/metadata.name": kube-system
        ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP
    EOF
    
  3. 允许工作负载和代理连接到为 Google 提供服务的 IP API 和服务,包括 Cloud Service Mesh 证书授权机构:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-egress-to-google-apis
      namespace: team-x
    spec:
      podSelector: {}
      policyTypes:
        - Egress
      egress:
      - to:
        - ipBlock:
            cidr: 199.36.153.4/30
        - ipBlock:
            cidr: 199.36.153.8/30
    EOF
    
  4. 允许工作负载和代理连接到 GKE 元数据服务器:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-egress-to-metadata-server
      namespace: team-x
    spec:
      podSelector: {}
      policyTypes:
        - Egress
      egress:
      - to: # For GKE data plane v2
        - ipBlock:
            cidr: 169.254.169.254/32
      - to: # For GKE data plane v1
        - ipBlock:
            cidr: 127.0.0.1/32 # Prior to 1.21.0-gke.1000
        - ipBlock:
            cidr: 169.254.169.252/32 # 1.21.0-gke.1000 and later
        ports:
        - protocol: TCP
          port: 987
        - protocol: TCP
          port: 988
    EOF
    
  5. 可选:允许 team-x 命名空间中的工作负载和代理相互连接:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-egress-to-same-namespace
      namespace: team-x
    spec:
      podSelector: {}
      ingress:
        - from:
          - podSelector: {}
      egress:
        - to:
          - podSelector: {}
    EOF
    
  6. 可选:允许 team-x 命名空间中的工作负载和代理连接到其他团队部署的工作负载:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: allow-egress-to-team-y
      namespace: team-x
    spec:
      podSelector: {}
      policyTypes:
        - Egress
      egress:
      - to:
        - namespaceSelector:
            matchLabels:
              "kubernetes.io/metadata.name": team-y
    EOF
    
  7. Sidecar 代理之间的连接会持久保留。当您应用新网络政策时,现有连接不会关闭。重启 team-x 命名空间中的工作负载以确保关闭现有连接:

    kubectl -n team-x rollout restart deployment
    
  8. 验证是否仍然可以从 team-x 命名空间中的测试应用向 example.com 发出 HTTP 请求:

    kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- curl -I http://example.com
    

    请求成功并返回 200 OK 响应。

使用专用 Google 访问通道和 IAM 权限直接访问 Google API

Google 的 API 和服务使用外部 IP 地址公开。当具有 VPC 原生别名 IP 地址的 pod 使用专用 Google 访问通道连接到 Google API 时,流量始终不会离开 Google 的网络。

为本教程设置基础架构时,您可以为 GKE pod 使用的子网启用专用 Google 访问通道。如需允许访问专用 Google 访问通道使用的 IP 地址,您可以创建路由、VPC 防火墙规则和专用 DNS 区域。此配置可让 pod 直接访问 Google API,无需通过出站流量网关发送流量。您可以控制哪些 API 可供特定 Kubernetes 服务账号(以及命名空间), 适用于 GKE 的工作负载身份联合和 IAM。Istio 授权不会生效,因为出站流量网关不处理与 Google API 的连接。

您必须先使用 IAM 授予权限,然后 pod 才能调用 Google API。您用于本教程的集群已配置为使用 适用于 GKE 的工作负载身份联合,它允许 Kubernetes 服务账号充当 Google 服务账号。

  1. 为您的应用创建 Google 服务账号:

    gcloud iam service-accounts create sa-test-app-team-x
    
  2. 允许 Kubernetes 服务账号模拟 Google 服务账号:

    gcloud iam service-accounts add-iam-policy-binding \
      --role roles/iam.workloadIdentityUser \
      --member "serviceAccount:${PROJECT_ID}.svc.id.goog[team-x/test]" \
      sa-test-app-team-x@${PROJECT_ID}.iam.gserviceaccount.com
    
  3. 针对 team-x 命名空间中的测试应用,使用 Google 服务账号的电子邮件地址为 Kubernetes 服务账号添加注释。

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      annotations:
        iam.gke.io/gcp-service-account: sa-test-app-team-x@${PROJECT_ID}.iam.gserviceaccount.com
      name: test
      namespace: team-x
    EOF
    
  4. 测试应用 pod 必须能够访问 Google 元数据服务器(作为 DaemonSet 运行),以获取调用 Google API 的临时凭据。为 GKE 元数据服务器创建服务条目:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: ServiceEntry
    metadata:
      name: metadata-google-internal
      namespace: istio-egress
      labels:
        # Show this service and its telemetry in the Cloud Service Mesh page of the Google Cloud console
        service.istio.io/canonical-name: metadata.google.internal
    spec:
      hosts:
      - metadata.google.internal
      ports:
      - number: 80
        name: http
        protocol: HTTP
      - number: 443
        name: tls
        protocol: TLS
      resolution: DNS
      location: MESH_EXTERNAL
      exportTo:
      - 'istio-egress'
      - 'team-x'
    EOF
    
  5. 另外,为 private.googleapis.com 和 storage.googleapis.com 创建服务条目:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: ServiceEntry
    metadata:
      name: private-googleapis-com
      namespace: istio-egress
      labels:
        # Show this service and its telemetry in the Cloud Service Mesh page of the Google Cloud console
        service.istio.io/canonical-name: googleapis.com
    spec:
      hosts:
      - private.googleapis.com
      - storage.googleapis.com
      ports:
      - number: 80
        name: http
        protocol: HTTP
      - number: 443
        name: tls
        protocol: TLS
      resolution: DNS
      location: MESH_EXTERNAL
      exportTo:
      - 'istio-egress'
      - 'team-x'
    EOF
    
  6. 验证 Kubernetes 服务账号是否配置正确,可以充当 Google 服务账号:

    kubectl -n team-x exec -it $(kubectl -n team-x get pod -l app=test \
        -o jsonpath={.items..metadata.name}) -c test -- gcloud auth list
    

    您会看到 Google 服务账号列为有效且唯一的身份。

  7. 在 Cloud Storage 存储桶中创建测试文件:

    echo "Hello, World!" > /tmp/hello
    gcloud storage buckets create gs://${PROJECT_ID}-bucket
    gcloud storage cp /tmp/hello gs://${PROJECT_ID}-bucket/
    
  8. 向服务账号授予列出和查看存储桶中的文件的权限:

    gcloud storage buckets add-iam-policy-binding gs://${PROJECT_ID}-bucket/ \
        --member=serviceAccount:sa-test-app-team-x@${PROJECT_ID}.iam.gserviceaccount.com \
        --role=roles/storage.objectViewer
    
  9. 验证测试应用是否可以访问测试存储桶:

    kubectl -n team-x exec -it \
    $(kubectl -n team-x get pod -l app=test -o jsonpath={.items..metadata.name}) \
    -c test \
    -- gcloud storage cat gs://${PROJECT_ID}-bucket/hello
    

    您可以看到以下信息:

    Hello, World!
    

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

为避免系统因本教程中使用的资源向您的 Google Cloud 账号收取费用,请完成以下各部分中的步骤:

删除项目

为避免支付费用,最简单的方法是删除您为本教程创建的项目。

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

后续步骤