주요 HTTP Status

|

주요 HTTP Status Code

200 OK

정상 응답 상태 코드

301 Moved Permanently

HTTP 요청을 보낸 주소의 URL이 변경되었다는 상태 코드.
보통 Location 헤더가 포함되는 것이 일반적이며,
Location 헤더에 새로운 주소가 포함되어 있음

HTTP/1.1 301 Moved Permanently
Location: http://www.snowdeer.com/index.html

400 Bad Request

잘못된 요청일 때 보내는 응답 코드.
주로 Request에 잘못된 값들이 보내졌을 때 사용함

401 Unauthorized

사용자가 로그인이 필요한 경우 401을 리턴

403 Forbidden

로그인은 되어 있으나, 해당 리소스에 대한 접근 권한이 없는 경우 리턴

404 Not Found

HTTP 요청을 보내는 URL 주소가 존재하지 않을 경우 보내는 응답 코드

500 Internal Server Error

서버 내부 오류가 발생했을 때 리턴하는 응답 코드

Poetry 샘플 코드

|

Poetry 활용 샘플 코드 작성

Poetry Init

$ mkdir snowdeer-poerty
$ cd snowdeer-poerty
$ poetry init -n

Poetry Add [Packages]

$ poetry add "fastapi[standard]" uvicorn

Creating virtualenv snowdeer-poetry in /Users/snowdeer/Workspace/snowdeer/snowdeer-poetry/.venv
Using version ^0.125.0 for fastapi
Using version ^0.38.0 for uvicorn

Updating dependencies
Resolving dependencies... Downloading https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.
Resolving dependencies... Downloading https://files.pythonhosted.org/packages/c3/e2/51d9ee443aabcd5aa581d45b18b6198ced364b5cd97e5504c5d782ceb82c/fastar-0.8.0-cp3
Resolving dependencies... Downloading https://files.pythonhosted.org/packages/c3/e2/51d9ee443aabcd5aa581d45b18b6198ced364b5cd97e5504c5d782ceb82c/fastar-0.8.0-cp3
Resolving dependencies... Downloading https://files.pythonhosted.org/packages/86/7a/b970cd0138b0ece72eb28f086e933f9ed75b795716ad3de5ab22994b3b54/rignore-0.7.6-cp
Resolving dependencies... (6.2s)

Package operations: 42 installs, 0 updates, 0 removals

  • Installing typing-extensions (4.15.0)
  • Installing exceptiongroup (1.3.1)
  • Installing idna (3.11)
  • Installing mdurl (0.1.2)
  • Installing anyio (4.12.0)
  • Installing certifi (2025.11.12)
  • Installing dnspython (2.8.0)
  • Installing h11 (0.16.0)
  • Installing markdown-it-py (4.0.0)
  • Installing pygments (2.19.2)
  • Installing annotated-types (0.7.0)
  • Installing click (8.3.1)
  • Installing email-validator (2.3.0)
  • Installing httpcore (1.0.9)
  • Installing httptools (0.7.1)
  • Installing pydantic-core (2.41.5)
  • Installing python-dotenv (1.2.1)
  • Installing pyyaml (6.0.3)
  • Installing rich (14.2.0)
  • Installing shellingham (1.5.4)
  • Installing typing-inspection (0.4.2)
  • Installing urllib3 (2.6.2)
  • Installing uvloop (0.22.1)
  • Installing watchfiles (1.1.1)
  • Installing websockets (15.0.1)
  • Installing fastar (0.8.0)
  • Installing httpx (0.28.1)
  • Installing pydantic (2.12.5)
  • Installing rich-toolkit (0.17.1)
  • Installing rignore (0.7.6)
  • Installing sentry-sdk (2.48.0)
  • Installing typer (0.20.1)
  • Installing uvicorn (0.38.0)
  • Installing fastapi-cloud-cli (0.7.0)
  • Installing markupsafe (3.0.3)
  • Installing tomli (2.3.0)
  • Installing annotated-doc (0.0.4)
  • Installing fastapi-cli (0.0.16)
  • Installing jinja2 (3.1.6)
  • Installing python-multipart (0.0.21)
  • Installing starlette (0.50.0)
  • Installing fastapi (0.125.0)

Writing lock file

디렉토리 구조

아래와 같은 구조의 디렉토리 생성

.
├── poetry.lock
├── pyproject.toml
├── README.md
└── snowdeer_poetry
    ├── __init__.py
    └── main.py

snowdeer_poetry/main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def hello_endpoint():
    return ["hello", "snowdeer"]

실행

그 이후 루트 디렉토리에서 아래 명령어 실행

$ poetry run uvicorn snowdeer_poetry.main:app --host 0.0.0.0 --port 8314 --reload

INFO:     Will watch for changes in these directories: ['/Users/snowdeer/Workspace/snowdeer/snowdeer-poetry']
INFO:     Uvicorn running on http://0.0.0.0:8314 (Press CTRL+C to quit)
INFO:     Started reloader process [60728] using WatchFiles
INFO:     Started server process [60770]
INFO:     Waiting for application startup.

코드에서 uvicorn 실행하는 법

만약 코드에서 uvicorn을 실행하려면 아래와 같이 main.py 수정

수정된 main.py

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def hello_endpoint():
    return ["hello", "snowdeer"]

import uvicorn

def dev_server():
    uvicorn.run("snowdeer_poetry.main:app", host="0.0.0.0", port=8314, reload=True)

pyproject.toml

# 파일 맽 끝에 아래 라인 추가

[tool.poetry.scripts]
dev = "snowdeer_poetry.main:dev_server"

실행

$ poetry run dev

INFO:     Will watch for changes in these directories: ['/Users/snowdeer/Workspace/snowdeer/snowdeer-poetry']
INFO:     Uvicorn running on http://0.0.0.0:8314 (Press CTRL+C to quit)
INFO:     Started reloader process [64017] using WatchFiles
INFO:     Started server process [64022]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

영상 캡셔닝 AI 모델(ViT) 테스트

|

실행 환경

맥북(M1 Pro) 기반으로 실습 테스트

패키지 설치

# 가상환경 생성
python -m venv venv
source venv/bin/activate

# 패키지 설치
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu
pip install transformers pillow opencv-python accelerate

사용 가능 디바이스 확인

Apple Silicon 환경에서는 mps 디바이스 사용 가능

ViT 기반 모델

영상에서 8개의 keyframe을 추출해서 캡션을 생성하는 코드

main.py

import torch
import cv2
from PIL import Image
from transformers import VisionEncoderDecoderModel, ViTImageProcessor, GPT2Tokenizer
import argparse
import sys

def setup_device():
    """M1 Pro MPS 또는 CPU 설정"""
    if torch.backends.mps.is_available():
        return "mps"
    return "cpu"

def load_model(device):
    """모델과 토크나이저 로드"""
    model_name = "nlpconnect/vit-gpt2-image-captioning"
    model = VisionEncoderDecoderModel.from_pretrained(model_name)
    feature_extractor = ViTImageProcessor.from_pretrained(model_name)
    tokenizer = GPT2Tokenizer.from_pretrained(model_name)
    
    # 패딩 토큰 설정
    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token
    
    model.to(device)
    model.eval()
    return model, feature_extractor, tokenizer

def extract_key_frames(video_path, max_frames=8):
    """주요 프레임 추출"""
    cap = cv2.VideoCapture(video_path)
    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    
    frames = []
    step = max(1, total_frames // max_frames)
    
    for i in range(0, total_frames, step):
        cap.set(cv2.CAP_PROP_POS_FRAMES, i)
        ret, frame = cap.read()
        if ret:
            rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            frames.append(Image.fromarray(rgb_frame))
    
    cap.release()
    return frames

def generate_captions(model, feature_extractor, tokenizer, frames, device):
    """각 프레임 캡션 생성"""
    captions = []
    
    for frame in frames:
        # 이미지 전처리
        pixel_values = feature_extractor(images=frame, return_tensors="pt").pixel_values.to(device)
        
        # 캡션 생성
        with torch.no_grad():
            generated_ids = model.generate(
                pixel_values, 
                max_length=30,
                num_beams=5,
                early_stopping=True,
                pad_token_id=tokenizer.eos_token_id
            )
        
        # 토크나이저로 디코딩
        caption = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
        captions.append(caption.strip())
    
    return captions

def summarize_video(captions):
    """캡션들을 하나의 영어 문장으로 요약"""
    if not captions:
        return "No content detected in the video."
    
    # 중복 제거 후 가장 대표적인 캡션 선택
    unique_captions = list(set(captions))
    if len(unique_captions) <= 2:
        return " and ".join(unique_captions)
    
    # 주요 장면들 결합
    main_scenes = unique_captions[:3]
    return f"A video featuring {', '.join(main_scenes[:-1])} and {main_scenes[-1]}."

def main(video_path):
    print(f"Processing video: {video_path}")
    
    # 디바이스 및 모델 설정
    device = setup_device()
    print(f"Using device: {device}")
    
    model, feature_extractor, tokenizer = load_model(device)
    
    # 프레임 추출
    frames = extract_key_frames(video_path, max_frames=8)
    print(f"Extracted {len(frames)} key frames")
    
    # 캡션 생성
    captions = generate_captions(model, feature_extractor, tokenizer, frames, device)
    print("Generated captions:", captions)
    
    # 최종 요약
    summary = summarize_video(captions)
    print("\n" + "="*50)
    print("VIDEO SUMMARY (영어 한 문장):")
    print(summary)
    print("="*50)
    
    return summary

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python video_caption.py ")
        sys.exit(1)
    
    video_path = sys.argv[1]
    main(video_path)
</pre>

### 실행 결과

$ python video.py example6.mp4

Processing video: example6.mp4
Using device: mps
Extracted 9 key frames
The attention mask is not set and cannot be inferred from input because pad token is same as eos token. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.
We strongly recommend passing in an `attention_mask` since your input_ids may be padded. See https://huggingface.co/docs/transformers/troubleshooting#incorrect-output-when-padding-tokens-arent-masked.
You may ignore this warning if your `pad_token_id` (50256) is identical to the `bos_token_id` (50256), `eos_token_id` (50256), or the `sep_token_id` (None), and your input is not padded.
Generated captions: ['a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a man riding a wave on top of a surfboard', 'a surfer riding a wave in the ocean']

==================================================
VIDEO SUMMARY (영어 한 문장):
a surfer riding a wave in the ocean and a man riding a wave on top of a surfboard
==================================================

이미지 인식 AI 모델(ViT) 테스트

|

실행 환경

맥북(M1 Pro) 기반으로 실습 테스트

패키지 설치

# 가상환경 생성
python -m venv venv
source venv/bin/activate

# 패키지 설치
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install transformers pillow

사용 가능 디바이스 확인

Apple Silicon 환경에서는 mps 디바이스 사용 가능

import torch

if torch.backends.mps.is_available():
    device = torch.device("mps")   # Apple GPU
elif torch.cuda.is_available():
    device = torch.device("cuda")
else:
    device = torch.device("cpu")

print("Using device:", device)

ViT 기반 모델

Hugging Face의 google/vit-base-patch16-224는 ImageNet으로 사전학습된 표준 ViT로, 맥북에서도 작은 배치로 실행 가능한 모델임. 만약 더 가벼운 모델이 필요할 경우에는 google/vit-small-patch16-224Distilled ViT 계열도 선택 가능함

main.py

아래는 input.jpg 파일을 읽어서 어떤 그림인지 키워드를 리턴해주는 코드

import torch
from PIL import Image
from transformers import AutoImageProcessor, ViTForImageClassification

# 1) 디바이스 선택 (위에서 설명한 로직 재사용 가능)
if torch.backends.mps.is_available():
    device = torch.device("mps")
elif torch.cuda.is_available():
    device = torch.device("cuda")
else:
    device = torch.device("cpu")
print("Using device:", device)

# 2) 모델 이름 선택 (맥북에서도 무난한 베이스 모델)
model_name = "google/vit-base-patch16-224"

# 3) 이미지 전처리기와 모델 로드
image_processor = AutoImageProcessor.from_pretrained(model_name)
model = ViTForImageClassification.from_pretrained(model_name).to(device)
model.eval()

# 4) 이미지 로드 (input.jpg 경로에 이미지 준비)
image_path = "input.jpg"
image = Image.open(image_path).convert("RGB")

# 5) 전처리 및 텐서 변환
inputs = image_processor(image, return_tensors="pt")
inputs = {k: v.to(device) for k, v in inputs.items()}

# 6) 추론
with torch.no_grad():
    outputs = model(**inputs)
    logits = outputs.logits
    probs = logits.softmax(dim=-1)
    top_prob, top_idx = probs.max(dim=-1)

# 7) 결과 해석
predicted_label_idx = top_idx.item()
predicted_prob = top_prob.item()
predicted_label_name = model.config.id2label[predicted_label_idx]

print(f"Predicted class: {predicted_label_name} (p={predicted_prob:.4f})")

실행 결과

Using device: mps
Fast image processor class <class 'transformers.models.vit.image_processing_vit_fast.ViTImageProcessorFast'> is available for this model. Using slow image processor class. To use the fast image processor class set `use_fast=True`.
Predicted class: banana (p=0.9748)

ReplicaSet 및 Deployment

|

ReplicaSet

지정한 개수만큼 Pod를 복제하는 리소스

snowdeer-replicaset.yaml

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: snowdeer-http-server
  labels:
    app: snowdeer-http-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: snowdeer-http-server
  template:
    metadata:
      labels:
        app: snowdeer-http-server
    spec:
      containers:
        - name: nginx
          image: nginx:latest
$ kubectl get pods                         

NAME                         READY   STATUS    RESTARTS   AGE
snowdeer-http-server-45gsp   1/1     Running   0          14m
snowdeer-http-server-5tk4z   1/1     Running   0          14m
snowdeer-http-server-st74m   1/1     Running   0          14m
$ kubectl get replicaset snowdeer-http-server

NAME                   DESIRED   CURRENT   READY   AGE
snowdeer-http-server   3         3         3       14m

Deployment

하지만, 본격적 운영 환경에서는 ReplicaSet을 추천하지는 않고 Deployment를 권장함. Deployment를 사용하는 가장 큰 이유는 무중단 업데이트 기능.

snowdeer-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: snowdeer-nginx-deployment
  labels:
    app: snowdeer-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: snowdeer-nginx
  template:
    metadata:
      labels:
        app: snowdeer-nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.25.3
          ports:
            - containerPort: 80

실행

$ kubectl apply -f snowdeer-deployment.yaml 

deployment.apps/snowdeer-nginx-deployment created

$ kubectl get deployment

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
snowdeer-nginx-deployment   3/3     3            3           48s

$ kubectl get pods      

NAME                                         READY   STATUS    RESTARTS   AGE
snowdeer-nginx-deployment-847759b688-2zbgh   1/1     Running   0          47s
snowdeer-nginx-deployment-847759b688-j7pql   1/1     Running   0          44s
snowdeer-nginx-deployment-847759b688-nl6cw   1/1     Running   0          41s

$ kubectl get replicaset                     

NAME                                   DESIRED   CURRENT   READY   AGE
snowdeer-nginx-deployment-847759b688   3         3         3       76s

무중단 업데이트 실습

아까 작성했던 snowdeer-deployment.yaml 파일을 수정해서 nginx의 버전을 수정함

snowdeer-deployment.yaml(수정)

컨테이너 이미지를 수정함

apiVersion: apps/v1
kind: Deployment
metadata:
  name: snowdeer-nginx-deployment
  labels:
    app: snowdeer-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: snowdeer-nginx
  template:
    metadata:
      labels:
        app: snowdeer-nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80

실행

$ kubectl apply -f snowdeer-deployment.yaml 

deployment.apps/snowdeer-nginx-deployment configured

$ kubectl get pods      

NAME                                         READY   STATUS    RESTARTS   AGE
snowdeer-nginx-deployment-847759b688-gzztg   1/1     Running   0          15s
snowdeer-nginx-deployment-847759b688-xc75g   1/1     Running   0          18s
snowdeer-nginx-deployment-847759b688-zszxt   1/1     Running   0          20s

$ kubectl get replicaset                     

NAME                                   DESIRED   CURRENT   READY   AGE
snowdeer-nginx-deployment-66fb57596b   0         0         0       2m18s
snowdeer-nginx-deployment-847759b688   3         3         3       4m59s

Deployment 업데이트 방식

Deployment의 업데이트 방식은 기본적으로 RollingUpdate로 되어있음. 아래 명령어로 확인 가능

$ kubectl describe deployment snowdeer-nginx-deployment

Name:                   snowdeer-nginx-deployment
Namespace:              default
CreationTimestamp:      Sun, 07 Dec 2025 22:11:52 +0900
Labels:                 app=snowdeer-nginx
Annotations:            deployment.kubernetes.io/revision: 4
Selector:               app=snowdeer-nginx
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
// ...

RollingUpdateStrategy: 25% max unavailable, 25% max surge 항목은 전체 25%의 Pod까지 동시 종료할 수 있음을 의미함. max surge는 최대 몇 개의 Pod를 새로 생성할 수 있는것인지를 의미하며, 오래된 Pod와 신규 Pod로 인해 필요 클러스터의 용량과 비용 증가 가능성이 있기 때문에 주의해서 설정할 필요 있음.

하지만 경우(빠르게 전체 적용 등)에 따라서는 Recreate 타입이 더 유리한 경우도 있음.

그런 경우에는 아래와 같이 선언 가능

snowdeer-deployment.yaml(수정)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: snowdeer-nginx-deployment
  labels:
    app: snowdeer-nginx
spec:
  replicas: 3
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: snowdeer-nginx
  template:
    metadata:
      labels:
        app: snowdeer-nginx
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80