Самохостинг Gitea на k3s: полный боевой разбор

Хочется свой GitHub, но без лишних облаков и подписок. Gitea — легковесный, быстрый, и устанавливается за один helm install. k3s даёт вам полноценный Kubernetes на одной VPS за пять минут. Вместе они закрывают 90% потребностей небольшой команды или домашней лаборатории. Разберём всё по шагам: от создания namespace до первого git push через SSH.

Что потребуется

  • VPS или bare-metal с Ubuntu 22.04 / Debian 12 (минимум 2 ГБ RAM, 20 ГБ диска)
  • k3s уже запущен: kubectl get nodes возвращает Ready
  • helm v3 установлен
  • Домен или IP с возможностью открыть порты 80, 443, 22 (или нестандартный SSH)
  • kubectl настроен (kubeconfig в ~/.kube/config)

Если k3s ещё не стоит — одна команда решает вопрос:

curl -sfL https://get.k3s.io | sh -
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER:$USER ~/.kube/config

k3s в стандартной поставке включает Traefik как Ingress Controller и local-path как StorageClass — нам оба пригодятся.

Namespace и StorageClass

Изолируем всё в отдельный namespace. Так проще чистить, обновлять и отлаживать.

kubectl create namespace gitea
kubectl config set-context --current --namespace=gitea

Проверим доступные StorageClass:

kubectl get storageclass

На ванильном k3s вы увидите local-path (default). Он хранит данные на хосте в /var/lib/rancher/k3s/storage/. Для продакшена на одном узле это нормально, пока вы делаете регулярные бэкапы. Если у вас несколько узлов — смотрите в сторону Longhorn или NFS-провизионера.

PostgreSQL в кластере

Gitea умеет работать с SQLite, MySQL и PostgreSQL. SQLite — это удобно для теста, но при нагрузке выше десятка пушей в минуту начнутся локи. Ставим PostgreSQL через официальный Helm-чарт Bitnami.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

Создадим файл значений postgres-values.yaml:

auth:
  username: gitea
  password: "ЗАМЕНИТЕ_НА_СИЛЬНЫЙ_ПАРОЛЬ"
  database: gitea

primary:
  persistence:
    storageClass: local-path
    size: 10Gi
  resources:
    requests:
      memory: 256Mi
      cpu: 100m
    limits:
      memory: 512Mi

metrics:
  enabled: false

Устанавливаем:

helm install postgres bitnami/postgresql \
  --namespace gitea \
  --values postgres-values.yaml \
  --wait

Флаг --wait держит команду до тех пор, пока под не перейдёт в Running. Через минуту-две должно появиться:

kubectl get pods -n gitea
# NAME                    READY   STATUS    RESTARTS   AGE
# postgres-postgresql-0   1/1     Running   0          90s

Запомните Service-имя — оно нам понадобится: postgres-postgresql.gitea.svc.cluster.local.

Установка Gitea через Helm

Официальный чарт Gitea живёт на gitea-charts:

helm repo add gitea-charts https://dl.gitea.com/charts/
helm repo update

Создадим основной файл конфигурации gitea-values.yaml. Это главный рычаг — прочитайте его целиком перед применением:

# --- Общие настройки ---
replicaCount: 1

image:
  tag: "1.22"   # Укажите последний стабильный тег

# --- Ingress ---
ingress:
  enabled: true
  className: traefik
  annotations:
    traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
    cert-manager.io/cluster-issuer: letsencrypt-prod   # если cert-manager установлен
  hosts:
    - host: git.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: gitea-tls
      hosts:
        - git.example.com

# --- Service ---
service:
  http:
    type: ClusterIP
    port: 3000
  ssh:
    type: NodePort
    port: 22
    nodePort: 30022   # внешний порт SSH

# --- База данных ---
postgresql:
  enabled: false   # используем внешний PostgreSQL

postgresql-ha:
  enabled: false

gitea:
  config:
    database:
      DB_TYPE: postgres
      HOST: postgres-postgresql.gitea.svc.cluster.local:5432
      NAME: gitea
      USER: gitea
      PASSWD: "ЗАМЕНИТЕ_НА_СИЛЬНЫЙ_ПАРОЛЬ"   # тот же, что в postgres-values.yaml
    server:
      DOMAIN: git.example.com
      ROOT_URL: https://git.example.com
      SSH_DOMAIN: git.example.com
      SSH_PORT: 30022
      START_SSH_SERVER: "true"
    repository:
      DEFAULT_BRANCH: main
    service:
      DISABLE_REGISTRATION: "false"   # отключите после регистрации администратора
      REQUIRE_SIGNIN_VIEW: "false"
    security:
      INSTALL_LOCK: "false"   # станет true после первого запуска мастера

# --- Хранилище ---
persistence:
  enabled: true
  storageClass: local-path
  size: 20Gi
  accessModes:
    - ReadWriteOnce

# --- Ресурсы ---
resources:
  requests:
    memory: 128Mi
    cpu: 100m
  limits:
    memory: 512Mi
    cpu: 500m

Несколько пояснений по ключевым параметрам:

service.ssh.type: NodePort — Traefik не проксирует TCP-трафик из коробки (нужен дополнительный IngressRouteTCP). NodePort проще: порт 30022 напрямую пробрасывается с хоста в под. Пользователи клонируют через ssh://[email protected]:30022/user/repo.git.

START_SSH_SERVER: "true" — Gitea поднимает встроенный SSH-сервер внутри пода. Это избавляет от необходимости монтировать хостовый /etc/ssh.

INSTALL_LOCK: "false" — при первом запуске Gitea покажет веб-мастер установки. После его прохождения значение автоматически станет true в app.ini. Если хотите пропустить мастер и всё настроить через values.yaml — сразу поставьте "true" и заполните все поля конфига.

Устанавливаем:

helm install gitea gitea-charts/gitea \
  --namespace gitea \
  --values gitea-values.yaml \
  --wait --timeout 5m

Смотрим на поды:

kubectl get pods -n gitea
# NAME                     READY   STATUS    RESTARTS   AGE
# postgres-postgresql-0    1/1     Running   0          5m
# gitea-7d9f8b5c4-xk2pv    1/1     Running   0          2m

Первый вход и настройка администратора

Откройте браузер на https://git.example.com. Если видите мастер установки — большинство полей уже заполнено из values.yaml. Обратите внимание:

  • Database type: PostgreSQL
  • Host: postgres-postgresql.gitea.svc.cluster.local:5432 — должен подтянуться автоматически
  • Admin account: создайте здесь же, не пропустите этот шаг

После завершения мастера Gitea перезапишет app.ini внутри PVC и установит INSTALL_LOCK = true.

Если предпочитаете CLI — создайте администратора через kubectl exec:

kubectl exec -n gitea deploy/gitea -- \
  /app/gitea/gitea admin user create \
  --username admin \
  --password "СИЛЬНЫЙ_ПАРОЛЬ" \
  --email [email protected] \
  --admin

Проверка SSH-доступа

Добавьте свой публичный ключ в Gitea (Settings → SSH / GPG Keys), затем:

ssh -T [email protected] -p 30022
# Hi admin! You've successfully authenticated, but Gitea does not provide shell access.

Клонирование:

git clone ssh://[email protected]:30022/admin/test-repo.git

Чтобы не указывать порт каждый раз, добавьте в ~/.ssh/config:

Host git.example.com
    User git
    Port 30022
    IdentityFile ~/.ssh/id_ed25519

После этого работает стандартный синтаксис: git clone [email protected]:admin/test-repo.git.

TLS без cert-manager

Если cert-manager не установлен, самый быстрый путь — установить его прямо сейчас:

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set installCRDs=true

Создайте ClusterIssuer для Let’s Encrypt:

# letsencrypt-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: traefik
kubectl apply -f letsencrypt-issuer.yaml

После этого аннотация cert-manager.io/cluster-issuer: letsencrypt-prod в Ingress начнёт работать: сертификат будет получен и обновляться автоматически.

Обновление Gitea

Helm-обновление — одна команда:

helm upgrade gitea gitea-charts/gitea \
  --namespace gitea \
  --values gitea-values.yaml \
  --wait

Перед мажорными обновлениями (например, 1.21 → 1.22) проверьте CHANGELOG на ломающие изменения. Gitea при старте автоматически выполняет миграции базы данных, но резервная копия перед обновлением — обязательна.

Бэкап

Gitea хранит данные в двух местах:

  1. PostgreSQL — все метаданные: пользователи, issues, pull requests, настройки
  2. PVC (/data внутри пода) — репозитории (bare git), аватары, вложения

Бэкап базы данных

kubectl exec -n gitea postgres-postgresql-0 -- \
  pg_dump -U gitea gitea | gzip > gitea-db-$(date +%Y%m%d).sql.gz

Бэкап репозиториев

POD=$(kubectl get pod -n gitea -l app.kubernetes.io/name=gitea -o jsonpath='{.items[0].metadata.name}')

kubectl exec -n gitea "$POD" -- \
  tar czf - /data/gitea/repositories | \
  cat > gitea-repos-$(date +%Y%m%d).tar.gz

Автоматизируйте через CronJob в кластере или простой cron на хосте с доступом к kubectl. Храните бэкапы вне ноды — на S3, Backblaze B2 или смонтированном NFS.

Встроенный backup-инструмент Gitea

Gitea умеет делать самосогласованный дамп:

kubectl exec -n gitea "$POD" -- \
  /app/gitea/gitea dump -c /data/gitea/conf/app.ini \
  --file /tmp/gitea-dump.zip --type zip

kubectl cp gitea/"$POD":/tmp/gitea-dump.zip ./gitea-dump-$(date +%Y%m%d).zip

Архив содержит и базу данных, и репозитории, и конфиг — удобно для полного восстановления на чистой машине.

Отладка типичных проблем

Pod в CrashLoopBackOff

kubectl logs -n gitea deploy/gitea --previous

Чаще всего причина: неверный пароль к PostgreSQL или опечатка в HOST. Сверьте значение PASSWD в gitea-values.yaml и postgres-values.yaml.

Gitea недоступна по домену

kubectl describe ingress -n gitea
kubectl get events -n gitea --sort-by='.lastTimestamp'

Убедитесь, что DNS домена указывает на IP вашей ноды, и порты 80/443 открыты в фаерволле:

ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 30022/tcp

SSH работает, но медленно

Внутри пода встроенный SSH-сервер Gitea может медленно резолвить клиентский IP. Добавьте в конфиг:

gitea:
  config:
    server:
      SSH_SERVER_HOST_KEYS: ssh/gitea.rsa,ssh/gitea.ed25519
    log:
      LEVEL: Warn

PVC остался после helm uninstall

Helm не удаляет PersistentVolumeClaim автоматически — это защита от случайной потери данных. Если хотите полную очистку:

helm uninstall gitea -n gitea
kubectl delete pvc -n gitea --all
kubectl delete pvc -n gitea data-postgres-postgresql-0

Интеграция с CI/CD

Gitea поддерживает Gitea Actions — совместимый с GitHub Actions движок. Включите в values.yaml:

gitea:
  config:
    actions:
      ENABLED: "true"
      DEFAULT_ACTIONS_URL: https://gitea.com

Запустите Act Runner рядом с Gitea:

helm install act-runner gitea-charts/act-runner \
  --namespace gitea \
  --set gitea.instanceURL=https://git.example.com \
  --set gitea.token="ВАШ_REGISTRATION_TOKEN"

Токен берётся из Gitea: Site Administration → Runners → Create new runner.

После этого .gitea/workflows/ci.yaml в репозитории работает по той же логике, что GitHub Actions, но полностью внутри вашей инфраструктуры.

Производительность и ресурсы

На одном узле с 4 ГБ RAM комфортно живут:

  • PostgreSQL: 256–512 МБ
  • Gitea: 128–256 МБ в покое, до 512 МБ при активных CI-прогонах

Если памяти мало — рассмотрите SQLite: для 1-5 разработчиков и нескольких десятков репозиториев разницы не заметите, зато сэкономите 200+ МБ.

Включите кэш в памяти для ускорения веб-интерфейса:

gitea:
  config:
    cache:
      ADAPTER: memory
      INTERVAL: 60
    session:
      PROVIDER: memory

При нескольких репликах (replicaCount: 2) переключитесь на Redis:

gitea:
  config:
    cache:
      ADAPTER: redis
      HOST: redis://gitea-redis-master:6379/0
    session:
      PROVIDER: redis
      PROVIDER_CONFIG: redis://gitea-redis-master:6379/1

И установите Redis через Bitnami: helm install gitea-redis bitnami/redis --namespace gitea --set auth.enabled=false.


Итого: у вас работающий Gitea на k3s с PostgreSQL, TLS, SSH-доступом и базовым планом бэкапов. Следующий шаг — настроить Gitea Actions и подключить внешний S3 для хранения артефактов. Но это уже отдельная история.

👁 Views: 225,652 · Unique visitors: 59,963