本文成文于 2019年9月,将介绍如何使用 Traefik v1 搭建易于维护管理的 Gogs 。

原计划是替换家中 HomeLab 的代码仓库,但由于 GitLab CI 的良好体验,家里的 HomeLab 最终还是选择继续使用 GitLab。

这篇文章也就沉入了草稿箱,最近在折腾 Traefik 升级和测试服务器,遇到了一些相关的小需求,故将内容更新了一些后发布出来,希望能帮到有需要的同学。

写在前面

一直以来,都在使用 GitLab 作为团队/个人的仓库工具,随着版本的不断升级,GitLab 的界面功能越来越强大,消耗的服务器资源也越来越多。

最近将 GitLab Community Edition 升级到了 12.3+ ,服务器上依旧是丝般顺滑,但是家里 UPS 显示服务器待机功率默默上去了 10w。

这 10w 消耗的电费是小,但是原本静音的服务器,开始了轻微的风扇转动,这就有些不能忍了,于是有了使用更轻量应用替换 GitLab 的想法。

前置准备

Gogs 自身支持 HTTPS、支持挂在 SSL 证书,但是考虑到可维护性,这个事情交托给 Traefik 来处理。

Gogs 默认数据库使用的是 SQLite,轻量有余,但是作为重要数据的数据后端却不是那么安全,从官方网站的“如何修复数据库”可以看到挂掉的可能性还是不少的,所以我们要将其替换。

Gogs 默认的缓存方案是应用本身的内存,一般来说足够应付个人/小团队使用,但是为了进一步提高性能和健壮性,我们将缓存功能从应用主体解耦,交托于 Redis 进行处理。

那么开始配置 Gogs 依赖的软件和环境吧。

配置 MySQL 数据库

使用 compose 配置数据库非常简单,二十行以内解决战斗:

version: '3.6'

services:

  db:
    image: mysql:5.7.16
    restart: always
    expose:
      - 3306
    volumes:
      # 标准 Linux 系统下使用
      #   - /etc/localtime:/etc/localtime:ro
	  #   - /etc/timezone:/etc/timezone:ro
      - ./mysql:/var/lib/mysql
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: gogs
      MYSQL_DATABASE: gogs
      MYSQL_USER: gogs
      MYSQL_PASSWORD: gogs
      TZ: Asia/Shanghai

配置 Redis 内存缓存

使用 compose 搞定 Redis 更为简单,因为我们不需要将缓存持久化,所以不到二十行就完事了:

  cache:
    image: redis:5.0-alpine
    restart: always
    expose:
      - 6379
    environment:
      TZ: Asia/Shanghai
    # volumes:
      # 标准 Linux 系统下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro

配置 Gogs 应用

不需要 Traefik 、MySQL、Redis 的 Gogs 编排文件显得十分简单:

version: '3.6'

services:

  app:
    image: gogs/gogs:0.11.91
    restart: always
    ports:
      - 22:22
      - 80:3000
    volumes:
      # 标准 Linux 系统下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro
      - ./data/:/data/
    labels:
      - "traefik.enable=true"
      - "traefik.port=3000"
      - "traefik.frontend.rule=Host:${GOGS_DOMIAN}"
      - "traefik.frontend.entryPoints=http,https"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.SSLProxyHeaders=X-Forwarded-Proto:https"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.frameDeny=true"

networks:
  traefik:
    external: true

但是这样配置的应用,显然少了“应用健康检查”、“仓库数据安全存储”、“页面高性能响应”的能力。

定制网络环境

想让 Gogs 、Redis、MySQL 作为一个整体一起正常工作,又不受到其他应用干扰,需要做几件事:

  • 将它们放置相同的网段
  • 仅对外暴露 Gogs ,对外隐藏 MySQL、Redis

想要达到这个效果,需要修改 docker-compose.yml 文件,定义 networks

version: '3.6'

services:

  app:
    networks:
      - traefik
      - gogs

  db:
    networks:
      - gogs


  cache:
    networks:
      - gogs

networks:
  gogs:
    internal: true
  traefik:
    external: true

上面的示例代码中,我们声明了两个网络环境,分别为私有网络 gogs,用于 Gogs 和 MySQL、Redis 互通;外部的网络 traefik,用于暴露 Web 服务、Git SSH 服务给用户。

配置服务域名

应用网路互通后,Gogs 可以通过 Docker 赋予的容器名称访问 MySQL、Redis,或者使用 gogs 随机分配的内网地址进行数据交互。

然而这两种方案都不是特别利于维护,一旦容器扩展/重建后,容器名称会发生变化、分配的 IP 地址也会发生变化。

所以这里可以使用 compose 组网声明 MySQL、Redis 的内网域名。

version: '3.6'

services:

  app:
    links:
      - db:mysql.gogs.lab.com
      - cache:cache.gogs.lab.com

然后在 Gogs 容器中访问上面的域名就能够直接访问到 MySQL、Redis 啦。

解决 Gogs 启动时因为依赖服务未就绪报错

如果你直接启动包含三个应用的编排文件,可能会遇到 Gogs 报错,所以可以配合给三个应用都添加健康检查,以及启动依赖关系来解决问题:

version: '3.6'

services:

  app:
    depends_on:
      - db
      - cache
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:3000 || exit 1"]


  db:
    healthcheck:
      test: ["CMD-SHELL", "/etc/init.d/mysql status"]
      interval: 30s

  cache:
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

解决多网卡情况下 Traefik 概率不工作的问题

现在,你可能会发现一贯很灵敏的 Traefik 出现了偶尔不工作的问题,原因是 Traefik 有时将端口暴露到了 gogs 私有网络网卡上,解决方案很简单,声明 Traefik 工作使用的网卡就成:

version: '3.6'

services:

  app:
    labels:
      - "traefik.docker.network=traefik"

避免反代情况下 Gogs 寻找服务域名

根据之前的经验,反代的应用有可能会向公网 DNS 寻求帮助,查找服务域名,比如 gogs.lab.com ,为了避免这种情况发生,应用出现转半天转不开的情况,我们可以选择让 Gogs 容器的服务域名的解析地址映射为本地:

version: '3.6'

services:

  app:
    extra_hosts:
      - "${GOGS_DOMIAN}:127.0.0.1"

持久化数据文件

相比较“默认方案”直接映射 /data 整个大目录,如果将子目录单独映射,则可以更好的控制应用数据迁移、配置更新。

version: '3.6'

services:

  app:
    volumes:
      - ./app.ini:/data/gogs/conf/app.ini:ro
      - ./logs:/data/gogs/data/log
      - ./data/avatars:/data/gogs/data/avatars
      - ./data/ssh:/data/ssh
      - ./data/git:/data/git

定制页面模版

官方文档中提到我们可以修改 custom/templates/inject/public/css 下的文件,来定制页面展示。

但是容器中,这块实际的目录却有一些变化,如果你有定制模版的需求,可以参考下面的配置解决问题。

version: '3.6'

services:

  app:
    volumes:
     - ./data/custom/template/head.tmpl:/app/gogs/templates/inject/head.tmpl
      - ./data/custom/template/footer.tmpl:/app/gogs/templates/inject/footer.tmpl
      - ./data/custom/inject-assets/:/app/gogs/public/inject-assets/

限制 Gogs 的日志文件大小

加上了健康检查的 Gogs ,日志会随着时间慢慢变大,而这里日志对于我们解决问题没有丝毫帮助,因该被丢弃:

app_1    | [Macaron] 2019-09-28 07:07:04: Started GET / for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:07:04: Completed GET / 302 Found in 7.6542ms
app_1    | [Macaron] 2019-09-28 07:07:04: Started GET /user/login for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:07:04: Completed GET /user/login 200 OK in 16.5518ms
app_1    | [Macaron] 2019-09-28 07:07:34: Started GET / for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:07:34: Completed GET / 302 Found in 2.892ms
app_1    | [Macaron] 2019-09-28 07:07:34: Started GET /user/login for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:07:34: Completed GET /user/login 200 OK in 10.2743ms

配置 Gogs 应用日志输出选项,给出一个“最大尺寸”限制即可:

version: '3.6'

services:

  app:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"

完整配置

将上述所有内容合并,完整的 docker-compose.yml 配置文件如下:

version: '3.6'

services:

  app:
    image: ${DOCKER_GOGS_IMAGE}
    restart: always
    networks:
      - traefik
      - gogs
    expose:
      - 3000
    ports:
      - 22:22
    links:
      - db:${MYSQL_HOST}
      - cache:${REDIS_HOST}
    depends_on:
      - db
      - cache
    labels:
      - "traefik.enable=true"
      - "traefik.port=3000"
      - "traefik.frontend.rule=Host:${GOGS_DOMIAN}"
      - "traefik.frontend.entryPoints=http,https"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.SSLProxyHeaders=X-Forwarded-Proto:https"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.frameDeny=true"
      - "traefik.docker.network=traefik"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
    extra_hosts:
      - "${GOGS_DOMIAN}:127.0.0.1"
    volumes:
      # 标准 Linux 系统下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro
      - ./app.ini:/data/gogs/conf/app.ini:ro
      - ./logs:/data/gogs/data/log
      - ./data/avatars:/data/gogs/data/avatars
      - ./data/ssh:/data/ssh
      - ./data/git:/data/git
      # 根据自己需求使用
      # - ./data/custom/template/head.tmpl:/app/gogs/templates/inject/head.tmpl
      # - ./data/custom/template/footer.tmpl:/app/gogs/templates/inject/footer.tmpl
      # - ./data/custom/inject-assets/:/app/gogs/public/inject-assets/
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:3000 || exit 1"]
      interval: 5s


  db:
    image: ${DOCKE_MYSQL_IMAGE}
    restart: always
    networks:
      - gogs
    expose:
      - 3306
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MYSQL_ROOT_PASSWORD: gogs
      MYSQL_DATABASE: gogs
      MYSQL_USER: gogs
      MYSQL_PASSWORD: gogs
      TZ: Asia/Shanghai
    volumes:
      # 标准 Linux 系统下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro
      - ./mysql:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "/etc/init.d/mysql status"]
      interval: 30s


  cache:
    image: ${DOCKER_REDIS_IMAGE}
    restart: always
    networks:
      - gogs
    expose:
      - 6379
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
    environment:
      TZ: Asia/Shanghai
    # volumes:
      # 标准 Linux 系统下使用
      # - /etc/localtime:/etc/localtime:ro
      # - /etc/timezone:/etc/timezone:ro

networks:
  gogs:
    internal: true
  traefik:
    external: true

和配置文件搭配使用的 .env 环境变量文件内容如下:

DOCKER_GOGS_IMAGE=gogs/gogs:0.11.91
GOGS_DOMIAN=gogs.lab.com

DOCKE_MYSQL_IMAGE=mysql:5.7.16
MYSQL_HOST=mysql.gogs.lab.com

DOCKER_REDIS_IMAGE=redis:5.0-alpine
REDIS_HOST=cache.gogs.lab.com

Gogs 使用的 app.ini 配置文件内容:

APP_NAME = Private Repo
RUN_USER = git
RUN_MODE = prod

[database]
DB_TYPE  = mysql
HOST     = mysql.gogs.lab.com:3306
NAME     = gogs
USER     = gogs
PASSWD   = gogs
SSL_MODE = disable
PATH     = data/gogs.db

[cache]
ADAPTER=redis
INTERVAL=60
HOST=network=tcp,addr=cache.gogs.lab.com:6379,password=,db=0,pool_size=100,idle_timeout=180

[repository]
ROOT = /data/git/gogs-repositories
FORCE_PRIVATE=true
MAX_CREATION_LIMIT=-1
DISABLE_HTTP_GIT=true

[server]
DOMAIN           = gogs.lab.com
HTTP_PORT        = 3000
ROOT_URL         = https://gogs.lab.com/
DISABLE_SSH      = false
SSH_PORT         = 22
SSH_LISTEN_HOST  = 0.0.0.0
SSH_LISTEN_PORT  = 22
START_SSH_SERVER = false
OFFLINE_MODE     = true

[mailer]
ENABLED = false

[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL     = false
DISABLE_REGISTRATION   = false
ENABLE_CAPTCHA         = false
REQUIRE_SIGNIN_VIEW    = true

[picture]
DISABLE_GRAVATAR        = true
ENABLE_FEDERATED_AVATAR = false

[session]
PROVIDER=redis
PROVIDER_CONFIG=network=tcp,addr=cache.gogs.lab.com:6379,password=,db=0,pool_size=100,idle_timeout=180

[log]
MODE      = console, file
LEVEL     = Info
ROOT_PATH = /app/gogs/log

[admin]
DISABLE_REGULAR_ORG_CREATION=true

[security]
INSTALL_LOCK = true
LOGIN_REMEMBER_DAYS=true
SECRET_KEY   = pLdr79uA4YnwDab

[other]
SHOW_FOOTER_BRANDING=false

启动应用

使用 docker-compose up 启动应用,稍等片刻可以看到日志类似下面:

Network traefik is external, skipping
Creating network "gogs_gogs" with the default driver
Creating gogs_db_1    ... done
Creating gogs_cache_1 ... done
Creating gogs_app_1   ... done
Attaching to gogs_cache_1, gogs_db_1, gogs_app_1
cache_1  | 1:C 28 Sep 2019 15:38:57.955 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
cache_1  | 1:C 28 Sep 2019 15:38:57.955 # Redis version=5.0.6, bits=64, commit=00000000, modified=0, pid=1, just started
cache_1  | 1:C 28 Sep 2019 15:38:57.955 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
db_1     | Initializing database
db_1     | 2019-09-28T07:38:58.172565Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
db_1     | 2019-09-28T07:38:59.117064Z 0 [Warning] InnoDB: New log files created, LSN=45790
db_1     | 2019-09-28T07:38:59.264592Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
db_1     | 2019-09-28T07:38:59.281117Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: 08ba4fc7-e1c3-11e9-a436-0242c0a89003.
app_1    | usermod: no changes
cache_1  | 1:M 28 Sep 2019 15:38:57.956 * Running mode=standalone, port=6379.
cache_1  | 1:M 28 Sep 2019 15:38:57.956 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
cache_1  | 1:M 28 Sep 2019 15:38:57.956 # Server initialized
cache_1  | 1:M 28 Sep 2019 15:38:57.956 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
cache_1  | 1:M 28 Sep 2019 15:38:57.956 * Ready to accept connections
db_1     | 2019-09-28T07:38:59.284542Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
db_1     | 2019-09-28T07:38:59.287236Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
app_1    | Sep 28 07:38:59 syslogd started: BusyBox v1.30.1
... ...
... ...
db_1     | 2019-09-28T07:39:11.612027Z 0 [Note] Event Scheduler: Loaded 0 events
db_1     | 2019-09-28T07:39:11.612400Z 0 [Note] mysqld: ready for connections.
db_1     | Version: '5.7.16'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
app_1    | 2019/09/28 07:39:11 [TRACE] Custom path: /data/gogs
app_1    | 2019/09/28 07:39:11 [TRACE] Log path: /app/gogs/log
app_1    | 2019/09/28 07:39:11 [TRACE] Build Time: 2019-08-12 02:30:12 UTC
app_1    | 2019/09/28 07:39:11 [TRACE] Build Git Hash: c154721f4a8f3e24d2f6fb61e74b4b64529255c2
app_1    | 2019/09/28 07:39:11 [ INFO] Private Repo 0.11.91.0811
app_1    | 2019/09/28 07:39:11 [ INFO] Cache Service Enabled
app_1    | 2019/09/28 07:39:11 [ INFO] Session Service Enabled
app_1    | 2019/09/28 07:39:14 [ INFO] Git Version: 2.22.0
app_1    | 2019/09/28 07:39:14 [ INFO] Git config user.name set to Gogs
app_1    | 2019/09/28 07:39:14 [ INFO] Git config user.email set to gogs@fake.local
app_1    | 2019/09/28 07:39:14 [ INFO] SQLite3 Supported
app_1    | 2019/09/28 07:39:14 [ INFO] Run Mode: Production
app_1    | 2019/09/28 07:39:14 [ INFO] Listen: http://0.0.0.0:3000
app_1    | [Macaron] 2019-09-28 07:39:14: Started GET / for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:39:14: Completed GET / 302 Found in 2.4563ms
app_1    | [Macaron] 2019-09-28 07:39:14: Started GET /user/login for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:39:14: Completed GET /user/login 200 OK in 13.0259ms
app_1    | [Macaron] 2019-09-28 07:39:20: Started GET / for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:39:20: Completed GET / 302 Found in 1.4996ms
app_1    | [Macaron] 2019-09-28 07:39:20: Started GET /user/login for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:39:20: Completed GET /user/login 200 OK in 11.8768ms
app_1    | [Macaron] 2019-09-28 07:39:25: Started GET / for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:39:25: Completed GET / 302 Found in 1.9598ms
app_1    | [Macaron] 2019-09-28 07:39:25: Started GET /user/login for 127.0.0.1
app_1    | [Macaron] 2019-09-28 07:39:25: Completed GET /user/login 200 OK in 10.8364ms
app_1    | [Macaron] 2019-09-28 07:39:30: Started GET / for 127.0.0.1

访问 gogs.lab.com 打开页面,就可以开始使用啦。

gogs 默认界面

备份数据

备份数据需要使用 gogs backup ,不论是在 容器内执行,还是在容器外使用 docker exec 都是可以的。

chown -R /backup
su git -c "/app/gogs/gogs backup -v --target /backup/"

2019/09/28 17:12:36 [ INFO] Backup succeed! Archive is located at: /app/gogs/backup/gogs-backup-20190928171236.zip

最后

比较巧合的是,去年九月开始,gogs 的更新开始了休眠模式,随后它的 fork 版本 Gitea 开始了茁壮成长。

下一篇聊聊,怎么使用 Traefik v2 TCP 模式搭建 Gitea 。

–EOF