整理旧工作区并恢复未纳管 IaC #84
@@ -49,6 +49,7 @@ authelia/secret.yaml
|
||||
**/secret.yaml
|
||||
**/credentials.yml
|
||||
**/terraform.tfvars
|
||||
**/credentials.auto.tfvars
|
||||
**/tailscale/helm.sh
|
||||
**/cloudflared/backup/
|
||||
**/cloudflared/secret.yaml
|
||||
@@ -118,3 +119,4 @@ apps/netboot/config/log/
|
||||
# Blocky's per-day query logs. Bind-mounted into the container, one file per
|
||||
# day, and every DNS query the LAN makes ends up in them.
|
||||
apps/blocky/logs/
|
||||
.venv/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Blocky — LAN DNS. STAGED, NOT DEPLOYED. See README.md.
|
||||
# Blocky — 已部署的 LAN 主 DNS。见 README.md。
|
||||
#
|
||||
# WHY compose on the laptop and NOT a k3s Deployment, given everything else here
|
||||
# is Kubernetes:
|
||||
@@ -52,3 +52,10 @@ services:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# 避免与 DN42 的 172.20.0.0/14 重叠。
|
||||
networks:
|
||||
default:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.28.0.0/24
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# Blocky — LAN resolver, ad-blocker and split-horizon DNS.
|
||||
#
|
||||
# DEPLOYED 2026-07-28 and verified, but NOT yet the LAN resolver — clients still
|
||||
# get the DC/router pair from DHCP. Making it the resolver needs a DHCP change on
|
||||
# the NEC IX; see README.md. Until then only clients that query 192.168.10.127
|
||||
# explicitly are affected, so this is safely reversible.
|
||||
# LAN 主 DNS 为 192.168.10.127,NEC IX 192.168.10.1 为备用。
|
||||
# DN42 条件转发经 VyOS,参见 README.md。
|
||||
|
||||
ports:
|
||||
# These are the CONTAINER's listen addresses, so they must be unqualified —
|
||||
@@ -35,6 +33,13 @@ conditional:
|
||||
# Queries for the AD zone go straight to the DC, which is authoritative. This
|
||||
# replaces the "DC first, router second" resolver ordering that clients use today.
|
||||
mapping:
|
||||
# DN42 由 VyOS 使用注册地址转发,避免 LAN 私网源地址缺少回程。
|
||||
dn42: 192.168.10.2
|
||||
20.172.in-addr.arpa: 192.168.10.2
|
||||
21.172.in-addr.arpa: 192.168.10.2
|
||||
22.172.in-addr.arpa: 192.168.10.2
|
||||
23.172.in-addr.arpa: 192.168.10.2
|
||||
d.f.ip6.arpa: 192.168.10.2
|
||||
ad.ddupan.top: 192.168.10.5
|
||||
# Reverse lookups for LAN hosts — the DC holds the reverse zone.
|
||||
10.168.192.in-addr.arpa: 192.168.10.5
|
||||
|
||||
@@ -52,11 +52,16 @@ prefix_roles:
|
||||
# range. It makes the collision VISIBLE — the range shows 100% utilised and the
|
||||
# address never appears as a suggestion — where plain YAML shows nothing at all.
|
||||
ip_ranges:
|
||||
- start: 192.168.10.10/24
|
||||
- start: 192.168.10.128/24
|
||||
end: 192.168.10.250/24
|
||||
status: active
|
||||
mark_utilized: true
|
||||
description: "NEC IX DHCP pool — do NOT statically allocate inside this."
|
||||
description: "NEC IX DHCP pool, updated 2026-09-14. Do NOT statically allocate inside this."
|
||||
- start: 192.168.10.251/24
|
||||
end: 192.168.10.254/24
|
||||
status: reserved
|
||||
mark_utilized: true
|
||||
description: "用户确认预留,尚未分配;不可按扫描无响应视为空闲。"
|
||||
|
||||
vlan_group:
|
||||
name: lab
|
||||
@@ -144,8 +149,8 @@ devices:
|
||||
role: hypervisor
|
||||
type: 10vgcto1ww
|
||||
serial: PC1AGX1Q
|
||||
description: "Proxmox VE 9.2. LINSTOR satellite. The node that randomly froze."
|
||||
comments: "AMD Ryzen 5 PRO 2400GE w/ Vega, 8 threads, 7 GiB RAM. BIOS M1XKT45A. Raven Ridge idle bug fixed in BIOS: Power Supply Idle Control = Typical Current Idle."
|
||||
description: "Proxmox VE 9.2. LINSTOR satellite."
|
||||
comments: "AMD Ryzen 5 PRO 2400GE w/ Vega, 8 threads, 7 GiB RAM. BIOS M1XKT45A."
|
||||
interfaces:
|
||||
- { name: vmbr0, type: bridge, ip: 192.168.10.7/24, primary: true, mtu: 9000, dns_name: pve2.ad.ddupan.top }
|
||||
|
||||
@@ -154,7 +159,7 @@ devices:
|
||||
type: 10vgcto1ww
|
||||
serial: PC1AGX1P
|
||||
description: "Proxmox VE 9.2. LINSTOR satellite."
|
||||
comments: "AMD Ryzen 5 PRO 2400GE w/ Vega, 8 threads, 7 GiB RAM. BIOS M1XKT55A. Same silicon as pve2, so susceptible to the same idle bug in principle."
|
||||
comments: "AMD Ryzen 5 PRO 2400GE w/ Vega, 8 threads, 7 GiB RAM. BIOS M1XKT55A."
|
||||
interfaces:
|
||||
- { name: vmbr0, type: bridge, ip: 192.168.10.9/24, primary: true, mtu: 9000, dns_name: pve3.ad.ddupan.top }
|
||||
|
||||
@@ -181,9 +186,8 @@ devices:
|
||||
# Wi-Fi. Runs as an AP/bridge, not a router — the NEC IX is the gateway, so this box's
|
||||
# routing, NAT and DHCP are not in play. Wireless clients land directly on the flat LAN.
|
||||
#
|
||||
# ⚠ Its address .10 is the FIRST ADDRESS OF THE DHCP POOL above. Either it holds a lease
|
||||
# (so the address can move) or it is a static that overlaps the pool. NetBox surfaces
|
||||
# the overlap; the underlying config still needs a decision. See ../README.md.
|
||||
# 2026-09-14: NEC IX 为此 MAC 固定分配 .10;动态池已迁到 .128–.250。
|
||||
# 操作与回滚记录:infrastructure/samba-ad/router-dhcp-nec-ix.md。
|
||||
#
|
||||
# Identified by MAC OUI d4:2c:46 = BUFFALO.INC plus the model string on its login page.
|
||||
- name: ap-buffalo
|
||||
|
||||
@@ -12,3 +12,10 @@ services:
|
||||
- "38008:38008"
|
||||
volumes:
|
||||
- "/mnt/pool/games/ps3:/games:rw"
|
||||
|
||||
# 避免与 DN42 的 172.20.0.0/14 重叠。
|
||||
networks:
|
||||
default:
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.28.1.0/24
|
||||
|
||||
@@ -10,6 +10,9 @@ homelab_dns:
|
||||
- { zone: ad.ddupan.top, name: pve1, type: A, values: [192.168.10.4] }
|
||||
- { zone: ad.ddupan.top, name: pve2, type: A, values: [192.168.10.7] }
|
||||
- { zone: ad.ddupan.top, name: pve3, type: A, values: [192.168.10.9] }
|
||||
- { zone: ad.ddupan.top, name: sandbox1, type: A, values: [10.60.0.11] }
|
||||
- { zone: ad.ddupan.top, name: sandbox2, type: A, values: [10.60.0.12] }
|
||||
- { zone: ad.ddupan.top, name: sandbox-k8s, type: A, values: [10.60.0.13] }
|
||||
- { zone: ad.ddupan.top, name: retrolab, type: A, values: [10.60.0.10] }
|
||||
- { zone: ad.ddupan.top, name: grafana, type: A, values: [192.168.10.127] }
|
||||
- { zone: ad.ddupan.top, name: netbox, type: A, values: [192.168.10.127] }
|
||||
@@ -53,3 +56,18 @@ homelab_dns:
|
||||
values: [ff392451-b0b1-45bb-964e-6d9372c3a9e3.cfargotunnel.com]
|
||||
proxied: true
|
||||
terraform: { managed: false }
|
||||
# OCI 主机直接解析公网 IP,SSH 不经过 Cloudflare 代理。
|
||||
- name: oci-arm.ddupan.top
|
||||
type: A
|
||||
values:
|
||||
- 129.225.138.179
|
||||
proxied: false
|
||||
ttl: 300
|
||||
terraform: { managed: false }
|
||||
- name: oci-amd.ddupan.top
|
||||
type: A
|
||||
values:
|
||||
- 129.225.176.134
|
||||
proxied: false
|
||||
ttl: 300
|
||||
terraform: { managed: false }
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Docker 地址池与 DN42
|
||||
|
||||
DN42 使用 `172.20.0.0/14`。laptop 的 Docker 默认地址池改为 `172.28.0.0/16`,
|
||||
按 `/24` 分配新 bridge,避免本地直连路由与 DN42 前缀重叠。
|
||||
`ansible/site.yml` 合并现有 daemon.json,保留 NVIDIA runtime;先热加载 live-restore,
|
||||
再重启 daemon 使默认地址池生效,避免已有容器随 daemon 停止。
|
||||
|
||||
```bash
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-docker ansible-playbook -i localhost, infrastructure/docker/ansible/site.yml --check --diff
|
||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-docker ansible-playbook -i localhost, infrastructure/docker/ansible/site.yml
|
||||
```
|
||||
|
||||
已有网络不会自动换地址。本次单独迁移结果:
|
||||
|
||||
| 网络 | 原地址 | 当前地址/状态 |
|
||||
|---|---|---|
|
||||
| blocky_default | 172.20.0.0/16 | 172.28.0.0/24,Compose 明确声明 |
|
||||
| ps3netsrv_default | 172.21.0.0/16 | 172.28.1.0/24,Compose 明确声明 |
|
||||
| research-auto_default | 172.22.0.0/16 | 172.28.2.0/24,仓库外 research-auto Compose 明确声明 |
|
||||
| netboot_default | 172.23.0.0/16 | 删除无端点的遗留网络;netboot 两个容器均使用 host 网络 |
|
||||
|
||||
Blocky 健康检查与 DNS 查询通过;ps3netsrv 运行,游戏数据挂载保留。
|
||||
research-auto 的 postgres 容器仅 create、未启动,原命名卷 `research-auto_postgres_data` 保留。
|
||||
|
||||
旧运行容器曾引用仓库重组前的 `/home/panxiao81/services/<app>` 挂载路径;
|
||||
本次 Blocky 已用 `apps/blocky` 路径重建,netboot 等未重建的容器仍需在重建时使用当前 Compose。
|
||||
不要在未检查 bind mount 路径的情况下关闭 live-restore 并重启所有容器。
|
||||
@@ -0,0 +1,2 @@
|
||||
[defaults]
|
||||
local_tmp = /tmp/ansible-docker
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
- name: 为 DN42 排除 Docker 地址池重叠
|
||||
hosts: localhost
|
||||
connection: local
|
||||
become: true
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
docker_address_pools:
|
||||
- base: 172.28.0.0/16
|
||||
size: 24
|
||||
tasks:
|
||||
- name: 读取现有 Docker 配置并保留 runtimes 等设置
|
||||
ansible.builtin.slurp:
|
||||
src: /etc/docker/daemon.json
|
||||
register: docker_config
|
||||
no_log: true
|
||||
|
||||
# 先让旧 daemon 知道 live-restore,随后重启才能保留运行容器。
|
||||
- name: 启用 live-restore
|
||||
ansible.builtin.copy:
|
||||
content: "{{ (docker_config.content | b64decode | from_json | combine({'live-restore': true})) | to_nice_json }}\n"
|
||||
dest: /etc/docker/daemon.json
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
backup: true
|
||||
validate: /usr/bin/dockerd --validate --config-file %s
|
||||
register: live_restore_config
|
||||
|
||||
- name: 热重载 live-restore
|
||||
ansible.builtin.systemd_service:
|
||||
name: docker
|
||||
state: reloaded
|
||||
when: live_restore_config.changed and not ansible_check_mode
|
||||
|
||||
- name: 确认运行中的 daemon 已启用 live-restore
|
||||
ansible.builtin.command: docker info --format '{{ '{{' }}.LiveRestoreEnabled{{ '}}' }}'
|
||||
register: live_restore_status
|
||||
changed_when: false
|
||||
retries: 5
|
||||
delay: 2
|
||||
until: live_restore_status.stdout == 'true'
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 配置 DN42 范围之外的默认地址池
|
||||
ansible.builtin.copy:
|
||||
content: "{{ (docker_config.content | b64decode | from_json | combine({'live-restore': true, 'default-address-pools': docker_address_pools})) | to_nice_json }}\n"
|
||||
dest: /etc/docker/daemon.json
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
backup: true
|
||||
validate: /usr/bin/dockerd --validate --config-file %s
|
||||
register: docker_pool_config
|
||||
|
||||
- name: 保留运行容器并重启 daemon 使地址池生效
|
||||
ansible.builtin.systemd_service:
|
||||
name: docker
|
||||
state: restarted
|
||||
when: docker_pool_config.changed and not ansible_check_mode
|
||||
+110
-10
@@ -1,13 +1,113 @@
|
||||
# OCI infrastructure recovery
|
||||
# OCI 云上基础设施
|
||||
|
||||
The original Terraform source is currently unavailable. The likely authoritative
|
||||
state remains in OCI Object Storage. Reconstruct configuration here only after
|
||||
taking an encrypted independent state backup.
|
||||
`terraform/` 是独立 Terraform 根模块,从 OCI Object Storage 中的现有 state 恢复。
|
||||
Terraform 管理云 API 资源;实例内的软件、Kubernetes 和操作系统配置不在该 state 中。
|
||||
|
||||
Safety requirements:
|
||||
## 资源与来源
|
||||
|
||||
- preserve the existing state lineage and serial;
|
||||
- reproduce the current VM and public-network design first;
|
||||
- reach a zero-change plan before any apply;
|
||||
- protect the instance and boot volume from destruction;
|
||||
- treat migration to a private subnet as a separate reviewed change.
|
||||
- Region:`ap-osaka-1`,compartment 为 tenancy 根。
|
||||
- State:namespace `axckv9ylwqxr`,bucket `oci-k8s-free-tier-tfstate`,key `terraform.tfstate`。
|
||||
- 恢复源:2026-08-15 12:26:24 UTC 对象,25,261 字节,serial `249`,
|
||||
lineage `c945c6c4-ee01-d7b7-23f5-37207ea60609`,Terraform `1.15.8`。
|
||||
- 7 个受管资源保留原地址:`oci_core_instance.vm`、`oci_core_vcn.vcn`、
|
||||
`oci_core_subnet.public`、`oci_core_internet_gateway.igw`、`oci_core_route_table.public`、
|
||||
`oci_core_security_list.public`、`oci_limits_quota.free_tier_quota`。
|
||||
- 2 个数据源:`oci_core_images.ubuntu`、`oci_identity_availability_domains.ads`;保留 4 个原输出。
|
||||
- VM:`homelab-vm`,A1 Flex,2 OCPU / 12 GB RAM / 100 GB 启动盘,
|
||||
私网 `10.0.0.124`,恢复时公网 `129.225.138.179`。
|
||||
- VCN `10.0.0.0/16`,公共子网 `10.0.0.0/24`,默认路由经 Internet Gateway;
|
||||
入站保留 TCP 22、UDP 41641、ICMP type 3/code 4,出站全部允许。
|
||||
- 配额语句保留 A1 4 核 / 24 GB、10 个卷、200 GB 总存储限制;这些语句不是费用保证。
|
||||
- Bucket 自身不在 state 内,不由此根模块管理。
|
||||
|
||||
## 恢复设计
|
||||
|
||||
原变量、模块意图、provider 精确版本和生命周期规则无法从 state 完整恢复。
|
||||
本次选择并锁定 `oracle/oci 9.1.0`,提交 lockfile;这不是声称找回了原 provider 版本。
|
||||
资源间的 VCN、路由表、安全列表、子网和 DHCP 引用已重建。
|
||||
启动镜像固定为现有 image OCID,避免数据源选中更新镜像导致 VM 替换。
|
||||
新增 `prevent_destroy` 保护现有 VM;没有用 `ignore_changes` 掩盖配置差异。
|
||||
|
||||
Provider 使用本机 `~/.oci/config` 的 `DEFAULT` profile,可用变量覆盖 profile 和 region。
|
||||
metadata 经敏感变量传入,只保存在忽略的 `terraform.tfvars.json`,不进入版本库。
|
||||
State、plan、metadata 变量与 `.terraform/` 都不得提交;plan JSON 同样可能含敏感数据。
|
||||
保留原目录的恢复要求:保留 lineage/serial,先复现当前 VM 和公共网络设计,
|
||||
实际基础设施变更前达到严格零变更,并保护实例及启动盘;迁移私有子网须单独评审。
|
||||
已用既有 GPG 加密子密钥 `5A6A04D1B216C64E` 创建独立加密源备份
|
||||
`.recovery/source.tfstate.gpg`;本地原始副本的 lineage/serial 保持不变。
|
||||
|
||||
## 日常维护
|
||||
|
||||
旧 Terraform/CI 已由维护者确认停用,当前仓库已接管原 OCI Object Storage state。
|
||||
`versions.tf` 使用 OCI backend,直接连接原对象,未迁移本地恢复副本。
|
||||
认证沿用本机 OCI CLI 的 `DEFAULT` profile;不要在配置中写密钥。
|
||||
|
||||
```bash
|
||||
cd infrastructure/oci/terraform
|
||||
terraform init
|
||||
terraform validate
|
||||
terraform plan -input=false -out=change.tfplan
|
||||
# 核对计划后执行:
|
||||
terraform apply change.tfplan
|
||||
```
|
||||
|
||||
新 checkout 需从受限 state 副本提取 metadata 至被忽略的 `terraform.tfvars.json`。
|
||||
`prepare-local-state.py` 可执行这一步,同时保留 `.recovery/terraform.tfstate` 备份;
|
||||
当前 backend 使用远端对象,`.recovery/` 中的副本不再参与日常 plan/apply。
|
||||
下载前设置 `umask 077`,并使用 GPG 加密源备份;不要把本地副本上传覆盖远端。
|
||||
|
||||
## 恢复验证记录
|
||||
|
||||
Terraform 1.15.8、OCI provider 9.1.0 验证通过。
|
||||
恢复计划唯一更新是 VM metadata 的敏感标记,plan JSON 中 before/after 值相同。
|
||||
经授权 apply 后,本地完整刷新 plan 达到 `No changes`。
|
||||
旧 state 在 provider 刷新后补充 VM shape/VNIC、subnet IPv4 CIDR、route type 字段,
|
||||
这些读回差异没有产生基础设施修改计划。
|
||||
接管时重新连接原远端对象,因此新增实例计划也包含原 VM 的同一敏感标记归一化。
|
||||
|
||||
## AMD 实例
|
||||
|
||||
`oci_core_instance.amd` 配置为 `homelab-amd`,`VM.Standard.E2.1.Micro`,1 GB RAM,
|
||||
Ubuntu 24.04 x86_64,50 GB / 10 VPU 启动盘。复用现有公共子网和 SSH 公钥,
|
||||
没有复制 A1 实例的其他初始化内容,也不部署 Kubernetes。
|
||||
镜像固定为 `Canonical-Ubuntu-24.04-2026.08.25-0`,实例具有 `prevent_destroy` 保护。
|
||||
|
||||
创建前 API 确认大阪为 home region、机型计费类型为 `ALWAYS_FREE`,AMD 配额剩余 2 台。
|
||||
存储盘点只有 A1 的 100 GB 启动盘;已核对两个启动盘合计 150 GB,200 GB 免费额度内剩余 50 GB。
|
||||
免费额度跨启动盘和块存储共享;后续新增资源仍需重新核对实际占用。
|
||||
|
||||
实例已创建并确认 `RUNNING`,私网 `10.0.0.158`,公网 `129.225.176.134`。
|
||||
登录命令:`ssh [email protected]`(本次未验证 SSH 登录)。
|
||||
远端 state 已保存;创建后完整刷新 plan 为 `No changes`,退出码 0。
|
||||
|
||||
参考:[Always Free 资源](https://docs.oracle.com/en-us/iaas/Content/FreeTier/freetier_topic-Always_Free_Resources.htm)、
|
||||
[OCI provider 认证](https://docs.oracle.com/en-us/iaas/Content/dev/terraform/configuring.htm)、
|
||||
[OCI backend 配置](https://developer.hashicorp.com/terraform/language/backend/oci)。
|
||||
|
||||
## DNS 登录入口
|
||||
|
||||
- ARM:`ssh [email protected]`
|
||||
- AMD:`ssh [email protected]`
|
||||
|
||||
公网 A 记录声明位于 `../dns/records.yml`,在 Cloudflare 上关闭代理,TTL 300 秒。
|
||||
当前通过 DNS API 管理,未加入 OCI Terraform state;公网 IP 变化时需同步记录。
|
||||
|
||||
## WireGuard/BGP 与 DN42
|
||||
|
||||
家中端点已从 laptop 迁移到 VyOS `192.168.10.2`,AMD 与 VyOS 同属 AS4242421811,
|
||||
通过独立 WireGuard 接口建立双栈 iBGP;laptop 保留原有 NEC BGP 和 OSPF,按路由经 VyOS 转发。
|
||||
Ansible 配置与运行方法见 [ansible/README.md](ansible/README.md)。
|
||||
|
||||
注册前缀 `172.21.111.160/27`、`fdd0:98df:15b0::/48` 已在内部路由中准备:
|
||||
VyOS `.161` / `::1`,AMD `.162` / `::2`,使用 loopback /32、/128。
|
||||
首个外部 DN42 peer 已接入 RoutedBits Osaka(AS4242420207),AMD 使用独立 `wg-dn42-1`
|
||||
和单 IPv6 MP-BGP 会话承载双栈;详见 [Ansible runbook](ansible/README.md)。
|
||||
外部明细留在 AMD,`172.20.0.0/14`、`fd00::/8` 汇总经 iBGP 下发 VyOS;IPv4 /14 再经 OSPF 下发 LAN。
|
||||
LAN 的 DN42 IPv6 /64 地址由 VyOS SLAAC 下发,fd00::/8 通过 RA RIO 分发,不通告 IPv6 默认路由。内部家中/OCI 业务路由不得向外部 DN42 邻居通告。
|
||||
|
||||
Terraform 管理 AMD NSG、VNIC 转发与 VCN 回程;Ansible 管理路由器/主机及 ARM 的 Tailscale
|
||||
回程例外。Docker 与 DN42 的 /14 地址重叠已迁出,见 [Docker runbook](../docker/README.md)。
|
||||
|
||||
VyOS 的 `wg42` 主 IPv4 已改为注册的 `172.21.111.161/32`,内部 BGP 改为
|
||||
单 link-local IPv6 会话承载双栈。三个 LAN 私网到 DN42 /14 由 VyOS 定向 masquerade,
|
||||
排除本 AS /27;OCI 业务保持原源地址。IPv6 不做 NAT,由 `ansible/dn42-ra.yml` 管理三个 LAN 的 SLAAC 与专用路由通告。
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
# VyOS ↔ OCI:WireGuard 与 DN42 内部 BGP
|
||||
|
||||
站点端已从 laptop 迁移到 VyOS `192.168.10.2`。VyOS 位于双层 NAT 后,主动连接
|
||||
`oci-amd.ddupan.top:51820`,keepalive 25 秒;LAN 访问 DN42 时在 VyOS 做定向 masquerade。
|
||||
主机配置由 Ansible 管理;OCI NSG、VNIC 转发和 VCN 回程路由由旁边的 Terraform 管理。
|
||||
|
||||
| 节点 | 接口 | 传输 IPv4 | 传输 IPv6 | ASN |
|
||||
|---|---|---|---|---|
|
||||
| VyOS | wg42 | 172.21.111.161/32 | fe80::1811:1/64(BGP) | 4242421811 |
|
||||
| AMD | wg-oci | 10.255.254.1/30(历史传输地址) | fe80::1811:2/64(BGP) | 4242421811 |
|
||||
|
||||
AMD 显式设置 `fe80::1811:2/64`;VyOS BGP 使用固定 `fe80::1811:1/64`,双方 AllowedIPs 包含
|
||||
`fe80::/64`。FRR 在 ULA 建邻时仍需接口具有 link-local 下一跳地址。
|
||||
|
||||
MTU 1380,Linux 使用 `Table = off`,WireGuard AllowedIPs 用于选 peer 与源地址校验,
|
||||
站点路由由 iBGP 安装。内外部均使用单条 IPv6 link-local 会话承载双 AFI,并启用 extended-nexthop。
|
||||
内部旧 IPv4/ULA BGP 邻居已退役;ULA 传输地址保留用于路由下一跳,不再用于建邻。
|
||||
VyOS 10.2.4 通过 `OCI-MP-IN` 的 `ipv6-next-hop prefer-global` 优先采用通告中的 ULA 下一跳,
|
||||
避免 link-local NHT 显示 overlay index unresolved、BGP 已建邻但路由未安装。
|
||||
|
||||
## 地址与通告边界
|
||||
|
||||
已注册 `172.21.111.160/27`、`fdd0:98df:15b0::/48`:
|
||||
|
||||
| 节点 | 路由器 IPv4 | 路由器 IPv6 |
|
||||
|---|---|---|
|
||||
| VyOS(IPv4 在 wg42,IPv6 在 lo) | 172.21.111.161/32 | fdd0:98df:15b0::1/128 |
|
||||
| AMD loopback | 172.21.111.162/32 | fdd0:98df:15b0::2/128 |
|
||||
|
||||
VyOS 为注册的 /27、/48 建立 distance 254 的 blackhole 聚合路由,保证精确前缀存在,
|
||||
避免未分配地址落入默认路由。已分配的本地地址及 AMD 的 /32、/128 优先于聚合。
|
||||
|
||||
内部通告严格过滤:
|
||||
|
||||
- VyOS → AMD:`192.168.10.0/24`、`10.60.0.0/24`、`10.61.0.0/24` 和注册 /27、/48。
|
||||
- AMD → VyOS:`10.0.0.0/24`、AMD 的注册 /32、/128,以及 DN42 汇总 `172.20.0.0/14`、`fd00::/8`。
|
||||
- VyOS 向 LAN OSPF 只重分发 OCI /24、DN42 /14 和注册 IPv4 /27,使用精确 route-map、E1 metric。
|
||||
既有直连 LAN/SDN 的 OSPF area 声明保持不变,不使用泛化的 redistribute connected。
|
||||
|
||||
**首个外部 peer 为 RoutedBits Osaka(AS4242420207),由 `dn42.yml` 单独管理。** 外部邻居使用独立的 import/export
|
||||
过滤,只对外通告注册 /27、/48;禁止把上述内部业务前缀的过滤器复用到外部邻居。
|
||||
入口使用 DN42 指南的保留地址、互联网络与前缀长度规则(IPv6 /44–/64),
|
||||
并优先拒收本 AS、LAN、OCI 前缀;尚未配置注册表 ROA 校验。外部明细留在 AMD,汇总通过 iBGP 下发 VyOS;IPv4 汇总再经 OSPF 下发 LAN。
|
||||
|
||||
每个外部 WireGuard peer 使用独立接口,BGP 可复用本机 loopback 地址。
|
||||
单接口多个 WireGuard peer 要求可明确区分的 AllowedIPs;多家 peer 都提供同一 DN42 路由范围时,
|
||||
使用独立接口让 BGP 决定出口,避免相同 AllowedIPs 抢占 peer。
|
||||
|
||||
## 执行
|
||||
|
||||
```bash
|
||||
cd infrastructure/oci/ansible
|
||||
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
|
||||
ansible-playbook site.yml --check --diff
|
||||
ansible-playbook site.yml
|
||||
ansible-playbook site.yml # 复跑应 changed=0
|
||||
```
|
||||
|
||||
`vyos.yml` 先在路由器本机生成密钥并保存,再交换公钥并增量应用 VyOS set 命令。
|
||||
VyOS native config 含私钥,因此相关模块使用 `no_log`,不把配置备份到 Git 或打印出来。
|
||||
AMD 私钥在 `/etc/wireguard/wg-oci.key`(0600),由本机 PostUp 加载,不返回控制机。
|
||||
首次 check mode 无法生成私钥,因此会跳过依赖不存在公钥的 Linux 配置渲染。
|
||||
|
||||
`retire-laptop.yml` 是迁移收尾:先检查 VyOS 邻居,再停止 laptop 的 wg-oci,
|
||||
删除试验邻居、三个新增 network 语句及专用防火墙链,保留 laptop 原 AS65001 ↔ NEC AS65000
|
||||
会话、原有 VPN 路由和 OSPF。旧私钥保留在 laptop 受限文件中,隧道和防火墙单元已禁用。
|
||||
|
||||
Linux 端 FRR 通过 `vtysh -f` 应用独立配置片段,另行 `write memory` 持久化;
|
||||
文件变更时重建受管邻居,未变更的重跑不重置会话。移除前缀时还须显式删除已退出管理的
|
||||
`network` 语句,不能只追加 set 命令,也不能清空整份 BGP 配置。
|
||||
|
||||
## OCI 回程与 Tailscale
|
||||
|
||||
VCN 虚拟路由器不参与主机间 iBGP;三个家中业务前缀的静态回程指向 AMD Private IP OCID。
|
||||
首次启用时必须先将 VNIC `skip_source_dest_check` 设为 true,OCI 才接受该私有 IP 为路由目标。
|
||||
当前 Terraform 的 route table → subnet → instance 依赖使首次引导需要先设置该标志。
|
||||
重建 AMD 后须重新查询并更新 `amd_router_private_ip_ocid`。
|
||||
|
||||
ARM 接受 Tailscale 家中子网路由,table 52 原本抢走回程。
|
||||
Ansible 在 ARM 设置 priority 5101–5103、仅匹配这三个目的前缀的 `lookup main` 规则,
|
||||
让这些流量使用 OCI 网关→AMD;其他 Tailscale 地址保持原路径。
|
||||
AMD 的 Zebra route-map 仅为本机发起的 BGP 业务流量选择 `10.0.0.158` 源地址,不改写转发源地址。
|
||||
|
||||
## Docker 地址冲突
|
||||
|
||||
DN42 使用 `172.20.0.0/14`。原 laptop Docker 的四个 /16 已清除,默认池改为 `172.28.0.0/16`,
|
||||
详见 [Docker runbook](../../docker/README.md)。最长前缀匹配决定路由,但不能解决两套网络实际
|
||||
地址重叠;不能只检查本 AS 注册的 /27 而忽略其他 DN42 注册前缀。
|
||||
|
||||
## 验证与停用
|
||||
|
||||
```bash
|
||||
# VyOS operational mode
|
||||
show interfaces wireguard wg42 summary
|
||||
show bgp summary
|
||||
show ip route 172.21.111.162/32
|
||||
show ipv6 route fdd0:98df:15b0::2/128
|
||||
# AMD
|
||||
sudo wg show wg-oci latest-handshakes
|
||||
sudo vtysh -c 'show bgp summary'
|
||||
ping 192.168.10.4
|
||||
ping -I 172.21.111.162 172.21.111.161
|
||||
ping -6 -I fdd0:98df:15b0::2 fdd0:98df:15b0::1
|
||||
```
|
||||
|
||||
业务网段测试应使用业务源地址;DN42 loopback 测试使用注册地址。
|
||||
LAN 已通过 `dn42-ra.yml` 部署 DN42 SLAAC 与专用 RIO,不发布 IPv6 默认路由;OCI VCN 保持原配置。
|
||||
停用或回滚须同时处理 BGP、WireGuard、OSPF 重分发、VCN 回程和 ARM 例外规则,
|
||||
不能仅停止隧道后留下静态回程指向不可达节点。
|
||||
|
||||
参考:[WireGuard](https://www.wireguard.com/quickstart/)、
|
||||
[VyOS WireGuard](https://docs.vyos.io/en/1.5/configuration/interfaces/wireguard.html)、
|
||||
[DN42 入门](https://www.dn42.dev/howto/Getting-Started)。
|
||||
|
||||
## 迁移验收
|
||||
|
||||
2026-09-14:
|
||||
|
||||
- VyOS IPv4 iBGP 收到 OCI /24、AMD /32,通告三个家中前缀与注册 /27;IPv6 会话双方各收到一条前缀。
|
||||
- 两端 DN42 loopback IPv4、IPv6 互通;IPv6 本次采样约 3.1 ms。
|
||||
- laptop 的 `10.0.0.0/24` 与 `172.21.111.160/27` 经 OSPF 指向 `192.168.10.2`,不再使用旧隧道。
|
||||
- PVE1 可访问 ARM 私网和 AMD 的 DN42 IPv4;AMD 可访问 PVE1、`10.60.0.1`、`10.61.0.1`。
|
||||
- laptop 旧 WireGuard 和专用防火墙 service 已停止/禁用,原 NEC BGP 会话保留。
|
||||
|
||||
LAN 网关可能返回 ICMP Redirect,提示客户端将 VyOS 作为同网段下一跳;这是既有 LAN
|
||||
拓扑的正常结果,没有为此修改客户端或网关的 redirect 策略。
|
||||
|
||||
## AMD 外部 DN42 首个接口
|
||||
|
||||
2026-09-15 已通过 `ansible-playbook dn42.yml` 准备独立监听:
|
||||
|
||||
- 接口:`wg-dn42-1`,UDP endpoint:`oci-amd.ddupan.top:51821`(`129.225.176.134:51821`)。
|
||||
- 公钥:`YQ/X3QmNocnr0u4aUm5qhcV328StSNtg+ULd9AKCdhQ=`。
|
||||
- 私钥仅保存在 AMD `/etc/wireguard/wg-dn42-1.key`,由 root 受限目录保护,不返回控制机。
|
||||
- `wg-quick@wg-dn42-1` 开机启动;主机入站规则随接口启停,OCI NSG 规则由 Terraform 管理。
|
||||
- Link-local:`fe80::1811:2/64`,本机作用域为 `%wg-dn42-1`。首个 peer 使用单条 IPv6 BGP 会话承载双栈(MP-BGP + RFC 8950 extended next hop)。
|
||||
- 对端:AS4242420207,`router.osa1.routedbits.com:51811`,link-local `fe80::207`。
|
||||
- 对端公钥:`96PwUEGi/ijdmKO+IjuZ+J6DeykuTRukZD5atajfeH4=`。
|
||||
- WireGuard 使用 `Table = off`;AllowedIPs 为 `fe80::/64, 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8`,keepalive 25 秒。
|
||||
- 双 AFI 共用 IPv6 TCP 会话并启用 extended-nexthop;每 AFI maximum-prefix 10000,出口只允许注册 /27 和 /48。
|
||||
- 本机发起 DN42 流量使用注册地址作为 preferred source,不做 NAT。
|
||||
- Ubuntu 自带 FRR 8.4.4 不满足 [DN42 FRR 指南](https://dn42.dev/howto/frr) 的 link-local 版本要求。
|
||||
`tasks/frr-dn42.yml` 使用官方 frr-10.7 软件源,固定 10.7.1;升级前配置仅在 AMD `/var/backups/frr-before-dn42` 备份。
|
||||
|
||||
原 `wg-oci:51820` 继续承载与 VyOS 的内部互联。
|
||||
|
||||
2026-09-15 接入验收:WireGuard 握手与 `fe80::207%wg-dn42-1` 连通;FRR 10.7.1
|
||||
会话 Established,双方已协商 IPv4/IPv6 AFI 与 extended nexthop。采样接收 IPv4 1174、IPv6 1273 条,
|
||||
对外仅通告 `172.21.111.160/27`、`fdd0:98df:15b0::/48`。AMD 无需显式指定源地址,
|
||||
即可访问对端 `172.20.19.78`、`fdb1:e72a:343d::f`,各 3/3 回复,约 1 ms;
|
||||
IPv4 内核路由下一跳为 `via inet6 fe80::207 dev wg-dn42-1`。内部 VyOS 双栈 BGP 会话已恢复。
|
||||
|
||||
## 向 LAN 分发 DN42 汇总
|
||||
|
||||
`site.yml` 在 AMD 生成 `172.20.0.0/14`、`fd00::/8` 的 BGP aggregate,并通过内部精确
|
||||
prefix-list 通告 VyOS。不使用全局 `summary-only`,避免抑制对外通告的注册 /27、/48;
|
||||
对外出口过滤仍只允许这两个注册前缀。AMD 保留外部明细与汇总丢弃路径,无匹配明细的流量
|
||||
在 AMD 丢弃。汇总只要仍有覆盖的 BGP 明细(包括本 AS 注册前缀)就可能存在,不能作为外部
|
||||
peer 在线状态指示。自己的 /27、/48 更具体,继续指向本地站点。
|
||||
|
||||
VyOS 将 IPv4 /14 通过既有 OSPF E1 分发到 LAN;IPv6 /8 通过 RA 的 RIO 分发至接受该选项的客户端。
|
||||
LAN 的 DN42 IPv6 地址通过 SLAAC 自动分配。
|
||||
当前 LAN IPv4 客户端访问 DN42 /14 由 VyOS 定向 masquerade 到 `172.21.111.161`;
|
||||
IPv6 使用 SLAAC 分配的注册地址直接路由。
|
||||
AMD 只放行注册前缀在内部 `wg-oci` 与外部 `wg-dn42-1` 之间转发。
|
||||
|
||||
2026-09-15 汇总验收:VyOS 双栈 BGP 分别收到 /14、/8,下一跳为 AMD;
|
||||
laptop 的 `172.20.0.0/14` 为 OSPF 路由,经 `192.168.10.2 dev br0`。
|
||||
使用 VyOS 注册的 /32、/128 作为源,经 AMD 访问 RoutedBits 的双栈地址各 3/3 回复,
|
||||
约 4–5 ms。`site.yml` 执行成功,ARM 无变更;LAN 地址配置保持原状。
|
||||
|
||||
## DN42 DNS 转发
|
||||
|
||||
`ansible-playbook dn42.yml dn42-dns.yml` 管理入口路由和 VyOS DNS。LAN 的 Blocky
|
||||
(`192.168.10.127`) 将 `.dn42`、172.20–23 的 IPv4 反向区和 `d.f.ip6.arpa`
|
||||
转给 VyOS `192.168.10.2:53`。VyOS 仅接受三个内部 LAN 网段,使用注册地址
|
||||
`172.21.111.161` / `fdd0:98df:15b0::1` 发起递归转发,不做 NAT。
|
||||
|
||||
| 上游 | IPv4 | IPv6 |
|
||||
|---|---|---|
|
||||
| a0.recursive-servers.dn42 | 172.20.0.53 | fd42:d42:d42:54::1 |
|
||||
| a3.recursive-servers.dn42 | 172.23.0.53 | fd42:d42:d42:53::1 |
|
||||
|
||||
两个上游的双栈地址均配置,递归请求设置 RD;转发域配置 NTA,避免使用公网根信任链
|
||||
验证 DN42 私有命名空间。本地转发器不声明已完成 DN42 DNSSEC 信任锚验证。
|
||||
IPv4 anycast /32 需要允许四个 `172.2x.0.0/24` 中的 /28–/32,不能只保留 /14 的 /21–/29。
|
||||
新增互联网络明细仅由 AMD 接收;LAN 汇总仍是既定 /14、通过 RIO 通告的 IPv6 /8。
|
||||
NEC 备用 DNS 与 k3s CoreDNS 本次未修改。
|
||||
|
||||
DNS 验收(2026-09-15):从 VyOS 用注册地址直查 a0/a3 的四个双栈地址均获得回复。
|
||||
LAN 查询 Blocky 可得到 a0 的 A、a3 的 AAAA;AD 与公网域名正常,Blocky healthy。
|
||||
VyOS DNS playbook 复跑 changed=0;AMD 已应用完整入口规则。
|
||||
|
||||
## VyOS LAN 到 DN42 masquerade
|
||||
|
||||
规则 18100 排除本 AS `172.21.111.160/27`;18110、18120、18130 分别匹配三个 LAN
|
||||
源网段,目的仅 `172.20.0.0/14` 且出口 `wg42`,translation 为 `masquerade`。
|
||||
为使 masquerade 选中注册地址,`172.21.111.161/32` 从 lo 移到 wg42,并移除
|
||||
`10.255.254.2/30`;不能在 wg42 仍以传输私网地址为主 IPv4 时直接启用 masquerade。
|
||||
现有 OCI 业务互联继续保留原源地址,DNS 转发使用的 `172.21.111.161` 保持可用。
|
||||
AMD 仍仅允许 DN42 注册源前缀进入外部隧道,不在 AMD 做第二次 NAT。
|
||||
|
||||
2026-09-15 masquerade 验收:laptop `192.168.10.127` 到 RoutedBits `172.20.19.78`
|
||||
3/3 回复约 4.7 ms,VyOS NAT 表显示转换为 `172.21.111.161`;到 ARM `10.0.0.124`
|
||||
3/3 回复约 4.2 ms,NAT 表确认保留 `192.168.10.127`。
|
||||
内部 link-local 单会话双 AFI 已建立;外部 peer 保持独立接口和精确出口。
|
||||
|
||||
## LAN DN42 IPv6 RA(不发布默认路由)
|
||||
|
||||
执行 `ansible-playbook dn42-ra.yml`,为三个 LAN 启用 SLAAC:
|
||||
|
||||
| LAN | VyOS 接口 | 前缀 | 路由器地址 |
|
||||
|---|---|---|---|
|
||||
| 192.168.10.0/24 | eth0 | fdd0:98df:15b0:10::/64 | fdd0:98df:15b0:10::1 |
|
||||
| 10.60.0.0/24 | eth1 | fdd0:98df:15b0:60::/64 | fdd0:98df:15b0:60::1 |
|
||||
| 10.61.0.0/24 | eth2 | fdd0:98df:15b0:61::/64 | fdd0:98df:15b0:61::1 |
|
||||
|
||||
Router Lifetime 为 **0**,不发布 `::/0`;PIO 开启 on-link 与 autonomous 标志,
|
||||
preferred lifetime 14400 秒、valid lifetime 86400 秒。RIO 只包含 `fd00::/8`,
|
||||
有效期 180 秒;RA 周期 10–30 秒。不发布 RDNSS、DNSSL、DHCPv6 标志或链路 MTU,
|
||||
保留客户端现有 DNS 和公网出口。IPv6 经 VyOS→AMD→DN42 直接路由,不做 NAT66。
|
||||
现有 /48 对外通告及内部回程已覆盖这三个 /64,无须泄漏每个 LAN 的明细到外部。
|
||||
|
||||
客户端须支持并接受 RIO。当前 laptop 的 br0 `accept_ra=0`、`forwarding=1`,
|
||||
不会自动配置;本次保留其网络设置。Linux 路由主机如需接受 RA,需要在自身网络管理
|
||||
配置中显式启用,并允许至少 /8 的 RIO(`accept_ra_rt_info_max_plen`)。
|
||||
关闭 RA 时注意 PIO 的有效期;不要只删路由器接口地址而留下仍有效的客户端地址。
|
||||
|
||||
2026-09-15 RA 抓包验收:主 LAN 收到不带标签的 `:10::/64`;VLAN 100/110
|
||||
分别携带 `:60::/64`、`:61::/64`。三者 Router Lifetime 均为 0,PIO 为 onlink/auto,
|
||||
RIO 为 fd00::/8、180 秒,没有 DNS 或默认路由通告。`dn42-ra.yml` 复跑 changed=0。
|
||||
同一 LAN 上的临时 Linux 测试客户端自动获得地址与 RIO,无 IPv6 默认路由;测试后自动删除。
|
||||
@@ -0,0 +1,8 @@
|
||||
[defaults]
|
||||
inventory = inventory/hosts.yml
|
||||
roles_path = roles
|
||||
local_tmp = /tmp/ansible-oci
|
||||
host_key_checking = True
|
||||
interpreter_python = auto_silent
|
||||
[ssh_connection]
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=accept-new
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
- name: 配置 VyOS DN42 DNS 转发
|
||||
hosts: site_routers
|
||||
gather_facts: false
|
||||
vars:
|
||||
dn42_dns_zones:
|
||||
- dn42
|
||||
- 20.172.in-addr.arpa
|
||||
- 21.172.in-addr.arpa
|
||||
- 22.172.in-addr.arpa
|
||||
- 23.172.in-addr.arpa
|
||||
- d.f.ip6.arpa
|
||||
dn42_dns_servers:
|
||||
- 172.20.0.53
|
||||
- 172.23.0.53
|
||||
- fd42:d42:d42:54::1
|
||||
- fd42:d42:d42:53::1
|
||||
tasks:
|
||||
- name: 配置受限监听、注册地址源与条件转发
|
||||
vyos.vyos.vyos_config:
|
||||
lines: "{{ lookup('template', 'templates/vyos-dn42-dns.conf.j2').splitlines() | reject('equalto', '') | list }}"
|
||||
save: true
|
||||
comment: Ansible DN42 DNS forwarding
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: 在 LAN 通告 DN42 SLAAC 地址与专用路由
|
||||
hosts: site_routers
|
||||
gather_facts: false
|
||||
vars:
|
||||
dn42_ra_lans:
|
||||
- interface: eth0
|
||||
prefix: fdd0:98df:15b0:10::/64
|
||||
address: fdd0:98df:15b0:10::1/64
|
||||
- interface: eth1
|
||||
prefix: fdd0:98df:15b0:60::/64
|
||||
address: fdd0:98df:15b0:60::1/64
|
||||
- interface: eth2
|
||||
prefix: fdd0:98df:15b0:61::/64
|
||||
address: fdd0:98df:15b0:61::1/64
|
||||
tasks:
|
||||
- name: 配置接口地址、SLAAC 与 RIO,不通告默认路由或 DNS
|
||||
vyos.vyos.vyos_config:
|
||||
lines: "{{ lookup('template', 'templates/vyos-dn42-ra.conf.j2').splitlines() | reject('equalto', '') | list }}"
|
||||
save: true
|
||||
comment: Ansible DN42 SLAAC without default route
|
||||
@@ -0,0 +1,98 @@
|
||||
---
|
||||
# 首个外部 peer:RoutedBits Osaka,IPv6 link-local MP-BGP。
|
||||
- name: 准备 AMD DN42 WireGuard 监听
|
||||
hosts: oci_amd
|
||||
become: true
|
||||
vars:
|
||||
dn42_interface: wg-dn42-1
|
||||
dn42_port: 51821
|
||||
dn42_linklocal: fe80::1811:2/64
|
||||
dn42_peer_linklocal: fe80::207
|
||||
dn42_peer_asn: 4242420207
|
||||
dn42_peer_endpoint: router.osa1.routedbits.com:51811
|
||||
dn42_peer_public_key: 96PwUEGi/ijdmKO+IjuZ+J6DeykuTRukZD5atajfeH4=
|
||||
tasks:
|
||||
- name: 准备 FRR link-local 支持
|
||||
ansible.builtin.import_tasks: tasks/frr-dn42.yml
|
||||
- name: 创建受限密钥目录
|
||||
ansible.builtin.file:
|
||||
path: /etc/wireguard
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0700'
|
||||
- name: 在 AMD 本机生成独立私钥
|
||||
ansible.builtin.shell: umask 077; wg genkey > /etc/wireguard/{{ dn42_interface }}.key
|
||||
args:
|
||||
creates: /etc/wireguard/{{ dn42_interface }}.key
|
||||
no_log: true
|
||||
- name: 写入监听配置
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/wireguard/{{ dn42_interface }}.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
content: |
|
||||
# Ansible 管理;首个 peer 使用 IPv6 link-local MP-BGP + extended next hop。
|
||||
[Interface]
|
||||
Address = {{ dn42_linklocal }}
|
||||
ListenPort = {{ dn42_port }}
|
||||
MTU = 1380
|
||||
Table = off
|
||||
PostUp = wg set %i private-key /etc/wireguard/{{ dn42_interface }}.key
|
||||
PostUp = iptables -w -C INPUT -p udp --dport {{ dn42_port }} -j ACCEPT 2>/dev/null || iptables -w -I INPUT 1 -p udp --dport {{ dn42_port }} -j ACCEPT
|
||||
PostDown = iptables -w -D INPUT -p udp --dport {{ dn42_port }} -j ACCEPT
|
||||
|
||||
[Peer]
|
||||
PublicKey = {{ dn42_peer_public_key }}
|
||||
Endpoint = {{ dn42_peer_endpoint }}
|
||||
AllowedIPs = fe80::/64, 172.20.0.0/14, 10.0.0.0/8, 172.31.0.0/16, fd00::/8
|
||||
PersistentKeepalive = 25
|
||||
notify: 重启 DN42 接口
|
||||
- name: 启用 DN42 监听
|
||||
ansible.builtin.systemd_service:
|
||||
name: wg-quick@{{ dn42_interface }}
|
||||
enabled: true
|
||||
state: started
|
||||
- name: 应用配置
|
||||
ansible.builtin.meta: flush_handlers
|
||||
- name: 写入外部 BGP 配置片段
|
||||
ansible.builtin.template:
|
||||
src: templates/dn42-bgp.conf.j2
|
||||
dest: /etc/frr/dn42-routedbits.vtysh
|
||||
owner: root
|
||||
group: frr
|
||||
mode: '0640'
|
||||
register: dn42_bgp_config
|
||||
changed_when: dn42_bgp_config.changed or ('(deleted)' in frr_running.stdout)
|
||||
notify: 应用 DN42 BGP
|
||||
- name: 应用 BGP 配置
|
||||
ansible.builtin.meta: flush_handlers
|
||||
- name: 读取公开信息
|
||||
ansible.builtin.command: wg show {{ dn42_interface }} {{ item }}
|
||||
loop:
|
||||
- public-key
|
||||
- listen-port
|
||||
changed_when: false
|
||||
register: dn42_public_info
|
||||
- name: 显示公钥和端口
|
||||
ansible.builtin.debug:
|
||||
msg: '{{ dn42_public_info.results | map(attribute="stdout") | list }}'
|
||||
handlers:
|
||||
- name: 重启 FRR
|
||||
ansible.builtin.systemd_service:
|
||||
name: frr
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
- name: 重启 DN42 接口
|
||||
ansible.builtin.systemd_service:
|
||||
name: wg-quick@{{ dn42_interface }}
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
- name: 应用 DN42 BGP
|
||||
ansible.builtin.command: vtysh -f /etc/frr/dn42-routedbits.vtysh
|
||||
notify: 保存 FRR 配置
|
||||
when: not ansible_check_mode
|
||||
- name: 保存 FRR 配置
|
||||
ansible.builtin.command: vtysh -c 'write memory'
|
||||
when: not ansible_check_mode
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
wg_interface: wg-oci
|
||||
wg_port: 51820
|
||||
wg_mtu: 1380
|
||||
wg_home_prefixes: [192.168.10.0/24, 10.60.0.0/24, 10.61.0.0/24]
|
||||
wg_cloud_prefixes: [10.0.0.0/24]
|
||||
dn42_asn: 4242421811
|
||||
dn42_ipv4: 172.21.111.160/27
|
||||
dn42_ipv6: fdd0:98df:15b0::/48
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
wg_address: 10.255.254.2/30
|
||||
wg_peer_address: 10.255.254.1
|
||||
wg_peer_host: oci_amd
|
||||
wg_endpoint: oci-amd.ddupan.top:51820
|
||||
wg_keepalive: 25
|
||||
wg_lan_interface: br0
|
||||
bgp_asn: 65001
|
||||
bgp_peer_asn: 4242421811
|
||||
bgp_router_id: 192.168.10.127
|
||||
bgp_export: '{{ wg_home_prefixes }}'
|
||||
bgp_import: '{{ wg_cloud_prefixes }}'
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
wg_address: 10.255.254.1/30
|
||||
wg_peer_address: 172.21.111.161
|
||||
wg_peer_host: vyos_rtr
|
||||
wg_endpoint: ''
|
||||
wg_keepalive: 0
|
||||
wg_lan_interface: ens3
|
||||
bgp_asn: 4242421811
|
||||
bgp_peer_asn: 4242421811
|
||||
bgp_router_id: 10.0.0.158
|
||||
bgp_export:
|
||||
- 10.0.0.0/24
|
||||
- 172.21.111.162/32
|
||||
bgp_import:
|
||||
- 192.168.10.0/24
|
||||
- 10.60.0.0/24
|
||||
- 10.61.0.0/24
|
||||
- 172.21.111.160/27
|
||||
wg_ipv6_address: fdd0:98df:15b0:ffff::1/64
|
||||
wg_peer_ipv6: fdd0:98df:15b0:ffff::2
|
||||
bgp_export6:
|
||||
- fdd0:98df:15b0::2/128
|
||||
bgp_import6:
|
||||
- fdd0:98df:15b0::/48
|
||||
wg_linklocal_address: fe80::1811:2/64
|
||||
|
||||
# 仅向内部邻居通告汇总;外部 peer 保持精确出口过滤。
|
||||
bgp_summary: [172.20.0.0/14]
|
||||
bgp_summary6: [fd00::/8]
|
||||
dn42_external_interface: wg-dn42-1
|
||||
|
||||
# 内部与外部统一使用 link-local 单会话双 AFI;节点地址仍在 loopback。
|
||||
bgp_transport_peer: fe80::1811:1
|
||||
bgp_retired_peers: [10.255.254.2, "fdd0:98df:15b0:ffff::2"]
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
all:
|
||||
children:
|
||||
wireguard_sites:
|
||||
hosts:
|
||||
oci_amd:
|
||||
ansible_host: oci-amd.ddupan.top
|
||||
ansible_user: ubuntu
|
||||
oci_routed_hosts:
|
||||
hosts:
|
||||
oci_arm:
|
||||
ansible_host: oci-arm.ddupan.top
|
||||
ansible_user: ubuntu
|
||||
retired_wireguard_sites:
|
||||
hosts:
|
||||
laptop:
|
||||
ansible_connection: local
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
site_routers:
|
||||
hosts:
|
||||
vyos_rtr:
|
||||
ansible_host: 192.168.10.2
|
||||
ansible_user: vyos
|
||||
ansible_connection: ansible.netcommon.network_cli
|
||||
ansible_network_os: vyos.vyos.vyos
|
||||
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
- name: 确认路由器内部 BGP 已建立
|
||||
hosts: site_routers
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: 确认到 AMD 的邻居
|
||||
vyos.vyos.vyos_command:
|
||||
commands: show bgp neighbors 10.255.254.1
|
||||
register: migration_bgp
|
||||
changed_when: false
|
||||
failed_when: "'BGP state = Established' not in migration_bgp.stdout[0]"
|
||||
|
||||
- name: 退役 laptop 的 WireGuard 试验端点
|
||||
hosts: retired_wireguard_sites
|
||||
become: true
|
||||
tasks:
|
||||
- name: 停止并禁用旧隧道
|
||||
ansible.builtin.systemd_service:
|
||||
name: wg-quick@wg-oci
|
||||
state: stopped
|
||||
enabled: false
|
||||
|
||||
- name: 检查原 BGP 试验邻居是否存在
|
||||
ansible.builtin.command: vtysh -c 'show running-config'
|
||||
register: laptop_frr
|
||||
changed_when: false
|
||||
|
||||
- name: 只移除本次试验添加的 BGP 节点,保留 NEC 邻居和 OSPF
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- vtysh
|
||||
- -c
|
||||
- configure terminal
|
||||
- -c
|
||||
- router bgp 65001
|
||||
- -c
|
||||
- no neighbor 10.255.254.1
|
||||
- -c
|
||||
- address-family ipv4 unicast
|
||||
- -c
|
||||
- no network 192.168.10.0/24
|
||||
- -c
|
||||
- no network 10.60.0.0/24
|
||||
- -c
|
||||
- no network 10.61.0.0/24
|
||||
- -c
|
||||
- exit-address-family
|
||||
- -c
|
||||
- exit
|
||||
- -c
|
||||
- no ip protocol bgp route-map OCI-WG-SOURCE
|
||||
- -c
|
||||
- no route-map OCI-WG-SOURCE
|
||||
- -c
|
||||
- no ip prefix-list OCI-WG-IN
|
||||
- -c
|
||||
- no ip prefix-list OCI-WG-OUT
|
||||
- -c
|
||||
- end
|
||||
- -c
|
||||
- write memory
|
||||
when: "'neighbor 10.255.254.1 remote-as' in laptop_frr.stdout"
|
||||
|
||||
- name: 停止并禁用旧防火墙启动单元
|
||||
ansible.builtin.systemd_service:
|
||||
name: oci-wg-firewall
|
||||
state: stopped
|
||||
enabled: false
|
||||
|
||||
- name: 只删除旧隧道专用防火墙链
|
||||
ansible.builtin.shell: |
|
||||
set -eu
|
||||
changed=0
|
||||
for pair in INPUT:OCI-WG-IN FORWARD:OCI-WG-FWD; do
|
||||
parent=${pair%%:*}; chain=${pair#*:}
|
||||
if iptables -w -nL "$chain" >/dev/null 2>&1; then
|
||||
while iptables -w -C "$parent" -j "$chain" 2>/dev/null; do
|
||||
iptables -w -D "$parent" -j "$chain"
|
||||
done
|
||||
iptables -w -F "$chain"
|
||||
iptables -w -X "$chain"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
echo "$changed"
|
||||
register: retired_chains
|
||||
changed_when: retired_chains.stdout == '1'
|
||||
|
||||
- name: 移除旧的 BGP 配置片段,避免误用
|
||||
ansible.builtin.file:
|
||||
path: /etc/frr/oci-wireguard.vtysh
|
||||
state: absent
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: 重载 FRR daemons
|
||||
ansible.builtin.systemd_service:
|
||||
name: frr
|
||||
state: reloaded
|
||||
when: not ansible_check_mode
|
||||
- name: 刷新专用防火墙
|
||||
ansible.builtin.systemd_service:
|
||||
name: oci-wg-firewall
|
||||
daemon_reload: true
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
- name: 重启 WireGuard
|
||||
ansible.builtin.systemd_service:
|
||||
name: wg-quick@{{ wg_interface }}
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
- name: 应用 BGP 增量配置
|
||||
ansible.builtin.include_tasks: apply-bgp.yml
|
||||
when: not ansible_check_mode
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: 应用受管 FRR 配置片段
|
||||
ansible.builtin.command: vtysh -f /etc/frr/oci-wireguard.vtysh
|
||||
register: bgp_apply
|
||||
failed_when: bgp_apply.rc != 0 or '% Unknown' in bgp_apply.stdout or '% Unknown' in bgp_apply.stderr
|
||||
|
||||
- name: 保存完整运行配置以便重启恢复
|
||||
ansible.builtin.command: vtysh -c 'write memory'
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: 安装 AMD DN42 loopback 地址启动单元
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/dn42-loopback.service
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=DN42 router loopback addresses
|
||||
Before=frr.service
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/ip address replace 172.21.111.162/32 dev lo
|
||||
ExecStart=/usr/sbin/ip -6 address replace fdd0:98df:15b0::2/128 dev lo
|
||||
RemainAfterExit=yes
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
register: dn42_loopback_unit
|
||||
|
||||
- name: 启用 AMD DN42 loopback
|
||||
ansible.builtin.systemd_service:
|
||||
name: dn42-loopback
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
state: "{{ 'restarted' if dn42_loopback_unit.changed else 'started' }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 开启路由器 IPv6 转发
|
||||
ansible.posix.sysctl:
|
||||
name: net.ipv6.conf.all.forwarding
|
||||
value: '1'
|
||||
sysctl_file: /etc/sysctl.d/90-dn42-forwarding.conf
|
||||
sysctl_set: true
|
||||
@@ -0,0 +1,163 @@
|
||||
---
|
||||
- name: 安装 WireGuard 与 FRR
|
||||
ansible.builtin.apt:
|
||||
name: [wireguard-tools, frr, iptables]
|
||||
state: present
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
register: packages
|
||||
retries: 3
|
||||
delay: 5
|
||||
until: packages is succeeded
|
||||
|
||||
- name: 创建 WireGuard 受限目录
|
||||
ansible.builtin.file:
|
||||
path: /etc/wireguard
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0700'
|
||||
|
||||
- name: 在站点本机生成私钥,永不复制到控制机
|
||||
ansible.builtin.shell: 'umask 077; wg genkey > /etc/wireguard/{{ wg_interface }}.key'
|
||||
args:
|
||||
creates: '/etc/wireguard/{{ wg_interface }}.key'
|
||||
no_log: true
|
||||
|
||||
- name: 检查现有私钥
|
||||
ansible.builtin.stat:
|
||||
path: '/etc/wireguard/{{ wg_interface }}.key'
|
||||
register: wg_key_file
|
||||
|
||||
- name: 读取可交换的公钥
|
||||
ansible.builtin.shell: 'wg pubkey < /etc/wireguard/{{ wg_interface }}.key'
|
||||
register: wg_public_key
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
when: wg_key_file.stat.exists
|
||||
|
||||
- name: 启用 BGP daemon,保留其他协议
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/frr/daemons
|
||||
regexp: '^bgpd='
|
||||
line: bgpd=yes
|
||||
notify: 重载 FRR daemons
|
||||
when: not ansible_check_mode or not packages.changed
|
||||
|
||||
- name: 确保 FRR 运行
|
||||
ansible.builtin.systemd_service:
|
||||
name: frr
|
||||
enabled: true
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 配置 DN42 路由器地址
|
||||
ansible.builtin.include_tasks: dn42-loopback.yml
|
||||
when: bgp_export6 is defined
|
||||
|
||||
- name: 开启 IPv4 转发
|
||||
ansible.posix.sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: '1'
|
||||
sysctl_file: /etc/sysctl.d/90-oci-wireguard.conf
|
||||
sysctl_set: true
|
||||
|
||||
- name: 安装专用防火墙规则脚本
|
||||
ansible.builtin.template:
|
||||
src: firewall.sh.j2
|
||||
dest: /usr/local/sbin/oci-wg-firewall
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
notify: 刷新专用防火墙
|
||||
|
||||
- name: 安装防火墙启动单元
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/oci-wg-firewall.service
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=OCI WireGuard scoped forwarding (no NAT)
|
||||
After=network-pre.target
|
||||
Before=wg-quick@{{ wg_interface }}.service
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/oci-wg-firewall
|
||||
RemainAfterExit=yes
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
notify: 刷新专用防火墙
|
||||
|
||||
- name: 启动防火墙规则
|
||||
ansible.builtin.systemd_service:
|
||||
name: oci-wg-firewall
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 写入只含公钥的隧道配置
|
||||
ansible.builtin.template:
|
||||
src: wg.conf.j2
|
||||
dest: '/etc/wireguard/{{ wg_interface }}.conf'
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0600'
|
||||
when: wg_key_file.stat.exists and (hostvars[wg_peer_host].wg_key_file | default({"stat":{"exists":false}})).stat.exists
|
||||
notify: 重启 WireGuard
|
||||
|
||||
- name: 启动 WireGuard
|
||||
ansible.builtin.systemd_service:
|
||||
name: 'wg-quick@{{ wg_interface }}'
|
||||
enabled: true
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 写入受管 BGP 增量配置
|
||||
ansible.builtin.template:
|
||||
src: bgp.conf.j2
|
||||
dest: /etc/frr/oci-wireguard.vtysh
|
||||
owner: frr
|
||||
group: frr
|
||||
mode: '0640'
|
||||
notify: 应用 BGP 增量配置
|
||||
when: not ansible_check_mode or not packages.changed
|
||||
|
||||
- name: 先完成配置变更再验证
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: 检查 WireGuard 对端握手
|
||||
ansible.builtin.command: 'wg show {{ wg_interface }} latest-handshakes'
|
||||
changed_when: false
|
||||
register: wg_handshake
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: wg_handshake.stdout.split() | length == 2 and (wg_handshake.stdout.split()[-1] | int) > 0
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 验证隧道互通
|
||||
ansible.builtin.command: "{{ ('ping -6 -I ' ~ wg_interface ~ ' -c 3 -W 3 ' ~ bgp_transport_peer) if bgp_transport_peer is defined else ('ping -I ' ~ wg_interface ~ ' -c 3 -W 3 ' ~ wg_peer_address) }}"
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 验证 BGP 建邻并收到预期前缀数量
|
||||
ansible.builtin.command: vtysh -c 'show bgp summary json'
|
||||
changed_when: false
|
||||
register: bgp_summary
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: >-
|
||||
(bgp_summary.stdout | from_json).get('ipv4Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_address), {}).get('state') == 'Established'
|
||||
and (bgp_summary.stdout | from_json).get('ipv4Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_address), {}).get('pfxRcd', 0) == bgp_import | length
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 验证 IPv6 iBGP 建邻与前缀
|
||||
ansible.builtin.command: vtysh -c 'show bgp summary json'
|
||||
changed_when: false
|
||||
register: bgp_summary6
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: >-
|
||||
(bgp_summary6.stdout | from_json).get('ipv6Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_ipv6), {}).get('state') == 'Established'
|
||||
and (bgp_summary6.stdout | from_json).get('ipv6Unicast', {}).get('peers', {}).get(bgp_transport_peer | default(wg_peer_ipv6), {}).get('pfxRcd', 0) == bgp_import6 | length
|
||||
when: not ansible_check_mode and bgp_import6 is defined
|
||||
@@ -0,0 +1,80 @@
|
||||
{% set v4_peer = bgp_transport_peer | default(wg_peer_address) %}
|
||||
{% set v6_peer = bgp_transport_peer | default(wg_peer_ipv6 | default("")) %}
|
||||
! vtysh -f 使用配置模式,write memory 由独立 handler 执行。
|
||||
! 只重建本角色拥有的邻居和过滤器,保留既有 BGP/OSPF。
|
||||
router bgp {{ bgp_asn }}
|
||||
no neighbor {{ v4_peer }}
|
||||
{% for peer in bgp_retired_peers | default([]) %}
|
||||
no neighbor {{ peer }}
|
||||
{% endfor %}
|
||||
exit
|
||||
no ip prefix-list OCI-WG-IN
|
||||
no ip prefix-list OCI-WG-OUT
|
||||
{% for prefix in bgp_import %}
|
||||
ip prefix-list OCI-WG-IN seq {{ loop.index * 10 }} permit {{ prefix }}
|
||||
{% endfor %}
|
||||
{% for prefix in bgp_export + (bgp_summary | default([])) %}
|
||||
ip prefix-list OCI-WG-OUT seq {{ loop.index * 10 }} permit {{ prefix }}
|
||||
{% endfor %}
|
||||
router bgp {{ bgp_asn }}
|
||||
bgp router-id {{ bgp_router_id }}
|
||||
neighbor {{ v4_peer }} remote-as {{ bgp_peer_asn }}
|
||||
neighbor {{ v4_peer }} description OCI-WireGuard
|
||||
neighbor {{ v4_peer }} update-source {{ wg_interface }}
|
||||
{% if bgp_transport_peer is defined %}
|
||||
neighbor {{ v4_peer }} interface {{ wg_interface }}
|
||||
neighbor {{ v4_peer }} capability extended-nexthop
|
||||
{% endif %}
|
||||
address-family ipv4 unicast
|
||||
{% for prefix in bgp_summary | default([]) %}
|
||||
aggregate-address {{ prefix }}
|
||||
{% endfor %}
|
||||
{% for prefix in bgp_export %}
|
||||
network {{ prefix }}
|
||||
{% endfor %}
|
||||
neighbor {{ v4_peer }} activate
|
||||
neighbor {{ v4_peer }} prefix-list OCI-WG-IN in
|
||||
neighbor {{ v4_peer }} prefix-list OCI-WG-OUT out
|
||||
neighbor {{ v4_peer }} maximum-prefix {{ bgp_import | length }}
|
||||
exit-address-family
|
||||
exit
|
||||
! 仅为这些 BGP 路由选择本机业务 IP;转发报文源地址保持不变,不是 NAT。
|
||||
route-map OCI-WG-SOURCE permit 10
|
||||
match ip address prefix-list OCI-WG-IN
|
||||
set src {{ bgp_router_id }}
|
||||
exit
|
||||
route-map OCI-WG-SOURCE permit 100
|
||||
exit
|
||||
ip protocol bgp route-map OCI-WG-SOURCE
|
||||
|
||||
{% if bgp_export6 is defined %}
|
||||
no ipv6 prefix-list OCI-WG6-IN
|
||||
no ipv6 prefix-list OCI-WG6-OUT
|
||||
{% for prefix in bgp_import6 %}
|
||||
ipv6 prefix-list OCI-WG6-IN seq {{ loop.index * 10 }} permit {{ prefix }}
|
||||
{% endfor %}
|
||||
{% for prefix in bgp_export6 + (bgp_summary6 | default([])) %}
|
||||
ipv6 prefix-list OCI-WG6-OUT seq {{ loop.index * 10 }} permit {{ prefix }}
|
||||
{% endfor %}
|
||||
router bgp {{ bgp_asn }}
|
||||
neighbor {{ v6_peer }} remote-as {{ bgp_peer_asn }}
|
||||
neighbor {{ v6_peer }} update-source {{ wg_interface }}
|
||||
{% if bgp_transport_peer is not defined %}
|
||||
address-family ipv4 unicast
|
||||
no neighbor {{ v6_peer }} activate
|
||||
exit-address-family
|
||||
{% endif %}
|
||||
address-family ipv6 unicast
|
||||
{% for prefix in bgp_summary6 | default([]) %}
|
||||
aggregate-address {{ prefix }}
|
||||
{% endfor %}
|
||||
{% for prefix in bgp_export6 %}
|
||||
network {{ prefix }}
|
||||
{% endfor %}
|
||||
neighbor {{ v6_peer }} activate
|
||||
neighbor {{ v6_peer }} prefix-list OCI-WG6-IN in
|
||||
neighbor {{ v6_peer }} prefix-list OCI-WG6-OUT out
|
||||
neighbor {{ v6_peer }} maximum-prefix {{ bgp_import6 | length }}
|
||||
exit-address-family
|
||||
exit
|
||||
{% endif %}
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
# 仅重建专用链,不保存/覆盖 Docker、k3s、Tailscale 或 OCI 的其他动态规则。
|
||||
for chain in OCI-WG-IN OCI-WG-FWD; do
|
||||
iptables -w -nL "$chain" >/dev/null 2>&1 || iptables -w -N "$chain"
|
||||
iptables -w -F "$chain"
|
||||
done
|
||||
iptables -w -A OCI-WG-IN -p udp --dport {{ wg_port }} -j ACCEPT
|
||||
{% for prefix in [wg_peer_address ~ '/32'] + bgp_import %}
|
||||
iptables -w -A OCI-WG-IN -i {{ wg_interface }} -s {{ prefix }} -j ACCEPT
|
||||
{% endfor %}
|
||||
{% for local_prefix in bgp_export %}
|
||||
{% for remote_prefix in bgp_import %}
|
||||
iptables -w -A OCI-WG-FWD -i {{ wg_interface }} -o {{ wg_lan_interface }} -s {{ remote_prefix }} -d {{ local_prefix }} -j ACCEPT
|
||||
iptables -w -A OCI-WG-FWD -i {{ wg_lan_interface }} -o {{ wg_interface }} -s {{ local_prefix }} -d {{ remote_prefix }} -j ACCEPT
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% if dn42_external_interface is defined %}
|
||||
# 仅允许注册地址在内部隧道与外部 DN42 之间转发,保持无 NAT。
|
||||
iptables -w -A OCI-WG-FWD -i {{ wg_interface }} -o {{ dn42_external_interface }} -s {{ dn42_ipv4 }} -d 172.20.0.0/14 -j ACCEPT
|
||||
iptables -w -A OCI-WG-FWD -i {{ dn42_external_interface }} -o {{ wg_interface }} -s 172.20.0.0/14 -d {{ dn42_ipv4 }} -j ACCEPT
|
||||
{% endif %}
|
||||
iptables -w -A OCI-WG-FWD -i {{ wg_interface }} -j DROP
|
||||
iptables -w -A OCI-WG-FWD -o {{ wg_interface }} -j DROP
|
||||
iptables -w -C INPUT -j OCI-WG-IN 2>/dev/null || iptables -w -I INPUT 1 -j OCI-WG-IN
|
||||
iptables -w -C FORWARD -j OCI-WG-FWD 2>/dev/null || iptables -w -I FORWARD 1 -j OCI-WG-FWD
|
||||
|
||||
{% if bgp_import6 is defined %}
|
||||
ip6tables -w -nL OCI-WG6-IN >/dev/null 2>&1 || ip6tables -w -N OCI-WG6-IN
|
||||
ip6tables -w -F OCI-WG6-IN
|
||||
ip6tables -w -A OCI-WG6-IN -i {{ wg_interface }} -s {{ dn42_ipv6 }} -j ACCEPT
|
||||
ip6tables -w -C INPUT -j OCI-WG6-IN 2>/dev/null || ip6tables -w -I INPUT 1 -j OCI-WG6-IN
|
||||
{% endif %}
|
||||
|
||||
{% if dn42_external_interface is defined %}
|
||||
ip6tables -w -nL OCI-DN42-FWD >/dev/null 2>&1 || ip6tables -w -N OCI-DN42-FWD
|
||||
ip6tables -w -F OCI-DN42-FWD
|
||||
ip6tables -w -A OCI-DN42-FWD -i {{ wg_interface }} -o {{ dn42_external_interface }} -s {{ dn42_ipv6 }} -d fd00::/8 -j ACCEPT
|
||||
ip6tables -w -A OCI-DN42-FWD -i {{ dn42_external_interface }} -o {{ wg_interface }} -s fd00::/8 -d {{ dn42_ipv6 }} -j ACCEPT
|
||||
ip6tables -w -A OCI-DN42-FWD -i {{ dn42_external_interface }} -j DROP
|
||||
ip6tables -w -A OCI-DN42-FWD -o {{ dn42_external_interface }} -j DROP
|
||||
ip6tables -w -C FORWARD -j OCI-DN42-FWD 2>/dev/null || ip6tables -w -I FORWARD 1 -j OCI-DN42-FWD
|
||||
{% endif %}
|
||||
@@ -0,0 +1,16 @@
|
||||
[Interface]
|
||||
Address = {{ wg_address }}{% if wg_ipv6_address is defined %}, {{ wg_ipv6_address }}, {{ wg_linklocal_address }}{% endif %}
|
||||
|
||||
ListenPort = {{ wg_port }}
|
||||
MTU = {{ wg_mtu }}
|
||||
Table = off
|
||||
# 私钥在本机生成和读取;配置模板与控制机不保存私钥。
|
||||
PostUp = wg set %i private-key /etc/wireguard/{{ wg_interface }}.key
|
||||
|
||||
[Peer]
|
||||
PublicKey = {{ hostvars[wg_peer_host].wg_public_key.stdout }}
|
||||
AllowedIPs = {{ ([wg_peer_address ~ '/32'] + bgp_import + (bgp_import6 | default([])) + (['fe80::/64'] if bgp_import6 is defined else [])) | join(', ') }}
|
||||
{% if wg_endpoint %}
|
||||
Endpoint = {{ wg_endpoint }}
|
||||
{% endif %}
|
||||
PersistentKeepalive = {{ wg_keepalive }}
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
- import_playbook: vyos.yml
|
||||
|
||||
- name: 配置无 NAT 的 WireGuard 与 BGP 站点互联
|
||||
hosts: wireguard_sites
|
||||
become: true
|
||||
strategy: linear
|
||||
roles:
|
||||
- wireguard_bgp
|
||||
|
||||
- name: 为 OCI ARM 设置经 AMD 的站点回程,保留其余 Tailscale 路由
|
||||
hosts: oci_routed_hosts
|
||||
become: true
|
||||
tasks:
|
||||
- name: 安装三个目标网段的策略路由脚本
|
||||
ansible.builtin.template:
|
||||
src: templates/arm-routes.sh.j2
|
||||
dest: /usr/local/sbin/oci-site-return-routes
|
||||
mode: '0755'
|
||||
notify: 更新 OCI ARM 回程
|
||||
|
||||
- name: 安装回程策略启动单元
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/systemd/system/oci-site-return-routes.service
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Route homelab prefixes through OCI AMD rather than Tailscale
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/oci-site-return-routes
|
||||
RemainAfterExit=yes
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
notify: 更新 OCI ARM 回程
|
||||
|
||||
- name: 启用回程策略
|
||||
ansible.builtin.systemd_service:
|
||||
name: oci-site-return-routes
|
||||
daemon_reload: true
|
||||
enabled: true
|
||||
state: started
|
||||
when: not ansible_check_mode
|
||||
handlers:
|
||||
- name: 更新 OCI ARM 回程
|
||||
ansible.builtin.systemd_service:
|
||||
name: oci-site-return-routes
|
||||
daemon_reload: true
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
- name: 备份升级前 FRR 配置(仅远端)
|
||||
ansible.builtin.command: cp -a /etc/frr /var/backups/frr-before-dn42
|
||||
args:
|
||||
creates: /var/backups/frr-before-dn42
|
||||
- name: 安装 FRR 官方仓库签名密钥
|
||||
ansible.builtin.get_url:
|
||||
url: https://deb.frrouting.org/frr/keys.gpg
|
||||
dest: /usr/share/keyrings/frrouting.gpg
|
||||
mode: '0644'
|
||||
- name: 配置 FRR 10.7 官方仓库
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/apt/sources.list.d/frr.list
|
||||
mode: '0644'
|
||||
content: |
|
||||
deb [signed-by=/usr/share/keyrings/frrouting.gpg] https://deb.frrouting.org/frr noble frr-10.7
|
||||
register: frr_repo
|
||||
- name: 安装支持 link-local peering 的固定 FRR 版本
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- frr=10.7.1-0~ubuntu24.04.1
|
||||
- frr-pythontools=10.7.1-0~ubuntu24.04.1
|
||||
update_cache: true
|
||||
cache_valid_time: '{{ 0 if frr_repo.changed else 3600 }}'
|
||||
dpkg_options: force-confdef,force-confold
|
||||
register: frr_package
|
||||
notify: 重启 FRR
|
||||
- name: 检查 FRR 后台进程是否仍引用旧二进制
|
||||
ansible.builtin.shell: readlink /proc/$(cat /run/frr/bgpd.pid)/exe
|
||||
changed_when: false
|
||||
register: frr_running
|
||||
- name: 调度切换已升级的 FRR 进程
|
||||
ansible.builtin.debug:
|
||||
msg: 检查进程是否需要重启以使用已安装的 10.7.1
|
||||
changed_when: "'(deleted)' in frr_running.stdout"
|
||||
notify: 重启 FRR
|
||||
- name: 完成 FRR 升级
|
||||
ansible.builtin.meta: flush_handlers
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
# 5101..5103 保留给此互联,优先于 Tailscale 的 52 表(规则 5270)。
|
||||
# 主表仍使用 OCI 网关;VCN 路由表再将这三个前缀交给 AMD。
|
||||
{% for prefix in wg_home_prefixes %}
|
||||
if ! ip -4 rule show | grep -F 'to {{ prefix }} lookup main' >/dev/null; then
|
||||
ip -4 rule add priority {{ 5100 + loop.index }} to {{ prefix }} lookup main
|
||||
fi
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,62 @@
|
||||
! Ansible 管理:首个 DN42 peer,单 IPv6 会话双 AFI。
|
||||
! 精确出口与地址范围入口;尚未接入注册表 ROA 校验。
|
||||
no ip prefix-list DN42-RB-IN
|
||||
ip prefix-list DN42-RB-IN seq 1 deny 172.22.166.0/24 le 32
|
||||
ip prefix-list DN42-RB-IN seq 5 deny 172.21.111.160/27 le 32
|
||||
ip prefix-list DN42-RB-IN seq 10 deny 10.0.0.0/24 le 32
|
||||
ip prefix-list DN42-RB-IN seq 20 deny 10.60.0.0/24 le 32
|
||||
ip prefix-list DN42-RB-IN seq 30 deny 10.61.0.0/24 le 32
|
||||
ip prefix-list DN42-RB-IN seq 40 deny 192.168.10.0/24 le 32
|
||||
ip prefix-list DN42-RB-IN seq 1001 permit 172.20.0.0/24 ge 28 le 32
|
||||
ip prefix-list DN42-RB-IN seq 1002 permit 172.21.0.0/24 ge 28 le 32
|
||||
ip prefix-list DN42-RB-IN seq 1003 permit 172.22.0.0/24 ge 28 le 32
|
||||
ip prefix-list DN42-RB-IN seq 1004 permit 172.23.0.0/24 ge 28 le 32
|
||||
ip prefix-list DN42-RB-IN seq 1100 permit 172.20.0.0/14 ge 21 le 29
|
||||
ip prefix-list DN42-RB-IN seq 2001 permit 10.100.0.0/14 le 32
|
||||
ip prefix-list DN42-RB-IN seq 2002 permit 10.127.0.0/16 le 32
|
||||
ip prefix-list DN42-RB-IN seq 2003 permit 10.0.0.0/8 ge 15 le 24
|
||||
ip prefix-list DN42-RB-IN seq 3001 permit 172.31.0.0/16 le 32
|
||||
ip prefix-list DN42-RB-IN seq 9999 deny 0.0.0.0/0 le 32
|
||||
ip prefix-list DN42-RB-OUT seq 10 permit 172.21.111.160/27
|
||||
no ipv6 prefix-list DN42-RB6-IN
|
||||
ipv6 prefix-list DN42-RB6-IN seq 5 deny fdd0:98df:15b0::/48 le 128
|
||||
ipv6 prefix-list DN42-RB6-IN seq 1001 permit fd00::/8 ge 44 le 64
|
||||
ipv6 prefix-list DN42-RB6-IN seq 9999 deny ::/0 le 128
|
||||
ipv6 prefix-list DN42-RB6-OUT seq 10 permit fdd0:98df:15b0::/48
|
||||
interface {{ dn42_interface }}
|
||||
exit
|
||||
router bgp 4242421811
|
||||
no neighbor {{ dn42_peer_linklocal }}
|
||||
neighbor {{ dn42_peer_linklocal }} remote-as {{ dn42_peer_asn }}
|
||||
neighbor {{ dn42_peer_linklocal }} shutdown
|
||||
neighbor {{ dn42_peer_linklocal }} interface {{ dn42_interface }}
|
||||
neighbor {{ dn42_peer_linklocal }} update-source {{ dn42_interface }}
|
||||
neighbor {{ dn42_peer_linklocal }} description RoutedBits-OSA1
|
||||
neighbor {{ dn42_peer_linklocal }} capability extended-nexthop
|
||||
neighbor {{ dn42_peer_linklocal }} timers connect 10
|
||||
address-family ipv4 unicast
|
||||
neighbor {{ dn42_peer_linklocal }} activate
|
||||
neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB-IN in
|
||||
neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB-OUT out
|
||||
neighbor {{ dn42_peer_linklocal }} maximum-prefix 10000
|
||||
exit-address-family
|
||||
address-family ipv6 unicast
|
||||
neighbor {{ dn42_peer_linklocal }} activate
|
||||
neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB6-IN in
|
||||
neighbor {{ dn42_peer_linklocal }} prefix-list DN42-RB6-OUT out
|
||||
neighbor {{ dn42_peer_linklocal }} maximum-prefix 10000
|
||||
exit-address-family
|
||||
no neighbor {{ dn42_peer_linklocal }} shutdown
|
||||
exit
|
||||
! 保留既有业务源地址规则,在其后为 DN42 路由选择注册地址;不是 NAT。
|
||||
route-map OCI-WG-SOURCE permit 20
|
||||
match ip address prefix-list DN42-RB-IN
|
||||
set src 172.21.111.162
|
||||
exit
|
||||
route-map DN42-SOURCE6 permit 10
|
||||
match ipv6 address prefix-list DN42-RB6-IN
|
||||
set src fdd0:98df:15b0::2
|
||||
exit
|
||||
route-map DN42-SOURCE6 permit 100
|
||||
exit
|
||||
ipv6 protocol bgp route-map DN42-SOURCE6
|
||||
@@ -0,0 +1,14 @@
|
||||
set service dns forwarding listen-address '192.168.10.2'
|
||||
set service dns forwarding source-address '172.21.111.161'
|
||||
set service dns forwarding source-address 'fdd0:98df:15b0::1'
|
||||
set service dns forwarding no-serve-rfc1918
|
||||
{% for prefix in wg_home_prefixes %}
|
||||
set service dns forwarding allow-from '{{ prefix }}'
|
||||
{% endfor %}
|
||||
{% for zone in dn42_dns_zones %}
|
||||
set service dns forwarding domain {{ zone }} recursion-desired
|
||||
set service dns forwarding domain {{ zone }} addnta
|
||||
{% for server in dn42_dns_servers %}
|
||||
set service dns forwarding domain {{ zone }} name-server '{{ server }}'
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% for lan in dn42_ra_lans %}
|
||||
set interfaces ethernet {{ lan.interface }} address '{{ lan.address }}'
|
||||
set service router-advert interface {{ lan.interface }} default-lifetime '0'
|
||||
set service router-advert interface {{ lan.interface }} interval min '10'
|
||||
set service router-advert interface {{ lan.interface }} interval max '30'
|
||||
set service router-advert interface {{ lan.interface }} prefix {{ lan.prefix }} preferred-lifetime '14400'
|
||||
set service router-advert interface {{ lan.interface }} prefix {{ lan.prefix }} valid-lifetime '86400'
|
||||
set service router-advert interface {{ lan.interface }} route fd00::/8 route-preference 'medium'
|
||||
set service router-advert interface {{ lan.interface }} route fd00::/8 valid-lifetime '180'
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,12 @@
|
||||
source /opt/vyatta/etc/functions/script-template
|
||||
if /bin/cli-shell-api existsActive interfaces wireguard wg42 private-key; then
|
||||
echo KEY_EXISTS
|
||||
builtin exit 0
|
||||
fi
|
||||
configure
|
||||
set interfaces wireguard wg42 address '10.255.254.2/30'
|
||||
set interfaces wireguard wg42 private-key "$(wg genkey)"
|
||||
commit || builtin exit 1
|
||||
save || builtin exit 1
|
||||
exit
|
||||
echo KEY_CREATED
|
||||
@@ -0,0 +1,84 @@
|
||||
set interfaces wireguard wg42 description 'OCI AMD / DN42 internal transport'
|
||||
set interfaces wireguard wg42 address '172.21.111.161/32'
|
||||
set interfaces wireguard wg42 address 'fdd0:98df:15b0:ffff::2/64'
|
||||
set interfaces wireguard wg42 mtu '1380'
|
||||
set interfaces wireguard wg42 port '51820'
|
||||
set interfaces wireguard wg42 peer amd address '129.225.176.134'
|
||||
set interfaces wireguard wg42 peer amd port '51820'
|
||||
set interfaces wireguard wg42 peer amd public-key '{{ hostvars.oci_amd.wg_public_key.stdout | trim }}'
|
||||
set interfaces wireguard wg42 peer amd persistent-keepalive '25'
|
||||
set interfaces wireguard wg42 peer amd allowed-ips '10.255.254.1/32'
|
||||
set interfaces wireguard wg42 peer amd allowed-ips '10.0.0.0/24'
|
||||
set interfaces wireguard wg42 peer amd allowed-ips '172.21.111.162/32'
|
||||
set interfaces wireguard wg42 peer amd allowed-ips 'fdd0:98df:15b0::/48'
|
||||
set interfaces loopback lo address 'fdd0:98df:15b0::1/128'
|
||||
set protocols static route 172.21.111.160/27 blackhole distance '254'
|
||||
set protocols static route6 fdd0:98df:15b0::/48 blackhole distance '254'
|
||||
set protocols bgp system-as '{{ dn42_asn }}'
|
||||
set protocols bgp parameters router-id '172.21.111.161'
|
||||
{% for p in ['10.0.0.0/24', '172.21.111.162/32', '172.20.0.0/14'] %}
|
||||
set policy prefix-list OCI-SITE-IN rule {{ loop.index * 10 }} action 'permit'
|
||||
set policy prefix-list OCI-SITE-IN rule {{ loop.index * 10 }} prefix '{{ p }}'
|
||||
{% endfor %}
|
||||
{% for p in wg_home_prefixes + [dn42_ipv4] %}
|
||||
set protocols bgp address-family ipv4-unicast network '{{ p }}'
|
||||
set policy prefix-list OCI-SITE-OUT rule {{ loop.index * 10 }} action 'permit'
|
||||
set policy prefix-list OCI-SITE-OUT rule {{ loop.index * 10 }} prefix '{{ p }}'
|
||||
{% endfor %}
|
||||
set protocols bgp address-family ipv6-unicast network '{{ dn42_ipv6 }}'
|
||||
set policy prefix-list6 OCI-SITE6-IN rule 10 action 'permit'
|
||||
set policy prefix-list6 OCI-SITE6-IN rule 10 prefix 'fdd0:98df:15b0::2/128'
|
||||
set policy prefix-list6 OCI-SITE6-OUT rule 10 action 'permit'
|
||||
set policy prefix-list6 OCI-SITE6-OUT rule 10 prefix '{{ dn42_ipv6 }}'
|
||||
set policy prefix-list OCI-TO-OSPF rule 10 action 'permit'
|
||||
set policy prefix-list OCI-TO-OSPF rule 10 prefix '10.0.0.0/24'
|
||||
set policy route-map OCI-TO-OSPF rule 10 action 'permit'
|
||||
set policy route-map OCI-TO-OSPF rule 10 match ip address prefix-list 'OCI-TO-OSPF'
|
||||
set protocols ospf redistribute bgp route-map 'OCI-TO-OSPF'
|
||||
set protocols ospf redistribute bgp metric-type '1'
|
||||
set policy prefix-list DN42-TO-OSPF rule 10 action 'permit'
|
||||
set policy prefix-list DN42-TO-OSPF rule 10 prefix '{{ dn42_ipv4 }}'
|
||||
set policy route-map DN42-TO-OSPF rule 10 action 'permit'
|
||||
set policy route-map DN42-TO-OSPF rule 10 match ip address prefix-list 'DN42-TO-OSPF'
|
||||
set protocols ospf redistribute static route-map 'DN42-TO-OSPF'
|
||||
set protocols ospf redistribute static metric-type '1'
|
||||
set interfaces wireguard wg42 peer amd allowed-ips 'fe80::/64'
|
||||
|
||||
set interfaces wireguard wg42 peer amd allowed-ips '172.20.0.0/14'
|
||||
set interfaces wireguard wg42 peer amd allowed-ips 'fd00::/8'
|
||||
set policy prefix-list6 OCI-SITE6-IN rule 20 action 'permit'
|
||||
set policy prefix-list6 OCI-SITE6-IN rule 20 prefix 'fd00::/8'
|
||||
set policy prefix-list OCI-TO-OSPF rule 20 action 'permit'
|
||||
set policy prefix-list OCI-TO-OSPF rule 20 prefix '172.20.0.0/14'
|
||||
|
||||
set interfaces wireguard wg42 address 'fe80::1811:1/64'
|
||||
set protocols bgp neighbor fe80::1811:2 remote-as '{{ dn42_asn }}'
|
||||
set protocols bgp neighbor fe80::1811:2 description 'OCI AMD internal MP-BGP'
|
||||
set protocols bgp neighbor fe80::1811:2 interface source-interface 'wg42'
|
||||
set protocols bgp neighbor fe80::1811:2 update-source 'fe80::1811:1'
|
||||
set protocols bgp neighbor fe80::1811:2 capability extended-nexthop
|
||||
set protocols bgp neighbor fe80::1811:2 address-family ipv4-unicast prefix-list import 'OCI-SITE-IN'
|
||||
set protocols bgp neighbor fe80::1811:2 address-family ipv4-unicast prefix-list export 'OCI-SITE-OUT'
|
||||
set protocols bgp neighbor fe80::1811:2 address-family ipv6-unicast prefix-list import 'OCI-SITE6-IN'
|
||||
set protocols bgp neighbor fe80::1811:2 address-family ipv6-unicast prefix-list export 'OCI-SITE6-OUT'
|
||||
|
||||
{% for path in vyos_retired_paths %}
|
||||
{% if vyos_retired_present.results[loop.index0].rc == 0 %}
|
||||
delete {{ path }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
set nat source rule 18100 description 'Exclude local DN42 allocation'
|
||||
set nat source rule 18100 outbound-interface name 'wg42'
|
||||
set nat source rule 18100 destination address '172.21.111.160/27'
|
||||
set nat source rule 18100 exclude
|
||||
{% for prefix in wg_home_prefixes %}
|
||||
set nat source rule {{ 18100 + loop.index * 10 }} description 'LAN to DN42 masquerade'
|
||||
set nat source rule {{ 18100 + loop.index * 10 }} outbound-interface name 'wg42'
|
||||
set nat source rule {{ 18100 + loop.index * 10 }} source address '{{ prefix }}'
|
||||
set nat source rule {{ 18100 + loop.index * 10 }} destination address '172.20.0.0/14'
|
||||
set nat source rule {{ 18100 + loop.index * 10 }} translation address 'masquerade'
|
||||
{% endfor %}
|
||||
set policy route-map OCI-MP-IN rule 10 action 'permit'
|
||||
set policy route-map OCI-MP-IN rule 10 set ipv6-next-hop prefer-global
|
||||
set protocols bgp neighbor fe80::1811:2 address-family ipv4-unicast route-map import 'OCI-MP-IN'
|
||||
set protocols bgp neighbor fe80::1811:2 address-family ipv6-unicast route-map import 'OCI-MP-IN'
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
- name: 读取 AMD 的现有 WireGuard 公钥
|
||||
hosts: oci_amd
|
||||
become: true
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: 仅交换公钥
|
||||
ansible.builtin.shell: wg pubkey < /etc/wireguard/wg-oci.key
|
||||
register: wg_public_key
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: 配置 VyOS 站点与 DN42 内部路由
|
||||
hosts: site_routers
|
||||
gather_facts: false
|
||||
vars:
|
||||
vyos_retired_paths:
|
||||
- interfaces wireguard wg42 address 10.255.254.2/30
|
||||
- interfaces loopback lo address 172.21.111.161/32
|
||||
- protocols bgp neighbor 10.255.254.1
|
||||
- protocols bgp neighbor fdd0:98df:15b0:ffff::1
|
||||
tasks:
|
||||
- name: 检查 VyOS 是否已有 WireGuard 私钥,仅返回存在状态
|
||||
ansible.builtin.command:
|
||||
argv: [ssh, -o, BatchMode=yes, -o, ConnectTimeout=10, '[email protected]', /bin/cli-shell-api, existsActive, interfaces, wireguard, wg42, private-key]
|
||||
delegate_to: localhost
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
register: vyos_key_present
|
||||
changed_when: false
|
||||
failed_when: vyos_key_present.rc not in [0, 1]
|
||||
check_mode: false
|
||||
|
||||
- name: 首次在 VyOS 本机生成并保存私钥,控制机不接收私钥
|
||||
ansible.builtin.command:
|
||||
argv: [ssh, -o, BatchMode=yes, -o, ConnectTimeout=10, '[email protected]', /bin/vbash, -s]
|
||||
stdin: "{{ lookup('template', 'templates/vyos-key-bootstrap.vbash.j2') }}"
|
||||
delegate_to: localhost
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
no_log: true
|
||||
register: vyos_key_bootstrap
|
||||
changed_when: true
|
||||
when: not ansible_check_mode and vyos_key_present.rc == 1
|
||||
|
||||
- name: 读取 VyOS WireGuard 公钥
|
||||
vyos.vyos.vyos_command:
|
||||
commands: show interfaces wireguard wg42 public-key
|
||||
register: vyos_wg_public
|
||||
changed_when: false
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 准備 Linux 端需要的公钥事实
|
||||
ansible.builtin.set_fact:
|
||||
wg_public_key:
|
||||
stdout: '{{ vyos_wg_public.stdout[0] | trim }}'
|
||||
wg_key_file:
|
||||
stat:
|
||||
exists: true
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: 检查待退役的旧地址和 BGP 邻居是否存在
|
||||
ansible.builtin.command:
|
||||
cmd: 'ssh -o BatchMode=yes [email protected] /bin/cli-shell-api existsActive {{ item }}'
|
||||
loop: '{{ vyos_retired_paths }}'
|
||||
delegate_to: localhost
|
||||
vars:
|
||||
ansible_connection: local
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
register: vyos_retired_present
|
||||
changed_when: false
|
||||
failed_when: vyos_retired_present.rc not in [0, 1]
|
||||
check_mode: false
|
||||
|
||||
- name: 应用 VyOS 双栈 WireGuard、iBGP 与精确路由过滤
|
||||
vyos.vyos.vyos_config:
|
||||
lines: "{{ lookup('template', 'templates/vyos-site.conf.j2').splitlines() | map('trim') | reject('equalto', '') | list }}"
|
||||
save: true
|
||||
comment: Ansible OCI WireGuard DN42 preparation
|
||||
register: vyos_site_config
|
||||
changed_when: (vyos_site_config.commands | default([]) | length) > 0
|
||||
no_log: true
|
||||
@@ -0,0 +1,9 @@
|
||||
.terraform/
|
||||
*.tfstate*
|
||||
*.tfplan
|
||||
*.tfvars
|
||||
*.tfvars.json
|
||||
*.auto.tfvars*
|
||||
crash.log
|
||||
crash.*.log
|
||||
.recovery/
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/oracle/oci" {
|
||||
version = "9.1.0"
|
||||
hashes = [
|
||||
"h1:luO69Wv8J0GKbkd+hr2UbouenTZBAPhyAxTQacJAeGU=",
|
||||
"zh:0343d74d10e8c0b803874a303bea5b6619e76762ace8fdafb1bae6d867c3ece0",
|
||||
"zh:077ac3c561c1b9af97c9325900b65825279537a8f85074adcf3f11af151e387e",
|
||||
"zh:2571fc3f274fb081aae27d7b9fd150e3ab39309fa15241bf48b8877b8f8d8529",
|
||||
"zh:26e16906006b6cb3022a06abc21884f08e1b35696bb7259b92d4aaf0e8056a17",
|
||||
"zh:3882078acbfddb13a5b64d37afcded15ca56d3381f46314e4869e68dfc7f2759",
|
||||
"zh:3d9b6c5e1362abbb7469cd096e68a7c1bf642959621784b6b096f0fae97dcf89",
|
||||
"zh:462ef1bb079e78aadd50377fde647144c8bb0da324c314e997f4d15af81991f4",
|
||||
"zh:510f88ab1592960c73fc004de8329ceff26803d470e22522623aefb99e917163",
|
||||
"zh:573a7139600f21d5ae1e733317948b6f352a5434d7d06e0d3910211f79d4f286",
|
||||
"zh:647b750d4fa8d982cbeef73d2beeec1dc7076ee32ee8833d0ff634e2f7ccdad0",
|
||||
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
|
||||
"zh:b1539e73622d3ee4fe11c951c11063341b8b302a167a9bad6712bb0e075ccdb7",
|
||||
"zh:d4f1012d1a62bdc69230d7e3adc5f3bcb78a755adb858d7e6cd6af0200255828",
|
||||
"zh:fa11dc03a714e8f7056bb5ff92646f1f34dea74339650b5fafdd923a5bcc5864",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# E2 Micro 使用独立的 x86_64 镜像;固定版本以免镜像目录更新触发重建。
|
||||
resource "oci_core_instance" "amd" {
|
||||
availability_domain = oci_core_instance.vm.availability_domain
|
||||
compartment_id = var.compartment_ocid
|
||||
display_name = "homelab-amd"
|
||||
shape = "VM.Standard.E2.1.Micro"
|
||||
|
||||
create_vnic_details {
|
||||
subnet_id = oci_core_subnet.public.id
|
||||
skip_source_dest_check = true
|
||||
nsg_ids = [oci_core_network_security_group.wireguard.id]
|
||||
assign_public_ip = true
|
||||
hostname_label = "homelab-amd"
|
||||
display_name = "homelab-amd"
|
||||
}
|
||||
|
||||
# 仅复用登录公钥,不复制原实例可能新增的 user_data 等初始化内容。
|
||||
metadata = {
|
||||
ssh_authorized_keys = var.instance_metadata["ssh_authorized_keys"]
|
||||
}
|
||||
|
||||
source_details {
|
||||
source_type = "image"
|
||||
source_id = "ocid1.image.oc1.ap-osaka-1.aaaaaaaap3ki4u45x7acf3q3upe6sudoe4l3ogin55dmgp3gye6lvhhjg6mq"
|
||||
boot_volume_size_in_gbs = 50
|
||||
boot_volume_vpus_per_gb = 10
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
output "amd_instance_id" {
|
||||
value = oci_core_instance.amd.id
|
||||
}
|
||||
output "amd_private_ip" {
|
||||
value = oci_core_instance.amd.private_ip
|
||||
}
|
||||
output "amd_public_ip" {
|
||||
value = oci_core_instance.amd.public_ip
|
||||
}
|
||||
output "amd_ssh_command" {
|
||||
value = "ssh ubuntu@${oci_core_instance.amd.public_ip}"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
# versions.tf 已配置相同的 OCI backend;此文件保留为参数参考。
|
||||
# 只连接已有对象,禁止把恢复副本迁移覆盖远端。
|
||||
bucket = "oci-k8s-free-tier-tfstate"
|
||||
namespace = "axckv9ylwqxr"
|
||||
key = "terraform.tfstate"
|
||||
region = "ap-osaka-1"
|
||||
auth = "APIKey"
|
||||
config_file_profile = "DEFAULT"
|
||||
@@ -0,0 +1,53 @@
|
||||
resource "oci_core_instance" "vm" {
|
||||
agent_config {
|
||||
are_all_plugins_disabled = false
|
||||
is_management_disabled = false
|
||||
is_monitoring_disabled = false
|
||||
}
|
||||
availability_config {
|
||||
is_live_migration_preferred = false
|
||||
recovery_action = "RESTORE_INSTANCE"
|
||||
}
|
||||
availability_domain = "diPX:AP-OSAKA-1-AD-1"
|
||||
compartment_id = var.compartment_ocid
|
||||
create_vnic_details {
|
||||
assign_private_dns_record = false
|
||||
assign_public_ip = "true"
|
||||
display_name = "homelab-vm"
|
||||
hostname_label = "homelab-vm"
|
||||
private_ip = "10.0.0.124"
|
||||
skip_source_dest_check = false
|
||||
subnet_id = oci_core_subnet.public.id
|
||||
}
|
||||
display_name = "homelab-vm"
|
||||
fault_domain = "FAULT-DOMAIN-1"
|
||||
instance_options {
|
||||
are_legacy_imds_endpoints_disabled = false
|
||||
}
|
||||
launch_options {
|
||||
boot_volume_type = "PARAVIRTUALIZED"
|
||||
firmware = "UEFI_64"
|
||||
is_consistent_volume_naming_enabled = true
|
||||
is_pv_encryption_in_transit_enabled = false
|
||||
network_type = "PARAVIRTUALIZED"
|
||||
remote_data_volume_type = "PARAVIRTUALIZED"
|
||||
}
|
||||
metadata = var.instance_metadata
|
||||
shape = "VM.Standard.A1.Flex"
|
||||
shape_config {
|
||||
memory_in_gbs = 12
|
||||
ocpus = 2
|
||||
}
|
||||
source_details {
|
||||
boot_volume_size_in_gbs = "100"
|
||||
boot_volume_vpus_per_gb = "10"
|
||||
source_id = var.image_ocid
|
||||
source_type = "image"
|
||||
}
|
||||
|
||||
# 原生命周期配置不可恢复;保护现有 VM,避免重建丢失系统盘数据。
|
||||
lifecycle {
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
data "oci_core_images" "ubuntu" {
|
||||
compartment_id = var.compartment_ocid
|
||||
operating_system = "Canonical Ubuntu"
|
||||
operating_system_version = "24.04"
|
||||
shape = "VM.Standard.A1.Flex"
|
||||
sort_by = "TIMECREATED"
|
||||
sort_order = "DESC"
|
||||
}
|
||||
|
||||
data "oci_identity_availability_domains" "ads" {
|
||||
compartment_id = var.compartment_ocid
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
resource "oci_core_internet_gateway" "igw" {
|
||||
compartment_id = var.compartment_ocid
|
||||
display_name = "homelab-igw"
|
||||
enabled = true
|
||||
vcn_id = oci_core_vcn.vcn.id
|
||||
}
|
||||
|
||||
resource "oci_core_route_table" "public" {
|
||||
compartment_id = var.compartment_ocid
|
||||
display_name = "homelab-public-rt"
|
||||
route_rules {
|
||||
destination = "0.0.0.0/0"
|
||||
destination_type = "CIDR_BLOCK"
|
||||
network_entity_id = oci_core_internet_gateway.igw.id
|
||||
}
|
||||
dynamic "route_rules" {
|
||||
for_each = toset(local.wireguard_home_prefixes)
|
||||
content {
|
||||
destination = route_rules.value
|
||||
destination_type = "CIDR_BLOCK"
|
||||
network_entity_id = var.amd_router_private_ip_ocid
|
||||
}
|
||||
}
|
||||
vcn_id = oci_core_vcn.vcn.id
|
||||
}
|
||||
|
||||
resource "oci_core_security_list" "public" {
|
||||
compartment_id = var.compartment_ocid
|
||||
display_name = "homelab-public-sl"
|
||||
egress_security_rules {
|
||||
description = ""
|
||||
destination = "0.0.0.0/0"
|
||||
destination_type = "CIDR_BLOCK"
|
||||
protocol = "all"
|
||||
stateless = false
|
||||
}
|
||||
ingress_security_rules {
|
||||
description = ""
|
||||
icmp_options {
|
||||
code = 4
|
||||
type = 3
|
||||
}
|
||||
protocol = "1"
|
||||
source = "0.0.0.0/0"
|
||||
source_type = "CIDR_BLOCK"
|
||||
stateless = false
|
||||
}
|
||||
ingress_security_rules {
|
||||
description = ""
|
||||
protocol = "17"
|
||||
source = "0.0.0.0/0"
|
||||
source_type = "CIDR_BLOCK"
|
||||
stateless = false
|
||||
udp_options {
|
||||
max = 41641
|
||||
min = 41641
|
||||
}
|
||||
}
|
||||
ingress_security_rules {
|
||||
description = ""
|
||||
protocol = "6"
|
||||
source = "0.0.0.0/0"
|
||||
source_type = "CIDR_BLOCK"
|
||||
stateless = false
|
||||
tcp_options {
|
||||
max = 22
|
||||
min = 22
|
||||
}
|
||||
}
|
||||
# 内层源地址保持家中网段;OCI 安全列表须允许这些已路由的流量。
|
||||
dynamic "ingress_security_rules" {
|
||||
for_each = toset(local.wireguard_home_prefixes)
|
||||
content {
|
||||
protocol = "all"
|
||||
source = ingress_security_rules.value
|
||||
source_type = "CIDR_BLOCK"
|
||||
stateless = false
|
||||
description = "WireGuard routed homelab traffic"
|
||||
}
|
||||
}
|
||||
vcn_id = oci_core_vcn.vcn.id
|
||||
}
|
||||
|
||||
resource "oci_core_subnet" "public" {
|
||||
cidr_block = "10.0.0.0/24"
|
||||
compartment_id = var.compartment_ocid
|
||||
dhcp_options_id = oci_core_vcn.vcn.default_dhcp_options_id
|
||||
display_name = "homelab-public-subnet"
|
||||
dns_label = "public"
|
||||
prohibit_public_ip_on_vnic = false
|
||||
route_table_id = oci_core_route_table.public.id
|
||||
security_list_ids = [oci_core_security_list.public.id]
|
||||
vcn_id = oci_core_vcn.vcn.id
|
||||
}
|
||||
|
||||
resource "oci_core_vcn" "vcn" {
|
||||
cidr_blocks = ["10.0.0.0/16"]
|
||||
compartment_id = var.compartment_ocid
|
||||
display_name = "homelab-vcn"
|
||||
dns_label = "homelab"
|
||||
is_ipv6enabled = false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
output "instance_id" {
|
||||
value = oci_core_instance.vm.id
|
||||
}
|
||||
|
||||
output "instance_private_ip" {
|
||||
value = oci_core_instance.vm.private_ip
|
||||
}
|
||||
|
||||
output "instance_public_ip" {
|
||||
value = oci_core_instance.vm.public_ip
|
||||
}
|
||||
|
||||
output "ssh_command" {
|
||||
value = "ssh ubuntu@${oci_core_instance.vm.public_ip}"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
"""从已下载的 state 初始化本地恢复环境,不输出 metadata,也不覆盖现有文件。"""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
os.umask(0o077)
|
||||
source = Path(sys.argv[1])
|
||||
raw = source.read_bytes()
|
||||
state = json.loads(raw)
|
||||
vm = next(r for r in state['resources'] if r['mode'] == 'managed'
|
||||
and r['type'] == 'oci_core_instance' and r['name'] == 'vm')
|
||||
root = Path(__file__).resolve().parent
|
||||
local = root / '.recovery' / 'terraform.tfstate'
|
||||
variables = root / 'terraform.tfvars.json'
|
||||
if local.exists() or variables.exists():
|
||||
sys.exit('本地 state 或 tfvars 已存在;请先核对并自行备份,脚本不会覆盖。')
|
||||
local.parent.mkdir(mode=0o700, exist_ok=True)
|
||||
with local.open('xb') as f:
|
||||
f.write(raw)
|
||||
with variables.open('x') as f:
|
||||
json.dump({'instance_metadata': vm['instances'][0]['attributes']['metadata']}, f, indent=2)
|
||||
f.write('\n')
|
||||
print('已准备本地 state 和 metadata 变量;未修改远端。')
|
||||
@@ -0,0 +1,4 @@
|
||||
provider "oci" {
|
||||
region = var.region
|
||||
config_file_profile = var.oci_config_profile
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
resource "oci_limits_quota" "free_tier_quota" {
|
||||
compartment_id = var.compartment_ocid
|
||||
description = "Enforce Always Free tier compliance for compute and storage."
|
||||
name = "always-free-tier-quota"
|
||||
statements = ["set compute-core quota standard-a1-core-count to 4 in tenancy", "set compute-memory quota standard-a1-memory-count to 24 in tenancy", "set block-storage quota volume-count to 10 in tenancy", "set block-storage quota total-storage-gb to 200 in tenancy"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
variable "region" {
|
||||
type = string
|
||||
default = "ap-osaka-1"
|
||||
}
|
||||
variable "oci_config_profile" {
|
||||
type = string
|
||||
default = "DEFAULT"
|
||||
}
|
||||
variable "compartment_ocid" {
|
||||
type = string
|
||||
default = "ocid1.tenancy.oc1..aaaaaaaalncmdinfnljot5oh7rqleavphhsa6jpyurfu4luztcct3tp5gsfa"
|
||||
}
|
||||
# 固定现有启动镜像;查询最新 Ubuntu 镜像不应触发现有实例重建。
|
||||
variable "image_ocid" {
|
||||
type = string
|
||||
default = "ocid1.image.oc1.ap-osaka-1.aaaaaaaayniglvbm5xyligxib2nk3jz3zendq6ywckp4gppteii4tg7wzhua"
|
||||
}
|
||||
variable "instance_metadata" {
|
||||
description = "从受限 state 副本提取的原实例 metadata;仅存于忽略的本地 tfvars。"
|
||||
type = map(string)
|
||||
sensitive = true
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
terraform {
|
||||
required_version = ">= 1.15.8, < 2.0.0"
|
||||
required_providers {
|
||||
oci = {
|
||||
source = "oracle/oci"
|
||||
version = "= 9.1.0"
|
||||
}
|
||||
}
|
||||
# 已停用旧执行流程,当前根模块直接管理既有对象;不迁移本地恢复副本。
|
||||
backend "oci" {
|
||||
bucket = "oci-k8s-free-tier-tfstate"
|
||||
namespace = "axckv9ylwqxr"
|
||||
key = "terraform.tfstate"
|
||||
region = "ap-osaka-1"
|
||||
auth = "APIKey"
|
||||
config_file_profile = "DEFAULT"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
locals {
|
||||
wireguard_home_prefixes = ["192.168.10.0/24", "10.60.0.0/24", "10.61.0.0/24"]
|
||||
}
|
||||
|
||||
# route table -> subnet -> instance 已存在依赖,不能反向引用实例的私有 IP。
|
||||
# 此值从现有 AMD VNIC 核对;重建实例后必须重新查询,禁止继续使用旧 OCID。
|
||||
variable "amd_router_private_ip_ocid" {
|
||||
type = string
|
||||
default = "ocid1.privateip.oc1.ap-osaka-1.abvwsljrhxq2cw46zhjyrnn5zbz4rrynjbzlynkz6ubhiedadr72zcaideaq"
|
||||
}
|
||||
|
||||
resource "oci_core_network_security_group" "wireguard" {
|
||||
compartment_id = var.compartment_ocid
|
||||
vcn_id = oci_core_vcn.vcn.id
|
||||
display_name = "homelab-amd-wireguard"
|
||||
}
|
||||
|
||||
resource "oci_core_network_security_group_security_rule" "wireguard" {
|
||||
network_security_group_id = oci_core_network_security_group.wireguard.id
|
||||
direction = "INGRESS"
|
||||
protocol = "17"
|
||||
source = "0.0.0.0/0"
|
||||
source_type = "CIDR_BLOCK"
|
||||
stateless = false
|
||||
description = "WireGuard NAT traversal; peer public key authenticates clients"
|
||||
udp_options {
|
||||
destination_port_range {
|
||||
min = 51820
|
||||
max = 51820
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "oci_core_network_security_group_security_rule" "dn42_first_peer" {
|
||||
network_security_group_id = oci_core_network_security_group.wireguard.id
|
||||
direction = "INGRESS"
|
||||
protocol = "17"
|
||||
source = "0.0.0.0/0"
|
||||
source_type = "CIDR_BLOCK"
|
||||
stateless = false
|
||||
description = "DN42 first external WireGuard peer; public key authentication"
|
||||
udp_options {
|
||||
destination_port_range {
|
||||
min = 51821
|
||||
max = 51821
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,13 @@
|
||||
---
|
||||
# Shared authentication for typed community.proxmox API modules. Secrets are
|
||||
# runtime inputs; never put them in inventory or vars files.
|
||||
pve_api_host: "{{ lookup('env', 'PROXMOX_HOST') }}"
|
||||
pve_api_user: "{{ lookup('env', 'PROXMOX_USER') }}"
|
||||
pve_api_token_id: "{{ lookup('env', 'PROXMOX_TOKEN_ID') }}"
|
||||
pve_api_token_secret: "{{ lookup('env', 'PROXMOX_TOKEN_SECRET') }}"
|
||||
pve_api_validate_certs: true
|
||||
pve_api_python_interpreter: "{{ lookup('env', 'PVE_API_PYTHON') | default(ansible_playbook_python, true) }}"
|
||||
|
||||
ansible_user: root
|
||||
ansible_python_interpreter: /usr/bin/python3
|
||||
|
||||
|
||||
@@ -19,20 +19,13 @@ all:
|
||||
|
||||
pve2:
|
||||
ansible_host: 192.168.10.7
|
||||
# ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G. THIS is the node that
|
||||
# randomly froze. Kernel workaround below; also set BIOS
|
||||
# "Power Supply Idle Control" -> "Typical Current Idle" (the more
|
||||
# reliable half of the fix, and it can only be done physically).
|
||||
pve_kernel_cmdline_extra: "idle=nomwait processor.max_cstate=1"
|
||||
# ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G.
|
||||
pve_install_disk: nvme0n1
|
||||
pve_bulk_disk: sda
|
||||
|
||||
pve3:
|
||||
ansible_host: 192.168.10.9
|
||||
# Same Raven Ridge silicon as pve2 but has never frozen. Deliberately
|
||||
# left WITHOUT the C-state workaround: processor.max_cstate=1 blocks
|
||||
# deeper idle states and costs power, so it is not applied pre-emptively.
|
||||
# If pve3 ever freezes, set pve_kernel_cmdline_extra here too.
|
||||
# ThinkCentre, Ryzen 2400GE (Raven Ridge), 8G.
|
||||
pve_install_disk: nvme0n1
|
||||
pve_bulk_disk: sda
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
proxmoxer==2.3.0
|
||||
requests==2.34.2
|
||||
@@ -13,3 +13,5 @@ collections:
|
||||
- name: ansible.posix # mount, authorized_key
|
||||
- name: community.general # proxmox_kvm (guest lifecycle). NOTE: deb822_repository
|
||||
# is ansible.builtin, NOT community.general — see CLAUDE.md
|
||||
- name: community.proxmox
|
||||
version: 2.0.0 # typed PVE API modules for HA, SDN and guest lifecycle
|
||||
|
||||
@@ -14,12 +14,26 @@ pve_ha_resources:
|
||||
max_restart: 3
|
||||
max_relocate: 2
|
||||
|
||||
# pve3 is the normal home for the critical router. Non-strict preserves pve1
|
||||
# and pve2 as failover targets when pve3 is unavailable.
|
||||
pve_ha_rules:
|
||||
- name: prefer-vyos-pve3
|
||||
type: node-affinity
|
||||
resources: ["vm:100"]
|
||||
nodes: ["pve3:100", "pve1:10", "pve2:10"]
|
||||
strict: false
|
||||
comment: "Prefer critical VyOS router on pve3, away from CI nodes"
|
||||
|
||||
pve_ha_portable_vms:
|
||||
- vmid: 100
|
||||
node: pve3
|
||||
cpu: x86-64-v2-AES
|
||||
|
||||
# --- Watchdog ---------------------------------------------------------------
|
||||
# ⚠ WHY THIS MATTERS: adding ANY HA resource arms fencing cluster-wide. PVE's
|
||||
# default is `softdog`, a SOFTWARE watchdog — a kernel timer, which CANNOT fire if
|
||||
# the kernel itself is frozen. That is precisely the failure this cluster has
|
||||
# actually seen (pve2's Raven Ridge idle freeze). A hardware watchdog is
|
||||
# independent silicon and fires regardless.
|
||||
# the kernel itself is frozen. A hardware watchdog is independent silicon and
|
||||
# fires regardless.
|
||||
#
|
||||
# Verified available 2026-07-26:
|
||||
# pve1 (Intel i3-6100U) -> iTCO_wdt (timeout 30s)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Deliberately does NOT reboot. Swapping the watchdog under an armed cluster is a
|
||||
# manual, ordered operation (see ../../../README-ha.md); a surprise rolling reboot
|
||||
# of all three nodes is exactly what you do not want an idempotent play to do.
|
||||
- name: reboot required for watchdog
|
||||
- name: Reboot required for watchdog
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
Watchdog config changed on {{ inventory_hostname }}. It takes effect on the
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
---
|
||||
# Proxmox HA. Read roles/pve_ha/defaults/main.yml first — it explains why the
|
||||
# resource list is short and why the watchdog choice is not cosmetic.
|
||||
- name: Validate Proxmox API inputs
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- pve_api_host | length > 0
|
||||
- pve_api_user | length > 0
|
||||
- pve_api_token_id | length > 0
|
||||
- pve_api_token_secret | length > 0
|
||||
fail_msg: >-
|
||||
Export PROXMOX_HOST, PROXMOX_USER, PROXMOX_TOKEN_ID and
|
||||
PROXMOX_TOKEN_SECRET before running ha.yml.
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
tags: [ha]
|
||||
|
||||
# --- Watchdog ---------------------------------------------------------------
|
||||
# Done BEFORE registering resources: adding a resource arms fencing, and arming
|
||||
# fencing on a software watchdog is the weakest configuration.
|
||||
- name: Select the hardware watchdog module
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/default/pve-ha-manager
|
||||
@@ -12,13 +21,9 @@
|
||||
line: "WATCHDOG_MODULE={{ pve_ha_watchdog_module }}"
|
||||
create: false
|
||||
when: pve_ha_watchdog_module | length > 0
|
||||
notify: reboot required for watchdog
|
||||
notify: Reboot required for watchdog
|
||||
tags: [ha, watchdog]
|
||||
|
||||
# watchdog-mux opens /dev/watchdog, which belongs to whichever watchdog registered
|
||||
# FIRST. PVE loads softdog at boot, so it wins and the hardware module ends up as
|
||||
# an unused watchdog1. Blacklisting softdog is what actually makes the hardware
|
||||
# one take effect — setting WATCHDOG_MODULE alone does nothing.
|
||||
- name: Blacklist softdog so the hardware watchdog claims /dev/watchdog
|
||||
ansible.builtin.copy:
|
||||
dest: /etc/modprobe.d/pve-ha-watchdog.conf
|
||||
@@ -29,48 +34,147 @@
|
||||
# {{ pve_ha_watchdog_module }} register as watchdog0 and own /dev/watchdog.
|
||||
blacklist softdog
|
||||
when: pve_ha_watchdog_module | length > 0
|
||||
notify: reboot required for watchdog
|
||||
notify: Reboot required for watchdog
|
||||
tags: [ha, watchdog]
|
||||
|
||||
# ⚠ NOT DONE LIVE ON PURPOSE. Swapping the watchdog on a running node means
|
||||
# stopping watchdog-mux and unloading softdog while fencing is armed — get the
|
||||
# order wrong and the node self-fences (reboots). The safe live procedure is
|
||||
# documented in ../../README-ha.md; otherwise it simply takes effect on the next
|
||||
# reboot, which is why the handler only WARNS rather than rebooting anything.
|
||||
|
||||
# --- Resources ---------------------------------------------------------------
|
||||
- name: Read current HA resources
|
||||
ansible.builtin.command: ha-manager status
|
||||
register: pve_ha_status
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
- name: Read current HA VM configurations
|
||||
ansible.builtin.uri:
|
||||
url: >-
|
||||
https://{{ pve_api_host }}:8006/api2/json/nodes/{{ item.node }}/qemu/{{ item.vmid }}/config
|
||||
headers:
|
||||
Authorization: >-
|
||||
PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }}
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
return_content: true
|
||||
loop: "{{ pve_ha_portable_vms }}"
|
||||
loop_control:
|
||||
label: "VM {{ item.vmid }}"
|
||||
register: pve_ha_vm_config
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
tags: [ha]
|
||||
|
||||
- name: Register HA resources
|
||||
ansible.builtin.command: >-
|
||||
ha-manager add {{ item.sid }}
|
||||
--state {{ item.state }}
|
||||
--max_restart {{ item.max_restart }}
|
||||
--max_relocate {{ item.max_relocate }}
|
||||
- name: Reconcile portable CPU models for HA VMs
|
||||
community.proxmox.proxmox_kvm:
|
||||
api_host: "{{ pve_api_host }}"
|
||||
api_user: "{{ pve_api_user }}"
|
||||
api_token_id: "{{ pve_api_token_id }}"
|
||||
api_token_secret: "{{ pve_api_token_secret }}"
|
||||
validate_certs: "{{ pve_api_validate_certs }}"
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
node: "{{ item.node }}"
|
||||
vmid: "{{ item.vmid }}"
|
||||
cpu: "{{ item.cpu }}"
|
||||
update: true
|
||||
state: present
|
||||
loop: "{{ pve_ha_portable_vms }}"
|
||||
loop_control:
|
||||
label: "VM {{ item.vmid }} -> {{ item.cpu }}"
|
||||
index_var: vm_index
|
||||
when: pve_ha_vm_config.results[vm_index].json.data.cpu != item.cpu
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||
tags: [ha]
|
||||
|
||||
- name: Read current HA resources
|
||||
ansible.builtin.uri:
|
||||
url: "https://{{ pve_api_host }}:8006/api2/json/cluster/ha/resources"
|
||||
headers:
|
||||
Authorization: >-
|
||||
PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }}
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
return_content: true
|
||||
register: pve_ha_resource_state
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
tags: [ha]
|
||||
|
||||
- name: Reconcile HA resources through the PVE API
|
||||
community.proxmox.proxmox_cluster_ha_resources:
|
||||
api_host: "{{ pve_api_host }}"
|
||||
api_user: "{{ pve_api_user }}"
|
||||
api_token_id: "{{ pve_api_token_id }}"
|
||||
api_token_secret: "{{ pve_api_token_secret }}"
|
||||
validate_certs: "{{ pve_api_validate_certs }}"
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
name: "{{ item.sid }}"
|
||||
state: present
|
||||
hastate: "{{ item.state }}"
|
||||
max_restart: "{{ item.max_restart }}"
|
||||
max_relocate: "{{ item.max_relocate }}"
|
||||
loop: "{{ pve_ha_resources }}"
|
||||
loop_control:
|
||||
label: "{{ item.sid }}"
|
||||
# `ha-manager add` errors if the resource already exists, so gate on the status
|
||||
# output. This is what keeps a second run at changed=0.
|
||||
when: "'service ' ~ item.sid not in (pve_ha_status.stdout | default(''))"
|
||||
when: >-
|
||||
_current_resource | length == 0 or
|
||||
_current_resource.state != item.state or
|
||||
_current_resource.max_restart | int != item.max_restart | int or
|
||||
_current_resource.max_relocate | int != item.max_relocate | int
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||
_current_resource: >-
|
||||
{{ pve_ha_resource_state.json.data |
|
||||
selectattr('sid', 'equalto', item.sid) | first | default({}) }}
|
||||
tags: [ha]
|
||||
|
||||
- name: Verify HA is armed and the resource is known
|
||||
ansible.builtin.command: ha-manager status
|
||||
register: pve_ha_verify
|
||||
changed_when: false
|
||||
- name: Reconcile HA placement rules through the PVE API
|
||||
community.proxmox.proxmox_cluster_ha_rules:
|
||||
api_host: "{{ pve_api_host }}"
|
||||
api_user: "{{ pve_api_user }}"
|
||||
api_token_id: "{{ pve_api_token_id }}"
|
||||
api_token_secret: "{{ pve_api_token_secret }}"
|
||||
validate_certs: "{{ pve_api_validate_certs }}"
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
name: "{{ item.name }}"
|
||||
state: present
|
||||
type: "{{ item.type }}"
|
||||
resources: "{{ item.resources }}"
|
||||
nodes: "{{ item.nodes }}"
|
||||
strict: "{{ item.strict }}"
|
||||
comment: "{{ item.comment }}"
|
||||
disable: false
|
||||
loop: "{{ pve_ha_rules }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||
tags: [ha]
|
||||
|
||||
- name: Show HA state
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ pve_ha_verify.stdout_lines }}"
|
||||
- name: Read HA placement rules through the PVE API
|
||||
community.proxmox.proxmox_cluster_ha_rules_info:
|
||||
api_host: "{{ pve_api_host }}"
|
||||
api_user: "{{ pve_api_user }}"
|
||||
api_token_id: "{{ pve_api_token_id }}"
|
||||
api_token_secret: "{{ pve_api_token_secret }}"
|
||||
validate_certs: "{{ pve_api_validate_certs }}"
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
register: pve_ha_rule_state
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||
tags: [ha]
|
||||
|
||||
- name: Assert declared placement rules exist
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- >-
|
||||
(pve_ha_rules | map(attribute='name') | list |
|
||||
difference(pve_ha_rule_state.rules | default([]) |
|
||||
map(attribute='rule') | list) | length) == 0
|
||||
fail_msg: A declared HA placement rule is absent after reconciliation
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
tags: [ha]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
# Per-host kernel cmdline additions, driven by `pve_kernel_cmdline_extra` in the
|
||||
# inventory. Currently only pve2 sets it (Raven Ridge random-freeze workaround);
|
||||
# pve3 is identical silicon but is deliberately left alone -- see inventory.
|
||||
# inventory. No node currently sets it; keep the role for future kernel parameters.
|
||||
#
|
||||
# This is a no-op on hosts that do not define the variable.
|
||||
|
||||
|
||||
@@ -1,60 +1,81 @@
|
||||
---
|
||||
# SDN config is cluster-wide (/etc/pve/sdn/), so everything runs once.
|
||||
|
||||
- name: Read existing zones
|
||||
ansible.builtin.command:
|
||||
cmd: "pvesh get /cluster/sdn/zones --output-format json"
|
||||
register: _zones
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
- name: Validate Proxmox API inputs
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- pve_api_host | length > 0
|
||||
- pve_api_user | length > 0
|
||||
- pve_api_token_id | length > 0
|
||||
- pve_api_token_secret | length > 0
|
||||
fail_msg: Missing Proxmox API environment variables
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
|
||||
- name: Create the VLAN zone
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
pvesh create /cluster/sdn/zones --zone {{ pve_sdn_zone }} --type vlan
|
||||
--bridge {{ pve_sdn_bridge }} --mtu {{ pve_sdn_mtu }}
|
||||
--nodes {{ groups['pve'] | join(',') }}
|
||||
when: pve_sdn_zone not in (_zones.stdout | from_json | map(attribute='zone') | list)
|
||||
- name: Read current PVE SDN zones
|
||||
ansible.builtin.uri:
|
||||
url: "https://{{ pve_api_host }}:8006/api2/json/cluster/sdn/zones"
|
||||
headers:
|
||||
Authorization: >-
|
||||
PVEAPIToken={{ pve_api_user }}!{{ pve_api_token_id }}={{ pve_api_token_secret }}
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
return_content: true
|
||||
register: pve_sdn_zone_state
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
|
||||
- name: Read existing vnets
|
||||
ansible.builtin.command:
|
||||
cmd: "pvesh get /cluster/sdn/vnets --output-format json"
|
||||
register: _vnets
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
- name: Reconcile the PVE VLAN zone
|
||||
community.proxmox.proxmox_zone:
|
||||
api_host: "{{ pve_api_host }}"
|
||||
api_user: "{{ pve_api_user }}"
|
||||
api_token_id: "{{ pve_api_token_id }}"
|
||||
api_token_secret: "{{ pve_api_token_secret }}"
|
||||
validate_certs: "{{ pve_api_validate_certs }}"
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
zone: "{{ pve_sdn_zone }}"
|
||||
type: vlan
|
||||
bridge: "{{ pve_sdn_bridge }}"
|
||||
mtu: "{{ pve_sdn_mtu }}"
|
||||
nodes: "{{ groups['pve'] | join(',') }}"
|
||||
update: true
|
||||
state: present
|
||||
when: >-
|
||||
_current_zone | length == 0 or
|
||||
_current_zone.type != 'vlan' or
|
||||
_current_zone.bridge != pve_sdn_bridge or
|
||||
_current_zone.mtu | int != pve_sdn_mtu | int or
|
||||
_current_zone.nodes != (groups['pve'] | join(','))
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||
_current_zone: >-
|
||||
{{ pve_sdn_zone_state.json.data |
|
||||
selectattr('zone', 'equalto', pve_sdn_zone) | first | default({}) }}
|
||||
|
||||
- name: Create the VNets
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
pvesh create /cluster/sdn/vnets --vnet {{ item.name }}
|
||||
--zone {{ pve_sdn_zone }} --tag {{ item.tag }}
|
||||
--alias '{{ item.alias }}'
|
||||
# The module owns the SDN global lock and applies pending configuration before
|
||||
# releasing it; a separate unconditional `pvesh set /cluster/sdn` is harmful
|
||||
# because it reports changed on every run.
|
||||
- name: Reconcile PVE VNets
|
||||
community.proxmox.proxmox_vnet:
|
||||
api_host: "{{ pve_api_host }}"
|
||||
api_user: "{{ pve_api_user }}"
|
||||
api_token_id: "{{ pve_api_token_id }}"
|
||||
api_token_secret: "{{ pve_api_token_secret }}"
|
||||
validate_certs: "{{ pve_api_validate_certs }}"
|
||||
ca_path: /etc/ssl/certs/ca-certificates.crt
|
||||
vnet: "{{ item.name }}"
|
||||
zone: "{{ pve_sdn_zone }}"
|
||||
tag: "{{ item.tag }}"
|
||||
alias: "{{ item.alias }}"
|
||||
update: true
|
||||
state: present
|
||||
loop: "{{ pve_sdn_vnets }}"
|
||||
loop_control:
|
||||
label: "{{ item.name }} (vlan {{ item.tag }})"
|
||||
when: item.name not in (_vnets.stdout | from_json | map(attribute='vnet') | list)
|
||||
run_once: true
|
||||
|
||||
- name: Apply the SDN configuration
|
||||
# SDN changes stay PENDING until applied; without this the VNet bridges are
|
||||
# never actually created on the nodes.
|
||||
ansible.builtin.command:
|
||||
cmd: "pvesh set /cluster/sdn"
|
||||
register: _apply
|
||||
changed_when: true
|
||||
run_once: true
|
||||
|
||||
- name: Report
|
||||
ansible.builtin.shell:
|
||||
cmd: "pvesh get /cluster/sdn/vnets --output-format json | python3 -c \"import json,sys;[print(' ',v['vnet'],'vlan',v.get('tag'),'zone',v.get('zone')) for v in json.load(sys.stdin)]\""
|
||||
register: _rep
|
||||
changed_when: false
|
||||
run_once: true
|
||||
|
||||
- name: Show it
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ _rep.stdout_lines }}"
|
||||
label: "{{ item.name }} (VLAN {{ item.tag }})"
|
||||
run_once: true
|
||||
delegate_to: localhost
|
||||
no_log: true
|
||||
vars:
|
||||
ansible_python_interpreter: "{{ pve_api_python_interpreter }}"
|
||||
|
||||
@@ -28,6 +28,9 @@ pve_vms:
|
||||
description: "VyOS router: OSPF peer to the NEC IX, gateway for the SDN VNets."
|
||||
cores: 2
|
||||
memory: 2048
|
||||
# Portable across Intel pve1 and AMD pve2/pve3. `host` transferred memory
|
||||
# but failed target-side resume during the 2026-09-17 migration.
|
||||
cpu: x86-64-v2-AES
|
||||
disk: "pve-rg:8"
|
||||
# Headless by design: UEFI + serial console, NO emulated VGA. VyOS's own
|
||||
# kernel cmdline carries console=ttyS0, and the installer was answered with
|
||||
@@ -56,6 +59,7 @@ pve_vms:
|
||||
# are AMD.
|
||||
cores: 4
|
||||
memory: 4096
|
||||
cpu: host
|
||||
disk: "pve-rg:40"
|
||||
bios: ovmf
|
||||
machine: q35
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
--description '{{ item.item.description }}'
|
||||
--machine {{ item.item.machine | default('q35') }}
|
||||
--bios {{ item.item.bios | default('ovmf') }}
|
||||
--memory {{ item.item.memory }} --cores {{ item.item.cores }} --cpu host
|
||||
--memory {{ item.item.memory }} --cores {{ item.item.cores }} --cpu {{ item.item.cpu | default('x86-64-v2-AES') }}
|
||||
--scsihw virtio-scsi-single
|
||||
--scsi0 {{ item.item.disk }},discard=on,ssd=1
|
||||
--efidisk0 {{ item.item.disk.split(':')[0] }}:1,efitype=4m,pre-enrolled-keys=0
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: 更新现有 AD 域控 DNS 上游
|
||||
hosts: samba_dc
|
||||
become: true
|
||||
gather_facts: false
|
||||
tasks:
|
||||
- name: 仅应用 DNS 转发器配置
|
||||
ansible.builtin.import_role:
|
||||
name: samba_ad_dc
|
||||
tasks_from: dns_forwarder
|
||||
@@ -7,7 +7,7 @@ samba_ad_realm: "AD.DDUPAN.TOP"
|
||||
samba_ad_domain: "DDUPAN"
|
||||
samba_ad_dc_hostname: "dc1"
|
||||
samba_ad_dc_ip: "192.168.10.5"
|
||||
samba_ad_dns_forwarder: "192.168.10.1"
|
||||
samba_ad_dns_forwarder: "192.168.10.127" # Blocky:DN42 条件转发及公网解析
|
||||
samba_ad_reverse_zone: "10.168.192.in-addr.arpa" # reverse of 192.168.10.0/24
|
||||
|
||||
# Static records now live in ../../dns/records.yml and are reconciled as complete
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
- name: 更新 Samba DNS 转发器并校验配置
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/samba/smb.conf
|
||||
regexp: '^\s*dns forwarder\s*='
|
||||
insertafter: '^\[global\]'
|
||||
line: "\tdns forwarder = {{ samba_ad_dns_forwarder }}"
|
||||
validate: '/usr/bin/testparm -s %s'
|
||||
backup: true
|
||||
notify: restart samba-ad-dc
|
||||
@@ -87,6 +87,9 @@
|
||||
creates: /var/lib/samba/private/sam.ldb
|
||||
no_log: true # keep the admin password out of logs
|
||||
|
||||
- name: 持续管理已初始化域控的 DNS 上游
|
||||
ansible.builtin.import_tasks: dns_forwarder.yml
|
||||
|
||||
- name: Install the generated krb5.conf system-wide
|
||||
ansible.builtin.copy:
|
||||
src: /var/lib/samba/private/krb5.conf
|
||||
|
||||
Reference in New Issue
Block a user