-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use CDK for ecs sample app setup (#77)
*Issue #, if available:* *Description of changes:* We use CDK to replace the bash script to set up the ECS sample app. In addition, the following changes are made: 1. Create a separate VPC for hosting the sample app to improve network isolation. 2. Place ECS services into a private subnet with egress. 3. Place RDS database into a private isolated subnet and use Secrets Manager to manage the access to the database. 4. Modify the cloudwatch agent config to replace the Dependencies with proper service names. 5. Use private ECR instead of public ECR to obtain images (README is updated accordingly). 6. Fix issues caused by regions not being specified in customers and visits services. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
- Loading branch information
1 parent
9c200fb
commit 5044467
Showing
37 changed files
with
5,660 additions
and
1,797 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.js | ||
!jest.config.js | ||
*.d.ts | ||
node_modules | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.ts | ||
!*.d.ts | ||
|
||
# CDK asset staging directory | ||
.cdk.staging | ||
cdk.out |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Ignore artifacts: | ||
build | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
{ | ||
"app": "npx ts-node --prefer-ts-exts lib/app.ts", | ||
"watch": { | ||
"include": ["**"], | ||
"exclude": [ | ||
"README.md", | ||
"cdk*.json", | ||
"**/*.d.ts", | ||
"**/*.js", | ||
"tsconfig.json", | ||
"package*.json", | ||
"yarn.lock", | ||
"node_modules", | ||
"test" | ||
] | ||
}, | ||
"context": { | ||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true, | ||
"@aws-cdk/core:checkSecretUsage": true, | ||
"@aws-cdk/core:target-partitions": ["aws", "aws-cn"], | ||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, | ||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, | ||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, | ||
"@aws-cdk/aws-iam:minimizePolicies": true, | ||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, | ||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, | ||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, | ||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true, | ||
"@aws-cdk/core:enablePartitionLiterals": true, | ||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, | ||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, | ||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, | ||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, | ||
"@aws-cdk/aws-route53-patters:useCertificate": true, | ||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false, | ||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, | ||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, | ||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, | ||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, | ||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, | ||
"@aws-cdk/aws-redshift:columnId": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, | ||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, | ||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, | ||
"@aws-cdk/aws-kms:aliasNameRef": true, | ||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, | ||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true, | ||
"@aws-cdk/aws-efs:denyAnonymousAccess": true, | ||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, | ||
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, | ||
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, | ||
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, | ||
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, | ||
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, | ||
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, | ||
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, | ||
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, | ||
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, | ||
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, | ||
"@aws-cdk/aws-eks:nodegroupNameAttribute": true, | ||
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, | ||
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, | ||
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, | ||
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, | ||
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, | ||
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, | ||
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, | ||
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, | ||
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, | ||
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, | ||
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, | ||
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
#!/usr/bin/env bash | ||
|
||
# Script to synthesize, deploy, or destroy AWS CDK stacks with stack dependencies | ||
# Usage: ./ecs-cdk.sh <action> | ||
# Example for deploy: ./ecs-cdk.sh deploy | ||
# Example for destroy: ./ecs-cdk.sh destroy | ||
# Example to only synth: ./ecs-cdk.sh synth | ||
|
||
ACTION=$1 | ||
|
||
# Check for action parameter | ||
if [[ -z "$ACTION" ]]; then | ||
echo "Usage: $0 <action>" | ||
echo "action can be 'synth', 'deploy', or 'destroy'" | ||
exit 1 | ||
fi | ||
|
||
|
||
# Run CDK synth once for all stacks | ||
if [[ "$ACTION" == "synth" || "$ACTION" == "deploy" ]]; then | ||
echo "Running npm install" | ||
npm install | ||
|
||
echo "Running CDK bootstrap" | ||
cdk bootstrap | ||
|
||
rm -rf cdk.out | ||
echo "Running CDK synth for all stacks..." | ||
if cdk synth; then | ||
echo "CDK synth successful!" | ||
if [[ "$ACTION" == "synth" ]]; then | ||
exit 0 | ||
fi | ||
else | ||
echo "CDK synth failed. Exiting." | ||
exit 1 | ||
fi | ||
fi | ||
|
||
# Deploy or destroy all stacks in the app | ||
if [[ "$ACTION" == "deploy" ]]; then | ||
echo "Starting CDK deployment for all stacks in the app" | ||
if cdk deploy --all --require-approval never; then | ||
echo "Deployment successful for all stacks in the app" | ||
else | ||
echo "Deployment failed. Attempting to clean up resources by destroying all stacks..." | ||
cdk destroy --all --force --verbose | ||
fi | ||
elif [[ "$ACTION" == "destroy" ]]; then | ||
echo "Starting CDK destroy for all stacks in the app" | ||
cdk destroy --all --force --verbose | ||
echo "Destroy complete for all stacks in the app" | ||
else | ||
echo "Invalid action: $ACTION. Please use 'synth', 'deploy', or 'destroy'." | ||
exit 1 | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module.exports = { | ||
testEnvironment: 'node', | ||
roots: ['<rootDir>/test'], | ||
testMatch: ['**/*.test.ts'], | ||
transform: { | ||
'^.+\\.tsx?$': 'ts-jest', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import * as assert from 'assert'; | ||
import { App } from 'aws-cdk-lib'; | ||
|
||
import { getLatestAdotJavaTag, getLatestAdotPythonTag } from './utils'; | ||
import { EcsClusterStack } from './stacks/ecsStack'; | ||
import { IamRolesStack } from './stacks/iamRolesStack'; | ||
import { PetClinicNetworkStack } from './stacks/petClinicNetworkStack'; | ||
import { ServiceDiscoveryStack } from './stacks/servicediscoveryStack'; | ||
import { LogStack } from './stacks/logStack'; | ||
import { LoadBalancerStack } from './stacks/loadbalancerStack'; | ||
import { RdsDatabaseStack } from './stacks/databaseStack'; | ||
|
||
class ApplicationSignalsECSDemo { | ||
private readonly app: App; | ||
|
||
constructor() { | ||
this.app = new App(); | ||
this.runApp(); | ||
} | ||
|
||
public async runApp(): Promise<void> { | ||
const [adotJavaImageTag, adotPythonImageTag] = await Promise.all([ | ||
getLatestAdotJavaTag(), | ||
getLatestAdotPythonTag(), | ||
]); | ||
|
||
assert(adotJavaImageTag !== '', 'ADOT Java Image Tag is empty'); | ||
assert(adotPythonImageTag !== '', 'ADOT Python Image Tag is empty'); | ||
|
||
const petClinicNetworkStack = new PetClinicNetworkStack(this.app, 'PetClinicNetworkStack'); | ||
|
||
const logStack = new LogStack(this.app, 'LogStack'); | ||
|
||
const loadbalancerStack = new LoadBalancerStack(this.app, 'LoadBalancerStack', { | ||
vpc: petClinicNetworkStack.vpc, | ||
securityGroup: petClinicNetworkStack.albSecurityGroup, | ||
}); | ||
|
||
const rdsDatabaseStack = new RdsDatabaseStack(this.app, 'RdsDatabaseStack', { | ||
vpc: petClinicNetworkStack.vpc, | ||
rdsSecurityGroup: petClinicNetworkStack.rdsSecurityGroup, | ||
}); | ||
|
||
const iamRolesStack = new IamRolesStack(this.app, 'IamRolesStack'); | ||
|
||
// Grant ecsTaskRole access to database | ||
rdsDatabaseStack.dbSecret.grantRead(iamRolesStack.ecsTaskRole); | ||
rdsDatabaseStack.dbSecret.grantWrite(iamRolesStack.ecsTaskRole); | ||
|
||
const serviceDiscoveryStack = new ServiceDiscoveryStack(this.app, 'ServiceDiscoveryStack', { | ||
vpc: petClinicNetworkStack.vpc, | ||
}); | ||
|
||
new EcsClusterStack(this.app, 'EcsClusterStack', { | ||
vpc: petClinicNetworkStack.vpc, | ||
securityGroups: [petClinicNetworkStack.ecsSecurityGroup], | ||
ecsTaskRole: iamRolesStack.ecsTaskRole, | ||
ecsTaskExecutionRole: iamRolesStack.ecsTaskExecutionRole, | ||
serviceDiscoveryStack: serviceDiscoveryStack, | ||
logStack: logStack, | ||
adotPythonImageTag: adotPythonImageTag, | ||
adotJavaImageTag: adotJavaImageTag, | ||
dbSecret: rdsDatabaseStack.dbSecret, | ||
dbInstanceEndpointAddress: rdsDatabaseStack.rdsInstance.dbInstanceEndpointAddress, | ||
loadBalancerTargetGroup: loadbalancerStack.targetGroup, | ||
loadBalancerDnsName: loadbalancerStack.loadBalancer.loadBalancerDnsName, | ||
}); | ||
|
||
this.app.synth(); | ||
} | ||
} | ||
|
||
new ApplicationSignalsECSDemo(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { Construct } from 'constructs'; | ||
import { | ||
DatabaseInstance, | ||
SubnetGroup, | ||
Credentials, | ||
DatabaseInstanceEngine, | ||
PostgresEngineVersion, | ||
StorageType, | ||
} from 'aws-cdk-lib/aws-rds'; | ||
import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; | ||
import { StackProps, Stack, CfnOutput, Duration, RemovalPolicy, Tags } from 'aws-cdk-lib'; | ||
import { Vpc, SecurityGroup, SubnetType, InstanceType, InstanceClass, InstanceSize } from 'aws-cdk-lib/aws-ec2'; | ||
|
||
interface RdsDatabaseStackProps extends StackProps { | ||
readonly vpc: Vpc; | ||
readonly rdsSecurityGroup: SecurityGroup; | ||
} | ||
|
||
export class RdsDatabaseStack extends Stack { | ||
private readonly vpc: Vpc; | ||
private readonly DB_INSTANCE_IDENTIFIER: string = 'petclinic-python'; | ||
public readonly rdsInstance: DatabaseInstance; | ||
public readonly dbSecret: Secret; | ||
|
||
constructor(scope: Construct, id: string, props: RdsDatabaseStackProps) { | ||
super(scope, id, props); | ||
|
||
this.vpc = props.vpc; | ||
|
||
// Create DB Subnet Group | ||
const dbSubnetGroup = new SubnetGroup(this, 'MyDBSubnetGroup', { | ||
vpc: this.vpc, | ||
description: 'Subnet group for RDS', | ||
subnetGroupName: 'my-db-subnet-group', | ||
vpcSubnets: { | ||
subnetType: SubnetType.PRIVATE_ISOLATED, // Ensure private subnets with NAT are used | ||
}, | ||
removalPolicy: RemovalPolicy.DESTROY, | ||
}); | ||
|
||
// Create a Secret for the database credentials | ||
this.dbSecret = new Secret(this, 'DBSecret', { | ||
secretName: 'PetClinicDBCredentials', | ||
generateSecretString: { | ||
secretStringTemplate: JSON.stringify({ username: 'root' }), | ||
generateStringKey: 'password', | ||
excludePunctuation: true, | ||
includeSpace: false, | ||
}, | ||
}); | ||
|
||
// Create database instance | ||
this.rdsInstance = new DatabaseInstance(this, 'MyDatabase', { | ||
vpc: this.vpc, | ||
credentials: Credentials.fromSecret(this.dbSecret), | ||
vpcSubnets: { | ||
subnetType: SubnetType.PRIVATE_ISOLATED, // Ensure private subnets with NAT are used | ||
}, | ||
publiclyAccessible: false, | ||
instanceIdentifier: this.DB_INSTANCE_IDENTIFIER, | ||
instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO), // db.t3.micro | ||
engine: DatabaseInstanceEngine.postgres({ | ||
version: PostgresEngineVersion.VER_14, | ||
}), | ||
allocatedStorage: 20, // 20 GB allocated storage | ||
maxAllocatedStorage: 25, | ||
storageType: StorageType.GP2, | ||
subnetGroup: dbSubnetGroup, | ||
securityGroups: [props.rdsSecurityGroup], | ||
multiAz: false, // Disable Multi-AZ | ||
backupRetention: Duration.days(0), // 0 days backup retention | ||
removalPolicy: RemovalPolicy.DESTROY, // For dev/testing environments | ||
deletionProtection: false, // Disable deletion protection | ||
deleteAutomatedBackups: true, | ||
}); | ||
|
||
Tags.of(this.rdsInstance).add('Name', this.DB_INSTANCE_IDENTIFIER); | ||
|
||
// Output the subnet group name | ||
new CfnOutput(this, 'DBSubnetGroupName', { | ||
value: dbSubnetGroup.subnetGroupName, | ||
}); | ||
} | ||
} |
Oops, something went wrong.