ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

apt-get 补的库,一次重建全没了

apt-get 补的库,一次重建全没了 我把一台 PHP 容器的php -m调到零告警四个扩展全都能加载。后来在面板里改了一次环境设置四个扩展全挂回去了。补的东西没进镜像。它一直在容器的可写层里。这台是 1Panel v2 起的环境PHP 8.4.25镜像1panel-php-fpm:8.4.25基础镜像 Debian 13trixie服务器 4 核 8G。事情出在 9 月 16 号和 17 号这两天下面的回显数值都原样保留。容器名我在下面统一用变量你在 1Panel 里起的名字是什么就填什么。# 换成你在 1Panel 里给 PHP 运行环境起的名字 容器名exportPHP_CTNphp84同样是往容器里动手结果完全不一样。docker restart $PHP_CTN只重启进程容器文件系统原封不动你apt-get补的库和命令都还在不用管它。容器重建就全丢了。docker compose up、在 1Panel 里改环境设置、升级镜像这三种都算重建。docker restart容器重建镜像层只读来自 1panel-php-fpm:8.4.25容器可写层你 apt-get 补的库都写在这里执行了什么操作容器文件系统保留补装项都还在可写层被丢弃补装项全部消失这条我当时判断错过一次。我以为unzip不依赖任何库重建之后应该还在。错了。unzip同样是apt-get装的跟那些库一样活在容器的可写层里。决定去留的是你执行了哪种操作跟这个包有没有依赖链没关系。两层文件系统Docker 容器 镜像层只读 容器可写层。镜像层的内容来自docker pull拉下来的镜像所有容器共享容器内的任何操作都改不动它。你在容器里做的写操作apt-get install、改文件、建文件全都落在可写层。这一层是这个容器实例私有的。docker restart只是把容器进程重启一遍可写层不动补装项就还在。重建的本质是销毁旧容器实例、用同一个镜像重新建一个新实例。新实例的可写层是全新的空白层旧的连带里面所有补装内容一起被丢弃。你补的东西从头到尾就没进过镜像只在某一代容器的可写层里待着。第一次补库时我是照着报错信息一个个试包名的。重放的时候换了个思路不记当时敲了哪条命令记怎么重新推出这条命令。包名会变后面有真实案例。存命令迟早过期存方法不会。第一步是问 PHP 自己扩展目录在哪。硬编码路径是个坑目录名里带着 PHP 的 API 版本号/usr/local/lib/php/extensions/no-debug-non-zts-20240924/ ^^^^^^^^^^ 换 PHP 版本就变换个小版本20240924这段就变了。# 本机验证这条能直接拿到扩展目录dockerexec-i$PHP_CTNphp-recho ini_get(extension_dir), \n;然后把遍历扩展目录、逐个ldd、只留not found打包成一条# 遍历所有扩展 .so找出依赖缺失的那些dockerexec-i$PHP_CTNbash-c EXT_DIR$(php -r echo ini_get(\extension_dir\);) for so in $EXT_DIR/*.so; do [ -e $so ] || continue miss$(ldd $so 2/dev/null | grep not found) if [ -n $miss ]; then echo --- $(basename $so) --- echo $miss fi done三个细节都是踩过才知道的。[ -e $so ] || continue通配符一个都没匹配到的时候$so会是字面量*.so这句把它挡掉。2/dev/null有些.so不是动态库ldd会往 stderr 刷not a dynamic executable。ldd只回答缺不缺不回答该不该装。缺库的扩展不代表你都需要补之前先确认哪些扩展是项目真在用的。这套遍历加过滤的脚本逻辑我在同类环境上用替身函数做过 dry-run把ldd换成模拟输出遍历、continue保护、basename、计数全部符合预期。稿子里标「本机验证」的就是这一类它验的是脚本逻辑不是我那台机器上的结果。补库之前这条命令当时还是硬编码路径的版本跑出来的是--- gd.so --- libpng16.so.16 not found libavif.so.16 not found libwebp.so.7 not found libjpeg.so.62 not found libXpm.so.4 not found libfreetype.so.6 not found --- intl.so --- libicuio.so.76 not found libicui18n.so.76 not found libicuuc.so.76 not found --- zip.so --- libzip.so.5 not found --- memcached.so --- libmemcached.so.11 not found四个扩展一共缺 11 个共享库9 月 16 号实测。补完再跑这条复验# 复验有告警就有输出空输出 全部加载成功dockerexec-i$PHP_CTNphp-m21|grep-iUnable to load9 月 17 号实测空输出零告警。重建之后怎么重放按这个顺序走一遍# ① 先看丢没丢 —— 有输出说明扩展又挂了dockerexec-i$PHP_CTNphp-m21|grep-iUnable to load# ② 反查现在缺哪些库# ③ 补库dockerexec-i$PHP_CTNbash-lc apt-get update -qq apt-get install -y --no-install-recommends unzip # 包名逐个试Debian 版本过渡期同名包会有多个候选 for p in libzip5 libzip4; do apt-get install -y --no-install-recommends $p 2/dev/null { echo libzip via $p OK; break; } done apt-get install -y --no-install-recommends libjpeg62-turbo libwebp7 libfreetype6 libxpm4 libavif16 for p in libpng16-16t64 libpng16-16; do apt-get install -y --no-install-recommends $p 2/dev/null { echo libpng via $p OK; break; } done apt-get install -y --no-install-recommends libicu76 # memcached 的库同样是 t64 过渡改名包名别按规律硬推 for p in libmemcached11t64 libmemcached11; do apt-get install -y --no-install-recommends $p 2/dev/null { echo libmemcached via $p OK; break; } done dockerrestart$PHP_CTN# ④ 复验dockerexec-i$PHP_CTNphp-m21|grep-iUnable to load有一件事得说清楚。我在容器里补库的时候没把实际执行的命令存档事后想重放才发现包名记不全了。上面这段是我事后重建的自适配版本用for循环逐个试候选包名容忍包名不确定。它不等于当初那条。更打脸的是这个自适配版本第一次写出来时自己就漏了一个扩展四个扩展里memcached那条没写进去后来拿实际回执逐项核对才发现。漏一项的后果跟全没装一样那个扩展照样Unable to load。只是告警从 4 条变成 1 条很容易被当成差不多好了。把它验收干净靠的是遍历所有扩展逐个ldd人眼会漏。所以这一课比「记得存档」更值钱存下来的清单会写错能自动验收的方法不会。顺便说一句unzip这个包名是确定的实测装的是unzip 6.0-29deb13u1没有依赖链是清单里最稳的一条。所以zip扩展躺着的时候unzip命令往往是你唯一剩下的解包通道。Debian 正在做 64 位 time_t 过渡t64一批包改了名。libpng16.so.16这个库包名从libpng16-16变成了libpng16-16t64。你的补库命令要是写死了旧名在新系统上就是E: Unable to locate package。同一个坑我踩了两次。libmemcached.so.11对应的包我按 Debian 的命名规律libnameso 主版本推成了libmemcached11到 Debian 13 上一看实际叫libmemcached11t64又是 t64 改名。按规律推断包名一样会错。所以上面那段用了for p in libpng16-16t64 libpng16-16逐试的写法。同样的坑还有 ICU 库libicu76里的76是 ICU 版本号跟着系统走。重放清单得有人存、有人找、有人执行。它天然适合放进项目仓库比如scripts/php-ext-replay.sh前提是你真的写了、下次真的想起来。重建之后有一段空窗期从重建完成到你想起去重放中间这段时间服务是带病运行的。扩展没加载相关功能直接报错。重建要是发生在你改完别的配置顺手做了个compose up的时候这个窗口可能一直持续到你下次发现问题。上面三条坑的共同原因只有一个补装没进镜像。那就让它进镜像基于 1Panel 的 PHP 镜像构建自己的镜像# Dockerfile —— 基于 1Panel 的 PHP 运行环境镜像把补装固化进镜像层 FROM 1panel-php-fpm:8.4.25 # Debian 13trixie源已就绪一次性补共享库 unzip # 注意libpng / libmemcached 在 t64 过渡期都有两个候选包名各用一个循环逐个试 # —— 两个循环不能合并合并后前者一命中就 break后者会被整个漏掉 RUN set -eux; \ apt-get update -qq; \ apt-get install -y --no-install-recommends \ unzip \ libzip5 \ libjpeg62-turbo libwebp7 libfreetype6 libxpm4 libavif16 \ libicu76; \ for p in libpng16-16t64 libpng16-16; do \ apt-get install -y --no-install-recommends $p break; \ done; \ for p in libmemcached11t64 libmemcached11; do \ apt-get install -y --no-install-recommends $p break; \ done; \ apt-get clean; \ rm -rf /var/lib/apt/lists/*写这个 Dockerfile 时踩到一个坑注释不能写在续行RUN的中间。Docker 解析\续行时会把注释行按普通内容并进来整条指令被从注释处截断后半段的for循环和apt-get clean全丢了。所以上面把所有注释都挪到了RUN之前。这类语法错误在构建时不一定报错只是少装了几个包。建议用 hadolint 之类的工具过一遍。构建、打标、在 1Panel 里把这个镜像指过去。此后再怎么重建补装项都随镜像一起回来。我这次为什么没走这条路两个现实原因。一是当时的目标是尽快让环境可用。补库当场就解决了阻塞自建镜像要额外处理构建、打标、以及 1Panel 的运行环境关联。二是会脱离 1Panel 的自动管理。PHP 运行环境是面板托管的换成自建镜像后面板的升级运行环境这类操作行为就不再可预期升级路径得你自己维护。怎么取舍你只是这次把环境搭起来重放清单够了你要长期运维、还会反复重建构建自定义镜像是明显更划算的一步。
返回列表