Verificando autenticación…

Saltar al contenido principal

Python Cloud Function

Propósito y Alcance

Este proyecto es un template básico diseñado para facilitar el desarrollo y despliegue de funciones de Google Cloud Functions Generation 2 utilizando Python. El objetivo principal es proporcionar una estructura de proyecto que permita a los desarrolladores comenzar rápidamente y que sea adaptable a diversas necesidades de desarrollo en la nube.

Problema que resuelve: Simplifica la configuración inicial y la estructura de un proyecto para Google Cloud Functions Gen 2, promoviendo buenas prácticas de organización y despliegue.

Alcance:

  • Proporciona una estructura de directorios base.
  • Incluye un ejemplo de función HTTP simple (main.py).
  • Define la gestión de dependencias (requirements.txt).
  • Configuración para despliegue en múltiples entornos (dev, intg, prod) a través de archivos YAML.
  • Integración con pipelines de CI/CD (GitLab CI y Jenkins).
  • Configuración básica para la Cloud Function (runtime, memoria, timeout, etc.).

Este template está pensado para una función sencilla, pero puede ser adaptado para funciones más complejas o integrado con otros servicios de Google Cloud, como Pub/Sub, Firestore, etc.

Arquitectura

El proyecto está diseñado para desplegarse como una Google Cloud Function de Segunda Generación (Gen 2). Este tipo de función se ejecuta en un entorno containerizado sobre Cloud Run, lo que ofrece mayor flexibilidad y capacidades que la primera generación.

La arquitectura del proyecto se organiza de la siguiente manera:

.
├── deploy
│ ├── env
│ │ ├── dev.yaml # Variables de entorno para desarrollo
│ │ ├── intg.yaml # Variables de entorno para integración
│ │ └── prod.yaml # Variables de entorno para producción
│ └── function # Folder de configuración Cloud Function
│ ├── environment.yaml.erb # Configuración de entorno para la función (template)
│ └── runtime-config.yaml.erb # Configuración de tiempo de ejecución (template)
├── src # Folder principal del código
│ ├── main.py # Código principal de la función
│ └── requirements.txt # Dependencias de Python
├── .gitlab-ci.yml # Configuración de pipeline para GitLab CI
├── Jenkinsfile # Pipelines de integración Jenkins
└── README.md # Documentación del proyecto

El punto de entrada de la función se define en src/main.py. Las configuraciones específicas del despliegue y del entorno de ejecución de la Cloud Function se gestionan mediante los archivos .yaml y .yaml.erb en el directorio deploy/.

Tecnologías y Dependencias

  • Lenguaje: Python 3.12 (definido en deploy/function/runtime-config.yaml.erb)
  • Plataforma Cloud: Google Cloud Functions Gen 2
  • Framework de Funciones: functions-framework==3.8.1
  • Librerías Principales:
    • MarkupSafe~=2.1.5 (para escapado seguro de HTML)
    • (Otras dependencias se añaden en src/requirements.txt según sea necesario)
  • CI/CD:
    • GitLab CI (configurado en .gitlab-ci.yml usando $LATAM_PIPELINE_CLOUD_FUNCTION_DEPLOY)
    • Jenkins (referenciado por Jenkinsfile)
  • Gestión de Dependencias: Pip (a través de requirements.txt)

Configuración Local

Prerrequisitos

  • Python 3.12
  • Pip (generalmente viene con Python)
  • Google Cloud SDK (CLI gcloud) instalado y configurado.
  • (Opcional) Un entorno virtual para Python (e.g., venv, conda).

Pasos de Configuración

  1. Clonar el repositorio: Sigue los siguientes pasos

  2. (Opcional pero recomendado) Crear y activar un entorno virtual:

    python -m venv venv
    source venv/bin/activate # En Linux/macOS
    # venv\Scriptsctivate # En Windows
  3. Instalar dependencias:

    pip install -r src/requirements.txt
  4. Configurar src/main.py: Modifica la función entry_point (o la función que definas como punto de entrada) en src/main.py según las necesidades de tu lógica de negocio.

    # src/main.py
    import functions_framework
    from markupsafe import escape

    @functions_framework.http
    def entry_point(request):
    """HTTP Cloud Function.
    Args:
    request (flask.Request): The request object.
    Returns:
    The response text, or any set of values that can be turned into a
    Response object using `make_response`.
    """
    request_json = request.get_json(silent=True)
    request_args = request.args

    if request_json and "name" in request_json:
    name = request_json["name"]
    elif request_args and "name" in request_args:
    name = request_args["name"]
    else:
    name = "World"
    return f"Hello {escape(name)}!"
  5. Ejecutar localmente (usando Functions Framework): Para probar la función localmente, puedes usar el Functions Framework:

    functions-framework --target=entry_point --source=src/main.py --port=8080 --debug

    Luego, puedes acceder a http://localhost:8080 en tu navegador o con curl.

Despliegue

El despliegue de la Cloud Function se gestiona a través de pipelines de CI/CD y scripts de gcloud.

Configuración de Despliegue

Los archivos clave para la configuración del despliegue son:

  • deploy/function/runtime-config.yaml.erb: Define las propiedades de ejecución de la Cloud Function.

    runtime: python312
    cpu: 1
    memory: 512MB
    timeout: 3600s
    region: "<%= region %>" # Se interpola desde archivos de entorno
    entry-point: entry_point
    gen2: ~
    source: src
    max-instances: 2
    trigger-http:
    update-labels: "cmdb-<%= cmdb_it_element %>=<%= cmdb_it_element %>"
    service-account: "<%= service_account %>"
  • deploy/function/environment.yaml.erb: Define variables de entorno que se pasarán a la función.

    BRANCH: "<%= branch_name %>"
    IS_PROD: "<%= is_prod_deploy %>"
    GIT_COMMIT: "<%= git_commit %>"
    BUILD_ID: "<%= build_id %>"

    TEST_ENV: "<%= test_env %>" # Ejemplo, se interpola desde archivos de entorno
  • deploy/env/*.yaml: Contienen variables específicas para cada entorno (dev, intg, prod). Ejemplo dev.yaml:

    test_env: "test in dev"
    region: ${{ REGION }} # Estas pueden ser variables de CI/CD
    service_account: ${{ SERVICE_ACCOUNT }}
    project_id: ${{ PROJECT_ID }}
    cmdb_it_element: ${{ IT_ELEMENT }}

Pipelines de CI/CD

  • GitLab CI: El archivo .gitlab-ci.yml utiliza un componente de pipeline predefinido para el despliegue:

    include:
    - component: $LATAM_PIPELINE_CLOUD_FUNCTION_DEPLOY

    Este pipeline se encarga de los pasos de build, test, security, release, deploy, etc.

  • Jenkins: Un Jenkinsfile está incluido en la raíz del proyecto, sugiriendo la posibilidad de integración con Jenkins para los pipelines.

Comandos gcloud (Ejemplo Manual)

Aunque el despliegue suele ser automatizado, un comando gcloud manual para desplegar una función Gen 2 podría parecerse a (adaptado de la configuración):

gcloud functions deploy YOUR_FUNCTION_NAME \
--gen2 \
--runtime=python312 \
--region=YOUR_REGION \
--source=./src \
--entry-point=entry_point \
--trigger-http \
--allow-unauthenticated \ # O configurar autenticación según sea necesario
--service-account=YOUR_SERVICE_ACCOUNT_EMAIL \
--memory=512MB \
--cpu=1 \
--timeout=3600s \
--max-instances=2 \
--set-env-vars=VAR1=value1,VAR2=value2 \ # Variables del environment.yaml.erb
--update-labels=cmdb-YOUR_CMDB_ELEMENT=YOUR_CMDB_ELEMENT

Las variables (YOUR_FUNCTION_NAME, YOUR_REGION, etc.) y los archivos de configuración (--env-vars-file) serían manejados por el pipeline de CI/CD a partir de los archivos deploy/env/*.yaml y deploy/function/*.yaml.erb.

Endpoints de la API

La función de ejemplo en src/main.py expone un endpoint HTTP.

MétodoEndpointDescripciónParámetros de Solicitud (JSON o Query)Ejemplo de Solicitud (Query)Ejemplo de Respuesta (Éxito)Autenticación
GET/POST/ (relativo a la URL de la función)Saluda al nombre proporcionado o a "World" si no se proporciona nombre. El nombre de la función forma parte de la URL base.name (string, opcional)curl "FUNCTION_URL?name=Usuario"Hello Usuario!--trigger-http (por defecto permite no autenticado si se especifica, o requiere IAM)

Ejemplo de Solicitud (JSON con curl):

curl -X POST \
FUNCTION_URL \
-H "Content-Type: application/json" \
-d '{"name": "Mundo"}'

Respuesta:

Hello Mundo!

Nota: La URL base de la función (FUNCTION_URL) es proporcionada por Google Cloud después del despliegue.

Componentes Específicos

Este template base no incluye integraciones directas con componentes como Pub/Sub, Storage, Redis, Kafka, etc. Sin embargo, está diseñado para ser extendido fácilmente.

Para integrar con otros servicios de Google Cloud o servicios externos:

  1. Añadir Dependencias: Agrega las librerías cliente necesarias al archivo src/requirements.txt. Por ejemplo:

    • Para Google Cloud Storage: google-cloud-storage
    • Para Google Cloud Pub/Sub: google-cloud-pubsub
  2. Modificar src/main.py: Implementa la lógica para interactuar con el servicio deseado dentro de tu función.

    # Ejemplo conceptual para interactuar con Storage
    from google.cloud import storage

    def interact_with_storage():
    storage_client = storage.Client()
    bucket = storage_client.bucket("my-bucket-name")
    blob = bucket.blob("my-file.txt")
    # ... realizar operaciones con el blob ...
  3. Configuración de Entorno: Si es necesario, añade variables de entorno (nombres de buckets, topics, etc.) en los archivos deploy/function/environment.yaml.erb y deploy/env/*.yaml.

  4. Permisos: Asegúrate de que la cuenta de servicio (service-account especificada en runtime-config.yaml.erb) asociada a la Cloud Function tenga los permisos IAM necesarios para interactuar con los otros servicios.

Pruebas

Este template no incluye un framework de pruebas configurado por defecto (como pytest o unittest). Se recomienda añadir pruebas unitarias y de integración según las necesidades del proyecto.

Pasos Sugeridos para Añadir Pruebas:

  1. Elegir un Framework de Pruebas: Por ejemplo, pytest.

    • Añadir pytest a src/requirements.txt (o a un requirements-dev.txt).
  2. Escribir Pruebas: Crear un directorio tests/ en la raíz del proyecto o dentro de src/ y escribir los archivos de prueba.

    # ejemplo tests/test_main.py
    # from src.main import entry_point # Ajustar import según estructura
    # import flask

    # def test_entry_point_no_name():
    # req = flask.Request.from_values() # Simulación básica
    # # Mockear get_json y args si es necesario para una prueba más robusta
    # # O usar el cliente de prueba de Flask con Functions Framework
    # response = entry_point(req)
    # assert response == "Hello World!"

    # def test_entry_point_with_name_query():
    # req = flask.Request.from_values(args={'name': 'TestUser'})
    # response = entry_point(req)
    # assert response == "Hello TestUser!"
  3. Ejecutar Pruebas:

    python -m pytest
  4. Integración con CI/CD: Añadir un paso en el pipeline de CI/CD (.gitlab-ci.yml o Jenkinsfile) para ejecutar las pruebas automáticamente.

Consideraciones de Seguridad

  • Permisos de Cuenta de Servicio: La cuenta de servicio especificada en deploy/function/runtime-config.yaml.erb (service_account) debe seguir el principio de moindre privilège (menor privilegio). Otorgar solo los roles y permisos IAM estrictamente necesarios para que la función opere.
  • Acceso a la Función:
    • Para funciones HTTP, considera si deben ser de acceso público (--allow-unauthenticated) o si deben requerir autenticación IAM.
    • Para funciones activadas por eventos (e.g., Pub/Sub, Storage), la autenticación es manejada por el servicio que invoca.
  • Manejo de Secretos: Evita hardcodear secretos (API keys, contraseñas) en el código. Utiliza variables de entorno (configuradas a través de environment.yaml.erb y los archivos env/*.yaml, cuyos valores pueden provenir de un gestor de secretos en el pipeline de CI/CD) o Google Secret Manager.
  • Dependencias: Mantén las dependencias en src/requirements.txt actualizadas y revisa regularmente por vulnerabilidades conocidas.
  • Validación de Entrada: Para funciones HTTP, valida y sanea todas las entradas del usuario (e.g., request.args, request.get_json()) para prevenir vulnerabilidades como XSS (aunque MarkupSafe ayuda con el escapado en la salida en el ejemplo).
  • Registro y Monitoreo: Configura un logging adecuado para auditoría y depuración. Utiliza Cloud Logging y Cloud Monitoring.