전체 구성 개요
GitHub Actions로 Spring Boot 빌드
서버에 .jar 파일 자동 배포 (scp 또는 sftp)
SSH 키 기반 인증 방식 사용 (비밀번호 방식 지양)
서버에서 systemd 서비스로 구동 및 무중단 배포
Nginx Reverse Proxy 설정
도메인 연결까지
서버 디렉토리 구조 예시 (/home/sample)
/home/sample/ ├── current -> sample-v2.jar # 심볼릭 링크 ├── sample-v1.jar ├── sample-v2.jar ├── start.sh ├── stop.sh └── sample.service # systemd 서비스
1. SSH 키 기반 인증 준비
SSH Key 생성 및 서버 등록
1. 로컬에서 SSH 키 생성
여기서 github-actions@our-sample 는 임의 명이다.
github-deploy-key 또한 원하는 이름으로 변경하면된다.
중요한것은 깃허브에서 actions 에서 우분트 22 에 접속할수 있는 개인 키 등록과 우부트 22에 공개 키 설정이다.
PowerShell 사용 시 (경로를 명시적으로 써야 함)
ssh-keygen -t rsa -b 4096 -C "github-actions@our-sample" -f "$env:USERPROFILE\.ssh\github-deploy-key"
Git Bash 사용 시 (추천).
ssh-keygen -t rsa -b 4096 -C "github-actions@our-sample" -f ~/.ssh/github-deploy-key
2. GitHub에 개인 키 등록
github-deploy-key 내용을 복사
GitHub 저장소> 깃허브 해당 프로젝트에서 > Settings > Secrets and variables > Actions > New secret 이름은 예: DEPLOY_KEY 값을 복붙
3.서버에 공개 키 등록
서버에 .ssh 디렉토리 만들기
권한 설정 문제 : root 로 할것
mkdir -p ~/.ssh chmod 700 ~/.ssh
공개 키 붙여넣기:
로컬에서 github-deploy-key.pub 파일 내용을 복사한 뒤, 서버에서:
중요한 오해 방지]
서버에 ~/.ssh/github-deploy-key 추가하는 게 아닙니다.
서버는 공개 키만 필요하며, 개인 키는 절대 서버에 두지 않습니다.
개인 키: GitHub Actions에서만 사용
공개 키: 서버의 ~/.ssh/authorized_keys에만 위치
✅ 확인: GitHub Actions에서 서버 접속이 되는가?
GitHub Actions가 아래 명령으로 배포 서버에 SSH 접속 가능해야 합니다:
ssh -i ~/.ssh/id_rsa sample@sample.com
버 측 ~/.ssh/authorized_keys 확인
우분투 서버에 다음 내용이 등록되어야 합니다:
~/.ssh/authorized_keys 파일에 개인키에 대응되는 공개키가 포함되어야 합니다.
cat ~/.ssh/github-deploy-key.pub >> ~/.ssh/authorized_keys
cat ~/.ssh/github-deploy-key.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys
또한 해당 SSH 사용자의 ~/.ssh 디렉토리와 권한은 다음과 같아야 합니다:
root 권한
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys
참조:
https://macaronics.net/m03/nodejs/view/2423
2.GitHub에 Secrets 등록
GitHub > Settings > Secrets and variables > Actions > New repository secret
아래 항목 등록
권한 설정 문제 : 유저를 sample 접속 할경우 워크플로우에 항상 sample 권한을 부여 해야 합니다.
a) DEPLOY_KEY: 개인 키 (~/.ssh/github-deploy-key 내용) b) HOST: sample.com c) USER: sample d) TARGET_DIR: /home/sample e) SERVICE_NAME: sample.service f) PORT: 225 g) APP_PROPERTIES : application.properties 전체 내용을 그대로 붙여넣기
3. GitHub Actions 설정 (CI/CD)
설정 테스트 : deploy.yml 으로 실제 CI/CD 작업 전에
deploy-test.yml 파일을 만든후 접속연결 상태등을 테스트 한다.
name: SSH Deploy Key & Connection Test on: workflow_dispatch: # 수동 실행 버튼으로 테스트할 수 있게 함 jobs: test-ssh: runs-on: ubuntu-latest steps: - name: ✅ Show runner info run: | echo "Running on: $(uname -a)" echo "Bash version: $(bash --version)" - name: ✅ Setup SSH private key from GitHub Secrets run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa echo "✅ Private key has been written to ~/.ssh/id_rsa" - name: ✅ ssh-keyscan known_hosts 등록 (포트 포함) run: | ssh-keyscan -p ${{ secrets.PORT }} -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts echo "✅ ssh-keyscan 완료" echo "✅ known_hosts 내용:" cat ~/.ssh/known_hosts - name: ✅ SSH 접속 테스트 run: | ssh -p ${{ secrets.PORT }} -o StrictHostKeyChecking=no ${{ secrets.USER }}@${{ secrets.HOST }} "echo '✅ SSH 접속 성공!'" - name: ✅ 서버 디렉토리 접근 및 권한 확인 run: | ssh -p ${{ secrets.PORT }} ${{ secrets.USER }}@${{ secrets.HOST }} "ls -ld ${{ secrets.TARGET_DIR }} || echo '❌ 디렉토리 없음 또는 권한 문제'" - name: ✅ scp 전송 테스트 (더미 파일) run: | echo "This is a test file from GitHub Actions" > dummy.txt scp -P ${{ secrets.PORT }} dummy.txt ${{ secrets.USER }}@${{ secrets.HOST }}:${{ secrets.TARGET_DIR }}/dummy-from-actions.txt echo "✅ SCP 파일 전송 완료" - name: ✅ 서버에 dummy 파일 존재 확인 run: | ssh -p ${{ secrets.PORT }} ${{ secrets.USER }}@${{ secrets.HOST }} "ls -l ${{ secrets.TARGET_DIR }}/dummy-from-actions.txt || echo '❌ 파일 확인 실패'"
Spring Boot에서 application.properties (또는 application.yml) 파일은 DB 비밀번호, API 키 등 민감한 정보를 담고 있기 때문에 .gitignore로 커밋하지 않는 것이 원칙입니다.
그렇다면 배포 자동화(GitHub Actions)에서 어떻게 이 파일을 운영 서버에 전달하느냐?
→ 여러 가지 방법이 있지만, 가장 깔끔하고 관리하기 좋은 방법은 GitHub Secrets를 활용해서 배포 시 생성하는 것입니다.
GitHub Repository > Settings > Secrets and variables > Actions
아래와 같이 Secret을 등록하세요:
이름값 예시
APP_PROPERTIES아래 내용 참고
예시 값 (실제 DB 접속정보에 맞게 작성):
spring.datasource.url=jdbc:mysql://localhost:3306/mydb spring.datasource.username=myuser spring.datasource.password=supersecurepassword spring.jpa.hibernate.ddl-auto=update server.port=8080
application.properties 전체 내용을 그대로 붙여넣기 하세요.
깃허브의 프로젝트 구조가 다음과 같다
✅ 프로젝트 구조
/ ├── .github/ │ └── workflows/ │ └── deploy.yml ├── backend/ ← ★ Spring Boot 3.4 프로젝트 위치 │ └── pom.xml │ └── src/ │ └── target/our-sample.jar (빌드 결과) ├── frontend/ ← React 프로젝트즈
즉, backend 아래에 스프링부트가 프로젝트 가 존재하고, 그리고 frontend 디렉토리 안에 리액트 프로젝트가 존재하는 구조이다.
최종 : .github/workflows/deploy.yml 파일 생성:
name: CI/CD for Spring Boot on: push: branches: [ main ] # main 브랜치에 push 발생 시 실행됨 jobs: deploy: runs-on: ubuntu-latest # GitHub Actions가 실행될 가상 환경 steps: - name: Checkout code uses: actions/checkout@v3 # GitHub 저장소의 소스코드를 가져옴 - name: Set up Java uses: actions/setup-java@v3 with: distribution: 'temurin' # Temurin(Java OpenJDK) 사용 java-version: '17' # Java 17로 설정 - name: ✅ Create application.properties from secret run: | mkdir -p backend/src/main/resources # resources 디렉토리 생성 printf "%s" "${{ secrets.APP_PROPERTIES }}" > backend/src/main/resources/application.properties # GitHub Secrets에서 APP_PROPERTIES 값을 받아 파일로 저장 - name: Build Spring Boot app working-directory: ./backend run: mvn clean package -DskipTests # 테스트는 생략하고 빌드 - name: Setup SSH key run: | mkdir -p ~/.ssh echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa # GitHub Secrets에서 SSH 프라이빗 키 생성 chmod 600 ~/.ssh/id_rsa ssh-keyscan -p ${{ secrets.PORT }} -H ${{ secrets.HOST }} >> ~/.ssh/known_hosts # 서버 공개키 스캔하여 known_hosts에 등록 (SSH 보안 경고 방지) - name: Copy JAR to server run: | scp -P ${{ secrets.PORT }} backend/target/our-sample.jar \ ${{ secrets.USER }}@${{ secrets.HOST }}:${{ secrets.TARGET_DIR }}/our-sample-latest.jar # 생성된 JAR 파일을 서버에 전송 (scp로 복사) - name: Restart service via SSH run: | ssh -p ${{ secrets.PORT }} ${{ secrets.USER }}@${{ secrets.HOST }} << EOF sudo systemctl stop ${{ secrets.SERVICE_NAME }} # 기존 서비스 중단 mv ${TARGET_DIR}/our-sample-latest.jar ${TARGET_DIR}/our-sample-v2.jar # 파일 이름 변경 ln -sf ${TARGET_DIR}/our-sample-v2.jar ${TARGET_DIR}/current # 심볼릭 링크 업데이트 sudo systemctl start ${{ secrets.SERVICE_NAME }} # 서비스 재시작 EOF
✅ 장점
민감정보를 GitHub에 커밋하지 않음 ✅
환경 설정을 GitHub Secrets에서 통합 관리 ✅
다른 환경(dev/staging/prod)에도 적용하기 쉬움 ✅
✅ 필수 GitHub Secrets 설정 목록
이름설명
APP_PROPERTIES : application.properties 파일 전체 내용
DEPLOY_KEY 개인 키 (private key) 전체 문자열
USER 예: sample
HOST 예: sample.com
PORT 예: 987
TARGET_DIR 예: /home/ubuntu
SERVICE_NAME : sample.service
4.서버 설정: Systemd 서비스 생성
서버에서 /etc/systemd/system/sample.service 생성:
/home/ubuntu/ 에 깃허브오 생성된 jar 을 확인한다.
[Unit] Description=Our sample Spring Boot App After=network.target [Service] User=root WorkingDirectory=/home/ubuntu ExecStart=/usr/bin/java -Xmx256m -Xms128m -jar /home/ubuntu/our-sample-latest.jar SuccessExitStatus=143 Restart=on-failure RestartSec=5 StandardOutput=append:/home/sample/output.log StandardError=append:/home/sample/error.log [Install] WantedBy=multi-user.target
# 실행 권한 및 등록 sudo systemctl daemon-reexec sudo systemctl daemon-reload sudo systemctl enable sample
.service 파일을 수정했다면:
sudo systemctl daemon-reload
그 다음 서비스를 다시 시작하세요: 서비스 재시작:
sudo systemctl restart sample.service
이렇게 하면 경고 없이 정상적으로 서비스가 시작됩니다.
이후 상태를 확인하려면:
sudo systemctl status sample.service
5.Nginx Reverse Proxy 설정
/etc/nginx/sites-available/sample:
server { listen 80; server_name your-domain.com; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }
sudo ln -s /etc/nginx/sites-available/sample /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl restart nginx
6. 무중단 배포 전략 (심볼릭 링크 방식)
새 .jar → sample-v2.jar
current → sample-v2.jar 로 심볼릭 링크 교체
systemd 서비스 재시작
===========================================
================== ========================
=====================================
✅ 그러면 위 설정한 방식이 정말 무중단 배포 일까?
GitHub Actions 스크립트 중 Restart service via SSH 부분은 이렇게 되어 있죠:
sudo systemctl stop ${{ secrets.SERVICE_NAME }} mv ${TARGET_DIR}/our-advertising-latest.jar ${TARGET_DIR}/our-advertising-v2.jar ln -sf ${TARGET_DIR}/our-advertising-v2.jar ${TARGET_DIR}/current sudo systemctl start ${{ secrets.SERVICE_NAME }}
이 방식의 동작은?
기존 서비스 중단
→ systemctl stop으로 기존 Spring Boot 앱 프로세스를 중단시킴.JAR 파일 이름 변경 및 링크 갱신
→ our-advertising-latest.jar를 our-advertising-v2.jar로 이름 변경하고,
→ /home/advertising/current라는 링크를 새 파일로 재지정함.새로운 서비스 시작
→ systemctl start로 새 JAR 파일을 실행.
문제: 이건 "무중단" 배포가 아님
이 과정에서 분명히 잠깐의 다운타임이 발생합니다.
stop → 애플리케이션 중단
start → 다시 기동되기까지 수 초 소요
이 시간 동안에는 서비스가 사용 불가 상태입니다.
즉, 기존 요청이 끊기고, 새 요청도 에러가 날 수 있습니다.
✅ 그럼 진짜 무중단 배포는 어떻게 하나요?
1. 리버스 프록시 + 포트 스위칭 방식 (추천)
Nginx 또는 HAProxy 같은 리버스 프록시를 앞단에 두고,
백엔드 앱을 두 개의 포트(예: 8081, 8082) 중 하나에 번갈아 배포한 뒤 프록시 설정을 라이브에서 스위칭하는 방식입니다.
사용자 요청 ↓ +--------+ +----------------------+ +----------------------+ | Nginx | ───▶ | Spring Boot @8081 | 또는 | Spring Boot @8082 | +--------+ +----------------------+ +----------------------+ ↕ ↑ ↑ config reload │ │ └──── 새 버전 배포중 ────┘
새 포트에 새 앱 기동
상태 확인 후 Nginx 설정 변경 (nginx -s reload)
기존 포트 앱 종료
이렇게 하면 절대 요청이 끊기지 않습니다.
2. 배포 자동화 도구 사용 (예: Kubernetes, AWS ECS, Naver CLOVA)
이들은 기본적으로 무중단 배포를 위한 헬스체크, 롤링 업데이트, 로드 밸런서 기능을 제공합니다.
✅ 결론
요약
지금 설정은 JAR 파일 교체 후 서비스 재시작을 수동으로 하는 방식으로,
엄밀히 말해 무중단 배포는 아닙니다.
이를 해결하려면 Nginx를 이용한 포트 스위칭 방식을 도입하거나,
Spring Boot 앱 자체를 클러스터 구조나 컨테이너 오케스트레이션 환경으로 올리는 것이 필요합니다.
✅ 현재 가장 널리 사용되는 무중단 배포 방식은 Kubernetes 기반의 롤링 업데이트 방식입니다.
⭕ 무중단 배포 방식 중 가장 많이 쓰이는 것
Kubernetes가 무중단에 강한 이유
Kubernetes는 기본적으로 무중단 배포를 위한 기능이 포함되어 있습니다:
롤링 업데이트 (Rolling Update):
기존 Pod를 천천히 교체하며 새 버전을 배포
→ 트래픽은 정상 동작 중인 Pod로만 전달헬스체크 (Liveness/Readiness Probes):
새 Pod가 완전히 준비되었는지 확인 후에만 트래픽 전달
→ 아직 준비 안 된 앱에 요청 전달 안 함자동 롤백:
새 버전이 문제 생기면 자동으로 이전 버전으로 되돌림서비스 디스커버리 및 로드밸런싱 내장
심플하게
Nginx + Spring Boot 포트 스위칭 무중단 배포 구성도 가능하다.
★현재 상황에서 Kubernetes 설정이 "어려운 이유
기존에 사용 중인 환경이 단순 EC2 + SSH 배포나 단일 서버 기반 Spring Boot 서비스라면, Kubernetes를 도입하려면 다음과 같은 준비가 필요합니다:
1. 클러스터 환경 필요
Kubernetes는 Pod, Node, Cluster 개념이 전제입니다.
AWS EKS, GCP GKE, Azure AKS 같은 클라우드 관리형 서비스를 사용하거나, 자체적으로 kubeadm 등으로 클러스터를 구성해야 합니다.
2. Spring Boot를 Docker 이미지로 컨테이너화
K8s에서는 JAR 파일을 직접 배포하지 않습니다.
반드시 Docker 이미지로 만들어야 합니다.
3. K8s 배포 설정 파일 필요
deployment.yaml, service.yaml, ingress.yaml 등을 작성해야 함
그리고 헬스체크(Liveness/Readiness probe) 설정도 필요함
4. CI/CD 파이프라인 재구성 필요
GitHub Actions → JAR 복사 방식은 불가능
Docker 이미지 빌드 + 컨테이너 레지스트리(ECR, GCR) 푸시 + kubectl apply or helm 배포
Kubernetes로 가기 위해 필요한 것 정리
항목 설명
✅ Dockerize Spring Boot 앱을 Docker 이미지로 만들기
✅ Kubernetes Cluster 로컬 Minikube 또는 클라우드 EKS, GKE 등 준비
✅ YAML 설정 파일 Deployment, Service, Ingress, ConfigMap 등
✅ CI/CD 수정 JAR → Docker 이미지 빌드 & 푸시 + K8s 배포
✅ 도메인 + 로드밸런서 (선택 ) 외부에서 접속 가능하게 설정
시작이 부담되면?
1. Docker + Nginx 무중단 배포 먼저 시도
서버 하나에 docker-compose로도 Nginx + Spring Boot 무중단 배포 가능
복잡도는 낮고 실전 배포처럼 구성 가능
결론 요약
✅ 학습/로컬 테스트는 하루 가능
✅ 실무용 클러스터 + 무중단 배포까지는 최소 2~3일 소요
댓글 ( 0)
댓글 남기기