Skip to content

Commit

Permalink
fix rss Input is not proper UTF-8
Browse files Browse the repository at this point in the history
  • Loading branch information
cvvz committed Dec 22, 2021
1 parent 9a8e7ed commit e376363
Show file tree
Hide file tree
Showing 18 changed files with 45 additions and 48 deletions.
2 changes: 1 addition & 1 deletion content/post/arm-login-failed.md
Expand Up @@ -22,4 +22,4 @@ tags: ["Linux"]
1. 两种登录方式首先都要经过PAM插件的处理,SSH登录是由SSHD通过子进程的方式启动shell,串口登录则是拉起/bin/login,由/bin/login启动shell替代自己。
2. shell启动后,会去执行`/etc/profile`中的一系列脚本,配置系统环境。

这里面可能出问题的环节有:PAM插件、/bin/login进程、/bin/bash和/etc/profile。但这个问题的现象是/bin/bash被拉起后,很快又闪退了,因此问题肯定出在/etc/profile脚本中。最后排查发现脚本中限制了串口登录的终端设备名为`ttyS0`,否则直接退出。但新的ARM设备的串行终端名称是`ttyAMA0`
这里面可能出问题的环节有:PAM插件、/bin/login进程、/bin/bash和/etc/profile。但这个问题的现象是/bin/bash被拉起后,很快又闪退了,因此问题肯定出在/etc/profile脚本中。最后排查发现脚本中限制了串口登录的终端设备名为`ttyS0`,否则直接退出。但新的ARM设备的串行终端名称是`ttyAMA0`
4 changes: 2 additions & 2 deletions content/post/call-stack.md
Expand Up @@ -167,7 +167,7 @@ Dump of assembler code for function main:
End of assembler dump.
```

查看此时栈帧指针和栈顶指针的值:
查看此时栈帧指针和栈顶指针的值:

```armasm
(gdb) i r r11 sp
Expand All @@ -181,7 +181,7 @@ __libc_start_main + 276 in section .text of /lib/arm-linux-gnueabihf/libc.so.6

可以看到,栈帧指针指向的返回地址是`__libc_start_main + 276`,即**main函数是由__libc_start_main调用的**

由前面分析得知,栈帧指针-4地址处存放的是上一个函数的栈帧指针,于是我们继续向上追溯`__libc_start_main`的调用者地址,可以发现其值为0:
由前面分析得知,栈帧指针-4地址处存放的是上一个函数的栈帧指针,于是我们继续向上追溯`__libc_start_main`的调用者地址,可以发现其值为0:

```armasm
(gdb) x /xw 0x7efffaec-4
Expand Down
2 changes: 1 addition & 1 deletion content/post/celery-worker-zombie.md
Expand Up @@ -92,7 +92,7 @@ pid 78217

从这个结果我们可以看出,**fork出来的子进程虽然和父进程不共享堆栈(子进程获得父进程堆栈的副本),但是他们共享正文段**,所以他们都执行了程序的最后一行,各自输出了自己的pid。

接着来分析上述worker的代码,在`execute()`中,通过两次fork,最终使得`do_execute()`运行在一个孤儿进程中,如果正常运行,最终会执行`os._exit(0)`正常退出。然而,如果运行过程中抛出异常又会发生什么呢?根据父子进程共享正文段这一结论,我们可以知道这个孤儿进程抛出的异常会被第32行的`except`捕获到,并继续向上抛出异常,然后会被第9行`worker()`中的`except`捕获,并执行`on_failure()`**也就是说,这个孤儿进程最终执行到了worker的代码里去了,而worker本身是一个死循环,因此这个孤儿进程就不会退出了。理论上来说,最终它会运行到第4行,成为一个“worker副本”,等待接收任务**
接着来分析上述worker的代码,在`execute()`中,通过两次fork,最终使得`do_execute()`运行在一个孤儿进程中,如果正常运行,最终会执行`os._exit(0)`正常退出。然而,如果运行过程中抛出异常又会发生什么呢?根据父子进程共享正文段这一结论,我们可以知道这个孤儿进程抛出的异常会被第32行的`except`捕获到,并继续向上抛出异常,然后会被第9行`worker()`中的`except`捕获,并执行`on_failure()`**也就是说,这个孤儿进程最终执行到了worker的代码里去了,而worker本身是一个死循环,因此这个孤儿进程就不会退出了。理论上来说,最终它会运行到第4行,成为一个“worker副本”,等待接收任务**

至于为什么kill worker的父进程会导致worker变僵尸进程,需要深入研究一下celery源码中的信号处理方法。猜测是父进程在退出前,会先保证所有worker子进程已经退出,而它误以为这个“worker副本”也是自己的子进程,但是却没办法通过向子进程发送信号的方式使其退出,于是就阻塞住了自己的退出流程。而其他已经正常退出的worker就会一直处于僵尸状态。

2 changes: 1 addition & 1 deletion content/post/container-network.md
Expand Up @@ -28,7 +28,7 @@ autoCollapseToc: false
以Docker为例,在bridge模式下:

- Docker Daemon第一次启动时会创建`docker0`网桥;
- 在创建容器时,会创建一个veth pair,即veth设备对。
- 在创建容器时,会创建一个veth pair,即veth设备对。

veth pair有两个端点:

Expand Down
11 changes: 4 additions & 7 deletions content/post/container.md
Expand Up @@ -88,7 +88,7 @@ cpu cgroup能限制cpu的使用率,但是cpu cgroup并没有办法解决平均

### cpuset cgroup

cpuset cgroup用于进程绑核,主要通过设置`cpuset.cpus``cpuset.mems`两个字段来实现。
cpuset cgroup用于进程绑核,主要通过设置`cpuset.cpus``cpuset.mems`两个字段来实现。

在kubernetes中,当 Pod 属于 Guaranteed QoS 类型,并且 requests 值与 limits 被设置为同一个相等的**整数值**就相当于声明Pod中的容器要进行绑核。

Expand All @@ -107,7 +107,7 @@ cpuset cgroup用于进程绑核,主要通过设置`cpuset.cpus`和`cpuset.mem
>
> Q:执行 `kubectl top` 命令获取到的pod指标是从哪里来的?
>
> A:整个执行路径是:`kubectl -> apiserver -> aggregated-apiserver -> metric-server -> kubelet(cAdvisor) -> cgroup`。最终来源就是cgroup。而Linux `top`命令的指标数据的来源是`/proc`文件系统。
> A:整个执行路径是:`kubectl -> apiserver -> aggregated-apiserver -> metric-server -> kubelet(cAdvisor) -> cgroup`。最终来源就是cgroup。而Linux `top`命令的指标数据的来源是`/proc`文件系统。
## kubelet、Docker、CRI、OCI

Expand All @@ -123,14 +123,11 @@ kubelet和docker的集成方案:

`kubelet -> dockershim -> docker daemon -> containerd -> containerd-shim -> runc -> container`

dockershim实现了[CRI](https://github.com/kubernetes/kubernetes/blob/8327e433590f9e867b1e31a4dc32316685695729/pkg/kubelet/apis/cri/services.go)定义的gRPC接口,实现方式就是充当docker daemon的客户端,向docker daemon发送命令。实际上dockershim和docker daemon都可以被干掉,[kubernetes在v1.20也的确这么做了](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation)。docker从kubernetes中被移除后,我们可以直接使用[containerd](https://github.com/containerd/containerd)[CRI-O](https://github.com/cri-o/cri-o)作为CRI。
dockershim实现了[CRI](https://github.com/kubernetes/kubernetes/blob/8327e433590f9e867b1e31a4dc32316685695729/pkg/kubelet/apis/cri/services.go)定义的gRPC接口,实现方式就是充当docker daemon的客户端,向docker daemon发送命令。实际上dockershim和docker daemon都可以被干掉,[kubernetes在v1.20也的确这么做了](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation)。docker从kubernetes中被移除后,我们可以直接使用[containerd](https://github.com/containerd/containerd)[CRI-O](https://github.com/cri-o/cri-o)作为CRI。

[runC](https://github.com/opencontainers/runc)则是一个[OCI](https://github.com/opencontainers/runtime-spec)的参考实现,底层通过Linux系统调用为容器设置 namespaces 和 cgroups, 挂载 rootfs。当然kubernetes其实不关心OCI的底层是怎么实现的,只要能保证遵循OCI文档里的标准,就能自己实现一个OCI。[Kata](https://github.com/kata-containers/kata-containers)就是遵循了OCI标准实现的安全容器。它的底层是用虚拟机实现的资源强隔离,而不是namespace。

Kata中的VM可以和Pod做一个类比:

* kubelet调用CRI的`RunPodSandbox`接口时,如果是runC实现的OCI,则会去创建`infra`容器,并执行`/pause`将容器挂起;如果是Kata,则会去创建一个虚拟机。
* 接着kubelet调用`CreateContainer`去创建容器,对于runC,就是创建容器进程并将他们的namespace加入`infra`容器中去;对于Kata,则是往VM中添加容器。



* 接着kubelet调用`CreateContainer`去创建容器,对于runC,就是创建容器进程并将他们的namespace加入`infra`容器中去;对于Kata,则是往VM中添加容器。
2 changes: 1 addition & 1 deletion content/post/coredump.md
Expand Up @@ -29,4 +29,4 @@ tags: []

问题发生的原因是这个异步回调接口返回的太慢,调用方函数已经运行完毕,此时栈空间已经被操作系统回收并分配给其他函数,这时再执行回调函数修改原先的局部变量,就造成了踩内存。

这里原先的局部变量是个`int`类型,回调函数想将其修改为1,结果就成了将一个`char*`类型的变量值修改为了`0x01`
这里原先的局部变量是个`int`类型,回调函数想将其修改为1,结果就成了将一个`char*`类型的变量值修改为了`0x01`
6 changes: 3 additions & 3 deletions content/post/k8s-3-deletion-webhook.md
Expand Up @@ -9,11 +9,11 @@ toc: true
autoCollapseToc: false
---

最近在玩admission webhook时,发现一个奇怪的现象:我配置了validatingWebhookConfiguration使其监听pod的删除操作,结果发现每次删除Pod的时候,webhook会收到三次delete请求:
最近在玩admission webhook时,发现一个奇怪的现象:我配置了validatingWebhookConfiguration使其监听pod的删除操作,结果发现每次删除Pod的时候,webhook会收到三次delete请求:

{{< figure src="/3-delete.png" width="1000px" >}}

从日志打印上可以分析出,第一次删除请求来自于kubectl客户端,后面两次来自于pod所在的node节点。为什么会收到三次delete请求呢?
从日志打印上可以分析出,第一次删除请求来自于kubectl客户端,后面两次来自于pod所在的node节点。为什么会收到三次delete请求呢?

## 删除一个Pod的过程

Expand Down Expand Up @@ -184,7 +184,7 @@ func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubec
### statusManager发送删除请求
kubelet以goroutine的方式运行着一个`statusManager`,它的作用就是周期性的监听Pod的状态变化,然后执行`func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {`。在`syncPod`中,注意到有如下的逻辑:
kubelet以goroutine的方式运行着一个`statusManager`,它的作用就是周期性的监听Pod的状态变化,然后执行`func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {`。在`syncPod`中,注意到有如下的逻辑:
```go
func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
Expand Down
2 changes: 1 addition & 1 deletion content/post/k8s-monitor.md
Expand Up @@ -15,7 +15,7 @@ autoCollapseToc: false

我们可以把监控系统划分为:采集指标、存储、展示和告警四个部分。

存储使用时序数据库TSDB、前端展示使用grafana、告警系统也有多种开源实现。我重点介绍一下和指标采集相关的内容。
存储使用时序数据库TSDB、前端展示使用grafana、告警系统也有多种开源实现。我重点介绍一下和指标采集相关的内容。

### 理解指标

Expand Down
4 changes: 2 additions & 2 deletions content/post/k8s-network-DNS.md
Expand Up @@ -11,7 +11,7 @@ tags: ["kubernetes", "DNS"]

## 默认DNS策略

Pod默认的[dns策略](https://kubernetes.io/zh/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy)`ClusterFirst`,意思是先通过kubernetes的**权威DNS服务器**(如CoreDNS)直接解析出A记录或CNAME记录;如果解析失败,再根据配置,将其转发给**上游DNS服务器**。以CoreDNS为例,它的配置文件Corefile如下所示:
Pod默认的[dns策略](https://kubernetes.io/zh/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy)`ClusterFirst`,意思是先通过kubernetes的**权威DNS服务器**(如CoreDNS)直接解析出A记录或CNAME记录;如果解析失败,再根据配置,将其转发给**上游DNS服务器**。以CoreDNS为例,它的配置文件Corefile如下所示:

```shell
~ kubectl get cm -n kube-system coredns -o yaml
Expand Down Expand Up @@ -103,7 +103,7 @@ Name: kubernetes.default.svc.cluster.local
Address: 192.168.0.1
```

Headless Service的域名解析稍微复杂一点。
Headless Service的域名解析稍微复杂一点。

> ClusterIP可以看作是Service的头,而Headless Service,顾名思义也就是指定他的ClusterIP为None的Service。
Expand Down
8 changes: 4 additions & 4 deletions content/post/k8s-network-cross-host.md
Expand Up @@ -38,7 +38,7 @@ tags: ["kubernetes","CNI","容器"]

所谓underlay,也就是没有在宿主机网络上的虚拟层,容器和宿主机处于同一个网络层面上。

> 在这种情形下,Kubernetes 内外网络是互通的,运行在kubernetes中的容器可以很方便的和公司内部已有的非云原生基础设施进行联动,比如DNS、负载均衡、配置中心等,而不需要借助kubernetes内部的DNS、ingress和service做服务发现和负载均衡。
> 在这种情形下,Kubernetes 内外网络是互通的,运行在kubernetes中的容器可以很方便的和公司内部已有的非云原生基础设施进行联动,比如DNS、负载均衡、配置中心等,而不需要借助kubernetes内部的DNS、ingress和service做服务发现和负载均衡。
所谓overlay,其实就是在容器的IP包外面附加额外的数据包头,然后**整体作为宿主机网络报文中的数据进行传输**。容器的IP包加上额外的数据包头就用于跨主机的容器之间通信,**容器网络就相当于覆盖(overlay)在宿主机网络上的一层虚拟网络**。如下图所示:

Expand All @@ -64,14 +64,14 @@ tags: ["kubernetes","CNI","容器"]

Flannel VXLAN模式的原理和UDP模式差不多,区别在于:

1. UDP模式创建的是TUN设备(flannel0),VXLAN模式创建的是VTEP设备(flannel.1)。
1. UDP模式创建的是TUN设备(flannel0),VXLAN模式创建的是VTEP设备(flannel.1)。
2. VTEP设备全程工作在内核态,性能比UDP模式更好。

VXLAN模式的工作流程:

1. container-1根据默认路由规则,将IP包发往cni网桥,出现在宿主机的网络栈上;
2. flanneld预先在宿主机上创建好了路由规则,数据包到达cni网桥后,随即被转发给了flannel.1,flannel.1是一个VTEP设备,**它既有 IP 地址,也有 MAC 地址**
3. **在node2上的目的VTEP设备启动时,node1上的flanneld会将目的VTEP设备的IP地址和MAC地址分别写到node1上的路由表和ARP缓存表中**。
3. **在node2上的目的VTEP设备启动时,node1上的flanneld会将目的VTEP设备的IP地址和MAC地址分别写到node1上的路由表和ARP缓存表中**
4. 因此,node1上的flannel.1通过查询路由表,知道要发往目的容器,需要经过10.1.16.0这个网关。**其实这个网关,就是目的VTEP设备的ip地址**
```shell
$ route -n
Expand All @@ -80,7 +80,7 @@ tags: ["kubernetes","CNI","容器"]
...
10.1.16.0 10.1.16.0 255.255.255.0 UG 0 0 0 flannel.1
```
5. 又由于**这个网关的MAC地址,事先已经被flanneld写到了ARP缓存表中**,所以内核直接把目的VTEP设备的MAC地址封装到链路层的帧头即可:
5. 又由于**这个网关的MAC地址,事先已经被flanneld写到了ARP缓存表中**,所以内核直接把目的VTEP设备的MAC地址封装到链路层的帧头即可:
{{< figure src="/flannel-vxlan-frame.jpg" width="500px">}}
6. **flanneld还负责维护FDB(转发数据库)中的信息**,查询FDB,就可以通过这个目的VTEP设备的MAC地址找到宿主机Node2的ip地址。
7. 有了目的IP地址,接下来进行一次常规的、宿主机网络上的封包即可。
Expand Down
10 changes: 5 additions & 5 deletions content/post/k8s-network-service.md
Expand Up @@ -30,15 +30,15 @@ kubernetes 10.20.126.169:6443,10.28.116.8:6443,10.28.126.199:6443 348d
```

1. 首先数据包从容器中被路由到cni网桥,出现在宿主机网络栈中。
2. Netfilter在`PREROUTING`链中处理该数据包,最终会将其转到`KUBE-SERVICES`链上进行处理:
2. Netfilter在`PREROUTING`链中处理该数据包,最终会将其转到`KUBE-SERVICES`链上进行处理:
```shell
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
```
3. `KUBE-SERVICES`链将目的地址为`192.168.0.1`的数据包跳转到`KUBE-SVC-NPX46M4PTMTKRN6Y`链进行处理:
```shell
-A KUBE-SERVICES -d 192.168.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
```
4. `KUBE-SVC-NPX46M4PTMTKRN6Y`链以**相等概率**将数据包跳转到`KUBE-SEP-A66XJ5Q22M6AZV5X`、`KUBE-SEP-TYGT5TFZZ2W5DK4V``KUBE-SEP-KQD4HGXQYU3ORDNS`链进行处理:
4. `KUBE-SVC-NPX46M4PTMTKRN6Y`链以**相等概率**将数据包跳转到`KUBE-SEP-A66XJ5Q22M6AZV5X``KUBE-SEP-TYGT5TFZZ2W5DK4V``KUBE-SEP-KQD4HGXQYU3ORDNS`链进行处理:
```shell
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-A66XJ5Q22M6AZV5X
-A KUBE-SVC-NPX46M4PTMTKRN6Y -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-TYGT5TFZZ2W5DK4V
Expand Down Expand Up @@ -72,7 +72,7 @@ kube-prox 253942 root 12u IPv6 1852002168 0t0 TCP *:31849 (LISTEN)
```shell
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
```
3. 先跳到`KUBE-MARK-MASQ`链打上**特殊记号`0x4000/0x4000`**,这个特殊记号**后续在`POSTROUTING`链中进行SNAT时用到**
3. 先跳到`KUBE-MARK-MASQ`链打上**特殊记号`0x4000/0x4000`**,这个特殊记号**后续在`POSTROUTING`链中进行SNAT时用到**
```shell
-A KUBE-NODEPORTS -p tcp -m comment --comment "default/webapp:" -m tcp --dport 31849 -j KUBE-MARK-MASQ

Expand All @@ -87,7 +87,7 @@ kube-prox 253942 root 12u IPv6 1852002168 0t0 TCP *:31849 (LISTEN)
```shell
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
```
这条规则的意思就是:带有`0x4000/0x4000`这个特殊标记的数据包在离开节点之前,在`POSTROUTING`链上进行一次SNAT,即`MASQUERADE`。而这个特殊标记,如前所述,是在外部客户端数据流入节点时打上去的。
这条规则的意思就是:带有`0x4000/0x4000`这个特殊标记的数据包在离开节点之前,在`POSTROUTING`链上进行一次SNAT,即`MASQUERADE`。而这个特殊标记,如前所述,是在外部客户端数据流入节点时打上去的。

## 总结

Expand All @@ -112,7 +112,7 @@ kube-prox 253942 root 12u IPv6 1852002168 0t0 TCP *:31849 (LISTEN)

## kube-proxy的IPVS模式

上述流程描述的是kube-proxy的iptables模式的工作流程,这个模式最大的问题在于:
上述流程描述的是kube-proxy的iptables模式的工作流程,这个模式最大的问题在于:

* kube-proxy需要为service配置大量的iptables规则,并且刷新这些规则以确保正确性;
* iptables的规则是以链表的形式保存的,对iptables的刷新需要遍历链表
Expand Down

0 comments on commit e376363

Please sign in to comment.