16.1 서버리스
1. 서버리스 컴퓨팅 이해하기
⇒ 서버리스는 영어로 'serverless'이다. 'server(서버) + less(없는)' 이지만, 사실 서버가 없다는 것이 아니다.
클라우드 서비스가 대신 관리함으로 개발자/운영자가 서버를 관리하는 부담이 줄어든다는 의미이다.
- 서버리스 컴퓨팅을 할 때는 AWS ES2나 구글 컴퓨트 엔진(Google Compute Engine : GCE)과는 다르게
WM 인스턴스를 미리 구매하지 않아도 된다.
- 단순히 코드를 업로드 한 뒤, 사용량에 따라 요금을 지불하면 된다.
(함수처럼 호출할 때만 실행됨, FaaS(Function as a Service)
- 24시간 작동할 필요가 없는 서버인 경우, 서버리스 컴퓨팅을 사용하면 필요한 경우에만 실행되어 요금 절약 가능
- AWS : 람다(Lambda)나 게이트웨어(API Gateway), S3 등
- GCP : 클라우드 런(Cloud Run), 파이어베이스(Firebase), 클라우드 펑션스(Cloud Functions)
, 클라우드 스토리지(Clod Storage)
16.2 AWS S3 사용하기
1. AWS S3 사용해보기
2. 버킷 만들기 ( 버킷 만들기나 시작하기 버튼 클릭)
3. 버킷 리전 설정하기
⇒ 버킷 이름은 고유한 이름을 사용할 것
- 이름, 리전 정의하기. 권한에서 모든 퍼블릭 액세스 차단 체크박스 해제
- 실제 운영되는 서비스는 S3 앞에 다른 서비스를 둬 S3에 있는 파일에 접근해도 되는지 권한을 체크해
해당 파일에 접근할 수 있게 한다.
4. 버킷 생성 확인하기 ( 화면이 뜨면 생성된 버킷 클릭)
5. 버킷 정책 수정하기 (권한 - 버킷 정책 - 편집 메뉴 선택)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AddPerm",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::nodejsbook9488/*"
}
]
}
6. 내 보안 자격 증명하기(상단 메뉴 계정이름 클릭 - 내 보안 자격증명 메뉴 선택)
7. 액세스 키 발급 받기 (엑세스 키 섹션에서 새 엑세스 키 만들기 버튼 클릭)
⇒ 보안 액세스 키는 다시 볼 수 없으므로 아래 그림의 키 파일 다운로드 버튼 눌러 저장
9. aws-sdk로 S3 도입하기
⇒ multer-s3와 @aws-sdk/client-s3 패키지를 설치한 후 .env에 발급받은 보안키를 기입한다.
- 과금요소 : 1년간 저장용량 5GB, 데이터 로드 2만건, 데이터 업로드 2천건까지 무료(보안엑세스키 주의)
// .env
...
S3_ACCESS_KEY_ID=DKWOPJIDFOISJ1234JIOIJWF
S3_SECRET_ACCESS_KEY_ID=SDLKDFJOIWDJFP2IO3J1ORIFJ2OIEJ2ASDFQWSDS1
10. aws-sdk로 S3 도입하기
⇒ S3 Client로 AWS에 관한 설정을 한다.(ap-northeast-2는 서울 리전)
// routes/post.js
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { afterUploadImage, uploadPost } = require('../controller/post');
const { isLoggedIn } = require('../middlewares');
const { S3Client } = require('@aws-sdk/client-s3');
const multerS3 = require('multer-s3');
const router = express.Router();
try {
fs.readdirSync('uploads');
} catch (error) {
console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
fs.mkdirSync('uploads');
}
const s3 = new S3Client({
credentials : {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY_ID,
},
region : 'ap-northeast-2', // 지역코드
});
const upload = multer({
/*
storage : multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads/');
},
filename(req, file, cb) {
const ext = path.extname(file.originalname);
console.log("ext : " + ext);
console.log("file.originalname : " + ext);
console.log("path.basename(file.originalname, ext) : " + path.basename(file.originalname, ext));
cb(null, path.basename(file.originalname, ext)+ Date.now() + ext);
},
}),
*/
storage : multerS3({
s3,
bucket : 'nodejsbook9488',
key(req, file, cb) {
cb(null, `original/${Date.now()}_${file.originalname}`);
}
}),
limits : { fileSize : 5 * 1024 * 1024 },
});
// POST : /post/img
router.post('/img', isLoggedIn, upload.single('img'), afterUploadImage);
// form의 enctype이 multipart/form-data 일경우 빈 multer를 전달해줘야 submit이 정상처리된다.
const upload2 = multer();
router.post('/', isLoggedIn, upload2.none(), uploadPost); // csurf 처리
module.exports = router;
11. 이미지 업로드 시도하기 (http://localhost:8001에 접속해 로그인 후 이미지 업로드)
⇒ S3 버킷에 이미지가 업로드 된 것을 확인한다.
16.3 AWS Lambda 사용하기
1. 이미지 리사이징을 위해 람다 사용
⇒ 이미지 리사이징은 CPU를 많이 사용하기 때문에 기존 서버로 작업 시 무리가 간다.
- Lambda라는 기능을 사용 해 필요할 때만 서버를 실행해서 리사이징 처리한다.
- Lambda는 데이터 비용을 줄이기 위해 화질을 저하 시키고 용량을 대폭 줄인다.
2. 람다용 package.json과 .gitgnore 작성하기
// aws-upload/package.json
{
"name": "aws-upload",
"version": "0.0.1",
"description": "Lambda 이미지 리사이징",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "JaeikKim",
"license": "ISC",
"dependencies": {
"@aws-sdk/client-s3": "^3.504.0",
"sharp": "^0.33.2"
},
"devDependencies": {
"prettier": "^3.2.4"
}
}
// aws-upload/.gitgnore
node_modules
3. sharp로 리사이징하기 ( Sharp는 이미지 리사이징을 위한 라이브러리이다.)
// aws-upload/index.js
const sharp = require('sharp'); // 이미지 리사이징 라이브러리
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client();
// 람다 실행부분(event에 버킷과 데이터 정보가 들어있다)
exports.handler = async (event, context, callback) => {
const Bucket = event.Records[0].s3.bucket.name;
const Key = decodeURIComponent(event.Records[0].s3.object.key); // original/고양이.png
const filename = Key.split('/').at(-1);
const ext = Key.split('.').at(-1).toLowerCase();
// sharp에서는 확장자가 jpg면 jpeg로 변경해줘야 한다.
const requiredFormat = ext === 'jpg' ? 'jpeg' : ext;
console.log('name', filename, 'ext', ext);
try {
// s3.send(new GetObjectCommand({ Bucket, Key})) 로 버킷에서 이미지를 가져온다.
const getObject = await s3.send(new GetObjectCommand({ Bucket, Key}));
const buffers = [];
for await (const data of getObject.Body) {
buffers.push(data);
}
const imagebuffer = Buffer.concat(buffers);
console.log('put', imagebuffer.length);
// 리사이징 처리
const resizedImage = await sharp(imagebuffer)
.resize(200, 200, { fit : 'inside'})
.toFormat(requiredFormat) // 확장자 지정
.toBuffer(); // 버퍼로 변환
// s3.send(new PutObjectCommand({ ... }); 로 버킷에 이미지 데이터 저장
await s3.send(new PutObjectCommand({
Bucket,
Key : `thumb/${filename}`,
Body : resizedImage,
}));
// 람다 종료 및 응답데이터(첫번째자리는 에러자리, 두번째자리는 응답값자리) 전달
return callback(null, `thumb/${filename}`);
} catch (error) {
console.error(error);
return callback(error);
}
};
4. 코드 깃 허브로 전송하기
⇒ 먼저 GitHub에 aws-upload 리파지토리를 생성 및 업로드 후 Lightsail 인스턴스에서 클론한다.
// 콘솔
$ git init
$ git add .
$ git commit -m "Initial commit"
$ git remote add origin https://아이디:토큰@github.com/아이디/aws-upload
$ git push origin master
// ssh
$ git clone https://github.com/아이디/aws-upload
$ cd aws-uplaod
$ npm i
5. 코드 압축해서 S3으로 보내기
// ssh
$ sudo zip -r aws-upload.zip ./*
$ ls
aws-upload.zip ...
// ssh (S3에 파일을 업로드할 수 있는 권한 설정)
$ curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
$ sudo unzip awscliv2.zip
$ sudo ./aws/install
$ aws configure
AWS Access Key ID [None] : [키 아이디]
AWS Secret Access Key [None] : [시크릿 키 아이디]
Default region name [None] : ap-northeast-2
Default output format [None] : json
$ aws s3 cp "aws-uplaod.zip" s3://버킷명
6. 람다 서비스 설정하기 (컴퓨팅-Lambda)
7. 새함수 만들기(함수 생성 버튼 클릭)
⇒ 함수명은 node-deploy로, 런타임은 Node.js 20.x로 설정
- 역할은 템플릿에서 새 역할 생성 선택, S3 객체 읽기 전용 권한 부여
9. zip 파일 업로드하기
⇒ 코드 섹션에서 S3에 올린 파일을 선택
11. S3를 트리거로 설정하기 (좌측 S3를 트리거로 선택)
12. 트리거 상세 설정하기
13. NodeBird에 람다 연결하기
⇒ 기존 original 폴더 부분을 thumb(리사이징) 폴더로 교체처리한다.
// controller/post.js
const { Post, Hashtag } = require('../models');
exports.afterUploadImage = (req, res) => {
// res.json({ url : `/img/${req.file.filename}`})
console.log(req.file.location);
const originalUrl = req.file.location; // s3의 주소가 들어있다.
const url = originalUrl.replace(/\/original\//, '/thumb/');
res.json({ url, originalUrl });
};
...
// views/main.html
...
{% for twit in twits %}
<div class="twit">
<input type="hidden" value="{{twit.User.id}}" class="twit-user-id">
<input type="hidden" value="{{twit.id}}" class="twit-id">
<div class="twit-author">{{twit.User.nick}}</div>
{% if not followingIdList.includes(twit.User.id) and twit.User.id !== user.id %}
<button class="twit-follow">팔로우하기</button>
{% endif %}
<div class="twit-content">{{twit.content}}</div>
{% if twit.img %}
<div class="twit-img"><img src="{{twit.img}}" onerror="this.src = this.src.replace(/\/thumb\//, '/original/');" alt="섬네일"></div>
{% endif %}
</div>
{% endfor %}
...
<script>
if (document.getElementById('img')) {
document.getElementById('img').addEventListener('change', function(e) {
const formData = new FormData();
console.log(this, this.files);
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';
})
.catch((err) => {
console.error(err);
});
});
}
...
</script>
14. 이미지 업로드하기
⇒ 람다를 통해 이미지 리사이징 된 것을 확인하기
'BackEnd > Node' 카테고리의 다른 글
[노드교과서] 섹션 14. 15장 AWS에 배포해보기 (2) | 2024.02.02 |
---|---|
[노드교과서] 섹션 13. 14장 CLI 프로그램 만들기 (2) | 2024.01.31 |
[노드교과서] 섹션 12. 13장 실시간 경매 시스템 만들기(서버센트이벤트, 스케줄링) (2) | 2024.01.31 |
[노드교과서] 섹션 11. 12장 실시간 GIF 채팅방 만들기(웹소켓, Socket.IO) (0) | 2024.01.28 |
[노드교과서] 섹션 10. 11장 테스트 해보기(단위, 통합, 부하) (0) | 2024.01.25 |