Google Jib插件使用

1,652 阅读3分钟

前端时间在工作时用到了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传过来的startclass参数,但是当应用中存在多个main时需要指定mainClass,否则会爆couldnotfindorload{start-class}参数,但是当应用中存在多个main时需要指定mainClass,否则会爆could not find or load {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文件夹

1

其中的两个文件是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…