Amazon ECS로 Swift 웹 어플리케이션 실행하기

원문: Running Swift Web Applications with Amazon ECS

Swift는 안전, 성능 및 소프트웨어 디자인 패턴에 대해 현대적으로 접근해 만들어진 범용 프로그래밍 언어입니다. Swift의 목표는 시스템 프로그래밍에서 데스크탑과 모바일 어플리케이션 프로그래밍, 클라우드 서비스까지 확장하는 최고의 언어가 되는 것입니다. 개발자로서 동일한 스택으로 어플리케이션을 구성할 수 있다는 가능성과 Swift의 장점을 클라이언트 및 서버단에서 활용할 수 있다는 사실에 흥분했습니다. 코드는 더욱 간결해지고 iOS 환경과 더 밀접하게 통합됩니다.

이 글에서 Swift를 이용해 웹 어플리케이션을 만들고 우분투 리눅스 이미지와 Amazon ECR을 사용해 Amazon ECS에 배포 하는 방법에 대해 설명합니다.

컨테이너 배포 개요

Swift는 우분투에서 사용할 수 있는 버전의 컴파일러를 제공합니다. 컴파일러 외에도 웹 서버, 컨테이너 전략 및 트레픽 최고치를 자동으로 확대하는 자동 클러스터 관리가 필요합니다.

다음은 서비스를 클라우드에 배포 할 때 내려야 할 결정들입니다:

  • HTTP server Swift를 지원하는 HTTP 서버를 선택하세요. Vapor가 가장 쉽습니다. Vapor는 iOS, MACOS 및 우분투에서 동작하는 Swift 3.0의 type-safe한 웹 프레임워크입니다. Swift 어플리케이션을 배포하는 것은 매우 간단하고 쉽습니다. Vapor는 새 Vapor 어플리케이션을 만들고, Xcode 프로젝트를 생성 및 빌드하는 것 뿐만 아니라 어플리케이션을 Heroku 또는 Docker에 배포 하는 것을 도와주는 CLI를 가지고 있습니다. 다른 Swift 웹 서버도 좋지만 이 글에서는 제가 시작하기에 쉽다고 생각한 Vapor를 사용하겠습니다.

Tip: Vapor 슬렉 그룹에 들어오세요, 매우 도움이 됩니다. 질문이 있다면 원하는 답변을 받을 수 있습니다.

  • Container model Docker는 배포된 어플리케이션을 소프트웨어 컨테이너 안에서 빌드, 실행, 테스트, 배포 할 수 있게 해주는 오픈 소스 기술입니다. Docker를 사용하면 개발을 위해 표준화된 단위로 소프트웨어를 패키징 할 수 있습니다. 여기에는 소프트웨어 실행에 필요한 모든 것이 포함됩니다: 코드, 런타임, 소프트웨어 툴, 시스템 라이브러리 등 Docker는 빠르고 안전하고 일관되게, 환경에 구애받지 않고 어플리케이션을 배포 할 수 있게 해줍니다.
    이 글에서는 Docker를 사용하지만 Heroku를 선호하는 경우 Vapor도 Heroku와 호환됩니다.
  • 이미지 저장소 Docker를 컨테이너 배포 단위로 선택한 후에는 규모에 맞춰 배포를 자동화 하기 위해 Docker 이미지를 저장소에 저장해야 합니다. Amazon ECR은 완벽하게 관리되는 Docker 레지스트리며 AWS IAM 정책을 사용해 저장소를 안전하게 보호할 수 있습니다.
  • 클러스터 관리 솔루션 Amazon ECS는 확장성이 뛰어나고 Docker 컨테이너를 지원하는 성능이 좋은 컨테이너 관리 서비스며 Amazon EC2 인스턴스의 관리 클러스터에서 어플리케이션을 쉽게 실행할 수 있게 해줍니다. ECS가 있으면 자체 클러스터 관리 인프라를 실치하고 운영하고 확장 할 필요가 없습니다.

ECS를 사용하면 자체 클러스터 인프라를 설치, 운영 확장 할 필요가 없기 때문에 컨테이너를 어플리케이션의 한 부분(분산 또는 기타)으로 채택하는것이 쉽습니다. ECS에서 Docker를 사용하면 장기간 실행되는 어플리케이션, 서비스 및 배치 프로세스를 유연하게 스케줄링 할 수 있습니다. ECS는 어플리케이션 가용성을 유지 관리하며 컨테이너를 확장 할 수 있게 해줍니다.

1

HTTP 서버(Vapor)에서 실행되는 Swift 웹 어플리케이션이 있고, 이 서버는 컨테이너(Docker)에 배포 되었으며, 이미지는 수평으로 확장할 수 있도록 자동 클러스터 관리(ECS)가 있는 보안 저장소 (ECR)에 저장되어 있습니다다.

AWS 계정 준비하기

  1. AWS 계정을 가지고 있지 않다면 http://aws.amazon.com에 접속해 화면의 명령을 따라 계정을 생성하세요.
  2. 네비게이션 바에 있는 지역 선택자를 사용해 Swift 웹 어플리케이션을 배포하고 싶은 AWS 지역을 선택하세요.
  3. 원하는 지역에서 보안 키를 만드세요.

둘러보기

다음은 Swift로 작성된 웹 어플리케이션을 설정하고 ECS에 배포하는데 필요한 단계들입니다:

  1. AWS CloudFormation template 인스턴스를 다운로드하고 실행하세요. CloudFormation template은 Swift, Vapor, Docker 및 AWS CLI를 설치합니다.
  2. SSH로 인스턴스에 접속하세요.
  3. vapor 예제 코드를 다운받으세요.
  4. 로컬에서 Vapor 웹 어플리케이션을 테스트하세요.
  5. 새로운 API를 포함해서 Vapor 예제 코드를 향상시키세요.
  6. 코드를 저장소에 푸쉬하세요.
  7. 코드를 도커 이미지로 만드세요.
  8. 이미지를 Amazon ECR에 푸쉬하세요.
  9. Swift 웹 어플리케이션을 Amazon ECS에 배포하세요.

세부 단계

  1. CloudFormation template을 다운로드하고 EC2 인스턴스를 작동시키세요. CloudFormation은 Swift와 Vapor, Docker 및 git을 설치하고 설정합니다. 인스턴스를 시작하려면 여기서 CloudFormation을 실행하세요.
  2. SSH로 인스턴스에 접속하세요:
    ssh –i ec2-user@<bastion host ip>
  3. vapor 예제 코드를 다운받으세요. 이 코드는 웹 어플리케이션에 사용중인 예제를 배포하는데 도움이됩니다:
    git clone https://github.com/awslabs/ecs-swift-sample-app.git
  4. Vapor 어플리케이션을 로컬에서 테스트하세요:
    1. Vapor 프로젝트 빌드:
      cd ~/ecs-swift-sample-app/example \
      vapor build
    2. Vapor 프로젝트 실행:
      vapor run serve --port=8080
    3. 서버 실행 확인(새 터미널 창에서):
      ssh -i ubuntu@ curl localhost:8080
  5. Vapor 코드를 향상시키세요:
    1. 샘플 어플리케이션에 새 route를 추가하는 가이드를 따라 하세요: https://Vapor.readme.io/docs/hello-world
    2. 웹 어플리케이션을 로컬에서 테스트하세요:
      bash vapor run serve --port=8080 curl http://localhost/hello.
  6. 변경사항을 커밋하고 깃허브 저장소에 푸쉬하세요:
    git add –all
    git commit –m
    git push
  7. 코드를 이용해 새 도커 이미지를 빌드하세요:
    docker build -t swift-on-ecs \
    --build-arg SWIFT_VERSION=DEVELOPMENT-SNAPSHOT-2016-06-06-a \
    --build-arg REPO_CLONE_URL=https://github.com/awslabs/ecs-swift-sample-app.git/ \
    ~/ ecs-swift-sample-app/example
  8. ECR에 업로드: ECR 저장소를 만들고 Amazon ECR 시작하기에 있는 단계를 따라 이미지를 푸쉬하세요.
  9. ECS 클러스터를 생성하고 Amazon ECS 시작하기에 있는 단계를 따라 작업을 실행하세요:
    1. 작업을 생성할 때 ECR 이미지에 전체 레지스트리/저장소:태그 이름을 사용해야 합니다. 예를 들면, aws_account_id.dkr.ecr.us-east-1.amazonaws.com/my-web-app:latest.
    2. 포트 포워딩을 8080으로 설정해야 합니다.
  10. 이제 컨테이너로 가서 public IP 주소를 확인하고 결과를 보기 위해 접속해 봅시다.
    1. 실행 중인 작업을 열고 URL을 확인합니다:
      2
    2. 브라우저에서 URL에 접속합니다:
      3

당신의 첫 번째 Swift 웹 어플리케이션이 지금 실행되고 있습니다.

이제, 서비스를 확장하기 위해 ECS with Auto Scaling를 사용할 수 있고 서비스를 모니터링 하기 위해 CloudWatch metrics 와 CloudWatch events를 사용할 수 있습니다.

결론

Swift의 장점을 활용하고 싶다면, Amazon ECS와 Amazon ECR을 활용해 Vapor를 웹 컨테이너로 사용하고 Swift 웹 어플리케이션을 대규모로 배포할 수 있으며 클러스터 관리를 Amazon ECS에 위임할 수 있습니다.

이 글을 넘어서 Swift를 가지고 할 수 있는 많은 흥미로운 것들이 있습니다. Swift를 더 배우시려면 추가적인 Swift 라이브러리들을 보시고 Swift 문서를 읽어보세요.

질문이나 제안이 있으시면 코멘트로 남겨주세요.

당신이 AWS 계정을 만들고 가장 먼저 해야 할 일 들과 하지 말아야 할 일 들

이 글은 tmknom님이 Qiita에 올려주신 ‘AWS 계정을 만들고 나서 바로 해야 할 일들 정리(AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ)‘에서 영감을 받아 작성되었습니다. 처음 AWS 계정을 만들고 나서 무엇을 해야 할 것인가도 중요하지만 하지 말아야 할 것을 아는 것도 중요하기 때문에 해야 할 것들과 하지말아야 할 것들에 대해서 정리해 보았습니다.


AWS 계정(루트 사용자) 보호

루트 사용자란?

AWS에 가입할때 맨 처음 만드는 e-mail어드레스와 패스워드로 인증하는 계정을 루트 사용자 라고 합니다. 이 루트 사용자는 그야말로 모든 권한을 지니고 있으며 권한에 대한 제약이 불가능하기 때문에 각별히 신경써서 다루어야 합니다. AWS의 모범사례에 의하면 AWS 루트 사용자를 만든 이후 가장 먼저 해야 할 일은 IAM 계정을 만들고 권한을 부여할 수 있는 IAM 관리자 계정을 만들고 이후 루트 사용자 대신 그 계정을 사용하는 것 입니다. 루트 사용자는 원칙적으로는 평상시엔 사용하지 않는 것이 좋습니다. 혹시 이 글을 읽으시는 분 중에 아직 루트 사용자를 이용하여 작업을 하시는 분이 계시다면 지금 즉시 관리자 권한을 지닌 IAM유저를 만들고 루트 사용자의 사용을 중지하시기 바랍니다.

AWS 계정에 대한 액세스 키를 만들지 말 것

AWS는 패스워드 인증 이외에 CLI나 SDK 사용을 위한 액세스 키 인증을 제공합니다. 하지만 루트 사용자에 대해서 액세스 키를 만들지는 마세요. 이미 있다면 삭제하시고 다른 IAM유저의 액세스 키를 사용해서 작업하시기 바랍니다.

루트 사용자 암호 변경

루트 사용자의 경우 강력한 암호를 사용하시기 바랍니다. 루트 사용자의 암호 변경 방법은 아래 링크에서 확인하실 수 있습니다.

루트 사용자에 대해 MFA 활성화

보안을 강화하기 위해 패스워드만 사용한 인증보다는 MFA(Multi-Factor Authentication)을 사용한 인증을 설정해 두세요. AWS에서는 Google Authenticator와 같은 개방형 TOPT 표준을 지원하는 애플리케이션을 MFA에 사용할 수 있습니다. 루트 사용자의 인증에 MFA를 설정하는 방법은 아래 링크를 참고하시기 바랍니다.

관리자용 IAM 그룹과 사용자의 작성

관리자용 IAM그룹을 만들고 IAM 사용자를 작성하여 관리자 그룹에 배치시킵니다. 구체적인 방법은 아래 링크를 참고하세요.

IAM유저 중에서도 관리자 권한과 같은 중요한 권한을 지닌 유저에 대해서는 MFA사용을 강제하는것이 좋습니다. 루트 사용자가 아닌 IAM유저에 대해서는 하드웨어 MFA나 가상 MFA이외에 휴대폰 문자 메세지(SMS)에 의한 MFA 사용도 가능합니다.

암호 정책 구성

관리자용 계정으로 로그인 한 후에 암호 정책을 설정하여 IAM 사용자의 암호에 대하여 복잡성 요건과 의무적인 교체주기를 지정할 수 있습니다.

보안 상태(Security Status)의 확인

여기까지 진행하셨다면 AWS를 안전하게 사용하기 위한 최소한의 조치가 완료되었습니다. IAM 서비스 페이지(https://console.aws.amazon.com/iam/home)로 이동하면 Security Status가 다음과 같이 모두 녹색이 되어 있는것을 확인합니다.

Security Status

청구정보 관련 설정

IAM에 대한 설정이 끝났다면 다음은 청구정보에 대한 설정을 진행해 봅시다.

IAM유저의 청구서 정보에 대한 액세스 허가

기본설정에서는 루트 사용자 이외에 유저가 청구정보에 접극하는것이 허가되어 있지 않습니다. 루트 사용자를 가급적 사용하지 않기 위해 IAM유저가 청구서 정보에 접근할 수 있도록 허가해 봅시다.

IAM유저에 대해 결제 정보 액세스를 허용했다면 이후 작업은 관리자 권한을 지닌 IAM유저를 사용해서 진행 할 수 있습니다.

청구정보와 비용 관리에 대한 설정

이메일로 청구서 받기

아래 링크의 지시에 따라 간단한 설정으로 이메일로 청구서를 받아볼 수 있습니다.

또한 프리티어 한고를 초과할 경우 메일이나 휴대폰 문자(SMS)를 통해 알려주는 결제 경보를 생성할 수도 있습니다.

그 밖의 설정들

이메일 설정

AWS로부터의 이메일을 설정합니다. 마케팅 메일을 받을지는 알아서들…

AWS Email Preferences

CloudTrail 유효화

AWS API호출에 대한 이력을 추적할 수 있는 CloudTrail이라는 서비스가 있습니다. 추적 내용은 S3 버킷에 저장됩니다.

git-secrets

git-secrets는 AWS가 만들어 공개하고 있는 툴로서 시크릿 액세스키와 같은 비밀 정보가 git에 커밋 되는 것을 막아줍니다.

GitHub에는 이렇게 잘못 올라온 비밀정보를 노리는 봇들이 수없이 동작하고 있어 실수로 정보가 공개되게 되면 악용되게 될 소지가 큽니다.

Lambda + CloudWatch Events + KMS를 사용하여 AWS 메니지먼트 콘솔에 대한 무단 액세스를 빠르게 감지하기

제목 그대로 입니다. 사실 내용이 초급자가 진행하기엔 만만치 않지만 도움이 되시는 분 도 있을것 같아 링크를 올려봅니다. 원문은 일본어 기사인데 조만간 번역해서 소개해 보기로 하고 일단은 구글번역 링크를 적어봅니다.

끝으로

이것으로 일단 가장 기본적인 설정이 끝났습니다.

추가적으로는 아래 IAM 모범 사례의 내용을 살펴보고 적용시킴으로서 더욱 강력한 보안을 확보 할 수 있습니다. 몇가지는 이 글과 겹치는 부분도 있지만 상세한 내용을 읽고 확인해 두는것도 좋을듯 합니다.

AWSKRUG 신년 콘퍼런스 – re:Invent 특집 (1월 21일)

아마존 웹 서비스를 사용자들이 운영하는 커뮤니티인 “AWS한국사용자모임(AWSKRUG)”에서는 작년 11월 미국 라스베가스에서 열린 AWS re:Invent 2016 행사에 직접 참여하셨던 개발자 분들을 목소리를 통해 클라우드 컴퓨팅의 미래에 대해 알아 보실 수 있는 공유 행사를 개최합니다. 개발자들이 흥미로워할 만한 신규 클라우드 서비스에 대한 심도 있는 세션을 준비했으니 관심 있는 분들의 많은 참여 바랍니다.

  • 일시: 2017년 1월 21일(토) 오후 1시-6시
  • 장소: 세종대학교 학생회관 (지하철 7호선어린이 대공원역 6번출구)
  • 참가 신청: http://onoffmix.com/event/84915

■ 주요 주제

  • 개발자를 위한 Amazon Lightsail Deep-Dive, 정창훈 (당근마켓 개발자)
  • Amazon Athena를 통한 데이터 분석하기, 김명보(VCNC 개발자)
  • Amazon Lex를 이용한 초간단 인공지능 챗봇 만들기, 이두희 (멋쟁이 사자처럼)
  • AWS Step Functions을 통한 서버리스 마이크로서비스 만들기, 김현민 (4CSoft 개발자)
  • Blox: Docker 클러스터링에 ECS만으로는 부족하셨다고요?, 유은총 (스포카 개발자)
  • 실시간 유동 인구 측정을 위한 서버리스 분석 플랫폼 구축 사례, 김승연(제로웹CTO)
  • AWS re:Invent 신규 서비스 총정리, 윤석찬 (AWS 테크에반젤리스트)

Webpack과 자바스크립트용 AWS SDK 를 사용해 어플리케이션을 만들고 번들하기 – 파트 2

※ 원문: https://aws.amazon.com/ko/blogs/developer/using-webpack-and-the-aws-sdk-for-javascript-to-create-and-bundle-an-application-part-2/

이전 글에서 webpack과 자바스크립트 AWS SDK를 사용해서 어플리케이션을 만들고 번들하는 방법에 대해서 소개했습니다.

이번 글에서는 필요한 AWS 서비스로만 번들 만들기, Node.js에서도 동작하는 번들 생성하기와 같은 다른 기능을 파헤쳐보겠습니다.

개별 서비스를 포함하기

webpack을 사용하는 것의 장점 중 하나는 webpack이 의존성을 분석하고 어플리케이션이 필요한 코드만을 포함할 수 있다는 것입니다. 이전 프로젝트에서 webpack이 2.38 MB짜리 번들을 생성했다는 것을 알아챘을 수도 있습니다. 왜냐 하면 webpack이 현재 s3.js에 있는 아래 구문을 기반으로 자바스크립트 AWS SDK 전체를 가져오기 때문입니다.

var AWS = require('aws-sdk');

require 구문을 아래와 같이 바꾸면 webpack이 Amazon S3 서비스만을 가져오게 할 수 있습니다:

var AWS = require('aws-sdk/clients/s3');

AWS.config를 사용해서 설정 가능한 모든 AWS SDK 옵션은 서비스를 초기화 할 때도 설정할 수 있습니다. 아래와 같은 구문으로 전역 설정을 하면 계속해서 모든 서비스에서 AWS 네임스페이스에 접근할 수 있습니다:

var AWS = require('aws-sdk/global');

다음은 이런 변화를 주면 s3.js파일이 어떻게 바뀌는지를 보여주는 예시입니다.

s3.js

// Amazon S3 서비스 클라이언트 가져오기
var S3 = require('aws-sdk/clients/s3');

// 보안 자격 증명과 지역 설정
var s3 = new S3({
    apiVersion: '2006-03-01',
    region: 'REGION', 
    credentials: {/* */}
  });

/**
 * 이 함수는 버킷에서 객체 목록을 검색합니다
 * 그 다음, 제공된 콜백을 전달받은 에러 혹은 데이터와 함께 실행합니다
 */
function listObjects(bucket, callback) {
  s3.listObjects({
    Bucket: bucket
  }, callback);
}

// 함수 핸들러를 내보냅니다
module.exports = listObjects;

이제 npm run build 명령어를 실행하면 아래와 같은 결과를 보여줍니다.

    Version: webpack 1.13.2
    Time: 720ms
      Asset    Size  Chunks             Chunk Names
    bundle.js  797 kB     0  [emitted]  main
      [0] multi main 28 bytes {0} [built]
      [1] ./browser.js 653 bytes {0} [built]
      [2] ./s3.js 803 bytes {0} [built]
       + 155 hidden modules

이제 생성된 번들 용량이 2.38 MB 에서 797 KB로 떨어졌습니다.

최종 용량을 더 줄이기 위해 webpack에서 생성된 코드를 축소하도록 설정할 수 있습니다!

Node.js 번들 생성하기

webpack을 사용해서 설정에서 target: ‘node’ 를 지정하면 Node.js에서 작동하는 번들을 생성할 수 있습니다. 디스크 용량이 제한되어 있는 환경에서 Node.js 어플리케이션을 실행할 때 유용할 수 있습니다.

node.js 파일을 생성해서, Node.js 번들을 빌드하도록 프로젝트를 업데이트 해 봅시다. 이 파일은 browser.js 파일과 거의 동일합니다. 그러나 S3 객체를 DOM 나열하는 대신 콘솔에 출력합니다.

node.js

// listObjects 함수를 가져옵니다
var listObjects = require('./s3');
var bucket = 'BUCKET';
// 지정된 버킷에서 listObjects 를 호출합니다
listObjects(bucket, function(err, data) {
  if (err) {
    console.log(err);
  } else {
    console.log('S3 Objects in ' + bucket + ':');
    // 반환된 각각 객체의 키값을 출력합니다
    data.Contents.forEach(function(metadata) {
      console.log('Key: ' + metadata.Key);
    });
  }
});

다음, node.js를 진입점으로 사용하도록 webpack.config.js 파일을 업데이트하고 webpack이 Node.js 번들을 생성해야 하는 것을 알 수 있도록 target: “node” 라는 필드를 추가합니다.

webpack.config.js

// 파일 경로를 분석하기 위해 path를 포함합니다
var path = require('path');
module.exports = {
  // 어플리케이션의 진입점을 지정합니다.
  entry: [
    path.join(__dirname, 'node.js')
  ],
  // 번들된 코드를 포함하는 출력 파일을 지정합니다
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  // Node.js 번들을 생성해야 하는 것을 알려줍니다
  target: "node",
  module: {
    /**
      * webpack에게 'json'파일 읽는 방법을 말해줍니다
      * 왜냐하면 기본적으로 webpack은
      * 자바스크립트 파일을 다루는 방법만 알기 때문입니다.
      * webpack이 'json' 파일이 포함되어있는 'require()' 구문을 만나면
      * json-loader를 사용하게 됩니다
      */
    loaders: [
      {
        test: /.json$/, 
        loaders: ['json']
      }
    ]
  }
}

새로운 번들을 생성하기 위해 npm run build 명령어를 실행하세요. 커맨드라인에 node bundle.js 명령어를 실행해서 이 코드를 테스트할 수 있습니다. 이렇게하면 콘솔에 S3 객체 리스트가 출력됩니다!

시도해 보세요!

자바스크립트용 AWS SDK v2.6.1 에서 새로 webpack을 지원하는 것에 대한 의견을 듣고 싶습니다! 시도해 보시고 피드백을 코멘트나 Github에 남겨주세요!

KMS로 인증 정보를 암호화 하고 Lambda 실행시에 복호화 하기

원문: KMSで認証情報を暗号化しLambda実行時に復号化する

데이터베이스나 API서버와 같은 외부 시스템과 연결하는 경우 종종 인증 정보를 필요로 하게 됩니다.

이번에는 AWS Key Management Service(이하 KMS)의 공통 키 암호방식을 사용하여 암호화 된 인증 정보를 AWS Lambda 함수의 코드에 포함 된 함수 호출시 인증 정보를 해독하는 방법을 소개합니다.

decrypt-sensitive-data-with-kms-on-lambda-invocation

기본적인 아이디어는 다음 블로그에 쓰여져 있으며, 이 포스팅에서는 KMS를 사용한 암호화 작업만을 추려내었습니다.

http://ijin.github.io/blog/2015/08/06/github-to-lambda-to-slack/

KMS는 마스터 키를 사용한 암호화와 복호화 처리를 API로 분리해 내었기 때문에 이를 사용하여 인증 정보를 암호화 합니다.

KMS와 Lambda의 연계

다음의 순서로 동작을 확인합니다.

  1. AWS KMS 마스터 키 생성
  2. AWS CLI로 마스터 키를 사용하여 암호화와 복호화 처리를 확인
  3. AWS Lambda에서 암호화 된 데이터(ciphertext)를 복호화 하여 출력
  4. AWS Lambda 함수에 KMS:Decrypt 권한을 부여
  5. AWS Lambda 함수를 실행하고 복호화 되어 있는지를 확인

KMS 마스터 키 생성

우선 Lambda 함수에서 사용할 마스터 키를 만듭니다.

관리 콘솔에 로그인한 후 IAM에서 Encryption Keys를 선택하면 KMS관리 화면으로 이동할 수 있습니다. KMS라는 이름은 메뉴상에서는 존재하지 않으므로 주의하시기 바랍니다.

kms_management_console

KMS 키는 리전에 보관됩니다. Filter의 풀다운 메뉴에서 키를 생성하는 지역을 선택하고 “Create Key”를 클릭하여 마스터 키 생성화면으로 이동합니다.

Key Administrative Permissions 와 Key Permissions 에는 평소 사용하는 시스템 관리자를 선택합니다.

AWS CLI에서 암호화및 복호화 처리 확인

프로그램을 작성하기 전에 CLI에서 암호화 및 복호화 작업을 확인합니다.

마스터키의 일람을 확인합니다.

$ aws kms list-keys
{
    "Keys": [
        {
            "KeyArn": "arn:aws:kms:ap-northeast-1:123456789012:key/xxx-yyy-zzz",
            "KeyId": "xxx-yyy-zzz"
        }
    ],
    "Truncated": false
}

마스터키의 ARN을 환경변수에 설정합니다. 키를 지정하는 방법에는 KeyId 또는 별칭(Alias)을 사용하는 두가지 방법이 있습니다.

$ export KEYID=arn:aws:kms:ap-northeast-1:123456789012:key/xxx-yyy-zzz # KeyId
$ export KEYID=arn:aws:kms:ap-northeast-1:123456789012:alias/lambda # Alias

마스터키를 사용한 복호화

마스터키로 암호화를 하기위해서는 Encrypt API를 사용합니다.

$ aws kms encrypt --key-id $KEYID --plaintext 'hello, world!'
{
    "KeyId": "arn:aws:kms:ap-northeast-1:123456789012:key/xxx-yyy-zzz",
    "CiphertextBlob": "CiAUkK3nep3+LDfCjRPA5NDnd5NEXv5BWWjweqEySvaTLBKUAQEBAgB4FJCt53qd/iw3wo0TwOTQ53eTRF7+QVlo8HqhMkr2kywAAABrMGkGCSqGSIb3DQEHBqBcMFoCAQAwVQYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxHrDshpbxSGRgMAXECARCAKHfUd1sJjxRX/7tq7twil6vaXjtPZsnr9AURI1gjR+RPL4WlQTvNDjE="
}

응답(response)의 Plaintext는 복호화한 데이터(즉 plaintext)를 base64로 엔코드한 것 입니다.

base64로 디코드하면 plaintext가 됩니다.

$ aws kms decrypt --ciphertext-blob fileb://encrypted --query Plaintext --output text | base64 --decode
hello, world!

Lambda 함수화

KMS로 암호화 한 후 base64로 엔코딩한 문자열을 람다 함수내에 적어넣어 Lambda함수 실행시에 복호화해 보겠습니다.

보통은 암호화한 유저데이터를 사용하여 무언가 처리를 하지만 이번에는 복호화한 문자열을 그대로 반환합니다.

Lambda함수는 파이썬으로 구현하였습니다.

import base64
import boto3

# base64 encoded ciphertext
ciphertext_blob_encoded = 'CiAUkK3nep3+LDfCjRPA5NDnd5NEXv5BWWjweqEySvaTLBKUAQEBAgB4FJCt53qd/iw3wo0TwOTQ53eTRF7+QVlo8HqhMkr2kywAAABrMGkGCSqGSIb3DQEHBqBcMFoCAQAwVQYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAxHrDshpbxSGRgMAXECARCAKHfUd1sJjxRX/7tq7twil6vaXjtPZsnr9AURI1gjR+RPL4WlQTvNDjE='

# ciphertext
ciphertext_blob = base64.b64decode(ciphertext_blob_encoded)

def lambda_handler(event, context):
    kms = boto3.client('kms')
    dec = kms.decrypt(CiphertextBlob = ciphertext_blob)
    return dec['Plaintext'] #plaintext

변수 ciphertext_blob_encoded 에는 ciphertext를 base64로 엔코드한 문자열을 설정했습니다. 위에서 CLI로 암호화한 문자열을 적어줍니다.

KMS 사용권한을 Lambda 함수에게 부여한다

Lambda함수에 설정한 역할(Role)에 마스터키를 사용한 복호화 권한을 추가합니다. 역할에 인라인 정책(Inline policy)을 추가합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1448696327000",
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:123456789012:key/xxx-yyy-zzz"
            ]
        }
    ]
}

Resource에는 KMS 마스터키의 arn을 설정합니다.

Lambda 함수를 실행한다

$ aws lambda invoke \
        --function-name kms_demo \
        --payload '' \
        response.txt
{
    "StatusCode": 200
}

$ cat response.txt
"hello, world!"

무사히 복호화 되었군요.

마무리

민감한 정보는 KMS로 암호화 함으로서 프로그램의 소스코드 내에 안심하고 적어넣을 수 있습니다. 소스코드와 KMS의 Decrypt API를 실행권한을 모두 획득하지 않는 이상 원래 정보를 손에 넣는것은 불가능합니다.

KMS는 어떻게 써야 하는지 잘 모르겠다고 하는 이야기가 가끔 들려오지만 사용법이 단순하면서도 보안성을 높이는데 있어서 대딘히 강력한 서비스 입니다. 이번 포스팅에서는 이러한 KMS의 유즈케이스를 소개해 보았습니다.

Webpack과 자바스크립트용 AWS SDK 를 사용해 어플리케이션을 만들고 번들하기 – 파트 1

원문 : Using webpack and the AWS SDK for JavaScript to Create and Bundle an Application – Part 1

우리는 자바스크립트용 AWS SDK 2.6.1 버전에서 webpack에 대한 지원을 추가했습니다. webpack과 SDK 같은 툴을 사용하면 모듈화된 코드를 작성할 수 있도록 자바스크립트 모듈을 번들할 수 있는 방법을 제공합니다.

이 글은 webpack과 자바스크립트용 AWS SDK를 사용해서 버킷에 있는 Amazon S3 객체 목록를 보여주는 간단한 어플리케이션을 만들고 번들하는 방법을 설명합니다.

왜 webpack을 사용해야 할까?

webpack과 같은 툴은 어플리케이션 코드를 분석하고 import와 require 구문을 찾아 어플리케이션이 필요로하는 모든 에셋들을 가지는 번들을 생성합니다.

webpack은 기본적으로 자바스크립트 파일을 다루지만 JSON이나 CSS, 이미지 파일같이 다른 종류도 다루도록 설정할 수 있습니다! 이렇게하면 웹페이지를 통해 에셋을 쉽게 전달하도록 어플리케이션의 에셋을 잘 패키징할 수 있습니다.

webpack을 사용하면 필요한 서비스로 이루어진 번들을 만들고 Node.js에서도 작동하는 번들을 만들수 있습니다.

선행 조건

이 글을 따라가기 위해서 node.js와 npm이 설치되어 있어야 합니다 (npm은 node.js와 함께 설치됩니다). 이 툴들이 설치되어 있다면 새로운 디렉토리를 만들고 npm install x 명령어를 사용해 이 프로젝트에 필요한 의존성 파일들을 다운로드 합니다. x에는 아래와 같은 단어가 들어갑니다.

  • aws-sdk: AWS SDK
  • webpack: webpack CLI 와 자바스크립트 모듈
  • json-loader: webpack에게 JSON파일 읽는 방법을 알려주는 플러그인

어플리케이션 세팅하기

프로젝트를 저장하는 디렉토리를 생성하는 것으로 시작합니다. 프로젝트 이름은 aws-webpack으로 짓겠습니다.

어플리케이션은 아래에 있는 세개의 파일을 가집니다:

  • s3.js는 버킷을 문자열로 받아들이는 함수와 콜백 함수를 내보내고 객체 목록을 콜백 함수에 반환합니다.
  • browser.js는 s3.js 모듈을 포함하고 listObjects 함수를 호출하며 결과를 보여줍니다.
  • index.html은 webpack이 생성한 자바스크립트 번들을 참조합니다.

아래와 같이 프로젝트의 루트 디렉토리에 이 파일들을 생성합니다:

s3.js
중요: 보안 자격증명 설정은 스스로 하셔야 합니다.

// AWS SDK를 포함합니다
var AWS = require('aws-sdk');

// 지역(region)과 자격증명(credentials)을 설정 하세요,
// 이것은 서비스 클라이언트로 직접 이동할 수 있습니다
AWS.config.update({region: 'REGION', credentials: {/* */}});

var s3 = new AWS.S3({apiVersion: '2006-03-01'});

/**
 * 이 함수는 버킷에서 객체 목록을 검색합니다
 * 그 다음, 제공된 콜백을 전달받은 에러 혹은 데이터와 함께 실행합니다
 */
function listObjects(bucket, callback) {
  s3.listObjects({
    Bucket: bucket
  }, callback);
}

// 함수 핸들러를 내보냅니다
module.exports = listObjects;

browser.js

// listObjects 함수를 포함합니다
var listObjects = require('./s3');
var bucket = 'BUCKET';
// 특정한 버킷에서 listObjects 를 호출합니다
listObjects(bucket, function(err, data) {
  if (err) {
    alert(err);
  } else {
    var listElement = document.getElementById('list');
    var content = 'S3 Objects in ' + bucket + ':n';
    // 반환된 객체의 키 값을 출력합니다
    content +=  data.Contents.map(function(metadata) {
      return 'Key: ' + metadata.Key;
    }).join('n');
    listElement.innerText = content;
  }
});

지금, Amazon S3에 요청을 담당하는 자바스크립트 파일이 하나, 웹페이지에 S3 객체 목록을 추가하는 자바스크립트 파일이 하나, 하나의 div 태그와 script 태그를 가지고 있는 HTML파일이 있습니다. 웹페이지에서 데이터를 출력주기 전 마지막 순서로 webpack을 사용해서 script 태그를 참조하는 bundle.js 파일을 생성합니다.

webpack 설정하기

일반 자바스크립트 파일을 사용해서 webpack의 설정 옵션을 지정합니다. 기본적으로 webpack은 프로젝트의 루트 디렉토리에 있는 webpack.config.js 을 찾습니다. webpack.config.js 파일을 만들어 봅시다.

webpack.config.js

// 파일 경로를 분석하기 위해 path를 포함합니다
var path = require('path');
module.exports = {
  // 어플리케이션의 진입점을 지정합니다.
  entry: [
    path.join(__dirname, 'browser.js')
  ],
  // 번들된 코드를 포함하는 출력 파일을 지정합니다
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    /**
      * webpack에게 'json'파일 읽는 방법을 말해줍니다
      * 왜냐하면 기본적으로 webpack은
      * 자바스크립트 파일을 다루는 방법만 알기 때문입니다.
      * webpack이 'json' 파일이 포함되어있는 'require()' 구문을 만나면
      * json-loader를 사용하게 됩니다
      */
    loaders: [
      {
        test: /.json$/, 
        loaders: ['json']
      }
    ]
  }
}

webpack.config.js에서 진입점을 browser.js로 지정했습니다. 진입점이란 포함된 모듈 탐색을 시작하는 파일입니다. 그리고 출력을 bundle.js로 정의했습니다. 이 번들은 어플리케이션을 실행하기 위해 필요한 모든 자바스크립트를 포함합니다. s3.js가 browser.js에 포함되어있기 때문에 webpack은 이미 s3.js를 포함하는 것을 알고있습니다. 따라서 s3.js를 진입점으로 지정할 필요가 없습니다. 또한, aws-sdk가 s3.js에 포함되어 있기 때문에 aws-sdk를 포함해야 하는 것도 알고있습니다.

webpack에게 포함된 JSON 파일을 다루는 방법을 말해주는 로더를 지정했음을 주목하세요, 이 경우에 이전에 설치한 json-loader를 사용합니다. 기본적으로 webpack은 자바스크립트 파일만 지원하지만 로더를 사용해서 다른 종류의 파일을 포함하는 지원 또한 추가할 수 있습니다. AWS SDK는 JSON 파일을 엄청나게 사용하기 때문에 이런 추가적인 설정이 없으면 webpack이 번들을 생성할 때 에러를 던집니다.

webpack 실행하기

어플리케이션을 빌드할 준비가 거의 다 됐습니다! package.json에서 scripts 객체에 “build”: “webpack” 를 추가하세요.

{
  "name": "aws-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "build": "webpack"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "aws-sdk": "^2.6.1"
  },
  "devDependencies": {
    "json-loader": "^0.5.4",
    "webpack": "^1.13.2"
  }
}

이제 커맨드라인에서 npm run build 명령어를 실행하면 webpack이 프로젝트 파일의 루트 디렉토리에 bundle.js 파일을 생성합니다. webpack이 알려주는 결과가 아래와 같아야 합니다:

    Version: webpack 1.13.2
    Time: 1442ms
      Asset     Size  Chunks             Chunk Names
    bundle.js  2.38 MB     0  [emitted]  main
      [0] multi main 28 bytes {0} [built]
      [1] ./browser.js 653 bytes {0} [built]
      [2] ./s3.js 760 bytes {0} [built]
       + 343 hidden modules

이제 브라우저에서 index.html파일을 열 수 있고 예시에 있는 것과 같은 출력을 볼 수 있습니다.

시도해 보세요!

다음 글에서는 webpack과 자바스크립트용 AWS SDK를 사용하는 것의 다른 기능을 살펴보겠습니다.

자바스크립트용 AWS SDK v2.6.1 에서 새로 webpack을 지원하는 것에 대한 의견을 듣고 싶습니다! 시도해 보시고 피드백을 코멘트나 Github에 남겨주세요!

AWS Lambda 를 이용한 테스트 구현

원문: http://engineering.pivotal.io/post/running-tests-in-aws-lambda/

본 포스팅에서는 s3cli라는 S3 및 S3호환 Go_lang 기반 클라이언트의 동작 테스트를 위해 AWS Lambda 를 사용한 예를 소개하고 있습니다. 이는 Cloud FoundryBOSH로 알려진 시스템들이 S3나 Swift와 같은 외부 Blob store에 다양한 파일 및 데이터를 저장하기 위해 사용되고 있는 도구 입니다.

아래에 소개되는 테스트의 내용은, AWS access_key_id 및 secret_key_id 없이 IAM Role 을 사용하는 경우 문제없이 S3 버켓에 주어진 권한을 사용해서 접근해야 하는 기능을 테스트하기 위해 구현되었습니다. IAM Role은 AWS가 제공하는 서비스간 상호 접근을 위해 IAM을 통해 할당받은 별도의 사용자 키가 없이도 다른 서비스에 접근 권한을 지정할 수 있는 방법입니다. 권한이 지정된 IAM Role 이 활성화된 상태로 구동된 EC2에서 동작하는 AWS SDK는 인증 정보가 코드에 주어지지 않더라도 내부적으로 임시 인증을 할당 받는 방법을 사용하여 다른 서비스에 접근할 수 있습니다.

EC2 를 사용한 테스트

이러한 IAM Role 기능은 AWS 서비스에서만 유효한 것이며, 따라서 종래의 s3cli 에 대한 기능 테스트 방법은 EC2와 S3 버켓을 준비하고 s3cli가 EC2 위에서 문제없이 동작하는지 확인하는 것이었습니다. 이 테스트를 위해 이전에 우리는 다음과 같은 방법을 사용했었습니다.

  1. CloudFormation 을 사용하여 VPC, IAM Role, 그리고 CI worker로 부터 SSH를 받을 수 있도록 구성된 EC2 인스턴스를 start
  2. 생성된 EC2 인스턴스에 s3cli 바이너리를 SCP로 복사하고 실행하여 바이너리가 의존성 문제 없이 동작하는지 확인
  3. CI Worker는 EC2 인스턴스에 SSH 로 접근하여 테스트를 수행하고 0 이외의 exit 이 발생했는지 확인

위에 설명된 방법의 문제는 일단 CloudFormation 을 사용한 환경의 구성에 시간이 매우 오래 걸린다는 것과, 테스트를 위해 관련이 없는 자잘한 리소스들이 함께 생성되어 비용을 낭비한다는 점, 그리고 기본적으로 이 잠깐의 테스트 하나를 위해 사용되는 EC2의 비용이 낮지 않다는 점에 있습니다. 이를테면 CloudFormation 은 VPC를 생성 수의 제한, EC2 생성 수의 제한등, s3cli의 기능 테스트와는 관계 없는 다른 수많은 원인으로 인해 스택의 생성이 실패할 수 있는 가능성이 있기 때문에, 이를 추적하기 위한 시간과 비용의 낭비를 초래할 가능성이 있겠습니다.

Lambda 를 사용한 테스트

AWS Lambda는 이런 문제를 해결하기에 매우 적절한 도구입니다. Lambda는 이런 간단한 테스트를 위해 EC2를 구동하기 위해 필요한 전체 네트워크 스택과 CloudFormation을 사용할 필요를 없앨 수 있습니다. 또한 CloudFormation을 사용해 스택을 구성하고 EC2 인스턴스를 기동하는데 필요한 기나긴 시간을 낭비할 필요도 없습니다.

한가지 더, s3cli의 기능을 테스트하기에 더 없이 좋았던 것중 하나는 바로 s3cli 도구의 IAM Role 을 사용한 AWS 서비스 연동의 테스트에 10초 이상 필요하지 않다는 것입니다. 즉, 기존 한번 켜면 무조건 1시간의 EC2 비용을 지불하는 대신 10초의 Lambda 사용료만 지불하면 되므로 매우 저렴한 테스트를 구현할 수 있게 된 것입니다.

현재 AWS Lambda 를 사용하는데 있어 제약 조건 중 하나는 바로 Node.js, Python, Java 이 세가지만 사용할 수 있다는 것입니다. 또한 별도의 BASH 지원이 없으므로 임의의 패키지를 만들어 업로드 하는 방법을 사용하고, 이 업로드된 패키지를 읽기 전용 파일 시스템을 통해 접근해서 구동합니다. (물론 Lambda 에서도 /tmp 를 사용하면 파일시스템에 읽기 쓰기 모두 가능합니다.)

배포용 패키지 작성

위에 설명한 방법을 사용하여 s3cli를 테스트 하고 그 결과를 확인하기 위해 아래의 코드를 참조합니다. 여기 예제에서는 Ginko 를 사용 했습니다.

import os
import logging
import subprocess

def test_runner_handler(event, context):
    os.environ['S3_CLI_PATH'] = './s3cli'
    os.environ['BUCKET_NAME'] = event['bucket_name']
    os.environ['REGION'] = event['region']
    os.environ['S3_HOST'] = event['s3_host']

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)

    try:
        output = subprocess.check_output(['./integration.test', '-ginkgo.focus', 'AWS STANDARD IAM ROLE'],
                                env=os.environ, stderr=subprocess.STDOUT)
        logger.debug("INTEGRATION TEST OUTPUT:")
        logger.debug(output)
    except subprocess.CalledProcessError as e:
        logger.debug("INTEGRATION TEST EXITED WITH STATUS: " + str(e.returncode))
        logger.debug(e.output)
        raise

위에 사용된 Lambda 함수는 매우 간단합니다. 첫째, event를 통해 필요한 환경 변수를 가져옵니다. 이 event는 Lambda 함수가 호출될때 함께 전달 됩니다. 그리고 테스트에서 발생하는 로그를 확인하기 위해 STDOUT과 STDERR 로거를 구성합니다. 또한 Lambda의 exit 코드를 확보하기 위해서 try 블럭 안에 서브 프로세스 콜을 구성하고 subprocess.CalledProcessError를 통해 로그를 확보합니다.

s3cli와 테스트를 위한 실행 파일들이 스크립트로서 동일한 디렉토리에서 호출되는 것에 주의 합니다.

다음으로, 테스트 도구와 s3cli에 64-bit Linux 아키텍처를 사용하도록 컴파일 옵션을 구성합니다.

git clone https://github.com/pivotal-golang/s3cli
cd s3cli
GOOS=linux GOARCH=amd64 go build s3cli/s3cli
GOOS=linux GOARCH=amd64 ginkgo build src/s3cli/integration

이제 필요한 파일을 모두 묶어 패키지로 구성합니다.

zip -j deployment.zip src/s3cli/integration/integration.test s3cli lambda_function.py

AWS 환경 준비

s3cli 테스를 위한 사전 준비는 아래와 같습니다.

  • 테스트용 S3 버켓 생성
  • AWS IAM 사용자 생성 A) Lambda 함수 실행 권한 B) CloudWatch 읽기/쓰기 권한 다음 링크의 정책을 적용
  • AWS IAM Service role 생성 AWS Lambda 서비스가 CloudWatch와 S3버켓에 다음의 권한을 허용하도록 구성 s3::GetObject*, s3::PubObject*, s3::List*, s3::DeleteObject*. 이 Role을 생성할때 “AWS Lambda”를 선택하여 Lambda가 CloudWatch와 S3에 쓰기 접근을 할 수 있도록 구성. 이 과정을 거쳐 신규로 생성된 IAM Role 의 ARN 정보를 복사해 두는 것을 잊지 않고 생성될 Lambda 함수에 IAM_ROLE_ARN 환경 변수로 전달 해야 함.

위의 설정 구성은 CloudFormation 템플릿을 사용하여 간편하게 구성이 가능합니다. 아래에 소개된 단계는 AWS 명령줄 인터페이스 를 설정하고 사용하는 방법에 대해서 소개합니다.

# example session
$ aws configure

AWS Access Key ID [****************AAAA]:
AWS Secret Access Key [****************AAAA]:
Default region name [us-east-1]:
Default output format [json]:

Lambda 함수 실행

이제 우리는 압축된 배포 패키지를 준비했으므로 아래의 AWS 명령을 사용하여 Lambda 함수를 생성하고 호출할 수 있습니다.

aws lambda create-function \
  --region us-east-1 \
  --function-name MY_LAMBDA_FUNCTION \
  --zip-file fileb://deployment.zip \
  --role ${IAM_ROLE_ARN} \
  --timeout 300 \
  --handler lambda_function.test_runner_handler \
  --runtime python2.7

aws lambda invoke \
  --invocation-type RequestResponse \
  --function-name MY_LAMBDA_FUNCTION \
  --region us-east-1 \
  --log-type Tail \
  --payload '{"region": "us-east-1", "bucket_name": "YOUR_BUCKET_NAME", "s3_host": "s3.amazonaws.com"}' \
  lambda.log

중요: 위의 내용에 fileb:// 는 잘못 기재된 것이 아님 fileb

aws lambda invoke 명령의 실행 결과는 아래와 같이 JSON 으로 표시 될 것입니다.

{
  "LogResult": "<BASE64 ENCODED OUTPUT TRUNCATED TO LAST 4KB>",
  "FunctionError": "(Unhandled|Handled)",
  "StatusCode": 200
}

첫째로, aws lambda invoke 명령 자체는 실행한 Lambda 함수가 에러 없이 종료 되었는지 아닌지와 관계 없이 동작합니다. 이는 출력되는 FunctionError 키를 통해 해당 Lambda 함수의 실행에 에러가 있었는지를 판단할 수 있으며, 이러한 output에 jq 와 같은 도구를 사용하여 간단히 테스트가 성공인지 실패인지 알수 있겠습니다.

추가적으로, 이 테스트는 기본적으로 4kb 이상의 output 을 생성할 것이며, Base64로 인코딩 된 LogResult를 디코드 하더라도 일부 로그가 삭제되는 현상이 있을 수 있습니다.

완전한 로그를 가져오기

다행이 우리에겐 logging.Logger 로부터 생성되는 console output 은 AWS CloudWatch Logs 로그로 보내도록 구성했습니다. 물론, 이를 위해서는 IAM Role에 Labmda 함수가 로그 그룹, 로그 스트림, 로그 이벤트에 대한 생성 권한이 주어져야 합니다.

CloudWatch 에서 로그를 가져오기 위해서는 Lambda 함수 이름을 바탕으로 로그 그룹의 이름을 사용해야 합니다. 이후 아래의 명령어를 사용하여 누락 없는 전체 로그를 가져올 수 있습니다.

LOG_GROUP_NAME="/aws/lambda/MY_LAMBDA_FUNCTION"
LOG_STREAM_NAME=$(
  aws logs describe-log-streams --log-group-name=${LOG_GROUP_NAME} \
  | jq -r ".logStreams[0].logStreamName"
)
LOG_STREAM_EVENTS_JSON=$(
  aws logs get-log-events \
    --log-group-name=${LOG_GROUP_NAME} \
    --log-stream-name=${LOG_STREAM_NAME}
)

echo ${LOG_STREAM_EVENTS_JSON} | jq -r ".events | map(.message) | .[]"

정리

이와 같은 방법을 사용하면 고가의 EC2 대신 간단한 테스트에 대해 Lambda 를 사용하여 비용과 시간, 그리고 EC2와 VPC 네트워크를 생성하는데 있어 발생할 수 있는 실패요인을 줄일 수 있습니다. 여기서는 s3cli 라는 도구의 IAM Role 동작에 대한 테스트를 구성하는 방법에 대해 기술하였으나, 이와 유사한 목적에 어디든 응용하실 수 있겠습니다.

Amazon API Gateway + Lambda: 텔레그램 및 슬랙 전달 봇 만들기

안녕하세요! GS SHOP에서 IT Driven Open Communication 이라는 프로젝트를 하고 있는 김승연입니다.

저는 전부터 Labmda에 관심이 많았는데요. 아무리 t1.micro로 돌린다고 해도 Tokyo region 기준으로 한 달에 15달러가 나오는게 너무 아깝더라고요. 실제로 서버가 필요한(Web 서버가 요청을 받고 처리하고 응답해주는) 시간은 한 달에 많아도 7분(90ms * 4300 request) 내외였는데 한 달동안 언제든 요청을 받기 위해서 29일 23시간 53분을 기다리는 요금을 내는게 비효율적이라고 생각했어요.

하지만 Lambda는 EC2와는 달리 가입 후 12개월이 지나도 프리티어를 유지해주고 저 정도의 request면 무료로 이용이 가능했습니다!

Lambda-Free

Lambda-Bill

그래서 Lambda를 좋아하고, 이번에 회사에서 텔레그렘을 이용하는 팀과 슬랙을 이용하는 팀이 같이 소통해야될 일이 있어서 텔레그렘과 슬랙을 이어주는 봇을 만들어주면 어떨까? 하는 생각이 있어서 Lambda랑 API Gateway로 만들고 과정 일부를 공유하면 재밌겠다 싶어서 이 글을 쓰게 되었습니다 😀

Lambda 시작하기

먼저 Lambda console에 들어가봅시다! 좌측의 Create a Lambda function 클릭 여러 가지 형태의 lambda function을 만들 수 있는데 한 단계씩 올라갈 것이기 때문에 일단 Skip 하겠습니다! Name에는 TelegramToSlack, 그리고 저는 Python 2.7을 이용할게요. 코드는 Code entry type은 Edit code inline을 선택하고 내용은 간단하게 event를 그대로 return해볼게요.

def lambda_handler(event, context):
  return event

Nodejs version: https://gist.github.com/news700/6f1cbe80663a8965a0832ad277b4970e

Lambda function에는 eventcontext 두 개의 파라미터가 넘어오는데, event는 lambda function으로 넘어오는 파라미터들이 Dictionary 형태로 넘어오는 곳이고 context는 함수 이름, 버젼, arn, 메모리 제한, 남은 시간 등을 가져올 수 있는 객체에요..

넘어가서 handlerrole을 정해줘야하는데요. 기본으로 Handler에는 lambda_function.lambda_handler라는 값이 들어있어요. lambda_function이라는 모듈의 lambda_handler라 는 함수를 사용하겠다는 의미인데 inline으로 코드를 편집하게 되면 우리가 편집하는 코드의 파일명이 lambda_function.py이기 때문에 지금 우리가 짠 함수를 실행하겠다는 의미가 돼요. 나중에 프로젝트의 크기가 커지면 파일을 분리하게 되고 AWS Lambda가 실행해야할 함수를 찾아야될 때 이 값을 조정해서 사용하게 돼요.

Role은 이 함수가 어떤 권한을 가지고 실행될 것이냐를 결정해요. 최소한 lambda function을 실행할 수 있는 권한을 포함해야돼요. 그 외에 우리가 짜는 코드가 필요한 권한이 있다면(dynamodb에 데이터를 작성한다던가) 적절히 AWS IAM에서 Role을 생성하여 선택해주면 돼요. 일단 우리는 콤보박스에서 Create New Role 안의 Basic execution role을 이용할게요. lambda를 실행할 수만 있는 권한이에요. 일단 Role을 생성해준 뒤 해당 Role을 선택하여 사용할 수 있어요. Basic execution role을 클릭하면 새로운 창이 열려요(혹시 팝업이 차단돼서 안열릴 수 있으니 확인해보세요!)

IAM Management Console 창이 새로 떴는데 IAM Role에는 Create a new IAM Role을 선택해주시고 Role Name은 lambda_basic_execution으로 할게요. Allow 하고 돌아오면 Role에 lambda_basic_execution이 생성돼 있어요. 혹시 생성돼있지 않다면 새로고침을 해주세요! 다른 내용은 임시저장돼있어요 ㅎㅎ

여기까지 했다면 대략 이런 화면이 됐을거에요

Create-Lambda

Memory와 Timeout은 사용할 수 있는 memory와 최대 실행시간을 정하는건데요. 기본 값으로 두고 넘어갈게요. Next를 누르고 Create function을 누르면 Congratulations! Your Lambda function “TelegramToSlack” has been successfully created. 라는 메시지가 상단에 뜨네요!

그 위에 Test 버튼이 있어요. 이 함수를 웹상에서 테스트를 해볼 수 있는 기능이에요. JSON 형태로 파라미터를 지정할 수 있는데 이 파라미터들은 event변수에 dictionary 형태로 전달돼요. Hello World template 그대로 Save and test를 눌러봅시다. 그러면 아랫쪽에 Execution result: succeeded를 확인하실 수 있어요! 넘겼던 파라미터들이 잘 return된 모습을 확인할 수 있어요.

이렇게 Lambda의 기본 사용법을 알아봤습니다. 이제 API Gateway와의 연동을 진행해볼게요.

Lambda의 API Gateway 연동

이제 API Gateway Console로 들어가보죠. 파란색 Create API 버튼을 눌러주세요. API name에는 뭐 .. 음 .. Telegram으로 해볼게요. 이제 Create API를 눌러주세요.

이렇게 API Gateway에서 API를 생성해줬어요. Lambda와 연동해주기 위해 다시 Lambda console로 가서 TelegramToSlack을 선택해주세요. 화면에 보면 Code 탭 옆쪽에 API Endpoints 라는 탭이 있는 걸 확인할 수 있습니다.

API-Endpoints-Tab

누르고 들어가서 Add API endpoint를 클릭, API endpoint type에 API Gateway를 선택해주세요. API name을 클릭하면 Telegram이 보일거에요. Telegram을 선택해주시고 Resource name은 API path를 의미해요. /to-slack 정도로 해줄게요. Method는 POST로 설정해주세요. Telegram Bot이 POST요청을 보낼거에요. Deployment stage는 여러 단계의 stage를 두고 API 버젼을 따로 관리할 수 있는데 기본값 prod 그대로 두겠습니다. Security는 Open으로 설정해줘야 Telegram Bot이 들어올 수 있어요. 이제 Submit을 클릭합시다!

Add-Endpoint

이제 다시 API Gateway console로 가서 Telegram을 선택해보면 /to-slack 이 추가돼있는 것을 볼 수 있어요. 클릭해보면 Test를 해볼 수 있어요.

To-Slack-Added

Request body에 {“foo”: “bar”} 를 입력하고 Test를 눌러보면 response가 잘 오는 것을 확인할 수 있어요.

To-Slack-Test

Telegram Bot 세팅하기

Telegram Bot API에는 setWebHook이라는 API가 있는데요. 봇이 볼 수 있는 메시지를 설정한 https 주소로 보내주는 기능입니다. 먼저 Telegram에서 봇을 만들게요. https://core.telegram.org/bots 에 따라 텔레그렘에서 botfather 를 검색해 대화를 시작하면 Bot을 생성하고 API Key를 줍니다. 그 뒤에 텔레그램 Group에서 봇이 메시지를 받기 위해서는 privacy 모드를 해제해줘야돼요. botfather에게 /setprivacy 명령을 이용해 해제할 수 있어요.

Telegram

Lambda console의 API endpoints 탭에 가보면 API endpoint URL이 있습니다. Telegram bot의 web hook을 해당 URL로 걸어줄게요.

$ curl https://api.telegram.org/bot<API TOKEN>/setWebhook?url=<API endpoint URL>
{"ok":true,"result":true,"description":"Webhook was set"}

이제 해당 봇이 참여하는 대화의 메시지들은 우리가 만든 API Gateway로 가고 그 endpoint는 Lambda function을 실행시켜서 POST로 넘어온 값을 그대로 돌려줄겁니다!

Slack에 메시지 보내기

Slack의 Incoming WebHook을 이용해서 쉽게 한 채널에 메시지를 보낼 수 있습니다. Incoming WebHook은 https://slack.com/apps/build -> Make a Custom Integration -> Incoming WebHooks 에서 만들 수 있어요. 메시지를 보낼 채널을 선택하거나 만듭니다. 저는 telegram이라는 채널을 만들었어요. 초록색 Add Incoming WebHooks integration 버튼을 누르면 다음 페이지의 아랫쪽에 Webhook URL을 확인할 수 있어요.

이제 telegram에서 메시지가 올 때 마다 해당 URL에 “asdf”라는 메시지를 보내봅시다. Lambda console에 가서 코드를 다음과 같이 수정해주세요.

import httplib
import json
import urllib

def lambda_handler(event, context):
    conn = httplib.HTTPSConnection('hooks.slack.com')
    conn.request(
        'POST',
        '/services/T01234567/B0123456/abcdefghijklmnopqrstuvwxyz',  # slack에서 받은 webhook path
        urllib.urlencode({'payload': json.dumps({'text': 'asdf'})}),
        {'Content-Type': 'application/x-www-form-urlencoded'}
    )
    conn.getresponse()
    conn.close()

Nodejs version: https://gist.github.com/news700/37ab1ab1aa769c52e0361ee8a8ae0ed9

Save and test를 눌러 실행해보면 Slack telegram 채널에 asdf 라는 메시지가 온 것을 확인할 수 있어요! Telegram에서도 우리가 만든 봇 이름을 검색해서 메시지를 전송해보면 봇한테 메시지를 쓸 때 마다 Slack에 asdf가 오는 것을 확인할 수 있어요!

Slack

이제 Telegram에서 보내주는 포맷에 맞춰 전달받은 텍스트가 있으면 해당 텍스트를 슬랙에 넘겨주는 코드로 바꿔볼게요.

import httplib
import json
import urllib

def lambda_handler(event, context):
    if 'message' not in event:
        return
    if 'text' not in event['message']:
        return
    text = event['message']['text']
    conn = httplib.HTTPSConnection('hooks.slack.com')
    conn.request(
        'POST',
        '/services/T01234567/B0123456/abcdefghijklmnopqrstuvwxyz',  # slack에서 받은 webhook path
        urllib.urlencode({'payload': json.dumps({'text': text})}),
        {'Content-Type': 'application/x-www-form-urlencoded'}
    )
    conn.getresponse()
    conn.close()

Nodejs version: https://gist.github.com/news700/ce8154389f7e0bb7a8cb2b6812a73eb0

Final

이제 코드를 조금 고치면 새로운 방에 초대될 때마다 새로운 슬랙 채널을 만들 수도 있고 작성자도 표시해주거나 사진을 전송할 수도 있겠네요. 새로운 API를 추가해서 슬랙에서 쓴 메시지를 텔레그램에서도 받아볼 수 있겠네요. 여기서부터는 한 번 원하시는대로 직접 만들어보세요!

고맙습니다!