技巧36 保留容器的bash历史
在一个容器里做实验时,用户会认识到在完成的时候可以擦除所有痕迹,这是一个很开放的体验。但是这样做也会失去一些便利性。其中一个我们经历过很多次的痛点便是它会忘掉我们之前在容器里执行的一系列命令。
问题
想要将容器的bash历史与宿主机上的命令历史共享。
解决方案
使用 -e
标志,Docker挂载卷,以及一个bash别名,实现和宿主机自动共享容器的bash历史。
为了理解这个问题,我们将会展示一个简单的场景,在这个场景下失去历史记录会很烦人。
试想一下,用户正在Docker容器里做一些实验,并且工作内容是一些有趣而又可以复用的事情。这里我们通过一个简单的 echo
命令举个例子,但是它也可能会是一长串复杂并且拼接成的程序,最终它会生成一个有用的输出:
$ docker run -ti --rm ubuntu /bin/bash
$ echo my amazing command
$ exit
一段时间后,用户想要重新调用早先执行过的那个难以置信的 echo
命令。但是,用户没能记住它,而且在屏幕上可以滚动的终端会话也过期了。出于习惯,用户尝试在宿主机上翻看bash历史:
$ history | grep amazing
什么都没返回,因为bash历史被保存到了如今已经删除的容器里,而不是切回来的宿主机上。为了在宿主机上共享bash历史,可以在运行Docker镜像时挂载一个卷。下面是一个例子:
$ docker run -e HIST_FILE=/root/.bash_history \ ⇽--- 设置bash拾取的环境变量。这可以确保使用的bash历史文件就是用户挂载的那一份
-v=$HOME/.bash_history:/root/.bash_history \ ⇽--- 将容器里root目录下的bash历史文件映射到宿主机上
-ti ubuntu /bin/bash
提示
用户可能想要将容器的bash历史和宿主机分开。要达成这个目的,一种办法便是改变前面 -v
参数的第一部分的值。
每次都要输入一段内容还是挺不方便的,因此为了让它对用户更加友好,可以将这条命令设置成别名然后放到~/.bashrc文件里。
$ alias dockbash='docker run -e HIST_FILE=/root/.bash_history \
-v=$HOME/.bash_history:/root/.bash_history
这仍然存在一些瑕疵,如果想执行 dockerrun
命令,必须得记得敲 dockbash
。为了追求更完美的用户体验,可以将代码清单5-6所示的内容写入~/.bashrc文件中。
代码清单5-6 自动挂载宿主机bash历史记录的函数别名
function basher() { ⇽--- 创建一个名为basher的函数来处理docker命令
if [[ $1 = 'run' ]] ⇽--- 确定basher/docker的第一个参数是否是run
then
shift ⇽--- 删除之前传入的一系列参数中的一个
/usr/bin/docker run \ ⇽--- 执行之前执行过的docker run命令,通过指定Docker命令的绝对路径来避免和接下来的docker别名混淆。在实施这一方案前,需要先通过执行which docker命令找出宿主机上实际的绝对路径
-e HIST_FILE=/root/.bash_history \
-v $HOME/.bash_history:/root/.bash_history "$@" ⇽--- 在run的后面带上实际参数传给Docker运行时
else
/usr/bin/docker "$@" ⇽--- 以完整的原始传入参数执行docker命令
fi
}
alias docker=basher ⇽--- 针对在命令行上调用的docker命令起一个别名,映射到刚创建的basher函数。这可以确保调用docker的操作在bash从path路径上查找docker二进制文件之前被捕获到
讨论
如今,用户下一次打开bash shell,执行任何 dockerrun
命令时,在该容器内执行的命令都会被添加到宿主机上的bash历史。一定要确保Docker路径是对的。比如,它可能会放在/bin/docker。
注意
为了让history文件得到更新,用户需要注销宿主机上的原始bash会话。这得归咎于bash的微妙机制,以及它更新保存在内存里的bash历史的方式。如有疑问,不妨先退出所有已知的bash会话,然后再启动一个新的,以尽量确保bash历史是最新的。
许多带有提示的命令行工具也会存储历史记录,SQLite就是一个例子(它会把历史记录存放到一个.sqlite_history文件里)。如果用户不想使用技巧102中介绍的可用于Docker的集成日志记录方案,那么可以使用类似的做法让应用程序写入到的是一个最终放到容器外面的文件。请注意,由于日志记录的复杂性(比如日志轮换),这也就意味着用一个目录作为卷可能会比单个文件更简单些。