@fjall/components-infrastructure 4.4.0 → 6.0.0
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/lib/config/aws/ebsDefaultEncryption.js +1 -1
- package/dist/lib/config/aws/inspectorEnablement.js +1 -1
- package/dist/lib/config/aws/oidcConnector.d.ts +6 -6
- package/dist/lib/config/aws/oidcConnector.js +1 -1
- package/dist/lib/config/aws/s3BlockPublicAccess.js +1 -1
- package/dist/lib/config/aws/securityServicesAdmin.js +2 -2
- package/dist/lib/lambda-assets/cert-generator/asset/index.js +31 -31
- package/dist/lib/patterns/aws/account.d.ts +11 -10
- package/dist/lib/patterns/aws/account.js +11 -10
- package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +16 -3
- package/dist/lib/patterns/aws/clickhouseDatabase.js +23 -2
- package/dist/lib/patterns/aws/compute.js +1 -1
- package/dist/lib/patterns/aws/computeEcs.js +5 -0
- package/dist/lib/patterns/aws/computeEcsTypes.d.ts +12 -2
- package/dist/lib/patterns/aws/computeLambda.d.ts +1 -1
- package/dist/lib/patterns/aws/database.js +5 -5
- package/dist/lib/patterns/aws/devSubstrate.js +1 -1
- package/dist/lib/patterns/aws/payload.js +3 -3
- package/dist/lib/patterns/aws/staticSite.js +1 -1
- package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +1 -1
- package/dist/lib/resources/aws/compute/ecs.js +1 -0
- package/dist/lib/resources/aws/compute/ecsCapacityConfig.d.ts +43 -0
- package/dist/lib/resources/aws/compute/ecsCapacityConfig.js +161 -0
- package/dist/lib/resources/aws/compute/ecsConstants.d.ts +18 -0
- package/dist/lib/resources/aws/compute/ecsConstants.js +32 -0
- package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +1 -1
- package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +9 -6
- package/dist/lib/resources/aws/compute/ecsServiceFactory.js +17 -41
- package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +2 -6
- package/dist/lib/resources/aws/compute/ecsValidation.js +15 -2
- package/dist/lib/resources/aws/compute/persistentDataVolume.js +1 -1
- package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +58 -25
- package/dist/lib/resources/aws/database/clickhouseConstants.js +89 -25
- package/dist/lib/resources/aws/database/clickhouseUserData.js +77 -4
- package/dist/lib/resources/aws/database/rdsAurora.js +1 -1
- package/dist/lib/resources/aws/database/rdsInstance.js +2 -2
- package/dist/lib/resources/aws/iam/identityCenter/user.js +1 -1
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +9 -5
- package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +21 -12
- package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +1 -1
- package/dist/lib/resources/aws/networking/ipamPool.js +1 -1
- package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +1 -1
- package/dist/lib/resources/aws/utilities/tlsCertGenerator.js +1 -1
- package/dist/lib/utils/engineCompat.d.ts +42 -13
- package/dist/lib/utils/engineCompat.js +111 -25
- package/package.json +30 -23
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { AmiHardwareType } from "aws-cdk-lib/aws-ecs";
|
|
2
|
+
import { Token } from "aws-cdk-lib";
|
|
3
|
+
import { DEFAULT_EC2_INSTANCE_TYPE, DEFAULT_EC2_MIN_CAPACITY, DEFAULT_EC2_MAX_CAPACITY, DEFAULT_EC2_INSTANCE_MONITORING, DEFAULT_WARM_POOL_MIN_SIZE, DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN, inferAmiHardwareType } from "./ecsConstants.js";
|
|
4
|
+
/**
|
|
5
|
+
* Every `Ec2CapacityConfig` field, classified. The `Record<keyof …, …>`
|
|
6
|
+
* satisfies-shape is a compile-time exhaustiveness guard in compiled `src/`
|
|
7
|
+
* (typescript-standards § "Compile-time guards must live in compiled src/"):
|
|
8
|
+
* adding a field to `Ec2CapacityConfig` without classifying it here fails
|
|
9
|
+
* `tsc`, so a new ASG-shaping field cannot silently join the "not compared,
|
|
10
|
+
* silently discarded on a shared ASG" bucket.
|
|
11
|
+
*/
|
|
12
|
+
const EC2_CONFIG_FIELD_ROLES = {
|
|
13
|
+
instanceType: "keyed",
|
|
14
|
+
amiHardwareType: "keyed",
|
|
15
|
+
warmPool: "keyed",
|
|
16
|
+
persistentDataVolume: "keyed",
|
|
17
|
+
availabilityZones: "keyed",
|
|
18
|
+
minCapacity: "scalar",
|
|
19
|
+
maxCapacity: "scalar",
|
|
20
|
+
desiredCapacity: "scalar",
|
|
21
|
+
instanceMonitoring: "scalar",
|
|
22
|
+
associatePublicIpAddress: "scalar",
|
|
23
|
+
machineImage: "opaque",
|
|
24
|
+
userData: "opaque",
|
|
25
|
+
blockDevices: "opaque",
|
|
26
|
+
tags: "tags",
|
|
27
|
+
memoryLimitMiB: "taskDefinition"
|
|
28
|
+
};
|
|
29
|
+
function fieldsWithRole(role) {
|
|
30
|
+
return Object.keys(EC2_CONFIG_FIELD_ROLES).filter((field) => EC2_CONFIG_FIELD_ROLES[field] === role);
|
|
31
|
+
}
|
|
32
|
+
const KEYED_ASG_FIELDS = fieldsWithRole("keyed");
|
|
33
|
+
const SCALAR_ASG_FIELDS = fieldsWithRole("scalar");
|
|
34
|
+
const OPAQUE_ASG_FIELDS = fieldsWithRole("opaque");
|
|
35
|
+
/**
|
|
36
|
+
* Defaults resolved before a scalar field is compared — the SAME values the
|
|
37
|
+
* ASG construction applies (`getOrCreateAsgCapacityProvider`), imported from
|
|
38
|
+
* their `ecsConstants.ts` single source, so an explicit value equal to the
|
|
39
|
+
* default compares equal to an omitted one instead of tripping the drift
|
|
40
|
+
* throw on a pair that synthesises identically. Fields with no construction
|
|
41
|
+
* default (`desiredCapacity`, `associatePublicIpAddress`) compare raw.
|
|
42
|
+
*/
|
|
43
|
+
const SCALAR_ASG_FIELD_DEFAULTS = {
|
|
44
|
+
minCapacity: DEFAULT_EC2_MIN_CAPACITY,
|
|
45
|
+
maxCapacity: DEFAULT_EC2_MAX_CAPACITY,
|
|
46
|
+
instanceMonitoring: DEFAULT_EC2_INSTANCE_MONITORING
|
|
47
|
+
};
|
|
48
|
+
function resolvedScalar(config, field) {
|
|
49
|
+
return config[field] ?? SCALAR_ASG_FIELD_DEFAULTS[field];
|
|
50
|
+
}
|
|
51
|
+
/** Deep tag-record equality, treating `{}` and absent as the same ASG shape. */
|
|
52
|
+
function tagRecordsEqual(a, b) {
|
|
53
|
+
const aKeys = a === undefined ? [] : Object.keys(a);
|
|
54
|
+
const bKeys = b === undefined ? [] : Object.keys(b);
|
|
55
|
+
if (aKeys.length !== bKeys.length)
|
|
56
|
+
return false;
|
|
57
|
+
if (a === undefined || b === undefined)
|
|
58
|
+
return true;
|
|
59
|
+
return aKeys.every((key) => Object.is(a[key], b[key]));
|
|
60
|
+
}
|
|
61
|
+
function findDifferingAsgFields(ec2Config, origin) {
|
|
62
|
+
const differing = SCALAR_ASG_FIELDS.filter((field) => !Object.is(resolvedScalar(ec2Config, field), resolvedScalar(origin, field)));
|
|
63
|
+
if (!tagRecordsEqual(ec2Config.tags, origin.tags))
|
|
64
|
+
differing.push("tags");
|
|
65
|
+
for (const field of OPAQUE_ASG_FIELDS) {
|
|
66
|
+
if ((ec2Config[field] === undefined) !== (origin[field] === undefined)) {
|
|
67
|
+
differing.push(field);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return differing;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Rejects at synth when a service reusing a shared ASG is incompatible with
|
|
74
|
+
* it: either the origin's `persistentDataVolume` makes the ASG a singleton
|
|
75
|
+
* (two tasks would race to attach the same EBS volume), or the reusing
|
|
76
|
+
* service asks for ASG settings that differ from the ones the ASG was built
|
|
77
|
+
* with — those settings would be silently ignored (same silent-ignore class
|
|
78
|
+
* as `validateEc2ServiceSizing`).
|
|
79
|
+
*/
|
|
80
|
+
export function assertSharedAsgConfigMatches(serviceName, ec2Config, key, origin) {
|
|
81
|
+
// Cross-service only: the construction path legitimately re-resolves the
|
|
82
|
+
// SAME service's provider (e.g. a second task definition riding its ASG),
|
|
83
|
+
// and that re-entry must not trip the singleton throw.
|
|
84
|
+
if (origin.ec2Config.persistentDataVolume !== undefined &&
|
|
85
|
+
serviceName !== origin.serviceName) {
|
|
86
|
+
throw new Error(`Service '${serviceName}': its ec2Config resolves to the same EC2 capacity key ('${key}') ` +
|
|
87
|
+
`as service '${origin.serviceName}', whose persistentDataVolume pairs that auto scaling ` +
|
|
88
|
+
`group with a single EBS data volume. A persistent-data-volume service is a singleton — ` +
|
|
89
|
+
`two services sharing its ASG would race to attach the same volume. Change a keyed field ` +
|
|
90
|
+
`(${KEYED_ASG_FIELDS.map((f) => `'${f}'`).join(", ")}) so this service gets its own ASG.`);
|
|
91
|
+
}
|
|
92
|
+
const differing = findDifferingAsgFields(ec2Config, origin.ec2Config);
|
|
93
|
+
if (differing.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
throw new Error(`Service '${serviceName}': ec2Config ${differing.map((f) => `'${f}'`).join(", ")} ` +
|
|
96
|
+
`${differing.length === 1 ? "is" : "are"} ignored — it shares an auto scaling group with ` +
|
|
97
|
+
`service '${origin.serviceName}' (both resolve to EC2 capacity key '${key}'), and that ASG ` +
|
|
98
|
+
`is built from the first service's ec2Config. Either give the two services the same value ` +
|
|
99
|
+
`for ${differing.map((f) => `'${f}'`).join(", ")}, or give this service its own ASG by ` +
|
|
100
|
+
`changing a keyed field (${KEYED_ASG_FIELDS.map((f) => `'${f}'`).join(", ")}).`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Generates a unique key for EC2 config so services with matching
|
|
104
|
+
* configurations share an ASG.
|
|
105
|
+
*/
|
|
106
|
+
export function getEc2ConfigKey(ec2Config) {
|
|
107
|
+
const instanceType = ec2Config.instanceType ?? DEFAULT_EC2_INSTANCE_TYPE;
|
|
108
|
+
const amiHardwareType = ec2Config.amiHardwareType ??
|
|
109
|
+
(inferAmiHardwareType(instanceType) === AmiHardwareType.ARM
|
|
110
|
+
? "ARM"
|
|
111
|
+
: "STANDARD");
|
|
112
|
+
const warmPoolKey = ec2Config.warmPool
|
|
113
|
+
? `wp${ec2Config.warmPool.minSize ?? DEFAULT_WARM_POOL_MIN_SIZE}-${ec2Config.warmPool.reuseOnScaleIn ?? DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN}`
|
|
114
|
+
: "nowp";
|
|
115
|
+
const baseKey = `${instanceType}-${amiHardwareType}-${warmPoolKey}`;
|
|
116
|
+
// PDV services are implicit singletons — two identical PDV configs still
|
|
117
|
+
// collide on this key, so sharing is rejected in assertSharedAsgConfigMatches.
|
|
118
|
+
const pdv = ec2Config.persistentDataVolume;
|
|
119
|
+
const extras = [];
|
|
120
|
+
if (pdv !== undefined) {
|
|
121
|
+
extras.push(`pdv-${pdv.deviceName}-${pdv.sizeGb}-${stableAzSegment(pdv.availabilityZone)}`);
|
|
122
|
+
}
|
|
123
|
+
// Serialise actual AZ names — `az${length}` would let services pinned to different AZs collide on `az1`.
|
|
124
|
+
// CDK Tokens (env-agnostic stacks) substitute a stable sentinel to keep logical IDs deterministic.
|
|
125
|
+
if (ec2Config.availabilityZones !== undefined) {
|
|
126
|
+
const azKey = [...ec2Config.availabilityZones]
|
|
127
|
+
.map(stableAzSegment)
|
|
128
|
+
.sort()
|
|
129
|
+
.join(",");
|
|
130
|
+
extras.push(`az-${azKey}`);
|
|
131
|
+
}
|
|
132
|
+
return extras.length === 0 ? baseKey : `${baseKey}-${extras.join("-")}`;
|
|
133
|
+
}
|
|
134
|
+
function stableAzSegment(az) {
|
|
135
|
+
return Token.isUnresolved(az) ? "synthAz" : az;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Cross-service shared-ASG compatibility: groups EC2 services by their
|
|
139
|
+
* capacity key and rejects a reuse that is incompatible with the ASG's origin
|
|
140
|
+
* — config drift the shared ASG would silently discard, or a
|
|
141
|
+
* `persistentDataVolume` singleton being shared. Pure; called from BOTH
|
|
142
|
+
* validation layers — resources `validateEcsClusterProps` and patterns
|
|
143
|
+
* `validateEcsProps` (generator-standards § "Validate at the Lowest Layer the
|
|
144
|
+
* Field Belongs To") — with the same check at the construction site
|
|
145
|
+
* (`getOrCreateAsgCapacityProvider`) as defence-in-depth.
|
|
146
|
+
*/
|
|
147
|
+
export function validateSharedEc2CapacityConfig(services) {
|
|
148
|
+
const origins = new Map();
|
|
149
|
+
for (const service of services) {
|
|
150
|
+
if (service.capacityProvider !== "EC2")
|
|
151
|
+
continue;
|
|
152
|
+
const ec2Config = service.ec2Config ?? {};
|
|
153
|
+
const key = getEc2ConfigKey(ec2Config);
|
|
154
|
+
const origin = origins.get(key);
|
|
155
|
+
if (origin === undefined) {
|
|
156
|
+
origins.set(key, { serviceName: service.name, ec2Config });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
assertSharedAsgConfigMatches(service.name, ec2Config, key, origin);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Monitoring } from "aws-cdk-lib/aws-autoscaling";
|
|
1
2
|
import { AmiHardwareType } from "aws-cdk-lib/aws-ecs";
|
|
2
3
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
3
4
|
export declare const DEFAULT_EC2_INSTANCE_TYPE = "t4g.micro";
|
|
@@ -8,6 +9,23 @@ export declare const DEFAULT_LOG_RETENTION = RetentionDays.TWO_WEEKS;
|
|
|
8
9
|
export declare const DEFAULT_FARGATE_CPU = 256;
|
|
9
10
|
export declare const DEFAULT_FARGATE_MEMORY_MIB = 512;
|
|
10
11
|
export declare const DEFAULT_EC2_CONTAINER_MEMORY_MIB = 1024;
|
|
12
|
+
export declare const DEFAULT_EC2_MIN_CAPACITY = 2;
|
|
13
|
+
export declare const DEFAULT_EC2_MAX_CAPACITY = 3;
|
|
14
|
+
export declare const DEFAULT_EC2_INSTANCE_MONITORING = Monitoring.BASIC;
|
|
15
|
+
/**
|
|
16
|
+
* The container hard memory limit for an EC2-capacity ECS service.
|
|
17
|
+
*
|
|
18
|
+
* Takes `ec2Config` and nothing else — deliberately. The service-level
|
|
19
|
+
* `memoryLimitMiB` is Fargate-only, and a `?? serviceProps.memoryLimitMiB`
|
|
20
|
+
* fallback here is the silent-ignore footgun `validateEc2ServiceSizing`
|
|
21
|
+
* exists to reject. Narrowing the parameter to the one field that may feed
|
|
22
|
+
* the derivation makes that fallback unrepresentable rather than merely
|
|
23
|
+
* discouraged: re-adding it requires widening this signature, which is a
|
|
24
|
+
* visible act in review. Do not widen it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveEc2ContainerMemoryMiB(ec2Config: {
|
|
27
|
+
readonly memoryLimitMiB?: number;
|
|
28
|
+
} | undefined): number;
|
|
11
29
|
export declare const DEFAULT_ECS_FALLBACK_IMAGE = "amazon/amazon-ecs-sample";
|
|
12
30
|
export declare const DEFAULT_CUSTOM_RESOURCE_TIMEOUT_SECONDS = 300;
|
|
13
31
|
export declare const DEFAULT_HEALTH_CHECK_GRACE_SECONDS = 120;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Monitoring } from "aws-cdk-lib/aws-autoscaling";
|
|
1
2
|
import { AmiHardwareType } from "aws-cdk-lib/aws-ecs";
|
|
2
3
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
3
4
|
// Canonical source: @fjall/generator schemas/constants.ts — keep in sync
|
|
@@ -16,6 +17,28 @@ export const DEFAULT_LOG_RETENTION = RetentionDays.TWO_WEEKS;
|
|
|
16
17
|
export const DEFAULT_FARGATE_CPU = 256;
|
|
17
18
|
export const DEFAULT_FARGATE_MEMORY_MIB = 512;
|
|
18
19
|
export const DEFAULT_EC2_CONTAINER_MEMORY_MIB = 1024;
|
|
20
|
+
// Read at two sites that must agree (code-quality § coupled values): the ASG
|
|
21
|
+
// construction (`getOrCreateAsgCapacityProvider`) and the shared-ASG drift
|
|
22
|
+
// compare (`ecsCapacityConfig.ts` SCALAR_ASG_FIELD_DEFAULTS) — the compare
|
|
23
|
+
// resolves the same defaults the construction applies, or an explicit value
|
|
24
|
+
// equal to the default would read as drift.
|
|
25
|
+
export const DEFAULT_EC2_MIN_CAPACITY = 2;
|
|
26
|
+
export const DEFAULT_EC2_MAX_CAPACITY = 3;
|
|
27
|
+
export const DEFAULT_EC2_INSTANCE_MONITORING = Monitoring.BASIC;
|
|
28
|
+
/**
|
|
29
|
+
* The container hard memory limit for an EC2-capacity ECS service.
|
|
30
|
+
*
|
|
31
|
+
* Takes `ec2Config` and nothing else — deliberately. The service-level
|
|
32
|
+
* `memoryLimitMiB` is Fargate-only, and a `?? serviceProps.memoryLimitMiB`
|
|
33
|
+
* fallback here is the silent-ignore footgun `validateEc2ServiceSizing`
|
|
34
|
+
* exists to reject. Narrowing the parameter to the one field that may feed
|
|
35
|
+
* the derivation makes that fallback unrepresentable rather than merely
|
|
36
|
+
* discouraged: re-adding it requires widening this signature, which is a
|
|
37
|
+
* visible act in review. Do not widen it.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveEc2ContainerMemoryMiB(ec2Config) {
|
|
40
|
+
return ec2Config?.memoryLimitMiB ?? DEFAULT_EC2_CONTAINER_MEMORY_MIB;
|
|
41
|
+
}
|
|
19
42
|
// AWS sample image used when no ECR repository is provided. Consumed by both
|
|
20
43
|
// the resources/ image resolver and the patterns/ defaults block — keep them
|
|
21
44
|
// in lockstep via this single export.
|
|
@@ -46,10 +69,19 @@ export const ARM_INSTANCE_PREFIXES = [
|
|
|
46
69
|
"r6gd",
|
|
47
70
|
"r7g",
|
|
48
71
|
"r7gd",
|
|
72
|
+
"r8g",
|
|
73
|
+
"r8gd",
|
|
74
|
+
"c8g",
|
|
75
|
+
"c8gd",
|
|
76
|
+
"c8gn",
|
|
49
77
|
"m6g",
|
|
50
78
|
"m6gd",
|
|
51
79
|
"m7g",
|
|
52
80
|
"m7gd",
|
|
81
|
+
"m8g",
|
|
82
|
+
"m8gd",
|
|
83
|
+
"x8g",
|
|
84
|
+
"i8g",
|
|
53
85
|
"a1",
|
|
54
86
|
"x2gd",
|
|
55
87
|
"im4gn",
|
|
@@ -49,7 +49,7 @@ export class EcsLifecycleHookMigration extends Construct {
|
|
|
49
49
|
: "DISABLED"
|
|
50
50
|
};
|
|
51
51
|
this.lambda = new LambdaFunction(this, `${id}Fn`, {
|
|
52
|
-
runtime: Runtime.
|
|
52
|
+
runtime: Runtime.NODEJS_24_X,
|
|
53
53
|
handler: "index.handler",
|
|
54
54
|
code: Code.fromInline(source),
|
|
55
55
|
lambdaDescription: `${id} ECS deployment lifecycle hook for migration`,
|
|
@@ -3,7 +3,8 @@ import type { FargateTaskDefinition, Ec2TaskDefinition } from "aws-cdk-lib/aws-e
|
|
|
3
3
|
import { type ISecurityGroup } from "aws-cdk-lib/aws-ec2";
|
|
4
4
|
import { TargetTrackingScalingPolicy } from "aws-cdk-lib/aws-applicationautoscaling";
|
|
5
5
|
import { type AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling";
|
|
6
|
-
import { type
|
|
6
|
+
import { type Ec2AsgOrigin } from "./ecsCapacityConfig.js";
|
|
7
|
+
import { type EcsServiceProps, type QueueScalingResources } from "./ecsTypes.js";
|
|
7
8
|
import type { EcsConstructContext } from "./ecsContext.js";
|
|
8
9
|
/**
|
|
9
10
|
* Resolves the user's `circuitBreaker` config to the CDK
|
|
@@ -17,14 +18,16 @@ export declare function resolveCircuitBreaker(config: false | {
|
|
|
17
18
|
/** Mutable state for ASG capacity provider deduplication. */
|
|
18
19
|
export interface AsgCapacityState {
|
|
19
20
|
providers: Map<string, AsgCapacityProvider>;
|
|
21
|
+
/**
|
|
22
|
+
* The service + ec2Config that first created each keyed ASG, so a later
|
|
23
|
+
* service sharing the key can be checked against it
|
|
24
|
+
* ({@link assertSharedAsgConfigMatches}).
|
|
25
|
+
*/
|
|
26
|
+
asgOrigins: Map<string, Ec2AsgOrigin>;
|
|
20
27
|
autoScalingGroup?: AutoScalingGroup;
|
|
21
28
|
asgSecurityGroup?: ISecurityGroup;
|
|
22
29
|
}
|
|
23
|
-
|
|
24
|
-
* Generates a unique key for EC2 config so services with matching
|
|
25
|
-
* configurations share an ASG.
|
|
26
|
-
*/
|
|
27
|
-
export declare function getEc2ConfigKey(ec2Config: Ec2CapacityConfig): string;
|
|
30
|
+
export { getEc2ConfigKey, validateSharedEc2CapacityConfig, type Ec2AsgOrigin, type SharedEc2CapacityServiceInput } from "./ecsCapacityConfig.js";
|
|
28
31
|
/**
|
|
29
32
|
* Gets or creates an ASG capacity provider for an EC2-backed service.
|
|
30
33
|
* Services with matching EC2 configs share the same ASG.
|
|
@@ -2,16 +2,16 @@ import { FargateService, Ec2Service, PropagatedTagSource, PlacementStrategy, Asg
|
|
|
2
2
|
import { Peer, Port, SubnetType, UserData } from "aws-cdk-lib/aws-ec2";
|
|
3
3
|
import { ServicePrincipal } from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import { Role } from "../iam/role.js";
|
|
5
|
-
import { CfnOutput, Duration
|
|
5
|
+
import { CfnOutput, Duration } from "aws-cdk-lib";
|
|
6
6
|
import { AdjustmentType, PredefinedMetric, ScalableTarget, ServiceNamespace, StepScalingAction, TargetTrackingScalingPolicy } from "aws-cdk-lib/aws-applicationautoscaling";
|
|
7
7
|
import { Alarm, ComparisonOperator, MathExpression } from "aws-cdk-lib/aws-cloudwatch";
|
|
8
8
|
import { ApplicationScalingAction } from "aws-cdk-lib/aws-cloudwatch-actions";
|
|
9
|
-
import { Monitoring } from "aws-cdk-lib/aws-autoscaling";
|
|
10
9
|
import { SecurityGroup } from "../networking/securityGroup.js";
|
|
11
10
|
import { Ec2Instance } from "./ec2.js";
|
|
12
11
|
import { vpcHasNatGateways } from "../../../utils/vpcUtils.js";
|
|
13
12
|
import { toPascalCase } from "../../../utils/capitaliseString.js";
|
|
14
|
-
import { DEFAULT_EC2_INSTANCE_TYPE, DEFAULT_WARM_POOL_MIN_SIZE, DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN, DEFAULT_HEALTH_CHECK_GRACE_SECONDS, DEFAULT_MIN_HEALTHY_PERCENT, DEFAULT_MAX_HEALTHY_PERCENT, DEFAULT_DESIRED_COUNT, inferAmiHardwareType } from "./ecsConstants.js";
|
|
13
|
+
import { DEFAULT_EC2_INSTANCE_TYPE, DEFAULT_EC2_MIN_CAPACITY, DEFAULT_EC2_MAX_CAPACITY, DEFAULT_EC2_INSTANCE_MONITORING, DEFAULT_WARM_POOL_MIN_SIZE, DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN, DEFAULT_HEALTH_CHECK_GRACE_SECONDS, DEFAULT_MIN_HEALTHY_PERCENT, DEFAULT_MAX_HEALTHY_PERCENT, DEFAULT_DESIRED_COUNT, inferAmiHardwareType } from "./ecsConstants.js";
|
|
14
|
+
import { assertSharedAsgConfigMatches, getEc2ConfigKey } from "./ecsCapacityConfig.js";
|
|
15
15
|
import { ScalingType } from "./ecsTypes.js";
|
|
16
16
|
import { isServiceFargate, isServiceEc2 } from "./ecsTaskDefinition.js";
|
|
17
17
|
/**
|
|
@@ -26,40 +26,7 @@ export function resolveCircuitBreaker(config) {
|
|
|
26
26
|
const rollback = config?.rollback ?? true;
|
|
27
27
|
return { enable: true, rollback };
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
* Generates a unique key for EC2 config so services with matching
|
|
31
|
-
* configurations share an ASG.
|
|
32
|
-
*/
|
|
33
|
-
export function getEc2ConfigKey(ec2Config) {
|
|
34
|
-
const instanceType = ec2Config.instanceType ?? DEFAULT_EC2_INSTANCE_TYPE;
|
|
35
|
-
const amiHardwareType = ec2Config.amiHardwareType ??
|
|
36
|
-
(inferAmiHardwareType(instanceType) === AmiHardwareType.ARM
|
|
37
|
-
? "ARM"
|
|
38
|
-
: "STANDARD");
|
|
39
|
-
const warmPoolKey = ec2Config.warmPool
|
|
40
|
-
? `wp${ec2Config.warmPool.minSize ?? DEFAULT_WARM_POOL_MIN_SIZE}-${ec2Config.warmPool.reuseOnScaleIn ?? DEFAULT_WARM_POOL_REUSE_ON_SCALE_IN}`
|
|
41
|
-
: "nowp";
|
|
42
|
-
const baseKey = `${instanceType}-${amiHardwareType}-${warmPoolKey}`;
|
|
43
|
-
// PDV services are implicit singletons — sharing an ASG would let two tasks race for the same EBS volume.
|
|
44
|
-
const pdv = ec2Config.persistentDataVolume;
|
|
45
|
-
const extras = [];
|
|
46
|
-
if (pdv !== undefined) {
|
|
47
|
-
extras.push(`pdv-${pdv.deviceName}-${pdv.sizeGb}-${stableAzSegment(pdv.availabilityZone)}`);
|
|
48
|
-
}
|
|
49
|
-
// Serialise actual AZ names — `az${length}` would let services pinned to different AZs collide on `az1`.
|
|
50
|
-
// CDK Tokens (env-agnostic stacks) substitute a stable sentinel to keep logical IDs deterministic.
|
|
51
|
-
if (ec2Config.availabilityZones !== undefined) {
|
|
52
|
-
const azKey = [...ec2Config.availabilityZones]
|
|
53
|
-
.map(stableAzSegment)
|
|
54
|
-
.sort()
|
|
55
|
-
.join(",");
|
|
56
|
-
extras.push(`az-${azKey}`);
|
|
57
|
-
}
|
|
58
|
-
return extras.length === 0 ? baseKey : `${baseKey}-${extras.join("-")}`;
|
|
59
|
-
}
|
|
60
|
-
function stableAzSegment(az) {
|
|
61
|
-
return Token.isUnresolved(az) ? "synthAz" : az;
|
|
62
|
-
}
|
|
29
|
+
export { getEc2ConfigKey, validateSharedEc2CapacityConfig } from "./ecsCapacityConfig.js";
|
|
63
30
|
/**
|
|
64
31
|
* Gets or creates an ASG capacity provider for an EC2-backed service.
|
|
65
32
|
* Services with matching EC2 configs share the same ASG.
|
|
@@ -71,8 +38,13 @@ export function getOrCreateAsgCapacityProvider(ctx, serviceProps, state) {
|
|
|
71
38
|
const key = getEc2ConfigKey(ec2Config);
|
|
72
39
|
const existing = state.providers.get(key);
|
|
73
40
|
if (existing) {
|
|
41
|
+
const origin = state.asgOrigins.get(key);
|
|
42
|
+
if (origin !== undefined) {
|
|
43
|
+
assertSharedAsgConfigMatches(serviceProps.name, ec2Config, key, origin);
|
|
44
|
+
}
|
|
74
45
|
return existing;
|
|
75
46
|
}
|
|
47
|
+
state.asgOrigins.set(key, { serviceName: serviceProps.name, ec2Config });
|
|
76
48
|
const safeKey = key.replace(/[^a-zA-Z0-9]/g, "");
|
|
77
49
|
const instanceType = ec2Config.instanceType ?? DEFAULT_EC2_INSTANCE_TYPE;
|
|
78
50
|
const amiHardwareType = ec2Config.amiHardwareType
|
|
@@ -80,8 +52,8 @@ export function getOrCreateAsgCapacityProvider(ctx, serviceProps, state) {
|
|
|
80
52
|
? AmiHardwareType.STANDARD
|
|
81
53
|
: AmiHardwareType.ARM
|
|
82
54
|
: inferAmiHardwareType(instanceType);
|
|
83
|
-
const minCapacity = ec2Config.minCapacity ??
|
|
84
|
-
const maxCapacity = ec2Config.maxCapacity ??
|
|
55
|
+
const minCapacity = ec2Config.minCapacity ?? DEFAULT_EC2_MIN_CAPACITY;
|
|
56
|
+
const maxCapacity = ec2Config.maxCapacity ?? DEFAULT_EC2_MAX_CAPACITY;
|
|
85
57
|
const asgSecurityGroup = ctx.props.cluster?.securityGroup ??
|
|
86
58
|
new SecurityGroup(ctx.scope, `${safeKey}AsgSecurityGroup`, {
|
|
87
59
|
vpc: ctx.cluster.vpc,
|
|
@@ -129,7 +101,7 @@ export function getOrCreateAsgCapacityProvider(ctx, serviceProps, state) {
|
|
|
129
101
|
EcsOptimizedImage.amazonLinux2023(amiHardwareType),
|
|
130
102
|
userData: ec2Config.userData ?? UserData.forLinux(),
|
|
131
103
|
role: instanceRole,
|
|
132
|
-
instanceMonitoring: ec2Config.instanceMonitoring ??
|
|
104
|
+
instanceMonitoring: ec2Config.instanceMonitoring ?? DEFAULT_EC2_INSTANCE_MONITORING,
|
|
133
105
|
capacityRebalance: true,
|
|
134
106
|
ecsClusterArn: ctx.cluster.clusterArn,
|
|
135
107
|
...(ec2Config.desiredCapacity !== undefined && {
|
|
@@ -160,7 +132,11 @@ export function getOrCreateAsgCapacityProvider(ctx, serviceProps, state) {
|
|
|
160
132
|
// MTP's ProtectedFromScaleIn flag does NOT clear on CP deletion, so
|
|
161
133
|
// CFN rollback wedges on a stranded protected instance. The drain
|
|
162
134
|
// path is Ec2GracefulTerminationHandler instead.
|
|
163
|
-
enableManagedTerminationProtection: false
|
|
135
|
+
enableManagedTerminationProtection: false,
|
|
136
|
+
// A min=max pinned ASG cannot scale; managed scaling would still mint a
|
|
137
|
+
// CapacityProviderReservation target-tracking pair that wedges in ALARM
|
|
138
|
+
// whenever a task cannot place (2026-07-24 ClickHouse phantom task).
|
|
139
|
+
enableManagedScaling: minCapacity !== maxCapacity
|
|
164
140
|
});
|
|
165
141
|
ctx.cluster.addAsgCapacityProvider(provider);
|
|
166
142
|
state.providers.set(key, provider);
|
|
@@ -5,7 +5,7 @@ import { StringParameter } from "aws-cdk-lib/aws-ssm";
|
|
|
5
5
|
import { buildParameterPath } from "@fjall/util";
|
|
6
6
|
import { resolveOrgId } from "../../../utils/cdkContext.js";
|
|
7
7
|
import { validateSsmPathComponent, validateSecretName } from "./ecsValidation.js";
|
|
8
|
-
import { DEFAULT_LOG_RETENTION, DEFAULT_FARGATE_CPU, DEFAULT_FARGATE_MEMORY_MIB,
|
|
8
|
+
import { DEFAULT_LOG_RETENTION, DEFAULT_FARGATE_CPU, DEFAULT_FARGATE_MEMORY_MIB, resolveEc2ContainerMemoryMiB } from "./ecsConstants.js";
|
|
9
9
|
import { LogGroup } from "../logging/logGroup.js";
|
|
10
10
|
import { getContainerImage } from "./ecsImages.js";
|
|
11
11
|
import { resolveRemoteConnections } from "./ecsRemoteConnections.js";
|
|
@@ -214,12 +214,8 @@ export function addContainersToTask(ctx, serviceName, serviceProps, taskDefiniti
|
|
|
214
214
|
stopTimeout: containerConfig.stopTimeout !== undefined
|
|
215
215
|
? Duration.seconds(containerConfig.stopTimeout)
|
|
216
216
|
: undefined,
|
|
217
|
-
// EC2 container memory is ec2Config-only by design — do NOT add a
|
|
218
|
-
// `?? serviceProps.memoryLimitMiB` fallback (that is the silent-ignore
|
|
219
|
-
// footgun `validateEc2ServiceSizing` exists to reject).
|
|
220
217
|
...(isServiceEc2(serviceProps) && {
|
|
221
|
-
memoryLimitMiB: serviceProps.ec2Config
|
|
222
|
-
DEFAULT_EC2_CONTAINER_MEMORY_MIB
|
|
218
|
+
memoryLimitMiB: resolveEc2ContainerMemoryMiB(serviceProps.ec2Config)
|
|
223
219
|
})
|
|
224
220
|
});
|
|
225
221
|
if (containerConfig.port !== undefined &&
|
|
@@ -3,6 +3,7 @@ import { evaluateBakeGuard } from "@fjall/util/docker";
|
|
|
3
3
|
import { toKebab, SSM_COMPONENT_PATTERN, SSM_COMPONENT_ERROR, SECRET_NAME_PATTERN, SECRET_NAME_ERROR } from "@fjall/util";
|
|
4
4
|
import { ScalingType } from "./ecsTypes.js";
|
|
5
5
|
import { DEFAULT_EC2_CONTAINER_MEMORY_MIB } from "./ecsConstants.js";
|
|
6
|
+
import { validateSharedEc2CapacityConfig } from "./ecsCapacityConfig.js";
|
|
6
7
|
/**
|
|
7
8
|
* Validates ECS cluster props before construction.
|
|
8
9
|
* Pure function — does not depend on class state.
|
|
@@ -156,12 +157,19 @@ export function validateEcsClusterProps(props) {
|
|
|
156
157
|
// backlog scaling — CPU/MEMORY target-tracking produces no datapoint at
|
|
157
158
|
// zero running tasks, so DesiredCount never rises (the 2026-06-04
|
|
158
159
|
// asset-discovery outage). Reject the un-wakeable shape at synth.
|
|
160
|
+
// scalingType undefined means scaling was EXPLICITLY disabled
|
|
161
|
+
// (resolveScalingConfig defaults undefined input to CPU): no policy exists
|
|
162
|
+
// to wake the service, so desiredCount 0 is a deliberately parked service
|
|
163
|
+
// (the ClickHouse bring-up escape hatch), not the trap.
|
|
159
164
|
if (service.desiredCount === 0 &&
|
|
165
|
+
service.scalingType !== undefined &&
|
|
160
166
|
service.scalingType !== ScalingType.QUEUE) {
|
|
161
167
|
throw new Error(`Service '${service.name}': desiredCount is 0 but scalingType is ` +
|
|
162
|
-
`${service.scalingType
|
|
168
|
+
`${service.scalingType} — a service that starts at zero can ` +
|
|
163
169
|
"only wake on queue backlog. Use ScalingType.QUEUE with queueScaling.queues " +
|
|
164
|
-
"so it scales up from 0,
|
|
170
|
+
"so it scales up from 0, raise desiredCount to keep at least one task running, " +
|
|
171
|
+
"or disable scaling to park the service deliberately (patterns layer: " +
|
|
172
|
+
"scaling: false; resources layer: omit scalingType). " +
|
|
165
173
|
"CPU/MEMORY target-tracking produces no datapoint at zero tasks and never scales up.");
|
|
166
174
|
}
|
|
167
175
|
if (service.scalingType === ScalingType.QUEUE) {
|
|
@@ -189,6 +197,11 @@ export function validateEcsClusterProps(props) {
|
|
|
189
197
|
}
|
|
190
198
|
}
|
|
191
199
|
}
|
|
200
|
+
// Cross-service, so it runs after the per-service loop: EC2 services whose
|
|
201
|
+
// ec2Config resolves to the same capacity key share one ASG — reject config
|
|
202
|
+
// drift the shared ASG would silently discard, and any sharing of a
|
|
203
|
+
// persistentDataVolume (singleton) ASG.
|
|
204
|
+
validateSharedEc2CapacityConfig(props.services);
|
|
192
205
|
}
|
|
193
206
|
/**
|
|
194
207
|
* Rejects service-level task sizing that is silently ignored for EC2 capacity.
|
|
@@ -157,7 +157,7 @@ export class PersistentDataVolume extends Construct {
|
|
|
157
157
|
resources: [asgArnWildcard]
|
|
158
158
|
});
|
|
159
159
|
this.lambda = new LambdaFunction(this, `${id}Fn`, {
|
|
160
|
-
runtime: Runtime.
|
|
160
|
+
runtime: Runtime.NODEJS_24_X,
|
|
161
161
|
handler: "index.handler",
|
|
162
162
|
code: Code.fromInline(source),
|
|
163
163
|
lambdaDescription: `${id} ${PERSISTENT_DATA_VOLUME_LAUNCHING_DESCRIPTION}`,
|
|
@@ -14,10 +14,15 @@ export declare const CLICKHOUSE_DATABASE_NAME = "analytics";
|
|
|
14
14
|
* max_concurrent_queries=8) — see clickhouseUserData.ts. Bumping to a larger
|
|
15
15
|
* instance MUST be paired with raising those thread/concurrency caps.
|
|
16
16
|
*
|
|
17
|
-
* Next size up
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Next size up (2026-07-31 right-sizing, designs/2026-07-31-clickhouse-
|
|
18
|
+
* factory-fit-for-purpose.md): `r8g.medium` (1 vCPU Graviton4, 8 GiB,
|
|
19
|
+
* ~+30%/core over Graviton3) — CH's own doctrine floors production at
|
|
20
|
+
* 8 GiB, and the memory-bound single-tenant profile wants RAM before
|
|
21
|
+
* cores. `m7g.large` (2 vCPU, 8 GiB) only when CPU-bound. Set via
|
|
22
|
+
* `clickhouseInstanceType` CDK context or the `instanceType` prop —
|
|
23
|
+
* the container memory limit derives automatically via
|
|
24
|
+
* `clickHouseTaskMemoryMiB()`, but the thread/concurrency caps in
|
|
25
|
+
* clickhouseUserData.ts still need a lockstep re-tune. */
|
|
21
26
|
export declare const DEFAULT_CLICKHOUSE_INSTANCE_TYPE = "m7g.medium";
|
|
22
27
|
/** ClickHouse container image. Explicit `docker.io/` prefix is required so
|
|
23
28
|
* string-form consumers in `ecsImages.ts#getContainerImage()` route through
|
|
@@ -38,17 +43,35 @@ export declare const DEFAULT_CLICKHOUSE_INSTANCE_TYPE = "m7g.medium";
|
|
|
38
43
|
* Image size is ~250 MB larger but irrelevant on a single-instance EC2 ASG
|
|
39
44
|
* that pulls once per launch. Upstream CH CI runs its full perf + stress
|
|
40
45
|
* matrix on the Ubuntu build; Alpine is community-tier coverage. */
|
|
41
|
-
export declare const CLICKHOUSE_IMAGE = "docker.io/clickhouse/clickhouse-server:26.3.
|
|
46
|
+
export declare const CLICKHOUSE_IMAGE = "docker.io/clickhouse/clickhouse-server:26.3.17.56";
|
|
42
47
|
/** EBS volume configuration. */
|
|
43
48
|
export declare const CLICKHOUSE_EBS_VOLUME_SIZE_GB = 80;
|
|
44
49
|
export declare const CLICKHOUSE_EBS_IOPS = 3000;
|
|
45
50
|
export declare const CLICKHOUSE_EBS_THROUGHPUT_MBPS = 125;
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
|
|
51
|
+
/** Host memory reserved from the ClickHouse container: kernel + ECS agent +
|
|
52
|
+
* the host-metrics timer. The ECS agent advertises MemTotal minus
|
|
53
|
+
* CLICKHOUSE_ECS_RESERVED_MEMORY_MIB, and MemTotal itself runs below the
|
|
54
|
+
* AWS nominal figure (firmware carve-outs plus page-struct overhead that
|
|
55
|
+
* grows with RAM, ~205 MiB observed on the 4 GiB m7g.medium), so this
|
|
56
|
+
* reserve must absorb both or the task never places. 1 GiB is proven at
|
|
57
|
+
* 4–16 GiB; the ≥32 GiB rows double it (see clickHouseTaskMemoryMiB). */
|
|
58
|
+
export declare const CLICKHOUSE_HOST_RESERVE_MIB = 1024;
|
|
59
|
+
/** MiB the ECS agent withholds from advertised capacity — written to
|
|
60
|
+
* /etc/ecs/ecs.config as `ECS_RESERVED_MEMORY` by buildClickHouseUserData.
|
|
61
|
+
* Must stay well inside CLICKHOUSE_HOST_RESERVE_MIB: the container limit is
|
|
62
|
+
* derived from nominal memory, so the host reserve is what absorbs this
|
|
63
|
+
* withholding at task placement. */
|
|
64
|
+
export declare const CLICKHOUSE_ECS_RESERVED_MEMORY_MIB = 256;
|
|
65
|
+
/** Total memory (GiB) of the instance types the ClickHouse construct knows
|
|
66
|
+
* how to size a container for. Values are the AWS nominal figures. */
|
|
67
|
+
export declare const CLICKHOUSE_INSTANCE_MEMORY_GIB: Record<string, number>;
|
|
68
|
+
/** ECS container memory for the ClickHouse server task, derived from the
|
|
69
|
+
* instance type so a size change cannot leave the hand-set container cap
|
|
70
|
+
* behind (the pre-2026-07 shape: a hardcoded 3072 that silently starved —
|
|
71
|
+
* or over-committed — any non-4-GiB host). Unknown types throw at synth:
|
|
72
|
+
* a memoryLimitMiB above what the host advertises strands the task in
|
|
73
|
+
* PROVISIONING with no CloudFormation error. */
|
|
74
|
+
export declare function clickHouseTaskMemoryMiB(instanceType: string): number;
|
|
52
75
|
/** ClickHouse ports. */
|
|
53
76
|
export declare const CLICKHOUSE_HTTP_PORT = 8123;
|
|
54
77
|
export declare const CLICKHOUSE_NATIVE_PORT = 9000;
|
|
@@ -58,22 +81,16 @@ export declare const CLICKHOUSE_PROMETHEUS_PORT = 9363;
|
|
|
58
81
|
* See aiDocs/patterns/clickhouse-tls-pattern.md § "The contract". */
|
|
59
82
|
export declare const CLICKHOUSE_HTTPS_PORT = 8443;
|
|
60
83
|
export declare const CLICKHOUSE_TCP_SECURE_PORT = 9440;
|
|
61
|
-
/** Mount path inside the
|
|
62
|
-
*
|
|
63
|
-
*
|
|
84
|
+
/** Mount path inside the ClickHouse container for the materialised TLS cert
|
|
85
|
+
* + key. The EC2 user-data bootstrap (`buildClickHouseUserData` tlsBootstrap)
|
|
86
|
+
* fetches the PEMs from Secrets Manager onto the EBS data volume at
|
|
87
|
+
* `<mount>/server-certs`, which is bind-mounted read-only into the container
|
|
88
|
+
* at this path. */
|
|
64
89
|
export declare const CLICKHOUSE_TLS_CERT_MOUNT_PATH = "/etc/clickhouse-server/certs";
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
/** UID:GID the official ClickHouse image runs as. Init container `chown`s
|
|
69
|
-
* the materialised cert files to this UID so the server can read them. */
|
|
90
|
+
/** UID:GID the official ClickHouse image runs as. The user-data bootstrap
|
|
91
|
+
* `chown`s the materialised cert files to this UID so the server can read
|
|
92
|
+
* them. */
|
|
70
93
|
export declare const CLICKHOUSE_UID = 101;
|
|
71
|
-
/** Digest-pinned alpine image used by the TLS init container. The init
|
|
72
|
-
* container needs `jq` (extracts `cert`+`key` from the server-cert JSON
|
|
73
|
-
* secret); alpine ships it via `apk add` — but pinning the digest avoids
|
|
74
|
-
* fetching `:latest` on every deploy. Bump in lockstep with renovate
|
|
75
|
-
* alerts; CI verifies the digest resolves. */
|
|
76
|
-
export declare const ALPINE_INIT_CONTAINER_IMAGE = "public.ecr.aws/docker/library/alpine:3.20@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d";
|
|
77
94
|
/** EBS device name for the data volume (must match user data script). */
|
|
78
95
|
export declare const CLICKHOUSE_EBS_DEVICE_NAME = "/dev/xvdf";
|
|
79
96
|
/** EBS mount path on the EC2 host. */
|
|
@@ -123,6 +140,22 @@ export declare const CLICKHOUSE_SERVER_ROLE_TAG: {
|
|
|
123
140
|
readonly key: "ClickHouseRole";
|
|
124
141
|
readonly value: "server";
|
|
125
142
|
};
|
|
143
|
+
/** CloudWatch identity of the host metrics: namespace, metric names, and the
|
|
144
|
+
* dimension key. Three sites consume this and they MUST match: (a) the
|
|
145
|
+
* user-data put-metric-data timer (`buildClickHouseUserData`) that publishes
|
|
146
|
+
* the datapoints, (b) the host alarms (`createClickHouseAlarms`) that
|
|
147
|
+
* consume them, and (c) the instance-role PutMetricData namespace condition
|
|
148
|
+
* (`clickhouseDatabase.ts`). One-character drift re-blinds the alarms
|
|
149
|
+
* silently — and with disk-critical on TreatMissingData.BREACHING, a
|
|
150
|
+
* namespace typo pages as a phantom full disk. "CWAgent" matches what the
|
|
151
|
+
* CloudWatch Agent would publish, keeping dashboards portable if the timer
|
|
152
|
+
* is ever replaced by the real agent. */
|
|
153
|
+
export declare const CLICKHOUSE_HOST_METRICS: {
|
|
154
|
+
readonly namespace: "CWAgent";
|
|
155
|
+
readonly memoryMetric: "mem_used_percent";
|
|
156
|
+
readonly diskMetric: "disk_used_percent";
|
|
157
|
+
readonly asgDimension: "AutoScalingGroupName";
|
|
158
|
+
};
|
|
126
159
|
/** Shared secret generation options (all ClickHouse users share the same policy). */
|
|
127
160
|
export declare const CLICKHOUSE_SECRET_OPTIONS: {
|
|
128
161
|
readonly excludePunctuation: true;
|