서비스가 멈췄을 때 운영자가 먼저 확인할 기준

리눅스 서버에서 웹 애플리케이션, 배치 작업, API 서버를 운영할 때 프로세스가 예고 없이 종료되는 일은 피하기 어렵습니다. 메모리 부족으로 강제 종료되거나, 외부 API 오류가 누적되거나, 배포 후 설정값이 빠지는 상황도 생깁니다. 이때 단순히 터미널에서 실행 명령을 다시 입력하는 방식은 재현성과 책임 범위가 불분명합니다. systemd 서비스로 등록하면 실행 사용자, 환경 변수, 재시작 정책, 시작 순서, 로그 확인 경로를 서버 설정으로 고정할 수 있습니다.

운영의 목표는 모든 오류를 즉시 없애는 것이 아니라, 오류가 발생했을 때 서비스가 안전하게 복구되고 원인을 빠르게 추적할 수 있게 만드는 것입니다. 자동 재시작은 일시적인 프로세스 종료에 효과적이지만, 설정 오류나 데이터 손상까지 숨기면 안 됩니다. 따라서 재시작 횟수 제한과 로그 확인 절차를 함께 설계해야 합니다.

서비스 유닛 파일을 최소한의 운영 설정으로 작성하기

다음 예시는 Node.js API 서버를 systemd로 실행하는 기본 형태입니다. Java, Python, Go 서비스도 ExecStart만 각 런타임 실행 명령으로 바꾸면 같은 원칙을 적용할 수 있습니다. 유닛 파일은 보통 /etc/systemd/system 아래에 두고, 애플리케이션 전용 계정으로 실행합니다. root 계정으로 장기 실행하는 서비스는 파일 권한과 외부 침해 사고의 영향 범위를 크게 만듭니다.

[Unit]
Description=Example API Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=appsvc
Group=appsvc
WorkingDirectory=/opt/example-api
EnvironmentFile=/etc/example-api/example-api.env
ExecStart=/usr/bin/node /opt/example-api/server.js
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=300
StartLimitBurst=5
TimeoutStopSec=30
KillSignal=SIGTERM
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

After=network-online.target만으로 네트워크가 완전히 준비되었다고 단정할 수는 없지만, 부팅 직후 외부 DB나 API에 연결하는 서비스가 너무 빨리 시작되는 문제를 줄입니다. EnvironmentFile에는 비밀값을 직접 유닛 파일에 쓰지 않고 별도 권한 파일로 분리합니다. 해당 파일은 appsvc가 읽을 수 있어야 하며, 일반 사용자에게 노출되지 않도록 권한을 제한합니다.

Restart=always는 정상 종료까지 계속 다시 실행할 수 있어 배치성 작업이나 의도적인 종료 흐름에 맞지 않을 수 있습니다. 일반적인 상시 API 서버는 Restart=on-failure가 시작점으로 적합합니다. StartLimitIntervalSec와 StartLimitBurst는 짧은 시간에 반복 재시작하는 장애 루프를 막습니다. 제한에 걸렸다면 무작정 재시작하기보다 journal 로그와 애플리케이션 로그에서 최초 종료 원인을 확인해야 합니다.

반영, 기동, 상태 확인 명령

유닛 파일을 새로 만들거나 수정한 뒤에는 systemd가 설정을 다시 읽도록 daemon-reload가 필요합니다. 서비스 상태는 active 여부만 보지 말고 최근 로그와 마지막 종료 코드를 함께 확인합니다. 배포 자동화에서도 아래 명령의 종료 코드를 검사하면 설정 오류를 조기에 발견할 수 있습니다.

sudo systemctl daemon-reload
sudo systemctl enable --now example-api.service
sudo systemctl status example-api.service --no-pager
sudo journalctl -u example-api.service -n 100 --no-pager
sudo journalctl -u example-api.service -f

enable은 서버 재부팅 뒤에도 서비스를 시작하도록 연결을 만듭니다. 배포 중 코드만 바뀐 경우에는 restart보다 reload를 지원하는지 먼저 확인합니다. Node.js처럼 프로세스 자체를 재기동해야 하는 서비스라면 restart가 일반적이지만, 종료 신호를 받고 진행 중 요청을 정리할 시간을 줘야 합니다. TimeoutStopSec과 애플리케이션의 SIGTERM 처리 시간을 함께 점검하면 배포 중 요청 손실을 줄일 수 있습니다.

장애가 반복될 때 보는 순서

  • systemctl status에서 Main PID, exit code, 재시작 횟수를 확인합니다.
  • journalctl -u로 종료 직전의 표준 출력과 표준 오류를 확인합니다.
  • OOM 가능성이 있으면 journalctl -k 및 dmesg에서 Out of memory, Killed process 기록을 찾습니다.
  • 환경 변수 파일의 경로, 소유자, 권한과 WorkingDirectory의 실제 배포 경로를 대조합니다.
  • 포트 충돌은 ss -ltnp로 확인하고, 외부 의존성 장애는 timeout과 재시도 로그를 별도로 남깁니다.

자동 재시작 횟수가 계속 늘어나는 상태는 정상 운영이 아닙니다. 특히 데이터베이스 마이그레이션 실패, 필수 환경 변수 누락, 인증서 만료 같은 문제는 프로세스를 반복 실행해도 해결되지 않습니다. 장애 알림에는 서비스 이름, 호스트, 마지막 종료 코드, 최근 로그 일부, 재시작 횟수를 포함해 담당자가 바로 판단할 수 있게 구성하는 편이 좋습니다.

운영 체크리스트

  • 서비스는 root가 아닌 전용 계정으로 실행한다.
  • 환경 변수와 비밀값은 유닛 파일과 분리하고 파일 권한을 제한한다.
  • on-failure 재시작과 재시작 횟수 제한을 함께 설정한다.
  • 배포 후 status와 최근 journal 로그를 자동 검증한다.
  • 반복 장애는 재시작보다 종료 원인과 시스템 자원 로그를 먼저 확인한다.

systemd 설정은 단순한 자동 실행 기능이 아니라 서비스의 복구 정책과 운영 절차를 코드처럼 관리하는 기반입니다. 유닛 파일을 애플리케이션 저장소 또는 인프라 설정 저장소에서 함께 버전 관리하면 서버마다 다른 실행 방식이 생기는 문제도 줄일 수 있습니다.