๐Ÿงฐ DevOps

S3, CloudFront, Lambda@Edge๋ฅผ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ(6) - ๋ฆฌ์‚ฌ์ด์ง• ๋กœ์ง ์ž‘์„ฑ ๋ฐ ํ…Œ์ŠคํŠธ

์—ฐ_์šฐ๋ฆฌ 2023. 5. 29. 11:25
๋ฐ˜์‘ํ˜•

๋ชฉ์ฐจ

     

     

     

    ์ด ๊ฒŒ์‹œ๊ธ€์€ ์‹œ๋ฆฌ์ฆˆ๋ฌผ์ž…๋‹ˆ๋‹ค!
    ์•„๋ž˜ ๋ชฉ์ฐจ๋ฅผ ๋จผ์ € ํ™•์ธํ•ด์ฃผ์„ธ์š”

    1. Lambda@Edge๋ž€?
    2. S3, CloudFront ์…‹ํŒ…
    3. CloudFront ์ฟผ๋ฆฌ์ŠคํŠธ๋ง ์บ์‹œ ์…‹ํŒ…
    4. IAM ์—ญํ•  ์ƒ์„ฑ
    5. Lambda@Edge ๋ฐฐํฌ ์…‹ํŒ… ๋ฐ ๋กœ๊ทธ ํ™•์ธ
    6. ๋ฆฌ์‚ฌ์ด์ง• ๋กœ์ง ์ž‘์„ฑ ๋ฐ ํ…Œ์ŠคํŠธ

     

     

     

    ๋žŒ๋‹ค ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•˜๊ธฐ

    serverless framework๋ฅผ ์ด์šฉํ•˜๋ฉด ๋žŒ๋‹ค๋ฅผ ๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ,

    ํ•จ์ˆ˜์— ์ „๋‹ฌ๋˜๋Š” event ๊ฐ์ฒด๊ฐ€ ๋น„์–ด์žˆ๋Š” ์ƒํƒœ์—์„œ ์‹คํ–‰๋˜์–ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

    ์„œ๋ฒ„๋ฆฌ์Šค์˜ --path ์˜ต์…˜์„ ์ด์šฉํ•˜์—ฌ ๋กœ์ปฌ์˜ jsonํŒŒ์ผ์„ ์ฝ์–ด event ๊ฐ์ฒด์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ค์‹œ๋‹ค.

     

     

    1. ์ด์ „ ๊ฒŒ์‹œ๊ธ€์—์„œ ํ™•์ธํ•œ ๋กœ๊ทธ๋ฐ์ดํ„ฐ์—์„œ event๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•˜์—ฌ OriginResponseEvent.json ํŒŒ์ผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

     

    2. ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค

    #serverless invoke local --function ํ•จ์ˆ˜๋ช… --path event.jsonํŒŒ์ผ๊ฒฝ๋กœ
    
    $ serverless invoke local --function imageResize --path OriginResponseEvent.json

     

    ์ด์ œ ๋ฒˆ๊ฑฐ๋กญ๊ฒŒ ๋žŒ๋‹ค๋ฅผ ๋ฐฐํฌํ•˜์ง€ ์•Š์•„๋„ ๋กœ์ปฌ์—์„œ ๋žŒ๋‹ค๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

     

     

     

     

     

    ๋ฆฌ์‚ฌ์ด์ง• ๋กœ์ง ์ž‘์„ฑ ์ „์—...

     

    Sharp ์„ค์น˜

    ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘ ๊ฐ€์žฅ ์œ ๋ช…ํ•œ Sharp ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ด…์‹œ๋‹ค.

    Sharp๋Š” ์‹คํ–‰ํ™˜๊ฒฝ์— ๋”ฐ๋ผ ์„ค์น˜ ๋ช…๋ น์–ด๊ฐ€ ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

    #linux ํ™˜๊ฒฝ
    npm install sharp --platform=linux
    
    #window ํ™˜๊ฒฝ
    npm install sharp --platform=win32

     

     

    package.json script ์ˆ˜์ •

    ๋žŒ๋‹ค๊ฐ€ ์‹คํ–‰๋  linuxํ™˜๊ฒฝ๊ณผ ๋กœ์ปฌ(window) ์‹คํ–‰ํ™˜๊ฒฝ์ด ์„œ๋กœ ๋‹ค๋ฅด๊ธฐ๋•Œ๋ฌธ์—

    ๋กœ์ปฌ์—์„œ๋Š” --platform=win32๋กœ ์„ค์น˜ํ•ด์„œ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€

    ๋ฐฐํฌํ•ด์•ผํ•˜๋ฉด --platform=linux๋กœ ์„ค์น˜ํ•ด์„œ ๋ฐฐํฌํ•ด์•ผํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

     

    package.json ํŒŒ์ผ์˜ script๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ ๋กœ์ปฌ ์‹คํ–‰ ๋ช…๋ น, ๋ฐฐํฌ ์‹คํ–‰ ๋ช…๋ น์„ ๊ฐ„ํŽธํ•˜๊ฒŒ ์ˆ˜์ •ํ•ด๋ด…์‹œ๋‹ค.

    • prestart, predeploy : ์‹คํ–‰ํ™˜๊ฒฝ์— ๋งž๋Š” sharp ์„ค์น˜ ๋ช…๋ น์–ด ์ž…๋ ฅ
    • start, deploy : ๋กœ์ปฌ/๋ฐฐํฌ ์‹คํ–‰ ๋ช…๋ น์–ด ์ž…๋ ฅ
      "scripts": {
        "prestart": "npm uninstall sharp && npm install sharp --platform=win32",
        "start": "serverless invoke local --function imageResize --path OriginResponseEvent.json",
        "predeploy": "npm uninstall sharp && npm install sharp --platform=linux --arch=x64",
        "deploy": "serverless deploy"
      },

    ์ด์ œ ๋กœ์ปฌ ์‹คํ–‰์€ npm run start, ์šด์˜ ๋ฐฐํฌ๋Š” npm run deploy๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

     

     

     

    ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋ฐ์ดํ„ฐ S3์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ

    ์˜ค๋ฆฌ์ง„์œผ๋กœ๋ถ€ํ„ฐ ์‘๋‹ต์„ ์ˆ˜์‹ ํ•œ ์ƒํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋žŒ๋‹ค event.json์— S3 ์ •๋ณด๊ฐ€ ์กด์žฌํ•˜์ง€๋งŒ,

    ์ด๋ฏธ์ง€ ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋Š” ์—†๊ธฐ ๋•Œ๋ฌธ์— S3์— ๋‹ค์‹œ ์ ‘๊ทผํ•ด์„œ ์›๋ณธ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๊ฐ€์ง€๊ณ  ์™€์•ผํ•ฉ๋‹ˆ๋‹ค.

     

    AWS SDK for JavaScript v3 ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ @aws-sdk/client-s3 ์„ค์น˜ํ•ด์ค๋‹ˆ๋‹ค.

    npm install @aws-sdk/client-s3

     

     

    ResponseBody ํฌ๊ธฐ ํ• ๋‹น๋Ÿ‰

    Lambda@Edge ํ•จ์ˆ˜์—์„œ Response Body๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ, ํฌ๊ธฐ ํ• ๋‹น๋Ÿ‰์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ 

     

     

     

    ์ฟผ๋ฆฌ์ŠคํŠธ๋ง ๊ฐ’ ํ™•์ธ

    ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์„ ์ถ”๊ฐ€ํ•˜๋ฉด event๊ฐ์ฒด์˜ ์–ด๋””์— ์ฟผ๋ฆฌ์ŠคํŠธ๋ง๊ฐ’์ด ๋“ค์–ด์˜ค๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

    querystring๊ฐ’์€ ?๋นผ๊ณ  ์ž…๋ ฅํ•œ ๊ทธ๋Œ€๋กœ ๋“ค์–ด์˜ค๋„ค์š”

    OriginResponseEvent.json ํŒŒ์ผ์— ์ž…๋ ฅํ•ด์ค์‹œ๋‹ค.

     

     

     

     

     

    ๋ฆฌ์‚ฌ์ด์ง• ๋กœ์ง ์ž‘์„ฑ

    ์œ„์—์„œ ์ œ์•ฝ์‚ฌํ•ญ ๋“ฑ์„ ํ™•์ธํ–ˆ์œผ๋‹ˆ ์ด์ œ ์ง„์งœ๋กœ ๋กœ์ง์„ ์ž‘์„ฑํ•ด๋ด…์‹œ๋‹ค

    //index.js
    'use strict';
    
    
    import Sharp from "sharp"
    import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
    const S3 = new S3Client({
        region: 'ap-northeast-2'
    });
    
    const getQuerystring = (querystring, key) => {
        return new URLSearchParams("?" + querystring).get(key);
    }
    
    
    export const imageResize = async (event, context) => {
        console.log("imageResize ์‹คํ–‰!!");
    
        //===================================================================
        //event์—์„œ ์š”์ฒญ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        const { request, response } = event.Records[0].cf;
    
        //์ฟผ๋ฆฌ์ŠคํŠธ๋ง ๊ฐ€์ ธ์˜ค๊ธฐ
        //์ฟผ๋ฆฌ์ŠคํŠธ๋ง์ด ์กด์žฌํ•˜์ง€ ์•Š๋‹ค๋ฉด ๋ฆฌ์‚ฌ์ด์ฆˆ ์ฒ˜๋ฆฌํ•  ํ•„์š” ์—†์œผ๋‹ˆ ์›๋ณธ ๋ฐ˜ํ™˜
        const querystring = request.querystring;
        if(!querystring){
            console.log("querystring is empty!! return origin");
            return response;
        }
    
        //uri ๊ฐ€์ ธ์˜ค๊ธฐ
        const uri = decodeURIComponent(request.uri);
        //ํŒŒ์ผ๋ช…์ด ์˜์–ด์ธ ๊ฒฝ์šฐ : "/samples/chun.jpeg"
        //ํŒŒ์ผ๋ช…์ด ํ•œ๊ธ€์ธ ๊ฒฝ์šฐ : "/samples/%E1%84%86%E1%85%A9%E1%84%85%E1%85%A1%E1%86%AB.png"
    
        //ํŒŒ์ผ ํ™•์žฅ์ž ๊ฐ€์ ธ์˜ค๊ธฐ
        const extension = uri.match(/(.*)\.(.*)/)[2].toLowerCase();
        console.log("extension", extension);
    
        //gif๋Š” sharp๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž์ฒด ์ด์Šˆ ์กด์žฌ. ๋ฆฌ์‚ฌ์ด์ง•ํ•˜์ง€ ์•Š๊ณ  ๋ฐ”๋กœ ์›๋ณธ ๋ฐ˜ํ™˜
        if(extension === 'gif'){
            console.log("extension is gif!! return origin");
            return response;
        }
    
    
        //์ฟผ๋ฆฌ์ŠคํŠธ๋ง ํŒŒ์‹ฑ
        const width = Number(getQuerystring(querystring, "w")) || null;
        const height = Number(getQuerystring(querystring, "h")) || null;
        const fit = getQuerystring(querystring, "f");
        const quality = Number(getQuerystring(querystring, "q")) || null;
        console.log({
            width,
            height,
            fit,
            quality
        });
    
    
        //s3 ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        const s3BucketDomainName = request.origin.s3.domainName;
        let s3BucketName = s3BucketDomainName.replace(".s3.ap-northeast-2.amazonaws.com", "");
        s3BucketName = s3BucketName.replace(".s3.amazonaws.com", "");
        console.log("s3BucketName", s3BucketName);
        const s3Path = uri.substring(1);
        //===================================================================
    
    
        //S3์—์„œ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
        let s3Object = null;
        try{
            s3Object = await S3.send(new GetObjectCommand({
                Bucket: s3BucketName,
                Key: s3Path
            }));
            console.log("S3 GetObject Success");
        }catch (err){
            console.log("S3 GetObject Fail!! \n" +
                "Bucket: " + s3BucketName + ", Path: " + s3Path + "\n" +
                "err: " + err);
            return err;
        }
    
    
        //์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ฆˆ ์ˆ˜ํ–‰
        const s3Uint8ArrayData = await s3Object.Body.transformToByteArray();
    
        let resizedImage = null;
        try{
            resizedImage = await Sharp(s3Uint8ArrayData)
                .resize({
                    width: width,
                    height: height,
                    fit: fit
                })
                .toFormat(extension, {
                    quality: quality
                })
                .toBuffer();
            console.log("Sharp Resize Success");
        }catch (err) {
            console.log("Sharp Resize Fail!! \n" +
                "Bucket: " + s3BucketName + ", Path: " + s3Path + "\n" +
                "err: " + err);
            return err;
        }
    
    
        //๋žŒ๋‹ค์—ฃ์ง€์—์„œ ์‘๋‹ต์„ ๋งŒ๋“œ๋Š” ๊ฒฝ์šฐ, ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๋Š” body์— ํฌ๊ธฐ์ œํ•œ์ด ์žˆ๋‹ค.
        //base64 ์ธ์ฝ”๋”ฉ ํ…์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ 1.3MB(1,048,576 byte)๊นŒ์ง€ ๊ฐ€๋Šฅ.
        const resizedImageByteLength = Buffer.byteLength(resizedImage, 'base64');
        console.log('resizedImageByteLength:', resizedImageByteLength);
    
        //๋ฆฌ์‚ฌ์ด์ง•ํ•œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๊ฐ€ 1MB ์ด์ƒ์ธ ๊ฒฝ์šฐ ์›๋ณธ ๋ฐ˜ํ™˜
        if (resizedImageByteLength >= 1048576) {
            console.log("resizedImageByteLength >= 1048576!! return origin");
            return response;
        }
    
    
        //๋ฆฌ์‚ฌ์ด์ง•ํ•œ ์ด๋ฏธ์ง€ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๋„๋ก response ์ˆ˜์ •
        response.status = 200;
        response.body = resizedImage.toString('base64');
        response.bodyEncoding = 'base64';
        console.log("imageResize ์ข…๋ฃŒ!!");
        return response;
    };

     

     

     

     

     

    ๋กœ์ปฌ ํ…Œ์ŠคํŠธ

    npm run start

    ์ˆ˜ํ–‰์€ ์„ฑ๊ณตํ•˜๋Š”๋ฐ ์‹ค์ œ๋กœ ๋ฆฌ์‚ฌ์ด์ง• ๋œ ์ด๋ฏธ์ง€ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ ค๋ฉด

    body์— ๋‹ด๊ธด base64์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜, ๋žŒ๋‹ค๋ฅผ ๋ฐฐํฌํ•ด์„œ ์ˆ˜ํ–‰๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค...

     

     

     

     

    ๋กœ์ปฌ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ํŒŒ์ผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” index-local.js ์ถ”๊ฐ€

    index.js์˜ imageResize ํ•จ์ˆ˜๋ฅผ importํ•˜์—ฌ ์‹คํ–‰ํ•˜๊ณ ,

    response๋กœ ๋ฐ›๋Š” body๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜์—ฌ ๋กœ์ปฌ์—์„œ ๋ฆฌ์‚ฌ์ด์ง• ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

    //index-local.js
    'use strict';
    
    
    import { imageResize } from "./index.js";
    import eventJson from "./OriginResponseEvent.json" assert{ type: "json"}; //json์„ ๊ฐ€์ ธ์˜ค๋ ค๋ฉด assert๋ฅผ ๋ถ™์—ฌ์ฃผ์–ด์•ผํ•œ๋‹ค.
    import fs from "fs";
    
    
    export const imageResizeToFile = async (event, context) => {
        console.log("imageResizeToFile start!!");
        event = eventJson;
    
    
        console.log("===============================");
        const response = await imageResize(event, context);
        console.log("===============================");
    
    
        const buf = new Buffer(response.body, 'base64');
        fs.writeFile('output.jpg', buf ,function(err, result) {
            if(err){console.log('error', err);}
        });
    
        console.log("imageResizeToFile end!!");
    };

     

     

     

    run-func ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜

    index-local.js ํŒŒ์ผ์˜ imageResizeToFile ํ•จ์ˆ˜๋ฅผ ๋กœ์ปฌ์—์„œ ์‹คํ–‰๊ฐ€๋Šฅํ•˜๋„๋ก run-func ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋„ ์„ค์น˜ํ•ด์ค๋‹ˆ๋‹ค.

    npm install run-func

     

     

    package.json script ์ˆ˜์ •

    package.json์˜ start ๋ช…๋ น์–ด๋ฅผ ์ˆ˜์ •ํ•ด์ค๋‹ˆ๋‹ค.

    "scripts": {
        "prestart": "npm uninstall sharp && npm install sharp --platform=win32",
        "start": "run-func index-local.js imageResizeToFile",
        "predeploy": "npm uninstall sharp && npm install sharp --platform=linux --arch=x64",
        "deploy": "serverless deploy"
      },

     

     

    ๋‹ค์‹œ npm run startํ•˜์—ฌ ์‹คํ–‰์‹œํ‚ค๋ฉด output.jpg๋กœ ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๊ฐ€ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.

     

     

     

     

     

     

    ์šด์˜ ๋ฐฐํฌ ๋ฐ ํ…Œ์ŠคํŠธ

    npm run deploy

     

     

     

    ์›๋ณธ (1058 x 1411)

     

     

     

    ํฌ๊ธฐ ์ค„์ด๊ธฐ (200 x 250)

     

     

     

     

    ํฌ๊ธฐ ๋Š˜๋ฆฌ๊ธฐ (2000 x 2500)

     

     

     

     

    ์•„์ฃผ ํฌ๊ฒŒ ๋Š˜๋ฆฌ๊ธฐ (8000 x 8000) → ์šฉ๋Ÿ‰์ด ์ปค์ ธ์„œ ์›๋ณธ๋ฐ˜ํ™˜!! (1058 x 1411)

     

     

     

     

     

     

    ๋ฐ˜์‘ํ˜•
    • ๋„ค์ด๋ฒ„ ๋ธ”๋Ÿฌ๊ทธ ๊ณต์œ ํ•˜๊ธฐ
    • ํŽ˜์ด์Šค๋ถ ๊ณต์œ ํ•˜๊ธฐ
    • ํŠธ์œ„ํ„ฐ ๊ณต์œ ํ•˜๊ธฐ
    • ๊ตฌ๊ธ€ ํ”Œ๋Ÿฌ์Šค ๊ณต์œ ํ•˜๊ธฐ
    • ์นด์นด์˜คํ†ก ๊ณต์œ ํ•˜๊ธฐ