# 普通用户启动Docker_VM链
本章节介绍在宿主机上以**非 root(普通用户)** 身份,通过命令行工具启动一条**启用 Docker 合约引擎(`VM-GO`)** 的长安链的全过程。若不涉及 cgroup 资源限制,普通用户部署与 root 用户部署的操作步骤基本一致,唯一区别是需要将部署用户加入 `docker` 组(详见 [环境准备](#环境准备))。
**前提假设**:
1. 本文默认读者已经决定**启用 Docker 合约引擎**(即在 `prepare.sh` 中选择 `enable vm go = YES`)。若未启用 Docker 合约引擎,节点主进程本身对宿主机没有 root 依赖,普通用户直接执行 `./start.sh` 即可,无需参考本文;
2. 本文默认使用证书模式(`prepare.sh`)进行说明,PK / PWK 模式(`prepare_pk.sh`、`prepare_pwk.sh`)在**权限相关的操作步骤上完全一致**,仅证书 / 密钥生成脚本不同;
3. 本章节仅涉及"如何用非root用户跑起一条链",不涉及生产环境的账号规划与安全加固。生产环境请按照贵司安全规范另行加固。
**操作用户约定**:下文默认部署用户为 `deploy`(请按实际情况替换)。**除非章节中明确标注"以 root / sudo 身份执行",其余所有步骤均以 `deploy` 用户执行**,因此部署目录、`data`、`log` 的属主天然为 `deploy`,无需额外 `chown`。
---
## 环境准备
### 操作系统与依赖软件
- 操作系统:Linux(推荐 Ubuntu 20.04+、CentOS 7+);
- 软件依赖:`docker`(≥ 20.10)、`golang`(1.24.13+)、`gcc`(7.3+)、`git`。
Docker 安装完成后,请**将部署用户加入 `docker` 用户组**,否则该用户执行 `docker` 命令会因 `/var/run/docker.sock` 权限不足而失败:
```shell
# 需要 root 权限执行
sudo usermod -aG docker
```
**注意**:`usermod` 生效前需要**重新登录**该用户的会话(`exit` 后重新 `ssh` 或重开一个终端),执行 `id` 命令确认输出中包含 `docker` 用户组后再继续。
### 拉取合约引擎镜像
拉取官方 Docker 合约引擎镜像(版本号请按实际使用的长安链版本替换):
```shell
docker pull chainmakerofficial/chainmaker-vm-engine:v2.3.9
```
由于国内对 Docker Hub 的访问限制,可能出现无法拉取镜像的情况:
```
Unable to find image 'chainmakerofficial/chainmaker-vm-engine:v2.3.9' locally
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).
```
针对这种情况,可以替换为长安链团队自建的 Docker 镜像源,修改 Docker 配置文件 `/etc/docker/daemon.json`(若不存在则新建):
```json
{
"registry-mirrors": ["https://hub-dev.cnbn.org.cn"]
}
```
配置完成后需要重启 Docker 服务:`sudo systemctl restart docker`
`hub-dev.cnbn.org.cn` 是长安链团队自建的镜像源,也可以直接访问该地址,通过左上角搜索获取目前支持的镜像列表。
配置完成后重新执行拉取命令即可。
### 部署目录规划
```shell
mkdir -p /home/deploy/chainmaker
cd /home/deploy/chainmaker
```
后续生成的 `release` 包 / 数据目录 / 日志目录都放置在该目录下。
---
## 下载源码、编译并制作安装包
### 下载源码
本文以 chainmaker-go v2.3.10、chainmaker-cryptogen v2.3.7 为例进行说明,全文所有步骤均基于该版本。如需部署更高版本,请在[版本迭代说明](../instructions/版本迭代说明.md)中查询对应的合约引擎版本和证书生成工具版本,并替换下方命令中的分支号。
```shell
# 下载 chainmaker-go 源码
git clone -b v2.3.10 --depth=1 https://git.chainmaker.org.cn/chainmaker/chainmaker-go.git
# 下载证书生成工具源码
git clone -b v2.3.7 --depth=1 https://git.chainmaker.org.cn/chainmaker/chainmaker-cryptogen.git
```
### 编译证书生成工具
```shell
cd chainmaker-cryptogen
make
cd ..
```
### 软链接 cryptogen 到 tools 目录
```shell
cd chainmaker-go/tools
ln -s ../../chainmaker-cryptogen/ .
cd ..
```
### 生成证书与配置
进入源码目录 `chainmaker-go/scripts`,执行 `prepare.sh` 生成 4 节点集群的证书和 `chainmaker.yml`:
```shell
cd scripts
./prepare.sh 4 1
```
按提示交互(关键项如下,其他保持默认):
```
input consensus type (0-SOLO,1-TBFT(default),3-HOTSTUFF,4-RAFT,5-DPOS):
input log level (DEBUG|INFO(default)|WARN|ERROR):
enable vm go (YES|NO(default)): YES
vm go transport protocol (uds|tcp(default)): tcp
```
- `enable vm go`:**填 `YES`**,启用 Docker 合约引擎;
- `vm go transport protocol`:非 root 场景建议使用 **`tcp`** (uds 需要挂载 socket 目录,容器内外的用户身份需一致)。
### 编译并生成安装包
```shell
./build_release.sh
```
执行成功后可在 `chainmaker-go/build/release/` 下看到 4 个节点的 tar 包:
```
../build/release/
├── chainmaker-v2.3.10-wx-org1.chainmaker.org-YYYYMMDDhhmmss-x86_64.tar.gz
├── chainmaker-v2.3.10-wx-org2.chainmaker.org-YYYYMMDDhhmmss-x86_64.tar.gz
├── chainmaker-v2.3.10-wx-org3.chainmaker.org-YYYYMMDDhhmmss-x86_64.tar.gz
└── chainmaker-v2.3.10-wx-org4.chainmaker.org-YYYYMMDDhhmmss-x86_64.tar.gz
```
---
## 部署与启动
根据实际情况选择以下两种方式之一。
### 方式一:在当前服务器上直接启动(推荐)
如果编译和部署在**同一台服务器**上完成,可直接使用 `cluster_quick_start.sh` 脚本一键完成**解压 + 启动**,无需手动解压:
```shell
cd chainmaker-go/scripts
./cluster_quick_start.sh normal
```
该脚本会自动解压 `build/release/` 下的所有 tar 包,并依次调用每个节点的 `bin/start.sh` 启动节点和合约引擎容器。
启动成功后,将 tar 包备份,以免下次重新启动时文件被覆盖:
```shell
mkdir -p ../build/bak
mv ../build/release/*.tar.gz ../build/bak
```
### 方式二:在另一台服务器上准备好后拷贝部署
如果编译在**开发机**上完成,需要将安装包拷贝到部署机后再启动。
在部署机上解压单个节点的安装包:
```shell
cd /home/deploy/chainmaker
tar -zxvf chainmaker-v2.3.10-wx-org1.chainmaker.org-*.tar.gz
```
解压得到的 `chainmaker-v2.3.10-wx-org1.chainmaker.org/` 目录结构大致如下:
```
chainmaker-v2.3.10-wx-org1.chainmaker.org/
├── bin/ # start.sh / stop.sh / cgroup_setup.sh / cgroup_prepare.sh 等
├── config/
├── data/
├── lib/
└── log/
```
进入 `bin/` 目录执行启动单个节点:
```shell
cd /home/deploy/chainmaker/chainmaker-v2.3.10-wx-org1.chainmaker.org/bin
./start.sh -f full
```
参数说明:
- `full`:同时启动 chainmaker 节点进程和 `VM-GO-*` 合约引擎容器;
- `-f`:如果对应容器已存在(停止状态),强制删除后重建;
- `alone`:仅启动 chainmaker 节点,不拉起合约引擎(适用于合约引擎独立部署场景)。
验证启动结果:
- 查看节点进程:
```shell
ps -ef | grep chainmaker | grep -v grep
# 预期能看到 ./chainmaker start -c ../config/wx-org1.chainmaker.org/chainmaker.yml
```
- 查看合约引擎容器:
```shell
docker ps | grep VM-GO
# 预期能看到 VM-GO-wx-org1.chainmaker.org-name-<时间戳> 处于 Up 状态
# 例如:VM-GO-wx-org1.chainmaker.org-name-20241016154829
```
- 查看节点与合约引擎是否建立通信:
```shell
less /home/deploy/chainmaker/chainmaker-v2.3.10-wx-org1.chainmaker.org/log/system.log | grep -i "all connection established"
```
按上述流程依次启动剩余节点。
---
## 停止
### 方式一:停止整个集群
```shell
cd chainmaker-go/scripts
./cluster_quick_stop.sh # 仅停止节点和合约引擎容器,保留 data / log
./cluster_quick_stop.sh clean # 停止后额外清理各节点的 data 目录和 log 目录
```
### 方式二:停止单个节点
```shell
cd /home/deploy/chainmaker/chainmaker-v2.3.10-wx-org1.chainmaker.org/bin
./stop.sh full
```
`stop.sh` 会同时停止 chainmaker 节点进程和该节点关联的 `VM-GO-*` 容器。
参数说明:
- `full`:同时停止 chainmaker 节点进程和 `VM-GO-*` 合约引擎容器;
- `alone`:仅停止 chainmaker 节点进程,不停止合约引擎容器;
- 不带参数:交互式询问是否同时停止合约引擎容器(默认询问)。
---
## 开启 cgroup 的启动流程
cgroup 功能默认关闭。如需对合约进程的 CPU / 内存使用进行资源隔离和限制,可按本节步骤手动开启。
开启 cgroup 后,合约引擎需要写宿主机 `/sys/fs/cgroup/` 目录对合约进程做 CPU / 内存限制,因此需要先由 root 一次性执行 `cgroup_prepare.sh` 完成预配置(创建子目录并 chown 给部署用户),之后 `deploy` 用户运行 `start.sh` 时会自动完成 cgroup 挂载。
### 开启 cgroup 的配置修改
打开 `config/wx-org1.chainmaker.org/chainmaker.yml`,将合约引擎 cgroup 配置修改为:
```yaml
vm:
common:
contract_engine:
cgroup:
# 开启 cgroup
disable: false
# 单个合约进程最大内存(MiB),-1 表示不限制
max_mem_size_per_process: 512
# 单个合约进程最大 CPU 百分比(相对于全部核心的总和),-1 表示不限制
# 例如 8 核机器上填 0.5,表示单个合约进程最多可用 4 核算力
max_cpu_percent_per_process: 0.5
# 设备白名单 / 黑名单(可选,格式参考 Linux devices cgroup 语法)
devices_allow: ""
devices_deny: ""
```
配置项说明:
| 字段 | 含义 |
| --- | --- |
| `disable` | 是否禁用 cgroup。`false` 表示启用 |
| `max_mem_size_per_process` | 单个合约进程内存上限(MiB)。`-1` 表示不限制 |
| `max_cpu_percent_per_process` | 单个合约进程 CPU 使用上限(占宿主机 CPU 总量的百分比,取值 0~1)。`-1` 表示不限制 |
| `devices_allow` / `devices_deny` | 设备访问白 / 黑名单,按 Linux devices cgroup 语法书写 |
**⚠️ `max_mem_size_per_process` 建议不低于 32MiB**,否则合约进程可能在启动阶段即被内核 OOM Kill。
### 由管理员执行一次 cgroup 预配置
在**首次部署前**(或者宿主机重启后 cgroup 状态被清空时),由具备 root 权限的管理员执行:
```shell
# 以 root 或 sudo 身份执行
cd /home/deploy/chainmaker/chainmaker-v2.3.10-wx-org1.chainmaker.org/bin
sudo bash cgroup_prepare.sh deploy VM-GO-wx-org1.chainmaker.org-name-<时间戳>
```
参数说明:
- 第 1 个参数 `deploy`:部署用户名,与 `usermod -aG docker` 时使用的用户一致;
- 第 2 个及后续参数:一个或多个**容器名**,与 `start.sh` 中最终使用的容器名保持一致。容器名的格式为 `VM-GO-{org_id}-name-{时间戳}`,例如 `VM-GO-wx-org1.chainmaker.org-name-20241016154829`。
**同一台机器上部署多个节点时可一次性传入所有容器名**,示例:
```shell
sudo bash cgroup_prepare.sh deploy \
VM-GO-wx-org1.chainmaker.org-name-20241016154829 \
VM-GO-wx-org2.chainmaker.org-name-20241016154829 \
VM-GO-wx-org3.chainmaker.org-name-20241016154829 \
VM-GO-wx-org4.chainmaker.org-name-20241016154829
```
脚本会自动检测 cgroup 版本并完成对应子目录的创建与 `chown`。执行成功后能看到形如 `✅ cgroup 预配置完成(共 4 个容器)` 的提示。
### 以非 root 用户启动
预配置完成后,`deploy` 用户即可直接启动。此方式会自动处理所有节点的启动,无需逐个操作。提供两种启动方式:
#### 方式一:使用集群管理脚本启动(推荐)
```shell
cd chainmaker-go/scripts
./cluster_quick_start.sh normal
```
#### 方式二:手动启动
```shell
cd /home/deploy/chainmaker/chainmaker-v2.3.10-wx-org1.chainmaker.org/bin
./start.sh -f full
```
参数说明:
- `full`:同时启动 chainmaker 节点进程和 `VM-GO-*` 合约引擎容器;
- `-f`:如果对应容器已存在(停止状态),强制删除后重建;
- `alone`:仅启动 chainmaker 节点,不拉起合约引擎(适用于合约引擎独立部署场景)。
### 验证 cgroup 是否生效
启动后,可以通过如下步骤验证 cgroup 已经生效:
- 检查子目录是否属于 `deploy` 用户:
```shell
# cgroup v2
ls -l /sys/fs/cgroup/chainmaker-vm-VM-GO-wx-org1.chainmaker.org-name-20241016154829
# cgroup v1
ls -l /sys/fs/cgroup/memory/chainmaker-vm-VM-GO-wx-org1.chainmaker.org-name-20241016154829
ls -l /sys/fs/cgroup/cpu/chainmaker-vm-VM-GO-wx-org1.chainmaker.org-name-20241016154829
```
- 触发一次合约调用后,可在上述目录下看到以合约进程 PID 命名的子目录(内含 `memory.limit_in_bytes` 等文件),其值应与 `chainmaker.yml` 中配置一致。
- 若合约在运行时主动申请超过 `max_mem_size_per_process` 的内存,可通过内核日志确认 OOM Kill 已经触发:
```shell
sudo dmesg -T | grep -E 'killed as a result of limit of|memory: usage' | tail -20
```
---
## 常见问题
### permission denied while trying to connect to the Docker daemon socket
现象:
```
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
```
原因:`deploy` 用户尚未加入 `docker` 用户组。
处理:
```shell
sudo usermod -aG docker deploy
# 退出当前会话并重新登录
```
### mkdir: cannot create directory '/sys/fs/cgroup/chainmaker-vm-...': Permission denied
现象:`start.sh` 输出 `ERROR: failed to create cgroup v2 directory: /sys/fs/cgroup/chainmaker-vm-...`。
原因:以非 root 用户启动,但**未事先执行 `cgroup_prepare.sh` 完成预配置**。
处理:由管理员执行一次 [由管理员执行一次 cgroup 预配置](#由管理员执行一次-cgroup-预配置) 的预配置脚本。
### 宿主机重启后 cgroup 目录消失
`/sys/fs/cgroup/` 下的子目录属于内核 cgroupfs,其状态**不会跨重启持久化**。宿主机重启后,请重新执行一次 `cgroup_prepare.sh` 恢复预配置:
```shell
sudo bash cgroup_prepare.sh deploy VM-GO-wx-org1.chainmaker.org-name-<时间戳> ...
```
如需开机自动预配置,可将该脚本写入宿主机的 systemd unit 或 rc.local 中。
### 合约调用总是"运行慢"或偶发失败,`dmesg` 中出现 `Task in /docker/... killed as a result of limit of ...`
现象:cgroup 内存限制配置过低,合约进程在 Go runtime 初始化阶段就被 OOM Kill。
处理:将 `chainmaker.yml` 中 `max_mem_size_per_process` 上调到合理值(**建议不低于 32MiB**,详见 [开启 cgroup 的配置修改](#开启-cgroup-的配置修改))后,通过 `stop.sh + start.sh` 重启节点即可生效。
### 清理 data/ 目录时权限不足
`data/contract-bins/` 中的文件属主为容器内 root,普通部署用户执行 `rm -rf data` 或 `./cluster_quick_stop.sh clean` 时会报 `Permission denied`。使用 sudo 清理即可:
```shell
# 单节点
sudo rm -rf data log/*
# 集群(chainmaker-go/scripts 目录下)
sudo rm -rf ../build/release/*/data ../build/release/*/log/*
```
### cgroup v2 环境下执行 cgroup_prepare.sh 报错
现象:脚本报错 `无法写入 /sys/fs/cgroup/cgroup.subtree_control`。
原因:向 `/sys/fs/cgroup/cgroup.subtree_control` 写入需要保证宿主机根 cgroup 下没有运行进程。一般是宿主机上尚未通过 systemd 迁移根 cgroup 中的进程。
处理:可先重启宿主机或参考发行版文档处理后再执行本脚本。
### 端口冲突或防火墙拦截
启用 Docker 合约引擎后,节点与合约引擎之间通过两个 TCP 端口通信(`tcp` 协议下):
- `contract_engine.port`(默认 `22351`):**由 `VM-GO-*` 容器监听**,节点主进程作为客户端连入,用于"节点 → 合约引擎"的合约调用请求。该端口需要通过 `docker run -p` 暴露到宿主机;
- `runtime_server.port`(默认 `32351`):**由节点主进程监听在宿主机上**,合约引擎沙箱作为客户端连回,用于"合约引擎沙箱 → 节点"的状态读写等回调。该端口不属于容器,但容器必须能访问到宿主机的这个端口(`prepare.sh` 生成配置时已默认使用 `host.docker.internal` 或宿主机 IP)。
多节点同机部署时请为每个节点指定不同的端口(在 `prepare.sh` 的命令行参数中即可传入),并确保防火墙放行相应端口。
---
## 相关链接
- [启动支持Docker_VM的链](../instructions/启动支持Docker_VM的链.md)
- [长安链配置管理](长安链配置管理.md)
- [自拉起服务](../instructions/自拉起服务.md)