난이도 : 초급 Teodor Zlatanov, 프로그래머
2001 년 7 월 01 일 UNIX 시스템 관리는 언제나 골치 아픈 문제이다. 하지만 올바른 툴만 있다면 좀 더 쉬워질 수 있다. 이 글에서 Toedor는 시스템 관리의 능률화와 안전성을 위해 Perl을 사용할 것을 제안한다. 시스템 설정 엔진인 cfengine은 이런 관점에서 볼 때 매우 중요한 툴이라고 할 수 있다.
이 글에 나와있는 대로 실습해보고 싶다면 우선 Perl 5.6.0을 설치해야 한다. 최신(2000 또는 이후버전) 버전의 UNIX
계열 (Linux, Solaris, BSD) 시스템 이라면 더욱 좋을 것 같다. 초기 버전의 Perl과 UNIX와 기타 OS
상에서도 예제를 실행할 수 있지만 잠재적 오류가 있을 수 있다는 것도 생각해야 할 것이다.
모든 UNIX 벤더들은 '표준화'는 '바보 같은 짓' 이란 생각을 가지고 있다. 이것이 UNIX 관리가 상당히 도전적인 문제가
되는 이유이기도 하다. 심지어 같은 벤더가 제공한 OS (SunOS 4.x 과 Solaris 5.x) 라도 근본적으로 다를 수
있다. 어떤 경우에는 벤더들이 존재하지 않는다. 예를 들어 Linux와 같은 경우 단일 벤더가 없다. 몇몇 Linux 계열의
시스템들은 각자 그들만의 특징을 지니고 있다. POSIX 표준화는 이러한 문제들을 풀기위한 하나의 단계이다. 시스템 관리에 필요한
기능을 갖춘 작은 서브셋 만을 보장할 뿐이다.
내가 즐겨 쓰는 말이 있다 : 자신의 툴에 정통할 것! 시스템 관리는 단 한 개의 툴, 한 개의 언어, 한 가지의 접근방식을
고집할 때 악몽이 될 수 있다. 유연성을 가져라.
시스템 관리는 한번이면 족하다. 이것은 어떠한 간단한 sysadmin 작업도 반복되면 지루해진다는 것을 의미한다. 만일 단순하고
지루한 일을 두 번 이상 반복하고 있다고 판단되면 자동화 시켜라. 자동화는 어려운 작업이다. 하지만 자동화 되었을 때의 이점을
고려해서 결정하라.
cfengine 툴
시스템 관리를 자동화하는 문제를 심각하게 고려중이라면 cfengine을 알고 있어야 한다. vi 에디터로 많은 시간을 보내고
싶다면 cfengine을 무시해도 좋다. :)
cfengine은 시스템 구성 엔진이다. 이것은 인풋으로 구성 스크립트를 받아들이고 그러한 스크립트에
기초해서 수행된다. 현재 1.6.3 버전까지 나와있고 (매우 안정적이다) 2.0도 곧 출시될 예정이다. cfengine 개발에
대한 기타 자세한 정보는 웹사이트를 방문하라. (참고자료)
cfengine이 제공하는 모든 것을 사용할 필요는 없다. 그리고 한번에 모든 것이 필요하지도 않다. cfengine 설정
파일은 간단하게 시작해야 하며 자동화하고자 하는 것이 생길 때마다 서서히 늘려가야 한다.
cfengine 명령어 레퍼런스를 비롯하여 다음과 같은 주목할만한 기능들이 있다.:
- 파일 권한 및 ACL은 모니터링 및 픽스(fix)가 가능하다. 예를 들어, /etc/shadow는 0400/root/sys
권한으로 유지 될 수 있다. 권한이 변경될 경우 시스템 관리자에게 알리거나 즉시 픽스할 수 있다.
- NFS 파일시스템은 상응하는 fstab 변경을 통해서 자동적으로 마운트 또는 언마운트 될 수 있다.
- Netmasks, DNS 설정, 디폴트 루트, 네트워크 인터페이스는 단일 파일을 통해 관리된다.
- 파일과 디렉토리는 다른 장소에 반복적으로 복사된다. 원격 서버로부터 복사될 수도 있고 로컬 서버에서 복사 된다.
- 파일은 편집될 수 있으며 (정규식과 global search/replace를 제공하는 강력한 기능이다) 순환기록 (예를
들어, 로그 파일) 및 삭제 될 수 있다.
- 파일 (디렉토리에 존재하는 단일 또는 모든 파일 또는 regex에 매치(match)되는 파일)과 전체 디렉토리는 링크
될 수 있다.
- 프로세스 테이블과 매치되는 정규식에 따라, 프로세스들은 시작되거나 중단, 또는 재시작 되고 임의의 신호로 보내질 수
있다.
- 임의의(Arbitrary) 명령이 실행될 수 있다.
- 위에 제시한 모든 것은 OS 종류, 시간, 임의의 사용자 정의 클래스, 파일의 존재여부, 디렉토리, 파일의 데이터 등등에
따라 달라질 수 있다.
cfengine이 하는 모든 일을 Perl을 이용하여 할 수 있다. (이미 잘 만들어진 바퀴를 왜 다시 만들려 하는가?) 예를
들어 한 단어를 다른 것으로 대체하려면 파일 편집은 간단하게 한 줄로 끝낼 수 있다. 시스템의 하위 유형, 논리적인 시스템 분할,
기타 다양한 요소들을 고려하기 시작할 때 "one-line"은 "300-line"이 될 수 있다. 그렇다면 cfengine을
사용하여 100-line의 설정 코드를 만들어보는 것은 어떠한가?
최소한의 설정 파일로 시작해서 점차적으로 cfengine으로 옮겨 갈 수 있다. 어떤 누구도 갑작스러운 변화는 원하지 않는다.
적어도 모든 시스템 관리자들은 더욱 그렇다. 어떤 것이 잘못되면 혹독한 비난은 그들의 몫이 되기 때문이다.
설정 파일 관리
설정 파일을 관리하는 것은 힘든 일이다. cfengine 이 작업에 적절한지 여부를 생각하는 것이 관리의 시작이다. 불행하게도
cfengine의 편집은 라인(line)지향이기 때문에 복잡한 설정 파일은 적합하지 않을 수가 있다. 하지만 TCP wrappers
설정 파일인 /etc/hosts.allow 와 같은 간단한 파일들은 cfengine 을 통해서 완벽하게 수행될 수 있다.
한 개 이상의 설정 파일이 필요할 때가 있다. 예를 들어 /etc/resolv.conf 안에 두개의 DNS 설정파일이 하나는
외부용으로 다른 하나는 다른 하나는 내부 머신에 필요하다고 가정해 보자. 외부 DNS resolv.conf 파일은 자연스럽게
"external" 디렉토리 안으로 들어가고 내부 resolve.conf 파일은 그에 상응하는 "internal" 디렉토리 안으로
들어갈 수 있다. 두 디렉토리 모두 일종의 설정 파일용 root 인 global "spec" 디렉토리 하에 있다고 가정해보자.
.
다음 코드는 해당 머신에 적합한 파일이름을 찾아가며 spec 디렉토리 안을 찾는다. 요청 받은 것과 일치하는 파일을 찾아가면서
/usr/local/spec 으로 시작해서 아래로 내려갈 것이다. 그리고 이것은 각각의 디렉토리 이름이 몇몇 머신에 속해있는
클래스와 같은 것인지의 여부를 검사한다. 그래서 locate_global ('resolv.conf', 'wonka')을 요청하면,
root 디렉토리나 "wonka" 머신이 속해있는 클래스와 일치하는 이름을 가진 root 디렉토리의 자식 디렉토리에 있는 /usr/local/spec에서
resolve.conf 파일의 다음 부분을 찾을 것이다. 그래서 만약 "wonka"가 "chocolate" 클래스에 속해있고
/usr/local/spec/chocolate/resolv.conf file 이 있다면 locate_global()은 "/usr/local/spec/chocolate/resolv.conf"을
리턴할 것이다.
만일 locate_global()가 동일한 이름의 하나 이상의 파일들을 찾는다면 (예를 들어 /usr/local/spec/chocolate/resolv.conf
와 /usr/local/spec/resolv.conf) 중지 할 것이다. 틀린 것 중 하나를 가지는 것보다 어떤 설정도 하지
않는 것이 더 낫기 때문이다. 또한 머신이 한 개 이상의 클래스에 속할 수 있다는 것에도 유의하라.
여러분은 다음과 같은 구조에서 설정 할 수 있다.
- /usr/local/spec/external/chocolate/resolv.conf
- /usr/local/spec/internal/chocolate/resolv.conf
- /usr/local/spec/external/sugar/resolv.conf
- /usr/local/spec/internal/sugar
위에 나열한 것들은 외부 및 내부의 "chocolate" 과 "sugar" 머신 용 파일을 포함하고 있다. 여러분은 your
machine_belongs_to_class() 함수를 정확하게 설정해야 한다.
일단 locate_global()이 파일 이름을 리턴하면 이것을 scp 이나 rsync 를 이용하여 원격 시스템에 복사하는
것은 매우 간단하다. 파일 권한과 속성을 보유하고 있어야 함을 기억하라. Scp는 "-p" 플래그(flag)가 필요하고 rsync는
"-a" 플래그 가 필요하다. 사용하고자 하는 파일 복사 명령어에 대한 문서자료를 참조하라. 일관성 있는 설정 파일 트리를 얻게
될 것이다.
Listing 1: spec 디렉토리 트래버스
# {{{ locate_global: use spec directory to find a file matching the current class
sub locate_global($$)
{
# this code uses File::Find
my $spec_dir = '/usr/local/spec';
my $file = shift || return undef; # file name sought
my $machine = shift || return undef; # machine name
my @matches;
my $find_sub =
sub
{
print "found file $_";
push @matches, $File::Find::name if ($_ eq $file);
# the machine_belongs_to_class sub returns true if a machine
# belongs to a class; we stop traversing down otherwise
$File::Find::prune = 1 unless
machine_belongs_to_class($machine, $_) || $_ eq '.';
};
find($find_sub, $spec_dir);
if (scalar @matches > 1)
{
print "More than one match for file $file,",
"machine $machine found: @matches" ;
return undef;
}
elsif (scalar @matches == 1)
{
return $matches[0]; # this is the right match
}
else
{
return undef; # no files found
}
}
# }}}
|
이러한 종류의 /usr/local/spec 구조를 설정하려고 할 때 한가지 문제가 생긴다. resolv.conf 가 어떻게
/etc로 들어가는지 아는가? 여러분은 여기에 나타낸 대로 계층적인 구조 없이 수행하거나 그것을 변환하거나 ("/"을 "+"
로 대체 한다. 이것은 다소 위험스럽고 조잡한 방법이다) 또는 심볼릭 이름과 실제 이름 사이의 개별 매핑을 유지해야 한다. 예를
들어 "root-profile" 은 "~root/.profile"의 심볼릭 이름이 될 수 있다. 마지막 방법은 내가 좋아하는
방법이기도 하다. 왜냐하면 이것은 파일 이름을 단순화하고 파일 숨김 문제를 제거하기 때문이다. 모든 것이 한 개의 디렉토리 구조
하에 명확하고 정돈되어있다. 물론 이것은 파일을 리스트에 매번 추가해야 하는 작업이 필요하다. 프로그램은 "resolv.conf"
가 원격 시스템상의 "/etc/resolv.conf"에 복사되어야 함을 인식해야 한다. 그리고 "dfstab"이 "/etc/dfs/dfstab"(NFS
파일 시스템을 공유하기위한 Solaris 파일)으로 가야 한다는 것도 인식해야 한다.
spec 디렉토리 계층을 설정했다면 무엇을 할 수 있는지 살펴보자. 원한다면 얼마든지 Joe 라는 이름의 사용자를 찾을 수
있다. :
Listing 2: 모든 패스워드 파일을 찾아 Joe 로 "grep" 하기
grep Joe `find /usr/local/spec -name passwd`
|
또는 모든 단어를 다른 것으로 대체하려면 rep.pl (David Pitts 작성) 툴을 사용할 수 있다. :
Listing 3: 모든 호스트 파일을 찾아 "wonka" 를 "willy"로 변경하기
find /usr/local/spec -name hosts -exec rep.pl wonka willy {} \;
|
이제 Perl을 이용하여 listing 2와 3을 작성할 수 있게 되었다. find2perl 유틸리티를 이용해보자. 하지만
처음부터 find 만을 사용하는 것이 훨씬 더 간단하다. 이것은 모든 시스템 관리자가 사용할 수 있는 훌륭한 유틸리티이다. 두개의
Listing을 작성하는데 5분 정도가 소요된다. find2perl의 사용 방법을 파악하여 이것이 생성하는 코드를 파일에 저장하고
파일을 실행시키기 까지 얼마나 많은 시간이 걸리는지 직접 실습해 보자.
태스크 자동화 (Task automation)
태스크 자동화는 매우 광범위한 주제이다. 이 글에서는 UNIX의 비대화형(NON-INTERACTIVE) 명령어의 자동화에 대해
설명하겠다. 대화형(interactive) 명령어 자동화에 있어서 Expect 는 현재 사용할 수 있는 것 중 최상의 툴이다.
신택스를 배우거나 Perl의 Expect.pm 모듈을 사용해야 한다. CPAN에서 Expect.pm 정보를 얻을 수 있다. (참고자료)
cfengine를 이용하여 거의 오든 태스크를 자동화 할 수 있다. 하지만 함수는 Makefile 함수와 상당히 비슷하다.
변수에 대해 복잡한 연산을 수행하기 어렵다. hash로 부터 얻은 매개변수가 있는 명령어를 실행시켜야 한다거나 개별 함수를 통해서
실행시켜야 한다면 쉘 스크립트나 Perl로 변환하는 것이 최상의 방법이다. 함수 기능에 있어서는 Perl이 더 낫다. 그렇다고
해서 쉘 스크립트를 간과해서는 안된다. 그저 간단한 명령어만을 실행해야 할 때가 있기 때문이다.
사용자 추가를 자동화 시키는 것은 공통적인 관심사이다. adduser.pl 스크립트를 작성하거나 adduser 프로그램을
사용할 수 있다. 최신 UNIX 시스템은 adduser 프로그램을 제공하고 있다. 사용하고 있는 모든 UNIX 시스템에서 신택스를
일관성 있게 유지해야 한다. 하지만 범용 adduser 프로그램 인터페이스를 작성하지는 말라. 이것은 너무 어려운 일이고 여러분이
모든 UNIX 변수들을 숙지했다고 생각했을 때 조만간 누군가가 Win32 또는 MacOS 에 대해서도 요청하게 될 것이다. 상당히
도전적이지 않고서는 Perl 전체의 문제를 푸는 것은 쉬운 일이 아니다. 단지 스크립트가 user name, password,
home directory 등을 요청하도록 하여 adduser 를 system()와 함께 호출하도록 하라.
Listing 4: 간단한 스크립트로 adduser 호출하기
#!/usr/bin/perl -w
use strict;
my %values; # will hold the values to fill in
# these are the known adduser switches
my %switches = ( home_dir => '-d', comment => '-c', group => '-G',
password => '-p', shell => '-s', uid => '-u');
# this location may vary on your system
my $command = '/usr/sbin/adduser ';
# for every switch, ask the user for a value
foreach my $setting (sort keys %switches, 'username')
{
print "Enter the $setting or press Enter to skip: ";
$values{$setting} = ;
chomp $values{$setting};
# if the user did not enter data, kill this setting
delete $values{$setting} unless length $values{$setting};
}
die "Username must be provided" unless exists $values{username};
# for every filled-in value, add it with the right switch to the command
foreach my $setting (sort keys %switches)
{
next unless exists $values{$setting};
$command .= "$switches{$setting} $values{$setting} ";
}
# append the username itself
$command .= $values{username};
# important - let the user know what's going to happen
print "About to execute [$command]";
# return the exit status of the command
exit system($command);
|
Perl을 이용한 태스크 중에는 프로세스를 모니터 하고 재시작 하는 것이 있다. 보통 이것은 Proc::ProcessTable
CPAN 모듈로 수행된다. Proc::ProcessTable CPAN 모듈은 전체 프로세스 테이블을 통과하여 많은 중요한 속성들과
프로세스 리스트를 사용자에게 제공한다. 하지만 나는 cfengine을 권하고 싶다. 이것은 훨씬 더 나은 프로세스 모니터링과
재시작 기능을 제공한다. 그와 같은 툴을 작성하는 데에 어려움이 있다면, 바퀴를 다시 발명하라. cfengine을 사용하고 싶지
않다면 pgrep 과 pkill 유틸리티를 사용해 보라. 이것들은 최신 UNIX 시스템에 포함되어있다. pkill -HUP inetd
는 4줄 이상의 Perl 스크립트 분량을 하나의 압축된 명령어에서 수행할 것이다. 여러분이 수행하고 있는 프로세스 모니터링이
매우 복잡하거나 시간에 민감하다면 반드시 Perl을 사용한다.
Proc::ProcessTable 예제에서는 kill() 이라는 Perl 함수를 사용하는 방법을 설명할 것이다. 매개변수로
쓰이는 "9" 는 가장 강한 kill() 아규먼트이다. inetd 프로세스를 완전히 중단하고 싶지 않다면 root 권한으로 실행하지
말라.
Listing 5: 모든 inetds 중단하기
use Proc::ProcessTable;
$t = new Proc::ProcessTable;
foreach $p (@{$t->table})
{
# note that we will also kill "xinetd" and all processes
# whose command line contains "inetd"
kill 9, $p->pid if $p->cmndline =~ 'inetd';
}
|
요약
UNIX 시스템 관리의 가장 큰 난점은 UNIX 벤더들이 표준화를 지양한다는 점이다. 이로인해서 Perl은 모든 UNIX 시스템의
문제들에 대해 대응할 수 없을 정도로 무력하다. 패스워드 파일 신택스, 파일 시스템 공유, 로그의 신속한 트래킹(tracking)
같은 문제들은 cfengine와 같은 툴 없이는 관리할 수 없게 되었다. 그럼에도 불구하고 희망은 있다. 결국 우리가 Perl이
시스템 관리를 단순화 시킬 수 있는 방법을 모색할 것이기 때문이다.
Perl은 cfengine 와 인터페이스가 아주 잘 이루어진다. 사용자에 맞춘cfengine 설정을 만들기 위해서 Perl을
사용할 수 있고 cfengine에서 Perl 스크립트를 실행시킬 수 있다. 나는 그 두 가지 모두 실행해 보았고 어떤 문제도
없었다. 하지만 cfengine 는 간단한 설정 언어 문제와 표현할 수 있는 데이터 구조의 부족 문제가 있다. cfengine에
대해서 나중에 자세히 다루겠다.
이 글에서 소개한 중앙집중화된 설정 파일 전략은 매우 유용한 것으로 입증되었다. 나는 이것을 6개월 정도 성공적으로 사용해왔다.
전체 계층을 CVS 와 같은 버전 제어 시스템으로 설정한다면 어떤 상태로든 복귀할 수 있는 버전화된 파일의 혜택을 누릴 수 있을
것이다.
참고자료
필자소개  | 
| Teodor Zlatanov는 1999년 보스톤 대학에서 컴퓨터 공학 석사학위 취득했다. Perl, Java, C++ 를 사용하여, 1992년부터 프로그래머로 일하고
있다. 주요 관심 부분은 text parsing, 3-tier 클라이언트/서버 데이터베이스 아키텍처, UNIX 시스템
관리, CORBA 및 프로젝트 관리에 대한 오픈 소스 작업이다. |
기사에 대한 평가
|