S3 & Cloudfront & Lambda@edge로 이미지 관리(3) - Lambda@edge 설정

김지원·2021년 11월 8일
0

image

목록 보기
3/3

Lambda@edge 설정

1. 권한 생성

  • Lambda@edge가 여러 서비스를 연결할 수 있도록 역할을 부여해주어야 합니다.
  • 일단 Lambda@edge와 연결 될 서비스를 찾아보도록 하겠습니다.
  1. 이미지를 압축하는 역할이기 때문에 S3에서 파일을 get합니다.
  2. cloudfront와 연결해야 합니다.
  3. 잘 작동했는지 log 확인해야 합니다.

결론적으로 이런 정책을 만들어야합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole",
                "lambda:GetFunction",
                "lambda:EnableReplication",
                "cloudfront:UpdateDistribution",
                "s3:GetObject",
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams"
            ],
            "Resource": "*"
        }
    ]
}

1번을 위해 s3: GetObject
2번을 위해 cloudfront:UpdateDistribution,lambda:GetFunction, lambda:EnableReplication
3번을 위해 log관련 정책을 넣었습니다.

조금만 자세히 설명드리면

lambda:GetFunction 에서는 cloudfront 이벤트 발생 시 ARN을 지정하는 역할을 하고
lambda:EnableReplication 에서는 복제 서비스의 구성을 가져올 수 있는 권한을 부여합니다.

자세한 내용

2. IAM 역할 생성

  • 역할에서 사용할 정책을 생성했으니 IAM 역할을 생성해줍니다.
  • 위에서 만든 정책을 연결한 후 서비스의 주체를 설정하기 위해 신뢰관계를 수정해줍니다.
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": {
            "Service": [
               "lambda.amazonaws.com",
               "edgelambda.amazonaws.com"
            ]
         },
         "Action": "sts:AssumeRole"
      }
   ]
}

이렇게 바꿔주도록 합니다. 역할 부분이 끝이 났습니다.

3. lambda@edge 생성

lambda@edge는 버지니아 북부에서 생성해야합니다.

  • 지역을 버지니아 북부로 바꾼 후 lambda를 만들어줍니다.
  • 일반 구성은 제한 시간 10초, 메모리 128MB로 해줍니다.

🙄 여기서 용량이 커 오래걸리게 된다면 제한 시간을 늘려가면서 컨트롤해야 합니다.

4. 코드 작성

⭐ 저는 이미지 압축으로 sharp라는 모듈을 사용할 것입니다.

여기서 문제점이 하나 있습니다.

lambda는 linux기반이기 때문에 linux용 sharp를 설치해야합니다.

window 기준으로 아래의 코드를 이용하시면 됩니다.😎

npm install
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp

index.js

"use strict";

const querystring = require("querystring"); // Don't install
const AWS = require("aws-sdk"); // Don't install
const Sharp = require("sharp"); //Linux용 install
const convert = require("heic-convert"); //heic 파일 jpeg로 변환용

const allowedExtension = [
  "jpg",
  "jpeg",
  "png",
  "webp",
  "heic"
];

const MAX_WIDTH = 1280;
const MAX_HEIGHT = 720;
// 버킷 찾기
const S3 = new AWS.S3({
  region: "ap-northeast-2",
});
const BUCKET = "u-market";

exports.handler = (event, context, callback) => {
  const { request, response } = event.Records[0].cf;
  // Parameters are w, h, f, q and indicate width, height, format and quality.
  const params = querystring.parse(request.querystring);

  // query로 받은 이미지 압축 크기와 높이
  if (!params.w && !params.h) {
    return callback(null, response);
  }

  // uri와 이름, 확장자찾기
  const { uri } = request;
  const [, imageName, extension] = uri.match(/\/?(.*)\.(.*)/);

  // Init variables
  let width;
  let height;
  let format;
  let quality; // Sharp는 이미지 포맷에 따라서 품질(quality)의 기본값이 다릅니다.
  let resizedImage;

  // 사이즈 구별
  width = parseInt(params.w, 10) ? parseInt(params.w, 10) : MAX_WIDTH;
  height = parseInt(params.h, 10) ? parseInt(params.h, 10) : MAX_HEIGHT;

  // Init quality.
  if (parseInt(params.q, 10)) {
    quality = parseInt(params.q, 10);
  }

  // 확장자 바꾸는 코드
  format = params.f ? params.f : extension;
  format = format === "jpg" ? "jpeg" : format;
  format = format === "HEIC" ? "jpeg" : format;
  // 이미지 파일이 아니라면 500 error 
  if (!allowedExtension.includes(extension)) {
    response.status = "500";
    response.headers["content-type"] = [
      { key: "Content-Type", value: "text/plain" },
    ];
    response.body = `${extension} is not allowed`;
    callback(null, response);
    return;
  }

  // For AWS CloudWatch.
  console.log(`parmas: ${JSON.stringify(params)}`); // Cannot convert object to primitive value.
  console.log(`name: ${imageName}.${extension}`); // Favicon error, if name is `favicon.ico`.
  // S3에서 이미지 받아오는 코드
  S3.getObject({
    Bucket: BUCKET,
    Key: decodeURI(imageName + "." + extension),
  })
    .promise()
    .then((data) => {
    // HEIC 파일 JPEG로 convert
      if (extension === "HEIC" || extension === "heic") {
        return convert({
          buffer: data.Body,
          format: "JPEG",
          quality: 1,
        });
      } else {
        return data.Body;
      }
    })
    .then((input) => {
    
      resizedImage = Sharp(input);
      resizedImage
        .metadata()
        .then((meta) => {
          return resizedImage
            .resize(width, height)
            .toFormat(format, {
              quality,
            })
            .toBuffer();
        })
        .then((buffer) => {
          // response에 리사이징 한 이미지를 담아서 반환
          response.status = 200;
          response.body = buffer.toString("base64");
          response.bodyEncoding = "base64";
          response.headers["content-type"] = [
            { key: "Content-Type", value: `image/${format}` },
          ];
          callback(null, response);
        });
    })
    .catch((error) => {
      response.status = "404";
      response.headers["content-type"] = [
        { key: "Content-Type", value: "text/plain" },
      ];
      response.body = `${request.uri} is not found. and ${error}`;
      callback(null, response);
    });
};

sharp를 설치하고 나면 index.js, package.json, package-lock.json, node_modules

이 4가지 파일,폴더를 압축하여 lambda에 올려줍니다.

저는 index.js에 handler를 넣었기 때문에 핸들러를 index.handler로 설정해주었습니다.

5. 배포

  • lambda의 오른쪽에 작업에서 lmabda@edge 배포를 클릭합니다.

  • 여기서 cloudfront를 선택 후 캐시동작에서 캐시동작을 실행시킬 S3 폴더를 지정합니다.
  • cloudfront 이벤트는 응답을 받을 것이기 때문에 오리진 응답으로 해줍니다.

6. 확인

아까 선택한 동작의 주소에 들어가서 함수 연결에 lambda@edge의 주소가 적혀있다면 성공입니다!

Hit from cloudfront까지 뜬다면 성공적으로 cloudfront에 배포된 것입니다. 🙇‍♂️

profile
backend-developer

0개의 댓글