Gitea · 自托管 Git 服务

Gitea 是集群里的自托管 Git 服务,提供仓库托管、LFS、Issue、Packages 与 Actions。它以单副本 StatefulSet 跑在 gitea namespace,使用 rootless 镜像,仓库与 LFS 数据落在 NFS PVC 上,元数据存进集群内的 PostgreSQL。HTTP 通过 Cloudflare Tunnel + traefik 对外,SSH 通过 MetalLB LoadBalancer 暴露。

概览

  • Name
    镜像
    Description
    gitea/gitea:1.26.2-rootless(容器以 1000:1000 非 root 运行)
  • Name
    namespace
    Description
    gitea
  • Name
    工作负载
    Description
    StatefulSet,1 副本(gitea-0
  • Name
    存储
    Description
    volumeClaimTemplate data 30Gi,nfs-client storageClass,挂载到 /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-rootlessGitea 本体,监听 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] DOMAIN gitea.yldm.tech、ROOT_URL https://gitea.yldm.tech/、HTTP_PORT 3000;SSH 容器内监听 2222(SSH_LISTEN_PORT)、对外宣告 22(SSH_PORT),SSH_DOMAIN gitea-ssh.yldm.tech,内置 SSH server 开启,LFS 开启。
  • [database] DB_TYPE = postgres,HOST 指向 postgres-primary,库/用户均为 giteaSSL_MODE = disable
  • [security] INSTALL_LOCK = truePASSWORD_HASH_ALGO = pbkdf2_hiDISABLE_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

SecretremoteRef.key用途
gitea-secretsgitea/security / gitea/admin / gitea/databaseSECRET_KEY、INTERNAL_TOKEN、LFS_JWT_SECRET、OAUTH2_JWT_SECRET、METRICS_TOKEN、DB_PASSWORD、管理员账号
gitea-pg-admindatabase/prod/postgresPostgres 超级用户 user/password,仅供 pg-bootstrap 建角色和库

注入主容器的环境变量包括 GITEA__security__SECRET_KEYGITEA__security__INTERNAL_TOKENGITEA__server__LFS_JWT_SECRETGITEA__oauth2__JWT_SECRETGITEA__metrics__TOKENGITEA__database__PASSWD,全部 secretKeyRefgitea-secrets

访问与监控

namespace 内有四个 Service:

Service类型端口说明
gitea-httpClusterIP3000traefik ingress 的后端
gitea-headlessHeadless3000 / 2222StatefulSet 的 headless service
gitea-metricsClusterIP3000(metrics)专给 ServiceMonitor,带 app.kubernetes.io/component: metrics 标签独占选择
gitea-sshLoadBalancer22 → 2222MetalLB 暴露 SSH

HTTP 入口:Ingress giteaingressClassName: 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-dlcurlimages/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)。

注意事项

镜像 tag 由 argocd-image-updater 管理(semver 严格 ^\d+\.\d+\.\d+$),不要手动 pin。但 Gitea 跨大版本可能涉及数据库 schema 迁移,bare tag bump 不一定安全,bump 前确认升级路径。

返回 infrastructure 总览

评论