很多搞容器化的朋友应该都遇到过这种需求:手头有一个Kylin系统的ISO安装包,想把它直接变成一个Docker镜像,而不是先去装一台虚拟机、再慢慢往里灌软件做快照。尤其是做国产化适配、离线交付、或者要复刻一套和生产环境完全一致的容器运行环境时,这种“从ISO直接构建Docker镜像”的路子非常实用。
我最早接触这个场景是在一个适配项目里,某公司要求所有中间件和业务应用必须跑在Kylin容器中,但官方仓库里又找不到现成的Kylin基础镜像,网络环境还受限,不能随意从公共仓库拉取。当时翻了不少资料,试过用虚拟机安装再打包,也试过用debootstrap的思路处理rpm系的系统,最后踩出了一条比较顺的路径:把Kylin ISO里的根文件系统完整解出来,再用docker import导入成本地镜像。
这篇文章我把这套方法完整复述一遍,基于Docker 20.10.7环境,结合Kylin ISO的目录结构特点,从原理拆解到实操命令再到排错技巧,尽量每个细节都讲透。
1. 为什么要废这个劲:从ISO构建镜像的核心思路与方案选型
1.1 这条路解决的是什么问题
很多人第一反应是:直接docker pull一个Kylin镜像不就行了?但实际场景里往往没那么简单。
- 内网环境根本没有公共仓库镜像源,离线环境连不了外网。
- 某些定制版Kylin系统,官方压根没有发布对应的Docker镜像,只有ISO安装包。
- 业务方要求“容器环境要和物理机/虚拟机里的系统完全一致”,包括内核版本以外的所有文件、库、工具链、配置风格。
这时候,从ISO构建镜像就成了一个非常可靠的兜底方案。整个过程不需要联网,只要有一份ISO文件,就能生成一个自带完整根文件系统的Docker镜像。
1.2 安装介质里藏着什么秘密
理解Kylin ISO的目录结构,是解决“能不能直接转成镜像”的关键。Kylin系统的ISO,本质上是引导安装程序的启动盘。安装程序运行时,真正的“系统本体”往往以压缩包或只读文件系统的形式隐藏在特定目录中。
常见的情况是:
/LiveOS/目录下存在一个巨大的squashfs.img,里面打包了Live系统完整的根文件系统。- 或者
/root/、/images/等目录下存在installer.img之类的安装镜像文件。
换句话说,ISO不是直接用,而是要去里面把“已经装好的系统的根目录”挖出来。这也是整个方案的核心突破口:用squashfs镜像解压出来的内容,拿到Docker里重新打包。
1.3 对比其他几种方案,为什么选import
我在项目里实际对比过三种方案,各有优劣,但最终都殊途同归。
| 方案 | 思路 | 优点 | 缺点 |
|---|---|---|---|
| 虚拟机装系统再快照打包 | 先在虚拟机里装好Kylin,装完以后把整个根目录tar下来,再docker import | 环境最接近真实机器,可自定义预装软件 | 耗时极长,虚拟磁盘占空间大,操作繁琐,还得处理虚拟机工具残留 |
| 用debootstrap类似工具直接构建根目录 | 针对Debian系很成熟,但Kylin是RPM体系,工具链不匹配 | 能生成最小化根文件系统 | 对RPM系的支持弱,依赖解析容易出问题,手动处理很麻烦 |
| 从ISO中解包根文件系统再导入 | 直接取官方ISO里的现成rootfs,再docker import | 速度快、无操作系统安装过程、完全复刻系统文件 | 需要理解ISO结构,解包后要手动做容器化适配 |
选择第三种方案后,整体效率提升非常明显,一条命令就能搞定根文件系统提取,后续全是修修补补的细活。这篇讲的就是这条路。
2. 准备工作:环境、工具与前置检查一个都不能少
2.1 宿主机环境要求
我的宿主机环境是较常见的Linux服务器,Docker版本正好是20.10.7,通过内网离线安装。这里必须提醒一下,Docker 20.10.7虽然比较“老”,但处理docker import这种基础指令完全没有问题,甚至可以说兼容性很好。
宿主机本身建议也使用同体系的Linux系统,因为后续挂载ISO、处理squashfs需要内核支持相关文件系统。不过如果宿主机是别的Linux发行版,只要装了squashfs-tools和tar,也能正常完成解包操作。
关键检查项:
- Docker服务状态正常:
systemctl status docker确认是running。 - 宿主机有root权限,因为挂载ISO和解压都会涉及系统级操作。
- 磁盘空间充足。这一点别忽略,一个几GB的ISO解压后根文件系统可能膨胀到十几GB,再加上后续tar包、docker镜像,至少预留20GB以上空间比较稳妥。
2.2 需要安装的工具
整个流程依赖的工具其实不多,但缺少哪一个都会卡壳。
# 检查是否已安装squashfs-tools rpm -qa | grep squashfs # 未安装时用包管理器安装 yum install -y squashfs-toolssquashfs-tools最主要提供unsquashfs命令,用来解包squashfs.img。这里我建议大家提前确认一下版本,太老的squashfs-tools对高版本压缩算法支持不好,解包时会报Unsupported option之类的错误。
另外还需要tar、mount、losetup这些基础命令,这些绝大多数系统都自带,不需要额外处理。
2.3 把ISO文件拷到目标服务器
生产环境里,ISO文件常常存放在文件服务器或者Windows跳板机上,需要先传到Linux服务器。这个过程建议用scp或内网共享挂载,操作前先确保传输完整性,用md5sum或sha256sum做一下校验。
md5sum Kylin-Desktop-V10-SP1-2024.iso校验这一步绝对不要偷懒,我见过有人ISO没传完就急着挂载,结果squashfs.img解压到一半直接报CRC错误,既浪费时间又容易怀疑人生。
2.4 明确Docker 20.10.7的适配性
为什么标题里特意强调Docker 20.10.7?因为这个版本处于一个比较微妙的时间点:它支持较新的OCI标准,同时又不强制要求镜像必须包含某些特殊元数据。
构建Kylin镜像时,我们用的是docker import,它本质上不执行RUN、CMD这些指令,只是把tar包直接转换成镜像。这意味着即使根文件系统里有某些不标准的东西,Docker也不会在构建阶段卡住,兼容性上限完全由运行时决定。
提示:如果你用的是更新版本的Docker,比如24.x甚至25.x,这套流程完全通用,
docker import的核心行为这么多年基本没变过。
3. 核心实操全流程:从ISO到Docker镜像一步步来
3.1 第一步:挂载ISO并查看目录结构
先把ISO挂载到指定目录,没有目录就自己创建。
mkdir -p /mnt/iso mount -o loop /root/Kylin-Desktop-V10-SP1-2024.iso /mnt/iso ls /mnt/iso/正常情况下能看到类似这样的目录内容:
boot EFI isolinux LiveOS repodata Packages etc其中重点检查LiveOS目录,因为这里大概率躺着我们想要的东西。
ls /mnt/iso/LiveOS/有些版本会看到squashfs.img,有些版本叫rootfs.img或installer.img。如果是squashfs.img,下一步就是解包;如果是一个ext4格式的img,那就要用mount直接挂载或者用guestfish提取,这两种情况我在第四节会补充说明。
3.2 第二步:解出squashfs.img里的根文件系统
先用unsquashfs把它解到一个工作目录。这一步耗时取决于ISO大小和服务器磁盘性能,几GB的文件一般几分钟内能完成。
mkdir -p /opt/kylin-rootfs unsquashfs -d /opt/kylin-rootfs /mnt/iso/LiveOS/squashfs.img解压完成后检查一下目录内容:
ls /opt/kylin-rootfs正常的Linux根目录骨架就出来了:bin、etc、usr、var、home等目录都在。这就说明提取成功。
这里提醒一个细节:不要直接在解包出来的目录上改东西,先导入Docker再改。原因很简单,解包目录是普通文件夹,没有容器运行时限制,改了以后不一定能完整保留权限和特殊文件属性。
3.3 第三步:用tar打包根文件系统
在/opt/kylin-rootfs的上级目录,执行打包命令。注意别把目录本身路径打进包,需要在根目录内操作:
cd /opt/kylin-rootfs tar --numeric-owner --xattrs -cf /root/kylin-rootfs.tar .参数说明一下:
--numeric-owner:保留数字形式的UID/GID,避免换环境后因用户映射混乱导致权限错乱。--xattrs:保留扩展属性,Kylin系统里很多文件依赖capabilities和selinux属性,丢了会出问题。
打包过程中如果出现“file changed as we read it”的警告,通常是有进程还在写这个目录,最好确认没有其他任务在占用解压目录。
3.4 第四步:docker import生成镜像
打完tar包,后面的操作就清爽了:
docker import /root/kylin-rootfs.tar kylin:v10正常情况下输出一个镜像ID。用docker images查看:
docker images | grep kylin看到有kylin且标签为v10就行。这一步如果报错,常见原因是tar包路径有问题,或者tar包内没有完整的etc/目录。
3.5 第五步:验证镜像能不能启动
导入镜像只是第一步,真正关键的是验证容器能否正常交互:
docker run -it --rm kylin:v10 bash如果能跳进容器的bash提示符,并且执行cat /etc/os-release能看到Kylin系统信息,基本宣告构建成功。然后再检查几个基础命令是否可用:
cat /etc/os-release which yum which dnf df -h到这里为止,一个可用的Kylin基础镜像就建好了。不过先别急着用它跑业务,后面还有几个坑等着踩。
4. 镜像生成后的关键配置与验证
4.1 容器内的时区、语言和DNS设置
直接从ISO解包出来的根文件系统,时区和语言通常保持安装默认值。容器里面如果不设置时区,日志时间戳会跟本地区时间对不上,排查问题时分外痛苦。
运行容器后,先做基础配置:
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime echo "LANG=zh_CN.UTF-8" > /etc/locale.confDNS设置直接改/etc/resolv.conf也行,但Docker默认会覆盖这个文件。正确做法是在构建最终镜像时写入配置,或者在运行容器时通过--dns参数指定。
4.2 建设性的建议:第一次import后不要直接交付
docker import生成的镜像,文件系统是原封不动的rootfs,很多东西不适合直接进生产环境。我习惯的做法是,先以这个镜像为基础,启动一个临时容器,在容器里做完系统初始化后再commit成第二个镜像。
docker run -it --name kylin-init kylin:v10 bash在容器里依次处理:
- 清空临时文件和缓存:
yum clean all,删除/var/cache/下的残余。 - 配置yum源为可用的内网源或保持离线独占。
- 安装业务依赖的基础软件包。
- 创建需要预先存在的用户和目录。
完成后commit:
docker commit kylin-init kylin:v10-base这样交付出去的镜像,比直接import出来的干净得多。
4.3 镜像体积与层的关系
导入的Kylin基础镜像通常体积不小,一个完整rootfs打包导入后很可能超过2GB。这不是异常现象,ISO里包含的库和基础工具非常齐全,代价就是镜像体积大。
解决体积问题可以分两步走:
- 第一步:在容器内清理缓存和临时文件后commit,能瘦掉一些。
- 第二步:用
docker export导出容器快照,再用docker import重新导入,所有中间层都会塌缩成一层,镜像文件会变干净。
4.4 版本信息的反复确认
容器里执行:
cat /etc/kylin-release确认系统版本和宿主机需求一致。不同版本的Kylin对应的包管理器和glibc版本不同,做国产化适配时需要提前确认这些参数,避免开发完拿去现场跑直接报“缺少GLIBC_XX”。
5. 实操中踩过的坑与排查技巧
再顺的流程,实际操作中也免不了踩坑。我把处理过程中整理的高频问题列成速查表,方便对照处理。
| 问题现象 | 可能原因 | 处理方法 |
|---|---|---|
| unsquashfs报错:Unsupported option | squashfs-tools版本太老,不支持新版压缩算法 | 升级squashfs-tools到4.4以上再试 |
| docker import完成后镜像ID无效 | tar包路径包含根目录本身,例如打成/root/前缀 | cd到根文件系统目录内再执行tar . |
| 容器启动后提示Failed to mount cgroup | 容器缺少systemd特权能力,与rootfs本身无关 | 运行时加--privileged或按需挂载cgroup |
| yum源无法使用 | 原系统源指向安装盘路径,容器内不可用 | 注释掉无效源,添加内网或离线源配置 |
| 找不到systemctl命令 | 有些最小化Kylin镜像未安装systemd | 用/usr/sbin/init或直接安装systemd |
| 容器启动白屏/闪退 | 根文件系统解包不完整 | 核对ISO校验值和解包空间;重新解压背景 |
5.1 坑一:systemd作为PID 1的坑
ISO解出的rootfs,服务管理是基于systemd的。但Docker容器里直接用systemd作为1号进程并不可靠,除非做特殊配置。
原因很简单:容器默认不是完整的init系统,systemd启动时需要挂载cgroup等一堆它认为理所当然的资源。最直接的规避方式:容器里不用systemd管服务,应用直接以前台进程方式跑,这是Docker的标准做法。
如果业务非要systemd不可,就得在docker run时加特权模式并手动挂载相关目录:
docker run -d --name test --privileged kylin:v10-base /usr/sbin/init但对大多数微服务和中间件容器,建议避开这个方向,省得自找麻烦。
5.2 坑二:内核模块与硬件相关工具问题
容器共享宿主机内核,rootfs里的内核模块目录实际上是“自欺欺人”。如果业务程序依赖特定内核模块比如ip_vs、overlay等,必须在宿主机上提前加载,容器内操作没有意义。
# 排查容器内某模块是否存在,通常返回空 find /lib/modules/$(uname -r) -name "*.ko"这个坑在一些做网络组件的项目里特别常见,很多人在容器里装软件、modprobe半天,结果发现根本没权限,实际上是方向搞反了。
5.3 坑三:磁盘空间预估不足
解压一个squashfs时,临时目录空间消耗是ISO的2~3倍,tar包是rootfs压缩后的量,docker层再做一次快照。整个链路下来,一个不足4GB的ISO,最终占用的磁盘空间轻松超过10GB。
建议操作前先把df -h看清楚,至少在三个位置有足够空间:
- ISO文件存储目录
- rootfs解压目录
- Docker数据目录(通常是
/var/lib/docker)
5.4 坑四:selinux上下文的丢失
虽然打包命令带了--xattrs,但导入Docker镜像后,容器内外文件系统上下文属性不一定能完整保留。运行容器时如果遇到权限异常,但普通读写权限检查没问题,可以临时加上:
docker run -it --security-opt label=disable kylin:v10-base bashZTE、麒麟这类系统对selinux策略比较敏感,这个参数在排查容器内授权问题时几乎必试。
6. 进阶玩法与实际场景适配
6.1 把custom脚本和配置文件固化成层
手动配置完的容器,通过commit生成镜像,这种方式大家都会。但更推荐把整个初始化过程写成一条自动化脚本,以后每次做完rootfs解包就能一键固化,避免人工操作的遗漏。
大致流程:
- 从ISO解包出rootfs。
- tar导入成base镜像。
- 用Dockerfile基于base镜像,写入所有定制内容。
FROM kylin:v10-base ENV LANG=zh_CN.UTF-8 \ TZ=Asia/Shanghai RUN yum install -y telnet bind-utils && yum clean all ADD ./app /opt/app CMD ["bash"]这种方式比反复commit要可追溯得多,后面换版本、加依赖时改Dockerfile就行。
6.2 离线仓库的适配
离线场景构建镜像时,基础镜像构建完以后,软件包的安装靠内网yum源解决。如果连内网yum都没有,只能在制作rootfs时提前安装好所有软件,做成胖镜像交付。这两条路我都跑过,前者柔性好,后者一次性,业务连续性要求高的建议走前者。
6.3 镜像导出与再分发
构建好的镜像,打包成tar文件分发到其他服务器:
docker save kylin:v10-base | gzip > kylin-v10-base.tar.gz目标服务器上导入:
docker load -i kylin-v10-base.tar.gz这个方法同样适用于跨架构服务器,前提是镜像与宿主机架构匹配,arm的镜像不能在x86的Docker里跑。如果需要同时支持x86和arm,需要分别在对应架构的服务器上从ISO构建镜像,再分别save分发。
6.4 多版本并行维护
实际项目里,经常出现业务系统A基于Kylin V10 SP1验证,业务系统B基于V10 SP2适配的情况。建议镜像tag直接带上ISO版本号,比如kylin:v10-sp1、kylin:v10-sp2,不要统一打latest,否则版本漂移问题会让人很崩溃。
docker tag kylin:v10 kylin:v10-sp1 docker tag kylin:v10 kylin:v10-sp2镜像本身是廉价的,保留多个tag成本极低,带来的版本可追溯价值却很高。
7. 最后再啰嗦一点:实际操作中的几个心得
这套方法运行了很久,最大的感想是“从ISO直接构建镜像”这事,本质上不是技术在卡人,而是对操作系统文件构成的理解在卡人。搞清楚ISO内部结构,知道squashfs里藏着rootfs,后面一切都顺理成章。
实际操作时建议你先在一个不影响业务的目录里从头到尾跑一遍,把ISO结构、解包时间、镜像体积、容器启动效果都摸清楚,再投入到生产交付里。如果解包过程中遇到特殊版本结构,比如不是squashfs而是ext4根的img,用mount -o loop挂载后再打包,效果是一样。
构建完基础镜像,别忘了验证业务软件在容器里的运行状态。很多国产化适配项目里,软件包和底层系统的兼容性本身就存在不确定性,早发现早处理才是这类工作的最优解。