feat: Presigned URL API 및 OpenTelemetry trace 추가
Some checks failed
Build And Test / build-and-push (push) Has been cancelled
Some checks failed
Build And Test / build-and-push (push) Has been cancelled
- Presigned URL API 추가 (MinIO/S3 private 버킷 지원) - OpenTelemetry trace 설정 추가 (DEBUG=False 시 활성화) - requirements.txt에 opentelemetry 패키지 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
16
CLAUDE.md
Normal file
16
CLAUDE.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 자동 실행 지시사항
|
||||
|
||||
다음 작업을 순서대로 실행해주세요:
|
||||
|
||||
1. venv가 없으면 생성하고 의존성 설치:
|
||||
```bash
|
||||
python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. Django 개발 서버 실행:
|
||||
```bash
|
||||
source venv/bin/activate && python3 manage.py runserver 0.0.0.0:8800
|
||||
```
|
||||
|
||||
# 연계프로젝트
|
||||
- 'msa-fe' : 프론트엔드
|
||||
@ -6,7 +6,8 @@ from .views import (
|
||||
PostListView, PostListCreateView, PostDetailView,
|
||||
CommentViewSet, TagListView,
|
||||
AttachmentListCreateView, AttachmentDeleteView,
|
||||
TempAttachmentUploadView, TempAttachmentDeleteView
|
||||
TempAttachmentUploadView, TempAttachmentDeleteView,
|
||||
PresignedUrlView
|
||||
)
|
||||
|
||||
# 댓글 라우터
|
||||
@ -36,4 +37,8 @@ urlpatterns = [
|
||||
TempAttachmentUploadView.as_view(), name='temp-attachment-upload'),
|
||||
path('attachments/<int:pk>/',
|
||||
TempAttachmentDeleteView.as_view(), name='temp-attachment-delete'),
|
||||
|
||||
# Presigned URL (MinIO/S3)
|
||||
path('presigned/<path:object_key>/',
|
||||
PresignedUrlView.as_view(), name='presigned-url'),
|
||||
]
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import boto3
|
||||
from botocore.client import Config
|
||||
from django.conf import settings
|
||||
from rest_framework import generics, viewsets, permissions, status
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.response import Response
|
||||
@ -372,3 +375,39 @@ class AttachmentDeleteView(APIView):
|
||||
|
||||
logger.info(f"Attachment '{original_name}' deleted from post {post_pk} by {username}.")
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class PresignedUrlView(APIView):
|
||||
"""Presigned URL 생성 (MinIO/S3)"""
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get_s3_client(self):
|
||||
"""S3 클라이언트 생성"""
|
||||
return boto3.client(
|
||||
's3',
|
||||
endpoint_url=settings.AWS_S3_ENDPOINT_URL,
|
||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||
config=Config(signature_version='s3v4'),
|
||||
region_name=settings.AWS_S3_REGION_NAME,
|
||||
)
|
||||
|
||||
def get(self, request, object_key):
|
||||
"""GET presigned URL 생성"""
|
||||
try:
|
||||
s3_client = self.get_s3_client()
|
||||
presigned_url = s3_client.generate_presigned_url(
|
||||
'get_object',
|
||||
Params={
|
||||
'Bucket': settings.AWS_STORAGE_BUCKET_NAME,
|
||||
'Key': object_key,
|
||||
},
|
||||
ExpiresIn=3600, # 1시간 유효
|
||||
)
|
||||
return Response({'url': presigned_url})
|
||||
except Exception as e:
|
||||
logger.error(f"Presigned URL 생성 실패: {e}")
|
||||
return Response(
|
||||
{'detail': 'Presigned URL 생성 실패'},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
@ -37,6 +37,11 @@ else:
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-ec9me^z%x7-2vwee5#qq(kvn@^cs!!22_*f-im(320_k5-=0j5')
|
||||
|
||||
# OpenTelemetry Trace 설정
|
||||
SERVICE_PLATFORM = os.getenv("SERVICE_PLATFORM", "none")
|
||||
TRACE_SERVICE_NAME = os.getenv("TRACE_SERVICE_NAME", "msa-django-blog")
|
||||
TRACE_ENDPOINT = os.getenv("TRACE_ENDPOINT", "none")
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = int(os.environ.get('DEBUG', 1))
|
||||
|
||||
|
||||
@ -9,8 +9,76 @@ https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
# Django 설정을 미리 불러온다
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_prj.settings')
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
# DEBUG 모드 아닐 때만 OpenTelemetry 활성
|
||||
if not settings.DEBUG:
|
||||
import grpc
|
||||
from opentelemetry import trace
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
from opentelemetry.sdk.trace import TracerProvider
|
||||
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
||||
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
||||
from opentelemetry.instrumentation.django import DjangoInstrumentor
|
||||
from opentelemetry.instrumentation.requests import RequestsInstrumentor
|
||||
from opentelemetry.instrumentation.logging import LoggingInstrumentor
|
||||
from opentelemetry.instrumentation.dbapi import trace_integration
|
||||
import MySQLdb
|
||||
|
||||
trace.set_tracer_provider(
|
||||
TracerProvider(
|
||||
resource=Resource.create({
|
||||
"service.platform": settings.SERVICE_PLATFORM,
|
||||
"service.name": settings.TRACE_SERVICE_NAME,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
# TRACE_CA_CERT 설정에 따른 gRPC credentials 구성
|
||||
credentials = None
|
||||
ca_cert_path = os.getenv('TRACE_CA_CERT', '').strip()
|
||||
if ca_cert_path and os.path.exists(ca_cert_path):
|
||||
with open(ca_cert_path, 'rb') as f:
|
||||
ca_cert = f.read()
|
||||
credentials = grpc.ssl_channel_credentials(root_certificates=ca_cert)
|
||||
insecure = False
|
||||
else:
|
||||
insecure = True
|
||||
|
||||
otlp_exporter = OTLPSpanExporter(
|
||||
endpoint=settings.TRACE_ENDPOINT,
|
||||
insecure=insecure,
|
||||
credentials=credentials,
|
||||
headers={
|
||||
"x-scope-orgid": settings.SERVICE_PLATFORM,
|
||||
"x-service": settings.TRACE_SERVICE_NAME
|
||||
}
|
||||
)
|
||||
|
||||
trace.get_tracer_provider().add_span_processor(
|
||||
BatchSpanProcessor(otlp_exporter)
|
||||
)
|
||||
|
||||
# Django 요청/응답 추적
|
||||
DjangoInstrumentor().instrument()
|
||||
|
||||
# HTTP 클라이언트 요청 추적 (requests 라이브러리)
|
||||
RequestsInstrumentor().instrument()
|
||||
|
||||
# 로그와 Trace 연동 (trace_id, span_id를 로그에 자동 추가)
|
||||
LoggingInstrumentor().instrument(set_logging_format=True)
|
||||
|
||||
# MySQL DB 쿼리 추적
|
||||
trace_integration(
|
||||
MySQLdb,
|
||||
"connect",
|
||||
"mysql",
|
||||
capture_parameters=True,
|
||||
)
|
||||
|
||||
|
||||
application = get_wsgi_application()
|
||||
|
||||
@ -35,3 +35,23 @@ sqlparse==0.5.3
|
||||
typing_extensions==4.13.2
|
||||
uritemplate==4.1.1
|
||||
urllib3==2.4.0
|
||||
# OpenTelemetry Trace
|
||||
googleapis-common-protos==1.70.0
|
||||
grpcio==1.72.1
|
||||
opentelemetry-api==1.34.0
|
||||
opentelemetry-exporter-otlp==1.34.0
|
||||
opentelemetry-exporter-otlp-proto-common==1.34.0
|
||||
opentelemetry-exporter-otlp-proto-grpc==1.34.0
|
||||
opentelemetry-exporter-otlp-proto-http==1.34.0
|
||||
opentelemetry-instrumentation==0.55b0
|
||||
opentelemetry-instrumentation-dbapi==0.55b0
|
||||
opentelemetry-instrumentation-django==0.55b0
|
||||
opentelemetry-instrumentation-logging==0.55b0
|
||||
opentelemetry-instrumentation-requests==0.55b0
|
||||
opentelemetry-instrumentation-wsgi==0.55b0
|
||||
opentelemetry-proto==1.34.0
|
||||
opentelemetry-sdk==1.34.0
|
||||
opentelemetry-semantic-conventions==0.55b0
|
||||
opentelemetry-util-http==0.55b0
|
||||
protobuf==5.29.5
|
||||
wrapt==1.17.2
|
||||
|
||||
Reference in New Issue
Block a user