前端时间在工作时用到了jib插件,所以写一遍文章来记录使用中的问题。
简介
jib是谷歌开源的一款插件,可以将java程序构建成docker 镜像或oci镜像,我司是用来构建docker image镜像的。jib拥有maven 和 gradle 两个插件版本,我所使用的是 maven 版本。
谷歌官方是这样介绍jib的。
Jib builds optimized Docker and OCI images for your Java applications without a Docker daemon - and without deep mastery of Docker best-practices. It is available as plugins for Maven and Gradle and as a Java library.
GitHub地址:github.com/GoogleConta…
Docker file maven 插件
docker 的容器化操作为我们提供了不少便利,通过dockerfile文件可以构建一个和文件提供者一模一样的容器,这样就屏蔽因为依赖环境而带来的问题。下面将介绍目前知名的几款插件。
dockerfile-maven Github地址 github.com/spotify/doc…
不过此项目已经归档,不在维护
docker-maven-plugin Github地址 github.com/fabric8io/d… 这个项目目前还在持续更新中,star也有1.6k
jib Github地址 github.com/GoogleConta… 谷歌开源的,实力毋庸置疑,star有11.9k
dockerfile-maven因为已经归档不再更新了,所以并没有进行考虑,docker-maven-plugin 因为版本迭代的比较慢,所以最终选择了谷歌的jib,在我搞好了jib以后公司为了和老项目保持一致,又让我换成了docker-mavne-plugin,未来或许写一篇docker-maven-plugin插件的使用文章。
使用
jib插件无需写dokcerfile文件,所以参数都用pom中的参数来完成,可以像写dockerfile一样写pom文件。
jib中所有的参数都在readme文件中列出,所以在使用时遇到问题可以去读读readme文件,地址 github.com/GoogleConta…
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.1.4</version>
<configuration>
<from>
<image>harbor.xxxxx</image>
</from>
<to>
<image>harbor.xxx</image>
</to>
<container>
<workingDirectory>/application</workingDirectory>
<volumes>/application/log</volumes>
<environment>
<TZ>Asia/Shanghai</TZ>
</environment>
<ports>
<port>8084</port>
</ports>
<mainClass>com.glory.cms.Application</mainClass>
<entrypoint>
<arg>/bin/sh</arg>
<arg>-c</arg>
<arg>java ${JAVA_OPTS} -cp /app/resources/:/app/classes/:/app/libs/* com.glory.cms.Application</arg>
</entrypoint>
</container>
<allowInsecureRegistries>true</allowInsecureRegistries>
</configuration>
<executions>
<execution>
<id>build-image</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
- from 定义了依赖镜像 相当于dockerfile 中的
FROM - to 打包好后的镜像信息,jib在打包时可以使用maven内置变量
- workingDirectory 指定工作目录 相当于dockerfile 中的
WORKDIR - volumes 相当于 dockerdile 中的
VOLUME指定容器的目录挂载点 - environment 容器的环境变量,可以指定多个
- ports 容器要暴露的端口号
- mainClass 应用程序的主类,不使用时jib会接收spring传过来的{start-class}错误,详细问题参考issuse github.com/GoogleConta…
- entrypoint 相当于dockerfile中的 entrypoint,entrypoint没指定时jib将会输出默认的执行命令,格式是java -cp
- allowInsecureRegistries jib默认只接收https请求,如果要使用http请求的话需要将此参数设置为true
- skip 代表跳过jib执行,在某些情况下非常有用
绑定生命周期
你可能想在maven的某个生命周期中去执行jib命令,这个时候就需要在 executions 中进行配置了。
- phase 要绑定的maven生命周期
- goal 要执行的jib命令,可以设置多个
jib在创建镜像时会在根目录下创建/app文件夹
其中的两个文件是jvm启动参数,用于自定义entrypoint命令
/app/jib-classpath-file:Jib 将用于默认应用启动的运行时类路径/app/jib-main-class-file: 主类
启动命令
- (Java 9+)
java -cp @/app/jib-classpath-file @/app/jib-main-class-file - (shell)
java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )
公司的项目需要通过jvm参数区分不同的场景,所以需要自定义entrypoint命令。
["java", "-cp"]执行时不会对参数进行解析,但是["/bin/sh", "-c"]可以。因为要接收jvm参数,所以我定义了一个${JAVA_OPTS}环境变量,通过run命令执行时加上 -e JAVA_OPTS=""来接收自定义的jvm参数。除此之外还有-e "SPRING_PROFILES_ACTIVE=test"这种方式,但是我这边不知道为什么无法实现,所以取巧的用了 JAVA_OPTS这种方式。
问题
unable to find valid certification path to requested target
自签名证书不会被信任,jib官方提供了一个解决方案 github.com/GoogleConta…
import javax.net.ssl.*;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class InstallCert {
public static void main(String[] args) throws Exception {
String host;
int port;
char[] passphrase;
String[] h = {"harbor.ascent-ft.com"};//地址
if ((h.length == 1) || (h.length == 2)) {
String[] c = h[0].split(":");
host = c[0];
port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
String p = (h.length == 1) ? "changeit" : h[1];
passphrase = p.toCharArray();
} else {
System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
return;
}
File file = new File("jssecacerts");
if (file.isFile() == false) {
char SEP = File.separatorChar;
File dir = new File(System.getProperty("java.home") + SEP
+ "lib" + SEP + "security");
file = new File(dir, "jssecacerts");
if (file.isFile() == false) {
file = new File(dir, "cacerts");
}
}
System.out.println("Loading KeyStore " + file + "...");
InputStream in = new FileInputStream(file);
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(in, passphrase);
in.close();
SSLContext context = SSLContext.getInstance("TLS");
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
context.init(null, new TrustManager[]{tm}, null);
SSLSocketFactory factory = context.getSocketFactory();
System.out.println("Opening connection to " + host + ":" + port + "...");
SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
socket.setSoTimeout(10000);
try {
System.out.println("Starting SSL handshake...");
socket.startHandshake();
socket.close();
System.out.println();
System.out.println("No errors, certificate is already trusted");
} catch (SSLException e) {
System.out.println();
e.printStackTrace(System.out);
}
X509Certificate[] chain = tm.chain;
if (chain == null) {
System.out.println("Could not obtain server certificate chain");
return;
}
BufferedReader reader =
new BufferedReader(new InputStreamReader(System.in));
System.out.println();
System.out.println("Server sent " + chain.length + " certificate(s):");
System.out.println();
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest md5 = MessageDigest.getInstance("MD5");
for (int i = 0; i < chain.length; i++) {
X509Certificate cert = chain[i];
System.out.println
(" " + (i + 1) + " Subject " + cert.getSubjectDN());
System.out.println(" Issuer " + cert.getIssuerDN());
sha1.update(cert.getEncoded());
System.out.println(" sha1 " + toHexString(sha1.digest()));
md5.update(cert.getEncoded());
System.out.println(" md5 " + toHexString(md5.digest()));
System.out.println();
}
System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
String line = reader.readLine().trim();
int k;
try {
k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
} catch (NumberFormatException e) {
System.out.println("KeyStore not changed");
return;
}
X509Certificate cert = chain[k];
String alias = host + "-" + (k + 1);
ks.setCertificateEntry(alias, cert);
OutputStream out = new FileOutputStream("jssecacerts");
ks.store(out, passphrase);
out.close();
System.out.println();
System.out.println(cert);
System.out.println();
System.out.println
("Added certificate to keystore 'jssecacerts' using alias '"
+ alias + "'");
}
private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
private static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 3);
for (int b : bytes) {
b &= 0xff;
sb.append(HEXDIGITS[b >> 4]);
sb.append(HEXDIGITS[b & 15]);
sb.append(' ');
}
return sb.toString();
}
private static class SavingTrustManager implements X509TrustManager {
private final X509TrustManager tm;
private X509Certificate[] chain;
SavingTrustManager(X509TrustManager tm) {
this.tm = tm;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
throw new UnsupportedOperationException();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new UnsupportedOperationException();
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
this.chain = chain;
tm.checkServerTrusted(chain, authType);
}
}
}
按1可生成jssecacerts文件,将该文件放入**JAVA_HOME/jre/lib/security**目录下即可解决问题
参考文档
使用 Google Jib 构建 Java 容器 cloud.tencent.com/developer/a…
Jib构建镜像的问题分析(Could not find or load main class ${start-class}) www.cnblogs.com/bolingcaval…