在做 Java 程式容器化時都會遇到一個問題, ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", ...] $JAVA_OPTS
這樣的寫法
就是個字元串無法在運作時展開。為了不把參數寫死到容器裡,每次調整參數重新建構鏡像,可以有多種方案,先介紹幾種不夠好的方案。
-
,這種方式的問題是 java 不是容器主程序(至于為什麼要保證 java 是主程序,又是一個話題,是容器化基本最佳實踐之一);ENTRYPOINT java $JAVA_OPTS -jar ...
-
,這種寫法其實等價于上面一種方式,上面一種方式在運作時就是以ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar ..."]
方式運作的,是以缺陷也是相同的;/bin/sh -c "java $JAVA_OPTS -jar ..."
-
然後在腳本中啟動 java,使用腳本對于需要在啟動時做複雜操作的容器比較有用,但是對啟動 java 來說未免小題大作,并且同樣有 java 不是容器主程序的問題。ENTRYPOINT ["entrypoint.sh"]
從 shell 角度出發,解決非主程序問題的方案是使用
exec
指令,
exec
在啟動其後參數中的指令時,不會建立子程序而是用指令程序替換自身,使指令程序占用自身的 PID(exec 其後的第一個指令替換了自身之後,後續的其它指令自然也不會被執行了)。于是上面
這三個方案可以改為:
-
ENTRYPOINT exce java $JAVA_OPTS -jar ...
-
ENTRYPOINT ["sh", "-c", "exce java $JAVA_OPTS -jar ..."]
- 腳本裡寫
exec java $JAVA_OPTS -jar ...
使用 exec 可以解決之前的問題,但是随之而來的問題是……醜,任何額外的指令都會破壞整潔,對于追求 clean code 的程式員來說 Dockerfile 也必須是整潔的。還好 java 是一個成熟的生态,其實本身提供了相應的環境變量
JDK_JAVA_OPTIONS
和
JAVA_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 的時代并不是個好選擇。
綜上所述,可以得出這樣的決策路徑:
- Java 9 及以上的 java 指令使用
JDK_JAVA_OPTIONS
- CI/CD 或者打包工具之類的非 java 指令時使用
JAVA_TOOL_OPTIONS
- Java 9 以下的 java 指令使用
JAVA_TOOL_OPTIONS
極特殊情況下需要覆寫指令行上的參數時,先反思自己,再反思自己,最後找各家自己定義的環境變量。
最後一個問題,看到這裡可能會有疑問,設定環境變量會不會影響到其它 java 程序?如果遵循了容器化的最佳實踐,那答案顯然是不會,而且即使在主機上,要想多個程序間環境變量互不影響也是很簡單的事情不是嗎?