编辑推荐: |
本文将概述,将软件迁移到容器的具体的技术建议和指导,范围从镜像构建过程到如何在生产中运行。应用需求将决定应用程序应该如何迁移。
本文来自于云世,由Alice编辑、推荐。 |
|
背景
当考虑将应用程序迁移到容器中时,有三种主要的高级策略:
无论选择哪种方法,重要的是要认识到大多数软件是在现代之前设计和编写的。基于镜像的容器被发明了,即使你选择了“提升和转移”方法(可能在单个容器中运行单体应用程序),你的应用程序也可能需要修改。为了成功地迁移到容器,考虑到你的应用程序的需要和linux容器的性质,需要一个可靠的迁移策略。
本文将概述,将软件迁移到容器的具体的技术建议和指导,范围从镜像构建过程到如何在生产中运行。应用需求将决定应用程序应该如何迁移。
思维模型
使用容器的商业优势和技术优势一样多。在构建和使用容器时,分层至关重要。你需要查看你的应用程序,并考虑每一部分,以及它们是如何一起工作的——类似于将程序分解为一系列类和函数的方法。容器由包和脚本组成,它们与其他容器结合在一起构建应用程序。因此,使用容器时要考虑到应用程序是由更小的单元组成的,将这些单元打包成易于使用的东西将使容器化的应用程序更容易理解、部署和维护。
适当的分层
分层的目的是在前一层之上提供一个抽象层,以构建更复杂的东西。层是逻辑单元,其中的内容是相同类型的对象或执行类似的任务。
适当的层数将使容器易于使用。过多的层次将会变得过于复杂和困难。应用程序的适当层数应该反映应用程序的复杂性——应用程序越复杂,层数就越多。例如,如果Hello World容器打印到标准输出(stdout)“Hello World”,它不需要配置、进程管理或依赖项,所以它只需要一个单层。但是,如果我们扩展Hello World应用程序向用户发送“hello”时,我们需要第二层来收集输入。
启动脚本
在服务层上提供一个薄抽象层,启动脚本非常有效,该抽象层在容器启动时运行。启动脚本在使用非常简单的(API)基础层之上提供额外的功能,供operator 接入。启动脚本最常见的任务是设置权限、移动配置文件、更改文件的所有权、清理目录和启动服务。
以重启为中心
应用程序的每个生命周期操作都使用“重新启动”,因为这是向进程发出操作正在发生的信号的一种廉价而有效的方法。生命周期操作需要重新启动以更改进程的运行方式。
例如,为MariaDB执行一个重新配置操作。假设一个JSON文件是一个简单的(RPC),它映射配置文件的位置和启动信息。这个JSON文件由set_configs.py解释,它在容器启动时将配置文件复制到它们的位置。用户通过改变主机上的配置并重新启动容器来重新配置MariaDB。
授权编排
应用程序的每一层不意味着都放在容器中,所以不要添加太多层。如果这样做,将很快与现有工具、其他层重叠,并使容器变得更加复杂。
我们不希望将工具构建到只能部署和管理应用程序的容器中,从而增加额外的工作。相反,我们希望编制工具能够轻松地管理我们包装好的容器。
应用程序需求
从体系结构、安全性和性能的角度来看,应用程序有特定的需求。其中一些需求会影响将应用程序迁移到一个容器或将其分解为多个容器所需的努力程度。
架构
从架构的角度来看,将应用程序迁移到容器与从Unix迁移到Linux或操作系统升级没有什么不同。通常,应用程序已经运行多年,文档常不存在或过时。与大多数迁移一样,执行迁移的技术人员必须完成必要的工作,以了解应用程序如何有结构地运作。他们必须重新设计其建立的引擎。至少,必须能够回答以下问题:
- 这个应用程序的二进制文件在哪里?它们是通过安装程序安装的,将它们放在一个地方,还是遍布整个文件系统?是否有一个易于启动的二进制文件,或者是否有一个可以使用的简单systemd单元文件?
- 这个应用程序的数据驻留在哪里?它是只读的还是读写的?它能被两个并发进程安全地写入吗?
- 所有配置数据位于何处?它是在单个目录中,单个文件中,还是在文件系统中的多个位置中?
- 这个应用程序有什么样的私密数据?可以在应用程序中配置秘密数据的位置吗?是否可以将它们移动到单独的目录中,或者是否可以通过身份或证书服务器使用某种密钥访问它们?
- 这个应用程序需要什么样的网络访问?是简单的HTTP吗?是名称服务器吗?哪些需要用户数据报协议(UDP)?或者,它是一个真正复杂的应用程序,需要使用internet协议安全(IPsec)之类的东西在容器之间进行点对点加密?
- 安装程序是一个shell脚本,可以反向设计以获得更多关于应用程序设置的信息吗?二进制文件是通过RPMs或其他类型的包管理器安装的吗?
- 应用程序的许可是否允许轻松地在容器镜像中分发应用程序?有时许可是非常严格的; 有时可能需要有site许可证。
- 应用程序是否容易重启?Apache可以重新启动数千次而不会失败,但是数据库可能会损坏表。这会让协调和恢复变得更加困难吗?
回答这些问题将决定你的应用程序是否适合容器迁移——如果难度级别太高,它就不值得操作。
表1。在数据中心中看到的典型工作负载
安全
在许多方面,容器化应用程序的安全决策与在进程中运行的常规应用程序没有什么不同。对于给定的应用程序,须决定什么隔离级别是足够的。在评估要迁移的工作负载并决定容器是否提供足够的隔离时,检查当前运行的工作负载的隔离程度非常重要。
从下图左到右,每个技术决策都提供了越来越多的隔离。对于某些应用程序,常规的Linux进程提供了足够的隔离。在同一个Linux操作系统实例上运行MySQL和web服务器是很常见的。另一方面,有时一个应用程序的两个副本需要位于两个不同的数据中心,这些数据中心受到不同的天气和地震的影响——这在灾难恢复中很常见。
例如,高性能计算(HPC)工作负载现在通常在大型集群中运行,只有常规的Linux进程隔离。这个并不完美——大型集群中的研究人员可能试图黑进彼此的进程,但这通常被确定为可接受的风险水平。
另外一个例子,即使是虚拟机,是常见的面向外部的服务,如域名服务器(DNS)、HTTP、或虚拟专用网(VPN)服务,与面向内部的服务完全不同的运行在面向外部服务的虚拟化集群,比如Oracle数据库或SAP实例。这种配置相当于机架级隔离。很少有安全专家愿意在同一个虚拟化集群中运行这两种类型的工作负载,容器平台也是如此—--通常这些类型的服务将运行在不同的集群中。
因此,当你想知道容器是否提供了足够的隔离时,请在工作负载要求的情况中考虑它。
性能
还须对工作负载进行性能分析。容器和虚拟化是可添加的技术,可以与裸金属硬件结合起来提供特性和功能。表2提供了一个快速指南,帮助确定在将应用程序迁移到容器中时应该考虑的重要功能。
容器是使用控制组(cgroups)、安全增强Linux (SELinux)和命名空间等技术的Linux进程,为应用程序提供更高级别的隔离。允许它们以本机或接近本机的速度运行。没有像虚拟化这样的抽象层,因此集群中的所有容器化应用程序都必须基于相同的硬件架构结构和操作系统。
在HPC环境中,可以将容器添加到裸金属来提供相似的性能水平,同时提高隔离水平。另一方面,如果工作负载是一个需要在Windows和Linux中使用组件的企业级应用程序,则组合容器和虚拟化可能是一个更好的选择。在虚拟机中运行容器提供了硬件自由、更好的隔离和使用容器镜像提高可管理性的组合。
使用表2可以更好地理解组合不同技术的利弊:工作负载平台比较
技术清单
最佳实践
层化应用。
层的数量应该反映应用程序的复杂性。
容器的抽象级别略高于RPM。
避免在容器内解决所有问题。
使用启动脚本层从运行时提供一个简单抽象。
在容器中建立清晰和简洁的操作,由外部工具控制。
识别和分离代码、配置和数据。
代码应该存在于镜像层中。
配置、数据和私密数据应该来自环境。
容器即意味重新启动。
不要重新创建进程。
永远不要构建最新的标签——它会防止构建随着时间的推移被重复。
用活性和敏捷检查。
架构
在创建容器化的应用程序镜像时,请考虑这个检查表。了解以下信息很重要:
1. 确定在容器中运行应用程序所需的所有二进制文件的位置。
a.使用层——考虑核心构建和应用运行时版本
b.确定依赖关系,并确定前面的层是否应该包含依赖关系,特别是它们是否可以被其他应用程序共享或使用。
c.确定二进制文件将如何启动:script, systemd, etc
2. 识别包含应用程序配置的文件和目录。这些需要在运行时绑定到应用程序中。配置应该在环境(dev/QA/production),不应该嵌入到容器镜像中
3.确定将包含应用程序数据的文件和目录。这些将需要在运行时绑定到应用程序中。应用程序数据应该来自环境—dev/QA/production—并且不应该嵌入到容器镜像中
4. 确定应用程序需要什么网络协议。这将决定这些服务是在集群内部提供还是在客户外部提供。
5. 安装程序脚本可以被反向工程以更好地理解它是如何工作的吗?
a. 确定它所做的配置更改——尝试确定是否可以使用脚本或配置管理来替换安装程序脚本。是否可以在运行时将配置传递给容器镜像以动态设置参数?
b.确定数据存储的位置——尝试确定数据在安装期间是否设置了模式,以及是否可以在应用程序运行时编写脚本。
6. 确定是否可以轻松重启服务。如果服务对重启敏感,使用活性和敏捷检查来确定operator是否需要干预。在容器编排环境中,尽可能地实现自动化是非常关键的。
7. 确定日志记录和输出应该放在哪里。通常,rpm安装的应用程序将日志放在/var/log或其他已知位置。在容器化的环境中,这可能会将大量数据转储到读写层中。
8. 确定是否需要扩展单个进程。如果有必要,将应用程序分解为多个容器。
安全
在创建容器化的应用程序镜像时,请考虑这个检查表,了解以下信息很重要:
1. 尽可能使用策略。 使用安全环境约束和服务帐户来设置这些策略。发现基于配置文件的控件通常比针对每个应用程序的自定义规则更好用,而且通常被更广泛地采用和维护。
2. 识别包含应用程序机密的文件。这包括证书和密码之类的东西。这些不应该嵌入到容器镜像中,因为下载镜像的任何人都可以访问它们。它们应该由容器环境进行保护和提供。
3.避免在需要特权的端口上运行容器。使用内置的安全环境约束来阻止这种情况。
4. 避免以root用户运行容器,即使使用Kernal命名空间,也存在升级到容器之外的风险,这可能危及底层容器宿主机。使用内置的安全环境约束来阻止这一点。
5. 在可能的情况下,使用只读根文件系统运行容器。使用安全环境约束来执行此操作。
a.对于web内容,也以只读的卷挂载方式运行服务
6. 确定所需的隔离级别。运用最小特权原则和深度防守原则在容器平台环境中,使用安全环境约束和服务帐户配置技术。
性能
在创建容器化的应用程序映像时,请考虑这个检查表,了解以下信息很重要:
- 确定应用程序是否访问或创建临时文件。
默认情况下,这些文件系统是只读挂载的。这些可以挂载为卷,但是要注意来自多个进程的读写访问。
a. / sys
b. / proc:
i. 提供kernal数据结构接口的伪文件系统。
ii. 大多数文件是只读的,但有些文件允许更改kernal变量。
iii. /proc/[pid]ns中的条目表示为容器化的进程id实例化的kernal命名空间。
iv.例如:/proc/asound、/proc/bus、 / proc / fs 、 / proc/irq、 /proc/sys 和 /proc/sysrq-trigger
c. / dev:
i.在容器中,应用程序可以访问有限的设备文件,例如 /dev/null和/dev/ zero。如果不使用特权模式,容器化的应用程序将无法访问主机设备文件,例如/dev/sdX和/dev/ ttysx。
d. /运行:
i.在docker v1.10之后,用户可以为docker运行传递-tmpfs选项,然后/run作为tmpfs挂载到容器中。
ii. 在Red Hat系统上,/run/secrets总是作为tmpfs挂载,以提供注入订阅信息的位置
iii. 在Linux主机上操作。/run作为TMPFS挂载,以保存进程的临时数据(例如:daemon的pid)。这将在服务器重新启动后被删除。在容器中,只有/run/secrets被挂载,因为tmpfs-/run本身包含在/(根)文件系统中。因此,即使容器重新启动,/run下的文件也不会被删除。
2. 确定应用程序是否需要更改内核参数(/proc/sys)或访问特殊的硬件。
- 默认情况下,对于容器化进程,/proc/sys下的内核调优变量是只读的。
- 运行这些类型的应用程序可能需要使用正确的内核参数或硬件配置某些节点,并使用节点选择器在具有特殊配置或资源的节点上调度应用程序例如:/proc/sys/fs/mqueue, /proc/sys/ kernel/(msgmax , msgmnb , msgmni , sem , small , shmmax , shmmni ,和 shm rmid_forced)。
- 如果应用程序本身需要更改kernal参数(例如,通过sysctl),那么它应该运行在具有特权容器的独立集群中。
3.日期、时间和地点:
- 确定应用程序是否需要不同的区域设置(例如,JST)。在镜像构建时将其设置为重建镜像。
- 确定申请是否需要更改日期
4. 确定应用程序是否希望使用固定的IP地址。
- 尽量避免静态IP配置
- 容器网口IP地址不能修改
- 在可能的情况下使用主机名,因为Kubernetes中的服务网络会处理好的网络。
- 如果应用程序有包含IP地址的参数或配置,则拦截ENTRYPOINT在启动时动态更改配置文件。使用像SED或Ansible这样的工具来做这件事。
5. 确定应用程序是否需要使用多个网络接口。
- Kubernetes当前不支持多个虚拟网卡。像bondinq这样的网络冗余函数不能在容器中使用;这应该在宿主机进行。pod应该被设计成在具有正常网络的节点上发生故障并重新启动。配置适当的活性和敏捷检查。
- 通过一些第三方工具可以实现多个网络接口。
结论
成功地将现有应用程序迁移到一个或多个容器中,需要理解应用程序并开发一个全面的计划。几乎任何应用程序都可以被容器化。但重要的是要了解所需的工作量,并确保到容器的转换保持性能和维护提高了安全性。
|