JackZhu's Blog

JackZhu's Blog


  • 首页

  • 关于

  • 标签

Kubernetes-核心概念

发表于 2021-01-20 |

Kubernetes-核心概念

Pod

Pod 是 Kubernetes 的一个最小调度以及资源单元。用户可以通过 Kubernetes 的 Pod API 生产一个 Pod,让 Kubernetes 对这个 Pod 进行调度,也就是把它放在某一个 Kubernetes 管理的节点上运行起来。一个 Pod 简单来说是对一组容器的抽象,它里面会包含一个或多个容器。

在 Pod 里面,我们也可以去定义容器所需要运行的方式。比如说运行容器的 Command,以及运行容器的环境变量等等。Pod 这个抽象也给这些容器提供了一个共享的运行环境,它们会共享同一个网络环境,这些容器可以用 localhost 来进行直接的连接。而 Pod 与 Pod 之间,是互相有 隔离的。

Deployment

Deployment 是在 Pod 这个抽象上更为上层的一个抽象,它可以定义一组 Pod 的副本数目、以及这个 Pod 的版本。一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元。

Kubernetes 是通过 Controller,也就是我们刚才提到的控制器去维护 Deployment 中 Pod 的数目,它也会去帮助 Deployment 自动恢复失败的 Pod。

Service

Service 提供了一个或者多个 Pod 实例的稳定访问地址。

实现 Service 有多种方式,Kubernetes 支持 Cluster IP,上面我们讲过的 kuber-proxy 的组网,它也支持 nodePort、 LoadBalancer 等其他的一些访问的能力。

Ingress

Ingress Controller是一个统称,并不是只有一个,有如下这些:

  • Ingress NGINX: Kubernetes 官方维护的方案,也是本次安装使用的 Controller。
  • F5 BIG-IP Controller: F5 所开发的 Controller,它能够让管理员通过 CLI 或 API 让 Kubernetes 与 OpenShift 管理 F5 BIG-IP 设备。
  • Ingress Kong: 著名的开源 API Gateway 方案所维护的 Kubernetes Ingress Controller。
  • Traefik: 是一套开源的 HTTP 反向代理与负载均衡器,而它也支援了 Ingress。
  • Voyager: 一套以 HAProxy 为底的 Ingress Controller。

Namespace

Namespace 是用来做一个集群内部的逻辑隔离的,它包括鉴权、资源管理等。Kubernetes 的每个资源,比如刚才讲的 Pod、Deployment、Service 都属于一个 Namespace,同一个 Namespace 中的资源需要命名的唯一性,不同的 Namespace 中的资源可以重名。

参考文献

  • https://www.infoq.cn/article/knmavdo3jxs3qpkqtzbw
  • https://www.servicemesher.com/blog/kubernetes-ingress-controller-deployment-and-ha/

Kubernetes-cluster-介绍

发表于 2021-01-19 |

Kubernetes 组件

kubernetes

Kubernetes 是一个自动化的容器(container)编排平台,它负责应用的部署、应用的弹性以及应用的管理,这些都是基于容器的

image

Kubernetes架构图

image

image

Master (Control Plane Components)

apiserver

Kubernetes 中所有的组件都会和 API Server 进行连接,组件与组件之间一般不进行独立的连接,都依赖于 API Server 进行消息的传送;
API Server 设计上考虑了水平伸缩,也就是说,它可通过部署多个实例进行伸缩。 你可以运行 API Server 的多个实例,并在这些实例之间平衡流量。

scheduler

控制平面组件,负责监视新创建的、未指定运行节点(node)的 Pods,选择节点让 Pod 在上面运行。

调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。

controller-manager

从逻辑上讲,每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。

这些控制器包括:

节点控制器(Node Controller): 负责在节点出现故障时进行通知和响应。
副本控制器(Replication Controller): 负责为系统中的每个副本控制器对象维护正确数量的 Pod。
端点控制器(Endpoints Controller): 填充端点(Endpoints)对象(即加入 Service 与 Pod)。
服务帐户和令牌控制器(Service Account & Token Controllers): 为新的命名空间创建默认帐户和 API 访问令牌.

etcd

etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。

image

Node

kubelet

一个在集群中每个节点(node)上运行的代理。 它保证容器(containers)都 运行在 Pod 中。

kubelet 接收一组通过各类机制提供给它的 PodSpecs,确保这些 PodSpecs 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。

kube-proxy

kube-proxy 是集群中每个节点上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。

kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。

如果操作系统提供了数据包过滤层并可用的话,kube-proxy 会通过它来实现网络规则。否则, kube-proxy 仅转发流量本身。

Container Runtime

Kubernetes 支持多个容器运行环境: Docker、 containerd、CRI-O 以及任何实现 Kubernetes CRI (容器运行环境接口)。

参考文献

  • https://kubernetes.io/zh/docs/concepts/overview/components
  • https://www.infoq.cn/article/knmavdo3jxs3qpkqtzbw

Linux Union File System 文件系统

发表于 2020-09-11 |

Linux Union File System 文件系统

UnionFS

Union File System,所谓UnionFS就是把不同物理位置的目录合并mount到同一个目录中,中文叫联合文件系统的文件系统

具备如下特性:

联合挂载:将多个目录按层次组合,一并挂载到一个联合挂载点

写时复制:对联合挂载点的修改不会影响到底层的多个目录,而是使用其他目录记录修改的操作

目前有多种文件系统可以被当作联合文件系统,实现如上的功能:overlay2,aufs,devicemapper,btrfs,zfs,vfs等等。

AUFS

AUFS 是一种UnionFS, AUFS 英文全称就 Advanced Multi-Layered Unification Filesystem 曾经也叫 Acronym Multi-Layered Unification Filesystem、Another Multi-Layered Unification Filesystem
到现在AUFS都还进不了Linux内核主干,据说是Linus一直不同意

AUFS 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
root@jackzhu-ubuntu:/aufs# tree
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
├── c
│   └── 3.txt
└── mnt

4 directories, 3 files
root@jackzhu-ubuntu:/aufs# cat */*.txt
a dir file
b dir file
c dir file

root@jackzhu-ubuntu:/aufs# mount -t aufs -o dirs=./a:./b:./c none ./mnt/
root@jackzhu-ubuntu:/aufs# tree
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
├── c
│   └── 3.txt
└── mnt
├── 1.txt
├── 2.txt
└── 3.txt

4 directories, 6 files

刚刚创建了a,b,c,mnt四个目录,分别在a,b,c目录写入一个txt文件,然后把a,b,c三个文件夹mount到mnt目录里面,这个时候就能在mnt目录里面看到a,b,c目录里面的所有文件了,注意上面mount命令没有加权限的参数,接下来尝试在mnt目录修改几个txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
root@jackzhu-ubuntu:/aufs/mnt# pwd
/aufs/mnt
root@jackzhu-ubuntu:/aufs/mnt# cat 1.txt
a dir file
a dir file
root@jackzhu-ubuntu:/aufs/mnt# cat 2.txt
b dir file
b dir file
root@jackzhu-ubuntu:/aufs/mnt# cat 3.txt
c dir file
c dir file

root@jackzhu-ubuntu:/aufs# tree
.
├── a
│   ├── 1.txt
│   ├── 2.txt
│   └── 3.txt
├── b
│   └── 2.txt
├── c
│   └── 3.txt
└── mnt
├── 1.txt
├── 2.txt
└── 3.txt

4 directories, 8 files

root@jackzhu-ubuntu:/aufs# cat b/2.txt
b dir file
root@jackzhu-ubuntu:/aufs# cat c/3.txt
c dir file
root@jackzhu-ubuntu:/aufs# cat a/1.txt
a dir file
a dir file
root@jackzhu-ubuntu:/aufs# cat a/2.txt
b dir file
b dir file
root@jackzhu-ubuntu:/aufs# cat a/3.txt
c dir file
c dir file
root@jackzhu-ubuntu:/aufs#

看上面的结果,很奇怪a目录多了2个txt文件,b,c目录的文件并没有改变,原因是上面mount操作的时候没有指定权限,默认mount aufs 多个目录的时候,只有第一个目录是可写的目录,其它后面几个目录都是只读的,那为什么第一个目录 会多几个文件,这个技术叫CoW,全称copy-on-wirte 中文叫【写时复制】,目的就是为了节省磁盘空间提高资源利用率

如果a,b,c目录有相同的文件名怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
root@jackzhu-ubuntu:/aufs# tree
.
├── a
│   └── 1.txt
├── b
│   └── 2.txt
├── c
│   └── 3.txt
└── mnt

4 directories, 3 files
root@jackzhu-ubuntu:/aufs# cat a/1.txt
a dir file
a dir file
root@jackzhu-ubuntu:/aufs# cat b/2.txt
b dir file
root@jackzhu-ubuntu:/aufs# cat c/3.txt
c dir file
root@jackzhu-ubuntu:/aufs# echo "a dir file" > a/2.txt
root@jackzhu-ubuntu:/aufs# tree
.
├── a
│   ├── 1.txt
│   └── 2.txt
├── b
│   └── 2.txt
├── c
│   └── 3.txt
└── mnt

4 directories, 4 files
root@jackzhu-ubuntu:/aufs# mount -t aufs -o dirs=./a:./b:./c none ./mnt
root@jackzhu-ubuntu:/aufs# tree
.
├── a
│   ├── 1.txt
│   └── 2.txt
├── b
│   └── 2.txt
├── c
│   └── 3.txt
└── mnt
├── 1.txt
├── 2.txt
└── 3.txt

4 directories, 7 files
root@jackzhu-ubuntu:/aufs# cat ./mnt/2.txt
a dir file

root@jackzhu-ubuntu:/aufs# umount none
root@jackzhu-ubuntu:/aufs# mount -t aufs -o dirs=./b:./a:./c none ./mnt
root@jackzhu-ubuntu:/aufs# cat ./mnt/2.txt
b dir file
root@jackzhu-ubuntu:/aufs#

Aufs会根据mount命令的顺序最左边目录优先级越高,后面目录的文件就不会出现

overlay2

overlay2是一个类似于aufs的现代的联合文件系统,并且更快。overlay2已被收录进linux内核,它需要内核版本不低于4.0,如果是RHEL或Centos的话则不低于3.10.0-514。

overlay2结构:

image

如上,overlay2包括lowerdir,upperdir和merged三个层次,注意:overlay2 的文件系统下面的几个参数就是mount的固定参数, 其中:

  • lowerdir:表示较为底层的目录,修改联合挂载点不会影响到lowerdir, 类似容器镜像底层,只有只读权限
  • upperdir:表示较为上层的目录,修改联合挂载点会在upperdir同步修改,读写层,CoW到这个目录
  • merged:是lowerdir和upperdir合并后的联合挂载点,挂载展示
  • workdir:用来存放挂载后的临时文件与间接文件,runtime临时目标

overlay2 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
[root@k8s-master overlay2]# pwd
/root/go-test/overlay2
[root@k8s-master overlay2]# mkdir lower1 lower2 merged upper work
[root@k8s-master overlay2]# tree
.
├── lower1
├── lower2
├── merged
├── upper
└── work

5 directories, 0 files
[root@k8s-master overlay2]# echo "lower1 a file" > lower1/a
[root@k8s-master overlay2]# echo "lower2 b file" > lower2/b
[root@k8s-master overlay2]# echo "upper c file" > upper/c
[root@k8s-master overlay2]# tree
.
├── lower1
│   └── a
├── lower2
│   └── b
├── merged
├── upper
│   └── c
└── work

5 directories, 3 files

[root@k8s-master overlay2]# mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged
[root@k8s-master overlay2]# tree merged/
merged/
├── a
├── b
└── c

0 directories, 3 files
[root@k8s-master overlay2]# cat merged/a
lower1 a file
[root@k8s-master overlay2]# cat merged/b
lower2 b file
[root@k8s-master overlay2]# cat merged/c
upper c file
[root@k8s-master overlay2]#

[root@k8s-master overlay2]# echo "dddddd" > merged/d
[root@k8s-master overlay2]# tree
.
├── lower1
│   └── a
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── b
│   ├── c
│   └── d
├── upper
│   ├── c
│   └── d
└── work
└── work

6 directories, 8 files
[root@k8s-master overlay2]#

[root@k8s-master overlay2]# rm -f merged/d
[root@k8s-master overlay2]# tree
.
├── lower1
│   └── a
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── b
│   └── c
├── upper
│   └── c
└── work
└── work

6 directories, 6 files

[root@k8s-master overlay2]# echo "dddddd" > merged/d
[root@k8s-master overlay2]# tree
.
├── lower1
│   └── a
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── b
│   ├── c
│   └── d
├── upper
│   ├── c
│   └── d
└── work
└── work

6 directories, 8 files

[root@k8s-master overlay2]# rm -f upper/d
[root@k8s-master overlay2]# tree
.
├── lower1
│   └── a
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── b
│   └── c
├── upper
│   └── c
└── work
└── work

6 directories, 6 files


[root@k8s-master overlay2]# echo "lower1 aaaaaa" > lower1/aabb
[root@k8s-master overlay2]# tree
.
├── lower1
│   ├── a
│   └── aabb
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── aabb
│   ├── b
│   └── c
├── upper
│   └── c
└── work
└── work

6 directories, 8 files
[root@k8s-master overlay2]# echo "lower1 aaaaaabbbb" >> lower1/aabb
[root@k8s-master overlay2]# tree
.
├── lower1
│   ├── a
│   └── aabb
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── aabb
│   ├── b
│   └── c
├── upper
│   └── c
└── work
└── work

6 directories, 8 files
[root@k8s-master overlay2]# cat merged/aabb
lower1 aaaaaa
lower1 aaaaaabbbb
[root@k8s-master overlay2]# echo "lower1 aaaaaabbbbcccccc" >> merged/aabb
[root@k8s-master overlay2]# tree
.
├── lower1
│   ├── a
│   └── aabb
├── lower2
│   └── b
├── merged
│   ├── a
│   ├── aabb
│   ├── b
│   └── c
├── upper
│   ├── aabb
│   └── c
└── work
└── work

6 directories, 9 files
[root@k8s-master overlay2]# umount /root/go-test/overlay2/merged
[root@k8s-master overlay2]# tree
.
├── lower1
│   ├── a
│   └── aabb
├── lower2
│   └── b
├── merged
├── upper
│   ├── aabb
│   └── c
└── work
└── work

6 directories, 5 files

参考文献

  • https://www.infoq.cn/article/analysis-of-docker-file-system-aufs-and-devicemapper/
  • https://fuckcloudnative.io/posts/use-devicemapper/
  • https://coolshell.cn/articles/17061.html
  • https://coolshell.cn/articles/17200.html
  • https://ieevee.com/tech/2017/05/12/docker-dm.html
  • https://segmentfault.com/a/1190000008489207
  • https://staight.github.io/2019/10/04/%E5%AE%B9%E5%99%A8%E5%AE%9E%E7%8E%B0-overlay2/
  • https://docs.docker.com/storage/storagedriver/overlayfs-driver/
  • https://arkingc.github.io/2017/05/05/2017-05-05-docker-filesystem-overlay/

Linux cgroup go

发表于 2020-08-27 |

CGroup 介绍

Linux CGroup全称Linux Control Group, 是Linux内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)。这个项目最早是由Google的工程师在2006年发起(主要是Paul Menage和Rohit Seth),最早的名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词太过广泛,为避免混乱,被重命名为cgroup,并且被合并到2.6.24版的内核中去

概念及原理

cgroups子系统
cgroups为每种可以控制的资源定义了一个子系统。典型的子系统介绍如下:

  1. cpu 子系统,主要限制进程的 cpu 使用率
  2. cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告
  3. cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点
  4. memory 子系统,可以限制进程的 memory 使用量
  5. blkio 子系统,可以限制进程的块设备 io
  6. devices 子系统,可以控制进程能够访问某些设备
  7. net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制
  8. freezer 子系统,可以挂起或者恢复 cgroups 中的进程
  9. ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace

使用方法

查看系统已有cgroup列表两种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@localhost cgroup]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)

[root@localhost cgroup]# lssubsys -m
cpuset /sys/fs/cgroup/cpuset
cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
blkio /sys/fs/cgroup/blkio
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
pids /sys/fs/cgroup/pids
rdma /sys/fs/cgroup/rdma

查看系统开启了哪些cgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost cgroup]# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 8 1 1
cpu 7 48 1
cpuacct 7 48 1
blkio 10 48 1
memory 2 90 1
devices 12 48 1
freezer 9 1 1
net_cls 4 1 1
perf_event 6 1 1
net_prio 4 1 1
hugetlb 5 1 1
pids 3 58 1
rdma 11 1 1

手动mount memory cgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@localhost cgroup]# mkdir cgroup-demo
[root@localhost cgroup]# mkdir cgroup-demo/memory
[root@localhost cgroup]# mount -t cgroup -o memory memory ./cgroup-demo/memory
[root@localhost cgroup]# ls cgroup-demo/memory/
cgroup.clone_children memory.kmem.max_usage_in_bytes memory.memsw.limit_in_bytes memory.usage_in_bytes
cgroup.event_control memory.kmem.slabinfo memory.memsw.max_usage_in_bytes memory.use_hierarchy
cgroup.procs memory.kmem.tcp.failcnt memory.memsw.usage_in_bytes notify_on_release
cgroup.sane_behavior memory.kmem.tcp.limit_in_bytes memory.move_charge_at_immigrate release_agent
demo memory.kmem.tcp.max_usage_in_bytes memory.numa_stat system.slice
machine.slice memory.kmem.tcp.usage_in_bytes memory.oom_control tasks
memory.failcnt memory.kmem.usage_in_bytes memory.pressure_level user.slice
memory.force_empty memory.limit_in_bytes memory.soft_limit_in_bytes
memory.kmem.failcnt memory.max_usage_in_bytes memory.stat
memory.kmem.limit_in_bytes memory.memsw.failcnt memory.swappiness
[root@localhost ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
demo-memory on /go/cgroup/cgroup-demo/memory type cgroup (rw,relatime,seclabel,memory)

手动 mount cpu cgroup 报错,cpu already mounted or mount point busy. 解决办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost cgroup]# mkdir ./cgroup-demo/cpu
[root@localhost cgroup]# mount -t cgroup -o cpu cpu ./cgroup-demo/cpu
mount: /go/cgroup/cgroup-demo/cpu: cpu already mounted or mount point busy.
[root@localhost cgroup]# uname -r
4.18.0-193.el8.x86_64
[root@localhost cgroup]# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 8 1 1
cpu 7 48 1
cpuacct 7 48 1
blkio 10 48 1
memory 2 91 1
devices 12 48 1
freezer 9 1 1
net_cls 4 1 1
perf_event 6 1 1
net_prio 4 1 1
hugetlb 5 1 1
pids 3 58 1
rdma 11 1 1
[root@localhost cgroup]# cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)

关于以上这个报错,google半天没找到眉头思路,最终在 米开朗基杨 大佬的指导下得知Centos8系统cpu,cpuacct要合并一起使用,应该是新的系统systemd把两个合并了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost cgroup]# cat /etc/systemd/system.conf  |grep cpu
#JoinControllers=cpu,cpuacct net_cls,net_prio

[root@localhost cgroup]# mount -t cgroup -o cpu,cpuacct demo-cpu ./cgroup-demo/cpu
[root@localhost cgroup]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,rdma)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
demo-memory on /go/cgroup/cgroup-demo/memory type cgroup (rw,relatime,seclabel,memory)
demo-cpu on /go/cgroup/cgroup-demo/cpu type cgroup (rw,relatime,seclabel,cpu,cpuacct)

把当前bash进程ID加入到cgroup memory限制当中,限制100m内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@localhost memory]# mkdir ./cgroup-demo/memory/demo-test/
[root@localhost memory]# cd ./cgroup-demo/memory/demo-test/
[root@localhost demo-test]# ls
cgroup.clone_children memory.kmem.tcp.max_usage_in_bytes memory.oom_control
cgroup.event_control memory.kmem.tcp.usage_in_bytes memory.pressure_level
cgroup.procs memory.kmem.usage_in_bytes memory.soft_limit_in_bytes
memory.failcnt memory.limit_in_bytes memory.stat
memory.force_empty memory.max_usage_in_bytes memory.swappiness
memory.kmem.failcnt memory.memsw.failcnt memory.usage_in_bytes
memory.kmem.limit_in_bytes memory.memsw.limit_in_bytes memory.use_hierarchy
memory.kmem.max_usage_in_bytes memory.memsw.max_usage_in_bytes notify_on_release
memory.kmem.slabinfo memory.memsw.usage_in_bytes tasks
memory.kmem.tcp.failcnt memory.move_charge_at_immigrate
memory.kmem.tcp.limit_in_bytes memory.numa_stat
[root@localhost demo-test]# echo "100m" > memory.limit_in_bytes
[root@localhost demo-test]# cat memory.limit_in_bytes
104857600
[root@localhost demo-test]# echo $$ > tasks
[root@localhost demo-test]# cat tasks
1689
2239
[root@localhost demo-test]# ps aux |grep 1689
root 931 0.0 0.2 16892 2132 ? Ss 22:30 0:00 /usr/sbin/mcelog --ignorenodev --daemon --foreground
root 1689 0.0 0.7 27448 6088 pts/0 Ss 22:33 0:00 -bash
root 2254 0.0 0.1 12320 1084 pts/0 S+ 23:02 0:00 grep --color=auto 1689

#启动一个500m内存的测试程序
[root@localhost cgroup]# stress --vm-bytes 500m --vm-keep -m 1
stress: info: [2314] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

#可以使用htop查看stress程序的内存情况,永远不会超过100m内存的,因为我们限制的是当前的bash,产生的子进程一并限制,想想docker 内存限制是不是也是类型这样的实现

把进程ID加入到cgroup cpu限制 20% 的使用率

1
2
3
4
5
6
7
8
9
10
[root@localhost cgroup]# mkdir ./cgroup-demo/cpu/demo-cpu
[root@localhost cgroup]# echo 20000 > ./cgroup-demo/cpu/demo-cpu/cpu.cfs_quota_us
[root@localhost cgroup]# echo $$ > ./cgroup-demo/cpu/demo-cpu/tasks
[root@localhost cgroup]# cat ./cgroup-demo/cpu/demo-cpu/tasks
1689
2495
[root@localhost cgroup]# stress --vm-bytes 500m --vm-keep -m 1
stress: info: [2504] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd

#可以使用htop查看stress程序的cpu的使用情况,永远不会超过20%CPU的,因为我们限制的是当前的bash,产生的子进程一并限制,想想docker CPU限制是不是也是类型这样的实现

查看进程ID都有哪些cgroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ~]# cat /proc/2504/cgroup
12:devices:/system.slice/sshd.service
11:rdma:/
10:blkio:/system.slice/sshd.service
9:freezer:/
8:cpuset:/
7:cpu,cpuacct:/demo-cpu
6:perf_event:/
5:hugetlb:/
4:net_cls,net_prio:/
3:pids:/user.slice/user-0.slice/session-1.scope
2:memory:/demo-test
1:name=systemd:/user.slice/user-0.slice/session-1.scope
[root@localhost ~]#

go语言简单实现,github代码下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package main

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strconv"
"syscall"
)

const (
// 挂载了 memory subsystem的hierarchy的根目录位置
cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"
cgroupCPUHierarchyMount = "/sys/fs/cgroup/cpu"
)

func main() {

if os.Args[0] == "/proc/self/exe" {
//容器进程
fmt.Printf("current pid %d \n", syscall.Getpid())

cmd := exec.Command("sh", "-c", "stress --vm-bytes 500m --vm-keep -m 1")
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}

cmd := exec.Command("/proc/self/exe")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
panic(err)
}
// 得到 fork出来进程映射在外部命名空间的pid
fmt.Printf("%+v", cmd.Process.Pid)

cgroupMenory(cmd.Process.Pid)
cgroupCPU(cmd.Process.Pid)

cmd.Process.Wait()
}

func cgroupCPU(pid int) {
// 创建子cgroup
newCgroupCPU := path.Join(cgroupCPUHierarchyMount, "cgroup-demo-cpu")
os.Mkdir(newCgroupCPU, 0755)

// 将容器进程放到子cgroup中
if err := ioutil.WriteFile(path.Join(newCgroupCPU, "tasks"), []byte(strconv.Itoa(pid)), 0644); err != nil {
panic(err)
}
// 限制cgroup的CPU使用
if err := ioutil.WriteFile(path.Join(newCgroupCPU, "cpu.cfs_quota_us"), []byte("20000"), 0644); err != nil {
panic(err)
}
}

func cgroupMenory(pid int) {
// 创建子cgroup
newCgroupMemory := path.Join(cgroupMemoryHierarchyMount, "cgroup-demo-memory")
os.Mkdir(newCgroupMemory, 0755)

// 将容器进程放到子cgroup中
if err := ioutil.WriteFile(path.Join(newCgroupMemory, "tasks"), []byte(strconv.Itoa(pid)), 0644); err != nil {
panic(err)
}
// 限制cgroup的内存使用
if err := ioutil.WriteFile(path.Join(newCgroupMemory, "memory.limit_in_bytes"), []byte("100m"), 0644); err != nil {
panic(err)
}
}

参考文献

  • https://learnku.com/articles/42117
  • https://coolshell.cn/articles/17049.html
  • https://tech.meituan.com/2015/03/31/cgroups.html
  • https://segmentfault.com/a/1190000006917884

Linux NameSpace Go

发表于 2020-08-05 |

概念

Namespace是Linux内核对系统资源进行隔离和虚拟化的特性,这些系统资源包括进程ID、主机名、用户ID、网络访问、进程间通讯和文件系统等

当前Linux一共实现六种不同类型的namespace。

Namespace类型 系统调用参数 内核版本
UTS namespaces CLONE_NEWUTS 2.6.19
IPC namespaces CLONE_NEWIPC 2.6.19
PID namespaces CLONE_NEWPID 2.6.24
Network namespaces CLONE_NEWNET 2.6.29
User namespaces CLONE_NEWUSER 3.8
Mount namespaces CLONE_NEWNS 2.4.19

UTS Namespace

UTS namespace 功能最简单,它只隔离了 hostname 和 NIS domain name 两个资源。同一个 namespace 里面的进程看到的 hostname 和 domain name 是相同的,这两个值可以通过 sethostname(2) 和 setdomainname(2) 来进行设置,也可以通过 uname(2)、gethostname(2) 和 getdomainname(2) 来读取。

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

import (
"log"
"os"
"os/exec"
"syscall"
)

func main() {
cmd := exec.Command("sh")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS,
}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}

User Namesapce

User namespace 隔离的是用户和组信息,在不同的 namespace 中用户可以有相同的 UID 和 GID,它们之间互相不影响。另外,还有父子 namespace 之间用户和组映射的功能。父 namespace 中非 root 用户也能成为子 namespace 中的 root,这样就能增加安全性(如果所有 namespace 的 root 用户都是一样的,会带来子 namespace 操作父 namespace 内容的危险)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"log"
"os"
"os/exec"
"syscall"
)

func main() {
cmd := exec.Command("sh")

cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS |
syscall.CLONE_NEWUTS |
syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getuid(),
Size: 1,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getgid(),
Size: 1,
},
},
}


cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}

Mount Namespace

mount namespace 是用来隔离各个进程看到的挂载点视图。在不同namespace中的进程看到的文件系统层次是不一样的。在mount namespace 中调用mount()和umount()仅仅只会影响当前namespace内的文件系统,而对全局的文件系统是没有影响的。

PID namespace

PID namespace 隔离的是进程的 pid 属性,也就是说不同的 namespace 中的进程可以有相同的 pid。PID namespace 和我们常见的系统规则一样,都是从 pid 1 开始,每次 fork、vfork、clone 调用都会分配新的 pid。

PID namespace 第一个运行的进程的 pid 编号为 1,也被成为 init 进程。所有的孤儿进程(父进程被杀死)都会被 reparent 到 init 进程,如果 init 进程挂掉了,系统会发送 SIGKILL 信号给该 namespace 中的所有进程来杀死它们。由此可见,init 进程对于 PID namespace 至关重要,因此在容器中你可能听说过关于哪个程序最适合做 init 进程的争论。

PID namespace 另外一个特殊的特性是,通过 unshare 和 setns 系统调用都不会都不会把当前进程加入到新的 namespace,而是把该进程的子进程进入到里面。之所以这样设计,是因为 pid 是进程非常重要的信息,很多应用程序都会假定这个值不会变化,如果 unshare 或者 setns 把当前进程加入到新的 namespace 中,那么进程的 PID 将会发生变化,原来的 PID 也会被其他进程使用,会导致很多程序出现问题。

avatar

IPC namespaces

IPC 是进程间通信的意思,作用是每个 namespace 都有自己的 IPC,防止不同 namespace 进程能互相通信(这样存在安全隐患)。

IPC namespace 隔离的是 IPC(Inter-Process Communication) 资源,也就是进程间通信的方式,包括 System V IPC 和 POSIX message queues。每个 IPC namespace 都有自己的 System V IPC 和 POSIX message queues,并且对其他 namespace 不可见,这样的话,只有同一个 namespace 下的进程之间才能够通信。

下面这些 /proc 中内容对于每个 namespace 都是不同的:

/proc/sys/fs/mqueue 下的 POSIX message queues
/proc/sys/kernel 下的 System V IPC,包括 msgmax, msgmnb, msgmni, sem, shmall, shmmax, shmmni, and shm_rmid_forced
/proc/sysvipc/:保存了该 namespace 下的 system V ipc 信息
在 linux 下和 ipc 打交道,需要用到以下两个命令:

ipcs:查看IPC(共享内存、消息队列和信号量)的信息
ipcmk:创建IPC(共享内存、消息队列和信号量)的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package main

import (
"fmt"
"os"
"path/filepath"
"syscall"
"flag"
"github.com/docker/docker/pkg/reexec"
"os/exec"
)



func init() {

fmt.Printf("arg0=%s,\n",os.Args[0])

reexec.Register("initFuncName", func() {
fmt.Printf("\n>> namespace setup code goes here <<\n\n")

newRoot := os.Args[1]

if err := mountProc(newRoot); err != nil {
fmt.Printf("Error mounting /proc - %s\n", err)
os.Exit(1)
}

fmt.Printf("newRoot:%s \n",newRoot)
if err := pivotRoot(newRoot); err != nil {
fmt.Printf("Error running pivot_root - %s\n", err)
os.Exit(1)
}

nsRun() //calling clone() to create new process goes here
})

if reexec.Init() {
os.Exit(0)
}
}




func checkRootfs(rootfsPath string) {
if _, err := os.Stat(rootfsPath); os.IsNotExist(err) {
fmt.Printf("rootfsPath %s is not found you may need to download it",rootfsPath)
os.Exit(1)
}
}

//implement pivot_root by syscall
func pivotRoot(newroot string) error {

preRoot := "/.pivot_root"
putold := filepath.Join(newroot,preRoot) //putold:/tmp/ns-proc/rootfs/.pivot_root


// pivot_root requirement that newroot and putold must not be on the same filesystem as the current root
//current root is / and new root is /tmp/ns-proc/rootfs and putold is /tmp/ns-proc/rootfs/.pivot_root
//thus we bind mount newroot to itself to make it different
//try to comment here you can see the error
if err := syscall.Mount(newroot, newroot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
fmt.Printf("mount newroot:%s to itself error \n",newroot)
return err
}

// create putold directory, equal to mkdir -p xxx
if err := os.MkdirAll(putold, 0700); err != nil {
fmt.Printf("create putold directory %s erro \n",putold)
return err
}

// call pivot_root
if err := syscall.PivotRoot(newroot, putold); err != nil {
fmt.Printf("call PivotRoot error, newroot:%s,putold:%s \n",newroot,putold)
return err
}

// ensure current working directory is set to new root
if err := os.Chdir("/"); err != nil {
return err
}

// umount putold, which now lives at /.pivot_root
putold = preRoot
if err := syscall.Unmount(putold, syscall.MNT_DETACH); err != nil {
fmt.Printf("umount putold:%s error \n",putold)
return err
}

// remove putold
if err := os.RemoveAll(putold); err != nil {
fmt.Printf("remove putold:%s error \n",putold)
return err
}

return nil
}


func mountProc(newroot string) error {
source := "proc"
target := filepath.Join(newroot, "/proc")
fstype := "proc"
flags := 0
data := ""

os.MkdirAll(target, 0755)
if err := syscall.Mount(
source,
target,
fstype,
uintptr(flags),
data,
); err != nil {
return err
}

return nil
}




func nsRun() {
cmd := exec.Command("/bin/sh")

cmd.Env = []string{"PATH=/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin"}

//set identify for this demo
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr


if err := cmd.Run(); err != nil {
fmt.Printf("Error running the /bin/sh command - %s\n", err)
os.Exit(1)
}
}




func main() {

var rootfsPath string
flag.StringVar(&rootfsPath, "rootfs", "/tmp/ns-proc/rootfs", "Path to the root filesystem to use")
flag.Parse()

checkRootfs(rootfsPath)

cmd := reexec.Command("initFuncName",rootfsPath)

cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS |
syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getuid(),
Size: 1,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getgid(),
Size: 1,
},
},
}


if err := cmd.Run(); err != nil {
fmt.Printf("Error running the reexec.Command - %s\n", err)
os.Exit(1)
}

}

Network Namespace

Net namespace 隔离的是和网络相关的资源,包括网络设备、路由表、防火墙(iptables)、socket(ss、netstat)、 /proc/net 目录、/sys/class/net 目录、网络端口(network interfaces)等等。

一个物理网络设备只能出现在最多一个网络 namespace 中,不同网络 namespace 之间可以通过创建 veth pair 提供类似管道的通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// +build linux

package main

import (
"bytes"
"flag"
"fmt"
"github.com/docker/docker/pkg/reexec"
"os"
"os/exec"
"syscall"

"path/filepath"
"net"
"time"

)

func init() {

fmt.Printf("arg0=%s,\n",os.Args[0])

reexec.Register("initFuncName", func() {
fmt.Printf("\n>> namespace setup code goes here <<\n\n")

newRoot := os.Args[1]

if err := mountProc(newRoot); err != nil {
fmt.Printf("Error mounting /proc - %s\n", err)
os.Exit(1)
}

fmt.Printf("newRoot:%s \n",newRoot)
if err := pivotRoot(newRoot); err != nil {
fmt.Printf("Error running pivot_root - %s\n", err)
os.Exit(1)
}


if err := waitNetwork(); err != nil {
fmt.Printf("Error waiting for network - %s\n", err)
os.Exit(1)
}


nsRun() //calling clone() to create new process goes here
})

if reexec.Init() {
os.Exit(0)
}
}


func nsRun() {
cmd := exec.Command("sh")

cmd.Env = []string{"PATH=/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin"}
//set identify for this demo
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr


if err := cmd.Run(); err != nil {
fmt.Printf("Error running the /bin/sh command - %s\n", err)
os.Exit(1)
}
}

func main() {

var rootfsPath,netsetPath string

flag.StringVar(&netsetPath, "netsetPath", "./netsetter.sh", "Path to the netset shell")
flag.StringVar(&rootfsPath, "rootfs", "/tmp/ns-proc/rootfs", "Path to the root filesystem to use")
flag.Parse()

checkRootfs(rootfsPath)
checkNetsetter(netsetPath)

cmd := reexec.Command("initFuncName",rootfsPath)

cmd.Env = []string{"PATH=/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin"}
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS |
syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getuid(),
Size: 1,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getgid(),
Size: 1,
},
},
}



if err := cmd.Start(); err != nil {
fmt.Printf("Error starting the reexec.Command - %s\n", err)
os.Exit(1)
}

// run netsetgo using default args

pid := fmt.Sprintf("%d", cmd.Process.Pid)

//netsetCmd := exec.Command("whoami" ) //see current user , my result is ubuntu not root
netsetCmd := exec.Command("sudo",netsetPath, pid) //
var out bytes.Buffer
var stderr bytes.Buffer
netsetCmd.Stdout = &out
netsetCmd.Stderr = &stderr

if err := netsetCmd.Start(); err != nil {
fmt.Printf("Error running netsetg:%s, stderr:%s, stdout:%s",fmt.Sprint(err),stderr.String(),out.String())
os.Exit(1)
}
fmt.Printf("run netsetter: stdout:%s \n",out.String())

if err := cmd.Wait(); err != nil {
fmt.Printf("Error waiting for the reexec.Command - %s\n", err)
os.Exit(1)
}

}



func checkNetsetter(netsetPath string) {
if _, err := os.Stat(netsetPath); os.IsNotExist(err) {
errMsg := fmt.Sprintf(`file %s not found! you must have a netsetter binary or shell and run with root privilege,
or run with argument -netsetPath path_to_your_netsetter
`, netsetPath)
fmt.Println(errMsg)
os.Exit(1)
}
}


func waitNetwork() error {
maxWait := time.Second * 60
checkInterval := time.Second
timeStarted := time.Now()

for {
fmt.Printf("status: waiting network ...\n")
interfaces, err := net.Interfaces()
if err != nil {
return err
}

if len(interfaces) > 1 {
return nil
}

if time.Since(timeStarted) > maxWait {
return fmt.Errorf("Timeout after %s waiting for network", maxWait)
}

time.Sleep(checkInterval)
}
}


func checkRootfs(rootfsPath string) {
if _, err := os.Stat(rootfsPath); os.IsNotExist(err) {
fmt.Printf("rootfsPath %s is not found you may need to download it",rootfsPath)
os.Exit(1)
}
}

//implement pivot_root by syscall
func pivotRoot(newroot string) error {

preRoot := "/.pivot_root"
putold := filepath.Join(newroot,preRoot) //putold:/tmp/ns-proc/rootfs/.pivot_root


// pivot_root requirement that newroot and putold must not be on the same filesystem as the current root
//current root is / and new root is /tmp/ns-proc/rootfs and putold is /tmp/ns-proc/rootfs/.pivot_root
//thus we bind mount newroot to itself to make it different
//try to comment here you can see the error
if err := syscall.Mount(newroot, newroot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
fmt.Printf("mount newroot:%s to itself error \n",newroot)
return err
}

// create putold directory, equal to mkdir -p xxx
if err := os.MkdirAll(putold, 0700); err != nil {
fmt.Printf("create putold directory %s erro \n",putold)
return err
}

// call pivot_root
if err := syscall.PivotRoot(newroot, putold); err != nil {
fmt.Printf("call PivotRoot error, newroot:%s,putold:%s \n",newroot,putold)
return err
}

// ensure current working directory is set to new root
if err := os.Chdir("/"); err != nil {
return err
}

// umount putold, which now lives at /.pivot_root
putold = preRoot
if err := syscall.Unmount(putold, syscall.MNT_DETACH); err != nil {
fmt.Printf("umount putold:%s error \n",putold)
return err
}

// remove putold
if err := os.RemoveAll(putold); err != nil {
fmt.Printf("remove putold:%s error \n",putold)
return err
}

return nil
}


func mountProc(newroot string) error {
source := "proc"
target := filepath.Join(newroot, "/proc")
fstype := "proc"
flags := 0
data := ""

os.MkdirAll(target, 0755)
if err := syscall.Mount(
source,
target,
fstype,
uintptr(flags),
data,
); err != nil {
return err
}

return nil
}

Centos7 系统默认没有开启NameSpases,以下方式开启

1
echo 640 > /proc/sys/user/max_user_namespaces

参考代码

  • https://github.com/xiaoJack/linux-namespace-go

参考文献

  • https://developer.aliyun.com/article/64928
  • https://here2say.com/41/
  • https://images.contentstack.io/v3/assets/blt300387d93dabf50e/bltb6200bc085503718/5e1f209a63d1b6503160c6d5/containers-vs-virtual-machines.jpg

Go项目使用Make工具编译

发表于 2019-08-10 |

简单的理解Make命令就是执行Makefile文件里面的shell命令,只是给出来了一套标准规范方便管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
$ cat Makefile
# Go parameters
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
BINARY_NAME=admin
BINARY_MAC=$(BINARY_NAME)_mac
BINARY_UNIX=$(BINARY_NAME)_unix
BINARY_WIN=$(BINARY_NAME)_win.exe


all: run

default:
$(GOBUILD) -o $(BINARY_NAME) -v

build-mac:
$(GOBUILD) -o $(BINARY_MAC) -v

build-linux:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v

build-win:
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BINARY_WIN) -v

test:
$(GOTEST) -v ./...
clean:
$(GOCLEAN)
rm -f $(BINARY_NAME)
rm -f $(BINARY_UNIX)
rm -f $(BINARY_WIN)
rm -f $(BINARY_MAC)
run:
$(GOBUILD) -o $(BINARY_NAME) -v
./$(BINARY_NAME)

学习参考

https://www.gnu.org/software/make/manual/make.html

http://www.ruanyifeng.com/blog/2015/02/make.html

go 错误总结

发表于 2019-08-10 |

指针引用类型错误

1
cannot use user (type common.User) as type *common.User in argument to common.UserService.UpdateUser

解决办法

1
x := &common.User{}

基础没学好还是要多看书啊:(

参考 stackoverflow


项目中Makefile报错,看问题是制表符的问题,这个配置文件需要制表符而不是空格

1
Makefile:14: *** missing separator (did you mean TAB instead of 8 spaces?).  Stop.

解决办法

使用vi打开Makefile,把所有的空格替换成制表符 “\t”

1
:%s/^[ ]\+/\t/g

参考 https://unix.stackexchange.com

Beego Docker-compose部署报views找不到

发表于 2019-08-10 |

Beego Docker-compose部署报 Handler crashed with error can’t find templatefile in the path:views/user/index.html

解决办法
docker-compose.yml 里面增加working_dir目录,类似cd到这个目录里面后在启动

1
working_dir: '/admin'

参考官方文档
https://docs.docker.com/compose/compose-file/#domainname-hostname-ipc-mac_address-privileged-read_only-shm_size-stdin_open-tty-user-working_dir

Golang生产环境中time包的zonefile.zip问题

发表于 2019-08-10 |

docker 环境运行编译好的go文件

1
open /usr/local/go/lib/time/zoneinfo.zip: no such file or directory

解决办法 Dockerfile 里面增加COPY

1
COPY ./zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip

当然./zoneinfo.zip 来自本地系统 /usr/local/go/lib/time/zoneinfo.zip

go mod 使用

发表于 2019-08-10 |

Golang 1.11 版本引入的 go mod,之前一直在使用go get方式管理包

本地开发环境情况说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
➜ /Users/jack/go/gomod/admin git:(master)>go env
GOARCH="amd64"
GOBIN="/Users/jack/go/bin"
GOCACHE="/Users/jack/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/jack/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/jack/go/gomod/admin/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/np/ks8717cn2_q91yhkw6gz08bm0000gn/T/go-build544492236=/tmp/go-build -gno-record-gcc-switches -fno-common"

之前项目都在 ~/go/src/里面,所有的包也是src目录里面


现有项目迁移

  1. 把之前的工程 src/admin 目录拷贝到$GOPATH/src之外)
  2. 在工程目录下执行 go mod init admin 该命令会创建一个go.mod文件
  3. 然后在该目录下执行 go build ,就可以了。你将看到:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
➜ /Users/jack/go/gomod/admin git:(dev)>make run
go build -o admin -v
go: finding github.com/go-sql-driver/mysql v1.4.1
go: finding github.com/astaxie/beego v1.12.0
go: finding github.com/pkg/errors v0.8.1
go: finding github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644
go: finding github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58
go: finding github.com/Knetic/govaluate v3.0.0+incompatible
go: finding github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737
go: finding github.com/OwnLocal/goes v1.0.0
go: finding github.com/go-redis/redis v6.14.2+incompatible
go: finding github.com/casbin/casbin v1.7.0
go: finding github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb
go: finding github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd
go: finding github.com/mattn/go-sqlite3 v1.10.0
go: finding github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542
go: finding github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726
go: finding github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b
Fetching https://golang.org/x/net?go-get=1
go: finding github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c
go: finding github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a
go: finding github.com/elazarl/go-bindata-assetfs v1.0.0
go: finding github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200)
get "golang.org/x/net": found meta tag get.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at https://golang.org/x/net?go-get=1
go: finding golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
go: finding github.com/gogo/protobuf v1.1.1
go: finding github.com/lib/pq v1.0.0
go: finding github.com/gomodule/redigo v2.0.0+incompatible
go: finding github.com/pelletier/go-toml v1.2.0
go: finding github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec
go: finding github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c
go: finding github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712
go: finding github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76
Fetching https://golang.org/x/crypto?go-get=1
Parsing meta tags from https://golang.org/x/crypto?go-get=1 (status code 200)
get "golang.org/x/crypto": found meta tag get.metaImport{Prefix:"golang.org/x/crypto", VCS:"git", RepoRoot:"https://go.googlesource.com/crypto"} at https://golang.org/x/crypto?go-get=1
go: finding golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
go: finding github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db
go: finding github.com/pkg/errors v0.8.0
Fetching https://gopkg.in/yaml.v2?go-get=1
go: finding github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d
Parsing meta tags from https://gopkg.in/yaml.v2?go-get=1 (status code 200)
get "gopkg.in/yaml.v2": found meta tag get.metaImport{Prefix:"gopkg.in/yaml.v2", VCS:"git", RepoRoot:"https://gopkg.in/yaml.v2"} at https://gopkg.in/yaml.v2?go-get=1
go: finding gopkg.in/yaml.v2 v2.2.1
Fetching https://gopkg.in/check.v1?go-get=1
Parsing meta tags from https://gopkg.in/check.v1?go-get=1 (status code 200)
get "gopkg.in/check.v1": found meta tag get.metaImport{Prefix:"gopkg.in/check.v1", VCS:"git", RepoRoot:"https://gopkg.in/check.v1"} at https://gopkg.in/check.v1?go-get=1
go: finding gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
go: downloading github.com/astaxie/beego v1.12.0
go: downloading github.com/pkg/errors v0.8.1
go: downloading github.com/go-sql-driver/mysql v1.4.1
go: downloading github.com/gomodule/redigo v2.0.0+incompatible
go: downloading github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644
go: downloading golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
go: downloading gopkg.in/yaml.v2 v2.2.1
./admin
2019/08/06 11:41:20.004 [I] [router.go:270] /Users/jack/go/src/admin/controllers no changed
2019/08/06 11:41:20.005 [I] [router.go:270] /Users/jack/go/src/admin/controllers no changed
2019/08/06 11:41:22.070 [I] [asm_amd64.s:1333] http server Running on http://:80
2019/08/06 11:41:22.070 [I] [asm_amd64.s:1333] Admin server Running on :8088
可以看到会自动去下载相对应的包

这个时候看项目目录里面自动创建两个文件 go.mod go.sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
➜ /Users/jack/go/gomod/admin git:(dev) ✗>cat go.mod go.sum 
module admin

require (
github.com/astaxie/beego v1.12.0
github.com/go-sql-driver/mysql v1.4.1
github.com/pkg/errors v0.8.1
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
)
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM=
github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y=
github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
下载下来的包放在 $GOPATH/pkg/mod 目录里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
➜ /Users/jack/go/pkg/mod >tree -L 2 $GOPATH/pkg/mod
/Users/jack/go/pkg/mod
├── cache
│   ├── download
│   └── vcs
├── github.com
│   ├── astaxie
│   ├── go-sql-driver
│   ├── gomodule
│   ├── pkg
│   └── shiena
├── golang.org
│   └── x
└── gopkg.in
└── [email protected]

13 directories, 0 files

学习参考

using-go-modules

Golang官方包依赖管理工具 go mod 简明教程

12

JackZhu

13 日志
3 标签
GitHub E-Mail
© 2021 JackZhu
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4