快乐学习
前程无忧、中华英才非你莫属!

Docker实战-Day4(Docker与开发1)

如何将Docker用作轻量级的虚拟机,由于虚拟机和Docker容器之间存在本质的差别,使用Docker在很多情况下可以大大加快开发速度。在转向更高级的Docker使用场景前,这也是上手Docker的有效手段,
接下里会介绍20余个技巧,使结合Docker的日常开发变得更加有用和高效。除了构建和运行容器外,读者还将了解到如何使用卷来持久化数据以及如何编排Docker宿主机。第5章覆盖了重要的配置管理领域。我们将会使用Dockerfile以及传统的配置管理工具来管理Docker的构建。我们还介绍了最小化Docker镜像的创建和数据管理等内容,以期减少镜像的膨胀。
在本部分结尾,读者将能够收获许多针对Docker单机使用场景的各种有用的技巧,并且准备好将Docker运用到DevOps场景。
将Docker用作轻量级虚拟机
 自本世纪以来,虚拟机(virtual machine,VM)已经在软件开发和部署等领域广泛普及。机器到软件的抽象使互联网时代下的软件和服务的更替及管控变得更加轻松和廉价。
Docker并不是一项虚拟机技术。它不会模拟一台机器的硬件,也不会包括一个操作系统。一个Docker容器在默认情况下没有被约束于指定的硬件限制。如果说Docker抽象了什么的话,那便是它将服务运行的环境给虚拟化了,而不是机器本身。此外,Docker很难运行Windows软件(甚至为其他Unix衍生的操作系统编写的软件)。虽说从某些方面来看,可以把Docker当成是一台虚拟机使用,但事实上,对互联网时代的开发人员和测试人员而言,有没有初始化进程或者是否直接和硬件交互并没有什么重大意义。而Docker和虚拟机有一些显著的共性,如它对周围硬件具有较好的隔离性以及顺应更加细粒度的软件交付方式。本章将会带读者领略一些之前使用虚拟机但如今可以用Docker实现的场景。相比于虚拟机,使用Docker不会给用户带来任何明显的功能优势,但是Docker在更替和环境跟踪方面带来的速度及便利性也许可以改写开发流水线的游戏规则。
从虚拟机到容器
在理想的情况下,从虚拟机迁移到容器也许就是在一个与虚拟机类似的发行版的Docker镜像上运行配置管理脚本这么简单。本节会为读者展示针对非理想情况的场景,该如何从虚拟机转换到容器。
将虚拟机转换为容器
Docker Hub上并不是拥有所有的基础镜像,因此,针对一些小众的Linux发行版和用例,人们可能需要创建自己的镜像。
同样的原则也适用于现有一台虚拟机想放到Docker里从而在其上迭代或者利用Docker生态系统优势的情况。
在理想情况下,用户会想使用标准的Docker技术,如一些结合了标准配置管理工具(见第5章)的Dockerfile,从头开始构建一个等同于虚拟机的容器。然而,现实情况是,很多虚拟机都没有被仔细地做过配置管理。这一点的确可能发生,因为一台虚拟机从人们开始用它的时候起会不断地演进,而以一个更加结构化的方式重新创建它的话,从成本上来说是不值得的。
问题
有一台虚拟机,想要将其转换成一个Docker镜像。
解决方案
为自己的虚拟机文件系统创建一个TAR文件,可以使用qemu-nbd、通过ssh执行tar命令或者其他方法,然后在Dockerfile里使用ADD命令加入TAR文件以创建自己的镜像。
讨论
首先我们将虚拟机划分为两大类:本地(虚拟机磁盘镜像放在本地,虚拟机的执行操作发生在用户的计算机上)以及远程(虚拟机磁盘镜像存储在远程,虚拟机的执行操作发生在其他地方)。这两类虚拟机(以及其他任何用户想为之创建Docker镜像的虚拟机)在原则上是一致的——需要拿到整个文件系统的TAR,然后用ADD命令将TAR文件加到scratch镜像的/。
ADD命令 
在镜像中拥有ADD命令时,Dockerfile的ADD命令(不像它的兄弟命令COPY)会自动将TAR文件(gzip压缩过的文件以及其他一些类似的文件类型也是如此)解压出来。
scratch镜像 
scratch镜像是一个零字节虚拟镜像,可以基于此构建其他镜像。一般来说,它适用于想使用一个Dockerfile复制(或者添加)一个完整文件系统的情况。
现在,让我们先来看看用户有一个本地Virtualbox虚拟机的情况。
在开始之前,首先需要完成下列任务:
1、安装qemu-nbd工具(在Ubuntu上是作为qemu-utils包的一部分提供出来的);
2、定义虚拟机的磁盘镜像的路径;
3、关闭虚拟机。
如果用户的虚拟机磁盘镜像是.vdi或者.vmdk格式的话,这一技巧应该会很奏效。其他格式则可能成败参半。下列代码展示了该如何将用户的虚拟机文件转成一个虚拟磁盘,这样一来他也就可以从这里复制出所有文件:
(1)设置一个环境变量指向用户的虚拟机磁盘镜像
(2)初始化一个qemu-nbd需要的内核模块
(3)列出这块磁盘上可以挂载的分区号
(4)将虚拟机的磁盘连接到一个虚拟设备节点
(5)通过qemu- nbd把选择的分区挂载到/mnt
(6)从/mnt创建出一个名为img.tar的TAR文件
(7)卸载分区然后用qemu-nbd清理
选择一个分区 
要选择挂载的分区的话,可以运行sudo cfdisk /dev/nbd0来查看可选项。
注意,如果看到了LVM选项,也就意味着磁盘采用的不是普通的分区方案,关于如何挂载LVM分区,用户将需要做一些额外的调研。
如果是远程虚拟机,用户需要作出一个选择:要么关掉虚拟机然后让运维团队介入,转储分区文件,要么在虚拟机是运行的状态下为它创建出一个TAR。
如果用户拿到的是分区转储文件,就可以很轻松地进行挂载,然后按照如下命令把它转换成一个TAR文件:
$ sudo mount -o loop partition.dump /mnt
$ sudo tar cf $(pwd)/img.tar -C /mnt .
$ sudo umount /mnt
另外,也可以选择从一个正在运行的系统创建出TAR文件。在登录到系统后可以轻松实现这一点:
$ cd / $ sudo tar cf /img.
tar --exclude=/img.tar --one-file-system /
至此,用户拿到了文件系统镜像的TAR,紧接着可以通过scp把它发送到其他机器。
状态中断的风险 
从一个正在运行的系统创建出TAR看上去可能是最简单的方案(没有关机,不需要安装软件或者请求别的团队),但是它也存在一些弊端——用户复制出来的文件可能存在状态不一致的情况,并且可能会在试用制作出来的新的Docker镜像时遇到一些莫名其妙的问题。如果只能这样做的话,那就尽可能多地停掉一些应用和服务。
一旦拿到了文件系统的TAR,便可以将其加到镜像里。这一过程再简单不过了,它是由一个两行代码的Dockerfile组成的:
FROM scratch
ADD img.tar /
现在可以执行docker build .,然后便能得到自己的镜像了!
从TAR导入 
除了ADD,Docker还提供了一个替代的docker import形式的命令,可以使用cat img.tar | docker import - new_image_name来导入文件。然而,即便选择在构建镜像时附带一些额外的指令,用户仍然始终需要创建一个Dockerfile。因此,使用ADD命令可能会更简单一些,也可以轻松地看到镜像的历史。
现在,因为在Docker里已经有了一个镜像,就可以开始用它来做一些实验了。在本例中,用户可能会从基于新镜像创建出一个新的Dockerfile开始,通过剥离文件和软件包来实验。
一旦完成了这一点并且得到了满意的结果,紧接着便可以在运行中的容器上用docker export导出一个新的、更小巧的TAR,用户可以把它用作新一层镜像的基础,然后重复这一过程直到对得到的镜像满意为止。图3-1中的流程图展示了这一过程。
图3-1 容器的“瘦身”过程
类宿主机容器
现在,我们将继续讨论一个在Docker社区里颇具争议的领域——运行一个从一开始就运行着多个进程的类宿主机镜像。在Docker社区里,一部分人认为这是一个糟糕的做法。容器并不是虚拟机(有着显著差异),而且完全没办法假装说对此没有困惑或者问题。好也罢,坏也罢,这一技巧将会展示该如何运行一个类宿主机镜像,然后讨论围绕着这种做法所带来的一些问题。
逐步引入 
运行类宿主机镜像会是一个说服Docker反对派的好方法,告诉他们Docker是有用的。他们用得越多,对范式的理解就会越透彻,微服务方案对他们来说也就越有意义。在我们将Docker引入企业内部后,我们发现这种单体方式是一个很棒的切入点,它可以推动人们从以前的在开发服务器及笔记本上开发转换到一个更包容和可管理的环境。由此,将Docker推广到测试、持续集成、托管环境以及DevOps工作流也就水到渠成了。
虚拟机和Docker容器之间的差异! 
虚拟机和Docker容器之间存在以下一些不同之处。
Docker 是面向应用的,而虚拟机是面向操作系统的。
Docker容器和其他容器共享同一个操作系统。相反,每个虚拟机都有一个由hypervisor管理的它们自己的操作系统。
Docker容器被设计成只运行一个主要进程,而不是管理多组进程。
问题
想要自己的容器就像一个正常的宿主机环境,可以运行多个进程和服务。
解决方案
使用一个被设计成模拟一台宿主机的镜像,然后置备它时包含需要的应用程序。
讨论
这里我们打算使用phusion/baseimage Docker镜像,一个被设计成运行多个进程的镜像。
第一步就是启动该镜像然后使用docker exec连到它里面:
user@docker-host$ docker run -d phusion/baseimage (1)
3c3f8e3fb05d795edf9d791969b21f7f73e99eb
1926a6e3d5ed9e1e52d0b446e (2)
user@docker-host$ docker exec -i -t
3c3f8e3fb05d795 /bin/bash (3)
root@3c3f8e3fb05d:/# (4)
在上述代码中,docker run将会在后台启动该镜像❶,执行该镜像默认的启动命令,然后返回新创建的容器的ID❷。
然后可以将这个容器的ID传给docker exec❸,该命令会在这个已经运行的容器内部启动一个新的进程。-i标志代表着可以和新进程交互,而-t则意味着想配置一个TTY,它允许在容器内开启一个终端(/bin/bash)❹。如果等待1 min,然后查看进程表,输出内容如代码清单3-1所示。代码清单3-1 一个类宿主机容器里正在运行的进程
(1)执行ps命令列出所有正在运行的进程
(2)一个简单的init进程,设计用来运行所有其他服务
(3)bash进程由docker exec启动,并且当作shell使用
(4)runsvdir运行所有在 /etc/service目录里定义的服务
(5)通过runsv命令在这里启动3个标准服务(cron、 sshd和syslog)
(6)当前执行的ps命令可以看到,容器的启动过程很像一台宿主机,初始化一些像cron和sshd这样的服务使它看上去和一台标准的Linux主机没什么两样。
        这作为对那些新入门的Docker工程师的最初演示时非常有用。
        至于这样做是否违反了微服务的“一个容器一个服务”的原则,在Docker社区里是一个见仁见智的问题。
        类宿主机镜像方案的支持者认为这样做并没有违背该原则,因为容       器仍然可以满足为里面运行的系统提供单一的离散功能的需求。
将一个系统拆成微服务容器
我们已经探讨了该如何把一个容器用作一个单体实体(像传统的服务器那样),并且阐明这会是一个快速将一个系统架构迁移到Docker上的好方法。然而,在Docker的世界里,公认的最佳实践是尽可能多地把系统拆分开,直到在每个容器上都只运行一个“服务”,并且所有容器都通过链接互相连通。由于这是Docker官方推荐的做法,因此读者会发现从Docker Hub上获取的绝大多数容器都遵循这个方案。而理解该如何以这种方式构建镜像,对与Docker生态系统的其他组件交互也尤为重要。
使用一个容器一个服务的主要原因在于可以更容易通过单一职责原则(single responsibility principle)实现关注点分离(separation of concerns)[1]。如果用户的容器执行的是单一任务,那么可以很方便地把该容器应用到从开发、测试到生产的整个软件开发生命周期里,而无须太担心它与其他组件的交互问题。这就使软件项目可以更敏捷地交付并且具备更好的扩展性。但是,它的确带来了一些管理上的负担,因此,最好思量一下在自己的用例场景下这样做是否真的值得。
暂且不论哪种方案更适合,最佳实践方法至少拥有一个明显的优势——正如所见,在使用Dockerfile时实验和重新构建都比前一套方案快上不少。
问题
想将应用程序拆分为各个单独的且更易于管理的服务。
解决方案
使用Docker将应用程序拆分并堆砌出多个基于容器的服务。
讨论
在Docker社区里,关于应当怎样严格遵守“一个服务一个容器”的规则方面还存在着一些争议,这其中部分源自在定义方面的一些异议——它是一个单个的进程,还是说可以是结合在一起共同满足一个需求的一组进程?最终,它往往会被归结为这样一种说法,即赋予从头开始重新设计系统的能力,微服务也许会是大多数人的选择。但是有时候实用主义可能会战胜理想主义——当为组织评估Docker时,为了能够让Docker尽可能更快、更容易地用起来,我们发现自己在当时的处境下只能选择单体这条路。
让我们一起来看看在Docker内部运行单体应用的其中一个具体弊端。正如代码清单3-2中所列的那样,我们需要先展示的是如何构建一个拥有数据库、应用程序以及Web服务器的单体应用。
简化版Dockerfile 这些例子只是用于教学目的,并且已经被相应简化。尝试直接运行它们不一定能正常工作。
代码清单3-2 配置一个简单的PostgreSQL、NodeJS和Nginx应用
FROM ubuntu:14.04
RUN apt-get update && apt-get install postgresql nodejs npm nginx
WORKDIR /opt
COPY . /opt/
RUN service postgresql start &&
        cat db/schema.sql | psql &&
        service postgresql stop
RUN cd app && npm install
RUN cp conf/mysite /etc/nginx/sites-available/ &&
        cd /etc/nginx/sites-enabled &&
        ln -s ../sites-available/mysite
何时在RUN语句里使用命令链 在RUN语句里使用&&能够有效确保这些命令作为一条命令执行。这一点非常有用,因为它可以保持镜像不至于太大。每条Dockerfile命令都会在之前的基础上创建一个单一的新镜像层。如果以这种方式执行一个软件包更新的命令,如apt-get update这样的安装命令,用户便能够确保无论软件包是何时安装的,它们都将是来源于一个已经更新过的包缓存。
前面的例子是一个简单的概念版的Dockerfile,它会在容器里安装一切需要的软件,并随后配置好数据库、应用程序和Web服务器。但是,如果想快速地重新构建容器的话就有问题了——仓库下的任何文件的任意改动均会造成一切事物从{*}开始重新构建,因为这种情况下无法复用之前的缓存。如果存在一些执行较慢的步骤(数据库的创建或者npminstall)的话可能就得在容器重新构建的时候等待一段时间。针对这个问题的解决方案便是将COPY . /opt/指令拆到应用(数据库、应用程序和Web配置)的各个部分:
(1)设置db
(2)设置app
(3)设置Web在前面的代码里,COPY命令被分成两个单独的指令。由于可以利用缓存复用在修改代码之前未经变更的交付文件,这就意味着数据库不会在每次代码更改的时候都重新构建。但是,由于缓存功能是相当简单粗糙的,容器仍然不得不在每次对schema脚本做出更改时完全地重新构建。解决这一问题的唯一途径便是抛弃原有顺序配置的步骤,创建多份Dockerfile,内容如代码清单3-3、代码清单3-4和代码清单3-5所示。
代码清单3-3 数据库 Dockerfile
代码清单3-4 应用程序 Dockerfile
代码清单3-5 Web服务器 Dockerfile
FROM ubuntu:14.04
RUN apt-get update && apt-get install
nginx WORKDIR /opt
COPY conf /opt/conf
RUN cp conf/mysite /etc/nginx/sites-available/ &&
        cd /etc/nginx/sites-enabled &&
        ln -s ../sites-available/mysite
每当db、app或者conf文件夹下的内容有一个发生变化,将会只有一个容器需要被重新构建。当有超过3个以上的容器又或者有一些对时间敏感的配置步骤时,这样做会特别有用——只要花上一些心思,在每个步骤中添加最低限度的所需文件,结果便是可以最大程度地利用.Dockerfile的缓存机制。在应用的Dockerfile(代码清单3-4)里,npm install操作定义在了一个单独的文件package.json里,因此我们可以通过修改我们的Dockerfile以利用dockefile本身的镜像层缓存机制,并且只需要在必要的时候才去重新构建缓慢的npm install,如代码清单3-6所示。
代码清单3-6 重新排序的Dockerfile,更早执行npm install
FROM ubuntu:14.04
RUN apt-get update && apt-get install
nodejs npm
WORKDIR /opt
COPY app/package.json /opt/app/package.json
RUN cd app && npm install
COPY app /opt/app
RUN cd app && ./minify_static.sh
但是,天下没有免费的午餐——我们必须将一个简单的Dockefile转换为多个重复的Dockerfile。我们可以通过添加另外一个Dockerfile当作自己的基础镜像来解决部分问题,但是其他这样重复的情况并不少见。此外,现在启动镜像时又会冒出一些新的复杂性——除在EXPOSE步骤让一些合适的端口可以用于链接以及修改Postgres配置外,我们还需要确保在自己每次启动时链接到各个容器。幸运的是,已经有这样的一款工具,它叫作docker-compose(前身是fig),我们将会在下面的篇章内容里介绍它。
管理容器的服务
Docker官方文档中清楚地表达了Docker容器并不是虚拟机。Docker容器和虚拟机之间一个关键的区别就是,容器是被设计成运行单个进程的。当该进程结束时,容器便会退出。这就是它和一台Linux虚拟机(或者任意一个Linux操作系统)的不同之处,它没有init进程。
init进程在Linux操作系统上是以进程ID为1并且父进程ID为0的形式运行的。这个init进程可能会被叫作“init”或者“systemd”。无论它叫什么,它的职责都是承担运行在该操作系统上的所有其他进程的维护工作。如果开始实验Docker的话,用户可能会发现自己仍然有启动多个进程的需求。例如,用户可能会想要运行一些cron作业来收拾本地应用的日志文件,又或者在容器里配置一个内部的memcached服务器。如果选择走这条路的话,那么可能最终需要编写一个shell脚本来管理这些子进程的启动。实际上,用户将会效仿init进程的做法。别这么干!进程管理中的许多问题之前都已经被其他人遇到过了,并且已经在预打包系统里解决了。
管理容器内服务的启动
当尝试将Docker用作虚拟机的替代品时,选择在容器里运行多个服务可能会是一个方便之举,又或者说这可能是在最初将虚拟机转换成容器后要运行重要服务时的必要之举。无论原因是什么,重要的是要避免尝试在容器里管理进程时重复造轮子。
问题
想要在一个容器里管理多个进程。
解决方案
使用Supervisor应用(http://supervisord.org/)来管理进程的启动。
讨论
我们打算展示一下如何置备带有Tomcat和一个Apache Web服务器的容器,并且以Supervisor托管的方式启动并运行它。
首先,如代码清单3-7所示,在一个新的空目录里创建Dockerfile。
代码清单3-7 Supervisor示例Dockerfile
(1)从ubuntu:14.04开始
(2)安装python- pip(用来安装Supervisor)、apache2和tomcat7
(3)设置一个环境变量,表明此会话是非交互式的
(4)通过pip安装Supervisor
(5)创建一些运行应用所需的维护
(6)利用echo_sup- ervisord_conf工具创建一个默认的super- visord配置文件
(7)将Apache和Tomcat的supervisord配置设定复制到镜像里,做好加到默认配置的准备
(8)将Apache和Tomcat的supervisord配置设定追加到supervisord的配置文件里
(9)由于不再有用处了,删除之前上传的文件
(10)现在,只需要在容器启动时运行Supervisor即可还将需要配置Supervisor,指示它需要启动哪些应用,如代码清单3-8所示。
代码清单3-8 supervisord_add.conf
(1)为supervisord声明全局配置块
(2)设置成不要后台运行Supervisor进程,因为对容器来说它是一个前台进程
(3)声明新程序的代码块
(4)用于启动在该代码块中声明的程序的命令
(5)配置相关日志由于用的是Dockerfile,因此可以借助标准的单个Docker命令来构建镜像
docker build -t supervised .
现在可以运行构建好的镜像了!
(1)将容器的80端口映射到宿主机上的9000端口,给容器分配一个名字,然后指定要运行的镜像名称,即之前构建命令标记的那个
(2)启动Supervisor进程
(3)启动被托管的进程
(4)被托管的进程被Supervisor识别为已经成功启动如果访问http://localhost:9000,应该就能看到启动的Apache服务器的默认页面。要清理容器,可以执行如下命令:
  如果访问http://localhost:9000,应该就能看到启动的Apache服务器的默认页面。
  要清理容器,可以执行如下命令:docker rm -f supervised
如果对Supervisor的其他替代品有兴趣,还有一个runit,上露个面篇章介绍过的Phusion基础镜像用到了它。
保存和还原工作成果
    
有些人说代码直到提交到了源代码管理中才算编写完成,对容器来说又何尝不是这样。
如果使用虚拟机可以借助快照来保存现有状态,但是Docker采取的是一个更为积极的方案,鼓励保存和复用已有的工作成果。
我们将介绍在开发中“保存游戏”的方式、打标签(tagging)技术的一些具体细节、Docker Hub的使用,以及如何在构建时指向特定镜像。由于这些操作被认为是非常基础的,因而Docker将它们打造得相对简单和快捷。尽管如此,对于Docker新手来说这仍然是一个令人困惑的主题,因此在这一节中,我们将带领读者一步步更加全面地了解与这个主题相关的内容。
在开发中“保存游戏”的方式
如果曾经开发过任意类型的软件,那么你应该不止一次抱怨过:“我敢肯定在此之前它运行得好好的!”也许原话还没有这么淡定。由于无法将系统还原到一个已知的良好(或者也许只有“更好”)状态,就只能赶紧强行修改(hack)代码以赶上最后期限或者修复漏洞,这也是许多人敲碎键盘累死累活的原因。源代码管理极大地改善了这一点,然而在特殊情况下还是存在以下两个问题:源代码仓库可能无法反映出“工作”环境下文件系统的状态;
暂时还不想把代码提交上去。第一个问题比第二个更明显一些。尽管像Git这样的现代化源代码管理工具可以轻松地创建出一次性的本地分支,但是抓取整个开发环境文件系统的状态并不是源代码管理的初衷。Docker通过它的提交(commit)功能提供了一个廉价、快捷的方式来保存容器的开发环境文件系统状态,而这正是接下来要探讨的内容。
问题
想要保存开发环境的状态。
解决方案
使用docker commit来保存状态。
讨论
我们不妨假设用户想要修改第1章里的to-do应用。ToDo公司的CEO对这个应用不是很满意,并且想把浏览器上显示的标题从“Swarm+React - TodoMVC”改为“ToDoCorp’s ToDo App”。用户拿不准要怎么实现这一点,也许会想要先把应用运行起来,然后通过修改文件来进行实验,看看到底会发生什么:
上述docker run命令 ❶ 在一个容器里以后台模式(-d)启动了to-do应用,将容器的8000端口映射到了宿主机上的8000端口(-p 8000:8000),为方便引用起见,将其命名为todobug1(--name todobug1),然后返回了该容器的ID。该容器启动时执行的命令默认会是我们构建的dockerinpractice/todoapp镜像在构建时指定的命令,该镜像也可以在Docker Hub上找到。第二条命令❷ 将会在正在运行的容器里启动/bin/bash。这里用到的是名为todobug1的容器,不过用户也可以使用其原本的容器ID。-i意味着这条exec命令以交互模式运行,而-t确保exec将会按照一个终端预期的那样工作。
如今我们已经在容器里了,那么,实验的第一步便是安装一个编辑器。我们更喜欢vim,所以采用了如下命令:
apt-get update
apt-get install vim
小经波折之后我们意识到需要修改的文件是local.html。因此,我们将该文件的第5行改成了如下内容:
<title>ToDoCorp's ToDo App</title>
但CEO的意思可能是希望标题都是小写的,因为她听说这样看上去会更时尚些。这两种方式我们都想准备好,所以我们选择先提交现有成果。在另外一个终端下运行如下命令:
$ docker commit todobug1 (1)
ca76b45144f2cb31fda6a31e55f784c93df8c9d
4c96bbeacd73cad9cd55d2970 (2)
(1)把之前创建出来的容器转成镜像
(2)刚提交的容器的新镜像ID如今已经将容器提交成镜像,并且可以在之后运行它。
状态是无法捕获的! 
提交一个容器只会保存在提交那个时刻容器的文件系统的状态,而不是进程。记住,Docker容器不是虚拟机。如果环境的状态依赖于一些正在运行的进程的状态,而这些进程不能通过标准文件恢复的话,这个技巧将无法帮助用户保存所需的那个状态。在这种情况下,用户也许得想办法看怎样才能恢复开发环境里进程的状态。
紧接着,将local.html的内容修改成另外一个可能要求的值:
<title>todocorp's todo app</title>
$ docker commit todobug1
071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036
现在拥有两个镜像的ID(在这个演示里是ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bbeac d73cad9cd55d2970和071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036,但是读者可能会不一样),代表两种选择。当CEO来评估其想要的方案时,可以把两个镜像都运行起来,然后让她决定要提交哪一个。
可以通过打开新的终端然后执行如下命令来实现这一点:
(1)将容器的8000端口映射到宿主机的8001端口,然后指定小写的那个镜像的ID
(2)将容器的8000端口映射到宿主机的8002端口,然后指定大写的那个镜像的ID
这样一来,便可以在http://local-host:8001上展示大写的方案,在http://localhost:8002上呈现小写的方案。毋庸置疑,比起一长串随机的字符串,肯定还有更好的办法来引用镜像。下面一个技巧将会关注如何给这些镜像指定一个名称,以便能够更加轻松地引用它们。
我们发现,当我们需要通过一系列棘手的命令来配置应用时这会是一个很有用的技巧。提交该容器时,一旦成功的话,它会记录我们的bash会话的历史,这也意味着通过一系列步骤重新恢复系统的状态成为可能。这可以节省大量的时间!而且,这在正实验一个新功能而又不太确定是否完工,或者当重现出一个漏洞然后想尽量确保能够回到那个挂掉的状态时,也很有用处。
外部依赖是无法捕获的! 对容器的任何外部的依赖(如数据库、Docker卷或者其他被调用的服务)在提交时均不会被保存。当前介绍的这个技巧没有任何外部的依赖,因此我们也无须担心这一点。
来源:Docker实践  作者:[美] 伊恩 · 米尔、 艾丹 · 霍布森 · 塞耶斯
打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » Docker实战-Day4(Docker与开发1)

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!