获取镜像
之前我们提到过Docker官方提供了一个公共的镜像仓库:Docker Hub,我们可以从上面获取镜像,获取镜像的命令:docker pull ,格式为:
docker pull [选项] [Docker Registry 地址:端口]/镜像名:标签
Docker 镜像仓库地址:地址的格式一般是<域名/IP>[:端口号],默认地址是Docker Hub。
仓库名:这里的仓库名是两段式名称,即<用户名>/<软件名>。对于Docker Hub,如果不给出用户名,则默认为library,也就是官方仓库。
比如:
[root@node ~]# docker pull ubuntu:16.04
Trying to pull repository docker.io/library/ubuntu ...
16.04: Pulling from docker.io/library/ubuntu
58690f9b18fc: Pull complete
b51569e7c507: Pull complete
da8ef40b9eca: Pull complete
fb15d46c38dc: Pull complete
Digest: sha256:0f71fa8d4d2d4292c3c617fda2b36f6dabe5c8b6e34c3dc5b0d17d4e704bd39c
Status: Downloaded newer image for docker.io/ubuntu:16.04
上面的命令没有给出Docker镜像仓库地址,因此将会从Docker Hub获取镜像。而镜像名称是ubuntu:16.04,因此将会获取官方镜像library/ubuntu仓库中标签为16.04的镜像。
从下载的过程中可以看出我们之前提及的分层存储的概念,镜像是有多层存储构成的。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的ID的前12位。并且下载结束后,给出该镜像完整的sha256的摘要,以确保下载的一致性。
运行
有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的ubuntu:16.04为例,如果我们打算启动里面的bash并且进行交互式操作的话,可以执行下面的命令。
[root@node ~]# docker run -it --rm ubuntu:16.04 /bin/bash
root@8a6b367102f5:/# cat /etc/o
opt/ os-release
root@8a6b367102f5:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.7 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.7 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
root@8a6b367102f5:/#
docker run就是运行容器的命令,具体格式我们会在后面的课程中进行详细讲解,我们这里简要的说明一下上面用到的参数。
-it:这是两个参数,一个是-i:交互式操作,一个是-t:终端。我们这里打算进入bash执行一些命令并查看返回结果,因此我们需要交互式终端。
--rm:这个参数是说容器推出后随之将其删除。默认情况下,为了排障需求,推出的容器并不会立即删除,除非手动docker rm。我们这只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用--rm可以避免浪费空间。
ubuntu:16.04:这里使用ubuntu:16.04镜像为基础来启动容器。
bash:放在镜像名称后面的是命令,这里我们希望有个交互式Shell,因此使用bash。
进入容器后,我们可以在shell下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release,这是linux常用的查看当前系统版本的命令,从返回的结果可以看出容器内是Ubuntu16.04.4 LTS系统。
最后我们通过exit退出这个容器。
列出镜像
[root@node ~]# docker image ls
[root@node ~]# docker images
列表包含了仓库名,标签,镜像ID,创建时间以及所占用的空间。镜像ID则是镜像的唯一标识,一个镜像可以对应多个标签。
镜像大小
如果仔细观察,会注意到,这里标识的所占用空间和在Docker Hub上看到的镜像大小不同。比如:ubuntu:16.04镜像大小,在这里是127MB,但是在Docker Hub显示的却是43MB。这是因为Docker Hub中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此Docker Hub所显示的大小是网络传输中更关心的流量大小。而docker image ls显示的是镜像下载到本地后,展开的大小,准确的说,是展开后各层所占空间的总和,因为镜像到本地后,察南空间的时候,更关心的是本地磁盘空间占用的大小。
另外一个需要注意的问题是,docker image ls列表中的镜像体积总和并非是所有镜像实际硬盘的消耗。由于docker 镜像是多层存储结构,并且可以继承,复用,因此不同镜像可能会因为使用相同的基础镜像,从而用于共同的层。由于Docker使用Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用的空间很可能要比这个列表镜像大小的总和要小的多。
你可以通过一下命令来便捷的查看镜像,容器,数据卷所占用的空间。
[root@node ~]# docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 7 5 285.3 MB 141.6 MB (49%)
Containers 8 0 156 B 156 B (100%)
Local Volumes 0 0 0 B 0 B
新建并启动
所需要的命令主要为docker run。
例如,下面的命令输出一个“Hello World”,之后终止容器。
[root@node ~]# docker run ubuntu:16.04 /bin/echo 'hello world'
hello world
这跟本地直接执行/bin/echo 'hello world'几乎没有区别。
下面的命令启动一个bash终端,允许用户进行交互。
[root@node ~]# docker run -it ubuntu:16.04 /bin/bash
root@153a835890e0:/#
其中,-t选项让docker分配一个伪终端并绑定到容器的标准输入上,-i则让容器的标准输入保持打开。
在交互模式下,用户可以通过所创建的终端来输入命令,例如
root@153a835890e0:/# pwd
/
root@153a835890e0:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
当docker run创建容器时,docker在后台运行的标准操作包括:
1、检查本地是否存在指定镜像,不存在就从共有仓库下载
2、利用镜像创建并启动一个容器
3、 分配一个文件系统,并在只读的镜像层外面挂在一层可读写层
4、从宿主机配置的网桥接口中桥接一个虚拟化接口到容器中去
5、从地址池配置一个ip地址给容器
6、执行用户指定的应用程序
7、执行完毕后容器终止
启动已终止容器
可以利用docker container start命令,直接将一个已经终止的容器启动运行。容器的核心为所执行的应用程序,所需要的资源都是应用程序所必须的。除此之外,并没有其他的资源。可以在伪终端中利用ps或top来查看进程信息。
root@153a835890e0:/# ps
PID TTY TIME CMD
11 ? 00:00:00 bash
20 ? 00:00:00 ps
可见。容器中仅运行了指定的bash应用。这种特点使得Docker对资源的利用率极高,是货真价实的轻量级虚拟化。
后台运行
更多的时候,需要让Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加-d参数来实现。
如果不使用-d参数运行容器
[root@node ~]# docker run ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
容器会把输出结果(STDOUT)打印在宿主机上面
如果使用了-d参数运行容器。
[root@node ~]# docker run -d ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
929c33b4fbdc5b180206185a11ae0f60bb03d09d2751a93bca7dc9ea7bf2c93e
此时容器会在后台运行并不会把输出结果打印到宿主机上面(输出结果可以使用docker logs查看)
[root@node ~]# docker logs 929c33b4fbdc
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
hello world
注:容器是否长久运行,是和docker run指定的命令有关,和-d参数无关。
使用-d参数启动后会返回一个唯一的id,也可以通过docker container ls 命令来查看容器信息。
root@node ~]# docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6449d023fef2 docker.io/ubuntu:16.04 "/bin/bash" 2 minutes ago Up 10 seconds eager_morse
或
[root@node ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6449d023fef2 docker.io/ubuntu:16.04 "/bin/bash" 2 minutes ago Up 47 seconds eager_morse
要获取容器的输出信息,可以通过docker container logs命令
终止容器
可以使用docker container stop来终止一个运行中的容器
此外,当docker容器中指定的应用终结时,容器也自动终止。
例如对于上一章中至启动一个终端的容器。用户通过exit命令或Ctrl+d来退出终端时,所创建的容器立刻终止。
终止状态的容器可以用docker container ls -a 命令看到,例如
[root@node ~]# docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6449d023fef2 docker.io/ubuntu:16.04 "/bin/bash" 7 minutes ago Up 5 minutes eager_morse
处于终止状态的容器,可以通过docker container start命令来重新启动。
此外,docker container restart命令会将一个运行状态的容器终止,然后再重新启动他。
进入容器
在使用-d参数时,容器启动后进入后台。某些时候需要进入容器进行操作,exec命令,-i -t参数
docker exec 后边可以跟多个参数,这里主要说明-i -t参数。
只有-i参数时,由于没有分配伪终端,界面没有我们熟悉的Linux命令提示符,但命令执行结果仍然可以返回。
当-i -t参数一起使用时,则可以看到我们熟悉的Linux命令提示符。
[root@node ~]# docker run -dit docker.io/ubuntu:16.04
319231e113844c5c6500980d331ace73b989fce237d4b4144a84171187b71570
[root@node ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
319231e11384 docker.io/ubuntu:16.04 "/bin/bash" 9 seconds ago Up 8 seconds eloquent_perlman
[root@node ~]# docker exec -i 319231e11384 bash
ls
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
exit
[root@node ~]# docker exec -it 319231e11384 bash
root@319231e11384:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
从这个stdin中exit。不会导致容器的停止。这就是为什么推荐大家使用docker exec的原因。
删除容器
可以使用docker container rm来删除一个处于终止状态的容器。例如
[root@node ~]# docker container rm aa7f033d619f
aa7f033d619f
[root@node ~]# docker rm c7c5787cc23f
c7c5787cc23f
也可以使用docker rm 容器名来删除,如果删除一个正在运行的容器,可以添加 -f 参数。Docker会发送SIGKILL信号给容器。
清理所有处于终止状态的容器
用docker container ls -a (或者docker ps -a)命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用下面命令可以清理掉所有处于终止状态的容器。
[root@node ~]# docker container prune
[root@node ~]# docker ps -aq
删除本地镜像
如果要删除本地镜像,可以使用docker image rm 命令,其格式为:
docker image rm [选项] <镜像>
或者docker rmi 镜像名
用ID、镜像名、摘要删除镜像
docker commit定制镜像
镜像是容器的基础,每次执行docker run的时候都会指定哪个镜像作为容器运行的基础。在之前的例子中,我们所使用的都是来自Docker Hub的镜像。直接使用这些镜像是可以满足一定的需求的,而当这些镜像无法直接满足需求时,我们就需要定制这些镜像。
镜像是多层存储,每一层是在前一层的基础上进行的修改;而容器也是多层存储,是在镜像为基础层加上一层作为容器运行时的存储层。
例子:
[root@node ~]# docker run --name web-server -d -p 80:80 nginx
这条命令会用nginx镜像启动一个容器,命名为web-server,并且映射端口80,这样我们可以用浏览器去访问这个nginx容器。
如果在Linux本机运行的Docker,或者如果使用的是Docker for Mac,docker for Windows,那么直接可以访问:http://localhost;如果是docker toolbox,或者是在虚拟机、云服务器上安装的Docker,则需要将localhost换为虚拟机地址或者实际云服务器地址。

我们可以改成欢迎Docker的文字,
[root@node ~]# docker exec -it web-server bash
root@12ea514bd21f:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

我们修改了容器文件,也就是改动了容器的存储层,我们可以通过docker diff命令看到具体的改动。
[root@node ~]# docker diff web-server
C /etc
C /etc/nginx
C /etc/nginx/conf.d
C /etc/nginx/conf.d/default.conf
C /root
A /root/.bash_history
C /run
A /run/nginx.pid
D /run/secrets
C /usr
C /usr/share
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
D /var/cache/nginx/client_temp
D /var/cache/nginx/fastcgi_temp
D /var/cache/nginx/proxy_temp
D /var/cache/nginx/scgi_temp
D /var/cache/nginx/uwsgi_temp
我们希望能将其保存下来形成镜像。当我们运行一个容器时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器的存储层。而docker提供了一个docker commit命令。可以将容器的存储层保存下来称为镜像。也就是在原有的镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
docker commit 的语法格式为;
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
我们可以用下面的命令将容器保存为镜像:
[root@node ~]# docker commit --author "戈少松测试" --message "test" web-server nginx:v2
sha256:7634ff16de6d4d21e5801406121969e373749b4a8d6f484426da18d7bef2ab71
[root@node ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 7634ff16de6d 5 seconds ago 141 MB
其中--author是指定修改的作者,而--message则是记录本次修改的内容。这点和git版本控制相似,不过这里这些信息可以省略留空。
我们可以用docker history具体查看镜像内的历史记录,如果比较nginx:latest的历史记录,我们会发现新增了我们提交的这一层
[root@node ~]# docker history nginx:v2
IMAGE CREATED CREATED BY SIZE COMMENT
7634ff16de6d 3 minutes ago nginx -g daemon off; #修改了默认的网络页面 1.19 kB test
605c77e624dd 4 months ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daem... 0 B
<missing> 4 months ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0 B
<missing> 4 months ago /bin/sh -c #(nop) EXPOSE 80 0 B
<missing> 4 months ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-en... 0 B
<missing> 4 months ago /bin/sh -c #(nop) COPY file:09a214a3e07c91... 4.61 kB
<missing> 4 months ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6... 1.04 kB
<missing> 4 months ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5... 1.96 kB
<missing> 4 months ago /bin/sh -c #(nop) COPY file:65504f71f5855c... 1.2 kB
<missing> 4 months ago /bin/sh -c set -x && addgroup --system... 61.1 MB
<missing> 4 months ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 0 B
<missing> 4 months ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.1 0 B
<missing> 4 months ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.5 0 B
<missing> 5 months ago /bin/sh -c #(nop) LABEL maintainer=NGINX ... 0 B
<missing> 5 months ago /bin/sh -c #(nop) CMD ["bash"] 0 B
<missing> 5 months ago /bin/sh -c #(nop) ADD file:09675d11695f65c... 80.4 MB
[root@node ~]# docker history nginx:latest
IMAGE CREATED CREATED BY SIZE COMMENT
605c77e624dd 4 months ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daem... 0 B
<missing> 4 months ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0 B
<missing> 4 months ago /bin/sh -c #(nop) EXPOSE 80 0 B
<missing> 4 months ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-en... 0 B
<missing> 4 months ago /bin/sh -c #(nop) COPY file:09a214a3e07c91... 4.61 kB
<missing> 4 months ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6... 1.04 kB
<missing> 4 months ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5... 1.96 kB
<missing> 4 months ago /bin/sh -c #(nop) COPY file:65504f71f5855c... 1.2 kB
<missing> 4 months ago /bin/sh -c set -x && addgroup --system... 61.1 MB
<missing> 4 months ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye 0 B
<missing> 4 months ago /bin/sh -c #(nop) ENV NJS_VERSION=0.7.1 0 B
<missing> 4 months ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.5 0 B
<missing> 5 months ago /bin/sh -c #(nop) LABEL maintainer=NGINX ... 0 B
<missing> 5 months ago /bin/sh -c #(nop) CMD ["bash"] 0 B
<missing> 5 months ago /bin/sh -c #(nop) ADD file:09675d11695f65c... 80.4 MB
新的镜像定制好,运行新镜像
[root@node ~]# docker run --name web-serverv2 -d -p 81:80 nginx:v2

用docker commit手动操作给旧的镜像添加了新的一层,形成新的镜像,对镜像多层存储应该有了更直观的感觉。
注意:docker commit命令除了学习之外,还有一些特殊的应用场合,比如被入侵后保存现场等。但是,不要使用docker commit定制镜像,定制镜像应该使用Dockerfile来完成。