서비스 · 프로세스 관리 27 / 50 · Part 3. systemd와 서비스
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

SysV init 시절에는 서버가 몇 번 runlevel로 부팅되는지가 중요했다. 3이면 텍스트 서버, 5면 그래픽 데스크톱, 1이면 단일 사용자 복구 모드. systemd에서는 이 개념이 target 으로 바뀌었다. target은 "이 상태에 도달하려면 무엇이 켜져 있어야 하는가"를 정의한 unit 묶음이다.

이번 글에서는 부팅이 어떤 target 계층을 거쳐 진행되는지, 기본 target을 어떻게 확인·변경하는지, 그리고 장애 복구에 쓰는 rescue·emergency 모드 가 무엇이 다르고 보안상 왜 중요한지 정리한다.


2. 핵심 개념

2-1. target의 특징

특징내용
프로세스 없음target 자체는 아무것도 실행하지 않는다. 의존성만 가진다
동기화 지점"network-online.target 이후", "multi-user.target에 포함" 처럼 기준점 역할
활성화 방식서비스의 [Install] WantedBy=multi-user.target → enable 시 target의 *.wants/에 연결
기본 targetdefault.target 심볼릭 링크가 가리키는 target까지 부팅

2-2. 주요 target

target용도
sysinit.target초기 시스템 준비 (마운트, journald, udev)
basic.target소켓·타이머·경로 unit 준비. 일반 서비스는 이후에 시작
multi-user.target서버의 표준 목표 — 네트워크, 로그인, 일반 서비스
graphical.targetmulti-user + 그래픽 로그인
network-online.target네트워크가 실제로 구성됨
rescue.target단일 사용자 복구 모드
emergency.target최소 긴급 모드

2-3. 관련 명령

명령용도
systemctl get-default기본 target 확인
systemctl set-default multi-user.target기본 target 변경 (링크 변경)
systemctl isolate rescue.target지금 해당 target으로 전환 (나머지 unit 중지)
systemctl list-dependencies TARGETtarget이 끌어오는 unit
커널 인자 systemd.unit=rescue.target부팅 시 특정 target으로 진입

3. 동작 원리

부팅 target 계층과 runlevel 대응

커널 → systemd (PID 1)
  → default.target 이 가리키는 target 확인 (예: multi-user.target)
  → 그 target 의 Requires/Wants 를 재귀적으로 모아 트랜잭션 구성
  → After/Before 순서에 맞춰 병렬 시작
      sysinit.target → basic.target → multi-user.target (sshd, crond, ...)
  → 목표 target 도달 = 부팅 완료

isolate는 지정한 target과 그 의존성에 포함되지 않는 unit을 모두 중지 한다. 원격 SSH 세션에서 systemctl isolate rescue.target을 실행하면 sshd가 중지되어 접속이 끊기므로, 원격에서는 사용하지 않는다.


4. 명령어 실습

# 1) 기본 target 과 runlevel 대응
systemctl get-default
ls -l /etc/systemd/system/default.target 2>/dev/null || readlink -f /usr/lib/systemd/system/default.target
ls -l /usr/lib/systemd/system/runlevel*.target | awk '{print $9, $10, $11}'
systemctl list-units --type=target --no-pager --no-legend | head -12

# 2) multi-user.target 이 끌어오는 것 / 기본 target 변경
systemctl list-dependencies multi-user.target --no-pager | head -14
systemctl show multi-user.target -p Requires,Wants,After --no-pager
systemctl set-default graphical.target; systemctl get-default
systemctl set-default multi-user.target

# 3) (Ubuntu) rescue.target 정의와 isolate 허용 여부
systemctl get-default
systemctl cat rescue.target --no-pager | grep -E '^(Description|Requires|Wants|Conflicts|After|AllowIsolate)'
systemctl show emergency.target rescue.target multi-user.target -p Id,AllowIsolate

실습 서버에서도 isolate rescue.target은 실행하지 않았다. 실행하면 SSH 세션이 끊기고 콘솔로만 복구할 수 있다.


5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 기본 target 과 runlevel 대응

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — multi-user.target 이 끌어오는 것

실제 실행 결과 — Ubuntu 24.04.5 · root@ubuntu-lab — target 전환과 rescue 의 의미

텍스트 원본(실제 출력):

[root@rocky9-lab ~]# systemctl get-default
graphical.target
[root@rocky9-lab ~]# ls -l /etc/systemd/system/default.target 2>/dev/null || readlink -f /usr/lib/systemd/system/default.target
/usr/lib/systemd/system/graphical.target
[root@rocky9-lab ~]# ls -l /usr/lib/systemd/system/runlevel*.target | awk '{print $9, $10, $11}'
/usr/lib/systemd/system/runlevel0.target -> poweroff.target
/usr/lib/systemd/system/runlevel1.target -> rescue.target
/usr/lib/systemd/system/runlevel2.target -> multi-user.target
/usr/lib/systemd/system/runlevel3.target -> multi-user.target
/usr/lib/systemd/system/runlevel4.target -> multi-user.target
/usr/lib/systemd/system/runlevel5.target -> graphical.target
/usr/lib/systemd/system/runlevel6.target -> reboot.target
[root@rocky9-lab ~]# systemctl list-units --type=target --no-pager --no-legend | head -12
  basic.target          loaded active active Basic System
  getty.target          loaded active active Login Prompts
  graphical.target      loaded active active Graphical Interface
  local-fs.target       loaded active active Local File Systems
  multi-user.target     loaded active active Multi-User System
  network-online.target loaded active active Network is Online
  network-pre.target    loaded active active Preparation for Network
  paths.target          loaded active active Path Units
  remote-fs.target      loaded active active Remote File Systems
  slices.target         loaded active active Slice Units
  sockets.target        loaded active active Socket Units
  sshd-keygen.target    loaded active active sshd-keygen.target
[root@rocky9-lab ~]# systemctl list-dependencies multi-user.target --no-pager | head -14
multi-user.target
● ├─atd.service
● ├─crond.service
● ├─labapp.service
● ├─sshd.service
● ├─systemd-ask-password-wall.path
● ├─systemd-logind.service
○ ├─systemd-update-utmp-runlevel.service
● ├─systemd-user-sessions.service
● ├─basic.target
● │ ├─paths.target
● │ ├─slices.target
● │ │ ├─-.slice
● │ │ └─system.slice
[root@rocky9-lab ~]# systemctl show multi-user.target -p Requires,Wants,After --no-pager | cut -c1-110
Requires=basic.target
Wants=labapp.service systemd-update-utmp-runlevel.service systemd-logind.service atd.service crond.service rem
After=rescue.target crond.service rescue.service labapp.service systemd-logind.service sshd.service systemd-us
[root@rocky9-lab ~]# systemctl set-default graphical.target; systemctl get-default; systemctl set-default multi-user.target
Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/graphical.target.
graphical.target
Removed "/etc/systemd/system/default.target".
Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target.
root@ubuntu-lab:~# systemctl get-default
graphical.target
root@ubuntu-lab:~# systemctl cat rescue.target --no-pager | grep -E '^(Description|Requires|Wants|Conflicts|After|AllowIsolate)'
Description=Rescue Mode
Requires=sysinit.target rescue.service
After=sysinit.target rescue.service
AllowIsolate=yes
root@ubuntu-lab:~# systemctl show emergency.target rescue.target multi-user.target -p Id,AllowIsolate
Id=emergency.target
AllowIsolate=yes

Id=rescue.target
AllowIsolate=yes

Id=multi-user.target
AllowIsolate=yes

6. 결과 해석

관찰의미
Rocky get-default → graphical.target컨테이너 이미지의 기본값. 그래픽 패키지가 없어도 graphical.target은 multi-user를 포함하므로 부팅은 된다
/etc/systemd/system/default.target 없음 → /usr/lib/.../graphical.target관리자가 바꾸지 않으면 패키지 기본 링크를 따른다
runlevel0~6 → poweroff/rescue/multi-user/graphical/reboot옛 runlevel 이름은 target의 별칭 으로 남아 있다. runlevel 2·3·4는 모두 multi-user다
활성 target: basic, getty, graphical, multi-user, network-online, sockets ...부팅이 거쳐 온 동기화 지점들
multi-user.target 의존성: atd, crond, labapp, sshd ...25편에서 enable한 labapp이 multi-user.target에 연결되어 있다
Wants=labapp.service ... atd.service crond.serviceenable 링크가 target의 Wants로 계산되었다
set-default → default.target 링크 생성/삭제기본 target 변경도 결국 심볼릭 링크 조작 이다
Ubuntu rescue.target: Requires=sysinit.target rescue.servicerescue는 sysinit까지만 올리고 root 셸(rescue.service)을 띄운다. 네트워크와 일반 서비스는 없다
AllowIsolate=yesisolate 대상이 될 수 있는 target만 이 값이 yes다

7. 보안 관점

주제내용
콘솔 root 셸부팅 메뉴에서 커널 인자에 systemd.unit=emergency.target(또는 rd.break, init=/bin/bash)을 넣으면 root 셸을 얻을 수 있다. GRUB 비밀번호, 콘솔·가상화 콘솔 접근 통제, 디스크 암호화가 방어책이다
rescue 인증Rocky/RHEL은 rescue·emergency 진입 시 root 비밀번호를 요구한다(sulogin). root 계정이 잠겨 있으면 진입 불가 → 복구 정책과 함께 설계
불필요한 target서버의 기본 target이 graphical이면 디스플레이 매니저 등 불필요한 서비스가 뜰 수 있다. 서버는 multi-user.target
지속성 연결점악성 unit은 WantedBy=multi-user.target 또는 default.target에 연결된다. multi-user.target.wants/는 점검 1순위 디렉터리다

8. 보안관제 관점

[점검]  systemctl get-default                                         ← 서버 정책과 일치하는가
        ls -l /etc/systemd/system/multi-user.target.wants/            ← 부팅 시 시작 목록
        ls -l /etc/systemd/system/default.target.wants/ 2>/dev/null
        grep -r 'systemd.unit\|init=' /proc/cmdline                   ← 비정상 부팅 인자
     ↓
[이벤트] 재부팅 기록 확인
        journalctl --list-boots | tail -3
        last -x reboot | head -3
     ↓
[판단]  예정되지 않은 재부팅 + rescue/emergency 진입 흔적 → 콘솔 접근 사고 조사
흔적확인 위치
부팅 인자/proc/cmdline, 이전 부팅은 journalctl -b -1 -k \| grep 'Command line'
rescue 진입journalctl -b -1 -u rescue.service -u emergency.service
재부팅 이력journalctl --list-boots, last -x

9. 실무에서 자주 발생하는 실수

실수결과예방
원격에서 isolate rescue.targetSSH 끊김, 콘솔 복구 필요원격에서는 사용 금지
/etc/inittab으로 runlevel 변경 시도systemd는 무시systemctl set-default
서비스의 WantedBy를 graphical.target으로서버(multi-user)에서 자동 시작 안 됨서버 서비스는 multi-user.target
After=network.target을 "네트워크 준비 완료"로 오해준비 전 시작network-online.target (26편)

10. 실습 체크리스트

[ ] get-default 로 기본 target 을 확인했다
[ ] runlevel*.target 이 target 별칭인 것을 확인했다
[ ] multi-user.target 의 의존성에 enable 한 서비스가 포함된 것을 확인했다
[ ] set-default 가 default.target 링크를 바꾸는 것을 확인했다
[ ] rescue.target 과 emergency.target 의 차이를 설명할 수 있다
[ ] 부팅 인자로 root 셸을 얻는 위험과 방어책을 설명할 수 있다

11. 핵심 정리

  • target은 프로세스 없이 의존성만 가진 unit 묶음·동기화 지점 이다.
  • 부팅은 default.target까지 sysinit → basic → multi-user 순으로 올라가는 과정이다.
  • runlevel은 target 별칭으로 남아 있고, 서버의 표준은 multi-user.target이다.
  • rescue는 sysinit까지 + root 셸, emergency는 최소 셸이다. 원격에서 isolate하지 않는다.
  • 콘솔 접근자는 부팅 인자로 root 셸을 얻을 수 있으므로 GRUB·콘솔 보안이 필요하다.

12. 다음 편 예고

다음 글 「28. drop-in으로 서비스 설정 변경 (systemctl edit)」 에서는 패키지 원본 unit을 건드리지 않고 설정을 바꾸는 drop-in(override.conf) 을 다룬다. 환경변수·nice·자원 한도를 바꾸고, ExecStart=를 바꿀 때 반드시 먼저 비워야 하는 규칙을 실패 로그로 확인한다.


참고 자료


시리즈 이동

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글