Python Cloud Function
Purpose and Scope
This project is a basic template designed to facilitate the development and deployment of Google Cloud Functions Generation 2 using Python. The main goal is to provide a project structure that allows developers to get started quickly and that is adaptable to various cloud development needs.
Problem it solves: It simplifies the initial setup and structure of a project for Google Cloud Functions Gen 2, promoting good organization and deployment practices.
Scope:
- Provides a base directory structure.
- Includes a simple HTTP function example (
main.py). - Defines dependency management (
requirements.txt). - Configuration for deployment in multiple environments (dev, intg, prod) through YAML files.
- Integration with CI/CD pipelines (GitLab CI and Jenkins).
- Basic configuration for the Cloud Function (runtime, memory, timeout, etc.).
This template is intended for a simple function, but can be adapted for more complex functions or integrated with other Google Cloud services, such as Pub/Sub, Firestore, etc.
Architecture
The project is designed to be deployed as a Google Cloud Function Second Generation (Gen 2). This type of function runs in a containerized environment on Cloud Run, offering greater flexibility and capabilities than the first generation.
The project architecture is organized as follows:
.
├── deploy
│ ├── env
│ │ ├── dev.yaml # Environment variables for development
│ │ ├── intg.yaml # Environment variables for integration
│ │ └── prod.yaml # Environment variables for production
│ └── function # Cloud Function configuration folder
│ ├── environment.yaml.erb # Environment configuration for the function (template)
│ └── runtime-config.yaml.erb # Runtime configuration (template)
├── src # Main code folder
│ ├── main.py # Main function code
│ └── requirements.txt # Python dependencies
├── .gitlab-ci.yml # Pipeline configuration for GitLab CI
├── Jenkinsfile # Jenkins integration pipelines
└── README.md # Project documentation
The function entry point is defined in src/main.py. Deployment-specific and Cloud Function runtime configurations are
managed through the .yaml and .yaml.erb files in the deploy/ directory.
Technologies and Dependencies
- Language: Python 3.12 (defined in
deploy/function/runtime-config.yaml.erb) - Cloud Platform: Google Cloud Functions Gen 2
- Functions Framework:
functions-framework==3.8.1 - Main Libraries:
MarkupSafe~=2.1.5(for safe HTML escaping)- (Other dependencies are added in
src/requirements.txtas needed)
- CI/CD:
- GitLab CI (configured in
.gitlab-ci.ymlusing$LATAM_PIPELINE_CLOUD_FUNCTION_DEPLOY) - Jenkins (referenced by
Jenkinsfile)
- GitLab CI (configured in
- Dependency Management: Pip (through
requirements.txt)
Local Setup
Prerequisites
- Python 3.12
- Pip (usually comes with Python)
- Google Cloud SDK
(
gcloudCLI) installed and configured. - (Optional) A Python virtual environment (e.g.,
venv,conda).
Setup Steps
-
Clone the repository: Follow these steps
-
(Optional but recommended) Create and activate a virtual environment:
python -m venv venvsource venv/bin/activate # On Linux/macOS# venv\Scripts\activate # On Windows -
Install dependencies:
pip install -r src/requirements.txt -
Configure
src/main.py: Modify theentry_pointfunction (or the function you define as the entry point) insrc/main.pyaccording to your business logic needs.# src/main.pyimport functions_frameworkfrom markupsafe import escape@functions_framework.httpdef 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 aResponse object using `make_response`."""request_json = request.get_json(silent=True)request_args = request.argsif 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)}!" -
Run locally (using Functions Framework): To test the function locally, you can use the Functions Framework:
functions-framework --target=entry_point --source=src/main.py --port=8080 --debugThen, you can access
http://localhost:8080in your browser or withcurl.
Deployment
Cloud Function deployment is managed through CI/CD pipelines and gcloud scripts.
Deployment Configuration
The key files for deployment configuration are:
-
deploy/function/runtime-config.yaml.erb: Defines the Cloud Function runtime properties.runtime: python312cpu: 1memory: 512MBtimeout: 3600sregion: "<%= region %>" # Interpolated from environment filesentry-point: entry_pointgen2: ~source: srcmax-instances: 2trigger-http:update-labels: "cmdb-<%= cmdb_it_element %>=<%= cmdb_it_element %>"service-account: "<%= service_account %>" -
deploy/function/environment.yaml.erb: Defines environment variables that will be passed to the function.BRANCH: "<%= branch_name %>"IS_PROD: "<%= is_prod_deploy %>"GIT_COMMIT: "<%= git_commit %>"BUILD_ID: "<%= build_id %>"TEST_ENV: "<%= test_env %>" # Example, interpolated from environment files -
deploy/env/*.yaml: Contain environment-specific variables for each environment (dev, intg, prod). Exampledev.yaml:test_env: "test in dev"region: ${{ REGION }} # These may be CI/CD variablesservice_account: ${{ SERVICE_ACCOUNT }}project_id: ${{ PROJECT_ID }}cmdb_it_element: ${{ IT_ELEMENT }}
CI/CD Pipelines
-
GitLab CI: The
.gitlab-ci.ymlfile uses a predefined pipeline component for deployment:include:- component: $LATAM_PIPELINE_CLOUD_FUNCTION_DEPLOYThis pipeline handles build, test, security, release, deploy, and other steps.
-
Jenkins: A
Jenkinsfileis included in the project root, suggesting the possibility of integration with Jenkins for pipelines.
gcloud Commands (Manual Example)
Although deployment is usually automated, a manual gcloud command to deploy a Gen 2 function might look like (adapted
from
the configuration):
gcloud functions deploy YOUR_FUNCTION_NAME \
--gen2 \
--runtime=python312 \
--region=YOUR_REGION \
--source=./src \
--entry-point=entry_point \
--trigger-http \
--allow-unauthenticated \ # Or configure authentication as needed
--service-account=YOUR_SERVICE_ACCOUNT_EMAIL \
--memory=512MB \
--cpu=1 \
--timeout=3600s \
--max-instances=2 \
--set-env-vars=VAR1=value1,VAR2=value2 \ # Variables from environment.yaml.erb
--update-labels=cmdb-YOUR_CMDB_ELEMENT=YOUR_CMDB_ELEMENT
Variables (YOUR_FUNCTION_NAME, YOUR_REGION, etc.) and configuration files (--env-vars-file) would be handled by
the
CI/CD pipeline from the deploy/env/*.yaml and deploy/function/*.yaml.erb files.
API Endpoints
The example function in src/main.py exposes an HTTP endpoint.
| Method | Endpoint | Description | Request Parameters (JSON or Query) | Request Example (Query) | Success Response Example | Authentication |
|---|---|---|---|---|---|---|
| GET/POST | / (relative to the function URL) | Greets the provided name or "World" if no name is provided. The function name is part of the base URL. | name (string, optional) | curl "FUNCTION_URL?name=User" | Hello User! | --trigger-http (by default allows unauthenticated if specified, or requires IAM) |
Request Example (JSON with curl):
curl -X POST \
FUNCTION_URL \
-H "Content-Type: application/json" \
-d '{"name": "World"}'
Response:
Hello World!
Note: The function base URL (FUNCTION_URL) is provided by Google Cloud after deployment.
Specific Components
This base template does not include direct integrations with components such as Pub/Sub, Storage, Redis, Kafka, etc. However, it is designed to be easily extended.
To integrate with other Google Cloud services or external services:
-
Add Dependencies: Add the necessary client libraries to the
src/requirements.txtfile. For example:- For Google Cloud Storage:
google-cloud-storage - For Google Cloud Pub/Sub:
google-cloud-pubsub
- For Google Cloud Storage:
-
Modify
src/main.py: Implement the logic to interact with the desired service within your function.# Conceptual example to interact with Storagefrom google.cloud import storagedef interact_with_storage():storage_client = storage.Client()bucket = storage_client.bucket("my-bucket-name")blob = bucket.blob("my-file.txt")# ... perform operations with the blob ... -
Environment Configuration: If necessary, add environment variables (bucket names, topics, etc.) in the
deploy/function/environment.yaml.erbanddeploy/env/*.yamlfiles. -
Permissions: Ensure that the service account (
service-accountspecified inruntime-config.yaml.erb) associated with the Cloud Function has the necessary IAM permissions to interact with the other services.
Testing
This template does not include a testing framework configured by default (such as pytest or unittest). It is
recommended to add unit and integration tests according to project needs.
Suggested Steps to Add Tests:
-
Choose a Testing Framework: For example,
pytest.- Add
pytesttosrc/requirements.txt(or to arequirements-dev.txt).
- Add
-
Write Tests: Create a
tests/directory at the project root or insidesrc/and write the test files.# example tests/test_main.py# from src.main import entry_point # Adjust import according to structure# import flask# def test_entry_point_no_name():# req = flask.Request.from_values() # Basic simulation# # Mock get_json and args if needed for a more robust test# # Or use the Flask test client with 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!" -
Run Tests:
python -m pytest -
CI/CD Integration: Add a step in the CI/CD pipeline (
.gitlab-ci.ymlorJenkinsfile) to run tests automatically.
Security Considerations
- Service Account Permissions: The service account specified in
deploy/function/runtime-config.yaml.erb(service_account) should follow the principle of least privilege. Grant only the IAM roles and permissions strictly necessary for the function to operate. - Function Access:
- For HTTP functions, consider whether they should be publicly accessible (
--allow-unauthenticated) or require IAM authentication. - For event-triggered functions (e.g., Pub/Sub, Storage), authentication is handled by the invoking service.
- For HTTP functions, consider whether they should be publicly accessible (
- Secret Handling: Avoid hardcoding secrets (API keys, passwords) in the code. Use environment variables (configured
through
environment.yaml.erband theenv/*.yamlfiles, whose values may come from a secrets manager in the CI/CD pipeline) or Google Secret Manager. - Dependencies: Keep dependencies in
src/requirements.txtup to date and review regularly for known vulnerabilities. - Input Validation: For HTTP functions, validate and sanitize all user inputs (e.g.,
request.args,request.get_json()) to prevent vulnerabilities such as XSS (althoughMarkupSafehelps with output escaping in the example). - Logging and Monitoring: Configure adequate logging for auditing and debugging. Use Cloud Logging and Cloud Monitoring.