포트폴리오목록으로

GitLab Runner를 이용한 CI/CD로 SpringBoot EC2에 자동배포

· Updated

목표

GitLab의 CI/CD 기능을 이용해서 main branch에 push하게 되면 AWS EC2에 SpringBoot jar를 빌드 및 배포까지 완료

시작

GitLab CI/CD 코드 작성

  • 최대한 민감한 정보는 환경변수로 설정
  • gradle-8.7과 jdk-17 사용
  • CI/CD 단계: build->test->deploy
  • Ubuntu서버의 /home/ubuntu/app/build/ 경로에 배포

.gitlab-ci.yml

YAML

image: gradle:8.7-jdk17-alpine

stages: # List of stages for jobs, and their order of execution
    - build
    - test
    - deploy

cache:
    paths:
        - .gradle/wrapper
        - .gradle/caches

build:
    stage: build
    script:
        - git update-index --add --chmod=+x gradlew
        - ./gradlew clean build
    artifacts:
        paths:
            - build/libs/*.jar
        expire_in: 1 week
    only:
        - main

test:
    stage: test
    script: ./gradlew test

deploy-to-ec2:
    stage: deploy
    before_script:
        - "command -v ssh-agent >/dev/null || ( apk update && apk add openssh )"
        - eval $(ssh-agent -s)
        - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
        - mkdir -p ~/.ssh
        - chmod 700 ~/.ssh
        - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
        - chmod 644 ~/.ssh/known_hosts

    script:
        - ssh -o StrictHostKeyChecking=no ubuntu@"$DEPLOY_SERVER" 'rm -rf ~/app/build/*.jar'
        - ssh -o StrictHostKeyChecking=no ubuntu@"$DEPLOY_SERVER" 'mkdir -p ~/app/build'
        - scp build/libs/*.jar ubuntu@"$DEPLOY_SERVER":~/app/build/demo.jar
        - ssh -o StrictHostKeyChecking=no ubuntu@"$DEPLOY_SERVER" '~/deploy.sh'
    only:
        - main

GitLab Variables 등록

settings - CI/CD - variables

plain

DEPLOY_SERVER: EC2 public ip
SSH_PRIVATE_KEY: EC2 접속용 PEM

deploy.sh

  • shell script 파일에는 현재 실행중인 java 어플리케이션이 있다면 중지시키고(없다면 생략; 최초 배포시) 새로 업로드 된 애플리케이션을 실행하는 코드가 포함됨
  • 원격으로 해당 코드를 작성하면 불편하니 /home/ubuntu/deploy.sh 경로에 미리 작성
Shell

REPOSITORY=/home/ubuntu/app/build
cd $REPOSITORY/

echo "> 현재 구동중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -f server-0.0.1-SNAPSHOT.jar)

echo "> CURRENT_PID: $CURRENT_PID"

if [ -z $CURRENT_PID ]; then
        echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
        echo "> kill -15 $CURRENT_PID"
        kill -15 $CURRENT_PID
        sleep 5
fi

echo "> 새 애플리케이션 배포"

JAR_NAME=$(ls $REPOSITORY/ | grep 'demo' | tail -n 1)

echo "> JAR_NAME: $JAR_NAME"

nohup java -jar $REPOSITORY/$JAR_NAME/server-0.0.1-SNAPSHOT.jar &>/dev/null &
  • 마지막 &>/dev/null &은 GitLab console에서 log때문에 멈춰버리는 현상 방지
  • GitLab에서 실행할 수 있도록 권한 변경
Shell

sudo chmod 755 deploy.sh
  • main branch에 SpringBoot 소스 push
    • 이 때 gredlew의 권한이 permission denied가 뜰 수 있음 -> 권한 허용해줘야함
      • .git있는 디렉터리에서 bash shell로 명령어 입력
Bash

git update-index --chmod=+x gradlew

각 단계별 Jobs 로그

build

Shell

Running with gitlab-runner 16.11.0~pre.21.gaa21be2d (aa21be2d)
  on blue-1.saas-linux-small.runners-manager.gitlab.com/default j1aLDqxS, system ID: s_ccdc2f364be8
  feature flags: FF_USE_IMPROVED_URL_MASKING:true
Resolving secrets
00:00
Preparing the "docker+machine" executor
00:12
Using Docker executor with image gradle:8.7-jdk17-alpine ...
Pulling docker image gradle:8.7-jdk17-alpine ...
Using docker image sha256:e2fca0d966895442c272c3752a58e1e7b6adcf09a1a0f716ce1e1450682bdb42 for gradle:8.7-jdk17-alpine with digest gradle@sha256:9c74a0bc86971b60c9b14a8a5d69ffddc243da104cd5a2251af34dcc2748046d ...
Preparing environment
00:05
Running on runner-j1aldqxs-project-57389325-concurrent-0 via runner-j1aldqxs-s-l-s-amd64-1714477937-6e1efe28...
Getting source from Git repository
00:01
Fetching changes with git depth set to 20...
Initialized empty Git repository in /builds/team5racle/test2/.git/
Created fresh repository.
Checking out b353f7f6 as detached HEAD (ref is main)...
Skipping Git submodules setup
$ git remote set-url origin "${CI_REPOSITORY_URL}"
Restoring cache
00:01
Checking cache for default-protected...
Downloading cache from https://storage.googleapis.com/gitlab-com-runners-cache/project/57389325/default-protected 
Successfully extracted cache
Executing "step_script" stage of the job script
00:50
Using docker image sha256:e2fca0d966895442c272c3752a58e1e7b6adcf09a1a0f716ce1e1450682bdb42 for gradle:8.7-jdk17-alpine with digest gradle@sha256:9c74a0bc86971b60c9b14a8a5d69ffddc243da104cd5a2251af34dcc2748046d ...
$ git update-index --add --chmod=+x gradlew
$ ./gradlew clean build
Downloading https://services.gradle.org/distributions/gradle-8.7-bin.zip
............10%.............20%.............30%.............40%............50%.............60%.............70%.............80%.............90%............100%
Welcome to Gradle 8.7!
Here are the highlights of this release:
 - Compiling and testing with Java 22
 - Cacheable Groovy script compilation
 - New methods in lazy collection properties
For more details see https://docs.gradle.org/8.7/release-notes.html
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :clean UP-TO-DATE
> Task :compileJava
> Task :processResources
> Task :classes
> Task :resolveMainClassName
> Task :bootJar
> Task :jar
> Task :assemble
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
> Task :test
> Task :check
> Task :build
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.7/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD SUCCESSFUL in 49s
8 actionable tasks: 7 executed, 1 up-to-date
Saving cache for successful job
00:01
Creating cache default-protected...
WARNING: .gradle/wrapper: no matching files. Ensure that the artifact path is relative to the working directory (/builds/team5racle/test2) 
WARNING: .gradle/caches: no matching files. Ensure that the artifact path is relative to the working directory (/builds/team5racle/test2) 
Archive is up to date!                             
Created cache
Uploading artifacts for successful job
00:04
Uploading artifacts...
build/libs/*.jar: found 2 matching artifact files and directories 
WARNING: Upload request redirected                  location=https://gitlab.com/api/v4/jobs/6748578402/artifacts?artifact_format=zip&artifact_type=archive&expire_in=1+week new-url=https://gitlab.com
WARNING: Retrying...                                context=artifacts-uploader error=request redirected
Uploading artifacts as "archive" to coordinator... 201 Created  id=6748578402 responseStatus=201 Created token=glcbt-65
Cleaning up project directory and file based variables
00:01
Job succeeded

build

Shell

Running with gitlab-runner 16.11.0~pre.21.gaa21be2d (aa21be2d)
  on blue-2.saas-linux-small-amd64.runners-manager.gitlab.com/default XxUrkriX, system ID: s_f46a988edce4
  feature flags: FF_USE_IMPROVED_URL_MASKING:true
Resolving secrets
00:00
Preparing the "docker+machine" executor
00:13
Using Docker executor with image gradle:8.7-jdk17-alpine ...
Pulling docker image gradle:8.7-jdk17-alpine ...
Using docker image sha256:e2fca0d966895442c272c3752a58e1e7b6adcf09a1a0f716ce1e1450682bdb42 for gradle:8.7-jdk17-alpine with digest gradle@sha256:9c74a0bc86971b60c9b14a8a5d69ffddc243da104cd5a2251af34dcc2748046d ...
Preparing environment
00:04
Running on runner-xxurkrix-project-57389325-concurrent-0 via runner-xxurkrix-s-l-s-amd64-1714478091-dd2e7375...
Getting source from Git repository
00:01
Fetching changes with git depth set to 20...
Initialized empty Git repository in /builds/team5racle/test2/.git/
Created fresh repository.
Checking out b353f7f6 as detached HEAD (ref is main)...
Skipping Git submodules setup
$ git remote set-url origin "${CI_REPOSITORY_URL}"
Restoring cache
00:01
Checking cache for default-protected...
Downloading cache from https://storage.googleapis.com/gitlab-com-runners-cache/project/57389325/default-protected 
Successfully extracted cache
Downloading artifacts
00:01
Downloading artifacts for build (6748578402)...
Downloading artifacts from coordinator... ok        host=storage.googleapis.com id=6748578402 responseStatus=200 OK token=glcbt-65
Executing "step_script" stage of the job script
00:51
Using docker image sha256:e2fca0d966895442c272c3752a58e1e7b6adcf09a1a0f716ce1e1450682bdb42 for gradle:8.7-jdk17-alpine with digest gradle@sha256:9c74a0bc86971b60c9b14a8a5d69ffddc243da104cd5a2251af34dcc2748046d ...
$ ./gradlew test
Downloading https://services.gradle.org/distributions/gradle-8.7-bin.zip
............10%.............20%.............30%.............40%............50%.............60%.............70%.............80%.............90%............100%
Welcome to Gradle 8.7!
Here are the highlights of this release:
 - Compiling and testing with Java 22
 - Cacheable Groovy script compilation
 - New methods in lazy collection properties
For more details see https://docs.gradle.org/8.7/release-notes.html
Starting a Gradle Daemon (subsequent builds will be faster)
> Task :compileJava
> Task :processResources
> Task :classes
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
> Task :test
Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.
You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
For more on this, please refer to https://docs.gradle.org/8.7/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
BUILD SUCCESSFUL in 48s
4 actionable tasks: 4 executed
Saving cache for successful job
00:00
Creating cache default-protected...
WARNING: .gradle/wrapper: no matching files. Ensure that the artifact path is relative to the working directory (/builds/team5racle/test2) 
WARNING: .gradle/caches: no matching files. Ensure that the artifact path is relative to the working directory (/builds/team5racle/test2) 
Archive is up to date!                             
Created cache
Cleaning up project dir

deploy

Bash

Running with gitlab-runner 16.11.0~pre.21.gaa21be2d (aa21be2d)
  on blue-5.saas-linux-small-amd64.runners-manager.gitlab.com/default -AzERasQ, system ID: s_4cb09cee29e2
  feature flags: FF_USE_IMPROVED_URL_MASKING:true
Resolving secrets
00:00
Preparing the "docker+machine" executor
00:12
Using Docker executor with image gradle:8.7-jdk17-alpine ...
Pulling docker image gradle:8.7-jdk17-alpine ...
Using docker image sha256:e2fca0d966895442c272c3752a58e1e7b6adcf09a1a0f716ce1e1450682bdb42 for gradle:8.7-jdk17-alpine with digest gradle@sha256:9c74a0bc86971b60c9b14a8a5d69ffddc243da104cd5a2251af34dcc2748046d ...
Preparing environment
00:05
Running on runner--azerasq-project-57389325-concurrent-0 via runner-azerasq-s-l-s-amd64-1714478166-87170077...
Getting source from Git repository
00:01
Fetching changes with git depth set to 20...
Initialized empty Git repository in /builds/team5racle/test2/.git/
Created fresh repository.
Checking out b353f7f6 as detached HEAD (ref is main)...
Skipping Git submodules setup
$ git remote set-url origin "${CI_REPOSITORY_URL}"
Restoring cache
00:00
Checking cache for default-protected...
Downloading cache from https://storage.googleapis.com/gitlab-com-runners-cache/project/57389325/default-protected 
Successfully extracted cache
Downloading artifacts
00:02
Downloading artifacts for build (6748578402)...
Downloading artifacts from coordinator... ok        host=storage.googleapis.com id=6748578402 responseStatus=200 OK token=glcbt-65
Executing "step_script" stage of the job script
00:28
Using docker image sha256:e2fca0d966895442c272c3752a58e1e7b6adcf09a1a0f716ce1e1450682bdb42 for gradle:8.7-jdk17-alpine with digest gradle@sha256:9c74a0bc86971b60c9b14a8a5d69ffddc243da104cd5a2251af34dcc2748046d ...
$ command -v ssh-agent >/dev/null || ( apk update && apk add openssh )
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.19/community/x86_64/APKINDEX.tar.gz
v3.19.1-476-g0fc5112363c [https://dl-cdn.alpinelinux.org/alpine/v3.19/main]
v3.19.1-478-g9c03b9c8a03 [https://dl-cdn.alpinelinux.org/alpine/v3.19/community]
OK: 22995 distinct packages available
(1/8) Installing openssh-keygen (9.6_p1-r0)
(2/8) Installing libedit (20230828.3.1-r3)
(3/8) Installing openssh-client-common (9.6_p1-r0)
(4/8) Installing openssh-client-default (9.6_p1-r0)
(5/8) Installing openssh-sftp-server (9.6_p1-r0)
(6/8) Installing openssh-server-common (9.6_p1-r0)
(7/8) Installing openssh-server (9.6_p1-r0)
(8/8) Installing openssh (9.6_p1-r0)
Executing busybox-1.36.1-r15.trigger
OK: 137 MiB in 77 packages
$ eval $(ssh-agent -s)
Agent pid 19
$ echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
Identity added: (stdin) ((stdin))
$ mkdir -p ~/.ssh
$ chmod 700 ~/.ssh
$ echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
$ chmod 644 ~/.ssh/known_hosts
$ ssh -o StrictHostKeyChecking=no ubuntu@"$DEPLOY_SERVER" 'rm -rf ~/app/build/*.jar'
Warning: Permanently added '43.203.253.137' (ED25519) to the list of known hosts.
$ ssh -o StrictHostKeyChecking=no ubuntu@"$DEPLOY_SERVER" 'mkdir -p ~/app/build'
$ scp build/libs/*.jar ubuntu@"$DEPLOY_SERVER":~/app/build/demo.jar
$ ssh -o StrictHostKeyChecking=no ubuntu@"$DEPLOY_SERVER" '~/deploy.sh'
> 현재 구동중인 애플리케이션 pid 확인
8037
> kill -15 8037
> 새 애플리케이션 배포
> JAR_NAME: demo.jar
Saving cache for successful job
00:01
Creating cache default-protected...
WARNING: .gradle/wrapper: no matching files. Ensure that the artifact path is relative to the working directory (/builds/team5racle/test2) 
WARNING: .gradle/caches: no matching files. Ensure that the artifact path is relative to the working directory (/builds/team5racle/test2) 
Archive is up to date!                             
Created cache
Cleaning up project directory an
privacy.md

개인정보 처리방침

이 사이트는 김상훈의 포트폴리오와 기술 블로그입니다. 회원가입, 댓글, 결제 기능은 제공하지 않습니다.

방문 분석

공식 운영 도메인에서 Google Analytics 4, Vercel Web Analytics와 Speed Insights를 사용해 페이지 방문, 유입 경로와 성능을 확인합니다. 개발·미리보기 환경에는 분석 도구를 활성화하지 않습니다.

Google Analytics는 브라우저 식별을 위해 쿠키를 사용할 수 있으며 페이지 URL, 브라우저·기기 정보와 상호작용을 처리합니다. URL과 입력 내용에 이름, 이메일 등 개인정보를 포함하지 마세요. 이 사이트는 이메일이나 연락처를 분석 이벤트로 직접 전송하지 않습니다.

Vercel 분석 도구는 방문 통계와 로딩·응답 성능을 처리합니다. Google과 Vercel의 실제 보관 기간 및 처리는 각 서비스 설정과 정책에 따릅니다.

외부 콘텐츠와 브라우저 저장

블로그에 포함된 외부 이미지는 해당 이미지 서버에서 불러오며 해당 서버에 접속 정보가 전달될 수 있습니다. 글꼴은 이 사이트 서버에서 제공합니다. 테마 선택은 브라우저 로컬 저장소에 저장됩니다.

문의

이메일로 보낸 내용은 문의에 답변하는 데 사용됩니다. 문의: rlatkdgns042@naver.com

· © 2026 Sanghun Kim