Django로 pinterest 만들기(0)

2022. 1. 27. 08:59
  • 인프런의 '작정하고 장고! Django로 pinterest따라만들기 : 바닥부터 배포까지' 라는 강의를 들으면서 작성
  • 해당 강의는 django, docker , aws등을 활용하여 장고홈페이지를 만들고 배포하는 것을 목적으로 함
  • (0)은 완강 후 혼자 전체과정을 스스로 만들어볼 때 참고할 수 있도록, 해당강의를 들으면서 어렵거나 헷갈렷던 부분에 대해 정리해둘 예정

이 포스팅의 내용은 다음과 같음(계속 추가 예정)

      • pycharm 스펠링체크 없애기
      • django envrion
      • git
      • 다른곳에서 작업하려면?(with git)
      • 구글폰트 , 폰트스트랩 적용
      • 스테틱파일 적용
      • Model,DB
      • CSS : display 속성
      • CSS : size 속성
      • 파이참 - 디버그
      • Django 의 CRUD!
      • Login / Logoutview의 redirect
      • django bootstrap4 적용 및 사용
      • HTML파일 안에서, user 변수 사용
      • context_object_name
      • 슈퍼계정생성
      • 미디어 파일을 위한 설정
      • 프로필앱의 목표
      • Form 만들기 /form_vaild
      • Mixin
      • related_name의 역할
      • Foreign key와 primaryKey
      • 모바일 디버깅
      • WYSIWYG
      • 구글아이콘 사용
      • 장고 메세지 태그조정
      • vps
      • 도커의 기본 개념
      • Docker : portainer.io
      • Docker : 컨테이너 생성(생성해서 Nginx 설치)
      • dockerfile
      • gunicorn
      • Nginx / docker network
      • nginx
      • static file모으기
      • docker Volume
      • Maria DB
      • 개발, 배포환경 분리
      • 컨테이너 만들때 주의사항
      • 컨테이너의 한계/보완
      • Docker swarm의 개념
      • Docker swarm의 구동
      • Docker secret을 이용한 보안
      • aws
      • aws:docker 설치
      • aws기반 stack 배포(벌쳐서버와 비교하여, aws서버에 배포하면서 수정되는 점)
      • 도메인구매 및 구현
      • aws HTTPS 구현

 

혼자만들면서 수정해보고 싶은 부분

  • 부트스트랩5를 적용?
  • 글씨체를 좀 더 정갈하게
  • css 정리

 


pycharm 스펠링체크 없애기

  • 파이참은 기본적으로 코멘트문(#로 시작되는 글)에 대해서도 스펠링채크를 해줌
  • file > setting > proofreading > typo 가서 process coments를 해제하면됨(혹은 setting에서 typo검색)
  • 그냥 검색하면 나오는건데, 이걸 적는 이유는 파이참이 최근에 패치가된건지 검색을 통해 들어간 블로그들이 설명하는 방식으로는 typo에 접근할 수가 없었음..

 

django envrion

  • Django의 secret_key를 숨기기 위해 , django environ을 사용해야함
  • 근데 7강에 나와있는 코드와 다르게, 업데이트되어서 그 방식에 변화가 좀 있었음..
  • 참고링크를 따라가니까 문제가 해결이 되었음 : https://ffoorreeuunn.tistory.com/358
  • 그리고 .env파일에 secret_key를 적어줄때, 따옴표 없이 입력해야함 ex )SECRET_KEY = djanbg*.....

 

Git

  • 해당 강의에서 git을 어느정도 사용함. git과 관련된 내용을 요파트에 다 적을 예정
  • .gitignore
    • git에서 추적하지 않고 무시해도 될 부분들에 관해 적어둠
    • 해당 파일에 적힌 경로는 깃에서 추적하지 않고 무시함
    • gitignore이 아니라 .gitignore임
  • gitbash
    • git전용의 cmd라고 생각하면 편할 듯
  • 명령어
    • 명령어를 입력할때는 git bash에서 manage.py가 있는 경로로 cd를 한 후에 사용하면 됨
    • git status : 상태확인(track이 안되어있으면 붉은색. 이때는 commit불가. 추적되면 초록색. 이때는 commit가능)
    • git add . : 모든 파일을 track시작
    • git commit -m "메세지내용" : commit이 실행됨. 즉 변경사항이 저장됨.메세지는 그냥 설명을 위한거라 있든 없든 상관X
    • git reset --hard HEAD : 최근의 마지막 commit으로 돌아감. 즉 마지막 커밋이래의 모든 변경사항을 다시날리고 실제파일들의 내용도 마지막 커밋상태로 돌아감!!
  • dockerfile의 cloneing을 위해서 새 repo를 만들고 다음과 같은 명령어를 진행함. 이렇게 안했을 때는 뭔지 모르겠는 이상한 에러가 발생했음. ㅠㅠㅠ
    • git remote add origin repo주소
    • git branch -M main
    • git push -u origin main

 

다른곳에서 작업하려면?(with git)

 

 

구글폰트 , 폰트스트랩 적용

  • 헤드파일에 부트스트랩, 구글폰트 적용
    • 구글폰트 :  구글폰트홈페이지(https://fonts.google.com/) > 그냥 맘에 드는거 골라서 들어가면 > select this tyle > 옆에 링크나오는거 head파일에 넣고 > 밑에 css를 style로 적용시키면 됨
  • css를 활용해 header , footer(header.html, footer.html) 꾸미기

 

스테틱파일 적용

  • static file관련 설정하고, html의 style을 css파일로 넘김
  • static file : css파일과 같은 정적인 파일들을 말함
  • setting.py에 다음과 같은 코드를 추가함
    • static_url : html에서 static 파일을 찾을때, static파일명 앞에 붙여주는 부분. 즉, static file의 기본경로가 됨
    • static_root : 나중에 collection?과 같은 명령시, staticroot파일로 static파일들이 모임
    • staticfiles_dirs : static파일을 찾을때, 해당 위치에 있는 파일을 찾음(template의 base_dir과 같은 것임)
STATIC_URL = 'static/'
#얘랑 css 파일의 href일부가 합쳐져서, css주소가 됨

STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
#os의 path모듈의 join함수
#BASE_DIR아래의 staticfiles폴더를 staticroot로 삼겠따
#static_root로 삼으면 나중에 collection과 같은 명령을 할때, staticroot로 파일들이 모임

STATICFILES_DIRS = [
    BASE_DIR / "static",
    ]
#앱에 종속되지 않는, staticfile의 DIR. 이걸 설정하면
#static 파일을 찾을때 여기서 찾아봄

 

  • 물론 마지막에 head파일에서 stylesheet를 적용해줘야 함(당연)
<link rel = "stylesheet" type = "text/css" href ="{% static 'base.css' %}">

 

  • 스타일 적용 우선순위 순서
    • html태그에 style속성을 통해 적힌 style
    • html 페이지에서 <style>~<stlye>사이에 적힌 style
    • 다른 파일(style.css 등)

 

  • css 적용안됨문제
    • cache때문에 그럼. 즉 브라우저에서 css.base파일을 이미 받아놧으면 , css파일을 수정해도 새로이 받지를 않고 캐쉬의 내용을 사용함
    • 해결법 : https://www.inflearn.com/questions/204510

 

Model , DB

  • 모델
    • 장고와 DB를 연동
    • DB의 row와 column이 item과 attribute로 연결됨
  • models.py에서 클래스를 만들어 데이터를 생성함
class HelloWorld(models.Model):
    text = models.CharField(max_length=255, null=False)
    #null = True이면, 이 text의 데이터들 중 빈게 있어도 된다 이말임. False면 필수
  • 클래스를 생성한 후 cmd에 다음을 입력
    • python manage.py makemigrations : db를 생성
    • python manage.py migrate : 생성된 db를 연동. 이때 처음 연동하면 , 이것저것 모든게 연동됨
  • setting.py의 db에서 db에 관한 정보를 확인할 수 잇음
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
#이 base_dir아래의 db.sqlite3에 모든 db정보가 저장되고 있는 것임
#엔진은 db의 종류를 말함

 

  • 모델을 만들었으면 다음을 실행해야 함
    • python manage.py makemigrations
    • python manage.py migrate
  • 그래야 만든 모델이 (app에)적용이 됨

 

CSS : display 속성

  • css의 display속성[모든 html태그는 display속성을 가짐]
    • block
      • 일반적인 default 속성부모태그의 최대넓이를 가짐. 줄바꿈이 발생함 ex)div,p
      • width/height가 적용됨. margin과 padding도 상하좌우 전부 반영됨
    • inline
      • 들어가있는 contents만큼의 크기를 차지함contents에 따라서, 태그가 한줄에 (오른쪽으로)배열되기가 쉬움
      • width/height가 적용되지 않음. margin과 padding은 좌우만 반영되고 상하가 반영되지 안흥ㅁ
      • ex)span
    • inline-block
      • inline처럼 전후 줄바꿈없이 한줄에 배치가능
      • width/height가 적용됨. margin과 padding도 상하좌우 전부 반영됨
      • 즉 inline인데 block의 속성을 약간 가지고 있다~로 보면 편함
      • ex)button, input
    • none
      • 존재하긴 하지만, 공간적으로 존재X
      • visibility:hidden은 그냥 보이지만 않을뿐. 공간은 그대로 가지고 있음. 즉 투명해진다고 생각하면 됨

 

CSS : size 속성

  • html태그들의 크기를 , 화면사이즈에 맞춰서 변하도록 해주는게 반응형임
  • px
    • 어떤 것에도 영향을 받지 않는, 절대값
  • em
    • 부모태그에 비례. 부모태그가 10px이고 자식태그가 1.5em이면 자식태그는 15px가됨
    • 근데 부모가 여러개면, 화면은 2배커졋는데 2*2*2배가 적용된다던지 하는 문제가 발생할 수 있음..
    • 그래서 em보다는 보통 rem을 많이 사용
  • rem
    • 일반적으로 많이 사용(최소한 이 프로젝트에서는 얘를 자주 사용할 듯)
    • root-em. 즉, 해당 html문서 전체에(최상위에) defualt로 적용되어있는 font-size에만 비례
    • 즉 중복되거나 그럴일이 없음
    • 그리고, html의 기본으로 설정된 폰트가 16px이므로 따로 설정이 없으면 1rem = 16px
    • rem과 함께 같이 많이 사용됨
    • 바로위의 부모태그에만 영향을 받음

 

파이참 : 디버그기능

 

장고의 CRUD

  • 장고는 crud로 유명함
  • crud에 대한 view, 즉 crud에 최적화 되어 crud에 대한 작업을 쉽게 할 수 있는 클래스들을 제공해줌
    • 이렇게 CRUD에 대해 django가 제공해주는 애들을 CBV(Class Based View)라고 함
    • 참고로 CBV의 반대는 FBV(Function based view)라고 함. 우리가 초반에 만들었던 Hello_world같은 함수임
    • 근데 FBV처럼 우리가 직접 함수기반으로 이것저것 만들면 너무 할게 많음
    • 반대로 CBV같이, 장고의 기능을 활용하면 > 그냥 몇개의 정보만 쥐어주면(모델,리다이렉트 경로 등..) 나머지는 장고가 알아서 해줌. 이렇게 장고의 기능을 활용하면 가독성이 매우 훌륭 + 코드짜기도 쉬움(생산성 증가)
    • 그래서 장고가 CRUD의 기능이 뛰어나다고 하는 것임
    • 근데 CRUD는 사실상 거의 모든 것에 적용이 가능한 것임

 

Login / Logoutview의 redirect

  • login / logoutview의 redirect경로
    • 우선, next라는 파라미터를 가진 벨류를 찾음. 있으면 그곳으로 감
    • 없으면, 프로젝트 폴더내에서 setting.py안에 있는 LOGIN_REDIRECT_URL을 찾아서 그리로 감
    • 이것도 없으면, Django의 default인 'profile'로 감
  • 밑의 예시는, login을 클릭하여 로그인화면으로 이동했을 때, url로 next 벨류값을 request.path로 준것임
  • 따라서, 로그인이 성공했을 때, next벨류의 값인 request.path로 이동하게 됨
<div class = "pragmatic_header">
<!--margin을 2rem 0 이런식으로 주면, 상하에 2rem 좌우에 0을 준다는 것 -->
    <div>
        <h1 class = "pragmatic_logo">Pragmatic</h1>
    </div>
    <div>
        <span>nav1</span>
        <span>nav2</span>
        <span>nav3</span>
        {% if not user.is_authenticated %}
        <!--login이 되어 있지 않다면!-->
        <a href = "{% url 'accountapp:login' %}?next={{ request.path }}">
<!--get방식으로 next변수의 값을 주는 거임. request.path는 현재 로그인화면을 요청한 경로. 즉 로그인화면 전 url을 의미함
로그인이 성공했을 때, next를 참조하여 그리로 이동하는데, request.path가 로그인의 경로임           -->
            <span>login</span>
        </a>
        {% else %}
        <a href = "{% url 'accountapp:logout' %}?next={{ request.path }}">
            <span>logout</span>
        </a>
        {% endif %}

    </div>
</div>

 

  • 근데 이것만 하면, 그냥 url을 통해서 접속시(~/login/) next값이 없으므로, settings.py에서 다음과 같은 설정을 추가해주어야 함
LOGIN_REDIRECT_URL = reverse_lazy('accountapp:hello_world')
#login성공시 redirect 주소
LOGOUT_REDIRECT_URL = reverse_lazy('accountapp:login')
#logout성공시 redirect 주소

 

django bootstrap4 적용 및 사용

  • 참고사이트(공식) : https://django-bootstrap4.readthedocs.io/en/latest/installation.html
    • 공식사이트에, 어떻게 부트스트랩 시작하는지 + form이나 이런거 어떻게 사용하는지도 나와있음
  • pip으로 설치한 이후, setting.py의 INSTALLED_APPS 에 'bootstrap4'를 써주면 적용 완료
  • 그 후에, 사용할 html페이지에, {% load bootstrap4 %} 를 넣고 사용하면 됨
  • 부트스트랩이 제공하는 폼 사용 : {% bootstrap_form form %}

 

HTML파일 안에서, user 변수 사용

  • 일반적으로 html템플릿에서 변수를 사용하기 위해서는 views.py에서 해당 변수를 보내주어야 함. 그 변수들은 보통 context를 통해서 views.py에서 보내짐
  • 예를들어, 다음과 같이 보내주기 때문에
context ={ 'hello_world_list' : hello_world_list }
  • HTML에서 {% if hello_world_list %} 처럼 쓸 수 있음. 즉, views.py에서 받지못하면 변수를 html템플릿에서 사용할 수가 없음
  • 하지만, view에서 넘겨주는 값말고도 사용가능한 객체들이 몇개 있는데, 그 중 하나가 User객체임
  • User는 웹서비스의 특수한 자원이므로, django에서 템플릿에 User에 관한 정보를 자동으로 넘겨줌(더 자세히는, context를 따로 지정해주든 말든, 자동으로 context를 생성해서 그 안에 user객체에 관한 정보를 넘겨줌)
  • 따라서 user는 views.py로부터 받지 않아도 템플릿에서 사용이 가능함
  • 참고 : https://www.inflearn.com/questions/280010

 

context_object_name

  • 아직 정확하게는 잘모르겠음..
  • 일단 detail.html과 views.py의 AccountDetailview라는 곳에 어느정도 설명을 남겨놨음
  • 지금까지 대충 정리한 걸로는, 원래 Detailview에서 템플릿에 제공해주는 정보를 객체에 담아서 전해줌
  • 근데 context_object_name을 설정하면 , 그 객체이름을 설정해줘서 가독성이 있게 만들어줌
  • 지금 의문인 부분은
    • 그럼 원래는 user라는 이름을 막가져다가 썼는데, 이거는 장고에서 기본적으로 제공되는 정보라서 ok
    • 현재 로그인한 유저의 정보니까, 어디서든 사용되는 것 ok
    • 그럼 context_object_name으로 바꿔준 부분은 도대체 어떤 부분에 관한 정보인지??
    • 계속해서 공부하면서 수정해나가고 있는데, user랑 별개로, user처럼 자동으로 넘어가는 정보가 있는 것 같음. 그 넘어가는 정보의 이름을 context_object_name을 사용해 수정해주나봄

 

데코레이터-accountapp

  • accountapp 의 Detail과 Updateview에서 사용할 데코레이터
  • Detail과 Updateview는 urls.py에서 작성된대로, url의 끝을 <int:pk>라는 값으로 받음.
  • 그 int:pk과 , 현재 접속한 user가 실제로 같은지를 확인해줌
def account_ownership_required(func):
    def decorated(request, *args, **kwargs):
        user = User.objects.get(pk=kwargs['pk'])
        #우리가 urls.py에서 pk로 받은 값들을 user로 주는 것임
        if not user == request.user:
            #pk로 받은 값이, 실제로 request를 보낸user와 동일한지를 확인해줌
            return HttpResponseForbidden()
        return func(request, *args, **kwargs)
    return decorated

 

슈퍼계정생성

  • python manage.py createsuperuser
  • 해당계정으로 /admin에 접속하여 user등을 관리할 수 있음

 

미디어 파일을 위한 설정

  • django에서는 미디어파일을 관리하기 위해 pillow라는 라이브러리가 필요함
    • pip install pillow
  • static file처럼 , setting.py에 추가적인 설정이 필요
MEDIA_URL = '/media/' #파일경로
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') #파일이 저장되는 위치
  • MEDIA_URL : 이미지파일을 들고올때, 들고오는 경로의 맨앞에 추가됨
  • MEDIA_ROOT : MEDIA파일들(이미지파일 등)이 저장되는 장소

 

프로필앱(profile app)의 목표

  • account app과 1:1로 연동되게 할 것임
  • 이미지,닉네임,메세지에 대한 정보
  • deleteview나 detailview는 사용하지 않음

 

def get_success_url

  • 작업이 성공했을때, 자동으로 연결되는 url값을 던져주는 함수
    def get_success_url(self):
        return reverse('accountapp:detail', kwargs={'pk': self.object.user.pk})
        #self.object = user임
        #profile.user 는 모델의 user임.
        #결론적으로 user의 pk를 넘겨줌

        #그렇게 해서, create가 success시 반환하는 url을
        #account:detail/<pk> 와 같은 식으로 보내줌.
  • 위의 코드에서, 작업이 성공했을때, accountapp의 detail이라는 path로 가도록함
  • 그러면서 self.object.user.pk의 값을 'pk'라는 이름으로 함께 던져줌
  • 참고로. accountapp:detail의 path는 다음과 같음
path('detail/<int:pk>', AccountDetailView.as_view(), name='detail'),
  • 즉, 무조건 <pk>값을 받도록 되어 있는데, 그걸 get_success_url함수에서 같이 던져주는 것임

 

Form만들기 / Form_valid

  • accountapp에서는 form으로 creationform이라는, django에서 기본적으로 제공하는 form을 사용하였음
  • 그러나, 그건 account와 같이 보편적이면서도 중요한 경우를 위하여 제공하는 것임
  • 일반적으로는 form을 새로이 만들어서 적용해줘야함

 

  • 그리고 그 form은 다음과 같이 만든다
  • 우선 model을 만들어줌. model에서 사용할 속성은 user/image/nickname/message 총네개임
class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    #profile과 User객체를 하나씩 1대1로 연결해준다
    #on_delete : profile과 연결된 User객체가 사라질때 어떻게 할지
    #Cascade :  함꼐 사라진다
    #related_name : 얘를 통해 쉽게 접근이 가능

    image = models.ImageField(upload_to='profile/', null=True)
    #이거 미디어파일임. 그래서 settings.py에 적힌대로 앞에 'media/'부분이 추가됨
    # 즉, 'media/profile/~' 이런식으로 저장됨
    #null = True : 없어도 된다.
    nickname = models.CharField(max_length=20, unique=True, null=True)
    #charfiled : 그냥 글자필드
    #unique = True : 고유해야함
    message = models.CharField(max_length=20,null = True)

 

  • 그리고, model을 바탕으로 form을 만든다
  • form에서 데이터를 제출하면 그 입력을 model에서 받는다~ 정도로 생각하면 좋음
  • model에서 지정한 네개의 속성 중, 세개(image/nickname/message)만 사용함
class ProfileCreationForm(ModelForm):
    class Meta:
        model = Profile
        fields = ['image', 'nickname', 'message']

 

  • 근데 이렇게만 하면 에러가 발생한다.

  • 왜그런고 하니, user정보가 없기때문임(form에서 user정보를 제출하지 않음)
  • 그리고 그렇게 form에서 user정보를 제공하지 않는 이유는, 보안을 위해서임

 

  • 그래서 이 오류를 해결하기 위해 views.py의 ProfileCreateView 클래스에 form_valid메소드를 추가해줌
    def form_valid(self, form):
        #profile의 model인 Profile을 보면, user / image / nickname / message 총 네개의 자료를 받지만,
        #form에서 보면 image/ nickname / message 총 세개의 데이터만 사용함
        #사용자가 그냥 지맘대로 user에 접속해서 이것저것 수정할까봐, user를 사용하지 않는 것임
        #근데 그러다보니까 user라는 데이터 정보가 없어서 문제가 발생함.
        #이를 해결하기 위해 , 지금 form_valid라는 애를 써주는 것임

        temp_profile = form.save(commit=False)
        # form에서 데이터를 보낼때, 그 데이터가 form에 들어가있음
        # form.save(commit = False) :form에서 보낸 데이터를 임시로 저장함
        temp_profile.user = self.request.user
        #유저정보를 self.request.user 즉 request를 보낸 user로 선언
        temp_profile.save()
        return super().form_valid(form)
  • form_valid는 두번째 인자로, 방금 form에서 보낸 데이터를 받음
  • 그리고 user정보가 없으므로, 방금 요청을 보낸 user를 user정보로 선언
  • 그러면 user정보가 저장됨
  • 물론 아직 의문인 부분 > temp_profile을 저장했는데, 그게 super.form_valid(form)에 왜 전해지는 것인지? form_valid가 self로 받기때문에, 그 부분에서 받아지는 것인가???
  • 나중엔 해결되어있길..

 

데코레이터-profileapp

  • profileapp의 ProfileUpdateview에서 사용할 데코레이터
  • url을 통해 얻게되는 pk가  현재 요청하는 유저의 pk와 일치한지를 확인해줌
  • 일치시 정상작동, 불일치시 forbidden을 돌려줌
from django.http import HttpResponseForbidden
from profileapp.models import Profile

def profile_ownership_required(func):
    def decorated(request, *args, **kwargs):
        profile = Profile.objects.get(pk=kwargs['pk'])
        #우리가 urls.py에서 pk로 받은 값들을 profile로 주는 것임
        if not profile.user == request.user:
            #pk로 받은 값이, 실제로 request를 보낸user와 동일한지를 확인해줌
            return HttpResponseForbidden()
        return func(request, *args, **kwargs)
    return decorated

 

MagicGrid

  • 만드는 웹페이지에 magicgrid기능을 사용하려 함
  • 해당 공식 깃헙 주소(https://github.com/e-oj/Magic-Grid)에서, dist폴더 > 가장 상위의 js파일의 내용 전체를 복사하여 js파일에 넣어줌 + 그거에 이어서 깃헙글 중 JSFIDLE(https://jsfiddle.net/eolaojo/4pov0rdf/)의 JS내용을 긁어서 붙여줌
  • (추가)공식 홈페이지의 깃헙주소가 업데이트를 하면서, 그대로 긁어올 시 약간의 오류가 발생함
    • 오류 : 화면을 처음띄웠을 때, 그림들이 일렬로 배열됨
    • 따라서, 일단은 그냥 선생님의 깃헙(https://github.com/noeul1114/pragmatic)의 js파일의 내용을 긁어왔음
  • 마지막으로 다음과 같은 내용을 더해줌
  • 모든 html태그의 img들에 대해, 이미지들이 로드될때 매직그리드의 포지셔닝을 재실행해라! 라는 것임
    • js구현 속도보다 사진의 로딩이 느려서, 사진이 로딩이 되었을때는 이미 js구현이 완료된 후라 원하는 대로 알맞게 구현현이 안됨
    • 포지셔닝을 다시해서 원하는대로 구현이 되도록 하는 것
var masonrys = document.getElementsByTagName("img")

for (let i = 0; i<masonrys.length;i++){
    masonrys[i].addEventListener('load',function(){
        magicGrid.positionItems();
    },false);
}

magicGrid.listen();

 

  • 추가로, js파일은 static에 넣어서 사용하므로, 랜더링할 html에 다음의 코드를 넣어줘야 함 
{% load static %}
 <script src = "{% static 'js/magicgrid.js' %}"></script>

 

 

Lorem Picsum

  • 별건 아니고, 그냥 사진을 사이즈에 맞게 랜덤으로 들고오는 거임
  • 밑의 사이트에서 Static Random Image 사진들을 가져와서 활용해 보았음
  • https://picsum.photos/

 

 

Mixin

  • Create View는 form은 존재하는 반면 object가 없음
  • Detail View는 object는 존재하는 반면, form이 없음
  • 이걸 상속받아서 클래스를 만드는 입장에서, DetailView로 구동시키는 페이지에 form을 사용해야 할 경우 애매한 상황이 발생 ex)  DetailView로 만든 상세보기 페이지에 comment입력을 위한 form이 필요할 경우
  • 사용하려는 View클래스에 ~Mixin 형태의 클래스를 하나더 상속 받는, 즉 다중상속을 통해서, 특정view에 특정 기능을 추가함

 

related_name의 역할

 

Foreign key와 primaryKey

 

모바일 디버깅

  • 지금 적는 방식은, 컴퓨터와 동일한 wifi를 쓸 경우에만 가능
  • 보통 개발단계에서 하는, 'python manage.py runserver'는 , 'python manage.py runserver 127.0.0.1 : 8000' 과 같음
    • 이건 그냥 내컴으로 서버를 돌리고 , 내컴만 접속할 수 있게 하는 것임
  • 그러면 모바일로 들어가서 보려면?
    • 'python manage.py runserver 0.0.0.0:8000'로 가동
    • AllOWED_HOST를 허용해줘야함
      • settings.py > ALLOWED_HOST
      • 원래는 비워져 있는데, 다음과 같이 입력
      •  
      • ALLOWED_HOSTS = ['*']
      • 누구든지 허용하겠다는 뜻임. 이거 안해주면 원래는 누가접속하든 다 막음
    • IP가 필요할 경우알아내는 법 : CMD에 들어가서 ipconfig 하면 나옴

 

모바일 반응형 만들기

  • html 파일의 헤더에 다음과 같이 입력해줌
<head>
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  • 화면사이즈에 맞춰서 줄어드는 코드

 

  • list.html도 다음과 같은 css코드를 추가해줌
<style>
.container {
padding: 0;
margin: 0, auto;
}


.container a {
width: 45%;
max-width:250px;
}
</style>
  • 위에 container는 , bootstrap에서 container의 padding이랑 margin값이 0이 default가 아니므로, 0으로 설정해줌
  • container는 기본 width = 45%이되, 최대 width가 250px가 되게함. 즉 화면이 작을때는 a태그가 45%를 차지할 수 있지만, 화면이 커지면 최대 넓이가 250을 넘어가지 못함

  • 마지막으로 base.css에서 다음과 같이 해줌
@media screen and (max-width:500px){
    html {
        font-size:13px;
    }
}
  • 스크린이 500px이하가 될경우, html의 기본 폰트가 13px이 됨(기본이 16xp이엿음)
    • 이에 따라 rem으로 제작한 모든 폰트사이즈들도 감소함

 

WYSIWYG

  • WYSIWYG는 다음과 같은 것임 : https://yabwe.github.io/medium-editor/
  • 이걸 article에 적용하였음.
  • 적용하는 법은 다음과 같음
    • 우선 form을 좀 수정해줘야함. 나는 ArticleCreationForm의 content 에 적용하려고 했으므로, content를 필요에 맞게 커스터마이징해줬음. content에 생성되는 글자의  class와 style을 설정해준다는 뜻임
class ArticleCreationForm(ModelForm):
    content = forms.CharField(widget=forms.Textarea(attrs={'class':'editable text-start','style':'height:auto;'}))
  • 이제 적용시키면 됨
  • 공식홈페이지 : https://github.com/yabwe/medium-editor
  • 홈페이지의 맨 첫번째 단락(Basic usage)에서, for a customone부분의 내용을 복사해서 적용하려는 html의 상단에 넣음
  • 조금 밑에 Usage부분에 다음의코드가있음: "<script>var editor = new MediumEditor('.editable');</script>​" 얘도 적용하려는 html의 바로 밑에 넣어줌. 그럼 끝!
  • 참고로, html의 block content에 적용하려면, 복사해서 넣어주는 코드를 {% block content %}와 {% endblock %}사이에 넣어줘야 함
  • 마지막으로, WYSIWYG를 적용하는 과정에서 , html태그가 같이 출력될 수 있음. 따라서, WYSIWYG가 적용되는 html에 다음과 같이 | safe를 추가해야함
{{ target_article.content | safe }}

 

구글아이콘 사용

  • 구글아이콘 사용
  • 깃헙에서 link를 긁어와서 헤드에 넣어줌 (깃헙주소 : https://github.com/google/material-design-icons)
  • 그다음 구글아이콘 홈페이지 들어가서, 원하는 아이콘을 클릭하면 옆에 코드가 나옴
  • (구글 아이콘 홈페이지 : https://fonts.google.com/icons?selected=Material+Icons)

 

장고 메세지 태그조정

MESSAGE_TAGS = {
    messages.ERROR: 'danger',
}#Error메세지의 태그를 'danger'로 수정
  • 이렇게 하면, ERROR메세지의 태그가 danger로 나옴

 

  • 그리고 나서, HTML에서 다음과 같이 활용함
{% for message in messages %}
<!--메세지가 있으면 출력함-->
<div class = "text-center">
    <div class = "btn btn-{{ message.tags }} rounded-pill px-5 ">
        {{ message }}
    </div>
</div>

{% endfor %}
  • message level이 success이면 , btn-success가 되어 파란색깔 메세지창이 나옴
  • message level이 danger이면 btn-danger가 되어, 붉은색깔 메세지창이나옴
  • 지렷다리..

 

Transaction

  • DB와 상호작용하는 여러가지 기능을 한번에 묶고, 그 기능들 중 하나라도 실행되지 않으면 전체를 취소
  • 즉 묶인 기능들중 하나라도 실패하는 것 없이, 모두가 정상적으로 수행될 수 있도록 하는 것임
    • ex) 'CreateLikeRecord가 기록' + 'AtricleLike를 1더함' 두가지를 Transaction으로 묶은 후, CreateLikeRecord는 기록되었으나  ArticleLike가 더해지지 않앗을 경우, CreateLikeRecord의 기록도 삭제됨

 

VPS(Virtual Private Server : 가상 사설 서버)

  • 실제 하드웨어들로 구성된 물리적서버(Physical Server)가 있고,  그 물리적서버로부터 일정 자원을 분배시켜놓은 걸 VPS(Virtual Private Server)라고 함
  • 공식 사이트 : https://my.vultr.com/
  • 당연히 물리적 서버전체를 빌리는 것보다, 소량으로 빌릴 수 있으므로 훨씬 저렴
  • 또 우리가 하는건, 그 위에 도커까지 설치되어 있는 사이트임
  • 가입후,
    • git bash에서 ssh root@ip주소
    • password입력
  • 하면 서버에 접속할 수 있음

 

도커의 기본개념

  • 도커는 이미지와 컨테이너라는 개념이 있음.
    • 그 둘은 마치 파이썬의 클래스와 인스턴스와 비슷함.
    • 즉, 만들어진 클래스에 기반하여 인스턴스를 찍어낼 수 있듯이, 한번 설치환경에 관련된 정보를 '이미지'로 저장해두면 , 그 설치환경에 관한 정보인 컨테이너를 필요시에 '이미지'에 기반하여 찍어낼 수 있는 것
  • 그럼 이미지는 어디서 가져오는가? 
    • 도커허브(Docker hub)[링크 : https://hub.docker.com/r/portainer/portainer-ce ]
    • 도커허브는 전세계에서, 모든 이미지를 올려둘 수 잇는 곳임. 거기에 올려두면, 반대로 원할때 내려받을 수 있음
    • 마치 오픈소스같은 개념임. 실제 유명한 기업들의 도커허브(설치환경)도 거기에 올려져있어서, 우리가 가져가서 수정도가능함

 

 

Docker : portainer.io

  • docker를 gui로 바꿔주는 곳. 즉 docker작업을 마우스로 클릭해서 할 수 있는 것. 우리는 이걸 사용해볼것임
  • 참고로, gui의 반대는 CLI임. 타이핑을 통해서 작업하는 것(Git Bash같은 느낌임)
  • 설치방법은 다음과 같음(선생님 강의를 최대한 따라갈 수 있게 함)
    • 도커 허브 홈페이지 접속(https://hub.docker.com/)
    • portainer를 검색하고 ce에 들어감. 참고로 ce는 무료인 community edition임
    • Getting started with portinaer CE 단락 > Deploy Portainer
  • 이 과정 부터는 맞는지 모르겠음. 일단 최대한 찾아보면서 해봤고, 무리없이 진행되긴 했음
    • Set up a new portinaer server installation > docker stand alone > install portainer with docker on linux
      • 리눅스인이유는, 서버를 우분투로 열었기 때문임
    • git bash에서 서버에 접속한 후,
    • Deploy ment의 첫줄 : docker volume create portainer_data 입력
    • 그 후 그밑에 있는 코드들 중 첫줄을 다음과 같이 수정해서 입력
    • docker run -d -p 9000:9000 --name portainer \ 입력. 그대로 긁어오면 안되고, 포트를 선생님 강의내용대로 9000으로 변경시켜줬음
    • 첫줄 입력한 후, 그다음 코드들을 그대로 긁어서 실행
  • 그 후 , 서버 'ip주소 : 9000'으로 접속하면 docker서버가 열림

 

Docker : 컨테이너 생성(생성해서 Nginx 설치)

  • 도커서버에서 container에 들어감
  • add container를 눌러서 추가컨테이너 생성
  • 다음과 같은 화면이 나옴

 

 
  • name은 그냥 원하는대로
  • image : 중요함. 만약 여기서 nginx라고 적어주면, 도커허브내에 있는 nginx기업의 image를 받아서 가져옴.
  • Manual network port publishing : 컨테이너와 호스트의 포트를 설정해줌. 필요에 따라 맞춰주는 작업이 필요함
  • 참고로 포트 80은 default같은 느낌임. http의 기본포트번호. 안적으면 80번 포트로 연결됨

 

Dockerfile

  • 도커파일은 다음과 같이 만들어야 함.
  • 선생님이 가르쳐준 부분이랑 좀 다른 부분이 있었음..

  • From python: 마치 extend같은 느낌. python 3.9.10버전을 가져오겠다는 뜻
  • Run git clone ~ : 해당 repo의 내용을 클로닝
  • WORKDIR ~ : cloneing해왔으니, pragmatic폴더가 있음. 그리로 이동
  • Run pip install~ : requirements. txt에 기록된 내용을 모두 인스톨
  • Run echo ~ : secretykey를 이걸로 해줌
  • Run python manage.py migrate ~ : manage.py migrate를 실행
  • EXPOSE : 포트설정
  • CMD : 자동 실행 명령어. 늘 실행됨

 

Gunicorn

  • 개발단계에서는 python manage.py runserver와 같은 명령어로 서버를 구동했으나, 이는 배포단계에서는 해서는 안되는 단계임
    • 장고공식에서도, 이를 금지함(보안 등의 문제로)
    • 그래서, runserver의 역할을 할 수 있는게 바로 gunicorn임.
    • 장고컨테이너 gunicorn이라는 라이브러리를 설치해줘서, 우리가 원래 쓰던 runserver라는 명령어 대신, gunicorn프로그램의 서버구동 명령어를 사용할 것임
  • 그럼 gunicorn이 정확히 뭐냐? 
    • nginx라는 웹서버와 우리가 만든 장고 컨테이너를 연결해주는 인터페이스라고 생각하면 됨
    • 일단은 약식으로, 그냥 runserver의 역할을 대체한다.. 정도로 생각하면 됨
  • gunicorn을 pip install해준 후, requirements에 그 내용을 반영 > dockerfile의 내용 수정
    • run pip install -r requirements.txt다음에, run pip install gunicorn 이부분을 추가해야 함. requirements에 gunicorn이 이미 반영되어있을 텐데, pip install gunicorn을 해주는 이유는, 캐쉬때문이라고 하는데.. 아직 정확히는 잘 모르겠음.
    • cmd부분을 다음과 같이 수정
RUN pip install -r requirements.txt
RUN pip install gunicorn

CMD ["gunicorn","pragmatic.wsgi","--bind","0.0.0.0:8000"]

 

Nginx의 필요성

  • 바로위의 gunicorn을 설치해주면, 잘 작동되는 것 같다가도 잘보면 static파일이 network에서 연결이 안되어 있음
  • 이는, 웹페이지의 내용이 static content(css)와 dynamic content(django 등)으로 나뉘는데, gunicorn과 django는 오로지 dynamic content를 처리하는 애들이기 때문(계발단계에서는 그냥 django에서 적용했으나 배포단계에선느 불가능하다는 듯)
  • 따라서, django와 gunicorn이 처리하지 못하는 static파일의 내용을 처리기 위해 사용되는게 nginx임
  • nginx가 static파일을 처리하도록 하기 위해서는 다음의 과정이 필요함
    • nginx컨테이너를 생성하고, django컨테이너와 dockernetwork를 사용해 연결
    • django 컨테이너 내부의 static파일들을 수집
    • 수집한 static파일을 nginx컨테이너와 동기화 시켜, nginx컨테이너에서 사용하고 처리할 수 있게 함
  • 바로밑의 내용보다 뒤쪽 강의(55강)내용이나, 내용흐름상 여기에 배치하는게 맞겠다 싶어서 여기에 배치함

 

Nginx / docker network

  • 54강(docker network의 이해 및 구현)강의를 한번 더 보는 것을 추천!
  • 사용자가 nginx 컨테이너 포트로 요청을 보내면, nginx포트에서 다시 django 컨테이너로 요청을 보내야함(그래야 연결이 되니까). 근데 nginx 컨테이너에서 어떻게 django 컨테이너로 요청을 보낼까?
    • 이걸 해결해주는, 즉 두 컨테이너를 연결해주는 도구가 dockernetwork임
    • 설정된 dockernetwork내에 있는 컨테이너끼리는 이름을 통해 마치 url처럼 접근이 가능하다
    • ex)django 컨테이너의 이름이 django_container_gunicorn 라고 하면, 'http:// django_container_gunicorn:8000'를 통해 접근이 가능함

 

  • 제일먼저 할 것은 network생성. 그냥 별다른 설정없이 이름만 직관적으로 지어주면됨
  • 그후에 , django conatiner생성. 이떄 방금 만든 network를 선택해줌

 

  • 우선, nginx파일을 이어주기 위해, nginx.conf파일을 만들어야 함
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;
    }
  }
}
  • 밑의 내용이 정확히 어떤 내용인지는 잘모르겠음..
  • 다만, porxy_pass부분은 장고 컨테이너의 이름과 포트를 적어주는 것. 앞에서 말한대로, 이름을 통해 url처럼 접근할 수 있게 해주는 것임
  • 그외의 내용은 거의다 gunicorn의 공식 홈피의 내용을 거의 바탕으로 함(https://gunicorn.org/#deployment)

 

  • 그 후, filezila를 통해 nginx.conf파일을 우리가 개설한 서버에 넣어줘야 함
  • 파일질라를 열면 다음과 같은 화면이 나옴

  • 벌처에서 개설한 서버 ip / 사용자명(root)  / 비번을 넣고 포트에 22를 넣어주면 , 가상서버와 연결이 됨
  • 적당한 위치에 위에서 만든 nginx.conf파일을 넣어줌

 

  • 그 후, niginx컨테이너를 만들어줘야함. 우리가 정한대로 포트 설정해주고(일단 수업에선 host와  container모두 80으로 해줫음)
  • network에서 미리만들어준 , 장고와 nginx를 이어주는 네트워크 설정
  • volume > volumemapping > container를 bind로 설정을 바꿔줌
  • container에 '/etc/nginx/nginx.conf'를 기입. host에 서버에 넣어준 'nginx.conf의 경로/nginx.conf' 를 해줌. 이게 컨테이너 안의 내용과 호스트를 연결해주는 것임

 

  • (추가)entity too large에러가 발생
    • 올리는 파일의 용량이 너무크면, 그걸 제한하는 기능이 있음(서버테러를 막기 위한다고 함)
    • http위치에 'client_max_body_size 100M;' 추가
http{
client_max_body_size 100M;
....
}

 

static파일 모으기

  • 개쉬움. 그냥 터미널에 'python manage.py collectstatic'를 입력해주면 , setting.py에 입력해놓은 static_root위치에 모든 static파일이 저장됨
  • 이걸 컨테이너내에서 실행해줘야 하므로, dockerfile에 해당 내용을 적고 새로 docker 의 image를 만들어 주면 됨
RUN python manage.py collectstatic

 

Docker Volume : static 파일 > nginx로 동기화

  • 58강 내용 참조!
  • 모은 static파일을 nginx로 동기화하기 위해서 dockervolume기능을 사용
  • docker volume은 다른 컨테이너 간에 있는 데이터를 공유할 수 있는 기능
  • 두가지 Volume이 있음 : Bind Volume , Named Volume
  • Bind Volume
    • 호스트 서버에서 파일이나 경로(폴더)를 컨테이너 내부의 파일로 동기화 시켜줘서 , 컨테이너 내부에서 사용할 수 있게 함
    • 바로위에서 , nginx 컨테이너를 구동할때 nginx.conf파일을 이러한 방식으로 동기화하엿음
  • Named volume
    • 도커 내부에서 , 새로운 볼륨(공간)을 만듬. 그리고 이 볼륨을 여러 컨테이너에 부착시켜서 동기화시킴
    • 동기화된 볼륨은 어떤 컨테이너에서든지 사용이 가능
    • Named Volume은 다른 컨테이너들이 사라져도 사라지지 않음(도커 내부에서 관리되므로). 즉 데이터가 유지가 됨
    • 우리는 static파일과 media파일을 named volume형식으로 관리할 것임
  • 결론적으로 다음과 같은 작업을 해야함
    • nginx / django container 생성시, named Volume과 동기화
    • nginx.conf파일의 수정 
include mime.types;

location /static/ {
    alias /data/static/;
}

location /media/ {
    alias /data/media/;
}
  • nginx.conf파일의 중간에 다음과 같은 내용이 추가됨
  • location부분은 컨테이너와 named volume을 연결하기 위함
  • mime.type는 서버의 통신과 관련된 부분이라고 선생님이 말씀하심

 

Maria DB

  • 장고 컨테이너가 꺼져도, db의 내용이 보존되도록 db 컨테이너를 꺼내서 데이터를 따로 보존
  • 이때 db에 volume을 붙여서 , 데이터가 유지되도록 함
  • 사실 bind을 활용해서 장고내의 db sqlite를 사용시 데이터가 보존되긴 함. 근데 Maria db를 사용하는 이유는 성능적인 부분 + 좀 있어보이기 때문 이라고 함ㅋㅋ(by 선생님)
  • 참고로 mariaDB는 mysql에서 부터 뻗어져 나온 것임. 즉 db엔진자체가 mysql임
  • Maria DB 컨테이너를 만들때는 환경변수를 설정해줘야 한다.
# docker run 할 때, -d는 detach, -p는 외부 연결 포트지정, -v 는 디렉토리 연결, -e는 환경변수 지정이다. docker run -d \ # detach 옵션, 백그라운드에서 돌린다! 
-e MARIADB_ROOT_PASSWORD=root_secret \ # 환경변수 지정, 루트 계정 비밀번호 
-e MARIADB_DATABASE=test \ # 환경변수 지정, 디폴트 생성 데이터베이스 이름 
-e MARIADB_USER=test_user \ # 환경변수 지정, 계정 
-e MARIADB_PASSWORD=test_password \ # 환경변수 지정, 계정 비밀번호 
-v $(PWD)/conf.d:/etc/mysql/conf.d \ # volume 설정, 현재 디렉토리의 conf.d 디렉토리와 도커 내부의 /etc/mysql/conf.d 경로를 연결(my.cnf 또는 custom-config.cnf 파일로 기본 언어 설정 utf8mb4로 설정이 가능하다.) 
-v $(PWD)/db:/var/lib/mysql \ # volume 설정, 현재 디렉토리의 db 디렉토리와 도커 내부의 /var/lib/mysql 경로를 연결(데이터베이스 데이터가 있으면 연결 가능) 
-p 3306:3306 \ # 포트설정, 컴퓨터의 3306과 도커의 3306 연결, localhost:3306으로 접속하면 도커의 3306으로 접속 mariadb # 이미지 이름
  • 위의 코드들 중, 위의 네가지 환경변수는 docker container 생성시 env에서 직접 적어줘야 함.
  • 또한 settings.py(나중에 deploy.py)에서 db와 관련된 설정값과 일치시켜줘야함. 
    • MYSQL_ROOT_PASSWORD
    • MYSQL_DATABASE
    • MYSQL_USER
    • MYSQL_PASSWORD
  • 나머지는 구동할때 설정할 수 있는 듯 함.
  • mariadb컨테이너 만들때 포트입력해줄 것!!!

 

개발, 배포환경 분리

  • 이제 배포를 하기때문에, 개발과정에서 혼자 돌려보는 환경(local)과 배포할때의 환경(deploy)을 구분해줘야 할 필요가 있음
  • settings. py를 다음과 같이 나눔
    • base.py : local과 deploy모두에서 import해서 사용할, 공통적인 부분
    • local.py : 개발환경
    • deploy.py : 배포환경. db와 관련된 설정을 수정해주고, 해당 파일의 debug값을 false로 수정(debug가 true이면는 현재 개발중인 상태를 의미)
  • 그외에 수정해준 부분
    • base_dir값을 수정해줬음. settings.py가 base.py로 이름이 바뀌고, 원래 있던 위치에서 한단계더 하위 폴더로 내려가면서, base_dir값을 수정해주었음(끝에 .parent를 하나 더 붙여줌)
    • manage.py에서 os.environ.setdefault부분을 다음과 같이 수정. 이 부분은 local환경에서의 settings.py의 위치를 두번째 인자로 받음
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pragmatic.settings.local')

 

 

컨테이너 만들때 주의사항

  • 필요없는 이미지 삭제
  • 잘안된다 싶으면 컨테이너들 재시작(가동순서로 인해 오류가 발생할 수 있음)>오타수정 > 볼륨이랑 이미지 새로만들어서 컨테이너 생성

 

컨테이너의 한계/보완

1)설정을 계속 반복적으로 해줘야 함

  • 매번마다 nginx, django, mariadb의 볼륨 네트워크 포트 이런거 모두 세팅을 반복적으로 해줘야 함. 매우 귀찮
  • 컨테이너에 들어가는 세팅을 하나의 파일로 만들어 놓음(Total stack setting)
  • 그럼 우리가 따로 설정안해줘도, 이젠 컨테이너들이 알아서 돌아감
  • 이런 컨테이너들의 setting을 Docker stack 이라고 함
  • 번외 : Docker compose라고 비슷한 역할을 하는게 있긴 한데, 그건 개발단계에서 편할려고 사용하는 도구정도의 느낌임

 

2)컨테이너가 꺼졌을 때(다운 되었을 때)

  • 예를 들어 장고 컨테이너가 다운되었을 때 > 컨테이너를 리부트(reboot)해줘야 함. 근데 우리가 늘 붙어있을 수도 없음. 그러면 어캄?
  • 그래서 컨테이너를 서비스라는 개념으로 관리
  • 만약 컨테이너가 꺼졌을 때, 자동으로 컨테이너를 reboot시켜주는 것이 가능함
  • 그외에도, 동일한 컨테이너를 복사해서 필요에따라 처리능력 같은걸 올려줄 수도 있음
  • 이러한 서비스에 관련된 부분도, Total stack setting에 같이 구현해줄 것임

 

3) YML파일

  • 바로 위에서 언급한 total stack settings를 yml파일로 만들어줌

 

도커 SWARM

1) 노드와 클러스터링

  • 노드 = 가상서버(벌처에서 빌린 것)하나
  • 이 노드가 여러개 있을 수 있음. 여러가지 서버를 하나의 서버인 것처럼 묶어주는 걸 'clustering' 이라고 함
  • 그리고 이 클러스터링을 통해 여러개의 노드를 관리할 수 있도록 해주는 것이 docker swarm임

 

2) docker swarm 

  • 위에서, 컨테이너가 꺼졌을 때를 대비해 서비스형태로 관리하겠다고 했는데, 이처럼 여러가지 컨테이너를 서비스내에서 관리하는 것을 container ochestration이라고 함.
  • container ochestration을 위한 툴로써, docker swarm / kubernetics / apache 등이 있음
  • 그리고 이중에서 dockerswarm이 가장 쉬움(핵심적인 부분만으로 설정하여 쉽게 만들 수 있도록 되어있음)

 

도커 SWARM 구동

1) swarm 설치

  • git bash등으로 서버접속 후, 적당히 cd를 이동한 다음 docker swarm init을 입력.
  • cd이동하기전에 먼저 .. 쳐주는 것 주의
  • 이렇게 하도 portainor.io에 가보면 swarm / secret / service가 추가되어 있음

 

2) yml파일 생성

  • 그동안 했던, 각 컨테이너의 생성에 관한 정보를 담아서 yml파일을 만들어줌
  • 만들어주는 과정에서, 라인에 주의해야 함
  • volume이나 network같은 경우는 미리 만들어 놓지 않아도, 맨밑에 network , volume에서 명시해주면 알아서 생성이 됨
version: "3.7"
services:
  nginx:
    image: nginx:1.21.6
    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_image
    networks:
      - network
    volumes:
      - static-volume:/pragmatic/staticfiles/
      - media-volume:/pragmatic/media/

  mariadb:
    image: mariadb:10.7
    networks:
      - network
    volumes:
      - maria-database:/var/lib/mysql
    environment:
      MARIADB_ROOT_PASSWORD: password1234
      MARIADB_DATABASE: django
      MARIADB_USER: django
      MARIADB_PASSWORD: password1234


networks:
  network:

volumes:
  static-volume:
  media-volume:
  maria-database:

 

3) stack 생성

  • 그동안 했던, 각 컨테이너의 생성에 관한 정보를 담아서 yml파일을 만들어줌
  • 'stack > add stack > upload > yml파일' 해주면 그냥 스택과 컨테이너가 생성됨
  • 컨테이너가 생성되었을 때, 구동이 되지 않는 컨테이너들이 함께 생성될 수 있음.
    • 이는, 컨테이들이 구동순서가 필요하기 때문
    • 예를 들어, 장고 컨테이너는 마리아db컨테이너가 있어야만 존재가 가능함. 근데 마리아 db보다 장고컨테이너가 먼저 생성되어 버리면, 걔는 구동될 수가 없어서 만들어진 채로 멈춰져 있게 됨. 그 후 마리아 db가 구동되고 나서 장고컨테이너가 다시 생성되면, 생성된 그 컨테이너는 구동이 되어서 running상태가 되는 것임.

 

Docker secret을 이용한 보안 (77강(66강) 참고)

1)Docker secret의 개념과 필요성

  • 장고의 sercet_key나 mariadb의 rootpassword등 보안과 관련된 정보를 파일에 적어두면 보안상 위험할 수 있음
  • 따라서 파일이 아니라 Docker 시스템 내부에서 그러한 정보를 관리하면서 필요에 따라 서비스에서 정보를 활용할 수 있도록 함
  • 자세히 말하면, secreykey가 각 서비스에 관여를 한다고 보면 됨

 

2)Docker secret의 생성

  • portainer에서는 그냥 secrets에 Name(이름)과 Secret(내용)을 입력하면 만들어짐
  • 만들어진 secrets는 해당 이름과 내용을 가진 파일형태로 , '/run/secrets/'경로에 저장이 됨.
  • 프로젝트 중에서는, django_secret_key와 mariadb_root_password , mariadb_password를 만들어 줬음

 

3)Docker secret의 제공

  • 각 컨테이너에서 해당 secret을 참고할 수 있도록 제공해줘야 함
  • 그냥 yml파일에 volume이랑 network처럼 제공해주면 됨.

 

  • 마리아 db컨테이너에서는 다음과 같이 하면 됨(참고 공식 사이트 :https://hub.docker.com/_/mariadb 에 있는 Docker secret 부분 참고)
    • yml파일에 어떤 secret이 있는지 적어주고
    • 앞서 언급했 듯, secret은 파일 형태로 제공이 되므로, 해당 컨테이너에 참조할 secret의 경로를 적어주면 됨
    • docker secret을 참조하는 환경변수는 끝에 반드시 _FILE을 붙여줘야 함
secrets:
    - MARIADB_ROOT_PASSWORD
    - MARIADB_PASSWORD
environment:
    MARIADB_DATABASE: django
    MARIADB_USER: django
    MARIADB_PASSWORD_FILE: /run/secrets/MARIADB_PASSWORD
    MARIADB_ROOT_PASSWORD_FILE: /run/secrets/MARIADB_ROOT_PASSWORD

 

  • 장고 컨테이너
    • 장고 에서는 django_secret_key와 mariadb접속을 위한 mariadb_password를 secret으로 제공
secrets:
  - MARIADB_PASSWORD
  - DJANGO_SECRET_KEY
    • 그리고 deploy.py에서 env와 관련된 설정을 변경해줌. 특정경로에 있는 파일의 내용을 읽는 함수를 만들고, 그 함수를 활용해서 Docker secret으로 저장한 django_key를 읽을 수 있도록 함
#secret_key를 읽는 함수
def read_secret(secret_name):
    file = open('/run/secrets/' + secret_name)
    #docker에서 설정한 secret들은 여기에 파일형태로 저장이 됨
    secret = file.read()
    secret = secret.rstrip().lstrip()
    #내용이 공백이나 이런게 많아서 정리를 해줌
    file.close()
    return secret

#secret_key 제공
SECRET_KEY = read_secret('DJANGO_SECRET_KEY')
  • 마지막으로 dockerfile에서 collectstatic을 맨마지막 작업으로 미뤄줌.
    • 원래는 EXPOSE 8000보다 앞에 있었는데, 이게 가능한 이유는 원래 dockerfile에서 env파일의 secret_key값을 먼저 제공해줬기 때문임. 근데 이제 secrek_key로 따로 뺴주니까, 기능이 구동이 안될 수도 있다함. 그래서 맨 마지막 단계(CMD)로 빼줌
    • --noinput이라는 구문을 추가. collectstatic이 css같은걸 모아서 nginx에 보내주기 위한 건데, 이때 모으는 과정에서 '진짜 모을까요?'뭐 이딴걸 물어볼 떄가 있다함. 그걸방지하기 위해 넣어줌

 

  • yml파일의 마지막에 secret들에, external:true 라는 구문을 달아줘야 함.(외부 컨테이너로 송출가능하다고 표시해주는 것)

 

AWS

1) vultr와 비교

  • 벌쳐에서 서버를 사던걸, aws에서 그냥 구현하는 것임
  • 다만, id/pw를 통해 서버에 접속하던 벌쳐와 달리 aws에서는 키(파일)을 통해서 서버에 접속
  • 그외에도, 벌쳐와 달리 도커가 깔려있지 않으므로 설치가 필요 + aws는 방화벽에 의해 포트가 닫혀있으므로 필요에 따라 포트를 열어줘야 함

 

2) 키페어 생성

  • 서비스 > 컴퓨팅 > EC2접속
  • 네트워크 및 보안 > 키페어 생성
  • RSA(window환경에서 사용가능) / .pem으로 생성

 

3) 인스턴스 생성

  • 인스턴스 > 인스턴스 시작
  • Ubuntu Server 20.04 프리티어 사용가능 선택 > t2(프리티어사용가능) 선택 > 다음:인스턴스 세부정보 구성 > 다음 > 다음:스토리지 추가(여기서 스토리지 용량 변경 가능) > 다음:태그추가
  • 여기서 name , webserver를 key와 value로 줬음 > 다음:보안그룹추가(방화벽과 포트에 관함) > 검토 및 시작 > 기존 키페어 사용

 

4) 인스턴스 접속

  • git bash등에서 키 파일이 있는 곳으로 이동
  • 'ssh -i 키파일이름 ubuntu@인스턴스의 퍼블릭 IPv4주소'입력
  • 키파일 이름에는 .pem도 포함되어야 함
  • 접속완료

 

AWS:docer 설치

1)도커가 없음을 확인

  • 벌쳐와 다르게 docker가 설치되어 있지 않음

  • 참고로, sudo는 관리자 권한을 주는? 그런거임.

 

2)도커설치

  • 공식문서 : https://docs.docker.com/engine/install/ubuntu/
  • 공식문서의 내용에 따라, Set up the reprository부분에 있는 코드와 Install docker Engine에 있는 코드를 그냥 다 입력해줌(모르겠으면, 보강3강(79강)을 참고하는 것을 추천)
  • 그럼 설치완료됨.

 

3)portainer 설치

  • 공식문서 : https://docs.portainer.io/v/ce-2.11/start/install/server/docker/linux
  • 해당 문서의 내용에 따라 코드를 쳐나가면 됨. 중요한 것은, ubuntu로 접속했기 때문에, 각 명령어의 앞에 'sudo'를 붙여줘야 함
  • 또, 앞에서 했던 것처럼, 8000번 포트에 관한 부분은 지우고, 9443 > 9000으로 수정
  • 마지막으로 방화벽을 켜줘야함(벌쳐와의 차이점).
  • 인스턴스 > 보안 > 보안그룹 > 인바운드 규칙 편집에 들어가면, 포트를 열어줄 수 있음. 참고로 소스를 0.0.0.0/0이라고 설정하면 누구나 들어올 수 있게 해주는 것임
  • 이제 그러면 , 9000으로 접속 가능

 

4)마지막으로 docker swarm설치

  • git bash에서 해당 서버에 접속한 채로 sudo docker swarm init
  • 좀 기다리면 portainer.io에서 swarm이 반영되어 있음.(좀 시간이 걸릴수도 있음. 나도 한 10분걸림)

 

aws기반 stack 배포(벌쳐서버와 비교하여, aws서버에 배포하면서 수정되는 점)

1)secret_key를 새로 만들어줌

2)nginx.conf 파일 전송

  • filezila로 파일을 보내는 것은 똑같음. 다만 서버에 접속하는 방식이 다름(aws는 ip/pw로 접속하는게 아니다보니까)
  • filezila > file > 사이트관리자로 들어간 후, 다음과 같이 설정

  • 사용자이름을 바꿔주고, 키파일을 찾을때 파일유형을 pem으로 해야 파일이 보임

 

  • 접속 후, 디렉토리를 만들어줄 때 에러가 발생함. 왜냐하면 root가 아니라 ubuntu유저로 접속을 해서 권한이 없음
  • 그래서 git bash로 접속해서 sudo를 사용해서 해야함.

  • sudo를 사용하니까 폴더가 만들어진 모습

 

  • 근데, 여전히 filezila에서 nginx.conf파일이 이동되지가 않음. 역시나 권한 문제 때문
  • 따라서, 다음과 같은 chmod 777을 사용해서 권한을 줘야 함

  • django_course라는 경로에서, 현재 유저(ubuntu)에게 권한을 제공하는 것

 

  • 그러고나서 nginx.conf파일 전송

 

  • 인스턴스 > 보안그룹에서 포트를 열어줘야 함

 

도메인 구매 및 구현

1)개요

  • gabia라는 업체에서 도메인을 구매했음(어디서든지 상관없긴함)
  • aws의 route53에서 구매한 도메인을 연결
  • route53을 다시 aws의 ec2와 연결

 

2)네임서버-도메인 연결

  • aws > 네트워킹 및 콘텐츠 전송> route53 > dns관리 > 호스팅 영역 생성 > 도메인주소 > 호스팅 영역 주소 생성
  • 하면 두종류의 레코드가 나오는데, 그 중 우리는 ns에 집중해야 함.
  • ns유형의 레코드의 '값/트래픽 라우팅 대상'이 route53과 gabia의 도메인을 이어주는 네임서버
  • 가비아에서 해당 도메인의 네임서버 설정에 들어가서, 방금 확인한 네임서버를 입력(입력할 때, 마지막에 . 을 빼야 함!!)

 

3)route53과 ec2인스턴스 연결

  • route53 > 호스팅영역 > 사이트 클릭 > 레코드 생성
  • 값에 EC2인스턴스의 ip주소를 넣음
  • 레코드이름에 'www'를 넣어줌. 안넣어줘도 될줄 알았는데, 자동으로 www가 생성이되어서 넣어주는게 좋을 듯

 

 

AWS : HTTPS 설정

1)https

  • 일반 http 프로토콜에서는 수많은 서버를 거치면서 목표하는 곳에 도달하는 과정에서, 누군가(혹은 거쳐가는 서버에서) 그 내용을 들여다 볼 수 있음
  • https는 이를 막기 위해, 메세지에 암호화 알고리즘을 적용하여 내용을 쉽게 알아볼 수 없게 함

 

2)aws : load balancer

  • 요즘은 서버의 처리량이 많아지면 그 서버를 여러개 두는게 추세임. 이때 각 서버에 부담(처리량)을 잘 분배해주는 시스템을 load balancer라고 함. 
  • 근데 aws에서 이 loadbalancer를 만들어야 https를 설정해줄 수 있음

 

3)aws : load balancer 생성

  • 로드밸런서를 생성하기전에 aws 우측상단에 위치를 서울로 설정되어있는지 확인이 필요함. 서울로 해야함.. 나는 버지니아 북부로 해놔가지고.. 인스턴스도 심지어 거기에 생김..
  • 인증서 발급
    • 인증서가 필요. 없다면 미리 만들고 오는 것을 추천. 다음 사이트를 참고. 
      • https://developer111.tistory.com/21
      • 인증서를 적는과정에서 도메인을 적으라고 하는데, '*.도메인.com' 와같은 식으로 적어줘야 함. 그래야 앞에 뭐가오든 그 인증서가 적용이 됨
      • 이름을 적으면 일단 인증서가 생성됨. 근데 '확인중' 대충 이런 느낌으로 대기중인 상태임
      • 클릭해서 들어가면 route53에서 레코드 생성이 있음. 그거하고 기다리면 됨.
    • 인증서 발급에는 시간이 좀 걸릴 수 잇음(한 10-20분정도. 최대 30분이라고 나와있음)

 

  • 인스턴스 > 로드 밸런싱 > 로드 밸런서 > 로드 밸런서 생성 > application load balancer
  • Basic configuration
    • 이름적어주고 / scheme : internet-facing / ip adrress type : ipv4 /
  •  네트워크 매핑
    • mappings에 있는 것 전부 클릭
  • 보안그룹(security groups)
    • 로드밸런서를 적용하려는 인스턴스와 동일한 보안그룹을 선택해줘야 함
    • 인스턴스 > 보안그룹에서 인스턴스의 보안그룹을 확인가능
  •  listeners and routings
    • https 추가(자동으로 443이 선택됨)
    • target group을 만들어야 함. Create targetgroup 눌러준 후, 'targettpye > instance // targetgroupname 작성  // 프로토콜은 http, 포트 80' 해주고 나머지는 그대로해서 target그룹을 만들어줌
    • 만들어준 타겟그룹을 각 리스너의 대상으로 넣어줌
  • securel listerner setting
    • 쉬움. 걍아까만든 certificate넣어주면 됨

 

 

4)레코드 생성

  • route53의 도메인에 들어가서, https를 연결하는 새로운 도메인이 필요함. 레코드 생성 들어가서
  • 별칭옵션을 열어주면 트래픽 라우팅 대상이라는 칸이 열림
    • application/classic load balancer 선택 > 지역(내 인스턴스와 로드벨런스가 있는 지역) 선택 > 로드벨런서 선택
    • 레코드이름에 www. 붙일것

 

5)보안 그룹 편집

  • 마지막으로 443번 포트를 열어줘야 함(https는 443번 포트를 사용)
  • 인스턴스 > 보안 그룹 > 인바운드 규칙 편집 > 443번추가

 

6)http에서 https리디렉션

  • http로 접속시 https로 자동 리디렉션이 되게 해줘야함
  • 로드벨런서 > 리스너 > http:80선택 > 편집 > default action에 기본으로 있던거 remove해주고 redirect추가 > 그럼 프로토콜을 선택하라고 나오는데(https가 선택되어 있음) 포트에 443을 적어줌

 

7)503 Service Temporarily Unavailable

  • 근데 이까지 해도.. 계속해서 503 service temporarily unavailable이 뜸..ㅠㅠ 진짜해결하는데 오래걸렷다
  • 대상그룹이 없어서 발생하는 문제임
  • 인스턴스 > 로드밸런싱 > 대상그룹 > 로드밸런서에 적용하려는 대상그룹선택
  • 밑에 Targets > register targets > availabe instance에서 맞는 대상 선택 > include as pending below하면 밑으로 내려감 > register pending targets
  • 그러면 끝!!!

 

8)csrf verification failed Error

  • 후.. 또 이글을 수정할 줄 몰랐는데, aws의 alb를 통해 https로 접근할 경우, csrf 관련에러가 발생한다

  • 처음에는 aws의 문제인줄 알았으나, django의 버전이 변하면서 , 장고가 https를 사용할때 추가설정을 해주지 않으면 csrf에러가 발생하는 것 같다.
  • deploy.py(settings.py)에 다음과 같은 구문을 추가
CSRF_TRUSTED_ORIGINS = ['https://www.humblebee.site']

SESSION_COOKIE_SECURE = True
  • 두 구문중 첫번째 구문은 필수. 두번째 구문도 csrf에러를 해결하는데 필요할 수도 있는 듯함
  • 일단 나는 둘다 추가하니까 문제가 해결되었음
  • 첫번재 구문에 들어가는 주소는, 구매한 도메인주소임. 나는 마지막에 /를 써줘서 에러가 났었는데, 딱 저대로 써줘야 함(마지막에 /를 붙여주면 안됨!!)
  • 참고 : https://stackoverflow.com/questions/38841109/csrf-validation-does-not-work-on-django-using-https

BELATED ARTICLES

more