🚀 玩转 Homelab:使用 Helm 在 K3s 中部署 Gitea,并接入外部 PostgreSQL
在自己的内网(Homelab)环境里,拥有一个轻量、好用、全加密(HTTPS)的代码托管系统是每个开发者的梦想。今天这篇博客就带大家复盘,如何利用 K3s、Traefik 网关以及外部自建的 PostgreSQL 数据库,一步步搭建属于自己的 Gitea。
我们将摒弃复杂的理论,用最纯粹的实战步骤,带你避开内网部署中的那些“隐藏大坑”。
🏗️ 整体架构一览
为了让整个系统足够轻量且好维护,我们采用了以下设计方案:
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
| flowchart TB subgraph Client ["开发者环境 (Client)"] Browser["浏览器 (HTTPS)"] GitCli["Git 客户端 (SSH)"] end
subgraph K3S ["K3s 集群边界 (K3s Cluster)"] direction TB subgraph Net ["网络与路由 (Ingress & Network)"] Traefik["Traefik Ingress Controller"] Middleware["force-https (Middleware)"] TLS["gitea-tls-secret (TLS Secret)"] end
subgraph Core ["核心计算 (Core Pod)"] Gitea["Gitea Pod (Rootless)"] DBSecret["postgres-secret (DB Secret)"] end
subgraph Outbound ["外部服务映射 (Egress)"] PGSVC["external-pg (Service)"] EPS["external-pg-slice (EndpointSlice)"] end end
subgraph External ["独立物理机/虚拟机"] PG[("外部 PostgreSQL 数据库")] end
%% 流量流向 Browser -->|443| Traefik Traefik -.->|1. 检查跳转| Middleware Traefik -.->|2. 解密证书| TLS Traefik ==>|3. 转发有效流量| Gitea
GitCli ==>|22| Gitea Gitea -.->|读取密码| DBSecret Gitea ==> PGSVC PGSVC --> EPS EPS ==>|跨网络访问| PG
|
核心服务:Gitea(采用官方轻量化 Rootless 镜像)。
数据存储:不使用集群内置的数据库,而是直接连内网独立的 PostgreSQL,实现数据解耦。
网络安全:使用内网自签名证书,通过 K3s 自带的 Traefik Ingress 实现 HTTP 自动强转 HTTPS。
Git 克隆:SSH 流量通过 K8s 的 LoadBalancer 服务直接穿透暴露,确保代码顺畅克隆。
🛠️ 第一步:打通数据孤岛(映射外部数据库)
在集群内部,我们希望像访问集群内服务一样访问外部的 PostgreSQL。这里我们使用 K8s 原生的 Service 和 EndpointSlice 来实现。
⚠️ 避坑指南:在 K8s 中,Service 和它的 EndpointSlice 必须处于同一个 Namespace(命名空间)下,否则流量无法正确转发!
创建 external-pg.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
| apiVersion: v1 kind: Service metadata: name: external-pg namespace: gitea spec: ports: - protocol: TCP port: 5432 targetPort: 5432 --- apiVersion: discovery.k8s.io/v1 kind: EndpointSlice metadata: name: external-pg-slice namespace: gitea labels: kubernetes.io/service-name: external-pg addressType: IPv4 ports: - protocol: TCP port: 5432 endpoints: - addresses: ["192.168.XX.XX"]
|
🔑 第二步:安全存储数据库密码
K8s 里的 Secret 用于存放敏感信息。
⚠️ 避坑指南:在 stringData 中,如果你的密码全是数字(例如:2026XXXX),必须用双引号 “” 包裹起来!否则 K8s 会把它当成数字类型而报错。
创建 gitea-postgres-secret.yaml:
1 2 3 4 5 6 7 8
| apiVersion: v1 kind: Secret metadata: name: postgres-secret namespace: gitea type: Opaque stringData: password: "你的数据库密码"
|
🔄 第三步:创建 Traefik HTTPS 强转中间件
为了让所有访问 http:// 的流量自动跳转到安全的 https://,我们需要给 Traefik 网关配置一个中间件。
创建 gitea-redirect-middleware.yaml:
1 2 3 4 5 6 7 8 9
| apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: name: force-https namespace: gitea spec: redirectScheme: scheme: https permanent: true
|
🔐 第四步:生成内网自签名 TLS 证书
因为我们使用的是内网自定义域名 gitea.yourdomain.local,无法直接通过公网的 Let’s Encrypt 签发免费证书,因此我们需要在本地通过 OpenSSL 签发一套私有的 TLS 证书,并将其转存为 K8s 的 Secret 供 Traefik 调度。
我们可以编写一个自动化 Shell 脚本 generate-gitea-secrets.sh 快速搞定这件事:
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
| #!/bin/bash
DOMAIN="gitea.yourdomain.local" NAMESPACE="gitea"
echo "==== 正在为 $DOMAIN 生成内网自签名证书 ===="
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \ -keyout gitea.key \ -out gitea.crt \ -subj "/CN=${DOMAIN}/O=MyHomelab" \ -addext "subjectAltName = DNS:${DOMAIN}"
echo "==== 正在将证书导入至 K8s Secret ===="
kubectl create namespace ${NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret tls gitea-tls-secret \ --cert=gitea.crt \ --key=gitea.key \ -n ${NAMESPACE} \ --dry-run=client -o yaml | kubectl apply -f -
echo "==== TLS 证书导入成功! ===="
|
💡 这里的避坑小细节
-addext “subjectAltName = DNS:…”:这行非常关键!现代浏览器(如 Chrome 和 Safari)对证书的安全审查非常严格。如果证书里没有包含 subjectAltName(简称 SAN)扩展属性,就算你强行信任了证书,浏览器依然会顽固地提示 ERR_CERT_COMMON_NAME_INVALID。
有效期 -days 3650:直接给足 10 年有效期,避免内网服务因为证书过期频繁断流。
赋予执行权限并运行它,我们就能在 gitea 命名空间下得到合法的 gitea-tls-secret 了:
1 2
| chmod +x generate-gitea-secrets.sh ./generate-gitea-secrets.sh
|
📝 第五步:编写核心 Gitea Helm 配置文件
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
| postgresql: { enabled: false } postgresql-ha: { enabled: false } valkey-cluster: { enabled: false } valkey: { enabled: false }
gitea: config: APP_NAME: "我的内网 Homelab Gitea" database: DB_TYPE: postgres HOST: "external-pg:5432" NAME: "你的数据库名" USER: "你的数据库用户名" server: ROOT_URL: "https://gitea.yourdomain.local/" SSH_DOMAIN: "gitea.yourdomain.local" SSH_PORT: 22
additionalConfigFromEnvs: - name: GITEA__DATABASE__PASSWD valueFrom: secretKeyRef: name: postgres-secret key: password
persistence: enabled: true size: 20Gi
service: http: type: ClusterIP port: 3000 ssh: type: LoadBalancer port: 22
ingress: enabled: true className: "traefik" annotations: kubernetes.io/ingress.class: traefik traefik.ingress.kubernetes.io/router.middlewares: gitea-force-https@kubernetescrd hosts: - host: gitea.yourdomain.local paths: - path: / pathType: Prefix tls: - secretName: gitea-tls-secret hosts: - gitea.yourdomain.local
|
现在我们可以使用helm命令去部署gitea了:
1 2 3
| helm repo add gitea https://dl.gitea.com/charts/ helm repo update helm upgrade --install gitea gitea/gitea -f gitea-values.yaml -n gitea
|
🚀 第六步:一键部署与避坑复盘
1. 数据库的大坑:权限未初始化
在第一次启动时,Gitea 可能会在初始化容器(init-container)处崩溃。查看日志后发现报错:pq: password authentication failed。
原因:外部 PostgreSQL 还没有为 Gitea 创建专属的用户和数据库。
解决办法:登录外部数据库,执行以下 SQL 即可解决:
1 2 3
| CREATE USER 你的用户名 WITH PASSWORD '你的密码'; CREATE DATABASE 你的数据库名 OWNER 你的用户名; GRANT ALL PRIVILEGES ON DATABASE 你的数据库名 TO 你的用户名;
|
2. 执行安装命令
在执行 Helm 安装时,要先检查本地的仓库别名。如果你的本地仓库别名就叫 gitea,那么命令如下:
1 2 3 4 5 6 7 8
| helm repo update gitea
helm upgrade --install gitea gitea/gitea \ -f gitea-values.yaml \ -n gitea \ --create-namespace
|
3. 网络解析的大坑:DNS 找不到与 Traefik 路由刷新
部署成功后,访问域名可能会遇到两个网络问题:
NXDOMAIN 错误:因为是内网域名,公共 DNS 无法解析。
解决办法:在访问网页的电脑上修改 hosts 文件,将域名指向你的 K3s 节点 IP(如 192.168.XX.XX)。
🎉 总结
当看到浏览器顶部的绿色小锁(或自签名证书的安全提示),并成功跳转到全加密的 Gitea 欢迎界面时,所有的折腾都是值得的!
通过这次实践,我们不仅学会了如何部署一个软件,更深刻理解了 K8s 命名空间隔离、网络路由(Ingress+Middleware)、以及外部资源映射(EndpointSlice) 的底层逻辑。
希望这篇避坑指南能帮到同样在折腾 Homelab 的你!有任何问题,欢迎在评论区留言讨论。 👇