运行你的第一个容器

DockerDockerBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在本实验中,你将运行你的第一个 Docker 容器。

容器只是一个隔离运行的进程(或一组进程)。隔离是通过 Linux 命名空间、控制组(cgroups)、seccomp 和 SELinux 实现的。请注意,Linux 命名空间和控制组是内置于 Linux 内核中的!除了 Linux 内核本身,容器并没有什么特别之处。

使容器变得有用的是围绕它的工具。对于这些实验,我们将使用 Docker,它是一个被广泛采用的用于使用容器构建应用程序的工具。Docker 为开发者和运维人员提供了一个友好的界面,以便在任何具有 Docker 引擎的环境中构建、交付和运行容器。由于 Docker 客户端需要一个 Docker 引擎,另一种选择是使用 Podman,它是一个无守护进程的容器引擎,用于开发、管理和运行 OCI 容器,并且能够以 root 身份或无 root 模式运行容器。出于这些原因,我们推荐使用 Podman,但由于 Docker 的广泛应用,本实验仍使用 Docker。

在本实验的第一部分,我们将运行我们的第一个容器,并学习如何检查它。我们将能够见证从 Linux 内核获得的命名空间隔离。

在运行第一个容器之后,我们将深入探讨容器的其他用途。你可以在 Docker 商店上找到许多这样的示例,并且我们将在同一主机上运行几种不同类型的容器。这将使我们看到隔离的好处——我们可以在同一主机上运行多个容器而不会产生冲突。

在本实验中,我们将使用一些 Docker 命令。有关可用命令的完整文档,请查看 官方文档


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("`Docker`")) -.-> docker/ContainerOperationsGroup(["`Container Operations`"]) docker(("`Docker`")) -.-> docker/SystemManagementGroup(["`System Management`"]) linux(("`Linux`")) -.-> linux/PackagesandSoftwaresGroup(["`Packages and Softwares`"]) docker/ContainerOperationsGroup -.-> docker/run("`Run a Container`") docker/ContainerOperationsGroup -.-> docker/ls("`List Containers`") docker/ContainerOperationsGroup -.-> docker/exec("`Execute Command in Container`") docker/ContainerOperationsGroup -.-> docker/top("`Display Running Processes in Container`") docker/SystemManagementGroup -.-> docker/version("`Show Docker Version`") docker/SystemManagementGroup -.-> docker/prune("`Remove Unused Docker Objects`") linux/PackagesandSoftwaresGroup -.-> linux/apt("`Package Handling`") subgraph Lab Skills docker/run -.-> lab-148982{{"`运行你的第一个容器`"}} docker/ls -.-> lab-148982{{"`运行你的第一个容器`"}} docker/exec -.-> lab-148982{{"`运行你的第一个容器`"}} docker/top -.-> lab-148982{{"`运行你的第一个容器`"}} docker/version -.-> lab-148982{{"`运行你的第一个容器`"}} docker/prune -.-> lab-148982{{"`运行你的第一个容器`"}} linux/apt -.-> lab-148982{{"`运行你的第一个容器`"}} end

入门指南

在 LabEx VM 上打开一个终端并运行 docker -h,这将显示 Docker CLI 的帮助页面。

$ docker -h
Flag shorthand -h has been deprecated, please use --help

Usage: docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

...

Management Commands:
builder Manage builds
config Manage Docker configs
container Manage containers
engine Manage the docker engine
image Manage images
network Manage networks
node Manage Swarm nodes
plugin Manage plugins
secret Manage Docker secrets
service Manage services
stack Manage Docker stacks
swarm Manage Swarm
system Manage Docker
trust Manage trust on Docker images
volume Manage volumes

Docker 命令行可用于管理 Docker 引擎的多个功能。在本实验中,我们将主要关注 container 命令。

在你的 LabEx VM 上安装 podman

sudo apt-get update
sudo apt-get install podman -y

如果安装了 podman,你可以运行替代命令进行比较。

sudo podman -h

你还可以通过 docker version 查看 Docker 的安装版本

docker version

Client:
Version: 20.10.21
...

Server:
Engine:
Version: 20.10.21
...

你会注意到 Docker 安装了一个 客户端 和一个 服务器:Docker 引擎。例如,如果你对 podman 运行相同的命令,你只会看到一个 CLI 版本,因为 podman 无守护进程运行,并且依赖于符合 OCI 的容器运行时(runc、crun、runv 等)与操作系统进行交互以创建正在运行的容器。

sudo podman version --events-backend=none
Version: 3.4.4
API Version: 3.4.4
Go Version: go1.17.3
Built: Thu Jan 1 08:00:00 1970
OS/Arch: linux/amd64

运行你的第一个容器

我们将使用 Docker CLI 来运行我们的第一个容器。

在 LabEx VM 上打开一个终端。

运行命令。

docker container run -t ubuntu top

使用 docker container run 命令,通过 top 命令运行一个使用 ubuntu 镜像的容器。-t 标志会分配一个伪终端,这是 top 命令正常工作所必需的。

$ docker container run -it ubuntu top
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
aafe6b5e13de: Pull complete
0a2b43a72660: Pull complete
18bdd1e546d2: Pull complete
8198342c3e05: Pull complete
f56970a44fd4: Pull complete
Digest: sha256:f3a61450ae43896c4332bda5e78b453f4a93179045f20c8181043b26b5e79028
Status: Downloaded newer image for ubuntu:latest

docker run 命令首先会执行 docker pull,将 ubuntu 镜像下载到你的主机上。下载完成后,它会启动容器。正在运行的容器的输出应该如下所示:

top - 20:32:46 up 3 days, 17:40,  0 users,  load average: 0.00, 0.01, 0.00
Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  2046768 total,   173308 free,   117248 used,  1756212 buff/cache
KiB Swap:  1048572 total,  1048572 free,        0 used.  1548356 avail Mem

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
      1 root      20   0   36636   3072   2640 R   0.3  0.2   0:00.04 top

top 是一个 Linux 实用工具,它会打印系统上的进程,并按资源消耗对它们进行排序。请注意,此输出中只有一个进程:它就是 top 进程本身。由于 PID 命名空间隔离,我们在这个列表中看不到主机上的其他进程。

容器使用 Linux 命名空间来提供与其他容器或主机的系统资源隔离。PID 命名空间为进程 ID 提供隔离。如果你在容器内部运行 top,你会注意到它显示的是容器的 PID 命名空间内的进程,这与你在主机上运行 top 时看到的情况大不相同。

尽管我们使用的是 ubuntu 镜像,但需要注意的是,我们的容器没有自己的内核。它使用主机的内核,而 ubuntu 镜像仅用于提供 Ubuntu 系统上可用的文件系统和工具。

使用 docker container exec 检查容器

docker container exec 命令是一种通过新进程“进入”正在运行的容器的命名空间的方法。

打开一个新终端。选择 终端 > 新建终端

在新终端中,使用 docker container ls 命令获取你刚刚创建的正在运行的容器的 ID。

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b3ad2a23fab3 ubuntu "top" 29 minutes ago Up 29 minutes goofy_nobel

然后使用该 ID,通过 docker container exec 命令在该容器内运行 bash。由于我们使用的是 bash 并且希望从终端与这个容器进行交互,因此使用 -it 标志以交互模式运行,同时分配一个伪终端。

$ docker container exec -it ID < CONTAINER > bash
root@b3ad2a23fab3:/## ```

瞧!我们刚刚使用 `docker container exec` 命令通过我们的 `bash` 进程“进入”了容器的命名空间。将 `docker container exec` 与 `bash` 一起使用是检查 Docker 容器的常见模式。

注意终端前缀的变化。例如 `root@b3ad2a23fab3:/`。这表明我们正在容器“内部”运行 `bash`。

**注意**:这与通过 SSH 连接到单独的主机或 VM 不同。我们不需要 SSH 服务器来连接到 `bash` 进程。请记住,容器使用内核级功能来实现隔离,并且容器运行在内核之上。我们的容器只是在同一主机上隔离运行的一组进程,我们可以使用 `docker container exec` 通过 `bash` 进程进入该隔离环境。运行 `docker container exec` 后,隔离运行的进程组(即我们的容器)包括 `top` 和 `bash`。

在同一个终端中,运行 `ps -ef` 来检查正在运行的进程。

```bash
root@b3ad2a23fab3:/## ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 20:34? 00:00:00 top
root 17 0 0 21:06? 00:00:00 bash
root 27 17 0 21:14? 00:00:00 ps -ef

你应该只看到 top 进程、bash 进程和我们的 ps 进程。

为了进行比较,退出容器,然后在主机上运行 ps -eftop。这些命令在 Linux 或 Mac 上都可以使用。对于 Windows,你可以使用 tasklist 来检查正在运行的进程。

root@b3ad2a23fab3:/## exit
exit
$ ps -ef
## 很多进程!

技术深入探讨
PID 只是为容器提供系统资源隔离的 Linux 命名空间之一。其他 Linux 命名空间包括:

  • MNT - 挂载和卸载目录,而不会影响其他命名空间
  • NET - 容器有自己的网络栈
  • IPC - 隔离的进程间通信机制,如消息队列。
  • User - 系统上用户的隔离视图
  • UTC - 为每个容器设置主机名和域名

这些命名空间共同为容器提供了隔离,使它们能够安全地一起运行,并且不会与在同一系统上运行的其他容器发生冲突。接下来,我们将演示容器的不同用途,以及在同一主机上运行多个容器时隔离的好处。

注意:命名空间是 Linux 内核的一个特性。但是 Docker 允许你在 Windows 和 Mac 上运行容器……这是如何实现的呢?秘密在于 Docker 产品或 Docker 引擎中嵌入了一个 Linux 子系统。Docker 将这个 Linux 子系统开源到了一个新项目:LinuxKit。能够在许多不同平台上运行容器是将 Docker 工具与容器一起使用的一个优势。

除了使用 Linux 子系统在 Windows 上运行 Linux 容器之外,由于在 Windows 操作系统上创建了容器原语,现在原生 Windows 容器也成为可能。原生 Windows 容器可以在 Windows 10 或 Windows Server 2016 或更高版本上运行。

注意:如果你在容器化的终端中运行此练习,并在终端中执行 ps -ef 命令,在退出 exec 命令后,你仍然只会看到一组有限的进程。你可以尝试在本地机器的终端中运行 ps -ef 命令以查看所有进程。

通过按 <ctrl>-c 来清理运行 top 进程的容器,列出所有容器并通过它们的 ID 删除容器。

docker ps -a

docker rm <CONTAINER ID>

运行多个容器

探索 Docker Hub

Docker Hub 是 Docker 镜像的公共中央注册表,其中包含社区镜像和官方镜像。

在搜索镜像时,你会找到“Docker 认证”、“已验证发布者”和“官方镜像”等过滤器。选择“Docker 认证”过滤器,以查找被视为企业就绪且经过 Docker 企业版产品测试的镜像。在开发要部署到生产环境的自定义镜像时,避免使用来自 Docker 商店的未经验证的内容非常重要。这些未经验证的镜像可能包含安全漏洞甚至恶意软件。

在本实验的第 2 步中,我们将使用 Docker Hub 中的一些经过验证的镜像启动几个容器:Nginx 网络服务器和 MongoDB 数据库。

运行 Nginx 服务器

让我们使用 Docker Hub 中的 官方 Nginx 镜像 运行一个容器。

docker container run --detach --publish 8080:80 --name nginx nginx

这里我们使用了几个新标志。--detach 标志将在后台运行此容器。publish 标志通过我们主机上的 8080 端口发布容器中的 80 端口(Nginx 的默认端口)。请记住,NET 命名空间为容器的进程提供了自己的网络栈。--publish 标志是一项功能,它允许我们通过容器将网络暴露到主机上。

你怎么知道 80 端口是 Nginx 的默认端口呢?因为它列在 Docker Hub 上的 文档 中。一般来说,经过验证的镜像的文档非常完善,在使用这些镜像运行容器时你会想要参考它们。

我们还指定了 --name 标志,用于为容器命名。每个容器都有一个名称,如果你不指定,Docker 会为你随机分配一个。指定自己的名称会使在容器上运行后续命令变得更容易,因为你可以引用名称而不是容器的 ID。例如:docker container inspect nginx 而不是 docker container inspect 5e1

由于这是你第一次运行 Nginx 容器,它将从 Docker 商店拉取 Nginx 镜像。后续从 Nginx 镜像创建的容器将使用位于你主机上的现有镜像。

Nginx 是一个轻量级的网络服务器。你可以在 LabEx VM 的“Web 8080”标签中访问 Nginx 服务器。切换到该标签并刷新页面以查看 Nginx 的输出。

步骤 2 Nginx

运行 mongo 数据库服务器

现在,运行一个 MongoDB 服务器。我们将使用 Docker Hub 中的 官方 MongoDB 镜像。我们将使用特定版本的 mongo 镜像:4.4,而不是使用 latest 标签(如果未指定标签,这是默认值)。

docker container run --detach --publish 8081:27017 --name mongo mongo:4.4

同样,由于这是我们第一次运行 mongo 容器,我们将从 Docker 商店拉取 mongo 镜像。我们使用 --publish 标志在我们的主机上暴露 27017 mongo 端口。我们必须使用除 8080 之外的端口进行主机映射,因为该端口已经在我们的主机上被暴露了。再次参考 Docker Hub 上的 官方文档 以获取有关使用 mongo 镜像的更多详细信息。

在 Web 浏览器中使用 0.0.0.0:8081 查看 MongoDB 的输出。你应该会看到一条消息,该消息将返回来自 MongoDB 的警告。

MongoDB 服务器输出警告

使用 docker container ls 检查你正在运行的容器

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6777df89fea nginx "nginx -g 'daemon..." 不到一秒前 已启动 2 秒 0.0.0.0:8080- nginx > 80/tcp
ead80a0db505 mongo "docker-entrypoint..." 17 秒前 已启动 19 秒 0.0.0.0:8081- mongo > 27017/tcp
af549dccd5cf ubuntu "top" 5 分钟前 已启动 5 分钟 priceless_kepler

你应该会看到你有一个 Nginx 网络服务器容器和一个 MongoDB 容器在你的主机上运行。请注意,我们尚未配置这些容器相互通信。

你可以看到我们给容器起的“nginx”和“mongo”名称,以及为 ubuntu 容器生成的随机名称(在我的例子中是“priceless_kepler”)。你还可以看到我们使用 --publish 标志指定的端口映射。有关这些正在运行的容器的更多详细信息,你可以使用 docker container inspect [容器 ID 命令。

你可能会注意到的一件事是,mongo 容器正在运行 docker-entrypoint 命令。这是容器启动时运行的可执行文件的名称。mongo 镜像在启动数据库进程之前需要一些预先配置。你可以通过在 github 上查看它来确切了解该脚本的作用。通常,你可以从 Docker 商店网站上的镜像描述页面找到到 github 源代码的链接。

容器是自包含且隔离的,这意味着我们可以避免具有不同系统或运行时依赖项的容器之间的潜在冲突。例如:在同一主机上部署使用 Java 7 的应用程序和使用 Java 8 的另一个应用程序。或者运行多个默认监听端口均为 80 的 Nginx 容器(如果使用 --publish 标志在主机上暴露,为主机选择的端口需要是唯一的)。由于 Linux 命名空间,隔离的好处是可能实现的。

注意:除了 Docker 之外,你无需在主机上安装任何东西即可运行这些进程!每个容器在容器内部都包含它所需的依赖项,因此你无需直接在主机上安装任何东西。

在同一主机上运行多个容器使我们能够充分利用单个主机上可用的资源(CPU、内存等)。这可以为企业节省大量成本。

虽然有时直接从 Docker Hub 运行镜像可能会很有用,但创建自定义镜像并将官方镜像作为这些镜像的起点会更有用。我们将在实验 2 中深入探讨构建我们自己的自定义镜像。

清理

完成本实验后,你的主机上会有一堆正在运行的容器。让我们清理一下这些容器。

首先,使用 docker container ls 获取正在运行的容器列表。

$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6777df89fea nginx "nginx -g 'daemon..." 3 分钟前 已启动 3 分钟 0.0.0.0:8080- nginx > 80/tcp
ead80a0db505 mongo "docker-entrypoint..." 3 分钟前 已启动 3 分钟 0.0.0.0:8081- mongo > 27017/tcp
af549dccd5cf ubuntu "top" 8 分钟前 已启动 8 分钟 priceless_kepler

接下来,对列表中的每个容器运行 docker container stop [容器 ID]。你也可以使用之前指定的容器名称。

$ docker container stop d67 ead af5
d67
ead
af5

注意:你只需引用足够多的 ID 数字以确保其唯一性。三位数字几乎总是足够的。

删除已停止的容器

docker system prune 是一个非常方便的命令,用于清理你的系统。它将删除任何已停止的容器、未使用的卷和网络以及悬空镜像。

$ docker system prune
WARNING! This will remove:
- 所有已停止的容器
- 所有至少一个容器未使用的卷
- 所有至少一个容器未使用的网络
- 所有悬空镜像
你确定要继续吗?[y/N] y
已删除的容器:
7872fd96ea4695795c41150a06067d605f69702dbcb9ce49492c9029f0e1b44b
60abd5ee65b1e2732ddc02b971a86e22de1c1c446dab165462a08b037ef7835c
31617fdd8e5f584c51ce182757e24a1c9620257027665c20be75aa3ab6591740

总共回收的空间:12B

总结

在本实验中,你创建了你的第一个 Ubuntu、Nginx 和 MongoDB 容器。

关键要点

  • 容器由 Linux 命名空间和控制组组成,它们提供了与其他容器和主机的隔离。
  • 由于容器的隔离特性,你可以在单个主机上调度多个容器,而不必担心冲突的依赖项。这使得在单个主机上运行多个容器变得更加容易:充分利用分配给该主机的资源,并最终在服务器成本上节省一些资金。
  • 在开发自己的镜像时,避免使用来自 Docker 商店的未经验证的内容,因为这些镜像可能包含安全漏洞甚至恶意软件。
  • 容器包含运行其中进程所需的一切,因此无需直接在主机上安装额外的依赖项。

您可能感兴趣的其他 Docker 教程