404频道

学习笔记

今年六月份入职阿里正好三周年,在阿里入职三年被称为”三年醇”,三年才称之为真正的阿里人。我个人在这三年的时间里也变化了许多,值得反思总结一下。

阿里缘起

三年前,从上家公司离开。当时可选择的机会还是比较多的,基本上职位比较match的面试都通过了,甚至有公司经过了七面。之所以最终选择了阿里,主要如下两个原因:

  1. 阿里的技术在国内的知名度是响当当的。从技术深度和广度而言,阿里的技术都可圈可点,进入阿里我想多接触一下牛人,技术更进一层。
  2. 过往工作经历没有一线大厂,需要丰富下阅历。大厂的经历还是比较关键,不光是为了给自己的职业生涯中增加一份光鲜的经历,更多的是想看大厂是如何运作的,同样一件事情,在不同规模的公司有着不同的处理思路。

主要是基于上述原因,即使不是最好选择,即使做出了部分牺牲,毅然决定加入阿里云,加入了国内最领先的云计算厂商之一。

工作内容及感受

上家公司为互联网公司,处理的业务为高并发的在线业务,在互联网公司摸爬滚打积累了大量经验。来到阿里云后,加入了混合云,场景变为了专有云,虽然工作内容同样为云原生领域,但本质上为离线业务,跟互联网的在线业务在工作内容上有了较大的区别。互联网业务在线化,仅需要几套在线环境即可满足需求,专有云场景却需要维护大量的测试环境和客户的离线环境,从而导致业务复杂度急剧上升。主要体现在如下方面:

  1. 专有云场景强化对版本的概念。由于大量环境的存在,要想做到统一的管理和运维,必须用版本来强管理,否则维护大量环境将成为灾难。
  2. 专有云对自动化部署的要求变高。几套环境的情况下,考虑到ROI,并不需要很高的自动化程度,相反引入自动化会给变更引入额外的复杂性。但如果成百上千套环境,自动化部署成为了强需求,混合云的很多能力都是围绕着自动化部署展开的。
  3. 专有云场景对自动化运维的要求变高。运维场景包括了升级、扩缩容、故障处理等,由于是离线的场景,运维的流程变成了:客户 -> 驻场运维 -> 远程运维支持 -> 研发,相比在线业务的支持流程会更长,更难找到bug的真正owner。一方面需要对支持链路上的人员进行相关的培训,更为重要的是需要通过增强平台的自动化运维能力来降低运维成本。
  4. bug在客户环境的问题收敛非常慢。由于有大量的客户环境存在,一旦发现bug,需要在客户现场打hotfix,由于绝大多数环境无法远程,需要人工在客户现场操作,受限于人力、客户变更窗口等限制,hotfix patch的速度很难快起来。从而导致一个bug,会在多个客户现场环境爆发。
  5. 墨菲定律凸显,只要是 bug 暴露出来的概率明显增加。由于大量环境的存在,且客户现场运行着多个历史版本,非常难以拉齐版本,哪怕是多年前的旧 bug,在客户现场也极有可能会被发现。
  6. 专有云场景下忌架构变更大,架构复杂。专有云场景下经常出现的问题是,一个组件在版本一的架构,还没等一线的运维熟悉架构,在版本二下更换为了另外一套方案,导致运维性特别差。在引入“高大上”的高级特性时也一定要慎重,避免将架构搞复杂,将学习成本变高,专有云的试错成本太高。专有云场景下做好的架构解决方案是,使用尽可能通用简洁的方案来解决复杂的业务问题。

虽然两份工作内容都是云原生领域,但因为业务场景的不同,却对工作的重心产生了质的差异,量变产生了质变。正是因为质变,导致用互联网的“快糙猛”、“短平快”的思路来做需要精心打磨的专有云业务,路子非常容易走偏。

从工作量上来看,虽然上一份工作也不怎么轻松,但跟当前工作相比,却是小巫见大巫。尤其是前两年的时间,正处在业务从0到1的阶段,基本上一直处于高度运转的状态,每天都会有大量的钉钉消息和大量的会议,不是在救火就是在救火的路上,钉钉消息泛滥到惨不忍睹。能休息的周末也非常少,基本上周末都用来加班干一周攒下来的工作了。下面为相对卷的轻一些的2021年度的钉钉报告,一年光钉钉的会议就有三万多分钟,平均一天有两个小时,还不包含阿里内部的阿里郎会议和会议室中参加的线下会议。

成长

最大的成长是在工作方法上有了比较大的进步。阿里有一套工作处事的方法论,“搞不定就上升问题”,“责任边界”,“总结思考”等基本上是行走江湖必备技能,在网络上大家也可以看到大量的带有“阿里味”的文章,虽然处理起工作我还是比较喜欢按照自己的方式,但实在搞不定的时候可作为兜底处理思路的,在阿里的江湖里要想风生水起除了靠技术实力,做事方法也极为关键。

文档能力有了很大提升。在过往的工作经历中,很多事情想好了直接就开干了,反正使用方也不会很多,导致写的文档比较少,毕竟写一篇文档有可能花费时间比写代码的时间还要长。但目前我基本上已经养成了写文档的好习惯,写文档是一个整理思路的好方法,同时也可以将方案分享出去。在阿里的这几年深切体会到文档的重要性,因为业务方特别多,哪怕是一个技术点也会被多个人频繁提问,这时候扔一篇文档是比较好的减负方法。

另一个比较大的转变是很少失眠了。上一份工作经历大概是处理在线业务的原因,或者是工作不够饱和,经常出现失眠的情况。来阿里之前还比较担心失眠的问题发生,顶不住阿里高强度的工作压力。结果后来发现失眠根本就不存在,当每天都处理N件事情后,超卖非常严重,已经从原来的脑力劳动变成了体力加脑力劳动,根本不存在失眠的情况。到现在为止,即使工作没有刚加入阿里时那么饱和,失眠的情况也比较少了。

不足

面向钉钉的工作方式没有改掉。刚加入阿里的时候,看到同事的钉钉怎么那么多群,而且都把消息清一色设置为了屏蔽状态,我还特别的不理解。入职没多长时间就渐渐习惯了,每天总会加入几个群,刚开始的时候还会关注群消息,后来发现大部分群都是跟自己无关的。由于会随时被@,所以就把消息提醒给关掉了,否则会特别影响注意力。关掉消息提醒后,消息的查看方式就从push模式变了定期pull模式,基本上每隔几分钟就会打开一次钉钉。虽然大部分情况下都能看到新的消息要处理,但也养成了一个坏习惯,一旦不打开钉钉,总感觉心里不舒服。我不知道这个坏习惯算不算一种医学上的疾病,至少我扣手指的坏习惯在医学上找到了对应的名称“强迫性皮肤剥离症”。这个坏习惯一度尝试修正,比如采用“番茄工作法”的方式,每隔十五分钟再打开一次钉钉,但一直没有强有力的推动下去,总是忍不住打开钉钉看一眼消息。钉钉俨然成为了我工作效率的最大杀手,这句话也不完全对,因为钉钉本来就是我工作的一部分。如果这个问题可以解掉,我的工作效率会上一个大大的台阶。

能力陷阱问题突出。工作中有大量时间是作为客服来答疑解惑各类问题,前期阶段不管我是不是owner,只要是我力所能及的都尽可能提供帮助,直到我实在扛不住。虽然一方面服务了大量的客户,但另一方面我自己可支配的时间变的非常少。后面渐渐将不是我的问题路由到其他同学,自己才释放出一些时间。但路由器其实不是那么好当的,平台有大量组件,而且不同的组件往往归属不同的owner,有很多问题其实并不能直观看出具体是哪个组件,很多情况下都是多个组件配合的问题,因此还需要充当问题初步定位的工作以及没有owner问题的默认路由。

自身的学习投入比重偏少。工作时间多的最突出问题就是没有太多的精力来投入到新技术新知识的学习,虽然最近一年有很大的改善,但相比上家公司学习的技术的投入是完全不能比的。更别提,刚工作那几年曾经看过满满一书架的技术书籍了,现在想来真是一种奢侈。

前瞻性不够。每天都有很多问题在排队的后果就是规划能力不够。阿里还是特别强调抬头看路,但现实中,我却大部分的时间消耗在了低头解问题,思考的时间偏少。长此以往,渐渐发现自己的创造性是不够的。多花时间独立思考,这条路永远是正确的。

写在最后

自己对未来有很多的期待,这里最想提升的点为:打造个人品牌影响力。个人影响力方面过去不够重视,期望将来能够行动起来。要想做到这一点并非易事,需要很多基础能力,比如需要大量的知识积累,需要大量的工作实践等。期望借着阿里的平台,能够最大化个人影响力,未来可期!

OCI(Open Container Initiative)为Linux基金会下的子项目,成立于2015年,由docker、coreos等公司发起,用来制定开放的容器和容器镜像标准,同时docker将其镜像格式和容器运行时runc捐献给了OCI,因此OCI中包含了容器镜像标准和容器运行时标准两部分。

容器镜像的发展历史

2013年,docker横空出世,docker的核心能力之一为将应用封装为镜像,便于打包、发布。

2014年,docker将镜像格式定位为docker镜像规范v1版本。

2016年,docker制定了新的镜像格式规范v2,解决v1版本的部分设计缺陷。

2017年,OCI发布了OCI镜像规范1.0版本,整个规范以docker镜像规范v2为基础,两者的规范比较类似。

容器镜像标准

官方定义:OCI Image Format Specification

容器镜像标准包含了镜像索引(可选)、manifest、层文件和配置文件四部分内容。包含了容器的构建、分发和准备可以运行的镜像。

https://github.com/opencontainers/image-spec/blob/main/img/media-types.png

skopeo为一个容器镜像和镜像仓库的命令行操作工具,可以使用该工具来学习OCI容器镜像规范,可以直接使用 yum install skopeo -y 进行安装。

使用如下命令可以将docker的镜像转换为oci格式,并将其保存到/tmp/local_nginx目录下。

1
skopeo copy docker://nginx oci:/tmp/local_nginx

/tmp/local_nginx的目录包含如下结构,参考链接:OCI Image Layout Specification

  • blobs 目录:包含了镜像Manifest、镜像层和镜像配置信息,均已sha256值命名的文件形式存储,文件可以为文本文件,也可以为gzip压缩的二进制文件。
  • oci-layout 文件:定义了当前目录结构的版本信息
  • index.json 文件:定义了镜像索引信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── blobs
│   └── sha256
│   ├── 4a7307612456a7f65365e1da5c3811df49cefa5a2fd68d8e04e093d26a395d60
│   ├── 67e9751bc5aab75bba375f0a24702d70848e5b2bea70de55e50f21ed11feed14
│   ├── 8f46223e4234ce76b244c074e79940b9ee0a01b42050012c8555ebc7ac59469e
│   ├── 935cecace2a02d2545e0c19bd52fe9c8c728fbab2323fc274e029f5357cda689
│   ├── b85a868b505ffd0342a37e6a3b1c49f7c71878afe569a807e6238ef08252fcb7
│   ├── efb56228dbd26a7f02dafc74a2ca8f63d5e3bb6df9046a921f7d8174e5318387
│   ├── f4407ba1f103abb9ae05a4b2891c7ebebaecab0c262535fc6659a628db25df44
│   └── fe0ef4c895f5ea450aca17342e481fada37bf2a1ee85d127a4473216c3f672ea
├── index.json
└── oci-layout

2 directories, 10 files

镜像索引

非必须部分。如果包含镜像索引,用来解决多架构问题。不同的平台上,可以使用同一个镜像tag,即可以获取到对应平台的镜像。

镜像索引为 json 格式的文件,查看 index.json 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
{
"schemaVersion": 2,
"manifests": [
{
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"digest": "sha256:15beb598b14fca498f13a46923de0614a17012abf675ba06e364904d642d8a61",
"size": 1183
}
]
}

镜像索引文件可以包含多种架构。其中digest对应的sha256值指向 blobs/sha256 下的文件,其文件为Manifest文件。

需要注意的是:docker 可以使用 docker manifest命令来查看镜像的manifest信息,但格式并非为OCI Manifest格式,而更类似于OCI index的信息,下面的命令中,可以看到rancher镜像为多镜像。

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
34
$ docker manifest inspect rancher/rancher
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 4732,
"digest": "sha256:b8f1fdb8228d32ae5fc6f240503cd8e22b214fcfd4ad2a8a0b03274f3ead4e95",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 4731,
"digest": "sha256:ae0fa74e8dce9b72bdc6611815deb16bbddc8fe0a555052ccc8127fdc1b76980",
"platform": {
"architecture": "arm64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 4519,
"digest": "sha256:94c03afba43e81885c3cd2f5065032d1b7f8f540860fcc1fce1bbd7f1068d3db",
"platform": {
"architecture": "s390x",
"os": "linux"
}
}
]
}

Manifest

参考:OCI Image Manifest Specification

Manifest为json格式的描述文件,包含了如下三个用途:

  1. 每一个镜像都有一个唯一的 id 标识
  2. 对于同一个镜像tag,可以支持多架构镜像
  3. 可以直接转换为OCI的运行时规范

查看 blobs/sha256/15beb598b14fca498f13a46923de0614a17012abf675ba06e364904d642d8a61 内容如下:

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
34
35
36
37
38
39
40
41
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:67e9751bc5aab75bba375f0a24702d70848e5b2bea70de55e50f21ed11feed14",
"size": 6567
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:b85a868b505ffd0342a37e6a3b1c49f7c71878afe569a807e6238ef08252fcb7",
"size": 31379408
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:f4407ba1f103abb9ae05a4b2891c7ebebaecab0c262535fc6659a628db25df44",
"size": 25354178
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:4a7307612456a7f65365e1da5c3811df49cefa5a2fd68d8e04e093d26a395d60",
"size": 603
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:935cecace2a02d2545e0c19bd52fe9c8c728fbab2323fc274e029f5357cda689",
"size": 893
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:8f46223e4234ce76b244c074e79940b9ee0a01b42050012c8555ebc7ac59469e",
"size": 666
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:fe0ef4c895f5ea450aca17342e481fada37bf2a1ee85d127a4473216c3f672ea",
"size": 1394
}
]
}

包含了config 和 layers 两部分信息,其中 config 信息为运行镜像的配置,layers 为镜像中的层信息,其中gzip说明镜像的层为gzip压缩格式,每个层一个压缩文件。

镜像配置

参考:OCI Image Configuration

通过 manifest 中的config信息,可以找到镜像的配置信息 blobs/sha256/67e9751bc5aab75bba375f0a24702d70848e5b2bea70de55e50f21ed11feed14:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
{
"created": "2022-06-23T04:13:24.820503805Z",
"architecture": "amd64",
"os": "linux",
"config": {
"ExposedPorts": {
"80/tcp": {}
},
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.23.0",
"NJS_VERSION=0.7.5",
"PKG_RELEASE=1~bullseye"
],
"Entrypoint": [
"/docker-entrypoint.sh"
],
"Cmd": [
"nginx",
"-g",
"daemon off;"
],
"Labels": {
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
},
"StopSignal": "SIGQUIT"
},
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:08249ce7456a1c0613eafe868aed936a284ed9f1d6144f7d2d08c514974a2af9",
"sha256:d5b40e80384bb94d01a8d2d8fb2db1328990e7088640132c33d3f691dd8a88ee",
"sha256:b2f82de68e0d9246de01fa8283876427af5d6f3fe21c4bb04785892d5d071aef",
"sha256:41451f050aa883f9102df03821485fc2e27611da05689c0ba25f69dcda308988",
"sha256:44193d3f4ea2bae7a5ae5983f2562f551618b787751a6abfb732b6d17393bb88",
"sha256:e7344f8a29a34b4861faf6adcf072afb26fadf6096756f0e3fc4c289cdefb7c2"
]
},
"history": [
{
"created": "2022-06-23T00:20:27.020952309Z",
"created_by": "/bin/sh -c #(nop) ADD file:8adbbab04d6f84cd83b5f4205b89b0acb7ecbf27a1bb2dc181d0a629479039fe in / "
},
{
"created": "2022-06-23T00:20:27.337378745Z",
"created_by": "/bin/sh -c #(nop) CMD [\"bash\"]",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:05.737870066Z",
"created_by": "/bin/sh -c #(nop) LABEL maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:05.834940798Z",
"created_by": "/bin/sh -c #(nop) ENV NGINX_VERSION=1.23.0",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:05.931909571Z",
"created_by": "/bin/sh -c #(nop) ENV NJS_VERSION=0.7.5",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:06.026686816Z",
"created_by": "/bin/sh -c #(nop) ENV PKG_RELEASE=1~bullseye",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:23.901038357Z",
"created_by": "/bin/sh -c set -x && addgroup --system --gid 101 nginx && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos \"nginx user\" --shell /bin/false --uid 101 nginx && apt-get update && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates && NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; found=''; for server in hkp://keyserver.ubuntu.com:80 pgp.mit.edu ; do echo \"Fetching GPG key $NGINX_GPGKEY from $server\"; apt-key adv --keyserver \"$server\" --keyserver-options timeout=10 --recv-keys \"$NGINX_GPGKEY\" && found=yes && break; done; test -z \"$found\" && echo >&2 \"error: failed to fetch GPG key $NGINX_GPGKEY\" && exit 1; apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* && dpkgArch=\"$(dpkg --print-architecture)\" && nginxPackages=\" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \" && case \"$dpkgArch\" in amd64|arm64) echo \"deb https://nginx.org/packages/mainline/debian/ bullseye nginx\" >> /etc/apt/sources.list.d/nginx.list && apt-get update ;; *) echo \"deb-src https://nginx.org/packages/mainline/debian/ bullseye nginx\" >> /etc/apt/sources.list.d/nginx.list && tempDir=\"$(mktemp -d)\" && chmod 777 \"$tempDir\" && savedAptMark=\"$(apt-mark showmanual)\" && apt-get update && apt-get build-dep -y $nginxPackages && ( cd \"$tempDir\" && DEB_BUILD_OPTIONS=\"nocheck parallel=$(nproc)\" apt-get source --compile $nginxPackages ) && apt-mark showmanual | xargs apt-mark auto > /dev/null && { [ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; } && ls -lAFh \"$tempDir\" && ( cd \"$tempDir\" && dpkg-scanpackages . > Packages ) && grep '^Package: ' \"$tempDir/Packages\" && echo \"deb [ trusted=yes ] file://$tempDir ./\" > /etc/apt/sources.list.d/temp.list && apt-get -o Acquire::GzipIndexes=false update ;; esac && apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base curl && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list && if [ -n \"$tempDir\" ]; then apt-get purge -y --auto-remove && rm -rf \"$tempDir\" /etc/apt/sources.list.d/temp.list; fi && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log && mkdir /docker-entrypoint.d"
},
{
"created": "2022-06-23T04:13:24.128160562Z",
"created_by": "/bin/sh -c #(nop) COPY file:65504f71f5855ca017fb64d502ce873a31b2e0decd75297a8fb0a287f97acf92 in / "
},
{
"created": "2022-06-23T04:13:24.233980553Z",
"created_by": "/bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b03c4e6c8c513ae014f691fb05d530257dfffd07035c1b75da in /docker-entrypoint.d "
},
{
"created": "2022-06-23T04:13:24.337299368Z",
"created_by": "/bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7de297435e32af634f29f7132ed0550d342cad9fd20158258 in /docker-entrypoint.d "
},
{
"created": "2022-06-23T04:13:24.441125652Z",
"created_by": "/bin/sh -c #(nop) COPY file:09a214a3e07c919af2fb2d7c749ccbc446b8c10eb217366e5a65640ee9edcc25 in /docker-entrypoint.d "
},
{
"created": "2022-06-23T04:13:24.534829205Z",
"created_by": "/bin/sh -c #(nop) ENTRYPOINT [\"/docker-entrypoint.sh\"]",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:24.627520512Z",
"created_by": "/bin/sh -c #(nop) EXPOSE 80",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:24.724935944Z",
"created_by": "/bin/sh -c #(nop) STOPSIGNAL SIGQUIT",
"empty_layer": true
},
{
"created": "2022-06-23T04:13:24.820503805Z",
"created_by": "/bin/sh -c #(nop) CMD [\"nginx\" \"-g\" \"daemon off;\"]",
"empty_layer": true
}
]
}

其中包含了如下几个关键信息:

  1. config:运行镜像的参数,比如entrypoint、labels等,跟通过 docker inspect 命令看到的信息比较类似。
  2. rootfs:镜像的层信息
  3. history:镜像的历史构建信息,如果empty_layer的值为true,说明未产生新的层

镜像层

镜像层同样存在于 blobs/sha256 目录下,且以压缩格式存储,一个层一个压缩文件。manifests文件中的 application/vnd.oci.image.layer.v1.tar+gzip 说明镜像层的压缩格式为gzip。

容器运行时标准

用来定义容器的配置、运行环境和声明周期。runc为容器运行时的官方实现,其主要代码来源为docker的容器运行时,kara-containers也有对应的OCI实现。

参考文档:opencontainers/runtime-spec

容器配置

定义在config.json文件中,定义了创建容器的字段。由于runc更具体的操作系统环境有关,其中部分的规范是跟具体操作系统有关的。执行runc spec可以获取到默认的config.json文件,文件内容如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{
"ociVersion": "1.0.2-dev",
"process": {
"terminal": true,
"user": {
"uid": 0,
"gid": 0
},
"args": [
"sh"
],
"env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm"
],
"cwd": "/",
"capabilities": {
"bounding": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"effective": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"permitted": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
],
"ambient": [
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE"
]
},
"rlimits": [
{
"type": "RLIMIT_NOFILE",
"hard": 1024,
"soft": 1024
}
],
"noNewPrivileges": true
},
"root": {
"path": "rootfs",
"readonly": true
},
"hostname": "runc",
"mounts": [
{
"destination": "/proc",
"type": "proc",
"source": "proc"
},
{
"destination": "/dev",
"type": "tmpfs",
"source": "tmpfs",
"options": [
"nosuid",
"strictatime",
"mode=755",
"size=65536k"
]
},
{
"destination": "/dev/pts",
"type": "devpts",
"source": "devpts",
"options": [
"nosuid",
"noexec",
"newinstance",
"ptmxmode=0666",
"mode=0620",
"gid=5"
]
},
{
"destination": "/dev/shm",
"type": "tmpfs",
"source": "shm",
"options": [
"nosuid",
"noexec",
"nodev",
"mode=1777",
"size=65536k"
]
},
{
"destination": "/dev/mqueue",
"type": "mqueue",
"source": "mqueue",
"options": [
"nosuid",
"noexec",
"nodev"
]
},
{
"destination": "/sys",
"type": "sysfs",
"source": "sysfs",
"options": [
"nosuid",
"noexec",
"nodev",
"ro"
]
},
{
"destination": "/sys/fs/cgroup",
"type": "cgroup",
"source": "cgroup",
"options": [
"nosuid",
"noexec",
"nodev",
"relatime",
"ro"
]
}
],
"linux": {
"resources": {
"devices": [
{
"allow": false,
"access": "rwm"
}
]
},
"namespaces": [
{
"type": "pid"
},
{
"type": "network"
},
{
"type": "ipc"
},
{
"type": "uts"
},
{
"type": "mount"
}
],
"maskedPaths": [
"/proc/acpi",
"/proc/asound",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/sys/firmware",
"/proc/scsi"
],
"readonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
}
}

运行时和生命周期

在config.json文件中可以声明跟容器生命周期相关的部分,比如prestart、poststop等。

定义了很多子命令,比如状态查询的state <container-id>,删除容器的delete <container-id>等,这些子命令runc部分均有实现。通过 runc state mycontainerid 来查看的输出结果如下:

1
2
3
4
5
6
7
8
9
10
{
"ociVersion": "1.0.2-dev",
"id": "mycontainerid",
"pid": 40805,
"status": "running",
"bundle": "/mycontainer",
"rootfs": "/mycontainer/rootfs",
"created": "2022-06-29T13:51:54.795617419Z",
"owner": ""
}

镜像分发规范

pull

pull manifest

接口定义:GET /v2/<name>/manifests/<reference>

<name>: 镜像的 namespace。
<reference>:镜像的 tag 或者摘要信息。

pull bolb

接口定义:GET /v2/<name>/blobs/<digest>

push

POST /v2/<name>/blobs/uploads/?digest=<digest>
PUT /v2/<name>/manifests/<reference>

list tag

接口定义:GET /v2/<name>/tags/list

返回格式如下:

1
2
3
4
5
6
7
8
{
"name": "<name>",
"tags": [
"<tag1>",
"<tag2>",
"<tag3>"
]
}

list references

接口定义: GET /v2/<name>/referrers/<digest>

delete tag

接口定义: DELETE /v2/<name>/manifests/<tag>

delete manifest

接口定义: DELETE /v2/<name>/manifests/<digest>

delete blobs

接口定义: DELETE /v2/<name>/blobs/<digest>

k8s支持情况

K8s可以通过 pod 的 spec.runtimeClassName 来指定 oci runtime 的实现方式。

参考

多架构镜像

查看镜像的多架构信息

可以使用 docker manifest inspect $image 命令来查看,manifest为docker的体验特性,在Linux系统下开启,需要在本地创建 ~/.docker/config.json 文件,内容如下:

1
2
3
{
"experimental": "enabled"
}

最好的方式为开启docker daemon的特性,修改 /etc/docker/daemon.json 文件:

1
2
3
{
"experimental": true
}

例如执行 docker manifest inspect golang:alpine 可以看到golang 官方的docker镜像包含了多架构信息,每个架构下会对应一个sha256值。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:725f8fd50191209a4c4a00def1d93c4193c4d0a1c2900139daf8f742480f3367",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:5adcff3a3e757841a6c7b07f1986b2a36cb0afaf47025e78bb17358eda2d541a",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v6"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:84a1e4174b934fbf8f1dfe9f7353a5be449096b6f2273d6af5a364ffd6bf8f15",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:86cfea5046e196f5061324c93f25ef05e1df58ba96721e0c0b42cc6e0cf22e49",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:5ad072476cb8b51dddaf4142789f1528c7d48a3a0c31941a5ce21177c8e47259",
"platform": {
"architecture": "386",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:fca6cbe2f1fb9095eac2669c0be58b482135f9cf7196d51ac7338ea3e7c556c7",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1365,
"digest": "sha256:3f7ac24ca4b3ce61b51439cb59b57a8151ba60bd73a0e33cc06020dda6b692cb",
"platform": {
"architecture": "s390x",
"os": "linux"
}
}
]
}

gcr.io 可以在 console 上直接看到信息,比如: nginx镜像

多架构镜像的构建

可以使用docker buildx命令,比如 docker buildx build -t <image-name> --platform=linux/arm64,linux/amd64 . --push 可以同时构建出arm64和amd64的镜像。

查看镜像的构建历史

可以使用 docker history --no-trunc ${image} 来查看镜像的每层构建命令

通过代理拉取镜像

创建或者修改/etc/docker/daemon.json文件,文件内容如下:

1
2
3
4
5
6
{
"registry-mirrors": [
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
]
}

重启docker后通过docker info命令查看输出结果:

1
2
3
4
5
$ docker info
Registry Mirrors:
https://hub-mirror.c.163.com/
https://mirror.baidubce.com/
Live Restore Enabled: false

常用基础镜像

  • nicolaka/netshoot:包含了丰富的网络命令,排查网络问题非常方便

参考资料

/etc/resolv.conf文件为Linux主机的DNS配置文件,在 Linux 主机上可以执行 man resolv.conf 查看帮助信息。

配置文件说明

整个配置文件分为了4个部分:nameserver、search、sortlist和options

nameserver

用来配置DNS服务器地址。支持ipv4和ipv6地址。如果要配置多个DNS服务器,可以增加多条配置,域名按照DNS服务器配置的顺序来解析。配置多条的例子如下:

1
2
nameserver 100.100.2.136
nameserver 100.100.2.138

在默认情况下,如果要查找的域名中不包含domain信息(即不包含.字符),此时会以当前hostname的domain信息来进行查找。可以通过search字段来修改该行为,即可以通过在域名的后面增加配置的后缀来进行查找。

如果存在多条search记录,则以最后一条search记录为准。

比如k8s的pod中的search域格式如下,如果要查找defaultnamespace下的service的svc1,此时系统会自动追加 svc1.default.svc.cluster.local来进行查找:

1
search default.svc.cluster.local svc.cluster.local cluster.local

sortlist

该字段极少场景下会用到。在做域名解析时,如果返回的A记录为多条,可以对结果进行排序,排序的依据为当前的字段配置。

1
sortlist 130.155.160.0/255.255.240.0 130.155.0.0

options

该配置必须加载一行中,格式如下,其中option部分可以为多个,多个之间用空格分割:

1
options option ...
  • inet6: 应用程序在执行系统调用 gethostbyname 时,会优先执行 AAAA 记录的查询。如果查询不到 ipv6 地址,再去查询 ipv4 地址。

实验

实验一

修改/resolv.conf配置文件的内容如下:

1
nameserver 8.8.8.8

可以看到该nameserver是生效的,但是访问map域名是不生效的,因为没有map这个域名.

1
2
3
4
5
6
[vagrant@localhost ~]$ ping map.baidu.com
PING map.n.shifen.com (119.75.222.71) 56(84) bytes of data.
64 bytes from 119.75.222.71: icmp_seq=1 ttl=63 time=4.22 ms

[vagrant@localhost ~]$ ping map
ping: unknown host map

实验二

修改文件内容如下:

1
2
3
nameserver 8.8.8.8

search baidu.com google.com

此时可以ping通map域名,解析到了跟map.baidu.com相同的域名.如果map.baidu.com的域名没有解析到,会继续解析map.google.com的域名。

1
2
3
[vagrant@localhost ~]$ ping map
PING map.n.shifen.com (112.80.248.48) 56(84) bytes of data.
64 bytes from 112.80.248.48: icmp_seq=1 ttl=63 time=28.6 ms

domain的作用跟search类似,作为search的默认值,因为search可以使用多个域。

参考资料

什么是CNCF Landscape?

CNCF(Cloud Native Computing Foundation)为云原生计算基金会的英文缩写,致力于云原生技术的普及和推广。

CNCF Landscape为CNCF的一个重要项目,为了帮助企业和个人开发者快速了解云原生技术的全貌,该项目维护在GitHub。CNCF Landscape的最重要的两个产出物为路线图和愿景图。

CNCF Landscape路线图

路线图的目的是指导用户使用云原生技术的路径和开源项目。其中包括了10个步骤。

CNCF Landscape愿景图

将云原生技术进行了分层分类,可以非常清晰的将云原生技术展示给用户。类似于软件架构中,又称“方块图”、“砌砖图”。

精简版图形如下:

供给层(provisioning)

愿景图的第一层,是云原生平台和云原生应用的基础。

自动化配置

关键词:IaC(基础设施即代码)、声明式配置

用来加速计算机资源的创建和配置,资源包括虚拟机、网络、防火墙规则、负载均衡等。只需要点击下按钮,即可自动化创建对应的资源,用来降低人工的维护。

典型工具代表:Terraform、Chef、Ansible、Puppet

CNCF项目列表:

CNCF项目 项目阶段 项目介绍
Akri 沙箱
CDK for Kubernetes 沙箱
Cloud Custodian 沙箱
KubeDL 沙箱
KubeEdge 孵化 华为开源的边缘计算项目
Metal3-io 沙箱
OpenYurt 沙箱 阿里云开源的边缘计算项目
SuperEdge 沙箱
Tinkerbell 沙箱

容器镜像仓库

用来存储和拉取容器镜像,最简单的项目如docker官方的单机版的docker registry,所有的公有云厂商也都有自己的工具。

CNCF项目包括:

CNCF项目 项目阶段 项目介绍
Harbor 毕业 由vmware打造的镜像仓库,提供了很多企业级的特性,广受企业内部使用。
Dragonfly 孵化 阿里开源的基于P2P技术做镜像分发项目,在大规模集群的场景下效果会比较明显。

安全合规

关键词:镜像扫描、镜像签名、策略管理、审计、证书管理、代码扫描、漏洞扫描、网络层安全

用来监控、增强应用和平台的安全性,包括从容器到k8s的运行时环境均会涉及到。

CNCF项目包括:

CNCF项目 项目阶段 项目介绍
cert-manager 沙箱 证书签发工具,部署在k8s之上,通过抽象CRD Certificate来管理证书,也可以用来管理Ingress的证书
Confidential Containers 沙箱
Curiefense 沙箱
Dex 沙箱
Falco 孵化
in-toto 孵化
Keylime 沙箱
Kyverno 沙箱 基于k8s的策略引擎工具,通过抽象CRD ClusterPolicy的方式来声明策略,在运行时通过webhook的技术来执行策略。相比于opa & gatekeeper,更加k8s化,但却没有编程语言的灵活性。
Notary 孵化
Open Policy Agent (OPA) 毕业 基于Rego语言的策略引擎,编程能力非常强大
Parsec 沙箱
[The Update Framework (TUF)](The Update Framework) 毕业

秘钥和身份管理

关键词:秘钥、身份、Secret、访问控制、认证、授权

CNCF项目包括:

CNCF项目 项目阶段 项目介绍
Athenz 沙箱
SPIFFE 孵化
SPIRE 孵化
Teller 沙箱

运行时层(Runtime)

云原生存储

关键词:PV、CSI、备份和恢复

云原生架构下,存储类的工具主要涉及到如下几个方面:

  1. 为容器提供云原生的存储。由于容器具有灵活、弹性的特点,云原生的存储相比传统存储会更复杂。
  2. 需要有统一的接口。这块基本都会使用k8s的CSI接口。另外,minio提供了S3协议的接口。
  3. 备份和还原功能。例如Velero可以用来备份k8s本身和容器使用到的存储。

CNCF项目包括:

CNCF项目 项目阶段 项目介绍
CubeFS 孵化
K8up 沙箱
Longhorn 孵化
OpenEBS 沙箱
ORAS 沙箱
Piraeus Datastore 沙箱
Rook 毕业
Vineyard 沙箱

容器运行时

容器运行时的三个主要特征:标准化、安全、隔离性。Containerd和CRI-O为容器运行时的标准实现方案,业界类似KataContainer的方式为将VM作为容器运行时,gVisor方案则在OS和容器中间增加了额外的安全层。

发展趋势:

  1. 基于 MicroVM 的安全容器技术,通过虚拟化和容器技术的结合,可以提升更高的安全性。比如 KataContainer。
  2. 操作系统的虚拟化程度进一步增加。Linux 4.5 版本的 CGroup V2 技术逐渐成熟,进一步提升了容器的隔离能力。Docker 提供了 rootless 技术,可以以非 root 用户运行,提升了容器的安全性。
  3. WebAssembly技术作为跨平台的容器技术可能会作为新的挑战者出现。
CNCF项目 项目阶段 项目介绍
Containerd 毕业
CRI-O 孵化 k8s的CRI的轻量级实现,支持runc和kata作为容器运行时
Inclavare Containers 沙箱
rkt 归档 CoreOS公司主导研发的容器引擎,昔日的Docker竞争对手,目前已经没落,已经被CNCF归档
WasmEdge Runtime 沙箱

云原生网络

关键词:SDN、CNI、Overlay网络

云原生网络具体指的是容器网络,k8s定义了CNI的网络规范,开源项目只需要实现CNI的网络规范即可,社区有非常多的CNI项目可以选择,比如Calico、Flannel、Weave Net等。网络分为Underlay和Overlay网络,其中容器网络为SDN网络,以overlay网络居多。

CNCF项目 项目阶段 项目介绍
Antrea 沙箱
Cilium 孵化
CNI-Genie 沙箱 华为云开源的多网络平面的项目,该项目并非具体的网络插件实现,而是k8s的CNI和具体网络插件实现的中间层,可以实现同一个节点上有多种网络插件的实现,支持同一个pod中有多个网卡。
CNI(Container Network Interface) 孵化 k8s的容器网络规范,指的一提的是在[plugins](containernetworking/plugins: Some reference and example networking plugins, maintained by the CNI team. (github.com))项目中,提供了很多k8s内置的简单容器插件,比如macvlan、bandwidth等
Kube-OVN 沙箱
Network Service Mesh 沙箱
Submariner 沙箱 Rancher公司开源的项目,用来解决k8s的多集群场景下的跨集群互通问题

编排和管理层(Orchestration & Management)

调度和编排

在单机的系统中,操作系统会来调度系统中运行的所有的进程,允许某个时间点调度某个进程到某个cpu上面。在集群的环境中,同样需要调度容器在某个时间点运行在某台主机的某个cpu上。

在云原生社区基本上形成以k8s为生态的调度和编排,主要的发展方向为:

  • 扩展k8s自身的功能。
  • k8s的多集群方向。
CNCF项目 项目阶段 项目介绍
Crossplane 孵化
Fluid 沙箱
Karmada 沙箱 华为云开源的k8s多集群管理项目,用来管理多个k8s集群。自己实现了一套完整的apiserver、scheduler、controller-manager,用来多k8s集群的调度。
kube-rs 沙箱
Kubernetes 毕业 CNCF的最重要项目,在容器编排领域具有绝对的统治地位。俗称“现代数据中心的操作系统”
Open Cluster Management 沙箱 Redhat主导的k8s多集群项目
Volcano 孵化 华为云开源的基于k8s的容器批量计算平台,常用于大数据、AI领域。k8s默认的Job设计较为简单,无法满足很多批处理场景。Volcano通过CRD扩展的方式定义了Queue、PodGroup、VolcanoJob等实现对批处理作业的抽象,并通过调度器扩展的方式来大幅提升pod的调度效率。
wasmCloud 沙箱

调协和服务发现

该领域主要包含两类工具:

  1. 服务发现引擎。
  2. 域名解析服务。比如CoreDNS。
CNCF项目 项目阶段 项目介绍
CoreDNS 毕业
etcd 毕业
k8gb 沙箱

远程过程调用(RPC)

用于进程间通讯的框架,主要解决的问题:

  1. 提供了框架,使开发者编码更简单。
  2. 提供了结构化的通讯协议。
CNCF项目 项目阶段 项目介绍
gRPC 孵化 业界使用较为广泛的RPC框架

服务代理

通常又称为负载均衡,从协议上来划分,可以分为四层负载均衡和七层负载均衡。

CNCF项目 项目阶段 项目介绍
BFE 沙箱
Contour 孵化
Envoy 毕业
OpenELB 沙箱

API网关

相比于七层负载均衡,API网关还提供了更多高级特性,比如认证、鉴权、限流等。

CNCF项目 项目阶段 项目介绍
Emissary-Ingress 孵化

服务网格

值得注意的是,业内最为流行的istio项目并不在该范围内。

CNCF项目 项目阶段 项目介绍
Kuma 沙箱
Linkerd 毕业 流行的服务网格项目
Meshery 沙箱
Open Service Mesh 沙箱
Service Mesh Interface 沙箱
Service Mesh Performance 沙箱

应用定义和应用部署

数据库

CNCF项目 项目阶段 项目介绍
SchemaHero 沙箱
TiKV 毕业 国内公司PingCAP开源的分布式kv数据库
Vitess 毕业 用来扩展mysql集群的数据库解决方案,突破单mysql集群的性能瓶颈

流式计算和消息

CNCF项目 项目阶段 项目介绍
CloudEvents 孵化 仅描述了事件的数据规范,并非具体的实现
NATS 孵化
Pravega 沙箱
Strimzi 沙箱
Tremor 沙箱

应用定义和镜像构建

CNCF项目 项目阶段 项目介绍
Artifact Hub 沙箱
Backstage 孵化
Buildpacks 孵化
Devfile 沙箱
Helm 毕业 k8s的应用打包工具
Krator 沙箱
KubeVela 沙箱 阿里云开源的基于OAM的应用模型的实现,用来做应用的发布,同时支持多集群
KubeVirt 孵化
KUDO 沙箱
Nocalhost 沙箱
Operator Framework 孵化 用来开发基于k8s CRD的operator框架,功能跟k8s亲生的kubebuilder非常相似
Porter
sealer 沙箱 阿里云开源的集群部署工具,理念比较先进,通过升维的方式可以通过类似Dockerfile的方式来构建集群镜像,并可以通过类似docker run的方式一键拉起完成的一套基于k8s的集群环境
Serverless Workflow
Telepresence

持续集成和持续交付

CNCF项目 项目阶段 项目介绍
Argo 孵化 k8s上应用广泛的工作流引擎
Brigade 沙箱
Flux 孵化 gitops工具
Keptn 孵化
OpenGitOps 沙箱
OpenKruise 沙箱 基于k8s能力扩展的组件,通过CRD的方式定义了很多对象,用来增强k8s的workload能力。该组件放到该领域下有些不合适。

可观测和分析

监控

CNCF项目 项目阶段 项目介绍
Cortex 孵化
Fonio 沙箱
Kuberhealthy 沙箱 k8s的巡检工具,用来检查k8s的健康状态。支持以插件的方式接入巡检脚本。
OpenMetrics 沙箱 从Prometheus项目中发展出来的监控数据格式标准,该项目仅定义标准,非实现。
Pixie 沙箱
Prometheus 毕业 云原生领域事实上的监控标准
Skooner 沙箱
Thanos 孵化 prometheus的集群化方案
Trickster 沙箱

日志

CNCF项目 项目阶段 项目介绍
Fluentd 毕业 日志收集工具

分布式会话跟踪

CNCF项目 项目阶段 项目介绍
Jaeger 毕业 User开源的完整的分布式会话跟踪项目
OpenTelemetry 孵化 同时集成了监控、日志和分布式会话跟踪三个领域的数据收集工具,大有一统可观察性领域的趋势。
OpenTracing 归档 已经完全被OpenTelemetry取代

混沌引擎

CNCF项目 项目阶段 项目介绍
Chaos Mesh 孵化
Chaosblade 沙箱
Litmus 沙箱

参考资料

核心原理

Flux为一款GitOPS的发布工具,应用信息全部放到git仓库中,一旦git仓库中应用信息有新的提交,Flux即可在k8s中发布新的部署。支持kustomize和helm chart形式的应用。目前该项目为CNCF的孵化项目。

快速开始

前置条件

  1. 已经存在k8s集群
  2. 可以获取到Github的个人token

安装flux cli

执行如下命令安装flux cli

1
curl -s https://fluxcd.io/install.sh | sudo bash

将如下内容追到到文件~/.bash_profile

1
. <(flux completion bash)

bootstrap

bootstrap操作会在k8s集群中安装flux到flux-system下,并且会通过git的方式来管理其自身。这里直接采用github,首先要获取到token,并设置为环境变量。

1
export GITHUB_TOKEN=xxx

并执行如下命令开始bootstrap

1
2
3
4
5
flux bootstrap github \
--owner=kuring \
--repository=flux-learn \
--path=clusters/my-cluster \
--personal

会自动创建github repo,并且会在main分支的clusters/my-cluster/flux-system下创建三个文件:

  • gotk-components.yaml:flux的组件yaml
  • gotk-sync.yaml
  • kustomization.yaml:使用kustomization来个性化flux组件

会安装如下组件到k8s中

1
2
3
4
5
6
$ k get deployment -n flux-system 
NAME READY UP-TO-DATE AVAILABLE AGE
helm-controller 1/1 1 1 27m
kustomize-controller 1/1 1 1 27m
notification-controller 1/1 1 1 27m
source-controller 1/1 1 1 27m

也可以使用更简单的方式flux install来安装flux,该命令不会将flux的安装信息存放到git仓库中,而是会直接安装。

部署应用

将上述的github项目copy到本地,需要特别注意的是github不允许使用密码来认证,需要在输入密码的地方输入token。

1
2
git clone https://github.com/kuring/flux-learn
cd fleet-infra

使用如下命令在git仓库中创建podinfo-source.yaml文件和kustomization文件

1
2
3
4
5
6
7
8
9
10
11
12
13
flux create source git podinfo \
--url=https://github.com/stefanprodan/podinfo \
--branch=master \
--interval=30s \
--export > ./clusters/my-cluster/podinfo-source.yaml

flux create kustomization podinfo \
--target-namespace=default \
--source=podinfo \
--path="./kustomize" \
--prune=true \
--interval=5m \
--export > ./clusters/my-cluster/podinfo-kustomization.yaml

podinfo-source.yaml文件内容如下:

1
2
3
4
5
6
7
8
9
10
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 30s
ref:
branch: master
url: https://github.com/stefanprodan/podinfo

podinfo-kustomization.yaml文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 5m0s
path: ./kustomize
prune: true
sourceRef:
kind: GitRepository
name: podinfo
targetNamespace: default

执行如下命令将文件提供到git仓库

1
2
git add -A && git commit -m "Add podinfo GitRepository"
git push

查看podinfo的部署状态,说明已经自动部署成功。

1
2
3
4
5
$ flux get kustomizations --watch
NAME REVISION SUSPENDED READY MESSAGE
podinfo master/bf09377 False True Applied revision: master/bf09377
flux-system main/81f2115 False True Applied revision: main/81f2115

修改podinfo-kustomization.yaml文件内容如下,并重新提交到git仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: podinfo
namespace: flux-system
spec:
interval: 5m0s
path: ./kustomize
prune: true
sourceRef:
kind: GitRepository
name: podinfo
targetNamespace: default
patches:
- patch: |-
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: podinfo
spec:
minReplicas: 3
target:
name: podinfo
kind: HorizontalPodAutoscaler

过会可以看到环境中的hpa最小副本数已经变更为3.

资料

在k8s的运维过程中,经常会有根据pid获取到pod名称的需求。比如某个pid占用cpu特别高,想知道是哪个pod里面的进程。

操作步骤

查看进程的cgroup信息

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat /proc/5760/cgroup 
11:rdma:/
10:hugetlb:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
9:freezer:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
8:pids:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
7:perf_event:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
6:blkio:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
5:devices:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
4:memory:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
3:cpuset,cpu,cpuacct:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
2:net_cls,net_prio:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3
1:name=systemd:/apsara_k8s/kubepods/burstable/podf87c35b4-c170-4e1c-a726-d839e2fe6bea/17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3

其中的 17e15f78a43b2ddc7caff93bcc03d5ca7f5249fbee4c2ade5c3c96a7279af0a3 即为容器的id,可以如下的命令直接获取到容器的pid

1
2
CID=$(cat /proc/5760/cgroup | awk -F '/' '{print $6}')
echo ${CID:0:8}

继续执行如下命令查看docker的label

1
docker  inspect 17e15f --format "{{json .Config.Labels}}" 

其中io.kubernetes.pod.namespace为pod所在的namespace,io.kubernetes.pod.name为pod的name。

参考资料

cue为使用golang编写的一款配置语言。

安装

mac用户执行:brew install cue-lang/tap/cue,其他操作系统用户可以直接使用源码安装:go install cuelang.org/go/cmd/cue@latest

命令行使用

创建如下文件first.cue

1
2
3
4
5
6
7
8
9
a: 1.5
a: float
b: 1
b: int
d: [1, 2, 3]
g: {
h: "abc"
}
e: string
  • cue fmt first.cue:对代码进行格式化
  • cue vet first.cue:校验语法的正确性
  • cue eval first.cue:获得渲染结果
  • cue export first.cue:将渲染结果以json格式的形式导出,如果指定参数–out yaml,则可以以yaml方式导出。如果要导出某个变量,可以使用-e参数来指定变量。

语法

基础数据类型

支持的数据类型包括:float、int、string、array、bool、struct、null、自定义数据类型

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
34
35
36
37
// float
a: 1.5

// int
b: 1

// string
c: "blahblahblah"

// array
d: [1, 2, 3, 1, 2, 3, 1, 2, 3]

// bool
e: true

// struct
f: {
a: 1.5
b: 1
d: [1, 2, 3, 1, 2, 3, 1, 2, 3]
g: {
h: "abc"
}
}

// null
j: null

// 表示两种类型的值,即可以是string,也可以是int
h: string | int

// 表示k的默认值为1,且类型为int
k: *1 | int

// 自定义数据类型
#abc: string

更复杂的自定义数据类型如下:

1
2
3
4
5
6
7
8
#abc: {
x: int
y: string
z: {
a: float
b: bool
}
}

定义cue模板

文件deployment.cue定义如下内容

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
// 自定义结构体类型
#parameter: {
name: string
image: string
}

template: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
selector: matchLabels: {
"app.oam.dev/component": parameter.name
}
template: {
metadata: labels: {
"app.oam.dev/component": parameter.name
}
spec: {
containers: [{
name: parameter.name // 引用自定义结构体变量parameter
image: parameter.image
}]
}}}
}

parameter:{
name: "mytest"
image: "nginx:v1"
}

执行 cue export deployment.cue -e template –out yaml 可获取到渲染结果。

引用

快速开始

部署

https://github.com/tektoncd/cli/releases下载tkn工具,该工具为tekton的命令行工具。

执行如下命令安装dashboard

1
kubectl apply --filename https://github.com/tektoncd/dashboard/releases/latest/download/tekton-dashboard-release.yaml

dashboard安装完成后仅申请了ClusterIP类型的Service,可以在本地通过kubectl端口转发的方式来对外提供服务,既可以通过ip:9097的方式来对外提供服务了。

1
kubectl --namespace tekton-pipelines port-forward svc/tekton-dashboard 9097:9097 --address 0.0.0.0 

执行如下命令,在k8s中提交yaml

1
kubectl apply --filename \ https://storage.googleapis.com/tekton-releases/pipeline/latest/release.yaml

会在k8s中提交如下两个deployment,此时tekton即安装完毕。

1
2
3
4
$ kubectl  get deploy -n tekton-pipelines 
NAME READY UP-TO-DATE AVAILABLE AGE
tekton-pipelines-controller 1/1 1 1 4m
tekton-pipelines-webhook 1/1 1 1 4m

创建task

创建如下yaml,提交一个任务定义,提交完成后任务并不会被执行

1
2
3
4
5
6
7
8
9
10
11
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: hello
spec:
steps:
- name: echo
image: alpine
script: |
#!/bin/sh
echo "Hello World"

继续提交如下的yaml,提交一次任务运行实例

1
2
3
4
5
6
7
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
name: hello-task-run
spec:
taskRef:
name: hello

查看任务执行情况

1
2
3
4
5
6
7
8
$ kubectl get taskrun hello-task-run
NAME SUCCEEDED REASON STARTTIME COMPLETIONTIME
hello-task-run True Succeeded 45s 22s

# 可以看到创建出了任务执行的pod
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
hello-task-run-pod 0/1 Completed 0 2m10s

创建流水线任务pipeline

创建新的task goodye

1
2
3
4
5
6
7
8
9
10
11
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: goodbye
spec:
steps:
- name: goodbye
image: ubuntu
script: |
#!/bin/bash
echo "Goodbye World!"

提交如下yaml,创建pipeline定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
name: hello-goodbye
spec:
tasks:
- name: hello
taskRef:
name: hello
- name: goodbye
runAfter:
- hello
taskRef:
name: goodbye

提交如下yaml,创建pipeline实例。

1
2
3
4
5
6
7
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: hello-goodbye-run
spec:
pipelineRef:
name: hello-goodbye

可以使用tkn来查看pipeline拉起的pod日志,该工具会将pod的日志合并到一起。

1
2
3
4
$ tkn pipelinerun logs hello-goodbye-run -f -n default
[hello : echo] Hello World

[goodbye : goodbye] Goodbye World!

操作系统级别

/proc/meminfo

其中的Buffers和Cached的迷惑性非常大,非常难理解。Buffers是指的磁盘数据的缓存,Cached是指对文件数据的缓存.

free

该命令实际上是通过读取/proc/meminfo文件得到的如下输出

1
2
3
4
$ free -h
total used free shared buff/cache available
Mem: 30G 22G 412M 47M 7.6G 7.3G
Swap: 0B 0B 0B

具体列含义:

  • total: 总内存大小
  • used:已经使用的内存大小,包含共享内存
  • free:未使用的内存大小
  • shared:共享内存大小
  • buff/cache:缓存和缓冲区的大小
  • available:新进程可用的内存大小

vmstat

1
2
3
4
$ 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
4 0 0 1091484 1142360 34545436 0 0 146 265 0 0 8 5 86 1 0

容器级别

通过kubectl命令来查看内存使用

1
2
3
$ kubectl top pod nginx-ingress-controller-85cd6c7b5d-md6vc
NAME CPU(cores) MEMORY(bytes)
nginx-ingress-controller-85cd6c7b5d-md6vc 22m 502Mi

通过docker stats命令来查看容器

1
2
3
$ docker stats $container_id
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
97d8bff3f89f k8s_nginx-ingress 6.33% 180.3MiB / 512MiB 35.21% 0B / 0B 0B / 0B 119

docker通过cgroup的统计数据来获取的内存值

参考文档:https://docs.docker.com/engine/reference/commandline/stats/

进程级别

具体的含义可以通过下文的/proc/$pid/status来查看,其他进程的内存含义

cat /proc/$pid/status

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cat status
Name: nginx
Umask: 0022
State: S (sleeping)
Tgid: 46787
Ngid: 0
Pid: 46787
PPid: 33
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 19062 19062 19062 19062
FDSize: 128
Groups:
VmPeak: 559768 kB
VmSize: 559120 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 285240 kB
VmRSS: 284752 kB
RssAnon: 279016 kB
RssFile: 3420 kB
RssShmem: 2316 kB
VmData: 371784 kB
VmStk: 136 kB
VmExe: 4716 kB
VmLib: 6828 kB
VmPTE: 900 kB
VmSwap: 0 kB
Threads: 33
SigQ: 1/123857
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000040001000
SigCgt: 0000000198016eef
CapInh: 0000001fffffffff
CapPrm: 0000000000000400
CapEff: 0000000000000400
CapBnd: 0000001fffffffff
CapAmb: 0000000000000000
NoNewPrivs: 0
Seccomp: 0
Speculation_Store_Bypass: vulnerable
Cpus_allowed: 0001
Cpus_allowed_list: 0
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 343464
nonvoluntary_ctxt_switches: 35061

具体字段含义可以查看man文档:https://man7.org/linux/man-pages/man5/proc.5.html,跟内存相关的字段如下:

  • VmRSS:虚拟内存驻留在物理内存中的部分大小
  • VmHWM:使用物理内存的峰值
  • VmData:进程占用的数据段大小

top命令查看

1
2
3
$ top -p $pid
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
46787 www-data 20 0 559120 286544 5740 S 1.3 0.9 2:43.64 nginx

具体列含义如下:

  • VIRT:进程使用虚拟内存总大小,即使没有占用物理内存,包括了进程代码段、数据段、共享内存、已经申请的堆内存和已经换到swap空间的内存等
  • RES: 进程实际使用的物理内存大小,不包括swap和共享内存
  • SHR:与其他进程的共享内存、加载的动态链接库、程序代码段的大小
  • MEM:进程使用的物理内存占系统总内存的百分比

ps命令查看

1
2
3
4
5
6
7
8
9
10
11
$ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
www-data 1 0.0 0.0 212 8 ? Ss Apr09 0:00 /usr/bin/dumb-init -- /nginx-ingress-controller
www-data 6 0.9 0.3 813500 99644 ? Ssl Apr09 67:32 /nginx-ingress-controller
www-data 33 0.0 0.7 458064 242252 ? S Apr09 0:53 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
www-data 46786 0.0 0.7 459976 239996 ? S 17:57 0:00 rollback logs/eagleeye.log interval=60 adjust=600
www-data 46787 1.3 0.8 559120 284452 ? Sl 17:57 2:22 nginx: worker process
www-data 46788 1.0 0.8 558992 285168 ? Sl 17:57 1:51 nginx: worker process
www-data 46789 0.0 0.7 452012 237152 ? S 17:57 0:01 nginx: cache manager process
www-data 46790 0.0 0.8 490832 267600 ? S 17:57 0:00 nginx: x
www-data 47533 0.0 0.0 60052 1832 pts/2 R+ 20:50 0:00 ps aux
  • RSS:虚拟内存中的常驻内存,即实际占用的物理内存,包括所有已经分配的堆内存、栈内存、共享内存,由于共享内存非独占,实际上进程独占的物理内存要少于RSS

pmap

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# pmap -x 452021
452021: nginx: worker process
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4716 1540 0 r-x-- tengine
0000000000a9a000 20 16 4 r---- tengine
0000000000a9f000 212 196 176 rw--- tengine
0000000000ad4000 248 60 60 rw--- [ anon ]
00002b0da6cb0000 136 4 0 r-x-- ld-2.17.so
00002b0da6cd2000 4 4 4 rw--- [ anon ]
00002b0da6cd3000 4 4 4 rw-s- zero (deleted)
00002b0da6cd7000 28 24 24 rw--- [ anon ]
00002b0da6cde000 64 8 8 rwx-- [ anon ]
00002b0da6ed1000 4 4 4 r---- ld-2.17.so
00002b0da6ed2000 4 4 4 rw--- ld-2.17.so
00002b0da6ed3000 4 4 4 rw--- [ anon ]
00002b0da6ed4000 8 0 0 r-x-- libdl-2.17.so
00002b0da70d8000 92 32 0 r-x-- libpthread-2.17.so
00002b0da72f0000 16 4 4 rw--- [ anon ]
00002b0da72f4000 32 0 0 r-x-- libcrypt-2.17.so
00002b0da72fc000 2044 0 0 ----- libcrypt-2.17.so
00002b0da74fb000 4 4 4 r---- libcrypt-2.17.so
00002b0da74fc000 4 4 4 rw--- libcrypt-2.17.so
00002b0da74fd000 184 0 0 rw--- [ anon ]
00002b0da752b000 1028 8 0 r-x-- libm-2.17.so
00002b0da7a35000 760 372 0 r-x-- libssl.so.1.1
00002b0da7d03000 2812 1124 0 r-x-- libcrypto.so.1.1
00002b0da7fc2000 2044 0 0 ----- libcrypto.so.1.1
00002b0da81c1000 172 172 172 r---- libcrypto.so.1.1
00002b0da81ec000 12 12 12 rw--- libcrypto.so.1.1
00002b0da81ef000 16 8 8 rw--- [ anon ]
00002b0da81f3000 84 40 0 r-x-- libgcc_s-4.8.5-20150702.so.1
00002b0da8409000 1808 316 0 r-x-- libc-2.17.so
00002b0da85cd000 2044 0 0 ----- libc-2.17.so
00002b0da87cc000 16 16 16 r---- libc-2.17.so
00002b0da87d0000 8 8 8 rw--- libc-2.17.so
00002b0da87d2000 20 12 12 rw--- [ anon ]
00002b0da87d7000 8 0 0 r-x-- libfreebl3.so
00002b0da87d9000 2044 0 0 ----- libfreebl3.so
00002b0da89d8000 4 4 4 r---- libfreebl3.so
00002b0da89d9000 4 4 4 rw--- libfreebl3.so
00002b0da8a00000 24576 21936 21936 rw--- [ anon ]
00002b0daa200000 8192 8180 8180 rw--- [ anon ]
00002b0daaa00000 8192 8192 8192 rw--- [ anon ]
00002b0dab200000 8192 8192 8192 rw--- [ anon ]
00002b0daba00000 8192 8172 8172 rw--- [ anon ]
00002b0dac200000 4096 4096 4096 rw--- [ anon ]
00002b0dac600000 4096 4096 4096 rw--- [ anon ]
00002b0daca00000 4096 12 12 rw-s- zero (deleted)
00002b0dace00000 1024 0 0 rw-s- zero (deleted)
00002b0dacf00000 1024 0 0 rw-s- zero (deleted)
00002b0dad000000 16384 16384 16384 rw--- [ anon ]
00002b0dae000000 10240 0 0 rw-s- zero (deleted)
00002b0db3f00000 24 16 0 r-x-- cjson.so
00002b0db3f06000 2048 0 0 ----- cjson.so
00002b0db4106000 4 4 4 r---- cjson.so
00002b0db4107000 4 4 4 rw--- cjson.so
00002b0db4108000 8 0 0 r-x-- librestychash.so
00002b0db410a000 2044 0 0 ----- librestychash.so
00002b0db4309000 4 4 4 r---- librestychash.so
00002b0db430a000 4 4 4 rw--- librestychash.so
00002b0db430b000 10240 1784 1784 rw-s- zero (deleted)
00002b0db4d0b000 10240 0 0 rw-s- zero (deleted)
00002b0db5800000 6144 6144 6144 rw--- [ anon ]
00002b0db5e00000 6144 6144 6144 rw--- [ anon ]
00002b0db6400000 8192 8192 8192 rw--- [ anon ]
00002b0db6c0b000 5120 8 8 rw-s- zero (deleted)
00002b0db7200000 8192 8192 8192 rw--- [ anon ]
00002b0dc2800000 1024 0 0 rw-s- zero (deleted)
00002b0dc2900000 10240 0 0 rw-s- zero (deleted)
00002b0dc3300000 10240 0 0 rw-s- zero (deleted)
00002b0dc861f000 2048 8 8 rw--- [ anon ]
00002b0dc881f000 4 0 0 ----- [ anon ]
00002b0dc8820000 2048 8 8 rw--- [ anon ]
00007fff60f6c000 136 44 44 rw--- [ stack ]
00007fff60fdd000 8 4 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 558996 289376 285888
  • Mapping: 支持如下值
    • [anon]:分配的内存
0%