404频道

学习笔记

常用垃圾回收方法

1.引用计数

类似于C++中的智能指针,每个对象维护一个引用计数,当对象被销毁时引用计数减一,当引用计数为0时立即回收对象。

在PHP、Python中使用,适用于内存比较紧张和实时性比较高的系统。

缺点:

  1. 由于频繁更新引用计数,降低了性能。
  2. 循环引用问题。解决办法为避免循环引用。

2.标记(mark)-清除(sweep)

分为标记和清除两个阶段,算法在70年代就提出了,非常古老的算法。

该算法有一个标记初始的root区域,和一个受控堆区。root区域主要是程序当前的栈和全局数据区域。

从root区域开始遍历所有被引用的对象,所有被访问到的对象被标记为“被引用”,标记完成后对未被引用的对象进行内存回收。

缺点:

标记阶段,都会Stop The World,会大大降低性能。

3.分代回收

将堆划分为两个或者多个代空间,新创建的对象存放于新生代,随着垃圾回收的重复执行,生命周期较长的对象会被放到老年代中。新生代和老年代采用不同的垃圾回收策略。

Go垃圾回收机制

Go中采用三色标记算法,本质上还是标记清除算法,但是对标记阶段有改进。

https://upload.wikimedia.org/wikipedia/commons/1/1d/Animation_of_tri-color_garbage_collection.gif

步骤如下:

  1. 开始时,所有的对象均为白色
  2. 从root开始扫描所有可达对象,标记为灰色,放入待处理队列。root包括了全局指针和goroutine栈上的指针。
  3. 从队列取出所有灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
  4. 重复3,直到灰色队列为空。
  5. 将白色对象进行回收

优点:用户程序和标记操作可以并行执行。

详细过程如下图:

通过上图可以看出,STW有两个过程:

  1. gc开始的时候,需要一些准备工作,如开启write barrier
  2. re-scan的过程

每个对象需要一个标记位,go中并未将标记位存放于对象的内存区域中,而是采用非侵入式的标记位。go单独使用了标记位图区域来对应内存中的堆区域。

write barrier: 在运行垃圾回收算法的同时,应用程序在一直执行,上文中提到了写屏障write barrier用于将这些内存的操作记录下来。

GC日志

GC日志对于定位问题还是比较方便的

1.开启GC日志

可以增加环境变量GODEBUG=gctrace=1来开启gc日志,gc日志会打印到标准错误中。例如有如下的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "time"

func main() {
for {
s := make([]int, 10)
for i := 0; i < 10000; i++ {
s = append(s, i)
}
time.Sleep(time.Nanosecond)
}
}

执行GODEBUG=gctrace=1 go run gc.go即可打印gc日志到标准错误中。

2.GC日志的含义

可以参照runtime

1
2
3
4
5
6
7
8
9
gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P
where the fields are as follows:
gc # the GC number, incremented at each GC
@#s time in seconds since program start
#% percentage of time spent in GC since program start
#+...+# wall-clock/CPU times for the phases of the GC
#->#-># MB heap size at GC start, at GC end, and live heap
# MB goal goal heap size
# P number of processors used

例子:

gc 11557 @179.565s 2%: 0.018+1.2+0.049 ms clock, 0.14+1.1/2.3/0.90+0.39 ms cpu, 13->14->7 MB, 14 MB goal, 8 P

3.GC的监控

对于线上GC的监控,基本上读取runtime.MemStats结构中的内容,然后存储到时序数据库中。具体有如下两种获取方式:

1
2
3
4
5
6
// 方式1
memStats := &runtime.MemStats{}
runtime.ReadMemStats(memStats)

// 方式2 json格式
expvar.Get("memstats").String()

references

古语有云,一年之计在于春,这句话对于很多植物而言再合适不过。在经历了寒冬之后,很多植物选择将最美好的一面在春天里绽放。

资源

1.PagerDuty

PagerDuty是一家Sass平台厂商,其产品为一款告警处理平台,提供了On-Call管理、告警收敛分组、告警时间报表,并集成了多种告警方式。

2.Fathom

https://github.com/usefathom/fathom/raw/master/assets/src/img/fathom.jpg?v=7

一款开源的简易网站数据分析工具,类似于Google Analytics或者百度分析。

3.Data Structure Visualizations

该网站将常见的计算机数据结构以可视化的形式展示在了界面上,可以在界面上点击按钮完成插入元素、删除元素等操作,对应的数据结构展现会实时发生变化,非常直观。

4.snowflake

https://user-gold-cdn.xitu.io/2018/2/11/16182507bcefae54?imageView2/0/w/1280/h/960/format/webp/ignore-error/1

Twitter开源的分布式算法,常用于分布式id的生成,使用毫秒时间戳、机器id、毫秒内的流水号来生成随机id。采用此种方法生成的id可以保证单机递增,但不能保证是全局递增的。

5.Netlify

很多人喜欢将自己的博客系统利用Github的Pages功能托管到Github上,但Github Pages并不支持对静态页面的构建,只能将构建完成后的页面推动到Github上。Netlify支持对静态网站的持续集成和持续部署,代码可以托管于Github上,Netlify会自动构建和发布,支持免费的https协议和CDN。

但可惜的是,实际测试下来,网站的访问速度在国内不是很理想。

6.996.ICU

全世界最大的同性交友社区Github上异常火爆的声讨996工作制度的项目,两天的时间内已经突破6万多Star,issue的数量也已经破万(截止到2019-03-28 19:55),要知道Github上Star数最多的项目freeCodeCamp也才接近30万Star,全世界运行设备最多的操作系统linux也才7万多Star,这简直创造了Github上Star数增长的奇迹。

很多人在issue中提到了加群交友吐槽、倾诉加班不满,甚至还有过来找男盆友的,活脱脱把issue玩成了贴吧,留言中清一色的汉字,说明基本是中国人在玩。

我个人对于强制996加班的事情不是很赞成,虽然过去三年中我的工作强度应该大于996,但更多的是出于个人自愿和对健康的无视,公司层面并没有强制要求。人生确实有非常多美好的事情可以参与和享受,对于程序员这个群体而言,电脑屏幕之外的世界还很大,还有太多的领域值得去探索和挖掘。但如果确实是因为个人的爱好,在工作中能够获得很好的成就感和满足感,996或者更高强度的加班,我个人觉得都是值得的。

说起ICU,程序员这个群的职业病是颈椎、腰椎、视力等,失眠多梦也是大有人在。我个人也确实身体出过一些问题而住过院,人往往都是在身体好的时候不懂得去重视自己的身体,当身体一旦出毛病的时候才懂得去珍惜。我曾经生病的时候也是鼓励自己要多锻炼和注意身体,但当身体好了之后,当时的愿望又抛到了脑后。

身体出现问题往往不是一朝一夕造成的,而是长期积累的结果,尤其是刚工作的前几年,趁着年轻确实能多加班熬夜,但30岁之后往往体力就跟不上了。还是奉劝各位,在工作的时候多注意休息和加强锻炼,无论是996,还是朝九晚五,都要多注意。

精彩文章

1.互联网运维工作

滴滴运维总监来伟在2017年对运维工作范围的思考,公司处在不同的阶段,运维所能干的事情也有所不同。

2.早点懂这几个道理,就不害怕被裁员了

IT行业中的一些职场规则,程序员在中年时期如果不做好职场转型,会逐步被更有活力更有体力的年轻人给取代。

golang中的panic用于异常处理,个人感觉没有try catch finally方式直观和易用。

func panic(v interface{})函数的作用为抛出一个错误信息,同时函数的执行流程会结束,但panic之前的defer语句会执行,之后该goroutine会立即停止执行,进而当前进程会退出执行。

func recover() interface{}定义在panic之前的defer语句中,用于将panic()进行捕获,这样触发panic时,当前gotoutine不会被退出。

recover所返回的内容为panic的函数参数,如果没有捕获到panic,则返回nil。

注意:recover仅能定义在defer中使用,在普通语句中无法捕获recover异常。recover可以不跟panic定义在同一个函数中使用。

example 1

1
2
3
4
5
6
7
8
9
import "fmt"

func main() {
defer fmt.Println(1)
fmt.Println(2)
panic(3)
fmt.Println(4)
defer fmt.Println(5)
}

panic()执行后,会先调用defer函数,然后打印panic: 3,当前goroutine退出,后续语句不再执行,程序输出:

1
2
3
4
5
6
7
8
2
1
panic: 3

goroutine 1 [running]:
main.main()
/Users/lvkai/src_test/go/panic/panic.go:8 +0xd5
exit status 2

example 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover: ", r)
}
}()
defer fmt.Println(1)
fmt.Println(2)
panic(3)
fmt.Println(4)
defer fmt.Println(5)
}()
fmt.Println(6)
}

在执行panic后,触发当前函数中的defer中的recover函数,此时panic后的当前函数中的语句同样是不再执行,但当前goroutine不会退出。也就是说panic被recover后,会影响到当前函数中的后续语句的执行,但不影响当前goroutine的继续执行,输出内容如下:

1
2
3
4
2
1
recover: 3
6

example 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover: ", r)
}
}()
func() {
defer fmt.Println(1)
panic(2)
}()
fmt.Println(3)
defer fmt.Println(4)
}()
fmt.Println(5)
}

recover跟panic定义在不同的函数中,仍然可以发挥作用。

1
2
3
1
recover: 2
5

春回大地,题图为即将融化的河面以及还在冰面上行走的路人。

资源

1.fastThread

在线的JVM线程栈分析工具,通过上传JVM Dump文件,在线查看线程分析结果。

2.全球空气质量地图

可以在线查看全球的PM2.5情况,很多国家的PM2.5都超过了200,但并不包含中国的数据,不知道是不是怕数据把其他国家吓死的缘故。

3.Walle

http://www.walle-web.io/docs/2/zh-cn/static/deploy-console.png

使用Python3开发的CI/CD平台,有相对友好的界面,目前Github Star数在6000+。

4.Træfɪk

为微服务而生的HTTP协议反向代理,自带API接口、dashboard,并支持Kubernetes Ingress、Mesos等,可动态加载配置文件等诸多nginx不具备的特性。

5.kcptun

https://github.com/xtaci/kcptun/blob/master/kcptun.png

基于KCP协议的UDP隧道,KCP协议能以比 TCP浪费10%-20%的带宽的代价,换取平均延迟降低 30%-40%。

6.k3s - 5 less than k8s

有人搞了个k3s项目,作为轻量级的k8s,整个二进制包只有40M大小。项目定位为边缘计算、IoT、CI等,支持多种硬件平台,裁剪了k8s的很多功能,比如云依赖,存储插件等,甚至连k8s依赖的etcd存储都默认更换为了sqlite3。

7.Drone

基于Golang的Container Native的CD平台,Github上star 17000+,插件的安装也是基于容器的。

8.kaniko

通过Dockerfile来构建docker镜像,需要dockerd进程的支持,这在物理机上操作没有任何问题。而如果要想在容器中通过Dockerfile构建docker镜像却变得困难起来,因为dockerd的运行需要root权限,而容器为了安全是不建议开启root权限的。

该工具可以在容器中不运行dockerd的情况下通过Dockerfile构建出docker镜像。

9.Nacos

阿里巴巴开源的微服务框架,支持配置中心、动态服务发现、动态DNS。

10.PowerDNS

https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png

Linux下除了bind外的另一个可选择的DNS服务器,数据存储在mysql中,还有一个可选择的漂亮UI。

精彩书籍

要想回顾一下过去的十年中都发生了哪些大事,中国发生了哪些变化,经济领域里有哪些大起大落,本书可以拿来一读。

最近突发奇想,想折腾一下路由器。经过研究半天后,锁定了网件R6400这款路由器,原因是可选择的系统较多,跟华硕的路由器架构一致,且支持较为强大的梅林系统。

整个刷机的过程还是非常简单的,虽然花了我不少时间,本文简单记录一下。

拿到手后,R6400比我印象中的要大不少,可以通过下图中的苹果手机进行对比。

R6400有v1和v2两个版本,其中v1版本的CPU频率为800MHz,v2版本的CPU频率为1GHz。v1版本的刷梅林系统和v2版本刷系统有所区别,v2版本需要先刷DD-WRT固件作为过渡固件,然后再刷梅林固件。

整个的过程最好用有线连接路由器操作,用无线会频繁掉线。

.chk结尾的文件为过渡固件,.trx为最终固件。

注意:下载的固件文件最好检验一下md5,确保固件的正确性。

刷dd-wrt固件

连接上路由器后,在chrome浏览器中输入http://www.routerlogin.net可跳转到网件管理系统,通过一堆路由器设置后会重启路由器,然后重新登录。

选择“是”后会下载新网件固件,其实该步骤可以选择“否”即可。固件下载完成后,会升级固件,路由器会自动重启。

下载DD-WRT固件文件“DD固件.chk”,并在路由器的管理界面“高级 -> 管理 -> 路由器升级”中上传固件。

这里选择是,然后开始升级固件,升级完成后路由器会重启。

重启完成后,Wifi信号变成dd-wrt,没有密码,可直接连接。

在浏览器中输入192.168.1.1,会出现dd-wrt的界面。用户名和密码可以直接输入admin,因为该系统仅为中间过度系统。在dd-wrt这个偏工程师化的系统中有非常详细的信息,包括路由器的CPU和Memory等的硬件信息,甚至还有load average,多么熟悉的指标。

升级梅林固件

在dd-wrt的固件升级中选择“R6400_380.70_0-X7.9.1-koolshare.trx”,刷入梅林固件。待路由器重启完成后,即完成梅林固件的刷入。此时路由器的Wifi SSID变为“NETGEAR”。

访问192.168.1.1,会出现梅林系统的管理界面,依次设置即可。

题外话:无线的密码修改完后,悲剧的事情发生了,路由器重启后居然连不上wifi,提示密码错误。不得不找来一台带有网口的笔记本用有线连接。在梅林管理系统中查看,未发现密码输入错误,明明输入的密码是对的,但SSID换一个密码居然奇迹般的可以无线连接了,怀疑是一个bug。

设置完成后路由器会重启,此时管理系统地址变更为192.168.50.1。

在“高级设置 -> 无线网络 -> 专业设置”中,调整区域为“United States”,据说可以加快速度。

要想使用软件中心,需要在系统设置中开启下图选项,并重启路由器。重启后,Format JFFS partition at next boot会自动设置为false。

ASUS Router

下载app “ASUS Router”,可以直接连接到路由器,这是因为网件的路由器架构跟华硕完全一致。

ref

现象

1
2
3
4
5
6
7
8
$ cat /etc/redhat-release
CentOS Linux release 7.2.1511 (Core)

$ uname -a
Linux c3-a05-136-45-10.com 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

$ docker info | grep "Storage Driver"
Storage Driver: devicemapper

在CentOS7.2的系统上,发现有一部分pod在delete后一直处于Terminating状态

1
2
3
4
5
6
7
8
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
httpserver-prod-1-6cb97dfbcc-25dsh 0/1 Terminating 0 55d <none> 10.136.45.6 <none>
httpserver-prod-1-6cb97dfbcc-f9flb 0/1 Terminating 0 54d <none> 10.136.45.4 <none>
httpserver-prod-1-6cb97dfbcc-m7sl4 0/1 Terminating 0 55d <none> 10.136.45.6 <none>
httpserver-prod-1-6cb97dfbcc-pqpht 0/1 Terminating 0 55d <none> 10.136.45.6 <none>
httpserver-prod-1-6cb97dfbcc-r987g 0/1 Terminating 0 55d <none> 10.136.45.4 <none>
httpserver-prod-1-6cb97dfbcc-zghhr 0/1 Terminating 0 54d <none> 10.136.45.6 <none>

查看docker的日志发现有如下报错信息如下,含义为在删除pod时由于/var/lib/docker/overlay/*/merged目录被其他应用占用,从而导致容器无法清除。

1
2
3
4
Jan 30 14:57:47 c3-a05-136-45-4.com dockerd[1510]: time="2019-01-30T14:57:47.704641914+08:00" level=error msg="Error removing mounted layer e6b7378c58a34cb42c6fa7924f7a52b7a19a64b2166d7a56f363e73ecba6e5a9: remove /var/lib/docker/overlay/98a56d695c9e3d0b6a9f3b5e0e60abf7cdb3ce73e976b00e36ca59028e585a36/merged: device or resource busy"
Jan 30 14:57:47 c3-a05-136-45-4.com dockerd[1510]: time="2019-01-30T14:57:47.704772288+08:00" level=error msg="Handler for DELETE /v1.31/containers/e6b7378c58a34cb42c6fa7924f7a52b7a19a64b2166d7a56f363e73ecba6e5a9 returned error: driver \"overlay\" failed to remove root filesystem for e6b7378c58a34cb42c6fa7924f7a52b7a19a64b2166d7a56f363e73ecba6e5a9: remove /var/lib/docker/overlay/98a56d695c9e3d0b6a9f3b5e0e60abf7cdb3ce73e976b00e36ca59028e585a36/merged: device or resource busy"
Jan 30 14:57:48 c3-a05-136-45-4.com dockerd[1510]: time="2019-01-30T14:57:48.228837657+08:00" level=error msg="Error removing mounted layer 2851b80d5c45d1cac3e7384116da0ad022af21701f9aa0d9ba3598efd5723030: remove /var/lib/docker/overlay/0ff0f98e1abf43c10711f2804cae3cf37efd597016d38b4753e2af19c2e27eb9/merged: device or resource busy"
Jan 30 14:57:48 c3-a05-136-45-4.com dockerd[1510]: time="2019-01-30T14:57:48.228953497+08:00" level=error msg="Handler for DELETE /v1.31/containers/2851b80d5c45d1cac3e7384116da0ad022af21701f9aa0d9ba3598efd5723030 returned error: driver \"overlay\" failed to remove root filesystem for 2851b80d5c45d1cac3e7384116da0ad022af21701f9aa0d9ba3598efd5723030: remove /var/lib/docker/overlay/0ff0f98e1abf43c10711f2804cae3cf37efd597016d38b4753e2af19c2e27eb9/merged: device or resource busy"

通过docker ps -a看到容器的状态为”Removal In Progress”。通过docker inspect可以看到容器的进程已经退出了。

1
2
3
4
5
6
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6b7378c58a3 golang-httpserver "/bin/sh -c 'go ru..." 7 weeks ago Removal In Progress k8s_golang-httpserver_httpserver-prod-1-6cb97dfbcc-f9flb_default_9e3d2cbb-f9d4-11e8-b61c-f01fafd10a1b_0

# docker inspect e6b7378c58a3 --format '{{.State.Pid}}'
0

使用docker rm命令删除容器会报错

1
2
# docker rm e6b7378c58a3
Error response from daemon: driver "overlay" failed to remove root filesystem for e6b7378c58a34cb42c6fa7924f7a52b7a19a64b2166d7a56f363e73ecba6e5a9: remove /var/lib/docker/overlay/98a56d695c9e3d0b6a9f3b5e0e60abf7cdb3ce73e976b00e36ca59028e585a36/merged: device or resource busy

通过kubectl delete pods命令虽然可以强制删除pod,但在宿主机上仍然能看到容器的状态为”Removal In Progress”。

1
2
3
# kubectl delete pods  httpserver-prod-1-6cb97dfbcc-f9flb --grace-period=0 --force
warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "httpserver-prod-1-6cb97dfbcc-f9flb" force deleted

通过搜索挂载目录的信息,可以找到是哪个进程挂载了该目录。可以看到是ntpd服务挂载了该目录。

1
2
3
4
5
6
7
8
9
10
# grep -nr 98a56 /proc/*/mountinfo
/proc/2725007/mountinfo:48:296 183 0:183 / /var/lib/docker/overlay/98a56d695c9e3d0b6a9f3b5e0e60abf7cdb3ce73e976b00e36ca59028e585a36/merged rw,relatime shared:88 - overlay overlay rw,lowerdir=/var/lib/docker/overlay/5e2a5f7af24e555a5afacd6a8faa406b42c51d7f2bb4cde22adcea22e0153583/root,upperdir=/var/lib/docker/overlay/98a56d695c9e3d0b6a9f3b5e0e60abf7cdb3ce73e976b00e36ca59028e585a36/upper,workdir=/var/lib/docker/overlay/98a56d695c9e3d0b6a9f3b5e0e60abf7cdb3ce73e976b00e36ca59028e585a36/work

# ps -ef | grep 2725007
ntp 2725007 1 0 Jan07 ? 00:00:02 /usr/sbin/ntpd -u ntp:ntp -g

# ntpd进程的启动时间在容器启动之后
# ps -ef | grep ntpd
root 1179644 18205 0 19:52 pts/1 00:00:00 grep --color=auto -d skip -i ntpd
ntp 3853149 1 0 Jan07 ? 00:00:02 /usr/sbin/ntpd -u ntp:ntp -g

查看ntpd.service文件内容如下,其中PrivateTmp=true,该选项用于控制服务是否使用单独的tmp目录:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Network Time Service
After=syslog.target ntpdate.service sntp.service

[Service]
Type=forking
EnvironmentFile=-/etc/sysconfig/ntpd
ExecStart=/usr/sbin/ntpd -u ntp:ntp $OPTIONS
PrivateTmp=true

[Install]
WantedBy=multi-user.target

问题复现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 在系统上启动一个容器,此时ntpd必须处于running状态
$ docker run -d httpserver:1 /bin/sh -c "while : ; do sleep 1000 ; done"

# 启动容器
$ docker run -d httpserver:1 /bin/sh -c "while : ; do sleep 1000 ; done"
200222b438aac43bbe32a6c54e31ced0848482b9dec3e519d2f847c70c1ce801

# 重启ntpd
$ systemctl restart ntpd

$ docker stop 200222b438aa

# 此时容器的相关信息还存在
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
200222b438aa httpserver:1 "/bin/sh -c 'while..." About a minute ago Exited (137) 7 seconds ago hardcore_yalow

# 强制删除容器失败
$ docker rm -f 200222b438aa
Error response from daemon: driver "devicemapper" failed to remove root filesystem for 200222b438aac43bbe32a6c54e31ced0848482b9dec3e519d2f847c70c1ce801: remove /var/lib/docker/devicemapper/mnt/e53342aa9cf5f43e73b6596f88939b8d3fdefaf1ca03ee95a24d867e1de6c522: device or resource busy


# 此时容器处于Removal In Progress状态
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
200222b438aa httpserver:1 "/bin/sh -c 'while..." 2 minutes ago Removal In Progress hardcore_yalow

# 再次重启ntpd进程
$ systemctl restart ntpd

# 强制删除成功
$ docker rm 200222b438aa
200222b438aa

经在如下版本的CentOS7系统实验,该问题不存在。

1
2
3
4
5
6
7
8
$ uname -a
Linux localhost.localdomain 3.10.0-862.9.1.el7.x86_64 #1 SMP Mon Jul 16 16:29:36 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

$ cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)

$ docker info | grep "Storage Driver"
Storage Driver: overlay2

问题产生原因

此问题为Systemd启用PrivateTmp选项后,导致mount namespace的一处内核bug。

处理方式

在/usr/lib/systemd/system/docker.service的[Service]中增加MountFlags=slave,并重新启动docker服务,注意重启docker后,容器会重启。

当然也可以通过重启ntpd服务的方式来临时解决问题,但当下次删除容器时还需要重启ntpd。

还有一种办法是修改ntpd.service中的PrivateTmp=true,然后重启ntpd服务。

ref

Docker 故障(device or resource busy)

Linux中的Buffer与Cache的含义通常非常容易混淆,两者翻译成中文都可以叫做缓存,都是数据在内存中的临时存储,而且网络上很多文章都是错误的。

1
2
3
4
$ free -h
total used free shared buff/cache available
Mem: 125G 12G 347M 9.3M 113G 113G
Swap: 0B 0B 0B

free命令直接将buff和cache写到了一块,说明两者有很多共同点。

1
2
3
4
5
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
7 1 0 364076 18664 118624552 0 0 214 11198 106 118 6 4 89 1 0
13 1 0 349096 18664 118638192 0 0 0 1012404 171031 270124 20 13 66 2 0

而通过vmstat命令可以分别看到buffer和cache的大小,单位为KB。

使用man free命令看到的解释如下:

1
2
3
buffers: Memory used by kernel buffers (Buffers in /proc/meminfo)

cache: Memory used by the page cache and slabs (Cached and Slab in /proc/meminfo)

查看proc的man手册结果如下:

1
2
3
4
5
6
7
8
9
10
11
Buffers %lu
Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).

Cached %lu
In-memory cache for files read from the disk (the pagecache). Doesn't include SwapCached.

SReclaimable %lu (since Linux 2.6.19)
Part of Slab, that might be reclaimed, such as caches.

SUnreclaim %lu (since Linux 2.6.19)
Part of Slab, that cannot be reclaimed on memory pressure.

上述信息,文档写的并不是非常明确。

可以看出buffers是磁盘数据的缓存,通常不会特别大,缓存的数据包括磁盘的写请求和读请求。内核用于将分散的写磁盘操作集中起来,批量写入磁盘。

Cached是文件数据的缓存,同样可以缓存读请求和写请求。

Slab包括了SReclaimalbe和Sunreclaim两部分信息,其中SReclaimable是可回收部分,SUnreclaim是不可回收部分。

关于文件和磁盘的区别如下:

磁盘是一个块设备,可以划分为多个分区,每个分区上可以构建不同的文件系统,文件系统挂载到目录上后,就可以对该文件系统进行读写文件操作了。

读写普通文件系统中的文件时,会经过文件系统,由文件系统跟磁盘进行交互,而文件系统的缓存为cache。读写磁盘或者分区时,会跳过文件系统,直接对磁盘进行操作,而操作系统对磁盘的缓存称之为buffer。

ref

富士山&富士吉田市,富士山的海拔高达3776米,远在80公里外的东京都能够看到。令人称奇的是,富士山海拔3360米以上的土地并不是归日本政府所有,而是归富士山上的浅间寺所有,日本政府每年都要支付大量的租金给浅间寺。在富士山周边游览后,突然萌生了登顶富士山的想法,不知是否有志同道合的驴友,可以相约在某年的夏季去一起实现梦想。

资源

1.CRIU

Linux下的一款实现checkpoint/restore功能的软件,该软件可以冻结某个正在运行的应用程序,并将应用程序的当前状态作为checkpoint存放在磁盘上的文件中,此后正在运行的应用程序会被kill。

此后,可以通过读取磁盘上的文件,恢复之前冻结的应用程序继续执行,而不是从main函数开始执行。

2.bindfs

将一个目录mount到另外一个目录的工具,利用该命令可以将docker中的路径挂载到宿主机上。具体操作命令类似如下:

1
2
3
4
PID=$(docker inspect b991b7ad105f --format {{.State.Pid}})
bindfs /proc/$PID/root /tmp/root
# 别忘了卸载目录
umount /tmp/root

3.微软亚洲研究院-对联电脑

微软亚洲研究院的自动对对联系统,给出上联后,可以自动给出多个下联,最终生成横批。

4.bcc

基于Linux eBPF的一系列的性能分析工具,包括IO、网络等多个方面。

5.pcstat

基于golang开发的linux下的文件缓存统计工具。

6.Electron

利用前端技术(JavaScript、HTML、CSS)来构建桌面程序的框架,当前很多流行的桌面应用都是使用该技术来开发的,比如VSCode、Slack、Atom等技术。

得益于ES6、V8引擎和Node.js,JavaScript技术已经横跨前端、后端、桌面端的技术栈。

7.Reading-and-comprehense-linux-Kernel-network-protocol-stack

该项目包含了对Linux网络协议栈的源码中文注释,对阅读Linux网络协议栈的代码有一些帮助。

精彩文章

  1. Kubernetes API 与 Operator:不为人知的开发者战争(一)
  2. Kubernetes API 与 Operator:不为人知的开发者战争(二)

精彩语句

  1. “不能用”“不好用”“需要定制开发”,这才是落地开源基础设施项目的三大常态。

– 张磊《深入剖析Kubernetes》

开源项目在落地到公司内部实际使用时,会发现有这样或者那样的问题。开源项目往往是个通用项目,公司在落地时,总有其特殊需求之处,开源软件无法面面俱到,往往只能覆盖一些通用的需求。再加上靠社区来驱动,在bug方面、功能方面跟商业软件也还有较大差距。

娱乐

1.《塞尔达传说-旷野之息》

任天堂Switch上的游戏神作,历时四年时间,300人的团队开发,最近一直在玩,已经深深被游戏设计的海拉鲁大陆所折服,完全开放的世界,不同于传统的闯关类游戏,该游戏的自由度非常高,有时候就单纯的在地图中瞎逛都是一种享受,随时都会有惊喜发生。

曾天真的以为,一个单机游戏能好玩到哪里去,但在玩游戏的每一刻都能体会到制作团队的用心,心里总是念到这才是我想要玩的游戏。自从玩了该游戏后,手机上的游戏再也没有打开过。我甚至一度感叹,在国内快糙猛的环境下是产生不了如此细腻良心作品的。如果大家有机会,可以尝试下这款游戏,或许会发现单机游戏还可以做得如此出彩。

2.ZELDA MAPS

同样是跟《塞尔达传说-旷野之息》相关的,由于塞尔达传说的地图实在过于庞大,包含了神庙、驿站、村庄、回忆(没错主人公Link失忆了)、各种支线任务、装备、呀哈哈、各类大小boss、迷宫等等,有玩家制作了一款在线的地图,可以在线查询地图中的各类元素,使用体验类似Google Map。还包含了账号体系,可以在地图上标记自己已经完成的任务。

在Dockerfile中ENTRYPOINT与CMD的功能类似,同时再加上docker run后面追加的容器启动参数,是极其容易混淆的。而且又掺杂着exec模式和shell模式。

这里先说几个结论,有了结论再跟进下面的例子来理解会更容易一些:

  1. 实际上docker容器进程的完整启动参数为ENTRYPOINT CMD,如果没有指定ENTRYPOINT,docker会提供一个隐式的值/bin/sh -c
  2. docker run后面跟的容器启动参数仅会覆盖CMD部分。

exec模式与shell模式

CMD和ENTRYPOINT两个命令均支持exec模式和shell模式。

exec模式格式类似CMD [ "top" ],当容器启动时,top命令的进程号为1。

为了能够获取到环境变量,通常的写法为CMD [ "sh", "-c", "echo $HOME" ],此时1号进程为sh。

shell模式的写法为CMD top,docker会以/bin/sh -c top的方式来执行命令,此时容器的1号进程为sh。

如果需要容器进程处理外部信号的情况下,shell模式下信号实际上时发送给了sh,而不是容器中的应用进程。

因此比较推荐使用exec模式,shell模式实际使用较少。

CMD

  • CMD [“param1”, “param2”] 为ENTRYPOINT提供默认参数,需要指定ENTRYPOINT
  • CMD [“executable”,”param1”,”param2”] exec模式
  • CMD command param1 param2 shell模式

CMD为容器提供默认的启动命令,如果在启动容器时通过命令行指定了的启动参数,则该启动参数会覆盖CMD默认的启动参数。

ENTRYPOINT

不能被docker run增加的参数覆盖,启动时要执行ENTRYPOINT的参数。

  • ENTRYPOINT [“executable”, “param1”, “param2”] exec模式
  • ENTRYPOINT command param1 param2 shell模式

exec模式

当为exec模式时,容器启动时,在命令行上添加的参数会被追加到ENTRYPOINT的参数列表中。

例如:

1
2
FROM ubuntu:latest
ENTRYPOINT [ "echo", "hello" ]

执行docker run --rm 0d89e8d4425a world,会输出hello world

shell模式

当ENTRYPOINT为shell模式时,docker run启动后追加的参数会被忽略。

例如:

1
2
3
FROM ubuntu:latest

ENTRYPOINT echo hello

执行docker run --rm 0841e19b4d2e world仅输出hello

ENTRYPOINT命令的覆盖

ENTRYPOINT的命令可以通过docker run中增加--entrypoint选项来使用命令行中指定的参数覆盖ENTRYPOINT的参数。

ENTRYPOINT与CMD的组合使用

当同时指定CMD和ENTRYPOINT模式时,实际上为ENTRYPOINT CMD

1
2
3
4
FROM ubuntu:latest

ENTRYPOINT [ "echo", "hello" ]
CMD [ "world" ]

docker run --rm 7edf658370d9会输出hello world,而docker run --rm 7edf658370d9 kitty会输出hello kitty

更复杂的情况可以参照下图:

https://docs.docker.com/engine/reference/builder/#understand-how-cmd-and-entrypoint-interact

如何查看ENTRYPOINT和CMD

可以通过docker history ${image} --no-trunc来查生成镜像的所有Dockerfile命令

ref

题图为中国铁道博物馆东郊馆中的毛泽东号列车

资源

1.Hawkular

Hawkular为RedHat开源的监控解决方案,实现语言为java,监控数据的底层存储引擎使用Cassandra,包含了告警功能。目前Github上的Star还较少。RedHat的OpenShift就使用了该监控方案。

2.Kong

基于Nginx OpenResty的API网关,支持自定义插件,支持比原生nginx更多的功能。

3.NuoDB

弹性可伸缩的关系型数据库,兼容SQL标准。将数据库中的事务和存储进行了分离,存储层支持多种存储系统,比如文件系统、Amazon S3和HDFS。因为存储层可以是外部的存储,意味着NuoDB的扩展性会大大增强,使其部署到Kubernetes成为了比较容易的事情。

4.Linux命令hping3

hping3是一个用于生成和解析tcp/ip协议的工具,能够对数据包进行定制,可用于端口扫描、DDOS攻击等,是一个比较常见的黑客工具。

5.Firecracker

Amazon开源的轻量级的虚拟机软件,使用KVM来创建和管理虚拟机,整体架构类似Kata Container。容器采用cgroup和namespace来做资源隔离,但是在安全性方面却比较差,轻量级的虚拟机在做到隔离性的同时,又提供了不错的启动速度,是容器领域的一个发展方向。

6.NginxConfig.io

NginxConfig.io是一款在线生成nginx配置文件的工具,可以通过点点鼠标,在文本框中内容的方式轻松生成nginx的配置文件。

7.Caddy

一款实用Go语言编写的负载均衡工具,默认启用HTTPS服务,可以使用Let’s Encrypt来自动签发证书。配置文件的写法也比nginx要简洁。

8.loki

Grafana团队最新发布的基于Go语言开发的日志聚合系统,loki不会对日志进行全文索引,而是以压缩聚合的方式进行存储,可以对日志流通过打标签的方式进行分组,页面的展示直接使用grafana。对Kubernetes Pod中的log做了特别的支持,比较适合抓取和存储Kubernetes Pod中的log。

个人感觉该工具未来会很火爆,尤其是跟Grafana有着无缝的整合。很多公司会使用ES来作为日志中心的底层存储,但不见得所有的服务都有按照关键字进行匹配搜索的需求,ES作为日志中心就显得不够高效和经济。

9.JSON-RPC

json-rpc是rpc通讯中的一种json格式标准,该协议要求request和response的内容必须为json格式,且json有固定的格式。

10.KSQL

Apache Kafka的开源SQL引擎,可以使用SQL的形式查询kafka中的消息,该产品跟Kafka一样,同样为Confluent出品。

精彩文章

1.北京五环外的真实中国

朋友圈刷屏文章,文章以gif动画的形式描述了社会底层人士的艰辛生活,他们背上扛起的不仅是压得直不起腰来的砖头,而是面对困难努力生活的勇气,有些时候为了生计确实没得选择。

当我们在抱怨生活的同时,可以想想比我们更苦更累却默默承受生活之重的人们,或许心里会好受些。

书籍

1.《深入解析Go》

从底层角度分析go语言实现,推荐所有golang开发者一看。

2.深入浅出Serverless:技术原理与应用实践

http://images.china-pub.com/ebook8050001-8055000/8054378/zcover.jpg

要想能够对Serverless技术的概念和现状有所了解,该书还是挺合适的。

该书介绍了公有云上的Serverless产品AWS Lambda、Azure Functions,开源项目OpenWhisk、Kubeless、Fission和OpenFasS,提供对这些技术的一站式了解。

0%