---
title: "EFK Stack으로 GitLab 로그 대시보드 구축"
description: "."
url: https://insight.infograb.net/blog/2021/10/25/gitlab-log-dashboard-with-efk-stack/
source: content/blog/archive/2021-10-25-gitlab-log-dashboard-with-efk-stack.md
---

import Image from "../src/components/Image"

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

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

![](/img/blog/2021-10-25-git-activity-dashboard.webp)

## EFK Stack이란?

**EFK 스택**은 [Elasticsearch](https://www.elastic.co/kr/elasticsearch/), [Fluentd](https://www.fluentd.org/architecture), [Kibana](https://www.elastic.co/kr/kibana/)로 구성된 스택을 의미하는 약어입니다. 이 세 가지 도구를 결합하여 확장 가능하고 유연하며 사용하기 쉬운 로그 수집 및 분석 파이프라인을 구축할 수 있습니다.

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

> 로그 수집기로 Fluentd 대신 Logstash를 사용하여 구성하면 **ELK Stack**이라고 합니다.  
> 두 로그 수집기 중 어느 것이 더 좋은지 판단하기 어려우며, 서로 장단점이 있습니다. 주어진 요구사항과 환경에 따라 더 적합한 것을 선택하여 사용하거나, 때에 따라서 동시에 사용할 수도 있습니다.  
> Logstash와 Fluentd를 비교한 아랫글들을 참고하십시오.
>
> - [Logstash vs. Fluentd, a drag race](https://medium.com/flowfactor/logstash-vs-fluentd-a-drag-race-d4df351ef3f6)
> - [Logstash vs Fluentd — Which one is better !](https://www.techmanyu.com/logstash-vs-fluentd-which-one-is-better-adaaba45021b)

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

자체 관리형 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 메뉴에서 이러한 로그 파일의 내용을 확인할 수 있습니다.

## 아키텍처

![](/img/blog/2021-10-25-logging-dashboard-architecture.webp)

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 Engine](https://docs.docker.com/engine/install/ubuntu/) 및 [Docker Compose](https://docs.docker.com/compose/install/)를 설치하고, Docker Compose를 이용하여 EFK(Elasticsearch + Fluentd + Kibana) Stack을 설치합니다.

### Timezone 설정

```bash
$ 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를 설치합니다.

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

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

```bash
$ 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)를 생성합니다.

```bash
cd /data
mkdir efk-stack
cd efk-stack
```

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

```bash
vi docker-compose.yml
```

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

```bash
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` 파일을 생성합니다.

```bash
mkdir fluentd
cd fluentd
vi Dockerfile
```

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

```bash
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에 저장하도록 구성합니다.

```bash
mkdir conf
cd conf
vi fluent.conf
```

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

```bash
<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)용 디렉토리를 생성합니다.

```bash
cd ../..
mkdir elasticsearch_data
```

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

```bash
$ 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를 추가합니다. 그러면 서명된 패키지를 얻을 수 있습니다.

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

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

```bash
sudo vi /etc/apt/sources.list
```

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

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

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

```bash
sudo apt-get update
```

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

```bash
sudo apt-get install td-agent-bit
```

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

```bash
sudo service td-agent-bit start
```

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

```bash
$ 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 로그에 맞게 수정합니다.

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

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

```bash
...

[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` 파일을 편집하여 구성합니다.

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

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

```bash
[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` 서비스를 재시작합니다.

```bash
sudo service td-agent-bit restart
```

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

```bash
sudo systemctl enable td-agent-bit.service
```

## Dashboard 구성

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

![](/img/blog/2021-10-25-elastic-home.webp)

### 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_shards`와 `number_of_replicas`의 값이 1인 것을 알 수 있습니다. Edit settings 탭에서 number_of_replicas의 값을 0으로 수정하면 Health가 `green` 상태로 변경되는 것을 확인할 수 있습니다.

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

![](/img/blog/2021-10-25-index-templates.webp)

### Indices

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

![](/img/blog/2021-10-25-indices.webp)

### Index Patterns

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

![](/img/blog/2021-10-25-index-patterns.webp)

### Discover

햄버거 아이콘을 클릭하고 Analytics > Discover 메뉴로 이동합니다.  
[Time range](https://www.elastic.co/guide/en/kibana/current/set-time-filter.html)를 설정하고 [KQL(Kibana Query Language)](https://www.elastic.co/guide/en/kibana/current/discover.html#search-in-discover)와 [Filter](https://www.elastic.co/guide/en/kibana/current/discover.html#filter-in-discover)를 이용하여 로그를 검색할 수 있습니다.

![](/img/blog/2021-10-25-discover.webp)

### Visualize Library

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

![](/img/blog/2021-10-25-visualize-library.webp)

### Git Activity Dashboard

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

![](/img/blog/2021-10-25-dashboard.webp)

## 맺음말

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

::cta::

::button::[GitLab 로그를 수집하고 관제하는 체계를 갖추고 싶으신가요? DevOps 전문가, 인포그랩과 상담하세요!](https://insight.infograb.net/contact/)::
