Logo gitea traefik docker

En este apunte describo la instalación de Gitea (servidor GIT) y Traefik (terminar certificados SSL de LetsEncrypt), junto con Redis (cache) y MySQL (DB). Instalo todas las aplicaciones como contenedores Docker en un Linux Alpine que se ejecuta como máquina virtual en mi servidor KVM. En el apunte anterior expliqué qué es Gitea y cómo montarlo sobre una máquina virtual directamente (sin docker).


En esta ocasión he añadido Traefik y Redis a la foto. Todo son contenedores Docker sobre un Linux ligero (Alpine Linux), ejecutándose, a su vez, como máquina virtual sobre mi Hypervisor QEMU/KVM. Este apunte refleja mi instalación en producción en mi red casera. Crédito va para el autor de esta buena guía, setup a self-hosted git service with gitea.

Arquitectura de los microservicios


Networking

Antes de entrar en harina ahí va la configuración de networking de mi instalación casera:

Configuración del networking

Este tipo de instalación permitirá conectar con el servidor en mi red privada (LAN) pero también desde Internet (obligatorio para recibir el certificado SSL de Letsencrypt). Aunque casi siempre lo uso en local, así es como abro los puertos de internet bajo demanda, para renovar el certificado o usar el servicio desde internet puntualmente:

export GITIP=192.168.1.200
iptables -t nat -I PREROUTING -i ppp0 -p tcp -m multiport --dports 22,80,443 -j DNAT --to-destination ${GITIP}
iptables -I FORWARD -p tcp -m multiport --dports 22,80,443 -d ${GITIP} -j ACCEPT

En mi proveedor DNS tengo git.tudominio.com apuntando a mi dirección ip pública (dinámica) y en el Servidor DNS local en mi instalación casera está así:

  • traefik.tudominio.com --> 192.168.1.200
  • git.tudominio.com --> 192.168.1.200

He configurado el Servicio openssh del Alpine Linux para que escuche por otro puerto (22222), de modo que dejo libre el puerto 22 para git sobre SSH en el contenedor de gitea, y el acceso a la web de Gitea se realizara vía HTTPS por el puerto 443.


Máquina virtual con Alpine Linux

El primer paso es la creación de VM basada en Alpine Linux con todo lo necesario para ejecutar Docker. Sigo la documentación y el ejemplo descrito en el apunte Alpine para ejecutar contenedores. Llamo al equipo git.tudominio.com.

  • Una vez que termino la instalación del Alpine Linux modifico su /etc/hosts
    127.0.0.1	git.tudominio.com git traefik traefik.tudominio.com localhost.localdomain localhost
    ::1		localhost localhost.localdomain
    
  • Entro en la VM con mi usuario y creo el directorio gitea donde colocaré todos los ficheros de trabajo para los contenedores.
    git:~$ id
    uid=1000(luis) gid=1000(luis) groups=10(wheel),101(docker),1000(luis),1000(luis)
    git:~$ pwd
    /home/luis
    git:~$ mkdir gitea
    


Contenedor Traefik

Primero voy a crear solo la parte de Traefik, para asegurarme que funciona correctamente.

  • Creo el fichero donde se guardará el certificado de letsencrypt.
    git:~/gitea$ mkdir data_traefik
    git:~/gitea$ touch data_traefik/acme.json
    git:~/gitea$ chmod 600 data_traefik/acme.json
    
  • Creo docker-compose.yml, de momento solo pongo el primer serivicio gitea-traefik. Cambia tu HOST y TUCORREO@TUDOMINIO.com por el adecuado.
    version: '3.9'
    services:
    gitea-traefik:
      image: traefik:2.7
      container_name: gitea-traefik
      restart: unless-stopped
      volumes:
        - ./data_traefik/acme.json:/acme.json
        - /var/run/docker.sock:/var/run/docker.sock
      networks:
        - public
      labels:
        - 'traefik.enable=true'
        - 'traefik.http.routers.api.rule=Host(`git.tudominio.com`)'
        - 'traefik.http.routers.api.entrypoints=https'
        - 'traefik.http.routers.api.service=api@internal'
        - 'traefik.http.routers.api.tls=true'
        - 'traefik.http.routers.api.tls.certresolver=letsencrypt'
      ports:
        - 80:80
        - 443:443
      command:
        - '--api'
        - '--providers.docker=true'
        - '--providers.docker.exposedByDefault=false'
        - '--entrypoints.http=true'
        - '--entrypoints.http.address=:80'
        - '--entrypoints.http.http.redirections.entrypoint.to=https'
        - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
        - '--entrypoints.https=true'
        - '--entrypoints.https.address=:443'
        - '--certificatesResolvers.letsencrypt.acme.email=TUCORREO@TUDOMINIO.com'
        - '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
        - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
        - '--log=true'
        - '--log.level=INFO'
      logging:
        driver: "json-file"
        options:
          max-size: "1m"
    networks:
    public:
      name: public
    
  • Arranco el servicio
    git:~/gitea$ docker-compose up -d
    WARNING: Network public not found.
    Creating network "public" with the default driver
    Creating gitea-traefik ... done
    git:~/gitea$ docker logs -f --since 1h gitea-traefik
    time="2022-03-27T08:33:57Z" level=info msg="Configuration loaded from flags."
    time="2022-03-27T08:33:57Z" level=info msg="Starting provider aggregator aggregator.ProviderAggregator"
    time="2022-03-27T08:33:57Z" level=info msg="Starting provider *traefik.Provider"
    time="2022-03-27T08:33:57Z" level=info msg="Starting provider *docker.Provider"
    time="2022-03-27T08:33:57Z" level=info msg="Starting provider *acme.ChallengeTLSALPN"
    time="2022-03-27T08:33:57Z" level=info msg="Starting provider *acme.Provider"
    time="2022-03-27T08:33:57Z" level=info msg="Testing certificate renew..." providerName=letsencrypt.acme ACME CA="https://acme-v02.api.letsencrypt.org/directory"
    
  • Cuando funciona todo puedo seguir con el resto de servicios


Contenedores Gitea, Redis y MySQL

  • Preparo los directorios donde dejaré los datos.
    git:~/gitea$ mkdir -p data/gitea      # Directorio para los datos de Gitea
    git:~/gitea$ mkdir -p mysql           # Directorio para los datos de MySQL
    
  • Añado los tres servicios a docker-compose.yml. Lo adapto a mis necesidades:
    • DOMAIN y SSH_DOMAIN (urls para hacer clone con git)
    • ROOT_URL (Configurado para usar HTTPS, incluyendo mi dominio)
    • SSH_LISTEN_PORT (este es el puerto de escucha para SSH dentro del contenedor)
    • SSH_PORT (Puerto que expongo hacia el exterior y se usará para el clone)
    • DB_TYPE (Tipo de Base de Datos)
    • traefik.http.routers.gitea.rule=Host() (cabecera para llegar a gitea vía web)
    • ./data/gitea (Ruta para la persistencia de los datos. En mi caso utilizo dejo los datos dentro de la máquina virtual)
  • Así es como queda el fichero final:
    #
    # docker-compose.yaml para gitea,traefik,redis y mysql
    #
    version: '3.9'
    #
    # Servicios
    #
    services:
    
    #
    gitea-traefik:
      image: traefik:2.8.7
      container_name: gitea-traefik
      restart: unless-stopped
      depends_on:
        gitea:
          condition: service_healthy
    #         condition: service_started
      volumes:
        - ./data_traefik/acme.json:/acme.json
        - /var/run/docker.sock:/var/run/docker.sock
      networks:
        - public
      labels:
        - 'traefik.enable=true'
        - 'traefik.http.routers.api.rule=Host(`git.tudominio.com`)'
        - 'traefik.http.routers.api.entrypoints=https'
        - 'traefik.http.routers.api.service=api@internal'
        - 'traefik.http.routers.api.tls=true'
        - 'traefik.http.routers.api.tls.certresolver=letsencrypt'
      ports:
        - 80:80
        - 443:443
      command:
        - '--api'
        - '--providers.docker=true'
        - '--providers.docker.exposedByDefault=false'
        - '--entrypoints.http=true'
        - '--entrypoints.http.address=:80'
        - '--entrypoints.http.http.redirections.entrypoint.to=https'
        - '--entrypoints.http.http.redirections.entrypoint.scheme=https'
        - '--entrypoints.https=true'
        - '--entrypoints.https.address=:443'
        - '--certificatesResolvers.letsencrypt.acme.email=TUCORREO@TUDOMINIO.com'
        - '--certificatesResolvers.letsencrypt.acme.storage=acme.json'
        - '--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http'
        - '--log=true'
        - '--log.level=INFO'
      logging:
        driver: "json-file"
        options:
          max-size: "1m"
    
    #  Gitea
    gitea:
      container_name: gitea
      image: gitea/gitea:1.17.3
      restart: unless-stopped
      #  Nota: Pendiente de estudiar:
      #  https://docs.docker.com/compose/startup-order/
      depends_on:
        gitea-cache:
          condition: service_healthy
        db:
          condition: service_healthy
      environment:
        - APP_NAME="Gitea"
        - USER_UID=1000
        - USER_GID=1000
        - USER=git
        - RUN_MODE=prod
        - DOMAIN=git.tudominio.com
        - SSH_DOMAIN=git.tudominio.com
        - HTTP_PORT=3000
        - ROOT_URL=https://git.tudominio.com
        - SSH_PORT=22
        - SSH_LISTEN_PORT=22
        - DB_TYPE=mysql
        - GITEA__database__DB_TYPE=mysql
        - GITEA__database__HOST=db:3306
        - GITEA__database__NAME=gitea
        - GITEA__database__USER=gitea
        - GITEA__database__PASSWD=gitea
        - GITEA__cache__ENABLED=true
        - GITEA__cache__ADAPTER=redis
        - GITEA__cache__HOST=redis://gitea-cache:6379/0?pool_size=100&idle_timeout=180s
        - GITEA__cache__ITEM_TTL=24h
        - GITEA__mailer__ENABLED=true
        - GITEA__mailer__FROM="TUCORREO@TUDOMINIO.com"
        - GITEA__mailer__MAILER_TYPE=smtp
        - GITEA__mailer__HOST="smtp.gmail.com:465"
        - GITEA__mailer__IS_TLS_ENABLED=true
        - GITEA__mailer__USER="TUCORREO@TUDOMINIO.com"
        - GITEA__mailer__PASSWD="TUCONTRASEÑA"
        - GITEA__mailer__HELO_HOSTNAME="git.tudominio.com"
      ports:
        - "22:22"
      networks:
        - public
      healthcheck:
        test: ["CMD-SHELL", "wget -q --no-verbose --tries=1 --spider localhost:3000/explore/repos || exit 1"]
        interval: 5s
        start_period: 2s
        timeout: 3s
        retries: 55
      volumes:
        - ./data_gitea:/data
        - /etc/timezone:/etc/timezone:ro
        - /etc/localtime:/etc/localtime:ro
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.gitea.rule=Host(`git.tudominio.com`)"
        - "traefik.http.routers.gitea.entrypoints=https"
        - "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
        - "traefik.http.routers.gitea.service=gitea-service"
        - "traefik.http.services.gitea-service.loadbalancer.server.port=3000"
      logging:
        driver: "json-file"
        options:
          max-size: "1m"
    
    # Redis
    gitea-cache:
      container_name: gitea-cache
      image: redis:6-alpine
      restart: unless-stopped
      networks:
        - public
      healthcheck:
        test: ["CMD", "redis-cli", "ping"]
        interval: 15s
        timeout: 3s
        retries: 30
      logging:
        driver: "json-file"
        options:
          max-size: "1m"
    
    # MySQL
    db:
      image: mysql:8
      restart: unless-stopped
      environment:
        - MYSQL_ROOT_PASSWORD=gitea
        - MYSQL_USER=gitea
        - MYSQL_PASSWORD=gitea
        - MYSQL_DATABASE=gitea
      healthcheck:
        test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD
        start_period: 2s
        interval: 10s
        timeout: 2s
        retries: 55
      networks:
        - public
      volumes:
        - ./data_mysql:/var/lib/mysql
    #
    # Networking
    networks:
    public:
      name: public
    


Arrancar todos los servicios

  • Paro Traefik (opcional)
    git:~/gitea$ docker-compose stop
    
  • Arranco todos los microservicios (contenedores)
    git:~/gitea$ docker-compose up -d
    Creating network "public" with the default driver
    Starting gitea-traefik ... done
    Starting gitea-cache   ... done
    Starting gitea_db_1    ... done
    Starting gitea         ... done:
    git:~/gitea$ docker-compose ps
      Name                   Command                  State                                       Ports
    --------------------------------------------------------------------------------------------------------------------------------------
    gitea           /usr/bin/entrypoint /bin/s ...   Up             0.0.0.0:22->22/tcp,:::22->22/tcp, 3000/tcp
    gitea-cache     docker-entrypoint.sh redis ...   Up (healthy)   6379/tcp
    gitea-traefik   /entrypoint.sh --api --pro ...   Up             0.0.0.0:443->443/tcp,:::443->443/tcp, 0.0.0.0:80->80/tcp,:::80->80/tcp
    gitea_db_1      docker-entrypoint.sh mysqld      Up             3306/tcp, 33060/tcp
    
  • Cotilleo los logs con:
    git:~/gitea$ docker-compose logs
    :
    


Parametrizar Gitea

Me dirijo a mi ROOT_URL, https://git.tudominio.com y entro en la configuración inicial.

Conecto con https://git.tudominio.com
  • Sección de correo. Uso mi cuenta de GMail con contraseña de aplicación.
Configuración del correo
Si más adelante quieres retocar la configuración puedes hacerlo modificando /home/luis/gitea/data/gitea/gitea/conf/app.ini. Recuerda que antes es conveniente parar el contenedor.
git:~/gitea$ docker-compose stop gitea
git:~/gitea$ nano data/gitea/gitea/conf/app.ini
:
[mailer]
ENABLED        = true
HOST           = smtp.gmail.com:465
FROM           = tucorreo@gmail.com
USER           = tucorreo@gmail.com
PASSWD         = tucontraseñadeaplicación
MAILER_TYPE    = smtp
IS_TLS_ENABLED = true
HELO_HOSTNAME  = git.tudominio.com
:
git:~/gitea$ docker-compose start gitea
  • Configuro el usuario administrador
Configuración de la cuenta de administrador
  • Hacemos click en “Instalar Gitea”. Cuando termina vuelvo a escribir la ROOT_URL y deberías ver lo siguiente al estar ya autenticado.
Conecto con https://git.tudominio.com
  • Si intento conectar desde INTERNET con http://git.tudominio.com me redirige a https://git.tudominio.com y veré lo siguiente
Página al conectar desde Internet con https://git.tudominio.com


Configuración de la clave SSH

Creo una clave SSH para poder autorizar a mi cliente git a hacer push/pull/commit a/desde Gitea. Aquí un ejemplo sin contraseña. Copio la clave pública para añadirla a gitea.

$ ssh-keygen -f ~/.ssh/id_gitea -t rsa -C "Gitea" -q -N ""
$ cat .ssh/id_gitea.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDjQxGLslvGHPty3i+NbsY7krjcY/e/JDJ7B+Svpc1DaY8PGCMTegy95PDZf91yoSe39nEq3MVVP8YpMop/gH0WbC8UQO9vI9BTLy1sv+vlGnf+do3h2hsqPrJCuhyPWWLKYzaieXmWHT06Bbwfl9pqOGKxKrqU9+uzn+pGu+cXqSngDBX4Gd4yaJERL/7lprXybT19+lMKKoYddlomv5nNcT3f4r+OW9YYvgQs8UL8a2JwVk++RCL2cIXSG//D25RN/0HVX0twJZoOwg+apWx9nEYNeazVCJlJwhQZOLE2VH1WClWy5YNwXz04wmzjGmtKMf8gtqduiSJV1Xuh6zcgmJ9iv/Qayu18JqUPTHA0CErdWcDC68kaoTQlOht9ZTHyoy4wXNyB1hQnv+kT1IQUvM9mJQIDbgrqUdlZXSRL3CLHC9IImRaHG9mp0eGxb7ZtgEeuumMFhI0NNJwX6YCbbRcfAQgS8DBiyxPLKyjMnV1SLDnMbZJth0gjj9eXKUM= Gitea
En la sección de preferencias del usuario
Añado la clave pública


Crear un repositorio público.

Vuelvo a la página web y creo el repositorio hola-mundo

Añadir repositorio
Creo el repositorio hola-mundo

Antes de poder trabajar con él, configuro mi cliente ($HOME/.ssh/config) y añado lo siguiente:

# Gitea
Host git.tudominio.com
  IdentityFile ~/.ssh/id_gitea
  User git
  Port 22

A partir de ahora ya puedo hacer clone, push, pull, etc…

$ git clone git@git.tudominio.com:luis/hola-mundo.git
Cloning into 'hola-mundo'...
X11 forwarding request failed
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.


Swagger API

Gitea viene con Swagger por defecto y el endpoint es /api/swagger

Conecto con Swagger


Actualizaciones

Tenemos que decidir qué estrategia seguimos y cómo hacerlas. La estrategia te la dejo a tí, cada cual es muy particular con este tema. En mi caso actualizo de vez en cuando gitea, mientras que el resto de los servicios cuando vea que merece la pena o hace falta.

Siempre importante hacer un backup completo, suelo parar la VM, hago un backup de la imagen del disco y luego la arranco y hago el update.

❗❗ Ah! Los datos siempre en un volumen externo fuera de los contenedores Docker ❗❗

En mi caso los tengo así, todos los datos en directorios externos:

/home
└── luis
    └── gitea
        ├── data_gitea
        ├── data_mysql
        └── data_traefik


Actualización de Gitea

  • Averiguo versiones disponibles en el Hub de Docker -> Gitea (tags)
  • Modifico el fichero docker-compose.yml y cambio el número de versión, de 1.16.5 a 1.16.6
    :
    #  Gitea
    gitea:
      container_name: gitea
      image: gitea/gitea:1.16.6
    :
    
  • Al hacer un pull se baja la versión
    git:~/gitea$ docker-compose pull gitea
    
  • Paro los servicios, elimino los contenedores y vuelvo a arrancarlos.
    git:~/gitea$ docker-compose down
    git:~/gitea$ docker-compose up -d
    
  • Al conectar con el navegador deberías ver que hizo correctamente la actualización.
Versión de gitea


Actualización del resto: Traefik, redis, mysql

  • Con el resto de servicios es igual, busca las últimas versiones:
    • Última versión en Docker de Traefik
    • Última versión en Docker de Redis
    • Última versión en Docker de Mysql
  • El proceso es el mismo que el de Gitea, editas el docker-compose.yml, cambias la versión en la entrada image: ..., haces un docker-compose pull, paras (docker-compose down) y arrancas (docker-compose up -d) todo de nuevo.


Arranque de los servicios

Arranque durante el boot: No necesito crear un script para que durante el boot se arranque los servicios.

En el fichero docker-compose.ymltengo la orden restart: unless-stopped en todos los servicios. Una vez que ejecute docker-compose up -d el daemon volverá a arrancarlos tras el boot.

Orden de los servicios: A pesar de haber configurado el fichero docker-compose.yml para que se ejecuten en orden, he detectado que tras el boot algunas veces traefik no se comparta y lo he resuelto programando un rearranque del mismo.

  • Creo un script de rearranque /home/luis/gitea/restart-traefik.sh
    #!/bin/ash
    #
    cd /home/luis/gitea
    while true; do
      sleep 30
      wget -q --no-verbose --tries=1 --spider https://git.tudominio.com/explore/repos 2> /dev/null
      if [ "${?}" -ne "0" ]; then
          docker-compose restart gitea-traefik
      fi
    done
    
  • Creo el ejecutable /etc/local.d/0-restart-traefik.sh
    #!/bin/ash
    #
    sleep 10
    sudo -H -u luis ash -c /home/luis/gitea/restart-traefik.sh
    
  • Activo el servicio local
    rc-update add local
    


Instalar qemu-guest-agent

Para poder controlar bien el apagado y encendido de esta VM desde KVM/QEMU o Proxmox VE necesito instalar el paquete qemu-guest-agent

git:~# apk add qemu-guest-agent
git:~# rc-update add qemu-guest-agent default
 * service qemu-guest-agent added to runlevel default