๐Ÿ„ AWS CDK 101๐ŸŒธ - lambda & CDK ์‹œ๊ณ„

26374 ๋‹จ์–ด awslambdaserverlesstypescript
๋ณธ๊ณ ์—์„œ ํ•„์š”ํ•œ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ตฌ์ถ•ํ•˜๋ ค๋ฉด ๊ธฐ๋ณธ์ ์ธ lambda ์ฐฝ๊ณ ๋ฅผ ์„ค์น˜ํ•ด์•ผ ํ•œ๋‹ค. ์ด ์ฐฝ๊ณ ๋Š” ๋’ท๊ธ€์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์›น ๊ฐˆ๊ณ ๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋‹ค๋ฅธ ์‹œ๋ฒ” ํ”„๋กœ์ ํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒํƒœ๊ณ„์— ๊ฐ€์ ธ๋‹ค ์ค„ ๊ฒƒ์ด๋‹ค.

AWS CDK๋ฅผ ์ฒ˜์Œ ์ ‘ํ•œ ์ดˆ๋ณด์ž๋Š” ์ด ์‹œ๋ฆฌ์ฆˆ ์ด์ „์˜ ๊ธ€์„ ๊ผญ ์ฝ์–ด ์ฃผ์‹ญ์‹œ์˜ค.
์ด์ „ ๊ธ€์„ ๋†“์น˜๋ฉด ์•„๋ž˜ ๋งํฌ๋ฅผ ํ†ตํ•ด ์ฐพ์•„๋ณด์„ธ์š”.
๐Ÿ” ์›๋ž˜๐Ÿ”— Dev Post
๐Ÿ” ์ด์ „ ๊ฒŒ์‹œ๋ฌผ ์ „๋‹ฌ๐Ÿ”—
์šฐ์„ , ์šฐ๋ฆฌ๊ฐ€ ์ „์ง„ํ•˜๊ธฐ ์ „์—, ํ˜„์žฌ์˜ ์‹œ๋ฒ” ์ž‘์—…์žฅ ์ฐฝ๊ณ ๋ฅผ ์ •๋ฆฌํ•˜๊ณ , ์ด๋Ÿฌํ•œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์–ด๋–ป๊ฒŒ ๋ฐ˜์˜ํ•˜๋Š”์ง€ ์•Œ์•„๋ณด์ž.์ด๊ฒƒ์€ ์šฐ๋ฆฌ๋กœ ํ•˜์—ฌ๊ธˆ ์ „์ง„ํ•˜๊ธฐ ์ „์— ์–ด๋–ค ๊ฐœ๋…์— ๋Œ€ํ•ด ์ž˜ ์ดํ•ดํ•˜๊ฒŒ ํ•  ๊ฒƒ์ด๋‹ค.

๋ถˆํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค ์ œ๊ฑฐโŒ›lib/cdk-workshop-stack.ts์„ ์—ฝ๋‹ˆ๋‹ค.
SQS ๋Œ€๊ธฐ์—ด๊ณผ SNS ํ…Œ๋งˆ, ๊ตฌ๋… ๋“ฑ ์ตœ์ดˆ๋กœ ๋งŒ๋“ค์–ด์ง„ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // const queue = new sqs.Queue(this, 'CdkWorkshopQueue', {
    //   visibilityTimeout: Duration.seconds(300)
    // });

    // const topic = new sns.Topic(this, 'CdkWorkshopTopic');

    // topic.addSubscription(new subs.SqsSubscription(queue));
  }
}

์šฐ๋ฆฌ๊ฐ€ ์ด๋ฃฌ ์„ฑ๊ณผ๋Š” ์šฐ๋ฆฌ๊ฐ€ cdk diff ๋ช…๋ น์„ ํ†ตํ•ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค.
IAM Statement Changes
โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   โ”‚ Resource                        โ”‚ Effect โ”‚ Action          โ”‚ Principal                 โ”‚ Condition                                                       โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ - โ”‚ ${CdkWorkshopQueue50D9D426.Arn} โ”‚ Allow  โ”‚ sqs:SendMessage โ”‚ Service:sns.amazonaws.com โ”‚ "ArnEquals": {                                                  โ”‚
โ”‚   โ”‚                                 โ”‚        โ”‚                 โ”‚                           โ”‚   "aws:SourceArn": "${CdkWorkshopTopicD368A42F}"                โ”‚
โ”‚   โ”‚                                 โ”‚        โ”‚                 โ”‚                           โ”‚ }                                                               โ”‚
โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::SQS::Queue CdkWorkshopQueue50D9D426 destroy
[-] AWS::SQS::QueuePolicy CdkWorkshopQueuePolicyAF2494A5 destroy
[-] AWS::SNS::Subscription CdkWorkshopQueueCdkWork
[-] AWS::SNS::Topic CdkWorkshopTopicD368A42F destroy
์ฆ‰, SQS์™€ SNS๋ฅผ ์‚ญ์ œํ•˜๋ฉด ์ƒˆ๋กœ ์ž‘์„ฑ๋œ ํ ์ •์ฑ…๊ณผ ์ด์™€ ๊ด€๋ จ๋œ ์ฃผ์ œ ๊ตฌ๋…์ด ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค.
ํ™˜๊ฒฝ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ •๋ฆฌํ•˜๋ ค๋ฉด cdk deploy์„ ์‹คํ–‰ํ•˜์‹ญ์‹œ์˜ค.

๋‹จ์ˆœ lambda ์Šคํƒ๐Ÿ”ญ
์ด์ œ ์šฐ๋ฆฌ๋Š” ์šฐ๋ฆฌ์˜ ํ™˜๊ฒฝ์„ ์ •๋ฆฌํ•˜๊ณ  ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ ๊ฐ„๋‹จํ•œ lambda ์ฐฝ๊ณ ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ ์ž ํ•œ๋‹ค. ์ด๊ฒƒ์€ ๊ณต๊ณต ์Šค์œ„์น˜๋กœ์„œ ์šฐ๋ฆฌ๊ฐ€ ์•ž์œผ๋กœ ๊ตฌ์ถ•ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ •๋ณด๋ฅผ ์šฐ๋ฆฌ์˜ ํ™˜๊ฒฝ์— ์ „๋‹ฌํ•  ๊ฒƒ์ด๋‹ค.import { Construct } from 'constructs';

The AWS CDK is shipped with an extensive library of constructs called the AWS Construct Library. The construct library is divided into modules, one for each AWS service. For example, if you want to define an AWS Lambda function, we will need to use the AWS Lambda construct library.


๊ณ„์†ํ•˜๊ธฐ ์ „์— ๋‹ค์Œ ์ˆœ์„œ์— ๋”ฐ๋ผ ์ƒˆ ์ฐฝ๊ณ ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค
lib ํด๋”์— CommonEventStack.ts๊ณผ ๊ฐ™์€ ์ƒˆ ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์„ ์ถ”๊ฐ€ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CommonEventStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);


  }
}

๊ทธ๋ฆฌ๊ณ  bin\cdk-workshop.ts์œผ๋กœ ๋Œ์•„๊ฐ€์„œ ์ƒˆ๋กœ ๋งŒ๋“  ์ฐฝ๊ณ ๋ฅผ ๊ฐ€์ ธ์˜ค์‹ญ์‹œ์˜ค.
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { CdkWorkshopStack } from '../lib/cdk-workshop-stack';
// importing our new stack
import { CommonEventStack } from '../lib/common-event-stack';

const app = new cdk.App();
new CdkWorkshopStack(app, 'CdkWorkshopStack');
// initializing our new stack
new CommonEventStack(app, 'CommonEventStack');

์ด ๊ณผ์ •์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก npm run build์„ ์‹คํ–‰ํ•  ๋•Œ๊ฐ€ ๋˜์—ˆ๋‹ค.
๋งˆ์ง€๋ง‰์œผ๋กœ, ์šฐ๋ฆฌ๋Š” cdk ls์„ ์‹คํ–‰ํ•˜์—ฌ ์•„๋ž˜์˜ ์ถœ๋ ฅ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
CdkWorkshopStack
CommonEventStack
๋ง๋ถ™์—ฌ ๋งํ•˜์ž๋ฉด, ๋‚ด๊ฐ€ ๋ฐฉ๊ธˆ ์šฐ๋ฆฌ์—๊ฒŒ ์ค€ ์ƒˆ ์ฐฝ๊ณ  ์ž์›์— ์ ‘๋‘์‚ฌ CommonEvent*์„ ๋ถ€์—ฌํ–ˆ๋Š”๋ฐ, ์šฐ๋ฆฌ๋Š” ๊ณง ๊ทธ๊ฒƒ์„ ์ฐฝ์„คํ•˜์—ฌ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ฐฝ๊ณ ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Œ์„ ์ฆ๋ช…ํ•  ๊ฒƒ์ด๋‹ค.
์ด์ œ lambda ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ๋•Œ๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์— lambda์ด๋ผ๋Š” ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  ์ƒˆ ํŒŒ์ผ event-entry.ts์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.TypeScript ํŒŒ์ผ์—์„œ ์‹œ์ž‘ํ•˜์—ฌ lambda์— ๋ฐฐ์น˜ํ•  ๋•Œ JavaScript๋กœ ์ „์†กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
exports.receiver = async function(event:any) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Message Received: ${event}\n`
  };
};
npmbuild์„ ์‹คํ–‰ํ•˜๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํŒŒ์ผ์ด ๊ฐ™์€ ํด๋” event-entry.js์— ์ƒ์„ฑ๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
"use strict";
exports.receiver = async function (event) {
    console.log("request:", JSON.stringify(event, undefined, 2));
    return {
        statusCode: 200,
        headers: { "Content-Type": "text/plain" },
        body: `Message Received: ${event}\n`
    };
};
//# sourceMappingURL*********

์ด ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ ์ฐฝ๊ณ  CommonEventStack.ts์— lambda ํ•จ์ˆ˜๋ฅผ ๊ตฌ์ถ•ํ•ฉ๋‹ˆ๋‹ค.
    //  A simple lambda Lambda resource definition
    const eventEntry = new lambda.Function(this, 'EventEntryHandler', {
      runtime: lambda.Runtime.NODEJS_14_X,    // execution environment
      code: lambda.Code.fromAsset('lambda'),  // code loaded from "lambda" directory
      handler: 'event-entry.receiver'                // file is "event-entry", function is "receiver"
    });
  }

๋งŒ์•ฝ ๊ทธ๊ฒƒ์ด ๋ถˆํ‰ํ•œ๋‹ค๋ฉด, ํ•„์š”ํ•œ ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์„ธ์š”๐Ÿ“Ž
import * as lambda from 'aws-cdk-lib/aws-lambda'
์—ฌ๊ธฐ์„œ NodeJs 14x๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰ํ•  ๋•Œ, ํ”„๋กœ์„ธ์„œ ํ•จ์ˆ˜๋Š” event-entry์ด๋ผ๋Š” ํŒŒ์ผ์—์„œ ์™”์œผ๋ฉฐ, ๊ทธ ํ•จ์ˆ˜ ์ด๋ฆ„์€ receiver๊ณผ ์œ ์‚ฌํ•˜๋‹ค.
ํ˜„์žฌ cdk synth CommonEventStack์„ ์‹คํ–‰ํ•˜์—ฌyaml ํ…œํ”Œ๋ฆฟ์—์„œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ฐพ๊ฑฐ๋‚˜ cdk diff CommonEventStack์„ ์‹คํ–‰ํ•˜์—ฌ ๋” ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์Œ ๋กœ๊ทธ์—๋Š” ์ฐพ์œผ๋ ค๋Š” ๋‚ด์šฉ์ด ๋‚˜์—ด๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
IAM Statement Changes
โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   โ”‚ Resource                             โ”‚ Effect โ”‚ Action         โ”‚ Principal                    โ”‚ Condition โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ + โ”‚ ${EventEntryHandler/ServiceRole.Arn} โ”‚ Allow  โ”‚ sts:AssumeRole โ”‚ Service:lambda.amazonaws.com โ”‚           โ”‚
โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
IAM Policy Changes
โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   โ”‚ Resource                         โ”‚ Managed Policy ARN                                                             โ”‚
โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ + โ”‚ ${EventEntryHandler/ServiceRole} โ”‚ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole โ”‚
โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Resources
[+] AWS::IAM::Role EventEntryHandler/ServiceRole EventEntryHandlerServiceRoleF9517B59 
[+] AWS::Lambda::Function EventEntryHandler EventEntryHandler0826D724 
์—ฌ๊ธฐ์— ๋‘ ๊ฐœ์˜ ์ž์›์„ ์ฐฝ์„คํ–ˆ๋‹ค. IAM:role๊ณผ Lambda:function์ด๋‹ค.๊ธฐ๋ณธ์ ์œผ๋กœ, ์šฐ๋ฆฌ๋Š” lambda ํ•จ์ˆ˜๋กœ ์ •ํ™•ํ•œ ์‹คํ–‰์„ ๋งก๋Š” ์ƒˆ๋กœ์šด ์—ญํ• ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.cdk deploy CommonEventStack์„ ์‹คํ–‰ํ•˜๊ณ  ์œ„์˜ IAM ์ •์ฑ… ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ y๊ณผ ํ™•์ธํ•˜์—ฌ ๋ฐฐํฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

CommonEventStack: creating CloudFormation changeset...

 โœ…  CommonEventStack

โœจ  Deployment time: 46.9s

Stack ARN:
arn:aws:cloudformation:ap-south-1:********:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540

โœจ  Total time: 60.19s


AWS ์ฝ˜์†”์˜ ๊ฒ€์‚ฌ๐Ÿ“Œ
์ด์ œ AWS ์ฝ˜์†”์—์„œ lambda๋ฅผ ํƒ์ƒ‰ํ•˜์—ฌ ๊ธฐ๋ณธ ์˜์—ญ์— ๋“ค์–ด๊ฐ€๋ฉด lambda๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


์ง€๊ธˆ ์ด lambda๋ฅผ ํ…Œ์ŠคํŠธํ•ด ๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.๐Ÿ‚
์ด๋ฅผ ์œ„ํ•ด, ์šฐ๋ฆฌ๋Š” ๋ฐ˜๋“œ์‹œ ์ƒ˜ํ”Œ ํ…Œ์ŠคํŠธ ์‚ฌ๊ฑด์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์œ„์—์„œ ๋งŒ๋“  ๊ฐ™์€ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹คํ–‰ํ•ฉ์‹œ๋‹ค.

ํด๋ผ์šฐ๋“œ ์›Œ์น˜ ๋กœ๊ทธ์—์„œ ์ด ๋กœ๊ทธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ƒˆ๋กœ์šด lambda ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์šฐ๋ฆฌ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๊ฐ€ ๋ณธ๊ณ ์—์„œ ์‹คํ˜„ํ•œ ๊ฒƒ์€ ๊ธฐ์ˆ ์  ์‹œ๋ฒ”์ผ ๋ฟ์ด๊ณ  ์šฉ๋ก€์— ๋”ฐ๋ผ ์ˆ˜์ง์œผ๋กœ ์ง‘์ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ตœ์„ ์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์—ฌ๊ฒจ์ง€์ง€ ์•Š๊ณ  ๋น„๊ฒฐ์ผ ๋ฟ์ด๋‹ค.
๋ณธ๊ณ ๋ฅผ ์™„์„ฑํ•˜๊ธฐ ์ „์— cdk cli๋ฅผ ํ•˜๋‚˜ ๋” ์•Œ์•„๋ณด๋ฉด ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ซ ์Šค์™‘ ๋ฐฐํฌ๐ŸŽบcdk deploy --hotswap

This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments. For this reason, only use it for development purposes. Never use hotswap for your production deployments!


ํ•ญ๋ชฉ์—์„œ lambda ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•ฉ์‹œ๋‹ค
exports.receiver = async function(event:any) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Hotswapped now! \nMessage Received: ${JSON.stringify(event)}\n`
  };
};
์ด๊ฒƒ์€ lambda๋ฅผ ์œ„ํ•œ ์ž์‚ฐ๋งŒ ๋ฐœํ‘œํ•ฉ๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒฝ์šฐ, lambda์—์„œ typescript๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์—ด๊ตํ™˜์„ ํ•˜๊ธฐ ์ „์— ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์„ ์žŠ์–ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ์„ค์ •๋œ ๋ฐฐ๋‹ฌ ๊ฐ€๋Šฅํ•œ ํŒŒ์ผ์€ ๋ฐœํ‘œ๋˜์ง€๋งŒ, js ํŒŒ์ผ์€ ์˜์›ํžˆ ์—…๋ฐ์ดํŠธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.๋‚ด json ํŒจํ‚ค์ง€์— package.json์˜ ์Šคํฌ๋ฆฝํŠธ์ฒ˜๋Ÿผ ์‹คํ–‰ํ•  ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค."fast": "npm run build && cdk deploy CommonEventStack --hotswap"์ด ์‹œ๊ฐ„์ด ์ค„์–ด๋“ค์—ˆ์ง€๋งŒ ์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•ด์•ผ ํ•  ๊ฒƒ์€ deployment time์œผ๋กœ ๋งค์šฐ ์ž‘์€ ๊ฐ’์œผ๋กœ ์ค„์—ˆ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.์ด์ „์˜ ์ „์ฒด ๋ฐฐ์น˜ ์‹œ๊ฐ„์€ 45์ดˆ์˜€๋Š”๋ฐ, ์ง€๊ธˆ์€ 2์ดˆ๋ฐ–์— ๋˜์ง€ ์•Š์•„ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค.
โœจ hotswapping resources:
   โœจ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH'
โœจ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH' hotswapped!

 โœ…  CommonEventStack

โœจ  Deployment time: 2.22s

Stack ARN:
arn:aws:cloudformation:ap-south-1:575066707855:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540

โœจ  Total time: 15.81s


์—ฌ๊ธฐ์„œ ๋‚˜๋Š” drift์„ ์†Œ๊ฐœํ–ˆ๋Š”๋ฐ, ๊ทธ ๋œป๊ณผ ๊ทธ๊ฒƒ์ด ์–ด๋–ป๊ฒŒ ๋„์›€์ด ๋˜๋Š”์ง€ ๋ณด์—ฌ ์ค€๋‹ค.์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ฐฝ๊ณ ๋‚˜ ๊ทธ ์•„๋ž˜์˜ ์ž์›์„ ์„ ํƒํ•˜๊ณ  ์ถ”์  ๋ฐ ์‹ฌ์‚ฌ๋ฅผ ์œ„ํ•ด ํ‘œ๋ฅ˜ ๊ฒ€์‚ฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํ‘œ๋ฅ˜ ๊ฒ€์ธก๐ŸŽข
ํ‘œ๋ฅ˜ ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด ์ฐฝ๊ณ ์˜ ์‹ค์ œ ์„ค์ •์ด ์˜ˆ์ƒํ•œ ์„ค์ •๊ณผ ๋‹ค๋ฅด๊ฑฐ๋‚˜ ํ‘œ๋ฅ˜๋˜์—ˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.CloudFormation์„ ์‚ฌ์šฉํ•˜์—ฌ ์ „์ฒด ์Šคํƒ์˜ ์ด๋™์ด๋‚˜ ์Šคํƒ์˜ ๊ฐœ๋ณ„ ๋ฆฌ์†Œ์Šค์˜ ์ด๋™์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ ์ž์›์˜ ์‹ค์ œ ์†์„ฑ ๊ฐ’์ด ์˜ˆ์ƒํ•œ ์†์„ฑ ๊ฐ’๊ณผ ๋‹ค๋ฅด๋ฉด ์ž์›์ด ์ด๋ฏธ ์ด๋™ํ–ˆ๋‹ค๊ณ  ์—ฌ๊ธด๋‹ค.์ด๊ฒƒ์€ ์‹ฌ์ง€์–ด ์†์„ฑ์ด๋‚˜ ์ž์›์ด ์‚ญ์ œ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํฌํ•จํ•œ๋‹ค.๋งŒ์•ฝ ์ฐฝ๊ณ ์˜ ํ•œ ๊ฐœ ์ด์ƒ์˜ ์ž์›์ด ์ด๋™ํ•œ๋‹ค๋ฉด, ์ฐฝ๊ณ ๊ฐ€ ์ด๋™ํ•œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.
์—ฌ๊ธฐ์„œ ๋ณธ ์ปดํ“จํ„ฐ ํด๋ผ์šฐ๋“œ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐฐ์น˜๋ฅผ ํ˜•์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์—ด๊ตํ™˜ ๋ฐฐ์น˜๋ฅผ ํ†ตํ•ด ์ดˆ๊ธฐ ์ฐฝ๊ณ  ๊ผญ๋Œ€๊ธฐ์˜ ํ‘œ๋ฅ˜ ๋ณ€ํ™”๋ฅผ ์ด์šฉํ•˜์—ฌ ๋”์šฑ ๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฅธ ๋ฐฐ์น˜๋ฅผ ์ง‘ํ–‰ํ•œ๋‹ค.


์ž์› ํ‘œ๋ฅ˜ ์ƒํƒœ ์ฝ”๋“œ ๋ฐ ์„ค๋ช…๐Ÿ—ฝ
์ด๊ฒƒ์€ ์ตœ๊ทผ์˜cloudformation๋ฐฐ์น˜์™€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„๊ต๋œ๋‹ค.
  • ์‚ญ์ œ
    ์ž์›์€ ์‚ญ์ œ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์˜ˆ์ƒํ•œ ํ…œํ”Œ๋ฆฟ ์„ค์ •๊ณผ ๋‹ค๋ฅด๋‹ค.
  • ์ˆ˜์ • ์‚ฌํ•ญ

  • ์ž์›์€ ์˜ˆ์ƒํ•œ ํ…œํ”Œ๋ฆฟ ์„ค์ •๊ณผ ๋‹ค๋ฅด๋‹ค.
  • ๋ฏธ๊ฒ€์‚ฌ
    CloudFormation์—์„œ ๋ฆฌ์†Œ์Šค๊ฐ€ ์˜ˆ์ƒํ•œ ํ…œํ”Œ๋ฆฟ ๊ตฌ์„ฑ๊ณผ ๋‹ค๋ฅธ์ง€ ํ™•์ธํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
  • ๋™๊ธฐ์‹
    ๋ฆฌ์†Œ์Šค์˜ ํ˜„์žฌ ๊ตฌ์„ฑ์ด ์›ํ•˜๋Š” ํ…œํ”Œ๋ฆฟ ๊ตฌ์„ฑ๊ณผ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์ข…๋ฃŒํ•˜๊ธฐ ์ „์— ๋‹ค์Œ deploy cli ๋ช…๋ น์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    CDK ์‹œ๊ณ„๐ŸŽช
    CDK watch๋Š” ํŒŒ์ผ ๋ณ€๊ฒฝ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ํ•„์š”ํ•  ๋•Œ CDK deploy๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.๊ทธ๊ฒƒ์€ cdk.json ํŒŒ์ผ์— ๋ชฉ๋ก์— ์ง€์ •๋œ ํŒŒ์ผ์„ ํฌํ•จํ•˜๊ณ  ๋ฐฐ์ œํ•˜๋Š” ๊ฒƒ์„ ๊ฐ์‹œํ•œ๋‹ค.
    
    {
        "watch": {
        "include": [
          "**"
        ],
        "exclude": [
          "README.md",
          "cdk*.json",
          "**/*.d.ts",
          "lambda/*.ts",
          "tsconfig.json",
          "package*.json",
          "yarn.lock",
          "node_modules",
          "test"
        ]
      },
    }
    
    
    
    ์œ„์˜ ๋ถ€๋ถ„์—์„œ ๋‚˜๋Š” ํŠน๋ณ„ํžˆ **/*.js์„ lambda/*.ts์œผ๋กœ ๊ฐฑ์‹ ํ•˜์˜€๋‹ค.์ด ๋ณ€๊ฒฝ์€ lambda ์›๋ณธ์„ ๊ฒ€์ถœํ•˜๋Š” ๋™์‹œ์— ts ํŒŒ์ผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๊ฒ€์ถœํ•  ์ˆ˜ ์—†๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.tsc์—์„œ ๋ณด๋‚ธ js ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.
    ๋”ฐ๋ผ์„œ ๋‚˜๋Š” tsc -w๊ณผ cdk watch CommonEventStack --hotswap์„ ์‹คํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ๋‘ ๊ฐœ์˜ ์œˆ๋„์šฐ์ฆˆ๋ฅผ ์‹œ์ž‘ํ•  ๊ฒƒ์ด๋‹ค.๋ณ‘ํ–‰ ์ž‘์—…์€expect lambda ์ฝ”๋“œ ๋ณ€๊ฒฝ์„ ์ž๋™์œผ๋กœ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž์˜ ์šฐํ˜ธ์ ์ธ ๋งž์ถคํ˜•์ž…๋‹ˆ๋‹ค.

    tsc -w this rewrites the js files.


    [4:26:07 PM] File change detected. Starting incremental compilation...
    
    [4:26:11 PM] Found 0 errors. Watching for file changes.
    

    cdk watch CommonEventStack --hotswap published my js files automatically and swiftly.


    Detected change to 'lambda/event-entry.js' (type: change). Triggering 'cdk deploy'
    
    โœจ  Synthesis time: 21.92s
    
    โœจ hotswapping resources:
       โœจ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH'
    โœจ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH' hotswapped!
    
     โœ…  CommonEventStack
    
    โœจ  Deployment time: 2.17s
    
    Stack ARN:
    arn:aws:cloudformation:ap-south-1:575066707855:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540
    current credentials could not be used to assume 'arn:aws:iam::575066707855:role/cdk-hnb659fds-lookup-role-575066707855-ap-south-1', but are for the right account. Proceeding anyway.
    (To get rid of this warning, please upgrade to bootstrap version >= 8)
    
    โœจ  Total time: 24.09s
    
    
    ์ด lambda์— ๋” ๋งŽ์€ ์—ฐ๊ฒฐ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋‹ค์Œ ๊ธ€์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    โญ ์šฐ๋ฆฌ์˜ ๋‹ค์Œ ๊ธ€์€ ์„œ๋ฒ„less์— ๋ฐœํ‘œ๋  ๊ฒƒ์ด๋‹ˆ, ๋ฐ˜๋“œ์‹œ ๋ณด์‹ญ์‹œ์˜ค
    ์ง€์ง€ํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!๐Ÿ™
    โ˜• Buy Me a Coffee์— ๊ฐ€์ž…ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋‹คํ–‰์ž…๋‹ˆ๋‹ค. ์ œ ๋…ธ๋ ฅ์„ ๋•๊ธฐ ์œ„ํ•ด ์•„๋ž˜์— ์—ด๊ฑฐํ•œ ๋‹น์‹ ์ด ์„ ํƒํ•œ ํ”Œ๋žซํผ์—์„œ ์ œ ๋Œ“๊ธ€์„ ์ฃผ๋ชฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    ๐Ÿ” ์›๋ž˜๐Ÿ”— Dev Post
    ๐Ÿ” ์ „์žฌํ•˜๋‹ค๐Ÿ”—

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ