网络相关

K8s中的external-traffic-policy

什么是external-traffic-policy

在k8s的Service对象(申明一条访问通道)中,有一个“externalTrafficPolicy”字段可以设置。有2个值可以设置:Cluster或者Local。

  • 1)Cluster表示:流量可以转发到其他节点上的Pod。
  • 2)Local表示:流量只发给本机的Pod。

image.png

这2种模式有什么区别

选择(1)Cluster

注:这个是默认模式,Kube-proxy不管容器实例在哪,公平转发。
Kube-proxy转发时会替换掉报文的源IP。即:容器收的报文,源IP地址,已经被替换为上一个转发节点的了。

image.png

原因是Kube-proxy在做转发的时候,会做一次SNAT (source network address translation),所以源IP变成了节点1的IP地址。

注意: snat确保回去的报文可以原路返回,不然回去的路径不一样,客户会认为非法报文的。(我发给张三的,怎么李四给我回应?丢弃!)

这种模式好处是负载均衡会比较好,因为无论容器实例怎么分布在多个节点上,它都会转发过去。当然,由于多了一次转发,性能会损失一丢丢。

选择(2)Local

这种情况下,只转发给本机的容器,绝不跨节点转发。
Kube-proxy转发时会保留源IP。即:容器收到的报文,看到源IP地址还是用户的。

image.png

缺点是负载均衡可能不是很好,因为一旦容器实例分布在多个节点上,它只转发给本机,不跨节点转发流量。当然,少了一次转发,性能会相对好一丢丢。

注:这种模式下的Service类型只能为外部流量,即:LoadBalancer 或者 NodePort 两种,否则会报错。

同时,由于本机不会跨节点转发报文,所以要想所有节点上的容器有负载均衡,就需要上一级的Loadbalancer来做了。

image.png

想要解决负载不均衡的问题:可以给Pod容器设置反亲和,让这些容器平均的分布在各个节点上(不要聚在一起)。

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 100
      podAffinityTerm:
        labelSelector:
          matchExpressions:
           - key: k8s-app
             operator: In
             values:
             - my-app
        topologyKey: kubernetes.io/hostname

HostPort

image.png

如下是我们的Nginx工作负载的Kubernetes YAML如何指定'ports'部分下的HostPort设置:

image.png

HostPort的优点:

  • 通过HostPort设置,您可以请求暴露主机上的任何可用端口。
  • 配置很简单,HostPort设置直接放在Kubernetes pod规范中。与NodePort相比,不需要创建其他对象来暴露应用程序。

HostPort的缺点:

  • 使用HostPort会限制pod的调度,因为只有那些具有指定端口可用的主机才能用于部署。
  • 如果工作负载的规模大于Kubernetes集群中的节点数,部署会失败。
  • 指定了相同HostPort的任何两个工作负载,都将无法部署在同一节点上。
  • 如果运行pod的主机出现故障,Kubernetes将不得不将pod重新安排到不同的节点。如此一来,可以访问工作负载的IP地址将发生变化,从而破坏应用程序的外部客户端。当pod重新启动时也会发生同样的事情,Kubernetes会在不同的节点上重新安排它们。

集群管理

worker 节点使用 kubectl

将 master 节点 /etc/kubernetes/admin.conf文件拷贝到 worker 节点相同目录下,然后配置环境变量:

echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source ~/.bash_profile # 立即生效

建立临时 pod 用以检测网络

busybox 网络检测 pod

apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  namespace: default
spec:
  nodeName: master
  containers:
  - name: busybox
    image: busybox:1.28.4
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

使用 calicoctl 命令访问 etcd 获取结果

vim /etc/calico/calicoctl.cfg

apiVersion: projectcalico.org/v3
kind: CalicoAPIConfig
metadata:
spec:
  etcdEndpoints: https://etcd1:2379,https://etcd2:2379,https://etcd3:2379
  etcdKeyFile: /etc/calico/key.pem
  etcdCertFile: /etc/calico/cert.pem
  etcdCACertFile: /etc/calico/ca.pem

测试https网页忽略证书检查

curl 'https://x.x.x.x/get_ips' -k
wget 'https://x.x.x.x/get_ips' --no-check-certificate 

清空 var 目录造成 ssh 无法登录

CentOS7上的SSH无法启动,报告/var/empty/sshd must be owned by root and not group or world-writable

chown -R root.root /var/empty/sshd 
chmod 744 /var/empty/sshd 

tcpdump 抓取数据包

tcpdump 抓取icmp数据包
tcpdump  -n  -i  eth1  icmp 
tcpdump -n -i ethname -vv port 22 && tcp 
tcpdump -n -i ethname -vv port 8080 && udp -w log.pcap
tcpdump -i calicba2f87f6bb -e -nn

容器内抓包

问题定位技巧:容器内抓包
在使用 kubernetes 跑应用的时候,可能会遇到一些网络问题,比较常见的是服务端无响应(超时)或回包内容不正常,如果没找出各种配置上有问题,这时我们需要确认数据包到底有没有最终被路由到容器里,或者报文到达容器的内容和出容器的内容符不符合预期,通过分析报文可以进一步缩小问题范围。那么如何在容器内抓包呢?本文提供实用的脚本一键进入容器网络命名空间(netns),使用宿主机上的tcpdump进行抓包。

kubectl get pod -o wide
kubectl describe pods -n crm-sprod sc-facade-b5cf7d9dc-jbsp4 | grep docker 获取container id

进入pod所在服务器
docker inspect -f {
} 2eb97134de41d974b22d7b21e3fe6cec45debed7c4a231c1a1a4d270df567505
获取到pid

进入pod的network namespace
nsenter -n --target pid
这时已经进入 pod 的 netns,可以执行宿主机上的 ip a 或 ifconfig 来查看容器的网卡,执行 netstat -tunlp 查看当前容器监听了哪些端口,再通过 tcpdump 抓包:
tcpdump -i eth0 -w test.pcap port 80

然后拷贝到本地用wireshark分析

存储相关

k8s之configmap和secret

configmap:把配置文件放在配置中心上,然后多个pod读取配置中心的配置文件,不过,configmap中的配置信息都是明文的,所以不安全;

secret:功能和configmap一样,只不过配置中心存储的配置文件不是明文的.configmap和secret也是专属于某个名称空间的.

容器热更新

当 ConfigMap 作为 volume 进行挂载时,它的内容是会更新的。

更新操作由 kubelet 的 Pod 同步循环触发。每次进行 Pod 同步时(默认每 10 秒一次),Kubelet 都会将 Pod 的所有 ConfigMap volume 标记为”需要重新挂载(RequireRemount)“,而 kubelet 中的 volume 控制循环会发现这些需要重新挂载的 volume,去执行一次挂载操作。

在 ConfigMap 的重新挂载过程中,kubelet 会先比较远端的 ConfigMap 与 volume 中的 ConfigMap 是否一致,再做更新。要注意,”拿远端的 ConfigMap” 这个操作可能是有缓存的,因此拿到的并不一定是最新版本。

由此,我们可以知道,ConfigMap 作为 volume 确实是会自动更新的,但是它的更新存在延时,最多的可能延迟时间是:

Pod 同步间隔(默认10秒) + ConfigMap 本地缓存的 TTL

kubelet 上 ConfigMap 的获取是否带缓存由配置中的 ConfigMapAndSecretChangeDetectionStrategy 决定

注意,假如使用了 subPath 将 ConfigMap 中的某个文件单独挂载到其它目录下,那这个文件是无法热更新的(这是 ConfigMap 的挂载逻辑决定的)

当然,配置文件更新完不代表业务逻辑就更新了,我们还需要通知应用重新读取配置进行业务逻辑上的更新。比如对于 Nginx,就需要发送一个 SIGHUP 信号量。

每次都手动让 Nginx 读取更新后的配置文件,很繁琐。我们可以使用 Reloader 自动让 pod 滚动更新。

Reloader

https://github.com/stakater/Reloader

部署

kubectl apply -f https://raw.githubusercontent.com/stakater/Reloader/master/deployments/kubernetes/reloader.yaml

默认将监听所有 namespace 下 secret 和 configmap 的变化。

使用

在 deployment 中添加注解

kind: Deployment
metadata:
  annotations:
    reloader.stakater.com/auto: "true"
spec:
  template: metadata:

或者直接执行命令:

kubectl patch deployments.apps my-app -p '{"metadata": {"annotations": {"reloader.stakater.com/auto": "true"}}}'

然后我们更改configMap清单,重新apply过后,我们可以看到pod会删除重启

处理容器数据磁盘被写满

容器数据磁盘被写满造成的危害:

  • 不能创建 Pod (一直 ContainerCreating)
  • 不能删除 Pod (一直 Terminating)
  • 无法 exec 到容器

判断是否被写满:

容器数据目录大多会单独挂数据盘,路径一般是 /var/lib/docker,也可能是 /data/docker 或 /opt/docker

可通过 docker info 确定:

$ docker info
...
Docker Root Dir: /var/lib/docker
...

如果没有单独挂数据盘,则会使用系统盘存储。判断是否被写满:

$ df
Filesystem     1K-blocks     Used Available Use% Mounted on
...
/dev/vda1       51474044  4619112  44233548  10% /
...
/dev/vdb        20511356 20511356         0 100% /var/lib/docker

解决方法

先恢复业务,清理磁盘空间

重启 dockerd (清理容器日志输出和可写层文件)
重启前需要稍微腾出一点空间,不然重启 docker 会失败,可以手动删除一些docker的log文件或可写层文件,通常删除log:

$ cd /var/lib/docker/containers
$ du -sh * # 找到比较大的目录
$ cd dda02c9a7491fa797ab730c1568ba06cba74cecd4e4a82e9d90d00fa11de743c
$ cat /dev/null > dda02c9a7491fa797ab730c1568ba06cba74cecd4e4a82e9d90d00fa11de743c-json.log.9 # 删除log文件

注意: 使用 cat /dev/null > 方式删除而不用 rm,因为用 rm 删除的文件,docker 进程可能不会释放文件,空间也就不会释放;log 的后缀数字越大表示越久远,先删除旧日志。

将该 node 标记不可调度,并将其已有的 pod 驱逐到其它节点,这样重启dockerd就会让该节点的pod对应的容器删掉,容器相关的日志(标准输出)与容器内产生的数据文件(可写层)也会被清理:

kubectl drain 10.179.80.31

重启 dockerd:

systemctl restart dockerd

取消不可调度的标记:

kubectl uncordon 10.179.80.31

定位根因,彻底解决

问题定位方法见附录,这里列举根因对应的解决方法:

日志输出量大导致磁盘写满:
减少日志输出
增大磁盘空间
减小单机可调度的pod数量
可写层量大导致磁盘写满: 优化程序逻辑,不写文件到容器内或控制写入文件的大小与数量
镜像占用空间大导致磁盘写满:
增大磁盘空间
删除不需要的镜像

附录

查看docker的磁盘空间占用情况

$ docker system df -v
Images space usage:

REPOSITORY                         TAG       IMAGE ID       CREATED         SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
<none>                             <none>    0b3c861abf6d   28 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    03b2a5a29f4d   28 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    2c2db87bf715   28 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    b13fca81aa1b   29 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    c2f2028c63bc   29 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    91d3dc864ada   29 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    8129c875763a   29 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    601b391d98ed   29 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    d2480f276ae5   29 hours ago    1.148GB   1.059GB       88.35MB       0
172.21.0.8/devops/gin-demo         latest    51b86726859e   29 hours ago    6.399MB   0B            6.399MB       0
<none>                             <none>    161ccfcb3145   29 hours ago    1.148GB   1.059GB       88.35MB       0
<none>                             <none>    5b66508631da   30 hours ago    1.148GB   1.059GB       88.35MB       0
golang                             latest    8735189b1527   7 days ago      940.6MB   940.6MB       0B            0
172.21.0.8/devops/nginx            latest    dd34e67e3371   8 days ago      133.2MB   0B            133.2MB       0
goharbor/harbor-exporter           v2.3.1    719fd825651e   5 weeks ago     81.02MB   36.55MB       44.47MB       0
goharbor/chartmuseum-photon        v2.3.1    3aba4510af16   5 weeks ago     178.2MB   36.55MB       141.6MB       0
goharbor/redis-photon              v2.3.1    4a0d49a4ece0   5 weeks ago     190.8MB   36.55MB       154.3MB       1
goharbor/trivy-adapter-photon      v2.3.1    a285847f857a   5 weeks ago     164MB     36.55MB       127.4MB       0
goharbor/notary-server-photon      v2.3.1    87a2dbfd122e   5 weeks ago     109.7MB   36.55MB       73.13MB       0
goharbor/notary-signer-photon      v2.3.1    7e29ff33ec85   5 weeks ago     106.9MB   36.55MB       70.32MB       0
goharbor/harbor-registryctl        v2.3.1    91e798004920   5 weeks ago     132MB     36.55MB       95.48MB       1
goharbor/registry-photon           v2.3.1    972ce19b1882   5 weeks ago     81.24MB   36.55MB       44.69MB       1
goharbor/nginx-photon              v2.3.1    3b3ede1db494   5 weeks ago     44.34MB   36.55MB       7.784MB       1
goharbor/harbor-log                v2.3.1    40a54594fe22   5 weeks ago     194.4MB   36.55MB       157.8MB       1
goharbor/harbor-jobservice         v2.3.1    d6e174ae0a00   5 weeks ago     170.7MB   36.55MB       134.1MB       1
goharbor/harbor-core               v2.3.1    f05acc3947d6   5 weeks ago     157.5MB   36.55MB       121MB         1
goharbor/harbor-portal             v2.3.1    4a15c5622fda   5 weeks ago     57.56MB   36.55MB       21.01MB       1
goharbor/harbor-db                 v2.3.1    b16a9c81ef03   5 weeks ago     262.8MB   36.55MB       226.2MB       1
goharbor/prepare                   v2.3.1    4ce629d59c20   5 weeks ago     287.7MB   36.55MB       251.1MB       0
ceph/ceph-grafana                  6.7.4     3b1077a63311   5 weeks ago     486.2MB   0B            486.2MB       1
ceph/ceph                          v16       6933c2a0b7dd   6 weeks ago     1.201GB   0B            1.201GB       5
kubernetesui/dashboard             v2.3.1    e1482a24335a   2 months ago    220MB     0B            220MB         0
quay.io/coreos/flannel             v0.14.0   8522d622299c   3 months ago    67.93MB   0B            67.93MB       0
quay.io/prometheus/node-exporter   v1.1.2    c19ae228f069   5 months ago    25.97MB   0B            25.97MB       0
quay.io/brancz/kube-rbac-proxy     v0.8.0    ad393d6a4d1b   9 months ago    48.95MB   0B            48.95MB       0
kubernetesui/metrics-scraper       v1.0.6    48d79e554db6   10 months ago   34.55MB   0B            34.55MB       0
prom/prometheus                    v2.18.1   de242295e225   15 months ago   140MB     2.648MB       137.3MB       1
prom/alertmanager                  v0.20.0   0881eb8f169f   20 months ago   52.08MB   2.648MB       49.43MB       0
prom/node-exporter                 v0.18.1   e5a616e4b9cf   2 years ago     22.93MB   0B            22.93MB       1

Containers space usage:

CONTAINER ID   IMAGE                                COMMAND                  LOCAL VOLUMES   SIZE      CREATED        STATUS                  NAMES
f77fe681058e   ceph/ceph                            "/usr/bin/ceph-osd -…"   0               0B        37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-osd.1
8a8e08d49aa8   ceph/ceph                            "/usr/bin/ceph-osd -…"   0               0B        37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-osd.0
3318900b84ed   ceph/ceph:v16                        "/usr/bin/ceph-mgr -…"   0               12.1kB    37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-mgr.server1.csdcgh
9e241e877dff   ceph/ceph                            "/usr/bin/ceph-crash…"   0               0B        37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-crash.server1
ed5945eefdc1   ceph/ceph:v16                        "/usr/bin/ceph-mon -…"   0               0B        37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-mon.server1
072c509a5316   ceph/ceph-grafana:6.7.4              "/bin/sh -c 'grafana…"   0               4.58kB    37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-grafana.server1
50604c3ad7e9   prom/prometheus:v2.18.1              "/bin/prometheus --c…"   0               0B        37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-prometheus.server1
ab39aeacb3a8   prom/node-exporter:v0.18.1           "/bin/node_exporter …"   0               0B        37 hours ago   Up 37 hours             ceph-aeef8b18-f27e-11eb-8817-525400d203e5-node-exporter.server1
efaa16355059   goharbor/nginx-photon:v2.3.1         "nginx -g 'daemon of…"   3               2B        5 days ago     Up 35 hours (healthy)   nginx
39c377257da9   goharbor/harbor-jobservice:v2.3.1    "/harbor/entrypoint.…"   0               1.57MB    5 days ago     Up 35 hours (healthy)   harbor-jobservice
98bc172d251a   goharbor/harbor-core:v2.3.1          "/harbor/entrypoint.…"   0               1.57MB    5 days ago     Up 35 hours (healthy)   harbor-core
204641cbfd33   goharbor/harbor-registryctl:v2.3.1   "/home/harbor/start.…"   1               1.57MB    5 days ago     Up 37 hours (healthy)   registryctl
a7dc4c50a19e   goharbor/redis-photon:v2.3.1         "redis-server /etc/r…"   0               0B        5 days ago     Up 35 hours (healthy)   redis
41f35c561d90   goharbor/harbor-portal:v2.3.1        "nginx -g 'daemon of…"   3               2B        5 days ago     Up 35 hours (healthy)   harbor-portal
e54fe0ab5f29   goharbor/harbor-db:v2.3.1            "/docker-entrypoint.…"   0               64B       5 days ago     Up 37 hours (healthy)   harbor-db
5c66d3476876   goharbor/registry-photon:v2.3.1      "/home/harbor/entryp…"   0               1.57MB    5 days ago     Up 35 hours (healthy)   registry
68d211836ec6   goharbor/harbor-log:v2.3.1           "/bin/sh -c /usr/loc…"   2               56B       5 days ago     Up 37 hours (healthy)   harbor-log

Local Volumes space usage:

VOLUME NAME                                                        LINKS     SIZE
792afe5f78ee1ff9aa0f31b025c186d75c7aad0085e3f2829b485844e7ee7e9c   1         0B
a9300105f36cca0b62b08c8e21de9fd6e0bf2ec202e00fad8480aa494f407f35   1         40B
d3573ddda98c65c0501d2db5090e59c7be2a903777bbde1a0fb9502f68fbe460   1         0B
a59cbc3cabb41eb60535484abd3ab56c44fcc35308716eb179a54eccd20a7bd7   1         0B
c68a051cd7f7d570103a5c812e57bead90b5f3dec7c7a69821bbabdf13d607b6   1         0B
cb50e35b60a6579860eb92fbbcf2ebeb523775b578d0ab5397552008ea36d7f1   1         40B
09b4804bf57a1640981aa969d6573f78ff670c6389092f6c0eb1fd1bc78596b8   1         0B
2f86493a9243535160b8c3cc677502eaee01c4b6e7f68375d5ad5eff64cd48b8   1         0B
969c01f2a39d03d1c9cdc14d7e649c823cd218d26a99b44c28d0ac8c293c918d   1         4B

Build cache usage: 0B

CACHE ID   CACHE TYPE   SIZE      CREATED   LAST USED   USAGE     SHARED

定位容器写满磁盘的原因
进入容器数据目录(假设是 /var/lib/docker,并且存储驱动是 aufs):

$ cd /var/lib/docker
$ du -sh *
88K	buildkit
42M	containers
16M	image
116K	network
13G	overlay2
16K	plugins
4.0K	runtimes
4.0K	swarm
4.0K	tmp
4.0K	trust
224K	volumes

找出日志输出量大的 pod
TKE 的 pod 中每个容器输出的日志最大存储 1G (日志轮转,最大10个文件,每个文件最大100m,可用 docker inpect 查看):

$ docker inspect fef835ebfc88
[
    {
         ...
        "HostConfig": {
            ...
            "LogConfig": {
                "Type": "json-file",
                "Config": {
                    "max-file": "10",
                    "max-size": "100m"
                }
            },
...

查看哪些容器日志输出量大:

$ cd /var/lib/docker/containers
$ du -sh *
44K	072c509a5316c06c0a0c67239f406b70b97226555952d25a611f8960b09d862e
1.8M	204641cbfd33f0bef002af3505c6ccce802468e56f0763f4e14d811159893812
2.3M	3318900b84edbae035678bc61dd17d8946db9cb530baf2f7ea3fbff5f510828f
9.0M	39c377257da9aa3ba85931b9c28305662595e38bad5df9c3629b159dc9d811c5
2.6M	41f35c561d90e9ee272837795d0e8f2f4a0cf98ace9065a88b5791ba03eaea19
60K	50604c3ad7e9cc38628ecd42161025b7a3789895acd21730dbc544f851ac3dab
40K	5b6ef8890d59785ca1622d32bea48b991fc09504533ba4abfe89e52630f1b73a
2.1M	5c66d347687626c011d5edd929716113e101ece9ee5406b2f96ff82fbde18928
44K	68d211836ec68cb4ac94ecbf237a5a8b00466617895deb3acd26b1d8db6d5620
36K	8a8e08d49aa8e2754f8cea303f0cb654056326ab4adf81b43a71df458898c162
588K	98bc172d251a4975ad7d740eb54088942316b174f6c8da97a4569445173b4032
40K	9e241e877dff7bd995aa3b02ecc846b3b178432972323fd21307edd4f6dcb990
252K	a7dc4c50a19ec40f91c8d867dfbce06e0defe4b6889c805e4103ef23d309c6ac
40K	ab39aeacb3a8f48f1f671c728db38d9433c541efe0a2027d750e430ade0658ae
48K	e54fe0ab5f29ba027c85d50a49cbbdfdca772d693e6e48190d2a3bad59de8706
22M	ed5945eefdc113c62a0c1d68dc3453e1ed7fdb06aabfa17ffe84418e2ae47a6f
600K	efaa16355059d2fd692e4c13f18673ff5c60388d6da4dc8452e02ccfcf5f51cc
36K	f77fe681058eaa711b56f9166831f3c7ee3519ced4f72df3d9bc98e9d83e129e

目录名即为容器id,使用前几位与 docker ps 结果匹配可找出对应容器,最后就可以推算出是哪些 pod 搞的鬼

运维命令

获取 Pod 和节点

如何查找非 running 状态的 Pod 呢?

 kubectl get pods -A --field-selector=status.phase!=Running | grep -v Complete

顺便一说,--field-selector 是个值得深入一点的参数。

如何获取节点列表及其内存容量:

kubectl get no -o json | \
>    jq -r '.items | sort_by(.status.capacity.memory)[]|[.metadata.name,.status.capacity.memory]| @tsv'
worker1	3826328Ki
worker2	3826328Ki

获取节点列表,其中包含运行在每个节点上的 Pod 数量

kubectl get po -o json --all-namespaces | \
>    jq '.items | group_by(.spec.nodeName) | map({"nodeName": .[0].spec.nodeName, "count": length}) | sort_by(.count)'
[
  {
    "nodeName": "worker1",
    "count": 11
  },
  {
    "nodeName": "worker2",
    "count": 19
  }
]

有时候 DaemonSet 因为某种原因没能在某个节点上启动。手动搜索会有点麻烦:

 $ ns=my-namespace
 $ pod_template=my-pod
 $ kubectl get node | grep -v \"$(kubectl -n ${ns} get pod --all-namespaces -o wide | fgrep ${pod_template} | awk '{print $8}' | xargs -n 1 echo -n "\|" | sed 's/[[:space:]]*//g')\"

使用 kubectl top 获取 Pod 列表并根据其消耗的 CPU 或 内存进行排序:

# CPU 排序
kubectl top pods -A | sort --reverse --key 3 --numeric
monitoring    prometheus-k8s-0                             34m          477Mi           
kube-system   kube-apiserver-worker1                       34m          476Mi           
kube-system   kube-apiserver-worker2                       21m          540Mi           
kube-system   kube-controller-manager-worker2              13m          80Mi            
monitoring    grafana-6dd5b5f65-plwgk                      5m           56Mi            
kube-system   kube-proxy-wzpbb                             3m           36Mi            
kube-system   kube-proxy-bp6g2                             3m           28Mi            
monitoring    prometheus-adapter-59df95d9f5-p9tgb          2m           37Mi            
monitoring    node-exporter-pv7xb                          2m           39Mi            
kube-system   kube-flannel-ds-7d8jg                        2m           24Mi            
default       csi-rbdplugin-provisioner-5c7c467848-8n8wz   2m           191Mi           
monitoring    node-exporter-hvqqt                          1m           40Mi            
kube-system   kube-scheduler-worker2                       1m           30Mi            
kube-system   kube-scheduler-worker1                       1m           32Mi            
kube-system   kube-flannel-ds-77xv9                        1m           29Mi            
monitoring    prometheus-operator-7775c66ccf-vmvsq         0m           59Mi            
monitoring    kube-state-metrics-76f6cb7996-5sb7d          0m           63Mi            
monitoring    blackbox-exporter-55c457d5fb-7thcw           0m           40Mi            
monitoring    alertmanager-main-0                          0m           34Mi            
kube-system   log-pilot-wj9tj                              0m           26Mi            
kube-system   log-pilot-nwf9x                              0m           13Mi            
kube-system   kube-eventer-5dbc97f5b-strgc                 0m           16Mi            
kube-system   kube-controller-manager-worker1              0m           36Mi            
kube-system   coredns-78fcd69978-tnkz4                     0m           22Mi            
kube-system   coredns-78fcd69978-f7xnk                     0m           25Mi            
halo          halo-nginx-747bb8f59b-k6jm2                  0m           7Mi             
halo          halo-deployment-745fb589c7-csqwk             0m           443Mi           
default       stakater-reloader-749b6cdb5-l8frx            0m           41Mi            
default       csi-rbdplugin-fvjq6                          0m           67Mi            
default       csi-rbdplugin-bjnjh                          0m           105Mi        

# 内存排序
kubectl top pods -A | sort --reverse --key 4 --numeric
kube-system   kube-apiserver-worker2                       51m          555Mi           
kube-system   kube-apiserver-worker1                       25m          476Mi           
monitoring    prometheus-k8s-0                             38m          474Mi           
halo          halo-deployment-745fb589c7-csqwk             0m           443Mi           
default       csi-rbdplugin-provisioner-5c7c467848-8n8wz   2m           191Mi           
default       csi-rbdplugin-bjnjh                          0m           105Mi           
kube-system   kube-controller-manager-worker2              11m          80Mi            
default       csi-rbdplugin-fvjq6                          0m           67Mi            
monitoring    kube-state-metrics-76f6cb7996-5sb7d          0m           63Mi            
monitoring    prometheus-operator-7775c66ccf-vmvsq         0m           59Mi            
monitoring    grafana-6dd5b5f65-plwgk                      2m           53Mi            
default       stakater-reloader-749b6cdb5-l8frx            0m           41Mi            
monitoring    node-exporter-hvqqt                          1m           40Mi            
monitoring    blackbox-exporter-55c457d5fb-7thcw           0m           40Mi            
monitoring    node-exporter-pv7xb                          1m           39Mi            
monitoring    prometheus-adapter-59df95d9f5-p9tgb          4m           37Mi            
kube-system   kube-controller-manager-worker1              0m           36Mi            
kube-system   kube-proxy-wzpbb                             3m           35Mi            
monitoring    alertmanager-main-0                          0m           34Mi            
kube-system   kube-scheduler-worker1                       1m           32Mi            
kube-system   kube-scheduler-worker2                       1m           30Mi            
kube-system   kube-flannel-ds-77xv9                        1m           29Mi            
kube-system   kube-proxy-bp6g2                             3m           28Mi            
kube-system   log-pilot-wj9tj                              1m           26Mi            
kube-system   coredns-78fcd69978-f7xnk                     1m           25Mi            
kube-system   kube-flannel-ds-7d8jg                        1m           24Mi            
kube-system   coredns-78fcd69978-tnkz4                     1m           22Mi            
kube-system   kube-eventer-5dbc97f5b-strgc                 0m           16Mi            
kube-system   log-pilot-nwf9x                              0m           13Mi            
halo          halo-nginx-747bb8f59b-k6jm2                  0m           7Mi           

获取 Pod 列表,并根据重启次数进行排序:

kubectl get pods —sort-by=.status.containerStatuses[0].restartCount

获取其它数据

如何输出 Pod 的 requests 和 limits

kubectl get pods -A -o=custom-columns='NAME:spec.containers[*].name,MEMREQ:spec.containers[*].resources.requests.memory,MEMLIM:spec.containers[*].resources.limits.memory,CPUREQ:spec.containers[*].resources.requests.cpu,CPULIM:spec.containers[*].resources.limits.cpu'
NAME                                                                                                                  MEMREQ            MEMLIM            CPUREQ        CPULIM
driver-registrar,csi-rbdplugin,liveness-prometheus                                                                    <none>            <none>            <none>        <none>
driver-registrar,csi-rbdplugin,liveness-prometheus                                                                    <none>            <none>            <none>        <none>
csi-provisioner,csi-snapshotter,csi-attacher,csi-resizer,csi-rbdplugin,csi-rbdplugin-controller,liveness-prometheus   <none>            <none>            <none>        <none>
stakater-reloader                                                                                                     <none>            <none>            <none>        <none>
halo                                                                                                                  500Mi             1Gi               500m          1
halo-nginx                                                                                                            <none>            <none>            <none>        <none>
coredns                                                                                                               70Mi              170Mi             100m          <none>
coredns                                                                                                               70Mi              170Mi             100m          <none>
kube-apiserver                                                                                                        <none>            <none>            250m          <none>
kube-apiserver                                                                                                        <none>            <none>            250m          <none>
kube-controller-manager                                                                                               <none>            <none>            200m          <none>
kube-controller-manager                                                                                               <none>            <none>            200m          <none>
kube-eventer                                                                                                          100Mi             250Mi             100m          500m
kube-flannel                                                                                                          50Mi              50Mi              100m          100m
kube-flannel                                                                                                          50Mi              50Mi              100m          100m
kube-proxy                                                                                                            <none>            <none>            <none>        <none>
kube-proxy                                                                                                            <none>            <none>            <none>        <none>
kube-scheduler                                                                                                        <none>            <none>            100m          <none>
kube-scheduler                                                                                                        <none>            <none>            100m          <none>
log-pilot                                                                                                             200Mi             500Mi             200m          <none>
log-pilot                                                                                                             200Mi             500Mi             200m          <none>
alertmanager,config-reloader                                                                                          100Mi,50Mi        100Mi,50Mi        4m,100m       100m,100m
blackbox-exporter,module-configmap-reloader,kube-rbac-proxy                                                           20Mi,20Mi,20Mi    40Mi,40Mi,40Mi    10m,10m,10m   20m,20m,20m
grafana                                                                                                               100Mi             200Mi             100m          200m
kube-state-metrics,kube-rbac-proxy-main,kube-rbac-proxy-self                                                          190Mi,20Mi,20Mi   250Mi,40Mi,40Mi   10m,20m,10m   100m,40m,20m
node-exporter,kube-rbac-proxy                                                                                         180Mi,20Mi        180Mi,40Mi        102m,10m      250m,20m
node-exporter,kube-rbac-proxy                                                                                         180Mi,20Mi        180Mi,40Mi        102m,10m      250m,20m
prometheus-adapter                                                                                                    <none>            <none>            <none>        <none>
prometheus,config-reloader                                                                                            400Mi,50Mi        50Mi              100m          100m
prometheus-operator,kube-rbac-proxy                                                                                   100Mi,20Mi        200Mi,40Mi        100m,10m      200m,20m

网络

在排除 CNI(例如 Flannel)故障的时候,经常会需要检查路由来识别故障 Pod。Pod 子网在这里非常有用:

kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}' | tr " " "\n"           
10.97.0.0/24
10.97.2.0/24

日志

使用可读的时间格式输出日志:

kubectl logs -f fluentbit-gke-qq9w9  -c fluentbit --timestamps

获取“前一个”容器的日志(例如崩溃的情况):

kubectl logs halo-deployment-745fb589c7-csqwk -n halo --previous

输出一个 Pod 中所有容器的日志:

kubectl -n my-namespace logs -f my-pod —all-containers

其它

把 Secret 复制到其它命名空间:

kubectl get secrets -o json --namespace namespace-old | \
   jq '.items[].metadata.namespace = "namespace-new"' | \
   kubectl create-f  -

下面两个命令可以生成一个用于测试的自签发证书:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=grafana.mysite.ru/O=MyOrganization"
 kubectl -n myapp create secret tls selfsecret --key tls.key --cert tls.crt

kubernetes命令自动补全

echo "source <(kubectl completion bash)" >> ~/.bashrc
source ~/.bashrc

kubectl写yaml太累,找样例太麻烦?

用run命令生成

kubectl run --image=nginx my-deploy -o yaml --dry-run > my-deploy.yaml

用get命令导出

kubectl get statefulset/foo -o=yaml --export > new.yaml

Pod亲和性下面字段的拼写忘记了

kubectl explain pod.spec.affinity.podAffinity

监控集群组件

集群整体状态

kubectl cluster-info 

更多集群信息

kubectl cluster-info dump

组件metrics

curl localhost:10250/stats/summary

组件监控状况

curl localhost:10250/healthz

Deployment升级与回滚

# 创建Deployment
kubectl run {deployment} --image={image} --replicas={rep.}

# 升级Deployment
kubectl set image deployment/nginx-deployment nginx=nignx:1.9.1
kubectl set resources deployment/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi

# 升级策略
minReadySeconds: 5
strategy:
  type: RollingUpdata
    maxSurge: 1 #默认25%
    maxUnavailable: 1 #默认25%

# 暂停Deployment
kubectl rollout pause deployment/nginx-deployment

# 恢复Deployment
kubectl rollout resume deployment/nginx-deployment

# 查询升级状态
kubectl rollout status deployment/nginx-deployment

# 查询升级历史
kubectl rollout history deploy/nginx-deployment
kubectl rollout history deploy/nginx-deployment --revision=2

# 回滚
kubectl rollout undo deployment/nginx-deployment --to-revision=2

# 应用弹性伸缩
kubectl scale deployment nginx-deployment --replicas=10

# 对接了Heapster,和HPA联动后
kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80

Node的隔离和恢复

apiVersion: v1
kind: Node
metadata:
  name: kubernetes-minion1
  labels:
    kubernetes.io/hostname: kubernetes-minion1
spec:
  unschedulable: true

然后,通过kubectl replace命令完成对Node状态的修改:

查看Node的状态,可以观察到在Node的状态中增加了一项SchedulingDisabled:

$ kubectl get nodes
NAME                 LABELS                                      STATUS
kubernetes-minion1   kubernetes.io/hostname=kubernetes-minion1   Ready, SchedulingDisabled

对于后续创建的Pod,系统将不会再向该Node进行调度。

另一种方法是不使用配置文件,直接使用kubectl patch命令完成:

kubectl patch node kubernetes-minion1 -p '{"spec":{"unschedulable":true}}'

需要注意的是,将某个Node脱离调度范围时,在其上运行的Pod并不会自动停止,管理员需要手动停止在该Node上运行的Pod。

同样,如果需要将某个Node重新纳入集群调度范围,则将unschedulable设置为false,再次执行kubectl replace或kubectl patch命令就能恢复系统对该Node的调度。

设置默认namespace

运行kubectl命令的不便之一是,每次编写命令时,都需要在最后使用该--namespace 选项。运维人员通常会忘记这一点,最终在错误的namespace中创建对象(pod,service,deployment)。

使用此技巧,您可以在运行kubectl命令之前设置namespace首选项。在执行kubectl命令之前运行以下命令,它将为您的当前上下文保存所有后续kubectl命令的namespace:

kubectl config set-context $(kubectl config current-context) --namespace=mynamespace

查看资源利用率

安装 metrics-server 后可以运行 kubectl top 查看相关资源的占用率

kubectl top node
kubectl top pod -n namespace

资源与调度

将Pod调度到指定的Node

首先,我们可以通过kubectl label命令给目标Node打上一个特定的标签,下面是此命令的完整用法:

kubectl label nodes <node-name> <label-key>=<label-value>

这里,我们为kubernetes-minion1节点打上一个zone=north的标签,表明它是“北方”的一个节点:

$ kubectl label nodes kubernetes-minion1 zone=north
NAME                 LABELS                                                 STATUS
kubernetes-minion1   kubernetes.io/hostname=kubernetes-minion1,zone=north   Ready

上述命令行操作也可以通过修改资源定义文件的方式,并执行kubectl replace -f xxx.yaml命令来完成。

然后,在Pod的配置文件中加入nodeSelector定义,以redis-master-controller.yaml为例:


apiVersion: v1
kind: ReplicationController
metadata:
  name: redis-master
  labels:
    name: redis-master
spec:
  replicas: 1
  selector:
    name: redis-master
  template:
    metadata:
      labels:
        name: redis-master
    spec:
      containers:
      - name: master
        image: kubeguide/redis-master
        ports:
        - containerPort: 6379
      nodeSelector:
        zone: north

运行kubectl create -f命令创建Pod,scheduler就会将该Pod调度到拥有zone=north标签的Node上去。

使用kubectl get pods -o wide命令可以验证Pod所在的Node:

kubectl get pods -o wide
NAME                 READY     STATUS    RESTARTS   AGE       NODE
redis-master-f0rqj   1/1       Running   0          19s       kubernetes-minion1

如果我们给多个Node都定义了相同的标签(例如zone=north),则scheduler将会根据调度算法从这组Node中挑选一个可用的Node进行Pod调度。

这种基于Node标签的调度方式灵活性很高,比如我们可以把一组Node分别贴上“开发环境”“测试验证环境”“用户验收环境”这三组标签中的一种,此时一个Kubernetes集群就承载了3个环境,这将大大提高开发效率。

需要注意的是,如果我们指定了Pod的nodeSelector条件,且集群中不存在包含相应标签的Node时,即使还有其他可供调度的Node,这个Pod也最终会调度失败。

让 master 节点可调度

kubectl taint node master node-role.kubernetes.io/master-
kubectl taint node master node-role.kubernetes.io/master=""

使用 nodeName 指定 pod 调度节点

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01

给每个namespace添加默认的内存和CPU限额

是人就会犯错。我们假定某人写了个应用,他每秒就会打开一个数据库连接,但是不会关闭。这样集群中就有了一个内存泄漏的应用。假定我们把该应用部署到了没有限额设置的集群,那么该应用就会crash掉一个节点。
为了避免这种情况,Kubernetes允许为每个namespace设置默认的限额。要做到这很简单,我们只需创建一个limit range 的 yaml 并应用到特定namespace。以下是一个例子:

apiVersion: v1
kind: LimitRange
metadata:
  name: mem-limit-range
  namespace: test
spec:
  limits:
  - default:
      memory: 512Mi
      cpu: 500m
    defaultRequest:
      memory: 256Mi
      cpu: 100m
    type: Container

将该内容创建一个yaml文件并将它应用到任何你想应用的namespace,例如namespace limit-example。使用了限额后,任何部署到该namespace的应用,假如没有主动设置限额,都将得到一个默认的512Mi的内存限额。

重要的线上应用改如何设置

节点资源不足时,会触发自动驱逐,将一些低优先级的 Pod 删除掉以释放资源让节点自愈。

  • 没有设置 request,limit 的 Pod 优先级最低,容易被驱逐;
  • request 不等于 limit 的其次;
  • request 等于 limit 的 Pod 优先级较高,不容易被驱逐。

所以如果是重要的线上应用,不希望在节点故障时被驱逐导致线上业务受影响,就建议将 request 和 limit 设成一致。

怎样设置才能提高资源利用率

如果给给你的应用设置较高的 request 值,而实际占用资源长期远小于它的 request 值,导致节点整体的资源利用率较低。当然这对时延非常敏感的业务除外,因为敏感的业务本身不期望节点利用率过高,影响网络包收发速度。所以对一些非核心,并且资源不长期占用的应用,可以适当减少 request 以提高资源利用率。

如果你的服务支持水平扩容,单副本的 request 值一般可以设置到不大于 1 核,CPU 密集型应用除外。比如 coredns,设置到 0.1 核就可以,即 100m。

尽量避免使用过大的 request 与 limit

如果你的服务使用单副本或者少量副本,给很大的 request 与 limit,让它分配到足够多的资源来支撑业务,那么某个副本故障对业务带来的影响可能就比较大,并且由于 request 较大,当集群内资源分配比较碎片化,如果这个 Pod 所在节点挂了,其它节点又没有一个有足够的剩余可分配资源能够满足这个 Pod 的 request 时,这个 Pod 就无法实现漂移,也就不能自愈,加重对业务的影响。

相反,建议尽量减小 request 与 limit,通过增加副本的方式来对你的服务支撑能力进行水平扩容,让你的系统更加灵活可靠。

避免测试 namespace 消耗过多资源影响生产业务

若生产集群有用于测试的 namespace,如果不加以限制,可能导致集群负载过高,从而影响生产业务。可以使用 ResourceQuota 来限制测试 namespace 的 request 与 limit 的总大小。 示例:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-test
  namespace: test
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "2"
    limits.memory: 2Gi

如何让资源得到更合理的分配

设置 Request 能够解决让 Pod 调度到有足够资源的节点上,但无法做到更细致的控制。如何进一步让资源得到合理的使用?我们可以结合亲和性、污点与容忍等高级调度技巧,让 Pod 能够被合理调度到合适的节点上,让资源得到充分的利用。

使用亲和性

  • 对节点有特殊要求的服务可以用节点亲和性 (Node Affinity) 部署,以便调度到符合要求的节点,比如让 MySQL 调度到高 IO 的机型以提升数据读写效率。
  • 可以将需要离得比较近的有关联的服务用 Pod 亲和性 (Pod Affinity) 部署,比如让 Web 服务跟它的 Redis 缓存服务都部署在同一可用区,实现低延时。
  • 也可使用 Pod 反亲和 (Pod AntiAffinity) 将 Pod 进行打散调度,避免单点故障或者流量过于集中导致的一些问题。

K8S 的设计就是假设节点是不可靠的。节点越多,发生软硬件故障导致节点不可用的几率就越高,所以我们通常需要给服务部署多个副本,根据实际情况调整 replicas 的值,如果值为 1 就必然存在单点故障,如果大于 1 但所有副本都调度到同一个节点了,那还是有单点故障,有时候还要考虑到灾难,比如整个机房不可用。

所以我们不仅要有合理的副本数量,还需要让这些不同副本调度到不同的拓扑域(节点、可用区),打散调度以避免单点故障,这个可以利用 Pod 反亲和性来做到,反亲和主要分强反亲和与弱反亲和两种。

先来看个强反亲和的示例,将 dns 服务强制打散调度到不同节点上:

affinity:
 podAntiAffinity:
   requiredDuringSchedulingIgnoredDuringExecution:
   - labelSelector:
       matchExpressions:
       - key: k8s-app
         operator: In
         values:
         - kube-dns
     topologyKey: kubernetes.io/hostname
  • labelSelector.matchExpressions 写该服务对应 pod 中 labels 的 key 与 value,因为 Pod 反亲和性是通过判断 replicas 的 pod label 来实现的。
  • topologyKey 指定反亲和的拓扑域,即节点 label 的 key。这里用的 kubernetes.io/hostname 表示避免 pod 调度到同一节点,如果你有更高的要求,比如避免调度到同一个可用区,实现异地多活,可以用 failure-domain.beta.kubernetes.io/zone。通常不会去避免调度到同一个地域,因为一般同一个集群的节点都在一个地域,如果跨地域,即使用专线时延也会很大,所以 topologyKey 一般不至于用 failure-domain.beta.kubernetes.io/region。
  • requiredDuringSchedulingIgnoredDuringExecution 调度时必须满足该反亲和性条件,如果没有节点满足条件就不调度到任何节点 (Pending)。

如果不用这种硬性条件可以使用 preferredDuringSchedulingIgnoredDuringExecution 来指示调度器尽量满足反亲和性条件,即弱反亲和性,如果实在没有满足条件的,只要节点有足够资源,还是可以让其调度到某个节点,至少不会 Pending。

我们再来看个弱反亲和的示例:

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 100
      podAffinityTerm:
        labelSelector:
          matchExpressions:
          - key: k8s-app
            operator: In
            values:
            - kube-dns
      topologyKey: kubernetes.io/hostname

注意到了吗?相比强反亲和有些不同哦,多了一个 weight,表示此匹配条件的权重,而匹配条件被挪到了 podAffinityTerm 下面。

使用污点与容忍

使用污点 (Taint) 与容忍 (Toleration) 可优化集群资源调度: 通过给节点打污点来给某些应用预留资源,避免其它 Pod 调度上来。 需要使用这些资源的 Pod 加上容忍,结合节点亲和性让它调度到预留节点,即可使用预留的资源。

驱逐

有时候我们需要对节点进行维护或进行版本升级等操作,操作之前需要对节点执行驱逐 (kubectl drain),驱逐时会将节点上的 Pod 进行删除,以便它们漂移到其它节点上,当驱逐完毕之后,节点上的 Pod 都漂移到其它节点了,这时我们就可以放心的对节点进行操作了。

有一个问题就是,驱逐节点是一种有损操作,驱逐的原理:

  • 封锁节点 (设为不可调度,避免新的 Pod 调度上来)。
  • 将该节点上的 Pod 删除。
  • ReplicaSet 控制器检测到 Pod 减少,会重新创建一个 Pod,调度到新的节点上。

这个过程是先删除,再创建,并非是滚动更新,因此更新过程中,如果一个服务的所有副本都在被驱逐的节点上,则可能导致该服务不可用。

我们再来下什么情况下驱逐会导致服务不可用:

  • 服务存在单点故障,所有副本都在同一个节点,驱逐该节点时,就可能造成服务不可用。
  • 服务没有单点故障,但刚好这个服务涉及的 Pod 全部都部署在这一批被驱逐的节点上,所以这个服务的所有 Pod 同时被删,也会造成服务不可用。
  • 服务没有单点故障,也没有全部部署到这一批被驱逐的节点上,但驱逐时造成这个服务的一部分 Pod 被删,短时间内服务的处理能力下降导致服务过载,部分请求无法处理,也就降低了服务可用性。

针对第一点,我们可以使用前面讲的反亲和性来避免单点故障。

针对第二和第三点,我们可以通过配置 PDB (PodDisruptionBudget) 来避免所有副本同时被删除,驱逐时 K8S 会 "观察" nginx 的当前可用与期望的副本数,根据定义的 PDB 来控制 Pod 删除速率,达到阀值时会等待 Pod 在其它节点上启动并就绪后再继续删除,以避免同时删除太多的 Pod 导致服务不可用或可用性降低,下面给出两个示例。

示例一 (保证驱逐时 nginx 至少有 90% 的副本可用):

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  minAvailable: 90%
  selector:
    matchLabels:
      app: zookeeper

示例二 (保证驱逐时 zookeeper 最多有一个副本不可用,相当于逐个删除并等待在其它节点完成重建):

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: zookeeper

优雅终止

概述

Pod 销毁时,会停止容器内的进程,通常在停止的过程中我们需要执行一些善后逻辑,比如等待存量请求处理完以避免连接中断,或通知相关依赖进行清理等,从而实现优雅终止目的。本文介绍在 Kubernetes 场景下,实现容器优雅终止的最佳实践。

容器终止流程

我们先了解下容器在 Kubernetes 环境中的终止流程:

  1. Pod 被删除,状态置为 Terminating。
  2. kube-proxy 更新转发规则,将 Pod 从 service 的 endpoint 列表中摘除掉,新的流量不再转发到该 Pod。
  3. 如果 Pod 配置了 preStop Hook ,将会执行。
  4. kubelet 对 Pod 中各个 container 发送 SIGTERM 信号以通知容器进程开始优雅停止。
  5. 等待容器进程完全停止,如果在 terminationGracePeriodSeconds 内 (默认 30s) 还未完全停止,就发送 SIGKILL 信号强制杀死进程。
  6. 所有容器进程终止,清理 Pod 资源。

preStop Hook

在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等) 而被终止之前,此回调会被调用。 如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。 在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。 Pod 的终止宽限周期在 PreStop 回调被执行之前即开始计数,所以无论 回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。 没有参数会被传递给处理程序。

管理K8s日志

管理K8s组件日志

组件日志

/var/log/kube-apiserver.log
/var/log/kube-proxy.log
/var/log/kube-controller-manager.log
/var/log/kubelet.log

使用systemd管理

journalctl -u kubelet

使用K8s插件部署

kubectl logs -f kube-proxy

管理K8s应用日志

容器标准输出截获

kubectl logs -f {pod name} -c {container name}
docker logs -f {docker name}

日志文件挂载到主机目录

 apiVersion: v1
 kind: Pod
 metadata:
   name: test-pod
 spec:
   containers:
   - image: test-webserver
     name: test-container
     volumeMounts:
     - mountPath: /log
       name: log-volume
   volumes:
   - name: log-volume
     hostPath:
       path: /var/k8s/log

如何让服务进行平滑更新?

触发 Deployment 更新

使用 kubectl rollout 命令让 pod 优雅滚动重启

kubectl rollout restart deployment xxxx

如何让服务进行平滑更新?

解决了服务单点故障和驱逐节点时导致的可用性降低问题后,我们还需要考虑一种可能导致可用性降低的场景,那就是滚动更新。为什么服务正常滚动更新也可能影响服务的可用性呢?别急,下面我来解释下原因。

假如集群内存在服务间调用:
image.png

当 server 端发生滚动更新时:
image.png

发生两种尴尬的情况:

    1. 旧的副本很快销毁,而 client 所在节点 kube-proxy 还没更新完转发规则,仍然将新连接调度给旧副本,造成连接异常,可能会报 "connection refused" (进程停止过程中,不再接受新请求) 或 "no route to host" (容器已经完全销毁,网卡和 IP 已不存在)。
    1. 新副本启动,client 所在节点 kube-proxy 很快 watch 到了新副本,更新了转发规则,并将新连接调度给新副本,但容器内的进程启动很慢 (比如 Tomcat 这种 java 进程),还在启动过程中,端口还未监听,无法处理连接,也造成连接异常,通常会报 "connection refused" 的错误。

针对第一种情况,可以给 container 加 preStop,让 Pod 真正销毁前先 sleep 等待一段时间,等待 client 所在节点 kube-proxy 更新转发规则,然后再真正去销毁容器。这样能保证在 Pod Terminating 后还能继续正常运行一段时间,这段时间如果因为 client 侧的转发规则更新不及时导致还有新请求转发过来,Pod 还是可以正常处理请求,避免了连接异常的发生。听起来感觉有点不优雅,但实际效果还是比较好的,分布式的世界没有银弹,我们只能尽量在当前设计现状下找到并实践能够解决问题的最优解。

针对第二种情况,可以给 container 加 ReadinessProbe (就绪检查),让容器内进程真正启动完成后才更新 Service 的 Endpoint,然后 client 所在节点 kube-proxy 再更新转发规则,让流量进来。这样能够保证等 Pod 完全就绪了才会被转发流量,也就避免了链接异常的发生。

最佳实践 yaml 示例:

apiVersion: v1
kind: Pod
metadata:
  name: helloweb1
  labels:
    app: helloweb
spec:
  containers:
    - name: helloweb
      image: med1tator/helloweb:v1
      readinessProbe:
          httpGet:
            path: /healthz
            port: 80
        httpHeaders:
            - name: X-Custom-Header
              value: Awesome
          initialDelaySeconds: 30
          timeoutSeconds: 10   
       lifecycle:
         preStop:
         exec:
           command: ["/bin/bash", "-c", "sleep 10"]		  
      ports:
        - containerPort: 80

健康检查怎么配才好?

我们都知道,给 Pod 配置健康检查也是提高服务可用性的一种手段,配置 ReadinessProbe (就绪检查) 可以避免将流量转发给还没启动完全或出现异常的 Pod;配置 LivenessProbe (存活检查) 可以让存在 bug 导致死锁或 hang 住的应用重启来恢复。但是,如果配置配置不好,也可能引发其它问题,这里根据一些踩坑经验总结了一些指导性的建议:

  • 不要轻易使用 LivenessProbe,除非你了解后果并且明白为什么你需要它,参考 Liveness Probes are Dangerous
  • 如果使用 LivenessProbe,不要和 ReadinessProbe 设置成一样 (failureThreshold 更大)
    探测逻辑里不要有外部依赖 (db, 其它 pod 等),避免抖动导致级联故障
  • 业务程序应尽量暴露 HTTP 探测接口来适配健康检查,避免使用 TCP 探测,因为程序 hang 死时, TCP 探测仍然能通过 (TCP 的 SYN 包探测端口是否存活在内核态完成,应用层不感知)

kubernetes 最佳实践:优雅热更新

当kubernetes对服务滚动更新的期间,默认配置的情况下可能会让部分连接异常(比如连接被拒绝),我们来分析下原因并给出最佳实践

滚动更新场景

使用 deployment 部署服务并关联 service

  • 修改 deployment 的 replica 调整副本数量来滚动更新
  • 升级程序版本(修改镜像tag)触发 deployment 新建 replicaset 启动新版本的 pod
  • 使用 HPA (HorizontalPodAutoscaler) 来对 deployment 自动扩缩容

更新过程连接异常的原因

滚动更新时,service 对应的 pod 会被创建或销毁,也就是 service 对应的 endpoint 列表会新增或移除endpoint,更新期间可能让部分连接异常,主要原因是:

  1. pod 被创建,还没完全启动就被 endpoint controller 加入到 service 的 endpoint 列表,然后 kube-proxy 配置对应的路由规则(iptables/ipvs),如果请求被路由到还没完全启动完成的 pod,这时 pod 还不能正常处理请求,就会导致连接异常
  2. pod 被销毁,但是从 endpoint controller watch 到变化并更新 service 的 endpoint 列表到 kube-proxy 更新路由规则这期间有个时间差,pod可能已经完全被销毁了,但是路由规则还没来得及更新,造成请求依旧还能被转发到已经销毁的 pod ip,导致连接异常

最佳实践

  • 针对第一种情况,可以给 pod 里的 container 加 readinessProbe (就绪检查),这样可以让容器完全启动了才被endpoint controller加进 service 的 endpoint 列表,然后 kube-proxy 再更新路由规则,这时请求被转发到的所有后端 pod 都是正常运行,避免了连接异常
  • 针对第二种情况,可以给 pod 里的 container 加 preStop hook,让 pod 真正销毁前先 sleep 等待一段时间,留点时间给 endpoint controller 和 kube-proxy 清理 endpoint 和路由规则,这段时间 pod 处于 Terminating 状态,在路由规则更新完全之前如果有请求转发到这个被销毁的 pod,请求依然可以被正常处理,因为它还没有被真正销毁

最佳实践 yaml 示例:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      component: nginx
  template:
    metadata:
      labels:
        component: nginx
    spec:
      containers:
      - name: nginx
        image: "nginx"
        ports:
        - name: http
          hostPort: 80
          containerPort: 80
          protocol: TCP
        readinessProbe:
          httpGet:
            path: /healthz
            port: 80
            httpHeaders:
            - name: X-Custom-Header
              value: Awesome
          initialDelaySeconds: 15
          timeoutSeconds: 1
        lifecycle:
          preStop:
            exec:
              command: ["/bin/bash", "-c", "sleep 30"]

配置健康检测

liveness

https://kubernetes.io/zh/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/

自定义检测命令

Liveness检测让我们可以自定义条件来判断容器是否健康,如果检测失败,则K8s会重启容器,我们来个例子实践下,准备如下yaml配置并保存为liveness.yaml:

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness
spec:
  restartPolicy: OnFailure
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 10   # 容器启动 10 秒之后开始检测
      periodSeconds: 5          # 每隔 5 秒再检测一次

启动进程首先创建文件 /tmp/healthy,30 秒后删除,在我们的设定中,如果 /tmp/healthy 文件存在,则认为容器处于正常状态,反正则发生故障。

livenessProbe 部分定义如何执行 Liveness 检测:
检测的方法是:通过 cat 命令检查 /tmp/healthy 文件是否存在。如果命令执行成功,返回值为零,K8s 则认为本次 Liveness 检测成功;如果命令返回值非零,本次 Liveness 检测失败。

initialDelaySeconds: 10 指定容器启动 10 之后开始执行 Liveness 检测,我们一般会根据应用启动的准备时间来设置。比如某个应用正常启动要花 30 秒,那么 initialDelaySeconds 的值就应该大于 30。

periodSeconds: 5 指定每 5 秒执行一次 Liveness 检测。K8s 如果连续执行 3 次 Liveness 检测均失败,则会杀掉并重启容器。

http 检测

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: k8s.gcr.io/liveness
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

tcp 检测

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

Readiness

Liveness 检测和 Readiness 检测是两种 Health Check 机制,如果不特意配置,Kubernetes 将对两种检测采取相同的默认行为,即通过判断容器启动进程的返回值是否为零来判断检测是否成功。

两种检测的配置方法完全一样,支持的配置参数也一样。不同之处在于检测失败后的行为:Liveness 检测是重启容器;Readiness 检测则是将容器设置为不可用,不接收 Service 转发的请求。

Liveness 检测和 Readiness 检测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用。用 Liveness 检测判断容器是否需要重启以实现自愈;用 Readiness 检测判断容器是否已经准备好对外提供服务。

生产环境中的kubernetes 优先级与抢占

基本原理

优先级与抢占是为了确保一个高优先级的pod在调度失败后,可以通过"挤走" 低优先级的pod,腾出空间后保证它可以调度成功。 我们首先需要在集群中声明PriorityClass来定义优先等级数值和抢占策略,

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high
value: 10000
preemptionPolicy: Never
globalDefault: false
description: "This priority class should be used for high priority service pods."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low
value: -999
globalDefault: false
description: "This priority class should be used for log priority service pods."

如上所示定义了两个PriorityClass对象。然后就可以在pod中声明使用它了:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nginx
  name: high-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: nginx
  template:
    metadata:
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx
        imagePullPolicy: Always
        name: nginx
        resources:
          limits:
            cpu: "500m"
      priorityClassName: high

这个 Pod 通过 priorityClassName 字段。声明了要使用名叫 high-priority 的 PriorityClass。当这个 Pod 被提交给 Kubernetes 之后,Kubernetes 的 Priority AdmissionController 就会自动将这个 Pod 的spec.priority 字段设置为10000。
如下:

  preemptionPolicy: Never
  priority: 10000
  priorityClassName: high

Pod创建好之后,调度器就会根据创建的priority进行调度的决策,首先会在等待队列中优先调度,如果调度失败就会进行抢占: 依次遍历所有的node找出最适合的node,将该nodename填充在spec.nominatedNodeName字段上,然后等待被抢占的pod全都退出后再次尝试调度到该node之上。具体的逻辑请自行阅读相关代码,此处不在赘述。

生产环境使用方式

  • v1.14版本的kubernetes该feature已经GA,默认开启,但此时我们往往没有做好准备,如果直接给pod设置优先级会导致很多意料之外的抢占,造成故障。 (参见How a Production Outage Was Caused Using Kubernetes Pod Priorities)。所以建议在初次使用的时候还是先显式关闭抢占,只设置优先级,等集群中所有的pod都有了各自的优先级之后再开启,此时所有的抢占都是可预期的。可以通过kube-scheduler 配置文件中的disablePreemption: true进行关闭
  • 调度器是根据优先级pod.spec.priority数值来决定优先级的,而用户是通过指定pod.sepc.priorityclass的名字来为pod选择优先级的,此时就需要Priority AdmissionController根据priorityclass name为pod自动转换并设置对应的priority数值。我们需要确保该admissionController开启,如果你的kube-apiserver中还是通过--admission-control flag来指定admissionoController的话需要手动添加Priority admissonController,如果是通过--enable-admission-plugins来指定的话,无需操作,该admissionController默认开启。
  • 按照集群规划创建对应的PriorityClass及其对应的抢占策略,目前支持两种策略: Never, PreemptLowerPriority。 Never可以指定不抢占其他pod, 即使该pod优先级特别高,这对于一些离线任务较为友好。 非抢占调度在v1.15中为alpha, 需要通过--feature-gates=NonPreemptingPriority=true 进行开启。
  • 在创建好了PriorityClass之后,需要防止高优先级的pod过分占用太多资源,使用resourceQuota机制来限制其使用量,避免低优先级的pod总是被高优先级的pod压制,造成资源饥饿。resoueceQuote可以通过指定scope为PriorityClass来限定某个优先级能使用的资源量:
apiVersion: v1
kind: ResourceQuota
metadata:
  name: high-priority
spec:
  hard:
    pods: "10"
  scopeSelector:
    matchExpressions:
    - operator : In
      scopeName: PriorityClass
      values: ["high"]

如上即为限制高优先级的pod最多能创建10个。operator指定了作用的对象,operator: In可以显式指定作用于的哪些priorityClass,operator: Exists则指定作用于该namespace下的所有priorityClass。

有时候我们想要只有priorityClass对应的resourceQuota存在之后才能创建pod,确保所有的priorityClass的pod资源都是受控的。 如果那个namespacew没有该resourceQuota则拒绝该pod的创建,该操作可以通过指定--admission-control-config-file文件来设置,内容如下:

apiVersion: apiserver.k8s.io/v1alpha1
kind: AdmissionConfiguration
plugins:
- name: "ResourceQuota"
  configuration:
    apiVersion: resourcequota.admission.k8s.io/v1beta1
    kind: Configuration
    limitedResources:
    - resource: pods
      matchScopes:
      - scopeName: PriorityClass 
        operator: In
        values: ["high"]

该配置定义了: "high"优先级的pod只能在指定的namespaces下创建,该namespaces有作用于"high"优先级的resouceQuota,上面第四步中的resouceQuota即可满足要求。
scopeName定义了作用的对象为priorityClass, operator指定了作用的范围,可以是In操作符,指定某几个value, 也可以是Exits操作符,指定所有的PriorityClass必须有对应的quota存在, 否则该namespace就无法创建该优先级的pod,这些操作符与上面resouceQuota中定义的一一对应。通过这样就限制了一些优先级只能在有资源约束的namespace下创建。

  • 如果没有显式指定优先级,则默认的优先级值为0,需要结合业务规划决定是否有必要调整默认优先级。
  • 对于一些daemonset需要显式设置较高的优先级来防止被抢占,在部署一个新的daemonset的时候需要考虑是否会造成大规模pod的抢占。
  • 等到所有的优先级设置完毕之后就可以开启抢占功能了,此时集群中所有pending 的高优先级pod就会瞬间抢占,还是需要额外小心,确保集群中高优先级的pod不会导致低优先级的pod大规模被kill,如果我们提前设置了对应的resource quota值,则会有一定的资源约束。
  • 优先级和抢占对于资源的精细化运营考验很大,对于resource quota的设置需要十分精细,需要考虑两个维度来设置: namespace层面和priority层面,我们既希望限制namespace使用的资源,有希望某个priority使用的资源,防止低优先级的pod资源饥饿。 可以在初期只考虑namespace层面的限制,priority层面通过上层业务来保证,例如创建任务的时候保证集群中高优先级的资源使用量不超过50%等。

CoreDNS 添加自定义DNS解析记录

这里用到的是 CoreDNS 的 hosts plugin 插件。该插件仅支持 A, AAAA, 和 PTR 记录。

在线修改 coredns 的 configmap,不用重启哦。

配置

kubectl edit configmap coredns -n kube-system

apiVersion: v1
data:
  Corefile: |
    .:53 {
        errors
        health
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
          pods insecure
          fallthrough in-addr.arpa ip6.arpa
        }
        hosts {
            192.168.1.122     demo1.xx.com
            192.168.1.123     demo2.xx.com
            fallthrough
        }
        prometheus :9153
        forward . /etc/resolv.conf {
          prefer_udp
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  labels:
    addonmanager.kubernetes.io/mode: EnsureExists
  name: coredns
  namespace: kube-system

还要注意的是 forward plugin 插件。用于设置 upstream Nameservers 上游 DNS 服务器。CoreDNS 就是通过它让容器能够解析外网的。

这里设置的是宿主机的 /etc/resolv.conf 文件中的 nameservers。

另外,在 kuberntets 中,pod 的默认 dnsPolicy 不是 Default,而是 ClusterFirst。

"ClusterFirst": Any DNS query that does not match the configured cluster domain suffix,
such as "www.kubernetes.io", is forwarded to the upstream nameserver inherited from the node.

“ClusterFirst”:任何与配置的集群域后缀不匹配的 DNS 查询,
例如“www.kubernetes.io”,被转发到从节点继承的上游名称服务器。

Kubernetes Pod dnsPolicy 配置

在Kubernetes中,可以针对每个Pod设置DNS的策略,通过PodSpec下的dnsPolicy字段可以指定相应的策略,目前支持的策略如下:

  • Default: Pod继承所在宿主机的设置,也就是直接将宿主机的/etc/resolv.conf内容挂载到容器中。
  • ClusterFirst: 默认的配置,所有请求会优先在集群所在域查询,如果没有才会转发到上游DNS。
  • ClusterFirstWithHostNet: 和ClusterFirst一样,不过是Pod运行在hostNetwork:true的情况下强制指定的。
  • None: 1.9版本引入的一个新值,这个配置忽略所有配置,以Pod的dnsConfig字段为准。

为什么会想起找一下dnsPolicy的文档呢,也是因为Pod里默认使用了ClusterFirst策略,导致经常有DNS请求出现timeout问题,想用一个简单的办法继承宿主机的配置,现在看来比较简单了,直接设置dnsPolicy:Default就可以了。

针对上面说的dnsConfig字段,也有个详细的说明:
dnsConfig字段包括下面几个属性:

  • nameservers: DNS Server的列表,最多3个IP/
  • searches: search域名列表,也就是/etc/resolv.conf中的search字段的配置,最多配置6个
  • options: u选项列表,也就是/etc/resolv.conf中的option字段的配置

一个测试的yaml

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: dns-example
spec:
  containers:
    - name: test
      image: nginx
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 1.2.3.4
    searches:
      - ns1.svc.cluster.local
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

最后Pod中的/etc/resolv.conf配置就如下:

nameserver 1.2.3.4
search ns1.svc.cluster.local my.dns.search.suffix
options ndots:2 edns0