Serverless Framework Plugin์„ ๋งŒ๋“ค์–ด์„œ Lambda timeout ์žก์•„๋‚ด๊ธฐ

geunsuryu-wonderwall
  • #Serverless Framework
  • #lambda
  • #plugin
  • #timeout

Serverless Framework Plugin ๋งŒ๋“ค๊ธฐ



์•ˆ๋…•ํ•˜์„ธ์š”. ์›๋”์›”/ํ”„๋กฌ ๋ฐฑ์—”๋“œ ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋Š” ์œ ๊ทผ์ˆ˜๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ €ํฌ ๋ฐฑ์—”๋“œํŒ€์€ ๋งŽ์€ ๋ถ€๋ถ„์—์„œ Serverless Framework๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Serverless Framework์— ๋ถ™์—ฌ์„œ ์“ธ ์ˆ˜ ์žˆ๋Š” plugin๋“ค์ด ๋งŽ์ด ์žˆ๋Š”๋ฐ, ์ด๋ฒˆ์—๋Š” ํ•„์š”ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” plugin์„ ์ฐพ์ง€ ๋ชปํ•ด์„œ, ๊ฐ„๋‹จํ•œ plugin์„ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ, ๊ทธ ๊ณผ์ •์„ ๊ณต์œ ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

Serverless Framework๋ž€?

Serverless Framework๋Š” ์„œ๋ฒ„๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” API Gateway์™€ Lambda๋ฅผ ์ด์šฉํ•ด์„œ ์‰ฝ๊ฒŒ API ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , ๊ทธ ์™ธ์— ํ•„์š”ํ•œ AWS ๊ตฌ์„ฑ์š”์†Œ๋“ค๋„ IaC ํ˜•ํƒœ๋กœ ์ถ”๊ฐ€๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ๋“ค๊ฒŒ ๋œ ๋ฐฐ๊ฒฝ

์ €ํฌ๋„ ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” Serverless Framework๋ฅผ API ์„œ๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜์ง€๋งŒ, SQS๋ฅผ ๋งŒ๋“ค๊ณ  SQS ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” Lambda function์„ ๊ด€๋ฆฌํ•˜๋Š” ์šฉ๋„๋กœ๋„ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

SQS์— ๋ถ™์ธ Lambda function์ด ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์„œ throw๋œ ๊ฒฝ์šฐ์—๋Š”, ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„๋˜๊ฒŒ๋” ์„ค์ •ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ Lambda function์ด timeout์ด ๋œ ๊ฒฝ์šฐ์—๋Š”, throw๋กœ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ์— ์žฌ์‹œ๋„ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ Lambda๊ฐ€ timeout์ด ๋œ ๊ฒฝ์šฐ, ๋”ฐ๋กœ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ํ•˜๊ณ  ์žˆ์ง€ ์•Š์•„์„œ ๋†“์น˜๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ฒจ์„œ,
์ด ๋ถ€๋ถ„์„ Serverless Framework์˜ plugin์œผ๋กœ ๋งŒ๋“ค์–ด๋ณด๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Lambda timeout ์ด๋ฒคํŠธ ์•Œ๋žŒ ์„ค์ •

Lambda๊ฐ€ timeout์œผ๋กœ ์ข…๋ฃŒ๋ ๋•Œ, Cloudwatch Logs์— Task timed out after ๋ผ๋Š” ํŠน์ •ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋‚จ๊ธฐ๋ฉด์„œ ์ข…๋ฃŒ๋ฉ๋‹ˆ๋‹ค.
Cloudwatch Logs์˜ ์ € ๋ฉ”์‹œ์ง€๋ฅผ ํ•„ํ„ฐํ•˜๊ณ , ํ•ด๋‹น ๋ฉ”ํŠธ๋ฆญ์— ์•Œ๋žŒ์„ ์„ค์ •ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑํ•˜๋ฉด,
Lambda์˜ timeout ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

plugin ๋งŒ๋“ค๊ธฐ

Serverless Framework ๊ณต์‹ ๋ฌธ์„œ์— plugin์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด ๊ฐ„๋‹จํ•˜๊ฒŒ ์†Œ๊ฐœ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค๊ธฐ์—๋Š” ์ถฉ๋ถ„ํ•˜์ง€๋งŒ, ์ž์„ธํ•˜๊ฒŒ ๋‚ด์šฉ์ด ๋‚˜์™€์žˆ์ง„ ์•Š์•„์„œ, ์ด ๋ธ”๋กœ๊ทธ ๋‚ด์šฉ์„ ์ฐธ๊ณ ํ•ด์„œ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

constructor์—์„œ๋Š” aws sdk ์š”์ฒญ์„ ์œ„ํ•œ provider์™€ Serverless Framework์˜ hook์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
(hook์˜ handler๋Š” async function์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค ๐Ÿ™‚)

    constructor(serverless) {
        this.#serverless = serverless;
        this.#provider = this.#serverless.getProvider(this.#serverless.service.provider.name);

        this.hooks = {
            'after:deploy:deploy': () => this.afterDeploy()
        };
    }

์‹ค์ œ ๋กœ์ง์€ hook์— ์„ค์ •ํ•ด๋‘” afterDeploy() ๋ฉ”์„œ๋“œ์—์„œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
provider request์š”์ฒญ์€ CloudFormation ์š”์ฒญ์„ ์ฐธ๊ณ ํ•ด์„œ ๊ตฌ์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

    async afterDeploy() {
        console.log('๐ŸŽƒ after:deploy:deploy');

        for (const func of Object.values(this.#serverless.service.functions)) {
            console.log(`๐Ÿ‘พ ${func.name}`);

            console.log(
                await this.#provider.request('CloudWatchLogs', 'putMetricFilter', {
                    logGroupName: `/aws/lambda/${func.name}`,
                    filterName: 'lambda-timeout',
                    filterPattern: 'Task timed out after', // ๋žŒ๋‹ค ํƒ€์ž„์•„์›ƒ ๋ฐœ์ƒํ–ˆ์„๋•Œ ์ฐํžˆ๋Š” ๋กœ๊ทธ
                    metricTransformations: [
                        {
                            metricNamespace: 'Fromm/LambdaTimeouts',
                            metricName: func.name,
                            metricValue: '1'
                        }
                    ]
                })
            );

            console.log(
                await this.#provider.request('CloudWatch', 'putMetricAlarm', {
                    AlarmName: `LambdaTimeoutAlarm - ${func.name}`,
                    Namespace: 'Fromm/LambdaTimeouts',
                    MetricName: func.name,
                    Period: 10, // ์ดˆ ๋‹จ์œ„ ๊ฐ„๊ฒฉ
                    EvaluationPeriods: 1, // ํ•˜๋‚˜์˜ ์•Œ๋žŒ์ด ๋ฐœ์ƒํ•˜๋ ค๋ฉด ํ‰๊ฐ€ํ•˜๋Š” ๊ธฐ๊ฐ„์˜ ์ˆ˜
                    Threshold: 1, // ์•Œ๋žŒ์ด ๋ฐœ์ƒํ•˜๋Š” ์ž„๊ณ„๊ฐ’
                    ComparisonOperator: 'GreaterThanOrEqualToThreshold', // ์ž„๊ณ„๊ฐ’ ์กฐ๊ฑด
                    Statistic: 'Minimum',
                    AlarmActions: ['arn:aws:sns:ap-northeast-2:xxxxxxxx:LambdaTimeoutTopic'], // ์•Œ๋žŒ ๋ฐœ์ƒ ์‹œ SNS ์ฃผ์ œ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค
                    OKActions: [], // ์•Œ๋žŒ์ด ํ•ด์ œ๋  ๋•Œ ์‹คํ–‰๋˜๋Š” ์ž‘์—…
                    InsufficientDataActions: [], // ๋ฐ์ดํ„ฐ๊ฐ€ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์‹คํ–‰๋˜๋Š” ์ž‘์—…
                    TreatMissingData: 'missing' // ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ 'missing'์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ๊ฒฝ๊ณ ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค
                })
            );
        }
    }

์ „์ฒด ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

'use strict';

class TimeoutPlugin {
    #serverless;
    #provider;

    constructor(serverless) {
        // .... ์œ„์˜ ๋‚ด์šฉ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
    }

    async afterDeploy() {
        // .... ์œ„์˜ ๋‚ด์šฉ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.
    }
}

module.exports = TimeoutPlugin;

๊ทธ๋ฆฌ๊ณ  serverless.yml์˜ plugins ํ•ญ๋ชฉ์— ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

service: app

functions:
    # ...

plugins:
    - ./timeout_plugin.js


๋งˆ๋ฌด๋ฆฌ

์ด์ œ ๋งŒ๋“ค์–ด์ง„ SNS ํ† ํ”ฝ์„ ์ฒ˜๋ฆฌํ•  Lambda function์„ ๋งŒ๋“ค๊ณ , ์Šฌ๋ž™ ์•Œ๋ฆผ ๋“ฑ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋žŒ๋‹ค์˜ timeout ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ฐ„๋‹จํ•œ ๊ธฐ๋Šฅ์ด์—ˆ์ง€๋งŒ, ๋ฌธ์„œ๋„ ๊ฐ„๋‹จํ•˜๊ฒŒ๋งŒ ๋‚˜์™€์žˆ์–ด์„œ ์ž๋ฃŒ๋ฅผ ์ฐพ๋Š๋ผ ์ƒ๊ฐ๋ณด๋‹ค ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๋๊นŒ์ง€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~ ๐Ÿ˜†

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ