SiamCafe · Blog
CDK Construct Career Development IT — สร้าง
บทความ

CDK Construct Career Development IT — สร้าง

เผยแพร่ 28 พฤษภาคม 2569

AWS CDK Construct คืออะไรและใช้งานอย่างไร

AWS CDK (Cloud Development Kit) เป็น Infrastructure as Code (IaC) framework ที่ให้เขียน cloud infrastructure ด้วยภาษา programming จริงเช่น TypeScript, Python, Java, Go แทนที่จะเขียน YAML/JSON แบบ CloudFormation CDK compile โค้ดเป็น CloudFormation templates แล้ว deploy

Construct เป็น building block หลักของ CDK แบ่งเป็น 3 levels คือ L1 (CFN Resources) ที่เป็น direct mapping กับ CloudFormation resources L2 (Curated Constructs) ที่เป็น higher-level abstractions พร้อม sensible defaults และ L3 (Patterns) ที่เป็น complete solutions ที่รวมหลาย resources เข้าด้วยกัน

CDK เหมาะสำหรับ IT professionals ที่ต้องการพัฒนา career ด้าน Cloud Infrastructure เพราะ demand สูง ใช้ programming skills ที่มีอยู่ ทำ code review, testing และ CI/CD ได้เหมือน application code และเป็น skill ที่ต้องการมากในตลาดงาน

ข้อดีของ CDK เมื่อเทียบกับ Terraform คือใช้ภาษา programming จริงที่มี loops, conditionals, type checking IDE support ครบ (autocomplete, error checking) สร้าง reusable constructs ได้ง่าย testing ด้วย standard test frameworks และ integrate กับ AWS services ได้ลึกกว่า

ติดตั้ง CDK และสร้างโปรเจกต์แรก

ขั้นตอนการเริ่มต้นใช้งาน CDK

ติดตั้ง AWS CDK CLI

npm install -g aws-cdk

ตรวจสอบ version

cdk --version

สร้างโปรเจกต์ใหม่

mkdir my-infra && cd my-infra

cdk init app --language typescript

โครงสร้างโปรเจกต์

my-infra/

├── bin/

│ └── my-infra.ts # App entry point

├── lib/

│ └── my-infra-stack.ts # Main stack

├── test/

│ └── my-infra.test.ts # Tests

├── cdk.json # CDK config

├── package.json

└── tsconfig.json

Bootstrap CDK (ครั้งแรกเท่านั้น)

cdk bootstrap aws://ACCOUNT_ID/ap-southeast-1

Synthesize CloudFormation template

cdk synth

Deploy

cdk deploy

Diff (ดูการเปลี่ยนแปลง)

cdk diff

Destroy

cdk destroy

=== bin/my-infra.ts ===

import * as cdk from 'aws-cdk-lib';

import { MyInfraStack } from '../lib/my-infra-stack';

const app = new cdk.App();

new MyInfraStack(app, 'MyInfraStack', {

env: {

account: process.env.CDK_DEFAULT_ACCOUNT,

region: 'ap-southeast-1',

},

tags: {

Environment: 'production',

Team: 'platform',

ManagedBy: 'cdk',

},

});

=== lib/my-infra-stack.ts ===

import * as cdk from 'aws-cdk-lib';

import * as ec2 from 'aws-cdk-lib/aws-ec2';

import * as ecs from 'aws-cdk-lib/aws-ecs';

import * as rds from 'aws-cdk-lib/aws-rds';

import { Construct } from 'constructs';

export class MyInfraStack extends cdk.Stack {

constructor(scope: Construct, id: string, props?: cdk.StackProps) {

super(scope, id, props);

VPC

const vpc = new ec2.Vpc(this, 'MainVpc', {

maxAzs: 3,

natGateways: 1,

});

ECS Cluster

const cluster = new ecs.Cluster(this, 'AppCluster', { vpc });

RDS

const db = new rds.DatabaseInstance(this, 'AppDb', {

engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_15 }),

vpc,

instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),

});

}

}

สร้าง Custom Constructs สำหรับ Production

สร้าง reusable constructs สำหรับใช้ข้าม projects

// lib/constructs/secure-api.ts — Custom L3 Construct
import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export interface SecureApiProps {
  readonly apiName: string;
  readonly stageName?: string;
  readonly throttleRateLimit?: number;
  readonly throttleBurstLimit?: number;
  readonly enableWaf?: boolean;
  readonly enableAccessLogs?: boolean;
  readonly corsOrigins?: string[];
}

export class SecureApi extends Construct {
  public readonly api: apigateway.RestApi;
  public readonly logGroup: logs.LogGroup;

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

    // Access Logs
    this.logGroup = new logs.LogGroup(this, 'ApiLogs', {
      retention: logs.RetentionDays.THIRTY_DAYS,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // API Gateway
    this.api = new apigateway.RestApi(this, 'Api', {
      restApiName: props.apiName,
      deployOptions: {
        stageName: props.stageName || 'prod',
        throttlingRateLimit: props.throttleRateLimit || 1000,
        throttlingBurstLimit: props.throttleBurstLimit || 500,
        accessLogDestination: new apigateway.LogGroupLogDestination(this.logGroup),
        accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields(),
        tracingEnabled: true,
        metricsEnabled: true,
      },
      defaultCorsPreflightOptions: props.corsOrigins ? {
        allowOrigins: props.corsOrigins,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      } : undefined,
    });

    // WAF
    if (props.enableWaf !== false) {
      const webAcl = new wafv2.CfnWebACL(this, 'WebAcl', {
        scope: 'REGIONAL',
        defaultAction: { allow: {} },
        rules: [
          {
            name: 'RateLimitRule',
            priority: 1,
            action: { block: {} },
            statement: {
              rateBasedStatement: {
                limit: 2000,
                aggregateKeyType: 'IP',
              },
            },
            visibilityConfig: {
              cloudWatchMetricsEnabled: true,
              metricName: 'RateLimitRule',
              sampledRequestsEnabled: true,
            },
          },
          {
            name: 'AWSManagedCommonRules',
            priority: 2,
            overrideAction: { none: {} },
            statement: {
              managedRuleGroupStatement: {
                vendorName: 'AWS',
                name: 'AWSManagedRulesCommonRuleSet',
              },
            },
            visibilityConfig: {
              cloudWatchMetricsEnabled: true,
              metricName: 'CommonRules',
              sampledRequestsEnabled: true,
            },
          },
        ],
        visibilityConfig: {
          cloudWatchMetricsEnabled: true,
          metricName: props.apiName,
          sampledRequestsEnabled: true,
        },
      });

      new wafv2.CfnWebACLAssociation(this, 'WafAssoc', {
        resourceArn: this.api.deploymentStage.stageArn,
        webAclArn: webAcl.attrArn,
      });
    }
  }

  addLambdaEndpoint(path: string, method: string, handler: lambda.Function) {
    const resource = this.api.root.resourceForPath(path);
    resource.addMethod(method, new apigateway.LambdaIntegration(handler), {
      authorizationType: apigateway.AuthorizationType.IAM,
    });
  }
}

// Usage:
// const api = new SecureApi(this, 'MyApi', {
//   apiName: 'user-service',
//   enableWaf: true,
//   corsOrigins: ['https://myapp.com'],
// });
// api.addLambdaEndpoint('/users', 'GET', getUsersLambda);

CDK Patterns สำหรับ Microservices Architecture

Patterns ที่ใช้บ่อยในการสร้าง microservices

// lib/constructs/microservice.ts — Microservice Pattern
import * as cdk from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { Construct } from 'constructs';

export interface MicroserviceProps {
  readonly serviceName: string;
  readonly vpc: ec2.IVpc;
  readonly cluster: ecs.ICluster;
  readonly dockerImage: string;
  readonly cpu?: number;
  readonly memoryMiB?: number;
  readonly desiredCount?: number;
  readonly environment?: Record<string, string>;
  readonly enableQueue?: boolean;
  readonly enableTable?: boolean;
}

export class Microservice extends Construct {
  public readonly service: ecsPatterns.ApplicationLoadBalancedFargateService;
  public readonly queue?: sqs.Queue;
  public readonly deadLetterQueue?: sqs.Queue;
  public readonly table?: dynamodb.Table;
  public readonly topic: sns.Topic;

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

    // SNS Topic for events
    this.topic = new sns.Topic(this, 'Events', {
      topicName: `-events`,
    });

    // SQS Queue (optional)
    if (props.enableQueue) {
      this.deadLetterQueue = new sqs.Queue(this, 'DLQ', {
        queueName: `-dlq`,
        retentionPeriod: cdk.Duration.days(14),
      });

      this.queue = new sqs.Queue(this, 'Queue', {
        queueName: `-queue`,
        visibilityTimeout: cdk.Duration.seconds(300),
        deadLetterQueue: {
          queue: this.deadLetterQueue,
          maxReceiveCount: 3,
        },
      });
    }

    // DynamoDB Table (optional)
    if (props.enableTable) {
      this.table = new dynamodb.Table(this, 'Table', {
        tableName: props.serviceName,
        partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
        sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
        pointInTimeRecovery: true,
        removalPolicy: cdk.RemovalPolicy.RETAIN,
      });
    }

    // ECS Fargate Service
    const env: Record<string, string> = {
      SERVICE_NAME: props.serviceName,
      SNS_TOPIC_ARN: this.topic.topicArn,
      ...(this.queue ? { SQS_QUEUE_URL: this.queue.queueUrl } : {}),
      ...(this.table ? { DYNAMODB_TABLE: this.table.tableName } : {}),
      ...(props.environment || {}),
    };

    this.service = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
      cluster: props.cluster,
      serviceName: props.serviceName,
      cpu: props.cpu || 256,
      memoryLimitMiB: props.memoryMiB || 512,
      desiredCount: props.desiredCount || 2,
      taskImageOptions: {
        image: ecs.ContainerImage.fromRegistry(props.dockerImage),
        environment: env,
        containerPort: 8080,
      },
      circuitBreaker: { rollback: true },
      healthCheckGracePeriod: cdk.Duration.seconds(60),
    });

    // Auto Scaling
    const scaling = this.service.service.autoScaleTaskCount({
      minCapacity: props.desiredCount || 2,
      maxCapacity: (props.desiredCount || 2) * 5,
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 70,
      scaleInCooldown: cdk.Duration.seconds(60),
      scaleOutCooldown: cdk.Duration.seconds(60),
    });

    // Grant permissions
    this.topic.grantPublish(this.service.taskDefinition.taskRole);
    if (this.queue) {
      this.queue.grantConsumeMessages(this.service.taskDefinition.taskRole);
    }
    if (this.table) {
      this.table.grantReadWriteData(this.service.taskDefinition.taskRole);
    }
  }
}

// Usage in Stack:
// const userService = new Microservice(this, 'UserService', {
//   serviceName: 'user-service',
//   vpc, cluster,
//   dockerImage: '123456.dkr.ecr.region.amazonaws.com/user-service:latest',
//   enableQueue: true,
//   enableTable: true,
// });

Testing CDK Constructs ด้วย Jest

เขียน unit tests สำหรับ CDK infrastructure

// test/secure-api.test.ts — CDK Construct Tests
import * as cdk from 'aws-cdk-lib';
import { Template, Match, Capture } from 'aws-cdk-lib/assertions';
import { SecureApi } from '../lib/constructs/secure-api';
import { Microservice } from '../lib/constructs/microservice';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';

describe('SecureApi Construct', () => {
  let app: cdk.App;
  let stack: cdk.Stack;
  let template: Template;

  beforeEach(() => {
    app = new cdk.App();
    stack = new cdk.Stack(app, 'TestStack');

    new SecureApi(stack, 'TestApi', {
      apiName: 'test-api',
      enableWaf: true,
      throttleRateLimit: 500,
      corsOrigins: ['https://example.com'],
    });

    template = Template.fromStack(stack);
  });

  test('creates API Gateway REST API', () => {
    template.hasResourceProperties('AWS::ApiGateway::RestApi', {
      Name: 'test-api',
    });
  });

  test('enables throttling', () => {
    template.hasResourceProperties('AWS::ApiGateway::Stage', {
      MethodSettings: Match.arrayWith([
        Match.objectLike({
          ThrottlingRateLimit: 500,
        }),
      ]),
    });
  });

  test('creates WAF WebACL', () => {
    template.hasResourceProperties('AWS::WAFv2::WebACL', {
      Scope: 'REGIONAL',
      DefaultAction: { Allow: {} },
    });
  });

  test('creates WAF association', () => {
    template.resourceCountIs('AWS::WAFv2::WebACLAssociation', 1);
  });

  test('creates CloudWatch log group', () => {
    template.hasResourceProperties('AWS::Logs::LogGroup', {
      RetentionInDays: 30,
    });
  });

  test('enables X-Ray tracing', () => {
    template.hasResourceProperties('AWS::ApiGateway::Stage', {
      TracingEnabled: true,
    });
  });
});

describe('Microservice Construct', () => {
  let template: Template;

  beforeEach(() => {
    const app = new cdk.App();
    const stack = new cdk.Stack(app, 'TestStack');

    const vpc = new ec2.Vpc(stack, 'Vpc');
    const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

    new Microservice(stack, 'TestService', {
      serviceName: 'test-svc',
      vpc,
      cluster,
      dockerImage: 'nginx:latest',
      enableQueue: true,
      enableTable: true,
      desiredCount: 2,
    });

    template = Template.fromStack(stack);
  });

  test('creates Fargate service', () => {
    template.hasResourceProperties('AWS::ECS::Service', {
      ServiceName: 'test-svc',
      DesiredCount: 2,
      LaunchType: 'FARGATE',
    });
  });

  test('creates SQS queue with DLQ', () => {
    template.hasResourceProperties('AWS::SQS::Queue', {
      QueueName: 'test-svc-queue',
    });
    template.hasResourceProperties('AWS::SQS::Queue', {
      QueueName: 'test-svc-dlq',
    });
  });

  test('creates DynamoDB table', () => {
    template.hasResourceProperties('AWS::DynamoDB::Table', {
      TableName: 'test-svc',
      BillingMode: 'PAY_PER_REQUEST',
      PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
    });
  });

  test('creates SNS topic', () => {
    template.hasResourceProperties('AWS::SNS::Topic', {
      TopicName: 'test-svc-events',
    });
  });

  test('creates auto scaling', () => {
    template.hasResourceProperties('AWS::ApplicationAutoScaling::ScalableTarget', {
      MinCapacity: 2,
      MaxCapacity: 10,
    });
  });

  test('snapshot test', () => {
    expect(template.toJSON()).toMatchSnapshot();
  });
});

// Run: npx jest --coverage

Career Path สำหรับ Cloud Infrastructure Engineer

แผนพัฒนา career ด้าน Cloud Infrastructure

=== Career Development Roadmap for Cloud/CDK Engineers ===

Level 1: Junior Cloud Engineer (0-2 years)

Skills:

  • Basic AWS services (EC2, S3, VPC, IAM, RDS)
  • Linux system administration
  • Git version control
  • Basic networking (TCP/IP, DNS, HTTP)
  • Scripting (Bash, Python)
  • Basic CDK: L1/L2 constructs, simple stacks

Certifications:

  • AWS Cloud Practitioner
  • AWS Solutions Architect Associate

Projects:

  • Deploy static website with CDK (S3 + CloudFront)
  • Create VPC with CDK
  • Deploy simple web app (ECS Fargate + ALB)

Level 2: Cloud Infrastructure Engineer (2-4 years)

Skills:

  • Advanced AWS services (ECS, Lambda, DynamoDB, SQS, SNS)
  • CDK L3 constructs and custom constructs
  • CI/CD pipelines (GitHub Actions, CodePipeline)
  • Docker and container orchestration
  • Monitoring (CloudWatch, Datadog)
  • Security best practices (IAM, WAF, encryption)
  • Terraform (multi-cloud awareness)

Certifications:

  • AWS Solutions Architect Professional
  • AWS DevOps Engineer Professional

Projects:

  • Build microservices platform with CDK
  • Create reusable CDK construct library
  • Implement CI/CD for infrastructure

Level 3: Senior Cloud/Platform Engineer (4-7 years)

Skills:

  • Multi-account AWS strategy (AWS Organizations)
  • Kubernetes (EKS) management
  • Service mesh (App Mesh, Istio)
  • Cost optimization
  • Disaster recovery planning
  • Team mentoring
  • Architecture design

Certifications:

  • AWS Security Specialty
  • Kubernetes CKA/CKAD

Projects:

  • Design multi-account landing zone
  • Build internal developer platform
  • Implement FinOps practices

Level 4: Principal/Staff Engineer (7+ years)

Skills:

  • Enterprise architecture
  • Multi-cloud strategy
  • Technical leadership
  • Open source contribution
  • Conference speaking
  • Strategic planning

=== Salary Ranges (Thailand, 2024) ===

Junior: 30,000 - 50,000 THB/month

Mid: 50,000 - 80,000 THB/month

Senior: 80,000 - 150,000 THB/month

Principal: 150,000 - 300,000+ THB/month

Remote (US companies): $80,000 - $200,000+/year

=== Key Resources ===

  • AWS CDK Workshop: https://cdkworkshop.com
  • CDK Patterns: https://cdkpatterns.com
  • Construct Hub: https://constructs.dev
  • AWS Well-Architected Framework
  • AWS Skill Builder (free courses)

FAQ คำถามที่พบบ่อย

Q: CDK กับ Terraform เลือกอันไหน?

A: CDK เหมาะสำหรับทีมที่ใช้ AWS เป็นหลักและมี developers ที่ถนัด TypeScript/Python ข้อดีคือ type safety, IDE support, testing frameworks Terraform เหมาะสำหรับ multi-cloud environments มี HCL ที่เรียนรู้ง่ายกว่า community ใหญ่กว่า หลายทีมใช้ทั้งสองโดย CDK สำหรับ application infrastructure และ Terraform สำหรับ foundational infrastructure

Q: CDK Construct Library ควรจัดการอย่างไร?

A: สร้างเป็น separate npm package หรือ PyPI package ใช้ semantic versioning publish ไปยัง private registry (CodeArtifact, npm private) เขียน tests ครบทุก construct มี documentation และ examples สร้าง CI/CD สำหรับ publish อัตโนมัติ ใช้ projen สำหรับ project management

Q: CDK เรียนรู้ยากไหม?

A: ถ้ามีพื้นฐาน TypeScript หรือ Python แล้วเรียนรู้ CDK ได้เร็ว 1-2 สัปดาห์สำหรับ basics แต่ต้องเข้าใจ AWS services ด้วยซึ่งใช้เวลามากกว่า เริ่มจาก CDK Workshop ที่เป็น hands-on tutorial แล้วลองสร้าง constructs จากงานจริง อ่าน source code ของ L2 constructs เพื่อเข้าใจ patterns

Q: CDK v2 กับ v1 ต่างกันอย่างไร?

A: CDK v2 รวมทุก AWS service libraries เข้า aws-cdk-lib package เดียว ไม่ต้อง install แยกทุก service เหมือน v1 มี assertions library ใหม่สำหรับ testing ดีกว่า v1 (assert module) ทุกโปรเจกต์ใหม่ควรใช้ v2 เท่านั้น v1 หมด maintenance แล้ว migration จาก v1 ไป v2 ทำได้ตาม migration guide อย่างเป็นระบบ