Libravatar 2.0 配置参数详解

讲透 ivatar 2.0 配置体系:三层配置加载顺序、config.py 全参数默认值与含义,WebP 内容协商、Gravatar 代理、ALLOWED_HOSTS、尺寸钳制逐项生效实测与排障实录。

LibravatarLibravatar头像服务2026/10/28

Libravatar 2.0 配置参数详解

目标:把 Libravatar 服务端(ivatar 2.0)的配置体系讲透——三层配置文件的加载与覆盖顺序、config.py 中全部业务参数的默认值与含义,以及关键开关(Gravatar 代理、WebP 内容协商、ALLOWED_HOSTS、尺寸钳制)的逐项生效实测。本文所有"实测"均来自真实 Ubuntu 22.04 服务器上部署的 ivatar 2.0 实例(部署过程见《Ubuntu 22.04 安装 Libravatar 2.0 指南》,协议与联邦机制背景见《Libravatar 2.0 介绍与选型指南》),命令与回显逐行照抄。

一、环境信息

项目取值
操作系统Ubuntu 22.04.5 LTS(x86_64)
服务端实现ivatar 2.0(源码 commit 6f4b7e0,2026-09-25,master 分支)
部署路径/opt/ivatar(虚拟环境 /opt/ivatar/.virtualenv)
Python3.10.12(系统)
Django5.2.18(requirements 声明 >= 4.2.16,实测安装 5.2.18)
数据库PostgreSQL 14.24(psycopg 3.3.6)
应用服务gunicorn 26.2.0,WSGI,监听 127.0.0.1:8000,systemd 单元 ivatar.service
反向代理nginx 1.18.0(Ubuntu),监听 8080
缓存memcached 1.6.14(127.0.0.1:11211)
验证方法manage.py diffsettings / curl 实测响应头与响应体

二、配置体系总览

2.1 三层配置链:settings.py → config.py → config_local.py

ivatar 是标准 Django 工程外加一层"可覆盖配置",加载顺序固定为三段:

  1. ivatar/settings.py:Django 工程原生设置,随源码安装,不要修改;
  2. /opt/ivatar/config.py:随源码分发的"官方推荐默认值",settings.py 在定义完自身变量后执行 from config import * 导入它(注释原文:「These are modified here because settings.py does 'from config import *' AFTER defining them」);
  3. /opt/ivatar/config_local.py:运维自定义覆盖层,config.py 的最后一行按需加载它:
# This MUST BE THE LAST!
if os.path.isfile(os.path.join(BASE_DIR, "config_local.py")):
    from config_local import *  # noqa # flake8: noqa # NOQA # pragma: no cover

由此得出两条铁律:

  • 只改 config_local.py,永远不改 config.py——config.py 会被升级覆盖,config_local.py 不会;
  • config_local.py 中显式赋值的优先级高于 config.py 中 os.environ.get(...) 读到的环境变量(因为 config_local 在 import 时最后执行)。

仓库中自带参考模板 config_local.py.example,起步方式即复制改名:

# cd /opt/ivatar
# cp config_local.py.example config_local.py

2.2 如何查看"当前生效值":diffsettings

Django 内置的 manage.py diffsettings 打印所有与 Django 全局默认值不同的配置项,行尾 ### 是它自己的标记(表示"这一行与默认不同"),不是配置内容。本部署实例的输出(节选,重点行):

# cd /opt/ivatar
# .virtualenv/bin/python manage.py diffsettings
ALLOWED_HOSTS = ['*']
BASE_DIR = '/opt/ivatar'  ###
BASE_URL = 'http://avatars.example.com/avatar/'  ###
CACHES = {'default': {'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': ['127.0.0.1:11211']}, 'filesystem': {'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': '/var/tmp/ivatar_cache', 'TIMEOUT': 86400, 'OPTIONS': {'MAX_ENTRIES': 1000000}}}
DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql', 'NAME': 'ivatar', 'USER': 'ivatar_user', 'PASSWORD': 'q1w2e3r4', 'HOST': '127.0.0.1', 'PORT': '5432', ...}}
DEFAULT_GRAVATARPROXY = False  ###
ENABLE_WEBP_CONTENT_NEGOTIATION = True  ###
IVATAR_VERSION = '2.0'  ###
LOGS_DIR = '/var/log/ivatar'  ###
PREGENERATE_PHOTO_SIZES = (28, 48, 80, 128, 256)  ###
SECRET_KEY = 'fZ7_O6oQrGiCVw-oABdO_HnIDajX1FpUP9uQ80O6Z9CniAMMkA1U0ZgGkni5lCrIuGA'
SECURE_BASE_URL = 'https://avatars.example.com/avatar/'  ###
SITE_NAME = 'libravatar'  ###
STATIC_ROOT = '/opt/ivatar/static'
USE_X_FORWARDED_HOST = True
(其余输出略,diffsettings 全量约 90 行)

说明:ALLOWED_HOSTS/SECRET_KEY/DATABASES/USE_X_FORWARDED_HOST/STATIC_ROOT 等行没有 ### 标记,是因为它们虽然被本项目赋值,但值恰好与 Django 全局默认形态一致或由 settings.py 原生定义;带 ### 的行是 ivatar 特有或与默认不同的项。判断"我的覆盖是否生效",直接在输出里找你的参数名即可,不必纠结标记。

三、配置参数详解

以下参数全部取自 /opt/ivatar/config.py(ivatar 2.0,commit 6f4b7e0),"本部署值"列为 /opt/ivatar/config_local.py 的实际覆盖值;"生效实测"统一见第四章。

3.1 站点标识与对外地址

参数config.py 默认本部署值说明
SITE_NAMEos.environ.get("SITE_NAME", "libravatar")未覆盖(libravatar)站点名,用于页面标题等展示
IVATAR_VERSION"2.0"未覆盖版本字符串,/version/ 端点的 application_version 字段即来源于此
SCHEMAROOT"https://www.libravatar.org/schemas/export/0.2"未覆盖导出 XML 的 schema 地址
SECURE_BASE_URLos.environ.get("SECURE_BASE_URL", "https://avatars.linux-kernel.at/avatar/")https://avatars.example.com/avatar/HTTPS 对外头像基础 URL,页面/邮件里拼头像链接用
BASE_URLos.environ.get("BASE_URL", "http://avatars.linux-kernel.at/avatar/")http://avatars.example.com/avatar/HTTP 对外头像基础 URL
LOGIN_REDIRECT_URLreverse_lazy("profile")未覆盖登录后跳转到个人资料页

注意 SECURE_BASE_URL/BASE_URL 是"展示与委派用"的声明值,不改变本机路由——对外服务的实际入口由 nginx 监听端口决定。另有一个容易困惑的现象:/stats/ 端点里 top_viewed_avatars 的 avatar_url 是请求发生时落库的历史值,改了 BASE_URL 后旧记录仍显示旧地址(实测确认,重启与清缓存都不回写), diffsettings 才是验证配置生效的可靠手段。

3.2 数据库(DATABASES)

config.py 的数据库选择逻辑(按优先级从上到下,先命中先生效):

try:
    from ivatar.settings import DATABASES
except ImportError:
    DATABASES = []

if "default" not in DATABASES:
    DATABASES["default"] = {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
    }

if "MYSQL_DATABASE" in os.environ:        # MySQL(编排部署风格变量)
    DATABASES["default"] = { ... }

if "POSTGRESQL_DATABASE" in os.environ:   # PostgreSQL(编排部署风格变量)
    DATABASES["default"] = { ... }

if "POSTGRES_DB" in os.environ:           # PostgreSQL(CI/CD 风格变量)
    DATABASES["default"] = { ... }

即:什么变量都不给,默认落到 BASE_DIR 下的 SQLite(db.sqlite3);裸机生产建议显式在 config_local.py 里写死 PostgreSQL:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "ivatar",
        "USER": "ivatar_user",
        "PASSWORD": "q1w2e3r4",
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

diffsettings 实测确认生效(见 2.2,ENGINE 已是 postgresql)。官方支持矩阵:SQLite(仅开发)/ MySQL-MariaDB / PostgreSQL;PostgreSQL 走 psycopg 3(实测 3.3.6)。

3.3 缓存体系

参数默认值说明
CACHES["default"]memcached PyMemcacheCache,127.0.0.1:11211主缓存(头像响应 page cache 等)。未启动 memcached 时头像接口会降级报错,安装篇 FAQ 有实测
CACHES["filesystem"]文件缓存 /var/tmp/ivatar_cache,TTL 86400,MAX_ENTRIES 1000000第二层文件缓存
CACHE_RESPONSETrue开启对头像响应的整页缓存
PHOTO_CACHE_TIMEOUT604800(7 天)用户照片响应缓存 TTL
MAX_USER_PHOTO_CACHE_SIZE500用户照片内存缓存条目上限
STATS_CACHE_TIMEOUT3600/stats/ 端点缓存 TTL(该端点未命中时要全表扫描照片表,务必保留缓存)
BLUESKY_CACHE_TIMEOUT3600Bluesky 代理载荷缓存 TTL

实测提醒:PHOTO_CACHE_TIMEOUT 意味着改完配置切换开关后,同一个哈希的旧响应可能仍从缓存返回——排障时先用 manage.py clear_cache 清缓存,或换一个全新邮箱哈希验证(本文所有两态实测均换用新哈希,见第四章)。

3.4 头像业务参数

参数默认值说明
DEFAULT_AVATAR_SIZE80未带 s= 参数时的默认边长(px)
AVATAR_MAX_SIZE512s= 参数的上限,超限被钳制到 512
MAX_PHOTO_SIZE10485760(10 MB,注释原文 "in bytes")用户上传照片体积上限
MAX_PIXELS7000单边最大像素(7000 px,防止解压炸弹)
JPEG_QUALITY85JPEG 重编码质量
MAX_NUM_PHOTOS5每用户最多照片数
MAX_NUM_UNCONFIRMED_EMAILS5待确认邮箱上限
MAX_LENGTH_EMAIL / MIN_LENGTH_EMAIL254 / 6邮箱长度校验
MAX_LENGTH_URL / MIN_LENGTH_URL255 / 11OpenID URL 长度校验
PREGENERATE_PHOTO_SIZES(28, 48, 80, 128, 256)保存照片时预生成的缩略图档位
PREGENERATE_THUMBNAILS_ON_SAVETrue保存即同步预生成(关掉则首次请求时现算,慢)
PREGENERATE_THUMBNAILS_ASYNCFalse预生成同步执行
PREGENERATE_WEBP_THUMBNAILSTrue预生成 WebP 版本缩略图

尺寸钳制实测(管理员账号头像,md5 = e64c7d89f26bd1972efa854d13d7dd61):

$ curl -s -o /dev/null -w "s=512: %{size_download}B\n" "http://avatars.example.com:8080/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=512"
s=512: 1880B
$ curl -s -o /dev/null -w "s=600: %{size_download}B\n" "http://avatars.example.com:8080/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=600"
s=600: 1880B
$ curl -s -o /dev/null -w "s=1:   %{size_download}B\n" "http://avatars.example.com:8080/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=1"
s=1:   69B

s=600 与 s=512 字节数完全一致(1880B),证实 AVATAR_MAX_SIZE = 512 把超限请求钳制到了 512px;s=1 正常返回 1px 图(69B)。

3.5 WebP 内容协商

参数默认值说明
ENABLE_WEBP_CONTENT_NEGOTIATIONTrue开启后,客户端 Accept 头带 image/webp 且请求的是用户上传照片时,返回 WebP
WEBP_QUALITY85WebP 编码质量

两态实测(alice@example.com 的照片,md5 = c160f8cc69a4f0bf2b0362752353d060;bob@example.com,md5 = 4b9bb80620f03eb3719e0a061c14283d):

开启(默认态)时——

$ curl -s -o /dev/null -w "%{http_code} %{content_type} %{size_download}B" -H "Accept: image/webp,image/*" "http://avatars.example.com:8080/avatar/c160f8cc69a4f0bf2b0362752353d060"
200 image/webp 96B

关闭(config_local.py 设 ENABLE_WEBP_CONTENT_NEGOTIATION = False 并重启服务后,用全新哈希验证)——同样请求只返回 image/png。

两个边界结论(实测):

  • 默认头像路径不受该开关影响:?d=retro 等生成型默认头像即便带 Accept: image/webp 也恒为 PNG——200 image/png 1267B;
  • PNG 用户照片(96B WebP vs 177B PNG)在该样例上 WebP 更小,但对已有小 PNG 收益有限,属正常现象。

3.6 Gravatar 联动开关

参数默认值说明
DEFAULT_GRAVATARPROXYTrue本库未命中头像时,代理 Gravatar 的结果(用户感知不到 Gravatar 存在)
DEFAULT_GRAVATARREDIRECTFalse本库未命中时 302 重定向到 Gravatar(暴露上游)
FORCEDEFAULTFalse全站强制走默认头像(等同所有请求带 f=y)

两态实测(全新哈希 00112233445566778899aabbccddeeff,本库无此邮箱头像):

关闭时(DEFAULT_GRAVATARPROXY = False)——直接走本站默认头像,302 到 nobody 静态图:

$ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}" "http://avatars.example.com:8080/avatar/00112233445566778899aabbccddeeff"
302 -> http://avatars.example.com:8080/static/img/nobody/80.png

开启时(DEFAULT_GRAVATARPROXY = True,重启 ivatar 服务后)——先 302 到站内 gravatarproxy 视图:

$ curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}" "http://avatars.example.com:8080/avatar/00112233445566778899aabbccddeeff"
302 -> http://avatars.example.com:8080/gravatarproxy/00112233445566778899aabbccddeeff?s=80

gravatarproxy 视图再去抓 Gravatar——服务器没有公网出口时这一步失败,实测返回 500:

$ curl -s -o /dev/null -w "%{http_code} %{content_type} %{size_download}B" "http://avatars.example.com:8080/gravatarproxy/00112233445566778899aabbccddeeff"
500 text/html; charset=utf-8 145B

journalctl 中的真实异常(排障实录,见第五章):

# journalctl -u ivatar --no-pager -n 4
Oct 28 13:52:07 ivatar-server gunicorn[8794]:   File "/usr/lib/python3.10/urllib/request.py", line 1351, in do_open
Oct 28 13:52:07 ivatar-server gunicorn[8794]:     raise URLError(err)
Oct 28 13:52:07 ivatar-server gunicorn[8794]: urllib.error.URLError: <urlopen error [Errno 101] Network is unreachable>

内部网络部署务必保持 DEFAULT_GRAVATARPROXY = False(或给服务器配好公网出口),否则所有未命中头像都是 500。

3.7 安全与上传

参数默认值说明
DEBUGsettings.py 开发默认 True,生产必须显式 False本部署 False(实测:出错页只给 "Server Error (500)" 空白页,不带堆栈)
SECRET_KEY无默认,必须生成会话/CSRF 签名密钥,用 manage.py generate_secret_key 生成后写入 config_local.py
ALLOWED_HOSTS["*"]允许的 Host 头列表。config.py 默认 * 便于起步,生产建议收紧(实测见第四章)
USE_X_FORWARDED_HOSTTrue从 X-Forwarded-Host 取 Host(nginx 反代场景必须)
TRUSTED_DEFAULT_URLSui-avatars.com、gravatar.com、dicebear.com 等 11 条d= 指向站外 URL 时的防开放重定向白名单(CWE-601 防护),支持 host_equals/host_suffix/path_prefix 组合
URL_TIMEOUT10抓取外部 URL(Gravatar/Bluesky 等)的超时秒数
FILE_UPLOAD_MAX_MEMORY_SIZE5 * 1024 * 1024(5 MB)超过则落盘临时文件
DATA_UPLOAD_MAX_MEMORY_SIZE5 * 1024 * 1024请求体上限
FILE_UPLOAD_PERMISSIONS0o644上传文件权限
ENABLE_FILE_SECURITY_VALIDATIONTrue上传文件安全校验(魔数验证,依赖系统 libmagic)
ENABLE_EXIF_SANITIZATIONTrue上传时清洗 EXIF(防照片泄露 GPS 等元数据)
ENABLE_MALICIOUS_CONTENT_SCANTrue恶意内容扫描

3.8 认证相关

参数默认值说明
AUTHENTICATION_BACKENDSOpenID 后端 + Fedora OIDC 后端 + ModelBackend三后端叠加;config.py 注释保留 django_auth_ldap.backend.LDAPBackend 开关(启用需装 LDAP 依赖,见安装篇)
OPENID_CREATE_USERSTrueOpenID 首次登录自动建用户
OPENID_UPDATE_DETAILS_FROM_SREGTrue从 OpenID sreg 拉取昵称邮箱
SOCIAL_AUTH_FEDORA_KEY / SOCIAL_AUTH_FEDORA_SECRETNoneFedora OIDC 客户端凭据(不用 Fedora 登录可不管)
ALLOWED_EXTERNAL_OPENID_REDIRECT_DOMAINS["avatars.linux-kernel.at", "localhost"]OpenID 回跳域名白名单,自部署要加上自己的域名
ADMIN_USERS[]特权用户名列表
TRUST_EMAIL_FROM_SOCIAL_AUTH_BACKENDS["fedora"]信任哪些社交后端声明的邮箱
SOCIAL_AUTH_JSONFIELD_ENABLEDTruesocial_django JSON 字段

3.9 Bluesky 代理

参数默认值说明
BLUESKY_API_TIMEOUT5Bluesky profile/session API 超时(秒)
BLUESKY_IMAGE_TIMEOUT5头像 CDN 下载超时(秒)
BLUESKY_CACHE_TIMEOUT3600代理载荷缓存 TTL(秒)
BLUESKY_IDENTIFIER / BLUESKY_APP_PASSWORDos.environ.get(..., None)App Password 凭据,走环境变量传入

3.10 性能与可观测

参数默认值说明
STATS_BATCH_SIZE100访问计数批量落库大小
ASYNC_ACCESS_COUNTTrue访问计数异步更新
ROBOHASH_OPTIMIZATION_ENABLEDTrueRobohash 优化实现(注释称 6–22x 性能提升)
ROBOHASH_CACHE_SIZE150机器人部件内存缓存条数(约 10–30 MB)
ROBOHASH_UPSCALE_CACHE_SIZE241024×1024 上采样部件 LRU 上限
PAGAN_CACHE_SIZE1000pagan(PGP 风格头像)缓存条数
LOGS_DIR无默认(注释示例 /var/log/ivatar)日志目录声明;注意 Django 的 LOGGING 文件处理器实际写 /opt/ivatar/logs/ivatar.log(settings.py 定义),两者不要混为一谈
OpenTelemetry默认不导出装了 instrumentation 但未配导出器,manage.py 每次都会打印提示(见 FAQ)

3.11 界面与语言

参数默认值说明
LANGUAGES德/英/加泰罗尼亚/捷克/西/巴斯克/法/意/日/荷/葡/俄/阿尔巴尼亚/土/乌克兰 15 语界面语言集合,配合 CustomLocaleMiddleware 按 Accept-Language 协商
BOOTSTRAP4本地静态资源(/static/css/bootstrap.min.css 等,带 SRI integrity)Bootstrap4 走本地文件,不引外网 CDN
MESSAGE_TAGSdebug/info/success/warning→danger站内消息样式映射

四、参数生效实测

4.1 验证方法论

  1. 改 /opt/ivatar/config_local.py;
  2. systemctl restart ivatar(gunicorn 不会热加载配置,必须重启);
  3. manage.py diffsettings 确认生效值;
  4. curl 用全新邮箱哈希做行为验证(避开 PHOTO_CACHE_TIMEOUT=7 天的响应缓存)。

4.2 开关两态实测汇总

参数状态实测命令要点实测结果
ENABLE_WEBP_CONTENT_NEGOTIATIONTrue(默认)curl -H "Accept: image/webp,image/*" 用户照片哈希200 image/webp 96B
同上False同命令(另一哈希)200 image/png
DEFAULT_GRAVATARPROXYFalse裸哈希(本库无头像)302 -> /static/img/nobody/80.png
同上True同命令302 -> /gravatarproxy/<hash>?s=80,内网无出口再 500
ALLOWED_HOSTS["*"]任意路径正常 200
同上["avatars.example.com"]curl http://localhost:8080/version/(Host 不匹配)HTTP/1.1 400 Bad Request,页面 Bad Request (400)
AVATAR_MAX_SIZE512(默认)s=600 与 s=512 对比字节数同为 1880B,超限被钳制

ALLOWED_HOSTS 误配的完整实测(收紧后用不匹配的 Host 访问):

# 把 config_local.py 的 ALLOWED_HOSTS 改为 ["avatars.example.com"] 并重启
$ curl -s -i "http://localhost:8080/version/" | head -6
HTTP/1.1 400 Bad Request
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 28 Oct 2026 13:52:37 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
$ curl -s "http://localhost:8080/version/"

<!doctype html>
<html lang="en">
<head>
  <title>Bad Request (400)</title>
</head>
<body>
  <h1>Bad Request (400)</h1><p></p>
</body>
</html>

改回 ALLOWED_HOSTS = ["*"](或加上实际访问域名)并重启后恢复 200。注意 nginx 原样透传了 Host 头(配置里有 proxy_set_header Host $host),所以 Django 看到的 Host 就是浏览器/客户端发的原始值。

4.3 SECURE_BASE_URL 覆盖验证

config_local.py 写入后:

SECURE_BASE_URL = "https://avatars.example.com/avatar/"
BASE_URL = "http://avatars.example.com/avatar/"

重启后 diffsettings 显示:

# .virtualenv/bin/python manage.py diffsettings | grep BASE_URL
BASE_URL = 'http://avatars.example.com/avatar/'  ###
SECURE_BASE_URL = 'https://avatars.example.com/avatar/'  ###

/version/ 端点回显(确认 IVATAR_VERSION=2.0 在线上生效):

$ curl -s "http://localhost:8080/version/"
{"commit_hash": "6f4b7e01cb2a6be57fea63aeb9fc95414b2d2037", "short_hash": "6f4b7e0", "branch": "master", "commit_date": "2026-10-28 13:13:16 ", "deployment_date": "2026-10-28 13:13:16 ", "deployment_status": "active", "version": "master-6f4b7e0", "application_version": "2.0"}

五、排障实录

5.1 改了 config_local.py 不生效

现象:开关改完 curl 行为不变。 原因:gunicorn 是常驻进程,Django 配置只在进程启动时加载一次,systemctl restart ivatar 前改动全部无效。 解决:systemctl restart ivatar 后再验证。

5.2 清了配置、缓存还在——两态实验"失效"

现象:把 ENABLE_WEBP_CONTENT_NEGOTIATION 从 True 改 False 后,老哈希请求仍返回 image/webp。 原因:CACHE_RESPONSE = True + PHOTO_CACHE_TIMEOUT = 604800,memcached 里的整页响应最长活 7 天,比实验周期长得多。 解决:换全新邮箱哈希验证(最可靠),或 manage.py clear_cache(回显 Cache "default" has been cleared!)后重试。

5.3 ALLOWED_HOSTS 收紧后整站 400

现象:把 ALLOWED_HOSTS 从 ["*"] 改成具体域名后,所有请求 400 Bad Request。 原因:请求的 Host 头(如 localhost:8080 或 IP:8080)不在列表里,Django 安全中间件直接拒绝——这正是该参数的设计行为,不是故障。 解决:把实际对外域名写进列表;经 nginx 反代时确保 proxy_set_header Host $host 存在且域名与列表一致。多域名/加 IP 调试期可临时 ["域名", "IP"]。

5.4 开启 Gravatar 代理后未命中头像 500

现象:DEFAULT_GRAVATARPROXY = True 后,无头像邮箱请求 500,页面只有 "Server Error (500)"。 定位:journalctl -u ivatar 见 urllib.error.URLError: <urlopen error [Errno 101] Network is unreachable>——服务器访问不了 gravatar.com。 解决:内网/无出口环境保持 DEFAULT_GRAVATARPROXY = False(未命中直接走本站默认头像,行为确定);确需代理则给服务器配公网出口或 HTTP 代理。

5.5 每条 manage.py 命令都打印 "No metric readers configured"

现象:任何 manage.py ... 输出第一行都是:

No metric readers configured - metrics will not be exported

原因:ivatar 内置 OpenTelemetry 埋点,未配置 OTLP/Prometheus 导出器时 SDK 每次进程启动都会提示。这不是错误,命令本身照常执行。 处理:忽略;要接监控时按 README 的 OpenTelemetry 章节配置导出端点。

六、常见问题

Q1:最小生产配置要写哪几行? A:config_local.py 五件事——DEBUG = False、SECRET_KEY(generate_secret_key 生成)、ALLOWED_HOSTS(实际域名)、DATABASES(PostgreSQL)、两个 *_BASE_URL。其余参数先用默认。

Q2:环境变量和 config_local.py 冲突谁赢? A:config_local.py 赢。config.py 用 os.environ.get(...) 读环境变量是在 import 时发生的,而 config_local.py 在 config.py 末尾才被 import,后加载的显式赋值覆盖前者。想强制用环境变量,就别在 config_local.py 里写那个参数。

Q3:想看某个参数最终生效值,翻三个文件太麻烦? A:manage.py diffsettings(找参数名)或 django-extensions 的 manage.py print_settings --format=pretty。

Q4:能把数据库换成 MySQL 吗? A:可以,官方支持 MySQL/MariaDB。设 MYSQL_DATABASE/MYSQL_USER/MYSQL_PASSWORD 环境变量,或在 config_local.py 写 DATABASES(ENGINE 用 django.db.backends.mysql)。注意 MAX_LENGTH_URL=255 的注释提到 MySQL 行长度限制,URL 字段勿加长。

Q5:/stats/ 里的头像 URL 还是旧域名? A:那是历史请求落库的统计值,改 BASE_URL 不会回写。以 diffsettings 为准。

七、参考链接

  1. ivatar 源码 config.py(本文全部参数的出处):/opt/ivatar/config.py(commit 6f4b7e0)
  2. ivatar 源码 config_local.py.example(模板):/opt/ivatar/config_local.py.example
  3. 本实例部署环境:/opt/ivatar/config_local.py(生产覆盖层)
  4. Libravatar Wiki《Running your own instance》:https://wiki.libravatar.org/running_your_own/
  5. 本专栏部署实测:《Ubuntu 22.04 安装 Libravatar 2.0 指南》
  6. 本专栏命令实操:《Libravatar 2.0 常用 CLI 命令与工具详解》
  7. 本专栏认知篇:《Libravatar 2.0 介绍与选型指南》