강좌

HOME > 강좌 >
강좌| 리눅스 및 오픈소스에 관련된 강좌를 보실 수 있습니다.
 
부하분산 클러스터 제작과 Fail Over
조회 : 4,586  


신순철 scshin@mail.suwon.ac.kr

     

    목차

    0. 들어가기에 앞서
    1. MEDUSA 클러스터
    2. 클러스터의 작동 방식
    3. Fail Over
    4. source
    5. MMD 프로젝트

 

0. 들어가기에 앞서

    대규모 웹서비스를 제공하기 위한 솔루션인 부하부산클러스터를 제작하고 이를 관리하기 위한 간단한 프로그램을 제작하였으며 본 문서에서는 이 방법을 설명하고자 한다. 클러스터는 현재 널리 알려진 방법인 LVS(LinuxVirtualServer)에서도 NAT 방식을 이용해서 제작하였으며, 리얼서버의 Fail Over 는 직접 작성한 MiniMMD 라는 프로그램에 의해 이루어지고 있으며 로드밸런서의 fail over는 아직 구현되지 않은 상태이다. 물론 ha-linux.org에서 제공하는 방법으로 또는 레드햇 패키지인 piranha 로 구현할 수 있지만 필자는 구현에 필요한 여러 개의 아이피가 없어서 보류해둔 상태이다. 그리고 이 방법은 완전한 fail over를 제공하지 못하는 단점과 노드의 낭비라는 단점이 있다.

    현재 매두사는 http://www.moo.pe.kr/ http://medusa.linuxchannel.net/ 에서 서비스되고 있다. 하지만 많은 것을 기대하지는 말라. 학교의 네트워크 성능과 매두사의 성능으로 인해 많은 경우 매우 느리게 뜬다. 물론 학교 안에서는 매우 빠르게 뜬다. 또한 학교 내에 있으므로 정전, 테스트 등의 이유로 종종 접속이 안되곤 한다.

    부하분산 클러스터 홈페이지  http://www.linuxvirtualserver.org/

    고가용성 솔류션  http://www.linuxvirtualserver.org/HighAvailability.html

 

1. MEDUSA 클러스터

    클러스터를 아래와 같은 사양으로 구성한 이유는 아래의 것들이 구할 수 있는 전부였기 때문이다.

    노드사양 : Pentium 150~120Mhz
                   Memory (routor 72M, real server 32~24M)
                   HDD (master 2G, slave no)
    kernel     : 2.2.14(nfs_patch,ipvs_patch)
    노드 수   : 4 nodes
    통신환경 : 10M Ethernet & Dummy Hub & 서브네트워크로 구성
    용도       : 부하분산 웹서버

 

2. 클러스터의 작동 방식

    1. 인터넷에서 페이지요청이 medusa에게 들어온다.
        인터넷에 있는 홍길동이라는 컴퓨터로부터 웹페이지 요청이 들어왔을 경우이다.
        다음은 패킷에 포함돼있는 정보이다 (패킷: source=홍길동, dest=medusa)
    2. 매두사는 요청패킷을 스케쥴링 알고리즘에 따라 선택된 리얼서버에게 전송한다.
        (패킷: source=홍길동, dest=리얼서버)
        이때 어떤 리얼서버가 선택될지는 모른다. 다만 관리자는 어떤 알고리즘에 의해서
        리얼서버를 선택하라는 것을 정해줄 수 있다.
    3. 리얼서버는 적당한 응답을 medusa 에게 보낸다.(패킷: source=리얼서버, dest=홍길동)
        홍길동이 요청한 페이지를 자신의 디폴트 게이트웨이인 매두사에게 보낸다.
    4. 매두사는 응답을 인터넷의 누군가에게 보내준다.(패킷: source=medusa, dest=홍길동)

    이러한 방법으로 처리를 함으로써 모든 요청을 로드밸런서(medusa)가 처리해주는 것처럼 보여진다. 보다시피 이렇게 패킷을 변경시키는 능력을 로드밸런서의 커널에 적재시켜야 하며 이것을 뒤에서 대충(^^)설명하겠다.

 

3. 제작 방법

    LVS에서 제공하는 방식은 크게 다음의 3가지가 있다.
    NAT(Network Address Transation)은 TCP/IP를 사용하므로 클러스터의 노드들이 어떤 OS인지 상관없이 부하분산을 해줄 수 있으며, 하나의 라우터는 보통 20개이하의 클러스터에서 라우터의 병목현상없이 서비스가 제공된다.

    IP Tunneling 방식은 서비스 요청이 들어오면 요청패킷을 캡슐화해서 클러스터내의 노드들에게 전송해준다. 이것은 응답이 라우터를 거쳐서 나가지 않으므로 하나의 라우터가 매우 많은 노드들을 거느릴 수가 있다. 그러나 단점은 클러스터내의 노드들이 캡슐화된 패킷을 해석할 수 있어야 하므로 현재는 리눅스에서만 가능하다. 또한 리얼서버들은 라우터를 게이트웨이로 사용하지 않으므로 다른 게이트웨가 필요하거나 공식IP를 가지고 있어야 한다

    DR(Direct Routing)방식은 클러스터의 확장성을 높이기 위해 IP NAT 방식과 IP tunneling방식의 장점만을 가져온 방식이다. 즉 서비스요청이 들어오면 패킷을 최소한으로 빨리 가공하여 리얼서버에게 보내면 리얼서버는 다른 루트를 통하여 응답을 보내는 것이다. 라우터가 요청패킷에 MAC 어드레스를 추가한다. 이것은 유일한 리얼서버를 결정할 수 있게 해준다. 이것을 위해서는 모든 노드들이 단일 segment에 존재해야 한다

    이 문서에서 설명하는 NAT의 경우는  node2, node3, node4 는 반드시 리눅스일 필요가 없다. NAT 방식은 TCP가 지원되는 OS이기만 하면 되기 때문이다. 즉 NT,window2000,OS/2 등이 모두 가능하며 설치 법은 일반 리눅스 박스와 거의 동일하다. 다만 리얼서버의 게이트웨이는 medusa 가 되어야 하며, 리얼서버들의 아이피는 내 마음대로 정해주어도 된다는 것이 다르다면 다른 것이다.

    우리는 medusa 라고 명명된 라우터(로드밸런서)로 사용될 머신을 적당히 튜닝하는 것으로써 클러스터를 구성할 수 있다. 이 글을 읽는 분이 커널패치와 컴파일 경험이 있다면 클러스터 제작은 매우 쉬우므로 이 문서에서는 설치에 대한 자세한 설명은 하지 않을 것이다. 그리고 커널패치와 컴파일 경험이 없는 분들은 커널 컴파일에 대한 약간의 공부를 하면 된다. (커널패치와 컴파일 역시 쉽다 ^^; 커널 패치는 일반적으로 cat patch-file | patch -p1 과 같은 명령으로 이루어지며 정확한 것은 제공되는 패치파일의 설명서에 제공될 것이다. 커널 컴파일을 하는데는 http://kldp.org/KoreanDoc/Kernel_Compile-KLDP 문서면 충분하다)

    일단 간단한 제작 순서는 http://www.linuxvirtualserver.org/software/index.html 에서 최신의 패치를 받아온 후 커널패치를 하고 적당히 컴파일한다 그리고 프롬프트 상에서 몇 개의 명령을 입력하는 것으로 모든 설치가 끝난다. 그리고 재 부팅시 설정이 지워지므로  /etc/rc.d/rc.local 또는 레드햇의 경우 /etc/rc.d/rc.local.mine 에 동일한 명령을 넣어주면 된다. 이것에 대한 설명은 http://www.linuxvirtualserver.org/VS-NAT.html 에 자세히 나와있다. 영문이 부담스럽다면 http://tunelinux.pe.kr/virtual/ 에 번역판이 있으며, kldp.org 에도 번역판이 있다.

 

4. Fail Over

    필자의 매두사 클러스터는 몇몇의 리얼서버가 다운됬을 때 심각한 문제가 발생한다. 예를 들어 node3 이 다운되었다면 node2로부터 제공되는 페이지들도 제대로 표시되지 않는 것을 본적이 있다. 어쨌든 안정적인 서비스를 위해서 fail over는 반드시 이루어져야 한다.

    fail over에는 두 가지가 있다 하나는 리얼서버의 fail over 와 하나는 로드밸런서의 fail over 이다. 리얼서버의 fail over는 간단한 문제이다. 하지만 로드밸런서의 fail over는 간단하지 않다. 본문서에서는 리얼서버의 fail over를 위한 간단한 프로그램 소스와 함께 설명을 할 것이다. 글고 로드밸런서의 fail over 는 '0. 들어가기에 앞서' 란에 있는 고가용성 솔루션을 참고 하기 바란다.

    리얼서버의 기본 아이디어는 주기적으로 리얼서버에게 웹페이지 요청을 하여 제대로 응답을 하는지를 체크하고 응답이 없으면 해당 리얼서버가 fail 되었음으로 앞으로 들어오는 요청을 그곳으로 전송(transation)하지 않도록 해주는 것이다. 이 것은 리얼서버가 fail 됬을 때 관리자가 콘솔상에서 해주는 행동과 일치하며 다만 이를 프로그램화 한 것뿐이다.

    예를 들어 wget http://node3/ 명령으로 node3 으로부터 웹페이지를 가져오려고 했으나 실패했다면 관리자는 ipvsadm -d -t medusa:80 -R node3:80 과 같은 명령으로 더 이상 메두사로 오는 요청을 node3 으로 보내지 못하도록 할 것이다.

    이러한 방법으로 전체 알고리즘을 설계해보면 아래와 같다.
     

    while(1) {
       for (i=0; i<node_numbers; I++) {
           state = IsAlive(node[i]);        // 노드가 살아있는지 검사
           if(before_stat[i] == state) continue;    // 노드 상태에 변화가 없으면
           if(state == TRUE)                // 노드가 죽었다가 살아났으면
              ServerOff( node[i] ); // ipvsadm -d -t medusa:80 -R node[i]:80 -m -w 1 실행
           else                     // 노드가 살았다가 죽었으면
              ServerOn( node[i] );  // ipvsadm -a -t medusa:80 -R node[1]:80 -m -w 1 실행
          before_state[i] = sta

    te;
       }
       sleep(delay_time);           // 몇 초마다 체크할 것인지
    }

 

5. source

    다음은 실제 리얼서버의 fail over를 지원하는 프로그램이다. C 언어에 능숙하다면 쉽게 이해할 수 있을 것이다. 그리고 그렇지 않다해도 메인부분은 쉽게 이해할 수 있을 것이며 메인 부분의 변경만으로 자신의 클러스터에 적용할 수 있다. 현재 프로그램은 3개의 리얼서버(node1, node3, node4)를 가지고 있는 경우이며 게으른 분은 굵은 글씨로 된 부분을 적당히 고쳐서 사용하면 된다.

    그리고 남의 프로그램을 분석하는 것은 소스가 작더라도 매우 재미없는 경우가 많다. 특히 필자의 허접한 코드에 짜증이 날 수도 있다. 이런 분들은 TcpConnect() 만 따서 직접 프로그램을 작성하는 것도 좋은 방법이다. 위의 알고리즘에서 IsAlive() 에 해당하는 루틴이다. 그리고 참고로 TcpConnect() 에서 TcpSend() 와 TcpRecv()를 호출하므로 3개의 루틴을 복사해서 사용하여야 한다.

     

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define DATALENG 1024
#define PORT                    80      /* 웹서비스 포트 */
#define SERVERNUM          3       /* 리얼서버의 숫자 */
#define INTERVAL 5                    /* 리얼서버 검사 주기(초 단위) */

int TcpSend(int sockfd,char *send_buf)
{   

    if (send(sockfd, send_buf, strlen(send_buf), 0) == -1)
    {   perror("TCP send");
        exit(1);
    }
    return 1;
}

int TcpRecv(int sockfd,char *recv_buf)
{
        int recv_num;

    if((recv_num=recv(sockfd,recv_buf,DATALENG,0))==-1)
    {   perror("TCP recv");
        exit(1);
    }
    return 1;
}

int TcpConnect(char *host,int port,char *send_buf, char *recv_buf)
{
    int sockfd;
    struct hostent *he;
    struct sockaddr_in their_addr;

    if ((he=gethostbyname(host)) == NULL) {  /* get the host info */
        herror("gethostbyname");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("TCP socket");
        exit(1);
    }

    their_addr.sin_family = AF_INET;
    their_addr.sin_port = htons(port);
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    bzero(&(their_addr.sin_zero), 8);

/* 리얼서버가 fail 됬으면 다음의 if 문에서 걸려서 리턴되게 된다 고칠 필요없다*/
    if (connect(sockfd, (struct sockaddr *)&their_addr,sizeof(struct sockaddr)) == -1) {
        close(sockfd);
        return 0;
    }

    TcpSend(sockfd,send_buf);
    TcpRecv(sockfd,recv_buf);
    close(sockfd);
    return 1;
}

/***** MAIN START ****/
int main()
{
    char send_buf[100], recv_buf[DATALENG];
    char OffCommand[SERVERNUM][100];
    char OnCommand[SERVERNUM][100];
    char addr[SERVERNUM][15];
    int ServerOn[SERVERNUM],temp,i;

/* 웹서버에게 페이지를 요청하는 문장이다
GET /index.html 은 요청하는 페이지의이름이다. 다른 이름과 경로로 바꾸어 적어주어도 무방하다.
User-Agent: 는 요청프로그램을 적어주는 부분으로 적당히 적어주면 된다.
필자의 경우 이 프로그램을 MMD 프로젝트의 코드에서 따온 것이므로 미니 MMD 라고 적었다.
Host: 부분은 요청하는 머신의 이름이다 일반적으로 로드밸선서의 별명을 적으면 된다*/
strcpy(send_buf,"GET /index.html HTTP/1.0\nUser-Agent: MiniMMD\nHost: medusa\nAccept: */*\n\n");

/* 리얼서버에 대한 정보를 하나씩 적어준다.
addr 은 리얼서버의 이름이며, OffCommand 는 리얼서버가 응답이 없을 때 수행해줄 명령이다.
이 명령은 210.123.54.230:80 으로 들어온 웹서비스 요청을 192.168.1.1:80 로 전송하지 않게 해준다.
OnCommand 는 리얼서버가 다시 살아나면 수행해줄 명령이다.*/
/* real server 192.168.1.1 */
strcpy(addr[0],"node1");
strcpy(OffCommand[0],"/sbin/ipvsadm -d -t 210.123.54.230:80 -R 192.168.1.1:80");
strcpy(OnCommand[0],"/sbin/ipvsadm -a -t 210.123.54.230:80 -R 192.168.1.1:80");
ServerOn[0]=1;

/* real server 192.168.1.3 */
strcpy(addr[1],"node3");
strcpy(OffCommand[1],"/sbin/ipvsadm -d -t 210.123.54.230:80 -R 192.168.1.3:80");
strcpy(OnCommand[1],"/sbin/ipvsadm -a -t 210.123.54.230:80 -R 192.168.1.3:80");
ServerOn[1]=1;

/* real server 192.168.1.4 */
strcpy(addr[2],"node4");
strcpy(OffCommand[2],"/sbin/ipvsadm -d -t 210.123.54.230:80 -R 192.168.1.4:80");
strcpy(OnCommand[2],"/sbin/ipvsadm -a -t 210.123.54.230:80 -R 192.168.1.4:80");
ServerOn[2]=1;

while(1) {
   /* 모든 리얼서버에 대해서 용청을 보내고 응답을 받기 위한 시도를 한다 */
   for(i=0; i<SERVERNUM ; i++) {
      printf("%s checking.... ",addr[i]);
      fflush(stdout);
      /* send_buf 의 내용(웹페이지 요청)을 주어진 주소(addr[i] port) 로 보내고 응답을
          recv_buf 에 받는다 */
      temp=TcpConnect(addr[i],PORT,send_buf,recv_buf);
      if(temp == 0) /* real server fail */
      {   printf(" Fail\n");
          fflush(stdout);
          if(ServerOn[i] == 1)
          {   ServerOn[i]=temp;
              /* 리얼서버가 죽었으면 더이상 리얼서버로 웹페이지 요청을 보내지 않도록 명령을
                  수행한다 */
              system(OffCommand[i]);
          }
      }
      else
      {   printf(" OK!!\n");
       fflush(stdout);
if(ServerOn[i] == 0)
{   ServerOn[i]=temp;
/* 리얼서버가 살아났으면 리얼서버로 웹페이지 요청을 다시 보내도록 명령을 수행한다 */
system(OnCommand[i]);
}
}
}       
/* 적당한 시간마다 리얼서버를 체크한다 */
sleep(INTERVAL);
}
return 0;
}


 

5. MMD 프로젝트

    개인적으로 클러스터의 여러 데몬의 모니터링과 fail over를 위한 프로그램을 제작하는 프로젝트이다. 아직은 시작단계이며, 초기 버전도 완성되지 않은 상태이다.
    현재 여러 데몬의 모니터링과 리얼서버의 fail over 가 구현되었으며, 라우터의 fail over 는 구현되지 않은 상태이다. 보다 자세한 설명은 http://www.moo.pe.kr/mmd/intro.htm에서 볼 수 있다. 또한 MiniMMD라는 이름으로 리얼서버의 fail over를 지원하는 프로그램을 소스와 함께 공개하였다.
    이 문서에서 설명한 방법과 같은 방법으로 작동한다. 다만 설정파일을 읽고, 해석하는 부분이 추가되었다. http://www.moo.pe.kr/mmd/mmd.tar.gz 에서 받을 수 있다.


[원글링크] : https://www.linux.co.kr/home2/board/subbs/board.php?bo_table=lecture&wr_id=793


이 글을 트위터로 보내기 이 글을 페이스북으로 보내기 이 글을 미투데이로 보내기

 
(주) 수퍼유저