mirror of
https://gitea.com/mcereda/oam.git
synced 2026-02-09 05:44:23 +00:00
chore(pulumi): add snippet about ecs service with containers with cascade start
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
1. [Assume role with MFA enabled but AssumeRoleTokenProvider session option not set](#assume-role-with-mfa-enabled-but-assumeroletokenprovider-session-option-not-set)
|
1. [Assume role with MFA enabled but AssumeRoleTokenProvider session option not set](#assume-role-with-mfa-enabled-but-assumeroletokenprovider-session-option-not-set)
|
||||||
1. [Attempting to deploy or update resources with pending operations from previous deployment](#attempting-to-deploy-or-update-resources-with-pending-operations-from-previous-deployment)
|
1. [Attempting to deploy or update resources with pending operations from previous deployment](#attempting-to-deploy-or-update-resources-with-pending-operations-from-previous-deployment)
|
||||||
1. [Change your program back to the original providers](#change-your-program-back-to-the-original-providers)
|
1. [Change your program back to the original providers](#change-your-program-back-to-the-original-providers)
|
||||||
|
1. [RangeError: Invalid string length](#rangeerror-invalid-string-length)
|
||||||
1. [Stack init fails because the stack supposedly already exists](#stack-init-fails-because-the-stack-supposedly-already-exists)
|
1. [Stack init fails because the stack supposedly already exists](#stack-init-fails-because-the-stack-supposedly-already-exists)
|
||||||
1. [Stack init fails due to missing scheme](#stack-init-fails-due-to-missing-scheme)
|
1. [Stack init fails due to missing scheme](#stack-init-fails-due-to-missing-scheme)
|
||||||
1. [Stack init fails due to invalid key identifier](#stack-init-fails-due-to-invalid-key-identifier)
|
1. [Stack init fails due to invalid key identifier](#stack-init-fails-due-to-invalid-key-identifier)
|
||||||
@@ -1466,6 +1467,35 @@ Solution:
|
|||||||
1. Run `pulumi install` to gather the required version.
|
1. Run `pulumi install` to gather the required version.
|
||||||
1. Try the action again now.
|
1. Try the action again now.
|
||||||
|
|
||||||
|
### RangeError: Invalid string length
|
||||||
|
|
||||||
|
Error message example:
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
Diagnostics:
|
||||||
|
pulumi:pulumi:Stack (someStack-dev):
|
||||||
|
error: Running program '/path/to/pulumi/project/index.ts' failed with an unhandled exception:
|
||||||
|
RangeError: Invalid string length
|
||||||
|
at markNodeModules (node:internal/util/inspect:1601:21)
|
||||||
|
at formatError (node:internal/util/inspect:1691:18)
|
||||||
|
at formatRaw (node:internal/util/inspect:1084:14)
|
||||||
|
at formatValue (node:internal/util/inspect:932:10)
|
||||||
|
at Object.inspect (node:internal/util/inspect:409:10)
|
||||||
|
at process.uncaughtHandler (/path/to/pulumi/project/node_modules/@pulumi/cmd/run/run.ts:302:48)
|
||||||
|
at process.emit (node:events:520:35)
|
||||||
|
at process.emit (/path/to/pulumi/project/node_modules/@pulumi/pulumi/vendor/ts-node@7.0.1/index.js:2204:25)
|
||||||
|
at process.emit (/path/to/pulumi/project/node_modules/source-map-support/source-map-support.js:516:21)
|
||||||
|
at emitUnhandledRejection (node:internal/process/promises:252:13)
|
||||||
|
error: an unhandled error occurred: Program exited with non-zero exit code: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Root cause: something is wrong in the stack's configuration; most likely, the code tries to load a key from it and fails
|
||||||
|
because it is missing in the file.
|
||||||
|
|
||||||
|
Solution: add all required missing keys to the stack's configuration file.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
### Stack init fails because the stack supposedly already exists
|
### Stack init fails because the stack supposedly already exists
|
||||||
|
|
||||||
Context: a stack fails to initialize.
|
Context: a stack fails to initialize.
|
||||||
|
|||||||
428
snippets/pulumi/aws/ecs service containers with chained start.ts
Normal file
428
snippets/pulumi/aws/ecs service containers with chained start.ts
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
import {
|
||||||
|
getListenerOutput, GetListenerResult,
|
||||||
|
getLoadBalancerOutput, GetLoadBalancerResult,
|
||||||
|
ListenerRule,
|
||||||
|
TargetGroup,
|
||||||
|
} from '@pulumi/aws/alb';
|
||||||
|
import { LogGroup } from '@pulumi/aws/cloudwatch';
|
||||||
|
import {
|
||||||
|
getSecurityGroupOutput, GetSecurityGroupResult,
|
||||||
|
getSubnetsOutput, GetSubnetsResult,
|
||||||
|
getVpcOutput, GetVpcResult,
|
||||||
|
SecurityGroup,
|
||||||
|
} from '@pulumi/aws/ec2';
|
||||||
|
import { getImageOutput, GetImageResult } from '@pulumi/aws/ecr';
|
||||||
|
import {
|
||||||
|
getClusterOutput, GetClusterResult,
|
||||||
|
Service,
|
||||||
|
TaskDefinition,
|
||||||
|
} from '@pulumi/aws/ecs';
|
||||||
|
import { getRoleOutput, GetRoleResult } from '@pulumi/aws/iam';
|
||||||
|
import { Secret, SecretVersion } from '@pulumi/aws/secretsmanager';
|
||||||
|
import { SecurityGroupEgressRule, SecurityGroupIngressRule } from '@pulumi/aws/vpc';
|
||||||
|
import {
|
||||||
|
Config as PulumiConfig,
|
||||||
|
Input as PulumiInput,
|
||||||
|
Output as PulumiOutput,
|
||||||
|
interpolate as pulumiInterpolate,
|
||||||
|
jsonStringify as pulumiJsonStringify,
|
||||||
|
} from '@pulumi/pulumi';
|
||||||
|
|
||||||
|
const pulumiConfig = new PulumiConfig();
|
||||||
|
|
||||||
|
const dbImage: string = pulumiConfig.get('dbImage')
|
||||||
|
|| '012345678901.dkr.ecr.eu-west-1.amazonaws.com/cache/dockerHub/library/postgres:16.8';
|
||||||
|
const dbPort: number = pulumiConfig.getNumber('dbPort') || 5432;
|
||||||
|
const dbName: string = pulumiConfig.get('dbName') || 'postgres';
|
||||||
|
const dbUser: string = pulumiConfig.get('dbUser') || 'postgres';
|
||||||
|
const dbPassword: string = pulumiConfig.requireSecret('dbPassword');
|
||||||
|
|
||||||
|
const vpc: PulumiOutput<GetVpcResult> = getVpcOutput({ default: true });
|
||||||
|
const subnets: PulumiOutput<GetSubnetsResult> = getSubnetsOutput({
|
||||||
|
region: vpc.region,
|
||||||
|
filters: [{
|
||||||
|
name: 'tag:Name',
|
||||||
|
values: [
|
||||||
|
'private-a',
|
||||||
|
'private-b',
|
||||||
|
'private-c',
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
const ecsCluster: PulumiOutput<GetClusterResult> = getClusterOutput({
|
||||||
|
region: vpc.region,
|
||||||
|
clusterName: 'development',
|
||||||
|
});
|
||||||
|
const ecsExecutionRole: PulumiOutput<GetRoleResult> = getRoleOutput({ name: 'mainApp-dev-ecsExecutionRole' });
|
||||||
|
const ecsTaskRole: PulumiOutput<GetRoleResult> = getRoleOutput({ name: 'mainApp-dev-ecsTaskRole' });
|
||||||
|
|
||||||
|
const vpn_securityGroup: PulumiOutput<GetSecurityGroupResult> = getSecurityGroupOutput({
|
||||||
|
vpcId: vpc.id,
|
||||||
|
name: 'vpn',
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadBalancer: PulumiOutput<GetLoadBalancerResult> = getLoadBalancerOutput({
|
||||||
|
region: vpc.region,
|
||||||
|
name: 'mainApp-dev',
|
||||||
|
});
|
||||||
|
const listener: PulumiOutput<GetListenerResult> = getListenerOutput({
|
||||||
|
loadBalancerArn: loadBalancer.arn,
|
||||||
|
port: 443,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainApp_imageTag: string = 'latest';
|
||||||
|
const mainApp_image: PulumiOutput<GetImageResult> = getImageOutput({
|
||||||
|
repositoryName: 'mainApp',
|
||||||
|
imageTag: mainApp_imageTag,
|
||||||
|
});
|
||||||
|
const mainApp_imageReference: PulumiOutput<string> = mainApp_image.imageUri;
|
||||||
|
|
||||||
|
const commonTags: PulumiInput<{ [key: string]: PulumiInput<string> }> = {
|
||||||
|
Team: 'Infrastructure',
|
||||||
|
Owner: 'infrastructure@example.org',
|
||||||
|
Application: 'MainApp',
|
||||||
|
Component: 'Service',
|
||||||
|
ManagedByPulumi: 'true',
|
||||||
|
PulumiProject: 'mainApp/infra/dev',
|
||||||
|
};
|
||||||
|
|
||||||
|
const dbPassword_secret: Secret = new Secret(
|
||||||
|
'dbPassword',
|
||||||
|
{
|
||||||
|
name: 'mainApp/dev/db/password',
|
||||||
|
description: 'Password',
|
||||||
|
tags: {
|
||||||
|
Application: 'MainApp',
|
||||||
|
Environment: 'Development',
|
||||||
|
Component: 'Service',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ protect: true },
|
||||||
|
);
|
||||||
|
new SecretVersion(
|
||||||
|
'dbPassword',
|
||||||
|
{
|
||||||
|
secretId: dbPassword_secret.id,
|
||||||
|
secretString: dbPassword,
|
||||||
|
versionStages: [
|
||||||
|
'INITIAL',
|
||||||
|
'AWSCURRENT',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ parent: dbPassword_secret },
|
||||||
|
);
|
||||||
|
|
||||||
|
const logGroup: LogGroup = new LogGroup(
|
||||||
|
'mainApp',
|
||||||
|
{
|
||||||
|
name: '/ecs/mainApp/dev',
|
||||||
|
tags: {
|
||||||
|
...commonTags,
|
||||||
|
},
|
||||||
|
|
||||||
|
retentionInDays: 14,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const containerDefinitions: unknown = [
|
||||||
|
{
|
||||||
|
name: 'db',
|
||||||
|
|
||||||
|
essential: true,
|
||||||
|
image: dbImage,
|
||||||
|
environment: [
|
||||||
|
// server config
|
||||||
|
{ name: 'POSTGRES_PORT', value: dbPort.toString() },
|
||||||
|
{ name: 'POSTGRES_DB', value: dbName },
|
||||||
|
{ name: 'POSTGRES_USER', value: dbUser },
|
||||||
|
{ name: 'POSTGRES_PASSWORD', value: dbPassword },
|
||||||
|
// needed by the health check
|
||||||
|
{ name: 'PGPORT', value: dbPort.toString() },
|
||||||
|
],
|
||||||
|
portMappings: [{
|
||||||
|
protocol: 'tcp',
|
||||||
|
appProtocol: 'http',
|
||||||
|
containerPort: dbPort,
|
||||||
|
hostPort: dbPort,
|
||||||
|
}],
|
||||||
|
healthCheck: {
|
||||||
|
command: [ 'CMD-SHELL', "pg_isready -h 'localhost' || exit 1" ],
|
||||||
|
interval: 30,
|
||||||
|
timeout: 5,
|
||||||
|
retries: 2,
|
||||||
|
startPeriod: 15,
|
||||||
|
},
|
||||||
|
logConfiguration: {
|
||||||
|
logDriver: 'awslogs',
|
||||||
|
options: {
|
||||||
|
'awslogs-region': 'eu-west-1',
|
||||||
|
'awslogs-group': logGroup.name,
|
||||||
|
'awslogs-stream-prefix': 'db',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'db-init',
|
||||||
|
|
||||||
|
essential: false, // cannot be essential if others need to depend on it being in the COMPLETE state
|
||||||
|
dependsOn: [
|
||||||
|
{
|
||||||
|
containerName: 'db',
|
||||||
|
condition: 'HEALTHY',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
image: mainApp_imageReference,
|
||||||
|
environment: [
|
||||||
|
{
|
||||||
|
name: 'PGHOST',
|
||||||
|
value: 'localhost', // ecs does *not* resolve container names to ip addresses like docker and k8s do
|
||||||
|
},
|
||||||
|
{ name: 'PGPORT', value: dbPort.toString() },
|
||||||
|
{ name: 'PGDATABASE', value: dbName },
|
||||||
|
{ name: 'PGUSER', value: dbUser },
|
||||||
|
],
|
||||||
|
secrets: [
|
||||||
|
{ name: 'PGPASSWORD', valueFrom: dbPassword_secret.arn },
|
||||||
|
],
|
||||||
|
workingDirectory: '/opt/src',
|
||||||
|
command: [ 'alembic', 'upgrade', 'head' ],
|
||||||
|
logConfiguration: {
|
||||||
|
logDriver: 'awslogs',
|
||||||
|
options: {
|
||||||
|
'awslogs-region': 'eu-west-1',
|
||||||
|
'awslogs-group': logGroup.name,
|
||||||
|
'awslogs-stream-prefix': 'db',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mainApp',
|
||||||
|
|
||||||
|
essential: true,
|
||||||
|
dependsOn: [{
|
||||||
|
containerName: 'db-init',
|
||||||
|
condition: 'COMPLETE',
|
||||||
|
}],
|
||||||
|
image: mainApp_imageReference,
|
||||||
|
environment: [
|
||||||
|
{
|
||||||
|
name: 'PGHOST',
|
||||||
|
value: 'localhost', // ecs does *not* resolve container names to ip addresses like docker and k8s do
|
||||||
|
},
|
||||||
|
{ name: 'PGPORT', value: dbPort.toString() },
|
||||||
|
{ name: 'PGDATABASE', value: dbName },
|
||||||
|
{ name: 'PGUSER', value: dbUser },
|
||||||
|
],
|
||||||
|
secrets: [
|
||||||
|
{ name: 'PGPASSWORD', valueFrom: dbPassword_secret.arn },
|
||||||
|
],
|
||||||
|
healthCheck: {
|
||||||
|
command: [ 'CMD-SHELL', 'curl -f http://localhost:8080/ || exit 1' ],
|
||||||
|
interval: 30,
|
||||||
|
timeout: 5,
|
||||||
|
retries: 2,
|
||||||
|
startPeriod: 15,
|
||||||
|
},
|
||||||
|
portMappings: [{
|
||||||
|
protocol: 'tcp',
|
||||||
|
appProtocol: 'http',
|
||||||
|
containerPort: 8080,
|
||||||
|
hostPort: 8080,
|
||||||
|
}],
|
||||||
|
logConfiguration: {
|
||||||
|
logDriver: 'awslogs',
|
||||||
|
options: {
|
||||||
|
'awslogs-region': 'eu-west-1',
|
||||||
|
'awslogs-group': logGroup.name,
|
||||||
|
'awslogs-stream-prefix': 'mainApp',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const taskDefinition: TaskDefinition = new TaskDefinition(
|
||||||
|
'mainApp',
|
||||||
|
{
|
||||||
|
family: 'mainApp',
|
||||||
|
tags: {
|
||||||
|
...commonTags,
|
||||||
|
},
|
||||||
|
|
||||||
|
executionRoleArn: ecsExecutionRole.arn,
|
||||||
|
taskRoleArn: ecsTaskRole.arn,
|
||||||
|
containerDefinitions: pulumiJsonStringify(containerDefinitions),
|
||||||
|
cpu: '2048',
|
||||||
|
memory: '4096',
|
||||||
|
networkMode: 'awsvpc',
|
||||||
|
requiresCompatibilities: [
|
||||||
|
'FARGATE',
|
||||||
|
],
|
||||||
|
runtimePlatform: {
|
||||||
|
cpuArchitecture: 'X86_64',
|
||||||
|
operatingSystemFamily: 'LINUX',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const securityGroup: SecurityGroup = new SecurityGroup(
|
||||||
|
'mainApp',
|
||||||
|
{
|
||||||
|
name: 'mainApp-dev',
|
||||||
|
description: 'Controls the network perimeter for MainApp in development',
|
||||||
|
tags: {
|
||||||
|
...commonTags,
|
||||||
|
Name: 'MainApp Dev',
|
||||||
|
},
|
||||||
|
|
||||||
|
vpcId: vpc.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
new SecurityGroupEgressRule(
|
||||||
|
'mainApp-allowAll:ipv4',
|
||||||
|
{
|
||||||
|
securityGroupId: securityGroup.id,
|
||||||
|
description: 'All IPv4 connections',
|
||||||
|
tags: {
|
||||||
|
Name: 'All IPv4',
|
||||||
|
},
|
||||||
|
|
||||||
|
cidrIpv4: '0.0.0.0/0',
|
||||||
|
ipProtocol: '-1',
|
||||||
|
},
|
||||||
|
{ parent: securityGroup },
|
||||||
|
);
|
||||||
|
new SecurityGroupEgressRule(
|
||||||
|
'mainApp-allowAll:ipv6',
|
||||||
|
{
|
||||||
|
securityGroupId: securityGroup.id,
|
||||||
|
description: 'All IPv6 connections',
|
||||||
|
tags: {
|
||||||
|
Name: 'All IPv6',
|
||||||
|
},
|
||||||
|
|
||||||
|
cidrIpv6: '::/0',
|
||||||
|
ipProtocol: '-1',
|
||||||
|
},
|
||||||
|
{ parent: securityGroup },
|
||||||
|
);
|
||||||
|
new SecurityGroupIngressRule(
|
||||||
|
'mainApp-internalTraffic',
|
||||||
|
{
|
||||||
|
securityGroupId: securityGroup.id,
|
||||||
|
description: 'Traffic between members of this Security Group',
|
||||||
|
tags: {
|
||||||
|
Name: 'Internal traffic',
|
||||||
|
},
|
||||||
|
|
||||||
|
referencedSecurityGroupId: securityGroup.id,
|
||||||
|
ipProtocol: '-1',
|
||||||
|
},
|
||||||
|
{ parent: securityGroup },
|
||||||
|
);
|
||||||
|
new SecurityGroupIngressRule(
|
||||||
|
'mainApp-vpn',
|
||||||
|
{
|
||||||
|
securityGroupId: securityGroup.id,
|
||||||
|
description: 'Traffic from the VPN',
|
||||||
|
tags: {
|
||||||
|
Name: 'VPN',
|
||||||
|
},
|
||||||
|
|
||||||
|
referencedSecurityGroupId: vpn_securityGroup.id,
|
||||||
|
ipProtocol: '-1',
|
||||||
|
},
|
||||||
|
{ parent: securityGroup },
|
||||||
|
);
|
||||||
|
|
||||||
|
const targetGroup: TargetGroup = new TargetGroup(
|
||||||
|
'mainApp',
|
||||||
|
{
|
||||||
|
name: 'MainApp',
|
||||||
|
tags: {
|
||||||
|
...commonTags,
|
||||||
|
},
|
||||||
|
|
||||||
|
vpcId: vpc.id,
|
||||||
|
ipAddressType: 'ipv4',
|
||||||
|
targetType: 'ip',
|
||||||
|
protocol: 'HTTP',
|
||||||
|
protocolVersion: 'HTTP2',
|
||||||
|
port: 80,
|
||||||
|
healthCheck: {
|
||||||
|
healthyThreshold: 5,
|
||||||
|
matcher: '200',
|
||||||
|
path: '/',
|
||||||
|
timeout: 5,
|
||||||
|
unhealthyThreshold: 2,
|
||||||
|
},
|
||||||
|
loadBalancingAlgorithmType: 'round_robin',
|
||||||
|
loadBalancingCrossZoneEnabled: 'use_load_balancer_configuration',
|
||||||
|
stickiness: {
|
||||||
|
enabled: true,
|
||||||
|
type: 'lb_cookie',
|
||||||
|
},
|
||||||
|
deregistrationDelay: 30,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
new ListenerRule(
|
||||||
|
'mainApp',
|
||||||
|
{
|
||||||
|
tags: {
|
||||||
|
...commonTags,
|
||||||
|
Name: 'MainApp',
|
||||||
|
},
|
||||||
|
|
||||||
|
listenerArn: listener.arn,
|
||||||
|
actions: [{
|
||||||
|
type: 'forward',
|
||||||
|
targetGroupArn: targetGroup.arn,
|
||||||
|
}],
|
||||||
|
conditions: [{
|
||||||
|
hostHeader: {
|
||||||
|
values: [
|
||||||
|
'main-app.dev.example.org',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
new Service(
|
||||||
|
'mainApp',
|
||||||
|
{
|
||||||
|
name: 'mainApp',
|
||||||
|
tags: {
|
||||||
|
...commonTags,
|
||||||
|
},
|
||||||
|
|
||||||
|
cluster: ecsCluster.arn,
|
||||||
|
taskDefinition: pulumiInterpolate`${taskDefinition.family}:${taskDefinition.revision}`,
|
||||||
|
capacityProviderStrategies: [{
|
||||||
|
capacityProvider: 'FARGATE_SPOT',
|
||||||
|
weight: 1,
|
||||||
|
}],
|
||||||
|
platformVersion: 'LATEST',
|
||||||
|
desiredCount: 1,
|
||||||
|
healthCheckGracePeriodSeconds: 5,
|
||||||
|
loadBalancers: [{
|
||||||
|
containerName: 'mainApp',
|
||||||
|
containerPort: 9000,
|
||||||
|
targetGroupArn: targetGroup.arn,
|
||||||
|
}],
|
||||||
|
networkConfiguration: {
|
||||||
|
subnets: subnets.ids,
|
||||||
|
securityGroups: [
|
||||||
|
securityGroup.id,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
forceNewDeployment: true,
|
||||||
|
deploymentCircuitBreaker: {
|
||||||
|
enable: true,
|
||||||
|
rollback: true,
|
||||||
|
},
|
||||||
|
enableEcsManagedTags: true,
|
||||||
|
propagateTags: 'SERVICE',
|
||||||
|
enableExecuteCommand: true,
|
||||||
|
waitForSteadyState: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user