Docker 入门教程 - 2021 最新版(上)

 

容器化的概念很早就有了。2013 年 Docker 引擎的出现使应用程序容器化变得更加容易。

根据 Stack Overflow 开发者调查-2020,Docker是开发者 #1 最想要的平台、#2 最喜欢的平台,以及#3 最流行的平台。

尽管 Docker 功能强大,但上手确并不容易。因此,本书将介绍从基础知识到更高层次容器化的的所有内容。读完整本书之后,你应该能够:

  • 容器化(几乎)任何应用程序

  • 将自定义 Docker 镜像上传到在线仓库

  • 使用 Docker Compose 处理多个容器

前提

  • 熟悉 Linux 终端操作

  • 熟悉 JavaScript(稍后的的演示项目用到了 JavaScript)

目录

  • 容器化和 Docker 简介

  • 怎样安装 Docker

    • 怎样在 macOS 里安装 Docker

    • 怎样在 Windows 上安装 Docker

    • 怎样在 Linux 上安装 Docker

  • 初识 Docker - Docker 基本知识介绍

    • 什么是容器?

    • 什么是 Docker 镜像?

    • 什么是仓库?

    • Docker 架构概述

    • 全景图

  • Docker 容器操作基础知识

    • 怎样运行容器

    • 怎样公开端口

    • 如何使用分离模式

    • 怎样列表展示容器

    • 怎样命名或者重命名一个容器

    • 怎样停止或者杀死运行中的容器

    • 怎样重新启动容器

    • 怎样创建而不运行容器

    • 怎样移除挂起的容器

    • 怎样以交互式模式运行容器

    • 怎样在容器里执行命令

    • 如何处理可执行镜像

  • Docker 镜像操作基础知识

    • 如何创建 Docker 镜像

    • 如何标记 Docker 镜像

    • 如何删除、列表展示镜像

    • 理解 Docker 镜像的分层

    • 怎样从源码构建 NGINX

    • 怎样优化 Docker 镜像

    • 拥抱 Alpine Linux

    • 怎样创建可执行 Docker 镜像

    • 怎样在线共享 Docker 镜像

  • 怎样容器化 JavaScript 应用

    • 如何编写开发 Dockerfile

    • 如何在 Docker 中使用绑定挂载

    • 如何在 Docker 中使用匿名卷

    • 如何在 Docker 中执行多阶段构建

    • 如何忽略不必要的文件

  • Docker 中的网络操作基础知识

    • Docker 网络基础

    • 如何在 Docker 中创建用户定义的桥接网络

    • 如何在 Docker 中将容器连接到网络

    • 如何在 Docker 中从网络分离容器

    • 如何删除 Docker 中的网络

  • 如何容器化多容器 JavaScript 应用程序

    • 如何运行数据库服务

    • 如何在 Docker 中使用命名卷

    • 如何从 Docker 中的容器访问日志

    • 如何在 Docker 中创建网络并连接数据库服务

    • 如何编写 Dockerfile

    • 如何在正在运行的容器中执行命令

    • 如何在 Docker 中编写管理脚本

  • 如何使用 Docker-Compose 组合项目

    • Docker Compose 基础

    • 如何在 Docker Compose 中启动服务

    • 如何在 Docker Compose 中列表展示服务

    • 如何在 Docker Compose 正在运行的服务中执行命令

    • 如何访问 Docker Compose 中正在运行的服务日志

    • 如何在 Docker Compose 中停止服务

    • 如何在 Docker Compose 中编写全栈应用程序

  • 结论

项目代码

可以在这个仓库中找到示例项目的代码,欢迎 ⭐。

完整代码在 containerized 分支。

贡献

这本书是完全开源的,欢迎高质量的贡献。可以在这个仓库中找到完整的内容。

我通常先在本书的 GitBook 版本上进行更改和更新,然后在将其发布在 freeCodeCamp 专栏。你可以在这个链接中找到本书的最新编辑中版本。别忘了评分支持。

如果你正在寻找本书的完整稳定版本,那么 freeCodeCamp 是最好的选择。如果你有所收获,请分享给你的朋友。

不管阅读本书的哪个版本,都不要忘记留下你的意见。欢迎提出建设性的批评。

容器化和 Docker 简介

摘自 IBM,

容器化意味着封装或打包软件代码及其所有依赖项,以便它可以在任何基础架构上统一且一致地运行。

换句话说,容器化可以将软件及其所有依赖项打包在一个自包含的软件包中,这样就可以省略麻烦的配置,直接运行。

举一个现实生活的场景。假设你已经开发了一个很棒的图书管理应用程序,该应用程序可以存储所有图书的信息,还可以为别人提供图书借阅服务。

如果列出依赖项,如下所示:

  • Node.js

  • Express.js

  • SQLite3

理论上应该是这样。但是实际上还要搞定其他一些事情。Node.js 使用了 node-gyp 构建工具来构建原生加载项。根据官方存储库中的安装说明,此构建工具需要 Python 2 或 3 和相应的的 C/C ++ 编译器工具链。

考虑到所有这些因素,最终的依赖关系列表如下:

  • Node.js

  • Express.js

  • SQLite3

  • Python 2 or 3

  • C/C++ tool-chain

无论使用什么平台,安装 Python 2 或 3 都非常简单。在 Linux 上,设置 C/C ++ 工具链也非常容易,但是在 Windows 和 Mac 上,这是一项繁重的工作。

在 Windows 上,C++ 构建工具包有数 GB 之大,安装需要花费相当长的时间。在 Mac 上,可以安装庞大的 Xcode 应用程序,也可以安装小巧的 Xcode 命令行工具包。

不管安装了哪一种,它都可能会在 OS 更新时中断。实际上,该问题非常普遍,甚至连官方仓库都专门提供了 macOS Catalina 的安装说明。

这里假设你已经解决了设置依赖项的所有麻烦,并且已经准备好开始。这是否意味着现在开始就一帆风顺了?当然不是。

如果你使用 Linux 而同事使用 Windows 该怎么办?现在,必须考虑如何处理这两个不同的操作系统不一致的路径,或诸如 nginx 之类的流行技术在 Windows 上未得到很好的优化的事实,以及诸如 Redis 之类的某些技术甚至都不是针对 Windows 预先构建的。

即使你完成了整个开发,如果负责管理服务器的人员部署流程搞错了,该怎么办?

所有这些问题都可以通过以下方式解决:

  • 在与最终部署环境匹配的隔离环境(称为容器)中开发和运行应用程序。

  • 将你的应用程序及其所有依赖项和必要的部署配置放入一个文件(称为镜像)中。

  • 并通过具有适当授权的任何人都可以访问的中央服务器(称为仓库)共享该镜像。

然后,你的同事就可以从仓库中下载镜像,可以在没有平台冲突的隔离环境中运行应用,甚至可以直接在服务器上进行部署,因为该镜像也可以进行生产环境配置。

这就是容器化背后的想法:将应用程序放在一个独立的程序包中,使其在各种环境中都可移植且可回溯。

现在的问题是:Docker 在这里扮演什么角色?

正如我之前讲的,容器化是一种将一切统一放入盒子中来解决软件开发过程中的问题的思想。

这个想法有很多实现。Docker 就是这样的实现。这是一个开放源代码的容器化平台,可让你对应用程序进行容器化,使用公共或私有仓库共享它们,也可以编排它们。

目前,Docker 并不是市场上唯一的容器化工具,却是最受欢迎的容器化工具。我喜欢的另一个容器化引擎是 Red Hat 开发的 Podman。其他工具,例如 Google 的 Kaniko,CoreOS 的 rkt 都很棒,但和 Docker 还是有差距。

此外,如果你想了解容器的历史,可以阅读 A Brief History of Containers: From the 1970s Till Now,它描述了该技术的很多重要节点。

怎样安装 Docker

Docker 的安装因使用的操作系统而异。但这整个过程都非常简单。

Docker可在 Mac、Windows 和 Linux 这三个主要平台上完美运行。在这三者中,在 Mac 上的安装过程是最简单的,因此我们从这里开始。

怎样在 macOS 里安装 Docker

在 Mac 上,要做的就是跳转到官方的下载页面,然后单击_Download for Mac(stable)_按钮。

你会看到一个常规的 Apple Disk Image 文件,在该文件的内有 Docker 应用程序。所要做的就是将文件拖放到 Applications 目录中。

Docker 入门教程 - 2021 最新版(上)_第1张图片

只需双击应用程序图标即可启动 Docker。应用程序启动后,将看到 Docker 图标出现在菜单栏上。

Docker 入门教程 - 2021 最新版(上)_第2张图片

现在,打开终端并执行 docker --version 和 docker-compose --version 以验证是否安装成功。

怎样在 Windows 上安装 Docker

在 Windows 上,步骤几乎相同,当然还需要执行一些额外的操作。安装步骤如下:

  1. 跳转到此站点,然后按照说明在 Windows 10 上安装 WSL2。

  2. 然后跳转到官方下载页面 并单击 Download for Windows(stable) 按钮。

  3. 双击下载的安装程序,然后使用默认设置进行安装。

安装完成后,从开始菜单或桌面启动 Docker Desktop。Docker 图标应显示在任务栏上。

Docker 入门教程 - 2021 最新版(上)_第3张图片

现在,打开 Ubuntu 或从 Microsoft Store 安装的任何发行版。执行 docker --version 和 docker-compose --version 命令以确保安装成功。

Docker 入门教程 - 2021 最新版(上)_第4张图片

也可以从常规命令提示符或 PowerShell 访问 Docker,只是我更喜欢使用 WSL2。

怎样在 Linux 上安装 Docker

在 Linux 上安装 Docker 的过程有所不同,具体操作取决于你所使用的发行版,它们之间差异可能更大。但老实说,安装与其他两个平台一样容易(如果不能算更容易的话)。

Windows 或 Mac 上的 Docker Desktop 软件包是一系列工具的集合,例如Docker EngineDocker ComposeDocker DashboardKubernetes 和其他一些好东西。

但是,在 Linux 上,没有得到这样的捆绑包。可以手动安装所需的所有必要工具。不同发行版的安装过程如下:

  • 如果你使用的是 Ubuntu,则可以遵循官方文档中的在 Ubuntu 上安装 Docker 引擎部分。

  • 对于其他发行版,官方文档中提供了 不同发行版的安装指南

    • 在 Debian上安装 Docker Engine

    • 在 Fedora 上安装 Docker Engine

    • 在 CentOS 上安装 Docker Engine

  • 如果你使用的发行版未在文档中列出,则可以参考从二进制文件安装 Docker 引擎指南。

  • 无论参考什么程序,都必须完成一些非常重要的 Linux 的安装后续步骤。

  • 完成 docker 安装后,必须安装另一个名为 Docker Compose 的工具。可以参考官方文档中的 Install Docker Compose 指南。

安装完成后,打开终端并执行 docker --version 和 docker-compose --version 以确保安装成功。

Docker 入门教程 - 2021 最新版(上)_第5张图片

尽管无论使用哪个平台,Docker 的性能都很好,但与其他平台相比,我更喜欢 Linux。在整本书中,我将使用 Ubuntu 20.10 或者 Fedora 33。

一开始就需要阐明的另一件事是,在整本书中,我不会使用任何 GUI 工具操作 Docker。

我在各个平台用过很多不错的 GUI 工具,但是介绍常见的 docker 命令是本书的主要目标之一。

初识 Docker - 介绍 Docker 基本知识

已经在计算机上启动并运行了 Docker,现在该运行第一个容器了。打开终端并执行以下命令:

docker run hello-world

# Unable to find image 'hello-world:latest' locally
# latest: Pulling from library/hello-world
# 0e03bdcc26d7: Pull complete 
# Digest: sha256:4cf9c47f86df71d48364001ede3a4fcd85ae80ce02ebad74156906caff5378bc
# Status: Downloaded newer image for hello-world:latest
# 
# Hello from Docker!
# This message shows that your installation appears to be working correctly.
# 
# To generate this message, Docker took the following steps:
#  1. The Docker client contacted the Docker daemon.
#  2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
#     (amd64)
#  3. The Docker daemon created a new container from that image which runs the
#     executable that produces the output you are currently reading.
#  4. The Docker daemon streamed that output to the Docker client, which sent it
#     to your terminal.
#
# To try something more ambitious, you can run an Ubuntu container with:
#  $ docker run -it ubuntu bash
# 
# Share images, automate workflows, and more with a free Docker ID:
#  https://hub.docker.com/
#
# For more examples and ideas, visit:
#  https://docs.docker.com/get-started/

hello-world 镜像是使用 Docker 进行最小化容器化的一个示例。它有一个从 hello.c 文件编译的程序,负责打印出终端看到的消息。

现在,在终端中,可以使用 docker ps -a 命令查看当前正在运行或过去运行的所有容器:

docker ps -a

# CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
# 128ec8ceab71        hello-world         "/hello"            14 seconds ago      Exited (0) 13 seconds ago                      exciting_chebyshev

在输出中,使用 hello-world 镜像运行了名为 exciting_chebyshev 的容器,其容器标识为 128ec8ceab71。它已经在 Exited (0) 13 seconds ago,其中 (0) 退出代码表示在容器运行时未发生任何错误。

现在,为了了解背后发生的事情,必须熟悉 Docker 体系结构和三个非常基本的容器化概念,如下所示:

  • 容器

  • 镜像

  • 仓库

我已经按字母顺序列出了这三个概念,并且将从列表中的第一个开始介绍。

什么是容器?

在容器化世界中,没有什么比容器的概念更基础的了。

官方 Docker resources 网站说 -

容器是应用程序层的抽象,可以将代码和依赖项打包在一起。容器不虚拟化整个物理机,仅虚拟化主机操作系统。

可以认为容器是下一代虚拟机。

就像虚拟机一样,容器是与主机系统是彼此之间完全隔离的环境。它也比传统虚拟机轻量得多,因此可以同时运行大量容器,而不会影响主机系统的性能。

容器和虚拟机实际上是虚拟化物理硬件的不同方法。两者之间的主要区别是虚拟化方式。

虚拟机通常由称为虚拟机监控器的程序创建和管理,例如 Oracle VM VirtualBox,VMware Workstation,KVM,Microsoft Hyper-V 等等。该虚拟机监控程序通常位于主机操作系统和虚拟机之间,充当通信介质。

Docker 入门教程 - 2021 最新版(上)_第6张图片

每个虚拟机都有自己的 guest 操作系统,该操作系统与主机操作系统一样消耗资源。

在虚拟机内部运行的应用程序与 guest 操作系统进行通信,该 guest 操作系统在与虚拟机监控器进行通信,后者随后又与主机操作系统进行通信,以将必要的资源从物理基础设施分配给正在运行的应用程序。

虚拟机内部运行的应用程序与物理基础设施之间存在很长的通信链。在虚拟机内部运行的应用程序可能只拥有少量资源,因为 guest 操作系统会占用很大的开销。

与虚拟机不同,容器以更智能的方式完成虚拟化工作。在容器内部没有完整的 guest 操作系统,它只是通过容器运行时使用主机操作系统,同时保持隔离 – 就像传统的虚拟机一样。

Docker 入门教程 - 2021 最新版(上)_第7张图片

容器运行时(即 Docker)位于容器和主机操作系统之间,而不是虚拟机监控器中。容器与容器运行时进行通信,容器运行时再与主机操作系统进行通信,以从物理基础设施中获取必要的资源。

由于消除了整个主机操作系统层,因此与传统的虚拟机相比,容器的更轻量,资源占用更少。

为了说明这一点,请看下面的代码片段:

uname -a
# Linux alpha-centauri 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux

docker run alpine uname -a
# Linux f08dbbe9199b 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 Linux

在上面的代码片段中,在主机操作系统上执行了 uname -a 命令以打印出内核详细信息。然后在下一行,我在运行 Alpine Linux 的容器内执行了相同的命令。

从输出中可以看到,该容器确实正在使用主机操作系统中的内核。这证明了容器虚拟化主机操作系统而不是拥有自己的操作系统这一点。

如果你使用的是 Windows 计算机,则会发现所有容器都使用 WSL2 内核。发生这种情况是因为 WSL2 充当了 Windows 上 Docker 的后端。在 macOS 上,默认后端是在  HyperKit 虚拟机管理程序上运行的 VM。

什么是 Docker 镜像?

镜像是分层的自包含文件,充当创建容器的模板。它们就像容器的冻结只读副本。镜像可以通过仓库进行共享。

过去,不同的容器引擎具有不同的镜像格式。但是后来,开放式容器计划(OCI)定义了容器镜像的标准规范,该规范被主要的容器化引擎所遵循。这意味着使用 Docker 构建的映像可以与 Podman 等其他运行时一起使用,而不会有兼容性问题。

容器只是处于运行状态的镜像。当从互联网上获取镜像并使用该镜像运行容器时,实际上是在先前的只读层之上创建了另一个临时可写层。

在本书的后续部分中,这一概念将变得更加清晰。但就目前而言,请记住,镜像是分层只读文件,其中保留着应用程序所需的状态。

什么是仓库?

已经了解了这个难题的两个非常重要的部分,即 Containers 和 Images 。最后一个是 Registry

镜像仓库是一个集中式的位置,可以在其中上传镜像,也可以下载其他人创建的镜像。Docker Hub 是 Docker 的默认公共仓库。另一个非常流行的镜像仓库是 Red Hat 的 Quay。

在本书中,我将使用 Docker Hub 作为首选仓库。

Docker 入门教程 - 2021 最新版(上)_第8张图片

可以免费在 Docker Hub 上共享任意数量的公共镜像。供世界各地的人们下载免费使用。可在我的个人资料(fhsinchy)页面上找到我上传的镜像。

Docker 入门教程 - 2021 最新版(上)_第9张图片

除了 Docker Hub 或 Quay,还可以创建自己的镜像仓库来托管私有镜像。计算机中还运行着一个本地仓库,该仓库缓存从远程仓库提取的镜像。

Docker 架构概述

既然已经熟悉了有关容器化和 Docker 的大多数基本概念,那么现在是时候了解 Docker 作为软件的架构了。

该引擎包括三个主要组件:

  1. Docker 守护程序: 守护程序(dockerd)是一个始终在后台运行并等待来自客户端的命令的进程。守护程序能够管理各种 Docker 对象。

  2. Docker 客户端: 客户端(docker)是一个命令行界面程序,主要负责传输用户发出的命令。

  3. REST API: REST API 充当守护程序和客户端之间的桥梁。使用客户端发出的任何命令都将通过 API 传递,最终到达守护程序。

根据官方文档,

“ Docker 使用客户端-服务器体系结构。Docker client 与 Docker daemon 对话,daemon 繁重地构建、运行和分发 Docker 容器”。

作为用户,通常将使用客户端组件执行命令。然后,客户端使用 REST API 来访问长期运行的守护程序并完成工作。

全景图

好吧,说的够多了。现在是时候了解刚刚学习的所有这些知识如何和谐地工作了。在深入解释运行 docker run hello-world 命令时实际发生的情况之前,看一下下面的图片:

Docker 入门教程 - 2021 最新版(上)_第10张图片

该图像是在官方文档中找到的图像的略微修改版本。执行命令时发生的事件如下:

  1. 执行 docker run hello-world 命令,其中 hello-world 是镜像的名称。

  2. Docker 客户端访问守护程序,告诉它获取 hello-world 镜像并从中运行一个容器。

  3. Docker 守护程序在本地仓库中查找镜像,并发现它不存在,所以在终端上打印 Unable to find image 'hello-world:latest' locally

  4. 然后,守护程序访问默认的公共仓库 Docker Hub,拉取 hello-world 镜像的最新副本,并在命令行中展示 Unable to find image 'hello-world:latest' locally

  5. Docker 守护程序根据新拉取的镜像创建一个新容器。

  6. 最后,Docker 守护程序运行使用 hello-world 镜像创建的容器,该镜像在终端上输出文本。

Docker 守护程序的默认行为是在 hub 中查找本地不存在的镜像。但是,拉取了镜像之后,它将保留在本地缓存中。因此,如果再次执行该命令,则在输出中将看不到以下几行:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
0e03bdcc26d7: Pull complete
Digest: sha256:d58e752213a51785838f9eed2b7a498ffa1cb3aa7f946dda11af39286c3db9a9
Status: Downloaded newer image for hello-world:latest

如果公共仓库中有可用镜像的更新版本,则守护程序将再次拉取该镜像。那个 :latest 是一个标记。镜像通常包含有意义的标记以指示版本或内部版本。稍后,将更详细地介绍这一点。

Docker 容器操作基础知识

在前面的部分中,已经了解了 Docker 的构建模块,还使用 docker run 命令运行了一个容器。

在本节中,将详细介绍容器的操作。容器操作是每天要执行的最常见的任务之一,因此,正确理解各种命令至关重要。

但是请记住,这并不是可以在 Docker 上执行的所有命令的详尽列表。我只会介绍最常见的那些。当想知道某一命令的更多用法时,可以访问 Docker 命令行的官方参考。

怎样运行容器

之前,已经使用 docker run 来使用 hello-world 镜像创建和启动容器。此命令的通用语法如下:

docker run 

尽管这是一个完全有效的命令,但是有一种更好的方式可以将命令分配给 docker 守护程序。

在版本 1.13 之前,Docker 仅具有前面提到的命令语法。后来,命令行经过了重构具有了以下语法:

docker   
 
  

使用以下语法:

  • object 表示将要操作的 Docker 对象的类型。这可以是 containerimagenetwork 或者 volume 对象。

  • command 表示守护程序要执行的任务,即 run 命令。

  • options 可以是任何可以覆盖命令默认行为的有效参数,例如端口映射的 --publish 选项。

现在,遵循此语法,可以将 run 命令编写如下:

docker container run 

image name 可以是在线仓库或本地系统中的任何镜像。例如,可以尝试使用fhsinchy / hello-dock 镜像运行容器。该镜像包含一个简单的 Vue.js应用程序,该应用程序在容器内部的端口 80 上运行。

请在终端上执行以下命令以使用此镜像运行容器:

docker container run --publish 8080:80 fhsinchy/hello-dock

# /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
# /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
# 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
# 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
# /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
# /docker-entrypoint.sh: Configuration complete; ready for start up

该命令不言自明。唯一需要说明的部分是 --publish 8080:80 部分,将在下一个小节中进行说明。

怎样公开端口

容器是隔离的环境。主机系统对容器内部发生的事情一无所知。因此,从外部无法访问在容器内部运行的应用程序。

要允许从容器外部进行访问,必须将容器内的相应端口发布到本地网络上的端口。--publish 或 -p 选项的通用语法如下:

--publish :

在上一小节中编写了 --publish 8080:80 时,这意味着发送到主机系统端口 8080 的任何请求都将转发到容器内的端口 80。

现在要在浏览器上访问该应用程序,只需访问  http://127.0.0.1:8080

Docker 入门教程 - 2021 最新版(上)_第11张图片

可以在终端窗口按下 ctrl + c 组合键或关闭终端窗口来停止容器。

如何使用分离模式

run 命令的另一个非常流行的选项是 ---detach 或 -d 选项。在上面的示例中,为了使容器继续运行,必须将终端窗口保持打开状态。关闭终端窗口会停止正在运行的容器。

这是因为,默认情况下,容器在前台运行,并像从终端调用的任何其他普通程序一样将其自身附加到终端。

为了覆盖此行为并保持容器在后台运行,可以在 run 命令中包含 --detach 选项,如下所示:

docker container run --detach --publish 8080:80 fhsinchy/hello-dock

# 9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc

与前面的示例不同,这次不会看到很多文字,而只获得新创建的容器的 ID。

提供选项的顺序并不重要。如果将 --publish 选项放在 --detach 选项之前,效果相同。

使用 run 命令时必须记住的一件事是镜像名称必须最后出现。如果在镜像名称后放置任何内容,则将其作为参数传递给容器入口点(在在容器内执行命令小节做了解释),可能会导致意外情况。

怎样列表展示容器

container ls 命令可用于列出当前正在运行的容器。执行以下命令:

docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   5 seconds ago       Up 5 seconds        0.0.0.0:8080->80/tcp   gifted_sammet

一个名为 gifted_sammet 的容器正在运行。它是在 5 seconds ago 前创建的,状态为 Up 5 seconds,这表明自创建以来,该容器一直运行良好。

CONTAINER ID 为 9f21cb777058,这是完整容器 ID 的前 12 个字符。完整的容器 ID 是 9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc,该字符长 64 个字符。在上一节中 docker container run  命令行的输的就是完整的容器 ID 。

列表的 PORTS 列下,本地网络的端口 8080 指向容器内的端口 80。name gifted_sammet 是由 Docker 生成的,可能与你的计算机的不同。

container ls 命令仅列出系统上当前正在运行的容器。为了列出过去运行的所有容器,可以使用 --all 或 -a 选项。

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                     PORTS                  NAMES
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   2 minutes ago       Up 2 minutes               0.0.0.0:8080->80/tcp   gifted_sammet
# 6cf52771dde1        fhsinchy/hello-dock   "/docker-entrypoint.…"   3 minutes ago       Exited (0) 3 minutes ago                          reverent_torvalds
# 128ec8ceab71        hello-world           "/hello"                 4 minutes ago       Exited (0) 4 minutes ago                          exciting_chebyshev

如你所见,列表 reverent_torvalds 中的第二个容器是较早创建的,并以状态代码 0 退出,这表明在容器运行期间未产生任何错误。

怎样命名或者重命名一个容器

默认情况下,每个容器都有两个标识符。如下:

  • CONTAINER ID - 64 个字符的随机字符串。

  • NAME - 两个随机词的组合,下划线连接。

基于这两个随机标识符来引用容器非常不方便。如果可以使用自定义的名称来引用容器,那就太好了。

可以使用 --name 选项来命名容器。要使用名为 hello-dock-container 的 fhsinchy/hello-dock 镜像运行另一个容器,可以执行以下命令:

docker container run --detach --publish 8888:80 --name hello-dock-container fhsinchy/hello-dock

# b1db06e400c4c5e81a93a64d30acc1bf821bed63af36cab5cdb95d25e114f5fb

本地网络上的 8080 端口被 gifted_sammet 容器(在上一小节中创建的容器)占用了。这就是为什么必须使用其他端口号(例如 8888)的原因。要进行验证,执行 container ls 命令:

docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   28 seconds ago      Up 26 seconds       0.0.0.0:8888->80/tcp   hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   4 minutes ago       Up 4 minutes        0.0.0.0:8080->80/tcp   gifted_sammet

一个名为 hello-dock-container 的新容器已经启动。

甚至可以使用 container rename 命令来重命名旧容器。该命令的语法如下:

docker container rename  

要将 gifted_sammet 容器重命名为 hello-dock-container-2,可以执行以下命令:

docker container rename gifted_sammet hello-dock-container-2

该命令不会产生任何输出,但是可以使用 container ls 命令来验证是否已进行更改。rename 命令不仅适用于处于运行状态的容器和还适用于处于停止状态的容器。

怎样停止或者杀死运行中的容器

可以通过简单地关闭终端窗口或单击 ctrl + c 来停止在前台运行的容器。但是,不能以相同方式停止在后台运行的容器。

有两个命令可以完成此任务。第一个是 container stop 命令。该命令的通用语法如下:

docker container stop 

其中 container identifier 可以是容器的 ID 或名称。

应该还记得上一节中启动的容器。它仍在后台运行。使用 docker container ls 获取该容器的标识符(在本演示中,我将使用 hello-dock-container 容器)。现在执行以下命令来停止容器:

docker container stop hello-dock-container

# hello-dock-container

如果使用 name 作为标识符,则 name 将作为输出返回。stop 命令通过发送信号SIGTERM 来正常关闭容器。如果容器在一定时间内没有停止运行,则会发出 SIGKILL 信号,该信号会立即关闭容器。

如果要发送 SIGKILL 信号而不是 SIGTERM 信号,则可以改用 container kill 命令。container kill 命令遵循与 stop 命令相同的语法。

docker container kill hello-dock-container-2

# hello-dock-container-2

怎样重新启动容器

当我说重启时,我指的如下是两种情况:

  • 重新启动先前已停止或终止的容器。

  • 重新启动正在运行的容器。

正如上一小节中学到的,停止的容器保留在系统中。如果需要,可以重新启动它们。container start  命令可用于启动任何已停止或终止的容器。该命令的语法如下:

docker container start 

可以通过执行 container ls --all 命令来获取所有容器的列表,然后寻找状态为 Exited 的容器。

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                        PORTS               NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   3 minutes ago       Exited (0) 47 seconds ago                         hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   7 minutes ago       Exited (137) 17 seconds ago                       hello-dock-container-2
# 6cf52771dde1        fhsinchy/hello-dock   "/docker-entrypoint.…"   7 minutes ago       Exited (0) 7 minutes ago                          reverent_torvalds
# 128ec8ceab71        hello-world           "/hello"                 9 minutes ago       Exited (0) 9 minutes ago                          exciting_chebyshev

现在要重新启动 hello-dock-container 容器,可以执行以下命令:

docker container start hello-dock-container

# hello-dock-container

现在,可以使用 container ls 命令查看正在运行的容器列表,以确保该容器正在运行。

默认情况下,container start 命令以分离模式启动容器,并保留之前进行的端口配置。因此,如果现在访问 http://127.0.0.1:8080,应该能够像以前一样访问 hello-dock 应用程序。

Docker 入门教程 - 2021 最新版(上)_第12张图片

现在,在想重新启动正在运行的容器,可以使用 container restart 命令。container restart 命令遵循与 container start 命令完全相同的语法。

docker container restart hello-dock-container-2

# hello-dock-container-2

这两个命令之间的主要区别在于,container restart 命令尝试停止目标容器,然后再次启动它,而 start 命令只是启动一个已经停止的容器。

在容器停止的情况下,两个命令完全相同。但是如果容器正在运行,则必须使用container restart 命令。

怎样创建而不运行容器

到目前为止,在本节中,已经使用 container run 命令启动了容器,该命令实际上是两个单独命令的组合。这两个命令如下:

  • container create 命令从给定的镜像创建一个容器。

  • container start 命令将启动一个已经创建的容器。

现在,要使用这两个命令执行运行容器部分中显示的演示,可以执行以下操作 :

docker container create --publish 8080:80 fhsinchy/hello-dock

# 2e7ef5098bab92f4536eb9a372d9b99ed852a9a816c341127399f51a6d053856

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS               NAMES
# 2e7ef5098bab        fhsinchy/hello-dock   "/docker-entrypoint.…"   30 seconds ago      Created                                 hello-dock

通过 container ls --all 命令的输出可以明显看出,已经使用 fhsinchy/hello-dock镜像创建了一个名称为 hello-dock 的容器。容器的 STATUS 目前处于 Created 状态,并且鉴于其未运行,因此不使用  --all 选项就不会列出该容器。

一旦创建了容器,就可以使用 container start 命令来启动它。

docker container start hello-dock

# hello-dock

docker container ls

# CONTAINER ID        IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# 2e7ef5098bab        fhsinchy/hello-dock   "/docker-entrypoint.…"   About a minute ago   Up 29 seconds       0.0.0.0:8080->80/tcp   hello-dock

容器 STATUS 已从 Created 更改为 Up 29 seconds,这表明容器现在处于运行状态。端口配置也显示在以前为空的 PORTS 列中。

尽管可以在大多数情况下使用 container run 命令,但本书稍后还会有一些情况要求使用 container create 命令。

怎样移除挂起的容器

如你所见,已被停止或终止的容器仍保留在系统中。这些挂起的容器可能会占用空间或与较新的容器发生冲突。

可以使用 container rm 命令删除停止的容器。通用语法如下:

docker container rm 

要找出哪些容器没有运行,使用 container ls --all 命令并查找状态为 Exited 的容器。

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS                      PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   6 minutes ago       Up About a minute           0.0.0.0:8888->80/tcp   hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   10 minutes ago      Up About a minute           0.0.0.0:8080->80/tcp   hello-dock-container-2
# 6cf52771dde1        fhsinchy/hello-dock   "/docker-entrypoint.…"   10 minutes ago      Exited (0) 10 minutes ago                          reverent_torvalds
# 128ec8ceab71        hello-world           "/hello"                 12 minutes ago      Exited (0) 12 minutes ago                          exciting_chebyshev

从输出中可以看到,ID为 6cf52771dde1 和 128ec8ceab71 的容器未运行。要删除 6cf52771dde1,可以执行以下命令:

docker container rm 6cf52771dde1

# 6cf52771dde1

可以使用 container ls 命令检查容器是否被删除。也可以一次删除多个容器,方法是将其标识符一个接一个地传递,每个标识符之间用空格隔开。

也可以使用 container prune 命令来一次性删除所有挂起的容器。

可以使用 container ls --all 命令检查容器列表,以确保已删除了挂起的容器:

docker container ls --all

# CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                  NAMES
# b1db06e400c4        fhsinchy/hello-dock   "/docker-entrypoint.…"   8 minutes ago       Up 3 minutes        0.0.0.0:8888->80/tcp   hello-dock-container
# 9f21cb777058        fhsinchy/hello-dock   "/docker-entrypoint.…"   12 minutes ago      Up 3 minutes        0.0.0.0:8080->80/tcp   hello-dock-container-2

如果按照本书的顺序进行操作,则应该只在列表中看到 hello-dock-container 和 hello-dock-container-2。建议停止并删除两个容器,然后再继续进行下一部分。

container run 和 container start 命令还有 --rm 选项,它们表示希望容器在停止后立即被移除。执行以下命令,使用 --rm 选项启动另一个 hello-dock 容器:

docker container run --rm --detach --publish 8888:80 --name hello-dock-volatile fhsinchy/hello-dock

# 0d74e14091dc6262732bee226d95702c21894678efb4043663f7911c53fb79f3

可以使用 container ls 命令来验证容器是否正在运行:

docker container ls

# CONTAINER ID   IMAGE                 COMMAND                  CREATED              STATUS              PORTS                  NAMES
# 0d74e14091dc   fhsinchy/hello-dock   "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:8888->80/tcp   hello-dock-volatile

现在,如果停止了容器,使用 container ls --all 命令再次检查:

docker container stop hello-dock-volatile

# hello-dock-volatile

docker container ls --all

# CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

该容器已被自动删除。从现在开始,我将对大多数容器使用 --rm 选项。不需要的地方我会明确提到。

怎样以交互式模式运行容器

到目前为止,只运行了 hello-world 镜像或 fhsinchy/hello-dock 镜像。这些镜像用于执行非交互式的简单程序。

好吧,镜像并不是那么简单。镜像可以将整个 Linux 发行版封装在其中。

流行的发行版,例如 Ubuntu,Fedora 和 Debian 都在 hub 有官方的 Docker 镜像。编程语言,例如 python、php、[go](https:// hub.docker.com/_/golang) 或类似 node 和 deno 都有其官方镜像。

这些镜像不但仅运行某些预配置的程序。还将它们配置为默认情况下运行的 shell 程序。在镜像是操作系统的情况下,它可以是诸如 sh 或 bash 之类的东西,在竟像是编程语言或运行时的情况下,通常是它们的默认语言的 shell。

正如可能从以前的计算机中学到的一样,shell 是交互式程序。被配置为运行这样的程序的镜像是交互式镜像。这些镜像需要在  container run  命令中传递特殊的 -it 选项。

例如,如果通过执行 docker container run ubuntu 使用 ubuntu 镜像运行一个容器,将不会发生任何事情。但是,如果使用 -it 选项执行相同的命令,会直接进入到 Ubuntu 容器内的 bash 上。

docker container run --rm -it ubuntu

# root@dbb1f56b9563:/# cat /etc/os-release
# NAME="Ubuntu"
# VERSION="20.04.1 LTS (Focal Fossa)"
# ID=ubuntu
# ID_LIKE=debian
# PRETTY_NAME="Ubuntu 20.04.1 LTS"
# VERSION_ID="20.04"
# HOME_URL="https://www.ubuntu.com/"
# SUPPORT_URL="https://help.ubuntu.com/"
# BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
# PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
# VERSION_CODENAME=focal
# UBUNTU_CODENAME=focal

从  cat /etc/os-release  命令的输出中可以看到,我确实正在与在 Ubuntu 容器中运行的 bash 进行交互。

-it 选项提供了与容器内的程序进行交互的场景。此选项实际上是将两个单独的选项混在一起。

  • 选项  -i 或  --interactive 连接到容器的输入流,以便可以将输入发送到 bash。

  • -t 或 --tty  选项可通过分配伪 tty 来格式化展示并提供类似本机终端的体验。

当想以交互方式运行容器时,可以使用 -it 选项。以交互式方式运行 node 镜像,如下:

docker container run -it node

# Welcome to Node.js v15.0.0.
# Type ".help" for more information.
# > ['farhan', 'hasin', 'chowdhury'].map(name => name.toUpperCase())
# [ 'FARHAN', 'HASIN', 'CHOWDHURY' ]

任何有效的 JavaScript 代码都可以在 node shell 中执行。除了输入 -it,还可以输入 --interactive --tty,效果一样,只不过更冗长。

怎样在容器里执行命令

在本书中初识 Docker 部分中,已经了解了在 Alpine Linux 容器内执行命令。 它是这样的:

docker run alpine uname -a
# Linux f08dbbe9199b 5.8.0-22-generic #23-Ubuntu SMP Fri Oct 9 00:34:40 UTC 2020 x86_64 Linux

在此命令中,在 Alpine Linux 容器中执行了 uname -a 命令。像这样的场景(要做的就是在特定的容器内执行特定的命令)非常常见。

假设想使用 base64 程序对字符串进行编码。几乎所有基于 Linux 或 Unix 的操作系统都可以使用此功能(但 Windows 则不可用)。

在这种情况下,可以使用 busybox 之类的镜像快速启动容器,然后执行命令。

使用 base64 编码字符串的通用语法如下:

echo -n my-secret | base64

# bXktc2VjcmV0

将命令传递到未运行的容器的通用语法如下:

docker container run  

要使用 busybox 镜像执行 base64  编码,可以执行以下命令:

docker container run --rm busybox echo -n my-secret | base64

# bXktc2VjcmV0

这里发生的是,在 container run 命令中,镜像名称后传递的任何内容都将传递到镜像的默认入口里。

入口点就像是通往镜像的网关。除可执行镜像外的大多数镜像(在使用可执行镜像小节中说明)使用 shell 或 sh 作为默认入口点。因此,任何有效的 shell 命令都可以作为参数传递给它们。

如何处理可执行镜像

在上一节中,我简要提到了可执行镜像。这些镜像旨在表现得像可执行程序。

以的 rmbyext 项目为例。这是一个简单的 Python 脚本,能够递归删除给定扩展名的文件。 要了解有关该项目的更多信息,可以查看仓库。

如果同时安装了 Git 和 Python,则可以通过执行以下命令来安装此脚本:

pip install git+https://github.com/fhsinchy/rmbyext.git#egg=rmbyext

假设的系统上已经正确设置了 Python,则该脚本应该可以在终端的任何位置使用。使用此脚本的通用语法如下:

rmbyext 

要对其进行测试,请在一个空目录下打开终端,并在其中创建具有不同扩展名的一些文件。可以使用 touch 命令来做到这一点。现在,计算机上有一个包含以下文件的目录:

touch a.pdf b.pdf c.txt d.pdf e.txt

ls

# a.pdf  b.pdf  c.txt  d.pdf  e.txt

要从该目录删除所有 pdf 文件,可以执行以下命令:

rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf

该程序的可执行镜像能够将文件扩展名用作参数,并像 rmbyext 程序一样删除它们。

fhsinchy/rmbyext 镜像的行为类似。该镜像包含 rmbyext 脚本的副本,并配置为在容器内的目录 /zone上运行该脚本。

现在的问题是容器与本地系统隔离,因此在容器内运行的 rmbyext 程序无法访问本地文件系统。因此,如果可以通过某种方式将包含 pdf 文件的本地目录映射到容器内的 /zone目录,则容器应该可以访问这些文件。

授予容器直接访问本地文件系统的一种方法是使用绑定挂载。

绑定挂载可以在本地文件系统目录(源)与容器内另一个目录(目标)之间形成双向数据绑定。这样,在目标目录中进行的任何更改都将在源目录上生效,反之亦然。

让我们看一下绑定挂载的实际应用。要使用此镜像而不是程序本身删除文件,可以执行以下命令:

docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf

# Removing: PDF
# b.pdf
# a.pdf
# d.pdf

已经在命令中看到了 -v $(pwd):/zone  部分,你可能已经猜到了 -v  或 --volume 选项用于为容器创建绑定挂载。该选项可以使用三个以冒号(:)分隔的字段。该选项的通用语法如下:

--volume ::

第三个字段是可选的,但必须传递本地目录的绝对路径和容器内目录的绝对路径。

在这里,源目录是 /home/fhsinchy/the-zone。假设终端当前在目录中,则 $(pwd) 将替换为包含先前提到的 .pdf 和 .txt 文件的 /home/fhsinchy/the-zone

可以在command substitution here 上了解更多信息。

--volume 或  -v  选项对 container run 以及 container create 命令均有效。我们将在接下来的部分中更详细地探讨卷,因此,如果在这里不太了解它们,请不要担心。

常规镜像和可执行镜像之间的区别在于,可执行镜像的入口点设置为自定义程序而不是 sh,在本例中为 rmbyext 程序。正如在上一小节中所学到的那样,在 container run命令中在镜像名称之后编写的所有内容都将传递到镜像的入口点。

所以最后,docker container run --rm -v $(pwd):/zone fhsinchy/rmbyext pdf 命令转换为容器内的  rmbyext pdf 。可执行镜像并不常见,但在某些情况下可能非常有用。

你可能感兴趣的:(微服务)