Docker

Docker

Docker架构

Docker工作流程

概念说明

Docker 镜像(Images)

Docker 镜像是用于创建 Docker 容器的模板,比如 Ubuntu 系统。

Docker 容器(Container)

容器是独立运行的一个或一组应用,是镜像运行时的实体。

Docker 客户端(Client)

Docker 客户端通过命令行或者其他工具使用 Docker SDK (https://docs.docker.com/develop/sdk/) 与 Docker 的守护进程通信。

Docker 主机(Host)

一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。

Docker Registry

Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。

Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。

一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

Docker Machine

Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。

## docker run
1
docker run ubuntu:15.10 /bin/echo "Hello world"
  • docker: Docker 的二进制执行文件。
  • run: 与前面的 docker 组合来运行一个容器。
  • ubuntu:15.10 指定要运行的镜像,Docker 首先从本地主机上查找镜像是否存在,如果不存在,Docker 就会从镜像仓库 Docker Hub 下载公共镜像。
  • /bin/echo “Hello world”: 在启动的容器里执行的命令
1
docker run -i -t ubuntu:15.10 /bin/bash
  • -t: 在新容器内指定一个伪终端或终端。
  • -i: 允许你对容器内的标准输入 (STDIN) 进行交互

输入后会直接进入到ubuntu15.10的交互界面中,即装了Ubuntu镜像的容器。

如果通过exit退出容器后,该容器也会停止。而Ctrl+P+Q退出则不会停止容器。

1
docker run -d ubuntu:15.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
  • -d:让容器在后台运行,执行命令为**-c**所跟内容

注意的是:不会进入容器的交互界面,因此也无法看到循环输出。如果想查看输出,docker logs 2b1b7a428627(容器ID)可以在外部查看容器输出

容器

1
docker pull tomcat

本地需要先拉取镜像,同一镜像可能会有不同版本,使用docker search tomcat可在dockerhub中找到所有镜像

当然也可以直接去Docker Hub官网搜索

查找tomcat镜像

复制命令即可拉取
1
docker ps -a

查看所有容器。docker ps -a -q则只查看容器ID

CONTAINER ID: 容器 ID。

IMAGE: 使用的镜像。

COMMAND: 启动容器时运行的命令。

CREATED: 容器的创建时间。

STATUS: 容器状态。

  • created(已创建)
  • restarting(重启中)
  • running 或 Up(运行中)
  • removing(迁移中)
  • paused(暂停)
  • exited(停止)
  • dead(死亡)

PORTS: 容器的端口信息和使用的连接类型(tcp\udp)。

NAMES: 自动分配的容器名称。也可以通过 --name CustomName来自定义容器名,Names在许多命令中可以替换<容器 ID>

1
2
docker stop <容器 ID>		#停止容器
docker restart <容器 ID> #重启容器
1
2
docker attach <容器 ID>	#进入后台容器,但是exit退出后会随之停止容器
docker exec <容器 ID> #进入后台容器,exit后不会停止容器
1
docker rm -f <容器 ID>	#删除容器
1
2
3
docker logs -f <容器 ID>	#查看容器输出日志
docker top <容器 ID> #查看容器内部运行进程
docker inspect <容器 ID> #查看记录容器配置信息和状态的JSON文件

镜像

1
docker images

本地镜像

  • REPOSITORY:表示镜像的仓库源
  • TAG:镜像的标签
  • IMAGE ID:镜像ID
  • CREATED:镜像创建时间
  • SIZE:镜像大小

因为同一个仓库源可以有多个TAG,代表不同版本。所以使用 REPOSITORY:TAG 来定义不同的镜像,不加TAG则默认为latest。

1
2
docker pull ubuntu:13.10
docker search httpd
1
docker rmi hello-world	#删除镜像
1
docker tag <镜像 ID> runoob/centos:dev #为镜像再设置标签

容器连接

端口映射

1
docker run -d -P training/webapp python app.py

运行一个简易Web容器,本地不存在Docker会自动获取并下载。查看端口映射情况

1
2
3
root@levi:~$ docker ps
CONTAINER ID IMAGE COMMAND ... PORTS NAMES
fce072cc88ce training/webapp "python app.py" ... 0.0.0.0:32768->5000/tcp grave_hopper

访问服务器的32768端口就可以映射到该容器,容器中运行app.py,故而可看到“Hello World”输出,访问之前记得开放安全组的32768端口。

  • -P :**是容器内部端口随机**映射到主机的高端口。
  • -p : 是容器内部端口绑定到指定的主机端口。

-p

1
2
3
docker run -d -p 5001:5000 training/webapp python app.py #5001映射到5000
docker run -d -p 127.0.0.1:5001:5000 training/webapp python app.py #通过访问 127.0.0.1:5001 来访问容器的 5000 端口。
docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py #默认绑定tcp,也可以绑定udp

容器互联

1
docker network create -d bridge test-net

创建新网络,-d指明网络类型为bridge,还有一种为overlay

1
docker network ls #可以列出所有网络

运行两个容器并连接到test-net网络

1
2
docker run -itd --name test1 --network test-net ubuntu /bin/bash
docker run -itd --name test2 --network test-net ubuntu /bin/bash

进入其中一个容器,可以通过ping指令连接到其他容器

1
2
docker exec -it test1 /bin/bash
ping test2

容器数据卷

容器在运行中生成的数据会随着容器的删除随之消失,为了做到数据持久化,即把容器的数据同步到本地,为此Docker提供了数据卷(Data Volume)。此外,也可以将数据写入镜像中并commit,这样再创建容器的时候就会读到这些数据了。

先导

1)一个数据卷是一个特别指定的目录,该目录利用容器的UFS文件系统可以为容器提供一些稳定的特性或者数据共享。数据卷可以在多个容器之间共享。

2)创建数据卷,只要在docker run命令后面跟上-v参数即可创建一个数据卷,当然也可以跟多个-v参数来创建多个数据卷,当创建好带有数据卷的容器后,就可以在其他容器中通过**–volumes-froms**参数来挂载该数据卷了,而不管该容器是否运行。也可以在Dockerfile中通过VOLUME指令来增加一个或者多个数据卷。

3)如果有一些数据想在多个容器间共享,或者想在一些临时性的容器中使用该数据,那么最好的方案就是你创建一个数据卷容器,然后在该临时性的容器中挂载该数据卷容器的数据。这样,即使删除了刚开始的第一个数据卷容器或者中间层的数据卷容器,只要有其他容器使用数据卷,数据卷都不会被删除的。

4)不能使用docker export、save、cp等命令来备份数据卷的内容,因为数据卷是存在于镜像之外的。备份方法: 创建一个新容器,挂载数据卷容器,同时挂载一个本地目录,然后把远程数据卷容器的数据卷通过备份命令备份到映射的本地目录里面。

5)可以把一个本地主机的目录当做数据卷挂载在容器上,同样是在docker run后面跟-v参数,不过-v后面跟的不再是单独的目录了,它是-v [host-dir]:[container-dir]:[rw|ro]这样格式的,其中host-dir是一个绝对路径的地址,如果host-dir不存在,则docker会创建一个新的数据卷,如果host-dir存在,但是指向的是一个不存在的目录,则docker也会创建该目录,然后使用该目录做数据源。

构建数据卷

只指定容器内路径

在docker run创建容器的时候,可以通过-v来指定容器挂载的数据卷。

1
docker run -it --name LeviVolume -v /home ubuntu /bin/bash

在容器外通过docker inspect LeviVolume便可找到数据卷的挂载信息。其中Source就是本机的数据卷路径。

匿名挂载&具名挂载

如上,-v /home就是匿名挂载,该卷是没有自定义卷名的。系统分配的是f645b8ff

我们也可以通过具名挂载-v UbuntuVolume:/home来指定该卷名为UbuntuVolume。

匿名挂载和具名挂载仅是卷名的区别,但值得留意的是,只指定容器内路径创建数据卷,该卷的Source都会放在/var/lib/docker/volumes/<卷名>/_data

指定本地和容器内路径

可以指定本机目录/home/HomeFile作为数据卷,设置可读写权限。

1
docker run -it --name LeviVolume -v /home/HomeFile:/home:rw ubuntu /bin/bash

数据卷容器

可以专门生成一个存放数据卷的容器,其他需要共享数据的容器可以直接挂载在该容器的下面。

1
docker run -it -v /var/volume1 -v /var/volume2 --name LeviVolume ubuntu /bin/bash

其他容器就可以挂载在LeviVolume下,这会继承LeviVolume的两个数据卷:

1
docker run -it --rm --volumes-from LeviVolume --name Sub_Container ubuntu /bin/bash

仓库

1
docker login #登入DockerHub

登入之后除了可以重dockerhub中pull镜像,search镜像外,还能通过push到dockerhub上。

1
docker push <username>/ubuntu:18.04

DockerFile

我们可以通过DockerFile自定义镜像,从而推到DockerHub上以供他人使用

制定镜像

  1. 创建Dockerfile文件,填写指令
1
2
FROM nginx
RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/html/index.html
  1. FROM和RUN

FROM:定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。

RUN:用于执行后面跟着的命令行命令。有以下俩种格式:

1
2
RUN <命令行命令>	# <命令行命令> 等同于,在终端操作的 shell 命令。
RUN ["可执行文件", "参数1", "参数2"] #等价于 RUN ./test.php dev offline

注意的是,Dockerfile中每一行指令都会新建一层,因此能同时执行的命令都用&&连接命令,以免造成镜像膨胀过大

1
2
3
RUN yum install wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN tar -xvf redis.tar.gz
1
2
3
RUN yum install wget \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& tar -xvf redis.tar.gz
  1. 构建镜像

在Dockerfile的目录下,执行创建动作

1
docker build -t nginx:v3 .	# .为上下文路径

由于docker的运行模式是C/S,docker引擎是S。实际的构建过程中是在docker引擎下完成的,因此我们可能需要吧本机的一些文件打包给docker引擎。如果没有指明上下文路径,则默认为Dockerfile所在位置

因此,上下文路径下要尽量干净,否则会因为文件过多而造成过程迟缓。

Dockerfile名字可以自定义,但是此时需要加上-f <DockerfileName>

Dockerfile指令

COPY

1
COPY [--chown=<user>:<group>] <源路径1>...  <目标路径>

**[–chown=:]**:可选参数,用户改变复制到容器内文件的拥有者和属组。

**<源路径>**:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则

**<目标路径>**:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建

ADD

1
ADD [--chown=<user>:<group>] <源路径1>...  <目标路径>

ADD和COPY功能一样,官方推荐COPY。只是ADD再传输tar压缩文件时,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。但是在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效

CMD & ENTRYPOINT

相较于RUN,CMD& ENTRYPOINT是在docker run时运行,而RUN是在docker build

CMD与ENTRYPOINT的作用一致,都是在执行镜像后再紧接着执行该命令。如CMD /bin/bash,docker run后直接执行/bin/bash来启动bash终端,从而允许人机交互。又如CMD ["ls","-a"],docker run后直接执行ls -a。

注意,CMD和ENTRYPOINT都是为新容器开启一个进程,所以不是以docker run ubuntu /bin/bash形式执行镜像的情况下,我们要加上CMD或ENTRYPOINT来保证容器中有进程。否则该容器会被自动回收。

CMD
1
2
3
CMD <shell 命令> 
CMD ["<可执行文件或命令>","<param1>","<param2>",...]
CMD ["<param1>","<param2>",...] #和ENTRYPOINT同时存在才有意义

CMD为启动容器指定的默认运行程序,程序运行结束后容器也随之结束。所以,如果Dockerfile中存在多个CMD指令,仅有最有一个生效(即只有最后的CMD对应的层会在docker build时添加到镜像中,但所有CMD都会执行一遍。生效和执行是两个概念!!)

CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。

CMD [“ls”,”-a”]会被docker run <自定义的镜像名> ls -a -l覆盖,即执行ls -al。但是docker run <自定义镜像名> -l时会报错,因为-l不会覆盖ls -a。

ENTRYPOINT
1
ENTRYPOINT ["<可执行文件或命令>","<param1>","<param2>",...]

docker run命令行参数不会覆盖ENTRYPOINT,而是将命令行参数作为参数送给ENTRYPOINT

ENTRYPOINT [“ls”,”-a”]在docker run <自定义镜像名> -l时不会报错,而是将其追加到ENTRYPOINT之后执行ls -al


当ENTRYPOINT和CMD同时出现时,可以达到缺省值的效果,配合CMD ["<param1>","<param2>",...]使用可以为ENTRYPOINT指定默认参数。

1
2
3
4
FROM nginx

ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参

当docker run不传参数docker run nginx:test时,容器执行:

1
nginx -c /etc/nginx/nginx.conf

当docker run传入参数docker run nginx:test -c /etc/nginx/new.conf,执行

1
nginx -c /etc/nginx/new.conf

ENV

1
2
ENV <key> <value>
ENV [<key>=<value>]+

示例设置 NODE_VERSION = 7.2.0 , 在后续的指令中可以通过 $NODE_VERSION 引用

1
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"

ARG

1
ARG <参数名>[=<默认值>]

与ENV作用一致,只是其作用域仅在Dockerfile内,经过docker build后便会消失。

docker build时可以通过 --build-arg <参数名>=<值> 来覆盖。

VOLUME

1
2
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。如果忘记挂载数据卷,会自动加载VOLUME。

此方式是匿名挂载,<路径1>就是容器内的Destination。

EXPOSE

1
EXPOSE <端口1> [<端口2>...]

将容器的端口暴露出来,这样在运行随机端口映射docker run -P 时,也只会随机选取暴露的端口使用。

WORKDIR

1
WORKDIR <工作目录路径>

指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在,但工作目录必须提前创建好。

制定工作目录后,该镜像对应容器会直接进入WORKDIR,进入容器后使用pwd可以看到当前目录。

USER

1
USER <用户名>[:<用户组>]

用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)

LABEL

1
LABEL <key>=<value> <key>=<value> <key>=<value> ...

为镜像添加一些元数据如作者等信息。

Compose

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!