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

标签: dev | 发表时间:2018-10-30 00:00 | 作者:
出处:http://itindex.net/relian

为了能够适应容器云平台的管理模式和管理理念,应用系统需要完成容器化的改造过程。对于新开发的应用,建议直接基于微服务架构进行容器化的应用开发;对于已经运行多年的传统应用系统,也应该逐步将其改造成能够部署到容器云平台上的容器化应用。本文针对传统的Java 应用,对如何将应用进行容器化改造和迁移到Kubernetes 平台上进行说明。

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

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

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

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

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

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

我们的目标是搭建一个简单的学员分数管理系统(Study Application),应用界面与架构如下图。

Study Application 是一个典型的J2EE 系统,为了方便理解,并没有采用额外的框架技术,而是采用了MySQL 数据库,将JSP 作为Web 页面,并通过JDBC 进行数据库操作,整个系统以标准方式部署在Tomcat 的webapp 目录下。

下图所示是Study Application的目录结构与说明。

下面是在index.jsp 中访问数据库的关键代码, 数据库连接的配置信息被放在jdbc.properties 属性文件中,便于在不同的环境下修改:

   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=tru
e&characterEncoding=UTF-8", user,password)
stmt = conn.createStatement();
String sql = "show databases like 'HPE_APP'";
rs =stmt.executeQuery(sql);

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

   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 源码。

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应用的容器化改造工作。

相关 [java 应用 容器] 推荐:

减少使用Java应用服务器,迎接Docker容器

- - ITeye资讯频道
【编者的话】随着Docker的发展,越来越多的应用开发者开始使用Docker. James Strachan写了一篇有关Java开发者如何使用Docker进行轻量级快速开发的文章. 他告诉我们,使用Docker和服务发现的机制,可以有效减轻Java运维人员的负担,进行项目的快速启动和持续迭代. 多年来,Java生态系统一直在使用应用服务器.

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

- - IT瘾-dev
为了能够适应容器云平台的管理模式和管理理念,应用系统需要完成容器化的改造过程. 对于新开发的应用,建议直接基于微服务架构进行容器化的应用开发;对于已经运行多年的传统应用系统,也应该逐步将其改造成能够部署到容器云平台上的容器化应用. 本文针对传统的Java 应用,对如何将应用进行容器化改造和迁移到Kubernetes 平台上进行说明.

Java中的CopyOnWrite容器

- - 酷 壳 - CoolShell.cn
感谢  清英 同学的投稿. Copy-On-Write简称COW,是一种用于程序设计中的优化策略. 其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略. 从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet.

Java应用运维

- - BlueDavy之技术blog
对于互联网产品或长期运行的产品而言,运维工作非常重要,尤其是在产品复杂了以后,在这篇blog中就来说下Java应用的运维工作(ps:虽然看起来各种语言做的系统的运维工作都差不多,但细节上还是会有很多不同,so本文还是只讲Java的). 苦逼的码农按照需求开发好了一个全新的Java Web应用,该发布上线给用户用了,要把一个Java Web应用发布上线,首先需要搭建运行的环境,运行的环境需要有JDK、APPServer,在已经装好了os的机器上装上JDK和APPServer,开发好的Java Web应用可以用maven直接打成war或ear,将这个打好的包scp或其他方式到目标机器上,准备妥当,就差启动了.

Java线程池应用

- - CSDN博客架构设计推荐文章
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务. 2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机). Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具.

Java 应用一般架构

- - SegmentFault 最新的文章
原文地址: https://blog.coding.net/blog/General-architecture-for-Java-applications. 当我们架设一个系统的时候通常需要考虑到如何与其他系统交互,所以我们首先需要知道各种系统之间是如何交互的,使用何种技术实现. 现在我们常见的不同系统不同语言之间的交互使用WebService,Http请求.

xssProject在java web项目中应用

- - Java - 编程语言 - ITeye博客
1.项目引入xssProtect-0.1.jar、antlr-3.0.1.jar、antlr-runtime-3.0.1.jar包. * 覆盖getParameter方法,将参数名和参数值都做xss过滤. * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
.

使用Gradle构建Java Web应用(译)

- - BlogJava-首页技术区
使用Gradle构建Java Web应用. 本文是发布在 java.net上的一篇摘自于一书中的 节选,介绍了使用 Gradle构建Java Web应用的过程. 刚刚接触Gradle,看到了这篇小文,随手译了出来:-) (2014.01.23最后更新). 在职业生涯和私人生活中,我们中间的许多人要同时管理多个项目.

Java-了解注解及其应用

- - BlogJava-qileilove
  注解就是一种标记,在程序中加了注解就等于加了标记,没加,就没有标记. java编译器、开发工具或是其他程序可以通过反射技术了解你的类或各种元素是否有标记,有什么标记就做什么. 比如:子类重写父类的方法,方法上必须有@override标记;若一个方法已过时不用了,就该方法添加注.   解@Deprecated,调用者反射时就明白这方法已过时.

消除Java应用中的Exception开销

- - 舒の随想日记
抛异常最大的消耗在于构造整个异常栈的过程,如果你的栈很深,特别是用了一些框架的话,这个开销基本是不可忽视的,之前做的一个优化显示当时应用中的一个异常使得整个应用的性能下降至少30%. 最大开销的地方在这里,当你去new一个Exception的时候,会调用父类Throwable的构造函数, Throwable的构造函数中会调用native的fillInStackTrace(),这个方法就会构造整个异常栈了.