需求

之前工作流的运行都是用的docker-java提供的api拉起的docker容器直接跑服务,但是最新线上的新业务资源消耗较大,单个容器如果不加控制,CPU和内存都会拉满,导致服务器莫名宕机事故的发生,所以Docker限制cpu使用率和内存限制就得安排上

实施

HostConfig构建

自定义HostConfig,设置cpu和内存限制,pipeline配置了就按照配置来,如果没有就走默认配置

public void setUp() {
this.dockerHostConfig = new HostConfig();
Double memoryValue = this.pipeline.getMemory() != null
? this.pipeline.getMemory() * 1024 * 1024 * 1024
: this.config.getDefaultMemoryLimitInGb() * 1024 * 1024 * 1024;
this.dockerHostConfig.withMemory(memoryValue.longValue()); double cpu = StringUtils.isNotBlank(this.pipeline.getCpu())
? Double.parseDouble(this.pipeline.getCpu())
: this.config.getDefaultCpuCoreLimit();
// 单个 CPU 为 1024,两个为 2048,以此类推
this.dockerHostConfig.withCpuShares((int)(cpu * 1024));
}

CreateContainerCmd 构建

public String startContainer(String image,
String name,
List<ContainerPortBind> portBinds,
List<ContainerVolumeBind> volumeBinds,
List<String> extraHosts,
List<String> envs,
List<String> entrypoints,
HostConfig hostConfig,
String... cmds) {
List<Volume> volumes = new ArrayList<>();
List<Bind> volumesBinds = new ArrayList<>(); ……
……
…… CreateContainerCmd cmd = this.client.createContainerCmd(image)
.withName(name)
.withVolumes(volumes)
.withBinds(volumesBinds); if (portBinds != null && portBinds.size() > 0) {
cmd = cmd.withPortBindings(portBindings);
} if (cmds != null && cmds.length > 0) {
cmd = cmd.withCmd(cmds);
} if (extraHosts != null && extraHosts.size() > 0) {
cmd.withExtraHosts(extraHosts);
} if (envs != null) {
cmd.withEnv(envs);
} if (entrypoints != null) {
cmd.withEntrypoint(entrypoints);
} // 这一句是重点
cmd.withHostConfig(hostConfig); CreateContainerResponse container = cmd.exec();
this.client.startContainerCmd(container.getId()).exec();
return container.getId();
}

docker inspect containerId

执行 docker inspect a436678ccb0c 结果如下

"HostConfig": {
"Binds": [],
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {
"max-file": "3",
"max-size": "10m"
}
},
"NetworkMode": "default",
"PortBindings": null,
"RestartPolicy": {
"Name": "",
"MaximumRetryCount": 0
}
"CpuShares": 2048,
"Memory": 6442450944,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": null
}

CpuShares和Memory已经是我们设置的默认值,API生效,我们再来看下执行的日志

proc "pipeline_task_4b86c7830e4c4e39a77c454589c9e7e9_1" starting 2021-09-22 17:30:15 logPath:/mnt/xx/xx/logs/2021/09/22/bfbadf65-ac41-459d-a96d-3dc9a0105c25/job.log
+ java -jar /datavolume/xxx/xx.jar --spring.profiles.active=test
STDERR: Error: Unable to access jarfile /datavolume/xxx/xx.jar
5c494aeacb87af3a46a4fedc6e695ae888d4d2b9d7e603f24ef7fe114956c782 finished!
proc "pipeline_task_4b86c7830e4c4e39a77c454589c9e7e9_1" exited with status 1
proc "新增节点" error
start to kill all pipeline task
pipeline exit with error

执行文件没有找到,向上看Binds为空,所以挂载丢了,可以为什么了?明明 withVolumes()withBinds() 两个方法逻辑都没有动,还是看下源码分析一下吧

问题定位与解决

看源码之前我们先了解一下docker的hostConfig,文件路径在:/var/lib/docker/containers//hostconfig.json

其实这个就是容器运行的宿主机配置,磁盘绑定,cpu、内存限制、DNS、网络以及种种配置都在这个文件中,docker-java中HostConfig对象其实就是这个json对应的model,我们自定义了HostConfig对象,问题应当是出在 cmd.withHostConfig(hostConfig); 这一句代码上

以前的绑定逻辑

之前没有限制,所以在实例化CreateContainerCmd时候没有定制HostConfig参数

CreateContainerCmd cmd = this.client.createContainerCmd(image)
.withName(name)
.withVolumes(volumes)
.withBinds(volumesBinds);

CreateContainerCmd withBinds

/**
*
* @deprecated see {@link #getHostConfig()}
*/
@Deprecated
default CreateContainerCmd withBinds(Bind... binds) {
Objects.requireNonNull(binds, "binds was not specified");
getHostConfig().setBinds(binds);
return this;
}

getHostConfig() 方法追溯到实现类 CreateContainerCmdImpl hostConfig是直接在类实例化的时候new出来的一个新对象

@JsonProperty("HostConfig")
private HostConfig hostConfig = new HostConfig();

我们再看下 CreateContainerCmdwithHostConfig() 方法,代码也是在实现类里面

@Override
public CreateContainerCmd withHostConfig(HostConfig hostConfig) {
this.hostConfig = hostConfig;
return this;
}

直接覆盖了对象中原来的hostConfig, 我们的withHostConfig又在最后调用的可不就把挂载丢了吗,正好CreateContainerCmd 的 withBinds 方法也被 @Deprecated 修饰了,我们就来调整一下代码

public String startContainer(String image,
String name,
List<ContainerPortBind> portBinds,
List<ContainerVolumeBind> volumeBinds,
List<String> extraHosts,
List<String> envs,
List<String> entrypoints,
HostConfig hostConfig,
String... cmds) {
List<Volume> volumes = new ArrayList<>();
List<Bind> volumesBinds = new ArrayList<>(); …… //这一行很关键
hostConfig.withBinds(volumesBinds); if (portBinds != null && portBinds.size() > 0) {
hostConfig.withPortBindings(portBindings);
} if (extraHosts != null && extraHosts.size() > 0) {
hostConfig.withExtraHosts(extraHosts.toArray(new String[extraHosts.size()]));
}
CreateContainerCmd cmd = this.client.createContainerCmd(image).withHostConfig(hostConfig)
.withName(name)
.withVolumes(volumes); if (cmds != null && cmds.length > 0) {
cmd = cmd.withCmd(cmds);
} if (envs != null) {
cmd.withEnv(envs);
} if (entrypoints != null) {
cmd.withEntrypoint(entrypoints);
} CreateContainerResponse container = cmd.exec();
this.client.startContainerCmd(container.getId()).exec();
return container.getId();
};

OK,搞定,docker stats 查看容器的cpu占用,始终不会超过200%

参考链接

https://github.com/docker-java/docker-java

Docker-Java限制cpu和内存及浅析源码解决docker磁盘挂载失效问题的更多相关文章

  1. 方法:Linux 下用JAVA获取CPU、内存、磁盘的系统资源信息

    CPU使用率: InputStream is = null; InputStreamReader isr = null; BufferedReader brStat = null; StringTok ...

  2. 如何使用 Docker 来限制 CPU、内存和 IO等资源?

    如何使用 Docker 来限制 CPU.内存和 IO等资源?http://www.sohu.com/a/165506573_609513

  3. Linux下使用java获取cpu、内存使用率

    原文地址:http://www.voidcn.com/article/p-yehrvmep-uo.html 思路如下:Linux系统中可以用top命令查看进程使用CPU和内存情况,通过Runtime类 ...

  4. java中的==、equals()、hashCode()源码分析(转载)

    在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. ==  java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...

  5. Java的三种代理模式&完整源码分析

    Java的三种代理模式&完整源码分析 参考资料: 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.c ...

  6. Java 集合系列 09 HashMap详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  7. Java 集合系列 10 Hashtable详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  8. Java 集合系列 06 Stack详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  9. Java 集合系列 05 Vector详细介绍(源码解析)和使用示例

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

随机推荐

  1. WPF 窗口 最前端 Topmost Owner

    WPF 中,如果我们想把某个窗口一直置于最前端,那么可以设置Topmost=true; 但是,这样就会有另外一个问题,就时你这个窗口,会一直处于最顶层,即使你想切换到其他程序的时候. 比如,你自己写的 ...

  2. 【版本管理工具】git的介绍及常用命令总结

    1 git简介 1.1  git是什么? "Git 是一个分布式版本控制软件,与CVS.Subversion一类的集中式版本控制工具不同,它采用了分布式版本库的作法,不需要服务器端软件,就可 ...

  3. Int 2e 与 Sysenter区别

    参考:张银奎<软件调试>第八章 Int 2e: Windows将2e号向量专门用作系统调用,在启动早起初始化中断描述表时便注册好了适合的服务例程.因此当NtDll中的NtReadFile发 ...

  4. etcd raft 处理流程图系列3-wal的存储和运行

    存储和节点的创建 raftexample中的存储其实有两种,一个是通过raft.NewMemoryStorage()进行创建的raft.raftStorage,关联到单个raft节点,另一个是通过ne ...

  5. 虚拟dom?diff算法?key?Vue原理的核心三问?打包教你搞定。

    为什么需要虚拟DOM 先介绍浏览器加载一个HTML文件需要做哪些事,帮助我们理解为什么我们需要虚拟DOM.webkit引擎的处理流程,如下图所示: 所有浏览器的引擎工作流程都差不多,如上图大致分5步: ...

  6. java一些工具类

    import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java. ...

  7. ubuntu 设置简单密码

    ubuntu自带的修改密码界面要求比较长.比较复杂的密码.但通过命令行可以不受此限制. 用如下命令,按提示输入密码即可. sudo passwd username

  8. Spring笔记(3)

    一.JDBC Template基本使用 1.开发步骤 1.1直接使用template 导入spring-jdbc和spring-tx坐标 <!-- JDBC--> <dependen ...

  9. client-go实战之三:Clientset

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  10. 【曹工杂谈】Maven底层容器Plexus Container的前世今生,一代芳华终落幕

    Maven底层容器Plexus Container的前世今生,一代芳华终落幕 前言 说实话,我非常地纠结,大家平时只是用Maven,对于内部的实现其实也不关心,我现在非要拉着大家给大家讲.这就有个问题 ...