aws sam, lambda 정리

  • nodejs 람다 기준

sam

  • sam cli로 로컬에서 람다를 작성하고 배포할 수 있다

실행환경

  • /var/task 에서 람다 함수가 실행된다
  • /opt/nodejs 에 레이어가 적재된다
  • /tmp 디렉터리는 유일한 쓰기 디렉터리이다
  • /tmp 디렉터리의 용량은 512MB이다
  • /tmp 디렉터리는 일회성이다

로컬에서 테스트

  • sam local start-api로 로컬에서 람다를 테스트해볼 수 있다
  • --skip-pull-image 옵션을 주어 실행 시간을 단축시킬 수 있다
  • 로컬에서 실행할 때 도커가 필요하다
  • 참고로 람다는 아마존 리눅스 2 환경에서 돌아간다

빌드

  • sam build 명령으로 설치되는 패키지에는 devDependencies가 포함되지 않는다

레이어

  • 레이어는 종속성을 따로 관리할 수 있게 해 준다
  • 노드 모듈은 웬만하면 레이어로 빼는 게 맞는 것 같다
  • 람다 노드 모듈 폴더를 포함하여 배포되면 용량이 커져서 브라우저에서 코드 조회를 할 수 없다
  • 공통적으로 자주 사용되는 패키지들을 묶어서 재사용할 수 있다
  • 레이어로 사용할
  • devDependencies가 포함되건 말건 상관없다면 심볼릭 링크를 사용하는 것도 나쁘지 않은 것 같다 참고

레이어 (내가 만든 모듈)

  • 내가 제작한 모듈도 레이어로 뺄 수 있다
  • sam project 루트 디렉터리를 기준으로 /opt/nodejs 에 내 모듈을 작성한다 (ex. /opt/nodejs/myModule.js)
  • sam project 루트 디렉터리에서 jsconfig.json를 다음과 같이 한다 (없으면 생성)
jsconfig.json
1
2
3
4
5
6
7
8
9
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": ["./opt/nodejs/*"]
}
},
"exclude": ["node_modules", "build"]
}
  • 이를 임포트 해서 사용하는 파일에서는
app.js
1
const myModule = require('myModule');
  • 이런 식으로 불러와 사용할 수 있다
  • 자동 완성도 잘 작동한다

레이어 yaml 설정

tmplate.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: functions/hello-world/
Handler: app.lambdaHandler
Runtime: nodejs12.x
Layers:
- !Ref MyModuleLayer
- !Ref HelloWorldFunctionLayer
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
# ...
HelloWorldFunctionLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: HelloWorldFunctionLayer
Description: Dependencies for SAM
ContentUri: layer/HelloWorldFunction
CompatibleRuntimes:
- nodejs12.x
LicenseInfo: 'MIT'
RetentionPolicy: Retain

MyModuleLayer:
Type: AWS::Serverless::LayerVersion
Properties:
LayerName: my-layer
Description: Dependencies for SAM
ContentUri: opt
CompatibleRuntimes:
- nodejs12.x
LicenseInfo: 'MIT'
RetentionPolicy: Retain
  • 이런 식으로 리소스에 레이어를 작성한다

참고

리눅스 백그라운드 프로세스 확인 명령어

terminal
1
2
3
4
5
6
7
8
9
10
11
// 리눅스 백그라운드 프로세스 확인
ps

// 모든 프로세스
ps -e

// 모든 프로세스 + 풀 리스트(uid, pid, ...)
ps -ef

// 'www'를 포함하는 결과만 표시
ps -ef|grep www

리눅스 shutdown 명령어

1
2
3
4
5
6
7
8
// 지금종료
sudo shutdown -h now

// 재시작
sudo shutdown -r now

// 종료예약 취소
sudo shutdown -c

aws lambda cors 설정

  • cors를 통해 도메인이 다른 서버로 요청을 보내고 결과를 받아 올 수 있다
  • 람다를 웹에서 실행할 수 있다

API 게이트웨이 설정

  • 람다를 실행하는 API를 하나 생성한다

  • 기본으로 생성되는 any method는 삭제하고
  • get 메서드를 하나 만들고, 내 람다함수를 붙여준다

  • 작업 > CORS 활성화 를 클릭한다

  • Access-Control-Allow-Origin 부분에 내가 원하는 origin을 설정한다
  • 여기서는 하나의 도메인만 설정할 수 있다
  • 여러개의 허용 도메인 허용하도록하려면 람다에서 로직을 짜야한다

  • 위처럼 options 메서드가 추가되고, get 메서드에 응답헤더가 추가된다

  • 설정이 완료되면 배포를 해야 적용이 된다

CORS

  • Cross-origin resource sharing

참고하면 좋은

rds 해킹당하기

  • 복구 방법에 대한 글이 아니다.
  • 그냥 털리면 어떻게 되는지, 왜 털렸을까하는 내용의 기록이다.

털린 증상

  • rds에 접속해보면 내 테이블들이 사라져있고,
  • PLEASE_READ_ME_XMG 라는 데이터베이스가 생겨있고
  • WARNING 테이블만 존재한다

  • WARNING테이블을 셀렉트해보면 아래와 같은 메시지가 적혀있다

To recover your lost databases and avoid leaking it: visit http://hn4wg4o6s5nc7763.onion and enter your unique token ee974966c47f1027 and pay the required amount of Bitcoin to get it back. Databases that we have: acl-manager. Your databases are downloaded and backed up on our servers. If we dont receive your payment in the next 9 Days, we will sell your database to the highest bidder or use them otherwise. To access this site you have use the tor browser https://www.torproject.org/projects/torbrowser.html

  • 이 웹사이트에 들어가서 토큰으로 로그인하고 비트코인을 지불하라는 내용이다
  • 일반 브라우저에서는 접속하지 못하고, tor browser에서만 접속가능하다
  • 나는 안들어가봤다. 왜냐면 중요한 데이터가 아니였기 때문이다

털린 이유

  • rds 퍼블릭 액세스 허용
  • 인바운드 규칙 모두 허용
  • rds 비밀번호 1234

조치

  • 중요한 정보가 없어서 그냥 rds를 삭제하고 다시 만들었다
  • 삭제할 때 스냅샷을 만들지 않도록 한다. 스냅샷 저장 비용이 발생할 수 있다
  • 새로 만든 RDS는 보안그룹 인바운드 규칙에서 내 IP만 접속가능하도록 해주었다
  • 비밀번호를 어렵게 설정했다

rds 삭제 방지 비활성화

  • rds를 켜놓은 상태에서 수정을 할 수 있다
  • rds 꺼놓은 상태에서 수정버튼을 누르면 무한 로딩이 기다리고있다..

AWS 네트워크 ACL

  • VPC를 위한 선택적 보안 계층
  • 시큐리티 그룹과 다르게 차단, deny 룰을 사용할 수 있다
  • ruleNumber가 낮은 숫자일수록 우선적용된다
  • 20개까지 규칙을 지정할 수 있다고 한다
  • 실제로 가용가능한 개수는 18개다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
CidrBlock: '0.0.0.0/0',
Egress: false,
Protocol: '-1',
RuleAction: 'allow',
RuleNumber: 100
},
{
CidrBlock: '0.0.0.0/0',
Egress: false,
Protocol: '-1',
RuleAction: 'deny',
RuleNumber: 32767
}
  • 인바운드의 경우 이 2개가 기본적으로 세팅되어있다

aws-sdk 로 ACL 룰 추가, 교체하기

acl.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var in_params: EC2.CreateNetworkAclEntryRequest = {
CidrBlock: ipv4 + '/24',
Egress: false, //If true, OUTbound rule. fasle is INbound
NetworkAclId: aclId,
Protocol: `6`, // -1 : all protocl // 6 : 사용자 지정 tcp
PortRange: { From: 80, To: 443 }, // http - https
RuleAction: 'deny',
RuleNumber: acl_cnt, // 우선순위 ruleNumber
};

// 기존 존재하는 acl 규칙 개수에 따라서 mode를 달리한다
// mode값에 따라 메서드를 달리함
// 20개가 안되는 경우 createNetworkAclEntry 를 사용함
// 20개 모두 찬경우 replaceNetworkAclEntry를 통해 교체하는 방법
const method =
mode === 'create' ? 'createNetworkAclEntry' : 'replaceNetworkAclEntry';

return new Promise((resolve, reject) => {
ec2[method](in_params, function (err, data) {
if (err) {
console.log(err.message, acl_cnt);

return reject(err.message);
} else {
console.log('Create AclEntry Successful : \n', in_params); // successful response
resolve(in_params);
}
});
});
  • 이상한 요청을 차단하기 위해 알아보았다

참고

AWS ELB를 사용할 때 express morgan 로깅 주의점

  • morgan 로깅할 때, 사용자의 ip를 기록하는
  • :remote-addr 토큰은 req.ip 를 사용한다
  • ELB, 로드밸런서를 사용하면 사용자의 ip가 아니라, ELB의 ip가 기록된다
  • 그래서 사용자의 ip를 가져오려면 x-forwarded-for 헤더의 내용을 봐야한다
1
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

morgan custom

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(...)

app.use(
morgan(
function (tokens, req, res) {
return [
req.headers['x-forwarded-for'] || req.connection.remoteAddress,
req.ip, // == :remote-addr
tokens.method(req, res),
tokens.url(req, res),
tokens.status(req, res),
].join(' ');
},
{
skip: function (req, res) {
return res.statusCode < 400;
},
}
)
);

(...)
  • 이런식으로 커스텀할 수 있다
  • req.headers['x-forwarded-for']의 내용은
1
X-Forwarded-For: <client>, <proxy1>, <proxy2>
  • 이런식으로 가장 앞의 ip가 사용자 ip이다
  • 뒤로 ,로 나열되는 ip는 프록시임으로 당황하지 말자
1
2
3
4
5
{
skip: function (req, res) {
return res.statusCode < 400;
},
}
  • morgan(f(), {})
  • morgan 두번째 인자로 옵션을 넣을 수 있다
  • skip을 주면 특정 경우에서 로깅을 하지 않는 옵션이다
  • return res.statusCode < 400;이런식으로 하면 400이하의 스테이터스 코드일 때,
  • 로깅을 하지 않는다는 의미이다
  • 이 경우 404, 500 같은 에러들만 로깅된다

참고

RDS 삭제시 주의사항

  • ㅠㅠ

RDS 삭제 시 자동으로 스냅샷 생성

  • RDS를 삭제하면 자동으로 스냅샷? 백업같은 것을 생성한다

  • RDS를 사용중도아니고, 삭제하니까 비용이 생긴게 삭제하면서 생긴 스냅샷 보관 비용이였다

  • RDS를 삭제한다면 RDS 삭제 후 꼭 RDS > 스냅샷 에서 생성된 스냅샷도 같이 삭제…

리눅스에서 특정 포트를 사용하는 프로세스 확인하기

terminal
1
2
3
lsof -i:8080

kill -9 $(lsof -t -i:8080)
  • ps -ef|grep node 이런식으로 node로 실행한 웹서버를 확인했었는데,
  • node로 실행한 많은 앱이 있을때 어떤게 내가 생각하는 앱인지 헷갈릴때가 있다
  • 이럴때 특정 포트를 사용하는 프로세스를 어떻게 검색할까 하다가 알아보았다

lsof

terminal
1
lsof -i:3000

COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 13721 ec2-user 18u IPv6 389226 0t0 TCP *:hbci (LISTEN)

  • 이런식으로 해당 포트를 사용하는 프로세스를 확인할 수 있다
terminal
1
lsof -t -i:3000

13721

  • -t옵션을 추가하면 pid만 깔끔하게 얻을 수 있어
  • kill -9 $(lsof -t -i:3000) 이런식으로 묶어서 사용할 수 있다

참고