[CI/CD] AWS EC2 자동화 배포 파이프라인 구축하기
실무환경에선 소프트웨어 개발(Development)과 ""운영(Operation)""이 결합되어있다.쉽게말해, 효율적이고 "지속 가능한" 소프트웨어 제공을 목표로 한다.(기업이 시장에서 생존하기 위해선,실적을 높
ceo-uk22.tistory.com
여는글
프로젝트 초기에는 빠른 개발과 배포에 집중하기 위해, CI/CD 파이프라인을 AWS CodeDeploy 기반으로 구성했었다.
빌드된 애플리케이션을 Docker 이미지로 변환하고,
ECR 업로드 후 S3에 저장된 배포 스크립트를 토대로 EC2에 배포하는 구조였다.
아래는 deploy.yml 스크립트 동작 요약이다.
1. Spring Boot 애플리케이션 빌드
2. Docker 이미지 변환
3. ECR 업로드
4. S3 + CodeDeploy 기반 EC2 배포 수행
이 구조는 간결하고 구현이 쉬워, 빠르게 개발에 몰입하기에 적합했다.
하지만 서비스 구조가 확장되면서, 인프라 보안과 운영 안정성 측면에서 한계가 명확히 드러났다.
문제 인식: 모든 리소스가 퍼블릭에 노출된 구조
당시 구성된 인프라는 EC2, RDS, S3 모두 VPC 퍼블릭 서브넷에 위치해 있었다.
개발 초기엔 접근 편의성이 우선이었지만, 결과적으로 보안상 치명적인 구조가 되어버렸다.
이후, 기능 개발보다 인프라 리스크 제거가 더 시급한 작업이라는 판단 하에
VPC 마이그레이션 및 CI/CD 구조 개선 작업을 우선적으로 진행하게 되었다.
Trade-off: 인프라 전략 결정
당장 눈앞의 API 기능개발보다, 인프라 구조를 바꾸는 게 장기적으로는 훨씬 중요한 선택이라고 판단했다.
결정한 방향은 이렇다:
- 백엔드 EC2, RDS, S3 포함한 모든 리소스를 VPC Private Subnet으로 이전
- 기존 배포 방식에 따라 CI/CD 구조도 함께 리디자인
- 비용은 ""최소비용 조건""으로 보수적으로 접근하되, 확장 가능성은 열어둔 설계
특히, 배포 파이프라인은 아키텍처에 따라 비용구성이 달라졌다.
따라서 각 서비스 별, 추가비용 발생여부를 "표 형식"으로 기록한 뒤 초기 전략 상 유지비용이 가장 적게드는 것을
CICD 아키텍처로 채택했다.
VPC EndPoint 수 | 추가 비용 (월 단위) | |
NAT Gateway 기반 CICD | $ 35 | |
SSM 기반 CICD | 5개 | $ 35 |
CodeDeply 기반 CICD | 5개 | $ 35 |
self-hosted runners 기반 CICD | 3개 | $ 21 |
최종 선택: Self-hosted Runner 기반 CI/CD
보안, 비용, 유지보수 관점에서 가장 현실적인 구조는 Github Actions Self-Hosted Runner 기반의 배포 파이프라인이었다.
- EC2 내부에 Runner를 직접 설치해, 외부 인터넷 없이도 빌드/배포 가능
- Docker 이미지 빌드 및 배포까지 자체 인스턴스 내에서 처리
- VPC Endpoint 구성 최소화로 비용 절감
이어서, Github Actions Self-Hosted-Runner 기반 CI/CD 배포 파이프라인을 어떻게 구성하였는지
그 구현 방법을 상세히 설명한다.
1. Private EC2 -> Github 접근 가능 네트워크 설정
Private EC2는 외부 인터넷 연결이 안되기에, Github와 통신을 하려면 Bastion Host (점프 서버)를 이용해야한다.
Bastion Host EC2는 퍼블릭으로 공개된 리소스이며, 이를 활용해서 Private EC2에서 Github로 접근가능하게 설정할 수 있다.
방법은 아래와 같다.
1-1. Bastion Host -> squid (프록시 서버) 설치 및 설정
1. squid 설치 (프록시 서버)
sudo yum install -y squid
2. squid.conf 수정
acl myvpc src {VPC CIDR}
http_access allow myvpc
http_access deny all
http_port 3128
수정 후, squid 서버 재부팅
sudo systemctl restart squid
3. Bastion Host 보안그룹 설정
AWS 콘솔에서 아래와 같은 보안그룹을 추가로 허용한다.
포트 | 소스 | |
Bastion Host - 인바운드 규칙 | 3128 | 대상 인스턴스가 소속된, VPC CIDR |
4. Bastion Host 방화벽 끄기
sudo systemctl stop firewalld
sudo systemctl disable firewalld
방화벽 꺼져 있는지 확인
sudo systemctl status firewalld
출력결과가, active (running) 아니면 된다.
1-2. Private EC2 -> Bastion Host EC2 프록시 서버 연결
1. 시스템 전역 변수 -> 프록시 서버변수 등록
sudo nano /etc/environment
그리고, 파일에 아래 내용 삽입
http_proxy=http://{Bastion-Host Private_IP}:3128
https_proxy=http://{Bastion-Host Private_IP}:3128
no_proxy=localhost,127.0.0.1,169.254.169.254
no_proxy 설정 -> 로컬 통신, AWS 메타데이터 서비스 주소는 프록시를 타지 않도록 설정한다.
(인스턴스가 자신의 IAM Role 정보, 사용자 데이터 등을 가져올 때 사용)
Private EC2 -> Github 통신 테스트
curl -I https://github.com
여기서, 응답이 오면 성공이다
이제, Private EC2 -> Github로 통신 가능하다.
2. Private EC2 -> self-hosted runner 구축
Github self-hosted runner는 워크플로우를 직접관리하는 서버(EC2)에서 실행하도록 도와주는
실행기이다.
즉, Private EC2에 self-hosted runner 설치하고 Github Action에서 Job 요청이 감지되면 지정된 워크플로우를
대상 EC2 (Private EC2)에서 수행한다. 설정 방법은 아래와 같다.
2-1. Private EC2 -> self-hosted runner 설치
1. Github runner 설치 스크립트 조회 -> new self-hosted runner 생성
워크플로우를 설정 대상 Github 레포 -> setting -> Runners -> new self-hosted runner 생성
(배포 대상 인스턴스 OS 확인 후 진행)
2. Private EC2 -> Runner 설치 스크립트 실행
# Runner 설치
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.322.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.322.0/actions-runner-linux-x64-2.322.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.322.0.tar.gz
# Runner Config 설정 (토큰)
./config.sh --url https://github.com/{서비스_레포_주소} --token {할당된 토큰 값}
2-2.백그라운드 실행 매니저 systemd -> Runner 프로세스 등록
1. systemd -> Runner 등록
sudo ./svc.sh install
2. systemd -> 프록시 환경변수 설정
Runner가 동작하는 환경이, Private EC2 환경이므로,
systemd 가 프록시 서버를 거쳐 Github와 통신할 수 있게 별도로 "systemd 시스템 전역변수: 프록시 환경변수"를 설정한다.
# 디렉토리 생성
sudo mkdir -p /etc/systemd/system/actions.runner.{runner_IP}.service.d
# override.conf 파일 수동 생성 (systemd 설정파일)
sudo nano /etc/systemd/system/actions.runner.{runner_IP}.service.d/override.conf
아래 내용을 override.conf 파일에 복붙
[Service]
Environment="http_proxy=http://{BastHost_Private_IP}:3128"
Environment="https_proxy=http://{BastHost_Private_IP}:3128"
systemd 설정 반영 및 재시작
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl restart actions.runner.{runner_IP}.service
Github Action Runner 백그라운드 실행확인
sudo systemctl status actions.runner.{runner_IP}.service
초록색 상태와 Running 출력되면 성공이다. 아래와 같이, Github Runners -> Status Idle 확인가능하다.
3. Deploy.yml -> 배포 스크립트 구성
1. Runners 배포 스크립트 설정
name: dev deploy
# 📌 워크플로우 트리거 설정: dev 브랜치에 push 또는 PR 발생 시 실행
on:
pull_request:
branches: [ dev ]
push:
branches: [ dev ]
jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.save-tag.outputs.image_tag }} # 빌드 시점 태그를 다음 job으로 전달
steps:
- uses: actions/checkout@v3 # 📥 코드 체크아웃
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Gradle caching # 💾 캐시로 빌드 속도 향상
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: $-gradle-$
restore-keys: |
$-gradle-
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle # 🛠️ 테스트 제외한 빌드 실행
run: ./gradlew clean build -x test --stacktrace
shell: bash
- name: Get current time # 🕒 태깅용 현재 시간 생성 (KST 기준)
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYY-MM-DDTHH-mm-ss
utcOffset: "+09:00"
- name: Save image tag # 🏷️ Docker 이미지 태그용 시간 저장
id: save-tag
run: |
echo "image_tag=${{ steps.current-time.outputs.formattedTime }}" >> $GITHUB_OUTPUT
- name: Show Current Time
run: echo "CurrentTime=${{ steps.current-time.outputs.formattedTime }}"
- name: Configure AWS credentials # 🔐 ECR 로그인용 AWS 인증 설정
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # {AWS 액세스 키}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # {AWS 시크릿 키}
aws-region: ap-northeast-2 # {AWS 리전: 서울}
- name: Login to Amazon ECR # 🔑 Docker 레지스트리 로그인
uses: aws-actions/amazon-ecr-login@v1
with:
mask-password: 'true'
- name: Build, tag, and push image to Amazon ECR
run: |
IMAGE_TAG=${{ steps.current-time.outputs.formattedTime }}
# 🐳 도커 이미지 빌드
docker build -t {서비스}:${IMAGE_TAG} .
# 🏷️ 이미지에 ECR 레지스트리 태그 붙이기
docker tag {서비스}:${IMAGE_TAG} {ECR_URL}/{서비스}:${IMAGE_TAG} # {AWS ECR URI}
# ☁️ 이미지 푸시
docker push {ECR_URL}/{서비스}:${IMAGE_TAG}
env:
DOCKER_BUILDKIT: 1 # 도커 빌드 최적화 플래그
deploy:
# 📦 self-hosted runner에서 실행되는 배포 Job
runs-on: self-hosted
needs: build-and-push # 빌드 완료 후 실행됨
steps:
- name: Configure AWS credentials (for ECR pull)
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # {AWS 액세스 키}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # {AWS 시크릿 키}
aws-region: ap-northeast-2
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v1
with:
mask-password: 'true'
- name: Pull and run new Docker image
run: |
IMAGE_TAG=${{ needs.build-and-push.outputs.image_tag }}
# 🔄 기존 컨테이너 종료 및 제거
docker stop {서비스} || true
docker rm {서비스} || true
# 🚀 새 이미지로 컨테이너 실행
docker run -d \
--restart=always \
--name {서비스} \
-p 8080:8080 \
{ECR_URL}/{서비스}:${IMAGE_TAG} # {AWS ECR URI}
2. systemd 매니저 -> Runner 자격증명용 VPC Endpoint STS 생성
VPC 엔드포인트 | com.amazonaws.{리전}.Sts |
3. CI/CD 배포 테스트
Github Action 목록에서 아래와 같이 성공하면 self-hosted-runner 기반 CI/CD 배포완료이다.
마치는 글 - 단순한 배포 자동화가 아닌, 인프라 전략의 전환
이번 작업은 단순히 CI/CD 파이프라인을 "잘 작동하게 만든다"는 수준을 넘어서, 서비스의 보안 및 운영 효율성까지 고려한
인프라 구조의 전략적 전환을 수행했었다.
처음에는 빠르게 개발하고 배포할 수 있는 구조가 필요했지만,
서비스가 고도화되며 퍼블릭 네트워크에 노출된 리소스 구조는 안전하지도, 확장 가능성이 낮았다.
이에 대한 대응으로 아래와 같은 상세 작업을 수행했다.
1. 퍼블릭 -> 프라이빗 VPC 전환
2. 최소 비용 설계에 따른 self-hosted runner 기반 배포 파이프라인 구축
이번 경험을 토대로 CI/CD는 "자동화"라는 기능만이 아니라,
운영/보안/비용의 균형을 잡아주는 인프라의 핵심 요소라는 것을 깨닫게 되었다.
CI/CD는 도구가 아니라 팀의 ""운영 철학""
앞으로의 작업은 EC2 및 RDS 인스턴스에 스케줄러 서비스를 활성화 시켜,
개발 환경에서 보다 효율적으로 인스턴스 유지비용을 관리할 수 있게 구성해볼 예정이다.
나아가, CloudWatch 서비스를 적극 도입하여, 내부 리소스 트래픽 사용량 추적 (CPU 사용량 등)을 활성화하고
인스턴스 내부 접근로그를 분석해, 리소스 낭비를 추적할 수 있는 내부 시스템을 구축해볼 예정이다.
'백엔드 개발' 카테고리의 다른 글
[DevOps 실무] 프로젝트 관리 효율을 2배 높이는 폴더 정리법 (1) | 2025.04.06 |
---|---|
[AWS] 서버 보안 : 팀원의 EC2 접근, 어떻게 열어줘야 할까? (0) | 2025.03.28 |
[AWS] 서버 보안 : Bastion Host 기반 Private EC2 구축하기 (0) | 2025.03.28 |
[AWS] VPC로 클라우드 리소스 보호하기 (2) | 2025.03.16 |
[CI/CD] AWS EC2 자동화 배포 파이프라인 구축하기 (0) | 2025.01.25 |