假设你已经与容器镜像打交道超过一分钟了。在这种情况下,你可能很熟悉那些无处不在的文件,它们逐层描述构建镜像所需的步骤。Dockerfiles。你知道吗,现在有越来越多的工具可以在没有Dockerfiles的情况下构建符合OCI的镜像?在这篇文章中,我们将看看Jib,这是一个100%基于Java的工具,它可以构建高度优化的镜像,而不必担心正确形成Dockerfile的问题。
我将假设你已经了解了镜像构建,并且至少对Dockerfile有基本的了解,但如果你是新手,你可能想看看我关于开发者驱动的工作流程的文章,在那里我深入了解了容器镜像构建的细节。
问题所在
作为一个Java开发者,除了你的应用代码库和库的依赖性之外,你可能还习惯于跟踪你所针对的JDK和JVM。这可能包括你正在构建的Web应用服务器版本,以及一些运行时配置,如垃圾收集器调整和数据库连接。你很可能不需要处理操作系统层面的问题,如软件包安装、文件系统权限、运行时使用的UID/GID,以及其他此类配置细节,这些问题历来都是由为你提供平台的运营和安全团队处理。
随着容器运行机制和位于其之上的协调系统的采用,这些低级别的问题正以基础设施即代码(IaC)配置文件的形式转移到开发者团队的范围内,这些配置文件是应用程序/服务代码库的一部分。这些文件有多种形式,如Kubernetes YAML、Terraform HCL、CloudFormation JSON,当然还有Dockerfiles,它定义了应用程序运行的容器镜像的结构。
将你的应用程序打包成一个镜像并在容器中运行,通常不是一件非常困难的事情。但是,确保镜像的格式良好、优化、安全,并符合你的组织标准,可能是一个挑战。开发人员现在的任务是学习操作系统级别的扫描工具,维护镜像的标签标准,学习最新的Dockerfile最佳实践,以及其他被要求拥有的东西。有一些刷新和扫描工具,如Hadolint、Dockle,以及我们自己的Snyk容器产品,可以帮助你完成这些任务。但是,如果我们能把这些新的要求和模板内容的大部分--如果不是全部--自动化呢?
进入Jib。一个100%基于Java的容器镜像构建工具
Jib是一个开源的、100%的Java工具,可以在没有Docker文件甚至没有容器运行时间的情况下构建符合OCI(Docker v2)的容器镜像。Jib可以作为独立的CLI工具使用,但最常用的是作为Maven或Gradle构建步骤插件。使用该插件,你可以简单地运行你已经用于构建**.NET的相同的mvn 或gradle 构建命令。jar,.**war今天,你也可以使用相同的构建命令来构建--也可以选择部署--一个OCI镜像,准备在容器中运行你的应用程序。
从Spring Boot应用开始,添加Jib就像在Maven pom.xml文件中插入这部分XML一样简单(完整的文档在这里),然后重新运行mvn package 。我们可以在这里设置几个关于我们希望容器放置位置的选项。为简单起见,本例将镜像部署到我们稍后要使用的镜像注册表上,但你也可以将镜像放置到本地的 **.**tar文件,或者,如果你碰巧有一个Docker守护进程在运行并且可以访问的话,也可以把镜像放到Docker的本地镜像缓存中(就像Docker构建那样)。
<build>
<plugins>
...
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<to>
<image>reg.mycorp.com/smalls/spring-goof</image>
</to>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
$mvn package
...
[INFO] --- jib-maven-plugin:3.1.1:build (default) @ spring-goof ---
[WARNING] 'mainClass' configured in 'maven-jar-plugin' is not a valid Java class: ${start-class}
[INFO]
[INFO] Containerizing application to localhost:5000/spring-goof...
[WARNING] Base image 'adoptopenjdk:8-jre' does not use a specific image digest - build may not be reproducible
[WARNING] The credential helper (docker-credential-desktop) has nothing for server URL: localhost:5000
[WARNING]
Got output:
credentials not found in native keychain
[WARNING] Cannot verify server at https://localhost:5000/v2/. Attempting again with no TLS verification.
[WARNING] Failed to connect to https://localhost:5000/v2/ over HTTPS. Attempting again with HTTP.
[INFO] The base image requires auth. Trying again for adoptopenjdk:8-jre...
[WARNING] The credential helper (docker-credential-desktop) has nothing for server URL: registry-1.docker.io
[WARNING]
Got output:
credentials not found in native keychain
[WARNING] The credential helper (docker-credential-desktop) has nothing for server URL: registry.hub.docker.com
[WARNING]
Got output:
credentials not found in native keychain
[INFO] Using credentials from Docker config (/Users/eric/.docker/config.json) for adoptopenjdk:8-jre
[INFO] Using base image with digest: sha256:117fae95422c19f1c1ddfb0f869913c1d934547e8eb903738a9fd2c3ad11a207
[INFO]
[INFO] Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, org.snyk.groceries.SpringGoofApplication]
[INFO]
[INFO] Built and pushed image as localhost:5000/spring-goof
[INFO] Executing tasks:
[INFO] [==============================] 100.0% complete
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.169 s
[INFO] Finished at: 2021-06-28T14:43:49-05:00
[INFO] ------------------------------------------------------------------------
注意:在上述输出中,有一些有趣的 "警告 "行,我们将在稍后讨论。
现在,在一台有容器运行时的机器上,简单地运行容器。docker run --rm -it -p 8080:8080 myimage:tag
$ docker run --rm -it -p8080:8080 reg.mycorp.com/smalls/spring-goof
Unable to find image reg.mycorp.com/smalls/spring-goof:latest' locally
latest: Pulling from spring-goof
c549ccf8d472: Pull complete
bd7766c75e8f: Pull complete
7e80a3d8823a: Pull complete
a7321fbff05c: Pull complete
05d4865ff251: Pull complete
e8d1ce8a5389: Pull complete
bc56aad8a781: Pull complete
Digest: sha256:74710d3c27ad84cb594b84a519c111a2b04a611ca52ac81f644e3ab5e15d0063
Status: Downloaded newer image for reg.mycorp.com/smalls/spring-goof:latest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.5.RELEASE)
2021-06-28 19:54:51.550 INFO 1 --- [ main] o.snyk.groceries.SpringGoofApplication : Starting SpringGoofApplication on beaa3641ca38 with PID 1 (/app/classes started by root in /)
如果你是Gradle用户,请查看官方文档,了解如何在你的build.gradle文件中做同样的事情的全部信息。
这就是全部,不需要Docker文件或额外的工具来构建镜像,只需要使用你已经在使用的构建Java工件的相同构建工具。这不仅简化了开发者的生活,而且还使CI构建代理支持变得更加容易,因为不需要暴露Docker套接字或管理其他构建工具。
挖掘镜像
好的,所以图像构建过程可能更容易,但是,如果你像我第一次看到它时一样,你可能有这样的问题。
Jib是如何构建图像的?
就像任何镜像构建工具一样,Jib创建了一组文件系统层,其中包含Java运行时、你的应用程序和它的所有依赖项,以及容器引擎用来知道如何启动JVM的元数据。
下面是这个例子的层信息,由docker image history 命令呈现。
$ docker image history localhost:5000/spring-goof:latest
IMAGE CREATED CREATED BY SIZE COMMENT
4c125b59f147 51 years ago jib-maven-plugin:3.1.1 79B jvm arg files
<missing> 51 years ago jib-maven-plugin:3.1.1 6.36kB classes
<missing> 51 years ago jib-maven-plugin:3.1.1 0B resources
<missing> 51 years ago jib-maven-plugin:3.1.1 30MB dependencies
<missing> 10 days ago /bin/sh -c #(nop) ENV JAVA_HOME=/opt/java/o… 0B
<missing> 10 days ago /bin/sh -c set -eux; ARCH="$(dpkg --prin… 108MB
<missing> 10 days ago /bin/sh -c #(nop) ENV JAVA_VERSION=jdk8u292… 0B
<missing> 10 days ago /bin/sh -c apt-get update && apt-get ins… 43.2MB
<missing> 10 days ago /bin/sh -c #(nop) ENV LANG=en_US.UTF-8 LANG… 0B
<missing> 10 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 10 days ago /bin/sh -c #(nop) ADD file:920cf788d1ba88f76… 72.7MB
注意CREATED ,前4层的日期都是 "51年前"。 这是Jib的可重复构建策略的一个副作用,该策略旨在为针对同一代码库的构建创建完全相同的层哈希值。关于这个的更多细节,请查看他们的FAQ。
从层注释中可以看出,我们有一堆来自AdoptOpenJDK默认基础镜像的层--下面会有更多介绍--后面依次是。
- 你的Maven/Gradle文件中定义的库的依赖性
- 资源文件
- 编译的应用程序代码中的类文件
- Java JVM参数文件。
再深入一点,使用开源工具dive,让我们看看这四层中的每一层都有哪些文件。
依赖关系
这一层包含将所有的.jar 文件添加到/app/libs 文件夹中。
│ Layers ├──────────────────────────────────────────────────────────────────── ┃ ● Current Layer Contents ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Cmp Size Command Permission UID:GID Size Filetree
73 MB FROM 679f8666b5733b3 drwxr-xr-x 0:0 30 MB └── app
43 MB apt-get update && apt-get install -y --no-install-recommends t drwxr-xr-x 0:0 30 MB └── libs
108 MB set -eux; ARCH="$(dpkg --print-architecture)"; case "${ARC -rw-r--r-- 0:0 445 kB ├── antlr-2.7.7.jar
30 MB jib-maven-plugin:3.1.1 -rw-r--r-- 0:0 1.9 MB ├── aspectjweaver-1.8.10.jar
0 B jib-maven-plugin:3.1.1 -rw-r--r-- 0:0 65 kB ├── classmate-1.3.3.jar
6.4 kB jib-maven-plugin:3.1.1 -rw-r--r-- 0:0 314 kB ├── dom4j-1.6.1.jar
79 B jib-maven-plugin:3.1.1 -rw-r--r-- 0:0 13 kB ├── evo-inflector-1.2.2.jar
-rw-r--r-- 0:0 1.8 MB ├── h2-1.4.196.jar
│ Layer Details ├───────────────────────────────────────────────────────────── -rw-r--r-- 0:0 75 kB ├── hibernate-commons-annotations-5
-rw-r--r-- 0:0 5.6 MB ├── hibernate-core-5.0.12.Final.jar
Tags: (unavailable) -rw-r--r-- 0:0 612 kB ├── hibernate-entitymanager-5.0.12.
Id: da844eca910f44e825c72121f4ec9700b3c9eec8d4c2407f926cdad0799b33e8 -rw-r--r-- 0:0 113 kB ├── hibernate-jpa-2.1-api-1.0.0.Fin
Digest: sha256:0862e7d5215a0ec2cf7d5a8864a5a0439d9c70c39b14cdc6c38f41487ca8f23 -rw-r--r-- 0:0 726 kB ├── hibernate-validator-5.3.5.Final
Command: -rw-r--r-- 0:0 56 kB ├── jackson-annotations-2.8.0.jar
jib-maven-plugin:3.1.1 -rw-r--r-- 0:0 283 kB ├── jackson-core-2.8.9.jar
资源
在这里我们看到/app/resources 和所有来自我们资源文件夹的相关文件。
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Current Layer Contents ├────────────────────────────────────────────────────
Cmp Size Command Permission UID:GID Size Filetree
73 MB FROM 679f8666b5733b3 drwxr-xr-x 0:0 0 B └── app
43 MB apt-get update && apt-get install -y --no-install-recommends t drwxr-xr-x 0:0 0 B └── resources
108 MB set -eux; ARCH="$(dpkg --print-architecture)"; case "${ARC -rw-r--r-- 0:0 0 B ├── application.properties
30 MB jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 0 B └── org
0 B jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 0 B └── snyk
6.4 kB jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 0 B └── groceries
79 B jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 0 B ├── domain
drwxr-xr-x 0:0 0 B └── repository
│ Layer Details ├─────────────────────────────────────────────────────────────
Tags: (unavailable)
Id: 7891783fbddf2ac3f624ecdea6fd7d51efa476bf87b82a3a1da2517167a7812a
Digest: sha256:bc7cee3aeb381d0b453212f345eaf34f55613c2dbb988af22f626877f4ecc89
Command:
jib-maven-plugin:3.1.1
类别
来自编译阶段的.class 文件在这一层中的/app/classes 。
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Current Layer Contents ├────────────────────────────────────────────────────
Cmp Size Command Permission UID:GID Size Filetree
73 MB FROM 679f8666b5733b3 drwxr-xr-x 0:0 6.4 kB └── app
43 MB apt-get update && apt-get install -y --no-install-recommends t drwxr-xr-x 0:0 6.4 kB └── classes
108 MB set -eux; ARCH="$(dpkg --print-architecture)"; case "${ARC drwxr-xr-x 0:0 6.4 kB └── org
30 MB jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 6.4 kB └── snyk
0 B jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 6.4 kB └── groceries
6.4 kB jib-maven-plugin:3.1.1 -rw-r--r-- 0:0 3.3 kB ├── SpringGoofApplicati
79 B jib-maven-plugin:3.1.1 drwxr-xr-x 0:0 1.7 kB ├── domain
-rw-r--r-- 0:0 1.7 kB │ └── Item.class
│ Layer Details ├───────────────────────────────────────────────────────────── drwxr-xr-x 0:0 1.3 kB └── repository
-rw-r--r-- 0:0 1.3 kB └── ItemRepository.
Tags: (unavailable)
Id: 5d4509a5f5856f5d6de92ed621d301861fccf414f9088d0c81e47572f48e4194
Digest: sha256:778c99f6b1c637ab73e3fff99e933d6d6a8d247f9849d2695066c4f2e823ee7
Command:
jib-maven-plugin:3.1.1
JVM Arg文件
┃ ● Layers ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ Current Layer Contents ├────────────────────────────────────────────────────
Cmp Size Command Permission UID:GID Size Filetree
73 MB FROM 679f8666b5733b3 drwxr-xr-x 0:0 79 B └── app
43 MB apt-get update && apt-get install -y --no-install-recommends t -rw-r--r-- 0:0 39 B ├── jib-classpath-file
108 MB set -eux; ARCH="$(dpkg --print-architecture)"; case "${ARC -rw-r--r-- 0:0 40 B └── jib-main-class-file
30 MB jib-maven-plugin:3.1.1
0 B jib-maven-plugin:3.1.1
6.4 kB jib-maven-plugin:3.1.1
79 B jib-maven-plugin:3.1.1
│ Layer Details ├─────────────────────────────────────────────────────────────
Tags: (unavailable)
Id: 22877091a2de894cac9070c121b116e5d3247b3a4f81774032c0698bf6a2de2f
Digest: sha256:d8a8afa0a3d1efdb00b38b28e3dd075342c0dc65e52977e56dd3f21c277ae94
Command:
jib-maven-plugin:3.1.1
最后,我们有几个文件,包含了在顶层/app 文件夹中的容器中启动JVM时使用的参数。在这个例子中,如果你要检查这两个文件的内容,你会发现运行时classpath和主类信息。
# cat jib-classpath-file
/app/resources:/app/classes:/app/libs/*
# cat jib-main-class-file
org.snyk.groceries.SpringGoofApplication
**注意:**根据你的Maven/Gradle构建结构,你可能会在Jib生成的镜像中遇到更多层次。关于其他可能的层的更多信息,请参见Jib FAQ。
Jib使用什么基础镜像,如果我需要使用自己的镜像怎么办?
默认情况下,Jib在构建JAR文件时使用DockerHub官方approopenjdk:jre-8基础镜像,在构建WAR时使用DockerHub官方jetty基础镜像。这可以通过插件的配置进行配置。详情请参见他们的官方文档,包括设置自定义ENTRYPOINT, USER 或其他声明(如果需要)。同样的文档也谈到了为什么设置你自己的基础镜像和使用特定的哈希值是一个好主意,以便执行构建的可重复性。许多组织都有内部 "祝福 "的基础镜像,开发团队必须使用这些镜像,这些镜像已经过安全和运营团队的审核和加固。下面是一个如何修改Maven pom.xml以使用这种镜像的例子。
<build>
<plugins>
...
<plugin>
...
<configuration>
<from>
<image>reg.mycorp.com/smalls-base/openjdk:8u292-2021.7.4</image>
</from>
...
</configuration>
...
</plugin>
...
</plugins>
</build>
还支持标准化镜像标签等其他设置。比方说,你的组织要求所有镜像包含以下标签。
- 源代码 Git网址
- Git提交的id/hash
- Maven项目构建版本
假设pom.xml已经可以访问这些数据,那么添加标签的Jib插件配置就非常简单。
<build>
<plugins>
...
<plugin>
...
<configuration>
...
<container>
<labels>
<git.remote.origin.url>${git.remote.origin.url}</git.remote.origin.url>
<git.commit.id>${git.commit.id}</git.commit.id>
<mvn.build.version>${project.version}</mvn.build.version>
</labels>
</container>
...
</configuration>
...
</plugin>
...
</plugins>
</build>
检查创建的图像,我们可以看到标签的应用,就像它们是通过DockerfileLABELS 行添加的一样(我在这里使用奇妙的jq命令行工具,从响应中提取该阵列)。
$ docker image inspect reg.mycorp.com/smalls/spring-goof:latest | jq .[].Config.Labels
{
"git.commit.id": "960d768e9739ffb8e9a503c9ad3f6dad86ac68b1",
"git.remote.origin.url": "git@github.com:mycorp-dev/spring-goof.git",
"mvn.build.version": "0.0.1-SNAPSHOT"
}
现在,想象一下,这些标准化配置的所有复杂性都通过<pluginManagement> 和其他通常的Maven配置在父POM中定义。开发人员不必再担心,甚至不必再看这些模板式的XML,架构师可以在整个组织内执行和更新标准,而不必麻烦他们的团队。
我怎样才能确保事情是安全的?
我们面临的一个挑战是,像Dockerfile这样的一般镜像构建工具,你可以做几乎任何你想做的事情,作为构建的一部分,包括安装包,使用ADD从随机的网络服务器下载文件,以及无数其他需要审查和检查的步骤。在Jib中,这些选择很多都是自动应用的,覆盖它们需要明确的Maven/Gradle配置变更,这在代码审查中是显而易见的,就像改变依赖关系或任何其他构建配置一样。
至于安全扫描,Snyk Container提供的一个伟大功能是建议使用漏洞较少的不同基础镜像,从今天起,即使没有Docker文件来参考你的镜像有哪些基础,该功能现在也能发挥作用。这意味着由Jib或任何非Dockerfile工具构建的镜像可以利用同样的建议。
如果你想跟随并扫描你自己的Java容器,请创建一个免费账户并获得关于如何安装Snyk扫描工具的说明。
保护你的Java容器
免费扫描您的Java容器的安全漏洞。
在这个例子中,我把我的基本镜像设置为openjdk:8u121-jre ,运行mvn package ,并把镜像拉到我的笔记本电脑上。现在我将简单地对它运行一个snyk container test 。
$ snyk container test localhost:5000/spring-goof:latest
Testing localhost:5000/spring-goof:latest...
... (a bunch of scan results removed here) ...
Organization: mycorp-snyk-org
Package manager: deb
Project name: docker-image|reg.mycorp.com/smalls/spring-goof:latest
Docker image: reg.mycorp.com/smalls/spring-goof:latest
Platform: linux/amd64
Base image: openjdk:8u181-jre-stretch
Licenses: enabled
Tested 261 dependencies for known issues, found 410 issues.
Base Image Vulnerabilities Severity
openjdk:8u181-jre-stretch 410 149 high, 91 medium, 170 low
Recommendations for base image upgrade:
Minor upgrades
Base Image Vulnerabilities Severity
openjdk:8-jre-stretch 205 71 high, 36 medium, 98 low
Major upgrades
Base Image Vulnerabilities Severity
openjdk:11.0.5-jre-stretch 178 66 high, 28 medium, 84 low
Alternative image types
Base Image Vulnerabilities Severity
openjdk:17-ea-22-oraclelinux8 0 0 high, 0 medium, 0 low
openjdk:16-jdk-oraclelinux7 0 0 high, 0 medium, 0 low
openjdk:17-ea-27-jdk-oraclelinux7 0 0 high, 0 medium, 0 low
openjdk:16-ea-29-jdk-oraclelinux8 0 0 high, 0 medium, 0 low
正如你所看到的,扫描自动检测到openjdk:8u181-jre-stretch 作为基本镜像(那是对openjdk:8u181-jre 的别名标签)并报告了它的410个漏洞。它还提出了一些改变基本镜像的建议,包括从一个小的版本升级到最新的openjdk:8-jre 标签--它有大约一半的问题--一直到完全没有漏洞的较新的JVMs。
所以你可以看到,我们不仅可以发现我们正在使用的镜像中存在哪些漏洞,而且我们还得到了如何通过较新的基础镜像来解决这些漏洞的建议,所有这些都不需要编写一行Dockerfile代码。
总结
正如你所看到的,Jib可以通过消除对学习Dockerfile语法或安装不熟悉的工具的需要,使开发者的Java应用容器化变得更加容易。它还可以帮助架构师通过众所周知的Maven或Gradle项目层次结构设施来管理标准化。因为Jib构建了符合OCI的镜像,你也可以利用行业标准的工具--如Snyk容器扫描器--来检查、部署和运行你的应用程序。