改由 cloud-init 声明 Ayatori 容器静态地址
terraform / validate (pull_request) Failing after 10m47s
yaml / yaml (pull_request) Failing after 10m49s
ansible / lint (pull_request) Successful in 12m6s
ansible / collection-test (pull_request) Successful in 12m35s

- 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]>
This commit is contained in:
2026-10-01 17:11:42 +00:00
co-authored by Claude Opus 5.5
parent 5dcfcad098
commit 094b38b4b5
6 changed files with 40 additions and 73 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ homelab_dns:
# RRsets are reconciled; Samba-generated AD/Kerberos records are untouched. # RRsets are reconciled; Samba-generated AD/Kerberos records are untouched.
records: records:
- { zone: ad.ddupan.top, name: incus, type: A, values: [192.168.10.127] } - { zone: ad.ddupan.top, name: incus, type: A, values: [192.168.10.127] }
# Static, outside the NEC IX pool (.128-.250); infrastructure/incus/ansible/containers.yml # Static, outside the NEC IX pool (.128-.250); infrastructure/incus/terraform reads
# reads these same values to pin each container's netplan address. # these same values into each container's cloud-init network-config.
- { zone: ad.ddupan.top, name: ayatori-dev, type: A, values: [192.168.10.11] } - { zone: ad.ddupan.top, name: ayatori-dev, type: A, values: [192.168.10.11] }
- { zone: ad.ddupan.top, name: ayatori-prod, type: A, values: [192.168.10.12] } - { zone: ad.ddupan.top, name: ayatori-prod, type: A, values: [192.168.10.12] }
- { zone: ad.ddupan.top, name: bao, type: A, values: [192.168.10.8] } - { zone: ad.ddupan.top, name: bao, type: A, values: [192.168.10.8] }
+8 -7
View File
@@ -111,11 +111,11 @@ ANSIBLE_CONFIG=ansible.cfg ANSIBLE_COLLECTIONS_PATH=collections ANSIBLE_LOCAL_TE
kube-apiserver、controller 或数据库;准备 Prod 空容器不代表生产服务已上线。 kube-apiserver、controller 或数据库;准备 Prod 空容器不代表生产服务已上线。
网络接入既有 `br0`,固定 MAC 分别为 `02:16:3e:aa:00:01`、`02:16:3e:aa:00:02`。 网络接入既有 `br0`,固定 MAC 分别为 `02:16:3e:aa:00:01`、`02:16:3e:aa:00:02`。
首次启动走 DHCP,随后 `ansible/containers.yml` 改为静态地址:Dev `192.168.10.11`、 Terraform 通过 `cloud-init.network-config` 配置静态地址:Dev `192.168.10.11`、
Prod `192.168.10.12`(`ayatori-{dev,prod}.ad.ddupan.top`)。地址只在 Prod `192.168.10.12`(`ayatori-{dev,prod}.ad.ddupan.top`)。地址只在
`infrastructure/dns/records.yml` 声明一次,netplan 与 AD DNS 均从此读取;须位于 `infrastructure/dns/records.yml` 声明一次,Terraform 与 AD DNS 均从此读取;须位于
NEC IX DHCP 池(`.128–.250`)之外。容器内禁用 cloud-init 网络模块,否则每次启动都会 NEC IX DHCP 池(`.128–.250`)之外。⚠ 镜像模板只在创建/复制时渲染 cloud-init seed,
重写回 DHCP。 对已有实例改地址不会生效(重启、`cloud-init clean` 都不行),只能重建实例。
初始账号 `panxiao81`,使用 `ansible/files/panxiao81.pub` 公钥登录,可 sudo; 初始账号 `panxiao81`,使用 `ansible/files/panxiao81.pub` 公钥登录,可 sudo;
密码登录关闭。宿主也可使用 `incus exec local:ayatori-dev -- bash`。 密码登录关闭。宿主也可使用 `incus exec local:ayatori-dev -- bash`。
@@ -131,7 +131,9 @@ terraform -chdir=infrastructure/incus/terraform plan -detailed-exitcode
``` ```
provider 固定 1.2.0 并保留 lockfile,镜像固定 Ubuntu 24.04 cloud 构建指纹。 provider 固定 1.2.0 并保留 lockfile,镜像固定 Ubuntu 24.04 cloud 构建指纹。
上游滚动镜像不保证永久保存,重建前需确认本机镜像缓存或归档可用。 上游滚动镜像不保证永久保存:该指纹在 2026-10-01 已从 `images:` 下架,现从 `local:`
缓存创建;缓存闲置超过 `images.remote_cache_expiry`(默认 10 天)会被自动删除,重建前需确认
本机缓存或归档可用,否则须改用新指纹。
cloud-init 用户设置主要在首次启动执行,修改声明不能替代后续用户/密钥轮换流程。 cloud-init 用户设置主要在首次启动执行,修改声明不能替代后续用户/密钥轮换流程。
当前 state 位于 `terraform/terraform.tfstate`(Git 忽略),是本地 backend; 当前 state 位于 `terraform/terraform.tfstate`(Git 忽略),是本地 backend;
@@ -144,5 +146,4 @@ cloud-init 用户设置主要在首次启动执行,修改声明不能替代后
`mkdir ... file exists` 竞争;按上面的串行 apply 执行。失败后先重新 plan, `mkdir ... file exists` 竞争;按上面的串行 apply 执行。失败后先重新 plan,
保留已成功创建的实例,不清理或销毁其资源。 保留已成功创建的实例,不清理或销毁其资源。
`ansible/containers.yml` 通过本机 Incus exec 幂等固定静态地址,并安装、启用 SSH server; `ansible/containers.yml` 通过本机 Incus exec 幂等安装并启用 SSH server。
走 exec 而非 SSH,切换地址不会切断 Ansible 自身连接。
+1 -10
View File
@@ -1,17 +1,8 @@
--- ---
- name: 准备 Ayatori 基础容器的网络与 SSH 入口 - name: 准备 Ayatori 基础容器的 SSH 入口
hosts: incus_hosts hosts: incus_hosts
gather_facts: false gather_facts: false
# 静态地址与 AD DNS 记录同源,避免两处地址漂移。
vars_files:
- ../../dns/records.yml
tasks: tasks:
- name: 按容器协调静态地址
ansible.builtin.include_tasks: tasks/container-network.yml
loop: [ayatori-dev, ayatori-prod]
loop_control:
loop_var: incus_container
- name: 按容器协调 SSH - name: 按容器协调 SSH
ansible.builtin.include_tasks: tasks/container-ssh.yml ansible.builtin.include_tasks: tasks/container-ssh.yml
loop: [ayatori-dev, ayatori-prod] loop: [ayatori-dev, ayatori-prod]
@@ -1,52 +0,0 @@
---
# 地址必须在 NEC IX DHCP 池(.128–.250)之外;池由路由器手工维护,没有 reservation 可声明。
# 用 incus exec 而非 SSH 执行,切换地址不会断开 Ansible 自身的连接。
- name: 读取容器的 AD DNS 地址
ansible.builtin.set_fact:
container_ipv4: >-
{{ (homelab_dns.samba.records
| selectattr('zone', 'equalto', 'ad.ddupan.top')
| selectattr('name', 'equalto', incus_container)
| first)['values'] | first }}
# cloud-init 网络模块每次启动都会重写 50-cloud-init.yaml,必须先禁用它,静态配置才能持久。
# 网关与 resolver 照搬原 DHCP 下发值(.1 网关;Blocky .127 优先、路由器 .1 兜底)。
- name: 声明静态网络
ansible.builtin.command:
argv:
- incus
- exec
- "local:{{ incus_container }}"
- --env
- "CLOUD_CFG={{ container_cloud_cfg }}"
- --env
- "NETPLAN={{ container_netplan }}"
- --
- sh
- -euc
- |
changed=0
put() {
if [ "$(cat "$1" 2>/dev/null)" != "$2" ]; then
printf '%s\n' "$2" > "$1"; chmod 600 "$1"; changed=1
fi
}
put /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg "$CLOUD_CFG"
put /etc/netplan/60-static.yaml "$NETPLAN"
if [ -e /etc/netplan/50-cloud-init.yaml ]; then
rm /etc/netplan/50-cloud-init.yaml; changed=1
fi
if [ "$changed" = 1 ]; then netplan apply; echo CHANGED; fi
vars:
container_cloud_cfg: "network: {config: disabled}"
container_netplan: |-
network:
version: 2
ethernets:
eth0:
dhcp4: false
addresses: [{{ container_ipv4 }}/24]
routes: [{to: default, via: 192.168.10.1}]
nameservers: {addresses: [192.168.10.127, 192.168.10.1]}
register: container_network
changed_when: "'CHANGED' in container_network.stdout"
@@ -55,9 +55,11 @@
register: container_sshd_write register: container_sshd_write
changed_when: true changed_when: true
# 24.04 新装时只有 ssh.socket 运行,/run/sshd 要等 ssh.service 的 RuntimeDirectory 才创建,
# 缺它时 sshd -t 直接失败;按同样的 0755 预建(/run 为 tmpfs,不留持久状态)。
- name: 校验 SSH 配置 - name: 校验 SSH 配置
ansible.builtin.command: ansible.builtin.command:
argv: [incus, exec, "local:{{ incus_container }}", --, /usr/sbin/sshd, -t] argv: [incus, exec, "local:{{ incus_container }}", --, sh, -c, install -d -m 0755 /run/sshd && exec /usr/sbin/sshd -t]
changed_when: false changed_when: false
- name: 更新运行中的 SSH 配置 - name: 更新运行中的 SSH 配置
+26 -1
View File
@@ -34,12 +34,24 @@ resource "incus_storage_pool" "ayatori" {
} }
} }
# 地址只在 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" { resource "incus_instance" "ayatori" {
for_each = toset(["dev", "prod"]) for_each = toset(["dev", "prod"])
name = "ayatori-${each.key}" name = "ayatori-${each.key}"
description = "Ayatori ${each.key} 基础容器;应用由独立部署流程管理" description = "Ayatori ${each.key} 基础容器;应用由独立部署流程管理"
# Ubuntu 24.04 cloud amd64,20260924_07:42;固定指纹避免重建时静默更换镜像。 # Ubuntu 24.04 cloud amd64,20260924_07:42;固定指纹避免重建时静默更换镜像。
image = "images:b39d3d56c30738ad74c507066cc5d410e1c0f1647bdcdff398be1ee51d63862a" # 上游 images: 已不再提供该构建(2026-10-01 重建时 "image couldn't be found"),只能取本机缓存;
# ⚠ 缓存镜像闲置超过 images.remote_cache_expiry(默认 10 天)会被 Incus 自动删除。
image = "local:b39d3d56c30738ad74c507066cc5d410e1c0f1647bdcdff398be1ee51d63862a"
type = "container" type = "container"
profiles = [] profiles = []
running = true running = true
@@ -50,6 +62,19 @@ resource "incus_instance" "ayatori" {
"limits.cpu" = "2" "limits.cpu" = "2"
"limits.memory" = "2GiB" "limits.memory" = "2GiB"
"limits.memory.swap" = "false" "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({ "cloud-init.user-data" = "#cloud-config\n${yamlencode({
hostname = "ayatori-${each.key}" hostname = "ayatori-${each.key}"
manage_etc_hosts = true manage_etc_hosts = true