Tutorial: solução de problemas locais de um serviço do Cloud Run


Neste tutorial, mostramos como um desenvolvedor de serviços pode resolver problemas de um serviço do Cloud Run corrompido usando as ferramentas do Google Cloud Observability para descoberta e um fluxo de trabalho de desenvolvimento local para investigação.

Neste "tutorial de estudo de caso" passo a passo do guia de solução de problemas é usado um projeto de amostra que resulta em erros de ambiente de execução quando implantados, que você soluciona para encontrar e corrigir o problema.

Objetivos

  • Escrever, criar e implantar um serviço no Cloud Run
  • Use o Error Reporting e o Cloud Logging para identificar um erro
  • Recuperar a imagem do contêiner do Container Registry para uma análise de causa raiz
  • Corrigir o serviço de "produção" e melhorá-lo para reduzir problemas futuros

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. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

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

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

    Go to project selector

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

  6. Ative a API Cloud Run Admin
  7. Instale e inicialize a CLI gcloud.
  8. Atualize os componentes:
    gcloud components update
  9. Siga as instruções para instalar o Docker localmente

Funções exigidas

Para conseguir as permissões necessárias para concluir o tutorial, peça ao administrador para conceder a você os seguintes papéis do IAM no seu projeto:

Para mais informações sobre a concessão de papéis, consulte Gerenciar o acesso a projetos, pastas e organizações.

Também é possível conseguir as permissões necessárias por meio de papéis personalizados ou de outros papéis predefinidos.

Como configurar padrões do gcloud

Para configurar a gcloud com os padrões do serviço do Cloud Run, realize as etapas a seguir:

  1. Defina seu projeto padrão:

    gcloud config set project PROJECT_ID

    Substitua PROJECT_ID pelo nome do projeto que você criou para este tutorial.

  2. Configure a gcloud para a região escolhida:

    gcloud config set run/region REGION

    Substitua REGION pela região compatível do Cloud Run.

Locais do Cloud Run

O Cloud Run é regional, o que significa que a infraestrutura que executa seus serviços do Cloud Run está localizada em uma região específica e é gerenciada pelo Google para estar disponível de maneira redundante em todas as zonas da região.

Atender aos seus requisitos de latência, disponibilidade ou durabilidade são os principais fatores para selecionar a região em que seus serviços do Cloud Run são executados. Geralmente, é possível selecionar a região mais próxima de seus usuários, mas considere a localização dos outros produtos do Google Cloud usados pelo serviço do Cloud Run. O uso de produtos do Google Cloud em vários locais pode afetar a latência e o custo do serviço.

O Cloud Run está disponível nas regiões a seguir:

Sujeitas aos preços do nível 1

  • asia-east1 (Taiwan)
  • asia-northeast1 (Tóquio)
  • asia-northeast2 (Osaka)
  • europe-north1 (Finlândia) Ícone de folha Baixo CO2
  • europe-southwest1 (Madri) Ícone de folha Baixo CO2
  • europe-west1 (Bélgica) Ícone de folha Baixo CO2
  • europe-west4 (Países Baixos) Ícone de folha Baixo CO2
  • europe-west8 (Milão)
  • europe-west9 (Paris) Ícone de folha Baixo CO2
  • me-west1 (Tel Aviv)
  • us-central1 (Iowa) Ícone de folha Baixo CO2
  • us-east1 (Carolina do Sul)
  • us-east4 (Norte da Virgínia)
  • us-east5 (Columbus)
  • us-south1 (Dallas) Ícone de folha Baixo CO2
  • us-west1 (Oregon) Ícone de folha Baixo CO2

Sujeitas aos preços do nível 2

  • africa-south1 (Johannesburgo)
  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seul, Coreia do Sul)
  • asia-southeast1 (Singapura)
  • asia-southeast2 (Jacarta)
  • asia-south1 (Mumbai, Índia)
  • asia-south2 (Déli, Índia)
  • australia-southeast1 (Sydney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Varsóvia, Polônia)
  • europe-west10 (Berlim) Ícone de folha Baixo CO2
  • europe-west12 (Turim)
  • europe-west2 (Londres, Reino Unido) Ícone de folha Baixo CO2
  • europe-west3 (Frankfurt, Alemanha) Ícone de folha Baixo CO2
  • europe-west6 (Zurique, Suíça) Ícone de folha Baixo CO2
  • me-central1 (Doha)
  • me-central2 (Damã)
  • northamerica-northeast1 (Montreal) Ícone de folha Baixo CO2
  • northamerica-northeast2 (Toronto) Ícone de folha Baixo CO2
  • southamerica-east1 (São Paulo, Brasil) Ícone de folha Baixo CO2
  • southamerica-west1 (Santiago, Chile) Ícone de folha Baixo CO2
  • us-west2 (Los Angeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Se você já criou um serviço do Cloud Run, é possível visualizar a região no painel do Cloud Run no console do Google Cloud.

Como montar o código

Crie um novo serviço completo do Cloud Run passo a passo. Lembre-se de que esse serviço cria um erro de ambiente de execução de propósito para o exercício de solução de problemas.

  1. Crie um novo projeto:

    Node.js

    Crie um projeto Node.js definindo o pacote de serviços, as dependências iniciais e algumas operações comuns.

    1. Crie um novo diretório hello-service:

      mkdir hello-service
      cd hello-service
      
    2. Crie um novo projeto Node.js gerando um arquivo package.json:

      npm init --yes
      npm install --save express@4
      
    3. Abra o novo arquivo package.json no seu editor e configure um script start para executar node index.js. Quando terminar, o arquivo terá esta aparência:

      {
        "name": "hello-broken",
        "description": "Broken Cloud Run service for troubleshooting practice",
        "version": "1.0.0",
        "private": true,
        "main": "index.js",
        "scripts": {
          "start": "node index.js",
          "test": "echo \"Error: no test specified\" && exit 0",
          "system-test": "NAME=Cloud c8 mocha -p -j 2 test/system.test.js --timeout=360000 --exit"
        },
        "engines": {
          "node": ">=16.0.0"
        },
        "author": "Google LLC",
        "license": "Apache-2.0",
        "dependencies": {
          "express": "^4.17.1"
        },
        "devDependencies": {
          "c8": "^10.0.0",
          "google-auth-library": "^9.0.0",
          "got": "^11.5.0",
          "mocha": "^10.0.0"
        }
      }
      

    Se você continuar a evoluir esse serviço além do tutorial imediato, preencha a descrição, o autor e avalie a licença. Para mais detalhes, leia a documentação do package.json.

    Python

    1. Crie um novo diretório hello-service:

      mkdir hello-service
      cd hello-service
      
    2. Crie um arquivo requirements.txt e copie suas dependências para ele:

      Flask==3.0.3
      pytest==8.2.0; python_version > "3.0"
      # pin pytest to 4.6.11 for Python2.
      pytest==4.6.11; python_version < "3.0"
      gunicorn==22.0.0
      Werkzeug==3.0.3
      

    Go

    1. Crie um novo diretório hello-service:

      mkdir hello-service
      cd hello-service
      
    2. Crie um projeto em Go inicializando um novo módulo go:

      go mod init example.com/hello-service
      

    É possível atualizar o nome específico como você quiser: atualize o nome se o código for publicado em um repositório de código acessível pela web.

    Java

    1. Crie um novo projeto de instrução:

      mvn archetype:generate \
        -DgroupId=com.example.cloudrun \
        -DartifactId=hello-service \
        -DarchetypeArtifactId=maven-archetype-quickstart \
        -DinteractiveMode=false
      
    2. Copie as dependências para sua lista de dependências pom.xml (entre os elementos <dependencies>):

      <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.9.4</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.12</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.12</version>
      </dependency>
      
    3. Copie a configuração de build para o pom.xml (nos elementos <dependencies>):

      <build>
        <plugins>
          <plugin>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>jib-maven-plugin</artifactId>
            <version>3.4.0</version>
            <configuration>
              <to>
                <image>gcr.io/PROJECT_ID/hello-service</image>
              </to>
            </configuration>
          </plugin>
        </plugins>
      </build>
      

  2. Crie um serviço HTTP para lidar com solicitações de entrada:

    Node.js

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      console.log('hello: received request.');
    
      const {NAME} = process.env;
      if (!NAME) {
        // Plain error logs do not appear in Stackdriver Error Reporting.
        console.error('Environment validation failed.');
        console.error(new Error('Missing required server parameter'));
        return res.status(500).send('Internal Server Error');
      }
      res.send(`Hello ${NAME}!`);
    });
    const port = parseInt(process.env.PORT) || 8080;
    app.listen(port, () => {
      console.log(`hello: listening on port ${port}`);
    });

    Python

    import json
    import os
    
    from flask import Flask
    
    
    app = Flask(__name__)
    
    
    @app.route("/", methods=["GET"])
    def index():
        """Example route for testing local troubleshooting.
    
        This route may raise an HTTP 5XX error due to missing environment variable.
        """
        print("hello: received request.")
    
        NAME = os.getenv("NAME")
    
        if not NAME:
            print("Environment validation failed.")
            raise Exception("Missing required service parameter.")
    
        return f"Hello {NAME}"
    
    
    if __name__ == "__main__":
        PORT = int(os.getenv("PORT")) if os.getenv("PORT") else 8080
    
        # This is used when running locally. Gunicorn is used to run the
        # application on Cloud Run. See entrypoint in Dockerfile.
        app.run(host="127.0.0.1", port=PORT, debug=True)

    Go

    
    // Sample hello demonstrates a difficult to troubleshoot service.
    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	log.Print("hello: service started")
    
    	http.HandleFunc("/", helloHandler)
    
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("Defaulting to port %s", port)
    	}
    
    	log.Printf("Listening on port %s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    
    func helloHandler(w http.ResponseWriter, r *http.Request) {
    	log.Print("hello: received request")
    
    	name := os.Getenv("NAME")
    	if name == "" {
    		log.Printf("Missing required server parameter")
    		// The panic stack trace appears in Cloud Error Reporting.
    		panic("Missing required server parameter")
    	}
    
    	fmt.Fprintf(w, "Hello %s!\n", name)
    }
    

    Java

    import static spark.Spark.get;
    import static spark.Spark.port;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class App {
    
      private static final Logger logger = LoggerFactory.getLogger(App.class);
    
      public static void main(String[] args) {
        int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
        port(port);
    
        get(
            "/",
            (req, res) -> {
              logger.info("Hello: received request.");
              String name = System.getenv("NAME");
              if (name == null) {
                // Standard error logs do not appear in Stackdriver Error Reporting.
                System.err.println("Environment validation failed.");
                String msg = "Missing required server parameter";
                logger.error(msg, new Exception(msg));
                res.status(500);
                return "Internal Server Error";
              }
              res.status(200);
              return String.format("Hello %s!", name);
            });
      }
    }

  3. Crie um Dockerfile para definir a imagem do contêiner usada para implantar o serviço:

    Node.js

    
    # Use the official lightweight Node.js image.
    # https://hub.docker.com/_/node
    FROM node:20-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 copying both package.json AND package-lock.json (when available).
    # Copying this first prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install dependencies.
    # if you need a deterministic and repeatable build create a
    # package-lock.json file and use npm ci:
    # RUN npm ci --omit=dev
    # if you need to include development dependencies during development
    # of your application, use:
    # RUN npm install --dev
    
    RUN npm install --omit=dev
    
    # 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.11
    
    # 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.21-bookworm 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:bookworm-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.4.0</version>
      <configuration>
        <to>
          <image>gcr.io/PROJECT_ID/hello-service</image>
        </to>
      </configuration>
    </plugin>
    

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 seu 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/hello-service

    em que PROJECT_ID é o ID do projeto no Google Cloud. Verifique o ID do projeto atual com gcloud config get-value project.

    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/hello-service

    em que PROJECT_ID é o ID do projeto no Google Cloud. Verifique o ID do projeto atual com gcloud config get-value project.

    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/hello-service

    em que PROJECT_ID é o ID do projeto no Google Cloud. Verifique o ID do projeto atual com gcloud config get-value project.

    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

    1. Use o auxiliar de credencial do gcloud para autorizar o Docker a enviar por push ao Container Registry.
      gcloud auth configure-docker
    2. Use o plug-in do Maven do Jib para criar e enviar por push o contêiner ao Container Registry.
      mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/hello-service

    em que PROJECT_ID é o ID do projeto no Google Cloud. Verifique o ID do projeto atual com gcloud config get-value project.

    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 hello-service --image gcr.io/PROJECT_ID/hello-service

    Substitua PROJECT_ID pelo ID do projeto do Google Cloud. hello-service é o nome da imagem do contêiner e do serviço Cloud Run. Observe que a imagem do contêiner é implantada no serviço e na região que você configurou anteriormente em Como configurar o gcloud

    Responda y, "Sim", ao prompt allow unauthenticated. Consulte Como gerenciar o acesso para mais detalhes sobre a autenticação baseada em IAM.

    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.

Testar

Teste o serviço para confirmar que o implantou. As solicitações falham com um erro HTTP 500 ou 503 (membros da classe Erros de servidor 5xx). O tutorial explica a solução de problemas dessa resposta de erro.

O serviço recebe um URL navegável automaticamente.

  1. Navegue até este URL com seu navegador da Web:

    1. Abra um navegador da Web.

    2. Encontre a saída do URL de serviço pelo comando de implantação anterior.

      Se o comando de implantação não fornecer um URL, algo deu errado. Analise a mensagem de erro e aja de acordo: se nenhuma orientação acionável estiver presente, consulte o guia de solução de problemas e tente novamente o comando de implantação.

    3. Navegue até esse URL copiando-o para a barra de endereço do seu navegador e pressionando ENTER.

  2. Veja o erro HTTP 500 ou HTTP 503.

    Se você receber um erro HTTP 403, talvez tenha rejeitado allow unauthenticated invocations no prompt de implantação. Conceda acesso não autenticado ao serviço para corrigir isso:

    gcloud run services add-iam-policy-binding hello-service \
      --member="allUsers" \
      --role="roles/run.invoker"
    

Para mais informações, leia Como permitir acesso público (não-autenticado).

Como investigar o problema

Veja que o erro HTTP 5xx encontrado acima em Como testar foi encontrado como um erro de ambiente de execução de produção. Este tutorial aborda um processo formal para lidar com ele. Embora os processos de resolução de erros de produção variem muito, este tutorial apresenta uma sequência específica de etapas para mostrar a aplicação de ferramentas e técnicas úteis.

Para investigar esse problema, siga estas etapas:

  • Colete mais detalhes sobre o erro relatado para ajudar na investigação e definir uma estratégia de mitigação.
  • Alivie o impacto do usuário decidindo avançar em uma correção ou reversão para uma versão íntegra conhecida.
  • Reproduza o erro para confirmar se os detalhes corretos foram coletados e se o erro não é uma falha única.
  • Realize uma análise de causa-raiz no bug para encontrar o código, a configuração ou o processo que criou o erro.

No início da investigação, há um URL, um carimbo de data/hora e a mensagem "Erro interno do servidor".

Como coletar mais detalhes

Reúna mais informações sobre o problema para entender o que aconteceu e determinar as próximas etapas.

Use as ferramentas disponíveis do Google Cloud Observability para coletar mais detalhes:

  1. Use o console do Error Reporting, que fornece um painel com detalhes e rastreamento de recorrência para erros com um rastreamento de pilha reconhecido.

    Acessar o console do Error Reporting

    Captura de tela da lista de erros, incluindo o &quot;Status de resolução&quot; das colunas, Ocorrências, Erro e &quot;Visto em&quot;.
    Lista de erros registrados. Os erros são agrupados por mensagem em revisões, serviços e plataformas.
  2. Clique no erro para ver os detalhes do rastreamento de pilha, observando as chamadas de função feitas pouco antes do erro.

    Captura de tela de um único rastreamento de pilha analisado, demonstrando um perfil comum desse erro.
    A amostra de rastreamento de pilha na página de detalhes do erro mostra uma única instância do erro. É possível analisar cada instância individual.
  3. Use o Cloud Logging para analisar a sequência de operações que levam ao problema, incluindo mensagens de erro que não estão incluídas no console do Error Reporting devido à falta de um rastreamento de pilha de erros reconhecido:

    Acessar o Console do Cloud Logging

    Selecione Revisão do Cloud Run > hello-service na primeira caixa suspensa. Isso filtra as entradas de registro para aquelas geradas pelo serviço.

Leia mais sobre como visualizar registros no Cloud Run

Reverter para uma versão íntegra

Se esse for um serviço estabelecido, conhecido por funcionar, haverá uma revisão anterior do serviço no Cloud Run. Este tutorial usa um novo serviço sem versões anteriores. Portanto, não é possível fazer uma reversão.

No entanto, se você tiver um serviço com versões anteriores para reverter, siga Como visualizar detalhes da revisão para extrair o nome do contêiner e os detalhes de configuração necessários para criar uma nova implantação íntegra do serviço.

Como reproduzir o erro

Usando os detalhes que você recebeu anteriormente, confirme se o problema ocorre de forma consistente nas condições de teste.

Envie a mesma solicitação HTTP testando-a novamente e veja se o mesmo erro e detalhes são relatados. Pode levar algum tempo para que os detalhes do erro sejam exibidos.

Como o serviço de amostra neste tutorial é somente leitura e não aciona efeitos secundários de complicações, a reprodução de erros na produção é segura. No entanto, para muitos serviços reais, isso não acontecerá: talvez seja necessário reproduzir erros em um ambiente de teste ou limitar essa etapa à investigação local.

A reprodução do erro estabelece o contexto para trabalhos futuros. Por exemplo, se os desenvolvedores não conseguirem reproduzir o erro, investigações adicionais podem exigir instrumentação adicional do serviço.

Como realizar uma análise de causa raiz

A análise da causa raiz é uma etapa importante na solução de problemas eficaz para garantir que você corrija o problema em vez do sintoma.

Anteriormente neste tutorial, você reproduziu o problema no Cloud Run, que confirma que o problema está ativo quando o serviço está hospedado no Cloud Run. Agora, reproduza o problema localmente para determinar se ele está isolado do código ou se ele só aparece na hospedagem de produção.

  1. Se você não tiver usado a CLI do Docker localmente com o Container Registry, faça a autenticação com o gcloud:

    gcloud auth configure-docker

    Para abordagens alternativas, consulte Métodos de autenticação do Container Registry.

  2. Se o nome da imagem do contêiner usado mais recentemente não estiver disponível, a descrição do serviço terá as informações da imagem do contêiner implantada mais recentemente:

    gcloud run services describe hello-service

    Encontre o nome da imagem do contêiner dentro do objeto spec. Um comando mais segmentado pode recuperá-lo diretamente:

    gcloud run services describe hello-service \
       --format="value(spec.template.spec.containers.image)"

    Esse comando revela um nome de imagem de contêiner como gcr.io/PROJECT_ID/hello-service.

  3. Extraia a imagem do contêiner do Container Registry para seu ambiente. Essa etapa pode levar alguns minutos para fazer o download da imagem do contêiner:

    docker pull gcr.io/PROJECT_ID/hello-service

    Atualizações posteriores na imagem do contêiner que reutilizam esse nome podem ser recuperadas com o mesmo comando. Se você pular esta etapa, o comando docker run abaixo extrairá uma imagem de contêiner se ela não estiver presente na máquina local.

  4. Execute localmente para confirmar que o problema não é exclusivo do Cloud Run:

    PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
       gcr.io/PROJECT_ID/hello-service

    Analisando os elementos do comando acima:

    • A variável de ambiente PORT é usada pelo serviço para determinar a porta a ser ouvida no contêiner.
    • O comando run inicia o contêiner, assumindo como padrão o comando entrypoint definido no Dockerfile ou em uma imagem de contêiner pai.
    • A sinalização --rm exclui a instância do contêiner na saída.
    • A sinalização -e atribui um valor a uma variável de ambiente. -e PORT=$PORT está propagando a variável PORT do sistema local para o contêiner com o mesmo nome de variável.
    • A sinalização -p publica o contêiner como um serviço disponível no localhost na porta 9000. Solicitações para localhost:9000 serão roteadas para o contêiner na porta 8080. Isso significa que a saída do serviço sobre o número da porta em uso não corresponde à forma como o serviço é acessado.
    • O argumento final gcr.io/PROJECT_ID/hello-service é uma imagem de contêiner tag, um rótulo legível para o identificador de hash sha256 de uma imagem de contêiner. Se não estiver disponível localmente, o docker tentará recuperar a imagem de um registro remoto.

    No navegador, abra http://localhost:9000. Verifique se há mensagens de erro na saída do terminal em {ops_name}}.

    Se o problema não for reproduzível localmente, ele poderá ser exclusivo para o ambiente do Cloud Run. Consulte o guia de solução de problemas do Cloud Run para conhecer áreas específicas.

    Nesse caso, o erro é reproduzido localmente.

Agora que o erro foi confirmado duplamente como persistente e causado pelo código de serviço em vez da plataforma de hospedagem, é hora de investigar o código mais de perto.

Para os fins deste tutorial, é seguro presumir que o código dentro do contêiner e o código no sistema local sejam idênticos.

Revise o rastreamento de pilha do relatório de erros e faça referência cruzada com o código para encontrar as linhas específicas com falha.

Node.js

Encontre a origem da mensagem de erro no arquivo index.js ao redor do número de linha chamado no rastreamento de pilha mostrado nos registros:
const {NAME} = process.env;
if (!NAME) {
  // Plain error logs do not appear in Stackdriver Error Reporting.
  console.error('Environment validation failed.');
  console.error(new Error('Missing required server parameter'));
  return res.status(500).send('Internal Server Error');
}

Python

Encontre a origem da mensagem de erro no arquivo main.py ao redor do número de linha chamado no rastreamento de pilha mostrado nos registros:
NAME = os.getenv("NAME")

if not NAME:
    print("Environment validation failed.")
    raise Exception("Missing required service parameter.")

Go

Encontre a origem da mensagem de erro no arquivo main.go ao redor do número de linha chamado no rastreamento de pilha mostrado nos registros:

name := os.Getenv("NAME")
if name == "" {
	log.Printf("Missing required server parameter")
	// The panic stack trace appears in Cloud Error Reporting.
	panic("Missing required server parameter")
}

Java

Encontre a origem da mensagem de erro no arquivo App.java ao redor do número de linha chamado no rastreamento de pilha mostrado nos registros:

String name = System.getenv("NAME");
if (name == null) {
  // Standard error logs do not appear in Stackdriver Error Reporting.
  System.err.println("Environment validation failed.");
  String msg = "Missing required server parameter";
  logger.error(msg, new Exception(msg));
  res.status(500);
  return "Internal Server Error";
}

Ao examinar esse código, as seguintes ações são realizadas quando a variável de ambiente NAME não está definida:

  • Um erro é registrado no Google Cloud Observability
  • Uma resposta de erro HTTP é enviada

O problema é causado por uma variável ausente, mas a causa raiz é mais específica: a alteração de código que adiciona a dependência de uma variável de ambiente não inclui alterações relacionadas aos scripts de implantação e documentação de requisitos de ambiente de execução.

Como corrigir a causa raiz

Agora que coletamos o código e identificamos a possível causa raiz, podemos tomar medidas para corrigi-lo.

  • Verifique se o serviço funciona localmente com o ambiente NAME disponível no local:

    1. Execute o contêiner localmente com a variável de ambiente adicionada:

      PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
       -e NAME="Local World!" \
       gcr.io/PROJECT_ID/hello-service
    2. Navegue para http://localhost:9000.

    3. Consulte "Hello Local World!" que aparece na página.

  • Modifique o ambiente de execução do Cloud Run para incluir essa variável:

    1. Execute o comando de atualização de serviços para adicionar uma variável de ambiente:

      gcloud run services update hello-service \
        --set-env-vars NAME=Override
      
    2. Aguarde alguns segundos enquanto o Cloud Run cria uma nova revisão com base na revisão anterior com a nova variável de ambiente adicionada.

  • Confirme se o serviço foi corrigido:

    1. Navegue até o URL do serviço do Cloud Run.
    2. Consulte "Hello Override!" que aparece na página.
    3. Certifique-se de que mensagens ou erros inesperados não apareçam no Cloud Logging ou no Error Reporting.

Como melhorar a velocidade das futuras soluções de problemas

Neste exemplo de problema de produção, o erro estava relacionado à configuração operacional. Há alterações de código que minimizam o impacto desse problema no futuro.

  • Melhore o registro de erros para incluir detalhes mais específicos.
  • Em vez de retornar um erro, faça com que o serviço retorne a um padrão seguro. Se o uso de um padrão representar uma alteração na funcionalidade normal, use uma mensagem de aviso para fins de monitoramento.

Vamos remover a variável de ambiente NAME como uma dependência forçada.

  1. Remova o código de manipulação NAME atual:

    Node.js

    const {NAME} = process.env;
    if (!NAME) {
      // Plain error logs do not appear in Stackdriver Error Reporting.
      console.error('Environment validation failed.');
      console.error(new Error('Missing required server parameter'));
      return res.status(500).send('Internal Server Error');
    }

    Python

    NAME = os.getenv("NAME")
    
    if not NAME:
        print("Environment validation failed.")
        raise Exception("Missing required service parameter.")

    Go

    name := os.Getenv("NAME")
    if name == "" {
    	log.Printf("Missing required server parameter")
    	// The panic stack trace appears in Cloud Error Reporting.
    	panic("Missing required server parameter")
    }

    Java

    String name = System.getenv("NAME");
    if (name == null) {
      // Standard error logs do not appear in Stackdriver Error Reporting.
      System.err.println("Environment validation failed.");
      String msg = "Missing required server parameter";
      logger.error(msg, new Exception(msg));
      res.status(500);
      return "Internal Server Error";
    }

  2. Adicione o novo código que define um valor substituto:

    Node.js

    const NAME = process.env.NAME || 'World';
    if (!process.env.NAME) {
      console.log(
        JSON.stringify({
          severity: 'WARNING',
          message: `NAME not set, default to '${NAME}'`,
        })
      );
    }

    Python

    NAME = os.getenv("NAME")
    
    if not NAME:
        NAME = "World"
        error_message = {
            "severity": "WARNING",
            "message": f"NAME not set, default to {NAME}",
        }
        print(json.dumps(error_message))

    Go

    name := os.Getenv("NAME")
    if name == "" {
    	name = "World"
    	log.Printf("warning: NAME not set, default to %s", name)
    }

    Java

    String name = System.getenv().getOrDefault("NAME", "World");
    if (System.getenv("NAME") == null) {
      logger.warn(String.format("NAME not set, default to %s", name));
    }

  3. Teste localmente recriando e executando o contêiner por meio dos casos de configuração afetados:

    Node.js

    docker build --tag gcr.io/PROJECT_ID/hello-service .

    Python

    docker build --tag gcr.io/PROJECT_ID/hello-service .

    Go

    docker build --tag gcr.io/PROJECT_ID/hello-service .

    Java

    mvn compile jib:build

    Confirme se a variável de ambiente NAME ainda funciona:

    PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
     -e NAME="Robust World" \
     gcr.io/PROJECT_ID/hello-service

    Confirme se o serviço funciona sem a variável NAME:

    PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
     gcr.io/PROJECT_ID/hello-service

    Se o serviço não retornar um resultado, confirme a remoção do código na primeira etapa que não removeu linhas extras, como aquelas usadas para gravar a resposta.

  4. Implemente esse recurso revisitando a seção Implantar seu código.

    Cada implantação em um serviço cria uma nova revisão e inicia automaticamente o tráfego de serviço quando estiver pronto.

    Para limpar as variáveis de ambiente definidas anteriormente:

    gcloud run services update hello-service --clear-env-vars

Adicione a nova funcionalidade do valor padrão à cobertura de teste automatizada do serviço.

Como encontrar outros problemas nos registros

Talvez você veja outros problemas no Visualizador de registros deste serviço. Por exemplo, uma chamada de sistema incompatível aparecerá nos registros como uma "Limitação do sandbox de contêiner".

Por exemplo, os serviços do Node.js às vezes resultam nesta mensagem de registro:

Container Sandbox Limitation: Unsupported syscall statx(0xffffff9c,0x3e1ba8e86d88,0x0,0xfff,0x3e1ba8e86970,0x3e1ba8e86a90). Please, refer to https://gvisor.dev/c/linux/amd64/statx for more information.

Nesse caso, a falta de suporte não afeta o serviço de amostra hello-service.

Solução de problemas do Terraform

Para resolver problemas ou dúvidas relacionadas ao Terraform, consulte Solução de problemas de validação de políticas do Terraform ou entre em contato com o suporte do Terraform.

Limpar

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. 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.

Como excluir recursos do tutorial

  1. Exclua o serviço do Cloud Run 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 no Console do Google Cloud.

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

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

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

A seguir