最近搭建DroneCI的时候入坑了Docker,才发现这是个很超级厉害的容器引擎。

熟悉了一些基本的CLI之后,我就把所有服务都迁移到了Docker上,真的是太方便好用了,以后就是换服务器了,也能快速迁移和部署。

当然在使用的过程中还是遇到了一些坑,但只要查查官方文档和stackoverflow社区,就会发现这些都不是问题。

为了避免后面的朋友踩坑,我就记录一下我遇到的最实用的三个小技巧吧:

  1. 使容器可以输入文字到stdin
  2. 前台容器转到后台运行
  3. 限制单个容器的CPU资源

1.使容器可以输入文字到stdin

默认情况下,容器内部是关闭stdin输入流的。也就是说无法读取外部的输入。

我在尝试把mirai-console移到容器里时就出现了这个问题,mirai-console是个机器人程序,有时候需要用控制台执行一些管理命令之类的,可以理解为和minecraft服务器是一样的用法。

在外面启动就一切正常,一放到容器里就出现红字Closing input service...,查过源代码得知这个报错是因为stdin输入流遇到了eof异常,也就是没有任何字节可以读取。

这个问题我排查了很久,最后stackoverflow上给出了答案:给容器加上-it参数就能正常输入文字了。

有时候我们经常会这样用sudo docker run -it alpine /bin/sh,只有加上-it参数才能正常使用sh命令。

这里的-it参数全拼是--interactive--tty

查官方文档可得知,--interactive的作用是保持stdin的开启,就算没有attach也是如此

image-20220331195613950

另一个参数--tty解释起来就比较复杂了,简单点说就算创建一个虚拟的TTY环境(简称PTY),而不是直接运行子进程(裸运行)。

image-20220331195731527

之前我在做MShell插件的时候就遇到了这个PTY和直接运行子进程的区别,最明显的地方就是调用screen命令的时候,如果没有PTY环境,screen命令就无法获取窗口的字符宽高,也就启动不起来。窗口的字符宽高就算PTY环境特有的一个属性。

所以其实只加-i参数在容器里就已经能从stdin读取输入了,但一般为了兼容性,大家还是会不约而同的加上-t参数,合起来就是-it参数

关于PseudoTYY,可以参考这篇文章

如果在使用docker-compose.yml,则可以这样设置参数,和使用-it效果是一样的

version: "3.7"

services:
  mirai-qq-bot:
    image: openjdk:16-alpine
    restart: always
    working_dir: /app
    entrypoint: ["java", "-jar", "mcl.jar"]
    
    // 相当于 -i 参数
    stdin_open: true
    
    // 相当于 -t 参数
    tty: true

2.前台容器转到后台运行

有时启动容器的时候不小心忘了加-d(分离)参数,就会导致挂在前台,不能退出。

Ctrl + C快捷键虽然输入可以退出,但是会连着容器一起停止运行,虽然可以重新启动然后加上-d参数,但是有的容器启动和退出非常慢,非常的不方便。

有两个办法可以解决,一个是比较暴力的关闭窗口,一个是比较温柔的快捷键方法

首先说比较暴力的方法吧,很简单,直接叉掉ssh窗口使其会话强行终止,那么容器就会自己断开了,同时还不会停止运行。这一点和直接在ssh窗口里运行程序不一样。

另一个比较温柔的方法是使用快捷键Ctrl + P -> Q,即先按住Ctrl键,然后点击一下P键,不要松开Ctrl键,接着点一下Q键,那么就可以很优雅地断开容器了。

但是,这个方法有个前提条件,启动的时候必须要加上-it参数,否则按快捷键是无效的。只能使用前面比较暴力的办法退出了。像这样做也是迫不得已的,因为docker只提供了attach命令,没有提供detach命令,就只好出此下策了。

3.限制单个容器的CPU资源

有时候需要限制单个容器的CPU使用率,避免占用过多资源,对其它程序造成影响。

好在Docker也提供了这么一个功能,只需要传几个参数进去就可以实现CPU分配了。

具体的参数和平台有关,不同的平台有不同的参数(Windows,就你特殊!)

参数
docker run --cpu-count CPU count (Windows only)
docker run --cpu-percent CPU percent (Windows only)
docker run --cpu-period Limit CPU CFS (Completely Fair Scheduler) period
docker run --cpu-quota Limit CPU CFS (Completely Fair Scheduler) quota
docker run --cpus (API 1.25+ )Number of CPUs

核心方面:如果是Win平台,可以使用--cpu-count参数控制使用的核心数量。如果是非Win平台可以使用--cpus参数来控制可用的核心数

CPU使用率方面:如果是Win平台,可以使用--cpu-percent参数控制每个核心的最大使用率(最大应该是100,但我没有实际测试过)。非Win平台就比较麻烦一点,需要填写两个参数:--cpu-period--cpu-quota

这两个参数都是内核的CFS(完全公平调度器)算法的参数,CFS有很多参数,如果感兴趣可以参考这篇文章

限制CPU使用率原理大致是限制执行时间来实现的。给定的CPU时间用完以后,就会被节流(throttlling)直到分配周期结束,才会从新开始分配CPU时间。

如果有多个进行在运行时,具体的调度可以看下面的图(来源:Kubernetes生产实践系列之三十一:Kubernetes基础技术之CPU资源的调度和管理(CFS)

image-20220331203008074

对于大部分一些对CPU实时性不敏感的常规应用来说,--cpu-period一般设置为100000,也就是100ms,意思是每100ms为一个CFS分配周期。另一个参数--cpu-quota这表示在这个周期里,运行进程执行多少时间,单位也是微秒,比如给80000(80毫秒)。这两个参数设置好以后,就可以确定的该容器最大的单核CPU占用率为

80000/100000=0.8=80%80000 / 100000 = 0.8 = 80\%

如果想要限制单核使用率到40%,就只需要将--cpu-quota参数设置为40000就好了

结尾

好像一不小心又写的太硬核了,写着写着就开始介绍一些底层的东西了。

Docker是个很方便的东西,只要打包好了镜像,可以到处迁移。无论宿主机是什么系统都可以屏蔽差异。就连Windows也是如此,当Docker法线你电脑上安装了WSL之后,会直接用WSL来运行Docker,该不该夸Docker聪明呢?

对于一些能增加生产力的好东西吧,我总是比较畏惧,对各种东西抱有偏见,但是一旦被迫上手之后就发现再也离不开了。

之前因为写mirai插件入坑的kotlin语言,上手后才发现这语言是如此的厉害又方便,真的是爱不释手。

这次接触Docker也是因为要搭建一个DroneCI服务,但它又只支持Docker部署,没法学了下Docker。结果发现Docker也是如此的好用方便。以至于我直接把我7个项目全丢进了Docker里。不仅方便迁移,还能提供隔离的效果,减少恶意软件入侵宿主机的风险。

好了,Docker技巧就分享到这了,如果有机会,再继续做第二期的内容吧。