in src/keycloak.ts [499:620]
constructor(scope: cdk.Construct, id: string, props: ContainerServiceProps) {
super(scope, id);
const vpc = props.vpc;
const cluster = new ecs.Cluster(this, 'Cluster', { vpc });
const taskRole = new iam.Role(this, 'TaskRole', {
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal('ecs.amazonaws.com'),
new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
),
});
const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDef', {
cpu: 4096,
memoryLimitMiB: 30720,
executionRole: taskRole,
});
const logGroup = new logs.LogGroup(this, 'LogGroup', {
retention: logs.RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
const kc = taskDefinition.addContainer('keycloak', {
image: ecs.ContainerImage.fromRegistry(this.getKeyCloakDockerImageUri(props.keycloakVersion.version)),
environment: Object.assign({
DB_ADDR: props.database.clusterEndpointHostname,
DB_DATABASE: 'keycloak',
DB_PORT: '3306',
DB_USER: 'admin',
DB_VENDOR: 'mysql',
// KEYCLOAK_LOGLEVEL: 'DEBUG',
JDBC_PARAMS: 'useSSL=false',
JGROUPS_DISCOVERY_PROTOCOL: 'JDBC_PING',
// We don't need to specify `initialize_sql` string into `JGROUPS_DISCOVERY_PROPERTIES` property,
// because the default `initialize_sql` is compatible with MySQL. (See: https://github.com/belaban/JGroups/blob/master/src/org/jgroups/protocols/JDBC_PING.java#L55-L60)
// But you need to specify `initialize_sql` for PostgreSQL, because `varbinary` schema is not supported. (See: https://github.com/keycloak/keycloak-containers/blob/d4ce446dde3026f89f66fa86b58c2d0d6132ce4d/docker-compose-examples/keycloak-postgres-jdbc-ping.yml#L49)
// JGROUPS_DISCOVERY_PROPERTIES: '',
}, props.env),
secrets: {
DB_PASSWORD: ecs.Secret.fromSecretsManager(props.database.secret, 'password'),
KEYCLOAK_USER: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'username'),
KEYCLOAK_PASSWORD: ecs.Secret.fromSecretsManager(props.keycloakSecret, 'password'),
},
logging: ecs.LogDrivers.awsLogs({
streamPrefix: 'keycloak',
logGroup,
}),
});
kc.addPortMappings(
{ containerPort: 8443 }, // HTTPS web port
{ containerPort: 7600 }, // jgroups-tcp
{ containerPort: 57600 }, // jgroups-tcp-fd
{ containerPort: 55200, protocol: ecs.Protocol.UDP }, // jgroups-udp
{ containerPort: 54200, protocol: ecs.Protocol.UDP }, // jgroups-udp-fd
);
// we need extra privileges to fetch keycloak docker images from China mirror site
taskDefinition.executionRole?.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryReadOnly'));
this.service = new ecs.FargateService(this, 'Service', {
cluster,
taskDefinition,
circuitBreaker: props.circuitBreaker ? { rollback: true } : undefined,
desiredCount: props.nodeCount ?? 2,
healthCheckGracePeriod: cdk.Duration.seconds(120),
});
// we need to allow traffic from the same secret group for keycloak cluster with jdbc_ping
this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(7600), 'kc jgroups-tcp');
this.service.connections.allowFrom(this.service.connections, ec2.Port.tcp(57600), 'kc jgroups-tcp-fd');
this.service.connections.allowFrom(this.service.connections, ec2.Port.udp(55200), 'kc jgroups-udp');
this.service.connections.allowFrom(this.service.connections, ec2.Port.udp(54200), 'kc jgroups-udp-fd');
if (props.autoScaleTask) {
const minCapacity = props.autoScaleTask.min ?? props.nodeCount ?? 2;
const scaling = this.service.autoScaleTaskCount({
minCapacity,
maxCapacity: props.autoScaleTask.max ?? minCapacity+5,
});
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: props.autoScaleTask.targetCpuUtilization ?? 75,
});
};
const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
vpc,
vpcSubnets: props.publicSubnets,
internetFacing: true,
});
printOutput(this, 'EndpointURL', `https://${alb.loadBalancerDnsName}`);
const listener = alb.addListener('HttpsListener', {
protocol: elbv2.ApplicationProtocol.HTTPS,
certificates: [{ certificateArn: props.certificate.certificateArn }],
});
listener.addTargets('ECSTarget', {
targets: [this.service],
// set slow_start.duration_seconds to 60
// see https://docs.aws.amazon.com/cli/latest/reference/elbv2/modify-target-group-attributes.html
slowStart: cdk.Duration.seconds(60),
stickinessCookieDuration: props.stickinessCookieDuration ?? cdk.Duration.days(1),
port: 8443,
protocol: elbv2.ApplicationProtocol.HTTPS,
});
// allow task execution role to read the secrets
props.database.secret.grantRead(taskDefinition.executionRole!);
props.keycloakSecret.grantRead(taskDefinition.executionRole!);
// allow ecs task connect to database
props.database.connections.allowDefaultPortFrom(this.service);
// create a bastion host
if (props.bastion === true) {
const bast = new ec2.BastionHostLinux(this, 'Bast', {
vpc,
instanceType: new ec2.InstanceType('m5.large'),
});
props.database.connections.allowDefaultPortFrom(bast);
}
}