주의!
이 글은 스타트업에 다닐 적, elk stack을 통하여 구축하라는 명령 하달로 인하여 급하게 준비한 내용이다.
금액적 측면에 대한 제한과.. 시간의 제한이 있었으므로 단순히 이런 생각을 했구나~! 라고 보고 넘기면 되겠다.
정해진 조건 아래에 구성을 한다면 다음과 같은 그림이 될 수 있을 것 같다.
우선, 위와 같이 구성하게 될 경우 다음과 같은 이슈가 존재한다.
서버 2가 다운되면 결국은 말짱꽝 아냐 ?
정확히 맞는 말이다. 하지만, 앞서 설명한 전제 조건을 기준으로 작성하다 보니 이런식으로 구성이 되었다.
만일 여러개의 인스턴스가 구성이 가능하다면 다음과 같이 구성했지 않았을까 싶다.
1. 앞단에 별도 인스턴스로써 HAProxy를 띄운다(혹은 ELB사용).
2. Shipper 용 Logstash 클러스터링 그룹을 생성한다.
3. Redis / Kafka 그룹을 생성한다.
4. 뒷단의 Indexer용 Logstash 클러스터링 그룹을 생성한다.
5. Elastic(master / data) 그룹을 생성한다.
6. Kibana 용 단일 인스턴스 생성
위와 같이 구성한다면, 최소 8개 이상의 인스턴스가 필요하다. 3개의 인스턴스 환경에서는 어떤 관점으로 접근하냐에 따라 다른 구성이 가능하다. 바로 '안전성 측면'으로 구성할 것인가 혹은 '성능적 측면'에서 구성할 것인가? 이다.
다중화 작업을 하기 위해서는 HAProxy와 Kibana는 별도 인스턴스 위에 동작해야 하며, 나머지 부분에 대해서 다중화를 진행해야한다. 이렇게 되면 인스턴스 3개 중에 2개가 이미 사용되버리고 만다.. 즉, 어차피 안고가야 하는 것이라면 인입점에서의 유실을 최소화 하는 데 집중하자는 것이 핵심이였다.
자, 그러면 하나씩 어떤 애플리케이션이며 왜 사용해야 하는지를 알아보자.
HAProxy
HAProxy는 기존의 하드웨어 스위치를 대체하는 소프트웨어 로드 밸런서로, 네트워크 스위치에서 제공하는 L4, L7 기능 및 로드 밸런서 기능을 제공한다.
HAProxy는 기본적으로 reverse proxy 형태로 동작한다. 우리가 브라우저에서 사용하는 proxy는 클라이언트 앞에서 처리하는 기능으로, forward proxy라 한다. reverse proxy의 역할을 간단히 설명하면, 실제 서버 요청에 대해서 서버 앞 단에 존재하면서, 서버로 들어오는 요청을 대신 받아서 서버에 전달하고 요청한 곳에 그 결과를 다시 전달하는 것이다.
왜쓸까?
HAProxy의 큰 장점 중 하나는 상대적으로 저렴한 리눅스 서버에 L4, L7 을 한번에 쉽게 구축할 수 있다는 것이다. 앞서 언급한 Nginx 같은 웹서버의 로드 밸런서는 HTTP 프로토콜에 대한 분산처리만을 해준다. 물론 HTTP 또는 HTTPS 기반의 웹서비스만 제공하는 경우라면 웹서버 로드밸런서가 탁월한 성능을 보여주기 때문에 최선의 선택일 수도 있다. 하지만 웹서버 외에 SMTP, DBMS 등을 비롯하여 소켓 통신을 하는 모든 경우에 로드밸런싱을 하고자 한다면 HAProxy가 합리적인 선택일 수 있다. HAProxy는 이를 위해서 HTTP와 TCP 두 가지 모드를 제공한다.
HAProxy의 동작 방식을 알아보고 HAProxy를 이용해서 어떤 구조로 확장할 수 있는지 알아보겠다.
HAProxy의 동작 흐름
최초 접근 시 서버에 요청 전달
응답 시 쿠키(cookie)에 서버 정보 추가 후 반환
재요청 시 proxy에서 쿠키 정보 확인 > 최초 요청 서버로 전달
다시 접근 시 쿠키 추가 없이 전달 > 클라이언트에 쿠키 정보가 계속 존재함(쿠키 재사용)
HAProxy HA(High availability) 구성
HAProxy는 기본적으로 VRRP(Virtual Router Redundancy Protocol: 여러 대의 라우터를 그룹으로 묶어 하나의 가상 IP 주소를 부여하고, 마스터로 지정된 라우터의 장애시 VRRP 그룹 내의 백업 라우터가 마스터로 자동 전환되는 프로토콜)를 지원한다. HAProxy의 성능상 초당 8만 건 정도의 연결을 처리해도 크게 무리가 없지만, 소프트웨어 기반의 솔루션이기 때문에 HAProxy가 설치된 서버에서 문제가 발생하면 하드웨어 L4보다는 불안정할 수 있다. 따라서 HA 구성으로 master HAProxy에 문제가 생기는 경우에도 slave HAProxy에서 서비스가 원활하게 제공될 수 있는 구성을 알아보겠다.
다음 그림과 같은 구성에서는 가상 IP 주소를 공유하는 active HAProxy 서버와 standby HAProxy 서버가 heartbeat를 주고 받으면서 서로 정상적으로 동작하는지 여부를 확인한다. active 상태의 서버에 문제가 발생하면 standby HAProxy가 active 상태로 변경되면서 기존 active HAProxy의 가상 IP 주소를 가져오면서 서비스가 무정지 상태를 유지한다. 다만 1초 정도의 순단 현상은 발생할 수 있다.
HAProxy 옵션
다음은 전역 옵션(global) 섹션과 기본 옵션(defaults) 섹션, 프록시 옵션 섹션(listen)의 주요 옵션에 관한 설명이다.
global # 전역 옵션 섹션
daemon: 백그라운드 모드(background mode)로 실행
log: syslog 설정
log-send-hostname: hostname 설정
uid: 프로세스의 userid를 number로 변경
user: 프로세스의 userid를 name으로 변경
node: 두 개 이상의 프로세스나 서버가 같은 IP 주소를 공유할 때 name 설정(HA 설정)
maxconn: 프로세스당 최대 연결 개수
Defaults # 기본 옵션 섹션
log: syslog 설정
maxconn: 프로세스당 최대 연결 개수
listen webfarm 10.101.22.76:80 : haproxy name ip:port
mode http: 연결 프로토콜
option httpchk: health check
option log-health-checks: health 로그 남김 여부
option forwardfor: 클라이언트 정보 전달
option httpclose: keep-alive 문제 발생 시 off 옵션
cookie SERVERID rewrite: 쿠키로 서버 구별 시 사용 여부
cookie JSESSIONID prefix: HA 구성 시 prefix 이후에 서버 정보 주입 여부
balance roundrobin: 순환 분배 방식
stats enable: 서버 상태 보기 가능 여부
stats uri /admin: 서버 상태 보기
uri server xvadm01.ncli 10.101.22.18:80 cookie admin_portal_1 check inter 1000 rise 2 fall 5: real server 정보
(server [host명] [ip]:[port] cookie [서버쿠키명] check inter [주기(m/s)] rise [서버구동여부점검횟수], fall [서비스중단여부점검횟수])
balance 옵션
로드 밸런싱의 경우 round robin 방식을 일반적으로 사용하지만 다른 여러 방식이 있다. 옵션에 적용할 수 있는 로드 밸런싱 알고리즘은 다음과 같다.
roundrobin: 순차적으로 분배(최대 연결 가능 서버 4128개), 즉 시간 단위로 균일하게 balancing
static-rr: 서버에 부여된 가중치에 따라서 로드 벨런싱
leastconn: 접속 수가 가장 적은 서버로 분배
source: 운영 중인 서버의 가중치를 나눠서 접속자 IP를 해싱(hashing)해서 분배
uri: 접속하는 URI를 해싱해서 운영 중인 서버의 가중치를 나눠서 분배(URI의 길이 또는 depth로 해싱)
url_param: HTTP GET 요청에 대해서 특정 패턴이 있는지 여부 확인 후 조건에 맞는 서버로 분배(조건 없는 경우 round robin으로 처리)
hdr: HTTP 헤더 에서 hdr()으로 지정된 조건이 있는 경우에 대해서만 분배(조건 없는 경우 round robin으로 처리)
rdp-cookie: TCP 요청에 대한 RDP 쿠키에 따른 분배
설정한 옵션
Logstash
Logstash는 자바 기반의 실시간 파이프라인 기능을 가진 오픈소스 데이터 수집 엔진입니다. Logstash는 서로 다른 소스(http, tcp, redis 등)의 데이터를 탄력적으로 통합하고 사용자가 선택한 목적지로 데이터를 정규화할 수 있습니다.
어떤 유형의 이벤트도 다양한 입력, 필터, 출력 플러그인을 통해 강화하고 전환할 수 있으며 기본 제공되는 여러 코덱으로 수집(ingestion) 프로세스를 한층 더 간소화할 수 있습니다.
다양한 플러그인(input, filter, output) 을 제공하고 있는 것이 최대의 장점.
input, output 은 필수파라미터, filter 는 옵션
input 은 데이터소스에서 가져오는 플러그인
filter는 해당 데이터를 원하는 대로 변경하는 플러그인
output 은 Data Destination 으로 변경된 데이터를 쓰는 플러그인
grok filter plugin
인입되는 데이터의 패턴을 찾아서 특정 필드들로 맵핑 변환
kafka에게 Producer 입장이 되어 logstash라는 토픽으로 메세징 큐를 쌓도록 한다. 형식은 json.
Kafka
카프카는 비동기 처리를 위한 메시징 큐의 한 종류이며, 프로듀서와 컨슈머가 있다. 대표적인 비동기 메시징 시스템인 메일과 비교하면 이해가 쉽다. 프로듀서는 카프카로 메시지를 보내게 되고, 해당 메시지는 카프카에 저장되어 보관중이게 된다. 그리고 컨슈머는 카프카에 저장되어 있는 메시지를 필요로 할때, 가져갈 수 있다.
주키퍼는 카프카의 노드 관리를 해주고, 토픽의 offset 정보 등을 저장하기 위해 필요하다. 주키퍼는 과반수 투표방식으로 결정하기 때문에 홀수로 구성해야 하고, 과반수 이상 살아 있으면 정상으로 동작한다.
ex) 주키퍼 3대일 경우 2대 이상, 주피커 5대일 경우 3대 이상
카프카에서 토픽은 데이터베이스의 table정도의 개념으로 생각하시면 될 것 같다. 카프카에 저장되는 데이터를 토픽이라는 이름으로 구분하기 위해서 사용한다. replica 2 or 3은 2개 or 3개로 복제할것인지를 의미하고, partition은 토픽을 몇개로 나눌지를 의미한다.
왜쓸까?
기존의 메시징 시스템에서는 broker가 consumer에게 메시지를 push해 주는 방식인데 반해, Kafka는 consumer가 broker로부터 직접 메시지를 가지고 가는 pull 방식으로 동작한다. 따라서 consumer는 자신의 처리능력만큼의 메시지만 broker로부터 가져오기 때문에 최적의 성능을 낼 수 있다. 기존의 push 방식의 메시징 시스템에서는 broker가 직접 각 consumer가 어떤 메시지를 처리해야 하는지 계산하고 어떤 메시지를 처리 중인지 트랙킹하였는데, Kafka에서는 consumer가 직접 필요한 메시지를 broker로부터 pull하므로 broker의 consumer와 메시지 관리에 대한 부담이 경감되었다. 즉, 메시지를 pull 방식으로 가져오므로 메시지를 쌓아두었다가 주기적으로 처리하는 batch consumer의 구현이 가능하다.
대용량의 실시간 로그 처리에 특화되어 설계된 메시징 시스템으로써 기존 범용 메시징 시스템대비 TPS가 매우 우수하다. 단, 특화된 시스템이기 때문에 범용 메시징 시스템에서 제공하는 다양한 기능들은 제공되지 않는다.
분산 시스템을 기본으로 설계되었기 때문에, 기존 메시징 시스템에 비해 분산 및 복제 구성을 손쉽게 할 수 있다.
Producer가 broker에게 다수의 메시지를 전송할 때 각 메시지를 개별적으로 전송해야하는 기존 메시징 시스템과는 달리, 다수의 메시지를 batch형태로 broker에게 한 번에 전달할 수 있어 TCP/IP 라운드트립 횟수를 줄일 수 있다.
메시지를 기본적으로 메모리에 저장하는 기존 메시징 시스템과는 달리 메시지를 파일 시스템에 저장한다.
Kafka의 분산처리
Zookeeper 클러스터와 Kafka를 별도로 구축하는 것을 추천. 또한, Kafka에서는 Zookeeper에 쓰기 연산이 대량으로 발생하므로 다른 서비스에서 사용하고 있는 Zookeeper 클러스터를 공유해서 쓰는것 또한 권장하지 않는다. 정리하자면 Kafka를 위한 Zookeeper 클러스터를 별도의 장비에 구축하고, 해당 Zookeeper 클러스터는 Kafka만 사용하도록 하는 것을 권장한다.
Kafka는 실제 설정은 따로 하지 않았다. 별도로 띄우기만 했을 뿐.
docker-compose 를 통해 띄웠으므로, 위와 같이 설정되어있다.
Elasticsearch
Elasticsearch는 분산형 RESTful 검색 및 분석 엔진이다. 분산형이기 때문에 데이터 증가에 따라 유연하게 확장할 수 있고, RESTful API를 제공하기 때문에 손쉽게 색인, 검색, 분석이 가능하다는 장점이 있다. 오늘날 많은 기업 및 개인이 다양한 검색, 로깅, 분석 서비스에 Elasticsearch를 활용하고 있다. 사용자 층이 두터운 만큼 관련 커뮤니티가 활발히 운영되고 있고 유스케이스 또한 풍부하여 다양한 상황에서 응용 가능하다.
ElasticSearch 는 루씬이 가지고 있는 검색 기능에 더해 다음과 같은 특징을 가지고 있다.
실시간으로 document의 각 필드를 검색 가능하게 인덱싱하고 분산하여 저장
실시간 분석이 가능한 분산형 검색 엔진
수백개의 서버에 수평적으로 쉽게 확장 가능하고 정형이나 비정형의 펩타데이터들을 다룰 수 있음
또한 서버에 쉽게 설치하여 standalone으로 사용 가능하며 RESTful api를 통해 손 쉽게 사용 가능하다.
ES 관련 설정
메모리 관련해서, ES 의 권고사항으로는 16~32G 메모리로 주로 해당 서버의 50%가 적당하다고 한다.
bootstrap.mlockall: true # jvm의 스왑 방지
echo 1048575 > /proc/sys/vm/max_map_count # 특정 프로세스가 소유할 수 있는 가상 메모리 영역 설정
sudo sysctl -w fs.file-max=262114 # 리눅스에서 전체 시스템이 가질 수 있는 최대 파일 개수 제한. ES가 파일로 쓸테니..
노드 구성
마스터 노드는 인덱스의 생성, 삭제 / 클러스터의 노드들 추적, 관리 / 데이터 입력 시 어느 샤드에 할당할지 결정의 역할을 한다. 따라서 마스터 노드를 안정적으로 관리하는 것이 전체 클러스터의 상태를 안정적으로 관리할 수 있는 중요한 요소가 된다. 공식 홈페이지에 보면 마스터 노드의 경우 데이터 인덱싱, 검색 등에 의해 CPU, Mem, I/O 리소스 소모가 클 수 있기 때문에 부하를 받지 않도록 master와 data node를 구분하여 운영하길 권장하고있다. 또한, 클라이언트 노드 등에서 마스터 노드로 인덱싱, 검색 요청 등을 라우팅 할 경우 마스터 노드에 부담이 될 수 있기 때문에 가능한 아무런 일을 시키지 않고 마스터 역할에만 집중할 수 있도록 설정하는 것이 좋다. 공식 홈페이지에서는 최소 3개 이상의 마스터 노드를 추천하며, 3개의 마스터 노드를 구성한 뒤, 데이터 노드만 늘리는 방식으로 진행하길 추천한다.
실제로 4대 미만의 환경이라면, 분리하는 것 보다는 마스터/데이터를 합치는 것이 더 안전성 측면에서 좋다고 한다.
Docker
Docker ?
도커는 컨테이너 기반의 오픈소스 가상화 플랫폼입니다.
컨테이너는 격리된 공간에서 프로세스가 동작하는 기술입니다. 가상화 기술의 하나지만 기존방식과는 차이가 있습니다.
기존의 가상화 방식은 주로 OS를 가상화하였습니다.
우리에게 익숙한 VMware나 VirtualBox같은 가상머신은 호스트 OS위에 게스트 OS 전체를 가상화하여 사용하는 방식입니다. 이 방식은 여러가지 OS를 가상화(리눅스에서 윈도우를 돌린다던가) 할 수 있고 비교적 사용법이 간단하지만 무겁고 느려서 운영환경에선 사용할 수 없었습니다.
이러한 상황을 개선하기 위해 CPU의 가상화 기술(HVM)을 이용한 KVMKernel-based Virtual Machine과 반가상화 Paravirtualization방식의 Xen이 등장합니다. 이러한 방식은 게스트 OS가 필요하긴 하지만 전체OS를 가상화하는 방식이 아니였기 때문에 호스트형 가상화 방식에 비해 성능이 향상되었습니다. 이러한 기술들은 OpenStack이나 AWS, Rackspace같은 클라우드 서비스에서 가상 컴퓨팅 기술의 기반이 되었습니다.
Docker-compose ?
복수개의 컨테이너가 하나의 애플리케이션으로 구동되는 경우 이 애플리케이션을 테스트하기 위해서는 역할별로 각각 하나의 컨테이너를 생성해야 한다.
예를 들어 규모가 있는 웹 애플리케이션일 경우에는 Web 서버 컨테이너, WAS 서버 컨테이너, DB 서버 컨테이너등을 생성해야 한다.
이 애플리케이션을 구동하기 위해서는 “docker run”명령어를 여러번 사용하여 컨테이너를 구동하고 개별적인 컨테이너가 정상적으로 동작하는지 확인하는 단계가 필요하다.
그러나 매번 이러한 단계등을 위하여 run명령어에 옵션을 설정하여 CLI(Command Line Interface)로 컨테이너를 생성하는 것은 비효율적이다.
하나의 서비스를 위해서 여러개의 컨테이너를 개별 서비스로 정의하여 컨테이너의 묶음으로 관리하게 된다면 무척 편리할 것이고, 이러한 개념으로 실제 구현된 것이 도커 컴포즈(Docker Compose)인 것이다.
도커 컴포즈(Docker Compose)는 컨테이너를 이용한 서비스의 개발과 CI(Continous Intergration)를 위하여 여러개의 컨테이너를 하나의 프로젝트로 다룰수 있는 작업환경을 제공한다.