说明
之前,基本是在单机上用Docker,在不影响当前环境的前提下,创建和使用一些特殊环境。最近,涉及到多Docker的协作,比如:在同一服务器上启动和管理多个容器;在一台服务器上使用类似的镜像版本,在不同的机器之间复制镜像等等,积累了一些docker使用方法,和大家分享一下。
环境搭建
要想了解整个流程,还是在自己机器上搭建环境,从头到尾过一遍,最为直接。
$ sudo apt-get install docker.io
安装好之后,就可以使用docker命令了。此时还只能用root身份,如果想让某个用户操作docker,则需要将其加入docker组。
$ sudo usermod -aG docker $your-user # 将新成员加入docker组
$ sudo service docker restart # 重启docker服务
$ newgrp - docker # 刷新docker成员
从官网拉下ubuntu镜像
$ docker run -it ubuntu bash # 如果本地不存在ubuntu镜像,则会从官网下载(pull)该镜像,进入该镜像,并以交互方式运行bash(后面介绍run的具体参数)
此时就进入了docker所启动系统命令行(后简称被DOCKER启动的系统为虚拟系统,运行DOCKER的计算机为本地系统,注意它和virtual box虚拟机不同),它是一个只是几十兆的小系统,此时可以用apt-get安装一些软件,来构造你的环境。
Docker的CS模式
从上面的service docker restart可以看出,Docker是Client/Server方式的,C/S之间通过socker通讯。Service端启在后台,client端用docker命令与server通讯。
(1) 显示 Docker 系统信息
$ docker info
容器相关操作
我们先把Docker看成虚拟机,容器就是虚拟机的运行实例。
(1) 创建和启动容器
上面简单介绍了用run启动容器,下面来看看run的具体参数和使用方法
$ docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
常用参数如下:
-d: 后台运行容器,并返回容器ID
-i: 以交互模式运行容器,通常与 -t 同时使用
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用
-p: 将容器端口映射到主机端口(-p 8890:8888,是把容器的8888端口映射到主机8890端口)
-v: 将主机目录映射到容器中(-v 主机目录:容器中目录,-v参数可以有多个)
--ip:指定ip地址
--rm:在停止运行(stop)后删除容器,这样关闭后就不再需要用docker rm删除容器了。
IMAGE 镜像的名字
COMMAND:需要启动的命令
(2) 查看当前运行的容器
可以通过以下命令,查看正在运行的容器。
$ docker ps
此时,能看到正运行容器的一些信息,注意其中的CONTAINER ID,之后我们会通过这个ID号操作指定的容器。
在搭建环境的最后一步,我们用docker run启动了一个容器,此时再开一个终端,用docker ps命令,就可以看到这个容器的存在。用exit退出后,再用docker ps,则看不到该容器了。
(3) 在已运行的容器中执行命令
我们常使用-d参数后台启动容器,用exec可与正在运行的容器交互。
$ docker exec -it CONTAINER_ID /bin/bash
该命令可进入正在运行的docker,常用它进入docker安装一些软件。
(4) 在虚拟系统与本地系统之间复制文件
$ sudo docker cp HOST_PATH CONTAINER_ID:CONTAINER_PATH
本地复制到容器
$ sudo docker cp CONTAINER_ID:CONTAINER_PATH HOST_PATH
容器复制到本地
(5) 查看某一容器的log
$ docker logs CONTAINER_ID
查看docker中的log信息,比如我们用docker在后台启一个jupyter,后来忘了token导致无法登录,就可以从log中找到。
(6) 停止运行中的容器
$docker stop CONTAINER_ID
(7) 删除容器
$docker rm -f CONTAINER_ID
删除之后,容器中的操作将不再保存。
run包含建立(create)容器和启动(start)容器两步。对应的关闭时,也会为stop关闭和rm删除两步。如果run运行时不使用-rm参数,则停止运行后该容器不会被删除。需要用rm命令手动删除。
5. 镜像Image相关操作
镜像一般指的是只读的数据包。容器是动态的,镜像是静态的,容器退出后该镜像依然存在,请注意,在容器中对虚拟系统的所做的修改并不会自动被保存在镜像之中。比如说,我下了N个软件,退出后,下次再run该镜像时,这些包就不存在了。
为什么不能像使用virtual box或者vmware虚拟机那样,一边运行一边保存呢?我觉得,是因为很多时候,在同一机器上可能基于同一个image启动多个container,如果即时保存,image里面倒底该保存哪个container呢?
(1) 查看镜像
可以通过以下命令,查看当前可用的image。
$ docker images
注意IMAGE ID如果想要操作特定镜像,则需要使用该ID。
(2) 以创建方式制作镜像
有时候我们需要保存安装的包和一些数据,以备下次使用。有两种方法:一种是用build方式创建新的镜像,一种是用commit在原有镜像的基础上修改后,保存成新的镜像。
$ docker build -t REPOSITORY:TAG
用当前目录的Dockerfile创建镜像,Dockerfile文件可以指定基础镜像,安装包,环境变量等等。
(3) 以修改方式制作镜像
在一个容器中修改之后,可以通过commit把修改保存到镜像上。
$ docker commit CONTAINER_ID REPOSITORY:TAG
此时,用docker images,就可以看到新的镜像了。新镜像只保存其基础版本的增补,并不会占太大空间,下次启动时,只需要指定REPOSITORY:TAG即可。
还可通过TAG命令修改其版本号
$ docker tag 旧版本号 新版本号
相对来说,build方法更加规范,能做出比较“干净”的镜像,我们知道这个镜像与基础版本有何不同,而commit相对比较随意,常用它来保存自己的工作现场。
(4) 删除镜像
先停掉(stop)启动的容器,然后运行rmi命令:
$ docker rmi IMAGE_ID
(5) 把一个机器上的镜像复制到另一台机器上
在要导出的机器A上,执行
$ docker save -o 文件名 IMAGE的TAG
在要导入的机器B上,执行
$ docker load -i 文件名
Layer层
做到这一步,有点好奇,docker内部到底是如何管理基础版本和其上复杂分支的呢?先来看看元数据。
用以下命令,可获取容器或者镜像的元数据
$ docker inspect CONTAINER_ID/IMAGE_ID
此时,可以看到当前的信息,比如IMAGE_ID的ID及其Parent,从而可确定其继承关系。在Docker内部,只保存各个版本之间的差异。
Layer是Docker用来管理镜像层的中间概念,因为单个镜像层可能被多个镜像使用,所以docker把layer和image的概念分开,layer存放了镜像层的diff_id,size,parent_id等内容。在同一个Docker版本管理系统中,只要Layer一致,就只保存一份。
默认情况下,镜像的存储路径为/var/lib/docker/aufs/,其中的layers文件夹存储的就是layer信息。
在机器A上,镜像的存储是增量的,如果用save/load方式复制到另一台机器B上,镜像会不会很大?答案是不会。镜像的存储是按Layers层层堆叠的,同一layer只存储一次,所以在向一台机器导入image时,会对比和保存新镜像和现存layer不同的部分。只是在复制过程中比较占空间。