이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • systemd는 Spring 애플리케이션 운영에서 어떤 문제를 해결하는가?
  • unit 파일에서 User, EnvironmentFile, ExecStart, Restart는 왜 중요한가?
  • restart loop나 start failure가 났을 때 어떤 순서로 확인해야 하는가?

개요

systemd는 Linux 서버에서 서비스를 시작하고, 멈추고, 재시작하고, 로그와 상태를 관리하는 기본 장치다.

Spring Boot jar를 단순히 java -jar로 실행하면 터미널 종료, 사용자 변경, 서버 재부팅, 로그 수집, 실패 재시작을 일관되게 관리하기 어렵다.

systemd unit으로 서비스화하면 운영자가 같은 명령과 같은 로그 경로로 상태를 확인할 수 있다.

systemd가 필요한 이유

운영 서버에서 서비스는 다음 조건을 만족해야 한다.

  • 서버 재부팅 후 자동으로 올라온다.
  • 특정 사용자로 실행된다.
  • 환경 파일을 통해 설정을 주입한다.
  • 실패 시 재시작 정책이 있다.
  • 종료 시 정상 signal을 받는다.
  • journal에 로그와 실패 원인이 남는다.
  • 현재 상태를 표준 명령으로 확인할 수 있다.

이 요구를 매번 shell script로 직접 맞추면 실수 지점이 많아진다.

systemd는 이 계약을 unit 파일로 고정한다.

Unit 파일 예시

[Unit]
Description=order-api Spring Boot service
After=network-online.target
Wants=network-online.target
 
[Service]
User=app
Group=app
WorkingDirectory=/opt/order-api
EnvironmentFile=/etc/order-api/order-api.env
ExecStart=/usr/bin/java -jar /opt/order-api/order-api.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
 
[Install]
WantedBy=multi-user.target

UserGroup은 서비스 권한 범위를 정한다.

WorkingDirectory는 상대 경로, 파일 접근, 로그 경로 해석에 영향을 준다.

EnvironmentFile은 Spring profile, DB URL, JVM option 같은 환경 값을 주입한다.

ExecStart는 실제 실행 명령이다.

Restart는 프로세스 실패 시 재시작 여부를 정한다.

SuccessExitStatus=143은 SIGTERM 종료를 정상 종료로 해석하게 하는 데 자주 사용된다.

기본 명령

서비스 상태는 systemd 명령으로 본다.

sudo systemctl daemon-reload
sudo systemctl enable order-api
sudo systemctl start order-api
sudo systemctl status order-api
sudo journalctl -u order-api --since "10 minutes ago"

unit 파일을 바꾼 뒤에는 daemon-reload가 필요하다.

이 과정을 빼먹으면 파일을 수정했는데도 systemd가 예전 unit 정의로 동작할 수 있다.

배포와 systemd

단일 서버 배포에서는 보통 다음 순서를 둔다.

  1. 새 jar를 별도 경로에 업로드한다.
  2. checksum이나 version을 확인한다.
  3. symlink를 새 jar로 전환한다.
  4. systemctl restart 또는 stop/start를 실행한다.
  5. systemctl statusjournalctl로 시작 실패를 확인한다.
  6. /actuator/health와 smoke test를 실행한다.

restart는 배포 성공이 아니다.

restart 후 Spring이 prod profile로 떴는지, port를 열었는지, DB 연결에 성공했는지 확인해야 한다.

장애 확인 순서

서비스가 올라오지 않으면 다음 순서로 본다.

systemctl status order-api
journalctl -u order-api -n 200 --no-pager
systemctl cat order-api
ls -l /etc/order-api/order-api.env
ls -l /opt/order-api/order-api.jar
ss -ltnp | grep 8080

확인할 항목은 다음이다.

  • unit 파일 문법 오류
  • Java 경로 오류
  • jar 파일 권한 오류
  • 환경 파일 누락
  • profile 오타
  • port 충돌
  • DB 연결 실패
  • restart loop
  • StartLimitBurst로 인한 재시작 제한

Restart 정책의 함정

Restart=always는 편해 보이지만 문제를 숨길 수 있다.

설정 오류로 즉시 죽는 서비스가 계속 재시작하면 로그가 빠르게 밀리고 CPU를 낭비한다.

DB 장애 때 모든 인스턴스가 빠르게 재시작하면 DB에 연결 폭주를 만들 수도 있다.

Spring 애플리케이션은 재시작보다 실패 원인과 준비 상태를 확인하는 것이 먼저다.

재시작 정책은 장애 완화가 아니라 마지막 안전장치로 봐야 한다.

운영 체크리스트

  • unit 파일이 repository나 배포 문서에서 관리되는가?
  • User=app처럼 전용 사용자를 쓰는가?
  • EnvironmentFile 권한이 제한되어 있는가?
  • ExecStart의 jar 경로와 Java 경로가 고정되어 있는가?
  • unit 변경 후 daemon-reload 절차가 있는가?
  • restart 후 health check와 smoke test가 실행되는가?
  • journal 로그 보관 정책이 정해져 있는가?

장애 신호

  • systemctl status에서 failed 또는 activating (auto-restart)가 반복된다.
  • journalctlPermission denied가 보인다.
  • EnvironmentFile을 찾지 못한다.
  • Address already in use로 port bind가 실패한다.
  • restart 후 health check는 성공하지만 잘못된 profile로 뜬다.
  • Start request repeated too quickly 메시지가 나온다.
  • 수동 실행한 프로세스와 systemd 프로세스가 중복되어 있다.

안전한 완화 조치

장애 중에는 restart를 반복하기 전에 로그와 unit을 본다.

unit 변경이 원인이라면 이전 unit 파일이나 이전 jar symlink로 되돌린다.

환경 파일 변경이 원인이라면 변경 diff를 확인하고 Secret 값을 노출하지 않는 방식으로 비교한다.

port 충돌이면 어떤 프로세스가 잡고 있는지 확인한 뒤 종료 여부를 판단한다.

재시작 폭주가 있으면 일시적으로 서비스를 stop하고 원인을 확인할 수 있다.

sudo systemctl stop order-api
sudo systemctl reset-failed order-api

단, stop은 사용자 영향이 있으므로 Load Balancer나 Nginx에서 해당 인스턴스를 빼는 절차와 함께 봐야 한다.

인프라 담당자와 공유할 자료

서비스: order-api
증상: systemd restart loop
시간대: 15:02부터
status: activating (auto-restart)
journal 핵심: /etc/order-api/order-api.env Permission denied
최근 변경: 배포 사용자와 env 파일 권한 변경
확인 요청: unit User/Group과 env 파일 owner/group 정책 확인

systemd 문제는 unit 파일, 환경 파일, jar 경로, 실행 사용자 정보를 함께 줘야 빠르게 좁혀진다.

실전 팁

  • unit 파일을 서버에서 직접만 수정하지 말고 변경 이력을 남긴다.
  • systemctl cat으로 실제 적용 중인 unit과 drop-in을 확인한다.
  • journalctl --since로 배포 시각 기준 로그를 본다.
  • restart 후에는 process 상태보다 health와 smoke test를 확인한다.
  • jar symlink 방식을 쓰면 rollback 대상이 분명해진다.

위험 신호!

  • nohup java -jar ... &로 운영 서비스를 띄운다.
  • systemd unit이 서버마다 다르게 수동 수정되어 있다.
  • EnvironmentFile에 Secret이 있는데 권한이 넓다.
  • restart loop를 해결하지 않고 RestartSec만 줄인다.
  • systemctl restart 후 정상 여부를 status만 보고 끝낸다.

확인 질문

확인 질문

  • systemd로 Spring 서비스를 관리하는 이유는 무엇인가?
    • 실행 사용자, 환경 파일, 재시작 정책, 로그, 자동 시작, 상태 확인을 표준화해 운영 중 같은 방식으로 진단하고 복구하기 위해서다.
  • unit 파일 변경 후 daemon-reload가 필요한 이유는 무엇인가?
    • systemd manager가 unit 정의를 다시 읽어야 수정된 설정이 실제 서비스 제어에 반영되기 때문이다.
  • restart loop에서 가장 먼저 볼 증거는 무엇인가?
    • systemctl status, journalctl -u, 실제 unit 내용, 환경 파일 권한, jar 경로, 포트 충돌 여부다.

참고 문서