Showing preview only (2,265K chars total). Download the full file or copy to clipboard to get everything.
Repository: zoux86/learning-k8s-source-code
Branch: master
Commit: 15e9fd4aa46c
Files: 103
Total size: 1.8 MB
Directory structure:
gitextract_ays31j2l/
├── .gitignore
├── README.md
├── docker/
│ ├── 0-docker章节介绍.md
│ ├── 1. linux namespaces 知识准备.md
│ ├── 10. 如何下载并二进制编译docker源码.md
│ ├── 11. dockercli 源码分析-docker run为例.md
│ ├── 12. dockerd源码分析-docker run为例.md
│ ├── 2. linux cgroup 知识准备.md
│ ├── 3. chroot 命令详解.md
│ ├── 4. 如何用golang 实现一个 busybox的容器.md
│ ├── 5. docker-overlay技术.md
│ ├── 6. docker pull原理分析.md
│ ├── 7. docker 命令详解.md
│ ├── 8. docker核心组件介绍.md
│ ├── 9. docker问题链路排查实例.md
│ └── 其他/
│ ├── 补充-僵尸进程处理.md
│ └── 补充-容器进程.md
├── etcd/
│ ├── 0. etcd常用操作.md
│ └── 协议理论知识/
│ ├── 1. cap原理.md
│ ├── 2. ACID理论.md
│ ├── 3. base理论.md
│ └── 4. raft协议.md
└── k8s/
├── README.md
├── client-go/
│ ├── 1- clientGo简介与章节安排.md
│ ├── 10. Controller-runtime原理分析.md
│ ├── 2-clientGo提供的四种客户端.md
│ ├── 3. apiserver中的list-watch机制.md
│ ├── 4. client informer机制简介.md
│ ├── 5. SharedInformerFactory机制.md
│ ├── 6. informer机制之cache.indexer机制.md
│ ├── 7. informer机制详解.md
│ ├── 8. client-go的workqueue详解.md
│ └── 9.从0到1使用kubebuilder创建crd.md
├── cni/
│ ├── 0.章节介绍.md
│ ├── 1. 网络基础知识.md
│ ├── 2. docker 4种 网络模式.md
│ ├── 3. docker容器网络的底层实现.md
│ ├── 4.k8s pod通信原理介绍.md
│ ├── 5. k8s 容器网络接口介绍.md
│ ├── 6.如何订制自己的cni.md
│ ├── 7. flannel原理浅析分析.md
│ └── 8. calico原理浅析md.md
├── install-k8s-from source code/
│ ├── 1-debian二进制安装v1.17 k8s.md
│ └── 2.window配置goland环境阅读kubernetes源码.md
├── kcm/
│ ├── 0-kcm启动流程.md
│ ├── 1-rs controller-manager源码分析.md
│ ├── 10-kcm-NodeLifecycleController源码分析.md
│ ├── 11.k8s node状态更新机制 .md
│ ├── 2-deployment controller-manager源码分析.md
│ ├── 3-k8s gc源码分析.md
│ ├── 3-k8s中以不同的策略删除资源时发生了什么.md
│ ├── 4-hpa-自定义metric server.md
│ ├── 4-hpa源码分析.md
│ ├── 5-job controller-manager源码分析.md
│ ├── 6-namespaces controller-manager源码分析.md
│ ├── 9-kubernetes污点和容忍度概念介绍.md
│ └── kcm篇源码分析总结.md
├── kube-apiserver/
│ ├── 0-apiserver笔记规划.md
│ ├── 1-v1.17 kube-apiserver启动参数介绍.md
│ ├── 10-kube-apiserver创建AggregatorServer.md
│ ├── 11-kube-apiserver 启动http和https服务.md
│ ├── 12-k8s之Authentication.md
│ ├── 13-k8s之Authorization.md
│ ├── 14-k8s之admission分析.md
│ ├── 15-k8s之etcd存储实现.md
│ ├── 16. 创建更新删除资源时apiserver做了什么工作.md
│ ├── 17-k8s之serviceaccount.md
│ ├── 18 event的定义.md
│ ├── 19. secret对象详解.md
│ ├── 2-kube-apiserver概述.md
│ ├── 20. kubectl exec原理介绍.md
│ ├── 21-kube-apiserver list-watch源码分析.md
│ ├── 3-k8s之资源介绍.md
│ ├── 4-scheme介绍.md
│ ├── 5-kube-apiserver启动流程汇总.md
│ ├── 6-kube-apiserver启动流程-资源注册+命令行初始.md
│ ├── 7-kube-apiserver创建APIServer通用配置.md
│ ├── 8-kube-apiserver创建APIExtensionsServer.md
│ └── 9-kube-apiserver 创建KubeAPIServer.md
├── kube-scheduler/
│ ├── 1. kube-scheduler简介.md
│ ├── 2-kube-scheduler源码分析.md
│ └── 3-如何编写一个scheduler plugin.md
├── kubectl/
│ ├── 0-ReadMe.md
│ ├── 1-kubectl 整体流程分析.md
│ ├── 2-client-go中连接apiserver的4种client介绍.md
│ ├── 3-kubectl Factory机制-上.md
│ ├── 4-kubectl Factor机制-下.md
│ ├── 5 visitor机制.md
│ ├── 6-kubectl中的所有visitor.md
│ ├── 7-kubectl create使用到的visitor.md
│ ├── 8- kubectl printer分析.md
│ └── 9-kubectl create整体流程分析.md
└── kubelet/
├── 0-readme.md
├── 1-kubelet 架构浅析.md
├── 10-k8s驱逐机制汇总.md
├── 2-kubelet初始化流程-上.md
├── 3-kubelet初始化流程-下.md
├── 4-kubelet 监听pod变化.md
├── 5-pod创建流程.md
├── 6-pod pleg更新流程.md
├── 7-pod delete流程.md
├── 8-kubelet gc流程.md
└── 9-kubelet驱逐源码分析.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*/.DS_Store
.DS_Store:
**/.DS_Store
.DS_Store?
================================================
FILE: README.md
================================================
# learning-k8s-source-code
从源码角度出发,学习k8s的原理。
目前打算以`kube-apiserver` `kube-controller-manager` `kube-scheduler` `kubelet` `proxy` 和 `kubectl` 这6个组件为主线进行源码级别的学习。
同时还顺便记录一些平时用到和k8s相关的知识,例如etcd, docker, linux等相关知识。
其他相关k8s组件的分析笔记发布在以下博客:
https://www.jianshu.com/c/b097c5e7eb9b
https://www.zhihu.com/column/c_1523054529113579520
https://blog.csdn.net/zxyuliwuzhognzx11/category_11880534.html?spm=1001.2014.3001.5482
================================================
FILE: docker/0-docker章节介绍.md
================================================
容器技术是 云发展的一个重要基础,docker就是当前很火的一种容器技术。
之前就知道docker利用了linux的cgruop, namespace + chroot + 联合文件系统实现的。
本章力求从源码角度对docker进行分析, docker版本为:https://github.com/moby/moby/tree/v19.03.9
章节安排:
(1)了解linux namespaces, cgroup, choot,联合文件系统的原理
(2)了解docker源码结构
(3)以常见的docker run nginx ls命令为主线, 从源码入手了解该命令背后的详细过程
================================================
FILE: docker/1. linux namespaces 知识准备.md
================================================
* [1 namespace 简介](#1-namespace-简介)
* [2\. pid namespace](#2-pid-namespace)
* [2\.1 如何查看一个进程的 pid namespace](#21-如何查看一个进程的-pid-namespace)
* [2\.2 子进程不共享父进程的pid namespaces](#22-子进程不共享父进程的pid-namespaces)
* [2\.3 pid namespace的原理](#23-pid-namespace的原理)
* [2\.4 task\_struct 结构图](#24-task_struct-结构图)
* [3 总结](#3-总结)
* [4\.参考](#4参考)
### 1 namespace 简介
`namespace(命名空间)` 是Linux提供的一种内核级别环境隔离的方法,很多编程语言也有 namespace 这样的功能,例如C++,Java等,编程语言的 namespace 是为了解决项目中能够在不同的命名空间里使用相同的函数名或者类名。而Linux的 namespace 也是为了实现资源能够在不同的命名空间里有相同的名称,譬如在 `A命名空间` 有个pid为1的进程,而在 `B命名空间` 中也可以有一个pid为1的进程。
有了 `namespace` 就可以实现基本的容器功能,著名的 `Docker` 也是使用了 namespace 来实现资源隔离的。
Linux支持6种资源的 `namespace`,分别为(文档):
| Type | Parameter | Linux Version |
| ------------------ | ------------- | ------------- |
| Mount namespaces | CLONE_NEWNS | Linux 2.4.19 |
| UTS namespaces | CLONE_NEWUTS | Linux 2.6.19 |
| IPC namespaces | CLONE_NEWIPC | Linux 2.6.19 |
| PID namespaces | CLONE_NEWPID | Linux 2.6.24 |
| Network namespaces | CLONE_NEWNET | Linux 2.6.24 |
| User namespaces | CLONE_NEWUSER | Linux 2.6.23 |
<br>
个人理解:namespace 就是对进程进行了内核资源的隔离(mount, uts, ipc, pid, network, user)这六种资源。
接下来以 pid 这个来介绍 namespaces是如何起作用的。
<br>
### 2. pid namespace
#### 2.1 如何查看一个进程的 pid namespace
/proc/pid/ns 目录下目前可以看到pid namespace
```
ps ajxf 查看到一个父进程,和子进程
4556 4574 4574 4574 ? -1 Ss 0 0:00 \_ nginx: master process nginx -g daemon off;
4574 4621 4574 4574 ? -1 S 101 0:00 \_ nginx: worker process
4574 4629 4574 4574 ? -1 S 101 0:00 \_ nginx: worker process
// 父进程
root@k8s-master:/proc/170/ns# ls -l /proc/4574/ns
total 0
lrwxrwxrwx 1 root root 0 Dec 5 08:49 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 ipc -> 'ipc:[4026532263]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 mnt -> 'mnt:[4026532331]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 net -> 'net:[4026532266]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 pid -> 'pid:[4026532333]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 pid_for_children -> 'pid:[4026532333]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Dec 5 08:49 uts -> 'uts:[4026532332]'
// 子进程和父进程有一样的namespaces
root@k8s-master:/proc/170/ns# ls -l /proc/4621/ns
total 0
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 ipc -> 'ipc:[4026532263]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 mnt -> 'mnt:[4026532331]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 net -> 'net:[4026532266]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 pid -> 'pid:[4026532333]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 pid_for_children -> 'pid:[4026532333]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 user -> 'user:[4026531837]'
lrwxrwxrwx 1 systemd-timesync systemd-journal 0 Dec 5 08:49 uts -> 'uts:[4026532332]
```
<br>
#### 2.2 子进程不共享父进程的pid namespaces
```
root@k8s-master:~# unshare --fork --pid --mount-proc sleep 100
```
<br>
```
1 701 701 701 ? -1 Ss 0 1:28 /usr/sbin/sshd -D
701 4462 4462 4462 ? -1 Ss 0 0:00 \_ sshd: root@pts/0,pts/1
4462 4497 4497 4497 pts/0 3994 Ss 0 0:00 \_ -bash
4497 3106 3106 4497 pts/0 3994 S 0 0:00 | \_ bash
3106 3994 3994 4497 pts/0 3994 S+ 0 0:00 | \_ unshare --fork --pid --mount-
3994 3995 3994 4497 pts/0 3994 S+ 0 0:00 | \_ sleep 100
```
<br>
```
// 这个是 sleep 100的进程,所以他的子进程和它是公用pid的
root@k8s-master:~# ls -l /proc/3995/ns
total 0
lrwxrwxrwx 1 root root 0 Dec 5 10:00 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Dec 5 10:00 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Dec 5 10:00 mnt -> 'mnt:[4026532334]'
lrwxrwxrwx 1 root root 0 Dec 5 10:00 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Dec 5 10:00 pid -> 'pid:[4026532335]' // 这里 pid 和pid_for_children是一样的
lrwxrwxrwx 1 root root 0 Dec 5 10:00 pid_for_children -> 'pid:[4026532335]'
lrwxrwxrwx 1 root root 0 Dec 5 10:00 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Dec 5 10:00 uts -> 'uts:[4026531838]'
root@k8s-master:~#
// 这个是 unshare 的进程,因为使用了 --pid mount,所以和父进程pid namespaces是不一样的
root@k8s-master:~# ls -l /proc/3994/ns
total 0
lrwxrwxrwx 1 root root 0 Dec 5 10:01 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Dec 5 10:01 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Dec 5 10:01 mnt -> 'mnt:[4026532334]'
lrwxrwxrwx 1 root root 0 Dec 5 10:01 net -> 'net:[4026531992]'
lrwxrwxrwx 1 root root 0 Dec 5 10:01 pid -> 'pid:[4026531836]' // 这里就是不一样的,因为这个进程是
lrwxrwxrwx 1 root root 0 Dec 5 10:01 pid_for_children -> 'pid:[4026532335]'
lrwxrwxrwx 1 root root 0 Dec 5 10:01 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Dec 5 10:01 uts -> 'uts:[4026531838]'
```
#### 2.3 pid namespace的原理
为了让每个进程都可以从属于某一个namespace,Linux内核为进程描述符添加了一个 `struct nsproxy` 的结构,如下:
```
struct task_struct {
...
/* namespaces */
struct nsproxy *nsproxy;
...
}
struct nsproxy {
atomic_t count;
struct uts_namespace *uts_ns;
struct ipc_namespace *ipc_ns;
struct mnt_namespace *mnt_ns;
struct pid_namespace *pid_ns;
struct user_namespace *user_ns;
struct net *net_ns;
};
```
从 `struct nsproxy` 结构的定义可以看出,Linux为每种不同类型的资源定义了不同的命名空间结构体进行管理。比如对于 `pid命名空间` 定义了 `struct pid_namespace` 结构来管理 。由于 namespace 涉及的资源种类比较多,所以本文主要以 `pid命名空间` 作为分析的对象。
我们先来看看管理 `pid命名空间` 的 `struct pid_namespace` 结构的定义:
```
struct pid_namespace {
struct kref kref;
struct pidmap pidmap[PIDMAP_ENTRIES];
int last_pid;
struct task_struct *child_reaper;
struct kmem_cache *pid_cachep;
unsigned int level;
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
struct vfsmount *proc_mnt;
#endif
};
```
因为 `struct pid_namespace` 结构主要用于为当前 `pid命名空间` 分配空闲的pid,所以定义比较简单:
- `kref` 成员是一个引用计数器,用于记录引用这个结构的进程数
- `pidmap` 成员用于快速找到可用pid的位图
- `last_pid` 成员是记录最后一个可用的pid
- `level` 成员记录当前 `pid命名空间` 所在的层次
- `parent` 成员记录当前 `pid命名空间` 的父命名空间
由于 `pid命名空间` 是分层的,也就是说新创建一个 `pid命名空间` 时会记录父级 `pid命名空间` 到 `parent` 字段中,所以随着 `pid命名空间` 的创建,在内核中会形成一颗 `pid命名空间` 的树,如下图(图片来源):

第0层的 `pid命名空间` 是 `init` 进程所在的命名空间。如果一个进程所在的 `pid命名空间` 为 `N`,那么其在 `0 ~ N 层pid命名空间` 都有一个唯一的pid号。也就是说 `高层pid命名空间` 的进程对 `低层pid命名空间` 的进程是可见的,但是 `低层pid命名空间`的进程对 `高层pid命名空间` 的进程是不可见的。
由于在 `第N层pid命名空间` 的进程其在 `0 ~ N层pid命名空间` 都有一个唯一的pid号,所以在进程描述符中通过 `pids` 成员来记录其在每个层的pid号,代码如下:
```
struct task_struct {
...
struct pid_link pids[PIDTYPE_MAX];
...
}
enum pid_type {
PIDTYPE_PID,
PIDTYPE_PGID,
PIDTYPE_SID,
PIDTYPE_MAX
};
struct upid {
int nr;
struct pid_namespace *ns;
struct hlist_node pid_chain;
};
struct pid {
atomic_t count;
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
unsigned int level;
struct upid numbers[1];
};
struct pid_link {
struct hlist_node node;
struct pid *pid;
};
```
这几个结构的关系如下图:

我们主要关注 `struct pid` 这个结构,`struct pid` 有个类型为 `struct upid` 的成员 `numbers`,其定义为只有一个元素的数组,但是其实是一个动态的数据,它的元素个数与 `level` 的值一致,也就是说当 `level` 的值为5时,那么 `numbers` 成员就是一个拥有5个元素的数组。而每个元素记录了其在每层 `pid命名空间` 的pid号,而 `struct upid` 结构的 `nr` 成员就是用于记录进程在不同层级 `pid命名空间` 的pid号。
我们通过代码来看看怎么为进程分配pid号的,在内核中是用过 `alloc_pid()` 函数分配pid号的,代码如下:
```
struct pid *alloc_pid(struct pid_namespace *ns)
{
struct pid *pid;
enum pid_type type;
int i, nr;
struct pid_namespace *tmp;
struct upid *upid;
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid)
goto out;
tmp = ns;
for (i = ns->level; i >= 0; i--) {
nr = alloc_pidmap(tmp); // 为当前进程所在的不同层级pid命名空间分配一个pid
if (nr < 0)
goto out_free;
pid->numbers[i].nr = nr; // 对应i层namespace中的pid数字
pid->numbers[i].ns = tmp; // 对应i层namespace的实体
tmp = tmp->parent;
}
get_pid_ns(ns);
pid->level = ns->level;
atomic_set(&pid->count, 1);
for (type = 0; type < PIDTYPE_MAX; ++type)
INIT_HLIST_HEAD(&pid->tasks[type]);
spin_lock_irq(&pidmap_lock);
for (i = ns->level; i >= 0; i--) {
upid = &pid->numbers[i];
// 把upid连接到全局pid中, 用于快速查找pid
hlist_add_head_rcu(&upid->pid_chain,
&pid_hash[pid_hashfn(upid->nr, upid->ns)]);
}
spin_unlock_irq(&pidmap_lock);
out:
return pid;
...
}
```
上面的代码中,那个 `for (i = ns->level; i >= 0; i--)` 就是通过 `parent` 成员不断向上检索为不同层级的 `pid命名空间`分配一个唯一的pid号,并且保存到对应的 `nr` 字段中。另外,还会把进程所在各个层级的pid号添加到全局pid哈希表中,这样做是为了通过pid号快速找到进程。
现在我们来看看怎么通过pid号快速找到一个进程,在内核中 `find_get_pid()` 函数用来通过pid号查找对应的 `struct pid`结构,代码如下(find_get_pid() -> find_vpid() -> find_pid_ns()):
```
struct pid *find_get_pid(pid_t nr)
{
struct pid *pid;
rcu_read_lock();
pid = get_pid(find_vpid(nr));
rcu_read_unlock();
return pid;
}
struct pid *find_vpid(int nr)
{
return find_pid_ns(nr, current->nsproxy->pid_ns);
}
struct pid *find_pid_ns(int nr, struct pid_namespace *ns)
{
struct hlist_node *elem;
struct upid *pnr;
hlist_for_each_entry_rcu(pnr, elem,
&pid_hash[pid_hashfn(nr, ns)], pid_chain)
if (pnr->nr == nr && pnr->ns == ns)
return container_of(pnr, struct pid,
numbers[ns->level]);
return NULL;
}
```
通过pid号查找 `struct pid` 结构时,首先会把进程pid号和当前进程的 `pid命名空间` 传入到 `find_pid_ns()` 函数,而在 `find_pid_ns()` 函数中通过全局pid哈希表来快速查找对应的 `struct pid` 结构。获取到 `struct pid` 结构后就可以很容易地获取到进程对应的进程描述符,例如可以通过 `pid_task()` 函数来获取 `struct pid` 结构对应进程描述符,由于代码比较简单,这里就不再分析了。
#### 2.4 task_struct 结构图
```
struct task_struct
{
/*
1. state: 进程执行时,它会根据具体情况改变状态。进程状态是进程调度和对换的依据。Linux中的进程主要有如下状态:
1) TASK_RUNNING: 可运行
处于这种状态的进程,只有两种状态:
1.1) 正在运行
正在运行的进程就是当前进程(由current所指向的进程)
1.2) 正准备运行
准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源,系统中有一个运行队列(run_queue),用来容纳所有处于可运行状态的进程,调度程序执行时,从中选择一个进程投入运行
2) TASK_INTERRUPTIBLE: 可中断的等待状态,是针对等待某事件或其他资源的睡眠进程设置的,在内核发送信号给该进程表明事件已经发生时,进程状态变为TASK_RUNNING,它只要调度器选中该进程即可恢复执行
3) TASK_UNINTERRUPTIBLE: 不可中断的等待状态
处于该状态的进程正在等待某个事件(event)或某个资源,它肯定位于系统中的某个等待队列(wait_queue)中,处于不可中断等待态的进程是因为硬件环境不能满足而等待,例如等待特定的系统资源,它任何情况下都不能被打断,只能用特定的方式来唤醒它,例如唤醒函数wake_up()等
它们不能由外部信号唤醒,只能由内核亲自唤醒
4) TASK_ZOMBIE: 僵死
进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。
5) TASK_STOPPED: 暂停
此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态
6) TASK_TRACED
从本质上来说,这属于TASK_STOPPED状态,用于从停止的进程中,将当前被调试的进程与常规的进程区分开来
7) TASK_DEAD
父进程wait系统调用发出后,当子进程退出时,父进程负责回收子进程的全部资源,子进程进入TASK_DEAD状态
8) TASK_SWAPPING: 换入/换出
*/
volatile long state;
/*
2. stack
进程内核栈,进程通过alloc_thread_info函数分配它的内核栈,通过free_thread_info函数释放所分配的内核栈
*/
void *stack;
/*
3. usage
进程描述符使用计数,被置为2时,表示进程描述符正在被使用而且其相应的进程处于活动状态
*/
atomic_t usage;
/*
4. flags
flags是进程当前的状态标志(注意和运行状态区分)
1) #define PF_ALIGNWARN 0x00000001: 显示内存地址未对齐警告
2) #define PF_PTRACED 0x00000010: 标识是否是否调用了ptrace
3) #define PF_TRACESYS 0x00000020: 跟踪系统调用
4) #define PF_FORKNOEXEC 0x00000040: 已经完成fork,但还没有调用exec
5) #define PF_SUPERPRIV 0x00000100: 使用超级用户(root)权限
6) #define PF_DUMPCORE 0x00000200: dumped core
7) #define PF_SIGNALED 0x00000400: 此进程由于其他进程发送相关信号而被杀死
8) #define PF_STARTING 0x00000002: 当前进程正在被创建
9) #define PF_EXITING 0x00000004: 当前进程正在关闭
10) #define PF_USEDFPU 0x00100000: Process used the FPU this quantum(SMP only)
#define PF_DTRACE 0x00200000: delayed trace (used on m68k)
*/
unsigned int flags;
/*
5. ptrace
ptrace系统调用,成员ptrace被设置为0时表示不需要被跟踪,它的可能取值如下:
linux-2.6.38.8/include/linux/ptrace.h
1) #define PT_PTRACED 0x00000001
2) #define PT_DTRACE 0x00000002: delayed trace (used on m68k, i386)
3) #define PT_TRACESYSGOOD 0x00000004
4) #define PT_PTRACE_CAP 0x00000008: ptracer can follow suid-exec
5) #define PT_TRACE_FORK 0x00000010
6) #define PT_TRACE_VFORK 0x00000020
7) #define PT_TRACE_CLONE 0x00000040
8) #define PT_TRACE_EXEC 0x00000080
9) #define PT_TRACE_VFORK_DONE 0x00000100
10) #define PT_TRACE_EXIT 0x00000200
*/
unsigned int ptrace;
unsigned long ptrace_message;
siginfo_t *last_siginfo;
/*
6. lock_depth
用于表示获取大内核锁的次数,如果进程未获得过锁,则置为-1
*/
int lock_depth;
/*
7. oncpu
在SMP上帮助实现无加锁的进程切换(unlocked context switches)
*/
#ifdef CONFIG_SMP
#ifdef __ARCH_WANT_UNLOCKED_CTXSW
int oncpu;
#endif
#endif
/*
8. 进程调度
1) prio: 调度器考虑的优先级保存在prio,由于在某些情况下内核需要暂时提高进程的优先级,因此需要第三个成员来表示(除了static_prio、normal_prio之外),由于这些改变不是持久的,因此静态(static_prio)和普通(normal_prio)优先级不受影响
2) static_prio: 用于保存进程的"静态优先级",静态优先级是进程"启动"时分配的优先级,它可以用nice、sched_setscheduler系统调用修改,否则在进程运行期间会一直保持恒定
3) normal_prio: 表示基于进程的"静态优先级"和"调度策略"计算出的优先级,因此,即使普通进程和实时进程具有相同的静态优先级(static_prio),其普通优先级(normal_prio)也是不同的。进程分支时(fork),新创建的子进程会集成普通优先级
*/
int prio, static_prio, normal_prio;
/*
4) rt_priority: 表示实时进程的优先级,需要明白的是,"实时进程优先级"和"普通进程优先级"有两个独立的范畴,实时进程即使是最低优先级也高于普通进程,最低的实时优先级为0,最高的优先级为99,值越大,表明优先级越高
*/
unsigned int rt_priority;
/*
5) sched_class: 该进程所属的调度类,目前内核中有实现以下四种:
5.1) static const struct sched_class fair_sched_class;
5.2) static const struct sched_class rt_sched_class;
5.3) static const struct sched_class idle_sched_class;
5.4) static const struct sched_class stop_sched_class;
*/
const struct sched_class *sched_class;
/*
6) se: 用于普通进程的调用实体
调度器不限于调度进程,还可以处理更大的实体,这可以实现"组调度",可用的CPU时间可以首先在一般的进程组(例如所有进程可以按所有者分组)之间分配,接下来分配的时间在组内再次分配
这种一般性要求调度器不直接操作进程,而是处理"可调度实体",一个实体有sched_entity的一个实例标识
在最简单的情况下,调度在各个进程上执行,由于调度器设计为处理可调度的实体,在调度器看来各个进程也必须也像这样的实体,因此se在task_struct中内嵌了一个sched_entity实例,调度器可据此操作各个task_struct
*/
struct sched_entity se;
/*
7) rt: 用于实时进程的调用实体
*/
struct sched_rt_entity rt;
#ifdef CONFIG_PREEMPT_NOTIFIERS
/*
9. preempt_notifier
preempt_notifiers结构体链表
*/
struct hlist_head preempt_notifiers;
#endif
/*
10. fpu_counter
FPU使用计数
*/
unsigned char fpu_counter;
#ifdef CONFIG_BLK_DEV_IO_TRACE
/*
11. btrace_seq
blktrace是一个针对Linux内核中块设备I/O层的跟踪工具
*/
unsigned int btrace_seq;
#endif
/*
12. policy
policy表示进程的调度策略,目前主要有以下五种:
1) #define SCHED_NORMAL 0: 用于普通进程,它们通过完全公平调度器来处理
2) #define SCHED_FIFO 1: 先来先服务调度,由实时调度类处理
3) #define SCHED_RR 2: 时间片轮转调度,由实时调度类处理
4) #define SCHED_BATCH 3: 用于非交互、CPU使用密集的批处理进程,通过完全公平调度器来处理,调度决策对此类进程给与"冷处理",它们绝不会抢占CFS调度器处理的另一个进程,因此不会干扰交互式进程,如果不打算用nice降低进程的静态优先级,同时又不希望该进程影响系统的交互性,最适合用该调度策略
5) #define SCHED_IDLE 5: 可用于次要的进程,其相对权重总是最小的,也通过完全公平调度器来处理。要注意的是,SCHED_IDLE不负责调度空闲进程,空闲进程由内核提供单独的机制来处理
只有root用户能通过sched_setscheduler()系统调用来改变调度策略
*/
unsigned int policy;
/*
13. cpus_allowed
cpus_allowed是一个位域,在多处理器系统上使用,用于控制进程可以在哪里处理器上运行
*/
cpumask_t cpus_allowed;
/*
14. RCU同步原语
*/
#ifdef CONFIG_TREE_PREEMPT_RCU
int rcu_read_lock_nesting;
char rcu_read_unlock_special;
struct rcu_node *rcu_blocked_node;
struct list_head rcu_node_entry;
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)
/*
15. sched_info
用于调度器统计进程的运行信息
*/
struct sched_info sched_info;
#endif
/*
16. tasks
通过list_head将当前进程的task_struct串联进内核的进程列表中,构建;linux进程链表
*/
struct list_head tasks;
/*
17. pushable_tasks
limit pushing to one attempt
*/
struct plist_node pushable_tasks;
/*
18. 进程地址空间
1) mm: 指向进程所拥有的内存描述符
2) active_mm: active_mm指向进程运行时所使用的内存描述符
对于普通进程而言,这两个指针变量的值相同。但是,内核线程不拥有任何内存描述符,所以它们的mm成员总是为NULL。当内核线程得以运行时,它的active_mm成员被初始化为前一个运行进程的active_mm值
*/
struct mm_struct *mm, *active_mm;
/*
19. exit_state
进程退出状态码
*/
int exit_state;
/*
20. 判断标志
1) exit_code
exit_code用于设置进程的终止代号,这个值要么是_exit()或exit_group()系统调用参数(正常终止),要么是由内核提供的一个错误代号(异常终止)
2) exit_signal
exit_signal被置为-1时表示是某个线程组中的一员。只有当线程组的最后一个成员终止时,才会产生一个信号,以通知线程组的领头进程的父进程
*/
int exit_code, exit_signal;
/*
3) pdeath_signal
pdeath_signal用于判断父进程终止时发送信号
*/
int pdeath_signal;
/*
4) personality用于处理不同的ABI,它的可能取值如下:
enum
{
PER_LINUX = 0x0000,
PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT,
PER_LINUX_FDPIC = 0x0000 | FDPIC_FUNCPTRS,
PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE,
PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS |
WHOLE_SECONDS | SHORT_INODE,
PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS,
PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE,
PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS,
PER_BSD = 0x0006,
PER_SUNOS = 0x0006 | STICKY_TIMEOUTS,
PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE,
PER_LINUX32 = 0x0008,
PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB,
PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS,
PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS,
PER_IRIX64 = 0x000b | STICKY_TIMEOUTS,
PER_RISCOS = 0x000c,
PER_SOLARIS = 0x000d | STICKY_TIMEOUTS,
PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO,
PER_OSF4 = 0x000f,
PER_HPUX = 0x0010,
PER_MASK = 0x00ff,
};
*/
unsigned int personality;
/*
5) did_exec
did_exec用于记录进程代码是否被execve()函数所执行
*/
unsigned did_exec:1;
/*
6) in_execve
in_execve用于通知LSM是否被do_execve()函数所调用
*/
unsigned in_execve:1;
/*
7) in_iowait
in_iowait用于判断是否进行iowait计数
*/
unsigned in_iowait:1;
/*
8) sched_reset_on_fork
sched_reset_on_fork用于判断是否恢复默认的优先级或调度策略
*/
unsigned sched_reset_on_fork:1;
/*
21. 进程标识符(PID)
在CONFIG_BASE_SMALL配置为0的情况下,PID的取值范围是0到32767,即系统中的进程数最大为32768个
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
在Linux系统中,一个线程组中的所有线程使用和该线程组的领头线程(该组中的第一个轻量级进程)相同的PID,并被存放在tgid成员中。只有线程组的领头线程的pid成员才会被设置为与tgid相同的值。注意,getpid()系统调用
返回的是当前进程的tgid值而不是pid值。
*/
pid_t pid;
pid_t tgid;
#ifdef CONFIG_CC_STACKPROTECTOR
/*
22. stack_canary
防止内核堆栈溢出,在GCC编译内核时,需要加上-fstack-protector选项
*/
unsigned long stack_canary;
#endif
/*
23. 表示进程亲属关系的成员
1) real_parent: 指向其父进程,如果创建它的父进程不再存在,则指向PID为1的init进程
2) parent: 指向其父进程,当它终止时,必须向它的父进程发送信号。它的值通常与real_parent相同
*/
struct task_struct *real_parent;
struct task_struct *parent;
/*
3) children: 表示链表的头部,链表中的所有元素都是它的子进程(子进程链表)
4) sibling: 用于把当前进程插入到兄弟链表中(连接到父进程的子进程链表(兄弟链表))
5) group_leader: 指向其所在进程组的领头进程
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
struct list_head ptraced;
struct list_head ptrace_entry;
struct bts_context *bts;
/*
24. pids
PID散列表和链表
*/
struct pid_link pids[PIDTYPE_MAX];
/*
25. thread_group
线程组中所有进程的链表
*/
struct list_head thread_group;
/*
26. do_fork函数
1) vfork_done
在执行do_fork()时,如果给定特别标志,则vfork_done会指向一个特殊地址
2) set_child_tid、clear_child_tid
如果copy_process函数的clone_flags参数的值被置为CLONE_CHILD_SETTID或CLONE_CHILD_CLEARTID,则会把child_tidptr参数的值分别复制到set_child_tid和clear_child_tid成员。这些标志说明必须改变子
进程用户态地址空间的child_tidptr所指向的变量的值。
*/
struct completion *vfork_done;
int __user *set_child_tid;
int __user *clear_child_tid;
/*
27. 记录进程的I/O计数(时间)
1) utime
用于记录进程在"用户态"下所经过的节拍数(定时器)
2) stime
用于记录进程在"内核态"下所经过的节拍数(定时器)
3) utimescaled
用于记录进程在"用户态"的运行时间,但它们以处理器的频率为刻度
4) stimescaled
用于记录进程在"内核态"的运行时间,但它们以处理器的频率为刻度
*/
cputime_t utime, stime, utimescaled, stimescaled;
/*
5) gtime
以节拍计数的虚拟机运行时间(guest time)
*/
cputime_t gtime;
/*
6) prev_utime、prev_stime是先前的运行时间
*/
cputime_t prev_utime, prev_stime;
/*
7) nvcsw
自愿(voluntary)上下文切换计数
8) nivcsw
非自愿(involuntary)上下文切换计数
*/
unsigned long nvcsw, nivcsw;
/*
9) start_time
进程创建时间
10) real_start_time
进程睡眠时间,还包含了进程睡眠时间,常用于/proc/pid/stat,
*/
struct timespec start_time;
struct timespec real_start_time;
/*
11) cputime_expires
用来统计进程或进程组被跟踪的处理器时间,其中的三个成员对应着cpu_timers[3]的三个链表
*/
struct task_cputime cputime_expires;
struct list_head cpu_timers[3];
#ifdef CONFIG_DETECT_HUNG_TASK
/*
12) last_switch_count
nvcsw和nivcsw的总和
*/
unsigned long last_switch_count;
#endif
struct task_io_accounting ioac;
#if defined(CONFIG_TASK_XACCT)
u64 acct_rss_mem1;
u64 acct_vm_mem1;
cputime_t acct_timexpd;
#endif
/*
28. 缺页统计
*/
unsigned long min_flt, maj_flt;
/*
29. 进程权能
*/
const struct cred *real_cred;
const struct cred *cred;
struct mutex cred_guard_mutex;
struct cred *replacement_session_keyring;
/*
30. comm[TASK_COMM_LEN]
相应的程序名
*/
char comm[TASK_COMM_LEN];
/*
31. 文件
1) fs
用来表示进程与文件系统的联系,包括当前目录和根目录
2) files
表示进程当前打开的文件
*/
int link_count, total_link_count;
struct fs_struct *fs;
struct files_struct *files;
#ifdef CONFIG_SYSVIPC
/*
32. sysvsem
进程通信(SYSVIPC)
*/
struct sysv_sem sysvsem;
#endif
/*
33. 处理器特有数据
*/
struct thread_struct thread;
/*
34. nsproxy
命名空间
*/
struct nsproxy *nsproxy;
/*
35. 信号处理
1) signal: 指向进程的信号描述符
2) sighand: 指向进程的信号处理程序描述符
*/
struct signal_struct *signal;
struct sighand_struct *sighand;
/*
3) blocked: 表示被阻塞信号的掩码
4) real_blocked: 表示临时掩码
*/
sigset_t blocked, real_blocked;
sigset_t saved_sigmask;
/*
5) pending: 存放私有挂起信号的数据结构
*/
struct sigpending pending;
/*
6) sas_ss_sp: 信号处理程序备用堆栈的地址
7) sas_ss_size: 表示堆栈的大小
*/
unsigned long sas_ss_sp;
size_t sas_ss_size;
/*
8) notifier
设备驱动程序常用notifier指向的函数来阻塞进程的某些信号
9) otifier_data
指的是notifier所指向的函数可能使用的数据。
10) otifier_mask
标识这些信号的位掩码
*/
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
/*
36. 进程审计
*/
struct audit_context *audit_context;
#ifdef CONFIG_AUDITSYSCALL
uid_t loginuid;
unsigned int sessionid;
#endif
/*
37. secure computing
*/
seccomp_t seccomp;
/*
38. 用于copy_process函数使用CLONE_PARENT标记时
*/
u32 parent_exec_id;
u32 self_exec_id;
/*
39. alloc_lock
用于保护资源分配或释放的自旋锁
*/
spinlock_t alloc_lock;
/*
40. 中断
*/
#ifdef CONFIG_GENERIC_HARDIRQS
struct irqaction *irqaction;
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
unsigned int irq_events;
int hardirqs_enabled;
unsigned long hardirq_enable_ip;
unsigned int hardirq_enable_event;
unsigned long hardirq_disable_ip;
unsigned int hardirq_disable_event;
int softirqs_enabled;
unsigned long softirq_disable_ip;
unsigned int softirq_disable_event;
unsigned long softirq_enable_ip;
unsigned int softirq_enable_event;
int hardirq_context;
int softirq_context;
#endif
/*
41. pi_lock
task_rq_lock函数所使用的锁
*/
spinlock_t pi_lock;
#ifdef CONFIG_RT_MUTEXES
/*
42. 基于PI协议的等待互斥锁,其中PI指的是priority inheritance/9优先级继承)
*/
struct plist_head pi_waiters;
struct rt_mutex_waiter *pi_blocked_on;
#endif
#ifdef CONFIG_DEBUG_MUTEXES
/*
43. blocked_on
死锁检测
*/
struct mutex_waiter *blocked_on;
#endif
/*
44. lockdep,
*/
#ifdef CONFIG_LOCKDEP
# define MAX_LOCK_DEPTH 48UL
u64 curr_chain_key;
int lockdep_depth;
unsigned int lockdep_recursion;
struct held_lock held_locks[MAX_LOCK_DEPTH];
gfp_t lockdep_reclaim_gfp;
#endif
/*
45. journal_info
JFS文件系统
*/
void *journal_info;
/*
46. 块设备链表
*/
struct bio *bio_list, **bio_tail;
/*
47. reclaim_state
内存回收
*/
struct reclaim_state *reclaim_state;
/*
48. backing_dev_info
存放块设备I/O数据流量信息
*/
struct backing_dev_info *backing_dev_info;
/*
49. io_context
I/O调度器所使用的信息
*/
struct io_context *io_context;
/*
50. CPUSET功能
*/
#ifdef CONFIG_CPUSETS
nodemask_t mems_allowed;
int cpuset_mem_spread_rotor;
#endif
/*
51. Control Groups
*/
#ifdef CONFIG_CGROUPS
struct css_set *cgroups;
struct list_head cg_list;
#endif
/*
52. robust_list
futex同步机制
*/
#ifdef CONFIG_FUTEX
struct robust_list_head __user *robust_list;
#ifdef CONFIG_COMPAT
struct compat_robust_list_head __user *compat_robust_list;
#endif
struct list_head pi_state_list;
struct futex_pi_state *pi_state_cache;
#endif
#ifdef CONFIG_PERF_EVENTS
struct perf_event_context *perf_event_ctxp;
struct mutex perf_event_mutex;
struct list_head perf_event_list;
#endif
/*
53. 非一致内存访问(NUMA Non-Uniform Memory Access)
*/
#ifdef CONFIG_NUMA
struct mempolicy *mempolicy; /* Protected by alloc_lock */
short il_next;
#endif
/*
54. fs_excl
文件系统互斥资源
*/
atomic_t fs_excl;
/*
55. rcu
RCU链表
*/
struct rcu_head rcu;
/*
56. splice_pipe
管道
*/
struct pipe_inode_info *splice_pipe;
/*
57. delays
延迟计数
*/
#ifdef CONFIG_TASK_DELAY_ACCT
struct task_delay_info *delays;
#endif
/*
58. make_it_fail
fault injection
*/
#ifdef CONFIG_FAULT_INJECTION
int make_it_fail;
#endif
/*
59. dirties
FLoating proportions
*/
struct prop_local_single dirties;
/*
60. Infrastructure for displayinglatency
*/
#ifdef CONFIG_LATENCYTOP
int latency_record_count;
struct latency_record latency_record[LT_SAVECOUNT];
#endif
/*
61. time slack values,常用于poll和select函数
*/
unsigned long timer_slack_ns;
unsigned long default_timer_slack_ns;
/*
62. scm_work_list
socket控制消息(control message)
*/
struct list_head *scm_work_list;
/*
63. ftrace跟踪器
*/
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
int curr_ret_stack;
struct ftrace_ret_stack *ret_stack;
unsigned long long ftrace_timestamp;
atomic_t trace_overrun;
atomic_t tracing_graph_pause;
#endif
#ifdef CONFIG_TRACING
unsigned long trace;
unsigned long trace_recursion;
#endif
};
```
### 3 总结
(1)pid 就是一个编号,通过pid namespace的引入,让每个进程,可以存在多个ns命名空间。比如2.2中的sleep,其实就是存在两层的ns。
第一层就是父进程所在的那层,父进程是直接fork和bash, 以及真正的1号进程的pid namespaces是一致的。
第二层就是 自己新创建的这层。
分配pid的时候,首先在第二层分配给sleep pid=1(这个没进去,看不见)
然后再再父进程的ns,给sleep 分配的pid = 3994
这样的话,就实现了进程隔离,因为子进程在第二层看到的 pid=1,所以它在第二层只能看到,自己产生的所有进程,从而达到了隔离。
### 4.参考
[容器原理之 - namespace](https://mp.weixin.qq.com/s/FnuOMbWAhLQoiCBA_NFYXA)
[Linux-进程描述符 task_struct 详解](https://www.cnblogs.com/JohnABC/p/9084750.html)
================================================
FILE: docker/10. 如何下载并二进制编译docker源码.md
================================================
* [1\. 如何下载docker源码](#1-如何下载docker源码)
* [2\. docker源码目录解析](#2-docker源码目录解析)
* [3\. 二进制编译docker源码](#3-二进制编译docker源码)
* [3\.1 下载需要编译的源代码](#31-下载需要编译的源代码)
* [3\.2 通过容器编译](#32-通过容器编译)
### 1. 如何下载docker源码
在下载docker源码的时候,发现有moby、docker-ce与docker-ee项目。
docker是一家公司,其中的一个产品就是docker。docker-ce是 免费版本。docker-ee的商用版本。目前docker-ee没有git repo。 docker-ce repo处于废弃状态。
docker将docker进行了开源,开源项目的名字是 moby。
至于为什么这么做,可以参考以下的issue。
https://www.zhihu.com/question/58805021
https://github.com/moby/moby/pull/32691
所以研究源码直接研究moby就可以了:
### 2. docker源码目录解析
```
├── AUTHORS
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── Dockerfile.aarch64
├── Dockerfile.armhf
├── Dockerfile.ppc64le
├── Dockerfile.s390x
├── Dockerfile.simple
├── Dockerfile.solaris
├── Dockerfile.windows
├── LICENSE
├── MAINTAINERS
├── Makefile
├── NOTICE
├── README.md
├── ROADMAP.md
├── VENDORING.md
├── VERSION
├── api api目录是docker cli或者第三方软件与docker daemon进行交互的api库,它是HTTP REST API. api/types:是被docker client和server共用的一些类型定义,比如多种对象,options, responses等。大部分是手工写的代码,也有部分是通过swagger自动生成的。
├── builder docker build dockerfile实现相关代码
├── cli Docker命令行接口,定义了docker支持的所有命令。例如docker stop等
├── client docker client端(发送http请求)。定义所有命令的client请求
├── cmd dockerd命令行实现,docker,dockerd的启动函数
├── container 和容器相关的数据结构定义,比如容器状态,容器的io,容器的环境变量
├── contrib 包括脚本,镜像和其它一些有用的工具,并不属于docker发布的一部分,正因为如此,它们可能会过时
├── daemon docker daemon实现
├── distribution docker镜像仓库相关功能代码,如docker push,docker pull
├── dockerversion docker镜像仓库相关功能代码,如docker push,docker pull
├── docs 文档相关
├── experimental 开启docker实验特性的相关文档说明
├── hack 与编译相关的工具目录
├── hooks 编译相关的钩子
├── image 镜像存储相关操作代码
├── integration-cli 集成测试相关命令行
├── keys 和测试相关的key
├── layer 镜像层相关操作代码
├── libcontainerd 与containerd通信相关lib
├── man 生成docker手册相关的代码
├── migrate 用于转换老的镜像层次,主要是转v1
├── oci 支持oci相关实现(容器运行时标准)
├── opts 处理命令选项相关
├── pkg 工具包。处理字符串,url,系统相关信号,锁相关工具
├── plugin docker插件处理相关实现
├── poule.yml
├── profiles linux下安全相关处理,apparmor和seccomp.
├── project 文档相关
├── reference 镜像仓库reference管理
├── registry 镜像仓库相关代码
├── restartmanager 容器重启策略实现
├── runconfig 容器运行相关配置操作
├── vendor go语言的目录,依赖第三方库目录
├── vendor.conf
└── volume docker volume相关的代码实现
```
### 3. 二进制编译docker源码-17.05.0版本
直接看源码肯定会在一些地方卡住,所以最好的办法就是编译源码,通过打日志/调试的方式来确定具体实现细节。
#### 3.1 下载需要编译的源代码
这里我是下载的 https://github.com/moby/moby/blob/v17.05.0-ce
```
# git clone https://github.com/moby/moby.git -b v17.05.0-ce
```
然后修改文件项目为: `/home/zoux/data/golang/src/github.com/docker/docker`
#### 3.2 通过容器编译
docker开发环境本质上是创建一个docker镜像,镜像里包含了docker的所有开发运行环境,本地代码通过挂载的方式放到容器中运行。
dockercore/docker就是官方提供的编译镜像。
```
docker run --rm -it --privileged -v /home/zoux/data/golang/src/github.com/docker/docker:/go/src/github.com/docker/docker dockercore/docker bash
## 进去之后可以直接运行 该命令进行编译
root@ab1bf697b6a6:/go/src/github.com/docker/docker# ./hack/make.sh binary
bundles/17.05.0-ce already exists. Removing.
---> Making bundle: binary (in bundles/17.05.0-ce/binary)
Building: bundles/17.05.0-ce/binary-client/docker-17.05.0-ce
Created binary: bundles/17.05.0-ce/binary-client/docker-17.05.0-ce
Building: bundles/17.05.0-ce/binary-daemon/dockerd-17.05.0-ce
Created binary: bundles/17.05.0-ce/binary-daemon/dockerd-17.05.0-ce
Copying nested executables into bundles/17.05.0-ce/binary-daemon
## 还可以自己设置tag
root@ab1bf697b6a6:/go/src/github.com/docker/docker# export DOCKER_GITCOMMIT=v17.05-zx
root@ab1bf697b6a6:/go/src/github.com/docker/docker# ./hack/make.sh binary
bundles/17.05.0-ce already exists. Removing.
---> Making bundle: binary (in bundles/17.05.0-ce/binary)
Building: bundles/17.05.0-ce/binary-client/docker-17.05.0-ce
Created binary: bundles/17.05.0-ce/binary-client/docker-17.05.0-ce
Building: bundles/17.05.0-ce/binary-daemon/dockerd-17.05.0-ce
Created binary: bundles/17.05.0-ce/binary-daemon/dockerd-17.05.0-ce
Copying nested executables into bundles/17.05.0-ce/binary-daemon
root@ab1bf697b6a6:/go/src/github.com/docker/docker# ./bundles/17.05.0-ce/binary-daemon/dockerd --version
Docker version 17.05.0-ce, build v17.05-zx
## 下载到本地, 一定要是dockerd-17.05.0-ce,而不是dockerd, dockerd只是一个链接文件
docker cp ab1bf697b6a6:/go/src/github.com/docker/docker/bundles/17.05.0-ce/binary-daemon/dockerd-17.05.0-ce /home/zoux/dockerd
root@ab1bf697b6a6:/go/src/github.com/docker/docker/bundles/17.05.0-ce/binary-daemon# ls -l
total 68704
-rwxr-xr-x 1 root root 8997448 Feb 23 09:12 docker-containerd
-rwxr-xr-x 1 root root 8448168 Feb 23 09:12 docker-containerd-ctr
-rw-r--r-- 1 root root 56 Feb 23 09:12 docker-containerd-ctr.md5
-rw-r--r-- 1 root root 88 Feb 23 09:12 docker-containerd-ctr.sha256
-rwxr-xr-x 1 root root 3047240 Feb 23 09:12 docker-containerd-shim
-rw-r--r-- 1 root root 57 Feb 23 09:12 docker-containerd-shim.md5
-rw-r--r-- 1 root root 89 Feb 23 09:12 docker-containerd-shim.sha256
-rw-r--r-- 1 root root 52 Feb 23 09:12 docker-containerd.md5
-rw-r--r-- 1 root root 84 Feb 23 09:12 docker-containerd.sha256
-rwxr-xr-x 1 root root 772400 Feb 23 09:12 docker-init
-rw-r--r-- 1 root root 46 Feb 23 09:12 docker-init.md5
-rw-r--r-- 1 root root 78 Feb 23 09:12 docker-init.sha256
-rwxr-xr-x 1 root root 2530685 Feb 23 09:12 docker-proxy
-rw-r--r-- 1 root root 47 Feb 23 09:12 docker-proxy.md5
-rw-r--r-- 1 root root 79 Feb 23 09:12 docker-proxy.sha256
-rwxr-xr-x 1 root root 7096504 Feb 23 09:12 docker-runc
-rw-r--r-- 1 root root 46 Feb 23 09:12 docker-runc.md5
-rw-r--r-- 1 root root 78 Feb 23 09:12 docker-runc.sha256
lrwxrwxrwx 1 root root 18 Feb 23 09:12 dockerd -> dockerd-17.05.0-ce
-rwxr-xr-x 1 root root 39392304 Feb 23 09:12 dockerd-17.05.0-ce
-rw-r--r-- 1 root root 53 Feb 23 09:12 dockerd-17.05.0-ce.md5
-rw-r--r-- 1 root root 85 Feb 23 09:12 dockerd-17.05.0-ce.sha256
```
### 4. 二进制编译docker源码-19.03.9版本
这个版本和17.5版本的不同在于,docker和dockerd分离了。
在docker v17.06 之后,docker cli 和dockerd分离了, 单独拆成了https://github.com/docker/cli
#### 4.1 docker编译
将该项目下载到 $GOPATH/src/github.com/docker 目录。然后有go环境,直接 `make binary`就可以编译docker源码。
```
root:/home/zoux/data/golang/src/github.com/docker/cli# source /home/zouxiang/config // 设置go环境
root:/home/zoux/data/golang/src/github.com/docker/cli# make binary // 编译
WARNING: you are not in a container.
Use "make -f docker.Makefile binary" or set
DISABLE_WARN_OUTSIDE_CONTAINER=1 to disable this warning.
Press Ctrl+C now to abort.
WARNING: binary creates a Linux executable. Use cross for macOS or Windows.
./scripts/build/binary
Building statically linked build/docker-linux-amd64
```
#### 4.2 dockerd编译
同样通过二进制编译。将该项目下载到 $GOPATH/src/github.com/docker 目录。然后有go环境,直接 `./hack/make.sh binary`就可以编译docker源码。
```
root:/home/zoux/data/golang/src/github.com/docker/docker# ./hack/make.sh binary
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# GITCOMMIT = 811a247d06-unsupported
# The version you are building is listed as unsupported because
# there are some files in the git repository that are in an uncommitted state.
# Commit these changes, or add to .gitignore to remove the -unsupported from the version.
# Here is the current list:
M cmd/dockerd/daemon.go
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Removing bundles/
---> Making bundle: binary (in bundles/binary)
Building: bundles/binary-daemon/dockerd-dev
GOOS="linux" GOARCH="amd64" GOARM=""
Created binary: bundles/binary-daemon/dockerd-dev
```
该过程可能会遇到报错。比如:
No package 'devmapper' found
make binary causes fatal error: btrfs/ioctl.h: No such file or directory
<br>
这是一些基础的包没装好。apt-get 或者yum安装就好了。
```
apt-get install -y libdevmapper-dev
apt-get install -y install btrfs-progs
apt-get install -y btrfs-progs-dev
```
================================================
FILE: docker/11. dockercli 源码分析-docker run为例.md
================================================
* [0\. 章节目的](#0-章节目的)
* [1\. docker run 客户端处理流程](#1-docker-run-客户端处理流程)
* [1\.1 docker 函数入口](#11-docker-函数入口)
* [2\. 初始化docker cli客户端](#2-初始化docker-cli客户端)
* [3\. 实例化newDockerCommand对象](#3-实例化newdockercommand对象)
* [3\.1 newDockerCommand](#31-newdockercommand)
* [3\.2\. NewRunCommand](#32-newruncommand)
* [3\.3 runContainer](#33-runcontainer)
* [3\.4 ContainerCreate & ContainerStart](#34-containercreate--containerstart)
* [3\.5 总结](#35-总结)
### 0. 章节目的
从本章节开始以 docker run niginx ls为例。从源码角度弄清楚docker run nginx ls具体过程。
本章节的目的就是弄清楚该命令运行时。 docker cli做了什么工作。
```
root# docker run nginx ls
bin
boot
dev
docker-entrypoint.d
docker-entrypoint.sh
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
```
<br>
顺便补充一下:
在docker v17.06 之前,docker cli(就是我们经常使用的docker) 和dockerd 源码是一起的。都在:https://github.com/moby/moby项目
并且都在cmd目录。
cmd/docker: 是docker cli的主函数目录
cmd/dockerd: 是dockerd的主函数目录
<br>在docker v17.06 之后,docker cli 和dockerd分离了, 单独拆成了https://github.com/docker/cli
所以,本节基于https://github.com/docker/cli/tree/v19.03.9 进行研究。
将该项目下载到 $GOPATH/src/github.com/docker 目录。然后有go环境,直接 `make binary`就可以编译源码。
<br>
### 1. docker run 客户端处理流程
#### 1.1 docker 函数入口
main函数主要就是定义了newDockerCommand, dockerd的mian函数在cmd/dockerd/docker.go
```
func runDocker(dockerCli *command.DockerCli) error {
tcmd := newDockerCommand(dockerCli)
cmd, args, err := tcmd.HandleGlobalFlags()
if err != nil {
return err
}
if err := tcmd.Initialize(); err != nil {
return err
}
args, os.Args, err = processAliases(dockerCli, cmd, args, os.Args)
if err != nil {
return err
}
if len(args) > 0 {
if _, _, err := cmd.Find(args); err != nil {
err := tryPluginRun(dockerCli, cmd, args[0])
if !pluginmanager.IsNotFound(err) {
return err
}
// For plugin not found we fall through to
// cmd.Execute() which deals with reporting
// "command not found" in a consistent way.
}
}
// We've parsed global args already, so reset args to those
// which remain.
cmd.SetArgs(args)
return cmd.Execute()
}
```
主要干了两件事:
(1)实例化newDockerCommand对象
(2)初始化了docker cli客户端
先看看初始化客户端做了什么。
### 2. 初始化docker cli客户端
Initialize函数进行了cli客户端的初始化。
docker是cs结构的框架,但是client server基本都是在同一台机器上。所以docker使用了unix socket进行进程的通信。这样的好处就是快,比tcp快1/7。
dockerd运行起来后,会创建一个socket,默认是 /var/run/docker.sock。基于这个sock文件就可以构造一个客户端,用于交互。
dockerd运行起来后,会在 /var/run 目录增加两个文件。docker.pid (进程编号), docker.sock。
可参考:[golang中基于http 和unix socket的通信代码实现(服务端基于gin框架)](https://blog.csdn.net/qq_33399567/article/details/107691339)
<br>
```
// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...InitializeOpt) error {
var err error
for _, o := range ops {
if err := o(cli); err != nil {
return err
}
}
cliflags.SetLogLevel(opts.Common.LogLevel)
if opts.ConfigDir != "" {
cliconfig.SetDir(opts.ConfigDir)
logrus.Errorf("zoux Initialize opts.ConfigDir is: %v", opts.ConfigDir)
}
if opts.Common.Debug {
debug.Enable()
}
cli.loadConfigFile()
baseContextStore := store.New(cliconfig.ContextStoreDir(), cli.contextStoreConfig)
logrus.Errorf("zoux Initialize baseContextStore is: %v", baseContextStore)
cli.contextStore = &ContextStoreWithDefault{
Store: baseContextStore,
Resolver: func() (*DefaultContext, error) {
return ResolveDefaultContext(opts.Common, cli.ConfigFile(), cli.contextStoreConfig, cli.Err())
},
}
cli.currentContext, err = resolveContextName(opts.Common, cli.configFile, cli.contextStore)
if err != nil {
return err
}
cli.dockerEndpoint, err = resolveDockerEndpoint(cli.contextStore, cli.currentContext)
if err != nil {
return errors.Wrap(err, "unable to resolve docker endpoint")
}
logrus.Errorf("zoux Initialize dockerEndpoint TLSData is %v: host is %v", cli.dockerEndpoint.TLSData, cli.dockerEndpoint.Host)
if cli.client == nil {
cli.client, err = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile)
if tlsconfig.IsErrEncryptedKey(err) {
passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil)
newClient := func(password string) (client.APIClient, error) {
cli.dockerEndpoint.TLSPassword = password
return newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile)
}
cli.client, err = getClientWithPassword(passRetriever, newClient)
}
if err != nil {
return err
}
}
logrus.Errorf("zoux Initialize cli.client is %v", cli.client)
return nil
}
```
在上面的核心函数增加了部分日志,可以看出来。
docker cli的构建核心就是,利用var/run/docker.sock 文件创建了go的客户端。
```
root@k8s-node:~# docker run nginx ls
ERRO[0000] zoux initialize opts.configDir is /root/.docker
ERRO[0000] zoux initialize baseContextStore is &{0xc0002f4e80 0xc00005a3a0}
ERRO[0000] zoux initialize dockerEndpooint TLSData is <nil>, host is unix:///var/run/docker.sock
ERRO[0000] zoux initizlize cli.client is &{http unix:///var/run/docker.sock unix /var/run/docker.sock 0xc000368720 1.40 map[User-Agent:Docker-Client/unknown-version (linux)] false false false}
bin
boot
dev
docker-entrypoint.d
docker-entrypoint.sh
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
```
### 3. 实例化newDockerCommand对象
#### 3.1 newDockerCommand
```
func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
var (
opts *cliflags.ClientOptions
flags *pflag.FlagSet
helpCmd *cobra.Command
)
cmd := &cobra.Command{
Use: "docker [OPTIONS] COMMAND [ARG...]",
Short: "A self-sufficient runtime for containers",
SilenceUsage: true,
SilenceErrors: true,
TraverseChildren: true,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return command.ShowHelp(dockerCli.Err())(cmd, args)
}
return fmt.Errorf("docker: '%s' is not a docker command.\nSee 'docker --help'", args[0])
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return isSupported(cmd, dockerCli)
},
Version: fmt.Sprintf("%s, build %s", version.Version, version.GitCommit),
DisableFlagsInUseLine: true,
}
opts, flags, helpCmd = cli.SetupRootCommand(cmd)
flags.BoolP("version", "v", false, "Print version information and quit")
setFlagErrorFunc(dockerCli, cmd)
setupHelpCommand(dockerCli, cmd, helpCmd)
setHelpFunc(dockerCli, cmd)
cmd.SetOutput(dockerCli.Out())
commands.AddCommands(cmd, dockerCli)
cli.DisableFlagsInUseLine(cmd)
setValidateArgs(dockerCli, cmd)
// flags must be the top-level command flags, not cmd.Flags()
return cli.NewTopLevelCommand(cmd, dockerCli, opts, flags)
}
```
newDockerCommand函数的核心就是:
(1)RunE
(2)PersistentPreRunE
(3)commands.AddCommands(cmd, dockerCli)
<br>
**RunE**就是打印help函数,这和实操是一样的。输入docker,后面什么都不带就是打印help。因为docker 本身是不能运行的,后面必须跟子命令。
**PersistentPreRunE**就是判断docker 输入的flags是否支持。
```
func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
errs := []string{}
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if !f.Changed {
return
}
if !isVersionSupported(f, details.Client().ClientVersion()) {
errs = append(errs, fmt.Sprintf(`"--%s" requires API version %s, but the Docker daemon API version is %s`, f.Name, getFlagAnnotation(f, "version"), details.Client().ClientVersion()))
return
}
if !isOSTypeSupported(f, details.ServerInfo().OSType) {
errs = append(errs, fmt.Sprintf(
`"--%s" is only supported on a Docker daemon running on %s, but the Docker daemon is running on %s`,
f.Name,
getFlagAnnotation(f, "ostype"), details.ServerInfo().OSType),
)
return
}
if _, ok := f.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental {
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker daemon with experimental features enabled`, f.Name))
}
if _, ok := f.Annotations["experimentalCLI"]; ok && !details.ClientInfo().HasExperimental {
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker cli with experimental cli features enabled`, f.Name))
}
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
})
if len(errs) > 0 {
return errors.New(strings.Join(errs, "\n"))
}
return nil
}
```
**commands.AddCommands:** 就是增加子命令。
这里我们主要关键 NewContainerCommand。 而docker run就是对应了NewRunCommand子命令。
```
// AddCommands adds all the commands from cli/command to the root command
func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
cmd.AddCommand(
// checkpoint
checkpoint.NewCheckpointCommand(dockerCli),
// config
config.NewConfigCommand(dockerCli),
// container
container.NewContainerCommand(dockerCli),
container.NewRunCommand(dockerCli),
// image
image.NewImageCommand(dockerCli),
image.NewBuildCommand(dockerCli),
// builder
builder.NewBuilderCommand(dockerCli),
// manifest
manifest.NewManifestCommand(dockerCli),
// network
network.NewNetworkCommand(dockerCli),
// node
node.NewNodeCommand(dockerCli),
// plugin
plugin.NewPluginCommand(dockerCli),
// registry
registry.NewLoginCommand(dockerCli),
registry.NewLogoutCommand(dockerCli),
registry.NewSearchCommand(dockerCli),
// secret
secret.NewSecretCommand(dockerCli),
// service
service.NewServiceCommand(dockerCli),
// system
system.NewSystemCommand(dockerCli),
system.NewVersionCommand(dockerCli),
// stack
stack.NewStackCommand(dockerCli),
// swarm
swarm.NewSwarmCommand(dockerCli),
// trust
trust.NewTrustCommand(dockerCli),
// volume
volume.NewVolumeCommand(dockerCli),
// context
context.NewContextCommand(dockerCli),
// legacy commands may be hidden
hide(stack.NewTopLevelDeployCommand(dockerCli)),
hide(system.NewEventsCommand(dockerCli)),
hide(system.NewInfoCommand(dockerCli)),
hide(system.NewInspectCommand(dockerCli)),
hide(container.NewAttachCommand(dockerCli)),
hide(container.NewCommitCommand(dockerCli)),
hide(container.NewCopyCommand(dockerCli)),
hide(container.NewCreateCommand(dockerCli)),
hide(container.NewDiffCommand(dockerCli)),
hide(container.NewExecCommand(dockerCli)),
hide(container.NewExportCommand(dockerCli)),
hide(container.NewKillCommand(dockerCli)),
hide(container.NewLogsCommand(dockerCli)),
hide(container.NewPauseCommand(dockerCli)),
hide(container.NewPortCommand(dockerCli)),
hide(container.NewPsCommand(dockerCli)),
hide(container.NewRenameCommand(dockerCli)),
hide(container.NewRestartCommand(dockerCli)),
hide(container.NewRmCommand(dockerCli)),
hide(container.NewStartCommand(dockerCli)),
hide(container.NewStatsCommand(dockerCli)),
hide(container.NewStopCommand(dockerCli)),
hide(container.NewTopCommand(dockerCli)),
hide(container.NewUnpauseCommand(dockerCli)),
hide(container.NewUpdateCommand(dockerCli)),
hide(container.NewWaitCommand(dockerCli)),
hide(image.NewHistoryCommand(dockerCli)),
hide(image.NewImagesCommand(dockerCli)),
hide(image.NewImportCommand(dockerCli)),
hide(image.NewLoadCommand(dockerCli)),
hide(image.NewPullCommand(dockerCli)),
hide(image.NewPushCommand(dockerCli)),
hide(image.NewRemoveCommand(dockerCli)),
hide(image.NewSaveCommand(dockerCli)),
hide(image.NewTagCommand(dockerCli)),
)
if runtime.GOOS == "linux" {
// engine
cmd.AddCommand(engine.NewEngineCommand(dockerCli))
}
}
```
#### 3.2. NewRunCommand
```
// NewRunCommand create a new `docker run` command
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
var opts runOptions
var copts *containerOptions
cmd := &cobra.Command{
Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
Short: "Run a command in a new container",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
copts.Image = args[0]
if len(args) > 1 {
copts.Args = args[1:]
}
return runRun(dockerCli, cmd.Flags(), &opts, copts)
},
}
flags := cmd.Flags()
flags.SetInterspersed(false)
// These are flags not stored in Config/HostConfig
flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
// Add an explicit help that doesn't have a `-h` to prevent the conflict
// with hostname
flags.Bool("help", false, "Print usage")
command.AddPlatformFlag(flags, &opts.platform)
command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled())
copts = addFlags(flags)
return cmd
}
```
<br>
和其他命令已有,设置了一堆flags, 还有校验,比如image 必须要有,制定了多个只允许第一个
核心就是runRun函数核心就是runContainer
```
func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), opts.ConvertKVStringsToMapWithNil(copts.env.GetAll()))
newEnv := []string{}
for k, v := range proxyConfig {
if v == nil {
newEnv = append(newEnv, k)
} else {
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
containerConfig, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
// just in case the parse does not exit
if err != nil {
reportError(dockerCli.Err(), "run", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
if err = validateAPIVersion(containerConfig, dockerCli.Client().ClientVersion()); err != nil {
reportError(dockerCli.Err(), "run", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
return runContainer(dockerCli, ropts, copts, containerConfig)
}
```
<br>
#### 3.3 runContainer
从下面的函数逻辑可以看出来,run container分为两个过程:creatContainer, StartContainer。
在`ContainerCreate()`和`ContainerStart()`中分别向daemon发送了create和start命令。下一步,就需要到docker daemon中分析daemon对create和start的处理。
```
createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name)
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil
```
<br>
```
// nolint: gocyclo
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
config := containerConfig.Config
hostConfig := containerConfig.HostConfig
stdout, stderr := dockerCli.Out(), dockerCli.Err()
client := dockerCli.Client()
config.ArgsEscaped = false
// 1.更加配置初始化是否attach,运行的os等
if !opts.detach {
if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
return err
}
} else {
if copts.attach.Len() != 0 {
return errors.New("Conflicting options: -a and -d")
}
config.AttachStdin = false
config.AttachStdout = false
config.AttachStderr = false
config.StdinOnce = false
}
// Telling the Windows daemon the initial size of the tty during start makes
// a far better user experience rather than relying on subsequent resizes
// to cause things to catch up.
if runtime.GOOS == "windows" {
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
}
ctx, cancelFun := context.WithCancel(context.Background())
defer cancelFun()
// 1.调用createContainer创建container
createResponse, err := createContainer(ctx, dockerCli, containerConfig, &opts.createOptions)
if err != nil {
reportError(stderr, "run", err.Error(), true)
return runStartContainerErr(err)
}
if opts.sigProxy {
sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
defer signal.StopCatch(sigc)
}
var (
waitDisplayID chan struct{}
errCh chan error
)
if !config.AttachStdout && !config.AttachStderr {
// Make this asynchronous to allow the client to write to stdin before having to read the ID
waitDisplayID = make(chan struct{})
go func() {
defer close(waitDisplayID)
fmt.Fprintln(stdout, createResponse.ID)
}()
}
attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
if attach {
if opts.detachKeys != "" {
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
}
close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)
if err != nil {
return err
}
defer close()
}
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)
//start the container
// 3.调用ContainerStart,运行容器
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
// If we have hijackedIOStreamer, we should notify
// hijackedIOStreamer we are going to exit and wait
// to avoid the terminal are not restored.
if attach {
cancelFun()
<-errCh
}
reportError(stderr, "run", err.Error(), false)
if copts.autoRemove {
// wait container to be removed
<-statusChan
}
return runStartContainerErr(err)
}
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
}
}
if errCh != nil {
if err := <-errCh; err != nil {
if _, ok := err.(term.EscapeError); ok {
// The user entered the detach escape sequence.
return nil
}
logrus.Debugf("Error hijack: %s", err)
return err
}
}
// Detached mode: wait for the id to be displayed and return.
if !config.AttachStdout && !config.AttachStderr {
// Detached mode
<-waitDisplayID
return nil
}
status := <-statusChan
if status != 0 {
return cli.StatusError{StatusCode: status}
}
return nil
}
```
#### 3.4 ContainerCreate & ContainerStart
从下面的代码很容易看出来。ContainerCreate核心逻辑如下:
(1)通过配置获取镜像tag等信息
(2)调用dockercli客户端,创建container。
(3)如果创建失败,并且是因为image的问题,并且 --pull=always或者missing,就先pull image,然后再次创建
```
--pull missing Pull image before running ("always"|"missing"|"never")
```
docker run参数详见: https://docs.docker.com/engine/reference/commandline/run/
<br>
```
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) {
config := containerConfig.Config
hostConfig := containerConfig.HostConfig
networkingConfig := containerConfig.NetworkingConfig
stderr := dockerCli.Err()
warnOnOomKillDisable(*hostConfig, stderr)
warnOnLocalhostDNS(*hostConfig, stderr)
var (
trustedRef reference.Canonical
namedRef reference.Named
)
containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile)
if err != nil {
return nil, err
}
defer containerIDFile.Close()
ref, err := reference.ParseAnyReference(config.Image)
if err != nil {
return nil, err
}
if named, ok := ref.(reference.Named); ok {
namedRef = reference.TagNameOnly(named)
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !opts.untrusted {
var err error
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil)
if err != nil {
return nil, err
}
config.Image = reference.FamiliarString(trustedRef)
}
}
//create the container
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
//if image not found try to pull it
if err != nil {
if apiclient.IsErrNotFound(err) && namedRef != nil {
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
// we don't want to write to stdout anything apart from container.ID
if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil {
return nil, err
}
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil {
return nil, err
}
}
// Retry
var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
if retryErr != nil {
return nil, retryErr
}
} else {
return nil, err
}
}
for _, warning := range response.Warnings {
fmt.Fprintf(stderr, "WARNING: %s\n", warning)
}
err = containerIDFile.Write(response.ID)
return &response, err
}
```
ContainerCreate, ContainerStart 直接就是Post /containers/create 或者/start 请求创建, 运行。
```
// ContainerCreate creates a new container based in the given configuration.
// It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) {
var response container.ContainerCreateCreatedBody
if err := cli.NewVersionError("1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
return response, err
}
// When using API 1.24 and under, the client is responsible for removing the container
if hostConfig != nil && versions.LessThan(cli.ClientVersion(), "1.25") {
hostConfig.AutoRemove = false
}
query := url.Values{}
if containerName != "" {
query.Set("name", containerName)
}
body := configWrapper{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
defer ensureReaderClosed(serverResp)
if err != nil {
return response, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
return response, err
}
// ContainerStart sends a request to the docker daemon to start a container.
func (cli *Client) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
query := url.Values{}
if len(options.CheckpointID) != 0 {
query.Set("checkpoint", options.CheckpointID)
}
if len(options.CheckpointDir) != 0 {
query.Set("checkpoint-dir", options.CheckpointDir)
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
ensureReaderClosed(resp)
return err
}
```
#### 3.5 总结
可以看出来 ContainerCreate和ContainerStart处理非常简单,就是
(1)利用var/run/docker.sock 文件创建了http的客户端
(2)调用cli 客户端发送post请求,创建和启动容器
================================================
FILE: docker/12. dockerd源码分析-docker run为例.md
================================================
* [0\. 章节目的](#0-章节目的)
* [1\. docker run服务器端处理流程](#1-docker-run服务器端处理流程)
* [1\.1 dockerd 函数入口](#11-dockerd-函数入口)
* [1\.2 runDaemon](#12-rundaemon)
* [1\.3 daemonCli\.start](#13-daemonclistart)
* [1\.4 NewDaemon](#14-newdaemon)
* [1\.5 dockerd的路由设置 containers](#15--dockerd的路由设置-containers)
* [2\. docker create container详细流程分析](#2-docker-create-container详细流程分析)
* [2\.1 postContainersCreate](#21-postcontainerscreate)
* [2\.2 containerCreate](#22-containercreate)
* [2\.3 daemon\.create](#23-daemoncreate)
* [2\.4 newContainer](#24--newcontainer)
* [2\.5 实验](#25-实验)
* [2\.5\.1 实验1\-观察目录变化](#251-实验1-观察目录变化)
* [2\.5\.2 实验2\-查看配置](#252-实验2-查看配置)
* [2\.6 总结](#26-总结)
* [3\. Docker start container详细流程分析](#3-docker-start-container详细流程分析)
* [3\.1 postContainerExecStart](#31-postcontainerexecstart)
* [3\.2 ContainerStart](#32-containerstart)
* [3\.3 containerStart](#33-containerstart)
* [4\. docker start 创建的详细过程](#4-docker-start-创建的详细过程)
* [4\.1 containerd的初始化](#41-containerd的初始化)
* [4\.2 容器的网络设置](#42-容器的网络设置)
* [4\.3 容器的spec设置\-createSpec函数](#43-容器的spec设置-createspec函数)
* [4\.4 containerd创建容器的详细流程](#44-containerd创建容器的详细流程)
* [5\. 总结](#5-总结)
### 0. 章节目的
以 docker run niginx ls为例。从源码角度弄清楚dockerd具体的执行过程。
源码版本:https://github.com/moby/moby/tree/v19.03.9-ce
从上一篇分析中,docker run 其实是分为了container create, container start这两个步骤。
### 1. docker run服务器端处理流程
还是先从docker的main函数可以入手。在安装docker之后。查看docker的配置,发现docker运行没有带任何参数。
```
root@k8s-node:~# ps -ef | grep docker
root 6164 5604 0 21:11 pts/1 00:00:00 grep docker
root 12493 1 0 17:40 ? 00:01:04 /usr/bin/dockerd
root@k8s-node:~# cat /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target
[Service]
Type=notify
ExecStart=/usr/bin/dockerd
ExecReload=/bin/kill -s HUP
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes
KillMode=process
Restart=on-failure
StartLimitBurst=3
StartLimitInterval=60s
[Install]
WantedBy=multi-user.target
```
<br>
#### 1.1 dockerd 函数入口
dockerd main函数在cmd/dockerd/docker.go。还是熟悉的cobra框架,所以直接从newDaemonCommand入手。
newDaemonOptions主要是调用了runDaemon命令,从上面分析看,这里默认dockerd启动没有flags。在之前配置镜像源的时候,经常在 `/etc/docker/daemon.json` 目录下进行如下配置。这个其实是docker的默认配置目录。
```
root@k8s-node:/etc/docker# cat daemon.json
{
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
```
<br>
```
func newDaemonCommand() (*cobra.Command, error) {
opts := newDaemonOptions(config.New())
cmd := &cobra.Command{
Use: "dockerd [OPTIONS]",
Short: "A self-sufficient runtime for containers.",
SilenceUsage: true,
SilenceErrors: true,
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
opts.flags = cmd.Flags()
return runDaemon(opts)
},
DisableFlagsInUseLine: true,
Version: fmt.Sprintf("%s, build %s", dockerversion.Version, dockerversion.GitCommit),
}
cli.SetupRootCommand(cmd)
flags := cmd.Flags()
flags.BoolP("version", "v", false, "Print version information and quit")
// 读取默认的配置文件。默认是 /etc/docker/daemon.json
defaultDaemonConfigFile, err := getDefaultDaemonConfigFile()
if err != nil {
return nil, err
}
flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "Daemon configuration file")
opts.InstallFlags(flags)
if err := installConfigFlags(opts.daemonConfig, flags); err != nil {
return nil, err
}
installServiceFlags(flags)
return cmd, nil
}
```
<br>
#### 1.2 runDaemon
```
func runDaemon(opts *daemonOptions) error {
daemonCli := NewDaemonCli()
return daemonCli.start(opts)
}
```
这里主要是 runDaemon -> daemonCli.start。
#### 1.3 daemonCli.start
start函数的核心逻辑如下:
1. 设置默认的配置,以及从命令行、文件读取配置。从打印出来的日志来看,确实没什么启动参数。基本都是默认值,比如
默认的docker目录是/var/lib/docker, 默人的sock是const DefaultDockerHost = "unix:///var/run/docker.sock"
2. 检查一些配置,比如是否debug模式,是否开启实验模式,是否以root运行等等
3. 创建docker-root目录文件,默认在 /var/lib/docker目录下
4. 创建docker.pid文件
5. 创建sever config
6. 根据config,创建一个sever
7. daemon程序可以根据选项监控多个地址,loadListeners遍历这些地址,也监听了多个地址。
8. initcontainerD 初始化容器运行时, initContainerD会调用supervisor.Start然后调用 startContainerd,启动containerd。会在/var/run/docker/containerd目录下,pid和sock文件。
9. 初始化pluginStore,实际就是生成一个map用来保存有哪些plugins
10. 初始化Middlewares, http的中间件,这些中间件主要进行版本兼容性检查、添加CORS跨站点请求相关响应头、对请求进行认证。
11. 实例化Daemon对象,做好 sever端的一切准备,包括检查网络以及其他环境
12. 实例化metric server
13. docker可能以集群方式运行,开启
14. 运行 swarm containers
15. 配置路由,包括contianer,image, driver等等
16. 初始化路由,接下里会分析
1. 开启服务器,以及通知就绪等等
```
func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
stopc := make(chan bool)
defer close(stopc)
// warn from uuid package when running the daemon
uuid.Loggerf = logrus.Warnf
// 1.设置默认的配置,以及从命令行、文件读取配置。从打印出来的日志来看,确实没什么启动参数。例如指定了
// root is /var/lib/docker, conf.TrustKeyPath is /etc/docker/key.json
opts.SetDefaultOptions(opts.flags)
// 增加日志打印输出。用于理解源码
logrus.Infof("zoux start flags.configFile is %v, damonConfig is %v, flags is %v, debug is %v, hosts is %v", opts.configFile, opts.daemonConfig,opts.Debug, opts.Hosts)
if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
return err
}
if err := configureDaemonLogs(cli.Config); err != nil {
return err
}
logrus.Info("Starting up")
cli.configFile = &opts.configFile
cli.flags = opts.flags
// 2.检查一些配置,比如是否debug模式,是否开启实验模式,是否以root运行等等
if cli.Config.Debug {
debug.Enable()
}
if cli.Config.Experimental {
logrus.Warn("Running experimental build")
if cli.Config.IsRootless() {
logrus.Warn("Running in rootless mode. Cgroups, AppArmor, and CRIU are disabled.")
}
if rootless.RunningWithRootlessKit() {
logrus.Info("Running with RootlessKit integration")
if !cli.Config.IsRootless() {
return fmt.Errorf("rootless mode needs to be enabled for running with RootlessKit")
}
}
} else {
if cli.Config.IsRootless() {
return fmt.Errorf("rootless mode is supported only when running in experimental mode")
}
}
// return human-friendly error before creating files
if runtime.GOOS == "linux" && os.Geteuid() != 0 {
return fmt.Errorf("dockerd needs to be started with root. To see how to run dockerd in rootless mode with unprivileged user, see the documentation")
}
system.InitLCOW(cli.Config.Experimental)
if err := setDefaultUmask(); err != nil {
return err
}
// 3. 创建docker-root目录文件,默认在 /var/lib/docker目录下
// Create the daemon root before we create ANY other files (PID, or migrate keys)
// to ensure the appropriate ACL is set (particularly relevant on Windows)
if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
return err
}
if err := system.MkdirAll(cli.Config.ExecRoot, 0700, ""); err != nil {
return err
}
potentiallyUnderRuntimeDir := []string{cli.Config.ExecRoot}
// 4.创建docker.pid文件
if cli.Pidfile != "" {
pf, err := pidfile.New(cli.Pidfile)
if err != nil {
return errors.Wrap(err, "failed to start daemon")
}
potentiallyUnderRuntimeDir = append(potentiallyUnderRuntimeDir, cli.Pidfile)
defer func() {
if err := pf.Remove(); err != nil {
logrus.Error(err)
}
}()
}
if cli.Config.IsRootless() {
// Set sticky bit if XDG_RUNTIME_DIR is set && the file is actually under XDG_RUNTIME_DIR
if _, err := homedir.StickRuntimeDirContents(potentiallyUnderRuntimeDir); err != nil {
// StickRuntimeDirContents returns nil error if XDG_RUNTIME_DIR is just unset
logrus.WithError(err).Warn("cannot set sticky bit on files under XDG_RUNTIME_DIR")
}
}
// 5.创建sever config
serverConfig, err := newAPIServerConfig(cli)
if err != nil {
return errors.Wrap(err, "failed to create API server")
}
// 6.根据config,创建一个sever
cli.api = apiserver.New(serverConfig)
// 7.daemon程序可以根据选项监控多个地址,loadListeners遍历这些地址,也监听了多个地址。
hosts, err := loadListeners(cli, serverConfig)
if err != nil {
return errors.Wrap(err, "failed to load listeners")
}
ctx, cancel := context.WithCancel(context.Background())
// 8.initcontainerD 初始化容器运行时, initContainerD会调用supervisor.Start然后调用 startContainerd,启动containerd。会在/var/run/docker/containerd目录下,pid和sock文件。
waitForContainerDShutdown, err := cli.initContainerD(ctx)
if waitForContainerDShutdown != nil {
defer waitForContainerDShutdown(10 * time.Second)
}
if err != nil {
cancel()
return err
}
defer cancel()
signal.Trap(func() {
cli.stop()
<-stopc // wait for daemonCli.start() to return
}, logrus.StandardLogger())
// Notify that the API is active, but before daemon is set up.
preNotifySystem()
// 9.初始化pluginStore,实际就是生成一个map用来保存有哪些plugins
pluginStore := plugin.NewStore()
// 10.初始化Middlewares, http的中间件,这些中间件主要进行版本兼容性检查、添加CORS跨站点请求相关响应头、对请求进行认证。
if err := cli.initMiddlewares(cli.api, serverConfig, pluginStore); err != nil {
logrus.Fatalf("Error creating middlewares: %v", err)
}
// 11.实例化Daemon对象,做好 sever端的一切准备,包括检查网络以及其他环境
d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore)
if err != nil {
return errors.Wrap(err, "failed to start daemon")
}
d.StoreHosts(hosts)
// validate after NewDaemon has restored enabled plugins. Don't change order.
if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil {
return errors.Wrap(err, "failed to validate authorization plugin")
}
cli.d = d
// 12. 实例化metric server
if err := cli.startMetricsServer(cli.Config.MetricsAddress); err != nil {
return err
}
// 13.docker可能以集群方式运行,开启
c, err := createAndStartCluster(cli, d)
if err != nil {
logrus.Fatalf("Error starting cluster component: %v", err)
}
// Restart all autostart containers which has a swarm endpoint
// and is not yet running now that we have successfully
// initialized the cluster.
// 14.运行 swarm containers
d.RestartSwarmContainers()
logrus.Info("Daemon has completed initialization")
// 15.配置路由,包括contianer,image, driver等等
routerOptions, err := newRouterOptions(cli.Config, d)
if err != nil {
return err
}
routerOptions.api = cli.api
routerOptions.cluster = c
// 16.初始化路由,接下里会分析
initRouter(routerOptions)
// 17. 开启服务器,以及通知就绪等等
go d.ProcessClusterNotifications(ctx, c.GetWatchStream())
cli.setupConfigReloadTrap()
// The serve API routine never exits unless an error occurs
// We need to start it as a goroutine and wait on it so
// daemon doesn't exit
serveAPIWait := make(chan error)
go cli.api.Wait(serveAPIWait)
// after the daemon is done setting up we can notify systemd api
notifySystem()
// Daemon is fully initialized and handling API traffic
// Wait for serve API to complete
errAPI := <-serveAPIWait
c.Cleanup()
shutdownDaemon(d)
// Stop notification processing and any background processes
cancel()
if errAPI != nil {
return errors.Wrap(errAPI, "shutting down due to ServeAPI error")
}
logrus.Info("Daemon shutdown complete")
return nil
}
日志输出结果:
logrus.Infof("zoux start flags.configFile is %v, damonConfig is %v, flags is %v, debug is %v, hosts is %v", opts.configFile, opts.daemonConfig,opts.Debug, opts.Hosts)
Feb 28 16:56:58 k8s-node dockerd[28021]: time="2022-02-28T16:56:58.742186824+08:00" level=info msg="zoux start flags.configFile is /etc/docker/daemon.json, damonConfig is &{{<nil> [] true map[] false [] [] [] 0 0 /var/run/docker.pid false /var/lib/docker /var/run/docker docker false map[] 0xc0005f3bd0 0xc0005f3bd8 15 false [] false false { } 0 0 {[] [] []} {json-file map[]} {{ } {0.0.0.0 <nil> <nil> true} false true true true true } {{[]} 1500} {[] [] []} {0 0} map[] false [] false map[] {{false [] } {<nil> <nil>}} moby plugins.moby} {map[] runc } false map[] 0 0 -500 false 67108864 false private false}, flags is false, debug is [], hosts is %!v(MISSING)"
```
**initContainerD**会调用supervisor.Start然后调用 startContainerd,启动containerd。会在/var/run/docker/containerd目录下,pid和sock文件。
```
root@k8s-node:/var/run/docker/containerd# ls
0ea51049a3dde9b6ca6940f563b920997cc7ff05425bfe5174f2fbced72a9feb 7f1343294ac385c400b076a0d0c62979909cede65e90b2a0d8615ddba36c19cd containerd-debug.sock containerd.toml daemon
root@k8s-node:/var/run/docker/containerd#
root@k8s-node:/var/run/docker/containerd#
root@k8s-node:/var/run/docker/containerd# systemctl start docker.service
root@k8s-node:/var/run/docker/containerd#
root@k8s-node:/var/run/docker/containerd# ls
492a9c1152120b8eafd70c476a04aa7d73b8ec359fbf01c55e55b70912872dfe 702cc9e5c234374195375cdc05bf34eb0484221f9da3e4288d2c37154f2325bd containerd-debug.sock containerd.pid containerd.sock containerd.toml daemon
root@k8s-node:/var/run/docker/containerd# ls
```
<br>
```
func (cli *DaemonCli) initContainerD(ctx context.Context) (func(time.Duration) error, error) {
var waitForShutdown func(time.Duration) error
if cli.Config.ContainerdAddr == "" {
systemContainerdAddr, ok, err := systemContainerdRunning(honorXDG)
if err != nil {
return nil, errors.Wrap(err, "could not determine whether the system containerd is running")
}
if !ok {
logrus.Debug("Containerd not running, starting daemon managed containerd")
opts, err := cli.getContainerdDaemonOpts()
if err != nil {
return nil, errors.Wrap(err, "failed to generate containerd options")
}
r, err := supervisor.Start(ctx, filepath.Join(cli.Config.Root, "containerd"), filepath.Join(cli.Config.ExecRoot, "containerd"), opts...)
if err != nil {
return nil, errors.Wrap(err, "failed to start containerd")
}
logrus.Debug("Started daemon managed containerd")
cli.Config.ContainerdAddr = r.Address()
// Try to wait for containerd to shutdown
waitForShutdown = r.WaitTimeout
} else {
cli.Config.ContainerdAddr = systemContainerdAddr
}
}
return waitForShutdown, nil
}
```
#### 1.4 NewDaemon
NewDaemon核心就是为了接下来开启 服务端路由做准备。包括
(1)环境的检测调整
(2)用户空间重映射特性
(3)对存储目录进行必要的权限调整、对daemon进程的`oom_score_adj`参数进行必要的调整(减小daemon进程被OS杀掉的可能性)、创建临时目录。
(4)调整进程的最大线程数限制 * 安装AppArmor相关的配置
(5)创建初始化了一堆与镜像存储相关的目录及Store,有以下几个:
`/var/lib/docker/containers` 这个目录是用来记录的是容器相关的信息,每运行一个容器,就在这个目录下面生成一个容器Id对应的子目录
`/var/lib/docker/image/${graphDriverName}/layerdb` 这个目录是用来记录layer元数据的
`/var/lib/docker/image/${graphDriverName}/imagedb` 这个目录是用来记录镜像元数据的
`/var/lib/docker/image/${graphDriverName}/distribution` 这个目录用来记录layer元数据与镜像元数据之间的关联关系
`/var/lib/docker/image/${graphDriverName}/repositories.json` 这个目录是用来记录镜像仓库元数据的
`/var/lib/docker/trust` 这个目录用来放一些证书文件 * `/var/lib/docker/volumes` 这个目录是用来记录卷元数据的
(6)如果配置了在集群中向外发布的访问地址,则需要初始化集群节点的服务发现Agent。一般来说就是定时向KV库报告自身的状态及公布访问地址
(7)再然后就是给Daemon对象的一系列属性赋上值。
(8)确保插件系统初始化完毕,然后根据`/var/lib/docker/containers`目录里容器目录还原部分容器、初始化容器依赖的网络环境,初始化容器之间的link关系等。
具体不一样对应了,看代码和注释就知道了。代码位置在:daemon/daemon.go
#### 1.5 dockerd的路由设置 containers
在2.2中,initRouter就是负责路由规则。可以看出来包括image, contianer, plugins等等。这里我们只关注container路由。
```
func initRouter(opts routerOptions) {
。。。
routers := []router.Router{
// we need to add the checkpoint router before the container router or the DELETE gets masked
checkpointrouter.NewRouter(opts.daemon, decoder),
container.NewRouter(opts.daemon, decoder, opts.daemon.RawSysInfo().CgroupUnified),
image.NewRouter(opts.daemon.ImageService()),
systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildkit, opts.features),
volume.NewRouter(opts.daemon.VolumesService()),
build.NewRouter(opts.buildBackend, opts.daemon, opts.features),
sessionrouter.NewRouter(opts.sessionManager),
swarmrouter.NewRouter(opts.cluster),
pluginrouter.NewRouter(opts.daemon.PluginManager()),
distributionrouter.NewRouter(opts.daemon.ImageService()),
}
。。。
opts.api.InitRouter(routers...)
}
```
上述所有的路由实现都对应在 api/server/router目录。
可以看出来:
container create: 对应了 r.postContainersCreate 这个实现函数
container start: 对应了 r.postContainerExecStart 这个实现函数
```
api/server/router/container/container.go
// NewRouter initializes a new container router
func NewRouter(b Backend, decoder httputils.ContainerDecoder, cgroup2 bool) router.Router {
r := &containerRouter{
backend: b,
decoder: decoder,
cgroup2: cgroup2,
}
r.initRoutes()
return r
}
// Routes returns the available routes to the container controller
func (r *containerRouter) Routes() []router.Route {
return r.routes
}
// initRoutes initializes the routes in container router
func (r *containerRouter) initRoutes() {
r.routes = []router.Route{
// HEAD
router.NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
// GET
router.NewGetRoute("/containers/json", r.getContainersJSON),
router.NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
router.NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
router.NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
router.NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
router.NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs),
router.NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats),
router.NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
router.NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
router.NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
// POST
//r.postContainersCreate 这个是 container create的实现函数
router.NewPostRoute("/containers/create", r.postContainersCreate),
router.NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
router.NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
router.NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
router.NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
router.NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
router.NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
router.NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
router.NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
router.NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
router.NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy), // Deprecated since 1.8, Errors out since 1.12
router.NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
router.NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
router.NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
router.NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
router.NewPostRoute("/containers/{name:.*}/update", r.postContainerUpdate),
router.NewPostRoute("/containers/prune", r.postContainersPrune),
router.NewPostRoute("/commit", r.postCommit),
// PUT
router.NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
// DELETE
router.NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
}
}
```
### 2. docker create container详细流程分析
docker create container在后端调用的是postContainersCreate,首先从源码角度分析详细流程
#### 2.1 postContainersCreate
postContainersCreate 函数逻辑如下:
1. 对request进行校验
2. 从表单获取contaienr name
3. 获取容器hostConfig, 网络config等配置
4. 传入配置信息,调用ContainerCreate进一步创建容器
看起来核心是backend.ContainerCreate 函数
```
func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// 1.对request进行校验
if err := httputils.ParseForm(r); err != nil {
return err
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
// 2.从表单获取contaienr name
name := r.Form.Get("name")
// 3.获取容器hostConfig, 网络config等配置
config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
if err != nil {
return err
}
version := httputils.VersionFromContext(ctx)
adjustCPUShares := versions.LessThan(version, "1.19")
// When using API 1.24 and under, the client is responsible for removing the container
if hostConfig != nil && versions.LessThan(version, "1.25") {
hostConfig.AutoRemove = false
}
if hostConfig != nil && versions.LessThan(version, "1.40") {
// Ignore BindOptions.NonRecursive because it was added in API 1.40.
for _, m := range hostConfig.Mounts {
if bo := m.BindOptions; bo != nil {
bo.NonRecursive = false
}
}
// Ignore KernelMemoryTCP because it was added in API 1.40.
hostConfig.KernelMemoryTCP = 0
// Ignore Capabilities because it was added in API 1.40.
hostConfig.Capabilities = nil
// Older clients (API < 1.40) expects the default to be shareable, make them happy
if hostConfig.IpcMode.IsEmpty() {
hostConfig.IpcMode = container.IpcMode("shareable")
}
}
if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
// Don't set a limit if either no limit was specified, or "unlimited" was
// explicitly set.
// Both `0` and `-1` are accepted as "unlimited", and historically any
// negative value was accepted, so treat those as "unlimited" as well.
hostConfig.PidsLimit = nil
}
// 4.传入配置信息,调用ContainerCreate进一步创建容器
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
Name: name,
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
AdjustCPUShares: adjustCPUShares,
})
if err != nil {
return err
}
return httputils.WriteJSON(w, http.StatusCreated, ccr)
}
```
backend.ContainerCreate最终调用的是 daemon.ContainerCreate
```
daemon/create.go
// ContainerCreate creates a regular container
func (daemon *Daemon) ContainerCreate(params types.ContainerCreateConfig) (containertypes.ContainerCreateCreatedBody, error) {
return daemon.containerCreate(createOpts{
params: params,
managed: false,
ignoreImagesArgsEscaped: false})
}
```
<br>
#### 2.2 containerCreate
containerCreate的核心逻辑如下:
1. 一开始纪录时间,估计是统计耗时用的,接下来看返回条件就知道,是做一系列的验证
2. 如果指定了镜像,就调用imageService.GetImage获取 image对象。这里只是为了获取镜像信息,如果没有镜像并没有拉取。原因是客户端docker会拉去镜像再重试
3. 修改hostconfig的不正常值,例如CPUShares、Memory
4. 继续调用daemon.create创建容器
5. 纪录已经创建容器的时间
```
func (daemon *Daemon) containerCreate(opts createOpts) (containertypes.ContainerCreateCreatedBody, error) {
start := time.Now()
if opts.params.Config == nil {
return containertypes.ContainerCreateCreatedBody{}, errdefs.InvalidParameter(errors.New("Config cannot be empty in order to create a container"))
}
os := runtime.GOOS
if opts.params.Config.Image != "" {
img, err := daemon.imageService.GetImage(opts.params.Config.Image)
if err == nil {
os = img.OS
}
} else {
// This mean scratch. On Windows, we can safely assume that this is a linux
// container. On other platforms, it's the host OS (which it already is)
if runtime.GOOS == "windows" && system.LCOWSupported() {
os = "linux"
}
}
warnings, err := daemon.verifyContainerSettings(os, opts.params.HostConfig, opts.params.Config, false)
if err != nil {
return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)
}
err = verifyNetworkingConfig(opts.params.NetworkingConfig)
if err != nil {
return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)
}
if opts.params.HostConfig == nil {
opts.params.HostConfig = &containertypes.HostConfig{}
}
err = daemon.adaptContainerSettings(opts.params.HostConfig, opts.params.AdjustCPUShares)
if err != nil {
return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, errdefs.InvalidParameter(err)
}
container, err := daemon.create(opts)
if err != nil {
return containertypes.ContainerCreateCreatedBody{Warnings: warnings}, err
}
containerActions.WithValues("create").UpdateSince(start)
if warnings == nil {
warnings = make([]string, 0) // Create an empty slice to avoid https://github.com/moby/moby/issues/38222
}
return containertypes.ContainerCreateCreatedBody{ID: container.ID, Warnings: warnings}, nil
}
```
<br>
#### 2.3 daemon.create
create主要逻辑如下:
1. 定义一些全局变量
2. 看起來还是只是getImages 没有pull
3. 根据镜像信息,再一次校验信息是否有误
4. 调用daemon.newContainer创建容器
5. 判断是否设置容器特权。 noNewPrivileges:设置为true后可以防止进程获取额外的权限(如使得suid和文件capabilities失效),该标记位在内核4.10版本之后可以在/proc/$pid/status中查看NoNewPrivs的设置值。更多参见 https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt
6. 为容器设置 可读性 layer层
7. 以 root uid gid的属性创建目录,在/var/lib/docker/containers目录下创建容器文件,并在容器文件下创建checkpoints目录
8. 根据特定的OS创建容器,比如默认路径已经创建volume(这些特性和os有关)
9. 设置网络
10. 更新网络
```
// Create creates a new container from the given configuration with a given name.
func (daemon *Daemon) create(opts createOpts) (retC *container.Container, retErr error) {
// 1. 定义一些全局变量
var (
container *container.Container
img *image.Image
imgID image.ID
err error
)
os := runtime.GOOS
// 2. getImages 获取镜像信息
if opts.params.Config.Image != "" {
img, err = daemon.imageService.GetImage(opts.params.Config.Image)
if err != nil {
return nil, err
}
if img.OS != "" {
os = img.OS
} else {
// default to the host OS except on Windows with LCOW
if runtime.GOOS == "windows" && system.LCOWSupported() {
os = "linux"
}
}
imgID = img.ID()
if runtime.GOOS == "windows" && img.OS == "linux" && !system.LCOWSupported() {
return nil, errors.New("operating system on which parent image was created is not Windows")
}
} else {
if runtime.GOOS == "windows" {
os = "linux" // 'scratch' case.
}
}
// On WCOW, if are not being invoked by the builder to create this container (where
// ignoreImagesArgEscaped will be true) - if the image already has its arguments escaped,
// ensure that this is replicated across to the created container to avoid double-escaping
// of the arguments/command line when the runtime attempts to run the container.
if os == "windows" && !opts.ignoreImagesArgsEscaped && img != nil && img.RunConfig().ArgsEscaped {
opts.params.Config.ArgsEscaped = true
}
// 3.根据镜像信息,再一次校验信息是否有误
if err := daemon.mergeAndVerifyConfig(opts.params.Config, img); err != nil {
return nil, errdefs.InvalidParameter(err)
}
if err := daemon.mergeAndVerifyLogConfig(&opts.params.HostConfig.LogConfig); err != nil {
return nil, errdefs.InvalidParameter(err)
}
// 4.调用daemon.newContainer创建容器
if container, err = daemon.newContainer(opts.params.Name, os, opts.params.Config, opts.params.HostConfig, imgID, opts.managed); err != nil {
return nil, err
}
defer func() {
if retErr != nil {
if err := daemon.cleanupContainer(container, true, true); err != nil {
logrus.Errorf("failed to cleanup container on create error: %v", err)
}
}
}()
// 5. 判断是否设置容器特权。 noNewPrivileges:设置为true后可以防止进程获取额外的权限(如使得suid和文件capabilities失效),该标记位在内核4.10版本
// 之后可以在/proc/$pid/status中查看NoNewPrivs的设置值。更多参见 https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt
if err := daemon.setSecurityOptions(container, opts.params.HostConfig); err != nil {
return nil, err
}
container.HostConfig.StorageOpt = opts.params.HostConfig.StorageOpt
// Fixes: https://github.com/moby/moby/issues/34074 and
// https://github.com/docker/for-win/issues/999.
// Merge the daemon's storage options if they aren't already present. We only
// do this on Windows as there's no effective sandbox size limit other than
// physical on Linux.
if runtime.GOOS == "windows" {
if container.HostConfig.StorageOpt == nil {
container.HostConfig.StorageOpt = make(map[string]string)
}
for _, v := range daemon.configStore.GraphOptions {
opt := strings.SplitN(v, "=", 2)
if _, ok := container.HostConfig.StorageOpt[opt[0]]; !ok {
container.HostConfig.StorageOpt[opt[0]] = opt[1]
}
}
}
// 6. 为容器设置 可读性 layer层
// Set RWLayer for container after mount labels have been set
rwLayer, err := daemon.imageService.CreateLayer(container, setupInitLayer(daemon.idMapping))
if err != nil {
return nil, errdefs.System(err)
}
container.RWLayer = rwLayer
rootIDs := daemon.idMapping.RootPair()
// 7. 以 root uid gid的属性创建目录,在/var/lib/docker/containers目录下创建容器文件,并在容器文件下创建checkpoints目录
if err := idtools.MkdirAndChown(container.Root, 0700, rootIDs); err != nil {
return nil, err
}
if err := idtools.MkdirAndChown(container.CheckpointDir(), 0700, rootIDs); err != nil {
return nil, err
}
if err := daemon.setHostConfig(container, opts.params.HostConfig); err != nil {
return nil, err
}
// 8. 根据特定的OS创建容器,比如默认路径已经创建volume(这些特性和os有关)
if err := daemon.createContainerOSSpecificSettings(container, opts.params.Config, opts.params.HostConfig); err != nil {
return nil, err
}
// 9.设置网络
var endpointsConfigs map[string]*networktypes.EndpointSettings
if opts.params.NetworkingConfig != nil {
endpointsConfigs = opts.params.NetworkingConfig.EndpointsConfig
}
// Make sure NetworkMode has an acceptable value. We do this to ensure
// backwards API compatibility.
runconfig.SetDefaultNetModeIfBlank(container.HostConfig)
// 10.更新网络
daemon.updateContainerNetworkSettings(container, endpointsConfigs)
if err := daemon.Register(container); err != nil {
return nil, err
}
stateCtr.set(container.ID, "stopped")
daemon.LogContainerEvent(container, "create")
return container, nil
}
```
<br>
接下来继续看看第四步,daemon.newContainer做了什么
#### 2.4 newContainer
可以看出来new container只是创建容器这个对象。具体就是给对象赋值。而创建目录啥的在createContainerOSSpecificSettings做了
```
func (daemon *Daemon) newContainer(name string, operatingSystem string, config *containertypes.Config, hostConfig *containertypes.HostConfig, imgID image.ID, managed bool) (*container.Container, error) {
var (
id string
err error
noExplicitName = name == ""
)
id, name, err = daemon.generateIDAndName(name)
if err != nil {
return nil, err
}
if hostConfig.NetworkMode.IsHost() {
if config.Hostname == "" {
config.Hostname, err = os.Hostname()
if err != nil {
return nil, errdefs.System(err)
}
}
} else {
daemon.generateHostname(id, config)
}
entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
base := daemon.newBaseContainer(id)
base.Created = time.Now().UTC()
base.Managed = managed
base.Path = entrypoint
base.Args = args //FIXME: de-duplicate from config
base.Config = config
base.HostConfig = &containertypes.HostConfig{}
base.ImageID = imgID
base.NetworkSettings = &network.Settings{IsAnonymousEndpoint: noExplicitName}
base.Name = name
base.Driver = daemon.imageService.GraphDriverForOS(operatingSystem)
base.OS = operatingSystem
return base, err
}
```
#### 2.5 实验
##### 2.5.1 实验1-观察目录变化
在执行 `docker container create --name nginx` 命令的过程中,时刻观察/var/lib/docker的变化,发现在create的阶段镜像文件以及挂载都准备好了。
如果nginx镜像不存在,可以看到下载进行的整个过程。
```
03/03/22 15:08 /var/lib/docker/tmp/ GetImageBlob163641926 CREATE
03/03/22 15:08 /var/lib/docker/tmp/ GetImageBlob307902189 CREATE
03/03/22 15:08 /var/lib/docker/tmp/ GetImageBlob256086888 CREATE
03/03/22 15:08 /var/lib/docker/tmp/ GetImageBlob630460839 CREATE
03/03/22 15:08 /var/lib/docker/tmp/ GetImageBlob086739162 CREATE
03/03/22 15:08 /var/lib/docker/tmp/ GetImageBlob105444465 CREATE
```
<br>
```
root@k8s-node:~# inotifywait -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w %f %e' -e modify,delete,create,attrib /var/lib/docker
03/03/22 11:44 /var/lib/docker/overlay2/ 4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/l/ GRIVPJLK7YAT3OXDTS4V2QFCUA CREATE
03/03/22 11:44 /var/lib/docker/overlay2/7a25fdc447cb19682434e15e2a721250a869eb3a75aa8d439bbd985e736f8ef4/ committed MODIFY
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/ merged CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/ work CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/ work ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/ work ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/ diff ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/ ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ #3fed CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ #3fed ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/ diff ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/ ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/ .dockerenv CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/ .dockerenv ATTRIB
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ #3fee CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ #3fee ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/ diff ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/ ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ #3ff0 CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/work/work/ #3ff2 CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/dev/ shm CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/dev/ shm ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/dev/ console CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/diff/dev/ console ATTRIB
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/ merged DELETE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/ 4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/l/ PV2PZDA4VGO3PPNCMHCCT4YDVN CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ link CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ linkMODIFY
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ work CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb-init/ committed CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ lower CREATE
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ lower MODIFY
03/03/22 11:44 /var/lib/docker/image/overlay2/layerdb/mounts/ 15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52 CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/containers/ 15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52 CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-hostconfig.json389982635 ATTRIB
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-config.v2.json466278670 CREATE
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-config.v2.json466278670 MODIFY
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-hostconfig.json070462229 CREATE
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-hostconfig.json070462229 MODIFY
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-hostconfig.json070462229 ATTRIB
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-config.v2.json466278670 ATTRIB
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ merged CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/work/ work CREATE,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/work/ work ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/work/ work ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/work/ work ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/work/work/ ATTRIB,ISDIR
03/03/22 11:44 /var/lib/docker/overlay2/4f6ff7566e421e62c68b599f85585f08ea662bfa44eb0eeb7e9da0e2858745cb/ merged DELETE,ISDIR
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-config.v2.json231746416 CREATE
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-config.v2.json231746416 MODIFY
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-hostconfig.json732808207 CREATE
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-hostconfig.json732808207 ATTRIB
03/03/22 11:44 /var/lib/docker/containers/15b8d0b7c46c37cf62a2c388aba016226e4ec327a5729991f6a1ae9e81b89e52/ .tmp-config.v2.json231746416 ATTRIB
```
##### 2.5.2 实验2-查看配置
实际上docker create container 是制定了所有配置。包括运行命令。从inspect 就可以看出来。
```
"Config": {
"Hostname": "687c38e427a4",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": true,
"AttachStderr": true,
"ExposedPorts": {
"80/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NGINX_VERSION=1.21.5",
"NJS_VERSION=0.7.1",
"PKG_RELEASE=1~bullseye"
],
"Cmd": [
"ls"
],
"Image": "nginx",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": [
"/docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
},
"StopSignal": "SIGQUIT"
},
```
#### 2.6 总结
docker create只是根据docker的配置(包括使用什么存储系统,root目录等),完成了所有的初始化。
主要是利用镜像层已有的数据。初始化container的所有数据。
主要是初始化这个目录:/var/lib/docker/containers/contaienrId
### 3. Docker start container详细流程分析
从上面的分析可以得出。docker create 就已经将所有的准备工作做好了,包括运行的参数。接下来看看docker start做了什么。
#### 3.1 postContainerExecStart
和create一样,这里主要是调用了postContainerExecStart进行start
```
func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// If contentLength is -1, we can assumed chunked encoding
// or more technically that the length is unknown
// https://golang.org/src/pkg/net/http/request.go#L139
// net/http otherwise seems to swallow any headers related to chunked encoding
// including r.TransferEncoding
// allow a nil body for backwards compatibility
version := httputils.VersionFromContext(ctx)
var hostConfig *container.HostConfig
// A non-nil json object is at least 7 characters.
if r.ContentLength > 7 || r.ContentLength == -1 {
if versions.GreaterThanOrEqualTo(version, "1.24") {
return bodyOnStartError{}
}
if err := httputils.CheckForJSON(r); err != nil {
return err
}
c, err := s.decoder.DecodeHostConfig(r.Body)
if err != nil {
return err
}
hostConfig = c
}
if err := httputils.ParseForm(r); err != nil {
return err
}
checkpoint := r.Form.Get("checkpoint")
checkpointDir := r.Form.Get("checkpoint-dir")
if err := s.backend.ContainerStart(vars["name"], hostConfig, checkpoint, checkpointDir); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
return nil
}
```
<br>
#### 3.2 ContainerStart
ContainerStart主要逻辑如下:
(1)根据容器name, 判断容器状态,比如paused状态的容器不能start等等。
(2)判断hostconfig信息等,hostconfig必须在create的时候指定,start只管启动
(3)调用containerStart进行start。核心是这个函数
```
// ContainerStart starts a container.
func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig, checkpoint string, checkpointDir string) error {
if checkpoint != "" && !daemon.HasExperimental() {
return errdefs.InvalidParameter(errors.New("checkpoint is only supported in experimental mode"))
}
container, err := daemon.GetContainer(name)
if err != nil {
return err
}
validateState := func() error {
container.Lock()
defer container.Unlock()
if container.Paused {
return errdefs.Conflict(errors.New("cannot start a paused container, try unpause instead"))
}
if container.Running {
return containerNotModifiedError{running: true}
}
if container.RemovalInProgress || container.Dead {
return errdefs.Conflict(errors.New("container is marked for removal and cannot be started"))
}
return nil
}
if err := validateState(); err != nil {
return err
}
// Windows does not have the backwards compatibility issue here.
if runtime.GOOS != "windows" {
// This is kept for backward compatibility - hostconfig should be passed when
// creating a container, not during start.
if hostConfig != nil {
logrus.Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and has been removed in Docker 1.12")
oldNetworkMode := container.HostConfig.NetworkMode
if err := daemon.setSecurityOptions(container, hostConfig); err != nil {
return errdefs.InvalidParameter(err)
}
if err := daemon.mergeAndVerifyLogConfig(&hostConfig.LogConfig); err != nil {
return errdefs.InvalidParameter(err)
}
if err := daemon.setHostConfig(container, hostConfig); err != nil {
return errdefs.InvalidParameter(err)
}
newNetworkMode := container.HostConfig.NetworkMode
if string(oldNetworkMode) != string(newNetworkMode) {
// if user has change the network mode on starting, clean up the
// old networks. It is a deprecated feature and has been removed in Docker 1.12
container.NetworkSettings.Networks = nil
if err := container.CheckpointTo(daemon.containersReplica); err != nil {
return errdefs.System(err)
}
}
container.InitDNSHostConfig()
}
} else {
if hostConfig != nil {
return errdefs.InvalidParameter(errors.New("Supplying a hostconfig on start is not supported. It should be supplied on create"))
}
}
// check if hostConfig is in line with the current system settings.
// It may happen cgroups are umounted or the like.
if _, err = daemon.verifyContainerSettings(container.OS, container.HostConfig, nil, false); err != nil {
return errdefs.InvalidParameter(err)
}
// Adapt for old containers in case we have updates in this function and
// old containers never have chance to call the new function in create stage.
if hostConfig != nil {
if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil {
return errdefs.InvalidParameter(err)
}
}
return daemon.containerStart(container, checkpoint, checkpointDir, true)
}
```
<br>
#### 3.3 containerStart
核心逻辑如下:
(1)判断容器状态,是否已经running或者dead
(2)通过defer函数进行收尾,然后start过程出现了错误,调用daemon.Cleanup,ContainerRm进行清理工作
(3)挂载目录。docker start过程也会很多目录的创建,mount
(4)设置容器的网络模式,默认模式bridge:同一个host主机上容器的通信通过Linux bridge进行。与宿主机外部网络的通信需要通过宿主机端 口进行NAT
(5)创建/proc /dev等spec文件,对容器所特有的属性都进行设置,例如:资源限制,命名空间,安全模式等等配置信息
(6)初始化libContainerd的 createOptions,到这里就是调用containerd了
(7)通过containerd创建容器
(8)通过containerd启动容器
(9)设置状态,已经running等等
```
// containerStart prepares the container to run by setting up everything the
// container needs, such as storage and networking, as well as links
// between containers. The container is left waiting for a signal to
// begin running.
func (daemon *Daemon) containerStart(container *container.Container, checkpoint string, checkpointDir string, resetRestartManager bool) (err error) {
start := time.Now()
container.Lock()
defer container.Unlock()
// 1.判断容器状态,是否已经running或者dead
if resetRestartManager && container.Running { // skip this check if already in restarting step and resetRestartManager==false
return nil
}
if container.RemovalInProgress || container.Dead {
return errdefs.Conflict(errors.New("container is marked for removal and cannot be started"))
}
if checkpointDir != "" {
// TODO(mlaventure): how would we support that?
return errdefs.Forbidden(errors.New("custom checkpointdir is not supported"))
}
// 2.通过defer函数进行收尾,然后start过程出现了错误,调用daemon.Cleanup,ContainerRm进行清理工作
// if we encounter an error during start we need to ensure that any other
// setup has been cleaned up properly
defer func() {
if err != nil {
container.SetError(err)
// if no one else has set it, make sure we don't leave it at zero
if container.ExitCode() == 0 {
container.SetExitCode(128)
}
if err := container.CheckpointTo(daemon.containersReplica); err != nil {
logrus.Errorf("%s: failed saving state on start failure: %v", container.ID, err)
}
container.Reset(false)
daemon.Cleanup(container)
// if containers AutoRemove flag is set, remove it after clean up
if container.HostConfig.AutoRemove {
container.Unlock()
if err := daemon.ContainerRm(container.ID, &types.ContainerRmConfig{ForceRemove: true, RemoveVolume: true}); err != nil {
logrus.Errorf("can't remove container %s: %v", container.ID, err)
}
container.Lock()
}
}
}()
// 3.挂载目录。docker start过程也会很多目录的创建,mount
if err := daemon.conditionalMountOnStart(container); err != nil {
return err
}
// 4.设置容器的网络模式,默认模式bridge:同一个host主机上容器的通信通过Linux bridge进行。与宿主机外部网络的通信需要通过宿主机端 口进行NAT
if err := daemon.initializeNetworking(container); err != nil {
return err
}
// 5. 创建/proc /dev等spec文件,对容器所特有的属性都进行设置,例如:资源限制,命名空间,安全模式等等配置信息
spec, err := daemon.createSpec(container)
if err != nil {
return errdefs.System(err)
}
if resetRestartManager {
container.ResetRestartManager(true)
container.HasBeenManuallyStopped = false
}
if err := daemon.saveApparmorConfig(container); err != nil {
return err
}
if checkpoint != "" {
checkpointDir, err = getCheckpointDir(checkpointDir, checkpoint, container.Name, container.ID, container.CheckpointDir(), false)
if err != nil {
return err
}
}
// 6.初始化libContainerd的 createOptions,到这里就是调用containerd了
createOptions, err := daemon.getLibcontainerdCreateOptions(container)
if err != nil {
return err
}
ctx := context.TODO()
// 7. 通过containerd创建容器
err = daemon.containerd.Create(ctx, container.ID, spec, createOptions)
if err != nil {
if errdefs.IsConflict(err) {
logrus.WithError(err).WithField("container", container.ID).Error("Container not cleaned up from containerd from previous run")
// best effort to clean up old container object
daemon.containerd.DeleteTask(ctx, container.ID)
if err := daemon.containerd.Delete(ctx, container.ID); err != nil && !errdefs.IsNotFound(err) {
logrus.WithError(err).WithField("container", container.ID).Error("Error cleaning up stale containerd container object")
}
err = daemon.containerd.Create(ctx, container.ID, spec, createOptions)
}
if err != nil {
return translateContainerdStartErr(container.Path, container.SetExitCode, err)
}
}
// 8. 通过containerd启动容器
// TODO(mlaventure): we need to specify checkpoint options here
pid, err := daemon.containerd.Start(context.Background(), container.ID, checkpointDir,
container.StreamConfig.Stdin() != nil || container.Config.Tty,
container.InitializeStdio)
if err != nil {
if err := daemon.containerd.Delete(context.Background(), container.ID); err != nil {
logrus.WithError(err).WithField("container", container.ID).
Error("failed to delete failed start container")
}
return translateContainerdStartErr(container.Path, container.SetExitCode, err)
}
// 9.设置状态,已经running等等
container.SetRunning(pid, true)
container.HasBeenStartedBefore = true
daemon.setStateCounter(container)
daemon.initHealthMonitor(container)
if err := container.CheckpointTo(daemon.containersReplica); err != nil {
logrus.WithError(err).WithField("container", container.ID).
Errorf("failed to store container")
}
daemon.LogContainerEvent(container, "start")
containerActions.WithValues("start").UpdateSince(start)
return nil
}
```
<br>
**docker start nginx 过程的目录变化**
```
root@k8s-node:~# inotifywait -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w %f %e' -e modify,delete,create,attrib /var/lib/docker
03/03/22 17:06 /var/lib/docker/overlay2/105b22191a32cf89aa1ffb96ee4a1a55032ed251a2877f5ea480c4d4921c5244/ merged CREATE,ISDIR
03/03/22 17:06 /var/lib/docker/overlay2/105b22191a32cf89aa1ffb96ee4a1a55032ed251a2877f5ea480c4d4921c5244/work/ work DELETE,ISDIR
03/03/22 17:06 /var/lib/docker/overlay2/105b22191a32cf89aa1ffb96ee4a1a55032ed251a2877f5ea480c4d4921c5244/work/ work CREATE,ISDIR
03/03/22 17:06 /var/lib/docker/overlay2/105b22191a32cf89aa1ffb96ee4a1a55032ed251a2877f5ea480c4d4921c5244/work/ work ATTRIB,ISDIR
03/03/22 17:06 /var/lib/docker/overlay2/105b22191a32cf89aa1ffb96ee4a1a55032ed251a2877f5ea480c4d4921c5244/work/ work ATTRIB,ISDIR
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/network/files/ local-kv.db MODIFY
03/03/22 17:06 /var/lib/docker/containers/8f7318baf651f7a9539e1d41486151946b36af202f0b0ef4257954911fb77edc/ hosts MODIFY
03/03/22 17:06 /var/lib/docker/containers/8f7318baf651f7a9539e1d41486151946b36af202f0b0ef4257954911fb77edc/ hosts MODIFY
03/03/22 17:06 /var/lib/docker/containers/8f7318baf651f7a9539e1d41486151946b36af202f0b0ef4257954911fb77edc/ resolv.conf MODIFY
03/03/22 17:06 /var/lib/docker/containers/8f7318baf651f7a9539e1d41486151946b36af2
```
<br>
### 4. docker start 创建的详细过程
上面已经知道了docker start的大致流程。接下里才是重点,就是containerd是如何创建容器的,以及runc是啥时候调用的等等。
这一节就是详细弄清楚整个过程,可能会拆分章节。
#### 4.1 containerd的初始化
在dockerd启动的时候,通过initContainerD函数启动了containerd
#### 4.2 容器的网络设置
待补充,需要补充其他知识,可能会再开一章节
#### 4.3 容器的spec设置-createSpec函数
Linux 内核提供了一种通过`/proc`文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。 proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。 它以文件系统的方式为访问系统内核数据的操作提供接口。
```
unc (daemon *Daemon) createSpec(c *container.Container) (retSpec *specs.Spec, err error) {
var (
opts []coci.SpecOpts
s = oci.DefaultSpec()
)
opts = append(opts,
WithCommonOptions(daemon, c),
WithCgroups(daemon, c),
WithResources(c),
WithSysctls(c),
WithDevices(daemon, c),
WithUser(c),
WithRlimits(daemon, c),
WithNamespaces(daemon, c),
WithCapabilities(c),
WithSeccomp(daemon, c),
WithMounts(daemon, c),
WithLibnetwork(daemon, c),
WithApparmor(c),
WithSelinux(c),
WithOOMScore(&c.HostConfig.OomScoreAdj),
)
if c.NoNewPrivileges {
opts = append(opts, coci.WithNoNewPrivileges)
}
// Set the masked and readonly paths with regard to the host config options if they are set.
if c.HostConfig.MaskedPaths != nil {
opts = append(opts, coci.WithMaskedPaths(c.HostConfig.MaskedPaths))
}
if c.HostConfig.ReadonlyPaths != nil {
opts = append(opts, coci.WithReadonlyPaths(c.HostConfig.ReadonlyPaths))
}
if daemon.configStore.Rootless {
opts = append(opts, WithRootless)
}
return &s, coci.ApplyOpts(context.Background(), nil, &containers.Container{
ID: c.ID,
}, &s, opts...)
}
```
#### 4.4 containerd创建容器的详细流程
待补充
### 5. 总结
(1)docker run nginx ls 其实是分成了两个步骤。`docker create contianer nginx ls` 和 `docker start nginx`
(2)docker create 做了前期的准备工作,包括下载镜像,准备所有的文件和目录
(3)docker start核心是调用containerd进行start,启动进程等等。这个过程涉及网络以及其他底层的知识。目前先了解到这里,还有很多细节比如第四章节还待补充。这个等补充一波知识后,再更新。
================================================
FILE: docker/2. linux cgroup 知识准备.md
================================================
* [0\. 说明](#0-说明)
* [1\. cgroup简介](#1-cgroup简介)
* [2\. CGroup 使用](#2-cgroup-使用)
* [3\. CGroup 基本概念](#3-cgroup-基本概念)
* [4\. CGroup 操作规则](#4-cgroup-操作规则)
* [5\. CGroup的原理实现](#5-cgroup的原理实现)
* [5\.1 cgroup 结构体](#51-cgroup-结构体)
* [5\.2 CGroup 的挂载](#52-cgroup-的挂载)
* [5\.3 向 CGroup 添加要进行资源控制的进程](#53-向-cgroup-添加要进行资源控制的进程)
* [5\.4 限制 CGroup 的资源使用](#54-限制-cgroup-的资源使用)
* [5\.5 限制进程使用资源](#55-限制进程使用资源)
* [6\.参考资料](#6参考资料)
### 0. 说明
本文章转载微信公众的一篇文章。地址如下:https://mp.weixin.qq.com/s/n796FnrKsfLLxcvV4-dAlg
该笔记绝大部分来源于上诉公众号,用于自己对cgroup的理解,当做笔记记录。
### 1. cgroup简介
`CGroup` 全称 `Control Group` 中文意思为 `控制组`,用于控制(限制)进程对系统各种资源的使用,比如 `CPU`、`内存`、`网络` 和 `磁盘I/O` 等资源的限制,著名的容器引擎 `Docker` 就是使用 `CGroup` 来对容器进行资源限制。
### 2. CGroup 使用
本文主要以 `内存子系统(memory subsystem)` 作为例子来阐述 `CGroup` 的原理,所以这里先介绍怎么通过 `内存子系统` 来限制进程对内存的使用。
> `子系统` 是 `CGroup` 用于控制某种资源(如内存或者CPU等)使用的逻辑或者算法
>
> 在系统的开机阶段,systemd会把支持的子系统挂载到默认的 `/sys/fs/cgroup` 目录下面。
`CGroup` 使用了 `虚拟文件系统` 来进行管理限制的资源信息和被限制的进程列表等,例如要创建一个限制内存使用的 `CGroup` 可以使用下面命令:
```
$ mount -t cgroup -o memory memory /sys/fs/cgroup/memory
```
上面的命令用于创建内存子系统的根 `CGroup`,如果系统已经存在可以跳过。然后我们使用下面命令在这个目录下面创建一个新的目录 `test`,
```
$ mkdir /sys/fs/cgroup/memory/test
```
这样就在内存子系统的根 `CGroup` 下创建了一个子 `CGroup`,我们可以通过 `ls` 目录来查看这个目录下有哪些文件:
```
$ ls -l /sys/fs/cgroup/memory/test
cgroup.clone_childrenmemory.kmem.max_usage_in_bytesmemory.limit_in_bytesmemory.numa_statmemory.use_hierarchy
cgroup.event_controlmemory.kmem.slabinfomemory.max_usage_in_bytesmemory.oom_controlnotify_on_release
cgroup.procsmemory.kmem.tcp.failcntmemory.memsw.failcntmemory.pressure_leveltasks
memory.failcntmemory.kmem.tcp.limit_in_bytesmemory.memsw.limit_in_bytesmemory.soft_limit_in_bytes
memory.force_emptymemory.kmem.tcp.max_usage_in_bytesmemory.memsw.max_usage_in_bytesmemory.stat
memory.kmem.failcntmemory.kmem.tcp.usage_in_bytesmemory.memsw.usage_in_bytesmemory.swappiness
memory.kmem.limit_in_bytesmemory.kmem.usage_in_bytesmemory.move_charge_at_immigratememory.usage_in_bytes
```
可以看到在目录下有很多文件,每个文件都是 `CGroup` 用于控制进程组的资源使用。我们可以向 `memory.limit_in_bytes` 文件写入限制进程(进程组)使用的内存大小,单位为字节(bytes)。例如可以使用以下命令写入限制使用的内存大小为 `1MB`:
```
$ echo 1048576 > /sys/fs/cgroup/memory/test/memory.limit_in_bytes
```
然后我们可以通过以下命令把要限制的进程加入到 `CGroup` 中:
```
$ echo task_pid > /sys/fs/cgroup/memory/test/tasks
```
上面的 `task_pid` 为进程的 `PID`,把进程PID添加到 `tasks` 文件后,进程对内存的使用就受到此 `CGroup` 的限制。
### 3. CGroup 基本概念
在介绍 `CGroup` 原理前,先介绍一下 `CGroup` 几个相关的概念,因为要理解 `CGroup` 就必须要理解他们:
- `任务(task)`。任务指的是系统的一个进程,如上面介绍的 `tasks` 文件中的进程;
- `控制组(control group)`。控制组就是受相同资源限制的一组进程。`CGroup` 中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组,也从一个进程组迁移到另一个控制组。一个进程组的进程可以使用 `CGroup` 以控制组为单位分配的资源,同时受到 `CGroup` 以控制组为单位设定的限制;
- `层级(hierarchy)`。由于控制组是以目录形式存在的,所以控制组可以组织成层级的形式,即一棵控制组组成的树。控制组树上的子节点控制组是父节点控制组的孩子,继承父控制组的特定的属性;
- `子系统(subsystem)`。一个子系统就是一个资源控制器,比如 `CPU子系统` 就是控制 CPU 时间分配的一个控制器。子系统必须附加(attach)到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制组都受到这个子系统的控制。
他们之间的关系如下图:

我们可以把 `层级` 中的一个目录当成是一个 `CGroup`,那么目录里面的文件就是这个 `CGroup` 用于控制进程组使用各种资源的信息(比如 `tasks` 文件用于保存这个 `CGroup` 控制的进程组所有的进程PID,而 `memory.limit_in_bytes` 文件用于描述这个 `CGroup` 能够使用的内存字节数)。
而附加在 `层级` 上的 `子系统` 表示这个 `层级` 中的 `CGroup` 可以控制哪些资源,每当向 `层级` 附加 `子系统` 时,`层级` 中的所有 `CGroup` 都会产生很多与 `子系统` 资源控制相关的文件。
### 4. CGroup 操作规则
使用 `CGroup` 时,必须按照 `CGroup` 一些操作规则来进行操作,否则会出错。下面介绍一下关于 `CGroup` 的一些操作规则:
1. 一个 `层级` 可以附加多个 `子系统`,如下图:
2. 一个已经被挂载的 `子系统` 只能被再次挂载在一个空的 `层级` 上,不能挂载到已经挂载了其他 `子系统` 的 `层级`,如下图:

3. 每个 `任务` 只能在同一个 `层级` 的唯一一个 `CGroup` 里,并且可以在多个不同层级的 `CGroup` 中,如下图:

4. 子进程在被 `fork` 出时自动继承父进程所在 `CGroup`,但是 `fork` 之后就可以按需调整到其他 `CGroup`,如下图:

### 5. CGroup的原理实现
#### 5.1 `cgroup` 结构体
前面介绍过,`cgroup` 是用来控制进程组对各种资源的使用,而在内核中,`cgroup` 是通过 `cgroup` 结构体来描述的,我们来看看其定义:
```
struct cgroup {
unsigned long flags; /* "unsigned long" so bitops work */
atomic_t count;
struct list_head sibling; /* my parent's children */
struct list_head children; /* my children */
struct cgroup *parent; /* my parent */
struct dentry *dentry; /* cgroup fs entry */
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
struct cgroupfs_root *root;
struct cgroup *top_cgroup;
struct list_head css_sets;
struct list_head release_list;
};
```
下面我们来介绍一下 `cgroup` 结构体各个字段的用途:
1. `flags`: 用于标识当前 `cgroup` 的状态。
2. `count`: 引用计数器,表示有多少个进程在使用这个 `cgroup`。
3. `sibling、children、parent`: 由于 `cgroup` 是通过 `层级` 来进行管理的,这三个字段就把同一个 `层级` 的所有 `cgroup` 连接成一棵树。`parent` 指向当前 `cgroup` 的父节点,`sibling` 连接着所有兄弟节点,而 `children` 连接着当前 `cgroup` 的所有子节点。
4. `dentry`: 由于 `cgroup` 是通过 `虚拟文件系统` 来进行管理的,在介绍 `cgroup` 使用时说过,可以把 `cgroup` 当成是 `层级` 中的一个目录,所以 `dentry` 字段就是用来描述这个目录的。
5. `subsys`: 前面说过,`子系统` 能够附加到 `层级`,而附加到 `层级` 的 `子系统` 都有其限制进程组使用资源的算法和统计数据。所以 `subsys` 字段就是提供给各个 `子系统` 存放其限制进程组使用资源的统计数据。我们可以看到 `subsys` 字段是一个数组,而数组中的每一个元素都代表了一个 `子系统` 相关的统计数据。从实现来看,`cgroup` 只是把多个进程组织成控制进程组,而真正限制资源使用的是各个 `子系统`。
6. `root`: 用于保存 `层级` 的一些数据,比如:`层级` 的根节点,附加到 `层级` 的 `子系统` 列表(因为一个 `层级` 可以附加多个 `子系统`),还有这个 `层级` 有多少个 `cgroup` 节点等。
7. `top_cgroup`: `层级` 的根节点(根cgroup)。
我们通过下面图片来描述 `层级` 中各个 `cgroup` 组成的树状关系:

`cgroup_subsys_state` 结构体
每个 `子系统` 都有属于自己的资源控制统计信息结构,而且每个 `cgroup` 都绑定一个这样的结构,这种资源控制统计信息结构就是通过 `cgroup_subsys_state` 结构体实现的,其定义如下:
```
struct cgroup_subsys_state {
struct cgroup *cgroup;
atomic_t refcnt;
unsigned long flags;
};
```
下面介绍一下 `cgroup_subsys_state` 结构各个字段的作用:
1. `cgroup`: 指向了这个资源控制统计信息所属的 `cgroup`。
2. `refcnt`: 引用计数器。
3. `flags`: 标志位,如果这个资源控制统计信息所属的 `cgroup` 是 `层级` 的根节点,那么就会将这个标志位设置为 `CSS_ROOT` 表示属于根节点。
从 `cgroup_subsys_state` 结构的定义看不到各个 `子系统` 相关的资源控制统计信息,这是因为 `cgroup_subsys_state` 结构并不是真实的资源控制统计信息结构,比如 `内存子系统` 真正的资源控制统计信息结构是 `mem_cgroup`,那么怎样通过这个 `cgroup_subsys_state` 结构去找到对应的 `mem_cgroup` 结构呢?我们来看看 `mem_cgroup` 结构的定义:
```
struct mem_cgroup {
struct cgroup_subsys_state css; // 注意这里
struct res_counter res;
struct mem_cgroup_lru_info info;
int prev_priority;
struct mem_cgroup_stat stat;
};
```
从 `mem_cgroup` 结构的定义可以发现,`mem_cgroup` 结构的第一个字段就是一个 `cgroup_subsys_state` 结构。下面的图片展示了他们之间的关系:

从上图可以看出,`mem_cgroup` 结构包含了 `cgroup_subsys_state` 结构,`内存子系统` 对外暴露出 `mem_cgroup` 结构的 `cgroup_subsys_state` 部分(即返回 `cgroup_subsys_state` 结构的指针),而其余部分由 `内存子系统` 自己维护和使用。
由于 `cgroup_subsys_state` 部分在 `mem_cgroup` 结构的首部,所以要将 `cgroup_subsys_state` 结构转换成 `mem_cgroup` 结构,只需要通过指针类型转换即可。如下代码:
`cgroup` 结构与 `cgroup_subsys_state` 结构之间的关系如下图:

`css_set` 结构体
由于一个进程可以同时添加到不同的 `cgroup` 中(前提是这些 `cgroup` 属于不同的 `层级`)进行资源控制,而这些 `cgroup` 附加了不同的资源控制 `子系统`。所以需要使用一个结构把这些 `子系统` 的资源控制统计信息收集起来,方便进程通过 `子系统ID` 快速查找到对应的 `子系统` 资源控制统计信息,而 `css_set` 结构体就是用来做这件事情。`css_set` 结构体定义如下:
```
struct css_set {
struct kref ref;
struct list_head list;
struct list_head tasks;
struct list_head cg_links;
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
};
```
下面介绍一下 `css_set` 结构体各个字段的作用:
1. `ref`: 引用计数器,用于计算有多少个进程在使用此 `css_set`。
2. `list`: 用于连接所有 `css_set`。
3. `tasks`: 由于可能存在多个进程同时受到相同的 `cgroup` 控制,所以用此字段把所有使用此 `css_set` 的进程连接起来。
4. `subsys`: 用于收集各种 `子系统` 的统计信息结构。
进程描述符 `task_struct` 有两个字段与此相关,如下:
```
struct task_struct {
...
struct css_set *cgroups;
struct list_head cg_list;
...
}
```
可以看出,`task_struct` 结构的 `cgroups` 字段就是指向 `css_set` 结构的指针,而 `cg_list` 字段用于连接所有使用此 `css_set` 结构的进程列表。
`task_struct` 结构与 `css_set` 结构的关系如下图:

`cgroup_subsys` 结构
`CGroup` 通过 `cgroup_subsys` 结构操作各个 `子系统`,每个 `子系统` 都要实现一个这样的结构,其定义如下:
```
struct cgroup_subsys {
struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss,
struct cgroup *cgrp);
void (*pre_destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
int (*can_attach)(struct cgroup_subsys *ss,
struct cgroup *cgrp, struct task_struct *tsk);
void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
struct cgroup *old_cgrp, struct task_struct *tsk);
void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
void (*exit)(struct cgroup_subsys *ss, struct task_struct *task);
int (*populate)(struct cgroup_subsys *ss,
struct cgroup *cgrp);
void (*post_clone)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*bind)(struct cgroup_subsys *ss, struct cgroup *root);
int subsys_id;
int active;
int disabled;
int early_init;
const char *name;
struct cgroupfs_root *root;
struct list_head sibling;
void *private;
};
```
`cgroup_subsys` 结构包含了很多函数指针,通过这些函数指针,`CGroup` 可以对 `子系统` 进行一些操作。比如向 `CGroup` 的 `tasks` 文件添加要控制的进程PID时,就会调用 `cgroup_subsys` 结构的 `attach()` 函数。当在 `层级` 中创建新目录时,就会调用 `create()` 函数创建一个 `子系统` 的资源控制统计信息对象 `cgroup_subsys_state`,并且调用 `populate()` 函数创建 `子系统` 相关的资源控制信息文件。
除了函数指针外,`cgroup_subsys` 结构还包含了很多字段,下面说明一下各个字段的作用:
1. `subsys_id`: 表示了子系统的ID。
2. `active`: 表示子系统是否被激活。
3. `disabled`: 子系统是否被禁止。
4. `name`: 子系统名称。
5. `root`: 被附加到的层级挂载点。
6. `sibling`: 用于连接被附加到同一个层级的所有子系统。
7. `private`: 私有数据。
`内存子系统` 定义了一个名为 `mem_cgroup_subsys` 的 `cgroup_subsys` 结构,如下:
```
struct cgroup_subsys mem_cgroup_subsys = {
.name = "memory",
.subsys_id = mem_cgroup_subsys_id,
.create = mem_cgroup_create,
.pre_destroy = mem_cgroup_pre_destroy,
.destroy = mem_cgroup_destroy,
.populate = mem_cgroup_populate,
.attach = mem_cgroup_move_task,
.early_init = 0,
};
```
另外 Linux 内核还定义了一个 `cgroup_subsys` 结构的数组 `subsys`,用于保存所有 `子系统` 的 `cgroup_subsys` 结构,如下:
```
static struct cgroup_subsys *subsys[] = {
cpuset_subsys,
debug_subsys,
ns_subsys,
cpu_cgroup_subsys,
cpuacct_subsys,
mem_cgroup_subsys
};
```
#### 5.2 `CGroup` 的挂载
前面介绍了 `CGroup` 相关的几个结构体,接下来我们分析一下 `CGroup` 的实现。
要使用 `CGroup` 功能首先必须先进行挂载操作,比如使用下面命令挂载一个 `CGroup`:
```
$ mount -t cgroup -o memory memory /sys/fs/cgroup/memory
```
在上面的命令中,`-t` 参数指定了要挂载的文件系统类型为 `cgroup`,而 `-o` 参数表示要附加到此 `层级` 的子系统,上面表示附加了 `内存子系统`,当然可以附加多个 `子系统`。而紧随 `-o` 参数后的 `memory` 指定了此 `CGroup` 的名字,最后一个参数表示要挂载的目录路径。
挂载过程最终会调用内核函数 `cgroup_get_sb()` 完成,由于 `cgroup_get_sb()` 函数比较长,所以我们只分析重要部分:
```
static int cgroup_get_sb(struct file_system_type *fs_type,
int flags, const char *unused_dev_name,
void *data, struct vfsmount *mnt)
{
...
struct cgroupfs_root *root;
...
root = kzalloc(sizeof(*root), GFP_KERNEL);
...
ret = rebind_subsystems(root, root->subsys_bits);
...
struct cgroup *cgrp = &root->top_cgroup;
cgroup_populate_dir(cgrp);
...
}
```
`cgroup_get_sb()` 函数会调用 `kzalloc()` 函数创建一个 `cgroupfs_root` 结构。`cgroupfs_root` 结构主要用于描述这个挂载点的信息,其定义如下:
```
struct cgroupfs_root {
struct super_block *sb;
unsigned long subsys_bits;
unsigned long actual_subsys_bits;
struct list_head subsys_list;
struct cgroup top_cgroup;
int number_of_cgroups;
struct list_head root_list;
unsigned long flags;
char release_agent_path[PATH_MAX];
};
```
下面介绍一下 `cgroupfs_root` 结构的各个字段含义:
1. `sb`: 挂载的文件系统超级块。
2. `subsys_bits/actual_subsys_bits`: 附加到此层级的子系统标志。
3. `subsys_list`: 附加到此层级的子系统(cgroup_subsys)列表。
4. `top_cgroup`: 此层级的根cgroup。
5. `number_of_cgroups`: 层级中有多少个cgroup。
6. `root_list`: 连接系统中所有的cgroupfs_root。
7. `flags`: 标志位。
其中最重要的是 `subsys_list` 和 `top_cgroup` 字段,`subsys_list` 表示了附加到此 `层级` 的所有 `子系统`,而 `top_cgroup` 表示此 `层级` 的根 `cgroup`。
接着调用 `rebind_subsystems()` 函数把挂载时指定要附加的 `子系统` 添加到 `cgroupfs_root` 结构的 `subsys_list` 链表中,并且为根 `cgroup` 的 `subsys` 字段设置各个 `子系统` 的资源控制统计信息对象,最后调用 `cgroup_populate_dir()` 函数向挂载目录创建 `cgroup` 的管理文件(如 `tasks` 文件)和各个 `子系统` 的管理文件(如 `memory.limit_in_bytes` 文件)。
#### 5.3 向 `CGroup` 添加要进行资源控制的进程
通过向 `CGroup` 的 `tasks` 文件写入要进行资源控制的进程PID,即可以对进程进行资源控制。例如下面命令:
```
$ echo 123012 > /sys/fs/cgroup/memory/test/tasks
```
向 `tasks` 文件写入进程PID是通过 `attach_task_by_pid()` 函数实现的,代码如下:
```
static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
{
pid_t pid;
struct task_struct *tsk;
int ret;
if (sscanf(pidbuf, "%d", &pid) != 1) // 读取进程pid
return -EIO;
if (pid) { // 如果有指定进程pid
...
tsk = find_task_by_vpid(pid); // 通过pid查找对应进程的进程描述符
if (!tsk || tsk->flags & PF_EXITING) {
rcu_read_unlock();
return -ESRCH;
}
...
} else {
tsk = current; // 如果没有指定进程pid, 就使用当前进程
...
}
ret = cgroup_attach_task(cgrp, tsk); // 调用 cgroup_attach_task() 把进程添加到cgroup中
...
return ret;
}
```
`attach_task_by_pid()` 函数首先会判断是否指定了进程pid,如果指定了就通过进程pid查找到进程描述符,如果没指定就使用当前进程,然后通过调用 `cgroup_attach_task()` 函数把进程添加到 `cgroup` 中。
我们接着看看 `cgroup_attach_task()` 函数的实现:
```
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
int retval = 0;
struct cgroup_subsys *ss;
struct cgroup *oldcgrp;
struct css_set *cg = tsk->cgroups;
struct css_set *newcg;
struct cgroupfs_root *root = cgrp->root;
...
newcg = find_css_set(cg, cgrp); // 根据新的cgroup查找css_set对象
...
rcu_assign_pointer(tsk->cgroups, newcg); // 把进程的cgroups字段设置为新的css_set对象
...
// 把进程添加到css_set对象的tasks列表中
write_lock(&css_set_lock);
if (!list_empty(&tsk->cg_list)) {
list_del(&tsk->cg_list);
list_add(&tsk->cg_list, &newcg->tasks);
}
write_unlock(&css_set_lock);
// 调用各个子系统的attach函数
for_each_subsys(root, ss) {
if (ss->attach)
ss->attach(ss, cgrp, oldcgrp, tsk);
}
...
return 0;
}
```
`cgroup_attach_task()` 函数首先会调用 `find_css_set()` 函数查找或者创建一个 `css_set` 对象。前面说过 `css_set` 对象用于收集不同 `cgroup` 上附加的 `子系统` 资源统计信息对象。
因为一个进程能够被加入到不同的 `cgroup` 进行资源控制,所以 `find_css_set()` 函数就是收集进程所在的所有 `cgroup` 上附加的 `子系统` 资源统计信息对象,并返回一个 `css_set` 对象。接着把进程描述符的 `cgroups` 字段设置为这个 `css_set` 对象,并且把进程添加到这个 `css_set` 对象的 `tasks` 链表中。
最后,`cgroup_attach_task()` 函数会调用附加在 `层级` 上的所有 `子系统` 的 `attach()` 函数对新增进程进行一些其他的操作(这些操作由各自 `子系统` 去实现)。
#### 5.4 限制 `CGroup` 的资源使用
本文主要是使用 `内存子系统` 作为例子,所以这里分析内存限制的原理。
可以向 `cgroup` 的 `memory.limit_in_bytes` 文件写入要限制使用的内存大小(单位为字节),如下面命令限制了这个 `cgroup` 只能使用 1MB 的内存:
```
$ echo 1048576 > /sys/fs/cgroup/memory/test/memory.limit_in_bytes
```
向 `memory.limit_in_bytes` 写入数据主要通过 `mem_cgroup_write()` 函数实现的,其实现如下:
```
static ssize_t mem_cgroup_write(struct cgroup *cont, struct cftype *cft,
struct file *file, const char __user *userbuf,
size_t nbytes, loff_t *ppos)
{
return res_counter_write(&mem_cgroup_from_cont(cont)->res,
cft->private, userbuf, nbytes, ppos,
mem_cgroup_write_strategy);
}
```
其主要工作就是把 `内存子系统` 的资源控制对象 `mem_cgroup` 的 `res.limit` 字段设置为指定的数值。
#### 5.5 限制进程使用资源
当设置好 `cgroup` 的资源使用限制信息,并且把进程添加到这个 `cgroup` 的 `tasks` 列表后,进程的资源使用就会受到这个 `cgroup` 的限制。这里使用 `内存子系统` 作为例子,来分析一下内核是怎么通过 `cgroup` 来限制进程对资源的使用的。
当进程要使用内存时,会调用 `do_anonymous_page()` 来申请一些内存页,而 `do_anonymous_page()` 函数会调用 `mem_cgroup_charge()` 函数来检测进程是否超过了 `cgroup` 设置的资源限制。而 `mem_cgroup_charge()` 最终会调用 `mem_cgroup_charge_common()` 函数进行检测,`mem_cgroup_charge_common()` 函数实现如下:
```
static int mem_cgroup_charge_common(struct page *page, struct mm_struct *mm,
gfp_t gfp_mask, enum charge_type ctype)
{
struct mem_cgroup *mem;
...
mem = rcu_dereference(mm->mem_cgroup); // 获取进程对应的内存限制对象
...
while (res_counter_charge(&mem->res, PAGE_SIZE)) { // 判断进程使用内存是否超出限制
if (!(gfp_mask & __GFP_WAIT))
goto out;
if (try_to_free_mem_cgroup_pages(mem, gfp_mask)) // 如果超出限制, 就释放一些不用的内存
continue;
if (res_counter_check_under_limit(&mem->res))
continue;
if (!nr_retries--) {
mem_cgroup_out_of_memory(mem, gfp_mask); // 如果尝试过5次后还是超出限制, 那么发出oom信号
goto out;
}
...
}
...
}
```
`mem_cgroup_charge_common()` 函数会对进程内存使用情况进行检测,如果进程已经超过了 `cgroup` 设置的限制,那么就会尝试进行释放一些不用的内存,如果还是超过限制,那么就会发出 `OOM (out of memory)` 的信号。
### 6.参考资料
[容器三把斧之 | cgroup原理与实现](https://mp.weixin.qq.com/s/n796FnrKsfLLxcvV4-dAlg)
[CGroup 介绍](https://mp.weixin.qq.com/s/66MKhzWTVCZ_nJ07fPrVIw)
================================================
FILE: docker/3. chroot 命令详解.md
================================================
* [1\. chroot命令介绍](#1-chroot命令介绍)
* [2\. chroot实践](#2-chroot实践)
* [2\.1 执行bash, ls命令](#21-执行bash-ls命令)
* [2\.2 执行ps命令](#22-执行ps命令)
* [2\.3 如何实现容器内pid 隔离](#23-如何实现容器内pid-隔离)
* [1\. 在容器外面证明可以做到](#1-在容器外面证明可以做到)
* [2\. 先取消之前的proc挂载](#2-先取消之前的proc挂载)
* [3\. 提取docker镜像中的rootfs文件](#3-提取docker镜像中的rootfs文件)
* [4\. 参考文档](#4-参考文档)
### 1. chroot命令介绍
把根目录换成指定的目的目录
**chroot命令** 用来在指定的根目录下运行指令。chroot,即 change root directory (更改 root 目录)。在 linux 系统中,系统默认的目录结构都是以`/`,即是以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为`/`位置。
在经过 chroot 命令之后,系统读取到的目录和文件将不在是旧系统根下的而是新根下(即被指定的新的位置)的目录结构和文件,因此它带来的好处大致有以下3个:
**增加了系统的安全性,限制了用户的权力:**
在经过 chroot 之后,在新根下将访问不到旧系统的根目录结构和文件,这样就增强了系统的安全性。这个一般是在登录 (login) 前使用 chroot,以此达到用户不能访问一些特定的文件。
**建立一个与原系统隔离的系统目录结构,方便用户的开发:**
使用 chroot 后,系统读取的是新根下的目录和文件,这是一个与原系统根下文件不相关的目录结构。在这个新的环境中,可以用来测试软件的静态编译以及一些与系统不相关的独立开发。
**切换系统的根目录位置,引导 Linux 系统启动以及急救系统等:**
chroot 的作用就是切换系统的根位置,而这个作用最为明显的是在系统初始引导磁盘的处理过程中使用,从初始 RAM 磁盘 (initrd) 切换系统的根位置并执行真正的 init。另外,当系统出现一些问题时,我们也可以使用 chroot 来切换到一个临时的系统。
<br>
### 2. chroot实践
直接使用是不行的,所以需要构建好test目录
```
root@k8s-master:~# chroot test
chroot: failed to run command ‘/bin/bash’: No such file or directory
```
<br>
#### 2.1 执行bash, ls命令
```
root@k8s-master:~/test# tree
.
├── bin
│ ├── bash // bin目录下要有bash可执行文件
│ └── ls
├── lib
│ ├── libc.so.6 //还要有ddl
│ ├── libdl.so.2
│ └── libtinfo.so.6
└── lib64
└── ld-linux-x86-64.so.2
// 还不能执行ls,因为没有ls对应的ddl
root@k8s-master:~/test/bin# chroot /root/test ls
ls: error while loading shared libraries: libselinux.so.1: cannot open shared object file: No such file or directory
// 通过ldd 查看ls依赖哪些动态链接库,然后拷贝到lib目录
root@k8s-master:~/test/bin# ldd ls
linux-vdso.so.1 (0x00007ffff6bb8000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f9580683000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f95804c2000)
libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f958044e000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f9580449000)
/lib64/ld-linux-x86-64.so.2 (0x00007f95808d6000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f9580428000)
root@k8s-master:~/test/bin#
root@k8s-master:~/test/bin#
root@k8s-master:~/test/bin# cp /lib/x86_64-linux-gnu/libselinux.so.1 /root/test/lib
有了这些,就可以chroot,执行bash, ls了
root@k8s-master:~/test# pwd
/root/test
root@k8s-master:~/test# tree
.
├── bin
│ ├── bash
│ └── ls
├── lib
│ ├── libc.so.6
│ ├── libdl.so.2
│ ├── libpcre.so.3
│ ├── libpthread.so.0
│ ├── libselinux.so.1
│ └── libtinfo.so.6
└── lib64
└── ld-linux-x86-64.so.2
3 directories, 9 files
```
<br>
```
成功chroot了,并且可以执行ls
root@k8s-master:~/test# chroot /root/test
bash-5.0# ls
bin lib lib64
```
#### 2.2 执行ps命令
ps命令有点特殊,除了需要拷贝ddl文件之外,还需要mount
```
root@k8s-master:~/test# chroot . ps
Error, do this: mount -t proc proc /proc
// 只能这样用, 其实最正确的做法应该是 mount -t proc proc /root/test/proc
root@k8s-master:~/test# mount -t proc proc proc
root@k8s-master:~/test#
root@k8s-master:~/test# pwd
/root/test
root@k8s-master:~/test# ls
bin lib lib64 proc
// 可以看到,这里ps是看到了所有的 进程
root@k8s-master:~# chroot test bash
bash-5.0# l ps
bash-5.0# ps
PID TTY TIME CMD
20877 ? 00:00:00 bash
20929 ? 00:00:00 ps
32545 ? 00:00:00 bash
bash: history: /root/.bash_history: cannot create: No such file or directory
bash-5.0# ps -ef
UID PID PPID C STIME TTY TIME CMD
0 1 0 0 Oct23 ? 00:07:35 /sbin/init nopti nospectre_v2 nospec_store_bypass_disable
0 2 0 0 Oct23 ? 00:00:00 [kthreadd]
0 3 2 0 Oct23 ? 00:00:00 [rcu_gp]
0 4 2 0 Oct23 ? 00:00:00 [rcu_par_gp]
0 6 2 0 Oct23 ? 00:00:00 [kworker/0:0H-kblockd]
0 8 2 0 Oct23 ? 00:00:00 [mm_percpu_wq]
0 9 2 0 Oct23 ? 00:03:21 [ksoftirqd/0]
0 10 2 0 Oct23 ? 00:25:33 [rcu_sched]
。。。。。
bash-5.0# cd proc
bash-5.0# ls
1 16 192 212 24 279 381 666 cmdline kmsg swaps
10 17 193 213 240 28 3856 669 consoles kpagecgroup sys
10696 170 194 214 241 281 3873 670 cpuinfo kpagecount sysrq-trigger
10738 171 195 215 242 28614 3928 671 crypto kpageflags sysvipc
11 172 196 216 243 29 3937 685 devices loadavg thread-self
11292 173 19646 21635 244 3 4 688 diskstats locks timer_list
11310 174 19654 217 245 30 455 692 dma meminfo tty
115 175 197 22 246 31 4556 693 driver misc uptime
116 176 198 224 247 32 4574 701 execdomains modules version
11681 177 2 225 248 32521 4621 714 fb mounts vmallocinfo
11700 178 20 226 249 32529 4629 718 filesystems mtrr vmstat
118 179 200 227 25 32530 492 732 fs net zoneinfo
119 180 206 228 250 32545 5271 8 interrupts pagetypeinfo
12 181 207 229 251 32560 54 8371 iomem partitions
122 187 208 230 26 33 5447 9 ioports sched_debug
14 188 20877 231 27 337 55 9586 irq schedstat
1485 189 209 232 27530 34 555 acpi kallsyms self
15 19 21 233 276 35 6 buddyinfo kcore slabinfo
1505 190 210 234 278 36 6134 bus key-users softirqs
15434 191 211 235 27808 3728 65 cgroups keys stat
```
### 2.3 如何实现容器内pid 隔离
##### 1. 在容器外面证明可以做到
```
root@k8s-master:~# unshare --fork --pid --mount-proc /bin/bash
root@k8s-master:~#
root@k8s-master:~# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 1 19:25 pts/0 00:00:00 /bin/bash
root 11 1 0 19:25 pts/0 00:00:00 ps -ef
root@k8s-master:~#
```
<br>
##### 2. 先取消之前的proc挂载
```
root@k8s-master:~/test# cd proc/
root@k8s-master:~/test/proc# ls
1 16 190 211 235 27530 34 555 acpi kallsyms self
10 16679 191 212 23982 276 35 6 buddyinfo kcore slabinfo
10696 16776 192 213 23983 278 36 65 bus keys softirqs
10738 17 193 214 24 27808 3728 666 cgroups key-users stat
11 170 194 215 240 279 381 669 cmdline kmsg swaps
11292 171 195 216 241 28 3856 670 consoles kpagecgroup sys
11310 172 196 21635 242 281 3873 671 cpuinfo kpagecount sysrq-trigger
115 173 19646 217 243 28614 3928 685 crypto kpageflags sysvipc
116 174 19654 22 244 29 3937 688 devices loadavg thread-self
11681 175 197 224 245 3 4 692 diskstats locks timer_list
11700 176 198 225 246 30 455 693 dma meminfo tty
118 177 2 226 24640 31 4556 701 driver misc uptime
119 178 20 227 247 32 4574 714 execdomains modules version
12 179 200 228 248 32521 4621 718 fb mounts vmallocinfo
122 180 206 229 249 32529 4629 732 filesystems mtrr vmstat
14 181 207 230 25 32530 492 8 fs net zoneinfo
1485 187 208 231 250 32545 5271 9 interrupts pagetypeinfo
15 188 209 232 251 32560 54 9362 iomem partitions
1505 189 21 233 26 33 5447 9586 ioports sched_debug
15434 19 210 234 27 337 55 9647 irq schedstat
root@k8s-master:~/test/proc#
root@k8s-master:~/test/proc#
root@k8s-master:~/test/proc# cd ..
root@k8s-master:~/test# ls
bin lib lib64 proc
root@k8s-master:~/test# umount /root/test/proc/
root@k8s-master:~/test#
root@k8s-master:~/test# ls
bin lib lib64 proc
root@k8s-master:~/test# ls proc/
```
<br>
```
// 先通过unshare 隔离出来pid,就是这个/bin/bash 就是新的shell进程
root@k8s-master:~# unshare --fork --pid --mount-proc /bin/bash
// 这个时候文件目录还是系统
root@k8s-master:~# ls
apiserver-to-kubelet-rbac.yaml c.txt kubernetes-server-linux-amd64.tar.gz test1
a.sh cup pod.yaml test.sh
a.txt kubectl pod.yaml-1 testYaml
b.txt kube-flannel.yml svc TLS
cni-plugins-linux-amd64-v0.8.6.tgz kubernetes test
root@k8s-master:~#
root@k8s-master:~# ls test/proc/
root@k8s-master:~#
root@k8s-master:~# mount -t proc proc /root/test/proc
// 修改root
root@k8s-master:~# chroot test
bash-5.0# l ps
PID TTY TIME CMD
1 ? 00:00:00 bash
21 ? 00:00:00 bash
23 ? 00:00:00 ps
bash: history: /root/.bash_history: cannot create: No such file or directory
// 进程已经改变了,只能看到自己的进程
bash-5.0# ps -ef
UID PID PPID C STIME TTY TIME CMD
0 1 0 0 11:36 ? 00:00:00 /bin/bash
0 21 1 0 11:38 ? 00:00:00 /bin/bash -i
0 24 21 0 11:38 ? 00:00:00 ps -ef
```
<br>
**如何查看默认的shell**
```
root# echo ${SHELL}
/bin/bash
```
<br>
### 3. 提取docker镜像中的rootfs文件
参考: https://www.cnblogs.com/sparkdev/p/8556075.html
通过 chroot 运行 busybox 为例
busybox 包含了丰富的工具,我们可以把这些工具放置在一个目录下,然后通过 chroot 构造出一个 mini 系统。简单起见我们直接使用 docker 的 busybox 镜像打包的文件系统。先在当前目录下创建一个目录 rootfs:
<br>
```
root# mkdir rootfs
// 提取busybox镜像的rootfs到当前目录
root# (docker export $(docker create busybox) | tar -C rootfs -xvf -)
.dockerenv
bin/
bin/[
bin/[[
bin/acpid
bin/add-shell
bin/addgroup
bin/adduser
bin/adjtimex
bin/ar
bin/arch
bin/arp
bin/arping
bin/ash
bin/awk
bin/base32
bin/base64
bin/basename
bin/bc
bin/beep
bin/blkdiscard
bin/blkid
bin/blockdev
bin/bootchartd
bin/brctl
bin/bunzip2
bin/busybox
bin/bzcat
bin/bzip2
bin/cal
bin/cat
bin/chat
bin/chattr
bin/chgrp
bin/chmod
bin/chown
bin/chpasswd
bin/chpst
bin/chroot
bin/chrt
bin/chvt
bin/cksum
bin/clear
bin/cmp
bin/comm
bin/conspy
bin/cp
bin/cpio
bin/crond
bin/crontab
bin/cryptpw
bin/cttyhack
bin/cut
bin/date
bin/dc
bin/dd
bin/deallocvt
bin/delgroup
bin/deluser
bin/depmod
bin/devmem
bin/df
bin/dhcprelay
bin/diff
bin/dirname
bin/dmesg
bin/dnsd
bin/dnsdomainname
bin/dos2unix
bin/dpkg
bin/dpkg-deb
bin/du
bin/dumpkmap
bin/dumpleases
bin/echo
bin/ed
bin/egrep
bin/eject
bin/env
bin/envdir
bin/envuidgid
bin/ether-wake
bin/expand
bin/expr
bin/factor
bin/fakeidentd
bin/fallocate
bin/false
bin/fatattr
bin/fbset
bin/fbsplash
bin/fdflush
bin/fdformat
bin/fdisk
bin/fgconsole
bin/fgrep
bin/find
bin/findfs
bin/flock
bin/fold
bin/free
bin/freeramdisk
bin/fsck
bin/fsck.minix
bin/fsfreeze
bin/fstrim
bin/fsync
bin/ftpd
bin/ftpget
bin/ftpput
bin/fuser
bin/getconf
bin/getopt
bin/getty
bin/grep
bin/groups
bin/gunzip
bin/gzip
bin/halt
bin/hd
bin/hdparm
bin/head
bin/hexdump
bin/hexedit
bin/hostid
bin/hostname
bin/httpd
bin/hush
bin/hwclock
bin/i2cdetect
bin/i2cdump
bin/i2cget
bin/i2cset
bin/i2ctransfer
bin/id
bin/ifconfig
bin/ifdown
bin/ifenslave
bin/ifplugd
bin/ifup
bin/inetd
bin/init
bin/insmod
bin/install
bin/ionice
bin/iostat
bin/ip
bin/ipaddr
bin/ipcalc
bin/ipcrm
bin/ipcs
bin/iplink
bin/ipneigh
bin/iproute
bin/iprule
bin/iptunnel
bin/kbd_mode
bin/kill
bin/killall
bin/killall5
bin/klogd
bin/last
bin/less
bin/link
bin/linux32
bin/linux64
bin/linuxrc
bin/ln
bin/loadfont
bin/loadkmap
bin/logger
bin/login
bin/logname
bin/logread
bin/losetup
bin/lpd
bin/lpq
bin/lpr
bin/ls
bin/lsattr
bin/lsmod
bin/lsof
bin/lspci
bin/lsscsi
bin/lsusb
bin/lzcat
bin/lzma
bin/lzop
bin/makedevs
bin/makemime
bin/man
bin/md5sum
bin/mdev
bin/mesg
bin/microcom
bin/mim
bin/mkdir
bin/mkdosfs
bin/mke2fs
bin/mkfifo
bin/mkfs.ext2
bin/mkfs.minix
bin/mkfs.vfat
bin/mknod
bin/mkpasswd
bin/mkswap
bin/mktemp
bin/modinfo
bin/modprobe
bin/more
bin/mount
bin/mountpoint
bin/mpstat
bin/mt
bin/mv
bin/nameif
bin/nanddump
bin/nandwrite
bin/nbd-client
bin/nc
bin/netstat
bin/nice
bin/nl
bin/nmeter
bin/nohup
bin/nologin
bin/nproc
bin/nsenter
bin/nslookup
bin/ntpd
bin/nuke
bin/od
bin/openvt
bin/partprobe
bin/passwd
bin/paste
bin/patch
bin/pgrep
bin/pidof
bin/ping
bin/ping6
bin/pipe_progress
bin/pivot_root
bin/pkill
bin/pmap
bin/popmaildir
bin/poweroff
bin/powertop
bin/printenv
bin/printf
bin/ps
bin/pscan
bin/pstree
bin/pwd
bin/pwdx
bin/raidautorun
bin/rdate
bin/rdev
bin/readahead
bin/readlink
bin/readprofile
bin/realpath
bin/reboot
bin/reformime
bin/remove-shell
bin/renice
bin/reset
bin/resize
bin/resume
bin/rev
bin/rm
bin/rmdir
bin/rmmod
bin/route
bin/rpm
bin/rpm2cpio
bin/rtcwake
bin/run-init
bin/run-parts
bin/runlevel
bin/runsv
bin/runsvdir
bin/rx
bin/script
bin/scriptreplay
bin/sed
bin/sendmail
bin/seq
bin/setarch
bin/setconsole
bin/setfattr
bin/setfont
bin/setkeycodes
bin/setlogcons
bin/setpriv
bin/setserial
bin/setsid
bin/setuidgid
bin/sh
bin/sha1sum
bin/sha256sum
bin/sha3sum
bin/sha512sum
bin/showkey
bin/shred
bin/shuf
bin/slattach
bin/sleep
bin/smemcap
bin/softlimit
bin/sort
bin/split
bin/ssl_client
bin/start-stop-daemon
bin/stat
bin/strings
bin/stty
bin/su
bin/sulogin
bin/sum
bin/sv
bin/svc
bin/svlogd
bin/svok
bin/swapoff
bin/swapon
bin/switch_root
bin/sync
bin/sysctl
bin/syslogd
bin/tac
bin/tail
bin/tar
bin/taskset
bin/tc
bin/tcpsvd
bin/tee
bin/telnet
bin/telnetd
bin/test
bin/tftp
bin/tftpd
bin/time
bin/timeout
bin/top
bin/touch
bin/tr
bin/traceroute
bin/traceroute6
bin/true
bin/truncate
bin/ts
bin/tty
bin/ttysize
bin/tunctl
bin/ubiattach
bin/ubidetach
bin/ubimkvol
bin/ubirename
bin/ubirmvol
bin/ubirsvol
bin/ubiupdatevol
bin/udhcpc
bin/udhcpc6
bin/udhcpd
bin/udpsvd
bin/uevent
bin/umount
bin/uname
bin/unexpand
bin/uniq
bin/unix2dos
bin/unlink
bin/unlzma
bin/unshare
bin/unxz
bin/unzip
bin/uptime
bin/users
bin/usleep
bin/uudecode
bin/uuencode
bin/vconfig
bin/vi
bin/vlock
bin/volname
bin/w
bin/wall
bin/watch
bin/watchdog
bin/wc
bin/wget
bin/which
bin/who
bin/whoami
bin/whois
bin/xargs
bin/xxd
bin/xz
bin/xzcat
bin/yes
bin/zcat
bin/zcip
dev/
dev/console
dev/pts/
dev/shm/
etc/
etc/group
etc/hostname
etc/hosts
etc/localtime
etc/mtab
etc/network/
etc/network/if-down.d/
etc/network/if-post-down.d/
etc/network/if-pre-up.d/
etc/network/if-up.d/
etc/passwd
etc/resolv.conf
etc/shadow
home/
proc/
root/
sys/
tmp/
usr/
usr/sbin/
var/
var/spool/
var/spool/mail/
var/www/
root# ls rootfs
bin dev etc home proc root sys tmp usr var
// proc是空的
root/rootfs# cd proc/
root /rootfs/proc# ls
root /rootfs/proc#
没有任何进程()
root # chroot rootfs /bin/ps
PID USER TIME COMMAND
root # chroot rootfs /bin/sh
/ # ps -ef
PID USER TIME COMMAND
/ #
/ # ps ajxf
PID USER TIME COMMAND
/ #
/ #
```
### 4. 参考文档
[chroot介绍和使用](https://wangchujiang.com/linux-command/c/chroot.html)
[浅析Linux中的.a、.so、和.o文件](https://oldpan.me/archives/linux-a-so-o-tell)
用linux命令实现容器: https://juejin.cn/post/6951639064843911175
unshare详解: unshare 就是使用与父进程不共享的命名空间运行 子进程
https://juejin.cn/post/6987564689606180900
================================================
FILE: docker/4. 如何用golang 实现一个 busybox的容器.md
================================================
* [1\. 背景](#1-背景)
* [2\. 如何运行](#2-如何运行)
* [3\. 参考](#3-参考)
### 1. 背景
在入手docker源码之前,这里先用一个例子先理解一下,上面提到的Linux原理。
主要参考这个repo:https://github.com/jiajunhuang/cup/blob/master/README.md
原repo中需要准备工作为:
(1)创建rootfs,并且自己下载 busybox 二进制文件
但是我按照要求,下载好这个二进制文件,放入rootfs/bin 目录后一直报错:
```
root /data/golang/src/cup/cup# ./cup \
>
2021/12/05 15:21:44 main start...
2021/12/05 15:21:44 path is :
2021/12/05 15:21:44 childProcess start...uid: 0, gid: 0
2021/12/05 15:21:44 child: hostname: kmaster
2021/12/05 15:21:44 child: hostname: cup-host
2021/12/05 15:21:44 failed to run command: fork/exec /bin/busybox: no such file or directory
panic: failed to run command: fork/exec /bin/busybox: no such file or directory
```
因此为了更好的应用,和理解原理,这里做了一些修改。主要是修改了rootfs。rootfs的内容直接从busybox提取出来。
```
root@zoux:/home/zoux/data/golang/src/cup/cup# (docker export $(docker create busybox) | tar -C rootfs -xvf -)
.dockerenv
bin/
bin/[
...
```
最终的目录结构:
```
root /data/golang/src/cup/cup# tree -L 1
.
├── cup
├── LICENSE
├── main.go
├── Makefile
├── README.md
└── rootfs
1 directory, 5 files
```
<br>
### 2. 如何运行
(1) make 生成二进制文件 cup
(2) ./cup 即可
```
root /data/golang/src/cup/cup# ./cup
2021/12/05 18:28:16 main start...
2021/12/05 18:28:16 childProcess start...uid: 0, gid: 0
2021/12/05 18:28:16 child: hostname: kmaster
2021/12/05 18:28:16 child: hostname: cup-host
/ # ps ajxf
PID USER TIME COMMAND
1 root 0:00 {exe} childProcess
6 root 0:00 /bin/busybox sh
7 root 0:00 ps ajxf
/ # ls
bin dev etc home proc root sys tmp usr var
```
### 3. 参考
[Linux Namespace 技术与 Docker 原理浅析](https://www.cnblogs.com/dream397/p/13999018.html)
================================================
FILE: docker/5. docker-overlay技术.md
================================================
* [0 背景](#0-背景)
* [1 overlay介绍](#1-overlay介绍)
* [2\. 实验\-通过实验来理解](#2-实验-通过实验来理解)
* [2\.1 实验设置](#21-实验设置)
* [2\.2 补充实验](#22-补充实验)
* [2\.2 结论](#22-结论)
* [2\.2\.1 workdir作用是什么](#221-workdir作用是什么)
* [2\.2\.2 文件覆盖规则](#222-文件覆盖规则)
* [3 源码分析\-通过原理来理解](#3-源码分析-通过原理来理解)
* [4 总结](#4-总结)
### 0 背景
cgroup, namespaces, chroot都是Linux 已有功能。这些计算是可以做到了隔离。但是docker在这些基层上来,加上了联合文件系统,这个是docker image的基础,使得镜像可以分层继承。overlay是docker联合文件系统的一种。本节就是对overlay的基础知识进行整理总结。
### 1 overlay介绍

`OverlayFS` 文件系统主要有三个角色,`lowerdir`、`upperdir` 和 `merged`。`lowerdir` 是只读层,用户不能修改这个层的文件;`upperdir` 是可读写层,用户能够修改这个层的文件;而 `merged` 是合并层,把 `lowerdir` 层和 `upperdir` 层的文件合并展示。
<br>
使用 `OverlayFS` 前需要进行挂载操作,挂载 `OverlayFS` 文件系统的基本命令如下:
```
$ mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged
```
参数 `-t` 表示挂载的文件系统类型,这里设置为 `overlay` 表示文件系统类型为 `OverlayFS`,而参数 `-o` 指定的是 `lowerdir`、`upperdir` 和 `workdir`,最后的 `merged` 目录就是最终的挂载点目录。下面说明一下 `-o` 参数几个目录的作用:
1. `lowerdir`:指定用户需要挂载的lower层目录,指定多个目录可以使用 `:` 来分隔(最大支持500层)。
2. `upperdir`:指定用户需要挂载的upper层目录。
3. `workdir`:指定文件系统的工作基础目录,挂载后内容会被清空,且在使用过程中其内容用户不可见。
### 2. 实验-通过实验来理解
#### 2.1 实验设置
```
root@k8s-master:~/testOverlay# mkdir -p fileRoot A B C worker
root@k8s-master:~/testOverlay# echo "from A" > A/a.txt
root@k8s-master:~/testOverlay# echo "from B" > B/b.txt
root@k8s-master:~/testOverlay# echo "from C" > C/c.txt
root@k8s-master:~/testOverlay# mkdir -p A/aa
root@k8s-master:~/testOverlay# tree
.
├── A
│ ├── aa
│ └── a.txt
├── B
│ └── b.txt
├── C
│ └── c.txt
├── fileRoot
└── worker
```
<br>
指定 A,B是 底层文件; C是 上层文件。 worker为工作目录。 入口函数为 fileRoot
```
mount -t overlay overlay -o lowerdir=A:B,upperdir=C,workdir=worker fileRoot
```
查看fileRoot结果:
```
root@k8s-master:~/testOverlay# mount -t overlay overlay -o lowerdir=A:B,upperdir=C,workdir=worker fileRoot
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# ls fileRoot/
aa a.txt b.txt c.txt
// 1.对worker目录进行实验。 结果:worker目录可以写入,但是不会影响fileRoot文件
root@k8s-master:~/testOverlay# echo "from worker" > worker/work.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# ls fileRoot/
aa a.txt b.txt c.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# ls worker/
work work.txt
// 2.文件覆盖规则实验; lowerdir可以手动修改
root@k8s-master:~/testOverlay# echo "from A1" > A/a.txt
root@k8s-master:~/testOverlay# cat fileRoot/a.txt
from A1
root@k8s-master:~/testOverlay# ls worker/
work work.txt
root@k8s-master:~/testOverlay# ls worker/work
// 3. 覆盖顺序测试: upperdir优先级最高,lowerdir按照mount时从左到右的顺序,权重依次降低,左边的覆盖右边的同名文件或者文件夹。
root@k8s-master:~/testOverlay# echo "from B" > B/a.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# ls fileRoot/
aa a.txt b.txt c.txt
root@k8s-master:~/testOverlay# cat fileRoot/a.txt
from A1
root@k8s-master:~/testOverlay# cat A/a.txt
from A1
root@k8s-master:~/testOverlay# cat B/a.txt
from B
oot@k8s-master:~/testOverlay# echo "from A" > A/b.txt
root@k8s-master:~/testOverlay# cat A/b.txt
from A
root@k8s-master:~/testOverlay# cat fileRoot/b.txt
from A
root@k8s-master:~/testOverlay# cat B/b.txt
from B
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# cat C/c.txt
from C
root@k8s-master:~/testOverlay# cat fileRoot/c.txt
from C
root@k8s-master:~/testOverlay# echo "from A" > A/c.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# cat A/c.txt
from A
root@k8s-master:~/testOverlay# cat C/c.txt
from C
root@k8s-master:~/testOverlay# cat fileRoot/c.txt
from C
// 目录中的文件也是一样,存在同名的时,以左边的A为准
root@k8s-master:~/testOverlay# mkdir B/aa
root@k8s-master:~/testOverlay# echo "from bb" > B/aa/a.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# cat fileRoot/aa/a.txt
from aa
root@k8s-master:~/testOverlay# echo "from bb" > B/aa/b.txt
// 为什么aa目录下没有b.txt
root@k8s-master:~/testOverlay# ls fileRoot/aa
a.txt
root@k8s-master:~/testOverlay# ls fileRoot/aa
a.txt
root@k8s-master:~/testOverlay# ls B/aa/b.txt
B/aa/b.txt
root@k8s-master:~/testOverlay# ls A/aa/b.txt
ls: cannot access 'A/aa/b.txt': No such file or directory
root@k8s-master:~/testOverlay# ls fileRoot/aa
a.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# ls fileRoot/
aa a.txt b.txt c.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# cd fileRoot/
root@k8s-master:~/testOverlay/fileRoot# ls
aa a.txt b.txt c.txt
root@k8s-master:~/testOverlay/fileRoot# cd aa/
root@k8s-master:~/testOverlay/fileRoot/aa# ls
a.txt
// 破案了,因为 A//aa 目录的优先级 比 B/aa高,所以fileRoot/aa = A/aa
root@k8s-master:~/testOverlay# echo "from aa" > A/aa/e.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# ls fileRoot/a
aa/ a.txt
root@k8s-master:~/testOverlay# ls fileRoot/a
aa/ a.txt
root@k8s-master:~/testOverlay# ls fileRoot/aa/
a.txt e.txt
root@k8s-master:~/testOverlay#
root@k8s-master:~/testOverlay# echo "from bb" > B/aa/f.txt
root@k8s-master:~/testOverlay# ls fileRoot/aa/
a.txt e.txt
root@k8s-master:~/testOverlay#
// 这个就有,所以
root@k8s-master:~/testOverlay# echo "from bb" > B/f.txt
root@k8s-master:~/testOverlay# ls fileRoot/
aa a.txt b.txt c.txt f.txt
root@k8s-master:~/testOverlay# cat fileRoot/f.txt
from bb
// 为啥这个f.txt 不是from aa ???, 看起来又不是A为主??
root@k8s-master:~/testOverlay# echo "from bb" > B/f.txt
root@k8s-master:~/testOverlay# ls fileRoot/
aa a.txt b.txt c.txt f.txt
root@k8s-master:~/testOverlay# cat fileRoot/f.txt
from bb
root@k8s-master:~/testOverlay# echo "from aa" > A/f.txt
root@k8s-master:~/testOverlay# cat fileRoot/f.txt
from bb
root@k8s-master:~/testOverlay# cat fileRoot/f.txt
from bb
在merged文件夹所做的所有修改,最终都会存储到upperdir目录中
root@k8s-master:~/testOverlay# echo "from fileRoot" > fileRoot/fr.txt
root@k8s-master:~/testOverlay# ls C
c.txt fr.txt
```
<br>
#### 2.2 补充实验
```
root@k8s-master:~/testOver# mkdir -p fileRoot A/aa B/aa C worker
root@k8s-master:~/testOver# echo "from A" > A/a.txt
root@k8s-master:~/testOver# echo "from A" > A/aa/a.txt
root@k8s-master:~/testOver# echo "from B" > B/aa/a.txt
root@k8s-master:~/testOver# echo "from B" > B/aa/b.txt
root@k8s-master:~/testOver# echo "from B" > B/a.txt
root@k8s-master:~/testOver# echo "from B" > B/b.txt
root@k8s-master:~/testOver# echo "from C" > C/c.txt
root@k8s-master:~/testOver# mount -t overlay overlay -o lowerdir=A:B,upperdir=C,workdir=worker fileRoot
root@k8s-master:~/testOver# ls fileRoot/
aa a.txt b.txt c.txt
root@k8s-master:~/testOver# ls fileRoot/a.txt
fileRoot/a.txt
root@k8s-master:~/testOver# cat fileRoot/a.txt
from A
root@k8s-master:~/testOver# ls fileRoot/aa/
a.txt b.txt
root@k8s-master:~/testOver# cat fileRoot/aa/a.txt
from A
```
#### 2.2 结论
##### 2.2.1 workdir作用是什么
通过实验:wokrdir目录平时都是空的,但是可以手动写入文件,写入文件后不影响overlay文件(fileRoot);
通过实验没看出来,查询资料,解析是:
workdir选项是必需的,用于在原子操作中将文件切换到覆盖目标之前准备文件(workdir必须与upperdir在同一文件系统上)。
资料来源:[http](http://windsock.io/the-overlay-filesystem/) : [//windsock.io/the-overlay-filesystem/](http://windsock.io/the-overlay-filesystem/)
我可能会猜测“覆盖目标”的意思`upperdir`。
所以...某些文件(也许是“ whiteout”文件?)是非原子创建和配置的`workdir`,然后原子移动到的`upperdir`。
链接:https://qastack.cn/unix/324515/linux-filesystem-overlay-what-is-workdir-used-for-overlayfs
<br>
##### 2.2.2 文件覆盖规则
(1)lowerdir的值可以是一些的文件夹列表,文件都可以读写
(2)merged文件夹是最终联合起来的文件系统,我们可以在merged文件夹中访问所有lowerdir和upperdir中的内容
(3)文件的覆盖顺序,upperdir目录拥有最高覆盖权限,lowerdir按照mount时从左到右的顺序,权重依次降低,左边的覆盖右边的同名文件或者文件夹。
(4)在merged文件夹所做的所有修改,最终都会存储到upperdir目录中
(5)workdir指定的目录需要和upperdir位于同一目录中
(6)mount的时候,lowerdir相同文件会被最左边的覆盖,不同的文件和合并到相同目录 (补充实验)
### 3 源码分析-通过原理来理解
目前暂时先不设计这一块代码,了解大概的使用即可。如果需要,后面参考这两个链接再仔细研究。
https://mp.weixin.qq.com/s/pgu0uXvokgBTXUNk1LpB6Q
https://docs.docker.com/storage/storagedriver/overlayfs-driver/
### 4 总结
从实验结果来看,docker image里面的应该是最新的在最左边。
================================================
FILE: docker/6. docker pull原理分析.md
================================================
* [0\. 章节目标](#0-章节目标)
* [1\. docker pull busybox 引入](#1-docker-pull-busybox-引入)
* [1\.1 引入的问题](#11-引入的问题)
* [2\. docker pull 原理](#2-docker-pull-原理)
* [2\.1 查看docker 信息](#21-查看docker-信息)
* [2\.2 Root Dir](#22-root-dir)
* [2\.3 image目录](#23-image目录)
* [2\.4 如何获取dockerhub镜像的manifest](#24-如何获取dockerhub镜像的manifest)
* [3\. docker pull后的文件是如何存储的](#3-docker-pull后的文件是如何存储的)
* [3\.1 查看image元数据信息\-imageConfig](#31-查看image元数据信息-imageconfig)
* [3\.2 sha256sum 作用](#32-sha256sum-作用)
* [3\.3 diff\_ids vs docker pull的layer\-id](#33-diff_ids-vs-docker-pull的layer-id)
* [3\.4 如何查看每一层的layer在哪](#34-如何查看每一层的layer在哪)
* [4\. 结论](#4-结论)
* [5 参考](#5-参考)
### 0. 章节目标
从体验和原理入手, 弄清楚doker pull 镜像的过程; 弄清楚docker 镜像是如何存储的, 为后面docker pull 源码做准备。
### 1. docker pull busybox 引入
```
root@k8s-master:~# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox
3cb635b06aa2: Pull complete //该镜像只有一层
Digest: sha256:b5cfd4befc119a590ca1a81d6bb0fa1fb19f1fbebd0397f25fae164abe1e8a6a
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
// 镜像id 是 ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af
root@k8s-master:/var/lib/docker/overlay2# docker images --no-trunc
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest sha256:ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af 4 days ago 1.24MB
root@k8s-master:~# docker pull busybox:latest
latest: Pulling from library/busybox
Digest: sha256:b5cfd4befc119a590ca1a81d6bb0fa1fb19f1fbebd0397f25fae164abe1e8a6a
Status: Image is up to date for busybox:latest
docker.io/library/busybox:latest
root@k8s-master:~# docker rmi busybox:latest
Untagged: busybox:latest
Untagged: busybox@sha256:b5cfd4befc119a590ca1a81d6bb0fa1fb19f1fbebd0397f25fae164abe1e8a6a
Deleted: sha256:ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af
Deleted: sha256:64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed
root@k8s-master:~# docker pull zoux/pause-amd64:3.0
3.0: Pulling from zoux/pause-amd64
4f4fb700ef54: Pull complete
ce150f7a21ec: Pull complete
Digest: sha256:f04288efc7e65a84be74d4fc63e235ac3c6c603cf832e442e0bd3f240b10a91b
Status: Downloaded newer image for zoux/pause-amd64:3.0
```
#### 1.1 引入的问题
Q: Digest 是什么 ?
A:镜像在服务器端的 sha256sum ID。
Q: rmi 的时候为什么还要delete: 64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed ?
A: 64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed 是bosybox 的rootfs_id
<br>
### 2. docker pull 原理

关键信息:
(1)manifest 有什么信息
(2)image config是什么
(3)diff_ids是什么
#### 2.1 查看docker 信息
```
root@k8s-master:~# docker info
Client:
Debug Mode: false
Server:
Containers: 11
Running: 4
Paused: 0
Stopped: 7
Images: 7
Server Version: 19.03.9
Storage Driver: overlay2 // 使用的是 overlay2文件系统
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
init version: fec3683
Security Options:
apparmor
seccomp
Profile: default
Kernel Version: 4.19.0-17-amd64
Operating System: Debian GNU/Linux 10 (buster)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 3.854GiB
Name: k8s-master
ID: DN3J:XOLZ:VIGR:W4E2:LK47:PCEH:43KP:LFCW:XPRG:NPEZ:4DRR:TPTE
Docker Root Dir: /var/lib/docker // docker 关键文件
Debug Mode: false
Registry: https://index.docker.io/v1/
Labels:
Experimental: true
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
https://b9pmyelo.mirror.aliyuncs.com/
Live Restore Enabled: false
Product License: Community Engine
WARNING: No swap limit support
```
#### 2.2 Root Dir
这里一个非常关键的就是: Docker Root Dir: /var/lib/docker
```
root@k8s-master:~# ls -l /var/lib/docker
total 60
drwx------ 2 root root 4096 Oct 23 16:13 builder
drwx--x--x 4 root root 4096 Oct 23 16:13 buildkit
drwx------ 3 root root 4096 Oct 23 16:13 containerd
drwx------ 13 root root 4096 Dec 12 16:51 containers
drwx------ 3 root root 4096 Oct 23 16:13 image
drwxr-x--- 3 root root 4096 Oct 23 16:13 network
drwx------ 55 root root 12288 Dec 12 16:51 overlay2
drwx------ 4 root root 4096 Oct 23 16:13 plugins
drwx------ 2 root root 4096 Dec 12 16:50 runtimes
drwx------ 2 root root 4096 Oct 23 16:13 swarm
drwx------ 2 root root 4096 Dec 12 16:50 tmp
drwx------ 2 root root 4096 Oct 23 16:13 trust
drwx------ 2 root root 4096 Oct 23 16:13 volume
```
和镜像存储有关的信息如下:
- overlay2: 镜像和容器的层信息
- image:存储镜像元相关信息
#### 2.3 image目录
```
root@k8s-master:~# tree -L 1 /var/lib/docker/image/overlay2/
/var/lib/docker/image/overlay2/
├── distribution
├── imagedb
├── layerdb
└── repositories.json
3 directories, 1 file
```
repositories.json就是存储镜像信息,主要是name和image id的对应,digest和image id的对应。当pull镜像的时候会更新这个文件。
```
root@k8s-master:/var/lib/docker# cat image/overlay2/repositories.json
{
"Repositories": {
"busybox": {
"busybox:latest": "sha256:ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af",
"busybox@sha256:b5cfd4befc119a590ca1a81d6bb0fa1fb19f1fbebd0397f25fae164abe1e8a6a": "sha256:ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af"
},
"zoux/pause-amd64": {
"zoux/pause-amd64:3.0": "sha256:99e59f495ffaa222bfeb67580213e8c28c1e885f1d245ab2bbe3b1b1ec3bd0b2",
"zoux/pause-amd64@sha256:f04288efc7e65a84be74d4fc63e235ac3c6c603cf832e442e0bd3f240b10a91b": "sha256:99e59f495ffaa222bfeb67580213e8c28c1e885f1d245ab2bbe3b1b1ec3bd0b2"
},
"nginx": {
"nginx:latest": "sha256:f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e",
"nginx@sha256:097c3a0913d7e3a5b01b6c685a60c03632fc7a2b50bc8e35bcaa3691d788226e": "sha256:ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291",
"nginx@sha256:644a70516a26004c97d0d85c7fe1d0c3a67ea8ab7ddf4aff193d9f301670cf36": "sha256:87a94228f133e2da99cb16d653cd1373c5b4e8689956386c1c12b60a20421a02",
"nginx@sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603": "sha256:f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e"
},
"quay.io/coreos/flannel": {
"quay.io/coreos/flannel:v0.15.0": "sha256:09b38f011a29c697679aa10918b7514e22136b50ceb6cf59d13151453fe8b7a0",
"quay.io/coreos/flannel@sha256:bf24fa829f753d20b4e36c64cf9603120c6ffec9652834953551b3ea455c4630": "sha256:09b38f011a29c697679aa10918b7514e22136b50ceb6cf59d13151453fe8b7a0"
},
"rancher/mirrored-flannelcni-flannel-cni-plugin": {
"rancher/mirrored-flannelcni-flannel-cni-plugin:v1.2": "sha256:98660e6e4c3ae49bf49cd640309f79626c302e1d8292e1971dcc2e6a6b7b8c4d",
"rancher/mirrored-flannelcni-flannel-cni-plugin@sha256:b69fb2dddf176edeb7617b176543f3f33d71482d5d425217f360eca5390911dc": "sha256:98660e6e4c3ae49bf49cd640309f79626c302e1d8292e1971dcc2e6a6b7b8c4d"
}
}
}
```
<br>
```
root@k8s-master:~# docker images --digests
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
busybox latest sha256:b5cfd4befc119a590ca1a81d6bb0fa1fb19f1fbebd0397f25fae164abe1e8a6a ffe9d497c324 4 days ago 1.24MB
```
**查看docker image信息**
```
root@k8s-master:~# export DOCKER_CLI_EXPERIMENTAL=enabled //需要开启docker cli
root@k8s-master:~# docker manifest inspect busybox:latest
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 527,
"digest": "sha256:50e44504ea4f19f141118a8a8868e6c5bb9856efa33f2183f5ccea7ac62aacc9", //这个为啥不一样
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{ // 其他平台。。
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 527,
"digest": "sha256:0252da5f2df7425dcf48afb4bc337966dfeb2d87079ea3f7fe25051d5b9e9c26",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v5"
}
},
]
}
```
**解答疑问:**
从这里就可以看出来,repositories.json存储了 镜像id和 digestsid的对应关系。
digestsid 就是存储在服务器远端的 所有镜像文件的 sha256值。
当第二次docker pull的时候,发现 busybox:latest 对应的 digestsid=b5cfd4befc119a590ca1a81d6bb0fa1fb19f1fbebd0397f25fae164abe1e8a6a。
一查看repositories.json,发现本地有这个镜像,所以不会再下载了。
<br>
digest是manifest的sha256:,因为manifest在本地没有,我们可以通过registry的结果去获取。
#### 2.4 如何获取dockerhub镜像的manifest
https://stackoverflow.com/questions/55269256/how-to-get-manifests-using-http-api-v2
https://zhuanlan.zhihu.com/p/95900321
这个看起来可以的
https://gist.github.com/tnozicka/f46b37f57f7ac755fefa6a0f0c8a77bf
```
repo=openshift/origin && curl -H "Authorization: Bearer $(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" | jq --raw-output .token)" "https://registry.hub.docker.com/v2/${repo}/manifests/latest"
root@k8s-master:~# repo=zoux/pause-amd64 && curl -H "Authorization: Bearer $(curl -sSL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" | jq --raw-output .token)" "https://registry.hub.docker.com/v2/${repo}/manifests/3.0"
{
"schemaVersion": 1,
"name": "zoux/pause-amd64",
"tag": "3.0",
"architecture": "amd64",
"fsLayers": [
{
"blobSum": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
},
{
"blobSum": "sha256:ce150f7a21ecb3a4150d71685079f2727057c1785323933f9fdd0750874e13e5"
},
{
"blobSum": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1"
}
],
"history": [
{
"v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"95722352e41d\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"f8e2eec424cf985b4e41d6423991433fb7a93c90f9acc73a5e7bee213b789c52\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":[\"/pause\"],\"OnBuild\":null,\"Labels\":{}},\"container\":\"a9873535145fe72b464d3055efbac36aab70d059914e221cbbd7fe3cac53ef6b\",\"container_config\":{\"Hostname\":\"95722352e41d\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) ENTRYPOINT \\u0026{[\\\"/pause\\\"]}\"],\"Image\":\"f8e2eec424cf985b4e41d6423991433fb7a93c90f9acc73a5e7bee213b789c52\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":[\"/pause\"],\"OnBuild\":null,\"Labels\":{}},\"created\":\"2016-05-04T06:26:41.522308365Z\",\"docker_version\":\"1.9.1\",\"id\":\"3d2e5b3ef4b070401482a8161420136e75da9354ccfc7cece40b2b5ba8d0f1be\",\"os\":\"linux\",\"parent\":\"58ca451648f521bb9749d929fab33c76c1aec4ac54990f4d33fb86705682ec32\"}"
},
{
"v1Compatibility": "{\"id\":\"58ca451648f521bb9749d929fab33c76c1aec4ac54990f4d33fb86705682ec32\",\"parent\":\"00fa447be331f70e08ea0dfff0174e514aac7f0f089a6c4d3a8f58d855a10b3e\",\"created\":\"2016-05-04T06:26:41.091672218Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:b7eb6a5df9d5fbe509cac16ed89f8d6513a4362017184b14c6a5fae151eee5c5 in /pause\"]}}"
},
{
"v1Compatibility": "{\"id\":\"00fa447be331f70e08ea0dfff0174e514aac7f0f089a6c4d3a8f58d855a10b3e\",\"created\":\"2016-05-04T06:26:40.628395649Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ARG ARCH\"]}}"
}
],
"signatures": [
{
"header": {
"jwk": {
"crv": "P-256",
"kid": "W2RG:USLL:S22T:VLMH:PO66:FQVK:M5BQ:WYME:FDIC:TNX4:J4TE:LKIW",
"kty": "EC",
"x": "abyPWJMVZM6xBosAkf1sUh4D30sa-4XEjXNTuIv72_s",
"y": "9miJIR5j2yXpcTaxqrFW491OEKc0npyWDYAa5KLxDNw"
},
"alg": "ES256"
},
"signature": "WZVTu9_Q2jFeNViqxIXUf_bLlLTjhH5tAjdcdCB0ohC1hgyxLIrt1hAeG2ZZkxg0wBuEaWm8ip6C1yt6Vad9SQ",
"protected": "eyJmb3JtYXRMZW5ndGgiOjIzOTEsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAyMS0xMi0yNVQwMjozNjowN1oifQ"
}
]
}
```
### 3. docker pull后的文件是如何存储的
#### 3.1 查看image元数据信息-imageConfig
镜像元数据存储在了/var/lib/docker/image/<storage_driver>/imagedb/content/sha256/目录下,名称是以镜像ID命名的文件,镜像ID可通过docker images查看,这些文件以json的形式保存了该镜像的rootfs信息、镜像创建时间、构建历史信息、所用容器、包括启动的Entrypoint和CMD等等。
这里以bosybox镜像为例: 从docker pull的输出可以看出来,busybox只有一层, 3cb635b06aa2
```
// docker pull busybox之前
root@k8s-master:/var/lib/docker/image/overlay2/imagedb/content/sha256# ls
09b38f011a29c697679aa10918b7514e22136b50ceb6cf59d13151453fe8b7a0
87a94228f133e2da99cb16d653cd1373c5b4e8689956386c1c12b60a20421a02
98660e6e4c3ae49bf49cd640309f79626c302e1d8292e1971dcc2e6a6b7b8c4d
99e59f495ffaa222bfeb67580213e8c28c1e885f1d245ab2bbe3b1b1ec3bd0b2
ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291
f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e
// docker pull的时候,只有这个pull
3cb635b06aa2: Pull complete
// 下载镜像之后
root@k8s-master:/var/lib/docker/image/overlay2/imagedb/content/sha256# ls
09b38f011a29c697679aa10918b7514e22136b50ceb6cf59d13151453fe8b7a0
87a94228f133e2da99cb16d653cd1373c5b4e8689956386c1c12b60a20421a02
98660e6e4c3ae49bf49cd640309f79626c302e1d8292e1971dcc2e6a6b7b8c4d
99e59f495ffaa222bfeb67580213e8c28c1e885f1d245ab2bbe3b1b1ec3bd0b2
ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291
f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e
ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af // 多了这一层, 每个文件名就是一个imageid
// 文件内容是镜像的详细信息
root@k8s-master:/var/lib/docker/image/overlay2/imagedb/content/sha256# cat ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af
{
"architecture": "amd64",
"config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"sh"
],
"Image": "sha256:47595422ea26649bce6768903b3f14aa220694e0811e1bdb5e5bd6fd3df852b2",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"container": "0234093c99ba42a97028378063ca32364ca85f74b6804ae65da0f874c16cff69",
"container_config": {
"Hostname": "0234093c99ba",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"sh\"]"
],
"Image": "sha256:47595422ea26649bce6768903b3f14aa220694e0811e1bdb5e5bd6fd3df852b2",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": { }
},
"created": "2021-12-08T00:22:34.424256906Z",
"docker_version": "20.10.7",
"history": [
{
"created": "2021-12-08T00:22:34.228923742Z",
"created_by": "/bin/sh -c #(nop) ADD file:e2d2d9591696b14787114bccd6c84033d8e8433ce416045672e2870b983b6029 in / "
},
{
"created": "2021-12-08T00:22:34.424256906Z",
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed"
]
}
}
```
<br>
#### 3.2 sha256sum 作用
sha256sum:计算文件的哈希值
```
root@k8s-master:~# sha256sum a.sh
96a9988dd952b0910d4d808187b52a623fda2a45b86337b61a76589618f901bf a.sh
没看错,镜像id就是 该image-config文件的hash值
root@k8s-master:/var/lib/docker/image/overlay2/imagedb/content/sha256# sha256sum ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af
ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af // 该文件的hash值 ffe9d497c32414b1c5cdad8178a85602ee72453082da2463f1dede592ac7d5af
```
**镜像id就是 该image-config文件的hash值!!!**
#### 3.3 diff_ids vs docker pull的layer-id
/var/lib/docker/image/overlay2/imagedb/content/sha256 目录存放了镜像的 config。并且指定了 diff_ids是: 64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed
这个看起来就是 具体镜像文件了。
docker pull是: 3cb635b06aa2
diff_ids: 64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed
这两为啥又不一样:
在pull镜像的时候显示的是各个layer的digest信息,在image config存的是diffid。要区分这两个,还要先回答为什么manifest的layer的表达和image config的layer的表达中不是一个东西。
<br>
**结论:** image config里面的diffid 就是本地解压后的 layer sha256sum值。 docker pull的是服务器端压缩的 layer sha256sum
当我们去registry上拉layer的时候,拉什么格式的呢,是根据请求中的media type决定的,因为layer存在本地的时候未压缩的,或者说是解压过的。
为了在网络上传输的更加快呢,所有media type一般会指定压缩格式的,比如gzip的,具体有哪些格式,见:[media type](https://link.zhihu.com/?target=https%3A//docs.docker.com/registry/spec/manifest-v2-2/%23media-types)
结合我最开始说的(manifest对应registry服务端的配置,image config针对本地存储端的),其实也就不难理解了。
当docker发现本地不存在某个layer的时候,就会通过manifest里面的digest + mediaType(一般是"application/vnd.docker.image.rootfs.diff.tar.gzip")去registry拉对应的leyer。
然后在image id存的对应的diff id就是上面拿到的tar.gz包解压为tar包的id。
```
# curl -H "Accept:application/vnd.docker.image.rootfs.diff.tar.gzip" https://docker-search.4pd.io/v2/ubuntu/blobs/sha256:7ddbc47eeb70dc7f08e410a667948b87ff3883024eb41478b44ef9a81bf400c -o layer1.tar.gz
# sha256sum layer1.tar.gz
7ddbc47eeb70dc7f08e410a6667948b87ff3883024eb41478b44ef9a81bf400c layer1.tar.gz
# sha256sum layer1.tar
cc967c529ced563b7746b663d98248bc571afdb3c012019d7f54d6c092793b8b layer1.tar
```
**distribution目录存放了对应的转换关系**
v2metadata-by-diffid : 文件名是 diff_ids, 文件的值是digest
diffid-by-digest: 文件名是digest, 文件值是 diff_ids
```
root@k8s-master:/var/lib/docker/image/overlay2/distribution/v2metadata-by-diffid/sha256# cat 64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed
[{"Digest":"sha256:3cb635b06aa273034d7080e0242e4b6628c59347d6ddefff019bfd82f45aa7d5","SourceRepository":"docker.io/library/busybox","HMAC":""}]
root@k8s-master:/var/lib/docker/image/overlay2/distribution/diffid-by-digest/sha256# cat 3cb635b06aa273034d7080e0242e4b6628c59347d6ddefff019bfd82f45aa7d5
sha256:64cac9eaf0da6a7ae6519b6c7198929f232324e0822b5e359ee0e27104e2d3ed
```
#### 3.4 如何查看每一层的layer在哪
以curlimages/curl:7.75.0镜像为例:
```
{
"architecture": "amd64",
"config": {
"User": "curl_user",
"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CURL_VERSION=7_75_0", "CURL_RELEASE_TAG=curl-7_75_0", "CURL_GIT_REPO=https://github.com/curl/curl.git", "CURL_CA_BUNDLE=/cacert.pem"],
"Entrypoint": ["/entrypoint.sh"],
"Cmd": ["curl"],
"Labels": {
"Maintainer": "James Fuller \u003cjim.fuller@webcomposite.com\u003e",
"Name": "curl",
"Version": "1.0.0",
"docker.cmd": "docker run -it curl/curl:7.75.0 -s -L http://curl.haxx.se",
"se.haxx.curl": "curl",
"se.haxx.curl.description": "network utility",
"se.haxx.curl.release_tag": "curl-7_75_0",
"se.haxx.curl.version": "7_75_0"
},
"ArgsEscaped": true,
"OnBuild": null
},
"created": "2021-02-03T10:22:09.59342396Z",
"history": [{
"created": "2020-12-17T00:19:41.960367136Z",
"created_by": "/bin/sh -c #(nop) ADD file:ec475c2abb2d46435286b5ae5efacf5b50b1a9e3b6293b69db3c0172b5b9658b in / "
}, {
"created": "2020-12-17T00:19:42.11518025Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "ARG CURL_RELEASE_TAG=latest",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "ARG CURL_RELEASE_VERSION",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "ARG CURL_GIT_REPO=https://github.com/curl/curl.git",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "ENV CURL_VERSION=7_75_0",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "ENV CURL_RELEASE_TAG=curl-7_75_0",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "ENV CURL_GIT_REPO=https://github.com/curl/curl.git",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "LABEL Maintainer=James Fuller \u003cjim.fuller@webcomposite.com\u003e",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "LABEL Name=curl",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "LABEL Version=",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "LABEL docker.cmd=docker run -it curl/curl:7.75.0 -s -L http://curl.haxx.se",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:18:02.868616268Z",
"created_by": "RUN |3 CURL_RELEASE_TAG=curl-7_75_0 CURL_RELEASE_VERSION=7_75_0 CURL_GIT_REPO=https://github.com/curl/curl.git /bin/sh -c apk add --no-cache brotli brotli-dev libssh2 nghttp2-dev \u0026\u0026 rm -fr /var/cache/apk/* # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:18:03.050522395Z",
"created_by": "RUN |3 CURL_RELEASE_TAG=curl-7_75_0 CURL_RELEASE_VERSION=7_75_0 CURL_GIT_REPO=https://github.com/curl/curl.git /bin/sh -c addgroup -S curl_group \u0026\u0026 adduser -S curl_user -G curl_group # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:08.691286411Z",
"created_by": "COPY /cacert.pem /cacert.pem # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:08.691286411Z",
"created_by": "ENV CURL_CA_BUNDLE=/cacert.pem",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:22:08.768815145Z",
"created_by": "COPY /alpine/usr/local/lib/libcurl.so.4.7.0 /usr/lib/ # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:08.853211212Z",
"created_by": "COPY /alpine/usr/local/bin/curl /usr/bin/curl # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:09.262850838Z",
"created_by": "RUN |3 CURL_RELEASE_TAG=curl-7_75_0 CURL_RELEASE_VERSION=7_75_0 CURL_GIT_REPO=https://github.com/curl/curl.git /bin/sh -c ln -s /usr/lib/libcurl.so.4.7.0 /usr/lib/libcurl.so.4 # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:09.516766096Z",
"created_by": "RUN |3 CURL_RELEASE_TAG=curl-7_75_0 CURL_RELEASE_VERSION=7_75_0 CURL_GIT_REPO=https://github.com/curl/curl.git /bin/sh -c ln -s /usr/lib/libcurl.so.4 /usr/lib/libcurl.so # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:09.516766096Z",
"created_by": "USER curl_user",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:22:09.59342396Z",
"created_by": "COPY entrypoint.sh /entrypoint.sh # buildkit",
"comment": "buildkit.dockerfile.v0"
}, {
"created": "2021-02-03T10:22:09.59342396Z",
"created_by": "CMD [\"curl\"]",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}, {
"created": "2021-02-03T10:22:09.59342396Z",
"created_by": "ENTRYPOINT [\"/entrypoint.sh\"]",
"comment": "buildkit.dockerfile.v0",
"empty_layer": true
}],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": ["sha256:777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf", "sha256:019dd39b82bba02007b940007ee0662015ff0a11ddd55fb7b4a4f6f1e3f694f2", "sha256:ead19f98b65e2cb338cab0470d7ddadc8a23c32ccd34ab6511a35393c7b7335d", "sha256:bcbfcc5b87d4afa5cf8981569a2dcebfd01643a7ddbe82f191062cf677d024b2", "sha256:6e767bd912c28e4d667adfec7adcf1dab84f76ecf0b71cba76634b03a00e67e8", "sha256:9904f3d51f2e6e052fd2ce88494090739f23acec20f2a9c3b2d3deb86874dd0e", "sha256:56a8d17054bd206ae215f3b81ecbb2d2715b21f48966763fc8c9144ac8f8d46e", "sha256:939fe15ec48dad8528237a6330438426dd8627db92a891eb610e36075274e2f5", "sha256:3e7aa53fce9350e24217d0b33912c286a4748e36facfd174c32ec53303be025f"]
}
}
```
/var/lib/docker/image/overlay2/layerdb/sha256目录存放的diffids的最上层信息,也就是777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf,这是个目录
那这个里面到底是啥意思呢,这个里面是chainid,这个是因为chainid的一层是依赖上一层的,这就导致最后算出来的rootfs是统一的。 公式为(具体可见:[layer-chainid](https://link.zhihu.com/?target=https%3A//github.com/opencontainers/image-spec/blob/master/config.md%23layer-chainid)): chaninid(1) = diffid(1) chainid(n) = sha256(chain(n-1) diffid(n) )
```
root@k8s-node:~# cd /var/lib/docker/image/overlay2/layerdb/sha256
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256# ls
02aca22ece6a3cd150e7df6e3a651c1386983a9cd525250e804957e5c8629a05
1be8816ebbd7f52290964aa6df8ff27825772a40baded5d91a152ded7c2534a3
1bfbb02dea047ad9341efddc61f0b8a9b473b86001bf7605df7a8880b157b8a9
4006d6bc83834f41eae67f73db4fd4ed3364b06362780a529b44dd5015711092
439f01e6ba92ba1e5b3be977f73014ab80e7997462b9ca86f44ae9b6cdc99cb7
4d4eb19da25f4f4649cf74c7028acd317962959e4b9b55aec27b4cfc3b867b93
5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef
666604249ff52593858b7716232097daa6d721b7b4825aac8bf8a3f45dfba1ce
722f29343eb01a012a210445f66fc22678ca5750ae3bba2cfde9a5c3b62c701d
777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf //第一层
7897c392c5f451552cd2eb20fdeadd1d557c6be8a3cd20d0355fb45c1f151738
7fcb75871b2101082203959c83514ac8a9f4ecfee77a0fe9aa73bbe56afdf1b4
8473ff61fb5a229cfb7e0410cc815321b3bbe7a88c22766fe4f3f643a7ea2e32
85e5b916bf35f12eeb78c6d89d1cba758c0a60d516401beef41a2aa65f8ddb76
b6b031f5155c8fdd924e4e2508b6ae4018ff646efa86734c8c34b0d61a82b5ea
bfb718dadfd11e598f98dc1314421be5bdee044f417a4149bfd370083db78e6e
d43d6edaff1c22bfd53fcb4b0aa1f00dcd987d45b38ac3971317350785c18574
d8546a51a3203d6ac8eb7b5b0f23a97e77aa706e0ee2136e8747c000538926bd
e8f232ecf2faa5a124d8025eaea6861ff94fc1a5c7da17d7b9712aa24431293e
eea7cd97478d04eff4f9fc36c229d9e9f3d42740e6dc02d6578104e945f38d9f
f1dd685eb59e7d19dd353b02c4679d9fafd21ccffe1f51960e6c3645f3ceb0cd
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256#
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256#
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256/777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf# ls -l
total 32
-rw-r--r-- 1 root root 64 Dec 19 20:41 cache-id 真正对应的layer数据那个目录
-rw-r--r-- 1 root root 71 Dec 19 20:41 diff 该层的diffid
-rw-r--r-- 1 root root 7 Dec 19 20:41 size 该层的大小
-rw-r--r-- 1 root root 19501 Dec 19 20:41 tar-split.json.gz layer压缩包的split文件
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256/777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf# cat cache-id
840c5d412d4af8d058a526074900c098c1469ecd2f08fb21c39d23ffd2a9d527
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256/777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf# cat diff
sha256:777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf
```
<br>
/var/lib/docker/overlay2/就是layer数据存放的目录,比如每个chainid里面cache-id都回应这个目录下面的一个目录
diff 目录就是所有数据的目录
```
// 没有lower, diff目录
root@k8s-node:/var/lib/docker/overlay2/840c5d412d4af8d058a526074900c098c1469ecd2f08fb21c39d23ffd2a9d527# ls
committed diff link
root@k8s-node:/var/lib/docker/overlay2/840c5d412d4af8d058a526074900c098c1469ecd2f08fb21c39d23ffd2a9d527/diff# ls -l
total 68
drwxr-xr-x 2 root root 4096 Dec 16 2020 bin
drwxr-xr-x 2 root root 4096 Dec 16 2020 dev
drwxr-xr-x 15 root root 4096 Dec 16 2020 etc
drwxr-xr-x 2 root root 4096 Dec 16 2020 home
drwxr-xr-x 7 root root 4096 Dec 16 2020 lib
drwxr-xr-x 5 root root 4096 Dec 16 2020 media
drwxr-xr-x 2 root root 4096 Dec 16 2020 mnt
drwxr-xr-x 2 root root 4096 Dec 16 2020 opt
dr-xr-xr-x 2 root root 4096 Dec 16 2020 proc
drwx------ 2 root root 4096 Dec 16 2020 root
drwxr-xr-x 2 root root 4096 Dec 16 2020 run
drwxr-xr-x 2 root root 4096 Dec 16 2020 sbin
drwxr-xr-x 2 root root 4096 Dec 16 2020 srv
drwxr-xr-x 2 root root 4096 Dec 16 2020 sys
drwxrwxrwt 2 root root 4096 Dec 16 2020 tmp
drwxr-xr-x 7 root root 4096 Dec 16 2020 usr
drwxr-xr-x 12 root root 4096 Dec 16 2020 var
```
<br>
这里很奇怪的一点就是: 镜像中 diff_ids 这个为啥只有第一层在 /var/lib/docker/image/overlay2/layerdb/sha256 目录中,其他的都 不在吗?
```
"diff_ids": ["sha256:777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf", "sha256:019dd39b82bba02007b940007ee0662015ff0a11ddd55fb7b4a4f6f1e3f694f2", "sha256:ead19f98b65e2cb338cab0470d7ddadc8a23c32ccd34ab6511a35393c7b7335d", "sha256:bcbfcc5b87d4afa5cf8981569a2dcebfd01643a7ddbe82f191062cf677d024b2", "sha256:6e767bd912c28e4d667adfec7adcf1dab84f76ecf0b71cba76634b03a00e67e8", "sha256:9904f3d51f2e6e052fd2ce88494090739f23acec20f2a9c3b2d3deb86874dd0e", "sha256:56a8d17054bd206ae215f3b81ecbb2d2715b21f48966763fc8c9144ac8f8d46e", "sha256:939fe15ec48dad8528237a6330438426dd8627db92a891eb610e36075274e2f5", "sha256:3e7aa53fce9350e24217d0b33912c286a4748e36facfd174c32ec53303be025f"]
```
其实不是的,这里的diff_ids可以认为是累加的changeid,比如说我想知道第二层对应 overlay的文件。就可以。
```
必须这样这个是因为chainid的一层是依赖上一层的,这就导致最后算出来的rootfs是统一的。 公式为(具体可见:[layer-chainid](https://link.zhihu.com/?target=https%3A//github.com/opencontainers/image-spec/blob/master/config.md%23layer-chainid)): chaninid(1) = diffid(1) chainid(n) = sha256(chain(n-1) diffid(n) )
// 02aca22ece6a3cd150e7df6e3a651c1386983a9cd525250e804957e5c8629a05 就是第二层的目录,里面的cache_id就是 overLay-id
root@k8s-node:/var/lib/docker/image/overlay2/layerdb# echo -n "sha256:777b2c648970480f50f5b4d0af8f9a8ea798eea43dbcf40ce4a8c7118736bdcf sha256:019dd39b82bba02007b940007ee0662015ff0a11ddd55fb7b4a4f6f1e3f694f2" | sha256sum
02aca22ece6a3cd150e7df6e3a651c1386983a9cd525250e804957e5c8629a05 -
// 4d4eb19da25f4f4649cf74c7028acd317962959e4b9b55aec27b4cfc3b867b93 就是第三层层的目录,里面的cache_id就是 overLay-id
root@k8s-node:/var/lib/docker/image/overlay2/layerdb/sha256# echo -n "sha256:02aca22ece6a3cd150e7df6e3a651c1386983a9cd525250e804957e5c8629a05 sha256:ead19f98b65e2cb338cab0470d7ddadc8a23c32ccd34ab6511a35393c7b7335d" | sha256sum
4d4eb19da25f4f4649cf74c7028acd317962959e4b9b55aec27b4cfc3b867b93 -
```
这样 b4c36536404c5e7e468080cabf0c664a45b68eece4a37ff09cac8395869131fc (02aca22ece6a3cd150e7df6e3a651c1386983a9cd525250e804957e5c8629a05 cache-id的内容)就是第二层的overlay 文件。
```
root@k8s-node:/var/lib/docker/overlay2/b4c36536404c5e7e468080cabf0c664a45b68eece4a37ff09cac8395869131fc/diff# ls
etc lib usr
root@k8s-node:/var/lib/docker/overlay2/0422e796ce6cdc75d11303c0018b65ca9285dc36b812f4e14c4f68dbc01bc6d9/diff# ls
etc home
// 有lower, work目录
root@k8s-node:/var/lib/docker/overlay2/0422e796ce6cdc75d11303c0018b65ca9285dc36b812f4e14c4f68dbc01bc6d9# ls
committed diff link lower work
```
<br>
再找一个最简单的。或者直接比较镜像中文件和第一层layer文件,发现第一层layer文件是最基础的。
```
root@# docker pull zoux/pause-amd64:3.0
3.0: Pulling from zoux/pause-amd64
4f4fb700ef54: Pull complete
ce150f7a21ec: Pull complete
Digest: sha256:f04288efc7e65a84be74d4fc63e235ac3c6c603cf832e442e0bd3f240b10a91b
Status: Downloaded newer image for zoux/pause-amd64:3.0
"diff_ids":[
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
"sha256:41ff149e94f22c52b8f36c59cafe7538b70ea771e62d9fc6922dedac25392fdf",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"]}}
echo -n "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef sha256:41ff149e94f22c52b8f36c59cafe7538b70ea771e62d9fc6922dedac25392fdf" | sha256sum
```
### 4. 结论
(1)一个镜像有一个唯一的imageid 和 digestid。imageid 可以认为是本地image config的sha256sum值,digestid是服务器端该镜像config的sha256sum值。
例如本地image config 保存在 /var/lib/docker/image/overlay2/imagedb/content/sha256 目录。该目录下,一个文件就是一个image config。
对该文件内容计算sha256sum得出来的值就是 imageid, 也就是文件名。
/var/lib/docker/image/overlay2/repositories.json 存放了对应的转换关系。
(2)为什么有了imageid,还需要digestid。因为本地的image config一般都是解压后的,服务器端一般都是压缩打包的,所以可以认为digestid是服务器端压缩好的image config 的sha256sum
(3)image config里面的diffid 就是本地解压后的 layer sha256sum值。 docker pull的是服务器端压缩的 layer sha256sum。
/var/lib/docker/image/overlay2/distribution/v2metadata-by-diffid/sha256 目录下存放了对应的转换关系。
(4)diffids是本地镜像每一层的sha256sum值。 pull 的是 服务器中每一层的sha256sum值。
(5)/var/lib/docker/image/overlay2/layerdb/sha256 存放了 diffids -> overlay(实际文件) 转换 (cacheid)
但是不是第一层的要经过转换。
(6)var/lib/docker/overlay2/0422e796ce6cdc75d11303c0018b65ca9285dc36b812f4e14c4f68dbc01bc6d9/diff 是实际每一层的文件内容
第一层没有 lower, work目录,因为从第二层开始才是联合文件。
<br>
举例说明:
f04288efc7e65a84be74d4fc63e235ac3c6c603cf832e442e0bd3f240b10a91b 是该镜像config在 服务器的sha256sum值
4f4fb700ef54, ce150f7a21ec表示该镜像有俩层,是 layer在服务器压缩文件的 sha256sum
```
root@k8s-master: # docker pull zoux/pause-amd64:3.0
3.0: Pulling from zoux/pause-amd64
4f4fb700ef54: Pull complete
ce150f7a21ec: Pull complete
Digest: sha256:f04288efc7e65a84be74d4fc63e235ac3c6c603cf832e442e0bd3f240b10a91b
Status: Downloaded newer image for zoux/pause-amd64:3.0
```
<br>
```
root@k8s-master:# docker rmi zoux/pause-amd64:3.0
Untagged: zoux/pause-amd64:3.0
// untag服务器 digestid
Untagged: zoux/pause-amd64@sha256:f04288efc7e65a84be74d4fc63e235ac3c6c603cf832e442e0bd3f240b10a91b
// 删除镜像id
Deleted: sha256:99e59f495ffaa222bfeb67580213e8c28c1e885f1d245ab2bbe3b1b1ec3bd0b2
// 删除 layer-id (不是diff-ids, 是转换好的id,所以通过这个id,可以直接在 )
Deleted: sha256:666604249ff52593858b7716232097daa6d721b7b4825aac8bf8a3f45dfba1ce
Deleted: sha256:7897c392c5f451552cd2eb20fdeadd1d557c6be8a3cd20d0355fb45c1f151738
// 找到真正的overlay目录
/var/lib/docker/image/overlay2/layerdb/sha256/7897c392c5f451552cd2eb20fdeadd1d557c6be8a3cd20d0355fb45c1f151738# cat cache-id
d932ba5b6deb33a4933760be2010ffb5a81bfd874a42b36678fbcf5a3091f827
```
### 5 参考
https://zhuanlan.zhihu.com/p/95900321
================================================
FILE: docker/7. docker 命令详解.md
================================================
* [1\.docker 常见命令行用法](#1docker-常见命令行用法)
* [1\.1 docker 系统本身相关](#11-docker-系统本身相关)
* [1\.1\.1 docker info](#111-docker-info)
* [1\.1\.2 docker system](#112-docker-system)
* [1\.1\.3 docker events](#113-docker-events)
* [1\.2 docker image相关](#12-docker-image相关)
* [1\-虚悬镜像](#1-虚悬镜像)
* [2\-docker image ls 格式化展示](#2-docker-image-ls-格式化展示)
* [3\-Untagged 和 Deleted](#3-untagged-和-deleted)
* [1\.3 docke container相关](#13-docke-container相关)
* [1\-docker diff](#1-docker-diff)
* [2\-docker top](#2-docker-top)
* [3\-docker attach](#3-docker-attach)
* [4\-docker logs \-f containerId](#4-docker-logs--f-containerid)
* [2\. docker api](#2-docker-api)
* [2\.1 Unix domain socket介绍](#21--unix-domain-socket介绍)
* [2\.2 如何通过 unix socket 使用docker](#22-如何通过-unix-socket-使用docker)
* [2\.3 如何通过restful api 使用docker](#23-如何通过restful-api-使用docker)
* [3\. 参考](#3-参考)
## 1.docker 常见命令行用法
### 1.1 docker 系统本身相关
#### 1.1.1 docker info
查看docker 的详细信息,例如docker root目录,使用的联合文件系统等等
```
root@k8s-node:~# docker info
Client:
Debug Mode: false
Server:
Containers: 9
Running: 4
Paused: 0
Stopped: 5
Images: 4
Server Version: 19.03.9
Storage Driver: overlay2
Backing Filesystem: extfs
Supports d_type: true
Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc version: dc9208a3303feef5b3839f4323d9beb36df0a9dd
init version: fec3683
Security Options:
apparmor
seccomp
Profile: default
Kernel Version: 4.19.0-17-amd64
Operating System: Debian GNU/Linux 10 (buster)
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 3.854GiB
Name: k8s-node
ID: FZUV:UMD7:U4L5:KUOH:WYWM:HI6I:HYOD:WSXF:E4D7:RUP2:4ETP:OQTY
Docker Root Dir: /var/lib/docker
Debug Mode: false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
https://b9pmyelo.mirror.aliyuncs.com/
Live Restore Enabled: false
Product License: Community Engine
WARNING: No swap limit support
```
#### 1.1.2 docker system
```
Usage: docker system COMMAND
Manage Docker
Commands:
df Show docker disk usage
events Get real time events from the server
info Display system-wide information
prune Remove unused data
Run 'docker system COMMAND --help' for more information on a command.
// 查看镜像实际占用的磁盘空间
root@k8s-master:~# docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 7 5 491.1MB 140.1MB (28%)
Containers 11 4 3.557kB 2.324kB (65%)
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B
```
#### 1.1.3 docker events
获取docker server的实时事件
```
# docker events --since 112141543
2022-01-17T12:29:19.046917401+08:00 container die 78deadc2dcd6a3fafc9ac6f8380e1cd8853ffd6bc33796a224ece76d17dd1d92 (Maintainer=James Fuller <jim.fuller@webcomposite.com>, Name=curl, Version=1.0.0, annotation.io.kubernetes.container.hash=bef672e5, annotation.io.kubernetes.container.restartCount=686, annotation.io.kubernetes.container.terminationMessagePath=/dev/termination-log, annotation.io.kubernetes.container.terminationMessagePolicy=File, annotation.io.kubernetes.pod.terminationGracePeriod=10, docker.cmd=docker run -it curl/curl:7.75.0 -s -L http://curl.haxx.se, exitCode=0, image=sha256:26a9afb7027cca51ed4f7915474a04822a13e99fce2e1eecad3d43aab6199387, io.kubernetes.container.logpath=/var/log/pods/default_nginx1_cc8a9cfb-872c-44ba-9899-b4c8bbc93a21/nginx/686.log, io.kubernetes.container.name=nginx, io.kubernetes.docker.type=container, io.kubernetes.pod.name=nginx1, io.kubernetes.pod.namespace=default, io.kubernetes.pod.uid=cc8a9cfb-872c-44ba-9899-b4c8bbc93a21, io.kubernetes.sandbox.id=e93a3ae70771ca0e4954fcb6ecf0ffd091eebfc64bcb3cbf461c94eb5474c9aa, name=k8s_nginx_nginx1_default_cc8a9cfb-872c-44ba-9899-b4c8bbc93a21_686, se.haxx.curl=curl, se.haxx.curl.description=network utility, se.haxx.curl.release_tag=curl-7_75_0, se.haxx.curl.version=7_75_0)
2022-01-17T12:29:19.386628039+08:00 container destroy 98c26f5e6c744e7733eaf39fd4a0bfc3692d312213f0504664353157d5d446d9 (Maintainer=James Fuller <jim.fuller@webcomposite.com>, Name=curl, Version=1.0.0, annotation.io.kubernetes.container.hash=bef672e5, annotation.io.kubernetes.container.restartCount=685, annotation.io.kubernetes.container.terminationMessagePath=/dev/termination-log, annotation.io.kubernetes.container.terminationMessagePolicy=File, annotation.io.kubernetes.pod.terminationGracePeriod=10, docker.cmd=docker run -it curl/curl:7.75.0 -s -L http://curl.haxx.se, image=sha256:26a9afb7027cca51ed4f7915474a04822a13e99fce2e1eecad3d43aab6199387, io.kubernetes.container.logpath=/var/log/pods/default_nginx1_cc8a9cfb-872c-44ba-9899-b4c8bbc93a21/nginx/685.log, io.kubernetes.container.name=nginx, io.kubernetes.docker.type=container, io.kubernetes.pod.name=nginx1, io.kubernetes.pod.namespace=default, io.kubernetes.pod.uid=cc8a9cfb-872c-44ba-9899-b4c8bbc93a21, io.kubernetes.sandbox.id=e93a3ae70771ca0e4954fcb6ecf0ffd091eebfc64bcb3cbf461c94eb5474c9aa, name=k8s_nginx_nginx1_default_cc8a9cfb-872c-44ba-9899-b4c8bbc93a21_685, se.haxx.curl=curl, se.haxx.curl.description=network utility, se.haxx.curl.release_tag=curl-7_75_0, se.haxx.curl.version=7_75_0)
2022-01-17T12:29:19.448410928+08:00 container create 21c6aa12859cf40f78c0a80f6ef4b782e86b86a84a23fefb860b73cfed55cf31 (Maintainer=James Fuller <jim.fuller@webcomposite.com>, Name=curl, Version=1.0.0, annotation.io.kubernetes.container.hash=bef672e5, annotation.io.kubernetes.container.restartCount=687, annotation.io.kubernetes.container.terminationMessagePath=/dev/termination-log, annotation.io.kubernetes.container.terminationMessagePolicy=File, annotation.io.kubernetes.pod.terminationGracePeriod=10, docker.cmd=docker run -it curl/curl:7.75.0 -s -L http://curl.haxx.se, image=sha256:26a9afb7027cca51ed4f7915474a04822a13e99fce2e1eecad3d43aab6199387, io.kubernetes.container.logpath=/var/log/pods/default_nginx1_cc8a9cfb-872c-44ba-9899-b4c8bbc93a21/nginx/687.log, io.kubernetes.container.name=nginx, io.kubernetes.docker.type=container, io.kubernetes.pod.name=nginx1, io.kubernetes.pod.namespace=defau
gitextract_ays31j2l/
├── .gitignore
├── README.md
├── docker/
│ ├── 0-docker章节介绍.md
│ ├── 1. linux namespaces 知识准备.md
│ ├── 10. 如何下载并二进制编译docker源码.md
│ ├── 11. dockercli 源码分析-docker run为例.md
│ ├── 12. dockerd源码分析-docker run为例.md
│ ├── 2. linux cgroup 知识准备.md
│ ├── 3. chroot 命令详解.md
│ ├── 4. 如何用golang 实现一个 busybox的容器.md
│ ├── 5. docker-overlay技术.md
│ ├── 6. docker pull原理分析.md
│ ├── 7. docker 命令详解.md
│ ├── 8. docker核心组件介绍.md
│ ├── 9. docker问题链路排查实例.md
│ └── 其他/
│ ├── 补充-僵尸进程处理.md
│ └── 补充-容器进程.md
├── etcd/
│ ├── 0. etcd常用操作.md
│ └── 协议理论知识/
│ ├── 1. cap原理.md
│ ├── 2. ACID理论.md
│ ├── 3. base理论.md
│ └── 4. raft协议.md
└── k8s/
├── README.md
├── client-go/
│ ├── 1- clientGo简介与章节安排.md
│ ├── 10. Controller-runtime原理分析.md
│ ├── 2-clientGo提供的四种客户端.md
│ ├── 3. apiserver中的list-watch机制.md
│ ├── 4. client informer机制简介.md
│ ├── 5. SharedInformerFactory机制.md
│ ├── 6. informer机制之cache.indexer机制.md
│ ├── 7. informer机制详解.md
│ ├── 8. client-go的workqueue详解.md
│ └── 9.从0到1使用kubebuilder创建crd.md
├── cni/
│ ├── 0.章节介绍.md
│ ├── 1. 网络基础知识.md
│ ├── 2. docker 4种 网络模式.md
│ ├── 3. docker容器网络的底层实现.md
│ ├── 4.k8s pod通信原理介绍.md
│ ├── 5. k8s 容器网络接口介绍.md
│ ├── 6.如何订制自己的cni.md
│ ├── 7. flannel原理浅析分析.md
│ └── 8. calico原理浅析md.md
├── install-k8s-from source code/
│ ├── 1-debian二进制安装v1.17 k8s.md
│ └── 2.window配置goland环境阅读kubernetes源码.md
├── kcm/
│ ├── 0-kcm启动流程.md
│ ├── 1-rs controller-manager源码分析.md
│ ├── 10-kcm-NodeLifecycleController源码分析.md
│ ├── 11.k8s node状态更新机制 .md
│ ├── 2-deployment controller-manager源码分析.md
│ ├── 3-k8s gc源码分析.md
│ ├── 3-k8s中以不同的策略删除资源时发生了什么.md
│ ├── 4-hpa-自定义metric server.md
│ ├── 4-hpa源码分析.md
│ ├── 5-job controller-manager源码分析.md
│ ├── 6-namespaces controller-manager源码分析.md
│ ├── 9-kubernetes污点和容忍度概念介绍.md
│ └── kcm篇源码分析总结.md
├── kube-apiserver/
│ ├── 0-apiserver笔记规划.md
│ ├── 1-v1.17 kube-apiserver启动参数介绍.md
│ ├── 10-kube-apiserver创建AggregatorServer.md
│ ├── 11-kube-apiserver 启动http和https服务.md
│ ├── 12-k8s之Authentication.md
│ ├── 13-k8s之Authorization.md
│ ├── 14-k8s之admission分析.md
│ ├── 15-k8s之etcd存储实现.md
│ ├── 16. 创建更新删除资源时apiserver做了什么工作.md
│ ├── 17-k8s之serviceaccount.md
│ ├── 18 event的定义.md
│ ├── 19. secret对象详解.md
│ ├── 2-kube-apiserver概述.md
│ ├── 20. kubectl exec原理介绍.md
│ ├── 21-kube-apiserver list-watch源码分析.md
│ ├── 3-k8s之资源介绍.md
│ ├── 4-scheme介绍.md
│ ├── 5-kube-apiserver启动流程汇总.md
│ ├── 6-kube-apiserver启动流程-资源注册+命令行初始.md
│ ├── 7-kube-apiserver创建APIServer通用配置.md
│ ├── 8-kube-apiserver创建APIExtensionsServer.md
│ └── 9-kube-apiserver 创建KubeAPIServer.md
├── kube-scheduler/
│ ├── 1. kube-scheduler简介.md
│ ├── 2-kube-scheduler源码分析.md
│ └── 3-如何编写一个scheduler plugin.md
├── kubectl/
│ ├── 0-ReadMe.md
│ ├── 1-kubectl 整体流程分析.md
│ ├── 2-client-go中连接apiserver的4种client介绍.md
│ ├── 3-kubectl Factory机制-上.md
│ ├── 4-kubectl Factor机制-下.md
│ ├── 5 visitor机制.md
│ ├── 6-kubectl中的所有visitor.md
│ ├── 7-kubectl create使用到的visitor.md
│ ├── 8- kubectl printer分析.md
│ └── 9-kubectl create整体流程分析.md
└── kubelet/
├── 0-readme.md
├── 1-kubelet 架构浅析.md
├── 10-k8s驱逐机制汇总.md
├── 2-kubelet初始化流程-上.md
├── 3-kubelet初始化流程-下.md
├── 4-kubelet 监听pod变化.md
├── 5-pod创建流程.md
├── 6-pod pleg更新流程.md
├── 7-pod delete流程.md
├── 8-kubelet gc流程.md
└── 9-kubelet驱逐源码分析.md
Condensed preview — 103 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,365K chars).
[
{
"path": ".gitignore",
"chars": 47,
"preview": "*/.DS_Store\n.DS_Store:\n**/.DS_Store\n.DS_Store?\n"
},
{
"path": "README.md",
"chars": 424,
"preview": "\n\n# learning-k8s-source-code\n\n从源码角度出发,学习k8s的原理。\n\n目前打算以`kube-apiserver` `kube-controller-manager` `kube-scheduler` `kub"
},
{
"path": "docker/0-docker章节介绍.md",
"chars": 300,
"preview": "容器技术是 云发展的一个重要基础,docker就是当前很火的一种容器技术。\n\n之前就知道docker利用了linux的cgruop, namespace + chroot + 联合文件系统实现的。\n\n本章力求从源码角度对docker进行分析"
},
{
"path": "docker/1. linux namespaces 知识准备.md",
"chars": 29416,
"preview": "* [1 namespace 简介](#1-namespace-简介)\r\n* [2\\. pid namespace](#2-pid-namespace)\r\n * [2\\.1 如何查看一个进程的 pid namespace](#21-如何查"
},
{
"path": "docker/10. 如何下载并二进制编译docker源码.md",
"chars": 7854,
"preview": "* [1\\. 如何下载docker源码](#1-如何下载docker源码)\n* [2\\. docker源码目录解析](#2-docker源码目录解析)\n* [3\\. 二进制编译docker源码](#3-二进制编译docker源码)\n * "
},
{
"path": "docker/11. dockercli 源码分析-docker run为例.md",
"chars": 22183,
"preview": "* [0\\. 章节目的](#0-章节目的)\n* [1\\. docker run 客户端处理流程](#1-docker-run-客户端处理流程)\n * [1\\.1 docker 函数入口](#11-docker-函数入口)\n* [2\\. 初"
},
{
"path": "docker/12. dockerd源码分析-docker run为例.md",
"chars": 53509,
"preview": "* [0\\. 章节目的](#0-章节目的)\n* [1\\. docker run服务器端处理流程](#1-docker-run服务器端处理流程)\n * [1\\.1 dockerd 函数入口](#11-dockerd-函数入口)\n * [1"
},
{
"path": "docker/2. linux cgroup 知识准备.md",
"chars": 16230,
"preview": "* [0\\. 说明](#0-说明)\n* [1\\. cgroup简介](#1-cgroup简介)\n* [2\\. CGroup 使用](#2-cgroup-使用)\n* [3\\. CGroup 基本概念](#3-cgroup-基本概念)\n* [4"
},
{
"path": "docker/3. chroot 命令详解.md",
"chars": 16089,
"preview": "* [1\\. chroot命令介绍](#1-chroot命令介绍)\r\n* [2\\. chroot实践](#2-chroot实践)\r\n * [2\\.1 执行bash, ls命令](#21-执行bash-ls命令)\r\n * [2\\.2 执行"
},
{
"path": "docker/4. 如何用golang 实现一个 busybox的容器.md",
"chars": 1663,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. 如何运行](#2-如何运行)\n* [3\\. 参考](#3-参考)\n\n### 1. 背景\n\n在入手docker源码之前,这里先用一个例子先理解一下,上面提到的Linux原理。\n\n主要参考这个r"
},
{
"path": "docker/5. docker-overlay技术.md",
"chars": 7749,
"preview": "* [0 背景](#0-背景)\n* [1 overlay介绍](#1-overlay介绍)\n* [2\\. 实验\\-通过实验来理解](#2-实验-通过实验来理解)\n * [2\\.1 实验设置](#21-实验设置)\n * [2\\.2 补充实"
},
{
"path": "docker/6. docker pull原理分析.md",
"chars": 35989,
"preview": "* [0\\. 章节目标](#0-章节目标)\r\n* [1\\. docker pull busybox 引入](#1-docker-pull-busybox-引入)\r\n * [1\\.1 引入的问题](#11-引入的问题)\r\n* [2\\. do"
},
{
"path": "docker/7. docker 命令详解.md",
"chars": 21142,
"preview": "* [1\\.docker 常见命令行用法](#1docker-常见命令行用法)\r\n * [1\\.1 docker 系统本身相关](#11-docker-系统本身相关)\r\n * [1\\.1\\.1 docker info](#111-d"
},
{
"path": "docker/8. docker核心组件介绍.md",
"chars": 14825,
"preview": "\n\n* [0\\. 章节目的](#0-章节目的)\n* [1\\.docker 组件介绍](#1docker-组件介绍)\n* [2\\. docker 组件分析](#2-docker-组件分析)\n * [2\\.1 docker](#21-dock"
},
{
"path": "docker/9. docker问题链路排查实例.md",
"chars": 6280,
"preview": "* [1\\. 确定问题](#1-确定问题)\r\n* [2\\. 开始排查](#2-开始排查)\r\n * [2\\.1 排除是否是dockerd出现了问题](#21-排除是否是dockerd出现了问题)\r\n * [2\\.2 排除是否是contai"
},
{
"path": "docker/其他/补充-僵尸进程处理.md",
"chars": 3926,
"preview": "### 1. 背景\n\n再使用容器时用户不当的使用,可能会造成了大量的僵尸进程没有回收,从而导致容器kill失败。kill 9 , kill 15 singal都没有反应。\n\n因此针对这个问题,输出一个处理报告。该报告分为两个部分:用户如何预"
},
{
"path": "docker/其他/补充-容器进程.md",
"chars": 7754,
"preview": "### 1. 为什么杀不死 容器的1号进程\n\n动作:在容器内杀死1号进程。\n\n```\n# kubectl exec -it zx-hpa-7c669876bb-bddsr -n test-zx /bin/sh\n# ps -ef\nUID "
},
{
"path": "etcd/0. etcd常用操作.md",
"chars": 6690,
"preview": "\r\n\r\n### 1. 实用脚本\r\n\r\n```\r\n### cat zoux_etcdctl.sh\r\n#! /bin/bash\r\n# Already test in etcd V3.0.4 and V3.1.7\r\n# Deal with ls "
},
{
"path": "etcd/协议理论知识/1. cap原理.md",
"chars": 720,
"preview": "### 1.cap\n\n\nCAP 理论对分布式系统的特性做了高度抽象,形成了三个指标\n\n* 一致性\n* 可用性\n* 分区容错性\n\n\n\n一致性(Consistency):客户端每次读写操作,不管是访问哪个节点都是一样的数据\n\n可用性(Avail"
},
{
"path": "etcd/协议理论知识/2. ACID理论.md",
"chars": 4254,
"preview": "### 1.ACID是什么\n\n事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。\n\nACID\nAtomic(原子性)\n\nConsistency(一致性)\n\nIsolation(隔离性)\n\nDura"
},
{
"path": "etcd/协议理论知识/3. base理论.md",
"chars": 492,
"preview": "BASE 理论是 CAP 理论中的 AP 的延伸,是对互联网大规模分布式系统的实践总结,强调可用性。几乎所有的互联网后台分布式系统都有 BASE 的支持,这个理论很重要,地位也很高。一旦掌握它,你就能掌握绝大部分场景的分布式系统的架构技巧,"
},
{
"path": "etcd/协议理论知识/4. raft协议.md",
"chars": 4853,
"preview": "[toc]\n\n### 1. raft算法是如何初始化的\n\n初始状态下,集群中所有的节点都是跟随者状态。\n\nRaft 算法实现了随机超时时间的特性。也就是说,每个节点等待领导者节点心跳信息的超时时间间隔是随机的。通过下面的图片你可以看到,集群"
},
{
"path": "k8s/README.md",
"chars": 40,
"preview": "## 版本说明\n\n如无特别说明,本章节所涉及的k8s源码版本皆为 V1.17.4"
},
{
"path": "k8s/client-go/1- clientGo简介与章节安排.md",
"chars": 7328,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. client-go简介](#1-client-go简介)\r\n * [1.1 client-go章节安排](#11-client-go章节"
},
{
"path": "k8s/client-go/10. Controller-runtime原理分析.md",
"chars": 27380,
"preview": "- [1. Controller-runtime结构介绍](#1-controller-runtime----)\n- [2. Controller-runtime 底层原理](#2-controller-runtime-----)\n * "
},
{
"path": "k8s/client-go/2-clientGo提供的四种客户端.md",
"chars": 29839,
"preview": "Table of Contents\r\n=================\r\n\r\n * [0. 四种客户端简介](#0-四种客户端简介)\r\n * [1.discovery](#1discovery)\r\n * [1.1 Server"
},
{
"path": "k8s/client-go/3. apiserver中的list-watch机制.md",
"chars": 14620,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. 背景](#1-背景)\r\n * [2. list watch机制](#2-list-watch机制)\r\n * [2.1 如何实现实时性]"
},
{
"path": "k8s/client-go/4. client informer机制简介.md",
"chars": 34982,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. informer机制简介](#1-informer机制简介)\r\n * [1.2. informer机制 example介绍](#12-i"
},
{
"path": "k8s/client-go/5. SharedInformerFactory机制.md",
"chars": 11718,
"preview": "Table of Contents\n=================\n\n * [1.章节介绍](#1章节介绍)\n * [2. SharedInformerFactory](#2-sharedinformerfactory)\n "
},
{
"path": "k8s/client-go/6. informer机制之cache.indexer机制.md",
"chars": 13883,
"preview": "Table of Contents\n=================\n\n * [1. 背景](#1-背景)\n * [2. Indexer结构说明](#2-indexer结构说明)\n * [3 store结构说明](#3-store结"
},
{
"path": "k8s/client-go/7. informer机制详解.md",
"chars": 38784,
"preview": "Table of Contents\n=================\n\n * [1.章节介绍](#1章节介绍)\n * [2. cache.SharedIndexInformer结构介绍](#2-cachesharedindexinfo"
},
{
"path": "k8s/client-go/8. client-go的workqueue详解.md",
"chars": 16964,
"preview": "Table of Contents\n=================\n\n * [1. 章节介绍](#1-章节介绍)\n * [2. workerqueue介绍](#2-workerqueue介绍)\n * [2.1 queue]("
},
{
"path": "k8s/client-go/9.从0到1使用kubebuilder创建crd.md",
"chars": 13359,
"preview": "- [0. 下载kubebuilder](#0---kubebuilder)\n- [1. 创建目录](#1-----)\n- [2. 初始化项目](#2------)\n- [3. 创建api和controller](#3---api-cont"
},
{
"path": "k8s/cni/0.章节介绍.md",
"chars": 115,
"preview": "本章节主要了解cni的相关知识,章节安排如下:\n\n(1)网路基础知识介绍\n\n(2)容器云用到的网络知识\n\n(3)kubelet中的cni介绍\n\n(4)flannel原理分析\n\n(5)cacilo原理分析\n\n(6)如何订制cni \n"
},
{
"path": "k8s/cni/1. 网络基础知识.md",
"chars": 16454,
"preview": "* [1\\. 网络基础知识](#1-网络基础知识)\n * [1\\.1 基础概念](#11-基础概念)\n * [1\\.2 一个宿主是如何处理数据包的](#12--一个宿主是如何处理数据包的)\n* [2\\. 物理层工作原理](#2-物理层"
},
{
"path": "k8s/cni/2. docker 4种 网络模式.md",
"chars": 8924,
"preview": "* [1\\. 介绍](#1-介绍)\n* [2 bridge模式](#2-bridge模式)\n* [3 host模式](#3-host模式)\n* [4\\. none模式](#4-none模式)\n* [5 container模式](#5-con"
},
{
"path": "k8s/cni/3. docker容器网络的底层实现.md",
"chars": 10102,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. 如何理解network namespaces](#2-如何理解network-namespaces)\n* [3\\. 不同namespaces之间是如何通信的](#3-不同namespaces"
},
{
"path": "k8s/cni/4.k8s pod通信原理介绍.md",
"chars": 6117,
"preview": "* [1\\. 目标](#1-目标)\n* [2\\. 通信原理](#2-通信原理)\n * [2\\.1 同一个Pod内部不同容器之间的通信](#21-同一个pod内部不同容器之间的通信)\n * [2\\.2 同一个节点上不同pod之间的通信原理"
},
{
"path": "k8s/cni/5. k8s 容器网络接口介绍.md",
"chars": 18280,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. Kubelet cni介绍](#2-kubelet-cni介绍)\n * [2\\.1 kubelet cni相关启动参数介绍](#21-kubelet-cni相关启动参数介绍)\n * [2"
},
{
"path": "k8s/cni/6.如何订制自己的cni.md",
"chars": 8078,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. conf如何配置](#2-conf如何配置)\n* [3\\. cni插件如何实现](#3-cni插件如何实现)\n * [3\\.1 摘抄部分](#31-摘抄部分)\n * [3\\.2 原创部分"
},
{
"path": "k8s/cni/7. flannel原理浅析分析.md",
"chars": 802,
"preview": "### 1. 原理简介\n\nFlannel 是 CoreOS 团队针对 Kubernetes 设计的一个网络规划实现。简单来说,它的功能有以下几点:\n\n1、使集群中的不同 Node 主机创建的 Docker 容器都具有全集群唯一的虚拟 IP "
},
{
"path": "k8s/cni/8. calico原理浅析md.md",
"chars": 566,
"preview": "\n\n他写的太好了,可以参考:[https://www.cnblogs.com/goldsunshine/p/10701242.html](https://links.jianshu.com/go?to=https%3A%2F%2Fwww.c"
},
{
"path": "k8s/install-k8s-from source code/1-debian二进制安装v1.17 k8s.md",
"chars": 33525,
"preview": "Table of Contents\n=================\n\n * [1. 集群规划](#1-集群规划)\n * [2.准备工作](#2准备工作)\n * [2.1 修改主机名](#21-修改主机名)\n * [2"
},
{
"path": "k8s/install-k8s-from source code/2.window配置goland环境阅读kubernetes源码.md",
"chars": 512,
"preview": "Table of Contents\n=================\n\n* [1. 代码下载](#1-代码下载)\n\n### 1. 代码下载\n\n(1)管理员运行git \n\n(2) 然后使用-c core.symlinks=true 来下载链"
},
{
"path": "k8s/kcm/0-kcm启动流程.md",
"chars": 37813,
"preview": "Table of Contents\n=================\n\n* [1. 定义-main](#1-定义-main)\n * [1.1 NewKubeControllerManagerOptions](#11-newkube"
},
{
"path": "k8s/kcm/1-rs controller-manager源码分析.md",
"chars": 37071,
"preview": "Table of Contents\n=================\n\n * [1. startReplicaSetController](#1-startreplicasetcontroller)\n * [1.1 rs中的ex"
},
{
"path": "k8s/kcm/10-kcm-NodeLifecycleController源码分析.md",
"chars": 51150,
"preview": "* [1\\. startNodeLifecycleController](#1-startnodelifecyclecontroller)\n* [2\\. NewNodeLifecycleController](#2-newnodelifec"
},
{
"path": "k8s/kcm/11.k8s node状态更新机制 .md",
"chars": 3221,
"preview": "**注意**\n\n为了防止参考链接失效,本文摘抄自:https://www.qikqiak.com/post/kubelet-sync-node-status/\n\n\n\n当 Kubernetes 中 Node 节点出现状态异常的情况下,节点上的"
},
{
"path": "k8s/kcm/2-deployment controller-manager源码分析.md",
"chars": 50021,
"preview": "Table of Contents\n=================\n\n * [1. deploy基础概念](#1-deploy基础概念)\n * [1.1. metadata.generation & status.ob"
},
{
"path": "k8s/kcm/3-k8s gc源码分析.md",
"chars": 40438,
"preview": "Table of Contents\n=================\n\n * [1. K8s 的垃圾回收策略](#1-k8s-的垃圾回收策略)\n * [2 gc 源码分析](#2-gc-源码分析)\n * [2.1 初始化 ga"
},
{
"path": "k8s/kcm/3-k8s中以不同的策略删除资源时发生了什么.md",
"chars": 35355,
"preview": "Table of Contents\n=================\n\n * [1. 孤儿模式](#1-孤儿模式)\n * [2. 后台模式](#2-后台模式)\n * [3. 前台模式](#3-前台模式)\n * [4. 总结](#4"
},
{
"path": "k8s/kcm/4-hpa-自定义metric server.md",
"chars": 11008,
"preview": "Table of Contents\n=================\n\n * [1. custom-metrics-apiserver简介](#1-custom-metrics-apiserver简介)\n * [2. 定制自己的met"
},
{
"path": "k8s/kcm/4-hpa源码分析.md",
"chars": 20639,
"preview": "Table of Contents\n=================\n\n * [1. hpa介绍](#1-hpa介绍)\n * [1.1 hpa是什么](#11-hpa是什么)\n * [1.2 hpa如何用起来](#12-"
},
{
"path": "k8s/kcm/5-job controller-manager源码分析.md",
"chars": 31983,
"preview": "Table of Contents\n=================\n\n * [1. job简介](#1--job简介)\n * [2. job controller源码分析-初始化](#2-job-controller源码分析-初始"
},
{
"path": "k8s/kcm/6-namespaces controller-manager源码分析.md",
"chars": 13679,
"preview": "Table of Contents\n=================\n\n * [1. startNamespaceController](#1-startnamespacecontroller)\n * [2. NewNamespace"
},
{
"path": "k8s/kcm/9-kubernetes污点和容忍度概念介绍.md",
"chars": 4630,
"preview": "### 1. 概念介绍\n\n **污点(Taint)** 应用于node身上,表示该节点有污点了,如果不能忍受这个污点的pod,你就不要调度/运行到这个节点上。如果是不能运行到这个节点上,那就是污点驱逐了。\n\n**容忍度(Toleration"
},
{
"path": "k8s/kcm/kcm篇源码分析总结.md",
"chars": 348,
"preview": "目前为止,kcm篇源码分析共hpa, gc, deploy, rs, job, ns 6个主要的控制器。通过这些源码分析,总结下目前的工作:\n\n(1)更了解kcm的机制。kcm就是一堆控制器的结合。每个控制器只干自己相关的事情,通过控制器的"
},
{
"path": "k8s/kube-apiserver/0-apiserver笔记规划.md",
"chars": 297,
"preview": "本章节的目标就是弄懂kube-apiserver的实现细节。从本质来说,kube-apiserver就是一个go server服务器端。\n\n假设我要实现kube-apiserver,我想到的要考虑的以下的事情:\n\n(1)apiserver的"
},
{
"path": "k8s/kube-apiserver/1-v1.17 kube-apiserver启动参数介绍.md",
"chars": 14618,
"preview": "\n\n摘自:https://v1-17.docs.kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-apiserver/\n\n\n\n- –etcd-servers:"
},
{
"path": "k8s/kube-apiserver/10-kube-apiserver创建AggregatorServer.md",
"chars": 38511,
"preview": "* [1\\. kube\\-apiserver 背景介绍](#1-kube-apiserver-背景介绍)\n* [2\\. CreateAggregatorServer源码分析](#2-createaggregatorserver源码分析)\n "
},
{
"path": "k8s/kube-apiserver/11-kube-apiserver 启动http和https服务.md",
"chars": 14014,
"preview": "* [1\\. 启动http服务](#1-启动http服务)\n * [1\\.1 链路流程](#11-链路流程)\n * [1\\.2 insecureHandlerChain](#12-insecurehandlerchain)\n* [2\\."
},
{
"path": "k8s/kube-apiserver/12-k8s之Authentication.md",
"chars": 29998,
"preview": "Table of Contents\n=================\n\n * [1. 简介](#1-简介)\n * [2. 认证器的生成](#2-认证器的生成)\n * [2.1 调用链路](#21-调用链路)\n * [2"
},
{
"path": "k8s/kube-apiserver/13-k8s之Authorization.md",
"chars": 25153,
"preview": "Table of Contents\n=================\n\n * [1. Authorization简介](#1-authorization简介)\n * [2. 6种授权机制](#2-6种授权机制)\n * [2.1"
},
{
"path": "k8s/kube-apiserver/14-k8s之admission分析.md",
"chars": 30425,
"preview": "Table of Contents\n=================\n\n * [1. 背景](#1-背景)\n * [2. 分析流程](#2-分析流程)\n * [2.1 Admission的注册](#21-admission的注"
},
{
"path": "k8s/kube-apiserver/15-k8s之etcd存储实现.md",
"chars": 26086,
"preview": "Table of Contents\n=================\n\n * [1. etcd 配置](#1-etcd-配置)\n * [2. Apiserver定义etcd的config](#2-apiserver定义etcd的con"
},
{
"path": "k8s/kube-apiserver/16. 创建更新删除资源时apiserver做了什么工作.md",
"chars": 38753,
"preview": "* [1\\. 简介](#1-简介)\n* [2\\. 流程介绍](#2-流程介绍)\n* [3\\. pod创建](#3-pod创建)\n * [3\\.1 pod create 前端逻辑](#31-pod-create-前端逻辑)\n * [3\\."
},
{
"path": "k8s/kube-apiserver/17-k8s之serviceaccount.md",
"chars": 20214,
"preview": "Table of Contents\n=================\n\n * [1. 什么是serviceaccount](#1-什么是serviceaccount)\n * [2、Service account与User accoun"
},
{
"path": "k8s/kube-apiserver/18 event的定义.md",
"chars": 5379,
"preview": "\n\nk8s集群中,controller-manage、kube-proxy、kube-scheduler、kubelet等组件都会产生大量的event。这些event对查看集群对象状态或者监控告警等等都非常有用。本章写一下自己对k8s中ev"
},
{
"path": "k8s/kube-apiserver/19. secret对象详解.md",
"chars": 10796,
"preview": "- [1. Secret 介绍-分为三大类](#1-secret---------)\n * [1.1 Opaque Secret方式](#11-opaque-secret--)\n + [1.1.1 通过volume挂载和环境变量的区"
},
{
"path": "k8s/kube-apiserver/2-kube-apiserver概述.md",
"chars": 72792,
"preview": "* [Table of Contents](#table-of-contents)\n * [1\\. kube\\-apiserver组件整体功能](#1-kube-apiserver组件整体功能)\n * [2\\. bootstra"
},
{
"path": "k8s/kube-apiserver/20. kubectl exec原理介绍.md",
"chars": 17262,
"preview": "- [0. 章节目标](#0-----)\n- [1. kubectl 端做的操作](#1-kubectl------)\n * [1.1 remotecommand包简介](#11-remotecommand---)\n * [1.2 SP"
},
{
"path": "k8s/kube-apiserver/21-kube-apiserver list-watch源码分析.md",
"chars": 26845,
"preview": "- [0. 背景](#0---)\n- [1. List-watch api定义](#1-list-watch-api--)\n- [2. 核心handler函数-ListResource](#2---handler---listresourc"
},
{
"path": "k8s/kube-apiserver/3-k8s之资源介绍.md",
"chars": 22072,
"preview": "* [1\\. 资源的表示](#1-资源的表示)\n * [1\\.1 Group](#11-group)\n * [1\\.2 version](#12-version)\n * [1\\.3 Resource](#13-resource)\n "
},
{
"path": "k8s/kube-apiserver/4-scheme介绍.md",
"chars": 19023,
"preview": "[toc]\n\n<br>\n\n### 1. schema简介-内存型的资源注册表\n\nKubernetes系统拥有众多资源,每一种资源就是一个资源类型,这些资源类型需要有统一的注册、存储、查询、管理等机制。目前Kubernetes系统中的所有资源"
},
{
"path": "k8s/kube-apiserver/5-kube-apiserver启动流程汇总.md",
"chars": 758,
"preview": "本节算是apiserver源码分析的总纲,apiserver启动流程如下所示:\n\n(1)Pod, svc,node 等资源注册\n\n(2)apiserver cobra命令行解析\n\n(3)RunE运行Run(completedOptions,"
},
{
"path": "k8s/kube-apiserver/6-kube-apiserver启动流程-资源注册+命令行初始.md",
"chars": 33995,
"preview": "* [Table of Contents](#table-of-contents)\n * [1\\. 资源注册](#1-资源注册)\n * [2\\. Cobra命令行参数解析](#2-cobra命令行参数解析)\n * [2"
},
{
"path": "k8s/kube-apiserver/7-kube-apiserver创建APIServer通用配置.md",
"chars": 41286,
"preview": "* [Table of Contents](#table-of-contents)\n * [1\\. 背景介绍](#1-背景介绍)\n * [1\\.1 CreateServerChain](#11-createservercha"
},
{
"path": "k8s/kube-apiserver/8-kube-apiserver创建APIExtensionsServer.md",
"chars": 28662,
"preview": "* [Table of Contents](#table-of-contents)\n * [1\\. 背景回顾](#1-背景回顾)\n * [2\\. 生成apiExtensionsConfig](#2-生成apiextensions"
},
{
"path": "k8s/kube-apiserver/9-kube-apiserver 创建KubeAPIServer.md",
"chars": 42584,
"preview": "* [Table of Contents](#table-of-contents)\n * [1\\. 背景回顾](#1-背景回顾)\n * [2\\. 创建KubeAPIServer](#2-创建kubeapiserver)\n "
},
{
"path": "k8s/kube-scheduler/1. kube-scheduler简介.md",
"chars": 3233,
"preview": "Table of Contents\n=================\n\n * [1. kube-scheduler功能简介](#1-kube-scheduler功能简介)\n * [2. kube-scheduler调度框架预览](#2"
},
{
"path": "k8s/kube-scheduler/2-kube-scheduler源码分析.md",
"chars": 50016,
"preview": "Table of Contents\n=================\n\n * [1. 源码阅读背景](#1-源码阅读背景)\n * [2. Kube-scheduler启动过程-源码分析](#2-kube-scheduler启动过程"
},
{
"path": "k8s/kube-scheduler/3-如何编写一个scheduler plugin.md",
"chars": 3970,
"preview": "Table of Contents\n=================\n\n * [0. 背景](#0-背景)\n * [1. 实现testPlugin](#1-实现testplugin)\n * [2. 注册testPlugin](#2-"
},
{
"path": "k8s/kubectl/0-ReadMe.md",
"chars": 89,
"preview": "本章主要通过 kubectl create -f pod.yaml 为主线, 研究kubectl 的实现机制,特别是对Factory, Builder, visitor有所了解。"
},
{
"path": "k8s/kubectl/1-kubectl 整体流程分析.md",
"chars": 15325,
"preview": "Table of Contents\n=================\n\n * [1. cmd/kubectl/kubectl.go](#1-cmdkubectlkubectlgo)\n * [2. NewDefaultKubectlCo"
},
{
"path": "k8s/kubectl/2-client-go中连接apiserver的4种client介绍.md",
"chars": 15426,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. client-go 中4种连接apiserver的客户端](#1-client-go-中4种连接apiserver的客户端)\r\n * ["
},
{
"path": "k8s/kubectl/3-kubectl Factory机制-上.md",
"chars": 22828,
"preview": "Table of Contents\n=================\n\n * [1.背景](#1背景)\n * [2 kubectl的kubeconfig](#2-kubectl的kubeconfig)\n * [2.1 kube"
},
{
"path": "k8s/kubectl/4-kubectl Factor机制-下.md",
"chars": 26142,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. 背景](#1-背景)\r\n * [1.1 MatchVersionFlags](#11-matchversionflags)\r\n * ["
},
{
"path": "k8s/kubectl/5 visitor机制.md",
"chars": 3848,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. 背景](#1-背景)\r\n * [2. visitor机制](#2-visitor机制)\r\n * [2.1 举例说明](#21-举例说明"
},
{
"path": "k8s/kubectl/6-kubectl中的所有visitor.md",
"chars": 18303,
"preview": "Table of Contents\n=================\n\n * [1. Visitor 接口](#1-visitor-接口)\n * [2. visitor种类](#2-visitor种类)\n * [2.1 Str"
},
{
"path": "k8s/kubectl/7-kubectl create使用到的visitor.md",
"chars": 12371,
"preview": "Table of Contents\n=================\n\n * [1. 背景说明](#1-背景说明)\n * [1.1 get](#11-get)\n * [1.2 delete](#12-delete)\n "
},
{
"path": "k8s/kubectl/8- kubectl printer分析.md",
"chars": 15513,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. kubectl 强大的格式化输出](#1-kubectl-强大的格式化输出)\r\n * [1.1 常见的用法: kubectl -o/--"
},
{
"path": "k8s/kubectl/9-kubectl create整体流程分析.md",
"chars": 20010,
"preview": "Table of Contents\n=================\n\n * [1. kubectl create 命令定义](#1-kubectl-create-命令定义)\n * [1.1 editBeforeCreate]("
},
{
"path": "k8s/kubelet/0-readme.md",
"chars": 101,
"preview": "本章节基于1.17.4版本的kubelet代码。力求从源码角度了解:\n\n(1)kubelet的启动过程\n\n(2)创建/删除/更新 pod的整个流程\n\n(3)以此为基础了解csi, cni,cri相关知识"
},
{
"path": "k8s/kubelet/1-kubelet 架构浅析.md",
"chars": 6257,
"preview": "Table of Contents\r\n\r\n * [1. 概要](#1-概要)\r\n * [2. kubelet 的主要功能](#2-kubelet-的主要功能)\r\n * [2.1 kubelet 默认监听四个端口,分别为 1025"
},
{
"path": "k8s/kubelet/10-k8s驱逐机制汇总.md",
"chars": 2234,
"preview": "### 1. 驱逐\n\nEviction,即驱逐的意思,意思是当节点出现异常时,为了保证工作负载的可用性,kubernetes将有相应的机制驱逐该节点上的Pod。\n\n### 2. 驱逐类型\n\n 目前有4个主要的驱逐场景, 分布是手工驱逐,节点"
},
{
"path": "k8s/kubelet/2-kubelet初始化流程-上.md",
"chars": 62520,
"preview": "Table of Contents\r\n=================\r\n\r\n * [1. kubelet入口函数](#1-kubelet入口函数)\r\n * [2. NewKubeletCommand函数](#2-newkubelet"
},
{
"path": "k8s/kubelet/3-kubelet初始化流程-下.md",
"chars": 26325,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. pleg\\.Start](#2-plegstart)\n* [3\\.syncLoop](#3syncloop)\n * [3\\.1 syncLoopIteration 相关channel介绍]"
},
{
"path": "k8s/kubelet/4-kubelet 监听pod变化.md",
"chars": 12109,
"preview": "* [1\\.背景](#1背景)\n* [1\\. makePodSourceConfig](#1-makepodsourceconfig)\n* [2\\. PodConfig 结构体介绍](#2-podconfig-结构体介绍)\n* [3\\.Me"
},
{
"path": "k8s/kubelet/5-pod创建流程.md",
"chars": 50818,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. HandlePodAdditions](#2-handlepodadditions)\n * [2\\.1 dispatchWork](#21-dispatchwork)\n * [2\\."
},
{
"path": "k8s/kubelet/6-pod pleg更新流程.md",
"chars": 2883,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. HandlePodSyncs](#2-handlepodsyncs)\n * [2\\.1 dispatchWork](#21-dispatchwork)\n * [2\\.2 UpdatePo"
},
{
"path": "k8s/kubelet/7-pod delete流程.md",
"chars": 32145,
"preview": "* [1\\.背景](#1背景)\n* [2\\. HandlePodUpdates](#2-handlepodupdates)\n * [2\\.1 syncPodFn](#21-syncpodfn)\n * [2\\.2 kl\\.killPod("
},
{
"path": "k8s/kubelet/8-kubelet gc流程.md",
"chars": 18947,
"preview": "* [1\\. 背景](#1-背景)\n* [2\\. StartGarbageCollection](#2-startgarbagecollection)\n* [3\\. container gc处理流程](#3-container-gc处理流程"
},
{
"path": "k8s/kubelet/9-kubelet驱逐源码分析.md",
"chars": 20214,
"preview": "- [1. 关键调用链路](#1-------)\n- [2. initializeRuntimeDependentModules](#2-initializeruntimedependentmodules)\n- [3. evictionMa"
}
]
About this extraction
This page contains the full source code of the zoux86/learning-k8s-source-code GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 103 files (1.8 MB), approximately 605.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.