你想构建一个Java应用程序并在Docker中运行它吗?你知道在使用Docker构建Java容器有哪些最佳实践?
在下面的速查表中,我将为你提供构建生产级Java容器的最佳实践,旨在优化和保r K 5 # H i ] #护要投入A 3 [ q J o生产环境中的Docker镜像。

1.Docker镜像使用确定性O [ o – } = 2 \的标签
2.在Java镜像中仅安装需要的内容
3.查找并修复Java镜像中的安全漏洞
4.使用多阶段构建Java镜像
5.l 2 Y不要以root用户身份运H Q 5 j e h [ k行Java应用程序
6.Jz Z | A 2 P gava应用程序不N ] \ 1 x & r要使用PID为1的进程
7.优雅下线Java应用程f O \ 8 ]序
8.bs _ d i ` F W J z b | 4 * 1使用 .dockerignorE p @ : A /e文件
9.确保Java版本支持容器
10.谨慎使用容器自动化生成工具` l @ r ( # K
构建z ] ; k H I C ?一个简单的Java容器镜像
让我们从简单的Dockerfile开始,在构建Java容器时,我们经常会有如下类似的内容:
- FROMmaven
- RUNmkdir/app
- WORKDIR/app
- COPY./app
- RUNmvncle( / I _aninstalm H M l/ X F x \l
- C2 w 0 j c 6 ;MD"mvn""exec:M ) H L 7 j tjai : {va"
- CopythattR : K - _ 8 ! =oafilenamedDockerfile,thenbuildV b + A l [andrunit.
- $dockerbuild.-tjavah G i-application# # - P * | A ~ ;
- $dockerrun-p8080:8080java-\ r & { ! V u ` NapplicatioH ; L ^F \ n a ; W * 9 qn
这很简单,而且有效。但是,此镜像充满错误。
我们不仅应该了解如何正确使用Maven,而且还应避免像上述示例那样构建Java容器。
下面,让我们开始逐步改进这个DockerfQ g e | M N @ li p . ~ile,使你的Java应用程序生成A g z l i \ 3 z Q高效,安全的Docker镜像。
1.Dob B g b 5 8cker镜像使用确定性的标签
当使用Maven构建Java容器镜像时,我们首先需要基于Maven镜像。但是,你知道使用Maven基本镜像时实际上引入了哪些内容吗s * S :?
当你使用下面* \ ( e的代码行构建镜像时,你将获得该Maven镜像的最新版本:
FROM maven
这似乎是一个有趣的功能,但是这种采用Maven默认镜像的策略可能存在一些潜在问题:
- 你的Docker构建不是幂等的。这意味着每次Q 7 v f 3 I ~构建的结果可能会完全不同,今天的最新镜像可能不同于明天或下周9 , b Q V t @ ; y的最新$ E A Y – l镜像,导致2 u h R \ ,你的应用程序的字节码也是不同的,并v K E @且可能发生意外。因此,构建[ 6 ] ^ j镜像时,我们希望具有可复制的确定性行为。
- Maven Docker镜像是基于完整的操作系统镜像。这样会导= A 2 / V致许多其他二进制文件出现, . .在最终的生产镜像中,但是运行你的Java应用程序不需要很多T e G M O \ ^这些二进制文件。因此,将它们作为Java容器镜像的一部分存在一些缺点:
1.镜像体积变大,导致更长的下载和构建$ i P g / $ . n时间. e + r。
2.额外的二进制文件可能会引入安全漏3 { / , } o I c d洞。
如何解决呐?
- 使用适合你需求的最/ V E 0小基础镜像 考虑一下-你是否需要一个完整的操作系统(包括所有额外的二进制文件)来运行你的程序?如果没有,也许基于alpines 8 | Y V L ^ R T镜像或* 8 t !Debian的镜像会更好。
- 使用r – Y 5 ^3 ? k M 5 ; [特定的镜像 如果使f 8 p用特定的镜像,则已经可以控制和预测某些行为。如果我使用maven:3.6.3-jdk-11-i b K D 6 Q D { Kslim镜像T , \ /,则已经确定我正在使用JDK 11和= p ] M a ) fMaven 3.6.3。JDK和Maven的更新,将不再影响Java容器的行为。为了更加精确,你也可以使用镜像的SHA256哈希值。使用哈希将确保你每次构建镜像时都使用完全相同的基础镜像。
让我们用这些知识更新V 0 ) * ~ g我们的Dockerfile:
- FROMmaven:3.\ ( o ! r V y p6.3-jdk-11-slim@sha256:68ce1cd457u { ( ) s c } - i891f48d1e137c7d6a4493f60843e84c9e; E & + T2634e3df1d3d5b381d36c
- RUNmk8 C + ` W )dir/app
- WORKDIR4 x P/app
- COPY./4 : d } T & 6f ] [ % j Mapp
- RUNmvn( 2 o Hcleanpackage-DskipTests
2.在Java镜像中仅安装需要的内容
以下命令会在S D 6 V H j L C容器中构建Java程序,包括其q @Q * & f i P N U H U B 2 j z所有依赖项。这意味着源代码和构建系统都将会是Java容V ( 9 \ t器的一部分。
RUN mvn clean package –1 \ e ~ KZ 3 KDskipTe] r y Rsts
我们都知道Java是一种编译语言。这意味着我们只需要由你的构建环境创建的工件,而不需要` b #代码本身。这也意味着构建环境不应成为Java镜像的一^ ^ Q P ( : ! W $部分。
要运行Java镜像,我们也不需要完整的JDK。一个Java运行时环境(JRE)就足够了。c O [ } R B a % r因此,从本质上X 8 r U m /讲,如果它是可运行的JAR,那么只需要使用JRE和已编译的Jx . m # 6 ~ @ \ava构件来e ~ ]构建镜像。
使用Maven在CI流水线e 3 ; 8 ! 8中都构建编译程序,然后将JAR复制到镜像中,如下面的更新的Dockerfile中所示:
- FROMopenjdk:11-jre-slim@m k t @su 5 n P ` g &ha256:$ k 9 331a5d3$ ? % ? v ]fa2942eea891cf954f7d07, ` B Q o / i 0359e09cf1b1f3d35fb32fedebb1e3399fc9~ = + O Ce
- RUNmkdir/app
- COPY./target/java-application.jar/app/jav% B H Ia-application.jar
- WORKDI- S q / -R/app
- CMD"java""-jar""jav_ 9 ) 0 o v Ha-application.jar"
3. 查找并修G n L ~复Java镜像中的安全漏洞
通过上面,O ! r L 7 A s ! G我们已经开始使用适合我们需求的最小基础镜像了,但是,我不知道此基本镜像中的二进制文件是否包含问题。让我们使用安全工具(如 Snyk CLI)扫描] ^ # c [ F y . =测试我们的Docker镜像。你可以在此处注册免P w N b f D n费的Snyk帐户。
使用npm,brew,scoop或从Github下载最新的二进制文件c 3 v O & 7 8 } r来安装Snyk CLI:
- $npminst{ . `n Q G o j 5all-gsnyk
- $sny/ { 8kauth
- $snykcontainertestopenjdk:11-jre-slim@sha256:31) F O } N : K Eai O D P5d3fa2942eea891cf954f7d07359e09cf1b1f3d35n8 c T w @ ; , 7 U k { { / ? d % $fb32fedebb1e3399fc9e--file=Doe N 4 { 3ckerfile
使用我刚刚创建的免费| j } ! 4 _帐户来登录。使用snyk contK w * ^ \ 8 R 7ainer test可以测试任何Docker镜像。此外,我还可以添加D– U j W . [ 9 ) Vockerfj + ^ C I g f ]ile以获得\ l \ 7 b + g更好的建议。
S5 p ^ H H & Rnyk在此基本镜像中发现了58个安全问题。它们中的大多数与O x P N r j 5 [Debian Linux发行版附带的二进制文件有关。 根据此信T q q 7 9 ) U h .息,我将基础镜像切换为n o h r b由adoptopenjdk提供的
openjdk11:jre-11.0.9.1_1-alpine镜像。
- FROMadoptopenjdk/openjdk11:jre-11.0.9.1_1-alpinp 4 (7 o , T t J G De@sha256:b6ab039066382d39cfc( t !843= ` ! i , Q P s9O 4 * F14ef1fc624aa60e2a16ede433509ccadd6d9c r ~ g \ M95b1f
然后使用snyh Q 9 F xk container命令对此进行测试时,此k 3 & b Y [ ] 3 M镜像没M L @ , ? v有已知的漏h J I _洞。
以类似的方式,你可以通过snyk test命令% r & 7在项目的根目f u T v g l录中测试Java应用程序。我建议你在本地计算机上进行开发时,请同时测试应j 9 F @ X \用程序和创建的Java容器镜像。紧接着,对CI流水线中的镜像和应用程序执行相同的测试自动化。
另外,请记住,随着时间的推E c L D \ O移会j W | & h T J发现漏洞。一旦发现新漏L ) } m | 6洞,你可能希望得到通知。
还有,使用snyk monitor监视你的应用程序,你将能够及时发现新的安全问题时采取适当的措施。
另外s X = e,你也可以将git存储库连接到Snyk,这样我们就可以帮助查, x l K m ^ W 6 Q找和补救漏洞。
让我们更新当前的Dockerfile:
- FROMadoptopenjdk/openjdk11:jre-5 ` { $ @ ! G f w11.0.9.1_1-alpine@sha256:b6ab039066382dm s [ \ M39cfc8439^ : h14ef1fc624aa60e2a16ede433509cca\ @ z v 0 . 9 Zdd6d995b1f
- RUNmkdir/app
- COPY./target/java-applicZ ) L Tation.jar/app/java-application.jar
- WORKDIR/uX zC _ } j v 2 x 7 E |A p v U H U o Y -sr/src/project4 B N F ^
- CMD"java""-jar""java] v v $-application.jar"
4.使用多? = e阶段构建Java镜像
在本文的前面,我们谈到了我们不需要在容器中构n g {建Java~ S + F g y k X k应用程序。但是,在某# v 9 \ a w % *些情况下,将我们的应用程序构建为Dockew = } I \ ( h f Lr镜像的一部分很方便。
我们可以将Docker镜像的a p } + S : 9 M构建分为多个阶段。我们可以使用构建应用程序所需的所有工具来构建镜像,并在最后阶段创建实际的生产镜像。
- FROMmaven:3.6.3-r s / wjdX \ f j e kk-11-slim@sha256:68ce1cd457891f48d1e137c7d63 9 z 5 @ a w x Oa4493f60843e84c9e2634e3d[ I O % P of1d3d5b381d36` } ;cASbuild
- RUNmkdirV , 1 S/project
- COPY./prs | 3 & d N Y $ \oject
- WORKDIR/project
- RUNmvncleanpackage-Dskv 2 X Y WK l v t / A 6 kipTests
- FROMadoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@shN I ` g / B Aa256:b6ab039066382d9 u q R P i39cW # P ! Sfc84391f | 54ef1fc624aa60e2a16ede4l ; ) R )33509ccadd6d, h 6 ` s v I p995b1f
- RUNmkdir/app
- COPY-8 7 a v-from=build/projec[ 6 H ~ r S ( h 2t/target/java-applicatM O N N G Rion.jar/app/ja$ A }vaJ # U s D G-application.jar+ A 5 / )
- WORKDIR/app
- CMD"java""-jar""java-application.jar"
防R v O \ N % E #止敏感信息泄漏
在创建) R :Jh P ) ^ava应用程序和Docker镜像时,很有可能需要连接到私有仓库,类似settings.xmj V b 7 ql的配置文件经常会泄露敏感6 # U e e w信J r J E ? )息。但在使用多阶段构建: z & * ] 4 j * R时,你可以安全地将settingsb / d.xml复制到你的构建容器中。带有凭据的设置将不会出现在你的最终镜像中。此外,如果是将凭据用作命令行参数,^ [ 7 r则可以在构建镜像中安全地执行此操作。
使用多阶段构建,你可以创建多个阶段,仅2 ` $ ) ; 4将结果复制到最终的生产镜像中。这是分离是确保在生| / C W 2 c d产环境中不泄d o f \ (漏数据的一种方法。
哦,顺便说一句,使用docker hiv , 0 S : ^ 7 Nstory命令查看Java镜像的输出:
- $dockerhisto7 e S P - Kryjava-application
输* 9 % d 0 t F W出仅显示来自容器镜像的信息,而不显示构建镜像的过程。
5.不要以Root用户运行容器
创建Dockz Y Eer容器时,s ; ? . R k你需要应用最小特权原则,防止由于某种原因攻击者能够入侵你的应用程序,则你不希望他们能够访问所有内容。
拥有多层安全性,可以帮助你减少系统威胁。因此,必须确保你不以root用户身份运行应用程序。
但默认情况下,创z % 4 l q $ u J W建Dock) _ J n h F x 8er容器时,你将以root身份运行它。尽e $ i L : l n \管这对于开发很方便,但是你不希望在生产镜像中使用它。假设由于某种原因,攻击者可以访问终端或可以执行代码。在那种情况下,它对R a Y w f ) a 8 ;正在运行的容器具有显著6 O 1的特权,并且访问B 5 ` H q主机文件系统。
解决方2 ; p $ s 2案非常简单。创建一个有限特权的特定用户来运行你的应用程序,并确保该用户可以运行该应用] x Y o A程序。最后,在c G | (运行应用程序之前,不要忘记使用新创建的用户。
让我们相应地更新我X 8 Q们的Dockerfiles A 9 B 8 I L j。
- FROMmav_ Z | q a \en:3.6.3-jdk-11-slim@sha256:68ce1cd4578i \ 9 E B x91f48d1e137; % M ) E mc7L 3 f Md6a4493f60843eR * k { - i / P84c9e2634e) W E * R h & [ ;3df1d38 i Hd5b381d36cASbuildf : b K $ t
- RUNmkdir/p3 [ } 6 F aroject
- COPY./pro- 3 g 5ject
- WORKDIR/project
- RUNmvncleanpackage( j * P-DskipTests
- FROMadopto- ] E E : D Dpenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6u k l IX 6 S 9 ] \ab039066382d39cfc843914ef1fc62N Y m P E u \ P E4aa60e2a16ede433509ccadd6d995b1f
- RUNmkdirI E # N/app
- RUNaddgroup--systemT z ! ? { & *javauser&&adduser-S-s/bin/false-Gjavauserja0 R X } ; f 3 uvausu y+ ^ x o a i n Of S g m * * | 0 N 1 + ]er
- COPY--from=build/project/target/java-applicaH x 4 | 2 /tion.jar/a! n [ | ] d &pp/java-application.jar
- WORKDIR/app
- RUNchoa + & y uwn-Rjavauser:jn L N p . 6avauser/ar ^ L Q %pp
- USERjavauser
- CMD"java""-jarG { [ = &""java-application.jar"
64 p } D # F G S.Java应用程序不要使用PID为\ ~ Z \ ( l1的进程
在许多示例中,我看到了使用构建环境来启动容器化Java应用程序的常见错误。
上面,我们了解了要在 Java容器中使用Mave) U+ Y G G u ~ K g t 0 t ?n或Gra1 4 m l 4 Xdlek A p \ g ` q ~ /的重要性,但是使用如下g f n * i } W命令,会有不同的效果:
- CMD “mvn” “exec:java”
- CMD [“mvn”, “spring-boot run”]
- CMD “gradle” “boD 9 l f D 2 y 5 jotRun”
- C\ c EMD “run-app.sh”
在Docker中运行应用程序时,第一个应; U @ E l用程序将以进程ID为1(PID=1)运行。Linux+ 5 G 2 U内核会以特殊方式处理PID为1的进程。通常,进程号为1的PID上的过程是初始化过程。如果我们使H | 1 f \ w # Q `用MaA ? O g ( 7ven运行Java应用程序,那么如何确定D N P ?Maven将类似SIGTERM信号转发给Java进程呢?
如果像下h t | 5 , ] [面的示例,那样运行Docker容器,则JaO # u xva应用程序将^ j 0具有PID为1的进程。
- CMD“java”“-v & 2 h ! p U bjar”“w [ 0applc m Bication.jar”
请注意,j 5 h Q y R Rdocker kill和docker stop命令仅向\ b 9 s # \ JPID为1的容器进程发送信号。例如,~ C @ Q v & Z如果你正在运行Java应用的shell脚本,/bin/sh不会将信号转发给子进程。
更为重要的是,在Linux中Z d C ; d,PID为1的容器进程还有一些其2 g y他职责。在“ 《DocY / H 3 5 = rke\ V 4 3 ? & X Z *r和僵p q t U k尸进程问题》”一文( + l / * – X中对它们进行了很好的描述。因此,在某些情况下,你不希+ n 2 I 7 ?N { + f l ; N望应用程序成为PID为1的进程,因为你不知道如何处H r 1 C b 3 0理这些问题。一个很好的解决方案是使用dumb-init。
- RUNapkadddumb-init
- CMD"dumb-init""java""-jaI = P z [ { 8 Zr""java-application.jar"
当你像这样运行Docker容器时# [ F c @ e ` – \,dumb-init会占用PID为1的容器1 Z y 4 V进程并承担所有责任。你的Java5 k & ^ & ) \ 2流程不再需要考虑这一点。
我们更新后的DU r ? = YE u Q M I T hockW 0 ; e 4 } $ j0 b jerfile现在看起来像这样:
- FROMmaven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7J + 9 z a H t $d6a4493f60843e84c9e2Z z , M i e / r634e3df1d36 } ^ 0 } -d5b381d36cASbuild
- RUNmkdir/project
- COPY./project
- WORKDIR/project
- RUNmvncleanpackage-Dsk+ u J ! 9ipTests
- FROMadoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
- RUNapkadddumb-init
- RO B B $ m i \ HUNmkdir/app
- RUNaddgroupA # } 8--systemjava~ 4 7 s ) C +user&&J W c ; -adduser-S-s/bin/false-Gjavauserj7 : T U 1 b V ^ /avauser
- COPY--from=build/project/tarT \* w q ! 3 b \7 N x K \ 8 h ( Q X `get/java-codJ 3 : O 6e-workshop-0.0.1-SNAPSHOT.jar/app/java-app\ \ d r V ^ 6 ] Wlication.jar
- WORKDIRw 3w 9 j f 4 O \ t W % . */app
- RUNchown-Rjavauser:javausfO G ! 0 ) b J Uer/app
- USERjavauser
- CMD"dumb-init""java""-jar""java-application.jar"
7.优雅下线Java应用程序
当你的应5 M H U F 4 4用程序收到关闭信号时,9 E W N G X 7 P y理想情况下,我们希望所有内容都能正常关闭。根o P J f b $ W V (据你开发应用程序的方式,中断信号(SIGINW Q U 5T= , r e _ b _ m N)或CTRL + C可能导致立即终止进程。
这可能不是你想要的东西,因为i q 0 ; %诸如此类的事情可能会导致意外行为,l 6 v y G Y甚至导致数据丢失。
当你将应用程序作为Payara或Apache Tomcat之j w 4 + v W 3 s d类的\ ; t M QWeb服务器的` w a ` o ` F _一部分运行时,该Web服务器很可能会正常关闭。对于某些支持可运行应用程序的框架也是如此。例如,Spring Boot具有嵌入式Tomcat版本,可以有效地处理关机问题。
当你创建一个独立的Java应用程序或手动创建一个可运行的JAR时,你必须自己处理这些中断信号。
解决方案非常简单。添加一) m E : # q个退出钩子(hook ),如下面的示例所示。收到类似S: m Y * = PIGIN+ S j # lT信号后,优雅下线应用程序的进程将会被启动。
- Runtime.getRuntime().addSD M \ / ihutdownHook(nel d g $wThread(){
- @Overri3 E % s v q 5 jde
- publicvoidrun(){
- System.out.println("IM T z & 8 TnsideAddShutdownHook");
- }
- });
诚然,与Dockerfile相关的问题相比,这是一个S r B通用的W_ g 1eb应用程序问题,但在容器环境中更重要。
8.使用 .K + x w Y c 5dockerignore文件
为了防止# 6 n 0 Q T不必要的文件D 5 h污染git存储库,你可以使用.gitignore文件。
对于Docker镜像,我们有\ * Y类似的东西-.dockerignore文件。类似于git的忽略文件,l L b d 0 P S n ~它是为了防止Docker镜像中出现不需要的文件或目录。同时,我们也不希望敏感信息泄漏到我们的Docker镜像中。
请参阅以下示例的.dockerignore:
- .dockerignore
- **/*j ] N J.lo7 N c ~ Qg
- Dockerfile
- .git
- .gitign- G 8 = \ H % 2 [ore 0 O -e
使用.dockerB Y +ignore文件的要点是:
- 跳过仅用于测试目的的C ~ a 4 ] ` d W O依赖项。
- 使你免于泄露密钥或凭据信息进入Javax f i S Docker镜{ T w $ O T像$ o $ k _ N 5的文件。
- 另外,日志文件也可能包含你不想公开的敏感信B Y | I \息。
- 保持Docker镜像的美观和整洁, y 2,本质上是使镜像变小。除此之外,它还有助于防止意外行为。
9.确保Java版本支持容器
Java虚拟机(JVM)是一件了不起的事情。它会根据其运行的系统进行自我调整。有基于行为的调整,可以动态优化堆的大小。但是,在Java 8和Java 9等较旧的版本中,J( e G s NVM无法识别容器设置的CPU限g – a B f ^制或内存限制。这些较旧的Y l mJava版本的JVM看到了主机系统上的全部内存和所有; $ r [ jCPU容量。DockK f j o Per设Q W ] k Z U c s置的限制将被O $ Z s }忽略。
随着Java 10的发布` S [ l 7 8,JVM现在可以感知容器,并且可以识别容器设置的约束。该功能I ; x @ e H g : fUseContainerSupport是JVM标志,默认情况下设置为活动状态e x 2 U P R。Java 1x v Y k C , 9 ~0中发布的容器感知功能也已移, ~ t 8植到Java-8u191。
对于Java 8之前的版本,你可以手动尝试使用该-G t Y [ 3Xmx标志来限制堆大小,但这是一个痛苦的练习。紧接着,堆大小不等于Jav= 9 a 7a使用的内存。对于Java-8u1F 9 & o 9 ? @ *31和Java 9,容器感知功能是实验性的,你必须主动激活。
-XX:+d C x C } UnlockExperimentalVMOptions -XX:+ UseCGroupMemoryL; Y V , Y |imitForHeH R . , | f * fap
最好的选择是将Java更新到10以上的版本,以便默认情况下支持容器。不幸的是,许多公司仍i U \ ; K + 8 –然严重依赖Java8。这意味着你应该在Docker镜像中更新到Java的最新版本,或者确保至少使用Java 8 uU K + E Vpdate 19& 0 S1或更高版本。
10.谨慎使用容器自动化生成工具
你可能会偶然发现适用于构建系统8 { 2的出色工具和插件。除了这些插件,还有一些很棒的工具可以帮助你创建Java容器,甚至可以根据需要自动发布应用。
从开发人员9 ` ] % {的角度来看,这看起来很棒,因为你不必在创建实际应用程序时,还要花费精力维护Dockerfile。
这样的插件的一个例子是JIB* v # ) M V L +。如下所示,我只需要调用mvn jib:dockerBuiP L uld% R – j W命令可以构建镜像
- &ln ^ { @t;plugin&y z 3 wgt;
- <groupId>com.google.cloud.tools</groupId>
- <artifactId>jib-m/ B .av3 h y J Z E ven-plugi} e ; V \ ^ f v Rn<z } } [ R J w i;/artiX . i =factId>
- &Q \ * @lt;version>2.7.1</version@ CB N F G 2 c D>
- <configuration>
- <to>
- <image>myimJ - M c hage</image>
- </to>
- </configuratz p $ e 0 l ^ ,ion>
- </plugin>
它将为我构建一个H N A U F 7 = . h具有指定名称的Docker镜像,而没有任何麻烦。
使用2.3及更高版本时,可以通过调用mvn命令进行操作:
- mvnspring-boot:build-n 2 r ( t 5 J 5 Pimage
在种情况下,系统都0 2 z C ) # _会自动为我创建/ f G U B 4 /一个Jav[ * z 2 w I j Pa镜像。这些镜像还比较小,那是因为他们正在使用非发行版镜像或buik y d – V0 { @ E B [ xldM p y m p M lpack作为镜像的基础。但是,无论镜像大小如何,你如何知道这些z 8 8 1 V % C h容器是安全的U 4 ^ ^ T J | s?你需r 1 W k ~要进行更深入的调查,即使! b 0 \ 3 @这样,你也不确定将来是否会保持这种状态。
我并不d * m @ Z是说你在创建Java D– s docker时不应使用这些工具。但7 ` ] ~ q是,如果E e = ; g 2w K F 3 { 0 b W[ H % U b p !你打算发布这些镜像,则应研究Java镜像所有方面的安全。镜像] e f S uR D ` u & I a { + d扫描将是一个好的开始。从安全性的角度来看,我的观点是,以完全控[ * r l \ h制和正= L h E , u ( H确的方式4 ; Yp % h j Z 9 u ) X创建Dockerfile,是创建镜像更好,更安全的方U O m !式。8 ? o D C 3