Docker 进阶

#Docker

说明

  之前,基本是在单机上用 Docker,在不影响当前环境的前提下,创建和使用一些特殊环境。最近,涉及到多 Docker 的协作,比如:在同一服务器上启动和管理多个容器;在一台服务器上使用类似的镜像版本,在不同的机器之间复制镜像等等,积累了一些 docker 使用方法,和大家分享一下。

##环境搭建

  要想了解整个流程,还是在自己机器上搭建环境,从头到尾过一遍,最为直接。

1
$ sudo apt-get install docker.io

  安装好之后,就可以使用 docker 命令了。此时还只能用 root 身份,如果想让某个用户操作 docker,则需要将其加入 docker 组。

1
2
3
4
5
$ 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 系统信息

1
$ docker info

容器相关操作

  我们先把 Docker 看成虚拟机,容器就是虚拟机的运行实例。

(1) 创建和启动容器

  上面简单介绍了用 run 启动容器,下面来看看 run 的具体参数和使用方法

1
2
3
4
5
6
7
8
9
10
11
$ 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) 查看当前运行的容器

  可以通过以下命令,查看正在运行的容器。

1
$ docker ps

  此时,能看到正运行容器的一些信息,注意其中的 CONTAINER ID,之后我们会通过这个 ID 号操作指定的容器。

  在搭建环境的最后一步,我们用 docker run 启动了一个容器,此时再开一个终端,用 docker ps 命令,就可以看到这个容器的存在。用 exit 退出后,再用 docker ps,则看不到该容器了。

(3) 在已运行的容器中执行命令

  我们常使用 -d 参数后台启动容器,用 exec 可与正在运行的容器交互。

1
$ docker exec -it CONTAINER_ID /bin/bash

  该命令可进入正在运行的 docker,常用它进入 docker 安装一些软件。

(4) 在虚拟系统与本地系统之间复制文件

1
$ sudo docker cp HOST_PATH CONTAINER_ID:CONTAINER_PATH

本地复制到容器

1
$ sudo docker cp CONTAINER_ID:CONTAINER_PATH HOST_PATH

容器复制到本地

(5) 查看某一容器的 log

1
$ docker logs CONTAINER_ID

  查看 docker 中的 log 信息,比如我们用 docker 在后台启一个 jupyter,后来忘了 token 导致无法登录,就可以从 log 中找到。

(6) 停止运行中的容器

1
$docker stop CONTAINER_ID

(7) 删除容器

1
$docker rm -f CONTAINER_ID

  删除之后,容器中的操作将不再保存。

 run 包含建立(create)容器和启动(start)容器两步。对应的关闭时,也会为 stop 关闭和 rm 删除两步。如果 run 运行时不使用 -rm 参数,则停止运行后该容器不会被删除。需要用 rm 命令手动删除。

镜像 Image 相关操作

  镜像一般指的是只读的数据包。容器是动态的,镜像是静态的,容器退出后该镜像依然存在,请注意,在容器中对虚拟系统的所做的修改并不会自动被保存在镜像之中。比如说,我下了N个软件,退出后,下次再 run 该镜像时,这些包就不存在了。

  为什么不能像使用 virtual box 或者 vmware 虚拟机那样,一边运行一边保存呢?我觉得,是因为很多时候,在同一机器上可能基于同一个 image 启动多个 container,如果即时保存,image 里面倒底该保存哪个 container 呢?

(1) 查看镜像

  可以通过以下命令,查看当前可用的 image。

1
$ docker images

  注意 IMAGE ID 如果想要操作特定镜像,则需要使用该 ID。

(2) 以创建方式制作镜像

  有时候我们需要保存安装的包和一些数据,以备下次使用。有两种方法:一种是用 build 方式创建新的镜像,一种是用 commit 在原有镜像的基础上修改后,保存成新的镜像。

1
$ docker build -t REPOSITORY:TAG

  用当前目录的 Dockerfile 创建镜像,Dockerfile 文件可以指定基础镜像,安装包,环境变量等等。

(3) 以修改方式制作镜像

  在一个容器中修改之后,可以通过 commit 把修改保存到镜像上。

1
$ docker commit CONTAINER_ID REPOSITORY:TAG

  此时,用 docker images,就可以看到新的镜像了。新镜像只保存其基础版本的增补,并不会占太大空间,下次启动时,只需要指定 REPOSITORY:TAG 即可。

  还可通过 TAG 命令修改其版本号

1
$ docker tag 旧版本号 新版本号

  相对来说,build 方法更加规范,能做出比较“干净”的镜像,我们知道这个镜像与基础版本有何不同,而 commit 相对比较随意,常用它来保存自己的工作现场。

(4) 删除镜像

  先停掉(stop)启动的容器,然后运行 rmi 命令:

1
$ docker rmi IMAGE_ID

(5) 把一个机器上的镜像复制到另一台机器上

  在要导出的机器 A 上,执行

1
$ docker save -o 文件名 IMAGE的TAG

  在要导入的机器 B 上,执行

1
$ docker load -i 文件名

Layer 层

  做到这一步,有点好奇,docker 内部到底是如何管理基础版本和其上复杂分支的呢?先来看看元数据。

  用以下命令,可获取容器或者镜像的元数据

1
$ 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 不同的部分。只是在复制过程中比较占空间。

参考

(1) Docker 命令大全

http://www.runoob.com/docker/docker-command-manual.html