# 对传统应用进行容器化改造 一站式学习AI基础知识+核心技术+实操教程+免费工具集 9大课程,边学习边实操,带你通关计算机视觉应用,还有免费工具集和开放平台供所有开发者使用! 本文由 陈计节 翻译自 FP Complete 网站上的文章 CONTAINERIZING A LEGACY APPLICATION: AN OVERVIEW,原作者 Emanuel Borsboom。 以下为译文全文,如需阅读英文原文,请转到文末获取链接: 本文接下来简要介绍什么是容器化,要在 Docker 容器中运行传统应用的缘由,容器化的过程,其间可能遇到的问题,在用容器部署之后的其他步骤等。这将明显减轻部署工作的压力,并让应用朝着零停机部署和横向缩放的方向前进。 注:本文专注在简化应用的部署过程,并不包含需要对应用重新设计的内容,比如高可用和横向扩展。 ## 概念 ### 什么是“传统”应用? 并没有一个特定的定义能够描述所有的传统应用,但它们有一些共同的特性: 使用本地文件系统来持久化存储,数据文件和应用的文件混合在一起。 在同一个服务器上运行很多服务,比如 MySQL 数据库,Redis 服务器,nginx web 服务器,一个 Ruby on Rails 应用,以及一大堆定时任务 使用大杂烩式的脚本和手工流程进行安装和升级(文档也很简陋)。 配置是存储在文件里的,通常散落在多个位置,并与应用的文件混在一起。 进程间的通信是借助本地文件系统进行的(比如在磁盘上放一个文件,另一个进程来读取),而不是TCP/IP。 按照单个服务器上只运行一个应用的示例的方式来设计的。 ### 传统应用的缺点 自动化部署很困难。 如果需要运行应用的多个不同的实例,很难让多个实例在同一个服务器上“共存”。 如果服务器停机,由于需要手工流程所以需要较长的时间来恢复。 部署新版本的过程基本是手动的,或者大部分是手动的,难以回滚。 很有可能测试环境与生产环境有较大差异,导致一些生产环境问题不能在测试期间发现。 很难通过增加新的实例来进行横向扩展。 ### 什么是容器化? 将应用“容器化”的过程,就是让应用能够运行在 Docker 容器或类似技术中,它们能将操作系统环境和应用封装在一起(完整的系统镜像)。由于容器能给应用提供近似于完整系统的环境,这就为在不修改,或者少量修改应用的情况下,对应用的部署进行现代化改造提供了一种思路。这也是应用的架构持续能保持“云友好”的基础。 ## 容器化的好处 部署容易多了:使用新的容器镜像直接替换整个老版本。 自动化部署也相对容易,甚至可以完全由 CI(Continuous Integration, 持续集成)来驱动。 部署失败时的回滚只要切换到之前的镜像。 应用升级非常容易,因为现在没有可能出错的“中间步骤”了(不管它是否影响整个部署过程的成功)。 相同的容器镜像可以在不同的环境中充分测试,再直接部署到生产环境。这可以确保测试态与生产态的产品是完全一致的。 系统更容易从宕机中恢复,因为可以迅速在新硬件资源上启动装有这个应用的新容器,并附加到同一数据源上。 开发人员能在本地以容器的形式,在更逼真的环境里测试新功能。 硬件资源的利用更高效,在单一主机上现在可以运行多个容器应用,而以前不能。 容器化是支持零停机升级、金丝雀部署、高可用和横向扩展的坚实基础。 ## 容器化之外的选择 用 Puppet 和 Chef 之类的配置管理工具,能解决一部分的“传统”问题,比如环境一致性等。但它们不能支持“原子”部署,以及对应用+环境的完整回滚。而一种无法方便回滚的部署方案,仍然会在部署中途充满风险。 虚拟机镜像是能实现部分上述能力的另一种方法,而且在有些情形中,相对于容器,使用完整的虚机进行“原子地”部署会更合适。但使用虚机的主要问题是,它对硬件的利用率更低效。因为虚机需要一些独占的资源(CPU、内存和磁盘等),而容器之间可以共享主机的资源。 ## 如何容器化 ### 一、准备工作 #### 列出存储数据的文件系统位置 由于部署新版本应用是通过替换 Docker 镜像实现的,所以任何持久化的数据都应该存储在容器之外。如果运气不错的话,可能遇到应用已经将所有数据都写入了特定位置,不过多数传统应用常将它们的数据往磁盘上到处乱写,还有可能与应用本身的文件混在一起。Docker 的可加载存储卷(volume)让主机的文件系统能暴露给容器用作特定路径,这样数据可以在容器之间留存。所以,我们无论是哪种情况,我们都需要列出用于存储数据的位置。 现在你可以考虑考虑让应用里所有输出的数据写入到文件系统的同一目录去了,这样能明显简化容器化版本的部署工作。不过,如果修改应用难以达成,这也并不是必须的。 #### 找出会随部署环境变化的配置数据 为了确保一致性,同一个镜像要在多套环境中使用(比如,测试和生产),因此必须要列出所有在不同环境中会变化的配置值,在启动容器的时刻再设置值。容器中的程序到时候可以从环境变量,或者从配置文件中获取这些配置的值。 你可以现在就考虑修改应用并支持从环境变量中读取配置,以便简化容器化的过程。同样的,如果不好修改应用,这也是不一定是必要的。 #### 找出容易移出去的服务 在同一机器上,我们的应用可能要依赖一些其他服务,它们如果独立性比较高、使用 TCP/IP 通信,就很容易能移出去。举例来说,如果在同一机器上运行 MySQL 或 PostgreSQL 数据库,或者类似 Redis 的缓存,那就容易移出去了。可能同时还需要调整配置,才能支持指定机器名(hostname)和端口(port)而不是直接认为应用运行在 localhost。 ### 二、创建容器镜像 #### 创建用于安装应用的 Dockerfile 如果已经有基于脚本或者 Chef、Puppet 之类的配置管理工具的自动化安装能力,那这个过程就很简单了。挑选一个喜欢的系统镜像、安装所有依赖,然后运行自动化脚本就行了。 如果目前的安装过程是手动的,就需要写一些脚本了。不过,由于镜像的状态是已知的,在这儿编写脚本要比基于可能存在不一致性的原生系统来的容易。 如果提前找出了要移出去的服务,那么在脚本里就不应该安装它们了。 下面是一个简单的示例 Dockerfile: ``` # 基于官方 Ubuntu 16.04 Docker 镜像 FROM ubuntu:16.04 # 安装所依赖的 Ubuntu 软件包 RUN apt-get install -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # 将应用的文件复制到镜像里 ADD . /app # 运行安装脚本 RUN /app/setup.sh # 切换到应用的目录 WORKDIR /app # 指定应用的启动脚本 COMMAND /app/start.sh ``` #### 制作用于配置的启动脚本 如果应用已经在使用环境变量中读取配置值了,那这一步可以跳过了。如果要从文件里读取特定环境相关的配置值,那启动脚本就要能从环境变量里读取配置值,并将这些值更新到配置文件中去。 这里有一个启动脚本的例子: ``` #!/usr/bin/env bash set -e # 把环境变量 $MYAPPCONFIG 的值添加到配置文件中 cat >>/app/config.txt <