노트

macOS launchd로 상주 프로세스 띄우기

Running Persistent Processes with macOS launchd

인프라#tooling · 연결된 개념 6개

쉽게 말하면

launchd는 macOS가 맡아 주는 오뚝이 관리자예요. plist에 실행할 프로그램을 적어 두면 로그인할 때 띄워 주고, 프로세스가 넘어지면 다시 일으켜 세워서 늘 떠 있게 해 줘요.

비유가 깨지는 곳 launchd는 로그인 셸 설정을 읽지 않아 PATH가 최소한이니 실행 파일은 절대 경로로 써요. 또 plist의 환경변수를 바꿨다면 kickstart -k로는 안 되고 bootout 후 bootstrap으로 다시 올려요.

launchd는 macOS의 서비스 관리자(service manager)다(리눅스의 systemd에 해당). ~/Library/LaunchAgents에 plist(property list)를 두면 로그인할 때 프로세스를 띄우고, 죽으면 다시 살린다.

plist 핵심 키

<key>Label</key><string>com.example.relay</string>
<key>ProgramArguments</key>
<array><string>/usr/local/bin/myserver</string><string>--port</string><string>8080</string></array>
<key>WorkingDirectory</key><string>/path/to/app</string>
<key>EnvironmentVariables</key>
<dict><key>PATH</key><string>/usr/local/bin:/usr/bin:/bin</string></dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/tmp/myserver.log</string>
<key>StandardErrorPath</key><string>/tmp/myserver.err</string>
  • 실행 파일은 절대 경로로 쓴다. launchd는 로그인 셸 설정을 읽지 않아 PATH가 최소한으로만 잡혀 있다(환경변수 스코프와 source)
  • RunAtLoad는 로드 즉시 실행, KeepAlive는 종료되면 재시작이다
  • 비밀값을 plist에 넣으면 파일 권한을 좁히고, 저장소에 올리지 않는다

다루는 명령

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.relay.plist  # 로드
launchctl print gui/$(id -u)/com.example.relay | grep -E "state|pid"            # 상태
launchctl kickstart -k gui/$(id -u)/com.example.relay                           # 재시작
launchctl bootout gui/$(id -u)/com.example.relay                                # 언로드

함정: plist를 고쳤는데 반영이 안 된다

kickstart -k는 이미 로드된 job 정의로 프로세스만 다시 띄운다. plist의 EnvironmentVariables를 바꿨다면 bootout으로 내린 뒤 bootstrap으로 다시 올려야 한다. bootout이 끝나기 전에 바로 bootstrap하면 "I/O error"로 실패할 수 있어, 잠깐 기다리거나 상태를 확인한 뒤 올린다. 실행 중인 프로세스가 실제로 받은 환경은 ps eww <pid>로 확인할 수 있다.

로그 파일을 보고도 원인이 안 보이면, 프로세스 안에서 예외를 삼키고 있지 않은지 의심한다(asyncio 태스크 예외가 조용히 사라지는 문제, 예외 삼키기). 포트가 계속 점유돼 있다면 KeepAlive가 되살리는 중일 수 있다(포트를 쓰는 프로세스 찾아 종료하기).

출처: man launchd.plist, man launchctl · Apple 문서 보관소: Creating Launch Daemons and Agents

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 커넥션 드레이닝과 무중단 재시작

    배포 중 서버를 재시작하는 몇 초 동안 로드밸런서가 그 서버로 요청을 보내면 502가 난다. 먼저 로드밸런서에서 빼고(드레이닝), 진행 중인 요청을 마친 뒤 재시작하고, 준비되면 다시 넣는다.

  • kubectl 디버깅 치트시트

    kubectl은 쿠버네티스 클러스터를 조회·조작하는 CLI(Command-Line Interface)다. 앱 개발자가 가장 자주 쓰는 건 "내 Pod가 떠 있나, 왜 죽었나"를 확인하는 명령들이다.

  • hosts 파일

    도메인 이름과 IP 주소의 짝을 적어 두는 OS(Operating System)의 텍스트 파일. 이름을 해석할 때 DNS 서버보다 먼저 참조되므로, 특정 도메인을 내 컴퓨터나 다른 IP로 보내는 데 쓴다. macOS·Linux는 /etc/hosts, Windows는 C:\Windows\System32\drivers\etc\hosts다.

  • Spring Boot Actuator

    Spring Boot Actuator는 운영 중인 앱의 상태를 HTTP 엔드포인트로 보여주는 모듈이다. 헬스 체크, 지표, 설정값, 로그 레벨, 등록된 빈 같은 정보를 /actuator/* 아래에 노출한다.

  • 에이전트 루프

    LLM이 도구 호출을 요청하면 실행해 결과를 돌려주고, 도구 호출 없이 답할 때까지 반복하는 구조. 코딩 에이전트도 챗봇형 에이전트도 이 반복 위에 서 있다.

보기 옵션