-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add AWS implementation and IaC #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
ffefa93
a0b9565
f92f770
03e01a1
e02a6e2
5dc72ca
9fd53b1
c784466
ef2c47a
46378bf
4e9c392
630ded4
a0d6c50
70df2cf
728f589
3f8db04
4dc35d0
c94f5d1
da4a502
22dfdd6
afea477
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| { | ||
| "app": "npx ts-node --prefer-ts-exts bin/user-service.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:enableImdsBlockingDeprecatedFeature": false, | ||
| "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, | ||
| "@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, | ||
| "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, | ||
| "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, | ||
| "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, | ||
| "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true, | ||
| "@aws-cdk/core:enableAdditionalMetadataCollection": true, | ||
| "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false, | ||
| "@aws-cdk/aws-s3:setUniqueReplicationRoleName": true, | ||
| "@aws-cdk/aws-events:requireEventBusPolicySid": true, | ||
| "@aws-cdk/core:aspectPrioritiesMutating": true, | ||
| "@aws-cdk/aws-dynamodb:retainTableReplica": true, | ||
| "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true, | ||
| "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true, | ||
| "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true, | ||
| "@aws-cdk/aws-s3:publicAccessBlockedByDefault": true | ||
| } | ||
| } |
| 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' | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,203 @@ | ||
| import { Vpc, SecurityGroup, Port } from "aws-cdk-lib/aws-ec2"; | ||
| import { Construct } from "constructs"; | ||
| import { SharedProps } from "./constructs/shared-props"; | ||
| import { | ||
| Cluster, | ||
| ContainerImage, | ||
| CpuArchitecture, | ||
| FargateService, | ||
| FargateTaskDefinition, | ||
| FirelensLogRouterType, | ||
| LogDriver, | ||
| LogDrivers, | ||
| OperatingSystemFamily, | ||
| Protocol, | ||
| } from "aws-cdk-lib/aws-ecs"; | ||
| import { Repository } from "aws-cdk-lib/aws-ecr"; | ||
| import { Topic } from "aws-cdk-lib/aws-sns"; | ||
| import { Queue } from "aws-cdk-lib/aws-sqs"; | ||
| import { | ||
| ApplicationLoadBalancer, | ||
| ApplicationTargetGroup, | ||
| TargetType, | ||
| ApplicationProtocol, | ||
| HealthCheck, | ||
| Protocol as HealthCheckProtocol, | ||
| } from "aws-cdk-lib/aws-elasticloadbalancingv2"; | ||
| import { Duration, CfnOutput } from "aws-cdk-lib"; | ||
| import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns"; | ||
|
|
||
| export class ApiProps { | ||
| sharedProps: SharedProps; | ||
| vpc: Vpc; | ||
| cluster: Cluster; | ||
| } | ||
|
|
||
| export class Api extends Construct { | ||
| stickerClaimedQueue: Queue; | ||
| stickerClaimedDLQ: Queue; | ||
| userRegisteredTopic: Topic; | ||
| constructor(scope: Construct, id: string, props: ApiProps) { | ||
| super(scope, id); | ||
|
|
||
| const ecrRepository = Repository.fromRepositoryName( | ||
| this, | ||
| "UserServiceRepo", | ||
| "stickerlandia-user-management" | ||
| ); | ||
|
|
||
| this.userRegisteredTopic = new Topic(this, "UserRegisteredTopic", { | ||
| topicName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-registered`, | ||
| }); | ||
| this.stickerClaimedDLQ = new Queue(this, "StickerClaimedDLQ", { | ||
| queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-dlq`, | ||
| }); | ||
|
|
||
| //TODO: Add EventBridge rule mapping to subscribe to sticker claimed events published to the shared EventBus. | ||
| this.stickerClaimedQueue = new Queue(this, "StickerClaimedQueue", { | ||
| queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed`, | ||
| deadLetterQueue: { | ||
| queue: this.stickerClaimedDLQ, | ||
| maxReceiveCount: 5, // Messages will be sent to DLQ after 5 failed attempts | ||
| }, | ||
| }); | ||
|
|
||
| const applicationTaskDef = new FargateTaskDefinition( | ||
| this, | ||
| "UserServiceTaskDef", | ||
| { | ||
| runtimePlatform: { | ||
| operatingSystemFamily: OperatingSystemFamily.LINUX, | ||
| cpuArchitecture: CpuArchitecture.ARM64, | ||
| }, | ||
| cpu: 256, | ||
| memoryLimitMiB: 512, | ||
| } | ||
| ); | ||
|
|
||
| const containerPort = 8080; | ||
|
|
||
| const container = applicationTaskDef.addContainer("UserServiceContainer", { | ||
| image: ContainerImage.fromEcrRepository( | ||
| ecrRepository, | ||
| props.sharedProps.version | ||
| ), | ||
| portMappings: [ | ||
| { | ||
| containerPort: containerPort, | ||
| protocol: Protocol.TCP, | ||
| }, | ||
| ], | ||
| environment: { | ||
| ConnectionStrings__messaging: "", | ||
| ConnectionStrings__database: props.sharedProps.connectionString, | ||
| Aws__UserRegisteredTopicArn: this.userRegisteredTopic.topicArn, | ||
| Aws__StickerClaimedQueueUrl: this.stickerClaimedQueue.queueUrl, | ||
| Aws__StickerClaimedDLQUrl: this.stickerClaimedDLQ.queueUrl, | ||
| DRIVING: "ASPNET", | ||
| DRIVEN: "AWS", | ||
| DISABLE_SSL: "true", | ||
| }, | ||
| logging: LogDrivers.firelens({ | ||
| options: { | ||
| Name: "datadog", | ||
| Host: "http-intake.logs.datadoghq.eu", | ||
| TLS: "on", | ||
| dd_service: props.sharedProps.serviceName, | ||
| dd_source: "aspnet", | ||
| dd_message_key: "log", | ||
| dd_tags: `project:${props.sharedProps.serviceName}`, | ||
| provider: "ecs", | ||
| apikey: props.sharedProps.datadog.apiKey, | ||
| }, | ||
| }), | ||
| }); | ||
| container.addDockerLabel( | ||
|
jeastham1993 marked this conversation as resolved.
|
||
| "com.datadoghq.tags.env", | ||
| props.sharedProps.environment | ||
| ); | ||
| container.addDockerLabel( | ||
| "com.datadoghq.tags.service", | ||
| props.sharedProps.serviceName | ||
| ); | ||
| container.addDockerLabel( | ||
| "com.datadoghq.tags.version", | ||
| props.sharedProps.version | ||
| ); | ||
|
|
||
| container.addPortMappings({ | ||
| containerPort: containerPort, | ||
| protocol: Protocol.TCP, | ||
| }); | ||
|
|
||
| applicationTaskDef.addContainer("datadog-agent", { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's going to be interesting here is, once we deploy more than one service to ECS-fargate we're likely going to want a single
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the moment, the only deployment method for Fargate is per task. The team are working on that.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see why you couldn't just deploy the agent ordinarily and point at it, the same as in any other containerised environment? Does some part of the auto-instrumentation or magical environment discovery not work? |
||
| image: ContainerImage.fromRegistry("public.ecr.aws/datadog/agent:latest"), | ||
| portMappings: [ | ||
| { | ||
| containerPort: 8125, | ||
| protocol: Protocol.UDP, // Dogstatsd port | ||
| }, | ||
| { | ||
| containerPort: 8126, | ||
| protocol: Protocol.TCP, // APM port | ||
| }, | ||
| ], | ||
| containerName: "datadog-agent", | ||
| environment: { | ||
| DD_API_KEY: props.sharedProps.datadog.apiKey, | ||
| DD_SITE: props.sharedProps.datadog.site, | ||
| DD_APM_ENABLED: "true", | ||
| DD_LOGS_ENABLED: "true", | ||
| ECS_FARGATE: "true", | ||
| DD_APM_NON_LOCAL_TRAFFIC: "true", | ||
| DD_DOGSTATSD_NON_LOCAL_TRAFFIC: "true", | ||
| DD_APM_IGNORE_RESOURCES: `(GET) /api/users/v1/health`, | ||
| }, | ||
| }); | ||
| applicationTaskDef.addFirelensLogRouter("firelens-router", { | ||
| essential: true, | ||
| image: ContainerImage.fromRegistry("amazon/aws-for-fluent-bit:stable"), | ||
| firelensConfig: { | ||
| type: FirelensLogRouterType.FLUENTBIT, | ||
| options: { | ||
| enableECSLogMetadata: true, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| this.userRegisteredTopic.grantPublish(applicationTaskDef.taskRole); | ||
| this.stickerClaimedQueue.grantSendMessages(applicationTaskDef.taskRole); | ||
| this.stickerClaimedDLQ.grantSendMessages(applicationTaskDef.taskRole); | ||
| this.stickerClaimedQueue.grantConsumeMessages(applicationTaskDef.taskRole); | ||
| this.stickerClaimedDLQ.grantConsumeMessages(applicationTaskDef.taskRole); | ||
|
|
||
| // TODO: move this to a shared infra project to allow one ALB across multiple services | ||
| const service = new ApplicationLoadBalancedFargateService( | ||
|
jeastham1993 marked this conversation as resolved.
|
||
| this, | ||
| "UserServiceFargateService", | ||
| { | ||
| cluster: props.cluster, | ||
| taskDefinition: applicationTaskDef, | ||
| assignPublicIp: false, | ||
| publicLoadBalancer: true, | ||
| desiredCount: 1, | ||
| loadBalancerName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service-lb`, | ||
| serviceName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service`, | ||
| listenerPort: 80, | ||
| protocol: ApplicationProtocol.HTTP, | ||
| healthCheckGracePeriod: Duration.minutes(2), | ||
| circuitBreaker: { | ||
| rollback: true, | ||
| enable: true, | ||
| }, | ||
| } | ||
| ); | ||
| service.targetGroup.configureHealthCheck({ | ||
| path: "/api/users/v1/health", | ||
| interval: Duration.seconds(60), | ||
| timeout: Duration.seconds(5), | ||
| enabled: true, | ||
| healthyHttpCodes: "200-499", | ||
| }); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to ask how we're doing queue/topic ownership given the stacks are going to be broken down by service!
So the queue exists here - and when we add the topic on the award-service side, we'll add a subscription here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the point of integration between services should be Amazon EventBridge. So we'd have a shared event bus deployed as part of some shared infra, with the name/ARN stored in SSM. In the respective CDK stacks we can then pull down a reference to that event bus to define rules. Left it out for the moment, can add when we come to add the proper AWS deployments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we likely don't need SNS, eh?