
💡
모든 글은 가급적 미괄식으로 작성되어, 독자도 같은 상황을 겪게 설계했습니다.
직전에 노드 모니터링 구축하기[1]를 진행했습니다.
이 과정에서 여러 매트릭(metric)을 PromQL로 질의하고 테스트했습니다.
다음에 진행할 클러스터 모니터링 구축하기[2]를 진행하기 전에
Prometheus로 수집이 불가능한 여러 정보를 로그(log) 형태로 수집하려고 합니다.
이를 위해 Loki를 배포하고 Fluent-bit을 포함한 실습을 진행하고자 합니다.
마지막으로 모니터링 시스템 배포하기[3]에서 누락한 Persistent 설정을 추가합니다.
A. 실습 환경
A.1. rancher local-path-provisioner 설치하기
권장 사례인 Dynamic Provisioning*을 사용하기 위해 StorageClass가 필요합니다.
💡
[Dynamic Provisioing*] PersistentVolume을 별도로 생성 및 관리하지 않기 위해서 PersistentVolume의 생성 및 관리를 StorageClass에 위임을 하여 필요한 시점에 PersistentVolume을 생성 및 제거할 수 있게 해줍니다.
Local 을 지원하는 기본 플러그인인 kubernetes.io/no-provisioner [A.1.1]은
Dynamic Provisioing을 지원하지 않고 강제로 사용할 시 순환 참조*가 발생합니다.
💡
[순환 참조*] Local Plugin인 no-provisioner을 사용하는 StorageClass인 local-storage를 만들고 나서 PersistentVolumeClaim의 storageClassName에 local-storage를 참조하고 Pod에서 이 persistentVolumeClaim을 참조하면 아래의 순환 참조가 발생한다. 1. Pod가 생성되기 전에 PersistentVolume이 필요하다. 2. PersistentVolume은 파드가 생성되어야 생성된다.
kubernetes.io/no-provisioner [A.1.1]에서는 두 가지 방법만 쓸 수 있다.
nodeSelector를 명시하고 Immediate 방식으로 PersistentVolume 생성
PersistentVolume을 별도로 생성하고 PersistentVolumeClaim을 생성
💡
1. Immediate : PersistentVolumeClaim이 생기면 PersistentVolume도 생성 2. WaitForFirstConsumer : 실제로 PersistentVolumeClaim을 소비하면 PersistentVolume 생성 (Pod가 생성된 이후…)
아래 코드를 실행하면 local-storage라는 StorageClass가 생성된다.
본 문서에서는 사용하지 않으나, 참고를 위해서 첨부하였습니다.
mkdir -p ~/local-storage-provisioner
cd ~/local-storage-provisioner
cat <<EOF > local-storage-provisioner.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
# volumeBindingMode: Immediate
reclaimPolicy: Delete
allowVolumeExpansion: false
EOF
kubectl apply -f ~/local-storage-provisioner/local-storage-provisioner.yaml
Dyanmic을 지원하는 rancher/local-path-provisioner [A.1.2]를 사용합니다.
GitHub에서 가이드하는 Git Clone 방식에 따라서 설치를 진행하였습니다.
Helm 차트 클론받기
mkdir -p ~/rancher cd ~/rancher git clone https://github.com/rancher/local-path-provisioner.git cd local-path-provisioner
Helm 릴리즈
cd ~/rancher/local-path-provisioner helm upgrade --install local-path \ --namespace kube-system \ --create-namespace \ ~/rancher/local-path-provisioner/deploy/chart/local-path-provisioner/
권장하지는 않으나,
Immediate를 기본값으로 하고 싶다면 아래를 참고하면 됩니다.
cd ~/rancher/local-path-provisioner
helm upgrade --install local-path \
--namespace kube-system \
--create-namespace \
--set storageClass.volumeBindingMode=Immediate \
~/rancher/local-path-provisioner/deploy/chart/local-path-provisioner/
A.2. Loki 배포하기
클러스터 모니터링에서 중요한 2가지 지표를 보기 위해서는 로그가 필요합니다.
kubernetes events
kubernetes control plane logs (journalctl logs / crictl logs)
로그 저장, 조회를 위해서 grafana/loki [A.2.1]을 사용합니다.
로그 저장 및 인덱스를 위해서 S3, DynamoDB를 사용할 수도 있지만
온프레미스의 환경에 맞게 FileSystem 타입을 사용하여 구현했습니다.
tHelm 저장소 추가하기
helm repo add grafana https://grafana.github.io/helm-charts
Helm 저장소 업데이트
helm repo update
Helm 저장소 확인하기
helm search repo loki-stack
Loki values.yaml 파일 생성하기
mkdir -p ~/loki cd ~/loki cat <<EOF > loki-stack.values.yaml loki: auth_enabled: false commonConfig: replication_factor: 1 storage: type: filesystem # 스키마 구성 추가 schemaConfig: configs: - from: 2020-10-24 store: tsdb object_store: filesystem schema: v13 index: prefix: index_ period: 24h # 스토리지 구성 상세화 storageConfig: boltdb_shipper: active_index_directory: /data/loki/boltdb-shipper-active cache_location: /data/loki/boltdb-shipper-cache cache_ttl: 24h shared_store: filesystem filesystem: directory: /data/loki/chunks serviceMonitor: enabled: true gateway: enabled: true replicas: 1 # 단일 배포 모드 설정 singleBinary: replicas: 1 persistence: enabled: true size: 10Gi storageClass: local-path # 다른 배포 컴포넌트 비활성화 read: replicas: 0 write: replicas: 0 backend: replicas: 0 # 명시적으로 deploymentMode 설정 deploymentMode: "SingleBinary" service: type: ClusterIP EOF
helm upgrade --install loki grafana/loki \ --namespace kube-monitor \ --create-namespace \ -f ~/loki/loki-stack.values.yaml
Loki 배포 상태 확인하기
kubectl get all -n kube-monitor | grep loki
pod/loki-0 0/2 Pending 0 4m6s pod/loki-canary-btf6w 1/1 Running 0 4m6s pod/loki-chunks-cache-0 0/2 Pending 0 4m6s pod/loki-gateway-5d9698cd4b-n9lcc 1/1 Running 0 4m6s pod/loki-results-cache-0 2/2 Running 0 4m6s service/loki ClusterIP 10.102.123.246 <none> 3100/TCP,9095/TCP 4m6s service/loki-canary ClusterIP 10.107.220.44 <none> 3500/TCP 4m6s service/loki-chunks-cache ClusterIP None <none> 11211/TCP,9150/TCP 4m6s service/loki-gateway ClusterIP 10.99.106.75 <none> 80/TCP 4m6s service/loki-headless ClusterIP None <none> 3100/TCP 4m6s service/loki-memberlist ClusterIP None <none> 7946/TCP 4m6s service/loki-results-cache ClusterIP None <none> 11211/TCP,9150/TCP 4m6s daemonset.apps/loki-canary 1 1 1 1 1 <none> 4m6s deployment.apps/loki-gateway 1/1 1 1 4m6s replicaset.apps/loki-gateway-5d9698cd4b 1 1 1 4m6s statefulset.apps/loki 0/1 4m6s statefulset.apps/loki-chunks-cache 0/1 4m6s statefulset.apps/loki-results-cache 1/1 4m6s
A.3. FluentBit 배포 준비하기
💡
A.3. Loki 는 기본적으로 로그 수집 기능을 제공하지 않습니다. 따라서 PromTail, Fluentd, Fluent-bit 등의 로그 수집기를 사용해야 합니다. 저는 확장성, 출력값 조정 등의 이유로 Fluent-bit을 선택하였습니다.
컨트롤 플레인의 로그들은 다양한 형태로 저장되고 있습니다.
이를 가공하기 위해서 grafana/helm-charts/fluent-bit[A.3.1]을 사용합니다.
Helm 저장소 추가하기
helm repo add grafana https://grafana.github.io/helm-charts
Helm 저장소 업데이트
helm repo update
Helm 저장소 확인하기
helm search repo fluent-bit
NAME CHART VERSION APP VERSION DESCRIPTION grafana/fluent-bit 2.6.0 v2.1.0 Uses fluent-bit Loki go plugin for gathering lo...
A.4. Prometheus Persistent 수정
모니터링 시스템 배포하기 (Prometheus, Grafana) [A.4.1]에서
빠르게 스킵하고 지나간 Persistent 설정을 보완하여 진행합니다.
helm upgrade \
--install prometheus prometheus-community/prometheus \
--set pushgateway.enabled=false \
--set alertmanager.enabled=false \
--set nodeExporter.enabled=true \
--set server.service.type="NodePort" \
--set server.statefulSet.enabled=false \
--set server.persistentVolume.enabled=true \
--set server.persistentVolume.storageClass=local-path \
--namespace kube-monitor \
--create-namespace
이후 노드 모니터링 구축하기 (Prometheus, Grafana) [A.4.1]에서
수집 간격을 줄이기 위해서 수정한 Grafana ConfigMap을 다시 수정해줍니다.
kubectl edit configmap prometheus-server -n kube-monitor -o yaml
기존
prometheus.yml: | global: evaluation_interval: 1m scrape_interval: 1m scrape_timeout: 10s
변경
prometheus.yml: | global: evaluation_interval: 1m scrape_interval: 30s scrape_timeout: 10s
ConfigMap 변경을 반영하기 위해서 Rollout을 진행합니다.
kubectl rollout restart daemonset/prometheus-prometheus-node-exporter -n kube-monitor
kubectl rollout restart deployment/prometheus-server -n kube-monitor
kubectl rollout restart deployment/prometheus-kube-state-metrics -n kube-monitor
daemonset.apps/prometheus-prometheus-node-exporter restarted
deployment.apps/prometheus-server restarted
deployment.apps/prometheus-kube-state-metrics restarted
A.5. Grafana Persistent 수정
모니터링 시스템 배포하기 (Prometheus, Grafana) [A.5.1]에서
빠르게 스킵하고 지나간 Persistent 설정을 보완하여 진행합니다.
helm upgrade \
--install grafana grafana/grafana \
--set replicas=1 \
--set service.enabled=true \
--set service.type=NodePort \
--set persistence.enabled=true \
--set persistence.storageClassName=local-path \
--namespace kube-monitor \
--create-namespace
B. Fluent-bit, Loki, Grafana 실습하기
B.1. 기본 지식
모니터링 시스템 배포하기 (Prometheus, Grafana) [B.1]에서는
Prometheus*에서 node-exporter 등을 활성화 하는 것으로 편하게 진행했습니다.
💡
[Prometheus*] 이는 시계열 데이터를 TSDB(Time-series database)에 저장합니다. 시계열 데이터에는 라벨(Label)을 통해서 많은 정보들을 포함합니다. 대부분 쿼리(PromQL)는 라벨을 통한 필터링 및 집계가 가능합니다.
Loki*에서는 시간, 메타데이터, 콘텐츠로 데이터를 분할합니다.
💡
[Loki*] 이는 시계열 데이터를 메타데이터 인덱싱 기법으로 저장합니다. 저장 장소는 TSDB, Object Storage, Block Storage 등으로 다양합니다. 대부분 쿼리(LogQL)은 메타데이터 기반의 필터링 및 집계가 가능합니다.
PromTail, Fluent-bit* 등을 사용하면 Loki로 로그를 전송할 수 있습니다.
하지만 앞으로 진행할 실습의 특성상 확장성 높은 Fluent-Bit을 선택했습니다.
이번 문서의 특성상 가볍고 확장성 좋은 Fluent-bit을 선택했습니다.
💡
[PromTail vs Fluent-bit*] 대부분의 실습에서는 PromTail을 사용하여 작업을 진행합니다. 이 친구는 DaemonSet 형태로 배포되어 추가적인 설정이 없어도 작동합니다. 하지만 시스템(journalctl), 커널 감시(falco), 감사 로그(trivy, kyverno) 등 다양한 형태로 저장되는 로그를 수집하기 위해서는 그 기능이 부족합니다. 이번 실습에서는 Fluent-bit을 선택했으며, 그 이유는 다음과 같습니다. 1. Sidecar container, Operator pod 등의 다양한 패턴으로 구축 가능 2. Log filtering, output formatting 등의 다양한 최적화 가능 3. 비슷한 기능을 하는 Fluentd에 비해 더 적은 리소스 사용
Fluent-Bit은 아래와 같이 INPUT, FILTER, BUFFER, OUTPUT으로 구성됩니다.
각 계층은 1~N개 존재할 수 있으며 세부적인 설정을 통해 확장할 수 있습니다.
결정적으로 Plugin을 활용하면 Loki, Kafka, Splunk 등과도 연계 가능합니다.
편의성 측면에서 PromTail이 훨씬 편리함에도 불구하고
확장성 측면을 고려하여 Fluent-Bit을 사용하는 방안으로 결정했습니다.
B.2. Fluent-bit으로 NGINX 로그 수집하기
Fluent-Bit의 INPUT, FILTER, PARSER 등을 정의할 ConfigMap을 만들어줍니다.
mkdir -p ~/fluent-bit/example/
cd ~/fluent-bit/example/
cat <<EOF > configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Daemon off
Log_Level info
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/nginx/access.log
Parser nginx
Tag kube.var.log.containers.nginx.access
[INPUT]
Name tail
Path /var/log/nginx/error.log
Parser nginx-error
Tag kube.var.log.containers.nginx.error
[FILTER]
Name kubernetes
Match nginx.*
Merge_Log On
Keep_Log On
K8S-Logging.Parser On
K8S-Logging.Exclude On
[OUTPUT]
Name loki
Match *
Host loki.kube-monitor.svc.cluster.local
Port 3100
Labels job=fluentbit,service_name=nginx-with-fluentbit-sidecar
Label_Keys $level,$detected_level
parsers.conf: |
[PARSER]
Name nginx
Format regex
Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
[PARSER]
Name nginx-error
Format regex
Regex ^(?<time>[0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}) \[(?<level>.*?)\] (?<pid>[0-9]+)#(?<tid>[0-9]+): (?<message>.*)
Time_Key time
Time_Format %Y/%m/%d %H:%M:%S
EOF
kubectl apply -f ~/fluent-bit/example/configmap.yaml
kubectl replace -f ~/fluent-bit/example/configmap.yaml
Sidecar pattern으로 Fluent-Bit을 실행하는 NGINX Pod를 실행합니다.
mkdir -p ~/fluent-bit/example/
cd ~/fluent-bit/example/
cat <<EOF > pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: nginx-with-fluentbit-sidecar
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: varlog
mountPath: /var/log/nginx
- name: fluent-bit
image: fluent/fluent-bit:latest
volumeMounts:
- name: varlog
mountPath: /var/log/nginx
readOnly: true
- name: config-volume
mountPath: /fluent-bit/etc/
volumes:
- name: varlog
emptyDir: {}
- name: config-volume
configMap:
name: fluent-bit-config
EOF
kubectl delete -f ~/fluent-bit/example/pod.yaml --force
kubectl apply -f ~/fluent-bit/example/pod.yaml
배포한 NGINX Pod의 앤드포인트를 확인하고 요청을 보내서 로그를 생성합니다.
NGINX Pod 앤드포인트 확인하기
kubectl get pods nginx-with-fluentbit-sidecar -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,CONTAINER:.spec.containers[0].name,PORT:.spec.containers[0].ports[0].containerPort
NAME IP CONTAINER PORT nginx-with-fluentbit-sidecar 10.244.0.150 nginx 80
NGINX Pod 앤드포인트 요청 전송하기
POD_ENDPOINT=$(kubectl get pods nginx-with-fluentbit-sidecar -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,CONTAINER:.spec.containers[0].name,PORT:.spec.containers[0].ports[0].containerPort | awk 'NR>1 {print $2":"$4}') for i in {1..10}; do curl -k $POD_ENDPOINT; done
<h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
아래와 같이 Data source에 Loki를 선택하고 아래와 같이 LogQL을 작성합니다.
샘플인 LogQL은 아래와 같이 총 3개로 지정하였습니다.
LogQL 작성하기
{job=~".+"}
LogQL 수정하기
{job="fluentbit", service_name="nginx-with-fluentbit-sidecar"}
LogQL 포맷팅 수정하기
{job="fluentbit", service_name="nginx-with-fluentbit-sidecar"} | json | line_format "{{.service_name}}({{.remote}}) - {{.host}} [{{.time}}] \"{{.method}} {{.path}}\" {{.code}} {{.size}} \"{{.referer}}\" \"{{.agent}}\""