스프링 프로젝트를 Docker 이미지로 빌드해서 AWS ECR에 올리고 올라간 ECR의 이미지를 기반으로 AWS EB에 배포해보자!
GitHub Actions를 사용하기 위해서는 프로젝트의 최상단 경로에 .github
라는 디렉토리를 만들고 그 아래에 workflows
디렉토리를 만든다. 이제 그 안에 yml
파일을 생성한다.
예시
.github/workflows/example.yml
일단 yml
파일의 내용은 아래와 같다.
name: Build and Push Docker Image
on:
push:
branches:
- deploy
jobs:
build-and-push-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean build -x test
- 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: ap-northeast-2
- 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: ECR 리포지토리 이름
IMAGE_TAG: ECR에 올라갈 이미지의 태그
run: |
docker buildx build --platform=linux/amd64 -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"
- name: Get current time
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYYMMDD_HH-mm-ss
utcOffset: "+09:00"
- name: Generate deployment package
run: |
mkdir -p deploy
cp Dockerrun.aws.json deploy/Dockerrun.aws.json
cd deploy && zip -r deploy.zip .
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v14
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
application_name: AWS EB 애플리케이션 이름
environment_name: AWS EB 환경 이름
version_label: earth-docker-${{steps.current-time.outputs.formattedTime}}
region: ap-northeast-2
deployment_package: deploy/deploy.zip
wait_for_environment_recovery: 200
📌 1
name: Build and Push Docker Image
on:
push:
branches:
- deploy
Build and Push Docker Image라는 이름을 갖는 Workflow는 deploy 브랜치에 Push 될 경우 경우 실행된다.
📌 2
jobs:
build-and-push-image:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
...
Workflow
는 다양한 job
으로 구성된다. 최소 한 개 이상의 job을 정의해야 한다.
build-and-push-image
라는 job
을 생성하고, 그 아래에 steps
가 존재하는 구조다.
runs-on
은 어떤 OS에서 실행될지 지정하는 것으로, ubuntu-latest
최신 버전의 우분투 환경으로 지정했다.
steps
의 uses
는 어떤 액션을 사용할지 지정하는 것이다. 이미 만들어진 액션을 사용할 때 지정한다.
actions/checkout@v2
는 Workflow에서 접근할 수 있도록 리포지토리를 체크아웃하는 데 사용되는 공식 GitHub 작업이다.
체크아웃
: 저장소(리포지토리)에서 파일을 받아오는 것
체크인
: 체크아웃으로 받아온 파일을 수정 후, 저장소(리포지토리)를 새로운 버전으로 갱신하는 것
📌 3
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
자바 버전을 설정한다. 1.8은 Java 8이라는 것인데 Java 11부터는 11로 쓰고 있다.
https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html 에서 봐도 Java 11부터 11로 쓰고 있고, Java 10까지는 1.x로 쓰고 있다는 것을 알 수 있다.
📌 4
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean build -x test
run
에는 커맨드를 넣으면 된다.
graldew
커맨드를 실행할 수 있는 권한을 부여한다. ./gradlew clean build -x test
는 테스트 없이 빌드한다는 뜻이다.
📌 5
- 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: ap-northeast-2
AWS 자격을 증명하는 것이다. AWS iam 사용자의 access key
와 secret key
를 넣으면 되는데 이 부분은 외부에 노출되면 아주 위험하므로 GitHub의 Secrets
를 이용한다. 리포지토리 Settings -> Secrets
로 가면 설정할 수 있다.
📌 6
- 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: ECR 리포지토리 이름
IMAGE_TAG: ECR에 올라갈 이미지의 태그
run: |
docker buildx build --platform=linux/amd64 -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"
로컬 Docker 클라이언트에서 하나 이상의 Amazon ECR 레지스트리에 로그인한다.
도커 이미지로 빌드하고 ECR 리포지토리에 push 한다.
AWS EB로 생성되는 EC2는 linux/amd64
환경이다.
FROM openjdk:8-jdk-alpine
COPY build/libs/*.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
도커 이미지로 빌드 시에 필요한 Dockerfile
파일이다.
alpine
을 붙이면 생성된 도커 이미지의 용량이 더 작다.
gradle로 프로젝트를 빌드해서 생성된 jar
파일을 도커 이미지로 빌드할 때 필요한 파일이다.
gradle로 빌드하면 jar
파일이 2개가 생성되는데 하나는 plain.jar
다. plain.jar
는 의존성 관련 내용이 포함되어 있지 않고 모듈의 클래스와 리소스만 포함하는 파일이다.
(참고: https://stackoverflow.com/questions/67935064/difference-between-spring-boot-2-5-0-generated-jar-and-plain-jar)
build.gradle
에
jar {
enabled = false
}
를 작성하면 gradle build
시에 plain.jar
가 생성되지 않는다.
📌 7
- name: Get current time
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYYMMDD_HH-mm-ss
utcOffset: "+09:00"
우리나라 시간은 UTC 기준으로 9시간이 빠르기 때문에 9시간을 더한다.
📌 8
- name: Generate deployment package
run: |
mkdir -p deploy
cp Dockerrun.aws.json deploy/Dockerrun.aws.json
cd deploy && zip -r deploy.zip .
Dockerrun.aws.json
파일을 zip 파일로 만드는 과정이다.
{
"AWSEBDockerrunVersion": "1",
"Image": {
"Name": "이미지 URI(AWS ECR에서 확인 가능)",
"Update": "true"
},
"Ports": [
{
"ContainerPort": 8080
}
]
}
Dockerrun.aws.json
파일이다. AWS ECR에 올라간 도커 이미지를 실행하는 데 필요한 설정을 하는 파일이다.
📌 9
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v14
with:
aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
application_name: AWS EB 애플리케이션 이름
environment_name: AWS EB 환경 이름
version_label: earth-docker-${{steps.current-time.outputs.formattedTime}}
region: ap-northeast-2
deployment_package: deploy/deploy.zip
wait_for_environment_recovery: 200
아까 생성한 zip 파일을 AWS EB에 업로드 및 배포하는 과정이다.
도커 환경의 AWS EB를 만들고 애플리케이션에 업로드 및 배포를 하려면 아래 사진의 업로드 및 배포
를 클릭해 Dockerrun.aws.json
를 압축한 파일을 올리면 된다.
Dockerrun.aws.json
만 올리는 경우에는 zip
으로 압축하지 않고 올려도 된다고 하는데 아직 직접 해보지 않아 확실하진 않다.위 사진처럼 AWS EB console에 들어가 수동적으로 업로드 및 배포를 할 수 있지만 GitHub Actions
를 활용한 무중단 배포
를 구축할 것이기 때문에 .github/workflows/example.yml
에 위 내용을 작성한다.
이제 99%의 작업은 끝났고 AWS EB의 구성 -> 보안
으로 가서
이 역할에 권한을 하나 추가해야 한다. AWS IAM -> 역할
로 가서 위 사진의 역할로 들어간다.
AmazonEC2ContainerRegistryReadOnly
를 추가하면 정상적으로 작동이 될 것이다.
끝! 👍
+
EB 배포는 기본적으로 한 번에 모두
방법을 사용한다. 이는 아주 잠깐의 다운타임
이 발생할 수 있고 이를 사용하면 위에서 설명한 방법이 무중단 배포
라고 할 수 없을 것 같다. 한 번에 모두
방법은 사용되고 있는 인스턴스에 새로운 버전을 배포하기 때문에 잠깐의 다운타임
이 생겼다고 하면 변경 불가능
방법은 새로운 인스턴스에 새로운 버전을 배포하고 이전 버전을 종료시킨다. 그래서 배포 방법을 변경 불가능
으로 수정했다.
위 사진은 변경 불가능
으로 수정하고 새로운 버전을 배포했을 때 인스턴스 상태 화면이다.
한 번에 모두(All at once) - 가장 빠른 배포 방법이다. 단기간의 서비스 손실이 허용될 수 있고 빠른 배포가 중요한 경우에 적합하다. 이 방법을 사용하면 Elastic Beanstalk에서 각 인스턴스에 새 애플리케이션 버전을 배포한다. 그런 다음 웹 프록시 또는 애플리케이션 서버를 다시 시작해야 할 수 있다. 결과적으로 짧은 시간 동안 사용자가 애플리케이션을 사용할 수 없거나 가용성이 감소할 수 있다.
롤링(Rolling) - 가동 중지를 방지하고 가용성 감소를 최소화하는 대신 배포 시간이 길어진다. 완전한 서비스 손실이 허용될 수 없는 경우에 적합하다. 이 방법을 사용하면 애플리케이션이 한 번에 한 인스턴스 배치로 사용자 환경에 배포된다. 배포 전반에 걸쳐 대부분의 대역폭이 유지된다.
추가 배치를 사용한 롤링(Rolling with additional batch) - 가용성 감소를 방지하지만 배포 시간이 롤링 방법보다도 오래 걸립니다. 배포 전반에 걸쳐 동일한 대역폭을 유지해야 하는 경우에 적합합니다. 이 방법을 사용하면 Elastic Beanstalk에서 추가 인스턴스 배치를 시작한 다음 롤링 배포를 수행합니다. 추가 배치를 시작하는 데는 시간이 걸리며 배포 전반에 걸쳐 동일한 대역폭이 유지됩니다.
변경 불가능(Immutable) - 기존 인스턴스를 업데이트하는 대신 새 애플리케이션 버전이 항상 새 인스턴스에 배포되도록 하는 더 느린 배포 방법입니다. 또한 배포가 실패할 경우 빠르고 안전하게 롤백할 수 있다는 추가 이점이 있습니다. 이 방법을 사용하면 Elastic Beanstalk에서 변경 불가능한 업데이트를 수행하여 애플리케이션을 배포합니다. 변경이 불가능한 업데이트에서 두 번째 Auto Scaling 그룹이 사용자 환경에서 시작되고 새 인스턴스가 상태 확인을 통과할 때까지 새 버전과 기존 버전이 함께 트래픽을 처리합니다.
트래픽 분할(Traffic splitting) - canary 테스트 배포 방법입니다. 이전 애플리케이션 버전을 통해 나머지 트래픽을 계속 처리하면서 수신 트래픽의 일부를 사용하여 새 애플리케이션 버전의 상태를 테스트하려는 경우에 적합합니다.
참고: https://docs.aws.amazon.com/ko_kr/elasticbeanstalk/latest/dg/using-features.deploy-existing-version.html
혹시 EB 환경은 어떻게 설정하셨는지 알 수 있을까요?