NixOS 가상 머신을 이용한 통합 테스트#
- Authors:
@olafklingt, Domen Kožar @domenkozar
- Editor:
Valentin Gagarin @fricklerhandwerk
무엇을 배우게 됩니까?#
이 튜토리얼에서는 NixOS 설정을 테스트하기 위한 Nixpkgs 기능을 소개합니다. 또한 여러 머신이 관여하는 분산 테스트 시나리오를 구성하는 방법도 보여 줍니다.
무엇이 필요합니까?#
소개#
Nixpkgs는 분산 시스템의 통합 테스트를 자동화하는 테스트 환경을 제공합니다. 이 환경을 사용하면 선언적 NixOS 설정의 집합을 바탕으로 테스트를 정의하고, QEMU를 백엔드로 삼아 Python 셸로 해당 설정들과 상호 작용할 수 있습니다. 이러한 테스트는 NixOS가 의도대로 동작하는지 확인하는 데 널리 사용되므로, 일반적으로 NixOS 테스트라고 부릅니다. 이 테스트는 NixOS 외부의 어떤 Linux 머신[1]에서도 작성하고 실행할 수 있습니다.
Nix의 설계 특성 덕분에 통합 테스트는 재현 가능하며, 이 때문에 지속적 통합(CI) 파이프라인에서 유용합니다.
testers.runNixOSTest 함수#
NixOS VM 테스트는 testers.runNixOSTest 함수를 사용하여 정의합니다. NixOS VM 테스트의 패턴은 다음과 같습니다.
1let
2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
3 pkgs = import nixpkgs { config = {}; overlays = []; };
4in
5
6pkgs.testers.runNixOSTest {
7 name = "test-name";
8 nodes = {
9 machine1 = { config, pkgs, ... }: {
10 # ...
11 };
12 machine2 = { config, pkgs, ... }: {
13 # ...
14 };
15 };
16 testScript = { nodes, ... }: ''
17 # ...
18 '';
19}
testers.runNixOSTest 함수는 테스트 옵션을 지정하는 모듈을 인자로 받습니다. 이 모듈은 설정 값만 지정하므로 축약된 모듈 표기법을 사용할 수 있습니다.
다음 설정 값을 반드시 지정해야 합니다.
name은 테스트의 이름을 정의합니다.테스트 스크립트에는 가상 머신이 둘 이상 관여할 수 있으므로,
nodes에는 이름이 붙은 설정의 집합이 들어 있습니다. 각 가상 머신은 NixOS 설정으로부터 생성됩니다.testScript는 Python 테스트 스크립트를 리터럴 문자열 또는nodes속성을 받는 함수 형태로 정의합니다. 이 Python 테스트 스크립트는nodes에 사용된 이름을 통해 가상 머신에 접근할 수 있습니다. 이 스크립트는 가상 머신에서 슈퍼유저 권한을 가집니다. Python 스크립트에서 각 가상 머신은machine객체를 통해 접근할 수 있습니다. NixOS는 이러한 설정에 대해 테스트를 실행하는 메서드를 제공합니다.
테스트 프레임워크는 가상 머신을 자동으로 시작하고 Python 스크립트를 실행합니다.
최소 예제#
기본 설정에 대한 최소한의 테스트로, 사용자 root와 alice가 Firefox를 실행할 수 있는지 확인하겠습니다. 예제는 처음부터 직접 만들어 나가겠습니다.
고정된 버전의 Nixpkgs를 사용하고, 전역 설정에 의해 의도치 않게 오버라이드되지 않도록 설정 옵션과 오버레이를 명시적으로 지정하십시오:
1let 2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11"; 3 pkgs = import nixpkgs { config = {}; overlays = []; }; 4in 5 6pkgs.testers.runNixOSTest { 7 # ... 8}
테스트에 내용을 잘 설명하는 이름을 붙이십시오:
1name = "minimal-test";
이 예제에서는 가상 머신을 하나만 사용하므로, 지정하는 노드의 이름은 간단히
machine입니다. 이 이름은 임의로 정한 것이며 자유롭게 선택할 수 있습니다. 설정으로는 이전 튜토리얼에서 사용했던 기본 설정 중 관련된 부분을 사용합니다:1nodes.machine = { config, pkgs, ... }: { 2 users.users.alice = { 3 isNormalUser = true; 4 extraGroups = [ "wheel" ]; 5 packages = with pkgs; [ 6 firefox 7 tree 8 ]; 9 }; 10 11 system.stateVersion = "23.11"; 12};
테스트 스크립트는 다음과 같습니다:
1machine.wait_for_unit("default.target") 2machine.succeed("su -- alice -c 'which firefox'") 3machine.fail("su -- root -c 'which firefox'")
이 Python 스크립트는
nodes속성 집합에서 사용한 가상 머신 설정에 붙인 이름인machine을 참조합니다.이 스크립트는 systemd가
default.target에 도달할 때까지 기다립니다.su명령으로 사용자를 전환하고,which명령으로 해당 사용자가firefox에 접근할 수 있는지 확인합니다.which firefox명령이 사용자alice에게는 성공하고root에게는 실패할 것으로 기대합니다.이 스크립트는
testScript속성의 값이 됩니다.
완성된 minimal-test.nix 파일의 전체 내용은 다음과 같습니다:
1let
2 nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
3 pkgs = import nixpkgs { config = {}; overlays = []; };
4in
5
6pkgs.testers.runNixOSTest {
7 name = "minimal-test";
8
9 nodes.machine = { config, pkgs, ... }: {
10
11 users.users.alice = {
12 isNormalUser = true;
13 extraGroups = [ "wheel" ];
14 packages = with pkgs; [
15 firefox
16 tree
17 ];
18 };
19
20 system.stateVersion = "23.11";
21 };
22
23 testScript = ''
24 machine.wait_for_unit("default.target")
25 machine.succeed("su -- alice -c 'which firefox'")
26 machine.fail("su -- root -c 'which firefox'")
27 '';
28}
테스트 실행하기#
모든 머신을 준비하고 테스트 스크립트를 실행하려면 다음과 같이 하십시오:
$ nix-build minimal-test.nix
...
test script finished in 10.96s
cleaning up
killing machine (pid 10)
(0.00 seconds)
/nix/store/bx7z3imvxxpwkkza10vb23czhw7873w2-vm-test-run-minimal-test
가상 머신의 대화형 Python 셸#
테스트를 개발하거나 무언가가 잘못되었을 때는 테스트를 대화형으로 조작해 보거나 머신의 터미널에 접속하는 것이 유용합니다.
테스트 프레임워크로 대화형 Python 세션을 시작하려면 다음과 같이 하십시오:
$ $(nix-build -A driverInteractive minimal-test.nix)/bin/nixos-test-driver
여기에서는 어떤 테스트 작업이든 실행할 수 있습니다. test_script() 함수로 minimal-test.nix의 testScript 속성을 실행하십시오.
가상 머신이 아직 시작되지 않았다면, machine 객체의 메서드를 처음 호출할 때 테스트 환경이 가상 머신을 시작합니다.
하지만 다음 명령으로 가상 머신을 직접 시작할 수도 있습니다:
>>> machine.start()
특정 노드의 경우,
또는
>>> start_all()
모든 노드의 경우입니다.
다음 명령으로 가상 머신의 대화형 셸에 들어갈 수 있습니다:
>>> machine.shell_interact()
그리고 다음과 같은 셸 명령을 실행할 수 있습니다:
uname -a
Linux server 5.10.37 #1-NixOS SMP Fri May 14 07:50:46 UTC 2021 x86_64 GNU/Linux
성공한 테스트 다시 실행하기
테스트 결과는 Nix 스토어에 보관되므로, 성공한 테스트는 캐시됩니다. 즉, 테스트 설정(노드 설정과 테스트 스크립트)이 의미상 동일하게 유지되는 한 Nix는 테스트를 다시 실행하지 않습니다. 따라서 테스트를 다시 실행하려면 결과를 삭제해야 합니다.
심볼릭 링크를 통해 결과를 삭제하려고 하면 다음과 같은 오류가 발생합니다:
nix-store --delete ./result
finding garbage collector roots...
0 store paths deleted, 0.00 MiB freed
error: Cannot delete path '/nix/store/4klj06bsilkqkn6h2sia8dcsi72wbcfl-vm-test-run-unnamed' since it is still alive. To find out why, use: nix-store --query --roots
대신 심볼릭 링크를 먼저 제거한 다음에 캐시된 결과를 제거하십시오:
rm ./result
nix-store --delete /nix/store/4klj06bsilkqkn6h2sia8dcsi72wbcfl-vm-test-run-unnamed
이 작업은 명령 하나로도 수행할 수 있습니다:
result=$(readlink -f ./result) rm ./result && nix-store --delete $result
여러 가상 머신을 사용하는 테스트#
테스트에는 여러 가상 머신이 포함될 수 있습니다. 예를 들어 클라이언트-서버 간 통신을 테스트할 수 있습니다.
다음 예제 구성에는 다음이 포함됩니다:
기본 설정으로 nginx를 실행하는
server라는 이름의 가상 머신.HTTP 요청을 보낼 수 있도록
curl을 사용할 수 있는client라는 이름의 가상 머신.client와server사이의 테스트 로직을 조율하는testScript.
client-server-test.nix 파일의 전체 내용은 다음과 같습니다:
let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-23.11";
pkgs = import nixpkgs { config = {}; overlays = []; };
in
pkgs.testers.runNixOSTest {
name = "client-server-test";
nodes.server = { pkgs, ... }: {
networking = {
firewall = {
allowedTCPPorts = [ 80 ];
};
};
services.nginx = {
enable = true;
virtualHosts."server" = {};
};
};
nodes.client = { pkgs, ... }: {
environment.systemPackages = with pkgs; [
curl
];
};
testScript = ''
server.wait_for_unit("default.target")
client.wait_for_unit("default.target")
client.succeed("curl http://server/ | grep -o \"Welcome to nginx!\"")
'';
}
테스트 스크립트는 다음 단계를 수행합니다:
서버를 시작하고 준비될 때까지 기다립니다.
클라이언트를 시작하고 준비될 때까지 기다립니다.
클라이언트에서
curl을 실행하고grep을 사용하여 예상되는 반환 문자열을 확인합니다. 테스트는 반환 값에 따라 통과하거나 실패합니다.
테스트를 실행하십시오:
$ nix-build client-server-test.nix
NixOS 테스트에 관한 추가 정보#
CI에서 통합 테스트를 실행하려면 하드웨어 가속이 필요하지만, 많은 CI가 이를 지원하지 않습니다.
GitHub Actions에서 통합 테스트를 실행하려면 하드웨어 가속을 비활성화하는 방법을 참고하십시오.
NixOS에는 교육용 예제로 활용할 수 있는 방대한 테스트 모음이 포함되어 있습니다.
IRC와의 Matrix 브리징이 좋은 참고 사례입니다.