简介:
在正常生产环境中使用k8s部署业务后能正常运行还不够,我们需要很多附加的东西来满足日常的需求。比如日志、监控、告警等。这一篇给大家分享一下我们生产环境中的日志集中解决方案。当然不敢说是最好的,分享出来供大家参考。
在正常环境中有几类日志我们比较关心:
1、k8s中的ingress日志。比如traefik,里面记录的从公网域名访问进来的访问记录,类似nginx的access.log
2、istio中envoy边车日志。如果启用了istio那么这个日志也是需要的,每次程序被调用时都会有一个记录。
3、k8s中的事件日志。里面就有容器健康检查失败重新部署,或者pod新建、删除等事件。kubectl describe pods中的Events:内容
4、业务程序运行时产生的业务日志。比如java中常用的log4j套件输出的日志和kubectl logs命令查看的日志。这个和输出方式有关。
实现方式
这里日志收集的方式是采用elk模式,如果大家感兴趣还有loki的模式。当然这次分享是基于elk的。首先要部署的还是es集群,这里我们使用的是虚拟机部署方式非容器化。理由es是公共的组件可以对接所有的产品业务线,单独拿出来部署可以给别的产品线输出集中化日志存储方案。平时维护量还是比较少的,当然我们的es集群每15分钟大概800万条记录规模并不是特别大, 不过以后出现瓶颈横向扩容也不是问题。基于es集群作为日志集中存储,又有集中收集方式。我会按照我自己的理解说说各个的特点。
1、采用边车模式使用filebeat直接对接es集群。这个方式优点是可以随着业务部署一起,pod运行起来以后就能自动把日志输出到es中。但是会有单独的开销,因为每个pod实例都需要额外的硬件资源来运行filebeat。网络可靠性要求比较高,需要实时把日志传过去。
2、业务程序的log4j对接logstash或者kafka。这个方式优点是全部集成了,缺点是依赖第三方。如果处理不好logstash或者kafka宕机可能会影响到业务程序本身的运行,导致业务中断。
3、业务日志写本地文件,通过本地磁盘挂载到pod中。优点是对现有程序没改动,缺点是需要单独去做日志集中处理。
我们目前的做法就是第三种,通过pod挂载本地磁盘目录。单独使用日志收集把日志集中到一台日志服务器上,在通过logstash存入es集群。具体实现方式我主要写目前的我们生产的集中方式。
实施细节
请注意这不是可以照抄的部署文档,只是一个方案的呈现。请在使用的过程中自行根据自己的环境调整适配。
部署rsyslog-server
编辑/etc/rsyslog.conf
$ModLoad imrelp
$InputRELPServerRun 2514
$template myformat,"%msg%\n"
$template istio, "/data/logs/rancher/%syslogtag%/%$year%-%$month%-%$day%-%$hour%.log"
$template ztk8s, "/data/logs/ztk8s/%syslogtag%/%$year%-%$month%-%$day%-%$hour%.log"
local3. ?ztk8s
local4.
?istio;myformat
这里使用的是relp模块,所有服务端和客户端都需要安装rsyslog-relp的包才能正常使用。配置文件定义了两个日志类型,local3作为业务日志收集,local4作为k8s的相关系统组件日志收集。这里目录为rancher是因为我们用的rancher2管理页面。
1、traefik日志集中:
日志的定义需要修改configmap
apiVersion: v1
data:
traefik.toml: |-
# traefik.toml
logLevel = "info"
defaultEntryPoints = ["http","https"]
[entryPoints]
[entryPoints.http]
address = ":80"
compress = true
[entryPoints.https]
address = ":443"
compress = true
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "/ssl/tls.crt"
KeyFile = "/ssl/tls.key"
[entryPoints.traefik] #dashboard端口
address = ":8080"
[entryPoints.prometheus] #metrics端口
address = ":9100"
[ping]
entryPoint = "http" #健康检查
[kubernetes]
[kubernetes.ingressEndpoint]
publishedService = "kube-system/traefik"
[traefikLog]
format = "json"
[accessLog] #访问日志
filePath = "/data/node.log"
format = "json"
[api] #dashboard功能
entryPoint = "traefik"
dashboard = true
[metrics]
[metrics.prometheus]
entryPoint = "prometheus"
kind: ConfigMap
metadata:
name: traefik
namespace: kube-system
然后把主机目录挂载到容器的/data目录中。比如/home/logs/traefik
修改traefik部署的deployment yaml文件
volumeMounts:
- mountPath: /config
name: config
- mountPath: /ssl
name: ssl
- mountPath: /data #日志容器目录
name: acclog
volumes:
- configMap:
defaultMode: 420
name: traefik
name: config
- name: ssl
secret:
defaultMode: 420
secretName: traefik-default-cert
- hostPath:
path: /home/logs/traefik #日志主机目录
type: ""
name: acclog
/home/logs/traefik 在这个目录中就能看到类似于nginx的access.log日志了。
在主机上做一个标签调度比如只允许运行到标签为ingress=traefik的主机上。这样分2-3个主机,部署traefik作为负载均衡和高可用。为防止日志过大占用满磁盘空间,可以配置一个日志轮替。
到这些主机上使用rsyslog进行日志收集到固定的日志服务器中。为防止日志过大,使用logrotate进行日志轮替
cat /etc/logrotate.d/traefik
/home/logs/traefik/node.log
{
su root root
dateext
dateformat -%Y-%m-%d-%H #文件格式
extension .log #扩展名
notifempty
hourly #每小时轮替一次
rotate 24 #保留24个副本
missingok
nocompress #不压缩旧日志,可以指定压缩。
sharedscripts
postrotate
/bin/kill -s USR1 `ps aux | grep traefik | grep -v grep | awk '{print $2}'` 2> /dev/null || true
#完成日志轮替以后执行的脚本,这里配合traefik官网的介绍是传入 USR1的信号给traefik程序就行了
endscript
}
这样就是按小时轮替文件了。详细日志轮替配置推荐这篇文章 https://www.cnblogs.com/yanwei-wang/p/5241436.html
使用rsyslog把所有主机的traefik日志收集到一台主机上。
修改/etc/rsyslog.conf添加
$ModLoad imfile
$ModLoad omrelp
创建文件/etc/rsyslog.d/traefik.conf
$WorkDirectory /var/spool/rsyslog
$InputFileName /home/logs/traefik/node.log
$InputFileTag traefik
$InputFileStateFile traefik
$InputFileSeverity debug
$InputFileFacility local4
$InputFilePersistStateInterval 20000
$RepeatedMsgReduction off
$InputRunFileMonitor
$InputFilePollInterval 3
$template BiglogFormatTomcat,"%msg%\n"
local4.* :omrelp:logserver.tz.com:2514
然后重新启动systemctl restart rsyslog这样在日志服务器中相关目录就能看到内容了。
2、k8s的事件日志收集
在生产运行中k8s里面所有相关动作都会用事件的方式来呈现,比如一个pod是否改变状态、某个程序是否可用、容器是否重启、健康状态改变、拉取镜像、启动和创建容器等。这些事件对我们进行事故排查非常有用,这些事件保存在etcd的集群中。但是k8s为了保证etcd的性能默认只保存1小时以内并且是目前存在于集群中相关资源的事件信息,其它信息会被定时清理。这就需要把事件导出到其它的日志系统中保存。这里我们选择先导出到日志文件,再通过filebeat导入到elk中方便查阅。注意filebeat的镜像版本一定要和elk的版本一致否则无法上传成功。
克隆源代码
git clone https://github.com.cnpmjs.org/heptiolabs/eventrouter.git
修改Makefile中相关参数,以适应中国网络
修改REGISTRY变量为自己私服的地址
修改BUILD_IMAGE为golang:1.14.2以支持通过env设置go-proxy
修改编译步骤中$(DOCKER_BUILD)后边的参数为 "go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct && CGO_ENABLED=0 go build"
修改Dockerfile
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories && apk update --no-cache && apk add ca-certificates
执行make all命令生产镜像
然后docker push eventrouter:xxx上传到私有仓库中。
部署yaml文件。
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: eventrouter
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: eventrouter
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: eventrouter
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: eventrouter
subjects:
- kind: ServiceAccount
name: eventrouter
namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: eventrouter-cm
namespace: kube-system
data:
config.json: |-
{
"sink": "glog"
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namespace: kube-system
data:
filebeat.yml: |-
filebeat.inputs:
- type: log
enabled: true
json.keys_under_root: true
json.overwrite_keys: true
paths:
- "/data/log/eventrouter/*"
output.elasticsearch:
hosts: ["escluster.tz.com:9200"]
index: "filebeat-k8s-pro-event-%{+yyyy.MM.dd}"
setup.ilm.enabled: false
setup.template.name: "filebeat-k8s-pro-event"
setup.template.pattern: "filebeat-k8s-pro-event-*"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: eventrouter
namespace: kube-system
labels:
app: eventrouter
spec:
replicas: 1
selector:
matchLabels:
app: eventrouter
template:
metadata:
labels:
app: eventrouter
tier: control-plane-addons
spec:
containers:
- name: kube-eventrouter
image: docker.tz.com/eventrouter:v0.2
command:
- "/bin/sh"
args:
- "-c"
- "/eventrouter -v 3 -log_dir /data/log/eventrouter"
volumeMounts:
- name: eventrouter-cm
mountPath: /etc/eventrouter
- name: log-path
mountPath: /data/log/eventrouter
- name: filebeat
image: docker.elastic.co/beats/filebeat:7.7.1
command:
- "/bin/sh"
args:
- "-c"
- "filebeat -c /etc/filebeat/filebeat.yml"
volumeMounts:
- name: filebeat-config
mountPath: /etc/filebeat/
- name: log-path
mountPath: /data/log/eventrouter
serviceAccount: eventrouter
volumes:
- name: eventrouter-cm
configMap:
name: eventrouter-cm
- name: filebeat-config
configMap:
name: filebeat-config
- name: log-path
emptyDir: {}
这样再到kibana中就能配置相关索引查看日志了。
3、业务日志
我们的业务是spring-boot开发的,采用的log4j日志套件输出。日志输出的统一路径是logs/node.log文件,相对程序运行的路径。容器的工作目录是/home/platfrom/。所以日志在容器中的完整路径是/home/platfrom/logs/node.log。映射的主机目录是/home/logs/xxx/下面,具体的路径比如 platform-user项目,在主机上的完整路径是/home/logs/platform-user/node.log。
具体的deployment部署yaml为:
volumeMounts:
- mountPath: /home/platform/logs
name: logs
volumes:
- hostPath:
path: /home/logs/platform-user
type: DirectoryOrCreate
name: logs
这里有个很麻烦的事情,我们业务在容器中运行的是非root账户。在自动创建了主机目录以后所有权还是root,导致容器中的程序无法写日志文件。我们的解决方案是,使用inotifywait来自动修改权限和生成rsyslog的日志配置。
所以需要安装inotify-tools、screen的包
先创建一个配置文件。多留几个空行,后面的脚本需要使用。
/etc/rsyslog.d/platform.conf
local3.* :omrelp:logserver.tz.com:2514
编辑监控脚本logsconf.sh。这个脚本可以放在一个nfs的共享目录中,每个k8s的宿主机都挂载nfs目录。这样就避免每个主机都去创建一次脚本了。我们这里的nfs主要是作为工具共享使用的,如果用nfs作为共享存储可能会有很严重的性能问题,这会直接导致业务中断。(血的教训,用实际故障买过单的)
#!/bin/bash
inotifywait -m -e create /home/logs/ | while read file
do
#获取新建的文件目录
# /home/logs/ CREATE,ISDIR test
BASEDIR=$(echo $file | awk '{print $1}')
EVENT=$(echo $file | awk '{print $2}')
DIR=$(echo $file | awk '{print $3}')
if [ `echo $EVENT |grep ISDIR` ] ;then
echo "$EVENT $BASEDIR$DIR"
chown 1000:1000 -R $BASEDIR$DIR
chmod 764 -R $BASEDIR$DIR
#生成日志文件
sed -i '2i$template BiglogFormatTomcat,"%msg%\\n"' /etc/rsyslog.d/platform.conf
sed -i '2i$InputFilePollInterval 3' /etc/rsyslog.d/platform.conf
sed -i '2i$InputRunFileMonitor' /etc/rsyslog.d/platform.conf
sed -i '2i$RepeatedMsgReduction off' /etc/rsyslog.d/platform.conf
sed -i '2i$InputFilePersistStateInterval 20000' /etc/rsyslog.d/platform.conf
sed -i '2i$InputFileFacility local3' /etc/rsyslog.d/platform.conf
sed -i '2i$InputFileSeverity debug' /etc/rsyslog.d/platform.conf
sed -i "2i\$InputFileStateFile $DIR" /etc/rsyslog.d/platform.conf
sed -i "2i\$InputFileTag $DIR" /etc/rsyslog.d/platform.conf
sed -i "2i\$InputFileName $BASEDIR$DIR/node.log" /etc/rsyslog.d/platform.conf
sed -i '2i$WorkDirectory /var/spool/rsyslog' /etc/rsyslog.d/platform.conf
systemctl restart rsyslog
fi
done
脚本做个一个倒叙插入,为啥要倒叙插入的原因忘记了。大家可以试试正常的echo去插入配置文件。
执行命令screen -dmS watchlog /opt/share/shell/k8s/logsconf.sh,脚本路径自己修改。把这个命令放到/etc/rc.local文件中每次启动时自动运行。这样到日志服务器中就能看到日志搜集的文件了。为了防止日志文件过大,可以采用日志轮替的方式或者定期清理。
4、istio的envoy日志。
这个日志的收集只适用于启用了istio的场景,没有使用istio可以不用管。
我们所有的数据信息访问都由边车Envoy来转发,整个应用集群部署完以后你会发现所有程序模块之间的链接就是Envoy的链接。而我们程序异常的时候可能需要用到这些链接记录来排查问题,而Envoy默认是没有开启日志输出的。日志的开启可以通过修改istio的相关配置来实现。在部署istio的名称空间istio-system中的istio配置文件。
kubectl get configmaps -n istio-system istio -o yaml 可以使用这个命令查看详细内容。
在配置名称mesh的内容下有一个accessLogFile的选项 accessLogFile: "/dev/stdout"代表输出到容器的终端上。
修改完以后需要重启istio-sidecar-injector、istio-galley、istio-pilot三个服务让配置生效。重启之后配置会自动下发,去现有的应用中选择istio-proxy的容器就能看到日志了。
另外两个选项:
accessLogEncoding代表日志输出格式默认是json。可选项JSON和TEXT
accessLogFormat代表日志内容格式。
可以通过istioctl命令来修改,也可以手动编辑configmap文件来修改。
istioctl manifest apply --set values.global.proxy.accessLogFile="/dev/stdout"
这时候日志是保存在宿主机/var/log/pods/platform_platform-message-v1-5b4fbbb99d-bbgj9_7c5d07a4-4dd6-4e81-a718-dceb5a5e980f/istio-proxy/0.log这样类似的文件中。
我们可以进行一轮日志收集到专门的服务器上保存以备查看。
这里可以直接修改上面traefik.conf的配置做个修改就行。
$WorkDirectory /var/spool/rsyslog
$InputFileName /var/log/pods/*/istio-proxy/0.log
$InputFileTag istio
$InputFileStateFile istio
$InputFileSeverity debug
$InputFileFacility local3
$InputFilePersistStateInterval 20000
$RepeatedMsgReduction off
$InputRunFileMonitor
$InputFilePollInterval 3
$template BiglogFormatTomcat,"%msg%\n"
local3.* :omrelp:logserver.tz.com:2514
这里全部日志就都收集到了rsyslog-server的服务器上了。接下来可以使用logstash对日志进行处理,格式化再写入到es中。
logstash配置举例:
input{
file {
path => "/data/logs/rancher/istio-envoy/*.log"
codec => "json"
type => "istio-envoy"
}
file{
path => "/data/logs/ztk8s/platfrom-user/*.log"
codec => "json"
type => "platfrom-user"
}
}
filter {
if [type] == "istio-envoy" {
json {
source => "log"
}
}
else if [type] == "platfrom-user"{
json {
source=>"inparam"
}
json {
source=>"result"
}
}
}
output {
if "_grokparsefailure" not in [tags] {
elasticsearch {
hosts => ["escluster.tz.com:9200"]
manage_template => true
index => "%{type}-%{+YYYY.MM.dd}"
}
}
}
我们的业务日志输出就是json格式,相对来说格式化起来更简单一些。其它的日志格式需要自己根据实际情况进行修改。到此日志集中分享就到这里,适合自己的才是最好的。希望大家看了以后有所启发,再次申明这不是产品部署文档不可以照抄,对此产生的一切问题由使用者自己负责。
来源:oschina
链接:https://my.oschina.net/u/4408208/blog/4560465