服务器操作指北(8)FreeIPA、K3s 部署与 HTTPS 证书配置
创建于 2026-09-19
更新于 2026-09-19
科技
k3s
freeipa
ipv6
podman
traefik
https
30967 字 · 约 104 分钟

前言

由于服务器上的用户和权限需要统一管理,故而部署 FreeIPA,通过 LDAP、Kerberos 和 SSSD 管理用户认证及 sudo 规则。同时,服务器上还运行着 Gitea、Nextcloud 等容器服务,使用 K3s 管理部署,并通过 Traefik 统一配置 HTTPS 入口。

FreeIPA 使用 Podman 部署,与 K3s 共用宿主机时,遇到了网页端口占用和容器转发规则的问题。另外,服务器使用 IPv6 访问外网,内部认证需要稳定地址,因此也配置了双栈网络和内网 DNS。本文按 FreeIPA、K3s、HTTPS 的部署顺序记录相关配置及问题处理。

操作环境为 Ubuntu 24.04,FreeIPA 版本为 4.12.2,K3s 版本为 v1.34.5+k3s1,Traefik 版本为 3.6.9。域名、地址、端口和自定义目录均已替换为示例值,使用时需自行修改。下文记录安装配置和遇到的问题。已有服务调整配置前,应先备份数据。

  • 20260919:补充网络准备、完整双栈容器参数、DNS 记录、转发规则、客户端入域及证书申请步骤。

本文以三台位于同一二层内网的 Ubuntu 24.04 为例:host-a 为控制和数据节点,host-bhost-c 为工作节点。命令默认在 host-a 执行,其他位置会另行标注。三台机器已安装系统、可通过 SSH 和 sudo 管理,数据盘已挂载到 /srv/example;分区、RAID 和数据迁移不在本文范围内。

准备一个自己持有、已在 DNSPod 托管的公网域名。example.com、邮箱和全部地址都需要替换;示例域名不能用于实际申请证书。网络应能拉取镜像、访问软件源、NTP、DNSPod API 和 ACME 服务。先完成第一至三节的内网部署,再配置公共证书,最后部署后续两篇的业务。

一、部署 FreeIPA

1.1 域名与网络准备

FreeIPA 提供用户、用户组、主机和权限管理,客户端通过 SSSD 获取身份及 sudo 策略,Kerberos 负责认证,DNS 提供服务发现。安装前应先确定域名与 Realm,后续客户端入域及服务证书都会使用这些名称。

以下采用一组示例参数:

配置项 示例值 说明
DNS 区域 lab.example.com 自行替换为实际管理的域名
Kerberos Realm LAB.EXAMPLE.COM 示例使用域名大写形式
IPA 服务名 ipa.lab.example.com FreeIPA 容器的完整主机名
宿主机名 host-a.lab.example.com 宿主机自身的客户端身份
内网 IPv4 192.168.50.10 供其他机器访问的稳定地址
内网 IPv6 fd12:3456:789a::10 示例 ULA,实际应自行规划

ipahost-a 可以解析到相同宿主地址,但两者保留独立名称。不要为了部署 IPA 把已入域宿主机的 hostname 改成 IPA 服务名。

三台机器的地址约定如下;网关为 192.168.50.1,网卡名示例为 eno1。ULA 前缀需自行规划,并确认不与 VPN、容器网络重叠。

节点 IPv4 IPv6 ULA hostname
控制和数据节点 192.168.50.10/24 fd12:3456:789a::10/64 host-a.lab.example.com
工作节点一 192.168.50.11/24 fd12:3456:789a::11/64 host-b.lab.example.com
工作节点二 192.168.50.12/24 fd12:3456:789a::12/64 host-c.lab.example.com

在每台机器先安装工具,并设置各自的主机名。以下为 host-a

bash
1
2
3
4
5
6
sudo apt update sudo apt install -y curl ca-certificates openssl dnsutils chrony jq iptables sudo hostnamectl set-hostname host-a.lab.example.com sudo systemctl enable --now chrony ip -br address sudo netplan get

下面以 Netplan + systemd-networkd 为例。编辑当前管理 eno1 的 Netplan 文件,将该接口配置调整为下列内容,保留其他接口。不要另写一份与现有接口配置冲突的文件;使用 NetworkManager 的机器应在其现有连接中设置同样的地址、DNS 和自动 IPv6。

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
network: version: 2 renderer: networkd ethernets: eno1: dhcp4: false addresses: - 192.168.50.10/24 - fd12:3456:789a::10/64 routes: - to: default via: 192.168.50.1 accept-ra: true nameservers: addresses: [192.168.50.1]

host-bhost-c 分别替换地址。公网 IPv6 地址和默认路由继续从路由器 RA 获得,内部认证使用固定 ULA;不手工填写动态公网前缀。操作网络时保留控制台或第二条连接,再检查并应用:

bash
1
2
3
4
5
6
sudo netplan generate sudo netplan try ip -br address show eno1 ip -6 route show default ping -c 3 192.168.50.1 chronyc tracking

netplan try 确认连通后按提示接受配置。若网络由 NetworkManager 管理,可用 nmcli connection show --active 找到连接名,再执行下面的等价修改;不要同时应用上面的 networkd 配置。将连接名、地址和主机名按各节点替换:

bash
1
2
3
4
5
sudo nmcli connection modify 'YOUR_CONNECTION' \ ipv4.method manual ipv4.addresses 192.168.50.10/24 \ ipv4.gateway 192.168.50.1 ipv4.dns 192.168.50.1 \ ipv6.method auto ipv6.addresses fd12:3456:789a::10/64 sudo nmcli connection up 'YOUR_CONNECTION'

三台机器的 /etc/hosts 中加入下列记录,删除同名的旧地址映射,并把本机 FQDN 放在对应行的第一个名称。保留 localhost 行:

text
1
2
3
192.168.50.10 host-a.lab.example.com host-a ipa.lab.example.com ipa 192.168.50.11 host-b.lab.example.com host-b 192.168.50.12 host-c.lab.example.com host-c

这仅用于安装前的引导,不代替后面的 DNS。执行 hostname -f 应返回本机 FQDN。宿主使用服务入口地址,容器的 hostname 则为 ipa.lab.example.com,其 /etc/hosts 由 Podman 按容器接口生成。

在三台机器上启用转发;从 RA 获取默认路由的接口使用 accept_ra=2。保存为 /etc/sysctl.d/90-example-k3s.conf

conf
1
2
3
net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1 net.ipv6.conf.eno1.accept_ra=2
bash
1
2
3
4
5
sudo sysctl --system ip -6 route show default # 从另外两台主机分别测试,不能只在本机测试 ping -c 3 192.168.50.10 ping -6 -c 3 fd12:3456:789a::10

默认使用 Flannel VXLAN。按 K3s 网络要求在已有防火墙中放行以下内网流量;不要开启另一套防火墙覆盖原规则:

来源 → 目标 协议/端口 用途
三台节点 → 控制节点 TCP 6443 集群 API 与节点加入
三台节点相互访问 UDP 8472、TCP 10250 VXLAN、节点指标
内网客户端与 Pod → IPA 宿主 TCP/UDP 53、88、464;TCP 389、636 DNS、Kerberos、LDAP
内网客户端 → 入口节点 TCP 80、443、8443 IPA、业务 HTTPS
Traefik 所在节点/Pod → IPA 宿主 TCP 18081、18444 IPA 网页后端

规则应同时覆盖计划使用的 IPv4 与 ULA,以及 Pod 网段。宿主和网关仍需允许必要 ICMPv6、返回流量与出站访问。本文假定这些内网流量可达;有 UFW、firewalld 或交换机 ACL 时,先在原规则中完成放行,再继续。第三节的专用规则只解决 IPA 容器的 FORWARD,不代替整套边界防火墙。

1.2 安装 Podman 与创建数据目录

容器部署参考 FreeIPA 官方容器说明。FreeIPA 容器内部通过 systemd 管理目录服务、Kerberos、HTTP 和 CA,本次使用 rootful Podman 运行。

bash
1
2
3
4
5
6
7
8
9
10
sudo apt update sudo apt install -y podman # 示例目录,自行修改为已经挂载的数据盘目录 sudo install -d -m 0700 /srv/example/ipa-data # almalinux-10 为镜像系列,不固定某次构建;部署时应记录镜像摘要 sudo podman pull quay.io/freeipa/freeipa-server:almalinux-10 sudo podman image inspect quay.io/freeipa/freeipa-server:almalinux-10 \ --format '{{.Id}} {{.RepoDigests}}'

AlmaLinux 10 镜像在 x86_64 上要求 CPU 支持 x86-64-v3,较老服务器应先确认镜像兼容性。不要为了修复网络访问问题直接更换基础系统大版本。

镜像标签会随构建更新,本文安装命令是按当前配置整理的首次部署示例,不代表重新执行了原始安装。/data 保存容器配置与身份数据,后续重建容器时继续挂载同一目录。已有数据目录不能当成一次全新安装重新初始化。

1.3 创建容器并初始化

为了给 K3s 的 Traefik 留出网页入口,这里直接把 FreeIPA 容器的 80、443 发布到宿主机高位端口。LDAP、Kerberos 和 DNS 仍使用各自的标准协议端口,并只绑定内网地址。

以下直接创建最终使用的双栈网络,宿主的 IPv4 与 ULA 必须已经存在。先检查 sudo ss -lntup,确认计划发布的端口未被其他服务占用。

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 子网不能与宿主、VPN、K3s Pod/Service 网络重叠 sudo podman network create --subnet 10.90.0.0/24 \ --ipv6 --subnet fd12:3456:789c::/64 ipa-dualstack # 明确容器使用自身 named,不依赖容器网络后端的 DNS 转发 sudo install -d -m 0755 /etc/example-ipa printf 'nameserver 127.0.0.1\n' | sudo tee /etc/example-ipa/resolv.conf >/dev/null # 首次交互安装;根据提示输入目录管理密码与 IPA admin 密码 sudo podman run --name ipa-server -it \ --hostname ipa.lab.example.com \ --network ipa-dualstack --ip 10.90.0.10 --ip6 fd12:3456:789c::10 \ --dns=none \ --add-host=ipa.lab.example.com:10.90.0.10 \ -e IPA_SERVER_IP=no-update \ --read-only \ -v /srv/example/ipa-data:/data:Z \ -v /etc/example-ipa/resolv.conf:/etc/resolv.conf:ro \ -p 192.168.50.10:18081:80/tcp \ -p 192.168.50.10:18444:443/tcp \ -p 192.168.50.10:389:389/tcp \ -p 192.168.50.10:636:636/tcp \ -p 192.168.50.10:88:88/tcp \ -p 192.168.50.10:88:88/udp \ -p 192.168.50.10:464:464/tcp \ -p 192.168.50.10:464:464/udp \ -p 192.168.50.10:53:53/tcp \ -p 192.168.50.10:53:53/udp \ -p '[fd12:3456:789a::10]:389:389/tcp' \ -p '[fd12:3456:789a::10]:636:636/tcp' \ -p '[fd12:3456:789a::10]:88:88/tcp' \ -p '[fd12:3456:789a::10]:88:88/udp' \ -p '[fd12:3456:789a::10]:464:464/tcp' \ -p '[fd12:3456:789a::10]:464:464/udp' \ -p '[fd12:3456:789a::10]:53:53/tcp' \ -p '[fd12:3456:789a::10]:53:53/udp' \ quay.io/freeipa/freeipa-server:almalinux-10 \ ipa-server-install \ --domain=lab.example.com \ --realm=LAB.EXAMPLE.COM \ --ip-address=10.90.0.10 \ --setup-dns --no-host-dns --no-reverse \ --forwarder=2606:4700:4700::1111 \ --forwarder=2606:4700:4700::1001 \ --no-ntp

本例一次安装身份服务和 integrated DNS。--ip-address 使用容器实际接口地址,稍后再把权威记录改为宿主入口;--no-reverse 表示不在这里创建反向区域。转发器必须能从容器访问,若本地不能访问示例公共 IPv6 DNS,应换成可达的递归 DNS。

Podman 文件与 DNS 参数,本例挂载只含 127.0.0.1 的解析文件,并用 --dns=none 停止 Podman 自动生成该文件,让容器查询自己的 named。--add-host 提前提供安装时的容器主机名映射,避免只读 hosts 被安装器修改;IPA_SERVER_IP=no-update 避免每次启动把对外记录改回容器地址。按安装提示确认域名,并分别设置 Directory Manager 与 IPA admin 密码。安装可能持续数分钟,看到成功提示后容器仍在前台运行,可按 Podman 的 Ctrl-PCtrl-Q 脱离终端,或另开终端检查:

bash
1
2
sudo podman exec ipa-server ipactl status sudo podman logs --tail 100 ipa-server

容器显示 Running 时,IPA 内部服务可能还在启动,应等待 ipactl status 中各服务正常。健康检查另外保存为文件,便于查看具体错误,而不只看被终端截断的输出:

bash
1
sudo podman exec ipa-server ipa-healthcheck > ~/ipa-healthcheck.json

Podman 的 rootful 与 rootless 容器分别管理。普通用户执行 podman ps 看不到 root 创建的容器,不能通过随意创建一个 podman 用户组来改变这一点。后续运维使用受控 sudo 权限,统一由 systemd 启动服务。

1.4 配置开机启动

对于上面已经创建的容器,可以使用下面的 systemd unit 管理启动和停止。若已有 Quadlet 或生成的 unit,应修改现有配置,避免两套服务重复启动同一容器。

ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# /etc/systemd/system/example-ipa.service [Unit] Description=FreeIPA container Wants=network-online.target After=network-online.target RequiresMountsFor=/srv/example/ipa-data [Service] Type=simple ExecStart=/usr/bin/podman start --attach ipa-server ExecStop=/usr/bin/podman stop --time 120 ipa-server Restart=on-failure TimeoutStopSec=150 [Install] WantedBy=multi-user.target

首次从前台安装切换到 systemd 管理时,先正常停止原容器,再加载服务:

bash
1
2
3
4
sudo podman stop --time 120 ipa-server sudo systemctl daemon-reload sudo systemctl enable --now example-ipa.service sudo podman exec ipa-server ipactl status

本次现有环境曾遇到停止超时:Podman 已进入 Stopping,容器内 systemd 却没有收到关机任务。验证在容器内发送停止信号后,LDAP 与 Tomcat 能正常退出,因此在原 unit 的 podman stop 之前增加了:

ini
1
2
# 放在原 ExecStop 前面,仅用于本文复现的停止信号问题 ExecStop=-/usr/bin/timeout 10 /usr/bin/podman exec ipa-server /bin/kill -s RTMIN+3 1

实测服务停止时间从超时缩短到约 4 秒。这是针对当前运行环境的兼容处理,不需要在没有该问题的安装中默认加入。

1.5 配置内部 DNS

FreeIPA 是内部区域的权威 DNS。第一步确认容器确实使用本机 DNS,并取出公开 CA 证书,后面验证 HTTPS 与客户端入域要用到:

bash
1
2
3
4
5
6
sudo podman exec ipa-server cat /etc/resolv.conf sudo podman exec ipa-server ipactl status sudo install -d -m 0755 /etc/example-ipa sudo podman cp ipa-server:/etc/ipa/ca.crt /etc/example-ipa/ca.crt sudo chmod 0644 /etc/example-ipa/ca.crt openssl x509 -in /etc/example-ipa/ca.crt -noout -subject -fingerprint -sha256

resolv.conf 应包含 nameserver 127.0.0.1,并且 named 正常运行。如果容器运行时没有保留这一设置,先检查 Podman 版本、网络后端和启动参数,不要继续入域;本例不再要求安装后另行补 DNS 或重建网络。已有实例不能直接套用首次安装命令。

取得管理员票据后,通过 IPA CLI 修改区域中的记录:

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 在容器内执行,交互输入管理员密码 sudo podman exec -it ipa-server bash kinit admin # 修改已有记录;若记录不存在,使用 dnsrecord-add ipa dnsrecord-mod lab.example.com ipa --a-rec=192.168.50.10 ipa dnsrecord-mod lab.example.com ipa --aaaa-rec=fd12:3456:789a::10 ipa dnsrecord-mod lab.example.com ipa-ca --a-rec=192.168.50.10 --aaaa-rec=fd12:3456:789a::10 # 以下记录在新区域中尚不存在;已有记录使用 dnsrecord-mod ipa dnsrecord-add lab.example.com host-a --a-rec=192.168.50.10 --aaaa-rec=fd12:3456:789a::10 ipa dnsrecord-add lab.example.com host-b --a-rec=192.168.50.11 --aaaa-rec=fd12:3456:789a::11 ipa dnsrecord-add lab.example.com host-c --a-rec=192.168.50.12 --aaaa-rec=fd12:3456:789a::12 ipa dnsrecord-add lab.example.com git --a-rec=192.168.50.10 --aaaa-rec=fd12:3456:789a::10 ipa dnsrecord-add lab.example.com files --a-rec=192.168.50.10 --aaaa-rec=fd12:3456:789a::10 ipa dnsrecord-mod lab.example.com @ --a-rec=192.168.50.10 --aaaa-rec=fd12:3456:789a::10 # 核对 A/AAAA,不应再向客户端返回 10.90.0.10 等容器地址 ipa dnsrecord-show lab.example.com ipa --all ipa dnszone-mod lab.example.com --dynamic-update=FALSE exit

同时应防止容器启动时重新把 A 记录更新成容器地址,可按官方容器说明配置 IPA_SERVER_IP=no-update。本例使用固定记录,因此关闭了区域动态更新;需要自动注册客户端地址的环境不能直接照搬这一项。

这里先让三台节点直接使用 IPA DNS,它通过前面配置的 forwarder 查询外部区域。在每台机器的原 Netplan 文件中,将 nameservers.addresses 改为 [192.168.50.10, "fd12:3456:789a::10"],然后执行 sudo netplan try。NetworkManager 分支则执行:

bash
1
2
3
4
sudo nmcli connection modify 'YOUR_CONNECTION' \ ipv4.ignore-auto-dns yes ipv4.dns 192.168.50.10 \ ipv6.ignore-auto-dns yes ipv6.dns fd12:3456:789a::10 sudo nmcli connection up 'YOUR_CONNECTION'

使用 networkd 且 RA 同时下发了其他 DNS 时,在 /etc/systemd/resolved.conf.d/ipa.conf 写入以下配置,给内部区域指定解析路径:

ini
1
2
3
[Resolve] DNS=192.168.50.10 fd12:3456:789a::10 Domains=~lab.example.com

先用 sudo install -d /etc/systemd/resolved.conf.d 创建目录,再保存配置并执行 sudo systemctl restart systemd-resolved。检查 /etc/resolv.conf 指向 systemd-resolved 管理的文件,使用 resolvectl status 确认生效。两种地址属于同一 IPA 实例,不构成 DNS 高可用。

从三台机器分别检查 DNS,成功后删除 /etc/hosts 中安装前添加的 ipa.lab.example.com ipa 别名,保留各主机自身记录:

bash
1
2
3
4
5
6
dig @192.168.50.10 ipa.lab.example.com A dig @fd12:3456:789a::10 ipa.lab.example.com AAAA dig @192.168.50.10 _ldap._tcp.lab.example.com SRV dig @192.168.50.10 _kerberos._udp.lab.example.com SRV resolvectl query ipa.lab.example.com getent ahosts git.lab.example.com

应得到宿主入口地址,SRV 应指向 ipa.lab.example.com。需要让其他电脑经网关解析时,再在现有网关 DNS 中添加条件转发。Linux dnsmasq 可将下面内容保存为 /etc/dnsmasq.d/ipa.conf,执行 sudo dnsmasq --test 后重启 dnsmasq,并由 DHCP 将该网关作为客户端 DNS;设备路由器应在其 DNS 转发配置页填写同样的区域与上游,不能把 Linux 文件路径直接搬过去。

conf
1
2
server=/lab.example.com/192.168.50.10 server=/lab.example.com/fd12:3456:789a::10

尚未调整网关时,可以先让管理电脑直接使用 IPA DNS。

二、部署 K3s

2.1 安装控制节点

K3s 是 Kubernetes 发行版,集成了 containerd、CoreDNS、Traefik 等组件。当前业务规模下,沿用 K3s 即可,应用仍通过 Kubernetes 资源和 Helm 管理。

本次需要双栈,按照 K3s 网络文档,应在首次创建集群时设置 Pod 和 Service 的双栈网段。已有 IPv4-only 集群需先做备份和重建准备,不能只修改下面配置然后重启。

在控制节点先执行 sudo install -d -m 0755 /etc/rancher/k3s,再创建 /etc/rancher/k3s/config.yaml

yaml
1
2
3
4
5
6
7
8
# 全部为示例地址;Pod、Service、宿主和 Podman 网段不得重叠 node-name: host-a node-ip: "192.168.50.10,fd12:3456:789a::10" flannel-iface: eno1 # 自行修改为内网网卡 cluster-cidr: "10.44.0.0/16,fd12:3456:789b:100::/56" service-cidr: "10.45.0.0/16,fd12:3456:789b:200::/112" flannel-ipv6-masq: true write-kubeconfig-mode: "0600"

此处 Pod 使用 ULA IPv6,通过 flannel-ipv6-masq 处理 IPv6 出口转换。宿主固定 ULA 应先写入实际使用的 NetworkManager 或其他网络配置,不能只通过临时 ip address add 添加。

K3s 安装说明安装,下面固定到本文使用版本,后续部署应自行评估版本:

bash
1
2
3
4
curl -sfL https://get.k3s.io -o /tmp/install-k3s.sh # 检查安装脚本后执行 sudo env INSTALL_K3S_VERSION=v1.34.5+k3s1 sh /tmp/install-k3s.sh server sudo k3s kubectl get nodes -o wide

如果默认 IPv6 路由通过 RA 获取,需要检查对应接口在启用 IPv6 转发后仍接受 RA。本次对实际接收 RA 的接口持久化配置 accept_ra=2,并确认默认路由持续存在,没有对所有接口盲目追加配置。

2.2 加入工作节点

控制节点的加入凭据位于 /var/lib/rancher/k3s/server/node-token。先在控制节点用具有 sudo 权限的普通管理账号导出一份仅自己可读的临时文件,不把 token 输出到终端:

bash
1
2
3
umask 077 sudo cat /var/lib/rancher/k3s/server/node-token > ~/k3s-join-token chmod 0600 ~/k3s-join-token

再在每台工作节点执行,SSH 用户自行替换,确认 scp 成功后才安装凭据:

bash
1
2
3
4
5
6
7
8
sudo install -d -m 0755 /etc/rancher/k3s token_dir=$(mktemp -d) scp your-admin@192.168.50.10:k3s-join-token "$token_dir/join-token" # 上一步失败时停止,不继续安装 sudo install -m 0600 "$token_dir/join-token" /etc/rancher/k3s/join-token rm "$token_dir/join-token" rmdir "$token_dir" sudo test -s /etc/rancher/k3s/join-token

两台工作节点都完成复制后,在控制节点删除 ~/k3s-join-token,保留工作节点所需的 root 专属凭据文件。这个流程不要求预先配置免密码 sudo,也不应将文件纳入公开配置仓库。

在工作节点的 /etc/rancher/k3s/config.yaml 中配置。host-c 的 node-name、IPv4 和 ULA 分别改为 host-c.12::12

yaml
1
2
3
4
5
node-name: host-b # 第二台工作节点改成 host-c server: "https://192.168.50.10:6443" token-file: /etc/rancher/k3s/join-token node-ip: "192.168.50.11,fd12:3456:789a::11" # 每台节点分别修改 flannel-iface: eno1
bash
1
2
curl -sfL https://get.k3s.io -o /tmp/install-k3s.sh sudo env INSTALL_K3S_VERSION=v1.34.5+k3s1 sh /tmp/install-k3s.sh agent

服务器之间需要允许所选 CNI、API Server 等集群内部流量,但不应把这些管理端口一起映射到公网。控制节点检查:

bash
1
2
3
4
sudo k3s kubectl get nodes -o wide sudo k3s kubectl get pods -A -o wide sudo k3s kubectl get nodes \ -o custom-columns=NAME:.metadata.name,POD-CIDRS:.spec.podCIDRs

2.3 集群内部解析 FreeIPA

K3s 默认通过 CoreDNS 提供 Pod DNS。内部 IPA 区域采用条件转发,避免 Pod 查询到公网泛解析。新集群将下面内容保存为 ipa-coredns.yaml;已有 coredns-custom 时使用 sudo k3s kubectl -n kube-system edit configmap coredns-custom,只增加 ipa.server,保留其他键:

yaml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1 kind: ConfigMap metadata: name: coredns-custom namespace: kube-system data: ipa.server: | lab.example.com:53 { errors cache 30 forward . 192.168.50.10 fd12:3456:789a::10 }
bash
1
2
3
4
5
6
sudo k3s kubectl apply -f ipa-coredns.yaml sudo k3s kubectl -n kube-system get configmap coredns -o jsonpath='{.data.Corefile}' sudo k3s kubectl -n kube-system rollout restart deployment/coredns sudo k3s kubectl -n kube-system rollout status deployment/coredns --timeout=180s sudo k3s kubectl run ipa-dns-test --rm -i --restart=Never --image=busybox:1.37 -- \ sh -ec 'nslookup ipa.lab.example.com; nslookup -type=AAAA ipa.lab.example.com; nslookup -type=SRV _ldap._tcp.lab.example.com'

Corefile 应包含 import /etc/coredns/custom/*.server。从 Pod 内也应查询到正确 A、AAAA 和 SRV。若宿主成功而 Pod 超时,继续检查第三节转发规则后重试本测试;不能跳过后直接部署依赖 LDAP 的应用。

2.4 确认节点与存储

后续两篇使用静态 local PV,数据固定到 host-a,应用采用单副本和 Recreate。这里没有跨节点数据复制,三台机器不等于业务已经高可用。

bash
1
2
3
sudo k3s kubectl get nodes -L kubernetes.io/hostname sudo k3s kubectl describe node host-a findmnt -T /srv/example

三台节点都应为 Ready,host-a 的 hostname 标签应为 host-a。本文新集群未设置 NoSchedule 污点;若自行增加污点,需要为后续业务和入口配置对应 tolerations。确认 /srv/example 是预期数据盘,容量充足,再创建各应用目录。

三、处理 Podman 与 K3s 的网络冲突

3.1 网页端口由 Traefik 统一接入

K3s 默认部署的 Traefik 通过 ServiceLB 提供 80、443 入口。若 Podman 同时将 FreeIPA 的 80、443 发布到相同宿主地址,就会争用端口。可以先检查监听与 ServiceLB 工作负载:

bash
1
2
3
4
sudo ss -lntup sudo podman port ipa-server sudo k3s kubectl -n kube-system get svc traefik sudo k3s kubectl -n kube-system get pods -o wide

本例已经把 Podman 网页端口改为 1808118444,然后让 Traefik 将 ipa.lab.example.com 的 HTTPS 流量转发到后端。FreeIPA 原证书继续使用,因此配置 TCP TLS 透传,保留客户端到 IPA 的 TLS 会话。

以下使用无 selector 的 Service 和 EndpointSlice 表示集群外后端,地址是宿主内网地址,不是 Podman 容器临时地址:

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
apiVersion: v1 kind: Service metadata: name: ipa-backend namespace: default spec: ports: - name: https port: 443 targetPort: 18444 - name: http port: 80 targetPort: 18081 --- apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: ipa-backend-v4 namespace: default labels: kubernetes.io/service-name: ipa-backend addressType: IPv4 ports: - name: https protocol: TCP port: 18444 - name: http protocol: TCP port: 18081 endpoints: - addresses: - "192.168.50.10" # 自行修改为 Podman 宿主地址 conditions: ready: true --- apiVersion: traefik.io/v1alpha1 kind: IngressRouteTCP metadata: name: ipa-https namespace: default spec: entryPoints: - websecure routes: - match: HostSNI(`ipa.lab.example.com`) services: - name: ipa-backend port: 443 tls: passthrough: true --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: name: ipa-http namespace: default spec: entryPoints: [web] routes: - match: Host(`ipa.lab.example.com`) kind: Rule services: - name: ipa-backend port: 80

保存为 ipa-route.yaml 后应用并验证:

bash
1
2
3
sudo k3s kubectl wait --for=condition=Established crd/ingressroutetcps.traefik.io --timeout=180s sudo k3s kubectl apply -f ipa-route.yaml curl --cacert /etc/example-ipa/ca.crt https://ipa.lab.example.com/ipa/ui/

这里使用 IngressRouteTCP 的 SNI 匹配与 TLS 透传,不要配置捕获所有域名的 HostSNI(*),否则会影响同一入口的其他 HTTPS 业务。这里也提供 HTTP 80 转发,保留 IPA 的 CA 获取接口和自身跳转行为。

3.2 检查 FORWARD 与容器转发规则

修改端口后,如果宿主机能访问而其他节点仍然超时,需要继续检查转发链。本次环境中 FORWARD 默认策略为 DROP,不能只确认 Podman 已创建端口映射。

bash
1
2
3
4
sudo iptables -S FORWARD sudo iptables -t nat -S sudo ip6tables -S FORWARD sudo podman inspect ipa-server

若确认是这里丢包,可建立专用规则。以下脚本只刷新自己的链,允许内网节点与 Pod 访问 IPA,按当前容器地址匹配转发后的目标,并放行对应返回流量。保存为 example-ipa-forward,网段与网络名称须与前文一致:

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash set -euo pipefail # K3s 可能先于 IPA 启动;IPA 自己启动后也会调用本脚本 if ! /usr/bin/podman container exists ipa-server; then exit 0; fi if [ "$(/usr/bin/podman inspect -f '{{.State.Running}}' ipa-server)" != true ]; then exit 0; fi container_json=$(/usr/bin/podman inspect ipa-server) ipa4=$(jq -er '.[0].NetworkSettings.Networks["ipa-dualstack"].IPAddress | select(length > 0)' <<<"$container_json") ipa6=$(jq -er '.[0].NetworkSettings.Networks["ipa-dualstack"].GlobalIPv6Address | select(length > 0)' <<<"$container_json") allow_ipa() { local cmd=$1 dest=$2 shift 2 "$cmd" -w -N EXAMPLE_IPA 2>/dev/null || "$cmd" -w -S EXAMPLE_IPA >/dev/null "$cmd" -w -F EXAMPLE_IPA for source in "$@"; do "$cmd" -w -A EXAMPLE_IPA -s "$source" -d "$dest" -p tcp \ -m multiport --dports 53,80,88,389,443,464,636 -j ACCEPT "$cmd" -w -A EXAMPLE_IPA -s "$source" -d "$dest" -p udp \ -m multiport --dports 53,88,464 -j ACCEPT "$cmd" -w -A EXAMPLE_IPA -s "$dest" -d "$source" \ -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT done # 容器访问递归 DNS 和公共 HTTPS,回包仅允许已建立连接 "$cmd" -w -A EXAMPLE_IPA -s "$dest" -p tcp -m multiport --dports 53,80,443 -j ACCEPT "$cmd" -w -A EXAMPLE_IPA -s "$dest" -p udp --dport 53 -j ACCEPT "$cmd" -w -A EXAMPLE_IPA -d "$dest" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT "$cmd" -w -A EXAMPLE_IPA -j RETURN while "$cmd" -w -C FORWARD -j EXAMPLE_IPA 2>/dev/null; do "$cmd" -w -D FORWARD -j EXAMPLE_IPA done "$cmd" -w -I FORWARD 1 -j EXAMPLE_IPA } allow_ipa iptables "$ipa4" 192.168.50.0/24 10.44.0.0/16 allow_ipa ip6tables "$ipa6" fd12:3456:789a::/64 fd12:3456:789b:100::/56

这组规则适用于本文 rootful Podman 和 iptables 兼容后端;使用原生 nftables/firewalld 的独立丢弃链时,还需在原防火墙中允许相同流量,不能认为某一处 ACCEPT 能越过所有链。没有转发丢包的环境可跳过这组附加规则。

安装并先执行一次:

bash
1
2
3
4
sudo install -m 0755 example-ipa-forward /usr/local/sbin/example-ipa-forward sudo /usr/local/sbin/example-ipa-forward sudo iptables -nvL EXAMPLE_IPA sudo ip6tables -nvL EXAMPLE_IPA

为了在容器和 K3s 启动后恢复规则,分别创建 /etc/systemd/system/example-ipa.service.d/forward.conf/etc/systemd/system/k3s.service.d/ipa-forward.conf,两者内容相同:

ini
1
2
[Service] ExecStartPost=/usr/local/sbin/example-ipa-forward

目录先用下面命令创建,保存上述文件后重新加载。当前规则已经手动应用,无需为写入 drop-in 立即重启集群:

bash
1
2
3
4
5
sudo install -d /etc/systemd/system/example-ipa.service.d /etc/systemd/system/k3s.service.d # 将上述内容分别保存到两个文件后执行 sudo systemctl daemon-reload sudo systemctl cat example-ipa.service sudo systemctl cat k3s.service

不要保存整套动态 Kubernetes 规则到 iptables-persistent,也不要清空 FORWARD 或全局改成 ACCEPT。若手工重载防火墙导致专用链丢失,重新执行本脚本,并重新进行 DNS、LDAP 和 HTTPS 测试。

3.3 验证双栈入口

第一节已发布 LDAP、Kerberos 和 DNS 的 IPv6 端口,不需要重建容器。Traefik 初次安装的 Service 还需要启用双栈。在新集群上创建 /var/lib/rancher/k3s/server/manifests/traefik-config.yaml

yaml
1
2
3
4
5
6
7
8
9
10
apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- service: spec: ipFamilyPolicy: PreferDualStack

在另外两台机器上验证 HTTPS 或执行入域前,从可信控制节点复制 CA,并核对上文输出的 SHA-256 指纹。下面在工作节点执行,SSH 用户与主机地址自行替换:

bash
1
2
3
scp your-admin@192.168.50.10:/etc/example-ipa/ca.crt /tmp/example-ipa-ca.crt openssl x509 -in /tmp/example-ipa-ca.crt -noout -fingerprint -sha256 sudo install -D -m 0644 /tmp/example-ipa-ca.crt /etc/example-ipa/ca.crt

入域时再将下一小节命令的 hostname 分别改成 host-b.lab.example.comhost-c.lab.example.com

在控制节点检查 Service 与 Helm 安装任务:

bash
1
2
sudo k3s kubectl -n kube-system get svc traefik -o wide sudo k3s kubectl -n kube-system get jobs,pods

等 Service 具有两种地址族、相关 Pod 就绪后,在已复制 CA 的另一台节点测试:

bash
1
2
curl -4 --cacert /etc/example-ipa/ca.crt -I https://ipa.lab.example.com/ipa/ui/ curl -6 --cacert /etc/example-ipa/ca.crt -I https://ipa.lab.example.com/ipa/ui/

两种协议都通过后再执行下一小节的客户端入域。第四节完整 HelmChartConfig 保留了这个双栈字段,后续直接替换本例文件。

公网 IPv6 出站保留。网关应允许返回流量及必要 ICMPv6,主动入站仅开放实际业务。NDP 代理负责地址可达性,不能替代入站防火墙。

3.4 客户端入域与 sudo 权限

此时 IPA 的 DNS、HTTP 与 HTTPS 已可从内网访问,开始将三台宿主机作为客户端加入域。先在控制节点执行,再分别处理另外两台。

Ubuntu 客户端安装 IPA 客户端工具,确认主机名唯一、解析和时间正常后执行入域。入域使用的管理员权限与日常登录用户分别配置。

bash
1
2
3
4
5
6
7
8
9
10
11
12
13
sudo apt install -y freeipa-client libsss-sudo ldap-utils sssd-tools # 主机名按每台机器分别修改;根据提示输入具有入域权限的账号 sudo ipa-client-install \ --hostname=host-a.lab.example.com \ --server=ipa.lab.example.com \ --domain=lab.example.com \ --realm=LAB.EXAMPLE.COM \ --ca-cert-file=/etc/example-ipa/ca.crt \ --mkhomedir hostname -f sudo sssctl config-check

在已入域的控制节点创建后两篇使用的测试成员,用户名、姓名与邮箱自行替换:

bash
1
2
3
4
5
6
7
8
9
kinit admin ipa user-add yourname --first=Your --last=Name --email=your-email@example.com --password kdestroy # 用新用户初始密码获取票据,按提示完成首次密码修改 kinit yourname klist getent passwd yourname id yourname kdestroy

如果用户已经存在,改用 ipa user-show yourname --all 核对属性,不重复创建。若使用其他终端测试,应在那台终端同样完成 DNS、时间和 CA 配置。

如果系统已有同名本地用户,应先检查 /etc/passwd、NSS 查询结果及文件所有者。仅把本地用户 UID 设置成与 IPA 用户一致,不会把两个身份自动合并。 本地账号仍可能优先匹配;本次没有通过批量删除本地用户来处理这个问题。

免密码 sudo 在 IPA 的 Sudo Rules 中配置。示例规则的字段如下:

字段 设置
Who 指定运维用户组,例如 sysadmin
Access this host 指定目标主机或主机组
Run Commands 按需选择;完整管理权限才使用 Any Command
RunAs Users → External root
Sudo Option !authenticate

root 是目标 Linux 系统上的用户,不要求在 IPA 中创建同名目录用户,所以填在 External 中。客户端还需启用 SSSD sudo 支持,检查 /etc/nsswitch.conf 中的来源:

conf
1
sudoers: files sss

编辑 /etc/nsswitch.conf,已有 sudoers: 行就增加 sss,没有则新增上面的行。在 /etc/sssd/sssd.conf 的对应 [domain/lab.example.com] 中确认 sudo_provider = ipa[sssd] 若显式列出 services,在原列表中加入 sudo。保持文件权限 0600,执行 sudo sssctl config-checksudo systemctl restart sssd

以下为等价 IPA 规则创建命令,在已入域控制节点上执行。规则授予指定组在这三台主机上的完整 root 权限,只把实际运维人员加入该组:

bash
1
2
3
4
5
6
7
8
9
kinit admin ipa group-add sysadmin --desc='Server administrators' ipa group-add-member sysadmin --users=yourname ipa sudorule-add sysadmin-nopasswd --cmdcat=all ipa sudorule-add-user sysadmin-nopasswd --groups=sysadmin ipa sudorule-add-host sysadmin-nopasswd \ --hosts=host-a.lab.example.com --hosts=host-b.lab.example.com --hosts=host-c.lab.example.com ipa sudorule-add-runasuser sysadmin-nopasswd --users=root ipa sudorule-add-option sysadmin-nopasswd --sudooption='!authenticate'

不存在于 IPA 的 root 会作为 external RunAs 用户处理,使用 ipa sudorule-show sysadmin-nopasswd --all 检查结果。规则存在缓存;使用 IPA 普通运维用户重新登录每台主机,再验证:

bash
1
2
sudo -l sudo -n -k -u root /usr/bin/id -u

第二条不使用已有 sudo 认证时间戳,返回 0 才说明此次允许免密码以 root 执行。但如果本机还保留 NOPASSWD sudoers 文件,该结果不能单独证明权限来自 IPA;排查时需要对照本地规则与 SSSD 日志。

四、统一配置 HTTPS 证书

4.1 公共证书与 IPA CA 的用途

FreeIPA 页面与 LDAPS 可以使用 IPA CA。已入域客户端信任该 CA,浏览器则需要单独检查信任库。在 Firefox 的“设置 → 隐私与安全 → 证书 → 查看证书 → 证书颁发机构 → 导入”中导入可信来源的 /etc/ipa/ca.crt,核对指纹后勾选用于识别网站的信任。重新打开 IPA 页面,确认证书名称和签发链正确。不同浏览器的信任库分别处理,不关闭证书验证。

Gitea、Nextcloud 需要被外部浏览器访问,因此使用 Let’s Encrypt 公共证书,由 Traefik 终止 TLS。这样业务无需分别维护一套证书申请脚本。

示例申请 lab.example.com*.lab.example.com,覆盖区域根与下一层业务名称。泛域名不覆盖多层名称,也不会自动替换 IPA 内部证书;第三节的 IPA 透传路由继续使用原 CA。

4.2 腾讯云 DNSPod 权限与凭据

DNS-01通过 _acme-challenge TXT 验证域名控制权,可以申请泛域名证书,不要求业务暴露 80 或 443。这里使用腾讯云 DNSPod,通过专门子账户配置 API 权限。

授权按 DNSPod 可授权资源类型限定到目标区域,所需操作为 DescribeDomainListDescribeRecordListCreateRecordDeleteRecord。策略使用 dnspod 操作前缀。域名资源标识按控制台及 API 的资源格式填写,不能把不同产品的域名字符串 ID 混用。

在 CAM 中新建自定义策略,使用下面的 JSON,再绑定到专用子用户。DescribeDomainList 是列举操作,单独允许;写入和删除限制到指定区域。将 MAIN_ACCOUNT_UIN 替换为资源所属主账号 ID,将 DNSPOD_DOMAIN_ID 替换为 DNSPod API 返回的数字 DomainId:

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{ "version": "2.0", "statement": [ { "effect": "allow", "action": ["dnspod:DescribeDomainList"], "resource": ["*"] }, { "effect": "allow", "action": ["dnspod:DescribeRecordList", "dnspod:CreateRecord", "dnspod:DeleteRecord"], "resource": ["qcs::dnspod::uin/MAIN_ACCOUNT_UIN:domain/DNSPOD_DOMAIN_ID"] } ] }

DomainId 可在腾讯云 API Explorer 选择 DNSPod DescribeDomainList,用资源所属账号查询 example.com,读取 DomainList 对应条目的 DomainId;不是 CAM 子用户 ID,也不是其他域名产品的字符串资源 ID。若实际托管区域为 example.com,申请 *.lab.example.com 也授权该父区域,除非已单独委派 lab.example.com

移除子用户原先的全域写权限策略,也检查用户组继承权限。此策略仍可修改目标区域中的其他记录,不是仅限 ACME TXT。先通过后面的 staging 签发确认列举、创建和清理都成功,再切正式 CA。

为该子用户创建 API 密钥。在控制节点交互输入,以下命令不回显内容,也不把值写进 shell 历史:

bash
1
2
3
4
5
6
7
8
9
10
11
sudo install -d -m 0700 /root/example-dns sudo bash -c ' umask 077 read -r -s -p "SecretId: " dns_id </dev/tty printf "\n" >/dev/tty read -r -s -p "SecretKey: " dns_key </dev/tty printf "\n" >/dev/tty test -n "$dns_id" && test -n "$dns_key" || exit 1 printf "%s" "$dns_id" > /root/example-dns/secret-id printf "%s" "$dns_key" > /root/example-dns/secret-key '

然后创建 Kubernetes Secret:

bash
1
2
3
4
# 示例文件路径,自行修改;目录 0700,凭据文件 0600 sudo k3s kubectl -n kube-system create secret generic traefik-dns \ --from-file=secret-id=/root/example-dns/secret-id \ --from-file=secret-key=/root/example-dns/secret-key

Secret 保存在集群中,拥有管理权限的账号仍可读取,不能把它视为对集群管理员保密的保险箱。备份集群数据库和 Secret 时也需要同样限制访问。

4.3 持久化 ACME 数据

Traefik 将 ACME 账户、证书和私钥保存到 acme.json。该文件必须放在持久卷中,否则 Pod 重建后可能丢失账户和证书状态。

以下示例在 host-a 上创建目录,使用 Traefik 镜像的运行 UID/GID 65532;若自行更换镜像或安全上下文,需要相应修改:

bash
1
2
3
4
5
sudo install -d -o 65532 -g 65532 -m 0700 /srv/example/traefik-acme # 不覆盖已有 acme.json,仅为新目录创建文件 sudo touch /srv/example/traefik-acme/acme.json sudo chown 65532:65532 /srv/example/traefik-acme/acme.json sudo chmod 0600 /srv/example/traefik-acme/acme.json

将下面清单保存为 traefik-acme-volume.yaml,创建专用 PV/PVC:

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: v1 kind: PersistentVolume metadata: name: traefik-acme spec: capacity: storage: 1Gi accessModes: [ReadWriteOnce] persistentVolumeReclaimPolicy: Retain storageClassName: "" local: path: /srv/example/traefik-acme nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: [host-a] # 修改为实际节点名称 --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: traefik-acme namespace: kube-system spec: accessModes: [ReadWriteOnce] storageClassName: "" volumeName: traefik-acme resources: requests: storage: 1Gi
bash
1
2
sudo k3s kubectl apply -f traefik-acme-volume.yaml sudo k3s kubectl -n kube-system get pvc traefik-acme

文件放在 local PV 时,Traefik 同样固定到该节点,使用单副本与 Recreate,避免多实例共同写入同一个 ACME 文件。

4.4 配置 Traefik DNS-01

K3s 自带 Traefik 通过 HelmChart 管理。自定义参数写入同名 HelmChartConfig,不要直接修改会被 K3s 重建的内置清单。

下面是 Traefik 的配置参数,对应 Traefik chart 39.0.2。先保存为当前目录下的 traefik-config.yaml,替换邮箱和节点名。它包含第三节已有的双栈字段;自行增加过配置的集群需合并保留。示例使用测试 CA,先确认 DNS 验证成功。

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- deployment: replicas: 1 updateStrategy: type: Recreate rollingUpdate: null nodeSelector: kubernetes.io/hostname: host-a persistence: enabled: true existingClaim: traefik-acme path: /data volumes: - name: traefik-dns mountPath: /run/secrets/dns type: secret env: - name: TENCENTCLOUD_SECRET_ID_FILE value: /run/secrets/dns/secret-id - name: TENCENTCLOUD_SECRET_KEY_FILE value: /run/secrets/dns/secret-key - name: TENCENTCLOUD_PROPAGATION_TIMEOUT value: "300" additionalArguments: - --certificatesresolvers.dns.acme.email=your-email@example.com - --certificatesresolvers.dns.acme.storage=/data/acme-staging.json - --certificatesresolvers.dns.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory - --certificatesresolvers.dns.acme.dnschallenge.provider=tencentcloud - --certificatesresolvers.dns.acme.dnschallenge.resolvers=[2606:4700:4700::1111]:53,[2606:4700:4700::1001]:53 - --certificatesresolvers.dns.acme.dnschallenge.propagation.delaybeforechecks=30s service: spec: ipFamilyPolicy: PreferDualStack ports: business: port: 8444 exposedPort: 8443 protocol: TCP expose: default: true

示例新增了 business 入口:Service 对外 8443,转到容器内 8444。两者可以不同;路由器需要将外部业务端口转到 Service 暴露的端口。原 websecure 入口继续承担 IPA 路由。

如果节点有 NoSchedule 污点,需要为 Traefik 和对应 ServiceLB Pod 配置匹配的容忍,否则服务可能没有落到需要的入口节点。本文只展示相关配置片段,不能覆盖已有集群中的这些调度参数。

4.5 泛解析 CNAME 导致验证失败

本次公网使用通配 CNAME。首次签发时,尚不存在的 _acme-challenge 查询命中了泛解析,客户端沿 CNAME 跟随到其他名称,导致找不到预期 TXT。

确认没有专门的 ACME CNAME 委派后,在前面的 env 中补充:

yaml
1
2
- name: LEGO_DISABLE_CNAME_SUPPORT value: "true"

同时,内网 IPA DNS 与公网区域采用不同答案,DNS-01 自检需查询公网解析器,不能依赖内部权威区域。本例指定了可从服务器访问的公共 IPv6 DNS,并保留传播等待时间。

若后续改成专门的 CNAME 验证委派,需要重新评估 LEGO_DISABLE_CNAME_SUPPORT,不能一直保留。具体环境变量以 lego 腾讯云 providerTraefik ACME 配置为准。

4.6 申请泛域名并配置业务路由

保存为 traefik-tlsstore.yaml,配置默认 TLSStore 和签发名称:

yaml
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: traefik.io/v1alpha1 kind: TLSStore metadata: name: default namespace: kube-system spec: defaultGeneratedCert: resolver: dns domain: main: lab.example.com sans: - "*.lab.example.com"

执行下面命令安装完整 HelmChartConfig,该目录由 K3s 自动加载;等 Helm 安装任务成功、Deployment 更新后再检查 TLSStore。检查 Traefik 日志及测试证书;测试 CA 的证书不受普通浏览器信任,这是预期行为。

bash
1
2
3
4
sudo install -m 0600 traefik-config.yaml /var/lib/rancher/k3s/server/manifests/traefik-config.yaml sudo k3s kubectl -n kube-system get jobs,pods sudo k3s kubectl apply -f traefik-tlsstore.yaml sudo k3s kubectl -n kube-system logs deployment/traefik --tail=100

检查 sudo k3s kubectl -n kube-system get deployment traefik -o yaml 中的 args 已变为 staging 配置,日志没有 DNS 权限、传播或清理失败。可以不部署业务,先检查入口返回的默认测试证书:

bash
1
2
openssl s_client -connect 192.168.50.10:8443 -servername git.lab.example.com </dev/null 2>/dev/null | openssl x509 -noout -issuer -dates -ext subjectAltName

签发者应为测试 CA,SAN 包含区域根与泛域名,不能是 Traefik 自动生成的默认自签证书。确认后,编辑当前目录的 traefik-config.yaml,将 caserver 改为 https://acme-v02.api.letsencrypt.org/directorystorage 改为 /data/acme.json,再次执行前面的 sudo install。等 Helm 更新完成,复查 Deployment args、日志与证书;测试与正式账户文件分别保留。

此时尚未部署 Gitea、Nextcloud,HTTPS 返回 404 是正常的,但证书链必须能通过验证:

bash
1
curl --resolve git.lab.example.com:8443:192.168.50.10 -I https://git.lab.example.com:8443/

后续两篇分别提供业务 Service 与 IngressRoute,这里不创建指向不存在 Service 的示例路由。

证书申请和后续续期由 Traefik 完成,K3s 负责运行工作负载,PVC 保存账户与私钥,无需再添加一套申请证书的定时脚本。Pod 重建后会从持久卷加载证书,日常维护时检查证书有效期和续期日志即可。

五、内外网访问与应用设置

5.1 公网解析与端口转发

公网业务可以保留区域根 A 记录与通配 CNAME,例如:

text
1
2
lab.example.com A 203.0.113.10 *.lab.example.com CNAME lab.example.com.

其中 203.0.113.10 是文档示例地址。仅用于内部认证的主机私网记录放在 IPA DNS,不需要公开到公网区域。删除旧记录前,先确认内部 DNS 已能提供所需名称和 SRV。

路由器管理服务占用 443 时,在路由器端口转发页新建一条 TCP 规则:外部端口 8443,内部目标 192.168.50.10,内部端口 8443,并允许这条转发流量通过边界防火墙。保存后从手机蜂窝网络等外网测试。IPv4 上游还存在 NAT 时,上游也需要同样转发,否则本层映射不足以使服务公开。不要把 IPA、数据库或集群管理端口一并映射。

没有部署公网 IPv6 入口时,不为业务发布公网 AAAA;内网 ULA AAAA 可以继续保留。证书可通过 DNS-01 申请,完全不依赖公网业务端口开放。

内网 DNS 为业务名称返回内部地址,外网返回公开入口。内外统一使用 https://files.lab.example.com:8443/,协议、域名和端口保持一致。已有客户端可能缓存旧 A/AAAA 或不存在记录的答案,可先通过 dig 比较,再按需刷新缓存。

5.2 Gitea 与 Nextcloud 的反向代理设置

Gitea 需要设置对外 URL,否则生成链接或跳转仍可能使用旧 HTTP 地址。容器环境变量示例:

yaml
1
2
3
GITEA__server__DOMAIN: git.lab.example.com GITEA__server__ROOT_URL: https://git.lab.example.com:8443/ GITEA__server__SSH_DOMAIN: git.lab.example.com

SSH 的端口及入口单独配置,HTTP 反向代理不会自动代理 SSH。

Nextcloud 在 config.php 中设置访问域名和反向代理后的 URL:

将以下片段合并进现有 $CONFIG,域名、端口和代理网段自行修改:

php
1
2
3
4
5
'trusted_domains' => ['files.lab.example.com:8443'], 'overwritehost' => 'files.lab.example.com:8443', 'overwriteprotocol' => 'https', 'overwrite.cli.url' => 'https://files.lab.example.com:8443', 'trusted_proxies' => ['10.44.0.0/16'],

受信任代理应限定到 Traefik 实际来源地址范围,不能为了消除提示直接配置任意来源。如果容器入口脚本从环境变量生成配置,还要修改对应 Deployment 的环境变量,否则手工修改可能在重建后被旧值覆盖。

5.3 验证服务与证书

以下业务测试在完成 Nextcloud 篇部署后执行;仅完成本文时还没有 files 路由。分别验证内网 IPv4 和 IPv6,保持证书校验开启:

bash
1
2
3
4
5
6
7
8
curl -4 -IL https://files.lab.example.com:8443/ curl -6 -IL https://files.lab.example.com:8443/ # 检查实际返回证书的名称、签发者和有效期 openssl s_client \ -connect files.lab.example.com:8443 \ -servername files.lab.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

IPA 使用其 CA 单独检查:

bash
1
2
3
curl -4 --cacert /etc/ipa/ca.crt -I https://ipa.lab.example.com/ipa/ui/ curl -6 --cacert /etc/ipa/ca.crt -I https://ipa.lab.example.com/ipa/ui/ sudo podman exec ipa-server ipactl status

FreeIPA 健康检查中的容器时间服务及权限问题仍需分别处理。时间同步由宿主机维护,其他错误根据健康检查输出逐项排查。

参考

手机扫码阅读
本文作者: 有次元袋的 tiger
本文链接: https://www.superheaoz.top/2026/09/61942/
版权声明: 本站点所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我的个人天地