动态给容器指定 Java 启动参数

简介: 在做 Java 程序容器化时都会遇到一个问题,ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", ...] 这样的写法 $JAVA_OPTS 就是个字符串无法在运行时展开。为了不把参数硬编码到容器里,每次调整参数重新构建镜像,可以有多种方案。

在做 Java 程序容器化时都会遇到一个问题,ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", ...] 这样的写法 $JAVA_OPTS 就是个字符串无法在运行时展开。为了不把参数硬编码到容器里,每次调整参数重新构建镜像,可以有多种方案,先介绍几种不够好的方案。

  1. ENTRYPOINT java $JAVA_OPTS -jar ...,这种方式的问题是 java 不是容器主进程(至于为什么要保证 java 是主进程,又是一个话题,是容器化基本最佳实践之一);
  2. ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar ..."],这种写法其实等价于上面一种方式,上面一种方式在运行时就是以 /bin/sh -c "java $JAVA_OPTS -jar ..." 方式运行的,所以缺陷也是相同的;
  3. ENTRYPOINT ["entrypoint.sh"] 然后在脚本中启动 java,使用脚本对于需要在启动时做复杂操作的容器比较有用,但是对启动 java 来说未免小题大作,并且同样有 java 不是容器主进程的问题。

从 shell 角度出发,解决非主进程问题的方案是使用 exec 命令,exec 在启动其后参数中的指令时,不会创建子进程而是用指令进程替换自身,使指令进程占用自身的 PID(exec 其后的第一个指令替换了自身之后,后续的其它指令自然也不会被执行了)。于是上面

这三个方案可以改为:

  1. ENTRYPOINT exce java $JAVA_OPTS -jar ...
  2. ENTRYPOINT ["sh", "-c", "exce java $JAVA_OPTS -jar ..."]
  3. 脚本里写 exec java $JAVA_OPTS -jar ...

使用 exec 可以解决之前的问题,但是随之而来的问题是……丑,任何额外的命令都会破坏整洁,对于追求 clean code 的程序员来说 Dockerfile 也必须是整洁的。还好 java 是一个成熟的生态,其实本身提供了相应的环境变量 JDK_JAVA_OPTIONSJAVA_TOOL_OPTIONS

JDK_JAVA_OPTIONS 是在 Java 9 引入的,java 程序启动时不需要在命令行指定就会自动读取的环境变量,它略微有些限制,主要是为了防止滥用不允许使用可能改变主类或者让主类不执行的参数,通常需要指定的内存、GC 等参数都可以使用。遇到不允许使用的参数时 java 会直接报错并退出,所以只要程序顺利启动就不用担心使用了不允许使用的参数。在这里指定的参数无法覆盖命令行的相同参数,需要锁定的配置可以直接指定在 ENTRYPOINT中。

JAVA_TOOL_OPTIONS 是存在很久的环境变量,这个环境变量同样不需要在命令行显式指定。它名字中的 TOOL 提示了除了 java 命令之外其它 java 工具命令例如 javac 之类的也会去读取这个变量的值。在这里指定的参数既不能覆盖命令行的相同参数,也不能覆盖 JDK_JAVA_OPTIONS 中的相同参数,优先级最低。

除此之外,还有各家专用的一些环境变量,比如 Oracle 家的 _JAVA_OPTIONS、IBM 家的 IBM_JAVA_OPTIONS,它们通常提供了覆盖命令行上相同参数的能力,但是环境变量名却不可移植,在 Xxx as Code 的时代并不是个好选择。

综上所述,可以得出这样的决策路径:

  1. Java 9 及以上的 java 命令使用 JDK_JAVA_OPTIONS
  2. CI/CD 或者打包工具之类的非 java 命令时使用 JAVA_TOOL_OPTIONS
  3. Java 9 以下的 java 命令使用 JAVA_TOOL_OPTIONS

极特殊情况下需要覆盖命令行上的参数时,先反思自己,再反思自己,最后找各家自己定义的环境变量。
最后一个问题,看到这里可能会有疑问,设置环境变量会不会影响到其它 java 进程?如果遵循了容器化的最佳实践,那答案显然是不会,而且即使在主机上,要想多个进程间环境变量互不影响也是很简单的事情不是吗?

目录
相关文章
|
6月前
|
Java 虚拟化 容器
(Java)Java里JFrame窗体的基本操作(容器布局篇-1)
容器 容器,我的理解是可以包容其他东西的玩意。它可以是一个盒子,可以是一个虚拟化的物品,可只要能包裹住其他存在质体的东西,那么都可以称作是容器。例如:JPanel组件和JScollPane组件两者都是容器也是组件。 既然有容器,那么容器中的布局就必不可少了。不然不规矩的摆放物品,人类看不习惯,我也看不习惯 ???? 本篇内容,将说明java JFrame窗体里容器中几类布局。 说明:所有在JFrame窗体里的容器布局都会使用setLayout()方法,采用的布局参数都将放进这个方法里 绝对布局 调用窗体容器
193 1
|
10月前
|
存储 缓存 安全
Java 集合容器常见面试题及详细解析
本文全面解析Java集合框架,涵盖基础概念、常见接口与类的特点及区别、底层数据结构、线程安全等内容。通过实例讲解List(如ArrayList、LinkedList)、Set(如HashSet、TreeSet)、Map(如HashMap、TreeMap)等核心组件,帮助读者深入理解集合容器的使用场景与性能优化。适合准备面试或提升开发技能的开发者阅读。
173 0
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
10月前
|
缓存 Java API
Java 集合容器实操技巧与案例详解
本教程基于Java 8+新特性和现代开发实践,深入讲解Java集合容器的实操技巧。通过具体场景演示Stream API数据处理、ConcurrentHashMap并发控制、LinkedHashMap实现LRU缓存、TreeSet自定义排序等高级特性。同时涵盖computeIfAbsent优化操作、EnumMap专用集合使用、集合统计与运算(交集、并集、差集)等内容。代码示例丰富,助力掌握高效编程方法。[点击获取完整代码](https://pan.quark.cn/s/14fcf913bae6)。
184 0
|
Java Linux Maven
java依赖冲突解决问题之容器加载依赖jar包如何解决
java依赖冲突解决问题之容器加载依赖jar包如何解决
|
存储 安全 算法
Java容器及其常用方法汇总
Java Collections框架提供了丰富的接口和实现类,用于管理和操作集合数据。
297 2
Java容器及其常用方法汇总
|
监控 Java 中间件
8G的容器Java堆才4G怎么就OOM了?
本文记录最近一例Java应用OOM问题的排查过程,希望可以给遇到类似问题的同学提供参考。
|
存储 Ubuntu 关系型数据库
《docker基础篇:7.Docker容器数据卷》包括坑、回顾下上一讲的知识点,参数V、是什么、更干嘛、数据卷案例
《docker基础篇:7.Docker容器数据卷》包括坑、回顾下上一讲的知识点,参数V、是什么、更干嘛、数据卷案例
256 13
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
消息中间件 NoSQL Kafka
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
510 4