AWS VPC CNI路由分析

amazon-vpc-cni-k8s是AWS基于VPC CNI的k8s网络插件,有着高性能及高度灵活的优点。项目地址:
https://github.com/aws/amazon-vpc-cni-k8s

下面我们通过分析其源码,查看实际上相关的CNI网络是怎么实现的。

1. AWS VPC CNI Agent node部分

Node上的Agent程序与路由相关的,主要做2块工作,一个是配置主网卡(SetupHostNetwork),一个是配置辅助网卡(setupENINetwork)。

1.1 SetupHostNetwork

针对host网络和主网卡Primary ENI做一些配置,Primary ENI: 主机的默认网卡,默认一般为eth0。

  1. 如果启用ipv4和支持node port,设置RP Filter:

  1. 设置Primary eni网卡的mtu:

  1. 先删除main表里优先级为1024的rule

  1. 如果启用node port支持,添加基于fwmark的路由策略:

  1. 如果启用ipv4且启用pod eni,则:

  1. 创建snat的iptables规则,这里省略

1.2 setupENINetwork

setupENINetwork为非Primary ENI的弹性网卡配置网络,每块弹性网卡都有一个主IP+N个辅助IP

eniName: 主机的默认网卡,默认一般为eth0+x,X为网卡序号,如eth1

  1. 设置eni网卡的mtu

  1. 让eni网卡up

  1. 删除eni网卡上所有已经存在的ip地址

  1. 设置eni网卡的ip

  1. 在deviceNumber + 1路由表里,删除已经存在默认路由,添加默认路由指向eni子网的网关

2. AWS CNI二进制文件

CNI二进制文件主要是实现CNI标准规则的几个接口:

2.1 cmdAdd

  1. grpc请求”/rpc.CNIBackend/AddNetwork”调用ipam分配ip

  2. 构建hostVethName

  1. 创建veth和容器内网卡,绑定容器网卡ip地址,容器内设置路由和其他内核参数

4.以下步骤在POD network namespce内执行

  1. 启动host网卡

  1. 添加用于host上访问pod的路由

  1. 添加用于访问pod流量的路由策略

3. 参考文件

  • AWS 弹性网卡参考

https://aws.amazon.com/cn/premiumsupport/knowledge-center/ec2-ubuntu-secondary-network-interface/

  • cni文档

https://github.com/containernetworking/cni/blob/spec-v1.0.0/SPEC.md
https://www.cni.dev/docs/

  • libcni库

https://github.com/containernetworking/cni/tree/main/scripts
https://www.cni.dev/docs/spec-upgrades/#specific-guidance-for-plugins-written-in-go

  • cni调用原理

http://www.noobyard.com/article/p-mjmyxamv-ob.html
https://blog.csdn.net/shida_csdn/article/details/79752411
https://segmentfault.com/a/1190000019956620
https://morningspace.github.io/tech/k8s-net-cni/

k8s apiserver 自签名证书过期导致集群故障

1. 故障处理过程

今天接到同事反馈发现有一套k8s apiserver集群出现如下报错:

随后去api server节点上查询api server日志,发现也有大量报错:

让人奇怪的是,并不是所有请求都报证书错误,通过日志发现,大量的请求到api server都是200的,出现500的为少数。

同时我们通过上面的报错发现,报错的是10.13.96.11:36528,这个IP是api server自己。

最后我们将错误定位到以下范围:

api server自己访问自己报证书过期的错误,而其它组件访问都是正常的。

同时我们检查了服务器上的所有k8s 集群使用的证书,并没有过期。

在无计可施时,怀着试试看的想法,我们将api server服务重启了,发现重启后恢复正常。果然是重启能解决99%的问题。

2. 故障原因排查

故障恢复后,我们一直在排查原因,排查原因时我们发现下面3点:

  • api server服务正好启动了一年没有重启
  • api server自己访问自己报证书过期的错误
  • api server访问自己使用的是IP地址,然我们api server对其它组件及外部都是使用域名的

下面就只能看源码了,通过api server的源码,我们发现api server启动时会自动生成一个自签名证书:

k8s.io/apiserver/pkg/server/options/serving_with_loopback.go:

而且这个自签名证书的时间为一年。

k8s.io/client-go/util/cert/cert.go:

3. 故障结论

故障原因很明显:api server启动时会自动生成一个自签名证书,而且这个自签名证书的时间为一年。

故障处理方法:重启api server(建议在一年内重启)

github上有人提过issue,官方以k8s本来就应该一年内升级且会重启为由,不认为这是一个问题:https://github.com/kubernetes/kubernetes/issues/86552

linux kernel 4.8以上内核NR_KERNEL_STACK的改变

1. NR_KERNEL_STACK是干什么用的?

通过下面的命令,我们可以查看内核栈的数量:

这个值在内核中的宏为:NR_KERNEL_STACK,表示当前内核中有多少个内核栈。

我们使用这个数字来监控K8S节点上使用PID的数量,以避免PID被耗尽。

2. 那么PID与内核栈有什么关系呢?

在linux系统中,每个进程、子进程和线程在运行时都会有一个PID。这些进程或线程在运行时,因为CPU需要进行任务切换,在任务切换时就需要上下文交换,在上下文交换时就需要把当前进程的上下文压到内核栈内去,以便下次再运行时取出继续执行。

所以可以确定:每个进程、子进程和线程都会有一个内核栈。内核栈的数量与PID的数量大致相当。

注:基于linux内核的线程,比如java的线程与linux的线程是一一对应的,nodejs只使用了linux的进程,线程模型是其自己实现的,golang最特别,使用了多进程,每个进程上有多线程(基于内核),线程上还是自己实现的协程或者说goroute(可以理解为自己实现的线程)

继续阅读

PVC在K8S集群迁移或恢复需注意地方

目前在做同城双活,每个机房都有一个k8s集群,其中一些共享卷需要做到双活的2个集群上都可用。

比如我们现在在主机房的集群上有一个PVC:

以及对应的PV:

继续阅读

k8s源码分析:kube-apiserver –admission-control及–enable-admission-plugins –disable-admission-plugins参数差异

kubernetes源码分支:1.18

先说结论,kube-apiserver启动时:

  1. –admission-control参数带的插件将是apiserver启动的插件,不包括默认插件
  2. –admission-control和–enable-admission-plugins –disable-admission-plugins不能同时使用
  3. –enable-admission-plugins参数不需要按加载顺序填写
  4. 不使用–admission-control参数时,api server会同时启动默认插件
  5. –enable-admission-plugins参数启用时,api server会同时启动默认插件,除非使用了–disable-admission-plugins显示的关闭某个插件
  6. –enable-admission-plugins和–disable-admission-plugins如果同时填写了某一个插件,这个插件将会被加载

继续阅读

traefik 重写配置

traefik ingress同样可以配置URL的重写:

  • traefik 1.x配置方法

下面是一个完整例子:

参考文档:
https://s0docs0traefik0io.icopy.site/v1.7/basics/#path-matcher-usage-guidelines

https://docs.traefik.io/v1.7/basics/#rules-order

继续阅读

在kubernetes 上部署ceph Rook测试

1. 部署rook

下载:

修改配置:

部署:

继续阅读

通过docker overlay2 目录名查找容器名

有时候经常会有个别容器占用磁盘空间特别大,这个时候就需要通过docker overlay2 目录名查找容器名:

先进入overlay2的目录,这个目录可以通过docker的配置文件(/etc/docker/daemon.json)内找到。然后看看谁占用空间比较多。

再通过目录名查找容器名:

如果发现有目录查不到,通常是因为容器已经被删掉了,目录没有清理,这时直接清理便可:

k8s创建集群只读service account

有时需要在k8s 集群上给比如开发人员创建一个只读的service account,在这里记录一下创建方法:

先创建oms-viewonly.yaml:

然后创建:
kubectl apply -f oms-viewonly.yaml

最后就可以使用以下命令查找刚刚创建SA的token:
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep oms-read | awk '{print $1}')

k8s 命令常用批量操作

用一行命令搞定:
kubectl get pods --all-namespaces -o wide | grep Evicted | awk '{print $1,$2}' | xargs -L1 kubectl delete pod -n

如:

也可以把Evicted换成OutOfcpu等其它状态使用。

批量加标签:

停用deployment:
kubectl patch deployment -p '{"spec":{"replicas":0}}' -n public-devops-tomcat-dev public-devops-oomtest-tomcat-dev

启用deployment:
kubectl patch deployment -p '{"spec":{"replicas":1}}' -n public-devops-tomcat-dev public-devops-oomtest-tomcat-dev

批量停用deployment:
kubectl get deployment --all-namespaces -o wide | grep public-devops-tomcat-dev | awk '{print $1,$2}' | xargs -L1 kubectl patch deployment -p '{"spec":{"replicas":0}}' -n

另一种批量停启deployment的方法:

  • 先从保存好现有的pod数信息
    kubectl get deployments --all-namespaces | grep -v "NAMESPACE" | grep -v "kube-system" | grep -v "default" | grep -v "prometheus" > deployments.txt

  • 再批量配置所有deployment的pod数为0
    kubectl get deployments --all-namespaces | grep -v "NAMESPACE" | grep -v "kube-system" | grep -v "default" | grep -v "prometheus" | awk '{print $1,$2}' | xargs -L1 kubectl scale --replicas=0 deployment -n

  • 执行以下脚本从保存好的配置中恢复配置: