30년 버틴 init, 5년 만에 갈아치워진 표준

리눅스에서 PID 1로 부팅 직후 가장 먼저 실행되는 프로세스가 init이다. 이 자리는 30년 가까이 거의 한 사람이 지키고 있었다 — SysVinit. 그런데 2010년대 초반부터 갑자기 풍경이 바뀐다. 2011년 Fedora 15에서 등장한 systemd가 5년 만에 거의 모든 메이저 배포판의 기본 init을 차지했고, 그 사이에 잠깐 등장했다 사라진 Upstart도 있었다.

이 글은 세 init 시스템을 기능 비교가 아니라 연표로 다룬다.

  • 어떤 배포판이 언제 SysVinit을 버렸는가
  • Upstart는 왜 짧게 살았는가
  • systemd 채택을 둘러싼 Debian 투표, Devuan 분기, Ubuntu의 입장 변화는 어떻게 흘러갔는가
  • 지금도 systemd를 쓰지 않는 배포판은 어디인가

서버에 SSH로 들어가서 ps -p 1 -o comm= 한 번 쳐봤을 때 출력 한 줄이 어떤 역사 위에 서 있는지 정리해 보자.


세 세대, 한눈에

세대이름등장부팅 모델설정 단위대표 배포판 (전환 시점)
1세대SysVinit1983 (System V)직렬 / 런레벨/etc/inittab + /etc/init.d/*.sh거의 모든 리눅스 (~ 2010s)
2세대Upstart2006이벤트 기반/etc/init/*.confUbuntu 6.10 ~ 14.04, RHEL 6
3세대systemd2010병렬 / 의존성 + cgroup*.service, *.socket, *.timer, *.targetFedora 15+, RHEL 7+, Debian 8+, Ubuntu 15.04+, Arch, openSUSE 12.1+

세 시스템이 가진 본질적 차이는 한 줄로 줄이면 이렇다.

  • SysVinit — 셸 스크립트를 순서대로 돌린다
  • Upstart — 이벤트가 발생하면 잡(job)을 돌린다
  • systemd — 의존성 그래프를 따라 유닛(unit)을 병렬로 돌리고 그 후로도 계속 본다

1세대: SysVinit (1983~)

뿌리는 AT&T가 1983년에 낸 Unix System V다. 리눅스에는 1990년대 초 Miquel van Smoorenburg(미컬 반 스모렌부르흐)가 포팅한 sysvinit 패키지로 흘러들어왔고, 그 후 약 20년간 거의 모든 리눅스 배포판의 기본 init이 됐다.

핵심 구조는 단순하다.

/etc/inittab          → 어느 런레벨에 어떤 스크립트를 돌릴지 선언
/etc/init.d/*         → 각 서비스의 start/stop/restart 스크립트
/etc/rc{0..6}.d/      → 런레벨별 심볼릭 링크 (S20foo, K80foo …)

런레벨(runlevel)은 시스템의 모드 번호다. 관습적으로 7개를 쓴다.

런레벨의미
0halt (종료)
1 (S)single user mode
2multi-user, 네트워크 없음 (Debian 계열은 네트워크 포함)
3multi-user, 네트워크 있음 (서버 기본값)
4미정의/사용자 정의
5multi-user + GUI (데스크톱 기본값)
6reboot

런레벨 진입 시 /etc/rcN.d/S로 시작하는 링크를 번호 순으로 실행, 빠져나갈 때 K 링크를 실행한다. 부팅이란 곧 “쉘 스크립트를 정해진 순서대로 한 줄씩 돌리는” 일이었다.


SysVinit의 한계

SysVinit이 30년을 살아남은 건 단순하기 때문이고, 5년 만에 밀려난 것도 그 단순함 때문이다.

1. 직렬 부팅S20foo 가 끝나야 S21bar 가 시작된다. CPU가 놀고 있어도, 두 서비스가 서로 무관해도 순서를 바꿀 수 없다. 부팅이 느린 가장 큰 이유였다.

2. 의존성 표현이 약하다 — “DB가 떠 있어야 web이 뜬다” 같은 관계를 번호(S20, S21)로만 표현한다. LSB 헤더가 의존성을 적을 수 있게 해줬지만 여전히 선형 정렬이 전제다.

3. 데몬 추적이 어렵다 — 스크립트가 &로 백그라운드에 넘긴 자식 프로세스를 init이 직접 추적하지 않는다. PID 파일이 진실의 원천이고, 어긋나면 service foo status가 거짓말을 한다. 자식이 더블 fork 해서 도망가면 더 곤란하다.

4. 서비스가 죽으면 그걸로 끝 — auto-restart는 respawn을 inittab에 직접 거는 식이거나, monit/supervisord 같은 외부 도구를 끌어와야 한다.

이 네 가지 한계가 2000년대 후반 “부팅이 빨라야 하는 노트북"과 “의존성 많은 데스크톱 환경"의 시대와 충돌했다. 다음 두 세대는 모두 이 네 가지 중 하나 이상을 풀려고 시작된다.


2세대: Upstart (2006~)

Canonical의 Scott James Remnant(스콧 제임스 렘넌트)가 만들었다. 첫 출시는 Ubuntu 6.10 “Edgy Eft” (2006-10). 이때부터 Ubuntu의 기본 init은 SysVinit이 아니라 Upstart였다 (호환 모드로 SysV 스크립트도 돌렸지만).

Upstart의 출발점은 “부팅은 단순한 순서가 아니라 일련의 사건들이다” 라는 발상이다. USB가 꽂힌다, 네트워크가 올라온다, 디스크가 마운트된다 — 이런 이벤트가 발생할 때마다 그에 맞는 잡이 트리거되도록 모델을 짰다.

설정은 /etc/init/*.conf 에 한 잡씩 둔다.

description "Hello Web"

start on runlevel [2345]
stop on runlevel [!2345]

respawn
respawn limit 10 5

exec /usr/local/bin/hello-web --port 8080

respawn 한 줄로 자동 재시작이 해결되고, start on filesystem and net-device-up 식으로 이벤트 조합도 가능했다. SysVinit에 비하면 진짜 진보였다.

채택도 빨랐다. Ubuntu 9.10 “Karmic Koala” (2009-10) 에서 SysV 호환 레이어 없이 native Upstart 부팅을 기본화했고, RHEL 6 (2010-11) 이 정식 채택, Google의 ChromeOS와 일부 Fedora 릴리스도 한때 Upstart를 썼다.


Upstart가 짧게 살았던 이유

그런데 RHEL 6 이후 Upstart의 채택은 거기서 멈춘다. 2010년대 초반에 새로 등장한 systemd로 흐름이 갈아탔고, Ubuntu 본진마저 결국 같은 길을 갔다. 단순히 “더 좋은 게 나왔다” 로는 설명이 부족하다.

1. Canonical의 CLA 정책 — Upstart 코드 기여는 Canonical이 요구하는 Contributor License Agreement에 동의해야 받았다. 코드를 받아 상용으로 재배포할 권리를 회사에 양도하는 형태였는데, 다른 진영은 이 조항을 부담스러워했다. 같은 시기 Linux 커널을 비롯해 여러 핵심 프로젝트는 CLA 없이 받는 흐름이었던 것과 대조됐다.

2. 이벤트 모델의 표현력 한계 — “A 이후 B” 같은 단순 의존이 이벤트로는 어색했다. systemd가 들고 나온 의존성 그래프 + Wants= / Requires= / After= 식 선언이 더 자연스러운 표현이라는 평가가 빠르게 자리잡았다.

3. cgroup 기반 추적 부재 — Upstart는 자식 프로세스 추적을 SysVinit과 비슷한 한계 안에서 풀었다. systemd가 cgroup을 fork-bomb에도 끄떡없는 추적 메커니즘으로 활용한 것에 비하면 약했다.

4. Red Hat의 자체 동력 — Fedora/RHEL이 systemd를 적극적으로 이끌면서, 큰 생태계 플레이어 중 하나가 Upstart 진영에서 빠져나갔다. RHEL 6의 Upstart는 단명했고, RHEL 7부터는 systemd로 갔다.

결정타는 Debian과 Ubuntu가 차례로 systemd로 넘어간 시점이다. 그 부분은 뒤에서 따로 정리한다.


3세대: systemd (2010~)

2010년 4월, Red Hat의 Lennart Poettering(레나르트 푀터링)과 Kay Sievers(카이 지버스)가 발표한 글 한 편으로 등장한다 (“Rethinking PID 1”). 핵심 아이디어는 세 가지였다.

1. 의존성 기반 병렬 부팅 — 유닛(*.service, *.socket, *.target 등)이 의존성을 명시하고, 의존성이 풀린 것부터 병렬로 출발한다.

2. 소켓 활성화 (socket activation) — 데몬을 미리 띄우는 대신, systemd가 먼저 listen 소켓을 열어두고 첫 연결이 들어올 때 데몬을 깨운다. macOS의 launchd에서 영감을 받은 모델.

3. cgroup으로 프로세스 추적 — 서비스가 fork한 자식, 손자까지 cgroup에 묶여 있어 정확하게 추적·정리된다. PID 파일 위조나 더블 fork로 도망갈 수 없다.

이 세 가지가 동시에 풀린 게 결정적이었다. 이전 세대가 풀지 못한 한계를 한꺼번에 정리했고, 부팅 시간 단축이라는 가시적 효과도 따라왔다.

다만 systemd는 init만이 아니라 logind, journald, networkd, resolved, timedated 등 시스템 영역의 여러 컴포넌트를 흡수해 갔고, “Unix 철학과 어긋난다"는 비판도 같은 시기에 나왔다. 이 글은 그 논쟁에 한 발 들이지 않고, 채택 흐름만 따라간다.


systemd 채택 연표

발표 시점부터 메이저 배포판들의 채택 시점을 연도순으로 보면 흐름이 분명해진다.

연·월사건
2010-04systemd 첫 발표 (Lennart Poettering, Kay Sievers)
2011-05Fedora 15 — 메이저 배포판 중 첫 systemd 기본 채택
2011-11openSUSE 12.1 — systemd 기본
2012-05Mageia 2 — systemd 기본
2012-10Arch Linux — SysVinit에서 systemd로 전환
2013CoreOS — 출범부터 systemd가 핵심 (컨테이너 호스트 OS)
2014-06RHEL 7 / CentOS 7 — 엔터프라이즈 표준이 바뀐 분기점
2014-11Debian Technical Committee 투표 — systemd를 Jessie의 기본 init으로 결정
2014-11-27Devuan 분기 발표 — “Veteran Unix Admins” 명의
2015-04Debian 8 “Jessie” 정식 출시 (systemd 기본)
2015-04-23Ubuntu 15.04 “Vivid Vervet” — Upstart에서 systemd로
2017-05-25Devuan 1.0 “Jessie” — Debian 기반, systemd 없는 첫 안정판

4년 (2011~2015) 만에 Fedora, RHEL, Debian, Ubuntu, openSUSE, Arch 가 모두 systemd로 정렬된다. 이 정도 속도로 init 같은 핵심 컴포넌트가 통일된 적은 리눅스 역사에 거의 없다.


Debian의 투표와 Devuan 분기

Debian은 의사결정에 시간이 오래 걸리는 프로젝트다. systemd 채택도 예외가 아니어서 2013년 후반부터 Technical Committee 안에서 격론이 오갔고, 결국 2014년 11월 표결로 Debian 8 “Jessie"의 기본 init은 systemd 로 결정된다.

표결 자체는 그것대로 정리됐지만, 같은 결의에 끼어 있던 또 다른 항목 — “패키지가 systemd 의존성을 강제로 걸 수 있느냐” — 가 분기를 불렀다. 결의는 “다른 init 시스템 지원이 권장되지만 의무는 아니다(recommended, but not mandatory)” 로 나왔다. 즉 패키지가 systemd 외에 안 돌게 만들어도 막지 않는다는 의미였다.

이 결과에 반발한 일부 Debian 사용자/개발자가 “Veteran Unix Admins” 이름으로 2014년 11월 27일 Devuan 분기를 발표한다. 약 2년 반의 패키지 감사·수정 끝에 2017년 5월 25일 Devuan 1.0 “Jessie” 가 나왔다 — Debian 8을 베이스로 systemd 훅을 모두 들어내고 SysVinit (또는 OpenRC)을 기본 init으로 하는 버전이다.

Devuan은 이후로도 Debian을 한 단계씩 따라가며 출시를 이어가고 있다. 규모는 작지만 “systemd 없이도 Debian 생태계를 쓰고 싶다"는 수요에 답하는 진영으로 살아남았다.


Ubuntu가 자존심을 접은 결정

Ubuntu에게 init은 단순한 부품이 아니었다. Upstart는 Canonical의 자체 프로젝트였고, 거의 10년간 Ubuntu의 기본 init이었다. 그런데 Debian이 systemd로 결정한 직후, Mark Shuttleworth(마크 셔틀워스)는 “Ubuntu도 upstream(Debian)과 보조를 맞추겠다"고 발표한다.

마이그레이션은 비교적 부드러웠다.

  • Ubuntu 15.04 “Vivid Vervet” (2015-04-23) — 기본 init이 systemd로 전환. 단, Ubuntu Touch (모바일) 는 예외.
  • Ubuntu 15.04 ~ 16.10 — 부팅 시 GRUB에서 Upstart와 systemd를 선택할 수 있는 듀얼 부팅 기간 유지. 회귀 시 도망갈 길을 일정 기간 열어둔 운영적 선택이었다.
  • Ubuntu 16.10 이후 Upstart 옵션 제거. 이 시점부터 Ubuntu는 완전히 systemd 단독.

Canonical 입장에서는 자기 프로젝트를 접고 경쟁 프로젝트를 받아들인 결정이었지만, 그 무렵엔 systemd가 사실상 표준이 된 상태였고 Debian과 다른 init을 유지하는 비용이 더 커졌다.


systemd를 안 쓰는 배포판들

2026년 현재도 systemd가 아닌 init을 기본으로 쓰는 배포판이 남아 있다. 컨테이너 베이스 이미지나 임베디드, 보수적 운영을 위한 선택지로 의외로 자주 등장한다.

배포판기본 init비고
Alpine LinuxOpenRC컨테이너 베이스 이미지 점유율이 높다. musl + busybox + OpenRC 조합
Void Linuxrunit단순함과 빠른 부팅이 강점
GentooOpenRC (기본) / systemd 옵션profile 선택으로 둘 다 사용 가능
DevuanSysVinit / OpenRCDebian 8 분기 후 독자 노선
SlackwareBSD-style init15.0 (2022) 시점에도 SysV가 아닌 BSD 스타일 유지
Artix LinuxOpenRC / runit / s6 / dinitArch 기반의 systemd-free 분기

여기서 중요한 점 하나 — Alpine은 컨테이너 이미지 시장에서 표준급 점유율을 가지고 있다. 즉 “내 노트북은 Ubuntu고 systemd만 만져봤다"고 해도, 컨테이너에 FROM alpine:... 한 줄을 넣는 순간 OpenRC 기반 시스템과 만난다. 다만 컨테이너 안에서는 init이 거의 의미를 갖지 않는다는 별도의 이슈가 있는데, 그건 마지막에서 다시 본다.


같은 작업, 세 가지 표현

세 init 시스템의 차이를 가장 빨리 느끼는 방법은 같은 서비스를 세 가지 형식으로 옆에 두고 보는 것이다. hello-web 이라는 가상의 HTTP 서버를 부팅 시 자동 기동하고 죽으면 자동 재시작하도록 등록한다고 해보자.

SysVinit — /etc/init.d/hello-web

#!/bin/sh
### BEGIN INIT INFO
# Provides:          hello-web
# Required-Start:    $network $remote_fs
# Required-Stop:     $network $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: hello web server
### END INIT INFO

DAEMON=/usr/local/bin/hello-web
PIDFILE=/var/run/hello-web.pid

case "$1" in
  start)
    start-stop-daemon --start --background \
      --make-pidfile --pidfile $PIDFILE \
      --exec $DAEMON
    ;;
  stop)
    start-stop-daemon --stop --pidfile $PIDFILE
    rm -f $PIDFILE
    ;;
  restart)
    $0 stop; sleep 1; $0 start
    ;;
  status)
    [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE) 2>/dev/null \
      && echo "running" || echo "stopped"
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|status}"; exit 1
    ;;
esac

그 후 update-rc.d hello-web defaults (Debian 계열) 또는 chkconfig hello-web on (RHEL 계열) 로 런레벨 링크를 만들어야 한다. 자동 재시작은 별도 도구가 필요하다.

Upstart — /etc/init/hello-web.conf

description "hello web server"

start on runlevel [2345]
stop on runlevel [!2345]

respawn
respawn limit 10 5

exec /usr/local/bin/hello-web --port 8080

스크립트가 아니라 선언문이다. start-stop-daemon, PID 파일, status 분기, 런레벨 링크 — 전부 사라졌다. respawn 한 줄로 자동 재시작도 끝.

systemd — /etc/systemd/system/hello-web.service

[Unit]
Description=hello web server
After=network.target

[Service]
ExecStart=/usr/local/bin/hello-web --port 8080
Restart=on-failure
RestartSec=2

[Install]
WantedBy=multi-user.target

등록은 systemctl enable --now hello-web 한 줄. 의존성을 After=로 명시하고, 재시작 정책도 Restart=on-failure로 명확하다. 자식 프로세스 추적은 cgroup이 알아서 해준다.

같은 의도가 41줄 → 9줄 → 12줄로 짧아진다. 짧아진 만큼 무엇을 init이 책임지고 무엇을 사용자가 짜야 하는지의 경계가 옮겨갔다는 뜻이다.


컨테이너 시대의 init — PID 1 문제

여기까지가 “호스트 OS의 init” 이야기였다. 그런데 컨테이너 시대로 들어오면 init의 의미가 한 번 더 뒤집힌다.

도커 컨테이너 안에서 PID 1은 실제 init 시스템이 아니라 그냥 사용자가 실행한 프로세스다. CMD ["node", "server.js"] 면 node가 PID 1이 된다. 그런데 PID 1에는 두 가지 특별한 책임이 있다.

1. 좀비 자식 프로세스 수확 — 자식이 죽으면 부모가 wait()을 해줘야 좀비가 정리된다. PID 1이 안 해주면 좀비가 영구히 쌓인다.

2. 시그널 처리 — 커널은 PID 1에게 기본 시그널 핸들러를 설정해주지 않는다. 명시적으로 처리하지 않으면 SIGTERM, SIGINT가 무시된다.

대부분의 애플리케이션은 이 두 가지를 신경쓰고 만들어지지 않았다. 그래서 컨테이너 생태계에는 경량 init 들이 등장했다.

  • tini — Docker가 --init 플래그로 채택한 사실상 표준
  • dumb-init — Yelp가 만든 alternatives
  • s6-overlay — 컨테이너 안에서 멀티 프로세스를 다룰 때

Kubernetes는 이 문제를 한 단계 더 위에서 다룬다. Pod 종료 시 컨테이너 PID 1에 SIGTERM을 보내고 grace period 후 SIGKILL로 가는데, 앱이 SIGTERM을 안 잡으면 매번 강제 종료가 발생한다. (이 흐름은 SIGINT, SIGTERM, SIGHUP, SIGKILL — 쿠버네티스 시대의 유닉스 시그널 에서 깊게 다뤘다.)

호스트의 init은 의존성과 부팅 속도를 고민하지만, 컨테이너의 PID 1은 시그널 전파와 좀비 수확 이라는 더 원초적인 책임으로 돌아간다. systemd의 시대에도 init의 본질적 의미는 여전히 현장에서 살아 있다.


한 줄로

SysVinit의 30년은 “단순함이 호환성을 만든 시대"였고, systemd의 5년은 “의존성과 cgroup이 표준을 만든 시대"였다. 그 사이의 Upstart는 좋은 아이디어 한 가지로 잠깐 빛났지만 큰 생태계의 흐름을 못 이긴 사례로 남았다. PID 1 자리에 무엇이 앉느냐는 결국 그 시대 운영체제가 무엇을 가장 중요하게 여기는지를 비추는 거울이다.


참고