Kubernetes 애플리케이션의 RUM-APM-LOG 연동 분석¶
사용 사례 소개¶
기업의 가장 중요한 수익원은 비즈니스이며, 현재 대부분의 기업은 비즈니스를 해당 IT 시스템으로 운영합니다. 따라서 기업의 비즈니스 안정성을 보장하는 것은 결국 기업 내부의 IT 시스템을 어떻게 보장할 것인지로 귀결됩니다. 비즈니스 시스템에 이상이나 장애가 발생하면 일반적으로 비즈니스, 애플리케이션 개발, 운영 등 여러 부서의 동료가 함께 협력하여 문제를 조사해야 하며, 플랫폼 간, 부서 간, 전문 영역 간 등의 다양한 문제가 존재하여 조사에 시간과 노력이 많이 소모됩니다.
이 문제를 해결하기 위해 현재 업계에서는 비교적 성숙한 방식인 RUM + APM + LOG를 통해 전체 비즈니스 시스템의 프론트엔드/백엔드, 로그를 통합 모니터링하고, 세 가지 데이터를 핵심 필드로 연결하여 연동 분석을 구현함으로써 관련 작업자의 효율성을 높이고 시스템의 안정적인 운영을 보장합니다.
- APM: 애플리케이션 성능 모니터링(APM)
- RUM: 실제 사용자 모니터링(RUM)
- LOG: 로그
이 문서는 세 가지 모니터링을 연결하는 방법과 Guance를 사용한 연동 분석 관점을 설명합니다. 데모로 사용된 애플리케이션은 RuoYi 권한 관리 시스템이며, 자세한 내용은 <0부터 1까지 Guance로 Spring Cloud 서비스의 관측 가능성 구축하기>를 참조하세요.
로그와 관련하여 이 문서에서는 DataKit의 Logfwd 수집기를 사용하여 비즈니스 Pod의 로그를 수집합니다. DataKit에서 Logfwd 수집기를 활성화하고, Pod에 Logfwd Sidecar를 추가하여 비즈니스 컨테이너의 로그를 수집하여 DataKit에 전달합니다. 비즈니스에서 Sidecar에 접근 가능하므로 로그 파일을 호스트에 저장할 필요가 없습니다. 자세한 사용법은 아래의 System 배포 모듈을 참조하세요. DataKit은 로그를 수신한 후 구성된 Pipeline을 사용하여 로그 파일을 파싱합니다.
사전 조건¶
계정 등록¶
Guance로 이동하여 계정을 등록하고, 등록된 계정/비밀번호로 로그인합니다.
DaemonSet 방식으로 DataKit 배포¶
OpenWay 주소의 token 획득¶
「관리」 - 「기본 설정」을 클릭하고 아래 그림의 token을 복사합니다.
「통합」 - 「DataKit」 - 「Kubernetes」를 클릭하여 최신 datakit.yaml 파일을 가져옵니다.
설치 실행¶
- 위 단계의
datakit.yaml파일을 사용하고, 위 그림에서 가져온 token으로 파일의your-token을 교체합니다. - container 수집기, logfwd 수집기, ddtrace 수집기를 활성화합니다. 즉, DataKit 컨테이너에
container.conf,logfwdserver.conf,ddtrace.conf파일을 마운트합니다.
참고: DataKit 버전에 따라 구성이 다를 수 있으므로 최신 버전을 기준으로 하세요. 이 yaml은 이번 배포의 전체 구성이며, 이후 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
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:
- guance.com
resources:
- datakits
verbs:
- get
- list
- 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
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- env:
- name: HOST_IP
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: ENV_K8S_NODE_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: spec.nodeName
- name: ENV_DATAWAY
value: https://openway.guance.com?token=XXXXXX
- name: ENV_GLOBAL_HOST_TAGS
value: host=__datakit_hostname,host_ip=__datakit_ip,cluster_name_k8s=k8s-prod
- name: ENV_DEFAULT_ENABLED_INPUTS
value: cpu,disk,diskio,mem,swap,system,hostobject,net,host_processes,container,statsd,ebpf,rum
- name: ENV_ENABLE_ELECTION
value: enable
- name: ENV_GLOBAL_ENV_TAGS
value: cluster_name_k8s=k8s-prod
- name: ENV_HTTP_LISTEN
value: 0.0.0.0:9529
- name: ENV_NAMESPACE
value: guance-k8s-demo
#- name: ENV_LOG_LEVEL
# value: debug
#- name: ENV_K8S_CLUSTER_NAME
# value: k8s-prod
image: pubrepo.guance.com/datakit/datakit:1.4.10
imagePullPolicy: Always
name: datakit
ports:
- containerPort: 9529
hostPort: 9529
name: port
protocol: TCP
securityContext:
privileged: true
volumeMounts:
- mountPath: /var/run
name: run
- mountPath: /var/lib
name: lib
- mountPath: /var/log
name: log
#- mountPath: /var/run/containerd/containerd.sock
# name: containerd-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/logfwdserver.conf
name: datakit-conf
subPath: logfwdserver.conf
- mountPath: /usr/local/datakit/conf.d/ddtrace/ddtrace.conf
name: datakit-conf
subPath: ddtrace.conf
- 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
tolerations:
- operator: Exists
volumes:
- configMap:
name: datakit-conf
name: datakit-conf
- hostPath:
path: /var/run
name: run
- hostPath:
path: /var/lib
name: lib
- hostPath:
path: /var/log
name: log
- 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:*"]
#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"
#### ddtrace
ddtrace.conf: |-
[[inputs.ddtrace]]
endpoints = ["/v0.3/traces", "/v0.4/traces", "/v0.5/traces"]
# ignore_resources = []
customer_tags = ["node_ip"]
[inputs.ddtrace.close_resource]
"*" = ["PUT /nacos/*","GET /nacos/*","POST /nacos/*"]
## tags is ddtrace configed key value pairs
# [inputs.ddtrace.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
#### logfwdserver
logfwdserver.conf: |-
[inputs.logfwdserver]
## logfwd 接收端监听地址和端口
address = "0.0.0.0:9531"
[inputs.logfwdserver.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
Kubernetes 클러스터마다 DaemonSet으로 배포된 DataKit의 선출을 구분하기 위해 ENV_NAMESPACE 환경 변수를 추가해야 하며, 동일한 token 아래에서 값이 중복될 수 없습니다.
동일한 token 아래에서 다른 Kubernetes 클러스터를 구분하기 위해 전역 태그를 추가해야 하며, 값은 cluster_name_k8s=k8s-prod입니다.
자세한 내용은 <다중 Kubernetes 클러스터 메트릭 수집 모범 사례>를 참조하세요.
명령어 실행
DataKit 설치가 완료되면 Linux 호스트의 일반적인 플러그인이 기본적으로 활성화됩니다. 「Guance」 - 「시나리오」 - 「인프라 Linux 호스트 모니터링 뷰」에서 확인할 수 있습니다.
| 수집기 이름 | 설명 |
|---|---|
| cpu | 호스트의 CPU 사용량 수집 |
| disk | 디스크 점유율 수집 |
| diskio | 호스트의 디스크 IO 수집 |
| mem | 호스트의 메모리 사용량 수집 |
| swap | Swap 메모리 사용량 수집 |
| system | 호스트 운영 체제 부하 수집 |
| net | 호스트 네트워크 트래픽 수집 |
| host_process | 호스트에서 상주(10분 이상 활성)하는 프로세스 목록 수집 |
| hostobject | 호스트 기본 정보 수집(운영 체제 정보, 하드웨어 정보 등) |
| kubernetes | Kubernetes 클러스터 메트릭 수집 |
| container | 호스트의 컨테이너 객체 및 컨테이너 로그 수집 |
「인프라」 모듈을 클릭하여 DataKit이 설치된 모든 호스트 목록을 확인합니다.
「호스트 이름」을 클릭하면 해당 호스트의 상세 시스템 정보와 통합 실행 상태(해당 호스트에 설치된 모든 플러그인)를 확인할 수 있습니다.
애플리케이션 예제 배포¶
예제 설명¶
Web 계층은 게이트웨이를 통해 백엔드의 Auth 및 System 서비스에 액세스합니다. Web은 Vue로 개발되었고, 백엔드는 Java로 개발되었습니다.
예제에서는 Statsd를 사용하여 JVM을 수집합니다. 예제에서 사용된 이미지 저장소는 172.16.0.215:5000입니다. 예제에서는 ddtrace를 사용하여 Java 애플리케이션의 JVM 메트릭을 수집하며, 예제에서 사용된 Nacos, Redis, MySQL의 내부 IP는 172.16.0.230입니다.
Web 배포 파일 작성¶
Web 애플리케이션의 콘텐츠를 /usr/local/k8s/dist 디렉터리에 복사합니다.
/usr/local/k8s/DockerfileWeb 파일을 새로 만듭니다.
내용은 다음과 같습니다.
FROM nginx:1.21.0
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
COPY nginx.conf /etc/nginx/nginx.conf
RUN mkdir -p /data/nginx/web/dist/
COPY ./dist/ /data/nginx/web/dist/
WORKDIR /etc/nginx
CMD ["nginx","-g","daemon off;"]
EXPOSE 80
EXPOSE 443
/usr/local/k8s/nginx.conf 파일을 새로 만듭니다. 내용은 다음과 같습니다.
/usr/local/k8s/nginx.conf
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
client_max_body_size 50m;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location / {
root /data/nginx/web/dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
location /prod-api/{
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://172.16.0.229:30001/;
}
location /nginx_status{
stub_status;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
/usr/local/k8s/web-deployment.yaml 파일을 새로 만듭니다. 파일 내용은 다음과 같습니다.
/usr/local/k8s/web-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: web-service
labels:
app: web-service
spec:
selector:
app: web-service
ports:
- protocol: TCP
port: 80
nodePort: 30000
targetPort: 80
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-service
labels:
app: web-service
spec:
replicas: 1
selector:
matchLabels:
app: web-service
template:
metadata:
labels:
app: web-service
spec:
containers:
- env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
name: web-service
image: 47.96.6.150:5000/df-demo/demo-web:v1
#command: ["sh","-c"]
ports:
- containerPort: 80
protocol: TCP
dd-java-agent 이미지¶
java -jar 방식으로 사용자의 jar를 시작할 때는 -javaagent:/usr/local/datakit/data/dd-java-agent.jar를 사용해야 합니다. 사용자의 이미지에 이 jar가 반드시 존재하는 것은 아니므로, 사용자의 비즈니스 이미지를 변경하지 않기 위해 dd-java-agent.jar를 포함하는 이미지를 만든 다음, Init 컨테이너 방식으로 비즈니스 컨테이너보다 먼저 시작하여 공유 스토리지 방식으로 dd-java-agent.jar를 제공합니다.
Guance에서 이 이미지를 이미 제공하고 있습니다.
이 예제에서는 Sidecar 방식을 사용합니다. jar를 이미지에 직접 포함하려면 dd-java-agent를 다운로드하고, Dockerfile에서 아래 스크립트를 참조하여 jar를 이미지에 포함시킨 후, 배포 yaml의 -javaagent에서 사용하는 jar를 포함시킨 jar로 변경하면 됩니다.
FROM openjdk:8u292
ENV workdir /data/app/
RUN mkdir -p ${workdir}
COPY dd-java-agent.jar ${workdir} # 여기서 dd-java-agent를 이미지에 포함시킵니다.
Gateway 배포 파일 작성¶
/usr/local/k8s/DockerfileGateway를 새로 만듭니다.
파일 내용은 다음과 같습니다.
FROM openjdk:8u292
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ENV jar demo-gateway.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"]
/usr/local/k8s/gateway-deployment.yaml 파일을 새로 만듭니다. 파일 내용은 다음과 같습니다.
/usr/local/k8s/gateway-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: gateway-service
labels:
app: gateway-service
spec:
selector:
app: gateway-service
ports:
- protocol: TCP
port: 9299
nodePort: 30001
targetPort: 9299
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway-service
labels:
app: gateway-service
spec:
replicas: 2
selector:
matchLabels:
app: gateway-service
template:
metadata:
labels:
app: gateway-service
spec:
containers:
- env:
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NACOS_IP
value: "172.16.0.230"
- name: JAVA_OPTS
value: |-
-javaagent:/usr/dd-java-agent/agent/dd-java-agent.jar -Ddd.service.name=demo-k8s-gateway -Ddd.tags=container_host:$(POD_NAME),node_ip:$(DD_AGENT_HOST) -Ddd.service.mapping=redis:redisk8s -Ddd.env=dev -Ddd.agent.port=9529
- name: PARAMS
value: "--spring.redis.host=$(NACOS_IP) --spring.nacos.ip=$(NACOS_IP)"
name: gateway-service
image: 47.96.6.150:5000/df-demo/demo-gateway:v1
#command: ["sh","-c"]
ports:
- containerPort: 9299
protocol: TCP
volumeMounts:
- mountPath: /usr/dd-java-agent/agent
name: ddagent
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:
- emptyDir: {}
name: ddagent
Auth 배포 파일 작성¶
/usr/local/k8s/DockerfileAuth를 새로 만듭니다.
파일 내용은 다음과 같습니다.
FROM openjdk:8u292
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ENV jar demo-auth.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"]
/usr/local/k8s/auth-deployment.yaml 파일을 새로 만듭니다. 파일 내용은 다음과 같습니다.
/usr/local/k8s/auth-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: auth-service
labels:
app: auth-service
spec:
selector:
app: auth-service
ports:
- protocol: TCP
port: 9200
targetPort: 9200
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-service
labels:
app: auth-service
spec:
replicas: 2
selector:
matchLabels:
app: auth-service
template:
metadata:
labels:
app: auth-service
spec:
containers:
- env:
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NACOS_IP
value: "172.16.0.230"
- name: JAVA_OPTS
value: |-
-javaagent:/usr/dd-java-agent/agent/dd-java-agent.jar -Ddd.service.name=demo-k8s-auth -Ddd.tags=container_host:$(POD_NAME),node_ip:$(DD_AGENT_HOST) -Ddd.service.mapping=redis:redisk8s -Ddd.env=dev -Ddd.agent.port=9529
- name: PARAMS
value: "--spring.redis.host=$(NACOS_IP) --spring.nacos.ip=$(NACOS_IP)"
name: auth-service
image: 47.96.6.150:5000/df-demo/demo-auth:v1
#command: ["sh","-c"]
ports:
- containerPort: 9200
protocol: TCP
volumeMounts:
- mountPath: /usr/dd-java-agent/agent
name: ddagent
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:
- emptyDir: {}
name: ddagent
System 배포 파일 작성¶
/usr/local/k8s/DockerfileSystem을 새로 만듭니다.
파일 내용은 다음과 같습니다.
FROM openjdk:8u292
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ENV jar demo-modules-system.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"]
/usr/local/k8s/system-deployment.yaml 파일을 새로 만듭니다. Pod에서는 3개의 이미지(172.16.0.238/df-ruoyi/demo-system:v1, pubrepo.guance.com/datakit/logfwd:1.2.7, pubrepo.guance.com/datakit-operator/dd-lib-java-init)를 사용합니다.
dd-lib-java-init는 dd-java-agent.jar 파일을 system-container 비즈니스 컨테이너에 제공하고, logfwd는 비즈니스 컨테이너의 로그 파일을 수집합니다. logfwd의 구성 파일은 ConfigMap을 통해 컨테이너에 마운트되며, 구성 파일에서 수집할 로그 파일 위치, source 이름 등을 지정합니다.
system-deployment.yaml의 전체 내용은 다음과 같습니다.
system-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: system-service
spec:
selector:
app: system-pod
ports:
- protocol: TCP
port: 9201
#nodePort: 30001
targetPort: 9201
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: system-deployment
#labels:
# app: system-deployment
spec:
replicas: 1
selector:
matchLabels:
app: system-pod
template:
metadata:
labels:
app: system-pod
spec:
containers:
- name: system-container
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: DD_AGENT_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: NACOS_IP
value: "172.16.0.229"
- name: DB_IP
value: "172.16.0.230"
- name: JAVA_OPTS
value: |-
-javaagent:/usr/dd-java-agent/agent/dd-java-agent.jar -Ddd.service.name=demo-k8s-system -Ddd.tags=container_host:$(POD_NAME),node_ip:$(DD_AGENT_HOST) -Ddd.service.mapping=mysql:mysql-k8s,redis:redisk8s -Ddd.env=dev -Ddd.agent.port=9529
- name: PARAMS
value: "--spring.redis.host=$(DB_IP) --spring.nacos.ip=$(NACOS_IP) --spring.db.ip=$(DB_IP)"
image: 172.16.0.238/df-ruoyi/demo-system:v1
#command: ["sh","-c"]
ports:
- containerPort: 9201
protocol: TCP
volumeMounts:
- name: ddagent
mountPath: /usr/dd-java-agent/agent
- name: varlog
mountPath: /data/app/logs/ruoyi-system
resources:
limits:
memory: 512Mi
requests:
memory: 256Mi
- name: logfwd
image: pubrepo.guance.com/datakit/logfwd:1.2.7
env:
- name: LOGFWD_DATAKIT_HOST
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: status.hostIP
- name: LOGFWD_DATAKIT_PORT
value: "9531"
- name: LOGFWD_LOGFWD_ANNOTATION_DATAKIT_LOG_CONFIGS
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.annotations['datakit/log']
- name: LOGFWD_POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: LOGFWD_POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
volumeMounts:
- mountPath: /var/log
name: varlog
- mountPath: /opt/logfwd/config
name: logfwd-config
subPath: config
initContainers:
- name: ddtrace-agent-sidecar
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
volumeMounts:
- mountPath: /ddtrace/agent
name: ddagent
restartPolicy: Always
volumes:
- name: varlog
emptyDir: {}
- name: ddagent
emptyDir: {}
- configMap:
name: logfwd-conf
name: logfwd-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: logfwd-conf
data:
config: |
[
{
"loggings": [
{
"logfiles": ["/var/log/info.log","/var/log/error.log"],
"source": "k8s-log-system",
"multiline_match": "^\\d{4}-\\d{2}-\\d{2}"
}
]
}
]
또한 system-deployment.yaml 파일에서는 환경 변수를 사용하여 DataKit 및 logfwd 포트를 지정합니다.
환경 변수 설명:
- LOGFWD_DATAKIT_HOST: DataKit 주소
- LOGFWD_DATAKIT_PORT: logfwd 포트
logfwd-conf 매개변수 설명:
- logfiles: 로그 파일 목록.
- ignore: 파일 경로 필터, glob 규칙 사용, 조건 중 하나라도 일치하면 해당 파일을 수집하지 않음.
- source: 데이터 소스.
- service: 추가 태그, 비어 있으면 기본적으로 $source 사용.
- pipeline: Pipeline 사용 시 스크립트 경로 정의.
- character_encoding: 인코딩 선택.
- multiline_match: 여러 줄 일치.
- remove_ansi_escape_codes: ANSI 이스케이프 코드(예: 표준 출력의 텍스트 색상 등)를 삭제할지 여부, 값은 true 또는 false.
링크 데이터에 node_ip 태그 추가¶
datakit.yaml에 ConfigMap 추가:
ddtrace.conf: |-
[[inputs.ddtrace]]
endpoints = ["/v0.3/traces", "/v0.4/traces", "/v0.5/traces"]
# ignore_resources = []
customer_tags = ["node_ip"]
volumeMounts 아래에 추가:
- mountPath: /usr/local/datakit/conf.d/ddtrace/ddtrace.conf
name: datakit-conf
subPath: ddtrace.conf
실제 사용자 모니터링(RUM)¶
애플리케이션 생성¶
「Guance 플랫폼」에 로그인하여 「RUM」 - 「애플리케이션 생성」 - 「Web 유형 선택」 - 「동기 로드」를 선택하고, 애플리케이션 이름에 web-k8s-demo를 입력합니다.
프론트엔드 RUM 모니터링 활성화¶
DataKit에서 RUM 수집기를 활성화하려면 ENV_DEFAULT_ENABLED_INPUTS 환경 변수 값 끝에 rum을 추가합니다.
- name: ENV_DEFAULT_ENABLED_INPUTS
value: cpu,disk,diskio,mem,swap,system,hostobject,net,host_processes,container,statsd,ebpf,rum
RUM에서 사용하는 DataKit 주소는 고객의 네트워크에서 접근 가능한 주소여야 하므로, DataKit의 구성 파일 /usr/local/datakit/conf.d/datakit.conf의 listen="0.0.0.0:9529"를 수정해야 합니다.
이 예제에서 사용된 DataKit은 DaemonSet 방식으로 배포되었으며, 기본 구성이 이미 수정되었습니다. 실제 프로덕션 환경에서는 RUM에 사용되는 DataKit을 별도로 배포하는 것이 좋습니다.
/usr/local/k8s/dist/index.html 파일을 수정하고, head에 다음 내용을 추가합니다.
<script src="https://static.guance.com/browser-sdk/v2/dataflux-rum.js" type="text/javascript"></script>
<script>
window.DATAFLUX_RUM &&
window.DATAFLUX_RUM.init({
applicationId: 'appid_ddxxxxxxxxxxxxxxxxxx5',
datakitOrigin: 'http://172.16.0.230:9529',
env: 'production',
version: '1.0.0',
trackInteractions: true,
traceType: 'ddtrace',
allowedTracingOrigins: ["http://8.136.207.182:30000","http://8.136.193.105:30000","http://8.136.204.98:30000"]
})
</script>
-
applicationId: 애플리케이션 ID.
-
datakitOrigin: 사용자가 접근 가능한 DataKit 주소 또는 도메인 이름입니다. 여기서 172.16.0.230은 k8s의 node1 IP 주소입니다.
-
env: 필수, 애플리케이션이 속한 환경으로, test 또는 product 또는 다른 필드입니다.
-
version: 필수, 애플리케이션의 버전 번호입니다.
-
allowedTracingOrigins: RUM과 APM을 연결합니다. 백엔드 서버 주소 또는 도메인 이름을 구성합니다. 이 예제에서는 프론트엔드와 백엔드 액세스 주소가 모두 http://8.136.193.105:30000/이므로 구성 시 30000 포트를 추가해야 합니다.
-
trackInteractions: 사용자 행동 통계입니다. 예: 버튼 클릭, 정보 제출 등의 동작.
-
traceType: 선택 사항이며 기본값은 ddtrace입니다. 현재 ddtrace, zipkin, skywalking_v3, jaeger, zipkin_single_header, w3c_traceparent의 6가지 유형을 지원합니다.
RUM에 대한 자세한 내용은 <Web 애플리케이션 모니터링(RUM) 모범 사례>를 참조하세요.
애플리케이션 성능 모니터링(APM)¶
ddtrace 활성화¶
자세한 내용은 <링크 데이터에 node_ip 태그 추가>를 참조하세요.
Java 애플리케이션 ddtrace 연결¶
system 이미지 파일 DockerfileSystem에서 jar를 시작하는 명령은 다음과 같습니다.
환경 변수 JAVA_OPTS는 배포 파일 system-deployment.yaml에서 다음과 같이 정의됩니다.
- name: JAVA_OPTS
value: |-
-javaagent:/usr/dd-java-agent/agent/dd-java-agent.jar -Ddd.service.name=demo-k8s-system -Ddd.tags=container_host:$(POD_NAME) -Ddd.service.mapping=mysql:mysql-k8s,redis:redisk8s -Ddd.env=dev -Ddd.agent.port=9529
JAVA_OPTS 상세 설명:
-Ddd.env: 애플리케이션의 환경 유형, 선택 사항
-Ddd.tags: 사용자 정의 태그, 여러 개는 쉼표로 구분, 선택 사항
-Ddd.service.name: JVM 데이터 소스의 애플리케이션 이름, 필수
-Ddd.agent.host=localhost DataKit 주소, 선택 사항
-Ddd.agent.port=9529 DataKit 포트, 필수
-Ddd.version: 버전, 선택 사항
-Ddd.jmxfetch.check-period: 수집 빈도(밀리초), 기본값 1500, 선택 사항
-Ddd.jmxfetch.statsd.host=127.0.0.1 statsd 수집기의 연결 주소로 DataKit 주소와 동일, 선택 사항
-Ddd.jmxfetch.statsd.port=8125: DataKit의 statsd 수집기 UDP 연결 포트, 기본값 8125, 선택 사항
-Ddd.trace.health.metrics.statsd.host=127.0.0.1: 자체 메트릭 데이터 수집 전송 주소로 DataKit 주소와 동일, 선택 사항
-Ddd.trace.health.metrics.statsd.port=8125: 자체 메트릭 데이터 수집 전송 포트, 선택 사항
-Ddd.service.mapping: 애플리케이션이 호출하는 redis, mysql 등의 별칭, 선택 사항
참고:
JAVA_OPTS에는 링크 데이터를 보고할 DataKit 주소가 지정되어 있지 않습니다. 대신 yaml에서 환경 변수DD_AGENT_HOST를 정의하여 링크 데이터를 보고할 DataKit 주소를 지정합니다. Kubernetes 클러스터에서 링크 데이터 보고 원칙은 POD 링크 데이터를 동일한 호스트에 배포된 DataKit에 보고하는 것입니다. 자세한 구성은system-deployment.yaml을 참조하세요.
교차 출처 요청 화이트리스트 설정¶
response.headers.add('Access-Control-Allow-Headers','x-datadog-parent-id,x-datadog-sampled,x-datadog-sampling-priority,x-datadog-trace-id')
로그¶
logback.xml 구성¶
logback.xml을 수정하여 traceId, spanId, service를 로그에 출력하여 링크와 연결합니다.
로그 수집 활성화¶
Kubernetes 로그 수집은 datakit의 logfwd 수집기를 사용하는 것이 좋습니다.
logfwdserver.conf: |-
[inputs.logfwdserver]
## logfwd 수신 측 수신 주소 및 포트
address = "0.0.0.0:9531"
[inputs.logfwdserver.tags]
# some_tag = "some_value"
# more_tag = "some_other_value"
volumeMounts 아래에 추가:
- mountPath: /usr/local/datakit/conf.d/log/logfwdserver.conf
name: datakit-conf
subPath: logfwdserver.conf
로그 파싱 Pipeline¶
Pipeline을 사용하여 System 시스템에서 생성된 로그를 파싱하고, 키 정보(예: traceID)를 태그로 잘라내어 링크와 연결할 수 있도록 합니다.
「로그」 모듈을 클릭하고 「Pipelines」로 이동한 후, Pipeline을 새로 만듭니다. 필터에서 System 모듈이 로그를 수집할 때 설정한 Source: k8s-log-system을 선택하고, 다음 내용을 입력한 후 테스트에 성공하면 「저장」을 클릭합니다.
#2022-08-09 13:39:57.392 [http-nio-9201-exec-4] INFO c.r.s.c.SysUserController - [list,70] - demo-k8s-system 1241118275256671447 9052729774571622516 - 查询用户列表开始
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")
애플리케이션 배포¶
이미지 빌드 및 Harbor 저장소에 업로드¶
$ cd /usr/local/k8s/
$ docker build -t 172.16.0.215:5000/df-demo/demo-web:v1 -f DockerfileWeb .
$ docker push 172.16.0.215:5000/df-demo/demo-web:v1
$ docker build -t 172.16.0.215:5000/df-demo/demo-gateway:v1 -f DockerfileGateway .
$ docker push 172.16.0.215:5000/df-demo/demo-gateway:v1
$ docker build -t 172.16.0.215:5000/df-demo/demo-auth:v1 -f DockerfileAuth .
$ docker push 172.16.0.215:5000/df-demo/demo-auth:v1
$ docker build -t 172.16.0.215:5000/df-demo/demo-system:v1 -f DockerfileSystem .
$ docker push 172.16.0.215:5000/df-demo/demo-system:v1
배포¶
$ cd /usr/local/k8s/
$ kubectl apply -f web-deployment.yaml
$ kubectl apply -f gateway-deployment.yaml
$ kubectl apply -f auth-deployment.yaml
$ kubectl apply -f system-deployment.yaml
링크 분석¶
RUM APM 연동¶
Web 애플리케이션에 접속하여 「시스템 관리」 - 「사용자 관리」를 클릭합니다. 그러면 사용자 목록 조회 요청인 list가 트리거됩니다. dataflux-rum.js는 trace-id를 생성하여 header에 저장하며, list 인터페이스에 해당하는 trace-id가 2772508174716324531인 것을 확인할 수 있습니다. 요청이 백엔드의 list 인터페이스를 호출하면, 백엔드의 ddtrace가 trace-id를 읽어 자체 trace 데이터에 기록합니다. logback.xml에 %X{dd.trace_id}가 추가되어 trace_id가 로그와 함께 출력되므로 RUM, APM, 로그의 연동이 구현됩니다.
「RUM」 - 「ruoyi-k8s-web」 - 「탐색기」 - 「view」를 클릭합니다. 이전 작업은 사용자 관리 목록 조회였으므로 목록에서 /system/user를 클릭합니다.
「Fetch/XHR」을 클릭합니다.
위 그림의 prod-api/system/user/list를 클릭합니다. prod-api는 nginx가 추가한 전달 요청이고, /system/user/list는 백엔드의 API 인터페이스입니다. 들어가면 구체적인 요청 및 소요 시간을 확인할 수 있습니다.
로그 분석¶
「로그」 모듈을 클릭하고 「전체 소스」를 선택합니다. 기본적으로 최근 15분간의 로그를 조회합니다. 프론트엔드에서 생성된 trace_id 2772508174716324531을 「검색창」에 입력하고 Enter 키를 눌러 검색합니다.
「APM」 - 「링크 필터 상자」를 클릭하고 trace_id:704229736283371775를 입력한 후 Enter 키를 눌러 링크의 호출 상황을 검색합니다.
「SysUserController.list」를 클릭하여 상세 정보를 확인합니다.





















