Como usar o Pub/Sub com o Cloud Run for Anthos


Este tutorial mostra como gravar, implantar e chamar um serviço do Cloud Run for Anthos a partir de uma assinatura de push do Pub/Sub.

Objetivos

  • Gravar, criar e implantar um serviço no Cloud Run for Anthos
  • Chame o serviço publicando uma mensagem em um tópico do Pub/Sub.

Custos

Neste documento, você usará os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem estar qualificados para uma avaliação gratuita.

Antes de começar

  1. Faça login na sua conta do Google Cloud. Se você começou a usar o Google Cloud agora, crie uma conta para avaliar o desempenho de nossos produtos em situações reais. Clientes novos também recebem US$ 300 em créditos para executar, testar e implantar cargas de trabalho.
  2. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  3. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  4. No console do Google Cloud, na página do seletor de projetos, selecione ou crie um projeto do Google Cloud.

    Acessar o seletor de projetos

  5. Verifique se a cobrança está ativada para o seu projeto do Google Cloud.

  6. Ativar a API Cloud Run for Anthos
  7. Instale e inicialize a CLI gcloud.
  8. Instale o componente kubectl:
    gcloud components install kubectl
  9. Atualize os componentes:
    gcloud components update
  10. Se você estiver usando o Cloud Run for Anthos, crie um novo cluster usando as instruções em Como configurar o Cloud Run for Anthos.

Como configurar padrões da gcloud

Para configurar a gcloud com os padrões do serviço do Cloud Run for Anthos, faça o seguinte:

  1. Defina o projeto padrão:

    gcloud config set project PROJECT_ID

    Substitua PROJECT_ID pelo nome do projeto que você usou neste tutorial.

  2. Configure a gcloud para o cluster:

    gcloud config set run/platform gke
    gcloud config set run/cluster CLUSTER-NAME
    gcloud config set run/cluster_location REGION

    Substitua:

    • CLUSTER-NAME pelo nome que você usou para o cluster;
    • REGION pelo local do cluster compatível de sua escolha.

Como criar um tópico do Pub/Sub

O serviço de amostra é acionado por mensagens publicadas em um tópico do Pub/Sub. Portanto, é necessário criar um tópico no Pub/Sub.

Para criar um novo tópico do Pub/Sub, use o comando:

gcloud pubsub topics create myRunTopic

É possível usar myRunTopic ou substituir por um nome de tópico exclusivo no projeto do Cloud.

Como recuperar o exemplo de código

Para recuperar o exemplo de código para uso, siga estas etapas:

  1. Clone o repositório do aplicativo de amostra na máquina local:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Go

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    Outra alternativa é fazer o download da amostra como um arquivo ZIP e extraí-lo.

  2. Altere para o diretório que contém o código de amostra do Cloud Run for Anthos:

    Node.js

    cd nodejs-docs-samples/run/pubsub/

    Python

    cd python-docs-samples/run/pubsub/

    Go

    cd golang-samples/run/pubsub/

    Java

    cd java-docs-samples/run/pubsub/

Como olhar para o código

O código deste tutorial consiste nisto:

  • Um servidor que processa solicitações recebidas.

    Node.js

    Para que a facilidade no teste do serviço do Node.js seja mantida, a configuração do servidor é separada da inicialização dele.

    O servidor da Web Node.js está configurado em app.js.

    const express = require('express');
    const app = express();
    
    // This middleware is available in Express v4.16.0 onwards
    app.use(express.json());
    O servidor da Web é iniciado em index.js:
    const app = require('./app.js');
    const PORT = parseInt(parseInt(process.env.PORT)) || 8080;
    
    app.listen(PORT, () =>
      console.log(`nodejs-pubsub-tutorial listening on port ${PORT}`)
    );

    Python

    import base64
    import os
    
    from flask import Flask, request
    
    app = Flask(__name__)

    Go

    
    // Sample run-pubsub is a Cloud Run service which handles Pub/Sub messages.
    package main
    
    import (
    	"encoding/json"
    	"io/ioutil"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	http.HandleFunc("/", HelloPubSub)
    	// Determine port for HTTP service.
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("Defaulting to port %s", port)
    	}
    	// Start HTTP server.
    	log.Printf("Listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    

    Java

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class PubSubApplication {
      public static void main(String[] args) {
        SpringApplication.run(PubSubApplication.class, args);
      }
    }

  • Um gerenciador que processa a mensagem do Pub/Sub e registra uma saudação.

    Node.js

    app.post('/', (req, res) => {
      if (!req.body) {
        const msg = 'no Pub/Sub message received';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
      if (!req.body.message) {
        const msg = 'invalid Pub/Sub message format';
        console.error(`error: ${msg}`);
        res.status(400).send(`Bad Request: ${msg}`);
        return;
      }
    
      const pubSubMessage = req.body.message;
      const name = pubSubMessage.data
        ? Buffer.from(pubSubMessage.data, 'base64').toString().trim()
        : 'World';
    
      console.log(`Hello ${name}!`);
      res.status(204).send();
    });

    Python

    @app.route("/", methods=["POST"])
    def index():
        envelope = request.get_json()
        if not envelope:
            msg = "no Pub/Sub message received"
            print(f"error: {msg}")
            return f"Bad Request: {msg}", 400
    
        if not isinstance(envelope, dict) or "message" not in envelope:
            msg = "invalid Pub/Sub message format"
            print(f"error: {msg}")
            return f"Bad Request: {msg}", 400
    
        pubsub_message = envelope["message"]
    
        name = "World"
        if isinstance(pubsub_message, dict) and "data" in pubsub_message:
            name = base64.b64decode(pubsub_message["data"]).decode("utf-8").strip()
    
        print(f"Hello {name}!")
    
        return ("", 204)
    
    

    Go

    
    // PubSubMessage is the payload of a Pub/Sub event.
    // See the documentation for more details:
    // https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage
    type PubSubMessage struct {
    	Message struct {
    		Data []byte `json:"data,omitempty"`
    		ID   string `json:"id"`
    	} `json:"message"`
    	Subscription string `json:"subscription"`
    }
    
    // HelloPubSub receives and processes a Pub/Sub push message.
    func HelloPubSub(w http.ResponseWriter, r *http.Request) {
    	var m PubSubMessage
    	body, err := ioutil.ReadAll(r.Body)
    	if err != nil {
    		log.Printf("ioutil.ReadAll: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    	// byte slice unmarshalling handles base64 decoding.
    	if err := json.Unmarshal(body, &m); err != nil {
    		log.Printf("json.Unmarshal: %v", err)
    		http.Error(w, "Bad Request", http.StatusBadRequest)
    		return
    	}
    
    	name := string(m.Message.Data)
    	if name == "" {
    		name = "World"
    	}
    	log.Printf("Hello %s!", name)
    }
    

    Java

    import com.example.cloudrun.Body;
    import java.util.Base64;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    // PubsubController consumes a Pub/Sub message.
    @RestController
    public class PubSubController {
      @RequestMapping(value = "/", method = RequestMethod.POST)
      public ResponseEntity receiveMessage(@RequestBody Body body) {
        // Get PubSub message from request body.
        Body.Message message = body.getMessage();
        if (message == null) {
          String msg = "Bad Request: invalid Pub/Sub message format";
          System.out.println(msg);
          return new ResponseEntity(msg, HttpStatus.BAD_REQUEST);
        }
    
        String data = message.getData();
        String target =
            !StringUtils.isEmpty(data) ? new String(Base64.getDecoder().decode(data)) : "World";
        String msg = "Hello " + target + "!";
    
        System.out.println(msg);
        return new ResponseEntity(msg, HttpStatus.OK);
      }
    }

    É necessário codificar o serviço para retornar um código de resposta HTTP preciso. Códigos de êxito, como HTTP 200 ou 204, confirmam o processamento completo da mensagem do Pub/Sub. Códigos de erro, como HTTP 400 ou 500, indicam que a mensagem será repetida, conforme descrito no guia Como receber mensagens usando push.

  • Um Dockerfile que define o ambiente operacional do serviço. O conteúdo do Dockerfile varia de acordo com o idioma.

    Node.js

    
    # Use the official lightweight Node.js 12 image.
    # https://hub.docker.com/_/node
    FROM node:18-slim
    
    # Create and change to the app directory.
    WORKDIR /usr/src/app
    
    # Copy application dependency manifests to the container image.
    # A wildcard is used to ensure both package.json AND package-lock.json are copied.
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install dependencies.
    # If you add a package-lock.json speed your build by switching to 'npm ci'.
    # RUN npm ci --only=production
    RUN npm install --production
    
    # Copy local code to the container image.
    COPY . .
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]
    

    Python

    
    # Use the official Python image.
    # https://hub.docker.com/_/python
    FROM python:3.10
    
    # Allow statements and log messages to immediately appear in the Cloud Run logs
    ENV PYTHONUNBUFFERED True
    
    # Copy application dependency manifests to the container image.
    # Copying this separately prevents re-running pip install on every code change.
    COPY requirements.txt ./
    
    # Install production dependencies.
    RUN pip install -r requirements.txt
    
    # Copy local code to the container image.
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . ./
    
    # Run the web service on container startup.
    # Use gunicorn webserver with one worker process and 8 threads.
    # For environments with multiple CPU cores, increase the number of workers
    # to be equal to the cores available.
    # Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
    

    Go

    
    # Use the offical golang image to create a binary.
    # This is based on Debian and sets the GOPATH to /go.
    # https://hub.docker.com/_/golang
    FROM golang:1.17-buster as builder
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Retrieve application dependencies.
    # This allows the container build to reuse cached dependencies.
    # Expecting to copy go.mod and if present go.sum.
    COPY go.* ./
    RUN go mod download
    
    # Copy local code to the container image.
    COPY . ./
    
    # Build the binary.
    RUN go build -v -o server
    
    # Use the official Debian slim image for a lean production container.
    # https://hub.docker.com/_/debian
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM debian:buster-slim
    RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
        ca-certificates && \
        rm -rf /var/lib/apt/lists/*
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/server /server
    
    # Run the web service on container startup.
    CMD ["/server"]
    

    Java

    Esta amostra usa o Jib (em inglês) para criar imagens do Docker usando ferramentas comuns do Java. O Jib otimiza builds de contêiner sem a necessidade de um Dockerfile ou de ter o Docker (em inglês) instalado. Saiba mais sobre como criar contêineres Java com o Jib.
    <plugin>
      <groupId>com.google.cloud.tools</groupId>
      <artifactId>jib-maven-plugin</artifactId>
      <version>3.3.1</version>
      <configuration>
        <to>
          <image>gcr.io/PROJECT_ID/pubsub</image>
        </to>
      </configuration>
    </plugin>
    

Para saber detalhes sobre como autenticar a origem das solicitações do Pub/Sub, leia a seção abaixo sobre como integrar ao Pub/Sub.

Como enviar o código

O código de envio consiste em três etapas: criar uma imagem de contêiner com o Cloud Build, fazer upload da imagem de contêiner no Container Registry e implantar a imagem de contêiner no Cloud Run.

Para enviar o código, siga estas etapas:

  1. Compile seu contêiner e publique no Container Registry.

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    em que PROJECT_ID é o ID do projeto do Cloud e pubsub é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem de "SUCESSO" contendo o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    em que PROJECT_ID é o ID do projeto do Cloud e pubsub é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem de "SUCESSO" contendo o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/pubsub

    em que PROJECT_ID é o ID do projeto do Cloud e pubsub é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem de "SUCESSO" contendo o ID, a hora da criação e o nome da imagem. A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

    Java

    mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/pubsub

    em que PROJECT_ID é o ID do projeto do Cloud e pubsub é o nome que você quer dar ao seu serviço.

    Após a conclusão, você verá uma mensagem "BUILD SUCCESS". A imagem é armazenada no Container Registry e poderá ser reutilizada, se você quiser.

  2. Execute o comando a seguir para implantar o app:

    gcloud run deploy pubsub-tutorial --image gcr.io/PROJECT_ID/pubsub

    Substitua PROJECT_ID pelo ID do projeto do Cloud. pubsub é o nome do contêiner e pubsub-tutorial é o nome do serviço. Observe que a imagem do contêiner é implantada no serviço e no cluster que você configurou anteriormente em Como configurar o gcloud.

    Aguarde até que a implantação esteja concluída. Isso pode levar cerca de 30 segundos. Em caso de sucesso, a linha de comando exibe o URL de serviço. Esse URL é usado para configurar uma assinatura do Pub/Sub.

  3. Se você quer implantar uma atualização de código no serviço, repita as etapas anteriores. Cada implantação em um serviço cria uma nova revisão e inicia automaticamente o tráfego de serviço quando estiver pronto.

Como integrar ao Pub/Sub

Agora que implantamos nosso serviço do Cloud Run, configuraremos o Pub/Sub para enviar mensagens para ele.

Para integrar o serviço com o Pub/Sub:

  1. Ative o Pub/Sub para criar tokens de autenticação no seu projeto:

    gcloud projects add-iam-policy-binding PROJECT_ID \
         --member=serviceAccount:service-PROJECT-NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
         --role=roles/iam.serviceAccountTokenCreator

    Substitua:

    • PROJECT_ID pelo ID do projeto do Cloud.
    • PROJECT-NUMBER pelo número do projeto do Cloud.
  2. Crie ou selecione uma conta de serviço para representar a identidade da assinatura de Pub/Sub.

    gcloud iam service-accounts create cloud-run-pubsub-invoker \
         --display-name "Cloud Run for Anthos Pub/Sub Invoker"

    É possível usar cloud-run-pubsub-invoker ou substituir por um nome exclusivo no projeto do Cloud.

  3. Crie uma assinatura do Pub/Sub com a conta de serviço:

    1. Ative o TLS e o HTTPS automáticos para o cluster e adicione um mapeamento de domínio ao serviço.

    2. Registre a propriedade do domínio para o Pub/Sub.

    3. Adicione um código para validar o token de autenticação anexado a mensagens do Pub/Sub. O código de amostra é fornecido na Autenticação e autorização pelo endpoint de push.

      A autenticação deve garantir que o token seja válido e associado à conta de serviço esperada. Ao contrário do Cloud Run, o Cloud Run para Anthos não tem verificação de autorização integrada para verificar se o token é válido ou se a conta de serviço tem autorização para invocar o Cloud Run para Anthos.

    4. Crie uma assinatura do Pub/Sub com a conta de serviço:

      gcloud pubsub subscriptions create myRunSubscription --topic myRunTopic \
           --push-endpoint=SERVICE-URL/ \
           --push-auth-service-account=cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com

      Substitua:

      • myRunTopic pelo tópico que você criou anteriormente.
      • SERVICE-URL pelo URL do serviço personalizado. Especifique https como o protocolo.
      • PROJECT_ID pelo ID do projeto do Cloud.

      A sinalização --push-auth-service-account ativa a funcionalidade de push do Pub/Sub para Autenticação e autorização.

Seu serviço agora está totalmente integrado ao Pub/Sub.

Como testar

Para testar a solução de ponta a ponta, siga estas etapas:

  1. Para enviar uma mensagem do Pub/Sub para o tópico:

    gcloud pubsub topics publish myRunTopic --message "Runner"

    Também é possível publicar mensagens programaticamente em vez de usar a linha de comando, conforme mostrado neste tutorial. Para mais informações, consulte Como publicar mensagens.

  2. Navegue até os registros de serviço:

    1. Navegue até a página do Cloud Run for Anthos no console do Cloud:

      Acessar o Cloud Run for Anthos

    2. Clique no serviço pubsub-tutorial.

    3. Selecione a guia Registros.

      Os registros podem demorar alguns instantes para aparecer. Se você não os vir imediatamente, verifique novamente após alguns instantes.

  3. Procure a mensagem "Hello Runner!".

Limpeza

Para ver um caso de uso mais detalhado do uso do Cloud Run for Anthos com o Pub/Sub, ignore a limpeza por enquanto e continue o tutorial sobre o processamento de imagens.

Se você criou um novo projeto para este tutorial, exclua o projeto. Se você usou um projeto atual e quer mantê-lo sem as alterações incluídas neste tutorial, exclua os recursos criados para o tutorial.

Como excluir o projeto

O jeito mais fácil de evitar cobranças é excluindo o projeto que você criou para o tutorial.

Para excluir o projeto:

  1. No Console do Google Cloud, acesse a página Gerenciar recursos.

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

Como excluir recursos do tutorial

  1. Exclua o serviço do Cloud Run para Anthos que você implantou neste tutorial:

    gcloud run services delete SERVICE-NAME

    SERVICE-NAME é o nome escolhido do serviço.

    Também é possível excluir os serviços do Cloud Run for Anthos no console do Google Cloud:

    Acessar o Cloud Run for Anthos

  2. Remova as configurações padrão da gcloud que você adicionou durante a configuração do tutorial:

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  3. Remova a configuração do projeto:

     gcloud config unset project
    
  4. Exclua outros recursos do Google Cloud criados neste tutorial:

A seguir