개발 환경
⚙️ [ Java17, Gradle, Spring 3.3.1 ], [ Github, GithubActions ], [ AWS, EC2 - ubuntu, S3, CodeDeploy ]
CI/CD?
CI/CD는 지속적 통합(Continuous Integration)
과 지속적 배포 (Continuous Deployment)
의 약자입니다.
CI
는 지속적으로 코드를 통합하고 자동화된 테스트를 거치는 것을 의미합니다. 이로 인해서 버그가 줄어들고, 코드의 변경사항이 생길 때 마다 수동으로 테스트하지 오류가 줄어듭니다. 또한 개발자들 끼리 서로 코드가 충돌(conflict)날 수 있는데, 이를 잡아줄 수 있습니다.
CD
는 CI된 코드를 자동적으로 배포하는 것을 의미합니다. 배포 과정을 자동화하여 사람이 낼 수 있는 실수를 없앨 수 있고, 자주 배포하여 사용자에게 빠르게 제공합니다.
CI/CD!
CI/CD를 도와주는 툴로는 GithubActions, Jenkins, Travis CI, CircleCI
등이 있습니다. 저는 이번에 GithubActions
를 이용해서 CI/CD를 구축할 것인데, 보다 큰 규모의 프로젝트에서는 Jenkins
를 주로 씁니다.
CI/CD를 쓰는 이유로는 여러가지가 있습니다. 주로 비용 감소, 개발 품질 향상
2가지를 뽑을 수 있습니다.
먼저,비용 감소
는 통합과 배포에 쓰는 비용을 줄여서 개발자가 온전히 개발에만 시간을 투자할 수 있게 해줍니다. 개발 품질 향상
은 수정 사항이나 버그픽스에 대해서 바로바로 반영될 수 있게 해줍니다.
GithubActions - 아키텍처 및 구성 요소
GithubActions를 이용한 CI/CD 과정은 다음과 같습니다. GithubActions에서 S3에 업로드하면 CodeDeploy가 EC2에 S3에 있는 파일을 다운로드하고 스크립트를 EC2에서 실행합니다. 스크립트는 저희가 수동으로 해주는 과정을 미리 적어놓고 자동으로 실행되게 해준다고 보시면 됩니다.

GithubActions의 구성 요소는 대표적으로 Workflow, Event, Jobs, Secrets
등이 있습니다. 아래 설명을 보시고 ci.yml 코드를 보시면 보다 쉽게 이해하실 수 있습니다.
Workflow
- 정의: 작업의 자동화된 프로세스를 정의한 YAML 파일입니다. .github/workflows 디렉토리에 저장됩니다.
- 구조: 워크플로우는 이벤트, 작업(job), 스텝(step)으로 구성됩니다.
Event
- 정의: 워크플로우가 실행되는 트리거입니다.
- 예시:
push: 브랜치에 코드가 푸시될 때
pull_request: 풀 리퀘스트가 열리거나 업데이트될 때
Jobs
- 정의: 워크플로우 내에서 병렬 또는 순차적으로 실행되는 단위입니다.
- 특징: 각 작업은 독립적인 가상 환경에서 실행됩니다.
- 구성 요소: runs-on: 작업이 실행될 환경을 지정 (예: ubuntu-latest, windows-latest)
Secrets
- 정의: 민감한 정보를 안전하게 저장하고 워크플로우에서 사용할 수 있도록 합니다.
- 예시: API 키, 토큰 등
GithubActions - CI/CD 구축
우선 actions에 가면, 아래와 같이 나오는데, 자신의 설정에 맞는 ci.yml
파일을 찾을 수 있습니다. 저는 여기서 배포할 branch
설정과 dependency-submission
을 지우는 것 정도만 수정했습니다. configure
를 누르시면, repository/.github/workflows/
위치에 ci.yml 파일을 자동생성해줍니다.

제가 사용한 ci.yml
파일입니다.
name: Java CI with Gradle
on:
# main이라는 브렌치에 push라는 이벤트가 발생했을 때, CI가 진행되게 해줍니다.
push:
branches:
- "main"
# - "develop"
# pr을 보냈을 때, 테스트를 하기 위해 추가적으로 작성함. 지우셔도 됩니다.
pull_request:
branches:
- "main"
# - "develop"
# jobs에는 CI가 어떻게 진행될지 적습니다.
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 저희 프로젝트가 Backend라는 repo안에 project가 존재하기 때문에, 한 단계 들어가야해서 이런식으로 적었습니다.
# run: chmod +x projectname/gradlew
- name: Build with Gradle Wrapper
run: ./gradlew clean build --no-daemon -x test
# run: cd projectname && ./gradlew clean build --no-daemon -x test
ci.yml 파일을 actions에서 만드셨다면, API 키, 토큰 등 중요한 정보를 Setting
에 들어가셔서 아래 Secrets and variables
를 클릭합니다. 그러면 토글에 Actions
가 나오는데, 클릭해줍니다.

그러면 아래와 같이 나오는데, New repository secrets
를 누르셔서 API 키, 토큰 등 노출되면 안되는 정보들을 넣어줍니다. cd.yml
에 applicatios.properties
를 생성하실 수 있으니 properties
파일을 통째로 넣으셔도 됩니다.(이 때, github에 properties 파일은 삭제해주시고 올려주시면 됩니다.)

위에서 보았던 CI/CD 과정에서 이제 cd.yml
은 S3에 업로드 및 CodeDeploy가 실행되게 해줘야 합니다. 쉽게 말해서 AWS에서 수행될 내용을 적어준다고 생각하면 됩니다. 제가 사용한 cd.yml
은 아래와 같습니다. secrets.
은 actions에서 설정한 내용이 불러와 지는 것이고, env.
은 cd 파일에 환경설정한 부분들이 불러와지는 것입니다.
name: Deploy to Amazon EC2
on:
push:
branches:
- "main"
# - "develop"
# 본인의 aws 설정을 넣어주시면 됩니다. 직접 넣으셔도, actions에 넣으셔도 상관없습니다.
env:
AWS_REGION: ${secrets.region}
S3_BUCKET_NAME: ${secrets.bucket_name}
CODE_DEPLOY_APPLICATION_NAME: ${secrets.code_deploy_application_name}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${secrets.code_deploy_application_group_name}
permissions:
contents: read
# AWS에서 작동할 내용들입니다.
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 앞서 말한 application.properties를 생성하는 부분입니다.
- name: Create application.properties
run: echo "${{ secrets.APPLICATION_PROPERTIES }}" > [ root ]/src/main/resources/application.properties
- name: Build with gradle
run: ./gradlew clean build --no-daemon -x test
- name: Create zip file
run: zip -r ${{ github.sha }}.zip [ root ]/build/libs/
- 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: 'your region'
- name: Upload to AWS S3
run: |
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--ignore-hidden-files \
--s3-location s3://${{env.S3_BUCKET_NAME}}/${{ github.sha }}.zip \
--source .
# 배포 그룹에서 배포를 시작하는데, 여기서 스크립트 파일을 읽어 EC2를 실행하게 됩니다.
- name: Deploy to AWS EC2 from S3
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=${{ env.S3_BUCKET_NAME }},key=${{ github.sha }}.zip,bundleType=zip
CodeDeploy
이제 CodeDeploy 에서 사용할 스크립트와 appspec.yml
에 대해서 알아봅시다. 현재는 비용문제로 배포한 사이트를 모두 내려놔서 없지만, 아래 파일을 보면 file
을 설치할 경로와 권한 마지막으로 hooks
가 있는데, hooks
는 오른쪽 순서대로 진행됩니다. 자세한 설명은 다른 블로그 혹은 공식 사이트에서 봐주시면 좋겠습니다. 우선 ApplicationStart
를 통해서 location
에 위치한 스크립트 파일을 ec2에서 실행해준다고 생각하시면 됩니다.

version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app/
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
다음으로는 실행한 스크립트 파일입니다. 현재 배포된 서버를 다운시키고, 새롭게 배포하는 방식입니다. project에 본인의 프로젝트 이름을 넣어주시고, 저는 java를 사용하여 jar파일을 이용해서 배포했습니다.
PROJECT_ROOT="/home/ubuntu/app/project"
JAR_FILE="$PROJECT_ROOT/project-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
TIME_NOW=$(date +%c)
# 현재 실행 중인 애플리케이션 찾기
CURRENT_PID=$(pgrep -f $JAR_FILE)
# 실행 중인 애플리케이션이 있다면 종료
if [ -z "$CURRENT_PID" ]; then
echo "$TIME_NOW : No application running" >> $DEPLOY_LOG
else
echo "$TIME_NOW : Kill process $CURRENT_PID" >> $DEPLOY_LOG
kill -9 $CURRENT_PID
fi
# 애플리케이션 복제
echo "$TIME_NOW : copy $JAR_FILE" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $PROJECT_ROOT
# 애플리케이션 권한부여 및 백그라운드 실행
echo "$TIME_NOW : run $JAR_FILE" >> $DEPLOY_LOG
chmod +x $JAR_FILE
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &
# 새로운 프로세스 아이디를 로그에 기록
NEW_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW : New process id is $NEW_PID" >> $DEPLOY_LOG
마지막으로 실제로 서버가 실행됐는지 확인해주시면 됩니다. 만약, 제대로 실행되지 않았다면, error_log에 들어가셔서 봐주시면 됩니다. 한 번에 쓴 글이 아니라 중간에 빠진 내용도 있을 수도 있을 것 같은데, 혹시 제대로 안되신다면 댓글에 남겨주시면 감사하겠습니다.
'개발자 공부 > 인프라 - AWS' 카테고리의 다른 글
[ AWS ] EC2 인스턴스에 HTTPS 적용하기 (feat. Route53, ALB, 프로젝트) (0) | 2024.08.07 |
---|
개발 환경
⚙️ [ Java17, Gradle, Spring 3.3.1 ], [ Github, GithubActions ], [ AWS, EC2 - ubuntu, S3, CodeDeploy ]
CI/CD?
CI/CD는 지속적 통합(Continuous Integration)
과 지속적 배포 (Continuous Deployment)
의 약자입니다.
CI
는 지속적으로 코드를 통합하고 자동화된 테스트를 거치는 것을 의미합니다. 이로 인해서 버그가 줄어들고, 코드의 변경사항이 생길 때 마다 수동으로 테스트하지 오류가 줄어듭니다. 또한 개발자들 끼리 서로 코드가 충돌(conflict)날 수 있는데, 이를 잡아줄 수 있습니다.
CD
는 CI된 코드를 자동적으로 배포하는 것을 의미합니다. 배포 과정을 자동화하여 사람이 낼 수 있는 실수를 없앨 수 있고, 자주 배포하여 사용자에게 빠르게 제공합니다.
CI/CD!
CI/CD를 도와주는 툴로는 GithubActions, Jenkins, Travis CI, CircleCI
등이 있습니다. 저는 이번에 GithubActions
를 이용해서 CI/CD를 구축할 것인데, 보다 큰 규모의 프로젝트에서는 Jenkins
를 주로 씁니다.
CI/CD를 쓰는 이유로는 여러가지가 있습니다. 주로 비용 감소, 개발 품질 향상
2가지를 뽑을 수 있습니다.
먼저,비용 감소
는 통합과 배포에 쓰는 비용을 줄여서 개발자가 온전히 개발에만 시간을 투자할 수 있게 해줍니다. 개발 품질 향상
은 수정 사항이나 버그픽스에 대해서 바로바로 반영될 수 있게 해줍니다.
GithubActions - 아키텍처 및 구성 요소
GithubActions를 이용한 CI/CD 과정은 다음과 같습니다. GithubActions에서 S3에 업로드하면 CodeDeploy가 EC2에 S3에 있는 파일을 다운로드하고 스크립트를 EC2에서 실행합니다. 스크립트는 저희가 수동으로 해주는 과정을 미리 적어놓고 자동으로 실행되게 해준다고 보시면 됩니다.

GithubActions의 구성 요소는 대표적으로 Workflow, Event, Jobs, Secrets
등이 있습니다. 아래 설명을 보시고 ci.yml 코드를 보시면 보다 쉽게 이해하실 수 있습니다.
Workflow
- 정의: 작업의 자동화된 프로세스를 정의한 YAML 파일입니다. .github/workflows 디렉토리에 저장됩니다.
- 구조: 워크플로우는 이벤트, 작업(job), 스텝(step)으로 구성됩니다.
Event
- 정의: 워크플로우가 실행되는 트리거입니다.
- 예시:
push: 브랜치에 코드가 푸시될 때
pull_request: 풀 리퀘스트가 열리거나 업데이트될 때
Jobs
- 정의: 워크플로우 내에서 병렬 또는 순차적으로 실행되는 단위입니다.
- 특징: 각 작업은 독립적인 가상 환경에서 실행됩니다.
- 구성 요소: runs-on: 작업이 실행될 환경을 지정 (예: ubuntu-latest, windows-latest)
Secrets
- 정의: 민감한 정보를 안전하게 저장하고 워크플로우에서 사용할 수 있도록 합니다.
- 예시: API 키, 토큰 등
GithubActions - CI/CD 구축
우선 actions에 가면, 아래와 같이 나오는데, 자신의 설정에 맞는 ci.yml
파일을 찾을 수 있습니다. 저는 여기서 배포할 branch
설정과 dependency-submission
을 지우는 것 정도만 수정했습니다. configure
를 누르시면, repository/.github/workflows/
위치에 ci.yml 파일을 자동생성해줍니다.

제가 사용한 ci.yml
파일입니다.
name: Java CI with Gradle
on:
# main이라는 브렌치에 push라는 이벤트가 발생했을 때, CI가 진행되게 해줍니다.
push:
branches:
- "main"
# - "develop"
# pr을 보냈을 때, 테스트를 하기 위해 추가적으로 작성함. 지우셔도 됩니다.
pull_request:
branches:
- "main"
# - "develop"
# jobs에는 CI가 어떻게 진행될지 적습니다.
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 저희 프로젝트가 Backend라는 repo안에 project가 존재하기 때문에, 한 단계 들어가야해서 이런식으로 적었습니다.
# run: chmod +x projectname/gradlew
- name: Build with Gradle Wrapper
run: ./gradlew clean build --no-daemon -x test
# run: cd projectname && ./gradlew clean build --no-daemon -x test
ci.yml 파일을 actions에서 만드셨다면, API 키, 토큰 등 중요한 정보를 Setting
에 들어가셔서 아래 Secrets and variables
를 클릭합니다. 그러면 토글에 Actions
가 나오는데, 클릭해줍니다.

그러면 아래와 같이 나오는데, New repository secrets
를 누르셔서 API 키, 토큰 등 노출되면 안되는 정보들을 넣어줍니다. cd.yml
에 applicatios.properties
를 생성하실 수 있으니 properties
파일을 통째로 넣으셔도 됩니다.(이 때, github에 properties 파일은 삭제해주시고 올려주시면 됩니다.)

위에서 보았던 CI/CD 과정에서 이제 cd.yml
은 S3에 업로드 및 CodeDeploy가 실행되게 해줘야 합니다. 쉽게 말해서 AWS에서 수행될 내용을 적어준다고 생각하면 됩니다. 제가 사용한 cd.yml
은 아래와 같습니다. secrets.
은 actions에서 설정한 내용이 불러와 지는 것이고, env.
은 cd 파일에 환경설정한 부분들이 불러와지는 것입니다.
name: Deploy to Amazon EC2
on:
push:
branches:
- "main"
# - "develop"
# 본인의 aws 설정을 넣어주시면 됩니다. 직접 넣으셔도, actions에 넣으셔도 상관없습니다.
env:
AWS_REGION: ${secrets.region}
S3_BUCKET_NAME: ${secrets.bucket_name}
CODE_DEPLOY_APPLICATION_NAME: ${secrets.code_deploy_application_name}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${secrets.code_deploy_application_group_name}
permissions:
contents: read
# AWS에서 작동할 내용들입니다.
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 앞서 말한 application.properties를 생성하는 부분입니다.
- name: Create application.properties
run: echo "${{ secrets.APPLICATION_PROPERTIES }}" > [ root ]/src/main/resources/application.properties
- name: Build with gradle
run: ./gradlew clean build --no-daemon -x test
- name: Create zip file
run: zip -r ${{ github.sha }}.zip [ root ]/build/libs/
- 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: 'your region'
- name: Upload to AWS S3
run: |
aws deploy push \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--ignore-hidden-files \
--s3-location s3://${{env.S3_BUCKET_NAME}}/${{ github.sha }}.zip \
--source .
# 배포 그룹에서 배포를 시작하는데, 여기서 스크립트 파일을 읽어 EC2를 실행하게 됩니다.
- name: Deploy to AWS EC2 from S3
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=${{ env.S3_BUCKET_NAME }},key=${{ github.sha }}.zip,bundleType=zip
CodeDeploy
이제 CodeDeploy 에서 사용할 스크립트와 appspec.yml
에 대해서 알아봅시다. 현재는 비용문제로 배포한 사이트를 모두 내려놔서 없지만, 아래 파일을 보면 file
을 설치할 경로와 권한 마지막으로 hooks
가 있는데, hooks
는 오른쪽 순서대로 진행됩니다. 자세한 설명은 다른 블로그 혹은 공식 사이트에서 봐주시면 좋겠습니다. 우선 ApplicationStart
를 통해서 location
에 위치한 스크립트 파일을 ec2에서 실행해준다고 생각하시면 됩니다.

version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app/
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
ApplicationStart:
- location: scripts/deploy.sh
timeout: 60
runas: ubuntu
다음으로는 실행한 스크립트 파일입니다. 현재 배포된 서버를 다운시키고, 새롭게 배포하는 방식입니다. project에 본인의 프로젝트 이름을 넣어주시고, 저는 java를 사용하여 jar파일을 이용해서 배포했습니다.
PROJECT_ROOT="/home/ubuntu/app/project"
JAR_FILE="$PROJECT_ROOT/project-0.0.1-SNAPSHOT.jar"
APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"
TIME_NOW=$(date +%c)
# 현재 실행 중인 애플리케이션 찾기
CURRENT_PID=$(pgrep -f $JAR_FILE)
# 실행 중인 애플리케이션이 있다면 종료
if [ -z "$CURRENT_PID" ]; then
echo "$TIME_NOW : No application running" >> $DEPLOY_LOG
else
echo "$TIME_NOW : Kill process $CURRENT_PID" >> $DEPLOY_LOG
kill -9 $CURRENT_PID
fi
# 애플리케이션 복제
echo "$TIME_NOW : copy $JAR_FILE" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $PROJECT_ROOT
# 애플리케이션 권한부여 및 백그라운드 실행
echo "$TIME_NOW : run $JAR_FILE" >> $DEPLOY_LOG
chmod +x $JAR_FILE
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &
# 새로운 프로세스 아이디를 로그에 기록
NEW_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW : New process id is $NEW_PID" >> $DEPLOY_LOG
마지막으로 실제로 서버가 실행됐는지 확인해주시면 됩니다. 만약, 제대로 실행되지 않았다면, error_log에 들어가셔서 봐주시면 됩니다. 한 번에 쓴 글이 아니라 중간에 빠진 내용도 있을 수도 있을 것 같은데, 혹시 제대로 안되신다면 댓글에 남겨주시면 감사하겠습니다.
'개발자 공부 > 인프라 - AWS' 카테고리의 다른 글
[ AWS ] EC2 인스턴스에 HTTPS 적용하기 (feat. Route53, ALB, 프로젝트) (0) | 2024.08.07 |
---|