[AWS] Slack Slash Commands๋กœ API Gateway ํ†ตํ•ฉ๋œ Lambda ํ•จ์ˆ˜ ํ˜ธ์ถœํ•˜๊ธฐ(+IAM, CloudWatch)

2022. 7. 19. 18:01ใ†AWS

โ˜„๏ธ To-Be Architecture

To-Be Architectrue

 

Slack Slash commands๋ฅผ ์ด์šฉํ•˜์—ฌ API Gateway๋ฅผ ํ†ตํ•ด Lambda ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด๋ณด์ž.

(Slack Docs: Enabling interactivity with Slash Commands๋ฅผ ๋จผ์ € ์ฝ๊ณ  Slash Command๊ฐ€ ์–ด๋–ค ๊ฒƒ์ธ์ง€, ์–ด๋–ค ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋Š”์ง€ ํŒŒ์•…ํ•œ ํ›„ ์‹œ์ž‘ํ•˜์‹œ๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋ .)

 

 

 

 

 

 

 

IAM

Lambda ํ•จ์ˆ˜์— ์—ฐ๊ฒฐํ•  IAM ์—ญํ• ์„ ์ƒ์„ฑํ•˜์ž. API Gateway๊ฐ€ Lambda ํ•จ์ˆ˜๋ฅผ invoke ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ์ค€๋‹ค.

1. IAM Policy ์ƒ์„ฑ

// IAM Policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "test",
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "*"
        }
    ]
}

2. IAM Role ๊ตฌ์„ฑ

2-1. IAM Role ์ƒ์„ฑ

IAM Role ์ƒ์„ฑ

  • Truested entity type : AWS Service
  • Use case : Lambda
  • 1์—์„œ ๋งŒ๋“  policy ์—ฐ๊ฒฐ

2-2. ์‹ ๋ขฐ๊ด€๊ณ„ ํŽธ์ง‘

  • AWS API Gateway๋„ ํ•ด๋‹น role์„ assume ํ•  ์ˆ˜ ์žˆ๋„๋ก Principal์˜ Service์— apigateway.amazonaws.com์„ ์ถ”๊ฐ€.
// IAM Role > Trust relationships
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "lambda.amazonaws.com",
                    "apigateway.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

Lambda

1. Lambda ํ•จ์ˆ˜ ์ƒ์„ฑ

  • ์‹คํ–‰ ์—ญํ•  > ๊ธฐ์กด ์—ญํ•  ์‚ฌ์šฉ > [IAM]์—์„œ ๋งŒ๋“  ์—ญํ•  ์„ ํƒ

Lambda ํ•จ์ˆ˜ ์ƒ์„ฑ
Lambda ํ•จ์ˆ˜ > ๊ตฌ์„ฑ > ๊ถŒํ•œ

  • ์ƒ์„ฑ๋œ ๋žŒ๋‹ค ํ•จ์ˆ˜์˜ ๊ตฌ์„ฑ ํƒญ > ๊ถŒํ•œ์—์„œ ์—ญํ• ์ด ์—ฐ๊ฒฐ๋˜์—ˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

2. ์ฝ”๋“œ ์ž‘์„ฑ

# ์›ํ•˜๋Š” ๋กœ์ง์„ ์ž‘์„ฑํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
def lambda_handler(event, context):
    return {
        "response_type": "in_channel",
        "text": "Success~~~~~~~!"
    }

API Gateway

1. ์‹ ๊ทœ API ์ƒ์„ฑ

AWS API Gateway > create API

  • ์—”๋“œํฌ์ธํŠธ ์œ ํ˜•
    • Regional(์ง€์—ญ) : ํ˜„์žฌ region์— API๊ฐ€ ๋ฐฐํฌ๋œ๋‹ค.
    • Edge Optimized(์—ฃ์ง€ ์ตœ์ ํ™”) : Cloudfront ๋„คํŠธ์›Œํฌ์— API๊ฐ€ ๋ฐฐํฌ๋œ๋‹ค.
    • Private(ํ”„๋ผ์ด๋น—) : API Gateway๋ฅผ ์œ„ํ•œ VPC ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

2. ๋ฉ”์†Œ๋“œ ์ƒ์„ฑ

AWS API Gateway >  API > Create Method

  • slash command๋Š” API Gateway Endpoint๋กœ HTTP POST๋ฅผ ๋ณด๋‚ด๊ธฐ ๋•Œ๋ฌธ์— request type์„ POST๋กœ ์„ค์ •ํ•œ๋‹ค.
    • ์›ํ•˜๋Š” ๊ฒฝ์šฐ, GET ์š”์ฒญ์œผ๋กœ ํ˜ธ์ถœํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

AWS API Gateway > POST method ๊ตฌ์„ฑ

  • Integration : Lambda ํ•จ์ˆ˜
  • Lambda Function : ์œ„์—์„œ ์ƒ์„ฑํ•œ ๋žŒ๋‹ค ํ•จ์ˆ˜์˜ ์ด๋ฆ„์„ ์ž…๋ ฅ.

AWS API Gateway > Resource > Method > Lambda ํ•จ์ˆ˜์™€ Integration Request ๊ตฌ์„ฑ

 ๋žŒ๋‹ค ์ฝ˜์†”์— ๊ฐ€์„œ ํ™•์ธํ•ด๋ณด์ž.

AWS Lambda > Configuration > Triggers

ํ•ด๋‹น ํ•จ์ˆ˜์—์„œ trigger ํ•˜๋Š” ์„œ๋น„์Šค๋กœ API Gateway๊ฐ€ ์ถ”๊ฐ€๋์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Configuration ํƒญ > (์‚ฌ์ด๋“œ๋ฐ”) Triggers์—์„œ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ 1์—์„œ ์ƒ์„ฑํ•œ API(hyeonju-api)๊ฐ€ ํŠธ๋ฆฌ๊ฑฐํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค๋กœ ์ถ”๊ฐ€๋์Œ์„ ์˜๋ฏธํ•œ๋‹ค.

AWS Lambda > Configuration > Permissions > Resource-based policy

๋˜, ๋žŒ๋‹ค ํ•จ์ˆ˜์˜ Configuration ํƒญ > (์‚ฌ์ด๋“œ๋ฐ”) Permissions > Resource-based policy ์„น์…˜์—์„œ ์ •์ฑ…์ด ์ถ”๊ฐ€๋์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ƒ์„ฑํ•œ POST ๋ฉ”์†Œ๋“œ์— ๋Œ€ํ•ด ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ invoke ํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ–ˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค. AWS ์„œ๋น„์Šค ์™ธ์— ๋‹ค๋ฅธ AWS Account์—๊ฒŒ๋„ ํ•จ์ˆ˜๋ฅผ invokeํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

3. Integration Request ์„ค์ •

๋‹ค์‹œ API Gateway ์ฝ˜์†”๋กœ ๋Œ์•„์˜ค์ž.

Integration Request์—์„œ๋Š” ๋ฐฑ์—”๋“œ์˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ํƒ€์ž…(Lambda, HTTP, AWS service ๋˜๋Š” Mock)์™€ ์–ด๋–ป๊ฒŒ ์š”์ฒญ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฉ”์†Œ๋“œ์˜ ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋‚ด์ง€๊ธฐ ์ „๊นŒ์ง€ ๋ณ€ํ™˜๋˜๋Š”์ง€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋žŒ๋‹ค ํ•จ์ˆ˜๋Š” ํ—ค๋”๋‚˜ ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์ง€ ๋ชปํ•˜์ง€๋งŒ, API Gateway๋ฅผ ์ด์šฉํ•˜์—ฌ JSON์œผ๋กœ ์š”์ฒญ ๊ฐ’์„ ๋ชจ๋‘ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

AWS API Gateway > Method > Integration Reuest > Mapping Template (1)

  • [Mapping Templates] ํƒญ ์„ ํƒ > [When there are no templates defined (recommended)] ์„ ํƒ > [Add mapping template] ๋ฒ„ํŠผ ์„ ํƒ
  • application/x-www-form-urlencoded ์ž…๋ ฅ
    • slack slash command์—์„œ POST ๋ฐฉ์‹์œผ๋กœ request๋ฅผ ์ „์†กํ•  ๋•Œ ๋ฐ์ดํ„ฐ์˜ Content-type ํ—ค๋”๋Š” application/x-www-form-urlencoded์ด๋‹ค.
    • application/x-www-form-urlencoded๋Š” ์„œ๋ฒ„์— ์ „์†ก๋˜๋Š” HTTP ๋ฉ”์‹œ์ง€๊ฐ€ ํ•˜๋‚˜์˜ ํฐ ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์ด๊ณ  ์ด๋ฆ„/๊ฐ’ ์Œ๋“ค์ด "&"๋กœ ๊ตฌ๋ถ„๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ด๋ฆ„๊ณผ ๊ฐ’์€ "="์œผ๋กœ ๊ตฌ๋ถ„๋œ๋‹ค.

AWS API Gateway > Method > Integration Reuest > Mapping Template (2)

  • Mapping Template์„ ์ด์šฉํ•˜์—ฌ ์š”์ฒญ ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ JSON์œผ๋กœ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ณ€ํ™˜ํ•˜๊ณ  Lambda์˜ event ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜๊ฒจ์ฃผ์ž.
## convert HTML POST data to JSON
 
## get the raw post data from the AWS built-in variable and give it a nicer name
#set($rawAPIData = $input.path('$'))

## first we get the number of "&" in the string, this tells us if there is more than one key value pair
#set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())
 
## if there are no "&" at all then we have only one key value pair.
## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs.
## the "empty" kv pair to the right of the ampersand will be ignored anyway.
#if ($countAmpersands == 0)
 #set($rawPostData = $rawAPIData + "&")
#end
 
## now we tokenise using the ampersand(s)
#set($tokenisedAmpersand = $rawAPIData.split("&"))
 
## we set up a variable to hold the valid key value pairs
#set($tokenisedEquals = [])
 
## now we set up a loop to find the valid key value pairs, which must contain only one "="
#foreach( $kvPair in $tokenisedAmpersand )
 #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
 #if ($countEquals == 1)
  #set($kvTokenised = $kvPair.split("="))
  #if ($kvTokenised[0].length() > 0)
   ## we found a valid key value pair. add it to the list.
   #set($devNull = $tokenisedEquals.add($kvPair))
  #end
 #end
#end
 
## next we set up our loop inside the output structure "{" and "}"
{
#foreach( $kvPair in $tokenisedEquals )
  ## finally we output the JSON for this pair and append a comma if this isn't the last pair
  #set($kvTokenised = $kvPair.split("="))
 "$util.urlDecode($kvTokenised[0])" : #if($kvTokenised[1].length() > 0)"$util.urlDecode($kvTokenised[1])"#{else}""#end#if( $foreach.hasNext ),#end
#end
}

4. API ๋ฐฐํฌ

AWS API Gateway > ๋ฉ”์†Œ๋“œ ์„ ํƒ > Actions > Deploy API

  • API๋Š” ์Šคํ…Œ์ด์ง€์— ๋ฐฐํฌ๋œ๋‹ค. ์Šคํ…Œ์ด์ง€๋Š” API์˜ ํ™˜๊ฒฝ(dev, prod, beta)๊ณผ ๊ฐ™๋‹ค.

Deploy API > Stage ์„ ํƒ (์—†๋Š” ๊ฒฝ์šฐ ์ƒ์„ฑ)

  • Stage๋Š” ๋ฐฐํฌ์— ๋Œ€ํ•œ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ €์žฅํ•˜๊ณ  ์–ธ์ œ๋“  ๊ทธ ์ „ ๋ฐฐํฌ๋กœ ๋กค๋ฐฑํ•  ์ˆ˜ ์žˆ๋‹ค.  ์ถ”ํ›„์— ์‰ฝ๊ฒŒ ๋กค๋ฐฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐ ๋ฐฐํฌ์— ๋Œ€ํ•œ description์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

  • API๊ฐ€ ์œ„ URL๋กœ ๋ฐฐํฌ๋˜์—ˆ๋‹ค. ํ•ด๋‹น URL์„ ๋ณต์‚ฌํ•˜์ž.

Slack Slash Command

1. ์›ํ•˜๋Š” ์ฑ„๋„์— Slash Commands ์•ฑ ์ถ”๊ฐ€ 

Slack ์ถ”๊ฐ€ > ์›ํ•˜๋Š” ๋ช…๋ น์–ด ์ž…๋ ฅ

2. slash commands ์„ค์ •

API GW Strage URL ์ž…๋ ฅ > ํ†ตํ•ฉ์„ค์ • ์ €์žฅ

  • ํ†ตํ•ฉ ์„ค์ • > URL์— ๋ฐฐํฌํ•œ stage์˜ URL์„ ์ž…๋ ฅํ•œ๋‹ค.
  • ์œ„์™€ ๊ฐ™์€ ์„ค์ •๋Œ€๋กœ๋ผ๋ฉด, ํ•ด๋‹น ์ฑ„๋„์—์„œ /hyeonju๋ฅผ ์ž…๋ ฅ ์‹œ ํ•ด๋‹น URL๋กœ request๊ฐ€ ์ „์†ก๋œ๋‹ค.

3. ํ…Œ์ŠคํŠธ

Slack์—์„œ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž!

ํ…Œ์ŠคํŠธ ์„ฑ๊ณต~~! ๐Ÿ˜‡

๐Ÿšจ ์ฃผ์˜ ๐Ÿšจ 
slash command ๋’ค ๋ฐ˜๋“œ์‹œ ํ…์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
ex) /hyeonju aaa

ํ…์ŠคํŠธ ์ž‘์„ฑ ์—†์ด /hyeonju๋งŒ ์ž…๋ ฅ ์‹œ 500 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

 

 


(์„ ํƒ) Cloudwatch๋กœ API Gateway ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ธฐ

slack slash commands ๊ตฌ์„ฑ ํ›„, ์•„๋ž˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๋ฐ ๋กœ๊น…์„ ์œ„ํ•ด ์ถ”๊ฐ€๋กœ cloudwatch๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

Slack Slash Commands ์š”์ฒญ ์‹คํŒจ

1. IAM Role ์ƒ์„ฑ

API Gateway์— ๊ณ„์ •์˜ CloudWatch Log๋ฅผ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค.

AWS IAM > IAM role ์ƒ์„ฑ

  • Service : API Gateway

AWS IAM > IAM role ์ƒ์„ฑ > Add Permission

  • AWS Managed Policy์ธ [AmazonAPIGatewayPushToCloudWatchLogs] ์ •์ฑ…์„ ์ถ”๊ฐ€ํ•œ๋‹ค.
    ์ด์ „ ๋‹จ๊ณ„์—์„œ ์„œ๋น„์Šค๋กœ API Gateway ์„ ํƒ ์‹œ ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋˜์–ด ์žˆ๋‹ค.
  • ์ด๋ฆ„ ์„ค์ • ํ›„, ๊ทธ๋Œ€๋กœ ์—ญํ•  ์ƒ์„ฑ.

AWS IAM > IAM ์—ญํ•  ์ƒ์„ฑ ์™„๋ฃŒ > ARN ๋ณต์‚ฌ

  • ์ƒ์„ฑ๋œ ์—ญํ• ์˜ ARN์„ ๋ณต์‚ฌํ•œ๋‹ค.

2. AWS API Gateway์˜ API IAM Role ์„ค์ •

AWS API Gateway > API > Settings

  • Settings์˜ CloudWatch log role ARN ์ž…๋ ฅ์ฐฝ์— ๋ณต์‚ฌํ•œ IAM Role ARN์„ ์ž…๋ ฅํ•œ๋‹ค.

3. AWS CloudWatch ๋กœ๊ทธ ๊ทธ๋ฃน ์ƒ์„ฑ

AWS Cloudwatch  > Log groups > Log group ์ƒ์„ฑ

4. Stage์— ๋Œ€ํ•œ Logs/Tracing ์„ค์ • 

AWS API Gateway > API > Stages > Stage ์„ ํƒ > Logs/Tracing ํƒญ

  • Enable CloudWatch ํ™œ์„ฑํ™”
    • Log full requests/responses data ํ™œ์„ฑํ™”
  • Enable Access Logging ํ™œ์„ฑํ™”
    • List of Lost Variables๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์›ํ•˜๋Š” ๋กœ๊น… ๋ณ€์ˆ˜ ์„ ํƒํ•˜์—ฌ ๋กœ๊ทธ ํฌ๋งท ๊ตฌ์„ฑ

AWS CloudWatch > Log group > Log Stream ํ™•์ธ

  • ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ Cloudwatch > Log group์—์„œ ์œ„์™€ ๊ฐ™์ด ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 


 

๐Ÿ”— ์ฐธ๊ณ  ๋ฌธ์„œ

  1. Slack API Docs: Enabling interactivity with Slash Commands
  2. Building a Slack Slash Command with C# + AWS Lambda + API Gateway
  3. ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ: Slack Slash Command๋ฅผ ํ†ตํ•ด ์ง‘ ๊ทผ์ฒ˜ ์•ฝ๊ตญ ๋งˆ์Šคํฌ ์ˆ˜๋Ÿ‰ ์•Œ์•„๋ณด๊ธฐ
  4. AWS Docs: [AWS API Gateway] Cloudwatch ๋กœ๊น…์— ๋Œ€ํ•œ ๊ถŒํ•œ
  5. AWS Docs: Amazon API Gateway์—์„œ ์Šคํ…Œ์ด์ง€ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ AWS Lambda ํ†ตํ•ฉ์„ ์ •์˜ํ–ˆ์Šต๋‹ˆ๋‹ค. API ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ "๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜"์™€ 500 ์ƒํƒœ ์ฝ”๋“œ๊ฐ€ ํ‘œ์‹œ๋˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?