Intro
개발환경 셋업 : Pycharm
django 개발 패턴
장고의 개발 패턴 : MVC? MVT?
- Model : sql 없이 database와 객체를 주고 받고~
- 객체 하나 -> Row 하나, 멤버 변수 -> Columns
- View : 계산, 유저와 서버 간의 소통( request, response )
- Template( Controller ) : frontend ( html, css, js ) 구성
Django Tutorial
첫 앱 시작, 그리고 기본적인 view 만들기
앱 추가하기
1. python manage.py startapp accountapp
2. 프로젝트 폴더의 settings.py 에 INSTALLED_APPS에 'accountapp' 등록
views.py
하나의 기능 -> 하나의 함수
request를 인자로 받고 HttpResponse('hello world') return
alt+enter로 자동 import 가능
urls.py
- 프로젝트 폴더의 urls.py -> path('account/', include('accountapp.urls')), 추가
- app 폴더에 urls.py 추가
- app_name = "accountapp" -> 앱 이름 명시
- path('hello_world/', hello_world, name='hello_world'),
서버 실행
python manage.py runserver
[local host ip]/account/hello_world/
Git 의 소개
GIT이란?
-> Version Control 시스템, 따라서 오류가 났을 때 rollback 가능! + 협업에 편리한 기능 다수 존재
Branch : 다른 개발 버전 ( 기존 버전에 영향 X )
Merge 기능을 통해 branch들을 합칠 수도 있다.
Gitignore 설정, 환경변수 분리, 첫 커밋
Gitignore란
깃에서 추적되지 않을 파일을 지정해놓는 파일
project 루트에서 .gitignore file 추가
https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore -> pycharm 프로젝트에 맞는 파일
가상환경 폴더를 무시하기 위해 venv/ 추가
db.sqlite3와 .idea/ 추가
환경 변수 분리
git에 올리기 전에 settings.py 에 담겨있는 secret key 등 민감한 정보를 분리해야 함
https://django-environ.readthedocs.io/en/latest/ -> 내부적으로 secret key를 읽어오는 라이브러리
- pip install django-environ
- 문서를 따라 최상위 폴더에 .env 추가 후 시크릿키 따옴표 없이 넣어주기
- settings.py에 import os, environ
- 문서 처음에 있는 내용 붙여넣고 read_env 부분 괄호 안에 env_file = os.path.join( BASE_DIR, '.env' ) 추가
- 원래 있던 secret key 부분도 env('SECRET_KEY')로 대체
- .gitignore에 .env 추가
--
추가적으로 아래 모듈 다운로드
pip install python-memcached
pip install django-redis
GIT 활성화
- pycharm 메뉴 - VCS - enable version control ...
- 터미널에서 cd PycharmProjects/almighty/
- git status -> 현재 올라가지 않은 추적 파일 상태
- git add . -> 모든 추적 파일 스테이지에 올림
- git commit -m "Initial commit"
장고 Template의 extends, include 구문과 render 함수
gitignore에 pycache 추가
- 프로젝트 폴더와 앱 폴더에 있는 pycache 폴더 삭제 ( 파인더로 )
- git add . -> git commit -m "Delete redundant files"
- .gitignore 파일에 __pycache__/ 추가
Hyper Text -> 문서 간 이동 가능
Markup Language
extends / include 구문
- extends : pre-made html 바탕 파일을 가져오는 것
- include : 작은 html 조각을 템플릿에 가져오는 것
Template base
- 최상위 폴더에 templates 폴더 생성
- base.html 추가하고 이전에 있던 views.py의 메서드를 render( request, 'base.html' )
- settings.py의 templates 부분에 DIR 추가
include / extends / block 구문을 이용한 뼈대 html 만들기
Static 설정 및 CSS 파일 분리
CSS 간단 핵심
Display 속성
- Block : 부모 태그의 width 100%를 차지, 높이는 따로 설정하지 않으면 내용물에 따름
- Inline : 내용물의 크기 만큼만 자리를 차지
- Inline-block : block처럼 보이지만 인라인처럼 내용물만큼의 width를 가짐
- None : 존재하긴 하지만 브라우저에 나타나지 않음
- visibillity : hidden과는 달리 자리도 차지하지 않게 됨
SIZE 척도
-> related with font-size
- px : 부모 관계 없이 px 단위의 절대값
- em : 부모 요소( 조상 전체 곱해서 ) 폰트 사이즈의 배수
- rem : 최상위 요소( root html, 기본 16px ) 폰트 사이즈의 배수, 주로 사용
- %: 바로 위의 부모 요소 폰트 사이즈의 배수
CSS display 속성, rem 단위 실습
Model, DB 연동
Model
-> DB 알 필요 없이 장고가 DB와 연동
- models.py에 DB 아이템으로 쓰일 class 생성 ( models.Model 상속 )
- python manage.py makemigrations
- python manage.py migrate
HTTP 프로토콜 GET, POST
GET / POST
USER --request--> <--response-- Server
'무엇'을 주고받는지 추가적인 정보를 보내는 방식
- GET : url에 파라미터로 데이터를 담아 전송, 양이 적고 치명적이지 않은 데이터 조회 등에 사용.
- POST : 정보를 body에 넣어 전송. create, update, user 관련 등에 사용
-> https가 아닌 http라면 암호화 X
GET, POST 프로토콜 실습
post 사용 시 form 태그 내부에 {% csrf_token %} 반드시 쓰기
POST 통신을 이용한 DB 데이터 저장 실습
DB SAVE 과정
- Send POST data
- Recieve POST data
- Save DB
- request.POST.get('input name')
- model 객체 생성 후 값 담고 .save()
- render 함수의 인자로 객체 보내기
- html에서 사용
DB 정보 접근 및 장고 템플릿 내 for loop
READ
Model클래스.objects.all() 를 통해 리스트로 모든 행을 가져올 수 있음
단순히 페이지 새로고침 -> HttpResponseRedirect()
django.urls의 reverse('앱이름:함수이름')으로 url 대체 가능
Pycharm 디버깅 설정
DEBUG 환경 설정
상단 Run 메뉴 - Edit Configurations...
- templates 중 python 선택
- script path를 [프로젝트최상위폴더]/venv/bin 으로 설정
- parameter를 runserver로 설정
- manage.py 우클릭 - debug 'manage'
Accountapp implementation
CreateView를 통한 회원가입 구현
CreateView() 이용하여 View 만들기
class AccountCreateView(CreateView):
# 주요 파라미터 지정
model = User
form_class = UserCreationForm
success_url = reverse_lazy("accountapp:hello_world")
-> reverse()는 함수형, reverse_lazy()는 클래스형
template_name = 'accountapp/create.html'
-> path에 AccountCreateView.as_view() 이용하여 등록
HTML에서 연결하기
- action="{% url 'accountapp:create' %}"로 라우팅
- {{ form }} 으로 UserCreationForm 자동 생성
Login / Logout 구현
LOGIN & LOGOUT
로그인, 로그아웃은 따로 함수 만들지 않고 직접 파라미터를 넣어도 ok
path('login/', LoginView.as_view(template_name='acountapp/login.html'), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
LOG IN & OUT Redirect Mechanism
next -> LOGIN_REDIRECT_URL이 없으면 default로 가게 됨
- html에서 login, logout 페이지 링크를 if문으로 생성 후 href="{% url 'accountapp:login' %}?next={{ request.path }}"
- settings.py에 LOGIN_REDIRECT_URL = reverse_lazy('accountapp:hello_world')과 LOGOUT_REDIRECT_URL = reverse_lazy('accountapp:login') 등록
Bootstrap 을 이용한 Form 디자인 정리
DetailView를 이용한 개인 페이지 구현
UserDetailView
- class AccountDetailView(DetailView) 생성, 파라미터로 model과 template_name
- DetailView 조회를 위해서는 PrimaryKey 정보가 필요하므로 'detail/<int:pk>'의 url을 가짐
- href="{% url 'accountapp:detail" pk=user.pk %}"
- 본인의 정보만 볼 수 있게끔 class에 context_object_name = 'target_user' 파라미터 추가하고 html에서 user 대신 target_user 사용
UpdateView를 이용한 비밀번호 변경 구현
정보 수정 페이지
pk가 필요한 것을 제외하면 CreateView와 거의 동일함!
- class AccountUpdateView(UpdateView)
- url은 detail과 비슷하게 /<int:pk> 추가
form 파일 만들기
class AccountUpdateForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super.__init__(*args, **kwargs)
self.fields['username'].disabled = True
-> 이후 views.py의 form_class 파라미터 수정
DeleteView기반 회원탈퇴 구현
회원 탈퇴 기능
DeleteView를 이용해서 다른 view와 마찬가지로 작성, pk 필요
target_user 사용도 추가
Authentication
Authentication 인증시스템 구축
return HttpResponseForbidden() -> 오류 페이지 return
Decorator를 이용한 코드 간소화
Decorator 패턴
@login_required -> 로그인 체크, 반환까지 그대로 해줌
@method_decorator(데코레이터, '메서드이름') -> 일반 function을 사용하는 데코레이터를 method( 클래스 내의 함수 )에 사용할 수 있도록 해주는 데코레이터
사용자 정의 데코레이터 만들기
프로젝트 폴더 내에 decorators.py 추가 후 def
원래 함수에 필요한 인자 받아서 내용 구현 !
마찬가지로 method decorator 이용하면 된다
Decorator 구문 줄이기
상단에서 묶음 이름 = [ 데코레이터 이름 배열 ] 으로 묶어두면
@method_decorator( 묶음 이름, '필요한 함수 이름')으로 한 번에 사용 가능 !
Profileapp Implementation
Profileapp 시작 그리고 ModelForm
Profileapp 구현 시작
Profileapp 마무리
이미지 사용하기
<img src="{{ target_user.profile.image.url }}"
이미지 라우팅 추가
프로젝트 urls.py의 urlpatterns 배열 뒤에 + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 추가
-> 모두 django.conf에서 import
get_success_url 함수 그리고 리팩토링
프로필 생성 / 수정 후 디테일 페이지 띄우기
def get_success_url(self):
return reverse('accountapp:detail', kwargs={'pk': self.object.user.pk})
Articleapp Implementation
MagicGrid 소개 및 Articleapp 시작
Magic Grid
임의의 높이를 가진 카드형 레이아웃을 위한 javascript 라이브러리
https://github.com/e-oj/Magic-Grid
- dist/magic-grid.cjs.js의 내용을 statics/js 폴더에 생성
- JSFIDDLE 페이지의 Sample Usage에 있는 js 내용 추가
- html & css 작성하고 link
Lorem Picsum : https://picsum.photos/ -> 일정한 크기의 랜덤 이미지 url 반환
Article 모델 생성 오류 수정
model에 created_at = models.DateField(auto_now_add=True, null=True)
ListView, Pagination 소개 및 적용
List View
게시글, 회원 정보 등 -> Single Object
Article List -> Multiple Object 를 표현하기 위한 view
Pagination
-> 객체 리스트를 페이지화
Infinite Scroll -> 스크롤하면 자동으로 추가 로딩
article_List와 page_obj 객체가 html에서 주요하게 쓰임
- for문을 이용해 article in article_List로 객체 불러옴
- 카드 내용(이미지)는 snippets에 따로 빼서 include문으로 불러오고, with article=article로 객체를 함께 넘겨줌
- 마찬가지로 pagination으로 page_obj를 넘겨 include
- page_obj.has_previous : 이전 페이지 유무
- page_obj.previous_page_number : 이전 페이지 num
- page_obj.number : 현재 페이지 num
- page_obj.has_next : 다음 페이지 유무
- page_obj.next_page_number : 다음 페이지
Commentapp Implementation
Mobile Responsive Layout
모바일 디버깅, 반응형 레이아웃
모바일로 local server 접속
python manage.py runserver = python manage.py runserver 127.0.0.1:8000
-> 컴퓨터에서 서버를 돌리고, 컴퓨터로만 들어갈 수 있는 local host
따라서 0.0.0.0:8000 으로 구동하면 ip 기반으로 포트를 열게 되므로 모바일에서도 접속 가능해짐
CMD에서 ipconfig 명령어로 IP를 확인하고 이를 통해 접속! ( 같은 공유기 사용 시에만 가능 )
접속 설정
settings.py의 ALLOWED_HOSTS = []에 '*' 추가 ( 모든 호스트 허용 )
화면 크기 맞추기
- <head> 태그 안에 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 추가
- .container div { width 삭제 }
- .container a { width: 45%; max-width: 250px; }
- .container { padding: 0; margin: 0 auto; }
- magicgrid.js에서 gutter: 12, 로 지정
- css에서 @media screen and (max-width:500px) 을 통해 스크린 사이즈가 500이하일 때 html { font-size: 12px } 적용
Projectapp Implementation
Subscribeapp Implementation
RedirectView을 통한 SubscribeApp시작
RedirectView
-> 구독 버튼을 누르면 해당 페이지에서 바로 처리
- def get_redirect_url()
- def get()
Field Lookup을 사용한 구독 페이지 구현
Field Lookup
Model.object.filter(pk='', user='') -> AND function
OR function, WHERE function -> Field Lookups
project__in=projects -> SELECT ... WHERE project in ()
https://docs.djangoproject.com/en/3.1/ref/models/querysets/#field-lookups
Django Wrap-up
WYSIWYG 의 소개 및 적용
WYSIWYG ( 이지윅 에디터 )
-> 보이는 대로 쓰인다 !
https://github.com/yabwe/medium-editor
script link 복붙 -> var editor = new Medium ... script 복붙
글 작성 내용이 태그 포함해서 나올 때
{{ target_article.content | safe }}
프로젝트 정리 및 다듬기
객체 이름 str로 만들기
models.py에서 def __str__(self):
return f'{self.pk} : {self.title}'
Home DIR 만들기
프로젝트 urls.py에 path('', 사용할view.as_view(), name='home')
Material Icon
https://material.io/resources/icons/?style=baseline
https://github.com/google/material-design-icons
스타일 시트 link 복붙, class에 material-icons 주고 아이콘 이름을 a 태그 사이에 값으로 넣으면 끝!
What is DOCKER? : Service Deployment
Why Docker? 서비스 배포로 들어가며
VPS 대여
VULTR
-> VPS( 가상 사설 서버 ) 대여 플랫폼
실제 서버는 물리적인 컴퓨터가 필요, 대여를 위해서는 많은 비용 필요함!
VULTR 사용하기
https://www.vultr.com/?ref=8741024-6G
정보 입력 후 일정 크레딧을 무료로 받을 수 있음
1. + 버튼 누르고 가상 서버 추가, Cloud Compute 선택
2. 서버 위치를 고르고 서버 타입은 Application - Docker - Ubuntu 선택, 사이즈는 가장 작게 ! ( 과금 단위는 사용 시간 )
3. 나머지 설정은 나중에! 하고 deploy하면 설치됨
가상 서버 접속
터미널에서 ssh 명령어를 통해 원격의 서버에 접속 가능
-> ssh라고 쳤을 때 나오는 게 없다면 openSSH 설치
서버 정보 참고, ssh root@ip 명령어 입력 후 암호 입력
docker 설치 확인을 위해 docker 명령어 실행
docker container ls 명령어로 현재 실행중인 컨테이너의 목록 확인 가능
Docker Container, Image
Docker GUI Portainer 컨테이너 생성
dockerhub : 전세계에서의 도커 이미지 공유 사이트
portainer.io : Docker CLI -> GUI 소프트웨어
portainer 설치
dockerhub(https://hub.docker.com/r/portainer/portainer-ce )에서 portainer-ce 이미지를 docker에서 사용하기 위한 커맨드 입력
- docker volume create portainer_data
- docker run -d -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
ip주소:9000 접속 후 portainer 구동 확인 가능
나오는 창에서 비번 설정하여 create user -> docker connect
Port의 이해 그리고 Nginx 컨테이너 생성
PORT
서버와 통신할 때 사용되는 출입구 번호!
- Vultr 가상 서버 안에 docker 시스템과 portainer시스템이 존재, 여기에는 특정한 port가 존재
- 외부의 port와 portainer의 port를 연결
Port 80 -> HTTP protocol 기본 포트
Nginx 컨테이너
- portainer.io에서 Containers 탭 선택 - add container
- name과 image에 nginx 입력
- publish a new port 클릭 -> 80, 80 ( 테스트용 )
- deploy
- 서버 ip를 포트 없이 그대로 입력하면 연결 확인 가능
django 소스코드 Github 업로드
Django Container
컨테이너 안에 소스를 넣고 구동해야 하므로 복잡할 수 있음!
- Upload source to Github
- Write Dockerfile -> 이미지 설계도 같은 느낌 !
- Build Image
- Run Container
Upload Source to Github
- new repository 생성
- 생성 후 나오는 페이지의 설명(2)에 따라 업로드
- git remote add origin "주소"
- git branch -M main ( master보단 main... )
- git push -u origin main
- pip freeze >> requirements.txt로 의존성 정보 남기기
Dockerfile 구문
Dockerfile 구문
FROM : base image를 가져옴 ( 부모 이미지 )
RUN : 실행 명령어 ( pip list, install, git, run ... )
WORKDIR : cd...의 역할, 절대 경로 사용
EXPOSE : 가상 서버와 연결시킬 수 있도록 django port를 노출
CMD : 컨테이너를 실행할 때 필요한 커맨드들 입력 ( python manage,py runserver 0.0.0.0:8000 )
Dockerfile 작성 및 Image, Container 생성
Dockerfile 작성
portainer 페이지에 images - build a image에서 web editor를 사용하거나 pycharm 등에서 만들어서 파일을 올릴 수 있음
# 베이스 이미지 가져오기 -> Python 공식 이미지 ( dockerhub에 존재 )
FROM python:3.9.0
WORKDIR /home/
RUN git clone https://github.com/isdiscodead/likelion_django_study.git
WORKDIR /home/likelion_django_study/
# requirements에 있는 라이브러리들 모두 설치
RUN pip install -r requirements.txt
# db 연동
RUN python manage.py migrate
EXPOSE 8000
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]그런데 이렇게는 실행이 안 됨 ( 환경 변수 분리해놨기 때문 )
-> RUN 커맨드 두 개 사이에 .env 파일에 있는 시크릿 키를 echo 커맨드로 넣어서 테스트 ! ( 실제로는 이렇게 ㄴㄴ )
RUN echo "SECRET_KEY=시크릿키값" > .envDjango Container 만들기
django_test_image:1 처럼 이름을 짓고 이미지 빌드
이미지 탭에서 생성된 이미지 확인 후, 컨테이너 탭에서 add
django_container 이름으로 생성, 이미지 이름에 django_test_image:1 넣기
포트는 8000 - 8000 연결 ( 장고 컨테이너 - 외부 ) 후 deploy
Gunicorn 설치 및 runserver 명령어 대체
Gunicorn
Django Container로 runserver 시 문제! runserver는 개발용
따라서 runserver를 대체하고, Nginx와 Django를 연결해주는 인터페이스인 Gunicorn 라이브러리를 Django Container 안에 넣어주어야 함
Gunicorn 설치
개발 터미널에서 pip install gunicorn 후 pip freeze > requirements.txt로 넣어주고, 깃에 업로드
Dockfile 수정
1. pip install -r requirements.txt 뒤에 pip install gunicorn 추가 ( 캐시 지우기 용 )
2. CMD 부분 수정
# 수정 전
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
# 수정 후
CMD ["gunicorn", "almighty.wsgi", "--bind", "0.0.0.0:8000"]
3. 버전 :2로 만들어서 image 생성 후 django_container_gunicorn으로, 8080 - 8000 포트 연결해서 새로운 컨테이너 생성 ( 8000번은 이미 사용 중이므로 외부 포트를 8080으로 하는 것 )
그런데 이 상태에서는 static 파일들을 가져오지 못하므로 Nginx를 연결해주어야 한다 !
Docker Network, Volume
Docker Network의 이해 및 구현
외부 ip + 80 -> Nginx 내부 80 -> Django 내부 8000
Django 내부 8000 -> ?? 경로를 알 수 없음 !
Docker Network
Container들을 하나의 네트워크로 묶어줌 ! 내부에 있는 Container들끼리 Container Name을 기반으로 요청 주고받기가 가능해짐 ( http://django_container_gunicorn:8000 과 같은 방식 )
Docker Network : Django Container
- 먼저 원래 있던 Container들을 portainer 빼고 모두 지워줌
- 네트워크 탭에서 add -> nginx-django으로 생성
- django_container_gunicorn을 port 없는 채로, 네트워크 탭에서 nginx-django 선택해서 deploy
Docker Network : Nginx Container
Nginx 컨테이너는 먼저 설정 파일을 만들어줘야 함
nginx.conf -> gunicorn( https://gunicorn.org/#deployment )에서 베이스 가져온 뒤 아래처럼 수정
worker_processes auto;
events {
}
http {
server {
listen 80;
location / {
proxy_pass http://django_container_gunicorn:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Filezilla
위의 설정 파일을 서버에 올려주기 위해 Filezilla( https://filezilla-project.org/ ) 라는 프로그램을 이용해야 함
- filezilla에서 vultr에 있는 ip, root, 비밀번호, 22번 포트를 이용해서 가상 서버 안의 파일에 접근할 수 있음
- home 폴더 안에 django_course 폴더 생성 후 로컬 폴더에 있는 설정 파일을 옮겨주면 끝!
- 이후 nginx 이미지를 이용한 컨테이너 생성, 포트는 80 - 80으로 연결하고 Network 탭에서 nginx-django 선택
- volume 탭에서 container : /etc/nginx/nginx.conf 로 하고 bind 체크, host : /home/django_course/nginx.conf로 deploy
- 이제 그냥 ip주소만으로 서버 접속이 가능해짐!
Static 의 이해
Static, 정적 파일이란
- 초기의 웹 : Static ( 정적인 html )으로만 이루어짐
- 공간적 부족을 이유로 동적인 파일들이 생기기 시작
- Static Content는 Server에서, Dynamic Content는 Application에서 관리하는 방식으로 분리됨
왜 Django - Gunicorn이 static을 사용하지 못할까?
-> Nginx가 server에 해당, Django와 Gunicorn이 Application에 해당하기 때문에 static 파일들을 사용하려면 Nginx가 반드시 필요함!
static 사용하기
- Collect static content from Django container
- Synchronize static contents with Nginx container
Collectstatic 명령을 통한 Static 파일 취합
Collect Static
- python manage.py collectstatic 으로 static 파일들을 취합
- 터미널에 출력된 경로에서 모든 static 파일들을 확인 가능 -> 경로 기억해두기
- Dockerfile에 RUN python manage.py collectstatic 명령어를 추가하고 새로운 image 빌드
✔ static이 collect 되는 경로는 settings.py에서 STATIC_URL과 STATIC_ROOT에서 설정됨
Docker Volume의 이해
Docker의 Volume이란
1. Bind Volume : Host Server( Vultr )의 Nginx.conf와 Nginx Container의 Nginx.conf를 연동
2. Named Volume : docker 안에서 하나의 Named Volume 생성 후 각각의 Container들에 붙여서 동기화 ! -> Container가 사라지더라도 volume은 남아있음
Docker Volume 생성 및 Container 적용
Docker Volume 사용하기
- Container들 전부 삭제 ( portainer 빼고 )
- image와 network는 그대로 사용, volume 탭에서 add volume
- static 볼륨 생성, media 볼륨 생성 ( 설정 X )
- django_container_gunicorn 생성 ( 이미지와 포트는 그대로, network : nginx-django, volume : /home/프로젝트명/staticfiles/와 static - local, /home/프로젝트명/media/와 media - local )
- nginx 컨테이너생성 ( 이미지와 포트 그대로 80-80, network : nginx-django, volume : /data/static/과 static - local, /data/media/와 media - local , bind : etc/nginx/nginx.conf - /home/django_course/nginx.conf )
컨테이너 생성 전에 nginx.conf를 수정하기 !!
listen 80; 아래에 코드 추가 후 저장 및 filezilla로 업로드
include mime.types;
location /static/ {
alias /data/static/;
}
location /media/ {
alias /data/media/;
}Local , Remote environment detachment
MariaDB 컨테이너를 이용한 DB 분리
현재는 Django Container에 모두 Data가 들어있는 상태
-> 안정성을 위해 분리 작업이 필요함! bind와 sqlite를 사용해도 되지만 DB 성능을 위해 분리하는 것이 좋음
DB 분리 작업
- MariaDB Container 안에 Volume 존재 -> Container의 생애 주기와 관련 없이 Data를 유지
- local 환경에선 sqlite를 사용했지만 배포 환경에서는 MariaDB를 사용
Maria DB Container
mariaDB도 dockerhub에 공식 이미지가 존재 ( https://hub.docker.com/_/mariadb )
- add container 누른 다음에 mariadb 컨테이너를 mariadb:10.5 이미지로 생성
- 생성 시 환경 변수 사용을 위해 Env 탭에서 add env variable -> 문서에 있는
MARIADB_ROOT_PASSWORD을 추가
개발/배포 설정 분리
개발 환경과 배포 환경의 설정 분리
- 프로젝트명의 폴더( settings.py가 있는 ) 안에서 settings라는 이름으로 new python package 생성
- settings.py를 해당 폴더로 옮기고 base.py로 이름 변경, local.py와 deploy.py 추가
- env 부분과 Allowed_host 부분을 잘라내서 두 군데 모두에 복사하고 from .base import *로 임포트
- DATABASES, CACHES 부분도 잘라내서 옮겨줌
설정 파일 수정
deploy.py
DEBUG = False
DATABASES 밑에 있는 링크를 들어가서 내용 복붙 후 sqlite3 -> mysql로 수정, 아래 차례대로 django, django, password1234, HOST mariadb, PORT 3306으로 설정
base.py
BASE_DIR 에 .parent를 한번 더 써주어야 함! ( 폴더가 하나 더 생겼기 때문 )
manage.py
os.environ.setdefault 부분에서 경로에 .local을 추가해주어야 함
MariaDB 컨테이너 설정 및 Django 연동
MariaDB 컨테이너 설정
- 원래 있던 mariadb 컨테이너 삭제
- database 라는 이름으로 volume 생성
- nginx-django network 연결, volume은 dockerhub의 mariadb 공식 문서 Caveats 부분 참고, /var/lib/mysql/ 사용해서 database - local 생성
- deploy.py에 써두었던 database 정보를 토대로 env 부분 작성( MYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD )
Django Container Dockerfile 수정
- 변경 사항 모두 git에 push
- Dockfile의 git clone 명령어 앞에 RUN echo "testing" 추가하여 캐시 변경
- RUN pip install mysqlclient 추가
- CMD 부분 ["bash", "-c", "python manage.py migrate --settings=almigthy.settings.deploy && gunicorn almighty.wsgi --env DJANGO_SETTINGS_MODULE=almigthy.settings.deploy --bind 0.0.0.0:8000"] 으로 변경
- django_test_image:4로 이미지 생성
Django Container 수정
4번 이미지로 django_container_gunicorn 생성
network, volume 설정 이전과 같이 해주기!
-> Django 컨테이너를 삭제했다가 다시 추가해도 멀쩡해짐
Docker Swarm, Stack, Secret
Container의 한계, Docker Stack의 이해
Container의 한계
1. Repetitive Configuration
모든 컨테이너마다 Port, Volume, Network 설정 반복
-> Total STACK Settings 파일 만들기 ! = Docker STACK
- Docker Compose라는 유사한 기능이 있지만 배포용 X 다른 기능
- yml 파일에 작성
2. Container shutdown
24시간 모니터링을 하며 리부팅 불가능! 따라서 Service라는 상위 개념으로 복사하여 관리 ( 설정 파일을 통한 Automatic REBOOT, 컨테이너 여러개로 복제도 가능 -> Scale out Containers )
Docker Swarm 의 이해
Docker Swarm
가상 서버 하나를 Node라고 함
이 여러가지 서버를 하나의 서버(서비스)인 것처럼 묶어주는 역할. 현재는 1개의 노드만 사용 !
노드 전체에 걸쳐 Service들이 존재 ( 컨테이너 종류마다 하나씩 ) -> Container Orchestration
Stack을 위한 yml 파일 작성
먼저 swarm 모드를 키기 위해 서버에 접속
- ssh root@ip주소 로 서버에 접속
- cd ..
- cd home/django_course/
- docker swarm init
가장 먼저 만들어진 노드가 manager node가 되게 됨!
이렇게 추가를 하고 나면 portainer에서 Swarm, Secrets, Services 탭을 확인할 수 있음
yml 파일 작성하기
root 폴더 안에 docker-compose.yml 생성
version: "3.7"
services:
django:
image: django_test_image:3 ( 테스트용이기 때문에 DB 분리 이전 이미지 사용 )
ports:
- 8000:8000
stack 탭 - add stack : django_stack
yml 파일을 upload하여 deploy
-> service 탭에서 scale을 늘리는 것만으로도 container 복제 가능 !
통합 yml 파일 작성
통합 yml 파일 작성
- 일종의 도메인으로 사용되는 '이름'을 반드시 유의해서 다른 설정과 동일하게 적어주기!!
- 이미지의 버전은 명시해주는 것이 좋음
- 추가적으로 networks와 volumes에 관한 내용도 따로 작성
version: "3.7"
services:
nginx:
image: nginx:1.19.5
networks:
- network
volumes:
- /home/django_course/nginx.conf:/etc/nginx/nginx.conf
- static-volume:/data/static
- media-volume:/data/media
ports:
- 80:80
django_container_gunicorn:
image: django_test_image:1
networks:
- network
volumes:
- static-volume:/home/likelion_django_study/staticfiles
- media-volume:/home/likelion_django_study/media
mariadb:
image: mariadb:10.5
networks:
- network
volumes:
- maria-database:/var/lib/mysql
environment:
MARIADB_ROOT_PASSWORD: password1234
MYSQL_DATABASE: django
MYSQL_PASSWORD: password1234
MYSQL_USER: django
networks:
network:
volumes:
static-volume:
media-volume:
maria-database:
run 되고 있지 않는 컨테이너의 경우 컨테이너 간의 의존성 때문에 발생함! 이런 컨테이너를 삭제하기 위한 방법도 존재하긴 함. 그냥 지워도 문제 X
Docker Secret을 이용한 보안
Docker Secrets
보안되어야 할 정보( secretkey, password 등 )를 docker 내에서 따로 관리해주는 기능
- Secrets tap - add secret
- 이름 정해주기 ( DJANGO_SECRET_KEY )
- dockerfile에 적어두었던 secretkey 옮기기
마찬가지로 db 관련 환경 변수들도 옮겨주고 기존 내용은 지워줘도 OK!
Secret 제공하기 : yml 파일 수정
- docker-compose 파일에서 서비스 내용에 secrets: 를 추가
- evironment에서 _FILE 접미사 이용 -> /run/secrets/mysql/secret이름
- volumes나 networks와 마찬가지로 하단에서 따로 정의 추가, external: true 옵션 추가!
django_container_gunicorn:
image: django_test_image:5
networks:
- network
volumes:
- static-volume:/home/likelion_django_study/staticfiles
- media-volume:/home/likelion_django_study/media
secrets:
- MYSQL_PASSWORD
- DJANGO_SECRET_KEY
mariadb:
image: mariadb:10.5
networks:
- network
volumes:
- maria-database:/var/lib/mysql
secrets:
- MYSQL_PASSWORD
- MYSQL_ROOT_PASSWORD
environment:
MYSQL_USER: django
MYSQL_DATABASE: django
MYSQL_PASSWORD_FILE: /run/secrets/MYSQL_PASSWORD
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/MYSQL_ROOT_PASSWORD
secrets:
DJANGO_SECRET_KEY:
external: true
MYSQL_PASSWORD:
external: true
MYSQL_ROOT_PASSWORD:
external: trueSecret 제공하기 : settings/deploy 파일 수정
- secret을 읽어오는 함수 정의
- secret 정보들을 read_secret(secret_name)으로 불러오기
def read_secret(secret_name):
file = open('/run/secrets/' + secret_name)
secret = file.read()
secret = secret.rstrip().lstrip()
file.close()
return secret
SECRET_KEY = read_secret('DJANGO_SECRET_KEY')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django',
'USER': 'django',
'PASSWORD': read_secret('MYSQL_PASSWORD'),
'HOST': 'mariadb',
'PORT': '3306',
}
}Secret 제공하기 : Dockerfile 수정
manage.py collectstatic을 CMD 부분으로 미뤄줌 ( 현재 처음 상태에서는 secret_key가 불러와지지 않기 때문, --noinput 옵션과 --settings 옵션 추가! )
CMD ["bash", "-c", "python manage.py collectstatic --noinput --settings=almighty.settings.deploy && python manage.py migrate --settings=almighty.settings.deploy && gunicorn almighty.wsgi --env DJANGO_SETTINGS_MODULE=almighty.settings.deploy --bind 0.0.0.0:8000"]
-> image 생성 후 stack도 image 이름 바꾼 후 재생성
보강 1: AWS / HTTPS / 좋아요 시스템
AWS 과금 관련 주의사항
AWS 서비스 주의사항
vultr에서는 과금 요소는 instance 대여비가 대부분, 초반 크레딧 제공으로 부담 낮음! 그러나 AWS는 과금 부담이 있을 수 있음 ㅜㅜ
보강 개요
AWS와 Vultr의 차이
- 서버 접속 시 VULTR는 id/pw 이용, AWS는 Key File( Pem ) 이용
- AWS에는 docker가 깔려있지 않음
- AWS는 기본적으로 port들이 firewall로 막혀있음
AWS EC2 인스턴스 생성
AWS EC2 인스턴스 생성하기
- 가입 후 로그인 - 콘솔에 로그인
- 서비스 탭에서 EC2 선택
- 네트워크 및 보안 탭에서 키 페어 선택 후 생성
- 이름 설정 aws_pragmatic_key, 파일 형식 pem 선택
- 다운로드된 키파일은 프로젝트 폴더에 넣어줌
- 인스턴스 탭에서 생성 선택, Ubuntu Server 18.04 선택
- 인스턴스 유형 선택 후 구성, 스토리지 크기 등은 그대로! 태그 Name:webserver로 추가( 예시 ), 보안 그룹은 그때그때 열어주면 됨
- 시작하기 누르면 기존 키페어 선택하기
인스턴스 접속하기
인스턴스 정보에서 확인 가능한 퍼블릭 IPv4 주소를 통해 ssh 접속과 기타 기능 사용 가능!
- 인스턴스 상태가 실행중임을 확인
- 터미널에서 cd를 통해 프로젝트 폴더로 이동
- ssh -i key이름.pem ubuntu@ip주소 로 접속
AWS Docker 설치
AWS 인스턴스에 Docker 설치하기
https://docs.docker.com/engine/install/ubuntu/
위 링크 참고, 보통 우클릭으로 복붙 가능! docker 명령어로 확인 가능
$ sudo apt-get update
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
Portainer 설치하기
https://documentation.portainer.io/v2.0/deploy/ceinstalldocker/
sudo docker volume create portainer_data
sudo docker run -d -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
AWS 방화벽 뚫어주기
- ASW 인스턴스 콘솔에서 보안 탭 - 보안 그룹
- 인바운드 규칙 편집 - 규칙 추가
- 사용자 지정 TCP, 포트 범위 9000 , 소스 0.0.0.0/0
이후 ip:9000으로 portainer 접속 가능, sudo docker swarm init 명령어로 swarm 사용 가능 !
AWS 기반 Stack 재배포
AWS EC2 인스턴스에 Stack 배포
- Secrets에서 MYSQL_ROOT_PASSWORD, MYSQL_PASSWORD, DJANGO_SECRET_KEY 추가
- filezilla로 nginx.conf도 다시 올려주어야 함! 그런데 AWS는 pem 키파일로 접속해야 하므로 좌측 상단의 사이트 관리자 열기 버튼으로 서버를 설정해야 함
- New site 추가 후 SFTP 프로토콜, 호스트에 ip, 포트는 22, 로그온 유형은 키파일 선택, 사용자 ubuntu, 키파일 업로드 후 연결
- 마찬가지로 파일을 넣을 디렉터리를 만들어야 하는데, 권한이 필요하므로 터미널에서 cd .. 로 home 디렉터리로 간 다음 sudo mkdir django_course로 디렉터리 생성
- cd ..로 다시 홈으로 간 다음 sudo chmod 777 django_course/ 로 권한 부여한 다음 파일을 옮겨줌
- image도 dockerfile 업로드로 추가! ( django_test_image:5 )
- add stack으로 다시 스택도 deploy 해줌
- AWS 콘솔의 보안 그룹에서 인바운드 규칙을 80번 포트로 새로 추가
AWS 도메인 연결
도메인 연결
업체 상관 없이 도메인 구매 -> AWS Route53 서비스와 연결 -> EC2 서버와 연결
- 서비스 탭에서 네트워킹 및 콘텐츠 전송 중 Route 53 선택 -> DNS 관리의 호스팅 영역 생성 클릭
- 도메인 입력 및 퍼블릭 호스팅 영역 선택 후 생성
- 레코드 중 유형이 NS인 부분이 name server이므로 이 정보를 도메인 관리 시스템에 입력
- EC2 인스턴스와 연결을 위해 A유형 레코드 생성 -> 레코드 이름( www 또는 생략... ), 값에 인스턴스 ip 입력 후 생성
AWS HTTPS 설정
HTTPS란
HTTP + SECURE!
본래 HTTP의 POST 방식은 BODY 안에 추가적인 내용들을 넣어서 보내는데, 경로 중간에 있는 서버에서 정보를 볼 수 있게 됨 -> HTTPS는 BODY 내의 내용을 암호화하여 보안 ✔
LOAD Balancer
서버에 부하가 많을 때 요즘은 Scale Out으로 클러스터링하는 추세, 이때 부하를 알맞게 분산시켜주는 것이 LOAD Balancer!
로드 밸런서 / HTTPS 설정하기
- EC2 대시보드에서 로드 밸런싱 탭 - 로드 밸런서 생성
- HTTP / HTTPS 유형 선택
- 이름 pragmatic, 인터넷 경계, IPv4 선택
- HTTP(80), HTTPS(443) 리스너 추가
- 가용 영역 선택 ( 4개 전부 선택 )
- ACM에서 인증서 선택 -> AWS에서 자동 발급/관리
- 인스턴스에서 사용 중인 보안 그룹 선택( 생성 가능 )
- 대상 그룹 이름 webserver, 프로토콜 HTTP
- 설정할 인스턴스를 선택하고 로드 밸런서 생성
- Route53에서 도메인에 HTTPS를 연결해줄 A 레코드 생성( 레코드 이름 www, 별칭 LoadBalancer-서울-이름 선택
- 인스턴스의 보안 그룹 - 인바인드 규칙에 TCP - 443번 포트 추가
Private Github Repo , 그리고 RSA 키 등록
Github Private Repository
이미지를 생성할 때 git clone을 이용함 -> public이어야 함
private repo의 경우 자격 증명이 필요해짐 -> RSA Key 등록
RSA Key 생성 / 등록
- 서버 접속 후 ssh-keygen -t rsa -b 4096 -C "사용 중인 이메일 주소" -> rsa 타입의 암호화 척도 4096인 키 생성
- 키를 저장할 경로 ( 생략 가능), passphrase도 생략 가능
- id_rsa( 개인키), id_rsa.pub( 공개키 ) 생성 확인 가능
- cat id_rsa.pub 명령어로 출력된 내용 복사
- git profile - settings - SSH and GPG keys
- new SSH key - 이름 clone key, 복사한 내용 붙여넣기
- touch ~/home/ubuntu/.ssh/known_hosts -> 현재 호스트를 알려줄 파일 생성
- ssh-keyscan github.com >> /home/ubuntu/.ssh/known_hosts
- git clone 시 링크를 SSH 타입으로 복사하여 사용
Dockerfile 수정 및 이미지 빌드
Private Repo를 위한 Dockerfile 수정
WORKDIR 이전에 작성 ! 개인 키 파일은 보여지면 안 되기 때문에 이미지 파일의 보안에도 신경써야 함!
- RUN mkdir /root/.ssh/ ( /home/ubuntu/.ssh/ 와 동일하지만 컨테이너 내부에서는 root를 사용하기 때문 )
- ADD ./.ssh/id_rsa /root/.ssh/id_rsa -> 상대 경로를 이용해 호스트의 key 파일 복사
- RUN chmod 600 /root/.ssh/id_rsa
- RUN touch /root/.ssh/known_hosts
- RUN ssh-keyscan github.com >> /root/.ssh/known_hosts
- GIT 링크를 SSH 프로토콜로 가져오기
서버 컨테이너 안에서 이미지 생성하기
- Dockerfile을 filezilla로 root 폴더( /home/ubuntu/ )에 올리기
- sudo docker image build -t django_test_image:6 . ( .은 현재 디렉터리에서 만들 것이라는 뜻 )
Likeapp 모델 설정
Like app 생성 및 모델 등 수정
- articleapp의 models.py에서 like = models.IntegerField(default=0) 추가
- startapp으로 likeapp 추가 ( 굳이 만들지 않고 article 앱에서 기능 추가해도 OK ) 후 settings와 url에 앱 등록
- likeapp에 urls.py 추가하여
- models.py에 LikeRecord 생성 ( user, article ) + class Meta에 unique_together('user', 'article') 추가
- makemigrations, migrate 명령어로 변경사항 저장
- article 앱의 detail.html 부분에 Like 관련 내용 추가
Likeapp View 구현
Like View 만들기
views.py에서 아래 내용 작성 -> url 등록
@method_decorator(login_required, 'get')
class LikeArticleView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse('articleapp:detail', kwargs={'pk':kwargs['pk']})
def get(self, *args, **kwargs):
user = self.request.user
article = get_object_or_404(Article, pk=kwargs['pk'])
if LikeRecord.objects.filter(user=user, article=article).exists():
return HttpResponseRedirect(reverse('articleapp:detail', kwargs={'pk': kwargs['pk']}))
else:
LikeRecord(user=user, article=article).save()
article.like += 1
article.save()
return super(LikeArticleView, self).get(self.request, *args, **kwargs)Django Message 적용 및 응용
Django Message란
유저가 요청 -> 요청에 대한 결과를 return ( 성공 여부 등 )
Message의 종류나 중요도 등에 따라 level을 다르게!
Django Message 사용하기
- from django.contrib import messages로 임포트 해오기
- messages.add_message(self.request, messages.레벨, "메시지") 로 호출
- template 단에서는 {% for message in messages %}를 통해 message를 원하는 div에 불러올 수 있음 ( base.html에 작성 )
- level에 따라 bootstrap 디자인 다르게 -> message doc의 Message tags 부분의 MESSAGE_TAGS 부분을 settings.py에 붙여넣기 한 후 ERROR의 태그 변경
- {{ message.tags }}를 통해 스타일 클래스 지정
Transaction 개요
Transaction이란
여러 개의 DB 상호작용이 연관되어 있을 때 하나의 작업처럼 연결( 하나라도 성공하지 못한다면 모두 fail 처리 ) -> Django decorator 사용
Transaction 구현
Transaction으로 Like 기능 묶기
- 기존의 DB 관련 코드를 모두 db_transaction(user, article)에 넣어줌
- 이미 like가 존재할 경우 raise ValidationError()
- @transaction.atomic 데코레이터를 통해 트랜잭션화
- try-except 구문으로 db_transaction 함수를 호출하고 결과에 따라 message 내보내기
@transaction.atomic
def db_transaction(user, article):
if LikeRecord.objects.filter(user=user, article=article).exists():
raise ValidationError('Like already exists')
else:
LikeRecord(user=user, article=article).save()
article.like += 1
article.save()
def get(self, *args, **kwargs):
user = self.request.user
article = get_object_or_404(Article, pk=kwargs['pk'])
try:
db_transaction(user, article)
messages.add_message(self.request, messages.SUCCESS, "좋아요가 반영되었습니다.")
except ValidationError:
messages.add_message(self.request, messages.ERROR, "좋아요는 한 번만 가능합니다.")
return HttpResponseRedirect(reverse('articleapp:detail', kwargs={'pk': kwargs['pk']}))
return super(LikeArticleView, self).get(self.request, *args, **kwargs)




