K8s 로그 수집을 위한 Logback Socket 모범 사례¶
적용 사례 소개¶
Logback 로그 출력은 일반적으로 사용되는 file 및 stdout 외에도 socket(TCP) 출력이 가능합니다. Socket 기반 로그 전송의 가장 큰 장점은 스토리지 비용 절감입니다. 애플리케이션에서 생성된 로그는 로컬에서 일부 메모리 버퍼링을 거친 후 수집 엔드포인트로 전송됩니다.
마찬가지로 DataKit도 Socket 로그 수집을 지원합니다. 이 문서는 K8s 환경에서 Spring Boot 애플리케이션의 로그를 Logback Socket 방식으로 Guance 플랫폼에 푸시하여 관측하는 방법을 설명합니다.
사전 조건¶
- Guance 계정을 먼저 생성해야 합니다.
- Spring Boot 애플리케이션
- docker-harbor
- K8s 클러스터
설치 및 배포¶
K8s 환경에서 Datakit 설치 및 구성¶
Kubernetes 환경에서 DataKit 설치는 문서 <Kubernetes 애플리케이션의 RUM-APM-LOG 연동 분석>을 참조하세요.
1 로그 수집 파일 구성¶
로그를 수신하려면 log socket을 활성화하고 9541 포트를 연 다음 Pipeline을 구성해야 합니다.
logging-socket-demo.conf
[[inputs.logging]]
## required
# logfiles = [
# "/var/log/syslog",
# "/var/log/message",
# ]
# only two protocols are supported:TCP and UDP
sockets = [
"tcp://0.0.0.0:9541",
# "udp://0.0.0.0:9531",
]
## glob filteer
ignore = [""]
## your logging source, if it's empty, use 'default'
source = "socket_log"
## add service tag, if it's empty, use $source.
service = "socket_service"
## grok pipeline script name
pipeline = "logback_socket_pipeline.p"
## optional status:
## "emerg","alert","critical","error","warning","info","debug","OK"
ignore_status = []
## optional encodings:
## "utf-8", "utf-16le", "utf-16le", "gbk", "gb18030" or ""
character_encoding = ""
## The pattern should be a regexp. Note the use of '''this regexp'''
## regexp link: [https://golang.org/pkg/regexp/syntax/#hdr-Syntax](https://golang.org/pkg/regexp/syntax/#hdr-Syntax)
# multiline_match = '''^\S'''
## removes ANSI escape codes from text strings
remove_ansi_escape_codes = false
[inputs.logging.tags]
service = "socket-demo"
2 Pipeline으로 로그 파싱¶
logback_socket_pipeline.p는 socket 로그 형식을 파싱하여 Guance 플랫폼에서 쉽게 확인하고 사용할 수 있도록 합니다.
전문은 다음과 같습니다.
logback_socket_pipeline.p
#------------------------------------ 警告 -------------------------------------**
# 不要修改本文件,如果要更新,请拷贝至其它文件,最好以某种前缀区分,避免重启后被覆盖**
#----------------------------------------------------------------------------------- **
# access log**
json(_,msg,"message")**
json(_,class,"class")**
json(_,appName,"service")**
json(_,thread,"thread")**
json(_,severity,"status")**
json(_,trace,"trace_id")**
json(_,span,"span_id")**
json(_,`@timestamp`,"time")**
default_time(time)**
3 token 교체¶
datakit.yaml의 token을 사용자 고유의 token으로 변경해야 합니다. 전문은 다음과 같습니다.
datakit.yaml
apiVersion: v1
kind: Namespace
metadata:
name: datakit
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: datakit
rules:
- apiGroups:
- rbac.authorization.k8s.io
resources:
- clusterroles
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
- nodes/proxy
- namespaces
- pods
- pods/log
- events
- services
- endpoints
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
- daemonsets
- statefulsets
- replicasets
verbs:
- get
- list
- watch
- apiGroups:
- batch
resources:
- jobs
- cronjobs
verbs:
- get
- list
- watch
- apiGroups:
- metrics.k8s.io
resources:
- pods
- nodes
verbs:
- get
- list
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: datakit
namespace: datakit
---
apiVersion: v1
kind: Service
metadata:
name: datakit-service
namespace: datakit
spec:
selector:
app: daemonset-datakit
ports:
- protocol: TCP
port: 9529
targetPort: 9529
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: datakit
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: datakit
subjects:
- kind: ServiceAccount
name: datakit
namespace: datakit
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app: daemonset-datakit
name: datakit
namespace: datakit
spec:
revisionHistoryLimit: 10
selector:
matchLabels:
app: daemonset-datakit
template:
metadata:
labels:
app: daemonset-datakit
annotations:
datakit/logs: |
[
{
"disable": true
}
]
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- env:
- name: HOST_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
- name: ENV_DATAWAY
value: https://openway.guance.com?token=<you token>
- name: ENV_GLOBAL_HOST_TAGS
value: host=__datakit_hostname,host_ip=__datakit_ip,cluster_name=k8s-dev
- name: ENV_DEFAULT_ENABLED_INPUTS
value: cpu,disk,diskio,mem,swap,system,hostobject,net,host_processes,container,statsd,ddtrace
- name: ENV_ENABLE_ELECTION
value: enable
- name: ENV_HTTP_LISTEN
value: 0.0.0.0:9529
- name: ENV_LOG_LEVEL
value: info
image: pubrepo.guance.com/datakit/datakit:1.2.6
imagePullPolicy: IfNotPresent
name: datakit
ports:
- containerPort: 9529
hostPort: 9529
name: port
protocol: TCP
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-socket
readOnly: true
- mountPath: /usr/local/datakit/conf.d/container/container.conf
name: datakit-conf
subPath: container.conf
#- mountPath: /usr/local/datakit/conf.d/log/logging.conf
# name: datakit-conf
# subPath: logging.conf
- mountPath: /usr/local/datakit/pipeline/demo_system.p
name: datakit-conf
subPath: log_demo_system.p
- mountPath: /usr/local/datakit/conf.d/log/logging-socket-demo.conf
name: datakit-conf
subPath: logging-socket-demo.conf
- mountPath: /usr/local/datakit/pipeline/logback_socket_pipeline.p
name: datakit-conf
subPath: logback_socket_pipeline.p
- mountPath: /host/proc
name: proc
readOnly: true
- mountPath: /host/dev
name: dev
readOnly: true
- mountPath: /host/sys
name: sys
readOnly: true
- mountPath: /rootfs
name: rootfs
- mountPath: /sys/kernel/debug
name: debugfs
workingDir: /usr/local/datakit
hostIPC: true
hostPID: true
restartPolicy: Always
serviceAccount: datakit
serviceAccountName: datakit
volumes:
- configMap:
name: datakit-conf
name: datakit-conf
- hostPath:
path: /var/run/docker.sock
name: docker-socket
- hostPath:
path: /proc
type: ""
name: proc
- hostPath:
path: /dev
type: ""
name: dev
- hostPath:
path: /sys
type: ""
name: sys
- hostPath:
path: /
type: ""
name: rootfs
- hostPath:
path: /sys/kernel/debug
type: ""
name: debugfs
updateStrategy:
rollingUpdate:
maxUnavailable: 1
type: RollingUpdate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: datakit-conf
namespace: datakit
data:
#### container
container.conf: |-
[inputs.container]
docker_endpoint = "unix:///var/run/docker.sock"
containerd_address = "/var/run/containerd/containerd.sock"
enable_container_metric = true
enable_k8s_metric = true
enable_pod_metric = true
## Containers logs to include and exclude, default collect all containers. Globs accepted.
container_include_log = []
container_exclude_log = ["image:pubrepo.guance.com/datakit/logfwd*", "image:pubrepo.guance.com/datakit/datakit*"]
exclude_pause_container = true
## Removes ANSI escape codes from text strings
logging_remove_ansi_escape_codes = false
kubernetes_url = "https://kubernetes.default:443"
## Authorization level:
## bearer_token -> bearer_token_string -> TLS
## Use bearer token for authorization. ('bearer_token' takes priority)
## linux at: /run/secrets/kubernetes.io/serviceaccount/token
## windows at: C:\var\run\secrets\kubernetes.io\serviceaccount\token
bearer_token = "/run/secrets/kubernetes.io/serviceaccount/token"
# bearer_token_string = "<your-token-string>"
[inputs.container.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
#### logging
logging.conf: |-
[[inputs.logging]]
## required
logfiles = [
"/rootfs/var/log/k8s/ruoyi-system/info.log",
"/rootfs/var/log/k8s/ruoyi-system/error.log",
]
## glob filteer
ignore = [""]
## your logging source, if it's empty, use 'default'
source = "k8s-demo-system1"
## add service tag, if it's empty, use $source.
service = "k8s-demo-system1"
## grok pipeline script path
pipeline = "demo_system.p"
## optional status:
## "emerg","alert","critical","error","warning","info","debug","OK"
ignore_status = []
## optional encodings:
## "utf-8", "utf-16le", "utf-16le", "gbk", "gb18030" or ""
character_encoding = ""
## The pattern should be a regexp. Note the use of '''this regexp'''
## regexp link: https://golang.org/pkg/regexp/syntax/#hdr-Syntax
multiline_match = '''^\d{4}-\d{2}-\d{2}'''
[inputs.logging.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
#### system-log
log_demo_system.p: |-
#日志样式
#2021-06-25 14:27:51.952 [http-nio-9201-exec-7] INFO c.r.s.c.SysUserController - [list,70] ruoyi-08-system 5430221015886118174 6503455222153372731 - 查询用户
grok(_, "%{TIMESTAMP_ISO8601:time} %{NOTSPACE:thread_name} %{LOGLEVEL:status}%{SPACE}%{NOTSPACE:class_name} - \\[%{NOTSPACE:method_name},%{NUMBER:line}\\] - %{DATA:service_name} %{DATA:trace_id} %{DATA:span_id} - %{GREEDYDATA:msg}")
default_time(time,"Asia/Shanghai")
logback_socket_pipeline.p: |-
#------------------------------------ 警告 -------------------------------------
# 不要修改本文件,如果要更新,请拷贝至其它文件,最好以某种前缀区分,避免重启后被覆盖
#-----------------------------------------------------------------------------------
# access log
json(_,msg,"message")
json(_,class,"class")
json(_,appName,"service")
json(_,thread,"thread")
json(_,severity,"status")
json(_,trace,"trace_id")
json(_,span,"span_id")
json(_,`@timestamp`,"time")
default_time(time)
logging-socket-demo.conf: |-
[[inputs.logging]]
## required
# logfiles = [
# "/var/log/syslog",
# "/var/log/message",
# ]
# only two protocols are supported:TCP and UDP
sockets = [
"tcp://0.0.0.0:9541",
# "udp://0.0.0.0:9531",
]
## glob filteer
ignore = [""]
## your logging source, if it's empty, use 'default'
source = "socket_log"
## add service tag, if it's empty, use $source.
service = "socket_service"
## grok pipeline script name
pipeline = "logback_socket_pipeline.p"
## optional status:
## "emerg","alert","critical","error","warning","info","debug","OK"
ignore_status = []
## optional encodings:
## "utf-8", "utf-16le", "utf-16le", "gbk", "gb18030" or ""
character_encoding = ""
## The pattern should be a regexp. Note the use of '''this regexp'''
## regexp link: https://golang.org/pkg/regexp/syntax/#hdr-Syntax
# multiline_match = '''^\S'''
## removes ANSI escape codes from text strings
remove_ansi_escape_codes = false
[inputs.logging.tags]
service = "sign"
4 배포¶
배포 상태 확인
[root@master ~]# kubectl get pods -n datakit
NAME READY STATUS RESTARTS AGE
datakit-pf4sp 1/1 Running 0 22h
datakit-tj9zq 1/1 Running 0 22h
Spring Boot 애플리케이션¶
Spring Boot 애플리케이션 기반의 작업 단계는 다음과 같습니다.
1 pom 의존성 추가¶
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>4.9</version>
</dependency>
2 logback socket 구성¶
logback socket 구성
<!-- 对日志进行了json序列化处理,dk支持文本格式的日志,可以通过socket直接推送过去-->
<appender name="socket" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- datakit host: logsocket_port -->
<destination>${dkSocketHost}:${dkSocketPort}</destination>
<!-- 日志输出编码 -->
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC+8</timeZone>
</timestamp>
<pattern>
<pattern>
{
"severity": "%level",
"appName": "${logName:-}",
"trace": "%X{dd.trace_id:-}",
"span": "%X{dd.span_id:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"msg": "%message\n%exception"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<root level="info">
<!-- socket appender -->
<appender-ref ref="socket" />
</root>
3 logback-spring.xml¶
전문은 다음과 같습니다. (참고: 실제 애플리케이션 상황에 따라 조정할 수 있습니다.)
logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<!-- 部分参数需要来源于properties文件 -->
<springProperty scope="context" name="logName" source="spring.application.name" defaultValue="localhost.log"/>
<springProperty scope="context" name="dkSocketHost" source="datakit.socket.host" />
<springProperty scope="context" name="dkSocketPort" source="datakit.socket.port" />
<!-- 配置后可以动态修改日志级别-->
<jmxConfigurator />
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] %X{dd.service} %X{dd.trace_id} %X{dd.span_id} - %msg%n" />
<!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,,,, -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${logName}/${logName}.log</file> <!-- 使用方法 -->
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/${logName}/${logName}-%d{yyyy-MM-dd}.log.%i</fileNamePattern>
<maxFileSize>64MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 对日志进行了json序列化处理,dk支持文本格式的日志,可以通过socket直接推送过去-->
<appender name="socket" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- datakit host: logsocket_port -->
<destination>${dkSocketHost}:${dkSocketPort}</destination>
<!-- 日志输出编码 -->
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>UTC+8</timeZone>
</timestamp>
<pattern>
<pattern>
{
"severity": "%level",
"appName": "${logName:-}",
"trace": "%X{dd.trace_id:-}",
"span": "%X{dd.span_id:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger{40}",
"msg": "%message\n%exception"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<!-- 只打印error级别的内容 -->
<logger name="com.netflix" level="ERROR" />
<logger name="net.sf.json" level="ERROR" />
<logger name="org.springframework" level="ERROR" />
<logger name="springfox" level="ERROR" />
<!-- sql 打印 配置-->
<logger name="com.github.pagehelper.mapper" level="DEBUG" />
<logger name="org.apache.ibatis" level="DEBUG" />
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="socket" />
</root>
</configuration>
4 application.properties¶
datakit.socket.host=192.168.11.12
datakit.socket.port=9542
server.port=8080
spring.application.name=socket-demo
5 Dockfile¶
FROM openjdk:8u292
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ENV jar springboot-logback-socket-appender-demo.jar
ENV workdir /data/app/
RUN mkdir -p ${workdir}
COPY ${jar} ${workdir}
WORKDIR ${workdir}
ENTRYPOINT ["sh", "-ec", "exec java ${JAVA_OPTS} -jar ${jar} ${PARAMS} 2>&1 > /dev/null"]
6 Docker 이미지 게시¶
jar를 현재 디렉토리에 복사하고 이미지를 빌드합니다.
docker build -t registry.cn-shenzhen.aliyuncs.com/lr_715377484/springboot-logback-socket-appender-demo:v1 .
docker-hub에 푸시합니다. 여기서는 예시로 Alibaba Cloud Hub 레포지토리에 푸시했습니다.
docker push registry.cn-shenzhen.aliyuncs.com/lr_715377484/springboot-logback-socket-appender-demo:v1
7 배포¶
-
springboot-logback-socket-appender-demo-deployment.yaml파일을 작성하고 다음 매개변수를 수정해야 합니다. -
DATAKIT_SOCKET_PORT: datakit 로그 socket 포트입니다.
- dd-java-agent는 datadog의 Java-agent로 trace에 사용됩니다. 필요하지 않으면 관련 구성을 제거할 수 있습니다.
전문 내용은 다음과 같습니다.
springboot-logback-socket-appender-demo-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: logback-socket-service
labels:
app: logback-socket-service
spec:
selector:
app: logback-socket-service
ports:
- protocol: TCP
port: 8080
nodePort: 32100
targetPort: 8080
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: logback-socket-service
labels:
app: logback-socket-service
spec:
replicas: 1
selector:
matchLabels:
app: logback-socket-service
template:
metadata:
labels:
app: logback-socket-service
spec:
nodeName: master
containers:
- env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DATAKIT_SOCKET_PORT
value: "9541"
- name: JAVA_OPTS
value: |-
-javaagent:/usr/dd-java-agent/agent/dd-java-agent.jar -Ddd.service.name=demo-k8s-logback-socket -Ddd.tags=container_host:$(PODE_NAME) -Ddd.service.mapping=mysql:mysql-k8s,redis:redisk8s -Ddd.env=dev -Ddd.agent.port=9529
- name: PARAMS
value: "--datakit.socket.host=$(DD_AGENT_HOST) --datakit.socket.port=$(DATAKIT_SOCKET_PORT)"
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
name: logback-socket-service
image: registry.cn-shenzhen.aliyuncs.com/lr_715377484/springboot-logback-socket-appender-demo:v1
#command: ["sh","-c"]
ports:
- containerPort: 8080
protocol: TCP
volumeMounts:
- name: ddagent
mountPath: /usr/dd-java-agent/agent
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
initContainers:
- command:
- sh
- -c
- set -ex;mkdir -p /ddtrace/agent;cp -r /datadog-init/* /ddtrace/agent;
image: pubrepo.guance.com/datakit-operator/dd-lib-java-init
imagePullPolicy: Always
name: ddtrace-agent-sidecar
volumeMounts:
- mountPath: /ddtrace/agent
name: ddagent
restartPolicy: Always
volumes:
- name: ddagent
emptyDir: {}
- 애플리케이션 배포
- 애플리케이션 상태 확인
[root@master logback-socket]# kubectl get pods -l app=logback-socket-service
NAME READY STATUS RESTARTS AGE
logback-socket-service-74bd778fcf-cqcn9 1/1 Running 0 5h41m
Guance에서 로그 확인¶
로그 탐색기
로그 상세

