11월 15일(금) ~ 11월 18일(월) 수업 중
AWS Elastic Container Registry(ECR)와 Elastic Container Service(ECS)를 활용하면 애플리케이션 배포 프로세스를 자동화하여 안정성과 효율성을 극대화할 수 있다. ECR과 ECS를 활용한 CI/CD 파이프라인을 단계별로 구축하는 방법을 정리하였다.
전체 목차는 아래와 같다.
0. ECR과 ECS 소개
1. Spring Boot 프로젝트 생성
2. Docker 이용한 CI/CD 적용
3. Git Ops 설정
4. Docker Hub와 연동하여 이미지 빌드 및 배포 자동화
5. AWS ECR(Elastic Container Registry)
6. GitHub Actions에서 ECR에 접근 설정
7. AWS ECS(Elastic Container Service)
8. Github Actions에서 ECS 적용하기 => CD
0. ECR과 ECS 소개
- ECR (Elastic Container Registry): Docker 컨테이너 이미지를 저장하고 관리하는 완전 관리형 컨테이너 레지스트리 서비스로, 높은 가용성과 보안을 제공한다. ECR을 사용하면 Docker Hub처럼 이미지를 쉽게 푸시하고 ECS와 연동하여 배포할 수 있다.
- ECS (Elastic Container Service): 컨테이너 오케스트레이션 서비스로, AWS에서 컨테이너 기반 애플리케이션을 실행, 관리, 확장할 수 있도록 돕는다. ECS는 Fargate(서버리스) 또는 EC2 인스턴스를 기반으로 애플리케이션을 배포할 수 있다.
CI/CD 파이프라인의 주요 단계는 다음과 같다:
- 코드 작성 및 푸시: 애플리케이션 소스를 GitHub 등의 소스 관리 툴에 푸시한다.
- 이미지 빌드 및 ECR 푸시: 소스 코드를 기반으로 Github Actions에서 Docker 이미지를 빌드하고 ECR에 업로드한다.
- ECS 배포: ECR에 저장된 최신 Docker 이미지를 ECS로 배포하여 업데이트된 애플리케이션을 서비스한다.
CI(Continuous Integration): 코드 변경 사항을 통합하고 테스트하여 빌드 가능한 상태를 유지하는 과정 => 코드 작성 및 푸시, 이미지 빌드
CD(Continuous Deployment): 빌드된 애플리케이션을 자동으로 배포하는 과정 => ECR푸시 및 ECS 배포
1. Spring Boot 프로젝트 생성
(1) Spring Project 생성
- 프로젝트 이름(Name)
- Type: Gradle-Groovy
- Group
- JDK, Java 버전 설정
이렇게 4가지 정도 신경써서 작성하고 [Next] 로 넘어간다.
정말 최소한의 스프링 부트 프로젝트를 만들기 위해서 아래 3가지 Dependency를 설정했다.
- Spring Boot DevTools
- Spring Web
- Lombok
(2) Spring Boot 코드 작성
FrontController.java
FrontController.java 파일을 만들고 아래와 같이 작성해준다.
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class FrontController {
@RequestMapping("/")
public Map<String, Object> index(){
Map<String, Object> model = new HashMap<>();
model.put("result", "success");
return model;
}
}
- @RestController: 이 클래스가 REST API 요청을 처리하는 컨트롤러이며, 반환값은 JSON 형식으로 응답.
- @RequestMapping("/"): HTTP 요청의 URL 매핑을 지정한다. http://localhost:8080/로 접근했을 때 이 메서드가 실행된다.
- index() 메서드: 루트 URL 요청이 들어왔을 때, {"result": "success"}와 같은 JSON 형식의 응답을 생성하여 Map<string, object> 형식의 데이터를 반환한다.
- HashMap: 데이터를 저장하기 위해 사용한 자료구조로, 키("result")와 값("success")으로 데이터를 저장한다.
application.yaml
기존의 application.properties 파일을 application.yaml 파일로 수정하고, 80 포트로 접근할 수 있도록 수정한다.
server:
port: 80
2. Docker 이용해서 CI/CD 적용
(1) Docker Image 생성 및 도커 허브에 push
./gradlew clean build
- Gradle Wrapper를 사용하여 프로젝트를 빌드하는 명령어
- clean: 이전 빌드 결과를 삭제
- build: 프로젝트의 컴파일, 테스트, 패키징 단계를 수행해 최종 결과물을 생성하며, 결과물은 build/libs 디렉터리에 생성
- 빌드 결과물로 build/libs 디렉터리에 2개의 JAR 파일이 생성된다.
- Jenkins를 이용하는 경우는 이 과정에서 compileJava를 수행하고, 단위 테스트 및 코드 커버리지 측정을 진행하며, 정적 분석 도구를 사용해 코드 품질을 점검할 수 있다.
(2) 이미지 생성을 위한 Dockerfile 작성
(2-1) Java 17 환경에서 Spring Boot 애플리케이션을 컨테이너화하기 위한 설정을 정의하는 Dockerfile 을 작성
FROM amazoncorretto:17
CMD ["./mvnw", "clean", "package"]
COPY ./build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
- FROM amazoncorretto:17 Amazon Corretto 17(JDK 17 기반)의 Docker 이미지를 기반으로 사용. (Java 실행 환경 제공)
- CMD ["./mvnw", "clean", "package"] 컨테이너 실행 시 Maven Wrapper(mvnw)를 이용해 프로젝트를 빌드하고, clean으로 이전 빌드 결과를 제거한 후 package로 JAR 파일을 생성.
- COPY ./build/libs/*.jar app.jar 로컬 시스템의 build/libs 디렉터리에 있는 JAR 파일을 컨테이너 내부로 복사하여 app.jar 이름으로 저장.
- ENTRYPOINT ["java", "-jar", "app.jar"] 컨테이너가 실행될 때 Java 명령으로 app.jar 애플리케이션을 실행.
(2-2) 이미지 생성을 위한 명령어
현재 디렉터리의 Dockerfile을 기반으로 이미지를 빌드하고 cicd로 태그하는 명령어
docker build -t cicd .
(2-3) 생성된 이미지 확인
도커에 저장된 이미지들의 목록을 확인하는 명령어
docker images
(2-4) 이미지를 컨테이너로 만들어서 실행
이름이 cicd(--name)인 컨테이너를 백그라운드에서 실행하고(-d), 호스트의 80포트를 컨테이너의 80번 포트에 연결(-p)하는 명령어
docker run --name cicd -d -p 80:80 cicd
(2-5) springboot 실행하지 않아도 localhost:80 에서 작동 중인 것 확인
3. GitOps 설정
(3-1) github에서 레포지토리 생성
(3-2) git 레포지토리와 springboot project와 연동
git init
git add .
git commit -m "init"
git branch -M main
git remote add origin https://github.com/[아이디]/[레포지토리이름].git
git push -u origin main
4. Docker Hub와 연동하여 이미지 빌드 및 배포 자동화
(4-1) Docker Hub에 레포지토리 생성
(4-2) Docker Hub에서 Access Token 발급
(4-3) Github Repository에서 Secret Key 생성
아래 세 항목에 대한 시크릿 키를 생성한다.
- DOCKERHUB_TOKEN(필수) - 위에서 발급받은 키
- DOCKERHUB_USERNAME
- DOCKERHUB_REPOSITORY
.github/workflows 디렉터리에 yaml 파일 작성
name: Java and AWS ECS CICD
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
build-docker-image:
#ubuntu 환경에서 수행
runs-on: ubuntu-latest
steps:
#소스 코드 가져오기
- uses: actions/checkout@v3
#JDK 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
#Spring Boot Application Build
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: clean bootJar
#Docker Hub 로그인
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
#이미지 업로드
- name: build and release
run: |
docker build -t ${{ secrets.DOCKERHUB_REPOSITORY }} .
docker tag ${{ secrets.DOCKERHUB_REPOSITORY }}:latest ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:latest
docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:latest
이 워크플로는 Spring Boot 애플리케이션의 빌드 및 Docker 이미지 생성을 자동화하고, Docker Hub에 업로드하는 프로세스를 구현하여 CI/CD를 간소화한다. 코드를 구체적으로 보자면 아래와 같다.
name: Java and AWS ECS CICD
on:
push:
branches: [ "main" ]
- name: 워크플로우 이름을 설정. (Java and AWS ECS CICD)
- on: 특정 이벤트(여기서는 push 이벤트)가 발생했을 때 워크플로를 실행한다.
- branches: main 브랜치에 코드가 푸시될 때 실행된다.
2. 권한 설정
permissions:
contents: read
- contents: read: 워크플로 실행 시 리포지토리 내용을 읽는 권한을 부여한다.
3. Job 정의
jobs:
build-docker-image:
runs-on: ubuntu-latest
- jobs: 작업 단위를 정의.
- build-docker-image: 작업 이름
- runs-on: ubuntu-latest: 해당 작업이 실행될 환경으로 최신 Ubuntu 이미지가 사용된다.
4-1. 작업단계(Steps) - 소스 코드 가져오기
- uses: actions/checkout@v3
- 리포지토리의 소스 코드를 가져온다. 이 단계에서 GitHub Actions Runner에 현재 브랜치의 모든 파일이 복사된다.
4-2. 작업단계(Steps) - JDK 설치
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- Java 17 환경을 설정
- distribution: temurin: Eclipse Temurin JDK를 사용
4-3. 작업단계(Steps) - Spring Boot 애플리케이션 빌드
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: clean bootJar
- Gradle을 사용해 애플리케이션을 빌드
- arguments: clean bootJar: 기존 빌드 결과물을 제거하고 bootJar로 새로 빌드
4-4. 작업단계(Steps) - Docker Hub 로그인
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- Docker Hub에 로그인
- secrets.DOCKERHUB_USERNAME 및 secrets.DOCKERHUB_TOKEN: GitHub Secrets에 저장된 Docker Hub 계정 정보
4-5. 작업단계(Steps) - Docker 이미지 빌드 및 업로드
- name: build and release
run: |
docker build -t ${{ secrets.DOCKERHUB_REPOSITORY }} .
docker tag ${{ secrets.DOCKERHUB_REPOSITORY }}:latest ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:latest
docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:latest
- docker build: 현재 디렉터리의 Dockerfile을 사용해 Docker 이미지를 빌드한다.
- -t ${{ secrets.DOCKERHUB_REPOSITORY }}: 이미지 이름을 설정한다.
- docker tag: 이미지를 태그로 식별한다.
- 최신 버전(:latest)으로 태깅한다.
- docker push: Docker Hub에 이미지를 업로드한다.