《Docker技术入门与实践》笔记
2 核心概念
Docker的三个核心概念分别为:镜像、容器和仓库。
2.1 镜像
Docker镜像类似于虚拟机镜像,可以理解为一个只读的模板。
2.2 容器
容器的定义:容器有效地将由单个操作系统管理的资源划分到孤立的组中,以更好地在孤立的组之间平衡有冲突的资源使用需求。与虚拟化相比,这样既不需要指令级模拟,也不需要即时编译。容器可以在核心CPU本地运行指令,而不需要任何专门的解释机制。此外,也避免了准虚拟化和系统调用替换中的复杂性。
可以将Docker容器理解为一种轻量级的沙盒。每个容器内运行着一个应用,不同的容器相互隔离,容器之间也可以通过网络互相通信。容器的创建和停止都十分快速,几乎跟创建和终止原生应用一致;另外,容器自身对系统资源的额外需求也十分有限,远远低于传统虚拟机。很多时候,甚至直接把容器当作应用本身也没有任何问题。
沙盒是一种虚拟技术,多用于计算机安全技术,其原理是通过重定向技术,把程序生成和修改的文件重定向到自身文件夹中,当某个程序试图发挥作用时,安全软件可先让它在沙盒中运行,如有恶意行为,则禁止程序进一步运行,不会对系统造成危害。
2.3 仓库
- docker仓库是集中存放镜像文件的场所
- 仓库注册服务器(Registry)是存放仓库(Repository)的地方
- 一个Registry可以存放多个Repository,每个Repository中存放一类镜像,用标签(tag)进行区分
3 使用docker镜像
docker运行容器前需要本地存在对应的镜像,如果镜像没保存在本地,docker会尝试先从默认镜像仓库(Docker Hub公共注册服务器中的仓库),用户也可以通过配置,使用自定义的镜像仓库。
3.1 获取镜像
官网Docker Hub(需要VPN才能注册)提供了镜像给用户下载。我们可以直接从官网拉下镜像,命令格式:
$ docker pull NAME[:TAG]
例子:
获取ubuntu 14.04的基础镜像:
$ docker pull ubuntu:14.04
若省略了TAG,则默认下载最新版本。严格的讲,镜像仓库名称还应该加上仓库地址(即注册服务器)作为前缀,只是默认从Docker Hub上下载,所以该前缀可以忽略,实际是:
$ docker pull registry.hub.docker.com/ubuntu:14.04
如果从非官方仓库下载镜像,则需要在仓库名称前指定完整的仓库地址。
镜像文件一般由若干层组成,每个层由一个唯一id(256位)表示。当不同镜像包括相同的层时,本地仅存储该层的一份内容,减少了存储空间。
利用镜像创建容器,运行bash应用:
$ docker run -it ubuntu:15.04 bash
root@3d5884dd705a:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
3.2 查看镜像信息
列出本地主机上已有镜像信息:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 15.04 d1b55fd07600 2 years ago 131MB
- REPOSITORY:来自哪个仓库。
- TAG:标签信息,标记同一仓库的不同镜像。
- IMAGE ID:镜像ID(唯一标志符),一般可以使用该ID的前若干个字符组成的可区分串来替代完整的ID。
- CREATED:创建时间,说明镜像最后更新时间。
- SIZE:镜像大小(逻辑大小),实际物理上占用的空间会小于各镜像的逻辑大小之和。
image子命令:
- -a,--all=true|false:列出所有镜像文件(包括临时文件),默认为否
- --digests=true|false:列出摘要,默认为否
- -f,--filter=[]:过滤列出的镜像
- --format="TEMPLATE":控制输出格式
- --no-trunc=true|false:对输出结果中太长的部分是否进行截断,默认为是
- -q,--quit=true|false:仅输出ID信息,默认为否
为镜像添加一个tag,与原先的tag指向同一个镜像,因此实际占用内存只有一个镜像大小:
$ docker tag ubuntu:15.04 myubuntu:15.04
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myubuntu 15.04 d1b55fd07600 2 years ago 131MB
ubuntu 15.04 d1b55fd07600 2 years ago 131MB
查看一个镜像的详细信息:
$ docker inspect ubuntu:15.04
以上返回结果是json格式的消息,可以只获取其中一项,如操作系统:
$ docker inspect ubuntu:15.04 -f {{".Os"}}
查看镜像历史:
$ docker history ubuntu:15.04
3.3 搜索镜像
搜索远端仓库中共享的镜像,默认搜索官网仓库Docker Hub的镜像。
例子:搜索所有自动创建的星超过100的带ubuntu的镜像:
$ docker search --filter=is-automated=true --filter=stars=3 ubuntu
DESCRIPTION STARS OFFICIAL AUTOMATED
...
3.4 删除镜像
- 用标签删除镜像:
$ docker rmi [-f] ubuntu:15.04
- 当同个镜像拥有多个标签时,docker rmi只是删除该镜像多个标签中的指定标签而已,并不影响镜像文件。
- 但当镜像只剩下一个标签时,docker rmi会彻底删除镜像。
- 用ID删除镜像,会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身:
$ docker rmi [-f] d1b55fd07600
当镜像创建的容器存在时,镜像文件无法直接删除,需要增加-f参数。但正确的做法是先删除镜像的所有容器,再删除镜像。
3.5 创建镜像
创建镜像的主要方法有:
- 基于已有镜像的容器创建
- 基于本地模板导入
- 基于Dockerfile创建
- 基于已有镜像的容器创建:
先对已有镜像进行修改,然后再提交:
$ docker commit -m "... ..." -a "author name" 容器ID Name:TAG
- -a,--author="":作者信息
- -c,--change=[]:提交时执行Dockerfile指令
- -m,--message="":提交消息
- -p,--pause=true:提交时暂停容器运行
- 基于本地模板导入:
可以直接从一个操作系统模板导入一个镜像,模板可以从OpenVZ下载。导入的方法如下:
$ docker import [OPTIONS] file|URL|-[REPOSITORY[:TAG]]
例子:
$ cat ubuntu-14.04-x86.tar.gz | docker import - ubuntu:14.04
或
$ docker import ubuntu-14.04-x86.tar.gz ubuntu:14.04
ubuntu-14.04-x86.tar.gz为下载的模板。
3.6 存出和载入镜像
- 存出镜像
将镜像ubuntu:15.04导出为ubuntu_15.04.tar:
$ docker save -o ubuntu_15.04.tar ubuntu:15.04
- 载入镜像
$ docker load --input ubuntu_15.04.tar
或:
$ docker load < ubuntu_15.04.tar
3.7 上传镜像
需要先登陆:
$ docker login
添加标签:
$ docker tag ubuntu:15.04 hanzai/ubuntu:15.04 // hanzai是我的账号名
上传镜像:
$ dokcer push NAME[:TAG] // 默认上传到Docker Hub官方仓库
或:
$ docker push [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
$ docker push hanzai/ubuntu:15.04
4 操作Docker容器
容器是镜像的一个运行实例,不同的是,镜像是静态的只读文件,而容器带有运行时需要的可写文件层。
4.1 创建容器
$ docker create -it ubuntu:14.04 // 创建容器,会为该容器分配一个名称,可用docker ps查看,也可以用--name string参数来指定容器名称
d4f5a0e3ae733.... // 容器ID
$ docker start d4f5 // 启动容器
或
$ docker run -it ubuntu:14.04 /bin/bash // 创建并启动容器
# exit 或 Ctrl+d // 退出容器
或
$ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
// 后台以守护进程的形式运行
ab855a5b5af2b... // 容器ID
$ docker logs ab855 // 查看日志
hello world
hello world
...
使用docker run时,后台运行的标准操作为:
- 检查本地是否存在制定的镜像,不存在就从公有仓库下载
- 利用镜像创建,并启动一个容器
- 分配一个文件系统给容器,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从网桥的地址池配置一个ip地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被自动终止
4.2 终止容器
$ docker stop [-t|--time[=10]] [CONTAINER] // 等待一段时间(默认10秒)后关闭容器
$ docker restart [CONTAINER] // 重启容器
4.3 进入容器
使用-d参数启动容器后会进入后台,用户无法看到容器中的信息,也无法进行操作.
- attach命令
$ docker attach [--detach-keys[=[]]] [--no-stdin] [--sig-proxy[=true]] CONTAINER_NAME // CONTAINER_NAME可以使用docker ps查询
--detach-keys[=[]] : 退出attach模式的快捷键序列,默认CTRL-p或CTRL-q.
--no-stdin=true|false : 是否关闭标准输入,默认打开.
--sig-proxy=true|false : 是否代理收到的系统信号给应用进程,默认true.
当多个窗口同时使用attach连接到同一个容器时,所有窗口都会同步显示.
- exec命令
$ docker exec [-d|--detach] [--detach-keyss[=[]]] [-i|--interactive] [--privileged] [-t|--tty] [-u|--user[=USER]] CONTAINER COMMAND [ARG...]
-i,--interactive : 打开标准输入接受用户输入命令,默认false.
--privileged : 是否给执行命令最高权限,默认false.
-t,--tty : 分配伪终端,默认false.
-u,--user[=USER] : 执行命令的用户名或ID.
如:
$ docker exec -it ab855a5b5af2b /bin/bash
4.4 删除容器
删除处于终止或退出状态的容器:
$ docker rm [-f|--force] [-l|--link] [-v|--volumes] CONTAINER [CONTAINER...]
-f,--force : 是否强行终止并删除一个运行中的容器.
-l,--link : 删除容器连接,但保留容器.
-v,--volumes : 删除容器挂载的数据卷.
4.5 导入和导出容器
- 导出容器
导出容器是指导出一个已经创建的容器到一个文件中,不管容器是否处于运行状态.
$ docker export -o filename.tar CONTAINER
或
$ docker export CONTAINER > filename.tar
- 导入容器
导出的文件可以使用docker import导入变成镜像:
$ docker import [-c|--change[=[]]] [-m|--message[=MESSAGE]] file|URL|[REPOSITORY[:TAG]]
例如:
$ docker import filename.tar REPOSITORY:TAG
5 访问Docker仓库
- docker仓库是集中存放镜像文件的场所,分公有仓库和私有仓库
- 仓库注册服务器(Registry)是存放仓库(Repository)的地方
- 一个Registry可以存放多个Repository,每个Repository中存放一类镜像,用标签(tag)进行区分
5.1 Docker Hub
登陆:
$ docker login
镜像资源分为两类:
- 基础镜像/根镜像:由Docker公司创建,验证,支持,提供.往往使用一个单词作为镜像名称.
- 用户创建的镜像:由Docker用户创建并维护,带有用户名称为前缀,表明是某用户下的某仓库.
docker支持自动创建功能,允许用户通过Docker Hub指定跟踪一个目标网站(目前支持Github或BitBucket)上的项目,一旦项目发生新的提交,则自动执行创建.
6 Docker数据管理
容器中的数据管理方式主要有:
- 数据卷:容器内数据直接映射到本地主机环境;
- 数据卷容器:使用特定容器维护数据卷。
6.1 数据卷
数据卷是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器。其提供了很多特性:
- 数据卷可以在容器之间共享和重用,容器间传递数据将变得高效方便;
- 对数据卷内数据的修改会立马生效,无论是容器内操作还是本地操作;
- 对数据卷的更新不会影响镜像,解耦了应用和数据;
- 卷会一直存在,直到没有容器使用,可以安全地卸载它。
- 在容器内创建一个数据卷:
使用ubuntu:14.04镜像创建一个ubuntu容器,使用-v参数创建一个数据卷挂载到容器的/hanry目录中:
$ docker run -it --name ubutnu -v /hanry ubuntu:14.04 /bin/bash
- 挂载一个主机目录作为数据卷:
使用ubuntu:14.04镜像创建一个ubuntu容器,使用-v参数指定挂载一个本地已有目录F:\forDocker到容器的/hanry目录中作为数据卷:
$ docker run -it --name ubutnu -v F:\forDocker:/hanry ubuntu:14.04 /bin/bash
docker挂载数据卷的默认权限是读写(rw),可以通过设置修改:
$ docker run -it --name ubutnu -v F:\forDocker:/hanry:ro ubuntu:14.04 /bin/bash
在这个过程中可能会遇到目录无法共享或防火墙问题,可参考以下方式解决:
解决共享磁盘问题的方案
6.2 数据卷容器
如果用户需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器也是一个容器,但它的目的是专门用来提供数据卷供其他容器挂载。
首先,创建一个数据卷容器dbdata,并在其中创建一个数据卷挂载到/dbdata:
$ docker run -it -v /dbdata --name dbdata ubuntu:14.04
然后,在其他容器中使用--volumes-from参数来挂载dbdata容器中的数据卷:
$ docker run -it --volumes-from dbdata --name db1 ubuntu:14.04
$ docker run -it --volumes-from dbdata --name db2 ubuntu:14.04
这样,在dbdata、db1、db2三个容器中的/dbdata目录下修改数据,其他容器都能看到。
若删除挂载的容器,数据卷不会自动删除。若要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式使用-v参数来删除:
$ docker rm -v CONTAINER // 在删除容器时,顺便删除容器挂载的数据卷
6.3 利用数据卷容器来迁移数据
- 备份
1、从dbdata容器中挂载数据卷,也就是挂载到目录/dbdata
2、将本地目录F:\Docker2挂载到容器中的/backup目录作为数据卷
3、使用镜像ubuntu:14.04创建并启动一个worker容器
4、worker容器启动后,将/dbdata目录下内容打包压缩到容器内的/backup/backup.tar,也就是主机F:\Docker2目录下的backup.tar
$ docker run -it --volumes-from dbdata -v F:\Docker2:/backup --name worker ubuntu:14.04 tar cvf /backup/backup.tar /dbdata
或:
$ docker run -it --volumes-from dbdata -v F:\Docker2:/backup --name worker ubuntu:14.04 /bin/bash
# tar cvf /backup/backup.tar /dbdata
- 恢复
1、从dbdata容器中挂载数据卷,也就是挂载到目录/dbdata
2、将本地目录F:\Docker2挂载到容器中的/backup目录作为数据卷
3、使用镜像ubuntu:14.04创建并启动一个容器
4、容器启动后,将主机F:\Docker2目录下,也就是/backup目录下的backup.tar解压
$ docker run -it --volumes-from dbdata -v F:\Docker2:/backup ubuntu:14.04 tar xvf /backup/backup.tar
或:
$ docker run -it --volumes-from dbdata -v F:\Docker2:/backup ubuntu:14.04 /bin/bash
# tar xvf /backup/backup.tar
7 端口映射与容器 互联
Docker提供了两种满足服务访问的功能:
- 允许映射容器内应用服务端口到本地宿主主机
- 互联机制实现多个容器间通过容器名来快速访问
7.1 端口映射实现访问容器
当容器运行一些网络应用,要让外部访问这些应用时,可以通过-P或-p参数来指定映射端口。使用-P参数时,会随机映射一个端口到内部容器开放网络的端口(使用docker ps -l或者docker logs -f CONTAINER命令查看);使用-p参数可以指定要映射的端口,并且在一个指定端口上只能绑定一个容器,支持的格式有:
- 映射到指定地址的指定端口 IP:HostPort:ContainerPort
- 映射到指定地址的任意端口 IP::ContainerPort
- 映射到所有接口地址 HostPort:ContainerPort
将本地端口映射到容器的端口,可以多次使用-p参数多次绑定:
$ docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py
映射一个特定的地址:
$ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
指定udp端口:
$ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
绑定127.0.0.1的任意端口到容器的5000端口,本地主机会自动分配一个端口:
$ docker run -d -p 127.0.0.1::5000 training/webapp python app.py
查看端口
$ docker port CONTAINER port
7.2 互联机制实现便捷访问
容器的互联会在源和接收容器之间创建连接关系,接收容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址。
使用--link name:alias参数可以让容器之间安全地进行交互。name是要连接的容器的名称,alias是这个连接的别名。
$ docker run -d --name db training/postgres
$ docker run -d -P --name web --link db:db training/webapp python app.py
docekr通过两种方式为容器公开连接信息:
- 更新环境变量:
$ docker run -rm --name web2 --link db:db training/webapp env
输出中DB_开头的环境变量是供web容器连接db容器使用的。
- 更新/etc/hosts文件。
docker会添加host信息到父容器的/etc/hosts文件中:
$ docker run -it -rm --link db:db training/webapp /bin/bash
# cat /etc/hosts
// 里边包含两个host信息,一个是web容器,使用自己的id作为默认主机名;一个是db容器的IP和主机名。
# ping db // 可以直接使用容器名进行通信。
8 使用Dockerfile创建镜像
Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile来快速创建自定义的镜像。
8.1 基本结构
Dockerfile由一行行命令语句组成,以#开头表示注释。一般包含四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
# 使用的基础镜像,必须放在第一行
FROM ubutntu
# 维护者信息
MAINTAINER docker_user docker_user@email.com
# 镜像操作指令。
# RUN指令将对镜像执行跟随的命令。每执行一条RUN指令,镜像就添加新的一层,并提交。
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# 容器启动时执行的指令
CMD /usr/sbin/nginx
8.2 指令说明
- FROM
FROM指令是最重要的一个且必须为Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境。如果本地不存在,则会从Docker Hub Registry上拉取所需的镜像文件。
如果在同一个Dockerfile中创建多个镜像,可以使用多个FROM指令。
FROM <image>
FROM <image>:<tag>
FROM <image>@<digest>
- MAINTAINER
指定维护者信息,会被写入生成镜像的Author属性中,格式:
MAINTAINER <name>
MAINTAINER image_creator@docker.com
- RUN
运行指定命令。格式:
RUN <command> // 默认在shell终端中运行命令
RUN ["<executable>","<param1>","<param2>"] // 该指令会被解析为Json数组,必须使用双引号。使用exec执行,不启动shell。
每条RUN指令在当前镜像的基础上执行指定命令,并提交为新的镜像。若命令较长可以使用\来换行。
- CMD
指定容器启动时默认执行的命令,格式:
CMD ["<executable>","<param1>","<param2>"] // 使用exec执行,推荐使用
CMD <command> <param1> <param2> // 在/bin/sh中执行
CMD ["<param1>","<param2>"]
每个Dockerfile只能有一条CMD命令,若有多条CMD命令,则只执行最后一条。
- LABEL
指定生成镜像的元数据标签信息,格式:
LABEL <key>=<value> <key>=<value> ...
- EXPOSE
声明镜像内服务所监听的端口,格式:
EXPOSE <port> [<port> ...]
只是声明,并不会自动完成端口映射。
- ENV
为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其他指令(ENV、ADD、COPY等)所调用。格式:
ENV <key> <value>
ENV <key>=<value> ...
- ADD
格式:
ADD <src> <dest>
复制指定的<src>路径下的内容到容器的<dest>路径下,其中<src>可以是Dockerfile所在目录的一个相对路径,也可以是一个URL,还可以是tar文件(会自动解压到<dest>路径下);<dest>可以是镜像内的绝对路径,或相对于工作目录(WORKDIR)的相对路径。
- COPY
格式:
CPOY <src> <dest>
用于从Docker主机的<src>复制文件至创建的镜像的<dest>,若<dest>不存在则自动创建。
- ENTRYPOINT
指定镜像的默认入口命令,会在启动容器时作为根命令执行,所有传入值作为该命令的参数,格式:
ENTRYPOINT <command> <param1> <param2>
ENTRYPOINT ["executable","parm1","parm2"]
每个Dockerfile只能有一条ENTRYPOINT命令,若有多条ENTRYPOINT命令,则只执行最后一条。运行是可以被--entrypoint参数覆盖。
- VOLUME
创建一个数据卷挂载点,格式:
VOLUME ["/data"]
可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保存的数据等。
- USER
指定运行images的用户名或UID,默认的运行用户为root。后续指令也会用指定的身份。格式:
USER <UID> | <User_Name>
- WORKDIR
为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD设定工作目录。格式:
WORKDIR <dirpath>
可以使用多个WORKDIR指令。后续命令若是相对路径,则会基于之前的命令指定的路径。
- ARG
指定镜像内使用的参数,在执行docker build命令时才以--build-arg <varname>=<name>传入,格式:
ARG <name>[=<default value>]
- ONBUILD
若所创建的镜像作为其他镜像的基础镜像时,所执行的创建操作指令。格式:
ONBUILD <instruction>
- STOPSIGNAL
指定所创建镜像启动的容器接收退出的信号值。格式:
STOPSIGNAL signal
- HEALTHCHECK
容器启动时如何进行健康检查,格式:
HEALTHCHECK [options] CMD command // 根据所执行命令返回值是否为0来判断
HEALTHCHECK NONE // 禁止基础镜像中的健康检查
- SHELL
指定其他命令使用shell时的默认shell类型,格式:
SHELL ["executable","parm1","parm2"]
默认:SHELL ["/bin/sh","-c"]
8.3 创建镜像
$ docker build [options] path
读取path下的Dockerfile,并将该路径下的所以内容发送给Docker服务端,有服务端来创建镜像。
/tmp/docker_builder下的Dockerfile,-t指定生成镜像标签信息
$ docker build -t build_repo/first_image /tmp/docker_builder
8.4 .dockerignore文件
可以使用.dockerignore文件来让Docker忽略指定的目录和文件,如:
# comment
*/temp*
*/*/temp*
tmp?
~*