공공부하자개발 · 영어 학습 노트
리눅스
리눅스 운영RHEL 에서 배포·운영하기0/13 완료
  • 01셸 기본과 파일 시스템
  • 02텍스트 처리 (grep·sed·awk)
  • 03프로세스·서비스 관리 (systemd)
  • 04자바 애플리케이션 배포
  • 05Tomcat·nginx 배포·재기동
  • 06로그·디스크 운영 (logrotate·cron)
  • 07네트워크 진단 (ss·curl·firewalld)
  • 08셸 스크립트 (운영 자동화)
  • 09장애 대응 (jstack·jmap·OOM killer·FD)
  • 10보안 기초 (ssh 강화·SELinux·감사 로그)
  • 11컨테이너 기초 (podman)
  • 12백업·복구 (설정·릴리스·DB 덤프)
  • 13Docker 실무
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 리눅스 운영 › 03 / 13

프로세스·서비스 관리 (systemd)

섹션 7진행 0 / 13
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 프로세스와 PID

실행 중인 프로그램 하나가 프로세스이고, 커널이 번호(PID)를 붙입니다. 모든 프로세스는 부모(PPID)가 있고, 부모가 죽으면 자식은 PID 1 인 systemd 에게 입양됩니다.

변수·명령 뜻
$ 현재 셸의 PID
$! 직전 백그라운드 PID
$PPID 부모 PID
ps -ef 전체 프로세스 목록
ps -p PID -o ... 특정 프로세스의 지정 열

자바 앱을 띄운 셸이 부모이고 java 가 자식입니다. 셸이 닫히면 자식은 HUP 신호를 받아 함께 죽는 것이 기본 동작입니다.

2.2 프로세스 찾기

ps -ef | grep java 가 가장 흔한 조합입니다. grep 자기 자신도 목록에 나오므로 grep -v grep 을 붙이거나 pgrep 을 씁니다.

bash
ps -ef | grep '[j]ava'          # [j] 트릭: grep 자신은 제외
pgrep -f 'app.jar'              # 명령줄 전체에서 찾아 PID 만
jps -l                          # JDK 도구: 자바 프로세스만 클래스·jar 이름과 함께

jps 는 JDK 에 들어 있습니다. 같은 서버에 자바 앱이 여러 개면 ps 보다 jps -l 이 한눈에 들어옵니다.

2.3 신호와 kill

kill 은 프로세스에 신호를 보내는 명령입니다. 기본 신호 TERM 은 "정리하고 종료해라" 라는 요청이고, 프로세스가 무시할 수도 있습니다.

신호 번호 뜻 자바 앱 반응
TERM 15 종료 요청 shutdown hook 실행
KILL 9 즉시 강제 종료 정리 없이 사라짐
HUP 1 터미널 끊김 기본은 종료
INT 2 Ctrl+C TERM 과 비슷
0 - 생존 확인만 신호 안 보냄

kill -9 는 마지막 수단입니다. Spring 의 @PreDestroy, DB 커넥션 반납, 처리 중인 요청 마무리가 모두 건너뛰어집니다. 먼저 kill PID 를 보내고 30초쯤 기다린 뒤에도 안 죽을 때만 씁니다.

2.4 종료 코드 읽기

프로세스가 신호로 죽으면 종료 코드는 128 + 신호 번호 입니다. 로그나 systemctl status 에서 이 숫자를 보면 무슨 일이 있었는지 바로 압니다.

종료 코드 뜻
0 정상 종료
1 앱이 스스로 에러 종료
137 128+9, KILL 로 죽음 (OOM killer 흔함)
143 128+15, TERM 으로 죽음 (정상 정지)

자바 앱이 TERM 을 받아 정상 정지하면 143 을 돌려줍니다. systemd 는 0 이 아닌 종료를 실패로 보므로 유닛 파일에 SuccessExitStatus=143 을 적어 줍니다.

2.5 백그라운드와 nohup

명령 끝에 & 를 붙이면 백그라운드로 돌고 즉시 프롬프트가 돌아옵니다. 그러나 터미널을 닫으면 HUP 신호로 함께 죽습니다.

bash
nohup java -jar app.jar > app.out 2>&1 &    # HUP 무시, 출력은 파일로
jobs                                          # 현재 셸의 백그라운드 작업
disown                                        # 이미 띄운 작업을 셸에서 떼기

nohup 은 임시 실행에는 충분하지만 재부팅 시 자동 시작이 없고 재시작도 안 됩니다. 운영 서비스는 반드시 systemd 로 올립니다.

2.6 systemd 와 유닛 파일

systemd 는 PID 1 로 부팅 직후 뜨는 관리자입니다. 서비스 하나가 유닛 파일 하나이고, 위치는 /etc/systemd/system/이름.service 입니다.

유닛 파일은 세 섹션으로 나뉩니다. [Unit] 은 설명과 순서, [Service] 는 실행 방법, [Install] 은 부팅 시 소속입니다.

ini
[Unit]
Description=MyApp Spring Boot service
After=network-online.target

[Service]
User=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=-/opt/myapp/conf/env
ExecStart=/usr/bin/java $JAVA_OPTS -jar /opt/myapp/current/app.jar
SuccessExitStatus=143
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

ExecStart 는 절대 경로여야 하고 셸 문법(&&, |, >)을 쓸 수 없습니다. 복잡하면 실행 스크립트를 만들어 그 경로를 적습니다. 04 레슨에서 그 스크립트를 만듭니다.

2.7 [Service] 주요 키

키 뜻 권장 값
Type 시작 방식 simple
User Group 실행 계정 전용 계정
EnvironmentFile 환경 변수 파일 - 붙여 없어도 통과
Restart 재시작 조건 on-failure
RestartSec 재시작 대기 5
TimeoutStopSec 정지 대기 후 KILL 30
LimitNOFILE 열 수 있는 파일 수 65536

Type=simple 은 ExecStart 프로세스가 곧 서비스라는 뜻으로, 자바 앱에 맞습니다. LimitNOFILE 은 커넥션이 많은 앱에서 "Too many open files" 를 막습니다. 08 장애 대응에서 다시 봅니다.

2.8 systemctl 명령

유닛 파일을 만들거나 고친 뒤에는 반드시 daemon-reload 를 먼저 합니다. 이걸 빼면 systemd 가 옛 내용을 그대로 씁니다.

명령 뜻
systemctl daemon-reload 유닛 파일 다시 읽기
systemctl start myapp 지금 시작
systemctl stop myapp 지금 정지
systemctl restart myapp 정지 후 시작
systemctl status myapp 상태·PID·최근 로그
systemctl enable myapp 부팅 시 자동 시작
systemctl enable --now myapp enable + start
systemctl is-active myapp 스크립트용 상태 확인

start 와 enable 은 다릅니다. start 만 하면 재부팅 뒤 안 뜨고, enable 만 하면 지금은 안 뜹니다. 둘 다 필요하면 enable --now 로 한 번에 합니다.

2.9 journalctl 로 로그 보기

systemd 로 띄운 서비스의 표준 출력은 저널에 모입니다. 파일을 찾을 필요 없이 journalctl -u 서비스 로 봅니다.

명령 뜻
journalctl -u myapp 서비스 로그 전체
journalctl -u myapp -f 실시간 따라가기
journalctl -u myapp -n 100 최근 100줄
journalctl -u myapp --since '1 hour ago' 시간 범위
journalctl -u myapp -p err 에러 이상만

02 레슨의 grep 을 그대로 붙일 수 있습니다. journalctl -u myapp --since today | grep -c ERROR 가 오늘 에러 수입니다.

2.10 자원 확인

프로세스가 살아 있어도 CPU 나 메모리를 다 쓰고 있으면 장애입니다. 세 명령으로 현재 상태를 봅니다.

bash
top -b -n 1 | head -n 15        # 한 화면만 찍고 종료, CPU·MEM 상위
uptime                          # 부하 평균 1·5·15분, 코어 수보다 크면 과부하
free -m                         # 메모리 MB, available 열을 본다

top 을 대화형으로 열면 P 는 CPU 순, M 은 메모리 순 정렬, q 로 나옵니다. top -p PID 로 자바 프로세스 하나만 볼 수도 있습니다.

2.11 완전 종료 흐름

systemctl stop myapp 을 치면 다음 순서로 진행됩니다. 자바 앱이 이 흐름에 맞게 동작하는지가 무중단 배포의 전제입니다.

  1. systemd 가 TERM 을 보냅니다.
  2. JVM 이 shutdown hook 을 돌립니다. Spring 은 빈의 @PreDestroy 를 호출하고 커넥션 풀을 닫습니다.
  3. TimeoutStopSec 안에 안 끝나면 KILL 을 보냅니다.

Spring Boot 는 server.shutdown=graceful 로 완전 종료를 켜면 처리 중인 요청을 마친 뒤 종료합니다. 이때 spring.lifecycle.timeout-per-shutdown-phase 가 TimeoutStopSec 보다 짧아야 KILL 을 피합니다.

2.12 전용 계정으로 실행

앱은 root 가 아닌 전용 계정으로 띄웁니다. 계정을 만들고 배포 폴더 소유권을 주는 것이 유닛 파일 작성 전 준비입니다.

bash
sudo useradd -r -s /sbin/nologin myapp          # 로그인 불가 시스템 계정
sudo mkdir -p /opt/myapp /var/log/myapp
sudo chown -R myapp:myapp /opt/myapp /var/log/myapp

-s /sbin/nologin 은 이 계정으로 ssh 접속을 막습니다. 앱이 뚫려도 공격자가 얻는 권한이 이 계정 범위로 제한됩니다.

핵심 원리
  • 2.1 프로세스와 PID
  • 2.2 프로세스 찾기
  • 2.3 신호와 kill
  • 2.4 종료 코드 읽기
  • 2.5 백그라운드와 nohup
  • 2.6 systemd 와 유닛 파일
  • 2.7 [Service] 주요 키
  • 2.8 systemctl 명령
  • 2.9 journalctl 로 로그 보기
  • 2.10 자원 확인
  • 2.11 완전 종료 흐름
  • 2.12 전용 계정으로 실행
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제