A-8: 시스템 서비스와 패키지 관리
목차 25
안녕하세요, 홍순구입니다. 지난 시간 우리는 살아 움직이는 프로세스를 들여다보고(ps·top), 말 안 듣는 녀석을 멈추고(kill), 무거운 작업을 백그라운드로 돌리는(잡 제어) 것까지 해냈어요. 마지막에 nohup으로 "터미널을 닫아도 안 죽는" 백그라운드 실행을 맛봤는데, 그때 제가 솔직하게 말씀드렸죠. nohup은 임시방편이라고요.
오늘은 그 임시방편을 졸업합니다. 현업 서버에서 진짜 필요한 건 내가 터미널에서 손으로 띄우는 게 아니라, 시스템이 알아서 챙기는 것이에요. 서버 전원을 켜면 웹 서버가 저절로 뜨고, 어쩌다 죽으면 알아서 되살아나고, 매일 새벽엔 백업이 손 하나 안 대도 돌아가는 — 그걸 가능하게 하는 주인공이 오늘의 systemd예요. 그리고 그 모든 프로그램을 한 줄로 설치해주는 패키지 매니저(apt), 반복 작업을 정해진 시각에 예약하는 cron까지 오늘 함께 다룹니다.
겁먹을 것 없어요. 지난 시간에 프로세스를 손으로 다뤄봤으니, 오늘은 그걸 시스템에게 "정식으로 맡기는" 법을 배우는 것뿐이에요. 한 단계씩 천천히 가볼게요.
오늘 걸어갈 길을 한눈에 그려둘게요.
오늘의 여정 — 손으로 띄우던 걸 시스템에 맡기기
──────────────────────────────────────────────────
Step 1 systemd 란? — 부팅 때부터 모든 걸 챙기는 관리자 (PID 1)
Step 2 소프트웨어 설치 — 패키지 매니저로 한 줄에 (apt·dnf)
Step 3 서비스 다루기 — systemctl 로 켜고 끄고 자동 기동
Step 4 서비스의 설정서 — 유닛 파일 읽기
Step 5 로그 들여다보기 — journalctl 로 무슨 일이 있었나
Step 6 매일 저절로 — cron 으로 작업 예약하기
오늘 실습은 가벼운 웹 서버 하나(nginx)를 직접 설치해서, 그걸 서비스로 켜고 끄고 로그를 들여다보는 식으로 죽 이어집니다. 망가뜨릴 일은 없으니 편하게 따라오세요. (실습 환경은 지난 시간들처럼 여러분의 Ubuntu — WSL2·VM·클라우드 — 기준이에요. 맥 기본 터미널에는 systemd가 없어서, 오늘 실습은 리눅스 환경에서 따라오시는 걸 권합니다.)
💡 오늘 수업의 핵심 — "프로그램을 systemd 서비스로 맡기면 부팅과 함께 저절로 뜨고 죽어도 되살아난다 — apt 로 설치하고, systemctl 로 다루고, journalctl 로 들여다보고, cron 으로 예약한다"
지난 시간 프로세스를 다룰 때는 내가 직접 띄우고(&) 내가 직접 멈췄어요(kill). 오늘은 그 일을 시스템에게 맡깁니다. "이 프로그램은 부팅하면 자동으로 띄워줘, 죽으면 되살려줘"라고 등록해두면, 내가 자는 사이에도 시스템이 알아서 챙겨주죠. 그 등록·관리의 중심에 systemd가 있고, 설치는 apt, 로그는 journalctl, 예약은 cron이 맡습니다. 오늘 이 넷이 서로 어떻게 맞물리는지를 손에 익혀요.
🎯 학습 목표
systemd가 무엇인지(부팅 때 가장 먼저 뜨는 PID 1, 모든 서비스의 관리자) 이해하고, 지난 시간의nohup임시방편과 무엇이 다른지 구분한다- 패키지 매니저(
apt, 레드햇 계열dnf)로 소프트웨어를 설치하고,systemctl로 서비스를 시작·중지하고 부팅 자동 기동(enable)을 건다 - 유닛 파일을 읽어 서비스의 정체를 파악하고,
journalctl로 서비스 로그를 추적하며,cron으로 "매일 정해진 시각에 도는" 반복 작업을 예약한다
Step 1: "systemd 란? — 부팅 때부터 모든 걸 챙기는 관리자"
지난 시간 프로세스 트리를 거슬러 올라가다 보면 결국 PID 1에 닿는다고 했죠. 모든 프로세스의 조상, 부팅할 때 가장 먼저 뜨는 단 하나의 프로세스. 그게 바로 오늘의 주인공 systemd예요. 실제로 확인해볼게요.
ps -p 1 -o pid,comm # PID 1 이 누구인지 확인
PID COMMAND
1 systemd
지난 시간엔 그냥 "모든 프로세스의 조상" 정도로 짚고 넘어갔는데, 오늘은 이 녀석이 무슨 일을 하는지를 제대로 들여다봅니다.
먼저 이름부터. 컴퓨터 전원을 켜면, 커널(kernel, 운영체제의 핵심)이 부팅을 마친 뒤 맨 처음 단 하나의 프로그램을 띄워요. 이렇게 시스템이 켜질 때 가장 먼저 떠서 나머지 모든 걸 준비시키는 프로그램을 init(이닛, initialization = 초기화)이라고 불러요. 옛날부터 유닉스 계열엔 항상 이 init 역할이 있었고, 요즘 거의 모든 리눅스 배포판에서 그 자리를 맡은 게 systemd입니다.
전원을 켜면…
커널(kernel)이 부팅하고, 맨 처음 단 하나의 프로세스를 띄운다
│
▼
systemd (PID 1) ← 모든 서비스의 부모. 부팅부터 종료까지 시스템을 총괄
│
│ 부팅하며 필요한 서비스들을 차례차례 띄운다
├─ ssh.service ← 원격 접속(SSH) 대기
├─ nginx.service ← 웹 서버
├─ cron.service ← 예약된 작업 실행기
└─ … 그 외 수십 개의 서비스
systemd를 한마디로 비유하면 건물의 총무예요. 아침에 가장 먼저 출근해서 불을 켜고, 각 부서(서비스)를 정해진 순서대로 가동시키고, 누가 쓰러지면(프로세스가 죽으면) 다시 일으켜 세우고, 무슨 일이 있었는지 일지를 적어두죠(로그). 우리가 지난 시간에 손으로 했던 "프로그램 띄우기·지켜보기·되살리기"를, systemd는 시스템 차원에서 자동으로 해줍니다.
그럼 여기서 자꾸 나오는 서비스(service)라는 말부터 잡고 갈게요. 서비스는 사용자가 직접 띄우지 않아도 시스템이 백그라운드에서 조용히 돌려주는 프로그램이에요. 웹 서버, 데이터베이스, SSH 접속 대기 프로그램처럼요. 화면도 없고 누가 시키지 않아도 묵묵히 자기 일을 하죠. 이런 프로그램을 전통적으로 데몬(daemon)이라고도 불러요(그래서 이름이 sshd·nginx처럼 d로 끝나는 경우가 많아요 — daemon의 d). systemd 세계에서는 이 데몬 하나하나를 .service 단위(유닛, unit)로 관리합니다. 위 그림의 ssh.service·nginx.service가 바로 그거예요.
자, 이제 지난 시간 끝에 아쉬웠던 nohup 이야기로 돌아가 볼게요. nohup 명령 &은 "터미널을 닫아도 안 죽게" 해주는 임시 처방이었죠. 그런데 그게 전부예요. 서버를 재부팅하면 그 프로그램은 그냥 사라지고, 어쩌다 죽어도 아무도 되살려주지 않아요. systemd 서비스로 등록하면 이게 완전히 달라집니다.
| 원하는 것 | nohup & (지난 시간) |
systemd 서비스 (오늘) |
|---|---|---|
| 터미널 닫혀도 살아있기 | ✓ | ✓ |
| 부팅하면 자동으로 켜지기 | ✗ | ✓ |
| 죽으면 알아서 되살아나기 | ✗ | ✓ |
| 로그를 체계적으로 모아 조회 | ✗ (nohup.out에 뒤섞임) |
✓ (journalctl) |
표를 보면 왜 현업에서 "진짜 서비스는 systemd에 맡긴다"고 하는지 한눈에 보이죠. nohup은 잠깐 급할 때 쓰는 반창고, systemd는 정식 등록이에요. 오늘 우리는 이 정식 등록을, 가벼운 웹 서버 nginx를 직접 설치해 띄우면서 손에 익혀볼 거예요. 그 첫걸음인 "설치"부터 다음 Step에서 시작합니다.
💡 한 줄 정리
systemd는 부팅할 때 가장 먼저 뜨는 PID 1(init)으로, 모든 서비스의 부모이자 시스템 전체의 총무다. 서비스(데몬)를 부팅과 함께 자동으로 띄우고, 죽으면 되살리고, 로그를 모은다. nohup이 "터미널 닫혀도 살아있기"까지만 해준다면, systemd 서비스는 부팅 자동 기동·자동 재시작·체계적 로그까지 전부 챙겨준다.
🙋 학생 질문 — "systemd 말고 다른 것도 있나요? 가끔 보이는 service 명령은 뭐예요?"
좋은 관찰이에요. 옛날 리눅스는 systemd가 아니라 SysV init이라는 더 단순한 방식을 썼어요. 그 시절엔 서비스를 켤 때 service nginx start라고 치고, 부팅 자동 기동은 chkconfig로 걸고, 서비스 스크립트는 /etc/init.d/ 폴더에 뒀죠. 인터넷에서 오래된 글을 보면 아직 이 명령들이 보일 거예요.
지금은 거의 모든 주요 배포판(Ubuntu·Debian·Fedora·RHEL 등)이 systemd로 넘어왔어요. 그래서 우리는 처음부터 현행 방식인 systemctl을 배웁니다. 그럼 옛날 service nginx start는 이제 안 먹히냐고요? 사실 아직 먹혀요 — 다만 그건 속으로 systemctl을 대신 불러주는 호환용 껍데기(래퍼)일 뿐이에요. 새로 익히는 건 systemctl 하나면 충분하고, service나 init.d는 "예전엔 이렇게 썼구나" 정도로만 알아두면 됩니다.
Step 2: "소프트웨어 설치 — 패키지 매니저로 한 줄에"
systemd로 서비스를 다루려면, 먼저 다룰 서비스가 있어야겠죠. 그래서 오늘의 실습 친구인 웹 서버 nginx를 설치하는 것부터 시작합니다. 그런데 잠깐 — 지금까지 우리가 쓴 ls·grep·vim 같은 명령은 리눅스에 처음부터 들어 있었어요. nginx처럼 새 프로그램은 어디서 구해 올까요?
윈도우나 맥이라면 웹사이트에 가서 설치 파일을 내려받아 더블클릭하죠. 리눅스 서버는 화면도 없는데 그럴 수 없어요. 대신 훨씬 편한 방법이 있습니다. 패키지 매니저(package manager) — 명령 한 줄로 프로그램을 검색하고, 설치하고, 업데이트하고, 지우는 도구예요.
비유하자면 스마트폰의 앱스토어 + 자동 설치 기사를 합친 거예요. "이거 깔아줘" 한마디면, 그 프로그램이 돌아가는 데 필요한 다른 부품(의존성, dependency)까지 알아서 찾아 함께 깔아주죠. Ubuntu·Debian 계열에서 그 역할을 하는 게 apt(Advanced Package Tool)입니다.
sudo apt update # 설치 가능한 목록을 최신으로 갱신 (항상 먼저!)
sudo apt install nginx # nginx 웹 서버 설치
여기서 sudo가 붙은 이유, A-3에서 배웠죠. 프로그램을 시스템 전역에 설치하는 건 관리자 권한이 필요한 일이라서예요. 그리고 설치 전에 sudo apt update를 먼저 치는 습관이 중요해요. 이건 실제로 뭘 설치하는 게 아니라, "지금 받을 수 있는 프로그램 목록(카탈로그)"을 최신으로 새로 고치는 명령이에요. 카탈로그가 오래되면 엉뚱한 옛 버전을 받거나 "그런 패키지 없다"고 할 수 있거든요.
apt install nginx를 치면 이런 출력이 주르륵 흐릅니다.
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
nginx nginx-common nginx-core
...
Setting up nginx (...) ...
보세요 — 내가 친 건 nginx 하나인데, nginx-common·nginx-core 같은 부품까지 알아서 함께 깔리죠. 이게 의존성 자동 처리예요. 패키지 매니저가 안에서 하는 일을 그림으로 보면:
sudo apt install nginx
│
▼
apt 가 알아서…
① 의존하는 다른 패키지까지 찾아내고 (nginx-common, nginx-core …)
② 인터넷 저장소(repository)에서 내려받아
③ 알맞은 위치에 설치하고
④ 서비스로 등록까지 (Ubuntu 에선 설치하면 nginx 가 바로 켜진다)
특히 ④번 — Ubuntu에서는 nginx를 설치하는 순간 그게 곧바로 systemd 서비스로 등록되고 자동으로 켜져요. 다음 Step에서 systemctl로 만나게 될 바로 그 서비스죠.
설치 말고 자주 쓰는 명령도 짚어둘게요.
apt search nginx # 이름·설명에 nginx 가 든 패키지 검색
apt list --installed # 지금 설치된 패키지 전체 목록
apt list --installed | grep nginx # 그중 nginx 관련만 (A-4 의 파이프+grep)
sudo apt remove nginx # 패키지 제거 (설정 파일은 남김)
sudo apt purge nginx # 설정 파일까지 깨끗이 제거
여기서 두 가지를 정리하고 갈게요. 첫째, 인터넷 옛 글을 보면 apt-get install이 많이 보일 거예요. apt-get은 오래전부터 있던 명령이고, apt는 그걸 사람이 터미널에서 치기 좋게 다듬은 현행 버전이에요(진행 막대·색깔 출력 등이 붙었죠). 직접 손으로 칠 땐 apt, 출력 형식이 변하면 안 되는 자동화 스크립트 안에서만 apt-get을 쓰는 게 관례예요. 우리는 apt로 익힙니다.
둘째, 리눅스에는 크게 두 가문이 있어요. 데비안 계열(Ubuntu 포함)은 apt를 쓰고, 레드햇 계열(Fedora·Rocky·RHEL)은 dnf를 써요. 명령 모양만 조금 다르지 하는 일은 똑같습니다.
| 하는 일 | 데비안 계열 (apt) |
레드햇 계열 (dnf) |
|---|---|---|
| 설치 | sudo apt install nginx |
sudo dnf install nginx |
| 검색 | apt search nginx |
dnf search nginx |
| 삭제 | sudo apt remove nginx |
sudo dnf remove nginx |
yum이라는 이름도 보일 텐데, dnf의 옛 이름이에요. 요즘 레드햇 계열은 yum을 쳐도 사실 속으로 dnf가 대신 실행돼요(호환용). 새로 배우는 건 dnf 하나면 됩니다. 그리고 가문과 상관없이 어디서나 똑같이 돌도록 묶은 새 포장 방식(snap·flatpak)도 있는데, 주로 데스크톱 앱을 깔 때 쓰고 서버에서 우리가 다루는 건 거의 apt/dnf라 오늘은 이름만 기억해두면 충분해요.
💡 한 줄 정리
패키지 매니저는 명령 한 줄로 검색·설치·삭제를 해주고 의존성까지 자동으로 챙긴다. Ubuntu/Debian은 apt(update로 목록 갱신 먼저 → install), 레드햇 계열은 dnf다. apt-get/yum은 구·호환용이고, 직접 칠 땐 현행인 apt/dnf를 쓴다. 방금 nginx를 설치했으니 다음 Step들의 실습 대상이 생겼다.
🙋 학생 질문 — "sudo apt update 랑 sudo apt upgrade 는 뭐가 달라요?"
헷갈리기 딱 좋은 한 글자 차이죠. update는 "설치 가능한 목록(카탈로그)"만 최신으로 새로 고치는 거예요 — 실제로 깔리는 건 하나도 없어요. 반면 upgrade는 이미 깔려 있는 패키지들을 실제로 최신 버전으로 올리는 명령이에요.
그래서 보통 sudo apt update && sudo apt upgrade 순서로 씁니다. 먼저 목록을 새로 고치고(update), 그 최신 목록을 기준으로 실제 업그레이드(upgrade)를 하는 거죠. 새 프로그램을 깔 때도 update를 먼저 해두면, 오래된 카탈로그 때문에 옛 버전을 받는 실수를 피할 수 있어요.
Step 3: "서비스 다루기 — systemctl 로 켜고 끄고 자동 기동"
nginx를 설치했으니, 이제 systemd에게 명령을 내려볼 차례예요. 그 명령 창구가 systemctl(system control)입니다. 가장 먼저, 가장 많이 쓰는 명령부터. 서비스가 지금 어떤 상태인지 물어보는 status예요.
systemctl status nginx # nginx 서비스의 현재 상태
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-06-17 09:30:11 KST; 5min ago
Main PID: 4012 (nginx)
Tasks: 2 (limit: 4631)
Memory: 5.4M
CGroup: /system.slice/nginx.service
├─4012 "nginx: master process /usr/sbin/nginx"
└─4013 "nginx: worker process"
처음 보면 빽빽하지만, 실전에서 읽는 줄은 정해져 있어요.
맨 앞의 ● 초록색이면 잘 돌고 있다는 뜻 (멈췄으면 회색, 실패면 빨강)
Loaded 유닛 파일 위치 + enabled(부팅 자동 기동을 걸어뒀나)
Active (가장 중요) active (running) = 잘 도는 중 — 언제부터인지도 보인다
Main PID 이 서비스의 핵심 프로세스 번호 ← A-7 의 그 PID 를 알아서 알려준다
지난 시간엔 ps aux | grep nginx로 직접 PID를 찾았는데, 서비스로 등록해두니 systemctl status가 Main PID를 콕 집어 알려주죠. 이제 그 서비스를 켜고 꺼봅시다.
sudo systemctl stop nginx # 서비스 중지
sudo systemctl start nginx # 서비스 시작
sudo systemctl restart nginx # 껐다 켜기 (설정을 바꿨을 때)
sudo systemctl reload nginx # 멈추지 않고 설정만 다시 읽기 (지원하는 서비스만)
restart와 reload의 차이를 짚어둘게요. restart는 서비스를 완전히 껐다 켜요 — 그 찰나에 잠깐 끊깁니다. reload는 서비스를 멈추지 않은 채 설정 파일만 다시 읽어요. 그래서 운영 중인 웹 서버의 설정을 바꿨을 땐, 접속이 끊기지 않게 reload를 먼저 시도하는 게 현업 감각이에요.
이제 오늘 Step 3의 핵심, enable과 disable입니다. 부팅 자동 기동을 거는 명령이에요.
sudo systemctl enable nginx # 부팅할 때마다 자동으로 켜지게 등록
sudo systemctl disable nginx # 부팅 자동 기동 해제
sudo systemctl enable --now nginx # 자동 기동 등록 + 지금 바로 시작까지 한 번에
여기서 입문자가 가장 많이 헷갈리는 구분 하나. start와 enable은 전혀 다른 일이에요.
start 지금 이 순간 한 번 켜기 (재부팅하면 다시 꺼져 있다)
enable "부팅할 때마다 켜라" 예약 (지금 당장 켜지진 않는다)
enable --now 예약 + 지금 바로 켜기 (둘 다 — 실무에서 가장 자주)
start만 하면 지금은 떠 있어도 서버를 재부팅하면 사라지고, enable만 하면 다음 부팅 전까지는 안 떠 있어요. 그래서 새 서비스를 정식으로 올릴 땐 보통 enable --now로 둘 다 겁니다. 빠르게 상태만 확인하고 싶을 땐 한 단어로 답해주는 명령도 있어요(스크립트에서 쓰기 좋죠).
systemctl is-active nginx # active / inactive 한 단어로
systemctl is-enabled nginx # enabled / disabled 한 단어로
마지막으로 A-7과 이어지는 장면 하나. nginx는 웹 서버라 80 포트를 잡아요. 지난 시간에 배운 ss로 확인해볼게요.
sudo ss -ltnp | grep :80 # 80 포트를 누가 잡고 있나 (A-7 에서 배운 그 명령)
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=4012,...))
지난 시간엔 포트를 잡은 정체불명의 프로세스를 찾아 kill로 정리했죠. 그런데 이번엔 그 프로세스가 우리가 정식으로 띄운 서비스예요. 그러니 멈추고 싶으면 kill이 아니라 sudo systemctl stop nginx로 멈춰야 해요. systemd가 관리하는 서비스를 kill로 죽이면, systemd가 "어, 죽었네?" 하고 되살리려 들 수도 있거든요. 관리자가 관리하는 건 관리자에게 부탁하는 게 정석입니다.
💡 한 줄 정리
systemctl status로 상태(active/inactive/failed)와 Main PID·자동 기동(enabled) 여부를 읽고, start/stop/restart/reload로 제어한다. start(지금 한 번)와 enable(부팅 자동 기동)은 별개라 보통 enable --now로 둘 다 건다. systemd가 관리하는 서비스는 kill이 아니라 systemctl stop으로 멈춘다.
🙋 학생 질문 — "systemctl start 했는데 왜 재부팅하면 또 꺼져 있어요?"
아주 흔한, 그리고 신입이 운영 서버에서 한 번씩 크게 데는 함정이에요. start는 "지금 한 번만" 켜는 명령이에요. "부팅할 때마다 자동으로 켜기"는 enable이 따로 해주는 일이고요. 둘은 완전히 별개라, start만 하고 enable을 빠뜨리면 서버를 재부팅하는 순간 그 서비스가 통째로 사라져요.
새 서비스를 올릴 때 start만 하고 안심했다가, 며칠 뒤 서버 점검으로 재부팅 한 번 됐는데 서비스가 안 떠서 한밤중에 호출당하는… 실제로 자주 벌어지는 일이에요. 그래서 sudo systemctl enable --now 서비스로 "지금 켜기 + 부팅 자동 기동"을 한 번에 거는 습관을 들이면 안전합니다.
Step 4: "서비스의 설정서 — 유닛 파일 읽기"
Step 3에서 systemctl status 출력에 Loaded: /usr/lib/systemd/system/nginx.service라는 경로가 보였죠. 그 .service 파일이 바로 "이 서비스를 어떻게 띄우고 멈출지" 적어둔 설명서예요. 이걸 유닛 파일(unit file)이라고 불러요. systemd는 이 파일을 읽고 서비스를 다룹니다. 직접 열어볼게요.
systemctl cat nginx # nginx 서비스의 유닛 파일을 그대로 보기
# /usr/lib/systemd/system/nginx.service (핵심만 발췌 — 실제 파일은 더 깁니다)
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target
[Service]
Type=forking
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=/usr/sbin/nginx -s quit
[Install]
WantedBy=multi-user.target
처음엔 외계어 같지만, 세 구획(섹션)으로 나눠 보면 의외로 단순해요.
[Unit] 이 서비스의 정체와 언제 띄울지
Description= → 사람이 읽을 설명
After=network.target → 네트워크가 준비된 뒤에 띄워라 (실행 순서)
[Service] 어떻게 띄우고 멈출지 (실제 실행 명령)
ExecStart= → 서비스를 시작할 때 실행할 명령
ExecReload= → reload 할 때 실행할 명령
ExecStop= → 멈출 때 실행할 명령
[Install] 부팅 자동 기동(enable) 시 어디에 걸지
WantedBy=multi-user.target → 일반 서버 부팅 단계에 포함시켜라
그런데 가만 보면 이 nginx 유닛엔 한 가지가 없어요. Step 1 표에서 약속했던 "죽으면 알아서 되살아나기"를 담당할 줄이요. nginx 기본 유닛은 자동 재시작을 걸어두지 않거든요. 그 일을 하는 게 바로 Restart= 옵션인데, 직접 서비스를 만들 때 이 한 줄을 넣어주면 됩니다. 감을 잡기 위해 아주 단순한 내 서비스를 상상해볼게요. 지난 시간 nohup으로 아쉽게 띄우던 그 파이썬 프로그램을, 이번엔 정식 서비스로 올리는 거예요. /etc/systemd/system/myapp.service에 이렇게 적으면:
[Unit]
Description=My Python App
[Service]
ExecStart=/usr/bin/python3 /home/alice/app.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
Restart=on-failure 이 한 줄이, Step 1 표의 그 "✓ 죽으면 되살아남"의 정체예요. 이 줄 덕에 app.py가 비정상 종료되면 systemd가 알아서 다시 띄워줍니다. 이렇게 세 구획만 채우면, 내가 만든 프로그램도 sudo systemctl enable --now myapp으로 nginx처럼 부팅 자동 기동·자동 재시작까지 갖춘 정식 서비스가 돼요. nohup 임시방편을 완전히 졸업하는 순간이죠.
유닛 파일을 두는 폴더는 둘로 나뉘는데, 이 구분이 중요해요.
/usr/lib/systemd/system/ ← 패키지(apt)가 설치한 기본 유닛 (nginx.service 등)
여기 파일은 직접 수정하지 않는다 (업데이트하면 덮어써짐)
/etc/systemd/system/ ← 관리자(나)가 만들거나 고치는 유닛
내 커스텀 서비스(myapp.service)는 여기에 둔다
패키지가 깔아준 파일은 건드리지 말고, 내가 만들거나 고칠 건 /etc/systemd/system/에 둡니다. 그리고 유닛 파일을 새로 만들거나 고친 뒤엔 반드시 이 명령을 쳐야 해요.
sudo systemctl daemon-reload # 바뀐 유닛 파일을 systemd 에게 다시 읽으라고 알림
systemd는 부팅할 때 유닛 파일들을 한 번 읽어 기억해둬요. 그래서 파일을 고쳐도 곧장 반영되지 않습니다. daemon-reload로 "설명서 다시 읽어"라고 알려준 뒤에야 새 내용이 적용돼요.
💡 한 줄 정리
유닛 파일(.service)은 서비스의 설명서로, [Unit](정체·실행 순서)·[Service](실행 명령·Restart)·[Install](자동 기동 위치) 세 구획으로 돼 있다. systemctl cat으로 읽고, 패키지 기본 유닛은 /usr/lib/systemd/system/, 내가 만드는 건 /etc/systemd/system/에 둔다. Restart=on-failure가 자동 재시작의 정체이며, 파일을 바꾸면 daemon-reload로 알린다.
🙋 학생 질문 — "유닛 파일을 직접 고쳤는데 왜 안 바뀌어요?"
daemon-reload를 빠뜨렸을 거예요. systemd는 효율을 위해 유닛 파일들을 메모리에 읽어두고 그걸로 동작해요. 그래서 디스크의 파일을 고쳐도, systemd가 기억하는 내용은 그대로라 바뀐 게 반영되지 않죠.
순서는 이래요. ① 유닛 파일 수정 → ② sudo systemctl daemon-reload(다시 읽어) → ③ sudo systemctl restart 서비스(새 설정으로 재시작). "분명히 고쳤는데 그대로네?" 하고 한참 헤매는 신입의 단골 함정이 바로 ②번을 잊는 거예요. 파일을 만졌으면 daemon-reload, 이 한 쌍을 기억하세요.
Step 5: "로그 들여다보기 — journalctl 로 무슨 일이 있었나"
서비스가 failed로 떴거나, 잘 돌다 갑자기 멈췄을 때 — "왜?"를 알려면 로그(log)를 봐야 해요. 로그는 프로그램이 "나 지금 이런 일을 했고, 여기서 이런 문제가 생겼어"라고 남기는 일지예요.
기반 개념부터 한 문단 짚을게요. 예전엔 프로그램마다 제각각 /var/log/ 아래 다른 파일에 로그를 남겨서, 문제가 생기면 "이 프로그램 로그는 어느 파일에 있더라?"부터 찾아 헤매야 했어요. systemd는 이걸 깔끔하게 정리했습니다. journald라는 로그 수집기를 두고 모든 서비스의 로그를 한곳에 모아요. 그래서 우리는 서비스 이름만 대면 그 로그만 쏙 뽑아볼 수 있죠. 그 꺼내 보는 명령이 journalctl(journal = 일지)입니다.
journalctl -u nginx # nginx 서비스의 로그만 (-u = unit)
자주 쓰는 옵션을 묶어둘게요.
-u 서비스 특정 서비스(유닛)의 로그만
-f 실시간으로 따라가기 (새 로그가 쌓이는 걸 지켜봄, Ctrl+C 로 빠져나옴)
-n 50 최근 50줄만
--since "..." 특정 시점 이후만
-p err 에러 수준(priority) 이상만
-e 맨 끝(가장 최근)으로 바로 점프
journalctl -u nginx -f # 실시간으로 nginx 로그 지켜보기
journalctl -u nginx -n 50 # 최근 50줄
journalctl -u nginx --since "2026-06-17 09:00" # 그 시각 이후
journalctl -u nginx --since "1 hour ago" # 1시간 전부터
journalctl -u nginx -p err # 에러만 골라 보기
이제 가장 실전적인 흐름. 서비스가 failed로 떴을 때 원인을 찾는 길이에요. Step 3의 status와 이어집니다.
systemctl status 서비스 → ● failed (빨간 불) 확인
│
▼
journalctl -u 서비스 -n 50 -e → 마지막 로그에 실패 원인 (포트 충돌·설정 오타 등)
│
▼
원인을 고친 뒤 → sudo systemctl restart 서비스
현업에서 "서비스가 안 떠요" 연락이 오면 흐름은 거의 항상 이거예요. status로 failed를 확인하고 → journalctl로 마지막 로그를 읽어 원인을 찾죠. A-4에서 grep으로 긴 로그를 뒤지던 그 감각을, 이번엔 systemd가 모아준 로그에 그대로 씁니다(journalctl -u nginx | grep 키워드처럼 파이프도 붙어요).
한 가지 안심 포인트도 챙겨둘게요. 예전엔 기본 설정에서 서버를 재부팅하면 journald 로그가 날아가기도 했는데, 요즘 Ubuntu(26.04 등)는 로그를 디스크에 영구 저장해서 재부팅 뒤에도 지난 로그를 그대로 추적할 수 있어요. 그래서 "새벽 3시에 서비스가 한 번 죽었다 살아났다는데 왜?" 같은 의문도, 그 시각을 journalctl --since "2026-06-17 03:00"처럼 짚어서 되짚어볼 수 있죠.
💡 한 줄 정리
journalctl은 systemd가 모아둔 로그를 꺼내 보는 명령이다. -u 서비스로 특정 서비스 로그만, -f로 실시간, -n으로 최근 N줄, --since로 시점, -p err로 에러만 본다. 서비스가 failed면 status로 확인한 뒤 journalctl -u 서비스 -e로 마지막 로그에서 원인을 찾는다.
🙋 학생 질문 — "journalctl 첫 화면이 너무 길어서 안 끝나요. 어떻게 빠져나와요?"
journalctl은 기본적으로 less(A-2에서 본 그 페이지 뷰어)로 열려요. 그래서 화살표나 스페이스로 넘기고, 빠져나오려면 q를 누르면 됩니다.
길게 헤매고 싶지 않으면 옵션으로 양을 줄이세요. 맨 끝(가장 최근)만 보고 싶으면 -e, 줄 수를 정하려면 -n 50을 붙이면 돼요. 반대로 지금 실시간으로 쌓이는 걸 지켜보려면 -f를 쓰고, 다 봤으면 Ctrl+C로 중단합니다.
Step 6: "매일 저절로 — cron 으로 작업 예약하기"
오늘의 마지막 조각이에요. systemd가 "서비스를 살아있게" 챙긴다면, "정해진 시각에 반복 실행"은 cron(크론)이 맡아요. 매일 새벽 3시 데이터 백업, 5분마다 서버 상태 점검처럼 사람이 깨어 있지 않아도 저절로 도는 예약 작업이죠.
cron도 결국 백그라운드에서 도는 서비스예요(Step 1 그림의 cron.service가 바로 그거죠). 이 서비스가 시계를 보다가, 등록된 예약표(crontab, cron table)에 적힌 시각이 되면 명령을 대신 실행해줍니다. 내 예약표를 다루는 명령은 이렇게 생겼어요.
crontab -e # 내 예약표 편집 (vim/nano 가 열린다 — A-6)
crontab -l # 내 예약표 보기
crontab -r # 내 예약표 전체 삭제 (주의: 되돌릴 수 없음)
crontab -e를 처음 치면 어떤 편집기로 열지 물어봐요. 여기서 지난 시간 익힌 vim이나 nano가 등장하죠. 이제 예약표에 적는 문법, 그 유명한 5필드를 봅시다.
┌───────────── 분 (0-59)
│ ┌─────────── 시 (0-23)
│ │ ┌───────── 일 (1-31)
│ │ │ ┌─────── 월 (1-12)
│ │ │ │ ┌───── 요일 (0-6, 0=일요일)
│ │ │ │ │
* * * * * 실행할 명령
다섯 칸으로 "언제"를 정하고, 그 뒤에 "무엇을" 실행할지 적어요. *는 "매번(모든 값)", 숫자는 "그 값일 때", */5는 "5단위마다"예요. 예시로 읽어보면 감이 잡혀요.
0 3 * * * 매일 새벽 3시 0분 (분=0, 시=3, 나머지는 매번)
*/5 * * * * 5분마다 (*/5 = 0,5,10,15… 분)
0 9 * * 1 매주 월요일 오전 9시 (요일 1 = 월요일)
30 0 1 * * 매월 1일 0시 30분
실전으로 "매일 새벽 3시에 백업 스크립트 실행"을 등록해볼게요. crontab -e를 열어 아래 한 줄을 추가합니다.
0 3 * * * /home/alice/backup.sh >> /home/alice/backup.log 2>&1
앞의 0 3 * * *가 "매일 새벽 3시", 그다음이 실행할 명령이에요. 뒤에 붙은 >> /home/alice/backup.log 2>&1이 눈에 익죠? A-4에서 배운 리다이렉션이에요. 출력과 에러를 로그 파일에 차곡차곡 쌓는 거죠. cron 작업은 실행될 때 화면이 없어서, 이렇게 출력을 파일로 남겨두지 않으면 "결과가 어떻게 됐는지" 알 길이 없어요. 그래서 로그 리다이렉션은 cron의 필수 습관이에요.
한 가지 함정도 미리 일러둘게요. cron이 명령을 실행하는 환경은 내가 평소 쓰는 셸과 달라서, 명령을 찾는 경로(PATH)가 최소한으로만 잡혀 있어요. 그래서 python3처럼 짧은 이름으로 적으면 "그런 명령 없다"며 실패할 수 있어요. cron에 넣는 명령은 /usr/bin/python3처럼 절대경로로 적는 게 안전합니다.
참고로 systemd에도 timer라는 예약 기능이 있어서 요즘은 그걸 쓰기도 해요. 다만 cron이 더 단순하고 어느 리눅스에서나 통해서, 입문은 cron으로 충분합니다.
자, 여기까지 오면 로컬 리눅스 안에서 소프트웨어를 설치하고(apt), 서비스로 띄우고(systemctl), 로그를 보고(journalctl), 작업을 예약하는(cron) 전 과정을 다 거친 거예요. 그런데 현업에서 이 모든 일은 보통 내 노트북이 아니라 저 멀리 있는 원격 서버에서 벌어지죠. 그 이야기는 다음 시간에 이어집니다.
💡 한 줄 정리
cron은 정해진 시각에 명령을 자동 실행하는 예약 서비스다. crontab -e로 편집, -l로 확인한다. 5필드(분·시·일·월·요일)로 시점을 정하고 *=매번, */5=5단위마다다. cron 작업은 화면이 없으니 출력을 로그 파일로 리다이렉션하고(>> 로그 2>&1), 명령은 절대경로로 적는다.
🙋 학생 질문 — "cron 에 등록한 작업이 안 도는 것 같아요. 뭐부터 봐야 하죠?"
가장 흔한 원인 두 가지부터 의심하세요. ① 명령 경로 — 방금 말한 그 함정이에요. cron은 평소 셸과 환경이 달라 python3 같은 짧은 이름을 못 찾을 수 있어요. /usr/bin/python3처럼 절대경로로 바꾸면 해결되는 경우가 많아요. ② 출력을 안 남김 — 화면이 없으니 >> 로그파일 2>&1로 출력과 에러를 남겨두면, 거기에 "왜 실패했는지"가 그대로 찍혀요.
그리고 시각을 5필드로 제대로 적었는지 crontab -l로 다시 확인하고, cron 서비스 자체가 일을 했는지는 journalctl -u cron으로 볼 수 있어요(Step 5에서 배운 그 명령이 여기서도 통하죠). 이 세 곳만 보면 대부분 원인이 잡힙니다.
마무리
오늘은 손으로 띄우던 프로그램을 시스템에 정식으로 맡겼어요. nginx 하나를 설치(apt)해서, 서비스로 켜고 끄고 자동 기동을 걸고(systemctl), 설명서를 읽고(유닛 파일), 로그를 추적하고(journalctl), 반복 작업을 예약(cron)하는 전 과정을 거쳤죠. 지난 시간 nohup으로 아쉬웠던 "진짜 살아있는 서비스"를, 오늘 제대로 띄운 거예요. 핵심을 세 가지로 정리할게요.
오늘 배운 핵심 세 가지
💡 하나 — systemd는 부팅 때 가장 먼저 뜨는 PID 1(init)이자 모든 서비스의 관리자다. 소프트웨어는 패키지 매니저로 설치한다 — Ubuntu/Debian은 apt(update 먼저 → install), 레드햇 계열은 dnf. apt-get/yum은 구·호환용이다.
💡 둘 — systemctl로 서비스를 다룬다. status로 상태(active/inactive/failed)·Main PID·자동 기동 여부를 읽고, start/stop/restart/reload로 제어하며, start(지금 한 번)와 enable(부팅 자동 기동)은 별개라 보통 enable --now로 둘 다 건다.
💡 셋 — 유닛 파일(.service)은 [Unit]/[Service]/[Install] 세 구획의 설명서이고, Restart=on-failure가 자동 재시작의 정체다. 서비스가 failed면 journalctl -u 서비스로 로그를 보고 원인을 찾는다. 반복 작업은 cron으로 5필드(분·시·일·월·요일) 예약한다.
다음 시간 예고
오늘 우리는 로컬 리눅스 한 대 안에서 설치·서비스·로그·예약을 모두 다뤘어요. 그런데 현업에서 이 일들은 거의 다 내 노트북이 아니라 저 멀리 데이터센터나 클라우드에 있는 원격 서버에서 벌어집니다. 그 서버엔 화면도 키보드도 없어요 — 어떻게 들어가서 명령을 칠까요?
다음 시간엔 그 문을 엽니다. SSH로 원격 서버에 안전하게 접속해서, 오늘 배운 apt로 소프트웨어를 깔고 systemctl로 서비스를 띄우고 journalctl로 로그를 보는 — 똑같은 일을 "남의 컴퓨터" 위에서 하는 법이요. 매번 비밀번호를 치는 대신 키(key) 하나로 안전하게 드나드는 SSH 키 인증까지요. 신입이 서버 앞에서 가장 많이 헤매는 그 첫 관문을, 다음 시간에 편하게 넘어가요. 파일을 주고받고 방화벽으로 문을 여닫는 본격 원격 운영은 그 뒤에 이어집니다. 그럼 다음 시간에 만나요.
과제
오늘 과제도 전부 여러분 자신의 리눅스 환경(WSL2·VM·클라우드)에서 직접 쳐보는 실습이에요. nginx를 설치해 서비스로 다뤄보는 게 핵심인데, 설치가 부담되면 마지막에 sudo apt remove nginx로 깔끔하게 지우면 되니 편하게 해보세요. 위험한 명령은 없고, 막혀도 다시 깔거나 멈추면 됩니다.
[기초] 패키지 설치하고 서비스 상태 확인하기
apt로 프로그램을 깔고, 그게 서비스로 어떻게 등록됐는지 눈으로 확인해보세요.
sudo apt update로 목록을 갱신한 뒤sudo apt install nginx로 설치한다apt list --installed | grep nginx로 정말 설치됐는지 확인한다systemctl status nginx를 쳐서,Active줄이active (running)인지와Main PID가 몇 번인지 눈으로 짚어본다systemctl is-active nginx와systemctl is-enabled nginx로 상태를 한 단어로도 확인해본다
[응용] 서비스 제어 + 부팅 자동 기동 + 포트 확인
이제 그 서비스를 직접 켜고 끄고, 부팅 자동 기동과 포트까지 들여다보세요.
sudo systemctl stop nginx후systemctl status nginx로inactive가 됐는지 확인하고, 다시sudo systemctl start nginx로 켠다sudo ss -ltnp | grep :80으로nginx가 80 포트를 잡고 있는지 확인한다(A-7에서 배운 명령)systemctl is-enabled nginx로 자동 기동 여부를 보고,sudo systemctl disable nginx로 껐다가sudo systemctl enable --now nginx로 다시 건다systemctl cat nginx로 유닛 파일을 열어[Service]구획의ExecStart줄이 무엇을 실행하는지 찾아본다
[심화] cron 으로 예약하고 로그로 확인하기 (서버 상황 상상)
서버 상황을 상상해봅시다. "운영 서버의 디스크 사용량을 주기적으로 기록해두고 싶다. 나중에 '언제부터 디스크가 찼나'를 되짚어보기 위해서다."
- 간단한 스크립트
~/disk-log.sh를 만든다. 안에는date와df -h /의 결과를 파일에 덧붙이는 내용을 넣고,chmod +x ~/disk-log.sh로 실행 권한을 준다(A-3에서 배운 그 권한) crontab -e로 예약표를 열어, "5분마다 그 스크립트를 실행하고 출력을~/disk.log에 리다이렉션"하는 한 줄을 등록한다(테스트라*/5로 짧게 잡는다)- 5분쯤 기다린 뒤
crontab -l로 등록을 확인하고,~/disk.log에 기록이 쌓이는지 본다.journalctl -u cron으로cron이 실제로 실행했는지도 확인해본다 - 정리하며 생각해본다. 만약 스크립트 경로나
python3같은 명령을 짧은 이름으로 적었다면 왜 실패할 수 있을까?cron에서 절대경로가 왜 중요한지 두세 문장으로 적어본다
생각해볼 주제
1. start 와 enable 은 왜 굳이 따로일까
운영 서버에 새 서비스를 올린 신입이 sudo systemctl start만 하고 enable을 빠뜨렸어요. 며칠 잘 돌다가 서버가 점검으로 한 번 재부팅되자, 그 서비스만 통째로 사라져 한밤중에 호출을 받았죠. systemd는 왜 "지금 켜기(start)"와 "부팅 자동 기동(enable)"을 굳이 별개의 명령으로 나눴을까요? 둘을 하나로 합쳐버렸다면 어떤 불편이 생겼을지(예: 잠깐 테스트로만 띄우고 부팅 땐 안 떴으면 할 때), 그리고 현업에서 서비스를 배포할 때 왜 enable --now를 습관처럼 함께 쓰는지 연결지어 생각해보세요.
2. 왜 프로그램을 직접 받지 않고 패키지 매니저로 깔까
윈도우·맥에서는 보통 웹사이트에서 설치 파일을 직접 받아 깝니다. 그런데 리눅스 서버에서는 거의 다 apt/dnf 같은 패키지 매니저로 깔죠. 패키지 매니저가 "의존성을 자동으로 챙기고, 신뢰할 수 있는 정해진 저장소에서만 받고, 명령 한 줄로 깔끔하게 지울 수 있게" 해주는 게 서버 운영에서 왜 그렇게 중요할까요? 반대로, 패키지 매니저에 없는 최신 프로그램을 인터넷에서 직접 받아 깔 때 어떤 위험(보안·업데이트 누락·삭제의 어려움)이 따르는지도 함께 생각해보세요.
3. "죽으면 알아서 되살리기"는 항상 좋을까
유닛 파일의 Restart=on-failure 덕에 서비스가 죽으면 systemd가 자동으로 되살립니다. 든든하죠. 그런데 만약 그 서비스가 "설정 파일에 오타가 있어서" 뜨자마자 죽는 상황이라면 어떻게 될까요? systemd는 죽은 걸 또 되살리고, 또 죽고… 이 일을 무한히 반복할 수 있어요. 자동 재시작이 오히려 진짜 문제(설정 오류)를 가려버릴 수도 있다는 뜻이죠. "죽으면 되살린다"는 정책이 언제 약이 되고 언제 독이 되는지, 그리고 무한 재시작을 막으려면 어떤 안전장치(예: 일정 시간 안에 너무 자주 죽으면 재시작을 멈추기)가 필요할지 생각해보세요.
✅ 예시 답안정답 보기
과제와 생각해볼 주제의 예시답안입니다. 서비스 관리는 글로 읽으면 다 아는 것 같다가도, nginx를 직접 깔고 켜고 끄고 로그를 들여다봐야 손에 익어요. PID·시각 같은 숫자가 책과 다른 건 당연하니(여러분 환경의 실제 값이니까요), 숫자보다 흐름이 맞는지를 보면 됩니다. 설치가 부담되면 실습이 끝난 뒤 sudo apt remove nginx로 깔끔하게 지우면 돼요.
🎯 [과제 1 예시답안] 패키지 설치하고 서비스 상태 확인하기
채점 포인트
| 확인 항목 | 무엇을 보면 되는가 |
|---|---|
| 설치 | sudo apt update 후 sudo apt install nginx로 설치했는가 |
| 설치 확인 | apt list --installed | grep nginx로 정말 깔렸는지 확인했는가 |
| 상태 읽기 | systemctl status에서 Active 줄과 Main PID를 눈으로 짚었는가 |
| 한 단어 확인 | is-active/is-enabled로 active/enabled를 확인했는가 |
풀이 예시
$ sudo apt update
...
$ sudo apt install nginx
...
Setting up nginx (...) ...
$ apt list --installed | grep nginx
nginx/now ... [installed]
$ systemctl status nginx
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Wed 2026-06-17 09:30:11 KST; 1min ago
Main PID: 4012 (nginx)
$ systemctl is-active nginx
active
$ systemctl is-enabled nginx
enabled
설치하자마자 systemctl status가 active (running)이고 is-enabled도 enabled로 나오죠. Ubuntu에서는 패키지를 깔면 그 서비스를 곧바로 시작하고 부팅 자동 기동까지 걸어주기 때문이에요. Main PID 4012는 지난 시간에 ps aux | grep으로 직접 찾던 그 PID인데, 이젠 status가 알아서 알려주죠.
💡 튜터의 한마디 — 이 과제의 핵심은 systemctl status를 읽는 눈을 들이는 거예요. 현업에서 서버에 들어가 "이 서비스 잘 떠 있나?"를 확인하는 첫 명령이 바로 이거거든요. Active 줄 하나면 살았는지(running)·죽었는지(dead)·실패했는지(failed)가 다 보이고, enabled인지까지 함께 읽으면 "재부팅해도 살아남을 서비스인가"까지 한눈에 파악됩니다.
🎯 [과제 2 예시답안] 서비스 제어 + 부팅 자동 기동 + 포트 확인
채점 포인트
| 확인 항목 | 무엇을 보면 되는가 |
|---|---|
| 중지·시작 | stop 뒤 status로 inactive 확인하고 다시 start했는가 |
| 포트 확인 | ss -ltnp | grep :80으로 nginx가 80 포트를 잡았는지 봤는가 |
| 자동 기동 토글 | disable 후 enable --now로 다시 걸었는가 |
| 유닛 읽기 | systemctl cat으로 ExecStart 줄을 찾았는가 |
풀이 예시
$ sudo systemctl stop nginx
$ systemctl is-active nginx
inactive
$ sudo systemctl start nginx
$ sudo ss -ltnp | grep :80
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=4012,...))
$ systemctl is-enabled nginx
enabled
$ sudo systemctl disable nginx
$ sudo systemctl enable --now nginx
$ systemctl cat nginx
# /usr/lib/systemd/system/nginx.service
...
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
...
stop 뒤 is-active가 inactive로 바뀌고, 다시 start하면 ss로 80 포트를 잡은 nginx가 보이죠(A-7에서 배운 그 ss 명령이 그대로 통해요). disable로 자동 기동을 껐다가 enable --now로 다시 거는 것도 해보고, systemctl cat으로 유닛 파일을 열어 ExecStart가 실제로 /usr/sbin/nginx를 실행한다는 걸 확인했어요.
💡 튜터의 한마디 — 여기서 꼭 손에 익혀야 할 건 stop/start(지금 상태)와 enable/disable(부팅 정책)이 별개라는 감각이에요. 둘을 따로 만져보면 "아, 지금 떠 있는 거랑 부팅 때 자동으로 뜨는 건 다른 일이구나"가 분명해지죠. 그리고 멈출 땐 kill이 아니라 systemctl stop을 쓴다는 것 — systemd가 관리하는 서비스는 systemd에게 부탁하는 게 정석이라는 점도 같이 기억하세요.
🎯 [과제 3 예시답안] cron 으로 예약하고 로그로 확인하기
채점 포인트
| 확인 항목 | 무엇을 보면 되는가 |
|---|---|
| 스크립트 준비 | ~/disk-log.sh를 만들고 chmod +x로 실행 권한을 줬는가 |
| 예약 등록 | crontab -e로 5필드 + 리다이렉션 한 줄을 등록했는가 |
| 실행 확인 | crontab -l·~/disk.log·journalctl -u cron으로 실행을 확인했는가 |
| 절대경로 고민 | cron의 최소 PATH와 절대경로의 필요성을 적었는가 |
풀이 예시
먼저 디스크 사용량을 기록할 간단한 스크립트를 만들고 실행 권한을 줍니다.
$ cat ~/disk-log.sh
#!/bin/bash
date
df -h /
$ chmod +x ~/disk-log.sh
이제 crontab -e로 예약표를 열어, 5분마다 그 스크립트를 실행하고 출력을 로그 파일에 쌓는 한 줄을 등록합니다(테스트라 */5로 짧게 잡았어요).
$ crontab -e
# 아래 한 줄을 추가하고 저장
*/5 * * * * /home/alice/disk-log.sh >> /home/alice/disk.log 2>&1
$ crontab -l
*/5 * * * * /home/alice/disk-log.sh >> /home/alice/disk.log 2>&1
5분쯤 기다린 뒤 로그 파일과 cron 서비스 로그를 확인하면, 작업이 실제로 돌았다는 게 보여요.
$ cat ~/disk.log
Wed Jun 17 09:35:01 KST 2026
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 50G 12G 36G 26% /
$ journalctl -u cron -n 3
... CRON[5012]: (alice) CMD (/home/alice/disk-log.sh >> /home/alice/disk.log 2>&1)
~/disk.log에 시각과 디스크 사용량이 쌓이고, journalctl -u cron(Step 5에서 배운 명령)에도 cron이 그 명령을 실행한 기록이 남죠.
절대경로 고민: cron이 명령을 실행하는 환경은 내 평소 셸과 달라서 PATH가 /usr/bin:/bin 정도로 최소한만 잡혀요. 그래서 그 밖의 위치(예: /usr/local/bin에 깐 도구)나 가상환경의 python3처럼 짧은 이름으로 부르는 명령은 "그런 명령 없다"며 실패할 수 있어요. 스크립트 경로도, 그 안에서 부르는 명령도 절대경로로 적어두면 환경 차이에 흔들리지 않습니다.
💡 튜터의 한마디 — cron이 "안 도는 것 같아요"는 신입의 단골 질문인데, 디버깅 순서는 거의 정해져 있어요. ① 명령을 절대경로로 적었나 → ② 출력을 >> 로그 2>&1로 남겼나 → ③ journalctl -u cron에 실행 기록이 있나. 이 세 곳만 보면 대부분 원인이 잡혀요. 화면이 없는 자동 작업일수록 "로그를 남기는 습관"이 곧 실력입니다.
🤔 [생각해볼 주제 1] start 와 enable 은 왜 굳이 따로일까
문제 상황 요약
start(지금 켜기)와 enable(부팅 자동 기동)은 별개의 명령이다. 신입이 start만 하고 enable을 빠뜨려 재부팅 후 서비스가 사라지는 사고도 났다. systemd는 왜 이 둘을 굳이 나눴을까?
튜터의 가이드 및 해설
핵심은 이 둘이 서로 다른 축을 다룬다는 데 있어요. start/stop은 "지금 이 순간 떠 있나"라는 현재 상태를 다루고, enable/disable은 "부팅할 때 자동으로 띄울까"라는 정책을 다뤄요. 이 둘은 독립적이라, 네 가지 조합이 모두 의미가 있어요 — 지금 떠 있고 부팅에도 뜨기, 지금만 떠 있기, 지금은 꺼졌지만 부팅엔 뜨기, 둘 다 아니기.
둘을 하나로 합쳤다면 이 유연함이 사라져요. 예를 들어 "이 서비스는 지금 잠깐 테스트로만 띄워보고, 부팅 땐 안 떴으면 좋겠다"가 불가능해지죠. 반대로 "지금은 정비 중이라 띄우지 않지만, 다음 부팅부터는 자동으로 떠야 한다"처럼 enable만 걸어두고 start는 미루고 싶은 경우도 실무엔 있어요. 그래서 systemd는 둘을 분리해두고, 흔한 경우(지금 켜기 + 부팅에도 켜기)는 enable --now라는 지름길로 한 번에 처리하게 해뒀어요.
결국 이 분리는 불편이 아니라 표현력이에요. 다만 그 표현력의 대가로 "둘 다 해야 할 때 하나를 빠뜨리는" 실수가 생기니, 새 서비스를 정식으로 올릴 땐 enable --now를 습관으로 박아두는 거죠.
💡 핵심을 한마디로
"start는 '지금 상태', enable은 '부팅 정책' — 서로 다른 축이라 별개예요. 둘을 합치면 '잠깐만 띄우기'나 '부팅부터 띄우기' 같은 조합이 불가능해지죠. 그래서 분리해두되, 새 서비스 배포 땐 enable --now로 둘 다 거는 게 현업의 기본 습관입니다."
🤔 [생각해볼 주제 2] 왜 프로그램을 직접 받지 않고 패키지 매니저로 깔까
문제 상황 요약
윈도우·맥은 웹사이트에서 설치 파일을 직접 받아 깐다. 그런데 리눅스 서버는 거의 다 apt/dnf로 깐다. 패키지 매니저를 쓰는 게 서버 운영에서 왜 그렇게 중요할까?
튜터의 가이드 및 해설
패키지 매니저의 가치는 네 가지로 정리돼요. 첫째, 의존성 자동 처리. nginx 하나를 깔면 그게 필요로 하는 부품까지 알아서 함께 깔리죠. 직접 받으면 "이게 안 돌아요" → "아, 이 라이브러리가 없네" → 또 받기를 반복해야 해요. 둘째, 신뢰할 수 있는 출처. apt는 배포판이 검증하고 서명한 저장소에서만 받아요. 인터넷 아무 데서나 받은 설치 파일은 변조됐을 위험이 있죠. 셋째, 일괄 업데이트. 깔린 모든 패키지를 apt upgrade 한 번으로 최신 보안 패치까지 올릴 수 있어요 — 서버 보안의 기본이죠. 넷째, 깔끔한 제거. apt remove로 흔적 없이 지워져, 직접 받은 파일이 시스템 곳곳에 남는 일이 없어요.
반대로 패키지 매니저에 없는 최신 프로그램을 인터넷에서 직접 받아 깔면 이 네 가지를 모두 잃어요. 보안 검증이 없고, 업데이트를 손으로 챙겨야 하고, 의존성도 직접 맞춰야 하고, 나중에 깔끔히 지우기도 어렵죠. 그래서 꼭 최신 버전이 필요하다면, 아무 데서나 받는 대신 그 프로그램의 공식 저장소를 apt에 추가해서 여전히 패키지 매니저의 관리 아래 두는 게 정석이에요. 서버 운영에서 중요한 건 "한 번 깔기"가 아니라 "오래 안전하게 유지·갱신·제거하기"라서, 그 전 과정을 책임지는 패키지 매니저가 표준인 거예요.
💡 핵심을 한마디로
"패키지 매니저는 단순한 설치 도구가 아니라 의존성·보안 서명·일괄 업데이트·깔끔한 제거까지 책임지는 관리 체계예요. 서버에선 '한 번 깔기'보다 '오래 안전하게 유지하기'가 중요하니까요. 최신 버전이 필요하면 직접 받지 말고 공식 저장소를 추가해 여전히 apt 관리 아래 두는 게 정답입니다."
🤔 [생각해볼 주제 3] "죽으면 알아서 되살리기"는 항상 좋을까
문제 상황 요약
Restart=on-failure 덕에 서비스가 죽으면 systemd가 자동으로 되살린다. 든든하다. 그런데 설정 오타로 뜨자마자 죽는 서비스라면, 되살리고 또 죽기를 무한 반복할 수 있다. 자동 재시작이 언제 약이고 언제 독일까?
튜터의 가이드 및 해설
자동 재시작이 빛나는 건 일시적인 장애예요. 잠깐 메모리가 부족했거나, 의존하던 다른 서비스가 순간 끊겼거나 하는 경우죠. 이럴 땐 다시 띄우면 멀쩡히 돌아오니, systemd가 알아서 되살려주는 게 큰 도움이 돼요. 사람이 새벽에 깨어 손댈 필요가 없죠.
문제는 결정적인 결함일 때예요. 설정 파일에 오타가 있거나, 필요한 파일이 없거나 해서 "몇 번을 띄워도 똑같이 죽는" 상황이라면, 자동 재시작은 죽음과 부활을 무한 반복하며 CPU만 갉아먹고 진짜 원인(오타)은 가려버려요. "계속 재시작되고 있으니 살아는 있나 보다" 하고 넘어가면, 정작 서비스는 한 번도 제대로 안 뜬 상태인 거죠. 자동 재시작이 오히려 장애를 감추는 독이 되는 순간이에요.
그래서 systemd에는 안전장치가 있어요. "일정 시간(예: 10초) 안에 정해진 횟수(예: 5번)보다 자주 죽으면, 더는 되살리지 말고 멈춰라"라는 한도(StartLimitIntervalSec·StartLimitBurst)를 걸 수 있죠. 이러면 무한 루프에 빠지지 않고 failed 상태로 멈춰서, 사람이 로그를 보고 진짜 원인을 고치게 돼요. 결국 자동 재시작은 "되살리되, 무한히는 아니게" 그리고 "재시작이 잦으면 알림이 가게" 모니터링과 함께 써야 약이 됩니다.
💡 핵심을 한마디로
"자동 재시작은 일시적 장애엔 약이지만, 설정 오타 같은 결정적 결함엔 독이에요 — 무한히 되살리며 진짜 원인을 가리거든요. 그래서 systemd의 재시작 한도(StartLimitBurst)로 '너무 자주 죽으면 멈추게' 막고, 잦은 재시작엔 알림이 가도록 모니터링과 함께 쓰는 게 핵심입니다."