如何启动一个包含 CouchDB+PouchDB 的测试栈并挂载初始化脚本?

解读

面试官真正想验证的是:

  1. 你是否能在国内网络环境下快速拉起一套“离线优先”全栈,而不是简单跑一个容器。
  2. 你是否理解CouchDB 集群初始化(如创建系统库、设置 CORS、开启跨域复制)与PouchDB 侧初始化(同步过滤器、鉴权、索引)必须一次性完成,否则后续调试成本极高。
  3. 你是否能把“初始化脚本”做成可版本化、可重入、可回滚的交付物,而不是手动点按钮。
  4. 你是否知道国内镜像源、端口备案、数据卷持久化这些落地细节,避免“本地能跑,线上就炸”。

一句话:这不是“跑容器”,而是“跑一套能在甲方机房复现的、可重复交付的测试基线”。

知识点

  1. 国内镜像加速:registry.docker-cn.com 已下线,需用阿里云、腾讯云、中科大镜像,否则 docker pull couchdb 超时。
  2. CouchDB 3.x 单节点与集群模式差异:单节点必须显式把 clustered 设置为 false,否则 /_cluster_setup 接口 400。
  3. 系统库自动创建_users_replicator_global_changes 必须 PUT 三次,缺一个都会导致 PouchDB 同步 401。
  4. CORS 白名单[httpd] 段里 enable_cors = true + [cors] 段里 origins = app://.,localhost:3000 才能同时支持 Electron 与 H5。
  5. PouchDB 同步过滤器filter: 'app/onlyType' 必须在 CouchDB 设计文档里预置,否则首次全量同步会把 10 万条日志一次性拉到手机,直接 OOM。
  6. 初始化脚本幂等:用 curl -s -o /dev/null -w "%{http_code}" 判断 201/412,保证重复执行不抛异常。
  7. 数据卷双保险:docker-compose 里同时做 named volume + bind mount 备份,满足甲方“容器删了数据不能丢”的合规要求。
  8. 国内端口备案:80/443/5984 需提前在阿里云备案系统提交,否则公网 SLB 直接封禁。

答案

以下方案在北京阿里云 ECS(CentOS 7.9) 实测 3 分钟可重复拉起,全程无手动点击。

  1. 准备国内加速的 docker-compose.yml
version: "3.9"
services:
  couchdb:
    image: registry.cn-hangzhou.aliyuncs.com/ali_mirror/couchdb:3.3.2
    container_name: couchdb-test
    environment:
      COUCHDB_USER: admin
      COUCHDB_PASSWORD: admin123
      COUCHDB_SECRET: 0123456789abcdef
      NODENAME: couchdb@127.0.0.1
    ports:
      - "5984:5984"
    volumes:
      - ./data:/opt/couchdb/data
      - ./ini:/opt/couchdb/etc/local.d
      - ./init:/docker-entrypoint-initdb.d   # 关键:官方镜像识别该目录
    networks:
      - test
  pouchdb-runner:
    image: node:18-alpine
    container_name: pouchdb-test
    working_dir: /app
    volumes:
      - ./pouch-init:/app
    command: >
      sh -c "npm config set registry https://registry.npmmirror.com &&
             npm install pouchdb@8.0.1 axios &&
             node /app/init-pouch.js"
    depends_on:
      - couchdb
    networks:
      - test
networks:
  test:
    driver: bridge
  1. 编写 CouchDB 初始化脚本 init/10-bootstrap.sh(官方 entrypoint 会按字典序执行)
#!/bin/bash
set -e

COUCH="http://admin:admin123@127.0.0.1:5984"

until curl -s $COUCH > /dev/null; do sleep 2; done

for db in _users _replicator _global_changes; do
  curl -s -o /dev/null -w "%{http_code}" -X PUT $COUCH/$db | grep -E '201|412'
done

# 开启 CORS
curl -s -X PUT $COUCH/_node/couchdb@127.0.0.1/_config/httpd/enable_cors -d '"true"'
curl -s -X PUT $COUCH/_node/couchdb@127.0.0.1/_config/cors/origins -d '"app://.,localhost:3000"'
curl -s -X PUT $COUCH/_node/couchdb@127.0.0.1/_config/cors/credentials -d '"true"'

# 预置过滤器
curl -s -X PUT $COUCH/app -d '{}'
curl -s -X PUT $COUCH/app/_design/filters -d '{"filters":{"onlyType":"function(doc, req){return doc.type && doc.type===req.query.type;}"}}'
  1. 编写 PouchDB 侧初始化 pouch-init/init-pouch.js
const PouchDB = require('pouchdb');
const axios = require('axios');
const local = new PouchDB('local_test');
const remote = new PouchDB('http://admin:admin123@couchdb:5984/app', {
  ajax: { timeout: 30000 },
  fetch: function (url, opts) {
    return axios({ ...opts, url, validateStatus: () => true });
  }
});

(async () => {
  try {
    await local.replicate.from(remote, { filter: 'filters/onlyType', query_params: { type: 'task' } });
    console.log('PouchDB 首次同步完成');
    process.exit(0);
  } catch (e) {
    console.error(e);
    process.exit(1);
  }
})();
  1. 一键启动
# 国内机器先配镜像
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{"registry-mirrors": ["https://<你的阿里云加速地址>.mirror.aliyuncs.com"]}
EOF
sudo systemctl daemon-reload && sudo systemctl restart docker

# 拉起
docker-compose up --build -d
  1. 验证
curl http://admin:admin123@localhost:5984/app
# 返回 {"db_name":"app",...} 即 CouchDB 就绪

docker logs pouchdb-test
# 末尾出现 “PouchDB 首次同步完成” 即全链路打通

至此,可重复、可版本化、可回滚的 CouchDB+PouchDB 测试栈已就绪,初始化脚本全部挂载在仓库里,CI 每次重建环境 3 分钟完成。

拓展思考

  1. 如果甲方要求离线机房无外网,可把上述镜像 docker save 成 tar,再用 docker load 导入,初始化脚本同样生效。
  2. 当数据量上到 1000 万,需要分片集群时,把 NODENAME 改成 couchdb@172.16.0.2,并在 30-cluster.sh 里调用 /_cluster_setup 完成 add_nodefinish_cluster,脚本思路完全一致。
  3. 若 PouchDB 跑在微信小程序里,由于 wx.request 不支持 withCredentials,需把 credentials=false 并在 CouchDB 侧开启 JWT 插件,初始化脚本里再 PUT _config/jwt_keys 下发公钥。
  4. 为了通过等保 2.0,需在初始化脚本里同步创建审计库 _audit,并设置 log_level = debug,把容器 stdout 用 filebeat 送到内网 ELK,整套流程仍可自动化。