Skip to content
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

Add rds to eks cdk #91

Merged
merged 2 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cdk/eks/bin/eks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NetworkStack } from '../lib/stacks/network-stack';
import { IAMStack } from '../lib/stacks/iam-stack';
import { EksStack } from '../lib/stacks/eks-stack';
import { SloStack } from '../lib/stacks/slo-stack';
import { RdsStack } from '../lib/stacks/rds-stack';
import { SyntheticCanaryStack } from '../lib/stacks/canary-stack';

const app = new App();
Expand All @@ -14,25 +15,35 @@ const enableSlo = app.node.tryGetContext('enableSlo') || false;
const networkStack = new NetworkStack(app, 'AppSignalsEksNetworkStack');
const iamStack = new IAMStack(app, 'AppSignalsEksIamStack');

const rdsStack = new RdsStack(app, 'AppSignalsEksRdsStack', {
vpc: networkStack.vpc,
rdsSecurityGroup: networkStack.rdsSecurityGroup,
})

rdsStack.addDependency(networkStack);

const eksStack = new EksStack(app, 'AppSignalsEksClusterStack', {
vpc: networkStack.vpc,
eksClusterRoleProp: iamStack.eksClusterRoleProp,
eksNodeGroupRoleProp: iamStack.eksNodeGroupRoleProp,
ebsCsiAddonRoleProp: iamStack.ebsCsiAddonRoleProp,
sampleAppRoleProp: iamStack.sampleAppRoleProp,
cloudwatchAddonRoleProp: iamStack.cloudwatchAddonRoleProp,
rdsClusterEndpoint: rdsStack.clusterEndpoint,
rdsSecurityGroupId: networkStack.rdsSecurityGroupId,
});

eksStack.addDependency(networkStack);
eksStack.addDependency(iamStack);
eksStack.addDependency(rdsStack);

const syntheticCanaryStack = new SyntheticCanaryStack(app, 'AppSignalsSyntheticCanaryStack', {
vpc: networkStack.vpc,
albEndpoint: eksStack.ingressExternalIp.value,
syntheticCanaryRoleProp: iamStack.syntheticCanaryRoleProp,
})

syntheticCanaryStack.addDependency(eksStack);
syntheticCanaryStack.addDependency(rdsStack);

// After AppSignal is enabled, it takes up to 10 minutes for the SLO metrics to become available. If this is deployed before the SLO metrics
// are available, it will fail.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,12 @@ spec:
value: 'postgres'
- name: DB_USER
value: 'djangouser'
- name: DB_USER_PASSWORD
value: 'asdfqwer'
- name: DATABASE_PROFILE
value: postgresql
- name: REGION
value: ${REGION}
- name: DB_SERVICE_HOST
value: db.pet-clinic.svc.cluster.local
value: <HOST>
- name: DB_SERVICE_PORT
value: '5432'
# command: ["sh", "-c"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@ spec:
value: 'postgres'
- name: DB_USER
value: 'djangouser'
- name: DB_USER_PASSWORD
value: 'asdfqwer'
- name: DATABASE_PROFILE
value: postgresql
- name: DB_SERVICE_HOST
value: db.pet-clinic.svc.cluster.local
value: <HOST>
- name: DB_SERVICE_PORT
value: '5432'
# command: ["sh", "-c"]
Expand Down
28 changes: 21 additions & 7 deletions cdk/eks/lib/stacks/eks-stack.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as path from 'path';
import { Construct } from 'constructs';
import { StackProps, Stack, CfnJson, CfnWaitConditionHandle, CfnWaitCondition } from 'aws-cdk-lib';
import { Vpc, InstanceType } from 'aws-cdk-lib/aws-ec2';
import { StackProps, Stack, CfnJson, Fn, CfnWaitConditionHandle, CfnWaitCondition } from 'aws-cdk-lib';
import { Vpc, InstanceType, ISecurityGroup,SecurityGroup, Port } from 'aws-cdk-lib/aws-ec2';
import { Role, RoleProps, PolicyStatement, FederatedPrincipal, Effect } from 'aws-cdk-lib/aws-iam';
import { CfnAddon, Cluster, KubernetesManifest, KubernetesVersion, ServiceAccount, KubernetesObjectValue, Nodegroup } from 'aws-cdk-lib/aws-eks';
import { StringParameter } from 'aws-cdk-lib/aws-ssm';
Expand All @@ -15,7 +15,9 @@ interface EksStackProps extends StackProps {
eksNodeGroupRoleProp: RoleProps,
ebsCsiAddonRoleProp: RoleProps,
sampleAppRoleProp: RoleProps,
cloudwatchAddonRoleProp: RoleProps
cloudwatchAddonRoleProp: RoleProps,
rdsClusterEndpoint: string,
rdsSecurityGroupId: string,
}

export class EksStack extends Stack {
Expand Down Expand Up @@ -52,12 +54,20 @@ export class EksStack extends Stack {

// Ingress External Ip
public readonly ingressExternalIp: KubernetesObjectValue;
private readonly rdsSecurityGroup: ISecurityGroup;
private readonly rdsClusterEndpoint: string;

constructor(scope: Construct, id: string, props: EksStackProps) {
super(scope, id, props);

const { vpc, eksClusterRoleProp, eksNodeGroupRoleProp, ebsCsiAddonRoleProp, sampleAppRoleProp, cloudwatchAddonRoleProp } = props;
const { vpc, eksClusterRoleProp, eksNodeGroupRoleProp, ebsCsiAddonRoleProp, sampleAppRoleProp, cloudwatchAddonRoleProp, rdsClusterEndpoint, rdsSecurityGroupId } = props;
this.vpc = vpc;
this.rdsClusterEndpoint = rdsClusterEndpoint;
this.rdsSecurityGroup = SecurityGroup.fromSecurityGroupId(
this,
'ImportedRdsSecurityGroup',
rdsSecurityGroupId
);

// The IAM roles must be created in the EKS stack because some of the roles need to be given federated principals, and this cannot be done if the role is imported
this.eksClusterRole = new Role(this, 'EksClusterRole', eksClusterRoleProp);
Expand All @@ -66,7 +76,6 @@ export class EksStack extends Stack {
this.sampleAppRole = new Role(this, 'SampleAppRole', sampleAppRoleProp);
this.cloudwatchAddonRole = new Role(this, 'CloduwatchAddonRole', cloudwatchAddonRoleProp);


// Create EKS Cluster
this.cluster = this.createEksCluster();
// Add the Cloduwatch Addon
Expand Down Expand Up @@ -121,7 +130,12 @@ export class EksStack extends Stack {
maxSize: 5,
releaseVersion: nodeGroupAmiReleaseVersion,
});


this.rdsSecurityGroup.addIngressRule(
cluster.connections.securityGroups[0],
Port.tcp(5432),
'Allow EKS to connect to RDS'
)
return cluster;
}

Expand Down Expand Up @@ -172,7 +186,7 @@ export class EksStack extends Stack {
manifestFiles.forEach((file) => {
const filePath = path.join(manifestPath, file);
const yamlFile = readYamlFile(filePath);
const transformedYamlFile = transformYaml(yamlFile, this.account, this.region, this.SAMPLE_APP_NAMESPACE, this.ingressExternalIp?.value);
const transformedYamlFile = transformYaml(yamlFile, this.account, this.region, this.SAMPLE_APP_NAMESPACE, this.ingressExternalIp?.value, this.rdsClusterEndpoint);
const manifest = this.cluster.addManifest(transformNameToId(file), ...transformedYamlFile);

dependencies.forEach((dependnecy) => {
Expand Down
1 change: 1 addition & 0 deletions cdk/eks/lib/stacks/iam-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class IAMStack extends Stack {
ManagedPolicy.fromAwsManagedPolicyName('AmazonDynamoDBFullAccess'),
ManagedPolicy.fromAwsManagedPolicyName('AmazonBedrockFullAccess'),
ManagedPolicy.fromAwsManagedPolicyName('AWSXrayFullAccess'),
ManagedPolicy.fromAwsManagedPolicyName('SecretsManagerReadWrite'),
],
};

Expand Down
12 changes: 10 additions & 2 deletions cdk/eks/lib/stacks/network-stack.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Construct } from 'constructs';
import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Vpc, SubnetType } from 'aws-cdk-lib/aws-ec2';
import { PrivateHostedZone } from 'aws-cdk-lib/aws-route53';
import { Vpc, SubnetType, SecurityGroup } from 'aws-cdk-lib/aws-ec2';

export class NetworkStack extends Stack {
// Expose properties for use in other stacks
public readonly vpc: Vpc;
public readonly rdsSecurityGroup: SecurityGroup;
public readonly rdsSecurityGroupId: string;

constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
Expand All @@ -29,6 +30,13 @@ export class NetworkStack extends Stack {
natGateways: 2,
});

this.rdsSecurityGroup = new SecurityGroup(this, 'EksRdsSecurityGroup', {
vpc: this.vpc,
description: 'Allow traffic from EKS',
});

this.rdsSecurityGroupId = this.rdsSecurityGroup.securityGroupId;

// Output the VPC and subnet IDs
new CfnOutput(this, 'PetClinicEksVPCID', {
value: this.vpc.vpcId,
Expand Down
71 changes: 71 additions & 0 deletions cdk/eks/lib/stacks/rds-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { RemovalPolicy, Fn, Duration, CfnOutput } from 'aws-cdk-lib';
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SubnetGroup, DatabaseCluster, DatabaseClusterEngine, AuroraPostgresEngineVersion, Credentials, PerformanceInsightRetention } from 'aws-cdk-lib/aws-rds';
import { InstanceType, InstanceClass, InstanceSize} from 'aws-cdk-lib/aws-ec2';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { Vpc, SecurityGroup } from 'aws-cdk-lib/aws-ec2';

interface RdsStackProps extends StackProps {
vpc: Vpc;
rdsSecurityGroup: SecurityGroup
}

export class RdsStack extends Stack {
public readonly clusterEndpoint: string;
private readonly securityGroup: SecurityGroup;
private readonly rdsSubnetGroupName = 'eks-rds-subnet-group';
private readonly dbClusterIdentifier = 'eks-petclinic-python';
private readonly masterUsername = 'djangouser';
private readonly secretName = 'petclinic-python-dbsecret';
constructor(scope: Construct, id: string, props: RdsStackProps) {
super(scope, id, props);

const { vpc, rdsSecurityGroup } = props;
this.securityGroup = rdsSecurityGroup;

// Create a database subnet group
const dbSubnetGroup = new SubnetGroup(this, 'EksDbSubnetGroup', {
vpc: vpc,
subnetGroupName: this.rdsSubnetGroupName,
description: 'Subnet group for RDS',
removalPolicy: RemovalPolicy.DESTROY,
});

const dbSecret = new Secret(this, 'EksDbSecret', {
secretName: this.secretName,
description: `Randomly generated password for ${this.dbClusterIdentifier} database`,
generateSecretString: {
passwordLength: 10,
excludeCharacters: '/@" ',
},
removalPolicy: RemovalPolicy.DESTROY,
});

// Create an Aurora PostgreSQL cluster
const dbCluster = new DatabaseCluster(this, 'EksRdsCluster', {
engine: DatabaseClusterEngine.auroraPostgres({
version: AuroraPostgresEngineVersion.VER_15_4,
}),
credentials: Credentials.fromUsername(this.masterUsername, {
password: dbSecret.secretValue,
}),
instances: 1,
instanceProps: {
instanceType: InstanceType.of(InstanceClass.BURSTABLE3, InstanceSize.MEDIUM),
vpc,
},
subnetGroup: dbSubnetGroup,
backup: {
retention: Duration.days(1),
},

performanceInsightRetention: PerformanceInsightRetention.LONG_TERM,
removalPolicy: RemovalPolicy.DESTROY,
clusterIdentifier: this.dbClusterIdentifier,
securityGroups: [this.securityGroup],
});

this.clusterEndpoint = dbCluster.clusterEndpoint.socketAddress.split(':')[0];
}
}
6 changes: 4 additions & 2 deletions cdk/eks/lib/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function readYamlFile(filePath: string): Record<string, any>[] {
}

// Function that replaces the namespace and account id placeholder with actual values
export function transformYaml(obj: any, accountId: string, region: string, namespace: string, ingressExternalIp: string): any {
export function transformYaml(obj: any, accountId: string, region: string, namespace: string, ingressExternalIp: string, rdsClusterEndpoint: string): any {
if (typeof obj === 'object' && obj !== null) {
for (const key in obj) {
if (typeof obj[key] === 'string') {
Expand All @@ -37,9 +37,11 @@ export function transformYaml(obj: any, accountId: string, region: string, names
obj[key] = `http://${ingressExternalIp}`;
} else if (obj[key].includes('${REGION}')) {
obj[key] = obj[key].replace('${REGION}', region);
} else if (obj[key].includes('<HOST>')) {
obj[key] = obj[key].replace('<HOST>', rdsClusterEndpoint);
}
} else if (typeof obj[key] === 'object') {
transformYaml(obj[key], accountId, region, namespace, ingressExternalIp);
transformYaml(obj[key], accountId, region, namespace, ingressExternalIp, rdsClusterEndpoint);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions cdk/eks/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.