AWS에는 다양한 서비스가 있다. 그 중에서 사이드 프로젝트나 실제 업무에서 자주 쓰이는 S3에 대해 알아보자
S3는 Simple Storage Service의 약자이다. 확장성과 데이터 가용성, 보안 및 성능을 제공하는 객체 스토리지 서비스인데, 쉽게 말해 파일 저장 서버라고 보면 된다.
서버에 데이터를 저장할 때 DB 레코드에 저장을 한다. 텍스트나 숫자와 같이 간단한 데이터 타입은 괜찮지만, 이미지나 동영상 같이 파일 크기도 큰 파일은 어떻게 저장을 할까?
DB에 BLOB이라는 포맷이 존재하지만 이걸 조회하는건 너무나 비효율적이기 때문에 실제로는 파일의 링크를 저장하고 그 링크를 불러오는 방식으로 진행한다.
구조를 나타내면 위 그림처럼 볼 수 있다. 이를 정리하면 아래와 같다.
- 클라이언트에서 API 서버로 이미지 요청
- API 서버에서 DB 레코드 조회
- 조회된 레코드에서 이미지 링크 반환
- 클라이언트에서 S3로 이미지 요청
- S3에서 이미지 다운로드
S3의 개념은 이렇고 이제 실제로 이미지를 업로드해보자
먼저 AWS S3에서 버켓을 만든다. 버켓은 EC2의 인스턴스와 비슷한 개념이다.
AWS의 S3에 들어가서 오른쪽 상단에 보면 버켓 만들기가 있다.
만들기 버튼을 클릭해서 들어가면 버켓의 이름을 정해야 한다. 대문자와 대시(-), 언더바(_)를 제외한 특수문자는 불가능하니 참고하자
객체 소유권에서 ACL 활성화를 선택하고 퍼블랙 액세스 차단을 비활성화 시켜야 다른 사용자가 업로드 할 수 있다.
IAM 서비스에 들어가서 사용자를 추가한다. admin이나 전체 서비스에 접근 가능한 계정은 보안에 위험이 있을 수 있으므로, S3만 접근 가능한 계정을 만들 것이다.
액세스 유형은 키나 암호를 설정할 수 있는데, AWS-SDK로 접근할 때는 액세스 키 방식을 사용해야 한다.
권한 설정에서 기존 정책 중 S3 Full Access를 선택한다.
사용자 생성을 완료하고 나면 위와 같이 액세스 키와 시크릿 키를 받을 수 있다. 해당 페이지를 벗어나면 다시 확인할 수 없으니 보관할 수 있는 곳에 저장해두자
Node.js에서 S3에 이미지를 업로드 하기 위해서 직접 SDK로 연결할 수도 있지만 Multer라는 라이브러리를 활용할 수 있다.
- aws-sdk : AWS Software Development Kit
- multer : node.js 이미지 업로드 라이브러리
- multer-s3 : multer 전용 s3 이미지 업로드 라이브러리
필요한 모듈을 npm install을 통해 다운받고 s3 객체를 생성해서 config 설정을 해야 한다. regeion에는 버켓을 사용하는 지역과, accessKeyId에는 사용자 액세스 키를, secretAccessKey에는 사용자 시크릿 키를 입력해주면 된다.
다음으로 multer 객체를 생성해서 S3 버켓과 연결시킬 데이터를 입력해주면 된다.
storage는 multerS3로 설정하고, 버켓 이름은 내가 생성한 버켓명을 적는다.
콜백함수의 두 번째 인자에 경로를 포함한 파일명을 적어주면 설정 끝이다.
const uploadImage = multer({
storage: multerS3({
s3 : s3,
bucket : process.env.AWS_BUCKET,
contentType: multerS3.AUTO_CONTENT_TYPE,
key : (req, file, callback) => {
// 콜백 함수 두 번째 인자에 파일명(경로 포함)을 입력
callback(null, `folder/${fileName}`);
},
acl : 'public-read-write'
})
});
실제 서버로 이미지 업로드를 할 때는 내가 만든 multer 객체의 내장함수 array나 single을 호출하여 사용한다.
위 사진처럼 array를 호출할 경우 두 번째 인자에 최대 업로드 개수를 설정할 수 있다.
테스트 후 AWS S3의 버켓에 들어가보면 업로드한 이미지가 잘 올라간 것을 볼 수 있다.
Multer-S3에는 다양한 옵션이 있는데, 필요한 옵션들은 검색 후 사용하면 된다.
내가 사용했던 옵션은 limits로 이미지 용량 제한이 있었다.
아래는 예제로 사용한 소스코드이다. 확장자 검사는 옵션이 아니라 직접 작성해서 넣은 것이다.
const aws = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');
const path = require('path');
const s3 = new aws.S3({
region : process.env.AWS_S3_REGION,
accessKeyId : process.env.AWS_S3_ACCESS_KEY,
secretAccessKey: process.env.AWS_S3_SECRET_KEY
});
// 확장자 검사 목록
const allowedExtensions = ['.png', '.jpg', '.jpeg', '.bmp', '.gif'];
// multer 객체 생성
const uploadImage = multer({
storage: multerS3({
s3 : s3,
bucket : process.env.AWS_BUCKET,
contentType: multerS3.AUTO_CONTENT_TYPE,
key : (req, file, callback) => {
const userId = req.verifiedToken.userInfo;
// 오늘 날짜 구하기
const today = new Date();
const currentYear = today.getFullYear();
const currentMonth = today.getMonth() + 1;
const currentDate = today.getDate();
const date = `${currentYear}-${currentMonth}-${currentDate}`;
// 임의번호 생성
let randomNumber = '';
for (let i = 0; i < 8; i++) {
randomNumber += String(Math.floor(Math.random() * 10));
}
// 확장자 검사
const extension = path.extname(file.originalname).toLowerCase();
if (!allowedExtensions.includes(extension)) {
return callback(new Error('확장자 에러'));
}
// folder라는 파일 내부에 업로드한 사용자에 따라 임의의 파일명으로 저장
callback(null, `folder/${userId}_${date}_${randomNumber}`);
},
// acl 권한 설정
acl : 'public-read-write'
}),
// 이미지 용량 제한 (5MB)
limits: {
fileSize: 5 * 1024 * 1024
}
});