생각보다 알아야 할 것도 많고, 생각보다 잘못된 정보도 많고, 많은 사람들이 처음 접근할 때 같은 문제를 겪을 것 같아서 삽질 기록을 남긴다. 딱 정리된 상태로 한번에 실행되는 예제를 원하는 사람에게는 좀 안맞을 수도 있다. 나도 해보고 나니까 정리해서 올리라면 올릴 수는 있겠는데, 그보다는 삽질한 과정을 쭉 따라해보면 Lambda 에 익숙해지는데 도움이 될 것 같아서 했던짓 그대로 정리한다.

우선, 호기롭게 aws console 에서 lambda 페이지로 들어가 Get Started Now 버튼을 누른다. (저 위에 캡쳐 화면에 있는 파란버튼)

그러면 blueprint 선택 화면이 나오는데, 나는 초보이므로 알맞은 blueprint 님을 찾아야 한다.

node.js 4.3 을 선택하고 dynamo db 를 사용한 blueprint 를 찾으니 simple-mobile-backend 라는 내 요구조건에 딱 맞는것처럼 보이는 blueprint 가 보인다. 선택하자.

트리거 설정화면으로 넘어가는데, 저 비어있는 사각형 점선 안을 클릭하면 trigger 를 선택할 수 있다. 여기서 선택해놓으면 귀찮은 설정들을 알아서 해준다. API Gateway 를 선택한다.

API Gateway 를 선택하면 추가로 설정할 것들이 나오는데, 웹으로도 API 오픈을 해야하고, 여러모로 기본으로 선택된 AWS IAM 은 귀찮으므로 Open with access key 를 선택한다. (나중에 다 고칠 수 있다)

그 다음에는 이 길다란 function 설정화면이 나온다. 그냥 Name 만 쓰고 Next

하려고 하면 Role name 도 넣어야 된다고 한다. 적당히 아무렇게나 넣고 Next

Review 화면 대충 보고 Create function 버튼을 누르면 당장에라도 작동할것만 같은 분위기의 화면을 보여준다

이게 이렇게 해피앤딩이면 내가 이거 쓸 일도 없었다.

앞에서 뭔가 URL 이 나왔으니 눌러보면 바로 실행 될것만 같은 기분이다.

안된다. 앞으로 계속 안될거다. 하나하나 잡아나가자.

Forbidden 이라는건 권한이 없다는거겠지. 앞에서 security 설정을 open with access key 로 했는데, 나는 api 콜하면서 access key 를 넣은적도 없고, key 를 발급 받은 적도 없다. 그 설정이 API Gateway 에 있었으니 API Gateway 로 가보자.

이렇게 자동으로 LambdaMicroservice 라는 API Gateway 가 하나 생성되어 있다. 이름 누르고 /testFunction 이라고 되어있는 resource 를 누르면

이렇게 API Key 가 Required 인 걸 볼 수 있다.

ANY 로 되어있는 것까지 누르면

이런 뭔가 그럴듯한 화면이 나온다.

하지만 이 화면에서 무슨짓을 해도 API Key 를 세팅할 수 없다.

API Keys 로 가자. Actions 버튼 누르고 Create API Key 누르면

이렇게 나온다. 아무 이름이나 넣고 Save 버튼을 누르면

이렇게 나오고 API key 옆에 있는 Show 를 누르면 API key 를 드디어 볼 수 있다. 이거 잘 보이는 곳에 복사해놓자. 나중에 쓴다.

이거 만들었다고 끝나는거 아니다. Usage Plans 로 가자 (여기 가는데 하루 걸렸다. 이걸 메뉴얼에서 찾을 수 있을거라고 생각하지 마라)

Create 버튼눌러보면 위와 같이 나온다. 이름 아무거나 넣고, 체크박스 두개 다 풀어준 다음 Next 누른다.

Add API Stage 누르고, 콤보박스 하나씩 누르고 (선택할 수 있는게 어짜피 하나씩 밖에 없다.) 체크모양 버튼 누르면 Next 를 누를 수 있다.

여기에서 Add API Key to Usage Plan 을 눌러 아까 만들어둔 API Key 이름을 추가해주고 Done 버튼을 누르자.

위와 같이 Usage Plans 를 통해서 API 와 API Key 를 매핑시켜줘야 서로가 서로를 알아본다.

또하나, 빼먹으면 서글퍼지는게, API Gateway 에서 뭔가 설정을 변경했을 땐 Deploy 를 해줘야 반영된다.

위와같이 resource 선택된 상태에서 Actions 눌러서 Deploy API 를 누른다.

하나밖에 없는 stage 인 prod 를 선택해주고 Deploy 누른다. stackoverflow 에서 API Gateway 관련된거 검색해보면 맨날 답변이 “Deploy 하세요” 다. 까먹으면 고생한다.

위의 과정을 거쳐서 API Key 를 손에 넣었다면 이 key 를 request header 에 넣고 조회를 해보자

헤더를 넣어야 하니 문명인답게 Postman 을 써서 호출해봤다. headers 에 x-api-key 로 API Key 를 넣어준다. 오류 메시지가 바뀐걸 볼 수 있다.

이제 403 Forbidden 은 해결됐다. 이제부터는 Internal server error 와의 전쟁이다.

Internal server error 는 서버 에러다. 전에 났던 Forbidden 오류가 API Gateway 에서 난 오류이기 때문에 Lambda 근처에도 못가본 오류인 것에 비해 Internal server error 는 어쨋든 서버까진 갔고, 오류가 났다는 소리다. Lambda 에서 낸 오류이기 때문에 상세한 오류 내용을 보려면 Lambda 로그를 보면 된다.

https://ap-northeast-2.console.aws.amazon.com/cloudwatch/home?region=ap-northeast-2#logs:

로그는 CloudWatch 에 따로 Logs 메뉴가 있다. 위에 보이는 로그 그룹을 누르면 Log Streams 목록이 나오는데, 최신걸 눌러서 보면 된다. (오류가 난 즉시 반영되는건 아니고, 몇초정도 걸린다.)

위와 같이 나오는데, 접혀있어서 보기 힘드니 오른쪽 상단에 보이는 Text 앞의 라디오 버튼을 누른다.

이제 오류 메시지가 보인다. “Unrecognized operation \”undefined\””

애초에 blue print 로 만들었던 Lambda function 은 바로 실행되도록 만들어져 있지가 않다. Lambda function 코드 한부분을 자세히 보자

event 객체에 operation, payload, tableName 이런게 들어있어야 작동을 할텐데 그런거 없다. 사실 API Gateway 를 trigger 로 쓰는 환경에서 저걸 넣어줄 방법이 있는지도 잘 모르겠다. 게다가 tableName 이라고? dynamoDB 테이블도 아직 안만들었네? 차근차근 해결해보자. 먼저 dynamoDB 테이블부터 만들자

저 파란버튼 Create table 눌러서 만들면 된다.

Table name 과 Primary key 를 적당히 적는다. 나머지는 기본 설정으로..

다시 Lambda 로 돌아와서. handler 코드를 이렇게 수정해보자

exports.handler = (event, context, callback) => {
    console.log('Received event:', JSON.stringify(event, null, 2));
    const operation = event.httpMethod;
    const payload = {
        TableName: 'test'
    }
    switch (operation) {
        case 'POST':
            dynamo.putItem(payload, callback);
            break;
        case 'PATCH':
            dynamo.updateItem(payload, callback);
            break;
        case 'DELETE':
            dynamo.deleteItem(payload, callback);
            break;
        case 'GET':
            dynamo.scan(payload, callback);
            break;
        default:
            callback(new Error(`Unrecognized operation "${operation}"`));
    }
};

이렇게 하면 제대로 돌아간다는게 아니라 앞의 오류는 해결할 수 있다는 거다. 이 코드는 앞으로 계속 수정해나간다. 첫줄에 주석되어있던 console.log 를 풀었기 때문에 event 객체의 구조를 로그에서 볼 수 있고, 그 정보를 토대로 수정했다. operation 은 RESTful 구현을 위해 httpMethod 에서 받아왔고, payload 와 TableName 은 하드코딩했다. TableName 은 좀전에 dynamoDB 에 만든 테이블 명이다. 그 밑에 switch 는 case 부분을 method 에 맞게 수정했고, 필요 없는건 삭제했다.

여기까지 수정하고 실행해보자

또 오류다. 아까처럼 로그 확인해보자. 아무리 확인해도 오류가 난 흔적은 없다. 이 상황에서 몇시간 날렸다.

답부터 말하자면 저 오류는 Lambda 에서 낸게 아니다. API Gateway 에서 낸거다. 위의 캡쳐화면 오른쪽 중간을 자세히보면 502 Bad Gateway 라고 써있다. Gateway 가 나빴던 거다.

API Gateway 에서 뭐가 불만이었는지는 API Gateway 로그를 찍어봐야한다. 이제 다시 API Gateway 페이지로 돌아가보자.

쩜쩜쩜으로 단락을 나누는건 다 이유가 있어서 나누는거다. 이 문제 해결하는것도 내용이 참 길다.

Stage 에서 prod 선택하면 나오는 화면이다. 여길 잘 보면 CloudWatch Settings 에 Enable CloudWatch Logs 라는 체크박스가 있다. 이걸 체크하면 로그가 찍히겠지.

체크박스 누르니 몇가지 설정이 더 나온다. 최대한 많은 로그를 볼 수 있도록 하고 오른쪽 아래에 숨어있는 Save Changes 버튼을 누른다.

버튼을 누르면 이런 오류 메시지가 나와주신다. 아, 신난다.

https://console.aws.amazon.com/iam/home?#/roles

IAM 으로 가서 Roles 로 간다. Create New Role 버튼을 누르자.

Role Name 을 적당히 넣고,

Amazon API Gateway 가 있으니 Select 하자

Policy Name 을 보니 이게 맞는갑다.

위에 나오는 Role ARN 카피하자, 바로 뒤에 쓴다. 그리고 파란버튼 눌러서 마무리

다시 API Gateway 화면으로 돌아와서,

Settings 에 가보면 아까 만들어진 ARN 을 붙여넣을만한 칸이 있다. 여기에 붙어넣고 Save 누르자.

다시 앞에서 실패했던 CloudWatch Settings 부분으로 돌아가서

이거 다시 세팅하고 Save Changes 버튼 누르면 성공.

이제 다시 HTTP Request 를 날려서 (위의 Postman 화면) Internal server error 메시지를 한번 더 보자. 그리고 Cloud watch 의 Logs 로 가보면

/aws/apigateway/welcome 이라는 별로 안반가운 그룹과 API-Gateway 어쩌고 하는 긴 이름의 그룹이 추가되었다. 저 긴 이름의 그룹을 눌러보자.

생각보다 로그가 상당히 많은데, 맨 위에 있는 Last Event Time 이 제대로 찍혀있는 저 로그가 진짜다.

정답은 맨 밑에 있다. Malformed Lambda proxy response.

이번 단락에서 API Gateway 의 로그를 찍게 만드는 과정을 쭉 나열했다. 이 과정은 AWS 메뉴얼 보고 한건데, https://aws.amazon.com/ko/premiumsupport/knowledge-center/api-gateway-cloudwatch-logs/ 이런 메뉴얼을 보고 해냈을 땐 방탈출 게임 한판 깬 느낌도 나고 좋다.

Malformed Lambda proxy response.

API Gateway 에서 Lambda 를 호출할 때에는 원하는 response 형태가 정해져 있다.

https://docs.aws.amazon.com/ko_kr/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html

언제나 메뉴얼에 답이 있다. 찾기 어려워서 그렇지…

리턴 형식을 맞춰주자. 앞에서 수정한 Lambda function 에서 GET 부분만 다음과 같이 수정해서 돌려보자

        case 'GET':
            dynamo.scan(payload, (err, data) => {
                callback(null, {
                    'statusCode': 200,
                    'headers': {},
                    'body': JSON.stringify(data)
                });
            });
            break;

callback 호출을 저렇게 형식 딱 맞춰주고 HTTP Request 날려서 확인해보면 드디어 꿈에 그리던 리턴 코드 200을 보게 된다. 데이터가 안나와서 아쉽다면 DynamoDB 가서 데이터 하나 넣고 조회해보자

저렇게 가서 Create Item 누르면 된다.

이 에디터까지는 힘들어서 설명 못하겠다. 막 눌러보고 안되면 그냥 id 만 적자.

성공~~

미안하지만 이게 진 엔딩은 아니다. 웹에서 쓰려면 CORS (Cross-Origin Resource Sharing) 를 설정해야된다. 웹에서 안쓸거라면.. 그냥 여기까지만 봐도 되겠다.

이제는 좀 익숙해질 때도 된 API Gateway 로 가보자

Resources 에서 생성해놨던 resource 를 선택하고, Actions 를 보면 Enable CORS 가 보인다.

이런식으로 설정화면이 나올텐데 그대로 파란버튼 눌러서 만들어주면 Confirm 화면 한번 나온다. 파란버튼 누르면 끝.

이렇게 나오고 OPTIONS method 가 추가된게 보인다.

잊지말고 Deploy 하자. Deploy 해야만 적용된다. Actions 누르면 Deploy API 나온다.

이제 Lambda 도 수정해줘야한다. response header 에 Access-Control-Allow-Origin 를 추가해줘야 하기 때문이다.

        case 'GET':
            dynamo.scan(payload, (err, data) => {
                callback(null, {
                    'statusCode': 200,
                    'headers': {
                        'Access-Control-Allow-Origin': '*'
                    },
                    'body': JSON.stringify(data)
                });
            });
            break;

최종적으로 위와 같이 headers 를 추가해주면 완료. 웹에서도 CORS 문제 없이 잘 돌아간다. 진짜 잘 돌아가는지 테스트해보고 싶은데 웹 코딩 하기 귀찮은 사람들을 위해 codepen 에 올려놨다.

https://codepen.io/yumenohosi/pen/evvvxx

위의 과정을 잘 따라했다면 이 코드에 url 과 api key 넣고 실행했을 때 데이터 잘 받아오는걸 확인할 수 있다.

처음 쓸 때는 insert, update, delete 다 쓰려고 했는데, 쓰다보니 너무 길어져서 생략한다. insert, update, delete 는 그냥 dynamoDB 쓸줄 알면 된다. get 하고 똑같은 방식으로 payload 만 적당히 수정하면 되는데… 이게 dynamoDB 를 조금은 공부해야 원활하게 된다.

AWS 가 워낙 발전이 빠르다보니 이렇게 화면을 캡쳐해놔도 좀 지나서 보면 다 바뀌어 있는 경우가 많다. 반대로 말하면 삽질 도중에 검색한 사이트들이 옛날 기능 기준으로 되어 있어서 잘 안맞는 경우도 많았다. 그렇다고 AWS 메뉴얼처럼 글로만 써있으면 그것도 따라하기 쉽지 않아서, 그냥 경험한 것 그대로 남겨 놓는 정도의 의미로 작성했다. 삽질 한 덕분에 Lambda 와 API Gateway, Cloud Watch 의 Logs 에 대한 경험이 많이 쌓여서 이것저것 응용해볼만 해졌다는 소득도 있었다. 이렇게 이해를 어느정도 해 둔 다음 serverless 같은 프레임워크로 옮겨가면 좋을 것 같다.