尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

内网离线部署指南:Docker安装与中间件镜像导入编排全流程

内网离线部署指南:Docker安装与中间件镜像导入编排全流程 做运维的同学应该都遇到过这种局面接手一台新服务器或者要在一个封闭的内网环境里搭建一套业务系统结果发现这台机器连不上外网Docker Hub 就更不用想了。但业务不能等MySQL、Redis、Nginx、消息中间件这些基础组件一个都不能少。如果每个都去源码编译安装光是处理依赖就够折腾一整天的。我最近又完整地做了一遍这件事——在 x86 架构的 Linux 服务器上离线安装 Docker 引擎再用 docker-compose 一键把各类中间件部署起来。整个过程踩了不少坑也沉淀了一套可以复用的流程今天完整记录下来希望能给同样被困在内网环境里的朋友一些参考。这篇文章我会从方案选型、安装包准备、镜像获取与导入到 docker-compose 编排部署中间件再到常见问题的排查完整走一遍流程。无论你是在 CentOS 7、CentOS 8 还是 Rocky Linux 上操作思路和命令基本都是通用的。1. 离线部署的整体思路与准备工作1.1 内网环境部署的典型场景很多刚接触离线部署的朋友会有一个疑问服务器真的完全不能访问外网吗根据我的实际接触情况主要有这么几种物理隔离网络机房与互联网物理断开只能通过摆渡机或 U 盘拷文件进去这是最严格的内网环境。安全组限制服务器可以访问内网资源但出方向被安全策略封住无法访问公网仓库。临时故障或带宽限制外网带宽极低拉一个几百 MB 的镜像需要几小时等不起。不管哪种场景核心诉求是一样的在没有外网的情况下把 Docker 运行时、编排工具和中间件镜像完整地搬到目标机器上并且保证能正常启动、稳定运行。1.2 方案选型为什么锁定 Docker在离线环境下部署中间件还有另一种传统方式把 RPM 包、源码包全部下载好传进内网然后逐个编译安装。这种方式的问题在于依赖关系非常痛苦——装一个 MySQL 可能要连带解决 libaio、numactl、openssl 的一系列依赖版本稍微不对就编译失败。而 Docker 方案的优势在于镜像本身就是一个完整的运行环境里面已经把中间件及其依赖全部打包好了。你只需要做三件事在内网装好 Docker 引擎。把中间件镜像导进去。用 docker-compose 把容器跑起来。对比下来Docker 方案在离线场景下的交付效率是最高的而且部署出的环境高度一致不会出现“在我机器上是好的到你机器上就挂了”的问题。这也是我为什么一直推荐内网环境优先考虑 Docker 的原因。1.3 先澄清一个概念这里说的“中间件”是什么“中间件”这个词在不同语境下含义完全不同。做 Web 开发的同学听到“中间件”第一反应可能是 Laravel 里的 Middleware、Vue 里的路由守卫或者 Django 里的中间件类那是代码层面的请求拦截器。而运维和系统架构层面说的“中间件”通常是指独立部署的基础软件服务比如数据库、缓存、消息队列、反向代理等它们为业务系统提供数据存储、通信和流量转发能力。这篇文章讨论的显然是后者。我会以 MySQL、Redis、Nginx、RabbitMQ 这几个最常用的中间件为例演示完整的离线部署流程。理解了这套流程你需要部署 Kafka、Elasticsearch、GitLab 等组件时思路完全一样只是镜像和配置不同。1.4 准备工作清单在开始之前先盘一下需要准备的东西一台有外网的 Linux 机器用于下载 Docker 安装包和拉取镜像。如果只有 Windows/Mac也可以装 Docker Desktop 来拉镜像后面会提到注意架构差异。目标内网服务器操作系统建议 CentOS 7.9、Rocky Linux 8、Ubuntu 20.04本文以 CentOS 系为主。一个足够大的 U 盘或摆渡盘建议 16GB 以上因为容器镜像加起来体积不小。内网服务器有 root 权限。在动手之前先确认目标服务器的架构uname -m如果输出x86_64说明是标准的 x86 架构下文所有操作都适用。如果输出的是aarch64那是 ARM 架构服务器镜像和安装包都需要单独准备不能用 x86 的版本。2. Docker 引擎与 docker-compose 的离线安装实操2.1 在有网环境准备 Docker 安装包离线安装 Docker 最稳妥的方式是把 Docker 官方仓库里的所有 RPM 包连同依赖全部下载下来再用 U 盘拷到内网。首先在有网机器上安装yum-utils和repotrack工具yum install -y yum-utils createrepo然后添加 Docker 官方源yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo接下来是关键步骤下载 Docker 相关的全部 RPM 包。这里有一个经验提示——不要用yumdownloader因为yumdownloader默认只下载你指定的包本身不会自动把依赖一并拉下来。repotrack会把指定包以及它们的所有依赖全部递归下载更适合做离线安装包。mkdir -p /data/docker-rpm cd /data/docker-rpm repotrack docker-ce docker-ce-cli containerd.io docker-compose-plugin如果目标机器是 CentOS 7我建议顺手把docker-ce-rootless-extras也下载下来虽然多数场景用不到但保不齐哪天需要。下载完成后把整个目录打包cd /data tar zcf docker-rpms.tar.gz docker-rpm然后把这个 tar 包通过 U 盘或 scp 传到内网服务器。这里还要说一个提升效率的小技巧与其到内网再一个一个 rpm 安装不如把下载好的目录做成一个本地 yum 源。在目标服务器上执行cd /data/docker-rpm createrepo .然后写一个 repo 文件/etc/yum.repos.d/docker-local.repo[docker-local] nameDocker Local Repo baseurlfile:///data/docker-rpm enabled1 gpgcheck0优先使用本地源之后yum install docker-ce就会直接解析本地 RPM 包的依赖关系非常省心。2.2 目标服务器导入与安装在内网服务器上解压 tar 包tar zxf docker-rpms.tar.gz -C /data如果刚才做了本地 yum 源直接执行yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin如果没有做本地源也可以用yum localinstall安装当前目录下的所有 rpm 包cd /data/docker-rpm yum localinstall -y ./*.rpm注意yum localinstall会检查当前目录下的依赖包如果所有依赖都齐全安装过程会很顺利。如果提示缺少依赖说明 repotrack 没有把所有依赖抓全需要回到有网机器重新下载缺失的包。安装完成后先不要急着启动把开机自启和基础配置做好systemctl daemon-reload systemctl enable docker systemctl start docker验证一下是否成功docker version能看到 Client 和 Server 两部分的版本信息就说明 Docker 引擎已经装好并能正常工作了。2.3 离线安装 docker-compose 的两种方式有了 Docker 引擎还差一个编排工具。docker-compose 的离线安装有两条路我分别说一下。第一种方式也是最推荐的方式直接下载二进制文件。在有网机器上打开 GitHub 的 docker/compose releases 页面根据版本下载docker-compose-linux-x86_64这个文件重命名为docker-compose传到内网服务器上放到/usr/local/bin/目录赋予执行权限mv docker-compose /usr/local/bin/docker-compose chmod x /usr/local/bin/docker-compose docker-compose version第二种方式是使用 Docker 官方插件版 compose也就是前面 repotrack 时一起下载的docker-compose-plugin包。安装这个 rpm 后系统里会多出docker compose命令注意中间没有横线。两种方式功能基本一致但插件版与 Docker CLI 集成更紧密后续升级也更方便。如果你两种都装了建议统一用docker compose命令避免混用。2.4 启动 Docker 服务与基础验证刚装完 Docker有几个基础配置建议直接做掉免得后续部署中间件踩坑。第一确认 Docker 存储驱动是 overlay2docker info | grep Storage Driver正常情况下输出应为overlay2。如果显示devicemapper说明系统内核较老或配置有问题建议升级系统后再操作。第二确认 Cgroup 驱动与 systemd 一致。如果你的宿主机是 systemd 管理的建议修改 Docker 的 daemon 配置mkdir -p /etc/docker cat /etc/docker/daemon.json EOF { exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } } EOF systemctl restart docker这段配置还顺带做了两件事限制单个容器日志文件大小避免日志无限增长把磁盘打满。这在生产环境里非常实用尤其是 MySQL、Nginx 这类日志量大的中间件。第三验证 Docker 能否正常拉取本地镜像。离线环境没有外网不能跑docker run hello-world但可以通过检查镜像列表来验证docker images此时应该是空的没关系下一步就是导入中间件镜像。3. 中间件镜像的离线获取与导入3.1 有网环境拉取镜像并打包在有网机器上先把需要的镜像全部拉下来。以最常用的三个组件为例docker pull mysql:8.0.33 docker pull redis:7.2-alpine docker pull nginx:1.24-alpine这里有两个选型经验尽量使用带-alpine标签的镜像。Alpine Linux 是一个非常精简的发行版构建出的镜像体积通常只有标准版的一半甚至更少。比如nginx:1.24大约 190MB而nginx:1.24-alpine只有约 45MB。在离线传输场景下体积小意味着更少的拷贝时间。固定镜像版本号不要用latest。离线环境里一旦装上了就很难更新如果用latest标签过几个月再想回滚到指定版本会非常痛苦。我对生产环境的习惯是精确到小版本如mysql:8.0.33而不是mysql:8.0。拉取完成后打包docker save -o middleware-images.tar mysql:8.0.33 redis:7.2-alpine nginx:1.24-alpine把多个镜像打包到一个 tar 文件里是允许的docker load的时候会全部恢复。如果你想把 Kafka、RabbitMQ 等组件一起打包直接追加镜像名即可docker save -o middleware-images.tar mysql:8.0.33 redis:7.2-alpine nginx:1.24-alpine rabbitmq:3.12-management打包完成后用ls -lh middleware-images.tar看一下体积做到心里有数。3.2 离线导入镜像的两种思路对比把 tar 包传到内网服务器后导入命令非常简单docker load -i middleware-images.tar执行完会看到每个镜像的加载日志。然后确认一下docker images如果镜像出现在列表里说明导入成功。这里我想重点强调一下docker save和docker export的区别因为我在实际工作中见过很多人把这两个命令混用导致导入后的镜像无法启动。docker save作用对象是镜像导出的是镜像本身及其所有历史层保留完整的构建信息、环境变量、入口命令。配合docker load使用恢复出来的镜像和原来完全一致。docker export作用对象是容器导出的是容器当前的文件系统快照。配合docker import导入后会丢失镜像的 CMD、ENTRYPOINT、ENV 等元数据而且所有层会合并成一层体积也会变大。对比项docker save loaddocker export import作用对象镜像容器是否保留历史层保留合并为单层CMD/ENTRYPOINT 等配置完整保留丢失适用场景离线迁移镜像容器文件系统备份结论很明确离线部署中间件一律用docker savedocker load。只有当你需要把某个容器当前的运行状态导出给别人排查才考虑docker export。3.3 架构匹配与镜像瘦身架构匹配是离线部署里最容易忽略的问题。很多开发者习惯在本地用 Docker Desktop 拉镜像、做验证如果本地电脑是 Apple Silicon 芯片默认拉取的是 ARM64 架构的镜像。把这些镜像 save 出来传到 x86_64 的服务器上 load虽然不会报错但运行时会直接报exec format error因为 CPU 指令集不兼容。解决方法是拉镜像时明确指定平台docker pull --platform linux/amd64 mysql:8.0.33如果你有网机器的架构本来就是 x86_64那不用特意指定默认就是 amd64。但如果你用的是 Apple Silicon 电脑这个--platform参数就非常重要了。至于镜像瘦身除了优先选择 alpine 版本还可以在制作自定义镜像时注意清理中间产物和多阶段构建的使用。不过对于本文的应用场景——直接把官方中间件镜像拉下来用——最重要的就是选对版本标签其他优化空间不大。4. 基于 docker-compose 批量部署中间件4.1 部署 MySQL 8.0镜像导入到内网后接下来就是编排部署。先在宿主机上规划好目录结构。我的习惯是统一放在/data/middleware下每个服务一个子目录mkdir -p /data/middleware/mysql/data mkdir -p /data/middleware/nginx/{conf,html,logs} mkdir -p /data/middleware/redis/data这样做的好处是所有中间件的数据、配置、日志都集中在同一棵目录树里备份和迁移时一目了然。然后写/data/middleware/docker-compose.yml。先给出 MySQL 的部分services: mysql: image: mysql:8.0.33 container_name: mysql8 restart: always environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: Root123456 MYSQL_DATABASE: app_db MYSQL_USER: app MYSQL_PASSWORD: App123456 command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci - --lower_case_table_names1 ports: - 3306:3306 volumes: - /data/middleware/mysql/data:/var/lib/mysql - /etc/localtime:/etc/localtime:ro networks: - middleware networks: middleware: driver: bridge几个参数分别说明MYSQL_ROOT_PASSWORDroot 账号密码。生产环境一定换成高强度密码。MYSQL_DATABASE/MYSQL_USER/MYSQL_PASSWORD容器首次启动时自动创建数据库和普通用户省去手动初始化。command中的--character-set-serverutf8mb4和--collation-serverutf8mb4_unicode_ci设置默认字符集为 utf8mb4这是避免中文乱码的关键。lower_case_table_names1表名不区分大小写。这个参数和 MySQL 8.0 的初始化有交互建议在首次启动前就加上。挂载/etc/localtime或设置TZ让容器时区和宿主机保持一致否则日志时间会比北京时间慢 8 小时。启动cd /data/middleware docker compose up -d查看状态docker compose ps第一次启动 MySQL 会对数据目录做初始化可能需要 20 到 30 秒稍等片刻再用客户端连接。4.2 部署 Redis 7 并配置主从Redis 的 compose 配置比 MySQL 简单多了。单机版redis: image: redis:7.2-alpine container_name: redis7 restart: always command: [redis-server, --requirepass, Redis123456, --appendonly, yes, --maxmemory, 256mb] ports: - 6379:6379 volumes: - /data/middleware/redis/data:/data networks: - middleware--requirepass设置访问密码--appendonly yes开启 AOF 持久化--maxmemory限制 Redis 最大内存。这些都是生产环境的基本配置。如果需要部署主从模式可以再加一个 slave 服务redis-slave: image: redis:7.2-alpine container_name: redis-slave restart: always depends_on: - redis command: [redis-server, --slaveof, redis, 6379, --requirepass, Redis123456, --masterauth, Redis123456] ports: - 6380:6379 volumes: - /data/middleware/redis-slave/data:/data networks: - middleware这里有三点要注意--slaveof redis 6379使用的是 compose 服务名redis而不是 IP。Docker 内置 DNS 会自动把服务名解析成对应容器的 IP所以主从之间可以通过服务名互通。主从都要设置相同的requirepass否则从节点连接主节点时会被拒绝。--masterauth是从节点连接主节点时用的认证密码必须和主节点的requirepass一致。启动后用redis-cli验证docker exec -it redis-slave redis-cli -a Redis123456 info replication输出里role:slavemaster_link_status:up就说明主从同步正常。4.3 部署 NginxNginx 的部署重点在于配置文件的挂载。先准备一个简单的宿主机配置文件/data/middleware/nginx/conf/default.confserver { listen 80; server_name _; location / { root /usr/share/nginx/html; index index.html; } location /api/ { proxy_pass http://你的业务服务IP:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }然后 compose 配置nginx: image: nginx:1.24-alpine container_name: nginx restart: always ports: - 80:80 - 443:443 volumes: - /data/middleware/nginx/conf:/etc/nginx/conf.d - /data/middleware/nginx/html:/usr/share/nginx/html - /data/middleware/nginx/logs:/var/log/nginx networks: - middlewareNginx 镜像默认会读取/etc/nginx/conf.d/下的所有.conf文件。把宿主机目录挂载进去后修改配置文件不需要重启容器直接执行docker exec nginx nginx -s reload这在调试阶段非常方便。注意不要把整个/etc/nginx目录都挂载出来那会覆盖镜像里默认的nginx.conf导致主配置文件里引用的变量和路径全部失效很容易把自己绕进去。4.4 消息中间件等更多组件的扩展部署MySQL、Redis、Nginx 是绝大多数业务系统最常用的三件套。实际工作中还会遇到消息中间件比如 RabbitMQ、Kafka部署思路完全一样。以 RabbitMQ 为例镜像自带 management 插件compose 配置rabbitmq: image: rabbitmq:3.12-management container_name: rabbitmq restart: always environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: Rabbit123456 ports: - 5672:5672 - 15672:15672 volumes: - /data/middleware/rabbitmq:/var/lib/rabbitmq networks: - middleware5672是 AMQP 协议端口15672是 Web 管理后台端口。启动后访问http://服务器IP:15672就能看到管理界面。Kafka 稍微复杂一点新版本支持 KRaft 模式不需要再单独部署 ZooKeeper。官方镜像apache/kafka:3.7.0内部已经包含了 broker 和 controller 的角色配置。离线拉取这个镜像时注意体积比前几个大不少save 出来的 tar 可能会超过 500MB拷盘时留足空间。总体上离线部署各种中间件的套路就是三板斧拉镜像、save、load然后写 compose 编排。每多部署一个组件只是多一个 compose service逻辑上没有额外复杂度。5. 常用 Docker 命令与运维管理技巧5.1 镜像与容器生命周期管理离线环境部署完日常运维主要靠下面这些命令# 查看运行中容器 docker ps # 查看所有容器包括已停止 docker ps -a # 停止 / 启动 / 重启容器 docker stop redis7 docker start redis7 docker restart redis7 # 进入容器内部 docker exec -it redis7 /bin/sh # 查看容器资源占用 docker stats # 查看容器详细信息 docker inspect mysql8这里提醒一下MySQL 镜像底层是 Ubuntu/Debian容器里的 shell 是/bin/bash而 Redis 的 alpine 版本里只有/bin/sh没有 bash。用docker exec时注意区分否则会报No such file or directory。5.2 日志查看、数据备份与恢复日志排障是运维里最频繁的操作基本命令docker logs -f --tail 200 mysql8-f表示持续跟踪输出--tail 200表示只显示最近 200 行。这个用法要看容器内服务是否在前台运行。如果中间件进程在容器里是后台启动的docker logs可能看不到日志这种情况直接看挂载目录里的日志文件更靠谱。数据备份也是刚需。比如备份 MySQL 数据最简单的方式是用docker exec调用 mysqldumpdocker exec mysql8 sh -c exec mysqldump -uroot -pRoot123456 app_db /data/backup/app_db_$(date %Y%m%d).sqlRedis 的 AOF 文件、RDB 快照可以直接在/data/middleware/redis/data目录下找到把整个目录拷贝走就是一份完整备份。Nginx 则主要备份配置目录/data/middleware/nginx/conf。5.3 镜像与容器导出方式对比表上面第 3 部分已经详细对比过save/load和export/import。这里再补一个完整表格方便以后查阅命令组合操作对象保留元数据保留层历史适用场景docker save docker load镜像完整保留保留镜像迁移、离线部署docker export docker import容器丢失 CMD 等合并单层容器文件系统快照docker commit容器会生成新镜像保留且新增一层保存容器修改为新镜像实战建议除非你有非常明确的需求否则永远不要在生产环境用docker commit和docker export。离线部署镜像的标准姿势永远是docker save之后传文件。6. 常见问题与排查技巧实录6.1 Docker 服务启动失败内网服务器安装完 Docker执行systemctl start docker报错这种情况很常见。先看具体原因journalctl -u docker -n 50根据我的经验报错原因主要集中在这几类旧版本 Docker 残留以前装过 docker 或 docker-latest没有卸载干净。解决办法是先把旧包全部移除再安装新版本yum remove docker docker-client docker-common docker-engine/var/lib/docker磁盘空间不足docker info会打印根目录使用情况如果可用空间不足需要清理磁盘或换到大分区。iptables / nftables 规则冲突某些系统加固脚本会预置防火墙规则导致 Docker 无法创建 NAT 规则。临时验证可以先停掉 firewalldsystemctl stop firewalld systemctl start docker如果确实是防火墙冲突建议调整防火墙规则而不是直接禁用具体规则按 Ubuntu 或 CentOS 的不同管理方式分别处理。6.2 容器启动后立即退出容器docker compose up -d后一秒钟就退出先不要急着改配置第一步永远是看日志docker logs mysql8常见原因大概有几个方向端口被占用宿主机上已经有一个 MySQL 在监听 3306新容器绑定不了端口。解决办法是停掉宿主机占用端口的服务或者修改 compose 里的映射端口比如3307:3306。数据目录权限不足MySQL 容器需要/var/lib/mysql目录可写。如果挂载的宿主机目录所有者不是容器内用户MySQL 初始化时会启动失败。解决办法是设置目录属主chown -R 999:999 /data/middleware/mysql/dataMySQL 容器内的用户 UID 是 999直接把宿主目录的属主改成 999 即可。配置文件语法错误Nginx 挂载的配置文件有问题容器也会启动失败。可以先在宿主机上验证配置docker run --rm -v /data/middleware/nginx/conf:/etc/nginx/conf.d:ro nginx:1.24-alpine nginx -t6.3 端口映射、防火墙与外部访问问题容器起来了但在其他机器上访问 3306 端口不通这种问题八成出在防火墙或安全组。CentOS 7 及以上默认使用 firewalld放行端口firewall-cmd --zonepublic --add-port3306/tcp --permanent firewall-cmd --reload如果服务器前面还有云安全组或硬件防火墙也需要在对应策略里放行端口。这个环节很容易被忽略因为容器本身没问题但外部流量到不了宿主机。另外还有一种情况容器里的服务监听的是 127.0.0.1而不是 0.0.0.0。Redis 如果配置了bind 127.0.0.1即使端口映射正确外部也连不上。Docker 官方镜像默认通常监听在所有接口但如果你用了自建镜像或自定义配置需要检查监听地址。6.4 离线部署场景下的其他高频问题最后集中回答几个离线场景下的高频问题。第一个docker load时报invalid tar format。这通常意味着 tar 包不是通过docker save生成的或者文件在拷贝过程中损坏。排查方式是检查文件大小是否和源文件一致以及重新用docker save再导一次。第二个导入镜像后docker images里看不到。确认docker load的日志里有没有Loaded image字样并检查镜像名和标签是否和 compose 里写的完全一致包括版本号。第三个Windows 上装 Docker Desktop 时提示virtualization support not detected。这是本机虚拟化没有被开启的问题。需要进 BIOS 开启 Intel VT-x 或 AMD-VWindows 里还要确认 Hyper-V 和 Windows Hypervisor Platform 组件已启用。这个场景和 Linux 服务器离线部署不是一回事但既然很多朋友会先在本地用 Docker Desktop 打包镜像顺便提一下。第四个离线服务器上执行docker compose up -d时提示pull access denied。这通常是镜像没有被正确 load 进当前这台机器。Docker Compose 发现本地没有指定镜像时默认会尝试从远程仓库拉取。离线环境网络不通就报了这个错。解决方法很直接先docker images确认镜像在本地再执行docker compose up。第五个容器内时区不对。默认情况下很多官方镜像的时区是 UTC直接看日志时间会和北京时间差 8 小时。解决办法两种要么在 compose 里设置环境变量TZ: Asia/Shanghai要么挂载宿主机的/etc/localtime文件进容器。MySQL、Redis、Nginx 都适用。第六个内网无法访问 Docker Hub有网机器也不能安装 Docker。这种情况下可以尝试在能联网的浏览器或下载工具里直接下载镜像的离线包文件或者使用部分云厂商提供的镜像加速站点把镜像文件下载回来。如果确实没有任何渠道拿到镜像包那离线部署 Docker 这条路就走不通只能老老实实回到源码编译的老路上。最后再分享一个我自己的习惯每次离线部署前我会把整个流程做成一个脚本从解压 RPM 包、配置本地 yum 源到 load 镜像、执行 compose up一条命令完成。这样不仅自己下次省事团队里的新人也能照着跑。另外生产环境如果节点比较多强烈建议在内网搭一个私有镜像仓库把离线下来的镜像直接 push 进去后续所有服务器都从内网仓库拉取效率和手动传 tar 包完全不在一个量级。这篇文章里的方法是我在实际环境中反复验证过的希望能帮你在离线部署的路上少走几步弯路。
返回列表