작년에 공부하면서 여기저기 호스팅 해두었던 사이트들을 오랜만에 접속해보니 연결이 끊겨있는 것들이 있었다.
그 중 Oracle freetier에서 nohup으로 실행을 시켜둔 게 있는데, 그 당시에도 간혹 연결이 끊겨서 매번 번거롭게 접속해 nohup npm start & 등의 명령어를 쳐줬던 기억이 난다. (start에는 node ./index 등이 설정되어 있다)
그때는 방법을 몰랐으나, 지금은 솔라나 밸리데이터(^.ㅠ...)를 데브넷에서 돌려보면서 서비스 관리를 할 수 있게 되었으니 연결이 끊겨도 자동으로 재시작해서 동작할 수 있게끔 시스템 데몬을 돌리려고 한다.
시스템 데몬을 사용하면 수행하려는 동작이 적힌 파일을 백그라운드에서 실행하여 정상적으로 동작하는지 지속적으로 확인하고 관리할 수 있다.
앞으로도 비슷한 식으로 쓸 일이 생길 것 같아서 systemd service의 옵션으로 서비스 파일을 만들고 실행하는 방법을 정리해보겠다.
순서 : systemd 옵션 > systemd 파일 작성(+실행 파일 작성) > systemctl 명령어로 자동재시작되는 서비스 실행
* 파일 작성법만 간략히 보고 싶다면 하단의 작성 파트부터 보시면 됩니다.
[ Systemd 옵션 ]
service 파일은 Unit, Service, Install 3개의 섹션으로 이루어져있다.
솔라나 밸리데이터의 서비스 파일을 예시로 보며, 나중에 사용할 수도 있을 것 같은 systemd 문법을 정리해봤다.
① Unit 섹션
[Unit]
Description=Solana Validator
After=network.target
Wants=systuner.service
StartLimitIntervalSec=0
- Description : 이 프로세스에 대한 설명을 추가하는 구문이다.systemctl status <systemname>.service
를 사용하여 시스템 상태를 조회할 때 함께 나온다.
- After | Before : 서비스의 순서를 구성한다. 나열된 유닛들을 실행한 후 | 실행하기 전 이 프로세스를 실행하도록 한다.
위의 예시에서는 프로세스가 실행되기 전에 network.target이 선행적으로 실행되어야 한다. 이 자리에는 다른 service 파일도 올 수 있고, 띄어쓰기로 구분하여 여러 유닛을 지정할 수도 있다.
* 지정 가능한 target 목록은 systemctl list-units --type target 으로 확인할 수 있다. 여기에 --all을 추가하면 inactive 유닛들도 확인이 가능하니 참고하자.
- Requires : 상위 의존성을 구성한다. 목록의 유닛이 정상적이어야 프로세스가 시작된다.
- BindsTo : Requires와 매우 유사하다. systemd 개입 없이 상위 프로세스가 사라진 경우 해당 프로세스도 같이 중지한다.
- PartOf : Requires와 매우 유사하다. 상위 의존 프로세스를 중지하거나 재시작하면 해당 프로세스도 중지하거나 재시작된다. 한 서비스가 다른 서비스에 종속성을 가지는 경우 사용한다.
- Wants : Requires보다 다소 완화된 옵션이다. 상위 의존 프로세스가 시작되지 않아도 전체 수행과정에 영향을 끼치지 않는다. 한 유닛을 다른 유닛과 연계할 경우 사용한다.
- Conflicts : 이 프로세스가 실행되면 지정한 유닛이 중지되고, Conflicts에 설정한 유닛이 실행되면 이 프로세스가 중지된다. 각 서비스가 반대 역할을 하거나 보조적인 역할을 수행할 때 서비스 실행을 관리하기에 좋을 것이다.
- OnFailure : 이 프로세스가 '실패' 상태가 되면 수행할 유닛 목록
- StartLimitBurst : 서비스가 잘 동작하지 않아 재시작할 때 해당 횟수까지만 재시작하도록 하는 옵션이다.
- StartLimitIntervalSec : 뒤에 숫자가 오면 해당 초 동안은 재시작하지 않는다는 것이다.
일반적으로 StartLimitBurst와 함께 쓰이면 몇초 내에 몇번 이상 재시작하지 않는 옵션을 거는 등의 방식으로 쓰인다.
(서비스 섹션에 들어가야 한다는 얘기도 있어서 서비스 섹션에 넣고 돌려봤더니 systemctl status를 봤을 때 유닛 섹션에 있으면 적용 안 된다고 뱉어냈다. 케이스에 따라 status를 확인해서 맞는 섹션에 위치시켜야 할 듯)
② Service 섹션
[Service]
Type=simple
Restart=on-failure
RestartSec=1
LimitNOFILE=1000000
LogRateLimitIntervalSec=0
User=<username>
Environment=PATH=/bin:/usr/bin:/home/<username>/.local/share/solana/install/active_release/bin
Environment=SOLANA_METRICS_CONFIG=host=https://metrics.solana.com:8086,db=devnet,u=scratch_writer,p=topsecret
ExecStart=/home/deploy/start-validator.sh
- Type=
simple(기본값) : ExecStart의 메인 프로세스가 즉시 수행되면서 서비스가 구동됐다고 간주한다. 이때 해당 프로세스의 문제로 정상적인 구동이 되지 않았더라도, systemctl의 status는 success로 표시된다. 실행 이후 별도로 관리가 필요하지 않을 때 적합하며, 다른 유닛과 통신하기 위해 소켓을 사용하는 경우에는 적합하지 않다.
forking : ExecStart에 명시한 부모 프로세스에서 fork()를 호출해 자식 프로세스를 생성해 구동하는 방식이다. 부모 프로세스를 추적할 수 있도록 PIDFile= 필드에 PID 파일을 선언해야 한다. 자식 프로세스 생성이 완료되면 부모 프로세스는 종료되며, 부모 프로세스가 생성한 자식 프로세스가 서비스 데몬 등으로 지속적으로 구동될 때 적합하다.
oneshot : simple과 유사하지만, 메인 프로세스가 종료된 후에도 활성화 상태로 간주하도록 RemainAfterExit=yes 옵션을 줄 수 있다.
notify : simple과 유사하지만, sd_notify() 함수를 통해 알림 메시지가 전송된 후에 시작된다. 자세한 내용은 libsystemd-daemon.so에 선언되어 있다는 듯 하다.
dbus : simple과 유사하지만, 메인 프로세스에 DBUS에 지정된 BusName이 전송된 후에 시작된다.
* simple, forking, oneshot이 많이 사용된다고 한다.
- RemainAfterExit=yes(no) : 프로세스가 종료된 이후에 활성화(비활성화) 상태로 간주한다.
- GuessMainPID=yes(no) : Type=forking 인데 PIDFile= 설정이 없으면 작동한다. systemd가 서비스의 메인 PID를 판단하기 어려운 경우 추측할지 정한다.
데몬이 여러개의 프로세스로 이루어졌을 경우 PID를 잘못 추측할 수도 있고, 그로 인해 오류 검출이나 자동 재시작 등의 작업이 불가능할 수 있다. 기본값은 yes 다.
- PIDFile : 절대경로로 PID 파일을 지정한다. Type=forking일 때 권장되는 옵션이다.
- BusName : Type=dbus 일 때 필수적인 옵션이다.
- Restart=
no(기본값) : 프로세스를 다시 시작하지 않는다.
on-success : 프로세스가 정상적으로 종료됐을 때만 재시작한다.
on-failure : 프로세스가 비정상적으로 종료되었을 때 재시작한다.
ex) 리턴값이 0이 아닐 때, core dump 같은 비정상적인 시그널을 받고 종료됐을 때, 타임아웃값 내 응답이 없을 때 등
on-watchdog : WatchdogSec= 에 설정된 시간 내 응답이 없는 경우에만 재시작한다.
on-abort : 지정되지 않은 리턴값을 받은 경우 재시작한다.
always : 종료 상태와 무관하게 재시작한다. (사용자가 중지해도 시스템이 다시 띄우므로 설정된 프로세스 중지 시 유의)
* Restart 옵션은 해당 프로세스가 죽었거나 WatchdogSec= 만큼의 시간 동안 응답이 없는 경우 재시작한다. ExecStartPre=, ExecStartPost=, ExecStopPre=, ExecStopPost=, ExecReload= 으로 설정한 유닛에는 적용되지 않는다.
- RestartSec : 재시작 명령을 수행할 때 중지 이후 다시 시작할 때 대기(sleep) 시간을 설정한다.
기본값은 100ms고, 각각 min, s, ms 단위로 설정한다. 이 옵션은 Restart= 옵션이 있는 경우에만 적용된다.
- WatchdogSec : 프로세스가 시작되고 유닛 상태를 감시할때의 상태값을 리턴할 때의 대기 시간을 설정한다. Restart= 옵션이 on-failure, always 인 경우 유닛을 자동으로 재시작하는데 이 때 WatchdogSec 설정을 해주어야 한다. 기본값은 0으로, 유닛 상태 감시를 사용하지 않는다.
- User | Group : 유닛의 프로세스를 수행할 사용자명 | 그룹명 등을 지정한다.
- Environment : 프로세스에서 사용할 환경 변수를 선언한다. 반드시 Exec= 옵션보다 상단에 위치해야 한다.
ex) Environment="ONE=one" 'Two=two two'
=> $ONE은 one, $TWO는 two, ${TWO}는 two two
- EnvironmentFile : 프로세스에서 사용할 환경 변수 파일을 지정한다. 반드시 Exec= 보다 상단에 위치해야 한다.
환경 변수 파일에서 # 이나 ; 로 시작되는 라인은 주석 처리된다. Environment= 와 같이 사용하면 Environment= 옵션의 환경변수가 적용된다.
- ExecStart : 프로세스를 실행하는 명령이다. 처음에 오는 인수는 실행 파일에 대한 절대 경로여야 한다.
Type=oneshot 이면 둘 이상의 명령을 지정할 수 있고, 아니면 하나의 명령만 가능하다. 여러 명령어를 지정할 때는 단독 세미콜론으로 구분해 단일 지시문에 연결할 수 있다.
ex) ExecStart=command1 ; command2 ; command3 또는
ExecStart=command1
ExecStart=command2
- ExecStop : 프로세스를 중지하는 명령이다. 중지 방식은 KillMode= 옵션으로 지정한다.
- KillMode=
control-group(기본값) : 이 유닛의 그룹까지 모두 중지시킨다. 그룹은 이 유닛과 종속성을 가지는 유닛들을 말한다.
process : 이 유닛, 즉 메인 프로세스만 중지시킨다.
none : 아무 액션도 하지 않는다.
- ExecStartPre, ExecStartPost, ExecStopPre, ExecStopPost : 유닛 시작/중지 이전/이후에 실행할 추가 명령이다.
- RootDirectory : 루트인 '/' 의 경로를 지정한다.
- WorkingDirectory : 프로세스의 작업 경로를 지정한다. 별도로 지정하지 않으면 루트를 작업 디렉토리로 사용한다.
- LimitNOFILE : Open File Descriptors의 최대 숫자를 설정한다. ulimit -n 명령어를 입력하면 최대로 열 수 있는 파일수가 나오는데, 이를 설정하는 옵션이라고 생각해도 될 듯 하다. LimitNOFILE=infinity로 설정하면 ulimit 값을 따라간다.
시스템상 정해진 최대 숫자를 넘게 설정할 수는 없다고 하는데 솔라나 문서에서 제시한 Systemd Unit 내용에서는 위의 옵션을 포함하고 있고, 모니터링 Grafana에 나오는 Open File Descriptors 수치를 조회해보면 약 40만 정도가 나왔다.
더 자세한 내용에 대해 다루고 있는 링크들은 아래에 접어서 첨부했다.
LimitNOFILE 관련 오류와 명령어 : https://github.com/syncthing/syncthing/issues/5319
파일디스크럽터 설명 : https://dev-ahn.tistory.com/96
ulimit과 맵핑되는 systemd의 프로퍼티 : https://iamhereweare.com/2021/01/07/systemd-and-ulimit/
③ Install 섹션
systemctl enable <servicename> 으로 구동 시 서비스가 자동으로 구동되도록 할 때 이용하는 섹션이다.
[Install]
WantedBy=multi-user.target
WantedBy : 서비스가 어떤 전제조건 하에서 실행되는지 결정하는 프로퍼티다. systemctl enable 로 유닛을 등록할 때 등록에 필요한 유닛을 지정할 수 있다.
network.target : 네트워크가 active 되었는지 판단한다. 실제로 up 되었는지에 대해서는 관여하지 않아, 서비스가 구동되는 시점에 네트워크가 active지만 up이 덜 되었을 경우 서비스가 다른 네트워크와 통신하는 작업에 실패할 수 있다.
multi-user.target : runlevel 3 환경이 셋업되었는지 판단한다.
network-online.target : 네트워크가 온라인 상태인지 판단한다.
Alias : 유닛의 알리아스 이름을 지정한다. systemctl enable 명령어를 사용해 알리아스 이름으로 생성할 수 있다. 알리아스 이름은 확장자 이름을 가져야 한다. service, socket, mount, swap 등이 있다.
ex) httpd.service의 Alias=apache.service
Also : systemctl enable과 disable로 유닛을 등록하거나 해제할 때 다른 유닛도 같이 등록/해제하도록 할 수 있다.
Targets table
SysV Runlevel
|
systemd Target
|
Notes
|
0
|
runlevel0.target, poweroff.target
|
Halt the system.
|
1, s, single
|
runlevel1.target, rescue.target
|
Single user mode.
|
2, 4
|
runlevel2.target, runlevel4.target,
multi-user.target |
User-defined/Site-specific runlevels. By default, identical to 3.
|
3
|
runlevel3.target, multi-user.target
|
Multi-user, non-graphical. Users can usually login via multiple consoles or via the network.
|
5
|
runlevel5.target, graphical.target
|
Multi-user, graphical. Usually has all the services of runlevel 3 plus a graphical login.
|
6
|
runlevel6.target, reboot.target
|
Reboot
|
emergency
|
emergency.target
|
Emergency shell
|
[ Systemd 파일 작성 ]
나는 작업 폴더 경로에서 npm start 를 입력하면 특정 포트에서 서버가 돌아간다.
처음에는 해당 작업 폴더에 들어가서 nohup npm start & 로 돌렸는데, 에러가 나면 자꾸 사이트가 죽어버려서 자동으로 재시작되게끔 시스템으로 구성해봤다.
시스템 데몬을 돌리는 service 파일은 다음 경로에 만들면 된다.
sudo nano /etc/systemd/system/<servicename>.service
# nano 대신 vi 등을 사용해도 무방
권한은 644를 기본으로 하며, 이와 다를 경우 보안상 취약한 상태라고 볼 수 있다.
서비스 파일의 ExecStart 에는 명령어를 직접 넣을 수도 있고, 명령어 줄로 구성된 실행 파일을 넣어서 실행시킬 수도 있다.
1) ExecStart에 명령어 직접 넣기
[Unit]
Description=Test system
[Service]
Type=simple
Restart=on-failure
RestartSec=1
User=<username>
WorkingDirectory=/home/<username>/Test
ExecStart=npm start
[Install]
WantedBy=multi-user.target
나는 시스템에서 실행하다가 에러가 나면 재시작하게끔 하고 싶어서 on-failure 옵션을 넣었는데, 잘 작동할지는 에러가 나야 확인할 수 있을 것 같다ㅎ...
2) ExecStart 에 실행 파일 넣기
[Unit]
Description=Test system
[Service]
Type=simple
Restart=on-failure
RestartSec=1
User=<username>
WorkingDirectory=/home/<username>/Test
ExecStart=/home/<username>/Test/test.sh
[Install]
WantedBy=multi-user.target
작업경로에서 sudo nano test.sh 와 같은 명령어로 실행 파일을 만든다.
#!/bin/sh
npm start
위의 #!/bin/sh 를 꼭 넣어줘야 한다. 안 넣으면 에러 뱉으면서 실행을 못한다.
sudo chmod 755 test.sh
실행 파일이므로 실행 권한도 설정해야 한다.
[ Systemctl 명령어로 시스템 돌리기 ]
sudo systemctl enable --now <servicename>.service
서버를 재부팅해도 자동으로 재시작하는 서비스를 지금 실행한다. 여러 서비스를 한번에 실행하고 싶으면 && 로 이어주면 된다.
작성했던 서비스 파일을 수정한 후 반영하려면 다음의 명령어를 입력해야 한다.
sudo systemctl daemon-reload
그 외에도 자주 사용하는 systemctl 명령어는 아래와 같다.
sudo systemctl enable <servicename>.service # 재부팅 시 자동재시작하게끔 등록
sudo systemctl disable <servicename>.service # 재부팅 시 자동재시작하지 않도록 등록 해제
sudo systemctl stop <servicename>.service # 서비스 실행 멈추기
systemctl status <servicename>.service # 서비스 실행 상태 확인
특히 systemctl status 의 경우 시스템이 잘 살아있는지 확인할 수도 있지만,
해당 시스템으로 실행한 내용에 대한 로그도 10줄 정도가 함께 나오기 때문에 문제가 있는 경우 확인할 때 유용하다.
로그 몇줄만으로 문제를 확인하기 힘들다면, 다시 시도해본 후 다음 명령어로 전체에 대한 최근 로그를 조회할 수 있다.
journalctl -xe
참고자료
리눅스 명령어 매뉴얼 - systemd.service :
https://www.commandlinux.com/man-page/man5/systemd.service.5.html
systemd 옵션 참고 블로그 :
https://fmd1225.tistory.com/93
'Linux' 카테고리의 다른 글
[Linux] shell script for문 / solana airdrop / service 백그라운드 구동 / journalctl 을 통한 service 로그 확인 (0) | 2022.06.21 |
---|