My First Impressions of Nix

Michael Lynch

Nix에 대한 첫인상

Nix는 소스 파일에 따라 소프트웨어 환경을 구성하는 도구입니다. Hacker News와 Twitter에서 Nix에 대한 이야기를 점점 더 자주 듣게 되었습니다. 그 아이디어가 마음에 들어 지난 몇 주 동안 이것저것 실험해 보고 있습니다.

코드로 인프라를 관리해 온 경험

10년 전, 컴퓨터 시스템의 설정을 소스 코드로 정의할 수 있게 해주는 도구인 Salt를 알게 되었습니다. 내 컴퓨터와 VM에 어떤 서비스가 설치되어 있는지를 정의하는 git 저장소가 있다는 아이디어가 정말 마음에 들었습니다. 컴퓨터를 완전히 밀어버려도 설정 도구를 다시 실행하면 원래 상태로 되돌릴 수 있었습니다.

그 후 몇 년 동안 Salt를 가지고 놀다가 Ansible을 알게 되었는데, 같은 아이디어를 더 잘 구현했다고 느꼈습니다.

모든 개발은 홈랩 서버 위의 VM에서 진행합니다. 프로젝트마다 별도의 VM을 두고, 모두 Ansible로 관리하고 있습니다.

Ansible의 문제점

Ansible의 가장 큰 문제는 속도가 끔찍하게 느리다는 점입니다. 제 VM 중 하나에 Ansible을 실행하면 보통 10~15분이 걸립니다.

예를 들어 새로운 apt 패키지 foo를 설치하고 싶다고 가정해 보겠습니다. 그냥 sudo apt install --yes foo를 실행해 5초 만에 설치할 것인가요? 아니면 Ansible 롤을 열고 설정을 편집해 foo를 설치하는 단계를 추가한 뒤 플레이북을 실행하고 15분을 기다릴 것인가요? 당연히 저는 전자를 더 자주 하게 되고, 그 결과 실제 환경은 그 환경을 대변해야 할 Ansible 파일과 점점 어긋나게 됩니다.

또 다른 문제는 Ansible의 변경 사항이 하위 호환되지 않는다는 점입니다. 그래서 새로운 Ansible 기능을 활용하기 위해 플레이북을 업데이트하려면 모든 플레이북을 호환되도록 함께 업데이트해야 합니다. 하지만 모든 플레이북을 다시 작성하고 테스트할 의욕이 생기지 않아 결국 오래된 버전에 머물게 됩니다. 저는 아직도 3년 전에 나온 Ansible 2.9를 쓰고 있습니다.

Nix가 끌리는 이유

요즘 Nix와 NixOS에 대한 이야기를 점점 더 많이 듣습니다. 제가 흥미롭게 지켜보는 개발자 중 상당수가 Nix 실험에 대해 이야기하고 있습니다.

가장 인상 깊었던 추천은 Vagrant, Packer, Consul, Terraform 같은 널리 쓰이는 오픈소스 인프라 도구를 만든 회사 Hashicorp의 공동 창업자 Mitchell Hashimoto의 말이었습니다. 그는 Nix를 “최근 몇 년간 배운 기술 중 가장 긍정적인 영향을 준 기술 1위”라고 했습니다.

지금까지의 내 Nix 여정. 여전히 최근 몇 년간 배운 기술 중 가장 긍정적인 영향을 준 기술이라고 생각한다.

Nix라는 아이디어는 Ansible과 많이 닮았습니다. Nix도 코드로 설정을 정의하면 시스템이 그 상태가 되도록 만들어 줍니다.

Nix vs. Ansible

Nix는 몇 가지 중요한 면에서 Ansible과 다르며, 그 점이 흥미롭습니다.

Nix는 Ansible보다 빠릅니다

Ansible은 자신이 구성하는 시스템의 ‘상태’라는 개념이 없습니다. 제 VM 중 하나에 Ansible을 실행하는 데 15분이 걸린다면, 1분 뒤에 같은 플레이북을 다시 실행해도 약 10분이 걸립니다. 두 번 실행 사이에 패키지 업데이트가 없으니 약간의 시간은 절약되겠지만, 여전히 거의 같은 작업을 반복합니다.

Ansible은 “아, 방금 이 머신을 구성했으니 지금 할 일이 없네”라고 말하지 않습니다. 마지막 실행 이후 무슨 일이 있었을지 알 수 없기 때문에 매번 모든 설정을 다시 수행해야 합니다.

반면 Nix는 상태 개념이 있습니다. 200줄짜리 Nix 설정에서 한 줄을 바꿨다면, 나머지 199줄에 해당하는 작업을 다시 할 필요가 없습니다. Nix는 시스템 상태를 설정 파일과 비교해 한 줄짜리 변경만 적용하면 된다는 것을 알아차립니다. 그리고 그 변경은 보통 몇 초 만에 끝납니다.

수정 (2023-06-19): 경험이 더 많은 독자 한 분이 Nix가 제가 생각한 방식으로는 상태를 가지지 않는다고 정정해 주었습니다:

Nix가 빠른 이유는 상태를 가지기 때문이 아닙니다. 함수형이고 재현 가능하기 때문에 정확성을 해치지 않으면서 캐시를 활용할 수 있기 때문에 빠릅니다.

저는 Nix가 어떤 작업들이 어떤 시스템 상태를 만드는지를 추적한다고 생각했습니다. 예를 들어 작업 X와 Y를 수행하면 상태 A가 되고, 작업 X, Y, Z를 수행하면 상태 B가 된다고 할 때, 상태 A에 있는 Nix 시스템은 상태 B로 가기 위해 작업 Z만 수행하면 된다고 생각했습니다.

새롭게 이해한 바로는, 상태 A에 있는 Nix 시스템에 상태 B로 가라고 요청하면 Nix는 작업 X, Y, Z를 모두 수행하지만, 작업 X와 Y의 결과는 캐시되어 있어 거의 즉시 완료된다는 것입니다.

Nix는 로컬 설정을 우선합니다

Ansible은 네트워크를 통해 시스템을 구성하도록 설계되었습니다. 대상을 localhost로 지정할 수는 있지만, 그건 Ansible이 최적화된 시나리오가 아닙니다.

Nix는 자신이 속한 환경을 구성하도록 설계되었습니다. Nix에서는 환경에 무엇이 있어야 하는지를 정의하면, Nix가 그 자리에서 그 환경을 만들어 줍니다. NixOS에서는 운영체제 전체를 정의하고, NixOS가 운영체제를 그 상태로 만듭니다. 파일 시스템, 리눅스 커널, 부트로더 같은 낮은 수준의 요소까지 바꿀 수 있습니다.

이 점은 Ansible 플레이북 하나를 최신 Ansible 버전을 쓰도록 업그레이드하려면 시스템의 모든 플레이북을 함께 업그레이드해야 했던 제 문제를 해결해 줍니다. 서로 다른 버전의 Nix를 실행하는 여러 Nix 시스템을 함께 운용해도 아무 문제가 없습니다. Ansible 2.9가 필요한 파일, 2.10이 필요한 파일, 2.14가 필요한 파일이 섞여 있다면 이들을 동시에 다루는 일이 매우 번거롭습니다.

Nix의 변경은 원자적입니다

Ansible에서는 구성 도중 절반에서 실패해 시스템이 정의되지 않은 상태로 남기 쉽습니다.

Nix에서는 변경이 원자적으로 이루어집니다. Nix는 시스템을 원하는 상태로 만들거나, 아니면 설정을 변경하기 전 상태로 롤백합니다.

도움이 되었던 Nix 자료

Nix에 대해 가장 많이 듣는 불만 중 하나는 문서가 부족하거나 잘못되었거나 질이 낮다는 것입니다. 제 경험으로는 문서가 숙련된 Nix 사용자를 대상으로 쓰여 있다는 느낌이 들었습니다.

제가 찾은 많은 Nix 문서는 “그냥 이 줄들을 추가하세요!”라고 말합니다.

어?

어떤 파일에요? 그리고 파일의 어디에 그 줄들을 추가하라는 거죠?

지금까지 찾은 자료 중 가장 좋았던 것들은 다음과 같습니다:

  • Zero to Nix: 지금까지 찾은 것 중 가장 좋은 입문용 Nix 튜토리얼 모음입니다. Determinate Systems에서 만들었으며, 이곳에서는 초보자 친화적인 블로그 글을 통해 Nix 활용법을 소개합니다.
  • NixOS for the Impatient: 이전에도 NixOS를 두어 번 설치해 보려 했지만, 이 글 덕분에 생각보다 훨씬 쉽다는 것을 알게 되었고 마침내 설치 과정을 끝까지 밀어붙일 수 있었습니다.
  • “Some notes on using nix” by Julia Evans: Julia 역시 Nix를 막 시작한 입문자라, 생태계에 아직 낯선 부분이 많은 상황에서도 어떤 장애물을 만났고 어떻게 우회했는지를 보는 것이 도움이 되었습니다.

실패한 시도 #1: VM에 NixOS 설치

Proxmox VM에서 “NixOS for the Impatient” 튜토리얼을 따라 했고, 처음에는 모든 것이 잘 되었습니다. 그러다 튜토리얼에서 호스트 이름을 바꾸는 부분에 이르러 재부팅을 했는데, VM이 이상한 상태에 빠졌습니다. 부팅은 되는 것 같은데 로그인이 되지 않았습니다. 로그인 화면에서 비밀번호를 입력하면 화면이 그대로 멈춰 버렸습니다.

NixOS를 Proxmox VM 서버에 성공적으로 설치했지만, 두 번째 부팅에서 로그인 후 멈췄습니다.

실패한 시도 #2: Raspberry Pi 4에 NixOS 설치

VM이 안 되니 다음으로 당연한 선택은 베어 메탈이라고 생각했습니다. 마침 여분의 Raspberry Pi 4가 하나 있어 재미있는 하드웨어로 실험해 보면 좋겠다고 생각했습니다.

Raspberry Pi 4에 NixOS를 설치하는 공식처럼 보이는 튜토리얼 두 개를 찾았습니다:

두 튜토리얼 모두 이미 Nix 환경을 실행하고 있다는 것을 전제로 한다는 점이 문제였습니다. 저는 메인 컴퓨터인 Win10 시스템에서 microSD를 준비하려 했기 때문에 Nix가 설치되어 있지 않았습니다.

NixOS 다운로드 페이지에는 64비트 ARM 이미지가 있습니다. Raspberry Pi 4가 64비트 ARM을 지원하니 한번 시도해 보기로 했습니다.

제가 선호하는 microSD 플래싱 도구인 Balena Etcher를 실행했습니다. 첫 번째 경고 신호는 Etcher가 “지금 무슨 생각으로 이러는 거예요? 이건 부팅 가능한 이미지도 아닌데요”라는 식으로 반응한 것이었습니다.

파티션 테이블 없음. 부팅 가능한 이미지가 아닌 것 같습니다. 이미지에 파티션 테이블이 포함되어 있지 않으며, 기기에서 인식되거나 부팅되지 않을 수 있습니다.

그래도 계속 진행했습니다! 하지만 microSD로 부팅을 시도하자 Pi 역시 이것이 부팅 가능한 이미지가 아니라는 데 동의한 듯 그냥 멈춰 버렸습니다.

'진행 상황: USB-MSD 부팅 모드 시도 중'이라고 표시된 Pi 부팅 화면

공식 Raspberry Pi Imager 유틸리티로 같은 이미지를 플래싱해 보았지만 결과는 같았습니다.

업데이트: 마침내 해결했습니다.

성공: Dell 미니 컴퓨터에 NixOS 설치

업무 테스트 대부분을 Dell Optiplex 7040에서 진행합니다. 밀어버려도 되는 베어 메탈 머신은 그것뿐이어서 거기에 시도해 보았습니다.

모든 과정이 “NixOS for the Impatient”에 나온 대로 정확히 진행되었습니다. 설치는 시작부터 끝까지 약 10분이 걸렸습니다. 그중 7분은 그냥 파일을 복사하는 시간이었습니다. 이번은 테스트용 기기라 암호화를 건너뛰어 재부팅할 때마다 비밀번호를 입력하는 단계를 없앴습니다.

Dell Optiplex 7040에 NixOS 설치하기 (파일 복사 구간은 빠르게 재생했습니다)

결국 완전히 동작하는 NixOS 설치를 갖추게 되었습니다!

실패한 시도 #3: Raspberry Pi 4에 NixOS 설치 (재도전)

이제 동작하는 NixOS 머신이 생겼으니 Raspberry Pi에 다시 도전해 보고 싶었습니다. 이전에 막혔던 이유는 microSD 이미지를 준비할 Nix 환경이 없었기 때문인데, 이제는 그 환경이 생겼습니다.

nix.dev 튜토리얼을 따라 했고, 첫 시도보다는 더 진행되었지만 여전히 부팅에 실패했습니다. Pi는 다채로운 색상의 화면이 나온 뒤 그대로 멈췄습니다:

NixOS 시스템에서 NixOS Pi aarch64 microSD 이미지를 플래싱해 Pi에서 부팅하자 다채로운 화면에서 멈췄습니다.

업데이트: 마침내 해결했습니다.

작업 1: SSH 접속 설정

자, 이제 Dell Optiplex에 설치된 동작하는 NixOS로 돌아오겠습니다.

메인 머신에서 NixOS 시스템으로 SSH 접속을 해야 했습니다. 그러려면 시스템에 SSH 키를 올려야 했습니다.

NixOS에서 콘솔 애플리케이션을 열고 sudo nano /etc/nixos/configuration.nix를 입력했습니다. 그런 다음 environment.systemPackages로 이동해 다음 줄들을 추가했습니다:

  environment.systemPackages = with pkgs; [
    vim
    curl
  ];

변경 사항을 적용하기 위해 다음을 실행했습니다:

sudo nixos-rebuild switch

이제 vimcurl을 사용할 수 있게 되었으니 GitHub에서 SSH 공개 키를 가져왔습니다:

sudo mkdir -p /etc/nixos/ssh

GITHUB_USERNAME='mtlynch'
curl "https://github.com/${GITHUB_USERNAME}.keys" | \
  sudo tee --append /etc/nixos/ssh/authorized_keys

다음으로 sudo vim /etc/nixos/configuration.nix를 실행하고 다음 줄들을 추가했습니다:

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;
  users.users.mike.openssh.authorizedKeys.keyFiles = [
    /etc/nixos/ssh/authorized_keys
  ];

마지막으로 다시 빌드하고 재부팅했습니다. 재부팅이 꼭 필요했는지는 잘 모르겠습니다:

sudo nixos-rebuild switch && sudo reboot

그리고 성공했습니다! 그 뒤로는 메인 컴퓨터에서 NixOS 시스템으로 ssh 접속을 할 수 있었습니다.

작업 2: Gnome 잡동사니 제거

OS에서 가장 먼저 눈에 띈 것은 잡동사니가 많다는 점이었습니다. 연락처, 날씨 앱 같은 원치 않는 기본 앱이 잔뜩 있었습니다:

어떻게 없애는지 검색해 보니 Gnome 셸의 기본 애플리케이션이라는 것을 알게 되었습니다. /etc/nixos/configuration.nix에 다음 한 줄을 추가하면 비활성화할 수 있습니다:

services.gnome.core-utilities.enable = false;

또는 각 유틸리티를 하나씩 제거할 수도 있습니다:

 environment.gnome.excludePackages = with pkgs.gnome; [
    baobab      # disk usage analyzer
    cheese      # photo booth
    eog         # image viewer
    epiphany    # web browser
    gedit       # text editor
    simple-scan # document scanner
    totem       # video player
    yelp        # help viewer
    evince      # document viewer
    file-roller # archive manager
    geary       # email client
    seahorse    # password manager

    gnome-calculator
    gnome-calendar
    gnome-characters
    gnome-clocks
    gnome-contacts
    gnome-font-viewer
    gnome-logs
    gnome-maps
    gnome-music
    gnome-screenshot
    gnome-system-monitor
    gnome-weather
    gnome-disk-utility
    pkgs.gnome-connections
  ];

저는 “전부 비활성화” 옵션을 선택하고 다시 빌드했습니다:

sudo nixos-rebuild switch

그러자 잡동사니가 모두 사라졌습니다:

작업 3: 시스템 모니터 다시 가져오기 (실패)

유지할 가치가 있다고 생각한 Gnome 도구는 시스템 모니터 하나였습니다. environment.systemPackages 목록에 추가한 뒤 다시 빌드해 보았지만 빌드에 실패했습니다:

$ sudo nixos-rebuild switch
building Nix...
building the system configuration...
error: undefined variable 'gnome-system-monitor'

       at /etc/nixos/configuration.nix:130:5:

          129|     curl
          130|     gnome-system-monitor
             |     ^
          131|   ];
(use '--show-trace' to show detailed location information)

다른 가능한 이름인 gnome-shell-system-monitor 같은 것도 시도해 보았지만 설치 방법을 찾지 못했습니다.

수정 (2023-06-19): 올바른 패키지 이름은 gnome.gnome-system-monitor라는 점을 독자분들이 알려 주었습니다. 제가 놓친 부분은 search.nixos.org에서 패키지를 검색할 수 있다는 것이었습니다.

앞으로 더 알아보고 싶은 것들

Nix와 NixOS를 며칠 써 본 소감은 만족스럽습니다. 들었던 대로입니다. 잘 활용하면 엄청나게 강력할 수 있지만, 초기 학습 비용이 많이 들고 정보를 여기저기서 찾아봐야 합니다.

아직 겉만 살짝 만져본 수준이라 앞으로 Nix에 대해 더 배우고 싶은 것들은 다음과 같습니다.

NixOS 시스템에서 VS Code Remote SSH 사용하기

모든 개발은 VS Code의 Remote SSH로 진행합니다. VS Code에서 NixOS 시스템으로 원격 접속을 시도하면 설치가 실패합니다. VS Code가 대상 시스템에 일종의 서버를 설치해야 하는데, NixOS에 설치하는 방법을 모르는 것 같습니다.

이 문제를 해결해 줄 것 같은 nixos-vscode-server라는 git 저장소가 있습니다. 아직 시도해 보지는 않았습니다.

Nix의 주요 개념들이 어떻게 맞물리는지

“flakes”나 “derivations” 같은 단어를 보지만 아직 무슨 뜻인지 모릅니다. Nix 언어 문법도 아직 이해하지 못했지만, JavaScript나 Python과 어느 정도 비슷해서 일단 어림짐작으로 넘어가고 있습니다. 하지만 Nix를 효과적으로 쓰려면 분명 언어를 배워야 할 것입니다.

결정성은 언제 보장되는가?

Nix에 대한 논의를 보면 가장 자주 언급되는 특징 중 하나가 Nix는 결정적(deterministic)이라는 점입니다.

지금까지는 어떻게 결정적인지 이해가 되지 않습니다. 설치할 패키지를 지정할 때 무결성 해시는커녕 버전 번호조차 지정하지 않았습니다. 같은 Nix 설정을 1년 후에 실행하면, 지정한 vimcurl 패키지의 다른 버전을 설치하게 되므로 다른 시스템이 될 것이라고 생각합니다.

패키지 버전을 더 정확하게 지정하는 방법이 있을 거라 생각하지만, 아직 배우지 못했습니다.

나는 누구를 신뢰하는가?

패키지를 지정할 때 단순히 패키지 이름 목록을 적었습니다:

  environment.systemPackages = with pkgs; [
    vim
    curl
  ];

위와 같이 패키지를 지정할 수 있다는 것은 Nix가 기본 저장소에서 패키지를 가져오고 있다는 뜻입니다. 저장소는 여러 개인가요? 어떤 저장소를 사용할지는 어떻게 정하나요?

원문은 Michael Lynch님이 에 게재했습니다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.