ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Fabric docker-compose网络配置与避坑指南

Fabric docker-compose网络配置与避坑指南

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/msp,TLS 材料在同级的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_NAME=demo,让项目名固定下来;三是老老实实把CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE写成docker_demo。我一般用第一种,因为语义最清晰,看文件就知道最终网络叫什么。验证方法很简单,网络起来后执行docker network ls,看实际名字,再回头核对环境变量,一分钟的事。

2.4 environment:变量对齐比写对更难

环境变量这一块,难点不在记住有哪些变量,而在于同一份信息要在多个地方保持一致。举个具体的:peer 的CORE_PEER_ID是peer0.org1.demo.com,CORE_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_SPEC=INFO - ORDERER_GENERAL_LISTENADDRESS=0.0.0.0 - ORDERER_GENERAL_LISTENPORT=7050 - ORDERER_GENERAL_LOCALMSPID=OrdererMSP - ORDERER_GENERAL_LOCALMSPDIR=/var/hyperledger/orderer/msp - ORDERER_GENERAL_TLS_ENABLED=true - 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_BOOTSTRAPMETHOD=file - 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: - demo

3.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要写成域名加 7052,CORE_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_ENDPOINT=unix:///host/var/run/docker.sock - CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=demo - FABRIC_LOGGING_SPEC=INFO - CORE_PEER_TLS_ENABLED=true - 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_ID=peer0.org1.demo.com - CORE_PEER_ADDRESS=peer0.org1.demo.com:7051 - CORE_PEER_LISTENADDRESS=0.0.0.0:7051 - CORE_PEER_CHAINCODEADDRESS=peer0.org1.demo.com:7052 - CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:7052 - CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org1.demo.com:7051 - CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org1.demo.com:7051 - CORE_PEER_LOCALMSPID=Org1MSP - CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp - CORE_OPERATIONS_LISTENADDRESS=0.0.0.0:9444 - CORE_METRICS_PROVIDER=prometheus 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_NAME=ca.org1.demo.com - FABRIC_CA_SERVER_TLS_ENABLED=true - FABRIC_CA_SERVER_PORT=7054 - FABRIC_CA_SERVER_OPERATIONS_LISTENADDRESS=0.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: - demo

CLI 容器是我很推荐加的一个东西。它本身不承载网络功能,就是一个装了 peer 命令行工具的容器,把证书目录、通道产物目录、compose 网络挂进去,环境变量指向你要操作的那个 peer。有了它,建通道、加入通道、安装链码这些操作都可以在容器里用一行命令完成,不用在宿主机装一堆工具,也不用每次手动导出一堆环境变量。

cli: container_name: cli image: hyperledger/fabric-tools:$IMAGE_TAG tty: true stdin_open: true environment: - GOPATH=/opt/gopath - FABRIC_LOGGING_SPEC=INFO - CORE_PEER_ID=cli - CORE_PEER_ADDRESS=peer0.org1.demo.com:7051 - CORE_PEER_TLS_ENABLED=true - 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_LOCALMSPID=Org1MSP - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.demo.com/users/Admin@org1.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: - demo

CLI 容器的挂载路径看起来很长,但逻辑很简单:把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_TAG=2.5 COMPOSE_PROJECT_NAME=demo DOMAIN_SUFFIX=demo.com

compose 会自动读取同目录下的.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: "3"

restart: unless-stopped保证容器异常退出后自动拉起,但要配合健康检查才有意义,否则容器起了一个坏掉的服务,重启一百次也没用。日志轮转一定要配,Fabric 的日志在 INFO 级别下一天能写几百兆,不加限制迟早把磁盘吃满。

我在实际搭网时最深的体会是,这份 compose 文件的价值不在于“写得多全”,而在于“写得多可复现”。一份好的配置,换台机器、换个同事,up -d之后行为完全一致,不用口头补充“哦这里要改一下”“那里要注意”。把所有隐含知识都显式写进文件和注释里,才是真正把网络搭稳了。后面如果要做多机部署,这套单机的 compose 配置也能直接拆成每个节点一份,把挂载路径换成共享存储或者各自的本地路径,环境变量几乎不用动,迁移成本很低。

返回列表