1. 问题背景与冲突根因分析
1.1 K3s 的 80/443 到底是谁在占用
K3s 虽然是轻量级 Kubernetes,但它的功能并不缩水,默认就带了一套完整的 ingress controller,即 traefik。安装 K3s 后,traefik 会以 DaemonSet 的方式运行,并借助 K3s 自带的 svclb(Service Load Balancer)把 80 和 443 端口直接绑定到宿主机上。
这意味着,只要你执行完那句安装 K3s 的常见命令,这台机器的 80 端口就已经有主了。很多人在装完 K3s 之后并没有使用过任何 ingress 资源,甚至根本忘了 traefik 的存在,等到部署 Harbor 时才突然发现 80 端口被占,非常典型。
验证方式很简单:
bash复制ss -lntp | grep -E ':80|:443'
输出里会看到一条 k3s 或 svclb 相关的监听记录,占用进程名类似 svclb-traefik-xxx。这就是端口冲突的源头。
1.2 Harbor 为什么非要绑定 80
Harbor 的安装包解压后,默认的 harbor.yml 模板里 http.port 就是 80,install.sh 会通过 docker-compose 启动它的 nginx 组件,把 80 映射到宿主机。之所以默认这么设计,是因为 Harbor 把自己定位成一个企业级镜像仓库,对外访问路径越简单越好,用户执行 docker login
问题是,当宿主机已经被 K3s 占用了 80,再去执行 docker-compose up -d,docker 在创建容器并绑定端口时就会失败,报错就是 bind: address already in use。K3s 和 Harbor 之间并没有任何协调机制,两边各管各的,冲突几乎不可避免。
1.3 冲突发生的典型表现
端口冲突的表现不只是安装时报错,还有几种比较隐蔽的情况。比如你如果先装了 Harbor,再装 K3s,那么装完 K3s 后访问 Harbor 页面,会看到浏览器里出现 "default backend - 404" 之类的 traefik 页面,而不是 Harbor 的登录界面。这是因为 traefik 启动时发现 80 被占用,但它并不会自动退出,而是以一种半吊子的状态抢占了入口,导致流量进不去 Harbor。
反过来,如果你先装 K3s 再装 Harbor,那么 install.sh 大概率会在启动 nginx 容器的时候失败。这两种情况我都遇到过,本质上都是“一个端口两个服务”的矛盾。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 四个解决思路与方案选型
2.1 方案A:关闭 K3s 内置 traefik,把端口让给 Harbor
这个方法最直接,适合“这台机器上就专门跑 Harbor,不打算再用 K3s ingress”的场景。做法是在安装 K3s 时加上 --disable traefik 参数,或者修改已有安装的配置后重启 K3s。
优点是一劳永逸,80/443 完全腾出来;缺点是你也失去了 traefik 这个入口,未来如果有其他服务想走 ingress,就得自己另装 nginx ingress controller 之类的组件。
2.2 方案B:修改 Harbor 的 http 端口,避开冲突
不改 K3s,把 harbor.yml 里的 http.port 从 80 改成 8080 或 9080。这样 Harbor 的 nginx 只绑定宿主机的 8080,和 traefik 的 80 不冲突。
优点是保留 K3s 原有能力,缺点是从此 docker login 需要写端口:docker login
2.3 方案C:把 Harbor 容器接入 K3s,用 ingress 暴露
理论上,Harbor 如果跑在宿主机 docker-compose 里,就没法直接进入 K3s 的 ingress 网络。除非你把 Harbor 整体搬到 K3s 里,用 helm 或 manifest 方式部署,再通过 ingress 资源对外暴露,这样宿主机 80 端口就由 traefik 统一管理,Harbor 不再单独占用。
这个方案思路最正,但复杂度也最高。Harbor 有 core、registry、jobservice、portal、nginx 等十几个容器,数据卷、权限、密钥生成步骤都比 docker-compose 模式繁琐。对于个人开发环境,我不建议一上来就采用它。
2.4 方案选型对照表
| 方案 | 改动位置 | 对已有服务的影响 | 访问方式 | 推荐场景 |
|---|---|---|---|---|
| A 关闭 traefik | K3s 配置 | ingress 功能丢失 | 80 端口直连 | 节点专用于 Habor |
| B 改 Harbor 端口 | harbor.yml | 无 | ip:8080 | 节点还需要其他 ingress |
| C 接入 K3s | 应用架构 | 需要重新部署 K3s 工作负载 | ingress 统一入口 | 生产环境或完整集群场景 |
我在实际项目里优先采用的是方案A。因为单机开发机上,K3s 装下来主要是为了顺手跑一些容器服务,traefik 对我不是必需项,而 Harbor 反而是一天到晚要用到的东西。把 80 端口让给它,性价比最高。
3. 实操:Harbor 使用 80 端口的完整落地过程
3.1 前置检查:确认端口占用与处理
无论最终选择哪个方案,第一步都是确认端口占用情况,不要凭感觉。
先看当前系统里 80 端口是谁在监听:
bash复制ss -lntp | grep ':80'
如果显示 traefik/svclb 相关进程,确认是 K3s 占用。如果显示 nginx 或其他进程,也要记录下来。然后确认这台机器是不是还跑着别的 web 服务,比如 Apache、Nginx 或者面板类工具。很多人忘了自己之前装过这些,导致改完 K3s 后 Harbor 依然起不来。把所有监听 80 的进程都列出来,逐个处理,才能彻底释放端口。
3.2 步骤一:修改 K3s 启动配置释放端口
如果你在装 K3s 之前就想到了端口问题,最干净的命令是这种形式:
bash复制curl -sfL https://get.k3s.io | sh -s - --disable traefik
如果 K3s 已经装好,可以修改配置文件后重启。K3s 的配置路径是 /etc/rancher/k3s/config.yaml,往里加一段:
yaml复制disable:
- traefik
然后重启 K3s 服务:
bash复制systemctl restart k3s
重启后验证 traefik 是否已经退出:
bash复制kubectl get pods -n kube-system | grep traefik
正常情况下应该查不到任何 traefik 相关 Pod。此时再检查端口:
bash复制ss -lntp | grep ':80'
如果没有输出,说明 80 端口已经释放。
需要提醒的是,关闭 traefik 后,如果你在 K3s 里还有别的 Ingress 资源,它们会失去入口,访问会失败。所以我一般会先问自己一句:这台机器上已有的服务里,哪些依赖 ingress?如果有,就改用方案B。
3.3 步骤二:Ubuntu 环境安装部署 Harbor
端口释放干净后,开始装 Harbor。以 Ubuntu 系统为例,先确认环境里有没有 docker-compose。新版 Harbor 的 install.sh 需要 docker-compose 命令,如果没装,先处理这里。
Ubuntu 上安装 docker-compose 的常见做法:
bash复制apt update
apt install -y docker-compose-v2
如果安装后命令名是 docker compose,而 Harbor 的 install.sh 找的是 docker-compose,可以做一个软链接:
bash复制ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
或者下载官方 docker-compose 二进制放到 /usr/local/bin/docker-compose 并加执行权限。
接着下载 Harbor 离线安装包。建议从 Harbor 官方 GitHub releases 页面下载 harbor-offline-installer-v2.x.x.tgz,离线包的好处是避开了在线拉取镜像时网络不稳定的问题。
解压并进入目录:
bash复制tar xzf harbor-offline-installer-v2.11.1.tgz
cd harbor
复制配置模板:
bash复制cp harbor.yml.tmpl harbor.yml
修改 harbor.yml,核心字段如下:
yaml复制hostname: your-server-ip
http:
port: 80
# 如果没有可用的 HTTPS 证书,把 https 段整块注释掉
# https:
# port: 443
# certificate: /your/cert.pem
# private_key: /your/key.pem
harbor_admin_password: Harbor12345
data_volume: /data/harbor
特别注意:hostname 不要写 localhost,否则后面 docker login 用 IP 访问会报证书或 hostname 不匹配。如果只有内网 IP,就直接写内网 IP。
然后执行安装:
bash复制./install.sh
安装过程会先做配置校验,再生成 docker-compose.yml,最后启动一系列容器。等待所有容器状态为 healthy 或者 running 即表示成功。
看到这里有人会问,harbor.yml 里 http.port 写 80,会不会还和什么冲突?实际上我们已经在 3.2 把 traefik 关了,80 端口是空的,所以没问题。如果这步报 bind: address already in use,回头再检查是不是有别的进程也在占 80。
3.4 步骤三:功能验证
Harbor 启动后,用浏览器访问 http://服务器IP/,能看到登录页面就说明 web 界面正常。默认账号 admin,密码是 harbor.yml 里配置的 harbor_admin_password。
更关键的验证是能不能正常 push 镜像:
先登录:
bash复制docker login 服务器IP -u admin -p Harbor12345
打一个测试镜像:
bash复制docker pull nginx:1.25
docker tag nginx:1.25 服务器IP/library/my-nginx:1.0
docker push 服务器IP/library/my-nginx:1.0
注意 library 是 Harbor 默认的公共项目名。push 成功后可以在页面的项目里看到镜像,说明整个链路已经打通。
这里有一个小细节:docker login 不写端口时,默认走 https。如果你在 harbor.yml 里把 https 段注释掉了,只开了 http 80,那么 docker 客户端默认会使用 https 协议连过去,导致 login 报错。解决方法是把地址写成 IP:80,或者在 /etc/docker/daemon.json 里加 insecure-registries。这个点很容易踩,我后面问题排查章节再展开。
4. 备选:不改 K3s 时如何让 Harbor 跑起来
4.1 修改 Harbor 端口到 8080 的配置差异
如果你的节点还有别的业务依赖 traefik,不能关,那最实际的做法就是把 Harbor 的端口改掉。
在 harbor.yml 里把 http.port 改成 8080:
yaml复制http:
port: 8080
执行 ./install.sh,启动之后,Harbor 就只占用宿主机的 8080 端口。访问地址变成 http://服务器IP:8080/,docker login 也必须带端口:
bash复制docker login 服务器IP:8080 -u admin -p Harbor12345
这里额外提醒:如果 8080 端口也被别的服务占了,换成 9080、5000 都可以,但要注意 5000 可能和某些内部 registry 的默认端口概念混淆,虽然实际上没有冲突,但排查时容易造成误解,个人建议用 8080 或 8088。
改端口这个方案,最大的代价不是技术,而是后续使用的规范性。团队成员每台机器都要记得在地址里写端口,一旦有人漏了,就会默认走 443,报错后又得排查半天。
4.2 用 Nginx 反代统一访问入口
改端口带来的体验问题,可以在宿主机上再加一个 Nginx,把 80 的请求转发到 8080 的 Harbor 上。这样对外还是 http://IP/,内部实际由 Harbor 的 8080 响应,用户无感知。
Nginx 反代的配置片段:
nginx复制server {
listen 80;
server_name harbor.example.com;
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
注意 client_max_body_size 一定要设为 0,否则推送大镜像时会被 Nginx 的默认 1MB 请求体限制卡住。这个是我实际踩过的坑,docker push 一个几百 MB 的镜像时,客户端直接报 413 Request Entity Too Large,当时排查了很久。
配置完成后重新加载 Nginx,访问体验就和 80 端口直连没有区别了。
5. 典型报错与问题排查实录
5.1 "harbor happened in config validation" 到底怎么查
很多人在执行 ./install.sh 时,会遇到一段以 harbor happened in config validation 开头的错误信息。我第一次见到这个报错时也是一头雾水,因为这段文字看起来像是个笼统的配置校验挂了,并没有直接告诉你哪里错了。
正常情况下,报错信息后面会紧跟具体的失败原因,比如:
- invalid config file path: xxx
- protocol must be http or https
- missing required attribute: harbor_admin_password
- data_volume 指定的目录不存在
排查思路是打开 harbor.yml,逐项检查。
第一,端口是否合法。http.port 必须是 0 到 65535 的整数,且不要和 https.port 相同。如果你写了 https 段,但 certificate 和 private_key 指向的文件不存在,同样会报配置校验错误。
第二,hostname 是否为空。hostname 是必填项,不能为空或 localhost。
第三,data_volume 路径。如果这个目录不存在,install.sh 一般会尝试创建,但如果上级目录权限不足,也会报错。
最直接的排查办法是在执行 install.sh 前先用 prepare 命令单独验证:
bash复制./prepare
prepare 脚本会调用底层逻辑生成配置文件,如果配置有问题,这里就会抛出具体异常,比 install.sh 里的提示更精确。看到具体错误后,改完配置重新执行 install.sh 即可。
5.2 端口明明腾出来了,为何还是启动失败
这是让人最有挫败感的情况:K3s 的 traefik 关了,ss 查询 80 端口没有任何输出,Harbor 的 install.sh 也走完了,但 docker-compose 拉起容器的时候还是报错。别看日志只显示了 bind: address already in use,背后可能的原因有好几个。
最常见的:你改的配置没有真正生效。比如你改了 /etc/rancher/k3s/config.yaml 后没有重启 K3s,或者 systemctl restart k3s 之后 K3s 又自动把 traefik 拉起来了。验证方法是在改完后再次执行 kubectl get pods -n kube-system | grep traefik,确认 Pod 彻底没了再继续下一步。
另外一个容易被忽略的问题是 IPv6。ss -lntp 默认显示 IPv4 的监听情况,有些服务会同时监听 :::80(IPv6 任意地址),如果你只过滤了 :80 没过滤 :::80,容易漏看。建议用完整命令:
bash复制ss -lntp | grep -E ':80\b'
如果显示 :::80,说明确实还有进程占用。
还有一种情况是 Docker 的 docker0 网桥本身或者系统里残留的旧容器占用端口。执行 docker ps -a 看看有没有之前启动失败残留的 Harbor 容器,有的话先 docker rm 掉,再重新执行 install.sh。
5.3 docker login 常见的证书和协议报错
Harbor 部署成功之后,镜像推送阶段是第二个事故高发区。常见报错是:
code复制x509: certificate signed by unknown authority
这个错误通常是因为 docker 客户端默认对非 TLS 的 registry 不信任。解决方式是把地址加入 insecure-registries。
编辑 /etc/docker/daemon.json:
json复制{
"insecure-registries": ["192.168.1.100", "192.168.1.100:8080"]
}
重启 docker:
bash复制systemctl restart docker
重启后重新执行 docker login。这里注意是重启 docker daemon,不是重启 Harbor 容器。如果你把 Harbor 也跑在 docker 里,重启 daemon 会导致 Harbor 容器全部被重启,属于正常现象,等它起来再登录即可。
实际操作中,我建议在 harbor.yml 的 hostname 和 docker push 的地址上,尽量直接用 IP 加端口,而不要用带前缀的域名,避免后续 DNS 解析和证书匹配的问题。如果团队使用域名,务必保证域名解析正确,并让 Harbor 的 https 段配了真正的证书,否则就得走 insecure-registries。
5.4 常见问题速查表
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| install.sh 报 bind: address already in use | 80 被 K3s traefik 或 nginx 占用 | 关闭 traefik 或改 Harbor 端口,再查端口占用 |
| harbor happened in config validation | harbor.yml 端口/路径/字段非法 | 执行 ./prepare 查看具体报错,逐项修配置 |
| docker login 报 x509 错误 | 未配置 insecure-registries | 修改 daemon.json,加入 IP:端口,重启 docker |
| docker push 报 413 | 反代 Nginx 限制请求体 | 设置 client_max_body_size 0 |
| Harbor 容器都是 Exited | 系统重启后端口被抢 | 检查宿主机其他服务,或配置开机自启顺序 |
| 页面打不开但端口有监听 | 防火墙或安全组拦截 | 检查 ufw/iptables/云平台安全组规则 |
6. 写在后头的一些经验
这套端口冲突问题,我前前后后处理过不下五次,每次都是不同的环境,但根因都一模一样:缺少端口规划。如果你准备在一台机器上同时跑 K3s 和 Harbor,我建议动手前先想清楚端口分配。K3s 的 traefik 占 80/443,Harbor 的 nginx 也要占 80/443,两个都是硬冲突。我的习惯是:宿主机如果主要服务是 Harbor,就果断在 K3s 里 --disable traefik;如果 K3s 才是主角,Harbor 就老老实实改端口走反代,别在 80 上死磕。
最后再分享一个实用小技巧:Harbor 部署完之后,记得把 harbor.yml 所在目录做一次归档备份,因为后续升级或者迁移时,install.sh 需要基于原始的 harbor.yml 重新生成配置。很多人部署完就把安装包删了,等到要升级才发现还得再下载一遍,很折腾。把这份配置当作关键资产管理起来,端口规划加上配置备份,这个组合能帮你省掉后面大量的排障时间。
