k8s日志集中收集解决方案

简介:

在正常生产环境中使用k8s部署业务后能正常运还不够,我们需要很多附加的东西来满足日常的需求。比如日志、监控、告警等。这一篇给大家分享一下我们生产环境中的日志集中解决方案。当然不敢说是最好的,分享出来供大家参考。
在正常环境中有几类日志我们比较关心:
1、k8s中的ingress日志。比如traefik,里面记录的从公网域名访问进来的访问记录T G 1 ] ],类似nginx的access.log
2、istio中envoy边车日志。如果启用了is( q ttio那么这个日志也是需要的,每次程序被调用时都会有一个记录。
3、k8s中的事件日 6 T志。里面就有容器健康检* o ! x O H : - ]查失败重新部署,或者pod新建、删/ 3 l U = ( {除等事件。kubectl describe pods中的Events:内容
4、业务程序运时产p 6 _ K h G ]生的业务日志。比如java中常用的log4j套件输出的日志和kubectl logs命令查看的日志。这W O } a个和输出方H ) w r式有关。

实现方式

这里日志收集的方式是K w +采用elk模式,如果大家感兴趣还有loki的模式。当然这次分享是基于elk的。首先要部署A N i ! 2的还是esW } 7 w . , T R k集群,这里我们使用的是虚拟机部署方式非容器化_ B 0 s s Q 4 2q , X B i理由es是公共的组件可以对接所有的产品业务; ; + y线,单独拿出来部署可以给别的产品线输出集中化日志存储方案。平时维护l s X 8 m量还是比较少的,当然我们的es集群每15分钟大概800万条记录规模并不是特别大, 不过以后出现瓶颈横向扩容也不是问题。基于es集群作为日志集中存储,又有集中^ K 8 p 2 ! q /收集方式。我会按照我自己的理解说说各个的特点。
1、采用边车模式使用fileK / 4 O 0beat直接对接es集群。这个方式优点是可以随着业务部署一起,pod运行起来以后就9 J l o D 7 y 6能自G 0 a y动把日志输出到es中。但是会有单独的开销,因为每个pod实例都需要额外的硬件资源来运行filebeat。网络可靠性要求比较高,需要实时把日志传过去。
2、业务程序n p D R 1的log4j对接logstash或者kafka。这个方式优点是全部com.cn/tag/%e9%9b%86%e6%88%90" target="_blank">集成了,缺点是依赖第三方。如果处理不% - y好logstash或者kafka宕机可能会影响到业务程序本身的运行,导致业务中; u p J _ u断。
3、业务日志写N ( j b本地文件,通过本地磁盘挂载到pod中。优点是对J r W 1 t现有程序没改动,缺点是需要单独去做日志集中处n y V 0理。
我们目前的做法就是第三种,通过pod挂载本地磁盘目录。单独使用日志收集把日志集中到一台日志服务器上,在通过logz G L G q b Sstash存入es集群。具体实现方式我主要写目前的我们生产的集中方式。

实施细节

请注意这不是可以照抄的部署文档- a 6 g M,只是# W i % a P一个方案的呈现。请在使用的过程中自行根据自己的环境调整适配。

部署rsyslog-server
编辑f ) O K O @ O w D/etc/rsyslog.conf
$ModLoad imrelp
$InputRELPServerRun 2514

$template myformat,"%msg%\n"
$teg M ( | :mplate istio, "/data/logs/rancher/%syslogtag%/%X m V m$year%-%$month%-%$day%-%e ^ ] B , ^$hour%.log"
$template ztk8s, "/data/logs/ztk8s/%syslogtag%/%$yeaa u `r%-%$mont} l Lh%-%$day%-%$hour%.log"
local3. ?g Q u Aztk8s
local4.
?istio;myformat
这里使用的是relp模块,所有服务端和客户端都需要安装rsyslo| { & 0 0 T /g-ry , $ Selp的包才能正常使用。配置文件定义了两个日志类型,localp } 0 b Q f C c ^3作为业务日志收集,local4作为k8s的相关系统组件日志收集。这里目录为rancher是因H $ C为我们用的ranc. c Z V ) [ ]her2管理页面。
1、traefik日志集中:
日志的定义需要修改k o ] 5configmap

apiVersion: v1
dataD + m 1 ;  6 Y:
traefik.toml: |-
# traefik.toml
logLevel6 b n f q n C [ ] = "info"
defaultEntryPoints = ["http","https"]
[entryPoints]
[e| | 2 %ntryPoints.http]
address = ":80"
compress = true
[entryPoints.https]
addrE h A Less = ":443"
compress = true
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "/ssl/tls.crt"
KeyFile = "/ssl/tw = 4 ~ v o ! rls.key"
[entryPoints.traefik] #dashboard端口
aS 9 r f ?ddress = ":8080"
[entryPoints.prometheus] #metrics端口
address = ":9100"
[ping]
entryPoint = "http" #健康检查
[kn K k Y { ` aubernetes]
[kubern: G T  aetes.ingressEndpoint]
publishedService = "kube-system/traefik"
[traefikLog]
format = "json"
[accessLog]  #访问日志
filePath = "/data/node.log"
format = "json"
[api] #dashboard功能
entryPoint = "traefik"
dashboarB J D u Y 1 Dd = true
[metrics]
[metricsp } 6 w | % [.prometh% / d h Ceus]
entryPoint = "prometheus"
kind: ConfigMap
metU $ Xadata:
name: traefik
namespace: kube-system

然后把主机目录挂载到容器的/data目录中。比如/home/logs/traefik
修改traefik部署的deu & = ( % z 9 6ployment yaml文件

        volumeMo| ? g ^ ~ # m ? Qunts:
- mountPath: /config
nam] c f d 2 * o *e: config
- mountPath: /ssl
name: ssl
- mountPath: /data #日志容器目录
name: acclog
volumes:
- configMap:
defaultMode: 420
name: traefik
name: config
- name: ssl
secret:
de5 | o FfaultMode: 420
secretName: traefik-default-cert
- hos5 a 3 M | ^ #tPath:
paY r @ k C vth: /home/logs/traefik #日志主机目录
type: ""
name: acclog

/. 3 D P Ghome/logs/traefik 在这个目录中就能看到类似于nS U o { | #ginx的access.log日志了。
在主机上做一个标签调度比如只允许运行到标签为ingress=traefik的主机上。这样分2-3个主机,部署traefik作为负载均衡和高可用。为防止日志过大占用满磁盘空间,可以配置一个日志f L D 3 y ] / ) (轮替。
到这些主机上使用rsyslog进行日志收集到固定的日志服务器中。为防止日志过大,使用logrotate进行w m S 3 S [日志轮替

cat /etc/logrotate.d/tL K [raefik
/home/logs/traefik/node.lK } {og
{
su rooO z O _ v N o 7 bt rooH J { a e ( gt
dateext
dateformat -%Y-%m-%d-%H  #文件格式
extensionA # A y .log  #扩展名
notifempty
hourly    #M t . u w C K每小时轮替一次
rotate 24  #保留24个副本
missingok
nocompress #不压缩旧日# m *志,可以指定压缩。
sharedscripts
postrotate
/bin/kill -s USR; L i ` B 1 `ps aux | grep traefik | grep -v grep | awk '{print $2}'` 2> /dev/null || true
#完成日志轮替以后执行的脚本,这里配合traefik官网的介绍是传入 USR1的信号给traefik程序就行~ 4 b Y u了
endscript
}

这样就是按小时轮替文件了。详细日志轮替配置推荐这篇文章 https://www.cnblogs.com/yanwei-wang/pX S z Y F ) ^/5241436.htO % @ h / -ml
使用rsyslog把所有主机的traefik日( l { q志收集到一台主机上。
修改/etc/rsyslog.conf添加
$ModLoad imfile
$ModLoad omrelp
创建文件z ^ i +/etc/rsyslog.d/traefik.conf

$WorkDirectory /var/spool/rsyslog
$InputFileName /home/logs/traefik/node.log
$InputFileTag traefik
$Iz % - # d 3nputFileStateFile trO f 2 n , B # [aefik
$InputFileSeverity debug
$InputFileFacility local4
$InputFilePersistStateInterval 20000
$RepeatedMsgReduction off
$Inph [ $ ? yutRunFileMonitor
$InputFilePollInterval 3
$template BiglogFormatTomcat,"%msg%\n"
local4.* :om( n  O y f Rrelp:logserver.tz.com:2514

然后重新启动systemctl restart rsyslog这样在日志服务器中相关目; x W 1 U D录就能看到内容了。
2、k8s的事件日志收集
在生产运行中k8s里面所有相关动作都会用事件的方式来呈现,比如一个pod是否改变状态、某个程序是否可用、容器是否重启、健康[ w 2 E S q 9 3状态改变、拉取镜像、启动和创建容器等。这些事件对我们进行事故排查非常有用,这些事件保存在etcd的集群中。但是k8s为了保证eto C Q = ?cd的性能默认只保存1小时以内并且是目前存在于集群中相关资源的事件信息,其它信息会被定时清理。这就需要把事件导出到其它的日志系统中保存。这里我们选择先导出到日志文件,再通过filebeat导x f b } , [ ^入到elk中方便查阅。注意filebeat的镜像版本一定要和elk的版本一致否则无法上传成功。
克隆源代码
git clone https://github.com.cnpmjs.org/heptiolabs/eventrouter.git
修改Makefile中相关参数,以适应中国网络
修改RE$ 1 u $ e kGISTs H $ J | y a O HRY变量为自己私服的地址
修改BUILD_IMAGE为golang:1.14.2以支持通过en! E ; 1 ; M x |v设置go-proxy
修改编译步骤中$(DOCKER_BUILD)后边的参数为 "go env -w GO111Mi L | d F - q M _ODULE=on && go env -w GOPROXYa U ?=https://goproxy.cn,direct && CGO_ENABLED=0 go build"
修改DockerfR e B d 8ile
RUN sed -i "s/dl-cS A ? B 5 K 7dn.alpinelinux.org/mir- E Vrors.aliyun.com/g" /etc/apk/repositorieH m C 7 X qs && apk up$ Z z = 9date --no-cache && apk a) = O 4 N bdZ * o ^ id ca-certificates
执行make all命令生产镜像
然后docker push eve 0 S ( , = #entrouter:xxx上传到私有仓r T Q X } f M库中。
部署yaml文件。

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: eventrouter
namespace: kube-system
---
apiVeP 4 U } % )rsion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: eventrouter
rules:
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "watch"6 [ l L {, "list"]
---
apiVersion: rbac.autJ m / F U v phorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: ek z ~ V C |ventrouter
roleRef:
apiGroup: rbac.authoF S Y m krization.k8s.io
kind: ClusterRole
name: eventrouter
subjects:
- kind: ServiceAccount
name: eventrouter
namespace: kube-system
---
apiVersion: v1
kinu T M C z & o A nd: ConfigMap
metadata:
name: eventrouter-cm
namespace: kube-system
data:
config.json: |-
{
"sink": "glog"
}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: filebeat-config
namm n  O k l @espace:Y . X q kube-systemb l } 4 w 3
data:
filebeat.yml: |-
filebeat.inputs:
- type: log
enabled: true
json.keys_under_root: true
json.overwrite_keys: true
paths:
- "/data/log/eventrouter/*o D # W Q "
output.elasticsearch:
hosts: ["es] N 0 5 r A d }cluster.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: "filebe* J Aat-k8s-pro-event-*"
---
apiVersion: apps/v1
kind: Deployment
metadata:
nam& i P } 5 /e: eventrouter
namespace: kube-system
labels:
ap$ m M B P g cp: eventrouter
spec:
replicas: 1
selector:
matchLabels:
app: eventrouter
ta : p  / # D S Template:
metadata:
labelL e X 2 ) ! N us:
app: eventrouter
tier: control-plane-addons
spec:
containers:
- name: kube-y x eeventrouter
image: docker.tz.comV H c W ! | i/eventrouter:v0.2
command:
- "/bin/sh"L t v , ` l @ -
args:
- "-c"
- "/eventrouter -v 3P C  / b 6 -log_dir /data/log/evenS } { m ntrouterF X Y q d e _"
volumeMounts:
- name: eventrouter-cm
mounK K _ . F c = r 5tPath: /etc/eventrouter
- name: log-path
mountPath: /data/loN 0 t G = B Og/eventrouter
- n_ o ` - M 5 #ame= i + y 8 | N [: filebeat
image: docker.elastic.co/beats/filebeat:7.7.T | ( s 1 U |1
command:
- "/bin/sh"
args:
- "-c"
- "fi2 7 B ^ b mlebeat -c /etc/filebeat/filebeat.yml"
volumeMounts:
- name: filebeat-config
mountPath: /etc/filebeat/
- name: log-pathS { i V x !
mountPath: /data/log/eventrouter
serviceAccount: eveP ] Entrouter
volumes:
- name: eventrouter-cm
configMap:
name: even$ 0 7trouter-cm
- name: filebeat-config
configMap:
name: filebeaf j V @ U a q Wt-config
- name: log-path
emptyDir: {}

b L j y . } E c G样再到kibana中就能配置相关索引查看日志了。
3、业务日志
我们的业务是spring-boot开发的,采用的log4j日志套件输出。日志输出| O $ s T / % ( |的统一路径是logs/node.log文件,U ) r { ( a T J r相对程序运行的1 Q *路径。容器的工作目录是/home/j V ! d . ? x dplatfrom/。所以日志在容器中的完整路径是/home/platfrom/logs/node.log。映射的主机e @ & v O目录是/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
nami % ^ O e: logY O ` o ~s

这里有个很麻烦的事情,我们业务在容器中运行的是非root账户。在自动创建了主机目录以后所有权还是root,导致容器中的程序无法写日志文件。我们的解决方案是,使用inotifywait来自动修改权限和生成rsyslog的日志配置。
所以需要安装inotify-tools、screen的包
先创建一u M s y个配置文件。多留几个空行,后面的脚本需要使用。
/etc/m H p m u R rsyslog.d/plat( , x R h O Eform.conf


local3.* :omrelp:logserver.tz.com:2514

编辑监控脚本logsconf.sh。这个脚本可以放在一个nfs的共享目录中,每个k8s的宿主机都挂载nfs目录。这样就避免每个主机都去创建一次脚本了。我们这里的nfs主要是作为工具共享使用4 6 w的,如果用nfs作为共享存储可^ b 4 j 7能会有很严重p [ ] $ n 2的性能问题,这会直接导致业务中断。(血的教训,用实际故障N O k买过单的)

#!/biz K +n/bash
inotifywait -m -e create /hom$ G n  r 9 $ qe/logs/ | while read file
do
#获取新建的文件目录
#  /homi { 7 oe/logs/* =  3 CREATE,ISDIR test
BASEDIR=$(echo $file | awk '{print $1}')& S W T
EVENT=$(echo $file | awk '{print $2}')
DIR=$(echo $file | awk '{print $3F P )}')
if [ `echo $EVENT |grep ISDIR` ] ;thenn X 3 &
echo "$EVENT  $BASEDIR$DIR"
chown 1000:1000 -R $BASEDIR$DIR
chmod 764 -R $BASEDIR$DIR
#生k F v x S * ] r @成日志文件
sed -i '2i$template BiglogFo+ }  j ;rmatTomcat,"%msg%\\n"' /etc/rsyslog.d/platform.conf
sed -i '2i$InputFilePollIntervals M ^ q 3' /etc/rsyslog.d/platforM T X qmB u 1 6 k } ` v.conf
sed -i '2i$InputRunFileMonitor' /etc/rsyslog.d/platform.conf
seG w L X - *d -i '_ Z N g P2i$RepeatedMsgReduction off' /etcL = [ , A L 6 0/rsyslog.d/platform.conf
sed -i '2i$InputFilePersistState/ Z o 8 TInterval 20000' /etc/rsyslog.d/platform.con$ r l U o A `f
sed -i '2i$InputFileFacility local3' /etc/rsyslog.d/platform.conf
sed -i '2i$InputFileSeverityS @ o t 1 p o ! m debug' /etc/rsyslog.d/( o fplatform.conf
sed -i "2i\$InputFileStateFile $DIR" /etc/rsys= u hlog.d/platform.conf
sed -i o z B"2i\$InputFileTagl V 8 $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
systemcf - Ztl restary | & D dt rsyslog
fi
done

脚本做个一个倒叙% _ $ 7 R }插入,为啥要倒叙插入的原因忘记了。大家可以J | H 7 B s试试正常的echo^ e } +去插入配置文件。
执行命令screen -dmS watchlog /opt/share/shell/k8s/logsconf.s; O v q Kh,脚本路径自己修改。把这个命令放到/etc/rc.local文件中每次启动时自动运行。这样到日志服务器中就能看到日志搜集的文件o 6 L I了。为了防止日志文件过大,可以采用日志轮替的方式或者定期清理。
4、istio的envoy日$ # } e I i N i &志。
这个日志的收集只适用于启用了istio的场景,没有使用_ i * a 0 8 ] 3istio可以不用管。
我们所有的数据信息访问都由边车Envoy来转发,整U B I } A k p b个应用集群部署完以后你会发现所有程序模块之间的链接就是Envoy的链接。而我们程序异常的时候可能需要用到这些链接记录来排查问题,而Envoy默认是没有开启日志输出的。日志的开启可以通过修改istio的相关配置来实现。在部署istio的名称空间istio-system中的istio配置文件。
kubectl get configmaps -n ist/ W d o c . W 5 #io-system istio -o yaml 可以使用这个命令查看详细内容。
在配置名称mesh的内容下有一个accessLogFile的选项 accessLogL H #File: "/dev/stdout"代表输出到容器的终端上。
修改完以后需要重启istio-sidecar-+ J W Q u l D 0injector、istio-galley、istio-pilot三个服务让配置生效。重启之后配置会自动下发,去现有的应用中选择istio-proxy的容器就能看到日志了。
另外两个选项:
accessLogEncoding代表日志输出格式默认是json。可选项JSON和TEXT
accessLogFormat代表日志内容格式。
可以通过is] w O c E k 4 itioctl命令来修改,也可以手动编辑coy u K q * cnfigmap文件来修改。
istioctl manifest apply --set values.global.proxy.acces3 { ^sLogFix = ! #le="/dev/stdout"
这时候日l # ; t 2志是保存在宿主3 & @ D - m G g -机/var/log/pods/platT ^ Cfor& Z X s s n wm_platform-message-v1-@ Q : 3 ? 6 Q % f5b4fbbb99d-bbgj9_7c5d07a4-4dd6-4e81-a718-dceb5a5e980f/istio-proxy/0.logw A U这样类似的文件中。
我们可以进行一轮日志收集到专门的服务器上保存 G j X以备查看。
/ 2 s b ! & m里可以直接修( { 9 X J r ,改上面traefik.conf的配置做个修改就行。

$WorkDirectory /var/spool/rsyslog
$InputFileName /var/log/pods/*/istio-proxy/0.log
$InputFileTag isti5 c = d !o
$InputFi] b : Y x G _leStateFile istio
$InputFileSeverity debug
$InputFileFacility localm ] z3
$InputFilePersistStateInterval 20000
$RepeatedMsgReductionp 3 h G a 5 ) off
$Inputp 9 C ] M / y $ sRunFileMonitor
$InputFilePollInter} n ` , Hval 3
$template BiglogFormatTomcat,"%$ f F t 1 ? Nmsg%\n"
loV  ) p 6 A f E scal3.* :omrelp:logserver.tz.com:2514

这里全部日志就都收集到了rsyslogG = 7 j : m / -server的服务器上了。接下来可以使用logstash对日志进行处理,格式化再写入到es中。r n Q w & m
logsF 1 * # } rtash配置举例:

input{
file {
path => "/data/logs/rancher/istio-envoy/*.log"
codec => "json"
type => "istio-envoy"
}
file{
pT z X h z E J ) wath => "H 6 S _ 3/data/logs/ztk8s/platf, w l Urom-user/*.log"
codec => "json"
type =>/ = ` 0 ] k W Z e K ^"platfrom-user"
}
}
filter {
if [type] == "istio-envoy" {
json {
source => "lK # j Y D B R ( *og"
}
}
elss + -e if [type] == "platfrom-user"{
json {
source=>"inparam"
}
json {
source=>"rek K D  B g usult"
}
}
}
oW | )  7 q ! 4utput {
if "_grokparsefW w f - ! 8 Jailure" not in [tags] {
elasticsearch {
hosts => ["escluster.tz.com:9200"]
manage_tes 2 : 4 k q B F Vmplate => true
indexI P E f => "%{type}-%{+YYYY.MM.dd}"
}
}
}

我们的业务_ l c日志输出就是json格式,相对来说格式化起来更简7 ; ) 7 * b单一些。其它的日志格式- m 3 i { E需要自己根据实际情况进行修改。到此日志集中分享就到这里,适合自己的才是最好的。希望大家看了以后有所启发,再次申明这不是产品部署文档不可以照{ ] t L B抄,对此产生的一切问题由使用者自己负责。