AWS Lambda 로 배포 자동화 구축 (feat. Docker Hub)

2024. 5. 30. 17:28☁️ Cloud

 

서론

최근 해커톤을 나가면서 배포 자동화 프로세스를 고민하게 됐다. 필자는 백엔드 개발자이기 때문에 프론트엔드 개발자가 빠르게 서버와 통신할 수 있도록 먼저 EC2 인스턴스를 배포했다.

 

기존 배포 프로세스는 아래와 같다.

1. 로컬에서 스프링부트 도커 이미지를 빌드하여 도커 허브로 push

2. EC2 인스턴스에 SSH 접속하여 도커 허브의 이미지 pull

3. pull 받은 이미지를 빌드하여 EC2 내부에서 서버 실행

- EC2 내부에 shell script 를 작성하여 image pull 과 container run 은 한번에 진행할 수 있다.

기존 프로세스

 

위 방법은 3가지 스텝으로 구분 돼 비교적 간단해보이지만, 코드에 변경이 있을 때마다 EC2 내부에서 새로운 도커 이미지를 pull 받는 과정을 반복해야 했다. 매번 EC2 내부에 접속하는 것 말고 더 간단한 방법은 없을까? 도커 허브로 이미지를 말아 올리기만 해도 EC2에 자동 배포 할 수 있는 방법은 무엇이 있을까. 이미 잘 알려진 Github action을 이용한 방법이 있지만 단순 EC2 배포만 하고 싶었기에 Github action을 사용하는 것은 과하다고 생각했다.

단순히 서버를 띄우는 작업만 자동화 하고 싶었기에 도커 허브의 웹훅을 사용해 AWS Lambda로 배포하는 방법을 선택했다. 

 

대략적인 개요는 아래와 같다. 

 

1. 로컬에서 도커 허브로 이미지 Push

2. 도커 허브에 Webhook 설정하여 이미지가 푸시되면 특정 URL 호출 (여기서는 AWS API Gateway url에 해당)

3. API Gateway 가 트리거 되면 Lambda 함수를 실행하여 EC2 내부의 쉘 스크립트 실행.

4. 쉘 스크립트는 도커 허브의 이미지를 pull 받아 자동 실행하는 명령어로 설정.

 

기존 배포 프로세스는 개발자가 직접 push, pull 을 해야했지만, 변경된 프로세스에선 이미지 push 만 하면 된다. 매번 EC2 로 접속해서 이미지를 pull 받는 번거로움을 덜어준다.

 


 

 

1. 도커허브 이미지 pull 및 Webhook 설정

 

도커허브에 이미 레포지토리가 있다면 해당 레포지토리를 사용해도 좋고, 새로 이미지를 push 해서 레포지토리를 생성해도 된다.

필자는 아래처럼 shell script 를 만들어 로컬에서 푸시하도록 만들었다.

#!/bin/bash

# Clean the project
./gradlew clean

# Build the project
./gradlew build

# Set the Docker image name and tag
DOCKER_IMAGE_NAME=tbnsok/guitar4u
DOCKER_IMAGE_TAG=latest

# Build the Docker image using buildx
docker buildx build --platform linux/amd64 --load --tag ${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG} .

# Push the Docker image to the Docker Hub registry
docker push ${DOCKER_IMAGE_NAME}:${DOCKER_IMAGE_TAG}

 

 

이미지가 성공적으로 push 되면 레포지토리가 생성된 것을 확인할 수 있다.

 

 

해당 레포지토리의 Webhooks 탭으로 들어가면 Webhook Url 을 등록할 수 있다. 이곳에 API Gateway Url 을 입력하고 저장하면 된다.

그럼 이제 API Gateway  Url 을 등록하기 위해 AWS Console 로 이동하자. 

 

2. IAM 세팅

우선 람다를 생성하기 전에 IAM 세팅을 한다. 람다 함수와 EC2 에 적용할 IAM 역할을 각각 생성한다.

람다에 적용할 IAM 을 먼저 생성해보자.

 

 

1. 사용할 서비스는 Lambda 로 선택하고 다음을 누른다.

2. 필요한 권한을 추가한다. 우선 AWS System Manager 권한이 필요하므로 AmazonSSMFullAccess policy와 CloudWatch 로그수집을 위해 AWSLambdaBasicExecutionRole policy 를 추가했다.

3. 필요한 policy 를 선택하여 Role 의 이름까지 설정하면  Lambda 에게 적용할 IAM policy 는 생성이 완료된다. 필자는 Role 의 이름을 이해하기 쉽게 ssm-lambda 로 정했다.

 

 

 

3. Lambda 함수 생성

AWS Lambda 탭에서 함수를 생성하자. 필자는 파이썬 코드로 함수를 작성할 거라 런타임은 python 으로 지정했다. 

실행역할은 기존 역할 사용 토글을 체크하고, 이전에 생성한 IAM 역할(ssm-lambda)를 지정한다. 설정이 완료되면 함수를 생성하자. 

 

 

 

 

람다 함수가 생성된 후 아래의 코드를 사용해서 람다를 배포했다. 중요한 부분은 commands 인데, ssm_agent 가 EC2 Instance 에 보내는 command 를 의미한다. 아래 코드에선 run.sh 쉘 스크립트를 실행하기 위한 commands 로 구성돼 있다.

import json
import boto3
import time

def lambda_handler(event, context):
    # EC2 인스턴스 ID
    instance_id = '인스턴스ID'
    print("Starting EC2 command execution")

    # AWS EC2 클라이언트 생성
    ec2 = boto3.client('ec2')
    # EC2 인스턴스에 명령어 실행
    ssm_client = boto3.client('ssm')
    try:
        # AWS SSM client
        ssm_client = boto3.client('ssm')
        response = ssm_client.send_command(
            InstanceIds=[instance_id],
            DocumentName='AWS-RunShellScript',
            Parameters={
                'commands': [
                    'cd /home/ec2-user/'
                    ,'./run.sh'
                ]
            }
        )
        
        print("SSM command response: ", response)
        
        command_id = response['Command']['CommandId']
        
        # Wait for the command to complete
        time.sleep(5)
        
        # Get the command invocation result
        invocation_response = ssm_client.get_command_invocation(
            CommandId=command_id,
            InstanceId=instance_id
        )
        
        print("Command invocation response: ", invocation_response)
        
        return {
            'statusCode': 200,
            'body': json.dumps('Docker image pull command sent to EC2 instance')
        }
    except Exception as e:
        print(f"Error: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps(f"Internal server error: {str(e)}")
        }

 

 

4. API Gateway 세팅

람다 함수 배포까지 완료되면 해당 람다를 외부에서 호출할 수 있도록 API 게이트웨이를 설정해준다.

'트리거 추가' 버튼을 눌러 람다를 호출할 수 있는 API Gateway 를 추가하자. 

API 게이트웨이까지 추가하면 위 화면처럼 보인다.

 

API Gateway 의 엔드포인트가 생성되면 이를 도커 허브 웹훅의 url 로 등록해주자.

그럼 도커허브에 이미지가 push 될 때 webhook 이 해당 api gateway 의 url 을 호출한다. Api gateway 는 람다함수의 트리거로 동작하여 람다함수를 실행시킨다.

 

 

5. EC2 인스턴스 세팅

우선 EC2 도 Lambda 와 마찬가지로 IAM role 을 세팅해주어야 한다. 아래처럼 권한 정책을 설정하고  Instance 와 연결해주자.

 

이제 EC2 내부에 SSH 접속하여 amazon-ssm-agent 를 설치/실행해야 한다.

$ sudo systemctl status amazon-ssm-agent
$ sudo yum update -y
$ sudo yum install -y amazon-ssm-agent
$ sudo systemctl start amazon-ssm-agent
$ sudo systemctl enable amazon-ssm-agent

# amazon-ssm-agent 상태 확인
$ sudo systemctl status amazon-ssm-agent

 

 

ssm-agent 의 status 를 확인했을 때 위 처럼 나오면 정상 동작하는 것이다. 만약 active 하지 않다면 로그를 살펴보고 대처할 수 있다. 필자는 IAM role 을 제대로 설정하지 않아서 몇번 에러를 겪었다. 위에서 언급한 IAM policy 를 잘 적용해주자 ⭐️

 

이제 EC2 내부에 shell script 를 추가해주자. 사실 이 부분은 개발자마다 설정하는 방법이 다를 것 같은데, 필자는 EC2 내부에 shell script 를 미리 만들어두고 Lambda 에서 해당 스크립트를 실행하도록 만들었다.

스크립트는 기존에 EC2에서 실행 중인 도커 이미지를 중지시키고 삭제한 후, 도커 허브에서 최신 도커 이미지를 pull 받고 실행하도록 작성했다. 즉 Lambda 함수가 EC2 외부에서 쉘 스크립트를 실행하면, 도커 허브의 이미지 pull 부터 실행까지 한큐에 진행할 수 있게 된다. 아래는 필자가 작성한 shell script 의 일부다.

#!/bin/bash

# Specify the image name

image_name="tbnsok/guitar4u"

# Get the list of all running container IDs for the specified image
container_ids=$(docker ps -q --filter ancestor=$image_name)

# Check if there are any running containers for the specified image
if [ -z "$container_ids" ]; then
  echo "No running containers for image $image_name to stop and remove."
  exit 0
fi

# Stop and remove each container
for container_id in $container_ids; do
  echo "Stopping container ID: $container_id"
  docker stop $container_id
  
  echo "Removing container ID: $container_id"
  docker rm $container_id
done

echo "All running containers for image $image_name have been stopped and removed."

docker pull $image_name
docker container run -t -p 8080:8080 $image_name

 

이로써 docker hub ---webhook---> api gateway ---trigger---> lambda ---command---> ec2 deploy 가 한큐에 진행될 수 있다.

 

개선된 프로세스

 


결론

첨엔 간단한 방법이라 소개했지만 생각보다 이것저것 세팅할 것이 많았다. 그래도 AWS Lambda 와 docker 에 익숙한 사람이라면 금방 적용할 수 있는 내용이라 생각한다. 필자는 사실 Lambda 를 처음 사용해봐서 초기 세팅에 조금 애먹었다. (특히 IAM ㅎㅎ,,)

그래도 한번 세팅하고 나면 도커 허브에 이미지를 말아올리는 것 만으로 자동으로 서버를 배포할 수 있다. 역시 뭐든 초기 세팅이 어려운 법이다.

 

사실 배포 자동화하면 먼저 떠오르는건 젠킨스나 Github action 을 이용한 방법이다. 그럼에도 굳이 Lambda 를 통한 배포 자동화를 선택한 이유는 필자가 겪은 환경이 단기 해커톤이라는 짧은 레이스였기 때문이다. Github action 을 이용한 배포 자동화도 좋은 방법이지만, 뭐랄까 하룻밤에 모든걸 태워버리는 해커톤에서 사용하기엔 세팅이 생각보다 오래 걸릴 것이라 생각했다. 

 

물론 Lambda 를 통한 배포 자동화가 무조건 빠르고 짱이야! 라고 말하기에도 무리가 있다. 우선 이 방법은 무중단 배포를 세팅하기 쉽지 않고, 도중에 람다가 동작하지 않는(ex, cold start) 문제도 무시할 수 없다. 비용문제도 무시할 수 없다. 배포용으로 람다를 호출하는건 크게 비용이 들지 않을거라 생각한 면도 있지만 그래도 유료는 유료다.

 

필자가 구상한 방법은 운영상 무중단 배포가 꼭 필요하지 않으면서 단기간에 간단한 배포 자동화 기능만 필요할 때 사용하면 좋다고 생각한다. 딱 해커톤 용 배포 자동화로 사용하면 좋지 않을까,,? 라고 생각하며 글을 마무리합니다 안농!