Gitea · 自托管 Git 服务
Gitea 是集群里的自托管 Git 服务,提供仓库托管、LFS、Issue、Packages 与 Actions。它以单副本 StatefulSet 跑在 gitea namespace,使用 rootless 镜像,仓库与 LFS 数据落在 NFS PVC 上,元数据存进集群内的 PostgreSQL。HTTP 通过 Cloudflare Tunnel + traefik 对外,SSH 通过 MetalLB LoadBalancer 暴露。
secret/gitea/* 种到 Vault(gitea/security、gitea/admin、gitea/database 等),ExternalSecret 才能拉到 gitea-secrets,否则 Pod 起不来。具体 vault kv put 命令见 manifest 目录下的 VAULT_SETUP.md。概览
- Name
- 镜像
- Description
gitea/gitea:1.26.2-rootless(容器以 1000:1000 非 root 运行)
- Name
- namespace
- Description
gitea
- Name
- 工作负载
- Description
- StatefulSet,1 副本(
gitea-0)
- Name
- 存储
- Description
- volumeClaimTemplate
data30Gi,nfs-clientstorageClass,挂载到/var/lib/gitea
- Name
- 端口
- Description
- 容器 3000(http)、2222(ssh,rootless 不能绑 1024 以下端口)
- Name
- 数据库
- Description
- 外部 PostgreSQL,
postgres-primary.postgres.svc.cluster.local:5432,库名gitea
- Name
- HPA / VPA / PDB
- Description
- 无(单副本不适用)
namespace 上有 ResourceQuota(gitea-quota:pods 5、requests.cpu 1 / memory 1Gi、limits.cpu 4 / memory 4Gi、PVC 3、storage 60Gi)和 LimitRange(gitea-limits:容器默认 request 50m/64Mi、limit 200m/256Mi,上限 2 CPU / 2Gi)。
部署形态
StatefulSet 在主容器之前跑两个 initContainer,再启动 Gitea 主进程:
| 容器 | 镜像 | 作用 |
|---|---|---|
pg-bootstrap(init) | postgres:18-alpine | 用 Postgres 超级用户凭据幂等创建 gitea 角色与数据库(CREATE … IF NOT EXISTS 守卫),每次 Pod 启动都跑 |
init-config(init) | busybox:1.38 | 把 ConfigMap 里的 app.ini 拷到可写 emptyDir,再挂到主容器 /etc/gitea |
gitea(主) | gitea/gitea:1.26.2-rootless | Gitea 本体,监听 3000(http) / 2222(ssh) |
主容器请求 CPU 100m / 内存 256Mi,上限 CPU 2 / 内存 1Gi。Pod 带 reloader.stakater.com/auto: "true" 注解,app.ini ConfigMap 变更时由 reloader 重启 Pod。readiness 与 liveness 探针都打 /api/healthz(http 端口)。
pg-bootstrap 的脚本有两处针对环境的处理,改的时候别踩回去:用纯 SQL 语句而非 DO $$ … $$ 块(BusyBox ash 在 sh -ec 下会把 $$ 折叠成 $,导致 Postgres 语法错误);并在跑真正的 SQL 前用 psql -tAc 'SELECT 1' 重试探测最多 30 次,绕开 kube-router 在 Pod 启动后异步装 iptables 规则导致的 5–15s "Connection refused" 窗口。init-config 则是因为 Gitea 入口脚本会 chmod /etc/gitea/app.ini,直接挂只读 ConfigMap subPath 会失败,所以先拷进可写 emptyDir。
配置与依赖
非密钥配置放在 gitea-config ConfigMap 的 app.ini 里;所有敏感值通过 GITEA__<section>__<KEY> 环境变量在运行时注入并覆盖 app.ini(env 优先级高于文件)。
ConfigMap app.ini 关键点:
[server]DOMAINgitea.yldm.tech、ROOT_URLhttps://gitea.yldm.tech/、HTTP_PORT 3000;SSH 容器内监听 2222(SSH_LISTEN_PORT)、对外宣告 22(SSH_PORT),SSH_DOMAINgitea-ssh.yldm.tech,内置 SSH server 开启,LFS 开启。[database]DB_TYPE = postgres,HOST 指向postgres-primary,库/用户均为gitea,SSL_MODE = disable。[security]INSTALL_LOCK = true、PASSWORD_HASH_ALGO = pbkdf2_hi、DISABLE_GIT_HOOKS = true。[service]关闭注册(DISABLE_REGISTRATION = true)、邮箱默认私有。- 启用 Packages、Actions(
DEFAULT_ACTIONS_URL = github)、metrics、oauth2、bleve issue/repo 索引;mailer 关闭,session 用 file provider。
依赖的密钥来自两个 ExternalSecret,都走 ClusterSecretStore vault-backend:
| Secret | remoteRef.key | 用途 |
|---|---|---|
gitea-secrets | gitea/security / gitea/admin / gitea/database | SECRET_KEY、INTERNAL_TOKEN、LFS_JWT_SECRET、OAUTH2_JWT_SECRET、METRICS_TOKEN、DB_PASSWORD、管理员账号 |
gitea-pg-admin | database/prod/postgres | Postgres 超级用户 user/password,仅供 pg-bootstrap 建角色和库 |
注入主容器的环境变量包括 GITEA__security__SECRET_KEY、GITEA__security__INTERNAL_TOKEN、GITEA__server__LFS_JWT_SECRET、GITEA__oauth2__JWT_SECRET、GITEA__metrics__TOKEN、GITEA__database__PASSWD,全部 secretKeyRef 自 gitea-secrets。
访问与监控
namespace 内有四个 Service:
| Service | 类型 | 端口 | 说明 |
|---|---|---|---|
gitea-http | ClusterIP | 3000 | traefik ingress 的后端 |
gitea-headless | Headless | 3000 / 2222 | StatefulSet 的 headless service |
gitea-metrics | ClusterIP | 3000(metrics) | 专给 ServiceMonitor,带 app.kubernetes.io/component: metrics 标签独占选择 |
gitea-ssh | LoadBalancer | 22 → 2222 | MetalLB 暴露 SSH |
HTTP 入口:Ingress gitea,ingressClassName: traefik,host gitea.yldm.tech,cert-manager 签 letsencrypt-prod 证书(gitea-tls),external-dns 把记录指向 Cloudflare Tunnel(cloudflare-proxied: true,target 为 cfargotunnel 域名)。为大仓库 push 放宽了 body size(proxy-body-size: "0")和读写超时(600s)。
SSH 入口:gitea-ssh 是 MetalLB LoadBalancer,external-dns 在 gitea-ssh.yldm.tech 发布非代理 A 记录指向动态分配的 LB IP(Cloudflare 不能代理裸 SSH,所以这条只在 LAN / WireGuard / 路由器端口转发下可达,不走 Cloudflare 公网边缘)。该 Service 保留默认 externalTrafficPolicy: Cluster——L2 + 单副本下用 Local 会在 MetalLB 选到没跑 Pod 的节点时丢流量。
监控:ServiceMonitor gitea 选择 gitea-metrics,scrape /metrics(30s 间隔),用 gitea-secrets 里的 METRICS_TOKEN 做 Bearer 授权([metrics] 段要求 Authorization: Bearer <token>)。无 PrometheusRule。
备份
CronJob gitea-backup 每天 03:00 跑(concurrencyPolicy: Forbid,错开 DNF 与 argo db-backup 的 04:00)。流程:initContainer kubectl-dl(curlimages/curl)从官方源下载 kubectl 二进制;主容器 minio/mc 通过 RBAC 授予的 pods/exec 权限在 gitea-0 内执行 gitea dump --skip-index --type tar.gz -f -,把 dump 流式 pipe 给 mc,直传到 MinIO database-backups/gitea/<日期>/,再清理 7 天前的旧备份。
gitea dump 必须在 gitea-0 实例内跑(同时读 config + DB + LFS 才一致);--skip-index 跳过可重建的 bleve 索引(约 632M);-f - 流式输出避免本地落约 13G 文件。mc pipe 多段缓冲会 OOM,所以备份容器把内存上限提到 2Gi(超过 LimitRange 默认 256Mi)。MinIO 凭据来自 ExternalSecret gitea-backup-minio(Vault minio/root)。
注意事项
gitea-ssh 用的 MetalLB LB IP(.220)由 MetalLB L2 speaker 做 ARP 应答。按 CLAUDE.md,若 metallb speaker 挂了,这些 LB IP(含 traefik ingress .221、游戏服 .222)会一起不可达——SSH 连不上时先排查 metallb,而非 Gitea 本身。镜像 tag 由 argocd-image-updater 管理(semver 严格 ^\d+\.\d+\.\d+$),不要手动 pin。但 Gitea 跨大版本可能涉及数据库 schema 迁移,bare tag bump 不一定安全,bump 前确认升级路径。
返回 infrastructure 总览