Infra Architecture/Compute Platforms
Compute Platforms - 08. 서비스 관리와 systemd
서버의 모든 서비스를 지휘하는 관리자 — systemd
서버를 부팅하면 — 커널이 로드되고, init 프로세스(PID 1)가 실행되고, 그 init이 nginx·sshd·PostgreSQL 같은 서비스들을 차례로 띄운다. foundations 02의 부팅 글에서 이 흐름을 봤다. 그때 "systemd가 PID 1이다"라고만 짚고 넘어갔다. 이 글은 그 PID 1, systemd를 깊이 파헤친다 — systemd가 서버의 모든 서비스를 어떻게 시작·중지·감시·재시작하는지. unit 파일의 구조, 의존성 그래프, 저널(journal), 타이머, cgroups 연동까지. 서버 관리자가 매일 쓰는 systemctl start nginx 뒤에서 무슨 일이 일어나는지.
systemd는 논쟁적인 소프트웨어다. 2010년 Lennart Poettering(Red Hat)이 발표했을 때 — Linux 진영의 많은 사람이 반발했다. "init는 단순해야 한다", "systemd는 너무 크다", "모든 걸 삼키는 모노리스다". 그럼에도 — systemd는 2026년 현재 모든 주요 Linux 배포판(Ubuntu·RHEL·Debian·SUSE·Rocky)의 기본 init이 됐다. 그 이유는 — 제대로 작동하기 때문이다. 부팅이 빠르고, 서비스 관리가 일관되고, 로깅·cgroups·네트워크 설정까지 통합해서 — 개별 도구를 조립하는 복잡함을 줄여 준다. 사실 논쟁은 역사로 남고, systemd는 Linux 서버 관리의 사실상 표준이다.
init의 진화 — sysvinit에서 systemd까지
systemd를 이해하려면 먼저 init의 역사를 봐야 한다. init는 커널이 부팅 후 실행하는 첫 번째 사용자 공간 프로세스(PID 1)다. 이 프로세스가 모든 다른 서비스를 시작한다.
1세대 — sysvinit(1980년대 ~ 2010년대). UNIX System V에서 시작된 전통 init. /etc/inittab에 설정을 두고, 런레벨(runlevel) 단위로 서비스를 시작했다. 런레벨 3(다중 사용자·네트워크)이면 /etc/rc3.d/ 디렉터리의 스크립트를 순차적으로 실행했다. 하나의 서비스가 느리면 그 뒤의 서비스가 전부 기다렸다. 부팅이 느렸다 — 서버 부팅에 3 ~ 5분이 걸리는 게 보통이었다. 서비스 간 의존성도 수동으로 스크립트에 하드코딩해야 했다.
2세대 — Upstart(2006 ~ 2014, Ubuntu). Canonical이 만든 이벤트 기반 init. 서비스 시작을 이벤트로 트리거하고, 병렬 실행을 지원했다. sysvinit보다 빨랐지만 — Ubuntu 이외의 배포판에서는 안 쓰였다.
3세대 — systemd(2010~, 현대). Lennart Poettering이 설계. 의존성 그래프를 분석해 서비스를 병렬로 시작한다. network-online.target이 필요한 서비스는 그 target이 완료된 뒤에 시작하고, 그렇지 않은 서비스는 바로 시작한다. socket activation(소켓 활성화)으로 서비스 시작을 더 지연시킨다. 결과 — 현대 Linux 서버는 수 초에서 수십 초 안에 부팅된다. foundations 02에서 본 systemd-analyze blame이 각 서비스의 시작 시간을 보여준다.
| init 세대 | 시대 | 부팅 방식 | 병렬성 | 전형 부팅 시간 |
|---|---|---|---|---|
| sysvinit | 1980 ~ 2010 | 런레벨별 순차 스크립트 | 없음 | 3 ~ 5분 |
| Upstart | 2006 ~ 2014 | 이벤트 기반 | 부분 | 1 ~ 2분 |
| systemd | 2010 ~ | 의존성 그래프 + 병렬 | 완전 | 수 초 ~ 수십 초 |
unit — 서비스·마운트·타이머의 단위
systemd는 모든 관리 대상을 unit(유닛) 이라는 단위로 다룬다. unit의 종류는 여러 가지다.
| unit 타입 | 확장자 | 역할 | 예 |
|---|---|---|---|
| service | .service |
프로세스(서비스) 시작·중지·감시 | nginx.service, sshd.service |
| socket | .socket |
소켓 활성화 (요청이 오면 서비스 시작) | docker.socket |
| target | .target |
unit 그룹 (목표 상태) | multi-user.target, graphical.target |
| mount | .mount |
파일시스템 마운트 | /home.mount |
| timer | .timer |
정시 실행 (cron 대체) | backup.timer |
| device | .device |
장치(udev) | /dev/sda1.device |
| swap | .swap |
swap 파티션 활성화 | /swapfile.swap |
| path | .path |
파일 경로 변화 감지 | inotify 기반 트리거 |
가장 많이 쓰는 것은 .service(서비스)다. 예시를 보자.
# /etc/systemd/system/nginx.service (요약)
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target # network.target 이후에 시작
Wants=network-online.target # network-online을 "원함" (실패해도 OK)
[Service]
Type=notify # systemd에 준비 완료를 알림
ExecStart=/usr/sbin/nginx -g "daemon off;"
ExecReload=/usr/sbin/nginx -s reload
Restart=on-failure # 실패 시 자동 재시작
RestartSec=5s # 5초 대기 후 재시작
[Install]
WantedBy=multi-user.target # multi-user.target에 포함
이 파일이 의미하는 것을 단계별로 보자.
[Unit] 섹션 — 메타데이터와 의존성. Description은 사람이 읽는 이름. After=network.target은 "network.target이 끝난 뒤에 시작하라"는 의존성. 주의 — After=는 순서만 정할 뿐, network.target을 시작하지는 않는다. network.target을 시작하려면 Wants=나 Requires=가 필요하다.
[Service] 섹션 — 실제 프로세스 실행 방법. Type=notify는 nginx가 "준비 완료"를 systemd에 알리는 형태. ExecStart는 시작 명령. Restart=on-failure는 비정상 종료 시 자동 재시작 — foundations 03에서 본 "MTTR을 줄이는" 자동 복구의 기본이다.
[Install] 섹션 — systemctl enable로 부팅 시 자동 시작하게 만드는 설정. WantedBy=multi-user.target은 "multi-user.target이 시작될 때 이 서비스도 시작하라"는 의미.
의존성 — Requires, Wants, After, Before
systemd의 의존성 모델은 두 축으로 이루어진다 — 요구(Requires/Wants) 와 순서(After/Before). 이 둘은 다르다.
- Requires= — "강한 의존성". 의존 대상이 실패하면 이 서비스도 실패. 예:
Requires=network.target— 네트워크가 안 되면 nginx도 안 띄움. - Wants= — "약한 의존성". 의존 대상이 실패해도 이 서비스는 계속. 예:
Wants=network-online.target— 네트워크가 늦게 되도 nginx는 시작. - After= — 순서만. "이 대상이 시작된 뒤에 시작하라". 의존성을 만들지 않는다.
- Before= — 반대. "이 대상이 시작되기 전에 시작하라".
이 모델이 sysvinit보다 우월한 이유는 — 의존성이 선언적이라는 것이다. 관리자가 "nginx는 네트워크 뒤에 띄워야 해"라고 쓰면, systemd가 그 의존성 그래프를 분석해서 최적의 시작 순서를 계산한다. sysvinit에서는 관리자가 수동으로 스크립트 번호(S01network, S09nginx)를 매겨야 했다.
Type — 서비스가 "준비됐다"고 알리는 방식
Type= 설정은 — systemd가 이 서비스를 "시작 완료"로 판정하는 기준이다. 이게 왜 중요한가? systemd는 병렬 시작을 위해 각 서비스의 "준비 완료"를 알아야 한다 — 완료된 서비스에 의존하는 다른 서비스를 시작할 수 있으니까.
| Type | 준비 완료 판정 | 전형 사례 |
|---|---|---|
| simple | ExecStart가 fork하면 바로 | 포어그라운드 도구 |
| forking | 프로세스가 fork하고 부모가 종료되면 | 전통 데몬 (구식) |
| notify | 서비스가 sd_notify()로 알리면 | nginx, PostgreSQL |
| oneshot | ExecStart가 종료되면 | 초기화 스크립트, 설정 적용 |
| idle | CPU가 한가할 때 | 사용자 세션 |
Type=notify가 가장 정확하다 — 서비스 자체가 "내가 준비됐어"라고 알리기 때문. systemd는 그 알림을 받은 뒤에 이 서비스에 의존하는 다른 서비스를 시작한다. Type=simple은 — ExecStart가 실행되면 바로 "준비 완료"로 간주하지만, 실제로는 서비스가 아직 초기화 중일 수 있다(예: DB가 포트를 열기 전에 nginx가 DB에 연결 시도). 그래서 정확성이 필요한 서비스는 Type=notify를 쓴다.
journal — 통합 로깅
systemd는 journald(저널 데몬)를 통해 모든 서비스의 로그를 하나의 이진 파일에 모은다. 전통 Linux에서는 서비스마다 /var/log/에 개별 로그 파일을 만들었고(messages, syslog, nginx/error.log, postgres.log), grep으로 찾아야 했다. journald는 이를 통합 — 모든 로그가 구조화된 이진 형태(binary)로 저장되고, journalctl 명령으로 조회한다.
# 전체 저널 (모든 서비스)
journalctl
# 특정 서비스만
journalctl -u nginx
# 이번 부팅의 로그만
journalctl -b
# 이전 부팅 (장애 분석 시)
journalctl -b -1
# 에러 이상만 필터
journalctl -p err
# 실시간 추적 (tail -f 같은)
journalctl -f
# 특정 시간대
journalctl --since "2026-07-21 09:00" --until "2026-07-21 12:00"
저널의 장점은 — 구조화된 데이터다. 각 로그 엔트리는 단순 텍스트가 아니라, 타임스탬프·서비스 이름·우선순위(facility/priority)·PID·메타데이터를 구조화해서 갖는다. journalctl -u nginx -p err 한 줄로 "nginx의 에러 로그만"을 볼 수 있는 건 이 구조화 덕분이다. 전통 syslog에서는 이게 안 됐다 — grep nginx /var/log/messages | grep error 식으로 텍스트 처리를 해야 했다.
단점은 — 이진 형태라 cat·grep으로 직접 못 읽는다는 것. journalctl을 써야 한다. 또한 영구 저장(재부팅 후에도 로그 유지)이 기본이 아닌 경우가 있어, /var/log/journal/ 디렉터리를 만들어 영구 저장을 명시적으로 켜야 한다.
timer — cron의 현대적 대체
systemd의 timer unit은 cron(크론)을 대체하는 정기 작업 스케줄러다. cron은 "매일 새벽 3시에 백업" 같은 정기 작업을 실행하는 전통 도구였다. timer는 cron보다 유연하다.
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily backup timer
[Timer]
OnCalendar=*-*-* 03:00:00 # 매일 새벽 3시
Persistent=true # 서버가 꺼져 있었으면 부팅 후 실행
Unit=backup.service # 실행할 서비스
[Install]
WantedBy=timers.target
timer가 cron보다 나은 점:
- 서비스로 실행 — timer가 실행하는 작업은 systemd
.service로 정의되므로, 로그가 저널에 남고, cgroups로 자원 제한을 받고,Restart=로 재시작할 수 있다. cron은 그냥 셸 스크립트를 실행하고 끝. - 정확한 시간 보장 —
Persistent=true면, 서버가 꺼져 있어서 실행 못 한 작업을 부팅 후에 실행한다. cron은 그냥 건너뛴다. - 모니터링 통합 —
systemctl status backup.timer로 마지막 실행 시간·다음 실행 시간·상태를 본다. cron은 별도 도구(systemctl status cron또는 로그 확인)가 필요.
| 특성 | cron | systemd timer |
|---|---|---|
| 정의 방식 | crontab 파일 | .timer + .service unit |
| 로깅 | 개별 파일 또는 메일 | journald 통합 |
| 자원 제한 | 없음 | cgroups로 가능 |
| 실패 시 재시도 | 없음 | service Restart=로 가능 |
| 서버 다운 시 | 건너뜀 | Persistent=true면 부팅 후 실행 |
| 모니터링 | 별도 도구 | systemctl status |
cgroups 연동 — 서비스별 자원 제한
systemd는 cgroups와 자연스럽게 통합된다. 각 서비스는 자동으로 고유한 cgroup에 배치되고, 그 cgroup을 통해 CPU·메모리·IO를 제한할 수 있다.
# nginx.service에 자원 제한 추가
[Service]
# ... 기존 설정 ...
CPUQuota=200% # CPU 2코어 (200% = 2개 코어)
MemoryMax=512M # 메모리 512MB 제한
IOWeight=500 # 디스크 IO 가중치 (1 ~ 10000, 기본 100)
이렇게 설정하면 — nginx가 CPU를 2코어 이상 쓰려 하면 스로틀되고, 메모리 512MB를 넘으면 OOM killer가 nginx 프로세스를 죽인다. foundations 05에서 본 VM의 자원 제한(vCPU·메모리 할당)과 비슷하지만 — systemd는 프로세스 단위로 더 세밀하게 제어한다. 컨테이너 런타임(containerd·Docker)도 내부적으로 systemd cgroups를 사용한다.
# 서비스의 cgroup 상태 확인
systemd-cgtop # cgroup별 CPU·메모리 사용량 top
systemctl show nginx --property=CPUQuotaPerSecUSec
systemctl show nginx --property=MemoryMax
실습 — systemd 상태 직접 확인
1. 서비스 상태
# 서비스 상태 (가장 자주 쓰는 명령)
systemctl status nginx
# 활성 서비스 목록
systemctl list-units --type=service --state=running
# 실패한 서비스
systemctl --failed
확인할 것: Active: active (running)이면 정상. failed면 원인을 journalctl -u nginx로. --failed로 장애 서비스를 한눈에.
2. 의존성 그래프
# nginx의 의존성 트리
systemctl list-dependencies nginx
# 역의존성 (nginx를 필요로 하는 서비스)
systemctl list-dependencies --reverse nginx
# 임계 경로 (부팅 시 가장 느린 서비스 체인)
systemd-analyze critical-chain
확인할 것: list-dependencies로 nginx가 무엇에 의존하는지(네트워크·파일시스템 등)를 본다. critical-chain으로 부팅 시간의 병목을 잡는다.
3. 저널 조회
# 이번 부팅의 nginx 로그
journalctl -u nginx -b
# 최근 1시간의 에러 로그 (모든 서비스)
journalctl --since "1 hour ago" -p err
# 디스크 공간 차지하는 저널 크기
journalctl --disk-usage
확인할 것: --disk-usage로 저널이 얼마나 디스크를 차지하는지. 너무 크면 journalctl --vacuum-size=500M으로 500MB로 줄인다.
4. 타이머 목록
# 활성 타이머 (cron 대체)
systemctl list-timers --all
# 특정 타이머의 다음 실행 시간
systemctl status backup.timer
확인할 것: NEXT 열(다음 실행 시간)과 LAST 열(마지막 실행 시간). PASSED가 n/a면 한 번도 안 실행됐다는 뜻.
5. 서비스의 cgroup 자원 사용량
# 모든 서비스의 cgroup별 자원 사용량
systemd-cgtop
# 특정 서비스의 cgroup 경로
cat /proc/$(pidof nginx | awk '{print $1}')/cgroup
확인할 것: systemd-cgtop에서 CPU·메모리 사용량이 높은 서비스. /proc/PID/cgroup에서 서비스가 속한 cgroup 경로(/system.slice/nginx.service)를 확인.
미검증: 위 명령은 systemd가 기본 init인 Linux 배포판(Ubuntu·RHEL·Debian·Rocky)에서 동작. WSL2도 지원되지만 일부 기능(journal 영구 저장 등)이 제한적일 수 있음.
판단 표 — 서비스 관리 도구 비교
| 기능 | sysvinit + cron | systemd |
|---|---|---|
| 서비스 시작 | /etc/init.d/nginx start |
systemctl start nginx |
| 자동 시작 | chkconfig nginx on |
systemctl enable nginx |
| 로깅 | /var/log/messages (텍스트) |
journalctl -u nginx (구조화) |
| 의존성 | 수동 스크립트 번호 | 선언적 (After/Requires/Wants) |
| 병렬 시작 | 불가 | 가능 (의존성 그래프) |
| 자원 제한 | 별도 cgroups 설정 | unit 파일에 CPUQuota= 등 |
| 정기 작업 | cron | timer unit |
| 재시작 | 별도 스크립트 (monit 등) | Restart=on-failure |
systemd는 Linux 서버의 "집사"다
systemd는 논쟁적으로 시작했지만 — 2026년엔 Linux 서버 관리의 사실상 표준이다. 서비스 시작·중지·감시·재시작, 로깅, 자원 제한, 정기 작업, 부팅 의존성 관리까지 — 이전에는 각각 다른 도구(sysvinit·syslog·cron·cgroups tools·monit)로 따로 하던 일을 하나로 통합했다. "모노리스"라는 비판도 있지만 — 통합 덕분에 관리자가 학습해야 할 도구가 줄어들고, 서비스 관리가 일관성을 갖는다.
systemd를 이해하면 다른 질문에도 답이 보인다. "왜 서비스가 부팅 시 자동으로 안 뜨나" → systemctl is-enabled nginx가 disabled여서. "왜 장애 났을 때 로그가 없나" → 저널이 영구 저장이 안 켜져 있어서(/var/log/journal/ 확인). "왜 서버가 느린가" → systemd-cgtop에서 특정 서비스가 CPU를 독점해서. 서버 관리의 기본 — systemctl status 한 줄에서부터 전체 시스템의 상태가 보인다.
다음 글에서는 systemd가 관리하는 서비스(와 컨테이너)를 어떻게 만들고 배포하는지를 다룬다 — 이미지 빌드와 Immutable Infrastructure. Packer로 golden image를 만들고, Ansible으로 설정하고, 클라우드 AMI로 배포하는 흐름. foundations 06의 IaC 철학이 구체 도구로 번역되는 글이다.
참고
- Poettering, L., Sievers, K. "systemd for Administrators", 2010 — systemd 발표와 설계 원칙. 접근 2026-07-21. URL: https://systemd.io/
- freedesktop.org, "systemd Documentation" — unit·target·socket activation·timer. 접근 2026-07-21. URL: https://www.freedesktop.org/wiki/Software/systemd/
- Linux kernel documentation, "Cgroups v2", kernel.org — systemd가 사용하는 cgroups v2. 접근 2026-07-21
- Nemeth, E., Snyder, G., Hein, T.
5th ed, Addison-Wesley, 2017, Ch.2 — systemd 실무. 접근 2026-07-21 - Pezzè, M. "systemd: The Good, The Bad, and The Ugly", 2015 — systemd 논쟁 분석. 접근 2026-07-21
- Lucas, M. W.
2nd ed, Tilted Windmill Press, 2021 — journalctl 실전 가이드. 접근 2026-07-21 - Linux man pages, "systemd.unit(5)", "systemd.service(5)", "systemd.timer(5)", "journalctl(1)" — 공식 매뉴얼. 접근 2026-07-21
'Infra Architecture > Compute Platforms' 카테고리의 다른 글
| Compute Platforms - 10. 서버 성능 분석 (0) | 2026.07.23 |
|---|---|
| Compute Platforms - 09. 이미지 빌드와 Immutable Infrastructre (0) | 2026.07.23 |
| Compute Platforms - 07. 컨테이너 런타임 (1) | 2026.07.22 |
| Compute Platforms - 06. HCI (Hyper-Converged Infrastructure) (0) | 2026.07.22 |
| Compute Platforms - 05. VM 라이프사이클 (1) | 2026.07.22 |