1P by GN⁺ 2일전 | ★ favorite | 댓글 1개
  • Nitro는 임베디드, 서버, 데스크톱, 컨테이너에 모두 적용 가능한 초소형 프로세스 슈퍼바이저 및 init 시스템
  • 시스템 상태를 RAM에만 저장하여 읽기 전용 파일 시스템에서도 무리 없이 동작하며, 빠르고 효율적인 이벤트 기반 설계를 제공함
  • 구성 방식은 단순한 스크립트 디렉터리 구조로, 복잡한 설정 파일이나 부가적인 빌드 과정 없이 서비스 관리가 가능함
  • Parmetrized 서비스, 견고한 재시작, 개별 서비스별 신뢰성 높은 로깅 기능 등 컨테이너, 임베디드 환경에 최적화된 기능을 지원함
  • nitroctl 툴을 통한 원격 제어, 신호 기반 동작 제어 등 높은 유연성과 통제력을 보장함

개요

Nitro는 Linux에서 pid 1로도 사용할 수 있는 초소형 프로세스 슈퍼바이저

주요 적용 분야는 아래와 같음

  • 임베디드, 데스크톱, 서버 등 다양한 용도의 Linux 머신용 init
  • Linux initramfs의 init
  • Docker/Podman/LXC/Kubernetes 등 컨테이너 환경의 init
  • POSIX 시스템에서 권한 없이 동작하는 슈퍼비전 데몬

구성은 디렉터리 기반 스크립트 구조를 사용하며, 기본 위치는 /etc/nitro

요구사항

  • 커널의 Unix 소켓 지원 필요
  • tmpfs 또는 쓰기 가능한 /run 디렉터리 필요

다른 시스템 대비 장점

  • 모든 상태 정보는 RAM에만 유지되어 읽기 전용 루트 파일 시스템에서도 별도 트릭 없이 동작함
  • 이벤트 기반, 폴링 없는 동작 방식으로 효율성 제공
  • 런타임 중 메모리 동적 할당이 없음
  • 파일 디스크립터가 무한정 소모되지 않음
  • 하나의 self-contained 바이너리(옵션으로 제어 바이너리 추가) 만 필요함
  • 설정 파일 변환 및 컴파일 필요 없음, 서비스는 스크립트가 들어 있는 단순 디렉터리
  • 서비스 재시작 및 로깅 체인 지원
  • 시스템 시계가 정확하지 않아도 정상 동작
  • FreeBSD에서 /etc/ttys를 통해 실행 가능
  • musl libc 사용 시 초소형 static 바이너리 제작 가능

서비스 관리

  • 각 서비스 디렉터리(기본 /etc/nitro 내부)는 아래 파일을 포함할 수 있음

    • setup: 서비스 시작 전 실행되는 (옵션) 스크립트, 정상 종료(0) 시에만 서비스 시작 가능
    • run: 서비스 동작 스크립트, 종료되지 않는 한 서비스가 살아있는 상태로 인식됨, 미구현 시 one-shot 서비스로 처리됨
    • finish: run 종료 후 실행되는 (옵션) 스크립트, 종료 상태 및 시그널 값을 인자로 전달
    • log: 다른 서비스 디렉터리를 가리키는 심볼릭 링크, run 출력 내용을 해당 서비스의 입력으로 파이프 연결(로깅 체인 활용 가능)
    • down: 이 파일이 존재하면 nitro가 기본적으로 이 서비스를 올리지 않음
    • 디렉터리명이 '@'로 끝나면 무시되어 파라미터 서비스로 활용 가능
    • 서비스명은 64글자 미만, /, ,, 줄바꿈 문자를 포함할 수 없음
  • runit의 chpst 유틸리티가 run 스크립트 작성 시 유용함

특수 서비스

  • LOG: log 링크가 없는 모든 서비스의 로그 기록용 디폴트 서비스
  • SYS: SYS/setup은 모든 서비스 구동 전 실행, 순서 있는 서비스 구동 구현 가능
    • SYS/finish: 전체 종료 단계 진입 전 실행
    • SYS/final: 모든 프로세스 종료 후 실행
    • SYS/fatal: 치명적 에러 발생 시 종료 대신 실행(있을 경우)
    • SYS/reincarnate: shutdown 대신 실행되어 예컨대 initramfs 재구현 등에 활용 가능

파라미터라이즈드 서비스

  • '@'로 끝나는 서비스 디렉터리는 nitro가 무시하지만, 심볼릭 링크 또는 nitroctl 명령을 통해 직접 지정 가능
  • '@' 뒤 파라미터가 첫 번째 인자로 각 스크립트에 전달됨
    • 예: agetty@/runagetty@tty1 심볼릭 링크가 있으면 agetty@/run tty1 실행
    • nitroctl up agetty@tty2 입력 시 agetty@/run tty2 실행 가능(디렉터리 존재 여부 무관)

동작 모드

  • 전체 라이프사이클은 부팅, 서비스 실행(슈퍼비전), 종료 세 단계로 구성
    • 부팅: 특수 서비스 SYS가 존재하면 setup부터 실행, 이후 모든 non-down 서비스 실행
    • 서비스가 종료되면 재시작, 단 최근 재시작이 빠르면 2초 대기
    • nitroctl Reboot 또는 Shutdown으로 종료 신호 전달 가능
      • 이때 SYS/finish → 모든 서비스 SIGTERM(최대 7초 대기) → SIGKILL → SYS/final → 종료 시퀀스
    • 컨테이너나 권한 없는 슈퍼바이저용일 경우 프로세스만 종료

nitroctl을 이용한 제어

  • nitroctl CLI 도구로 멀리서 nitro를 제어할 수 있음

명령어 예시:

  • list: 서비스 목록, 상태, PID, uptime, 마지막 종료 상태 출력
  • up/down/start/stop/restart: 서비스 시작·중지·재시작 등 제어
  • 신호 전송: p(SIGSTOP), c(SIGCONT), h(SIGHUP), a(SIGALRM), i(SIGINT), q(SIGQUIT), 1(SIGUSR1), 2(SIGUSR2), t(SIGTERM), k(SIGKILL)
  • pidof: 지정 서비스의 PID 출력
  • rescan: 서비스 디렉터리 재읽기, 추가·제거 서비스 반영
  • Shutdown/Reboot: 전체 시스템 종료·재부팅

신호를 통한 제어

  • nitro 프로세스에 시그널 직접 전송으로 컨트롤 가능
    • SIGHUP: 서비스 재스캔(rescan)
    • SIGINT: 재부팅
    • SIGTERM: 종료(nitro가 pid 1이 아니면)

Linux에서 init으로서의 nitro

  • Nitro는 자체 포함형 바이너리로 Linux pid 1로 직접 부팅 가능
  • /dev, /run을 필요 시 마운트하며, 기타 동작은 SYS/setup에서 처리
  • Ctrl-Alt-Del 이벤트에 질서 정연한 재부팅 트리거

Docker 컨테이너에서 init으로서 Nitro 사용

  • Nitro는 정적으로 빌드되어 컨테이너에 간단히 포함 가능
  • /run이 컨테이너에 존재해야 기본 소켓 경로 사용 가능
  • 컨트롤 소켓을 바인드 마운트 처리하면 외부에서 nitroctl로 원격 제어 가능

FreeBSD에서의 Nitro

  • /etc/ttys에 다음 줄 추가로 FreeBSD init에서 nitro를 슈퍼바이즈 가능
    /etc/nitro "/usr/local/sbin/nitro" "" on
    

저자

감사

  • daemontools, freedt, runit, perp, s6 등 기존 프로세스 슈퍼비전 시스템들의 상세 분석 위에서 개발됨

라이선스

  • 0BSD 라이선스(자세한 내용은 LICENSE 파일 참조)
Hacker News 의견
  • runit와의 비교를 보고 싶음. runit는 극도로 미니멀하면서도 거의 완전한 init 시스템임. 컨트롤 디렉터리, 선언적(declarative)이 아닌 의존성, 비슷한 스크립트 구성, 로깅 접근 방식 등 비슷한 점이 많음. 설명 페이지에도 runit 이야기가 잠깐 나오며 chpst 유틸을 함께 쓰는 것을 추천함. 차별점으로는 하나의 서비스 디렉터리로 여러 유사 프로세스(예: agetty)를 파라미터화하여 관리하는 구조가 좋다고 생각함. reboot나 shutdown을 단일 바이너리(nitroctl)로 바로 실행할 수 있음. 반면 runit는 여러 개의 바이너리 구조임

    • 지난 해 runit로 프로세스를 관리하던 마지막 서버들을 은퇴시키면서 아쉬움이 컸음. 약 15년 전에 처음 runit 서비스를 직접 짜면서 이게 리눅스에서 서비스 관리하는 표준 방식이라 믿었음. 이후 5년간 리눅스를 떠나 있다 돌아오니 systemd가 기본이 되어 있었고, 나쁜 평을 여러 번 들었지만 점차 왜곡된 반감이 많다는 걸 알게 되었음. 현재는 파충류 vivarium에서 Pi Zero로 카메라와 온도 데이터 스트리밍 서비스를 돌리고 있는데 systemd로 세팅하는 것이 굉장히 쉬웠음. OpenSuse 데스크톱이나 업무용 노트북에서도 systemd로 다양한 서비스를 간단하게 운용할 수 있었음. ‘표준이 있다는 건 오히려 좋은 일’이란 생각이 듦

    • runit와 nitro의 적절한 미니멀 비교가 2024년에 발표된 Leah Neukirchen의 발표 슬라이드(PDF)에 있음
      https://leahneukirchen.org/talks/#nitroyetanotherinitsy

    • Leah Neukirchen은 Void Linux 커뮤니티에서 활발히 활동하는 분임. 이 프로젝트가 Void와 밀접히 연관될 것으로 예상함. 좀 더 공식적으로 Void에서 nitro를 사용하는 방법에 대한 글을 써줬으면 좋겠음

    • “선언적(declarative) 의존성이 없다”는 점이 장점이라는 것인지 궁금함. systemd를 init로 비판하는 의견은 많이 들어봤지만 선언적 설계 자체를 비판하는 경우는 드물었음. 이유가 있는지 자세히 듣고 싶음

    • Void Linux에서 runit를 접하게 되어 init 시스템으로 잘 쓰고 있지만, UI와 문서화가 부족한 점이 아쉬웠음. 특히 로깅 설정이 정말 어려웠음. 비슷하게 단순하지만 더 합리적 기본값, 더 직관적인 UI, 더 나은 문서를 가진 대안을 써보고 싶음

  • 컨테이너에서 init 시스템을 돌리자는 이야기를 볼 때마다 항상 고민임. 실제로 필요에 의해 설계하는 경우도 있지만, 오히려 너무 복잡하게 만드는 경우가 자주 보였음 (특히 Kubernetes, 클라우드 환경에서 실제로 분리 설계를 더 제대로 했어야 함). ‘어차피 다들 이렇게 쓰니까’라는 게 현상인 것 같기도 한데, 굳이 ‘더 잘 만들겠다’면서 문제가 퍼지는 게 나은지, 아니면 기존 솔루션으로 격하게 실패하도록 내버려두는 게 나은지 항상 애매함

    • 어플리케이션 컨테이너는 유닉스 철학 ‘한 가지 일만 잘하라’를 따라야 한다고 생각함. 그런데 컨테이너에서 어떤 이유로든 fork를 한다면, PID 1에 진짜 init가 있어야 한다고 봄

    • 로보틱스 분야에서 겪은 바로는, 많은 컨테이너가 본래 베어메탈에서 돌던 복잡한 시스템이 컨테이너로 옮겨온 경우임. 프로세스간에 비구조화된 RPC가 많아 굳이 여러 개의 별도 컨테이너로 잘게 쪼갤 메리트가 없음. monolithic 앱 컨테이너 내부에서 여러 프로세스를 띄우기엔 supervisor, runit, systemd, tmux까지 각양각색 옵션들이 통용됨

    • Fly.io, Render, Google Cloud Run처럼 컨테이너 단위로 과금하는 호스팅을 사용한 적 있음. 가격 때문에 한 컨테이너에 여러 프로세스를 띄워야 할 때가 종종 있음

  • NixOS의 새로운 기능인 modular-services가 Nixpkgs에 포함되었음. 새로운 init 시스템 혹은 새 커널로 NixOS 포팅이 훨씬 쉬워질 예정이라, 지금이 nitro 같은 실험을 해보기 좋은 시기라고 생각함

  • Chimera Linux에서 쓰고 있는 dinit과 nitro를 비교해보고 싶음. readme를 빠르게 훑어보니 서비스 의존성 관리가 아직 안되는 것 같아 보임
    dinit: https://github.com/davmac314/dinit

    • Nitro는 선언적으로 서비스 의존성을 다루지 않음. 명령 하나로 서비스 간의 의존 그래프를 예쁘게 보는 게 불가능함. 하지만 setup 스크립트에서 필요한 서비스를 명시해두면 해당 서비스가 떠있는지 확인하고 자동으로 기다렸다 리트라이 함. 의존성 그래프를 보려면 grep 같은 걸로 스크립트를 직접 짜는 수밖에 없음. 반면 서비스가 죽을 때 연쇄적으로 dependent 서비스도 제대로 내리는 걸 깜빡하기 쉬운데, nitro 자체로는 이런 걸 탐지할 편리한 방법이 없음

    • Artix Linux에서 dinit을 써봤는데 정말 가볍고 인상적이었음
      Artix FAQ: https://artixlinux.org/faq.php

  • 이런 저수준 프로젝트들을 보면 정말 흥미로움. systemd가 전통적인 SysV·POSIX 틀을 넘어서 리눅스 커널 특화 기능을 잘 활용한 점이 좋았음. 하지만 그게 끝이 아니길 바라고, 계속해서 새로운 아이디어와 혁신이 나와주었으면 함. 최근 직접 제조용 자동화에서 UEFI 펌웨어로부터 바로 netboot되는 Linux 커널에 Go로 직접 짠 단일 init 바이너리만 내장한 셋업을 구현해봄. 직접 가져온 코드와 고수준 언어만으로 OS 환경을 다 제어하니 각종 서브프로세스와 수많은 텍스트 설정파일을 관리할 필요 없다는 점이 정말 자유로웠음

  • 약 13년 전에 C로 직접 init 시스템을 구축한 경험이 있음. 예정보다 훨씬 많은 노력이 필요했고, GUI와 백엔드를 성능 낮은 하드웨어에서 빠르게 부팅시키는 데 사용함. 재미있는 프로그래밍 연습이었으나 이미 비슷한 솔루션이 존재했을지도 모르겠다는 자각이 나중에 들었음. 동료가 같은 회사에서 또 다른 init을 만드는 바람에 내 첫 버전은 거의 libc 외에 의존성 없이 가벼웠지만, 동료의 버전은 libevent 기반으로 더 고급 기능이 많았음

  • AWS Nitro와 이름과 기능이 겹친다는 점이 신경 쓰임
    https://docs.aws.amazon.com/whitepapers/latest/…

    • 이름만 겹칠 뿐, init 시스템과 하이퍼바이저는 근본적으로 완전히 다름

    • 문제 생길 일은 거의 없다고 봄. 하나는 누구나 쓸 수 있는 init 시스템이고, AWS Nitro는 기업 내부에서만 쓰는 KVM 포크임

  • s6와 비교해 nitro가 어떤지 궁금함. 최근 도커 컨테이너에 s6로 init 시스템을 구성해봤는데, s6-overlay로 많은 파일을 직접 만들어야 했고 생각보다 직관적이지 않았음

  • Distrust에서 rust로 500라인 이하의 초간단 init 시스템을 직접 작성해서, 보안 필수 enclave 환경에서 몇몇 클라이언트가 실서비스로 사용 중임. rust 표준 라이브러리만 써서 감사를 매우 쉽게 했음
    https://git.distrust.co/public/nit

    • 깔끔해 보이지만(nit보다 33% 더 큼), readme에는 빌드 방법만 나와 있고, 실제 인터페이스나 작동 방식은 설명이 없음
  • 의존성 지정 불가, 유저/그룹 설정 없음, 순서 수동 지정 필요, 병렬 서비스 실행 없음, 리소스 관리 없음. 이런 게 빠진 시스템을 init 시스템이라고 부르지 않았으면 함. 그냥 베어본 process supervisor임

    • 실제로 이 모든 기능을 잘 해냄(심지어 systemd보다 더 나았다는 경험임). 나는 nitro 대신 오랜 기간 daemontools(그리고 nitro는 그 계승)만을 써왔음. 사용법이 믿을 수 없이 쉽고 안정적이고, 이해하기 쉬움. 의존성 문제에 대해서도 ‘요건 각자 알아서, 대신 간단·저렴·신뢰성 좋은 툴을 제공합니다’라는 djb/daemontools 스타일이 훨씬 실무적임