Docker 运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker 会从镜像仓库下载该镜像。它是容器的基石,层叠的只读文件系统,应用联合加载(union mount)技术。

【1】先来打个样

      先来让你真真切切的感受一下,我们尝试启动一个docker。首先我们从仓库拉去镜像,然后容器以镜像为基础运行一个容器,然后就退并删除。这一系列操作中就会涉及到一些操作命令。

[1]从Docker Registry搜索镜像;
        docker search 仓库名
  [2]从 Docker 镜像仓库获取镜像的命令:
        docker pull [选项] [Docker Registry 地址[:端口号]/] 仓库名 [:标签]
    注释: 
        Docker Registry地址:默认是Docker Hub,是不是有点像git从远程仓库一样,拉取源代码,哈哈哈。
        仓库名:两段式,<用户名>/<软件名> ,对于Docker Hub,默认是library,可以不写,官方镜像。
        标签:不写就表示拉取最新的lastest
  [3]就是运行容器的命令docker run 
    参数:交互式执行
        -it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果。
        --rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。
        我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
        htppd:这是指用htppd镜像为基础来启动容器。
        bash:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是 bash,也可以写/bin/bash
   [4]进入容器以后,也就是我们在shell下执行命令
    我们执行了 cat /etc/os-release这是 Linux 常用的查看当前系统版本的命令,
从返回的结果可以看到容器内是Debian GNU/Linux 10 (buster)系统然后我们可以使用exit退出这个容器。
[root@VM_16_189_centos ~]# docker search apache
NAME                               DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
httpd                              The Apache HTTP Server Project                  2581                [OK]                
tomcat                             Apache Tomcat is an open source implementati…   2485                [OK]                
cassandra                          Apache Cassandra is an open-source distribut…   1016                [OK]
/*
下载过程中,可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。
下载也是一层层的去下载,并非单一文件。
下载过程中给出了每一层的 ID 的前 12 位。
并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。
*/
[root@VM_16_189_centos ~]# docker pull httpd
Using default tag: latest
latest: Pulling from library/httpd
f5d23c7fed46: Pull complete 
b083c5fd185b: Pull complete 
bf5100a89e78: Pull complete 
98f47fcaa52f: Pull complete 
622a9dd8cfed: Pull complete 
Digest: sha256:dc4c86bc90593c6e4c5b06872a7a363fc7d4eec99c5d6bfac881f7371adcb2c4
Status: Downloaded newer image for httpd:latest
//有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。
//    交互式启动容器并且以bash作为交互。
[root@VM_16_189_centos ~]# docker run -it --rm httpd bash
//成功进入到容器里面
root@7fe1c1d0c5f7:/usr/local/apache2# cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
root@7fe1c1d0c5f7:/usr/local/apache2# 
//退出容器
root@7fe1c1d0c5f7:/usr/local/apache2# exit
exit

【2】常用命令

-[查看docker基本信息]
	#显示 Docker 系统信息,包括镜像和容器数。
	[root@VM_16_189_centos ~]# docker info  

-[搜索镜像]
	 -查找镜像
        -docker search 一次最多搜索25个

-[列出镜像]
	#我们使用docker images可以查看到镜像,也可以用docker image ls 
    [root@VM_16_189_centos ~]# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              e445ab08b2be        2 weeks ago         126MB
ubuntu              18.04               3556258649b2        2 weeks ago         64.2MB
ubuntu              latest              3556258649b2        2 weeks ago         64.2MB
httpd               latest              ee39f68eb241        3 weeks ago         154MB
	#列表中包含了 仓库名,标签,镜像id,创建时间,所占空间。
	#IMAGE ID 表示镜像的唯一ID 但是列表没有显示完全,可以加参数  --no-trunc
	#例如-docker images --no-trunc查看完整的ImageId
#可以看到其中ubuntu:18.04和ubuntu:latest的id是一样的,所以他们使用的同一个镜像,只是打上了不同标签

-[列出部分镜像]
    #根据仓库名列出镜像
    [root@VM_16_189_centos ~]# docker image ls ubuntu
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              18.04               3556258649b2        2 weeks ago         64.2MB
ubuntu              latest              3556258649b2        2 weeks ago         64.2MB
    #列出特定的某个镜像,也就是说指定仓库名和标签
    [root@VM_16_189_centos ~]# docker image ls ubuntu:latest
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              3556258649b2        2 weeks ago         64.2MB

-[特定形式显示镜像]
 	#显示所有镜像的id
    [root@VM_16_189_centos ~]# docker image ls -q
e445ab08b2be
3556258649b2
3556258649b2
ee39f68eb241
	#显示不截断镜像id 参数--no-trunc  
	#如果想既有长镜像,又有短镜像,参数--digests
    [root@VM_16_189_centos ~]# docker image ls --no-trunc -q
sha256:e445ab08b2be8b178655b714f89e5db9504f67defd5c7408a00bade679a50d44
sha256:3556258649b2ef23a41812be17377d32f568ed9f45150a26466d2ea26d926c32
sha256:3556258649b2ef23a41812be17377d32f568ed9f45150a26466d2ea26d926c32
sha256:ee39f68eb241fd811887da7e21425ac657074363daa9969b9519504785f5d60d
	#自定义列表显示格式
	#可能只是对表格的结构不满意,希望自己组织列;
	#或者不希望有标题,这样方便其它程序解析结果等,这就用到了 Go 的模板语法
    [root@VM_16_189_centos ~]# docker image ls --format "{{.ID}}: {{.Repository}}"
e445ab08b2be: nginx
3556258649b2: ubuntu
3556258649b2: ubuntu
ee39f68eb241: httpd
	#以表格等距显示,并且有标题行,和默认一样,不过自己定义列
    [root@VM_16_189_centos ~]# docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
IMAGE ID            REPOSITORY          TAG
e445ab08b2be        nginx               latest
3556258649b2        ubuntu              18.04
3556258649b2        ubuntu              latest
ee39f68eb241        httpd               latest

-[查看镜像详细信息]
	命令docker inspect nginx:latest
	参数 使用-f显示某一列特定的字段的值
	[root@VM_16_189_centos docker]# docker inspect -f {{".Config"}} nginx:v3    
{   false false false map[80/tcp:{}] false false false [PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NGINX_VERSION=1.17.2 NJS_VERSION=0.3.3 PKG_RELEASE=1~buster] [nginx -g daemon off;] <nil> true sha256:e445ab08b2be8b178655b714f89e5db9504f67defd5c7408a00bade679a50d44 map[]  [] false  [] map[maintainer:NGINX Docker Maintainers <docker-maint@nginx.com>] SIGTERM <nil> []}

-[镜像体积]
 #镜像在DockerHub上是有压缩的,当我们下载到本地展开后,通过docker image ls才可以看到相对准确的大小。
另外,由于镜像是多层存储结构,可以继承复用,不同的镜像可能会复用相同的基础镜像,
有相同的层,Docker使用UnionFS,相同的层只需要保存一层就可以了,
因此实际镜像总和比这个images列表加起来的和要小。
    #另外你可以用docker system df看到镜像,容器,数据卷占用的空间
[root@VM_16_189_centos ~]# docker system df
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images              3                   1                   274.6MB             210.4MB (76%)
Containers          2                   1                   572.8MB             0B (0%)
Local Volumes       0                   0                   0B                  0B
Build Cache         0                   0                   0B                  0B

-[虚悬镜像]
    #可能会有仓库名和标签名都是<none>,这种我们给它叫做 虚悬镜像(dangling image) ,
    #由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。
    #可以使用docker image ls -f dangling=true看到
    [root@VM_16_189_centos ~]# docker image ls -f dangling=true
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              00285df0df87        5 days ago          342 MB

-[中间层镜像]
#	为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。
docker image ls 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。
中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。
只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。

-[标记标签]
	docker tag : 标记本地镜像,将其归入某一仓库。
	[root@VM_16_189_centos ~]# docker tag nginx:latest nginx:tacks
[root@VM_16_189_centos ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              e445ab08b2be        2 weeks ago         126MB
nginx               tacks               e445ab08b2be        2 weeks ago         126MB

-[删除本地镜像]
	#命令: docker image rm [选项] <镜像1> [<镜像2> ...]
	#或者: docker rmi 删除本地一个或多少镜像。 -f强制删除
	#指定删除,可以用短ID,长ID,镜像名,或者摘要,一般我们用镜像名,或者短id,只要能区分开就可以。
    [root@VM_16_189_centos ~]# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              e445ab08b2be        2 weeks ago         126MB
ubuntu              18.04               3556258649b2        2 weeks ago         64.2MB
ubuntu              latest              3556258649b2        2 weeks ago         64.2MB
httpd               latest              ee39f68eb241        3 weeks ago         154MB
[root@VM_16_189_centos ~]# docker image rm e44
Untagged: nginx:latest
Untagged: nginx@sha256:eb3320e2f9ca409b7c0aa71aea3cf7ce7d018f03a372564dbdb023646958770b
Deleted: sha256:e445ab08b2be8b178655b714f89e5db9504f67defd5c7408a00bade679a50d44
Deleted: sha256:4f71ea073b438369b87f20ad9cc8aca17efcd777a98ef6a396cebaa84355e46c
Deleted: sha256:1758ea933cbf0900bc59fa45893440675d66d8848c3595f6f6ae9cdac34ecaf0

-[删除多个镜像]
	#可以使用 docker image ls -q 来配合使用 docker image rm,这样可以成批的删除希望删除的镜像。
	#例如删除nginx的镜像
        docker image rm $(docker image ls -q nginx) 

-[Untagged 和 Deleted]
	#在删除镜像的时候,我们可以看到,有两步不一样的操作,我们知道镜像,唯一标识是ID,另外一个镜像可以有多个标签
	#所以,我们在使用删除命令的时候,也有可能只是删除某一个标签,另外也有标签指向这个镜像,所以我们可能不会直接删除镜像,
只有当我们把标签都Untagged之后,然后才可以知道到是不是可以删除镜像。所以等到我们删除所有标签的时候,可能也会触发删除镜像。
    #另外由于镜像是多层存储结构,当我们删除到某一层的时候,也有其他镜像依赖于这个基础镜像,
    #所以还是不会删除该层。之到没有其他镜像依赖于此。才可以删除。
    #另外还有一点,容器是基于镜像运行的,所以如果有容器正在运行,那么必须先进行关闭,才可以将其删除。

【3】docker commit

【不能乱用commit】
    -docker commit一般用于特殊场合,例如保存被入侵的现场,
但是最好不要用这个命令去定制镜像,这样会让镜像变的臃肿镜像是容器的基础,
我们之前使用的都是来自docker hub上的镜像,但是当我们需要一些特定的需求的时候,
这些镜像不能直接满足我们,我们最好使用dockerfile来进行定制。
    -我们知道镜像是多层存储结构,每一层在前一层上进行修改,容器以镜像为基础层,再加一层运行的存储层。
-定制一个web服务器
//指定nginx后台运行(-d指定)一个容器命名为WebNginx(--name指定) 并且指定本地80端口映射到8888(-p指定)
    [root@VM_16_189_centos ~]# docker run --name WebNginx -d -p 8888:80 nginx
ec2d90bb5147df53247a3b6cdbf788f3e69ebdcb3dd14c25acd5e8af0c25bbf7
//然后打开浏览器运行对应的地址端口进行访问可以看到nginx默认成功欢迎界面
//接着我想定制这个镜像,将欢迎界面改为Hello Docker
//利用docker exec进入容器,-it 一个是 -i:交互式操作,一个是 -t 终端。也就是交互式终端
    [root@VM_16_189_centos ~]# docker exec -it WebNginx bash
//然后我们修改nginx的index.html界面,再次访问浏览器发现样式已经改变
root@ec2d90bb5147:/# echo '<h1>Hello Docker!</h1>' > /usr/share/nginx/html/index.html 
root@ec2d90bb5147:/# exit 
exit
//我们修改了容器文件也就是存储层的东西,我们可以docker diff查看到具体的改动
[root@VM_16_189_centos ~]# docker diff WebNginx
C /root
A /root/.bash_history
C /run
A /run/nginx.pid
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/uwsgi_temp
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
[root@VM_16_189_centos ~]#
//然后这个也就是我们假设要定制的,我们希望保存下来这个镜像
//当我们运行一个容器的时候,任何文件修改的操作都会被记录在容器存储层中。
//我们把当前存储层保存下来成为一个镜像,从而构建了新的镜像,再下次运行这个新的镜像的时候就会是我们修改后的
-使用命令docker commit [选项]  <容器ID或容器名> [<仓库名>[:<标签>]]
//--author 指定作者,--message 指定镜像信息
[root@VM_16_189_centos ~]# docker commit --author "Tacks" --message "修改默认nginx成功界面为hello docker"  WebNginx nginx:v2 
sha256:b41eca264bfe5a4032f6fbac102fe4d83699a2a1b009969daf5329769a352387
[root@VM_16_189_centos ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               v2                  b41eca264bfe        6 seconds ago       126MB
nginx               latest              e445ab08b2be        2 weeks ago         126MB
-查看镜像的历史记录
#可以发现我们新提交的镜像相关信息 -H :以可读的格式打印镜像大小和日期,默认为true;
[root@VM_16_189_centos ~]# docker history nginx:v2
IMAGE               CREATED              CREATED BY                                      SIZE                COMMENT
b41eca264bfe        About a minute ago   nginx -g daemon off;                            244B                修改默认nginx成功界面为hello docker
e445ab08b2be        2 weeks ago          /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B  
...
//然后运行一下新的这个镜像
[root@VM_16_189_centos ~]# docker run --name WebNginx2 -d -p 8889:80 nginx:v2
335a1952b24543e7863b5635b2ece75e6d1c45ca734ee9173f25557a75414356
//这里我们开启一个新的服务WebNginx2,来运行我们新的镜像,可以访问对应8889端口
    至此,我们完成了一次定制镜像,虽然只是小小改动一个默认nginx成功页面,但是我们看到diff的时候也有其他文件变更。
如果之后我们还想手动安装新的软件包或者编译构建,从而定制新的镜像,那将会是很可怕的,而且维护镜像也是比较难的,
比较臃肿,而且当我们这样去定制新的镜像的时候,其他人并不知道这个定制的新的镜像是什么样的,成了黑箱镜像。
    所以我们还是使用DokcerFile去定制新的镜像。

【4】Dockerfile

【Dockerfile定制镜像】
    -Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,
就是描述该层应当如何构建。所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。
就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。
    而 FROM 就是指定 基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
    RUN 指令是用来执行命令行命令的,每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,
    执行结束后,commit 这一层的修改,构成新的镜像。不能过多使用RUN在一个文件,每一个RUN都相当于建立一层镜像。
    -我们下面创建一个Dockerfile的文件然后去构建一个新的镜像,另外最后面我们可以看到
    RUN 指令启动了一个容器 b190fb9db8ca,执行了所要求的命令,并最后提交了这一层 7496839b5515,随后删除了所用到的这个容器b190fb9db8ca
-命令
    docker build [选项] <上下文路径/URL/->
    
    [root@VM_16_189_centos ~]# mkdir mynginx
[root@VM_16_189_centos ~]# ls
mynginx
[root@VM_16_189_centos ~]# cd mynginx/
[root@VM_16_189_centos mynginx]# vim Dockerfile
[root@VM_16_189_centos mynginx]# cat Dockerfile
FROM nginx
RUN echo '<h1>hello,Docker! I am Tacks!</h1>' > /usr/share/nginx/html/index.html
[root@VM_16_189_centos mynginx]# docker build -t nginx:v3 . //后面实际上有一个. 也就是上下文的
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> e445ab08b2be
Step 2/2 : RUN echo '<h1>hello,Docker! I am Tacks!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in b190fb9db8ca
Removing intermediate container b190fb9db8ca
 ---> 7496839b5515
Successfully built 7496839b5515
Successfully tagged nginx:v3
    
-理解 docker build 的工作原理。
    Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。
    Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,
    而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。
    因此,虽然表面上我们好像是在本机执行各种 docker 功能,
    但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。
    也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
-理解镜像构造原理
    每个镜像都由很多层次构成,Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。
    通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,
    另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,
    Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。
    Docker 在 AUFS 上构建的容器也是利用了类似的原理。

【小结】

docker info

显示 Docker 系统信息,包括镜像和容器数。

docker search 仓库名

从Docker Registry搜索镜像默认是会显示25个

docker pull 仓库名

从 Docker 镜像仓库获取镜像的命令

docker run -it 仓库名 bash

交互式进入容器

docker run --name WebNginx -d -p 8888:80 nginx

指定容器名称,-d后台运行,-p端口映射

docker images/docker image ls

查看镜像列表 --no-trunc完整id

docker image ls ubuntu

根据仓库名列出相关镜像

docker image ls ubuntu:latest

列出特定的某个镜像,也就是说指定仓库名和标签

docker image ls -q

显示所有镜像的id

docker image ls --format "{{.ID}}: {{.Repository}}"

自定义列显示id和仓库名

docker inspect nginx:latest

查看镜像的详细信息

docker history nginx:v2

查看指定镜像的创建历史

docker tag nginx:latest nginx:tacks

给指定的镜像添加标签

docker image rm 镜像名/id

删除本地镜像

docker rmi 镜像名/id

删除本地镜像