1편2편과 이어지는 게시물입니다.

VPC

이전 편에서는 기본적인 람다 사용법과 함께 문자 전송을 위한 람다를 업로드 했습니다.

그렇다면 고정된 IP 주소를 활용해서 문자를 보내려면 어떻게 해야 할까요?

답은 VPC 즉 계정 전용 네트워크를 활용하는 것입니다.

이 VPC를 활용하면 특정한 트래픽을 차단/허용하거나 고정된 IP 주소를 사용하거나 하는 등의 일이 가능해집니다.

그럼 시작해볼까요?

VPC 부분은 조금 어렵기도 하고 한 가지 설정만 잘못 건드려도 금세 에러가 발생하기 때문에 굉장히 주의를 기울여야합니다.

VPC 생성

가장 먼저 할 일은 VPC 대시보드로 이동해서 좌측의 VPCs 메뉴에 접속하는 것입니다.

스크린샷 2018-10-13 오전 8.17.13.png

이제 파란색 VPC 만들기 메뉴가 보일 것입니다.

스크린샷 2018-10-13 오전 8.19.46.png

이 버튼을 클릭하면 아래와 같은 대화창이 나옵니다.

스크린샷 2018-10-13 오전 8.22.36.png

만약 CIDR 블록이 무엇인지 잘 모른다면 위의 설정을 일단 따라서 입력해주세요.

CIDR 블록이 무엇인지에 대해서는 이전에 정리한 글을 소개하겠습니다. AWS를 위한 네트워크 용어 정리 - CIDR와 서브넷

다음 할 일은 서브넷을 만드는 일입니다.

서브넷 생성

서브넷이란 쉽게 말하면 네트워크의 작은 조각이라고 얘기할 수 있습니다.

우리가 생성한 네트워크는 여러 서비스가 나눠 사용하는 것이기 때문에 (데이터베이스, 여러 람다들, 웹서버 등), 적절하게 서브넷으로 분리해서 사용해야 합니다.

인터넷 접속을 하는가 안 하는가에 따라서도 서브넷을 분리해야 하므로 더욱 조심해야겠죠?

서브넷에 대한 더 자세한 사항은 AWS를 위한 네트워크 용어 정리 - CIDR와 서브넷 참조하시면 됩니다.

다시 왼쪽 메뉴에서 VPCs 아래에 있는 서브넷을 들어가면 아까와 같이 서브넷 생성이라고 적혀있는 파란 버튼을 볼 수 있습니다.

버튼을 누르면 서브넷 생성을 위한 창으로 연결됩니다.

스크린샷 2018-10-13 오전 8.29.58.png

위와 같이 입력해주면 되는데, 주의사항은

  1. 총 세 개의 서브넷을 생성해야하며,
  2. Name 태그는 1개는 public 나머지 2개는 private으로 붙여줍니다.
  3. CIDR 블록은 11.0.1.0/24 ~ 11.0.3.0/24 순으로 붙여주면 됩니다.

서브넷 생성이 완료되었다면,

다음은 인터넷 게이트 웨이 생성입니다.

인터넷 게이트 웨이 역시 왼쪽 메뉴에서 찾을 수 있으며 생성 요령 또한 다른 서비스와 동일합니다.

인터넷 게이트 웨이는 별다른 설정 없이 이름만 입력하면 됩니다!

스크린샷 2018-10-13 오전 8.32.31.png

인터넷 게이트 웨이가 생성되었다면 위의 [작업] 메뉴에서 VPC에 연결을 진행합니다.

라우팅 테이블 설정

라우팅 테이블 또한 왼쪽의 메뉴에서 찾을 수 있는데,

서브넷을 생성하면 기본적으로 생성되는 라우팅 테이블이 있습니다.

이 테이블을 메인테이블이라고 합니다.

이 메인 테이블 외에 추가로 테이블을 생성해야 하는데요,

생성 요령은 다른 서비스와 같습니다.

라우팅 테이블 생성 버튼을 누르고 적당한 이름을 붙여줍니다.

이제 총 두 개의 라우팅 테이블이 생겼는데, 각자의 역할은 아래와 같다.

메인테이블 서브 테이블
역할 내부 서비스(데이터베이스 등)로부터 받은 트래픽을 라우팅함 VPC 내부의 웹 서버로부터의 요청이나 인터넷 게이트웨이를 통해 전달된 외부 트래픽을 라우팅함
서브넷 프라이빗 서브넷이 연결됨 퍼블릭 서브넷이 연결됨

이제 설정을 해줍시다.

스크린샷 2018-10-13 오전 8.44.38.png

먼저 서브 테이블 설정입니다.

[라우팅] 탭에서 편집을 누르고, 대상주소에 0.0.0.0/0을 대상에는 인터넷 게이트웨이의 id를 넣어줍니다. (자동 완성됩니다.)

스크린샷 2018-10-13 오전 8.46.12.png

다음으로 [서브넷 연결] 탭에서 편집을 눌러 퍼블릭 서브넷을 연결해줍니다. (여기서 퍼블릭 서브넷은 아까 public 이라고 이름을 붙인 서브넷을 말한다.)

NAT Gateway

NAT Gateway이란 Network Address Translator Gateway의 줄임말로 네트워크 주소 변환 게이트웨이를 의미합니다. 이는 프라이빗 서브넷에 존재하는 서비스들(데이터베이스, 람다 함수 등)을 탄력적 IP 주소(EIP)를 가진 NAT을 통해 인터넷에 연결하며, 네트워크 외부에서는 해당 서비스를 접속할 수 없게 만드는 역할도 합니다.

즉 고정된 주소를 가진 게이트웨이(관문)의 역할을 수행하며, 외부 트래픽을 통제하고 내부 트래픽이 인터넷과 고정된 주소로 요청을 할 수 있게끔 도와주는 것이죠.

아래 그림은 위 관계를 잘 보여줍니다.

c71934dde9b528e48892872184b9f643.png
<출처> AWS 공식 문서

  1. 프라이빗 서브넷 안에 기타 서비스들 (데이터베이스, 람다 등)이 있고, NAT Gateway를 통해 외부에 요청을 보낼 수 있다.
  2. 웹서버의 요청은 NAT Gateway의 탄력적 IP (EIP)를 통해 이뤄진다.
  3. 프라이빗 서브넷과 퍼블릭 서브넷은 서로 다른 라우팅 테이블을 사용한다.

이제부터는 비용 문제가 있기 때문에 NAT Gateway 대신에 EC2 인스턴스를 활용한 NAT Instance를 사용합니다.

자세한 사항은 아래 참고 링크를 읽어보세요.

[참고]

  1. NAT Instance vs NAT Gateway

  2. NAT Gateway 요금

NAT Instance 생성 - 1

다시 홈으로 돌아와서 EC2 대시보드로 이동해서

좌측 메뉴의 EC2 인스턴스 생성에 활용할 [키페어] 생성 메뉴로 이동합니다.

스크린샷 2018-10-13 오전 9.02.49.png

키페어 생성 버튼을 누르고 키페어를 생성하면 .pem 확장자를 가진 파일을 다운로드하는데 이후 EC2 인스턴스 접속에 필요하므로 절대 삭제해서는 안 됩니다! (다른 키페어를 만들어도 되긴 하지만요.)

다음은 보안 그룹 생성입니다.

보안 그룹은 쉽게 말해 방화벽과 같은 역할을 수행하며, 명시적으로 허용되지 않은 트래픽(보안 그룹의 규칙에 해당되지 않는)의 접근을 차단합니다.

서버에 접속이 안 되거나 Timeout 에러를 겪게 된다면 가장 먼저 의심해봐야 하는 부분이 보안 그룹 설정일 정도로 초심자가 쉽게 지나치므로 조심할 필요가 있습니다.

물론 튜토리얼을 잘 따라오시면 그런 걱정은 없겠죠?

다음으로 키페어 생성 위의 [보안그룹] 메뉴로 들어가줍니다.

보안 그룹 생성을 누르면 나오는 대화창에 아래와 같이 인바운드 규칙을 편집해줍니다.

인바운드 규칙이라는 말에서 대충 짐작할 수 있듯이 보안 그룹이 허용하는 IP 주소를 의미합니다.

스크린샷 2018-10-13 오전 9.08.23.png

ssh에는 컴퓨터의 퍼블릭 IP 주소를(현재 접속 중인 IP 주소를 모르시는 분들은 링크에 들어가셔서 Your Public IPv4 is: 뒤의 주소를 적으시면 됩니다.),
http/https의 소스에는 해당 보안그룹의 아이디를 넣어줍니다. (자기 자신의 아이디를 넣는 것은 해당 보안그룹을 사용하는 람다끼리 네트워킹을 허용하는 것을 의미한다. 즉 정해진 IP 주소 대역 혹은 보안 그룹의 아이디 값이 소스에 들어갈 수 있는 것이다.)

[추가 설명]
보안그룹의 인바운드 규칙의 소스에 보안그룹의 아이디를 넣는 것은 해당 보안그룹을 사용하는 서비스끼리는 서로 트래픽을 허용한다는 것을 의미한다.

[참고]
https://serverfault.com/questions/820377/how-ec2-security-group-uses-group-id-as-the-value-in-the-source-column

NAT Instance 생성 - 2

이제 좌측 메뉴에서 [인스턴스]를 누르고 파란색 인스턴스 시작 버튼을 눌러봅시다.

스크린샷 2018-10-13 오전 9.10.57.png

1단계 AMI 선택에서는 amzn-ami-vpc-nat이라고 검색한 뒤 커뮤니티 AMI에서 가장 위에 있는 것으로 골라줍니다.

2단계는 인스턴스 유형 설정입니다. t2.micro로 설정해주시면 됩니다. 용도에 따라 자유롭게 선택하셔도 됩니다. 저는 튜토리얼 목적이기 때문에 가장 작은 사이즈로 골랐습니다.

3단계는 인스턴스 세부 정보 구성입니다.

스크린샷 2018-10-13 오전 9.13.16.png

[네트워크]에서 우리가 이전에 생성한 VPC를 넣어주고,

[서브넷]에는 퍼블릭 서브넷을,

[퍼블릭 IP 자동 할당]은 활성화를 체크해줍니다.

잊지 말고 꼭 체크해주세요~

4, 5 단계는 특별하게 건드리지 않아도 되고,

6단계는 기본 보안그룹이 아닌 이전에 생성한 보안그룹으로 선택합니다.

이제 [검토 및 시작] 단계에서 시작을 누르면 키페어를 선택하라고 얘기할텐데 그 때 미리 생성해둔 키페어를 넣어줍니다.

조금 기다리고 나면 생성된 인스턴스를 확인할 수 있습니다!

이제 인스턴스를 우클릭해서 [네트워크] - [소스/대상 확인]을 비활성화해줍니다.

스크린샷 2018-10-13 오전 9.18.09.png

다음은 탄력적 IP 구성입니다.

마찬가지로 좌측메뉴에서 탄력적 IP로 이동해서

새 주소 할당을 눌러줍니다.

할당된 주소를 우클릭하면 아래와 같이 주소를 연결할 수 있습니다.

스크린샷 2018-10-13 오전 9.19.55.png

인터넷 접속 확인

다시 인스턴스 메뉴로 돌아와서

이번에는 인스턴스 시작 버튼 오른쪽에 있는 연결 버튼을 누릅니다.

이제

  1. 키페어 파일의 경로를 확인합니다.
  2. 키페어 파일을 다운로드한 경로로 이동한 뒤 아래 명령어를 입력합니다.
    chmod 400 xxx.pem
    
  3. 이제 연결 창에 표시된 예) 아래의 명령어로 터미널에서 접속합니다.

정상적으로 접속이 이뤄졌다면 아래와 같이 뜰 것입니다.
만약 Timeout이 발생하고 제대로 연결이 되지 않는다면 보안그룹 설정에서 ssh가 인바운드 룰에 포함되어 있는지 확인해주시고, 대상에 컴퓨터의 네트워크 주소 (퍼블릭 IP)를 넣어주세요.

스크린샷 2018-10-13 오전 9.23.57.png

1편에서 했던 것처럼 간단한 http 요청을 넣어보면 제대로 작동하는지 여부를 알 수 있습니다.

라우팅 테이블 설정 - 2

이제 NAT Instance가 추가되었습니다.

이제는 메인 테이블의 라우팅 구성을 할 차례인데요,

라우팅 테이블 설정 - 1의 서브 테이블 라우팅 구성을 할 때와 같은 요령으로

  1. 라우팅 탭에서
    대상 주소 0.0.0.0/0, 대상은 NAT Instance의 id로 설정하고,
  2. 서브넷 연결 탭에서
    두 개의 프라이빗 서브넷을 연결해줍니다.

람다의 VPC 설정

람다의 코드 편집 메뉴에서 쭉 내려가면

스크린샷 2018-10-13 오전 9.30.37.png

위와 같이 네트워크 설정을 해줄 수 있습니다.

VPC에는 우리가 생성한 VPC를,

서브넷에는 "무조건" 프라이빗 서브넷을 넣어줍니다.

보안그룹 역시 새로 생성한 보안그룹을 넣어줍니다.

저..장!

음.. VPC 설정을 할 수 있는 권한이 없군요.

이럴 때는 [IAM] - 좌측 [역할] 로 들어가서 파란색 정책 연결 버튼을 누른 후,

AWSLambdaVPCAccessExecutionRole를 검색해서 추가해주면 됩니다.

스크린샷 2018-10-13 오전 9.33.36.png

이제 변경한 VPC 설정이 잘 저장될 것입니다.

마지막으로 탄력적 IP 주소를 알리고 서비스의 발송 IP 주소에 등록해주기만 하면?

스크린샷 2018-10-13 오전 9.36.29.png

에러 없이 잘 작동하는 람다의 모습을 볼 수 있습니다.

끝일까요?

Cold Start

아쉽지만 아직 해결하지 못한 문제가 있습니다.

람다는 함수 호출이 이뤄지면 얼마간 Warm 상태를 유지합니다. (Warm 상태란 람다가 빠른 시간 내에 응답하는 것을 의미합니다.)

하지만 함수 호출이 얼마간 이뤄지지 않으면 (정확한 시간은 얼마인지 모르겠습니다.)

Cold 상태로 접어들게 되고, 이 함수를 다시 응답이 가능한 상태까지 만드는데 추가로 시간이 걸리게 됩니다.

이런 현상은 VPC를 이용하면 더 심해지는데 VPC를 셋업하는 시간까지 이 Cold Start 시간에 포함되기 때문입니다.

어떻게하면 이 문제를 해결할 수 있을까요?

정답은 문자 전송 람다 이외에 1) 문자 남은 횟수를 체크하는 새로운 람다를 만들고 2) 이 람다를 지속적으로 (30분 간격) 호출하는 다른 람다를 만드는 것입니다.

Check-remain Lambda

먼저 남은 문자 횟수를 체크하는 aligo-check-remain 람다를 만듭시다.

코드는 문자 전송의 경우와 거의 일치하며, 엔드포인트와 Credential 부분만 조금 수정하면 됩니다.

그리 어려운 작업은 아니라는 거죠.

const axios = require('axios')

exports.handler = async (event, context, callback) => {
  const options = {
    timeout: 1000,
    maxRedirects: 5,
    headers: {
      'Content-type': 'application/x-www-form-urlencoded',
    },
  }

  const formatToQueryString = ([key, value]) => {
    return `${key}=${value}`
  }

  Object.entries = object => Object.keys(object).map(key => [key, object[key]])

  const withCredentials = {
    ...event,
    key: process.env.ALIGO_API_KEY,
    userid: process.env.ALIGO_USER_ID,
  }

  const queryString = Object.entries(withCredentials)
    .map(formatToQueryString)
    .join('&')

  const url = `https://apis.aligo.in/remain/?${queryString}`

  try {
    const { data } = await axios.post(url, options)
    await callback(null, data)
  } catch (error) {
    await callback(error)
  }
}

다음으로 잊지 말고 환경변수에 ALIGO_API_KEYALIGO_USER_ID를 잘 넣어주고,

문자 전송 함수와 동일하게 VPC 구성을 합니다.

스크린샷 2018-10-13 오전 9.47.05.png

위와 같이 람다가 잘 작동하는지 확인해줍니다.

Lambda calls another Lambda

이제 주기적으로 aligo-check-remain을 호출하는 call-aligo-check-remain을 작성할 차례입니다.

const AWS = require('aws-sdk')
AWS.config.region = 'ap-northeast-2'
const lambda = new AWS.Lambda()

exports.handler = (event, context, callback) => {
    const aligoTest = {
        key: process.env.API_KEY,
        userid: process.env.USER_ID,
    }

    const params = {
        FunctionName: 'aligo-check-remain',
        InvocationType: 'RequestResponse',
        LogType: 'Tail',
        Payload: JSON.stringify(aligoTest)
    }

    lambda.invoke(params, (err, data) => {
        if (err) {
            callback(err)
        } else {
            callback(null, data)
        }
    })
};

[참고]

  1. region을 잘 확인해주세요. 가끔 서울 리전에서 생성한줄 알고 있다가 알고보니 미국 동부였던 경우도 있습니다. (네, 그게 바로 접니다...)
  2. FunctionName을 잘 확인해주세요. (ResourceNotFound 에러가 발생합니다.)

이제 CloudWatchEvent를 설정해줄 차례입니다.

CloudWatchEvent은 AWS의 잡 스케줄러(지정된 시간에 지속적으로 어떤 작업을 수행할 수 있도록 설정해주는 소프트웨어)입니다.

저 같은 경우 복잡한 규칙 생성이 필요없기 때문에 (30분 간격으로 호출), rate 표현식을 사용했지만 좀 더 복잡한 셋팅이 필요하신 분들은 cron 표현식을 사용할 수 있습니다.

[참고]
rate 또는 cron을 활용한 예약 표현식

스크린샷 2018-10-13 오전 9.52.41.png

여기에 다른 람다와 마찬가지로 환경변수 설정과 VPC 설정을 마쳐줍니다.

마지막으로 다른 두 람다와는 다르게 다른 lambda를 호출하는 역할을 추가해줘야합니다. (이 람다는 다른 람다를 호출하는 람다이기 때문에 '다른 람다를 실행하는' 정책이 역할에 존재해야 합니다.)

아까 VPC 역할을 추가했을 때와 같은 방법으로 AWSLambdaRole을 추가해줍니다.

이후에 테스트 호출을 하면?

스크린샷 2018-10-13 오전 9.56.35 1.png

Payload항목에서 호출 결과를 확인할 수 있습니다.

정말 끝!

TroubleShooting

Timeout 에러 발생

  • 혹시 람다의 VPC 설정에서 서브넷 항목에 퍼블릭 서브넷을 포함하지는 않았나요? 프라이빗 서브넷만을 포함하도록 VPC 설정을 변경해주세요.
  • 혹시 람다의 VPC 설정에서 선택된 보안그룹이 NAT instance 에 명시된 보안그룹과 다르지는 않은가요?

인스턴스 ssh 접속 관련

  • ~ are too open
    키 페어가 공개적으로 공개되어있기 때문에 발생하는 문제입니다.
    chmod 400 "path/to/xxxxx.pem"을 입력해보세요.

  • 접속을 시도했는데 아무 것도 뜨지 않아요.
    이 경우 보안그룹의 인바운드 규칙을 확인해주세요.
    인바운드 유형에 ssh 그리고 소스에는 0.0.0.0/0 (모든 트래픽)을 넣어주면 접속이 될 것입니다.

인증오류 -IP

  • 혹시 알리고 서비스의 발송 IP에 탄력적 IP 주소를 적는 것을 빠뜨리셨나요?
  • 혹은 튜토리얼을 읽으면서 빠뜨린 부분은 없는지 확인해주세요.

한글 메시지가 보내지지 않아요.

  • 알리고 서비스에서 한글 문자를 보낼 때는 encodeURI 함수를 통해 메시지를 인코딩한 다음 보내주세요.
    (위의 코드 샘플을 사용한 경우 이미 처리가 돼있습니다.)

5.요청이 잘 보내지다가 어느 순간부터 갑자기 요청이 안 보내져요.

  • 혹시 작업 pc나 작업 네트워크가 바뀌었나요? 이 경우에는 보안그룹 설정이나 알리고 IP 등록 작업 등을 다시 해주셔야 합니다. 만약 어느 부분을 수정해야할지 모르겠다면 처음부터 튜토리얼을 주욱 따라가는 것도 방법입니다. 디버깅에 왕도는 없으니까요!

본 포스트는 제 블로그의 포스트를 재구성한 내용입니다. 포스트가 마음에 드셨다면 제 블로그도 놀러와서 구독/좋아요 눌러주세요 ^^