K8s クラスタ上に HA 構成の Gitea を構築する

gitea は helm チャートがあり k8s クラスタ上に簡単にデプロイできる。High Availability を参照することで HA 構成も構築可能。
HA 構成ではデータベースやメモリキャッシュも外部に外出しして可用性を確保する。
name | product | 機能 |
---|---|---|
indexer | meilisearch | issue 等の検索 |
memory cache | dragonfly | セッションやキャッシュの保存 |
Relational DB | cloudnativePG | 全般的なデータ保存 |
storage | minio | assets などの保存 |
database, memory cache はデフォルトでは postgresql (mysql), redis が使用できるが、ここではせっかくなのでそれぞれ postgres 互換の cloudnativePG, redis 互換の dragonfly を使ってみる。
上記の product もそれぞれ helm chart でデプロイできるので、各 product を構築した後、gitea 設定をカスタマイズしてデプロイするという流れ。
概要図

Meilisearch
Deploy Meilisearch using Helm
にしたがって helm でデプロイする。
helm repo add meilisearch https://meilisearch.github.io/meilisearch-kubernetes
helm upgrade -n meilisearch -i meilisearch meilisearch/meilisearch --create-namespace \
--set replicaCount=2

Dragonfly
Dragonfly operator をインストールした後、Instance と呼ばれる CRD を使ってデプロイする。
- https://www.dragonflydb.io/docs/getting-started/kubernetes-operator#installation
- https://www.dragonflydb.io/docs/managing-dragonfly/high-availability
operator のインストール
# Install the CRD and Operator
kubectl apply -f https://raw.githubusercontent.com/dragonflydb/dragonfly-operator/main/manifests/dragonfly-operator.yaml
Instance の作成
kubectl apply -f https://raw.githubusercontent.com/dragonflydb/dragonfly-operator/main/config/samples/v1alpha1_dragonfly.yaml
pod と service が作成される。
$ kubectl get pod,svc -l app=dragonfly-sample
NAME READY STATUS RESTARTS AGE
pod/dragonfly-sample-0 1/1 Running 0 22h
pod/dragonfly-sample-1 1/1 Running 0 22h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/dragonfly-sample ClusterIP 10.109.200.27 <none> 6379/TCP 22h
redis 互換であるので svc に対して redis-cli
で接続可能。
$ redis-cli -h 10.109.200.27
10.109.200.27:6379> get key
(nil)
10.109.200.27:6379> set key value
OK
10.109.200.27:6379> get key
"value"
10.109.200.27:6379>

CloudNativePG
dragonfly と同様 operator をインストールした後 Cluster という CRD を作成することでデプロイする。
operator をインストール。
helm repo add cnpg https://cloudnative-pg.github.io/charts
helm upgrade --install cnpg \
--namespace cnpg-system \
--create-namespace \
cnpg/cloudnative-pg
Cluster 作成前にデフォルトユーザーを記載した secret を作成。username/password はいずれも gitea.
apiVersion: v1
data:
username: Z2l0ZWE=
password: Z2l0ZWE=
kind: Secret
metadata:
name: gitea-postgres-secret
type: kubernetes.io/basic-auth
Cluster 作成時に bootstrap を実行して user や database を作成するために以下を参考に bootstrap
フィールドを設定。
Cluster CRD のマニフェストは以下。
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: cluster-example-initdb
spec:
instances: 3
bootstrap:
initdb:
database: gitea
owner: gitea
secret:
name: gitea-postgres-secret
storage:
size: 1Gi
これを適用すると instances に設定した数の pod と接続用の svc が作成される。
$ kubectl get pod,svc -n gadget -l cnpg.io/cluster=cluster-example-initdb
NAME READY STATUS RESTARTS AGE
pod/cluster-example-initdb-1 1/1 Running 0 21h
pod/cluster-example-initdb-2 1/1 Running 0 21h
pod/cluster-example-initdb-3 1/1 Running 0 21h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cluster-example-initdb-r ClusterIP 10.102.12.26 <none> 5432/TCP 21h
service/cluster-example-initdb-ro ClusterIP 10.107.192.79 <none> 5432/TCP 21h
service/cluster-example-initdb-rw ClusterIP 10.101.109.103 <none> 5432/TCP 21h
CloudNativePG は postgres と互換性があるので psql などで接続できる。
$ psql "postgresql://gitea:gitea@10.101.109.103:5432/gitea"
psql (14.11 (Ubuntu 14.11-0ubuntu0.22.04.1), server 16.2 (Debian 16.2-1.pgdg110+2))
WARNING: psql major version 14, server major version 16.
Some psql features might not work.
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
gitea=>
gitea=> \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+----------+----------+---------+-------+-----------------------
gitea | gitea | UTF8 | C | C |
postgres | postgres | UTF8 | C | C |
template0 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | C | C | =c/postgres +
| | | | | postgres=CTc/postgres
(4 rows)
gitea=>

Minio
minio は operator をインストールした後、tenant (aws のリージョンみたいなもの) を作成する。
まず operator をデプロイ。
helm repo add minio-operator https://operator.min.io
helm install \
--namespace minio-operator \
--create-namespace \
operator minio-operator/operator
operator をデプロイすると operator の console アクセス用の svc などが作成される。
次に tenant を作成する。ドキュメントでは operator console を使って作成する方法が推奨されているが、community が管理する tenant 用 helm chart があるのでこちらを使ってデプロイする。
chart 設定ファイルを取得。
helm show values minio-operator/tenant > values.yml
ここでは tenant 名を ap-northeast-1 とする。また、デフォルトでは pod 4 つ、pvc は 1 pod につき 4 つ作成されるが、ここでは pod 2, pvc 2 に変更する。
tenant:
name: ap-northeast-1
pools:
###
# The number of MinIO Tenant Pods / Servers in this pool.
# For standalone mode, supply 1. For distributed mode, supply 4 or more.
# Note that the operator does not support upgrading from standalone to distributed mode.
- - servers: 4
+ - servers: 2
###
# Custom name for the pool
name: pool-0
###
# The number of volumes attached per MinIO Tenant Pod / Server.
- volumesPerServer: 4
+ volumesPerServer: 2
###
デプロイ
helm install -n minio --create-namespace minio minio-operator/tenant -f values.yml
これで ap-northeast-1 tenant 内に 2 つの minio pod が起動する。
$ kubectl get pod,svc,pvc
NAME READY STATUS RESTARTS AGE
pod/ap-northeast-1-pool-0-0 2/2 Running 0 35s
pod/ap-northeast-1-pool-0-1 2/2 Running 0 35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/ap-northeast-1-console ClusterIP 10.100.94.142 <none> 9443/TCP 36s
service/ap-northeast-1-hl ClusterIP None <none> 9000/TCP 36s
service/minio ClusterIP 10.104.135.47 <none> 443/TCP 36s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/data0-ap-northeast-1-pool-0-0 Bound pvc-c2a427a6-9ed0-4705-bb36-6bb9703b6ce4 10Gi RWO openebs-hostpath <unset> 9m14s
persistentvolumeclaim/data0-ap-northeast-1-pool-0-1 Bound pvc-74bef1ac-f740-4fd5-a1a7-84b0fc991512 10Gi RWO openebs-hostpath <unset> 9m14s
persistentvolumeclaim/data1-ap-northeast-1-pool-0-0 Bound pvc-c5ac58ef-d09d-4ab2-9642-f0df356f9d06 10Gi RWO openebs-hostpath <unset> 9m14s
persistentvolumeclaim/data1-ap-northeast-1-pool-0-1 Bound pvc-e79e49a2-8279-4dc2-935e-19d955651527 10Gi RWO openebs-hostpath <unset> 9m14s
svc の ap-northeast-1-console にアクセスすることで webUI を使用できるが、ここでは minio client の mc
を使って bucket を作成する。
mc のインストール
curl https://dl.min.io/client/mc/release/linux-amd64/mc -o mc
chmod +x mc
sudo mv mc /usr/local/bin/mc
mc --help
作った tenant を mc の alias に設定。書式は以下。
mc alias set [alias_name] [endpoint] [user] [password]
username/password は tenant operator の values.yaml に記載されている。デフォルトではそれぞれ minio
minio123
。接続先は headless の service/myminio-hl
を指定する。
mc alias set minio https://ap-northeast-1-hl.minio.svc.cluster.local:9000 minio minio123
もしくは minio svc を宛先に指定しても ok。この場合は --insecure
をつける。
mc alias set minio https://10.104.135.47 minio minio123 --insecure
gitea セットアップ時に使用するため tenant に bucket gitea
を作成。
$ mc mb minio/gitea
Bucket created successfully `minio/gitea`.
# minio svc の場合
# mc mb --insecure minio/gitea
$ mc ls minio/
[2024-04-21 06:12:14 GMT] 0B gitea/

Gitea
準備がかなり長くなったが必要なものが揃ったので gitea をカスタムしてデプロイする。
helm repo add gitea-charts https://dl.gitea.com/charts/
helm show values gitea-charts/gitea > values.yml
values.yml の中身を変更していく。
indexer を meilisearch に指定。
gitea:
indexer:
ISSUE_INDEXER_CONN_STR: http://meilisearch.meilisearch.svc.cluster.local:7700
ISSUE_INDEXER_ENABLED: true
ISSUE_INDEXER_TYPE: meilisearch
REPO_INDEXER_ENABLED: false
メモリキャッシュを dragonfly に指定。
gitea:
config:
queue:
TYPE: redis
CONN_STR: redis://dragonfly-sample.dragonfly.svc.cluster.local:6379
session:
PROVIDER: redis
PROVIDER_CONFIG: redis://dragonfly-sample.dragonfly.svc.cluster.local:6379
cache:
ENABLED: true
ADAPTOR: false
HOST: redis://dragonfly-sample.dragonfly.svc.cluster.local:6379
redis-cluster:
enabled: false
RDB を cloudnativePG に指定。
gitea:
config:
database:
DB_TYPE: postgres
HOST: cluster-example-initdb-rw.cnpg.svc.cluster.local:5432
NAME: gitea
USER: gitea
PASSWD: gitea
postgresql-ha:
enabled: false
外部ストレージを minio に指定
persistence:
enabled: false
accessModes:
- ReadWriteMany
gitea:
config:
picture:
AVATAR_STORAGE_TYPE: minio
storage:
STORAGE_TYPE: minio
SERVE_DIRECT: false
MINIO_ENDPOINT: ap-northeast-1.minio.svc.cluster.local:9000
MINIO_LOCATION: ap-northeast-1
MINIO_ACCESS_KEY_ID: minio
MINIO_SECRET_ACCESS_KEY: minio123
MINIO_BUCKET: gitea
MINIO_USE_SSL: true
MINIO_INSECURE_SKIP_VERIFY: true
デプロイ
helm install -n gitea gitea gitea-charts/gitea -f values.yml --create-namespace
これでひとまず動く gitea がデプロイされる。
以降は補助機能や使い勝手をカスタマイズしていく。

LDAP
gitea デプロイ時に LDAP サーバー接続設定もまとめて行う。
bindpass, binduser は secret に保存する
apiVersion: v1
kind: Secret
metadata:
name: gitea-ldap-secret
type: Opaque
stringData:
bindDn: "uid=admin,ou=people,dc=ldap,dc=centre,dc=com"
bindPassword: password
values.yml を編集して LDAP サーバーのホストや bind, ユーザー検索フィルターなどを追加する。
gitea:
ldap:
- name: "LLDAP"
securityProtocol: unencrypted
host: ldap.centre.com
port: 3890
userSearchBase: "ou=people,dc=ldap,dc=centre,dc=com"
userFilter: "(&(objectClass=person)(|(uid=%[1]s)(mail=%[1]s)))"
adminFilter: "(memberof=cn=gitea_admin,ou=groups,dc=ldap,dc=centre,dc=com)"
emailAttribute: mail
existingSecret: gitea-ldap-secret # 上記で作成した secret 名
usernameAttribute: uid
firstnameAttribute: givenName
surnameAttribute: sn
emailAttribute: mail
avatarAttribute: jpegPhoto
synchronizeUsers: true
helm の README に乗っていないプロパティは admin の add-ldap: Add new LDAP (via Bind DN) authentication source
などの引数から読み取る。項目は -
を削除して大文字でつなぐことで対応する。例えば --firstname-attribute
は firstnameAttribute
となる。
なお、Gitea の UI から追加するときは LDAP 側の group に基づいて自動で org に所属するように設定することができるが、この機能はまだ未対応らしい。gitea CLI でこの機能が実装されていないため helm chart でも指定できない。

OAuth
oauth を設定する場合は LDAP と同様に values.yml の gitea.oauth
に設定していく。
gitea:
oauth:
- name: authelia
provider: "openidConnect"
key: gitea
existingSecret: gitea-oauth-secret
autoDiscoverUrl: "https://auth.example.com/.well-known/openid-configuration"
iconUrl: https://gitea.ops.com/assets/authelia.png
groupClaimName: groups
adminGroup: admin
groupTeamMap: '{"gitea_developer": {"developer": []}}'
oauth の client の secret も k8s secret として作成し、value.yml では existingSecret
に secret 名を記載する。
apiVersion: v1
kind: Secret
metadata:
name: gitea-oauth-secret
type: Opaque
stringData:
secret: test
oauth のサインインボタンに独自の画像を設定する場合は gitea コンテナ内に画像をマウントする必要があるが、configmap の binaryData を使えば可能。
例えば authelia.png
をアイコンに設定する場合はまず configmap を作成。
kubectl create configmap gitea-icons \
--save-config \
--from-file=authelia.png=authelia.png
gitea コンテナ内の /data/gitea/public/assets/authelia.png
にマウントするため values.yml に追加。
extraVolumes:
- name: gitea-icons
configMap:
name: gitea-icons
items:
- key: "authelia.png"
path: "authelia.png"
extraContainerVolumeMounts:
- name: gitea-icons
mountPath: "/data/gitea/public/assets"
gitea:
oauth:
- name: authelia
iconUrl: https://gitea.ops.com/assets/authelia.png
LDAP や OAuth は記事を書いた際に docker で構築した設定を参考にしているが、これらを k8s にデプロイすればこちらも HA 構成にできる。
authelia は helm chart があるのでコレを使えばよさそう。
LLDAP は現時点で公式の chart がなく HA 構成も対応してないので、k8s にデプロイする際は openldap の chart などを使う必要がある。

UI Theme
UI のテーマにカスタムテーマを使いたい場合は https://gitea.com/gitea/helm-chart#themes に指定方法が書いてあるが、試してもうまく適用されなかったので docker と同じ方法で設定する。
template の内容を書いた configmap を作成。
apiVersion: v1
kind: ConfigMap
metadata:
name: gitea-template
namespace: gitea
data:
body_outer_pre.tmpl: |
{{ if .IsSigned }}
{{ if and (ne .SignedUser.Theme "gitea") (ne .SignedUser.Theme "arc-green") }}
<link rel="stylesheet" href="https://theme-park.dev/css/base/gitea/{{.SignedUser.Theme}}.css">
{{end}}
{{ else if and (ne DefaultTheme "gitea") (ne DefaultTheme "arc-green") }}
<link rel="stylesheet" href="https://theme-park.dev/css/base/gitea/{{DefaultTheme}}.css">
{{end}}
コンテナ内の /data/gitea/templates/custom/body_outer_pre.tmpl
にマウントするよう values.yml を編集。 gitea.config.ui
に選択可能なテーマ一覧とデフォルトテーマを指定。
extraVolumes:
- name: gitea-theme-template
configMap:
name: gitea-template
items:
- key: "body_outer_pre.tmpl"
path: "body_outer_pre.tmpl"
extraContainerVolumeMounts:
- name: gitea-theme-template
mountPath: "/data/gitea/templates/custom"
gitea:
config:
ui:
THEMES: gitea,arc-green,plex,aquamarine,dark,dracula,hotline,organizr,space-gray,hotpink,onedark,overseerr,nord
DEFAULT_THEME: dracula
これで起動時にデフォルトテーマが適用される。各ユーザの外観から theme に指定した他のテーマに切替可能。
テーマは theme-park.dev にあるものが使える。内部的にはここにあるテーマを指定するとそれに対応した github 上の css を読み込む形式になっている。例えば dracula theme を指定した際は以下の css が読み込まれている。

Runner
Gitea は ver. 1.19 から Actions に対応しており、github workflow とほぼ同じ構文の yaml ファイルを repository に置くことでワークフローを実行できる。runner はこのワークフローの実行環境のこと。
github や gitlab の self-hosted runner と同様に gitea でも runner は自分で用意する必要がある。
helm chart では現時点で runner は未対応。ただ以下のような PR が作成されており先月にコメントされているので近いうちにマージされるかもしれない。
k8s 用の runner のマニフェストは以下にあるので、これを使うことで runner 自体は k8s 上にデプロイできる。
gitlab と同様に gitea runner でも以下のレベルでそれぞれ runner を登録できる。
You can register a runner in different levels, it can be:
Instance level: The runner will run jobs for all repositories in the instance.
Organization level: The runner will run jobs for all repositories in the organization.
Repository level: The runner will run jobs for the repository it belongs to.
基本的には gitea 側で作成したいレベルに応じて registration token を発行 → token を指定した runner pod を作成 → 使用可能という流れになる。
ここでは Instance level
(gitea 全体)で使用可能な runner を登録する。
admin ユーザーで gitea にログインし、Site Administration > Actions > Create New Runner を選択すると文字列の registration token が取得できるので、これを保存した k8s secret を作成する。
githea のマニフェスト例を使うと以下のようになる。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: act-runner-vol
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
stringData:
token: qCKPicvoWCfzch2hKEQsXNEIMrRsRe9UfSbjFXYL
kind: Secret
metadata:
name: runner-secret
type: Opaque
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: act-runner
name: act-runner
spec:
replicas: 1
selector:
matchLabels:
app: act-runner
strategy: {}
template:
metadata:
labels:
app: act-runner
spec:
restartPolicy: Always
volumes:
- name: runner-data
persistentVolumeClaim:
claimName: act-runner-vol
securityContext:
fsGroup: 1000
containers:
- name: runner
image: gitea/act_runner:nightly-dind-rootless
imagePullPolicy: Always
# command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- /opt/act/run.sh"]
env:
- name: DOCKER_HOST
value: tcp://localhost:2376
- name: DOCKER_CERT_PATH
value: /certs/client
- name: DOCKER_TLS_VERIFY
value: "1"
- name: GITEA_INSTANCE_URL
value: http://gitea-http.gitea.svc.cluster.local:3000
- name: GITEA_RUNNER_REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: runner-secret
key: token
securityContext:
privileged: true
volumeMounts:
- name: runner-data
mountPath: /data
runner pod 作成、登録に成功すると runner が追加される。
適当に repository を作成し、.gitea/workflow/demo.yml
を作って push する。
中身は https://docs.gitea.com/usage/actions/quickstart#use-actions と同じ。
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."
また、repository の設定から Action の有効化も設定しておく。
gitea -> runner への接続に成功すると Actions タブからワークフローの実行結果が確認できる。実行状況は runner pod のログからも確認可能。
なお runner を登録すると gitea pod で completed POST /api/actions/runner.v1.RunnerService/FetchTask
という大量のログが定期的に出力されるが、現時点では仕様通り通りの挙動とのこと。issue で改善の提案がされている。
Action の使用
github 上のパブリックレポジトリで公開されている Github Action であれば https://github.com/[owner]/[repo]@[version]
の書式でワークフロー内で使用できる。例えば python をセットアップして pip で依存パッケージをインストールするワークフローは以下。
---
name: Lint and static check codes
on: [push]
jobs:
check:
runs-on: ubuntu-22.04
name: Python test
steps:
- uses: https://github.com/actions/checkout@v3
- uses: https://github.com/actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install flake8 black mypy yamllint
- name: Flake8
run: flake8 *.py
- name: Black
run: black *.py
- name: Mypy
run: mypy *.py
- name: Yaml lint
run: yamllint .
初回実行時は action の tar.gz をダウンロードするため時間がかかるが、2 回目以降はキャッシュが効いてて短くなる。
::group::Installed versions
Version 3.11 was not found in the local cache
Version 3.11 is available for downloading
Download from "https://github.com/actions/python-versions/releases/download/3.11.9-8525206794/python-3.11.9-linux-22.04-x64.tar.gz"
Extract downloaded archive
[command]/usr/bin/tar xz --warning=no-unknown-keyword --overwrite -C /tmp/4693931b-bfc8-4669-95c8-59d620a2e360 -f /tmp/a67367ea-35a6-402d-9dc4-6969c6516735
Execute installation script
Check if Python hostedtoolcache folder exist...
Creating Python hostedtoolcache folder...
Create Python 3.11.9 folder
Copy Python binaries to hostedtoolcache folder
Create additional symlinks (Required for the UsePythonVersion Azure Pipelines task and the setup-python GitHub Action)
Upgrading pip...
github action が使えると github ワークフローの大部分を使い回せるので移行が楽になりそう。