Mac使用docker安装宝塔
- 项目背景
-
- 初衷
- 背景知识介绍
- 项目实例
- [一顿操作猛如虎之后,根据系统错误日志,发现了真相: !系统错误日志(https://i-blog.csdnimg.cn/direct/92684e286517401d8c416b1179838df2.png)](#一顿操作猛如虎之后,根据系统错误日志,发现了真相:
) -
-
- [5.2 修复步骤](#5.2 修复步骤)
- [✅重建 pyenv](#✅重建 pyenv)
- 6、根本原因分析(了解)
-
- [6.1. 崩溃现场(已确认)](#6.1. 崩溃现场(已确认))
- [6.2. 根本原因:gevent / greenlet 多版本混乱 + C ABI 严重不兼容](#6.2. 根本原因:gevent / greenlet 多版本混乱 + C ABI 严重不兼容)
- [6.3. 崩溃机制推断](#6.3. 崩溃机制推断)
- 7、番外
-
- [7.1🎯 根因定位](#7.1🎯 根因定位)
- [7.2🔧 修复动作](#7.2🔧 修复动作)
-
- 小结
项目背景
初衷
由于 macOS 上无法直接安装宝塔面板(因为宝塔仅支持 Linux 系统),但可以通过 Docker 容器 技术在 Mac 上运行一个 Linux 环境,并在其中安装宝塔面板。Docker功能的强大给开发人员带来了很大的便利。正巧,最近的开发项目需要搭建宝塔环境,本人通过Docker成功安装了宝塔,现总结下操作经验,避免以后遗忘,也希望能给大家带来帮助。
背景知识介绍
docker安装各类容器的步骤是统一的,首先要拉取镜像;其次是创建容器,常见容器比如nginx、python、mysql、redis、php等,如果我们开发需要不同的版本,创建不同版本的容器即可,是非常方便的;执行docker命令进入容器进行各种操作,比如mysql容器,进入后我们就可以执行mysql命令;最后启动容器,该过程指定与我们的宿主主机对应的端口进行通信。
项目实例
下面以安装宝塔为例,详细介绍下安装步骤及遇到的问题。
1、拉取镜像
拉取Linux镜像,Ubuntu或CentOS镜像都可以。
docker pull ubuntu:22.04
执行命令报错,错误信息如下:
# 报错信息
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 默认的官方镜像仓库 registry-1.docker.io 位于国外,在国内网络环境下访问速度慢或不稳定,导致连接超时。
为 Docker 配置国内镜像加速器,如图所示:

点击保存并重启docker。
bash
# 1、验证配置是否生效
# 打开终端,执行命令:
docker info
# Registry Mirrors部分展示刚添加的镜像地址,配置生效。
# 2、重新拉取镜像
docker pull ubuntu:22.04
# 错误消失,拉取速度显著提升
2、创建并启动容器
# 创建并启动一个名为 bt-panel 的 Ubuntu 容器
# --privileged=true: 赋予特权,以便宝塔管理服务
# -p: 端口映射,格式为 宿主机端口:容器端口
# -v: 目录挂载,将本地文件夹映射到容器内,方便管理网站文件
docker run -itd --name bt-panel \
--privileged=true \
-p 8888:8888 \
-p 80:80 \
-p 443:443 \
-p 3306:3306 \
-p 22:22 \
-v ~/www:/www/wwwroot \
ubuntu:22.04 \
/bin/bash
注:~/www 是你 Mac 本地的文件夹,你可以改为其他路径。
如果 8888 端口被占用,可改为 -p 8889:8888,访问时也需对应修改端口。
3、进入容器并安装宝塔面板
# 1、执行docker命令进入容器
docker exec -it bt-panel /bin/bash
# 2、容器内操作以下命令,更新软件源并安装必要工具
apt update
apt install -y wget curl sudo
#3、下载并运行宝塔安装脚本
# 使用宝塔官方最新安装脚本
wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec
注:安装过程中会提示选择地区(输入 y 或对应数字)和时区(选择 Asia/Shanghai)。
等待安装完成,屏幕最后会显示 外网面板地址、username 和 password,请务必截图或复制保存。

4、访问宝塔面板
# 在浏览器中输入:
http://127.0.0.1:8888/XXXX
对应本人安装的宝塔面板地址:http://127.0.0.1:8888/eedebdfd
5、兼容问题修复
5.1问题排查过程
此时遇到了大问题,一直访问不成功,于是进行排查。
# 1、通过宝塔命令查看状态
bt status
# 2、重新启动宝塔面板
bt restart
# 3、查看面板错误日志
tail -n 50 /www/server/panel/logs/error.log
# 4、查看系统日志
dmesg | tail -n 20
一顿操作猛如虎之后,根据系统错误日志,发现了真相:

5.2 修复步骤
✅重建 pyenv
bash
# 在 bt-panel 容器内执行
# 1. 先备份
tar zcf /root/panel_pyenv_backup_$(date +%F).tar.gz /www/server/panel/pyenv
# 2. 卸载所有冲突包 + 重装正确版本
/www/server/panel/pyenv/bin/pip3 uninstall -y gevent gevent-websocket greenlet
# 删干净所有重复 dist-info 和孤立 .so
rm -rf /www/server/panel/pyenv/lib/python3.7/site-packages/gevent-20.12.1.dist-info \
/www/server/panel/pyenv/lib/python3.7/site-packages/greenlet-0.4.17.dist-info \
/www/server/panel/pyenv/lib/python3.7/site-packages/greenlet.cpython-37m-x86_64-linux-gnu.so
# 3. 安装匹配 gevent 22 + greenlet 2.x 组合 (greenlet <3.0)
/www/server/panel/pyenv/bin/pip3 install --no-cache-dir \
"gevent==22.10.2" \
"gevent-websocket==0.10.1" \
"greenlet>=2.0,<3.0"
# 4. 清理所有 __pycache__
find /www/server/panel -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null; true
# 5. 重启面板(关键!)
bt restart
宝塔面板可以正常访问,如下图所示:

6、根本原因分析(了解)
6.1. 崩溃现场(已确认)
python3[43824]: segfault at a9 ip 000055abe47ee078 sp 00007ffc95be8338 error 4 in python3.7
-
故障地址
0xa9:典型的 NULL + 0xa9 偏移空指针解引用 -
错误码 4:用户态读失败(page not present, user read)
-
崩溃点机器码解码 :
asmmov rax, [rdi+0x8] ; 从第一个参数(某个结构体)偏移+8取出指针 test BYTE [rax+0xa9], 0x40 ; <-- 崩溃! rax == NULL即:结构体
rdi的+0x8位置本该有一个子指针,但它是NULL,随后访问NULL+0xa9触发段错误。这是C 扩展(greenlet/gevent)上下文切换时破坏 PyThreadState 链表指针的典型特征。
6.2. 根本原因:gevent / greenlet 多版本混乱 + C ABI 严重不兼容
site-packages 目录下同时存在两个不同版本的 gevent 和两个不同版本的 greenlet(4 份 dist-info!):
| 包 | 版本 A(旧) | 版本 B(新,运行时生效) | 版本要求/兼容性 |
|---|---|---|---|
| gevent | 20.12.1 dist-info 残留 |
22.10.2 实际运行 | gevent 20.12.1 要求:greenlet >= 0.4.17, < 2.0 |
| greenlet | 0.4.17 dist-info + 孤立 .so(109KB) |
3.0.3 实际运行(1.4MB .so) | greenlet 3.x 的 C 结构体/API 相对 0.4.x 完全重写 |
| Python | --- | 3.7.9 (2021年旧版) | greenlet 3.x 虽宣称兼容 3.7,但与旧版 gevent 20.x 编译出来的 C 扩展 .so (_gevent_cgreenlet.cpython-37m.so 等)ABI 完全不匹配 |
此外还有一个孤立残留文件:
/www/server/panel/pyenv/lib/python3.7/site-packages/greenlet.cpython-37m-x86_64-linux-gnu.so
(109KB, 2021年1月, 对应 greenlet 0.4.17 旧版)
6.3. 崩溃机制推断
- 某次宝塔面板的升级/补丁过程未彻底清理旧版 ,导致
gevent-20.12.1.dist-info+greenlet-0.4.17旧 C 扩展残留与新版 gevent 22 + greenlet 3 共存。 - 运行时 gevent 的旧版 C 扩展(
_gevent_cgreenlet.so、_gevent_cevent.so等)调用 greenlet 3.x 的上下文切换函数。 - 由于两版本
PyGreenlet/PyThreadState相关结构体字段大小、偏移、链表指针布局完全不同,切换过程中把PyThreadState->next(结构体偏移 +0x8,对应崩溃代码mov rax,[rdi+0x8])写成了 NULL。 - 下次 Python 解释器进入主循环检查 tracing flag 时(
[rax+0xa9], 0x40),因 rax=NULL 触发segfault at a9。 - 这解释了:为何我们单独跑 greenlet/gevent 测试能通过(压力低、没走到触发旧 C 扩展的代码路径);但在 BT-Panel 高并发实际请求(软件列表更新、任务执行等)下就有几率命中。
7、番外
本人用CentOS镜像源重新安装了宝塔,登录宝塔面板后,多次安装nginx失败。
bash
# 错误日志
宝塔安装nginx失败:Reinstall 1 Package Total download size: 547 k Installed size: 2.0 M Downloading packages: Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : wget-1.14-18.el7_6.1.x86_64 1/1 install-info: No such file or directory for /usr/share/info/wget.info.gz Verifying : wget-1.14-18.el7_6.1.x86_64 1/1 Installed: wget.x86_64 0:1.14-18.el7_6.1 Complete! 检测到系统组件wget不存在,无法继续安装
7.1🎯 根因定位
wget 其实一直都装好了 ,yum 日志里 Installed: wget.x86_64 Complete! 也是千真万确的。真正导致报错 检测到系统组件wget不存在 的原因是:
| 根因链 | 证据 |
|---|---|
① Docker 精简 CentOS 7 镜像里 which 命令这个 rpm 包根本没装 |
rpm -q which → package which is not installed;type which → not found(诊断脚本 T1 T6) |
② 宝塔 /www/server/panel/install/nginx.sh:235-244 使用 which ${RUN_BIN} 判断组件是否存在 |
SYS_BIN=(wget tar xz unzip) → 依次走 which wget(nginx.sh 第 232~244 行) |
③ 因为 which 自身不存在,which wget returncode 永远是 127(不是 wget 不存在,是 which 这个命令自己不存在) |
RED 重放检测代码:which wget => exit=127,然后重新 yum install -y wget 后再次 which wget 仍然 127 → 走到 echo 检测到系统组件wget不存在(RED 完整复现) |
④ 次要:rpm -ql wget 声明应有 /usr/share/info/wget.info.gz,实际该文件缺失(安装时警告 install-info: No such file or directory for /usr/share/info/wget.info.gz)→ 导致 rpm -V wget exit 1,部分脚本会把它当作"安装损坏" |
诊断 T2 T7:/usr/share/info/ 目录里只有 dir,wget.info.gz 确实丢了 |
💡 附带影响:tree / tar / xz / unzip 也同样被 which 误判为"不存在" ------只要容器里没装
which包,宝塔任何软件安装(PHP/MySQL/Redis......)都会走同样的失败流程。修复which包是一次性解决全部这类误报。
7.2🔧 修复动作
bash
# 1) 核心修复:安装缺失的 which 包(和宝塔同步安装 tree)
yum install -y which tree
# → which-2.20-7.el7 / tree-1.6.0-10.el7 成功安装
# → 此刻 which wget exit=0 ✅, which tar/xz/unzip/tree 全部 exit=0 ✅
# 2) 补 wget rpm 声明应有但实际缺失的 wget.info.gz(消除 rpm -V 非零退出)
gzip -9c /dev/null > /usr/share/info/wget.info.gz; chmod 644 $_
/sbin/install-info /usr/share/info/wget.info.gz /usr/share/info/dir || :
# → rpm -V wget 只剩 S.5....T. c /etc/wgetrc(仅配置改动,正常)✅
# 3) 重新调用 nginx 安装脚本,后台编译安装
nohup bash -x /www/server/panel/install/nginx.sh install 1.26 > install.log 2>&1 &
# → 这次不再在 wget 检测处退出,顺利走完:
# install wget/tree ✔ → jemalloc 编译 ✔ → nginx 1.26.1 源码编译 ✔
# → init.d 落盘 + chkconfig on ✔ → /etc/init.d/nginx start Starting nginx... done ✔
# → 基础网站流量统计程序安装完成 ✔
小结
Docker容器解决了多个不同项目同时开发要求的差异性开发环境问题,愿大家都能被温柔以待,少走一些弯路。共勉~