k8s调研学习方向

网络 flannel/calico 组网和calico的ipam的实现 cilium 全面了解这个网络插件 metallb 全面了解这个网络插件,底层实现 kube-proxy 主要关注iptables规则,ipset和ipvs的使用 存储 csi 关注这个接口的具体定义实现 juicefs 关注源码层面的原理 glusterfs 试用与了解 minio 源码级研究 etcd 源码级研究 核心组件 kube-apiserver 主要关注存储的实现,性能与横向扩展 kubelet 主要关注底层创建pod的完整流程,包括cgroup,存储,cri,网络配置等 kube-scheduler 主要看调度的整体流程,以及基于批的调度实现 扩展组件 client-go 关注list-watch机制,informer原理,apiserver端的实现 operator和controller-runtime库,以及自己实现一个operator,并支持api多版本…

kube-apiserver代码分析 – API多版本再探

在上一篇API多版本初探中已经描述了API资源是如何注册到路由当中,并大概介绍了api请求如何根据优先版本存储到etcd中。本篇文章主要介绍apiserver如何将etcd中的数据按照按照用户指定的版本返回。在探究同一个api group下不同版本资源兼容问题之外,还会探究不同api group下的不同资源是如何做到兼容的。本文基于kubernetes-1.19.11版本。 本文关注的重点内容有: apiserver如何读取解析etcd中的数据 apiserver怎么返回用户指定的版本资源 不同api group怎么做到兼容,如netwoking.k8s.io/v1beta1下的Ingress资源和extensions/v1beta1下的Ingress资源 在阅读源码过程中在protobuf处理关键位置加入了打印堆栈日志,先找出整个函数调用链,再根据链路分析整个执行流程会更容易的多,具体修改和打印的栈信息可在后面的附录中查看。 首先看pkg/master/import_known_versions.go这个文件,该文件中有许多xxx/install的import。 import ( // These imports are the API groups the API server will support. _ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admissionregistration/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" ... 来看下这个文件的作用。从文件的名称来看就知道是导入所有的所有已知的api版本,在install包的init函数中会将所有已定义的资源加载到runtime schema中,主要包含下面两类: 在pkg/apis/${group}/types.go中定义的类型 在k8s.io/api/${group}/${version}/types.go中定义的 第1类资源代表了内部版本的表示方法,是在etcd中读取的数据(或api请求数据)反序列化之后的表示类型。第2类资源为不同的group下特定version的类型,在写入etcd的(或api响应的)数据是根据特定group/version下的类型进行序列化的。 在install中有一个SetVersionPriority()方法,用来设置写入etcd是优先使用的版本。当然第2类资源在注册的时候还会加上一些设置默认值和转换到内部版本或从内部版本转换的方法来实现内部版本和外部版本之间的互相转换。这些方法的定义在pkg/apis/${group}/${version}下的default.go,conversion.go,zz_generated.xxx.go中定义的。 接下来以Ingress资源来说明转换的读取,转换,返回响应的流程,首先通过加日志查看当前runtime schema中可以看到有三个Ingress类型的定义。分别对应的是extensions/v1beta1,networking.k8s.io/v1beta1和内部版本,可以看到内部版本有两个,分别是extensions/__internal和networking.k8s.io/__internal,但是其他俩对应的实际类型都是pkg/apis/networking/types.go中定义的Ingress类型。 再来回顾一下加载api资源注册路由部分,有一个InstallAPIs()方法,该参数中的networkingrest.RESTStorageProvider{}来自pkg/registry/networking/rest/storage_settings.go中定义的storage接口,这个storage接口设置了ingresses资源对应ingressStorage。 func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (map[string]rest.Storage, error) { storage := map[string]rest.Storage{} // ingresses ingressStorage, ingressStatusStorage, err := ingressstore.NewREST(restOptionsGetter) if err != nil { return storage, err } storage["ingresses"]…

kube-apiserver代码分析 – API多版本初探

kube-apiserver是k8s中最为核心的组件,对外暴露restful接口,实现对集群中各种资源的增删改查操作。kubelet,kube-*组件都通过apiserver获取自己感兴趣的资源做处理,系统中所有的组件都只负责自己的部分,最终会促使各种资源到达期望的状态。 apiserver中的api资源是由组构成的,叫做ApiGroup,如apps,extensions,每个资源组下面又有不同类型的资源,称为Kind,如Deployment。每个分组下还会有不同的版本,在相同分组的不同版本下面相同的Kind的资源可能会有字段的增删等变更。因此apiserver需要能够正确处理不同版本资源之间的兼容性处理。从废弃策略的规则#2中可以了解到,不同版本之间的资源可以互相转换,并且不丢失任何信息,接下来分析apiserver是具体怎么实现的。本文基于k8s的1.8.0版本版本的代码。 首先看下apiserver的启动流程,下面是关键的函数调用链,去掉了不相关代码和条件判断等。 main() cmd/kube-apiserver/apiserver.go:31 NewAPIServerCommand() cmd/kube-apiserver/app/server.go:99 Run() cmd/kube-apiserver/app/server.go:151 CreateServerChain() cmd/kube-apiserver/app/server.go:169 # 返回的config.ExtraConfig中保存了 CreateKubeAPIServerConfig() cmd/kube-apiserver/app/server.go:273 buildGenericConfig() cmd/kube-apiserver/app/server.go:417 # 加载当前版本默认api资源配置 genericConfig.MergedResourceConfig = master.DefaultAPIResourceConfigSource() cmd/kube-apiserver/app/server.go:431 # 将参数配置中的--runtime-config应用到默认api资源配置 s.APIEnablement.ApplyTo(genericConfig, master.DefaultAPIResourceConfigSource(), legacyscheme.Scheme) cmd/kube-apiserver/app/server.go:449 # 设置存储端后端对应的api操作接口 storageFactoryConfig.APIResourceConfig = genericConfig.MergedResourceConfig # 将apiserver和storage的配置返回了,就是说apiserver和storage都是使用的用户自定义的配置对默认配置进行覆盖后的配置 # 返回的 genericConfig.MergedResourceConfig保存了api启用配置,类型为*storage.ResourceConfig # 返回的storageFactory.APIResourceConfig也保存了api启用配置,类型也为*storage.ResourceConfig CreateKubeAPIServer() cmd/kube-apiserver/app/server.go:191 kubeAPIServerConfig.Complete().New() cmd/kube-apiserver/app/server.go:219 # 启用legacy api(/api/v1) m.InstallLegacyAPI() pkg/master/master.go:405 # 启用api(/apis/{groupn}) m.InstallAPIs() m.GenericAPIServer.InstallAPIGroups() pkg/master/master.go:553 `` 其中m.InstallLegacyAPI()是安装旧版接口,及/api/v1接口路径下的接口。我们主要看m.InstallAPIS这部分,这部分安装的接口的都在/apis/{group}路径下。 现在深入到InstallAPIGroups()这个函数中以及看后面的详细调用链。 // Exposes given api groups in the API. func (s *GenericAPIServer) InstallAPIGroups(apiGroupInfos…

linux配置多级服务器登录和隧道映射

通常,办公环境的电脑无法直接连接到开发测试服务器,往往需要进行多次ssh跳转。这时可通过配置ssh支持自动跳转登录功能。假设有2台服务器A和B在我们的开发测试环境中,本地我们只能连接到A服务器,而A服务器可以连接到B服务器。编辑~/.ssh/config文件,输入下列内容: Host serverA User root HostName 10.1.2.3 IdentityFile /home/myusername/.ssh/id_rsa Port 22 Host serverB User root HostName 192.168.1.3 IdentityFile /home/myusername/.ssh/id_rsa port 22 ProxyJump serverA 此时,在本地即可通过ssh serverB直接连接到服务器B的ssh服务。如果要使用免密方式进行登录。则需要执行ssh-copy-id root@serverA和ssh-copy-id root@serverB进行配置免密。 在进行了这样的配置之后,我们也可以更加方便的建立隧道来使用了。比如在服务器上有一个8080的http服务,想要在本地浏览器里访问。那么,可以执行下列命令建立隧道: ssh -Nf -Llocalhost:8080:192.168.1.3:8080 serverB 此命令会将serverB上的8080端口映射到本地的8080端口上,在浏览器中我们只需要输入http://localhost:8080可以访问到serverB上的web服务。 同样,也可以通过-R选项将本地端口映射到serverB的某个端口上,比如在微信公众号开发测试的时候,可以将配置在公众平台的服务器地址上的服务映射到本地。 为了避免网络连接中断导致隧道断开连接,可以使用systemd来保活,隧道进程退出后自动将其拉起: # cat /lib/systemd/system//my-http-proxy.service [Unit] Describe=my nginx agent After=network.target [Service] LimitNOFILE=10000 Type=simple User=root Group=root ExecStart=/usr/bin/ssh -oExitOnForwardFailure=yes -oPubkeyAuthentication=yes -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oServerAliveInterval=5 -oServerAliveCountMax=3 -Llocalhost:8080:localhost:8080 -N root@serverB Restart = always RestartSec = 1s StartLimitInterval = 0 [Install] WantedBy=network.target 执行systemctl…

linux下tcp keepalive相关参数调整测试

首先说明下面三个和keepalive相关的内核参数以及默认的值 # sysctl -a | grep keepalive # 在会后一次发送数据包后多久向对方发起探测 net.ipv4.tcp_keepalive_time = 7200 # 在没有收到对方确认时,会按照这个时间间隔再次探测 net.ipv4.tcp_keepalive_intvl = 75 # 在没有收到对方确认时,进行探测的次数 net.ipv4.tcp_keepalive_probes = 9 下面通过在本地环境上测试这些参数,首先将本地的默认keepalive参数进行修改 # sysctl -a | grep keepalive net.ipv4.tcp_keepalive_intvl = 5 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_time = 20 下载并编译带keepalive功能支持的netcat命令行工具 git clone https://github.com/cyberelf/netcat-keepalive.git cd netcat-keepalive/ make linux 运行tcpdump进行抓包 tcpdump -iany port 18888 启动服务端监听 ./nckl-linux -v4K -l 18888 使用nc去连接 nc -v -p55666 localhost 18888 可以看到抓包内容如下 root@debian:/home/blue# tcpdump -iany port 18888 tcpdump: verbose…

记一次k8s集群pod一直terminating问题的排查

现象描述 pod一直处于terminating状态,或者很久才能删除,内核日志中持续打印unregister_netdevice: waiting for XXX to become free. Usage count = 1。 故障诊断 经过定位和排查,定位到是内核的一个bug导致网络设备无法删除。具体参考https://github.com/torvalds/linux/commit/ee60ad219f5c7c4fb2f047f88037770063ef785f。 另外在github的k8s的issues里也有该bug的相关讨论。有人给出了付现这个问题的方式,以及验证上面提到的修复方法是否有效。下面是按照他给出的方案做的复现和验证。具体可参考https://github.com/moby/moby/issues/5618#issuecomment-549333485。 问题排查 从kubelet内核日志来看是在删除pod的网卡设备时因为内核的引用计数bug,导致无法删除。后续对网卡信息的查询和再次删除操作应该也会导致超时失败(根据日志推断,暂时还未在代码中找到对应调用,线上环境也没法重启调整日志级别和调试)。 首先需要看一个概念:PLEG。 PLEG (pod lifecycle event generator) 是 kubelet 中一个非常重要的模块,它主要完成以下几个目标: 从 runtime 中获取 pod 当前状态,产生 pod lifecycle events 从 runtime 中获取 pod 当前状态,更新 kubelet pod cache 接下来分析一下造成问题的原因应该是k8s的PLEG在同步pod信息时,可能要查询网卡详情(ip地址),由于内核bug导致超时,致使syncLoop中每执行一次遍历的时间过长(4分钟左右),因此新建pod和删除pod的时候,node上的信息和server上的信息更新不及时。用busybox测试创建和删除时,通过docker ps可以看到响应容器很快就可以启动或删除掉。 从图中可以看到该日志:Calico CNI deleting device in netns /proc/16814/ns/net这条。这是在pod执行删除是产生的。在正常情况下后面会有删除完成的日志信息,如下图: 但上面的日志里的无此信息,并且10s后打印了unregister_netdevice xxx的日志。这里是触发了内核bug。通过ps aux | grep calico也可以看到在对应时间有一个calico进程启动去执行操作,目前这个进程还在(10.209.33.105),这里估计k8s也有bug,没有wait pid,导致calico成为僵尸进程。 下图是kubelet日志。其中的PLEG is not healthy日志也是在对应的时间点出现。 问题本地复现 要在本地复现这个问题,首先需要给内核打补丁来协助复现。 diff --git a/net/ipv4/route.c b/net/ipv4/route.c index a0163c5..6b9e7ee…

轮指和修指甲小计

轮指 拇指要放松才能弹均匀,四个手指头下意识去独立控制,ima拨弦时要快速发力。90左右慢速练习时要把声音弹足够大和清晰。 指甲 由于m型指甲,斜坡不用太长,斜坡太长会有指甲摩擦琴弦的声音,最高坡点在手指中心偏向小指方向一点点的位置。指甲不用太长,从正面看相对于指肚多出1至2毫米即可。拨弦方向在标准姿势稍垂直于琴弦一点点,可以有效利用m型指甲中间凹点带动下压琴弦。合理的指甲形状可以通过拨弦时的阻力确定,阻力太小太顺滑发不出圆润的声音,因为无法有效的下压琴弦,要调整到一个阻力合适的形状。…

十二平均律基础知识

间隔半音数 间隔名 大致频率比 0 perfect unison 完全一度 1:1 1 minor second 小二度 16:15 2 major second 大二度 9:8 3 minor third 小三度 6:5 4 major third 大三度 5:4 5 perfect fourth 完全四度 4:3 6 augmented fourth 增四度diminished fifth 减五度 45:3264:45 7 perfect fifth 完全五度 3:2 8 minor sixth 小六度 8:5 9 major sixth 大六度 5:3 10 minor seventh 小七度 16:9 11 major seventh 大七度 15:8 12 perfect octave 完全八度 2:1…

sed命令详解

个人工作之中经常使用sed,但是比较简单的命令,并没有仔细的研究过sed详细用法,所以参考man手册进行了详细的整理。 sed是一个流式文本编辑器,可用来过滤和转换文本。基本用法如下: [source,shell] ---- sed [OPTION]... {script-only-if-no-other-script} [input-file]... ---- 其中的“[OPTION]”是可选的命令行参数,“{script-only-if-no-other-script}”是要执行的sed命令(如果没有用-e或-f指定别的命令的话,否则会视为输入文件路径),“[input-file]”是输入文件的路径。 sed的命令格式为:[地址]命令。地址是可选的,根据具体的命令而定。如‘/^hostname/aip=8.8.8.8’,‘p’等,地址格式和具体的命令可参下文。 == 选项及其含义 -n:: --quiet:: --silent:: 禁用自动打印模式空间,也就是禁止打印匹配的内容 -e script, --expression=script:: 添加要执行的sed命令 -f script-file, --file=script-file:: 将文件中的内容添加到要执行的命令中 --follow-symlinks:: 在in-place(如下)模式下,追踪符号链接 -i[SUFFIX], --in-place[=SUFFIX]:: 对文件进行直接更改,如果提供了SUFFIX,会使用SUFFIX作为扩展名进行备份 -l N, --line-length=N:: 指定行的长度 --posix:: 禁用所有的GNU扩展 -E, -r, --regexp-extended:: 在命令中使用posix扩展正则表达式(考虑到兼容性,应使用-E) -s, --separate:: 将各个文件单独对待,而不是一个长的连续的文本流。 --sandbox:: 在沙箱模式下运行 -u, --unbuffered:: 每次加载较小数量的数据,并加快刷新输入缓存的频率。 -z, --null-data:: 采用NUL字符(ascii 0)分割行 --help:: 显示帮助信息 --version:: 打印版本信息 如果没有指定-e,--expression,-f或--file选项,那么第一个非选项参数(及位置参数)将被作为命令来对待,剩下的所有参数都将作为文件名处理,如果没有指定文件名,则在标准输出中读取数据。 == 命令列表 === 无地址命令 : label:: *b*和*t*的标号 #comment:: 注释持续到换行符(或-e参数的结尾) }:: {}区块的闭合括号 === 0或1个地址的命令…

malloc分配不同大小内存的效率

分别对64B,1K, 4K, 16K, 256K, 4M, 64M,512M,2G大小进行测试,测试程序如下: [source,c] —- #include #include #include #include long long count = 0; void alarm_handler(int sig) { printf(“%lld\n”, count); count = 0; alarm(1); } int main() { signal(SIGALRM, alarm_handler); alarm(1); while(1) { void *data = malloc(8589934592); if (!data) printf(“alloc error\n”); free(data); count++; } } —- 机器配置: 内存:8GB,1600MHz CPU:Intel Core I5,2 Core,4 Thread,2500MHz 测试时机器内存占用(足够各个大小): [source,shell] —- blue@debian:/tmp$ free -g total used free shared buff/cache available Mem: 7…