Skip to content

搞英语 → 看世界

翻译英文优质信息和名人推特

Menu
  • 首页
  • 作者列表
  • 独立博客
  • 专业媒体
  • 名人推特
  • 邮件列表
  • 关于本站
Menu

使用 Terraform 自动将 NixOS 机器同化到您的 Tailnet 中

Posted on 2022-12-07

英雄形象 nix-flake-terraform-waifu 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 帐户。
马拉是黑客
< Mara > 迂腐地,Scaleway 可以用任何其他服务器主机替换。您还可以删除所有特定于 Tailscale 的配置。您也可以使用不同的 DNS 提供商。您可能需要检查您选择的提供商的 Terraform 注册表。大多数常见和不常见的云都应该有一个 Terraform 提供者,但事实和情况可能会有所不同。 GitHub 可以替换为任何其他 git 主机。

在编写本教程时,我还做出以下假设:

  • 您有一个名为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 有一个很好的指南,我不会在这里重复。

马拉是黑客
< Mara > 如果您没有安装 AWS CLI,请使用nix run nixpkgs#awscli2代替该文档中的aws命令。

最后,使用以下命令将所有这些变量设置到您的环境中:

 export $(cat .env |xargs -L 1) 

马拉是黑客
< Mara > 如果您经常这样做,您可能希望在您的 shell 配置文件中将此命令别名为loaddotenv 。

配置 Terraform

在您的 git 存储库中,创建一个名为main.tf的新文件。这是您的 Terraform 配置将要存在的地方。您可以使用任何您喜欢的名称,但惯例是使用main.tf作为“主要”资源,任何补充资源都可以存在于它们自己的文件中。

Terraform 的最佳实践之一是将其世界观存储在非本地存储中,例如Amazon S3 。我的状态桶名为within-tf-state ,但您的状态桶名称会有所不同。有关如何建立此类状态桶的更多信息,请参阅上游 Terraform 文档。

马拉很开心
< Mara > 如果不设置状态桶,Terraform 会默认将状态存储在当前工作目录中。此状态文件将包含生成的机密,例如 Tailscale 授权密钥。最好将其存储在 S3 中,以避免意外泄露 GitHub 存储库中的秘密。
 # 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 

马拉是黑客
< Mara > 如果你还没有安装 terraform,你可以运行它而不安装它,方法是在这些命令中的任何一个中将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。

凯蒂是咖啡
< Cadey > 我个人在我的服务器上使用 NixOS unstable,因为我重视更新和滚动发布。

这听起来有点神秘(在某种程度上确实如此),但在较高的层次上,它依赖于/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 移过来的原因。

凯蒂是咖啡
< Cadey > 严格来说,您不必创建一个浮动 IP 地址来附加到服务器,但这样做是最佳实践。如果您将来更换生产主机,让它的 IPv4 地址保持不变可能是个好主意。 DNS 传播需要永远。
 # 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 } 

马拉很开心
< Mara > 创建两个单独的 DNS 条目背后的原因是对读者的练习。
 { 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; } 

马拉很开心
< Mara > 其余部分是给读者的练习。

创建你的服务器

最后,现在一切就绪,您可以使用terraform apply创建您的服务器:

 terraform apply

Terraform 将打印出它认为需要做的事情的列表。请仔细阅读并确保它提出的计划对您有意义。当您对 Terraform 将做正确的事情感到满意时,请按照它给您的说明进行操作。如果您不满意它会做正确的事情,请按 control-c。

让它运行,它将自动创建您在main.tf中声明的所有基础设施。整个基础架构图应如下所示:

马拉是黑客
< Mara > 如果这对您来说太小,请单击此处。图中发生了很多事情,因为 Terraform 列出了所有内容及其最终依赖项。

您可以使用以下命令通过 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 的乐趣!

原文: https://xeiaso.net/blog/nix-flakes-terraform

本站文章系自动翻译,站长会周期检查,如果有不当内容,请点此留言,非常感谢。
  • Abhinav
  • Abigail Pain
  • Adam Fortuna
  • Alberto Gallego
  • Alex Wlchan
  • Answer.AI
  • Arne Bahlo
  • Ben Carlson
  • Ben Kuhn
  • Bert Hubert
  • Bits about Money
  • Brian Krebs
  • ByteByteGo
  • Chip Huyen
  • Chips and Cheese
  • Cool Infographics
  • Dan Sinker
  • David Walsh
  • Dmitry Dolzhenko
  • Elad Gil
  • Ellie Huxtable
  • Ethan Marcotte
  • Exponential View
  • FAIL Blog
  • Founder Weekly
  • Geoffrey Huntley
  • Geoffrey Litt
  • Greg Mankiw
  • Henrique Dias
  • Hypercritical
  • IEEE Spectrum
  • Investment Talk
  • Jaz
  • Jeff Geerling
  • Jonas Hietala
  • Josh Comeau
  • Lenny Rachitsky
  • Lou Plummer
  • Matt Stoller
  • Mert Bulan
  • Mostly metrics
  • News Letter
  • NextDraft
  • Non_Interactive
  • Not Boring
  • One Useful Thing
  • Phil Eaton
  • Product Market Fit
  • Readwise
  • ReedyBear
  • Robert Heaton
  • Ruben Schade
  • Sage Economics
  • Sam Altman
  • selfh.st
  • Shtetl-Optimized
  • Simon schreibt
  • Slashdot
  • Small Good Things
  • Taylor Troesh
  • Telegram Blog
  • The Macro Compass
  • The Pomp Letter
  • Thinking Deep & Wide
  • Tim Kellogg
  • 英文媒体
  • 英文推特
  • 英文独立博客
©2025 搞英语 → 看世界 | Design: Newspaperly WordPress Theme