Docker入门

441 阅读32分钟

一、Docker简介

Docker是一个操作系统级的虚拟化技术,是基于LXC技术构建的轻量级容器引擎。

LXC技术:LXC是Linux Container的缩写,Linux Container容器是一种内核虚拟化技术,可以提供轻量级的虚拟化,以便隔离进程和资源。

img

二、Docker入门

1.在Linux中下载并安装Docker

1.1.前提条件

Docker要求CentOS系统内核高于3.10

uname -r 查看当前系统内核版本

[root@VM-8-9-centos ~]# uname -r
3.10.0-1127.19.1.el7.x86_64

如果版本达不到要求需要更新

yum -y update:升级所有包同时也升级软件和系统内核
yum -y upgrade:只升级所有包,不升级软件和系统内核。

从 2017 年 3 月开始 docker 在原来的基础上分为两个分支版本: Docker CE 和 Docker EE。

Docker CE 即社区免费版,Docker EE 即企业版,强调安全,但需付费使用。

1.2.卸载旧版本的Docker

 yum -y remove docker docker-engine docker.io containerd runc

1.3.通过yum安装 Docker

安装一些必要的系统工具

yum install -y yum-utils device-mapper-persistent-data lvm2

添加软件源信息

yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

更新yum缓存

yum makecache fast

安装Docker-ce

yum -y install docker-ce

启动Docker

systemctl start docker

1.4.使用脚本安装Docker

curl -fsSL https://get.docker.com -o get-docker.sh
bash get-docker.sh

安装Docker并将镜像源修改成阿里的镜像源

curl -fsSL https://get.docker.com -o get-docker.sh
bash get-docker.sh --mirror Aliyun

1.5.启动Docker进程

systemctl start docker

1.6.验证docker是否成功安装

docker ps 

1.7.镜像加速

docker在海外,后续拉取镜像十分缓慢,可以把镜像源改成国内的

在 /etc/docker/daemon.json文件里添加配置,没有就创建一个

{ 
   "registry-mirrors": ["http://hub-mirror.c.163.com"] }
{

1.8.镜像源

Docker官方中国区

https://registry.docker-cn.com

网易

http://hub-mirror.c.163.com

阿里云

https://pee6w651.mirror.aliyuncs.com

中国科学技术大学

https://docker.mirrors.ustc.edu.cn

删除Docker

yum remove docker-ce
rm -rf /var/lib/docker

2.Hello,Docker!

2.1.镜像和容器的概念

镜像

镜像可以理解为一个打包了运行环境的特殊文件系统,它包含了容器启动运行所需的所有信息,包括运行程序和配置数据等。镜像不包含任何动态数据,其内容在构建之后也不会改变。

例如,一个官方的Ubuntu14.04镜像,就包含了一套完整的Ubuntu14.04最小系统的root文件系统。

容器

镜像和容器的关系,类似于面向对象程序设计中的类和实例一样,镜像是静态的定义,而容器是镜像运行时的实体,可以看成是一个具备某个运行环境的非常轻量的虚拟机。容器可以被创建、启动、停止和删除等。在创建容器时,需要显示地为容器指定镜像。指定镜像之后,容器就具备了镜像中保存的运行环境了。

例如,可以为容器指定Ubuntu14.04的镜像,然后该容器就具备Ubuntu14.04的运行环境了。

2.2.Docker使用基本过程

1.获取镜像

在安装完Docker服务之后,本地是没有任何镜像的,所以首先需要获取所需要的镜像。

2.基于该镜像创建并启动一个容器

在获取到所需的镜像之后,就可以基于该镜像创建并启动一个容器,该容器就具备了镜像包含的运行环境了。同时,在创建容器时也可以设置容器的启动命令,该命令会在容器启动时执行。

3.进入该容器,执行“程序”

在容器成功创建并启动之后,该容器就具备了ubuntu的运行环境。我们可以进入该容器内部,并在其内部执行任何在ubuntu系统上的程序了。这里的“程序”可以是“Linux命令”、“shell脚本”、“C++程序”等。

2.3.启动一个容器并输出Hello Docker

docker pull busybox:latest
docker run --name first_docker_container busybox:latest echo "Hello Docker"
  • 第一条命令:获取一个名为busybox:latest的镜像。这条命令会从Docker Hub官方镜像仓库获取一个名为busybox:latest的镜像(busybox的最新版),并把它下载到宿主机。其中busybox是最小的Linux系统。
  • 第二条命令: 创建并启动一个容器,并执行相应命令。首先,--name设置容器的名字为first_docker_container,然后为容器指定了busybox:latest作为启动镜像,最后设置了该容器的启动命令为echo "Hello Docker"。容器启动并输出 “Hello Docker”后,将其停止。

3.拉取镜像

3.1.获取镜像

docker pull [选项] <仓库名> <标签>
  • docker pull:Docker拉取镜像的命令关键词;
  • [选项]:命令选项;
  • 仓库名:仓库名的格式一般为<用户名>/<软件名>。对于Docker Hub,如果不指定用户名,则默认为library,即官方镜像;
  • 标签:标签是区分镜像不同版本的一个重要参数,<仓库名>:<标签>会唯一确定一个镜像。默认为latest。

例如拉取一个Ubuntu 14.04的官方镜像

docker pull ubuntu:14.04

如果标签为空的话就会使用默认的标签,也就是latest,执行上面的命令后默认会去Docker Hub中寻找名为repoName的仓库,不存在则返回错误信息,如果存在,就从仓库拉取对应的标签的镜像。

不加标签默认拉取最新的版本

docker pull ubuntu latest

拉取一个不存在的镜像会报错

[root@localhost Desktop]# docker pull aaa
Using default tag: latest
Error response from daemon: repository aaa not found: does not exist or no pull access

4.启动一个容器

启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态的容器重新启动。

4.1.第一种方式:新建并启动

docker run命令会基于指定的镜像创建一个容器并且启动它。docker run的基本语法如下:

docker run [选项] 镜像名 [命令] [参数]

docker run: Docker创建并启动容器的命令关键词;

  • 选项: 命令选项,最常用的包括
    • -d后台运行容器并返回容器ID,
    • -i以交互模式运行容器,
    • -t为容器分配一个伪输入终端,
    • --name 指定启动容器的名称。
  • 镜像名: 以<仓库名>:<标签>的方式来指定;
  • 命令: 设置启动命令,该命令在容器启动后执行;
  • 参数: 其他一些参数。

docker run背后的工作

Docker在后台运行的标准操作包括:

  1. 检查本地是否存在指定的镜像,不存在就从公有仓库下载启动;
  2. 利用镜像创建并启动一个容器;
  3. 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层;
  4. 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去;
  5. 从地址池配置一个ip地址给容器;
  6. 执行用户指定的启动命令;
  7. 执行完毕后容器被终止。

实例一

创建并启动一个容器,容器中具有ubunt的运行环境,输出hello docker

docker run ubunt:14.04 echo 'hello docker'

实例二

创建并启动一个容器,容器中具有Ubuntu的运行环境,容器名为firstContainer,为容器分配一个终端,与用户进行交互

docker run -it --name firstContainer ubunt /bin/bash
  • -i选项告诉Docker保持标准输入输出流对容器开放,-t选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上;--name为容器设置容器名。
  • docker run是创建一个新容器并启动,所以这条命令创建的容器与上个实例的创建的容器不是同一个容器。而且由于本地已经存在ubuntu:latest镜像了,所以并不需要再次从Docker Hub中下载,而是直接使用本地的ubuntu:latest镜像构建容器。
  • 启动容器之后,我们进入容器内部并在终端进行与容器交互。我们可以根据左侧的命令提示符判断自己是否在容器内部。例如上面的例子,当左侧的命令提示符为root@localhost时,表示我们在容器外部,而命令提示符为:root@fe263c9359dd/时,表示我们在容器内部,且容器的ID是fe263c9359dd。我们可以通过exit退出当前的容器。

4.2.第二种方式:启动一个已经终止的容器

docker run 每次都会创建一个新的容器,可以通过docker start命令,使用容器名或容器id启动一个已经终止的容器

docker start [选项] 容器 [容器2...]
  • docker start: Docker启动容器的命令关键词;
  • 选项: 命令选项;
  • 容器: 需要启动的容器,该容器用“容器ID”或“容器名”表示,如果指定了多个容器,那么就将这些容器都启动。

现有名为firstContainer的容器处于终止状态,可以通过docker start firstContainer启动它。

4.3.查看容器信息

如果既不知道容器名,又不知道容器id,可以使用docker ps 来查看容器的信息。

docker ps -a 可以查看Docker内的所有容器,

docker ps -a命令执行结果

[root@VM-8-9-centos ~]# docker ps -a
CONTAINER ID   IMAGE            COMMAND                 CREATED         STATUS     PORTS          NAMES

faeacc3b5aa7   busybox:latest   "echo 'Hello Docker'"   2 minutes ago   Exited (0) 2 minutes ago  first

5799b6f4eeee   busybox:latest   "echo 'Hello Docker'"   8 minutes ago   Exited (0) 8 minutes ago  first_docker_container

实例

创建并启动一个容器,容器名为firstContainer,具备busybox的运行环境。并输出hello world

docker busybox
docker run --name firstContainer busybox:latest echo "hello worldr"

5.停止一个容器

5.1.使用docker stop停止一个容器

docker stop可以用来终止一个正在运行的容器。它的命令格式如下:

docker stop [OPTIONS] Container [Container ...]

其中:

docker stop: Docker停止容器的命令关键词;

OPTIONS:命令选项,其中-t指定等待多少秒后如果容器还没终止,就强行停止,默认等待10秒;

Container:需要启动的容器,该容器用“容器ID”或“容器名”表示,如果指定了多个容器,那么就将这些容器都启动。

停止一个名为firstContainer的容器,可以执行

docker stop firstContainer

执行完成后该容器会处于终止状态,可以通过docker ps -a查看。

5.2.什么情况下容器启动后会立即终止?

除了使用docker stop命令来强制地终止一个容器以外,当容器的启动命令终结时,容器也自动会终止。以

docker run --name testcontainer ubuntu echo 'hello docker

为例,echo 'hello docker'就是该容器的启动命令。实际上执行完这条命令后,执行docker ps -a,可以发现testcontainer容器是处于终止状态的。

5.3.如何才能使容器启动后不立即终止

如果容器的主进程不停止,容器就不会停止。

5.3.1.将启动命令设置为死循环
docker run ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"

这条命令在创建并启动容器之后,会执行/bin/sh -c "while true; do echo hello world; sleep 1; done",由于该命令永远都不会执行完毕,除非强行终止,所以容器的主进程sh不会停止,因此容器也将不会停止。但是这样的做的话,无法正常的操作容器,而且它会占据资源,所以这种做法在实际的工作中意义并不大,/bin/sh -c 将字符串当命令来执行。

5.3.2.将命令设置为“启动一直运行的子进程”
docker run --name first_container -it ubuntu /bin/bash

执行完这条命令后,创建并启动容器之后,执行/bin/bash,会启动一个子进程,此时父进程(也就是容器的主进程sh)会进入sleep状态,由于sleep状态不是终止状态,所以容器会继续运行。

为什么在容器中输入exit或者执行ctrl D后,容器将会终止呢,这是因为exit会退出(结束)当前进程,也就是/bin/bash,由于子进程结束,sh主进程恢复到运行态,然而由于没有命令需要继续执行,所以sh主进程结,因此容器终止。

6.进入一个容器

6.1.进入一个docker容器的几种方法

1.使用ssh登录容器;

2.使用nsenter、nsinit等等第三方工具;

3.使用docker本身提供的工具

6.2.使用docker attach进入一个容器内部

命令

docker attach 容器ID或容器名字

首先使用docker run创建了一个容器,为其分配了伪终端,打开了它的标准输入流,并且让它在后台执行。然后使用docker attach进入了该容器内部,实际上就是进入容器“启动命令”的终端。

容器ID可以不用输全,只要能代表容器即可。例如下面的0539就是代表容器ID以0539开头的容器,一般情况下,前4位就能唯一标识一个容器了。

[root@localhost]# docker run -itd ubuntu /bin/bash
0539852938cdb9538f67750d07ed8c7fa072de742d5c0c02128576f2d227ec46
[root@localhost]# docker attach 0539
root@0539852938cd:/# 
root@0539852938cd:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr
root@0539852938cd:/# exit
exit
[root@localhost]#

6.3.使用docker exec进入一个容器内部

docker exec [选项] 容器名|容器ID 命令参数

实例:进入test2容器,创建一个名为HelloWorld的文件夹

docker pull ubuntu
docker run -itd --name test2 ubuntu /bin/bash
docker exec test2 mkdir HelloWorld

6.4.attach与exec的主要区别

  1. attach直接进入容器“启动命令”的终端,不会启动新的进程;
  2. exec则是在容器中打开新的终端,并且可以启动新的进程;
  3. 如果想直接在终端中查看容器“启动命令”的输出,用attach;其他情况使用exec。

7.删除容器

7.1.删除一个终止状态的容器

使用 docker rm 容器名|容器ID 来删除一个处于终止状态的容器,不加参数的情况下只能删除处于终止状态的容器。

步骤

1.docker ps -a查看所有的容器

2.查看容器的ID

3.执行docker rm 容器名或容器ID 命令

7.2.删除一个正在运行的容器

删除一个处于运行状态的容器有两种方式

1.执行docker stop停止该容器,然后使用docker rm删除

2.执行docker rm -f 强制删除

实例:

1.删除所有处于终止状态的容器

docker rm $(docker ps -aq)

2.删除所有容器

docker rm -f $(docker ps -aq)

三、镜像管理

1.基于Commit定制镜像

之前一直使用的镜像都是来自于官方Docker Hub中的镜像,有时候需要在这些镜像的基础上进行修改,来定制符合需求的镜像。

将“对容器的修改”保存为镜像

在Docker中提供了一个命令docker commit,该命令会把对容器的修改提交成一个镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层(该存储层仅仅保存了容器所做的修改),将这些内容构成一个新的镜像。docker commit的基本语法如下:

docker commit [选项] 容器名 [镜像名]

其中:

docker commit:Docker拉取镜像的命令关键词;

[选项]:命令选项,其中--author指定作者,--message制定commit的信息;

容名:容器的名字;

镜像名:新镜像的名字,以<仓库名>:<标签>的方式来指定。如果不显示设置,将默认为None:None。(这个代表没有指定镜像名)

实例

定制一个busybox:v1镜像,该镜像在busybox:latest的基础上,新增了一个hello.txt文件。

docker pull busybox
docker run --name container1 busybox touch hello.txt
docker commit container1 busybox:v1

使用commit定制镜像的缺陷,实际工作中很少用commit制作镜像

1.会保存很多不必要的文件

2.commit定制的镜像是黑箱镜像,除了制作人谁也不知道制作出来的,过一段时间可能作者都不知道怎么制作出来的,因此维护非常困难

2.基于save保存镜像与基于load加载镜像

为了防止镜像丢失或备份镜像;并且在需要的时候恢复镜像,就需要保存和加载镜像

2.1.将镜像保存到tar包

docker save [选项] 镜像名.tar  [镜像名.tar]

选项:

-o:指定写到一个文件中,而不是标准输出流中;

例如,将alpine:latest镜像保存到tar包,对应的语句如下:

docker save alpine:latest > alpine.tar
或者
docker save -o alpine:lateste alpine.tar

保存多个镜像

docker save alpine:latest ubuntu:latest > image.tar

2.2.从tar包加载镜像

docker load使用docker save保存的tar文件加载镜像,它的具体语法如下:

docker load [选项]

选项,

-i:指定从一个tar文件中读取,而不是标准输入流中。

例如,从alpine.tar中加载镜像,对应的语句如下:

docker load < alpine.tar
或者
docker load -i alpine.tar

如果一个tar包中包含多个镜像,那么会将这些镜像全部到加载进来。

结合这两个命令以及ssh甚至pv的话,利用 Linux强大的管道,我们可以写一个命令完成从一个机器将镜像迁移到另一个机器,并且带进度条的功能:

docker save <镜像名> | bzip2 | pv | ssh <用户名>@<主机名> 'cat | docker load'

实例:

1.busybox:latest镜像保存为一个tar包;

2.删除busybox:latest镜像后,从tar包加载busybox:latest镜像。

拉取镜像
docker pull busybox:latest
保存到tar包
docker save busybox:latest > busybox:latest.tar
删除镜像
docker rmi busybox:latest
从tar包加载镜像
docker load < busybox:latest.tar

3.导入导出容器

3.1.导出

docker export [选项] 容器名

选项:

-o指定写到一个文件中,而不是标准输出流中。

例如将容器Container1保存到tar包

docker export container1 > container1.tar
或者
docker export container1 -o container.tar

3.2.导入

docker import  文件或URL| - [镜像名]

文件|URL|: 指定docker import的对象,可以是文件或者某个URL;

[镜像名]: 以<仓库名>:<标签>的方式来指定。

例如从container1.tar中加载镜像,镜像名为test:v1.0

cat container1.tar | docker import - test:v1.0

docker export和docker save的区别

首先,两者的操作对象不同。docker save是将一个镜像保存为一个tar包,而docker export是将一个容器快照保存为一个tar包。

然后,docker export导出的容器快照文件将丢弃所有的历史记录和元数据信息,即仅保存容器当时的快照状态;而docker save保存的镜像存储文件将保存完整记录,体积也要大。下图就能够很好的说明,ubuntu:test仅仅占97.8MB而ubuntu:latest却占了120MB。

实例

将busyboxContainer容器的文件系统保存为一个tar包;

通过该tar包导入一个busybox:v1.0镜像。

docker pull busybox
docker run --name busyboxContainer busybox echo "Hello,World"
docker export busyboxContainer > busybox.tar
cat busybox.tar| docker import - busybox:v1.0

4.删除镜像

删除本地镜像可以使用docker rmi;rm是删除,i是image,镜像的意思。

删除镜像前需要删除容器,也可以使用 docker rmi -f强制删除镜像。

docker rmi [选项] image [image...]

选项:

-f:强制删除

image:需要删除的镜像。这里的镜像可以用“镜像短ID”、“镜像长ID”、“镜像名”、“镜像的digest”来标识。

使用下面的命令可以查看镜像的具体信息包括digest

docker images -- digest

删除ubuntu:latest镜像,有以下几种方法:

1.镜像短ID:docker rmi 14f6;(这个代表镜像id以14f6开头的镜像,一般而言,前四位可以唯一标志,如果不可以,docker会提示的)

2.镜像长ID:docker rmi 14f60031763d;

3.镜像名: docker rmi ubuntu:latest;

4.镜像的digest:docker rmi digest值

以上的方法都能删除掉ubuntu:v1镜像。但日常生活中,我们比较常用的是短ID以及镜像名,因为用起来最方便。

删除多个镜像

删除所有仓库名为redis的镜像,可以这么写:

docker rmi $(docker images -q redis)

删除所有镜像

docker rmi $(docker images -qa)

删除镜像前需要删除容器,也可以使用 docker rmi -f强制删除镜像。

5.构建私有Registry

5.1创建一个私人仓库

在Docker Hub中提供了创建私人仓库的镜像Resposity(镜像仓库):Registry

docker run -d -p 5000:5000 --restart=always --name registry registry:2

从这条命令可以看出,这个私人仓库以容器的形式运行着。其中--restart=always是指在Docker服务重启或者registry容器退出时会重新启动.

而-p是指将宿主机的5000端口映射到容器的5000端口,这样就可以通过宿主机ip:5000访问到容器的5000端口了。(registry容器默认会监听5000端口);-d参数是指在后台运行。

-v指定私人仓库的存储位置,添加-v /mnt/registry:/var/lib/registry可以将私人仓库的存储位置设置为宿主机的/mnt/registry。

5.2.将镜像推送到私人仓库

1.使用docker tag给镜像添加一个标签

如果想要将镜像推送到私人仓库而不是Docker Hub,首先必须使用docker tag命令,使用主机名和端口来标记一个镜像,如下所示,为ubuntu:latest镜像加上一个localhost:5000/my-ubuntu:latest的标签。

docker tag ubuntu:latest localhost:5000/my-ubuntu

使用docker push将镜像推送到私人仓库

使用docker push命令可以将镜像推送到仓库,默认情况下会将镜像推送到官方仓库Docker Hub中去,但是如果推送一个“用主机名和端口来标记”的镜像,那么就会推送到私人仓库。

docker push localhost:5000/my-ubuntu

5.3.从私人仓库拉取镜像

docker pull可以从仓库拉取某个镜像,默认情况下,也是从官方仓库拉取。当我想从私人仓库拉取my-ubuntu:latest镜像。执行以下命令就行了。

docker pull localhost:5000/my-ubuntu

5.4.查看或者删除私人仓库中的镜像

Docker提供的Registry镜像没有提供查看镜像和删除镜像的指令,但是有第三方的软件可以提供这些功能,例如:harbor。

5.5.删除私人仓库

私人仓库实质上就是一个容器,所以删除私人仓库就是删除私人仓库对应的容器。我们可以使用docker rm -f 强制删除删除它,但是这样删除之后,私人仓库中存储的镜像并不会被删除掉。如果你想在删除私人仓库的同时,也将镜像删除,需要添加-v参数,也就是docker rm -f -v。例如删除本地的私人仓库,可以执行以下语句:

docker rm -vf myregistry

实例

1.构建一个私人仓库
docker pull registry:2
docker run -d -p 5000:5000 --restart=always --name myregistry registry:2
2.拉取ubuntu镜像
docker pull ubuntu
3.使用docker tag给ubuntu加上一个标签localhost:5000/my-ubuntu:latest
docker tag ubuntu:latest localhost:5000/my-ubuntu:latest
4.将localhost:5000/my-ubuntu:latest镜像推送到私人仓库
docker push  localhost:5000/my-ubuntu:latest
5.删除本地镜像
docker rmi localhost:5000/my-ubuntu:latest
6.从私人仓库拉取localhost:5000/my-ubuntu:latest镜像
docker pull localhost:5000/my-ubuntu
7.删除私人仓库并将私人仓库中的镜像也删除掉
docker rm -vf myregistry

四、Dockerfile

1.初识Dockerfile

1.1.Dockerfile简介

Dockerfile描述了组装镜像的步骤,其中每一条命令都是单独执行的,除了FROM指令外,其他每一条指令都在上一条指定所生成的镜像基础上执行,执行完会生成一个新的镜像层,新的镜像层覆盖在原来的镜像层之上,从而形成了新的镜像。解决了之前提及的无法重复、镜像构建透明性和体积的问题。

1.2.Dockerfile基本的两条指令:FROM和RUN

FROM指定基础镜像; 格式:FROM<镜像名>或 FROM <镜像名>:<标签>。

FROM指令的功能是为后面的指令提供基础镜像,因此一个有效的Dockerfile必须以FROM指令作为第一条非注解指令。若FROM指令中tag参数为空,则tag默认为latest;若参数image或tag指定镜像不存在,则返回错误。

RUN执行命令; 格式:RUN <命令>

RUN 指令是用来执行命令行命令的。RUN指令会在前一条命令创建出的镜像的基础上创建一个容器,并在容器中运行命令。在命令结束运行后提交新容器为新镜像,新镜像被Dockerfile的下一条指令使用。

1.3.使用Dockerfile构建一个镜像

步骤

1.创建一个空的文件,并进入

2.创建一个名为Dockerfile的文件,并根据实际需要补全内容

3.使用dockerfile构建一个名为testimage的镜像docker build -t testimage .

-t 指定新镜像的镜像名

命令的最后有一个小数点不能丢

实例

使用Dockerfile构建一个名为testimage的镜像,该镜像具有ubuntu:latest的运行环境,并且在根目录下创建了一个Linux的文件夹

mkdir Ubuntuimage
cd Ubuntuimage
touch Dokerfile
vim Dockerfile
#添加下面的内容
FROM ubuntu:latest
RUN mkdir Linux

docker build -t testimage .

在Dockerfile的编写过程中一定要牢记一点:镜像的每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。

2.docker build、COPY和ADD

2.1.docker build命令详解

Dockerfile创建完成后,可以使用docker build命令根据Dockerfile构建一个镜像。

docker build [选项] 上下文路径|URL

选项:

-t:指定镜像名字;

-f:指定Dockerfile,不指定则默认使用当前文件夹下的Dockerfile。

除了从本地构建以外,docker build还支持从URL构建,比如可以直接从Git repo中构建。

2.2.COPY命令和ADD指令

COPY指令语法:

COPY <源路径>  <目标路径>

ADD指令语法:

ADD <源路径> <目标路径>

COPY与ADD指令在功能十分相似,但在COPY的路径上添加了一些功能。比如ADD的源文件路径可以是URL,这种情况下Docker会下载URL指向的文件。

当源文件路径是tar压缩文件时,使用COPY会拷贝源文件到指定目录,ADD命令自动解压这个压缩文件到指定目录。

实例

使用Dockerfile构建一个名为busybox:v3的镜像,具体要求如下:

以Ubuntu为基础镜像; 将目录下的dir1.tar“解压提取后”,拷贝到新镜像的/中; 使用docker build基于该Dockerfile构建一个名为Ubuntu:v3的镜像。

mkdir ubuntuImage
cd ubuntuImage
mkdir dir1 && -cvf dir1.tar dir1 && rm -rf dir1
touch Docfile
vim Dockerfile
#添加到Dockerfile文件中的内容
FROM ubuntu:latest
ADD ./dir1.tar /
#退出vim,以该Dockerfile构建一个名为busybox:v3的镜像
docker build -t ubuntu:v3 .

3.CMD指令和ENTRYPOINT指令

CMD和ENTRYPOINT都是为镜像指定容器启动命令的常用Dockerfile指令。

CMD和Entrypoint的区别

共同点:

指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD/ENTRYPOINT 命令。如果指定了多条命令,只有最后一条会被执行。 用法都支持exec和shell两种使用方式且用法相近

区别:

CMD: 启动容器时候指定了运行的命令,则会覆盖掉镜像中 CMD 指定的命令

ENTRYPOINT: 启动容器时候指定了运行的命令,并不会覆盖掉镜像中Entrypoint的命令

实例

要求:

1.以busybox:latest为基础镜像; 2.将启动命令设置为df -Th,要求df命令不能被覆盖,但-Th能够被覆盖。 3.使用docker build基于该Dockerfile构建一个名为mydisk:v1的镜像。

mkdir busyboxImage
cd busyboxImage
touch Dockerfile
echo "FROM busybox:latest" > Dockerfile
echo 'ENTRYPOINT ["df"]' >>Dockerfile
echo 'CMD ["-Th"]' >>Dockerfile
docker build -t mydisk:v1 .

4.ENV、EXPOSE、WORKDIR、ARG指令

WORKDIR指令

WORKDIR为其他指令设置工作目录; 格式:WORKDIR <工作目录路径>。

WORKDIR /tmp

WORKDIR指令为Dockerfile中的任何RUN,CMD,ENTRYPOINT,COPY和ADD指令设置工作目录(或称当前目录);如果WORKDIR对应的目录不存在,将会自动被创建。

EXPOSE指令

ENV设置环境变量; 格式:ENV 或ENV =;

这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,还是运行时的应用,都可以直接使用这里定义的环境变量。

ENV str="Hello,World"
ENV APACHE_HOME /var/tmp/apache-tomcat-8.0.45

ARG指令

ARG构建参数; 格式:ARG <参数名>[=<默认值>];

ARG与ENV有些类似,它们都可以被后面的其它指令直接使用,但是它并不是环境变量,这意味着将来容器运行时是不会存在ARG变量的。

什么时候用ARG,什么时候用ENV? 如果想保存为环境变量,就用ENV;如果只想在Dockerfile中临时使用,就用ARG。

EXPOSE指令

EXPOSE暴露端口; 格式:EXPOSE <端口1> [<端口2>...]

EXPOSE指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。如果想要公开容器的端口,必须在docker run是指定-p参数去公开端口或者指定-P参数公开所有被EXPOSE的端口。

在Dockerfile中写入这样的声明有两个好处

1.帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;

2.在运行时使用随机端口映射时,也就是 docker run -P时,会自动随机映射 EXPOSE 的端口。

实例

要求:

以busybox:latest作为基础镜像; 声明暴露3000端口; 将变量var1="test"设置为环境变量; 设置工作目录为/tmp,在工作目录下创建一个1.txt文件; 基于该Dockerfile文件,构建一个名为testimage:v1的镜像。

mkdir busyboxImage
cd busyboxImage
touch Dockerfile
echo "FORM busybox:latest" >Dockerfile
echo "EXPOSE 3000" >> Dockerfile
echo "ENV var1=test" >> Dockerfile
echo "WORKDIR /tmp" >>Dockerfile
echo "RUN touch 1.txt" >> Dockerfile

5.ONBUILD和VOLUME指令

ONBUILD指令

ONBUILD添加一个将来执行的触发器(trigger)

格式: ONBUILD <其它指令>

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如RUN, COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行,在镜像构建完成后,触发器指令会被清除,不会被后面的镜像继承。

VOLUME指令 VOLUME定义匿名卷; 格式:VOLUME ["<路径1>", "<路径2>"...]或VOLUME <路径>;

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于数据卷(volume)中。

6.镜像构建时的缓存机制

镜像构建时的缓存机制

在构建映像的过程中,Docker将按照指定的顺序逐步执行您的Dockerfile中的指令。随着每条指令的检查,Docker将在其缓存中查找可重用的现有映像,而不是创建一个新的(重复)映像。如果您不想使用缓存,可以在docker build命令中使用--no-cache = true选项。

五、数据卷操作

1.创建一个数据卷

Docker1.9版本以后,Docker提供了一条新的命令:docker volume,用来创建、查看、删除数据卷。而传统的docker run -v创建一个数据卷的方式也仍然保留了下来。

创建数据卷的方式

1.使用docker volume create创建一个数据卷,并指定数据卷的名字。

docker volume create --name vo1

2.创建新容器的时候,通过添加-v标签创建一个数据卷

docker run -itd -v/data ubuntu /bin/bash

3.在创建新容器的时候,指定数据卷的名字,并挂载到容器目录

docker run -itd -v vo2:/data ubuntu /bin/bash

日常工作一般都用第三种,创建容器指定数据卷的名字,并挂载到指定的目录

实例

创建一个名为vo1的数据卷,并将该数据卷挂载到container1容器的/dir1目录。

docker pull ubuntu
docker run -v vo1:/dir --name container1  ubuntu

2.挂载和共享数据卷

2.1.挂载数据卷

默认情况下,在创建数据卷时,会在宿主机中的/var/lib/docker/volume/下创建一个以“数据卷名”为名的目录,并将数据卷的内容保存在该目录下的/_data目录下;也就是将数据卷的内容保在/var/lib/docker/volume/数据卷名/data中,数据卷的内容会和容器的挂载点始终保持一致。“数据卷名”可以用户指定,如果不指定,就会随机生成一个“数据卷名”。

挂载宿主机目录

将宿主机的/host/dir挂载到了容器的/container/dir目录。

docker run --name vocotainer1 -v /host/dir:/container/dir ubuntu

需要注意的是,宿主机的目录和容器的目录必须使用绝对路径。如果宿主机不存在/host/dir目录,则会创建一个空文件夹。在/host/dir下的所有文件和文件夹都可以在容器中在/container/dir下被访问。如果镜像中本来就存在/container/dir文件夹,那么该文件夹下所有内容都会被删除,保证与宿主机中文件夹一致。

同时创建多个数据卷

在挂载的时候也可以同时创建多个数据卷,例如下面的命令就创建了两个数据卷co3和co4

docker run --name vocotainer2 -v co3:/data -v co4:/dir1 ubuntu

与其他容器共享数据卷

在使用docker run创建并启动一个新容器时,也可以使用--volumes-from标签使容器与已有的容器共享数据卷;下面的命令创建了一个名为vocotainer3的容器,并与vocontainer1共享数据卷。因为vocontainer1的挂载点在/container/dir上,所以如果vocotainer3的挂载点也将会是/container/dir。

docker run --name vocotainer3 --volumes-from vocontainer1 ubuntu

通常如果有一些文件如果需要被多个容器共享,一种常见的做法就是创建一个数据容器(该容器仅仅用来共享数据而不做其他用途),其他容器与之共享数据卷。

实例

要求:

创建一个名为container1的容器,并将本地主机的/dir1目录挂载到容器中的/codir1中; 创建一个名为container2的容器,与container1共享数据卷。

docker pull ubuntu
docker run -v /dir:codir --name container1 ubuntu
docker run --volumes-from container1 --name container2 ubuntu

3.查看数据卷的名字

3.1.查看数据卷的具体信息

查看数据卷先创建数据卷,没有指定数据卷的名字

docker run -v /data --name vocontainer1 ubuntu

查看数据卷

docker inspect --type container vocontainer1

会输出ID,创建时间,名字和其他很多参数。

3.2.仅查看数据卷的名字

通过--format来解析docker inspect

docker inspect --type container --format='{{range .Mounts}}{{.Name}}{{end}}' 容器名|容器ID

查看vocontainer1的数据卷名字

docker inspect --type container --format='{{range .Mounts}}{{.Name}}{{end}}' vocontainer1

4.删除数据卷

第一种方式

如果该数据卷还被其他容器使用就会删除失败,没有被其他容器使用就会删除

docker volume rm 数据卷名

第二种方式

删除容器的同时删除数据卷,分两种情况:

1.在创建数据卷的时候没有指定数据卷的名字

如果该数据卷还被其他容器使用就会删除失败,没有被任何容器使用就会删除

2.在创建数据卷的时候指定了数据卷的名字

删除容器只会解除数据卷与容器之间的联系,要删除数据卷需要使用第一种方式

docker rm -v 容器名或容器ID

第三方式

在创建容器时指定了--rm标签,那么在容器处于“终止状态”时就会删除容器以及尝试删除容器所对应的数据卷。

如果没有指定了数据卷名,那么将删除对应的数据卷。如果指定了数据卷名,也只是解除了数据卷和容器的联系,真正要删除,还是要使用第一种方式。

删除无用的数据卷

如果执行下面这条命令,那么会将所有没有被容器使用的数据卷删除掉。

docker volume prune

实例

docker pull ubuntu
docker run -v vo4:/data --name container1 ubuntu
docker rm -v container1
docker volume rm vo4

5.备份和恢复数据卷

1.备份数据卷

首先创建一个容器vocontainer1,并创建了一个名为db1的数据卷,将数据卷挂在到容器的/dbdate目录。

docker run -v db1:/dbdate --name vocontainer1 ubuntu

首先进入一个空白目录,使用--volumes-from创建一个新容器,这样新容器与container1容器共享dbdata挂载目录,同时把主机上的当前目录挂载到容器的 /backup 目录

docker run --volumes-from container1 -v $(pwd):/backup ubuntu tar -cvf /backup/backup.tar /dbdata

容器启动后,使用了tar 命令来将 dbdata目录压缩,并保存在 /backup/backup.tar文件中,由于主机的当前目录挂载在容器的/backup目录下,而绑定挂载的两个目录的内容完全保持一致,所以相当于将dbcontainer1数据卷的内容压缩后备份到了宿主机的当前目录了。

2.恢复一个数据卷

首先创建一个带有空数据卷的容器container2,挂载目录为/dbdata,数据卷名为db1。

docker run -v db1:/dbdata --name container2 ubuntu /bin/bash

然后进入之前保存backup.tar的宿主机目录,在该目录下执行下面命令,该命令创建一个新容器,新容器与container2容器共享dbdata挂载目录,同时将主机的当前目录挂载的容器的/backup中。

docker run --volumes-from container2 -v $(pwd):/backup busybox tar -xvf /backup/backup.tar -C /dbdata

启动容器时,使用tar命令将数据卷的备份文件backup.tar解压到/dbdata目录,由于该容器与container2容器共享一个数据卷,也就相当于将backup.tar解压到了container2的/dbdata目录。又因为container2将名为db1的数据卷挂载到了/dbdata上,所以实质上就将db1的数据卷内容完全恢复了!

实例:备份和恢复vo1数据卷

创建vo1数据卷并在数据卷中添加1.txt

docker pull ubuntu
docker run --name vocontainer -v vo1:/dir1 ubuntu touch /dir1/1.txt

将vo1数据卷的数据备份到宿主机的/newback中,将容器的/backup路径挂载上去,并将容器内/dir1文件夹打包至/backup/backup.tar

docker run --volumes-from vocontainer1 -v /newback:/backup ubuntu tar -cvf /backup/backup.tar  /dir

删除所有的容器以及它使用的数据卷

docker rm -vf $(docker ps -aq)
docker volume rm vo1

在次创建一个vo1的数据卷

docker run -itd --name vocontainer2 -v vo1:/dir1 ubuntu /bin/bash

将保存在宿主机中备份文件的数据恢复到vocontainer2的/中

docker run --volumes-from vocontainer2 -v /newback:/backup ubuntu tar -xvf /backup/backup.tar -C /