百度网盘链接:https://pan.baidu.com/s/15t_TSH5RRpCFXV-93JHpNw?pwd=8od3 提取码:8od3
6 污点、容忍度、亲和性
6.1 node节点亲和性
6.1.1 node节点亲和性调度:nodeAffinity
[root@master1 ~]# kubectl explain pods.spec.affinity
KIND: Pod
VERSION: v1
RESOURCE: affinity <Object>
FIELDS:
nodeAffinity <Object>
podAffinity <Object>
podAntiAffinity <Object>
[root@master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity
FIELDS:
preferredDuringSchedulingIgnoredDuringExecution <[]Object>
requiredDuringSchedulingIgnoredDuringExecution <Object>
prefered表示有节点尽量满足这个位置定义的亲和性,这不是一个必须的条件,软亲和性
require表示必须有节点满足这个位置定义的亲和性,这是个硬性条件,硬亲和性
[root@master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution
FIELDS:
nodeSelectorTerms <[]Object> -required-
Required. A list of node selector terms. The terms are ORed.
[root@master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms
FIELDS:
matchExpressions <[]Object>
matchFields <[]Object>
matchExpressions:匹配表达式的
matchFields:匹配字段的
[root@master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.matchFields
FIELDS:
key <string> -required-
values <[]string>
[root@master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.matchExpressions
FIELDS:
key <string> -required-
operator <string> -required-
Possible enum values:
- `"DoesNotExist"`
- `"Exists"`
- `"Gt"`
- `"In"`
- `"Lt"`
- `"NotIn"`
values <[]string>
key:检查label
operator:做等值选则还是不等值选则
values:给定值
6.1.2 使用requiredDuringSchedulingIgnoredDuringExecution硬亲和性
把myapp-v1.tar.gz上传到node2和node1上,手动解压:
[root@node1 ~]# ctr -n=k8s.io images import myapp-v1.tar.gz
[root@node2 ~]# ctr -n=k8s.io images import myapp-v1.tar.gz
[root@master1 ~]# vim pod-nodeaffinity-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-node-affinity-demo
namespace: default
labels:
app: myapp
tier: frontend
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone
operator: In
values:
- foo
- bar
containers:
- name: myapp
image: docker.io/ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
我们检查当前节点中有任意一个节点拥有zone标签的值是foo或者bar,就可以把pod调度到这个node节点的foo或者bar标签上的节点上。
[root@master1 ~]# kubectl apply -f pod-nodeaffinity-demo.yaml
[root@master1 ~]# kubectl get pods -o wide | grep pod-node
pod-node-affinity-demo 0/1 Pending 0 node1
status的状态是pending,上面说明没有完成调度,因为没有一个拥有zone的标签的值是foo或者bar,而且使用的是硬亲和性,必须满足条件才能完成调度。给这个node1节点打上标签zone=foo再查看。
[root@master1 ~]# kubectl label nodes node1 zone=foo
[root@master1 ~]# kubectl get pods -o wide
pod-node-affinity-demo 1/1 Running 0 node1
6.1.3 使用preferredDuringSchedulingIgnoredDuringExecution软亲和性
[root@master1 ~]# vim pod-nodeaffinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-node-affinity-demo-2
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: docker.io/ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: zone1
operator: In
values:
- foo1
- bar1
weight: 10
- preference:
matchExpressions:
- key: zone2
operator: In
values:
- foo2
- bar2
weight: 20
[root@master1 ~]# kubectl apply -f pod-nodeaffinity-demo-2.yaml
[root@master1 ~]# kubectl get pods -o wide | grep demo-2
pod-node-affinity-demo-2 1/1 Running 0 node1
上面说明软亲和性是可以运行这个pod的,尽管没有运行这个pod的节点定义的zone1标签。
Node节点亲和性针对的是pod和node的关系,Pod调度到node节点的时候匹配的条件。
测试weight权重:
weight是相对权重,权重越高,pod调度的几率越大假如给node1和node2都打上标签:
[root@master1 ~]# kubectl label nodes node1 zone1=foo1
[root@master1 ~]# kubectl label nodes node2 zone2=foo2
preferredDuringSchedulingIgnoredDuringExecution:
- preference:
matchExpressions:
- key: zone1
operator: In
values:
- foo1
- bar1
weight: 10
- preference:
matchExpressions:
- key: zone2
operator: In
values:
- foo2
- bar2
weight: 20
pod在定义node节点亲和性的时候,node1和node2都满足条件,都可以调度pod,但是node2具有的标签是zone2=foo2,pod在匹配zone2=foo2的权重高,那么pod就会优先调度到node2上。
[root@master1 ~]# kubectl label nodes node1 zone1-
[root@master1 ~]# kubectl label nodes node2 zone2-
[root@master1 ~]# kubectl delete -f pod-nodeaffinity-demo-2.yaml
6.2 Pod节点亲和性
6.2.1 pod亲和性调度的两种表示形式
podaffinity:pod和pod更倾向腻在一起,把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;
podunaffinity:pod和pod更倾向不腻在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。
第一个pod随机选则一个节点,做为评判后续的pod能否到达这个pod所在的节点上的运行方式,这就称为pod亲和性;我们怎么判定哪些节点是相同位置的,哪些节点是不同位置的;我们在定义pod亲和性时需要有一个前提,哪些pod在同一个位置,哪些pod不在同一个位置,这个位置是怎么定义的,标准是什么?以节点名称为标准,这个节点名称相同的表示是同一个位置,节点名称不相同的表示不是一个位置。
[root@master1 ~]# kubectl explain pods.spec.affinity.podAffinity
KIND: Pod
VERSION: v1
RESOURCE: podAffinity <Object>
FIELDS:
preferredDuringSchedulingIgnoredDuringExecution <[]Object>
requiredDuringSchedulingIgnoredDuringExecution <[]Object>
requiredDuringSchedulingIgnoredDuringExecution: 硬亲和性
preferredDuringSchedulingIgnoredDuringExecution:软亲和性
[root@master1 ~]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution
KIND: Pod
VERSION: v1
RESOURCE: requiredDuringSchedulingIgnoredDuringExecution <[]Object>
DESCRIPTION:
FIELDS:
labelSelector <Object>
namespaces <[]string>
topologyKey <string> -required-
topologyKey:位置拓扑的键,这个是必须字段,是判断节点是不是同一个位置的参数。
rack=rack1
row=row1
使用rack的键是同一个位置
使用row的键是同一个位置
labelSelector: #我们要判断pod跟别的pod亲和,跟哪个pod亲和,需要靠labelSelector,通过labelSelector选则一组能作为亲和对象的pod资源。
namespace: #labelSelector需要选则一组资源,那么这组资源是在哪个名称空间中呢,通过namespace指定,如果不指定namespaces,那么就是当前创建pod的名称空间
[root@master1 ~]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector
KIND: Pod
VERSION: v1
RESOURCE: labelSelector <Object>
FIELDS:
matchExpressions <[]Object>
matchLabels <map[string]string>
6.2.2 pod节点亲和性
定义两个pod,第一个pod做为基准,第二个pod跟着它走。
[root@master1 ~]# vim pod-required-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app2: myapp2
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod-required-affinity-demo-1.yaml
[root@master1 ~]# vim pod-required-affinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app2
operator: In
values:
- myapp2
topologyKey: kubernetes.io/hostname
上面表示创建的pod必须与拥有app=myapp标签的pod在一个节点上。
[root@master1 ~]# kubectl apply -f pod-required-affinity-demo-2.yaml
[root@master1 ~]# kubectl get pods -o wide 显示如下:
pod-first running node2
pod-second running node2
[root@master1 ~]# kubectl delete -f pod-required-affinity-demo-1.yaml //删除pod
[root@master1 ~]# kubectl delete -f pod-required-affinity-demo-2.yaml //删除pod
6.2.3 pod节点反亲和性
定义两个pod,第一个pod做为基准,第二个pod跟它调度节点相反。
[root@master1 ~]# vim pod-required-anti-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app1: myapp1
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod-required-anti-affinity-demo-1.yaml
[root@master1 ~]# vim pod-required-anti-affinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app1
operator: In
values:
- myapp1
topologyKey: kubernetes.io/hostname
[root@master1 ~]# kubectl apply -f pod-required-anti-affinity-demo-2.yaml
[root@master1 ~]# kubectl get pods -o wide //显示两个pod不在一个node节点上,这就是pod节点反亲和性
pod-first running node1
pod-second running node2
[root@master1 ~]# kubectl delete -f pod-required-anti-affinity-demo-1.yaml //删除pod
[root@master1 ~]# kubectl delete -f pod-required-anti-affinity-demo-2.yaml //删除pod
6.2.4 topologykey
[root@master1 ~]# kubectl label nodes node1 zone=foo
[root@master1 ~]# kubectl label nodes node2 zone=foo
[root@master1 ~]# vim pod-first-required-anti-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-first
labels:
app3: myapp3
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod-first-required-anti-affinity-demo-1.yaml
[root@master1 ~]# vim pod-second-required-anti-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-second
labels:
app: backend
tier: db
spec:
containers:
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh","-c","sleep 3600"]
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app3 ,operator: In, values: ["myapp3"]}
topologyKey: zone
[root@master1 ~]# kubectl apply -f pod-second-required-anti-affinity-demo-1.yaml
[root@master1 ~]# kubectl get pods -o wide
pod-first running node1
pod-second pending <none>
第二个pod是pending,因为两个节点是同一个位置,现在没有不是同一个位置的了,而且我们要求反亲和性,所以就会处于pending状态,如果在反亲和性这个位置把required改成preferred,那么也会运行。
[root@master1 ~]# kubectl delete -f pod-first-required-anti-affinity-demo-1.yaml
[root@master1 ~]# kubectl delete -f pod-second-required-anti-affinity-demo-1.yaml
[root@master1 ~]# kubectl label nodes node1 zone-
[root@master1 ~]# kubectl label nodes node2 zone-
6.3 污点、容忍度
6.3.1 污点及容忍度解释
给了节点选则的主动权,我们给节点打一个污点,不容忍的pod就运行不上来,污点就是定义在节点上的键值属性数据,可以定决定拒绝那些pod。
taints是键值数据,用在节点上,定义污点
tolerations是键值数据,用在pod上,定义容忍度,能容忍哪些污点
pod亲和性是pod属性,但是污点是节点的属性,污点定义在k8s集群的节点上的一个字段。
[root@master1 ~]# kubectl explain node.spec.taints
KIND: Node
VERSION: v1
RESOURCE: taints <[]Object>
FIELDS:
effect <string> -required-
key <string> -required-
timeAdded <string>
value <string>
taints的effect用来定义对pod对象的排斥等级:
NoSchedule:仅影响pod调度过程,当pod能容忍这个节点污点,就可以调度到当前节点,后来这个节点的污点改了,加了一个新的污点,使得之前调度的pod不能容忍了,那这个pod会怎么处理,对现存的pod对象不产生影响。
NoExecute:既影响调度过程,又影响现存的pod,如果现存的pod不能容忍节点后来加的污点,这个pod就会被驱逐。
PreferNoSchedule:最好不,也可以,是NoSchedule的柔性版本。
[root@master1 ~]# kubectl describe nodes master1
Taints: node-role.kubernetes.io/control-plane:NoSchedule
上面可以看到master这个节点的污点是Noschedule。
所以我们创建的pod都不会调度到master上,因为我们创建的pod没有容忍度。
[root@master1 ~]# kubectl describe pods kube-apiserver-master1 -n kube-system
Tolerations: :NoExecute op=Exists
可以看到这个pod的容忍度是NoExecute,则可以调度到master1上。
[root@master1 ~]# kubectl taint --help //管理节点污点
6.3.2 NoSchedule
把node2当成是生产环境专用的,其他node是测试的。给node2打污点,pod如果不能容忍就不会调度过来。
[root@master1 ~]# kubectl taint node node2 node-type=production:NoSchedule
[root@master1 ~]# vim pod-taint.yaml
apiVersion: v1
kind: Pod
metadata:
name: taint-pod
namespace: default
labels:
tomcat: tomcat-pod
spec:
containers:
- name: taint-pod
ports:
- containerPort: 8080
image: tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod-taint.yaml
[root@master1 ~]# kubectl get pods -o wide
taint-pod running node1
可以看到都被调度到node1上了,因为node2这个节点打了污点,而我们在创建pod的时候没有容忍度,所以node2上不会有pod调度上去的。
6.3.3 NoExecute
[root@master1 ~]# kubectl taint node node1 node-type=dev:NoExecute //给node1也打上污点
[root@master1 ~]# kubectl get pods -o wide
taint-pod termaitering
可以看到已经存在的pod节点都被撵走了
6.3.4 toleration
[root@master1 ~]# vim pod-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-deploy
namespace: default
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
tolerations:
- key: "node-type"
operator: "Equal"
value: "production"
effect: "NoExecute"
tolerationSeconds: 3600
[root@master1 ~]# kubectl apply -f pod-demo-1.yaml
[root@master1 ~]# kubectl get pods
myapp-deploy 1/1 Pending 0 11s node2
还是显示pending,因为我们使用的是equal(等值匹配),所以key和value,effect必须和node节点定义的污点完全匹配才可以,把上面配置effect: "NoExecute"变成effect: "NoSchedule",tolerationSeconds: 3600这行去掉。
[root@master1 ~]# kubectl delete -f pod-demo-1.yaml
[root@master1 ~]# kubectl apply -f pod-demo-1.yaml
[root@master1 ~]# kubectl get pods -o wide
myapp-deploy 1/1 running 0 11s node2
上面就可以调度到node2上了,因为在pod中定义的容忍度能容忍node节点上的污点
再次修改如下部分:
tolerations:
- key: "node-type"
operator: "Exists"
value: ""
effect: "NoSchedule"
只要对应的键是存在的,exists,其值被自动定义成通配符。
[root@master1 ~]# kubectl delete -f pod-demo-1.yaml
[root@master1 ~]# kubectl apply -f pod-demo-1.yaml
[root@master1 ~]# kubectl get pods
发现还是调度到node2上
myapp-deploy 1/1 running 0 11s node2
再次修改:
tolerations:
- key: "node-type"
operator: "Exists"
value: ""
effect: ""
有一个node-type的键,不管值是什么,不管是什么效果,都能容忍。
[root@master1 ~]# kubectl delete -f pod-demo-1.yaml
[root@master1 ~]# kubectl apply -f pod-demo-1.yaml
[root@master1 ~]# kubectl get pods -o wide 显示如下:
myapp-deploy running node1
可以看到node2和node1节点上都有可能有pod被调度
[root@master1 ~]# kubectl taint nodes node1 node-type:NoExecute- //删除污点
[root@master1 ~]# kubectl taint nodes node2 node-type- //删除污点
6.4 Pod常见的状态和重启策略
6.4.1 常见的pod状态
第一阶段:
挂起(Pending):
1、正在创建Pod但是Pod中的容器还没有全部被创建完成,处于此状态的Pod应该检查Pod依赖的存储是否有权限挂载、镜像是否可以下载、调度是否正常等。
2、我们在请求创建pod时,条件不满足,调度没有完成,没有任何一个节点能满足调度条件,已经创建了pod但是没有适合它运行的节点叫做挂起,调度没有完成。
失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
未知(Unknown):未知状态,所谓pod是什么状态是apiserver和运行在pod节点的kubelet进行通信获取状态信息的,如果节点之上的kubelet本身出故障,那么apiserver就连不上kubelet,得不到信息了,就会看Unknown,通常是由于与pod所在的node节点通信错误。
Error 状态:Pod 启动过程中发生了错误
成功(Succeeded):Pod中的所有容器都被成功终止,即pod里所有的containers均已terminated。
第二阶段:
Unschedulable:Pod不能被调度, scheduler没有匹配到合适的node节点。
PodScheduled:pod正处于调度中,在scheduler刚开始调度的时候,还没有将pod分配到指定的node,在筛选出合适的节点后就会更新etcd数据,将pod分配到指定的node。
Initialized:所有pod中的初始化容器已经完成了。
ImagePullBackOff:Pod所在的node节点下载镜像失败。
Running:Pod内部的容器已经被创建并且启动。
还有其他状态如下:
Evicted状态:出现这种情况,多见于系统内存或硬盘资源不足,可df-h查看docker存储所在目录的资源使用情况,如果百分比大于85%,就要及时清理下资源,尤其是一些大文件、docker镜像。
CrashLoopBackOff:容器曾经启动了,但可能又异常退出了。
6.4.2 pod重启策略
Pod的重启策略(RestartPolicy)应用于Pod内的所有容器,当某个容器异常退出或者健康检查失败时,kubelet将根据重启策略来进行相应的操作。
Pod 的spec中包含一个restartPolicy字段,其可能取值包括Always、OnFailure和Never,默认值是 Always。
Always:只要容器异常退出,kubelet就会自动重启该容器。(这个是默认的重启策略)
OnFailure:当容器终止运行且退出码不为0时,由kubelet自动重启该容器。
Never:不论容器运行状态如何,kubelet都不会重启该容器。
6.4.3 测试Always重启策略
[root@master1 ~]# vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
labels:
app: myapp
spec:
restartPolicy: Always
containers:
- name: tomcat-pod-java
ports:
- containerPort: 8080
image: tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod.yaml
1)正常停止容器里的tomcat服务
[root@master1 ~]# kubectl exec -it demo-pod -- /bin/bash
bash-4.4# /usr/local/tomcat/bin/shutdown.sh
[root@master1 ~]# kubectl get pod //查看pod状态
demo-pod 1/1 Running 1 (5s ago) 3m24s
发现正常停止容器里的tomcat服务,容器重启了一次,pod又恢复正常了。
2)非正常停止容器里的tomcat服务
[root@master1 ~]# kubectl exec -it demo-pod -- /bin/bash
bash-4.4# kill 1
[root@master1 ~]# kubectl get pod
demo-pod 1/1 Running 2 (5s ago) 3m24s
上面可以看到容器终止了,并且又重启一次,重启次数增加了一次
6.4.4 测试never重启策略
[root@master1 ~]# vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
labels:
app: myapp
spec:
restartPolicy: Never
containers:
- name: tomcat-pod-java
ports:
- containerPort: 8080
image: tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod.yaml
1)正常停止容器里的tomcat服务
[root@master1 ~]# kubectl exec -it demo-pod -- /bin/bash
bash-4.4# /usr/local/tomcat/bin/shutdown.sh
[root@master1 ~]# kubectl get pod
demo-pod 1/1 Completed 0 3m24s
发现正常停止容器里的tomcat服务,pod正常运行,容器没有重启
2)非正常停止容器里的tomcat服务
[root@master1 ~]# kubectl exec -it tomcat-pod -- /bin/bash
bash-4.4# kill 1
[root@master1 ~]# kubectl get pod
demo-pod 1/1 error 0 3m24s
可以看到pod状态是error,并且没有重启,说明重启策略是never,那么pod里容器服务无论如何终止,都不会重启。
6.4.5 测试OnFailure重启策略
[root@master1 ~]# vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: default
labels:
app: myapp
spec:
restartPolicy: OnFailure
containers:
- name: tomcat-pod-java
ports:
- containerPort: 8080
image: tomcat:8.5-jre8-alpine
imagePullPolicy: IfNotPresent
[root@master1 ~]# kubectl apply -f pod.yaml
1)正常停止容器里的tomcat服务
[root@master1 ~]# kubectl exec -it demo-pod -- /bin/bash
bash-4.4# /usr/local/tomcat/bin/shutdown.sh
[root@master1 ~]# kubectl get pod
demo-pod 0/1 complete 0 3m24s
正常停止容器里的tomcat服务,退出码是0,pod里的容器不会重启
2)非正常停止容器里的tomcat服务
[root@master1 ~]# kubectl exec -it tomcat-pod -- /bin/bash
bash-4.4# kill 1
[root@master1 ~]# kubectl get pod
demo-pod 1/1 running 1 3m24s
非正常停止pod里的容器,容器退出码不是0,那就会重启容器
标签:master1,kubectl,demo,亲和性,容忍度,污点,pod,root,节点 From: https://www.cnblogs.com/KrillLiszt/p/17007195.html