티스토리 뷰
2020년 말, AWS Lambda의 새로운 기능으로 컨테이너 이미지 지원이 발표되었다. 기존 lambda 의 프로비저닝에 비해 얻을 수 있는 이점을 요약하자면 다음과 같다.
- 최대 10GB 크기의 컨테이너 이미지로 패키징 및 배포할 수 있는 기능 제공
- Dependencies 관리 및 설치의 용이성
- 다른 linux distro 의 이미지에서도 사용 가능함. (다만, 좀 까다로움. aws lambdaric 를 설치해야함.)
나는 Dependencies 설치가 쉽다는 점 하나만으로 docker 이미지를 사용할만한 가치가 있다고 생각한다.
Boilerplate
한줄요약 : 🪄 작성된 스크립트만 잘 실행시켜주면 컨테이너 이미지 빌드부터, ECR 등록, Lambda 및 API GW 배포까지 다 해줘요.
(커스터마이징도 쉬울걸요..?)
지금까지 lambda의 프로비저닝 자동화 경험으로는 amazon-linux2 이미지의 VM을 띄워놓고, 그 VM 안에서 빌드 및 프로비저닝을 해왔다. (golang이라면 이럴 필요까지야 없겠지만.) M1 Macbook의 업무 개발 환경으로 넘어오면서 OS에 의존적이지 않은 Lambda 의 프로비저닝이 필요했는데, 이를 해결하기 위해서 위와 같은 보일러플레이트를 만들었다.
PoC 의 개념으로 시작한 보일러플레이트인데, 생각보다 쓸만한 것 같다. 로컬에서 의존성 설치하고 압축해서 프로비저닝하는 것 보다 좋은 것 같다. 로컬머신과 독립된 도커 이미지에서 빌드하기 때문에 완벽히 멱등성(Idempotent)을 확보할 수 있다.
00-login-ecr.sh
aws ecr get-login-password --region "$AWS_REGION" \
| docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com
AWS ECR 에 로그인하여, 토큰을 받아와 docker 로그인하는 쉘스크립트다. 여기서 토큰은 12시간동안만 유효함을 참고한다.
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/get-login-password.html
01-create-ecr-repository.sh
aws ecr create-repository --repository-name "$SERVICE_NAME"
AWS ECR repository를 만드는 쉘 스크립트다.
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/create-repository.html
02-create-ecr-repository.sh
docker build -t "$SERVICE_NAME".
docker tag "$SERVICE_NAME":latest "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com/"$SERVICE_NAME":latest
docker push "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com/"$SERVICE_NAME":latest
Docker 빌드를 하여 tag 후 ECR 에 Push 하는 스크립트다.
03-create-iam-for-lambda.sh
람다를 위한 iam role 을 만든다.
aws iam create-role --role-name "$LAMBDA_ROLE_NAME" --assume-role-policy-document file://iam/role.json
aws iam put-role-policy --role-name "$LAMBDA_ROLE_NAME" --policy-name "$LAMBDA_ROLE_NAME"-policy --policy-document file://iam/policy.json
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/create-role.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/iam/put-role-policy.html
04-deploy-to-lambda.py
람다 함수를 만드는 스크립트다. `--code ImageUri` 플래그를 볼 수 있는데, ECR 이미지를 이용하여 람다를 프로비저닝한다. 람다를 만드는데 드는 시간이 거의 들지 않아 깜짝 놀랬다.
aws lambda create-function \
--role arn:aws:iam::"$AWS_ACCOUNT_ID":role/"$LAMBDA_ROLE_NAME" \
--function-name "$SERVICE_NAME" \
--package-type Image \
--code ImageUri="$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com/"$SERVICE_NAME":latest
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/create-function.html
05-create-api-gateway.py
이 스크립트는 꽤 길다. API Gateway 를 프로비저닝하고 이 API에 리소스를 추가한 뒤, 이 리소스에 메소드를 추가하고, API 스테이지를 만든 후 배포시키는 최종 스크립트다. 스크립트는 길지만 읽기는 어렵지 않을 것이다. 늘상 써오던 방식이지만, python 의 subprocess를 열어 실행시킨다. aws cli 를 여러번 호출하고 있다. python을 쓰는 이유는 여러 리소스들의 id같은 것들을 가지고 있다가 계속 사용해야하기 때문이다.
#! /usr/bin/python3
import common
import os
stage_name = os.getenv('STAGE')
service_name = os.getenv('SERVICE_NAME')
aws_account_id = os.getenv('AWS_ACCOUNT_ID')
aws_region = os.getenv('AWS_REGION')
apigateway_http_method = os.getenv('APIGATEWAY_HTTP_METHOD')
apigateway_authorization_type = os.getenv('APIGATEWAY_AUTHORIZATION_TYPE')
cmd = [
'aws', 'apigateway', 'create-rest-api', '--name', service_name, '--region', aws_region
]
result = common.run_cli(cmd)
apigateway_id = result.get('id')
cmd = [
'aws', 'apigateway', 'get-resources', '--rest-api-id', apigateway_id, '--region', aws_region
]
result = common.run_cli(cmd)
resources = result.get('items')
root_resource_id = resources[0]['id']
cmd = [
'aws', 'apigateway', 'create-resource', '--rest-api-id', apigateway_id, '--region', aws_region,
'--parent-id', root_resource_id, '--path-part', '{proxy+}'
]
result = common.run_cli(cmd)
resource_id = result.get('id')
cmd = [
'aws', 'apigateway', 'put-method', '--rest-api-id', apigateway_id, '--region', aws_region,
'--resource-id', resource_id, '--http-method', apigateway_http_method,
'--authorization-type', apigateway_authorization_type
]
result = common.run_cli(cmd)
cmd = [
'aws', 'apigateway', 'put-method-response', '--rest-api-id', apigateway_id, '--region', aws_region,
'--resource-id', resource_id, '--http-method', apigateway_http_method,
'--status-code', '200'
]
result = common.run_cli(cmd)
lambda_arn = f'arn:aws:lambda:{aws_region}:{aws_account_id}:function:{service_name}'
cmd = [
'aws', 'apigateway', 'put-integration', '--rest-api-id', apigateway_id, '--region', aws_region,
'--resource-id', resource_id, '--http-method', apigateway_http_method, '--type', 'AWS', '--integration-http-method', apigateway_http_method,
'--uri', f'arn:aws:apigateway:{aws_region}:lambda:path/2015-03-31/functions/{lambda_arn}/invocations'
]
result = common.run_cli(cmd)
cmd = [
'aws', 'apigateway', 'put-integration-response', '--rest-api-id', apigateway_id, '--region', aws_region,
'--resource-id', resource_id, '--http-method', apigateway_http_method, '--selection-pattern', '', '--status-code', '200'
]
result = common.run_cli(cmd)
cmd = [
'aws', 'lambda', 'add-permission', '--function-name', service_name, '--source-arn', f'arn:aws:execute-api:{aws_region}:{aws_account_id}:{apigateway_id}/*/{apigateway_http_method}/*',
'--principal', 'apigateway.amazonaws.com', '--action', 'lambda:InvokeFunction', '--statement-id', f'invoke{apigateway_id}'
]
result = common.run_cli(cmd)
cmd = [
'aws', 'apigateway', 'create-deployment', '--rest-api-id', apigateway_id, '--stage-name', stage_name
]
result = common.run_cli(cmd)
print('#' * 80)
print(f'https://{apigateway_id}.execute-api.ap-northeast-2.amazonaws.com/{stage_name}')
print('#' * 80)
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/create-rest-api.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/get-resources.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/create-resource.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/put-method.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/put-integration.html
- https://awscli.amazonaws.com/v2/documentation/api/latest/reference/apigateway/create-deployment.html
10-update-function.sh
코드와 이미지를 수정하고나서 람다에 반영시키기 위한 스크립트다. 위에서 했던 설명의 반복정도다.
docker build -t "$SERVICE_NAME" .
docker tag "$SERVICE_NAME":latest "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com/"$SERVICE_NAME":latest
docker push "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com/"$SERVICE_NAME":latest
aws lambda update-function-code \
--function-name "$SERVICE_NAME" \
--image-uri "$AWS_ACCOUNT_ID".dkr.ecr."$AWS_REGION".amazonaws.com/"$SERVICE_NAME":latest
번외편; 삽질 - 완전 다른 Linux distro 이미지로 해보려다가...
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html