@fjall/components-infrastructure 10.1.1 → 10.1.3

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.
@@ -1,5 +1,7 @@
1
+ import { Duration } from "aws-cdk-lib";
1
2
  import { ApplicationLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2";
2
3
  import { SubnetType } from "aws-cdk-lib/aws-ec2";
4
+ import { DEFAULT_ALB_IDLE_TIMEOUT_SECONDS } from "@fjall/util/httpKeepAlive";
3
5
  /**
4
6
  * Context-free Application Load Balancer factory shared by the ECS cluster path
5
7
  * and the dev substrate. Subnet placement is derived from `internetFacing`
@@ -10,6 +12,10 @@ export function createApplicationLoadBalancer(scope, id, options) {
10
12
  return new ApplicationLoadBalancer(scope, id, {
11
13
  vpc: options.vpc,
12
14
  internetFacing: options.internetFacing,
15
+ // Pinned (rather than left at the AWS default of the same value) because
16
+ // ALB-fronted containers receive it as FJALL_ALB_IDLE_TIMEOUT_SECONDS
17
+ // (ecsTaskDefinition.ts) for keep-alive tuning — one constant, both sides.
18
+ idleTimeout: Duration.seconds(DEFAULT_ALB_IDLE_TIMEOUT_SECONDS),
13
19
  ...(options.securityGroup && { securityGroup: options.securityGroup }),
14
20
  ...(options.loadBalancerName && {
15
21
  loadBalancerName: options.loadBalancerName
@@ -41,6 +41,19 @@ export declare const DEFAULT_ROLLING_UPDATE_PAUSE_SECONDS = 300;
41
41
  export declare function resolveEc2ContainerMemoryMiB(ec2Config: {
42
42
  readonly memoryLimitMiB?: number;
43
43
  } | undefined): number;
44
+ /**
45
+ * Hard memory limit for the synthetic `fjall-schema-gate` container on
46
+ * EC2-capacity services. The gate is a run-to-completion Node probe (a
47
+ * handful of DB queries, then exit) — giving it the service's full
48
+ * `ec2Config.memoryLimitMiB` doubles the task's placement requirement and
49
+ * can exceed the instance's registered memory entirely: with the 2048 MiB
50
+ * deploy-worker limit the task demanded 4096 MiB against a t4g.medium's
51
+ * 3835, making every task structurally unplaceable (2026-08-12 production
52
+ * wedge — armed silently at desiredCount 0, detonated on the first
53
+ * queue-driven scale-up mid-CFN-update). Fargate services are unaffected:
54
+ * their memory is task-level, so the gate shares the task envelope.
55
+ */
56
+ export declare const SCHEMA_GATE_CONTAINER_MEMORY_MIB = 512;
44
57
  export declare const DEFAULT_ECS_FALLBACK_IMAGE = "amazon/amazon-ecs-sample";
45
58
  export declare const DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS = 300;
46
59
  export declare const DEFAULT_HEALTH_CHECK_GRACE_SECONDS = 120;
@@ -54,6 +54,19 @@ export const DEFAULT_ROLLING_UPDATE_PAUSE_SECONDS = 300;
54
54
  export function resolveEc2ContainerMemoryMiB(ec2Config) {
55
55
  return ec2Config?.memoryLimitMiB ?? DEFAULT_EC2_CONTAINER_MEMORY_MIB;
56
56
  }
57
+ /**
58
+ * Hard memory limit for the synthetic `fjall-schema-gate` container on
59
+ * EC2-capacity services. The gate is a run-to-completion Node probe (a
60
+ * handful of DB queries, then exit) — giving it the service's full
61
+ * `ec2Config.memoryLimitMiB` doubles the task's placement requirement and
62
+ * can exceed the instance's registered memory entirely: with the 2048 MiB
63
+ * deploy-worker limit the task demanded 4096 MiB against a t4g.medium's
64
+ * 3835, making every task structurally unplaceable (2026-08-12 production
65
+ * wedge — armed silently at desiredCount 0, detonated on the first
66
+ * queue-driven scale-up mid-CFN-update). Fargate services are unaffected:
67
+ * their memory is task-level, so the gate shares the task envelope.
68
+ */
69
+ export const SCHEMA_GATE_CONTAINER_MEMORY_MIB = 512;
57
70
  // AWS sample image used when no ECR repository is provided. Consumed by both
58
71
  // the resources/ image resolver and the patterns/ defaults block — keep them
59
72
  // in lockstep via this single export.
@@ -3,9 +3,11 @@ import { Duration } from "aws-cdk-lib";
3
3
  import { Secret as EcsSecret } from "aws-cdk-lib/aws-ecs";
4
4
  import { StringParameter } from "aws-cdk-lib/aws-ssm";
5
5
  import { buildParameterPath } from "@fjall/util";
6
+ import { DEFAULT_ALB_IDLE_TIMEOUT_SECONDS, FJALL_ALB_IDLE_TIMEOUT_ENV_VAR } from "@fjall/util/httpKeepAlive";
7
+ import { SCHEMA_GATE_CONTAINER_NAME } from "@fjall/util/migration";
6
8
  import { resolveOrgId } from "../../../utils/cdkContext.js";
7
9
  import { validateSsmPathComponent, validateSecretName } from "./ecsValidation.js";
8
- import { DEFAULT_LOG_RETENTION, DEFAULT_FARGATE_CPU, DEFAULT_FARGATE_MEMORY_MIB, resolveEc2ContainerMemoryMiB } from "./ecsConstants.js";
10
+ import { DEFAULT_LOG_RETENTION, DEFAULT_FARGATE_CPU, DEFAULT_FARGATE_MEMORY_MIB, resolveEc2ContainerMemoryMiB, SCHEMA_GATE_CONTAINER_MEMORY_MIB } from "./ecsConstants.js";
9
11
  import { LogGroup } from "../logging/logGroup.js";
10
12
  import { getContainerImage } from "./ecsImages.js";
11
13
  import { resolveRemoteConnections } from "./ecsRemoteConnections.js";
@@ -194,6 +196,14 @@ export function addContainersToTask(ctx, serviceName, serviceProps, taskDefiniti
194
196
  ...remoteEnv,
195
197
  ...(containerConfig.port
196
198
  ? { PORT: String(containerConfig.port) }
199
+ : {}),
200
+ // The ALB half of the keep-alive contract (@fjall/util/httpKeepAlive):
201
+ // only the first port-bearing container is ALB-registered, and the
202
+ // value must match the idleTimeout pinned in applicationLoadBalancer.ts.
203
+ ...(isFirstWithPort && !ctx.loadBalancerDisabled
204
+ ? {
205
+ [FJALL_ALB_IDLE_TIMEOUT_ENV_VAR]: String(DEFAULT_ALB_IDLE_TIMEOUT_SECONDS)
206
+ }
197
207
  : {})
198
208
  },
199
209
  secrets,
@@ -221,8 +231,13 @@ export function addContainersToTask(ctx, serviceName, serviceProps, taskDefiniti
221
231
  startTimeout: containerConfig.startTimeout !== undefined
222
232
  ? Duration.seconds(containerConfig.startTimeout)
223
233
  : undefined,
234
+ // EC2 placement demands the SUM of container hard limits — the gate
235
+ // probe must not inherit the service's full per-container limit (see
236
+ // SCHEMA_GATE_CONTAINER_MEMORY_MIB; the gate name is synth-reserved).
224
237
  ...(isServiceEc2(serviceProps) && {
225
- memoryLimitMiB: resolveEc2ContainerMemoryMiB(serviceProps.ec2Config)
238
+ memoryLimitMiB: containerConfig.name === SCHEMA_GATE_CONTAINER_NAME
239
+ ? SCHEMA_GATE_CONTAINER_MEMORY_MIB
240
+ : resolveEc2ContainerMemoryMiB(serviceProps.ec2Config)
226
241
  })
227
242
  });
228
243
  if (containerConfig.port !== undefined &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fjall/components-infrastructure",
3
- "version": "10.1.1",
3
+ "version": "10.1.3",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/fjall-tech/fjall.git",
@@ -80,8 +80,8 @@
80
80
  },
81
81
  "dependencies": {
82
82
  "@aws-sdk/client-organizations": "^3.1098.0",
83
- "@fjall/generator": "^10.1.1",
84
- "@fjall/util": "^10.1.1",
83
+ "@fjall/generator": "^10.1.3",
84
+ "@fjall/util": "^10.1.3",
85
85
  "constructs": "^10.7.2"
86
86
  },
87
87
  "overrides": {