kubernetes controller manager 报错 CIDRNotAvailable

今天查看集群事件

kubectl get events --sort-by=.metadata.creationTimestamp
kubectl get events -A -w

发现 controller manager 时不时有报错:

Node xxx status is now: CIDRNotAvailable

和同行稍微交流一会,参考官网文档 https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/

将controller-manager的配置 --allocate-node-cidrs 改为 false,错入日志就没有了。

实际上集群也没有问题,只是不断会有这样的错误日志。具体原因还待深究。目前只做记录。

这个配置的官方解释是:

--allocate-node-cidrs
Should CIDRs for Pods be allocated and set on the cloud provider.

kubernetes pod 挂载不同路径

今天给一个daemonset 创建挂载卷,要求多个pod不会挂载同一个挂载卷,查看了相关资料,有两种方式可以达到:

  1. 使用 statefulset的volumeClaimTemplate
  2. 加载环境变量挂载subpath

第一种方式因为需要使用 storageClassName ,目前的环境没有做相关的东西,嫌麻烦就先不做了,第二种方式简单,需要在普通挂载的基础上新增两个内容:

  1. 环境变量 valueFrom/fieldRef
  2. 挂载 subPathExpr
apiVersion: v1
kind: Pod
metadata:
  name: pod1
spec:
  containers:
  - name: container1
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    image: busybox
    command: [ "sh", "-c", "while [ true ]; do echo 'Hello'; sleep 10; done | tee -a /logs/hello.txt" ]
    volumeMounts:
    - name: workdir1
      mountPath: /logs
      subPathExpr: $(POD_NAME)
      readOnly: false
  restartPolicy: Never
  volumes:
  - name: workdir1
    hostPath:
      path: /var/log/pods

使用 fluentd 抓取 k8s 的组件日志并推送至 EFK 日志栈

以容器方式启动的组件,常常随着容器的停止而销毁,给平时定位问题带来了一定的难度。

这一篇记录如何使用 fluentd 采集 kubernetes 的容器组件的日志,并推送到es中。本文适用的 kubernetes 版本为 v1.10。

日志类型

k8s系统组件有两种类型:在容器中运行的和不在容器中运行的。例如:

  • 在容器中运行的 kube-scheduler 和 kube-proxy。
  • 不在容器中运行的 kubelet 和容器运行时(例如 Docker)

kubelet和docker的日志都由 journald 进行管理,定位问题时问题不大。

容器组件日志位置

文件夹 /var/log/containers 下包含了所有组件的日志链接。

进入这个文件夹我们可以看到一家人的组件排的整整齐齐:

$ ls /var/log/containers

calico-node-xxx.log
coredns-xxx.log
kube-apiserver-xxx.log
kube-controller-manager-xxx.log
kube-proxy-xxx.log
kube-scheduler-xxx.log
traefik-ingress-controller-xxx.log

实际上这是个软链接,后边还跟了两层软链接:

-> /var/log/pods

-> /var/lib/docker/containers/

更多需要考虑的

想象一下我们使用 kibana 查看日志,在满屏的日志中,我们需要区分这些日志:

  • 不同的节点
  • 同一节点的不同组件
  • 同一组件的不同容器

其中同一节点不同组件,我们可以在 fluentd 中对 日志来源打 tag 标记。可以参考 fluentd 官方帮助 https://docs.fluentd.org/input/tail

不同容器可以通过 fluentd 配置把文件名附到日志字段。

节点名也同样如此。那么fluentd 容器如何获得节点名呢?但是这有个窍门,为 fluentd 容器增加一个环境变量:

env:
  - name: MY_NODE_NAME
    valueFrom:
      fieldRef:
        fieldPath: spec.nodeName

更多实用的变量可参考官方:https://kubernetes.cn/zh/docs/tasks/inject-data-application/environment-variable-expose-pod-information/

开始

基本思路是fluentd 的配置文件使用 configmap 进行保存。

然后将所有组件的日志和configmap都挂在到 fluentd 容器中。

创建 fluentd 配置文件

注意 fluentd 要配置 read_from_head true ,否则会有组件启动日志薛定谔采集的问题。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-for-k8s-component
data:
  fluent.conf: |-
    <source>
    @type tail
    read_from_head true
    path /var/log/containers/kube-apiserver-*.log
    path_key file_name 
    pos_file /log/fluentd/pos/pos-kube-apiserver.log
    tag runtime-kube-apiserver
    limit_recently_modified 7d
    multiline_flush_interval 1s
    <parse> 
        @type json
        time_key time
        time_type string
        time_format %Y-%m-%dT%H:%M:%S.%N%z
    </parse>
    </source>
    ... ...
    # 依葫芦画瓢把一系列的日志搞进来
    ... ...
    
    # 为每条记录附加节点字段
    <filter runtime-*>
      @type record_transformer
      <record>
        nodename "#{ENV['MY_NODE_NAME']}"
      </record>
    </filter>

    # es地址
    <match runtime-*>
    @type forward
    <server>
      host 1.2.3.4
      port 12345
      weight 50
    </server>
    <server>
      host 1.2.3.4
      port 12346
      weight 50
    </server>
    
    <buffer tag,time>
        @type file
        path /var/log/td-agent/buffer/docker
        timekey 3600
        timekey_wait 0s
        timekey_use_utc false
        flush_mode interval
        flush_interval 10s
        chunk_limit_size 20M
    </buffer>
    </match>

创建 fluentd damenset

需要注意的有:

  • 采集容器在每个节点上都有,所以要避免 calico 分配IP导致路由表难看,直接使用 host 的IP即可。
  • 注意污染和容忍的配置,要容忍所有污染,否则一旦新增一个污染就取不到这个节点的日志了。
  • 镜像尽可能选新的
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd-for-k8s-component
  labels:
    k8s-app: fluentd-for-k8s-component
    version: v1
spec:
  selector:
    matchLabels:
      k8s-app: fluentd-for-k8s-component
  template:
    metadata:
      labels:
        k8s-app: fluentd-for-k8s-component
        version: v1
    spec:
      containers:
        - name: fluentd
          image: "fluent/fluentd:v1.4"
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 200Mi
          env:
            - name: MY_NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
          volumeMounts:
            - name: fluentd-pos
              mountPath: /log/fluentd/pos
            - name: fluentd-config
              mountPath: /fluentd/etc/fluent.conf
              subPath: fluent.conf
            - name: var-log-containers
              mountPath: /var/log/containers
              readOnly: true
            - name: var-log-pods
              mountPath: /var/log/pods
              readOnly: true
            - name: var-log-real
              mountPath: /var/lib/docker/containers
              readOnly: true
          securityContext:
            runAsUser: 0
      terminationGracePeriodSeconds: 30
      volumes:
        - name: fluentd-config
          configMap:
            name: fluentd-for-k8s-component
            defaultMode: 420
        - name: fluentd-pos
          hostPath:
              path: /app/fluentd/pos
              type: DirectoryOrCreate
        - name: var-log-containers
          hostPath:
              path: /var/log/containers
              type: DirectoryOrCreate
        - name: var-log-pods
          hostPath:
              path: /var/log/pods
              type: DirectoryOrCreate
        - name: var-log-real
          hostPath:
              path: /var/lib/docker/containers
              type: DirectoryOrCreate
      hostNetwork: true
      tolerations:
      - operator: Exists
        effect: NoSchedule
      - operator: Exists
        effect: NoExecute
      - key: CriticalAddonsOnly
        operator: Exists

查看并记录服务器硬件信息

查了相关的文章做个记录,从CPU、内存、网卡、硬盘、主板、BIOS、操作系统几个方面。

CPU

cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c   # cpu核数和型号,主频等。
getconf LONG_BIT                                        # 位数
dmidecode | grep "Product Name"                         # 服务器型号

内存

free -mh                 # 概要查看内存情况
dmidecode -t memory      # 查看内存硬件信息

网卡

ifconfig
ethtool bond0
ethtool bond1

硬盘

lsblk  # 硬盘和分区分布
fdisk -l # 硬盘和分区的详细信息

主板

dmidecode |grep 'Serial Number' # 主板序列号

BIOS

dmidecode -t bios

操作系统

uname -a
cat /etc/redhat-release
dmidecode -s system-serial-number # 系统序列号

一个简单的脚本

#!/usr/bin/env bash

if  [ ! -e '/usr/bin/wget' ]; then
    echo "Error: wget command not found. You must be install wget command at first."
    exit 1
fi

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;36m'
PLAIN='\033[0m'

get_opsy() {
    [ -f /etc/redhat-release ] && awk '{print ($1,$3~/^[0-9]/?$3:$4)}' /etc/redhat-release && return
    [ -f /etc/os-release ] && awk -F'[= "]' '/PRETTY_NAME/{print $3,$4,$5}' /etc/os-release && return
    [ -f /etc/lsb-release ] && awk -F'[="]+' '/DESCRIPTION/{print $2}' /etc/lsb-release && return
}

next() {
    printf "%-70s\n" "-" | sed 's/\s/-/g'
}

calc_disk() {
    local total_size=0
    local array=$@
    for size in ${array[@]}
    do
        [ "${size}" == "0" ] && size_t=0 || size_t=`echo ${size:0:${#size}-1}`
        [ "`echo ${size:(-1)}`" == "K" ] && size=0
        [ "`echo ${size:(-1)}`" == "M" ] && size=$( awk 'BEGIN{printf "%.1f", '$size_t' / 1024}' )
        [ "`echo ${size:(-1)}`" == "T" ] && size=$( awk 'BEGIN{printf "%.1f", '$size_t' * 1024}' )
        [ "`echo ${size:(-1)}`" == "G" ] && size=${size_t}
        total_size=$( awk 'BEGIN{printf "%.1f", '$total_size' + '$size'}' )
    done
    echo ${total_size}
}

cname=$( awk -F: '/model name/ {name=$2} END {print name}' /proc/cpuinfo | sed 's/^[ \t]*//;s/[ \t]*$//' )
cores=$( awk -F: '/model name/ {core++} END {print core}' /proc/cpuinfo )
freq=$( awk -F'[ :]' '/cpu MHz/ {print $4;exit}' /proc/cpuinfo )
tram=$( free -m | awk '/Mem/ {print $2}' )
uram=$( free -m | awk '/Mem/ {print $3}' )
swap=$( free -m | awk '/Swap/ {print $2}' )
uswap=$( free -m | awk '/Swap/ {print $3}' )
up=$( awk '{a=$1/86400;b=($1%86400)/3600;c=($1%3600)/60} {printf("%d days, %d hour %d min\n",a,b,c)}' /proc/uptime )
load=$( w | head -1 | awk -F'load average:' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//' )
opsy=$( get_opsy )
arch=$( uname -m )
lbit=$( getconf LONG_BIT )
kern=$( uname -r )
#ipv6=$( wget -qO- -t1 -T2 ipv6.icanhazip.com )
disk_size1=($( LANG=C df -hPl | grep -wvE '\-|none|tmpfs|devtmpfs|by-uuid|chroot|Filesystem|udev|docker' | awk '{print $2}' ))
disk_size2=($( LANG=C df -hPl | grep -wvE '\-|none|tmpfs|devtmpfs|by-uuid|chroot|Filesystem|udev|docker' | awk '{print $3}' ))
disk_total_size=$( calc_disk "${disk_size1[@]}" )
disk_used_size=$( calc_disk "${disk_size2[@]}" )

clear
next
echo -e "CPU                  : ${BLUE}$cname${PLAIN}"
echo -e "核心数               : ${BLUE}$cores${PLAIN}"
echo -e "CPU 主频             : ${BLUE}$freq MHz${PLAIN}"
echo -e "硬盘                 : ${BLUE}$disk_total_size GB ($disk_used_size GB Used)${PLAIN}"
echo -e "内存                 : ${BLUE}$tram MB ($uram MB Used)${PLAIN}"
echo -e "交换分区             : ${BLUE}$swap MB ($uswap MB Used)${PLAIN}"
echo -e "OS                   : ${BLUE}$opsy${PLAIN}"
echo -e "Arch                 : ${BLUE}$arch ($lbit Bit)${PLAIN}"
echo -e "内核                 : ${BLUE}$kern${PLAIN}"
echo -e "运行时间             : ${BLUE}$up${PLAIN}"
echo -e "平均负载             : ${BLUE}$load${PLAIN}"
next

附:dmidecode 是什么

dmidecode可以让你在Linux系统下获取有关硬件方面的信息。

dmidecode的作用是将DMI数据库中的信息解码,以可读的文本方式显示。由于DMI信息可以人为修改,因此里面的信息不一定是系统准确的信息。

dmidecode遵循SMBIOS/DMI标准,其输出的信息包括BIOS、系统、主板、处理器、内存、缓存等等。

DMI(Desktop Management Interface,DMI)就是帮助收集电脑系统信息的管理系统,DMI信息的收集必须在严格遵照SMBIOS规范的前提下进行。SMBIOS(System Management BIOS)是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范。SMBIOS和DMI是由行业指导机构Desktop Management Task Force(DMTF)起草的开放性的技术标准,其中DMI设计适用于任何的平台和操作系统。

DMI充当了管理工具和系统层之间接口的角色。它建立了标准的可管理系统更加方便了电脑厂商和用户对系统的了解。DMI的主要组成部分是Management Information Format(MIF)数据库。这个数据库包括了所有有关电脑系统和配件的信息。通过DMI,用户可以获取序列号、电脑厂商、串口信息以及其它系统配件信息。