시스템 · 서버핵심 · 21분 읽기

프로세스·서비스와 systemd

실행 중인 프로세스, 서비스 관리 상태, 실제 서비스 응답은 별도로 확인해야 합니다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. unit 정의·관리 상태·실제 프로세스·업무 응답을 구분한다.
  2. 서비스 시작과 실패·재시작을 시간 순서로 해석한다.
  3. enable·start·reload·daemon-reload의 적용 대상을 설명한다.
먼저 알면 좋은 개념사용자·그룹과 최소 권한

01fork·exec·wait로 읽는 프로세스 생성과 종료

  • 는 실행 중인 프로그램의 주소 공간, 자격 정보, 열린 파일, 실행 상태 등을 묶은 커널 관리 단위다.
  • 전형적인 셸 실행에서는 계열로 자식을 만들고 자식이 계열로 새 프로그램 이미지를 적재한다.
  • 는 성공하면 같은 PID에서 프로그램을 교체하므로 새 PID를 하나 더 만드는 호출과 다르다.
  • 부모는 계열로 자식의 종료 상태를 회수할 수 있다.
  • 종료 코드는 성공·실패 의미를 프로그램이 정하는 통신 수단이다.
  • ps의 PID는 식별자, PPID는 현재 부모, SID는 세션, PGID는 그룹을 뜻한다.
  • R은 실행 가능, S는 인터럽트 가능한 대기, D는 보통 I/O 관련 비인터럽트 대기, Z는 실행을 끝냈지만 종료 상태가 회수되지 않은 좀비다.
    • 가 CPU를 계속 계산하는 상태라는 해석은 틀리다.
  • 은 정상 정리 기회를 주는 종료 요청이고 SIGKILL은 프로그램의 정리 핸들러로 처리할 수 없는 강제 종료다.
bash
ps -eo pid,ppid,sid,pgid,tty,stat,cmd
ps -p 1234 -o pid,ppid,stat,etime,%cpu,%mem,args
# STAT의 문자와 실제 활동을 구분해서 읽는다.

교육용 예시. 표시된 결과는 설명을 위한 예시이며 실제 실행 기록이 아니다.

02전통적인 데몬화와 systemd 서비스의 차이

  • 전통적인 데몬은 뒤 부모를 끝내고 setsid로 새 세션을 만들어 제어 터미널에서 분리한다.
  • 다시 하여 최종 자식을 세션 리더가 아니게 만드는 형태는 제어 터미널을 다시 획득하는 일을 방지하려는 목적이 있다.
    • 작업 디렉터리와 , 열린 파일 디스크립터도 서비스 요구에 맞게 조정한다.
  • 부모가 끝난 자식은 PID 1 또는 설정된 subreaper에 입양될 수 있으므로 PPID가 1이라는 사실만이 데몬의 정의는 아니다.
  • 아래에서는 프로그램이 불필요하게 이중 를 하지 않고 전경에서 계속 실행되도록 두는 방식이 자연스럽다.
  • 서비스 관리자가 주 와 cgroup 안의 자식들을 추적하고 표준 출력·오류를 journal로 수집할 수 있기 때문이다.
  • 명령 뒤에 &를 붙이면 셸의 백그라운드 작업이 되지만 재시작 정책, 부팅 의존성, 상태 보고까지 갖춘 관리 서비스가 자동으로 만들어지는 것은 아니다.

03Type 값은 시작 명령의 완료 기준을 정한다

  • Type=simple은 서비스 를 생성한 이른 시점에 시작이 진행된 것으로 취급하며 자체가 실패하는 경우를 뒤늦게 볼 수 있다.
  • Type=는 프로그램 이미지 실행이 성공했는지를 시작 성공 판정에 반영한다.
    • 하지만 두 값 모두 데이터베이스 초기화나 HTTP 리스너 준비가 끝났다는 애플리케이션 수준의 신호를 보장하지 않는다.
  • 는 애플리케이션이 준비 완료 알림을 보내도록 협력하는 방식이다.
  • 은 초기화·일회 작업의 명령 종료를 기다리며 RemainAfterExit=yes는 가 종료된 뒤에도 unit을 활성 상태로 유지하도록 할 수 있다.
  • Type=forking은 전통적 백그라운드 분기 프로그램에 맞추며 PIDFile 등의 주 식별이 필요할 수 있다.
  • running, exited, failed는 각 Type의 의미와 함께 읽어야 한다.
Type중심이 되는 완료/준비 조건
simple 생성 후 이른 시작 판단
프로그램 성공
notify프로그램의 준비 완료 통지
oneshot일회 명령의 성공적 종료
forking부모 종료와 남은 데몬 추적

04unit 파일의 각 절을 요청 처리와 연결하기

  • 는 service뿐 아니라 socket, timer, mount, target 등 여러 unit을 관리한다.
  • unit 유형은 관리하는 대상과 활성화 방식을 구분한다.
  • [Unit]은 설명과 의존 관계를, [Service]는 실행 방법과 속성을, [Install]은 enable할 때 만들 연결을 선언한다.
  • =는 상대적인 시작 순서를 정하고 =나 =는 다른 unit을 함께 끌어오는 관계를 표현한다.
  • =network-online.target만 적었다고 그 target을 반드시 시작하는 것도 아니고, network-online이 외부 인터넷 연결을 영구 보장하는 것도 아니다.
    • 실제로 네트워크 준비가 필요한 서비스에만 해당 의존성을 둔다.
  • User=와 Group=은 실행 자격 정보, WorkingDirectory=는 상대 경로 기준, =는 새 파일 생성 권한 상한에 영향을 준다.
  • ExecStart는 일반 셸 명령줄 전체와 같지 않으므로 파이프나 리다이렉션을 셸처럼 무조건 해석한다고 가정하지 않는다.
  • 아래 예시는 /opt/demo/app.py를 미리 설치한 별도 서비스 계정으로 실행한다는 가정이다.
  • Restart=on-failure는 실패 때 다시 시작하게 하지만 프로그램의 버그를 고치거나 매번 상태를 초기화하지 않는다.
ini
[Unit]
Description=Study demo application

[Service]
Type=exec
User=demo
Group=demo
WorkingDirectory=/srv/demo
ExecStart=/usr/bin/python3 /opt/demo/app.py
Restart=on-failure
RestartSec=3s
UMask=0027

[Install]
WantedBy=multi-user.target

교육용 예시. 표시된 결과는 설명을 위한 예시이며 실제 실행 기록이 아니다.

05설정 반영과 서비스 생애를 시간 순서로 추적하기

  • unit 파일을 수정한 뒤 를 수행하면 가 unit 정의와 의존 관계를 다시 읽는다.
  • 이 작업만으로 애플리케이션 를 재시작하거나 그 프로그램의 설정 파일을 다시 읽게 하지는 않는다.
  • start는 현재 unit을 시작하고, enable은 지정 target에서 활성화할 연결을 만든다.
    • enable --now는 등록과 현재 시작을 함께 요청한다.
  • 애플리케이션의 reload 지원 여부는 ExecReload나 프로그램의 동작에 달려 있다.
  • 정상 동작은 inactive에서 activating, Type에 맞는 준비 조건 뒤 active로 이어진다.
  • 주 가 비정상 종료하면 failed가 되거나 재시작 정책에 따라 다시 activating으로 갈 수 있다.
    • start 제한에 도달하면 반복 재시작이 중단될 수 있다.
  • 상태 변화의 이유는 MainPID, ExecMainStatus, journal의 같은 시각 기록을 함께 읽어야 한다.
  • active는 관리자가 보는 생애 상태이며 실제 요청 처리 성공은 리스너와 정상 응답으로 별도 설명할 수 있다.
  • reload는 서비스가 지원하는 설정 재읽기이며 restart는 중지와 재시작을 포함한다.
bash
systemctl cat demo.service
systemctl show demo.service -p Type -p MainPID -p SubState -p ExecMainStatus
journalctl -u demo.service -b --no-pager
# 명령은 unit 정의·현재 상태·이번 부팅 기록을 각각 보여 준다.

교육용 예시. 표시된 결과는 설명을 위한 예시이며 실제 실행 기록이 아니다.

LEARN THROUGH PRACTICE

SSH 서비스의 active와 enabled를 서로 다른 상태로 읽기

서비스를 지금 시작하는 작업과 부팅 시 시작 경로를 등록하는 작업을 실제 status 출력으로 구별한다.

실습 구성과 전제

  • 의 ssh.service 화면을 사용한다. 에 따라 같은 서버 프로그램의 unit 이름이 sshd.service일 수 있다.
  • 첫 화면과 enable 이후 화면은 Main PID와 시작 시각을 대조해 읽는다. socket activation 표시도 함께 살핀다.
  1. 현재 실행 상태를 읽는다

    • start는 현재 서비스의 시작을 요청한다. status의 Active가 active (running)이면 해당 unit의 실행 상태를 뜻하며, 부팅 때 자동 시작하도록 등록되었다는 뜻은 아니다.
    • ExecStartPre의 sshd 검사 성공과 Main PID를 보면 사전 검사와 실제 서버 를 분리할 수 있다. TriggeredBy의 ssh.socket은 별도 소켓 unit과의 관계다.
    bash
    systemctl status ssh.service --no-pager
    systemctl is-active ssh.service
    systemctl is-enabled ssh.service

    예시다. 각 조회 명령의 출력과 종료 상태는 서로 다른 질문에 답한다.

    4/22 · 명령·상태 출력 2원본 크기로 보기 ↗
    • Loaded 줄에는 disabled가 보이지만 Active 줄은 active (running)이다.
    • ExecStartPre의 종료 상태는 성공이고 Main PID가 표시되며, ssh.socket의 트리거 관계도 나타난다.
    확인 기준

    active와 disabled는 모순이 아니다. 실행 여부와 unit 파일의 활성화 상태를 별도 열로 기록한다.

  2. enable이 만든 시작 경로를 확인한다

    • enable은 unit의 설치 지시를 바탕으로 target의 같은 연결을 만든다. 기본 enable만으로 현재 를 새로 실행하거나 재시작하지 않는다.
    • 화면에서는 enable 후 생성 메시지가 나오고 Loaded가 enabled로 바뀐다. 이미 실행 중인 서버의 Main PID가 이어지는 것이 이 차이를 보여 준다.
    4/22 · 명령·상태 출력 3원본 크기로 보기 ↗
    • enable 출력에 multi-user.target.wants/ssh.service 등의 연결 생성 메시지가 보인다.
    • Loaded는 enabled로 바뀌었고 Active는 여전히 running이며 기존 Main PID가 유지된다.
    확인 기준

    enable --now는 활성화 등록과 현재 시작을 함께 요청하는 옵션이다. enable만 실행한 사례와 구분한다.

  3. 서비스·소켓·부팅 경로를 함께 대조한다

    • is-enabled만으로 서버가 현재 수신 가능한지 판단하지 않는다. 서비스 실패, 소켓 unit의 활성화, 실제 리슨 포트를 각각 확인한다.
    • 소켓 활성화를 사용하는 에서는 ssh.socket이 연결을 받아 서비스를 시작할 수 있다. unit 파일과 현재 소켓 상태를 확인한 뒤 해당 환경의 시작 경로를 설명한다.
    bash
    systemctl status ssh.socket --no-pager
    systemctl cat ssh.service ssh.socket
    ss -lnt

    설치된 unit 구성과 리슨 소켓을 읽기 전용으로 대조한다.

    확인 기준

    서비스가 active인데 접속되지 않으면 포트·주소·방화벽을 확인한다. 서비스가 inactive여도 소켓 활성화가 설정되었는지 먼저 살핀다.

실습에서 확인한 동작 원리

  • active는 현재 실행 상태이고 enabled는 시작 의존 관계를 등록한 상태다.
  • 설정 파일, unit 활성화, , 리슨 소켓은 서로 연결되지만 같은 증거는 아니다.
동작·명령 참고 문서
원리 심화

06요구 의존성과 순서 의존성은 서로 다른 그래프다

  • =db.service는 함께 시작할 대상을 끌어오지만 그 시작 실패가 항상 요청한 서비스를 실패시키지는 않는다. 기능을 선택적으로 결합할 때 쓰는 약한 요구 관계다.
  • 와 를 함께 두면 선행 unit의 시작 실패 때문에 후행 unit의 시작이 진행되지 않을 수 있다. 만으로 선행 시작 완료를 기다리는 순서가 생기지는 않는다.
  • =db.service는 두 작업이 모두 예정됐을 때 시작 순서를 정한다. db.service를 시작 작업에 포함시키는 효과는 · 등 별도 관계에서 온다.
  • Type=의 시작 완료는 프로그램 실행 성공이며 DB 연결·캐시 적재 완료는 아니다. 는 프로그램이 지원하는 READY=1 통지를 준비 경계로 사용할 수 있다.
  • Condition 계열 불충족으로 건너뛰는 경우와 가 시작하다 실패하는 경우는 다르다. 의존 unit이 항상 active여야 한다는 요구는 만으로 모두 표현되지 않는다.
예제로 추적

07RestartSec와 종료 상태를 시간선에 대입하기

  • 가상 서비스가 Type=, Restart=on-failure, RestartSec=3s로 설정됐고 성공 종료 상태를 따로 변경하지 않았다고 가정한다.
  • t=5s에 주 가 exit status 1로 끝나면 실패로 기록된다. 재시작이 허용되면 약 3초 뒤 새 시작 작업을 시도하지만 정확한 시각은 스케줄링 등에 달렸다.
  • 다음 실행이 status 0으로 종료되면 on-failure는 이 정상 종료만을 이유로 재시작하지 않는다. 계속 돌아야 한다는 업무 요구와 Restart 정책을 구분한다.
  • systemctl stop에 의한 중지는 실패 재시작 루프를 만들기 위한 사건이 아니다. 상태 변화의 원인이 자체 실패인지 관리 요청인지 로그에서 구분한다.
  • 짧은 시간에 실패가 반복되면 StartLimitIntervalSec·StartLimitBurst의 제한으로 다음 시도가 차단될 수 있다. RestartSec만 늘리거나 줄여 설명할 수 없는 상태다.
text
ActiveState=failed
SubState=failed
Result=exit-code
ExecMainCode=1
ExecMainStatus=1

systemctl show 형식의 가상 필드 예. ExecMainCode=1은 종료 원인 분류이고 ExecMainStatus=1이 프로그램의 종료 상태다.

검증·예외

08실행 전 실패와 프로그램 내부 실패의 출력 차이

  • 실행 파일이 없거나 실행할 수 없으면 서비스 프로그램의 업무 코드에 들어가기 전에 실패한다. journal의 실행 경로와 시스템 오류를 앱 로그 부재와 함께 해석한다.
  • 프로그램이 실행된 뒤 오류로 끝나면 종료 코드·시그널과 같은 시각의 앱 로그가 근거다. ExecMainStatus를 포트 번호나 재시작 횟수로 읽지 않는다.
  • systemctl cat은 unit 구성, 앱의 설정 검사는 프로그램 고유 설정을 다룬다. 가 앱 내부 설정을 검사하거나 새 실행 파일을 자동으로 기동하는 것은 아니다.
  • active/running인데 소켓이 없다면 초기화·바인딩·작업자 상태를 본다. Type의 시작 성공 기준이 사용자 요청을 처리할 준비 기준과 얼마나 일치하는지 확인한다.
  • start-limit-hit은 반복 시작이 제한됐다는 후속 상태다. 최초 실패의 실행 경로·권한·의존 연결 오류를 찾아야 제한 해제 후 같은 루프가 반복되지 않는다.

09장애를 시간 순서로 확인하기

  • unit 상태, 실제 , 리스닝 소켓, 로그, 요청 응답을 연결한다.
  • 실패 시각과 재시작 횟수, 종료 코드, 권한 오류를 먼저 확인하면 원인을 좁힐 수 있다.
  • 로그에는 운영 정보가 포함될 수 있으므로 공유할 때 필요한 부분만 남긴다.
bash
ps -eo pid,ppid,user,stat,comm
systemctl --failed
journalctl -b -p warning --no-pager -n 20

와 실패한 unit, 현재 부팅의 최근 경고 로그를 조회한다. 로그 접근에는 추가 권한이 필요할 수 있다.

10서비스가 시작되고 실패하는 과정

  • 관리자가 서비스 시작을 요청하면 는 unit 정의와 관계를 바탕으로 작업을 구성한다.
  • 필요한 의존 unit과 순서 조건이 처리되고 ExecStart 등의 설정에 따라 를 시작한다.
  • 어느 시점에 시작 완료로 판단하는지는 Type 등 서비스 정의에 달려 있다.
    • 예를 들어 Type=simple은 를 시작한 뒤 서비스가 시작됐다고 간주하는 방식이고, 는 준비 알림을 기다리는 방식이다.
    • 두 방식은 애플리케이션 준비 상태에 대한 기대가 다르다.
  • 서비스가 시작된 다음에도 주 가 비정상 종료할 수 있다.
  • 종료 상태와 Restart 정책, 시작 제한 등 조건에 따라 재시작하거나 failed로 남는다.
  • 반복 재시작은 원인 해결을 뜻하지 않으며 짧게 살아나는 만 보면 정상처럼 오해하기 쉽다.
  • 네트워크 연결이나 데이터베이스 초기화가 필요한 프로그램은 자신의 준비 상태를 어떻게 알릴지 설계해야 한다.
    • 단순한 관계만으로 의존 애플리케이션의 업무 준비 완료가 보장되지는 않는다.
  1. 현재 로드된 unit 정의를 사용해 작업을 만든다.
  2. 실행 사용자와 환경, 명령이 적용된다.
  3. Type에 맞는 기준으로 unit 상태가 바뀐다.
  4. 종료 코드와 시그널, 해당 시각의 로그를 남긴다.
  5. 조건이 맞으면 재시작하고 제한 등에 걸리면 실패 상태를 확인한다.

설명과 함께 보는 보조 도식

처리 과정

서비스 시작과 비정상 종료

일반적인 service unit의 생애를 관찰하기 위한 흐름이다.

전체 단계를 한 번에 읽기
  1. 01

    시작 요청

    현재 로드된 unit 정의를 사용해 작업을 만든다.

    요청
    start
  2. 02

    프로세스 실행

    실행 사용자와 환경, 명령이 적용된다.

    관찰
    PID·실행 계정
  3. 03

    시작 완료 판정

    Type에 맞는 기준으로 unit 상태가 바뀐다.

    업무 정상
    별도 응답 확인 필요
  4. 04

    비정상 종료

    종료 코드와 시그널, 해당 시각의 로그를 남긴다.

    관찰
    Result·종료 상태
  5. 05

    정책 적용

    조건이 맞으면 재시작하고 제한 등에 걸리면 실패 상태를 확인한다.

    다음 동작
    정책·제한에 따라 다름
학습 모식도. 표시한 조건에서의 객체와 상태 변화를 설명하며 운영 환경의 모든 분기와 내부 구현은 생략한다.

11상태·로그·소켓으로 실패 지점 찾기

  • unit 자체를 찾지 못하면 설치 경로와 이름, 로드된 정의부터 확인한다.
  • 가 시작되지 않으면 실행 파일, 계정, 작업 디렉터리, 환경과 권한 오류를 살펴본다.
  • 가 있는데 리스닝 소켓이 없다면 초기화 실패나 다른 주소에 바인딩했는지를 조사한다.
  • 소켓이 있어도 대표 요청이 실패하면 애플리케이션과 외부 의존성 단계로 범위를 옮긴다.
  • 이 순서는 관찰 범위를 넓히는 진단 절차이지 재시작을 반복하는 절차가 아니다. journal의 시간과 부팅 식별 범위를 맞추고 같은 시각의 종료 코드·재시작 횟수를 연결한다.
  • unit 파일 변경을 읽히는 와 서비스 내부 설정을 다시 읽는 reload는 관찰 결과도 다르다.
  • 부팅 자동 시작을 enable해도 이미 실행 중인 의 내용이 바뀌지 않는다.
  • 설정 반영 뒤에는 새 또는 새 설정이 실제 요청에 사용되는지 검증해야 한다.
  • 로그 보존이 제한된 환경에서는 과거 기록이 없다는 사실을 오류가 없었다는 증거로 삼지 않는다.
    • 가 존재한다고 요청을 정상 처리하는 것은 아니다.
  • CPU 사용률이 낮아도 잠금, 디스크 대기, 연결 고갈 때문에 응답하지 못할 수 있다.
구성·관찰 대상확인하는 정보구분할 문제
unit 로드 상태정의 존재·문법·의존성잘못된 이름과 경로
PID·계정·종료 코드실행 실패와 반복 종료
소켓주소·포트·프로토콜다른 주소에 리스닝
실제 요청응답·지연·업무 결과실행 중이나 기능은 실패
학습 확인

이해했는지 확인하기

  • active와 애플리케이션 응답을 각각 확인한다.
  • enable과 start, reload와 daemon-reload를 구분한다.
  • 실패 시각·종료 코드·로그를 연결한다.

함께 확인할 공식 자료