AWS

[AWS] image resizing failure, trouble shooting

안녕하세요. ManVSCloud 김수현입니다.

오늘은 이전에 실패했던 image resizing 실패 원인을 확인 후
정상적으로 resizing 성공 후기를 남겨보려고 합니다.


First Cause

첫번째 원인을 찾았습니다.
CloudFront 트리거 구성 시 Origin Response가 되어야하는데 Request로 설정을 해두었던 것!

Lambda에서 변경 시 위에서 오리진 응답으로 변경하면 됩니다.

만약 Cloudfront에서 변경을 하실 경우 연결된 cloudfront에서 [Behaviors]-[경로 선택 및 Edit] 후 아래 CloudFront Event에서 Origin Response로 변경해줄 수 있습니다.

이 부분은 리눅서님의 도움을 받았습니다.
해당 부분이 Response가 되어야하는 이유는 인지하고 있었는데 다시 한번 포인트를 잡아주시고 놓치고 지나친 부분 찾아주신 리눅서님에게 감사의 인사 올립니다.


Find hints

위 첫번째 원인을 해결하고도 정상적으로 이미지 리사이징이 되지 않았습니다.
그래서 현재 이미지 리사이징이 어떻게 동작하고있고 특정 오류가 발생하는가 확인해보기로 했습니다.


우선 CloudFront의 Cache statistics와 Monitoring을 체크해보았고
각 오류 코드 및 Header값을 확인해보았습니다.

3xx의 정체는 301, 2xx는 200, 4xx는 403으로 보입니다.

http://주소/경로/tom.png?w=110&h=60&f=webp&q=90 [Viewer Request]
Status Code 로301 Move Permanently를 받으며 리다이렉트 되었습니다.
X-Cache : Redirect from cloudfront

https로 리다이렉트 되고 Status Code로 200을 받았습니다.
x-cache : Miss from cloudfront 엣지 로케이션 캐시에 tom.png?w=110&h=60&f=webp&q=90가 없어 Origin에 요청합니다. 여기서 Origin은 tom.png 이미지가 있는 S3 버킷!
이미지도 잘 데려왔습니다.

그런데 이미지 리사이징이 되지않은 상태로 넘어왔습니다.
User가 Cloudfront로 “Viewer request” 했고 (1. User → Cloudfront)
Miss from cloudfront라 Cloudfront가 Origin에게 “Origin request” 하고 (2. Cloudfront → Origin)
Origin으로부터 “Origin reponse” 후 (3. Origin → Cloudfront)
Cloudfront가 다시 User에게 “Viewer response” (4. Cloudfront → User) 되어
이미지를 받았다라고 했을 때 [1], [2], [4] 과정은 문제가 없었을 것이라 생각됩니다.

[1],[4] 요청과 응답에 대한 통신은 정상적으로 되었으며 Miss from cloudfront 응답에
[2] CloudFront는 Origin에게 요청을 했을 것입니다.

그렇다면 [3] 과정에서 정상적으로 동작하지 않는 것이 있다는 것인데?

Cloudfront 설정은 몇번을 반복하여 보아도 더 이상 잘못 설정한 것이 없었습니다.
그래서 Lambda 설정을 조금 더 검토해보기로 했습니다.

Second Cause

원인을 전혀 못찾다가 사내 상사의 도움으로 “Node.js 버전에 맞는 소스 코드를 사용했는가? 소스 코드가 버전에 맞지않아서 그럴 수 있다”라는 힌트를 얻게 되었습니다.

그런데 아무리 생각해봐도 다른 설정은 잘 되어있고 정말 소스 코드쪽이 크게 의심되어 바로 버전과 소스 코드 변경을 진행하였습니다.

Node.js 버전은 12.x 버전으로 변경하였고 소스 코드는 아래와 같이 수정하였습니다.

'use strict';

const querystring = require('querystring'); // Don't install.
const AWS = require('aws-sdk'); // Don't install.
const Sharp = require('sharp');

const S3 = new AWS.S3({
  region: 'ap-northeast-2'
});
const BUCKET = 'mybucket';

exports.handler = async (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);

  // Required width or height value.
  if (!params.w && !params.h) {
    return callback(null, response);
  }

  // Extract name and format.
  const { uri } = request;
  const [, imageName, extension] = uri.match(/\/?(.*)\.(.*)/);

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

  // Init sizes.
  width = parseInt(params.w, 10) ? parseInt(params.w, 10) : null;
  height = parseInt(params.h, 10) ? parseInt(params.h, 10) : null;

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

  // Init format.
  format = params.f ? params.f : extension;
  format = format === 'jpg' ? 'jpeg' : format;

  // 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`.

  try {
    s3Object = await S3.getObject({
      Bucket: BUCKET,
      Key: decodeURI(imageName + '.' + extension)
    }).promise();
  } catch (error) {
    console.log('S3.getObject: ', error);
    return callback(error);
  }

  try {
    resizedImage = await Sharp(s3Object.Body)
      .resize(width, height)
      .toFormat(format, {
        quality
      })
      .toBuffer();
  } catch (error) {
    console.log('Sharp: ', error);
    return callback(error);
  }

  const resizedImageByteLength = Buffer.byteLength(resizedImage, 'base64');
  console.log('byteLength: ', resizedImageByteLength);

  // `response.body`가 변경된 경우 1MB까지만 허용됩니다.
  if (resizedImageByteLength >= 1 * 1024 * 1024) {
    return callback(null, response);
  }

  response.status = 200;
  response.body = resizedImage.toString('base64');
  response.bodyEncoding = 'base64';
  response.headers['content-type'] = [
    {
      key: 'Content-Type',
      value: `image/${format}`
    }
  ];
  return callback(null, response);
};

개발쪽은 아직 미숙하여 소스 코드는 아래 링크를 참고하였습니다.

소스 코드 변환 후 lambda 재배포하여 다시 한 번 리사이징 테스트를 해보았습니다.


Success

이미지 리사이징이 정상적으로 완료되었습니다!
Node.js 버전과 소스 코드 변경 후 정상적으로 리사이징이 되네요

이번 이미지 리사이징을 계기로 개발쪽 공부도 해야겠다는 생각이 드는데 바쁜 일정으로 올해는 무리입니다…

그래도 이미지 리사이징 성공할 수 있어서 뿌듯합니다.

긴 글 읽어주셔서 감사합니다.

Previous Post Next Post

You Might Also Like

No Comments

Leave a Reply