常见问题 网站建设,网站区域名怎么注册吗,精通网站建设电子档,在网站上做承诺本文由 陈计节 翻译自 FP Complete 网站上的文章 CONTAINERIZING A LEGACY APPLICATION: AN OVERVIEW#xff0c;原作者 Emanuel Borsboom。以下为译文全文#xff0c;如需阅读英文原文#xff0c;请转到文末获取链接#xff1a;本文接下来简要介绍什么是容器化#xff0c… 本文由 陈计节 翻译自 FP Complete 网站上的文章 CONTAINERIZING A LEGACY APPLICATION: AN OVERVIEW原作者 Emanuel Borsboom。以下为译文全文如需阅读英文原文请转到文末获取链接本文接下来简要介绍什么是容器化要在 Docker 容器中运行传统应用的缘由容器化的过程其间可能遇到的问题在用容器部署之后的其他步骤等。这将明显减轻部署工作的压力并让应用朝着零停机部署和横向缩放的方向前进。注本文专注在简化应用的部署过程并不包含需要对应用重新设计的内容比如高可用和横向扩展。概念什么是“传统”应用并没有一个特定的定义能够描述所有的传统应用但它们有一些共同的特性使用本地文件系统来持久化存储数据文件和应用的文件混合在一起。在同一个服务器上运行很多服务比如 MySQL 数据库Redis 服务器nginx web 服务器一个 Ruby on Rails 应用以及一大堆定时任务使用大杂烩式的脚本和手工流程进行安装和升级文档也很简陋。配置是存储在文件里的通常散落在多个位置并与应用的文件混在一起。进程间的通信是借助本地文件系统进行的比如在磁盘上放一个文件另一个进程来读取而不是TCP/IP。按照单个服务器上只运行一个应用的示例的方式来设计的。传统应用的缺点自动化部署很困难。如果需要运行应用的多个不同的实例很难让多个实例在同一个服务器上“共存”。如果服务器停机由于需要手工流程所以需要较长的时间来恢复。部署新版本的过程基本是手动的或者大部分是手动的难以回滚。很有可能测试环境与生产环境有较大差异导致一些生产环境问题不能在测试期间发现。很难通过增加新的实例来进行横向扩展。什么是容器化将应用“容器化”的过程就是让应用能够运行在 Docker 容器或类似技术中它们能将操作系统环境和应用封装在一起完整的系统镜像。由于容器能给应用提供近似于完整系统的环境这就为在不修改或者少量修改应用的情况下对应用的部署进行现代化改造提供了一种思路。这也是应用的架构持续能保持“云友好”的基础。容器化的好处部署容易多了使用新的容器镜像直接替换整个老版本。自动化部署也相对容易甚至可以完全由 CIContinuous 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 REQUIRED UBUNTU PACKAGES \ apt-get clean \ rm -rf /var/lib/apt/lists/*# 将应用的文件复制到镜像里ADD . /app# 运行安装脚本RUN /app/setup.sh# 切换到应用的目录WORKDIR /app# 指定应用的启动脚本COMMAND /app/start.sh制作用于配置的启动脚本如果应用已经在使用环境变量中读取配置值了那这一步可以跳过了。如果要从文件里读取特定环境相关的配置值那启动脚本就要能从环境变量里读取配置值并将这些值更新到配置文件中去。这里有一个启动脚本的例子#!/usr/bin/env bashset -e# 把环境变量 $MYAPPCONFIG 的值添加到配置文件中cat /app/config.txt ENDmy_app_config ${MYAPPCONFIG}END# 用环境变量 $MYAPPARG 作为应用的启动参数/app/bin/my-app --my-arg${MYAPPARG}推送镜像镜像生成之后使用 docker build需要推送到 Docker 仓储Registry中才能从部署机器上拉取到如果要在生成镜像的同一台机器上运行就不需要。可以使用 Docker Hub 来存储镜像用付费账号可以创建私有仓库大多数云服务商也提供容器仓储比如 Amazon ECR。给镜像设置标签比如 docker tag myimage mycompany/myimage:mytag之后就可以推送到仓库了比如 docker push mycompany/myimage:mytag。每次在应用新版本生成镜像时打上新的标签这样既能明确当前所运行的版本还能保留旧版本的镜像以便回滚。三、如何部署部署容器是个很大的话题接下来只关注直接使用 docker 命令运行容器的部分。在现实世界中应该考虑使用 docker-compose对于所有容器都运行在同一机器上的简单情形和 Kubernetes 在集群中编排容器之类的工具。被移出来的服务提前移出来的服务可以运行在单独的 Docker 容器中然后链接link到我们的应用所在容器。另外还可以用云上托管的服务。举个例子在 AWS 上可以使用 RDS 作为数据库、用 Elasticache 作为缓存这样可以极大地简化你的工作因为他们能为你解决后期维护高可用和备份等需求。运行 Postgres 数据库容器的例子docker run -d \ --name db \ -v /usr/local/postgresql/data:/var/lib/postgresql/data \ postgres容器化之后的应用要在 Docker 容器中运行一个应用只要用一个命令行docker run -d \ -p 8080:80 \ --name myapp \ -v /usr/local/myappdata:/var/lib/myappdata \ -e MYAPPCONFIGmyvalue \ -e MYAPPARGmyarg \ --link db:db \ myappimage:mytag其中的 -p 参数将容器里的 80 端口公开并映射到主机上的 8080 端口-v 参数设置要在容器里加载的、用于持久化数据的存储卷格式是 主机上的路径:容器中的路径-e 参数设置一个用于配置的环境变量值这些参数可以指定多次从而设置多个卷和环境变量而 --link参数将数据库所在容器以链接的方式传入这样应用就可以与数据库通信了。容器会根据 Dockerfile 中的 COMMAND 指令指定的脚本来启动。对应用进行升级如果要升级到应用的新版本只要停掉旧版的容器比如 docker rm -f myapp并用新的镜像标签启动新的容器就可以了可能有短暂的停机时间。回滚操作也类似只要换用旧版的镜像标签。更多相关考量“init” 进程PID 1传统应用通常有多个进程如果没有 “init” 守护进程PID 1的清理就容易出现孤儿进程orphan processes发生累积的情况了。Docker 默认并不提供这样的守护进程所以推荐自己用 ENTRYPOINT 在 Dockerfile 里添加一个。dumb-init 是众多初始守护进程中的比较轻量级的一个。phusion/baseimage 是一个包含 init 初始守护进程和其他一些服务的全功能基准镜像。 请查看我们博客上关于这个主题的文章Docker 守护进程PID-1, 孤儿进程, 僵尸进程和信号。守护进程和定时任务在使用 Docker 容器时一般只会在每个容器中运行一个进程。理想情况下所有守护进程和定时任务都应该移到其他容器中去不过对于传统应用来这也不一定都行得通主要是经常要求对应用进行重新设计。要运行多个进程也不是一定不行但确实会需要一些额外的一些配置因为标准的基准镜像里并不包含进程管理和调度能力。小型进程管理程序比如 runit比 systemd 之类的完整功能的子系统更适合在容器中用。phusion/baseimage 是一个包含 runit 和定时能力和其他一些服务的全功能基准镜像。存储卷的权限在容器里所有进程通常都以 root 身份运行不过也不是必须的。传统的应用对用户的需求通常复杂一些可能要用其他用户来运行或者用不同的用户运行多个进程。这可能给存储卷的使用带来一些麻烦因为 Docker 默认让加载的卷的所有权指向 root也就是说非 root 进程就不能写入到这些卷了。有两个方法可以解决这个问题第一种方式是在在创建容器之前先在主机上创建好目录由有正确的 UID/GID 的用户持有所有权。注意由于容器里和主机上的用户不能匹配所以需要用容器里用户的 UID/GID而不仅仅是用户名要一致。另一种方式是在容器里在启动过程中调整加载点的所有权。这就需要在切换到用来启动应用的非 root 用户之前还在以 root 身份运行期间处理。数据库迁移数据库结构迁移在部署工作中经常是一大挑战因为数据库结构通常与应用是严格耦合的这对迁移的时机提出了要求而且这也让回滚到旧版本变得更难因为数据库迁移并不一定容易回滚。完成这种迁移的方法是引入一个过渡步骤。如果需要对数据库结构做出与旧版本不兼容的变更那就将这个变更分为两次部署。比如如果想将数据移到另一处两个步骤是将数据同时写入旧的位置和的位置并只从新的位置读取。这意味着如果把应用回滚到前一个版本在回滚之前新产生的新数据是不会丢的。不再向旧的位置写入数据。 要注意的是如果希望部署期间没有停机时间就意味着在同一时间会有应用的多个版本在运行相应的也会带来更多挑战。数据备份对容器化的应用进行备份通常比较简单。数据文件可以从主机上备份而不需要担心数据会与应用程序的文件混在一起因为它们已经严格地分开了。如果将数据库迁移到了像 RDS 这样的托管服务他们就会处理好备份至少自己的工作会简化一些。迁移已有数据在生产环境中要把现有应用迁向容器化的版本就需要对旧的已有数据进行迁移。这个工作往往因地制宜不过最简单的就是停掉旧版本把数据备份直接恢复给新版本用。这个过程应该提前做好也不可避免地会需要一定的停机时间。结论虽然提前需要做一些工作对传统的应用进行容器化的过程会帮助我们更好地对它进行管控和自动化能把部署的压力降到最低。它给对应用进行现代化改造提供了一个明确的路径并能支持零停机部署、高可用和横向扩展。除了从零开始构建容器化应用FP Complete 已经多次开展了上述实践。如果你想了解迈向现代化、无压力部署之路可以了解一下我们的 DevOps 和咨询服务或直接联系我们。英文原文请手动复制https://www.fpcomplete.com/blog/2017/01/containerize-legacy-app