docker-java:用DockerClient的方式与主机交互

DockerClient使用

开启docker远程连接服务

docker默认只提供本地unix,sock文件的连接方式,让docker能够监听tcp端口还需要进行一些配置。

vim /lib/systemd/system/docker.service 
#配置普通模式,-H参数指定docker应用程序监听方式,或者采用下面的安全连接方式2选1
ExecStart=/usr/bin/dockerd -H unix://var/run/docker.sock -H tcp://0.0.0.0:2375
#配置安全连接,注意这里的/home/user/certs/ 根据自己情况替换为实际的的密钥存放路径 
ExecStart=/usr/bin/dockerd -D --tlsverify=true --tlscert=/home/user/certs/server-cert.pem --tlskey=/home/user/certs/server-key.pem --tlscacert=/home/user/certs/ca.pem -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock
#重载配置,重启服务
systemctl daemon-reload 
systemctl restart docker
#查看端口监听
netstat -nlp |grep 2375

如果目标registry只支持http的方式,修改docker默认使用的https的推送方式

#不存在就新建
vi /etc/docker/daemon.json
#配置insecure-registries
{
  "insecure-registries": ["122.12.12.12:5000"],
  "registry-mirrors": ["https://xxxxx.mirror.aliyuncs.com"]
}
#重载配置,重启服务
systemctl daemon-reload 
systemctl restart docker

引入依赖

#        <!-- https://mvnrepository.com/artifact/com.github.docker-java/docker-java -->
        <dependency>
            <groupId>com.github.docker-java</groupId>
            <artifactId>docker-java</artifactId>
            <version>3.3.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.github.docker-java/docker-java-transport-httpclient5 -->
        <dependency>
            <groupId>com.github.docker-java</groupId>
            <artifactId>docker-java-transport-httpclient5</artifactId>
            <version>3.3.0</version>
        </dependency>

工具类

import cn.hutool.core.util.ObjectUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.*;
import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.PullResponseItem;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
public class DockerUtil {


    private static volatile DockerClient dockerClient;

    private DockerUtil() {
    }

    private DockerUtil(String dockerHost, String dockerApiVersion, String dockerCertPath) {
        Objects.requireNonNull(dockerHost, "Docker 主机地址不能为空.");
        Objects.requireNonNull(dockerApiVersion, "Docker API 版本不能为空.");

        // 使用双重校验锁实现 Docker 客户端单例
        if (dockerClient == null) {
            synchronized (DockerUtil.class) {
                if (dockerClient == null) {
                    dockerClient = createDockerClient(dockerHost, dockerApiVersion, dockerCertPath);
                }
            }
        }
    }


    private DockerClient createDockerClient(String dockerHost, String dockerApiVersion, String dockerCertPath) {
        DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withApiVersion(dockerApiVersion)
                .withDockerHost(dockerHost)
                //如果开启安全连接,需要配置这行
                .withDockerTlsVerify(true).withDockerCertPath(dockerCertPath)
                .build();

        DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
                .dockerHost(config.getDockerHost())
                .sslConfig(config.getSSLConfig())
                .maxConnections(1000)
                .connectionTimeout(Duration.ofSeconds(60))
                .responseTimeout(Duration.ofMinutes(30))
                .build();

        return DockerClientImpl.getInstance(config, httpClient);
    }

    /**
     * 登录 Docker 镜像仓库
     *
     * @param authConfig 登录所需的认证信息
     * @throws RuntimeException 登录失败时抛出异常
     */
    public void login(AuthConfig authConfig) {
        try {
            Objects.requireNonNull(authConfig, "认证信息不能为空.");
            log.info("开始登录镜像仓库:{};username:{};password:{}", authConfig.getRegistryAddress(), authConfig.getUsername(), authConfig.getPassword());

            dockerClient.authCmd()
                    .withAuthConfig(authConfig)
                    .exec();
            log.info("镜像仓库登录成功:{}", authConfig.getRegistryAddress());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("镜像仓库登录失败:" + e.getMessage());
        }
    }

    /**
     * 从registry拉取Docker镜像
     *
     * @param tag 镜像名称
     * @return true表示拉取成功,false表示拉取失败
     */
    public void pullImage(AuthConfig authConfig, String tag) {
        Objects.requireNonNull(authConfig, "认证信息不能为空.");
        if (ObjectUtil.isEmpty(tag)) {
            throw new RuntimeException("镜像信息不能为空");
        }
        log.info("开始拉取 Docker 镜像: {}", tag);
        try { 
            PullImageResultCallback exec = new PullImageResultCallback() {
                @Override
                public void onNext(PullResponseItem item) { 
                    System.out.println(item.getStatus());
                }
            };
            PullImageCmd pullImageCmd = dockerClient.pullImageCmd(tag);
            pullImageCmd.withAuthConfig(authConfig)
                    .exec(exec)
                    .awaitCompletion(30, TimeUnit.MINUTES); 
            exec.close();
            log.info("镜像拉取成功:{};", tag);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("镜像拉取失败:{}" + e.getMessage());
        }
    }

    /**
     * 保存Docker镜像
     *
     * @param imageId  镜像Id
     * @param filePath 保存文件名
     * @return true表示保存成功,false表示保存失败
     */
    public void saveImage(java.lang.String imageId, String filePath) {

        if (ObjectUtil.isEmpty(filePath)) {
            throw new RuntimeException("参数错误:保存路径不能为空");
        }
        log.info("开始保存镜像:{}", imageId);
        try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(filePath));
             InputStream inputStream = dockerClient.saveImageCmd(imageId)
                     .exec()) {
            if (null == inputStream) {
                throw new RuntimeException("无法获取镜像");
            }
            byte[] bytesArray = new byte[4096];
            int bytesRead = -1;
            while ((bytesRead = inputStream.read(bytesArray)) != -1) {
                outputStream.write(bytesArray, 0, bytesRead);
            }
            log.info("镜像保存成功:{}", imageId);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("保存镜像异常:" + e.getMessage());
        }
    }

    /**
     * 删除Docker镜像
     *
     * @param imageId 镜像标签
     * @return true表示删除成功,false表示删除失败
     */
    public boolean removeImage(String imageId) {
        Objects.requireNonNull(imageId, "镜像 ID 不能为空.");
        log.info("开始删除 Docker 镜像: {}", imageId);
        try {
            // 如果镜像当前有容器在运行,则不进行删除操作
            if (isRunContainer(imageId)) {
                log.warn("Docker 镜像正在使用中,无法删除: {}", imageId);
                return false;
            }
            RemoveImageCmd removeImageCmd = dockerClient.removeImageCmd(imageId);
            removeImageCmd.exec();
            log.info("Docker 镜像删除成功: {}", imageId);
            return true;
        } catch (Exception e) {
            log.error("Docker 镜像删除失败: {};{}", imageId, e.getMessage());
            return false;
        }
    }

    /**
     * 获取所有 Docker 容器的信息
     *
     * @return 所有 Docker 容器的信息列表
     */
    public List<Container> listContainers() {
        log.info("开始获取所有 Docker 容器信息.");
        try {
            ListContainersCmd listContainersCmd = dockerClient.listContainersCmd();
            return listContainersCmd.exec();
        } catch (Exception e) {
            log.error("获取所有 Docker 容器信息失败: {}", e.getMessage());
            throw new RuntimeException("获取所有 Docker 容器信息失败: " + e.getMessage());
        }
    }

    /**
     * 是否有在运行的容器
     *
     * @param imageId
     * @return
     */
    public boolean isRunContainer(String imageId) {
        Objects.requireNonNull(imageId, "镜像 ID 不能为空.");
        log.info("检查 Docker 镜像是否正在使用中: {}", imageId);
        try {
            List<Container> containers = listContainers();
            List<String> containerNames = containers.stream()
                    .map(Container::getImageId)
                    .collect(Collectors.toList());
            log.info("列出所有容器成功,数量:{}", containerNames.size());
            if (ObjectUtil.isNotEmpty(containerNames) && containerNames.contains(imageId)) {
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("检查 Docker 镜像是否正在使用中失败: {}", e.getMessage());
            throw new RuntimeException("检查 Docker 镜像是否正在使用中失败: " + e.getMessage());
        }

    }

    /**
     * 推送镜像
     *
     * @param authConfig
     * @param tag
     */
    public static void pushImage(AuthConfig authConfig, String tag) {
        Objects.requireNonNull(authConfig, "认证信息不能为空.");
        Objects.requireNonNull(tag, "镜像信息不能为空.");
        log.info("开始推送 Docker 镜像: {}", tag);
        try {
            PushImageCmd pushImageCmd = dockerClient.pushImageCmd(tag);
            pushImageCmd.withAuthConfig(authConfig)
                    .start()
                    .awaitCompletion(30, TimeUnit.SECONDS);
            log.info("镜像push成功:{}", tag);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("镜像push失败:{}" + e.getMessage());
        }
    }

    /**
     * 获取镜像Id
     *
     * @param tag
     * @return
     */
    public String getImageId(String tag) {
        try {
            InspectImageCmd inspectImageCmd = dockerClient.inspectImageCmd(tag);
            InspectImageResponse image = inspectImageCmd.exec();
            if (null == image) {
                throw new RuntimeException("未获取到镜像信息:");
            }
            return image.getId();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("无法获取镜像信息:" + e.getMessage());
        }

    }

    // 使用 Builder 模式构建 DockerUtil 对象
     public static class Builder {

        private String dockerHost;
        private String dockerApiVersion;
        private String dockerCertPath;

        public Builder withDockerHost(String dockerHost) {
            this.dockerHost = dockerHost;
            return this;
        }

        public Builder withDockerApiVersion(String dockerApiVersion) {
            this.dockerApiVersion = dockerApiVersion;
            return this;
        }

        public Builder withDockerCertPath(String dockerCertPath) {
            this.dockerCertPath = dockerCertPath;
            return this;
        }

        public DockerUtil build() {
            return new DockerUtil(dockerHost, dockerApiVersion, dockerCertPath);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        String tag = "10.20.152.16:18080/test/redis:t1";
        AuthConfig authConfig = new AuthConfig()
                .withRegistryAddress("10.20.152.16:18080")
                .withUsername("admin")
                .withPassword("admin123");

        DockerUtil dockerUtil = new DockerUtil.Builder()
                //服务器ip
                .withDockerHost("tcp://10.50.80.165:2375")
                //API版本 可通过在服务器 docker version 命令查看
                .withDockerApiVersion("1.41")
                //安全连接密钥文件存放路径
                .withDockerCertPath("/home/user/certs/")
                .build();
        //登录
        dockerUtil.login(authConfig);

        /* DockerUtil.pushImage(authConfig , tag); */
        //拉取镜像
        dockerUtil.pullImage(authConfig, tag);
        String imageId1 = dockerUtil.getImageId(tag);

        //保存镜像
        dockerUtil.saveImage(imageId1, "E:\\pdfTest\\docker\\redis.tar");
        //删除镜像
        dockerUtil.removeImage(imageId1);
    }
}

开启安全连接的密钥制作参考

参考:https://blog.csdn.net/qq_36956154/article/details/82180551

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值