Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84a1e9e37f
|
@@ -1,30 +0,0 @@
|
|||||||
# 独立增量声明:全量 values.yaml 尚未覆盖所有线上客户端,不能用它覆盖 release。
|
|
||||||
authorization_policies:
|
|
||||||
incus_admin:
|
|
||||||
default_policy: deny
|
|
||||||
rules:
|
|
||||||
- policy: two_factor
|
|
||||||
subject: user:panxiao81
|
|
||||||
clients:
|
|
||||||
- client_id: incus
|
|
||||||
client_name: Incus
|
|
||||||
public: true
|
|
||||||
authorization_policy: incus_admin
|
|
||||||
require_pkce: true
|
|
||||||
pkce_challenge_method: S256
|
|
||||||
redirect_uris:
|
|
||||||
- https://incus.ad.ddupan.top/oidc/callback
|
|
||||||
audience:
|
|
||||||
- https://incus.ad.ddupan.top
|
|
||||||
scopes:
|
|
||||||
- openid
|
|
||||||
- offline_access
|
|
||||||
response_types:
|
|
||||||
- code
|
|
||||||
grant_types:
|
|
||||||
- authorization_code
|
|
||||||
- refresh_token
|
|
||||||
- urn:ietf:params:oauth:grant-type:device_code
|
|
||||||
access_token_signed_response_alg: RS256
|
|
||||||
userinfo_signed_response_alg: none
|
|
||||||
token_endpoint_auth_method: none
|
|
||||||
@@ -27,7 +27,7 @@ spec:
|
|||||||
type: RuntimeDefault
|
type: RuntimeDefault
|
||||||
containers:
|
containers:
|
||||||
- name: backstage
|
- name: backstage
|
||||||
image: zot.ad.ddupan.top/panxiao81/backstage@sha256:008a3ccf832c9ee061ef03766022789a7539bd9d001a658af2e9f3ff59a9a5cc
|
image: zot.ad.ddupan.top/panxiao81/backstage@sha256:e5a12550726f19a680bc7c40e2cc07cc624318f9279ee121814d293a006ef210
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
env:
|
env:
|
||||||
- name: BACKSTAGE_BASE_URL
|
- name: BACKSTAGE_BASE_URL
|
||||||
|
|||||||
@@ -6,11 +6,6 @@ homelab_dns:
|
|||||||
# Samba remains authoritative for the AD zone. Only these explicitly listed
|
# Samba remains authoritative for the AD zone. Only these explicitly listed
|
||||||
# RRsets are reconciled; Samba-generated AD/Kerberos records are untouched.
|
# RRsets are reconciled; Samba-generated AD/Kerberos records are untouched.
|
||||||
records:
|
records:
|
||||||
- { zone: ad.ddupan.top, name: incus, type: A, values: [192.168.10.127] }
|
|
||||||
# Static, outside the NEC IX pool (.128-.250); infrastructure/incus/terraform reads
|
|
||||||
# these same values into each container's cloud-init network-config.
|
|
||||||
- { zone: ad.ddupan.top, name: ayatori-dev, type: A, values: [192.168.10.11] }
|
|
||||||
- { zone: ad.ddupan.top, name: ayatori-prod, type: A, values: [192.168.10.12] }
|
|
||||||
- { zone: ad.ddupan.top, name: bao, type: A, values: [192.168.10.8] }
|
- { zone: ad.ddupan.top, name: bao, type: A, values: [192.168.10.8] }
|
||||||
- { zone: ad.ddupan.top, name: pve1, type: A, values: [192.168.10.4] }
|
- { 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: pve2, type: A, values: [192.168.10.7] }
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
# laptop Incus
|
|
||||||
|
|
||||||
Ansible 管理 Ubuntu 24.04 amd64 上的 Zabbly stable APT 源、公钥、固定包版本和
|
|
||||||
Incus 本地服务。当前版本为 `7.5.1`,完整 Debian 版本见
|
|
||||||
`ansible/group_vars/incus_hosts.yml`。使用系统包,不使用 snap。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-incus ansible-playbook -i infrastructure/incus/ansible/inventory/hosts.yml infrastructure/incus/ansible/site.yml --syntax-check
|
|
||||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-incus ansible-playbook -i infrastructure/incus/ansible/inventory/hosts.yml infrastructure/incus/ansible/site.yml --check --diff
|
|
||||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-incus ansible-playbook -i infrastructure/incus/ansible/inventory/hosts.yml infrastructure/incus/ansible/site.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
执行者需有免密 sudo。首次机器没有包索引时,check 模式不能验证待安装包的可用性;
|
|
||||||
实际执行会刷新该源并校验包签名。重跑应 `changed=0`。
|
|
||||||
|
|
||||||
## 管理边界
|
|
||||||
|
|
||||||
- `site.yml` 管安装;`access.yml` 管 Web UI/OIDC 入口。不执行 `incus admin init`,不创建存储池、profile、实例或受管网络。
|
|
||||||
- 保留 libvirt VM、k3s、现有 bridge、路由和防火墙配置;不引入 OVN/SDN/LB。
|
|
||||||
- HTTPS listener 为 `192.168.10.127:8443`,域名入口经现有 Envoy;不新增 `incus-admin` 成员。本地 `sudo incus` 保留为恢复入口。
|
|
||||||
- etcd 和共享 PostgreSQL 由各自目录管理;此目录不迁移它们。
|
|
||||||
- 不自动删除旧实例或存储;历史 `data/incus` 已由维护者另行授权删除,非本 playbook 行为。
|
|
||||||
|
|
||||||
## 版本与公钥维护
|
|
||||||
|
|
||||||
公钥来自 Zabbly,主指纹为 `4EFC590696CB15B87C73A3AD82CC8797C838DCFD`,
|
|
||||||
与[上游安装说明](https://github.com/zabbly/incus)核对后随配置保存。
|
|
||||||
更新密钥时先核对上游指纹。版本由 APT preferences 固定;升级需修改变量,
|
|
||||||
审阅 `--check --diff` 后执行,不自动降级。
|
|
||||||
上游 stable 源未承诺永久保留旧构建,长期离线重建需另行保存包及依赖。
|
|
||||||
|
|
||||||
## 验证与故障入口
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo incus version
|
|
||||||
sudo incus list local:
|
|
||||||
sudo incus storage list local:
|
|
||||||
sudo incus network list local:
|
|
||||||
systemctl status incus.service incus.socket
|
|
||||||
sudo journalctl -u incus.service -n 80 --no-pager
|
|
||||||
```
|
|
||||||
|
|
||||||
网络列表可能展示宿主已有的非受管接口,不代表 Incus 创建了网络。
|
|
||||||
安装验收不等于实例运行验收;存储、实例备份恢复方案留待资源初始化时确定。
|
|
||||||
共享知识入口:`homelab-wiki/services/incus.md`(随本次变更同步)。
|
|
||||||
|
|
||||||
## Web UI 与 Authelia
|
|
||||||
|
|
||||||
入口:`https://incus.ad.ddupan.top`,选择 **Login with SSO**,使用 `panxiao81`
|
|
||||||
并完成 MFA。Authelia 专属 policy 默认 deny,仅此账号可取得 Incus token。
|
|
||||||
该客户端登录者具有 Incus 完整管理权限,不根据当前 AD 组放权。
|
|
||||||
|
|
||||||
CLI 可使用 `incus remote add laptop https://incus.ad.ddupan.top --auth-type=oidc`,
|
|
||||||
按设备码流程在浏览器中完成同一登录;token 由客户端保存在本机,不写入 Git。
|
|
||||||
|
|
||||||
声明分工:
|
|
||||||
|
|
||||||
- `apps/authelia/clients/incus.yaml`:单用户 MFA 准入、public client、PKCE S256、
|
|
||||||
签名 access token、唯一 audience、浏览器回调和 device/refresh grants。
|
|
||||||
- `ansible/group_vars/incus_hosts.yml`:Incus OIDC issuer/client/audience/scopes 和 listener。
|
|
||||||
- `ansible/templates/gateway.yaml.j2`:独立 namespace、Service/EndpointSlice、HTTPRoute、
|
|
||||||
BackendTLSPolicy。入口复用现有通配符证书,后端以 Incus 的公共 server.crt 验证
|
|
||||||
`laptop` SAN,不跳过 TLS 验证。公共证书由 playbook 读取,不复制私钥。
|
|
||||||
- `infrastructure/dns/records.yml`:唯一 DNS 声明;`ansible/dns.yml` 只协调 Incus 与两个 Ayatori 容器的记录。
|
|
||||||
|
|
||||||
入口资源由此 Ansible playbook 管理,尚未交由 Flux。共享 Gateway 的后端 TLS
|
|
||||||
兼容配置由 Flux 管理:EnvoyProxy `eg` 允许 TLS 1.2–1.3,以连接要求 TLS 1.3
|
|
||||||
的 Incus;见 [PR #164](https://git.ddupan.top/panxiao81/homelab-infra/pulls/164)。
|
|
||||||
此补丁不升级网关、不改变前端证书或其他路由。
|
|
||||||
Authelia 当前不是 Flux 受管 release。协调脚本先读取 live Helm values,
|
|
||||||
仅合并 Incus client/policy,固定 chart 0.11.6 渲染后部署;保留 Hydra/Backstage 等
|
|
||||||
现有配置和秘密,禁止以滞后的 `apps/authelia/values.yaml` 全量覆盖线上 release。
|
|
||||||
临时 values 存在 0700 目录、0600 文件中并自动清理,部署前检查 release revision,
|
|
||||||
避免覆盖准备期间的并发 Helm 变更;仍应避免同时升级该 release。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 先 site.yml 安装 UI,再接入;均先 --check --diff 再移除这两个参数执行。
|
|
||||||
ANSIBLE_LOCAL_TEMP=/tmp/ansible-incus ansible-playbook -i infrastructure/incus/ansible/inventory/hosts.yml infrastructure/incus/ansible/access.yml --check --diff
|
|
||||||
|
|
||||||
# DNS 使用既有 Samba inventory/collection,仅修改 Incus 的 RRset。
|
|
||||||
cd infrastructure/samba-ad/ansible
|
|
||||||
ANSIBLE_CONFIG=ansible.cfg ANSIBLE_COLLECTIONS_PATH=collections ANSIBLE_LOCAL_TEMP=/tmp/ansible-incus-dns ansible-playbook -i inventory/hosts.yml ../../incus/ansible/dns.yml --check --diff
|
|
||||||
```
|
|
||||||
|
|
||||||
执行接入需要本机 Python3/PyYAML、Helm、kubectl 及现有集群管理凭据;DNS 另需
|
|
||||||
既有 Samba Ansible vault 与 SSH 权限。首次入口 namespace 不存在时 check 使用
|
|
||||||
客户端 dry-run;存在后使用 kubectl diff。后续实际重跑要求 `changed=0`。
|
|
||||||
|
|
||||||
### 故障与迁移
|
|
||||||
|
|
||||||
- Web 入口和 OIDC 依赖 k3s/Envoy/Authelia;它们不可用时使用宿主 `sudo incus`,
|
|
||||||
Incus daemon 与实例生命周期不依赖网页登录。
|
|
||||||
- `8443` 直连仍由 Incus 原生认证保护,但只注册域名 443 的 OIDC callback,
|
|
||||||
浏览器登录应从正式域名进入。网关不透传客户端 TLS 证书;远程主要使用 OIDC。
|
|
||||||
- 更换宿主 Incus 证书后重跑 `access.yml`,同步网关公共信任证书。
|
|
||||||
- 未来切换 IdM:更新 issuer/client/audience/scopes 和新 provider 的准入策略,
|
|
||||||
验证浏览器及 CLI 后再停用 Authelia client;本次不引入永久组模型。
|
|
||||||
- 移除用户准入不会立即撤销已签发的 JWT;紧急撤权需同时处理现有 token/会话。
|
|
||||||
- 回退入口时先撤 HTTPRoute,再关闭 `core.https_address`;保留本地管理和现有实例数据。
|
|
||||||
|
|
||||||
验证包括 DNS、TLS、route conditions、匿名 API 拒绝、PKCE/回调和设备码授权发起。
|
|
||||||
2026-09-25 维护者确认 `panxiao81` 完成 MFA 并成功返回 Incus 控制台。
|
|
||||||
安装、接入、DNS playbook 重跑均 `changed=0`;后续基础容器验收见下节。
|
|
||||||
未验证 Web 实例控制台或 Ayatori 应用数据路径。
|
|
||||||
|
|
||||||
## Ayatori 基础容器
|
|
||||||
|
|
||||||
`terraform/` 只管理 `ayatori-dev`、`ayatori-prod` 两个 Ubuntu 24.04 非特权 LXC,
|
|
||||||
以及专属 ZFS 存储池 `ayatori`(`data/incus-ayatori`)。每个容器上限 2 CPU、
|
|
||||||
2 GiB 内存、20 GiB rootfs,禁用容器 swap,自动启动。没有安装 Ayatori、k0s、
|
|
||||||
kube-apiserver、controller 或数据库;准备 Prod 空容器不代表生产服务已上线。
|
|
||||||
|
|
||||||
网络接入既有 `br0`,固定 MAC 分别为 `02:16:3e:aa:00:01`、`02:16:3e:aa:00:02`。
|
|
||||||
Terraform 通过 `cloud-init.network-config` 配置静态地址:Dev `192.168.10.11`、
|
|
||||||
Prod `192.168.10.12`(`ayatori-{dev,prod}.ad.ddupan.top`)。地址只在
|
|
||||||
`infrastructure/dns/records.yml` 声明一次,Terraform 与 AD DNS 均从此读取;须位于
|
|
||||||
NEC IX DHCP 池(`.128–.250`)之外。⚠ 镜像模板只在创建/复制时渲染 cloud-init seed,
|
|
||||||
对已有实例改地址不会生效(重启、`cloud-init clean` 都不行),只能重建实例。
|
|
||||||
|
|
||||||
SSH 全部由 cloud-init 完成:安装 openssh-server,创建 `panxiao81`(`ansible/files/panxiao81.pub`
|
|
||||||
公钥、免密 sudo),装包后再写 `/etc/ssh/sshd_config.d/60-homelab.conf` 关闭密码与 root 登录。
|
|
||||||
不使用 `ssh_pwauth`:它在装包前写出残缺的 `sshd_config`,包自带的 `UsePAM yes` 落不下来,
|
|
||||||
锁定密码的账号会被拒(2026-09-25 曾发生)。宿主也可使用 `incus exec local:ayatori-dev -- bash`。
|
|
||||||
provider 通过本地 Unix socket 操作,执行账号须有 socket 权限。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
terraform -chdir=infrastructure/incus/terraform init
|
|
||||||
terraform -chdir=infrastructure/incus/terraform plan -out=containers.tfplan
|
|
||||||
terraform -chdir=infrastructure/incus/terraform apply -parallelism=1 containers.tfplan
|
|
||||||
terraform -chdir=infrastructure/incus/terraform output containers
|
|
||||||
terraform -chdir=infrastructure/incus/terraform plan -detailed-exitcode
|
|
||||||
```
|
|
||||||
|
|
||||||
provider 固定 1.2.0 并保留 lockfile。镜像跟随 `images:ubuntu/24.04/cloud` 最新构建,
|
|
||||||
只在创建时使用(`ignore_changes`);不固定指纹,因为上游会下架旧构建,2026-10-01
|
|
||||||
重建时固定指纹已无法获取。
|
|
||||||
cloud-init 用户设置主要在首次启动执行,修改声明不能替代后续用户/密钥轮换流程。
|
|
||||||
|
|
||||||
当前 state 位于 `terraform/terraform.tfstate`(Git 忽略),是本地 backend;
|
|
||||||
现有 Bao 会话无法读取远端 tfstate 凭据,因此尚未启用远端 backend。
|
|
||||||
后续迁移须使用 `terraform init -migrate-state` 保留资源归属,不创建第二份独立 state。
|
|
||||||
实例和池启用 `prevent_destroy`;删除需显式审阅,不能通过移走整个资源块绕过保护。
|
|
||||||
当前为空系统,丢失后可重建;承载持久数据前须补离机备份与恢复验收。
|
|
||||||
|
|
||||||
首次从同一远端并行创建实例时,Incus 7.5.1 曾出现 simplestreams 缓存目录
|
|
||||||
`mkdir ... file exists` 竞争;按上面的串行 apply 执行。失败后先重新 plan,
|
|
||||||
保留已成功创建的实例,不清理或销毁其资源。
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
- name: 接入 Incus Web UI 与 Authelia
|
|
||||||
hosts: incus_hosts
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
tasks:
|
|
||||||
- name: 增量协调 Authelia 的 Incus 客户端
|
|
||||||
become: false
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv: "{{ ['python3', playbook_dir ~ '/../scripts/reconcile_authelia.py', '--desired', playbook_dir ~ '/../../../apps/authelia/clients/incus.yaml'] + (['--check'] if ansible_check_mode else []) }}"
|
|
||||||
register: incus_authelia
|
|
||||||
changed_when: "'changed=true' in incus_authelia.stdout"
|
|
||||||
check_mode: false
|
|
||||||
|
|
||||||
- name: 查询当前 Incus 服务配置
|
|
||||||
ansible.builtin.command: incus query /1.0
|
|
||||||
register: incus_server
|
|
||||||
changed_when: false
|
|
||||||
check_mode: false
|
|
||||||
no_log: true
|
|
||||||
|
|
||||||
- name: 协调 OIDC 与 LAN HTTPS listener
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv: [incus, config, set, "{{ item.key }}={{ item.value }}"]
|
|
||||||
loop: "{{ incus_server_config | dict2items }}"
|
|
||||||
when: (incus_server.stdout | from_json).config.get(item.key, '') != item.value
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: 读取 Incus 公共证书用于网关后端验证
|
|
||||||
ansible.builtin.slurp:
|
|
||||||
src: /var/lib/incus/server.crt
|
|
||||||
register: incus_backend_certificate
|
|
||||||
|
|
||||||
- name: 检查入口 namespace 是否已存在
|
|
||||||
become: false
|
|
||||||
ansible.builtin.command: kubectl get namespace incus --ignore-not-found -o name
|
|
||||||
register: incus_namespace
|
|
||||||
changed_when: false
|
|
||||||
check_mode: false
|
|
||||||
|
|
||||||
- name: 渲染并预览或应用专属入口资源
|
|
||||||
become: false
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv: "{{ (['kubectl', 'apply', '--dry-run=client', '-f', '-'] if incus_namespace.stdout == '' else ['kubectl', 'diff', '-f', '-']) if ansible_check_mode else ['kubectl', 'apply', '-f', '-'] }}"
|
|
||||||
stdin: "{{ lookup('template', 'gateway.yaml.j2') }}"
|
|
||||||
register: incus_gateway
|
|
||||||
check_mode: false
|
|
||||||
changed_when: "(incus_gateway.rc == 1 or incus_namespace.stdout == '') if ansible_check_mode else ('created' in incus_gateway.stdout or 'configured' in incus_gateway.stdout)"
|
|
||||||
failed_when: "incus_gateway.rc not in ([0, 1] if ansible_check_mode else [0])"
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
---
|
|
||||||
- name: 仅协调 Incus 与 Ayatori 容器的 AD DNS 记录
|
|
||||||
hosts: samba_dc
|
|
||||||
become: true
|
|
||||||
gather_facts: false
|
|
||||||
vars:
|
|
||||||
incus_dns_names: [incus, ayatori-dev, ayatori-prod]
|
|
||||||
vars_files:
|
|
||||||
- ../../dns/records.yml
|
|
||||||
- ../../samba-ad/ansible/group_vars/all/vars.yml
|
|
||||||
tasks:
|
|
||||||
- name: 从共享清单选择 Incus 相关 RRset
|
|
||||||
ansible.builtin.set_fact:
|
|
||||||
incus_dns_records: "{{ homelab_dns.samba.records | selectattr('name', 'in', incus_dns_names) | selectattr('zone', 'equalto', 'ad.ddupan.top') | list }}"
|
|
||||||
- name: 确认每个名称恰有一个受管 A 记录
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- incus_dns_records | length == incus_dns_names | length
|
|
||||||
- incus_dns_records | map(attribute='type') | unique == ['A']
|
|
||||||
- name: 协调 A 记录
|
|
||||||
ddupan.homelab.samba_dns_record:
|
|
||||||
server: "{{ samba_ad_dc_ip }}"
|
|
||||||
zone: "{{ item.zone }}"
|
|
||||||
name: "{{ item.name }}"
|
|
||||||
type: "{{ item.type }}"
|
|
||||||
values: "{{ item['values'] }}"
|
|
||||||
state: present
|
|
||||||
exact: true
|
|
||||||
loop: "{{ incus_dns_records }}"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOLvzIxZhVRd9wEFWR/uCOx7b4HQEdPDiZd8LCN7Hics panxiao81@laptop
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
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
|
|
||||||
=tHYp
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
# 显式升级版本,避免例行重跑意外升级虚拟化服务。
|
|
||||||
incus_package_version: '1:7.5.1-ubuntu24.04-202609250207'
|
|
||||||
incus_packages:
|
|
||||||
- incus
|
|
||||||
- incus-base
|
|
||||||
- incus-client
|
|
||||||
- incus-ui-canonical
|
|
||||||
|
|
||||||
# 先完成 IdP 准入限制,再配置 OIDC,最后开放 listener。
|
|
||||||
incus_server_config:
|
|
||||||
oidc.issuer: https://auth.ddupan.top
|
|
||||||
oidc.client.id: incus
|
|
||||||
oidc.audience: https://incus.ad.ddupan.top
|
|
||||||
oidc.scopes: openid,offline_access
|
|
||||||
core.https_address: 192.168.10.127:8443
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
all:
|
|
||||||
children:
|
|
||||||
incus_hosts:
|
|
||||||
hosts:
|
|
||||||
laptop:
|
|
||||||
ansible_connection: local
|
|
||||||
ansible_python_interpreter: /usr/bin/python3
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
---
|
|
||||||
- name: 安装 laptop 的 Incus 基础服务
|
|
||||||
hosts: incus_hosts
|
|
||||||
become: true
|
|
||||||
gather_facts: true
|
|
||||||
tasks:
|
|
||||||
- name: 限定已验证的平台
|
|
||||||
ansible.builtin.assert:
|
|
||||||
that:
|
|
||||||
- ansible_facts['distribution'] == 'Ubuntu'
|
|
||||||
- ansible_facts['distribution_release'] == 'noble'
|
|
||||||
- ansible_facts['architecture'] == 'x86_64'
|
|
||||||
fail_msg: 当前包版本只针对 Ubuntu 24.04 amd64 验证。
|
|
||||||
|
|
||||||
- name: 创建 APT 公钥目录
|
|
||||||
ansible.builtin.file:
|
|
||||||
path: /etc/apt/keyrings
|
|
||||||
state: directory
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0755'
|
|
||||||
|
|
||||||
- name: 安装已核对指纹的 Zabbly 公钥
|
|
||||||
ansible.builtin.copy:
|
|
||||||
src: zabbly.asc
|
|
||||||
dest: /etc/apt/keyrings/zabbly.asc
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
register: incus_key
|
|
||||||
|
|
||||||
- name: 声明 Zabbly stable 软件源
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: |
|
|
||||||
Enabled: yes
|
|
||||||
Types: deb
|
|
||||||
URIs: https://pkgs.zabbly.com/incus/stable
|
|
||||||
Suites: noble
|
|
||||||
Components: main
|
|
||||||
Architectures: amd64
|
|
||||||
Signed-By: /etc/apt/keyrings/zabbly.asc
|
|
||||||
dest: /etc/apt/sources.list.d/zabbly-incus-stable.sources
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
register: incus_source
|
|
||||||
|
|
||||||
# 防止系统自动更新绕开版本声明;显式改版本后重跑才升级。
|
|
||||||
- name: 固定 Incus 包版本
|
|
||||||
ansible.builtin.copy:
|
|
||||||
content: |
|
|
||||||
Package: {{ incus_packages | join(' ') }}
|
|
||||||
Pin: version {{ incus_package_version }}
|
|
||||||
Pin-Priority: 1000
|
|
||||||
dest: /etc/apt/preferences.d/incus
|
|
||||||
owner: root
|
|
||||||
group: root
|
|
||||||
mode: '0644'
|
|
||||||
|
|
||||||
# 只刷新本组件源,避免其他服务仓库故障阻塞安装。
|
|
||||||
- name: 刷新 Incus 包索引
|
|
||||||
ansible.builtin.command:
|
|
||||||
argv:
|
|
||||||
- apt-get
|
|
||||||
- update
|
|
||||||
- -o
|
|
||||||
- Dir::Etc::sourcelist=sources.list.d/zabbly-incus-stable.sources
|
|
||||||
- -o
|
|
||||||
- Dir::Etc::sourceparts=-
|
|
||||||
- -o
|
|
||||||
- APT::Get::List-Cleanup=0
|
|
||||||
- -o
|
|
||||||
- APT::Update::Error-Mode=any
|
|
||||||
changed_when: false
|
|
||||||
register: incus_refresh
|
|
||||||
retries: 3
|
|
||||||
delay: 5
|
|
||||||
until: incus_refresh.rc == 0
|
|
||||||
when: not ansible_check_mode
|
|
||||||
|
|
||||||
- name: 安装固定版本且禁止移除既有包
|
|
||||||
ansible.builtin.apt:
|
|
||||||
name: "{{ incus_packages | map('regex_replace', '$', '=' ~ incus_package_version) | list }}"
|
|
||||||
state: present
|
|
||||||
install_recommends: false
|
|
||||||
fail_on_autoremove: true
|
|
||||||
lock_timeout: 120
|
|
||||||
environment:
|
|
||||||
# 不让 needrestart 顺带重启 k3s、libvirt 等无关服务。
|
|
||||||
NEEDRESTART_MODE: l
|
|
||||||
register: incus_install
|
|
||||||
retries: 3
|
|
||||||
delay: 5
|
|
||||||
until: incus_install is succeeded
|
|
||||||
# check 模式不会创建新源,APT 无法解析只在新源存在的版本。
|
|
||||||
when: not (ansible_check_mode and (incus_source.changed or incus_key.changed))
|
|
||||||
|
|
||||||
- name: 提示首次 check 的包验证边界
|
|
||||||
ansible.builtin.debug:
|
|
||||||
msg: 软件源或公钥尚待写入,本次仅预览仓库配置;安装后须重跑 check 和幂等验证。
|
|
||||||
when: ansible_check_mode and (incus_source.changed or incus_key.changed)
|
|
||||||
|
|
||||||
- name: 启用 Incus 本地 socket
|
|
||||||
ansible.builtin.systemd_service:
|
|
||||||
name: incus.socket
|
|
||||||
enabled: true
|
|
||||||
state: started
|
|
||||||
when: not ansible_check_mode
|
|
||||||
|
|
||||||
- name: 启动 Incus 服务
|
|
||||||
ansible.builtin.systemd_service:
|
|
||||||
name: incus.service
|
|
||||||
state: started
|
|
||||||
when: not ansible_check_mode
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: incus
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: incus
|
|
||||||
namespace: incus
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- name: https
|
|
||||||
port: 8443
|
|
||||||
targetPort: 8443
|
|
||||||
---
|
|
||||||
apiVersion: discovery.k8s.io/v1
|
|
||||||
kind: EndpointSlice
|
|
||||||
metadata:
|
|
||||||
name: incus-laptop
|
|
||||||
namespace: incus
|
|
||||||
labels:
|
|
||||||
kubernetes.io/service-name: incus
|
|
||||||
endpointslice.kubernetes.io/managed-by: homelab-ansible
|
|
||||||
addressType: IPv4
|
|
||||||
ports:
|
|
||||||
- name: https
|
|
||||||
protocol: TCP
|
|
||||||
port: 8443
|
|
||||||
endpoints:
|
|
||||||
- addresses: [192.168.10.127]
|
|
||||||
conditions:
|
|
||||||
ready: true
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: incus-backend-ca
|
|
||||||
namespace: incus
|
|
||||||
data:
|
|
||||||
ca.crt: |
|
|
||||||
{{ incus_backend_certificate.content | b64decode | indent(4, true) }}
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1alpha3
|
|
||||||
kind: BackendTLSPolicy
|
|
||||||
metadata:
|
|
||||||
name: incus
|
|
||||||
namespace: incus
|
|
||||||
spec:
|
|
||||||
targetRefs:
|
|
||||||
- group: ''
|
|
||||||
kind: Service
|
|
||||||
name: incus
|
|
||||||
validation:
|
|
||||||
hostname: laptop
|
|
||||||
caCertificateRefs:
|
|
||||||
- group: ''
|
|
||||||
kind: ConfigMap
|
|
||||||
name: incus-backend-ca
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: incus
|
|
||||||
namespace: incus
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- group: gateway.networking.k8s.io
|
|
||||||
kind: Gateway
|
|
||||||
name: eg
|
|
||||||
namespace: envoy-gateway-system
|
|
||||||
sectionName: https
|
|
||||||
hostnames: [incus.ad.ddupan.top]
|
|
||||||
rules:
|
|
||||||
- matches:
|
|
||||||
- path:
|
|
||||||
type: PathPrefix
|
|
||||||
value: /
|
|
||||||
backendRefs:
|
|
||||||
- group: ''
|
|
||||||
kind: Service
|
|
||||||
name: incus
|
|
||||||
port: 8443
|
|
||||||
weight: 1
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: incus-http
|
|
||||||
namespace: incus
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- group: gateway.networking.k8s.io
|
|
||||||
kind: Gateway
|
|
||||||
name: eg
|
|
||||||
namespace: envoy-gateway-system
|
|
||||||
sectionName: http
|
|
||||||
hostnames: [incus.ad.ddupan.top]
|
|
||||||
rules:
|
|
||||||
- matches:
|
|
||||||
- path:
|
|
||||||
type: PathPrefix
|
|
||||||
value: /
|
|
||||||
filters:
|
|
||||||
- type: RequestRedirect
|
|
||||||
requestRedirect:
|
|
||||||
scheme: https
|
|
||||||
port: 443
|
|
||||||
statusCode: 301
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""只协调 Incus client/policy,保留线上其他客户端、秘密和 claims 配置。"""
|
|
||||||
import argparse
|
|
||||||
import copy
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
|
|
||||||
def merge(values, desired):
|
|
||||||
result = copy.deepcopy(values)
|
|
||||||
oidc = result['configMap']['identity_providers']['oidc']
|
|
||||||
clients = oidc.setdefault('clients', [])
|
|
||||||
for client in desired['clients']:
|
|
||||||
matches = [i for i, old in enumerate(clients) if old['client_id'] == client['client_id']]
|
|
||||||
if len(matches) > 1:
|
|
||||||
raise ValueError('发现重复 client_id,拒绝自动覆盖')
|
|
||||||
if matches:
|
|
||||||
clients[matches[0]] = copy.deepcopy(client)
|
|
||||||
else:
|
|
||||||
clients.append(copy.deepcopy(client))
|
|
||||||
oidc.setdefault('authorization_policies', {}).update(desired['authorization_policies'])
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def run(args):
|
|
||||||
# Helm 输出可能含秘密:只在内存处理,错误不回显正文。
|
|
||||||
result = subprocess.run(args, capture_output=True, text=True)
|
|
||||||
if result.returncode:
|
|
||||||
raise RuntimeError(f'{args[0]} {args[1]} 失败(退出码 {result.returncode});未输出可能含秘密的响应')
|
|
||||||
return result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument('--desired', required=True)
|
|
||||||
parser.add_argument('--check', action='store_true')
|
|
||||||
args = parser.parse_args()
|
|
||||||
release = json.loads(run(['helm', 'list', '-n', 'authelia', '-o', 'json']))
|
|
||||||
current = next(x for x in release if x['name'] == 'authelia')
|
|
||||||
if current['chart'] != 'authelia-0.11.6' or current['status'] != 'deployed':
|
|
||||||
raise RuntimeError('仅验证 authelia-0.11.6 且 release 必须 deployed;请先审阅版本变化')
|
|
||||||
before = json.loads(run(['helm', 'get', 'values', 'authelia', '-n', 'authelia', '-o', 'json']))
|
|
||||||
desired = yaml.safe_load(Path(args.desired).read_text())
|
|
||||||
after = merge(before, desired)
|
|
||||||
if before == after:
|
|
||||||
print('changed=false: Incus client/policy 已收敛')
|
|
||||||
return
|
|
||||||
|
|
||||||
# 目录 0700,文件 0600,退出即清理;既有秘密不写入仓库或日志。
|
|
||||||
with tempfile.TemporaryDirectory(prefix='incus-authelia-') as directory:
|
|
||||||
root = Path(directory)
|
|
||||||
path = root / 'values.json'
|
|
||||||
path.touch(mode=0o600)
|
|
||||||
path.write_text(json.dumps(after))
|
|
||||||
run(['helm', 'pull', 'authelia/authelia', '--version', '0.11.6', '--destination', directory])
|
|
||||||
chart = str(root / 'authelia-0.11.6.tgz')
|
|
||||||
run(['helm', 'template', 'authelia', chart, '-n', 'authelia', '-f', str(path)])
|
|
||||||
if args.check:
|
|
||||||
print('changed=true: 将只更新 Incus client/policy;固定 chart 渲染通过')
|
|
||||||
return
|
|
||||||
latest = next(x for x in json.loads(run(['helm', 'list', '-n', 'authelia', '-o', 'json'])) if x['name'] == 'authelia')
|
|
||||||
if latest['revision'] != current['revision'] or latest['status'] != 'deployed':
|
|
||||||
raise RuntimeError('Authelia release 在准备期间变化,请重试,避免覆盖并发修改')
|
|
||||||
run(['helm', 'upgrade', 'authelia', chart, '-n', 'authelia', '--reuse-values',
|
|
||||||
'-f', str(path), '--atomic', '--timeout', '5m', '--history-max', '10'])
|
|
||||||
print('changed=true: Incus client/policy 已部署,其他值保留')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import unittest
|
|
||||||
|
|
||||||
from reconcile_authelia import merge
|
|
||||||
|
|
||||||
|
|
||||||
class MergeTests(unittest.TestCase):
|
|
||||||
def test_preserves_unrelated_state_and_converges(self):
|
|
||||||
before = {'configMap': {'identity_providers': {'oidc': {
|
|
||||||
'clients': [{'client_id': 'hydra', 'client_secret': 'test-only'}],
|
|
||||||
'claims_policies': {'existing': {'id_token': ['email']}},
|
|
||||||
'authorization_policies': {'existing': {'default_policy': 'two_factor'}},
|
|
||||||
}}}}
|
|
||||||
desired = {'clients': [{'client_id': 'incus', 'public': True}],
|
|
||||||
'authorization_policies': {'incus_admin': {'default_policy': 'deny'}}}
|
|
||||||
after = merge(before, desired)
|
|
||||||
oidc = after['configMap']['identity_providers']['oidc']
|
|
||||||
self.assertEqual(oidc['clients'][0], before['configMap']['identity_providers']['oidc']['clients'][0])
|
|
||||||
self.assertIn('existing', oidc['claims_policies'])
|
|
||||||
self.assertIn('existing', oidc['authorization_policies'])
|
|
||||||
self.assertEqual(len(before['configMap']['identity_providers']['oidc']['clients']), 1)
|
|
||||||
self.assertEqual(merge(after, desired), after)
|
|
||||||
desired['clients'][0]['public'] = False
|
|
||||||
self.assertFalse(merge(after, desired)['configMap']['identity_providers']['oidc']['clients'][1]['public'])
|
|
||||||
|
|
||||||
def test_rejects_duplicate_identity(self):
|
|
||||||
before = {'configMap': {'identity_providers': {'oidc': {'clients': [{'client_id': 'incus'}, {'client_id': 'incus'}]}}}}
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
merge(before, {'clients': [{'client_id': 'incus'}], 'authorization_policies': {}})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
# This file is maintained automatically by "terraform init".
|
|
||||||
# Manual edits may be lost in future updates.
|
|
||||||
|
|
||||||
provider "registry.terraform.io/lxc/incus" {
|
|
||||||
version = "1.2.0"
|
|
||||||
constraints = "1.2.0"
|
|
||||||
hashes = [
|
|
||||||
"h1:9G5MaYQY9mKIpO341aG6f0Bt46DFD/bssrtNB+r861U=",
|
|
||||||
"zh:3be797962ed009eedcd6badb2cce1f707345032d48814f050f2103f00eba0177",
|
|
||||||
"zh:53fd1bf8685ef140372ab3282267fba38219dd2351efa723888a80aa41aa9367",
|
|
||||||
"zh:59a9f9bd027380346b8ebc09da7c98c7ef5aed0783d33aa3d3c4f51ca2fafd0f",
|
|
||||||
"zh:65ca786dd942c068b5953e7bd92c6813b1aeeb8a381da7cc96c45a846f3a5965",
|
|
||||||
"zh:6dd3262124c41f8a416cbd1c289dbf1ab9bf9116fc3f3be4714971272811926a",
|
|
||||||
"zh:8cce2da3db95f1088e02bf7ee68d9cacd55bf7b12dd0dd1e61165d5e5432e38b",
|
|
||||||
"zh:a637c5299aeed3984a0b39b45f4bac026d701a6cc203dff05a82a4f43027f37c",
|
|
||||||
"zh:a9017e39f3e8d2dbbfbbc1c6d663d22465b783d1c0e9a6e3ab324b497d0b14ed",
|
|
||||||
"zh:c3954bf1f4796a529dd76f5617f63f570dee76e9b7c17b12416e3afb059314ba",
|
|
||||||
"zh:dd5a081a9778794d89fa54ccddf4287550e9522d6e69b5e777859857809c9d20",
|
|
||||||
"zh:fa95ea158d045386be416cf9146c74a5e4b3fe5fd85850e1b8f6d2977c45bc9b",
|
|
||||||
"zh:fbe8006406da07ba0c40282050cd34343a584d028890c8601592414044abab7e",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_version = ">= 1.10.0"
|
|
||||||
required_providers {
|
|
||||||
incus = {
|
|
||||||
source = "lxc/incus"
|
|
||||||
version = "1.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "incus" {
|
|
||||||
default_remote = "local"
|
|
||||||
remote {
|
|
||||||
name = "local"
|
|
||||||
address = "unix://"
|
|
||||||
}
|
|
||||||
remote {
|
|
||||||
name = "images"
|
|
||||||
address = "https://images.linuxcontainers.org"
|
|
||||||
protocol = "simplestreams"
|
|
||||||
public = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 专用子 dataset,不接管整个宿主 data 池。
|
|
||||||
resource "incus_storage_pool" "ayatori" {
|
|
||||||
name = "ayatori"
|
|
||||||
driver = "zfs"
|
|
||||||
config = {
|
|
||||||
source = "data/incus-ayatori"
|
|
||||||
}
|
|
||||||
lifecycle {
|
|
||||||
prevent_destroy = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 地址只在 records.yml 声明一次,AD DNS(ansible/dns.yml)与此处同源读取;
|
|
||||||
# 须位于 NEC IX DHCP 池(.128–.250)之外,池由路由器手工维护,无法声明 reservation。
|
|
||||||
locals {
|
|
||||||
ayatori_ipv4 = {
|
|
||||||
for r in yamldecode(file("${path.module}/../../dns/records.yml")).homelab_dns.samba.records :
|
|
||||||
trimprefix(r.name, "ayatori-") => r.values[0]
|
|
||||||
if r.zone == "ad.ddupan.top" && startswith(r.name, "ayatori-")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 镜像不含 openssh-server。不设 ssh_pwauth:它会在装包前写出残缺的 sshd_config,
|
|
||||||
# 使包自带的默认配置(UsePAM yes、Include sshd_config.d)无法落地,锁定密码的账号随即被拒。
|
|
||||||
# 改为装包后(defer)再写 drop-in,只覆盖需要收紧的项。
|
|
||||||
locals {
|
|
||||||
ayatori_cloud_config = {
|
|
||||||
manage_etc_hosts = true
|
|
||||||
disable_root = true
|
|
||||||
users = [{
|
|
||||||
name = "panxiao81"
|
|
||||||
groups = ["sudo"]
|
|
||||||
shell = "/bin/bash"
|
|
||||||
sudo = ["ALL=(ALL) NOPASSWD:ALL"]
|
|
||||||
lock_passwd = true
|
|
||||||
ssh_authorized_keys = [trimspace(file("${path.module}/../ansible/files/panxiao81.pub"))]
|
|
||||||
}]
|
|
||||||
# WAN 随机掉线,装包须重试。
|
|
||||||
apt = { conf = "Acquire::Retries \"5\";" }
|
|
||||||
package_update = true
|
|
||||||
packages = ["openssh-server"]
|
|
||||||
write_files = [{
|
|
||||||
path = "/etc/ssh/sshd_config.d/60-homelab.conf"
|
|
||||||
defer = true
|
|
||||||
content = <<-EOT
|
|
||||||
PasswordAuthentication no
|
|
||||||
KbdInteractiveAuthentication no
|
|
||||||
PermitRootLogin no
|
|
||||||
EOT
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "incus_instance" "ayatori" {
|
|
||||||
for_each = toset(["dev", "prod"])
|
|
||||||
name = "ayatori-${each.key}"
|
|
||||||
description = "Ayatori ${each.key} 基础容器;应用由独立部署流程管理"
|
|
||||||
# 跟随 24.04 cloud 最新构建:同一发行版的构建只差安全更新,固定指纹会随上游下架而无法重建。
|
|
||||||
image = "images:ubuntu/24.04/cloud"
|
|
||||||
type = "container"
|
|
||||||
profiles = []
|
|
||||||
running = true
|
|
||||||
config = {
|
|
||||||
"boot.autostart" = "true"
|
|
||||||
"security.privileged" = "false"
|
|
||||||
"security.nesting" = "false"
|
|
||||||
"limits.cpu" = "2"
|
|
||||||
"limits.memory" = "2GiB"
|
|
||||||
"limits.memory.swap" = "false"
|
|
||||||
# ⚠ 镜像模板只在 create/copy 时渲染 cloud-init seed(when: [create, copy]),
|
|
||||||
# 对已有实例修改此键不会生效,重启或 cloud-init clean 也不会;改地址须重建实例。
|
|
||||||
# 网关与 resolver 沿用 LAN DHCP 下发值:.1 网关;Blocky .127 优先、路由器 .1 兜底。
|
|
||||||
"cloud-init.network-config" = yamlencode({
|
|
||||||
version = 2
|
|
||||||
ethernets = {
|
|
||||||
eth0 = {
|
|
||||||
addresses = ["${local.ayatori_ipv4[each.key]}/24"]
|
|
||||||
routes = [{ to = "default", via = "192.168.10.1" }]
|
|
||||||
nameservers = { addresses = ["192.168.10.127", "192.168.10.1"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
"cloud-init.user-data" = "#cloud-config\n${yamlencode(merge(local.ayatori_cloud_config, {
|
|
||||||
hostname = "ayatori-${each.key}"
|
|
||||||
}))}"
|
|
||||||
}
|
|
||||||
device {
|
|
||||||
name = "root"
|
|
||||||
type = "disk"
|
|
||||||
properties = {
|
|
||||||
path = "/"
|
|
||||||
pool = incus_storage_pool.ayatori.name
|
|
||||||
size = "20GiB"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device {
|
|
||||||
name = "eth0"
|
|
||||||
type = "nic"
|
|
||||||
properties = {
|
|
||||||
name = "eth0"
|
|
||||||
nictype = "bridged"
|
|
||||||
parent = "br0"
|
|
||||||
hwaddr = each.key == "dev" ? "02:16:3e:aa:00:01" : "02:16:3e:aa:00:02"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wait_for {
|
|
||||||
type = "ipv4"
|
|
||||||
nic = "eth0"
|
|
||||||
}
|
|
||||||
lifecycle {
|
|
||||||
# 镜像只在创建时使用;provider 按字符串比较,改写它不应触发重建现有实例。
|
|
||||||
ignore_changes = [image]
|
|
||||||
prevent_destroy = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output "containers" {
|
|
||||||
value = { for env, instance in incus_instance.ayatori : env => {
|
|
||||||
name = instance.name
|
|
||||||
ipv4 = instance.ipv4_address
|
|
||||||
mac = instance.mac_address
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# SecretStore 由独立控制面的部署管理,必须只读下述两个管理凭据路径。
|
|
||||||
apiVersion: external-secrets.io/v1
|
|
||||||
kind: ExternalSecret
|
|
||||||
metadata:
|
|
||||||
name: homelab-postgresql-prod-admin
|
|
||||||
spec:
|
|
||||||
refreshInterval: 1h
|
|
||||||
secretStoreRef:
|
|
||||||
name: homelab-postgresql-admin
|
|
||||||
kind: SecretStore
|
|
||||||
target:
|
|
||||||
name: homelab-postgresql-prod-admin
|
|
||||||
creationPolicy: Owner
|
|
||||||
data:
|
|
||||||
- secretKey: username
|
|
||||||
remoteRef:
|
|
||||||
key: infra/postgresql/ayatori/prod
|
|
||||||
property: username
|
|
||||||
- secretKey: password
|
|
||||||
remoteRef:
|
|
||||||
key: infra/postgresql/ayatori/prod
|
|
||||||
property: password
|
|
||||||
---
|
|
||||||
# SecretStore 由独立控制面的部署管理,必须只读下述两个管理凭据路径。
|
|
||||||
apiVersion: external-secrets.io/v1
|
|
||||||
kind: ExternalSecret
|
|
||||||
metadata:
|
|
||||||
name: homelab-postgresql-dev-admin
|
|
||||||
spec:
|
|
||||||
refreshInterval: 1h
|
|
||||||
secretStoreRef:
|
|
||||||
name: homelab-postgresql-admin
|
|
||||||
kind: SecretStore
|
|
||||||
target:
|
|
||||||
name: homelab-postgresql-dev-admin
|
|
||||||
creationPolicy: Owner
|
|
||||||
data:
|
|
||||||
- secretKey: username
|
|
||||||
remoteRef:
|
|
||||||
key: infra/postgresql/ayatori/dev
|
|
||||||
property: username
|
|
||||||
- secretKey: password
|
|
||||||
remoteRef:
|
|
||||||
key: infra/postgresql/ayatori/dev
|
|
||||||
property: password
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDMzCCAhugAwIBAgIUMs0iV657yC9UhA2p2vomLIbFnzgwDQYJKoZIhvcNAQEL
|
|
||||||
BQAwITEfMB0GA1UEAxMWZGR1cGFuLnRvcCBJbnRlcm5hbCBDQTAeFw0yNjA3MjQy
|
|
||||||
MDE1MDFaFw0zNjA3MjEyMDE1MzFaMCExHzAdBgNVBAMTFmRkdXBhbi50b3AgSW50
|
|
||||||
ZXJuYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6QWlwBe6f
|
|
||||||
t7Ca3KCTvr4Pz+jVO60WrMBoEDYYM8Mp04btBHzhAQHf9Pp8+15aEW9iUcQhqqm+
|
|
||||||
2vT6H0JEhIbplyCWY6Guv0mTu8f+lvFknJIl2b3JqnMLHJKjh/rBrsE12XZ3i17M
|
|
||||||
2tCr34BWcei85IZyQl5HMW6dB8lAE6bdom+YynK4oLJdej9DD6bSyM8WcL0OsneZ
|
|
||||||
NsjwOlNMy3zjbtaH6mH71SgbFinxLp3AAAuLVe1DIKhFxuTQeVr/WaPum5y/oOsc
|
|
||||||
0gJp9If6nsC33lpRGcPLiZE9kfFZa4fPe8laCaN8q1K253qZ0rjRiDhbTAppW4Fy
|
|
||||||
r5P67h+2D+TbAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
|
|
||||||
AQH/MB0GA1UdDgQWBBSOgk1fR0qhz/Bo4wD9g2BnOAzDXzAfBgNVHSMEGDAWgBSO
|
|
||||||
gk1fR0qhz/Bo4wD9g2BnOAzDXzANBgkqhkiG9w0BAQsFAAOCAQEANm5kKkts1Ar2
|
|
||||||
7IlS+TxLFrZ/C9yhIdGcBk2SL5E+5E8S3skQWLEPGLRwvV4RmiB8gQ2V6UyGLrCx
|
|
||||||
1MuuSmCDaSYL9G66sGX1MIHlQ0F0bHIOxxtsTwIYzb5Sl8h3MfsARabmOhE3xUkn
|
|
||||||
jaAT9YUweHhjF4vi0U1Q4F8oOSvu4eJp5dMx1r7b2bLN90A1xh9sfdkEenSBX0tm
|
|
||||||
xK82ROYXI2Ejv/EO+lPUIn3jfqbqrS2itw75Xz/ECHjIfSxvW98puP69U54a1gf6
|
|
||||||
gWdXslr0pGkyMHqxw4dmaecpK0QK3jvqCNycNwNBfMdCypS2QRy03adcUosEAP3O
|
|
||||||
LZU7Kd8aeg==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
common/ca.crt
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDMzCCAhugAwIBAgIUMs0iV657yC9UhA2p2vomLIbFnzgwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwITEfMB0GA1UEAxMWZGR1cGFuLnRvcCBJbnRlcm5hbCBDQTAeFw0yNjA3MjQy
|
||||||
|
MDE1MDFaFw0zNjA3MjEyMDE1MzFaMCExHzAdBgNVBAMTFmRkdXBhbi50b3AgSW50
|
||||||
|
ZXJuYWwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6QWlwBe6f
|
||||||
|
t7Ca3KCTvr4Pz+jVO60WrMBoEDYYM8Mp04btBHzhAQHf9Pp8+15aEW9iUcQhqqm+
|
||||||
|
2vT6H0JEhIbplyCWY6Guv0mTu8f+lvFknJIl2b3JqnMLHJKjh/rBrsE12XZ3i17M
|
||||||
|
2tCr34BWcei85IZyQl5HMW6dB8lAE6bdom+YynK4oLJdej9DD6bSyM8WcL0OsneZ
|
||||||
|
NsjwOlNMy3zjbtaH6mH71SgbFinxLp3AAAuLVe1DIKhFxuTQeVr/WaPum5y/oOsc
|
||||||
|
0gJp9If6nsC33lpRGcPLiZE9kfFZa4fPe8laCaN8q1K253qZ0rjRiDhbTAppW4Fy
|
||||||
|
r5P67h+2D+TbAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
|
||||||
|
AQH/MB0GA1UdDgQWBBSOgk1fR0qhz/Bo4wD9g2BnOAzDXzAfBgNVHSMEGDAWgBSO
|
||||||
|
gk1fR0qhz/Bo4wD9g2BnOAzDXzANBgkqhkiG9w0BAQsFAAOCAQEANm5kKkts1Ar2
|
||||||
|
7IlS+TxLFrZ/C9yhIdGcBk2SL5E+5E8S3skQWLEPGLRwvV4RmiB8gQ2V6UyGLrCx
|
||||||
|
1MuuSmCDaSYL9G66sGX1MIHlQ0F0bHIOxxtsTwIYzb5Sl8h3MfsARabmOhE3xUkn
|
||||||
|
jaAT9YUweHhjF4vi0U1Q4F8oOSvu4eJp5dMx1r7b2bLN90A1xh9sfdkEenSBX0tm
|
||||||
|
xK82ROYXI2Ejv/EO+lPUIn3jfqbqrS2itw75Xz/ECHjIfSxvW98puP69U54a1gf6
|
||||||
|
gWdXslr0pGkyMHqxw4dmaecpK0QK3jvqCNycNwNBfMdCypS2QRy03adcUosEAP3O
|
||||||
|
LZU7Kd8aeg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
# 公共部分只含公开 CA;接入时必须选择 dev/ 或 prod/,不提供跨环境聚合入口。
|
||||||
|
configMapGenerator:
|
||||||
|
- name: homelab-postgresql-ca
|
||||||
|
namespace: ayatori-system
|
||||||
|
files: [ca.crt]
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: homelab-postgresql-dev-admin
|
||||||
|
namespace: ayatori-system
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: homelab-postgresql-dev-admin
|
||||||
|
kind: SecretStore
|
||||||
|
target:
|
||||||
|
name: homelab-postgresql-dev-admin
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: username
|
||||||
|
remoteRef:
|
||||||
|
key: infra/postgresql/ayatori/dev
|
||||||
|
property: username
|
||||||
|
- secretKey: password
|
||||||
|
remoteRef:
|
||||||
|
key: infra/postgresql/ayatori/dev
|
||||||
|
property: password
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 仅交付本环境管理凭据;由控制面部署流程绑定专用 ESO 身份。
|
||||||
|
path "kv/data/infra/postgresql/ayatori/dev" { capabilities = ["read"] }
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: database.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: PostgreSQLInstance
|
||||||
|
metadata:
|
||||||
|
name: homelab-dev
|
||||||
|
spec:
|
||||||
|
endpoint:
|
||||||
|
host: pg-dev.ad.ddupan.top
|
||||||
|
hostaddr: 192.168.10.127
|
||||||
|
port: 5433
|
||||||
|
database: postgres
|
||||||
|
sslMode: verify-full
|
||||||
|
adminCredentialRef:
|
||||||
|
name: homelab-postgresql-dev-admin
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../common
|
||||||
|
- instance.yaml
|
||||||
|
- admin-credentials.yaml
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# 供未来独立控制面管理 SecretStore 的 ESO 身份绑定;本文件尚未应用。
|
|
||||||
path "kv/data/infra/postgresql/ayatori/prod" { capabilities = ["read"] }
|
|
||||||
path "kv/data/infra/postgresql/ayatori/dev" { capabilities = ["read"] }
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# 尚未 apply:须先准备管理 Secret、CA bundle 和角色感知的生产稳定入口。
|
|
||||||
apiVersion: database.ayatori.ddupan.top/v1alpha1
|
|
||||||
kind: PostgreSQLInstance
|
|
||||||
metadata:
|
|
||||||
name: homelab-prod
|
|
||||||
spec:
|
|
||||||
endpoint:
|
|
||||||
host: pg-prod.ad.ddupan.top
|
|
||||||
hostaddr: 192.168.10.2
|
|
||||||
port: 5432
|
|
||||||
database: postgres
|
|
||||||
sslMode: verify-full
|
|
||||||
adminCredentialRef:
|
|
||||||
name: homelab-postgresql-prod-admin
|
|
||||||
---
|
|
||||||
apiVersion: database.ayatori.ddupan.top/v1alpha1
|
|
||||||
kind: PostgreSQLInstance
|
|
||||||
metadata:
|
|
||||||
name: homelab-dev
|
|
||||||
spec:
|
|
||||||
endpoint:
|
|
||||||
host: pg-dev.ad.ddupan.top
|
|
||||||
hostaddr: 192.168.10.127
|
|
||||||
port: 5433
|
|
||||||
database: postgres
|
|
||||||
sslMode: verify-full
|
|
||||||
adminCredentialRef:
|
|
||||||
name: homelab-postgresql-dev-admin
|
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
kind: Kustomization
|
kind: Kustomization
|
||||||
# 仅供未来独立 Ayatori 控制面;此 namespace 必须与 manager 的 Secret namespace 一致。
|
# 公共入口不注册实例或同步凭据;必须显式选择 dev/ 或 prod/。
|
||||||
namespace: ayatori-system
|
|
||||||
resources:
|
resources:
|
||||||
- instances.yaml
|
- common
|
||||||
- admin-credentials.yaml
|
|
||||||
configMapGenerator:
|
|
||||||
- name: homelab-postgresql-ca
|
|
||||||
files: [ca.crt]
|
|
||||||
generatorOptions:
|
|
||||||
disableNameSuffixHash: true
|
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: homelab-postgresql-prod-admin
|
||||||
|
namespace: ayatori-system
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: homelab-postgresql-prod-admin
|
||||||
|
kind: SecretStore
|
||||||
|
target:
|
||||||
|
name: homelab-postgresql-prod-admin
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: username
|
||||||
|
remoteRef:
|
||||||
|
key: infra/postgresql/ayatori/prod
|
||||||
|
property: username
|
||||||
|
- secretKey: password
|
||||||
|
remoteRef:
|
||||||
|
key: infra/postgresql/ayatori/prod
|
||||||
|
property: password
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 仅交付本环境管理凭据;由控制面部署流程绑定专用 ESO 身份。
|
||||||
|
path "kv/data/infra/postgresql/ayatori/prod" { capabilities = ["read"] }
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: database.ayatori.ddupan.top/v1alpha1
|
||||||
|
kind: PostgreSQLInstance
|
||||||
|
metadata:
|
||||||
|
name: homelab-prod
|
||||||
|
spec:
|
||||||
|
endpoint:
|
||||||
|
host: pg-prod.ad.ddupan.top
|
||||||
|
hostaddr: 192.168.10.2
|
||||||
|
port: 5432
|
||||||
|
database: postgres
|
||||||
|
sslMode: verify-full
|
||||||
|
adminCredentialRef:
|
||||||
|
name: homelab-postgresql-prod-admin
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- ../common
|
||||||
|
- instance.yaml
|
||||||
|
- admin-credentials.yaml
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"""校验渲染后的环境隔离与引用关系,无网络、集群写入或凭据访问。"""
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1] / 'ayatori'
|
||||||
|
|
||||||
|
|
||||||
|
def render(environment):
|
||||||
|
return list(yaml.safe_load_all(subprocess.check_output(
|
||||||
|
['kubectl', 'kustomize', str(ROOT / environment)], text=True)))
|
||||||
|
|
||||||
|
|
||||||
|
class AyatoriManifestTests(unittest.TestCase):
|
||||||
|
def test_common_entry_cannot_register_instances_or_sync_credentials(self):
|
||||||
|
objects = render('.')
|
||||||
|
self.assertEqual([x['kind'] for x in objects], ['ConfigMap'])
|
||||||
|
self.assertEqual(objects[0]['metadata']['namespace'], 'ayatori-system')
|
||||||
|
|
||||||
|
def test_environment_isolation_scope_and_references(self):
|
||||||
|
for env, address, port in [('dev', '192.168.10.127', 5433), ('prod', '192.168.10.2', 5432)]:
|
||||||
|
with self.subTest(environment=env):
|
||||||
|
objects = render(env)
|
||||||
|
self.assertEqual(sorted(x['kind'] for x in objects),
|
||||||
|
['ConfigMap', 'ExternalSecret', 'PostgreSQLInstance'])
|
||||||
|
instance = next(x for x in objects if x['kind'] == 'PostgreSQLInstance')
|
||||||
|
secret = next(x for x in objects if x['kind'] == 'ExternalSecret')
|
||||||
|
self.assertNotIn('namespace', instance['metadata'])
|
||||||
|
self.assertEqual(instance['metadata']['name'], 'homelab-' + env)
|
||||||
|
self.assertEqual(instance['spec']['endpoint'], {
|
||||||
|
'host': 'pg-' + env + '.ad.ddupan.top', 'hostaddr': address,
|
||||||
|
'port': port, 'database': 'postgres', 'sslMode': 'verify-full'})
|
||||||
|
self.assertEqual(secret['metadata']['namespace'], 'ayatori-system')
|
||||||
|
self.assertEqual(instance['spec']['adminCredentialRef']['name'], secret['spec']['target']['name'])
|
||||||
|
self.assertEqual(secret['spec']['secretStoreRef'],
|
||||||
|
{'name': 'homelab-postgresql-' + env + '-admin', 'kind': 'SecretStore'})
|
||||||
|
self.assertEqual({x['remoteRef']['key'] for x in secret['spec']['data']},
|
||||||
|
{'infra/postgresql/ayatori/' + env})
|
||||||
|
self.assertEqual({x['secretKey'] for x in secret['spec']['data']}, {'username', 'password'})
|
||||||
|
self.assertEqual({x['remoteRef']['property'] for x in secret['spec']['data']}, {'username', 'password'})
|
||||||
|
policy = (ROOT / env / 'eso-policy.hcl').read_text()
|
||||||
|
self.assertEqual(re.findall(r'path\s+"([^"]+)"', policy),
|
||||||
|
['kv/data/infra/postgresql/ayatori/' + env])
|
||||||
|
self.assertEqual(re.findall(r'capabilities\s*=\s*\[([^]]+)\]', policy), ['"read"'])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user