Building Slack Bot App on AWS ECS Fargate with AWS CDK

Arda Batuhan Demir
Cloud Türkiye
Published in
11 min readAug 16, 2023

Selamlar herkese, bu yazıda son günlerde oldukça popüler hale gelen ve developer-friendly bir yaklaşım olan SlackOps süreci çerçevesinde Amazon Web Service’deki kaynaklarımızı yönetebilmemize yardımcı olabilecek bir Slack Bot App’ini AWS CDK yardımı ile inşa ediyor olacağız.

İlk olarak Slack Bot App’i inşa edebilmemiz için, Slack Workspace’inde bir Custom App yaratmamız ve sonrasında bot app ile alakalı olan secretları ve gerekli olan credentialları tanımlayarak başlayabiliriz.

https://api.slack.com/start/apps

Slack Bot App’imiz için bir isim belirledikten sonra, hangi workspace’de yer alacağını seçiyoruz ve ardından Custom App’i yaratıyoruz.

Yazacağımız Custom App ile Slack arasındaki iletişimin sağlanması için gerekli olan secretlar bot-token, app-token ve signing-secret.

NOT: Bu yaratacağımız credentialları local’de kullandıktan sonra Secret Manager üzerinde store edebilir, veya bir .env file da tutup bir S3 Bucket’ından ilgili Fargate Task’ı ayağa kalkarken CDK ile okutabiliriz.

AWS CDK ile Elastic Container Service, Task Definition, Application Load Balancer, Elastic Container Registry yaratma

Projemizin root dizininde cdk folder’ı altında AWS’de kullanacağımız resource’ların stacklerini yaratıyoruz.

demo-slack-app-service-ecr-stack.ts

import { Stack, StackProps, CfnOutput, aws_ecr } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class DemoSlackAppServiceECRStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const serviceECR = new aws_ecr.Repository(this, 'DemoSlackAppServiceEcrStack', {
repositoryName: 'demo-slack-service',
imageTagMutability: aws_ecr.TagMutability.IMMUTABLE
});
new CfnOutput(this, 'DemoSlackAppServiceEcrStackECRRepoARN', {
value: serviceECR.repositoryArn,
exportName: 'DemoSlackAppServiceEcrStackRepoARN'
});
new CfnOutput(this, 'DemoSlackAppServiceEcrStackRepoName', {
value: serviceECR.repositoryName,
exportName: 'DemoSlackAppServiceEcrStackRepoName'
});
}
}
import {
Stack,
StackProps,
Fn,
aws_ec2,
aws_ecs,
aws_ecr,
aws_iam,
aws_logs,
aws_s3,
aws_elasticloadbalancingv2 as elbv2
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { getAppEnv, getConfig } from '../config';
export class DemoSlackServiceEcsFargateStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const appEnv = getAppEnv();
const config = getConfig(scope, appEnv);
const IMAGE_TAG = process.env.IMAGE_TAG;
if (!IMAGE_TAG) {
throw new Error('Please provide an IMAGE_TAG value to start CDK process.');
}
const vpc = aws_ec2.Vpc.fromVpcAttributes(this, 'Vpc', {
vpcId: config.vpc.id,
availabilityZones: config.vpc.availabilityZones,
publicSubnetIds: config.vpc.publicSubnetIds,
privateSubnetIds: config.vpc.privateSubnetIds
});
const clusterSg = aws_ec2.SecurityGroup.fromSecurityGroupId(
this,
'ServicesEcsClusterSg',
Fn.importValue(`DemoSlackServicesECSClusterSgId-${appEnv}`)
);
const cluster = aws_ecs.Cluster.fromClusterAttributes(this, 'DemoSlackServicesEcsCluster', {
clusterName: Fn.importValue(`DemoSlackServicesECSClusterName-${appEnv}`),
vpc,
securityGroups: [clusterSg]
});
const ecrRepo = aws_ecr.Repository.fromRepositoryAttributes(this, 'DemoSlackAppServiceECRRepo', {
repositoryArn: Fn.importValue('DemoSlackAppServiceECRRepoARN'),
repositoryName: Fn.importValue('DemoSlackAppServiceECRRepoName')
});
const serviceImage = aws_ecs.EcrImage.fromEcrRepository(ecrRepo, IMAGE_TAG); const environmentBucket = aws_s3.Bucket.fromBucketAttributes(
this,
'DemoSlackServiceENVBucket',
{
bucketArn: Fn.importValue('DemoSlackServiceENVBucketARN'),
bucketName: Fn.importValue('DemoSlackServiceENVBucketName')
}
);
const taskDef = new aws_ecs.FargateTaskDefinition(this, 'DemoSlackServiceFargateTaskDef', {
memoryLimitMiB: config.fargateService.taskDefmemoryLimitMiB,
cpu: config.fargateService.taskDefCpu,
family: `SlackBuilderServiceFargateTaskDef-${appEnv}`
});
taskDef.addToExecutionRolePolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
resources: ['*'],
actions: ['s3:*']
})
);
taskDef.addToTaskRolePolicy(
new aws_iam.PolicyStatement({
effect: aws_iam.Effect.ALLOW,
resources: ['*'],
actions: ['*']
})
);
const logGroup = new aws_logs.LogGroup(this, 'DemoSlackServiceFargateContainerLogGroup', {
logGroupName: `/ecs/demo-slack-service-${appEnv}-logs`,
retention: appEnv === 'prod' ? aws_logs.RetentionDays.INFINITE : aws_logs.RetentionDays.FIVE_DAYS
});
const container = taskDef.addContainer('DemoSlackServiceFargateContainer', {
containerName: 'demo-slack-service',
image: serviceImage,
memoryReservationMiB: config.fargateService.taskDefmemoryLimitMiB,
cpu: config.fargateService.taskDefCpu,
environmentFiles: [aws_ecs.EnvironmentFile.fromBucket(environmentBucket, `slack-builder-service/${appEnv}.env`)],
logging: new aws_ecs.AwsLogDriver({
logGroup,
streamPrefix: 'ecs'
})
});
container.addPortMappings({
containerPort: 8080,
protocol: aws_ecs.Protocol.TCP
});
const serviceSg = new aws_ec2.SecurityGroup(this, 'DemoSlackServiceSecurityGroup', {
vpc,
allowAllOutbound: true,
securityGroupName: `demo-slack-service-sg-${appEnv}`,
description: `Demo Slack service ${appEnv} security group`
});
const service = new aws_ecs.FargateService(this, 'DemoSlackServiceECSService', {
cluster,
serviceName: 'demo-slack-service',
taskDefinition: taskDef,
desiredCount: config.fargateService.desiredCount,
securityGroups: [serviceSg]
});
// Service Task Auto Scale
const serviceAutoScaleTaskCount = service.autoScaleTaskCount({
minCapacity: config.fargateService.minCapacity,
maxCapacity: config.fargateService.maxCapacity
});
serviceAutoScaleTaskCount.scaleOnCpuUtilization('DemoSlackServiceECSServiceTaskAutoScaleCpu', {
targetUtilizationPercent: 70
});
serviceAutoScaleTaskCount.scaleOnMemoryUtilization('DemoSlackServiceECSServiceTaskAutoScaleMemory', {
targetUtilizationPercent: 70
});
// Application Load Balancer
const applicationLoadBalancerSg = new aws_ec2.SecurityGroup(this, 'DemoSlackServiceAlbSg', {
vpc,
allowAllOutbound: true,
securityGroupName: `slack-builder-service-alb-sg-${appEnv}`,
description: `Slack subscription service ALB sg ${appEnv}`
});
applicationLoadBalancerSg.addIngressRule(
aws_ec2.Peer.anyIpv4(),
aws_ec2.Port.tcp(80),
'allow http connection from anywhere'
);
applicationLoadBalancerSg.addIngressRule(
aws_ec2.Peer.anyIpv4(),
aws_ec2.Port.tcp(443),
'allow https connection from anywhere'
);
const applicationLoadBalancer = new elbv2.ApplicationLoadBalancer(this, 'DemoSlackServiceALB', {
vpc,
internetFacing: true,
securityGroup: applicationLoadBalancerSg,
loadBalancerName: `slack-builder-srv-alb-${appEnv}`
});
const serviceTargetGroup = new elbv2.ApplicationTargetGroup(this, 'DemoSlackServiceTargetGroup', {
protocol: elbv2.ApplicationProtocol.HTTP,
targetGroupName: `slack-builder-srv-${appEnv}-tg`,
targets: [service],
vpc: vpc,
healthCheck: {
enabled: true,
path: '/health-check'
}
});
const httpListenerAction = elbv2.ListenerAction.forward([serviceTargetGroup]); const httpListener = new elbv2.ApplicationListener(this, 'httpListener', {
loadBalancer: applicationLoadBalancer,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP
});
httpListener.addAction('httpListenerAction', {
action: httpListenerAction
});
}
}

NestJS ile yazacağımız app’imizin folder yapısı şu şekilde olabilir

Ortam bazlı değişken ve secretları config’de, önceden belirlediğiniz parametrik instance type’ları ve instance ami’ları da constants’da geliştireceğimiz slack uygulaması ile alakalı business domain’i ise domain’in altındaki slack folder’ında tutabiliriz.

Slack Bot App içerisinde kullanacağımız değişkenler ve credentialları parametrik olarak kod içerisinde by-pass edebilmek için config’i şu şekilde yazabiliriz.

export default (): IConfig => ({
appEnv: process.env.APP_ENV || 'dev',
port: parseInt(process.env.PORT, 10) || 3000,
awsRegion: process.env.AWS_REGION || 'eu-central-1',
signingSecret: process.env.SIGNING_SECRET || '',
botToken: process.env.BOT_TOKEN || '',
appToken: process.env.APP_TOKEN || '',
subnetId: process.env.SUBNET_ID || 'subnet-xxxxxxxxxxxx',
sgGroups: process.env.SG_GROUPS || 'sg-yyyyyyyyyy',
channelName: process.env.CHANNEL_NAME || '#demo-slack-bot'
});
export interface IConfig {
appEnv: string;
port: number;
awsRegion: string;
signingSecret: string;
botToken: string;
appToken: string;
subnetId: string;
sgGroups: string;
channelName: string;
}

Decorator yardımı ile SlackController ve SlackServisimizi tanımlıyoruz

import { Module } from '@nestjs/common';
import { SlackController } from './slack.controller';
import { SlackService } from './slack.service';
@Module({
controllers: [SlackController],
providers: [SlackService],
exports: [SlackService]
})
export class SlackModule {}

Slack Servisimiz için

// src/ec2.service.ts
import { Injectable } from '@nestjs/common';
import { EC2, config } from 'aws-sdk';
import { Controller, Post, Body } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
// Set your AWS credentials@Injectable()
export class SlackService {
private readonly ec2: EC2;
constructor(private readonly configService: ConfigService) {
this.ec2 = new EC2();
config.update({
region: this.configService.get<string>('awsRegion') // Change this to your desired AWS region
});
}
async createEC2Instance(imageId: string, instanceType: string, userName: string): Promise<string> {
const instanceParams = {
KeyName: 'demo-key',
ImageId: imageId,
InstanceType: instanceType,
MinCount: 1,
TagSpecifications: [
{
ResourceType: 'instance',
Tags: [
{
Key: 'Name',
Value: `${userName}-${Math.floor(Math.random() * 9999)}`
},
{
Key: 'env',
Value: 'demo'
},
{
Key: 'launch-time',
Value: (+new Date()).toString()
}
]
}
],
MaxCount: 1,
NetworkInterfaces: [
{
DeviceIndex: 0,
SubnetId: 'subnet-xxxxxxx',
Groups: ['sg-xxxxxxxx']
}
]
};
try {
const ec2Instance = await this.ec2.runInstances(instanceParams).promise();
return ec2Instance.Instances[0].InstanceId!;
} catch (error) {
console.error('Error creating EC2 instance:', error);
throw new Error('Failed to create EC2 instance.');
}
}
async listInstancesWithTag(tagKey: string, tagValue: string): Promise<EC2.Instance[]> {
const params: EC2.DescribeInstancesRequest = {
Filters: [
{
Name: `tag:${tagKey}`,
Values: [tagValue]
},
{
Name: 'instance-state-code',
Values: ['16', '0']
}
]
};
const response = await this.ec2.describeInstances(params).promise();
const instances: EC2.Instance[] = [];
for (const reservation of response.Reservations || []) {
instances.push(...(reservation.Instances || []));
}
return instances;
}
async stopInstance(instanceId: string): Promise<void> {
const params: EC2.StopInstancesRequest = {
InstanceIds: [instanceId]
};
await this.ec2.stopInstances(params).promise();
}
}
import { Controller, Post, Body } from '@nestjs/common';
import { Action, App, ExpressReceiver, SectionBlock } from '@slack/bolt';
import { SlackService } from './slack.service';
import { INSTANCE_TYPES, AMI_TYPES } from '../../constants';
import { ConfigService } from '@nestjs/config';
@Controller('slack')
export class SlackController {
private readonly app: App;
constructor(private readonly slackService: SlackService, private readonly configService: ConfigService) {
const appToken = this.configService.get<string>('appToken');
this.app = new App({
token: this.configService.get<string>('botToken'),
socketMode: true,
appToken
});
this.setupSlackCommands();
}
private setupSlackCommands() {
this.app.command('/create-ec2', async ({ ack, body, client, logger }) => {
await ack();
try {
// Call views.open with the built-in client
const result = await client.views.open({
// Pass a valid trigger_id within 3 seconds of receiving it
trigger_id: body.trigger_id,
// View payload
view: {
type: 'modal',
// View identifier
callback_id: 'view_1',
title: {
type: 'plain_text',
text: 'EC2 Create'
},
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*<EC2 Type>*\nSelect an EC2 Type.'
},
accessory: {
type: 'static_select',
placeholder: {
type: 'plain_text',
emoji: true,
text: 'Manage'
},
options: INSTANCE_TYPES.map((instanceType) => ({
text: {
type: 'plain_text',
text: instanceType
},
value: instanceType
})),
action_id: 'instance'
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*<Select AMI IDs>*\nSelect an AMI Id'
},
accessory: {
type: 'static_select',
placeholder: {
type: 'plain_text',
emoji: true,
text: 'Manage'
},
options: Object.keys(AMI_TYPES).map((ami) => ({
text: {
type: 'plain_text',
text: AMI_TYPES[ami]
},
value: ami
})),
action_id: 'ami'
}
}
],
submit: {
type: 'plain_text',
text: 'Submit'
}
}
});
logger.info(result);
} catch (error) {
logger.error(error);
}
});
this.app.action('instance', async ({ ack }) => {
await ack();
});
this.app.action('ami', async ({ ack }) => {
await ack();
});
// Handle a view_submission request
this.app.view('view_1', async ({ ack, body, view, client, logger }) => {
// Acknowledge the view_submission request
await ack();
// Do whatever you want with the input data - here we're saving it to a DB then sending the user a verification of their submission const val = view['state']['values'];
const user = body['user']['username'];
const channelName = this.configService.get<string>('channelName'); console.info('view', JSON.stringify(view));
console.info('body', JSON.stringify(body));
// Message to send user
let msg = '';
console.info('val', JSON.stringify(val));
const instance_block: SectionBlock = body.view.blocks[0] as SectionBlock;
const instance_block_accessory: Action = instance_block.accessory as Action;
const ami_block: SectionBlock = body.view.blocks[1] as SectionBlock;
const ami_block_accessory: Action = ami_block.accessory as Action;
const instance_type =
val[instance_block.block_id][instance_block_accessory.action_id]['selected_option']['value'];
const ami_type = val[ami_block.block_id][ami_block_accessory.action_id]['selected_option']['value'];
const userName = body['user']['username'];
const currentDate = new Date(); const options: Intl.DateTimeFormatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
timeZoneName: 'short'
};
const formattedDate = new Intl.DateTimeFormat('en-US', options).format(currentDate); // Message the user
try {
const ec2InstanceId = await this.slackService.createEC2Instance(ami_type, instance_type, userName);
console.log('instance created');
msg = `EC2 instance created successfully!\nInstance ID: ${ec2InstanceId}\nUser: <@${body.user.id}>\n Launch-time:${formattedDate}\n Instance-type:${instance_type}`;
await client.chat.postMessage({
text: msg,
channel: `${channelName}`
});
} catch (error) {
logger.error(error);
}
});
this.app.action('ami_selected', async ({ ack, body, context }) => {
try {
// Acknowledge the AMI selection event
await ack();
// Type assertion for the action payload to include 'actions'
const actionPayload = body as any;
const selectedAmi = actionPayload.actions[0]?.selected_option?.value;
const instanceType = actionPayload.actions[0]?.selected_option?.group?.text;
const userName = body['user']['username'];
// Check if selectedAmi and instanceType are defined before proceeding
if (selectedAmi && instanceType) {
// Create the EC2 instance using the selected instance type and AMI
const ec2InstanceId = await this.slackService.createEC2Instance(selectedAmi, instanceType, userName);
// Notify the user that the EC2 instance creation was successful
await this.app.client.chat.postMessage({
token: context.botToken,
channel: body.user.id,
text: `EC2 instance created successfully! Instance ID: ${ec2InstanceId}\nUser:${userName}`
});
} else {
console.error('Error: Selected AMI or instance type is missing.');
console.log('Selected AMI:', selectedAmi);
console.log('Instance Type:', instanceType);
}
} catch (error) {
console.error('Error handling AMI selection:', error);
}
});
this.app.command('/aws-help', async ({ ack, body, client, logger }) => {
await ack();
// Replace hello with the message
try {
const helpResult = client.views.open({
trigger_id: body.trigger_id,
view: {
type: 'modal',
callback_id: 'help_view',
title: {
type: 'plain_text',
text: 'Help'
},
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: 'Hey there 👋 to help you create and manage tasks in Slack.\nThere are two ways to quickly create tasks:'
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*1️⃣ Use the `/create-ec2` command*.It would be able to see creating the`test` instance. Try it out by using the `/create-ec2` command in this channel.'
}
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*2️⃣ Use the `/list-instances` command*.It would be able to see stop the`test`instance. Try it out by using the `/stop-ec2` command in this channel.'
}
}
]
}
});
} catch (error) {
console.log('err');
console.error(error);
}
});
this.app.action('stop_instance', async ({ ack, body, action, client }) => {
await ack();
const actionObject: any = JSON.parse(JSON.stringify(action));
const channelName = this.configService.get<string>('channelName');
console.info('action', actionObject);
await this.slackService.stopInstance(actionObject?.value);
await client.chat.postMessage({
text: 'Instance stopped',
channel: `${channelName}`
});
});
this.app.command('/list-instances', async ({ ack, body, client, logger }) => {
await ack();
let msg = '';
let listMsg = '';
const channelName = this.configService.get<string>('channelName');
try {
const instances = await this.slackService.listInstancesWithTag('env', 'test');
msg = 'No instances with the "env:test" tag found.';
if (instances.length === 0) {
await client.chat.postMessage({
text: msg,
channel: `${channelName}`
});
}
const instanceInfo = instances.map((instance) => `Instance ID: ${instance.InstanceId}`).join('\n');
listMsg = `Instances with "env:demo" tag:\n${instanceInfo}`;
const blocks: any = [
{
type: 'section',
text: {
type: 'plain_text',
emoji: true,
text: 'Looks a list of running instances:'
}
},
{
type: 'divider'
}
];
blocks.push(
...instances.map((instance) => ({
type: 'section',
text: {
type: 'mrkdwn',
text: `*${instance.Tags.find((tag) => tag.Key === 'Name')?.Value}*\n Launch Time: ${new Date(
Number(instance.Tags.find((tag) => tag.Key === 'launch-time').Value)
)}`
},
accessory: {
type: 'button',
text: {
type: 'plain_text',
emoji: true,
text: 'Stop'
},
style: 'danger',
value: instance.InstanceId,
action_id: 'stop_instance'
}
}))
);
await client.chat.postMessage({
text: listMsg,
channel: `${channelName}`,
blocks
/*[
{
type: 'section',
text: {
type: 'mrkdwn',
text: '*Today - 4:30-5pm*\nEveryone is available: @iris, @zelda'
},
accessory: {
type: 'button',
text: {
type: 'plain_text',
emoji: true,
text: 'Choose'
},
value: 'click_me_123',
action_id: 'today'
}
},
]*/
});
} catch (error) {
console.error('Error listing instances:', error);
}
});
this.app.command('/health-check', async ({ ack, respond }) => {
// Acknowledge the command request
await ack();
// Respond with a health status
const healthStatus = {
status: 'ok'
};
respond({
text: 'App is healthy',
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `App Status: ${healthStatus.status}`
}
}
]
});
});
}
async start() {
await this.app.start(this.configService.get<number>('appPort'));
console.log('⚡️ Slack app is running!');
}
}

Constant değerlerimiz için

const AMI_TYPES = {
"ami-0434fc627dfce8b27": "Deep Learning AMI GPU PyTorch 1.13.1 (Ubuntu 20.04) 20230215",
"ami-0819ccbfa0d3a8750": "Deep Learning AMI GPU PyTorch 1.12.1 (Ubuntu 20.04) 20221019 (64-bit (x86))",
"ami-02ee8d5b35a4a74b2": "Deep Learning AMI GPU PyTorch 1.13.1 (Ubuntu 20.04) 20221226 (64-bit (x86))",
"ami-0a39ed2b865d65970": "Deep Learning AMI GPU PyTorch 2.0.0 (Ubuntu 20.04) 20230401 (64-bit (x86))" ,
"ami-00970f57473724c10": "Amazon Linux 2023 AMI (64-bit (x86))",
"ami-03f65b8614a860c29": "Ubuntu Server 22.04 LTS (HVM), SSD Volume Type (64-bit (x86))",
} as const;
export { AMI_TYPES };
const INSTANCE_TYPES = [
'r6g.medium',
'm7gd.16xlarge',
'c6i.4xlarge',
'm7g.4xlarge',
'm4.4xlarge',
'f1.2xlarge',
't4g.nano',
't1.micro',
'c6a.48xlarge',
'g5.4xlarge',
't2.2xlarge',
'g3.16xlarge',
't3a.2xlarge',
'm6i.large',
'r5a.large',
'c7gn.large',
'c5d.12xlarge',
't4g.medium',
'm5.12xlarge',
'a1.medium',
't3.2xlarge',
'm5ad.4xlarge',
'm6a.metal',
'c5a.xlarge',
't2.large',
't3a.medium',
'g4ad.2xlarge',
'c1.medium',
'd3.xlarge',
'c5a.24xlarge',
'c5.xlarge',
'g4dn.12xlarge',
'c6gn.12xlarge',
'r5b.metal',
't3.xlarge',
'r5a.4xlarge',
'i4g.4xlarge',
'r3.2xlarge',
't2.micro',
'r5.metal',
't3.large',
't3a.small',
'g5.24xlarge',
't3.medium',
'm4.large',
'm6g.large',
'h1.16xlarge',
'g5g.8xlarge',
't3.nano',
't4g.small',
'r5.16xlarge',
'c4.2xlarge',
'inf1.2xlarge',
't3.micro',
'a1.metal',
'i3.large',
't3a.large',
'c3.2xlarge',
'i3.xlarge',
't4g.micro',
'c4.xlarge',
'g4dn.xlarge',
'm5.16xlarge',
'm4.10xlarge',
't2.medium',
'i2.4xlarge',
'm3.2xlarge',
't3.small',
'm3.large',
'im4gn.large',
'c5ad.8xlarge',
'c6gd.medium',
'c7gn.4xlarge',
'm5.large',
'm7g.metal',
't2.xlarge',
'r6g.xlarge',
'r5b.8xlarge',
'c7g.12xlarge',
'r6g.2xlarge',
't2.small',
'm6a.xlarge',
't3a.nano',
'g5.48xlarge',
'm6id.large',
't2.nano',
'm1.large',
'g2.8xlarge',
'm1.small',
't3a.micro',
'm6g.medium',
'c6a.large',
'c6id.4xlarge',
'm1.medium',
'm2.xlarge'
] as const;
export { INSTANCE_TYPES };

Dockerfile için

FROM node:16.13.0-alpine
WORKDIR /usr/src/appEXPOSE 3000COPY package*.json ./RUN npm installCOPY . .RUN npm run buildCMD ["node", "dist/main"]

CDK Deployment’ı için;

IMAGE_TAG=latest APP_ENV=demo cdk deploy --app "npx ts-node bin/ecs.ts" DemoSlackServiceECSFargateStack-demo
APP_ENV=demo cdk deploy --app "npx ts-node bin/ecr.ts" DemoSlackAppServiceEcrStack-demo

Son olarak CDK sizin için arka tarafta bir CloudFormation Stack’i oluşturuyor ve Stack’de yaşayacağnız herhangi bir problemde CloudFormation üzerinden kontrol edebilirsiniz

Son olarak AWS Console üzerinden ECS Clusterımızda yer alan Service’imizin içerisinden Taskımızın loglarına bakabiliriz.

/create-ec2 command’ini çağırarak uygulamamıza erişebiliriz

Instance Type seçimi için

AMI seçimi için

Yaratacağımız instance için gerekli instance type ve AMI ID’yi belirleyip submit ettikten sonra, geriye response mesajını şu şekilde alabiliriz.

/list-instances komutunu çağırarak açık olan instance’larımızın listesini görebilir ve istediğimizi kapatabiliriz.

AWS Console’dan test amaçlı kaldırdığımız ve durduğumuz makinaları da görebiliriz.

Keyifli okumalar.

--

--

Arda Batuhan Demir
Cloud Türkiye

5x AWS Cert* | Senior DevOps Engineer | Cloud Architect | AWS Community Builder