ARTICLE DETAIL

资讯详情

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

内网离线环境Docker部署实战:中间件迁移与踩坑指南

内网离线环境Docker部署实战:中间件迁移与踩坑指南 做交付这行久了你会发现一个铁律越是重要的环境越是没外网。内网隔离区、生产区、机房机柜全是一台台裸机摆在那yum源指向本地磁盘docker hub更是想都别想。这时候客户却要求你把mysql、redis、nginx这些中间件全部跑起来还得稳定、还得能扛得住审计。所以我把在x86架构Linux服务器上做docker离线安装、以及后续离线部署各类中间件的完整流程整理了一遍包括踩过的坑、备好的脚本、现场排查的思路给同样做交付和运维的同学一份能直接参考的实战记录。这篇文章不是什么教科书式教程就是实际项目里反复用过、验证过的流程。内容覆盖离线安装包怎么准备、docker本体怎么装、中间件镜像怎么离线搬运、mysql/redis/nginx/postgres/rabbitmq/elasticsearch怎么一个个跑起来最后是现场最容易踩的坑和排查手段。不管你是刚接触docker的新人还是被内网环境折磨过的老手应该都能从中拿到点能直接用的东西。1. 动手之前先搞清楚两件事1.1 为什么离线安装这个需求绕不开先说结论离线装docker不是冷门需求是很多交付现场的基本功。金融、电力、制造、政企这些行业的核心业务区绝大部分都是物理隔离或者逻辑隔离的网络。就算不是完全断网很多公司内部的yum源、docker镜像源也会因为安全策略不让随便访问。加上现在不少项目交付环境就是“裸机加光盘”操作系统装完就是最小安装连yum源都指向本地这时候你如果没提前把离线包准备好到了现场就是寸步难行。开发环境里大家习惯了在线安装一条命令解决所有问题但到了内网就完全另一回事。在线装docker依赖关系yum自动帮你搞定离线装你必须自己把依赖备齐。在线拉镜像docker pull一瞬间的事离线拉镜像你得先把镜像打包带走到了内网再load进去。这些步骤不是难而是很多人在开发环境里根本没接触过一旦现场断网就卡住。所以这篇文章不光是给运维看的也是给那些负责交付、实施、现场调试的开发同学看的。提前把离线安装这套流程跑一遍会让你在现场从容很多。1.2 x86系统架构必须一开始就确认清楚标题里专门强调x86系统架构这个事真的很关键。服务器CPU架构主要就是x86_64Intel/AMD和aarch64ARM两大类不同架构对应的docker安装包、中间件镜像都是不通用的。你要是拿一台ARM服务器的rpm包放到x86机器上装大概率报wrong architecture或者一堆依赖冲突镜像就更夸张拉了个ARM架构的镜像塞到x86机器上跑docker直接报exec format error根本起不来。所以动手第一步在目标机器上确认架构uname -m # x86_64 就是x86架构aarch64 就是ARM架构 lscpu | grep Architecture确认机器是x86_64之后再顺手确认操作系统发行版和版本。CentOS 7、CentOS 8、Ubuntu 20.04、统信、麒麟这些系统包管理方式都不一样rpm包、deb包、安装源都不通用。这一步没做后面装到一半发现依赖对不上回头排查会非常痛苦。提示我在现场的习惯是先把uname -m和cat /etc/os-release的输出存到交付文档里。别小看这一步后面所有安装包选择、镜像选择都以此为依据两条命令能避免一半以上的低级错误。2. docker本体离线安装的完整套路2.1 先在联网机器上把rpm包备齐离线安装的前提是你有一台能访问外网的“搬运工”机器这台机器的操作系统版本要尽量和目标机器一致至少大版本一致。目标机是CentOS 7.9你就找一台CentOS 7.x的机器下载目标机是Ubuntu就换成Ubuntu来下载deb包。跨大版本拿包依赖关系很容易翻车。在CentOS/RHEL系机器上推荐直接用yum的downloadonly功能把docker全家桶连同依赖一起拉下来。几步搞定# 安装yum-utils提供yumdownloader工具 yum install -y yum-utils # 添加docker官方源 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 下载docker本体和相关插件到当前目录下的offline_docker_rpm目录 mkdir -p offline_docker_rpm cd offline_docker_rpm yumdownloader --resolve docker-ce docker-ce-cli containerd.io docker-compose-plugin关键点是那行--resolve它会把依赖的包也一并下载不然你拷到内网机器上安装时就会这个缺那个缺。下载完检查一下目录里的包一般会看到container-selinux、libcgroup、iptables这些就是这些常被忽略的系统依赖。如果你从docker官网直接下载rpm包也可以但要知道官网只提供docker-ce等主包的rpm很多系统依赖还是要靠系统源补齐。所以我的推荐始终是yumdownloader --resolve一步到位别自己去手工爬依赖。另外提一句如果项目里还在用旧的docker-compose独立命令而不是docker compose子命令那还需要单独下载一个docker-compose-linux-x86_64二进制文件放到/usr/local/bin/docker-compose并加上执行权限。很多老脚本里写的是docker-compose没有这个文件会直接command not found。2.2 拷贝到内网机器后的安装步骤把离线rpm目录用U盘、内网FTP或者scp拷到目标机器上然后安装cd offline_docker_rpm yum localinstall *.rpm -y如果yum localinstall因为个别包冲突卡住可以用rpm手动安装顺序是先依赖后主包rpm -Uvh container-selinux*.rpm rpm -Uvh containerd.io*.rpm rpm -Uvh docker-ce-cli*.rpm rpm -Uvh docker-ce*.rpm rpm -Uvh docker-compose-plugin*.rpm装完启动并设置开机自启systemctl daemon-reload systemctl enable docker systemctl start docker启动之后第一件事不是急着跑容器而是看docker能不能正常对外提供接口docker version docker infodocker version能看到client和server两部分如果server部分不显示说明docker daemon没起来这时候马上去看日志journalctl -u docker --no-pager -n 50确认原因之后再继续。docker info里重点看存储驱动正常是overlay2如果显示vfs说明底层文件系统不支持d_type性能会差很多后面部署中间件时容易莫名其妙慢。我在实际项目里还遇到一种情况yum安装时会顺手把系统里的iptables、nftables升级掉导致防火墙规则混乱。所以装docker前建议先把firewalld状态确认一遍systemctl status firewalld。如果本身是开着的装完docker之后要把docker相关端口加到firewalld规则里或者确认目标网段能访问否则容器端口映射虽然通了外面访问还是会被拦。2.3 备选方案静态二进制包如果目标机器不是CentOS系、或者你实在不想折腾rpm依赖还有一种单文件方案从docker官方github releases下载docker-xx.x.x.tgz静态包。这个包解压后直接就是可执行文件不依赖额外系统库拷到内网机器就能用。tar xzf docker-20.10.17.tgz cp docker/* /usr/bin/但静态包有个缺点不带systemd服务文件需要自己写docker.service和docker.socket。我贴一个常用的简化版docker.service核心内容[Unit] DescriptionDocker Application Container Engine Afternetwork-online.target [Service] Typenotify ExecStart/usr/bin/dockerd ExecReload/bin/kill -s HUP $MAINPID LimitNOFILE1048576 LimitNPROCinfinity Restarton-failure [Install] WantedBymulti-user.target写完这个文件放到/etc/systemd/system/然后执行systemctl daemon-reload、systemctl enable docker、systemctl start docker。静态包方案适合紧急救援场景比如现场机器连yum都没有平时不做首选。注意静态包版本更新快但如果你要配合containerd和runc用记得把archive里自带的containerd和runc也一起拷到/usr/bin。我遇到过只拷了docker主程序、结果dockerd起来后找不到containerd的情况排查了半天。3. 中间件镜像的离线迁移与分发3.1 save/load二步法最稳妥也最常用安装包搞定之后真正的重头戏是中间件镜像怎么带到内网。docker本身不会凭空变出镜像离线环境下所有镜像都必须在外网机器上先拉好再打包搬运。最核心的就是两条命令docker save和docker load。在联网机器上操作# 拉取需要的镜像 docker pull mysql:8.0 docker pull redis:7.2 docker pull nginx:1.25 docker pull postgres:16 docker pull rabbitmq:3-management # 打包成tar文件 docker save -o images/mysql-8.0.tar mysql:8.0 docker save -o images/redis-7.2.tar redis:7.2 docker save -o images/nginx-1.25.tar nginx:1.25这其实可以写成脚本批量做我贴一个现场用的脚本片段按镜像列表逐个拉取和导出while read image tag; do docker pull ${image}:${tag} if [ $? -eq 0 ]; then docker save -o ${image//\//_}-${tag}.tar ${image}:${tag} fi done images.list拷到内网机器之后逐条load即可docker load -i mysql-8.0.tar docker load -i redis-7.2.tar docker load -i nginx-1.25.tarload完用docker images确认镜像已经出现在本机镜像列表里之后就能直接拿它启动容器了。3.2 save/load与export/import两个必须分清楚很多新手会把docker save和docker export搞混。save是把镜像保存成tar强调镜像的“元数据层”export则是把一个容器的文件系统导出成tar强调“运行态”。区别直接列个表对比项docker save/loaddocker export/import操作对象镜像image容器container是否包含元数据/标签包含load后镜像名和tag完整不包含import后镜像可能变成是否包含历史层包含不包含层会被合并适用场景离线迁移镜像临时备份/迁移容器文件离线部署中间件老老实实用save/load别碰export/import。我见过有人图省事用export导出一个容器再import结果镜像名丢了、启动参数没了、环境变量被抹掉最后容器虽然起来了数据却连不上排查半个多小时才发现是导出方式不对。另外save出来的tar一般比较大跨机器拷贝时建议压缩。可以用管道组合docker save mysql:8.0 | gzip mysql-8.0.tar.gz # 内网机器上还原 gunzip -c mysql-8.0.tar.gz | docker load实测mysql 8.0的镜像不压缩大约500到600MBgzip之后能压到200多MB传输时间省一大截。如果内网带宽特别窄还可以用pigz并行压缩速度更快。3.3 进阶方案内网自建镜像仓库如果只是单台机器部署save/load完全够用。但如果是一整个测试环境或者多台机器都要跑业务就不用一台一台拷tar了更好的做法是内网里搭一个私有镜像仓库。思路不复杂先在联网机器上拉registry镜像并save再到内网目标机上load并启动registry容器# 联网机器上 docker pull registry:2 docker save -o registry2.tar registry:2 # 内网任意一台能跑docker的机器上 docker load -i registry2.tar docker run -d --name registry --restartunless-stopped \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ registry:2然后在联网机器或者内网其他机器上把已经load进来的镜像重新tag成内网registry的地址再pushdocker tag mysql:8.0 10.10.x.x:5000/mysql:8.0 docker push 10.10.x.x:5000/mysql:8.0内网其他机器从私有仓库拉取时需要配置/etc/docker/daemon.json的insecure-registries因为自建registry默认是httpdocker客户端会拒绝非https的私有仓库{ insecure-registries: [10.10.x.x:5000] }配置完重启docker服务systemctl restart docker。有了私库之后任何一台内网机器只需要docker pull 10.10.x.x:5000/mysql:8.0就能完成镜像分发比反复拷贝tar文件省事太多。这也是我推荐给有批量交付需求团队的首选方案。4. 常用中间件的离线部署实操4.1 MySQL 8.xMySQL是离线部署里遇到最多的中间件没有之一。镜像已经load进内网机器后启动命令如下docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDRoot2024 \ -e MYSQL_DATABASEappdb \ -e MYSQL_USERappuser \ -e MYSQL_PASSWORDApp2024 \ -v /data/mysql:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ -e TZAsia/Shanghai \ --restart unless-stopped \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci几个参数拆开讲一下。MYSQL_ROOT_PASSWORD、MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD是镜像提供的初始化环境变量首次初始化时会自动建库建账号-v /data/mysql:/var/lib/mysql把数据目录映射到宿主机不然容器一删数据全没--character-set-server和--collation-server放在镜像名后面是因为它们要传给mysqld进程属于容器启动参数不是docker run的参数位置不能放错。数据目录权限是我踩过的坑。宿主机的/data/mysql如果不存在docker会用root自动创建但如果这个目录已经存在且属主不是uid 999mysql容器内默认用户容器启动就会因为权限不足直接abort。解决方法是提前执行chown -R 999:999 /data/mysql或者干脆让这个目录不存在交给docker初始化。另外MySQL 8.0默认root只允许localhost登录业务要远程连的话需要进容器授权docker exec -it mysql8 mysql -uroot -p # 在mysql命令行里执行 CREATE USER appuser% IDENTIFIED BY App2024; GRANT ALL PRIVILEGES ON *.* TO appuser%; FLUSH PRIVILEGES;4.2 Redis 7.xRedis的部署相对MySQL简单不少不涉及那么多初始化配置。如果只是单机使用一条命令就够了docker run -d --name redis7 \ -p 6379:6379 \ -v /data/redis:/data \ -e TZAsia/Shanghai \ --restart unless-stopped \ redis:7.2 \ redis-server --appendonly yes --requirepass Redis2024--appendonly yes开启AOF持久化Redis写命令会追加到appendonly.aof文件容器重启不丢数据--requirepass设置访问密码。数据目录/data/redis映射到容器里的/data这是Redis默认工作目录AOF和RDB文件都会落在这里。做主从复制的话从库启动命令加一行复制参数docker run -d --name redis7-slave \ -p 6380:6379 \ -v /data/redis-slave:/data \ --restart unless-stopped \ redis:7.2 \ redis-server --appendonly yes --requirepass Redis2024 \ --replicaof 主库IP 6379 \ --masterauth Redis2024主从之间如果设置了密码从库必须用--masterauth告诉它主库密码否则复制连接会被拒。还有一点容易忽略--replicaof后面的主库IP要用内网实际可达的地址别用容器IP或者localhost否则从库连不上。从库日志报MASTER aborted connection的时候十有八九就是主库IP写错了。4.3 NginxNginx离线部署同样不复杂核心是把配置文件、静态资源、日志目录都挂载到宿主机这样不需要进容器也能改配置、看日志mkdir -p /data/nginx/{conf.d,html,logs} docker run -d --name nginx \ -p 80:80 -p 443:443 \ -v /data/nginx/conf.d:/etc/nginx/conf.d \ -v /data/nginx/html:/usr/share/nginx/html \ -v /data/nginx/logs:/var/log/nginx \ -e TZAsia/Shanghai \ --restart unless-stopped \ nginx:1.25在/data/nginx/conf.d下新建一个server配置比如default.confnginx启动时自动加载。挂载进去的配置文件如果权限过严nginx worker进程读取时可能报permission denied。我一般把整个/data/nginx目录属主设成root配置文件权限644静态资源目录权限755。另一个坑是nginx容器默认日志会输出到stdout如果不想看JSON格式的docker日志可以把access_log和error_log指到挂载出来的/var/log/nginx下面日志排查方便很多。改完配置文件后执行docker exec nginx nginx -t检查语法再docker exec nginx nginx -s reload。这个习惯帮我避免了不少把nginx配置写错导致整站502的情况。4.4 PostgreSQLPostgreSQL部署跟MySQL思路一致环境变量略有区别docker run -d --name pg16 \ -p 5432:5432 \ -e POSTGRES_USERpostgres \ -e POSTGRES_PASSWORDPg2024 \ -e POSTGRES_DBappdb \ -v /data/pgsql:/var/lib/postgresql/data \ -e TZAsia/Shanghai \ --restart unless-stopped \ postgres:16注意PG16官方镜像里/var/lib/postgresql/data这个路径和更早版本有差异如果照搬网上老教程用/var/lib/postgresql/data/pgdata可能启动报错。我实测16版本用宿主机目录直接挂载到/var/lib/postgresql/data是可以正常初始化的。如果挂载目录报权限错同样是chown -R 999:999 /data/pgsqlPG容器默认用户uid也是999。4.5 RabbitMQRabbitMQ镜像分带管理界面和不带管理界面两种离线部署建议直接拉带management的版本docker run -d --name rabbitmq \ -p 5672:5672 -p 15672:15672 \ -e RABBITMQ_DEFAULT_USERadmin \ -e RABBITMQ_DEFAULT_PASSRabbit2024 \ -v /data/rabbitmq:/var/lib/rabbitmq \ -e TZAsia/Shanghai \ --restart unless-stopped \ rabbitmq:3-management5672是AMQP协议端口15672是Web管理界面端口。默认的guest/guest账号只允许本地访问所以要么用环境变量创建新账号要么进去rabbitmqctl添加用户。我建议第一种方式因为环境变量在容器初始化时自动建好省得后面再授权。4.6 Elasticsearch如果项目里要用日志检索ES也是离线部署常客。ES有两个特别容易踩的坑第一宿主机vm.max_map_count必须调大不然ES直接启动失败第二单机场景必须指定单节点模式否则会不断尝试加入集群然后退出。先执行两条宿主机设置sysctl -w vm.max_map_count262144 echo vm.max_map_count262144 /etc/sysctl.conf再启动容器docker run -d --name es8 \ -p 9200:9200 -p 9300:9300 \ -e discovery.typesingle-node \ -e ES_JAVA_OPTS-Xms512m -Xmx512m \ -e TZAsia/Shanghai \ -v /data/elasticsearch:/usr/share/elasticsearch/data \ --restart unless-stopped \ docker.elastic.co/elasticsearch/elasticsearch:8.11.3ES内存参数ES_JAVA_OPTS里的Xms和Xmx建议设为相同值比如512m避免JVM动态扩容造成性能抖动。挂载目录/data/elasticsearch的属主需要uid 1000否则ES启动会报I/O错误。还有一点ES 8默认开启安全认证如果不需要启动时加-xpack.security.enabledfalse。这个参数是否关闭要结合项目实际不要盲目照抄。5. 离线部署中踩过的坑与排查实录5.1 启动docker就失败先查这三处离线装完dockersystemctl start docker如果起不来我遇到最多的原因就三个。第一个是依赖包没装全尤其container-selinux和libcgroup这种隐藏依赖。报错通常是Failed to start docker.service: Unit not found或者dockerd启动时缺so文件。解决办法是回头用yum localinstall *.rpm把所有下载的包一起装别只装docker-ce主包。第二个是iptables/nftables冲突。CentOS 7上iptables服务和docker自带的iptables规则容易互相干扰报错一般是Failed to program FILTER chain: iptables failed。如果确定不会和现有规则冲突可以在/etc/docker/daemon.json里把iptables设为false但不建议长期这么干因为会失去docker的端口转发能力。第三个是内核模块缺失overlay存储驱动加载不了报错类似Error starting daemon: error initializing graphdriver: overlay2 is not supported。检查文件系统是不是xfs/ext4以及内核模块是否加载cat /proc/filesystems | grep overlay modprobe overlayCentOS 7的老内核有时候需要升级才能支持overlay2实在不行只能退化成vfs驱动性能影响明显不建议生产环境这么跑。5.2 镜像load之后的怪现象docker load -i xxx.tar执行完docker images里要么看不到要么镜像名变成none:none这种问题我遇到过好几次。排查下来基本是两个原因。第一个确认源tar文件本身没问题。如果torrent是从同事那里拷来的有可能对方其实是用docker export导出的容器不是镜像或者tar下了一半不全gzip解压时直接报unexpected EOF。第二个镜像名带私有仓库前缀。在联网机器上save时如果镜像名本来就是10.10.x.x:5000/mysql:8.0这种带仓库地址的load到内网后镜像名自然就是那一长串。部署时不仔细看镜像名docker run会提示Unable to find image找不到镜像。这不是bug重新tag一下就好docker tag 10.10.x.x:5000/mysql:8.0 mysql:8.0另外如果发现load进去的镜像比较大但docker images里没显示先执行docker system df看看存储空间是不是被镜像层占满了有时docker目录所在分区满了load会部分失败。5.3 运行期最容易被忽视的配置问题容器好不容易跑起来了业务却连不上这类问题最浪费时间。我把高频问题整理成速查表基本覆盖90%的现场情况现象最可能的根因处理办法容器内能连宿主机不能连dockerd iptables规则被清或firewalld拦截systemctl restart docker恢复默认规则检查firewalld端口放行宿主机能连外部机器不能连安全组/防火墙只放行了部分网段检查云安全组或机房防火墙放行对应端口MySQL远程连不上root账号默认localhost权限创建%账号并授权Redis连上后认证失败requirepass与客户端密码不一致核对docker run时--requirepass参数和客户端配置容器内时间差8小时未挂载localtime和设置TZ启动参数加-e TZAsia/Shanghai和-v /etc/localtime:/etc/localtime:ro容器重启后数据丢失未挂载数据卷或容器被删除重新规划-v挂载目录使用--restart unless-stoppedES启动反复退出vm.max_map_count未调大或堆内存不足sysctl设置并确认ES_JAVA_OPTS合理Nginx反代502上游服务地址写错或容器网络问题docker exec进去curl上游地址确认连通性这里单独说下docker容器的重启策略。我见过同事用--restart always如果容器因代码问题反复崩溃docker会一直重启它日志刷得飞快。生产环境更推荐--restart unless-stopped只有手动stop的容器才不会被拉起来崩溃自动重启依然有效。除非明确知道这个容器需要无限自愈否则不要盲目用always。5.4 一条龙脚本化的建议离线部署这件事做一次是学习做三次就该思考怎么脚本化了。我现在的做法是把整个流程固化成三个脚本一个在联网机器上跑prepare.sh负责yumdownloader下载rpm包和docker save导出所有镜像tar一个在目标机器上跑install.sh负责rpm安装、systemd启动、配置daemon.json还有一个deploy.sh按配置文件里的中间件清单逐个docker run启动容器。这套东西的好处是换了项目之后只要改改images.list和config.env整个离线交付一天就能跑完。镜像版本、端口、密码这些都集中在配置里别人接手也容易看懂。我强烈建议做交付的团队都这么搞而不是每次在现场一条条手敲docker run。最后再多说一句离线场景下的docker部署核心思路永远是“提前准备、统一入口、反复演练”。你提前把rpm包、镜像tar、配置脚本都备好在联网测试环境先跑通一遍到内网现场就是纯执行了。这也是我把这套流程沉淀成文档和脚本之后交付效率明显提升的最大原因。
返回列表