Skip to content

Commit

Permalink
Merge pull request #4 from nickpettican/infra-as-code
Browse files Browse the repository at this point in the history
Infrastructure as code
  • Loading branch information
nickpettican authored Dec 1, 2024
2 parents e18b829 + 5f4acb8 commit 7ed35ca
Show file tree
Hide file tree
Showing 16 changed files with 5,828 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
cd server && npm run qc;
cd ..;
cd client && npm run qc;
cd client && npm run qc;
cd ..;
cd infra && npm run qc;
8 changes: 8 additions & 0 deletions infra/.gitignore
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
6 changes: 6 additions & 0 deletions infra/.npmignore
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
7 changes: 7 additions & 0 deletions infra/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": false,
"printWidth": 80,
"trailingComma": "none",
"tabWidth": 4
}
235 changes: 235 additions & 0 deletions infra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# Infrastructure Setup

This directory contains the AWS CDK infrastructure code for the Offering Bowl platform. The infrastructure is designed to be cost-effective while maintaining scalability, using AWS free tier resources where possible.

## Architecture Overview

This is the current architecture for stage 1 (prototype).

- **Frontend**: Served from S3 through CloudFront (static website hosting)
- **Backend**: Single t2.micro EC2 instance running two Docker containers for the API
- **Database**: DynamoDB tables with on-demand pricing
- **Load Balancing**: Application Load Balancer (free tier eligible)

## Directory Structure

```
infra/
├── bin/
│ └── app.ts # CDK app entry point
├── lib/
│ ├── constructs/
│ │ └── database.ts # DynamoDB tables construct
│ └── stacks/
│ └── main-stack.ts # Main infrastructure stack
└── README.md
```

## Development Setup

### Prerequisites

1. AWS CLI installed and configured
2. Node.js and npm installed
3. AWS CDK CLI installed (`npm install -g aws-cdk`)
4. Docker installed for local development

### Database Tables

The following DynamoDB tables are defined in `constructs/database.ts`:

- Users (with role-index GSI)
- Settings (with userId-index GSI)
- Profile (with userId-index GSI)
- Activities (with userId-index GSI)
- Contracts (with patronId-monasticId-index GSI)
- Posts (with monasticId-index GSI)
- Receipts (with contractId-index GSI)
- Media

## Testing and Staging

### Development/Testing Workflow

You can safely deploy and tear down the infrastructure for testing:

1. Deploy staging environment:

```bash
cdk deploy StagingStack
```

2. Test the infrastructure

3. Tear down when done:

```bash
cdk destroy StagingStack
```

### Environment Management

The infrastructure supports multiple environments:

- **Staging**: For testing infrastructure changes

- Uses `RemovalPolicy.DESTROY` for easy cleanup
- Same setup as production but allows easy teardown
- Perfect for testing autoscaling configurations

- **Production**: Live environment

- Uses `RemovalPolicy.RETAIN` for data protection
- More restricted security settings
- Changes require careful planning

### Scaling Scenarios

The infrastructure is designed to evolve with your needs:

1. **Initial Setup** (Current)

- Single t2.micro EC2 instance
- Two Docker containers with ALB
- Perfect for prototype/MVP phase

2. **Autoscaling Ready**

- When high usage notifications arrive:
- Infrastructure code for autoscaling is prepared
- Can switch from single EC2 to autoscaling group
- Allows testing autoscaling in staging first

### Migration Process

When ready to implement autoscaling:

1. Test in staging:

- Deploy autoscaling configuration
- Verify scaling behaviors
- Test monitoring and alerts
- Practice rollback procedures

2. Production migration:

- Take snapshot of production EC2 for AMI
- Deploy autoscaling infrastructure
- Gradually move traffic over
- Maintain single EC2 until migration complete

### Monitoring and Alerts

The infrastructure includes CloudWatch alarms for:

- CPU Usage (>80% threshold)
- Request Count (>1000/minute threshold)
- Notifications sent to configured email addresses

This helps identify when to consider scaling up the infrastructure.

Remember to accept the initial SNS subscription email to receive alerts.

## Deployment

### First Time Setup

1. Install dependencies:

```bash
cd infra
npm install
```

2. Bootstrap CDK (first time only):

```bash
cdk bootstrap
```

### Deploying to Production

Deploy the entire stack:

```bash
cdk deploy
```

This will:

1. Create/update all DynamoDB tables
2. Set up the EC2 instance with Docker
3. Configure the Application Load Balancer
4. Set up S3 and CloudFront for the frontend

### Cost Optimization

The infrastructure is designed to use AWS free tier resources:

- EC2: t2.micro instance (750 hours/month free for 12 months)
- Application Load Balancer (750 hours/month free for 12 months)
- DynamoDB: On-demand pricing (minimal costs for low traffic)
- S3/CloudFront: Pay as you go (minimal for small applications)

### Future Scaling

When ready to scale beyond free tier:

1. Migrate to ECS for better container orchestration
2. Add auto-scaling groups for EC2 instances
3. Implement CloudWatch alarms for scaling triggers

## Troubleshooting

### Common Issues

1. DynamoDB Tables not creating locally:

- Check Docker logs: `docker compose logs dynamodb-local`
- Verify port mappings in docker-compose.yml

2. EC2 instance not starting:

- Check instance logs in AWS Console
- Verify security group settings

### Useful Commands

```bash
# List deployed stacks
cdk ls

# Compare deployed stack with current state
cdk diff

# Destroy stack (careful in production!)
cdk destroy

# Check DynamoDB tables locally
aws dynamodb list-tables --endpoint-url http://localhost:8000
```

#### Other useful commands

- `npm run build` compile typescript to js
- `npm run watch` watch for changes and compile
- `npm run test` perform the jest unit tests
- `npx cdk deploy` deploy this stack to your default AWS account/region
- `npx cdk diff` compare deployed stack with current state
- `npx cdk synth` emits the synthesized CloudFormation template

## Security

- EC2 instance role has minimal required permissions
- DynamoDB tables use encryption at rest
- CloudFront uses HTTPS only
- Security groups restrict access appropriately

## Contributing

1. Create a feature branch
2. Make infrastructure changes
3. Run `cdk diff` to verify changes
4. Submit a pull request

For significant infrastructure changes, please update this README and provide context in the PR.
36 changes: 36 additions & 0 deletions infra/bin/infra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { InfraStack } from "../lib/stacks/main-stack";

const app = new cdk.App();

// Development/Staging stack
new InfraStack(app, "StagingStack", {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION
},
stagingConfig: {
removalPolicy: cdk.RemovalPolicy.DESTROY, // For easy cleanup
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T2,
ec2.InstanceSize.MICRO
)
}
});

// Production stack
new InfraStack(app, "ProductionStack", {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "us-east-1"
},
productionConfig: {
removalPolicy: cdk.RemovalPolicy.RETAIN,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T2,
ec2.InstanceSize.MICRO
)
}
});
75 changes: 75 additions & 0 deletions infra/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"app": "npx ts-node --prefer-ts-exts bin/infra.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
}
}
9 changes: 9 additions & 0 deletions infra/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
testEnvironment: "node",
roots: ["<rootDir>/test"],
testMatch: ["**/*.test.ts"],
transform: {
"^.+\\.tsx?$": "ts-jest"
},
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"]
};
Loading

0 comments on commit 7ed35ca

Please sign in to comment.