为 Cloud Service Mesh 准备应用


Cloud Service Mesh 是一个功能强大的工具,用于管理和监控分布式应用。如需充分利用 Cloud Service Mesh,最好了解其底层抽象,包括容器和 Kubernetes。本教程介绍如何准备适用于 Cloud Service Mesh 的应用,从源代码到在 GKE 上运行的容器,直到安装 Cloud Service Mesh 之前的内容。

如果您已熟悉 Kubernetes 和服务网格概念,则可以跳过本教程,直接转到 Cloud Service Mesh 安装指南

目标

  1. 探索简单多服务“hello world”应用。
  2. 从来源运行应用
  3. 将应用容器化。
  4. 创建一个 Kubernetes 集群。
  5. 将容器部署到集群。

准备工作

请按照以下步骤启用 Cloud Service Mesh API:
  1. 访问 Google Cloud 控制台中的 Kubernetes Engine 页面
  2. 创建或选择项目。
  3. 稍作等待,让 API 和相关服务完成启用过程。 此过程可能耗时几分钟。
  4. Make sure that billing is enabled for your Google Cloud project.

本教程使用 Cloud Shell,其中预配了运行基于 Debian 的 Linux 操作系统的 g1-small Compute Engine 虚拟机 (VM)。

准备 Cloud Shell

使用 Cloud Shell 的优势如下:

  • Python 2 和 Python 3 开发环境(包括 virtualenv)均已设置。
  • 本教程中使用的 gclouddockergitkubectl 命令行工具已安装。
  • 您可以选择文本编辑器

    • 代码编辑器,可通过点击 Cloud Shell 窗口顶部的 来访问。

    • Emac、Vim 或 Nano,可从 Cloud Shell 中的命令行访问。

In the Google Cloud console, activate Cloud Shell.

Activate Cloud Shell

At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.

下载示例代码

  1. 下载 helloserver 源代码:

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    
  2. 切换到示例代码目录:

    cd anthos-service-mesh-samples/docs/helloserver
    

探索多服务应用

示例应用是使用 Python 编写的,它有两个使用 REST 进行通信的组件:

  • server:一个简单的服务器,具有一个 GET 端点 /,可将“hello world”输出到控制台。
  • loadgen:一种将流量发送到 server 的脚本,可配置每秒请求数 (RPS)。

示例应用

从来源运行应用

如需熟悉示例应用,请在 Cloud Shell 中运行它。

  1. sample-apps/helloserver 目录运行 server

    python3 server/server.py
    

    启动时,server 会显示以下内容:

    INFO:root:Starting server...
    
  2. 打开另一个终端窗口,以便向 server 发送请求。点击 以打开另一个会话。

  3. server 发送请求:

    curl http://localhost:8080
    

    server 的响应如下:

    Hello World!
    
  4. 从下载示例代码的目录中,切换到包含 loadgen 的目录:

    cd YOUR_WORKING_DIRECTORY/anthos-service-mesh-samples/docs/helloserver/loadgen
  5. 创建以下环境变量:

    export SERVER_ADDR=http://localhost:8080
    export REQUESTS_PER_SECOND=5
    
  6. 启动 virtualenv

    virtualenv --python python3 env
    
  7. 激活此虚拟环境:

    source env/bin/activate
    
  8. 安装 loadgen 的要求:

    pip3 install -r requirements.txt
    
  9. 运行 loadgen

    python3 loadgen.py
    

    在启动时,loadgen 会输出如下消息:

    Starting loadgen: 2019-05-20 10:44:12.448415
    5 request(s) complete to http://localhost:8080
    

    在其他终端窗口中,server 将如下消息写入控制台:

    127.0.0.1 - - [21/Jun/2019 14:22:01] "GET / HTTP/1.1" 200 -
    INFO:root:GET request,
    Path: /
    Headers:
    Host: localhost:8080
    User-Agent: python-requests/2.22.0
    Accept-Encoding: gzip, deflate
    Accept: */*
    

    从网络的角度来看,整个应用目前在同一主机上运行。因此,您可以使用 localhostserver 发送请求。

  10. 要停止 loadgenserver,请在每个终端窗口中输入 Ctrl-c

  11. loadgen 终端窗口中,停用虚拟环境:

    deactivate
    

将应用容器化

要在 GKE 上运行应用,您需要将示例应用(serverloadgen)打包到容器中。容器可用于打包应用,使其与底层环境隔离。

要将应用容器化,您需要一个 DockerfileDockerfile 是一个文本文件,用于定义将应用源代码及其依赖项汇编到 Docker 映像 中所需的命令。构建映像后,您可以将其上传到容器注册表,例如 Docker Hub 或 Container Registry

该示例随附一个 Dockerfile 用于 serverloadgen,后者具有构建映像所需的所有命令。以下是 serverDockerfile

FROM python:3.13-slim as base
FROM base as builder
RUN apt-get -qq update \
    && apt-get install -y --no-install-recommends \
        g++ \
    && rm -rf /var/lib/apt/lists/*

# Enable unbuffered logging
FROM base as final
ENV PYTHONUNBUFFERED=1

RUN apt-get -qq update \
    && apt-get install -y --no-install-recommends \
        wget

WORKDIR /helloserver

# Grab packages from builder
COPY --from=builder /usr/local/lib/python3.* /usr/local/lib/

# Add the application
COPY . .

EXPOSE 8080
ENTRYPOINT [ "python", "server.py" ]
  • FROM python:3-slim as base 命令告知 Docker 将最新的 Python 3 映像用作基础映像。
  • COPY . . 命令会将当前工作目录中的源文件(在本例中为 server.py)复制到容器的文件系统。
  • ENTRYPOINT 定义用于运行容器的命令。在本例中,该命令与您用于通过源代码运行 server.py 的命令几乎相同。
  • EXPOSE 命令指定 server 侦听端口 8080。此命令不会公开任何端口,但可用作您在运行容器时打开端口 8080 所需的文档。

准备将应用容器化

  1. 设置以下环境变量。将 PROJECT_ID 替换为您的 Google Cloud 项目的 ID。

    export PROJECT_ID="PROJECT_ID"
    export GCR_REPO="asm-ready"

    您可以在构建 Docker 映像时使用 PROJECT_IDGCR_REPO 值标记该 Docker 映像,然后将其推送到您的私有 Container Registry。

  2. 为 Google Cloud CLI 设置默认 Google Cloud 项目。

    gcloud config set project $PROJECT_ID
  3. 设置 Google Cloud CLI 的默认可用区。

    gcloud config set compute/zone us-central1-b
    
  4. 确保在 Google Cloud 项目中启用 Container Registry 服务。

    gcloud services enable containerregistry.googleapis.com
    

server 容器化

  1. 切换到示例 server 所在的目录:

    cd YOUR_WORKING_DIRECTORY/anthos-service-mesh-samples/docs/helloserver/server/
  2. 使用之前定义的 Dockerfile 和环境变量构建映像:

    docker build -t gcr.io/$PROJECT_ID/$GCR_REPO/helloserver:v0.0.1 .
    

    -t 标志表示 Docker 标记。这是您在部署容器时使用的映像的名称。

  3. 将映像推送到 Container Registry:

    docker push gcr.io/$PROJECT_ID/$GCR_REPO/helloserver:v0.0.1
    

loadgen 容器化

  1. 切换到示例 loadgen 所在的目录:

    cd ../loadgen
    
  2. 构建映像:

    docker build -t gcr.io/$PROJECT_ID/$GCR_REPO/loadgen:v0.0.1 .
    
  3. 将映像推送到 Container Registry:

    docker push gcr.io/$PROJECT_ID/$GCR_REPO/loadgen:v0.0.1
    

列出映像

获取代码库中的映像列表,以确认是否已推送映像:

gcloud container images list --repository gcr.io/$PROJECT_ID/asm-ready

该命令将返回您刚刚推送的映像名称:

NAME
gcr.io/PROJECT_ID/asm-ready/helloserver
gcr.io/PROJECT_ID/asm-ready/loadgen

创建 GKE 集群

您可以使用 docker run 命令在 Cloud Shell 虚拟机上运行这些容器。但在生产环境中,您需要以更统一的方式编排容器。例如,您需要一个可确保容器始终在运行的系统,并且需要一种方法来纵向扩容和启动容器的其他实例,以便处理流量增加。

您可以使用 GKE 运行容器化应用。GKE 是一个容器编排平台,其工作方式是将虚拟机连接到集群。每个虚拟机称为一个节点。GKE 集群由 Kubernetes 开源集群管理系统提供支持。Kubernetes 提供与集群进行交互的机制。

如需创建 GKE 集群,请执行以下操作:

  1. 创建集群:

    gcloud container clusters create asm-ready \
      --cluster-version latest \
      --machine-type=n1-standard-4 \
      --num-nodes 4
    

    gcloud 命令会在您之前设置的 Google Cloud 项目和可用区中创建一个集群。如需运行 Cloud Service Mesh,我们建议至少 4 个节点和 n1-standard-4 机器类型。

    创建集群的命令需要几分钟才能完成。集群准备就绪后,该命令会输出如下消息:

    NAME        LOCATION       MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION   NUM_NODES  STATUS
    asm-ready  us-central1-b  1.13.5-gke.10   203.0.113.1    n1-standard-2  1.13.5-gke.10  4          RUNNING
    
  2. kubectl 命令行工具提供凭据,以便使用它来管理集群:

    gcloud container clusters get-credentials asm-ready
    
  3. 现在,您可以使用 kubectl 与 Kubernetes 进行通信。例如,您可以运行以下命令来获取节点的状态:

    kubectl get nodes
    

    该命令会返回节点列表,具体如下:

    NAME                                       STATUS   ROLES    AGE    VERSION
    gke-asm-ready-default-pool-dbeb23dc-1vg0   Ready    <none>   99s    v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-36z5   Ready    <none>   100s   v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-fj7s   Ready    <none>   99s    v1.13.6-gke.13
    gke-asm-ready-default-pool-dbeb23dc-wbjw   Ready    <none>   99s    v1.13.6-gke.13
    

了解关键 Kubernetes 概念

下图显示在 GKE 上运行的应用:

容器化应用

在将容器部署到 GKE 之前,您可能需要查看一些关键 Kubernetes 概念。本教程末尾提供了相关链接,以便您详细了解每个概念。

  • 节点和集群:在 GKE 中,节点是虚拟机。在其他 Kubernetes 平台上,节点可以是物理机或虚拟机。集群是一组可以被视为单个机器的节点,您可以在其中部署容器化应用。

  • Pod:在 Kubernetes 中,容器在 Pod 中运行。Pod 是 Kubernetes 中的原子单元。Pod 包含一个或多个容器。您将在各自的 Pod 中部署 serverloadgen 容器。当 Pod 运行多个容器(例如,应用服务器和代理服务器)时,这些容器将作为一个实体进行管理并共享 Pod 资源。

  • Deployment:Deployment 是表示一组相同 Pod 的 Kubernetes 对象。Deployment 会运行分布在集群节点中的 Pod 的多个副本。Deployment 会自动替换失败或无响应的任何 Pod。

  • Kubernetes 服务:在 GKE 中运行应用代码会更改 loadgenserver 之间的网络。在 Cloud Shell 虚拟机中运行服务时,您可以使用地址 localhost:8080server 发送请求。部署到 GKE 后,将 Pod 计划在可用节点上运行。默认情况下,您无法控制 Pod 在哪个节点上运行,因此 Pod 没有稳定的 IP 地址。

    要获取 server 的 IP 地址,您必须在 Pod 上定义网络抽象(称为 Kubernetes Service)。Kubernetes Service 为一组 Pod 提供稳定的网络端点。有几种类型的 Serviceserver 使用 LoadBalancer,后者会公开一个外部 IP 地址,以便您可以从集群外部访问 server

    Kubernetes 还具有内置 DNS 系统,用于将 DNS 名称(例如 helloserver.default.cluster.local)分配给 Service。这允许集群内的 Pod 访问具有稳定地址的集群中的其他 Pod。您无法在集群外部(例如通过 Cloud Shell)使用此 DNS 名称。

Kubernetes 清单

从源代码运行应用时,您使用了命令式命令:python3 server.py

命令式意味着以动词为导向:“这样做”。

相比之下,Kubernetes 基于声明式模型运行。这意味着,您无需确切地告知 Kubernetes 做什么,而只需为 Kubernetes 提供所需状态。例如,Kubernetes 会根据需要启动和终止 Pod,以使实际系统状态与所需状态匹配。

您可以在一组清单或 YAML 文件中指定所需状态。YAML 文件包含一个或多个 Kubernetes 对象的规范。

该示例包含 serverloadgen 的 YAML 文件。每个 YAML 文件都指定 Kubernetes Deployment 对象和 Service 的所需状态。

服务器

apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloserver
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloserver
  template:
    metadata:
      labels:
        app: helloserver
    spec:
      containers:
      - image: gcr.io/google-samples/istio/helloserver:v0.0.1
        imagePullPolicy: Always
        name: main
      restartPolicy: Always
      terminationGracePeriodSeconds: 5
  • kind 表示对象的类型。
  • metadata.name 指定 Deployment 的名称。
  • 第一个 spec 字段包含所需状态的说明。
  • spec.replicas 指定所需 Pod 的数量。
  • spec.template 部分定义 Pod 模板。Pod 的规范中包含 image 字段,该字段是从 Container Registry 中拉取的映像的名称。

Service 的定义如下:

apiVersion: v1
kind: Service
metadata:
  name: hellosvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: helloserver
  type: LoadBalancer
  • LoadBalancer:客户端向网络负载平衡器的 IP 地址发送请求,网络负载平衡器具有稳定的 IP 地址,并且可从集群外部访问。
  • targetPort:回想一下,Dockerfile 中的 EXPOSE 8080 命令不会实际公开任何端口。您可以公开端口 8080,以便访问集群外部的 server 容器。在这种情况下,hellosvc.default.cluster.local:80(简称:hellosvc)映射到 helloserver Pod IP 的端口 8080
  • port:这是在发送请求时集群中的其他服务使用的端口号。

负载生成器

loadgen.yaml 中的 Deployment 对象类似于 server.yaml。一个显著区别是 Deployment 对象包含一个名为 env 的部分。此部分定义 loadgen 所需的环境变量,此变量是您在从来源运行应用时设置的。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: loadgenerator
spec:
  replicas: 1
  selector:
    matchLabels:
      app: loadgenerator
  template:
    metadata:
      labels:
        app: loadgenerator
    spec:
      containers:
      - env:
        - name: SERVER_ADDR
          value: http://hellosvc:80/
        - name: REQUESTS_PER_SECOND
          value: '10'
        image: gcr.io/google-samples/istio/loadgen:v0.0.1
        imagePullPolicy: Always
        name: main
        resources:
          limits:
            cpu: 500m
            memory: 512Mi
          requests:
            cpu: 300m
            memory: 256Mi
      restartPolicy: Always
      terminationGracePeriodSeconds: 5

由于 loadgen 不接受传入请求,因此将 type 字段设置为 ClusterIP。此类型提供集群中的服务可以使用的稳定 IP 地址,但不会向外部客户端公开 IP 地址。

apiVersion: v1
kind: Service
metadata:
  name: loadgensvc
spec:
  ports:
  - name: http
    port: 80
    targetPort: 8080
  selector:
    app: loadgenerator
  type: ClusterIP

将容器部署到 GKE

  1. 切换到示例 server 所在的目录:

    cd YOUR_WORKING_DIRECTORY/anthos-service-mesh-samples/docs/helloserver/server/
  2. 通过文本编辑器打开 server.yaml

  3. image 字段中的名称替换为您的 Docker 映像的名称。

    image: gcr.io/PROJECT_ID/asm-ready/helloserver:v0.0.1
    

    PROJECT_ID 替换为您的 Google Cloud 项目 ID。

  4. 保存并关闭 server.yaml

  5. 将 YAML 文件部署到 Kubernetes:

    kubectl apply -f server.yaml
    

    成功后,该命令会返回以下内容:

    deployment.apps/helloserver created
    service/hellosvc created
    

  6. 切换到 loadgen 所在的目录。

    cd ../loadgen
    
  7. 通过文本编辑器打开 loadgen.yaml

  8. image 字段中的名称替换为您的 Docker 映像的名称。

    image: gcr.io/PROJECT_ID/asm-ready/loadgen:v0.0.1
    

    PROJECT_ID 替换为您的 Google Cloud 项目 ID。

  9. 保存并关闭 loadgen.yaml,然后关闭文本编辑器。

  10. 将 YAML 文件部署到 Kubernetes:

    kubectl apply -f loadgen.yaml
    

    成功后,该命令会返回以下内容:

    deployment.apps/loadgenerator created
    service/loadgensvc created
    

  11. 检查 Pod 的状态:

    kubectl get pods
    

    该命令将返回如下状态:

    NAME                             READY   STATUS    RESTARTS   AGE
    helloserver-69b9576d96-mwtcj     1/1     Running   0          58s
    loadgenerator-774dbc46fb-gpbrz   1/1     Running   0          57s
    
  12. loadgen Pod 获取应用日志。将 POD_ID 替换为之前输出中的标识符。

    kubectl logs loadgenerator-POD_ID
    
  13. 获取 hellosvc 的外部 IP 地址:

    kubectl get service
    

    此命令的响应类似于下列内容:

    NAME         TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    hellosvc     LoadBalancer   10.81.15.158   192.0.2.1       80:31127/TCP   33m
    kubernetes   ClusterIP      10.81.0.1      <none>          443/TCP        93m
    loadgensvc   ClusterIP      10.81.15.155   <none>          80/TCP         4m52s
    
  14. hellosvc 发送请求。将 EXTERNAL_IP 替换为 hellosvc 的外部 IP 地址。

    curl http://EXTERNAL_IP
    

为 Cloud Service Mesh 做好准备

现在,您已将应用部署到 GKE。loadgen 可以使用 Kubernetes DNS (hellosvc:80) 向 server 发送请求,而您可以使用外部 IP 地址向 server 发送请求。虽然 Kubernetes 向您提供许多功能,但缺少服务的一些相关信息:

  • 服务如何交互?服务之间是什么关系?流量如何在服务之间流动?您知道 loadgenserver 发送请求,但假设您不熟悉应用。您不能通过查看 GKE 上正在运行的 Pod 的列表来回答这些问题。
  • 指标server 响应传入请求需要多长时间?每秒有多少请求 (RPS) 进入 server?是否有任何错误响应?
  • 安全信息:是 loadgenserver 普通 HTTPmTLS 之间的流量吗?

Cloud Service Mesh 可以回答这些问题。Cloud Service Mesh 是由 Google Cloud 管理的开源 Istio 项目版本。Cloud Service Mesh 的工作方式是在每个 Pod 中放置一个 Envoy 边车代理。Envoy 代理会拦截应用容器的所有入站和出站流量。这意味着 serverloadgen 均会获取 Envoy Sidecar 代理,并且从 loadgenserver 的所有流量均由 Envoy 代理进行调解。这些 Envoy 代理之间的连接构成服务网格。此服务网格架构在 Kubernetes 上提供一个控制层。

服务网格

由于 Envoy 代理在其自己的容器中运行,因此您可以在 GKE 集群上安装 Cloud Service Mesh,而无需对应用代码进行任何实质更改。不过,您可采用几个关键方法准备好使用 Cloud Service Mesh 进行应用插桩:

  • 所有容器的服务:serverloadgen Deployment 都附加 Kubernetes 服务。即使未收到任何入站请求的 loadgen 也有服务。
  • 服务中的端口必须进行命名:虽然 GKE 允许您定义未命名的服务端口,但 Cloud Service Mesh 要求您提供与端口协议匹配的端口名称。在 YAML 文件中,server 的端口命名为 http,因为 server 使用 HTTP 通信协议。如果 service 使用的是 gRPC,则应将端口命名为 grpc
  • 部署加标签:这有助于您使用 Cloud Service Mesh 流量管理功能,例如在同一服务的不同版本之间拆分流量。

安装 Cloud Service Mesh

访问 Cloud Service Mesh 安装指南,并按照说明操作在您的集群上安装 Cloud Service Mesh。

清理

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

要清理,请删除 GKE 集群。删除集群会删除构成容器集群的所有资源,例如计算实例、磁盘和网络资源。

gcloud container clusters delete asm-ready

后续步骤