性能优化与故障处理

概述

参考:

为什么有时候无法 kill 掉容器中 PID 为 1 的进程

容器无法启动时,如何排查

场景:有些时候我们用一个官方的容器直接启动,会报错,或者说效果不是我们想要的,我们大概知道如何排查,比如改改容器里面的配置文件,重新启动什么的,那么问题来了,容器起不来我怎么进去?

如下实例,启动一个 consul 容器报错

[root@10-222-32-122 ~]# docker run -d --name=consul --net=host gliderlabs/consul-server -bootstrap
[root@10-222-32-122 ~]# docker ps -a --no-trunc
CONTAINER ID                                                       IMAGE                      COMMAND                                                      CREATED             STATUS                     PORTS               NAMES
88f8ca844420937fc57c7f46b3b99222a7fdd47591e8a14da34c4110fe3f5c29   gliderlabs/consul-server   "/bin/consul agent -server -config-dir=/config -bootstrap"   3 minutes ago       Exited (1) 3 minutes ago                       consul
[root@10-222-32-122 ~]# docker logs consul
==> WARNING: Bootstrap mode enabled! Do not enable unless necessary
==> Starting Consul agent...
==> Error starting agent: Failed to get advertise address: Multiple private IPs found. Please configure one.
[root@10-222-32-122 ~]# hostname -I
10.222.32.122 172.17.0.1

我们可以通过下面的方法,让容器先夯住,让后进入容器调试

[root@10-222-32-122 ~]# docker rm -fv consul
[root@10-222-32-122 ~]# docker run  --rm  --entrypoint "ls" --name=consul --net=host gliderlabs/consul-server /config
agent.json
server.json
[root@10-222-32-122 ~]# docker run  -d --entrypoint tail --name=consul --net=host gliderlabs/consul-server -F /tmp/tmp.txt
[root@10-222-32-122 ~]# docker exec -it consul sh

docker 比较不错的 image 推荐

https://hub.docker.com/r/polinux/stress/ # 一个非常好用的压测容器,可以对容器指定其所使用的内存和 cpu 等资源的大小。当创建完资源配合等资源限制的对象后,可以通过该容器来测试资源限制是否生效。

使用示例如下:

docker run \
  -ti \
  --rm \
  polinux/stress stress \
    --cpu 1 \
    --io 1 \
    --vm 1 \
    --vm-bytes 128M \
    --timeout 1s \
    --verbose

https://hub.docker.com/r/containous/whoami # 一个 go 语言编写的 web 服务器,当请求该容器时,可以输出操作系统信息和 HTTP 请求等,信息如下所示:包括当前容器的 ip 地址,容器的主机名等等

如何运行一个容器并启动 bash

由于容器中的进行执行完成后就会自动退出,所以每次 docker run 之后,有一部分容器无法持久运行,比如 centos 镜像,这时候可以使用下面的方式让容器镜像持续输出任意内容而不会因进程完成而自动退出了

docker create -it -d --name test centos:latest && docker start test

通过 host 的 veth 设备查找对端 container 的 ip

for i in docker ps -q; do pid=$(docker inspect –format ‘{{.State.Pid}}’ $i); nsenter -t $pid –net ip addr | grep -A 2 if40; done

注意命令中 grep 筛选的 if40 中的 40 是宿主机上查到的 veth 设备的序号,通过 ip link show 即可获得设备需要,在屏幕最左侧。而 if40 则是容器网络设备所关联对端网络设备名称。下面示例可以看到,155 对应 if155,if154 对应 154

# 宿主机网络设备信息
~]# ip a
....
155: veth74d05e8@if154: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
    link/ether 0a:9e:19:c3:98:b7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::89e:19ff:fec3:98b7/64 scope link
       valid_lft forever preferred_lft forever

# 容器内网络设备信息
network-scripts]# nsenter -n -t 3039
network-scripts]# ip a
....
154: eth0@if155: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

通过各种系统信息查找目标容器

在管理 Kubernetes 集群的过程中,我们经常会遇到这样一种情况:在某台节点上发现某个进程资源占用量很高,却又不知道是哪个容器里的进程。有没有办法可以根据 PID 快速找到 Pod 名称呢?

要获取容器的 ID,通过在 PID 对应的 cgroup 信息中即可查到(比如进程号 32000 的 cgroup 信息在 /proc/32000/cgroup 文件中。该文件内容每行的最后一个段落就是 容器的 ID。)

通过 PID 查找容器

#!/bin/bash
pid=$1
CID=`cat /proc/${pid}/cgroup | head -1 | awk -F '/' '{print $5}'`
CID=$(echo ${CID:7:15})
sudo docker inspect $CID | jq '.[0].Config.Labels."io.kubernetes.pod.name"'

而想要反查,那么通过 docker top CONTAINER 命令即可直接获取改容器的 PID

通过 mount 信息查找目标容器

image.png 通过 mount 信息中的 layer 信息中的 cacheID,去 layer 的元数据目录中筛选包含该 cacheID 的文件

mounts]# pwd
/var/lib/docker/image/overlay2/layerdb/mounts
mounts]# grep -r d976eddf7575a3464486d92539229146f3df66080a3265195791ebb0d24b24dd ./
./mounts/28f5bed704dc80bed6dbaa8af514d2191d8d4ab0339bb3a663e66609ccd34c10/mount-id:d976eddf7575a3464486d92539229146f3df66080a3265195791ebb0d24b24dd
./mounts/28f5bed704dc80bed6dbaa8af514d2191d8d4ab0339bb3a663e66609ccd34c10/init-id:d976eddf7575a3464486d92539229146f3df66080a3265195791ebb0d24b24dd-init

可以看到该 layer 所属的 ContainerID 为 28f5bed704dc80bed6dbaa8af514d2191d8d4ab0339bb3a663e66609ccd34c10

这时候通过 docker ps 命令,筛选 ID 前几位,就可以找到该容器了

layerdb]# docker ps --all | grep 28f5bed704
28f5bed704dc        ubuntu:latest       "/bin/bash"         2 hours ago         Up 4 minutes                            docker_runtime_test

通过 netns 信息查找容器

failed to get sandbox ip: check network namespace closed: remove netns: unlinkat /var/run/netns/XXXXXX: device or resource busy。参考:https://github.com/containerd/containerd/issues/3667

有时候 pod 在删除时,会卡在 terminating 状态,并在日志中显示无法删除某个 /var/run/netns/ 目录下的 netns,根据这个 netns 的名称,可以找到容器

grep -l XXX /proc/*/mountinfo # 该命令会显示正在使用 XXX 这个 netns 的进程。然后根据进程号找到容器,重启它即可。

Docker 容器时间与宿主机同步

在我们平时使用 docker,运行我们的应用的时候,访问应用页面的时间与现在相差 8 个小时。无法结合时间点去判断当时服务的异常。同样,当我们在 docker 上运行某些服务时,需要时间与宿主机同步,否则会发生异常

为了保证容器和宿主机之间的时间同步,可以使用以下几种办法:

1.Docker run

使用 docker run 运行容器时,添加参数 -v /etc/localtime:/etc/localtime:ro

2.DockerFile

在 Docker File 中添加如下参数:将时区配置添加到环境变量,并使用软连接,并将时区配置文件覆盖

注意:需要保证所使用的基础镜像具有 tzdata 包,否则不会加载 /etc/localtime 文件以更新时区

RUN apk add --no-caceh tzdata
ENV TimeZone=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone >/etc/timezone

实例 DockerFile 如下:

设置变量与创建软链接这两个方法任选其一即可

FROM alpine   # Centos 基础镜像
RUN apk add --no-caceh tzdata
ENV TimeZone=Asia/Shanghai # 添加时区环境变量,亚洲,上海
RUN ln -snf /usr/share/zoneinfo/$TimeZone /etc/localtime && echo $TimeZone >/etc/timezone # 使用软连接,并且将时区配置覆盖/etc/timezone

构建镜像

docker build -t alpine:time .

正在运行的容器,时间如何同步?

这种方式同样适用于,构造镜像完成后,时间不同步的状况

但是,同样需要保证容器中具有 tzdata 包

在宿主机执行命令如下:

docker cp /usr/share/zoneinfo/Asia/Shanghai <容器名>:/etc/localtime