404频道

学习笔记

《金字塔原理》是一本教你如何清晰的思考问题,表达观点的畅销书籍,但其并非万能的,其层层架构建立在因果关系基础之上。但现实世界中的很多问题并非简单的因果关系,此时金字塔原理就用不上了。

什么是金字塔原理

定义:任何一件事情都可以归纳出一个中心论点,而这个中心论点由3到7个论据进行支撑。每一个论据它本身又可以拆分成一个论点,该论点同样也可以拆分成3到7个论据,如此重复形状就像金字塔一样。

三个注意点:

  • 结论先行
  • 每层结论下面的论据不要超过7个
  • 每一个论点要言之有物,有明确的思想

组织思想的方法

在有了论点之后,论据该怎么拆分呢?可以按照下面四个原则来组织思想:

  • 时间顺序
  • 空间顺序
  • 重要性顺序,比如按照老弱优先原则
  • 逻辑演绎顺序。所以的逻辑演绎,就是三大段:大前提、小前提和结论。比如,凡人皆有一死,苏格拉底是人,所以苏格拉底也会死。但不推荐用此方法,因为对于听众的要求比较高,必须能够集中注意力才能听明白。

在思考的过程中,如果还没有论点,也可以使用上述方法先得出论据,然后推理出论点。
梳理出的论据必须符合MECE法则:每一个论点下的论据,都应该相互独立,但又可以完全穷尽,即论据要做到不遗漏不重叠。

如何让别人对自己的观点感兴趣

SCQ法则:Situation(设置背景)、Complication(冲突)和Question(疑问)。背景即先介绍大家都认同的背景信息从而引入话题。在SCQ都讲完后,就可以引入自己的论点了。

参考

  • 得到 - 《金字塔原理》成甲解读

最近因为《致阿里》的缘故,读了不少阿里内网的热帖,很多都是洋洋洒洒几千字,而且说的有理有据。如果换做是我,很多帖子哪怕我憋上一天都是写不出来的。我一直在思考,我到底比别人差在了哪里。思来想去,其中一个原因是因为平时的思考总结不够,缺少积累。

恰巧看了张一鸣在字节跳动9周年演讲,三观跟我特别合,想以贴为例,一来分享一下我个人的内心想法加深对演讲内容的认识,二来可以依次来锻炼自己的逻辑归纳能力。后续如有特别值得学习的演讲,也会分享一下自己的学习和思考,比如2020年张小龙的微信公开课的演讲就特别值得学习。

文中提到最多的词莫过于“平常心”了,“平常心”是一个佛源词,看来张一鸣没少研究佛学,整篇演讲显得也比较佛系,对于平常心的最直白的解释就是:

| 吃饭的时候好好吃饭,睡觉的时候好好睡觉

要想做到“好好吃饭,好好睡觉”对我来说是挺难的,我举一个简单的例子。平常周末来说,特别想睡一个懒觉,如果在睡觉前自己明确知道因为工作没有完成,第二天早上会有人找我,那么第二天早上一定会醒的比较早,想睡个懒觉都很难,即使第二天上午并不一定有人在我醒之前找到我。说明因为有事情的缘故,已经在无形中影响了睡眠质量。再举个例子,春节假期的睡眠质量明显会高于平常周末的睡眠质量,原因是心里总有各种工作的事情不能完全放下。如果将春节假期的心态为平常心,那么一年中的其他时间对我而言都不是平常心。

每家公司在年初的时候总会定义一些目标,比如全年营收目标为1个亿。一旦有了营收目标后,那么大家的工作重心一定会围绕的着目标展开。因为毕竟公司的资源是有限的,但是为了达成营收的目标,并不能保持一颗“平常心”来工作,焦虑的员工很难打磨出最好的产品,往往其他的地方就不会做的太好,比如用户体验、代码质量等等,一些本来很好的点子因为有营收目标的缘故,也很难展开实施,从而导致一些创新项目的流失。

接下来就是一些平常心的工作原则,总结下来有如下几点:

  • 平常心对待自己,平常人做非常事
  • 平常心对待预期,没有预期和标签的束缚会发挥的更好
  • 平常心对待过去和未来,关注当下
  • 平常心对待竞争对手
  • 平常心对待业务
  • 平常心对待成功和失败

文中特意提到了互联网八股文的一段话,这里就不再摘出来,我平时也没少见到类似的话术,看完后的感觉就是真牛逼,打死我都写不出来,但转头就忘记讲啥了,也许就是当时压根就看不懂,因为这些话太抽象了。互联网行业本身是一个特别务实接地气的行业,现在也渐渐在内卷严重,抽象的词汇也越来越多,期望下一个风口的到来,或许会将这股邪气吹走一些。

引用

张一鸣演讲全文:外部波澜起伏,内心平静如常

异常处理分为error和defer和recover两类,其中error用来处理可预期的异常,recover用来处理意外的异常。

error

支持多个返回值,可以将业务的返回值和错误的返回值分开,很多都会返回两个值。如果不使用error返回值,可以用_变量来忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// parseConfig returns a parsed configuration for an Azure cloudprovider config file
func parseConfig(configReader io.Reader) (*Config, error) {
var config Config

if configReader == nil {
return &config, nil
}

configContents, err := ioutil.ReadAll(configReader)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(configContents, &config)
if err != nil {
return nil, err
}

// The resource group name may be in different cases from different Azure APIs, hence it is converted to lower here.
// See more context at https://github.com/kubernetes/kubernetes/issues/71994.
config.ResourceGroup = strings.ToLower(config.ResourceGroup)
return &config, nil
}


error的几种使用方式:

使用error的方式 说明 举例
errors.New 简单静态字符串的错误,没有额外的信息 errors.New(“shell not specified”)
fmt.Errorf 用于格式化的错误字符串 fmt.Errorf(“failed to start kubernetes.io/kube-apiserver-client-kubelet certificate controller: %v”, err)
实现Error()方法的自定义类型 客户段需要检测并处理该错误时使用该方式 见下文自定义error
Error wrapping Go 1.13支持的特性

errors.New

原则:

  • 不要在客户端判断error中的包含字符串信息。
BadGood
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package foo

func Open() error {
return errors.New("could not open")
}

// package bar

func use() {
if err := foo.Open(); err != nil {
if err.Error() == "could not open" {
// handle
} else {
panic("unknown error")
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package foo

var ErrCouldNotOpen = errors.New("could not open")

func Open() error {
return ErrCouldNotOpen
}

// package bar

if err := foo.Open(); err != nil {
if errors.Is(err, foo.ErrCouldNotOpen) {
// handle
} else {
panic("unknown error")
}
}

当然也可以使用自定义error类型,但此时由于要实现自定义error类型,代码量会增加。

自定义error

error是个接口,可以用来扩展自定义的错误处理。

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
// file: k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations/nestedpendingoperations.go

// NewAlreadyExistsError returns a new instance of AlreadyExists error.
func NewAlreadyExistsError(operationName string) error {
return alreadyExistsError{operationName}
}

// IsAlreadyExists returns true if an error returned from
// NestedPendingOperations indicates a new operation can not be started because
// an operation with the same operation name is already executing.
func IsAlreadyExists(err error) bool {
switch err.(type) {
case alreadyExistsError:
return true
default:
return false
}
}

type alreadyExistsError struct {
operationName string
}

var _ error = alreadyExistsError{}

func (err alreadyExistsError) Error() string {
return fmt.Sprintf(
"Failed to create operation with name %q. An operation with that name is already executing.",
err.operationName)
}

还可以延伸出更复杂一些的树形error体系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// package net

type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}



type UnknownNetworkError string

func (e UnknownNetworkError) Error() string

func (e UnknownNetworkError) Temporary() bool

func (e UnknownNetworkError) Timeout() bool

Error Wrapping

error类型仅包含一个字符串类型的信息,如果函数的调用栈信息为A -> B -> C,如果函数C返回err,在函数A处打印err信息,那么很难判断出err的真正出错位置,不利于快速定位问题。我们期望的效果是在函数A出打印err,能够精确的找到err的源头。


为了解决上述问题,需要error类型在函数调用栈之间传递,有如下解决方法:


使用fmt.Errorf()来封装error信息,基于已经存在的error再产生一个新的error类型,需要避免error中包含冗余信息。

BadGood
1
2
3
4
5
6
// err: failed to call api: connection refused
s, err := store.New()
if err != nil {
return fmt.Errorf(
"failed to create new store: %s", err)
}
1
2
3
4
5
6
// err: call api: connection refused
s, err := store.New()
if err != nil {
return fmt.Errorf(
"new store: %s", err)
}
1
2
failed to create new store: failed to call api: connection refused
error中会有很多的冗余信息
1
2
new store: call api: connection refused
error中没有冗余信息,同时包含了调用栈信息

但使用fmt.Errorf()来全新封装的error信息的缺点也非常明显,丢失了最初的err信息,已经在中间转换为了全新的err。

类型断言

类型转换如果类型不正确,会导致程序crash,必须使用类型判断来判断类型的正确性。

BadGood
1
t := i.(string)
1
2
3
4
t, ok := i.(string)
if !ok {
// handle the error gracefully
}

panic

用于处理运行时的异常情况。
image.png
使用原则

  • 不要使用panic,在kubernetes项目中几乎没有使用panic的场景
  • 即使使用panic后,一定要使用recover会捕获异常
  • 在测试用例中可以使用panic
BadGood
1
2
3
4
5
6
7
8
9
10
func run(args []string) {
if len(args) == 0 {
panic("an argument is required")
}
// ...
}

func main() {
run(os.Args[1:])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func run(args []string) error {
if len(args) == 0 {
return errors.New("an argument is required")
}
// ...
return nil
}

func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

client-go

client-go利用队列来进行重试


https://github.com/kubernetes/client-go/blob/master/examples/workqueue/main.go#L93

kube-builder

kube-builder为client-go的更上次封装,本质上跟client-go利用队列来进行重试的机制完全一致。

发生了错误后该如何处理

  • 打印错误日志
  • 根据业务场景选择忽略或者自动重试
  • 程序自己crash

如何避免

  • 在编写代码时增加防御式编程意识,不能靠契约式编程。一个比较简单的判断错误处理情况的方法,看下代码中if语句占用的比例。https://github.com/kubernetes/kubernetes/blob/master/pkg/kubelet/kubelet_volumes.go
  • 需求的评估周期中,不仅要考虑到软件开发完成的时间,同时要考虑到单元测试(单元测试用例的编写需要较长的时间)和集成测试的时间
  • 单元测试覆盖率提升,测试场景要考虑到各种异常场景

之前购买的网件R6400V2路由器刷到了梅林系统,但一直以来信号都特别差,甚至都比不过最便宜的水星路由器,想重新刷回官方系统看下是否是梅林系统的问题。本文记录下重新刷回梅林系统的操作步骤。

刷机之路

从如下的地址下载固件。

1
链接:https://pan.baidu.com/s/1EBvUBlXozo_4zkXaeUMQng 密码:pqqv

在梅林系统的界面上找到固件升级的地方,将下载的固件上传。

image.png
结果悲剧的事情发生了,路由器出现了不断重启的状态,并且没有无线信号。猜测可能是因为固件用的是R6400,而非R6400V2导致的。或者是因为是用无线网络升级的原因,导致升级到一半网断掉了,从而导致失败了。

救砖之路

按照网件通用救砖,超详细教程文档中的网件通用救砖方法,将路由器的一个LAN口跟电脑的网卡用网线直接相连,并设置网卡的ip地址为192.168.1.3,网关为255.255.255.0。

在路由器启动的状态下,在命令行执行ping -t 192.168.1.1来验证路由器是否可以ping通,如果不通,可能是因为路由器的LAN段不是192.168.1.0,可能是192.168.50.0的。

在windows电脑上下载对应的文件,并保存到本地的F盘下。

手工安装winpcap,主要是给nmrpflash来使用。

以管理员身份运行命令行工具,执行nmrpflash.exe -L后找到本机的网卡net1。
image.png
执行nmrpflash命令后,立即重启路由器,如果第一次提示Timeout,可以立即执行该命令,如果出现下图的提示,说明命令执行成功。
image.png

参考文档

MTU

MTU是指一个以太网帧能够携带的最大数据部分的大小,并不包含以太网的头部部分。一般情况下MTU的值为1500字节。

当指定的数据包大小超过MTU值时,ip层会根据当前的mtu值对超过数据包进行分片,并会设置ip层头部的More Fragments标志位,并会设置Fragment offset属性,即分片的第二个以及后续的数据包会增加offset,第一个数据包的offset值为0。接收方会根据ip头部的More Fragment标志位和Fragment offset属性来进行切片的重组。

如果手工将发送方的MTU值设置为较大值,比如9000(巨型帧),如果发送方设置了不分片(ip头部的Don’t fragment),此时如果发送的链路上有地方不支持该MTU,报文就会被丢弃。

offload特性

执行 ethtool -k ${device} 可以看到很多跟网络接口相关的特性,这些特性的目的是为了提升网络的收发性能。TSO、UFO和GSO是对应网络发送,LRO、GRO对应网络接收。

执行ethtool -K ${device} gro off/on 来开启或者关闭相关的特性。

LRO(Large Receive Offload)

通过将接收的多个tcp segment聚合为一个大的tcp包,然后传送给网络协议栈处理,以减少上层网络协议栈的处理开销。

但由于tcp segment并不是在同一时刻到达网卡,因此组装起来就会变得比较困难。

由于LRO的一些局限性,在最新的网络上,该功能已经删除。

GRO(Generic Receive Offload)

GRO是LRO的升级版,正在逐渐取代LRO。运行与内核态,不再依赖于硬件。

RSS hash 特性

网卡可以根据数据包放到不同的网卡队列来处理,并可以根据不同的数据协议来设置不同的值。

注意:该特性并非所有的网卡都支持

下面命令为查询 udp 协议的设置,可以看到 hash 的策略为根据源 ip 地址和目的 ip 地址。

1
2
3
4
$ ethtool -n eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA

可以使用 ethtool -N eth0 rx-flow-hash udp4 sdfn来修改hash 策略,sdfn对应的含义如下:

1
2
3
4
5
6
7
8
9
m   Hash on the Layer 2 destination address of the rx packet.
v Hash on the VLAN tag of the rx packet.
t Hash on the Layer 3 protocol field of the rx packet.
s Hash on the IP source address of the rx packet.
d Hash on the IP destination address of the rx packet.
f Hash on bytes 0 and 1 of the Layer 4 header of the rx packet.
n Hash on bytes 2 and 3 of the Layer 4 header of the rx packet.
r Discard all packets of this flow type. When this option is
set, all other options are ignored.

修改完成后再查看网卡的 hash 策略如下:

1
2
3
4
5
6
$ ethtool -n eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA
L4 bytes 0 & 1 [TCP/UDP src port]
L4 bytes 2 & 3 [TCP/UDP dst port]

参考文章

望京SOHO

又是很长的一段时间没有更新,果然又是不定期更新,文章的有些内容也是很久以前积累的,并不是因为太懒,而是确实没有太多的精力。

题图为雨中的望京SOHO,今年全国的雨水特别多,北京亦是如此。南方的鱼米之乡地区出现了严重的洪灾,不知道今年的粮食产量会受多大影响。我们的地球在人类翻天覆地的变更后实在经受不了太多的hack,愿雨季早日过去。

资源

1.bocker

bocker=bash + docker,其利用100多行bash代码实现的简易版的docker,使用到的底层技术跟docker是一致的,包括chroot、namespace、cgroup。

2.kubectx

天天操作k8s的工程师一定少不了使用kubectl命令,而用kubectl命令的工程师一定会特别烦天天输入-n ${namespace}这样的操作,该工具可以省去输入namespace的操作。刚开始的时候不是太习惯该工具,直到近期才感知到该工具的价值。🤦‍♂️

3.KubeOperator

https://raw.githubusercontent.com/KubeOperator/website/master/images/kubeoperator-ui.jpg

k8s集群的安装操作基本上都是黑屏来完成的,同时集群规模较大时,还需要一些自动化的手段来解决安装和运维物理机的问题。KubeOperator提供了界面化的操作来完成k8s集群的配置、安装、升级等的操作,底层也是调用了ansible来作为自动化的工具。该项目已经加入CNCF,期望后面可以做的功能更加强大,给k8s集群的运维带来便利。

4.awesome-operators

k8s生态的operator非常火爆,作为k8s扩展能力的一个重要组成部分,该项目汇总了常见的operator项目。

5.chaos-mesh

pingcap开源的Kubernetes的混沌工程项目,可以使用CRD的方式来注入故障到Kubernetes集群中。

6.devops-exercises

DevOps相关的一些面试题,涉及到的方面还是比较全的。

7.shell2http

可以将shell脚本放到业务页面上执行的工具,在web页面上点击按钮后,会执行shell脚本,shell脚本的输出会在web页面上显示。

8.Google Shell 风格指南

Google编程规范还是比较有权威性的,此为Shell的编码规范。

9.shellcheck

Shell作为弱类型的编程语言,稍有不慎还是非常容易写错语法的,至少很多的语法我是记不住的,每次都是边查语法边写🤦‍。该项目为Shell的静态检查工具,用来检查可能的语法错误,在Github上的start数量还是非常高的。

不仅支持命令行工具检查,而且还可以跟常用的编辑器集成(比如vim、vscode),用来实现边写边检查的效果。还提供了web界面,可以将shell脚本输入到web界面上来在线检查。

10.teambition

阿里的一款的远程协作工具,类似于国外slack+trello的结合版,在产品设计上能看到太多地方借鉴了trello,非常像是trello的本土化版本,更贴近国人的使用习惯,可用于管理团队和个人的任务。

11.IcePanel

IcePanel为vscode的一款插件,提供了k8s一些基础对象的编辑生成器,通过ui的界面即可生成k8s的ConfigMap、Deployment、Service等对象。

  1. Play with Kubernetes

一个提供在线的kubernetes集群的工具,在界面上点一下按钮就可以创建一个k8s集群,不需要注册,非常方便,但创建的集群只有四个小时的使用时间。可以用来熟悉k8s的基本操作,或者试验一些功能。

精彩文章

1.腾讯自研业务上云:优化Kubernetes集群负载的技术方案探讨

k8s虽然在服务器的资源利用率上比起传统的物理机或虚拟机部署服务方式有了非常大的提升,本文结合实践经验,从pod、node、hpa等多个维护来优化以便进一步的压榨服务器的资源。

书籍

1.[Linux开源网络全栈详解:从DPDK到OpenFlow])(http://product.china-pub.com/8061094)

该书可以作为全面了解开源软件网络的相关技术,涉及到Linux虚拟网络、DPDK、OpenStack、容器相关网络等知识。

2.Kubernetes 网络权威指南:基础、原理与实践

该书可以作为全面了解k8s相关的容器网络的相关技术,如果对k8s周边的虚拟网络知识有所全面了解,该书籍还是比较适合的。

在k8s中的内置资源很多都有status部分,比如deployment,用来标识当前资源的状态信息。同样在CRD的体系中,也都有status部分。这些status部分信息,是由operator来负责维护的。

如果直接采用kubectl edit的方式来修改status部分信息,会发现是无法直接修改status部分的,因为status是无法修改成功的,因为status部分是CR的一个子资源。

可以通过如下的方式来完成修改

  1. 首先要准备一个完整的yaml文件,包含了status部分信息

这个的格式必须为json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"apiVersion": "kuring.me/v1alpha1",
"kind": "Certificate",
"metadata": {
"name": "test",
"namespace": "default"
},
"spec": {
"secretName": "test"
},
"status": {
"phase": "pending"
}
}
  1. 获取系统的TOKEN信息

通常在kube-system下会有admin的ServiceAccount,会有一个对应的Secret来存放该ServiceAccount的token信息。执行kubectl get secret -n kube-system admin-token-r2bvt -o yaml获取到token信息,并其中的token部分进行base64解码。

  1. 执行如下的脚本

需要将其中的变量信息修改一下

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

obj_file=$1
kind=certificates
APISERVER=https://10.0.0.100:6443
namespace=default
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1yMmJ2dCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImRiY2IyNzUzLWE5OGMtMTFlYS04NGVjLTAwMTYzZTAwOGU3MCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.tX3jyNh-GEuZQg-hmy7igqh9vpTAz8Jh9uEv-diZ5XWjX9JYhxwD9nxTQvCcvzY7iPIbvxQfW2GHDZISPoopX0vQy9mQ7npVitrOvFovk06plefI5Gxjdft6vdpt-ArsGTpm7-s9G-3aBg5x41h3Cdgyv-W-ypFlCr9dKu9K7BcRIXSq_GQlq5TBmd-LKFXoer4QGwkn7geq5-ziMk_lY21jIGVdIkq9IRiH8NWuCl7l8i6nQESQDUUpMyKDCqkJqUFV8UkrQL7TfqurFP36_TUAQTh2ZAE8nFnrKRoa09BnjT-FoPO6Jnq6COQjk3PGDHV8LKNDAjCCrs0A53IYGw
obj=nginx-test

echo "begin to patch $obj the file "${obj_file}
curl -XPATCH -H "Accept: application/json" -H "Content-Type: application/merge-patch+json" --header "Authorization: Bearer $TOKEN" --insecure -d @${obj_file} $APISERVER/apis/kuring.me/v1alpha1/namespaces/${namespace}/${kind}/$obj/status

查看网桥设备以及端口

使用brctl show可以查看本地上的所有的网桥设备以及接到网桥设备上的所有网络设备。

查看网桥设备的mac地址表

执行brctl showmacs ${dev},常用来排查一些包丢在网桥上的场景。
其中port no为网桥通过mac地址学习到的某个mac地址所在的网桥端口号。

1
2
3
4
5
6
7
8
9
10
$ brctl showmacs br0
port no mac addr is local? ageing timer
1 02:50:89:59:ac:4b no 3.96
69 02:e2:14:78:d7:92 no 0.57
1 0a:1e:01:dc:67:87 no 10.23
1 0a:60:3c:ca:a8:85 no 6.04
1 0e:01:ce:d6:fc:66 no 8.36
1 0e:0c:f8:6c:08:75 no 56.73
58 0e:49:85:f6:a1:40 no 1.30
22 0e:c0:99:b0:d9:f9 no 0.85

查看网桥设备的某个端口的挂载设备

在上文中中可以获取到某个mac地址对应的网桥设备的端口号,要想知道某个网桥设备的端口号对应的设备可以使用brctl showstp ${dev}命令。

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
brctl showstp br0
br0
bridge id 8000.ae90501b5b47
designated root 8000.ae90501b5b47
root port 0 path cost 0
max age 20.00 bridge max age 20.00
hello time 2.00 bridge hello time 2.00
forward delay 15.00 bridge forward delay 15.00
ageing time 300.00
hello timer 0.03 tcn timer 0.00
topology change timer 0.00 gc timer 62.37
flags
bond0.11 (1)
port id 8001 state forwarding
designated root 8000.ae90501b5b47 path cost 100
designated bridge 8000.ae90501b5b47 message age timer 0.00
designated port 8001 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
veth02b41ce8 (20)
port id 8014 state forwarding
designated root 8000.ae90501b5b47 path cost 2
designated bridge 8000.ae90501b5b47 message age timer 0.00
designated port 8014 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
hairpin mode 1

在日常开发的过程中,经常会需要在本地开发的程序需要在k8s中调试的场景,比如,写了一个operator。如果此时,本地又没有可以直接可达的k8s集群,比如k8s是在公有云的vpc环境内,外面无法直接访问。此时,ssh又是可以直接通过公网vip访问的vpc的网络内的。为了满足此类需要,可以采用ssh tunnel的方式来打通本地跟远程的k8s集群。

1. 本地建立ssh tunnel到远程集群网络

mac用户可以通过SSH TUNNEL这个软件来在界面上自动化配置,具体的配置方式如下:

需要增加一个ssh的动态代理,监听本地的9909端口号。

2. 将远程集群的kubeconfig文件复制到本地

将k8s集群中~/.kube/config文件复制到本地的~/.kube/config目录下

3. 在命令行中执行kubectl命令

在命令行中配置http和https代理

1
2
export http_proxy=127.0.0.1:9909
export https_proxy=127.0.0.1:9909

然后至此就可以通过kubectl命令来访问远程的k8s集群了。

conntrack是netfilter提供的连接跟踪机制,允许内核识别出数据包属于哪个连接。是iptables实现状态匹配(-m state)以及nat的基础,由单独的内核模块nf_conntrack实现。

conntrack在图中有两处,一处位于prerouting,一处位于output。主机自身进程产生的数据包会经过output链的conntrack,主机的网络设备接收到的数据包会通过prerouting链的conntrack。

每个通过conntrack的数据包,内核会判断是否为新的连接。如果是新的连接,则在连接跟踪表中插入一条记录。如果是已有连接,会更新连接跟踪表中的记录。

需要特别注意的是,conntrack并不会修改数据包,如dnat、snat,而仅仅是维护连接跟踪表。

连接跟踪表的内容

/proc/net/nf_conntrack可以看到连接跟踪表的所有内容,通过hash表来实现。

1
ipv4     2 tcp      6 117 TIME_WAIT src=10.45.4.124 dst=10.45.8.10 sport=36903 dport=8080 src=10.45.8.10 dst=10.45.4.124 sport=8080 dport=36903 [ASSURED] mark=0 zone=0 use=2
  • 117: 该连接的生存时间,每个连接都有一个timeout值,可以通过内核参数进行设置,如果超过该时间还没有报文到达,该连接将会删除。如果有新的数据到达,该计数会被重置。
  • TIME_WAIT: 当前该连接的最新状态

涉及到的内核参数

  • net.netfilter.nf_conntrack_buckets: 用来设置hash表的大小
  • net.netfilter.nf_conntrack_max: 用来设置连接跟踪表的数据条数上限

iptables与conntrack的关系

iptables使用-m state模块来从连接跟踪表查找数据包的状态,上面例子中的TIME_WAIT即为连接跟踪表中的状态,但这些状态对应到iptable中就只有五种状态。特别需要注意的是,这五种状态是跟具体的协议是tcp、udp无关的。

状态 含义
NEW 匹配连接的第一个包
ESTABLISHED NEW状态后,如果对端有回复包,此时连接状态为NEW
RELATED 不是太好理解,当已经有一个状态为ESTABLISHED连接后,如果又产生了一个新的连接并且跟此时关联的,那么该连接就是RELATED状态的。对于ftp协议而言,有控制连接和数据连接,控制连接要先建立为ESTABLISHED,数据连接就变为控制连接的RELATED。那么conntrack怎么能够识别到两个连接是有关联的呢,即能够识别出协议相关的内容,这就需要通过扩展模块来完成了,比如ftp就需要nf_conntrack_ftp
INVALID 无法识别的或者有状态的数据包
UNTRACKED 匹配带有NOTRACK标签的数据包,raw表可以将数据包标记为NOTRACK,这种数据包的连接状态为NOTRACK

conntrack-tools

1
2
# 查看连接跟踪表
conntrack -L

reference

0%