一、概述
Docker是一个为开发人员或系统管理员提供基于容器开发、部署和运行应用程序功能的平台。应用程序容器化越来越受欢迎是因为它有以下特点:
- 灵活:不管多复杂的应用都可以容器化
- 轻量:容器利用并共享主机内核
- 更新:随时部署更新和升级
- 便携:不管是本地构建,还是云端部署,可以在任何地方运行
- 扩展:增加和自动分发容器副本
- 堆叠:随时垂直叠加服务
容器运行在本地Linux上并和其他容器共享主机内核,它以一个独立的进程运行,不占用任何其他可执行文件的内存,这使得它轻量。虚拟机(VM)以一个完整的操作系统运行,它通过管理程序虚拟化访问主机的资源。总的来说,虚拟机提供的环境的资源比大多数应用程序所需的资源更多。
容器对比传统虚拟机:
特性 | 容器 | 虚拟机 |
启动 | 秒级 | 分钟级 |
硬盘使用 | MB | GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机支持上千个容器 | 一般几十个 |
Docker的整个生命周期是由镜像(Image)、容器(Container)、仓库(Repository)构成,这三个对象介绍如下:
- 镜像:容器通过运行一个镜像而被加载,镜像是一个包含应用程序运行所需事物的可执行文件包,它包括代码、运行环境、依赖库、环境变量和配置文件。
- 容器:容器是一个镜像的运行实例,只能基于镜像创建容器。你可以使用
docker ps
命令来查看主机上所有运行中的容器。 - 仓库:仓库是存储镜像的地方,Docker Hub是所有人可以使用的公共仓库,Docker默认从Docker Hub上寻找镜像。你也可以创建私有仓库,存储内部镜像。
仓库的表示在官网称作注册处(Registry),其实也就是仓库的意思,名字只是个代号而已,意思懂了就行,它的功能类似Maven的仓库。
准备
安装好Docker,运行下面命令检查是否安装成功:
$ docker --version Docker version 19.03.1, build 74b1e89
该文章示例实验环境:Windows10、Virtual Box、Ubuntu 19.04,为了更方便操作Docker命令,不用使用sudo
,可以使用命令sudo usermod -a -G docker
来将当前用户添加到Docker用户组中。
还是老规则,运行一个Hello world来试试:
$ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 1b930d010525: Pull complete Digest: sha256:6540fc08ee6e6b7b63468dc3317e3303aae178cb8a45ed3123180328bcc1d20f Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly.
二、容器
每个镜像运行成功后,都会在本地加载一个容器。它是镜像运行时的实例,可以被创建、启动、停止、删除、暂停等。每个容器就是一个进程,而且是相互独立的,它拥有自己的文件系统、网络配置等。按照Docker规范,容器不应向其存储层写入任何数据,容器存储层要保持无状态化,所有的文件读写操作,都应该通过数据卷(Volume)来完成,或者绑定主机目录。正是如此,容器重启后其内部存储的数据会丢失。后面我们使用Nginx镜像来完成Docker容器的示例操作。
1. 获取镜像
使用 docker images
命令来查看本地的镜像,前面我们运行了一个Hello world镜像,所以Docker默认从Docker Hub拉取了一个最新版本的 hello-world
镜像。因为你运行的时候没有指定该镜像的 tag
(一般为版本号),所以取 latest
版本的镜像,可以通过在镜像后添加 :
来指定镜像的 tag
。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest fce289e99eb9 7 months ago 1.84kB
首先我们使用 docker search nginx
命令从Docker Hub上搜索Nginx的镜像,找到第一个Nginx官方镜像,然后使用 docker pull nginx
命令来将镜像拉取到本地。
如果要拉取指定版本的镜像可以使用:docker pull nginx:1.1.18
执行完命令后再次查看本地镜像会发现Nginx的镜像已经存在了。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest e445ab08b2be 10 days ago 126MB hello-world latest fce289e99eb9 7 months ago 1.84kB
2. 运行镜像
直接运行 docker run nginx
命令,我们发现终端啥也没输出,命令也没有结束,这时新建另一个终端使用下面命令查看Docker容器的运行进程:
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c1482b738243 nginx "nginx -g 'daemon of…" 4 minutes ago Up 4 minutes 80/tcp unruffled_sanderson
此时Nginx镜像已经运行起来了,使用命令 docker container ls
也可以查看运行中的容器,输出的信息中可以看到该运行容器的ID、镜像、命令、创建时间、状态、端口、名称等信息,可以发现 NAMES
为 unruffled_sanderson
,这是该容器的名称,因为我们没有指定,所以默认会随机生成。此时我们切回运行中的终端,使用 Ctrl+C
来中断该容器,也可以使用下面命令来停止或强制中断容器的运行:
# Stop 可以指定容器的ID和名称 $ docker container stop c1482b738243 $ docker container stop unruffled_sanderson # Kill 可以指定容器的ID和名称 $ docker container kill c1482b738243 $ docker container kill unruffled_sanderson
此时的容器只是停止运行,并没有销毁,可以使用下面命令查看停止运行的容器:
# 这两个命令的功能一样 $ docker ps -a $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c1482b738243 nginx "nginx -g 'daemon of…" 19 minutes ago Exited (0) 6 minutes ago unruffled_sanderson
我们可以使用 docker container start
来再次运行前面停止的容器,也可以使用 docker container restart
来重启容器。但此时我们不重新启动该容器,把该容器移除,然后指定名称并以守护进程的形式运行该容器:
# 移除容器 $ docker container rm unruffled_sanderson # 以守护进程并指定名称运行容器,返回的是容器的ID $ docker run -d --name nginx nginx 3e323839068ef98250d4c21b5a54527d28b488356817eda002b99faaa759fce0 # 查看容器发现名称为nginx $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3e323839068e nginx "nginx -g 'daemon of…" 2 minutes ago Up 2 minutes 80/tcp nginx
容器的 start,stop,kill,restart,rm
等命令可以根据ID和名称指定需要操作的容器,所以我们运行容器的时候应该自己指定容器的名称,而不是随机生成,这样能更方便的操作容器。
前面我们对容器进行了一系列的操作,但只是在终端一顿敲命令,并没有看到效果,也不知道Nginx是不是真的运行成功了。
在运行的时候,只需将Nginx容器暴露的端口映射到主机的端口上,就可以通过访问主机来访问容器中的Nginx了,在使用 docker ps
命令时可以看到 PORTS
的信息是 80/tcp
,可以使用下面运行命令来映射端口:
# 使用 -p 参数来指定映射端口,格式:-p 主机端口:容器端口 $ docker run -d --name nginx -p 8080:80 nginx 679c85ff52a6fb9dba399439a482abe52623f98d9316357f2792bae6661aa5d0 # 可以看到 PORTS 端口映射的信息 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 679c85ff52a6 nginx "nginx -g 'daemon of…" 9 minutes ago Up 9 minutes 0.0.0.0:8080->80/tcp nginx
运行成功后,打开浏览器输入 localhost:8080
就可以访问容器中的Nginx了
3. 进入容器
当镜像运行的时候,容器内部也是一个文件系统,你可以把它当作一个Linux操作系统,只不过它删去了多余的软件,只留下供应用程序启动所需的最小环境。我们可以使用下面命令进入容器的文件系统中进行操作容器:
$ docker exec -it nginx /bin/bash root@679c85ff52a6:/# ls bin boot devetc home liblib64 media mnt optproc root run sbin srv sys tmp usr var
现在我们已经进入容器里面了,找到Nginx的欢迎页面所在的文件夹,并修改 index.html
,可此时容器内部好像没有可用的文本编辑器,所以我们要把文件从容器内部复制到主机,修改完成后再从主机复制回去,这看上去似乎有些麻烦,但这只是暂时的。
# Nginx默认代理的文件夹路径 /usr/share/nginx/html # Nginx配置文件的路径 /etc/nginx/nginx.conf # 将容器中的文件复制到当前目录下 $ docker container cp nginx:/usr/share/nginx/html/index.html . # 修改 index.html 文件 $ ls index.html # 修改完成后再将文件复制回去 $ docker container cp ./index.html nginx:/usr/share/nginx/html/
上面的操作过后,Nginx的欢迎页面已经被我们修改了,然后刷新浏览器,就可以看见新的欢迎页面了。
前面我们把容器内部的文件修改了,如果这时停掉并移除容器,重新启动的话,刚才修改的文件恢复到原样了。
三、镜像
镜像包含了应用程序运行的所有依赖,包含代码、环境、配置等,所有镜像不包含任何动态数据,其内容在构建之后也不会被改变。从前面的示例中就可以看出来,如果要保持镜像修改的状态,需要重新构建一个新的镜像,后面我们使用两种方法来构建新的镜像。
镜像是多层存储,每一层是在前一层的基础上进行的修改,这种分层结构存储叫联合文件系统(Union FS),不同的Docker容器可以共享基础的文件系统层,同时加上自己独有的改动层,大大提高了存储的效率。同时,镜像也可以通过分层来继承,基于基础镜像(不是父镜像)可以构建各种具体的应用镜像。
1. 使用Commit构建镜像
查看修改
首先还像前面一样修改Nginx容器里面的欢迎页面,修改成功后可以使用下面命令来查看镜像里面文件的修改:
$ docker diff nginx C /usr C /usr/share C /usr/share/nginx C /usr/share/nginx/html C /usr/share/nginx/html/index.html C /run A /run/nginx.pid ...
提交镜像
修改了容器中的文件后,可以将当前状态保存为一个新的镜像版本,这有点类似于Git,使用下面命令来提交修改的镜像:
# -a:作者 -m:此次修改的记录 $ docker commit -a "ajn" -m "修改欢迎页面" nginx nginx:commit-tag sha256:5b0a74f485ba5daf79bf5e23eb0be14d9f131baaef92f55cf496201aca89ff60
提交成功后返回新的镜像ID,现在查看本地的镜像,发现多了一个版本。
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx commit-tag 5b0a74f485ba 6 seconds ago 126MB nginx latest e445ab08b2be 11 days ago 126MB
停止之前运行的Nginx镜像,使用下面命令运行新构建的Nginx,在浏览器中查看Nginx的欢迎页面已经修改了,而且停止并移除镜像,重新运行后欢迎页都是修改后的状态,因为这时的镜像已经是一个新镜像了。
$ docker run -d -p 8080:80 --name nginx-commit nginx:commit-tag 784722871f4bccbe7d3e5229759963d568c95ee32fcce1bdeaaecdbbb95be284
镜像历史
每个镜像都有它的修改提交的历史版本,我们可以查看前面构建的Nginx镜像的修改历史记录。
$ docker history nginx:commit-tag IMAGE CREATED CREATED BY SIZE COMMENT 5b0a74f485ba 7 minutes ago nginx -g daemon off; 698B 修改欢迎页面 e445ab08b2be 11 days ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B <missing> 11 days ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B <missing> 11 days ago /bin/sh -c #(nop) EXPOSE 80 0B ...
在实际应用中,尽量不要使用commit来构建新的镜像,前面我们使用 docker diff
查看修改文件时,会发现除了真正想要修改的文件外,还增加或修改了许多其它无关的文件。如果对容器进行更多的操作,会增加更多无关内容,使镜像变得臃肿。而且,前面说过镜像是分层存储的,镜像每提交一次,便会增加一层,所有新增的修改只存在于新增的一层中,就算是在一次提交中删除了文件,也不会删除上一层的文件。因此,定制镜像应该使用Dockerfile来完成。
2. 使用Dockerfile构建镜像
编写 Dockerfile
我们可以编写Dockerfile来基于当前镜像来构建新的镜像,Dockerfile包含了所有构建镜像的指令,要修改欢迎页面,只需要基于 nginx:latest
镜像来构建一个新的镜像即可,新建文件 Dockerfile
内容如下:
FROM nginx:latest COPY . /usr/share/nginx/html
我们只需将新的欢迎页面的 html
文件复制到Nginx容器代理的静态文件目录下就行。FROM
指令用于指定基础镜像,COPY
用于将基于上下文目录的文件复制到容器的目录中。Dockerfile还有许多其他指令,后面我们会专门写一篇文章来讲解。
编写 .dockerignore
在将上下文目录的文件复制到容器中时,并不是所有的文件我们都需要复制,比如临时文件、日志文件、打包文件等,可以编写 .dockerignore
文件来列举需要忽略的文件或文件夹。它类似Git中的 .gitignore
文件,同时也支持简单的通配符。在当前目录下新建 .dockerignore
文件内容如下:
# 忽略 Dockerfile Dockerfile
构建镜像
此时当前目录下的文件有三个,index.html
是我们修改后的Nginx欢迎页面文件,执行 docker build
命令来构建镜像,需要指定生成镜像的 tag
和上下文目录。
$ ls -alh 总用量 20K drwxr-xr-x 2 ajn ajn 4.0K 8月 4 12:11 . drwxr-xr-x 25 ajn ajn 4.0K 8月 4 12:11 .. -rw-r--r-- 1 ajn ajn 47 8月 4 12:03 Dockerfile -rw-r--r-- 1 ajn ajn 11 8月 4 12:11 .dockerignore -rw-r--r-- 1 ajn ajn 696 8月 4 09:04 index.html # 最后一个参数 . 为指定上下文目录为当前目录 $ docker build -t nginx:dockerfile-tag . Sending build context to Docker daemon 3.584kB Step 1/2 : FROM nginx:latest ---> e445ab08b2be Step 2/2 : COPY . /usr/share/nginx/html ---> 0562a7648fe9 Successfully built 0562a7648fe9 Successfully tagged nginx:dockerfile-tag # 运行新构建的镜像 $ docker run -d -p 8080:80 --name nginx-dockerfile nginx:dockerfile-tag 119b86117cffe633f8d066753f9cf3f98ab3120ee99bf94d21b68579c1bcb348
运行使用Dockerfile构建的Nginx镜像,在浏览器中查看欢迎页面。停止并移除容器,重新运行镜像,欢迎页面都是保持修改后的状态,因为此时镜像也已经是一个新镜像了。
四、仓库
仓库可以用来管理镜像,如果你想要将你的镜像分享给组织或其他人,或者获取别人的镜像,你可以使用私有仓库或者Docker Hub(官方公共仓库),我们使用Docker Hub来演示仓库的操作。
1. 注册账号
登录Docker Hub官网(https://hub.docker.com),注册账号,然后并登录。
2. 推送镜像
我们将前面使用Dockerfile定制的Nginx镜像推送到Docker Hub上,以便所有人能拉取使用。使用下面命令并输入用户名和密码,登录刚注册的账号:
$ docker login
因为Docker Hub上的镜像名称有规范,格式为 username/image:tag
,所以我们要先将构建的Nginx镜像的名称修改后上传:
# 修改本地的镜像名称为 aijiangnan/nginx:dockerfile-tag $ docker tag nginx:dockerfile-tag aijiangnan/nginx:dockerfile-tag $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE aijiangnan/nginx dockerfile-tag 4d15fa860adb 3 hours ago 126MB nginx dockerfile-tag 4d15fa860adb 3 hours ago 126MB # 推送镜像 $ docker push aijiangnan/nginx:dockerfile-tag The push refers to repository [docker.io/aijiangnan/nginx] d0ca3955a462: Pushed fe6a7a3b3f27: Mounted from library/nginx d0673244f7d4: Mounted from library/nginx d8a33133e477: Mounted from library/nginx dockerfile-tag: digest: sha256:715bf537442a74321349bbf428910a9256d6ea0d200170b9e2ebb63375d8ddbb size: 1155 推送完成后,在Docker Hub上就可以看到刚才上传的镜像了。
推送完成后,在Docker Hub上就可以看到刚才上传的镜像了。
3. 拉取镜像
前面我们在讲镜像的时候已经使用了如何从远程仓库拉取镜像,刚才上传的Nginx镜像所有Docker用户都可以搜索、拉取并使用了。
# 搜索推送的镜像 $ docker search aijiangnan NAME DESCRIPTION STARS OFFICIAL AUTOMATED aijiangnan/nginx 一品江南的Nginx 0 # 拉取镜像 $ docker pull aijiangnan/nginx:dockerfile-tag
直接从Docker Hub上拉取镜像的时候有时速度会特别慢,可以配置阿里云镜像来解决这个问题。新建文件/etc/docker/daemon.json
内容如下,然后重启Docker即可。
{"registry-mirrors": ["https://v1d7u721.mirror.aliyuncs.com"]}
命令参考: