AWS CDK와 Serverless Framework 함께 사용하기
AWS CDK와 Serverless Framework 함께 사용하기
이 가이드에서는 서버리스 애플리케이션을 만드는 두 가지 방법을 살펴보았습니다. SST 사용과 Serverless Framework 사용입니다. 하지만 이 두 가지를 함께 사용할 수도 있습니다.
즉, SST로 인프라를 생성하고 Serverless Framework로 Lambda 함수와 API를 관리할 수 있습니다. 이렇게 하고 싶은 몇 가지 이유가 있습니다.
- 현재 Serverless Framework를 사용하여 애플리케이션을 구축 중이며, 리소스에 대해 CloudFormation 대신 AWS CDK를 사용하고 싶은 경우.
- 또는 Serverless Framework에서 SST로 마이그레이션을 고려 중인 경우.
이번 장에서는 SST를 사용해 CDK로 인프라를 정의하고, 이를 Serverless Framework 앱에 연결하는 방법을 살펴보겠습니다.
배경
Serverless Framework와 CDK를 함께 사용하는 방법을 이해하기 위해, 먼저 각각의 앱 구조를 살펴보겠습니다.
Serverless Framework 앱 아키텍처
Serverless Framework 앱은 여러 서비스로 구성될 수 있으며, 전체 앱이 동일한 환경에 배포됩니다.
Serverless Framework는 내부적으로 CloudFormation을 사용한다는 것을 기억할 것입니다. 따라서 각 서비스는 CloudFormation 스택으로 대상 AWS 계정에 배포됩니다. 스테이지, 리전, AWS 프로필을 지정하여 이를 커스터마이징할 수 있습니다.
$ AWS_PROFILE=development serverless deploy --stage dev --region us-east-1
여기서 --stage
옵션은 스택 이름 앞에 스테이지 이름을 붙입니다. 따라서 동일한 AWS 계정에 여러 스테이지를 배포하더라도 리소스 이름이 충돌하지 않습니다.
이를 통해 서버리스 앱을 여러 환경에 쉽게 배포할 수 있습니다. 심지어 동일한 AWS 계정 내에서도 가능합니다.
위 예제에서 동일한 앱이 세 가지 다른 스테이지에 세 번 배포되었습니다. 두 스테이지는 동일한 AWS 계정에 있고, 세 번째는 별도의 계정에 있습니다.
이것은 serverless deploy
명령의 옵션을 변경하는 것만으로 가능합니다. 이를 통해 코드를 변경하지 않고도 여러 환경/스테이지에 배포할 수 있습니다.
CDK 앱 아키텍처
AWS CDK 앱은 여러 스택으로 구성됩니다. 각 스택은 CloudFormation 스택으로 대상 AWS 계정에 배포됩니다. 하지만 서버리스 앱과 달리, 각 스택은 서로 다른 AWS 계정이나 리전에 배포될 수 있습니다.
CDK 스택을 배포할 AWS 계정과 리전을 정의할 수 있습니다.
new MyStack(app, "my-stack", { env: { account: "1234", region: "us-east-1" } });
이것은 CDK 앱을 배포할 때마다 여러 환경에서 스택을 생성할 수 있다는 것을 의미합니다. 이 중요한 설계 차이 때문에 CDK 앱을 서버리스 서비스와 함께 직접 사용할 수 없습니다.
CDK 앱에서 특정 규칙을 따르면 이 문제를 해결할 수 있습니다. 하지만 이 규칙이 강제되는 경우에만 효과적입니다.
이상적으로는 CDK 앱이 Serverless Framework 앱과 동일한 방식으로 작동하길 원합니다. 그래서 함께 배포할 수 있습니다. 이는 앱을 자동으로 배포하기 위해 git push
를 할 때 더 중요해집니다.
SST가 이 문제를 해결합니다.
Enter, SST
SST는 Serverless Framework와 동일한 규칙을 따를 수 있게 해줍니다. 이는 여러분이 Lambda 함수를 다음과 같이 배포할 수 있다는 의미입니다.
$ AWS_PROFILE=production serverless deploy --stage prod --region us-east-1
그리고 나머지 AWS 인프라는 CDK를 사용할 수 있습니다.
$ AWS_PROFILE=production npx sst deploy --stage prod --region us-east-1
Serverless Framework와 마찬가지로, CDK 앱의 스택은 스테이지 이름으로 접두사가 붙습니다. 이제 Serverless Framework와 CDK를 함께 사용할 수 있습니다! 이를 통해 다음과 같은 작업을 수행할 수 있습니다.
여기서는 위의 Serverless Framework 예제와 마찬가지로, 앱이 세 개의 서비스로 구성됩니다. 단, 그 중 하나의 서비스는 SST를 사용해 배포된 CDK 앱입니다!
이를 배포할 때는 표준 cdk deploy
명령어 대신 npx sst deploy
명령어를 사용합니다.
이제 이 프레임워크들이 내부적으로 어떻게 동작하는지 알았으니, Serverless Framework로 빌드된 노트 앱을 인프라를 정의하는 SST 앱에 연결하는 방법을 살펴보겠습니다.
SST 앱 참조하기
먼저 serverless.yml
에 SST 앱을 참조하도록 추가합니다.
services/notes/serverless.yml
파일 상단에 provider:
블록 위에 다음 custom:
블록을 추가합니다.
custom:
# stage는 serverless 명령어 실행 시 전달된 값을 기반으로 설정됩니다.
# 또는 provider 섹션에 설정된 값으로 대체됩니다.
stage: ${opt:stage, self:provider.stage}
# 인프라를 배포하는 SST 앱의 이름
sstApp: ${self:custom.stage}-notes-infra
여기서 notes-infra
는 infrastructure/sst.json
에 정의된 SST 앱의 이름입니다.
{
"name": "notes-infra",
"region": "us-east-1",
"main": "stacks/index.js"
}
이제 serverless.yml
에서 정의한 내용을 좀 더 자세히 살펴보겠습니다.
-
먼저
stage
라는 커스텀 변수를 생성합니다.provider:
블록에 이미stage: dev
가 설정되어 있는데 왜 커스텀 변수가 필요한지 궁금할 수 있습니다. 이는serverless deploy --stage $STAGE
명령어를 통해 전달된 값을 기반으로 현재 프로젝트의 스테이지를 설정하기 위함입니다. 만약 배포 시 스테이지가 설정되지 않았다면, provider 블록에 설정된 값으로 대체됩니다.${opt:stage, self:provider.stage}
는 Serverless Framework에게 먼저opt:stage
(명령줄을 통해 전달된 값)를 찾고, 그 다음으로self:provider.stage
(provider 블록에 설정된 값)로 대체하도록 지시합니다. -
다음으로, SST 앱의 이름을 커스텀 변수로 설정합니다. 여기에는 스테이지 이름도 포함됩니다 —
${self:custom.stage}-notes-infra
. 이는 현재 서버리스 앱이 배포된 스테이지에 해당하는 SST 앱을 참조하도록 구성됩니다. 따라서 API 앱을dev
에 배포하면, dev 버전의 SST notes 앱을 참조하게 됩니다.
이 두 가지 간단한 단계를 통해 Serverless Framework와 CDK 앱을 SST를 사용하여 (느슨하게) 연결할 수 있습니다.
참고로, serverless.yml
의 상단은 다음과 같이 보일 것입니다.
service: notes-api
# 함수에 대해 최적화된 패키지 생성
package:
individually: true
plugins:
- serverless-bundle # Webpack으로 함수 패키징
- serverless-offline
- serverless-dotenv-plugin # .env를 환경 변수로 로드
custom:
# stage는 serverless 명령어 실행 시 전달된 값을 기반으로 설정됩니다.
# 또는 provider 섹션에 설정된 값으로 대체됩니다.
stage: ${opt:stage, self:provider.stage}
# 인프라를 배포하는 SST 앱의 이름
sstApp: ${self:custom.stage}-notes-infra
provider:
name: aws
runtime: nodejs12.x
stage: dev
region: us-east-1
DynamoDB 참조하기
이제 SST를 사용해 생성한 DynamoDB 테이블을 프로그래밍 방식으로 참조해 보겠습니다.
serverless.yml
파일에서 environment
와 iamRoleStatements
블록을 다음으로 교체하세요.
# 이 환경 변수들은 함수에서 process.env로 사용 가능합니다.
environment:
stripeSecretKey: ${env:STRIPE_SECRET_KEY}
tableName: !ImportValue "${self:custom.sstApp}-TableName"
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Scan
- dynamodb:Query
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:DescribeTable
# IAM 역할 권한을 특정 스테이지의 테이블로 제한합니다.
Resource:
- !ImportValue "${self:custom.sstApp}-TableArn"
들여쓰기를 정확히 복사했는지 확인하세요. provider
블록은 다음과 같이 보일 것입니다.
provider:
name: aws
runtime: nodejs12.x
stage: dev
region: us-east-1
# 이 환경 변수들은 함수에서 process.env로 사용 가능합니다.
environment:
stripeSecretKey: ${env:STRIPE_SECRET_KEY}
tableName: !ImportValue "${self:custom.sstApp}-TableName"
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Scan
- dynamodb:Query
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:DescribeTable
# IAM 역할 권한을 특정 스테이지의 테이블로 제한합니다.
Resource:
- !ImportValue "${self:custom.sstApp}-TableArn"
여기서 무엇을 하고 있는지 살펴보겠습니다.
-
SST 앱 이름을 사용해 CDK에서 설정한 CloudFormation 내보내기를 가져옵니다.
-
tableName
을 하드코딩된notes
에서!ImportValue '${self:custom.sstApp}-TableName'
으로 변경합니다. 이는 CDK에서 내보낸 테이블 이름을 가져옵니다. -
마찬가지로
!ImportValue '${self:custom.sstApp}-TableArn'
을 사용해 테이블 ARN을 가져옵니다. 이전에는 Lambda 함수에 리전 내 모든 DynamoDB 테이블에 대한 접근 권한을 부여했지만, 이제는 권한을 더 구체적으로 제한할 수 있습니다.
스테이지 이름을 설정에서 광범위하게 사용하고 있다는 것을 눈치챘을 것입니다. 이는 앱을 여러 환경에 동시에 배포할 수 있도록 하기 위함입니다. 이 설정을 통해 스테이지 이름만 변경하면 새로운 환경을 쉽게 생성하거나 제거할 수 있습니다.
Cognito 인증 역할에 추가하기
람다 함수에 IAM 접근 권한을 부여하는 주제를 다루고 있으니, API에도 비슷한 작업을 해야 합니다.
여기서 주의할 점은, 람다 함수나 DynamoDB 테이블에 대한 접근 권한을 명시적으로 부여할 필요가 없다는 것입니다. 이는 API 엔드포인트 수준에서 접근을 보호하기 때문입니다. API 엔드포인트에 접근할 수 있다면, 람다 함수에도 접근할 수 있다고 가정합니다. 그리고 앞서 설정한 DynamoDB 권한은 사용자가 아닌 람다 함수를 위한 것입니다. S3 버킷의 경우 사용자가 직접 파일을 업로드하므로, 이에 대한 접근도 보호해야 합니다. 다시 말해, 사용자가 접근할 수 있는 외부 접점은 API 엔드포인트와 S3 버킷 두 가지입니다. 따라서 이 두 가지에 대한 접근을 보호해야 합니다.
이제 SST 앱에서 생성한 인증 역할에 API 엔드포인트를 추가해 보겠습니다.
serverless.yml
에 새로운 리소스를 추가합니다.
Resources:
CognitoAuthorizedApiPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyName: ${self:custom.stage}-CognitoNotesAuthorizedApiPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "execute-api:Invoke"
Resource: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayRestApi}/*"
Roles:
- !ImportValue "${self:custom.sstApp}-CognitoAuthRole"
YAML은 읽기 어려울 수 있지만, 여기서 하는 작업은 다음과 같습니다.
-
${self:custom.stage}-CognitoNotesAuthorizedApiPolicy
라는 새로운 정책을 생성합니다. 여러 환경에 배포할 때 고유한 이름을 사용하도록 합니다. -
이 정책은
arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayRestApi}/*
리소스에 대한execute-api:Invoke
접근 권한을 가집니다. 이 리소스를 API에 연결하면,ApiGatewayRestApi
변수가 생성 중인 API로 대체됩니다. -
마지막으로, 이 정책을 이전에 생성하고 내보낸 역할인
!ImportValue '${self:custom.sstApp}-CognitoAuthRole'
에 연결합니다. 이 역할은 SST 앱에서 내보내야 합니다.
이제 SST 앱에서 생성한 인프라를 Serverless Framework 앱과 연결할 수 있습니다.
For help and discussion
Comments on this chapter