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


本教程介绍如何使用 Anthos Service Mesh 出站流量网关和其他 Google Cloud 控制措施保护来自 Google Kubernetes Engine 集群上部署的工作负载的出站流量。本教程是最佳做法指南的配套教程。

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

目标

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

费用

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

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

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

准备工作

  1. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  2. 确保您的 Google Cloud 项目已启用结算功能

  3. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 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. 将脚本设为可执行文件,并使用 source 命令运行该脚本来初始化您的环境:

    chmod +x ./init-egress-tutorial.sh
    source ./init-egress-tutorial.sh
    
  7. 设置所需的 Identity and Access Management (IAM) 角色。如果您是 Project Owner,则拥有完成安装所需的全部权限。如果您不是 Project Owner,请让管理员授予您以下 IAM 角色。在以下命令中,将 PROJECT_EMAIL_ADDRESS 更改为您用于登录 Google Cloud 的账号。

    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
        --member user:PROJECT_EMAIL_ADDRESS \
        --role=roles/editor \
        --role=roles/compute.admin \
        --role=roles/container.admin \
        --role=roles/resourcemanager.projectIamAdmin \
        --role=roles/iam.serviceAccountAdmin \
        --role=roles/iam.serviceAccountKeyAdmin \
        --role=roles/gkehub.admin \
        --role=roles/serviceusage.serviceUsageAdmin
    
  8. 启用本教程所需的 API:

    gcloud services enable \
        dns.googleapis.com \
        container.googleapis.com \
        compute.googleapis.com \
        monitoring.googleapis.com \
        logging.googleapis.com \
        cloudtrace.googleapis.com \
        meshca.googleapis.com \
        meshtelemetry.googleapis.com \
        meshconfig.googleapis.com \
        iamcredentials.googleapis.com \
        gkeconnect.googleapis.com \
        gkehub.googleapis.com \
        cloudresourcemanager.googleapis.com \
        stackdriver.googleapis.com
    

    启用 API 可能需要 1 分钟或更长时间才能完成。启用这些 API 后,您将会看到如下所示的输出:

    Operation "operations/acf.601db672-88e6-4f98-8ceb-aa3b5725533c" finished
    successfully.
    

设置基础架构

创建 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 \
        --destination-ranges 10.5.0.0/28 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow nodes to reach the Kubernetes API server."
    
  4. 在将 Sidecar 代理注入工作负载时,Anthos Service Mesh 使用网络钩子。允许 GKE API 服务器调用节点上运行的服务网格控制平面公开的网络钩子:

    gcloud compute firewall-rules create allow-ingress-api-server-to-webhook \
        --action ALLOW \
        --direction INGRESS \
        --rules tcp:15017 \
        --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 会自动创建相应的入站流量规则。

    gcloud compute firewall-rules create allow-egress-pods-and-services \
        --action ALLOW \
        --direction EGRESS \
        --rules all \
        --destination-ranges 10.1.0.0/16,10.2.0.0/20 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow pods and services on nodes to reach each other"
    
  6. 名为 Calico 的服务为 GKE 提供 NetworkPolicy API 功能。允许在子网内连接 Calico:

    gcloud compute firewall-rules create allow-egress-calico \
        --action ALLOW \
        --direction EGRESS \
        --rules tcp:5473 \
        --destination-ranges 10.0.0.0/24 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow Calico Typha within the subnet"
    
  7. GKE 需要 kubelet 只读端口才能读取节点指标。允许在子网内对其进行访问:

    gcloud compute firewall-rules create allow-egress-kubelet-readonly \
        --action ALLOW \
        --direction EGRESS \
        --rules tcp:10255 \
        --destination-ranges 10.0.0.0/24 \
        --network vpc-network \
        --priority 1000 \
        --description "Allow access to the kubelet read-only port within the subnet"
    
  8. 允许访问专用 Google 访问通道用于提供 Google API、Container Registry 和其他服务的预留 IP 地址集:

    gcloud compute firewall-rules create allow-egress-gcp-apis \
        --action ALLOW \
        --direction EGRESS \
        --rules tcp \
        --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)"
    
  9. 允许 Google Cloud 健康检查程序服务访问集群中运行的 pod:

    gcloud compute firewall-rules create allow-ingress-gcp-health-checker \
        --action ALLOW \
        --direction INGRESS \
        --rules tcp:80,tcp:443 \
        --source-ranges 130.211.0.0/22,35.191.0.0/16,35.191.0.0/16,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 网络。

创建一个专用 DNS 区域、一个“CNAME”和一个“A”记录,以便节点和工作负载可以使用专用 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 区域、一个“CNAME”和一个“A”记录,以便节点可以使用专用 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. 创建专用 GKE 集群:

    gcloud container clusters create cluster1 \
        --enable-ip-alias \
        --enable-private-nodes \
        --release-channel "regular" \
        --no-enable-basic-auth \
        --no-issue-client-certificate \
        --enable-master-authorized-networks \
        --master-authorized-networks ${SHELL_IP//\"}/32 \
        --master-ipv4-cidr 10.5.0.0/28 \
        --enable-network-policy \
        --service-account "sa-application-nodes@${PROJECT_ID}.iam.gserviceaccount.com" \
        --machine-type "e2-standard-4" \
        --num-nodes "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。

    Anthos Service Mesh 要求集群节点使用至少具有 4 个 vCPU 的机器类型。Google 建议将集群订阅到“常规”发布渠道,以确保节点运行的是 Anthos Service Mesh 支持的 Kubernetes 版本。如需了解详情,请参阅 Anthos Service Mesh 安装指南

    集群启用了 Workload Identity。Anthos Service Mesh 需要 Workload Identity,这也是从 GKE 工作负载访问 Google API 的推荐方法。

  3. 创建一个名为 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。

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

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

    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]
    

安装和设置 Anthos Service Mesh

本教程使用 Anthos Service Mesh 的可选功能。如需了解如何使用脚本安装 Anthos Service Mesh,请参阅文档中的安装指南

  1. 为要在其中部署的服务网格控制层面和出站流量网关创建命名空间:

    kubectl create ns istio-system
    kubectl create ns istio-egress
    
  2. 为 istio-egres、istio-system 和 kube-system 命名空间添加标签:

    kubectl label ns istio-egress istio=egress istio-injection=disabled
    kubectl label ns istio-system istio=system
    kubectl label ns kube-system kube-system=true
    

    这些标签稍后用于应用 Kubernetes NetworkPolicy。运行 istioctl 分析时,istio-injection=disabled 标签可防止虚假警告。

  3. 使用 Istio OperatorAPI 创建清单文件以自定义 Anthos Service Mesh 安装:

    cat << 'EOF' > ./asm-custom-install.yaml
    apiVersion: install.istio.io/v1alpha1
    kind: IstioOperator
    metadata:
      name: "egress-gateway"
    spec:
      meshConfig:
        accessLogFile: "/dev/stdout"
      components:
        egressGateways:
          - name: "istio-egressgateway"
            enabled: true
            namespace: "istio-egress"
            label:
              istio: "egress"
            k8s:
              tolerations:
              - key: "dedicated"
                operator: "Equal"
                value: "gateway"
              nodeSelector:
                cloud.google.com/gke-nodepool: "gateway"
    EOF
    

    此文件作为安装脚本的参数提供,它指定以下配置:

    • 运行在 istio-egress 命名空间中的入站流量网关部署具有容忍和 nodeSelector,因此它只能在 gateway 节点上运行。
    • 可以访问所有 Sidecar 代理发送到“stdout”的日志记录。
  4. 下载安装脚本:

    curl -O https://storage.googleapis.com/csm-artifacts/asm/install_asm
    
  5. 将文件的 SHA-256 签名下载到工作目录:

    curl -O https://storage.googleapis.com/csm-artifacts/asm/install_asm.sha256
    
  6. 在同一个目录中验证这两个文件是否下载:

    sha256sum -c --ignore-missing install_asm.sha256
    

    如果验证成功,则输出为:

    install_asm: OK
    

    为了确保兼容性,install_asm.sha256 文件包含两次校验和,以允许将脚本的任何版本重命名为 install_asm。如果出现 --ignore-missing 不存在的错误,请重新运行上一个命令,但不使用 --ignore-missing 标志。

  7. 让该脚本可执行:

    chmod +x install_asm
    
  8. 运行以下脚本安装 Anthos Service Mesh:

    ./install_asm \
        --mode install \
        --project_id ${PROJECT_ID} \
        --cluster_name cluster1 \
        --cluster_location ${ZONE} \
        --custom_overlay ./asm-custom-install.yaml \
        --output_dir ./ \
        --enable_all
    
  9. 脚本完成后,设置一个环境变量来保存 istioctl 工具的路径,并将其添加到初始化脚本中:

    ISTIOCTL=$(find "$(pwd -P)" -name istioctl)
    echo "ISTIOCTL=\"${ISTIOCTL}\"" >> ./init-egress-tutorial.sh
    

验证 Anthos Service Mesh 安装

  1. 检查 Anthos Service Mesh 控制层面组件是否在 istio-system 命名空间中运行:

    kubectl get pod -n istio-system
    

    您会看到 istio-ingressgatewayistiod-asm pod 在运行。

  2. 检查出站流量网关 pod 是否在 istio-egress 命名空间中和 gateway 节点池中的节点上运行:

    kubectl get pods -n istio-egress -o wide
    
  3. 出站流量网关 pod 具有用于选择 gateway 节点池中节点的 nodeSelector 以及使其在污点网关节点上运行的容忍。检查出站流量网关 pod 的 nodeSelector 和容忍:

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

    输出类似于以下内容:

    name                                   nodeSelector                                 tolerations
    istio-egressgateway-74687946f5-dg9mp   map[cloud.google.com/gke-nodepool:gateway]   map[key:dedicated operator:Equal value:gateway]
    

准备网格和测试应用

  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. 为了使 Anthos Service Mesh 自动注入代理 Sidecar,您必须在工作负载命名空间上设置修订版本标签。修订版本标签必须与已部署到集群的 Anthos Service Mesh 控制层面的修订版本匹配。在 istiod pod 上查找修订版本标签,并将其存储在环境变量中:

    REVISION_LABEL=$(kubectl get pod -n istio-system -l app=istiod \
      -o jsonpath='{.items[0].metadata.labels.istio\.io/rev}')
    
  5. team-xteam-y 命名空间上设置修订版本标签:

    kubectl label ns team-x istio.io/rev=${REVISION_LABEL}
    kubectl label ns team-y istio.io/rev=${REVISION_LABEL}
    
  6. 创建用于进行测试部署的 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
    
  7. 将测试应用部署到 team-x 命名空间:

    kubectl -n team-x create -f ./test.yaml
    
  8. 验证测试应用已部署到默认池中的节点,并已注入代理 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 在默认节点池中的节点上运行。

  9. 验证是否无法从测试容器向外部网站发出 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 资源。

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

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

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

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

  2. 将代理配置限制为在 istio-egressteam-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 会限制代理配置,使其仅包含通过定义服务条目明确添加到网格服务注册表的外部主机。

    istio-egress/*”部分指定边车代理选择来自 istio-egress 命名空间(使用 exportTo 属性提供)的路由。“team-x/*”部分包含在 team-x 命名空间中本地配置的任何路由。

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

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

    输出中仅包含少量用于出站流量网关的集群和一个用于测试 pod 本身的集群。

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

  1. 为端口 80 上的 HTTP 流量配置 GatewayGateway 会选择安装程序部署到 istio-egress 命名空间的 istio-egressgateway 代理。Gateway 配置应用于 istio-egress 命名空间并处理任何主机的流量。

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
      name: egress-gateway
      namespace: istio-egress
    spec:
      selector:
        istio: egress
      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:
          loadBalancer:
            simple: ROUND_ROBIN
          tls:
            mode: ISTIO_MUTUAL
    EOF
    
  3. istio-egress 命名空间中创建一个 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
    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
    

    输出类似于以下内容:

    ✔ 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=egress \
        -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
    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
    

    您可以看到以下信息:

    ✔ 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:
      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:
      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 源

您可以将出站流量网关配置为将纯文本 HTTP 请求“升级”(发起)为 TSL。与 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:
            loadBalancer:
              simple: ROUND_ROBIN
            portLevelSettings:
            - port:
                number: 443
              tls:
                mode: SIMPLE
                sni: example.com
    EOF
    
  2. 更新 example.com 的虚拟服务,在向目标主机发送请求时,发送到端口 80 的请求在网关上“升级”为端口 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=egress \
        -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: egress
      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:
          loadBalancer:
            simple: ROUND_ROBIN
          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:
      rules:
        - when:
          - key: destination.port
            values: ["8443"]
    EOF
    
  6. 运行 istioctl analyze 检查是否存在配置错误:

    ${ISTIOCTL} analyze -n istio-egress
    

    您可以看到以下信息:

    ✔ 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=egress \
        -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 用作额外控制措施

在很多情况下,应用可以绕过边车代理。您可以使用 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:
              istio: system
          podSelector:
            matchLabels:
              istio: istiod
        - namespaceSelector:
            matchLabels:
              istio: egress
          podSelector:
            matchLabels:
              istio: egress
    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:
              kube-system: "true"
        ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP
    EOF
    
  3. 允许工作负载和代理连接到提供 Google API 和服务的 IP,包括 Mesh CA:

    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
        ports:
        - 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:
              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,无需通过出站流量网关发送流量。您可以使用 Workload Identity 和 IAM 来控制哪些 API 可用于特定 Kubernetes 服务账号(以及命名空间)。Istio 授权不会生效,因为出站流量网关不处理与 Google API 的连接。

您必须先使用 IAM 授予权限,然后 pod 才能调用 Google API。您在本教程中使用的集群已配置为使用 Workload Identity,允许 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
    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
    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
    gsutil mb gs://${PROJECT_ID}-bucket
    gsutil cp /tmp/hello gs://${PROJECT_ID}-bucket/
    
  8. 向服务账号授予列出和查看存储桶中的文件的权限:

    gsutil iam ch \
    serviceAccount:sa-test-app-team-x@${PROJECT_ID}.iam.gserviceaccount.com:objectViewer \
        gs://${PROJECT_ID}-bucket/
    
  9. 验证测试应用是否可以访问测试存储桶:

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

    您可以看到以下信息:

    Hello, World!
    

清理

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

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

删除项目

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

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤