Kubernetes 架构监控
作者:Gianluca Arbezzano254 / 产品, 用例, 开发者
2018年3月19日
导航至
在监控 Kubernetes 架构时,您需要考虑两个重要方面。一是关于底层资源,即 Kubernetes 运行的裸机。二是关于您部署的每个服务、入口和 Pod。为了更好地了解您的集群,您需要从两者获取指标,以便您可以比较和参考这些指标。我写这篇文章是因为在 InfluxData,我们正在深入研究 Kubernetes,我认为现在是时候分享我们应用于集群的一些实践,以获得您的反馈(也因为它们运行良好)。您应该为监控设置一个完全专用的命名空间。我们称之为 monitoring
kubectl create namespace monitoring
不要将其部署在默认命名空间中。一般来说,默认命名空间应始终为空。我假设您能够在此处在 Kubernetes 上部署 InfluxDB 和 Chronograf,否则本文将变成一个无法阅读的糟糕 YAML 文件。
关于持久卷的说明。InfluxDB、Kapacitor 和 Chronograf 将数据存储在磁盘上。这意味着我们需要小心如何管理它们。否则,我们的数据将随容器消失。Kubernetes 有一个名为持久卷的资源,可帮助您根据集群的运行位置挂载卷。我们正在使用 AWS,并声明 EBS 卷来管理 /var/lib/influxdb
和其他目录。
现在您的系统已经运行起来,我们可以使用 DaemonSet 在每个节点上部署 Telegraf。此 Telegraf 代理将负责处理主机上的 iops、网络、cpu、内存、磁盘和其他服务等资源。为了做到这一点,我们需要共享主机上的一些目录,否则 Telegraf 最终将监控容器而不是主机。
DaemonSet 是 Kubernetes 的一种资源,可自动将容器分布在所有节点上。如果您需要部署指标或日志收集器(就像我们现在正在做的那样),它非常强大。
apiVersion: v1
kind: ConfigMap
metadata:
name: telegraf
namespace: monitoring
labels:
k8s-app: telegraf
data:
telegraf.conf: |+
[global_tags]
env = "$ENV"
[agent]
hostname = "$HOSTNAME"
[[outputs.influxdb]]
urls = ["$MONITOR_HOST"] # required
database = "$MONITOR_DATABASE" # required
timeout = "5s"
username = "$MONITOR_USERNAME"
password = "$MONITOR_PASSWORD"
[[inputs.cpu]]
percpu = true
totalcpu = true
collect_cpu_time = false
report_active = false
[[inputs.disk]]
ignore_fs = ["tmpfs", "devtmpfs", "devfs"]
[[inputs.diskio]]
[[inputs.kernel]]
[[inputs.mem]]
[[inputs.processes]]
[[inputs.swap]]
[[inputs.system]]
[[inputs.docker]]
endpoint = "unix:///var/run/docker.sock"
[[inputs.kubernetes]]
url = "http://1.1.1.1:10255"
---
# Section: Daemonset
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: telegraf
namespace: monitoring
labels:
k8s-app: telegraf
spec:
selector:
matchLabels:
name: telegraf
template:
metadata:
labels:
name: telegraf
spec:
containers:
- name: telegraf
image: docker.io/telegraf:1.5.2
resources:
limits:
memory: 500Mi
requests:
cpu: 500m
memory: 500Mi
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: "HOST_PROC"
value: "/rootfs/proc"
- name: "HOST_SYS"
value: "/rootfs/sys"
- name: ENV
valueFrom:
secretKeyRef:
name: telegraf
key: env
- name: MONITOR_USERNAME
valueFrom:
secretKeyRef:
name: telegraf
key: monitor_username
- name: MONITOR_PASSWORD
valueFrom:
secretKeyRef:
name: telegraf
key: monitor_password
- name: MONITOR_HOST
valueFrom:
secretKeyRef:
name: telegraf
key: monitor_host
- name: MONITOR_DATABASE
valueFrom:
secretKeyRef:
name: telegraf
key: monitor_database
volumeMounts:
- name: sys
mountPath: /rootfs/sys
readOnly: true
- name: docker
mountPath: /var/run/docker.sock
readOnly: true
- name: proc
mountPath: /rootfs/proc
readOnly: true
- name: docker-socket
mountPath: /var/run/docker.sock
- name: utmp
mountPath: /var/run/utmp
readOnly: true
- name: config
mountPath: /etc/telegraf
terminationGracePeriodSeconds: 30
volumes:
- name: sys
hostPath:
path: /sys
- name: docker-socket
hostPath:
path: /var/run/docker.sock
- name: proc
hostPath:
path: /proc
- name: utmp
hostPath:
path: /var/run/utmp
- name: config
configMap:
name: telegraf
如您所见,config-map 和 Telegraf 需要一些环境变量,因此我使用 secret
来注入它们。要创建它,请运行此命令,并根据您的需要替换选项
kubectl create secret -n monitoring generic telegraf --from-literal=env=prod --from-literal=monitor_username=youruser --from-literal=monitor_password=yourpassword --from-literal=monitor_host=https://your.influxdb.local --from-literal=monitor_database=yourdb
在此示例中,有一个名为 env 的参数在 prod 上设置。我们在每个 Telegraf 实例上设置此变量,它标识集群。如果您像我们一样复制环境,则可以在 Chronograf 上创建相同的仪表板,并使用模板变量在集群之间切换。
现在,如果您做的一切都正确,您将能够看到主机和点存储在 InfluxDB 和 Chronograf 上。这只是第一阶段:我们现在可以了解主机的情况,但我们对正在运行的服务一无所知。
Telegraf Sidecar
有不同的方法可以解决这个问题,但我们正在使用的方法称为 sidecar
。这个术语最近在网络和路由网格中变得流行,但它与我们正在做的事情类似。
假设您需要 etcd
,因为您的某个应用程序将其用作存储。在 k8s 上,它将是像这样的 StatefulSet
apiVersion: v1
data:
telegraf.conf: |+
[global_tags]
env = "$ENV"
[[inputs.prometheus]]
urls = ["http://localhost:2379/metrics"]
[agent]
hostname = "$HOSTNAME"
[[outputs.influxdb]]
urls = ["$MONITOR_HOST"]
database = "mydb"
write_consistency = "any"
timeout = "5s"
username = "$MONITOR_USERNAME"
password = "$MONITOR_PASSWORD"
kind: ConfigMap
metadata:
name: telegraf-etcd-config
namespace: myapp
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
namespace: "myapp"
name: "etcd"
labels:
component: "etcd"
spec:
serviceName: "etcd"
# changing replicas value will require a manual etcdctl member remove/add
# command (remove before decreasing and add after increasing)
replicas: 3
template:
metadata:
name: "etcd"
labels:
component: "etcd"
spec:
volumes:
- name: telegraf-etcd-config
configMap:
name: telegraf-etcd-config
containers:
- name: "telegraf"
image: "docker.io/library/telegraf:1.4"
volumeMounts:
- name: telegraf-etcd-config
mountPath: /etc/telegraf
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: MONITOR_HOST
valueFrom:
secretKeyRef:
name: monitor
key: monitor_host
- name: MONITOR_USERNAME
valueFrom:
secretKeyRef:
name: monitor
key: monitor_username
- name: MONITOR_PASSWORD
valueFrom:
secretKeyRef:
name: monitor
key: monitor_password
- name: ENV
valueFrom:
secretKeyRef:
name: monitor
key: env
- name: "etcd"
image: "quay.io/coreos/etcd:v3.2.9"
ports:
- containerPort: 2379
name: client
- containerPort: 2380
name: peer
env:
- name: CLUSTER_SIZE
value: "3"
- name: SET_NAME
value: "etcd"
command:
- "/bin/sh"
- "-ecx"
- |
IP=$(hostname -i)
for i in $(seq 0 $((${CLUSTER_SIZE} - 1))); do
while true; do
echo "Waiting for ${SET_NAME}-${i}.${SET_NAME} to come up"
ping -W 1 -c 1 ${SET_NAME}-${i}.${SET_NAME} > /dev/null && break
sleep 1s
done
done
PEERS=""
for i in $(seq 0 $((${CLUSTER_SIZE} - 1))); do
PEERS="${PEERS}${PEERS:+,}${SET_NAME}-${i}=http://${SET_NAME}-${i}.${SET_NAME}:2380"
done
# start etcd. If cluster is already initialized the `--initial-*` options will be ignored.
exec etcd --name ${HOSTNAME} \
--listen-peer-urls http://${IP}:2380 \
--listen-client-urls http://${IP}:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://${HOSTNAME}.${SET_NAME}:2379 \
--initial-advertise-peer-urls http://${HOSTNAME}.${SET_NAME}:2380 \
--initial-cluster-token etcd-cluster-1 \
--initial-cluster ${PEERS} \
--initial-cluster-state new \
--data-dir /var/run/etcd/default.etcd
如您所见,还有更多内容。有一个 config map,并且在同一个 pod 下部署了两个容器:etcd 和 Telegraf。同一 pod 下的容器共享网络命名空间,因此从 Telegraf 解析 etcd 就像调用 http://localhost:2379/metrics 一样容易。etcd 公开 Prometheus 风格的指标,您可以使用 Telegraf 输入插件来获取它们。
apiVersion: v1
data:
telegraf.conf: |+
[global_tags]
env = "$ENV"
[[inputs.prometheus]]
urls = ["http://localhost:2379/metrics"]
[agent]
hostname = "$HOSTNAME"
[[outputs.influxdb]]
urls = ["$MONITOR_HOST"]
database = "mydb"
write_consistency = "any"
timeout = "5s"
username = "$MONITOR_USERNAME"
password = "$MONITOR_PASSWORD"
kind: ConfigMap
metadata:
name: telegraf-etcd-config
namespace: myapp
假设您的 Go 应用程序使用我们的 sdk 将指标推送到 InfluxDB。您可以做的是在与我们为 etcd
所做的相同的 pod 上部署一个使用 http listener 输入插件的 Telegraf。这个插件很强大,因为它公开了一个兼容的 InfluxDB http 层,当您将应用程序指向 localhost:8086
时,您无需更改任何内容——您最终将与 Telegraf 通信而无需修改代码。
Telegraf 作为您的应用程序和 InfluxDB 之间的中间人是一个优势,因为它将批量处理请求,优化网络流量和 InfluxDB 上的负载。另一个优化是,虽然它需要一些代码,但可以将您的应用程序从 tcp 迁移到 udp。sdk 支持这两种方法,您可以使用 Telegraf 的 socket_listener_plugin。
这意味着您的应用程序将通过 upd 与 Telegraf 通信,并且它们共享网络命名空间,因此数据包丢失将最小化,您的应用程序将更快,Telegraf 将通过 tcp 将点通信到 InfluxDB,您可以确信一切都将落在 InfluxDB 中。奖励:如果 Telegraf 由于某种原因宕机,您的应用程序不会崩溃,因为 udp 不关心!您的应用程序将照常工作,但不会存储任何点。如果这种情况对您有效,那就太好了!
使用 Telegraf 作为 sidecar
来监控 Kubernetes 上的分布式应用程序的好处是,您的服务的监控配置将接近应用程序规范,因此部署很简单,并且共享同一个 pod 服务发现也很容易,就像调用 localhost
一样。
这通常是此环境中的一个问题,因为如果您有一个收集器,容器会发生变化,您将不知道它们将在哪里。配置可能很棘手,但使用此架构,Telegraf 将永远跟随您的应用程序。