NixOS 远程部署工具

将 NixOS 配置远程部署到多台服务器需要专门的工具。本文对比 NixOps、deploy-rs 和 Colmena 三种主流方案,详解配置编写、部署流程和回滚机制。

1. 为什么需要专门的部署工具?

NixOS 的系统重建命令 nixos-rebuild switch 设计初衷是在本地运行。但在以下场景中,需要在远程机器上执行配置更新:

  • 服务器集群管理:几十台生产服务器需要统一更新 Nginx 配置或安全补丁
  • 边缘节点部署:树莓派集群、IoT 设备需要在不物理接触的情况下更新
  • CI/CD 集成:Git 提交后自动部署到测试环境和生产环境
  • ** secrets 管理**:配置中包含 API 密钥、证书等敏感数据,需要安全地传递到目标主机

手动 SSH 到每台服务器运行 nixos-rebuild switch 显然不可扩展。Nix 社区开发了多种专用工具来解决这些需求。

2. deploy-rs

deploy-rs 是目前社区最活跃的 NixOS 远程部署工具。它使用 Flakes 管理配置,支持并行部署和自动回滚。

2.1 基本架构

deploy node        SSH/activaion        target node
(flakes config)  ------------------> (remote NixOS)

2.2 flake.nix 配置

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    deploy-rs = {
      url = "github:serokell/deploy-rs";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, deploy-rs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};

      # 通用 NixOS 模块
      commonModule = { config, pkgs, ... }: {
        nix.settings.experimental-features = [ "nix-command" "flakes" ];
        services.openssh.enable = true;
      };
    in
    {
      # NixOS 配置
      nixosConfigurations = {
        web-server = nixpkgs.lib.nixosSystem {
          inherit system;
          modules = [
            commonModule
            ./hosts/web-server.nix
          ];
        };

        database = nixpkgs.lib.nixosSystem {
          inherit system;
          modules = [
            commonModule
            ./hosts/database.nix
          ];
        };

        cache-server = nixpkgs.lib.nixosSystem {
          inherit system;
          modules = [
            commonModule
            ./hosts/cache-server.nix
          ];
        };
      };

      # 部署配置
      deploy = {
        # SSH 连接配置(可选,默认使用 ~/.ssh/config)
        sshOpts = [ "-o" "StrictHostKeyChecking=no" ];

        nodes = {
          web-server = {
            hostname = "web1.example.com";
            profiles.system = {
              user = "root";
              path = deploy-rs.lib.${system}.activate.nixos 
                self.nixosConfigurations.web-server;
            };
          };

          database = {
            hostname = "db1.internal";
            sshUser = "deploy";  # SSH 登录用户
            profiles.system = {
              user = "root";
              path = deploy-rs.lib.${system}.activate.nixos
                self.nixosConfigurations.database;
            };
          };

          cache-server = {
            hostname = "192.168.1.50";
            profiles.system = {
              user = "root";
              path = deploy-rs.lib.${system}.activate.nixos
                self.nixosConfigurations.cache-server;
            };
          };
        };
      };

      # deploy-rs 检查
      checks = builtins.mapAttrs (system: deploy-rs.lib.${system}.deployChecks) self.deploy;
    };
}

2.3 部署命令

# 部署到所有节点
deploy . -- -s

# 部署到特定节点
deploy .#web-server

# 只构建不激活(检查配置是否正确)
deploy . --build-only

# 使用 dry-activate 进行测试
deploy . --dry-activate

# 指定 SSH 密钥
deploy . --ssh-opts "-i /path/to/key"

2.4 自动回滚机制

deploy-rs 的关键特性是激活失败时自动回滚。部署流程如下:

  1. 构建目标系统配置
  2. 通过 SSH 将配置传输到目标主机
  3. 在目标主机上激活新配置(类似 nixos-rebuild switch)
  4. 如果激活失败或健康检查未通过,自动回滚到之前的配置
# 自定义激活检查(可选)
# 在目标主机上执行的脚本,验证服务是否正常运行
profiles.system = {
  user = "root";
  path = deploy-rs.lib.${system}.activate.nixos self.nixosConfigurations.web-server;
  
  # 激活后检查 Nginx 是否响应
  activationScript = ''
    #!/usr/bin/env bash
    for i in {1..10}; do
      if curl -sf http://localhost/health > /dev/null; then
        echo "Health check passed"
        exit 0
      fi
      sleep 1
    done
    echo "Health check failed, rolling back..."
    exit 1
  '';
};

3. Colmena

Colmena 是另一个流行的部署工具,它更像一个"分布式的 nixos-rebuild"。它使用本地评估并在目标主机上激活。

3.1 hive.nix 配置

Colmena 的核心配置文件是 hive.nix:

# hive.nix
{
  meta = {
    nixpkgs = import <nixpkgs> { system = "x86_64-linux"; };
    nodeNixpkgs = {
      # 为特定节点使用不同的 nixpkgs
      arm-node = import <nixpkgs> { system = "aarch64-linux"; };
    };
    nodeSpecialArgs = {
      # 传递给每个节点的额外参数
      all = { inherit (import ./secrets.nix) apiKey; };
    };
  };

  # 默认配置(应用到所有节点)
  defaults = { config, pkgs, ... }: {
    # 所有节点的基础配置
    nix.settings.experimental-features = [ "nix-command" "flakes" ];
    
    deployment = {
      targetHost = null;  # 由下面覆盖
      targetPort = 22;
      targetUser = "root";
      buildOnTarget = false;  # 在本地构建,然后上传
    };
  };

  # 节点定义
  web-server = { config, pkgs, ... }: {
    deployment.targetHost = "web1.example.com";
    
    imports = [ ./hosts/web-server.nix ];
    
    services.nginx = {
      enable = true;
      virtualHosts."example.com" = {
        locations."/" = {
          proxyPass = "http://localhost:8080";
        };
      };
    };
  };

  database = { config, pkgs, ... }: {
    deployment = {
      targetHost = "db1.internal";
      targetUser = "admin";  # 使用非 root 用户(需要 sudo)
      tags = [ "database" "production" ];
    };
    
    imports = [ ./hosts/database.nix ];
    
    services.postgresql = {
      enable = true;
      package = pkgs.postgresql_15;
      ensureDatabases = [ "myapp" ];
    };
  };

  cache-server = { config, pkgs, ... }: {
    deployment = {
      targetHost = "192.168.1.50";
      tags = [ "cache" "internal" ];
    };
    
    imports = [ ./hosts/cache-server.nix ];
    
    services.nix-serve = {
      enable = true;
      secretKeyFile = "/var/cache-priv-key.pem";
    };
  };

  # 树莓派节点(ARM 架构)
  arm-node = { config, pkgs, ... }: {
    deployment = {
      targetHost = "pi-node.local";
      targetPort = 2222;
    };
    
    # ARM 特定配置
    boot.loader.grub.enable = false;
    boot.loader.generic-extlinux-compatible.enable = true;
  };
}

3.2 Colmena 部署命令

# 部署所有节点
colmena apply

# 只部署特定节点
colmena apply --on web-server
colmena apply --on database

# 按标签部署
colmena apply --tag production
colmena apply --tag cache

# 并行部署
colmena apply --parallel 4

# 构建但不激活
colmena apply --build-only

# 评估配置(检查是否有错误)
colmena eval

# 在节点上执行命令
colmena exec --on web-server -- systemctl status nginx
colmena exec --tag database -- psql -l

# 上传文件到节点
colmena upload --on web-server ./local-file /remote/path

# SSH 到节点
colmena ssh web-server

3.3 密钥管理

Colmena 支持在部署时安全地传递 secrets:

# secrets.nix(加密文件,使用 agenix 或 sops-nix)
{
  database.password = "secret-password";
  api.key = "api-key-value";
}

# hive.nix
{ config, pkgs, secrets, ... }: {
  # 使用 secrets 配置服务
  services.postgresql.initialScript = pkgs.writeText "init.sql" ''
    CREATE USER myuser WITH PASSWORD '${secrets.database.password}';
  '';
}

4. NixOps

NixOps 是官方历史最久的部署工具,但目前维护不太活跃。它支持云资源创建(EC2、GCE、Azure 等)和配置管理一体化。

4.1 基本用法

# network.nix
{
  network = {
    description = "Web application cluster";
    enableRollback = true;
  };

  # 默认配置
  defaults = { config, pkgs, ... }: {
    services.openssh.enable = true;
    networking.firewall.enable = true;
  };

  web-server = { config, pkgs, nodes, ... }: {
    deployment.targetHost = "web1.example.com";
    
    services.nginx.enable = true;
    services.nginx.virtualHosts."app.com" = {
      locations."/".proxyPass = "http://localhost:3000";
    };
  };

  database = { config, pkgs, nodes, ... }: {
    deployment.targetHost = "db1.internal";
    
    services.postgresql.enable = true;
    services.postgresql.ensureDatabases = [ "app" ];
  };
}
# 创建部署
nixops create network.nix -d production

# 部署
nixops deploy -d production

# 查看状态
nixops info -d production
nixops check -d production

# 回滚
nixops rollback -d production

# 销毁资源
nixops destroy -d production

5. 选择指南

工具优点缺点适用场景
deploy-rs活跃维护,Flakes 原生,自动回滚,并行部署仅支持 NixOS,非官方小型到中型集群,追求简单易用
Colmena强大的标签系统,远程命令执行,灵活学习曲线稍高大型集群,需要精细控制
NixOps官方工具,支持云资源自动创建维护不活跃,Flakes 支持弱需要自动创建云资源的场景

6. Secrets 管理

部署中的一个关键挑战是如何安全地传递密码、API 密钥和证书。

6.1 agenix

agenix 使用 age 加密文件,在部署时由目标主机解密:

# flake.nix
{
  inputs = {
    agenix = {
      url = "github:ryantm/agenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, nixpkgs, agenix }:
    {
      nixosConfigurations.web-server = nixpkgs.lib.nixosSystem {
        modules = [
          agenix.nixosModules.default
          {
            age.secrets.database-password = {
              file = ./secrets/database-password.age;
              owner = "postgres";
              group = "postgres";
            };

            services.postgresql.initialScript = ''
              CREATE USER app WITH PASSWORD '$(cat ${config.age.secrets.database-password.path})';
            '';
          }
        ];
      };
    };
}

6.2 sops-nix

sops-nix 使用 Mozilla SOPS 管理 YAML/JSON 加密文件:

# .sops.yaml
keys:
  - &admin age1xxx...
creation_rules:
  - path_regex: secrets/.*\.yaml$
    key_groups:
      - age:
          - *admin

# flake.nix
{
  inputs.sops-nix.url = "github:Mic92/sops-nix";

  outputs = { self, nixpkgs, sops-nix }:
    {
      nixosConfigurations.web-server = nixpkgs.lib.nixosSystem {
        modules = [
          sops-nix.nixosModules.sops
          {
            sops.defaultSopsFile = ./secrets/secrets.yaml;
            sops.secrets.api_key = {
              owner = "myapp";
            };

            systemd.services.myapp.serviceConfig = {
              LoadCredential = "api_key:${config.sops.secrets.api_key.path}";
            };
          }
        ];
      };
    };
}

7. CI/CD 集成

7.1 GitHub Actions + deploy-rs

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Nix
        uses: cachix/install-nix-action@v24
        with:
          extra_nix_config: |
            experimental-features = nix-command flakes
      
      - name: Add SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
      
      - name: Build and Deploy
        run: |
          nix run github:serokell/deploy-rs -- . -- -s

8. 总结

NixOS 远程部署工具生态正在快速发展。deploy-rs 凭借其简洁和可靠性成为当前的主流推荐,Colmena 适合需要高级编排功能的大型集群:

能力deploy-rsColmenaNixOps
Flakes 支持✅ 原生✅ 支持⚠️ 实验性
并行部署✅✅✅
自动回滚✅❌✅
多架构✅✅✅
标签系统❌✅❌
云资源创建❌❌✅
活跃度高高低

对于新用户,推荐从 deploy-rs 开始。它结合了 Flakes 的现代工作流和可靠的部署机制,是学习 NixOS 远程部署的最佳起点。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「devops」更多文章

  1. 值班工程与告警疲劳治理
  2. 基础设施代码测试:Terratest、Kitchen 与 InSpec
  3. 发布列车与版本节奏治理