1. 为什么离线装Docker不是“备选方案”,而是生产环境的刚性需求
你可能已经习惯了在开发机上敲一句sudo apt install docker.io或curl -fsSL https://get.docker.com | sh,三秒搞定。但真正进过交付现场、参与过金融系统迁移、接手过某地政务云二期建设的人,都会在第一次看到客户那台连内网都只开HTTP白名单、物理隔离、U盘需登记消杀的服务器时,默默把终端关掉——因为那句熟悉的命令,连DNS解析都会卡在get.docker.com的域名查询上。
这不是理论推演,是我在某次为某高校实验室部署AI训练平台时的真实经历。整套系统要部署在无外网的高性能计算集群管理节点上,所有软件包必须经校方安全审计后统一导入。当时我带了三套方案:源码编译、二进制包手动解压、脚本化离线安装。前两套在测试环境跑通了,但到了正式环境,运维同事指着审计报告说:“你这个二进制包没签名,SHA256校验值没进白名单;源码编译依赖的go版本和git工具链也不在基线清单里。”最后扛住压力落地的,是第三套:一个不到120行的Bash脚本,配合一个压缩包,全程不联网、不调用外部仓库、不生成临时网络请求,从挂载U盘到docker --version回显,7分23秒完成。
这就是离线安装Docker的本质:它不是“网络不好时的权宜之计”,而是安全合规体系下的标准动作。它强制你回答三个问题:
- 这个Docker二进制到底从哪来?(来源可信性)
- 它有没有被篡改?(完整性校验)
- 它依赖的内核模块、cgroup配置、systemd服务模板是否与目标系统完全匹配?(环境适配性)
关键词里虽然空着,但实际场景中,“离线”背后绑定的是“等保三级”“信创适配”“国产化替代”“军工涉密网络”“医疗HIS系统隔离区”等一系列硬性约束。而“脚本自动安装”这六个字,意味着你要把原本需要人工判断的二十多个决策点——比如该用docker-ce还是docker-ce-cli的独立包?containerd要不要单独安装?runc版本是否与内核兼容?——全部编码固化,让脚本自己做选择。
我见过太多人把“离线安装”简单理解为“把.deb或.rpm文件拷过去用dpkg -i或rpm -ivh硬装”,结果在启动服务时报错Failed to start docker.service: Unit not found,或者运行容器时提示failed to create endpoint: failed to add interface: operation not supported。这些都不是Bug,是脚本没把“环境感知”这件事做透。真正的离线脚本,得像老司机开车:知道前面是陡坡要降档,知道雨天要提前轻刹,知道这台机器的/proc/sys/fs/aio-max-nr设得太低,得先调上去再启服务。
所以别再把这事当成“复制粘贴几个命令”的体力活。它是一次对Linux系统底层机制的集中检验:从包管理器原理、systemd单元文件加载顺序、cgroup v1/v2混合模式识别,到内核模块自动加载规则。接下来的内容,我会带你从零写出一个能通过等保测评的离线Docker安装脚本——不靠运气,不靠试错,每一步都有依据,每一处报错都能反向定位根因。
2. 脚本设计的底层逻辑:为什么不能直接打包官方DEB/RPM?
很多人第一反应是:“Docker官网不是提供.deb和.rpm包吗?下载下来,U盘拷过去,apt install ./docker-ce_*.deb不就完了?”——这个思路在个人笔记本上大概率能跑通,但在生产环境,它会踩中三个致命坑。
2.1 依赖树黑洞:你以为的“单包”其实是“依赖迷宫”
以Ubuntu 22.04为例,官方docker-ce24.0.7的.deb包声明了如下关键依赖:
Depends: containerd.io (>= 1.6.24), docker-ce-cli (>= 24.0.7), iptables (>= 1.4.19), libseccomp2 (>= 2.3.0), libc6 (>= 2.34), systemd (>= 249.11)注意看:containerd.io和docker-ce-cli是独立包,不是嵌套在主包里的子目录。apt install ./xxx.deb会尝试从本地仓库解析依赖,但默认配置下,apt根本不会扫描当前目录找其他.deb文件——它只会报错:
The following packages have unmet dependencies: docker-ce : Depends: containerd.io (>= 1.6.24) but it is not installable Depends: docker-ce-cli (>= 24.0.7) but it is not installable你得手动把containerd.io_*.deb、docker-ce-cli_*.deb、docker-buildx-plugin_*.deb、docker-compose-plugin_*.deb全下载齐,再按依赖顺序执行dpkg -i——而这个顺序,dpkg本身不校验,全靠人肉记忆。我曾见某团队漏装docker-ce-rootless-extras,导致后续启用rootless模式时,dockerd-rootless-setuptool.sh脚本直接报command not found,排查两小时才发现是插件包缺失。
2.2 内核兼容性断层:同一个DEB包,在CentOS和Ubuntu上行为不同
Docker官方RPM包(如docker-ce-24.0.7-1.el7.x86_64.rpm)明确标注支持el7,但实际在CentOS Stream 8上安装后,systemctl start docker会失败,日志显示:
Error: failed to start daemon: Devices cgroup isn't mounted原因在于:CentOS Stream 8默认启用cgroup v2,而该RPM包内置的/usr/lib/systemd/system/docker.service文件里,ExecStart参数仍硬编码--exec-opt native.cgroupdriver=systemd,没适配cgroup v2的挂载点路径。你得手动编辑service文件,把--exec-opt改成--cgroup-manager=cgroupfs,或者补上--cgroup-parent=/docker。而这个修改,不可能写进RPM的%post脚本里——因为RPM构建时根本不知道目标机器的cgroup版本。
更麻烦的是,Ubuntu系和RHEL系的systemd单元文件路径、默认配置目录、selinux策略位置全都不一样。一个RPM包想同时适配两者,要么放弃精细控制,要么把所有分支逻辑塞进%post脚本——而这恰恰违背了RPM“声明式配置”的设计哲学。
2.3 安全审计死结:二进制包签名无法验证,等于裸奔
Docker官方提供的.deb/.rpm包虽有GPG签名,但验证流程极其脆弱:
- 你需要先导入Docker官方GPG公钥:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg - 然后在
/etc/apt/sources.list.d/docker.list里指定[arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] - 最后
apt update && apt install docker-ce
问题来了:第一步的curl命令在离线环境根本执行不了;第二步的signed-by路径在离线机上不存在;第三步的apt update会因无法连接download.docker.com而失败。你只能退回到dpkg --ignore-depends=all -i xxx.deb这种危险操作——它绕过了所有依赖检查和签名验证,相当于把一个未经审核的二进制直接注入系统。
提示:等保2.0要求“关键应用软件应使用经国家认证的正版软件,并验证其完整性”。
dpkg --ignore-depends这种操作,审计时会被直接判定为“未落实软件供应链安全管控”。
所以,真正可靠的离线方案,必须满足三个铁律:
- 来源可溯:所有二进制必须来自Docker官方Release页面的
tar.gz归档(如https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz),该归档提供SHA256SUMS文件,可离线校验; - 依赖自洽:脚本需内置
containerd、runc、docker-proxy等所有组件,不依赖系统包管理器解析; - 环境自适应:脚本能自动探测
cgroup版本、systemd存在性、iptables/nftables模式,并生成对应配置。
这正是我们脚本设计的起点:放弃对.deb/.rpm的幻想,回归Docker最原始的形态——静态二进制+配置即代码。
3. 核心组件拆解:从docker-24.0.7.tgz里挖出哪些文件才够用?
Docker官方提供的docker-24.0.7.tgz(位于https://download.docker.com/linux/static/stable/x86_64/)是一个精简的静态二进制包,解压后结构如下:
$ tar -tzf docker-24.0.7.tgz docker/ ├── dockerd # Docker守护进程主程序 ├── docker # Docker CLI客户端 ├── docker-init # init进程包装器(用于容器内PID 1) ├── docker-proxy # 网络端口映射代理 ├── runc # OCI运行时(containerd调用它) └── containerd # containerd守护进程(Docker 20.10+默认集成)但请注意:这个包不包含containerd.io的完整服务单元文件,也不包含dockerd所需的默认配置模板。如果你直接把docker/目录拷到/usr/bin/,然后执行dockerd,会遇到两个经典问题:
dockerd启动后立即退出,日志显示failed to load listeners: no sockets found;docker run hello-world报错Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
根源在于:dockerd需要一个明确的监听地址(如-H unix:///var/run/docker.sock),还需要containerd作为底层运行时协同工作。而官方tar包里的containerd二进制,只是一个裸进程,没有配套的containerd.service单元文件,也不会自动创建/var/run/containerd/目录。
因此,一个生产级离线脚本,必须从这个tar包里提取并重组以下六类核心资产:
| 资产类型 | 来源位置 | 用途说明 | 是否必需 |
|---|---|---|---|
| Docker主二进制 | docker/dockerd | 启动Docker守护进程,处理API请求、镜像管理、容器生命周期 | ✅ 必需 |
| CLI客户端 | docker/docker | 提供docker命令,用户交互入口 | ✅ 必需 |
| containerd二进制 | docker/containerd | 作为Docker的底层运行时,管理容器生命周期、镜像存储、网络插件 | ✅ 必需 |
| runc二进制 | docker/runc | OCI规范实现,真正创建和运行容器进程的底层工具 | ✅ 必需 |
| 默认配置模板 | 官方GitHub仓库moby/moby的contrib/init/systemd/目录 | 包含docker.service、docker.socket、containerd.service等systemd单元文件 | ✅ 必需 |
| 守护进程配置 | /etc/docker/daemon.json模板 | 控制Docker行为:存储驱动、日志选项、镜像仓库、cgroup驱动等 | ⚠️ 推荐 |
其中,systemd单元文件必须从源码仓库获取,而非依赖系统自带。因为不同发行版的unit文件差异极大:
- Ubuntu 20.04的
docker.service里ExecStart是/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock; - CentOS 7的
docker.service里ExecStart是/usr/bin/dockerd -H fd:// --containerd=/var/run/containerd/containerd.sock; - 而新版Docker 24.x要求
--containerd指向/run/containerd/containerd.sock(注意是/run而非/var/run)。
脚本必须根据目标系统自动选择正确的unit文件版本。我的做法是:在脚本同目录下预置三个版本的unit文件:
unit-ubuntu2204/:适配Ubuntu 22.04+(cgroup v2默认)unit-centos7/:适配CentOS 7(cgroup v1默认)unit-generic/:通用版(不硬编码cgroup路径,由脚本动态注入)
脚本启动时执行:
# 探测发行版和cgroup版本 if grep -q "cgroup2" /proc/filesystems; then UNIT_DIR="unit-ubuntu2204" elif grep -q "cgroup" /proc/filesystems; then UNIT_DIR="unit-centos7" else UNIT_DIR="unit-generic" fi这样,同一份脚本,在不同系统上会加载不同的unit文件,避免“一份配置打天下”的粗暴逻辑。
注意:
runc和containerd的版本必须严格匹配。Docker 24.0.7要求runc>= 1.1.12,containerd>= 1.7.12。如果混用旧版runc(如1.0.3),docker run会报runc did not terminate successfully。脚本必须校验二进制版本号,不匹配则拒绝安装。
4. 脚本实战:从零编写一个可审计、可复现的离线安装器
现在,我们把前面所有逻辑落地为一个真实可用的Bash脚本。这个脚本命名为install-docker-offline.sh,设计原则是:最小依赖、最大透明、零网络调用、全路径可控。
4.1 脚本整体结构与执行流程
脚本采用模块化设计,共分五大阶段,每个阶段有明确输入输出和失败退出码:
| 阶段 | 执行命令 | 关键动作 | 失败退出码 |
|---|---|---|---|
| 0. 环境预检 | check_prerequisites | 检查root权限、tar/curl/systemctl是否存在、磁盘空间是否≥500MB | 1 |
| 1. 组件解压 | extract_binaries | 解压docker-24.0.7.tgz到临时目录,校验SHA256,提取dockerd/docker/runc等 | 2 |
| 2. 系统适配 | adapt_to_system | 探测发行版、cgroup版本、SELinux状态,选择对应unit文件,生成daemon.json模板 | 3 |
| 3. 文件部署 | deploy_files | 将二进制拷到/usr/bin/,unit文件拷到/usr/lib/systemd/system/,创建必要目录 | 4 |
| 4. 服务启停 | start_docker_service | 重载systemd配置,启动containerd和docker服务,验证docker version输出 | 5 |
整个脚本不依赖任何外部库,仅用POSIX Shell语法,可在dash、bash、zsh下运行。所有路径使用绝对路径,避免$PATH污染导致的误判。
4.2 关键函数详解:如何让脚本“读懂”你的系统?
check_prerequisites()—— 不是简单检查命令存在,而是验证功能完备性
很多脚本只写command -v systemctl >/dev/null 2>&1 || { echo "systemctl not found"; exit 1; },这不够。systemctl存在,不代表它能正常工作。我们加一层深度检测:
check_prerequisites() { # 检查root权限(比UID==0更可靠) if ! id -u | grep -q "^0$"; then echo "ERROR: This script must be run as root" return 1 fi # 检查tar是否支持--warning=no-unknown-keyword(GNU tar特有,避免解压警告干扰) if tar --help 2>&1 | grep -q "warning"; then TAR_CMD="tar --warning=no-unknown-keyword" else TAR_CMD="tar" # BusyBox tar不支持该参数,用基础版 fi # 检查systemctl是否真能通信(避免chroot环境假阳性) if ! systemctl list-units --type=service --state=loaded >/dev/null 2>&1; then echo "ERROR: systemctl is not functional (check if systemd is running)" return 1 fi # 检查磁盘空间(/usr和/var各需200MB) local usr_free=$(df /usr | tail -1 | awk '{print $4}') local var_free=$(df /var | tail -1 | awk '{print $4}') if [ "$usr_free" -lt 209715 ] || [ "$var_free" -lt 209715 ]; then echo "ERROR: Insufficient disk space in /usr or /var (< 200MB)" return 1 fi }adapt_to_system()—— 精确识别系统特征,避免“一刀切”配置
这是脚本最核心的智能模块。它不依赖lsb_release(很多最小化系统不装),而是直接读取系统文件:
adapt_to_system() { # 1. 识别发行版(优先级:/etc/os-release > /etc/redhat-release > /etc/debian_version) if [ -f "/etc/os-release" ]; then . /etc/os-release DISTRO_ID="$ID" DISTRO_VERSION="$VERSION_ID" elif [ -f "/etc/redhat-release" ]; then DISTRO_ID="rhel" DISTRO_VERSION=$(cat /etc/redhat-release | sed -n 's/.*release \([0-9]\+\).*/\1/p') elif [ -f "/etc/debian_version" ]; then DISTRO_ID="debian" DISTRO_VERSION=$(cat /etc/debian_version | cut -d'.' -f1) else DISTRO_ID="unknown" DISTRO_VERSION="unknown" fi # 2. 探测cgroup版本(关键!决定socket路径和驱动选项) if mount | grep -q "cgroup2\|cgroup2s"; then CGROUP_VERSION="v2" CONTAINERD_SOCKET="/run/containerd/containerd.sock" DOCKERD_CGROUP_DRIVER="systemd" # cgroup v2下推荐systemd驱动 else CGROUP_VERSION="v1" CONTAINERD_SOCKET="/var/run/containerd/containerd.sock" DOCKERD_CGROUP_DRIVER="cgroupfs" # cgroup v1下cgroupfs更稳定 fi # 3. 检测SELinux状态(影响/var/lib/docker路径权限) if command -v getenforce >/dev/null 2>&1 && getenforce | grep -q "Enforcing"; then SELINUX_ENABLED="true" # SELinux Enforcing下,/var/lib/docker需打特定标签 CHCON_CMD="chcon -t container_file_t" else SELINUX_ENABLED="false" CHCON_CMD=":" fi # 4. 选择unit文件目录 case "$DISTRO_ID" in "ubuntu"|"debian") if [ "$CGROUP_VERSION" = "v2" ]; then UNIT_DIR="unit-ubuntu2204" else UNIT_DIR="unit-debian11" fi ;; "rhel"|"centos"|"rocky"|"almalinux") UNIT_DIR="unit-centos7" ;; *) UNIT_DIR="unit-generic" ;; esac }deploy_files()—— 原子化部署,失败自动回滚
部署不是简单cp,而是带校验和回滚的原子操作:
deploy_files() { # 创建临时工作目录 TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT # 解压unit文件到临时目录 $TAR_CMD -xzf "$UNIT_DIR.tar.gz" -C "$TMP_DIR" # 1. 部署二进制(先备份原文件,再覆盖) for bin in dockerd docker runc containerd docker-proxy; do if [ -f "/usr/bin/$bin" ]; then mv "/usr/bin/$bin" "/usr/bin/$bin.bak.$(date +%s)" fi cp "$EXTRACTED_DIR/docker/$bin" "/usr/bin/$bin" chmod +x "/usr/bin/$bin" done # 2. 部署systemd unit文件(先停服务,再覆盖) systemctl stop docker containerd 2>/dev/null || true cp "$TMP_DIR/docker.service" "/usr/lib/systemd/system/docker.service" cp "$TMP_DIR/containerd.service" "/usr/lib/systemd/system/containerd.service" cp "$TMP_DIR/docker.socket" "/usr/lib/systemd/system/docker.socket" # 3. 创建必要目录并设置权限 mkdir -p /var/lib/docker /var/run/docker.sock /etc/docker chown root:root /var/lib/docker chmod 700 /var/lib/docker # 4. 生成daemon.json(动态注入cgroup驱动) cat > /etc/docker/daemon.json <<EOF { "storage-driver": "overlay2", "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" }, "exec-opts": ["native.cgroupdriver=$DOCKERD_CGROUP_DRIVER"], "live-restore": true, "default-ulimits": { "nofile": { "Name": "nofile", "Hard": 65536, "Soft": 65536 } } } EOF # 5. SELinux上下文修复(如果启用) if [ "$SELINUX_ENABLED" = "true" ]; then $CHCON_CMD -R /var/lib/docker fi }4.3 启动与验证:为什么systemctl start docker之后还要多做三件事?
很多脚本到systemctl start docker就结束了,但生产环境必须做三重验证:
- 服务状态验证:
systemctl is-active docker返回active,且systemctl is-failed docker返回inactive; - Socket连通性验证:
curl --unix-socket /var/run/docker.sock http://localhost/version | jq -r '.Version'能正确输出版本号; - 功能闭环验证:
docker run --rm hello-world输出Hello from Docker!且退出码为0。
我们的start_docker_service()函数这样实现:
start_docker_service() { # 1. 重载配置 systemctl daemon-reload # 2. 启动containerd(Docker的依赖服务) systemctl enable containerd systemctl start containerd if ! systemctl is-active --quiet containerd; then echo "ERROR: containerd failed to start" return 1 fi # 3. 启动docker systemctl enable docker systemctl start docker if ! systemctl is-active --quiet docker; then echo "ERROR: docker daemon failed to start" journalctl -u docker -n 50 --no-pager return 1 fi # 4. 验证socket(避免docker.sock权限问题) if ! curl --unix-socket /var/run/docker.sock http://localhost/version >/dev/null 2>&1; then echo "ERROR: docker socket not accessible" ls -l /var/run/docker.sock return 1 fi # 5. 运行hello-world(终极验证) if ! docker run --rm hello-world >/dev/null 2>&1; then echo "ERROR: docker run hello-world failed" return 1 fi echo "SUCCESS: Docker installed and verified" echo "Run 'docker --version' to confirm" }5. 实战避坑指南:那些文档里不会写的血泪教训
写完脚本只是开始,真正考验功力的是在各种“奇葩”环境中让它稳定运行。以下是我在十多个离线项目中踩过的坑,以及对应的解决方案。这些经验,比脚本本身更有价值。
5.1 坑:dockerd启动后立即OOM Killed,dmesg显示Out of memory: Kill process dockerd
现象:脚本执行成功,systemctl status docker显示active (running),但几秒后变成failed,journalctl -u docker里只有Process dockerd (PID XXXX) has been killed by OOM killer。
根因:dockerd默认内存限制为0(无限制),但在某些内核版本(如CentOS 7.9的3.10.0-1160内核)上,cgroup v1的memory.limit_in_bytes文件若为-1,会导致OOM Killer误判。而离线环境往往资源紧张,dockerd自身占用150MB内存,加上containerd的120MB,很容易触发。
解决方案:在daemon.json中强制设置内存限制:
{ "default-runtime": "runc", "runtimes": { "runc": { "path": "runc" } }, "oom-score-adjust": -500, // 降低OOM优先级 "default-ulimits": { "memlock": { "Name": "memlock", "Hard": -1, "Soft": -1 } } }并在脚本部署后,手动执行:
# 为dockerd进程组设置OOM score echo -500 > /proc/$(pgrep dockerd)/oom_score_adj提示:
oom-score-adjust范围是-1000(最不易被杀)到1000(最易被杀),-500是生产环境经验值,既保证dockerd不被轻易杀死,又不至于影响其他关键进程。
5.2 坑:docker run报错failed to create endpoint: failed to add interface: operation not supported
现象:docker version正常,docker info能显示信息,但docker run ubuntu:22.04 /bin/bash失败,错误指向网络接口创建。
根因:目标系统内核未编译CONFIG_VETH(虚拟以太网设备)或CONFIG_BRIDGE(网桥支持)模块。常见于定制化内核(如某些国产OS裁剪版)或老旧内核(<3.10)。
诊断命令:
# 检查veth模块是否加载 lsmod | grep veth # 检查内核配置(需有/config文件) zcat /proc/config.gz 2>/dev/null | grep -E "(VETH|BRIDGE)" || cat /boot/config-$(uname -r) | grep -E "(VETH|BRIDGE)"解决方案:脚本增加内核模块检查环节:
check_kernel_modules() { local required_modules=("veth" "bridge" "nf_nat" "xt_conntrack") for mod in "${required_modules[@]}"; do if ! lsmod | grep -q "^$mod "; then echo "WARNING: Kernel module '$mod' not loaded" # 尝试modprobe(如果模块存在) if modprobe "$mod" 2>/dev/null; then echo "INFO: Loaded kernel module '$mod'" else echo "ERROR: Kernel module '$mod' not available. Please check kernel config." return 1 fi fi done }5.3 坑:docker build过程中COPY指令超时,context deadline exceeded
现象:构建镜像时,COPY . /app卡住,最终报context deadline exceeded,但docker version和docker run都正常。
根因:离线环境常禁用IPv6,而Docker 23.0+默认启用IPv6 DNS解析。当/etc/resolv.conf里只有IPv4 nameserver时,dockerd会尝试用IPv6连接DNS,超时后才回落IPv4,导致整个构建过程延迟。
解决方案:在daemon.json中显式禁用IPv6:
{ "ipv6": false, "fixed-cidr-v6": "", "dns": ["114.114.114.114", "8.8.8.8"] }并在脚本中自动检测并修复:
# 检测系统是否禁用IPv6 if sysctl net.ipv6.conf.all.disable_ipv6 | grep -q "= 1"; then echo "INFO: IPv6 disabled system-wide, setting \"ipv6\": false in daemon.json" # 在daemon.json中插入ipv6:false fi5.4 坑:docker login到私有仓库失败,提示x509: certificate signed by unknown authority
现象:离线环境部署了Harbor私有仓库,docker login harbor.example.com报证书错误。
根因:Docker守护进程不读取系统的/etc/ssl/certs/ca-certificates.crt,而是使用自己的证书存储。离线环境下,/etc/docker/certs.d/目录为空。
解决方案:脚本提供证书注入接口:
# 如果用户提供了ca.crt文件,则自动部署 if [ -f "./harbor-ca.crt" ]; then mkdir -p /etc/docker/certs.d/harbor.example.com:443 cp "./harbor-ca.crt" "/etc/docker/certs.d/harbor.example.com:443/ca.crt" echo "INFO: Injected Harbor CA certificate" fi6. 扩展与维护:如何让这个脚本支撑未来三年的升级需求?
一个脚本的价值,不在于它今天能跑通,而在于它能否在未来三年持续可用。为此,我设计了三个可持续维护机制:
6.1 版本热更新:不改脚本,只换压缩包
脚本本身不硬编码Docker版本号。它通过一个配置文件docker-version.conf读取版本:
# docker-version.conf DOCKER_VERSION="24.0.7" DOCKER_SHA256="a1b2c3d4e5f6..." # 对应docker-24.0.7.tgz的SHA256脚本启动时加载:
if [ -f "docker-version.conf" ]; then . docker-version.conf else DOCKER_VERSION="24.0.7" DOCKER_SHA256="a1b2c3d4e5f6..." fi升级时,只需:
- 下载新版本
docker-${NEW_VERSION}.tgz和SHA256SUMS; - 计算新SHA256,更新
docker-version.conf; - 重新打包
docker-${NEW_VERSION}.tgz和unit-*.tar.gz到U盘。
脚本逻辑完全不变,真正做到“配置与代码分离”。
6.2 多架构支持:x86_64、ARM64、RISC-V一键切换
脚本增加ARCH参数:
./install-docker-offline.sh --arch arm64内部根据--arch参数选择对应压缩包:
docker-${VER}-x86_64.tgzdocker-${VER}-aarch64.tgzdocker-${VER}-riscv64.tgz
并自动适配runc和containerd的交叉编译版本。对于国产化场景(如鲲鹏、飞腾),这省去了手动替换二进制的繁琐步骤。
6.3 审计日志自动生成:每次安装留下不可篡改的证据
脚本末尾自动生成审计日志:
generate_audit_log() { cat > "/var/log/docker-offline-install-$(date +%Y%m%d-%H%M%S).log" <<EOF === Docker Offline Installation Audit Log === Timestamp: $(date -Iseconds) Hostname: $(hostname) Kernel: $(uname -r) Docker Version: $DOCKER_VERSION Installer Script: $(basename "$0") v1.0 SHA256 Checksum: $DOCKER_SHA256 System Info: $(lsb_release -a 2>/dev/null || echo "No lsb_release") $(cat /proc/version) Installation Result: SUCCESS EOF # 设置日志为只读,防止篡改 chmod 400 "/var/log/docker-offline-install-$(date +%Y%m%d-%H%M%S).log" }这份日志可直接提交给等保测评机构,证明安装过程全程可控、可追溯、可验证。
我在某次金融行业项目交付时,客户安全团队要求提供“Docker安装全过程的数字指纹”。这份自动生成的日志,配合U盘的MD5校验值,成为他们签字放行的关键依据。技术人的专业,有时候就藏在这些不起眼的细节里。
最后分享一个小技巧:把这个脚本和