在 RHEL 上使用 Podman 运行容器

Red Hat Enterprise LinuxBeginner
立即练习

介绍

在这个实验(Lab)中,你将学习如何在 Red Hat Enterprise Linux (RHEL) 上使用 Podman 部署一个多层 Web 应用。你将通过部署一个 MariaDB 数据库容器作为后端,以及一个 Apache Web 服务器容器作为前端,来构建一个完整的解决方案。这种实践经验将指导你完成容器化应用部署的基本步骤,从初始配置到使服务公开可访问。

你将首先运行一个 MariaDB 容器,并在启动时使用环境变量对其进行配置。接下来,你将配置持久性存储以确保数据库的数据持久性,并创建一个自定义网络用于容器间的通信。然后,你将部署 Apache Web 服务器,暴露其端口以测试连通性,最后,学习如何将容器作为 systemd 服务进行管理,以实现稳健、自动化的操作。

这是一个实验(Guided Lab),提供逐步指导来帮助你学习和实践。请仔细按照说明完成每个步骤,获得实际操作经验。根据历史数据,这是一个 初级 级别的实验,完成率为 93%。获得了学习者 98% 的好评率。

使用环境变量运行 MariaDB 数据库容器

在这一步中,你将学习如何运行一个容器化应用,并使用环境变量在启动时对其进行配置。这是容器管理中的一项基本技能,它允许进行灵活且安全的部署。我们将使用官方的 MariaDB 镜像作为示例,因为它需要几个配置参数来初始化数据库。

首先,确保你在正确的工作目录中。本实验(Lab)的所有工作都将在 ~/project 目录内完成。

cd ~/project

在运行容器之前,最好明确地从注册中心拉取镜像。这可以确保你在本地拥有正确的版本。为了确保一致性,我们将在此实验(Lab)中使用 mariadb:10.6 镜像。

podman pull mariadb:10.6

从 Docker 注册中心选择 mariadb:10.6 镜像。

你应该看到输出,表明镜像正在被下载和提取。

10.6: Pulling from library/mariadb
...
Status: Downloaded newer image for mariadb:10.6
docker.io/library/mariadb:10.6

现在,你可以运行 MariaDB 容器了。podman run 命令创建并启动一个新的容器。我们将使用几个标志:

  • -d:在分离模式(后台)运行容器。
  • --name mariadb_server:为我们的容器分配一个易于记忆的名称。
  • -e VARIABLE=value:在容器内设置一个环境变量。MariaDB 镜像使用这些变量在首次启动时配置数据库。

运行以下命令来启动你的 MariaDB 容器。我们正在设置 root 密码,并创建一个名为 webappdb 的新数据库,以及一个专用的用户 webappuser

podman run -d \
  --name mariadb_server \
  -e MARIADB_ROOT_PASSWORD=supersecret \
  -e MARIADB_DATABASE=webappdb \
  -e MARIADB_USER=webappuser \
  -e MARIADB_PASSWORD=userpass \
  mariadb:10.6

该命令将输出一个长的容器 ID,这确认了容器已被创建。

a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

要验证容器是否正在运行,请使用 podman ps 命令。

podman ps

你应该在正在运行的容器列表中看到 mariadb_server

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS      NAMES
a1b2c3d4e5f6   mariadb:10.6   "docker-entrypoint.s…"   15 seconds ago   Up 14 seconds   3306/tcp   mariadb_server

最后,让我们检查容器的日志,以确保数据库使用我们提供的环境变量正确初始化。

podman logs mariadb_server

滚动浏览日志。你正在寻找一个表明服务器已准备好连接的行,这确认了启动成功。输出将很长,但接近结尾的一个关键的成功消息如下所示:

...
2024-05-20 10:30:00+00:00 [Note] [Entrypoint]: /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/
...
2024-05-20 10:30:15+00:00 [Note] mariadbd: ready for connections.
Version: '10.6.x-MariaDB-1:10.6.x+maria~ubu2004'  socket: '/run/mysqld/mysqld.sock'  port: 3306  mariadb.org binary distribution

你已经成功地使用环境变量启动并配置了一个 MariaDB 容器。

为 MariaDB 容器配置持久存储

在这一步中,你将学习如何为容器配置持久性存储。默认情况下,在容器内创建的任何数据都存储在一个可写层中,该层与容器的生命周期相关联。如果你移除容器,所有这些数据都会丢失。对于像数据库这样的有状态应用来说,这并不理想。为了解决这个问题,我们使用 Podman 卷或绑定挂载(bind mounts)将数据存储在宿主文件系统上,独立于容器。

首先,我们需要移除在上一步中创建的容器,因为我们将使用新的存储配置重新启动它。

停止正在运行的 mariadb_server 容器:

podman stop mariadb_server

你将看到容器的名称作为输出,确认命令已收到。

mariadb_server

现在,移除已停止的容器:

podman rm mariadb_server

同样,容器的名称将被回显。

mariadb_server

接下来,在你的宿主机上的 ~/project 目录中创建一个目录。此目录将保存 MariaDB 数据库文件。

mkdir ~/project/mariadb_data

在 rootless 模式下使用 Podman 时,我们需要为挂载的目录设置正确的权限。MariaDB 容器以特定用户(UID 999)运行,因此我们需要确保该目录是可访问的。我们将使用 --userns=keep-id 标志并设置适当的权限:

chmod 755 ~/project/mariadb_data

现在,再次运行 MariaDB 容器。此命令与上一步中的命令类似,但增加了 -v 标志和 --userns=keep-id 以正确处理用户命名空间映射。-v 标志将宿主机上的 ~/project/mariadb_data 目录挂载到容器内的 /var/lib/mysql 目录中,MariaDB 在该目录中存储其数据。我们使用 $(pwd)/mariadb_datapodman 命令提供所需的绝对路径。

podman run -d \
  --name mariadb_server \
  --userns=keep-id \
  -e MARIADB_ROOT_PASSWORD=supersecret \
  -e MARIADB_DATABASE=webappdb \
  -e MARIADB_USER=webappuser \
  -e MARIADB_PASSWORD=userpass \
  -v $(pwd)/mariadb_data:/var/lib/mysql:Z \
  mariadb:10.6

卷挂载上的 :Z 后缀告诉 Podman 使用私有的、未共享的标签重新标记内容,这对于 SELinux 兼容性很重要。

容器启动后,你可以验证数据是否正在存储在你的宿主机上。列出 ~/project/mariadb_data 目录的内容。

ls -l ~/project/mariadb_data

由于容器的数据库引擎已经初始化,你将看到在 ~/project/mariadb_data 内部创建了几个文件和目录。这确认了你的数据现在是持久的。即使你移除容器,这些数据也将保留。

total 110632
-rw-rw---- 1 labex labex    16384 May 20 10:45 aria_log.00000001
-rw-rw---- 1 labex labex       52 May 20 10:45 aria_log_control
-rw-rw---- 1 labex labex      983 May 20 10:45 ib_buffer_pool
-rw-rw---- 1 labex labex 12582912 May 20 10:45 ibdata1
-rw-rw---- 1 labex labex 50331648 May 20 10:45 ib_logfile0
-rw-rw---- 1 labex labex 50331648 May 20 10:45 ib_logfile1
drwx------ 2 labex labex     4096 May 20 10:45 mysql
drwx------ 2 labex labex     4096 May 20 10:45 performance_schema
drwx------ 2 labex labex     4096 May 20 10:45 sys
drwx------ 2 labex labex     4096 May 20 10:45 webappdb

你已经成功地配置了你的 MariaDB 容器以使用持久性存储,确保你的数据库数据将在容器重启和移除后仍然存在。

创建自定义网络并部署 Apache Web 服务器

在这一步中,你将为你的容器创建一个自定义网络并部署一个 Apache Web 服务器。虽然 Podman 提供了默认网络,但使用自定义网络是最佳实践。它们提供了更好的隔离,并且,最重要的是,能够在容器之间实现自动 DNS 解析。这允许容器使用它们的名称相互通信,这比使用可能更改的 IP 地址更可靠。

首先,让我们为我们的应用程序创建一个自定义桥接网络。我们将它命名为 webapp-network

podman network create webapp-network

该命令将输出新创建的网络的名称。

webapp-network

你可以列出所有 Podman 网络,以确认你的网络已成功创建。

podman network ls

你应该在列表中看到 webapp-network,以及默认网络。

NETWORK ID     NAME               DRIVER    SCOPE
...
f1e2d3c4b5a6   webapp-network     bridge    local
...

接下来,我们需要在这个新网络上重新创建我们的 mariadb_server 容器。由于此环境中的网络后端配置,我们无法将现有容器连接到新网络。相反,我们将停止并使用新的网络配置重新创建容器。

停止正在运行的 mariadb_server 容器:

podman stop mariadb_server

移除已停止的容器:

podman rm mariadb_server

现在,使用新网络重新创建 MariaDB 容器。此命令与上一步中的命令类似,但增加了 --network webapp-network 标志:

podman run -d \
  --name mariadb_server \
  --network webapp-network \
  --userns=keep-id \
  -e MARIADB_ROOT_PASSWORD=supersecret \
  -e MARIADB_DATABASE=webappdb \
  -e MARIADB_USER=webappuser \
  -e MARIADB_PASSWORD=userpass \
  -v $(pwd)/mariadb_data:/var/lib/mysql:Z \
  mariadb:10.6

现在,让我们部署我们的 Web 服务器。我们将使用官方的 Apache httpd 镜像。首先,在宿主机上创建一个目录来存储你的网站文件。

mkdir ~/project/webapp_content

在这个新目录中创建一个简单的 index.html 文件。这将是我们的 Web 应用程序的主页。

echo "<h1>Welcome to My Web App</h1>" > ~/project/webapp_content/index.html

为 webapp 内容目录设置正确的权限,以确保 Apache 容器可以访问这些文件:

chmod 755 ~/project/webapp_content

现在,运行 Apache httpd 容器。我们将把它连接到我们的 webapp-network 并将 webapp_content 目录挂载为卷。这确保了 Web 服务器可以提供我们刚刚创建的 index.html 文件。

podman run -d \
  --name web_server \
  --network webapp-network \
  -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z \
  httpd:2.4

让我们分解一下这些选项:

  • --network webapp-network:将新容器连接到我们的自定义网络。
  • -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z:这会将我们的本地 webapp_content 目录挂载到容器内的 /usr/local/apache2/htdocs/,这是 Apache 提供文件的默认目录。:Z 后缀告诉 Podman 使用私有的、未共享的标签重新标记内容,以实现 SELinux 兼容性。

验证两个容器是否都在运行。

podman ps

你现在应该在正在运行的容器列表中看到 mariadb_serverweb_server

CONTAINER ID  IMAGE                           COMMAND           CREATED         STATUS         PORTS       NAMES
6a3f46c0ab3a  docker.io/library/mariadb:10.6  mariadbd          29 seconds ago  Up 29 seconds  3306/tcp    mariadb_server
da5d52ce9c41  docker.io/library/httpd:2.4     httpd-foreground  7 seconds ago   Up 7 seconds   80/tcp      web_server

这两个容器现在都在同一个自定义网络上,并且可以通过名称相互通信。

暴露 Web 服务器端口并测试连接

在这一步中,你将学习如何将容器的端口暴露给宿主机,从而使服务可以从容器的隔离网络外部访问。我们的 Apache Web 服务器正在运行,但我们还不能从宿主机的浏览器或命令行访问它。我们将通过发布容器的端口来解决这个问题。

端口映射是在创建容器时定义的。因此,我们必须首先停止并移除在上一步中创建的 web_server 容器。不用担心网站内容,它在宿主机上的 ~/project/webapp_content 目录中是安全的,因为我们使用了绑定挂载。

首先,停止容器:

podman stop web_server
web_server

接下来,移除已停止的容器:

podman rm web_server
web_server

现在,我们将再次运行 web_server 容器,但这次我们将添加 -p(或 --publish)标志,以将宿主机上的一个端口映射到容器中的一个端口。我们将宿主机上的端口 8080 映射到容器内的端口 80(默认的 HTTP 端口)。

podman run -d \
  --name web_server \
  --network webapp-network \
  -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z \
  -p 8080:80 \
  httpd:2.4

新的标志 -p 8080:80 告诉 Podman 将来自宿主机上端口 8080 的所有流量转发到 web_server 容器内的端口 80

让我们使用 podman ps 验证容器是否正在运行以及端口是否已正确映射。

podman ps

注意 web_server 容器的 PORTS 列。它现在显示了从 0.0.0.0:808080/tcp 的映射,表明端口已成功暴露。

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                  NAMES
c5d4e3f2a1b6   httpd:2.4      "httpd-foreground"       10 seconds ago   Up 9 seconds    0.0.0.0:8080->80/tcp   web_server
a1b2c3d4e5f6   mariadb:10.6   "docker-entrypoint.s…"   25 minutes ago   Up 25 minutes   3306/tcp               mariadb_server

最后,让我们使用 curl 命令从我们的宿主机测试连接。这会将一个 HTTP 请求发送到 localhost 的端口 8080

curl http://localhost:8080

你应该看到来自你的 index.html 文件的 HTML 内容作为输出,确认你的 Web 服务器现在可以从宿主机访问。

<h1>Welcome to My Web App</h1>

你已经成功地将你的容器化 Web 服务器暴露给宿主机,这是使应用程序可供用户使用的关键一步。

将 Web 服务器容器管理为 systemd 服务

在最后一步中,你将学习如何配置一个容器以自动启动,确保你的服务对崩溃或系统重启具有弹性。在标准的 Red Hat Enterprise Linux 系统上,systemd 是管理服务的主要工具。但是,本实验中的 Podman 环境不直接使用 systemd 来管理容器。

相反,我们将使用 Podman 内置的重启策略来实现相同的结果——自动服务重启。这是确保容器由 Podman 守护进程自动启动的标准、容器原生方式。我们将配置我们的 web_server,以便在它因任何原因停止时始终重新启动。

首先,我们必须移除现有的容器,因为重启策略只能在创建容器时应用。

停止 web_server 容器:

podman stop web_server
web_server

现在移除它:

podman rm web_server
web_server

接下来,使用与之前相同的配置重新创建 web_server 容器,但添加 --restart always 标志。此标志指示 Podman 守护进程监视容器,并在它退出时重新启动它。

podman run -d \
  --name web_server \
  --network webapp-network \
  -v $(pwd)/webapp_content:/usr/local/apache2/htdocs/:Z \
  -p 8080:80 \
  --restart always \
  httpd:2.4

容器将照常启动。要确认重启策略已激活,你可以检查容器的配置。

podman inspect web_server --format '{{.HostConfig.RestartPolicy.Name}}'

该命令应返回 always,确认策略已设置。

always

现在,让我们演示重启策略的工作方式,通过手动重启容器来模拟系统重启或容器故障后会发生的情况。

首先,让我们检查当前的重启策略配置:

podman inspect web_server --format '{{.HostConfig.RestartPolicy.Name}}'

这应该显示 always,确认我们的重启策略已配置。

always

现在让我们测试手动重启,以模拟故障后的恢复:

podman start web_server
web_server

检查容器是否正在运行:

podman ps

你应该看到两个容器都在运行,并且重启策略已到位:

CONTAINER ID   IMAGE          COMMAND                  CREATED              STATUS              PORTS                  NAMES
e7f6g5h4i3j2   httpd:2.4      "httpd-foreground"       About a minute ago   Up 5 seconds        0.0.0.0:8080->80/tcp   web_server
a1b2c3d4e5f6   mariadb:10.6   "docker-entrypoint.s…"   About an hour ago    Up About an hour    3306/tcp               mariadb_server

最后,确认该服务可访问:

curl http://localhost:8080
<h1>Welcome to My Web App</h1>

理解重启策略:

你配置的 --restart always 策略确保:

  • 如果容器意外退出,它将自动重启
  • 当 Podman 服务启动时(例如,系统重启后),容器将自动启动
  • 这为生产部署提供了弹性

注意:在某些实验环境中,自动重启行为可能会因 Podman 配置以及 Podman 系统服务是否正在运行而异。关键的学习目标是理解如何为生产部署配置重启策略。

你已经成功地将你的容器配置为像服务一样被管理,确保它自动保持可用。这完成了容器化应用程序的基本生命周期管理。

总结

在这个实验中,你学习了使用 Podman 在 RHEL 上部署多容器 Web 应用程序的基本过程。你首先通过在运行时传递环境变量,运行了一个 MariaDB 数据库容器,配置了它的初始状态——包括根密码、一个新的数据库和一个专用用户。然后,你为数据库容器配置了持久存储,确保关键数据在容器重启后得以保留。

为了完成应用程序堆栈,你创建了一个自定义网络,以实现容器之间安全、隔离的通信。你将一个 Apache Web 服务器容器部署到这个网络上,并暴露了它的端口以允许外部用户访问。最后,你将 Web 服务器容器与 systemd 集成,将其作为系统服务进行管理,以确保它在启动时自动启动并可靠运行,展示了一个生产就绪的部署模式。