ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Docker 部署 ArchivesSpace:从单容器到生产级编排全指南

Docker 部署 ArchivesSpace:从单容器到生产级编排全指南 1. ArchivesSpace 到底是什么以及为什么值得用 Docker 跑如果你在图书馆、档案馆、高校或任何跟存量资料管理沾边的单位待过大概率听说过 ArchivesSpace 这个名字。它是一个开源的档案信息管理平台用来做档案的著录、编目、检索、权限控制和数字化资源关联。简单说就是给你的馆藏资料建一个规范化的户口本每一份档案从入馆、整理、上架到对外开放检索全生命周期都在这个系统里留痕。但这个东西有个让 IT 运维不太舒服的特点——它不是一个大一统的应用而是由多个组件拼起来的。底层要一个数据库存元数据默认支持 H2 和 MySQL 两种要一个 Solr 做全文检索引擎上面还分成 Staff 前端档案管理员用的工作台、Public 前端访客检索界面、Backend APIRESTful 接口和 Indexer索引同步服务。以前部署一套你得手动装 Java 运行时、下载 Solr、装 MySQL、解压 ArchivesSpace 发行包、改配置文件、写系统服务脚本……每个环境变量不一样依赖版本稍微对不上就启动失败排错能排到怀疑人生。用 Docker 跑就完全是另一种体验。镜像把 Java 运行时、ArchivesSpace 应用代码、内置依赖全部打包好你只需要关心两个问题数据放哪、端口怎么映射。这也是我为什么写这篇东西的原因——我去年给单位做档案系统试点前后折腾了三种部署方式Docker 这条路走得最顺也最省心。这篇博文适合谁两类人。第一类是想快速评估 ArchivesSpace 功能的档案业务人员和 IT 支持人员你不需要了解 JVM 和 Solr 的内部机制跟着步骤能跑起来就行。第二类是准备上生产环境的运维工程师我后面会讲编排方案、数据持久化、备份恢复这些绕不开的硬话题。两种诉求我都会照顾到不过话说在前面第一遍建议先按顺序往下走别跳着看有些配置互相有依赖关系。2. 环境准备与镜像选型动手前先定方案2.1 基础环境要求先说你本机或服务器需要具备什么条件。ArchivesSpace 官方镜像基于amazoncorretto之类的 OpenJDK 镜像构建所以只要 Docker Engine 能跑底层系统是 Ubuntu、CentOS、Debian、Windows Server 甚至 macOS 其实差异不大。我实测的环境是 Ubuntu 22.04 LTS Docker 24.x Docker Compose v2这也是我认为最省心的一套组合。内存方面ArchivesSpace 官方建议至少 4GB 可用内存。如果你只是单容器跑着玩不接 MySQL2GB 也能勉强跑起来但 Solr 索引一上来就会很吃力。我建议开发环境 4GB生产环境至少 8GB。磁盘空间其实不太敏感应用本体加索引初始 5GB 绰绰有余大头是以后数字化档案文件存放目录那个另算。Docker 安装过程我就不啰嗦了不同平台的命令网上到处都是。有一个容易忽略的点是 Docker 版本别太老ArchivesSpace 的镜像经历了多次构建流程调整老版本 Docker 可能会遇到镜像拉取层校验问题。我建议 Docker Engine 不低于 20.10Compose 插件不低于 v2.10。在 Ubuntu 上装完以后先跑一下docker version确认客户端和服务端都正常再跑docker compose version确认 Compose 可用。2.2 官方镜像与第三方镜像怎么选Docker Hub 上搜 ArchivesSpace你能看到好几类镜像。最稳的肯定是官方那个archivesspace/archivesspace它跟随 GitHub 仓库同步更新标签规则也很直白比如v3.4.1、v3.5.0。我墙裂建议只用官方镜像原因有二一是官方镜像的启动脚本逻辑完整。它会在容器启动时自动执行数据库迁移如果你配置了外部数据库但库是空的它会建好全部表结构、初始化和默认配置生成。第三方镜像很多是把发行包解压进镜像就完事缺少这层逻辑你往往得手动进容器去跑aspace db:migrate。二是安全更新有保障。档案系统里存的是真实业务数据安全基线比普通内部工具高一个档次用官方镜像至少能说明构建过程可控。版本选择上我现在会用最新的 3.x 稳定版。3.x 相比 2.x 在 UI、OAI-PMH 接口、数字对象处理上都有明显改进。但有一点要注意ArchivesSpace 文档和社区帖子更新速度跟不上版本迭代网上很多教程是 2.x 时期的数据库配置项、环境变量名有变化往下看你就能感受到这个坑。# 查看官方镜像所有可用版本标签 docker pull archivesspace/archivesspace:latest docker image inspect archivesspace/archivesspace:latest --format {{.Config.Labels}}先拉 latest 标签通常指向最新的稳定版但不建议生产环境长期用 latest后续我会讲怎么把版本锁死。3. 先把服务跑起来单容器模式速览3.1 端口划分与首次启动ArchivesSpace 最让人困惑的一点是它默认占用一堆端口。官方镜像里已经把这些端口通过 ENV 和 EXPOSE 声明好了单容器模式下最省事的启动命令是这样docker run -d \ --name aspace \ -p 8080:8080 \ -p 8081:8081 \ -p 8082:8082 \ -p 8083:8083 \ -v aspace_data:/archivesspace/data \ -v aspace_logs:/archivesspace/logs \ archivesspace/archivesspace:v3.4.1这四个端口各管一摊给你捋清楚端口服务用途说明8080Staff 前端档案管理员登录、编目、管理的工作台8081Backend APIRESTful 接口一般不需要直接浏览器访问8082Public 前端对外访客的检索浏览界面8083Solr 管理端内置 Solr 的管理控制台不对外首次启动会有一个明显的过程容器起来之后Solr 初始化、数据库建表、索引同步都要跑一会儿。等 1-2 分钟后访问http://localhost:8080看到登录页就说明起来了。默认账号是admin密码也是admin第一次登录系统会强制你改掉。这个默认密码是安全上最大的坑后面我会专门说。3.2 数据持久化没你想的那么简单单容器模式下你可能会想我映射了 volume 就完事了。错。ArchivesSpace 的数据分三部分我只映射了其中两部分MySQL 或 H2 数据库文件——默认在/archivesspace/data下面日志文件——默认在/archivesspace/logs下面Solr 索引——默认在内置数据目录上面命令里我只映射了data和logsSolr 索引没做持久化。这样做是有意的Solr 索引只是检索的缓存它可以从数据库和文件存储中重建。如果你把 Solr 的索引目录做进持久化一旦版本升级、索引格式变化反而会带来一堆兼容性麻烦。官方推荐的思路就是索引丢了就重建不是什么大事。所以单容器模式适合什么场景适合你第一次接触 ArchivesSpace想在本地把界面、功能、数据模型都摸一遍看看它跟你单位的业务是不是匹配。它不适合生产。原因很直接一是 H2 数据库在并发写入场景下性能不够稳定二是内置 Solr 和应用抢内存三是单点挂了就是挂了。生产环境直接跳到下一节的编排方案。4. 生产级部署MySQL Solr ArchivesSpace 的编排方案4.1 为什么生产环境要拆开跑ArchivesSpace 的生产部署我建议至少拆成两个容器数据库独立出来应用和内置 Solr 放一起。有钱有闲的话可以拆三个——应用一个、Solr 单独一个、MySQL 一个。为什么数据库必须独立H2 是嵌入式数据库它把所有数据存在一个文件里适合单机演示。但档案系统的特点是什么数据持续增长、需要定期备份、查询路径复杂。MySQL 的成熟度、备份工具链生态、社区排错经验都远优于 H2。而且把数据库独立出来后应用容器就可以随时销毁重建数据库的命不随应用走。Solr 单独拆一个容器也可以尤其是你预期索引导入量很大的时候。但我实测下来个人和中小型团队的体量几十万条档案记录级别把 Solr 和应用合在一个容器里问题不大。真正要单独的 MySQL是我最强调的一条。4.2 Compose 文件拆解我自己在用的 compose 方案是官方示例的改良版加上了一些环境变量和网络配置。直接给你看完整的docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0 container_name: aspace-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-change_this_root_password} MYSQL_DATABASE: archivesspace MYSQL_USER: aspace MYSQL_PASSWORD: ${MYSQL_PASSWORD:-change_this_aspace_password} command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --innodb_buffer_pool_size1G volumes: - mysql_data:/var/lib/mysql networks: - aspace_net healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 10 aspace: image: archivesspace/archivesspace:v3.4.1 container_name: aspace-app restart: unless-stopped depends_on: mysql: condition: service_healthy ports: - 8080:8080 - 8081:8081 - 8082:8082 environment: ASPACE_DB_TYPE: mysql ASPACE_DB_HOST: mysql ASPACE_DB_PORT: 3306 ASPACE_DB_USER: aspace ASPACE_DB_PASSWORD: ${MYSQL_PASSWORD:-change_this_aspace_password} ASPACE_DB_NAME: archivesspace JAVA_OPTS: -Xms1024m -Xmx2048m volumes: - aspace_data:/archivesspace/data - aspace_logs:/archivesspace/logs - ./config/config.rb:/archivesspace/config/config.rb:ro networks: - aspace_net volumes: mysql_data: aspace_data: aspace_logs: networks: aspace_net: driver: bridge这个文件有几个细节值得展开讲。第一MySQL 8.0 的字符集参数。档案系统里中文是绝对的主角MySQL 8.0 默认字符集虽然是 utf8mb4但保险起见我仍然在启动命令里显式声明。千万别用 MySQL 5.7 的默认配置直接连排序规则不对会导致中文检索结果跟你预期不一致。第二healthcheck 的用途。depends_on加上condition: service_healthy是确保应用容器等数据库真正就绪后才启动。要是没这个条件应用容器起来发现连不上数据库不会自动重试而是直接退出。这是容器编排里最常见的怪问题——不是你的配置错了是启动顺序没管好。第三JAVA_OPTS 内存设置。这个变量不是随便传的。ArchivesSpace 实际上同时启动四个后台 Java 进程backend、frontend、public、indexer官方启动脚本会把JAVA_OPTS的基础参数应用到这几个进程上同时每个进程还有自己的额外内存参数控制。我给的是 1G 初始堆、2G 最大堆这是基于含内置 Solr 的场景调过的。机器内存只有 4G 的话JAVA_OPTS 最多给 2G 最大堆否则容器会 OOM。第四config.rb 挂载。容器外的配置文件通过只读方式挂进容器这是生产环境管理配置的标准做法。文件内容长什么样下一小节展开。4.3 关键配置项 config.rb 详解config.rb是 ArchivesSpace 所有运行时配置的集中地。容器化部署下最合理的做法是你在宿主机建好这个文件然后像上面 compose 那样挂载进去。我的最小生产配置长这样# 应用基础地址生产环境务必改成真实域名或IP AppConfig[:backend_url] http://localhost:8081 AppConfig[:frontend_url] http://localhost:8080 AppConfig[:public_url] http://localhost:8082 # 数据库 AppConfig[:db_url] jdbc:mysql://mysql:3306/archivesspace?useUnicodetruecharacterEncodingutf8mb4useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai AppConfig[:db_user] aspace AppConfig[:db_password] ENV[ASPACE_DB_PASSWORD] # 文件存储目录 AppConfig[:file_store_path] /archivesspace/data/files # 邮件配置如果你需要系统发通知 AppConfig[:mailto] archive-adminexample.com AppConfig[:notification_from_address] no-replyexample.com这里有个容易搞混的概念AppConfig[:backend_url]、frontend_url不是给你浏览器访问用的而是给系统内部各组件互相调用用的。比如 frontend 调 backend 的 API它会读这个配置。所以域名涉及容器内外的网络差异容器里localhost指向容器自身这里如果你把backend_url写成localhost没问题因为容器内 frontend 和 backend 在同一网络命名空间可以直接访问。但如果你用了上节说的单独网络后端和前端在不同容器里那backend_url就要写成http://aspace:8081服务别名。这是踩坑重灾区。db_url里面那段allowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai我解释一下MySQL 8.0 的默认认证插件是 caching_sha2_passwordJDBC 首次连接时需要允许获取公钥时区参数则避免容器和宿主机默认 UTC 导致的时间差 8 小时问题。5. 部署中最容易踩的坑密码、时区、内存与索引5.1 默认密码不只是 admin/admin前面提到默认账号密码是 admin/admin每次新部署都会强制改。但你可能不知道还有个隐藏级别系统初始化后除了 admin 账号ArchivesSpace 还会创建一组固定 API 账号比如用于 OAI-PMH 的oai用户用于后端程序的aspaceadmin。这些账号的初始密码也在官方文档里写得明明白白生产环境务必一并用 SQL 改掉或者至少设上强密码策略。实际运维里我发现一个更隐蔽的问题单容器模式下如果你用了已存在的 volume 重新部署新版本数据库和缓存都保留系统会认为你已经初始化过不会再让你重置密码。如果你丢失了 admin 密码不能像普通软件那样绕过去。怎么办我做过一次是通过直接改数据库表里的密码哈希拿一个已知密码的哈希替换进去。这不是什么正规操作路径但急等用的时候确实能救命。建议各位部署完成之后就立刻把管理员密码记录到企业密码管理工具里这事拖不得。5.2 时区问题日志和记录时间差 8 小时国内部署最容易忽略的就是时区。默认容器时区是 UTC你的档案元数据如果记录了时间比如创建时间、入库时间显示出来会比北京时间慢 8 小时。修改方式有三种在 compose 的环境变量里加TZAsia/Shanghai手动挂载/etc/localtime:/etc/localtime:ro在数据库 JDBC URL 里指定serverTimezoneAsia/Shanghai我建议三个一起上。第一个解决容器内进程的系统时间第二个解决 JVM 读取宿主时区第三个解决 MySQL 连接会话时区。只配其中一个后面 Log 索引出来的时间戳照样对不齐。5.3 Solr 索引不同步的排查思路跑了几个星期以后你可能会碰到这个现象档案记录明明在后台能看到但前台检索就是搜不到。或者新导入的记录没有被索引。看起来像 Solr 挂了但去 8083 一看核心还在commit 也正常。这时候先别急着重建索引按这个顺序排查# 1. 看应用日志里有没有索引同步报错 docker logs aspace-app 21 | grep -i indexer # 2. 看内置 Solr 的日志 docker exec -it aspace-app /bin/bash find /archivesspace/logs -name *.out -o -name *.log | xargs grep -i solr最常见的原因是内存不足导致 Indexer 进程被 OOM Killer 杀了或者非常罕见地死锁。遇到这种情况重启应用容器一般能解决——索引进程会重新拉起从上次的位置继续同步。如果反复出现就要调大JAVA_OPTS或者单独拆分 Solr 容器了。最万不得已的办法是触发全量索引重建。在后台管理界面进入系统设置能找到重置索引的选项或者从命令行执行docker exec -it aspace-app /archivesspace/scripts/indexer_reindex_all.sh这个操作会重新扫描全部档案记录数据量大的时候耗时较长建议在低峰期执行。5.4 端口映射踩坑本机已经占用 8080很多人第一次部署就栽在端口冲突上。服务器上已有的监控系统、Java 应用、Nginx哪个都可能占了 8080。解决办法不是改容器里的端口——ArchivesSpace 启动脚本写死了一些端口常量——而是改宿主机映射的左侧端口。比如你有服务占了 8080那就ports: - 8090:8080 - 8091:8081 - 8092:8082但要注意一旦改了宿主映射config.rb 里的frontend_url等配置必须同步改。因为这个配置不仅给浏览器访问用还参与一些回调逻辑比如后台生成导出文件的下载链接不保持一致就会出现页面能登录但下载文件 404的怪现象。6. 档案数据不能丢备份恢复方案设计6.1 什么数据需要备份跟普通业务系统不同档案系统对数据安全的要求更苛刻。你要备份的东西有三类数据类别所在位置备份策略MySQL 元数据mysql_data 卷每日全量 定期归档文件存储数字化档案aspace_data 卷的 data/files按增量同步配置文件config/config.rb 和 compose 文件纳入 Git 管理第一类和第二类是最关键的。我见过有同事以为只要备份了 MySQL整个系统就安全了——后来发现数字化扫描件全在data/files目录里数据库丢了能恢复记录结构文件丢了连记录对应的实体都找不回来。所以备份方案里这两样必须捆绑缺一不可。6.2 用 mysqldump 做一致性备份用 Docker volume 做备份有个坏处你把 mysql volume 直接拷贝副本只能保证文件存在不保证数据库一致性。恩正常情况下 MySQL 8.0 的 InnoDB 崩溃恢复机制能处理这种文件级拷贝但如果你启用了 binlog直接从文件拷贝会有一致性问题。稳妥做法永远是走 MySQL 自身的导出工具# 进入 MySQL 容器内执行导出 docker exec aspace-mysql \ mysqldump -u root -p \ --single-transaction \ --routines \ --triggers \ archivesspace aspace_backup_$(date %Y%m%d).sql--single-transaction参数对于 InnoDB 表很重要它能在不锁表的情况下获得一致性快照。档案系统通常不需要实时备份每天凌晨一次全量就够了。6.3 恢复演练别等灾难发生才学恢复备份做得再好没演练过恢复流程等于白做。我建议每季度做一次恢复演练流程很简单# 1. 新建一个临时 MySQL 容器导入备份 # 2. 修改 compose 文件指向这个临时库 # 3. 启动应用容器确认数据可检索恢复时有个细节如果你用了采集了一批索引数据恢复数据库后 Solr 索引并不需要手动清空。启动后 Indexer 会比对数据库记录和索引记录自动把增量补齐。这一点相当人性化。6.4 自动化备份脚本实例给你看我服务器上实际在跑的备份脚本逻辑很简单但很实用#!/bin/bash # /opt/aspace-backup/backup.sh BACKUP_DIR/data/aspace-backup DATE$(date %Y%m%d_%H%M) KEEP_DAYS14 # 1. 备份 MySQL docker exec aspace-mysql \ mysqldump -u root -p${MYSQL_ROOT_PASSWORD} \ --single-transaction \ --routines \ --triggers \ archivesspace ${BACKUP_DIR}/db_${DATE}.sql # 2. 备份文件存储目录 docker run --rm \ -v aspace_data:/source:ro \ -v ${BACKUP_DIR}:/backup \ alpine tar czf /backup/files_${DATE}.tar.gz -C /source data/files # 3. 清理过期备份 find ${BACKUP_DIR} -name db_*.sql -mtime ${KEEP_DAYS} -delete find ${BACKUP_DIR} -name files_*.tar.gz -mtime ${KEEP_DAYS} -delete注意第二步用了临时 alpine 容器来打包 volume这样不需要把数据先拷到宿主机再打包节省磁盘空间也省了一道手续。7. 对外访问与后续扩展反向代理和插件管理7.1 用 Nginx 代理统一入口生产环境很少有人直接让用户访问 8082 端口都是套一层 Nginx。我的做法是把 Staff 前端放内网或者只对工作人员开放Public 前端通过 443 对外提供 HTTPS 访问。那问题来了Public 前端部署在 8082你需要在 Nginx 里做个反向代理server { listen 443 ssl; server_name archive.example.edu.cn; # SSL 证书配置省略 location / { proxy_pass http://127.0.0.1:8082; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这里有个容易被忽略的问题Public 前端内部会生成一些绝对链接比如导出文件、分页链接它使用 config.rb 里AppConfig[:public_url]作为基准。你这个配置如果还写着http://localhost:8082用户通过https://archive.example.edu.cn访问时页面上生成的部分链接会跳到localhost:8082去——用户那边直接打不开。正确改法是AppConfig[:public_url] https://archive.example.edu.cn AppConfig[:frontend_url] http://staff.example.edu.cn:8080一定记住public_url写的是外面用户访问的完整外网地址不是容器内的地址。这个同理也适用于后面如果给 Staff 端做代理。7.2 插件与定制主题ArchivesSpace 支持插件机制官方插件市场有大量的工具比如批量导入导出、EAD 增强、主题美化。Docker 部署方式下加插件有两种办法。第一种是临时进容器把插件目录挂到宿主机但这种一重建容器就丢不推荐。第二种是维持一个私有镜像基于官方镜像做加法# Dockerfile ARG ASPACE_VERSIONv3.4.1 FROM archivesspace/archivesspace:${ASPACE_VERSION} COPY plugins/ /archivesspace/plugins/ COPY build/ /archivesspace/build/在 compose 里用build: .替代image: archivesspace/archivesspace:v3.4.1。重点来了有些插件需要在构建时编译前端资源。ArchivesSpace 后端进程加载插件的同时还需要把插件的静态资源CSS、JS打包进前端界面。纯复制目录不跑构建插件菜单能看到但样式和交互功能有可能缺失。如果你发现插件装了前台不生效大概率是少了build这一步。此时需要进容器里面跑一下/archivesspace/scripts/build_plugins.sh然后重启容器后台管理界面里的插件管理页面会显示当前加载了哪些插件以及版本信息。这套路我建议在正式更新插件之前先在测试环境走一遍避免生产环境改完重启才发现资源没编译。7.3 升级路径和版本锁定的经验容器化部署升级很方便但 ArchivesSpace 的升级有一个特点它是上报式的每次启动新版本应用系统检测到数据库 schema 版本比代码旧就会自动执行迁移脚本。这个设计对日常升级友好但你也应该注意升级前必做两件事备份数据库和文件存储按第六节方案走一遍只升级一个小版本跨度不要从 2.x 直接跳 3.x中间跨了多个迁移脚本成功率会明显下降升级操作本身很简单# 修改 compose 文件里的镜像版本号 # 然后重新拉取并重建 docker compose pull aspace docker compose up -d aspace容器启动后观察日志出现类似Database migration completed字样说明迁移成功。如果卡住不动大概率是数据库表比较大迁移比较耗时给它一点耐心。这个升级方式我跑了三个版本基本零事故。8. 我踩过的一些额外的小坑和最终建议补充几个容易被忽略但会让系统不好用的小细节一并写了。不要随意开启 Browse 界面的小组件配置。后台管理里有很多可选的社区组件比如数据可视化面板、地图组件等。这些组件在演示环境看着炫但实际数据量不大时反而拖慢页面加载而且有些组件依赖额外的 JavaScript 库和 Nginx 代理有一些兼容性问题。我一般建议开最少的组件保持核心检索功能的稳定。利用容器的资源限制参数。如果你的服务器上还跑着别的服务建议给 ArchivesSpace 容器加内存限制。比如deploy: resources: limits: memory: 4g不加限制的话JVM 会把宿主机所有空闲内存都吃掉导致宿主机其他服务跟着遭殃。这是我深有感触的——以前跑一个文档系统Java 应用经常把宿主机内存耗到触发 OOM后来统一加限制才消停。但要提醒你内存限制一旦设低JVM 启动时就该报错调整到稳妥值再重启。善用健康检查接口。ArchivesSpace 后端提供/health接口可以用来做容器健康检查和监控系统的探针。比如curl http://localhost:8081/health返回OK就说明后端活着。我在 Uptime Kuma 和 Prometheus 里都配了这个探针比看端口通不通靠谱因为它能确认应用层面的健康状态而不仅仅是 socket 活着。最后整体部署方案的一个核心思路是数据库和文件存储是命根子应用容器是可以随时随手扔掉的。这种思路决定了你如何设计容错、备份和升级流程。Docker 的优势在于让你用更快的方式部署和替换应用但数据的可靠性从来都不是自动化技术替你兜底的是你自己的备份机制在兜底。我这一年用下来ArchivesSpace 配合 Docker 的方式让档案管理员和 IT 之间的协作顺畅了非常多。前者不必理解 Java 进程和数据库迁移后者不必手工维护一堆系统服务。你跟着这篇文章走完第一遍从零到能用的时间大概在半天左右。后面再基于单位实际需求慢慢调整配置和主题就行这才是这套方案真正的价值所在。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进