- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章构建Java镜像的10个优秀实践由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
。
你想构建一个Java应用程序并在Docker中运行它吗?你知道在使用Docker构建Java容器有哪些最佳实践?
在下面的速查表中,我将为你提供构建生产级Java容器的最佳实践,旨在优化和保护要投入生产环境中的Docker镜像.
1.Docker镜像使用确定性的标签 。
2.在Java镜像中仅安装需要的内容 。
3.查找并修复Java镜像中的安全漏洞 。
4.使用多阶段构建Java镜像 。
5.不要以root用户身份运行Java应用程序 。
6.Java应用程序不要使用PID为1的进程 。
7.优雅下线Java应用程序 。
8.使用 .dockerignore文件 。
9.确保Java版本支持容器 。
10.谨慎使用容器自动化生成工具 。
构建一个简单的Java容器镜像 。
让我们从简单的Dockerfile开始,在构建Java容器时,我们经常会有如下类似的内容:
- FROM maven
- RUN mkdir /app
- WORKDIR /app
- COPY . /app
- RUN mvn clean install
- CMD "mvn" "exec:java"
- Copy that to a file named Dockerfile, then build and run it.
- $ docker build . -t java-application
- $ docker run -p 8080:8080 java-application
这很简单,而且有效。但是,此镜像充满错误.
我们不仅应该了解如何正确使用Maven,而且还应避免像上述示例那样构建Java容器.
下面,让我们开始逐步改进这个Dockerfile,使你的Java应用程序生成高效,安全的Docker镜像.
1.Docker镜像使用确定性的标签 。
当使用Maven构建Java容器镜像时,我们首先需要基于Maven镜像。但是,你知道使用Maven基本镜像时实际上引入了哪些内容吗?
当你使用下面的代码行构建镜像时,你将获得该Maven镜像的最新版本:
FROM maven 。
这似乎是一个有趣的功能,但是这种采用Maven默认镜像的策略可能存在一些潜在问题:
1.镜像体积变大,导致更长的下载和构建时间.
2.额外的二进制文件可能会引入安全漏洞.
如何解决呐?
让我们用这些知识更新我们的Dockerfile:
2.在Java镜像中仅安装需要的内容 。
以下命令会在容器中构建Java程序,包括其所有依赖项。这意味着源代码和构建系统都将会是Java容器的一部分.
RUN mvn clean package -DskipTests 。
我们都知道Java是一种编译语言。这意味着我们只需要由你的构建环境创建的工件,而不需要代码本身。这也意味着构建环境不应成为Java镜像的一部分.
要运行Java镜像,我们也不需要完整的JDK。一个Java运行时环境(JRE)就足够了。因此,从本质上讲,如果它是可运行的JAR,那么只需要使用JRE和已编译的Java构件来构建镜像.
使用Maven在CI流水线中都构建编译程序,然后将JAR复制到镜像中,如下面的更新的Dockerfile中所示:
- FROM openjdk:11-jre-slim@sha256:31a5d3fa2942eea891cf954f7d07359e09cf1b1f3d35fb32fedebb1e3399fc9e
- RUN mkdir /app
- COPY ./target/java-application.jar /app/java-application.jar
- WORKDIR /app
- CMD "java" "-jar" "java-application.jar"
3. 查找并修复Java镜像中的安全漏洞 。
通过上面,我们已经开始使用适合我们需求的最小基础镜像了,但是,我不知道此基本镜像中的二进制文件是否包含问题。让我们使用安全工具(如 Snyk CLI)扫描测试我们的Docker镜像。你可以在此处注册免费的Snyk帐户.
使用npm,brew,scoop或从Github下载最新的二进制文件来安装Snyk CLI:
- $ npm install -g snyk
- $ snyk auth
- $ snyk container test openjdk:11-jre-slim@sha256:31a5d3fa2942eea891cf954f7d07359e09cf1b1f3d35fb32fedebb1e3399fc9e --file=Dockerfile
使用我刚刚创建的免费帐户来登录。使用snyk container test可以测试任何Docker镜像。此外,我还可以添加Dockerfile以获得更好的建议.
Snyk在此基本镜像中发现了58个安全问题。它们中的大多数与Debian Linux发行版附带的二进制文件有关。 根据此信息,我将基础镜像切换为由adoptopenjdk提供的 openjdk11:jre-11.0.9.1_1-alpine镜像.
- FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
然后使用snyk container命令对此进行测试时,此镜像没有已知的漏洞.
以类似的方式,你可以通过snyk test命令在项目的根目录中测试Java应用程序。我建议你在本地计算机上进行开发时,请同时测试应用程序和创建的Java容器镜像。紧接着,对CI流水线中的镜像和应用程序执行相同的测试自动化.
另外,请记住,随着时间的推移会发现漏洞。一旦发现新漏洞,你可能希望得到通知.
还有,使用snyk monitor监视你的应用程序,你将能够及时发现新的安全问题时采取适当的措施.
另外,你也可以将git存储库连接到Snyk,这样我们就可以帮助查找和补救漏洞.
让我们更新当前的Dockerfile:
- FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
- RUN mkdir /app
- COPY ./target/java-application.jar /app/java-application.jar
- WORKDIR /usr/src/project
- CMD "java" "-jar" "java-application.jar"
4.使用多阶段构建Java镜像 。
在本文的前面,我们谈到了我们不需要在容器中构建Java应用程序。但是,在某些情况下,将我们的应用程序构建为Docker镜像的一部分很方便.
我们可以将Docker镜像的构建分为多个阶段。我们可以使用构建应用程序所需的所有工具来构建镜像,并在最后阶段创建实际的生产镜像.
- FROM maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build
- RUN mkdir /project
- COPY . /project
- WORKDIR /project
- RUN mvn clean package -DskipTests
- FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
- RUN mkdir /app
- COPY --from=build /project/target/java-application.jar /app/java-application.jar
- WORKDIR /app
- CMD "java" "-jar" "java-application.jar"
防止敏感信息泄漏 。
在创建Java应用程序和Docker镜像时,很有可能需要连接到私有仓库,类似settings.xml的配置文件经常会泄露敏感信息。但在使用多阶段构建时,你可以安全地将settings.xml复制到你的构建容器中。带有凭据的设置将不会出现在你的最终镜像中。此外,如果是将凭据用作命令行参数,则可以在构建镜像中安全地执行此操作.
使用多阶段构建,你可以创建多个阶段,仅将结果复制到最终的生产镜像中。这是分离是确保在生产环境中不泄漏数据的一种方法.
哦,顺便说一句,使用docker history命令查看Java镜像的输出:
输出仅显示来自容器镜像的信息,而不显示构建镜像的过程.
5.不要以Root用户运行容器 。
创建Docker容器时,你需要应用最小特权原则,防止由于某种原因攻击者能够入侵你的应用程序,则你不希望他们能够访问所有内容.
拥有多层安全性,可以帮助你减少系统威胁。因此,必须确保你不以root用户身份运行应用程序.
但默认情况下,创建Docker容器时,你将以root身份运行它。尽管这对于开发很方便,但是你不希望在生产镜像中使用它。假设由于某种原因,攻击者可以访问终端或可以执行代码。在那种情况下,它对正在运行的容器具有显著的特权,并且访问主机文件系统.
解决方案非常简单。创建一个有限特权的特定用户来运行你的应用程序,并确保该用户可以运行该应用程序。最后,在运行应用程序之前,不要忘记使用新创建的用户.
让我们相应地更新我们的Dockerfile.
- FROM maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build
- RUN mkdir /project
- COPY . /project
- WORKDIR /project
- RUN mvn clean package -DskipTests
- FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
- RUN mkdir /app
- RUN addgroup --system javauser && adduser -S -s /bin/false -G javauser javauser
- COPY --from=build /project/target/java-application.jar /app/java-application.jar
- WORKDIR /app
- RUN chown -R javauser:javauser /app
- USER javauser
- CMD "java" "-jar" "java-application.jar"
6.Java应用程序不要使用PID为1的进程 。
在许多示例中,我看到了使用构建环境来启动容器化Java应用程序的常见错误.
上面,我们了解了要在 Java容器中使用Maven或Gradle的重要性,但是使用如下命令,会有不同的效果:
在Docker中运行应用程序时,第一个应用程序将以进程ID为1(PID=1)运行。Linux内核会以特殊方式处理PID为1的进程。通常,进程号为1的PID上的过程是初始化过程。如果我们使用Maven运行Java应用程序,那么如何确定Maven将类似SIGTERM信号转发给Java进程呢?
如果像下面的示例,那样运行Docker容器,则Java应用程序将具有PID为1的进程.
- CMD“ java”“-jar”“ application.jar”
请注意,docker kill和docker stop命令仅向PID为1的容器进程发送信号。例如,如果你正在运行Java应用的shell脚本,/bin/sh不会将信号转发给子进程.
更为重要的是,在Linux中,PID为1的容器进程还有一些其他职责。在“ 《Docker和僵尸进程问题》”一文中对它们进行了很好的描述。因此,在某些情况下,你不希望应用程序成为PID为1的进程,因为你不知道如何处理这些问题。一个很好的解决方案是使用dumb-init.
- RUN apk add dumb-init
- CMD "dumb-init" "java" "-jar" "java-application.jar"
当你像这样运行Docker容器时,dumb-init会占用PID为1的容器进程并承担所有责任。你的Java流程不再需要考虑这一点.
我们更新后的Dockerfile现在看起来像这样:
- FROM maven:3.6.3-jdk-11-slim@sha256:68ce1cd457891f48d1e137c7d6a4493f60843e84c9e2634e3df1d3d5b381d36c AS build
- RUN mkdir /project
- COPY . /project
- WORKDIR /project
- RUN mvn clean package -DskipTests
- FROM adoptopenjdk/openjdk11:jre-11.0.9.1_1-alpine@sha256:b6ab039066382d39cfc843914ef1fc624aa60e2a16ede433509ccadd6d995b1f
- RUN apk add dumb-init
- RUN mkdir /app
- RUN addgroup --system javauser && adduser -S -s /bin/false -G javauser javauser
- COPY --from=build /project/target/java-code-workshop-0.0.1-SNAPSHOT.jar /app/java-application.jar
- WORKDIR /app
- RUN chown -R javauser:javauser /app
- USER javauser
- CMD "dumb-init" "java" "-jar" "java-application.jar"
7.优雅下线Java应用程序 。
当你的应用程序收到关闭信号时,理想情况下,我们希望所有内容都能正常关闭。根据你开发应用程序的方式,中断信号(SIGINT)或CTRL + C可能导致立即终止进程.
这可能不是你想要的东西,因为诸如此类的事情可能会导致意外行为,甚至导致数据丢失.
当你将应用程序作为Payara或Apache Tomcat之类的Web服务器的一部分运行时,该Web服务器很可能会正常关闭。对于某些支持可运行应用程序的框架也是如此。例如,Spring Boot具有嵌入式Tomcat版本,可以有效地处理关机问题.
当你创建一个独立的Java应用程序或手动创建一个可运行的JAR时,你必须自己处理这些中断信号.
解决方案非常简单。添加一个退出钩子(hook ),如下面的示例所示。收到类似SIGINT信号后,优雅下线应用程序的进程将会被启动.
诚然,与Dockerfile相关的问题相比,这是一个通用的Web应用程序问题,但在容器环境中更重要.
8.使用 .dockerignore文件 。
为了防止不必要的文件污染git存储库,你可以使用.gitignore文件.
对于Docker镜像,我们有类似的东西-.dockerignore文件。类似于git的忽略文件,它是为了防止Docker镜像中出现不需要的文件或目录。同时,我们也不希望敏感信息泄漏到我们的Docker镜像中.
请参阅以下示例的.dockerignore:
使用.dockerignore文件的要点是:
9.确保Java版本支持容器 。
Java虚拟机(JVM)是一件了不起的事情。它会根据其运行的系统进行自我调整。有基于行为的调整,可以动态优化堆的大小。但是,在Java 8和Java 9等较旧的版本中,JVM无法识别容器设置的CPU限制或内存限制。这些较旧的Java版本的JVM看到了主机系统上的全部内存和所有CPU容量。Docker设置的限制将被忽略.
随着Java 10的发布,JVM现在可以感知容器,并且可以识别容器设置的约束。该功能UseContainerSupport是JVM标志,默认情况下设置为活动状态。Java 10中发布的容器感知功能也已移植到Java-8u191.
对于Java 8之前的版本,你可以手动尝试使用该-Xmx标志来限制堆大小,但这是一个痛苦的练习。紧接着,堆大小不等于Java使用的内存。对于Java-8u131和Java 9,容器感知功能是实验性的,你必须主动激活.
-XX:+ UnlockExperimentalVMOptions -XX:+ UseCGroupMemoryLimitForHeap 。
最好的选择是将Java更新到10以上的版本,以便默认情况下支持容器。不幸的是,许多公司仍然严重依赖Java8。这意味着你应该在Docker镜像中更新到Java的最新版本,或者确保至少使用Java 8 update 191或更高版本.
10.谨慎使用容器自动化生成工具 。
你可能会偶然发现适用于构建系统的出色工具和插件。除了这些插件,还有一些很棒的工具可以帮助你创建Java容器,甚至可以根据需要自动发布应用.
从开发人员的角度来看,这看起来很棒,因为你不必在创建实际应用程序时,还要花费精力维护Dockerfile.
这样的插件的一个例子是JIB。如下所示,我只需要调用mvn jib:dockerBuild命令可以构建镜像 。
它将为我构建一个具有指定名称的Docker镜像,而没有任何麻烦.
使用2.3及更高版本时,可以通过调用mvn命令进行操作:
- mvn spring-boot:build-image
在种情况下,系统都会自动为我创建一个Java镜像。这些镜像还比较小,那是因为他们正在使用非发行版镜像或buildpack作为镜像的基础。但是,无论镜像大小如何,你如何知道这些容器是安全的?你需要进行更深入的调查,即使这样,你也不确定将来是否会保持这种状态.
我并不是说你在创建Java Docker时不应使用这些工具。但是,如果你打算发布这些镜像,则应研究Java镜像所有方面的安全。镜像扫描将是一个好的开始。从安全性的角度来看,我的观点是,以完全控制和正确的方式创建Dockerfile,是创建镜像更好,更安全的方式.
原文链接:https://www.toutiao.com/a6959742944421200387/ 。
最后此篇关于构建Java镜像的10个优秀实践的文章就讲到这里了,如果你想了解更多关于构建Java镜像的10个优秀实践的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!