开发一个能够提供机器学习模型预测服务的 Flask 应用程序是一项重要的成就。然而,在不同计算机或环境之间迁移此类应用程序时,一个常见的问题就会出现。一个应用程序可能在开发笔记本上完美运行,但当同事尝试运行它,或者当它被部署到服务器上时,就可能出现问题。这通常是由于一些微小差异造成的:不同的操作系统版本、不同的已安装库版本(如 scikit-learn 或 Flask),或者缺少系统工具。这种情况经常导致令人沮丧的“在我的机器上能跑!”的困境。容器化为这一挑战提供了一个强大的解决方案。你可以将其理解为一种方式,它将你的应用程序代码与运行所需的所有东西捆绑在一起:库、系统工具、运行时环境(例如特定的 Python 版本)以及配置设置。这个捆绑包被称为容器镜像。当你运行这个镜像时,你就会得到一个运行中的容器,它是一个标准化、隔离的应用程序运行环境。货运集装箱的比喻理解软件容器的一个有效方式是借助物理货运集装箱的比喻。在标准化货运集装箱出现之前,货物运输非常复杂。不同形状和大小的物品必须单独装载到船只、火车或卡车上,这使得过程缓慢、效率低下且容易损坏。标准化货运集装箱改变了一切。货物被装入这些统一的金属箱子中。然后,这些箱子可以使用标准设备(起重机、船只、火车、卡车)轻松地在任何地方移动,无论里面装的是什么。里面的内容是隔离且受保护的。软件容器对应用程序也起到了类似的作用:标准单元: 你的应用程序及其依赖项被打包成标准容器镜像格式。隔离性: 容器内的应用程序在其自己的隔离空间中运行,与主机系统和其他容器分开。它对文件系统、进程和网络有自己的视图。可移植性: 就像货运集装箱可以由任何标准船只或火车运输一样,软件容器镜像可以在任何安装了兼容容器引擎的机器上运行,无论底层操作系统细节或主机上安装的软件如何。容器与虚拟机的区别你可能听说过虚拟机(VMs)。虚拟机也提供隔离的环境,但它们的工作方式不同,通常更重(占用资源更多)。虚拟机(VMs): 虚拟机模拟整个物理计算机,包括硬件。每个虚拟机都需要一个完整的操作系统副本(“客户操作系统”)运行在管理程序之上,而管理程序本身则运行在主机操作系统的(“宿主操作系统”)之上。这使得例如在 Windows 主机上运行 Linux 操作系统成为可能,但它消耗大量资源(CPU、内存、磁盘空间),因为你运行的是多个完整的操作系统。容器: 容器则虚拟化操作系统本身。它们共享主机操作系统的内核,但将应用程序代码、库和依赖项打包到隔离的用户空间中。这意味着每个容器不需要单独的客户操作系统,这使得它们比虚拟机更轻量、启动更快、资源效率更高。下面是说明它们之间区别的示意图:digraph G { rankdir=TB; splines=ortho; node [shape=box, style="filled", fontname="sans-serif", fontsize=10]; subgraph cluster_vm { label = "虚拟机方式"; style=filled; color="#e9ecef"; node [fillcolor="#a5d8ff"]; edge [color="#495057"]; vm_app1 [label="应用 A\n(二进制/库)", fillcolor="#96f2d7"]; vm_app2 [label="应用 B\n(二进制/库)", fillcolor="#96f2d7"]; vm_os1 [label="客户操作系统 1"]; vm_os2 [label="客户操作系统 2"]; vm_hypervisor [label="管理程序", fillcolor="#ffec99"]; vm_host_os [label="宿主操作系统"]; vm_infra [label="基础设施\n(硬件)", fillcolor="#ced4da"]; vm_app1 -> vm_os1; vm_app2 -> vm_os2; vm_os1 -> vm_hypervisor; vm_os2 -> vm_hypervisor; vm_hypervisor -> vm_host_os; vm_host_os -> vm_infra; } subgraph cluster_container { label = "容器方式"; style=filled; color="#e9ecef"; node [fillcolor="#a5d8ff"]; edge [color="#495057"]; cont_app1 [label="应用 A\n(二进制/库)", fillcolor="#96f2d7"]; cont_app2 [label="应用 B\n(二进制/库)", fillcolor="#96f2d7"]; cont_engine [label="容器引擎\n(例如 Docker)", fillcolor="#ffec99"]; cont_host_os [label="宿主操作系统 (内核共享)"]; cont_infra [label="基础设施\n(硬件)", fillcolor="#ced4da"]; cont_app1 -> cont_engine; cont_app2 -> cont_engine; cont_engine -> cont_host_os; cont_host_os -> cont_infra; } }虚拟机与容器架构的对比。容器共享宿主操作系统内核,使其更轻量。使用容器化的优点将应用程序打包成容器具有多项优点,尤其是在部署方面:一致性: 主要优点!容器确保你的应用程序在完全相同的环境中运行,具有相同的库版本和配置,无论是在你的笔记本、测试服务器还是生产环境中。这消除了“在我的机器上能跑”的问题。隔离性: 运行在独立容器中的应用程序彼此之间以及与主机系统隔离。依赖冲突(例如,两个应用程序需要同一库的不同版本)得以避免,因为每个容器都有自己的一套依赖项。可移植性: 在一台机器上构建的容器镜像可以在任何安装了兼容容器引擎的机器上运行。这简化了在开发、测试和生产环境之间移动应用程序的过程,甚至不同的云服务提供商之间。效率: 因为容器共享宿主操作系统内核且不需要完整的客户操作系统,与虚拟机相比,它们使用更少的资源(CPU、内存、磁盘空间)。它们也几乎立即启动。可伸缩性: 需要为你的预测服务处理更多流量吗?通常可以很容易地从同一个镜像启动多个相同的容器来分散负载。为什么将你的机器学习服务容器化?对于我们构建的 Flask 预测服务,容器化非常有益。它确保你使用的特定 Python 版本、Flask 框架、scikit-learn、joblib(或 pickle)、numpy,以及模型所依赖的任何其他库,连同你保存的模型文件(.joblib 或 .pkl)和预处理步骤,都被打包在一起。当你部署这个容器时,你可以确信容器内的预测环境正是你所设想的,这使得你的部署过程更加可靠和可复现。在接下来的章节中,我们将专门了解 Docker,一个非常流行的用于创建和管理容器的平台,并学习如何将我们的 Flask 应用程序打包到 Docker 容器中。