금요일 퇴근 직전, 운영 서버가 재부팅 후 응답이 없습니다. 데이터센터에 직접 모니터를 연결하니 GRUB 화면에서 멈춰 있습니다. 커널 파라미터를 잘못 건드린 것 같은데, 어디서 뭘 수정해야 할지 모르면 손도 못 댑니다. 부팅 단계를 이해하면 이런 상황에서 5분 안에 서버를 살릴 수 있습니다.
부팅 프로세스와 systemd
컴퓨터의 전원을 켜는 순간부터 로그인 프롬프트가 나타나기까지, 리눅스는 복잡한 초기화 과정을 거칩니다. 이 과정을 이해하면 부팅 실패를 진단하고, 서비스를 올바르게 관리하며, 시스템 성능을 최적화할 수 있습니다.
부팅 프로세스와 systemd는 리눅스마스터 1급의 핵심 영역입니다. GRUB2 설정 파일 구분, systemctl 옵션, 런레벨과 타겟 대응 관계가 자주 출제됩니다.
- 1부팅 5단계 — BIOS → MBR → GRUB2 → 커널 → systemd 흐름
- 2GRUB2 설정 — /etc/default/grub 편집과 grub2-mkconfig
- 3systemd 유닛 타입 — service/socket/timer/target/mount
- 4systemctl — 서비스 생명주기 관리 명령어
- 5런레벨과 타겟 — SysV 런레벨과 systemd 타겟 대응
- 6journalctl — 구조화된 로그 조회와 부팅 로그 분석
systemd 명령어는 root 권한 없이도 상태 조회가 가능합니다. 서비스 시작/중지는 sudo가 필요합니다.
systemctl get-defaultsystemctl list-units --type=service --state=runningsystemd-analyze blame | head -10리눅스 부팅 5단계
전체 부팅 흐름
전원 ON
↓
┌─────────────────────────────────────┐
│ 1단계: BIOS/UEFI │
│ - POST (Power-On Self-Test) │
│ - 하드웨어 초기화 및 검사 │
│ - 부팅 디바이스 순서대로 탐색 │
│ - MBR(BIOS) 또는 EFI 파티션(UEFI) │
│ 에서 부트로더 실행 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 2단계: MBR / GPT │
│ - MBR: 디스크 0번 섹터(512바이트) │
│ 첫 446바이트: 부트 코드 │
│ 다음 64바이트: 파티션 테이블 │
│ 마지막 2바이트: 매직 넘버(0x55AA) │
│ - GRUB2 Stage 1 로드 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 3단계: GRUB2 (Grand Unified Bootloader)│
│ - 설정 파일 읽기 (/boot/grub2/grub.cfg)│
│ - 부팅 메뉴 표시 (타임아웃) │
│ - 커널 이미지와 initramfs 로드 │
│ - 커널 파라미터 전달 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 4단계: 커널(Kernel) 초기화 │
│ - 하드웨어 드라이버 초기화 │
│ - initramfs 마운트 (임시 루트 FS) │
│ - 실제 루트 파일시스템 마운트 │
│ - PID 1(/sbin/init 또는 /bin/systemd)│
│ 실행 │
└──────────────┬──────────────────────┘
↓
┌─────────────────────────────────────┐
│ 5단계: systemd (PID 1) │
│ - 기본 타겟 활성화 │
│ - 유닛 파일 읽기 및 의존성 분석 │
│ - 병렬로 서비스 시작 │
│ - 로그인 프롬프트 제공 │
└─────────────────────────────────────┘
initramfs 역할
커널 로드 후 실제 루트 파일시스템을 마운트하기 전에 임시 파일시스템이 필요합니다.
실습 전 디렉토리와 예제 파일을 먼저 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux-master/part2/exam_1 && cd /tmp/linux-master/part2/exam_1
cat > myapp.service << 'EOF'
[Unit]
Description=My App Service
After=network.target
[Service]
ExecStart=/usr/bin/sleep infinity
[Install]
WantedBy=multi-user.target
EOF
이제 실습을 진행합니다.
# initramfs 파일 확인
ls /boot/
# initramfs-5.14.0-570.52.1.el9.x86_64.img ← 임시 루트 FS
# vmlinuz-5.14.0-570.52.1.el9.x86_64 ← 커널 이미지
# initramfs 내용 확인
lsinitrd /boot/initramfs-$(uname -r).img | head -20
# -rw-r--r-- 1 root root ... etc/passwd
# drwxr-xr-x 3 root root ... lib
# drwxr-xr-x 2 root root ... lib/modules/...
GRUB2 설정과 관리
GRUB2 핵심 파일
GRUB2 파일 구조
├── /boot/grub2/grub.cfg ← 자동 생성 (직접 편집 금지!)
├── /etc/default/grub ← 사용자 설정 (여기 편집!)
├── /etc/grub.d/ ← 스크립트 (grub.cfg 생성에 사용)
│ ├── 00_header (기본 설정)
│ ├── 10_linux (리눅스 커널 항목)
│ ├── 30_os-prober (다중 부팅 OS 탐지)
│ └── 40_custom (사용자 커스텀 항목)
└── /boot/grub2/grubenv ← 환경 변수 저장
/etc/default/grub 주요 설정
# /etc/default/grub
GRUB_TIMEOUT=5 # 부팅 메뉴 대기 시간 (초), 0=즉시 부팅
GRUB_DEFAULT=0 # 기본 항목 번호 (0부터 시작), "saved"=마지막 선택
GRUB_TIMEOUT_STYLE=menu # menu=메뉴 표시, hidden=숨김, countdown=카운트다운
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_CMDLINE_LINUX="rhgb quiet" # 모든 커널에 추가할 파라미터
# rhgb: Red Hat Graphical Boot (부팅 화면 그래픽)
# quiet: 커널 메시지 숨김
# rd.break: initramfs 단계에서 셸 진입 (복구 시 사용)
# single: 단일사용자 모드 부팅
GRUB_DISABLE_RECOVERY="true" # 복구 모드 항목 숨김
GRUB2 설정 변경 절차
# 1. /etc/default/grub 편집
sudo vi /etc/default/grub
# GRUB_TIMEOUT=10 # 5초 → 10초로 변경
# 2. grub.cfg 재생성 (필수!)
# BIOS/MBR 시스템
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# UEFI 시스템
sudo grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
# Ubuntu
sudo update-grub # grub2-mkconfig의 래퍼
# 3. 변경 확인
grep "set timeout" /boot/grub2/grub.cfg
# set timeout=10
GRUB2 복구 모드
# 부팅 중 GRUB2 메뉴에서 'e' 키 → 편집 모드
# linux 라인에 커널 파라미터 추가 후 Ctrl+X로 부팅
# 예시: 단일사용자 모드 부팅 (root 패스워드 복구)
# linux /vmlinuz-... root=... ro rhgb quiet
# → 끝에 "single" 또는 "rd.break" 추가
# → Ctrl+X
# rd.break로 initramfs 셸 진입
# mount -o remount,rw /sysroot
# chroot /sysroot
# passwd root # root 패스워드 재설정
# touch /.autorelabel # SELinux 레이블 재적용
# exit; exit
systemd 아키텍처와 유닛 타입
systemd 기본 개념
systemd는 리눅스의 init 시스템으로, 병렬 서비스 시작을 통해 빠른 부팅과 강력한 의존성 관리를 제공합니다.
PID 1: systemd
├── 유닛(Unit) 관리
│ ├── service : 데몬 프로세스 (nginx.service)
│ ├── socket : IPC 소켓 (sshd.socket)
│ ├── timer : 주기적 실행 (cron 대체, backup.timer)
│ ├── target : 유닛 그룹 (multi-user.target)
│ ├── mount : 파일시스템 마운트 (home.mount)
│ ├── automount : 자동 마운트
│ ├── path : 파일/디렉토리 감시 (inotify)
│ └── slice : 리소스 그룹 (cgroups)
├── 저널 (journald) — 구조화된 로그
├── 로그인 관리 (logind)
└── 네트워크 관리 (networkd)
유닛 파일 위치
# 시스템 제공 유닛 (패키지 설치 시 생성)
/usr/lib/systemd/system/
# 관리자 커스텀 유닛 (우선순위 높음)
/etc/systemd/system/
# 런타임 유닛 (재부팅 시 삭제)
/run/systemd/system/
# 유닛 파일 편집 (덮어쓰기 안전 방법)
sudo systemctl edit nginx.service # override 파일 생성
sudo systemctl edit --full nginx.service # 전체 파일 편집
service 유닛 파일 구조
# /usr/lib/systemd/system/nginx.service
[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target # 이후에 시작
Wants=network-online.target # 약한 의존성 (없어도 시작)
Requires=network.target # 강한 의존성 (없으면 실패)
[Service]
Type=forking # 메인 프로세스가 fork 후 종료
PIDFile=/run/nginx.pid
ExecStartPre=/usr/bin/rm -f /run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t # 설정 검사
ExecStart=/usr/sbin/nginx # 시작 명령
ExecReload=/bin/kill -s HUP $MAINPID # reload 시 SIGHUP
ExecStop=/bin/kill -s QUIT $MAINPID # stop 시 SIGQUIT
PrivateTmp=true # 독립적인 /tmp 사용 (보안)
Restart=on-failure # 비정상 종료 시 재시작
RestartSec=5 # 재시작 전 대기 시간
[Install]
WantedBy=multi-user.target # enable 시 이 타겟의 wants에 추가
Service 타입 종류
| 타입 | 설명 | 예시 |
|---|---|---|
simple | ExecStart가 메인 프로세스 (기본값) | 단순 데몬 |
forking | 부모가 fork 후 종료, 자식이 메인 | nginx, apache |
oneshot | 실행 후 종료 (Type 변경 전까지 active) | 초기화 스크립트 |
notify | sd_notify()로 준비 완료 알림 | systemd-networkd |
dbus | D-Bus 버스 이름 획득 시 준비 완료 | NetworkManager |
systemctl — 서비스 관리
서비스 상태 관리
# 서비스 시작/중지/재시작/재로드
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx # 중지 후 시작
sudo systemctl reload nginx # 설정 재읽기 (무중단)
sudo systemctl try-restart nginx # 실행 중일 때만 재시작
# 서비스 상태 확인
systemctl status nginx
# ● nginx.service - The nginx HTTP and reverse proxy server
# Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled)
# Active: active (running) since Fri 2026-03-28 09:00:00 KST; 2h ago
# Process: 1234 ExecStart=/usr/sbin/nginx (code=exited, status=0/SUCCESS)
# Main PID: 1235 (nginx)
# CGroup: /system.slice/nginx.service
# ├─1235 nginx: master process /usr/sbin/nginx
# └─1236 nginx: worker process
#
# Mar 28 09:00:00 server nginx[1234]: nginx: the configuration file ... ok
자동 시작 설정
# 부팅 시 자동 시작 활성화
sudo systemctl enable nginx
# Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → ...
# 자동 시작 비활성화
sudo systemctl disable nginx
# 즉시 시작 + 자동 시작 활성화 (한 번에)
sudo systemctl enable --now nginx
# 서비스 마스킹 (활성화 자체를 금지)
sudo systemctl mask nginx # 어떤 방법으로도 시작 불가
sudo systemctl unmask nginx # 마스크 해제
유닛 목록 조회
# 모든 서비스 유닛 목록
systemctl list-units --type=service
# 실패한 유닛 목록
systemctl --failed
systemctl list-units --state=failed
# 활성화된 유닛 목록
systemctl list-unit-files --state=enabled
# 특정 타겟의 의존성 트리
systemctl list-dependencies multi-user.target
런레벨과 systemd 타겟
# SysV 런레벨과 systemd 타겟 대응
# 런레벨 0 (종료) = poweroff.target
# 런레벨 1 (단일사용자) = rescue.target
# 런레벨 2,3,4 (멀티유저) = multi-user.target
# 런레벨 5 (GUI) = graphical.target
# 런레벨 6 (재부팅) = reboot.target
# 현재 기본 타겟 확인
systemctl get-default
# multi-user.target
# 기본 타겟 변경 (재부팅 후 적용)
sudo systemctl set-default graphical.target
# Removed /etc/systemd/system/default.target
# Created symlink ... → /usr/lib/systemd/system/graphical.target
# 현재 세션에서 타겟 전환 (즉시)
sudo systemctl isolate multi-user.target
# 시스템 종료/재부팅
sudo systemctl poweroff
sudo systemctl reboot
sudo systemctl rescue # 복구 모드로 전환
journalctl — 구조화된 로그 분석
journalctl 기본 사용법
# 전체 로그 (최신 → 오래된 순: 기본은 오래된 것부터)
journalctl
# 최신 50줄 확인
journalctl -n 50
# 실시간 로그 (tail -f 처럼)
journalctl -f
# 특정 시간 범위
journalctl --since "2026-03-28 09:00:00"
journalctl --since "1 hour ago"
journalctl --since "2026-03-27" --until "2026-03-28"
부팅 로그 분석
# 현재 부팅의 모든 로그
journalctl -b
# 이전 부팅 로그
journalctl -b -1 # 바로 이전 부팅
journalctl -b -2 # 2번 이전 부팅
# 부팅 목록 확인
journalctl --list-boots
# -3 abc123 Thu 2026-03-25 ...
# -2 def456 Fri 2026-03-26 ...
# -1 ghi789 Sat 2026-03-27 ...
# 0 jkl012 Sun 2026-03-28 ... ← 현재
# 커널 메시지만 (dmesg)
journalctl -k
journalctl --dmesg
서비스별 로그 필터링
# 특정 서비스 로그
journalctl -u nginx
journalctl -u nginx -f # 실시간
# 여러 서비스 동시
journalctl -u nginx -u sshd
# 우선순위별 필터
journalctl -p err # err 이상 (crit, alert, emerg 포함)
journalctl -p warning # warning 이상
journalctl -b -p err # 현재 부팅의 에러 이상 로그
systemd-analyze — 부팅 성능 분석
# 전체 부팅 시간
systemd-analyze
# Startup finished in 1.234s (kernel) + 3.456s (initrd) + 8.765s (userspace) = 13.455s
# 서비스별 시작 시간 (느린 순)
systemd-analyze blame | head -10
# 3.456s NetworkManager-wait-online.service
# 2.123s dnf-makecache.service
# 1.876s plymouth-quit-wait.service
# 부팅 흐름 SVG 생성
systemd-analyze plot > /tmp/boot_plot.svg
직접 systemd 서비스 유닛을 작성하고 관리합니다.
1단계: 간단한 서비스 스크립트 작성
# 서비스로 실행할 스크립트 작성
sudo bash -c 'cat > /usr/local/bin/myapp.sh << '"'"'EOF'"'"'
#!/bin/bash
LOG=/var/log/myapp.log
echo "myapp 시작: $(date)" >> $LOG
while true; do
echo "$(date): 서비스 실행 중" >> $LOG
sleep 30
done
EOF'
sudo chmod +x /usr/local/bin/myapp.sh
2단계: systemd 유닛 파일 작성
sudo bash -c 'cat > /etc/systemd/system/myapp.service << '"'"'EOF'"'"'
[Unit]
Description=My Custom Application
After=network.target
Documentation=https://example.com/myapp
[Service]
Type=simple
User=nobody
ExecStart=/usr/local/bin/myapp.sh
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.target
EOF'
3단계: 서비스 등록 및 시작
# systemd 데몬 재로드 (새 유닛 파일 인식)
sudo systemctl daemon-reload
# 서비스 시작
sudo systemctl start myapp
# 상태 확인
systemctl status myapp
# ● myapp.service - My Custom Application
# Loaded: loaded (/etc/systemd/system/myapp.service; disabled)
# Active: active (running) since ...
# 자동 시작 활성화
sudo systemctl enable myapp
# Created symlink /etc/systemd/system/multi-user.target.wants/myapp.service → ...
# 로그 확인
journalctl -u myapp -f
# Mar 28 10:00:00 server myapp[5678]: myapp 시작: ...
# Mar 28 10:00:00 server myapp[5678]: 서비스 실행 중
4단계: 서비스 수정 (override 패턴)
# 기존 파일 직접 수정 대신 override 사용
sudo systemctl edit myapp
# 편집기에서 추가:
# [Service]
# Environment=APP_ENV=production
# RestartSec=5
# 저장 후 재시작
sudo systemctl daemon-reload
sudo systemctl restart myapp
5단계: 서비스 제거
sudo systemctl stop myapp
sudo systemctl disable myapp
sudo rm /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl reset-failed
- systemctl status myapp 실행 시 Active: active (running) 상태인가?
- journalctl -u myapp 로그에 '서비스 실행 중' 메시지가 출력됐는가?
- systemctl enable 후 /etc/systemd/system/multi-user.target.wants/ 에 symlink가 생성됐는가?
GRUB2 설정을 수정하고 올바른 절차로 적용합니다.
1단계: 현재 GRUB2 설정 확인
# 현재 설정 파일 확인
cat /etc/default/grub
# GRUB_TIMEOUT=5
# GRUB_DEFAULT=saved
# GRUB_CMDLINE_LINUX="..."
# 현재 grub.cfg에서 설정 확인
grep "set timeout" /boot/grub2/grub.cfg
# set timeout=5
2단계: 설정 변경
# 타임아웃 변경 (5초 → 10초)
sudo sed -i 's/GRUB_TIMEOUT=5/GRUB_TIMEOUT=10/' /etc/default/grub
# 또는 직접 편집
sudo vi /etc/default/grub
# 변경 확인
grep GRUB_TIMEOUT /etc/default/grub
# GRUB_TIMEOUT=10
3단계: grub.cfg 재생성
# BIOS 시스템 (CentOS/RHEL)
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
# Generating grub configuration file ...
# Found linux image: /boot/vmlinuz-5.14.0-570.52.1.el9.x86_64
# done
# 변경 적용 확인
grep "set timeout" /boot/grub2/grub.cfg
# set timeout=10 ← 변경됨
4단계: 부팅 성능 분석
systemd-analyze
# Startup finished in ... = 13.455s
systemd-analyze blame | head -5
# 가장 느린 서비스 5개
# 특정 서비스의 부팅 의존성 확인
systemd-analyze critical-chain nginx.service
# nginx.service +247ms
# └─network.target @3.456s
# └─NetworkManager.service @2.123s +1.234s
- grep 'set timeout' /boot/grub2/grub.cfg 결과가 set timeout=10으로 변경됐는가?
- systemd-analyze blame 출력에서 부팅 시간이 가장 긴 서비스 3개를 확인했는가?
- /etc/default/grub 수정 후 grub2-mkconfig를 빠트리지 않았는가?
증상
sudo systemctl start myapp
# Job for myapp.service failed because the control process exited with error code.
# See "journalctl -xe" for details.
systemctl status myapp
# ● myapp.service - My Custom Application
# Active: failed (Result: exit-code)
# Process: 5678 ExecStart=/usr/local/bin/myapp (code=exited, status=127)
진단
# 상세 로그 확인
journalctl -u myapp -n 30
# Mar 28 10:00:00 server systemd[1]: myapp.service: Main process exited, code=exited, status=127
# status=127: 실행 파일을 찾을 수 없음
# 유닛 파일 검증
systemd-analyze verify /etc/systemd/system/myapp.service
# ExecStart 경로 확인
which myapp
ls -la /usr/local/bin/myapp
# ls: /usr/local/bin/myapp: No such file or directory
# 실행 권한 확인
ls -la /usr/local/bin/myapp.sh
# -rw-r--r-- 1 root root ← 실행 권한 없음!
해결
# 1. 실행 파일 경로 수정
sudo vi /etc/systemd/system/myapp.service
# ExecStart=/usr/local/bin/myapp.sh ← 정확한 경로
# 2. 실행 권한 부여
sudo chmod +x /usr/local/bin/myapp.sh
# 3. 유닛 재로드 및 시작
sudo systemctl daemon-reload
sudo systemctl start myapp
sudo systemctl status myapp
증상
[FAILED] Failed to mount /data.
See 'systemctl status data.mount' for details.
...
[ ** ] Welcome to emergency mode!
원인
/etc/fstab에 존재하지 않는 UUID나 장치가 등록된 경우입니다.
복구 절차
# 1. 응급 모드에서 root 패스워드로 로그인
# 2. 루트 파일시스템 읽기쓰기 리마운트
mount -o remount,rw /
# 3. 실패한 마운트 확인
systemctl --failed
# data.mount failed
# 4. 로그 확인
journalctl -u data.mount
# ... /dev/disk/by-uuid/AAAA-BBB: can't read superblock
# 5. 현재 사용 가능한 디스크 확인
blkid
lsblk
# 6. fstab 수정 (문제 항목 주석 처리 또는 수정)
vi /etc/fstab
# #UUID=AAAA-BBB /data ext4 defaults 0 2 ← 주석 처리
# 7. 마운트 검증
mount -a
# 8. 재부팅
reboot
서비스 의존성 설계
마이크로서비스 환경에서 서비스 시작 순서와 의존성을 올바르게 설정하면 부팅 안정성이 향상됩니다.
# 데이터베이스가 준비된 후 앱 서버 시작
[Unit]
Description=Application Server
After=postgresql.service
Requires=postgresql.service
# Requires: postgresql이 없으면 이 서비스도 시작 안 함
# Wants: postgresql이 없어도 이 서비스 시작 시도
[Service]
Type=notify
ExecStart=/usr/bin/appserver
WatchdogSec=30 # 30초마다 watchdog 신호 확인
systemd 타이머로 cron 대체
# cron 대신 systemd timer 사용 (더 강력한 로깅, 의존성 관리)
# /etc/systemd/system/backup.service
# [Unit]
# Description=Database Backup
# [Service]
# Type=oneshot
# ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
# [Unit]
# Description=Run database backup daily
# [Timer]
# OnCalendar=*-*-* 02:00:00 # 매일 새벽 2시
# Persistent=true # 놓친 실행 즉시 실행
# [Install]
# WantedBy=timers.target
sudo systemctl enable --now backup.timer
systemctl list-timers # 타이머 목록과 다음 실행 시간
journald 로그 관리
# 로그 크기 제한 (/etc/systemd/journald.conf)
# SystemMaxUse=1G # 최대 디스크 사용량
# MaxRetentionSec=1month # 최대 보존 기간
# 현재 로그 사용 공간 확인
journalctl --disk-usage
# Archived and active journals take up 235.2M in the file system.
# 오래된 로그 삭제
sudo journalctl --vacuum-size=500M # 500MB 이하로 유지
sudo journalctl --vacuum-time=2weeks # 2주 이전 삭제