K3s + Harbor 端口冲突问题解决方案(Harbor 使用 80 端口)
1. 环境概述
- 操作系统:Linux(CentOS/Rocky 等)
- K3s 版本:v1.34.5+k3s1(单节点)
- Harbor 版本:v2.14.1,通过 Docker Compose 部署
- Harbor 配置:Nginx 容器直接绑定主机 80 端口
- K3s 网络插件 :默认使用 Traefik 作为 Ingress Controller,Service 类型为
LoadBalancer
2. 问题现象
完成 Harbor 部署后,在浏览器中访问 http://<主机IP>(例如 http://192.168.100.172),返回页面内容为:
404 page not found
此页面为 Traefik 的默认 404 页面,而非 Harbor 的登录界面,说明请求被 Traefik 拦截,未能到达 Harbor 容器。
3. 问题分析
3.1 端口占用情况
通过 ss -antl 查看主机端口监听状态:
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:*
LISTEN 0 4096 [::]:80 [::]:*
80 端口确实被 Harbor 的 Nginx 容器占用(通过 docker ps 确认)。但为何访问仍会进入 Traefik?
3.2 K3s 默认 Traefik 行为
K3s 安装时会自动部署 Traefik Ingress Controller,并创建一个 LoadBalancer 类型的 Service:
yaml
apiVersion: v1
kind: Service
metadata:
name: traefik
namespace: kube-system
spec:
ports:
- name: web
port: 80
targetPort: web
nodePort: 31415
- name: websecure
port: 443
targetPort: websecure
nodePort: 32397
type: LoadBalancer
K3s 自带的 service loadbalancer 组件(svclb-traefik)会监听主机端口,并通过 iptables 规则 将目标为 EXTERNAL-IP:80 的流量 DNAT 到 Traefik Pod 的 nodePort(如 31415)。这些规则优先级高于 Harbor 直接绑定的端口,导致请求被劫持。
3.3 iptables 规则验证
在主机上执行以下命令,可以看到相关 DNAT 规则:
bash
iptables -t nat -L PREROUTING -n -v
输出类似:
DNAT tcp -- 0.0.0.0/0 192.168.100.172 tcp dpt:80 to:10.43.160.149:31415
其中 10.43.160.149 是 Traefik Service 的 ClusterIP,31415 是 NodePort。正是这条规则将原本发往主机 80 端口的流量重定向到了 Traefik Pod,从而绕过了 Harbor 的 Nginx 容器。
4. 解决方案
核心思路:移除或修改 Traefik Service 对主机 80 端口的监听,释放该端口给 Harbor 使用。
由于用户明确要求 Harbor 使用 80 端口且不能修改 Harbor 配置,因此只能调整 Traefik 的暴露方式。
4.1 方案一:将 Traefik 的 web 端口改为其他端口(推荐)
修改 traefik Service,将 web 端口的 port 从 80 改为其他未占用端口(例如 81)。这样 K3s 的 svclb 组件将不再监听主机 80 端口,原有的 iptables DNAT 规则会被自动清理。
4.2 方案二:完全删除 Traefik Service 的 web 端口条目
如果不需要通过 Traefik 暴露任何 HTTP 服务,可以删除 web 端口条目,仅保留 websecure(443)端口。但这样会导致所有 HTTP Ingress 无法工作,一般不建议。
5. 操作步骤(以修改端口为例)
5.1 编辑 Traefik Service
bash
kubectl edit service -n kube-system traefik
5.2 修改端口配置
找到 ports 部分,将 web 端口的 port 值从 80 改为 81:
yaml
ports:
- name: web
nodePort: 31415
port: 81 # 原为 80,改为 81
protocol: TCP
targetPort: web
- name: websecure
nodePort: 32397
port: 443
protocol: TCP
targetPort: websecure
保存退出后,K3s 会自动更新 Service 和相关的 iptables 规则。
5.3 验证修改结果
bash
kubectl get service -n kube-system traefik
输出应显示:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.43.160.149 192.168.100.172 81:31415/TCP,443:32397/TCP 127m
此时主机 80 端口已不再被 Traefik 占用,仅 Harbor Nginx 容器监听:
bash
ss -antl | grep :80
应只显示 Harbor 的监听。
6. 验证结果
在浏览器中访问 http://192.168.100.172(或你的主机 IP),应看到 Harbor 的登录页面,而非 Traefik 的 404 页面。若仍无法访问,检查 Harbor 容器状态及防火墙规则。
7. 原理说明
7.1 K3s 的 LoadBalancer 实现
K3s 使用内置的 service loadbalancer 组件(由 svclb Pod 实现)为 LoadBalancer 类型的 Service 分配外部 IP。该组件在每个节点上运行一个 Pod,该 Pod 通过 netlink 和 iptables 在主机上创建规则,将发往 Service 外部 IP 的流量 DNAT 到 Service 的 ClusterIP 或 NodePort。对于 traefik Service,这些规则会拦截所有目的端口为 80 和 443 的流量,并转发到 Traefik Pod。
7.2 端口冲突的根本原因
- 直接绑定 :Harbor 的 Nginx 容器通过 Docker 的
-p 80:8080直接绑定主机 80 端口,这是用户空间的端口监听。 - 内核空间劫持 :iptables 规则位于内核的
PREROUTING链,优先级高于用户空间进程的监听。当请求到达主机时,内核首先检查 iptables 规则,匹配的流量会被直接 DNAT 到目标地址,用户空间的应用程序(如 Harbor Nginx)根本收不到数据包。
因此,尽管 ss 显示 80 端口被 Harbor 占用,实际上流量在进入应用层之前已被重定向。
7.3 解决方法的核心
通过修改 Traefik Service 的 port 值,使 K3s 的 svclb 不再创建针对主机 80 端口的 iptables 规则。原有的 DNAT 规则会在 Service 更新后被自动删除,从而让流量正常流入 Harbor 容器。
8. 注意事项
- 修改 Traefik Service 后,所有原本通过 HTTP(80端口)访问的 Ingress 资源将无法通过默认的 80 端口访问。如果需要继续使用 HTTP Ingress,可以考虑:
- 将 Ingress 的端口改为 81(即访问
http://<IP>:81),但需在 Ingress 规则中指定。 - 或者使用 HTTPS(443)替代 HTTP。
- 将 Ingress 的端口改为 81(即访问
- 如果未来需要恢复 Traefik 对 80 端口的监听,只需再次编辑 Service,将
port改回 80 即可。 - 确保防火墙(如 firewalld、iptables)没有额外限制 80 端口。Harbor 容器正常监听后,应能正常访问。
9. 总结
通过调整 K3s 中 Traefik Service 的端口映射,成功释放了主机 80 端口给 Harbor 使用,解决了因 iptables 规则优先级导致的端口冲突问题。该方法无需修改 Harbor 配置,保持了 Harbor 使用 80 端口的原意,且操作简单、可回滚,适用于单节点 K3s 环境。