infra
Platform

모듈 맵

[Infra Ops] systemd 서비스 등록과 자동 재시작 설정

0 / 52 완료

펼치기
0 / 52 완료0%

Infra-ops · 39 / 52

[Infra Ops] systemd 서비스 등록과 자동 재시작 설정

systemd unit 파일 작성, 기동 순서 의존성, auto restart 설정, 서비스 기동 전 점검 루틴까지

🚨INCIDENT ALERT
HIGH

새로 입사한 첫 주, Tomcat 기반 Java 애플리케이션을 서버에 배포하는 작업을 맡았습니다. 개발팀은 JAR 파일을 넘겼고, 실행 명령어도 알려줬습니다. 직접 실행해보니 잘 뜹니다. 그런데 다음날 아침 서버에 재접속해보니 서비스가 죽어있습니다. 누군가 서버를 재부팅했기 때문입니다. 또 nohup java -jar myapp.jar &로 띄웠지만, 서버 재시작 후마다 수동으로 올려야 하는 게 맞는 건지 의심이 듭니다.

운영 서버에서 서비스는 systemd가 관리해야 합니다. 재부팅 후 자동 기동, 장애 시 자동 재시작, 로그 통합 관리까지 — 이 모든 것이 unit 파일 하나로 해결됩니다.

이번 챕터에서 배울 것
  • 1systemd unit 파일의 [Unit], [Service], [Install] 섹션 구조를 설명할 수 있다
  • 2Type=simple/forking/oneshot/notify의 차이를 구분하고 상황에 따라 선택할 수 있다
  • 3After=, Requires=, Wants= 의존성 지시어로 기동 순서를 제어할 수 있다
  • 4Restart=on-failure, RestartSec, StartLimitInterval로 자동 재시작을 설정할 수 있다
  • 5기동 실패 시 systemctl status → journalctl 순서로 원인을 추적할 수 있다

systemd unit 파일 구조

💡개념

unit 파일의 세 가지 섹션

운영 서버에서 nohup java -jar app.jar &로 서비스를 띄우는 것은 두 가지 점에서 불완전합니다. 서버가 재부팅되면 아무도 다시 올려주지 않고, 프로세스가 죽어도 아무도 감지하지 못합니다. systemd unit 파일은 이 두 가지를 한 번에 해결하며, [Unit], [Service], [Install] 세 섹션의 역할을 이해하면 어떤 서비스든 운영 수준으로 등록할 수 있습니다.

systemd unit 파일 구조와 서비스 생명주기

서비스를 nohup으로 띄우는 방식은 두 가지 문제가 있습니다. 재부팅 후 자동 기동이 안 되고, 죽었을 때 아무도 다시 살려주지 않습니다. systemd unit 파일은 이 두 가지를 모두 해결합니다.

unit 파일은 /etc/systemd/system/ 디렉터리에 {서비스명}.service 형태로 저장합니다. 세 개의 섹션으로 구성됩니다.

INI
# /etc/systemd/system/myapp.service

[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/app
ExecStart=/usr/bin/java -jar /opt/app/myapp.jar
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

각 섹션의 역할:

섹션역할주요 지시어
[Unit]서비스 메타데이터와 의존성Description, After, Requires, Wants
[Service]실행 방법, 사용자, 재시작 정책Type, ExecStart, Restart, User
[Install]어느 타겟에서 활성화할지WantedBy

WantedBy=multi-user.target: 일반 서버 서비스의 표준입니다. 네트워크가 올라오고 멀티유저 환경이 준비된 후 서비스가 시작됩니다. GUI가 필요한 서비스는 graphical.target을 쓰지만, 서버 애플리케이션은 대부분 multi-user.target입니다.

💡개념

Type — 프로세스 기동 방식 구분

systemd가 서비스 기동 완료를 어떻게 판단할지를 Type이 결정합니다. 잘못 지정하면 의존 서비스가 너무 일찍 뜨거나, systemd가 기동 완료를 영원히 기다리는 상황이 생깁니다.

INI
# Type=simple (가장 일반적)
# ExecStart로 지정한 프로세스가 메인 프로세스
# 해당 프로세스가 실행되면 즉시 기동 완료로 간주
Type=simple
ExecStart=/usr/bin/java -jar /opt/app/myapp.jar

# Type=forking
# ExecStart 프로세스가 fork 후 부모가 종료하는 구조 (전통적인 데몬 방식)
# Nginx, Apache 같은 오래된 서비스에 사용
Type=forking
PIDFile=/var/run/myapp.pid

# Type=oneshot
# 한 번 실행하고 종료하는 작업 (스크립트, 초기화 작업 등)
# RemainAfterExit=yes를 함께 쓰면 종료 후에도 active 상태 유지
Type=oneshot
RemainAfterExit=yes
ExecStart=/opt/scripts/init-db.sh

# Type=notify
# 서비스가 준비 완료 시 sd_notify()로 systemd에 직접 신호를 보냄
# 기동 시간이 오래 걸리는 서비스에서 정확한 준비 완료 감지
Type=notify
ExecStart=/usr/bin/myapp --systemd

실무에서 선택 기준:

Java/Python/Node.js 애플리케이션은 대부분 Type=simple입니다. 직접 실행되는 단일 프로세스이기 때문입니다. Nginx처럼 fork 구조를 쓰는 서비스는 Type=forking, DB 초기화나 배치 스크립트는 Type=oneshot을 씁니다.

기동 순서 의존성

💡개념

After=, Requires=, Wants= 차이

서버 부팅 시 systemd는 여러 서비스를 병렬로 시작합니다. 의존성을 지정하지 않으면 DB가 준비되기 전에 애플리케이션이 먼저 뜨면서 연결 실패로 기동에 실패할 수 있습니다.

INI
[Unit]
Description=My Web Application
# 순서(After): network.target이 완료된 후에 시작
After=network.target

# 강한 의존성(Requires): postgresql.service가 없으면 이 서비스도 중지
# postgresql.service가 실패하면 myapp도 함께 실패
Requires=postgresql.service

# 약한 의존성(Wants): redis.service가 있으면 좋지만 없어도 기동
# redis가 실패해도 myapp은 계속 기동 시도
Wants=redis.service

# Requires + After 조합이 가장 일반적
# (After만 쓰면 순서는 보장하지만 의존성은 보장 안 함)
After=network.target postgresql.service
Requires=postgresql.service

세 가지 지시어 비교:

지시어의미종속 서비스 실패 시
After=순서만 보장 (이것보다 나중에)내 서비스는 계속 기동
Requires=강한 의존성내 서비스도 중지
Wants=약한 의존성내 서비스는 계속 기동

Tomcat + PostgreSQL 조합이라면 After=network.target 정도가 현실적입니다. DB가 같은 서버에 있을 때는 After=postgresql.service를 추가합니다. 외부 서버 DB는 애플리케이션이 재시도 로직을 갖는 것이 더 견고한 설계입니다.

systemd 서비스 상태 전환 — 장애 시 확인 흐름

자동 재시작 설정

💡개념

Restart 정책과 재시작 제한

자동 재시작은 운영 서버의 핵심 안전망입니다. 하지만 무한 재시작 루프를 방지하는 제한도 함께 설정해야 합니다.

INI
[Service]
ExecStart=/usr/bin/java -jar /opt/app/myapp.jar

# 비정상 종료 시에만 재시작 (권장)
Restart=on-failure

# 재시작 전 대기 시간(초) — 너무 짧으면 로그가 폭주
RestartSec=10

# 재시작 횟수 제한: StartLimitInterval초 안에 StartLimitBurst번 초과 시 중단
StartLimitInterval=60
StartLimitBurst=3
# → 60초 안에 3번 재시작하면 더 이상 시도하지 않음

Restart 옵션 비교:

재시작 조건사용 상황
no재시작 안 함수동 관리 서비스
on-failure비정상 종료 시일반 애플리케이션 (권장)
always종료 원인 무관항상 떠있어야 하는 서비스
on-abnormal시그널/타임아웃 종료 시-

재시작 제한(StartLimitBurst)을 초과해 서비스가 멈춘 경우:

서버 터미널
# 제한 카운터 리셋 후 재시작
sudo systemctl reset-failed myapp
sudo systemctl start myapp

실제 예시 — Tomcat과 Nginx

💡개념

Tomcat systemd unit 파일

패키지 매니저로 설치한 Tomcat은 unit 파일이 자동 생성됩니다. 직접 설치한 경우 아래를 참고합니다.

INI
# /etc/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat Web Application Container
After=network.target

[Service]
Type=forking
User=tomcat
Group=tomcat

Environment=JAVA_HOME=/usr/lib/jvm/java-17-openjdk
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid

ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh

Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Tomcat은 startup.sh가 자식 프로세스를 fork하고 종료하는 구조이므로 Type=forking을 씁니다. CATALINA_PID를 지정하면 systemd가 실제 Tomcat 프로세스 PID를 추적할 수 있습니다.

💡개념

shell script 기반 기동 방식과 비교

많은 레거시 환경에서 /etc/rc.local이나 cron @reboot로 서비스를 관리합니다. systemd와 비교하면 차이가 명확합니다.

항목shell script / rc.localsystemd unit
재부팅 후 자동 기동수동 설정 필요enable로 간단히 등록
장애 시 자동 재시작별도 감시 스크립트 필요Restart= 한 줄
로그 통합파일마다 별도 관리journalctl -u 로 통합
기동 순서 제어스크립트 순서에 의존After= / Requires=
상태 확인ps aux / 별도 구현systemctl status

이미 shell script로 관리 중인 서비스를 systemd로 전환할 때는 ExecStart에 그 스크립트를 그대로 넣어도 됩니다.

INI
[Service]
Type=oneshot
ExecStart=/opt/scripts/start-myapp.sh
RemainAfterExit=yes

서비스 등록과 활성화

1unit 파일 작성 및 등록

작성한 unit 파일을 /etc/systemd/system/에 복사합니다. 파일을 직접 이 경로에서 vim으로 작성해도 됩니다. /usr/lib/systemd/system/은 패키지 매니저가 관리하는 경로이므로 커스텀 서비스는 반드시 /etc/systemd/system/에 넣습니다.

로컬 터미널
# unit 파일 직접 작성
sudo vim /etc/systemd/system/myapp.service

# 또는 기존 파일 복사
sudo cp myapp.service /etc/systemd/system/
sudo cp myapp.service /etc/systemd/system/
🔍unit 파일 등록 확인
  • /etc/systemd/system/myapp.service 파일이 존재하는가
  • systemctl cat myapp으로 unit 파일 내용이 올바르게 읽히는가
  • [Unit], [Service], [Install] 세 섹션이 모두 포함됐는가
2daemon-reload → enable → start 순서 실행

unit 파일을 추가하거나 수정한 후에는 반드시 daemon-reload를 먼저 실행합니다. systemd가 파일 변경을 감지해 내부 설정을 갱신합니다. 이 단계를 건너뛰면 이전 내용 그대로 동작합니다.

서버 터미널
# 1. systemd에 unit 파일 변경 알림
sudo systemctl daemon-reload

# 2. 부팅 시 자동 시작 등록
sudo systemctl enable myapp

# 3. 지금 즉시 기동
sudo systemctl start myapp

# enable과 start를 한 번에: --now 플래그
sudo systemctl enable --now myapp
sudo systemctl daemon-reload && sudo systemctl enable myapp && sudo systemctl start myapp
🔍daemon-reload 및 서비스 등록 확인
  • systemctl daemon-reload 후 오류 메시지 없이 완료됐는가
  • systemctl is-enabled myappenabled를 반환하는가
  • systemctl status myapp에서 Active: active (running)이 표시되는가
3서비스 상태 확인

기동 후 상태를 확인합니다. active (running) 이 정상입니다. 출력 하단에 최근 로그 10줄이 함께 표시됩니다.

서버 터미널
# 서비스 상태 확인
sudo systemctl status myapp

# 출력 예시:
# ● myapp.service - My Application
#    Loaded: loaded (/etc/systemd/system/myapp.service; enabled; ...)
#    Active: active (running) since Fri 2026-05-30 10:00:00 KST; 5s ago
#  Main PID: 12345 (java)
#    CGroup: /system.slice/myapp.service
#            └─12345 /usr/bin/java -jar /opt/app/myapp.jar
sudo systemctl status myapp
🔍실행 후 확인할 것
  • systemctl status myapp — Active: active (running) 상태인가
  • Loaded 줄에 'enabled'가 표시됐는가 (재부팅 후 자동 기동 확인)
  • Main PID가 표시됐는가 (프로세스가 정상 실행 중)
  • 로그 하단에 애플리케이션 시작 메시지가 출력됐는가
  • curl -s http://localhost:8080/health 같은 헬스체크 엔드포인트가 응답하는가

기동 실패 시 로그 확인

4기동 실패 원인 추적

systemctl status로 짧은 에러를 먼저 확인하고, 충분하지 않으면 journalctl로 상세 로그를 봅니다.

서버 터미널
# 1단계: 현재 상태와 최근 로그 확인
sudo systemctl status myapp

# 2단계: 더 많은 로그 확인 (최근 50줄)
sudo journalctl -u myapp -n 50 --no-pager

# 3단계: 실시간 팔로우 (기동 시도하면서 로그 확인)
sudo journalctl -u myapp -f

# 특정 시간대 로그
sudo journalctl -u myapp --since "2026-05-30 09:00:00" --no-pager
sudo systemctl status myapp
🔍실행 후 확인할 것
  • systemctl status에서 Active: failed 또는 activating 상태인지 확인
  • status 출력 하단의 에러 메시지를 읽었는가
  • journalctl -u myapp -n 50 출력에서 Exception 또는 Error 라인을 찾았는가
  • ExecStart 경로의 파일이 실제로 존재하는지 확인했는가 (ls -la 로 검증)

트러블슈팅

원인: unit 파일의 ExecStart 경로가 잘못됐거나, 지정한 User로는 해당 파일을 실행할 권한이 없습니다. systemd는 ExecStart에 절대 경로를 요구합니다.

서버 터미널
# 에러 확인
sudo systemctl status myapp
# 예시: myapp.service: ExecStart=/opt/app/myapp.jar failed (errno=ENOENT)

sudo journalctl -u myapp -n 20 --no-pager
# 예시: Failed to execute command: No such file or directory

# ExecStart 경로 존재 여부 확인
ls -la /opt/app/myapp.jar
# → 없으면 실제 경로 확인 후 unit 파일 수정

# 실행 파일 권한 확인
ls -la /usr/bin/java
# User=appuser로 실행 가능한지 확인
sudo -u appuser /usr/bin/java -version

# 수정 후 적용 순서
sudo systemctl daemon-reload
sudo systemctl start myapp

ExecStart에 쉘 문법(~, 환경변수 직접 사용)은 동작하지 않습니다. 절대 경로를 써야 합니다. 환경변수가 필요하면 Environment= 또는 EnvironmentFile=을 unit 파일에 명시합니다.

원인: systemctl start는 지금 즉시 서비스를 시작하지만, 부팅 시 자동 시작은 등록하지 않습니다. enable 없이 start만 한 경우 재부팅하면 서비스가 꺼진 상태로 시작됩니다.

서버 터미널
# enable 상태 확인
sudo systemctl is-enabled myapp
# 출력: disabled  ← 이것이 원인

# 활성화 (다음 재부팅부터 자동 기동)
sudo systemctl enable myapp

# 확인
sudo systemctl is-enabled myapp
# 출력: enabled

# 현재도 즉시 기동 (이미 꺼진 경우)
sudo systemctl start myapp

# 한 번에 처리
sudo systemctl enable --now myapp

enable은 실제로 심볼릭 링크를 생성하는 작업입니다. /etc/systemd/system/multi-user.target.wants/myapp.service -> /etc/systemd/system/myapp.service 링크가 생성됩니다. disable은 이 링크를 제거합니다.

💼
실무 맥락
현업 패턴

실제 업무에서 이 지식이 쓰이는 상황:

주니어 엔지니어가 처음 서비스를 서버에 올릴 때 가장 자주 하는 실수가 두 가지입니다. nohup java -jar app.jar &로 띄우고 끝냈거나, systemctl start만 하고 enable을 빠뜨린 경우입니다. 둘 다 재부팅 후 서비스가 꺼져 있는 결과로 이어집니다.

신규 서비스 등록 표준 절차:

로컬 터미널
# 1. unit 파일 작성
sudo vim /etc/systemd/system/myapp.service

# 2. systemd에 알림
sudo systemctl daemon-reload

# 3. 활성화 + 즉시 기동
sudo systemctl enable --now myapp

# 4. 상태 확인
sudo systemctl status myapp

# 5. 로그 확인 (기동 직후 에러 없는지)
sudo journalctl -u myapp -n 20 --no-pager

기동 전 10분 점검 루틴:

서비스를 처음 등록하거나 설정을 변경한 뒤에는 이 순서로 확인합니다.

로컬 터미널
# ExecStart 경로 존재 확인
ls -la /opt/app/myapp.jar

# 실행 유저 확인 (unit 파일의 User=와 일치해야 함)
id appuser

# 포트 충돌 확인 (서비스가 사용할 포트)
sudo ss -tlnp | grep :8080

# 의존 서비스 상태 확인 (DB 등)
sudo systemctl status postgresql

현업에서는 배포 스크립트에 이 절차가 포함되는 경우가 많습니다. unit 파일을 배포 아티팩트와 함께 관리하고, 배포 시 daemon-reload → enable → restart → status 확인 순서를 자동화합니다. 다음 모듈에서는 이 서비스를 실제로 변경하는 운영 작업계획서 작성 방법을 다룹니다.

지식 확인

퀴즈 — 4문제

Q1

systemd Restart=always와 Restart=on-failure의 차이로 올바른 것은?

Q2

systemctl enable과 systemctl start의 차이로 올바른 것은?

Q3

After=network.target 설정이 필요한 이유는?

Q4

서비스 기동 실패 시 원인을 확인하는 올바른 절차는?

0 / 4 답변

🧪 실습으로 확인하기

Nginx 설치 및 기동

초급

Linux 서버에 Nginx를 설치하고 systemd 서비스로 등록하여 80포트에서 응답하는 상태까지 만든다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

infra-ops중급 · 60
[Infra Ops] 파일/설정/DB/인증서 롤백 절차와 판단 기준
인프라 서비스 운영 트랙 계속
linux입문 · 30
[Linux] 개발자가 왜 리눅스 서버와 커맨드라인을 반드시 배워야 하는가
Linux 트랙 시작점