0%

同步三要素

简介

在我之前写过一篇lockfree的文章,里面提到了CAS和内存屏障以及之间的关系,这里先总结一下。

lockfree技术点关系图

lockfree主要是通过一定的编程规范来避免线程之间流程阻塞。不阻塞并不难,难的是不阻塞的基础上确保结果的正确性。

图中有两条重要的路线,一条是并发写,写的过程是一个Read-Modify-Write的过程,不是单步的,因此为了确保正确性,需要一个把RMW做成原子操作,于是CAS就出场了,这是一个处理器支持的操作,在汇编级别支持RMW原子性。

另一条线是多核指令顺序一致性,cpu为了性能,会做指令乱序,这种乱序在同一cpu的程序看来是感知不到的(cpu会自己保证这一点),但是会被其他cpu上的程序感知到,带来异常,为了消除这种乱序,我们需要使用经过封装的原子变量类型,或者使用内存屏障。如果要防止乱序的场景是一个线程写一个值,一个线程读一个值,那么我们通过acquire和release两个轻量屏障就可以实现了,别的场景有可能需要使用全能内存屏障,别的场景是什么呢?比如生产者生产的同时还消费,消费者消费的同时还生产,这就比较复杂,可能需要全能屏障来解决。

那么这两条路线的交集是什么呢,交集是多核执行RMW指令序列时,为了顺序一致性,除了要做到顺序一致,还需要保证结果正确。这个时候就需要CAS配合内存屏障了。

总结完之后,这一文,主要是要展开讲一下这里面更本质的东西,cpu乱序的本质是什么,内存屏障的本质是什么,cas的本质是什么,并延伸到锁的本质是什么。涉及到的技术主要是3点,包括:缓存一致性协议,内存屏障,锁。这三点可总结为同步三要素。

阅读全文 »

linux namespace

linux通过namespace技术为进程提供虚拟视图,这项技术是容器的基础。本文主要介绍每个namespace的实现原理,但很可能不对技术本身做探讨。比如会讨论如何实现cgroup的虚拟视图,但不会研究cgroup的控制器的实现原理。
目前在内核(v5.19-rc2,为写作之日的最新版本)中已经支持的namespace有8个。

1
2
3
4
5
6
7
8
9
10
11
https://github.com/torvalds/linux/blob/v5.19-rc2/include/linux/nsproxy.h#L31
uts namespace
ipc namespace
mnt namespace
pid namespace
net namespace
time namespace
cgroup namespace

https://github.com/torvalds/linux/blob/v5.19-rc2/include/linux/user_namespace.h#L66
user namespace

这些namespace起作用的场景是3个系统调用:

  1. clone, 他接收namespace等参数,并完成fork进程的功能。
    https://man7.org/linux/man-pages/man2/clone.2.html
  2. unshare,他创建新的namespace,并把本进程放到新namespace内。
    https://man7.org/linux/man-pages/man1/unshare.1.html
  3. setns, 把当前进程加入到某些指定的namespace。
    https://man7.org/linux/man-pages/man2/setns.2.html

在分别讲每一个namespace之前,我们大致了解下内核代码的相关结构。

阅读全文 »

CKS知识总结

简介

CKS考试是kubernetes认证系列中中高级的一个证书,相比CKAD和CKA难度略大一些。

一方面虽然CKS跟CKA和CKAD有部分交集,比如k8s的基本使用,RBAC/secret的使用,集群升级等知识点,另一方面又是基于CKA的基础之上,考试也要求先通过CKA。

第二个相对难考的地方在于CKA和CKAD的考点都在kubernetes官网可以找到,但是CKS的很多知识点跳到了外部,涉及到外部的工具,外部的插件等等。

第三个点是CKS的部分知识点需要自己做一定的探索,换句话说不操作一遍的话都不知道他是什么,他涉及到什么知识。

同时,CKAD、CKA、CKS相同的点是都有前人为我们列好了考试大纲,列好了知识点和链接:CKA/CKAD/CKS

这里主要基于第3个难点,对涉及到的操作做一个细致的记录,方便大家参考,减少学习者的探索时间。

阅读全文 »

简介

挂载一个hostPath的volumes的时候,需要设置挂载方式为read only, 不然存在安全风险。

1
2
3
4
5
6
7
8
9
10
11
12
volumes:
- name: test-volume
hostPath:
path: /data
type: Directory
这时定义hostpath的volume,定义的时候没有readonly选项。

volumeMounts:
- name: test-volume
mountPath: /test-volume
readOnly: true

在官方文档中有这么一段话

1
2
3
4
5
https://kubernetes.io/docs/concepts/storage/volumes/#hostpath

Warning:
HostPath volumes present many security risks, and it is a best practice to avoid the use of HostPaths when possible. When a HostPath volume must be used, it should be scoped to only the required file or directory, and mounted as ReadOnly.

简单讲就是在强调你在挂载hostpath的volume的时候必须设置readOnly,且必须约束可以挂载的目录。

设置readOnly是因为,已经证明存在一些方法来绕过约束。比如我配置了hostPath不能访问A目录,但是我可以通过挂载B目录间接访问A目录;或者我配置了hostPath只能访问A目录,但是我可以通过A目录,间接访问到B目录。是不是很神奇。

下面演示两个例子,来说明不设置readOnly和不约束可挂载目录所带来的隐患。

阅读全文 »

简介

k8s中内置了一种安全策略,能够用来约束pod的行为,他叫PodSecurityPolicy,位于apiserver中,默认被关闭。psp定义了哪些是能做的,他的作用范围大都是在securityContext这个结构中,其他也有,比如可以定义哪些volume是支持的,定义哪些端口是允许的。他通过限制这些结构来达到约束pod的目的。

但是psp是一个即将被废弃的功能,如果你看到文章的时候k8s的版本已经出到了v1.25了那么你可以不用看这部分了,根据官方文档,psp会在v1.25被彻底拿掉。至于psp的继任者Pod Security Admission我会在后续补上,当前我本地安装的k8s版本还不能使用,要v1.22才能使用。

1
2
3
https://kubernetes.io/docs/concepts/policy/pod-security-policy/

PodSecurityPolicy is deprecated as of Kubernetes v1.21, and will be removed in v1.25. It has been replaced by Pod Security Admission.

我们来了解一下这个功能,并演示以下如何开启并使用他。

阅读全文 »

k8s中的危险权限

简介

本文列举了k8s中几个危险的权限,危险的权限是什么意思呢,就是说如果某个低权限的用户,因为其拥有某个特殊的权限,那么他就可以通过一些操作来提升自己的权限,从而破坏系统的权限控制体系,并产生意料之外的破坏,以及导致数据的泄露等。

这部分知识比较偏向攻击层面,而不是防守层面。我们通过一步步的手把手的操作,来观察如何突破权限,从而带来隐患的。

存在权限风险的操作主要有4个:bind,escalate,impersonate,create pod,我们一一来分析和测试。

阅读全文 »

树莓派搭建k8s集群

简介

我们有很多时候需要搭建一个k8s集群,可能为了开发,可能为了测试,可能为了学习。在选择机器上可以有几个选择,一个是使用几台PC,一个是开几个虚拟机,一个是使用几个轻量的开发板。这里我主要以学习为目的,并且选择使用轻量开发板的方式,并且以最常用的树莓派作为代表,来搭建一个k8s集群。

由于主要以学习考试为主,这里暂时不考虑搭建单机集群的方式,因为没法接触到涉及多个节点的操作场景。

同时也不考虑k3s,我知道k3s是专为轻量设备而设计的,功能跟k8s基本是一样的,但是k3s和k8s只是功能基本相同,但是在使用和配置上,都有很多不同,并且使用的是完全分开的文档。k8s在使用上经常需要查在线文档的,即便对k3s的文档很熟悉了,在使用k8s的时候还是会存在查不到k8s文档的情况。因此学习k8s的时候,用k3s做练习是不合适的。

至于选择树莓派作为载体是个人选择,你可以选择使用pc或者选择使用虚拟机。如果你选择使用pc或者虚拟机,那么下面的章节中,【刷写系统】和【配置系统】会有差异,仅供参考。再之后的步骤则都是通用的。

最终我们将完成以下集群的搭建:

cluster-device

一个路由器,3个树莓派,树莓派通过wifi连接路由器,3个树莓派中一个master node,两个worker node。

阅读全文 »

raft 协议解析

高可用的实现方案总结

在工程实践中,高可用的方案有很多,例举几个,大家一定知道大部分的名词:主备,双备,HAProxy, F5, VRRP,Sentinel, gossip, paxos等。

这一系列的技术方案,可以简化和归纳成两类:一类可以叫哨兵, 如图以haproxy为例:

haproxy

图片来自 Using HAProxy with the Proxy Protocol to Better Secure Your Database

这一类方案,有个共同点,都有一个类似哨兵的角色,细化的讲,有的叫proxy,有的叫gateway,有的叫monitor,有的叫sentinel,有的叫router,他们的作用都是类似的,就是感知并屏蔽内部不健康的主机,并对外提供一个始终可用的服务。完成高可用的目的。

另一类技术方案,可以叫一致性协议,如图基于gossip的redis cluster为例:

rediscluster

图片来自 微服务

这一类方案,也有一个共同点,就是都实现了一种内部节点之间的通信协议,协议能够发现异常节点,并且在内部通过某种机制来容错,不需要依赖外部的角色。 一致性协议的实现高可用的机制,最重要的一点就是半数同意的机制,因此能容忍半数-1的节点宕机。

阅读全文 »

不同形式的锁

最近发现锁的类型真是多种多样,好多还是第一次见,我就在这里记录一下。

RCU

RCU即read-copy-update.一种不阻塞读线程,只阻塞写线程的同步方式。

写线程如果有多个要自己做好互斥,一个时间只能有一个写线程。写线程严格执行R-C-U三步操作,但在第三步操作完的时候,因为把原来的值给更新掉了,原来旧的值就需要释放,那么持有了原来旧的值的读线程必须全部操作完成才行。这里所说的操作的旧值新值都是指针,只有指针才可以直接的确保原子性。

所以这里有个关键步骤是synchronize_rcu(),位于U之后和释放旧指针之前。synchronize_rcu的底层实现我不懂,它的原理大概是说等待所有cpu都调度一遍,就可以确保旧的读线程都操作完成了。为什么都调度一遍就可以确保都操作完了呢?因为所有的读操作都要求添加以下语句:

1
2
3
4
5
6
rcu_read_lock(); // 禁止抢占
p = rcu_dereference(gp); // rcu_dereference主要是加内存屏障
if (p != NULL) {
do_something_with(p->a, p->b, p->c);
}
rcu_read_unlock(); // 允许抢占
阅读全文 »