Dockerless 系列文章(二):如何在没有 Docker 和 Dockerfile 的情况下为 Rails 应用程序构建容器镜像

2019 年 8 月 27 日

Dockerless 系列文章(二):如何在没有 Docker 和 Dockerfile 的情况下为 Rails 应用程序构建容器镜像

在撰写本文时mkdev没有在生产环境中运行容器。下面构建的镜像仅用于开发、测试和CI系统,并不在产品服务器上运行。一旦mkdev决定在生产环境中使用容器,容器镜像的内容和设置也会变地更适合Pod环境。在你阅读这篇文章的时候请记住这一点。


前一篇文章中,我们讨论了要尝试 Dockerless 的原因。我们决定通过使用两个新工具来取代 Docker:Buildah 和 Podman。在本文中,我们将学习什么是 Buildah,以及如何通过它将 Ruby on Rails 应用程序部署到容器中去。


什么是容器镜像?


在学习 Buildah 之前,让我们先通过文章系统管理员容器入门来了解一下容器镜像。通过该篇文章,我们了解到容器镜像是一个包含两个内容的 TAR 文件:


  1. 容器根文件系统。简单地讲,它是一个目录,包含容器中所有的常规目录,如/usr、/home等。

  2. Json文件,它是一个定义了如何运行根文件系统的配置文件。它包括该执行哪些命令,设置哪些环境变量等。


容器镜像的内容按照OCI镜像规范定义,如果你想了解更多关于容器镜像结构的信息,该规范就是最好的选择。这听起来可能很疯狂,但是你的容器镜像并非一定要使用镜像规范,而镜像规范也可以用到其他事情上。


什么是 Buildah?


Buildah是一个容器镜像构建工具,可以生成兼容OCI的镜像。Buildah 作为一个单一的二进制文件发布,由 GO 语言编写。大多数现代 Linux 的发行版本都包含了 Buildah 的软件包,安装只需遵循官方说明即可。


Buildah 只能用来处理镜像。它的工作只是构建容器镜像并将其推送到注册中心,而没有涉及守护进程。Buildah 也不需要根权限来构建镜像。这使得 Buildah 非常适合作为 CI/CD 管道的一部分来使用:你可以轻松地在容器中运行Buildah,而无需授予该容器任何根权限。


对于基于容器的 CI 系统(比如 Gitlab CI 和 Docker 执行器),很多时候只是为了能够构建一个容器镜像,就需要搭建一个完整的 Docker 环境。对于我个人来讲,这有点太繁重了。Buildah 就完全没有必要这样做,因为对它来说事情只有需要做好和完全不应该做的区别。


在Open Shift中的BuildConfigurations场景中,Buildah看起来非常有用。从OpenShift 4.0开始,BuildConfgs将依赖于Buildah而不是Docker,因此不需要共享任何套接字或者在OpenShift平台中拥有特权容器。毫无置疑,这让在最流行的容器平台之一中构建容器镜像的方法变得更加安全和清晰。


Buildah 构建的镜像可以直接由 Docker 使用。它们不是“Buildah 镜像”,而只是“容器镜像”,它们遵循 OCI 规范,这也是 Docker 兼容的格式。那么我们该如何使用 Buildah 构建一个镜像呢?


使用 Buildahfile


开个玩笑,Buildah 并没有使用 Buildahfile 文件。实际上,Buildah 可以只使用 Dockerfiles,从 Docker 到 Buildah 的转变也变得异常简单。


在 mkdev 中,我们使用 Mattermost 作为消息传递平台的核心。重要的是,我们能够在本地运行 Mattermost,从而能够轻松地在 web 应用程序和消息传递系统之间完成开发整合。


尽管 Mattermost 已经提供了官方的 Docker 镜像,但是我们仍然不得不构建自己的镜像,因为我们更喜欢配置的方式,并且也为了更容易地运行 Mattermost 的临时测试实例。同时,我们也想预先安装一些导师们所依赖的插件。于是我们修改了官方的 Dockerfile 文件,并将其提供给 Buildah:


FROM alpine:3.9
# 设置ENV变量ENV PATH="/opt/mattermost/bin:${PATH}"ENV MM_VERSION=5.8.0# 为config设置默认值ENV MMDBCON=localhost:5432 \ MMDBKEY=XXXXXXXXXXXX \ MMSMTPUSERNAME=postfix \ MMSMTPPASSWORD=secrets \ MMSMTPSALT=XXXXXXXXXXXX \ MMGITHUBSECRET=secret \ MMGITHUBHOOK=localhost# 设置Mattermost版本的Build参数ARG PUID=2000ARG PGID=2000# 安装所需要的软件包RUN apk add --no-cache \ ca-certificates \ curl \ jq \ libc6-compat \ libffi-dev \ linux-headers \ mailcap \ netcat-openbsd \ xmlsec-dev \ && rm -rf /tmp/*## 获取MattermostRUN mkdir -p /opt/mattermost/data /opt/mattermost/plugins /opt/mattermost/client/plugins \ && cd /opt \ && curl https://releases.mattermost.com/$MM_VERSION/mattermost-team-$MM_VERSION-linux-amd64.tar.gz | tar -xvz \ && curl -L https://github.com/mattermost/mattermost-plugin-github/releases/download/v0.7.1/github-0.7.1.tar.gz -o /tmp/github.tar.gz \ && cd /opt/mattermost/plugins \ && tar -xvf /tmp/github.tar.gzCOPY files/entrypoint.sh /COPY files/mattermost.json /opt/mattermost/config/config.jsonRUN chmod +x /entrypoint.sh \ && addgroup -g ${PGID} mattermost \ && adduser -D -u ${PUID} -G mattermost -h /mattermost -D mattermost \ && chown -R mattermost:mattermost /opt/mattermost /opt/mattermost/plugins /opt/mattermost/client/pluginsUSER mattermost# 设置entrypoint和commandENTRYPOINT ["/entrypoint.sh"]WORKDIR /opt/mattermostCMD ["mattermost"]# 暴露容器端口8000EXPOSE 8065# 为挂载点声明volumesVOLUME ["/opt/mattermost/data", "/opt/mattermost/logs", "/opt/mattermost/config", "/opt/mattermost/plugins", "/opt/mattermost/client/plugins"]
复制代码


如果它看起来和常规的 Dockerfile 一样,那是因为它就只是一个常规的 Dockerfile。让我们开始运行 Buildah:


Buydahbud-tdocker.io/mkdevme/mattermost:5.8.0.
复制代码


接下来的输出类似于 docker build 命令。生成的镜像将存储在本地,我们可以通过运行 buildah images 命令进行查看。Buildah 一个不错的特点就是镜像都是用户特制的,这意味着只有创建这个镜像的用户能看到并使用它。如果以其他系统用户的身份运行 buildah images,我们将看不到任何内容。这与 Docker 不同,docker images 总是为所有用户列出相同的镜像集。


生成镜像后,可以将其推送到注册中心。Buildah 支持多种传输方式来推送镜像,一些传输方式例如:docker-daemon,如果你仍然在本地运行 Docker,并且希望 Docker 能够看到这个镜像;docker,如果你希望将镜像推送到与 Docker API 兼容的远程注册中心;还有一些其它的非 Docker 传输:oci、容器存储、dir 等等。


如果你选择 Docker hub 作为注册中心的话,你完全可以使用 Buildah 进行镜像推送。通过使用 Buildah,我们可以不用考虑 Docker 镜像的一些条款。这就像操作一个 Git 存储库,我们可以把它推送到 GitHub、GitLab 或 BitBucket,同样,我们也可以将容器镜像推送到 DockerHub、Quay、AWSECR 等不同的注册中心。


查看镜像信息


Buildah 支持的其中一个传输方式就是 dir。当你将镜像推送到 dir(文件系统上的一个目录)时,Buildah 将在那里存储 tarball 的层,配置你的镜像以及 JSON 清单文件。这只对调试有帮助,尤其是查看镜像的内部结构的时候。


创建一些目录并运行 buildah push IMAGE dir:/$(pwd),我并期望你真的去构建一个 Mattermost 镜像,你可以构建其他任何的镜像。如果你什么也没有并且也不想构建,那么你可以通过 buildah pull 从 docker hub 中心拉取任意一个镜像。


一旦拉取完成,你将看到一个名为 96c6e3522e18ff696e9c40984a8467ee15c8cf80c2d32ffc184e79cdfd4070f6 的文件,这实际上是一个 tarball。你可以解压缩这个文件到你选择的任何地方,并查看这个镜像层中的所有文件。你还会看到一个 imagemanifest.json 文件,如果是 Mattermost 镜像的话,它看起来像这样:


{  "schemaVersion": 2,  "config": {    "mediaType": "application/vnd.oci.image.config.v1+json",    "digest": "sha256:57ea4e4c7399849779aa80c7f2dd3ce4693a139fff2bd3078f87116948d1991b",    "size": 1262  },  "layers": [    {      "mediaType": "application/vnd.oci.image.layer.v1.tar",      "digest": "sha256:6bb94ea9af200b01ff2f9dc8ae76e36740961e9a65b6b23f7d918c21129b8775",      "size": 2832039    },    {      "mediaType": "application/vnd.oci.image.layer.v1.tar",      "digest": "sha256:96c6e3522e18ff696e9c40984a8467ee15c8cf80c2d32ffc184e79cdfd4070f6",      "size": 162162411    }  ]}
复制代码


镜像清单由OCI规范描述。如果仔细查看上面的示例,你就会发现它定义了两个层(vnd.oci.image.layer.v1.tar)和一个配置文件(vnd.oci.image.config.v1+json)。我们可以看到这个配置有一个摘要 57ea4e4c7399849779aa80c7f2dd3ce4693a139fff2bd3078f87116948d1991b。我们也有这个文件,尽管它看起来像镜像层文件,但它实际上是镜像的配置文件。


这可能有点令人困惑,但请记住,这种结构是为其他软件存储和处理而创建的,而不是让我们通过人眼来阅读的。如果你需要快速查找镜像中存储配置的文件,请先查看 manifest.json:


{  "created": "2019-05-12T16:13:28.951120907Z",  "architecture": "amd64",  "os": "linux",  "config": {    "User": "mattermost",    "ExposedPorts": {      "8065/tcp": {}    },    "Env": [      "PATH=/opt/mattermost/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",      "MM_VERSION=5.8.0",      "MMDBCON=localhost:5432",      "MMDBKEY=XXXXXXXXXX",      "MMSMTPUSERNAME=postfix",      "MMSMTPPASSWORD=secrets",      "MMSMTPSALT=XXXXXXXXXX",      "MMGITHUBSECRET=secret",      "MMGITHUBHOOK=localhost"    ],    "Entrypoint": [      "/entrypoint.sh"    ],    "Cmd": [      "mattermost"    ],    "Volumes": {      "/opt/mattermost/client/plugins": {},      "/opt/mattermost/config": {},      "/opt/mattermost/data": {},      "/opt/mattermost/logs": {},      "/opt/mattermost/plugins": {}    },    "WorkingDir": "/opt/mattermost"  },  "rootfs": {    "type": "layers",    "diff_ids": [      "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81",      "sha256:462e838baed1292fb825d078667b126433674cdc18c1ba9232e2fb8361fc8ac2"    ]  },  "history": [    {      "created": "2019-05-11T00:07:03.358250803Z",      "created_by": "/bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6ae03397b99ea77f2e9ee901c5c59e59f76f93adbb4035913 in / "    },    {      "created": "2019-05-11T00:07:03.510395965Z",      "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",      "empty_layer": true    },    {      "created": "2019-05-12T16:13:28.951120907Z"    }  ]}
复制代码


因此,整个容器镜像就只有一堆 tarball 和 json 文件!


你说 Dockerless,但你仍然依赖 Dockerfile!


Buildah 的创建者有意不引入新的 DSL 来定义容器镜像。他提供了两种定义镜像的方法:一个 Dockerfile 或者一系列 Buildah 命令。我们很快就会学到第二种方法,但我必须警告你,我不认为 Dockerfiles 会很快消失。除了名字本身,可能没有什么东西能与他们相提并论。想象一下,投身到 Dockerless,却发现自己仍然在写 Dockerfiles!


我希望它们被称为“Containerfiles”或者“Imagefiles”。这样社区就不会那么尴尬了。但是到目前为止,惯例仍然是将这个文件命名为 Dockerfile,我们只能忍受这个现状。


通过 Buildah 直接构建镜像


通过 Buildah 构建镜像的第二种方法是直接使用 Buildah 命令。Buildah 构建镜像是从一个基本镜像创建一个新的容器,然后在这个容器中运行命令。在运行完所有命令之后,你可以提交这个容器为镜像文件。让我们以这种方式构建一个镜像,然后讨论是否以及何时这样做才能比编写一个 Dockerfile 更方便。


首先,我们需要从现有的镜像开始创建一个新的容器:


buildah from centos:7


如果镜像还不存在,它会从注册中心被提取出来,就像使用 Docker 一样。buildah 命令行将返回启动容器的名称,通常是”IMAGEname-working-container“,在我们的示例中是“centos-working-container”。我们需要记住它,并在将来的所有命令中都使用相同的名称。


我们可以这个容器中运行 buildah run 命令:


buildah run centos-working-container – yum install unzip -y


我们还可以使用 buildah config 命令为未来的镜像配置各种 OCI 兼容的选项,例如环境变量:


buildah config -e ENVIRONMENT=test centos-working-container


我们还可以将完整的容器文件系统挂载到构建服务器中,并使用主机上的工具直接操作它。当我们不希望仅仅为了执行构建操作而在镜像中安装某些工具时,这非常有用。但要记住,在这种情况下,我们需要确保所有希望构建你镜像的机器需要安装这些工具,这也有点破坏了构建脚本的可移植性。


buildah mount centos-working-container


相应的,Buildah 会提供挂载文件系统的位置,例如/home/fodoj/.local/share/containers/storage/overlay/DIGEST/merged。作为测试,我们可以在那里创建一个文件:


touch hello-from-host /home/fodoj/.local/share/containers/storage/overlay/DIGEST/merged/home/


一旦我们对镜像感到满意,我们就可以提交了:


buildah mount centos-working-container


同时移除工作容器:


buildah rm centos-working-container


请注意,尽管 Buildah 的确运行了容器,但这除了对构建镜像之外别无用处。Buildah 不是容器引擎的替代品,它只提供一些原语来调试构建镜像的过程!


Podman 可以看到 Buildah 构建的镜像,这将是下一篇文章的主题。现在,如果你想验证文件 hello-from-host 是否真的存在,可以运行以下命令:


image=$(buildah from my-first-buildah-image)ls $(buildah mount $image)/home$> hello-from-host
复制代码


这将创建另外一个工作容器。挂载该容器并显示/home 目录的内容。如果你想使用 Buildah 而不 Dockerfile 来构建镜像,我们其实也是这么做的。你应该编写一个 shell 脚本来调用所有的命令,提交镜像并删除工作容器,而不是使用 Dockerfile。这就是 mkdev 的“Buildahfile”文件,实际上只是一个 shell 脚本:


#!/bin/bashset -xmkdev=$(buildah from centos:7)buildah run "$mkdev" -- curl -L http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -o epel-release-latest-7.noarch.rpmbuildah run "$mkdev" -- curl -L https://downloads.wkhtmltopdf.org/0.12/0.12.5/wkhtmltox-0.12.5-1.centos7.x86_64.rpm -o wkhtmltopdf.rpmbuildah run "$mkdev" -- curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip"buildah run "$mkdev" -- rpm -ivh epel-release-latest-7.noarch.rpmbuildah run "$mkdev" -- yum install centos-release-scl -ybuildah run "$mkdev" -- yum install unzip postgresql-libs postgresql-devel ImageMagick \                       autoconf bison flex gcc gcc-c++ gettext kernel-devel make m4 ncurses-devel patch \                       rh-ruby25 rh-ruby25-ruby-devel rh-ruby25-rubygem-bundler rh-ruby25-rubygem-rake \                       rh-postgresql96-postgresql openssl-devel libyaml-devel libffi-devel readline-devel zlib-devel \                       gdbm-devel ncurses-devel gcc72-c++ \                       python-devel git cmake python2-pip chromium chromedriver which -ybuildah run "$mkdev" -- pip install ansible boto3 botocorebuildah run "$mkdev" -- yum install wkhtmltopdf.rpm -ybuildah run "$mkdev" -- ln -s /usr/local/bin/wkhtmltopdf /bin/wkhtmltopdfbuildah run "$mkdev" -- unzip awscli-bundle.zipbuildah run "$mkdev" -- ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/awsbuildah run "$mkdev" -- yum clean all && rm -rf /var/cache/yumgit archive -o app.tar.gz --format=tar.gz HEADbuildah add "$mkdev" app.tar.gz /app/buildah add "$mkdev" infra/app/build/entrypoint.sh /entrypoint.shbuildah config --workingdir /app "$mkdev"buildah run "$mkdev" -- scl enable rh-ruby25 "bundle install"rm app.tar.gzbuildah config --port 3000 "$mkdev"buildah config --entrypoint '[ "/entrypoint.sh" ]' "$mkdev"buildah run "$mkdev" -- chmod +x /entrypoint.shbuildah config --cmd "bundle exec rails s -b '0.0.0.0' -P /tmp/mkdev.pid" "$mkdev"buildah config --env LC_ALL="en_US.UTF-8" "$mkdev"buildah run "$mkdev" -- rm -rf /app/buildah commit "$mkdev" "docker.io/mkdevme/app:dev"buildah rm "$mkdev"
复制代码


如果你已经构建过一个很好的容器镜像,那么这个脚本可能对你来说可能非常愚蠢。让我来解释一下脚本里发生的一些事情:


  1. 我们使用Centos 7作为基础镜像,因为我们在生产环境中使用了Centos7。即使我们还没有在生产环境中运行容器,也应该尽可能地使开发环境接近生产环境。

  2. 我们确实安装了大量的软件包,包括AWS CLI、Chromium、Software Collections等等。我们之所以这样做,是因为我们在开发环境和CI系统中使用了新构建的镜像。这两个位置都需要额外的工具来运行集成测试(Chromium)或执行一些打包和部署任务(AWS CLI和Ansible)。Software Collections在我们的生产环境中使用,在所有其他环境中使用相同的Ruby版本也很重要。

  3. 最后我们删除了应用程序本身的代码。对于这个用例,我们实际上并不需要将代码放在镜像中。在开发环境和CI系统中,我们都需要最新版本的代码,而不是嵌入到镜像中的东西。


我们将这个脚本存储在应用程序 repo 中,就像我们将 Dockerfile 保存在那里一样。一旦我们决定要在生产环境中的容器中运行 mkdev,我们就可以根据环境做来修改这个脚本,从而完成不同的事情。


只有当你的构建服务器能够运行 shell 脚本时,你才可以使用此方法。这不是一个问题,例如 Windows 有 WSL。你的主机系统不一定是基于 Linux 的,只要它能够在内部运行某种 Linux!MacOS 用户可以有一天在没有额外的 Linux 虚拟机的情况下会使用它吗?谁知道呢,让我们期待 Buildah 开发者正在实现这个功能吧。


Buildah 内部是如何工作的呢?


Podman 和 Buildah 的内部工作方式非常相似。它们都使用 Linux 内核特性,特别是用户命名空间和网络命名空间,以便可以在没有任何 root 权限的情况下运行容器。我不会在这篇文章中讨论这个问题,但是如果你已经等不及了,那就开始阅读下面的资源吧:



小结


Buildah 是一个伟大的工具,它不仅适用于本地开发,而且也适用于构建容器镜像相关的所有自动化实现。当然,这也不是唯一可用的选择,Google 的 Kaniko 就是另一个选项,尽管 Kaniko 更关注 Kubernetes 环境。


现在我们已经有了一个合适的镜像,是时候运行它了。在下一篇文章中,我将向你展示如何使用 Podman 完全自动化 Ruby on Rails 应用程序的本地开发环境。我们将学习如何使用 Podman 的 Kube YAML 特性来描述一个 kubernetes 兼容的 YAML 定义中的所有服务,如何在容器中运行 Rails 应用程序,以及如何在容器中测试 Rails 应用程序。当我们开始为集成测试创建 Mattermost 临时实例时,容器和 Podman 将会变得尤其方便。


原文链接:


https://mkdev.me/en/posts/dockerless-part-2-how-to-build-container-image-for-rails-application-without-docker-and-dockerfile


2019 年 8 月 27 日 11:415141

评论

发布
暂无评论
发现更多内容

三周学习总结

水浴清风

架构是训练营-第三周总结

第七周作业

极客大学架构师训练营

架构师 01 期,第七周课后作业

子文

架构师训练营-第七周

袭望

架构师训练营第三周心得

小兵

极客时间 - 架构师一期 - 第七周作业

_

极客大学架构师训练营 第七周作业

Week_07 总结

golangboy

极客大学架构师训练营

训练营第七周作业2

仲夏

第8周作业

静海

性能测试 课后作业

ABS

架构师训练营 Week03 作业-手写单例模式

Calvin

架构师训练营 - 第 7 周课后作业 -性能压测

树森

性能优化(1)

wing

极客大学架构师训练营

成为架构师 - 架构师训练营第 03 周

陈永龙Vincent

三周 作业

水浴清风

手写单例

落朽

第七周作业(作业一)

Geek_83908e

极客大学架构师训练营

性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化,为什么?

知行合一

架构训练营第三周作业

小兵

抽象工厂模式

猴子胖胖

golang 设计模式

第 3 周 代码重构作业

心在那片海

一期二班-吴水金-第五课总结

吴水金

性能压测的时候,随着并发压力的增加,系统响应时间和吞吐量如何变化

Jacky.Chen

架构师训练营第二期 Week 3 总结

bigxiang

极客大学架构师训练营

单例模式

猴子胖胖

golang 设计模式

Chrome浏览器引擎 Blink & V8

曲迪

Java chrome 前端 blink V8

第三周作业

皮蛋

架构师

架构师训练营第 1 期 week7 总结

张建亮

极客大学架构师训练营

服务器性能监控神器nmon使用介绍

MySQL从删库到跑路

Linux nmon 性能监控

工厂方法模式

猴子胖胖

golang 设计模式

Dockerless 系列文章(二):如何在没有 Docker 和 Dockerfile 的情况下为 Rails 应用程序构建容器镜像-InfoQ