RHEL 계열 관리 · 서비스 유닛 파일 작성 · 실습
systemd 유닛 파일 작성
목표
systemd 유닛 파일을 처음부터 작성하고, 타이머 쌍을 만들고, 유닛 검증 스크립트를 직접 만듭니다.
왜 중요한가
유닛 파일에서 가장 중요한 한 줄은 Type= 입니다. Type=simple 인데 프로세스가 데몬화하면 systemd 는 부모가 죽는 것을 서비스 종료로 보고 실패 처리합니다. 반대로 Type=forking 인데 포그라운드로 돌면 systemd 가 영원히 기다리다 타임아웃으로 죽입니다 — "시작이 30초 멈추다 실패한다" 의 전형적인 원인입니다.
그리고 [Install] 섹션이 없으면 systemctl enable 이 아무것도 하지 않아 부팅 시 자동 시작이 안 됩니다. 이건 재부팅 전까지 아무도 모릅니다.
이 실습은 AlmaLinux 9 가상머신에서 돕니다. systemd 가 PID 1 로 실제로 돌기 때문에, 유닛 파일을 쓴 다음 systemctl enable --now 로 곧바로 띄워 볼 수 있습니다. 파일 한 줄이 어떤 동작으로 이어지는지를 눈으로 확인하면서 배웁니다.\n\n예전에는 파드에서 돌아 systemctl 자체가 없었고, 작성과 검증까지가 전부였습니다. 처음 뜨는 데 30초쯤 걸립니다.
단계
1. /etc/systemd/system/labhub-api.service 를 만들고 [Unit], [Service], [Install] 세 섹션을 넣으세요. /root/unit 작업 디렉터리도 만드세요.
2. [Unit] 에 Description, Documentation, After=network-online.target, Wants=network-online.target 네 줄을 넣으세요.
3. [Service] 에 Type=notify, ExecStart=/usr/local/bin/labhub-api --config /etc/labhub/api.yaml, User=labhub, Group=labhub, WorkingDirectory=/opt/labhub 를 넣으세요. ExecStart 는 절대 경로여야 합니다.\n\n 실습 프로그램이 셸 스크립트이므로 NotifyAccess=all 도 함께 넣습니다. 기본값 main 은 MainPID 가 보낸 알림만 받는데, systemd-notify 는 스크립트의 자식이라 PID 가 다릅니다. 그러면 systemd 가 준비 신호를 못 받고 기다리다 Job for labhub-api.service failed because a timeout was exceeded 로 끝납니다 — 유닛 파일에는 아무 문제가 없어 보입니다.
4. 재시작 정책을 추가하세요. [Service] 에 Restart=on-failure 와 RestartSec=5 를, [Unit] 에 StartLimitIntervalSec=300 과 StartLimitBurst=5 를 넣습니다. 이 둘은 systemd 230 에서 [Service] 에서 [Unit] 로 옮겨 갔습니다 — [Service] 에 적으면 Unknown key name 만 남기고 조용히 무시되어 속도 제한이 걸리지 않습니다.
5. 보안 지시자를 추가하세요. NoNewPrivileges=true, ProtectSystem=strict, ProtectHome=true, PrivateTmp=true, ReadWritePaths=/var/lib/labhub /var/log/labhub
6. [Install] 에 WantedBy=multi-user.target 을 넣고, systemctl enable 이 만들 심링크를 직접 만드세요. 경로는 /etc/systemd/system/multi-user.target.wants/labhub-api.service 이고 원본 유닛을 가리켜야 합니다.
7. 타이머 쌍을 만드세요. /etc/systemd/system/labhub-backup.service(Type=oneshot, ExecStart 절대 경로)와 /etc/systemd/system/labhub-backup.timer([Timer] 에 OnCalendar=*-*-* 02:30:00, Persistent=true, Unit=labhub-backup.service, [Install] 에 WantedBy=timers.target).
8. /root/unit/lint.sh 를 작성하세요. 첫 번째 인자로 유닛 파일 경로를 받아 다음 세 규칙을 검사하고, 위반이 없으면 종료 코드 0, 있으면 0 이 아닌 값으로 끝나야 합니다.
- 규칙 1:
[Unit],[Service],[Install]세 섹션이 모두 있어야 한다 - 규칙 2:
ExecStart값이 절대 경로(/로 시작)여야 한다 - 규칙 3:
Type=forking이면PIDFile=이 있어야 한다
채점기가 여러분의 labhub-api.service(통과해야 함)와 /opt/fixtures/unit/bad-sample.service(실패해야 함) 양쪽으로 실행합니다.
참고
- 심링크는
ln -sf /etc/systemd/system/labhub-api.service /etc/systemd/system/multi-user.target.wants/labhub-api.service로 만듭니다. 디렉터리를 먼저 만드세요. ExecStart에는 셸 기능(파이프, 리다이렉션, 와일드카드)이 동작하지 않습니다. 필요하면/bin/bash -c '...'로 감쌉니다.- 이 실습은 AlmaLinux 9 VM 에서 돌고 systemd 가 PID 1 입니다. 그래서 쓴 유닛을
systemctl start로 곧바로 띄워 볼 수 있고, 8번이 실제로 그렇게 채점합니다. 예전에는 파드에서 돌아systemctl자체가 없었습니다. - 6번의 심링크는 손으로 만들어도 되지만,
systemctl enable이 만드는 것과 결과가 같다는 것을 한 번 확인해 두세요. 나중에 "enable 했는데 왜 안 뜨지" 를 만났을 때 심링크부터 보게 됩니다. - 흔한 실수 1: 8번 스크립트가 섹션 헤더를 부분 문자열로 매칭해
[Unit]과[Unite]를 구분하지 못하는 경우. - 흔한 실수 2: 6번에서 심링크가 아니라 파일을 복사하는 경우. systemd 는 심링크로 관리합니다.
단계 8개
- 세 섹션 골격
- [Unit] 섹션 채우기
- [Service] 실행 설정
- 재시작 정책
- 보안 강화
- [Install] 과 enable 결과
- 타이머 쌍 작성
- 유닛 검증 스크립트