Tutorial sobre el uso de Workflows con Cloud Run y Cloud Run Functions

En este tutorial se explica cómo usar Workflows para vincular una serie de servicios. Si conectas dos servicios HTTP públicos mediante funciones de Cloud Run, una API REST externa y un servicio de Cloud Run privado, puedes crear una aplicación flexible y sin servidor.

Desplegar las primeras funciones de Cloud Run

Después de recibir una solicitud HTTP, esta función HTTP genera un número aleatorio entre 1 y 100 y, a continuación, devuelve el número en formato JSON.

  1. Crea un directorio llamado randomgen y accede a él:

    mkdir ~/randomgen
    cd ~/randomgen
  2. Crea un archivo de texto con el nombre main.py que contenga el siguiente código de Python:

    import functions_framework
    import random
    from flask import jsonify
    
    
    @functions_framework.http
    def randomgen(request):
        randomNum = random.randint(1, 100)
        output = {"random": randomNum}
        return jsonify(output)
  3. Para admitir una dependencia de Flask para el procesamiento HTTP, crea un archivo de texto para el gestor de paquetes pip. Asigna el nombre de archivo requirements.txt y añade lo siguiente:

    flask>=1.0.2
    functions-framework==3.0.0
  4. Despliega la función con un activador HTTP y permite el acceso sin autenticar:

    gcloud functions deploy randomgen-function \
        --gen2 \
        --runtime python310 \
        --entry-point=randomgen \
        --trigger-http \
        --allow-unauthenticated

    La función puede tardar unos minutos en implementarse. También puedes usar la interfaz de funciones de Cloud Run en la Google Cloud consola para desplegar la función.

  5. Una vez que se haya implementado la función randomgen, puede confirmar la propiedad httpsTrigger.url:

    gcloud functions describe randomgen-function \
        --gen2 \
        --format="value(serviceConfig.uri)"
  6. Guarda la URL. Tendrás que añadirlo al archivo de origen de tu flujo de trabajo en ejercicios posteriores.

  7. Puedes probar la función con el siguiente comando curl:

    curl $(gcloud functions describe randomgen-function \
        --gen2 \
        --format="value(serviceConfig.uri)")

    Se genera y se devuelve un número aleatorio.

Desplegar la segunda función de Cloud Run

Después de recibir una solicitud HTTP, esta función HTTP extrae input del cuerpo JSON, lo multiplica por 2 y devuelve el resultado en formato JSON.

  1. Vuelve a tu directorio principal:

    cd ~
  2. Crea un directorio llamado multiply y accede a él:

    mkdir ~/multiply
    cd ~/multiply
  3. Crea un archivo de texto con el nombre main.py que contenga el siguiente código de Python:

    import functions_framework
    from flask import jsonify
    
    
    @functions_framework.http
    def multiply(request):
        request_json = request.get_json()
        output = {"multiplied": 2 * request_json['input']}
        return jsonify(output)
  4. Para admitir una dependencia de Flask para el procesamiento HTTP, crea un archivo de texto para el gestor de paquetes pip. Asigna el nombre de archivo requirements.txt y añade lo siguiente:

    flask>=1.0.2
    functions-framework==3.0.0
  5. Despliega la función con un activador HTTP y permite el acceso sin autenticar:

    gcloud functions deploy multiply-function \
        --gen2 \
        --runtime python310 \
        --entry-point=multiply \
        --trigger-http \
        --allow-unauthenticated

    La función puede tardar unos minutos en implementarse. También puedes usar la interfaz de funciones de Cloud Run en la Google Cloud consola para desplegar la función.

  6. Una vez que se haya implementado la función multiply, puede confirmar la propiedad httpsTrigger.url:

    gcloud functions describe multiply-function \
        --gen2\
        --format="value(serviceConfig.uri)"
  7. Guarda la URL. Tendrás que añadirlo al archivo de origen de tu flujo de trabajo en ejercicios posteriores.

  8. Puedes probar la función con el siguiente comando curl:

    curl -X POST MULTIPLY_FUNCTION_URL \
        -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
        -H "Content-Type: application/json" \
        -d '{"input": 5}'

    Debería devolver el número 10.

Conectar las dos funciones de Cloud Run en un flujo de trabajo

Un flujo de trabajo se compone de una serie de pasos descritos mediante la sintaxis de Workflows, que se puede escribir en formato YAML o JSON. Esta es la definición del flujo de trabajo. Para obtener una explicación detallada, consulta la página Referencia de sintaxis.

  1. Vuelve a tu directorio principal:

    cd ~
  2. Crea un archivo de texto con el nombre workflow.yaml que contenga lo siguiente:

    - randomgen_function:
        call: http.get
        args:
            url: RANDOMGEN_FUNCTION_URL
        result: randomgen_result
    - multiply_function:
        call: http.post
        args:
            url: MULTIPLY_FUNCTION_URL
            body:
                input: ${randomgen_result.body.random}
        result: multiply_result
    - return_result:
        return: ${multiply_result}
    
    • Sustituye RANDOMGEN_FUNCTION_URL por la URL de tu función randomgen.
    • Sustituye MULTIPLY_FUNCTION_URL por la URL de tu función multiply.

    Este archivo de origen vincula las dos funciones HTTP y devuelve un resultado final.

    .
  3. Después de crear el flujo de trabajo, puedes implementarlo para que esté listo para ejecutarse.

    gcloud workflows deploy WORKFLOW_NAME \
        --source=workflow.yaml \
        --service-account=${SERVICE_ACCOUNT}@PROJECT_ID.iam.gserviceaccount.com

    Sustituye WORKFLOW_NAME por el nombre que quieras darle al flujo de trabajo.

  4. Ejecuta el flujo de trabajo:

    gcloud workflows run WORKFLOW_NAME

    Una ejecución es una sola vez que se ejecuta la lógica contenida en la definición de un flujo de trabajo. Todas las ejecuciones de flujo de trabajo son independientes y el escalado rápido de Workflows permite un gran número de ejecuciones simultáneas.

    Una vez que se haya ejecutado el flujo de trabajo, la salida debería ser similar a la siguiente:

    result: '{"body":{"multiplied":120},"code":200,"headers":{"Alt-Svc":"h3-29=\":443\";
    ...
    startTime: '2021-05-05T14:17:39.135251700Z'
    state: SUCCEEDED
    ...
    

Conectar un servicio REST público en el flujo de trabajo

Actualiza tu flujo de trabajo y conecta una API REST pública (math.js) que pueda evaluar expresiones matemáticas. Por ejemplo, curl https://api.mathjs.org/v4/?'expr=log(56)'.

Ten en cuenta que, como has desplegado tu flujo de trabajo, también puedes editarlo en la página Flujos de trabajo de la Google Cloud consola.

  1. Edita el archivo de origen de tu flujo de trabajo y sustitúyelo por el siguiente contenido:

    - randomgen_function:
        call: http.get
        args:
            url: RANDOMGEN_FUNCTION_URL
        result: randomgen_result
    - multiply_function:
        call: http.post
        args:
            url: MULTIPLY_FUNCTION_URL
            body:
                input: ${randomgen_result.body.random}
        result: multiply_result
    - log_function:
        call: http.get
        args:
            url: https://api.mathjs.org/v4/
            query:
                expr: ${"log(" + string(multiply_result.body.multiplied) + ")"}
        result: log_result
    - return_result:
        return: ${log_result}
    
    • Sustituye RANDOMGEN_FUNCTION_URL por la URL de tu función randomgen.
    • Sustituye MULTIPLY_FUNCTION_URL por la URL de tu función multiply.

    De esta forma, se vincula el servicio REST externo a las funciones de Cloud Run y se devuelve un resultado final.

  2. Despliega el flujo de trabajo modificado:

    gcloud workflows deploy WORKFLOW_NAME \
        --source=workflow.yaml \
        --service-account=${SERVICE_ACCOUNT}@PROJECT_ID.iam.gserviceaccount.com

Desplegar un servicio de Cloud Run

Despliega un servicio de Cloud Run que, tras recibir una solicitud HTTP, extrae input del cuerpo JSON, calcula su math.floor y devuelve el resultado.

  1. Crea un directorio llamado floor y accede a él:

    mkdir ~/floor
    cd ~/floor
  2. Crea un archivo de texto con el nombre app.py que contenga el siguiente código de Python:

    import json
    import logging
    import os
    import math
    
    from flask import Flask, request
    
    app = Flask(__name__)
    
    
    @app.route('/', methods=['POST'])
    def handle_post():
        content = json.loads(request.data)
        input = float(content['input'])
        return f"{math.floor(input)}", 200
    
    
    if __name__ != '__main__':
        # Redirect Flask logs to Gunicorn logs
        gunicorn_logger = logging.getLogger('gunicorn.error')
        app.logger.handlers = gunicorn_logger.handlers
        app.logger.setLevel(gunicorn_logger.level)
        app.logger.info('Service started...')
    else:
        app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))

  3. En el mismo directorio, crea un archivo Dockerfile con el siguiente contenido:

    # Use an official lightweight Python image.
    # https://hub.docker.com/_/python
    FROM python:3.7-slim
    
    # Install production dependencies.
    RUN pip install Flask gunicorn
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY . .
    
    # Run the web service on container startup. Here we use the 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 0.0.0.0:8080 --workers 1 --threads 8 app:app

  4. Crea un repositorio estándar de Artifact Registry donde puedas almacenar tu imagen de contenedor Docker:

    gcloud artifacts repositories create REPOSITORY \
        --repository-format=docker \
        --location=${REGION}

    Sustituye REPOSITORY por un nombre único para el repositorio.

  5. Crea la imagen del contenedor:

    export SERVICE_NAME=floor
    gcloud builds submit --tag ${REGION}-docker.pkg.dev/PROJECT_ID/REPOSITORY/${SERVICE_NAME}
  6. Despliega la imagen de contenedor en Cloud Run y asegúrate de que solo acepte llamadas autenticadas:

    gcloud run deploy ${SERVICE_NAME} \
        --image ${REGION}-docker.pkg.dev/PROJECT_ID/REPOSITORY/${SERVICE_NAME}:latest \
        --no-allow-unauthenticated

Cuando veas la URL del servicio, el despliegue se habrá completado. Deberá especificar esa URL al actualizar la definición del flujo de trabajo.

Conectar el servicio de Cloud Run en el flujo de trabajo

Actualiza el flujo de trabajo y especifica la URL del servicio de Cloud Run.

  1. Vuelve a tu directorio principal:

    cd ~
  2. Edita el archivo de origen de tu flujo de trabajo y sustitúyelo por el siguiente contenido:

    - randomgen_function:
        call: http.get
        args:
            url: RANDOMGEN_FUNCTION_URL
        result: randomgen_result
    - multiply_function:
        call: http.post
        args:
            url: MULTIPLY_FUNCTION_URL
            body:
                input: ${randomgen_result.body.random}
        result: multiply_result
    - log_function:
        call: http.get
        args:
            url: https://api.mathjs.org/v4/
            query:
                expr: ${"log(" + string(multiply_result.body.multiplied) + ")"}
        result: log_result
    - floor_function:
        call: http.post
        args:
            url: CLOUD_RUN_SERVICE_URL
            auth:
                type: OIDC
            body:
                input: ${log_result.body}
        result: floor_result
    - create_output_map:
        assign:
          - outputMap:
              randomResult: ${randomgen_result}
              multiplyResult: ${multiply_result}
              logResult: ${log_result}
              floorResult: ${floor_result}
    - return_output:
        return: ${outputMap}
    
    • Sustituye RANDOMGEN_FUNCTION_URL por la URL de tu función randomgen.
    • Sustituye MULTIPLY_FUNCTION_URL por la URL de tu función multiply.
    • Sustituye CLOUD_RUN_SERVICE_URL por la URL de tu servicio de Cloud Run.

    De esta forma, se conecta el servicio de Cloud Run en el flujo de trabajo. Ten en cuenta que la clave auth asegura que se transmita un token de autenticación en la llamada al servicio de Cloud Run. Para obtener más información, consulta el artículo Hacer solicitudes autenticadas desde un flujo de trabajo.

  3. Despliega el flujo de trabajo modificado:

    gcloud workflows deploy WORKFLOW_NAME \
        --source=workflow.yaml \
        --service-account=${SERVICE_ACCOUNT}@PROJECT_ID.iam.gserviceaccount.com
  4. Ejecuta el flujo de trabajo final:

    gcloud workflows run WORKFLOW_NAME

    La salida debería ser similar a la siguiente:

    result: '{"floorResult":{"body":"4","code":200
      ...
      "logResult":{"body":"4.02535169073515","code":200
      ...
      "multiplyResult":{"body":{"multiplied":56},"code":200
      ...
      "randomResult":{"body":{"random":28},"code":200
      ...
    startTime: '2023-11-13T21:22:56.782669001Z'
    state: SUCCEEDED
    

¡Enhorabuena! Has desplegado y ejecutado un flujo de trabajo que conecta una serie de servicios entre sí.

Para crear flujos de trabajo más complejos con expresiones, saltos condicionales, codificación o decodificación Base64, subflujos de trabajo y más, consulta la referencia de sintaxis de Workflows y la descripción general de la biblioteca estándar.