EFK 스택을 구축하여 GitLab의 로그를 수집하면, 로그 데이터를 시각화하여 대시보드를 생성할 수 있습니다. 이를 활용하면 운영에 필요한 다양한 정보와 인사이트를 얻어, 데이터를 기반으로 의사결정을 내리는 데 도움이 됩니다. 또한, 시스템 관리자가 터미널에서 로그 파일에 직접 액세스하지 않고도, Web UI에서 로그를 분석하여 GitLab 시스템에서 발생하는 문제를 해결하는데 활용할 수 있습니다.

이번 글에서는 EFK 스택을 설치하고 구성하여 GitLab 로그를 수집한 후, Git Activity 대시보드를 만드는 방법을 알아보겠습니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

EFK Stack이란?

EFK 스택Elasticsearch, Fluentd, Kibana로 구성된 스택을 의미하는 약어입니다. 이 세 가지 도구를 결합하여 확장 가능하고 유연하며 사용하기 쉬운 로그 수집 및 분석 파이프라인을 구축할 수 있습니다.

ElasticsearchApache Lucene 기반의 분산 검색 및 분석 엔진입니다. 정해진 스키마가 없는 JSON 문서로 데이터를 저장합니다.
Kibana는 데이터 시각화 및 탐색 도구입니다. 차트와 그래프를 이용해 Elasticsearch 색인(Index)에 저장된 데이터를 시각화할 수 있습니다.
Fluentd는 로그 데이터를 수집 및 변환하여 다양한 백엔드(Elasticsearch, S3, Mongodb, HDFS 등)로 전달해주는 오픈소스 데이터 수집기입니다. 시스템 및 애플리케이션의 로그를 수집하여 전달하는 역할과 하나 혹은 여럿으로부터 전달받은 데이터를 필터링 및 처리하여 적절한 백엔드로 라우팅하는 역할로 구분한 Forwarder/Aggregator 아키텍처로 구성할 수 있습니다. 적은 리소스를 사용해야 하는 환경에서는 경량화 버전인 Fluent Bit를 사용할 수도 있습니다.

로그 수집기로 Fluentd 대신 Logstash를 사용하여 구성하면 ELK Stack이라고 합니다.
두 로그 수집기 중 어느 것이 더 좋은지 판단하기 어려우며, 서로 장단점이 있습니다. 주어진 요구사항과 환경에 따라 더 적합한 것을 선택하여 사용하거나, 때에 따라서 동시에 사용할 수도 있습니다.
Logstash와 Fluentd를 비교한 아랫글들을 참고하십시오.

로그 모니터링 시스템의 필요성

자체 관리형 GitLab의 Admin Area > Monitoring > Logs 메뉴를 통해 GitLab의 여러 로그 파일을 볼 수 있었으나, GitLab 13.0부터 다중 노드 설정에서는 로깅이 작동하지 않고 일부 정보를 표시하여 관리자에게 혼란을 줄 수 있어 Admin Area에서 이 기능이 제거되었습니다.

GitLab에는 GitLab 내의 모든 서비스와 구성 요소가 시스템 로그를 출력하는 고급 로그 시스템이 포함되어 있습니다. 이에 따라 GitLab Rails, GitLab Shell, NGINX, PostgreSQL, Puma 등의 로그가 production_json.log, api_json.log, application_json.log, integrations_json.log, sidekiq.log, gitlab-shell.log, puma_stdout.log, gitlab_access.log, gitlab_error.log 등 여러 로그 파일에 기록되며, 로그 파일이 일정 사이즈 이상이 되면 gzip으로 압축되고 교체됩니다.

EFK 스택으로 로그 모니터링 시스템을 구축하면, 로그를 분석하기 위해 터미널에서 여러 로그 파일에 액세스하지 않고도 Kibana Discover 메뉴에서 이러한 로그 파일의 내용을 확인할 수 있습니다.

아키텍처

photo | 인포그랩 GitLab | 인포그랩 GitLab

Fluent Bit가 GitLab의 로그를 수집하여 Fluentd로 전송합니다.
Fluentd는 Aggregator의 역할로 수집된 로그를 전달받아 Elasticsearch에 전달하여 데이터를 저장합니다.
Kibana에서는 Elasticsearch의 데이터를 조회하고 시각화합니다.

  • Ubuntu 20.04 LTS에 Docker Compose을 이용하여 EFK Stack 구성
  • GitLab이 설치된 서버에 Linux package로 TD-Agent Bit (Fluent Bit) 설치

EFK Stack 설치 및 구성

Docker EngineDocker Compose를 설치하고, Docker Compose를 이용하여 EFK(Elasticsearch + Fluentd + Kibana) Stack을 설치합니다.

Timezone 설정

$ date
Tue Sep 28 03:59:31 UTC 2021

$ sudo timedatectl set-timezone 'Asia/Seoul'

$ date
Tue Sep 28 12:59:51 KST 2021

$ sudo ls -al /etc/localtime
lrwxrwxrwx 1 root root 32 Sep 28 12:59 /etc/localtime -> ../usr/share/zoneinfo/Asia/Seoul

Chrony 설치

시간을 동기화하는 NTP(Network Time Protocol) 데몬(daemon)인 Chrony를 설치합니다.

sudo apt-get update
sudo apt-get install chrony -y
sudo systemctl enable chronyd
sudo systemctl start chronyd
sudo systemctl status chronyd

Timezone 및 시간 동기화 상태를 확인합니다.

$ sudo timedatectl status
Local time: Tue 2021-09-28 13:30:28 KST
Universal time: Tue 2021-09-28 04:30:28 UTC
RTC time: Tue 2021-09-28 04:30:28
Time zone: Asia/Seoul (KST, +0900)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no

EFK Stack 구성

Elasticsearch의 데이터를 저장할 수 있을 만큼의 여유 공간이 있는 경로에 EFK Stack 작업 디렉토리 (Working directory)를 생성합니다.

cd /data
mkdir efk-stack
cd efk-stack

docker-compose.yml 파일을 생성합니다.

vi docker-compose.yml

아래 내용으로 작성하고 저장합니다.

version: '3.9'

services:
fluentd:
build: ./fluentd
container_name: fluentd
mem_limit: 256m
volumes:
- './fluentd/conf:/fluentd/etc'
links:
- 'elasticsearch'
ports:
- '24224:24224'
- '24224:24224/udp'

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.15.0
container_name: elasticsearch
mem_limit: 12g
environment:
- 'xpack.security.enabled=false'
- 'discovery.type=single-node'
- 'bootstrap.memory_lock=true'
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
ports:
- '9200:9200'
volumes:
- './elasticsearch_data:/usr/share/elasticsearch/data'
logging:
driver: 'json-file'
options:
max-size: '200m'
max-file: '10'

kibana:
image: docker.elastic.co/kibana/kibana:7.15.0
container_name: kibana
mem_limit: 1g
links:
- 'elasticsearch'
ports:
- '5601:5601'

Elasticsearch plugin이 포함된 커스텀 Fluentd 이미지 생성을 위한 Dockerfile 파일을 생성합니다.

mkdir fluentd
cd fluentd
vi Dockerfile

아래 내용으로 작성하고 저장합니다.

FROM fluent/fluentd:v1.14.1-debian-1.0
USER root
RUN ["gem", "install", "fluent-plugin-elasticsearch", "--no-document", "--version", "5.1.0"]
USER fluent

Fluentd 구성 파일 fluentd/conf/fluent.conf를 생성하고, Fluent Bit으로 전달받은 로그를 Elasticsearch에 저장하도록 구성합니다.

mkdir conf
cd conf
vi fluent.conf

아래 내용으로 작성하고 저장합니다.

<system>
log_level debug
</system>
<source>
@type forward
@label @GITLAB
bind 0.0.0.0
port 24224
</source>
<label @GITLAB>
<match gitlab.**>
@type copy
<store>
@type elasticsearch
host elasticsearch
port 9200
log_es_400_reason true
logstash_format true
logstash_prefix gitlab
logstash_dateformat %Y.%m.%d
suppress_type_name true
include_tag_key true
tag_key @log_name
<buffer>
flush_interval 1s
retry_wait 5s
</buffer>
</store>
</match>
</label>
<label @ERROR>
<match **>
@type stdout
</match>
</label>

Elasticsearch의 데이터를 영속적(Persistent)으로 저장하기 위한 바인드 마운트(Bind mount)용 디렉토리를 생성합니다.

cd ../..
mkdir elasticsearch_data

docker-compose up -d --build 명령을 실행하여 EFK Stack을 시작하고 container 목록을 확인합니다.

$ docker-compose up -d --build
$ docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------------------------------------------------------------------------------------
elasticsearch /bin/tini -- /usr/local/bi ... Up 0.0.0.0:9200->9200/tcp,:::9200->9200/tcp, 0.0.0.0:9300->9300/tcp,:::9300->9300/tcp
fluentd tini -- /bin/entrypoint.sh ... Up 0.0.0.0:24224->24224/tcp,:::24224->24224/tcp, 0.0.0.0:24224->24224/udp,:::24224->24224/udp, 5140/tcp
kibana /bin/tini -- /usr/local/bi ... Up 0.0.0.0:5601->5601/tcp,:::5601->5601/tcp

Fluent Bit 설치 및 구성

로그를 수집하여 Fluentd에 전달하는 Fluent Bit를 GitLab이 설치된 서버에 설치합니다.
EFK Stack 설치 시와 마찬가지로 Timezone을 설정하고 Chrony 설치하여 시간을 동기화합니다.

Fluent Bit 설치

키링(keyring)에 Fluent Bit 서버의 GPG key를 추가합니다. 그러면 서명된 패키지를 얻을 수 있습니다.

$ wget -qO - https://packages.fluentbit.io/fluentbit.key | sudo apt-key add -
OK

APT 서버 엔트리를 소스 목록에 추가합니다.

sudo vi /etc/apt/sources.list

/etc/apt/sources.list 파일의 맨 하단에 아래 내용을 추가합니다.

deb https://packages.fluentbit.io/ubuntu/focal focal main

APT 패키지 인덱스를 업데이트합니다.

sudo apt-get update

아래 명령을 실행하여 최신 td-agent-bit를 설치합니다.

sudo apt-get install td-agent-bit

td-agent-bit 서비스를 시작합니다.

sudo service td-agent-bit start

서비스 상태를 확인합니다.

$ sudo service td-agent-bit status
● td-agent-bit.service - TD Agent Bit
Loaded: loaded (/lib/systemd/system/td-agent-bit.service; disabled; vendor preset: enabled)
Active: active (running) since Fri 2021-09-24 05:03:32 UTC; 49s ago
Main PID: 32547 (td-agent-bit)
Tasks: 3 (limit: 18973)
Memory: 2.4M
CGroup: /system.slice/td-agent-bit.service
└─32547 /opt/td-agent-bit/bin/td-agent-bit -c /etc/td-agent-bit/td-agent-bit.conf
...

Fluent Bit 구성

GitLab에서 Git 명령을 수행한 프로토콜(Protocol)에 따라 기록되는 로그 파일이 다릅니다. HTTP/HTTPS는 /var/log/gitlab/gitlab-rails/production_json.log 파일에, SSH를 통해 수행한 경우는 /var/log/gitlab/gitlab-shell/gitlab-shell.log 파일에 기록됩니다.

  • HTTP/HTTPS
    • Clone/Pull : action 필드의 값이 git_upload_pack
    • Push : action 필드의 값이 git_receive_pack
  • SSH
    • Clone/Pull : command 필드 git-upload-pack
    • Push : command 필드의 값이 git-receive-pack

GitLab의 Git 관련 로그를 수집하고 필터링하여 Fluentd에 전달하도록 구성합니다.

JSON Parser의 Time_Format를 GitLab 로그에 맞게 수정합니다.

cd /etc/td-agent-bit/
sudo vi parsers.conf

아래 내용으로 수정합니다.

...

[PARSER]
Name json
Format json
Time_Key time
#Time_Format %d/%b/%Y:%H:%M:%S %z
Time_Format %Y-%m-%dT%H:%M:%S %Z
Time_Strict off

...

GitLab의 로그 파일 중 Git 관련 로그만 수집하도록 td-agent-bit.conf 파일을 편집하여 구성합니다.

sudo cp td-agent-bit.conf td-agent-bit_orgin.conf
sudo vi td-agent-bit.conf

아래와 같이 작성하고 저장합니다.

[SERVICE]
flush 5
daemon Off
log_level debug
parsers_file parsers.conf
plugins_file plugins.conf
http_server Off
http_listen 0.0.0.0
http_port 2020

[INPUT]
Name tail
Path /var/log/gitlab/gitlab-rails/production_json.log
Parser json
Tag gitlab.gitlab-rails.*

[INPUT]
Name tail
Path /var/log/gitlab/gitlab-shell/gitlab-shell.log
Parser json
Tag gitlab.gitlab-shell.*

[FILTER]
Name grep
Match gitlab.gitlab-rails.*
Regex action git

[FILTER]
Name grep
Match gitlab.gitlab-shell.*
Regex command git

[FILTER]
Name modify
Match gitlab.gitlab-rails.*
Condition Key_value_matches action git
Copy action a_action
Copy meta.project a_project

[FILTER]
Name modify
Match gitlab.gitlab-shell.*
Rename user_id git_user_id
Copy command a_action
Copy gl_project_path a_project

[OUTPUT]
Name forward
Match *
Host <EFK-IP-or-Domain>
Port 24224

OUTPUT > Host : EFK Stack의 실제 IP 또는 도메인으로 수정해야 합니다.
Git 명령 이외의 로그도 수집하려면, INPUT, FILTER를 추가로 구성합니다.

td-agent-bit 서비스를 재시작합니다.

sudo service td-agent-bit restart

시스템 부팅 시 자동 재시작하도록 설정합니다.

sudo systemctl enable td-agent-bit.service

Dashboard 구성

웹 브라우저에서 http://<EFK-IP-or-Domain>:5601로 접속한 후, 좌측 상단의 햄버거 아이콘을 클릭하면 메뉴 패널이 나타납니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

Index Templates

Elasticsearch는 고가용성과 필요에 따라 확장할 수 있도록 하나 이상의 노드(서버)로 구성된 클러스터(Cluster)로 실행됩니다. Elasticsearch는 데이터를 JSON 형식의 문서로 저장하는데, 비슷한 특성을 가진 문서의 모음을 색인(인덱스, Index)라고 합니다. 색인은 방대한 양의 데이터를 저장할 수 있는데, 이 데이터가 단일 노드의 하드웨어 한도를 초과할 수도 있고 단일 노드에서 검색 요청 처리 시 속도가 너무 느려질 수 있습니다.

Elasticsearch는 이러한 문제를 해결하고자 색인을 이른바 **샤드(shard)**라는 조각으로 분할하여 저장합니다. 또한, 특정 샤드/노드에 문제가 생겼을 경우에 대비하여 페일오버 메커니즘(Failover Mechanism)이 있으며, 색인의 샤드에 대해 하나 이상의 복사본을 생성할 수 있는데, 이를 리플리카 샤드(Replica shard), 줄여서 **리플리카(Replica)**라고 합니다. Elasticsearch 7.0 이상 버전에서 Shard와 Replica의 기본값은 1입니다.

Stack Management > Data > Index Management 메뉴의 Indices 탭에서 gitlab-2021.09.28, gitlab-2021.09.29, gitlab-2021.09.30 등 인덱스 목록을 볼 수 있는데, 인덱스의 Health가 yellow 상태일 것입니다. 이는 Elasticsearch 클러스터가 단일 노드로 구성되어 있어 Shard와 Replica를 분산하여 저장할 다른 노드가 없기 때문입니다.
인덱스 중 하나를 클릭하고 Settings 탭을 확인하면 number_of_shardsnumber_of_replicas의 값이 1인 것을 알 수 있습니다. Edit settings 탭에서 number_of_replicas의 값을 0으로 수정하면 Health가 green 상태로 변경되는 것을 확인할 수 있습니다.

위와 같이 인덱스를 일일이 수정하지 않고, Index Template을 생성하여 인덱스가 사용할 Mappings, Settings을 설정해 두면, 인덱스가 생성될 때 자동으로 적용되게 할 수 있습니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

Indices

Index Template을 생성하고 Indices 탭으로 이동하면 모든 인덱스의 Health가 green 상태로 변경된 것을 확인할 수 있습니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

Index Patterns

Stack Management > Kibana > Index Patterns로 이동하여 Create index pattern 버튼을 클릭합니다.
Name에 gitlab-*을 입력하고 Timestamp field에 @timestamp을 선택한 후, Create index pattern 버튼을 클릭하여 Index Pattern을 생성합니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

Discover

햄버거 아이콘을 클릭하고 Analytics > Discover 메뉴로 이동합니다.
Time range를 설정하고 KQL(Kibana Query Language)Filter를 이용하여 로그를 검색할 수 있습니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

Visualize Library

Analytics > Visualize Library 메뉴로 이동하여 Create visualization 버튼을 클릭합니다.
New visualization 모달 창에서 Aggregation based의 Explore options 링크 클릭한 후, Data table, Metric, Pie, Vertical bar 등 Kibana에서 제공되는 다양한 Visualization 기능을 이용하여 로그 데이터를 시각화합니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

Git Activity Dashboard

Analytics > Dashboard 메뉴로 이동하여 Create dashboard 버튼을 클릭합니다.
Add from library 버튼을 클릭하고, 앞 단계에서 생성한 Visualize Library들을 선택하여 추가한 후, 배치하고 패널 사이즈를 조정합니다.

photo | 인포그랩 GitLab | 인포그랩 GitLab

맺음말

지금까지 EFK 스택을 구축하고 Fluent Bit로 GitLab 로그를 수집하여, Elasticsearch에 색인을 저장하고 설정한 후, 로그 데이터를 가시화하여 Git Activity 대시보드를 구성하는 방법을 알아보았습니다.

인포그랩은 GitLab 및 DevOps에 대한 맞춤 기술 지원을 제공합니다. GitLab과 대시보드 구축 관련하여 지원이 필요하시면 문의하기 로 연락 주십시오.