十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

自建CentOS基础镜像:从Dockerfile到私有仓库的完整实践

自建CentOS基础镜像:从Dockerfile到私有仓库的完整实践 1. 为什么我劝你不要直接拉公共镜像自建CentOS基础镜像的动机分析先说个容易被后知后觉的事。很多团队刚开始容器化时基础镜像都是直接从 Docker Hub 拉centos:7或者centos:latest就完事了直到某天线上环境出了个奇怪问题排查到最后发现是镜像里的 glibc 版本和公司内部的编译环境不一致。那一刻你就会明白基础镜像这件事迟早是需要自己动手做的。我自己维护过两套生产环境的 CentOS 基础镜像一套给 Java 服务用一套给 Python/数据分析任务用。从被公共镜像坑过到自己踩坑又填坑说实话自建基础镜像这件事投入产出比非常高特别是在团队规模超过十个人的时候收益会更明显。1.1 公共镜像最大的问题你不知道里面装了什么centos:7这类官方基础镜像不是不能用而是你无法完全掌控里面到底有什么。官方镜像为了保证大多数场景能跑起来会保留很多你根本用不上的包和配置。就拿网络工具来说公共镜像里连ifconfig、telnet、tcpdump这些都是没有的甚至连vim都没装排查问题的时候连看个端口、看个路由都要临时yum install在离线环境或者内网环境里这就非常痛苦。还有更隐蔽的问题官方镜像的构建参数你是看不到的。它的 locale 配置、时区设置、yum 源指向、软件包版本组合都是按官方自己的标准来的不一定符合你们团队的规范。比如国内访问镜像里的默认 yum 源速度很慢或者默认没有装glibc-langpack-zh导致中文乱码这些都是真实的踩坑点。再往深了说基础镜像是整个容器世界的“地基”。这样理解同一个 Dockerfile在 A 同事机器上构建出来的镜像和 CI 服务器上构建出来的镜像如果底层源不一样结果就可能有细微差异这种不确定性在排查线上故障时是最让人头疼的。自建基础镜像本质上是把“地基”的构建标准攥在自己手里。1.2 自建基础镜像的收益和适用场景自己构建 CentOS 基础镜像最直接的三个收益是可控、可复用、可追溯。可控指的是你能决定时区、字符集、yum 源、软件包集合甚至内核参数相关的初始化脚本可复用指的是团队所有成员构建业务镜像时只需要FROM registry.internal/centos-base:7.9这一行公共的配置自动继承可追溯指的是每个基础镜像都有明确的版本、构建时间和变更记录出了问题能快速定位到变更是哪一版引入的。至于适用场景我列几个比较典型的公司内部有统一的运维规范需要固定 CentOS 版本、固定时区为 Asia/Shanghai、统一字符集。业务服务在离线环境或者内网部署提前把需要的工具和依赖打进基础镜像避免部署时现装。团队想建立自己的镜像仓库体系把基础镜像作为 CI/CD 流水线的起点。希望控制基础镜像体积和层数减少业务镜像的构建和拉取时间。如果你只是个人学习 Docker拉公共镜像用一用完全没问题但只要你开始面向团队、面向生产自建基础镜像就是绕不开的一步。2. 构建前的准备工作Docker引擎和镜像源细节在动笔写 Dockerfile 之前先把准备工作做好后面可以省掉很多无谓的麻烦。这里说的准备工作有两块第一是 Docker 运行环境本身第二是镜像源。2.1 安装Docker引擎Linux、Windows、macOS三种情况我默认看到这篇文章的读者已经装了 Docker但为了照顾刚入门的朋友还是把三段最常见的安装路径快速梳理一遍。Linux 环境CentOS/RHEL 系安装 Docker 引擎官方推荐用 yum 仓库安装。先安装依赖包再配置 yum 仓库最后安装并启动服务sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl start docker sudo systemctl enable dockerWindows 和 macOS 用户直接用 Docker Desktop 即可。我特别提醒一句Docker Desktop 在 Windows 上依赖 WSL 2安装前先去 BIOS 里确认虚拟化已经打开不然启动 Docker 引擎时会直接报 virtualization 相关的错误。如果是 Windows 10 较老的版本还需要先更新 WSL 内核否则 Docker Desktop 装完也无法正常启动。装完之后跑一下验证命令docker version docker run --rm hello-worldhello-world能正常打印输出说明 Docker 引擎和镜像拉取链路都正常可以继续往下走。2.2 配置镜像源解决拉取慢和下载失败的问题从 Docker Hub 拉取 centos 镜像在国内网络环境经常会很慢或者超时这个不完全是带宽问题而是默认 registry 节点和本地的连接质量参差不齐。我的做法是在 Docker 引擎层面配置 registry mirror也就是给 Docker Hub 加一个国内加速源。Linux 环境修改/etc/docker/daemon.json{ registry-mirrors: [ https://docker.m.daocloud.io ] }改完重启 Docker 服务sudo systemctl daemon-reload sudo systemctl restart dockerWindows 和 macOS 上的 Docker Desktop 则是在图形界面里设置路径大概在 Settings - Docker Engine 里把同样的 JSON 配置填进去保存重启即可。注意registry mirror 只是加速拉取公开镜像不改变 Docker 的使用方式。如果公司有内部私有的镜像仓库后续配合docker login和 tag 前缀来使用这是后话。这里先不展开。3. Dockerfile路线最推荐的基础镜像构建方式基础镜像的构建路线其实有两条一条是写 Dockerfile 交给 Docker 去构建另一条是先把容器跑起来在容器里手动操作完再用docker commit导出成镜像。我可以很直接地说能用 Dockerfile 就用 Dockerfile因为可复现性太重要了。下面我自己最常用的一套基础镜像构建方案按步骤拆给你看。3.1 写好第一版Dockerfile从一行FROM开始先看一个完整的 Dockerfile构建一个带常用运维工具、固定时区、支持中文的 CentOS 7 基础镜像# 基础镜像版本锁定避免漂移 FROM centos:7.9.2009 # 维护者信息建议写团队公共邮箱而不是个人 LABEL maintaineropsexample.com # 配置 yum 源使用阿里云镜像站提高下载速度 RUN rm -rf /etc/yum.repos.d/*.repo \ curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo \ sed -i s#mirrorlist#\#mirrorlist#g /etc/yum.repos.d/CentOS-Base.repo \ sed -i s#baseurlhttp://mirror.centos.org#baseurlhttp://mirrors.aliyun.com#g /etc/yum.repos.d/CentOS-Base.repo # 安装常用工具注意清理缓存 RUN yum install -y --setopttsflagsnodocs \ vim \ wget \ curl \ net-tools \ iproute \ tcpdump \ telnet \ lsof \ unzip \ zip \ which \ bash-completion \ glibc-langpack-zh \ yum clean all \ rm -rf /var/cache/yum # 设置时区 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone # 环境变量统一写入 ENV LANGzh_CN.UTF-8 \ LC_ALLzh_CN.UTF-8 \ TZAsia/Shanghai # 指定工作目录 WORKDIR /data # 默认命令 CMD [/bin/bash]把这份内容保存成Dockerfile放在一个空目录下然后执行构建docker build -t centos-base:7.9-v1 .构建完成后用docker images查看你生成的镜像。我第一次构建的时候没有处理 yum 源结果是构建过程在curl那一步慢到怀疑人生换了镜像站之后速度是质的飞跃。3.2 Dockerfile里的关键指令和参数选择原因上面这个 Dockerfile 看起来不长但每一行都有讲究。我逐个说一下为什么这么写。FROM centos:7.9.2009这里必须明确指定版本号。centos:latest在 Docker Hub 里最终指向哪个版本是不可控的一旦上游变动同一份 Dockerfile 下次构建出来的可能就不是同一个基础环境。生产环境里镜像版本漂移是风险源头锁定精确 tag 是基本功。LABEL maintainer这个很多人会忽略但如果你是团队协作维护者信息就是救命信息。别人看到这个镜像出问题时至少知道该找谁确认当时的构建意图。yum 源处理这一步是我认为整个 Dockerfile 里最值得注意的部分。CentOS 官方源在国内的下载速度不稳定所以换到阿里云镜像站的 CentOS 仓库。这里有两个关键动作一是注释掉mirrorlist二是把baseurl替换为镜像站地址。如果不注释mirrorlistyum 会优先走镜像列表逻辑你设的 baseurl 可能就被跳过了等于白配。--setopttsflagsnodocs这个参数值得单独说。它告诉 yum 不用安装文档文件比如 man 页面、说明文件。基础镜像里根本不需要这些文档去掉之后能明显缩小镜像体积。用 apt 的人可能更容易理解类似于--no-install-recommends。串联指令我刻意把 yum 安装和清理缓存放到了一个 RUN 里。原因很简单Docker 的镜像是分层保存的每一条 RUN 都会生成一个新层。如果你把yum install和yum clean all分开写成两个 RUN清理缓存只会发生在后面那一层而前面那一层里依然保存着 yum 缓存镜像体积并没有真正降下来。只有用串在一起让它们共享同一个层缓存清理才生效。时区和字符集容器默认时区是 UTC日志时间一看就不对所以必须显式设置。安装glibc-langpack-zh是为了支持中文字符集然后配合ENV LANGzh_CN.UTF-8一起用。有些镜像只在环境变量里设置了 LANG但系统里根本没有对应的 locale 数据包中文照样乱码。WORKDIR /data这个纯粹是个人习惯。线上服务的数据目录统一放在/data下后续业务镜像继承之后所有容器默认就进入这个目录操作起来顺手。3.3 构建命令与镜像瘦身技巧让体积和速度都达到理想状态构建基础镜像不是写完 Dockerfile 就结束了要观察构建过程还要检查最终体积。我用 Dockerfile 构建时有三条额外经验。第一条构建时加--no-cache参数尤其是从零开始第一次构建基础镜像的时候。docker build --no-cache -t centos-base:7.9-v1 .虽然不加--no-cache在增量构建时会快很多但在基础镜像定型阶段我必须确保每一层都是全新构建的避免旧缓存掩盖问题。等版本稳定之后正常构建即可不用每次都强制无缓存。第二条构建完成后立即检查各层大小用docker history可以看得非常清楚docker history centos-base:7.9-v1你留意那些 SIZE 特别大的层如果恰好是 yum 相关的层说明清理不彻底的可能性很大。正常情况下加了yum clean all和nodocs优化的 CentOS 7 基础镜像体积应该控制在 200MB 左右。如果你构建出来有 400MB 甚至更大先怀疑缓存清理的问题。第三条不要过度追求镜像瘦身。网上有些教程会教你把/usr/share/doc、/usr/share/man全部删掉甚至把vim换成vi把less换成more这种优化在基础镜像里是得不偿失的。基础镜像要交付给整个团队用工具链稍微完整一点大家排查问题时的体验会好很多。省下来的几十 MB 对存储和网络来说都不敏感但对使用者的效率影响是立竿见影的。4. 容器导出路线交互式定制然后commit什么场景才值得用说完了 Dockerfile 路线再聊聊另一种做法先启动容器进入内部手动改配置、安装软件最后docker commit把容器打成镜像。这条路线我不推荐作为常规方案但它在某些特殊场景下确实有效率优势还是值得掌握的。4.1 交互式定制容器适合一次性、探索性构建假设你临时需要一个带特定软件组合的基础环境但不确定具体要装哪些包又懒得反复改 Dockerfile 重新构建。这时候可以先跑起一个纯 CentOS 容器在里面自由探索。docker run -it --name centos-dev centos:7.9.2009 /bin/bash进入容器后你可以像在一台普通 CentOS 虚拟机里一样操作。比如配置 yum 源、安装软件、测试命令是否正常。我在探索阶段经常会做的一件事是yum makecache和yum search来确认软件包名称这在写 Dockerfile 时容易猜错包名交互式环境里却能直接确认。容器内的操作细节和 Dockerfile 里差别不大核心还是那几步替换 yum 源、安装工具包、设置时区和语言、清理缓存。区别在于 Dockerfile 是写好了再执行而交互式是操作完再记录。需要注意的是交互式容器一旦用了yum install务必记得在同一轮操作里执行yum clean all并退出前清理临时文件否则容器导出成镜像后会把缓存打包进去体积直接膨胀。确认容器内的环境达到预期之后先exit退出容器但先别急着删除容器因为我们要用它来生成镜像。4.2 docker commit 导出镜像最大的隐患是两个“不一致”docker commit的用法很简单docker commit -m centos base with network tools -a opsexample.com centos-dev centos-base:7.9-v2-m是提交说明-a是作者信息centos-dev是容器名最后是目标镜像的仓库名和标签。执行完docker images就能看到新镜像。但我必须把这条路线的两个大坑讲清楚。第一个坑镜像内容不可复现。容器里每一步操作都依赖手工执行你甚至可能装完一个包又卸载了但文件系统叠加层里残留的痕迹不会完全消失。一个月后你想重新构建一个一模一样的镜像很难通过 replay 操作复现只能拿上一个镜像再改。这就是为什么 Dockerfile 路线更优秀的根本原因——它把构建步骤固化成代码了。第二个坑容器状态会污染镜像。如果你在交互式容器里修改过配置文件、创建过临时文件、甚至写入了日志这些内容在docker commit时全部会进入镜像。很多人在容器里跑过yum update之后直接 commit镜像体积莫名其妙大了一截多半就是这个原因。我的实操建议是commit 路线只用来做一次性、探索性的基础镜像验证一旦确认环境可行立刻把当时的操作整理成 Dockerfile 放到代码仓库里以后统一走 Dockerfile 构建。基础镜像这种东西最终归宿永远是代码化的 Dockerfile不应该是某个运维同事手边那个“改了之后就再也找不回”的容器。5. 保存、上传与团队复用基础镜像的交付流程镜像构建完成并不等于工作结束。基础镜像最终是要给团队其他人用的怎么把它安全、高效地交到大家手里是这最后一道工序。5.1 私有仓库推送 vs 文件导出按场景选方案团队内部使用基础镜像通常有两种方式推到镜像仓库或者导出成 tar 包分发。推送到镜像仓库是最标准的做法适合团队已经有了私有 registry比如 Harbor 或者极简的 registry 容器的情况。操作流程是docker login registry.internal.example.com docker tag centos-base:7.9-v1 registry.internal.example.com/base/centos-base:7.9-v1 docker push registry.internal.example.com/base/centos-base:7.9-v1docker tag把本地镜像加上 registry 地址前缀和仓库路径docker push推上去。业务 Dockerfile 里直接用FROM registry.internal.example.com/base/centos-base:7.9-v1即可。没有私有仓库的时候可以用docker save导出文件拿到目标机器上再用docker load导入docker save -o centos-base-7.9-v1.tar centos-base:7.9-v1在目标机器上docker load -i centos-base-7.9-v1.tar文件导出方案适合离线环境、临时演示或跨网设备迁移但不适合团队日常开发。原因是文件本身不带元数据、不好做版本管理多人同时改一个 tar 包很容易互相覆盖。我建议至少在公司内搭一个极简的私有 Registry哪怕是单机部署的 registry:2 也好用 20 秒搞定docker run -d -p 5000:5000 --restartalways --name registry registry:2有了这个本地仓库另一台机器上直接docker pull localhost:5000/centos-base:7.9-v1就能拉到比传 tar 包可靠得多。5.2 团队成员使用自建镜像的标准姿势基础镜像发布之后团队成员在写业务 Dockerfile 时应该遵守一个约定不直接修改基础镜像而是基于基础镜像再做一层自定义。举个例子某个 Python 服务需要 C 扩展编译环境那业务 Dockerfile 这样写FROM registry.internal.example.com/base/centos-base:7.9-v1 RUN yum install -y gcc gcc-c make \ yum clean all COPY requirements.txt /data/ RUN pip install -r requirements.txt这样每个团队都是基于同一个基础镜像来扩展任何人想确认底层环境是什么只需要查看基础镜像的 tag 和 Dockerfile 仓库就能知道全部信息。反过来如果某个团队直接改了基础镜像里的东西那基础镜像的“标准”意义就消失了排查问题时也会造成混乱。6. 构建过程中的常见问题与排查记录从我开始折腾 CentOS 基础镜像到现在踩过不少坑有些坑到现在偶尔还会遇到。这一节把我印象最深的几个问题整理成速查表你直接对照就能解决大部分情况。问题现象可能原因解决办法yum install 报 404 或找不到 repoyum 源仓库 URL 失效官方仓库调整了路径改用阿里云镜像站的 vault 路径或 centos 仓库路径构建过程在 yum 阶段特别慢使用的是默认官方源网络连接差替换为国内镜像站地址镜像体积比预期大很多yum 缓存没有清理或者安装时带了 doc 文件确认yum clean all和--setopttsflagsnodocs容器里中文显示为方块乱码缺少中文字符集支持 pack 包安装glibc-langpack-zh并设置LANGzh_CN.UTF-8容器内日志时间相差 8 小时容器默认使用 UTC 时区设置TZAsia/Shanghai并链接/etc/localtimesystemd 相关服务无法启动容器默认不是 PID 1systemd 无法管理基础镜像里不要安装 systemd 服务特殊场景用特权模式docker commit 后镜像过大容器内残留缓存、日志、临时文件commit 前手动清理并退出所有临时进程docker build 时报网络超时Docker 引擎未配置镜像源或 DNS 问题检查 daemon.json 内的 registry-mirrors 配置6.1 Yum源失效这是新版构建最常见的坑CentOS 7/8 的官方仓库地址这几年有过多次调整如果你拿着旧的 Dockerfile 直接构建经常会遇到Could not retrieve mirrorlist或者 404 错误。本质原因是官方把旧版本的软件包移到了 vault 归档目录默认的 mirrorlist 指向已经失效。我用得最多的是阿里云镜像站配置方式在 3.1 节里已经写过。这里补充一个更稳妥的做法直接把 baseurl 指向镜像站的 vault 目录。阿里云的 CentOS 7 仓库实际地址通常是https://mirrors.aliyun.com/centos-vault/7.9.2009/os/x86_64/你在 sed 替换时可以精确到版本号。这样即使官方源完全不可用镜像站的归档副本也还在构建能稳定跑通。如果你们公司的机器在内网连外网镜像站也访问不了那就需要在内网自建一个 yum 源镜像然后把 Dockerfile 里的 baseurl 指向内网地址。这种情况我在离线交付项目里做过很多次本质上是把同样的替换逻辑换成内网 IP 或域名。6.2 镜像体积异常如何判断和处理构建完镜像后如果体积明显偏大先用docker history看一下各个层的大小分布。最常见的元凶是 yum 缓存其次是/var/log里残留的日志文件以及/tmp下的临时文件。通过 Dockerfile 构建时我习惯在 RUN 的末尾统一清理RUN yum install -y --setopttsflagsnodocs packages \ yum clean all \ rm -rf /var/cache/yum \ rm -rf /var/log/yum.log \ rm -rf /tmp/*这一串操作保证的是一个 RUN 层内安装和清理同时发生。如果你把清理写成独立的 RUN前面那层依然带着缓存镜像大小不会真正降下来。这个技巧不仅适用于 CentOSDebian 系里用 apt 同理把apt-get install和rm -rf /var/lib/apt/lists/*合并到同一个 RUN 中。6.3 systemd、时区、cgroup等初始化问题基础镜像一般不需要 systemd因为容器通常只跑一个主进程PID 1 不是 init 系统。但如果你非要在一个容器里同时跑多个服务并且希望用systemctl start xxx来管理服务那就得给容器加特权模式和特殊挂载比如docker run -d --privileged --nametest --cgroupnshost -v /sys/fs/cgroup:/sys/fs/cgroup:rw centos-base:7.9-v1 /usr/sbin/init这样很麻烦也不符合容器的设计哲学。我个人的原则是基础镜像里不装 systemd 相关包每个容器只跑一个主进程多个服务拆成多个容器用 docker-compose 编排。你要是被 systemd 折腾过就会明白我说的省心。时区问题相对温和但也别掉以轻心。有时候你的应用读的是/etc/localtime符号链接有时候读的是TZ环境变量两个都设置好就万无一失。字符集同理环境变量和 locale 数据包缺一不可否则你看日志时就会发现服务本身没有乱码但打进日志里的中文全成了问号。我在实际使用中还有一个心得基础镜像的 Dockerfile 一定要和版本号、日期一起放进 git 仓库提交信息里写清楚变更点。比如“新增 net-tools 和 lsof”“替换 yum 源为镜像站”“加入中文语言包支持”。几周后团队有人问起某个基础镜像为什么是这个体积、这个工具集时你可以直接翻出提交记录给答案而不是靠回忆拼凑当时做了什么。最后再分享一个小技巧每次基础镜像定型后把构建日志和docker history输出存档一下放到团队的文档系统里。这个习惯帮我在一次“基础镜像突然变大 50MB”的问题排查中迅速定位到是某次构建误装了 debuginfo 包导致的。基础镜像的维护看起来就是写个 Dockerfile但真正考验人的是对每个变更、每个取舍都有完整的记录和判断。这条经验比任何具体配置都值钱。
返回列表