[每日短篇] 23 - 動態給容器指定 Java 啓動參數

在作 Java 程序容器化時都會遇到一個問題,ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", ...] 這樣的寫法 $JAVA_OPTS 就是個字符串沒法在運行時展開。爲了避免把參數硬編碼到容器裏,每次調整參數從新構建鏡像,能夠有多種方案,先介紹幾種不夠好的方案。java

  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 其後的第一個指令替換了自身以後,後續的其它指令天然也不會被執行了)。因而上面 3 個方案能夠改成程序員

  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_OPTIONSshell

  1. JDK_JAVA_OPTIONS 是在 Java 9 引入的,java 程序啓動時不須要在命令行指定就會自動讀取的環境變量,它略微有些限制,主要是爲了防止濫用不容許使用可能改變主類或者讓主類不執行的參數,一般須要指定的內存、GC 等參數均可以使用。遇到不容許使用的參數時 java 會直接報錯並退出,因此只要程序順利啓動就不用擔憂使用了不容許使用的參數。在這裏指定的參數沒法覆蓋命令行的相同參數,須要鎖定的配置能夠直接指定在 ENTRYPOINT中。
  2. JAVA_TOOL_OPTIONS 是存在好久的環境變量,這個環境變量一樣不須要在命令行顯式指定。它名字中的 TOOL 提示了除了 java 命令以外其它 java 工具命令例如 javac 之類的也會去讀取這個變量的值。在這裏指定的參數既不能覆蓋命令行的相同參數,也不能覆蓋 JDK_JAVA_OPTIONS 中的相同參數,優先級最低。

除此以外,還有各家專用的一些環境變量,好比 Oracle 家的 _JAVA_OPTIONS、IBM 家的 IBM_JAVA_OPTIONS,它們一般提供了覆蓋命令行上相同參數的能力,可是環境變量名卻不可移植,在 Xxx as Code 的時代並非個好選擇。工具

綜上所述,能夠得出這樣的決策路徑:編碼

  1. Java 9 及以上(呃,話說如今還有 Java 9 如下?)的 java 命令使用 JDK_JAVA_OPTIONS
  2. CI/CD 或者打包工具之類的非 java 命令時使用 JAVA_TOOL_OPTIONS
  3. Java 9 如下(囧)的 java 命令使用 JAVA_TOOL_OPTIONS
  4. 極特殊狀況下須要覆蓋命令行上的參數時,先反思本身,再反思本身,最後找各家本身定義的環境變量

最後一個問題,看到這裏可能會有疑問,設置環境變量會不會影響到其它 java 進程?若是遵循了容器化的最佳實踐,那答案顯然是不會,並且即便在主機上,要想多個進程間環境變量互不影響也是很簡單的事情不是嗎?命令行

相關文章
相關標籤/搜索