DATA 전문가로 가는 길

[Perl] Expect를 이용한 서버 체크하는 방법 (cygwin용) 본문

Programming/Perl

[Perl] Expect를 이용한 서버 체크하는 방법 (cygwin용)

EstenPark 2009. 3. 29. 14:45

오늘은 제가 회사에서 사용하고 있는 모듈 하나를 설명 해드리겠습니다.
net:telnet라는 좋은 모듈도 있지만 ssh를 이용해서 서버로 접근 한 후 명령어를 치고 나오려면 아무래도 Expect가 더 효율적이라고 생각 됩니다.

http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod

자세한 세부적인 설명은 해당 사이트에 가시면 Expect의 사용법을 자세하게 알수 있습니다.

Windowns + Cygwin + Perl
제가 구성한 환경입니다. 각 서버에 존이 틀려서 서버서에 서버로 이동 하지 못해 개인 PC에 해당 환경을 구성 하였습니다.

expect는 다른 응용 어프리케이션과 상호대화(interactive) 할 수 있게 만든 모듈입니다.
사용 벙법은 그렇게 어렵지 않고 간단하게 프로그램을 구현 할 수 있습니다.

ecpect를 이용하면 다른 어플리케이션과 상호 대화를 할 수 있게 됨으로써 자동화된 프로그램을 만들수가 있으며, telnet를 예를 들면, expect를 이용해서 telnet와 상호작용하게 함으로써 자동로그인 하는 프로그램을 만들 수 있습니다.

간단한 예로 telnet 을 써서 어떤 호스트에 연결하면 로그인 하기 위해서 수동 "아이디"와 "패스워드"를 입력 해야 할 것입니다.
telnet 203.234.151.xxx 명령어를 실행하면 "login :"과 "password :"와 같은 표준 출력을 만나게 되고 해당 아이디와 패스워드를 입력해야만 접근이 가능 합니다.
이러한 수동으로 되는 작업을 자동화 시킬 수 있는게 expect라고 생각 하시면 됩니다.

우선 필요한 모듈을 설치 하셔야 겠죠?

소스 설치 :
터미널을 통해서 상호대화 하는 프로그램임으로 터미널 관련 모듈 :. IO-Tty-1.08.tar.gz
expectperl expect 모듈은 확장 모듈 : Expect-1.21.tar.gz

cpan 설치 : 
install IO::Tty;
install Expect;

그럼 이중에서 expect를 이용한 원격 자동 시스템 체크하는 방법을 구현 해보도록 하겠습니다.
 아이피 아이디  패스워드
 203.221.xxx.111 estenpark  passwd1
 203.221.xxx.112 mass  passwd2
 203.221.xxx.113  root  passwd3
기본 구성은 위와 같이 총 3대의 서버가 있습니다.
문제는 아이피도 다르고 아이디/패스워드 모두 다릅니다.

그럼 처음부터 2차원 배열을 이용해서 각 원소를 넣는 방법도 있고 아니면 구조체를 이용해서 사용 하는 방법도 있을 겁니다.

그중 가장 완성도가 높은 자료구조를 이용한 서버 접근을 하도록 하겠습니다.
#!/usr/bin/perl

use strict;
use warnings;
use Expect;

my %server_data = (
'203.221.xxx.111' => {id=>'estenpark', pass=>'passwd1' },
'203.221.xxx.112' => {id=>'mass',         pass=>'passwd2' },
'203.221.xxx.113' => {id=>'root',            pass=>'passwd3' },
);

foreach my $ip ( sort keys %server_data ) {
printf "IP : %16s ID : %10s PW : %16s \n", $ip, $server_data{$ip}->{id}, $server_data{$ip}->{pass};

my $conn = "ssh -l ".$server_data{$ip}->{id}." ".$ip;
my $passwd = $server_data{$ip}->{pass};
my $file_name = "log/".$server_data{$ip}->{id}."_".$ip;
my $timeout = 100
;

my $exp = Expect->spawn("$conn");

$exp->log_file("$file_name","w");

$exp->expect($timeout,
             [qr ': $' => \&inputpassword],            
             [qr '[#>\$] $' => \&gethttpnum],            
             [timeout => \&timeouterr],);

sub timeouterr
{   
die "Login Time Out\n";
}

sub inputpassword
{
    my $lexp = shift;

    $lexp->send("$passwd\n");
    exp_continue;
}

sub gethttpnum
{
    my $lexp = shift;
    $lexp->send("checklist.sh\n");
    $lexp->send("exit\n");
    $exp->log_file();
    exp_continue;
}
}

각 서버에 접근 하여 checklist.sh를 실행하고 빠져 나오는 프로그램 입니다.

 my %server_data = (
'203.221.xxx.111' => {id=>'estenpark', pass=>'passwd1' },
'203.221.xxx.112' => {id=>'mass',         pass=>'passwd2' },
'203.221.xxx.113' => {id=>'root',            pass=>'passwd3' },
);
이부분 인데요. 해시 값으로 사용하여 해시를 만드는 방법 입니다.
각 서버의 아이피 별로 {id},{pass} 해시 키를 만들고 원하는 값을 출력 하면 됩니다.

foreach my $ip ( sort keys %server_data ) {
..
..
%server_data 해시의 값을 keys 함수를 이용하여 렉시컬 변수 $ip에 넣습니다.
%server_data 루프는 총 3번 돌게 됩니다.

my $exp = Expect->spawn("$conn");
spawn() 함수는 상호대화 할 프로그램을 띄우기 위한 목적으로 사용되며, 실행된 프로그램과 표준출력/입력을 통해서 상호대화하게 됩니다. 

$exp->log_file("$file_name","w");
log_file() 함수는 $object->log_file("filename" | $filehandle | \&coderef | undef) 과 같이 사용 됩니다.
실제로 expect() 함수가 돌기 전에 파일을 생성 합니다. (단일 서버로 접근 할려면 소스의 하단에 내려도 됩니다.)

$exp->expect($timeout,
             [qr ': $' => \&inputpassword],            
             [qr '[#>\$] $' => \&gethttpnum],            
             [timeout => \&timeouterr],);
expect() 함수는 단어의 뜻과 같이 특정한 문자열을 기다리고 있다가 해당 문자열이 발견되면, 어떤 행동을 취할 수 있도록 인터페이스를 제공해주는 부분입니다.
특히 패스워드 부분은 ': $' 한 이유는 서버별로 "passwd : ", "암호 : " 터미널에 출력되는 문자열이 다르기 때문에 ": "을 취급하는 문자열이 발견 되면 해당 함수에서 값을 가지고 오게 됩니다.
\&inputpassword 함수를 불러오는 방법을 흔히 $inputpassword()로 생각 하시겠지만 저 문법은 함수에 대한 래퍼런스 입니다.

timeout 이부분은 실제로 ssh 서버의 이상으로 인하여 장기간 동안 원하는 문자열을 만나지 못할 경우 프로그램에서 block된 상태에서 머무를수도 있기 때문에 일정 시간 후에 행동을 결정 하는 부분입니다.


 sub inputpassword
{
    my $lexp = shift;

    $lexp->send("$passwd\n");
    exp_continue;
}
inputpassword 위에서 호출하면 응답하는 부분입니다. 앞서 말한것 처럼 ": " 문자열을 만나면 호출 되는 되는 함수로써 send() 함수를 이용하여 패스워드 문자열을 전송 합니다.

 sub gethttpnum
{
    my $lexp = shift;
    $lexp->send("checklist.sh\n");
    $lexp->send("exit\n");
    $exp->log_file();
    exp_continue;
}
마지막으로 이함수는 프롬프트를 만났을때 실행되는 함수입니다. 프롬프트를 만났을 경우 우리가 사용하는 명령어를 입력하면 수행 하게 됩니다. 사실 expect는 이보다 훨씬 더 복잡하고 지능화된 일들을 할 수 있도록 설계되어 있고, 시스템의 정책 값을 저장하거나, 데이터를 복사하거나 백업, 시스템에 이상유무가 있는지 판단해서 이를 복구 하기 위한 용도등 매우 폭넑게 사용 할 수 있습니다.

예를 들자면 
checklist.sh


/usr/local/SystemCheck.sh > system.log

#------------------------------------------
# 1-1. CPU CHECK
#------------------------------------------

set CHK=`grep Average system |awk '{print $5}'`

if ( $CHK <= 50 ) then
        printf "system01|STEP01_01|N|%d\n" $CHK
else
        printf "system01|STEP01_01|Y\n"
endif


각 시스템에 필요한 정보를    checklist.sh   안에 설정 하고 수행 하면 각 서버별로 정상/비정상 유무를 확인 할 수 있을 거라고 생각 합니다.
이상이 없을 경우 : printf "system01|STEP01_01|Y\n"
이상이 있을 경우 : printf "system01|STEP01_01|N|%d\n" $CHK 

서버에서 시스템을 체크 하기 위해서는 정말 쉬운 일은 아닙니다. 지금은 서버 3대를 비유하여 프로그램을 작성 하였지만 호스팅 업체나 기타 서버가 많은 회사들은 100대 이상 넘을 거라고 생각 합니다.
그럼 그 100대에 들어가서 체크하는 것도 한계가 있는 거겠죠.

perl을 시작하면서 가장 인상 깊었던 말이 "게을러 져라" 라고 하는 문구 입니다.
비록 말 자체는 좋은 표현이 아닐지 모르겠지만 어떤 업무를 하다보면 조금더 능동적으로 바꾸고 싶거나 시간을 최대한 아낄 수 있는 방법이 아닌가 생각 됩니다.

그럼 이상...Expect 모듈 사용기..

참고 자료
================================================================================
             사이트                                                      이름

================================================================================
http://aero.springnote.com/                                                       (aero님)
http://www.joinc.co.kr/modules/moniwiki/wiki.php/Site/JPerl/expect    (윤상배님)
http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod           (cpan : expect)
http://cafe.naver.com/perlstudy.cafe                                 (대한민국Perl커뮤니티)
================================================================================





















 

Comments