在 GKE 上为移动客户端设置 Pub/Sub 代理


本教程介绍如何通过一个负责处理身份验证和授权逻辑的代理(而不是客户端凭据)将消息从移动应用或客户端应用发布到 Pub/Sub

虽然您可以使用 Identity and Access Management (IAM) 对从客户端到 Pub/Sub 的消息进行身份验证,但这些凭据长期有效,永不过期。在客户端应用中,可以通过应用反编译和逆向工程等技术发现这些凭据。

不过,您可将身份验证和授权逻辑分流到一个执行以下任务的代理:

  • 对传入请求进行身份验证以验证用户身份。
  • 将请求以及适当的 IAM 权限转发到 Pub/Sub。

本教程介绍如何在 Google Kubernetes Engine (GKE) 上实现 Pub/Sub 代理。本教程适用于定义和实现移动应用和客户端应用设计的应用开发者和系统架构师。本文档假定您了解 Kubernetes 基础概念并熟悉 Cloud Endpoints

本教程的请求流程

要了解 Pub/Sub 如何适用于流式传输流水线,请考虑执行点击流分析。在本用例中,您可能希望了解用户如何与您的移动应用进行交互。要获得这些数据分析,您需要实时捕获用户活动。下图为数据流程图。

Pub/Sub 代理在数据聚合之前接收客户端的消息。

应用捕获的数据通过代理推送到 Pub/Sub。Pub/Sub 可以具有下游订阅者(例如 Dataflow 或 Dataproc),他们会聚合数据,便于您可执行有意义的分析。

下图显示了本教程遵循的详细请求流程。

应用请求中流水线各部分的交互方式。

以下部分介绍了该图中各部分的交互方式。

用户身份验证

移动应用可使用各种方法对用户进行身份验证。身份验证流程取决于您的应用。本教程将介绍一种对用户进行身份验证的解决方案。本教程还阐述了该解决方案的实现

从客户端应用发往 Pub/Sub 代理的请求

应用后端生成一个短期有效的身份验证令牌,客户端将其存储在本地(例如,使用 Android Keystore 系统iOS 密钥链服务)。本教程使用 OpenID Connect (OIDC) ID 令牌对客户端应用进行身份验证。OIDC ID 令牌由 Google 颁发和签名。

该客户端应用使用 OIDC ID 令牌向 Pub/Sub 代理发送请求。Pub/Sub 代理验证令牌,并将请求与适当的 IAM 凭据一起转发到 Pub/Sub。

发布消息

对客户端应用成功进行身份验证后,Pub/Sub 代理会向 Pub/Sub 发送发布请求。通过 IAM,Pub/Sub 可帮助确保调用方(Pub/Sub 代理)具有相应的权限来发送发布请求。在本教程中,Pub/Sub 代理使用 Compute Engine 默认服务账号通过 Pub/Sub 进行身份验证。Compute Engine 默认服务账号具有 Editor IAM 角色 (roles/editor),可提供对 Pub/Sub 代理的发布商访问权限

目标

  • 创建 GKE 集群以运行 Pub/Sub 代理。
  • 创建 Pub/Sub 主题。
  • 部署 Pub/Sub 代理。
  • 配置 Endpoints,对发送到 Pub/Sub 代理的请求进行身份验证。
  • 验证消息是否已发布到 Pub/Sub。

费用

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

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

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 在 Google Cloud Console 中,转到项目选择器页面。

    转到“项目选择器”

  2. 选择或创建 Google Cloud 项目。

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

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

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

  5. 定义本教程中所需的环境变量:
        export PROJECT=$(gcloud config get-value project)
        export REGION=us-central1
        export ZONE=${REGION}-b
        export CLUSTER=pubsub-proxy
        export TOPIC=proxy-test
        export SERVICE_ACCOUNT=publish-test
        export ENDPOINTS_SERVICE="pubtest.endpoints.${PROJECT}.cloud.goog"
        export GENERATE_TOKEN="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts"
  6. 启用适用于 Cloud Build、Compute Engine、Google Kubernetes Engine、Artifact Analysis、Container Registry、Endpoints、Service Management、Service Control 和 Pub/Sub 的 API:
        gcloud services enable \
            cloudbuild.googleapis.com \
            compute.googleapis.com \
            container.googleapis.com \
            containeranalysis.googleapis.com \
            containerregistry.googleapis.com \
            endpoints.googleapis.com \
            servicemanagement.googleapis.com \
            servicecontrol.googleapis.com \
            pubsub.googleapis.com

创建 Pub/Sub 主题

  • 在 Cloud Shell 中,创建一个要在其中发布消息的 Pub/Sub 主题:

    gcloud pubsub topics create $TOPIC
    

创建 GKE 集群

  1. 在 Cloud Shell 中创建一个 GKE 集群:

    gcloud container clusters create $CLUSTER \
        --zone $ZONE \
        --scopes "https://www.googleapis.com/auth/cloud-platform"
    
  2. 获取正在运行的集群的凭据:

    gcloud container clusters get-credentials $CLUSTER \
        --zone $ZONE \
        --project $PROJECT
    

构建容器映像

  1. 在 Cloud Shell 中,克隆代码库:

    git clone https://github.com/GoogleCloudPlatform/solutions-pubsub-proxy-rest
    
  2. 使用 Cloud Build 根据源构建容器映像,然后将其保存到 Container Registry 中:

    cd solutions-pubsub-proxy-rest && \
        gcloud builds submit --tag gcr.io/$PROJECT/pubsub-proxy:v1
    

创建静态外部 IP 地址

  1. 在 Cloud Shell 中,创建一个静态外部 IP 地址,该地址稍后会分配给 Pub/Sub 代理负载平衡器:

    gcloud compute addresses create service-ip --region $REGION
    
  2. 将静态 IP 地址存储在环境变量 PROXY_IP 中:

    PROXY_IP=$(gcloud compute addresses describe service-ip \
        --region $REGION --format='value(address)')
    

部署端点

Pub/Sub 代理使用 Endpoints 对用户发出的请求进行身份验证。Endpoints 使用 Extensible Service Proxy (ESP) 提供身份验证监控跟踪API 生命周期管理等 API 管理功能。本教程仅使用 Endpoints 对发送到 Pub/Sub 代理的传入请求进行身份验证。

在本教程中,您将使用 Pub/Sub 代理将 ESP 部署为辅助信息文件。ESP 拦截传入请求并对其进行身份验证,然后将其转发到 Pub/Sub 代理。

  1. 在 Cloud Shell 中,将 [PROJECT_ID] 占位符替换为 openapi.yaml 文件中的 Google Cloud 项目 ID:

    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" openapi.yaml
    
  2. 在 OpenAPI 清单文件中,将 [IP_ADDRESS] 占位符替换为 PROXY_IP 的值:

    sed -i -e "s/\[IP_ADDRESS\]/$PROXY_IP/g" openapi.yaml
    
  3. 将 OpenAPI 服务定义部署到 Endpoints:

    gcloud endpoints services deploy openapi.yaml
    

    上述命令会创建以下内容:

    • 代管式服务,其名称是您在 openapi.yaml 文件 (pubtest.endpoints.project-id.cloud.goog) 的主机字段中指定的名称,其中 project-id 是您的 Google Cloud 项目的 ID。
    • DNS A 记录,它使用 openapi.yaml 文件的 x-google-endpoints 扩展程序中定义的服务名称和 Pub/Sub 代理负载平衡器 IP 地址映射。

    在部署期间,您会看到一条警告。您可将其忽略,因为本教程使用 OIDC ID 令牌而不是 API 密钥进行身份验证。

    WARNING: openapi.yaml: Operation 'post' in path '/publish': Operation does
    not require an API key; callers may invoke the method without specifying an
    associated API-consuming project. To enable API key all the
    SecurityRequirement Objects
    (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#security-requirement-object)
    inside security definition must reference at least one SecurityDefinition
    of type : 'apiKey'.
    
  4. 检查服务是否已正确部署:

    gcloud endpoints services describe ${ENDPOINTS_SERVICE}
    

    输出类似于以下内容:

    [...]
    producerProjectId: project-id
    serviceConfig:
      documentation:
        summary: Pub/Sub proxy exposed as an Endpoint API
    [...]
      name: pubtest.endpoints.project-id.cloud.goog
      title: PubSub Proxy
      usage: {}
    serviceName: pubtest.endpoints.project-id.cloud.goog
    

    在输出中:

    • project-id:您的 Google Cloud 项目的 ID。

部署代理

  1. 在 Cloud Shell 中,生成自签名 SSL 证书以允许通过 HTTPS 连接到代理。

    openssl req -x509 -nodes -days 365 \
        -newkey rsa:2048 -keyout ./nginx.key \
        -out ./nginx.crt \
        -subj "/CN=${ENDPOINTS_SERVICE}"
    
  2. 使用 SSL 证书和私钥创建 Kubernetes 密钥:

    kubectl create secret generic nginx-ssl \
        --from-file=./nginx.crt \
        --from-file=./nginx.key
    
  3. 将部署清单文件中的 [PROJECT_ID] 占位符替换为您的 Google Cloud 项目 ID:

    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" kube/deployment.yaml
    
  4. 将服务清单文件中的 [IP_ADDRESS] 占位符替换为 PROXY_IP 的值:

    sed -i -e "s/\[IP_ADDRESS\]/$PROXY_IP/g" kube/service.yaml
    
  5. 部署代理:

    kubectl apply -f kube/
    
  6. 验证部署是否成功:

    kubectl rollout status deployment/pubsub-proxy
    

    输出类似于以下内容:

    [...]
    deployment "pubsub-proxy" successfully rolled out
    
  7. 确保两个容器(ESP 和 Pub/Sub 代理)在 Pod 中运行:

    kubectl get pods $(kubectl get pod \
        -l app=pubsub-proxy \
        -o jsonpath="{.items[0].metadata.name}") \
        -o jsonpath={.spec.containers[*].name}
    

    输出内容类似如下:

    esp  pubsub-proxy
    
  8. 观察 EXTERNAL-IP 的值从 <pending> 更改为您之前创建的静态外部 IP 地址

    kubectl get svc pubsub-proxy -w
    

    输出类似于以下内容:

    NAME          TYPE          CLUSTER-IP    EXTERNAL-IP   PORT(S)        AGE
    pubsub-proxy  LoadBalancer  10.7.247.212  <pending>     443:31104/TCP  6m32s
    pubsub-proxy  LoadBalancer  10.7.247.212  <PROXY_IP>    443:31104/TCP  6m5s
    

    要停止观察,请按 CTRL+C

    Pub/Sub 代理成功部署后将在 https://${ENDPOINTS_SERVICE}/publish 中公开。新的 DNS 配置可能需要几分钟的时间才能传播。

  9. 验证 DNS 配置:

    watch nslookup ${ENDPOINTS_SERVICE}
    

    输出类似于以下内容:

    Server:   169.254.169.254
    Address:  169.254.169.254#53
    
    Non-authoritative answer:
    Name: pubtest.endpoints.project-id.cloud.goog
    Address: gke-load-balancer-ip
    

    在输出中:

    • gke-load-balancer-ip:您的 GKE 负载平衡器的 IP 地址(代理 IP)。

    要停止观察,请按 CTRL+C

如果在上述任一步骤遇到错误,请查看问题排查步骤

生成身份验证令牌

以下身份验证令牌生成流程仅作为演示示例。对于生产环境,您需要一种方法来让用户生成自己的身份验证令牌。例如,您可以在 Identity-Aware Proxy 文档中找到以编程方式获取 OIDC ID 令牌的示例代码。

若要生成身份验证令牌,请执行以下操作:

  1. 创建一个要为其生成 OIDC ID 令牌的 Google Cloud 服务账号:

    gcloud iam service-accounts create \
        $SERVICE_ACCOUNT \
        --display-name $SERVICE_ACCOUNT
    
  2. 获取服务账号的电子邮件标识:

    SA_EMAIL=${SERVICE_ACCOUNT}@${PROJECT}.iam.gserviceaccount.com
    
  3. 向该服务账号授予 Service Account Token Creator IAM 角色 (roles/iam.serviceAccountTokenCreator):

    gcloud iam service-accounts add-iam-policy-binding $SA_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role roles/iam.serviceAccountTokenCreator
    
  4. 使用 IAM Credentials API 生成 OIDC ID 令牌。

    TOKEN=$(curl -s ${GENERATE_TOKEN}/${SA_EMAIL}:generateIdToken \
        -H "Content-Type: application/json" \
        -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
        -d '{"audience": "'${ENDPOINTS_SERVICE}'", "includeEmail": "true"}' | jq -r ".token")
    

    Endpoints 服务名称是在 audience 字段中指定的。audience 声明可识别令牌的目标接收者。

  5. 验证是否已成功创建令牌:

    echo $TOKEN
    

    JSON Web 令牌 (JWT) 如下所示:

    eyJhbGciOiJSUzI1NiIsImtpZCI6IjY4NjQyODlm[...].eyJhdWQiOiJwdWJ0ZXN0LmVuZHBvaW50cy52aXR
    hbC1vY3RhZ29uLTEwOTYxMi5jbG91ZC5nb[...].SjBI4TZjZAlYo6lFKkrvfAcVUp_AJzFKoSsjNbmD_n[...]
    

使用代理调用 Pub/Sub

  1. 在 Cloud Shell 中,发布一条测试消息:

    curl -i -k -X POST https://${ENDPOINTS_SERVICE}/publish \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-Type: application/json" \
        -d '{"topic": "'$TOPIC'", "messages": [ {"attributes": {"key1": "value1", "key2" : "value2"}, "data": "test data"}]}'
    

    输出类似于以下内容:

    HTTP/2 200
    server: nginx
    date: Sun, 02 Jun 2019 03:53:46 GMT
    ...
    
  2. 检查消息是否已成功发布到 Pub/Sub 主题:

    kubectl logs -f --tail=5 deployment/pubsub-proxy -c pubsub-proxy
    

    Pub/Sub 代理部署日志会显示消息 Successfully published

    2019-06-02 03:49:39.723:INFO:oejs.Server:main: Started @2554ms
    Jun 02, 2019 3:53:44 AM com.google.pubsub.proxy.publish.PublishMessage
    getPublisher
    INFO: Creating new publisher for: proxy-test
    Jun 02, 2019 3:53:47 AM
    com.google.pubsub.proxy.publish.PublishMessage$1 onSuccess
    INFO: Successfully published: 569006136173844
    

问题排查

  1. 在 Cloud Shell 中,检查 Pub/Sub 代理 Pod 中两个容器的状态:

    kubectl describe pods $(kubectl get pod -l app=pubsub-proxy \
        -o jsonpath="{.items[0].metadata.name}")
    

    在日志输出中,这两个容器的状态为 Running

    [...]
    Containers:
      esp:
    [...]
      State:  Running
        Started:  Fri, 21 Jun 2019 16:41:30 +0530
      Ready:  True
      Restart Count:  0
    [...]
      pubsub-proxy:
        State:  Running
          Started:  Fri, 21 Jun 2019 16:41:42 +0530
        Ready:  True
        Restart Count:  0
    [...]
    
  2. (可选)查看容器日志,检查是否存在其他错误。例如,要检查 Pub/Sub 代理日志,请运行以下命令:

    kubectl logs -f --tail=10 deployment/pubsub-proxy -c pubsub-proxy
    

若在排查问题时需要帮助,请参阅以下文档:

清除数据

为避免系统因本教程中所用的资源向您的 Google Cloud 账号收费,您可以删除为本教程创建的 Google Cloud 项目,或者删除与本教程相关的资源。

删除 Google Cloud 项目

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

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

    转到“管理资源”

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

删除资源

如果您希望保留在本教程中使用的 Google Cloud 项目,请删除单个资源:

  1. 在 Cloud Shell 中,删除 GKE 集群:

    gcloud container clusters delete $CLUSTER --zone $ZONE --async
    
  2. 删除下载的代码、工件和其他依赖项:

    cd .. && rm -rf solutions-pubsub-proxy-rest
    
  3. 删除 Container Registry 中的映像:

    gcloud container images list-tags \
        gcr.io/$PROJECT/pubsub-proxy \
        --format 'value(digest)' | \
        xargs -I {} gcloud container images delete \
        --force-delete-tags --quiet \
        gcr.io/${PROJECT}/pubsub-proxy@sha256:{}
    
  4. 删除 Pub/Sub 主题:

    gcloud pubsub topics delete $TOPIC
    
  5. 删除服务账号:

    gcloud iam service-accounts delete $SA_EMAIL
    
  6. 删除 Endpoints:

    gcloud endpoints services delete ${ENDPOINTS_SERVICE}
    
  7. 删除静态 IP 地址:

    gcloud compute addresses delete service-ip --region $REGION
    

后续步骤