恢复 OCI Terraform 与站点网络 IaC

This commit is contained in:
2026-09-17 13:22:49 +00:00
parent a08c8a7303
commit 71eea7d8fc
41 changed files with 1786 additions and 10 deletions
+110 -10
View File
@@ -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 根。
- Statenamespace `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 Flex2 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_6450 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 接口建立双栈 iBGPlaptop 保留原有 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 OsakaAS4242420207),AMD 使用独立 `wg-dn42-1`
和单 IPv6 MP-BGP 会话承载双栈;详见 [Ansible runbook](ansible/README.md)。
外部明细留在 AMD`172.20.0.0/14``fd00::/8` 汇总经 iBGP 下发 VyOSIPv4 /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 与专用路由通告。
+232
View File
@@ -0,0 +1,232 @@
# VyOS ↔ OCIWireGuard 与 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/64BGP | 4242421811 |
| AMD | wg-oci | 10.255.254.1/30(历史传输地址) | fe80::1811:2/64BGP | 4242421811 |
AMD 显式设置 `fe80::1811:2/64`VyOS BGP 使用固定 `fe80::1811:1/64`,双方 AllowedIPs 包含
`fe80::/64`。FRR 在 ULA 建邻时仍需接口具有 link-local 下一跳地址。
MTU 1380Linux 使用 `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 |
|---|---|---|
| VyOSIPv4 在 wg42IPv6 在 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 OsakaAS4242420207),由 `dn42.yml` 单独管理。** 外部邻居使用独立的 import/export
过滤,只对外通告注册 /27、/48;禁止把上述内部业务前缀的过滤器复用到外部邻居。
入口使用 DN42 指南的保留地址、互联网络与前缀长度规则(IPv6 /44–/64),
并优先拒收本 AS、LAN、OCI 前缀;尚未配置注册表 ROA 校验。外部明细留在 AMD,汇总通过 iBGP 下发 VyOSIPv4 汇总再经 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 51015103、仅匹配这三个目的前缀的 `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 IPv4AMD 可访问 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 分发到 LANIPv6 /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 回复,
约 45 ms。`site.yml` 执行成功,ARM 无变更;LAN 地址配置保持原状。
## DN42 DNS 转发
`ansible-playbook dn42.yml dn42-dns.yml` 管理入口路由和 VyOS DNS。LAN 的 Blocky
(`192.168.10.127`) 将 `.dn42`、172.2023 的 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 的 AAAAAD 与公网域名正常,Blocky healthy。
VyOS DNS playbook 复跑 changed=0AMD 已应用完整入口规则。
## 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 msVyOS NAT 表显示转换为 `172.21.111.161`;到 ARM `10.0.0.124`
3/3 回复约 4.2 msNAT 表确认保留 `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 周期 1030 秒。不发布 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 均为 0PIO 为 onlink/auto
RIO 为 fd00::/8、180 秒,没有 DNS 或默认路由通告。`dn42-ra.yml` 复跑 changed=0。
同一 LAN 上的临时 Linux 测试客户端自动获得地址与 RIO,无 IPv6 默认路由;测试后自动删除。
+8
View File
@@ -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
+23
View File
@@ -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
+21
View File
@@ -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
+98
View File
@@ -0,0 +1,98 @@
---
# 首个外部 peerRoutedBits OsakaIPv6 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 }}
+52
View File
@@ -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'
+84
View File
@@ -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
+9
View File
@@ -0,0 +1,9 @@
.terraform/
*.tfstate*
*.tfplan
*.tfvars
*.tfvars.json
*.auto.tfvars*
crash.log
crash.*.log
.recovery/
+23
View File
@@ -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",
]
}
+45
View File
@@ -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"
+53
View File
@@ -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
}
}
+13
View File
@@ -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
}
+103
View File
@@ -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
}
+16
View File
@@ -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
}
+7
View File
@@ -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"]
}
+22
View File
@@ -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
}
+18
View File
@@ -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"
}
}
+48
View File
@@ -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
}
}
}