본문으로 건너뛰기
로컬과 클라우드 — 세 쌍의 대칭, 그리고 주소의 문제

생성 수정

IBM * 레드햇 AX 과정 · 21강

로컬과 클라우드 — 세 쌍의 대칭, 그리고 주소의 문제

2026년 7월 6일(월) ~ 7월 10일(금)

이 강의 문제풀이 — 배운 내용을 주관식 문답으로 확인해보세요.풀어보기 →

1주차의 첫 표에 축 2가 있었습니다. "내 컴퓨터(로컬) ↔ 남의 컴퓨터(원격·클라우드)" 입니다. 그때는 예시가 딱 하나였습니다. Git과 GitHub. 그리고 그 표 아래에 이런 줄이 있었습니다.

축 2 (로컬 ↔ 원격) | SAVE/LOAD ↔ 업로드/다운로드 (3주차), Docker ↔ Docker Hub, Model ↔ Hugging Face (11주차)

이번 주가 그 11주차입니다. 예고했던 자리에 도착했습니다. 이 장은 두 덩어리로 이루어져 있습니다.

  • 앞쪽은 축 2의 완성입니다. Git↔GitHub 한 쌍이 아니라 세 쌍이 정확히 같은 모양이라는 것, 그리고 셋 다 push/pull 로 오간다는 것을 봅니다.
  • 뒤쪽은 축 2의 역전입니다. 지금까지 "남의 컴퓨터"는 늘 남이 운영해주는 것이었습니다. 이번 주에는 내 두 번째 PC를 직접 "남의 컴퓨터"로 만듭니다. SSH로 들어가서 씁니다. 그러자 지금까지 한 번도 생각해본 적 없던 문제가 튀어나옵니다. 전원을 껐다 켜면 그 컴퓨터의 주소가 바뀐다는 것.

이 두 번째 문제가 이 주의 과제입니다.


이 장을 다 읽으면

  1. Git↔GitHub / Docker↔Docker Hub / Model↔Hugging Face 세 쌍이 같은 구조의 반복임을 설명하고, 각 쌍을 다섯 항목(개념·인터넷·핵심 기능·해결하는 문제·비유)으로 비교할 수 있다.
  2. 로컬을 엔진, 클라우드를 유통망이라는 한 쌍의 말로 정리하고, push/pull 이 세 곳에서 똑같이 생긴 이유를 말할 수 있다.
  3. 객체 검출의 One-shot과 Two-shot을 구분하고, YOLO가 왜 그 축의 오른쪽에서 태어났는지 설명할 수 있다.
  4. ANN·DNN·CNN·RNN을 포함 관계와 "무엇을 보존하는가" 축으로 정리할 수 있다.
  5. "도커라서 GPU를 못 쓴다"가 왜 틀린 진단인지, 진짜 원인을 어디서 찾아야 하는지 안다.
  6. 접속PC와 원격PC를 구분하고, WSL·OpenSSH 서버·포트포워딩·방화벽까지 SSH 접속 한 줄이 성립하기 위한 조건을 순서대로 나열할 수 있다.
  7. 원격 PC의 IP가 세 층(WSL 내부 / LAN / 공인)으로 나뉜다는 것을 알고, 그중 어느 층이 왜 바뀌는지 설명할 수 있다.
  8. 유동 IP 문제의 해결 방향 여섯 가지를 "어느 층을 고정하는가" 로 분류하고, 각각의 트레이드오프를 말할 수 있다.
  9. FastAPI 백엔드 폴더만 도커 이미지로 빌드해 도커허브에 올리고, 원격 PC에서 pull 해서 띄우는 전 과정을 재현할 수 있다.
  10. Human-in-the-Loop와 2단계 즉시 파기 정책이 각각 어떤 한계를 인정한 설계인지 설명할 수 있다.

0. 이 장의 판별 축

앞에서 배운 대로, 새 용어를 만나면 비유를 찾기 전에 아래 축 중 어디에 꽂히는지부터 정하세요.

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것
1어디에 있는가로컬 (내 컴퓨터)클라우드 (남의 컴퓨터)Git ↔ GitHub / Docker ↔ Docker Hub / Model ↔ Hugging Face
2주소가 유지되는가고정 (Static)유동 (Dynamic)공인 IP ↔ DHCP 임대 IP / WSL 내부 IP / Elastic IP ↔ 일반 IP
3무엇을 하는가엔진 (연산·변경)유통망 (배포·협업)로컬 실행 ↔ 레지스트리·허브
4어느 방향인가push (올린다)pull (내린다)세 쌍 전부 / docker push ↔ docker pull
5접속하는가 받는가접속PC (클라이언트)원격PC (서버)ssh 를 치는 쪽 ↔ sshd 가 도는 쪽

축 1이 이 장의 중심이고, 그 축은 이 교재의 1주차 축 2입니다. 새로 만든 축이 아니라 되찾아온 축입니다.

앞 장에서 이어집니다

어디서그때 이름이번 장에서
1주차 축 2내 컴퓨터 ↔ 남의 컴퓨터 (Git ↔ GitHub)축 1. 예시가 하나에서 셋으로 늘어나 완성됩니다 (2장)
3주차 2.4SAVE/LOAD ↔ 업로드/다운로드2.7절. 그때 "축이 다르다"고 경고했던 그 구분을 여기서 씁니다
7주차 14.5도커허브 = 로컬↔원격의 한 쌍2장에서 한 쌍이 아니라 세 쌍의 표로 확장됩니다
8주차 4장Image=클래스 / Container=인스턴스8장. 그 이미지를 어디에 보관하고 어떻게 옮기는가로 넘어갑니다
10주차 11장AWS EC2·RDS·S36장. "남의 컴퓨터를 빌린다" ↔ "내 컴퓨터를 서버로 만든다"의 대비
10주차 12.11Cloudflare Zero Trust 터널6.7절. 그때 편해서 쓴 도구가, 여기서는 유동 IP 문제의 해답으로 다시 나옵니다

축 3과 축 4는 축 1의 자식 축입니다. 어디에 있는지(축 1)가 정해지면 무엇을 하는지(축 3)가 따라오고, 둘 사이를 오가는 동작에는 자동으로 방향(축 4)이 생깁니다. 축 1 하나만 잡으면 셋이 같이 옵니다.


1. 축 2를 회수한다 — 예고했던 자리

1.1 1주차가 남긴 빚

1주차에서 Git과 GitHub를 배울 때 이런 표를 그렸습니다.

GitGitHub
무엇인가프로그램 (버전 관리 도구)웹 서비스 (원격 저장소)
어디 있나내 컴퓨터남의 컴퓨터 (클라우드)
인터넷없어도 됨필요

그리고 이렇게 정리했습니다. "여섯 줄 중 다섯 줄이 로컬에서 끝납니다. 인터넷이 필요한 건 마지막 한 줄뿐입니다."

그때는 이것이 Git이라는 도구의 특징처럼 보였습니다. 아니었습니다. 이것은 소프트웨어 세계 전체가 채택한 하나의 구조였고, Git은 그 구조의 첫 번째 사례일 뿐이었습니다.

1.2 같은 모양이 세 번 반복된다

[ 로컬 (내 컴퓨터 환경) ]                      [ 클라우드 (인터넷/공유 플랫폼) ]
┌─────────────────────────┐                  ┌─────────────────────────┐
│  Git                    │  ─── push ───▶   │  GitHub                 │
│  (코드 버전 관리)        │  ◀── pull ────   │  (코드 원격 저장·협업)   │
└─────────────────────────┘                  └─────────────────────────┘
             │                                            │
┌─────────────────────────┐                  ┌─────────────────────────┐
│  Docker                 │  ─── push ───▶   │  Docker Hub             │
│  (개발 환경 컨테이너화)  │  ◀── pull ────   │  (도커 이미지 저장·공유) │
└─────────────────────────┘                  └─────────────────────────┘
             │                                            │
┌─────────────────────────┐                  ┌─────────────────────────┐
│  Model                  │  ─── push ───▶   │  Hugging Face           │
│  (AI 가중치 파일·추론)   │  ◀── pull ────   │  (AI 모델 허브·호스팅)   │
└─────────────────────────┘                  └─────────────────────────┘

세 줄이 완전히 같은 모양입니다. 왼쪽 상자의 내용물만 코드 → 환경 → 가중치로 바뀔 뿐입니다.

1.3 로컬은 엔진, 클라우드는 유통망 ⭐

로컬(Local) — 기술의 '엔진' 이자 작업 공간입니다. 인터넷 환경과 무관하게 실제 연산과 변경 사항이 일어나는 물리적인 장소입니다.

클라우드(Cloud) — 기술의 '유통망' 이자 협업 공간입니다. 로컬의 한계를 넘어 백업·공유·협업·대규모 배포를 가능하게 만들어 줍니다.

이 한 쌍이 왜 중요한가 하면, "둘 중 어느 쪽이 더 중요한가" 라는 잘못된 질문을 없애주기 때문입니다. 엔진 없는 유통망은 팔 물건이 없고, 유통망 없는 엔진은 만든 걸 아무도 못 씁니다. 둘은 층위가 다릅니다.

  • 로컬(Local) — [실행 환경]인데, [연산이 실제로 일어나며 인터넷 없이도 성립한다]는 점에서 클라우드와 구별된다.
  • 클라우드(Cloud) — [실행 환경]인데, [타인의 인프라 위에 있고 네트워크를 통해서만 닿는다]는 점에서 로컬과 구별된다.

⚠️ "클라우드 = 인터넷"이 아닙니다. 인터넷은 길이고, 클라우드는 그 길 끝에 있는 남의 컴퓨터입니다. 10주차에서 배운 온프레미스(On-Premise) 가 정확히 그 반대쪽 개념이었습니다. premise 는 "전제"가 아니라 "건물·구내" 라는 것도 그때 짚었습니다.


2. 세 쌍의 대칭 ⭐

이 장의 중심입니다. 세 쌍을 각각 다섯 항목으로 뜯어본 다음, 마지막에 하나의 표로 겹칩니다.

2.1 첫 번째 쌍 — 코드 관리: Git ↔ GitHub

구분Git (로컬)GitHub (클라우드)
개념소스코드의 변경 이력을 기록하는 버전 관리 도구Git 리포지토리를 업로드하는 원격 저장소 플랫폼
인터넷인터넷 연결이 없어도 로컬에서 작동 가능인터넷 연결 필수
핵심 기능커밋(Commit), 브랜치(Branch), 롤백(Rollback)협업(Pull Request), 코드 리뷰, 오픈소스 공유
해결 문제"언제 무엇을 왜 바꿨는지 남는 게 없다""다른 사람과 같은 코드를 어떻게 같이 고치나"
비유내 컴퓨터에 쓰는 개인 비밀 일기장모두가 볼 수 있는 온라인 블로그·게시판

대체 가능성이 다릅니다. Git은 사실상 대체 불가지만, GitHub는 GitLab·Bitbucket 등으로 바꿀 수 있습니다. 왼쪽(엔진)은 표준이 하나로 수렴하고, 오른쪽(유통망)은 경쟁자가 여럿입니다. 이 비대칭은 세 쌍 모두에서 반복됩니다.

2.2 두 번째 쌍 — 환경 관리: Docker ↔ Docker Hub

구분Docker (로컬)Docker Hub (클라우드)
개념프로그램과 실행 환경을 하나로 묶는 가상화 플랫폼작성된 도커 이미지를 보관하고 배포하는 레지스트리
인터넷이미지가 이미 있으면 없어도 됨필수
핵심 기능컨테이너 생성, 로컬 환경 독립 실행이미지 업로드/다운로드 (push / pull)
해결 문제"내 컴퓨터에서는 잘 되는데 왜 서버에선 안 되지?"전 세계 어디서나 명령어 한 줄로 동일한 환경 복제
비유음식을 담아두는 밀폐 용기(컨테이너)반찬 밀키트를 모아두고 파는 마켓·앱스토어

레지스트리(Registry) 라는 단어를 눈여겨보세요. "저장소"가 아니라 "등록소" 입니다. 아무 파일이나 넣는 창고가 아니라 이름(태그)을 붙여 등록해두는 곳이라는 뜻이 담겨 있습니다. 그래서 도커허브에서 받을 때 python:3.13-slim 처럼 이름:버전 형식을 씁니다.

앞에서 배운 것과 이어집니다 — 8주차 4장에서 Image = 클래스, Container = 인스턴스, Volume = 디스크 라는 대응을 세웠습니다. 여기에 한 칸이 더 붙습니다. Docker Hub = 클래스 파일을 올려두는 원격 저장소입니다. 즉 "설계도(이미지)를 만들어 등록소에 올려두면, 아무 컴퓨터나 그걸 받아서 실체(컨테이너)를 찍어낼 수 있다"가 됩니다.

2.3 세 번째 쌍 — AI 모델 관리: Model ↔ Hugging Face

구분Model (로컬)Hugging Face (클라우드)
개념학습이 완료되어 저장된 AI 신경망 파일 자체AI 모델·데이터셋·웹 데모를 공유하는 AI 생태계 플랫폼
형태.safetensors, .bin, .pt 등 대용량 파일전 세계 연구자가 모델을 올리고 토론하는 커뮤니티
사용 방식로컬 GPU 인프라로 직접 추론·파인튜닝API 호출, 모델 다운로드, 클라우드 내 호스팅(Spaces)
해결 문제내 데이터를 밖으로 내보내지 않고 쓰기수십 GB 가중치를 각자 학습하지 않고 받아 쓰기
비유내 컴퓨터에 설치된 게임 실행 파일게임을 받고 평가하는 스팀(Steam) 플랫폼

왜 모델 파일의 확장자가 여러 개인가

세 확장자는 같은 것을 담지만 담는 방식이 다릅니다.

확장자정체특징
.bin가중치 덩어리초기 Hugging Face 표준. 내부는 역시 pickle 기반
.safetensorsHugging Face가 만든 안전한 포맷코드 실행 없이 숫자만 읽음. 로딩도 빠름. 현재 사실상 표준

⭐ .safetensors 라는 이름 자체가 "safe + tensors" 입니다. "텐서(숫자 덩어리)만 안전하게 담는다"는 선언입니다. 모델을 남에게서 받아 쓰는 시대가 되자 "받은 파일을 여는 행위 자체가 위험하다" 는 문제가 생겼고, 그 답이 이 포맷입니다.

텐서(Tensor) 는 3주차에서 배웠습니다. 스칼라(0차원) → 벡터(1차원) → 매트릭스(2차원) → 텐서(3차원 이상). 모델 파일은 결국 거대한 텐서들의 모음입니다. 그 이상도 이하도 아닙니다.

Spaces — 유통망이 실행까지 해준다

Hugging Face가 다른 두 쌍과 다른 점이 하나 있습니다. Spaces 입니다. GitHub는 코드를 보관만 하고(실행은 Vercel 같은 다른 서비스가), Docker Hub도 이미지를 보관만 합니다. 그런데 Hugging Face는 모델을 올리면 그 자리에서 데모 웹앱까지 띄워줍니다.

보관실행
GitHub✅❌ (Vercel·Netlify 등이 담당)
Docker Hub✅❌ (내 서버·클라우드가 담당)
Hugging Face✅✅ Spaces

⚠️ 이 차이 때문에 "허깅페이스는 깃허브 같은 거다"라고만 외우면 Spaces를 만났을 때 무너집니다. 보관과 실행은 다른 축이라는 것을 같이 기억하세요.

2.4 세 쌍을 한 표로 겹치기 ⭐

이 표가 이 장에서 가장 오래 남을 표입니다.

Git ↔ GitHubDocker ↔ Docker HubModel ↔ Hugging Face
로컬에 있는 것.git 폴더 (변경 이력)이미지·컨테이너.safetensors 가중치 파일
로컬에서 하는 일커밋·브랜치·롤백빌드·실행·격리추론·파인튜닝
올리는 명령git pushdocker pushhuggingface-cli upload / push_to_hub()
내리는 명령git pull / git clonedocker pullfrom_pretrained() / huggingface-cli download
클라우드가 주는 것협업(PR)·이슈·리뷰공개 레지스트리·태그 관리모델 카드·데이터셋·Spaces
이름 붙이는 단위브랜치·태그repo:tag조직/모델명
왼쪽이 없으면올릴 이력이 없음올릴 이미지가 없음올릴 가중치가 없음
오른쪽이 없으면나 혼자만 씀다른 서버에 못 옮김남이 못 받아 씀

마지막 두 줄이 핵심입니다. 왼쪽이 없으면 존재가 없고, 오른쪽이 없으면 확산이 없습니다.

2.5 push와 pull — 방향에는 이름이 있다 (축 4)

세 쌍이 전부 같은 두 단어를 씁니다. 우연이 아닙니다.

방향단어문자 그대로의 뜻하는 일
로컬 → 클라우드push밀어 올린다내가 만든 것을 유통망에 등록
클라우드 → 로컬pull끌어 내린다남이 만든 것을 내 엔진으로 가져옴
# 세 쌍에서 똑같이 생긴 명령
git push origin main
docker push myaccount/example-backend:0.1.0
huggingface-cli upload myaccount/my-model ./model     # 개념상 push
git pull origin main
docker pull myaccount/example-backend:latest
huggingface-cli download myaccount/my-model           # 개념상 pull

⚠️ 3주차에서 경고한 것을 다시 확인하세요. SAVE/LOAD 는 내 컴퓨터 안에서 메모리 ↔ 디스크를 오가는 것입니다(직렬화·역직렬화). push/pull(업로드/다운로드) 은 인터넷 너머 남의 서버와 주고받는 것입니다. model.save() 와 huggingface-cli upload 는 완전히 다른 축의 동작입니다. 헷갈리면 "인터넷이 필요한가"를 물으세요.

2.6 반례 — 이 쌍이 아닌 것들

미국식 분류로 배울 때 가장 효과가 큰 것이 반례입니다. 아래는 축 1처럼 보이지만 아닙니다.

이것이 쌍인가?왜 아닌가
VS Code ↔ GitHub Codespaces⚠️ 비슷하지만 아님같은 에디터를 어디서 돌리느냐의 문제. push/pull 하는 산출물이 없음
PostgreSQL ↔ Neon⚠️ 절반만 맞음로컬 DB와 클라우드 DB는 같은 것의 두 위치. 로컬에서 만들어 올리는 구조가 아님
Vercel❌오른쪽만 있는 서비스. 대응하는 "로컬 Vercel"이 없음 (vercel dev 는 흉내)
Ollama ↔ Hugging Face✅ 맞음Ollama는 로컬 실행 엔진, 허깅페이스는 유통망. 10주차에서 이미 이 구조를 썼습니다
ChatGPT❌로컬 대응물이 없는 완제품 서비스. 축 1이 아니라 "API냐 로컬 모델이냐" 축

⭐ 판별 질문 하나로 끝납니다: "왼쪽 없이 오른쪽이 성립하는가?" 성립하면 그건 축 1의 쌍이 아니라 그냥 클라우드 서비스입니다.

2.7 이 대칭을 외우면 무엇이 이득인가

아직 안 배운 것을 추론할 수 있게 됩니다. 이것이 이 교재가 축을 세우는 이유입니다.

만나게 될 것축 1로 미리 추론되는 것
npm / npm registry로컬에 node_modules, 원격에 패키지 등록소. npm publish 가 push
PyPI / pippip install 이 pull, twine upload 가 push
AWS ECR도커허브의 AWS판. 같은 자리, 다른 회사
Ollama 모델 저장소ollama pull — 이름부터 같습니다

새 도구를 만났을 때 "이건 왼쪽인가 오른쪽인가" 한 번만 물으면 절반은 이해가 끝납니다.


3. 모델이라는 파일 — 컴퓨터 비전과 YOLO

축 1의 왼쪽 세 번째 칸이 모델 파일이었습니다. 그럼 그 파일 안에는 무엇이 들었고, 어떻게 만들어지는가. 이번 주 첫날에 YOLO를 조사한 것이 그 답입니다.

3.1 왜 이 주에 YOLO가 나오는가

두 가지 이유입니다. 첫째, 모델 파일의 실물을 보기 위해서입니다. yolo11n.pt 는 몇 MB짜리 파일 하나입니다. 이 파일을 내려받고(pull), 파인튜닝해서 새 파일을 만들고, 다시 올리는(push) 전 과정이 축 1 그 자체입니다. 둘째, 컴퓨터 비전은 "로컬 GPU가 필요한 대표 작업" 이기 때문입니다. 이미지·영상은 데이터가 크고, 실시간이 필요하고, 밖으로 내보내기 곤란한 경우가 많습니다.

3.2 신경망 사형제 — ANN · DNN · CNN · RNN

먼저 소속 범주부터 정리합니다. 이 넷은 대등한 넷이 아닙니다.

ANN (인공신경망) ─── 전부의 상위 범주
 └── DNN (심층신경망)        ← 은닉층을 여러 겹 쌓은 것 = '딥'러닝의 '딥'
      ├── CNN (합성곱 신경망)  ← 공간을 보존한다
      └── RNN (순환 신경망)    ← 순서를 보존한다
약자영문한글종차(무엇이 다른가)주 용도
ANNArtificial Neural Network인공신경망입력층–은닉층–출력층으로 이루어진 기본형모든 신경망의 뿌리
DNNDeep Neural Network심층신경망은닉층이 여러 겹. 특징을 단계적으로 추출일반적인 딥러닝
CNNConvolutional Neural Network합성곱 신경망필터(커널)를 밀며 지역 특징을 뽑아 공간 구조를 보존이미지·영상
RNNRecurrent Neural Network순환 신경망직전 시점의 은닉 상태를 다음 시점으로 넘겨 순서를 보존문장·음성·시계열

⭐ CNN과 RNN을 가르는 축은 "무엇을 보존하는가"입니다. CNN은 공간, RNN은 시간(순서) 입니다. 그림은 옆 픽셀이 중요하고, 문장은 앞 단어가 중요합니다. 그 차이가 구조를 갈랐습니다.

왜 CNN이 이미지에서 이기는가 — 이미지를 한 줄로 펴서 일반 신경망(DNN)에 넣으면 "옆에 있다"는 정보가 사라집니다. CNN은 작은 필터를 이미지 위에서 미끄러뜨리며 계산하므로 위치 관계가 살아남고, 같은 필터를 재사용하니 파라미터도 훨씬 적습니다.

RNN은 어디로 갔는가 — 10주차에서 배운 어텐션과 트랜스포머가 RNN의 자리를 대부분 가져갔습니다. RNN은 앞에서부터 순서대로만 볼 수 있어 긴 문장에서 앞부분을 잊어버리는 문제(장기 의존성) 가 있었고, 어텐션은 모든 토큰을 한꺼번에 보며 주목도를 계산해서 그 문제를 우회했습니다.

앞에서 배운 것과 이어집니다 — 1주차의 축 1(포함 관계)이 여기서 또 나옵니다. AI ⊃ 머신러닝 ⊃ 딥러닝 이었고, 딥러닝 안이 다시 ANN ⊃ DNN ⊃ (CNN, RNN) 입니다. 프랙탈입니다.

3.3 객체 검출의 역사 — 왜 이렇게 어려웠는가

객체 검출(Object Detection) 은 "이 사진에 무엇이 있는가"(분류)에 "어디에 있는가"(위치) 까지 답해야 하는 작업입니다. 답의 형태가 레이블 + 사각형 좌표(Bounding Box) 인 셈입니다.

시기방식한계
컴퓨팅 파워 향상 이전영상처리 기법으로 해결 (윤곽선·색·특징점)조명·각도가 바뀌면 무너짐
2012년 AlexNet 이후딥러닝으로 전환연산량이 폭증
초기 딥러닝 접근단일 윈도우 영역분할정확도가 낮음
개선다계층 중첩 윈도우 + 교차영역 지정정확도는 올랐으나 연산량이 더 커짐

핵심 딜레마는 "어디를 볼지 모른다" 였습니다. 사진 어디에 물체가 있는지 모르니 여기저기 다 잘라서 확인해야 했고, 자를수록 정확해지지만 그만큼 느려집니다. 정확도와 속도의 트레이드오프입니다.

3.4 One-shot과 Two-shot ⭐ — 이 절의 판별 축

이 축 하나가 객체 검출 모델 전부를 두 줄로 가릅니다.

Two-shot (2단계)One-shot (1단계)
대표R-CNN 계열 (R-CNN → Fast → Faster)YOLO, SSD, RetinaNet
절차① RPN(Selective Search) 으로 후보 영역 약 2,000개 추림 → ② 그걸 전부 CNN에 전달이미지가 하나의 신경망을 한 번 통과하며 bounding box와 class를 동시 예측
연산량매우 큼작음
실시간성거의 zero실시간 가능
정확도초기에는 더 높았음초기에는 낮았으나 빠르게 따라잡음

RPN(Region Proposal Network) 은 "여기쯤 뭔가 있을 것 같다"는 후보 영역을 먼저 뽑아주는 단계입니다. Two-shot이라는 이름이 여기서 나옵니다. 한 번은 후보를 뽑고(shot 1), 한 번은 그 후보를 분류합니다(shot 2).

⭐ One-shot의 발상 전환: "후보를 고른 다음에 본다"가 아니라 "보면서 동시에 고른다" 입니다. 이미지를 격자로 나누고 각 칸이 직접 "내 칸에 물체 중심이 있는가 / 있다면 무슨 물체이고 상자는 얼마나 큰가"를 한 번에 답합니다.

3.5 YOLO의 정의

YOLO(You Only Look Once) — [객체 검출 모델]인데, [이미지를 신경망에 단 한 번 통과시켜 위치와 종류를 동시에 예측한다]는 점에서 R-CNN 계열과 구별된다.

  • 누가: Joseph Redmon
  • 언제: 2015년 v1 논문 발표
  • 당시 상황: 최고 성능 모델은 Faster R-CNN 이었습니다. YOLO가 처음으로 One-shot Detection을 고안했습니다.

이름이 그대로 설명입니다. "한 번만 본다." 사람이 사진을 볼 때 2,000군데를 잘라 보지 않는다는 직관에서 출발했습니다.

3.6 R-CNN 계열과의 비교

모델상대 속도특징
R-CNN기준후보 2,000개를 각각 CNN에 통과
Fast R-CNN약 10배 빠름이미지를 한 번만 CNN에 통과시키고 특징 맵을 공유
Faster R-CNN다시 약 10배후보 영역 추출까지 신경망으로 대체(RPN)
YOLO다시 약 10배후보 추출 단계 자체를 없앰

R-CNN → Fast R-CNN → Faster R-CNN → YOLO는 대략 10배씩 속도 차이가 납니다.

YOLO의 성능 수치: 45프레임(초당), 빠른 버전은 155프레임. 초당 30프레임이면 사람 눈에 영상으로 보입니다. 45fps는 실시간 영상 처리가 가능하다는 뜻입니다.

YOLO의 단점 — 장점만 외우면 안 됩니다.

단점내용
학습 정도에 따른 성능 저하학습 데이터가 부족하면 급격히 나빠짐
이미지 크기에 따른 성능 저하아주 작은 객체를 놓침
겹친 객체 예측 불확실성한 격자 칸에 여러 물체 중심이 몰리면 흔들림

3.7 v1부터 v5까지 — 무엇이 더해졌는가

버전핵심 변화왜 그렇게 했는가
v1GoogleNet 기반. 컨볼루션 24층 + 완전연결 2층, 풀링 미사용최초 구현. 구조를 단순하게 유지
v2일괄 정규화(Batch Normalization) 추가, 완전연결 대신 앵커박스(Anchor Box)학습 안정화 + 상자 크기를 처음부터 추측하지 않고 기준 상자에서 보정
v3로지스틱 회귀로 Objectness Score 예측, 반복 검출 방지"여기에 물체가 있을 확률"을 명시적으로 계산해 같은 물체를 여러 번 잡는 문제를 줄임
v4SPP 모듈 적용 — 추론시간 약 7% 증가, 정확도 5.7% 향상여러 크기의 풀링을 합쳐 다양한 크기의 객체에 대응
v5YOLOv3의 PyTorch 구현, BottleneckCSP 사용프레임워크가 Darknet에서 PyTorch로 — 쓰기 쉬워짐

⭐ v4의 숫자를 기억하세요. 시간 +7%를 내고 정확도 +5.7%를 샀습니다. 이것이 3주차에서 배운 트레이드오프의 교과서적 사례입니다. "좋아졌다"가 아니라 "무엇을 주고 무엇을 받았는가" 로 읽는 습관이 이 표에서 훈련됩니다.

⚠️ v5부터는 논문 저자가 바뀝니다. v1~v3은 Joseph Redmon, v4는 다른 연구팀, v5는 Ultralytics사의 구현입니다. 그래서 "버전 숫자가 크다 = 더 좋다"가 항상 성립하지는 않습니다. 계보가 갈라져 있습니다.

3.8 조사 보고서의 부록 개념 네 가지

개념무엇인가
Feature Correlation서로 다른 특징 맵 사이의 상관 관계. 두 지점이 얼마나 닮았는지를 재서 매칭·추적에 씀
Bilinear Interpolation격자 사이의 중간 값을 주변 값들의 거리 비율로 섞어 계산하는 방법
Segmentation사각 상자가 아니라 픽셀 단위로 물체 영역을 칠하는 것. 검출보다 한 단계 정밀
Online Model Aggregation여러 모델의 예측을 실시간으로 합쳐 하나의 결론을 내는 방식

Bilinear Interpolation 계산 예제 — 말로만 들으면 어렵고, 숫자를 넣으면 한 줄입니다.

A(2, 1)  ────────── E(4.9, 2.74) ────────── B(7, 4)

x축 전체 거리      d  = 7 − 2   = 5
A에서 E까지 거리   d1 = 4.9 − 2 = 2.9
비율               d1 / d = 2.9 / 5 = 0.58

검산:  y = 1 + 0.58 × (4 − 1) = 1 + 1.74 = 2.74  ✅

이 0.58이 "E는 A에서 B 쪽으로 58% 지점에 있다"는 뜻이고, E의 값은 A값 × (1 − 0.58) + B값 × 0.58 입니다. 이미지를 확대·축소할 때 없는 픽셀을 이런 식으로 채웁니다. YOLO가 입력 이미지를 640×640으로 맞출 때 실제로 쓰이는 계산입니다.

3.9 YOLO와 PyTorch는 무엇이 다른가

가장 자주 나오는 혼동입니다. 둘은 같은 줄에 놓고 비교할 대상이 아닙니다.

PyTorchYOLO
상위 범주딥러닝 프레임워크모델 아키텍처(+ 학습된 가중치)
하는 일텐서 연산, 자동 미분, GPU 할당, 학습 루프 제공"이미지에서 객체를 찾는다"는 구조와 가중치
관계YOLO가 그 위에서 돈다PyTorch 없이는 못 돈다(v5 이후)
비유엔진과 변속기그 위에 올린 차체 설계도

앞에서 배운 것과 이어집니다 — 1주차의 라이브러리 ↔ 프레임워크 축입니다. "누가 누구를 부르는가"로 갈렸습니다. PyTorch는 내 코드를 자기 규칙 안에서 부르는 쪽입니다. YOLO는 그 규칙 안에 얹히는 내용물입니다.

실무에서는 ultralytics 패키지가 YOLO를 감싸서 제공합니다. 그래서 우리가 실제로 부르는 것은 다음 한 줄입니다.

from ultralytics import YOLO

3.10 모델 크기 문자 — n / s / m / l / x

YOLOv8·YOLOv11에는 같은 구조를 크기만 다르게 만든 다섯 형제가 있습니다. n(Nano) → s(Small) → m(Medium) → l(Large) → x(Extra Large) 순으로 커지고, 느려지고, 정확해집니다.

⭐ 가장 가벼운 훈련용 모델은 YOLOv8/YOLOv11의 Nano(n) 입니다. 처음 파인튜닝을 시도할 때는 무조건 n 으로 시작하세요. 큰 모델로 시작하면 에러인지 느린 건지 구분이 안 됩니다. 7주차에서 배운 "문제를 좁혀라" 와 같은 원칙입니다.

3.11 얼굴 인식 — 사전학습 모델을 그대로 쓰는 경우

객체 검출과 달리, 얼굴 인식은 이미 잘 학습된 모델을 그대로 가져다 쓰는 것이 일반적입니다.

  • 패키지 keras_vggface, 모델 호출 VGGFace(model='resnet50')
  • 학습 데이터: VGGFace2 데이터셋 — 셀럽 9,131명, 약 331만 장
from keras_vggface.vggface import VGGFace

model = VGGFace(model='resnet50')   # ResNet-50 백본, VGGFace2 사전학습

331만 장이라는 숫자를 눈여겨보세요. 개인이 모을 수 있는 규모가 아닙니다. 이것이 축 1의 오른쪽(유통망)이 존재하는 이유 그 자체입니다. 누군가 331만 장으로 학습한 결과를 파일 하나로 받아 쓰는 것 — 그게 pull 입니다.

3.12 "도커라서 GPU를 못 쓴다"는 오해 ⭐

이번 주에 실제로 겪은 문제이자, 진단의 교과서입니다.

증상: 도커 컨테이너 안에서 YOLO 학습을 돌리려 하니 GPU를 못 쓴다는 에러. 흔한 오진: "도커는 격리되어 있으니까 GPU에 접근을 못 하는구나."

❌ "Docker라서 GPU를 못 쓴다"는 말은 사실이 아닙니다.

진짜 원인: Dockerfile 안에 이런 줄이 있었습니다.

# 문제의 줄 — requirements.txt 에서 무거운 패키지들을 걸러내고 설치했다
RUN grep -v -E 'torch|torchvision|ultralytics' requirements.txt > slim.txt \
    && pip install --no-cache-dir -r slim.txt

grep -v -E ... 는 "이 패턴에 맞는 줄을 빼라" 는 명령입니다. 이미지 용량을 줄이려고 torch·torchvision·ultralytics를 빌드에서 제외해버린 것입니다.

즉, GPU에 접근 못 한 게 아니라 컨테이너 안에 딥러닝 라이브러리 자체가 없었습니다.

진짜로 GPU를 쓰려면 ① NVIDIA Container Toolkit(컨테이너가 호스트의 GPU 드라이버에 접근하게 해주는 다리) 설치 ② 실행 시 --gpus all 플래그 ③ 이미지 안에 CUDA 대응 torch가 들어 있을 것, 이 셋이 모두 필요합니다.

docker run --gpus all -it myaccount/yolo-train:latest

⭐ 이 사건에서 가져갈 교훈은 GPU 설정법이 아닙니다. "에러 메시지가 가리키는 곳"과 "원인이 있는 곳"이 다를 수 있다는 것입니다. 에러는 GPU를 말했지만 원인은 Dockerfile 한 줄에 있었습니다. 컨테이너 안에서 문제가 생기면 먼저 컨테이너 안에 들어가서 pip list 를 확인하세요.

docker exec -it <컨테이너이름> bash
pip list | grep -E 'torch|ultralytics'   # 정말 설치되어 있는가부터 확인

4. 데이터셋을 아키텍처에 연결하기 — 로컬이냐 S3냐

YOLO를 파인튜닝하려면 데이터셋이 필요합니다. 그런데 그 데이터셋이 내 PC에 있을 수도, S3에 있을 수도 있습니다. 축 1이 그대로 설계 문제가 되는 순간입니다.

4.1 문제 정의

model.train(data="???")   ← 여기에 들어갈 경로가 실행 환경마다 다르다

내 PC에서는 C:/projects/dataset/data.yaml, 원격 우분투에서는 /home/desktop/dataset/data.yaml, S3에서 받아 쓸 때는 버킷에서 내려받아 임시 폴더에 푼 경로입니다. 이 경로를 학습 코드 안에 적으면, 환경이 바뀔 때마다 학습 코드를 고쳐야 합니다. 6주차에서 배운 대로, 자주 바뀌는 것을 안쪽에 두면 안 됩니다.

4.2 포트 — 도메인이 요구하는 것만 적는다

Outbound 포트를 하나 만듭니다.

# app/ports/outbound/face_dataset_port.py
from abc import ABC, abstractmethod

class FaceDatasetPort(ABC):
    """학습에 쓸 데이터셋 설정 파일의 '절대경로'를 돌려준다."""

    @abstractmethod
    def get_dataset_config_path(self) -> str:
        """data.yaml 의 절대경로를 반환한다."""
        ...

⭐ 포트가 요구하는 것은 딱 하나입니다. "data.yaml 의 절대경로를 다오." 어디서 어떻게 구했는지는 묻지 않습니다. 이것이 6주차의 포트(추상) 입니다.

4.3 어댑터 — 구하는 방법은 두 가지

# app/resource_adapters/local_yolo_dataset_adapter.py
class LocalYoloDatasetAdapter(FaceDatasetPort):
    def get_dataset_config_path(self) -> str:
        # 이미 로컬 디스크에 있으므로 경로만 조립해서 돌려준다
        return str(Path(self._base_dir, "data.yaml").resolve())

# app/resource_adapters/s3_dataset_adapter.py
class S3DatasetAdapter(FaceDatasetPort):
    def get_dataset_config_path(self) -> str:
        # ① S3에서 내려받고 ② 캐시 폴더에 풀고 ③ 그 경로를 돌려준다
        self._download_if_absent()
        return str(Path(self._cache_dir, "data.yaml").resolve())
어댑터데이터가 있는 곳축 1에서의 위치
LocalYoloDatasetAdapter내 디스크왼쪽 (로컬)
S3DatasetAdapterAWS S3 버킷오른쪽 (클라우드)

⭐ 축 1이 코드 구조로 내려앉은 자리입니다. "로컬이냐 클라우드냐"는 이제 개념이 아니라 어댑터 두 개입니다.

4.4 유스케이스 — 학습 코드는 바뀌지 않는다

# app/use_cases/train_face_model.py
class TrainFaceModelUseCase:
    def __init__(self, dataset_port: FaceDatasetPort) -> None:
        self._dataset_port = dataset_port          # ⭐ DI — 밖에서 주입받는다

    def execute(self, epochs: int = 50, imgsz: int = 640) -> None:
        data_path = self._dataset_port.get_dataset_config_path()
        model = YOLO("yolo11n.pt")
        model.train(data=data_path, epochs=epochs, imgsz=imgsz, device="cpu")

데이터 소스가 바뀌어도 App 코드는 수정할 필요가 없습니다. 조립하는 곳에서 어댑터만 갈아끼우면 됩니다.

# 조립소 — 여기 한 줄만 바뀐다
use_case = TrainFaceModelUseCase(LocalYoloDatasetAdapter(base_dir="./dataset"))
# use_case = TrainFaceModelUseCase(S3DatasetAdapter(bucket="...", prefix="...", cache_dir="./.cache"))

4.5 폴더 이름 확정 — resource_adapters

이번 주에 자원 어댑터 폴더명을 resource_adapters 로 확정했습니다.

app/
├── domain/                       # 순수 도메인 (가장 안쪽)
├── ports/
│   ├── inbound/                  # 밖에서 들어오는 문 (API·MCP 등)
│   └── outbound/                 # 밖으로 나가는 문 (DB·S3·데이터셋 등)
├── use_cases/                    # ⭐ 훈련 코드가 사는 곳
└── resource_adapters/            # outbound 포트의 구현체들

⚠️ 왜 폴더 이름을 확정하는 것이 중요한가 — 이름이 흔들리면 AI가 코드를 생성할 때마다 다른 자리에 파일을 만듭니다. 9주차에서 배운 하네스의 규칙이 이런 것입니다. CLAUDE.md 에 폴더 규칙을 적어두는 이유가 여기 있습니다.


5. 리눅스와 SSH — 내 두 번째 PC를 남의 컴퓨터로 만들기

여기서부터 이 장의 후반부입니다. 축 1의 오른쪽을 직접 만듭니다.

5.1 접속PC와 원격PC (축 5)

두 대의 컴퓨터에 역할 이름을 붙입니다. 이 이름을 흔들지 않는 것이 전체 실습의 절반입니다.

접속PC (클라이언트)원격PC (서버)
하는 일ssh 명령을 친다sshd(SSH 데몬)가 떠 있다
OS 제약윈도우·맥OS·우분투 무엇이든 무관SSH로 접속하려면 반드시 우분투(리눅스)
접근 경로내가 직접 앉아서 씀접속PC를 통해서만 접근
브랜치 이름laptopdesktop

⭐ 원격PC는 SSH로 반드시 우분투여야 하고, 원격은 접속PC를 통해서만 접근합니다. "원격PC 앞에 가서 직접 조작"하는 순간 이 실습의 의미가 사라집니다. 매일 아침 전원을 켜러 갈 때를 제외하면 원격PC의 화면은 보지 않는다는 규칙으로 진행합니다.

5.2 SSH의 정의

SSH(Secure Shell) — [원격 접속 프로토콜]인데, [통신 구간 전체를 암호화한다]는 점에서 텔넷(Telnet, 평문·23번)과 구별된다. 기본 포트는 22번입니다.

ssh <사용자이름>@<주소>
ssh desktop@192.168.0.228          # 기본 포트 22
ssh -p 2222 desktop@192.168.0.228  # 포트를 바꿨다면 -p 로 지정

이 한 줄이 성립하려면 네 가지가 동시에 참이어야 합니다. 이번 주 실습의 대부분이 이 넷을 만드는 일입니다.

  1. 원격PC에 SSH 서버가 설치되어 있다
  2. 그 서버가 실행 중이다 (active (running))
  3. 원격PC까지 가는 네트워크 경로가 뚫려 있다 (포트포워딩·방화벽)
  4. 그 주소가 지금도 유효하다 ← 이 넷째 조건이 이 주의 과제입니다

5.3 리눅스 기초 — ~ 는 home입니다

원격PC에 들어가면 프롬프트에 ~ 가 보입니다. (desktop@DESKTOP-PC:~$)

표기뜻
~홈 디렉터리(home). 그 사용자의 개인 폴더 = /home/<사용자이름>
/루트. 파일 시스템의 최상단
. / ..현재 디렉터리 / 상위 디렉터리

⚠️ ~ 를 "홈페이지"나 "루트"로 오해하지 마세요. 루트(/)는 시스템 전체의 꼭대기이고, ~(홈)는 나에게 할당된 방입니다. 윈도우의 C:\Users\사용자이름 에 해당합니다. 이동은 cd ~(홈), cd /(루트), cd -(직전 위치), 현재 위치 확인은 pwd 입니다.

5.4 WSL — 윈도우 안의 우분투

원격PC가 윈도우여도 됩니다. WSL(Windows Subsystem for Linux) 을 깔면 그 안에 우분투가 들어갑니다. 7주차에서 도커를 깔 때 이미 한 번 설치했던 그것인데, 이번에는 도커의 부속품이 아니라 그 자체가 서버가 됩니다.

wsl --install      # 관리자 권한 PowerShell. 설치 후 재부팅
┌──────────────────────── 원격 PC (물리적인 컴퓨터) ────────────────────────┐
│   윈도우 (호스트)                                                         │
│   ├── IP: 192.168.0.228   ← 공유기가 나눠준 LAN 주소                       │
│   └── WSL2 (가상 네트워크 안)                                             │
│        └── 우분투                                                         │
│             ├── IP: 172.30.174.137   ← WSL 내부 주소 (NAT 뒤)             │
│             └── sshd (SSH 서버)  ← 우리가 접속하고 싶은 대상               │
└──────────────────────────────────────────────────────────────────────────┘

⭐ 이 그림이 이번 주 모든 삽질의 원인이자 해답입니다. 접속하고 싶은 대상은 안쪽(우분투) 에 있는데, 밖에서 보이는 주소는 바깥쪽(윈도우) 것입니다.

5.5 사용자 만들기 — 이름을 브랜치와 맞춘다

우분투를 처음 켜면 루트 사용자로 시작합니다. 작업용 사용자를 따로 만듭니다.

adduser desktop                    # 사용자 생성 (비밀번호를 물어봄)
usermod -aG sudo desktop           # 'sudo' 그룹에 추가 = 관리자 권한 '자격' 부여
ubuntu config --default-user desktop    # 다음부터 이 사용자로 시작
명령하는 일몇 번 하나
adduser <이름>사용자와 홈 디렉터리(/home/<이름>) 생성1번
usermod -aG sudo <이름>관리자 권한의 '자격'을 부여1번(일회성)
sudo <명령>그 자격을 실제로 발동매번

⭐ usermod -aG sudo 는 권한을 '주는' 게 아니라 '자격을 부여하는' 것입니다. 신분증을 발급받는 것(일회성)과 그 신분증을 꺼내 보이는 것(매번)의 차이입니다. -aG 의 a 는 append이며, 이걸 빼면 기존 그룹이 전부 날아갑니다.

⭐ 이름 규칙 — PC별 유저 이름은 깃허브 브랜치 이름과 일치시킨다

PC우분투 사용자 이름깃허브 브랜치 이름
원격 SSH 서버desktopdesktop
로컬 WSL(접속PC)laptoplaptop

두 대에서 같은 저장소를 동시에 만지기 때문입니다. 터미널 프롬프트에 desktop@... 이 떠 있으면 지금 어느 PC에 있고 어느 브랜치를 만져야 하는지 눈으로 확인됩니다. 9주차에서 배운 대로, 규칙을 사람이 기억하게 하지 말고 화면이 알려주게 만드는 것입니다.

5.6 OpenSSH 서버 설치 — 클라이언트가 아닙니다

가장 자주 틀리는 지점입니다.

OpenSSH 클라이언트OpenSSH 서버
하는 일내가 나가서 접속남이 들어오게 받아줌
필요한 쪽접속PC원격PC
윈도우 기본값대부분 이미 설치됨설치 안 되어 있음
윈도우: 설정 > 시스템 > 선택적 기능 > [기능 보기] > "openssh" 검색 > OpenSSH 서버 > 설치
sudo apt update && sudo apt install -y openssh-server     # 우분투(WSL) 쪽

⚠️ "SSH가 이미 되는데 왜 또 깔지?" — 그건 클라이언트입니다. ssh 명령을 칠 수 있다는 것과, 남이 나에게 ssh 로 들어올 수 있다는 것은 완전히 다른 이야기입니다. 축 5입니다.

5.7 윈도우 터미널 설치와 기본 프로필

winget install Microsoft.WindowsTerminal      # 윈도우키 + R → cmd

설치 후 작업표시줄에 고정하고, 터미널 기본 프로필을 Ubuntu로 설정합니다(설정 → 시작 → 기본 프로필).

💡 기본 프로필을 우분투로 바꾸면 터미널을 열 때마다 바로 리눅스입니다. PowerShell과 우분투를 오가며 헷갈리는 일이 크게 줄어듭니다. 이것도 하네스입니다. 실수할 여지를 설정으로 없애는 것입니다.

5.8 포트포워딩 — 왜 이게 필요한가 ⭐

5.4절 그림을 다시 보세요. 문제는 이렇습니다.

접속PC ──── ssh desktop@192.168.0.228:22 ────▶ 윈도우 (192.168.0.228)
                                                  │  ??? 여기서 끊긴다
                                                  ▼
                                               우분투 (172.30.174.137) 의 sshd

192.168.0.228 은 윈도우의 주소입니다. 우분투는 그 안쪽 가상 네트워크(NAT) 뒤에 있어 밖에서 직접 보이지 않습니다. 그래서 윈도우에게 "22번 포트로 들어온 요청은 172.30.174.137의 22번으로 넘겨라" 라고 시킵니다. 이것이 포트포워딩(Port Forwarding), 윈도우 용어로는 portproxy 입니다.

# 관리자 권한 PowerShell
netsh interface portproxy add v4tov4 listenport=22 listenaddress=0.0.0.0 connectport=22 connectaddress=172.26.13.99
조각뜻
netsh interface portproxy add v4tov4IPv4 → IPv4 전달 규칙을 추가한다
listenport=22밖에서 들어오는 22번 포트를
listenaddress=0.0.0.0모든 랜카드에서 받는다
connectport=22안쪽 22번 포트로
connectaddress=172.26.13.99우분투의 내부 IP로 넘긴다

⚠️ connectaddress 에 넣을 우분투 내부 IP는 우분투에서 ip addr(또는 hostname -I)로 확인합니다.

5.9 방화벽 — 규칙을 만들어도 막혀 있으면 못 들어온다

포트포워딩은 "들어오면 어디로 보낼지" 를 정할 뿐, "들어와도 되는지" 는 방화벽이 정합니다. 둘 다 필요합니다.

netsh advfirewall firewall add rule name="WSL_SSH" dir=in action=allow protocol=TCP localport=22

dir=in(들어오는 방향) / action=allow(허용) / protocol=TCP / localport=22(22번 포트) / name(나중에 찾아 지울 수 있게 알아볼 이름으로).

⚠️ 접속이 안 될 때 점검 순서는 항상 안쪽 → 바깥쪽입니다. ① 우분투에서 sshd 가 떠 있는가 → ② 포트포워딩 규칙이 있는가 → ③ 방화벽이 열려 있는가 → ④ 주소가 맞는가. 순서를 바꾸면 시간을 훨씬 많이 씁니다.

5.10 규칙이 꼬였을 때 — 지우고 다시 등록한다

며칠 실습하면 포트 규칙이 여러 개 쌓여서 꼬입니다. 지우고 다시 등록하는 것이 가장 빠릅니다.

# 관리자 권한 PowerShell — .\ 를 반드시 붙입니다
.\netsh.exe interface portproxy delete v4tov4 listenport=22 listenaddress=0.0.0.0
.\netsh.exe interface portproxy add    v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=2222 connectaddress=172.30.174.137

netsh interface portproxy show all      # 현재 규칙 확인

.\ 는 왜 붙이는가 — PowerShell에서 netsh 만 치면 PowerShell의 별칭이나 함수가 먼저 잡힐 수 있습니다. .\netsh.exe 는 "지금 이 경로의 진짜 실행파일" 을 명시적으로 지목하는 표기입니다. "분명 명령을 쳤는데 아무 일도 안 일어난다"의 흔한 원인입니다.

왜 22번이 아니라 2222번인가 — 22번은 SSH의 표준 포트라 자동 공격 스캔이 가장 먼저 두드립니다. 2222번은 관례적인 대체 포트로, 충돌을 피하고 스캔도 조금 줄어듭니다.

⚠️ 포트를 바꾸면 네 곳을 전부 같이 바꿔야 합니다. ① sshd_config 의 Port ② portproxy의 listenport·connectport ③ 방화벽 localport ④ 접속 명령의 -p. 하나라도 22로 남아 있으면 안 됩니다.

5.11 sshd 설정 — 서버가 무엇을 허용할지 정한다

sudo nano /etc/ssh/sshd_config
Port 2222
PasswordAuthentication yes
AllowUsers desktop
ChallengeResponseAuthentication no
설정뜻왜 이렇게 두었는가
Port 2222서버가 귀 기울일 포트표준 포트 회피
PasswordAuthentication yes비밀번호 로그인 허용실습 단계라 키 배포 전
AllowUsers desktop이 사용자만 접속 가능나머지는 아예 시도조차 못 함
ChallengeResponseAuthentication no대화형 추가 인증 끔인증 경로를 좁혀 원인 추적을 쉽게

수정 후 반드시 재시작합니다. 설정 파일은 읽는 시점이 시작할 때입니다.

sudo service ssh restart

⚠️ PasswordAuthentication yes 는 실습용 설정입니다. 실제 서버에서는 공개키 인증만 허용하고 비밀번호 로그인을 끄는 것이 원칙입니다. 비밀번호는 추측 공격의 대상이 되지만 키는 그렇지 않습니다.

앞으로 다시 만납니다 — 14주차에서 AWS EC2에 붙을 때 .pem 키 파일을 씁니다. 그때가 공개키 인증입니다.

5.12 SSH 서버가 이상할 때 — 깨끗이 다시 깐다

sudo apt update && sudo apt remove -y openssh-server && sudo apt install -y openssh-server

세 명령을 && 로 이었습니다. && 는 앞 명령이 성공했을 때만 다음을 실행하므로, update 가 실패하면 지우지도 않습니다. 안전장치가 명령어 안에 들어 있는 것입니다.

⚠️ 재설치하면 sshd_config 가 기본값으로 돌아갈 수 있습니다. 5.11절 설정을 다시 넣어야 합니다.