Instructivo: Depura un servicio de Cloud Run mediante Eventarc

En este instructivo, se explica cómo solucionar errores del entorno de ejecución que se encuentran cuando implementas eventos de Cloud Storage mediante registros de auditoría de Cloud en un servicio de Cloud Run no autenticado.

Objetivos

  • Crear un bucket de Cloud Storage para que sea la fuente del evento
  • Compilar, subir y, luego, implementar una imagen de contenedor en Cloud Run.
  • Crear activadores de Eventarc.
  • Subir un archivo al bucket de Cloud Storage.
  • Solucionar problemas y corregir errores de entorno de ejecución

Costos

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

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud sean aptos para obtener una prueba gratuita.

Cuando finalices este instructivo, podrás borrar los recursos creados para evitar que se te siga facturando. Para obtener más información, consulta cómo hacer una limpieza.

Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Antes de comenzar

Sigue los requisitos previos en Recibe un evento de registros de auditoría de Cloud.

Crea un bucket de Cloud Storage

Crea dos buckets de almacenamiento en dos regiones como fuente de eventos para el servicio de Cloud Run:

us-east1

export BUCKET1="troubleshoot-bucket1-$(gcloud config get-value core/project)"
gsutil mb -l us-east1 gs://${BUCKET1}

us-west1

export BUCKET2="troubleshoot-bucket2-$(gcloud config get-value core/project)"
gsutil mb -l us-west1 gs://${BUCKET2}

Después de crear la fuente del evento, implementa el servicio del receptor de eventos en Cloud Run.

Recupera la muestra de código

Clone el repositorio:

Comienza a usarlo

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

Java

git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git
cd java-docs-samples/eventarc/audit-storage

.NET

git clone https://github.com/GoogleCloudPlatform/dotnet-docs-samples.git
cd dotnet-docs-samples/eventarc/audit-storage

Node.js

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git
cd nodejs-docs-samples/eventarc/audit-storage

Python

git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
cd python-docs-samples/eventarc/audit-storage

Revisa el código

El código de este instructivo consta de los siguientes elementos:

  • Un servidor que controla los mensajes entrantes unidos en un CloudEvent dentro de la solicitud HTTP POST:

    Comienza a usarlo

    
    // Sample audit_storage is a Cloud Run service which handles Cloud Audit Log events with Cloud Storage data.
    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    // HelloEventsStorage receives and processes a Cloud Audit Log event with Cloud Storage data.
    func HelloEventsStorage(w http.ResponseWriter, r *http.Request) {
    	s := fmt.Sprintf("Detected change in Cloud Storage bucket: %s", string(r.Header.Get("Ce-Subject")))
    	log.Printf(s)
    	fmt.Fprintln(w, s)
    }
    

    Java

    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class EventController {
    
      private static final List<String> requiredFields =
          Arrays.asList("ce-id", "ce-source", "ce-type", "ce-specversion");
    
      @RequestMapping(value = "/", method = RequestMethod.POST)
      public ResponseEntity<String> receiveMessage(
          @RequestBody Map<String, Object> body, @RequestHeader Map<String, String> headers) {
        for (String field : requiredFields) {
          if (headers.get(field) == null) {
            String msg = String.format("Missing expected header: %s.", field);
            System.out.println(msg);
            return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
          }
        }
    
        if (headers.get("ce-subject") == null) {
          String msg = "Missing expected header: ce-subject.";
          System.out.println(msg);
          return new ResponseEntity<String>(msg, HttpStatus.BAD_REQUEST);
        }
    
        String ceSubject = headers.get("ce-subject");
        String msg = "Detected change in Cloud Storage bucket: " + ceSubject;
        System.out.println(msg);
        return new ResponseEntity<String>(msg, HttpStatus.OK);
      }
    }

    .NET

    
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            logger.LogInformation("Service is starting...");
    
            app.UseRouting();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapPost("/", async context =>
                {
                    logger.LogInformation("Handling HTTP POST");
    
                    var ceSubject = context.Request.Headers["ce-subject"];
                    logger.LogInformation($"ce-subject: {ceSubject}");
    
                    if (string.IsNullOrEmpty(ceSubject))
                    {
                        context.Response.StatusCode = 400;
                        await context.Response.WriteAsync("Bad Request: expected header Ce-Subject");
                        return;
                    }
    
                    await context.Response.WriteAsync($"GCS CloudEvent type: {ceSubject}");
                });
            });
        }
    }
    

    Node.js

    const express = require('express');
    const app = express();
    
    app.use(express.json());
    app.post('/', (req, res) => {
      if (!req.header('ce-subject')) {
        return res
          .status(400)
          .send('Bad Request: missing required header: ce-subject');
      }
    
      console.log(
        `Detected change in Cloud Storage bucket: ${req.header('ce-subject')}`
      );
      return res
        .status(200)
        .send(
          `Detected change in Cloud Storage bucket: ${req.header('ce-subject')}`
        );
    });
    
    module.exports = app;

    Python

    import os
    
    from flask import Flask, request
    
    app = Flask(__name__)
    if __name__ == "__main__":
        app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
  • Un Dockerfile que define el entorno operativo del servicio. El contenido del Dockerfile varía según el lenguaje

    Comienza a usarlo

    
    # 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

    
    # Use the official maven/Java 8 image to create a build artifact.
    # https://hub.docker.com/_/maven
    FROM maven:3.8-jdk-11 as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    
    # Build a release artifact.
    RUN mvn package -DskipTests
    
    # Use AdoptOpenJDK for base image.
    # It's important to use OpenJDK 8u191 or above that has container support enabled.
    # https://hub.docker.com/r/adoptopenjdk/openjdk8
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM adoptopenjdk/openjdk11:alpine-slim
    
    # Copy the jar to the production image from the builder stage.
    COPY --from=builder /app/target/audit-storage-*.jar /audit-storage.jar
    
    # Run the web service on container startup.
    CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/audit-storage.jar"]
    

    .NET

    
    # Use Microsoft's official build .NET image.
    # https://hub.docker.com/_/microsoft-dotnet-core-sdk/
    FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build
    WORKDIR /app
    
    # Install production dependencies.
    # Copy csproj and restore as distinct layers.
    COPY *.csproj ./
    RUN dotnet restore
    
    # Copy local code to the container image.
    COPY . ./
    WORKDIR /app
    
    # Build a release artifact.
    RUN dotnet publish -c Release -o out
    
    # Use Microsoft's official runtime .NET image.
    # https://hub.docker.com/_/microsoft-dotnet-core-aspnet/
    FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime
    WORKDIR /app
    COPY --from=build /app/out ./
    
    # Run the web service on container startup.
    ENTRYPOINT ["dotnet", "AuditStorage.dll"]

    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 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.9-slim
    
    # 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.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

Envía el código

Para enviar el código, haz lo siguiente:

  1. Compila tu imagen de contenedor con Cloud Build y súbela a Container Registry:

    Comienza a usarlo

     gcloud builds submit --tag gcr.io/PROJECT_ID/audit-storage

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud. Verifica el ID del proyecto actual con gcloud config get-value project.

    Si la operación se realiza con éxito, se muestra 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 el contenedor a 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/audit-storage

      Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud.

      audit-storage es el nombre del contenedor. Verifica el ID del proyecto actual con gcloud config get-value project. Si la operación se realiza con éxito, se muestra un mensaje de éxito. La imagen se almacena en Container Registry y puede volver a usarse si así se desea.

    .NET

     gcloud builds submit --tag gcr.io/PROJECT_ID/audit-storage

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud. Verifica el ID del proyecto actual con gcloud config get-value project.

    Si la operación se realiza con éxito, se muestra 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.

    Node.js

     gcloud builds submit --tag gcr.io/PROJECT_ID/audit-storage

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud. Verifica el ID del proyecto actual con gcloud config get-value project.

    Si la operación se realiza con éxito, se muestra 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/audit-storage

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud. Verifica el ID del proyecto actual con gcloud config get-value project.

    Si la operación se realiza con éxito, se muestra 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.

  2. Implementa la imagen del contenedor en Cloud Run:

    gcloud run deploy troubleshoot-service --image gcr.io/PROJECT_ID/audit-storage \
    --allow-unauthenticated
    

    Reemplaza PROJECT_ID por el ID del proyecto de GCP.
    audit-storage es el nombre del contenedor y troubleshoot-service es el nombre del servicio de Cloud Run.

    Ten en cuenta que la imagen de contenedor se implementa en el servicio y en la región que configuraste antes en Configura gcloud.

  3. Cuando realices implementaciones en Cloud Run, responde y, “Sí”, a la opción Permitir sin autenticación. Para obtener más detalles sobre la autenticación basada en IAM, consulta Funciones y permisos de Eventarc.

    Cuando la implementación se realiza correctamente, la línea de comandos muestra la URL del servicio.

Crea un activador

Ahora que implementaste un servicio de Cloud Run, configura un activador para escuchar eventos de Cloud Storage a través de registros de auditoría.

  1. Crea un activador de registro de auditoría para detectar los eventos de Cloud Storage:

    gcloud eventarc triggers create troubleshoot-trigger \
      --destination-run-service=troubleshoot-service \
      --event-filters="type=google.cloud.audit.log.v1.written" \
      --event-filters="serviceName=storage.googleapis.com" \
      --event-filters="methodName=storage.objects.create" \
      --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
    

    Esto crea un activador llamado troubleshoot-trigger.

  2. Para confirmar que se creó troubleshoot-trigger, ejecuta el siguiente comando:

    gcloud eventarc triggers list
    

    Se muestra troubleshoot-trigger con un objetivo de troubleshoot-service.

Genera y visualiza un evento

Para confirmar que implementaste correctamente el servicio y puedes recibir eventos de Cloud Storage, sigue estos pasos:

  1. Crea y sube un archivo en el primer bucket de almacenamiento:

     echo "Hello World" > random.txt
     gsutil cp random.txt gs://${BUCKET1}/random.txt
    
  2. Supervisa los registros para verificar si el servicio recibió un evento. Los registros de servicio muestran que el servicio escucha eventos, lo que indica un problema en la configuración:

    Now listening on: http://0.0.0.0:8080

Investiga el problema

Ahora puedes continuar con el proceso de investigar por qué el servicio no recibe eventos.

Registros de auditoría

En este instructivo, los eventos de Cloud Storage se emiten a través de registros de auditoría y se envían a Cloud Run. Verifica si los registros de auditoría están habilitados para Cloud Storage.

  1. Ve a IAM y administración > Registros de auditoría y selecciona la casilla de verificación Google Cloud Storage. Ir a la consola de registros de auditoría de Cloud
  2. Asegúrate de que los tipos de registros Lectura de administración, Lectura de datos y Escritura de datos de los registros de auditoría de Cloud estén seleccionados.

Una vez que habilites los registros de auditoría de Cloud, vuelve a subir el archivo y verifica los registros. El servicio sigue sin recibir eventos y podría estar relacionado con la ubicación del activador.

Ubicación del activador

Puede haber varios recursos en diferentes ubicaciones y debes filtrar los eventos de fuentes que se encuentren en la misma región que el destino de Cloud Run. Para obtener más información, consulta las ubicaciones compatibles con Eventarc.

En este instructivo, implementaste el servicio de Cloud Run en us-central1. También creaste un activador en la misma ubicación porque estableciste eventarc/location en us-central1.

Para enumerar la ubicación del activador, describe su activador:

gcloud eventarc triggers describe troubleshoot-trigger

Sin embargo, creaste dos buckets en las ubicaciones us-east1 y us-west1. Para recibir eventos desde esas ubicaciones, debes crear activadores en ellas. Crea un activador en us-east1.

  1. Borra el activador existente en la ubicación us-central1:

       gcloud eventarc triggers delete troubleshoot-trigger
    
  2. Establece la ubicación y la región en us-east1:

      gcloud config set eventarc/location us-east1
      gcloud config set run/region us-east1
    
  3. Vuelve a enviar el código.

  4. Crea un activador nuevo en la ubicación us-east1:

      gcloud eventarc triggers create troubleshoot-trigger \
        --destination-run-service=troubleshoot-service \
        --event-filters="type=google.cloud.audit.log.v1.written" \
        --event-filters="serviceName=storage.googleapis.com" \
        --event-filters="methodName=storage.objects.create" \
        --service-account=${PROJECT_NUMBER}-compute@developer.gserviceaccount.com
    
  5. Verifica que se haya creado el activador:

       gcloud eventarc triggers list
    

La inicialización de un activador puede tomar hasta 10 minutos antes de comenzar a entregar eventos.

Hora de inicialización

Una vez que se crean los activadores, hay hasta 10 minutos de tiempo de inicialización para que los eventos comiencen a fluir. Ejecuta el siguiente comando para confirmar que los activadores estén activos:

   gcloud eventarc triggers list

Se muestra un resultado similar que indica el estado del activador:

    NAME                   TYPE                               DESTINATION_RUN_SERVICE  DESTINATION_RUN_PATH  ACTIVE
    troubleshoot-trigger3  google.cloud.audit.log.v1.written  troubleshoot-service2                          By 14:16:56
    troubleshoot-trigger   google.cloud.audit.log.v1.written  troubleshoot-service                           Yes

Después de 10 minutos, vuelve a subir un archivo a cada bucket. Los eventos para cada archivo se escriben en los registros del servicio de Cloud Run. Si el servicio no recibe eventos, podría estar relacionado con el tamaño de los eventos.

Tamaño del evento

Los eventos que envíe no deben exceder los límites de tamaño del evento (512 KB).

Dejó de funcionar un activador que ya se publicó antes

  1. Verifica que la fuente genere eventos. Verifica los registros de auditoría de Cloud y asegúrate de que el servicio supervisado emita registros. Si se registran registros, pero los eventos no se entregan, comunícate con el equipo de asistencia.

  2. Verifica que exista un tema de Pub/Sub con el mismo nombre de activador.

    1. Para enumerar los activadores, consulta la lista de activadores de Eventarc de gcloud.
    2. Para enumerar los temas de Pub/Sub, ejecuta lo siguiente:

        gcloud pubsub topics list
      

    Verifica que el nombre del tema de Pub/Sub incluya el nombre del activador creado. Si falta el tema de Pub/Sub, crea un tema cuando crees el activador.

  3. Verifica el estado del tema de Pub/Sub:

    1. Asegúrate de que haya una marca de verificación en la pestaña Activadores: en Cloud Console, ve a Cloud Run, selecciona el servicio que creaste y navega a la pestaña Activadores.

    2. Ve a Pub/Sub > Temas y haz clic en el tema de Pub/Sub.

      Ir a temas de Pub/Sub

    3. Supervisa si se publican mensajes en el tema con la métrica: topic/send_message_operation_count. Si no se publican mensajes en el tema, revisa los registros de auditoría de Cloud y asegúrate de que el servicio supervisado emite registros. Si se registran registros, pero los eventos no se entregan, comunícate con el equipo de asistencia.

      Métrica del tema

    4. Supervisa si los mensajes se enviaron de forma correcta a Cloud Run con la métrica: subscription/push_request_count por response_code.

      1. En Cloud Console, ve a Cloud Monitoring.

        Ir a Monitoring

      2. Agrega tu proyecto a un nuevo lugar de trabajo.

      3. Haz clic en Paneles y selecciona el panel Cloud Pub/Sub.

      4. En el panel de Cloud Pub/Sub, haz clic en el tema de Pub/Sub que creaste.

      5. En la sección Incidentes, haz clic en Crear política.

      6. En la página Crear política de alertas, haz clic en Agregar condición.

      7. En la pestaña Métrica, proporciona las condiciones siguientes:

        • Suscripción de Cloud Pub/Sub como el Tipo de recurso.
        • Solicitudes de envío como la métrica.
        • response_code como Agrupar por.
        • 0 como el Umbral de configuración. Métrica de suscripción Si quieres obtener más información sobre las métricas de uso de Pub/Sub, consulta Supervisa las suscripciones de envío.
      8. Haz clic en Agregar para ir a la página Crear política de alertas.

      9. En la sección ¿Cuáles son los pasos para arreglar el problema?, ingresa un nombre de alerta, por ejemplo, samplealert, y haz clic en Guardar.

      10. Para ver la alerta, ve a Supervisar > Panel > Cloud Pub/Sub. Haz clic en el tema Pub/Sub y, luego, en la pestaña Suscripción.

      Si se informan errores de envío, verifica los registros del servicio de Cloud Run. Si el extremo receptor muestra un código de estado incorrecto, eso indica que el código de Cloud Run no funciona como se espera y debes comunicarte con el equipo de asistencia.

Realice 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 las opciones de configuración predeterminadas de gcloud que agregaste durante la configuración del instructivo.

    Quita la configuración de la región:

     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:

    • Borra el activador:
      gcloud eventarc triggers delete TRIGGER_NAME
      
      Reemplaza TRIGGER_NAME por el nombre de tu activador.

    • Borra la imagen del contenedor llamada gcr.io/PROJECT_ID/audit-storage de Container Registry. Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud.

¿Qué sigue?