404频道

学习笔记

理论

待补充

安装

下载kind命令,但不需要创建一个k8s集群。

执行如下命令下载kn 二进制文件

1
2
3
4
5
6
7
wget https://github.com/knative/client/releases/download/knative-v1.2.0/kn-linux-amd64
mv kn-linux-amd64 /usr/local/bin/kn
chmod +x /usr/local/bin/kn

# 自动补全
echo -e "\n# kn" >> ~/.bash_profile
echo 'source <(kn completion bash)' >>~/.bash_profile

下载quickstart二进制文件

1
2
3
4
5
6
7
wget https://github.com/knative-sandbox/kn-plugin-quickstart/releases/download/knative-v1.2.0/kn-quickstart-linux-amd64
mv kn-quickstart-linux-amd64 /usr/local/bin/kn-quickstart
chmod +x /usr/local/bin/kn-quickstart

# 如下两条命令可以得到相同的输出结果
kn quickstart --help
kn-quickstart --help

执行 kn quickstart kind 命令即可创建出一个knative的k8s集群。

knative serving

serving的核心功能为提供弹性扩缩容能力。

CRD

image.png
Service:用来管理整个应用的生命周期。
Route:用来将流量分发到不同的Revision
Configuration:
Revision:

kpa功能

实践

最简单service

创建hello.yaml文件,内容如下,并执行 kubectl apply -f hello.yaml。其中service的名字为hello,revision的名字为world。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
spec:
template:
metadata:
# This is the name of our new "Revision," it must follow the convention {service-name}-{revision-name}
name: hello-world
spec:
containers:
- image: gcr.io/knative-samples/helloworld-go
ports:
- containerPort: 8080
env:
- name: TARGET
value: "World"

通过kn命令可以看到创建了一个service hello,并且有一个可以访问的url地址。

1
2
3
# kn service list
NAME URL LATEST AGE CONDITIONS READY REASON
hello http://hello.default.127.0.0.1.sslip.io hello-world 154m 3 OK / 3 True

knative抽象了Revision来标识该service对应的版本信息,可以使用kubectl命令,也可以使用kn命令来查看revision信息。

1
2
3
4
5
6
$ k get revisions.serving.knative.dev 
NAME CONFIG NAME K8S SERVICE NAME GENERATION READY REASON ACTUAL REPLICAS DESIRED REPLICAS
hello-world hello 1 True 0 0
$ kn revision list
NAME SERVICE TRAFFIC TAGS GENERATION AGE CONDITIONS READY REASON
hello-world hello 100% 1 6m33s 3 OK / 4 True

在宿主机上执行 curl http://hello.default.127.0.0.1.sslip.io 接口访问刚才创建的service。这里比较有意思的是为什么域名可以在宿主机上解析,该域名实际上是通过公网来解析的,域名服务器sslip.io负责该域名的解析。
本机的127.0.0.1的80端口实际是指向的是kind容器的31080端口,而31080为kourier-ingress对外暴露的服务。

1
2
# kubectl  get svc -A | grep 31080
kourier-system kourier-ingress NodePort 10.96.252.144 <none> 80:31080/TCP 3h16m

kourier-ingress为knative使用的ingress服务,该ingress并非k8s原生的ingress对象,而是自定义的ingress networking.internal.knative.dev/v1alpha1。service hello在创建的时候会同步创建一个ingress对象。在该ingress对象中可以看到刚才访问的域名hello.default.127.0.0.1.sslip.io,同时可以看到该ingress将域名指向到了k8s的service hello-world.default。

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
kubectl  get  ingresses.networking.internal.knative.dev hello -o yaml 
apiVersion: networking.internal.knative.dev/v1alpha1
kind: Ingress
metadata:
annotations:
networking.internal.knative.dev/rollout: '{"configurations":[{"configurationName":"hello","percent":100,"revisions":[{"revisionName":"hello-world","percent":100}],"stepParams":{}}]}'
networking.knative.dev/ingress.class: kourier.ingress.networking.knative.dev
serving.knative.dev/creator: kubernetes-admin
serving.knative.dev/lastModifier: kubernetes-admin
finalizers:
- ingresses.networking.internal.knative.dev
generation: 1
labels:
serving.knative.dev/route: hello
serving.knative.dev/routeNamespace: default
serving.knative.dev/service: hello
name: hello
namespace: default
ownerReferences:
- apiVersion: serving.knative.dev/v1
blockOwnerDeletion: true
controller: true
kind: Route
name: hello
uid: 4c58e77b-4871-42cc-bfa0-aa9fda9646ed
spec:
httpOption: Enabled
rules:
- hosts:
- hello.default
- hello.default.svc
- hello.default.svc.cluster.local
http:
paths:
- splits:
- appendHeaders:
Knative-Serving-Namespace: default
Knative-Serving-Revision: hello-world
percent: 100
serviceName: hello-world
serviceNamespace: default
servicePort: 80
visibility: ClusterLocal
- hosts:
- hello.default.127.0.0.1.sslip.io
http:
paths:
- splits:
- appendHeaders:
Knative-Serving-Namespace: default
Knative-Serving-Revision: hello-world
percent: 100
serviceName: hello-world
serviceNamespace: default
servicePort: 80
visibility: ExternalIP
status:
conditions:
- lastTransitionTime: "2022-03-11T12:47:26Z"
status: "True"
type: LoadBalancerReady
- lastTransitionTime: "2022-03-11T12:47:26Z"
status: "True"
type: NetworkConfigured
- lastTransitionTime: "2022-03-11T12:47:26Z"
status: "True"
type: Ready
observedGeneration: 1
privateLoadBalancer:
ingress:
- domainInternal: kourier-internal.kourier-system.svc.cluster.local
publicLoadBalancer:
ingress:
- domainInternal: kourier.kourier-system.svc.cluster.local

在default namespace下可以看到有三个service,其中hello-world的ingress转发的service。

1
2
3
4
# k get svc | grep hello
hello ExternalName <none> kourier-internal.kourier-system.svc.cluster.local 80/TCP 155m
hello-world ClusterIP 10.96.58.149 <none> 80/TCP 155m
hello-world-private ClusterIP 10.96.54.163 <none> 80/TCP,9090/TCP,9091/TCP,8022/TCP,8012/TCP 155m

通过查看该serivce的yaml,并未定义serivce的selector。

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

apiVersion: v1
kind: Service
metadata:
annotations:
autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
serving.knative.dev/creator: kubernetes-admin
labels:
app: hello-world
networking.internal.knative.dev/serverlessservice: hello-world
networking.internal.knative.dev/serviceType: Public
serving.knative.dev/configuration: hello
serving.knative.dev/configurationGeneration: "1"
serving.knative.dev/configurationUID: 13138d0f-ee5f-4631-94a5-6928546e504c
serving.knative.dev/revision: hello-world
serving.knative.dev/revisionUID: f3aaae74-6b79-4785-b60d-5607c0ab3bcf
serving.knative.dev/service: hello
serving.knative.dev/serviceUID: 79907029-32df-4f21-b14b-ed7d24e1a10e
name: hello-world
namespace: default
ownerReferences:
- apiVersion: networking.internal.knative.dev/v1alpha1
blockOwnerDeletion: true
controller: true
kind: ServerlessService
name: hello-world
uid: 3a6326c5-32b0-4074-bec2-4d3eed293b71
spec:
clusterIP: 10.96.58.149
clusterIPs:
- 10.96.58.149
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8012
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}

查看Service对应的Endpoint对象,可以看到Endpoint对象实际上指向到了knative-serving下的pod activator-85bd4ddcbb-6ms7n。

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
apiVersion: v1
kind: Endpoints
metadata:
annotations:
autoscaling.knative.dev/class: kpa.autoscaling.knative.dev
serving.knative.dev/creator: kubernetes-admin
labels:
app: hello-world
networking.internal.knative.dev/serverlessservice: hello-world
networking.internal.knative.dev/serviceType: Public
serving.knative.dev/configuration: hello
serving.knative.dev/configurationGeneration: "1"
serving.knative.dev/configurationUID: 13138d0f-ee5f-4631-94a5-6928546e504c
serving.knative.dev/revision: hello-world
serving.knative.dev/revisionUID: f3aaae74-6b79-4785-b60d-5607c0ab3bcf
serving.knative.dev/service: hello
serving.knative.dev/serviceUID: 79907029-32df-4f21-b14b-ed7d24e1a10e
name: hello-world
namespace: default
ownerReferences:
- apiVersion: networking.internal.knative.dev/v1alpha1
blockOwnerDeletion: true
controller: true
kind: ServerlessService
name: hello-world
uid: 3a6326c5-32b0-4074-bec2-4d3eed293b71
subsets:
- addresses:
- ip: 10.244.0.5
nodeName: knative-control-plane
targetRef:
kind: Pod
name: activator-85bd4ddcbb-6ms7n
namespace: knative-serving
resourceVersion: "809"
uid: df916ac6-3161-4bc6-bf8c-47bb7c83cc4a
ports:
- name: http
port: 8012
protocol: TCP

进一步查看knative-serving下有一个knative的组件activator。

1
2
3
k get deploy -n knative-serving activator 
NAME READY UP-TO-DATE AVAILABLE AGE
activator 1/1 1 1 3h35m

打开终端,执行一下 kubectl get pod -l serving.knative.dev/service=hello -w,重新执行 curl http://hello.default.127.0.0.1.sslip.io 发起新的请求,可以看到会有pod产生,且pod为通过deployment拉起。

1
2
3
4
5
$ kubectl get pod -l serving.knative.dev/service=hello -w
hello-world-deployment-7ff4bdb7fd-rqg96 0/2 Pending 0 0s
hello-world-deployment-7ff4bdb7fd-rqg96 0/2 ContainerCreating 0 0s
hello-world-deployment-7ff4bdb7fd-rqg96 1/2 Running 0 1s
hello-world-deployment-7ff4bdb7fd-rqg96 2/2 Running 0 1s

过一段时间没有新的请求后,pod会自动被删除,同时可以看到deployment的副本数缩成0。该namespace下并没有对应的hpa产生,说明deployment副本数的调整并非使用k8s原生的hpa机制。

1
2
3
4
5
hello-world-deployment-7ff4bdb7fd-rqg96   2/2     Terminating         0          63s
hello-world-deployment-7ff4bdb7fd-rqg96 0/2 Terminating 0 93s
$ k get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
hello-world-deployment 0/0 0 0 21h

service的流量切分

重新提交如下的yaml文件,将service对应的revision更新为knative。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
spec:
template:
metadata:
name: hello-knative
spec:
containers:
- image: gcr.io/knative-samples/helloworld-go
ports:
- containerPort: 8080
env:
- name: TARGET
value: "Knative"

重新查看revision,可以看到revision已经变更为了knative,同时可以看到老的revision world并没有被删除,只是没有了流量转发。

1
2
3
4
$ kn revision list
NAME SERVICE TRAFFIC TAGS GENERATION AGE CONDITIONS READY REASON
hello-knative hello 100% 2 49s 4 OK / 4 True
hello-world hello 1 10m 3 OK / 4 True

重新apply新的Service,将流量切分为hello-world和hello-knative两份,重新执行curl请求,可以看到结果会随机返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: hello
spec:
template:
metadata:
name: hello-knative
spec:
containers:
- image: gcr.io/knative-samples/helloworld-go
ports:
- containerPort: 8080
env:
- name: TARGET
value: "Knative"
traffic:
- latestRevision: true
percent: 50
- revisionName: hello-world
percent: 50

查看revision的信息,可以看到流量已经是50%的切分了。

1
2
3
4
# kn revision list              
NAME SERVICE TRAFFIC TAGS GENERATION AGE CONDITIONS READY REASON
hello-knative hello 50% 2 11m 3 OK / 4 True
hello-world hello 50% 1 21m 3 OK / 4 True

knative eventing

image.png
Source:k8s的CR对象,产生Event
Broker:用来分发Event
Trigger:Event触发器
Sink:Event输出结果

其中包含业务逻辑的可编程的部分在于Trigger部分,由于trigger实际上是无状态的服务,对于一些有状态的消息knative很难满足。比如同一类型的特定字段的event转发到特定的trigger上,broker实际上不具备可编程性,因此无法完成。

kn quickstart 命令会安装一个broker到环境中

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
$ k get brokers.eventing.knative.dev example-broker -o yaml 
apiVersion: eventing.knative.dev/v1
kind: Broker
metadata:
annotations:
eventing.knative.dev/broker.class: MTChannelBasedBroker
eventing.knative.dev/creator: kubernetes-admin
eventing.knative.dev/lastModifier: kubernetes-admin
name: example-broker
namespace: default
spec:
config:
apiVersion: v1
kind: ConfigMap
name: config-br-default-channel
namespace: knative-eventing
delivery:
backoffDelay: PT0.2S
backoffPolicy: exponential
retry: 10
status:
address:
url: http://broker-ingress.knative-eventing.svc.cluster.local/default/example-broker
annotations:
knative.dev/channelAPIVersion: messaging.knative.dev/v1
knative.dev/channelAddress: http://example-broker-kne-trigger-kn-channel.default.svc.cluster.local
knative.dev/channelKind: InMemoryChannel
knative.dev/channelName: example-broker-kne-trigger
conditions:
- lastTransitionTime: "2022-03-11T12:32:43Z"
status: "True"
type: Addressable
- lastTransitionTime: "2022-03-11T12:32:43Z"
message: No dead letter sink is configured.
reason: DeadLetterSinkNotConfigured
severity: Info
status: "True"
type: DeadLetterSinkResolved
- lastTransitionTime: "2022-03-11T12:32:43Z"
status: "True"
type: FilterReady
- lastTransitionTime: "2022-03-11T12:32:43Z"
status: "True"
type: IngressReady
- lastTransitionTime: "2022-03-11T12:32:43Z"
status: "True"
type: Ready
- lastTransitionTime: "2022-03-11T12:32:43Z"
status: "True"
type: TriggerChannelReady
observedGeneration: 1

快速开始

创建如下的Service,该service通过环境变量 BROKER_URL 作为broker地址,可以看到其地址为 quickstart工具默认安装的example-broker。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: cloudevents-player
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/min-scale: "1"
spec:
containers:
- image: ruromero/cloudevents-player:latest
env:
- name: BROKER_URL
value: http://broker-ingress.knative-eventing.svc.cluster.local/default/example-broker

访问service页面 http://cloudevents-player.default.127.0.0.1.sslip.io/ 可以在界面上创建Event后,产生如下的Event内容,格式完全遵循社区的CloudEvent规范。点击发送,即可以将消息发送给broker,但由于borker没有配置任何的Trigger,消息在发送到broker后会被直接丢弃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"root": {
"attributes": {
"datacontenttype": "application/json",
"id": "123",
"mediaType": "application/json",
"source": "manual",
"specversion": "1.0",
"type": "test-type"
},
"data": {
"message": "Hello CloudEvents!"
},
"extensions": {}
}
}

我们继续来给broker增加触发器,创建如下的yaml。该触发器定义了broker为example-broker,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: eventing.knative.dev/v1
kind: Trigger
metadata:
name: cloudevents-trigger
annotations:
knative-eventing-injection: enabled
spec:
broker: example-broker
subscriber:
ref:
apiVersion: serving.knative.dev/v1
kind: Service
name: cloudevents-player

重新在页面上发送event,可以看到消息的状态为接收。

参考

内核参数项

以CentOS7 系统为例,可以看到有1088个内核参数项。

1
2
3
4
$ uname -a
Linux iZbp17o12gcsq2d87y7x1hZ 3.10.0-1160.36.2.el7.x86_64 #1 SMP Wed Jul 21 11:57:15 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ sysctl -a 2>/dev/null | wc -l
1088

Linux的内核参数均位于/proc/sys目录下,涉及到如下几个目录:

分类 描述
abi
crypto
debug
dev 用来配置特定设备,比如raid、scsi设备
fs 文件子系统
kernel 内核子系统
net 网络子系统
user
vm 内存子系统

kernel子系统

参数 描述
kernel.panic 内核出现panic,重新引导前需要等待的时间,单位为秒。如果该值为0,,说明内核禁止自动引导
kernel.core_pattern core文件的存放路径

vm子系统

参数 描述
vm.min_free_kbytes 系统所保留空闲内存的最小值。该值通过公式计算,跟当前机器的物理内存相关。
vm.swappiness 用来控制虚拟内存,支持如下值:
0:关闭虚拟内存
1:允许开启虚拟内存设置的最小值
10:剩余内存少于10%时开启虚拟内存
100:完全适用虚拟内存。

该参数可以通过swapon 命令开启,swapoff关闭。
参考链接:https://linuxhint.com/understanding_vm_swappiness/

net子系统

参数 描述
net.ipv4.ip_local_reserved_ports 随机端口的黑名单列表,系统在发起连接时,不使用该内核参数内的端口号
net.ipv4.ip_local_port_range 随机端口的白名单范围,网络连接可以作为源端口的最小和最大端口限制
net.ipv4.rp_filter 是否开启对数据包源地址的校验, 收到包后根据source ip到route表中检查是否否和最佳路由,否的话扔掉这个包。这次如下值:
1. 不开启源地址校验
2. 开启严格的反向路径校验。对每个进来的数据包,校验其反向路径是否是最佳路径。如果反向路径不是最佳路径,则直接丢弃该数据包。
3. 开启松散的反向路径校验。对每个进来的数据包,校验其源地址是否可达,即反向路径是否能通(通过任意网口),如果反向路径不通,则直接丢弃该数据包。该内核参数 net.ipv4.conf.all.log_martians 可以来控制是否打开日志,日志打开后可以在/var/log/message中观察到。

TCP

tcp 相关内核参数可以使用 man 7 tcp 查看。

建连相关

syn 队列又称为半连接队列。服务端在接收到客户端的 SYN 包后,服务端向客户端发送 SYN + ACK 报文,此时会进入到半连接队列。

相关文章:Linux TCP backlog

断开连接相关

TCP TIME_WAIT

文件子系统

fs.mount-max

The value in this file specifies the maximum number of mounts that may exist in a mount namespace. The default value in this file is 100,000.

Linux 4.19 内核引入。当 mount namespace 中加载的文件数超过该值后,会报错 “No space left on device”。

内核参数在k8s的支持情况

大类 子类 备注
namespace内核参数 安全的内核参数 k8s默认支持的内核参数非常少,仅支持如下的内核参数:
1. kernel.shm_rmid_forced
2. net.ipv4.ip_local_port_range
3. net.ipv4.tcp_syncookies(在内核4.4之前为非namespace化)
4. net.ipv4.ping_group_range (从 Kubernetes 1.18 开始)
5. net.ipv4.ip_unprivileged_port_start (从 Kubernetes 1.22 开始)
namespace内核参数 非安全内核参数 默认禁用,pod可以调度成功,但会报错SysctlForbidden。修改kubelet参数开启 kubelet --allowed-unsafe-sysctls 'kernel.msg*,net.core.somaxconn'
非namespace内核参数 在容器中没有的内核参数 如:
net.core.netdev_max_backlog = 10000
net.core.rmem_max = 2097152
net.core.wmem_max = 2097152
非namespace隔离参数 直接修改宿主机的内核参数 在容器中需要开启特权容器来设置,如:
vm.overcommit_memory = 2
vm.overcommit_ratio = 95

操作系统的namespace化的内核参数仅支持:

  • kernel.shm*,
  • kernel.msg*,
  • kernel.sem,
  • fs.mqueue.*,
  • net.*(内核中可以在容器命名空间里被更改的网络配置项相关参数)。然而也有一些特例 (例如,net.netfilter.nf_conntrack_max 和 net.netfilter.nf_conntrack_expect_max 可以在容器命名空间里被更改,但它们是非命名空间的)。

k8s在pod中声明内核参数的方式如下:

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
name: sysctl-example
spec:
securityContext:
sysctls:
- name:
kernel.shm_rmid_forced
value: "0"

业界解决方案

ACK - 安全沙箱容器

阿里云ACK服务的安全沙箱容器,底层实现为runV,pod拥有独立的内核参数,相互之间不受影响。通过扩展pod的annotation来完成内核参数的修改:

1
2
annotations:
securecontainer.alibabacloud.com/sysctls: "net.bridge.bridge-nf-call-ip6tables=1,net.bridge.bridge-nf-call-iptables=1,net.ipv4.ip_forward=1"

阿里云ACK - 配置安全沙箱Pod内核参数

弹性容器实例

完全利用k8s的功能,有限内核参数修改。
https://help.aliyun.com/document_detail/163023.html

参考文档

在RHEL7系统中,firewalld和iptabels两个防火墙的服务并存,firewalld通过调用iptables命令来实现,firewalld和iptables均用来维护系统的netfilter规则,底层实现为内核的netfilter模块。

firewalld为systemd项目的一部分,为python语言实现,跟iptables相比,使用上更加人性化,提供了更高层次的抽象,用户不用关心netfilter的链和表的概念。

架构

最上层为用户界面层,提供了firewall-cmd和firewall-offline-cmd两个命令行工具,其中firewall-cmd为最主要的命令行工具。firewall-config为GUI工具。用户界面层通过firewalld提供的D-Bus接口进行通讯。

firewalld为daemon进程,其中zone、service、ipset等为firewalld抽象的概念。在接收到用户界面层的命令后,一方面需要将操作保存到本地文件,另外还需要调用更底层的如iptables、ipset、ebtables等命令来产生规则,最终在内核层的netfilter模块生效。

firewalld的管理

在RHEL7系统下,firewalld作为systemd家族的一员会默认安装。其service文件定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ cat /usr/lib/systemd/system/firewalld.service 
[Unit]
Description=firewalld - dynamic firewall daemon
Before=network-pre.target
Wants=network-pre.target
After=dbus.service
After=polkit.service
Conflicts=iptables.service ip6tables.service ebtables.service ipset.service
Documentation=man:firewalld(1)

[Service]
EnvironmentFile=-/etc/sysconfig/firewalld
ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS
ExecReload=/bin/kill -HUP $MAINPID
# supress to log debug and error output also to /var/log/messages
StandardOutput=null
StandardError=null
Type=dbus
BusName=org.fedoraproject.FirewallD1
KillMode=mixed

[Install]
WantedBy=multi-user.target
Alias=dbus-org.fedoraproject.FirewallD1.service

查看firewall-cmd的运行状态

1
2
$ firewall-cmd --state
not running

在安装完firewalld后,会在/usr/lib/firewalld/目录下产生默认的配置,该部分配置不可修改。同时会在/etc/firewalld目录下产生firewalld的配置,该部分配置会覆盖/usr/lib/firewalld/下的配置。/etc/firewalld目录下的文件内容如下,其中firewalld.conf文件为最主要的配置文件。

1
2
$ ls 
firewalld.conf helpers icmptypes ipsets lockdown-whitelist.xml services zones

概念

firewalld抽象了zone、service、ipset、helper、icmptypes几个概念。

zone

firewalld 将网络按照安全等级划分了不同的zone,zone的定义位于/usr/lib/firewalld/zones/目录下,文件格式为xml,包括了如下的zone。

  1. drop:只允许出向,任何入向的网络数据包被丢弃,不会回复icmp报文。
  2. block:任何入向的网络数据包均会被拒绝,会回复icmp报文。
  3. public:公共区域网络流量。不信任网络上的流量,选择接收入向的网络流量。
  4. external:不信任网络上的流量,选择接收入向的网络流量。
  5. DMZ:隔离区域,内网和外网增加的一层网络,起到缓冲作用。选择接收入向的网络连接。
  6. work:办公网络,信任网络流量,选择接收入向的网络流量。
  7. home:家庭网络,信任网络流量,选择接收入向的网络流量。
  8. internal:内部网络,信任网络流量,选择接收入向的网络流量。
  9. trusted:信任区域。所有网络连接可接受。

firewalld的默认zone为public,zone目录下的public.xml的内容如下:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"/>
<service name="dhcpv6-client"/>
<service name="cockpit"/>
</zone>

可以看到Public zone关联了三个service对象 ssh、dhcpv6-client、cockpit。没有匹配到该zone的流量,默认情况下会被拒绝。

在配置文件/etc/firewalld/firewalld.conf中,通过DefaultZone字段指定的默认zone为public。即如果开启了firewalld规则,那么默认仅会放行访问上述三个服务的流量。执行 iptables-save 命令实际上并为看到任何的iptables规则,说明firewalld是直接调用内核的netfilter来实现的。

service

service为firewalld对运行在宿主机上的进程的抽象,并在zone文件中跟zone进行绑定。比如ssh.xml的内容如下:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>SSH</short>
<description>Secure Shell (SSH) is a protocol for logging into and executing commands on remote machines. It provides secure encrypted communications. If you plan on accessing your machine remotely via SSH over a firewalled interface, enable this option. You need the openssh-server package installed for this option to be useful.</description>
<port protocol="tcp" port="22"/>
</service>

实践

向某个zone内增加端口号

执行 firewall-cmd --permanent --zone=public --add-port=80/tcp 即可向public zone内增加80端口号。同时可以在 /etc/firewalld/zones/public.xml 文件中看到新增加了80端口号。

1
2
3
4
5
6
7
8
9
10
$ cat public.xml
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Public</short>
<description>For use in public areas. You do not trust the other computers on networks to not harm your computer. Only selected incoming connections are accepted.</description>
<service name="ssh"/>
<service name="dhcpv6-client"/>
<service name="cockpit"/>
<port port="80" protocol="tcp"/>
</zone>

参数 --permanent 为固化到文件中,如果不增加该参数重启firewalld进程后配置会失效。

获取当前zone信息

1
2
3
$ firewall-cmd --get-active-zones
public
interfaces: eth0

每个网络设备可以属于不同的zone,可以根据网络设备名来查询所属的zone

1
2
$ firewall-cmd --get-zone-of-interface=eth0
public

设置当前默认zone

该命令会自动修改/etc/firewalld/firewalld.conf配置文件中的DefaultZone字段。

1
$ firewall-cmd --set-default-zone=trusted

查看当前配置规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports: 80/tcp
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

相关链接

audit简介

audit为linux内核安全体系的重要组成部分,用来记录内核的系统调用,文件修改等事件,用于审计目的。

  • auditctl: 面向用户的工具,类似于iptables命令
  • auditd: 负责将审计信息写入到/var/

启动auditd服务

auditd作为单独的服务运行在系统上,Redhat系统使用systemctl start auditd启动服务,启动后通过 ps -ef | grep auditd查看进程是否启动成功。

auditctl

查看auditd的运行状态

1
2
3
4
5
6
7
8
9
$ auditctl -s
enabled 1
failure 1
pid 638
rate_limit 0
backlog_limit 8192
lost 0
backlog 0
loginuid_immutable 0 unlocked

查看当前环境规则

1
2
3
4
5
$ auditctl -l
-w /tmp/hosts -p rwxa
-w /proc/sys/net/ipv4/tcp_retries1 -p rwxa
-w /proc/sys/net/ipv4/tcp_retries2 -p rwxa
-w /proc/sys/net/ipv4/tcp_retries2 -p wa

删除所有的audit规则

1
2
$ auditctl -D
No rules

实践

监控文件变化

  1. 执行 auditctl -w $file -p wa 来监控文件,比如监控内核参数 auditctl -w /proc/sys/net/ipv4/tcp_retries2 -p wa,其中-p指定了监控文件的行为,支持rwxa。
  2. 查看文件 cat /proc/sys/net/ipv4/tcp_retries2。
  3. 使用vim打开文件 vim /proc/sys/net/ipv4/tcp_retries2。
  4. 执行 ausearch -f /proc/sys/net/ipv4/tcp_retries2 命令查看,可以看到如下的日志
1
2
3
4
5
6
7
8
$ ausearch -f /proc/sys/net/ipv4/tcp_retries1
----
time->Mon Mar 28 12:44:48 2022
type=PROCTITLE msg=audit(1648442688.159:6232591): proctitle=76696D002F70726F632F7379732F6E65742F697076342F7463705F7265747269657331
type=PATH msg=audit(1648442688.159:6232591): item=1 name="/proc/sys/net/ipv4/tcp_retries1" inode=46629229 dev=00:03 mode=0100644 ouid=0 ogid=0 rdev=00:00 objtype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=PATH msg=audit(1648442688.159:6232591): item=0 name="/proc/sys/net/ipv4/" inode=8588 dev=00:03 mode=040555 ouid=0 ogid=0 rdev=00:00 objtype=PARENT cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=CWD msg=audit(1648442688.159:6232591): cwd="/root"
type=SYSCALL msg=audit(1648442688.159:6232591): arch=c000003e syscall=2 success=yes exit=3 a0=11687a0 a1=241 a2=1a4 a3=7ffe33dc14e0 items=2 ppid=8375 pid=8629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=250225 comm="vim" exe="/usr/bin/vim" key=(null)

监控文件夹变化

监控文件夹同样采用跟上述文件相同的方式,但有个问题是如果文件夹下内容较多,会一起监控,从而导致audit的log内容过多。

监控系统定期reboot

执行如下命令:

1
2
auditctl -w /bin/systemctl -p rwxa -k systemd_call
auditctl -a always,exit -F arch=b64 -S reboot -k reboot_call

待系统重启后执行如下命令:

1
ausearch -f reboot

参考文档

RedHat auditd文档

curl

  • –local-port:指定源端口号
  • –proxy:指定本地代理,例如:http://127.0.0.1:52114
  • -d:指定body,如果body比较小,可以直接指定-d 'login=emma&password=123',也可以通过指定文件的方式 -d '@data.txt'

history

bash会将历史命令记录到文件.bash_history中,通过history命令可以查看到历史执行的命令。但history在默认情况下,仅会显示命令,不会展示出执行命令的时间。history命令可以根据环境变量HISTTIMEFORMAT来显示时间,要想显示时间可以执行如下的命令:

1
HISTTIMEFORMAT='%F %T ' history

lrzsz

CentOS rpm包地址:https://rpmfind.net/linux/centos/7.9.2009/os/x86_64/Packages/lrzsz-0.12.20-36.el7.x86_64.rpm

ps

最常用的为ps -efps aux命令,两者的输出结果差不多,其中ps -ef为System V Style风格,ps aux为BSD风格,现在ps命令两者均支持。

1
2
3
4
5
6
7
8
9
10
11
$ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
www-data 1 0.0 0.0 212 8 ? Ss Apr09 0:00 /usr/bin/dumb-init -- /nginx-ingress-controller
www-data 6 0.9 0.3 813500 100456 ? Ssl Apr09 67:20 /nginx-ingress-controller --publish-service
www-data 33 0.0 0.7 458064 242252 ? S Apr09 0:53 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
www-data 46786 0.0 0.7 459976 239996 ? S 17:57 0:00 rollback logs/eagleeye.log interval=60 adjust=600
www-data 46787 1.4 0.8 559120 283328 ? Sl 17:57 2:07 nginx: worker process
www-data 46788 1.1 0.8 558992 284772 ? Sl 17:57 1:38 nginx: worker process
www-data 46789 0.0 0.7 452012 237152 ? S 17:57 0:01 nginx: cache manager process
www-data 46790 0.0 0.8 490832 267600 ? S 17:57 0:00 nginx: x
www-data 47357 0.0 0.0 60052 1832 pts/2 R+ 20:21 0:00 ps aux

每个列的值如下:

  • %MEM:占用内存百分比
  • VSZ: 进程使用的虚拟内存量(KB)
  • RSS:进程占用的固定内存量,驻留在页中的(KB)
  • STAT:进程的状态
  • TIME:进程实际使用的cpu运行时间

pssh

该工具的定位是在多台主机上批量执行pssh命令。

  1. 将文件存放到文件中 /tmp/hosts 中,文件格式如下:
    1
    2
    192.168.1.1
    192.168.1.2
  2. 批量执行shell命令:pssh -h /tmp/hosts -A -i ‘uptime’。

具体参数说明如下:

  • -A: 手工输入密码模式,如果未打通ssh免密,可以在执行pssh命令的时候手工输入主机密码,但要求所有主机密码必须保持一致

rsync

常用命令:

  1. rsync -avz -e 'ssh -p 50023' ~/git/arkctl root@100.67.27.224:/tm

常用参数:

  • --delete: 本地文件删除时,同步删除远程文件
  • -e 'ssh -p 50023': 指定 ssh 端口号
  • --exclude=.git: 忽略同步本地的 git 目录

scp

  • -P:指定端口号

strace

跟踪进程的系统调用

  • -p:指定进程
  • -s:指定输出的字符串的最大大小
  • -f:跟踪由fork调用产生的子进程

wget

  • -P: 当下载文件时,可以指定本地的下载的目录

split

split [-bl] file [prefix]

  • -b, –bytes=SIZE:对file进行切分,每个小文件大小为SIZE。可以指定单位b,k,m。

  • -C,–bytes=SIZE:与-b选项类似,但是,切割时尽量维持每行的完整性。

  • prefix:分割后产生的文件名前缀。

拆分文件:split -b 200m ip_config.gzip ip_config.gzip

文件合并:cat ip_config.gzip* > ip_config.gzip

python

快速开启一个http server python -m SimpleHTTPServer 8080

在python3环境下该命令变更为:python3 -m http.server 8080

格式化 json: cat 1.json | python -m json.tool

awk

按照,打印出一每一列 awk -F, '{for(i=1;i<=NF;i++){print $i;}}'

docker registry

  • 列出镜像:curl http://127.0.0.1:5000/v2/_catalog?n=1000
  • 查询镜像的tag: curl http://127.0.0.1:5000/v2/nginx/tags/list,如果遇到镜像名类似aa/bb的情况,需要转移一下 curl http://127.0.0.1:5000/v2/aa\/bb/tags/list

socat

  • 向本地的 socket 文件发送数据:echo "test" | socat - unix-connect:/tmp/unix.sock
  • 通过交互的方式输入命令:socat - UNIX-CONNECT:/var/lib/kubelet/pod-resources/kubelet.sock

git

  • 删除远程分支 git push origin --delete xxx
  • 强制更新远程分支:git push --force-with-lease origin feature/statefulset
  • 删除本地分支:git branch -D local-branch
  • 拉取远程分支并切换分支:git checkout -b develop origin/remote-branch develop为本地分支,origin/remote-branch为远程分支
  • 给 Github 设置代理
    1
    2
    3
    4
    5
    # 设置代理
    git config --global http.https://github.com.proxy socks5://127.0.0.1:13659

    # 取消代理
    git config --global --unset http.https://github.com.proxy

rpm

  • rpm -ivh xx.rpm:用来安装一个 rpm 包
  • rpm -qa:查看已经安装的包
  • rpm -ql: 查看已经安装的 rpm 的文件内容
  • rpm -qpR *.rpm: 查看rpm包的依赖
  • rpm -e *:要删除的rpm包

cloc

用来统计代码行数

  • cloc .: 用来统计当前的代码行数
  • cloc . --exclude-dir vendor:忽略目录 vendor,--exclude-dir 仅能支持一级目录
  • cloc . --fullpath --not-match-d=pkg/apis/:用来忽略目录 pkg/apis 下的文件

jq

用来解析 json 格式

key 中包含特殊字符,假设文件 data.json 格式如下:

1
2
3
4
5
{
"data": {
"a.json": "abc"
}
}

可以使用 cat data.json | jq '.data."a.json"' 或者 cat data.json | jq '.data["a.json"]' 的方式来解析其中的内容。

-r 参数:
echo '"{}"' | jq . 输出结果为 "{}" 即对应的格式为字符串。echo -r '"{}"' | jq -r . 输出结果为 {},已经解析为标准的 json 格式。

题图为周末的公园露营区。一周前曾经下过一场雪,地上覆盖着厚厚的一层雪,而一星期过后,上面却扎满了帐篷。城市里的人们,在捂了一个冬季后,终于迎来了阳光明媚的春天。虽内心充满着诗和远方,疫情之下,能约上三五好友,在草地上吃上一顿野餐,亦或在帐篷里美美的睡上一觉已是一件很奢侈的事情。

资源

1. Submariner

Rancher开源的一款k8s集群之间的容器网络打通的工具。k8s社区的网络插件中以overlay的网络插件居多,因为overlay的网络对底层物理网络几乎很少有依赖,通常会采用vxlan、IPIP等协议来实现。虽然overlay的网络插件用起来比较方便,但是两个k8s集群的容器网络通常是无法直接通讯,在多k8s集群的应用场景下比较受限。Submariner提供了容器网络的互通方案。

2. k8e

k8e为Kubernetes Easy的简写。社区里有k3s和k0s项目来提供了k8s精简版,本项目在k3s的基础之上又进一步进行了裁剪,移除了一些边缘场景的特性。

3. Rancher Desktop

k8s的发行版SUSE Rancher提供的k8s的桌面客户端,目前已经发布了1.0.0版本。

4. nginx config

Nginx作为最流行的负载均衡配置软件之一,有自己的一套配置语法。DigitalOcean提供的nginx config工具可以通过UI直接进行配置,并最终可以一键生成nginx的配置文件。

5. mizu

一款部署在k8s上的流量分析工具,可以认为是k8s版的tcpdump + wireshark。底层的实现也是基于libpcap抓包的方式,可以支持解析HTTP、Redis、Kafka等协议。

6. kube-bench

互联网安全中心(Center for Internet Security)针对k8s版本提供了一套安全检查的规范,约有200多页的pdf文档,本项目为针对该规范的实现。仅需要向k8s环境中提交一个job,即可得到最终的安全结果。很多公有云厂商也有自己的实现,比如阿里云ACK的实现

7. virtual-kubelet

virtual kubelet服务通过在k8s集群上创建虚拟node,当一个pod调度到虚拟node时,virtual kubelet组件以插件的形式提供了不同的实现,可以将pod创建在k8s集群之外。比如,在阿里云的场景下,可以将pod创建到弹性容器实例ECI上面,从而达到弹性的目的。该项目除了用于公有云一些弹性的场景外,还常用于边缘计算的场景。

8. cloudevents

CloudEvents定义了一种通用的方式描述事件数据的规范,由CNCF的Serverless工作组提出。阿里云的事件总线EventBridge基于此规范提供了比较好商业化产品。

相关链接:EventBridge 事件总线及 EDA 架构解析

9. kubectl-who-can

在k8s系统中,通常会通过RBAC的机制来配置某个账号拥有某种权限,但如果反过来要查询某个权限被哪些账号所拥有,就会麻烦很多。

该工具是一个k8s的命令行小工具,可以用来解决上述需求。比如查询拥有创建namespace权限的ServiceAccount有哪些,可以直接执行 kubectl-who-can create namespace

10. kyverno

Kyverno是一款基于k8s的策略引擎工具,通过抽象CRD ClusterPolicy的方式来声明策略,在运行时通过webhook的技术来执行策略。相比于opa & gatekeeper,更加k8s化,但却没有编程语言的灵活性。目前该项目为CNCF的孵化项目。

文章

1. 进击的Kubernetes调度系统

该系列一共三篇文档,分别讲解了如下内容:

  • 第一篇:k8s 1.16版本引入的Scheduling Framework
  • 第二篇:阿里云ACK服务基于Scheduling Framework实现的Gang scheduling
  • 第三篇:阿里云ACK服务基于Scheduling Framework实现的支持批任务的Binpack Scheduling

2. 中国的云计算革命尚未开始

作者通过工业革命时代的电气化道路做类似,认为当前云计算的阶段仍然比较初级,并且首先要解决的是人的问题,而不是技术本身。

阿里云上有众多的云产品,本文主要分析云产品的功能以及应用场景。

容器与中间件

该部分领域与我的工作重合度较高,为重点研究领域。

事件总线EventBridge

很多服务会产生输出供下游的服务来消费,最常见的解决办法是数据生产者通过rpc调用的方式将消息发送给数据消费者。但假如消费者的数量不止一处,该模式下就需要数据生产者将数据重复发送给多个消费者,该方式对于生产者可配置的灵活性要求非常高。

为了解决上述问题,将生产者和消费者解耦,解决办法就是引入一个中间层,这也是软件架构中最常见的解决复杂问题的方法。引入的中间层即为消息队列类的服务,比如Kafka。生成者仅负责生产数据到消息队列,消费者负责从消息队列消费数据,且可以存在多个消费者。生成者和消费者根本不用关心彼此,仅需要跟消息队列进行交互就可以了。

阿里云上的某个产品新增加了一个新的操作,比如ECS主机完成了一次快照操作,都会产生事件。如果有服务要消费事件,可以使用该产品。

阿里云上的很多产品会将新产生的事件发送到事件总线,另外也支持自定义事件源,通过编程的方式将事件推送到事件源上。

数据的消费模块跟通常的消息队列有所不同,这里的数据消费需要由事件总线产品主动推送消息到对应的服务,具体要推送到哪些服务,则需要在创建总线的时候配置,该功能即消息路由。支持的消费端包括钉钉、消息队列、Serverless服务、HTTP Server等。可以看到数据的消费端除了HTTP Server外,基本不需要额外开发一个服务,也是阿里云的一些其他云产品。

消息的规范完成遵循云原生社区CNCF的CloudEvent规则。

相关链接:

弹性容器实例ECI

该产品的功能比较简单,相当于提供了管控页面来创建k8s的pod,具体pod部署在哪里用户不需要关心,提供了非常好的弹性能力,充分发挥了云的优势。

具体在实现层面,实际上会以pod的形式部署在阿里云维护的公共k8s集群中,且容器的网络在用户指定的vpc中。

除了给用户提供直接创建容器实例外,还有很大一部分功能是给Serverless Kubernetes(ASK)和容器服务(ACK)来提供弹性扩缩容的功能。

企业级分布式应用服务EDAS

提供了应用托管和微服务的治理能力。

在应用托管方面支持应用发布到虚拟机和k8s两种方式。

微服务治理方面支持了Spring Cloud、Dubbo、HSF三种微服务框架。

Serverless应用引擎SAE

提供了类似于knative的serving功能,可以支持应用的托管,用户不需要关心底层的服务器资源,可以自动将用户的应用部署在托管的k8s集群中,且具备秒级的弹性扩缩容的功能。

Serverless工作流SWF

软件工程

康威定律

任何一个组织在设计一个系统的时候,这个系统的结构与这个组织的沟通结构是一致的。
工作了这么些年对此深有感触,即“组织架构决定软件架构”。

布鲁克定律

在一个已经延期的项目中增加人手只会让项目延期更长。
我个人不是特别认可此定律,该定律肯定是项目而定的,这要看项目的协作复杂程度,如果是体力劳动居多的项目,那么堆人还是特别好使的。

帕金森定律

一项工作会占用掉所有用来完成它的时间。即如果不给一个项目设置截止日期,那么该项目就永远完成不了。安排多少时间,就会有多少工作。

冰山谬论

一款新软件的开发成本只占管理层预算的总成本的25%左右。

其他

黄金圈法则

著名的营销顾问西蒙斯.涅克提出了一个“黄金圈”理论:三个同心圆,最里面的一个是Why,中间一层是How,最外面一层是What。

大多数人的思维方式是想做什么(what)和怎么做(how),不太考虑为什么这么做(why)。

本理论提倡的思维方式为:

  1. Why:最内层——为什么,做一件事的原因或目的,也可以说是理念和宗旨,属于战略层面;
  2. How:中间层——怎么做,针对这个目的或理念的计划,也即如何去做好这件事情,属于战术层面;
  3. What:最外层——是什么,最终得到什么,或者要做哪些具体的事,这基本是事情的表象,主要是执行层面的东西。

该法则在软件行业的述职晋升等场景下非常适用。

  1. Why:描述为什么做这个项目?
  2. How:做这个项目遇到的挑战有哪些,是怎么解决的。挑战和解决方法可以一一对应起来。
  3. What:项目的最终结果,最好有具体的可以量化的指标。

SWOT分析法

常见的战略分析方法,对研究对象进行全面、系统、准确的研究。

金字塔原理

参见《金字塔原理总结

成功的面试 = 把握正确清晰的用人标准 + 挖掘真实匹配的应聘者信息 = 以素质模型去“发问” + 用STAR方式去“追问”

STAR行为面试法

STAR是业界公认的最为有效的面试方法之一,为背景(Situation)、任务(Task)、行为(Action)、结果(Result)的缩写。该方法不仅用于面试的场合,也会用于述职、晋升答辩等场景。

任务(Task)描述在事情里的担任的角色和负责的任务。

行为(Action)是最关键部分,要了解做了什么,展现出了哪些能力。

结果(Result)部分通常需要虚实结合,且重点在实,围绕效率、效果、质量和成本四个维度量化评估。

STAR方法同样适用于述职汇报或者晋升中。

奥克姆剃刀理论

如无必要,勿增实体。

马斯洛需求层次理论

心理学中重要理论,将人类的需求分为五个层级:

  1. 生理
  2. 安全
  3. 社交
  4. 尊重
  5. 自我实现

人类的需求为逐步递进的,在满足了基本需求后,就会去实现更高的需求和目标。

在工作中,经常会用类似马斯洛需求层次理论中的金字塔结构来解释一些其他的有递进关系的场景 ,比如一个软件产品的设计目标。

SMART 原则

确定目标的五原则,通常用在绩效考核中。

  1. S(Special):目标必须是具体的
  2. M(Measurable):目标必须是可衡量的
  3. A(Attainable):目标必须是可实现的
  4. R(Relevant):与其他的目标有一定的相关性
  5. T(Time-bound):目标必须有完成的期限

常见名词

ROI:投入产出比

pod状态

1. 前置检查

在排查异常状态的pod错误之前,可以先检查一下node状态,执行kubectl get node查看是否所有的node状态都正常。

2. pod状态为CrashLoopBackOff

如果pod状态为CrashLoopBackOff状态,查看pod日志来定位原因,或者describe pod看一下。

3. pod状态为Pending

pod状态为Pending状态,说明调度失败,通常跟污点、标签、cpu、内存、磁盘等资源相关。

可以通过kubectl describe pod -n {NAMESPACE} {POD_NAME},可以在最后的Event部分找到原因。

4. pod状态为Init:0/1

有些pod会有Init Containers,这些container是在pod的containers执行之前先执行。如果Init Container出现未执行完成的情况,此时pod处于Init状态。

通过kubectl get pod -n {NAMESPACE} {POD_NAME} -o yaml 找到pod的Init Containers,并找到其中的name字段。执行kubectl logs -n {NAMESPACE} {POD_NAME} -c {INIT_CONTAINER_NAME}可以查看Init Container的日志来分析原因。

5. pod状态为Terminating

pod处于此种状态的原因大致可分为:
1、pod或其控制器被删除。
解决方法:查看pod控制器类型和控制器名称,查看其控制器是否正常。如果正常pod将会被重建,如果pod没有被重建,查看controller-manager是否正常。
2、pod所在节点状态NotReady导致。
解决方法:检查该节点的网络,cpu,内存,磁盘空间等资源是否正常。检查该节点的kubelet、docker服务是否正常。检查该节点的网络插件pod是否正常。

最常见的pod处于Terminating状态的解决办法为强制删除 kubectl delete pods -n ${namespace} ${name} --grace-period=0 --force

6. pod状态为Evicted

pod处于Evicted的原因大致可分为:
1、kubelet服务启动时存在驱逐限制当节点资源可用量达到指定阈值(magefs.available<15%,memory.available<300Mi,nodefs.available<10%,nodefs.inodesFree<5%)
会优先驱逐Qos级别低的pod以保障Qos级别高的pod可用。
解决方法:增加节点资源或将被驱逐的pod迁移到其他空闲资源充足的节点上。
2、pod所在节点上被打上了NoExecute的污点,此种污点会将该节点上无法容忍此污点的pod进行驱逐。
解决方法:查看该节点上的NoExecute污点是否必要。或者pod是否可以迁移到其他节点。
3、pod所在的节点为NotReady状态

通常可以通过kubectl describe pods ${pod} -n ${namespace}的底部的Events信息来找到一些问题的原因。例如下面例子中可以看到DiskPressure信息,说明跟磁盘相关。

1
2
3
4
5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Evicted 61s kubelet, acs.deploy The node had condition: [DiskPressure].
Normal Scheduled <invalid> default-scheduler Successfully assigned ark-system/bridge-console-bridge-console-554d57bb87-nh2vd to acs.deploy

或者根据pod的Message字段来找到原因

1
2
3
4
5
6
7
8
9
10
11
12
Name:           tiller-deploy-7f6456894f-22vgr
Namespace: kube-system
Priority: 0
Node: a36e04001.cloud.e04.amtest17/
Start Time: Mon, 08 Jun 2020 12:17:26 +0800
Labels: app=helm
name=tiller
pod-template-hash=7f6456894f
Annotations: <none>
Status: Failed
Reason: Evicted
Message: Pod The node had condition: [DiskPressure].

由于pod驱逐的原因排查跟时间点相关,需要根据pod被驱逐的时间来分析当时的状态。
4、批量删除状态Evicted的pod,此操作会删除集群里面所有状态为Evicted的pod

1
2
ns=`kubectl get ns | awk 'NR>1 {print $1}'`
for i in $ns;do kubectl get pods -n $i | grep Evicted| awk '{print $1}' | xargs kubectl delete pods -n $i ;done

7. pod状态为Unknown

通常该状态为pod对应节点的为NotReady,通过查看 kubectl get node 来查看node是否为NotReady。

8. pod为running,但是Not Ready状态

1
argo-ui-56f4d67b69-8gshr   0/1     Running     0          10h

类似上面这种状态,此时说明pod的readiness健康检查没过导致的,需要先从pod的健康检查本身来排查问题。可以通过 kubectl get pods -n ${namespace} ${name} -o yaml 找到pod的健康检查部分,关键字为readiness,然后进入pod中执行对应的健康检查命令来测试健康检查的准确性。

例如readiness的配置如下,需要进入pod中执行curl http://127.0.0.1/api/status,也可以在pod对应的node节点上执行curl [http://${pod_ip}/api/status](http://127.0.0.1/api/status)

1
2
3
4
5
6
7
8
9
10
livenessProbe:
failureThreshold: 3
httpGet:
path: /api/status
port: http
scheme: HTTP
initialDelaySeconds: 120
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1

9. pod为ContainerCreating状态

通过kubectl describe pods -n ${namespace} ${name}的Events部分来分析原因,可能的原因如下:

  • 网络分配ip地址失败

10. Init:CrashLoopBackOff

  1. 通过kubectl get pod -n {NAMESPACE} {POD_NAME} -o yaml 找到pod的Init Containers,并找到其中的name字段。
  2. 执行kubectl logs -n {NAMESPACE} {POD_NAME} -c {INIT_CONTAINER_NAME}可以查看Init Container的日志来分析原因。

11. PodInitializing

需要查看initContainer的日志

12. MatchNodeSelector

查看pod的status信息可以看到如下信息:

1
2
3
4
5
status:
message: Pod Predicate MatchNodeSelector failed
phase: Failed
reason: MatchNodeSelector
startTime: "2022-03-15T05:07:57Z"

说明该pod没有调度成功,在predicate的MatchNodeSelector阶段失败了,没有匹配上node节点。

在k8s 1.21之前的版本,存在bug,节点重启后可能遇到过问题,将pod delete后重新调度可以解决。https://github.com/kubernetes/kubernetes/issues/92067

参考

排查k8s上问题通常需要监控的配合,而k8s上的监控标准为prometheus,prometheus的dashboard最通用的为grafana。本文用来记录排查k8s问题时经常遇到的dashboard,dashboard监控的数据来源包括node-exporter、metrics-server、kube-state-metrics等最场景。

node top监控

下载链接

node上的pod监控

下载链接

0%