@caelo-cms/provisioning 0.11.5 → 0.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-db.js +575 -105
- package/dist/compose.d.ts +7 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +20 -0
- package/dist/compose.js.map +1 -1
- package/dist/pool-budget-stack.d.ts +78 -0
- package/dist/pool-budget-stack.d.ts.map +1 -0
- package/dist/pool-budget-stack.js +78 -0
- package/dist/pool-budget-stack.js.map +1 -0
- package/package.json +2 -2
- package/src/compose.ts +22 -0
- package/src/gateway-env-isolation.test.ts +2 -0
- package/src/pool-budget-stack.ts +116 -0
- package/src/pool-budget-stacks.test.ts +100 -0
- package/src/self-hosted-compose.test.ts +23 -0
- package/src/stack-contract-parity.test.ts +6 -2
- package/stacks/aws/Pulumi.yaml +12 -0
- package/stacks/aws/README.md +1 -0
- package/stacks/aws/index.ts +36 -0
- package/stacks/azure/Pulumi.yaml +4 -0
- package/stacks/azure/README.md +1 -0
- package/stacks/azure/index.ts +35 -2
- package/stacks/gcp/Pulumi.yaml +16 -8
- package/stacks/gcp/README.md +7 -3
- package/stacks/gcp/index.ts +45 -20
- package/stacks/gcp-firebase/Pulumi.yaml +5 -1
- package/stacks/gcp-firebase/index.ts +27 -3
- package/stacks/self-hosted/README.md +4 -0
package/stacks/aws/Pulumi.yaml
CHANGED
|
@@ -28,6 +28,18 @@ config:
|
|
|
28
28
|
type: string
|
|
29
29
|
default: db.t4g.small
|
|
30
30
|
description: RDS instance class. db.t4g.small is the cheapest Multi-AZ-capable Graviton class.
|
|
31
|
+
caelo-aws:maxConnections:
|
|
32
|
+
type: string
|
|
33
|
+
default: "100"
|
|
34
|
+
description: RDS max_connections (parameter group); every service's pool size is computed from it (docs/dev/connection-budget.md).
|
|
35
|
+
caelo-aws:adminMaxInstances:
|
|
36
|
+
type: string
|
|
37
|
+
default: "5"
|
|
38
|
+
description: Most admin tasks at once; part of the connection budget.
|
|
39
|
+
caelo-aws:gatewayMaxInstances:
|
|
40
|
+
type: string
|
|
41
|
+
default: "10"
|
|
42
|
+
description: Most gateway tasks at once; part of the connection budget (2 connections each).
|
|
31
43
|
caelo-aws:fargateCpu:
|
|
32
44
|
type: string
|
|
33
45
|
default: "512"
|
package/stacks/aws/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Pulumi stack provisioning Caelo on AWS managed services. Implements the shared `
|
|
|
7
7
|
| Concern | AWS resource |
|
|
8
8
|
|---|---|
|
|
9
9
|
| Managed Postgres | RDS Postgres 16 Multi-AZ + automated backups + encryption (`db.t4g.small` default) |
|
|
10
|
+
| Connection budget | `max_connections` pinned by a parameter group (`maxConnections`, default 100); the pool sizes for `adminMaxInstances` 5 / `gatewayMaxInstances` 10 are exported as `poolBudgetEnvOut` for the task definitions (#749): cms_admin 7, admin cms_public 4, gateway 2 per task (docs/dev/connection-budget.md) |
|
|
10
11
|
| Blob storage | Two S3 buckets (`caelo-<env>-media` + `caelo-<env>-static`) |
|
|
11
12
|
| CDN | CloudFront distribution with two origins (S3 static + S3 media); static files are served as files, no edge compute on the request path |
|
|
12
13
|
| Container runtime | ECS Fargate cluster + four services (admin / gateway / orchestrator / runner) |
|
package/stacks/aws/index.ts
CHANGED
|
@@ -39,6 +39,11 @@ import {
|
|
|
39
39
|
secretsReadPolicy,
|
|
40
40
|
} from "../../dist/aws-task-roles.js";
|
|
41
41
|
import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
|
|
42
|
+
import {
|
|
43
|
+
DEFAULT_GATEWAY_MAX_INSTANCES,
|
|
44
|
+
DEFAULT_MAX_CONNECTIONS,
|
|
45
|
+
stackPoolBudget,
|
|
46
|
+
} from "../../dist/pool-budget-stack.js";
|
|
42
47
|
import { ADMIN_MEMORY_DEFAULT, memoryQuantityMiB } from "../../dist/stack-contract.js";
|
|
43
48
|
import {
|
|
44
49
|
CLOUDFRONT_SVG_BEHAVIORS,
|
|
@@ -68,6 +73,25 @@ const _fargateMemoryMb = cfg.get("fargateMemoryMb") ?? "1024";
|
|
|
68
73
|
// ignored, and the admin task definition sizes itself from it.
|
|
69
74
|
const adminMemory = cfg.get("adminMemory") ?? ADMIN_MEMORY_DEFAULT;
|
|
70
75
|
const _adminMemoryMiB = memoryQuantityMiB(adminMemory);
|
|
76
|
+
// #1254 — the connection budget. RDS derives max_connections from instance
|
|
77
|
+
// memory; the parameter group below pins it, so the pool sizes computed
|
|
78
|
+
// here hold on every instance class. The task definitions (#749) set
|
|
79
|
+
// `poolBudgetEnvOut` on the admin and gateway tasks and scale to the same
|
|
80
|
+
// instance counts.
|
|
81
|
+
const maxConnections = Number.parseInt(
|
|
82
|
+
cfg.get("maxConnections") ?? String(DEFAULT_MAX_CONNECTIONS.aws),
|
|
83
|
+
10,
|
|
84
|
+
);
|
|
85
|
+
const adminMaxInstances = Number.parseInt(cfg.get("adminMaxInstances") ?? "5", 10);
|
|
86
|
+
const gatewayMaxInstances = Number.parseInt(
|
|
87
|
+
cfg.get("gatewayMaxInstances") ?? String(DEFAULT_GATEWAY_MAX_INSTANCES),
|
|
88
|
+
10,
|
|
89
|
+
);
|
|
90
|
+
const poolBudget = stackPoolBudget("aws", {
|
|
91
|
+
maxConnections,
|
|
92
|
+
adminMaxInstances,
|
|
93
|
+
gatewayMaxInstances,
|
|
94
|
+
});
|
|
71
95
|
if (_adminMemoryMiB === null) {
|
|
72
96
|
throw new Error(`caelo-aws:adminMemory "${adminMemory}" is not a memory quantity (e.g. 2Gi)`);
|
|
73
97
|
}
|
|
@@ -141,10 +165,20 @@ const dbSecurityGroup = new aws.ec2.SecurityGroup(`${namePrefix}-db-sg`, {
|
|
|
141
165
|
egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }],
|
|
142
166
|
});
|
|
143
167
|
|
|
168
|
+
// #1254 — max_connections is the input of the pool budget, so it is set,
|
|
169
|
+
// never left to the instance class's memory-derived default.
|
|
170
|
+
const dbParameters = new aws.rds.ParameterGroup(`${namePrefix}-pg-params`, {
|
|
171
|
+
family: "postgres16",
|
|
172
|
+
parameters: [
|
|
173
|
+
{ name: "max_connections", value: String(maxConnections), applyMethod: "pending-reboot" },
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
|
|
144
177
|
const db = new aws.rds.Instance(`${namePrefix}-pg`, {
|
|
145
178
|
engine: "postgres",
|
|
146
179
|
engineVersion: "16.4",
|
|
147
180
|
instanceClass: rdsInstanceClass,
|
|
181
|
+
parameterGroupName: dbParameters.name,
|
|
148
182
|
allocatedStorage: 20,
|
|
149
183
|
storageEncrypted: true,
|
|
150
184
|
multiAz: env === "production",
|
|
@@ -529,6 +563,8 @@ export const rdsAddressOut = db.address;
|
|
|
529
563
|
// The task roles the admin-side and gateway task definitions run as.
|
|
530
564
|
export const adminTaskRoleArnOut = taskRoles.admin.arn;
|
|
531
565
|
export const gatewayTaskRoleArnOut = taskRoles.gateway.arn;
|
|
566
|
+
// #1254 — the pool env the admin and gateway task definitions set.
|
|
567
|
+
export const poolBudgetEnvOut = poolBudget.env;
|
|
532
568
|
|
|
533
569
|
// Note: the bootstrap token (and other secrets like postgres-password,
|
|
534
570
|
// CSRF/cookie secrets, Anthropic key) are stored in Pulumi's encrypted
|
package/stacks/azure/Pulumi.yaml
CHANGED
|
@@ -38,3 +38,7 @@ config:
|
|
|
38
38
|
type: string
|
|
39
39
|
default: Standard_B2s
|
|
40
40
|
description: Postgres flexible-server SKU. B2s = burstable; bump to GP_Standard_D2s_v3 for production.
|
|
41
|
+
caelo-azure:maxConnections:
|
|
42
|
+
type: string
|
|
43
|
+
default: "429"
|
|
44
|
+
description: Postgres max_connections (server parameter; must not exceed the SKU's limit, 429 on B2s). Every app's pool size is computed from it (docs/dev/connection-budget.md).
|
package/stacks/azure/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Pulumi stack provisioning Caelo on Azure managed services. Implements the shared
|
|
|
7
7
|
| Concern | Azure resource |
|
|
8
8
|
|---|---|
|
|
9
9
|
| Managed Postgres | Azure DB for PostgreSQL flexible server (zone-redundant in production) + automated backups (geo-redundant in production) |
|
|
10
|
+
| Connection budget | `max_connections` pinned as a server parameter (`maxConnections`, default 429, the Standard_B2s limit); the admin, orchestrator and runner apps (10 replicas each) get a cms_admin pool of 7 and a cms_public pool of 4, the gateway a pool of 2 (docs/dev/connection-budget.md) |
|
|
10
11
|
| Blob storage | Storage Account with a private `media` container and static-website hosting (`$web`) |
|
|
11
12
|
| CDN / Edge | Azure Front Door Standard: the primary domain serves the static website as files, with a Front Door managed certificate; no edge compute on the request path |
|
|
12
13
|
| Container runtime | Four Container Apps (admin / gateway / orchestrator / runner) in one managed environment |
|
package/stacks/azure/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ import * as pulumi from "@pulumi/pulumi";
|
|
|
39
39
|
import type { CloudAdapterOutputs, DnsRecord } from "../../dist/adapter.js";
|
|
40
40
|
import { azureFrontDoorDnsRecords } from "../../dist/azure-front-door.js";
|
|
41
41
|
import { generateBootstrapToken } from "../../dist/bootstrap-token.js";
|
|
42
|
+
import { DEFAULT_MAX_CONNECTIONS, stackPoolBudget } from "../../dist/pool-budget-stack.js";
|
|
42
43
|
import {
|
|
43
44
|
ADMIN_MEMORY_DEFAULT,
|
|
44
45
|
azureContainerAppResources,
|
|
@@ -67,6 +68,22 @@ if (legacyLocation !== undefined) {
|
|
|
67
68
|
}
|
|
68
69
|
const location = cfg.require("region");
|
|
69
70
|
const flexibleServerSku = cfg.get("flexibleServerSku") ?? "Standard_B2s";
|
|
71
|
+
// #1254 — the server's max_connections, set explicitly below (the default
|
|
72
|
+
// Standard_B2s allows up to 429); every app's pool size is computed from it.
|
|
73
|
+
const maxConnections = Number.parseInt(
|
|
74
|
+
cfg.get("maxConnections") ?? String(DEFAULT_MAX_CONNECTIONS.azure),
|
|
75
|
+
10,
|
|
76
|
+
);
|
|
77
|
+
/** Replicas each Container App may scale to (see containerApp). */
|
|
78
|
+
const MAX_REPLICAS = 10;
|
|
79
|
+
// The admin, orchestrator and runner apps all open the admin's two pools,
|
|
80
|
+
// so the budget counts their replicas together as admin instances.
|
|
81
|
+
const ADMIN_SIDE_APPS = 3;
|
|
82
|
+
const poolBudget = stackPoolBudget("azure", {
|
|
83
|
+
maxConnections,
|
|
84
|
+
adminMaxInstances: ADMIN_SIDE_APPS * MAX_REPLICAS,
|
|
85
|
+
gatewayMaxInstances: MAX_REPLICAS,
|
|
86
|
+
});
|
|
70
87
|
// #553 — the admin runs the Lighthouse quality audit; same knob + default
|
|
71
88
|
// as every adapter (stack-contract.ts). Container Apps only accepts fixed
|
|
72
89
|
// CPU/memory pairs (2 GiB per vCPU), so the admin's CPU follows its memory
|
|
@@ -149,6 +166,18 @@ const pgServer = new azure.dbforpostgresql.Server(`${namePrefix}-pg`, {
|
|
|
149
166
|
network: { publicNetworkAccess: "Disabled" },
|
|
150
167
|
});
|
|
151
168
|
|
|
169
|
+
// #1254 — pinned so the pool budget never depends on the SKU's default.
|
|
170
|
+
const _pgMaxConnections = new azure.dbforpostgresql.Configuration(
|
|
171
|
+
`${namePrefix}-pg-max-connections`,
|
|
172
|
+
{
|
|
173
|
+
resourceGroupName: rg.name,
|
|
174
|
+
serverName: pgServer.name,
|
|
175
|
+
configurationName: "max_connections",
|
|
176
|
+
value: String(maxConnections),
|
|
177
|
+
source: "user-override",
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
152
181
|
const _pgAdmin = new azure.dbforpostgresql.Database(`${namePrefix}-cms-admin-db`, {
|
|
153
182
|
resourceGroupName: rg.name,
|
|
154
183
|
serverName: pgServer.name,
|
|
@@ -425,7 +454,7 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
|
|
|
425
454
|
},
|
|
426
455
|
},
|
|
427
456
|
template: {
|
|
428
|
-
scale: { minReplicas: env === "production" ? 1 : 0, maxReplicas:
|
|
457
|
+
scale: { minReplicas: env === "production" ? 1 : 0, maxReplicas: MAX_REPLICAS },
|
|
429
458
|
containers: [
|
|
430
459
|
{
|
|
431
460
|
name: args.serviceName,
|
|
@@ -453,6 +482,7 @@ function containerApp(args: ContainerAppArgs): azure.app.ContainerApp {
|
|
|
453
482
|
const adminDatabaseEnv = [
|
|
454
483
|
{ name: "ADMIN_DATABASE_URL", value: adminDatabaseUrl },
|
|
455
484
|
{ name: "PUBLIC_ADMIN_DATABASE_URL", value: publicAdminDatabaseUrl },
|
|
485
|
+
...poolBudget.env.admin,
|
|
456
486
|
];
|
|
457
487
|
const adminApp = containerApp({
|
|
458
488
|
serviceName: "admin",
|
|
@@ -464,7 +494,10 @@ const adminApp = containerApp({
|
|
|
464
494
|
});
|
|
465
495
|
const _gatewayApp = containerApp({
|
|
466
496
|
serviceName: "gateway",
|
|
467
|
-
databaseEnv: [
|
|
497
|
+
databaseEnv: [
|
|
498
|
+
{ name: "PUBLIC_DATABASE_URL", value: publicDatabaseUrl },
|
|
499
|
+
...poolBudget.env.gateway,
|
|
500
|
+
],
|
|
468
501
|
// Which X-Forwarded-For entry is the visitor (api-gateway client-ip.ts).
|
|
469
502
|
// #806 — no remote-MCP env: remote MCP rides Google IAP (gcp stacks
|
|
470
503
|
// only); unset, the gateway answers /api/mcp/* with 503
|
package/stacks/gcp/Pulumi.yaml
CHANGED
|
@@ -57,22 +57,30 @@ config:
|
|
|
57
57
|
type: string
|
|
58
58
|
default: "5"
|
|
59
59
|
description: |
|
|
60
|
-
Admin Cloud Run max instances.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
sizing for heavy editorial traffic
|
|
60
|
+
Admin Cloud Run max instances. Each instance gets an equal share of
|
|
61
|
+
the connection budget (docs/dev/connection-budget.md): at the
|
|
62
|
+
defaults, a cms_admin pool of 7. Bump alongside maxConnections +
|
|
63
|
+
cloudSqlTier when sizing for heavy editorial traffic; a scale-out the
|
|
64
|
+
server cannot afford fails at preview with the max_connections it needs.
|
|
64
65
|
caelo-gcp:maxConnections:
|
|
65
66
|
type: string
|
|
66
67
|
default: "100"
|
|
67
68
|
description: |
|
|
68
|
-
Postgres max_connections database flag
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
Postgres max_connections database flag; every service's pool size is
|
|
70
|
+
computed from it (#1254). Cloud SQL's db-f1-micro defaults to 25 —
|
|
71
|
+
too low for any sustained admin load. Tune up alongside cloudSqlTier
|
|
72
|
+
when scaling out.
|
|
72
73
|
caelo-gcp:gatewayMinInstances:
|
|
73
74
|
type: string
|
|
74
75
|
default: "0"
|
|
75
76
|
description: Gateway Cloud Run min instances. 0 = scale to zero. 1 = no cold start (+~$15/mo).
|
|
77
|
+
caelo-gcp:gatewayMaxInstances:
|
|
78
|
+
type: string
|
|
79
|
+
default: "10"
|
|
80
|
+
description: |
|
|
81
|
+
Gateway Cloud Run max instances. Part of the connection budget (2
|
|
82
|
+
connections each); 10 serve ~800 concurrent visitor requests. Raise
|
|
83
|
+
together with maxConnections.
|
|
76
84
|
caelo-gcp:iapAllowlist:
|
|
77
85
|
type: string
|
|
78
86
|
default: ""
|
package/stacks/gcp/README.md
CHANGED
|
@@ -26,11 +26,15 @@ pulumi config set caelo-gcp:project my-gcp-project
|
|
|
26
26
|
# Required (#607): no default, fixed after install.
|
|
27
27
|
pulumi config set caelo-gcp:region europe-west1
|
|
28
28
|
|
|
29
|
-
# Optional: scale up beyond the safe-by-default ceiling.
|
|
30
|
-
# size
|
|
31
|
-
#
|
|
29
|
+
# Optional: scale up beyond the safe-by-default ceiling. Every service's
|
|
30
|
+
# pool size is computed from max_connections and the instance counts
|
|
31
|
+
# (docs/dev/connection-budget.md): at the defaults (max_connections 100,
|
|
32
|
+
# 5 admin, 10 gateway instances) each admin instance gets a cms_admin
|
|
33
|
+
# pool of 7. A scale-out the server cannot afford fails at preview with
|
|
34
|
+
# the max_connections it needs.
|
|
32
35
|
# pulumi config set caelo-gcp:adminMaxInstances 10
|
|
33
36
|
# pulumi config set caelo-gcp:maxConnections 200
|
|
37
|
+
# pulumi config set caelo-gcp:gatewayMaxInstances 20
|
|
34
38
|
|
|
35
39
|
# Build + push images. Operator does this once + on every release.
|
|
36
40
|
gcloud auth configure-docker us-central1-docker.pkg.dev
|
package/stacks/gcp/index.ts
CHANGED
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
staticPublisherServiceAccountId,
|
|
32
32
|
} from "../../dist/gcp-names.js";
|
|
33
33
|
import { createSiteUrlMap } from "../../dist/gcp-url-map.js";
|
|
34
|
+
import {
|
|
35
|
+
DEFAULT_GATEWAY_MAX_INSTANCES,
|
|
36
|
+
DEFAULT_MAX_CONNECTIONS,
|
|
37
|
+
stackPoolBudget,
|
|
38
|
+
} from "../../dist/pool-budget-stack.js";
|
|
34
39
|
import { mcpArmorRuleArgs } from "../../dist/remote-mcp-edge.js";
|
|
35
40
|
import {
|
|
36
41
|
ADMIN_MEMORY_DEFAULT,
|
|
@@ -96,13 +101,14 @@ const adminMinInstances = Number.parseInt(cfg.get("adminMinInstances") ?? "0", 1
|
|
|
96
101
|
// #553 — the admin also runs the Lighthouse quality audit; same knob and
|
|
97
102
|
// default on every adapter (stack-contract.ts ADMIN_MEMORY_DEFAULT).
|
|
98
103
|
const adminMemory = cfg.get("adminMemory") ?? ADMIN_MEMORY_DEFAULT;
|
|
99
|
-
// adminMaxInstances default is intentionally low.
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
104
|
+
// adminMaxInstances default is intentionally low. Every admin instance
|
|
105
|
+
// holds two pools (cms_admin + cms_public) plus a LISTEN connection, and
|
|
106
|
+
// all of them share the server's max_connections with the gateway, the
|
|
107
|
+
// generator and the jobs. #1254 sizes the pools from that budget
|
|
108
|
+
// (src/pool-budget-stack.ts) instead of Bun's default 10 per pool; a
|
|
109
|
+
// scale-out the server cannot afford fails at preview, naming the
|
|
110
|
+
// connections it needs. Operators on larger SQL tiers bump maxConnections
|
|
111
|
+
// AND adminMaxInstances together.
|
|
106
112
|
//
|
|
107
113
|
// Why 5, not 10 (Cloud Run's default)? An incident on production showed
|
|
108
114
|
// admin scaling to 10 under bursty editor traffic, demanding 200 slots
|
|
@@ -111,11 +117,23 @@ const adminMemory = cfg.get("adminMemory") ?? ADMIN_MEMORY_DEFAULT;
|
|
|
111
117
|
// installs don't trip the same wall.
|
|
112
118
|
const adminMaxInstances = Number.parseInt(cfg.get("adminMaxInstances") ?? "5", 10);
|
|
113
119
|
const gatewayMinInstances = Number.parseInt(cfg.get("gatewayMinInstances") ?? "0", 10);
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
// #1254 — the gateway's scale-out is part of the connection budget: each
|
|
121
|
+
// instance holds a cms_public pool.
|
|
122
|
+
const gatewayMaxInstances = Number.parseInt(
|
|
123
|
+
cfg.get("gatewayMaxInstances") ?? String(DEFAULT_GATEWAY_MAX_INSTANCES),
|
|
124
|
+
10,
|
|
125
|
+
);
|
|
126
|
+
// Postgres connection slot ceiling, set as a database flag below (Cloud
|
|
127
|
+
// SQL's db-f1-micro defaults to 25). The pool budget is computed from it.
|
|
128
|
+
const maxConnections = Number.parseInt(
|
|
129
|
+
cfg.get("maxConnections") ?? String(DEFAULT_MAX_CONNECTIONS.gcp),
|
|
130
|
+
10,
|
|
131
|
+
);
|
|
132
|
+
const poolBudget = stackPoolBudget("gcp", {
|
|
133
|
+
maxConnections,
|
|
134
|
+
adminMaxInstances,
|
|
135
|
+
gatewayMaxInstances,
|
|
136
|
+
});
|
|
119
137
|
const wafAdaptiveProtection = cfg.getBoolean("wafAdaptiveProtection") ?? false;
|
|
120
138
|
const backupRetentionDays = Number.parseInt(cfg.get("backupRetentionDays") ?? "7", 10);
|
|
121
139
|
// IAP allowlist: comma-list of "user:<email>" or "group:<group@domain>".
|
|
@@ -292,11 +310,9 @@ const sqlInstance = new gcp.sql.DatabaseInstance(
|
|
|
292
310
|
diskSize: 20,
|
|
293
311
|
diskType: "PD_SSD",
|
|
294
312
|
diskAutoresize: true,
|
|
295
|
-
// db-f1-micro defaults max_connections=25
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
// instance level so the cap matches the Cloud Run scale-out
|
|
299
|
-
// headroom defined by adminMaxInstances + gatewayMaxInstances.
|
|
313
|
+
// db-f1-micro defaults max_connections=25. Set explicitly: the
|
|
314
|
+
// services' pool sizes are computed from this exact number
|
|
315
|
+
// (poolBudget above), so the server must have it.
|
|
300
316
|
databaseFlags: [
|
|
301
317
|
{ name: "max_connections", value: maxConnections.toString() },
|
|
302
318
|
// The operator-access sync job logs in as its service account (Cloud
|
|
@@ -787,13 +803,18 @@ const adminSvc = cloudRunService({
|
|
|
787
803
|
// in Cloud Run logs. 3600s is the platform max; no cost penalty for
|
|
788
804
|
// unused timeout (Cloud Run bills running time, not allocated time).
|
|
789
805
|
timeout: "3600s",
|
|
790
|
-
contractEnv: [
|
|
806
|
+
contractEnv: [
|
|
807
|
+
...adminEnvContract(envContractInputs),
|
|
808
|
+
...remoteMcpEnv.admin,
|
|
809
|
+
// #1254 — pool sizes from the connection budget.
|
|
810
|
+
...poolBudget.env.admin,
|
|
811
|
+
],
|
|
791
812
|
mediaVolume: adminMediaVolumeTemplate(adminMediaVolume(project, env)),
|
|
792
813
|
});
|
|
793
814
|
const gatewaySvc = cloudRunService({
|
|
794
815
|
serviceName: "gateway",
|
|
795
816
|
minInstances: gatewayMinInstances,
|
|
796
|
-
maxInstances:
|
|
817
|
+
maxInstances: gatewayMaxInstances,
|
|
797
818
|
memory: "512Mi",
|
|
798
819
|
// #806 — remote MCP: chat turns stream through the gateway for minutes,
|
|
799
820
|
// so it gets the admin's 3600 s instead of Cloud Run's 300 s default
|
|
@@ -802,7 +823,11 @@ const gatewaySvc = cloudRunService({
|
|
|
802
823
|
timeout: `${GATEWAY_TIMEOUT_SECONDS}s`,
|
|
803
824
|
// public_role on cms_public only: no cms_admin login of any role
|
|
804
825
|
// (#613, #635, CLAUDE.md §2).
|
|
805
|
-
contractEnv: [
|
|
826
|
+
contractEnv: [
|
|
827
|
+
...gatewayEnvContract(envContractInputs),
|
|
828
|
+
...remoteMcpEnv.gateway,
|
|
829
|
+
...poolBudget.env.gateway,
|
|
830
|
+
],
|
|
806
831
|
});
|
|
807
832
|
|
|
808
833
|
// =========================================================================
|
|
@@ -63,11 +63,15 @@ config:
|
|
|
63
63
|
caelo-gcp-firebase:maxConnections:
|
|
64
64
|
type: string
|
|
65
65
|
default: "100"
|
|
66
|
-
description: Postgres max_connections.
|
|
66
|
+
description: Postgres max_connections; every service's pool size is computed from it (docs/dev/connection-budget.md).
|
|
67
67
|
caelo-gcp-firebase:gatewayMinInstances:
|
|
68
68
|
type: string
|
|
69
69
|
default: "0"
|
|
70
70
|
description: Gateway Cloud Run min instances.
|
|
71
|
+
caelo-gcp-firebase:gatewayMaxInstances:
|
|
72
|
+
type: string
|
|
73
|
+
default: "10"
|
|
74
|
+
description: Gateway Cloud Run max instances; part of the connection budget (2 connections each).
|
|
71
75
|
caelo-gcp-firebase:iapAllowlist:
|
|
72
76
|
type: string
|
|
73
77
|
default: ""
|
|
@@ -37,6 +37,11 @@ import {
|
|
|
37
37
|
MCP_IAP_SERVICE_ACCOUNT_ID,
|
|
38
38
|
runServiceAccountId,
|
|
39
39
|
} from "../../dist/gcp-names.js";
|
|
40
|
+
import {
|
|
41
|
+
DEFAULT_GATEWAY_MAX_INSTANCES,
|
|
42
|
+
DEFAULT_MAX_CONNECTIONS,
|
|
43
|
+
stackPoolBudget,
|
|
44
|
+
} from "../../dist/pool-budget-stack.js";
|
|
40
45
|
import {
|
|
41
46
|
ADMIN_MEMORY_DEFAULT,
|
|
42
47
|
adminEnvContract,
|
|
@@ -93,7 +98,21 @@ const adminMinInstances = Number.parseInt(cfg.get("adminMinInstances") ?? "0", 1
|
|
|
93
98
|
const adminMemory = cfg.get("adminMemory") ?? ADMIN_MEMORY_DEFAULT;
|
|
94
99
|
const adminMaxInstances = Number.parseInt(cfg.get("adminMaxInstances") ?? "5", 10);
|
|
95
100
|
const gatewayMinInstances = Number.parseInt(cfg.get("gatewayMinInstances") ?? "0", 10);
|
|
96
|
-
|
|
101
|
+
// #1254 — part of the connection budget (stacks/gcp has the long comment).
|
|
102
|
+
const gatewayMaxInstances = Number.parseInt(
|
|
103
|
+
cfg.get("gatewayMaxInstances") ?? String(DEFAULT_GATEWAY_MAX_INSTANCES),
|
|
104
|
+
10,
|
|
105
|
+
);
|
|
106
|
+
const maxConnections = Number.parseInt(
|
|
107
|
+
cfg.get("maxConnections") ?? String(DEFAULT_MAX_CONNECTIONS["gcp-firebase"]),
|
|
108
|
+
10,
|
|
109
|
+
);
|
|
110
|
+
// #1254 — every service's pool sizes, from the server's max_connections.
|
|
111
|
+
const poolBudget = stackPoolBudget("gcp-firebase", {
|
|
112
|
+
maxConnections,
|
|
113
|
+
adminMaxInstances,
|
|
114
|
+
gatewayMaxInstances,
|
|
115
|
+
});
|
|
97
116
|
const backupRetentionDays = Number.parseInt(cfg.get("backupRetentionDays") ?? "7", 10);
|
|
98
117
|
const iapAllowlistRaw = cfg.get("iapAllowlist");
|
|
99
118
|
const iapAllowlist =
|
|
@@ -655,7 +674,7 @@ const envContractInputs = {
|
|
|
655
674
|
const gatewaySvc = cloudRunService({
|
|
656
675
|
serviceName: "gateway",
|
|
657
676
|
minInstances: gatewayMinInstances,
|
|
658
|
-
maxInstances:
|
|
677
|
+
maxInstances: gatewayMaxInstances,
|
|
659
678
|
memory: "512Mi",
|
|
660
679
|
// #806 — remote MCP: chat turns stream through the gateway for minutes
|
|
661
680
|
// (stack-contract.ts GATEWAY_TIMEOUT_SECONDS). The Firebase Hosting
|
|
@@ -670,7 +689,11 @@ const gatewaySvc = cloudRunService({
|
|
|
670
689
|
// request passes its rate limits, captcha and honeypot; staging requests
|
|
671
690
|
// pass the staging access gate.
|
|
672
691
|
ingress: "INGRESS_TRAFFIC_ALL",
|
|
673
|
-
contractEnv: [
|
|
692
|
+
contractEnv: [
|
|
693
|
+
...gatewayEnvContract(envContractInputs),
|
|
694
|
+
...remoteMcpEnv.gateway,
|
|
695
|
+
...poolBudget.env.gateway,
|
|
696
|
+
],
|
|
674
697
|
});
|
|
675
698
|
|
|
676
699
|
const adminSvc = cloudRunService({
|
|
@@ -693,6 +716,7 @@ const adminSvc = cloudRunService({
|
|
|
693
716
|
gatewayService: gatewaySvc.name,
|
|
694
717
|
}),
|
|
695
718
|
...remoteMcpEnv.admin,
|
|
719
|
+
...poolBudget.env.admin,
|
|
696
720
|
],
|
|
697
721
|
mediaVolume: adminMediaVolumeTemplate(adminMediaVolume(project, env)),
|
|
698
722
|
});
|
|
@@ -32,6 +32,10 @@ pulumi stack output bootstrapUrl
|
|
|
32
32
|
|
|
33
33
|
Open that URL once Caddy has issued the cert (~30 s).
|
|
34
34
|
|
|
35
|
+
## Connection budget
|
|
36
|
+
|
|
37
|
+
The generated Compose file starts Postgres with `max_connections=100` and sizes every pool from it (#1254, docs/dev/connection-budget.md): the admin gets a cms_admin pool of 77 and a cms_public pool of 4, the gateway a pool of 2, and static builds 2 per pool.
|
|
38
|
+
|
|
35
39
|
## Destroy
|
|
36
40
|
|
|
37
41
|
```bash
|