diff --git a/content/post/arm-login-failed.md b/content/post/arm-login-failed.md index fda978d..d4d4bde 100644 --- a/content/post/arm-login-failed.md +++ b/content/post/arm-login-failed.md @@ -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`。 diff --git a/content/post/call-stack.md b/content/post/call-stack.md index ccee93c..f22cf10 100644 --- a/content/post/call-stack.md +++ b/content/post/call-stack.md @@ -167,7 +167,7 @@ Dump of assembler code for function main: End of assembler dump. ``` -查看此时栈帧指针和栈顶指针的值: +查看此时栈帧指针和栈顶指针的值: ```armasm (gdb) i r r11 sp @@ -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 diff --git a/content/post/celery-worker-zombie.md b/content/post/celery-worker-zombie.md index d2e285b..12169cf 100644 --- a/content/post/celery-worker-zombie.md +++ b/content/post/celery-worker-zombie.md @@ -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就会一直处于僵尸状态。 diff --git a/content/post/container-network.md b/content/post/container-network.md index 2a89b86..daa1408 100644 --- a/content/post/container-network.md +++ b/content/post/container-network.md @@ -28,7 +28,7 @@ autoCollapseToc: false 以Docker为例,在bridge模式下: - Docker Daemon第一次启动时会创建`docker0`网桥; -- 在创建容器时,会创建一个veth pair,即veth设备对。 +- 在创建容器时,会创建一个veth pair,即veth设备对。 veth pair有两个端点: diff --git a/content/post/container.md b/content/post/container.md index c852d3c..d317830 100644 --- a/content/post/container.md +++ b/content/post/container.md @@ -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中的容器要进行绑核。 @@ -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 @@ -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中添加容器。 diff --git a/content/post/coredump.md b/content/post/coredump.md index 075faa6..371a255 100644 --- a/content/post/coredump.md +++ b/content/post/coredump.md @@ -29,4 +29,4 @@ tags: [] 问题发生的原因是这个异步回调接口返回的太慢,调用方函数已经运行完毕,此时栈空间已经被操作系统回收并分配给其他函数,这时再执行回调函数修改原先的局部变量,就造成了踩内存。 -这里原先的局部变量是个`int`类型,回调函数想将其修改为1,结果就成了将一个`char*`类型的变量值修改为了`0x01`。 \ No newline at end of file +这里原先的局部变量是个`int`类型,回调函数想将其修改为1,结果就成了将一个`char*`类型的变量值修改为了`0x01`。 \ No newline at end of file diff --git a/content/post/k8s-3-deletion-webhook.md b/content/post/k8s-3-deletion-webhook.md index be79fd1..8fe336e 100644 --- a/content/post/k8s-3-deletion-webhook.md +++ b/content/post/k8s-3-deletion-webhook.md @@ -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的过程 @@ -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) { diff --git a/content/post/k8s-monitor.md b/content/post/k8s-monitor.md index 545c486..c87529f 100644 --- a/content/post/k8s-monitor.md +++ b/content/post/k8s-monitor.md @@ -15,7 +15,7 @@ autoCollapseToc: false 我们可以把监控系统划分为:采集指标、存储、展示和告警四个部分。 -存储使用时序数据库TSDB、前端展示使用grafana、告警系统也有多种开源实现。我重点介绍一下和指标采集相关的内容。 +存储使用时序数据库TSDB、前端展示使用grafana、告警系统也有多种开源实现。我重点介绍一下和指标采集相关的内容。 ### 理解指标 diff --git a/content/post/k8s-network-DNS.md b/content/post/k8s-network-DNS.md index ce3544c..589f705 100644 --- a/content/post/k8s-network-DNS.md +++ b/content/post/k8s-network-DNS.md @@ -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 @@ -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。 diff --git a/content/post/k8s-network-cross-host.md b/content/post/k8s-network-cross-host.md index d675cac..2d1608f 100644 --- a/content/post/k8s-network-cross-host.md +++ b/content/post/k8s-network-cross-host.md @@ -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)在宿主机网络上的一层虚拟网络**。如下图所示: @@ -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 @@ -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地址,接下来进行一次常规的、宿主机网络上的封包即可。 diff --git a/content/post/k8s-network-service.md b/content/post/k8s-network-service.md index c33912a..47e990a 100644 --- a/content/post/k8s-network-service.md +++ b/content/post/k8s-network-service.md @@ -30,7 +30,7 @@ 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 ``` @@ -38,7 +38,7 @@ kubernetes 10.20.126.169:6443,10.28.116.8:6443,10.28.126.199:6443 348d ```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 @@ -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 @@ -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`。而这个特殊标记,如前所述,是在外部客户端数据流入节点时打上去的。 ## 总结 @@ -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的刷新需要遍历链表 diff --git a/content/post/k8s-volume.md b/content/post/k8s-volume.md index efb9aba..af2757d 100644 --- a/content/post/k8s-volume.md +++ b/content/post/k8s-volume.md @@ -37,7 +37,7 @@ docker run -v /home:/test ... 3. 调用 `pivot_root` 或 `chroot`,改变容器进程的根目录。至此,容器再也看不到宿主机的文件系统目录了。 -## kubelet挂载卷的过程 +## kubelet挂载卷的过程 当一个Pod被调度到一个节点上之后,kubelet首先为这个Pod在宿主机上创建一个Volume目录: @@ -81,9 +81,9 @@ hostPath类型的挂载方式,和宿主机上的Volume目录没啥关系,就 在Pod中,如果想使用持久化的存储,如上面提到的远程块存储、NFS存储,或是本地块存储(非hostPath),则在volumes字段中,定义`persistentVolumeClaim`,即PVC。 -PVC和PV进行绑定的过程,由`Volume Controller`中的`PersistentVolumeController`这个控制循环负责。所谓“绑定”,也就是填写PVC中的`spec.volumeName`字段而已。`PersistentVolumeController`只会将StorageClass相同的PVC和PV绑定起来。 +PVC和PV进行绑定的过程,由`Volume Controller`中的`PersistentVolumeController`这个控制循环负责。所谓“绑定”,也就是填写PVC中的`spec.volumeName`字段而已。`PersistentVolumeController`只会将StorageClass相同的PVC和PV绑定起来。 -StorageClass主要用来动态分配存储(Dynamic Provisioning)。StorageClass中的`provisioner`字段用于指定使用哪种[存储插件](https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner)进行动态分配,当然,前提是你要在kubernetes中装好对应的存储插件。`parameters`字段就是生成出来的PV的参数。 +StorageClass主要用来动态分配存储(Dynamic Provisioning)。StorageClass中的`provisioner`字段用于指定使用哪种[存储插件](https://kubernetes.io/docs/concepts/storage/storage-classes/#provisioner)进行动态分配,当然,前提是你要在kubernetes中装好对应的存储插件。`parameters`字段就是生成出来的PV的参数。 > `PersistentVolumeController`只是在找不到对应的PV资源和PVC进行绑定时,借助StorageClass生成了一个PV这个API对象。具体这个PV是怎么成为主机volume目录下的一个子目录的,则是靠前面所述的Attach + Mount两阶段处理后的结果。当然如果是NFS或本地持久化卷,就不需要`Volume Controller`进行Attach操作了。 @@ -106,4 +106,4 @@ PVC和PV进行绑定的过程,由`Volume Controller`中的`PersistentVolumeCon > > Q:删除一个被Pod使用中的PVC/PV时,kubectl会卡住,为什么? > -> A:PVC和PV中定义了`kubernetes.io/pvc-protection`、`kubernetes.io/pv-protection`这个finalizer字段,删除时,资源不会被apiserver立即删除,要等到`volume controller`进行**pre-delete**操作后,将finalizer字段删掉,才会被实际删除。而`volume controller`的**pre-delete**操作实际上就是检查PVC/PV有没有被Pod使用。 +> A:PVC和PV中定义了`kubernetes.io/pvc-protection`、`kubernetes.io/pv-protection`这个finalizer字段,删除时,资源不会被apiserver立即删除,要等到`volume controller`进行**pre-delete**操作后,将finalizer字段删掉,才会被实际删除。而`volume controller`的**pre-delete**操作实际上就是检查PVC/PV有没有被Pod使用。 diff --git a/content/post/login-permission-denied.md b/content/post/login-permission-denied.md index cc1649e..38ed300 100644 --- a/content/post/login-permission-denied.md +++ b/content/post/login-permission-denied.md @@ -11,7 +11,7 @@ tags: ["Linux"] ## 问题现象 -SSH登录主机失败,提示错误:`/bin/bash: Permission denied`。 +SSH登录主机失败,提示错误:`/bin/bash: Permission denied`。 ## 分析过程 @@ -23,7 +23,7 @@ Linux处理SSH远程登录的流程如下: 4. 通过`exec`系统调用将子进程替换为登录shell(这里是`/bin/bash`),shell的0、1、2文件描述符和伪终端相连 5. 用户通过伪终端和主机通信 -很明显这里的错误原因是因为设置了登陆用户的uid、gid的子进程没有`/bin/bash`的执行权限导致的,也就是第4步出错。通过`strace`跟踪sshd进程的系统调用,也印证了这一点:子进程确实是在执行`execve(/bin/bash)`时报`Permission denied`。 +很明显这里的错误原因是因为设置了登陆用户的uid、gid的子进程没有`/bin/bash`的执行权限导致的,也就是第4步出错。通过`strace`跟踪sshd进程的系统调用,也印证了这一点:子进程确实是在执行`execve(/bin/bash)`时报`Permission denied`。 继续实验,发现只有非root组内用户登录才会报该错误。因此将一个已登录的root组内用户修改为非root组内用户后,通过`strace`跟踪其执行`/bin/bash`的过程,发现是在`open`某个动态库权限不足。查看该库文件及路径的权限,发现原本应该是`755`权限的`/usr`目录变成了`750`。通过`stat`命令查看该文件夹被修改的时间点,定位到是设备上电脚本中的一个bug。 diff --git a/content/post/process-and-thread.md b/content/post/process-and-thread.md index 9fb5fd9..ae4522a 100644 --- a/content/post/process-and-thread.md +++ b/content/post/process-and-thread.md @@ -29,7 +29,7 @@ autoCollapseToc: false 由于同一进程中的线程共享资源,所以通信非常方便,直接读写同一块用户态内存即可,但是这必然就涉及到互斥和原子性问题。 -而进程要实现通信则需要借助内核和文件,所有的IPC,都是把内核和文件充当交换信息的桥梁。 +而进程要实现通信则需要借助内核和文件,所有的IPC,都是把内核和文件充当交换信息的桥梁。 ## 上下文切换 diff --git a/content/post/smtp-with-tls.md b/content/post/smtp-with-tls.md index ff470a9..8adbbe5 100644 --- a/content/post/smtp-with-tls.md +++ b/content/post/smtp-with-tls.md @@ -33,4 +33,4 @@ autoCollapseToc: false 接下来网上搜索`smtp VRFY disallowed`相关内容就能找到答案了:原来`libcurl`从7.34.0版本开始,要求SMTP客户端显式的设置 `CURLOPT_UPLOAD` 选项,否则libcurl将发送`VRFY`命令。而一般服务器出于安全性的考虑,会禁止执行VRFY命令。(参考[https://issues.dlang.org/show_bug.cgi?id=13042](https://issues.dlang.org/show_bug.cgi?id=13042) ) -> 通过抓包还证实了,不进行加密通信的应用层数据是明文传输的,smtp协议中的用户名密码被一览无余。 +> 通过抓包还证实了,不进行加密通信的应用层数据是明文传输的,smtp协议中的用户名密码被一览无余。 diff --git a/content/post/system-and-shell.md b/content/post/system-and-shell.md index 7677c12..1041f5c 100644 --- a/content/post/system-and-shell.md +++ b/content/post/system-and-shell.md @@ -26,13 +26,13 @@ tags: ["Linux"] 在学习《UNIX环境高级编程(第3版)》信号一章时,根据图10-27所示,执行 `system("/bin/ed")` 命令后,会分别调用`fork`/`exec`系统调用两次: 1. 第一次发生在调用`system`时,父进程`fork`一次,子进程执行`execl("/bin/sh","sh","-c","/bin/ed",(char *)0)`一次,子进程被替换为`/bin/sh`。 -2. 第二次发生在`/bin/sh`这个子进程中,`/bin/sh`会先`fork`一个子进程,这个子进程执行`exec("/bin/ed")`,用`/bin/ed`替换`/bin/sh`。 +2. 第二次发生在`/bin/sh`这个子进程中,`/bin/sh`会先`fork`一个子进程,这个子进程执行`exec("/bin/ed")`,用`/bin/ed`替换`/bin/sh`。 但是我在自己做实验时,用`strace`命令跟踪系统调用的过程,发现`system`系统调用执行过程中,**只`fork`了一次,`exec`了两次,主要的差异在于`/bin/sh`并没有`fork`子进程,而是直接执行了`exec("/bin/ed")`**。 ### 实验二 -我在shell下执行`sh -c "sleep 5"&`命令,根据书中的示例,执行`ps -f`后应该可以看到4个进程: +我在shell下执行`sh -c "sleep 5"&`命令,根据书中的示例,执行`ps -f`后应该可以看到4个进程: * `ps -f` * 当前shell进程 @@ -51,7 +51,7 @@ PID PPID CMD 103012 48673 ps -o pid,ppid,cmd ``` -## system的返回值到底是多少? +## system的返回值到底是多少? 使用如下程序对system的返回值进行实验: diff --git a/content/post/version-control-of-shared-object.md b/content/post/version-control-of-shared-object.md index 4d827a3..112a1b9 100644 --- a/content/post/version-control-of-shared-object.md +++ b/content/post/version-control-of-shared-object.md @@ -17,7 +17,7 @@ tags: [] ## so name -在介绍版本控制前,需要先了解动态链接库的三种name:`real name`、`soname`、`link name`。 +在介绍版本控制前,需要先了解动态链接库的三种name:`real name`、`soname`、`link name`。 * **link name**:`libxxx.so`称为动态链接库的`link name`。 * **real name**:实际编译出来的动态链接库是具有版本号后缀的,如`libxxx.so.x.y.z`,称为动态链接库的`real name`。 @@ -58,5 +58,5 @@ readelf -d libtest.so.1.0.0 | grep soname ## 升级动态库 -1. 小版本升级,比如从`libtest.so.1.0.0`升级为`libtest.so.1.1.1`。这个时候,按照约定它的soname`libtest.so.1`是不变的,所以使用者可以直接把新版本so丢到机器上,执行`ldconfig`,新生成的`libtest.so.1`就变成了指向`libtest.so.1.1.1`的软连接。小版本升级是后向兼容的,所以这里直接进行升级是没有问题的。 +1. 小版本升级,比如从`libtest.so.1.0.0`升级为`libtest.so.1.1.1`。这个时候,按照约定它的soname`libtest.so.1`是不变的,所以使用者可以直接把新版本so丢到机器上,执行`ldconfig`,新生成的`libtest.so.1`就变成了指向`libtest.so.1.1.1`的软连接。小版本升级是后向兼容的,所以这里直接进行升级是没有问题的。 2. 主版本升级,比如从`libtest.so.1.1.1`升级为`libtest.so.2.0.0`。这个时候,按照约定它的soname变成了`libtest.so.2`,此时`ldconfig`生成的软连接为`libtest.so.2`,指向`libtest.so.2.0.0`。一般主版本升级会有后向兼容性问题,但是由于使用了新的soname,因此对使用老版本so的程序没有影响。 diff --git a/content/post/why-aggregated-api-server-webhook.md b/content/post/why-aggregated-api-server-webhook.md index d5bf130..ec574e3 100644 --- a/content/post/why-aggregated-api-server-webhook.md +++ b/content/post/why-aggregated-api-server-webhook.md @@ -14,24 +14,24 @@ autoCollapseToc: false apiserver为客户端提供三种认证方式: 1. https**双向**认证(注意是双向认证,例如kubeconfig文件中既要配置客户端证书和私钥,又要配置CA证书) -2. http token认证(例如serviceaccount对应的secret中,包含token文件、ca证书,容器就是通过这两个文件和apiserver进行http token认证的) +2. http token认证(例如serviceaccount对应的secret中,包含token文件、ca证书,容器就是通过这两个文件和apiserver进行http token认证的) 3. http base认证(用户名+密码) ## admission webhook和扩展apiserver -对于这两种情形,**apiserver是作为客户端**,admission webhook和扩展apiserver作为服务端。 +对于这两种情形,**apiserver是作为客户端**,admission webhook和扩展apiserver作为服务端。 ### apiserver通过HTTPS连接admission webhook -当apiserver作为客户端连接admission webhook时,要求admission webhook必须提供https安全认证,但是默认是**单向**认证即可。**也就是admission webhook负责提供服务端证书供apiserver进行验证,但webhook默认可以不验证apiserver**。apiserver所需要的CA证书在webhookconfiguration文件中的`caBundle`字段中进行配置。如果不配置,则默认使用apiserver自己的CA证书。 +当apiserver作为客户端连接admission webhook时,要求admission webhook必须提供https安全认证,但是默认是**单向**认证即可。**也就是admission webhook负责提供服务端证书供apiserver进行验证,但webhook默认可以不验证apiserver**。apiserver所需要的CA证书在webhookconfiguration文件中的`caBundle`字段中进行配置。如果不配置,则默认使用apiserver自己的CA证书。 我们可以自己签发webhook的证书,如`istio`项目中使用的[脚本](https://github.com/istio/istio/blob/release-0.7/install/kubernetes/webhook-create-signed-cert.sh),或者像`openkruise`项目一样[在controller中生成证书](https://github.com/openkruise/kruise/blob/master/pkg/webhook/util/controller/webhook_controller.go#L262),当然也可以使用cert-manager自动生成和管理证书。 ### admission webhook验证apiserver -如果你的admission webhook想要验证客户端(也就是apiserver),那么就需要额外给apiserver提供一个配置文件,这个配置文件的内容和kubeconfig很像,可以指定apisever使用http base认证、http token或者证书来向webhook提供身份证明,具体过程详见[官方文档](https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers)。 +如果你的admission webhook想要验证客户端(也就是apiserver),那么就需要额外给apiserver提供一个配置文件,这个配置文件的内容和kubeconfig很像,可以指定apisever使用http base认证、http token或者证书来向webhook提供身份证明,具体过程详见[官方文档](https://kubernetes.io/zh/docs/reference/access-authn-authz/extensible-admission-controllers/#authenticate-apiservers)。 -简单来讲就是:在启动 apiserver时,通过`--admission-control-config-file` 这个参数指定了客户端认证的配置文件,这个文件的格式和用 kubectl 连接 apiserver 时用到的 `kubeconfig` 格式几乎一样。只不过这里,客户端是apiserver,服务端是admission webhook。 +简单来讲就是:在启动 apiserver时,通过`--admission-control-config-file` 这个参数指定了客户端认证的配置文件,这个文件的格式和用 kubectl 连接 apiserver 时用到的 `kubeconfig` 格式几乎一样。只不过这里,客户端是apiserver,服务端是admission webhook。 通过这种方式验证客户端,最麻烦的地方是需要手工维护kubeconfig,且对于每个webhook都需要维护一个。 @@ -41,7 +41,7 @@ aggregated apiserver在设计之初就解决了客户端认证的问题,具体 ## 以扩展apiserver的方式部署admission webhook -openshift 的 [generic-admission-server库](https://github.com/openshift/generic-admission-server#generic-admission-server) 是一种用来编写admission webhook的lib库,它声称**使用它可以避免apiserver为每一个admission webhook维护一个kubeconfig。(不过我觉得它最大的好处是可以不部署服务端证书,这是通过正常方式部署admission webhook时所办不到的)**。我们来看下它是如何实现的。 +openshift 的 [generic-admission-server库](https://github.com/openshift/generic-admission-server#generic-admission-server) 是一种用来编写admission webhook的lib库,它声称**使用它可以避免apiserver为每一个admission webhook维护一个kubeconfig。(不过我觉得它最大的好处是可以不部署服务端证书,这是通过正常方式部署admission webhook时所办不到的)**。我们来看下它是如何实现的。 1. 在(Validating/Mutating)WebhookConfiguration中,配置admission webhook为kubernetes服务: @@ -72,7 +72,7 @@ openshift 的 [generic-admission-server库](https://github.com/openshift/generic 1. apiserver过滤指定的请求,**将它发到自己的路径下**。 -2. 由aggregator转发到扩展apiserver,也就是真正的admission webhook进行处理。 +2. 由aggregator转发到扩展apiserver,也就是真正的admission webhook进行处理。 这样就省去了为apiserver配置和维护kubeconfig文件的步骤。