简明教程 | Docker篇 · 其一:基础入门

x33g5p2x  于2022-04-18 转载在 Docker  
字(7.7k)|赞(0)|评价(0)|浏览(182)

了解Docker

Docker是什么

Docker是指容器化技术,用于支持创建和使用 Linux 容器,同时Docker也是软件容器平台。

什么是容器(container)

容器是主机上与其他进程隔离的一个进程。这种隔离利用了内核对象命名空间(kernel namespaces)和控制组群(CGroup)。这些都是linux早已经存在的技术。Docker的作用就是将这些技术变得更易用。

什么是容器镜像(container image)

当运行容器时,它使用一个独立的文件系统,这个文件系统由容器镜像(container image)所提供。镜像包含了要运行容器需要的一切,包括依赖、配置、脚本、二进制文件等等。同时还包含了容器需要的其他配置,如环境变量和其他元数据。

简单理解Docker、Docker容器、Docker容器镜像

Docker可以简单理解为一个虚拟机平台,就类似于VMware,而容器当然就相当于一台台的虚拟机,所以容器镜像就类似于虚拟机的镜像啦。

为什么使用容器而不是虚拟机(VM)

容器比虚拟机更加"轻量",容器是一个应用层的抽象,多个容器运行时共享操作系统内核,只是作为独立的进程,占用空间少,启动快。而虚拟机是一个硬件层的抽象,多个虚拟机运行时,每个虚拟机都包含独立的操作系统,占用空间大,启动较慢。

Docker的其他优点
  • 模块化

Docker 容器化方法注重在不停止整个应用的情况下,单独截取部分应用进行更改的能力,所以容器天然适合微服务。

  • 易于跨平台

能简单的将一个平台上的应用迁移到另一个平台。

  • 快速部署

启动运行新硬件、实施部署并投入使用往往需要大量的时间。基于 Docker 的容器的部署时间只需几秒不等。因此你可以高效的创建或销毁容器。

简单试试Docker

我们已经知道了Docker是什么了,接下来试试用用它,首先我们知道要创建一个Docker容器,就要先获取一个Docker镜像,这就用到了我们的第一个命令。

docker pull
docker pull [options] name[:tag|@digest]
tag

要获取的image的tag

这个命令会从Docker的默认镜像仓库中获取你想要的镜像,你可以从镜像仓库中选择你需要的tag,下图以 docker hub 中的ubuntu的镜像为例

镜像仓库

用于存放 docker 镜像的地址,官方默认的仓库是 Docker Hub,当然你也使用国内的镜像仓库 阿里云 、网易云 、时速云DaoCloud ,又或者你也可以使用自己搭建的私有仓库。

我们如果想获取它,只需要使用以下命令

docker pull ubuntu:20.04

我们可以看到以上有很多的"层",每一层都是可以可以被重用的。

你也许注意到了,我们还可以使用digest的方法拉取image,而刚刚我们拉取完后输出了它的digest,也就是说我们还可以以下方法拉取同样的image。

docker pull ubuntu@sha256:3c9c713e0979e9bd6061ed52ac1e9e1f246c9495aa063619d9d695fb8039aa1f
docker run
docker run [options] IMAGE[:tag|@digest] [command] [arg...]

在拉取后我们就应该运行容器了,而 docker run 指令应该是Docker最复杂的命令了,我们先试着使用刚刚拉取的image

docker run -it ubuntu:20.04 /bin/bash

当运行以上的命令后会发现命令行发生了变化

[root@VM-0-3-centos ~]# docker run -it ubuntu:20.04 /bin/bash
root@b4cc8facbeb7:/#

这就进入了我们创建的ubuntu容器

然后再来看看我们刚才做了什么,-i 的意思是保持输入,-t 的意思是分配一个tty终端,如果难以理解的话,只需知道对于交互式进程,需要带上 -it 。而我们刚刚的使用的 /bin/bash 当然属于交互式进程,对于输入的这条命令,docker会将它作为容器的内部的第一个进程(也就是pid为1)。同时 docker 会监控pid为1的进程,当它退出时,容器也会退出。

现在我们使用 exit 退出终端,使用 docker ps 指令查看所有正在运行的容器(如果还要查看其他状态的容器需要加上 -a ),会发现容器的 status 处于exit状态了。这是因为bash为第一个进程,当docker发现到它退出时便会退出容器。

root@b4cc8facbeb7:/# exit
exit
[root@VM-0-3-centos ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND         CREATED          STATUS                      PORTS     NAMES
b4cc8facbeb7   ubuntu:20.04   "/bin/bash"     11 minutes ago   Exited (0) 5 seconds ago              magical_meninsky
[root@VM-0-3-centos ~]#

如果希望exit后自动删除它,可以带上 --rm 标签。

以及如果我们希望容器能在退出的时候,重新启动的话,就要用 --restart 更改重启策略。重启策略主要有以下几种

  1. no

不重启

  1. on-failure[:max-retries]

退出码非为0时重启,最多重启失败"max-retries"次

  1. always

总是重启

在某些时候我们并不希望容器在前台运行,例如Redis、Nginx之类的应用在后台运行就可以了,我们只需要加上 -d 标签,就能在后台运行应用。

--name 能帮助我们为容器命名。不过即使没有命名,Docker也会生成uuid来为唯一标识容器。

在需要设置环境变量的场景下可以使用 -e 指令设置, -e var="var 1"

除了这些必要的标签外,还有其他的标签,我们在后面再讨论。

docker 容器的状态转换

docker create

根据image创建一个容器,但是并不实际启动,使用形式与 run 相似

docker create -it --name my_ubuntu ubuntu:20.04 /bin/bash
docker start

将一个处于 createexited 状态的容器切换为 running 状态

docker start b4cc8facbeb7

等一下,我们似乎没有创建过叫做"b4cc8facbeb7"的容器?

实际上,我们之前说过Docker会生成uuid作为容器的id,当你使用 docker ps 查看容器时,就会发现container id这一列的存在,大部分可以能使用容器名称的场所也可以用容器id代替。

同时你并不需要完整的输入容器id,只需要能够唯一确认一个容器即可。所以在这里你甚至可以使用 docker start b

docker pause/unpause

你可以使用 pauseunpause 对容器进行冻结解冻,冻结状态下的容器中的所有进程会暂停

docker pause my_ubuntu
docker stop/kill

stopkill 都是让容器退出主进程(即pid为1的进程)。但 stopkill 更加优雅。

stop 会通知所在的容器即将退出,而 kill 会直接关闭容器。而容器会对 stop 传入的信号量进行响应然后退出(根据主程序的设置,也可能不退出),如果程序对信号量不做处理或没有退出才做强制退出。
实际上,在使用了 stopkill 后并不是一定退出了,需要用 docker ps 命令查看来确保退出。

这是因为执行 stop 的时候容器会先对主进程发送sigterm信号,如果主进程没有对sigterm信号做出处理,就不会退出。这样的话就必须等待到stop的超时时间,然后docker引擎会用 kill 关闭。

kill 指令也可能不能立刻杀死主进程,因为它发送的是sigkill信号。而原因属于《操作系统》的内容,所以这里简单略过。

docker rm

删除一个容器,如果要删除镜像的话就是 docker image rm 。这个容器必须要 exit 状态才能删除,否则的话需要带上 -f (--force)才能删除。

docker push

在执行push之前需要在对应的仓库 docker login,docker默认的仓库为docker hub

docker attach

用于进入正在运行的容器。例如使用了以下命令:

docker run -it -d --name my_ubuntu ubuntu:20.04 /bin/bash

此时后台运行后要再次进入只需要:

docker attach my_ubuntu
docker inspect

获取有关Docker对象的信息

docker inspect my_ubuntu
docker exec

在正在运行中的容器中执行命令

docker exec [options] container command [arg...]

需要注意的是,command必须是可执行文件,要执行命令需要:

docker exec -it my_ubuntu sh -c "echo hello"
docker cp

交换容器与宿主机上的文件或文件夹

docker cp [options] 容器名称:容器中的地址 宿主机上的目标地址
docker cp [options] 宿主机上的目标地址 容器名称:容器中的地址

用例:

把主机上/data/fileA放到容器test下的/home/fileA

docker cp /data/fileA test:/home/fileA
docker diff

跟踪容器创建以来更改的文件和目录

用例:

$ docker diff myNginx # 容器名称 
# C -> 更改、A -> 添加、D -> 删除
C /dev
C /dev/console
A /run/nginx.pid
C /var/lib/nginx/tmp
A /var/lib/nginx/tmp/client_body
docker 导入和导出

导出容器到压缩文件

docker export myNginx > latest.tar

从压缩文件或网络或输入流流导入

docker import [options] file|url|- [repository[:tag]]
docker import ./lastest.tar

同样可以做导入和导出的命令还有 saveload ,他们在使用上和 exportimport 相同。

而他们的区别在于:

  1. save 用来持久化image,export 用来持久化容器
  2. load 用来恢复image,import 恢复容器(但两者最后都会恢复为image)
  3. export 只会导出文件系统。也就是说任何元数据,如映射端口、 CMD 和 ENTRYPOINT 配置将会丢失。

基于以上的区别,export 的使用场景更偏向于制作基础镜像,save 的使用场景更偏向于打包复制镜像。

docker logs

查看容器的控制台的输出

先启动一个容器

docker run --name my_ubuntu -itd ubuntu:20.04 echo "hello world"

然后使用 docker logs 命令查看控制台

[root@VM-0-3-centos ~]# docker logs my_ubuntu
hello world

你还可以用 -t 标签输出时间戳和用 -f 跟踪控制台输出

docker stats

监控一个或多个容器的实时数据

[root@VM-0-3-centos ~]# docker stats
CONTAINER ID   NAME              CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
5ff9b00bdc08   competent_payne   0.12%     1.461MiB / 1.795GiB   0.08%     13.2kB / 0B       27.4MB / 0B       5
805ebe155e4f   clever_turing     0.12%     60.95MiB / 1.795GiB   3.32%     72.9MB / 43.6MB   1.29GB / 2.01MB   32
6fbdf2516b99   mysql             0.13%     94.53MiB / 1.795GiB   5.14%     16.3MB / 27.1MB   2.42GB / 968MB    47
docker top

展示容器的进程

容器间的网络通信

首先,我们可以使用 link 命令

docker --link <container>:<alias>

示例:

docker run -it --link redis:myRedis --name my_ubuntu ubuntu /bin/bash

这个命令创建了一个 ubuntu 的容器,然后连接到已经存在的 redis 容器中。连接后就能在 ubuntu 的容器用 myRedis:3306 直接访问另一个容器中的 redis。

不过需要注意的是,使用这种方式的时候需要考虑容器的启动顺序,如一个 redis 集群的搭建使用

  1. 首先启动 redis-master 容器节点
  2. 然后在两个 redis-slave 容器节点启动时使用 --link 连接到 master 节点中
  3. 最后在 APP 容器节点启动时连接到 redis-master 上

link命令的连接方式的缺点非常明显,当容器的数量巨大的时候则需要使用多个link标签,其管理复杂,所以在官方的示例中使用的是network的方法,并且 link 的方式即将被废弃

network和link的方法差不多

docker network create myNetwork

然后我们可以查看我们创建的network

$ docker network ls
NETWORK ID     NAME        DRIVER    SCOPE
a106580bca09   bridge      bridge    local
c68edd500b5c   host        host      local
f8ea00182e3a   myNetwork   bridge    local
c120edcc6e0e   none        null      local

除了我们创建的 myNetwork 外,还有三个内置的network,我们会在后面的文章中讨论这三个network

要连接到该网络的容器需要在创建容器时使用 --network 标签选择要添加的network,然后在同一个network下的容器就可以像是在同一个容器中进行连接

容器中的数据管理

  1. 数据卷(volume)

最常用的方式

  1. 绑定挂载(bind mounts)

不能跨平台,所以并非首选

  1. tmpfs

不常用

数据卷的解释

数据卷是一个或多个容器中专门指定的目录,它能够绕过联合文件系统,也就是说数据卷的本质是容器中一个特殊的目录。

创建数据卷

docker volume create --name "要创建的数据卷名称"

当然你也可以在创建新容器的时候同时创建数据卷,就像这样

docker run -d -v /data ubuntu /bin/bash

这条命令使用"-v"标签添加一个随机名字的数据卷并挂载到了容器中的"/data"目录下,不过你也可以使用指定名字的数据卷

docker run -d -v myVolumn:/data ubuntu /bin/bash

这下我们成功创建了一个数据卷,不过被创建的数据卷被放到哪了呢?我们可以使用以下命令查看我们创建的数据卷,在执行结果中我们从 Mountpoint 中看到数据卷的放置位置

$ docker volume inspect myVolume
[
    {
        "CreatedAt": "2021-04-08T21:08:01+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/myVolume/_data",
        "Name": "myVolume",
        "Options": {},
        "Scope": "local"
    }
]

创建完后就应该投入使用了,我们可以使用 --volumes-from 标签指定要共享的容器的名称

docker run --name test1 -d -v /data ubuntu /bin/bash

我们先创建一个新的容器,然后再新建一个共享的volume的容器

docker run -it --volumes-from test1 ubuntu /bin/bash

这样就完成了数据卷的共享

但是数据卷有时候是需要要删除的,不过简单的删除数据卷挂载的容器并不会删除数据卷,所以你需要手动的删除

docker volume rm <数据卷名称>

需要注意的是,这必须在没有容器挂载该数据卷的情况下才能成功

但是如果在创建时没有指定名称怎么办?我们可以这样

docker rm -v <容器名称>

使用上面这条命令可以在删除容器的同时顺便删除它所挂载的数据卷,并且你还可以在 docker run 的时候加上 --rm 标签,这样的话在容器停止运行时会删除容器和它的数据卷,当然,以上两条命令只会删除没有指定名称的数据卷 ,也就是说如果你已为数据卷命名,那就只能被第一种方法删除。

在某些时候我们也可以不创建数据卷,而直接挂载宿主机的目录

docker run -v /data/myData:/data/yourData ubuntu /bin/bash

这似乎和用数据卷的方式没多大区别。在这条命令中,我们将宿主机上的 /data/myData 文件夹绑定到了容器上的 /data/yourData 文件夹。

同时需要注意的是:

  • 文件夹必须使用绝对路径
  • 如果 /data/myData 不存在,则会创建一个空文件夹
  • 而容器中的 /data/yourData 如果已经存在,则原有内容会被隐藏

当然你也可以直接挂载单个文件

docker run 
	-v /data/configA.conf:/data/configA.conf 
	-v /data/configB.conf:/data/configB.conf 
ubuntu /bin/bash

当文件夹或文件被挂载时,可以用 :ro 来标记只读,和用 :z 来标记共享,:Z 来标记私有

docker run 
	-v /data/configA.conf:/data/configA.conf:ro 
	-v /data/configB.conf:/data/configB.conf:Z
ubuntu /bin/bash

最后我们还可以对数据卷备份和恢复

数据卷备份:

docker run 
	--rm 
    --volumes-from
    -v $(pwd):/backup
ubuntu 
tar cvf /backup/data.tar /data

数据卷恢复:

docker run 
	-it 
    --name vol_bck 
    -v /data 
ubuntu /bin/bash

docker run 
	--rm 
	--volumes-from vol_bak 
	-v $(pwd):/backup
ubuntu
tar xvf /backup/data.tar -C /

相关文章