리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 36 / 50 · Part 4. 프로세스·서비스·리소스
실습 환경: Rocky Linux 9 (10.0.0.200) / Ubuntu 22.04 (10.0.0.210)
이전 글: 35. Parent / Child Process
8편에서 부팅 과정을 다루며 커널이 첫 번째 사용자 프로세스로 systemd(PID 1) 를 실행한다고 했다. 35편에서는 모든 프로세스 트리의 뿌리가 systemd라는 것을 확인했다.
이번 글은 systemd가 무엇을 어떤 구조로 관리하는지 를 정리한다. 관제 관점에서 systemd가 중요한 이유는 두 가지다.
명령어 사용법(systemctl)은 37편에서 다루고, 이번 글은 구조 에 집중한다.
systemd가 관리하는 모든 대상을 Unit 이라 부른다. 확장자가 종류를 나타낸다.
| Unit 종류 | 관리 대상 | 예 |
|---|---|---|
.service | 데몬, 일회성 작업 | sshd.service |
.timer | 예약 실행 (cron 대체, 38편) | logrotate.timer |
.target | 여러 Unit의 묶음 (옛 runlevel) | multi-user.target |
.socket | 소켓에 연결이 오면 서비스 기동 | sshd.socket |
.mount | 마운트 지점 | tmp.mount |
.path | 파일·디렉터리 변화 감시 후 기동 | cups.path |
같은 이름의 Unit이 여러 곳에 있으면 위쪽이 이긴다.
| 우선순위 | 경로 | 용도 |
|---|---|---|
| 1 | /etc/systemd/system/ | 관리자 설정, 드롭인(X.service.d/*.conf) |
| 2 | /run/systemd/system/ | 런타임 생성, 재부팅 시 사라짐 |
| 3 | /usr/lib/systemd/system/ (RHEL) · /lib/systemd/system/ (Ubuntu) | 패키지 원본 |
| 사용자 | ~/.config/systemd/user/, /etc/systemd/user/ | 사용자 단위 서비스 |
패키지 원본(3)을 직접 수정하지 말고 /etc 쪽에 덮어쓰는 것이 원칙이다. 공격자도 같은 원리로 /etc/systemd/system/에 새 파일이나 드롭인을 만든다.
[Unit]
Description=OpenSSH server daemon
After=network.target # 순서: network.target 다음에 시작
Wants=sshd-keygen.target # 약한 의존: 함께 시작 시도
[Service]
Type=notify
ExecStart=/usr/sbin/sshd -D $OPTIONS # 실제 실행 명령 ← 가장 중요
ExecReload=/bin/kill -HUP $MAINPID # reload = SIGHUP (34편)
Restart=on-failure # 비정상 종료 시 자동 재시작
User= # 비어 있으면 root
[Install]
WantedBy=multi-user.target # enable 하면 이 target에 연결
| 지시어 | 의미 |
|---|---|
After= / Before= | 순서 만 정함 |
Wants= / Requires= | 의존 — 함께 시작. Requires는 실패하면 같이 실패 |
ExecStart= | 실행할 명령 (보안 점검 핵심) |
Restart= | 죽으면 다시 살림 — 악성 서비스가 kill 되어도 되살아나는 이유 |
User= | 실행 계정. 없으면 root |
WantedBy= | enable 시 어느 target에 연결할지 |

① enable의 실체는 심볼릭 링크다. systemctl enable sshd를 실행하면 [Install]의 WantedBy=multi-user.target을 보고 다음 링크를 만든다.
/etc/systemd/system/multi-user.target.wants/sshd.service
-> /usr/lib/systemd/system/sshd.service
부팅 시 systemd는 multi-user.target을 목표로 삼고, .wants/ 디렉터리 안의 링크를 따라 서비스를 시작한다. 그래서 *.wants/ 디렉터리를 보면 부팅 시 자동 실행되는 목록 을 알 수 있다.
② 의존성 그래프로 병렬 시작한다. 예전 SysV init은 스크립트를 번호 순서대로 하나씩 실행했지만, systemd는 After·Requires 관계만 지키면서 가능한 한 동시에 시작한다. 부팅이 빠른 이유다.
③ cgroup으로 서비스를 묶는다. 서비스가 시작되면 systemd는 그 프로세스와 모든 자손을 /sys/fs/cgroup/system.slice/<이름>.service/에 넣는다. 그래서:
systemctl stop은 해당 cgroup의 모든 프로세스 를 종료할 수 있다(서비스가 fork한 자식까지).④ 로그는 journald로 모인다. 서비스의 표준 출력·오류는 systemd-journald가 받아 저장한다(48편 journalctl).
# 1) PID 1 확인
ps -p 1 -o pid,comm,args
# 2) Unit 목록
systemctl list-units --type=service --state=running
systemctl list-unit-files --type=service --state=enabled
# 3) Unit 파일 내용과 실제 경로 (드롭인 포함)
systemctl cat sshd
systemctl show sshd -p FragmentPath,DropInPaths,ExecStart,User
# 4) 부팅 시 자동 실행 링크
ls -l /etc/systemd/system/multi-user.target.wants/
# 5) 의존성
systemctl list-dependencies multi-user.target | head -30
# 6) 부팅 시간 분석 (8편)
systemd-analyze blame | head
# 7) 프로세스가 속한 서비스
systemctl status <PID>
cat /proc/<PID>/cgroup
systemd-cgls --no-pager | head -40
# 8) 사용자 유닛과 linger
ls -la ~/.config/systemd/user/ 2>/dev/null
loginctl show-user <user> -p Linger
아래 출력은 형식 설명용 예시다.
① systemctl show dbus-helper -p FragmentPath,ExecStart,User
FragmentPath=/etc/systemd/system/dbus-helper.service
ExecStart={ path=/var/tmp/.cache/dbus-helper ; argv[]=/var/tmp/.cache/dbus-helper ; ... }
User=
| 단서 | 해석 |
|---|---|
FragmentPath가 /etc/systemd/system/ | 패키지가 아니라 누군가 직접 만든 Unit |
이름 dbus-helper | 정상 dbus 서비스처럼 보이게 한 위장 |
ExecStart가 /var/tmp/.cache/ | 숨김 디렉터리의 실행 파일 (22편) |
User= 비어 있음 | root 권한 실행 |
② 패키지 소속 확인
rpm -qf /etc/systemd/system/dbus-helper.service # RHEL
dpkg -S /etc/systemd/system/dbus-helper.service # Ubuntu
file /etc/systemd/system/dbus-helper.service is not owned by any package
어떤 패키지에도 속하지 않는 서비스 파일이다. 관리자가 직접 만든 정상 서비스일 수도 있으므로 변경 관리 기록과 대조한다.
| 지속성 기법 | 위치 | 점검 |
|---|---|---|
| 새 서비스 등록 (T1543.002) | /etc/systemd/system/*.service + .wants/ 링크 | 패키지 소속, ExecStart 경로 |
| 기존 서비스 드롭인 변조 | /etc/systemd/system/sshd.service.d/override.conf | systemctl cat으로 드롭인 표시 확인 |
| 타이머 (T1053.006) | *.timer + 같은 이름의 .service | systemctl list-timers --all (38편) |
| 사용자 유닛 | ~/.config/systemd/user/ + linger | root 권한 없이 가능 |
| 원본 Unit 변조 | /usr/lib/systemd/system/ | rpm -V systemd, rpm -Va \| grep systemd |
| 보안 서비스 비활성화 | systemctl disable --now auditd / mask | enable 상태 변화 감시 (37편) |
Restart=always가 설정된 악성 서비스는 프로세스를 kill 해도 systemd가 몇 초 뒤 다시 살린다. 반드시 systemctl stop → disable → Unit 파일 격리 순서로 처리해야 한다.
# RHEL: 어떤 패키지에도 속하지 않는 unit 파일
find /etc/systemd/system /usr/lib/systemd/system -name '*.service' -o -name '*.timer' |
while read -r f; do rpm -qf "$f" >/dev/null 2>&1 || echo "NOPKG $f"; done
# 최근 7일 내 변경된 unit·드롭인 (22편)
find /etc/systemd /usr/lib/systemd /run/systemd ~/.config/systemd -newermt '-7 days' -type f 2>/dev/null
# ExecStart 가 임시 경로를 가리키는 서비스
grep -rEl 'ExecStart=.*(/tmp|/var/tmp|/dev/shm|/\.)' /etc/systemd /usr/lib/systemd 2>/dev/null
# /etc/audit/rules.d/systemd.rules
-w /etc/systemd/system/ -p wa -k systemd_persist
-w /usr/lib/systemd/system/ -p wa -k systemd_persist
[Alert] auditd: /etc/systemd/system/dbus-helper.service 생성 (key=systemd_persist)
↓
[확인] systemctl cat → ExecStart=/var/tmp/.cache/dbus-helper, User 없음(root)
↓
[판단] 패키지 미소속 + 숨김 경로 + 위장 이름 → 지속성 설치
↓
[범위] 생성 시각 전후 secure 로그의 sudo/su 기록 → root 획득 경로 (21·49편)
↓
[Response] stop → disable → unit·실행 파일 격리(해시 보관) → daemon-reload → 다른 서버 동일 파일 탐색
.service, .timer, .target, .socket …/etc/systemd/system > /run > /usr/lib(Ubuntu /lib). 사용자 유닛은 ~/.config/systemd/user.ExecStart, User, Restart, WantedBy 다.enable은 *.target.wants/에 심볼릭 링크를 만드는 것이다.다음 글 「37. systemctl 서비스 관리」 에서는 이번 구조를 조작하는 도구 systemctl을 다룬다. start·stop·restart·reload의 차이, enable과 mask, status 출력(Active·Main PID·exit code)을 읽는 방법, 그리고 서비스 이상 종료를 관제에서 판단하는 방법을 정리한다.