Clark Boylan 67662bb735 Run gitea with memcached cache adapter
By default gitea caches everything in memory using a Go hashmap. There
is suspicion that the now many persistent AI web crawlers cause this
hashmap to grow in ways that eventually cause the Go GC system to pause
the world in noticeable ways when loading pages. Restarting the gitea
services seems to temporarily reset things (as it should with an in
memory map) until we cross some threshold and things become slow again.

The good news is that gitea supports several backends (called adapters)
for the cache. We elect to use memcached because it is relatively
simple and has a FOSS license (unlike redis). The other alternative we
could consider is twoqueue which also caches within memory in the Go
runtime but allows for setting a size limit. I've gone with memcached
because it doesn't rely on Golang GC, but twoqueue is likely simpler if
we want to start there.

Note we also bump the job timeout to 5400 seconds (90 minutes) from 4800
seconds (80 minutes) because a run on ovh-gra1 timed out while running
testinfra test cases (the very end of the job). It is possible that
using memcache is slightly slower than using in process memory caching,
but the goal here isn't to make things faster it is to make things more
consistent over time. As long as memcached performance is within the
same ballpark and doesn't degrade over time this is acceptable.

Change-Id: Ie9ca246a8321fe84d9a1582e35cd4c5459b48bee
2025-02-28 10:49:27 -08:00

266 lines
8.5 KiB
YAML

- name: Ensure docker-compose directory exists
file:
state: directory
path: /etc/gitea-docker
mode: 0700
- name: Write docker-compose file
template:
src: docker-compose.yaml.j2
dest: /etc/gitea-docker/docker-compose.yaml
mode: 0600
- name: Ensure gitea volume directories exists
file:
state: directory
path: "/var/gitea/{{ item }}"
owner: 1000
group: 1000
loop:
- conf
- data
- logs
- certs
- db
- name: Write app.ini
template:
src: app.ini.j2
dest: /var/gitea/conf/app.ini
- name: Write mariadb conn limit config file
copy:
src: 99-max_conn_my.cnf
dest: /var/gitea/conf/99-max_conn_my.cnf
owner: root
group: root
mode: 0644
- name: Install distro packages
package:
name:
# TODO(clarkb) does the install-docker role handle these two packages?
- docker-compose
- python3-requests
# Installed to make checking memcached stats easy in testing and for
# human led debugging.
- netcat-openbsd
state: present
- name: Install reverse proxy
include_tasks: proxy.yaml
- name: Get list of image IDs pre pull
# The --quiet flag prints out only image IDs
command: docker image list --quiet
register: pre_pull_image_ids
- name: Run docker-compose pull
shell:
cmd: docker-compose pull
chdir: /etc/gitea-docker/
- name: Get list of image IDs post pull
# The --quiet flag prints out only image IDs
command: docker image list --quiet
register: post_pull_image_ids
- name: Stop/Start gitea safely for Gerrit replication
when: pre_pull_image_ids.stdout_lines|sort != post_pull_image_ids.stdout_lines|sort
block:
- name: Run docker-compose stop
shell:
cmd: docker-compose stop --timeout 60
chdir: /etc/gitea-docker/
- name: Run docker-compose up mariadb memcached gitea-web
shell:
cmd: docker-compose up -d --timeout 60 mariadb memcached gitea-web
chdir: /etc/gitea-docker/
# We wait here for the main gitea service to start before starting
# the ssh service. This is friendly to gerrit replication.
- name: Wait until the web service is sufficiently up to start ssh
uri:
url: "https://localhost:3000/api/v1/users/root"
validate_certs: false
status_code: 200, 404
register: root_user_check
delay: 1
retries: 300
until: root_user_check and root_user_check.status in (200, 404)
- name: Run docker-compose up gitea-ssh
shell:
cmd: docker-compose up -d --timeout 60 gitea-ssh
chdir: /etc/gitea-docker/
- name: Run docker prune to cleanup unneeded images
shell:
# Keep images around for 3 days before pruning them. Allows for
# easy rollback if necessary. Note "3d" seems to be rejected by
# docker but 72 hours == 3 days.
cmd: docker image prune -f --filter "until=72h"
# User management outside of service bringup to avoid confusion between
# the two stages.
- name: Check if root user exists
uri:
url: "https://localhost:3000/api/v1/users/root"
validate_certs: false
status_code: 200, 404
register: root_user_check
delay: 1
retries: 300
until: root_user_check and root_user_check.status in (200, 404)
- name: Create root user
when: root_user_check.status==404
block:
- name: Create root user
command: >
/usr/local/bin/docker-compose -f /etc/gitea-docker/docker-compose.yaml exec -T gitea-web
gitea admin user create --name root --password {{ gitea_root_password }} --email {{ gitea_root_email }} --admin
no_log: "{{ gitea_no_log }}"
- name: Check if gerrit user exists
uri:
url: "https://localhost:3000/api/v1/users/gerrit"
validate_certs: false
status_code: 200, 404
register: gerrit_user_check
- name: Create gerrit user
when: gerrit_user_check.status==404
no_log: true
uri:
url: "https://localhost:3000/api/v1/admin/users"
validate_certs: false
method: POST
user: root
password: "{{ gitea_root_password }}"
force_basic_auth: true
status_code: 201
body_format: json
body:
email: "gerrit@review.opendev.org"
full_name: Gerrit
login_name: gerrit
must_change_password: false
password: "{{ gitea_gerrit_password }}"
send_notify: false
source_id: 0
username: gerrit
- name: List keys to determine which updates are necessary.
uri:
user: root
password: "{{ gitea_root_password }}"
force_basic_auth: true
url: "https://localhost:3000/api/v1/users/gerrit/keys"
validate_certs: false
status_code: 200
register: gerrit_key_check
no_log: true
# We want to allow for multiple keys in order to do key rotations.
# Check if both keys are present. If a key is not present then add it
# to Gitea. Keep in mind the two keys may be the same in which case
# we can skip the second key creation. Finally clean up any keys
# that don't match the two keys. This allows us to do key rotations.
- name: Determine if key A and key B are already present
set_fact:
key_A_present: "{{ gerrit_key_check.json | selectattr('key', 'equalto', gitea_gerrit_public_key_A ) | list | length > 0 }}"
key_B_present: "{{ gerrit_key_check.json | selectattr('key', 'equalto', gitea_gerrit_public_key_B ) | list | length > 0 }}"
- name: Add gerrit ssh key A
when: not key_A_present
no_log: true
uri:
user: root
password: "{{ gitea_root_password }}"
force_basic_auth: true
url: "https://localhost:3000/api/v1/admin/users/gerrit/keys"
validate_certs: false
method: POST
status_code: 201
body_format: json
body:
key: "{{ gitea_gerrit_public_key_A }}"
read_only: false
title: "Gerrit replication key A"
- name: Add gerrit ssh key B
when: not key_B_present and gitea_gerrit_public_key_A != gitea_gerrit_public_key_B
no_log: true
uri:
user: root
password: "{{ gitea_root_password }}"
force_basic_auth: true
url: "https://localhost:3000/api/v1/admin/users/gerrit/keys"
validate_certs: false
method: POST
status_code: 201
body_format: json
body:
key: "{{ gitea_gerrit_public_key_B }}"
read_only: false
title: "Gerrit replication key B"
- name: List keys again to ensure key ids are correct for deletion.
uri:
user: root
password: "{{ gitea_root_password }}"
force_basic_auth: true
url: "https://localhost:3000/api/v1/users/gerrit/keys"
validate_certs: false
status_code: 200
register: gerrit_key_check
no_log: true
- name: Delete old gerrit ssh keys
when: existing_pubkey.key != gitea_gerrit_public_key_A and existing_pubkey.key != gitea_gerrit_public_key_B
no_log: true
uri:
user: root
password: "{{ gitea_root_password }}"
force_basic_auth: true
url: "https://localhost:3000/api/v1/user/keys/{{ existing_pubkey.id }}"
validate_certs: false
method: DELETE
status_code: 204
loop: "{{ gerrit_key_check.json }}"
loop_control:
loop_var: existing_pubkey
- name: Set up cron job to pack git refs
cron:
name: pack-git-refs
state: present
job: >
/usr/local/bin/docker-compose -f /etc/gitea-docker/docker-compose.yaml exec -T gitea-web
find /data/git/repositories/ -maxdepth 2 -name *.git -type d -execdir git --git-dir={} gc --quiet \;
minute: '{{ 59 | random(seed=inventory_hostname) }}'
hour: '{{ 23 | random(seed=inventory_hostname) }}'
weekday: '*/2'
- name: Create db backup dest
file:
state: directory
path: /var/backups/gitea-mariadb
mode: 0700
owner: root
group: root
- name: Set up cron job to backup the database
cron:
name: gitea-db-backup
state: present
user: root
job: >
/usr/local/bin/docker-compose -f /etc/gitea-docker/docker-compose.yaml exec -T mariadb
bash -c '/usr/bin/mysqldump --opt --databases gitea --single-transaction -uroot -p"$MYSQL_ROOT_PASSWORD"' |
gzip -9 > /var/backups/gitea-mariadb/gitea-mariadb.sql.gz
minute: 42
hour: 4
- name: Rotate db backups
include_role:
name: logrotate
vars:
logrotate_file_name: /var/backups/gitea-mariadb/gitea-mariadb.sql.gz
logrotate_compress: false
- name: Setup db backup streaming job
block:
- name: Create backup streaming config dir
file:
path: /etc/borg-streams
state: directory
- name: Create db streaming file
copy:
content: >-
/usr/local/bin/docker-compose -f /etc/gitea-docker/docker-compose.yaml exec -T mariadb
bash -c '/usr/bin/mysqldump --skip-extended-insert --databases gitea --single-transaction -uroot -p"$MYSQL_ROOT_PASSWORD"'
dest: /etc/borg-streams/mysql