Canvas 1 Layer 1

그라파이트와 그라파나로 메트릭스 모니터링 시스템 구축하기

들어가며: 메트릭스 수집 도구 그라파이트와 대시보드

시스템 모니터링에 대해서 리뷰하거나, 직접 시스템 모니터링을 해봤다면 아래 그림과 같은 rrdtool로 만들어진 그래프를 자주 만나게 될 것입니다. rrdtool은 여전히 시스템 모니터링에 있어서 강자입니다만, 이 세계에 단지 rrdtool만 있는 것은 아닙니다. 특히 시계열 데이터 수집에 최적화된 타임시리즈 데이터베이스의 일종인 그라파이트Graphite는 시스템 메트릭스 수집에 있어 매력적인 도구 중 하나입니다.

rrdtool로 생성한 시스템 메트릭스 시각화
rrdtool로 생성한 시스템 메트릭스 시각화

그라파이트는 기본적으로 특정한 네임스페이스에 시간과 데이터를 계속해서 쌓아가는 특수한 데이터 저장소입니다. 이것만으로는 그래프까지 그려주는 rrdtool에 비해서 그다지 메리트가 없어보입니다. 하지만 그라파이트 프로젝트 중에는 그라파이트-웹Graphite-Web이라는 API 형태로 그래프 파일을 제공하거나, 수치 데이터를 제공해주는 모듈이 있습니다. 기본적인 그래프 생성기가 그렇게 훌륭하진 않습니다만 수치 데이터를 제공하는 API가 있어서 이를 기반으로 다양한 오픈소스 대시보드 어플리케이션들이 개발되고 있습니다. 사용자는 그라파이트에 데이터를 쌓아놓기만 하면, 자신의 취향에 맞는 대시보드를 선택해서 커스터마이징해나가면 됩니다.

이 글에서 소개할 대시보드는 그라파나Grafana입니다. 엘라스틱서치ElasticSearch의 대시보드 툴로 유명한 키바나Kibana 라는 프로젝트가 있습니다만, 그라파나는 이 키바나에서 영감을 받아 만들어진 그라파이트 판 키바나라고 이해하시면 좀 더 쉽습니다.

이 글에서는 그라파이트에 대한 전반적인 소개에 걸쳐 그라파이트를 구성하는 하나하나의 요소들을 시작으로 그라파나까지 도커를 사용해 모니터링 시스템 전체를 구축해보도록하겠습니다.

Tl;dr

$ docker run --name whisper nacyot/whisper
$ docker run -d -p 2003:2003 -p 2004:2004 -p 7002:7002 --volumes-from whisper -e NODE_NAME=cache nacyot/carbon-cache
$ docker run -d -p 8000:80 -e CARBONLINK_HOSTS="172.17.42.1:7002" --volumes-from whisper nacyot/graphite-web
$ docker run -d -p 9200:9200 -p 9300:9300 dockerfile/elasticsearch
$ docker run -d -p 8001:8000 nacyot/grafana
그라파나(Grafana)의 기본 대시보드
그라파나(Grafana)의 기본 대시보드

그라파나, 참 쉽죠?

그라파이트(Graphite): 메트릭스 수집 도구

앞서 그라파이트Graphite를 단순히 시계열 데이터 저장소라고 소개했습니다만, 이를 사용하기 위해서는 기본적으로 그라파이트의 각 구성 요소가 어떻게 이루어지는 지를 이해할 필요가 있습니다.

그라파이트의 아키텍처
그라파이트의 아키텍처

먼저 콜렉터collector는 그라파이트에 어떠한 데이터를 쌓기 위한 모듈입니다. 시계열 데이터베이스의 특성상 기본적으로 데이터가 저장될 네임스페이스와 시간, 데이터 이렇게 3가지 데이터가 필요합니다. 이 정보를 카본-캐시carbon-cache에 보냅니다.

카본-캐시는 콜렉터가 보낸 데이터를 받아 위스퍼Whisper에 저장합니다. 카본-캐시가 데이터 수집기라면 위스퍼는 실제로 데이터를 파일시스템에 저장하고 읽어오는 모듈입니다. 자 이제 위스퍼를 통해 데이터가 파일 시스템에 저장되었습니다.

그렇다면 이 데이터를 어떻게 가져올 수 있을까요? 이 시점에서 등장하는 게 그라파이트-웹입니다. 그라파이트-웹은 HTTP 프로토콜을 통해서 위스퍼에 저장된 데이터를 읽어와 이미지 파일이나, 데이터 형식으로 출력합니다. 그라파이트-웹은 기본적으로 데이터를 제공하는 API와 대시보드 기능 두 가지를 제공하고 있습니다. 여기서 제공하는 대시보드 기능을 그냥 사용해도 무방합니다만, 기본적으로 그렇게 편리하지는 않습니다.

그라파이트 프로젝트에 직접적으로 속한 프로젝트는 아닙니다만, 이 그라파이트-웹에서 대시보드를 제외하고 API 기능만을 따로 구현해둔 그라파이트-APIGraphite-API라는 모듈도 있습니다. 다른 대시보드를 사용한다면 그라파이트-웹이나 그라파이트-API 어느 툴을 사용해도 무방합니다.

이렇게 나눠서 보면 별로 복잡하지 않습니다만, 이 관계를 모르고 무턱대고 그라파이트를 사용해보려고 하면 새로운 용어들의 홍수에 현기증을 느낄지도 모릅니다. 그라파이트 하나 설치하면 그런 아름다운 마법은 없습니다. 간단해 보이지만 이 기본 구성을 이해해두는 건 그라파이트를 사용하는 데 큰 도움이 될 것입니다.

그라파이트(Graphite) 시작하기

기본적인 그라파이트 구성에 대해서 살펴보았으니 이제 실제로 설치해보도록 하겠습니다. 이 글에서는 도커Docker를 기반으로 진행해나갑니다. 도커만 사용가능하다면 어떤 OS라도 크게 상관은 없습니다.

데이터 저장소 위스퍼(Whisper)

우선은 데이터가 저장될 위스퍼부터 시작하겠습니다. 아래 명령어를 실행합니다.

$ docker pull nacyot/whisper
$ docker run --name whisper nacyot/whisper

nacyot/whisper Dockerfile

사실 nacyot/whisper 이미지에는 아무것도 없습니다. Dockerfile을 열어보면 아래와 같이 실행되는 명령어는 전혀 없습니다.

FROM busybox
MAINTAINER Daekwon Kim <propellerheaven@gmail.com>

VOLUME /opt/graphite/storage/whisper
CMD ["/bin/sh"]

자세히 보면 busybox라는 이미지를 기반으로 단지 /opt/graphite/storage/whisper 디렉토리를 마운트 시키는 역할을 하고 있습니다. 정말 이것뿐입니다. 이 도커 컨테이너는 볼륨 컨테이너로 사용됩니다. 볼륨 컨테이너에 대한 자세한 내용은 부록 A를 참고해주세요.

메트릭스 기록 모듈 carbon-cache

다음은 카본Carbon입니다. 실질적으로 데이터를 수집해서 파일 시스템에 기록하는 모듈이 바로 카본-캐시carbon-cache입니다.

$ docker pull nacyot/carbon-cache
$ docker run -d -p 2003:2003 -p 2004:2004 -p 7002:7002 --volumes-from whisper -e NODE_NAME=cache nacyot/carbon-cache

이것도 간단하죠? 이제 바로 데이터 수집이 가능합니다.

nacyot/carbon-base Dockerfile

먼저 carbon-cache 이미지를 살펴보기 전에 carbon-cache 이미지의 베이스가 되는 carbon-base를 살펴보겠습니다. carbon-base는 실제 모듈이 아니라 카본-캐시와 카본-릴레이를 위해 만들어진 중간 이미지입니다.

FROM nacyot/ubuntu
MAINTAINER Daekwon Kim <propellerheaven@gmail.com>

RUN apt-get -y update
RUN apt-get -y install python python-twisted

WORKDIR /usr/local/src

RUN git clone https://github.com/graphite-project/carbon.git
RUN git clone https://github.com/graphite-project/whisper.git
RUN cd whisper && git checkout master && python setup.py install
RUN cd carbon && git checkout 0.9.12 && python setup.py install

carbon-base 이미지에도 특별한 건 없습니다. 파이썬Python을 설치하고 카본Carbon과 위스퍼를 설치해줍니다.

nacyot/carbon-cache Dockerfile

다음으로 carbon-cache의 Dockerfile을 살펴보도록 하겠습니다.

FROM nacyot/carbon-base
MAINTAINER Daekwon Kim <propellerheaven@gmail.com>

ENV NODE_NAME cache
ENV LOCAL_DATA_DIR /opt/graphite/storage/whisper/
ENV MAX_CACHE_SIZE inf
ENV MAX_UPDATES_PER_SECOND 1000
ENV MAX_CREATES_PER_MINUTE inf
ENV LOG_UPDATES False
ENV LINE_RECEIVER_INTERFACE 0.0.0.0
ENV PICKLE_RECEIVER_INTERFACE 0.0.0.0
ENV CACHE_QUERY_INTERFACE 0.0.0.0
ENV LINE_RECEIVER_PORT 2003
ENV PICKLE_RECEIVER_PORT 2004
ENV CACHE_QUERY_PORT 7002

ADD ./config /opt/graphite/conf/
ADD ./setup_configs.sh /opt/graphite/setup_configs.sh
ADD ./run.sh /opt/graphite/run.sh
EXPOSE 2003 2004 7002

WORKDIR /opt/graphite
CMD ./setup_configs.sh && ./run.sh

사실 카본은 이미 carbon-base에 설치가 되었기 때문에 여기서 무언가를 설치하거나 실행하는 부분은 없습니다. 여기서는 우선 ENV 지시자를 통해서 기본 환경변수 값들을 설정합니다. 이 환경변수들은 carbon.conf 파일에서 사용됩니다.

다음으로 /config 디렉토리를 이미지 내의 카본 설정 디렉토리로 복사합니다. 이 디렉토리에는 carbon.confstorage-aggregation.conf, storage-schemas.conf 파일이 포함되어있습니다. 따로 만든 설정 파일을 사용한다면 이미지 실행시 -v 옵션으로 설정 파일이 포함된 폴더를 컨테이너에 마운트시키면 됩니다.

그리고 setup_configs.shrun.sh를 이미지 내부로 복사합니다. 2003, 2004, 7002 포트를 열어주고 WORKDIR을 설정해주고, 초기 명령어(CMD)를 설정합니다. 여기서부터는 설정파일과 복사한 셸스크립트의 용도에 대해서 간략히 설명합니다.

carbon.conf

먼저 carbon.conf를 살펴보겠습니다. 이 파일은 데이터를 수집하는 카본 데몬에 관한 설정을 담고 있습니다.

[cache]
LOCAL_DATA_DIR =
MAX_CACHE_SIZE =
MAX_UPDATES_PER_SECOND =
MAX_CREATES_PER_MINUTE =
LOG_UPDATES =

LINE_RECEIVER_INTERFACE =
PICKLE_RECEIVER_INTERFACE =
CACHE_QUERY_INTERFACE =
LINE_RECEIVER_PORT =
PICKLE_RECEIVER_PORT =
CACHE_QUERY_PORT = 

여기서는 기본적으로 사용하는 설정들을 나열해놓았습니다. 아무런 값도 들어가 있지않습니다. 이렇게도 실행하는 것은 불가능합니다. setup_configs.sh 셸스크립트를 사용해 이 설정 파일 템플릿에 환경변수 값을 자동적으로 입력합니다. 이 셸스크립트에 대한 자세한 내용은 부록 B를 참고해주세요.

각 설정에 대한 자세한 사항은 Graphite 문서를 참조하시기바랍니다.

카본(Carbon)에서 사용하는 3개의 포트 : 2003, 2004, 7002

위에서 EXPOSE 지시자를 통해서 2003, 2004, 7002, 이렇게 3개의 포트를 열었습니다. 데이터 수집 데몬이라고 했는데, 은근히 포트가 많죠. 각각의 포트가 어떤 용도로 사용되는 지 정도는 알아둘 필요가 있습니다.

2003과 2004는 데이터를 받아서 저장하는 포트입니다. 먼저 2003은 플레인텍스트 프로토콜plaintext protocol을 사용해 데이터를 받아들이며, 설정에서는 LINE_RECEIVER로 표현합니다. 한 줄 한 줄 아래와 같은 포맷을 사용합니다.

<metric path> <metric value> <metric timestamp>

실제로는 아래와 같습니다.

hello.graphite 3.5 1405608517

다음으로 2004는 피클 프로토콜pickle protocol을 사용합니다. 설정에서는 PICKLE_RECEIVER라고 표현합니다. 아래와 같은 형식을 사용합니다.

[(path, (timestamp, value)), ...]

하나의 네임스페이스에 대해서 다량의 정보를 수집할 때 유용합니다. 대개는 라이브러리나 이미 만들어져있는 수집기를 통해서 메트릭을 수집하기 때문에 프로토콜까지 이해하고 직접 작성할 일은 별로 없습니다. 포트의 용도 정도만 이해하셔도 충분하다고 생각합니다.

마지막으로 7002는 쿼리 포트입니다. 이 포트는 그라파이트-웹에서 사용합니다. 그런데 사실 그라파이트-웹은 위스퍼 데이터베이스(파일)에서 직접 데이터를 읽어옵니다. 그렇다면 조금 의문이 들 지도 모릅니다. 이 카본 데몬에 쿼리를 하는 건 어떤 용도로 쓰일까요? 사실 카본에서 받은 데이터는 위스퍼 데이터베이스에 실시간으로 저장되지 않습니다. 카본에서 받은 데이터는 기본적으로 메모리에 저장되고 위스퍼 라이브러리에 의해서 적절한 시점에 파일로 저장됩니다. 카본-캐시는 최근에 들어온 아직 파일에 쓰여지지 않은 데이터를 쿼리하는데 사용됩니다.

이것으로 3가지 포트의 수수께끼는 풀렸습니다.

storage-aggregation.conf와 storage-schemas.conf

이외에도 카본에는 데이터를 저장할 위스퍼 데이터베이스의 저장방식을 지정하기 위한 설정 파일들이 있습니다.

위스퍼 라이브러리는 데이터값을 받는 모든 네임스페이스에 대해서 해당하는 네임스페이스의 데이터가 저장되는 .wsp 파일을 생성합니다. storage-schemas.conf는 패턴을 통해서 특정한 패턴에 해당하는 네임스페이스의 데이터베이스를 만드는 구조를 정의합니다.

[carbon]
pattern = ^carbon\..*
retentions = 1m:31d,10m:1y,1h:5y

[highres]
pattern = ^highres.*
retentions = 1s:1d,1m:7d

[statsd]
pattern = ^statsd.*
retentions = 1m:7d,10m:1y

[default]
pattern = .*
retentions = 10s:1d,1m:7d,10m:1y

여기서 retentions 설정값이 15s:7d,1m:21d,15m:5y이면 해당하는 네임스페이스의 데이터는 15초 간격의 데이터는 7일, 1분 간격의 데이터는 21일, 15분 간격의 데이터는 5년 치를 저장하게 됩니다. 좀 더 구체적으로 설명하면 데이터를 저장하고 8일이 지나면 이 데이터는 1분 간격으로 저장되고, 1분보다 짧은 간격의 데이터들은 소실됩니다.

그런데 짧은 간격의 데이터들이 손실되면서 변환된다고 한다면 여기에는 분명 어떠한 조작이 일어날 것입니다. 이 때 데이터의 조작방법 정의하는 설정파일이 바로 storage-aggregation.conf입니다.

[min]
pattern = \.min$
xFilesFactor = 0.1
aggregationMethod = min

[max]
pattern = \.max$
xFilesFactor = 0.1
aggregationMethod = max

[sum]
pattern = \.count$
xFilesFactor = 0
aggregationMethod = sum

[default_average]
pattern = .*
xFilesFactor = 0.5
aggregationMethod = average

이 설정도 마찬가지로 특정한 패턴에 해당하는 네임스페이스에 대해서 적용됩니다. 기본은 average 통해서 데이터가 집계됩니다. 쉽게 말해 15초 간격의 데이터가 10 20 10 20 이었다면 1분 간격의 데이터는 15가 됩니다. 단 여기서 굉장히 주의해야할 부분이 하나 있습니다. 15초 간격의 데이터인데, 실제로는 데이터가 이 사이에 하나밖에 없다고 해보죠. 위스퍼에서는 시간과 데이터만 있으면 어떠한 데이터도 저장이 되기 때문이 이런 종류의 제약은 걸려있지 않습니다만, 이 데이터를 average로 집계하면 데이터가 소실되어 버립니다. 이렇게 데이터가 소실되어 버리면 실제로는 데이터가 존재하는 데도 API에서 호출될 때는 사라지는 현상이 발생하게됩니다. 이러한 상황을 방지하기 위해서는 데이터를 간격에 맞춰(혹은 더 자주) 충실히 넣어주거나, average 대신 다른 집계 방법을 사용할 필요가 있습니다. 이는 그라파이트를 처음 사용할 때 빠지기 쉬운 함정이므로 알아두면 좋습니다.

참고로 패턴 적용은 위에서부터 이루어진다는데 유의가 필요합니다. 즉 이 설정 파일들에서는 순서가 중요합니다. 예를 들어 [default]의 패턴 .*이 모든 네임스페이스에 들어맞기 때문에 이후에 어떤 설정을 넣어도 적용되지 않을 것입니다.

더 자세한 내용은 Graphite 문서를 참조해주시기바랍니다.

그라파이트-웹(Graphite-Web)

이것으로 무사히 carbon-cache까지 실행했습니다. 이걸로 데이터를 받을 준비는 끝났습니다. 다음은 저장된 데이터를 읽어오는 그라파이트-웹 모듈의 차례입니다.

$ docker pull naycot/graphite-web
$ docker run -d -p 8000:8000 -e CARBONLINK_HOSTS="172.17.42.1:7002" --volumes-from whisper nacyot/graphite-web

역시나 도커를 사용하면 간단합니다.

nacyot/graphite-web Dockrefile

이 도커파일에는 꽤나 많은 명령어들이 늘어져있습니다만, 자세히 보면 파이썬Python과 장고Django 관련 패키지 설치, 관련 폴더/파일 생성, 필요한 ENV 지정, 그라파이트-웹을 설치하는 과정에 불과합니다. 내용만 길 뿐이지, 읽어나가는 데 큰 어려움은 없을 것이라 생각합니다.

CMD ["/usr/bin/supervisord", "-n"]

마지막 CMD 지시자를 통해서 supervisordgunicorn_djangonginx를 사용해 graphite-web을 실행합니다.

supervisord.conf는 아래와 같이 구성됩니다.

[supervisord]
nodaemon = true

[program:nginx]
command = /usr/sbin/nginx
stdout_logfile = /var/log/supervisor/%(program_name)s.log
stderr_logfile = /var/log/supervisor/%(program_name)s.log
autorestart = true

[program:graphite-webapp]
directory = /opt/graphite/webapp
command = /usr/bin/gunicorn_django -b0.0.0.0:8000 -w2 graphite/settings.py
stdout_logfile = /var/log/supervisor/%(program_name)s.log
stderr_logfile = /var/log/supervisor/%(program_name)s.log
autorestart = true

그라파이트-웹 어플리케이션은 구니콘gunicorn을 통해서 실행시키고, 엔진xNginx의 프록시 기능을 통해서 외부에 노출시킵니다. nginx.conf 일부를 살펴보면 아래와 같이 프록시하고 있는 걸 알 수 있습니다.

proxy_pass                 http://127.0.0.1:8000;
proxy_set_header           X-Real-IP   $remote_addr;
proxy_set_header           X-Forwarded-For  $proxy_add_x_forwarded_for;
proxy_set_header           X-Forwarded-Proto  $scheme;
proxy_set_header           X-Forwarded-Server  $host;
proxy_set_header           X-Forwarded-Host  $host;
proxy_set_header           Host  $host;

이외에도 엔진xnginx를 사용하는 이유는 한 가지가 더 있습니다. 바로 외부 호스트에서 호출시 CORS를 사용하기 위함입니다.

add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, OPTIONS";
add_header Access-Control-Allow-Headers "origin, authorization, accept";

참고로 그라파이트-웹는 단순히 데이터를 전달하는 용도 뿐만 아니라, 웹사이트 기능을 포함하고 있어서 자체 정보를 저장하는 데이터베이스를 사용합니다. 이 데이터베이스에는 사이트 설정 및 관리자 정보 같은 것들이 저장됩니다. 여기서는 편의상 SQLite을 사용해서 컨테이너 내부에서만 사용합니다. 이 예제에서는 그라파이트-웹 대시보드를 사용하지 않습니다. 그파파나Grafana를 사용할 것이므로 별로 중요하지 않습니다. *

* 참고로 기본 관리자 계정은 admin/admin 입니다

local_settings.py에는 그라파이트-웹 설정이 들어갑니다만, 실제 파이썬 코드를 사용하므로 바로 환경변수를 사용할 수 있습니다. 앞에서 사용한 setup_configs.sh처럼 쉘 스크립트를 사용할 필요가 없습니다. 파이썬 코드를 바로 사용해서 환경변수를 통해 설정을 적용할 수 있습니다.

import os

LOG_DIR = '/var/log/graphite'
if os.getenv("CARBONLINK_HOSTS"):
    CARBONLINK_HOSTS = os.getenv("CARBONLINK_HOSTS").split(',')

if os.getenv("CLUSTER_SERVERS"):
    CLUSTER_SERVERS = os.getenv("CLUSTER_SERVERS").split(',')

if os.getenv("MEMCACHE_HOSTS"):
    CLUSTER_SERVERS = os.getenv("MEMCACHE_HOSTS").split(',')

여기서 중요한 옵션은 CARBONLINK_HOSTS입니다. 위에서 설명한 대로 카본에 직접 연결해서 아직 위스퍼에 저장되지 않은 데이터를 읽어오기 위한 용도입니다. 다시 앞선 그라파이트-웹 실행 명령어를 살펴봅니다.

$ docker run -d -p 8000:8000 -e CARBONLINK_HOSTS="172.17.42.1:7002" --volumes-from whisper nacyot/graphite-web

이를 통해 CARBONLINK_HOSTS를 통해 카본에서 직접 최신 데이터를 받아오며 --volumes-from을 통해서 위스퍼 데이터를 읽어오는 것을 알 수 있습니다.

그라파이트(Graphite) 정리

현재 상황을 확인해보겠습니다.

$ docker ps -a
CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS                      PORTS                                                                    NAMES
3acf95727292        nacyot/graphite-web:latest   /usr/bin/gunicorn_dj   10 minutes ago      Up 10 minutes               0.0.0.0:8000->8000/tcp                                                   thirsty_ritchie
5ec8670bef73        nacyot/carbon-cache:latest   /bin/sh -c './setup_   10 minutes ago      Up 10 minutes               0.0.0.0:2003->2003/tcp, 0.0.0.0:2004->2004/tcp, 0.0.0.0:7002->7002/tcp   tender_newton
48a012d30afb        nacyot/whisper:latest        /bin/sh                12 minutes ago      Exited (0) 12 minutes ago                                                                            whisper

앞서 설명했듯이 위스퍼는 실제로 실행중인 컨테이너가 아니므로 -a 옵션을 사용하지 않으면 보이지 않습니다. 카본-캐시를 사용해서 위스퍼에 데이터를 저장하고 그라파이트-웹에서 저장된 데이터와 카본-캐시에서 최신 데이터를 가져오도록 완벽히 세팅되었습니다.

여기까지 잘 따라오셨다면 http://0.0.0.0:8000에 접속해봅니다. 몇 번인가 언급했다시피 그라파이트-웹은 API 서버이자, 대시보드 어플리케이션 역할을 합니다.

Graphite-Web 대시보드
Graphite-Web 대시보드

오오, 뭔가 나오네요! 아직 어떠한 데이터를 직접 넣지는 않았습니다만, 카본은 기본적으로 데몬으로부터 간단한 데이터를 수집합니다.

그라파나(Grafana)

그라파나Grafana는 그라파이트Graphite나 인플럭스DBinfluxDB를 백엔드로 사용하는 대시보드 툴입니다. 그라파이트를 기반으로 하는 대시보다 툴은 더 많이 있습니다만, 최근에는 그라파나를 많이 사용합니다.

그라파나(Grafana) 실행하기

먼저 그라파나를 실행시키기 위해서는 엘라스틱서치가 필요합니다. 여기서 엘라스틱서치는 데이터 저장이 아닌 환경설정을 저장하기 위해 사용됩니다.

$ docker pull dockerfile/elasticsearch
$ docker run -d -p 9200:9200 -p 9300:9300 dockerfile/elasticsearch

다음 명령어로 그라파나를 실행합니다.

$ docker pull nacyot/grafana
$ docker run -d -p 8001:8000 nacyot/grafana

Dockerfile

그라파나는 앵귤러JSAngularJS를 기반으로 만들어진 웹어플리케이션으로 자바스크립트 프로젝트입니다. 따라서 레일스나 장고와 같은 서버 계층을 가지고 있지 않습니다. 설치과정을 살펴보면 그라파나 어플리케이션을 가져와, 적절한 위치에 설치하고 아파치Apache를 기반으로 실행합니다.

FROM centos:centos6
MAINTAINER Daekwon Kim <propellerheaven@gmail.com>

RUN rpm -iUvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
RUN yum update -y
RUN yum -y install httpd git
RUN cd /opt; git clone https://github.com/grafana/grafana.git
RUN cd /opt/grafana; git checkout $(git describe --tags `git rev-list --tags --max-count=1`)

RUN rm -f /etc/localtime
RUN cp -p /usr/share/zoneinfo/Japan /etc/localtime

RUN mkdir -p /opt/grafana/src/config

ADD ./config.js /opt/grafana/src/config.js
ADD ./grafana.conf /etc/httpd/conf.d/grafana.conf
ADD ./setup_configs.sh /opt/grafana/setup_configs.sh
ADD ./run.sh /opt/grafana/run.sh

ENV ES_API_HOST 172.17.42.1
ENV ES_API_PORT 9200
ENV GRAPHITE_API_HOST 172.17.42.1
ENV GRAPHITE_API_PORT 8000

WORKDIR /opt/grafana

EXPOSE 8000
CMD ./setup_configs.sh && ./run.sh

일단 다른 부분들은 대체로 비슷한데, 로컬 타임 설정하는 부분이 있습니다. 로컬 타임을 사용하지 않으면 도커 컨테이너에서는 UTC 시간이 사용됩니다.

$ docker run -i -t ubuntu bash
root@1f27c50b400d:/# date
Fri Jul 18 01:05:48 UTC 2014

config.js와 setup_configs.js

다음으로 config.js를 살펴봅니다.

define(['settings'],
       function (Settings) {
           "use strict";
           
           return new Settings({
               elasticsearch: "ES_API_HOST:ES_API_PORT",
               datasources: {
                 graphite: {
                   type: 'graphite',
                   url: 'GRAPHITE_API_HOST:GRAPHITE_API_PORT',
                   default: true,
                   render_method: 'GET'
                 }
               },
               default_route: '/dashboard/file/default.json',
               timezoneOffset: null,
               grafana_index: "grafana-dash",
               panel_names: [
                   'text',
                   'graphite'
               ]
           });
       });

앞서 설명했듯이 여기서 지정하는 엘라스틱서치 서버는 단순히 환경설정을 저장하기 위한 용도로 사용됩니다. 데이터는 그라파이트에서 가져옵니다. 환경 변수에 저장된 서버 정보는 config.js에서 치환됩니다. 이는 카본-캐시 에서 사용했던 것과 같은 기법입니다. setup_configs.conf를 살펴보죠.

#!/bin/bash

[ -f /opt/grafana/src/config/config.js ] && cp /opt/grafana/src/config/config.js /opt/grafana/src/config.js

sed -i -e "s/ES_API_HOST/${ES_API_HOST}/g" ./src/config.js
sed -i -e "s/ES_API_PORT/${ES_API_PORT}/g" ./src/config.js
sed -i -e "s/GRAPHITE_API_HOST/${GRAPHITE_API_HOST}/g" ./src/config.js
sed -i -e "s/GRAPHITE_API_PORT/${GRAPHITE_API_PORT}/g" ./src/config.js

우선 별도의 설정 파일을 마운트했을 경우 해당하는 config.js를 우선적으로 사용할 수 있도록 했습니다. 기본적으로 이 이미지는 엘라스틱서치와 그라파이트 서버에 연결할 것을 전제하고 있습니다만, 그라파이트는 데이터 백엔드로 여러 서버를 지정할 수도 있고 인플럭스DB를 지정할 수 있습니다. 물론 다른 설정들도 필요한 경우 수정할 수 있어야하니까요. 설정에 관한 자세한 사항 그라파나 문서를 참조해주세요.

나머지 부분은 서버 설정을 치환하는 부분입니다.

grafana.conf와 run.sh

아파치 설정입니다. 그라파나는 자체적인 웹 서버를 가지지 않으므로 엔진x나 아파치를 사용해서 실행해야합니다. 특별한 내용은 없습니다.

Listen 80
<VirtualHost *:80>
    ServerName grafana
    DocumentRoot "/opt/grafana/src"
</VirtualHost>

run.sh는 아파치를 실제로 실행시키는 파일입니다. -DFOREGROUND 옵션은 아파치를 데몬이 아니라 포그라운드에서 띄워줍니다.

#!/bin/bash

/usr/sbin/httpd -d . -f /etc/httpd/conf/httpd.conf -e info -DFOREGROUND

그라파나(Grafana) 사용하기

네, 여기까지 그라파나 실행 및 내부적으로 어떻게 실행하는 지에 대해서 설명했습니다. 앞서 실행시에 8001번 포트로 내부를 연결했습니다. 웹브라우저로 0.0.0.0:8001에 접속해주세요.

그라파나 - 랜덤워크 그래프
그라파나 - 랜덤워크 그래프

첫 페이지에 생성되는 데이터는 랜덤워크로 생성된 시계열 그래프입니다.

앞서 그라파이트-웹에서 본 그래프는 그라파나에서는 아래와 같이 보입니다.

그라파나 - 카본 데몬이 수집한 데이터)
그라파나 - 카본 데몬이 수집한 데이터)

여기까지 그라파나가 정상적으로 동작하는 것을 확인해보았습니다.

소스코드

이 글에서 다룬 Dockerfile의 최신 코드는 위 저장소에 있습니다.

각각의 이미지는 도커 허브Docker Hub에서 확인할 수 있습니다.

결론

먼 길을 돌아왔습니다. 하지만 시작할 때 이야기한 대로 실행하는 것은 간단합니다.

$ docker run --name whisper nacyot/whisper
$ docker run -d -p 2003:2003 -p 2004:2004 -p 7002:7002 --volumes-from whisper -e NODE_NAME=cache nacyot/carbon-cache
$ docker run -d -p 8000:80 -e CARBONLINK_HOSTS="172.17.42.1:7002" --volumes-from whisper nacyot/graphite-web
$ docker run -d -p 9200:9200 -p 9300:9300 dockerfile/elasticsearch
$ docker run -d -p 8001:8000 nacyot/grafana

실제로 사용하시고자 할 때는 커스터마이징이 필요합니다. 테스트 용도로 사용하는 동안에는 이 이미지들을 바로 사용해도 큰 문제는 없을 것입니다. 도커Docker를 사용하면 위에서 설명한 모든 내용이 이 명령어 5개로 압축됩니다.

여기서 다룬 내용은 그라파이트 이야기는 시작에 불과합니다. 그라파이트 자체에 대해서도 이해해야할 주제들이 꽤 있습니다만, 그라파이트는 무엇보다도 생태계가 상당히 잘 갖춰져있는 오픈소스 모니터링 툴입니다. Collected, Diamond, Metricsd, Sensu 같은 툴을 통해서 관리중인 모든 서버의 수치 데이터를 수집할 수도 있고, 부하가 커질 경우 위에서 말한 것처럼 스케일 아웃도 가능하고 Statsd를 사용해 버퍼 서버로 사용할 수도 있습니다. 또한 여기서는 그라파나만을 다뤘지만 앞서 언급한대로 다양한 대시보드 툴을 사용해 자신만의 대시보드를 만들 수도 있습니다. 나아가 Cabot 같은 툴을 이용해 수치를 감시하다가 특정 조건에 의해 경고를 보낼 수도 있습니다.

부록 A: 볼륨 컨테이너의 이해

도커의 볼륨 컨테이너라는 개념을 이해하고 계신다면 바로 이해하실 수 있겠지만, 별안간 등장한 busybox라는 이미지가 낯설어보일지도 모릅니다. 잠깐 비지박스BusyBox의 정체를 짚고 넘어가죠.

$ docker images busybox
REPOSITORY          TAG                   IMAGE ID            CREATED             VIRTUAL SIZE
busybox             latest                a9eb17255234        6 weeks ago         2.433 MB

images를 명령어를 실행해보면 busybox 이미지가 놀라울 정도로 작은 이미지라는 것을 알 수 있습니다. 비지박스의 정체는 경량 임베디드 리눅스 배포판의 일종입니다. 실제로 naycot/whisper가 하는 역할은 /opt/graphite/storage/whisper라는 디렉토리를 마운트해놓고, 관련된 모듈이 여기에 데이터를 저장하거나 읽어들이기 위한 역할만을 하는 정말로 데이터만을 위한 이미지입니다.

$ docker run --name whisper nacyot/whisper

눈치가 빠르신 분들은 이미 알아채셨겠지만, 이 docker run 명령어는 심상치 않습니다. 보통 셸을 사용하는 경우처럼 컨테이너에 직접 접속하고자 할 때는 -it 옵션을 사용하고, 반대로 백그라운드에서 실행시킬 때는 -d 옵션을 사용하는데, 여기에는 아무런 옵션이 보이질 않습니다. 실제로 이 명령어를 통해서 컨테이너는 생성되지만 실행되진 않습니다. 이렇게 데이터만을 위한 이미지는, 데이터만을 위한 컨테이너로 탈바꿈합니다.

볼륨 컨테이너를 사용하지 않아도 어플리케이션을 사용하는 데는 아무런 문제가 없습니다만, 볼륨 컨테이너를 사용하면 좋은 점이 있습니다. 먼저 AUFS와 같은 도커 파일 시스템 자체에 데이터를 기록하는 일은 성능 면에서 손해가 많은 편입니다. 다른 파일 시스템을 사용하는 방법도 있기는 합니다만 볼륨 기능을 사용해 특정한 디렉토리를 마운트 시키면 호스트와 같은 파일 시스템으로 데이터가 기록됩니다. 이를 통해 성능 손실을 막을 수 있습니다.

또한 볼륨 컨테이너를 사용하면, 이를 사용하는 프로세스의 실행 종료와 완전히 무관하게 볼륨 컨테이너가 삭제되지 않는 한 해당하는 컨테이너에 마운트된 데이터도 사라지지 않도록 영속성을 보장할 수 있게 해줍니다. 이를 통해 부가적으로 프로세스와 데이터의 논리적 분리를 통해서 좀 더 깔끔한 관리가 가능하게 해주며, 특히 다수의 프로세스에서 데이터를 공유할 때 특정 프로세스 컨테이너나 특정 호스트의 폴더에 의존하지 않는 구조를 만들 수 있게 해줍니다.

앞서 실행한 볼륨 컨테이너를 확인해보죠. 컨테이너는 실행중인 상태가 아니므로 -a 옵션을 통해서 확인합니다.

$ docker ps -al
CONTAINER ID        IMAGE                        COMMAND                CREATED             STATUS                           PORTS
45b4afcb4be2        nacyot/whisper:latest        /bin/sh                About an hour ago   Exited (0) About an hour ago                                                                              whisper

뭔가 아무것도 없는데 설명이 길었습니다만, 이걸로 데이터를 저장할 준비가 되었습니다.

부록 B: setutp_configs.sh

프로그래밍 코드를 사용하는 경우 환경변수를 쉽게 가져올 수 있습니다만, 설정 파일을 사용하는 프로그램의 경우 환경변수 값을 가져오는 것이 쉽지 않습니다. 이 때 환경변수를 설정 파일에 입력해주는 스크립트가 바로 setup_configs.sh입니다. 이 스크립트는 도커 컨테이너를 처음 실행할 때 실행됩니다.

#!/bin/bash

sed -i -e "s/\[cache\]/\[${NODE_NAME}\]/g" ./conf/carbon.conf
sed -i -e "s/\(LOCAL_DATA_DIR\).*$/\1 = $(printf "${LOCAL_DATA_DIR}" | sed -e 's/\//\\\//g')/g" ./conf/carbon.conf
sed -i -e "s/\(MAX_CACHE_SIZE\).*$/\1 = ${MAX_CACHE_SIZE}/g" ./conf/carbon.conf
sed -i -e "s/\(MAX_UPDATES_PER_SECOND\).*$/\1 = ${MAX_UPDATES_PER_SECOND}/g" ./conf/carbon.conf
sed -i -e "s/\(MAX_CREATES_PER_MINUTE\).*$/\1 = ${MAX_CREATES_PER_MINUTE}/g" ./conf/carbon.conf
sed -i -e "s/\(LOG_UPDATES\).*$/\1 = ${LOG_UPDATES}/g" ./conf/carbon.conf
sed -i -e "s/\(LINE_RECEIVER_INTERFACE\).*$/\1 = ${LINE_RECEIVER_INTERFACE}/g" ./conf/carbon.conf
sed -i -e "s/\(PICKLE_RECEIVER_INTERFACE\).*$/\1 = ${PICKLE_RECEIVER_INTERFACE}/g" ./conf/carbon.conf
sed -i -e "s/\(CACHE_QUERY_INTERFACE\).*$/\1 = ${CACHE_QUERY_INTERFACE}/g" ./conf/carbon.conf
sed -i -e "s/\(LINE_RECEIVER_PORT\).*$/\1 = ${LINE_RECEIVER_PORT}/g" ./conf/carbon.conf
sed -i -e "s/\(PICKLE_RECEIVER_PORT\).*$/\1 = ${PICKLE_RECEIVER_PORT}/g" ./conf/carbon.conf
sed -i -e "s/\(CACHE_QUERY_PORT\).*$/\1 = ${CACHE_QUERY_PORT}/g" ./conf/carbon.conf

sed 사용하는 단순한 스크립트이므로 설명은 생략하겠습니다. 이를 통해서 기본적인 설정들에 대해서는 실행시에 동적으로 환경 변수를 지정해 사용할 수 있습니다. 환경변수를 덮어쓰지 않으면 위에서 살펴본 카본-캐시 DockerfileENV 값들이 사용됩니다.