Eimis Anime Diffusion v1.0生成的图片——少女、凤女、蓬松的头发、精灵剪、红发、红眼、中二病、战争、人间地狱、美丽细致的爆炸、冰冷的机器、眼睛里的火光、燃烧, 金属质感, 精致布料, 金属雕刻, 体积, 最佳质量, 金属细节, 金属划痕, 金属缺陷, 杰作, 最佳质量, 最佳质量, 插图, 高分辨率, 杰作, 轮廓加深, 插图, (美丽细致的女孩),美丽细致的光芒,绿色项链,绿色耳环,和服,扇子,咧嘴笑
为了争论起见,假设您想使用Terraform创建所有云基础设施,但您还想使用NixOS和Nix flakes 。您将遇到的主要问题之一是 Nix 薄片和 Terraform 都是声明性的,并且没有简单的方法来填充 Terraform 状态和 Nix 薄片属性。我想我已经找到了一种方法来做到这一点,今天您将学习如何将这两个相互冲突的世界粘合在一起。
要求
为了按照编写的方式继续本教程,您需要已经设置以下内容:
- 一个Tailscale帐户。
- 一个Scaleway帐户。
- 安装了 Nix 的 amd64 Linux 机器或设置了 Apple Rosetta 或配置了
qemu-user
的 aarch64 Linux VM,以允许您在 aarch64 主机上运行 amd64 构建。 - AWS Route 53域设置。
- 一个 GitHub 帐户。
在编写本教程时,我还做出以下假设:
- 您有一个名为
tag:prod
的 Tailscale ACL 标签,您可以使用Tailscale SSH访问它。 - 您已启用 Nix 薄片。
- 运行这一切的设备在你的尾网上。
制作一个新的 GitHub 仓库
您需要做的第一件事就是创建一个新的 GitHub 存储库。你可以给它起任何你喜欢的名字,但我把我的名字命名为 automagic-terraform-nixos 。
创建存储库后,将其克隆到本地:
git clone [email protected]:Xe/automagic-terraform-nixos.git
创建一个包含以下条目的.gitignore
文件:
result .direnv .env .terraform
获取凭据
现在您有了一个新的 GitHub 存储库来存储文件,您需要收集各种凭证,Terraform 将使用这些凭证来控制您的基础设施提供者。为了便于使用,您将把它们存储在一个名为.env
的文件中,并使用 shell 命令将这些值加载到您的 shell 中。
多变的 | 如何获得 |
---|---|
TAILSCALE_TAILNET |
从管理面板复制组织名称。 |
TAILSCALE_API_KEY |
在管理面板中创建 API 密钥。 |
SCW_ACCESS_KEY |
在控制台中创建凭据并复制访问密钥。 |
SCW_SECRET_KEY |
在控制台中创建凭据并复制密钥。 |
接下来,您需要配置 AWS CLI,并扩展默认的 AWS API 客户端。 AWS 有一个很好的指南,我不会在这里重复。
nix run nixpkgs#awscli2
代替该文档中的aws
命令。最后,使用以下命令将所有这些变量设置到您的环境中:
export $(cat .env |xargs -L 1)
loaddotenv
。配置 Terraform
在您的 git 存储库中,创建一个名为main.tf
的新文件。这是您的 Terraform 配置将要存在的地方。您可以使用任何您喜欢的名称,但惯例是使用main.tf
作为“主要”资源,任何补充资源都可以存在于它们自己的文件中。
Terraform 的最佳实践之一是将其世界观存储在非本地存储中,例如Amazon S3 。我的状态桶名为within-tf-state
,但您的状态桶名称会有所不同。有关如何建立此类状态桶的更多信息,请参阅上游 Terraform 文档。
# main.tf terraform { backend "s3" { bucket = "within-tf-state" key = "prod" region = "us-east-1" } }
现在您已经设置了状态后端,您需要声明此 Terraform 配置将使用的提供程序。这将有助于确保 Terraform 从正确的所有者那里获取正确的提供者。在您刚刚声明的backend "s3"
块下方添加此 Terraform 配置块:
# main.tf terraform { # below the backend "s3" config
required_providers { aws = { source = "hashicorp/aws" }
cloudinit = { source = "hashicorp/cloudinit" }
tailscale = { source = "tailscale/tailscale" }
scaleway = { source = "scaleway/scaleway" } } }
此配置需要一些变量来处理在外部世界中管理的事物。 Scaleway 要求每个资源都是“项目”的一部分,您需要将该项目 ID 放入您的配置中。 Scaleway 提供程序还允许我们有一个默认的项目 ID,因此您将把您的项目 ID 放在一个变量中。
Route 53 (AWS DNS) 区域也将放入其自己的变量中。
# main.tf variable "project_id" { type = string description = "Your Scaleway project ID." }
variable "route53_zone" { type = string description = "DNS name of your route53 zone." }
您可以将默认值加载到terraform.tfvars
# terraform.tfvars project_id = "2ce6d960-f3ad-44bf-a761-28725662068a" route53_zone = "xeserv.us"
相应地更改您的项目 ID和 Route 53 区域名称。
完成后,您可以配置 Scaleway 提供程序。如果您想让所有资源默认在 Scaleway 的巴黎数据中心进行配置,您可以使用如下所示的配置:
# main.tf provider "scaleway" { zone = "fr-par-1" region = "fr-par" project_id = var.project_id }
现在您已经声明了所有样板文件,您可以使用命令terraform init
准备好 Terraform。这将自动下载所有需要的 Terraform 提供程序并在 S3 中设置状态文件。
terraform init
terraform
替换为nix run nixpkgs#terraform
现在 Terraform 已初始化,您可以通过创建指向它的data
资源将 Route 53 区域导入到您的配置中:
# main.tf data "aws_route53_zone" "dns" { name = var.route53_zone }
要确认一切正常,请运行terraform plan
并查看它是否报告需要创建 0 个资源:
terraform plan
如果它报告您的 DNS 区域不存在,请验证terraform.tfvars
中的配置并重试。
使用tailscale_tailnet_key
资源为您的新 NixOS 服务器创建 Tailscale authkey:
# main.tf resource "tailscale_tailnet_key" "prod" { reusable = true ephemeral = false preauthorized = true tags = ["tag:prod"] }
接下来,您需要为此虚拟机创建cloud-init配置。 Cloud-init 并不是管理这种同化的最佳工具,但它被广泛采用,因为它做得很好,你可以依赖它。
在 Terraform 中创建 cloud-init 配置的方法有很多,但我觉得最好为此使用cloudinit 提供程序。它可以让你从多个“部分”组装一个 cloud-init 配置,但这个例子只会使用一个部分。
data "cloudinit_config" "prod" { gzip = false base64_encode = false
part { content_type = "text/cloud-config" filename = "nixos-infect.yaml" content = sensitive(<<-EOT #cloud-config write_files: - path: /etc/NIXOS_LUSTRATE permissions: '0600' content: | etc/tailscale/authkey - path: /etc/tailscale/authkey permissions: '0600' content: "${tailscale_tailnet_key.prod.key}" - path: /etc/nixos/tailscale.nix permissions: '0644' content: | { pkgs, ... }: { services.tailscale.enable = true;
systemd.services.tailscale-autoconnect = { description = "Automatic connection to Tailscale"; after = [ "network-pre.target" "tailscale.service" ]; wants = [ "network-pre.target" "tailscale.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig.Type = "oneshot"; path = with pkgs; [ jq tailscale ]; script = '' sleep 2 status="$(tailscale status -json | jq -r .BackendState)" if [ $status = "Running" ]; then # if so, then do nothing exit 0 fi tailscale up --authkey $(cat /etc/tailscale/authkey) --ssh ''; }; } runcmd: - sed -i 's:#.*$::g' /root/.ssh/authorized_keys - curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIXOS_IMPORT=./tailscale.nix NIX_CHANNEL=nixos-unstable bash 2>&1 | tee /tmp/infect.log EOT ) } }
在撰写本文时,Scaleway 没有用于创建新服务器的预烘焙 NixOS 映像。您可以采取的一种方法是制作自己的预烘焙映像,然后根据需要对其进行自定义,但我认为使用nixos-infect将 Ubuntu 安装转换为 NixOS 安装更令人兴奋。 cloud-config 文件末尾的runcmd
块告诉 cloud-init 运行 nixos-infect 以将 VPS 重建为 NixOS unstable,但您可以将其更改为任何其他版本的 NixOS。
这听起来有点神秘(在某种程度上确实如此),但在较高的层次上,它依赖于/etc/NIXOS_LUSTRATE
文件,如NixOS 手册中关于从另一个 Linux 发行版安装 NixOS 部分中所述。您将在 Ubuntu 端使用 cloud-init 将 tailscale authkey 放入目标机器上的/etc/tailscale/authkey
中,然后通过将路径etc/tailscale/authkey
放入NIXOS_LUSTRATE
中确保它被复制到 NixOS 安装中文件。
您可以在这里做的另一件事是安装 Tailscale 并在 Ubuntu 端对其控制平面进行身份验证,然后将var/lib/tailscale
添加到NIXOS_LUSTRATE
文件中,但我觉得这可能比感染已经花费的时间更长带有 NixOS 的云实例。
nixos-infect 的功能之一是能够使用任意 Nix 表达式自定义目标 NixOS 安装。此配置将 NixOS 模块放入/etc/nixos/tailscale.nix
中,该模块执行以下操作:
- 启用 Tailscale 的节点代理 tailscaled
- 创建一个 systemd oneshot 作业(作为一次性脚本而不是持久服务运行的东西),它将向 Tailscale 验证机器并设置Tailscale SSH
oneshot 将从/etc/tailscale/authkey
读取相关的 authkey,这就是它从 Ubuntu 移过来的原因。
# main.tf resource "scaleway_instance_ip" "prod" {}
resource "scaleway_instance_server" "prod" { type = "DEV1-S" image = "ubuntu_jammy" ip_id = scaleway_instance_ip.prod.id enable_ipv6 = true cloud_init = data.cloudinit_config.prod.rendered tags = ["nixos", "http", "https"] }
最后,您可以使用此配置创建prod.your.domain
DNS 条目:
resource "aws_route53_record" "prod_A" { zone_id = data.aws_route53_zone.dns.zone_id name = "prod" type = "A" records = [scaleway_instance_ip.prod.address] ttl = 300 }
resource "aws_route53_record" "prod_AAAA" { zone_id = data.aws_route53_zone.dns.zone_id name = "prod" type = "AAAA" records = [scaleway_instance_server.prod.ipv6_address] ttl = 300 }
{ inputs = { nixpkgs.url = "nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; };
outputs = { self, nixpkgs, flake-utils }: let mkSystem = extraModules: nixpkgs.lib.nixosSystem rec { system = "x86_64-linux"; modules = [ # bake the git revision of the repo into the system ({ ... }: { system.configurationRevision = self.sourceInfo.rev; }) ] ++ extraModules; }; in flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system: let pkgs = import nixpkgs { inherit system; }; in rec { devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ terraform awscli2 ]; }; }) // { # TODO: put nixosConfigurations here later }; }
outputs 函数在这里可能看起来有点奇怪,但我们用它做了两件事:
- 使用 terraform 创建开发环境 (devShell) 并为 amd64 和 aarch64 linux 系统安装 AWS cli
- 设置动态定义
nixosConfigurations
还值得注意的是,在输出函数顶部定义的mkSystem
函数会将自定义配置的 git 提交烘焙到生成的 NixOS 配置中。这将导致无法部署未提交给 git 的更改。
将两个世界粘合在一起
现在你可以做一些令人兴奋的事情了:使用local-exec
provisioner和 shell 脚本将 Nix flakes 和 Terraform 的两个世界粘合在一起,如下所示:
#!/usr/bin/env bash
set -e [ ! -z "$ DEBUG " ] && set -x
USAGE (){ echo " Usage: ` basename $ 0 ` <server_name> " exit 2 }
if [ -z "$ 1 " ] ; then USAGE fi
server_name ="$ 1 " public_ip ="$ 2 "
ssh_ignore (){ ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $ * }
ssh_victim (){ ssh_ignore root@"$ { public_ip } " $ * }
mkdir -p " ./hosts/ $ { server_name } " echo "$ { public_ip } " >> ./hosts/"$ { server_name } "/public-ip
until ssh_ignore " root@ $ { server_name } " uname -av do sleep 30 done
scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no " root@ $ { server_name }:/etc/nixos/hardware-configuration.nix " " ./hosts/ $ { server_name } " || :
rm -f ./hosts/"$ { server_name } "/default.nix cat <<- EOC >> ./hosts/"$ { server_name } "/default.nix { ... }: { imports = [ ./hardware-configuration.nix ];
boot.cleanTmpDir = true; zramSwap.enable = true; networking.hostName = " $ { server_name }"; services.openssh.enable = true; services.tailscale.enable = true; networking.firewall.checkReversePath = "loose"; users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM6NPbPIcCTzeEsjyx0goWyj6fr2qzcfKCCdOUqg0N/v" # alrest ]; system.stateVersion = "23.05"; } EOC
git add . git commit -sm " add machine $ { server_name }: $ { public_ip } " nix build .#nixosConfigurations."$ { server_name } ".config.system.build.toplevel
export NIX_SSHOPTS =' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' nix-copy-closure -s root@"$ { public_ip } " $( readlink ./result) ssh_victim nix-env --profile /nix/var/nix/profiles/system --set $( readlink ./result) ssh_victim $( readlink ./result)/bin/switch-to-configuration switch
git push
通过在其定义的末尾添加此配置块,将供应器脚本添加到您的scaleway_instance_server
中:
# main.tf resource "scaleway_instance_server" "prod" { # ...
provisioner "local-exec" { command = "${path.module}/assimilate.sh ${self.name} ${self.public_ip}" }
provisioner "local-exec" { when = destroy command = "rm -rf ${path.module}/hosts/${self.name}" } }
这将在每次创建新实例时触发assimilate.sh
脚本运行,并在销毁实例时删除特定于主机的配置。
然后,您可以通过将以下配置添加到flake.nix
文件,将nixosConfigurations
输出连接到脚本创建的文件夹结构:
}) // { nixosConfigurations = let hosts = builtins.readDir ./hosts; in builtins.mapAttrs (name: _: mkSystem [ ./hosts/${name} ]) hosts; };
之所以可行,是因为我对 git 存储库中hosts
文件夹的目录结构做出了严格的假设。当我写这个配置时,我假设hosts
文件夹看起来像这样:
hosts └── tf-srv-naughty-perlman ├── default.nix ├── hardware-configuration.nix └── public-ip
每个主机都有自己的文件夹,以其自身命名,并在default.nix
中进行配置,并将指向任何其他相关配置(例如hardware-configuration.nix
)。由于此目录层次结构是可预测的,因此您可以使用builtins.readDir
函数获取hosts
目录中所有文件夹的列表:
nix-repl> builtins.readDir ./hosts { tf-srv-naughty-perlman = "directory"; }
然后,您可以使用 builtins.mapAttrs 遍历builtins.readDir
返回的属性集中的每个键->值对,并将主机名转换为builtins.mapAttrs
系统定义:
nix-repl> hosts = builtins.readDir ./hosts nix-repl> builtins.mapAttrs (name: _: ./hosts/${name}) hosts { tf-srv-naughty-perlman = /home/cadey/code/Xe/automagic-terraform-nixos/hosts/tf-srv-naughty-perlman; }
创建你的服务器
最后,现在一切就绪,您可以使用terraform apply
创建您的服务器:
terraform apply
Terraform 将打印出它认为需要做的事情的列表。请仔细阅读并确保它提出的计划对您有意义。当您对 Terraform 将做正确的事情感到满意时,请按照它给您的说明进行操作。如果您不满意它会做正确的事情,请按 control-c。
让它运行,它将自动创建您在main.tf
中声明的所有基础设施。整个基础架构图应如下所示:
您可以使用以下命令通过 SSH 连接到服务器:
ssh root@generated-server-name
手动推送配置更改
您可以使用许多 NixOS 工具来推送配置更改,例如deploy-rs ,但您也可以按照以下三个步骤手动推送配置更改:
- 为目标机器构建新的系统配置
- 将系统配置复制到目标机器
- 激活该新配置
您可以使用如下脚本自动执行这些步骤:
#!/usr/bin/env bash # pushify.sh
set -e [ ! -z "$ DEBUG " ] && set -x
# validate arguments USAGE (){ echo " Usage: ` basename $ 0 ` <server_name> " exit 2 }
if [ -z "$ 1 " ] ; then USAGE fi
server_name ="$ 1 " public_ip =$ ( cat ./hosts/ $ { server_name }/public-ip)
ssh_ignore (){ ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $ * }
ssh_victim (){ ssh_ignore root@"$ { public_ip } " $ * }
# build the system configuration nix build .#nixosConfigurations."$ { server_name } ".config.system.build.toplevel
# copy the configuration to the target machine export NIX_SSHOPTS =' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ' nix-copy-closure -s root@"$ { public_ip } " $( readlink ./result)
# register it to the system profile ssh_victim nix-env --profile /nix/var/nix/profiles/system --set $( readlink ./result)
# activate the new configuration ssh_victim $( readlink ./result)/bin/switch-to-configuration switch
你可以像这样使用它:
./pushify.sh generated-server-name
回滚
要回滚配置,SSH 到服务器并运行nixos-rebuild --rollback switch
。
设置自动更新
NixOS 的简洁且长期未被记录的功能之一是system.autoUpgrade模块。这允许 NixOS 系统定期轮询其配置更改或 NixOS 本身的更新并自动应用它们。如果内核升级,它甚至会重新启动。
为了设置它,创建一个名为common
的文件夹并将以下文件放入其中:
# common/default.nix { ... }: { system.autoUpgrade = { enable = true; # replace this with your GitHub repo flake = "github:Xe/automagic-terraform-nixos"; }; }
然后将./common
添加到mkSystem
函数中的模块列表中,如下所示:
mkSystem = extraModules: nixpkgs.lib.nixosSystem rec { system = "x86_64-linux"; modules = [ ./common ({ ... }: { system.configurationRevision = self.sourceInfo.rev; }) ] ++ extraModules; };
将这些更改提交到 git 并将配置部署到您的服务器:
git add . git commit -sm "set up autoUpgrade" git push ./pushify.sh generated-server-name
您的 NixOS 机器会在当地时间每天凌晨04:40
左右自动将更改拉取到您的 GitHub 存储库一次。您可以通过运行以下命令手动触发此操作:
ssh root@generated-server-name systemctl start nixos-upgrade.service journalctl -fu nixos-upgrade.service
读者练习
本教程已告诉您有关使用 Terraform 设置新的 NixOS 服务器所需了解的所有信息。以下是您可以做的一些练习,以帮助您了解有关配置新 NixOS 机器的新鲜有趣的事情:
- 设置到borgbase的备份。
- 使用agenix设置加密的秘密管理。
- 为您的机器创建一个 AWS IAM 用户并将秘密文件复制到它。您将如何使用新机器以编程方式执行此操作?提示:
NIXOS_LUSTRATE
可以提供帮助!将其用于 Let’s Encrypt。 - 尝试NixOS 手册中列出的一些服务。您将如何在 Tailscale 上暴露其中一个?
- 您将如何使用此 Terraform 清单在Vultr上创建一个实例?数字海洋怎么样?
- 您将如何将VPC附加到您的服务器并将其作为带有 Tailscale 的子网路由器公开给您的其他机器?
- 设置Pleroma 服务器。请务必使用Let’s Encrypt获取 HTTPS 证书!
我希望这是有启发性的!享受你的新服务器,享受探索 NixOS 的乐趣!