본문 바로가기
Always Awake/피로그래밍 12기(19.12.31~20.02.22)

로그쉐어 프로젝트 및 피로그래밍 12기 8주차 활동 정리(20.02.17~20.02.22)

by 욕심많은알파카 2020. 2. 25.

목요일(02.20)

AWS 배포하기 by 박정욱 선배님

 

-영문으로 된 도메인 사기

인터넷에서 도메인 쳐서 아무 사이트나 들어가서 사도 되지만, AWS Route 53(https://aws.amazon.com/ko/route53/)이라는 사이트도 있다.

 

-배포 준비

git branch에 따라서 실 서버에 배포할 때 사용될 settings와 requirements들을 분리하여 준비하여야한다.

개발환경, 배포환경, 로컬환경 등에서 사용되는 settings가 모두 각각 다르기때문에.

 

이때는 정확히 이해하지 못했고 배포할 생각도 없었지만, 결국 AWS배포를 시도하게 되면서 이 말에 대해서 이해하게 되었다.

 

-배포 이론

request를 보내면 어떻게 response가 오느냐?

서버에 요청을 보내면 NGINX에서 리퀘스트를 받고, uWSGI에서 django로 보낸다.(uWSGI가 django로 연결할 수 있게 세팅해놔야겠지? - 노션 5.uWSGI 설정 참조)

NGINX를 설치해야하고, uWSGI도 설치해서, 우리 로컬 또는 깃의 django와 연결해야한다.

NGINX는 “어떤 주소로 요청이 오면 이걸 처리해줄까?”를 설정해놓는다. (즉, 도메인orIP를 설정) 또, static파일의 location을 django가 처리해주지 않기 때문에 그것도 설정해놓는다(collectstatic).

NGINX에 처리가 왔을때 uWSGI에서 각 프로젝트(장고든 뭐든)에 연결해준다. 한 EC2에 굉장히 많은 프로젝트를 넣어놓고, 요청에 따라 각각 다른프로젝트로 연결되게 할 수 도있다.

 

-ssh

쉘(터미널)에 접속할 때 사용하는 프로토콜. 웹에 접속할 때 사용하는 프로토콜이 HTTP, HTTPS인것처럼.

ssh의 IP값을 보안강화하려면 사용자지정 위치로 잡아서 특정 위치에서만 접속 가능하게 한다던가…. 할수 있다.

 

-탄력적 IP

인스턴스에 잡혀있는 IP는 언제든 바뀔 수 있으므로(만약 한달 뒤에 접속이 안된다면, IP가 바뀐 것일 것이다),

특정 IP하나를 고정적으로 받아서 사용하기 위해서 탄력적 IP를 할당받는다.

 

-PrivateKey 생성시

윈도우는 puttyGen을 이용하고, 맥은 chmod 400을 이용하여 키를 허용한 뒤 ssh명령어를 통하여 쉘에 접속한다.

--> chmod 400 [pem키(경로포함)]

 

-ssh:명령어 축약

python은 안되고 python3로 적어야 하기 때문에 alias를 통해 명령어를 커스텀해서 바꿀수있다.

 

-rds

mysql같은거 쓰고있다면 RDS로 바꿔주면 된다. AWS에서 지원하는 DB서비스. 실배포할때는 RDS를써보자.

서버에서 특정 DB를 손쉽게 생성하고 관리해준다. 아이디와 관리자명 정도만 해주면 된다.

 

-uwsgi 경로설정

base는 manage.py가 있는곳

home은 venv가 있는곳

chdir은 base위치로 가줘야한다.

module은 base폴더로 chdir한뒤 프로젝트폴더(settings.py가 있던 그곳)의 uwsgi파일을 바라보게 해야한다.

 

배포의 마지막까지 나를 괴롭혔던 경로설정... 각 프로젝트마다 파일 위치가 다 너무 다르고 경로가 달라서, 그리고 로그를 찍어보는 방식도 익숙치 않아서 힘들었다(journalctl을 몇번 비웠는지 모르겠다). static파일의 경우는 구상이형이 결국에 도와줘서 제대로 경로를 잡았는데, 진짜 다음번에 보면 뽀뽀해줘야겠다.

 

-emperor

하나의 uWSGI가 여러개의 프로젝트를 관리할 수 있도록 설정해주는 것. 근데 프로젝트가 하나뿐이라면 설정안해놔도 별 상관은 없다.

 

 

 

팀프로젝트 로그쉐어 3주차

2주차에 생각보다 프론트의 많은 부분이 마무리되지 않았기 때문에, 3주차에는 프론트를 열심히 다듬고 마지막 핵심기능 몇개를 추가하는데에 집중했다. 짧은 기간때문인지 하루이틀만에 만든 템플릿이나 뷰에서 문제가 생기는 경우가 있었다. 따라서 묵혀두었던 버그들을 최대한 디버깅하려고 노력했다.

 

2주차에는 하루를 쉬었었지만, 3주차에는 발표기간이 얼마 남지 않았기 때문에 매일같이 모여서 프로젝트를 진행했다. 심지어 목요일에는 서울대입구역 앞 24시 카페에서 밤을 새기도 했다. 체력적으로는 힘들었지만, 그럼에도 불구하고 정신적으로 너무 즐거웠다.

 

3주차에 내가 접한 기술과제들은 이랬다.

 

1. 최근사용태그(recent tags)

 

로그 쉐어에서 개인의 프로필은 곧 타인이 보는 그 사람의 이력서이기도 하기때문에, 그 사람이 어떤 종류의 활동기록을 남기는지에 대해 한눈에 확인할 수 있는 지표가 필요했다. 최근 사용 태그는 그런 점에서 최근 올라온 게시글들의 태그를 싹 긁어와 보여주는(그리고 많이 쓴 태그의 경우 더 큰 사이즈의 폰트로 보여주는) 기능이었다.

 

@login_required
def profile_detail(request, pk):
    # 본인 or 관련 그룹원
    try:
        ans = False
        user = User.objects.get(pk=pk)  # 프로필의 user
        # 들어가려는 프로필이 본인의 프로필이 아니면서(타인의 프로필이면서) 프로필 user가 request.user의 그룹에 포함되지 않다면
        if request.user.id != user.pk:
            for group in request.user.user_groups.all():
                if user in group.members.all():
                    ans = True
                    break

            if ans == False:
                raise NotImplementedError

    except NotImplementedError:
        return render(request, 'http404.html')

    else:
        profile = user.user_profile  # user -> profile

        # 최근 태그 리스트
        post_list = user.user_post.order_by('-start_date', '-end_date')# 시작일 기준(만약 같다면 종료일 기준)으로 최근 게시물부터 태그가 들어가게 한다.
        recent_post_list = post_list[:10]
        # 아래 딕셔너리는 orderdDict여야 하므로 파이썬 3.6 버전 이상에서만 제대로 동작하는 코드.
        recent_tags_count = {}
        for post in recent_post_list:
            for tag in post.tags.all():
                if tag in recent_tags_count:
                    recent_tags_count[tag] += 1
                else:
                    recent_tags_count[tag] = 1

        # myprofile:profile_detail과 post:myprofile_post_list가 연동된 상태
        # myprofile:profile_detail 내 post:myprofile_post_list가 처음 화면
        # 스크롤을 내리면 post:myprofile_post_list_ajax 실행
        paginator = Paginator(post_list, 2)  # 한페이지에 담길 포스트 갯수
        page = request.GET.get('page')  # 첫 화면의 페이지(GET)

        try:
            posts = paginator.page(page)  # page(): 몇번째 페이지 리턴
        except PageNotAnInteger:
            posts = paginator.page(1)
        except EmptyPage:
            posts = paginator.page(paginator.num_pages)  # num_pages : 총 페이지 수

        context = {
            'profile': profile,
            'posts': posts,
            'recent_tags_count': recent_tags_count,
        }

        return render(request, 'myprofile/profile_detail.html', context)

 

최근 태그의 구현은 유저의 포스트 중 시작일 기준으로 정렬된 최신 10개의 포스트를 기준으로 한다.

한 포스트에 최대 10개까지의 태그밖에 입력할 수 없도록 만들어놓았으므로(그 이상 입력하여도 DB에 저장되지 않고 삭제된다) 최근 태그 리스트에 뜨는 태그는 최대 100개까지이다(10X10).

 

굳이 각 태그들에 대하여 count를 주는 이유는, template단에서 count에 따라 태그의 크기를 조정하기 위함이다.

<div>
    최근 태그 리스트 :
        {% for tag_name, tag_count in recent_tags_count.items %}
            {% if tag_count >= 2 %} <!--숫자는 추후 조정-->
                <span style="font-size: x-large">{{ tag_name }}</span>
            {% else %}
                <span>{{ tag_name }}</span>
            {% endif %}
        {% endfor %}
</div>

받아온 recent_tags_count에서 키 값쌍에 따라서, 값이 2 이상인 태그들은 일반 폰트사이즈보다 더 큰 형식으로 출력된다.

따라서 유저는 한눈에 더 자주 사용하는 키워드를 볼 수있다. 이를 통해서 그 유저가 최근에 어떤 활동을 해왔는지 한눈에 보기 쉬울 것이다.

구현된 최근 태그 리스트

이미 구현된 태그기능에 조금 더 살을 붙여서 이런 기능을 보여줄 수 있어서 만들면서 즐거웠던 기능이었다.

 

 

2. 관심태그(interested tags)

 

최근 사용태그와 더불어 관심태그를 구현하고자 하였다.

관심태그란 유저가 프로필을 작성할 때 입력해두도록 하는 것으로, 해당 유저가 어느 분야 또는 활동에 관심이 있는지에 대해서 표현하는 역할을 한다. 이 기능을 통해 프로필을 열자마자 해당 유저의 관심사를 한눈에 파악할 수 있다.

 

원래 관심태그는 그냥 프로필 모델의 한 부분에 들어가는 charfield에 불과했는데, 한쪽에 두기에 너무 심심한 것 같아 컬러풀한 관심태그를 만들어보고자 했다.

 

<script type="text/javascript">
        var get_random_RGB = function(){
            var r = Math.floor((Math.random()*0xcc)+0x11).toString(16);
            var g = Math.floor((Math.random()*0xcc)+0x11).toString(16);
            var b = Math.floor((Math.random()*0xcc)+0x11).toString(16);
            return '#' + r + g + b;
        }
        $('.interested_tag').each(function(){
            $(this).css('background-color', get_random_RGB);
        })
<script>

분리된 각 태그별로 Css를 조정하여 background 컬러를 입히는 작업을 자바스크립트로 진행하였다. 덕분에 화면을 새로고침할 때마다 태그의 색깔이 바뀐다(컬러풀!).

 

원리는 간단하나 생각보다 레퍼런스가 다 옛날 것이라서 당황했다. php를 이용해 구현한 사람은 많은데 자바스크립트를 쓴 사람은 그다지 많지 않더라.

 

get_random_RGB함수는 각 태그에 대해서 16진수의 값들을 랜덤으로 뱉어낸다. R값, G값, B값을 모두 합쳐 css로 적용한다.

각 값에서 최소값을 11로, 최대값을 cc로 한 이유는, 관심태그가 너무 밝아지거나 어두워지면 잘 눈에 보이지 않기 때문이다.

 

덕분에 이렇게 귀여운 관심태그를 완성.

 

3. 타임라인 시각화

 

로그쉐어 1주차 기획부터 꼭 하고싶었으나 너무 어려울 것 같아 손대지 못했던 타임라인 시각화.

 

로그쉐어활동기록을 남겨 이력을 정리하는 서비스이기 때문에 그 사람이 몇월에 어떤 활동을 했는지에 대해 한눈에 볼 수 있는 시각화자료가 필요했다. 아무리 포스팅이 많다 하더라도 글로 활동기간을 적어놓으면 눈에 잘 들어오지 않는다.

 

처음에는 이미 DB에 있는 포스팅의 정보를 가져와 시각화하는 것이기 때문에 matplolibary를 써야하나, 액셀로 정리 후 이미지로 변환시켜 이미지를 올려줘야 하나 등등 많은 고민을 했다. 그러나 적용하는데  너무 오랜 시간이 걸리거나 너무 비효율적이라는 판단 하에 쭉 미뤄두고있었다.

 

그러다가 우연히 jquery UI중 타임라인에 관련된 레퍼런스가 있는 것을 팀장님이 발견했다. 힘들긴 해도 하루이틀간 꼬박 붙잡고 있으면 커스텀해서 원하는대로 만들 수 있겠다는 생각이 들었다.

 

Scrollable Event Timeline Plugin With jQuery - eventline.js

The eventline.js plugin helps you to create a horizontal, slider-style event timeline using jQuery, Moment.js and Font Awesome iconic font.

www.jqueryscript.net

 

우리 조는 목요일을 기준으로 백/프론트 작업을 끝내는 것을 목표로 잡고 있었다. 마지막에 이걸 시각화 못하면 너무 아쉬울 것 같아 결국 밤을 새는 목요일에 나는 이 레퍼런스를 커스텀하는데에 매달렸다.

 

커스텀의 목표는 이랬다.

 

1. 일단 달력을 한글화하자.

2. 카탈리스트 형식으로 해당 이벤트라인의 짤막한 설명을 보여주는 것이 아니라, 해당 dot을 누르면 그 이벤트에 해당하는 post_detail로 이동시키기. 즉, jquery에 DB의 포스트 연동시키기.

3. 포스팅의 카테고리(인턴/대외활동/강연 등)에 따라 타임라인에서 dot의 색깔을 다르게해서, 한 눈에 어떤 이벤트인지 확인 가능하게 하기.

 

이 중 달력의 한글화는 moment함수에서 이미 한글을 지원해줘서 어렵지 않았고,

문제는 2번과 3번이었다.

해당 jqueryUI가 사용하는 형식으로, 원하는 정보를 DB에서 뽑아 넘겨줘야한다. 그것도 스크립트 내로!

그 정보를 UI에서 해석해서 이벤트라인에 표시하도록 하고, 정보 중 카테고리를 분석하여 색깔도 바꾸어줘야한다.

 

따라서 View에서 postlist의 수많은 정보를 스크립트로 넘기는 방법을 고민했다.

이때 결국에 1주차에 했던 REST API관련 삽질이 생각났다. 결국에 필요한 정보들은 정해져있고, 그 형식으로 사용할 수 있도록 json을 넘겨주면 될 것이다. 그 때 그 경험이 돌고 돌아서 여기로 오다니.

 

        #타임라인용 Context
        post_context = []
        index = 1
        post_list_reverse = post_list.order_by('start_date','end_date')
        for post in post_list_reverse:
            post_context.append({
                'id': index,
                'postId': post.id,
                'title': post.title,
                'startDate': str(post.start_date.year)+'-'+str(post.start_date.month)+'-'+str(post.start_date.day),
                'endDate': str(post.end_date.year)+'-'+str(post.end_date.month)+'-'+str(post.end_date.day),
                'category': post.category,
            })
            index += 1

        post_context = json.dumps(post_context)
        context['post_context'] = post_context

일단 날짜순으로 차례대로 정리해서 표시해야하기때문에 역순(최근순)으로 정렬되어있던것을 다시 시간순으로 정리한 post_list_reverse를 만든다.

 

그다음 해당 포스트에서 필요한 정보만을 뽑아서 dictionary화한다. 그리고 json화시켜 context로 넘겨준다.

function GetEvents(post_context_json) {
        {#ex#}
        var context = [
            {
               id: 1,
                title: "Catalyst 0",
               startDate: "2017-9-17",
               endDate: "2017-9-17",
               notes: "This is the last catalyst we shall be tracking in the year 2017. There are no interesting events occurring after this one."
            },
        ];
        console.log(post_context_json);

        return post_context_json
    }

이벤트라인의 GetEvents함수에 해당 형식을 맞추어서 받아온 post_context_json을 가져온다.

 

리스트 안의 딕셔너리 하나 하나는 각각 하나의 이벤트가 되어 타임라인에 표시될 것이다. 각 이벤트 안에 a태그를 넣어 클릭시 post_detail로 연결되도록 한다.

 

마지막으로 넘어온 카테고리에 따라 이벤트마커의 클래스명을 추가해주고 각각 다른 색깔의 css를 적용시켜주면 완성.

 

커스텀된 이벤트라인.

생각했던것보다 굉장히 어려운 수준의 커스텀은 아니라 시간이 오래걸리지는 않았다. 만들고나니까 예뻐서 흐뭇하기도 하고, 지쳐서 진이 쭉 빠졌다. 이미 있는 UI를 커스텀해서 사용하는것도 이렇게 어려운데 이걸 직접만드려면 얼마나 힘들까? 매번 프로그래밍을 공부하다 보면 거인의 어깨위에 올라있다는 말이 정말 실감난다.

 

 

4. AWS 배포

 

사실 타임라인 시각화를 마지막으로 프로젝트를 발표하고자 했지만, 아침에 집에 들어와 늦은 오후에 깬 뒤 몇몇 팀이 배포에 시도(심지어 성공)한 것을 알게 되었다. 그 전까지는 별 생각이 없었는데, 지기 싫은 자존심도 있고 지금 아니면 언제 시도해보랴 하는 생각도 들었다. 그리고 지금까지 만든 것을 누구나 접속할 수 있게 한번 올려보고싶은 마음도 있었다.

 

패기 반 오기 반으로 시작했다. 다행히도 길재은 선배님과 박정욱 선배님이 강의해주신 노션에 대부분의 커맨드와 세팅이 다 들어가 있었기 때문에 도전이나마 할 수 있었던 것 같다. 대부분의 커맨드를 따라 쓰고, 또 고쳐가면서 하나하나 배웠다.

 

EC2 인스턴스와 RDS를 간단히 연동만해서 gitclone받아 배포하는 것 뿐인데 왜이리 안되는게 많은지. 듣도보도 못한 오류들에 시달렸다.

배포 관련된 레퍼런스는 많지만 사실 그거 따라한다고 멀쩡히 작동되는 사람은 정말 한줌. 레퍼런스가 잘못 된 것이 아니라 그만큼 개개인의 프로젝트 구성이나 기기 설정이 다 다르기때문이다.

 

나같은 경우는 no python application found 오류가 정말 풀리지 않았는데,

uwsgi.ini에서 제대로 경로를 못잡아줘서 그런것이었다. chdir과 module 경로를 제대로 찾아줘야한다. 편의에 따라 프로젝트를 생성할 때 프로젝트폴더 바깥에 managy.py를 만드는 경우도 있고 폴더 안쪽에 만드는 경우도 있고.. 그런 식으로 구성이 다 다르기 때문에 명확히 폴더구조를 이해하고 코드를 넣어야한다. 늦은 밤에도 질문 받아주시고 차근차근 잘 설명해주신 손준혁선배님께 정말 감사드린다.

 

특히 ubuntu 18.04서버로 배포했기 때문에 터미널에서 CLI만으로 파일을 수정하는게 너무 힘들었다. 익숙하지않은 vim문법과 CLI란.

 

오류 상황도 파이참이나 자바스크립트 콘솔로 찍히듯 한눈에 볼 수 없었다. journalctl로 몇시 몇분에 어떤 오류가 발생했는지 일일이 다 찾아봐야했다. 한번 오류가 찍힐때마다 수십줄씩 찍히는 바람에 journalctl을 비우는 법을 가장 먼저 익혔다.

 

sudo journalctl --rotate 

sudo journalctl --vacuum-time=1s

 

이렇게 입력하면 journalctl이 최근 1초간의 기록만 남기고 모두 지우기 때문에 사실상 파일을 비우는 셈이 된다. 이 이후에 다시 로그를 찍고, journalctl을 열어 확인하고.. 이런 무식한 작업을 반복했었다(분명 더 좋은 방법이 있을것 같은데 나는 못찾았다. 혹시 아시는 분은 댓글좀..)

 

오후 6시에 시작해서 RDS 연동까지 총 11시간반이 걸렸다.

 

처음으로 배포를 성공해 브라우저에 ip주소를 찍어 내가 만든 사이트의 메인프론트화면을 봤을때,

RDS 연동까지 성공한 뒤 회원가입을 해서 내 회원정보의 입력이 DB에 찍힌 것을 봤을때,

기분이 정말 좋고도 이상했다. 프로그래밍 공부를 시작한 이후로 가장 기억에남는 몇 순간중 하나였다.

DB연결의 순간

 

 

이외에도 static파일의 경로 설정이나 시크릿키 암호화해서 보안처리하는 등의 이슈가 남아있었지만 그건 너무 길어지니 생략하자.

 

그렇게 3주차에 걸친 내 인생 첫 프로젝트가 끝났다. 로그쉐어를 비롯한 6개의 서비스가 2월 22일에 서울대학교 sk경영관 120호에서 그동안의 프로젝트 결과를 발표했다.

 

이에 대해서, 그리고 느낀점에 대해서는 다음 포스팅에 기록하겠다.

댓글