Skip to content
LinkedInGithub

AWS SSM을 이용하여 EC2에 Docker 이미지 배포하기

devops, aws, ssm, ec2, github, github-actions, docker, cd2 min read

아래 설명하는 방법은 Docker 이미지를 프로덕션에 배포하는데 사용되기 어렵다. 간단한 테스트 환경이나, 개발 환경과 같이 빠른 배포가 요구되지만, 많은 노력과 시간들 들이기 어려운 경우에 사용하면 좋을 것이다.

AWS SSM?

SSM은 AWS의 리소스 및 온프레미스 서버의 자동 및 수동 작업을 관리하는 역할을 한다. 자세한 내용은 더 나은 글들을 참고하자.

SSM은 사실 인스턴스를 관리하는데 초점이 맞춰져있다. 그래서, SSM을 이용하여 Docker 이미지를 배포하는 것은 조금은 이상해 보일 수 있다. 하지만, SSM을 이용하면, 인스턴스에 접속하지 않고도, 인스턴스에 명령을 내릴 수 있기 때문에(SSM의 SendCommand API), Docker 이미지를 인스턴스에서 내려받는데 사용할 수 있다. 우리는 이 SendCommand를 이용할 것이다.

Step-by-Step

준비물

  • Dockerfile
  • Private ECR

EC2 인스턴스를 생성할 때 사용하는 AMI에는 보통 SSM Agent가 기본적으로 설치되어 있다. 하지만 EC2 인스턴스가 AWS Systems Manager 접근하려면 권한이 필요하기 때문에 먼저, 해당 role을 생성, 이를 EC2에 Attach 시켜주어야 한다.

1. IAM role 생성

  • 우선 SessionManagerPermissions 라는 이름을 가진 Policy 부터 생성한다.

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Action": [
    "ssm:UpdateInstanceInformation",
    "ssmmessages:CreateControlChannel",
    "ssmmessages:CreateDataChannel",
    "ssmmessages:OpenControlChannel",
    "ssmmessages:OpenDataChannel"
    ],
    "Resource": "*"
    },
    {
    "Effect": "Allow",
    "Action": [
    "s3:GetEncryptionConfiguration"
    ],
    "Resource": "*"
    }
    ]
    }
  • 그 다음, MySessionManagerRole 라는 이름을 가진 Policy를 생성한다.

    이때 Trust Policy는 아래와 같고,

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Principal": {
    "Service": "ec2.amazonaws.com"
    },
    "Action": "sts:AssumeRole"
    }
    ]
    }

    Permission 은 방금 만든 Policy인 SessionManagerPermissions를 가지도록 설정한다.

2. EC2 인스턴스 생성

배포 머신이면서 곧, 서비스 머신인 인스턴스를 하나 생성하자. 해당 인스턴스에 필요한 Private Key 설정, SSH 연결, 웹 접속을 위한 Load Balancer 설정이나 도메인 연결등은 여기서 설명하지 않는다.

원하는 AMI를 이용하여 EC2 인스턴스를 생성하고, 생성시 IAM Role로 1에서 만든 SessionManagerPermissions를 가지도록 설정한다.

만약 EC2 인스턴스를 생성할 때, IAM Role을 설정하지 않았다면, 인스턴스 > Actions > Security > Modify IAM role에서 수정할 수 있다.

3. EC2 인스턴스 기본설정

반드시 root 권한으로 아래 설정들을 완료한다.

  • aws-cli 설치
  • docker 설치

SSM은 기본적으로 root 권한으로 모든 커맨드를 실행하기 때문에 aws cli 나 docker login 처럼 user에 영향을 받는 경우 root 권한으로 실행해야 삽질을 예방할 수 있다.

왜 User에 영향을 받나?
aws configure는 .aws/configure에, docker login은 .docker/config.json에 저장되기 때문이다.

4. Docker login

인스턴스에서 도커에 로그인한다. 이때에도 반드시 유저는 root 권한을 가지고 있어야 한다.

# aws configure 로 적절한 권한의 profile이 설정되었다고 가정한다.
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin <ecr-registry-url>

여기까지 완료되면, 기본적으로 도커 이미지 삭제, 컨테이너 삭제, 이미지 pull, 컨테이너 실행 등의 기본적인 도커 명령어가 로컬에서 작동함을 기대할 수 있기 때문에 외부에서 SSM을 통해 실행할 수 있는 환경이 만들어졌다.

5. 워크플로우 작성

일단 간단하게 AWS Private ECR에 Docker 이미지를 배포하는 Github Actions workflow를 짜보자.

.github/workflows/development.yml
---
name: "development"
'on':
push:
branches:
- 'deploy/dev'
jobs:
ci:
name: "Integration"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout Code"
uses: "actions/checkout@v2"
- name: "Use Node.js"
uses: actions/setup-node@v1
with:
node-version: '16.x'
- name: "Install packages"
run: |
npm install
- name: "Run unit tests"
run: |
npm run test
cd:
name: "Deployment"
runs-on: "ubuntu-latest"
needs:
- ci
steps:
- name: "Checkout Code"
uses: "actions/checkout@v2"
- name: Set tag
id: vars
run: echo "::set-output name=rev_sha::$(git rev-parse HEAD)"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY_NAME }}
IMAGE_TAG: ${{ steps.vars.outputs.rev_sha }}
run: |
cat .env.example | envsubst > .env
docker build -f Dockerfile -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

위 workflow는 간단한 CI 과정을 거친 뒤, 커밋 해시를 태그로 하는 Docker 이미지를 빌드하고, 이를 ECR에 푸시하는 간단한 workflow이다.

6. SSM 연결성 테스트

로컬 머신에서 인스턴스 머신이 가지는 aws profile과, instance-id를 활용하여 아래 커맨드를 실행해보자.

aws ssm send-command --document-name "AWS-RunShellScript" --document-version "\$LATEST" --parameters '{"workingDirectory":["/ubuntu"],"executionTimeout":["3600"],"commands":["ls -al"]}' --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --region ap-northeast-2 --profile <aws-profile> --instance-id <instance-id>

결과는 https://ap-northeast-2.console.aws.amazon.com/systems-manager/run-command/executing-commands?region=ap-northeast-2 에서 확인할 수 있다.

7. GitHub Actions 업데이트

여기까지 완료되었다면, workflow에 아래의 내용을 추가하자.

.github/workflows/development.yml
- name: AWS SSM Send-Command
uses: peterkimzz/aws-ssm-send-command@master
id: ssm
with:
aws-region: ${{ secrets.AWS_REGION }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
instance-ids: ${{ secrets.DEV_INSTANCE_ID }}
working-directory: /root
command: docker stop $(docker ps -a -q) && docker rmi $(docker images -a -q) || true && docker pull ${{ steps.build-image.outputs.image }} && docker run -d -p 80:80 --name api ${{ steps.build-image.outputs.image }}
comment: Run docker

기존에 돌고있던 Container를 멈추고, 각종 이미지를 일괄적으로 삭제한 뒤(없어도 exit code를 발생하지 않도록), docker 이미지를 풀 받고, docker run 하는 커맨드가 포함되어있다.

기본적으로 여러개의 커맨드를 실행할 수 없기 때문에 위와 같이 In-line으로 한번에 실행하거나, 특정 shell file을 올려두고, 이를 실행하는 방법으로 해결할 수 있다.