
1. 先搞清楚这份 docker-compose 文件在整张网络里站的位置1.1 手工敲命令的痛只有连着起过五次网的人懂刚接触 Fabric 的时候多数人都是照着教程一步步敲起 orderer 用一条docker run起 peer 再敲一条参数里塞十几个-e目录挂载写一长串-v。第一次能跑通成就感爆棚等到要重建网络、加一个 peer、给同事复现环境的时候你会发现那些命令散落在终端历史里改错一个字母就报TLS handshake failed排查半小时才发现是证书路径少了一级目录。docker-compose 文件解决的正是这件事它把“这台机器上要跑哪些容器、每个容器用什么镜像、挂载哪些目录、开放哪些端口、连哪个网络”全部收敛成一份 YAML。敲一次docker-compose up -d整张网络的容器拓扑就按描述被拉起重建、迁移、给同伴复现全都变成复制一个文件的事。这就是为什么几乎所有 Fabric 示例网络最终都会落到一份 compose 文件上——它不是语法练习而是把“网络拓扑”这件事从人脑记忆里搬到可版本管理的文本里。这份文件写得好不好直接决定了后面创建通道、部署链码、跑业务合约时你是顺畅推进还是一路在容器日志里刨根问底。下面我按自己反复搭网的经验把这份文件从结构到细节完整拆一遍包括那些教程里通常不写、但一定会踩的坑。1.2 目录结构与网络拓扑先画图再动手动手写 YAML 之前先在纸上把网络拓扑定下来这一步偷懒后面百分之八十的错都从这里来。我给自己的最小可用模型是一个排序服务节点、两个机构各一个 peer、每个机构一个 CA、再加一个命令行容器。这个规模足够跑通“建通道—加入—装链码—调用”的全流程又不至于让文件长到看不懂。与之配套的目录结构必须提前定好因为 compose 里的挂载路径全都相对它来写network/ ├── docker/ │ ├── docker-compose-test.yaml │ └── docker-compose-base.yaml ├── configtx/ │ └── configtx.yaml ├── crypto-config/ │ └── crypto-config.yaml ├── organizations/ # 由证书生成工具产出 │ ├── ordererOrganizations/ │ └── peerOrganizations/ ├── channel-artifacts/ # 创世块、通道交易文件 └── scripts/ └── network.sh这里有个容易被忽略的细节compose 文件里所有相对路径都是相对compose 文件自身所在目录解析的而不是相对你执行命令时的当前目录。所以我把 compose 文件放在docker/子目录里挂载路径就统一写成../organizations/...。很多人习惯在根目录执行docker-compose -f docker/docker-compose-test.yaml up然后发现容器起不来报证书文件不存在——不是文件没生成而是路径基准点搞错了。要么把相对路径写对要么干脆用--project-directory显式指定基准目录两种做法都行我倾向前者因为文件放在仓库里谁看都明白。1.3 版本号写 2.x 不是守旧是被依赖关系逼的打开任何一个 Fabric 官方示例version字段基本都写着2或2.4。新手第一反应是“这不是老语法吗为什么不用 3.x”。原因很实在3.x 移除了depends_on的条件写法也就是condition: service_healthy这类能力。Fabric 网络里 CA 必须先于 peer 就绪排序节点必须先于通道创建这种顺序控制在 2.x 里能靠健康检查加条件依赖表达得比较干净3.x 里就得自己写轮询脚本兜底。我的做法是version: 2.4配合healthcheck给 CA 容器加一个端口探测peer 用depends_on的条件形式挂上去。当然即便这么写也别完全信任它——depends_on只保证容器启动顺序不保证服务可用这一点在第 2.5 节会详细讲也是我最想提醒的一条。顺带说一句如果你所在的环境 Docker 引擎版本比较新docker-compose命令可能已经统一成docker compose中间是空格。两个命令读的是同一份文件语法完全兼容只是子命令组织方式不同。写脚本的时候建议先探测一下用哪个别在 CI 里因为一条命令名报错卡半天。2. 五个核心块逐个拆解写对不难对齐才难2.1 services每个容器就是一个网络角色services是整份文件的主干一个键就是一个容器键名建议直接写成“角色 域名”的形式比如orderer.demo.com、peer0.org1.demo.com。这样写有两个好处一是container_name通常与键名保持一致进容器排查时一眼能认出是谁二是 Fabric 内部大量使用域名做身份标识和 TLS 校验键名与域名对齐能省掉一堆脑子里的映射。每个 service 下面我固定写这几项container_name、image、environment、volumes、ports、networks排序节点再加command。镜像标签不要写latest用一个变量$IMAGE_TAG从.env注入或者直接写死具体版本号。我吃过这个亏某次本地镜像被更新覆盖链码容器和 peer 的版本对不上报了一堆看不懂的协议错误查了两小时才定位到版本漂移。写死版本是给自己留后路。另外container_name这一项在单机开发环境里写上很舒服但如果你打算在同一台机器上跑两套网络比如一套测试一套演示就必须去掉或者加前缀否则容器名冲突直接起不来。生产环境更是不要写死交给编排系统去分配。2.2 volumes证书落盘的三条铁律卷挂载是整份文件里最容易出错的部分我给自己的三条铁律是路径要对、权限要对、命名卷要区分。第一条路径对齐。证书生成工具产出的目录结构是固定的peer 的 MSP 在organizations/peerOrganizations/org1.demo.com/peers/peer0.org1.demo.com/mspTLS 材料在同级的tls目录下。挂载时容器内的目标路径必须与环境变量里声明的路径一致比如CORE_PEER_MSPCONFIGPATH/etc/hyperledger/fabric/msp那volumes里就必须把宿主机 MSP 目录挂到/etc/hyperledger/fabric/msp。这两处一旦不一致容器启动时就会报failed to load MSP而且报错信息不会告诉你到底是哪个路径错了只能自己对。第二条权限。证书文件是由宿主机上的工具生成的属主是当前用户而容器内进程通常以非 root 用户运行。如果生成的私钥权限是600且属主不匹配peer 读不到私钥就会起不来。我的处理方式是生成证书后统一chmod一遍或者在生成脚本里就设定好合适的权限别等到容器报错再回头折腾。第三条命名卷区分。账本数据/var/hyperledger/production、排序服务的数据目录这类需要持久化的路径用命名卷并在文件顶部的volumes:段里声明。每个节点的卷名要唯一比如peer0.org1.demo.com、orderer.demo.com。统一用一个peer-data卷的话两个 peer 会互相覆盖账本症状是区块高度莫名其妙对不上非常难查。注意开发阶段反复重建网络时记得清理命名卷。容器删了卷还在账本数据残留会导致新网络带着旧状态启动表现出来就是“明明重建了通道却已存在”。2.3 networks容器间通信的命名空间与那个著名的坑所有容器必须加入同一个自定义网络这一点没有商量余地。用默认的 bridge 网络也能通但 DNS 解析靠容器名而 Fabric 的证书 SAN 里写的是域名两边对不上就会 TLS 校验失败。自定义网络里 Docker 内置了 DNS容器之间可以直接用服务名互访peer0.org1.demo.com:7051这样的地址才能解析成功。这里有一个几乎所有新手都会踩的坑compose 会自动给网络名加项目前缀。假设你的 compose 文件在network/docker/目录下网络声明为demo那么实际创建出来的网络名是docker_demo取目录名做项目名。而 peer 启动链码容器时需要知道把链码容器接到哪个网络这个信息由CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE指定。如果你按直觉写成demo链码容器就会被接到一个不存在的网络或者默认网络症状是链码容器起来了但 peer 连不上它日志里反复刷connection refused。解决办法有三个按推荐程度排序一是在 compose 文件里用networks: demo: name: demo显式指定网络名绕开前缀二是在.env里设置COMPOSE_PROJECT_NAMEdemo让项目名固定下来三是老老实实把CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE写成docker_demo。我一般用第一种因为语义最清晰看文件就知道最终网络叫什么。验证方法很简单网络起来后执行docker network ls看实际名字再回头核对环境变量一分钟的事。2.4 environment变量对齐比写对更难环境变量这一块难点不在记住有哪些变量而在于同一份信息要在多个地方保持一致。举个具体的peer 的CORE_PEER_ID是peer0.org1.demo.comCORE_PEER_ADDRESS也是这个域名加端口CORE_PEER_GOSSIP_EXTERNALENDPOINT还是它MSP 目录挂载路径里又出现了这个域名。四个地方任何一个写错症状都不一样CORE_PEER_ID错了日志里的节点名对不上CORE_PEER_ADDRESS错了其他节点找不到它GOSSIP_EXTERNALENDPOINT错了跨机构 gossip 收不到区块。我的习惯是在写文件之前先把域名规划列成一张表后面所有配置都从表里抄角色域名端口MSP ID排序节点orderer.demo.com7050OrdererMSP机构一 peerpeer0.org1.demo.com7051Org1MSP机构二 peerpeer0.org2.demo.com7051Org2MSP机构一 CAca.org1.demo.com7054-机构二 CAca.org2.demo.com7054-有了这张表写 YAML 就是填空。另外提醒一点FABRIC_LOGGING_SPEC在开发阶段设成INFO就够排查疑难问题时临时改成DEBUG但别长期开着日志量会大到把磁盘写满而且真正的关键信息会被淹没。生产环境建议设成WARN配合日志采集工具按需开启。2.5 depends_on 骗了很多人启动顺序的真实解法depends_on只能保证容器按顺序创建和启动不能保证里面的服务已经准备好接受连接。这一点在 Fabric 网络里尤其致命CA 容器启动后需要几秒钟初始化数据库和监听端口如果 peer 紧接着启动并尝试连接 CA 申请证书就会拿到连接拒绝。我在 compose 2.x 里的写法是给关键服务加健康检查再用条件依赖services: ca.org1.demo.com: image: hyperledger/fabric-ca:$IMAGE_TAG healthcheck: test: [CMD, curl, -f, http://localhost:7054/cainfo] interval: 5s timeout: 3s retries: 10 peer0.org1.demo.com: depends_on: ca.org1.demo.com: condition: service_healthy这里要注意健康检查的探测命令必须在容器里有对应的可执行文件。很多精简镜像里没有curl探测永远失败容器状态一直是unhealthy反而把起网卡住。没有curl就用nc -z localhost 7054或者干脆写个 shell 判断。实在搞不定就别依赖健康检查老老实实在启动脚本里写一个循环每两秒探测一次端口最多等六十秒。这个笨办法我用得最多因为它不依赖镜像里有什么工具行为完全可预期。排序节点和 peer 之间的顺序倒是不用太在意peer 启动时连不上排序服务不会退出等排序服务起来后会自动重连。真正需要严格顺序的是 CA 与 peer以及创世块生成与排序节点启动。3. 手把手写配置四类容器的关键字段3.1 排序节点TLS 三件套与监听地址排序节点是整张网络的时间线它的配置重点在 TLS 和监听地址。我先说监听地址这个坑ORDERER_GENERAL_LISTENADDRESS默认是127.0.0.1在容器里这意味着只监听环回地址宿主机和其他容器都连不进来。必须显式改成0.0.0.0。我第一次搭的时候没改peer 日志里一直刷连接超时查了半天网络配置最后发现是监听地址的问题。TLS 部分要挂三个文件证书、私钥、根证书对应ORDERER_GENERAL_TLS_CERTIFICATE、ORDERER_GENERAL_TLS_PRIVATEKEY、ORDERER_GENERAL_TLS_ROOTCAS。注意ROOTCAS的值是个数组形式写成[/var/hyperledger/orderer/tls/ca.crt]少了方括号会解析失败。如果排序服务是多节点集群还要额外配置 cluster 相关的证书变量单节点可以不管。创世块挂载也别忘了。排序节点启动时需要引导区块路径由ORDERER_GENERAL_BOOTSTRAPFILE指定通常挂载到/var/hyperledger/orderer/orderer.genesis.block宿主机路径指向channel-artifacts/genesis.block。如果这个文件不存在容器会直接退出报错信息还算清楚但新手容易误以为是证书问题。启动前先ls一眼比事后排查省时间。orderer.demo.com: container_name: orderer.demo.com image: hyperledger/fabric-orderer:$IMAGE_TAG environment: - FABRIC_LOGGING_SPECINFO - ORDERER_GENERAL_LISTENADDRESS0.0.0.0 - ORDERER_GENERAL_LISTENPORT7050 - ORDERER_GENERAL_LOCALMSPIDOrdererMSP - ORDERER_GENERAL_LOCALMSPDIR/var/hyperledger/orderer/msp - ORDERER_GENERAL_TLS_ENABLEDtrue - ORDERER_GENERAL_TLS_PRIVATEKEY/var/hyperledger/orderer/tls/server.key - ORDERER_GENERAL_TLS_CERTIFICATE/var/hyperledger/orderer/tls/server.crt - ORDERER_GENERAL_TLS_ROOTCAS[/var/hyperledger/orderer/tls/ca.crt] - ORDERER_GENERAL_BOOTSTRAPMETHODfile - ORDERER_GENERAL_BOOTSTRAPFILE/var/hyperledger/orderer/orderer.genesis.block working_dir: /root command: orderer volumes: - ../organizations/ordererOrganizations/demo.com/orderers/orderer.demo.com/msp:/var/hyperledger/orderer/msp - ../organizations/ordererOrganizations/demo.com/orderers/orderer.demo.com/tls:/var/hyperledger/orderer/tls - ../channel-artifacts/genesis.block:/var/hyperledger/orderer/orderer.genesis.block - orderer.demo.com:/var/hyperledger/production/orderer ports: - 7050:7050 networks: - demo3.2 peer 节点Docker 套接字是链码的命门peer 的配置项最多我按重要性排个序。第一位是CORE_VM_ENDPOINT和 Docker 套接字挂载。Fabric 的链码运行在独立容器里由 peer 通过 Docker API 拉起所以 peer 容器必须能访问宿主机的 Docker 守护进程。做法是挂载/var/run/docker.sock或者在容器里挂/var/run/整个目录到/host/var/run/再把CORE_VM_ENDPOINT设成unix:///host/var/run/docker.sock。少了这一步链码永远起不来报错是Cannot connect to the Docker daemon。第二位是CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE前面 2.3 节讲过必须与真实网络名完全一致。这一项错了链码容器虽然能起来但 peer 连不上它表现是链码调用超时。第三位是链码监听地址。peer 除了 7051 的常规端口还有一个 7052 端口专门给链码容器回连。CORE_PEER_CHAINCODEADDRESS要写成域名加 7052CORE_PEER_CHAINCODELISTENADDRESS写成0.0.0.0:7052。新版 Fabric 还提供了CORE_PEER_CHAINCODELISTENADDRESS之外的链码服务发现方式但保留这两项最稳。第四位是 gossip 配置。CORE_PEER_GOSSIP_BOOTSTRAP填本机构的其他 peer单 peer 机构填自己就行CORE_PEER_GOSSIP_EXTERNALENDPOINT必须写成其他机构能访问到的地址容器网络内就是域名加端口。这一项写错跨机构同步区块会出问题症状是同一条交易在这个 peer 上有、在那个 peer 上没有。peer0.org1.demo.com: container_name: peer0.org1.demo.com image: hyperledger/fabric-peer:$IMAGE_TAG environment: - CORE_VM_ENDPOINTunix:///host/var/run/docker.sock - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODEdemo - FABRIC_LOGGING_SPECINFO - CORE_PEER_TLS_ENABLEDtrue - CORE_PEER_TLS_CERT_FILE/etc/hyperledger/fabric/tls/server.crt - CORE_PEER_TLS_KEY_FILE/etc/hyperledger/fabric/tls/server.key - CORE_PEER_TLS_ROOTCERT_FILE/etc/hyperledger/fabric/tls/ca.crt - CORE_PEER_IDpeer0.org1.demo.com - CORE_PEER_ADDRESSpeer0.org1.demo.com:7051 - CORE_PEER_LISTENADDRESS0.0.0.0:7051 - CORE_PEER_CHAINCODEADDRESSpeer0.org1.demo.com:7052 - CORE_PEER_CHAINCODELISTENADDRESS0.0.0.0:7052 - CORE_PEER_GOSSIP_BOOTSTRAPpeer0.org1.demo.com:7051 - CORE_PEER_GOSSIP_EXTERNALENDPOINTpeer0.org1.demo.com:7051 - CORE_PEER_LOCALMSPIDOrg1MSP - CORE_PEER_MSPCONFIGPATH/etc/hyperledger/fabric/msp - CORE_OPERATIONS_LISTENADDRESS0.0.0.0:9444 - CORE_METRICS_PROVIDERprometheus volumes: - /var/run/:/host/var/run/ - ../organizations/peerOrganizations/org1.demo.com/peers/peer0.org1.demo.com/msp:/etc/hyperledger/fabric/msp - ../organizations/peerOrganizations/org1.demo.com/peers/peer0.org1.demo.com/tls:/etc/hyperledger/fabric/tls - peer0.org1.demo.com:/var/hyperledger/production working_dir: /root command: peer node start ports: - 7051:7051 - 9444:9444 networks: - demo机构二的那一份把org1、Org1MSP、端口换成对应的值就行。宿主机端口必须错开机构二 peer 用9051:7051否则两个容器抢同一个宿主端口第二个直接起不来。这个错误信息很直白但如果你用脚本批量生成配置很容易漏改。3.3 CA 与 CLI一个管发证一个管发命令CA 容器的配置相对简单重点是数据库和 TLS。开发环境用默认的 SQLite 就够生产环境建议接外部数据库并把数据目录挂出来持久化。环境变量里需要设置管理员账号密码、机构名称和 MSP 标识这几个值要和后续申请证书时的参数一致。ca.org1.demo.com: container_name: ca.org1.demo.com image: hyperledger/fabric-ca:$IMAGE_TAG environment: - FABRIC_CA_HOME/etc/hyperledger/fabric-ca-server - FABRIC_CA_SERVER_CA_NAMEca.org1.demo.com - FABRIC_CA_SERVER_TLS_ENABLEDtrue - FABRIC_CA_SERVER_PORT7054 - FABRIC_CA_SERVER_OPERATIONS_LISTENADDRESS0.0.0.0:17054 ports: - 7054:7054 - 17054:17054 command: sh -c fabric-ca-server start -b admin:adminpw -d volumes: - ../organizations/fabric-ca/org1:/etc/hyperledger/fabric-ca-server networks: - demoCLI 容器是我很推荐加的一个东西。它本身不承载网络功能就是一个装了 peer 命令行工具的容器把证书目录、通道产物目录、compose 网络挂进去环境变量指向你要操作的那个 peer。有了它建通道、加入通道、安装链码这些操作都可以在容器里用一行命令完成不用在宿主机装一堆工具也不用每次手动导出一堆环境变量。cli: container_name: cli image: hyperledger/fabric-tools:$IMAGE_TAG tty: true stdin_open: true environment: - GOPATH/opt/gopath - FABRIC_LOGGING_SPECINFO - CORE_PEER_IDcli - CORE_PEER_ADDRESSpeer0.org1.demo.com:7051 - CORE_PEER_TLS_ENABLEDtrue - CORE_PEER_TLS_ROOTCERT_FILE/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.demo.com/peers/peer0.org1.demo.com/tls/ca.crt - CORE_PEER_LOCALMSPIDOrg1MSP - CORE_PEER_MSPCONFIGPATH/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.demo.com/users/Adminorg1.demo.com/msp working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer command: /bin/bash volumes: - /var/run/:/host/var/run/ - ../organizations:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ - ../channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts depends_on: - peer0.org1.demo.com networks: - demoCLI 容器的挂载路径看起来很长但逻辑很简单把organizations整个目录挂到容器里某个位置然后把环境变量指向这个位置下的具体证书。路径写长了容易错我的习惯是先挂载进容器ls一遍确认真实路径再回头填环境变量比对着文档猜要快得多。3.4 端口规划与卷挂载对照表端口冲突是起网阶段最常见的低级错误我整理了一张对照表每次建新网络照着分配服务容器内端口宿主机端口示例用途排序节点70507050排序服务 gRPC排序节点运维94439443指标与健康检查机构一 peer70517051peer gRPC机构一链码7052不映射链码容器回连机构一运维94449444指标采集机构二 peer70519051peer gRPC机构二运维94449445指标采集机构一 CA70547054证书签发机构二 CA70548054证书签发链码端口 7052 一般不映射到宿主机因为它是容器网络内部的回连通道映射出去反而增加暴露面。运维端口看你要不要接监控开发阶段可以先不映射等要做指标观测时再加。卷挂载这边我建议在文件顶部统一声明命名卷正文里引用。命名卷的好处是删除容器后数据还在坏处也是这个——重建网络时忘了清理会带着旧状态启动。我的习惯是在重建脚本里显式加一句清理命名卷的命令把这件事变成流程的一部分而不是靠记忆。3.5 多文件拆分与变量注入文件写到三四百行的时候改一个机构二的端口要往下翻很久容易改漏。我的做法是拆成两个文件docker-compose-base.yaml放各服务的公共模板docker-compose-test.yaml放具体实例。compose 支持用多个-f参数合并文件后加载的覆盖先加载的。docker-compose \ -f docker/docker-compose-base.yaml \ -f docker/docker-compose-test.yaml \ up -d合并规则要记清楚标量字段后者覆盖前者列表字段是追加而不是替换。所以端口列表如果两边都写最终是两个都生效这可能不是你想要的。我一般在 base 文件里只写环境变量和卷这类需要继承的部分具体端口和容器名放在实例文件里避免混淆。变量注入用.env文件把镜像标签、域名后缀、项目名这些放在里面IMAGE_TAG2.5 COMPOSE_PROJECT_NAMEdemo DOMAIN_SUFFIXdemo.comcompose 会自动读取同目录下的.env然后在 YAML 里用${IMAGE_TAG}引用。这样换版本只改一处不用满文件搜索替换。注意${}的默认值语法是${VAR:-default}给个兜底值能避免变量没定义时报空字符串那种错误排查起来很费劲。4. 起网、看日志、定位问题的一条龙经验4.1 三阶段起网法别一把梭我刚开始喜欢直接up -d一把起全部然后盯着满屏日志找问题。后来改成三阶段效率高很多第一阶段只起 CA确认两个 CA 容器都是Up状态并且能访问/cainfo接口返回 JSON。这一步没过后面的证书全白搭。第二阶段起排序节点确认容器没有退出日志里能看到Beginning to serve on service address这类字样。如果容器反复重启先看docker logs的最后二十行八成是创世块路径或者 MSP 路径不对。第三阶段起 peer 和 CLI起来后进 CLI 容器执行一次peer channel list能返回空列表就说明证书、TLS、网络三者都通了。这一条命令是我判断“网络是否可用”的标准动作比看日志快得多。# 阶段一 docker-compose -f docker/docker-compose-test.yaml up -d ca.org1.demo.com ca.org2.demo.com docker ps --format table {{.Names}}\t{{.Status}} # 阶段二 docker-compose -f docker/docker-compose-test.yaml up -d orderer.demo.com docker logs --tail 30 orderer.demo.com # 阶段三 docker-compose -f docker/docker-compose-test.yaml up -d docker exec -it cli peer channel list三阶段法的价值在于把问题范围缩小。一把梭的时候十几个容器同时刷日志你根本不知道先看哪个分阶段之后出错的一定是刚起的那个排查路径清晰。4.2 报错速查表这些坑我都踩过现象大概率原因处理动作peer 启动即退出日志含failed to load MSPMSP 挂载路径与环境变量不一致核对CORE_PEER_MSPCONFIGPATH与volumes目标路径链码容器起不来报 Docker daemon 连接失败未挂载 Docker 套接字挂载/var/run/并设置CORE_VM_ENDPOINT链码安装成功但调用超时链码容器网络名与 peer 配置不一致docker network ls核对真实网络名排序节点起来但 peer 连不上监听地址是127.0.0.1改为0.0.0.0报 TLS 握手失败证书 SAN 与访问域名不匹配检查证书配置里的 SAN 列表容器启动报端口被占用宿主机端口冲突调整映射端口机构间错开CLI 里命令报证书找不到挂载路径与变量路径不一致进容器ls确认真实路径重建网络后通道已存在命名卷未清理删除容器后清理对应命名卷跨机构区块不同步gossip 外部端点配置错误核对CORE_PEER_GOSSIP_EXTERNALENDPOINT日志疯狂增长占满磁盘日志级别设为 DEBUG改回 INFO 或 WARN关于 TLS 握手失败这一条我要多讲两句。Fabric 的证书里带有 SAN主题备用名称里面列的是这个证书允许被哪些域名访问。证书生成工具的配置文件里有一段SANS配置必须把localhost、127.0.0.1以及所有容器域名都写进去。改完配置要重新生成证书光重启容器没用。这个坑我踩过一次改了半小时环境变量都没解决最后发现是证书里压根没写那个域名。4.3 几条踩出来的经验文档里不会写第一条写文件之前先起一遍最小网络。我的习惯是先用命令行手动起一个 orderer 和一个 peer把路径、端口、变量都验证一遍确认能通之后再誊到 YAML 里。这样排查问题时变量少定位快。直接写三百行 YAML 然后一把梭出错时面对的是十几个未知数效率反而低。第二条路径一律用绝对思维写相对路径。写挂载路径的时候脑子里默念一遍“这个路径是相对 compose 文件目录的”写完ls一下宿主机对应位置确认文件真的在那儿。这一步花十秒能省掉后面半小时的No such file or directory排查。第三条别迷信depends_on。它在编排简单场景里够用但在 Fabric 这种对时序敏感的网络里只能算个辅助。真正的顺序保证来自启动脚本里的探测循环或者干脆分阶段手动起。我现在的脚本里CA 起完之后一定会轮询/cainfo直到返回 200 才继续这个二十行的循环比任何声明式依赖都可靠。第四条开发环境和演示环境用两套端口。同一台机器上经常要同时跑两套网络做对比端口全用默认值必然冲突。我的做法是演示环境统一加 1000 的偏移一条 sed 命令就能批量改不用逐个调。第五条给 compose 文件加注释。环境变量那么多三个月后回来看你自己都不记得那个CORE_PEER_GOSSIP_*是干嘛的。在关键变量上方写一行中文注释成本极低收益很高。我现在的习惯是每个 service 顶部写一句“这个容器负责什么、依赖谁、被谁依赖”排查时扫一眼就明白拓扑关系。第六条别在生产环境照抄开发配置。开发环境的 compose 文件为了简单往往把私钥直接挂载、日志全开、没有资源限制。生产环境至少要补上 CPU 和内存限制、重启策略、日志轮转配置。资源限制尤其重要某个 peer 内存失控会拖垮整台机器上的所有容器这个连带影响在测试环境不明显在生产环境是灾难。资源限制的写法很简单在 service 下加deploy: resources: limits: cpus: 0.50 memory: 1G reservations: memory: 512M restart: unless-stopped logging: driver: json-file options: max-size: 50m max-file: 3restart: unless-stopped保证容器异常退出后自动拉起但要配合健康检查才有意义否则容器起了一个坏掉的服务重启一百次也没用。日志轮转一定要配Fabric 的日志在 INFO 级别下一天能写几百兆不加限制迟早把磁盘吃满。我在实际搭网时最深的体会是这份 compose 文件的价值不在于“写得多全”而在于“写得多可复现”。一份好的配置换台机器、换个同事up -d之后行为完全一致不用口头补充“哦这里要改一下”“那里要注意”。把所有隐含知识都显式写进文件和注释里才是真正把网络搭稳了。后面如果要做多机部署这套单机的 compose 配置也能直接拆成每个节点一份把挂载路径换成共享存储或者各自的本地路径环境变量几乎不用动迁移成本很低。