- Terraform 写 cloud-init.network-config,地址从 records.yml 读取,与 AD DNS 同源 - 撤销 Ansible 直接改 netplan 的做法;镜像模板只在 create/copy 渲染 seed, 故两台空容器已用 -replace 重建 - 固定指纹已从上游 images: 下架,改从 local: 缓存创建 - sshd -t 前预建 /run/sshd:新装 24.04 只有 ssh.socket 运行,目录尚不存在 Co-Authored-By: Claude Opus 5.5 <[email protected]>
128 lines
3.8 KiB
Terraform
128 lines
3.8 KiB
Terraform
terraform {
|
||
required_version = ">= 1.10.0"
|
||
required_providers {
|
||
incus = {
|
||
source = "lxc/incus"
|
||
version = "1.2.0"
|
||
}
|
||
}
|
||
}
|
||
|
||
provider "incus" {
|
||
default_remote = "local"
|
||
remote {
|
||
name = "local"
|
||
address = "unix://"
|
||
}
|
||
remote {
|
||
name = "images"
|
||
address = "https://images.linuxcontainers.org"
|
||
protocol = "simplestreams"
|
||
public = true
|
||
}
|
||
}
|
||
|
||
# 专用子 dataset,不接管整个宿主 data 池。
|
||
resource "incus_storage_pool" "ayatori" {
|
||
name = "ayatori"
|
||
driver = "zfs"
|
||
config = {
|
||
source = "data/incus-ayatori"
|
||
}
|
||
lifecycle {
|
||
prevent_destroy = true
|
||
}
|
||
}
|
||
|
||
# 地址只在 records.yml 声明一次,AD DNS(ansible/dns.yml)与此处同源读取;
|
||
# 须位于 NEC IX DHCP 池(.128–.250)之外,池由路由器手工维护,无法声明 reservation。
|
||
locals {
|
||
ayatori_ipv4 = {
|
||
for r in yamldecode(file("${path.module}/../../dns/records.yml")).homelab_dns.samba.records :
|
||
trimprefix(r.name, "ayatori-") => r.values[0]
|
||
if r.zone == "ad.ddupan.top" && startswith(r.name, "ayatori-")
|
||
}
|
||
}
|
||
|
||
resource "incus_instance" "ayatori" {
|
||
for_each = toset(["dev", "prod"])
|
||
name = "ayatori-${each.key}"
|
||
description = "Ayatori ${each.key} 基础容器;应用由独立部署流程管理"
|
||
# Ubuntu 24.04 cloud amd64,20260924_07:42;固定指纹避免重建时静默更换镜像。
|
||
# 上游 images: 已不再提供该构建(2026-10-01 重建时 "image couldn't be found"),只能取本机缓存;
|
||
# ⚠ 缓存镜像闲置超过 images.remote_cache_expiry(默认 10 天)会被 Incus 自动删除。
|
||
image = "local:b39d3d56c30738ad74c507066cc5d410e1c0f1647bdcdff398be1ee51d63862a"
|
||
type = "container"
|
||
profiles = []
|
||
running = true
|
||
config = {
|
||
"boot.autostart" = "true"
|
||
"security.privileged" = "false"
|
||
"security.nesting" = "false"
|
||
"limits.cpu" = "2"
|
||
"limits.memory" = "2GiB"
|
||
"limits.memory.swap" = "false"
|
||
# ⚠ 镜像模板只在 create/copy 时渲染 cloud-init seed(when: [create, copy]),
|
||
# 对已有实例修改此键不会生效,重启或 cloud-init clean 也不会;改地址须重建实例。
|
||
# 网关与 resolver 沿用 LAN DHCP 下发值:.1 网关;Blocky .127 优先、路由器 .1 兜底。
|
||
"cloud-init.network-config" = yamlencode({
|
||
version = 2
|
||
ethernets = {
|
||
eth0 = {
|
||
addresses = ["${local.ayatori_ipv4[each.key]}/24"]
|
||
routes = [{ to = "default", via = "192.168.10.1" }]
|
||
nameservers = { addresses = ["192.168.10.127", "192.168.10.1"] }
|
||
}
|
||
}
|
||
})
|
||
"cloud-init.user-data" = "#cloud-config\n${yamlencode({
|
||
hostname = "ayatori-${each.key}"
|
||
manage_etc_hosts = true
|
||
ssh_pwauth = false
|
||
disable_root = true
|
||
users = [{
|
||
name = "panxiao81"
|
||
groups = ["sudo"]
|
||
shell = "/bin/bash"
|
||
sudo = ["ALL=(ALL) NOPASSWD:ALL"]
|
||
lock_passwd = true
|
||
ssh_authorized_keys = [trimspace(file("${path.module}/../ansible/files/panxiao81.pub"))]
|
||
}]
|
||
})}"
|
||
}
|
||
device {
|
||
name = "root"
|
||
type = "disk"
|
||
properties = {
|
||
path = "/"
|
||
pool = incus_storage_pool.ayatori.name
|
||
size = "20GiB"
|
||
}
|
||
}
|
||
device {
|
||
name = "eth0"
|
||
type = "nic"
|
||
properties = {
|
||
name = "eth0"
|
||
nictype = "bridged"
|
||
parent = "br0"
|
||
hwaddr = each.key == "dev" ? "02:16:3e:aa:00:01" : "02:16:3e:aa:00:02"
|
||
}
|
||
}
|
||
wait_for {
|
||
type = "ipv4"
|
||
nic = "eth0"
|
||
}
|
||
lifecycle {
|
||
prevent_destroy = true
|
||
}
|
||
}
|
||
|
||
output "containers" {
|
||
value = { for env, instance in incus_instance.ayatori : env => {
|
||
name = instance.name
|
||
ipv4 = instance.ipv4_address
|
||
mac = instance.mac_address
|
||
} }
|
||
}
|