본문 바로가기
Public Cloud/AWS - Practice

[AWS] EC2 Jenkins 기반 ECS 배포 파이프라인 구성

by ymkim 2025. 6. 9.

 

 

[Hands On] CI/CD – Jenkins pipeline을 이용한 ECS 배포 - NDS Cloud Tech Blog

이번 포스팅은 CI/CD의 기본적인 구조인, Jenkins pipeline을 이용해 ECS에 배포하는 Hands On을 진행 하겠습니다. 0. 개요 <CI/CD 란?> CI/CD란, 지속적인 통합, 지속적인 배포라는 개념의 애플리케이션 개발

tech.cloud.nongshim.co.kr

이번 시간에는 AWS EC2에 Jenkins를 설치한 후, CI/CD 배포 파이프라인을 구성해본다. 우선 CI 단계에서는 Spring Boot 애플리케이션의 Docker 이미지를 생성하여 ECR에 업로드하고, 이후 Jenkins를 기반으로 ECS에 배포하는 과정을 진행한다. 자세한 내용은 대부분 생략이 되었으며, 개인적인 기록용으로 사용된 포스팅이니, 위 블로그를 통해 자세한 부분은 설정해주면 될 것 같다.

01. EC2 Jenkins 설치

CI/CD’지속적인 통합(Continuous Integration)’’지속적인 배포(Continuous Deployment)’를 의미하며, 애플리케이션 개발부터 배포 단계의 모든 부분을 자동화함으로써, 변경 사항을 짧고 빠른 주기로 운영 환경에 적용하는 방법이다. 개념에 대한 내용은 간단히만 짚고, 우선 Jenkins가 설치되어 있는 EC2를 생성하는 부분부터 진행하자.

01-1. Jenkins용 EC2 생성

우선 Jenkins를 구동할 EC2를 생성한다. Terraform 기반으로 EC2를 생성하기에 EC2 생성 과정은 생략하며, Terraform 소스는 위 리포지토리를 참고하자. 이제 EC2 생성이 완료되었으면, 기존에 발급받은 search-jenkins-test.pem 키를 통해 EC2에 접속한다. 대신, 해당 EC2의 인바운드는 8080 포트가 열려 있어야 한다. 해당 부분은 인지하고 시작하자.

01-2. EC2 기본 환경 셋팅

cat <<'EOF'> run_jenkins.sh
#!/bin/bash
# Jenkins 베어메탈 설치 참고 URL
# - https://medium.com/@navidehbaghaifar/how-to-install-jenkins-on-an-ec2-with-terraform-d5e9ed3cdcd9

cd ~ && mkdir -p apps && cd apps

##############################
# 시스템 설정 및 기본 패키지 설치
##############################
echo -e "################################"
echo -e "# 시스템 설정 및 기본 패키지 설치"
echo -e "################################"
# 호스트명 설정
sudo hostnamectl --static set-hostname search-jenkins-test-01
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
echo

##############################
# 필수 패키지 설치
##############################
echo -e "################################"
echo -e "# 필수 패키지 설치"
echo -e "################################"
sudo dnf update -y # 시스템 패키지를 모두 최신 버전으로 업그레이드

# 필요한 패키지 한 번에 설치
sudo dnf install docker -y
sudo dnf install java-17-amazon-corretto -y
sudo dnf install wget -y
sudo dnf install git -y
sudo dnf install curl -y

# Docker 설정
sudo systemctl start docker # Docker 데몬 수동으로 시작
sudo systemctl enable docker # 시스템 부팅 시 Docker 데몬 자동 시작
sudo usermod -aG docker ec2-user # ec2-user가 Docker 명령어를 sudo 없이 실행할 수 있도록 권한 부여
sudo yum install java-17-amazon-corretto -y
java -version
echo

##############################
# Jenkins 설치
##############################
# Jenkins 리포지토리 추가
# Amazon Linux, CentOS 기본 리포지토리에는 Jenkins가 없거나,
# 너무 오래된 버전이 존재할 수 있기에, Jenkins 공식 리포지토리를 추가

# yum+dnf 패키지 설치 시 -> /etc/yum.repos.d/*.repo -> 경로를 검색해서 설치한다
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/redhat-stable/jenkins.repo

# 패키지 검증을 위해 Jenkins가 제공하는 공개키(GPG Key)를 시스템에 등록
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
echo

echo "Jenkins 설치 시작..."
sleep 5
sudo dnf install jenkins -y
sudo systemctl enable jenkins
sudo systemctl start jenkins
sudo systemctl status jenkins
echo
echo "Jenkins 설치 완료..."
EOF

EC2를 구동했다면, 위 shell script를 실행 해준다. 해당 script를 실행하면 Jenkins 설치가 완료된다. 설치 후에는 <public IPV4 address>:8080 형태로 서버에 접속을 진행하고 아래 명령어를 Jenkins UI의 Administrator password에 입력해준다.

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

위 명령어를 통해 나온 비밀번호를, Jenkins UI의 비밀번호로 입력한다.

후에는 Jenkins에서 사용할 플러그인을 설치한다. Install suggested plugins을 선택하여, Jenkins에서 추천하는 플러그인을 설치한다. 만약, 특정 플러그인만 설치하고자 할때는 Select plugins to install을 선택한다.

플러그인 설치가 완료되면, 관리자 계정을 생성하는 UI가 보인다. 여기서 관리자 ID, Password를 입력하고 Save and Continue 버튼을 클릭해준다.

또한, 다음 화면으로 Jenkins URL UI가 보인다. 해당 화면은 Jenkins가 어떤 주소(URL)로 접근이 되는지 지정하는 화면이다. 특이사항이 없으면 Save and Finish 버튼을 클릭한다.

위 과정을 모두 진행하면, 위와 같이 Jenkins가 정상 구동하는 것을 확인할 수 있다.

02. Jenkins Freestyle 파이프라인 구성

02-1. Jenkins 진입 화면

EC2 서버에 Jenkins를 설정하고, 기본적인 플러그인을 설치하는 과정은 완료하였다. 이제 Freestyle 방식으로 Jenkins 작업(Job)을 1개 생성한 후에, 간단한 build script를 실행하는 테스트를 진행할 예정이다. 우선, 이미지에서 보이는것과 같이 왼쪽 상단의 새로운 Item or New Item 버튼을 클릭한다.

02-2. Freestyle 옵션 정리

New Item을 선택하면 위와 같은 화면이 출력된다. 우선 빌드 작업(Job)의 이름을 기재하고 Freestyle project를 선택한다. 추가로 각 옵션에 대한 내용은 아래와 같다. 각 옵션에 대해 알아두는 것이 좋기에, 개념에 대해 짚고 넘어가는 시간을 가져보자.

1. Freestyle project(UI상에서 간단한 스크립트 제어)

  • Freestyle project은 Jenkins에서 제공해주는 가장 기본적인 Job 유형으로, 빌드, 테스트, 배포 작업을 Web UI 상에서 쉽게 컨트롤 할 수 있다. Freestyle project는 Jenkins를 처음 접하는 사람이나, 간단한 빌드 수행 시 유용하며, Web UI에서 필요한 옵션에 대하 체크하여 Job을 구성한다
  • Freestyle project은 간단하게 구성이 가능하지만, 병렬처리가 거의 불가능(가능하나 여러개의 Job 구성)하다고 봐야하며, 다수의 Repository와 연계하여 사용하는 것이 불가능하다. 또한, 파이프라인에 비해 커스터마이징이 제한적인 단점도 존재한다

2. Pipeline(CodeBuild 처럼 파이프라인 스크립트를 작성)

  • Jenkinsfile을 통해 코드 및 파이프라인 설정을 Git, GitLab과 같은 저장소에 저장할 수 있다. 이를 통해 코드 리뷰가 가능해지는 것은 물론이며, 버전 관리도 용이해지는 장점이 있다
  • Pipeline 옵션의 경우 Jenkinsfile을 기반으로 복잡한 CI/CD 워크플로우를 단순화 할 수 있다. GUI를 통해 실시간으로 빌드 현황을 파악할 수 있으며, 빌드에 소요된 시간도 확인 가능하다. 하지만, Jenkins 파일이 Groovy 기반으로 작성되기에, Groovy 언어를 알아야한다.

3. Multi-configuration Project(다양한 환경에서의 빌드/테스트 반복 자동화)

  • Multi-configuration Project는 같은 작업(Job)을 다양한 환경(Env)에서 반복 실행할 수 있도록 도와주는 Jenkins 기능이다. 예를 들어, Java 8, Java 17에서 빌드가 모두 잘 되는지, 또는 Ubuntu와 CentOS에서 테스트가 통과하는지를 자동으로 확인할 수 있다
  • 위 기능은 하나의 작업(Job)을 여러 환경에 자동으로 병렬 실행해주기 때문에, 호환성 테스트나 환경별 검증에 매우 유용하다. 하지만, 배포까지 담당하는 건 비추하는 것으로 보인다

4. Folder

  • Folder는 Jenkins에서 여러 작업(Job)을 폴더처럼 묶어서 정리할 수 있는 기능이다. 작업(Job)이 많아질수록 대시보드가 복잡해지는 경우가 있는데, Folder를 사용하면 팀별, 서비스별, 기능별로 작업(Job)을 구분할 수 있다
  • 확실한거는 더 찾아봐야 하는데, 해당 Folder 안에서 Item을 생성하는 기능은 있으나, 기존 작업(Job)을 옮기는 기능은 존재하지 않는 것으로 보인다.

5. Multibranch Pipeline

  • Jenkins에서는 동적으로 브랜치가 생성되는 경우, 이러한 이벤트를 감지하고 자동으로 작업(Job)을 만들어주는 기능은 Freestyle, Pipeline, Multi-configuration 같은 기능에서는 제공이 되지 않는다. 하지만, 이러한 기능을 제공해주는 Jenkins 옵션이 Multibranch Pipeline 옵션이다
  • 해당 옵션은, dev, master, featA같은 브랜치가 동적으로 생성되는 경우, Jenkins가 자동으로 이벤트를 감지하여, 해당 PR에 맞는 작업(Job)을 신규로 생성해준다

6. Organization Folder

  • Organization Folder란 Github, GitLab과 같은 조직(Organization)에 연결하여, 해당 조직에 포함된 여러 저장소를 Jenkins가 자동으로 스캔하고, 각 저장소에 대해 Multibranch Pipeline를 자동으로 생성 + 관리해주는 기능이다
  • Jenkins는 저장소 안의 브랜치나 PR도 자동으로 감지하여, 각 브랜치에 포함된 Jenkinsfile을 기반으로 파이프라인을 실행한다. 대신, 저장소가 많아지면 불필요한 작업이 발생할 수 있기에 특정 패턴만 적용이 되게 해야한다

02-3. Freestyle - 간단한 빌드 스크립트 출력

우선 간단한 Freestyle 작업(Job)을 생성해보자. 작업(Job)명을 위와 같이 기재하고, Freestyle project를 선택하고 OK를 클릭한다. OK를 클릭하면 Configuration 화면이 UI상에 표시되는 부분을 확인 할 수 있을 것이다.

우선 설명 란에는 해당 작업이 어떤 내용을 수행하는 작업(Job)인지 기재한다. 또한, 모든 옵션에 대한 설명은 아래와 같다.

1. General

  • Github project
    • 해당 작업(Job)이 Github 프로젝트와 연동되어 있는 경우 체크한다. 체크 시 프로젝트 URL 입력이 가능하며, Jenkins 대시보드에서 해당 Github 링크로 이동이 가능하다
  • Thottle builds
    • 동시에 실행될 수 있는 빌드 수를 제한하는 옵션이다. 병렬로 여러 빌드를 실행하는 것을 방지하여, 리소스 낭비를 막을 때 사용한다. 예를 들어, 동시에 3명이 수동으로 Job을 실행한 경우, Thottle builds 설정이 없으면, 3개의 빌드가 동시 실행된다. 하지만 설정한 경우 나머지 2개의 빌드는 대기 큐(Queue)로 들어간다
    • Number of builds는 동시에 실행이 가능한 최대 빌드 개수를 의미하며, Time period는 빌드 수행 이후 몇초간 대기 후 추가 빌드가 가능한지 설정하는 옵션이다. 또한, Allow user triggered builds to skip the rate limit 옵션은, 사용자가 수동으로 실행한 빌드는 제한을 무시하고 실행을 허용해줄지 여부를 지정하는 옵션이다
    • 중요한 부분은 Thottle builds 옵션은, 시간과 상관없이 A → B → C 순으로 이전 빌드 작업이 모두 완료 되어야 다음 빌드 작업을 진행할 수 있다. 병렬 처리를 위해서는, concurrent 빌드 옵션을 사용해야 한다
  • 오래된 빌드 삭제
    • 일정 빌드 개수 + 일정 기간이 지난 빌드를 자동으로 삭제하기 위한 옵션이다
  • 이 빌드는 매개변수가 있습니다
    • 작업(Job) 실행 전에 사용자에게 입력받는 매개변수가 있는 경우 설정한다
  • 필요한 경우 concurrent 빌드 실행
    • 특정 작업(Job)이 실행 중인 경우, 동시에 또 다른 빌드 실행을 허용하는 옵션이다

2. 소스 코드 관리

  • 소스 코드 관리는 Jenkins에서 어떤 소스 저장소를 사용할지 지정하는 옵션이다
    • Repository URL (예: Github 주소) : Jekins가 소스를 Download할 리포지토리 주소 지정
    • Credentials (예: 인증 정보) : Private Repo에 접근하는 경우, 인증 정보 기재
    • Branch to build (예: main, dev) : 어떤 브랜치를 기준으로 빌드할지 지정하는 옵션

3. Triggers

  • 빌드를 원격으로 유발
    • HTTP 요청을 통해, 외부에서 Jenkins Job을 실행할 수 있게 해주는 옵션이다
    • Jenkins에 등록된 Token 값과 함께 URL을 호출하면 실행된다
    • Github+GitLab Webhook 같은 자동 트리거 방식을 대체할 수 있다
  • Build after other projects are built
    • 다른 Jenkins 작업(Job)의 빌드가 완료 된 후에, 해당 Job을 자동으로 실행하도록 설정하는 옵션
    • 예) Front → Backend → Deploy 순서로 실행하고 싶을 때
      • Job A (빌드)
      • Job B (테스트) → 여기서 위 옵션 설정
      • Job C (배포)
  • Build periodically
    • Jenkins를 해당 작업(Job)을 주기적으로 실행 할 때 사용하는 옵션
  • GitHub hook trigger for GITScm polling
    • Github 푸시(Push), PR 이벤트 발생 시, 해당 이벤트 기반 Jenkins 작업(Job) 자동 실행하는 트리거
    • Github에 Webhook을 걸고, Github이 Jenkins로 HTTP POST 요청을 보낸다
    • Jenkins는 해당 HTTP POST 요청을 받고, SCM(Source Code Management) 변경 여부를 polling 방식으로 Github에 확인하고 빌드를 수행
  • Poll SCM
    • Jenkins가 Git 저장소를 일정 주기마다 직접 검사하는 방식
    • 소스코드 변경이 감지되면 자동으로 작업(Job)을 실행하는 트리거

4. Environment

  • Delete workspace before build starts
    • 빌드 시작 전에 작업 디렉토리를 완전히 삭제하는 옵션
    • 이전 빌드의 파일, 캐시, 설정이 현재 빌드에 영향을 주지 않게 초기화
    • 즉, 깨끗한 상태에서 빌드를 수행하고자 할 때 사용
  • Use secret text(s) or file(s)?
    • Jenkins 내부의 시크릿 텍스트, 파일, 키 등을 빌드 환경에 주입
    • API 키, 인증 토큰 민감 정보를 보호하기 위해 사용
  • Add timestamps to the Console Output
    • 빌드 콘솔 로그, 각 출력 라인에 타임스탬프 추가
  • Inspect build log for published build scans
    • Gradle이 만드는 빌드 보고서 링크를 Jenkins가 보여주는 기능
  • Terminate a build if it's stuck
    • Jenkins 빌드가 멈춘 상태가 되면, 강제 종료하는 기능
  • With Ant
    • Ant 기반으로 빌드하는 경우 사용

5. Build Steps

  • Jenkins가 실제로 어떤 작업을 수행할지 정의하는 단계

6. Build 후 조치

  • 빌드가 완료된 후에 Jenkins가 추가로 수행할 작업을 정의하는 단계

02-4. Freestyle Github 연동 빌드

1. Freestyle Item 생성

우선 대시보드 상에서 New Item 버튼을 클릭한다. 후에 Freestyle project를 클릭하고, 해당 작업(Job)의 내용을 입력한다.

2. General 설정

설명 란에는 해당 작업(Job)이 어떤 작업을 수행하는지 입력한다. 또한, Github Project의 경우 연결되는 Github URL을 입력하기 위함이고, Throttle builds 옵션을 1분으로 잡아서, 동시 빌드 작업을 제한한다.

2. Github 리포지토리 연결, Credentials 설정

 

Github Repository URL을 작성하였으면, 하단의 Credentials 정보를 설정해야 한다. 하지만, Jenkins 최초 셋팅이기 때문에 설정한 Credential 정보가 없으니, Add 버튼을 클릭하고 Credentials를 추가한다. Username에는 Github 사용자명을 넣고, 비밀번호, ID까지 입력해주면 될 것 같다. 또한, Credentials 정보를 모두 추가하였으면, brance 정보를 지정해준다. 또한, 어떤 브랜치를 기준으로 지정할지 설정이 필요한데, master 브랜치를 대상으로 빌드를 수행하도록 설정한다.

  • Username: github 로그인 ID 입력
  • Password: github access token 정보 입력
  • ID: 식별자 이름으로, credential의 별칭을 지정하는 옵션
  • Description: 해당 credential에 대한 설명 지정

3. Github WebHook 설정

다음으로는 빌드 유발 설정을 한다. 이번에는 Github에 Push를 하는 경우, Jenkins에서 자동으로 build 하는 방법에 대한 설정이다. 여기서는 Github hook Trigger for GITScm Polling 체크 박스를 선택한다. 또한, 해당 옵션은 Github WebHook 설정이 필요한 옵션이다. 관련 내용은 다른 분이 정리한 내용을 참고하자.

추가적으로 아래 Github IP 대역을 EC2 Jenkins의 인바운드에 추가해주어야 한다.
필자의 경우 테스트이기 때문에, 8080 포트에 아래 IP 대역을 추가해주었다.

 

Jenkins 깃허브 훅 설정 - GitHub hook trigger for GITScm Polling 설정하기

Jenkins에서 CI/CD를 구축하다 보니 "빌드 유발"에서 GitHub hook trigger for GITScm Polling가 존재했고 이에 이것을 어떻게 설정하는지 설명하고자 한다. 1. Github에서 세팅하기 1-1. Settings 들어가기 Jenkins의 CI

curiousjinan.tistory.com

"192.30.252.0/22",
"185.199.108.0/22",
"140.82.112.0/20",
"143.55.64.0/20"

후에는 Triggers 옵션의 Github hook Trigger for GITScm Polling 옵션을 체크를 해준다. 해당 옵션은 Github WebHook 이벤트가 발생하는 경우, Jenkins에 Trigger를 발생시키는 옵션이다.

4. 빌드 스텝 설정

Build Step의 경우 Invoke Gradle script을 선택한다. 해당 옵션은, 해당 프로젝트를 Gradle로 빌드 한다는 것을 의미하는 옵션이다. 여기서 Invoke Gradle은 Jenkins에 이미 설치된 Gradle을 사용하는 것을 의미하며 버전은 따로 지정이 가능하다. 또한, Use Gradle Wrapper 옵션은 프로젝트 폴더 안에 있는 Gradle 빌드 도구를 사용하는 것을 의미한다. 해당 옵션을 사용하면 프로젝트마다 다른 버전의 Gradle을 사용할 수 있다. 마지막으로 Tasks는 어떤 행위를 할지 지정해주는 설정이다. 예를 들어, clean build(지우고 다시 빌드), test(테스트) 등의 설정을 지정할 수 있다.

5. Gradle Installation 설치

  • System Configuration > Tools 이동
  • Tools 화면 하단의 Gradle Installation 셋팅
  • Gradle Installation 셋팅 이유는, Jenkins 내부에서 Gradle 빌드를 수행할 때 필요한 Gradle 환경을 구성하기 위함

6. 테스트 빌드 수행

마지막으로 빌드 시작 버튼을 클릭하면, 위와 같이 빌드에 성공한 화면을 확인할 수 있다.

03. Jenkins Pipeline 옵션 사용

 

[Hands On] CI/CD – Jenkins pipeline을 이용한 ECS 배포 - NDS Cloud Tech Blog

이번 포스팅은 CI/CD의 기본적인 구조인, Jenkins pipeline을 이용해 ECS에 배포하는 Hands On을 진행 하겠습니다. 0. 개요 <CI/CD 란?> CI/CD란, 지속적인 통합, 지속적인 배포라는 개념의 애플리케이션 개발

tech.cloud.nongshim.co.kr

Freestyle 설정은 간단히 사용해보았다. 이제 Jenkins를 통해 ECR에 이미지를 올리고, CodeDeploy를 사용하여 ECS Cluster에 ECS Service를 배포하는 과정을 진행 해보자. 생각하고 있는 구성은, CI를 통해 Spring Boot Application을 빌드하고, 해당 내용을 기반으로 Docker Image를 생성하여 ECR에 업로드를 수행하려고 한다. 후에, CD 파이프라인을 한벌 더 생성하여, 해당 파이프라인에서는 CodeDeploy와 aws cli를 통해 CD를 수행하는 작업을 진행 하고자 한다.

03-1. Jenkins Pipeline CI/CD 작업 흐름

  1. Github Master 브랜치에 신규 기능이 Push 된다
  2. Jenkins는 Github Credentials를 통해 소스 코드를 Clone 한다
  3. Jenkins는 해당 코드를 기반으로 Docker Image Build 후, 이미지를 ECR에 Push 한다
  4. Jenkins는 AWS CLI update-service 명령을 통해 새 배포를 수행한다
  5. ECS는 서비스 업데이트를 통해 Fargate로 배포 된다

03-2. Jenkins Pipeline 구성 순서

  1. Dockerfile(Spring Boot) 생성
  2. Github 소스 Push
  3. Jenkins 환경 변수 등록
  4. Jenkins Pipeline 설정

03-3. Dockerfile

# Build Stage
FROM gradle:8.14-jdk17 AS builder
WORKDIR /app
COPY --chown=gradle:gradle . .
RUN gradle build -x test

# Execute Stage
FROM openjdk:17-jdk-slim
ENV HOME_DIR=/apps/lib/
WORKDIR $HOME_DIR
COPY --from=builder /app/build/libs/*.jar search-jenkins-pipeline.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Dspring.profiles.active=local", "-jar", "search-jenkins-pipeline.jar"]

Spring Boot 애플리케이션을 위한 Dockerfile을 생성한다.

03-4. Github 소스 Push

 

GitHub - ym1085/springboot-jenkins-pipeline: Test Jenkins Pipeline CI/CD with Spring Boot

Test Jenkins Pipeline CI/CD with Spring Boot. Contribute to ym1085/springboot-jenkins-pipeline development by creating an account on GitHub.

github.com

Dockerfile을 생성하였으면, Jenkinsfile도 같이 생성해서 Github 리포지토리에 업로드 해준다. Jenkinsfile을 Jenkins Pipeline 사용 시, 해당 파이프라인이 Jenkinsfile을 기반으로 CI/CD 작업을 수행하기 위해 사용되는 파일이다. 우선, CD용 Jenkinsfile은 추후에 만들고, CI만 아래와 같이 패키지 구조를 잡아서 만들어준다.

project-root/
├── Dockerfile
├── ci/
│   └── Jenkinsfile.dev
└── src/

간단한 Jenkins용 프로젝트를 Github에 업로드 하였으니, 해당 Github 리포지토리에 Webhook 설정을 해주자. 만약, Github에 Push 이벤트가 발생하는 경우, Webhook 이벤트가 발생하여, 요청을 Jenkins에게 전달해주는 흐름이다.

03-4. Github Webhook 설정

프로젝트 리포지토리 > Settings > 이동하게 되면 이미지와 같이 Webhooks 옵션이 보인다. 해당 옵션을 클릭하면 Github Webhooks 관련 설정을 할 수 있고, Webhooks 화면에 접근하였으면, 아래와 같이 옵션을 셋팅해주면 된다.

  • Payload URL : http://:/github-webhook/
  • Contents Type : 요청 Content Type 지정 (application/json)
  • Secret : Github이 Webhook 요청 시, X-Hub-Signature-256 헤더 서명 추가 (공란)
  • SSL verification : Disable (현재 테스트이기에, 비활성화 시킨다)
  • Which events would you like to trigger this webhook?
    • 언제 Webhook Trigger 이벤트가 발생할지?
      • Just the push event : git push하는 경우만 이벤트 발생
      • Send me everything : 모든 이벤트 (git push, PR, star, fork..)
      • Let me select individual events : 사용자가 지정한 이벤트만 발생 시
  • Active : 활성 여부 (클릭)

Github Webhook IP 대역은 반드시 EC2 인바운드 그룹에 추가되어 있어야 한다.
아래 IP는 추가해주고, 안 열어주면 Github ↔ AWS 간 통신은 불가능하다.

"192.30.252.0/22",
"185.199.108.0/22",
"140.82.112.0/20",
"143.55.64.0/20"

위에서 말한 설정을 모두 하였으면, Update webhook 버튼을 클릭한다. 정상적으로 통신이 된다면 위 이미지처럼이 아닌, 초록색 체크박스가 보일 것이다. 만약, 빨간색 표시가 나오면 Github과 EC2 간의 통신이 불가능한 상태임을 의미하기에 추가적인 설정(보안그룹, ACL..등)을 확인해야 한다.

Webhooks 설정은 끝난 것 같다. 다음으로 Jenkins 빌드시 사용할 Github ID, Password Global Creential 변수를 생성해보자.

03-5. Jenkins Global 변수 설정

pipeline {
    // 파이프라인을 어떤 노드에서 실행할지 결정
    agent any

    tools {
        jdk "amazon-corretor-jdk17"
    }
    ....중략
}

tools { jdk "..." }는 Jenkins가 빌드할 때 쓸 JDK 버전을 직접 설치하고 경로까지 잡아주도록 지정하는 옵션이다. Jenkins 빌드 시 JDK 버전 관리의 주체를 Jenkins가 수행하도록 설정해야 하는 경우가 있는데, 이러한 경우 위 옵션을 사용하면 된다. 참고 바란다.

Jenkins 관리 > Credentials > (global) 클릭 > Add Credential를 클릭한다. 그러면 아래와 같은 화면이 출력된다.

후에, 아래와 같이 Credential 정보를 입력하고, Create 버튼을 클릭한다. 그러면 Credential 생성은 완료 된 것이다. 그러면 Credentail은 추가를 하였고, Jenkins Pipeline을 CI/CD로 구분하여 만들어보자.

  • Kind : Username with password
  • Scope : Global
  • Username : Github 계정 ID
  • Password : Github Token 정보
  • id : Credential을 식별하기 위한 Alias

03-6. Jenkins CI Pipeline 생성

이제 Spring Boot 애플리케이션을 Build하고, Builde 결과물을 통해 Docker image를 생성한 후에 ECR에 Push 하는 CI 파이프라인 스크립트를 작성한다. 우선 새로운 Item을 클릭한다.

새로운 Item을 클릭하면 위와 같은 화면을 볼 수 있다. 그러면 Item명과 Pipeline을 클릭하고 OK 버튼을 클릭한다.

OK 버튼을 클릭 후 각 옵션에 대해 위 이미지와 같이 설정을 해준다. Jenkins Pipeline 소스의 경우 아래 첨부 해두었으니, 필요에 따라서 커스텀 해서 사용하면 될 것 같다. 그러면 Save 버튼을 클릭한다.

pipeline {
    // 파이프라인을 어떤 노드에서 실행할지 결정
    agent any

    tools {
        jdk "amazon-corretor-jdk17"
    }

    // 환경 변수
    environment {
        PROFILE = "${PROFILE}" // 프로파일(prod, stg)
        AWS_REGION = "ap-northeast-2" // AWS Region 정보
        AWS_ACCOUNT_ID = "8xxxxxxxxxxxx" // AWS Acocunt 정보
        AWS_ECR_REPOSITORY = "search-jenkins-pipeline-${PROFILE}" // AWS ECR Repository 정보
        AWS_ECR_URL = "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${AWS_ECR_REPOSITORY}" // AWS ECR URL
        DOCKER_IMAGE_TAG = "${DOCKER_IMAGE_TAG}" // Docker Image Tag
        DOCKER_IMAGE = "${AWS_ECR_URL}:${DOCKER_IMAGE_TAG}" // Docker Image
    }

    // 파이프라인의 단계
    stages {
        // 디버깅
        stage("Debug") {
            steps {
                echo "================================================"
                echo "> Check Environment Variables"
                echo "AWS_REGION: ${AWS_REGION}"
                echo "AWS_ACCOUNT_ID: ${AWS_ACCOUNT_ID}"
                echo "AWS_ECR_REPOSITORY: ${AWS_ECR_REPOSITORY}"
                echo "AWS_ECR_URL: ${AWS_ECR_URL}"
                echo "DOCKER_IMAGE_TAG: ${DOCKER_IMAGE_TAG}"
                echo "DOCKER_IMAGE: ${DOCKER_IMAGE}"
                echo "PROFILE: ${PROFILE}"
                echo "================================================"
                echo "> Check Java Version"
                sh "java --version"
                echo "================================================"
                echo "> Check Docker Version"
                sh "docker --version"
                echo "================================================"
            }
        }

        // Git Clone 수행
        stage("Git Clone") {
            steps { 
                echo "================================================"
                echo "> Git Clone 수행"
                git credentialsId: "github-credentials-id", 
                branch: "master",
                url: "https://github.com/ym1085/springboot-jenkins-pipeline"
                echo "================================================"
            }
        }

        // Gradle Build 수행
        stage("Gradle Build") {
            steps {
                echo "================================================"
                echo "> Gradle 빌드 수행"
                sh "chmod +x ./gradlew"
                sh "./gradlew clean build -x test"
                echo "================================================"
            }
        }

        // Docker build 수행
        stage("Docker build") {
            steps {
                echo "================================================"
                echo "> Docker 빌드 수행"
                sh "docker build -t ${DOCKER_IMAGE} ."
                echo "================================================"
            }
        }

        // ECR Login
        stage("ECR Login") {
            steps { 
                echo "================================================"
                echo "> ECR Login"
                sh """
                aws ecr get-login-password --region ${AWS_REGION} | \
                docker login --username AWS --password-stdin ${AWS_ECR_URL}
                """
                echo "================================================"
            }
        }

        // ECR Push
        stage("ECR Push") {
            steps {
                echo "================================================"
                echo "> ECR Push"
                sh "docker push ${DOCKER_IMAGE}"
                echo "================================================"
            }
        }
    }

    post {
        success {
            echo "================================================"
            echo "✅ Success Build"
            echo "================================================"
        }
        failure {
            echo "================================================"
            echo "❌ Fail Build"
            echo "================================================"
        }
    }
}

위 스크립트를 보면 tools → jdk 부분이 보이는데, 이 부분은 Jenkins가 JDK를 설치하고 관리하도록 하는 옵션이다. 이전에 JDK를 Global로 설정하는 부분이 위에 있었으니 해당 부분을 참고하거나, 다른 분들의 블로그를 참고하는 것도 추천한다. 추가로, 이미지에는 나와 있지 않으나, DOCKER_IMAGE_TAG 매개변수도 Jenkins Pipeline 생성 시 추가해주자. 필자의 경우 1.0.0, 1.0.1 처럼 버전을 지정해서 Docker image 버전을 지정하고자 한다.

작업이 완료되면 ECR에 1.0.0 버전의 이미지가 업로드 되고, Build도 성공한 것을 확인할 수 있을 것이다. 그러면 ECR에 이미지를 올리는 작업까지 진행하였으니, 이번에는 ECS Service를 배포하는 CD 파이프라인을 구성 해보자.

03-7. Jenkins CD Pipeline 생성

이전 CI Pipeline을 만들때와 생성 흐름은 동일하다. 다만, 달라지는 부분이 있다고 하면 Pipeline Script와 Github를 따로 지정하지 않는다는 부분 정도가 존재할 것 같다. 2번째 Pipeline Script를 살펴보자. 이전에 파이라인을 생성하는 부분은 설명을 했기에 생략하고 스크립트 정도만 수정 해보자. 아래와 같이 스크립트 추가하고 빌드를 수행하면 된다.

pipeline {
    // 파이프라인을 어떤 노드에서 실행할지 결정
    agent any

    // 파이프라인 실행을 위한 매개변수 추가
    parameters {
        string(name: 'AWS_REGION', defaultValue: 'ap-northeast-2', description: 'AWS Region')
        string(name: 'AWS_ACCOUNT_ID', defaultValue: '842675972665', description: 'AWS Account ID')
        string(name: 'IMAGE_TAG', defaultValue: 'latest', description: 'ECR 이미지 태그')
        choice(name: 'PROFILE', choices: ['stg', 'prod'], description: '배포 환경')
    }

    tools {
        jdk "amazon-corretor-jdk17"
    }

    /*
        환경 변수 지정
        -> 1개의 Task에 N개의 Container가 구동되는 경우도 존재하기에, 고려해서 구성 해야 함
        -> 예: filebeat, api container 2개가 1개의 Task에 구동되는 경우
    */
    environment {
        AWS_REGION = "${params.AWS_REGION}"
        AWS_ACCOUNT_ID = "${params.AWS_ACCOUNT_ID}"
        ECS_CLUSTER_NAME = "search-ecs-cluster-${params.PROFILE}"
        ECS_SERVICE_NAME = "search-opensearch-ecs-service-${params.PROFILE}"
        ECS_TASK_FAMILY = "search-opensearch-api-td-${params.PROFILE}"
        ECS_DESIRED_COUNT = "1"
        OPENSEARCH_API_CONTAINER_NME = "search-opensearch-api-${params.PROFILE}"
        OPENSEARCH_API_ECR_REPO = "search-opensearch-api-${params.PROFILE}"
        OPENSEARCH_API_IMAGE_URI = "${params.AWS_ACCOUNT_ID}.dkr.ecr.${params.AWS_REGION}.amazonaws.com/${OPENSEARCH_API_ECR_REPO}:${params.IMAGE_TAG}"
    }

    stages {
        // Print Parameters
        stage("Debug") {
            steps {
                echo "================================================"
                echo "> Check Environment Variables"
                echo "AWS_REGION: ${params.AWS_REGION}"
                echo "AWS_ACCOUNT_ID: ${params.AWS_ACCOUNT_ID}"
                echo "PROFILE: ${params.PROFILE}"
                echo "ECS_CLUSTER_NAME: ${ECS_CLUSTER_NAME}"
                echo "ECS_SERVICE_NAME: ${ECS_SERVICE_NAME}"
                echo "ECS_TASK_FAMILY: ${ECS_TASK_FAMILY}"
                echo "OPENSEARCH_API_CONTAINER_NME: ${OPENSEARCH_API_CONTAINER_NME}"
                echo "OPENSEARCH_API_ECR_REPO: ${OPENSEARCH_API_ECR_REPO}"
                echo "OPENSEARCH_API_IMAGE_URI: ${OPENSEARCH_API_IMAGE_URI}"
                echo "================================================"
            }
        }

        // ECS Task Definition 신규 생성
        stage("Register ECS Task Definition") {
            steps {
                script {
                    // 기존 Task Definition 파일 조회
                    sh """
                    echo "기존 Task Definition 파일 조회"
                    aws ecs describe-task-definition \
                        --task-definition ${ECS_TASK_FAMILY} \
                        --query 'taskDefinition.containerDefinitions' \
                        --output json > task-definition.json
                    """

                    // 기존 Image 필드를 새로운 이미지 URI로 치환
                    sh """
                    echo "Task Definition 파일 수정"
                    sed -i 's|"image":.*|"image": "${OPENSEARCH_API_IMAGE_URI}",|' task-definition.json
                    """

                    // Task Definition 등록
                    sh """
                    echo "Task Definition 등록"
                    aws ecs register-task-definition \
                        --family ${ECS_TASK_FAMILY} \
                        --execution-role-arn arn:aws:iam::${params.AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole \
                        --network-mode awsvpc \
                        --requires-compatibilities FARGATE \
                        --cpu "512" \
                        --memory "1024" \
                        --container-definitions file://task-definition.json
                    """
                }
            }
        }

        // ECS Service 배포
        stage("Deploy ECS Service") {
            steps {
                script {
                    sleep time: 10, unit: 'SECONDS' // 10초 대기

                    def latestRevision = sh(
                        script: """
                        aws ecs describe-task-definition \
                        --task-definition ${ECS_TASK_FAMILY} \
                        --query 'taskDefinition.revision' \
                        --output text
                        """,
                        returnStdout: true // 출력 여부 지정
                    ).trim()

                    echo "최신 Task 리비전: ${latestRevision}"

                    // ECS 서비스 업데이트 (배포)
                    sh """
                    echo "ECS 서비스 업데이트"
                    aws ecs update-service \
                        --cluster ${ECS_CLUSTER_NAME} \
                        --service ${ECS_SERVICE_NAME} \
                        --desired-count ${ECS_DESIRED_COUNT} \
                        --task-definition ${ECS_TASK_FAMILY}:${latestRevision} \
                        --region ${params.AWS_REGION} \
                        --force-new-deployment
                    """
                }
            }
        }
    }
}

99. 참고 자료

 

AWS EC2 젠킨스 CI/CD를 구축하고 ECR 배포 파이프라인 생성하기

해당 게시글은 내용 정리 목적으로 작성되었습니다. 틀린 내용이 있다면 언제든지 말씀해 주세요이번 게시글에서는 AWS EC2에 젠킨스를 설치하는 방법에 대해서 소개해보겠다.1. Amazon EC2 생성1. Am

bamdule.tistory.com

 

Jenkins로 시작하는 CI: Freestyle 프로젝트 구축 가이드

이번 포스트에서는 Jenkins에서 Freestyle방식을 사용해서 Item을 구축하는 방법에 대해서 설명한다. 요즘은 Pipeline을 많이 사용하지만 Freestyle도 나름 간단하게 적용시켜서 사용하기에는 나쁘지 않은

curiousjinan.tistory.com

 

How to install Jenkins on AWS Ec2 instance

Setting Up Jenkins on AWS EC2: A Quick Installation Guide

medium.com

 

[Hands On] CI/CD – Jenkins pipeline을 이용한 ECS 배포 - NDS Cloud Tech Blog

이번 포스팅은 CI/CD의 기본적인 구조인, Jenkins pipeline을 이용해 ECS에 배포하는 Hands On을 진행 하겠습니다. 0. 개요 <CI/CD 란?> CI/CD란, 지속적인 통합, 지속적인 배포라는 개념의 애플리케이션 개발

tech.cloud.nongshim.co.kr

 

Jenkins on AWS

Jenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their software

www.jenkins.io