NixOS 系统配置管理

NixOS 是围绕 Nix 包管理器构建的 Linux 发行版,实现了声明式的系统配置。本文详解 configuration.nix 结构、模块系统、系统重建、服务管理和网络配置。

1. NixOS 的设计哲学

NixOS 与其他 Linux 发行版最根本的区别在于它是完全声明式的。传统的 Linux 系统是通过一系列命令和文件编辑逐步构建的,系统状态是命令序列的累积结果。而 NixOS 将系统的所有配置集中在 configuration.nix 中——这个文件描述了整个系统的"目标状态",NixOS 通过评估这个文件并将系统推进到该状态来工作。

这种设计带来了几个革命性的优势:

  • 版本控制:整个系统配置是单个文件(或多个导入的文件),可以使用 Git 管理
  • 原子升级:系统升级要么全部成功,要么回滚到之前的状态,永远不会处于半升级状态
  • 可复现:从 configuration.nix + hardware-configuration.nix 可以在任何兼容硬件上重建完全相同的系统
  • 多配置并存:可以在引导菜单中选择不同的系统配置(称为 generation)
  • 回滚安全:如果新配置导致系统无法启动,可以在启动时选择旧配置

2. configuration.nix 结构

2.1 基础配置框架

# /etc/nixos/configuration.nix
{ config, pkgs, lib, ... }:

{
  # 引入硬件配置(由 nixos-generate-config 生成)
  imports = [
    ./hardware-configuration.nix
    ./network-configuration.nix
    ./services-configuration.nix
  ];

  # 引导配置
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # 内核
  boot.kernelPackages = pkgs.linuxPackages_latest;

  # 主机名
  networking.hostName = "my-nixos-server";

  # 时区
  time.timeZone = "Asia/Shanghai";

  # 国际化
  i18n.defaultLocale = "zh_CN.UTF-8";

  # 用户配置
  users.users.alice = {
    isNormalUser = true;
    home = "/home/alice";
    description = "Alice";
    extraGroups = [ "wheel" "networkmanager" "docker" ];
    shell = pkgs.zsh;
    openssh.authorizedKeys.keys = [
      "ssh-rsa AAAAB3... alice@example.com"
    ];
  };

  # root 密码(建议使用 hashedPasswordFile 更安全)
  users.users.root.hashedPassword = "$6$rounds=500000$...";

  # 系统级包
  environment.systemPackages = with pkgs; [
    vim
    git
    htop
    curl
    wget
  ];

  # 启用 Nix Flakes
  nix.settings.experimental-features = [ "nix-command" "flakes" ];

  # 系统状态版本(不要修改!用于向后兼容)
  system.stateVersion = "24.05";
}

2.2 硬件配置

hardware-configuration.nix 由安装程序自动生成,包含设备特定的配置:

{ config, lib, pkgs, modulesPath, ... }:

{
  imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];

  # 文件系统挂载
  fileSystems."/" = {
    device = "/dev/disk/by-uuid/abc123-456";
    fsType = "ext4";
  };

  fileSystems."/boot" = {
    device = "/dev/disk/by-uuid/DEF-456";
    fsType = "vfat";
    options = [ "fmask=0022" "dmask=0022" ];
  };

  # 交换分区
  swapDevices = [ 
    { device = "/dev/disk/by-uuid/swap-uuid"; }
  ];

  # 内核模块
  boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" ];
  boot.kernelModules = [ "kvm-intel" "v4l2loopback" ];
  boot.extraModulePackages = [ ];
}

3. 网络配置

3.1 基础网络

{
  # DHCP 自动获取
  networking.useDHCP = true;

  # 或者静态 IP
  networking.interfaces.eth0 = {
    useDHCP = false;
    ipv4.addresses = [{
      address = "192.168.1.100";
      prefixLength = 24;
    }];
  };

  networking.defaultGateway = "192.168.1.1";
  networking.nameservers = [ "8.8.8.8" "8.8.4.4" ];

  # 防火墙
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 22 80 443 ];
    allowedUDPPorts = [ 53 ];
    
    # 允许从特定来源访问
    extraCommands = ''
      iptables -A INPUT -s 10.0.0.0/8 -p tcp --dport 5432 -j ACCEPT
    '';
  };
}

3.2 高级网络

{
  # 网桥配置
  networking.bridges.br0.interfaces = [ "eth0" "eth1" ];
  networking.interfaces.br0 = {
    ipv4.addresses = [{
      address = "10.0.0.1";
      prefixLength = 24;
    }];
  };

  # VLAN
  networking.vlans.vlan10 = {
    id = 10;
    interface = "eth0";
  };

  # WireGuard VPN
  networking.wireguard.interfaces.wg0 = {
    ips = [ "10.100.0.2/24" ];
    listenPort = 51820;
    privateKeyFile = "/etc/wireguard/private-key";
    peers = [{
      publicKey = "abc123...";
      allowedIPs = [ "10.100.0.0/24" ];
      endpoint = "vpn.example.com:51820";
      persistentKeepalive = 25;
    }];
  };

  # 网络管理器(桌面系统用)
  networking.networkmanager.enable = true;
}

4. 服务管理

4.1 SSH 服务

{
  services.openssh = {
    enable = true;
    settings = {
      PermitRootLogin = "no";
      PasswordAuthentication = false;
      PubkeyAuthentication = true;
    };
    ports = [ 22 ];
  };
}

4.2 Web 服务器(Nginx)

{ config, pkgs, ... }:

{
  services.nginx = {
    enable = true;
    
    # 全局配置
    recommendedGzipSettings = true;
    recommendedProxySettings = true;
    recommendedTlsSettings = true;

    # 虚拟主机
    virtualHosts = {
      "example.com" = {
        enableACME = true;  # 自动 Let's Encrypt 证书
        forceSSL = true;     # 强制 HTTPS
        locations."/" = {
          proxyPass = "http://localhost:8080";
          proxyWebsockets = true;
        };
      };

      "static.example.com" = {
        root = "/var/www/static";
        locations."/" = {
          extraConfig = ''
            add_header Cache-Control "public, max-age=31536000";
          '';
        };
      };
    };
  };

  # ACME(证书自动管理)
  security.acme = {
    acceptTerms = true;
    defaults.email = "admin@example.com";
  };
}

4.3 数据库

{
  # PostgreSQL
  services.postgresql = {
    enable = true;
    package = pkgs.postgresql_15;
    dataDir = "/var/lib/postgresql/15";
    ensureDatabases = [ "myapp" ];
    ensureUsers = [{
      name = "myuser";
      ensureDBOwnership = true;
    }];
    settings = {
      max_connections = 200;
      shared_buffers = "2GB";
    };
  };

  # Redis
  services.redis.servers.default = {
    enable = true;
    bind = "127.0.0.1";
    port = 6379;
  };

  # MySQL/MariaDB
  services.mysql = {
    enable = true;
    package = pkgs.mariadb;
    ensureDatabases = [ "myapp" ];
    ensureUsers = [{
      name = "myuser";
      ensurePermissions = {
        "myapp.*" = "ALL PRIVILEGES";
      };
    }];
  };
}

4.4 容器(Docker/Podman)

{
  # Docker
  virtualisation.docker = {
    enable = true;
    autoPrune = {
      enable = true;
      dates = "weekly";
    };
  };

  # Podman(无守护进程,更安全)
  virtualisation.podman = {
    enable = true;
    dockerCompat = true;  # 提供 docker 命令兼容
  };

  # 用户组
  users.extraGroups.docker.members = [ "alice" ];
}

4.5 定时任务

{
  # systemd timer(替代 cron)
  systemd.services.backup-mysql = {
    description = "Backup MySQL databases";
    serviceConfig = {
      Type = "oneshot";
      User = "root";
      ExecStart = pkgs.writeShellScript "backup-mysql" ''
        ${pkgs.mariadb}/bin/mysqldump --all-databases > /backup/mysql-$(date +%Y%m%d).sql
      '';
    };
  };

  systemd.timers.backup-mysql = {
    description = "Run MySQL backup daily";
    wantedBy = [ "timers.target" ];
    timerConfig = {
      OnCalendar = "daily";
      Persistent = true;
    };
  };
}

5. 系统重建

5.1 重建命令

# 测试配置是否正确(不实际应用)
sudo nixos-rebuild test

# 应用配置并使其持久化(推荐)
sudo nixos-rebuild switch

# 构建但不激活(用于检查)
sudo nixos-rebuild build

# 从 Flake 构建
sudo nixos-rebuild switch --flake /etc/nixos#my-host

# 回滚到上一个 generation
sudo nixos-rebuild switch --rollback

# 列出所有 generation
sudo nixos-rebuild list-generations

# 从特定 generation 启动(需要重启)
sudo nixos-rebuild boot --flake /etc/nixos#my-host

5.2 Generation 管理

每次 nixos-rebuild switch 都会创建一个新的 generation:

# 查看所有 generation
nixos-rebuild list-generations

# 删除旧 generation(保留最近 10 个)
nix-collect-garbage --delete-older-than 30d

# 保留特定的 generation
sudo nix-env --profile /nix/var/nix/profiles/system --delete-generations +10

# 启动时按 e 编辑引导条目
# 选择不同的 generation 启动

6. 模块化配置

6.1 拆分配置文件

大型系统的 configuration.nix 会变得非常长。使用模块系统拆分:

/etc/nixos/
├── configuration.nix          # 主配置,导入其他模块
├── hardware-configuration.nix  # 硬件配置(自动生成)
├── modules/
│   ├── networking.nix         # 网络配置模块
│   ├── services/
│   │   ├── web.nix           # Web 服务器
│   │   ├── database.nix      # 数据库服务
│   │   └── monitoring.nix    # 监控服务
│   ├── users.nix             # 用户配置
│   └── security.nix          # 安全设置
└── flake.nix                  # Flake 配置(如果使用)
# /etc/nixos/configuration.nix
{ config, pkgs, lib, ... }:

{
  imports = [
    ./hardware-configuration.nix
    ./modules/networking.nix
    ./modules/users.nix
    ./modules/security.nix
    ./modules/services/web.nix
    ./modules/services/database.nix
  ];

  # 全局配置...
  system.stateVersion = "24.05";
}

6.2 条件模块

# modules/services/monitoring.nix
{ config, pkgs, lib, ... }:

with lib;

let
  cfg = config.my.monitoring;
in
{
  options.my.monitoring = {
    enable = mkEnableOption "monitoring stack";
    
    prometheus.enable = mkOption {
      type = types.bool;
      default = true;
    };
    
    grafana.enable = mkOption {
      type = types.bool;
      default = true;
    };
  };

  config = mkIf cfg.enable {
    services.prometheus = mkIf cfg.prometheus.enable {
      enable = true;
      scrapeConfigs = [{
        job_name = "node";
        static_configs = [{
          targets = [ "localhost:9100" ];
        }];
      }];
    };

    services.grafana = mkIf cfg.grafana.enable {
      enable = true;
      settings.server.http_port = 3000;
    };
  };
}

6.3 使用条件模块

# /etc/nixos/configuration.nix
{
  imports = [ ./modules/monitoring.nix ];

  my.monitoring = {
    enable = true;
    prometheus.enable = true;
    grafana.enable = true;
  };
}

7. 系统级包与配置

7.1 环境配置

{
  # 系统 PATH 中的包
  environment.systemPackages = with pkgs; [
    vim
    git
    htop
    tcpdump
    iotop
  ];

  # 环境变量
  environment.variables = {
    EDITOR = "vim";
    PAGER = "less";
    MY_APP_CONFIG = "/etc/myapp/config.json";
  };

  # /etc 中的文件
  environment.etc."myapp/config.json".text = ''
    {
      "database": {
        "host": "localhost",
        "port": 5432
      }
    }
  '';

  # 或者从文件读取
  environment.etc."nginx/nginx.conf".source = ./nginx/nginx.conf;
}

7.2 字体与桌面

{
  # 字体配置
  fonts.packages = with pkgs; [
    noto-fonts
    noto-fonts-cjk       # 中日韩文字
    noto-fonts-emoji     # emoji
    fira-code            # 编程字体
  ];

  # 桌面环境
  services.xserver = {
    enable = true;
    displayManager.gdm.enable = true;
    desktopManager.gnome.enable = true;
  };

  # 或者 KDE Plasma
  services.displayManager.sddm.enable = true;
  services.desktopManager.plasma6.enable = true;
}

8. 总结

NixOS 的声明式配置管理重新定义了系统管理的方式:

操作传统 LinuxNixOS
安装软件apt install在 configuration.nix 中添加
配置服务systemctl enable + 编辑配置文件在 configuration.nix 中声明
系统升级apt upgrade(可能中途失败)nixos-rebuild switch(原子操作)
回滚手动恢复备份或重新安装启动时选择旧 generation
复制系统手动复刻所有步骤复制 configuration.nix + rebuild
版本控制不直接支持完整的 Git 版本控制

对于运维工程师和系统管理员,NixOS 提供了一种全新的工作方式:将系统视为代码,用版本控制管理变更,享受可复现性和原子升级带来的安全感。

NixOS 系统代际与回滚

NixOS 的每次 nixos-rebuild switch 都创建一个新的系统代际(generation):

# 查看所有代际
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

# 输出示例:
#   1   2024-01-01 ... 
#   2   2024-02-15 ...  ← 当前
#   3   2024-03-01 ...

# 回滚到第 1 代
sudo nix-env --switch-generation 1 --profile /nix/var/nix/profiles/system

# 启动时按空格进入 GRUB 菜单,选择任意历史代际

原子升级保证:如果 nixos-rebuild switch 中途失败(如电源中断),系统仍保持原代际可用。这不是「回滚」,而是「从未切换到新版本」。

命令式修改的陷阱:NixOS 鼓励纯声明式管理,但手动修改 /etc 文件或运行 nix-env -iA 安装包会破坏声明式一致性。建议:

  1. 禁止在 /etc 直接修改(除 NixOS 生成的链接外)
  2. 禁止 nix-env 命令安装包(应写入 configuration.nix)
  3. 使用 nixos-rebuild switch 作为唯一变更入口

继续阅读

探索更多技术文章

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

全部文章 返回首页

「devops」更多文章

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