๊ด€๋ฆฌ ๋ฉ”๋‰ด

๐‘†๐‘ข๐‘›๐‘ โ„Ž๐‘–๐‘›๐‘’ ๐‘Ž๐‘“๐‘ก๐‘’๐‘Ÿ ๐‘Ÿ๐‘Ž๐‘–๐‘›โœง

[AWS] S3 + Lambda ๋ฅผ ํ†ตํ•œ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• (macOS sharp ๋ชจ๋“ˆ ์˜ค๋ฅ˜, ๋ฌดํ•œ ๋žŒ๋‹ค ํ˜ธ์ถœ ์˜ค๋ฅ˜) ๋ณธ๋ฌธ

๐—ฃ๐—ฟ๐—ผ๐—ด๐—ฟ๐—ฎ๐—บ๐—บ๐—ถ๐—ป๐—ด๐Ÿ’ป/๐€๐–๐’

[AWS] S3 + Lambda ๋ฅผ ํ†ตํ•œ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• (macOS sharp ๋ชจ๋“ˆ ์˜ค๋ฅ˜, ๋ฌดํ•œ ๋žŒ๋‹ค ํ˜ธ์ถœ ์˜ค๋ฅ˜)

๐ŸคRyusun๐Ÿค 2025. 3. 27. 19:23

๋ฒ„ํ‚ท ์ƒ์„ฑ, ๋žŒ๋‹ค ์ƒ์„ฑ, IAM ์—ญํ•  ์ƒ์„ฑ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ๋” ์ž˜ ์„ค๋ช…๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ, ์ฐธ๊ณ ํ•˜์‹œ๋ฉด ๋œ๋‹ค.

https://oliveyoung.tech/2023-05-19/aws-lambda-resize/

 

AWS Lambda Image Resize ๋„์ž…๊ธฐ | ์˜ฌ๋ฆฌ๋ธŒ์˜ ํ…Œํฌ๋ธ”๋กœ๊ทธ

์‹ ๊ทœ ์ƒํ’ˆ ํ”„๋กœ์ ํŠธ์—์„œ AWS Lambda ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ์ ์šฉํ•˜๊ธฐ

oliveyoung.tech

 

 

ํŠธ๋ฆฌ๊ฑฐ์˜ ์ ‘๋‘์‚ฌ๋Š” images/origin/ ์œผ๋กœ ์„ค์ •ํ–ˆ๋‹ค. 

๋žŒ๋‹ค ์ฝ”๋“œ(node.js)

// dependencies
const AWS = require('aws-sdk'); // AWS SDK ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
const sharp = require('sharp'); // ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

// S3 ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
const s3 = new AWS.S3();

exports.handler = async (event, context, callback) => {
  // S3 ๋ฒ„ํ‚ท ๋ฐ ๊ฐ์ฒด ํ‚ค ์ถ”์ถœ
  const srcBucket = event.Records[0].s3.bucket.name;
  const srcKey = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));
  const dstBucket = event.Records[0].s3.bucket.name;

  // ์ด๋ฏธ์ง€ ํ™•์žฅ์ž ํ™•์ธ
  const typeMatch = srcKey.match(/\.([^.]*)$/);
  if (!typeMatch) {
    console.log("Could not determine the image type.");
    return;
  }

  const imageType = typeMatch[1].toLowerCase();
  if (imageType != "jpg" && imageType != "png") {
    console.log(`Unsupported image type: ${imageType}`);
    return;
  }

  // ๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ด๋ฏธ์ง€ ์ €์žฅ ๊ฒฝ๋กœ ์„ค์ •
  const resizedKey = 'images/resized/' + srcKey.split('/').pop();

  // ์›๋ณธ ์ด๋ฏธ์ง€ S3์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ
  try {
    const params = { Bucket: srcBucket, Key: srcKey };
    var origimage = await s3.getObject(params).promise();

    const originalImageSize = origimage.Body.length;
    console.log(`Original image size: ${originalImageSize} bytes`);

  } catch (error) {
    console.log(error);
    return;
  }

  const width = 200; // ๋ฆฌ์‚ฌ์ด์ฆˆ ๋„ˆ๋น„ ์„ค์ •

  try {
    // ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง•
    var buffer = await sharp(origimage.Body).resize(width).toBuffer();

    const resizedImageSize = buffer.length;
    console.log(`Resized image size: ${resizedImageSize} bytes`);

  } catch (error) {
    console.log(error);
    return;
  }

  try {
    // ๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ด๋ฏธ์ง€๋ฅผ S3์— ์—…๋กœ๋“œ
    const destparams = {
      Bucket: dstBucket,
      Key: resizedKey,
      Body: buffer,
      ContentType: "image"
    };

    // ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ˆ˜ํ–‰
    const putResult = await s3.putObject(destparams).promise();

  } catch (error) {
    console.log(error);
    return;
  }

  console.log('Successfully resized ' + srcBucket + '/' + srcKey + 
    ' and uploaded to ' + dstBucket + '/' + resizedKey);
};

 

S3์— ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•˜๋ฉด CloudWatch ๋กœ๊ทธ๋ฅผ ํ†ตํ•ด ์ด๋ฏธ์ง€๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ฆฌ์‚ฌ์ด์ง•๋˜์–ด ์ฒ˜๋ฆฌ๋˜์—ˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. S3์˜ resized ๋””๋ ‰ํ† ๋ฆฌ์—์„œ๋„ ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ €์žฅ๋˜์—ˆ๋‹ค.

 

 


 

์˜ค๋ฅ˜ & ํ•ด๊ฒฐ ๊ณผ์ •

1. AWS Lambda์—์„œ sharp ๋ชจ๋“ˆ ์˜ค๋ฅ˜ 

2025-03-26T12:24:49.529Z	undefined	ERROR	Uncaught Exception 	
{
    "errorType": "Error",
    "errorMessage": "Could not load the \"sharp\" module using the linux-x64 runtime\nPossible solutions:\n- Ensure optional dependencies can be installed:\n    npm install --include=optional sharp\n- Ensure your package manager supports multi-platform installation:\n    See https://sharp.pixelplumbing.com/install#cross-platform\n- Add platform-specific dependencies:\n    npm install --os=linux --cpu=x64 sharp\n- Consult the installation documentation:\n    See https://sharp.pixelplumbing.com/install",
    "stack": [
        "Error: Could not load the \"sharp\" module using the linux-x64 runtime",
        "Possible solutions:",
        "- Ensure optional dependencies can be installed:",
        "    npm install --include=optional sharp",
        "- Ensure your package manager supports multi-platform installation:",
        "    See https://sharp.pixelplumbing.com/install#cross-platform",
        "- Add platform-specific dependencies:",
        "    npm install --os=linux --cpu=x64 sharp",
        "- Consult the installation documentation:",
        "    See https://sharp.pixelplumbing.com/install",
        "    at Object.<anonymous> (/var/task/node_modules/sharp/lib/sharp.js:113:9)",
        "    at Module._compile (node:internal/modules/cjs/loader:1554:14)",
        "    at Object..js (node:internal/modules/cjs/loader:1706:10)",
        "    at Module.load (node:internal/modules/cjs/loader:1289:32)",
        "    at Function._load (node:internal/modules/cjs/loader:1108:12)",
        "    at TracingChannel.traceSync (node:diagnostics_channel:322:14)",
        "    at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)",
        "    at Module.require (node:internal/modules/cjs/loader:1311:12)",
        "    at require (node:internal/modules/helpers:136:16)",
        "    at Object.<anonymous> (/var/task/node_modules/sharp/lib/constructor.js:10:1)"
    ]
}

 

sharp๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค. ์ด๋Š” ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์ €์ˆ˜์ค€ ์ž‘์—…์„ ๊ณ ์†์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ปดํŒŒ์ผ๋œ ๋„ค์ดํ‹ฐ๋ธŒ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค. sharp ๋ชจ๋“ˆ์€ ์šด์˜ ์ฒด์ œ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์ปดํŒŒ์ผ๋˜๋Š”๋ฐ MacOS์—์„œ ๋นŒ๋“œ๋œ sharp ๋ชจ๋“ˆ์€ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ฝ”๋“œ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ์ปดํŒŒ์ผ๋˜๊ธฐ ๋•Œ๋ฌธ์— Linux ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋Š” AWS Lambda์—์„œ๋Š” ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๊ฒƒ์ด๋‹ค.

chatgpt ์— ๋ฌผ์–ด๋ดฃ๋”๋‹ˆ 2๊ฐ€์ง€ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•์„ ์ œ์‹œํ–ˆ๊ณ , ๋‘๊ฐœ ๋‹ค ์‹œ๋„ํ•ด๋ดค๋‹ค.

 

 

1. Lambda Layer ์‚ฌ์šฉ

2. Docker๋ฅผ ์ด์šฉํ•œ ๋นŒ๋“œ

 

AWS Lambda์—์„œ sharp ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด Lambda Layer๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๊ณ  Docker๋ฅผ ์ด์šฉํ•ด ๋นŒ๋“œํ•˜์—ฌ ๋ฐฐํฌํ–ˆ์ง€๋งŒ ์—ฌ์ „ํžˆ ๋™์ผํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์•„๋ž˜ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” zip ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•œ ํ›„, Lambda ์ฝ”๋“œ๋กœ ์—…๋กœ๋“œํ•˜๊ณ , index.js ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

https://github.com/dmswn1004/AWS-Lambda-S3-Image-Resizing

 

GitHub - dmswn1004/AWS-Lambda-S3-Image-Resizing: AWS Lambda ํŠธ๋ฆฌ๊ฑฐ + S3 ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ํ•จ์ˆ˜

AWS Lambda ํŠธ๋ฆฌ๊ฑฐ + S3 ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ํ•จ์ˆ˜. Contribute to dmswn1004/AWS-Lambda-S3-Image-Resizing development by creating an account on GitHub.

github.com

 

๋˜๋Š” ํ•ด๋‹น zip ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ›„, ์—…๋กœ๋“œ ํ•˜์‹œ๊ณ  ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ๋ฐ”๊ฟ”์ฃผ์…”๋„ ๋ฉ๋‹ˆ๋‹ค.

AWS-Lambda-S3-Image-Resizing-main.zip
14.09MB

 

๋งŒ์•ฝ ์œ„ ๋ฐฉ๋ฒ•์œผ๋กœ๋„ ํ•ด๊ฒฐ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์œˆ๋„์šฐ ์šด์˜์ฒด์ œ์—์„œ ๋‹ค์‹œ ์‹œ๋„ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ํ•˜๋‚˜์˜ ๋ฐฉ๋ฒ•์ด๋‹ค. ์œˆ๋„์šฐ ํ™˜๊ฒฝ์—์„œ ๋นŒ๋“œํ•œ sharp ๋ชจ๋“ˆ์ด Lambda์˜ ๋ฆฌ๋ˆ…์Šค ํ™˜๊ฒฝ๊ณผ ํ˜ธํ™˜๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

[์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์„ ๋ธ”๋กœ๊ทธ]

https://velog.io/@dgh06175/AWS-LambdaEdge-sharp-trouble-shooting

 

AWS lambda ์—์„œ sharp ๋ชจ๋“ˆ ์‚ฌ์šฉ ์‹œ Could not load the \"sharp\" module using the linux-x64 ์—๋Ÿฌ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์‚ฝ์งˆ๋กœ ์‚ฝ์งˆ ๋•๊ธฐ

velog.io

 

 

 

2. S3 ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ํ›„ ๋ฌดํ•œ ์—…๋กœ๋“œ ์˜ค๋ฅ˜

S3์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ง•ํ•˜์—ฌ ์—…๋กœ๋“œํ•  ๋•Œ, source ๋ฒ„ํ‚ท๊ณผ destination ๋ฒ„ํ‚ท์ด ๋™์ผํ•œ ๊ฒฝ์šฐ ๋ฌดํ•œ ์—…๋กœ๋“œ ํ˜„์ƒ์ด ๋ฐœ์ƒํ–ˆ๋‹ค. ์ด ๋ฌธ์ œ๋Š” ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๊ฐ€ ๋‹ค์‹œ ์›๋ณธ ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅ๋˜์–ด ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๊ณ„์†๋˜๋Š” ์ƒํ™ฉ์ด์—ˆ๋‹ค. 

 

 

 

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด, ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๋ฅผ ์›๋ณธ ๋””๋ ‰ํ† ๋ฆฌ์ธ origin ์—์„œ resized ๋””๋ ‰ํ„ฐ๋ฆฌ๋กœ ์ €์žฅํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์›๋ณธ ๋””๋ ‰ํ† ๋ฆฌ์˜ ์ด๋ฏธ์ง€๋Š” ๋ฆฌ์‚ฌ์ด์ง• ์ž‘์—…์„ ๊ฑฐ์ณ resized ๋””๋ ‰ํ† ๋ฆฌ์— ์ €์žฅ๋˜๋ฉฐ, ๋” ์ด์ƒ ์›๋ณธ ์ด๋ฏธ์ง€์— ๋Œ€ํ•œ ๋ฆฌ์‚ฌ์ด์ง• ์ž‘์—…์ด ๋ฐ˜๋ณต๋˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.

 

๋žŒ๋‹ค ํ•จ์ˆ˜ ์ฝ”๋“œ์˜ resized ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋ณ„๋„๋กœ ์„ค์ •ํ•จ์œผ๋กœ์จ ๋ฌดํ•œ ์—…๋กœ๋“œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. 

  // ๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ด๋ฏธ์ง€ ์ €์žฅ ๊ฒฝ๋กœ ์„ค์ •
  const resizedKey = 'images/resized/' + srcKey.split('/').pop();