Instructivo: Solución de problemas local de un servicio de Cloud Run

En este instructivo, se muestra cómo un desarrollador de servicios puede solucionar un problema en un servicio de Cloud Run con herramientas de Stackdriver para el descubrimiento y un flujo de trabajo de desarrollo local con fines de investigación.

En este “caso de éxito” paso a paso complementario a la guía de solución de problemas, se usa un proyecto de muestra que genera errores de entorno de ejecución cuando se implementa a fin de encontrar y solucionar el problema.

Objetivos

  • Escribir, compilar y, también, implementar un servicio en Cloud Run
  • Usar Error Reporting y Cloud Logging para identificar un error
  • Recuperar la imagen de contenedor de Container Registry para un análisis de causa raíz
  • Solucionar el servicio de “producción” y, luego, mejorarlo para mitigar futuros problemas

Costos

En este instructivo, se usan los siguientes componentes facturables de Cloud Platform:

Usa la calculadora de precios para generar una estimación de los costos según el uso previsto.

Los usuarios nuevos de Cloud Platform podrían cumplir con los requisitos para obtener una prueba gratuita.

Antes de comenzar

  1. Accede a tu cuenta de Google Cloud. Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. En la página del selector de proyectos de Google Cloud Console, selecciona o crea un proyecto de Google Cloud.

    Ir al selector de proyecto

  3. Asegúrate de que la facturación esté habilitada para tu proyecto de Cloud. Descubre cómo confirmar que tienes habilitada la facturación en un proyecto.

  4. Habilita la API de Cloud Run
  5. Instala e inicializa el SDK de Cloud.
  6. Actualiza los componentes:
    gcloud components update
  7. Sigue las instrucciones para instalar Docker de forma local.

Configura los valores predeterminados de gcloud

A fin de configurar gcloud con los valores predeterminados para el servicio de Cloud Run, sigue estos pasos:

  1. Configura el proyecto predeterminado:

    gcloud config set project PROJECT_ID

    Reemplaza PROJECT_ID por el nombre del proyecto que creaste para este instructivo.

  2. Configura gcloud en la región que elegiste:

    gcloud config set run/region REGION

    Reemplaza REGION por la región de Cloud Run compatible que prefieras.

Ubicaciones de Cloud Run

Cloud Run es regional, lo que significa que la infraestructura que ejecuta los servicios se ubica en una región específica, y Google la administra para que esté disponible de manera redundante en todas las zonas de esa región.

El cumplimiento de los requisitos de latencia, disponibilidad o durabilidad es el factor principal para seleccionar la región en la que se ejecutan los servicios de Cloud Run. Por lo general, puedes seleccionar la región más cercana a los usuarios, pero debes considerar la ubicación de los otros productos de Google Cloud que usa el servicio de Cloud Run. Si usas productos de Google Cloud en varias ubicaciones, la latencia y el costo del servicio pueden verse afectados.

Cloud Run está disponible en las siguientes regiones:

Sujetas a los Precios del nivel 1

  • asia-east1 (Taiwán)
  • asia-northeast1 (Tokio)
  • asia-northeast2 (Osaka)
  • europe-north1 (Finlandia) Ícono de la hoja CO más bajo2
  • europe-west1 (Bélgica)
  • europe-west4 (Países Bajos)
  • us-central1 (Iowa) Ícono de la hoja CO más bajo2
  • us-east1 (Carolina del Sur)
  • us-east4 (Virginia del Norte)
  • us-west1 (Oregón) Ícono de la hoja CO más bajo2

Sujetas a los Precios del nivel 2

  • asia-east2 (Hong Kong)
  • asia-northeast3 (Seúl, Corea del Sur)
  • asia-southeast1 (Singapur)
  • asia-southeast2 (Yakarta)
  • asia-south1 (Bombay, India)
  • asia-south2 Delhi (India)
  • australia-southeast1 (Sídney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Varsovia, Polonia)
  • europe-west2 (Londres, Reino Unido)
  • europe-west3 (Fráncfort, Alemania)
  • europe-west6 (Zúrich, Suiza) Ícono de la hoja CO más bajo2
  • northamerica-northeast1 (Montreal) Ícono de la hoja CO más bajo2
  • southamerica-east1 (Sao Paulo, Brasil) Ícono de la hoja CO más bajo2
  • us-west2 (Los Ángeles)
  • us-west3 (Las Vegas)
  • us-west4 (Salt Lake City)

Si ya creaste un servicio de Cloud Run, puedes ver la región en el panel de Cloud Run en Cloud Console.

Ensambla el código

Compila un servicio nuevo de saludo de Cloud Run paso a paso. Te recordamos que este servicio crea un error de entorno de ejecución a propósito para el ejercicio de solución de problemas.

  1. Crea un proyecto nuevo:

    Node.js

    Crea un proyecto de Node.js mediante la definición del paquete de servicio, las dependencias iniciales y algunas operaciones comunes.

    1. Crea un directorio hello-service nuevo:

      mkdir hello-service
      cd hello-service
      
    2. Crea un proyecto de Node.js nuevo mediante la generación de un archivo package.json:

      npm init --yes
      npm install --save express@4
      
    3. Abre el archivo package.json nuevo en tu editor y configura una secuencia de comandos start para ejecutar node index.js. Cuando termines, el archivo se verá de la siguiente manera:

      {
        "name": "hello-service",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
            "start": "node index.js",
            "test": "echo \"Error: no test specified\" && exit 1"
        },
        "keywords": [],
        "author": "",
        "license": "ISC",
        "dependencies": {
            "express": "^4.17.1"
        }
      }

    Si continúas evolucionando este servicio más allá de este instructivo, considera completar la descripción, agregar el autor y evaluar la licencia. Para obtener más detalles, consulta la documentación de package.json.

    Python

    1. Crea un directorio hello-service nuevo:

      mkdir hello-service
      cd hello-service
      
    2. Crea un archivo requirements.txt y copia tus dependencias en él:

      Flask==2.0.1
      pytest==5.3.0; python_version > "3.0"
      pytest==4.6.6; python_version < "3.0"
      gunicorn==20.1.0
      

    Go

    1. Crea un directorio hello-service nuevo:

      mkdir hello-service
      cd hello-service
      
    2. Crea un proyecto de Go mediante la inicialización de un módulo de Go nuevo:

      go mod init example.com/hello-service
      

    Puedes actualizar el nombre específico según lo desees; debes actualizar el nombre si el código se publica en un repositorio de código accesible en la Web.

    Java

    1. Crea un proyecto de Maven nuevo:

      mvn archetype:generate \
        -DgroupId=com.example.cloudrun \
        -DartifactId=hello-service \
        -DarchetypeArtifactId=maven-archetype-quickstart \
        -DinteractiveMode=false
      
    2. Copia las dependencias en tu lista de dependencias pom.xml (entre los elementos <dependencies>):

      <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.9.3</version>
      </dependency>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.3.0-alpha5</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.32</version>
      </dependency>
      
    3. Copia la configuración de compilación en tu pom.xml (en los elementos <dependencies>):

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

  2. Crea un servicio HTTP para controlar las solicitudes entrantes:

    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 = 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():
        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. Crea un Dockerfile a fin de definir la imagen de contenedor que se usa para implementar el servicio:

    Node.js

    
    # Use the official lightweight Node.js 10 image.
    # https://hub.docker.com/_/node
    FROM node:12-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 production dependencies.
    # If you add a package-lock.json, speed your build by switching to 'npm ci'.
    # RUN npm ci --only=production
    RUN npm install --only=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.9
    
    # 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.16-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

    En esta muestra, se usa Jib para compilar imágenes de Docker mediante herramientas de Java comunes. Jib optimiza las compilaciones de contenedores sin la necesidad de tener un Dockerfile o tener Docker instalado. Obtén más información sobre cómo compilar contenedores de Java con Jib.

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

Envía el código

El código de envío consta de tres pasos: compilar una imagen de contenedor con Cloud Build, subir la imagen de contenedor a Container Registry y, luego, implementar la imagen de contenedor en Cloud Run.

Para enviar el código, haz lo siguiente:

  1. Compila el contenedor y publica en Container Registry:

    Node.js

    gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP. Puedes verificar el ID del proyecto actual con gcloud config get-value project.

    Si la operación se completa de manera correcta, deberías ver un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Python

    gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP. Puedes verificar el ID del proyecto actual con gcloud config get-value project.

    Si la operación se completa de manera correcta, deberías ver un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Go

    gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP. Puedes verificar el ID del proyecto actual con gcloud config get-value project.

    Si la operación se completa de manera correcta, deberías ver un mensaje de ÉXITO con el ID, la hora de creación y el nombre de la imagen. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    Java

    1. Usa el auxiliar de credenciales de gcloud para autorizar a Docker a que envíe contenido a tu Container Registry.
      gcloud auth configure-docker
    2. Usa el complemento de Maven para Jib a fin de compilar y enviar el contenedor a Container Registry.
      mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/hello-service

    En el ejemplo anterior, PROJECT_ID es el ID de tu proyecto de GCP. Puedes verificar el ID del proyecto actual con gcloud config get-value project.

    Si se ejecuta de forma correcta, deberías ver un mensaje de COMPILACIÓN EXITOSA. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

  2. Ejecuta el comando siguiente para implementar tu app:

    gcloud run deploy hello-service --image gcr.io/PROJECT_ID/hello-service

    Reemplaza PROJECT_ID por el ID del proyecto de GCP. hello-service es el nombre de la imagen de contenedor y el nombre del servicio de Cloud Run. Ten en cuenta que la imagen de contenedor se implementa en el servicio y la región que configuraste anteriormente en Configura gcloud.

    Responde y, “Sí”, a la opción Permitir sin autenticación. Consulta Administra el acceso para obtener más detalles sobre la autenticación basada en IAM.

    Espera hasta que finalice la implementación; esto puede tomar alrededor de medio minuto. Si la operación se completa de forma correcta, la línea de comandos mostrará la URL de servicio.

Haz una prueba

Prueba el servicio a fin de confirmar que lo implementaste de forma correcta. Las solicitudes deben fallar con un error HTTP 500 o 503 (miembros de la clase Errores de servidor 5xx). En este instructivo, se presenta la solución de problemas de esta respuesta de error.

El servicio recibe automáticamente una URL navegable.

  1. Dirígete a esta URL en tu navegador web:

    1. Abre un navegador web.

    2. Busca la salida de la URL de servicio mediante el comando de implementación anterior.

      Si el comando de implementación no proporcionó una URL, se produjo un error. Revisa el mensaje de error y actúa en consecuencia: si no hay una guía práctica, revisa la guía de solución de problemas y vuelve a intentar usar el comando de implementación.

    3. Para dirigirte a esta URL, cópiala en la barra de direcciones del navegador y presiona INTRO.

  2. Consulta el error HTTP 500 o HTTP 503.

    Si recibes un error HTTP 403, es posible que hayas rechazado allow unauthenticated invocations en el mensaje de implementación. Otorga acceso no autenticado al servicio para corregir esto:

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

Para obtener más información, consulta Permite el acceso público (no autenticado).

Investiga el problema

Supongamos que el error HTTP 5xx que se encontró antes en Haz una prueba surgió como un error de entorno de ejecución de producción. En este instructivo, se presenta un proceso formal para controlarlo. Aunque los procesos de resolución de errores de producción varían mucho, en este instructivo se presenta una secuencia particular de pasos para mostrar la aplicación de herramientas y técnicas útiles.

Para investigar este problema, trabajarás en estas fases:

  • Recopilar más detalles sobre el error informado para respaldar una investigación más detallada y establecer una estrategia de mitigación
  • Aliviar el impacto en el usuario cuando decidas realizar una corrección o una reversión a una versión que sepas que está en buen estado
  • Reproducir el error para confirmar que se recopilaron los detalles correctos y que el error no es una falla única
  • Realizar un análisis de causa raíz en el error para encontrar el código, la configuración o el proceso que creó este error

Al comienzo de la investigación, verás una URL, una marca de tiempo y el mensaje “Error interno del servidor”.

Recopila más detalles

Recopila más información sobre el problema para comprender qué sucedió y determinar los próximos pasos.

Usa las herramientas de Stackdriver disponibles para obtener más detalles:

  1. Usa la consola de Error Reporting. Esta proporciona un panel con detalles y seguimiento de recurrencia de los errores con un seguimiento de pila reconocido.

    Ir a la consola de Error Reporting

    Captura de pantalla de la lista de errores, que incluyen las columnas “Resolution Status” (Estado de resolución), “Occurrences” (Casos), “Error” y “Seen in” (Visto en).
    Lista de errores registrados. Los errores se agrupan por mensaje en las revisiones, los servicios y las plataformas.
  2. Haz clic en el error para ver los detalles del seguimiento de pila, y ten en cuenta las llamadas a función realizadas justo antes del error.

    Captura de pantalla de un único seguimiento de pila analizado que muestra un perfil común de este error.
    En la “Stack trace sample” (Muestra de seguimiento de pila), en la página de detalles del error, se muestra una sola instancia del error. Puedes revisar cada instancia individual.
  3. Usa Cloud Logging para revisar la secuencia de operaciones que generaron el problema, incluidos los mensajes de error que no se incluyen en la consola de Error Reporting debido a la falta de un seguimiento de pila de errores reconocido:

    Ir a la consola de Cloud Logging

    Selecciona Revisión de Cloud Run > hello-service en el primer cuadro desplegable. Esto filtrará las entradas de registro y se mostrarán las que haya generado tu servicio.

Obtén más información sobre cómo ver registros en Cloud Run

Revierte a una versión en buen estado

Si este es un servicio establecido y que se sabe que funciona, habrá una revisión anterior del servicio en Cloud Run. En este instructivo, se usa un servicio nuevo sin versiones anteriores, así que no puedes realizar una reversión.

Sin embargo, si tienes un servicio con versiones anteriores a las que puedes revertir, consulta Visualiza detalles de revisión para extraer el nombre del contenedor y los detalles de configuración necesarios a fin de crear una implementación nueva de tu servicio que funcione.

Reproduce el error

Con los detalles que obtuviste antes, confirma que el problema se produzca de manera coherente en las condiciones de prueba.

Vuelve a hacer una prueba; cuando la realices, envía la misma solicitud HTTP y verifica si se informan los mismos detalles y el mismo error. Es posible que los detalles del error tarden un poco en aparecer.

Debido a que el servicio de muestra de este instructivo es de solo lectura y no genera efectos secundarios complicados, es seguro reproducir errores en la producción. Sin embargo, para muchos servicios reales, este no será el caso: es posible que tengas que reproducir errores en un entorno de prueba o limitar este paso a la investigación local.

Reproducir el error establece el contexto para el trabajo futuro. Por ejemplo, si los desarrolladores no pueden reproducir el error, es posible que se requiera una instrumentación adicional del servicio.

Realiza un análisis de causa raíz

El análisis de la causa raíz es un paso importante en el proceso de solución de problemas eficaz, ya que te aseguras de solucionar el problema en lugar de un síntoma.

En un paso anterior de este instructivo, reprodujiste el problema en Cloud Run y, así, confirmaste que el problema estaba activo cuando el servicio se alojaba en Cloud Run. Ahora, reproduce el problema de forma local para determinar si es un problema aislado del código o si solo aparece en el hosting de producción.

  1. Si no usaste la CLI de Docker de forma local con Container Registry, autentícala con gcloud:

    gcloud auth configure-docker

    Para conocer enfoques alternativos, consulta Métodos de autenticación de Container Registry.

  2. Si el nombre de la imagen del contenedor que usaste de forma más reciente no está disponible, la descripción del servicio contiene la información de la imagen del contenedor implementado más recientemente:

    gcloud run services describe hello-service

    Busca el nombre de la imagen del contenedor dentro del objeto spec. Con un comando más orientado puedes recuperarlo directamente:

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

    Este comando muestra un nombre de imagen de contenedor, como gcr.io/PROJECT_ID/hello-service.

  3. Extrae la imagen del contenedor de Container Registry a tu entorno. Este paso puede tomar varios minutos mientras se descarga la imagen del contenedor:

    docker pull gcr.io/PROJECT_ID/hello-service

    Las actualizaciones posteriores a la imagen del contenedor que vuelven a usar este nombre se pueden recuperar con el mismo comando. Si omites este paso, el comando docker run a continuación extrae una imagen de contenedor si no está presente en la máquina local.

  4. Ejecuta de forma local a fin de confirmar que el problema no es exclusivo de Cloud Run:

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

    A continuación, se muestra un desglose de los elementos del comando anterior:

    • El servicio usa la variable de entorno PORT para determinar el puerto que se escuchará dentro del contenedor.
    • El comando run inicia el contenedor de forma predeterminada con el comando de punto de entrada definido en el Dockerfile o en una imagen de contenedor principal.
    • La marca --rm borra la instancia de contenedor cuando se sale.
    • La marca -e asigna un valor a una variable de entorno. -e PORT=$PORT propaga la variable PORT del sistema local al contenedor con el mismo nombre de variable.
    • La marca -p publica el contenedor como un servicio disponible en localhost en el puerto 9000. Las solicitudes al localhost:9000 se enrutarán al contenedor en el puerto 8080. Esto significa que la salida del servicio sobre el número del puerto en uso no coincidirá con la forma en la que se accede al servicio.
    • El argumento final gcr.io/PROJECT_ID/hello-service es una imagen de contenedor tag, una etiqueta legible para el identificador de hash sha256 de una imagen de contenedor. Si no está disponible de forma local, Docker intenta recuperar la imagen de un registro remoto.

    En tu navegador, abre http://localhost:9000. Verifica la salida de la terminal en busca de mensajes de error que coincidan con los de Google Cloud's operations suite.

    Si el problema no se puede reproducir de forma local, puede ser único del entorno de Cloud Run. Revisa la guía de solución de problemas de Cloud Run para conocer las áreas específicas que debes investigar.

    En este caso, el error se reproduce de forma local.

Ahora que el error está doblemente confirmado como persistente y provocado por el código de servicio en lugar de la plataforma de hosting, es hora de investigar el código con más detalle.

Para los fines de este instructivo, podemos suponer que el código dentro del contenedor y el código del sistema local son idénticos.

Revisa el seguimiento de pila del informe de errores y la referencia cruzada con el código para encontrar las líneas específicas con errores.

Node.js

Encuentra la fuente del mensaje de error en el archivo index.js alrededor del número de línea indicado en el seguimiento de pila que se muestra en los 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

Encuentra la fuente del mensaje de error en el archivo main.py alrededor del número de línea indicado en el seguimiento de pila que se muestra en los registros:
NAME = os.getenv("NAME")

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

Go

Encuentra la fuente del mensaje de error en el archivo main.go alrededor del número de línea indicado en el seguimiento de pila que se muestra en los 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

Encuentra la fuente del mensaje de error en el archivo App.java alrededor del número de línea indicado en el seguimiento de pila que se muestra en los 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";
}

Si examinas este código, verás que se realizan las siguientes acciones si no se establece la variable de entorno NAME:

  • Se registra un error en Google Cloud's operations suite
  • Se envía una respuesta de error HTTP

El problema se debe a una variable faltante, pero la causa raíz es más específica: el cambio de código que agregó una dependencia excesiva de una variable de entorno no incluyó cambios relacionados con la documentación de requisitos del entorno de ejecución y las secuencias de comandos de implementación.

Corrige la causa raíz

Ahora que recopilamos el código y, también, identificamos la posible causa raíz, podemos tomar medidas para solucionarlo.

  • Comprueba si el servicio funciona de manera local con el entorno NAME disponible:

    1. Ejecuta el contenedor de forma local con la variable de entorno agregada:

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

    3. Se mostrará “Hello Local World!” en la página.

  • Modifica el entorno de servicio de Cloud Run en ejecución para incluir esta variable:

    1. Ejecuta el comando services update a fin de agregar una variable de entorno:

      gcloud run services update hello-service \
        --set-env-vars NAME=Override
      
    2. Espera unos segundos mientras Cloud Run crea una revisión nueva basada en la revisión anterior con la variable de entorno nueva agregada.

  • Confirma que el servicio ya se haya corregido:

    1. Dirige tu navegador a la URL de servicio de Cloud Run.
    2. Se mostrará “Hello Override!” en la página.
    3. Verifica que no aparezcan mensajes o errores inesperados en Cloud Logging o Error Reporting.

Mejora la velocidad de solución de problemas en el futuro

En este problema de producción de muestra, el error estaba relacionado con la configuración operativa. Hay cambios de código que minimizarán el impacto de este problema en el futuro.

  • Mejora el registro de errores para incluir detalles más específicos.
  • En lugar de mostrar un error, haz que el servicio vuelva a un valor predeterminado seguro. Si usar un valor predeterminado representa un cambio en la funcionalidad normal, usa un mensaje de advertencia para fines de supervisión.

Analicemos cómo quitar la variable de entorno NAME como una dependencia excesiva.

  1. Quita el código de control de NAME existente:

    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. Agrega un código nuevo que establezca un valor de resguardo:

    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. Vuelve a compilar y ejecutar el contenedor en los casos de configuración afectados para realizar pruebas locales:

    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

    Confirma que la variable de entorno NAME aún funcione:

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

    Confirma que el servicio funcione sin la variable NAME:

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

    Si el servicio no muestra un resultado, confirma que no se hayan quitado líneas adicionales en la eliminación del código en el primer paso, como las que se usan para escribir la respuesta.

  4. Dirígete de nuevo a la sección Implementa el código para implementarlo.

    Con cada implementación en un servicio se crea una revisión nueva y se comienza a entregar tráfico de manera automática cuando está lista.

    Para borrar las variables de entorno establecidas antes, haz lo siguiente:

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

Agrega la funcionalidad nueva del valor predeterminado a la cobertura de prueba automática para el servicio.

Encuentra otros problemas en los registros

Es posible que veas otros problemas en el visor de registros de este servicio. Por ejemplo, una llamada al sistema no compatible aparecerá en los registros como “Limitación de la zona de pruebas del contenedor”.

Por ejemplo, los servicios de Node.js a veces dan como resultado este mensaje 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.

En este caso, la falta de compatibilidad no afecta el servicio de muestra hello-service.

Realiza una limpieza

Si creaste un proyecto nuevo para este instructivo, bórralo. Si usaste un proyecto existente y deseas conservarlo sin los cambios que se agregaron en este instructivo, borra los recursos creados para el instructivo.

Borra el proyecto

La manera más fácil de eliminar la facturación es borrar el proyecto que creaste para el instructivo.

Para borrar el proyecto, sigue estos pasos:

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra los recursos del instructivo

  1. Usa este comando para borrar el servicio de Cloud Run que implementaste en este instructivo:

    gcloud run services delete SERVICE-NAME

    En el ejemplo anterior, SERVICE-NAME es el nombre del servicio que elegiste.

    También puedes borrar los servicios de Cloud Run desde Google Cloud Console.

  2. Quita la configuración de región predeterminada de gcloud que agregaste durante la configuración en el instructivo:

     gcloud config unset run/region
    
  3. Quita la configuración del proyecto:

     gcloud config unset project
    
  4. Borra otros recursos de Google Cloud que creaste en este instructivo:

¿Qué sigue?