-
Notifications
You must be signed in to change notification settings - Fork 2
add ec2 instance with pgbouncer #39
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
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
054a4e5
add ec2 instance with pgbouncer
hrodmn 6d561ce
switch to a ubuntu image
hrodmn 108e6f6
try new approach for startup script
hrodmn 7282e61
do not write separate script, just execute it
hrodmn a6d4293
fix pgbouncer.ini
hrodmn 1cb1e67
fix permissions
hrodmn a7def34
set up cloudwatch and healthcheck for pgbouncer
hrodmn 7f2f4f6
switch to session pooling mode
hrodmn b2a5919
use hash to force instance replacement
hrodmn 2f1dff3
run init script on each boot
hrodmn 84e4c76
switch it back to transaction mode
hrodmn 8017a38
move startup script to external file
hrodmn 07e655c
upgrade to t3.micro
hrodmn d2989f0
don't use file descriptor
hrodmn d4709a0
create log file
hrodmn 590f167
fine tune pgbouncer settings
hrodmn 7b8cbff
wait for dpkg lock
hrodmn 08273d9
make wget quieter
hrodmn b99dccb
simplify pgbouncer ingress rules
hrodmn 74d14ae
Upgrade to eoapi-cdk==7.3.0
hrodmn 5d21572
try granting permissions instead of an explicit security group
hrodmn 893ff50
Set pgbouncer maxConnections dynamically based on the RDS instance size
hrodmn 10b0c63
fix pgbouncer check, clean up setup script
hrodmn d95fe7c
re-instate cloudwatch write permissions
hrodmn e0d1dd2
make dbInstanceType a config parameter
hrodmn 5485819
fix up default pgBouncerConfig
hrodmn 7a3aff0
add DB_INSTANCE_TYPE to env in deploy.yml
hrodmn e874466
make sure /var/run/pgbouncer directory is created on each boot
hrodmn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,227 @@ | ||
| import { | ||
| aws_ec2 as ec2, | ||
| aws_iam as iam, | ||
| aws_secretsmanager as secretsmanager, | ||
| Stack, | ||
| } from "aws-cdk-lib"; | ||
| import { Construct } from "constructs"; | ||
|
|
||
| import * as fs from "fs"; | ||
| import * as path from "path"; | ||
|
|
||
| // used to populate pgbouncer config: | ||
| // see https://www.pgbouncer.org/config.html for details | ||
| export interface PgBouncerConfigProps { | ||
| poolMode?: "transaction" | "session" | "statement"; | ||
| maxClientConn?: number; | ||
| defaultPoolSize?: number; | ||
| minPoolSize?: number; | ||
| reservePoolSize?: number; | ||
| reservePoolTimeout?: number; | ||
| maxDbConnections?: number; | ||
| maxUserConnections?: number; | ||
| } | ||
|
|
||
| export interface PgBouncerProps { | ||
| /** | ||
| * Name for the pgbouncer instance | ||
| */ | ||
| instanceName: string; | ||
|
|
||
| /** | ||
| * VPC to deploy PgBouncer into | ||
| */ | ||
| vpc: ec2.IVpc; | ||
|
|
||
| /** | ||
| * The RDS instance to connect to | ||
| */ | ||
| database: { | ||
| instanceType: ec2.InstanceType; | ||
| connections: ec2.Connections; | ||
| secret: secretsmanager.ISecret; | ||
| }; | ||
|
|
||
| /** | ||
| * Whether to deploy in public subnet | ||
| * @default false | ||
| */ | ||
| usePublicSubnet?: boolean; | ||
|
|
||
| /** | ||
| * Instance type for PgBouncer | ||
| * @default t3.micro | ||
| */ | ||
| instanceType?: ec2.InstanceType; | ||
|
|
||
| /** | ||
| * PgBouncer configuration options | ||
| */ | ||
| pgBouncerConfig?: PgBouncerConfigProps; | ||
| } | ||
|
|
||
| export class PgBouncer extends Construct { | ||
| public readonly instance: ec2.Instance; | ||
| public readonly endpoint: string; | ||
|
|
||
| // The max_connections parameter in PgBouncer determines the maximum number of | ||
| // connections to open on the actual database instance. We want that number to | ||
| // be slightly smaller than the actual max_connections value on the RDS instance | ||
| // so we perform this calculation. | ||
|
|
||
| // TODO: move this to eoapi-cdk where we already have a complete map of instance | ||
| // type and memory | ||
| private readonly instanceMemoryMapMb: Record<string, number> = { | ||
| "t3.micro": 1024, | ||
| "t3.small": 2048, | ||
| "t3.medium": 4096, | ||
| }; | ||
|
|
||
| private calculateMaxConnections(dbInstanceType: ec2.InstanceType): number { | ||
| const memoryMb = this.instanceMemoryMapMb[dbInstanceType.toString()]; | ||
| if (!memoryMb) { | ||
| throw new Error( | ||
| `Unsupported instance type: ${dbInstanceType.toString()}`, | ||
| ); | ||
| } | ||
|
|
||
| // RDS calculates the available memory as the total instance memory minus some | ||
| // constant for OS overhead | ||
| const memoryInBytes = (memoryMb - 300) * 1024 ** 2; | ||
|
|
||
| // The default max_connections setting follows this formula: | ||
| return Math.min(Math.round(memoryInBytes / 9531392), 5000); | ||
| } | ||
|
|
||
| private getDefaultConfig( | ||
| dbInstanceType: ec2.InstanceType, | ||
| ): Required<PgBouncerConfigProps> { | ||
| // calculate approximate max_connections setting for this RDS instance type | ||
| const maxConnections = this.calculateMaxConnections(dbInstanceType); | ||
|
|
||
| // maxDbConnections (and maxUserConnections) are the only settings that need | ||
| // to be responsive to the database size/max_connections setting | ||
| return { | ||
| poolMode: "transaction", | ||
| maxClientConn: 1000, | ||
| defaultPoolSize: 5, | ||
| minPoolSize: 0, | ||
| reservePoolSize: 5, | ||
| reservePoolTimeout: 5, | ||
| maxDbConnections: maxConnections - 10, | ||
| maxUserConnections: maxConnections - 10, | ||
| }; | ||
| } | ||
|
|
||
| constructor(scope: Construct, id: string, props: PgBouncerProps) { | ||
| super(scope, id); | ||
|
|
||
| // Set defaults for optional props | ||
| const defaultInstanceType = ec2.InstanceType.of( | ||
| ec2.InstanceClass.T3, | ||
| ec2.InstanceSize.MICRO, | ||
| ); | ||
|
|
||
| const instanceType = props.instanceType ?? defaultInstanceType; | ||
| const defaultConfig = this.getDefaultConfig(props.database.instanceType); | ||
|
|
||
| // Merge provided config with defaults | ||
| const pgBouncerConfig: Required<PgBouncerConfigProps> = { | ||
| ...defaultConfig, | ||
| ...props.pgBouncerConfig, | ||
| }; | ||
|
|
||
| // Create role for PgBouncer instance to enable writing to CloudWatch | ||
| const role = new iam.Role(this, "InstanceRole", { | ||
| assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"), | ||
| managedPolicies: [ | ||
| iam.ManagedPolicy.fromAwsManagedPolicyName( | ||
| "AmazonSSMManagedInstanceCore", | ||
| ), | ||
| iam.ManagedPolicy.fromAwsManagedPolicyName( | ||
| "CloudWatchAgentServerPolicy", | ||
| ), | ||
| ], | ||
| }); | ||
|
|
||
| // Add policy to allow reading RDS credentials from Secrets Manager | ||
| role.addToPolicy( | ||
| new iam.PolicyStatement({ | ||
| actions: ["secretsmanager:GetSecretValue"], | ||
| resources: [props.database.secret.secretArn], | ||
| }), | ||
| ); | ||
|
|
||
| // Create PgBouncer instance | ||
| this.instance = new ec2.Instance(this, "Instance", { | ||
| vpc: props.vpc, | ||
| vpcSubnets: { | ||
| subnetType: props.usePublicSubnet | ||
| ? ec2.SubnetType.PUBLIC | ||
| : ec2.SubnetType.PRIVATE_WITH_EGRESS, | ||
| }, | ||
| instanceType, | ||
| instanceName: props.instanceName, | ||
| machineImage: ec2.MachineImage.fromSsmParameter( | ||
| "/aws/service/canonical/ubuntu/server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id", | ||
| { os: ec2.OperatingSystemType.LINUX }, | ||
| ), | ||
| role, | ||
| blockDevices: [ | ||
| { | ||
| deviceName: "/dev/xvda", | ||
| volume: ec2.BlockDeviceVolume.ebs(20, { | ||
| volumeType: ec2.EbsDeviceVolumeType.GP3, | ||
| encrypted: true, | ||
| deleteOnTermination: true, | ||
| }), | ||
| }, | ||
| ], | ||
| userData: this.loadUserDataScript(pgBouncerConfig, props.database), | ||
| userDataCausesReplacement: true, | ||
| }); | ||
|
|
||
| // Allow PgBouncer to connect to RDS | ||
| props.database.connections.allowFrom( | ||
| this.instance, | ||
| ec2.Port.tcp(5432), | ||
| "Allow PgBouncer to connect to RDS", | ||
| ); | ||
|
|
||
| // Set the endpoint | ||
| this.endpoint = this.instance.instancePrivateIp; | ||
| } | ||
|
|
||
| private loadUserDataScript( | ||
| pgBouncerConfig: Required<NonNullable<PgBouncerProps["pgBouncerConfig"]>>, | ||
| database: { secret: secretsmanager.ISecret }, | ||
| ): ec2.UserData { | ||
| const userDataScript = ec2.UserData.forLinux(); | ||
|
|
||
| // Set environment variables with configuration parameters | ||
| userDataScript.addCommands( | ||
| 'export SECRET_ARN="' + database.secret.secretArn + '"', | ||
| 'export REGION="' + Stack.of(this).region + '"', | ||
| 'export POOL_MODE="' + pgBouncerConfig.poolMode + '"', | ||
| 'export MAX_CLIENT_CONN="' + pgBouncerConfig.maxClientConn + '"', | ||
| 'export DEFAULT_POOL_SIZE="' + pgBouncerConfig.defaultPoolSize + '"', | ||
| 'export MIN_POOL_SIZE="' + pgBouncerConfig.minPoolSize + '"', | ||
| 'export RESERVE_POOL_SIZE="' + pgBouncerConfig.reservePoolSize + '"', | ||
| 'export RESERVE_POOL_TIMEOUT="' + | ||
| pgBouncerConfig.reservePoolTimeout + | ||
| '"', | ||
| 'export MAX_DB_CONNECTIONS="' + pgBouncerConfig.maxDbConnections + '"', | ||
| 'export MAX_USER_CONNECTIONS="' + | ||
| pgBouncerConfig.maxUserConnections + | ||
| '"', | ||
| ); | ||
|
|
||
| // Load the startup script | ||
| const scriptPath = path.join(__dirname, "./scripts/pgbouncer-setup.sh"); | ||
| let script = fs.readFileSync(scriptPath, "utf8"); | ||
|
|
||
| userDataScript.addCommands(script); | ||
|
|
||
| return userDataScript; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.