KR_Docker - somaz94/python-study GitHub Wiki

Python Docker ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ ๋„์ปค ๊ธฐ์ดˆ

ํŒŒ์ด์ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ๋„์ปค ์„ค์ •์ด๋‹ค.

# ๊ธฐ๋ณธ Dockerfile
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

โœ… ํŠน์ง•:

  • ๊ฒฝ๋Ÿ‰ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€
  • ์˜์กด์„ฑ ๊ด€๋ฆฌ
  • ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
  • ๊ณ„์ธต์  ๋นŒ๋“œ ๊ตฌ์กฐ
  • ์ด๋ฏธ์ง€ ์บ์‹ฑ ํ™œ์šฉ

๋„์ปค ๋ช…๋ น์–ด:

# ์ด๋ฏธ์ง€ ๋นŒ๋“œ
docker build -t myapp:latest .

# ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰
docker run -p 5000:5000 myapp:latest

# ์ปจํ…Œ์ด๋„ˆ ๋ชฉ๋ก ํ™•์ธ
docker ps

# ๋กœ๊ทธ ํ™•์ธ
docker logs <container_id>

# ์ปจํ…Œ์ด๋„ˆ ์ค‘์ง€
docker stop <container_id>

# ์ด๋ฏธ์ง€ ๋ชฉ๋ก ํ™•์ธ
docker images


2๏ธโƒฃ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

๋„์ปค๋ฅผ ์‚ฌ์šฉํ•œ ํŒŒ์ด์ฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ ๋ฐฉ๋ฒ•์ด๋‹ค.

# ๊ฐœ๋ฐœ์šฉ Dockerfile
FROM python:3.9-slim

WORKDIR /app

# ๊ฐœ๋ฐœ ๋„๊ตฌ ์„ค์น˜
RUN apt-get update && apt-get install -y \
    git \
    curl \
    vim \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# ์˜์กด์„ฑ ์„ค์น˜
COPY requirements.txt requirements-dev.txt ./
RUN pip install --no-cache-dir -r requirements.txt -r requirements-dev.txt

# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ
COPY . .

# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

โœ… ํŠน์ง•:

  • ๊ฐœ๋ฐœ ๋„๊ตฌ ํฌํ•จ
  • ํ•ซ ๋ฆฌ๋กœ๋”ฉ
  • ๋””๋ฒ„๊น… ์ง€์›
  • ๋ณผ๋ฅจ ๋งˆ์šดํŠธ๋กœ ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜
  • ๊ฐœ๋ฐœ์šฉ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •

VS Code์™€ ๋„์ปค ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์—ฐ๋™:

// .devcontainer/devcontainer.json
{
  "name": "Python Dev Environment",
  "dockerFile": "../Dockerfile.dev",
  "context": "..",
  "extensions": [
    "ms-python.python",
    "ms-azuretools.vscode-docker",
    "ms-python.vscode-pylance"
  ],
  "forwardPorts": [8000],
  "postCreateCommand": "pip install --user -e .",
  "remoteUser": "vscode",
  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash",
    "python.pythonPath": "/usr/local/bin/python",
    "python.linting.enabled": true,
    "python.linting.pylintEnabled": true
  }
}


3๏ธโƒฃ Docker Compose ์„ค์ •

์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋กœ ๊ตฌ์„ฑ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ด€๋ฆฌํ•˜๋Š” Docker Compose ์„ค์ •์ด๋‹ค.

# docker-compose.yml
version: '3.8'

services:
  web:
    build: 
      context: .
      dockerfile: Dockerfile
    container_name: python_web
    restart: unless-stopped
    ports:
      - "8000:8000"
    volumes:
      - .:/app
      - static_data:/app/static
    environment:
      - FLASK_ENV=development
      - DATABASE_URL=postgresql://user:password@db:5432/dbname
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    networks:
      - app_network
  
  db:
    image: postgres:13
    container_name: python_db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=dbname
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - app_network
      
  redis:
    image: redis:6-alpine
    container_name: python_redis
    restart: unless-stopped
    volumes:
      - redis_data:/data
    networks:
      - app_network

volumes:
  postgres_data:
  redis_data:
  static_data:

networks:
  app_network:
    driver: bridge

โœ… ํŠน์ง•:

  • ์„œ๋น„์Šค ์ •์˜
  • ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ
  • ๋ณผ๋ฅจ ๋งˆ์šดํŠธ
  • ๋„คํŠธ์›Œํฌ ์„ค์ •
  • ์„œ๋น„์Šค ๊ฐ„ ์˜์กด์„ฑ ์ •์˜

Docker Compose ๋ช…๋ น์–ด:

# ์„œ๋น„์Šค ์‹œ์ž‘
docker-compose up -d

# ์„œ๋น„์Šค ์ƒํƒœ ํ™•์ธ
docker-compose ps

# ์„œ๋น„์Šค ๋กœ๊ทธ ํ™•์ธ
docker-compose logs -f web

# ํŠน์ • ์„œ๋น„์Šค ์žฌ์‹œ์ž‘
docker-compose restart web

# ์„œ๋น„์Šค ์ค‘์ง€
docker-compose stop

# ์„œ๋น„์Šค ์ œ๊ฑฐ (๋ณผ๋ฅจ ํฌํ•จ)
docker-compose down -v


4๏ธโƒฃ ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ

๋„์ปค ์ด๋ฏธ์ง€ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ ๊ตฌ์„ฑ์ด๋‹ค.

# ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ Dockerfile
# ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€
FROM python:3.9-slim as builder

WORKDIR /app

# ๋นŒ๋“œ ์˜์กด์„ฑ ์„ค์น˜
RUN apt-get update && apt-get install -y \
    gcc \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt

# ์ตœ์ข… ์Šคํ…Œ์ด์ง€
FROM python:3.9-slim

WORKDIR /app

# ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋งŒ ์„ค์น˜
RUN apt-get update && apt-get install -y \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# ๋นŒ๋“œ ์Šคํ…Œ์ด์ง€์—์„œ ์ƒ์„ฑ๋œ ํœ  ํŒŒ์ผ ๋ณต์‚ฌ
COPY --from=builder /app/wheels /wheels
RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \
    && rm -rf /wheels

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋ณต์‚ฌ
COPY . .

# ๋น„๊ถŒํ•œ ์‚ฌ์šฉ์ž ์„ค์ •
RUN adduser --disabled-password --gecos '' appuser
USER appuser

# ์‹คํ–‰ ๋ช…๋ น
EXPOSE 8000
CMD ["gunicorn", "app:app", "-w", "4", "-b", "0.0.0.0:8000"]

โœ… ํŠน์ง•:

  • ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์ ํ™”
  • ๋นŒ๋“œ ์บ์‹œ ํ™œ์šฉ
  • ๋ณด์•ˆ์„ฑ ํ–ฅ์ƒ
  • ์˜์กด์„ฑ ๊ณ„์ธต ๋ถ„๋ฆฌ
  • ์‹คํ–‰ ํ™˜๊ฒฝ ์ตœ์†Œํ™”

์ด๋ฏธ์ง€ ํฌ๊ธฐ ๋น„๊ต:

# ์ผ๋ฐ˜ ๋นŒ๋“œ
$ docker build -t myapp:standard -f Dockerfile.standard .
$ docker images myapp:standard
REPOSITORY   TAG        SIZE
myapp        standard   1.2GB

# ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ
$ docker build -t myapp:multistage -f Dockerfile.multistage .
$ docker images myapp:multistage
REPOSITORY   TAG         SIZE
myapp        multistage   450MB


5๏ธโƒฃ ๋„์ปค ์ตœ์ ํ™”

ํŒŒ์ด์ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋„์ปค ์ด๋ฏธ์ง€๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

# ์ตœ์ ํ™”๋œ Dockerfile
FROM python:3.9-slim

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=off \
    PIP_DISABLE_PIP_VERSION_CHECK=on \
    PIP_DEFAULT_TIMEOUT=100 \
    POETRY_VERSION=1.1.14 \
    POETRY_NO_INTERACTION=1 \
    POETRY_VIRTUALENVS_CREATE=false

# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
WORKDIR /app

# ์‹œ์Šคํ…œ ์˜์กด์„ฑ ์„ค์น˜
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        gcc \
        postgresql-client \
        curl \
    && rm -rf /var/lib/apt/lists/*

# Poetry ์„ค์น˜ ๋ฐ ์˜์กด์„ฑ ๊ด€๋ฆฌ
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH="/root/.local/bin:$PATH"

# Poetry ์˜์กด์„ฑ ํŒŒ์ผ ๋ณต์‚ฌ ๋ฐ ์„ค์น˜
COPY pyproject.toml poetry.lock* ./
RUN poetry install --no-dev --no-root

# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ
COPY . .

# ์‚ฌ์ „ ์ปดํŒŒ์ผ
RUN python -m compileall /app

# ํฌํŠธ ์„ค์ •
EXPOSE 8000

# ํ—ฌ์Šค์ฒดํฌ
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

# ์‹คํ–‰ ๋ช…๋ น
CMD ["gunicorn", "app:app", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000"]

โœ… ํŠน์ง•:

  • ์„ฑ๋Šฅ ์ตœ์ ํ™”
  • ์บ์‹œ ๋ ˆ์ด์–ด ๊ด€๋ฆฌ
  • ๋ณด์•ˆ ์„ค์ •
  • ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ ์ ์šฉ
  • ํ—ฌ์Šค์ฒดํฌ ๊ตฌํ˜„

๋„์ปค ์ด๋ฏธ์ง€ ์Šค์บ”:

# Trivy๋ฅผ ์‚ฌ์šฉํ•œ ๋ณด์•ˆ ์ทจ์•ฝ์  ์Šค์บ”
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  aquasec/trivy image myapp:latest

# ์ด๋ฏธ์ง€ ๋ ˆ์ด์–ด ๋ถ„์„
docker history myapp:latest


6๏ธโƒฃ ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ

๋„์ปค๋ฅผ ์‚ฌ์šฉํ•œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ๊ตฌ์„ฑ๊ณผ ๋ฐฐํฌ ์ „๋žต์ด๋‹ค.

# docker-compose.prod.yml
version: '3.8'

services:
  web:
    image: myapp:${VERSION:-latest}
    restart: always
    expose:
      - "8000"
    environment:
      - DATABASE_URL=postgresql://user:${DB_PASSWORD}@db:5432/myapp
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY=${SECRET_KEY}
      - ENVIRONMENT=production
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    depends_on:
      - db
      - redis
    networks:
      - app_network
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
  
  db:
    image: postgres:13
    restart: always
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backups:/backups
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=myapp
    networks:
      - app_network
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G
      placement:
        constraints: [node.role == manager]
        
  redis:
    image: redis:6-alpine
    restart: always
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    networks:
      - app_network
      
  nginx:
    image: nginx:latest
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/ssl:/etc/nginx/ssl
      - static_volume:/var/www/static
    depends_on:
      - web
    networks:
      - app_network
      
  certbot:
    image: certbot/certbot
    volumes:
      - ./nginx/ssl:/etc/letsencrypt
      - ./nginx/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    
volumes:
  postgres_data:
  redis_data:
  static_volume:

networks:
  app_network:

๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ:

#!/bin/bash
# deploy.sh

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ
set -a
source .env.prod
set +a

# ์ด๋ฏธ์ง€ ๋นŒ๋“œ ๋˜๋Š” ๊ฐ€์ ธ์˜ค๊ธฐ
if [ "$1" == "build" ]; then
  echo "Building new image..."
  docker build -t myapp:$VERSION .
  docker tag myapp:$VERSION myregistry.com/myapp:$VERSION
  docker push myregistry.com/myapp:$VERSION
else
  echo "Pulling image..."
  docker pull myregistry.com/myapp:$VERSION
fi

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐฑ์—…
echo "Backing up database..."
docker-compose exec db pg_dump -U user myapp > ./backups/myapp_$(date +%Y%m%d_%H%M%S).sql

# ๋ฐฐํฌ
echo "Deploying version $VERSION..."
docker-compose -f docker-compose.prod.yml down
docker-compose -f docker-compose.prod.yml up -d

# ๋ฐฐํฌ ํ™•์ธ
echo "Checking deployment..."
sleep 10
docker-compose -f docker-compose.prod.yml ps
curl -s http://localhost/health | grep -q "ok" && echo "Deployment successful!" || echo "Deployment failed!"

โœ… ํŠน์ง•:

  • ํ™˜๊ฒฝ ๋ถ„๋ฆฌ
  • ๋กค๋ง ์—…๋ฐ์ดํŠธ ์ „๋žต
  • ๋ฆฌ์†Œ์Šค ์ œํ•œ ์„ค์ •
  • ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ
  • SSL ์ธ์ฆ์„œ ์ž๋™ํ™”


7๏ธโƒฃ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น…

๋„์ปคํ™”๋œ ํŒŒ์ด์ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋กœ๊น… ๊ตฌ์„ฑ์ด๋‹ค.

# docker-compose.monitor.yml
version: '3.8'

services:
  web:
    # ๊ธฐ์กด web ์„œ๋น„์Šค์— ์ถ”๊ฐ€
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
  
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus:/etc/prometheus
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--web.enable-lifecycle'
    ports:
      - "9090:9090"
    networks:
      - app_network
      
  grafana:
    image: grafana/grafana
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_USERS_ALLOW_SIGN_UP=false
    ports:
      - "3000:3000"
    networks:
      - app_network
      
  loki:
    image: grafana/loki
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - ./loki:/etc/loki
    networks:
      - app_network
      
  promtail:
    image: grafana/promtail
    volumes:
      - /var/log:/var/log
      - ./promtail:/etc/promtail
    command: -config.file=/etc/promtail/config.yml
    networks:
      - app_network

volumes:
  prometheus_data:
  grafana_data:

ํŒŒ์ด์ฌ ์•ฑ ๋ชจ๋‹ˆํ„ฐ๋ง ํ†ตํ•ฉ:

# app.py (Prometheus ํ†ตํ•ฉ ์˜ˆ์ œ)
from flask import Flask
from prometheus_flask_exporter import PrometheusMetrics
import logging
from logging.handlers import RotatingFileHandler
import os

app = Flask(__name__)
metrics = PrometheusMetrics(app)

# ์ปค์Šคํ…€ ๋ฉ”ํŠธ๋ฆญ ์ •์˜
request_count = metrics.counter(
    'request_count', 'App Request Count',
    labels={'endpoint': lambda: request.endpoint}
)

# ๋กœ๊น… ์„ค์ •
if not os.path.exists('logs'):
    os.mkdir('logs')
    
file_handler = RotatingFileHandler('logs/app.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
app.logger.setLevel(logging.INFO)
app.logger.info('Application startup')

@app.route('/')
@request_count
def index():
    app.logger.info('Index page requested')
    return 'Hello, World!'

@app.route('/health')
def health():
    return {"status": "ok"}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

โœ… ํŠน์ง•:

  • ์ค‘์•™ ์ง‘์ค‘์‹ ๋กœ๊น…
  • ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ๋ฐ ์‹œ๊ฐํ™”
  • ์•Œ๋ฆผ ์„ค์ •
  • ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
  • ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ์ถ”์ 


์ฃผ์š” ํŒ

โœ… ๋ชจ๋ฒ” ์‚ฌ๋ก€:

  • ๋ ˆ์ด์–ด ์ตœ์†Œํ™”: ๊ด€๋ จ RUN ๋ช…๋ น์„ ๊ฒฐํ•ฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์ ํ™”
  • ์บ์‹œ ํ™œ์šฉ: ๋ณ€๊ฒฝ์ด ์ ์€ ๋ ˆ์ด์–ด๋ฅผ ๋จผ์ € ๋ฐฐ์น˜ํ•˜์—ฌ ๋นŒ๋“œ ์‹œ๊ฐ„ ๋‹จ์ถ•
  • ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ ์ค€์ˆ˜: ๋ฃจํŠธ ๊ถŒํ•œ ์‚ฌ์šฉ ์ง€์–‘, ์ทจ์•ฝ์  ์Šค์บ๋‹ ์‹ค์‹œ
  • ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ™œ์šฉ: ์„ค์ • ๊ฐ’์„ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์ด์‹์„ฑ ํ–ฅ์ƒ
  • ๋ณผ๋ฅจ ๋งˆ์šดํŠธ ์ „๋žต: ๋ฐ์ดํ„ฐ ์ง€์†์„ฑ๊ณผ ์„ฑ๋Šฅ์„ ๊ณ ๋ คํ•œ ๋ณผ๋ฅจ ์„ค๊ณ„
  • ๋„คํŠธ์›Œํฌ ์„ค์ •: ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ๋ณด์•ˆ๊ณผ ๊ฒฉ๋ฆฌ๋ฅผ ์œ„ํ•œ ๋„คํŠธ์›Œํฌ ์„ค๊ณ„
  • ๋กœ๊น… ์„ค์ •: ๊ตฌ์กฐํ™”๋œ ๋กœ๊ทธ ํ˜•์‹๊ณผ ์ค‘์•™ ์ง‘์ค‘์‹ ๋กœ๊ทธ ์ˆ˜์ง‘ ๊ตฌ์„ฑ
  • ๋ชจ๋‹ˆํ„ฐ๋ง ๊ตฌ์„ฑ: ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
  • CI/CD ํ†ตํ•ฉ: ์ž๋™ํ™”๋œ ๋นŒ๋“œ, ํ…Œ์ŠคํŠธ ๋ฐ ๋ฐฐํฌ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ
  • ์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜: ํ™•์žฅ์„ฑ๊ณผ ๊ฐ€์šฉ์„ฑ์„ ์œ„ํ•œ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋„๊ตฌ ํ™œ์šฉ
  • ํšจ์œจ์ ์ธ ์ข…์†์„ฑ ๊ด€๋ฆฌ: Poetry ๋˜๋Š” Pipenv๋ฅผ ์‚ฌ์šฉํ•œ ์˜์กด์„ฑ ๊ด€๋ฆฌ
  • ์ ์ ˆํ•œ ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ์„ ํƒ: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๋Š” ์ ์ ˆํ•œ ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€ ์‚ฌ์šฉ
  • ํ—ฌ์Šค์ฒดํฌ ๊ตฌํ˜„: ์ปจํ…Œ์ด๋„ˆ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง์„ ์œ„ํ•œ ํ—ฌ์Šค์ฒดํฌ ์—”๋“œํฌ์ธํŠธ ์ œ๊ณต
  • ์ด๋ฏธ์ง€ ํƒœ๊น… ์ „๋žต: ์‹œ๋ฉ˜ํ‹ฑ ๋ฒ„์ €๋‹์„ ํ™œ์šฉํ•œ ์ด๋ฏธ์ง€ ํƒœ๊น… ์ „๋žต ์ˆ˜๋ฆฝ
  • ๋ฆฌ์†Œ์Šค ์ œํ•œ ์„ค์ •: ๋ฉ”๋ชจ๋ฆฌ, CPU ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ์œผ๋กœ ์•ˆ์ •์„ฑ ํ™•๋ณด


โš ๏ธ **GitHub.com Fallback** โš ๏ธ