OSX 에서 Google Cloud Platform 인스턴스에 ssh 접속하기

구글 Cloud Platform에 인스턴스를 만들고 작업을 위해 터미널로 접속을 할 필요가 있다.
osx(맥) 환경에서 어떻게 터미널을 이용해서 어떻게 접속하는지 간단하게 정리해 본다.

  • 기본적인 사항은 여기 참고 : https://cloud.google.com/compute/docs/console
  • 맥은 터미널에서 아래 명령어로 gcloud sdk를 설치해야 함 ( 윈도우 사용자는 https://cloud.google.com/sdk/
  •  $curl https://sdk.cloud.google.com | bash 
  • 위 명령어로 gcloud 를 설치하면 설치과정 마지막에 환경변수를 세팅할 rc파일을 물어보는데, default 가 .bashrc 라서 .profile 파일로 지정을 해 주어야 제대로
  • 아래 명령어로 gcloud workspace를 생성해야 한다.(터미널에서..)
  •  gcloud config set project YOUR_PROJECT_ID 
  • 다음과 같이 gcloud ssh 로 클라우드 머신에 접속해서 사용하면 됨.
  •  gcloud compute ssh YOUR_APPLICATION_NAME --zone us-central1-a 
  • 위에서 –zone 옵션을 주었는데, 같은 프로젝트로 여러 인스턴스를 만들었다면 zone 옵션은 필수다.
  • 다른 SSH 로 접속 하려면 아래 여기 참고 : https://cloud.google.com/compute/docs/console

dJango Template 에서 settings 값 사용하기

dJango 를 이용해서 만들고 있는 어플리케이션이 html view 와 rest api ( json )을 모두 지원하게 하려고 한다. 요청 URL 의 내용을 보고, api 요청인지, view 요청인지 판단하여 분기처리를 하기 위해 rest 처리용 urls 와 화면용 urls 를 분리하였다.

url(r'^api/product/', include('products.api_urls', namespace='product_api')),
url(r'^view/product/', include('products.view_urls', namespace='product_view')),

요청 url 이 /api/ 로 시작하면 api_urls 에 매핑되어 있는 RestAPI 용 view 가 실행이 되고, /view/ 로 시작하면 화면 출력용 view 가 호출되면서 지정한 template 에 의한 화면이 출력되게 된다.

따라서, template 에서 사용하는 링크는 /view/를 포함하고 있어야 화면간에 이동(link)을 할 수 있게 된다.

        <ul>
            
                <li><a href="/view/product/6/">testName1</a></li>
            
                <li><a href="/view/product/4/">testName</a></li>
            
                <li><a href="/view/product/3/">testName</a></li>
            
        </ul>
    

문제는 화면 요청을 특정하는 ‘/view/’ 라는 문자열을 하드코딩하지 않고, settings.py 에 아래와 같이 정의해 놓고,

VIEW_URL_CTX = '/view/'
API_URL_CTX = '/api/'

template 에서는 <a href="{{ VIEW_URL_CTX }}product/... > 와 같은 식으로 사용하고 싶었다.

하지만 template 에서는 사용자가 settings 에 정의한 값을 꺼내어 사용할 수 없었다. 그럴법도 한것이, 만약 대규모 프로젝트라면 settings.py 에는 DB 접속 정보부터 각종 설정에 이르는 민감하고 중요한 내용들이 들어있기 때문에 자유롭게 해당 값을 읽어서 사용할 수 있다면 문제가 될 소지가 많다.

설정은 모두 settings에 모아두고 그 값을 template 에서 사용하기 위해서는 어떻게 해야 할까?
context processor도 하나의 방법이 될 수 있지만 필자는 dJango 의 custom tag를 사용해서 위 문제를 해결하기로 했다.

그럼 custom tag를 만드는 방법을 알아보도록 하자.

먼저 custom tag를 만들기 위해서는 ‘templatetags’ 디렉토리를 만들어야 한다. 정확하게 얘기하면 __init__.py 가 포함되어 있는 python pacakge 를 만들어야 한다. 패키지 이름도 반드시 ‘templatetags’ 이어야 한다.

패키지를 만들었다면 패키지 custom tag를 구현할 py파일을 생성한다. 이때 생성하는 py 파일의 이름은 자유롭게 지정해도 좋다.

dJango 의 custom tag 는 tag 메소드를 정의하는 방법(argumemt의 갯수)에 따라 tag 의 형태가 많이 달라지므로 필요한 형식에 따라 주의깊게 tag를 정의해야 한다.

필자는 tag를 <a href=””{{ TAG_NAME }}”> 처럼 사용하고 싶기 때문에(즉,tag에 전달하는 argumement가 없다) assignment_tag를 사용하기로 했다. ( tag의 다양한 유형은 https://docs.djangoproject.com/en/1.7/howto/custom-template-tags/ 를 참고하도록 하자.)

from django import template

from django.conf import settings



register = template.Library()




from django import template
from django.conf import settings

register = template.Library()

@register.assignment_tag(takes_context=True)
def get_view_url_ctx(context):
    return getattr(settings, 'VIEW_URL_CTX', '')

@register.assignment_tag(takes_context=True)
def get_api_url_ctx(context):
    return getattr(settings, 'API_URL_CTX', '')

위와 같은 내용으로 templatetags/product_tags.py를 만들었다.

위 파일에서는 django.conf 의 settings를 import 하여 사용하고 있다. tag 구현체에 decorator를 달아서 tag유형을 assignment_tag 로 지정하였다.

이렇게 custom tag를 만들었으면 이제 template 에서 사용할 수 있다. template 에서 custom tag를 사용하려면 tag 파일을 load 해야 한다.template 파일의 시작하는 부분에 {% load your_template_tag_file_name %} 과 같이 적어준다.

필자의 환경에서는 abstract base template 을 정의해 놓고, 각각의 어플리케이션의 화면들이 base template을 상속하는 형태로 되어 있기 때문에 최상위 template파일에 아래와 같이 적어주었다.

{% load product_tags %}

이렇게 template에서 tag파일을 로딩하면 이제 tag를 사용할 수 있게 되는데, tag는 {% %} 안에 tag 이름과 argument를 적어서 사용하게 된다. 하지만 필자는 마치 settings에 정의한 global 변수처름 {{ TAG_NAME }} 과 같이 사용하고 싶어 alias를 지정해서 사용하기로 했다.

{% get_view_url_ctx as VIEW_URL_CTX %}
{% get_api_url_ctx as API_URL_CTX %}

이렇게 하면 template 파일에서 아래와 같이 {{ }} 안에 tag 를 사용할 수 있게 되고, 마치 settings에 정의한 값을 그대로 template 에서 사용하는 것처럼 보이게 할 수 있다.

{% block content %}
    {% if latest_product_list %}
        <ul>
            {% for product in latest_product_list %}
                <li><a href="{{ VIEW_URL_CTX }}product/{{ product.product_id }}/">{{ product.product_name }}</a></li>
            {% endfor %}
        </ul>
    {% else %}
        <p>등록된 상품이 없습니다 </p>
    {% endif %}

    <div>
        <img src="{{ STATIC_URL }}product/images/200ok.jpg"/>
    </div>
{% endblock %}

Django Rest Framework 의 Contents Negotiation 기능

dJango rest framework 는 Request 의 Accept 헤더값에 따른 content negotiation을 지원한다.

즉, API 요청 시 Accept 헤더값을 “application/html” 이라고 보내면 응답을 그에 맞게 보내주는데 브라우저에서 요청을 하면 아래 화면 과 유사한 화면이 나온다.

 

스크린샷 2015-03-01 22.01.23

 

위 화면은  rest framework 에서 제공하는 rest api를 테스트 할 수 있는 화면이다. 즉 application/html로 응답을 받겠다고(Accept) 요청을 했으니 rest framework 는 응답을 html 보낸 것이다. 테스트 하기에는 좋지만 API로서 써먹기는 좀 곤란다.

이 API 를 제대로 사용하기 위해서는 요청 시 Accept 헤더값을 “application/json”이라고 요청을 하면 된다.

그럼 아래 화면과 같이 응답을 JSON 문자열로 받게 된다.

(아래 화면은  PyCharm 에 포함된 rest client 를 이용해서  api 를 호출한 결과를 캡쳐한 것이다.)

스크린샷 2015-03-01 22.04.30

이정도면 꽤 스마트하게 동작한다고 볼 수 있겠다.

하지만 화면에 보는 것처럼 한글이 깨져나온다.

사실 오늘 이렇게 한글이 깨지는 문제때문에 오랜시간 동안 삽질을 했지만 해결 방법은 의외로 간단했다.

HttpResponse 헤더에 content-type = “application/json; charset=utf-8” 옵션을 줌으로 해서 인코딩 문제가 생기지 않도록 할 수 있었다. 그래서 이 경우에도 HttpResponse 헤더에 위와 같이 헤더를 설정하려고 했으나, 그렇게 하면 Content Negotiation 을 하기 위해 코드에서  Accept 헤더를 보고 분기를 해야 하는 이상한 상황이 생기게 될 것 같다.

@api_view(['GET', 'POST'])
def product_list(request):
    print("called product_list method")
    if request.method == 'GET':
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        # print(serializer.data)
        return Response(serializer.data, content_type='application/json; charset=utf-8')
        # return Response(serializer.data)

하지만 사실 생각해 보면 간단한 문제였다. 위에서 언급한 것처럼 client에서 Accept로 지정한 데로 rest framework는 response content 타입을 결정하고 있으니 char encoding 도 마찬가지일 것이다.

 

 

 

스크린샷 2015-03-01 23.39.11

 

위 그림처럼 헤더를 설정하고 요청을 하면 아래와 같이 한글이 정상적으로 출력되는 것을 확인할 수 있다.

스크린샷 2015-03-01 23.39.03