본문 바로가기
IT Security/LINUX Basic

40. Linux - 나만의 Service를 만들어 구동해보자2

by Rosmary 2021. 9. 25.
728x90
반응형

 

 

 

지난 포스팅에서는 Linux 서비스에 필요한 쉘 스크립트(기타 실행파일도 가능하다), 서비스 사용 이유 및 service 파일에 작성하는 옵션에 대해 알아보았다. 이번 포스팅에서는 자체 쉘 스크립트를 만들고, 만든 스크립트를 서비스로 등록하여 동작되도록 하는 전 과정에 대해 작성해보려 한다.

 

구동할 서비스는 다음과 같다. systemctl status를 치면, 해당 서비스에 대한 실시간 로그를 확인할 수 있는데, 이 로그에 짝수를 0부터 20까지, 홀수를 1부터 19까지 1초에 하나씩 출력하는 서비스 2개를 만들어보려 한다.

 

붉은 박스 부분이 각 서비스의 로그가 기록되는 부분이다. 이 부분에 짝/홀수를 기록할 것이다.

 

두 개의 서비스를 만드는 이유는 앞선 포스팅에서 보았던 옵션들(Requires, Wants 등) 일부를 확인하기 위함이다.

 

1. 스크립트의 작성

 

스크립트는 아래와 같이 두 개를 작성한다. 쉘 파일명은 각각 print_odds.sh(홀수 출력)와 print_even.sh(짝수출력)로 지정한다. 경로는 우선 /root/ 아래에 지정하려한다.

 

좌측이 print_odd.sh, 우측이 print_even.sh다.

실제 쉘 스크립트가 의도한대로 잘 동작하는지 sh 또는 source 명령어로 확인해보자.

# 명령어: sh {쉘 스크립트 파일명} 또는 source {쉘 스크립트 파일명}

 

 

위와 같이 정상적으로 동작한다면 서비스 구동을 위한 자체 쉘 스크립트 제작은 끝이다.

 

"어?? echo는 화면에 글자를 출력하는 명령어인거 같은데, systemctl status 로그에 글자를 찍는 명령어는 따로 없는 건가요??"

 

라는 질문이 있을 듯 한데, 쉘 스크립트가 서비스에 등록되어 실행되는 경우, echo와 같은 명령어는 화면이 아닌 로그에 출력될 문자를 기록한다. 이 부분은 서비스 구동 시 다시 확인해보도록 하자.

 

 

2. 서비스 파일 생성

 

이제 위에서 만든 쉘 스크립트를 서비스에 등록하는 작업을 진행해야 한다. 앞의 포스팅에서 봤듯이, 이 역할을 하는 파일은 확장자가 .service인 파일이며, 경로는 /usr/lib/systemd/system(CentOS7 기준)이다. 해당 경로로 이동한 뒤, 서비스 파일 두 개를 touch 명령어로 만들어주자. 

 

# 명령어: touch print_odd.service

             touch print_even.service

 

 

service 파일 생성이 완료되었다면, 각 파일에 [Unit], [Service] 및 [Install] 내용을 작성하도록 하자. 우선은 구동에 반드시 필요한 내용만 작성한다.

 

좌측이 print_odd.service, 우측이 print_even.service 내용이다.

둘 다 쉘 스크립트가 메인 프로세스로 동작하는 simple 타입으로 지정했다. 이제 서비스 구동 준비는 완료되었다. 실제 서비스 구동이 되는지 확인해보로고 하자.

 

 

3. Service 구동 확인

 

/usr/lib/systemd/system에 서비스가 등록된 순간부터, systemctl 로 서비스 상태를 조회하는 것이 가능해진다. 물론 service 파일이 오류 없이 잘 작성되었다는 전재하에 말이다.

 

두 서비스 모두 실행 상태가 아니라 Inactive로 상태가 출력된다.

 

이제 print_odd 서비스를 실행해보자. 실행 직후, 1초마다 systemctl status 명령어를 입력하여 print_odd 서비스의 상태를 확인해보자. 아마 아래와 같이 에러가 뜰 것이다.

 

failed 아래 process 코드를 보면 스크립트를 실행하는 프로세스가 203/EXEC으로 종료된 것이 보이는데, 이 코드는 서비스에 등록된 스크립트에 실행 권한이 없어서 발생한 문제이다. 실제 필자가 만든 print_odd.sh, print_even.sh는 파일 권한이 644이기 때문에 root의 실행 권한이 없는 파일이다. 따라서, chmod 명령어로 쉘 스크립트 파일의 권한을 744로 변경한 뒤, 다시 실행하면 정상 동작함을 확인할 수 있다.

 

 

위의 파일을 보면 필자가 쉘 스크립트를 직접 실행한 것과 동일한 결과가 서비스 로그에 찍히는 것을 확인할 수 있다. 그리고 서비스 시작 시점에서 service 파일에 등록한 Description 이름이 Started {Description} 형태로 나타나는 것을 확인할 수 있다.

 

등록한 서비스는 쉘 스크립트 동작이 완료되는 즉시 자동 종료된다. 단, 이렇게 스크립트의 정상 종료로 서비스가 종료되는 경우, 서비스 로그에 중단과 관련된 로그가 찍히지 않는다. 

 

만약 서비스가 잘 동작하는 와중에 systemctl stop 명령어로 서비스를 멈추게 될 경우, 아래와 같이 중단 관련 로그가 발생한다.

 

 

여기까지가 기본 옵션에 의한 구동 결과이다. 이제 service 파일에 옵션을 추가하여 그 결과를 알아보도록 하자.

 

(1) Unit 옵션

 

- Requires

필자는 print_odd.service에 Requires로 print_even 서비스를 등록하려 한다. 아래와 같이 등록해주면 된다.

Requires는 본 서비스가 동작할 때, 같이 동작할 다른 서비스를 정의하는 것이라고 이전의 포스팅에서 언급했다. 따라서, 필자가 print_odd 서비스만 systemctl start로 구동하더라도 print_even 역시 Requires에 의해 동시에 동작하게 된다.

 

두 서비스가 모두 중단되었는지 확인하고, systemctl start print_odd 명령어를 입력한 뒤 두 서비스의 상태를 보자.

 

 

- Wants

이번에는 Requires를 Wants로 변경하여 서비스를 구동해보자. 이전과 결과는 동일하게 나타난다. 하지만 Requires와 큰 차이점이 하나 존재한다. print_odd 서비스를 실행하고 약 1~2초 뒤에 print_even 서비스를 중단해보자. Requires로 지정되어 있다면 print_even 서비스 중단 시, print_odd도 함께 중단되며, Wants로 지정되어 있다면 print_even서비스가 중단되더라도 print_odd는 계속 실행됨을 확인할 수 있다.

 

Requires=print_even.service로 등록한 print_odd.service 파일의 실행 결과
Wants=print_even.service로 등록한 print_odd.service 파일의 실행 결과

 

- Conflicts

 

Conflicts는 Requires, Wanted와 달리, 본 서비스 실행 여부와 반대의 상태가 되어야 하는 서비스를 정의한다. 예를 들어, Conflicts에 print_even 서비스가 등록되어 있다면, print_odd 서비스 동작 시, pritn_even이 실행중이더라도 강제 종료가 된다. 

 

 

 

(2) Service 옵션

 

- Type: simple, forking

 

simple 타입과 forking 타입은 몇 가지 차이점이 있다. 먼저 simple은 서비스의 주 스크립트가 실행되는 그 순간에 서비스 시작 절차가 완료되었다고 판단하는 반면, forking은 스크립트 실행이 끝나야 서비스 시작 절차가 완료된다고 판단한다. simple과 forking의 시작 로그를 보자.

 

Type = simple 인 경우의 로그

 

자세히 보면 알겠지만, forking 타입의 서비스는 반드시 ExecStart의 스크립트가 종료되어야만 서비스 시작 절차가 완료된다고 판단하기 때문에 systemctl start 명령어를 입력하고 10초간(1~19까지 입력되는 시간 동안) 프롬프트가 떨어지지 않는다. 그러나 서비스의 주 기능인 숫자 입력이 끝나면 서비스도 종료된다.

 

그럼, forking은 도대체 어떤 경우에 사용하는 것일까? 이제 simple과 forking의 두 번째 차이점에 대해 보자. simple의 경우, 단일 프로세스 쉘 스크립트를 동작시키는 경우, forking은 복수의 쉘 스크립트를 자식 프로세스로 형성하여 동작시키는 경우 사용한다. 무슨 말이냐 하면, 만약 ExecStart 스크립트 내에 또 다른 스크립트를 구동하는 명령어가 백그라운드로 돌도록 작성되어 있다고 가정해보자. 이를 위해 추가로 스크립트를 생성하고 기존의 print_odd 스크립트 파일명을 변경하자.

 

# 명령어: mv print_odd.sh test.sh    (기존 print_odd.sh 파일명을 test.sh로 변경)

# 명령어: touch print_odd.sh

 

 

새로 생성한 print_odd.sh 파일은 cat > print_odd.sh 아래의 내용을 입력해주고, root 계정으로 실행권한을 부여해주자.

이제 systemctl start print_odd 명령어를 입력해보자.

 

로그를 보면 알겠지만, 실제 숫자를 출력하는 test.sh 파일은 백그라운드(&)로 구동했기 때문에 print_odd.sh 내에서 별도의 프로세스로 분기(forking)되어 실행되는 것을 확인할 수 있다. 따라서 매인 프로세스인 print_odd.sh는 test.sh가 종료될때까지 기다리지 않고(별도 프로세스로 실행되기 때문에) End print_odd.sh라는 문구를 출력하며 서비스를 시작하게 된다. 그리고 분기된 test.sh는 서비스가 실행된 뒤에 동작을 진행하게 되어, 위에서 보는 것과 같이 서비스 실행 후 숫자를 순차적으로 찍게 된다. 

 

- PermissionsStartOnly, User, Group

 

PermissionsStartOnly은 해당 서비스를 실행할 계정 또는 계정그룹을 지정할 때 사용한다. 보통 User 및 Group 지정 시 함께 사용한다. PermissionsStartOnly의 값은 yes, no 두 가지가 있으며 기본값은 no이다. 이번에는 print_odd 서비스를 test1이라는 계정이 실행하는 것처럼 만들어보려 한다. 

 

systemctl status 로그를 보면, 로그 기록 일자, 서버의 hostname 다음에 서비스 실행 주체 및 프로세스 번호가 나타나는 것이 보인다. 

 

 

위의 예시를 보면, print_even.sh에 의해 ENEN NUMBER가 로그에 작성되었고, systemd에 의해 print_even 서비스 종료와 관련된 로그(Systemd에 의해 최종적으로 print_even 서비스가 종료된 것이다)가 찍혀있음을 알 수 있다. 이 로그들은 시스템 로그인 /var/log/messages 파일에 고스란히 복사 및 작성된다.

 

실제 messages 파일의 내용. 필자가 만든 print_odd 관련 로그가 출력된 것이 보인다.

 

그럼, 저 messages 로그에 찍히는 서비스 실행 주체를 쉘 스크립트 파일명이 아닌 별도의 계정으로 지정할 수 있을까? 다시 정리하자면, 리눅스 내 아무 계정으로 본 스크립트를 실행하더라도 특정 계정이 실행한 것처럼 나타나게 하고 싶은 것이다.

 

이런 설정을 하는 이유는 간단한데, 특정 서비스와 관련된 파일은 특정 계정이 관리하도록 설계되어 있기 때문이다. 예를 들어 sshd 서비스의 경우, 리눅스 내에 sshd라는 계정에 의해 관련 파일에 접근하고 서비스 동작이 이루어진다.

 

 

필자도 print_odd 서비스를 test1이라는 계정에 의해 동작하도록 만들려고 한다. 이를 위해서, 필자는 필자가 만든 초기 버전의 print_odd.sh를 test1 계정의 홈 디렉토리에 옮기고, 파일 소유자도 test1로 변경할 것이다. 또한 print_odd.sh 파일 내 echo 명령어 뒤에 | logger를 추가해 줄 것이다.

 

 

!! logger는 echo로 출력할 내용을 messages 로그에 작성한다는 의미다. logger -t를 사용하면 특정 계정이 해당 로그를 작성한 것처럼 만드는 것도 가능하다. 하지만 서비스 쉘 스크립트 파일 내에서는 logger 명령어만 echo 뒤에 파이프로 붙어 있어도 서비스 실행 계정을 자체적으로 판단하여 로그에 입력한다.

 

그리고 service 파일에 PermissionsStartOptions와 User도 다음과 같이 추가한다. 물론 ExecStart 경로도 /home/test1/print_odd.sh로 수정한다.

 

수정한 뒤, print_odd 서비스를 실행하고, cat /var/log/messages 파일을 보면, 이전 서비스 실행 시 print_odd.sh로 찍혀있던 수행 주체가 test1로 찍혀있는 것이 보인다. 

 

 

특이한 점은, systemctl status로 보이는 로그에는 기존과 동일하게 print_odd.sh가 주체로 스크립트를 실행한 것으로 나타난다는 것이다.

 

 

즉, User와 Group 옵션은 자체 서비스 로그가 아닌 시스템 로그인 messages 파일에 등록하기 위한 옵션이라고 보면 된다.

 

 

[ Install ]

 

(1) Alias

 

Alias는 서비스가 enable(부팅 시 자동으로 서비스가 실행되도록 함)로 등록된 경우에만 사용할 수 있는 일종의 별칭이다. Alias={사용할 이름}.service 로 입력해주면 된다. 필자도 print_odd 서비스를 testtest라는 이름으로 구동될 수 있도록 Alias를 추가하고, print_odd 서비스를 enable 시키려 한다.

 

systemctl enable 명령어를 사용하니, 심볼릭 링크가 두 개 만들어졌다는 문구가 출력된다. 하나는 WantedBy에 지정된 multi-user.target 폴더 내에 만들어진 print_odd.service 파일의 심볼릭 링크고, 다른 하나는 Alias에 지정한 서비스명(testtest)으로 생성된 심볼릭 링크다. 이렇게 Alias에 대한 심볼릭 링크까지 형성되면, Alias 이름으로 서비스를 시작, 중지하는 것이 가능해진다.

 

 

반드시 enable로 심볼릭 링크가 형성되어 있어야만 Alias 서비스 명을 사용할 수 있기 때문에, print_odd 서비스를 disable로 변경하면 그 순간부터 Alias 서비스 명으로 서비스를 제어하는 것이 불가능해진다.

 

 

(2) WantedBy, RequiredBy

 

WantedBy와 RequiredBy는 서비스 enable 시 어느 경로에 심볼릭 링크를 형성할 것인지를 지정하는 옵션이다. 보통 [ Unit ] 부분의 Wants, Requires 서비스가 포함된 폴더를 지정하는데, 일반적인 서비스라면 Wanted=multi-user.target을 많이 사용한다.

 

 

 


 

이번 포스팅에서는, 이전 포스팅의 내용을 참고하여, 직접 쉘 스크립트와 서비스 파일을 생성하여 자체적인 서비스 구동이 이루어지는지 확인해보았다. 다행히 연휴로 인해 잠깐의 여유가 생겨 새로운 것을 추가로 학습할 수 있게 되었다. 필자가 실제로 운영하는 Linux 서버에는 서비스화되지 않은 몇몇 스크립트들이 존재하는데, 이번 포스팅 내용을 바탕으로 이들 스크립트 파일들도 시간이 나는대로 서비스화하여 조금 더 수월하게 관리를 할 수 있게 만들 예정이다.

 

 

Fin.

 

 

 

 

 

반응형

댓글