版本范围:下列示例保留 NixOS 24.05 的教程上下文;该版本已结束维护。实际项目应选择受支持的发行版、锁定 nixpkgs revision,并核对相应选项。
system.stateVersion表示兼容性基线,不应随升级机械改动。本文命令是教学示例,未在目标系统运行验证。
参考 nix.dev 的基础设施与系统部署思路;本文含扩展示例,并非该教程的逐字翻译。官方教程本身也含旧版 release,使用前需更新锁定输入。参考: Deploying NixOS using Terraform
结合 Terraform 的基础设施管理和 NixOS 的系统配置,实现完整的 IaC(基础设施即代码)。
为什么选择 Terraform + NixOS?#
| 特性 | Terraform | NixOS | 结合优势 |
|---|---|---|---|
| 基础设施管理 | ✅ 优秀 | ❌ 不涉及 | 云平台资源 |
| 系统配置 | ❌ 不涉及 | ✅ 优秀 | 声明式OS |
| 可复现性 | 声明资源和跟踪 state | 锁定输入并构建系统 closure | 云资源仍受平台状态、镜像及外部服务影响 |
| 版本管理 | 良好 | 优秀 | 完整历史 |
架构概述#
┌─────────────────┐ ┌─────────────────┐ ┌───────────────┐
│ Terraform │──────▶│ Cloud Provider │──────▶│ NixOS │
│ - VM/Network │ │ - AWS/GCP │ │ - configure │
│ - Firewall │ │ - Disk/IP │ │ - user │
└─────────────────┘ └─────────────────┘ └───────────────┘
│
▼
NixOS 安装镜像基础配置#
1. NixOS 镜像准备
AWS 已有适用的 NixOS AMI 时,不需要先构建破坏磁盘的自动安装 ISO。自定义安装介质适用于没有现成镜像的受控环境,是与云资源创建分开的安装路径。下面保留分区、格式化、挂载、生成配置和安装的完整流程,但将自动开机执行改为手动启动的 oneshot。
前提:这是专用的空白测试盘,数据已备份;磁盘身份、UEFI 模式和分区容量已确认;ISO 内含 target-configuration.nix,其中有正确的 bootloader、文件系统和 SSH 公钥,不含真实秘密。按目标选择该盘的稳定 by-id 路径和对应分区路径;示例占位符必须替换。此例需盘容量足够保留末尾 8 GiB swap。
# install-image.nix
{ pkgs, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix")
];
systemd.services.example-nixos-install = {
description = "Install NixOS on an explicitly selected empty test disk";
# 没有 wantedBy:不会开机自动清盘,确认后才手动启动
path = with pkgs; [ parted e2fsprogs dosfstools util-linux systemd nixos-install-tools ];
serviceConfig.Type = "oneshot";
script = ''
set -eu
disk='/dev/disk/by-id/<EMPTY_TEST_DISK>'
rootpart='/dev/disk/by-id/<EMPTY_TEST_DISK>-part1'
swappart='/dev/disk/by-id/<EMPTY_TEST_DISK>-part2'
esp='/dev/disk/by-id/<EMPTY_TEST_DISK>-part3'
test -b "$disk"
# 目标分区不得处于已挂载状态;由操作者在启动前核对 lsblk
parted "$disk" -- mklabel gpt
parted "$disk" -- mkpart primary 512MiB -8GiB
parted "$disk" -- mkpart primary linux-swap -8GiB 100%
parted "$disk" -- mkpart ESP fat32 1MiB 512MiB
parted "$disk" -- set 3 esp on
partprobe "$disk"
udevadm settle
mkfs.ext4 -L nixos "$rootpart"
mkswap -L swap "$swappart"
mkfs.fat -F 32 -n boot "$esp"
mount "$rootpart" /mnt
mkdir -p /mnt/boot
mount "$esp" /mnt/boot
swapon "$swappart"
nixos-generate-config --root /mnt
# 配置来自随 ISO 固定的本地源,保留生成的 hardware-configuration.nix
cp ${./target-configuration.nix} /mnt/etc/nixos/configuration.nix
nixos-install --no-root-passwd
# 不自动重启:先检查安装结果、SSH 用户和可用恢复介质
'';
};
}pkgs.systemd 为 service PATH 提供 udevadm。--no-root-passwd 只跳过安装时设置 root 密码,不会自动创建可登录的管理用户;目标配置须提前声明身份与授权。上例没有自动确认磁盘与备份,不能作为通用无人值守安装器。确认完成后才用 systemctl start example-nixos-install.service 触发安装,再检查日志与 /mnt 下结果。
若需要联网下载配置,应等待 network-online.target 并使用有错误处理的 HTTPS 下载和可信 hash/签名校验;不能用能 ping 某个公网域名来判断配置可信。更稳妥的是把已审阅配置固定进镜像。
2. Terraform 配置
以下保留 AWS provider 5.x 的历史配置范围。创建 main.tf 前应通过 NixOS 官方渠道与云平台核对区域、AMI owner、架构、用户名和 user-data 支持;provider 文档、.terraform.lock.hcl 和实际 AMI 决定接口。配置片段含占位符,不能直接 apply。创建 main.tf:
# 提供商配置(以 AWS 为例)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-west-2"
}
# 获取 NixOS AMI
data "aws_ami" "nixos" {
most_recent = true # 发现用;实际交付应记录并固定解析出的 AMI ID
owners = [ var.nixos_ami_owner ] # 从官方渠道核验的镜像发布账号
filter {
name = "name"
values = [ var.nixos_ami_name ] # 核验过的版本名;最终部署宜固定 AMI ID
}
filter {
name = "architecture"
values = [ "x86_64" ]
}
}
# 创建 VPC
resource "aws_vpc" "nixos_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "nixos-vpc"
}
}
# 子网
resource "aws_subnet" "nixos_subnet" {
vpc_id = aws_vpc.nixos_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
map_public_ip_on_launch = true
tags = {
Name = "nixos-subnet"
}
}
# 安全组
resource "aws_security_group" "nixos_sg" {
name_prefix = "nixos-sg"
vpc_id = aws_vpc.nixos_vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.admin_cidr] # 仅可信管理网络
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# SSH 密钥
resource "aws_key_pair" "deployer" {
key_name = "deployer-key"
public_key = trimspace(file(pathexpand("~/.ssh/id_ed25519.pub")))
}
# VPC 的公网子网需要 Internet Gateway 与默认路由
resource "aws_internet_gateway" "nixos" {
vpc_id = aws_vpc.nixos_vpc.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.nixos_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.nixos.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.nixos_subnet.id
route_table_id = aws_route_table.public.id
}
# 由操作者提供,不内置账号与真实网络身份
variable "nixos_ami_owner" { type = string }
variable "nixos_ami_name" { type = string }
variable "admin_cidr" { type = string }
# NixOS 配置(仅在已确认 AMI 支持这一 user_data 格式时使用)
locals {
nixos_config = <<-EOT
{ config, pkgs, ... }: {
imports = [
<nixpkgs/nixos/modules/virtualisation/amazon-image.nix>
];
ec2.hvm = true;
networking.hostName = "nixos-server";
services.openssh = {
enable = true;
settings.PermitRootLogin = "no";
};
users.users.admin = {
isNormalUser = true;
extraGroups = [ "wheel" ]; # docker 组具有接近 root 的权限,按实际需求另行授予
openssh.authorizedKeys.keys = [
"${trimspace(file(pathexpand("~/.ssh/id_ed25519.pub")))}"
];
};
security.sudo.wheelNeedsPassword = false;
services.nginx = {
enable = true;
virtualHosts.default = {
root = "$${pkgs.writeTextDir "index.html" "NixOS web service"}";
};
};
system.stateVersion = "24.05";
}
EOT
}
# EC2 实例
resource "aws_instance" "nixos" {
ami = data.aws_ami.nixos.id
instance_type = "t3.medium"
subnet_id = aws_subnet.nixos_subnet.id
vpc_security_group_ids = [aws_security_group.nixos_sg.id]
key_name = aws_key_pair.deployer.key_name
user_data = local.nixos_config
root_block_device {
volume_size = 20
volume_type = "gp3"
}
tags = {
Name = "nixos-server"
}
}
# 输出
output "public_ip" {
value = aws_instance.nixos.public_ip
}
output "ssh_command" {
value = "ssh admin@${aws_instance.nixos.public_ip}"
}传入 Nix 表达式的 $${...} 是 Terraform 对字面 ${...} 的转义,最终交给 Nix 求值;Terraform 的 ${trimspace(file(...))} 则在 Terraform 阶段插值。两层插值不要混用。AMI 的 EC2 user-data 机制依镜像配置决定:并非任意 AMI 都会把一段 Nix 配置自动应用。未核实支持时删除 user_data,用 SSH 部署构建好的系统 closure。公开 user-data 和 state 中只放公共配置,不能放私钥、API token 或密码。
上述 security.sudo.wheelNeedsPassword = false 会允许 wheel 组无密码 sudo,仅适用于已审阅的自动化管理账号策略;更严格的环境应限制具体命令或采用具备交互认证的部署方式。
使用方法#
1. 初始化
terraform init2. 计划
terraform plan -out=deployment.tfplan3. 应用
init 初始化 provider/backend,plan 计算候选变化,apply 才实际创建、修改或销毁云资源并可能产生费用。先审阅保存的 plan、目标账户和网络规则,再应用同一 plan。
terraform apply deployment.tfplan4. 连接服务器
ssh admin@$(terraform output -raw public_ip)使用 Flakes 管理配置#
Terraform 管理云资源,flake 独立定义系统;不存在一个通用的 hashicorp/terraform-provider-nixos flake 模块接口可直接套用。可以使用专门的第三方集成,但应逐项核对其真实仓库、接口和维护状态。这里用常规 NixOS flake 明确分工:
# flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; # 历史例子,实际选受支持版本
outputs = { self, nixpkgs }: {
nixosConfigurations.server = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
(nixpkgs + "/nixos/modules/virtualisation/amazon-image.nix")
];
};
};
}# 构建 closure,不激活云主机
nix build .#nixosConfigurations.server.config.system.build.toplevel
# 已确认 SSH 用户与权限后,单独进行远端临时激活;24.05 参数示例
nixos-rebuild test --flake .#server \
--target-host admin@<CLOUD_HOST> --use-remote-sudo
# 通过实际运行与 SSH 检查后,才采用 switch 持久部署官方 nix.dev 历史教程使用 tweag/terraform-nixos 的 aws_image_nixos 与 deploy_nixos 模块。它说明了同一分工,但旧 release 和历史 revision 不能当成当前版本保证;客户端/目标架构不同时还需要适当的 Linux builder 或交叉构建。
多环境管理#
开发/测试/生产
# environments/dev.tfvars
instance_type = "t3.small"
server_count = 1
# environments/prod.tfvars
instance_type = "t3.xlarge"
server_count = 3这些变量须在主配置中先声明并实际被资源使用;否则 tfvars 不会自动改变实例类型或数量。为各环境使用独立 state/backend 和凭据。
使用:
terraform plan -var-file=environments/prod.tfvars -out=prod.tfplan
# 审阅计划后实际应用
terraform apply prod.tfplan与 NixOps 对比#
| 维度 | Terraform | NixOps 类 NixOS 部署工具 |
|---|---|---|
| 云资源管理 | 通过各云 provider 定义 VM、网络、存储等资源 | 通过工具版本、backend/plugin 对接资源,能力随生态而变 |
| NixOS 配置 | 通过已核验 user-data、外部模块或独立部署流程连接 | 面向 NixOS closure 与系统部署,集成方式由工具接口决定 |
| 状态管理 | state、锁与远程 backend 需独立设计 | 各工具有自己的状态和 deployment 管理方式 |
| 团队协作 | 取决于 backend、权限、审批和团队流程 | 同样取决于工具生态、状态共享与权限流程 |
| 选择依据 | 现有基础设施 provider 与云治理需求 | NixOS 配置、节点管理与团队熟悉程度 |
这不是对工具稳定性或团队协作能力的排名。选型前核验所用工具版本、云 backend 和恢复流程。
最佳实践#
- 分离基础设施和配置:Terraform 管理云资源,NixOS 管理系统
- 使用远程状态:配置 S3 后端存储 Terraform 状态
- 版本锁定:锁定 NixOS 版本和 AMI ID
- 密钥管理:使用 AWS Secrets Manager 或 HashiCorp Vault
# 使用远程状态存储
terraform {
backend "s3" {
bucket = "<STATE_BUCKET>"
key = "nixos/terraform.tfstate"
region = "us-west-2"
encrypt = true
}
}State 可能记录敏感值,标记 sensitive 主要控制显示,不等于把数据从 state 删除。远程 backend 还要配置访问权限、加密、版本化、并发锁与恢复;不要把 state 或 plan 产物当普通公开源码上传。云资源回滚应通过审阅过的旧配置与新 plan,而不是假定 NixOS generation 回滚会恢复云网络或磁盘。
参考与延伸
- nix.dev Terraform 历史教程 ↗
- AWS provider EC2 instance ↗
- AWS provider route table ↗
- Terraform sensitive data ↗
根据技术笔记重新整理,示例使用通用名称。