본문 바로가기

명사 美 비격식 (무리 중에서) 아주 뛰어난[눈에 띄는] 사람[것]

이론/SCP Training & Certification

SK 네트웍스 AI 캠프 - 4_AI 활용 애플리케이션 개발 - Day60_pdf 벼락치기

11일, 12일에 딜레이됬던 pdf 자료를 공유해주셨다. 

 

Django

MVT, model, view, template 구조로 구성된다. 

project는 전체 웹사이트, app은 웹사이트안의 개별 기능이다 .각 app은 독립적인 기능을 담당해 memeber, board, product 등을 수행할 수 있다. 

 

django template language DTL

django에서 python으로 처리한 데이터를 html 화면에 동적으로 출력하기 위해 사용하는 템플릿 문

변수 {{}} {{ form.as_p }}  {{ message }}  조건문, 반복문 {%%} <a href="{% url 'home' %}">홈</a> , {% empty %} {% block content %} {% endblock %}<a href="{% url 'login' %}"> {% if user.is_authenticated %} {% include "includes/header.html" %} {% extends "base.html" %}  {% csrf_token %} 필터 혹은 기본값 지 | {{ content|safe }}  {{ name|upper }} {{ board.created_at|date:"Y-m-d" }} {{ board.created_at|date:"Y-m-d H:i" }} {{ board.content|truncatechars:20 }} {{ name|lower }} {{ name|default:"이름 없음" }} {{ boards|length }} {{ name|default:"guest"|upper }} , 주석 {##}, {# 사용자 이름 출력 #} <!-- HTML 주석 -->

* {% csrf_token %} CSRF Cross Site Request Forgery 공격을 방지하기위한 보안 토

* {{ content|safe }} 신뢰할수있는 html만 그대로 출력해야할 경우 safe 필터를 사용한다 .

 

css, js, 이미지등을 template에서 사용할떄 static을 사용한다,

{% load static %}

<link
rel="stylesheet"
href="{% static 'css/style.css' %}">

 

django-admin startproject config

명령어로 django 프로젝트를 생성하면 manage.py, config/_init_.py, settings.py, urls.py, asgi.py, wsgi.py, views.py, tests.py가 생성된다. 

manage.py django 프로젝트를 관리하는 명령어 실행파일

python manage.py runserver, migrate, createsuperuser startapp board

 

config/_init_.py

시작 기본 파일

 

config/ settings.py 프로젝트 전체 설정 파일

설치된 앱, 데이터베이스, 언어, 시간대, 정적파일, 미디어 파일, 보안설정, 템플릿

django는 기본적으로 sqlite를 사용한다. 

DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}

config/ urls.py 웹주소 관리

django에서는 사용자가 입력한 주소에 따라 기능 실행을 결정한다. 이를 url routing이라고 한다. 

 

config/ asgi.py ASGI 기반 서버 설정 파일

ASGI 서버에서 실행할때 사용 비동기 처리를 지원, Websocket, 비동기 요청 등 실시간/비동기 기능을 사용할 때 유리 .대표적인 ASGI 서버 Uvicorn, Daphe, Hypercom

 

config/ wsgi.py WSGI 기반의 서버 설정 파일

WSGI 서버에서 실행할때 사용 전통적인 동기 방식의 웹 요청 처리, django의 일반적인 웹 서비스 배포에서 오랫동안 사용됨

대표적인 WSGI 서버 Gunicorn, uWSGI

 

views.py,

사용자의 요청을 실제로 처리하는 부분, 실제 프로젝트에서 대부분 html template을 사용한다. 

view는 함수기반 뷰 FBV, 클래스기반 뷰 CBV로 나뉜다 . 클래스기반 view는 CBV는 공통 코드를 줄일 수 있어 실무에서 많이 사용한다 .

def board_list(request):
boards = Board.objects.all()
return render(
request,
"board/list.html",
{
"boards": boards
}
)



from django.views.generic import ListView
class BoardListView(ListView):

model = Board

 

tests.py

테스트용 코드

 

model.py

django에서는 model를 만들었다고 데이터베이스 테이블이 바로 생성되는것이 아니고 

python manage,py makemigrations 과정을 수행해야한다 .

ORM, object relational mapping, sql을 직접 자기성하지않고 python 코드로 데이터베이스를 사용할 수 있는 기능.

from django import form, forms.form혹은 forns.ModelForm 이 있고 Model과 연결된 경우 Modelform을 많이 사용한다 .

 

model을 관리자 페이지에서 관리하려면 등록과정을 거친다 .

from django.contrib import admin
from .models import Board
admin.site.register(Board)

 

member.py

로그인기능을 사용하려면 user 모델을 사용한다. 비밀번호는 일반 문자열로 저자이되지않고 암호화되어 저장된다 .

from django.contrib.auth.models import User

from django.contrib.auth import logout

User.objects.create_user(
username="hong",
password="1234"
)

logout(request)

 

로그인 사용자만 접근하도록 만들 수 있다. 로그인하지않은 사용자가 해당 페이지에 접속하면 로그인 페이지로 이동한다. 

dajgno에서는 서버에서 상태를 유지하기 위한 sesstion 기능을 제공한다. request.session["username"] = "hong"

다만 Django 기본 로그인 시스템을 사용하면 대부분의 경우 세션을 직접 처리하지 않아도된다 . session, authentication, csrf, security, message는 middleware를 통해 처리된다 .

from django.contrib.auth.decorators import login_required
@login_required
def board_create(request):

 

template/

사용자가 브라우저에서 보는 html 화면

 

 

 

 

 

 

 

 

 

 

 

Django 모델

애플리케이션에서 다루는 데이터 구조, 관계, 제약조건, 일부 행위를 python 클래스로 표현한것. 

각 모델은 django.db.models.model을 상속하고 클래스 속성으로 선언한 field는 데이터베이스의 열이 된다. 

기본 키를 직접 선언하징낳으면 django가 자동으로 기본 키를 추가한다 .

속성의 의미에 맞는 자료형을 정하고 다대일일경우 ForeignKey, 일대일일경우 ONeToOneField, 다대다일경우 ManyToMayField 등 설정을 한다 .

from django.db import models
class Student(models.Model):
name = models.CharField(max_length=100)
student_number = models.CharField(max_length=20, unique=True)
admitted_at = models.DateField()



CREATE TABLE student (
id BIGINT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
student_number VARCHAR(20) NOT NULL UNIQUE,
admitted_at DATE NOT NULL
);

 

django 모델 클래스는 일반 python 클래스처럼 보이지마 내부적으로 meta 클래스인 modelbase의 처리를 받는다. 

클래스가 정의되면 - 객체가 클래스 속성으로 전달되어 - modelbase가 필드를 수집해 - 필드이름, 모델참조, 열 이르 ㅁ등의 정보가 설정된다. - 필드 정보가 meta에 등록되고 - 기본 manager인 objects가 연결되며 - 관계접근을 위한 descriptor가 설치되어 - 앱 레지스트리에 모델이 등록된다 .

 

_meta는 관리자페이지, modelform, 직렬화, 마이그레이션 감지등 여러 기능에서 사용된다 .

Student._meta.db_table
Student._meta.pk
Student._meta.get_field("name")
Student._meta.get_fields()

makemigrations 는 현재 모델 상태와 기존 마이그레이션이 표현하는 과거 상태를 비교하고,
차이를 마이그레이션 파일로 기록한다 .

migrate 는 마이그레이션 의존성 그래프를 계산하고, 적용되지 않은 작업을 데이터베이스별
SQL 로 변환해 실행한다. 

python manage.py makemigrations
python manage.py migrate

 

objects 기본 manager, filter 조건 추가, order_by 정렬 조건 추가, 조건값은 sql 문자열에 직접 연결하지않고 파라미터롤 바인딩되며 orm 조회는 sql삽입 공격을 막는데 도움이 된다. 다만 raw(), raqSQL() , extra() 와 같이 직접 작성시 별도 주의가 필요하다 .

students = Student.objects.filter(
department__name="컴퓨터공학과"
).order_by("name")

# 사용자가 입력한 username
username = request.GET.get("username")
# Django ORM이 파라미터를 안전하게 처리
users = User.objects.filter(username=username)

 

raw()의 잘못딘 사용과 안전한 사용

username = request.GET.get("username")
# 위험: 사용자 입력을 SQL 문자열에 직접 삽입
users = User.objects.raw(
    f"SELECT * FROM auth_user WHERE username = '{username}'"
)

username = request.GET.get("username")
users = User.objects.raw(
    "SELECT * FROM auth_user WHERE username = %s",
    [username]
)

 

RawSql()의 잘못된 사용과 안전한 사용

keyword = request.GET.get("keyword") # 위험 query = RawSQL( f"name LIKE '%{keyword}%'", [] )

from django.db.models.expressions import RawSQL keyword = request.GET.get("keyword") query = RawSQL( "name LIKE %s", [f"%{keyword}%"] ) users = User.objects.annotate( matched= query )

 

extra()는 특히 오래된 api 현재 django에서는 사용을 피하는 것이 권장된다. 

keyword = request.GET.get("keyword")
User.objects.extra(
    where=[f"username = '{keyword}'"]
)

 

외래키와 일대일 관계에서는 select_related() SQL JOIN 을 이용해 한 질의로 가져옴

students = Student.objects.select_related("department")

 

다대다 또는 역방향일때는 perfetch_related() 별도 질의를 실행한 뒤 Python 에서 연결

departments = Department.objects.prefetch_related("students")

 

저장, 새객체인지 기존 객체인지와 기본 키 상태등에 따라 insert또는 update가 실행된다. save()는 자동으로 full_clean()을 호출하지않는다. 

* 모델 객체의 데이터를 저장하기 전에 전체 유효성 검사를 수행하는 메서드

student.full_clean()

student.save()

 

필드 종류

AutoField 자동 증가하는 32 비트 정수형 기본 키

BigAutoField 자동 증가하는 64 비트 정수형 기본 키

SmallAutoField  작은 범위의 자동 증가 정수입니다. 생성 가능한 행 수가 명확히 작을 때

CompositePrimaryKey Django 5.2 부터 지원된 복합 기본 키, 레거시 데이터베이스나 관계 자체가 자연스럽게 복합 식별자를 갖는 경우 유용하나 외부 라이브러리, 관리자 기능, 관계 처리 지원 범위를 확인해야해 일반 애플리케이션에서는 단일 대리 키와 uniqueconstraint가 더 단순화될 수 있다. 

class Release(models.Model):
pk = models.CompositePrimaryKey("version", "name")
version = models.IntegerField()
name = models.CharField(max_length=20)

CharField 길이가 제한된 문자열

TextField 길이가 긴 문자열

SlugField URL 의 경로 조각에 적합한 문자열

EmailField 이메일 형태를 검증하는 CharField

URLField URL 형식을 검증

GenericIPAddressField IPv4 또는 IPv6 주소를 저장하고 검증 192.168.0.10,  127.0.0.1, 8.8.8.8, 2001:db8::1...

IntegerField , SmallIntegerField, BigIntegerField 일반적인 정수

PositiveIntegerField, PositiveSmallIntegerField, PositiveBigIntegerField음수가 허용되지 않는 정수

FloatField 부동소수점 수

DecimalField 정확한 고정소수점 수

price = models.DecimalField(
max_digits=12,
decimal_places=2,
)

BooleanField 참과 거짓을 저장

DateField 날짜만 저장

TimeField 시간만 저장

DateTimeField 날짜와 시간을 함께 저장

auto_now_add 와 auto_now 처음 생성될때 설정하거나 save될때마다 자동 갱신.시간

created_at = models.DateTimeField(auto_now_add=True) 최초 생성 시간
updated_at = models.DateTimeField(auto_now=True) 수정할 때마다 갱신

DurationField 시간 간격

FileField 파일 자체보다 일반적으로 저장소의 파일 경로 또는 식별자

ImageField 이미지 크기 검증 등의 기능이 추가된 FileField

JSONField JSON 객체를 저장

metadata = models.JSONField(default=dict, blank=True)

BinaryField bytes 와 같은 원시 이진 데이터를 저장

UUIDField UUID 를 저장, universally unique identifier 값을 저장하는 django모델 필 드, 550e8400-e29b-41d4-a716-446655440000 굉장히 긴 문자열 형태고 중복될 가능성이 매우 낮은 고유 식별자.

GeneratedField 데이터베이스가 다른 열을 이용해 계산하는 생성 열, DB가 다른 컬럼의 값을 이용해서 자동으로 계산해주는 컬럼 , price = 10000 , quantity = 3라면 총금액은 30000이 된다. 이것을 직접 입려갛지않고 db가 자동으로 계산하게 만드는 것. total 표현에 좋다. 

from django.db.models import F
total = models.GeneratedField(
    expression=F("price") * F("quantity"),
    output_field=models.DecimalField(
        max_digits=14,
        decimal_places=2,
    ),
    db_persist=True,
)

ForeignKey 관계필드 다대일, OneToOneField 일대일, ManyToManyField 다대다

primary_key 기본키는 자도으로 null false, unique true가 된다 .

code = models.CharField(
max_length=20,
primary_key=True,
)

 

 

null=True 데이테베이스가 sql null을 허용한다는 의미

blank 폼과 모델 유효성 검사에서 빈 입력을 허용한다는 뜻

default 기본

unique 제약조건, 고유

db_default 데이터베이스 수준 기본

nickname = models.CharField(max_length=50, null=True)

nickname = models.CharField(max_length=50, blank=True)

status = models.CharField(max_length=20, default="PENDING")

from django.db.models.functions import Now
created_at = models.DateTimeField(db_default=Now())

student_number = models.CharField(
max_length=20,
unique=True,

 

 

choices 고정된 선택지를 제한

class Student(models.Model):
class Status(models.TextChoices):
ACTIVE = "ACTIVE", "재학"
LEAVE = "LEAVE", "휴학"
GRADUATED = "GRADUATED", "졸업"

status = models.CharField(
max_length=10,
choices=Status,
default=Status.ACTIVE,
)
student.get_status_display()

 

 

validators 유효성 검사 계층에서 작동 save전에 검증을 호출하지않으면 우회도리 수 있음으로 핵심 무결성 규칙은 db 제약조건도 함께 사용한다.  값이 조건에 맞는지 검사하는 규칙, save전에 full_clean()을 먼저 호출하지 않았다면 validator 검사가 자동으로 실행되지 않을 수 있으니 검증 먼저 수행 필수.

from django.core.validators import MaxValueValidator, MinValueValidator
class Student(models.Model):
    age = models.IntegerField(
        validators=[
            MinValueValidator(0),
            MaxValueValidator(150),
        ]
    )

 

db_index 단일 필드 인덱스, django 6.0문서는 기능이 더 풀부한 meta.indexs를 우선 고려핟록 안내한다. db_index=True는 해당 필드 하나에 인덱스를 만드는 단순한 방법이지만 meta.indexes는 여러 필드를 조합하거나 인덱스이름등을 지정하는등 더 다양한 설정이 가능하다. 

class Student(models.Model):
    email = models.EmailField(db_index=True)
    name = models.CharField(max_length=20)



class Order(models.Model):
    user_id = models.IntegerField()
    created_at = models.DateTimeField()

    class Meta:
        indexes = [
            models.Index(
                fields=["user_id", "created_at"]
            ),
        ]

 

 

외 기타 공통 인수

• verbose_name: 화면에 보여줄 이름

name = models.CharField(
    max_length=50,
    verbose_name="학생 이름"
)


• help_text: 입력 도움말

name = models.CharField( max_length=50, help_text="이름을 입력하세요." )


• db_column: 실제 데이터베이스 열 이름

student_name = models.CharField( max_length=50, db_column="student_nm" )


• db_comment: DB 열 설명

name = models.CharField( max_length=50, db_comment="학생의 이름" )


• editable=False: 관리자와 ModelForm 에서 편집하지 않음, Admin/Form에서 사용자가 수정하지 못하게하는것. 

created_at = models.DateTimeField( auto_now_add=True, editable=False )


• error_messages: 기본 검증 오류 메시지 변경

name = models.CharField( max_length=10, error_messages={ "blank": "이름을 입력해주세요." } )


• serialize: Django 직렬화 대상 여부 false시 직렬화 대상에서 제외. 객체를 데이터 형태로 변환할때 이 필드는 포함하지않는것. 일반적인 모델 개발에서는 자주 사용하는 옵션은 아니다. 

some_field = models.CharField(
    max_length=100,
    serialize=False
)

• db_tablespace: 인덱스 테이블스페이스, 인덱스를 어느 tablespace에 저장할지 지정하는 옵션, 자주쓰는 옵션은 아니다. 고급 옵션. 

name = models.CharField(
    max_length=50,
    db_index=True,
    db_tablespace="index_space"
)

 

 

 

on_delete 참조대상이 삭제될때 동작을 결정하는 필수인수 cascade 부모삭제시 자식 삭제, protect 객체가 존재하면 삭제를 금하고 protectederror 발생, restrict 제한삭제를 수행하고 restricederror 발생, set_null 외래키를 null로 변경, SET_DEFAULT 기본값 변경, set() 지정한 값 callable 반환값으로 변경, DO_NOTHING 아무것도 하지않는다.  

department = models.ForeignKey( # 자식도 삭제
    Department,
    on_delete=models.CASCADE
)
department = models.ForeignKey( # 부모도 삭제금
    Department,
    on_delete=models.PROTECT
)
department = models.ForeignKey( # protect와 비슷하지만 삭제 작업 전체 관계를 고려
    Department,
    on_delete=models.RESTRICT
)
department = models.ForeignKey( # 부모삭제, 자식이ㅡ fk를 null
    Department,
    null=True,
    on_delete=models.SET_NULL
)
department = models.ForeignKey( # 기본값 설
    Department,
    default=1,
    on_delete=models.SET_DEFAULT
)
def get_default_department(): # 지정한 값으로 변
    return Department.objects.get(name="미지정")
department = models.ForeignKey(
    Department,
    on_delete=models.SET(get_default_department)
)
department = models.ForeignKey( # 자식에게 아무것도 안함.
    Department,
    on_delete=models.DO_NOTHING
)

 

related_name 역방향 관계이름을 정한다. 지어하지않으면 기본적으로 생성된다. department.students.all() 보고싶을때 사용한다.  부모에서 자식들을 조회할 일이 있을 때 

department = models.ForeignKey(
Department,
on_delete=models.PROTECT,
related_name="students",
)
department.students.all()

 

related_query_name 역방향 필터 경로의 이름을 별도로 정한다.  related_name과 비슷해 보이지만 용도가 다르다. 홍길동이라는 학생이 속한 department를 찾을 수 있다. 

related_name = 역방향 접근 이름
related_query_name = 역방향 검색 이름

Department.objects.filter(
    student__name="홍길동"
)
department = models.ForeignKey(
Department,
on_delete=models.PROTECT,
related_name="students",
related_query_name="student",
)
Department.objects.filter(student__name="홍길동")

 

 

to_field 대상 모델으 기본키가 아닌 고유 필드를 참조한다 .기본적으로 부모의 id로 지정된다면 to_filed code를 사용하면 department의 code를 참조하게된다 .pk가 아닌 다른 고유한 값을 기준으로 관계를 맺고 싶을 때 사용한다. 

department = models.ForeignKey(
Department,
to_field="code",
on_delete=models.PROTECT,
)
related_name 역방향 객체 접근 이름 department.students.all()
related_query_name 역방향 QuerySet 검색 이름 Department.objects.filter(student__name=...)
to_field FK가 참조할 필드 변경 Department.code 참조

 

 

 

db_constraint foreignkey 관계를 db에서도 강제로 지킬지 여부. false시 검사하지않는다. 삭제할때 관계제약을 느슨하게 만드는 것. 

department = models.ForeignKey(
Department,
on_delete=models.DO_NOTHING,
db_constraint=False,
)

 

 

• fields: 고유성 조합에 포함할 필드 필드를 묶어 고유하게 만든다 .

class Enrollment(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["student", "course"],
                name="unique_student_course",
            )
        ]


• condition: 일부 행에만 적용할 조건, 활성화된 회원만 이메일이 중복되면 안될경우등의 특정 조건

class User(models.Model):
    email = models.EmailField()
    is_active = models.BooleanField(default=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["email"],
                condition=models.Q(is_active=True),
                name="unique_active_email",
            )
        ]


• include: 검색에 사용될 추가 컬럼, Covering Index와 관련된 고급 기능 인덱스 검색에는 사용하지 않지만, 결과 조회에 필요한 컬럼을 인덱스에 추가한다. 주로 PostgreSQL에서 사용하는 고급 기능이다. 

class Product(models.Model):
    name = models.CharField(max_length=100)
    category = models.CharField(max_length=50)
    price = models.IntegerField()

    class Meta:
        indexes = [
            models.Index(
                fields=["category"],
                include=["name", "price"],
                name="product_category_idx",
            )
        ]


• opclasses: PostgreSQL 등의 연산자 클래스, PostgreSQL에서는 같은 컬럼이라도 어떤 방식으로 검색할지를 지정할 수있다. 

어떤 문자열을 특정방식으로 검색하기 위해 연산자 클래슬르 지정할 수 있다. 

class Product(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        indexes = [
            models.Index(
                fields=["name"],
                opclasses=["varchar_pattern_ops"],
                name="product_name_pattern_idx",
            )
        ]


• nulls_distinct: 여러 NULL 을 서로 다른 값으로 볼지 설정, phone이 null인 사용자가 여러명 있을때 false로 설정시 같은것으로 취급해 중복으로 판단할 수 있다. true시 다른값으로 취급한다. 

class User(models.Model):
    phone = models.CharField(
        max_length=20,
        null=True
    )

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["phone"],
                name="unique_phone",
                nulls_distinct=False,
            )
        ]

 


• deferrable: 트랜잭션 종료 시점까지 검사 연기, 트랜젝션과 관련된 고급기능, db는 데이터 변경시 바로 제약조건을 검사하는 데 이를 사용시 당장은 위반해도 트랜젝션이 끝날때 검사하게할 수 있다. 복잡한 작업중 중간상태가 일시적으로 제약조건을위반할경우 유용하다 .

models.UniqueConstraint(
    fields=["position"],
    name="unique_position",
    deferrable=models.Deferrable.DEFERRED,
)


• violation_error_code: 모델 검증 오류 코드, 제약조건을 위반했을때 사용할 오류 코드 지정

models.UniqueConstraint(
    fields=["email"],
    name="unique_email",
    violation_error_code="duplicate_email",
)

• violation_error_message: 모델 검증 오류 메시지, 오휴 코드를 지정시 메시지도 직접 지정

models.UniqueConstraint(
    fields=["email"],
    name="unique_email",
    violation_error_message="이미 사용 중인 이메일입니다.",
)

 

CheckConstraint db 데이터를 저장할때 조건을 반드시 만족해야한다는 제약조건. 데이터 값 자체가 올바른 범위인지 db가 검사한다.

from django.db import models
class Student(models.Model):
    name = models.CharField(max_length=50)
    age = models.IntegerField()

    class Meta:
        constraints = [
            models.CheckConstraint(
                condition=models.Q(age__gte=0),
                name="age_gte_0",
            )
        ]

 

unique_for_date, unique_for_month, unique_for_year 특정 날짜 범위안에서 값이 중복되지지않게 하는 옵션 같은 날짜에는 중복 금지 등. slug = models.SlugField(unique=True)는 무조건 중복 금지지만 날짜 조건이 붙는것. 

class Post(models.Model):
    title = models.CharField(max_length=100)
    published_at = models.DateTimeField()

    slug = models.SlugField(
        unique_for_date="published_at"
    )

class Post(models.Model):
    title = models.CharField(max_length=100)
    published_at = models.DateTimeField()

    slug = models.SlugField(
        unique_for_month="published_at"
    )

class Post(models.Model):
    title = models.CharField(max_length=100)
    published_at = models.DateTimeField()

    slug = models.SlugField(
        unique_for_year="published_at"
    )

django 모델 설계는 단순히 python 클래스에 필드를 나열하는 작업이 아니라 의미적, 자료형, 무결성, 실행 설계를 포함한ㄷ.ㅏ 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

운영 환경에서는 python manage.py runserver 를 사용하지 않는다. Django 공식 배포
체크리스트도 운영용 WSGI 또는 ASGI 서버 사용을 요구한다.

 

py -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip

 

#mysqlclient 는 Django 에서 일반적으로 권장되는 MySQL 네이티브 드라이버

pip install "Django~=5.2.0" mysqlclient gunicorn

 

#리눅스의 환경에서 mysqlclient를 설치할 경우 아래 추가

sudo apt-get update
sudo apt-get install \
python3-dev \
default-libmysqlclient-dev \
build-essential \
pkg-config

 

#설

pip install mysqlclient

 

#프로젝트 설정

django-admin startproject config .
python manage.py startapp users
python manage.py check
python manage.py runserver

 

# 현재 환경에 설치된 패키지와 버전을 > 파일로 저장해 requirements/prod.txt가 만들어지고 저장된다. 다른 컴퓨터나 서버에서 똑같은 python  환경을 다시 설치하기 위해 사용.

pip freeze > requirements/prod.txt

 

 

env 환경변수 관리

DJANGO_SETTINGS_MODULE=config.settings.local
DJANGO_SECRET_KEY=local-only-secret
DJANGO_DEBUG=true

DB_NAME=myservice
DB_USER=myservice_app
DB_PASSWORD=local-password
DB_HOST=127.0.0.1
DB_PORT=3306

 

 

gitignore 추가

.gitignore:
.venv/
.env
__pycache__/
*.py[cod]
staticfiles/
media/
.pytest_cache/
.coverage

 

 

gitignore에 추가한 운영 비밀값은 다음중 하나에 저장한다 .

• AWS Secrets Manager
• GCP Secret Manager
• Azure Key Vault
• Kubernetes Secret
• systemd 의 권한 제한된 환경 파일
• CI/CD 플랫폼의 encrypted secret

 

 

config/settings/base.py 공통 설정

import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parents[2]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]

INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",

"django.contrib.messages",
"django.contrib.staticfiles",
"apps.users",
"apps.orders",
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = "config.urls"
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
LANGUAGE_CODE = "ko-kr"
TIME_ZONE = "Asia/Seoul"

USE_I18N = True
USE_TZ = True
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"



config/settings/local.py 개발환경 mysql 설정

from .base import *

DEBUG = True

ALLOWED_HOSTS = [
"localhost",
"127.0.0.1",
]

DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ.get("DB_HOST", "127.0.0.1"),
"PORT": os.environ.get("DB_PORT", "3306"),
"OPTIONS": {
"charset": "utf8mb4",
"init_command": (
"SET sql_mode="
"'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'"
),
},
}
}

 

 

실행

python manage.py runserver \
--settings=config.settings.local

# or 환경변수 지정
#export DJANGO_SETTINGS_MODULE=config.settings.local
#python manage.py runserver

#powershell
#$env:DJANGO_SETTINGS_MODULE = "config.settings.local"
#python manage.py runserver

 

 

 

 

운영환경 mysql 설정

config/settings/production.py

from .base import *
DEBUG = False

ALLOWED_HOSTS = [
"api.example.com",
]

CSRF_TRUSTED_ORIGINS = [
"https://api.example.com",
]

DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ["DB_PASSWORD"],
"HOST": os.environ["DB_HOST"],

"PORT": os.environ.get("DB_PORT", "3306"),
"CONN_MAX_AGE": 60,
"CONN_HEALTH_CHECKS": True,
"OPTIONS": {
"charset": "utf8mb4",
"isolation_level": "read committed",
"init_command": (
"SET sql_mode="
"'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'"
),
# 관리형 DB 가 TLS 를 요구하는 경우
# "ssl": {
# "ca": "/etc/ssl/certs/mysql-ca.pem",
# },
},
}
}

SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
# 신뢰할 수 있는 프록시에서 HTTPS 가 종료되는 경우에만 설정
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

 

 

 

local.py = 내 컴퓨터에서 편하게 개발하기 위한 설정
production.py = 실제 서비스에서 보안·성능·안정성을 확보하기 위한 설정

debug가 개발때는 true지만 운영떄는 false 사용자에게 노출될 위험이 있기 때문이다 .

allow_hosts가 운영시에는 컴퓨터에서 접속하는 주소만 허용하지만 운영시에는 실제 서비스 도메인만 허용한다 .

db설정은 비슷하다. 

host는 개발시 "HOST": os.environ.get("DB_HOST", "127.0.0.1") 로 표현되지만 운영에서는 "HOST": os.environ["DB_HOST"] 반드시 db host로 지정한것. 

CONN_MAX_AGE = 60 db 연결을 얼마나 오래 재사용할지. 운영서버에서는 요청이 많기 때문에 설정한다. 개발환경에서는 굳이 설정하지않아도 되는 경우가 많다. 

CONN_HEALTH_CHECKS = True 기존 db 연결을 재사용하기전에 연결이 정상인지 확인. 역시 운영환경에서 안정성을 위해 의미가 있는 설정

utf8mb4 mysql에서 unicode 문자를 제대로 저장하기 위한 문자셋  이모지 😀

STRICT_TRANS_TABLES mysql의 sql 처리모드를 엄격하게 설정하는 것.  잘못된 데이터를 넣으려고 할때 대충 변환해서 저장하는 것보다 오류를 발생해 데이터가 조용히 저장되는 것을 방지한다. 

"init_command": (
    "SET sql_mode="
    "'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION'"
)

isolation_level = "read committed" 트랜잭션 격리 수준, 다른 트랜젝션이 commmit하지않은 데이터를 읽지않도록 하는 수준으로 운영환경에서 중요하다 .개발환경에서는 db설정을 그대로 사용하는 경우가 많다. 

SSL mysql과 django통신을 tls 암호화할때 사용하는 설정 개발용 로컬 mysql에서는 보통 필요가 없다 .

SECURE_SSL_REDIRECT https로 강제.

SESSION_COOKIE_SECURE 세션쿠키를 https 연결에서만 전송하도록 한다 .

CSRF_COOKIE_SECURE csrf 쿠키도 https에서만 전송하도록 한다 .

HSTS 이 사이트가 앞으로 https로만 접속되돌 갛ㄴ다. 보안 정책 3153600은 브라우저가 365일으로 일정기간동안 해당 사이트에 http로 접근하지않고 https를 사용하도록 강제한다 .

SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

SECURE_CONTENT_TYPE_NOSNIFF 브라우저가 서버가 지정한 content type을 임의로 추측해 처리하는 것을 막는다. 지정한 파일형식을 믿고 함부로 추측하지 않는것. 

X_FRAME_OPTIONS = "DENY" 다른 사이트가 내 django 페이지를 iframe으로 띄우는것을 막는것 . clickjacking 공격을 줄이는 데 도움이 된다. 

SECURE_PROXY_SSL_HEADER 실제 운영에서 https 를 중간 프록시가 처리하는 구조가 있을 수 있다. django입장에서는 프록시 - http - django 처러 ㅁ보일수 있어 프록시임을 알게하도록 하는것. 신뢰할 수 있는 프록시에서 해당 헤더를 제대로 설정하는 경우에만 사용해야한다. 

 

 

 

docker를 사용하면 mysql을 컨테이너 안에서 실행 할 수 있다 .

# compose.yaml

services:
  db:                         # Docker Compose에서 사용할 서비스 이름
    image: mysql:8.4          # MySQL 8.4 Docker 이미지 사용

    command:
      # MySQL 서버의 기본 문자셋
      # 한글, 이모지 등을 제대로 저장하기 위해 utf8mb4 사용
      - --character-set-server=utf8mb4

      # 문자열 비교/정렬 규칙
      - --collation-server=utf8mb4_0900_ai_ci

      # MySQL의 기본 스토리지 엔진
      - --default-storage-engine=InnoDB

      # 트랜잭션 격리 수준
      # 다른 트랜잭션에서 아직 COMMIT하지 않은 데이터는 읽지 않음
      - --transaction-isolation=READ-COMMITTED

    environment:
      # 컨테이너가 처음 생성될 때 자동으로 만들 데이터베이스
      MYSQL_DATABASE: myservice

      # Django가 사용할 MySQL 사용자
      MYSQL_USER: myservice_app

      # 위 사용자의 비밀번호
      # ⚠️ 실제 운영환경에서는 코드에 직접 작성하지 않는 것이 좋음
      MYSQL_PASSWORD: local-password

      # MySQL root 계정의 비밀번호
      # ⚠️ 개발용 예시이며 운영에서는 Secret 등으로 관리
      MYSQL_ROOT_PASSWORD: local-root-password

      # 컨테이너의 시간대
      TZ: Asia/Seoul

    ports:
      # "호스트 PC 포트 : Docker 컨테이너 포트"
      #
      # 내 PC의 3306으로 들어오면
      # Docker의 MySQL 3306으로 연결
      #
      # 따라서 호스트 PC에서 Django 실행 시:
      # DB_HOST=127.0.0.1
      # DB_PORT=3306
      - "3306:3306"

    volumes:
      # MySQL 데이터를 Docker 볼륨에 저장
      #
      # 컨테이너를 삭제/재생성해도
      # mysql_data에 저장된 DB 데이터는 유지
      - mysql_data:/var/lib/mysql

    healthcheck:
      # MySQL이 실제로 요청을 받을 준비가 되었는지 확인
      # 단순히 컨테이너가 실행 중인지 확인하는 것보다 정확함
      test:
        - CMD
        - mysqladmin
        - ping
        - -h
        - localhost
        - -uroot
        - -plocal-root-password

      # 5초마다 health check
      interval: 5s

      # 한 번의 검사에 최대 3초 기다림
      timeout: 3s

      # 실패하면 최대 20번 재시도
      retries: 20


# Docker가 관리하는 영구 볼륨
# MySQL 데이터를 보존하기 위해 사용
volumes:
  mysql_data:
# compose.yaml

services:
  db:                         # Docker Compose에서 사용할 서비스 이름
    image: mysql:8.4          # MySQL 8.4 Docker 이미지 사용

    command:
      # MySQL 서버의 기본 문자셋
      # 한글, 이모지 등을 제대로 저장하기 위해 utf8mb4 사용
      - --character-set-server=utf8mb4

      # 문자열 비교/정렬 규칙
      - --collation-server=utf8mb4_0900_ai_ci

      # MySQL의 기본 스토리지 엔진
      - --default-storage-engine=InnoDB

      # 트랜잭션 격리 수준
      # 다른 트랜잭션에서 아직 COMMIT하지 않은 데이터는 읽지 않음
      - --transaction-isolation=READ-COMMITTED

    environment:
      # 컨테이너가 처음 생성될 때 자동으로 만들 데이터베이스
      MYSQL_DATABASE: myservice

      # Django가 사용할 MySQL 사용자
      MYSQL_USER: myservice_app

      # 위 사용자의 비밀번호
      # ⚠️ 실제 운영환경에서는 코드에 직접 작성하지 않는 것이 좋음
      MYSQL_PASSWORD: local-password

      # MySQL root 계정의 비밀번호
      # ⚠️ 개발용 예시이며 운영에서는 Secret 등으로 관리
      MYSQL_ROOT_PASSWORD: local-root-password

      # 컨테이너의 시간대
      TZ: Asia/Seoul

    ports:
      # "호스트 PC 포트 : Docker 컨테이너 포트"
      #
      # 내 PC의 3306으로 들어오면
      # Docker의 MySQL 3306으로 연결
      #
      # 따라서 호스트 PC에서 Django 실행 시:
      # DB_HOST=127.0.0.1
      # DB_PORT=3306
      - "3306:3306"

    volumes:
      # MySQL 데이터를 Docker 볼륨에 저장
      #
      # 컨테이너를 삭제/재생성해도
      # mysql_data에 저장된 DB 데이터는 유지
      - mysql_data:/var/lib/mysql

    healthcheck:
      # MySQL이 실제로 요청을 받을 준비가 되었는지 확인
      # 단순히 컨테이너가 실행 중인지 확인하는 것보다 정확함
      test:
        - CMD
        - mysqladmin
        - ping
        - -h
        - localhost
        - -uroot
        - -plocal-root-password

      # 5초마다 health check
      interval: 5s

      # 한 번의 검사에 최대 3초 기다림
      timeout: 3s

      # 실패하면 최대 20번 재시도
      retries: 20


# Docker가 관리하는 영구 볼륨
# MySQL 데이터를 보존하기 위해 사용
volumes:
  mysql_data:

 

 

 

 

 

 

AWS = 서버를 빌려주는/제공하는 클라우드 플랫폼
 Gunicorn = Django 애플리케이션을 실행해주는 웹 서버 프로그램 , Python WSGI 애플리케이션 서버  , AWS가 없어도 Gunicorn 쓸 수 있다. 개발단계에서 python manage.py runserver 0.0.0.0:8000   시 외부에서도  내 ip http://123.456.78.90:8000로 접속받을 수 있도록 할 수 있다. 다만 방화벽, 공유기 포트포워딩이 되어있어야한다. 집 PC에서 인터넷에 공개하는 건 보안상 권장되지 않아. 실제 배포 연습이라면 AWS EC2 같은 서버에서 하는 게 일반적  이다. 

 

설치

pip install gunicorn

 

실행

gunicorn config.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4 \
--timeout 30 \
--access-logfile - \
--error-logfile -

 

systemd

linux의 서비스 관리자. gunicorn config.wsgi:application 시 터미널을 닫거나 서버를 재부팅시 문제가 생김으로 systemd에게 gunicorn을 linux서비스로 등록하고 관리해달라고 하는거 .

서버가 켜질때마다 자동으로 실행하고 죽으면 다시 켜주는것을 설정

# /etc/systemd/system/myservice.service

[Unit]

# 이 서비스의 설명
Description=MyService Django

# 네트워크가 준비된 후 실행
After=network.target


[Service]

# Gunicorn/Django를 이 Linux 사용자 권한으로 실행
User=myservice

# 프로세스의 그룹
Group=www-data

# Django 프로젝트가 위치한 디렉터리
WorkingDirectory=/srv/myservice/current

# 환경변수 파일
# DB_PASSWORD, SECRET_KEY 등의 값을 여기서 가져올 수 있음
EnvironmentFile=/etc/myservice/myservice.env

# Gunicorn 실행
#
# /srv/myservice/venv/bin/gunicorn
#     → 가상환경에 설치된 Gunicorn
#
# config.wsgi:application
#     → Django의 WSGI 애플리케이션
#
# --bind unix:/run/myservice/gunicorn.sock
#     → TCP 포트가 아니라 Unix Socket으로 통신
#
# --workers 4
#     → Gunicorn worker 4개
#
# --timeout 30
#     → 요청 처리 시간이 30초를 넘으면 timeout
#
# --access-logfile -
#     → 접속 로그를 표준 출력으로
#
# --error-logfile -
#     → 오류 로그를 표준 출력으로
ExecStart=/srv/myservice/venv/bin/gunicorn \
config.wsgi:application \
--bind unix:/run/myservice/gunicorn.sock \
--workers 4 \
--timeout 30 \
--access-logfile - \
--error-logfile -

# Gunicorn이 비정상적으로 종료되면 다시 시작
Restart=always

# 다시 시작하기 전에 3초 기다림
RestartSec=3

# 임시 파일을 별도로 격리
PrivateTmp=true


[Install]

# Linux가 일반적인 운영 상태로 부팅될 때
# 이 서비스를 시작하도록 설정
WantedBy=multi-user.target

 

 

적용

sudo systemctl daemon-reload # 설정 다시 읽도록 하기
sudo systemctl enable --now myservice # 서비스시작과 부팅시 자동시작
sudo systemctl status myservice # 실행상태확인
sudo journalctl -u myservice -f # 실시간 로그 확인

 

Nginx

django를 gunicorn으로 실행할 수 있지만 실제 인터넷 사용자에게 서비스를 제공하려면 보토 아유에 nginx를 둔다. nginx가 외부 요청을 받아서 적절한곳으로 보내주는 역할을 하는데 

nginx가 url을 보고 역할을 나누고 static을 django까지 보내지않고 직접 파일을 제공한다. 

앞선 systemd에서 funicorn을 실행할때 --bind unix:/run/myservice/gunicorn.sock했다는건 unix socket을 만들어서 요청을 받을 준비를 한것. nginx와 gunicorn의 통신.  같은 소켓을 바라보고 있는것이다 .

 

# Gunicorn과 연결할 백엔드 서버를 정의
upstream myservice_backend {

    # Gunicorn이 열어놓은 Unix Socket으로 연결
    # 즉 Nginx → Gunicorn 연결
    server unix:/run/myservice/gunicorn.sock;
}


# ============================
# HTTP (80번 포트)
# ============================
server {

    # HTTP 80번 포트에서 요청을 받음
    listen 80;

    # 이 서버가 담당할 도메인
    server_name api.example.com;

    # HTTP로 들어오면 HTTPS로 강제 이동
    #
    # 예:
    # http://api.example.com/users
    #
    # ↓
    #
    # https://api.example.com/users
    return 301 https://$host$request_uri;
}


# ============================
# HTTPS (443번 포트)
# ============================
server {
    
    # HTTPS 443번 포트에서 요청을 받음
    listen 443 ssl http2;

    # 담당 도메인
    server_name api.example.com;


    # SSL/TLS 인증서
    # Let's Encrypt에서 발급받은 인증서
    ssl_certificate
        /etc/letsencrypt/live/api.example.com/fullchain.pem;

    # SSL/TLS 개인키
    # ⚠️ 외부에 노출되면 안 되는 중요한 파일
    ssl_certificate_key
        /etc/letsencrypt/live/api.example.com/privkey.pem;


    # 한 번의 요청에서 업로드할 수 있는
    # 최대 파일 크기 = 10MB
    client_max_body_size 10m;


    # ============================
    # 정적 파일
    # ============================
    location /static/ {

        # Django의 static 파일이 실제로 있는 위치
        #
        # 예:
        # /static/css/style.css
        #
        # → /srv/myservice/current/staticfiles/css/style.css
        alias /srv/myservice/current/staticfiles/;

        # 브라우저가 30일 동안 캐시하도록 설정
        expires 30d;

        # 캐시된 파일을 공개적으로 재사용 가능하도록 설정
        # immutable → 파일 내용이 변경되지 않는 것으로 간주
        add_header Cache-Control "public, immutable";
    }


    # ============================
    # 미디어 파일
    # ============================
    location /media/ {

        # 사용자가 업로드한 파일의 실제 위치
        alias /srv/myservice/shared/media/;
    }


    # ============================
    # 그 외 모든 요청
    # ============================
    location / {

        # Nginx가 받은 요청을
        # 위에서 정의한 Gunicorn으로 전달
        proxy_pass http://myservice_backend;


        # 원래 요청의 Host 정보를 Gunicorn/Django에 전달
        proxy_set_header Host $host;

        # 실제 사용자의 IP 전달
        proxy_set_header X-Real-IP $remote_addr;

        # 프록시를 거쳐온 실제 사용자 IP 목록 전달
        proxy_set_header X-Forwarded-For
            $proxy_add_x_forwarded_for;

        # 사용자가 HTTP/HTTPS 중 어떤 방식으로 접속했는지 전달
        #
        # 여기서는 HTTPS이므로:
        # X-Forwarded-Proto: https
        proxy_set_header X-Forwarded-Proto $scheme;


        # Gunicorn에 연결할 때 최대 5초 기다림
        proxy_connect_timeout 5s;

        # Gunicorn이 응답할 때 최대 30초 기다림
        proxy_read_timeout 30s;
    }
}

 

 

설정 수정시 바로 재시작하지않고 먼저 nginx 설정 파일에 문법 오류가 있는지 검사한다 .

sudo nginx -t
sudo systemctl reload nginx

 

 

 

 

안전한 배포 파이프라인, 대규모 서비스라면 단순 restart보다 인스턴스를 한대씩 배포, readiness 통과후 트래픽을 투입, 이전 인스턴스의 연결 종료 대기, 실패시 새 버전 트래픽 제거, db 스키마는 이전 애플리케이션과도 호환유지하는 등의 방식을 사용한다. db 마이그레이션은 애플리케이션 롤백보다 되돌리기가 어려워 코드가 롤백되더라도 새 스키마에서 동작하는가를 먼저 설계해야한다. 

 

CI 검사

# ============================
# 안전한 배포 파이프라인
# ============================

# 1. Django 프로젝트 기본 설정 검사
# 설정 오류, 잘못된 설정값 등이 있는지 확인
python manage.py check


# 2. 모델 변경사항이 있는데
# 아직 migration 파일을 만들지 않은 것이 있는지 검사
#
# makemigrations:
#   모델 변경 → migration 파일 생성
#
# --check:
#   실제 파일을 생성하지 않고
#   "생성할 migration이 있는지" 검사
#
# --dry-run:
#   어떤 migration이 만들어질지 시뮬레이션만 함
python manage.py makemigrations --check --dry-run


# 3. Django 테스트 실행
# 테스트 코드가 모두 통과하는지 확인
python manage.py test


# 4. 정적 파일을 한 곳으로 모음
#
# CSS / JavaScript / 이미지 등의 static 파일을
# STATIC_ROOT에 모음
#
# --noinput:
#   실행 중 사용자에게 확인 질문을 하지 않음
python manage.py collectstatic --noinput


# 5. 아직 적용되지 않은 migration이 있는지 검사
#
# migration을 실제로 실행하지 않고
# 적용되지 않은 migration이 있는지 확인
python manage.py migrate --check


# 6. 운영환경 설정 검사
#
# --deploy:
#   운영환경에서 문제가 될 만한
#   보안/배포 관련 설정을 추가로 검사
#
# production 설정 파일을 사용해서 검사
python manage.py check --deploy \
--settings=config.settings.production

 

 

검사를 통과했다면 실제 서버에서, 운영배포

# ============================
# 운영 배포
# ============================


# 1. 데이터베이스에 migration 적용
#
# 모델 변경사항을 실제 운영 DB에 반영
#
# 예:
# 새로운 테이블 생성
# 새로운 컬럼 추가
# 인덱스 생성 등
#
# --noinput:
#   중간에 사용자 입력을 요구하지 않음
python manage.py migrate --noinput \
--settings=config.settings.production


# 2. Django static 파일 수집
#
# 운영환경의 STATIC_ROOT에
# CSS / JS / 이미지 등의 static 파일을 모음
#
# Nginx가 이 파일들을 직접 제공할 수 있도록 준비
python manage.py collectstatic --noinput \
--settings=config.settings.production


# 3. Gunicorn 재시작
#
# 새로운 Django 코드와 설정을 적용하기 위해
# Gunicorn을 다시 시작
#
# systemd가 myservice라는 Gunicorn 서비스를 관리하고 있음
sudo systemctl restart myservice

 

 

 

 

 

헬스체크

django 서버가 정상적으로 서비스를 제공할 수 있는가를 자동으로 확인한다. 

서버 상태 확인용 주소를 만드는것 url을 만들어시 aws, kubernetes, 로드벨런서 등이 주기적으로 호출되는 것.

브라우저에서 직접 확인 할 수도 있다. http://localhost:8000/health/live/

운영환경에서는 다른 시스템이 자동으로 호출한다. AWS Load Balancer나 Kubernetes 같은 시스템에 5초마다 /health/ready 확인 등의 설정을 할 수 있는 것. 

from django.db import connection
from django.http import JsonResponse


def liveness(request):
    return JsonResponse({"status": "ok"})


def readiness(request):
    try:
        with connection.cursor() as cursor:
            cursor.execute("SELECT 1")
            cursor.fetchone()

        return JsonResponse({"status": "ready"})

    except Exception:
        return JsonResponse(
            {"status": "not_ready"},
            status=503,
        )
from django.urls import path
from .views import liveness, readiness

urlpatterns = [
    path("health/live/", liveness),
    path("health/ready/", readiness),
]
from django.db import connection
from django.http import JsonResponse


# ============================
# Liveness
# ============================
# "Django 프로세스 자체가 살아있나?"
# 확인하는 용도
def liveness(request):

    # Django가 정상적으로 요청을 처리할 수 있다면
    # 그냥 OK 응답
    return JsonResponse({
        "status": "ok"
    })


# ============================
# Readiness
# ============================
# "이 서버가 실제 요청을 처리할 준비가 되었나?"
# 확인하는 용도
def readiness(request):

    try:
        # Django를 통해 DB 연결을 가져옴
        with connection.cursor() as cursor:

            # DB에 간단한 쿼리를 실행
            # 실제 데이터는 조회하지 않고
            # DB 연결이 정상인지 확인하는 목적
            cursor.execute("SELECT 1")

            # 쿼리 결과를 하나 가져옴
            cursor.fetchone()


        # DB 연결까지 정상이라면
        # 요청을 처리할 준비가 된 상태
        return JsonResponse({
            "status": "ready"
        })


    except Exception:

        # DB 연결 등에 문제가 있으면
        # 아직 요청을 받을 준비가 안 된 상태
        #
        # 503 = Service Unavailable
        return JsonResponse(
            {
                "status": "not_ready"
            },
            status=503,
        )

 

 

 

 

mysql 데이터를 파일로 백업하고 다시 복구하는 방법

# ============================
# MySQL 논리 백업
# ============================

mysqldump \
    # 백업할 MySQL 서버 주소
    --host=mysql.example.internal \

    # 백업에 사용할 MySQL 사용자
    # 일반적으로 백업 전용 계정을 만들어 사용
    --user=backup_user \

    # 비밀번호를 입력받음
    # 실제 비밀번호를 명령어에 직접 적지 않음
    --password \

    # InnoDB에서 트랜잭션 기반의 일관된 스냅샷을 사용
    #
    # 백업하는 동안 일반적인 테이블 잠금을 피하면서
    # 백업 시점의 일관된 데이터를 얻는 데 유용
    --single-transaction \

    # 저장 프로시저(Stored Procedure) 포함
    --routines \

    # MySQL 이벤트(Event) 포함
    --events \

    # 트리거(Trigger) 포함
    --triggers \

    # GTID 관련 정보를 백업 파일에 넣지 않도록 설정
    # 복구 환경의 GTID 구성에 따라 불필요한 충돌을 피하는 데 사용
    --set-gtid-purged=OFF \

    # 백업할 데이터베이스 이름
    myservice \

    # mysqldump 결과를 myservice.sql 파일로 저장
    > myservice.sql

 

--single-transaction 은 백업 시작 시점의 데이터를 기준으로 일관성 있게 백업해 달라는 요청이다 . 일반적으로 테이블에 잠금을 걸어 서비스 전체를 멈추는 방시기보다 운영서비스에 적합하지만 만능으 ㄴ아니다. ddl이 실행되면 백업중에 문제가 생길 수 있다.

 

 

 

코드를 통째로 외운다기보다 이해를하자. 

세부 설정값은 나중에 공식 문서나 예전에 만든 설정 파일을 찾아서 가져다 쓰는 게 정상


Django
  ↓
Gunicorn = Django 실행
  ↓
systemd = Gunicorn 관리
  ↓
Nginx = 외부 요청 전달
  ↓
MySQL = DB



코드
 ↓
CI 검사
 ↓
migrate
 ↓
collectstatic
 ↓
Gunicorn 재시작