为 Stackato 制作 Docker 应用程序

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在 Stackato 3.6 版本中,我们引入了将 Docker 镜像部署到 Stackato 的 功能。这为 Cloud Foundry 和 Heroku 使用的由 buildpacks 驱动的常用应用程序部署方法提供了替代方法。

我们之前在这里没有介绍的是如何创建将在 Stackato 中运行并利用提供的服务的“Docker 化”应用程序。我们将在这里通过我们最喜欢的 Stackato 应用程序示例之一并将其转换为 Docker 映像来对此进行研究。

从一个新的“堆栈”开始

Docker 的一大优点是您可以运行不同版本的 Linux,而不必过多担心主机系统的操作系统。 Stackato 基于 Ubuntu 12.04 LTS,用于暂存应用程序的 stackato/stack-alsek 基础镜像也是如此,但没有什么能阻止我们运行基于不同发行版的 Docker 镜像。

我一直在尝试使用 Debian、CentOS 和 Alpine Linux 映像,但为此我们将从 opensuse 基本映像开始。

使用 Docker,您可以将系统配置划分为单独的 Docker 层,方法是将配置步骤分解为单个 Dockerfile 中的单独 RUN 指令,或者创建单独的图像并将它们与每个单独 Dockerfile 顶部的 FROM 指令链接在一起。在这个例子中,我们将两者都做一点。

更新和安装操作系统包

我有点坚持让我的操作系统保持最新,所以我在 Dockerfile 中做的第一件事就是使用操作系统的包管理器运行更新。在 openSUSE 上,这是通过 zypper 完成的。经过一些实验( docker run -i 选项对此至关重要)我想出了一个如下所示的 Dockerfile:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

openSUSE 基础镜像是有意最小化的(比大多数镜像都小,但这不一定是坏事)。如您所见,我冒昧地安装了:

  • python:对于 Python 网络应用程序非常重要
  • python-pip:用于安装我的 Python 依赖项
  • ca-certificates-mozilla:pip 需要使用 SSL
  • vim:因为我们在试验时需要一个编辑器
  • git:用于即时克隆 repos 或从 git 源进行 pip 安装
  • curl:用于一般的 HTTP 测试
  • wget:用于杂项获取可能有用的东西

安装这些工具后,我就拥有了编写 python 应用程序安装脚本或在本地运行的 Docker 容器中以交互方式进行尝试所需的大部分内容。它还为我提供了一些工具,我可能需要这些工具来对在 Stackato 中运行的应用程序进行故障排除(通过 stackato ssh 会话)。

请注意,我在 RUN 语句中将几个命令链接在一起。每次我在 Dockerfile 中使用 RUN 命令时,我都会得到另一个图像层,并且在当前版本的 Docker 中有 127 层的硬限制 (AUFS 默认限制)。将相关命令捆绑在一起可以减少我们创建的层数,并有助于确保我们不会达到上限。

如果我们有很多余量,那么在单独的 RUN 命令中进行更新和包安装步骤可能会有一些价值,以利用 Docker 在重建时使用缓存层的能力,这可以大大加快构建速度。肯定有不止一种方法可以做到这一点,但有关堆叠 Docker 层的最佳方法的指南 在其他地方 有广泛介绍。

让我们构建我们目前所拥有的,并可以继续使用 一个打包我们应用程序的 Dockerfile。

构建、测试和推送 Docker 镜像

我决定将我的新“base+1”图像层命名为“troytop/opensuse-python”。我通过在包含我的 Dockerfile 的空目录中运行此命令来构建它:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

我在这种情况下使用 --no-cache 因为我想确保每次构建时 zypper update 都运行,而不是依赖以前构建的缓存图像层。构建完成后,我通常会在交互式 shell 中环顾四周,以验证一切是否按预期工作:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

如果我要与其他人分享这个,我应该将它推送到 Docker Hub 或其他一些注册服务器:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget


下一层:bottle-currency-suse

Stackato-Apps/bottle-currency 应用程序 是我最喜欢的 Stackato 演示应用程序之一。 Billy Tat 已经基于 ubuntu:12.04 制作了一个 Docker 版本,所以我可以调整 他创建的 Dockerfile 以与我的 opensuse-python 图像一起使用。

这是一个细分:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

这里没有什么奇怪的。使用不同的容器作为起点并将我自己标识为维护者。


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

在这里,我创建了一个名为“www”的新用户并设置了活动用户和工作目录。如果我们不需要*必须*以 root 身份运行我们的应用程序,我们就不应该这样做。很多 Docker Web 应用程序都不会为这一步操心(看着你比利!),在 Docker 的上下文中这可能有点偏执,但我认为我们应该在这里应用相同的最佳实践,就像我们运行虚拟机或裸机上的服务器。


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

我的 Dockerfile 将存在于 bottle-currency 源的基目录中,因此该命令将递归地将当前目录的内容复制到上面定义的 WORKDIR 中。


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

这将安装应用程序所需的模块。对于暂存应用程序,这部分将由 buildpack 处理。


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

如果我们的应用程序没有看到 $PORT 环境变量,它默认在端口 8080 上运行,因此我们使用此命令在容器中公开该端口,以便将其路由到 DEA 主机上的外部端口。 Stackato 路由器将能够通过将外部请求转发到此主机名:端口来路由到应用程序。

在 Stackato 中运行暂存应用程序时,应用程序代码或 Procfile 必须引用 Stackato 在容器中提供的 $PORT 变量。使用 Docker Apps,我们只需确保只有一个端口暴露。


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

最后,我们指定启动 web 进程的命令。在 buildpack 世界中,这将在 Procfile 中指定。

准备好 Dockerfile 后,我们重复对 opensuse-python 映像执行的相同步骤来构建、测试和推送映像。测试步骤略有不同,因为我们可能想用浏览器查看它。为此,我们将容器的端口 8080 转发到主机上的端口 8000:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

假设我们在本地主机上执行所有这些操作,使用浏览器连接到 http://localhost:8000 将显示正在运行的 Web 应用程序。

它现在还行不通。如果您自己尝试这样做,您会看到“联系服务器时发生错误”,并且转换应用程序实际上不会运行。那是因为该应用正在寻找 Redis 数据服务。有几种方法可以将 Docker 容器链接到其他提供数据库的 Docker 容器,但是一旦它部署到 Stackato,我们就可以处理这个问题。现在,我们将把新镜像推送到 Docker Hub:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget


附加到服务

bottle-currency 应用程序查找由 REDIS_URL 环境变量公开的 Redis 数据服务。 Stackato 将环境变量 (VCAP_SERVICES、_URL 等)注入 Docker 容器,为已绑定到应用程序的任何服务提供连接信息。这是 12-factor 的做事方式 ,这也是 Cloud Foundry 中分阶段应用程序一直工作的方式。

因此,要将我们的应用程序部署到 Stackato:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

这从 Hub 获取 docker 镜像并部署它(还没有数据库)。接下来,我们创建一个新的 Redis 数据服务并将其绑定到应用程序:


 FROM opensuse:13.2
  MAINTAINER Troy Topnik <troyt@activestate.com>

RUN
zypper -n up
&& zypper -n install python python-pip ca-certificates-mozilla
vim git curl wget

此步骤将重新创建容器,其中包含必要的 REDIS_URL 变量。

使用您自己的代码,您可以找到多种方法来解析这些环境变量并连接到数据服务,但是如果您尝试部署其他人*已经打包为 Docker 映像的东西,您可能必须找到创造性的方法将 Stackato 提供的凭据重新格式化为应用程序可以理解的变量。这样做的方法是在第三方图像之上添加另一层,该层解析 Stackato 提供的环境变量,以应用程序可以理解的方式重新格式化它们,并从原始图像中重申 CMD / ENTRYPOINT 行。

但是我知道什么...

我可能做错了。我阅读了更多关于优化 Docker 镜像、管理镜像层中的更改以及编写好的 Dockerfile 的内容;我越发意识到我有很多东西要学。为容器打包应用程序的最佳实践仍在不断涌现,但我已经可以看到 Docker 提供的声明式、线性、分隔的打包和配置方法的巨大好处。

在平台即服务的上下文中运行 Docker 映像是全新的领域,但它已准备好进行探索。