VS Code로 AWS SAM에 대한 Remote 컨테이너 개발 환경 구축하기

AWS SAM으로 서버리스 아키텍처를 개발, 관리 그리고 배포하고자 개발 환경을 구축하면서 좌충우돌 했던 많은 시도와 과정을 글로 정리해보고자 합니다.

VS Code를 사용하게 된 이유는, Lambda 함수에서 Python을 사용하려고 하니까 별 다른 선택지가 없었기 때문입니다. AWS Toolkit 플러그인은 여러 IDE에 걸쳐서 존재하나, 일부 IDE에 대해서는 언어 제약이 있었습니다. 그런데, VS Code에서는 Remote 컨테이너 개발 환경을 구축할 수 있다는 사실이 기억났고, SAM 개발 환경을 Host에서 분리하여 언제나 동일한 개발 환경을 유지할 수 있는 Remote 개발 환경을 구축해보고자 하는 욕심이 생겼습니다.

Remote 개발 환경 구축 과정에서 예상되는 가장 큰 난관은 바로 도커였습니다. 컨테이너 개발 환경 자체가 도커 컨테이너로 구축되는데, SAM 에서도 소스 빌드 또는 로컬 Lambda 테스트를 위해서는 컨테이너가 필요하다는 것입니다. 즉, 도커 인 도커 환경이 필요하게 됩니다. 이에 대해서는 본문에서 따로 다루도록 하겠습니다.

1. Docker & VS Code 설치

먼저 Docker를 설치합니다. 필요에 따라서 윈도우 환경에서는 WSL2 등을 설정할 수 있습니다.

그 후에 VS Code를 설치합니다.

VS Code 설치가 완료되면 Remote-Container 익스텐션을 설치합니다. 좌측의 익스텐션 메뉴에서 검색하면 손쉽게 설치가 가능합니다.

2. 샘플 프로젝트

이제, AWS Toolkit을 통해서 제공되는 hello_world 샘플 코드를 이용해서 Remote 개발 환경에 대한 구축을 진행 해보겠습니다. hello_world의 트리 구조는 다음과 같습니다.

$ tree . |-- README.md |-- events | `-- event.json |-- hello_world | |-- __init__.py | |-- app.py | `-- requirements.txt |-- template.yaml `-- tests `-- unit |-- __init__.py `-- test_handler.py 4 directories, 8 files

코드 샘플을 테스트하고 배포하려면 다음과 같은 툴들이 필요합니다.

  • AWS CLI – Credential 관리
  • AWS SAM CLI – SAM 명령 수행
  • Python 3.8 – 로컬 유닛 테스트

이런 개발 환경을 Host에 구축하면 개발 환경을 구축하는 사람마다 필연적으로 CLI와 Python의 세부 버전이 달라질 수 있습니다. Remote 개발 환경을 구축하는 이유는 이러한 개발 환경을 통일하고, 직접적인 패키지 설치 없이 빠르게 개발 환경을 구축할 수 있도록 하기 위함입니다.

3. Remote 환경 정의하기

VS Code에서 Remote 컨테이너에 대한 정의는 .devcontainer 폴더 안에 devcontainer.json 파일과 Dockerfile 또는 docker-compose.yml 파일을 통해서 이루어집니다.

$ tree .devcontainer .devcontainer |-- Dockerfile |-- devcontainer.json `-- docker-compose.yml 0 directories, 3 files

도커 인 도커 dind

앞서, SAM 자체가 이미 도커 컨테이너를 사용하기 때문에 Remote 컨테이너는 반드시 도커 인 도커를 지원해야 한다는 언급을 했습니다. 이를 구현하는 방법은 여러 가지가 있지만, 가장 간단한 방법에는 큰 문제가 있습니다.

가장 간단한 방법은 Host 시스템의 도커 엔진을 컨테이너에서 함께 사용하는 것입니다. 이것은 도커 소켓을 마운트하여 손쉽게 달성할 수 있습니다.

docker run -it -v /var/run/docker.sock:/var/run/docker.sock <docker image>

다만, 컨테이너 안에서 또 다른 도커를 실행하면서 볼륨 마운트가 필요한 경우에 컨테이너 안에서 마운트를 위해 제공하는 Path는 안타깝게도 Host의 Path만 지원합니다. 도커 엔진 자체를 Host의 것을 이용하기 때문에, 도커 마운트 명령에 대한 Path는 컨테이너 내부에서 선언하더라도 결국 Host에 대한 것으로 인식되기 때문입니다. 이러한 이유로 컨테이너 내부의 경로에 대해서는 마운트가 불가능해서 SAM을 이용한 Lambda 함수 테스트 시에 마운트가 제대로 진행되지 않아서 오류가 발생하게 됩니다.

결국 다른 방법을 선택해야 하는데, 바로 도커 인 도커 이미지를 이용하는 것입니다. 이는 도커에서도 제한적으로만 사용할 것을 권장하고 있습니다. 도커에서 자체적으로 제공하는 도커 인 도커 (dind) 이미지를 이용하면 컨테이너에 이미 도커 환경이 구축되어 있어서 개발 환경만 구축해서 사용하면 됩니다. 그래서 저는 Dockerfile에서 docker:stable-dind 이미지를 사용합니다.

Dockerfile

FROM docker:stable-dind RUN apk add python3-dev py3-pip RUN apk add g++ git jq RUN pip install awscli aws-sam-cli

세부적인 버전을 고정하려면 따로 버전을 명시해서 설치하는 것을 권장합니다. AWS CLI는 단순히 Credential을 이용하기 위한 것으로 굳이 V2를 이용하지 않습니다.

docker-compose.yml

Dockerfile을 그대로 실행하면 권한 문제로 dind 이미지가 정상 구동되지 않습니다. 그래서 privileged 옵션을 제공하고 Host의 AWS Credential과 VS Code로 작업하는 워크스페이스에 대한 마운트를 정의합니다.

version: '3' services: vscode: build: context: . dockerfile: Dockerfile volumes: - ..:/workspace:cached - ~/.aws:/root/.aws:cached privileged: true

devcontainer.json

워크스페이스 경로와 컨테이너 빌드 후에 자동으로 설치할 익스텐션을 명시합니다.

{ "name": "hello_world", "dockerComposeFile": "docker-compose.yml", "service": "vscode", "workspaceFolder": "/workspace", "extensions": [ "amazonwebservices.aws-toolkit-vscode", "ms-python.python" ] }

Remote 컨테이너 빌드

이제, Remote 컨테이너를 빌드합니다. F1을 눌러 명령어 화면에서 Remote-Containers: Open Folder in Container 를 눌러 컨테이너 환경을 빌드하고 작업중이던 폴더를 컨테이너에서 열어줍니다. 정상적으로 모든 빌드가 끝나고 화면이 나타나면 아래와 같이 좌측 하단과 폴더 목록 상단에 컨테이너에서 작업중임이 표시됩니다.

4. SAM 테스트

이제 SAM이 정상 동작하는지 확인해봅니다.

빌드

/workspace # sam build SAM CLI now collects telemetry to better understand customer needs. You can OPT OUT and disable telemetry collection by setting the environment variable SAM_CLI_TELEMETRY=0 in your shell. Thanks for your help! Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html Building function 'HelloWorldFunction' Running PythonPipBuilder:ResolveDependencies Running PythonPipBuilder:CopySource Build Succeeded Built Artifacts : .aws-sam/build Built Template : .aws-sam/build/template.yaml Commands you can use next ========================= [*] Invoke Function: sam local invoke [*] Deploy: sam deploy --guided

로컬 Lambda

/workspace # sam local invoke HelloWorldFunction --event events/event.json Invoking app.lambda_handler (python3.8) Fetching lambci/lambda:python3.8 Docker container image........................................................................................................................................................................................................................................................ Mounting /workspace/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container START RequestId: 8b3b24c4-5db9-1b34-d1f9-e27f7aad47ad Version: $LATEST END RequestId: 8b3b24c4-5db9-1b34-d1f9-e27f7aad47ad REPORT RequestId: 8b3b24c4-5db9-1b34-d1f9-e27f7aad47ad Init Duration: 137.77 ms Duration: 2.83 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 34 MB {"statusCode":200,"body":"{\"message\": \"hello world\"}"}

Lambda를 시행하기 위한 도커 이미지를 다운받기 때문에 시간이 좀 걸리지만 정상 동작하는 것을 확인할 수 있습니다.

AWS Credential

패키지와 배포에는 AWS Credential이 요구됩니다. 앞서 Host의 Credential을 마운트 했기 때문에 별다른 설정 없이도 Host의 Access Key 정보를 이용해서 AWS CLI가 동작함을 확인할 수 있습니다.

/workspace # aws s3 ls 2020-04-29 08:35:38 *** 2020-04-29 08:35:37 *** 2020-06-02 02:16:58 *** ...

충분한 IAM 권한이 있으면 SAM 패키지와 배포도 가능합니다. 이것은 Remote 개발 환경이든 뭐든 큰 관계가 없어서 생략하겠습니다.

5. 마무리

이렇게 VS Code를 이용하여 AWS SAM에 대한 Remote 컨테이너 개발 환경을 구축 완료하였습니다. 이제 Git 소스에 .devcontainer를 포함하여 배포하고, 다른 개발자는 VS Code의 명령 화면에서 간단하게 Remote-Containers: Open Repository in Container를 통해서 별도의 사전 설치를 최소화하고 동일한 개발 환경을 공유할 수 있게 되었습니다.

본 예제에서는 사전 설치 패키지가 몇 없어서 굳이 이걸 왜 해야 하느냐고 하실 수 있습니다. 그러나, 다수의 개발자가 복잡하게 구축해놓은 개발 환경을 새로 참여하는 개발자에게 수많은 문서와 설명을 제공하는 것보다는 이렇게 개발 환경 자체를 코드로 정의함으로써 보다 Agile한 개발을 할 수 있다는 장점은 명백합니다.