My First Impressions of Nix

Michael Lynch

Nix에 대한 첫인상

원문은 Michael Lynch님이 에 게재했습니다. 이 블로그 구독하기

Nix는 소스 파일에 따라 소프트웨어 환경을 구성하는 도구다. Hacker News와 트위터에서 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를 실험한 경험을 공유하고 있다.

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

지금까지의 나의 Nix 여정. 여전히 최근 몇 년간 배운 기술 중 가장 긍정적인 영향을 준 기술 1위라는 생각에는 변함이 없다.

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

Nix vs. Ansible

Nix는 Ansible과 몇 가지 중요한 점에서 다른데, 그 부분이 흥미롭게 다가왔다.

Nix는 Ansible보다 빠르다

Ansible에는 자신이 설정하는 시스템의 ‘상태’라는 개념이 없다. 내 VM 중 하나에서 Ansible을 실행하는 데 15분이 걸린다면, 1분 뒤에 같은 플레이북을 다시 실행해도 10분 정도가 걸린다. 두 실행 사이에 패키지 업데이트가 없을 가능성이 높아 약간의 시간은 절약되지만, 그래도 거의 같은 작업을 반복한다.

Ansible은 “아, 방금 이 머신을 설정했으니 이제 할 일이 없네”라고 말하지 않는다. 마지막 실행 이후 무슨 일이 있었을지 모르니 매번 모든 설정을 다시 수행해야 한다.

반면 Nix는 상태라는 개념을 가지고 있다. 200줄짜리 Nix 설정에서 한 줄만 바꾸면 나머지 199줄에 대한 작업을 다시 할 필요가 없다. 시스템 상태를 설정 파일과 비교해 한 줄의 변경만 적용하면 된다는 것을 파악하고, 그 변경은 보통 몇 초 안에 끝난다.

수정 (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를 몇 번 설치해보려 했지만, 이 글을 보고 생각보다 쉽다는 확신이 들어 마침내 과정을 끝까지 밀어붙일 수 있었다.
  • Julia Evans의 “Some notes on using nix”: Julia 역시 Nix 초심자라, 생태계의 많은 부분이 여전히 새롭고 낯설었음에도 그녀가 마주한 장애물과 이를 어떻게 헤쳐나갔는지를 보는 것이 도움이 됐다.

실패한 시도 #1: VM에서의 NixOS

Proxmox VM에서 “NixOS for the Impatient” 튜토리얼을 따라 했고, 처음에는 모든 게 잘 됐다. 그러다 튜토리얼에서 호스트명을 바꾸는 부분에 이르러 변경 사항을 적용하려고 재부팅했는데, VM이 부팅은 되는 것 같지만 로그인은 안 되는 이상한 상태에 빠졌다. 로그인 화면에서 비밀번호를 입력하면 화면이 그냥 멈춰버렸다.

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

실패한 시도 #2: Raspberry Pi 4에서의 NixOS

VM이 안 되니 베어메탈이 다음으로 논리적인 선택지라고 생각했다. 마침 여분의 Raspberry Pi 4가 있어, Pi로 실험하면 재미있을 것 같았다.

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

두 튜토리얼 모두 이미 Nix 환경을 실행 중이라는 전제로 쓰여 있다는 게 문제였다. 나는 메인 컴퓨터인 Win10 시스템에서 microSD를 준비하려고 했기 때문에 Nix가 깔려 있지 않았다.

NixOS 다운로드 페이지에는 64비트 ARM 이미지가 올라와 있다. Raspberry Pi 4는 64비트 ARM을 지원하니, 그걸 한번 써보기로 했다.

microSD를 플래싱할 때 주로 쓰는 Balena Etcher를 실행했다. 첫 번째 경고 신호는 Etcher가 사실상 “지금 무슨 생각을 하는 거야? 이건 부팅 가능한 이미지도 아니야”라고 말했을 때였다.

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

그래도 그냥 밀고 나갔다! 하지만 microSD로 부팅을 시도하자 Pi 역시 이건 부팅 가능한 이미지가 아니라며 그냥 멈춰버렸다.

'Progress: Trying boot mode 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 Shell의 기본 애플리케이션이라는 걸 알게 됐다. /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)이라는 점이다.

지금까지는 어떻게 결정적인지 이해가 되지 않는다. 설치할 패키지를 지정할 때 무결성 해시는커녕 버전 번호도 지정하지 않았다. 1년 뒤에 같은 Nix 설정을 실행하면 내가 지정한 vimcurl 패키지의 다른 버전이 설치되어 다른 시스템이 될 것 같다.

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

나는 누구를 신뢰하는가?

패키지를 지정할 때 그냥 패키지 이름 목록이었다:

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

위와 같이 패키지를 지정할 수 있다는 건 Nix가 기본 저장소에서 패키지를 가져오고 있다는 뜻일 것이다. 저장소가 여러 개인가? 어떤 저장소를 쓸지는 어떻게 고르는 걸까?

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

댓글