Dockerless 系列文章(三):使用 Podman 将开发环境转到容器

阅读数:10392 2019 年 8 月 28 日 09:00

Dockerless系列文章(三):使用Podman将开发环境转到容器

什么是 Podman,它有用吗?mkdev.me 的本地开发环境如何?如何使用 Podman?去 Docker:真的值得吗?本文提供了一个非常有力的方案,能够几乎完全替代 Docker,总有更多容器领域内发生的事情需要了解,总有新的东西你需要学习和尝试。

在这个系列中的介绍性文章中,我提到过 Podman 和 Buildah 的一个缺点是其技术仍然很新并且变化很快。因为从 Podman 1.3.1 到 1.4.1 中的一个关键特性被破坏了,所以本文的发布推迟了很久。

幸运的是,Podman 1.4.1 及更高版本不仅修复了被破坏长达几周的功能,而且还最终测试了这些功能。希望未来版本中的功能不会出现如此大的意外。我最开始发出的警告仍然有效,那就是:因为新容器技术的工具链是新的,所以有时不稳定。

阅读本文前需要注意的两点:

1、我的警告可能不再有效,这取决于你阅读本文的时间。本文所述的是截至 2019 年 6 月的状态。如果你在 2019 年底或 2020 年才看到这篇文章,那么 Podman 可能已经足够成熟且稳定了,因此你也不必再担心小版本之间的被破坏的功能。

2、我会简要地提及 Podman 的工作原理,但我不会详细地介绍。如果你是一名基础架构工程师或者好奇,请点击我在文章中提供的所有链接了解更多的信息。如果你是一位不太关心内部构件的开发人员,那么请跳过它们,因为你可能需要花些时间来深入研究此主题,而这并不能立即为你的日常工作带来收益。

什么是 Podman,它有用吗?

Podman 是 Docker 的替代品,用于容器化应用程序的本地开发。Podman 命令将 1 对 1 映射 Docker 命令,包括它们的参数。你可以使用 podman 为 docker 添加别名,并且从不会发现管理本地容器的是两种完全不同的工具。

Podman 的核心功能之一是它专注于安全性。使用 Podman 不需要守护进程。相反,它使用传统的 fork-exec 模型,并且大量地使用用户名称空间网络名称空间。因此,Podman 比 Docker 更加孤立,使用起来也更安全。你甚至可以在容器中充当 root,而无需在主机上授予容器或 Podman 任何 root 权限。同时,容器中的用户无法在主机上执行任何 root 级别的任务。

Podman:一种更安全的运行容器的方法》一文中介绍了 Podman 模型如何提高安全性。如果你想了解关于 Podman 如何使用 Linux 名称空间的更多信息,请从《 Podman 和用户命名空间:完美联姻》一文开始阅读。最后,如果你想了解 Podman 这种方法可能遇到的障碍,请阅读《无 root 容器的缺点》。

对于大多数用户来说,Podman 的内部构件在日常使用中应该不太重要。重要的是 Podman 能够在后台以更安全的方式提供与 Docker 相同的开发人员体验。让我们看看这是否属实。

mkdev.me 的本地开发环境

mkdev.me 背后的主要 Web 应用程序是用 Ruby on Rails 编写的。要让开发人员能够在本地运行此应用程序,需要:

  1. PostgreSQL 服务器 ;
  2. Redis 服务器 ;
  3. Mattermost 实例(对于我们的聊天解决方案而言);
  4. Mattermost 测试实例(在自动化测试期间使用);

总共需要 5 个本地运行的服务(包括 Web 应用程序本身)。可以想象,任何新开发人员手动安装和配置所有这些服务都要相当长的时间。而这些操作一旦完成,我们无法保证生成的本地环境接近于生产环境:开发人员可以安装不同的 PostgreSQL 或 Mattermost 版本,这些版本尚未经过测试其是否可与 mkdev 一同使用。

使用一条命令来引导完整的开发环境并在几秒钟内运行与生产类似的设置,这不是很好吗?而这就是 Docker 和 Docker Compose 为开发人员提供的功能。这也是 Podman 能提供的。

Podman 的 pod 和它们的好处

在普通容器的顶部,Podman 有 pod。如果你听说过 Kubernetes,那么这个概念对你来说很熟悉。在 Kubernetes 中,pod 是一个最小的部署单元,它由单个或多个容器组成。Podman 的 pod 和 Kubernetes 完全一样。pod 中的所有容器共享相同的网络命名空间,因此它们可以通过 localhost 轻松地互相通信,而无需导出任何额外的端口。

pod 的 3 种可能的用例如下:

1. 准备好你的应用程序,以使其在 Kubernetes/Openshift 上运行

你可以在 Podman 中使用 pod 作为准备步骤,然后再将应用移至 Kubernetes。在许多情况下,对于真实的 Web 应用程序来说,使用 minikube 可能会更好,因为这能保证你拥有的 API 和功能和 Kubernetes 相同。你可能希望拥有部署、服务和其他资源,这些东西是你在生产环境中设置的重要组成部分。只是用 Podman 来模拟 pod 对这个没多大好处。

2. 在生产中使用 Podman 运行你的应用程序

如果你决定彻底的容器编排对你来说是一种矫枉过正(在许多情况下这也是一个非常好的决定),那么继续使用容器来封装和交付应用程序是有道理的。在某些情况下,你不仅可以从运行多个容器中受益,而且还可以在生产服务器上的 pod 中运行多个容器。但问题是相比于将容器作为单独的系统管理服务来运行,将容器放入 pod 中的好处究竟是什么?在这里我没法给出一个好的答案,但这个功能始终就在那里,有人或许会找到一个它在生产中的用例。

3. 简化你的开发环境

对于开发人员来说最终极和最有吸引力的原因是使用 Podman pod 来自动化开发环境。在这种情况下,你需要在同一个 pod 中运行应用程序依赖的所有服务。在真实 Kubernetes 集群上的生产环境中你绝对不应该做这样的事情,因为你的服务应该运行在不同复制控制器和服务端点后方的不同 pod 中。但对于本地开发来说,这样做却很方便。

Podman pod 和 Kubernetes pod

在看真实的例子之前,我们需要了解 Podman 的一个与 pod 相关的功能:play kube。Podman 没有 Docker Compose 的替代品。但第三方工具 podman-compose 可能会带来这个功能,但 mkdev 还没有来得及测试它。

Podman 拥有 pod 和一种从 YAML 定义来运行 pod 的方法,而不是使用 Docker Compose。此 YAML 定义与 Kubernetes pods YAML 兼容,所以你可以使用此 YAML,将其加载到 Kubernetes 集群中并使某些 pod 运行。

超出范围:支持 docker-compose。我们认为,Kubernetes 是组成 Pod 和编排容器事实上的标准,所以 Kubernetes YAML 可以说是事实上的标准文件格式。- 《Podman 文档》

Podman 的基本使用

我们首先需要创建一个暴露 5432 端口的新 pod:

复制代码
podman pod create --name postgresql -p 5432 -p 9187

我们可以使用 podman pod ps 命令来运行 pod:

复制代码
POD ID NAME STATUS CREATED # OF CONTAINERS INFRA ID
235164dd4137 postgresql Created 26 seconds ago 1 229b2a70b8c4

当你创建一个新的 pod 时,Podman 会自动启动 infra 容器,运行 podman ps 就可以看到它。

在这个 pod 中启动一个 PostgreSQL 容器:

复制代码
podman run -d --pod postgresql -e POSTGRES_PASSWORD=password postgres:latest

如果你没有 postgres:latest 图像,Podman 将自动从 Docker Hub 中获取它 – 这与 Docker CLI 相同。

在 postgresql pod 中启动另一个容器:通过 PostgreSQL Prometheus 导出器

复制代码
podman run -d --pod postgresql -e DATA_SOURCE_NAME="postgresql://postgres:password@localhost:5432/postgres?sslmode=disable" wrouesnel/postgres_exporter

我们可以使用 podman pod top postgresql 命令来查看 pod 中的顶级进程。如果运行 curl localhost:9187/metrics,我们就可以访问 PostgreSQL 指标。

如果我们想在不运行命令式 shell 命令的情况下再次创建相同的设置并将此设置存储为声明性代码,我们可以运行 podman generate kube postgresql > postgresql.yaml,生成一个 Kubernetes 兼容的 pod 定义。如果你按照该链接检查此 YAML 文件,你会看到 Podman 正确地配置了所有端口,而且还导出了所有环境变量,如果你想使用图像默认值,可以清除这些变量。

使用 podman pod rm postgresql –f 来删除 pod。然后,无需再次运行所有命令,运行 podman play kube postgresql.yaml 即可获得相同的结果。你也可以运行 kubectl apply -f postgresql.yaml 并在 Kubernetes 集群上运行这个 PostgreSQL。

警告:如果你碰巧使用的是 Podman 1.4.2,那么此时你会遇到一个“这个 GitHub 问题”中描述的错误。希望在你阅读它时,这个问题已经修复。如果没有修复,请按照问题描述中的步骤修复你的 YAML,或者复制“我的要点”的内容,其中包含了已修复过的定义。

由 Podman 生成的 YAML 文件不应该“按原样”使用,因为 Podman 会转储所有环境变量、securityContext 以及你可以在开发环境中丢弃的其他内容,或者可能在 Kubernetes 集群中具有更好默认值的东西。请将它看做是一个实用的脚手架,而不是最终的结果。

在真正的 Ruby on Rails 应用程序中使用 Podman

在 mkdev,我们用 Podman 完全自动化了开发环境。新开发人员(假设他们运行的是 Linux)可以通过运行单个脚本./script/bootstrap.sh 来运行应用程序。该脚本本身看起来像这样:

复制代码
#!/bin/bash
set +e
if [ "$(podman pod ps | grep mkdev-dev | wc -l)" == "0" ] ; then
echo "> > > Starting PostgreSQL, Redis and Mattermost"
podman play kube pod.yaml
else
echo "Development pod is already running. Re-create it? Y/N"
read input
if [ $input == "Y" ] ; then
podman pod rm mkdev-dev -f
podman play kube pod.yaml
else
echo "Leaving bootstrap process."
exit 0
fi
fi
echo "> > > Waiting for PostgreSQL to start"
until podman exec postgres psql -U postgres -c '\list'
do
echo "> > > > > > PostgreSQL is not ready yet"
sleep 1
done
podman exec -u postgres postgres psql -U postgres -d template1 -c 'create extension hstore;'
echo "> > > Creating development IM database"
until podman exec -u postgres postgres createdb mattermost; do sleep 1; done
echo "> > > Creating test IM database"
until podman exec -u postgres postgres createdb mattermost_test; do sleep 1; done
echo "> > > Creating and seeding the database"
./script/setup.sh
./script/exec.sh 'bundle exec rails db:create db:migrate db:test:prepare'
./script/seed.sh
echo "> > > Attempting to start the app"
./script/run.sh

我们依靠 play kube 功能来创建所有必需的服务,就像你通常运行 docker-compose up 一样。我们的 pod.yaml 定义了 4 个容器 – PostgreSQL、开发和测试 Mattermost 实例以及 Redis 服务器。我们运行 Mattermost 的专用测试实例,因为我们需要在进行集成测试后重置数据库,而且我们不想重置开发实例,因为这无疑是十分低效的。

我们不直接运行 rails 和 rake 命令,而是将它们隐藏在 scripts 文件夹中,类似的设置可参考 GitHub“统管一切的脚本”的文章,了解如何在内部组织这些脚本。

scripts/bootstrap.sh 会调用许多其他脚本,例如填充 (seed) 数据库、下载一些依赖项并触发数据库迁移。其中,开发人员发现有用的一个脚本是 scripts/exec.sh:

复制代码
#!/bin/bash
set -e
echo "Running command in new container ..."
podman run --pod mkdev-dev -it --rm -v $(pwd):/app:Z docker.io/mkdevme/app:dev $1

它会在新的应用程序容器中运行命令,然后删除此容器。这对于运行一次性命令如数据库迁移或 rake 任务等非常有用。

scripts/run.sh 只启动应用程序容器,如果没有启动,它会在内部启动 Rails 服务器:

复制代码
#!/bin/bash
set -e
if [ "$(podman ps | grep app | grep mkdevme | wc -l)" == "0" ] ; then
echo "> > > Starting new application container"
podman run --pod mkdev-dev --name app -v $(pwd):/app:Z -d docker.io/mkdevme/app:dev tail -f /app/log/development.log
fi
n=0
until [ $n -ge 5 ]
do
podman exec app /entrypoint.sh bundle exec rails s -b '0.0.0.0' -P /tmp/mkdev.pid
n=$[$n+1]
echo "Not all components are up. Sleeping for 10 seconds."
sleep 10
done

请注意,在应用程序容器内执行的命令只是一个 tail -f,它生成了一个永不消亡的容器。这样做主要是为了让开发人员能够快速进入容器并在内部调试内容,以防 Rails 拒绝启动,出现新的错误。
你可能不喜欢我们必须编写许多的 bash 脚本。它绝对不如单个 docker-compose.yaml 文件好。不过,这并不算太糟糕。这些脚本只需要编写一次,它们也不太复杂。最终,这更多的是审美的问题,而不是真正的技术缺陷。

通过这套方便的脚本,我们可以涵盖大多数开发任务,例如重启服务器、执行任何命令等等。但有些东西还需要改进,这是避免不了的,但总的来说结果我们非常满意。因为它,开发人员得到了相同的环境,具有相同的依赖版本,相同的 Ruby 版本以及相同的其它东西,他们可以在几秒钟内就(重新)创建整个本地设置。这些好处与 Docker 完全相同,而且我们根本没有用到 Docker。

去 Docker:值得吗?

这个系列文章到此就结束了。希望你学到了有关容器标准和新工具的新知识。但你可能还是会问一个问题:这值得吗?当然我自己也会问这个问题。应用良好和旧的 Docker 技能和实践肯定会更容易,可能我们会得到同样的结果,而且速度更快。

但重点是,最终我们得到了同样的结果。容器映像被构建,容器在开发和测试环境中被使用,我们取得了与 Docker 相同的优势。而且我们并没在功能上做太多的妥协,尽管我们的确在开始时遇到了 Podman 中的某些 bug。而且就在我写这篇文章时,我仍然发现了 Podman 的另一个 bug!

既然 mkdev 提供了一个关于 Podman 和 Buildah 的可行的解决方案,我们就可能会坚持用下去。虽然有些方法可以将应用程序部署在 Podman 生成的容器中,而且该容器是作为 systemd 服务来进行管理的,但我们没有任何理由将容器编排工具引入堆栈。我们的 pod.yaml 可用来为新的 Pull 请求部署评论应用程序,这将进一步改善我们的测试流程。而且,每个 Podman 版本都会推出更多的功能。

正如我在本系列的第一篇文章做过的那样,我鼓励你了解更多容器领域内发生的事情。总有新的东西你需要学习和尝试,也总有非常好的文章供你阅读,我已经在本系列的所有 3 篇文章中提供了它们的链接。

原文链接:

https://mkdev.me/en/posts/dockerless-part-3-moving-development-environment-to-containers-with-podman

评论

发布