前言:
现在我们对“构建镜像的方法有哪些”大概比较着重,大家都想要学习一些“构建镜像的方法有哪些”的相关资讯。那么小编也在网络上收集了一些有关“构建镜像的方法有哪些””的相关资讯,希望朋友们能喜欢,你们快快来学习一下吧!关注留言点赞,带你了解最流行的软件开发知识与最新科技行业趋势。
了解如何为您的 GraalVM 本机映像创建 Docker 镜像,并发现它比您在创建 Docker 镜像时习惯的操作要复杂一些。
在本文中,您将学习如何为 GraalVM 原生镜像创建 Docker 镜像。通过一些动手实验,您将了解到它在创建 Docker 镜像时比您习惯的要复杂一些。享受!
介绍
在上一篇博文中,您学习了如何为 Spring Boot 3 应用程序创建 GraalVM 本机映像。如今,应用程序通常以 Docker 镜像的形式分发,因此验证 GraalVM 本机镜像是如何做到这一点很有趣。GraalVM 本机镜像不需要 JVM,那么您可以使用更简约的 Docker 基础镜像吗?您将在本博客期间执行一些实验,并边做边学。
本博客中使用的资源可在GitHub上找到。
GraalVM 文档中提供的信息是一个很好的学习起点。阅读此博客时,这是很好的参考资料。
作为示例应用程序,您将使用上一篇文章中的 Spring Boot 应用程序。该应用程序包含一个基本的RestController,它只返回一条hello消息。它还RestController包括一些代码,以便结合反射执行测试,但这部分是为上一篇文章添加的。
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
// return "Hello GraalVM!"
String helloMessage = "Default message";
try {
Class<?> helloClass = Class.forName("com.mydeveloperplanet.mygraalvmplanet.Hello");
Method helloSetMessageMethod = helloClass.getMethod("setMessage", String.class);
Method helloGetMessageMethod = helloClass.getMethod("getMessage");
Object helloInstance = helloClass.getConstructor().newInstance();
helloSetMessageMethod.invoke(helloInstance, "Hello GraalVM!");
helloMessage = (String) helloGetMessageMethod.invoke(helloInstance);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
return helloMessage;
}
}
构建应用程序:
$ mvn clean verify
从存储库的根目录运行应用程序:
$ java -jar target/mygraalvmplanet-0.0.1-SNAPSHOT.jar
测试端点:
$ curl
Hello GraalVM!
您现在已准备好对这个应用程序进行 Docker 化!
先决条件
此博客的先决条件是:
基本的 Linux 知识,这篇文章中使用的是 Ubuntu 22.04
基本的 Java 和 Spring Boot 知识
基本的 GraalVM 知识
基本的 Docker 知识
基础SDKMAN知识
为 Spring Boot 应用程序创建 Docker 镜像
在本节中,您将为 Spring Boot 应用程序创建一个 Dockerfile。这是一个非常基本的 Dockerfile,不会在生产代码中使用。请参阅以前的帖子“ Docker 最佳实践”和“ Spring Boot Docker 最佳实践”,了解生产就绪 Docker 映像的提示和技巧。您将使用的 Dockerfile 如下:
FROM eclipse-temurin:17.0.5_8-jre-alpine
COPY target/mygraalvmplanet-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
您使用包含 Java JRE 的 Docker 基础映像,将 JAR 文件复制到映像中,最后运行 JAR 文件。
构建 Docker 镜像:
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
验证镜像的大小。它的大小为 188MB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydeveloperplanet/mygraalvmplanet 0.0.1-SNAPSHOT be12e1deda89 33 seconds ago 188MB
运行 Docker 镜像:
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
...
2023-02-26T09:20:48.033Z INFO 1 --- [ main] c.m.m.MyGraalVmPlanetApplication : Started MyGraalVmPlanetApplication in 2.389 seconds (process running for 2.981)
如您所见,应用程序在大约 2 秒内启动。
再次测试端点。首先,找到您的 Docker 容器的 IP 地址。在下面的输出中,IP 地址是 172.17.0.2,但它可能是您机器上的其他地址。
$ docker inspect mygraalvmplanet | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.2",
"IPAddress": "172.17.0.2",
使用 IP 地址调用端点并验证它是否有效。
$ curl
Hello GraalVM!
为了继续,停止容器,删除它,同时删除镜像。每次实验后都这样做。这样,您可以确保每次都从干净的情况开始。
$ docker rm mygraalvmplanet
$ docker rmi mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
为 GraalVM 原生镜像创建 Docker 镜像
让我们对 GraalVM 本机图像做同样的事情。首先,切换到使用 GraalVM。
$ sdk use java 22.3.r17-nik
创建原生镜像:
$ mvn -Pnative native:compile
创建一个类似的 Dockerfile (Dockerfile-native-image)。这次,您使用不带 JVM 的 Alpine Docker 基础映像。您不需要 JVM 来运行 GraalVM 本机映像,因为它是可执行文件而不是 JAR 文件。
FROM alpine:3.17.1
COPY target/mygraalvmplanet mygraalvmplanet
ENTRYPOINT ["/mygraalvmplanet"]
构建 Docker 镜像,这次带有一个额外的--file参数,因为文件名与默认值不同。
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT --file Dockerfile-native-image
验证 Docker 镜像的大小。现在只有 76.5MB 而不是之前的 177MB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydeveloperplanet/mygraalvmplanet 0.0.1-SNAPSHOT 4f7c5c6a9b29 25 seconds ago 76.5MB
运行容器并注意它没有正确启动。
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
exec /mygraalvmplanet: no such file or directory
exec /mygraalvmplanet: 没有这样的文件或目录
这里有什么问题?为什么这不起作用?
这是一个模糊的错误,但 Alpine Linux Docker 映像使用musl作为标准 C 库,而 GraalVM 本机映像是使用使用 glibc 的 Ubuntu Linux 发行版编译的。
让我们将 Docker 基础映像更改为 Ubuntu。Dockerfile 是Dockerfile-native-image-ubuntu:
FROM ubuntu:jammy
COPY target/mygraalvmplanet mygraalvmplanet
ENTRYPOINT ["/mygraalvmplanet"]
构建 Docker 映像。
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT --file Dockerfile-native-image-ubuntu
验证 Docker 镜像的大小,现在是 147MB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydeveloperplanet/mygraalvmplanet 0.0.1-SNAPSHOT 1fa90b1bfc54 3 hours ago 147MB
运行容器,它在不到 200 毫秒的时间内成功启动。
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
...
2023-02-26T12:48:26.140Z INFO 1 --- [ main] c.m.m.MyGraalVmPlanetApplication : Started MyGraalVmPlanetApplication in 0.131 seconds (process running for 0.197)
基于 Distroless 镜像创建 Docker 镜像
使用 Ubuntu 基础镜像构建的 Docker 镜像大小为 147MB。但是,Ubuntu 映像确实包含很多不需要的工具。我们可以使用尺寸非常小的distroless镜像来减小镜像的大小吗?
创建一个 DockerfileDockerfile-native-image-distroless并使用一个 distroless 基础镜像。
FROM gcr.io/distroless/base
COPY target/mygraalvmplanet mygraalvmplanet
ENTRYPOINT ["/mygraalvmplanet"]
构建 Docker 映像。
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT --file Dockerfile-native-image-distroless
验证 Docker 镜像的大小,现在是 89.9MB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydeveloperplanet/mygraalvmplanet 0.0.1-SNAPSHOT 6fd4d44fb622 9 seconds ago 89.9MB
运行容器,发现它无法启动。distroless 镜像中似乎没有几个必要的库。
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
/mygraalvmplanet: error while loading shared libraries: libz.so.1: cannot open shared object file: No such file or directory
/mygraalvmplanet:加载共享库时出错:libz.so.1:无法打开共享对象文件:没有这样的文件或目录
在谷歌搜索此错误消息时,您会发现线程提到从其他图像(例如,Ubuntu 图像)复制所需的库,但您会遇到下一个错误和下一个错误。这是一条艰难的道路,需要花费一些时间。例如,请参阅此线程。
可以在此处找到使用 distroless 图像的解决方案。
基于 Oracle Linux 创建 Docker 镜像
另一种创建 Docker 镜像的方法可以在 GraalVM GitHub 页面上找到。在 Docker 容器中构建本机镜像,并使用多阶段构建来构建目标镜像。
正在使用的 Dockerfile 是从此处复制的,可以在存储库中找到 Dockerfile-oracle-linux。
新建文件Dockerfile-native-image-oracle-linux,将Dockerfile-oracle-linux的内容复制进去,修改如下:
更新 Maven SHA 和 DOWNLOAD_URL。
更改 L36 以便像以前一样编译本机映像:mvn -Pnative native:compile
更改 L44 和 L45 以复制和使用mygraalvmplanet本机映像。
生成的 Dockerfile 如下所示:
FROM ghcr.io/graalvm/native-image:ol8-java17-22 AS builder
# Install tar and gzip to extract the Maven binaries
RUN microdnf update \
&& microdnf install --nodocs \
tar \
gzip \
&& microdnf clean all \
&& rm -rf /var/cache/yum
# Install Maven
# Source:
# 1)
# 2)
ARG USER_HOME_DIR="/root"
ARG SHA=1ea149f4e48bc7b34d554aef86f948eca7df4e7874e30caf449f3708e4f8487c71a5e5c072a05f17c60406176ebeeaf56b5f895090c7346f8238e2da06cf6ecd
ARG MAVEN_DOWNLOAD_URL=
RUN mkdir -p /usr/share/maven /usr/share/maven/ref \
&& curl -fsSL -o /tmp/apache-maven.tar.gz ${MAVEN_DOWNLOAD_URL} \
&& echo "${SHA} /tmp/apache-maven.tar.gz" | sha512sum -c - \
&& tar -xzf /tmp/apache-maven.tar.gz -C /usr/share/maven --strip-components=1 \
&& rm -f /tmp/apache-maven.tar.gz \
&& ln -s /usr/share/maven/bin/mvn /usr/bin/mvn
ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2"
# Set the working directory to /home/app
WORKDIR /build
# Copy the source code into the image for building
COPY . /build
# Build
RUN mvn -Pnative native:compile
# The deployment Image
FROM docker.io/oraclelinux:8-slim
EXPOSE 8080
# Copy the native executable into the containers
COPY --from=builder /build/target/mygraalvmplanet .
ENTRYPOINT ["/mygraalvmplanet"]
构建 Docker 映像。放松,这需要相当长的时间。
$ docker build . --tag mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT -f Dockerfile-native-image-oracle-linux
此镜像大小为 177MB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mydeveloperplanet/mygraalvmplanet 0.0.1-SNAPSHOT 57e0fda006f0 9 seconds ago 177MB
运行容器,它会在 55 毫秒后启动。
$ docker run --name mygraalvmplanet mydeveloperplanet/mygraalvmplanet:0.0.1-SNAPSHOT
...
2023-02-26T13:13:50.188Z INFO 1 --- [ main] c.m.m.MyGraalVmPlanetApplication : Started MyGraalVmPlanetApplication in 0.055 seconds (process running for 0.061)
所以,这很好用。这是为 GraalVM 原生镜像创建 Docker 镜像的方法:
根据您的目标基础镜像准备一个 Docker 镜像;
安装必要的工具,在此应用程序中为 GraalVM 和 Maven;
使用多阶段 Docker 构建来创建目标图像。
结论
可以为您的 GraalVM 本机映像创建 Docker 映像,但您需要了解您在做什么。使用多阶段构建是最佳选择。根据是否需要使用distroless镜像缩小镜像大小,需要自己准备镜像来构建原生镜像。