ARTICLE DETAIL

资讯详情

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

Docker容器导出导入实战:Hadoop环境快速迁移与复用

Docker容器导出导入实战:Hadoop环境快速迁移与复用 这里直接进入正题。关于“Docker部署Hadoop”系列前几篇文章已经把镜像拉取、容器创建、伪分布式环境调通这些步骤都走完了但这套环境基本还停留在自己机器上。不少人在这一步卡住测试环境里一切正常换一台电脑或者想把环境提交给同事做二次开发就只能从头再装一遍JDK、再配一遍SSH、再格式化一次NameNode浪费时间不说过程中还容易因为版本差异搞出各种莫名其妙的问题。这篇第四篇讲的就是怎么把已经调好的Hadoop容器打包带走——容器导出导入以及导入之后怎么快速恢复使用。对整个“Docker部署Hadoop”系列来说这一步补齐了环境迁移的最后一环让你不再被绑死在一台机器上。1. 为什么要把Hadoop容器导出导入迁移与复用场景1.1 什么时候用得着“导出导入”这套操作很多人觉得容器这东西直接用Dockerfile重新build一个不就完了为什么还要折腾导出导入我最早也有这个疑惑直到自己做了一次环境交接才明白。实际工作中遇到最多的几种场景是下面这些。第一类是环境迁移。你在自己的笔记本上把Hadoop伪分布式环境调好了但公司给你换了台新电脑或者你想把活儿挪到一台性能更好的服务器上继续跑。重新搭一遍意味着要把CentOS/Ubuntu基础镜像、JDK版本、Hadoop发行包、一堆配置文件全部重来快则半小时慢则半天而Docker导出导入只需要一条命令加一个tar包。第二类是团队协作和教学。我见过不少学校实验课、培训机构老师在一台机器上配好Hadoop环境然后要把这套环境分发给几十个学生。如果让每个学生都从零开始配光帮他们排查环境变量就得累死。把配置好的容器导出成镜像文件学生拿过去直接导入就能用效率完全不是一个量级。第三类是备份和时间回溯。容器用久了配置文件可能会被改乱HDFS里可能存了一些实验数据。隔一段时间把容器导出一次相当于给整个环境做了一个快照。后面万一改坏了直接导入之前的备份文件几分钟就能回到正常状态比在容器里一点一点排查省心太多。第四类是离线环境部署。有些机房内网环境没法直接访问Docker Hub这时候在能联网的机器上导出镜像或容器文件拷贝到内网机器上导入是绕过网络限制最实用的办法。1.2 导出导入 vs 重新搭建算笔时间账我拿自己常用的Hadoop伪分布式环境做过一次对比。从拉取基础镜像开始装JDK、传Hadoop安装包、改配置文件、配置SSH免密、启动NameNode和DataNode、跑通WordCount示例一套流程熟练工做下来也得20到30分钟。中间但凡遇到网络波动下载慢一点或者某个配置文件写错一个路径时间直接奔着一个小时去。而用Docker导出导入的方式导出过程基本就是几秒钟到几十秒的事主要看容器里数据量的大小。导入再加启动容器两分钟以内搞定。也就是说只要你的环境已经配置好一次后续复制这套环境的边际成本几乎为零。尤其当你需要同时起三台机器做集群实验的时候手动搭三遍环境简直是灾难导出镜像再批量导入才是正路。1.3 先搞清楚导出的是“容器”还是“镜像”动手之前有一个基本概念必须理清不然很容易在后面的操作中产生误解。Docker里有两种导出方式一种是docker export另一种是docker save它们的操作对象、产物格式、适用场景都不一样。docker export针对的是容器它把容器当前的文件系统整体打包成一个tar包但不会保留镜像的分层结构、历史命令和元数据。导入的时候需要用docker import导入结果是一个新的镜像原有容器的端口映射、容器名、网络配置统统不会保留。docker save针对的是镜像它把镜像的所有分层、标签、元数据一起打包保存导入的时候用docker load导入结果仍然是镜像可以最大程度还原镜像的原始信息。用一句话总结如果你要的是“整个容器的运行状态快照”用export如果你要的是“一个完整可追溯、带历史信息的镜像”用save。在Hadoop场景下两种方式都能用但实际踩坑点差别很大下面会细说。2. 导出前的准备先检查这些细节别等导出完才后悔2.1 确认容器当前状态这一步看着简单但很多人忽略了一个关键前提容器必须处于运行中或者至少是已创建状态导出的文件系统才完整。如果你已经docker rm了容器那就没法导出容器了这种情况只能去导出对应的镜像。实际操作时我习惯先执行一条命令看一眼当前容器列表docker ps -a输出里能看到所有容器的ID、名称、状态、端口映射信息。对于Hadoop容器我重点关注两件事容器名是什么、端口映射是否正常。比如一个典型的Hadoop伪分布式容器可能会把NameNode的9870端口Hadoop 3.x或50070端口Hadoop 2.x映射到宿主机上这些信息在导出后需要记下来因为后面导入新容器时要手动重新指定。另一个容易忽略的点是导出前最好确认一下容器里的服务进程状态。比如HDFS的NameNode、DataNode是否正常运行YARN的ResourceManager是否活着。你可以进容器看一眼docker exec -it hadoop-test bash jps确保关键进程都在再执行导出。虽然导出过程中服务进程有没有在跑理论上不影响文件系统的完整性但拿到新机器上恢复之后如果发现HDFS元数据有问题排查起来会比较痛苦。导出前先确认一次状态相当于提前排掉一个隐患。2.2 检查数据放在哪里容器层还是Volume这是我在前面几篇文章里反复强调的点也是导出导入操作里最容易踩的一个大坑。Hadoop容器在运行过程中会产生大量数据包括NameNode的元数据在dfs/name目录下、DataNode的数据块在dfs/data目录下、日志文件等。这些数据如果直接写在容器的可写层里那么docker export导出的是包含这些数据的完整文件系统导出包会比较大但恢复之后数据直接可用。如果这些数据是挂载在Volume或者宿主机目录上的比如启动容器时指定了-v /host/data:/opt/hadoop/data那docker export导出的是不包含挂载卷里的内容的。这个坑我踩过一次。有一次帮同事导出一套已经跑了一段时间的Hadoop环境导出完之后在另一台机器上导入、启动容器一看NameNode起不来报错说元数据目录是空的。排查了半天才发现他当时把HDFS的数据目录挂载到了宿主机路径上导出tar包的时候这些数据根本不在里面。所以导出前先确认一下你的容器启动命令里有没有挂载Volumedocker inspect hadoop-test看Mounts部分。如果Source和Destination里挂了数据目录你要么把宿主机上对应的目录一起拷贝走要么在导出前把HDFS的数据重新落到容器内部目录里比如把core-site.xml里的数据目录改回容器内路径再执行导出。2.3 清理临时文件和日志缩小导出体积容器用久了里面会积累不少没用的东西包括系统包管理器缓存、Hadoop运行日志、临时文件等。这些文件对恢复没有帮助但会白白增加导出包的体积。尤其当你的tar包要通过U盘或者网盘传输时动辄几个GB的文件传起来非常痛苦。我习惯在导出前做几个简单的清理动作。先清掉包管理器的缓存docker exec -it hadoop-test bash yum clean all # CentOS系 # 或者 apt-get clean # Ubuntu系然后是Hadoop的日志目录。Hadoop运行一段时间后$HADOOP_HOME/logs下面会有大量日志文件这些日志对迁移没有意义可以放心清理docker exec -it hadoop-test bash rm -rf $HADOOP_HOME/logs/*如果你在容器里装过一些临时下载的安装包比如从宿主机拷贝进去的Hadoop tar包解压完之后也可以删掉省出几百MB甚至上GB的空间。另外提醒一下清理操作和导出操作之间最好隔一小会儿让文件系统的状态稳定下来再执行导出。不要清理完立刻导出也不要在容器内有大量数据写入的时候导出否则导出的文件系统可能处于一种“写了一半”的状态。3. 两种导出方式深度对比docker export 与 docker save 怎么选3.1 原理层面一个打包文件系统一个打包镜像分层docker export的原理比较直接它把容器整个文件系统看作一棵目录树然后把这棵目录树的所有文件和目录打包成一个tar归档文件。它不关心这个容器是怎么构建出来的不保留镜像的历史分层也不保留容器启动时的配置信息。一句话它打的是“当前这一刻的实物快照”。docker save的原理则完全不一样。它把镜像的所有分层一层一层地打包每一层对应的内容都保留下来同时还包括镜像的元数据、标签、环境变量、默认CMD等信息。导入的时候Docker能够把这些层重新组装成一个完整的镜像docker history能看到这个镜像是怎么一步步构建出来的。实际使用中这个区别带来一个直接后果docker save导出的包往往比docker export更大因为它包含了多个分层的内容而且同一份文件在不同层里可能重复出现。但它的优势也很明显镜像的历史信息完整后面基于这个镜像再去二次构建Dockerfile基础层是可以被Docker缓存复用的。3.2 使用场景Hadoop容器迁移更适合哪种如果是针对Hadoop这种需要保留运行数据的场景我的建议是如果你希望迁移后HDFS数据、NameNode元数据、配置修改全部保留并且不介意丢失镜像构建历史那么用docker export导入后得到的镜像能最快恢复到可用状态。如果你更在乎的是镜像的可复现性希望迁移后的环境能基于这个镜像继续打标签、继续改Dockerfile迭代那么用docker save更合适。不过在实际操作中我还有一种组合用法这里分享给你先用docker export导出一份“带数据版本”的容器快照用于日常迁移和备份再用docker commit把当前容器转换成镜像然后用docker save导出一份“纯环境版本”的镜像文件用于后续二次开发和镜像分发。两个包各司其职应对不同需求。3.3 再补一个易混淆点docker commit 和 docker export 的关系docker commit也经常和这两个命令放在一起比较。它同样是把容器转换成镜像但转换过程是在Docker内部完成的产物直接变成一个新的镜像而不是一个tar文件。如果要用docker commit迁移环境通常还要配合docker save把生成的镜像再导出成文件。三者的关系可以这样理解docker export不生成镜像只生成文件系统快照docker commit生成镜像但不生成文件docker save把镜像打包成文件。迁移到另一台机器时最终面向的都是tar文件所以export配上import是一条路径commit配上save配上load是另一条路径。两条路径都能走通但效果和侧重点区别很大。4. 实操把Hadoop容器导出成可传输的tar包4.1 用 docker export 导出容器快照先演示最简单、最常用的一种方式。假设当前环境里有一个正在运行的Hadoop容器名字叫hadoop-test执行下面的命令把它导出docker export hadoop-test -o hadoop-container.tar或者用另一种写法docker export hadoop-test hadoop-container.tar两种写法等价。-o参数指定输出文件的路径和名字不写的话默认输出到标准输出终端里会直接涌出一堆二进制内容所以生产环境里-o是更推荐的写法。导出完成后在宿主机上确认一下文件信息ls -lh hadoop-container.tar正常情况下你会看到几GB甚至几十GB的文件具体取决于容器里Java、Hadoop安装目录的大小以及HDFS数据积累了多少。如果文件大小只有几百MB就要回头检查一下是不是数据目录挂在Volume里没打进去了。4.2 用 gzip 压缩导出包传输更省力tar包不压缩的话传输起来会比较慢尤其是要拷到远程服务器时。我一般导出后顺手压缩一下一条命令搞定docker export hadoop-test | gzip hadoop-container.tar.gzLinux下传输压缩包比传输原始tar包快不少特别是在网络环境一般的情况下体感很明显。唯一的代价是导入的时候需要先解压不过现在机器性能都不差多花十几秒换传输时间的大幅缩短这笔账很划算。如果你的机器上装了pigz还可以用多线程压缩进一步提速。不过这个属于锦上添花单机环境下gzip已经完全够用了。4.3 用 docker save 导出镜像备用方案如果你打算走commit save这条路径操作也不复杂。先在原有容器基础上生成一台新镜像docker commit hadoop-test hadoop-env:snapshot然后把这个新镜像保存成tar包docker save hadoop-env:snapshot -o hadoop-image.tar和docker export不同docker save可以同时导出多个镜像并且会完整保留镜像的分层信息。注意看你的文件大小如果包含相同的父镜像层docker save的体积通常比docker export大这属于正常现象。4.4 导出后的文件完整性校验文件传到目标机器之前建议先做一次完整性校验。最们的做法是给tar包算一个SHA256值记录在案sha256sum hadoop-container.tar.gz把输出的哈希值同文件一起拷贝走。在目标机器上导入之前先重新计算一遍哈希比对一致再执行导入能避免文件在传输过程中损坏导致导入失败。尤其是通过U盘拷贝、网盘下载这种非可靠链路这个步骤很值得做。5. 实操把容器导入到新机器并恢复Hadoop服务5.1 docker import 导入容器快照把压缩包拷贝到目标机器后先解压再导入。假设文件是gzip压缩过的两种方式都行。第一种是先解压再导入gzip -d hadoop-container.tar.gz docker import hadoop-container.tar hadoop-env:imported第二种是直接用管道把解压和导入合并成一条命令cat hadoop-container.tar.gz | docker import - hadoop-env:imported这里hadoop-env:imported是导入后新镜像的仓库名和标签可以自己定义。这个步骤执行完之后用docker images确认一下新镜像是否已经在列表里了。5.2 启动导入后的Hadoop容器docker import只是把文件系统转成了镜像这时候还没有容器。需要手动创建并启动容器。以Hadoop伪分布式环境为例启动命令要基于你之前记录的端口映射信息把关键端口重新映射到宿主机上docker run -d --name hadoop-restored \ -p 9870:9870 \ -p 8088:8088 \ -p 9000:9000 \ hadoop-env:imported \ /usr/sbin/sshd -D这段命令的含义是以后台模式运行一个名为hadoop-restored的容器把容器的9870、8088、9000端口分别映射到宿主机对应端口然后启动SSH服务作为前台进程保证容器不会启动后立刻退出。由于docker import不保留CMD指令这里手动指定启动命令是很有必要的。如果你之前用的是docker save docker load方式导入了原始镜像那么镜像里自带的CMD和ENTRYPOINT会保留下来启动命令可以更简单一些。但即便如此Hadoop容器在启动后服务进程一般也不会自动拉起需要手动进容器里做一次启停操作。5.3 进入容器并验证Hadoop服务是否正常容器启动后先进去看一眼进程状态docker exec -it hadoop-restored bash jps如果之前导出时数据保留得比较完整你大概率能看到NameNode、DataNode进程正在运行。如果进程没起来不要慌按顺序手动启动。先启动HDFS$HADOOP_HOME/sbin/start-dfs.sh再启动YARN$HADOOP_HOME/sbin/start-yarn.sh之后用jps重新确认进程列表再打开浏览器访问宿主机IP的9870端口NameNode Web UI和8088端口YARN ResourceManager UI能正常打开页面就说明环境基本恢复了。如果NameNode启动时报错常见原因是元数据目录不完整或者由于IP变化导致HDFS想要重新格式化。这个问题在实操中很常见我已经把排查思路整理在下一节。6. 常见问题与排查技巧实录6.1 导入后NameNode启动失败一直报格式相关错误遇到最多的一个问题就是导入镜像、启动容器后执行start-dfs.sh提示NameNode启动失败去看日志会发现类似NameNode is not formatted或者There appears to be a gap in the transaction ID之类的信息。这类问题的根源大多是HDFS的元数据目录不完整或者目录数据与集群ID不匹配。排查思路分两步。先看dfs.namenode.name.dir指向的目录是否为空ls -la $HADOOP_HOME/data/name/current/如果目录为空说明导出时元数据确实没有被打包进去。这时候可以手动重新格式化NameNodehdfs namenode -format但格式化之后之前存在HDFS里的数据会全部丢失。这是最坏情况一般只适合新环境或者用来恢复“环境本身”而不是“环境里积累的数据”。如果目录里有文件但依然出错可以考虑是集群ID不一致。HDFS的VERSION文件里保存着clusterIDNameNode和DataNode的clusterID必须一致。不一致的话可以手动把DataNode的clusterID改成和NameNode一致或者清理DataNode数据目录让它重新注册。6.2 容器能起来但Hadoop Web界面打不开容器正常启动了jps也能看到进程但浏览器访问9870端口就是打不开。这种情况八成是端口映射没配对。回顾一下你的docker run命令确认一下-p 9870:9870是不是真的写了。如果用docker ps看到端口映射为0.0.0.0:9870-9870/tcp基本就是映射成功了。还有一种情况是容器内的Hadoop服务绑定了固定IP。如果你在core-site.xml里把fs.defaultFS配成了某个具体的IP地址比如hdfs://192.168.1.100:9000而导入后的容器IP已经变了服务之间就会互相找不到。这时候需要修改core-site.xml和hdfs-site.xml里的IP为0.0.0.0或新环境的实际IP然后重启Hadoop集群。6.3 导入后容器启动立即退出用docker run启动导入的容器几秒钟后容器就退出了通过docker logs hadoop-restored查看日志发现没有输出任何内容。这个问题通常是因为启动命令写错了。docker import不会保留镜像的CMD和ENTRYPOINT你必须手动指定一个长期运行的进程。推荐用/usr/sbin/sshd -D作为前台进程容器就能持续运行。如果你之前用的是/bin/bash那容器启动后会立刻退出因为它没有任何阻塞任务在跑。6.4 导出的tar包太大传输和导入都慢tar包体积动辄几个GBU盘拷贝都嫌慢怎么办我常用的降体积方案有三个。第一导出前清理容器里的日志、缓存、临时文件这个前面说过了。第二用gzip压缩Hadoop安装目录里的文本配置文件、日志文件压缩率通常很高实测能减少40%到60%的体积。第三考虑用docker export而非docker save前者的包一般更小而且对于Hadoop这种“只要数据保留就行”的场景完全够用。6.5 快速自查清单我把上面这些经验总结成了一份速查表格方便你实际操作时挨个对照。问题现象常见原因排查与解决思路容器启动即退出启动命令没有阻塞进程手动指定/usr/sbin/sshd -D作为前台进程NameNode启动失败元数据目录不存在或clusterID不一致检查dfs.namenode.name.dir目录内容必要时重新格式化或同步clusterIDWeb界面打不开端口映射丢失或服务绑定旧IP确认docker run的-p参数修改配置文件中的IP为0.0.0.0或新IPHDFS里数据不见了数据目录在Volume里导出时没包含导出前把数据目录改到容器内路径或把宿主机挂载目录一起拷贝走tar包体积过大日志、缓存、重复分层占用空间清理日志和包管理器缓存用gzip压缩优先用docker export最后分享一个我个人的小习惯。每次导出一套Hadoop环境后我都会随手在tar包旁边放一个纯文本文件记录下容器的启动命令、端口映射、关键配置文件的修改点和数据目录位置。这个文件我管它叫“环境说明书”。表面上看起来是多了一点点工作量但等你三个月后需要从备份里恢复一套环境或者同事拿着你的tar包在另一台机器上怎么也起不来的时候这份说明能帮你省下大量沟通和排查成本。迁移结束之后这套环境就真正成为了你手里可以随时复用的资产而不只是某台机器上的一个运行实例。
返回列表