마루옵스서버 · 네트워크 · AI 기술 노트

소개문의

systemd 사용법: 서비스 생성·수정·삭제와 journalctl 로그 조회

노트북과 외부 모니터를 함께 사용하는 작업 공간

참고 사진 · 노트북과 외부 모니터를 함께 사용하는 작업 공간 · 사진: SuperBlobMonster / 원본 / CC BY-SA 3.0 · 목록용 크기 조정

터미널에서 실행한 프로그램을 SSH 접속이 끊긴 뒤에도 운영하려면 실행 주체와 재시작·로그 정책을 함께 정해야 합니다. systemd 서비스로 등록하면 시작 명령, 실행 계정, 실패했을 때의 처리와 부팅 시 동작을 한곳에서 관리할 수 있어요.

여기서는 외부에 포트를 열지 않고 주기적으로 로그만 남기는 maru-heartbeat.service를 만듭니다. 대상은 Rocky Linux 9, Ubuntu 24.04·26.04의 시스템 서비스입니다. 사용자 세션의 systemctl --user와는 관리 범위가 다릅니다.

함께 보는 Linux 운영 글 · Rocky 9 고정 IP · Ubuntu 24.04 Netplan · Ubuntu 26.04·nmtui · systemd 서비스·로그 · LVM 루트 확장 · 일반 파티션 루트 확장

이 글의 목차

1. start와 enable은 서로 다른 설정이다

명령하는 일기억할 점
start / stop지금 프로세스를 시작·중지부팅 시 자동 시작 설정과는 별개
enable / disable부팅 등의 의존 관계에 등록·해제–now를 붙이지 않으면 현재 실행 상태를 바꾸지 않음
restart중지한 뒤 다시 시작짧은 중단이 생길 수 있음
reload앱에 설정 다시 읽기를 요청서비스가 reload를 지원해야 함
daemon-reloadsystemd가 unit 정의를 다시 읽음실행 중인 앱이 자동 재시작되는 명령은 아님

disable은 실행 금지 기능이 아닙니다. 수동 시작이나 다른 unit의 의존 관계로 다시 실행될 수 있어요. 시작 자체를 막는 mask는 영향 범위가 더 크므로 목적을 확인하고 사용합니다. 자세한 동작은 systemctl 매뉴얼을 기준으로 확인할 수 있습니다.

systemctl --version
systemctl list-units --type=service --state=running
systemctl list-unit-files --type=service
systemctl --failed

2. 실행 계정과 프로그램을 준비한다

아래 이름이 이미 있는지 먼저 확인합니다. 같은 계정이나 서비스가 이미 있다면 다른 실습 이름을 정하고 이후 경로도 함께 바꿉니다. 계정과 실행 파일은 관리자만 변경하고, 서비스는 권한이 제한된 계정으로 실행합니다.

getent passwd maruops-demo
systemctl cat maru-heartbeat.service

아직 만들지 않은 상태라면 위 명령에서 계정·unit을 찾지 못하는 것이 예상됩니다. 새로 생성하는 경우에만 다음을 진행합니다.

sudo useradd --system --user-group --no-create-home \
  --shell /usr/sbin/nologin maruops-demo
sudoedit /usr/local/bin/maru-heartbeat
#!/usr/bin/bash
set -eu

interval="${HEARTBEAT_INTERVAL:-15}"
case "$interval" in
  ''|*[!0-9]*)
    printf 'HEARTBEAT_INTERVAL must be a positive integer\n' >&2
    exit 2
    ;;
esac
if [ "$interval" -lt 1 ]; then
  printf 'HEARTBEAT_INTERVAL must be at least 1\n' >&2
  exit 2
fi

trap 'printf "heartbeat stopping\n"; exit 0' TERM INT
while :; do
  printf 'heartbeat time=%s interval=%s\n' "$(date --iso-8601=seconds)" "$interval"
  sleep "$interval" &
  wait "$!"
done

스크립트는 표준 출력에 시간을 남기고, 종료 신호가 오면 정상 종료합니다. &는 sleep을 기다리는 동안 Bash가 신호를 처리하도록 사용했습니다. 실제 서비스의 주 프로세스를 백그라운드로 띄워 systemd를 빠져나가는 구성과는 구분합니다.

sudo chown root:root /usr/local/bin/maru-heartbeat
sudo chmod 755 /usr/local/bin/maru-heartbeat
sudo bash -n /usr/local/bin/maru-heartbeat
sudoedit /etc/maru-heartbeat.env
HEARTBEAT_INTERVAL=15
sudo chown root:root /etc/maru-heartbeat.env
sudo chmod 644 /etc/maru-heartbeat.env

이 환경 파일에는 공개해도 되는 간격 값만 넣었습니다. EnvironmentFile은 일반 셸 스크립트처럼 source하는 파일이 아니므로 export나 임의 셸 명령을 적지 않습니다. 비밀번호·토큰 같은 비밀은 환경 변수만으로 안전하게 감춰진다고 가정하지 말고 애플리케이션의 비밀 관리 방식을 따릅니다. systemd.exec의 환경 설정 설명을 참고하세요.

3. unit 파일을 작성하고 등록한다

sudoedit /etc/systemd/system/maru-heartbeat.service
[Unit]
Description=MaruOps heartbeat demonstration
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=exec
User=maruops-demo
Group=maruops-demo
EnvironmentFile=/etc/maru-heartbeat.env
ExecStart=/usr/local/bin/maru-heartbeat
Restart=on-failure
RestartSec=3s
TimeoutStopSec=10s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=maru-heartbeat
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target
설정선택한 이유
Type=exec지정 실행 파일을 실행하는 단계의 실패를 시작 오류로 다루기 위해 사용
User / Group서비스에 불필요한 root 권한을 주지 않음
Restart=on-failure비정상 종료 시 재시작. 관리자의 stop은 자동 재시작 대상이 아님
StartLimitIntervalSec / Burst짧은 시간의 무한 재시작을 제한
StandardOutput / Error앱 출력을 journal에서 조회
ProtectSystem / ProtectHome이 로그 전용 데모가 파일시스템·홈을 수정할 필요가 없어 접근을 제한

Type=exec로 시작되었다고 앱의 API나 DB 연결까지 준비됐다는 뜻은 아닙니다. 준비 완료를 정교하게 통지하려면 프로그램이 지원하는 notify 등의 방식을 검토합니다. 파일을 쓰는 실제 앱에서는 StateDirectory나 쓰기 허용 경로를 설계해야 하므로 이 데모의 보안 설정을 그대로 복사해 적용하지 않습니다. 서비스 유형·재시작 정책과 unit 공통 설정이 기준 문서입니다.

이 프로그램은 외부 네트워크를 사용하지 않으므로 network-online.target 의존성을 넣지 않았습니다. After는 시작 순서이고 상대 unit을 끌어오는 기능은 아니며, 네트워크 온라인 판정도 특정 DB·인터넷 서비스의 응답을 보장하지 않습니다.

sudo chmod 644 /etc/systemd/system/maru-heartbeat.service
sudo systemd-analyze verify /etc/systemd/system/maru-heartbeat.service
sudo systemctl daemon-reload
sudo systemctl enable --now maru-heartbeat.service
systemctl is-enabled maru-heartbeat.service
systemctl is-active maru-heartbeat.service
sudo systemctl status maru-heartbeat.service --no-pager

Rocky에서 실행 권한이 맞는데도 거부되면 SELinux 컨텍스트를 확인합니다. 기본 경로에 맞게 sudo restorecon -v /usr/local/bin/maru-heartbeat로 복원할 수 있습니다. 거부 원인을 확인하지 않고 SELinux를 끄는 방식으로 해결하지 않습니다.

4. 변경한 대상에 따라 반영 방법을 고른다

변경한 것반영 순서
앱 스크립트·환경 파일 내용해당 서비스 restart
unit 본문·drop-indaemon-reload 후 해당 서비스 restart
앱 자체의 설정앱이 reload를 지원하면 reload, 아니면 restart
부팅 시 실행 여부enable / disable; 지금도 바꾸려면 –now

로그 간격을 바꾸려면 /etc/maru-heartbeat.env의 값을 5로 바꾸고 재시작합니다. unit의 경로나 정의가 바뀐 것이 아니므로 이 경우 daemon-reload는 필요하지 않습니다. 이 데모에는 reload 동작을 구현하지 않았습니다.

sudoedit /etc/maru-heartbeat.env
sudo systemctl restart maru-heartbeat.service
sudo journalctl -u maru-heartbeat.service -n 20 --no-pager

drop-in으로 일부 설정을 덮어쓴다

배포판이나 패키지가 제공하는 unit을 바꿀 때는 원본을 직접 덮어쓰기보다 drop-in을 씁니다. 데모에서도 같은 방법을 연습할 수 있어요. 아래 명령의 편집 영역에 다음 두 줄을 넣고 저장합니다. 편집기에 안내된 “저장되는 영역” 밖에 적으면 반영되지 않을 수 있습니다.

sudo systemctl edit maru-heartbeat.service
[Service]
RestartSec=10s
sudo systemctl daemon-reload
sudo systemctl restart maru-heartbeat.service
systemctl cat maru-heartbeat.service
systemctl show maru-heartbeat.service \
  -p FragmentPath -p DropInPaths -p RestartUSec

일반적인 저장 경로는 /etc/systemd/system/maru-heartbeat.service.d/override.conf입니다. ExecStart를 drop-in에서 바꿀 때는 기존 목록을 비우는 ExecStart= 줄을 먼저 두고 새 ExecStart를 지정해야 합니다. 직접 만든 데모 unit도 삭제 전에 cat·FragmentPath로 실제 경로를 다시 확인합니다.

5. journalctl에서 필요한 로그만 본다

sudo journalctl -u maru-heartbeat.service -n 50 --no-pager
sudo journalctl -u maru-heartbeat.service -f

-f는 새 로그를 계속 기다립니다. Ctrl+C는 로그 조회를 끝내며 서비스는 계속 실행됩니다. 다음 명령은 각각 필요할 때 따로 실행합니다.

sudo journalctl -u maru-heartbeat.service --since today --no-pager
sudo journalctl -u maru-heartbeat.service --since '1 hour ago' -o short-iso --no-pager
sudo journalctl -u maru-heartbeat.service -b --no-pager
sudo journalctl --list-boots
sudo journalctl -u maru-heartbeat.service -b -1 --no-pager
sudo journalctl -k -b -p warning --no-pager

-b -1은 보관된 이전 부팅의 로그를 읽습니다. 이전 로그가 남아 있지 않으면 조회할 수 없습니다. -p warning은 journal에 기록된 심각도로 필터링하므로 앱의 출력 문장에 ERROR라고 적힌 것만으로 오류 등급이 되는 것은 아닙니다. journalctl 매뉴얼에서 필터의 의미를 확인할 수 있습니다.

로그를 재부팅 뒤에도 보관하려면

sudo journalctl --disk-usage
sudo install -d -m 755 /etc/systemd/journald.conf.d
sudoedit /etc/systemd/journald.conf.d/60-maruops-retention.conf
[Journal]
Storage=persistent
SystemMaxUse=200M
SystemKeepFree=1G
MaxRetentionSec=14day
sudo systemctl restart systemd-journald.service
sudo journalctl --flush
sudo journalctl --disk-usage

이 설정은 데모 한 개가 아니라 시스템 journal 전체에 적용되는 예시입니다. 용량과 기간은 운영 정책에 맞춰 바꿉니다. 설정된 기간보다 빨리 용량 한계에 닿으면 더 오래 보관된 로그가 먼저 정리될 수 있어요. 기본 Storage 동작은 배포판 설정도 확인합니다. journald 저장·용량 제한 문서를 기준으로 현재 보존 요구와 디스크 여유를 함께 판단합니다.

기존 로그를 정리해야 한다면 필요한 기록을 외부에 보관한 뒤 sudo journalctl --rotate --vacuum-time=14d처럼 정책에 맞춰 실행합니다. 이 작업은 오래된 아카이브 journal을 삭제하므로 되돌릴 수 없습니다. 서비스 하나만의 로그를 골라 지우는 명령도 아닙니다.

6. 실패 상태에서 볼 곳

sudo systemctl status maru-heartbeat.service --no-pager -l
sudo journalctl -u maru-heartbeat.service -b -n 100 --no-pager
systemctl show maru-heartbeat.service \
  -p Result -p ExecMainStatus -p NRestarts
표시먼저 확인할 항목
203/EXEC실행 경로·권한·shebang·SELinux·줄바꿈 형식
217/USERUser·Group 계정이 존재하는지
200/CHDIRWorkingDirectory 경로와 접근 권한
start-limit-hit반복 실패 원인과 재시작 횟수 제한

원인을 고친 뒤 시작 제한에 걸려 있으면 sudo systemctl reset-failed maru-heartbeat.service로 해당 unit의 실패 상태·카운터를 초기화하고 다시 start합니다. reset-failed가 실행 파일이나 잘못된 설정을 고치는 것은 아닙니다.

7. 직접 만든 서비스를 삭제한다

다음은 이 글에서 직접 만든 파일만 제거하는 절차입니다. 패키지가 설치한 서비스는 패키지의 제거·중지 정책을 따릅니다. 파일을 지우기 전에 실행과 자동 시작을 먼저 멈춥니다.

sudo systemctl disable --now maru-heartbeat.service
sudo rm -f /etc/systemd/system/maru-heartbeat.service.d/override.conf
sudo rmdir --ignore-fail-on-non-empty /etc/systemd/system/maru-heartbeat.service.d
sudo rm -f /etc/systemd/system/maru-heartbeat.service
sudo systemctl daemon-reload
systemctl list-unit-files maru-heartbeat.service

drop-in을 만들지 않았다면 해당 디렉터리가 없어 rmdir이 오류를 출력할 수 있습니다. 다른 파일이 들어 있는 디렉터리는 강제로 통째로 지우지 않습니다. systemctl status에서는 unit을 찾지 못하는 것이 예상됩니다. unit 파일을 삭제해도 과거 journal이나 앱 데이터는 자동 삭제되지 않습니다.

이 데모만을 위해 만든 실행 파일·환경 파일은 필요 없을 때 각각 제거합니다. 계정까지 지우려면 다른 프로세스·파일이 그 계정을 사용하지 않는지 확인합니다. 실제 애플리케이션은 실행 파일, 설정, 데이터, 로그의 수명이 서로 다르므로 서비스 등록 해제와 데이터 삭제를 구분해 두는 편이 좋습니다.

확인 범위 · 2026-10-18: unit 구문은 Rocky Linux 9.8의 systemd 252와 Ubuntu 26.04.1의 systemd 259에서 확인했습니다. 서비스 생성·실행, 환경 파일·drop-in 반영, 비정상 종료 뒤 자동 재시작, journal 조회·영구 저장은 Ubuntu 24.04.5 VM의 systemd 255에서 실행했습니다. 데모의 권한 제한이 모든 앱의 파일 접근 요구에 맞는다는 뜻은 아닙니다.

네트워크·systemd 예제 파일 내려받기 · Netplan YAML, 서비스·환경 파일과 스토리지 구조를 읽는 스크립트를 묶었습니다. 주소와 장치 이름은 실제 환경에 맞춰 바꿉니다. 디스크를 자동 확장하는 스크립트는 포함하지 않습니다.

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

닉네임·이메일·댓글 내용·IP 주소·브라우저 정보는 댓글 게시 및 관리에 사용됩니다. 이메일과 접속 정보는 공개 댓글에 표시하지 않습니다. 개인정보 요청은 운영자 이메일로 보내 주세요. 개인정보처리방침