Automated GCP cost control through policy enforcement.
3.4K
Automated GCP cost control through policy enforcement. Event-driven Cloud Run service that automatically enforces organization policies when budget thresholds are exceeded.
# Pull the latest image
docker pull syalioune/gcp-finops-sentinel:latest
# Run with basic configuration
docker run -p 8080:8080 \
-e ORGANIZATION_ID=123456789012 \
-e RULES_CONFIG='{"rules":[{"name":"budget_alert","conditions":{"threshold_percent":{"operator":">=","value":100}},"actions":[{"type":"log_only","message":"Budget exceeded"}]}]}' \
-e DRY_RUN=true \
syalioune/gcp-finops-sentinel:latest
| Tag | Description | Use Case |
|---|---|---|
latest | Latest stable release | Production |
1.0.0 | Specific version | Version pinning |
develop | Development build | Testing |
| Variable | Required | Description |
|---|---|---|
ORGANIZATION_ID | ✅ Yes | GCP Organization ID |
RULES_CONFIG | ⚠️ Yes* | Rules as JSON/YAML string |
RULES_CONFIG_PATH | ⚠️ Yes* | Path to rules file (default: /workspace/rules.json) |
DRY_RUN | ❌ No | Set to true for testing without enforcement |
ACTION_EVENT_TOPIC | ❌ No | Pub/Sub topic for action events |
LOG_LEVEL | ❌ No | DEBUG, INFO, WARNING, ERROR (default: INFO) |
* Either RULES_CONFIG or RULES_CONFIG_PATH must be provided.
| Variable | Default | Description |
|---|---|---|
SMTP_HOST | - | SMTP server hostname |
SMTP_PORT | 587 | SMTP server port |
SMTP_USER | - | SMTP username |
SMTP_PASSWORD | - | SMTP password |
SMTP_USE_TLS | true | Enable STARTTLS |
SMTP_FROM_EMAIL | $SMTP_USER | Sender email address |
Rules define when and what actions to take based on budget thresholds. Both JSON and YAML formats are supported.
| Operator | Description | Example |
|---|---|---|
>= | Greater than or equal | Trigger at 100% or higher |
> | Greater than | Trigger above 100% |
== | Equals | Trigger exactly at 100% |
< | Less than | Trigger below 100% |
<= | Less than or equal | Trigger at 100% or lower |
min | Minimum (inclusive) | Range: 80-89.99% (use with max) |
max | Maximum (inclusive) | Range: 80-89.99% (use with min) |
{
"rules": [
{
"name": "critical_budget_breach",
"description": "Restrict compute when budget exceeds 100%",
"conditions": {
"threshold_percent": {
"operator": ">=",
"value": 100
}
},
"actions": [
{
"type": "restrict_services",
"target_projects": ["prod-web-1", "prod-api-1"],
"services": ["compute.googleapis.com"]
},
{
"type": "send_mail",
"to_emails": ["[email protected]"],
"template": "budget_alert",
"custom_message": "Critical budget breach detected!"
}
]
}
]
}
rules:
- name: tiered_response
description: Graduated response based on budget thresholds
conditions:
# Range-based threshold: 80-89.99%
threshold_percent:
- operator: min
value: 80
- operator: max
value: 89.99
billing_account_filter: "012345-6789AB-CDEF01"
actions:
# Target projects by labels (dynamic discovery)
- type: restrict_services
target_labels:
env: prod
cost-center: engineering
services:
- compute.googleapis.com
- container.googleapis.com
# Apply constraint at folder level
- type: apply_constraint
target_folders:
- "123456789012"
constraint: compute.vmExternalIpAccess
enforce: true
| Action | Description | Parameters |
|---|---|---|
restrict_services | Deny specific GCP services | services, targeting |
apply_constraint | Apply org policy constraint | constraint, enforce, targeting |
send_mail | Send HTML email notification | to_emails, template |
log_only | Log without taking action | message, targeting |
All actions (except send_mail) require at least one:
target_projects: List of project IDstarget_folders: List of folder IDstarget_organization: Organization IDtarget_labels: Label key-value pairs (dynamic discovery)Clone the repository and use the provided Docker Compose environment with Pub/Sub emulator and MailHog:
# Clone repository
git clone https://github.com/syalioune/gcp-finops-sentinel.git
cd gcp-finops-sentinel
# Start complete local environment
docker compose up -d
# Publish test budget alert (120% threshold)
export PUBSUB_EMULATOR_HOST=localhost:8681
python scripts/publish-budget-alert-event.py --cost-amount 1200 --budget-amount 1000
# View logs
docker compose logs -f budget-function
# View email notifications at http://localhost:8025
Services:
# Start Pub/Sub emulator
docker run -d --name pubsub-emulator \
-p 8681:8681 \
gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators \
gcloud beta emulators pubsub start --host-port=0.0.0.0:8681
# Start MailHog for email testing
docker run -d --name mailhog \
-p 8025:8025 -p 1025:1025 \
mailhog/mailhog
# Run FinOps Sentinel
docker run -d --name finops-sentinel \
-p 8080:8080 \
-e ORGANIZATION_ID=123456789012 \
-e DRY_RUN=true \
-e PUBSUB_EMULATOR_HOST=pubsub-emulator:8681 \
-e SMTP_HOST=mailhog \
-e SMTP_PORT=1025 \
--link pubsub-emulator \
--link mailhog \
syalioune/gcp-finops-sentinel:latest
# View emails at http://localhost:8025
# Deploy with gcloud
gcloud run deploy gcp-finops-sentinel \
--image=syalioune/gcp-finops-sentinel:latest \
--platform=managed \
--region=us-central1 \
--set-env-vars="ORGANIZATION_ID=123456789012" \
--set-secrets="RULES_CONFIG=finops-rules:latest" \
--no-allow-unauthenticated
# Create Eventarc trigger for Pub/Sub
gcloud eventarc triggers create budget-alerts-trigger \
--destination-run-service=gcp-finops-sentinel \
--destination-run-region=us-central1 \
--event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
--transport-topic=projects/YOUR_PROJECT/topics/budget-alerts
version: '3.8'
services:
finops-sentinel:
image: syalioune/gcp-finops-sentinel:latest
ports:
- "8080:8080"
environment:
- ORGANIZATION_ID=123456789012
- DRY_RUN=true
- LOG_LEVEL=DEBUG
volumes:
- ./rules.json:/workspace/rules.json:ro
resource "google_cloud_run_v2_service" "finops_sentinel" {
name = "gcp-finops-sentinel"
location = "us-central1"
template {
service_account = google_service_account.finops_sentinel.email
containers {
image = "syalioune/gcp-finops-sentinel:latest"
env {
name = "ORGANIZATION_ID"
value = var.organization_id
}
env {
name = "RULES_CONFIG"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.rules.secret_id
version = "latest"
}
}
}
resources {
limits = {
cpu = "1"
memory = "512Mi"
}
}
}
}
}
The service account running the container needs:
| Role | Scope | Purpose |
|---|---|---|
roles/browser | Organization | Project discovery by labels |
roles/orgpolicy.policyAdmin | Organization | Policy enforcement |
roles/pubsub.publisher | Topic | Action event publishing (optional) |
# The container exposes port 8080
curl http://localhost:8080/
Container outputs structured JSON logs:
{
"severity": "INFO",
"message": "Processing budget alert",
"budget_id": "budget-123",
"threshold_percent": 120.5,
"matched_rules": 2
}
Apache License 2.0 - See LICENSE
Built for cloud cost optimization | Python 3.13 | Cloud Run | OpenTofu
Content type
Image
Digest
sha256:876cdae52…
Size
64.9 MB
Last updated
10 months ago
docker pull syalioune/gcp-finops-sentinel