将Java 应用容器化改造并迁移到Kubernetes 平台

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介:
为了能够适应容器云平台的管理模式和管理理念,应用系统需要完成容器化的改造过程。对于新开发的应用,建议直接基于微服务架构进行容器化的应用开发;对于已经运行多年的传统应用系统,也应该逐步将其改造成能够部署到容器云平台上的容器化应用。本文针对传统的Java 应用,对如何将应用进行容器化改造和迁移到Kubernetes 平台上进行说明。

要将传统Java 应用改造迁移到Kubernetes 平台上运行,通常要经过以下几个步骤。

(1)进行应用代码改造,要考虑配置文件、多实例部署下的分布式架构问题,并对程序代码和架构做出相应的改造。

(2)进行容器化改造,选择合适的基础镜像并打包生成新的应用镜像,使得应用能以容器方式部署、运行。

(3)进行Kubernetes 建模与部署,采用合适的Kubernetes 资源对象建模Java 应用,最终发布到Kubernetes 平台上实现应用的自动化运维。

接下来以一个传统的Java 应用改造迁移过程为例,来说明上述步骤中的细节。

1 Java 应用的容器化改造迁移

我们的目标是搭建一个简单的学员分数管理系统(Study Application),应用界面与架构如下图。
520865ab970006ffcecbc67368d83d225bccfbcd
Study Application 是一个典型的J2EE 系统,为了方便理解,并没有采用额外的框架技术,而是采用了MySQL 数据库,将JSP 作为Web 页面,并通过JDBC 进行数据库操作,整个系统以标准方式部署在Tomcat 的webapp 目录下。

下图所示是Study Application的目录结构与说明。
a30354db78ba7c7b8e8d1c1eed611fca76a0ddcb
下面是在index.jsp 中访问数据库的关键代码, 数据库连接的配置信息被放在jdbc.properties 属性文件中,便于在不同的环境下修改:

01124bdcd985184e8ba37e09e6fa8430eaee2f57

   Class.forName("com.mysql.jdbc.Driver");
    java.util.Properties pps = new java.util.Properties();
    pps.load(new java.io.FileInputStream("jdbc.properties"));
    String ip=pps.getProperty("mysql_ip");
    String user=pps.getProperty("user");
    String password=pps.getProperty("password");
    System.out.println("Connecting to database...");
    conn =
java.sql.DriverManager.getConnection("jdbc:mysql://"+ip+":3306"+"?useUnicode=true&characterEncoding=UTF-8", user,password)
        stmt = conn.createStatement();
        String sql = "show databases like 'HPE_APP'";
        rs =stmt.executeQuery(sql);

我们知道,应用在以容器化运行以后,是不建议进入容器里修改配置文件的(在多实例情况下很难保持配置文件同步更新),因此,需要修改从jdbc.properties 属性文件中获取数据库连接的以上代码,根据容器环境的要求,将其改为从环境变量中获取,改造后的代码如下:
3ec70a5c597ea153852e79c8723bfc6f45659111

    String ip=System.getenv("mysql_ip");
    String user=System.getenv("user");
    String password=System.getenv("password");

改造后的代码基本达到了容器化的要求,但对于一个完整的应用来说,由于还存在用户Session 会话保持的问题,因此还需要实现分布式的Session 会话机制,才能做到多实例部署,此时可以考虑采用Spring Session 框架来改造、升级我们的单体应用。对于大部分RESTful 服务,由于不需要会话保持功能,因此可以直接多副本部署,多个实例可以同时提供服务。

2 Java 应用的容器镜像构建

接下来,我们需要将自己的Java 应用打包为Docker 镜像,以容器方式启动并提供服务。在打包镜像时,需要注意以下几个关键问题。

(1)需要注意基础镜像的选择问题。选择基础镜像的两个原则:标准化与精简化。尽可能选择Docker 官方发布的基础镜像,这些基础镜像通常符合标准化与精简化这两个目标。比如,它们都有Dockerfile 源文件,我们可以获知此镜像是如何制作的,并可以在此基础上实现诸如软件版本、性能优化、日志及安全等方面的特殊定制,然后打包为公司级别的内部标准镜像,供各个项目使用。

(2)需要注意业务进程的启动方式。与在物理机上将自己的程序放到后台运行的方式不同,在容器化时,我们需要将自己的业务进程放到前台运行。这样一来,当业务进程由于某种原因而停止时,容器也随之销毁,我们就能及时观察到这种严重故障,并做出相应的行动来恢复系统。目前有一些系统在容器化的过程中采用了supervisord 这样的工具,将业务的主进程和辅助进程放到后台启动,并交给supervisord 监管,这种做法虽然在一定程度上也能实现自动重启故障进程的目标,但它将问题隐藏得更深,即使业务进程由于特殊故障始终无法重启成功,运维人员也发现不了问题,因此不建议采用这种方式启动业务进程。

(3)需要注意程序的日志输出问题。在物理机上运行业务进程时,我们通常会把程序日志输出到指定的文件中,以便更好地排查故障。但在容器化以后,我们需要改变这种做法,将程序的日志直接输出在容器的屏幕上(或者说控制台Console 上),此时Docker 会将这些输出日志存放到容器之外的特定文件中,第三方的日志收集工具(例如Elasticsearch)就可以方便采集这些日志并实现集中化的日志搜索和分析功能。此外,Docker 也提供了统一的log 命令来查看容器的日志,这推进了系统运维的标准化。Java 中常用的Log4j 及Slf4j日志框架都支持把日志输出到控制台的配置方式,在打包应用时,需要对日志的配置文件做出相应的修改。

(4)需要注意文件操作的问题。当业务进程运行在物理机上时,它看到的文件系统就是物理机的文件系统;但当业务进程运行在容器中时,它所访问的文件系统就是一种特殊的、被隔离的、分层模式的虚拟文件系统,在这种情况下,频繁进行I/O 操作的性能比较低。为了解决这个问题,容器可以使用Volume 将频繁进行操作的目录映射到容器外部(通常是物理机上);同时,Volume 也是容器与外部交换文件的重要工具,因此在制作镜像和运行容器时,需要考虑Volume 映射的问题,对于在程序运行过程中产生的大量临时文件和被频繁读写的文件,或者在需要跟外界交换文件时,可以选择挂载Volume。

下图是Study Application 打包镜像的示意图及对应的Dockerfile 源码。
1aa2d23012bffc92b70a95236770db6444a9b9dd
Study Application 的镜像继承了tomcat:9-alpine 这个官方的基础镜像,这个镜像基于Alpine Linux,如果对比一下,我们会发现,基于alpine 的镜像不到5MB,而基于Ubuntu或CentOS 的镜像都在100MB 以上。此外,从Study Application 的Dockerfile 来看,制作Java 类型应用的Docker 镜像是很方便的一件事,通常只需几行代码。

3 在Kubernetes 上建模与部署

在应用容器化后,就可以在Kubernetes 上建模与部署了,在建模的过程中,我们需要考虑一些关键问题,这些问题及其答案如下。

(1)将业务进程建模为Pod 还是RC?

对于这个问题,最重要的判断依据是该进程提供的是有状态服务还是无状态服务。对于无状态服务,比如大多数REST 接口的服务,通常是可以在任意节点上启动并提供服务的,例如我们这里的Web 应用程序就符合无状态服务。但对于有状态服务,比如MySQL服务,我们通常不能这么做,因为它依赖本地存储的数据库文件。对于有状态服务,我们通常只能将业务进程建模为Pod,这是因为RC 控制的Pod 实例可以从一台节点飘到另一台节点上,如果我们能够通过共享存储解决Pod 的状态问题,则也可以把某些有状态服务的进程建模为RC,这种做法与StatefulSet 很类似。

(2)我们是否需要在Pod 的基础上,继续建模对应的Service?

这主要取决于此Pod 是否会被其他业务进程(或终端用户)所访问,对于不会被其他业务进程所访问的Pod,我们无须建模对应的Service。实际上,在一个分布式系统中,大多数进程都会被建模为Service 并对应一个微服务,如果某个服务还需要被终端用户访问,则往往还需要“导出”外网访问地址,比如NodePort 端口。对于无须外部访问的Service,还可以考虑建模为Headless Service,在这种情况下,该Service 不会分配一个虚拟的ClusterIP,通信效率更高。

(3)是否需要考虑应用的数据存储问题?

如果只是本机存储,则可以直接使用Kubernetes Volume 资源对象;如果希望有远程存储功能,则可以考虑使用PV(Persistent Volume)。这样一来,不管Pod 被调度到哪台机器,都可以继续访问原来的存储数据。如果希望系统自动管理共享存储的空间,则可以考虑建模对应的StorageClass。

(4)是否需要考虑应用的配置问题?

我们知道,在几乎所有应用开发中,都会涉及配置文件的管理问题,比如StudyApplication 中的数据库配置信息,常见的互联网应用还有缓存中间件、消息队列、全文检索等一系列中间件的配置文件。而在分布式情况下,发布在多个节点上的Pod 副本都需要访问同一份配置文件,这也加大了配置管理的难度,为此业内的一些大公司专门开发了自己的一套配置管理中心,如360 的Qcon、百度的Disconf 等,但这些解决方案都比较复杂而且有侵入性。Kubernetes 则提供了无侵入的更简单的方案,这就是ConfigMap,我们可以把任意数量的配置文件放入ConfigMap 中,实现集中化管理,然后通过环境变量的方式将配置数据传递到Pod 里,或者通过Volume 方式挂载到Pod 内。

在Study Application 中,Web 应用在Kubernetes 上的建模如图6-4 所示。我们通过定义一个RC 来控制Web 的Pod 实例,数据库连接信息则通过环境变量传递到Pod 里,然后定义一个Service,并且通过NodePort 方式暴露到集群外供用户访问,即可完成这个Java应用的容器化改造工作。

本文选自 《Kubernetes权威指南:企业级容器云实战》一书,电子工业出版社9月出版。本书通过全新的视角,针对容器云领域现下的热点和技术难点,给出了基于Kubernetes的企业级容器云落地指南,为企业传统IT转型和业务上云提供助力。

95d720cb4419ef3bace850f2c0259918e92b2851
图书详情:https://item.jd.com/12420042.html
相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
19天前
|
运维 Kubernetes 监控
提升运维效率:容器化技术在现代IT基础设施中的应用
本文将探讨容器化技术如何优化企业的IT基础设施,提高部署效率和资源利用率。我们将深入分析容器技术的优势、实现步骤以及在实际运维中的应用场景。通过实例展示,帮助读者更好地理解并应用这一前沿技术,助力企业实现高效运维。
|
8天前
|
运维 JavaScript 虚拟化
探索容器化技术:Docker的实践与应用
【10月更文挑战第9天】探索容器化技术:Docker的实践与应用
31 3
|
12天前
|
Kubernetes Cloud Native 云计算
云原生之旅:构建你的第一个容器化应用
【8月更文挑战第75天】在数字化浪潮中,云原生技术成为推动企业创新和效率提升的关键动力。本篇文章将引导你开启云原生之旅,通过一个简易的步骤指南,帮助你构建并部署第一个容器化应用。我们将一起探索Docker容器的魅力,以及如何利用Kubernetes进行集群管理,实现服务的自动化部署、扩展和管理。无论你是云原生新手还是希望深化理解,这篇文章都将为你提供实践操作的启示和深入思考的契机。
|
18天前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
41 2
|
3天前
|
运维 JavaScript Linux
容器内的Nodejs应用如何获取宿主机的基础信息-系统、内存、cpu、启动时间,以及一个df -h的坑
本文介绍了如何在Docker容器内的Node.js应用中获取宿主机的基础信息,包括系统信息、内存使用情况、磁盘空间和启动时间等。核心思路是将宿主机的根目录挂载到容器,但需注意权限和安全问题。文章还提到了使用`df -P`替代`df -h`以获得一致性输出,避免解析错误。
|
5天前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
15 0
|
11天前
|
运维 监控 Cloud Native
深入了解容器化技术:Docker 的应用与实践
【10月更文挑战第6天】深入了解容器化技术:Docker 的应用与实践
19 0
|
11天前
|
Kubernetes Cloud Native 调度
深入探讨容器化技术:Kubernetes 的魅力
【10月更文挑战第6天】深入探讨容器化技术:Kubernetes 的魅力
27 0
|
13天前
|
运维 Kubernetes Cloud Native
云原生时代的容器编排:Kubernetes入门与实践
【10月更文挑战第4天】在云计算的浪潮中,云原生技术以其敏捷、可扩展和高效的特点引领着软件开发的新趋势。作为云原生生态中的关键组件,Kubernetes(通常被称为K8s)已成为容器编排的事实标准。本文将深入浅出地介绍Kubernetes的基本概念,并通过实际案例引导读者理解如何利用Kubernetes进行高效的容器管理和服务部署。无论你是初学者还是有一定经验的开发者,本文都将为你打开云原生世界的大门,并助你一臂之力在云原生时代乘风破浪。
|
14天前
|
Kubernetes Cloud Native 流计算
Flink-12 Flink Java 3分钟上手 Kubernetes云原生下的Flink集群 Rancher Stateful Set yaml详细 扩容缩容部署 Docker容器编排
Flink-12 Flink Java 3分钟上手 Kubernetes云原生下的Flink集群 Rancher Stateful Set yaml详细 扩容缩容部署 Docker容器编排
57 0

相关产品

  • 容器服务Kubernetes版