diff --git a/infrastructure/dns/records.yml b/infrastructure/dns/records.yml index 13e7c62..40daa59 100644 --- a/infrastructure/dns/records.yml +++ b/infrastructure/dns/records.yml @@ -7,8 +7,8 @@ homelab_dns: # RRsets are reconciled; Samba-generated AD/Kerberos records are untouched. records: - { 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 - # reads these same values to pin each container's netplan address. + # Static, outside the NEC IX pool (.128-.250); infrastructure/incus/terraform reads + # 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-prod, type: A, values: [192.168.10.12] } - { zone: ad.ddupan.top, name: bao, type: A, values: [192.168.10.8] } diff --git a/infrastructure/incus/README.md b/infrastructure/incus/README.md index a496ec0..6847015 100644 --- a/infrastructure/incus/README.md +++ b/infrastructure/incus/README.md @@ -111,11 +111,11 @@ ANSIBLE_CONFIG=ansible.cfg ANSIBLE_COLLECTIONS_PATH=collections ANSIBLE_LOCAL_TE kube-apiserver、controller 或数据库;准备 Prod 空容器不代表生产服务已上线。 网络接入既有 `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`)。地址只在 -`infrastructure/dns/records.yml` 声明一次,netplan 与 AD DNS 均从此读取;须位于 -NEC IX DHCP 池(`.128–.250`)之外。容器内禁用 cloud-init 网络模块,否则每次启动都会 -重写回 DHCP。 +`infrastructure/dns/records.yml` 声明一次,Terraform 与 AD DNS 均从此读取;须位于 +NEC IX DHCP 池(`.128–.250`)之外。⚠ 镜像模板只在创建/复制时渲染 cloud-init seed, +对已有实例改地址不会生效(重启、`cloud-init clean` 都不行),只能重建实例。 初始账号 `panxiao81`,使用 `ansible/files/panxiao81.pub` 公钥登录,可 sudo; 密码登录关闭。宿主也可使用 `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 构建指纹。 -上游滚动镜像不保证永久保存,重建前需确认本机镜像缓存或归档可用。 +上游滚动镜像不保证永久保存:该指纹在 2026-10-01 已从 `images:` 下架,现从 `local:` +缓存创建;缓存闲置超过 `images.remote_cache_expiry`(默认 10 天)会被自动删除,重建前需确认 +本机缓存或归档可用,否则须改用新指纹。 cloud-init 用户设置主要在首次启动执行,修改声明不能替代后续用户/密钥轮换流程。 当前 state 位于 `terraform/terraform.tfstate`(Git 忽略),是本地 backend; @@ -144,5 +146,4 @@ cloud-init 用户设置主要在首次启动执行,修改声明不能替代后 `mkdir ... file exists` 竞争;按上面的串行 apply 执行。失败后先重新 plan, 保留已成功创建的实例,不清理或销毁其资源。 -`ansible/containers.yml` 通过本机 Incus exec 幂等固定静态地址,并安装、启用 SSH server; -走 exec 而非 SSH,切换地址不会切断 Ansible 自身连接。 +`ansible/containers.yml` 通过本机 Incus exec 幂等安装并启用 SSH server。 diff --git a/infrastructure/incus/ansible/containers.yml b/infrastructure/incus/ansible/containers.yml index ab38033..dc55d01 100644 --- a/infrastructure/incus/ansible/containers.yml +++ b/infrastructure/incus/ansible/containers.yml @@ -1,17 +1,8 @@ --- -- name: 准备 Ayatori 基础容器的网络与 SSH 入口 +- name: 准备 Ayatori 基础容器的 SSH 入口 hosts: incus_hosts gather_facts: false - # 静态地址与 AD DNS 记录同源,避免两处地址漂移。 - vars_files: - - ../../dns/records.yml tasks: - - name: 按容器协调静态地址 - ansible.builtin.include_tasks: tasks/container-network.yml - loop: [ayatori-dev, ayatori-prod] - loop_control: - loop_var: incus_container - - name: 按容器协调 SSH ansible.builtin.include_tasks: tasks/container-ssh.yml loop: [ayatori-dev, ayatori-prod] diff --git a/infrastructure/incus/ansible/tasks/container-network.yml b/infrastructure/incus/ansible/tasks/container-network.yml deleted file mode 100644 index 1287808..0000000 --- a/infrastructure/incus/ansible/tasks/container-network.yml +++ /dev/null @@ -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" diff --git a/infrastructure/incus/ansible/tasks/container-ssh.yml b/infrastructure/incus/ansible/tasks/container-ssh.yml index 32a0e51..c1ebb64 100644 --- a/infrastructure/incus/ansible/tasks/container-ssh.yml +++ b/infrastructure/incus/ansible/tasks/container-ssh.yml @@ -55,9 +55,11 @@ register: container_sshd_write changed_when: true +# 24.04 新装时只有 ssh.socket 运行,/run/sshd 要等 ssh.service 的 RuntimeDirectory 才创建, +# 缺它时 sshd -t 直接失败;按同样的 0755 预建(/run 为 tmpfs,不留持久状态)。 - name: 校验 SSH 配置 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 - name: 更新运行中的 SSH 配置 diff --git a/infrastructure/incus/terraform/main.tf b/infrastructure/incus/terraform/main.tf index d21af58..27862db 100644 --- a/infrastructure/incus/terraform/main.tf +++ b/infrastructure/incus/terraform/main.tf @@ -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" { for_each = toset(["dev", "prod"]) name = "ayatori-${each.key}" description = "Ayatori ${each.key} 基础容器;应用由独立部署流程管理" # 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" profiles = [] running = true @@ -50,6 +62,19 @@ resource "incus_instance" "ayatori" { "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