Infrastructure
Infrastructure
This page describes the infrastructure the CM Marketplace platform runs on — GCP services, Terraform modules, networking, secrets, CI/CD, and container setup.
1. Cloud Provider and Region
| Property | Value |
|---|---|
| Cloud Provider | Google Cloud Platform (GCP) |
| Production Region | europe-west4 (Eemshaven, Netherlands) |
| Acceptance Region | europe-west4 |
| IaC Tool | Terraform 1.7.4 |
| CI/CD | GitHub Actions |
| Container Registry | Google Artifact Registry (europe-west4-docker.pkg.dev) |
2. Terraform Module Structure
Infrastructure is organized as reusable Terraform modules composed in per-environment configurations:
infra/
modules/
cloudrun/ # Cloud Run service deployment
database/ # Cloud SQL PostgreSQL
pubsub/ # Pub/Sub topics and subscriptions
cloudtasks/ # Cloud Tasks queues
redis/ # Memorystore Redis
gke/ # GKE Autopilot cluster
vpc_network/ # VPC, subnets, NAT, access connectors
secret_manager/ # Secret Manager access
iam/ # Service accounts and IAM roles
alerting/ # Monitoring alert policies
cloudscheduler/ # Cloud Scheduler jobs
workflow/ # Cloud Workflows (data sync templates)
google_apis/ # API enablement
environments/
production/ # Production config (composes all modules)
acceptance/ # Staging/acceptance config
payhub/ # PayHub payment environment
3. GCP Services Used
Compute
| Service | Purpose | Config |
|---|---|---|
| Cloud Run | All API microservices (11 services) | 1 vCPU, 1-2Gi RAM, autoscale 1-10 |
| GKE Autopilot | Long-running background workers | Fully managed, regional |
Data
| Service | Purpose | Config |
|---|---|---|
| Cloud SQL | PostgreSQL 14 database | Regional HA, SSD, auto-resize, PITR |
| Memorystore | Redis cache | 1GB BASIC, direct peering |
| Cloud Storage | File storage (product feeds, exports) | Standard storage |
Messaging
| Service | Purpose | Config |
|---|---|---|
| Cloud Pub/Sub | Async inter-service events | 2 topics, 12 subscriptions, 7-day retention |
| Cloud Tasks | Rate-controlled task dispatch | 4 queues, 1000 concurrent, 500/sec |
| Cloud Scheduler | Cron-based job triggering | HTTP and Pub/Sub targets |
| Cloud Workflows | Multi-step data sync orchestration | 4 workflow templates |
Security and Operations
| Service | Purpose | Config |
|---|---|---|
| Secret Manager | Secrets storage | 9 secret categories, per-service |
| IAM | Service account management | Per-service accounts, least privilege |
| Cloud Monitoring | Alert policies | 5xx error alerting, 60s detection |
| Cloud Logging | Structured logging | Serilog sink, all services |
Networking
| Service | Purpose | Config |
|---|---|---|
| VPC | Private networking | Regional, auto-subnets |
| Cloud NAT | Outbound static IP | Manual IP allocation, shared NAT |
| VPC Access Connector | Cloud Run to VPC bridge | 2-10 instances, 200-1000 Mbps |
4. Networking Architecture
Internet
|
v
Cloud Run (public endpoints)
|
+-- VPC Access Connector (shared-nat) --+
| |
v v
Cloud SQL (private IP) Redis (direct peering)
|
v
Cloud NAT (static IP) --> External APIs (Salesforce, Dynamics, etc.)
VPC Network
# infra/modules/vpc_network/network.tf
resource "google_compute_network" "default" {
auto_create_subnetworks = true
routing_mode = "REGIONAL"
}
Cloud NAT with Static IP
# infra/modules/vpc_network/router.tf
resource "google_compute_router_nat" "shared-nat" {
nat_ip_allocate_option = "MANUAL_ONLY"
nat_ips = [google_compute_address.static-marketplace.id]
min_ports_per_vm = 64
tcp_established_idle_timeout_sec = 1200
tcp_time_wait_timeout_sec = 120
tcp_transitory_idle_timeout_sec = 30
}
All outbound traffic exits through a single static IP, enabling IP allowlisting with external partners (Salesforce, Dynamics, etc.).
VPC Access Connector
# infra/modules/vpc_network/access_connector.tf
resource "google_vpc_access_connector" "shared-nat" {
machine_type = "f1-micro"
max_instances = 10
min_instances = 2
max_throughput = 1000
min_throughput = 200
}
5. Secrets Management
Secrets are stored in Google Secret Manager and organized by service:
# infra/environments/production/main.tf
secret_list = [
"common", # Shared across all services
"product_feed", # Product feed specific
"configuration", # Configuration service specific
"crm", # CRM adapter secrets
"livechat", # Livechat adapter secrets
"cms", # CMS adapter secrets
"comms", # Comms adapter secrets
"integrations", # Integration secrets
"cdp" # CDP adapter secrets
]
Secrets are injected as environment variables at Cloud Run startup via Terraform:
env_vars = merge(
local.run_env.common.redis,
local.run_env.common.gcloud,
nonsensitive(module.secret_manager.run_instance_secrets.crm),
nonsensitive(module.secret_manager.run_instance_secrets.common)
)
6. IAM and Service Accounts
Each service runs under a dedicated service account with least-privilege roles:
# infra/environments/production/main.tf
sa_roles = {
crm = ["roles/cloudsql.client", "roles/cloudtasks.enqueuer",
"roles/pubsub.publisher", "roles/redis.dbConnectionUser",
"roles/cloudscheduler.admin"]
livechat = ["roles/cloudscheduler.admin", "roles/cloudtasks.enqueuer",
"roles/redis.dbConnectionUser"]
configuration = ["roles/cloudsql.client", "roles/cloudtasks.enqueuer",
"roles/pubsub.publisher", "roles/redis.dbConnectionUser",
"roles/cloudtasks.viewer", "roles/cloudtasks.taskDeleter"]
cdp = ["roles/redis.dbConnectionUser", "roles/storage.objectUser",
"roles/cloudscheduler.admin"]
cms = ["roles/cloudscheduler.admin", "roles/cloudtasks.enqueuer",
"roles/redis.dbConnectionUser", "roles/storage.objectUser",
"roles/secretmanager.secretAccessor"]
product-feed = ["roles/cloudtasks.enqueuer", "roles/storage.objectUser",
"roles/redis.dbConnectionUser",
"roles/iam.serviceAccountUser"]
comms = ["roles/cloudtasks.enqueuer",
"roles/redis.dbConnectionUser"]
jsltservice = ["roles/logging.logWriter"]
integrations = ["roles/logging.logWriter"]
frontend = ["roles/logging.logWriter"]
}
7. Monitoring and Alerting
5xx Error Alert
# infra/modules/alerting/main.tf
resource "google_monitoring_alert_policy" "cloudrun_5xx_alert" {
display_name = "Cloud Run - Error Alert"
combiner = "OR"
enabled = true
conditions {
display_name = "Cloud Run service has 5xx errors"
condition_threshold {
filter = "resource.type = \"cloud_run_revision\" AND
metric.type = \"run.googleapis.com/request_count\" AND
metric.labels.response_code_class = \"5xx\""
duration = "60s"
comparison = "COMPARISON_GT"
threshold_value = 2
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_DELTA"
group_by_fields = ["resource.labels.service_name",
"metric.labels.response_code"]
}
}
}
alert_strategy {
auto_close = "1800s"
}
}
Triggers when any service returns more than 2 5xx errors in 60 seconds. Auto-closes after 30 minutes.
8. Container Setup
All services use a standard Dockerfile running as non-root:
# Example: src/app-crm/.../Dockerfile
FROM proget2.sharedservices.cmgroep.local/dockerfeed/trusted/dotnet/aspnet:10.0
RUN groupadd --gid 1001 appuser \
&& useradd --uid 1001 --gid 1001 -m appuser
USER appuser
WORKDIR /app
COPY ${BUILD_DIR} ./
EXPOSE 80
EXPOSE 443
ENTRYPOINT ["dotnet", "CM.Marketplace.Adapters.CRM.Web.API.dll"]
9. CI/CD Pipeline
Pipeline Flow
Developer pushes to main
|
v
GitHub Actions: Build + Test + Coverage + SonarQube
|
v
Docker Build --> Push to Artifact Registry (europe-west4)
|
v
Auto-deploy to Acceptance (Terraform apply)
|
v
Database migration (HTTP API call)
|
v
Manual trigger / repository_dispatch
|
v
Deploy to Production (Terraform plan + apply)
Production Deployment
# .github/workflows/deploy-prod.yaml
name: Deploy to Production
on:
workflow_dispatch:
repository_dispatch:
types: [artifact_committed_prod]
jobs:
deploy:
runs-on: engage-hosted
environment: Production
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with: { terraform_version: 1.7.4 }
- uses: google-github-actions/auth@v2
with: { credentials_json: "${{ secrets.GCP_CREDENTIALS }}" }
- run: terraform init
- run: terraform plan -var-file="production.tfvars.json" -out=plan
- run: terraform apply --auto-approve "plan"
Enabled Google APIs
# infra/modules/google_apis/locals.tf
apis = {
compute = "compute"
cloudrun = "run"
serverlessvpc = "vpcaccess"
secretmanager = "secretmanager"
cloudfunction = "cloudfunctions"
}
10. Environment Comparison
| Aspect | Production | Acceptance |
|---|---|---|
| DB Tier | db-custom-1-3840 | db-f1-micro |
| DB Availability | REGIONAL | ZONAL |
| Cloud Run Memory | 1-2Gi | 512Mi |
| Cloud Run Max Scale | 10 | 1-5 |
| Cloud Run Min Scale | 1-2 | 0-1 |
| Deploy Trigger | Manual / repository_dispatch | Auto on main push |
| Environment Protection | Required approval | None |
Last updated: May 2026