AWS의 람다(Lambda) / API 게이트웨이(API Gateway) / S3
GCP의 앱 엔진(App Engine) / 파이어베이스(Firebase) / 클라우드 펑션스(Cloud Functions) / 클라우드 스토리지(Cloud Storage)
FaaS(Function as a Service) : 람다와 클라우드 펑션스는 특정한 동작을 수행하는 로직을 저장하고 요청이 들어올 때 로직을 실행함
5. 버킷 만들기 버튼을 눌러 버킷 생성
6. 웹사이트에서 버킷의 이미지를 불러올 수 있도록 권한을 부여
- 버킷을 클릭하여 권한 > 버킷 정책 메뉴 선택하고 버킷 정책 편집기에 아래의 코드 생성 후 저장
{
"Version": "2022-01-24",
"Statement" : [
{
"Sid" : "AddPerm",
"Effect" : "Allow",
"Principal" : "*",
"Action" : [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::당신의버킷명/*"
}
]
}
7. AWS 액세스 키 발급
8. 프로젝트에 multer-s3 패키지, aws-sdk 패키지 설치
multer-s3 : multer에서 S3로 업로드할 수 있게 해주는 패키지
aws-sdk : AWS의 기능을 노드에서 사용할 수 있게 해주는 패키지, multer-s3 패키지에서 사용
npm i multer-s3 aws-sdk
.env파일에 발급 받은 키 파일 안에 적힌 액세스 키 ID와 보안 액세스 키를 복사
...
S3_ACCESS_KEY_ID = 액세스키ID
S3_SECRET_ACCESS_KEY = 액세스키
nodebird/routes/post.js
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const AWS = require('aws-sdk'); //추가
const multerS3 = require('multer-s3'); //추가
const { Post, Hashtag } = require('../models');
const { isLoggedIn } = require('./middlewares');
const router = express.Router();
try {
fs.readdirSync('uploads');
} catch (error) {
console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
fs.mkdirSync('uploads');
}
// AWS/config.update 메서드로 AWS에 관한 설정을 함
// accessKeyID, secretAccessKey, region 입력
AWS.config.update({
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: 'ap-northeast-2',
});
//multer의 storage 옵션을 multerS3로 교체
// multerS3 -> s3 객체, 버킷명, 파일명(key) 입력
// key 옵션으로 저장할 파일명을 설정하고 버킷 내부에서 original 폴더 아래에 생성
const upload = multer({
storage: multerS3({
s3: new AWS.S3(),
bucket: 'nodebird',
key(req, file, cb) {
cb(null, `original/${Date.now()}${path.basename(file.originalname)}`);
},
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
// req.file.location에 S3 버킷 이미지 주소가 담겨 있음
// 해당 주소를 클라이언트로 보냄
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
console.log(req.file);
res.json({ url: req.file.location });
});
...
S3에 올린 이미지를 리사이징한 후 줄어든 이미지를 다시 S3에 저장하는 작업을 수행할 것입니다.
1. nodebird 폴더 외부에 aws-upload 폴더를 만들고 package.json을 작성
{
"name": "aws-upload",
"version": "1.0.0",
"description": "Lambda 이미지 리사이징",
"main": "index.js",
"author": "ZeroCho",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.634.0",
"sharp": "^0.25.1"
}
}
2. aws-upload/.gitignore
node_modules
3. aws-upload/index.js
const AWS = require('aws-sdk');
const sharp = require('sharp');
const s3 = new AWS.S3();
//handler 함수가 람다 호출 시 실행되는 함수
// event : 호출 상황에 대한 정보가 담겨 있음
// context : 실행되는 함수 환경에 대한 정보가 담겨 있음
// callback : 함수가 완료되었는지 람다에게 알림
// callback의 첫번째 인수 error 여부 / 두번째 인수 반환값
exports.handler = async (event, context, callback) => {
// event 객체로부터 버킷 이름(Bucket)과 파일 경로(Key)를 받아옴
// 이를 통해 파일명과 확장자도 얻음
const Bucket = event.Records[0].s3.bucket.name;
const Key = event.Records[0].s3.object.key;
const filename = Key.split('/')[Key.split('/').length - 1];
const ext = Key.split('.')[Key.split('.').length - 1];
const requiredFormat = ext === 'jpg' ? 'jpeg' : ext; // sharp에서는 jpg 대신 jpeg 사용합니다.
console.log('name', filename, 'ext', ext);
// s3.getObject 메서드로 버킷으로부터 파일을 불러옴
// s3Object.Body에 파일 버퍼가 담겨 있음
try {
const s3Object = await s3.getObject({ Bucket, Key }).promise(); // 버퍼로 가져오기
console.log('original', s3Object.Body.length);
// sharp 함수를 파일 버퍼에 넣고 resize 메서드로 크기 지정
// resize(가로, 세로, 조정 기준)
// fit:inside -> 가로 세로 사이즈 안에 딱 맞게 이미지 조정
// toBuffer 메서드는 리사이징된 이미지 결과를 버퍼로 출력
const resizedImage = await sharp(s3Object.Body) // 리사이징
.resize(200, 200, { fit: 'inside' })
.toFormat(requiredFormat)
.toBuffer();
// s3.putObject 메서드로 리사이징된 이미지를 thumb 폴더에 저장
await s3.putObject({
Bucket,
Key: `thumb/${filename}`,
Body: resizedImage,
}).promise();
console.log('put', resizedImage.length);
return callback(null, `thumb/${filename}`);
} catch (error) {
console.error(error);
return callback(error);
}
};
4. 람다에 배포
Lightsail에서 빌드한 후 S3로 배포하고, 람다는 S3에서 배포된 파일을 가져와 함수로 만듦
1. 깃허브에 aws-upload 레포지터리 생성 후 push
2. Lightsail 인스턴스 SSH에 접속하여 깃허브 레포지터리 clone 받음
3. npm i 후 aws-upload 폴더 아래의 모든 파일을 압축하여 aws-upload.zip 파일 만듦
4. Lightsail에서 S3로 파일 업로드 (aws-cli 설치하고 aws configure 명령을 입력 후 발급받았던 키 파일 정보 입력)
5. aws-cli를 사용해 aws-upload.zip 업로드
5. 람다 서비스 설정
1. AWS 홈페이지 - 제품 - 주요 서비스 - AWS Lambda 클릭
2. 함수 생성 버튼 클릭
1) 함수 이름 : node-deploy
2) Runtime : Node.js 12.x
3. Amazon S3에서 파일 업로드
1) Amazon S3 링크 URL : https://버킷명.s3.지역명.amazonaws.com/파일명
4. 기본 설정 - 편집 버튼 클릭
1) 핸들러 이름 : '파일명.함수명' (책에서는 index.handler)
2) 메모리, 제한 시간이 부족할 경우 늘림
3) 실행 역할 : AWS 정책 템플릿에서 새 역할 생성을 선택
4) 역할 이름은 자유, 정책 템플릿에서 Amazon S3 객체 읽기 전용 권한 추가
5. 트리거 추가 : S3에 이미지를 업로드 할떄마다 람다 함수가 동작하도록 함
1) 트리거 구성 : S3 선택
2) 버킷 : 직접 만들었던 버킷 선택 (책에서는 nodebird)
3) 이벤트 유형 : 모든 객체 생성 이벤트 로 설정
4) 접두사 : original 폴더 안에 파일이 업로드되었을 때 함수를 트리거할 것이므로 original/ 로 설정
6. nodebird/routes/post.js
기존 주소에서 original 폴더를 thumb 폴더로 교체
...
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
console.log(req.file);
const originalUrl = req.file.location;
const url = originalUrl.replace(/\/original\//, '/thumb/');
res.json({ url, originalUrl });
});
...
7. nodebird/views/main.html
img 태그에 onerror 속성을 붙여 리사이징된 이미지를 로딩하는 데 실패하면 원본 이미지를 사용하도록 함
...
{% if twit.img %}
// 아래 div 태그 추가
<div class="twit-img">
<img
src="{{twit.img}}"
onerror="this.src = this.src.replace(/\/thumb\//, '/original/');"
alt="섬네일"
/>
</div>
...
{% block script %}
<script>
if (document.getElementById('img')) {
document.getElementById('img').addEventListener('change', function(e) {
const formData = new FormData();
formData.append('img', this.files[0]);
axios.post('/post/img', formData)
.then((res) => {
document.getElementById('img-url').value = res.data.url;
document.getElementById('img-preview').src = res.data.originalUrl; // 추가
document.getElementById('img-preview').style.display = 'inline';
})
이미지 미리보기(img-preview) 시에는 원본 이미지를 보여주고, 이미지를 저장한 후에는 리사이징된 이미지를 보여주도록 함 -> 이미지 업로드와 리사이징 간의 시간차 때문에 이미지가 보이지 않는 현상을 해결하기 위함
- 구글 클라우드 스토리지에 이미지를 업로드하기
1. 구글 클라우드 플랫폼 - Storage 선택 - 버킷 생성 버튼
- 버킷 이름 지정 : nodebird
- 데이터 저장 위치 선택 : 위치 유형 Region / 위치는 Seoul
- 버킷 권한 수정
2. 클라우드 스토리지 접근 키 발급받기
- 사용자 인증 정보 화면 - 사용자 인증 정보 만들기 버튼 클릭
- 서비스 계정 이름 : nodebird
- 서비스 계정 : nodebird
- 역할 : 저장소 관리자
- 키 만들기 버튼 클릭 -> 다운로드 받은 JSON 파일을 NodeBird 프로젝트 폴더로 복사
3. multer-google-storage 패키지 설치
npm i multer-google-storage
nodebird/routes/post.js
const express = require('express');
const multer = require('multer');
const fs = require('fs');
const multerGoogleStorage = require('multer-google-storage');
const upload = multer({
storage: multerGoogleStorage.storageEngine({
bucket: 'nodebird',
projectId: 'node-deploy-270114',
keyFilename: 'node-deploy-270114-b024dbed754a.json',
}),
limits: { fileSize: 5 * 1024 * 1024 },
});
storage 속성 안에 multerGoogleStorage를 사용하여 multer-google-storage 사용
bucket, projectId, keyFilename : 버킷명, 프로젝트 ID, 키 파일명
gcp-upload/package.json
nodebird 폴더 외부에 gcp-upload 폴더 생성 후 package.json 파일 작성
{
"name": "gcp-upload",
"version": "1.0.0",
"description": "Cloud Functions 이미지 리사이징",
"main": "index.js",
"author": "ZeroCho",
"license": "ISC",
"dependencies": {
"@google-cloud/storage": "^1.6.0",
"gm": "^1.23.1",
"sharp": "^0.25.1"
}
}
gcp-upload/.gitignore
node_modules
gcp-upload/index.js
1. git-upload 깃 레포지터리 생성하고 지금까지 작성한 코드를 push
2. 컴퓨터 엔진 인스턴스 SSH에 접근하여 깃허브 레포지터리를 clone 받음
3. clone 받은 후 npm i 를 하고 gcp-upload 폴더 아래의 모든 파일을 압축하여 gcp-upload.zip 파일 생성
4. 컴퓨트 엔진에서 클라우드 스토리지로 파일 업로드
- 함수 이름 : gcp-upload
- 트리거 : Cloud Storage
- 이벤트 유형 : 완료/생성
- 버킷 : 본인이 만든 버킷 선택
- 소스코드 : Cloud Storage의 ZIP을 클릭해 방금 업로드한 파일 업로드
- 실행할 함수 : resizeAndUpload (*index.js의 exports.resizeAndUpload 이 부분과 동일해야 함)
nodebird/routes/post.js
...
router.post('/img', isLoggedIn, upload.single('img'), (req, res) => {
console.log(req.file);
const filePath = req.file.path.split('/').splice(0, 3).join('/');
const originalUrl = `${filePath}/${req.file.filename}`;
const url = originalUrl.replace(/\/original\//, '/thumb/');
res.json({ url, originalUrl });
});
...
nodebird/views/main.html
...
{% if twit.img %}
<div class="twit-img">
<img
src="{{twit.img}}"
onerror="this.src = this.src.replace(/\/thumb\//, '/original/');"
alt="섬네일"
/>
</div>
{% endif %}
...
...
document.getElementById('img-preview').src = res.data.originalUrl;
...
[Node.js] 15장 AWS와 GCP로 배포하기 (0) | 2022.01.24 |
---|---|
[Node.js] 14장 CLI 프로그램 만들기 (0) | 2022.01.24 |
[Node.js] 13장 실시간 경매 시스템 만들기 (0) | 2022.01.17 |
[Node.js] 12장(2) 미들웨어와 소켓 연결하기 (0) | 2022.01.10 |
[Node.js] 12장 웹소켓으로 실시간 데이터 전송하기(1) (0) | 2022.01.06 |