systemd란 무엇이며 어떻게 동작하고 리눅스 서비스를 관리하는지
systemd는 리눅스의 초기화 시스템이자 서비스 관리자입니다. 커널이 systemd를 첫 번째 프로세스(PID 1)로 실행하고, systemd는 나머지 서비스를 올바른 순서로 시작하고 감시합니다. systemd의 모든 작업은 서비스, 타이머, 마운트 지점 등의 텍스트 정의인 유닛(unit)으로 이루어집니다. systemctl로 유닛을 시작, 중지하고 자동 시작에 등록하며, journalctl로 로그를 봅니다.
systemd는 Debian, Ubuntu, Fedora, RHEL 등 대부분의 현대 리눅스 배포판에서 쓰는 초기화 시스템이자 서비스 관리자입니다. 부팅 후 커널은 systemd를 PID 1번 첫 프로세스로 실행하고, systemd가 네트워크를 올리고, 디스크를 마운트하고, 웹 서버와 데이터베이스 등 나머지 모든 것을 시작합니다. 간단히 말해 systemd는 «어떤 서비스가 돌아야 하는지, 어떤 순서로 시작할지, 서비스가 죽으면 무엇을 할지 아는 관리자»입니다.
systemd를 쓰고 있는지 확인하는 것은 간단합니다:
$ ps -p 1 -o comm=
systemd
systemd가 필요해진 이유
리눅스는 예전에 SysV init을 썼습니다. /etc/init.d/에 있는 셸 스크립트들이 차례로 실행되는 방식이었습니다. 이해하기는 쉬웠지만 느리고 불안정했습니다. 각 스크립트가 자기 PID 파일을 직접 쓰고, 프로세스가 살아 있는지 스스로 판단해야 했으며, 죽은 서비스를 다시 띄우는 장치도 따로 붙여야 했습니다.
systemd는 이 일을 중앙에서 처리합니다:
- 병렬 시작. 서로 의존하지 않는 서비스는 동시에 시작되어 부팅이 빨라집니다.
- 의존성과 순서. 서비스 정의에 무엇이 필요한지(예: 네트워크)와 언제 시작해야 하는지가 명시됩니다.
- 감시. systemd는 cgroups로 서비스의 모든 프로세스를 알고, 죽으면 다시 시작할 수 있습니다.
- 통합 로그. 모든 서비스의 출력을 journald가 모읍니다. 로그는 명령 하나로 봅니다.
- 통일된 인터페이스. 모든 서비스를 같은 방식으로 다룹니다:
systemctl start|stop|restart|status.
서비스 관리자 말고도 systemd 프로젝트에는 여러 구성 요소가 있습니다: journald(로그), logind(사용자 세션), timesyncd(시간 동기화), resolved(DNS), networkd(네트워크). 모든 배포판이 이것들을 다 쓰지는 않지만, PID 1로서의 systemd와 journald는 거의 어디에나 있습니다.
유닛: systemd의 구성 단위
systemd가 관리하는 모든 것은 유닛(unit)으로 정의됩니다. 유닛은 INI 형식의 텍스트 파일이며, 종류는 확장자로 구분합니다.
| ead> | ||
|---|---|---|
| 종류 | 확장자 | 정의하는 것 |
| service | .service | 서비스: 데몬 프로세스 또는 일회성 작업 |
| timer | .timer | 다른 유닛을 일정에 따라 실행 (cron 대체) |
| socket | .socket | 연결이 들어올 때 서비스를 시작하는 소켓 |
| target | .target | 유닛의 묶음, 즉 «시스템 상태» (예: multi-user.target) |
| mount | .mount | 마운트 지점 |
| path | .path | 파일이나 디렉터리 감시 |
유닛 파일이 있는 곳
유닛 파일은 여러 디렉터리에 있으며, 우선순위가 정해져 있습니다:
/usr/lib/systemd/system/(Debian과 Ubuntu에는/lib/systemd/system/도 있음) — 패키지가 설치한 유닛. 직접 고치지 마세요. 패키지가 업데이트되면 변경 사항이 덮어써집니다./etc/systemd/system/— 관리자의 유닛과 재정의. 우선순위가 가장 높습니다./run/systemd/system/— 실행 중에 만들어진 임시 유닛.
재정의까지 모두 반영된 유닛의 최종 내용을 보려면 systemctl cat nginx.service를 사용하세요.
런레벨 대신 target
예전의 런레벨은 target으로 대체되었습니다. multi-user.target은 그래픽 없는 일반 서버 모드, graphical.target은 그래픽 화면이 있는 모드, rescue.target은 단일 사용자 복구 모드입니다. 현재 기본 target은 systemctl get-default로 보고, systemctl set-default multi-user.target으로 바꿉니다.
systemctl: 서비스 관리
systemctl은 systemd의 기본 도구입니다. 대부분의 명령은 시스템 상태를 바꾸므로 sudo가 필요합니다.
| ead> | |
|---|---|
| 명령어 | 기능 |
systemctl status nginx | 서비스 상태, PID, 메모리, 로그 마지막 줄 |
systemctl start nginx | 지금 시작 |
systemctl stop nginx | 중지 |
systemctl restart nginx | 재시작 (stop + start) |
systemctl reload nginx | 중지 없이 설정 다시 읽기 (서비스가 지원하는 경우) |
systemctl enable nginx | 자동 시작에 등록 |
systemctl disable nginx | 자동 시작에서 해제 |
systemctl enable --now nginx | 자동 시작 등록과 즉시 시작 |
systemctl is-active nginx | active/inactive 출력 — 스크립트에서 편리 |
systemctl list-units --type=service | 로드된 서비스 목록 |
systemctl --failed | 실패한 유닛 목록 |
systemctl daemon-reload | 유닛 파일을 고친 뒤 다시 읽기 |
systemctl status 읽는 법
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-10-12 09:14:03 UTC; 2h 5min ago
Docs: man:nginx(8)
Main PID: 812 (nginx)
Tasks: 3 (limit: 2307)
Memory: 6.1M
CPU: 154ms
CGroup: /system.slice/nginx.service
├─812 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─813 "nginx: worker process"
└─814 "nginx: worker process"
Loaded 줄은 유닛 파일이 어디서 왔고 자동 시작이 켜져 있는지(enabled)를 보여 줍니다. Active 줄은 현재 상태입니다: active (running), inactive (dead), failed 중 하나. CGroup 블록은 서비스의 모든 프로세스를 보여 줍니다. systemd는 이렇게 어떤 프로세스가 어느 서비스에 속하는지 압니다.
enable과 start의 차이
자주 헷갈리는 부분입니다. start는 서비스를 지금 실행하지만, 재부팅하면 저절로 올라오지 않습니다. enable은 target 디렉터리에 심볼릭 링크를 만들어(예: /etc/systemd/system/multi-user.target.wants/), 부팅할 때 서비스가 시작되게 하지만, 지금 당장 실행하지는 않습니다. 둘 다 필요하면 enable --now를 쓰세요.
세 번째 상태는 mask입니다. systemctl mask 서비스는 유닛을 /dev/null에 연결하여, 수동으로도 의존성으로도 시작할 수 없게 만듭니다. 서비스를 확실히 꺼 두어야 할 때 씁니다.
journalctl: 서비스 로그
서비스가 stdout과 stderr로 출력하는 모든 내용은 systemd-journald가 잡아서, 유닛 이름, PID, 우선순위, 시간 정보와 함께 바이너리 저널에 저장합니다. 저널은 journalctl로 읽습니다.
journalctl -u nginx # 서비스 하나의 로그
journalctl -u nginx -f # 실시간으로 보기, tail -f처럼
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx -n 50 # 마지막 50줄
journalctl -b # 현재 부팅 이후
journalctl -b -1 # 이전 부팅
journalctl -p err -b # 오류 이상만
journalctl -k # 커널 메시지
journalctl --disk-usage # 저널이 차지하는 공간
-p 옵션은 syslog 우선순위로 거릅니다: emerg, alert, crit, err, warning, notice, info, debug. 지정한 수준과 그보다 심각한 것이 모두 표시됩니다.
재부팅 후의 로그
/var/log/journal 디렉터리가 없고 /etc/systemd/journald.conf에 Storage=auto가 설정되어 있으면, 저널은 메모리(/run/log/journal)에만 있고 재부팅하면 사라집니다. 영구 보관하려면 Storage=persistent로 설정하거나 디렉터리를 만들고 journald를 재시작하세요. 크기 제한은 SystemMaxUse=로 정하며, 오래된 기록은 journalctl --vacuum-time=2weeks 또는 --vacuum-size=500M으로 정리합니다.
journald는 보통 rsyslog와 함께 동작합니다. journald가 메시지를 rsyslog에 넘기면, rsyslog는 전통적인 텍스트 로그를 /var/log/에 쓰고 로그를 중앙 서버로 보낼 수 있습니다.
내 서비스를 자동 시작하기: unit 파일 예제
계속 살아 있어야 하고, 부팅 때 올라오고, 죽으면 다시 시작해야 하는 애플리케이션이 있다고 해 봅시다. 스크립트든 바이너리든 상관없습니다. /etc/systemd/system/myapp.service 파일을 만드세요:
[Unit]
Description=My web application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/opt/myapp/bin/server --port 8080
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
각 섹션을 살펴봅시다:
- [Unit] — 설명과 의존성.
After=는 순서만 정하고,Wants=는 느슨한 의존성입니다(유닛을 함께 가져오되, 그것이 올라오지 않아도 실패하지 않음). 강한 의존성은Requires=입니다. - [Service] — 실행 방법.
Type=simple은ExecStart의 프로세스가 곧 서비스이며 백그라운드로 가지 않는다는 뜻입니다.User=는 권한 없는 사용자로 실행합니다.Restart=on-failure는 오류로 끝난 서비스를RestartSec초 뒤에 다시 시작합니다. - [Install] —
enable이 무엇을 하는지.WantedBy=multi-user.target은 «일반 부팅 때 실행»을 뜻합니다.
적용하려면:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin myapp
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
journalctl -u myapp -f
unit 파일에서 자주 하는 실수
- ExecStart에 상대 경로. 프로그램의 경로는 절대 경로여야 합니다. systemd는 셸처럼 사용자의 PATH에서 명령을 찾지 않습니다.
- daemon-reload를 잊음. 파일을 고친 뒤에도 systemd는 이전 버전을 계속 씁니다.
status에 경고가 표시됩니다. - 데몬이 백그라운드로 감. 프로그램이 스스로 fork하는데
Type=simple로 지정하면, systemd는 서비스가 끝났다고 판단합니다. 프로그램을 포그라운드 모드로 실행하거나,PIDFile=과 함께Type=forking을 쓰세요. - 프로필의 환경 변수. 서비스는
~/.bashrc나/etc/profile을 읽지 않습니다. 변수는Environment=나EnvironmentFile=로 주세요.
패키지 유닛을 건드리지 않고 바꾸기
패키지가 설치한 서비스의 설정을 파일을 직접 고치지 않고 바꾸려면 drop-in을 씁니다:
sudo systemctl edit nginx
편집기가 열리고, 변경 내용은 /etc/systemd/system/nginx.service.d/override.conf에 저장됩니다. 예를 들어 [Service] 아래에 Restart=always를 적을 수 있습니다. 편집기를 닫으면 systemd가 설정을 자동으로 다시 읽습니다.
cron 대신 systemd 타이머
타이머는 같은 이름의 서비스를 일정에 따라 실행하는 유닛입니다. cron에 비해 작업 로그가 저널에 남고, 의존성을 줄 수 있으며, 놓친 실행을 보충할 수 있습니다(Persistent=true). 매일 백업하는 예:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers
OnCalendar 값이 맞는지 확인하려면 systemd-analyze calendar "*-*-* 03:00:00"을 실행하세요. 다음 실행 시각을 보여 줍니다.
부팅 분석
systemd는 각 유닛이 시작된 시간을 기록하므로, 느린 부팅의 원인을 쉽게 찾을 수 있습니다:
systemd-analyze # 전체 부팅 시간
systemd-analyze blame # 시작 시간 순으로 유닛 나열
systemd-analyze critical-chain # 부팅을 지연시킨 체인
systemd-analyze verify /etc/systemd/system/myapp.service # unit 파일 검사
마무리
systemd는 현대 리눅스의 PID 1이자 서비스 관리자입니다. 모든 것을 유닛으로 정의하고, systemctl로 관리하며, 로그는 journalctl로 읽는 저널에 모으고, cron 대신 타이머로 작업을 실행할 수 있습니다. 능숙하게 쓰려면 status, enable --now, journalctl -u를 익히고, Restart=on-failure가 들어간 간단한 unit 파일을 쓸 줄 알면 충분합니다. systemd, journald, 로깅은 강의에서 실습과 함께 다룹니다.
자주 묻는 질문
내 시스템에서 systemd를 쓰는지 어떻게 확인하나요?
<code>ps -p 1 -o comm=</code>을 실행하세요. 출력이 systemd이면 systemd로 동작하는 것입니다. 또한 <code>/run/systemd/system</code> 디렉터리는 systemd로 부팅했을 때만 존재합니다.
systemctl restart와 reload의 차이는 무엇인가요?
restart는 서비스를 완전히 멈췄다가 다시 시작하므로 연결이 끊어집니다. reload는 서비스에게 멈추지 않고 설정을 다시 읽으라고 요청합니다. 다만 유닛에 <code>ExecReload</code>가 정의되어 있어야 동작합니다.
sudo 없이 일반 사용자가 systemd 서비스를 돌릴 수 있나요?
네, systemd에는 사용자 인스턴스가 있습니다. 유닛을 <code>~/.config/systemd/user/</code>에 두고 <code>systemctl --user</code>로 관리하세요. 로그인 세션이 없을 때도 돌게 하려면 <code>loginctl enable-linger</code>를 설정합니다.
서비스가 failed 상태인데 이를 어떻게 초기화하나요?
원인은 <code>journalctl -u 이름 -b</code>와 <code>status</code>의 Active 줄에서 찾으세요. 고친 뒤 서비스를 다시 시작하고, failed 표시는 <code>systemctl reset-failed</code>로 지울 수 있습니다.
systemd 로그는 디스크의 어디에 저장되나요?
영구 저장이면 <code>/var/log/journal/</code>에 바이너리 형식으로, 아니면 메모리의 <code>/run/log/journal/</code>에 있습니다. 텍스트 도구가 아니라 <code>journalctl</code>로 읽으세요.
이 주제는 “Linux: с нуля до уверенного администрирования” 강의에서 실습과 함께 자세히 다룹니다.
- systemd: как на самом деле запускаются сервисы в современном Linux
- Журналы и мониторинг: journald, rsyslog и sysstat