404频道

学习笔记

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,提供对这些技术的一站式了解。

time_wait状态

客户端在收到服务器端发送的FIN报文后发送ACK报文,并进入TIME_WAIT状态,等待2MSL(最大报文生存时间)后才断开连接,MSL在Linux中值为30s。

之所以设计time_wait主要用来解决以下异常场景:

  1. 确保对端处于关闭状态。主动断开连接一段发送最后一个ack报文,如果丢失,被动断开连接一端会重新发送fin报文。如果主动断开连接一方直接关闭,被动方会一直处于last-ack状态。
  2. 防止上一个连接中的包影响新的连接,上一个连接中的包在2MSL中一定可以到达对端。

过多的危害:在客户端占用过多的端口号

time_wait过多的解决思路

  1. net.ipv4.tcp_max_tw_buckets值调小,当TIME_WAIT的数量到达该值后,TIME_WAIT状态会被清除,相当于没有遵守tcp协议
  2. 修改TCP_TIMEWAIT_LEN的值,但需要重新编译内核,非常不建议修改
  3. 打开tcp_tw_recycle和tcp_timestamps
  4. 打开tcp_tw_reuse和tcp_timestamps
  5. 采用长连接

tcp有个tcp时间戳选项,第一个是发送方的当前时钟时间戳(4个字节),第二个4字节为从远程主机接收到的最新时间戳

相关内核参数

net.ipv4.tcp_max_tw_buckets

(integer; default: see below; since Linux 2.4) The maximum number of sockets in TIME_WAIT state allowed in the system. This limit exists only to prevent simple denial-of-service attacks. The default value of NR_FILE*2 is adjusted depending on the memory in the system. If this number is exceeded, the socket is closed and a warning is printed.

系统中允许的 time_wait 数量的最大值,当达到最大值后,新的连接会被拒绝。

net.ipv4.tcp_tw_timeout

time_wait 的超时时间,默认为 2MSL,即 60s,在大部分的 linux 系统下该值没法修改,仅在某些 OS 系统下可用。比如:Alibaba Cloud Linux 修改TCP TIME-WAIT超时时间

tcp_timestamp

用来控制tcp option字段,发送方在发送报文时会将当前时钟的时间值放入到时间戳字段。

net.ipv4.tcp_tw_reuse

(Boolean; default: disabled; since Linux 2.4.19/2.6) Allow to reuse TIME_WAIT sockets for new connections when it is safe from protocol viewpoint. It should not be changed without advice/request of technical experts.

tcp_tw_reuse意思为主动关闭连接的一方可以复用之前的time_wait状态的连接。

复用连接后,这条连接的时间更改为当前时间,延迟数据到达时,延迟数据时间小于新连接时间。

需要连接双方都打开timestamp选项。

该选项适用的范围为作为客户端主动断开连接,复用客户端的time_wait的状态,对服务端无影响。

net.ipv4.tcp_tw_recycle

内核会在一个RTO的时间内快速销毁掉time_wait状态,RTO时间为数据包重传的超时时间,该时间通过RTT动态计算,远小于2MSL。

需要连接双方都打开timestamp选项。

适用场景为服务端主动断开连接,time_wait状态位于服务端,服务端适用该选项快速回收time_wait状态的连接。

弊端:如果客户端在NAT网络中,如果配置了tcp_tw_recycle,可能会出现在一个RTO的时间内,只有一个客户端和自己连接成功的情况。

4.10之后,Linux内核修改了时间戳生成机制,该选项已经抛弃。

In Action

解决time_wait状态过多的比较好的思路为采用http的keepalive功能。

nginx

nginx对于upstream,默认是使用http1.0协议的,要想启用keepalive,需要在location中增加

1
2
proxy_http_version 1.1;
proxy_set_header Connection "";

在upstream中增加keepalive参数,这里的参数含义为每个nginx worker连接所有后端的最大连接数。

1
keepalive 200;

如果keepalive连接过少,此时由于使用的是http1.1的协议,upstream端不会主动断开连接,nginx会主动断开连接,此时nginx端的time_wait就会过多,会占用端口号,导致nginx端没有端口号可以使用。

引用

限流的方式有多种,每种都有其应用场景。

限制请求的方式包括:

  1. 丢弃请求
  2. 放在队列中,等有令牌后再请求
  3. 走降级逻辑

计数器

我之前设计的流控系统,以每秒为单位,如果一秒内超过固定的QPS,则将请求进行降级处理。该算法已经在生产环境中平稳运行了很久,也确实满足了业务的需求。

计数器流控算法简单粗暴,有一个缺点,即流控的单位为秒,但一秒的请求很可能是不均匀的,不能进行更细粒度的控制,也不允许流量存在某种程度的突发。

漏桶算法

请求先进入漏桶中,漏桶以一定的速度出水,当水流的速度过大时会直接溢出。

漏桶大小:起到缓冲的作用

漏桶的出水速度:该值固定

令牌桶算法

令牌桶算法相比漏桶算法而言,允许请求存在某种程度的突发,常用于网络流量整形和速率限制。

系统会恒定的速度往令牌桶中注入令牌,如果令牌桶中的令牌满后就不再增加。新请求来临时,会拿走一个令牌,如果没有令牌就会限制该请求。

这里的请求可以代表一个网络请求,或者网络的一个字节。

涉及到的变量:

  1. 网络请求平均速率r:每隔1/r秒向令牌桶中放入一个令牌,1秒共放入r个令牌
  2. 令牌桶的最大大小:令牌桶慢后,再放入的令牌会直接丢弃

令牌相当于操作系统中信号量机制。

业界较为出名的流控工具当属Guava中的RateLimiter,基于令牌桶算法实现。

在实际的代码实现中,并不一定需要一个固定的线程来定期往令牌桶中放入令牌,而是在请求到来时,直接计算得出当前是否还有令牌。比如下面的python代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time


class TokenBucket(object):

# rate是令牌发放速度,capacity是桶的大小
def __init__(self, rate, capacity):
self._rate = rate
self._capacity = capacity
self._current_amount = 0
self._last_consume_time = int(time.time())

# token_amount是发送数据需要的令牌数
def consume(self, token_amount):
increment = (int(time.time()) - self._last_consume_time) * self._rate # 计算从上次发送到这次发送,新发放的令牌数量
self._current_amount = min(
increment + self._current_amount, self._capacity) # 令牌数量不能超过桶的容量
if token_amount > self._current_amount: # 如果没有足够的令牌,则不能发送数据
return False
self._last_consume_time = int(time.time())
self._current_amount -= token_amount
return True

ref

15行Python代码,帮你理解令牌桶算法

0%