1. 배경
쿠버네티스 클러스터를 운영하며 안정적인 관리를 위해 로그 시스템을 구축하기 위해 EFK Stack을 테스트 한 내용을 기록합니다. 멀티 클러스터 환경에서 발생하는 로그를 한 개의 키바나에서 볼 수 있도록 설치합니다. 기본 흐름은 다음과 같습니다.
Fluent-bit는 Daemonset으로 생성되어 모든 서버에 생성되어 로그를 전송합니다.
Fluentd는 로그를 수집해 적절히 파싱해 ElasticSearch로 보냅니다.
ElastciSearch에서 수집한 로그를 Kibana에서 시각화합니다.
Fluent-bit 없이 Fluentd만 설치하여 로그를 보낼 수 있지만 규모가 큰 클러스터에서 Fluentd의 작업량이 많아져 부하가 생기기 때문에 로그를 전송하는 역할만하는 Fluent-bit을 따로 설치하였습니다. Fluent-bit에서 필요하지 않은 로그를 필터링해 전송하는 로그양을 줄이고 Fluentd에서 파싱을 적절히 하여 Elasticsearch로 전송합니다. 본 글에서는 Fluentd, Fluent-bit의 세부적인 설정방법보다 전체 EFK 스택을 설치하는 과정을 기록합니다.
2. Elasticsearch 설치
Elasticsearch는 Statefulset으로 생성되며, HA 구조를 위해 replicas를 3개로 생성해 멀티 마스터 구조로 구성합니다. 3개의 ES 파드가 밸런싱 되어 로그를 수집하는 구조가 아닌, 마스터 한 대로 사용하다가 에러가 발생하면 나머지 2개의 파드에서 새로운 리더를 선출해 중단 없이 사용하는 방식입니다. 7.1 버전의 ES에서는 security 설정 없이 사용이 가능하지만 최신 버전의 경우 xpack.security 설정을 해야 합니다. disable을 통해 편하게 설치할 수 있지만 보안을 위해 이를 설정합니다.
Secured Elasticsearch Cluster를 구성하기 위해서는 TLS 인증서가 필요한데, 이는 노드 간 데이터 전송을 할 때 사용합니다. 인증서는 각 노드에 대한 hostname과 IP 주소를 기반으로 생성됩니다. Elasticsearch에서는 HTTP와 Transport에 대한 SSL 설정을 관리하는데, HTTP는 Client-Elasticsearch Cluster의 통신에서, Transport는 클러스터 내부의 노드간 통신에서 사용합니다. 두 가지 각 각 다른 인증서를 사용하지만 편의를 위해 한 가지 인증서를 통해 설정할 수 있습니다
Self-Signed 인증서 생성
OpenSSL을 통해 rootCA를 생성합니다.
openssl req -newkey rsa:2048 -keyout es-ca.key -nodes -x509 -days 3650 -out es-ca.crt
Kibana, Elasticsearch에서 사용할 key를 생성합니다.
DOMAIN="elasticsearch-master"
$ openssl genrsa -out "${DOMAIN}".key 2048 && chmod 0600 "${DOMAIN}".key
인증서를 발급하기 위해 CSR(인증서 발급에 필요한 데이터를 담고 있는 인증서 신청 형식). 데이터 입력 시 Common Name을 elastic에서 사용하는 도메인을 입력합니다.
openssl req -new -sha256 -key "${DOMAIN}".key -out "${DOMAIN}".csr
생성한 CSR을 기반으로 인증서를 생성해 RootCA에서 서명합니다.
openssl x509 -req -in "${DOMAIN}".csr -CA es-ca.crt -CAkey es-ca.key -CAcreateserial -out "${DOMAIN}".crt -days 1825 -sha256
ElasticUser, Certificate Secret 생성
ElasticUser의 ID/PW 정보를 담고 있는 secret을 생성합니다.
k create secret genric elasticuser-credential --from-literal=username=elastic --from-literal=password=password
위에서 생성한 인증서를 Secret으로 생성합니다.
k create secret generic elastic-certificates --from-file=tls.key=elasticsearch-master.key --from-file=tls.crt=elasticsearch-master.crt --from-file=ca.crt=es-ca.crt
Elasticsearch Helm 설치
elastic 공식 Helm을 이용해 설치합니다.
helm repo add elastic https://helm.elastic.co
#values.yaml
clusterName: "elasticsearch"
nodeGroup: "master"
roles:
master: "true"
ingest: "true"
data: "true"
remote_cluster_client: "true"
ml: "true"
replicas: 3
minimumMasterNodes: 1
protocol: https
httpPort: 9200
imagePullPolicy: "IfNotPresent"
extraEnvs:
- name: "ELASTIC_PASSWORD"
valueFrom:
secretKeyRef:
name: "elasticuser-credential"
key: "password"
- name: "ELASTIC_USERNAME"
valueFrom:
secretKeyRef:
name: "elasticuser-credential"
key: "username"
esConfig:
elasticsearch.yml: |
xpack.security.enabled: "true"
xpack.security.transport.ssl.enabled: "true"
xpack.security.transport.ssl.supported_protocols: "TLSv1.2"
xpack.security.transport.ssl.client_authentication: "none"
xpack.security.transport.ssl.key: "/usr/share/elasticsearch/config/certs/tls.key"
xpack.security.transport.ssl.certificate: "/usr/share/elasticsearch/config/certs/tls.crt"
xpack.security.transport.ssl.certificate_authorities: "/usr/share/elasticsearch/config/certs/ca.crt"
xpack.security.transport.ssl.verification_mode: "certificate"
xpack.security.http.ssl.enabled: "true"
xpack.security.http.ssl.supported_protocols: "TLSv1.2"
xpack.security.http.ssl.client_authentication: "none"
xpack.security.http.ssl.key: "/usr/share/elasticsearch/config/certs/tls.key"
xpack.security.http.ssl.certificate: "/usr/share/elasticsearch/config/certs/tls.crt"
xpack.security.http.ssl.certificate_authorities: "/usr/share/elasticsearch/config/certs/ca.crt"
secretMounts:
- name: "elastic-certificates"
secretName: "elastic-certificates"
path: "/usr/share/elasticsearch/config/certs"
defaultMode: "0755"
resources:
requests:
cpu: "250m"
memory: "2Gi"
limits:
cpu: "1000m"
memory: "4Gi"
volumeClaimTemplate:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs-client"
resources:
requests:
storage: 30Gi
clusterHealthCheckParams: "wait_for_status=yellow&timeout=2s"
이후 values.yaml 파일을 통해 설치합니다.
helm install elasticsearch elastic/elasticsearch -f values.yaml
정상적으로 배포가 되면 다음과 같이 statefulset에 생성된 것을 확인할 수 있습니다.
3. Kibana 설치
kibana 마찬가지로 elastic 공식 Helm에서 설치합니다.
#values.yaml
elasticsearchHosts: "https://elasticsearch-master:9200"
replicas: 1
protocol: https
httpPort: 5601
imagePullPolicy: "IfNotPresent"
extraEnvs:
- name: "NODE_OPTIONS"
value: "--max-old-space-size=1800"
- name: "ELASTICSEARCH_USERNAME"
valueFrom:
secretKeyRef:
name: "elasticuser-credential"
key: "username"
- name: "ELASTICSEARCH_PASSWORD"
valueFrom:
secretKeyRef:
name: "elasticuser-credential"
key: "password"
kibanaConfig:
kibana.yml: |
server.ssl:
enabled: "true"
key: "/usr/share/kibana/config/certs/tls.key"
certificate: "/usr/share/kibana/config/certs/tls.crt"
certificateAuthorities: [ "/usr/share/kibana/config/certs/ca.crt" ]
clientAuthentication: "none"
supportedProtocols: [ "TLSv1.2", "TLSv1.3" ]
elasticsearch.ssl:
certificateAuthorities: [ "/usr/share/kibana/config/certs/ca.crt" ]
verificationMode: "certificate"
newsfeed.enabled: "false"
telemetry.enabled: "false"
telemetry.optIn: "false"
secretMounts:
- name: "elastic-certificates"
secretName: "elastic-certificates"
path: "/usr/share/kibana/config/certs"
defaultMode: "0755"
resources:
requests:
cpu: "55m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "2Gi"
Elasticsearch와 마찬가지로 Elasticuser 정보가 담겨있는 Secret과 인증서를 마운트해 Kibana에서 사용할 수 있도록 설정합니다. 이후 위의 values.yaml을 이용해 kibana를 배포합니다.
helm install kibana elastic/kibana -f values.yaml
배포 후 elasticsearch와 연결이 성공하면 파드가 Running 상태가 됩니다. 인증서 오류나, elasticuser의 ID/PW가 잘못되었을 경우 conatainer가 ReadnessProbe가 실패하여 READY 되지 않습니다.
이후 노드포트를 생성해 https로 접속하면 키바나가 정상적으로 실행되고 인증서 또한 적용된 것을 확인할 수 있습니다.
3. Fluentd 설치
Fluentd를 설치하기 위해 공식 Helm github를 clone 하여 values.yaml을 변경합니다.
git clone https://github.com/fluent/helm-charts.git
fluentd는 Deployment로 배포하기 위해 Daemonset을 변경합니다. 이후 Elasticsearch와 동일하게 인증서 정보가 담긴 Secret을 마운트 하여 Elasticsearch로 인증서 정보와 함께 설정합니다. Source 항목으로 경우 fluent-bit에서 받기 위해 forward로 설정합니다.
#values.yaml
kind: "Deployment"
# # Only applicable for Deployment or StatefulSet
replicaCount: 3
volumes:
- name: elastic-certs
secret:
secretName: elastic-certificates
defaultMode: 0777
volumeMounts:
- name: elastic-certs
mountPath: /etc/fluent/certs
readOnly: true
fileConfigs:
01_sources.conf: |-
## logs from podman
<source>
@type forward
@label @KUBE_ALL
bind 0.0.0.0
port 24224
</source>
02_fileter.conf |-
<label @KUBE_ALL>
<match **>
@type relabel
@label @OUTPUT_CONTAINER
</match>
</label>
04_outputs.conf: |-
<label @OUTPUT_CONTAINER>
<match **>
@type elasticsearch
hosts "https://elastic:password@elasticsearch-master:9200"
index_name fluentd-00001
ca_file /etc/fluent/certs/ca.crt
client_cert /etc/fluent/certs/tls.crt
client_key /etc/fluent/certs/tls.key
#port 9200
#path ""
#user elastic
#password changeme
</match>
</label>
설정한 values.yaml 파일을 이용해 fluentd를 설치합니다.
helm install fluentd fluentd/
fluentd가 설치되고 정상적으로 elasticsearch와 연결되면 다음과 같이 로그가 발생하는 것을 확인 할 수 있습니다.
4. fluent-bit 설치
fluentd와 동일한 repository에서 fluent-bit 폴더의 values.yaml을 수정합니다. 변경 사항은 다음과 같습니다. Output 항목을 fluentd에서 생성한 forward로 변경한 후 fluentd의 Service를 생성해 해당 포트로 전송합니다.
#values.yaml
config:
inputs: |
[INPUT]
Name tail
Path /var/log/containers/*.log
multiline.parser docker, cri
Tag kube.*
Mem_Buf_Limit 5MB
Skip_Long_Lines On
outputs: |
[OUTPUT]
Name forward
Match kube.*
Host fluentd-forward.efk.svc.cluster.local
Port 24224
Retry_Limit False
fluent-bit이 정상적으로 설치 되고 fluentd와 연결되었다면 다음과 같이 로그가 발생하는 것을 확인할 수 있습니다.
5. Index Pattern 생성
데이터가 정상적으로 들어오면 Kibana에서 index Pattern을 만들어 확인 할 수 있습니다.
Management → Stack Mangement → Index Patterns → Create index Pattern
인덱스 패턴은 fluentd 설정의 Index_name으로 설정됩니다. 이 내용이 자동으로 보이지 않다면 Fluentd와 Elasticsearch 연결이 정상적으로 이루어지지 않은 것으로 볼 수 있습니다.
이후 Discover에서 데이터 조회 시 로그가 정상적으로 들어오는 것을 확인할 수 있습니다.
6. Fluentd Secured Forward 설정
위에서 구성한 Fluent-bit - Fluentd 구조는 ClusterIP로 연결되어 있어서 같은 클러스터에서는 접근이 가능합니다. 하지만 멀티 클러스터 구조에서 다른 클러스터에서는 연결이 되지 않기 때문에 외부에서 접근이 가능한 서비스를 생성해야 합니다. 이를 위해 Secured Forward Plugins을 이용해 tls로 데이터를 전송하도록 설정합니다.
# create shared key
head -c 24 /dev/urandom | base64
#Fluentd-values.yaml
plugins:
- fluent-plugin-secure-forward
fileconfigs:
<source>
@type secure_forward
@label @KUBE_ALL
bind 0.0.0.0
port 24224
shared_key 6UOF5TYykmT+sgIi1vfkOsF428cs5hR5
self_hostname fluentd-forward.secure
secure no
</source>
#fluent-bit values.yaml
outputs: |
[OUTPUT]
Name forward
Match kube.*
Host fluentd-forward.efk.svc.cluster.local
Port 24224
shared_key 6UOF5TYykmT+sgIi1vfkOsF428cs5hR5
tls on
tls.verify off
Retry_Limit False
Time_as_Integer true
위의 설정을 적용해 배포하면 TLS를 통해 로그를 전송하게 됩니다.
7. Kubernetes Cluster 추가
다른 클러스터에서는 Fluent-bit만 설치하여 Fleuntd로 로그를 전송할 수 있도록 합니다. Fluentd로 연결하는 서비스의 경우 실제 구성에서는 LB를 이용했지만 현재 테스트에서는 NodePort로 Fluentd 서비스를 생성해 연결하였습니다. 위의 fluent-bit의 values.yaml과 동일하고 Host와 Port만 변경해서 설치하였습니다.
#fluent-bit values.yaml
outputs: |
[OUTPUT]
Name forward
Match kube.*
Host Node 주소
Port NodePort
shared_key 6UOF5TYykmT+sgIi1vfkOsF428cs5hR5
tls on
tls.verify off
Retry_Limit False
Time_as_Integer true
이후 키바나에서 현재 클러스터에 생성되어 있는 파드이름을 조회하면 관련 로그가 나오는 것을 확인할 수 있습니다.
현재 Fluentd의 동일한 OUTPUT을 참조하기 때문에 한 개의 index에서 모든 로그가 보이지만 OUTPUT을 분리해 index를 따로 구성하여 클러스터 별로 로그를 확인할 수 있습니다.
'Kubernetes' 카테고리의 다른 글
Velero를 이용한 쿠버네티스 Backup (0) | 2023.10.12 |
---|---|
EFK Stack을 이용한 쿠버네티스 로깅 스택 구축#2 (0) | 2023.10.12 |
Thanos를 이용한 Prometheus HA 구성 (1) | 2023.10.12 |
ArgoCD,Jenkins를 이용한 쿠버네티스 배포 (0) | 2023.10.12 |
Kubernetes HA 구성을 위한 Pod Scheduling (0) | 2023.10.12 |