计算单元Docker
Docker介绍
Docker在Pass平台中充当着计算单元的角色,Docker官网的标语是“Build,Ship and Run Any App,Anywhen”,即“一次构建,任意运行”。
Docker是什么
Docker容器属于虚拟化技术,类似于一个轻量级的虚拟机,专注于Linux平台,使用Linux的诸多内核特性,比如namespace、cgroups等,让进城运行在一个隔离的环境中(隔离性)。
Docker和虚拟机的区别:虚拟机技术相对于容器来说要“重”很多,虚拟机采用Hypervisor技术,是虚拟机与物理服务器中间的一层,为每一台虚拟机分配适量的内存、CPU、网络和磁盘,并加载所有虚拟机的客户操作系统,这种虚拟化意味着很大部分的计算、存储资源被使用在荣誉的客户操作系统上,启动过程要远远慢于容器。Docker采用了一种截然不同的方法,它直接采用Linux的容器技术来隔离进程,让其认为自己运行在一个单独的操作系统中,而实际上仍然运行在同一个操作系统中,共享同一个内核,资源利用率远高于Hypervisor。Docker在文件系统上使用了分层结构的AUFS(Another Unionfs)文件系统,将存储资源的共享也实现了最大化,进一步保证了资源的使用率。
如图,容器可以与主机上的其他容器、进程共享基础资源,而虚拟机的每个实例中却要运行一个完整的操作系统。
Docker术语
Docker有三个重要的术语能够帮助我们快速了解它的整个运行工作流,他们分别是镜像(image)、容器(container)和仓库(repositories)。
- 镜像
镜像类似于虚拟机的模板,可以看做静态文件。我们可以想象Linux的内核是0层,当我们构建一个Docker镜像时,其覆盖到了内核层上,这个镜像是1层,也可以称之为base image,镜像时只读的,不能够被修改。Docker镜像能够构建在另外一个镜像之上,如同乐高积木一层层叠加,起一个是parent image,它们继承了父镜像的所有属性。
Docker镜像有一个标识符,它是一个64位字符长度的十六进制字符串。
Docker镜像的生成方式有两种:一种是基于活动的容器,另一种则是在原有的镜像上通过文件描述说明来生成。Dockerfile就是Docker中专门定义的一种领域特定语言(DSL,Domain Specific Language),其包含了镜像生成的指令集,我们可以把它看做是Docker的Makefile文件。
Docker daemon是操作系统上的Docker管理进程,它负责对机器上的镜像、容器进行管理,该进程需要root管理员的权限才可运行,对外提供RESTFUL的API接口。
- 容器
一个Docker容器在执行命令docker run <image>
时创建,它在镜像上加入了可写的一层,在这一层上可以启动一个进程,并且具有两个不同的状态:运行(Running)或者退出(Exited)。启动一个容器时,它会一直保持运行状态,直到自己的退出或者手动停止,才进入到退出状态。
- 仓库
仓库名 | 仓库地址 |
---|---|
Docker Hub | hub.docker.com |
网易蜂巢 | c.163.com/hub#/m/home |
Docker安装
- Ubuntu Trusty 14.04 LTS
对Docker支持最好的OS版本就属于Ubuntu Trusty 14.04 LTS,可以使用apt-get快速安装。
1 | sudo apt-get update # 更新包管理器的列表清单 |
- Red Hat Enterprise Linux 7
红帽企业版 linux7支持Docker,Docker的安装源放在了红帽的附加仓库中。
使用红帽的订阅管理工具来安装Docker,首先运行从附加仓库、可选仓库中下载的软件包:
1 | subscription-manager repos --enable=rhel-7-server-extras-rpms |
在设置好订阅管理器的相关参数后,可以直接使用yum来安装Docker,在这里我们将Docker以及Docker的仓库安装在同一台服务器上,实际上Docker仓库是用来保存、维护所有影响的地方,通过这种安装方式,我们可以快速地建立自己的私有仓库,而DOcker需要在所有提供Docker服务的机器上安装:
1 | yum install docker docker-registry |
因为RHEL7的防火墙服务于Docker服务,有冲突存在,因此在安装之后我们需要将防火墙服务关闭:
1 | systemctl stop firewalld.service |
最后我们需要启动Docker服务,运行Docker服务,并查看Docker的服务状态:
1 | systemctl start docker.service |
Docker容器命令
1)从公网仓库获取一个Ubuntu最新的base镜像:
1 | docker pull ubuntu:latest |
2)从Ubuntu镜像运行一个容器,执行bash命令:
1 | docker run -dt ubuntu:latest bash |
3)查看当前主机下的所有镜像:
1 | docker images |
4)查看当前主机下的所有容器状态:
1 | docker ps -a |
run命令
Docker容器有一个清晰的生命周期状态,我们常常直接使用run命令从一个镜像来生成运行状态(Running)的容器,二十几上容器通过create、start、stop、run命令来管理自己的生命周期。create创建一个容器,这时容器没有任何状态,start启动一个新建的或者停止的容器,stop停止一个容器,rm将一个停止的容器从主机上删除。
run命令使用的基本方法如下:
1 | docker run [options] IMAGE [command] [args] |
run命令所涉及的选项机器说明如下表:
选项 | 说明 |
---|---|
-a,–attach=[] | 将当前的stdin、输出stdout或者错误输出stderr指定到容器中,在将容器放到后台运行时这个选项无效 |
-d,–detach | 将容器放到后台运行 |
-i,–interactive | 采用交互式运行容器,保持stdin标准输入文件为打开状态 |
-t,–tty | 分配一个伪终端标识符,这在你登录容器时需要打开 |
-p,–publish=[] | 容器内的端口服务在主机OS上时无法访问的,这就需要提前对外发布端口,我们也可以认为是端口映射(ip:hostport:containerport) |
–rm | 在容器运行完毕退出后自动删除容器(这个选项不能与-d同时使用) |
-v,–volume=[] | 容器中的数据会随着容器的生命周期的结束而消失,我们可以通过该选项将外部存储映射到容器内,将外部数据给容器访问,或者将容器的数据保存到外部(/host:/container) |
–volumes-from=[] | 从另外一个容器中mount卷 |
-w,–workdir=" " | 设置容器中的工作文件夹 |
–name=" " | 分配一个容器名字,如果不指定,则会自动生成 |
-h,–hostname=" " | 分配一个主机 |
-u,–user=" " | 指定容器运行后的uid或者用户名 |
-e,–env=[] | 容器内的环境变量,在容器差异化时可以通过这个选项为相同的镜像容器设置不同的环境变量 |
–env-file=[] | 从一个行级的文件中读取环境变量 |
–dns=[] | 设置容器的DNS服务器 |
–dns-search=[] | 设置容器的DNS查询域 |
–link=[] | 在同一主机上容器可以通过link链接进行访问,从而无须暴露端口(name:alias) |
–cpuset=" " | 通过cpuset让容器运行在指定的CPU上 |
-c,–cpu-shares=0 | cpu-shares是一个权重值,当多个容器运行在相同的CPU资源上时,会依据此权重值进行资源分配 |
-m,–memory=" " | 指定容器分配的内存大小 |
在使用run命令运行容器时,-t、-i这两个选项能够让我们通过终端进入容器中进行交互式命令操作,如果不带这两个选项,name我们将无法通过attach命令进入容器中,容器内的进程处于不可观测的状态。-d选项让容器作为后台进程运行,我们在容器中运行时会常常用到。
每次创建一个容器,容器会被分配到一个64位char字符的唯一ID,另外还会分配一个容器名,这个名称可以通过–name选项自定义,也可以随机生成。值得我们注意的是每次以ID操作一个容器时,并不需要我们输入完整的ID名称,通过ID字符中的最开始的两个字符就可以定位到容器。
1 | $ docker run -dit --name my-first-ct ubuntu /bin/bash |
在容器中的进程运行完成后,容器就退出了,这时容器并没有消失,而是作为exited状态存在着,我们可以通过start命令再次启动容器。ps命令能查看主机上所有容器的信息。你可以在容器启动时加入–rm选项,这样进程退出时容器会直接被删除。
1 | $ docker ps -a |
每一个容器动态的分配到了一个127.17.0.0/16的私网IP,启动后的HTTP服务器无法提供给外部访问,这里的外部指的是同一个OS内外的进程。这时-p选项可以发挥作用,它将容器内的端口绑定到OS的端口上,这样外部也就能够访问容器内的服务了。
1 | $ docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=test hub.c.163.com/library/mysql:latest |
在这个例子中,我们将端口通过3306映射出来,外部访问的3306端口即容器中的服务。
为了提高容器的安全性,避免潜在的风险,Docker默认在容器中关闭了内核的一些功能,一般场合并不要求使用这些功能,我们可以通过-privileged选项打开。
在容器中读写的数据不会再OS中体现,这些数据会随着容器的生命周期的消亡而消亡,有一些需求会需要这些数据保存下来,或者将OS上的数据提供给容器使用,–volumes选项能够将一个OS上的外部卷mount到容器中。
1 | $ docker run -d --name mysql -p 0.0.0.0:3306:3306 -v /Users/zjw/Documents/workspace/data:/var/lib/mysql:wr \ |
在上面docker命令中,我们使用-v将外部OS的卷mount发送给容器使用,在-v选项的最后我们可以加以wr或者ro来设定卷的读写权限。容器中默认的工作目录是根目录,在这里设置-w选项,制定工作目录为/
在Docker1.2之后的版本,除了能够mount一般的文件系统,还支持对设备映射绑定,需要使用–device选项。
最后对于容器中运行Service型的应用而言,容器内的进程都是长时间运行的,在Docker1.2之后的本本中加入了–restart选项来设定容器的重启策略。
- no:在容器小王退出后不重启(默认)。
- on-failure:在容器退出后,如果退出码不是0,则重启容器。它也支持一个退出码的最大值设置,例如on-failure=5。
- always:无论容器的退出码是什么,都将重启。
start命令
Docker start的选项基本与run命令一样:
1 | $ docker start [-i] [-a] <container(s)> |
stop命令
容器之词能够一个Shell命令后会由此带动一组进程,进程结束后容器自动退出,在某些情况下这些进程是服务类型的,会一直坚挺,等待访问请求的到来。这类进程是长任务行进程,如果要停止容器,name可以使用stop命令,它会向容器发送一个SIGERM信号,容器内的进程收到这个信号后会调用自己打的shutdown程序,这种停止方式很优雅,在一定的时间后如果容器还没有停止,则Docker会继续发送一个SIGKILL信号,强制性地停止容器。
1 | $ docker stop mysql |
restart命令
1 | docker restart mysql |
attach命令
attach命令常常让人们进入一个误区,类似于登录一个虚拟机OS。如前所述,要使用attach附属到一个容器上,在容器启动前要将-i、-t加上,在启动时也要加上-d选项,daemon容器在后台运行。attach附属到一个容器上,它如同将这个后台任务切换到前台,将用户终端的标准输入stdin、标准输出stdout,以及错误输出stderr指向容器进程中的这三个文件描述符中。
1 | $ docker run -dit ubuntu:latest bash |
ps命令
ps命令类似于linux系统的ls,用来罗列所有容器信息,用法如下:
1 | docker ps [options] |
选项 | 说明 |
---|---|
-a,–all | 显示所有的容器,包括已经停止的容器 |
-q,–quiet | 仅显示容器ID |
-s,–size | 打印容器的空间大小 |
-l,–latest | 仅显示最新启动的容器 |
-n=" " | 显示最近运行的n个容器,包括停止了的容器 |
–before=" " | 显示在某个容器ID之前启动的所有容器,包括停止了的容器 |
–after=" " | 显示在某个容器ID之后启动的所有容器,包括停止了的容器 |
inspect命令
inspect命令允许你获取一个容器、镜像的详细信息,它会返回一个JSON格式的数据。
1 | docker inspect a5ec |
1 | [ |
Docker镜像命令
Docker的基本工作流程是从仓库中搜索镜像,选择自己的镜像并下载,编辑镜像进行客户化,之后一句这个镜像产生容器。另外,为了实现在其他地方运行容器,将生成好的新镜像上传到仓库也非常重要。
客户化镜像的方式一般有两种:一种是基于容器生成镜像,另一种是用Dockerfile文件通过build命令构建镜像。
search、pull、push命令
search命令用来在仓库中搜索我们需要的镜像,下面的代码用于从仓库中搜索Mysql相关的镜像:
1 | docker search mysql | less |
pull命令来仓库中抓取镜像到本地文件上,默认从公有仓库中抓取,但是你也可以指定到私有仓库里.
从公有参股抓取MySql镜像的代码如下:
1 | docker pull mysql |
从公有仓库抓取带标签的MySql镜像的代码如下:
1 | docker pull mysql:latest |
如果想从私有仓库抓取镜像,name可以在镜像名称前加上仓库的地址:
1 | docker pull <path_to_registry>/<image> |
push命令与pull相对比,它将本地的一个镜像推送到仓库中:
1 | docker push NAME[:TAG] |
commit命令
commit可以来客户化镜像,它从我们现有的一个容器中生成一个新的镜像,你可以在镜像中写入自己的备注信息。如表3所示。
选项 | 说明 |
---|---|
-p,–pause | 在提交之前将容器暂停 |
-m,–message=“” | 添加用来描述镜像的备注信息 |
-a,–author | 添加作者信息 |
我们通过一个例子来学习从容器中生成镜像的方法。首先运行一个Ubuntu发行版的Linux,命令行使用bash:
1 | docker run --name my.ubuntu -it ubuntu /bin/bash |
另一个终端在这个容器之上生成镜像:
1 | docker commit -m "my.mysql - install mysql database on ubuntu" -a "zjw me@zhoujunwen.win" my.ubuntu zjw/code.it:v1 |
image、diff、rmi命令
image命令用来显示当前主机上的所有镜像信息,如表4。
选项 | 说明 |
---|---|
-a,–all | 显示所有镜像,包括中间层 |
-f,–filter=[] | 提供了一些镜像过滤值 |
–no-trunc | 不剪裁任何信息,显示完整的镜像ID |
-q,–quiet | 仅显示镜像ID |
diff命令用来比较当前容器与其启动镜像之间的文件差异:
1 | docker diff boring_hopper |
rmi命令用来删除镜像,在删除一个镜像时会将其下层的所有镜像全部删除:
1 | docker rmi pingan_ep |
save、load、export、import命令
save、load、export、import四个命令总的来说是将镜像保存为.tar格式的文件,以及向.tar格式的文件导入生成镜像。
save命令将镜像输出到stdout标准输出中,以.tar压缩格式保存文件,在文件中保留了镜像的所有层级以及元数据信息:
1 | docker save -o my-nginx.tar nginx.it |
-o 选项将输出从标准输出重定向到一个文件中,这种方式经常用来对镜像备份,之后使用load命令来加载这份压缩文件。
load命令从.tar压缩文件中加载镜像,这种方式会将镜像的层级及元数据信息全部还远:
1 | docker load -i my-ngixn.tar |
-i 选项指定具体的文件名,上例是从我们的压缩的.tar文件中还远镜像。
export命令与save命令的不同之处在于它直接从一个容器中导出压缩文件,同事,这个文件是以扁平方式存在的,在这个文件中镜像的层、元数据信息等都融合在一个文件中,其关联的历史信息将会丢失。
1 | sudo docker export my-redis > my-redis.bak.tar |
在这里我们将一个名为my-redis的容器导出到压缩文件中。
import命令创建一个空的镜像,之后从压缩文件中导入内容,它与export命令是对立的,import命令可以从远程URL和本地文件系统中导入文件:
1 | docker import http://example.com/test.tar.gz |
以上两项分别从远程和本地导入压缩文件,生成镜像。
Docker网络与链接
Docker容器的网络、数据卷决定了Docker如何外外部提供服务,并共享、存储数据。
Docker的网络模式
Docker的网络设置直接决定可我们如何向外暴露服务。使用–publish将容器内的端口映射到主机上(Docker默认的bridge模式),使用–net设置网络模式。Docker的网络模式设置有四种:
- host模式:使用–net=host指定。
- container模式:使用–net=container:NAME_or_ID指定。
- none模式:使用–net=none指定。
- bridge模式:使用–net=bridge指定。
Docker Daemon启动后会在服务器上创建一个名为Docker0的虚拟网桥,让我们通过一系列的命令查看Docker0网桥的IP地址设置、路由设置。
Note: Docker Ubuntu镜像如果没有ifconfig以及ping命令,则进入到容器的伪终端,执行下面命令安装:
1 | apt-get update |
如果没有brctl命令,则可以使用下面命令安装:
1 | apt-get install bridge-utils |
可以通过ifconfig查看Docker0的网卡设置,它被分配到一个172.17.42.1/16的ip地址,而172.17.0.0/16的整个私有网络IP段将保留给主机上创建的容器使用,默认的网络模式为bridge的容器在启动后都会有一个该网段IP地址的网卡:
1 | ifconfig Docker0 |
如果使用host模式,name容器将和主机公用一个Network Namespace,不会虚拟自己的网卡、配置自己的IP地址等,而是保持与主机一致。
现在用host模式启动一个容器,通过ifconfig查看网卡信息会发现与外部主机一模一样。
1 | $ docker run -dit --net=host ubuntu:latest /bin/bash |
进入虚拟终端,使用ifconfig查看网卡信息:
1 | root@moby:/# ifconfig |
我们在容器内部安装一个SSHd,之后启动服务:
1 | root@moby:/# apt-get install openssh-server |
编辑/etc/ssh/sshd_config文件,将容器上的sshd服务的端口改为1234,重新启动sshd:
1 | root@moby:/etc/ssh# /etc/init.d/ssh restart |
Dockerfile
基本指令集
1 | # 选择镜像,类似于继承 |
1 | # 添加作者信息 |
1 | # 安装ssh-package,并将端口修改为2222 |
1 | # 对外暴露端口 |
1 | # 将默认的命令设置为启动sshd |
随后我们通过build命令从本地读取Dockerfile文件,生成一个镜像Ubuntu-sshd,完成后启动一个镜像:
1 | docker build -t ubuntu-sshd:latest . |
通过inspect命令查看刚刚启动的容器的IP地址:
1 | docker inspect c6672c2e | grep "IPAddress" |
1)FROM
该指令用于指定基础镜像。
2)MAINTAINER
该指令用于设定镜像制作作者的信息。
3)RUN
该指令与CMD指令很容易让人误解,RUN指令运行后所有的内容豆浆被持久化,例如安装package、修改文件等,它是为了修改镜像数据而存在的,进程执行完毕后就会推出,不会保留在启动容器中。
4)CMD
CMD指令是镜像设置的默认命令,它与运行的进程相关。
5)EXPOSE
EXPOSE指令告诉容器在启动时将哪些端口暴露给外部。这里需要注意的是,即便在Dockerfile中设置了该项,在run命令执行时依然要设置-p选项的映射关系。
以上是最基本的额5条指令,在这过程中我们使用了build命令进行镜像的生成。
build命令的使用方式见下(见表5)如下:
1 | docker build [OPTIONS] PATH | URL | - |
选项 | 说明 |
---|---|
-t,–tag=“” | 在镜像成功生成后,应用到镜像上的名称标签 |
-q,–quiet | 构建过程不显示过程信息,默认是显示的 |
–rm=true | 成功构建后删除中间所有的临时容器 |
–force-rm | 强制删除所偶有中间容器的数据,即便没有成功 |
–no-cache | 在构建过程中不使用缓存 |
PATH、URL指定了Dockerfile所在的位子,它会将这个目录下的所有文件传送到Docker daemon中。
环境指令集
1)ENV
ENV指令用来设置环境变量:
1 | ENV <key> <value> |
该指令将
2)WORKDIR
该指令设置当前环境的工作目录,主要用来为RUN、CMD命令查找可执行文件的目录。指令可以在Dockerfile文件中多次使用,也支持使用相对路径。
3)USER
USER指令用来设置在Dockerfile脚本中运行RUN进程的用户,可以是用户名,也可以是UID。
数据指令集
1)VOLUME
VOLUME指令和run命令中的-v选项类似,在容器中创建一个mount点,从外部挂载一个卷,可以使外部存储或者其他容器。
2)ADD
ADD指令用来将本地文件复制到镜像中:
1 | ADD <src> <dest> |
ADD指令中的
3)COPY
COPY指令和ADD基本一样,最大的区别在于COPY只支持在本地文件中复制,也就是说如果你需要从URL外部或者stdin标准输入中复制文件,name将不受COPY指令支持。
ENTRYPOINT指令
RUN、CMD和ENTRYPOINT着三条指令比较容易混淆,如前所述,RUN命令用于在基础镜像上修改文件数据,将内容持久化而构建新镜像。在Dockerfile中有多条RUN,在执行完成任务后便退出,而CMD和ENTRYPOINT只有一条。
CMD是镜像生成后默认启动命令。ENTRYPOINT与CMD类似,也是容器默认执行的命令,但ENTRYPOINT让这个镜像更像一个独立的可执行文件,而在RUN命令之后输入的内容仅作为参数传。
栗子🌰一枚:
首先使用CMD作为最后默认的命令,这里仅仅放一个echo命令:
1 | CMD ["echo"] |
随后运行一个容器:
1 | docker run -it ubuntu-echo echo aa |
输出了:
aa
即在run容器是,后面的echo aa覆盖了Dockerfile中最后一行的echo命令。
如果我们使用ENTRYPOINT结尾,则:
1 | ENTRYPOINT ["echo"] |
同样,我们运行一个容器:
1 | docker run -it ubuntu-entry-echo echo aa |
name其输出的是:
echo aa
从这里可以看到”echo aa“在命令中不是作为CMD指令传入覆盖的,而是作为参数传给容器执行的,而这个容器相当于一个echo可执行文件。