2/11/2018

Install Parity to Ubuntu

Install parity

$ bash <(curl https://get.parity.io -L) -r stable
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   170    0   170    0     0    122      0 --:--:--  0:00:01 --:--:--   122
100  3856  100  3856    0     0   2182      0  0:00:01  0:00:01 --:--:-- 1892k
Release selected is: stable
Upgrading parity from 0.0.0 to 2.0.8

systemd

  • systemd unit file - /lib/systemd/system/parity.service
[Unit]
Description=Parity Ethereum Daemon
After=network.target

[Service]
# run as root, set base_path in config.toml
ExecStart=/usr/bin/parity --config /etc/parity/config.toml
# To run as user, comment out above and uncomment below, fill in user and group
# picks up users default config.toml in $HOME/.local/share/io.parity.ethereum/
# User=username
# Group=groupname
# ExecStart=/usr/bin/parity
Restart=on-failure

# Specifies which signal to use when killing a service. Defaults to SIGTERM.
# SIGHUP gives parity time to exit cleanly before SIGKILL (default 90s)
KillSignal=SIGHUP

[Install]
WantedBy=default.target

parity configuration

데이터 경로를 백업볼륨 경로로 선정. 자세한 설정은 해당 링크 참고. Configuring-Parity-Ethereum

  • parity config file - /etc/parity/config.toml
[parity]
base_path = "/var/lib/parity"
db_path = "/var/lib/parity/chains"
keys_path = "/var/lib/parity-data/keys"
...

# log option
[misc]
logging = "info"
log_file = "/var/lib/parity/parity.log"

run parity

parity 를 실행하기 위한 기본적인 셋업은 끝났으며, 정상작동을 확인해본다.

$ sudo systemctl start parity
# 실행 상태 확인
$ sudo systemctl status parity
# 로그 확인
$ sudo tail -f /var/lib/parity/parity.log

logrotate

parity 로그 파일을 logroate사용하여 관리한다.

  • logroate conf file - /etc/logrotate.d/wallet
/data/log/wallet.log { 
    daily
    compress
    rotate 365
    missingok
    notifempty
    dateext
    copytruncate
}
$ sudo logrotate -f /etc/logrotate.d/wallet
$ sudo gzip -d wallet.log-20201010.gz
$ sudo cat wallet.log-20201010

Reference

parity-ethereum github

22/10/2018

Flask gunicorn nginx docker

Best practice of dockerizing flask gunicorn nginx.

PS. If you want to see the sample code. Click this link GitHub Repository

Concept

Starting with the installation of flask application, stack it step by step with Bottom Up.

PS. This following descriptions may not be accurate and in such cases pleas read and understand the code in the repository

main app versions

  • python/3.6.4
  • nginx/1.15.5

Flask Web Application

$ python -m venv venv
$ . venv/bin/activate
$ python -V
$ pip install --upgrade pip
$ pip install Flask
$ pip list
# main.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return "HELLO, WORLD!"


@app.route('/ping')
def ping():
    return "PONG"
$ FLASK_APP=main.py flask run
$ curl 127.0.0.1:5000/ping
PONG
$ curl 127.0.0.1:5000/
HELLO, WORLD!

debug or development mode

  • it activates the debugger
  • it activates the automatic reloader
  • it enables the debug mode on the Flask application.
$ export FLASK_ENV=development
$ export FLASK_DEBUG=1
$ export FLASK_APP=main.py
$ flask run

PS. This makes it a major security risk and therefore it must never be used on production machines.

performance test for flaks standalone

$ flask run
$ ab -n 32 -c 16 127.0.0.1:5000/sleep/10
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:        Werkzeug/0.14.1
Server Hostname:        127.0.0.1
Server Port:            5000

Document Path:          /sleep/10
Document Length:        19 bytes

Concurrency Level:      16
Time taken for tests:   20.031 seconds
Complete requests:      32
Failed requests:        0
Total transferred:      5536 bytes
HTML transferred:       608 bytes
Requests per second:    1.60 [#/sec] (mean)
Time per request:       10015.349 [ms] (mean)
Time per request:       625.959 [ms] (mean, across all concurrent requests)
Transfer rate:          0.27 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing: 10006 10012   2.9  10012   10017
Waiting:    10005 10011   2.8  10011   10017
Total:      10006 10012   2.9  10012   10017

Percentage of the requests served within a certain time (ms)
  50%  10012
  66%  10013
  75%  10015
  80%  10015
  90%  10016
  95%  10017
  98%  10017
  99%  10017
 100%  10017 (longest request)

gunicorn

$ pip install gunicron
$ gunicorn -w 3 --threads 3 wsgi:app -b 0.0.0.0:5000
[2018-10-09 19:30:57 +0900] [79365] [INFO] Starting gunicorn 19.9.0
[2018-10-09 19:30:57 +0900] [79365] [INFO] Listening at: http://0.0.0.0:5000 (79365)
[2018-10-09 19:30:57 +0900] [79365] [INFO] Using worker: threads
[2018-10-09 19:30:57 +0900] [79368] [INFO] Booting worker with pid: 79368
[2018-10-09 19:30:57 +0900] [79369] [INFO] Booting worker with pid: 79369
[2018-10-09 19:30:57 +0900] [79370] [INFO] Booting worker with pid: 79370

main configuration

  • bind
  • workers
  • worker_class
  • threads
    • This setting only affects the Gthread worker type.
  • access_logfile
    • - means log to stdout
  • access_logformat
    • default: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
  • error_logfile
    • - means log to stderr
# TCP SOCKET BINDING
$ gunicorn -w 3 --access-logfile - -k gevent wsgi:app -b 0.0.0.0:5000
...
# UNIX SOCKET BINDING
$ gunicorn -w 3 --access-logfile - -k gevent wsgi:app -b unix:/tmp/gunicorn.sock
...

# Check The Socket
$ echo -e "GET /ping HTTP/1.0\r\n\r\n" | nc -q 2 -U /tmp/gunicorn.sock
PONG
$ gunicorn --config gunicorn_config.py wsgi:app

Nginx

mac os var, etc path

/usr/local/etc/nginx/
/usr/local/var/log/nginx

gunicorn-nginx.conf

/usr/local/etc/nginx/servers/gunicorn-nginx.conf

upstream gunicorn-app {
    server unix:/tmp/gunicorn.sock fail_timeout=0;
}


server {
    listen 80;
    server_name 0.0.0.0;

    client_max_body_size 5M;

    access_log /usr/local/var/log/nginx/access.log combined;
    error_log /usr/local/var/log/nginx/error_log.log warn;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        proxy_pass http://gunicorn-app;
    }
}
sudo nginx -c [config_path]
sudo nginx -s stop

Docker

When I dockerize, first setup the tools and environment necessary for dockerizing image. Then, enter console of the container, check the configuration, and run each application to verify normal operation. Afrter verifcation is completed, write CMD or ENTRYPOINT COMMAND.

Dockerfile
FROM python:3.6.6-jessie

MAINTAINER cgex

ENV SRVHOME=/srv/app

WORKDIR $SRVHOME

COPY ./ $SRVHOME

RUN apt-get update &amp;&amp; apt-get install -y \
    nginx \
    supervisor

COPY ./nginx/nginx.conf /etc/nginx/
COPY ./nginx/gunicorn-app.conf /etc/nginx/conf.d/

COPY ./gunicorn_config.py /etc/gunicorn/

COPY supervisord.conf /etc/supervisor/

RUN pip install --upgrade pip
RUN pip install -r requirements.txt

EXPOSE 80
EXPOSE 8080

# for receving stream of log 
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    &amp;&amp; ln -sf /dev/stderr /var/log/nginx/error.log

CMD ["/usr/bin/supervisord"]

REFERENCES

gunicorn vs uwsgi flask-concurrency-test

12/2/2018

WEB Framework Check Template

글에서 말하는프레임웍은 REST API 중심의프레임웍을 말한다. 해당 글의 작성 목적은프레임웍을 선택 혹은 구축하는데 있어 고려해볼사항들에 대해서 포괄적으로 언급한다. 파이썬 진형의 대표적인프레임웍 장고에서 많은 개념을 참고했다.

주요 고려 항목

  • Authentication
    • OAuth2, JWT
  • Explicit Project Structure
  • Architecture
    • Design Pattern. MVC Pattern
    • 장고의 경우 View, Model, Serializer 와 같은모듈의 명확한 역할 정의. 균일한 데이터 흐름을 갖게 하는게 매우 중요.
  • Middleware
    • Throttling
    • 필요하다면 Device 분기
    • Logging
    • 현재 서버 환경별 Request, Response Handling
      • Beta 환경인 경우. 토큰발행등으로 Beta 서버 추가 인증.
  • Dynamic and Elastic Settings Load. e.g) local, development, alpha, beta, production …
  • DDD or Adaptable MSA
    • 장고의 경우 앱을 Service 중심으로 분류. 서비스간 단일 인터페이스를 통해 의존관계 설립.
  • REST API URL일반화 가능여부.
    • 장고의 경우 function view => class view
      • class view경우 API 별 permission, authentication, throttling 관리가 매우 편해짐.
  • Exception Handling Centralization
  • Test Environment
    • Test Framework
  • Cache Backend & Engine
    • Redis, Memcached …
  • Storage Engines
    • MySQL, MSSQL, PostgreSQL …
  • ORM Engine
  • API Versioning
    • /v1/
  • API Documentation
    • Swagger
  • Web 혹은 Admin 필요시 Template Engine
  • Runtime Debugger
    • django-debbug-toolbar
  • Serialization

ON DEMAND

  • Asynchronous task queue
    • Celery
    • Email, SMS 발송 같은 서비스
  • Support DATA Team
    • DATA HOOK
  • Log Centralization
Web Django
7/2/2018

[Django] contenttypes framework

부제: django.contrib.contenttypes 은 무엇인가?

장고 프로젝트를 생성하고, settings.py 파일을 보면 기본값 설정들이 되어있고, INSTALLED_APPS 하위에 이미 어떤 장고 어플리케이션들이 선언되어있다. 해당 어플리케이션인들이 프로젝트에 판단하기 위해 역할을 알아볼 것이다. 이 포스트에서는 django.contrib.contenttypes대해 살펴볼것이다.

django.contrib.contenttypes

네이밍이 contenttypes 이여서 HTTP Header의 속성 Content-Type 에 관련된 앱인줄 알았다. 하지만 설명을 읽어보니 전혀 상관없었다.

Django includes a contenttypes application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.

설명을 보아하니, 장고 프로젝트안에서 선언된 모델을 추적하며, 높은수준의 사용하기 편한 인터페이스를 제공한단다. 목적은 파악되었다. 아직 필요한지 제거해도 되는지 판단이 서지 않아서 조금알아보겠다.

It’s generally a good idea to have the contenttypes framework installed; several of Django’s other bundled applications require it:

The admin application uses it to log the history of each object added or changed through the admin interface. Django’s authentication framework uses it to tie user permissions to specific models.

몇몇 장고의 번들 어플리케이션을 사용할때 contenttypes framework필요하단다… (몇몇!!!??? 말이여 방구여 그냥 쓰라는 의미로 받아들이자.) 장고 인증 프레임웍과 어드민에 의존 어플리케이션이란다. 더 이상 읽을 필요가 없다. 사용하기로 결정!!

PS. 장고에서 코드를 작성할때, 모델 사용을 참조 형식이 아닌, 문자열 형식으로 선언하고 런타임 환경에서 Lazy Loading 형식으로 구현하는 경우가 많다. 추축이지만 아마 contenttypes framework 때문에 가능한것 같다. 코드를 까서 살펴보면 되지만, 시간이 없다. 조사는 여기까지 그만 짜이찌엔!!

Django Python
31/1/2018

이벤트 기반의 이해

마이크로서비스 아키텍처 구축살펴보던이벤트 기반(Event Driven)대해서 언급이 나왔다. 읽기만 해서는 이해가 되지 않아 코드를 작성해보았다. 물론 아직도 이해가안된다.

오케스트레이션(Orchestration)

중앙에서 이벤트를 모두 관리처리

class Customrer():
    def __init__(self, name, address, email):
        self.name = name
        self.address = address
        self.email = email


class CustomerService():

    def signup(self, customrer):
        # 계좌 개설
        BankService().create_account(customrer)

        # 환영 메일 발송
        MailService().send(customrer.email)

        # 가입 패키지 발송
        PostService().send(customrer.address)


class BankService():
    def create_account(self, customrer):
        print("Service Name: %s, Customrer Name: %s" %(self.__class__.__name__, customrer.name))


class MailService():
    def send(self, to):
        print("Service Name: %s, To: %s" % (self.__class__.__name__, to))


class PostService():
    def send(self, to):
        print("Service Name: %s, Address: %s" % (self.__class__.__name__, to))


if __name__ == '__main__':
    customer = Customrer(name='SELO', address='Seoul, Korea', email='rochan87@gmail.com')
    CustomerService().signup(customer)

코레오그래피(Choreography)

발행/구독 패턴을 통한 느슨한 결합

class Customrer():
    def __init__(self, name, address, email):
        self.name = name
        self.address = address
        self.email = email

EventBook = {
    # example
    'main_event': {
        'events': ['dependent_event', ],
    }
}


def register_event(pub_event_name, sub_event):
    try:
        pub_event = EventBook[pub_event]
        pub_event.append(sub_event)
    except KeyError:
        EventBook[pub_event] = [sub_event]


def pub(event_name):
    def add(func):
        def __wrapper(*args, **kwargs):
            _r = func(*args, **kwargs)
            sub_events = EventBook[event_name]
            for sub_event in sub_events:
                sub_event(*args, **kwargs)
            return _r
        
        if event_name not in EventBook:
            EventBook[event_name] = []

        return __wrapper
    return add


def sub(event_name):
    def register(func):
        def __wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        if event_name not in EventBook:
            EventBook[event_name] = []

        EventBook[event_name].append(__wrapper)
        return __wrapper
    return register


SIGNUP = 'signup'
UPDATE = 'update'


class CustomerService:
    @pub(SIGNUP)
    def signup(self, customrer):
        print("SignUp\n")

    @pub(UPDATE)
    def update(self, customrer):
        print("Update\n")


class BankService:
    @sub(SIGNUP)
    def create_account(self, customrer):
        print("Service Name: %s, Customrer Name: %s" %(self.__class__.__name__, customrer.name))


class MailService:

    # 아래와 같이 구독하고 싶은 이벤트만 추가해서 원하는 동작을 수행한다.
    @sub(SIGNUP)
    @sub(UPDATE)
    def send(self, customrer):
        print("Service Name: %s, To: %s" % (self.__class__.__name__, customrer.email))


class PostService:
    @sub(SIGNUP)
    def send(self, customrer):
        print("Service Name: %s, Address: %s" % (self.__class__.__name__, customrer.address))



if __name__ == '__main__':
    customer = Customrer(name='SELO', address='Seoul, Korea', email='rochan87@gmail.com')
    CustomerService().signup(customer)
    CustomerService().update(customer)
MSA MicroserviceArchitecture EventDriven
25/1/2018

Dynamically import django settings for multiple environment such as local, dev, beta, production

장고 개발을 하다보면환경을 구분하고, 이에 따라 변경되야 하는 부분들이 존재한다. 예를들면 임포트 되어야하는 미들웨어 혹은 장고 어플리케이션들, 데이터베이스 연결정보 등이 달라진다. 이럴때환경에 따라 코드를 수정하는 작업은 해서는 안되는 작업이며, 언젠가 한번쯤 실수를 할것이고, 비명을 지르게될것이다. 꺄야양야야야야야 ㅅㅂ

어떻게 환경을 인식을 할 것인가?”

어플리케이션이 작동하는 환경을 나누어 보자. 아래처럼 환경을 구분하는 것이 정답은 아니며, 개발프로세스나 회사 업무에 따라 다를것이다.

  • local: 개발자 개인PC
  • development: 사내 네트워크 망에서 공유되는 서버를 통해 작동하는 어플리케이션 환경
  • staing: 외부 네트워크 망에서 작동하는 서버에서 제공되는 어플리케이션 환경. 릴리즈1차 외부 테스트 환경.
  • beta: staging동일하지만 production최대한 일치되는 환경으로 production 릴리즈 이전에 최종테스트가 일어나는 환경.
  • production: 어플리케이션이 실제 서비스 되는 환경.

위와 같이 다양한 환경에서 동일한 코드베이스의 장고 어플리케이션이 동작하게 되며, 각 환경에서 로드되어야하는 모듈이나 설정정보들이 다를것이다. 이렇게 다른점을 적용하기 위해서는 현재 어플리케이션이 작동하는 환경이 production 인지 local 인지 인식을 해야한다.

현재 작동하는 환경을 인식하기 위한 방법은 다양하지만, 필자가 가장 선호하는 방법은 환경변수 셋팅이다.

export SELO_ENV=local

TIP) 개인 개발PC .bash_profile같은부트스트래핑 파일에 구문을 추가해 놓으면 편하다.

import os
CURRENT_APP_ENV = os.getenv('SELO_ENV')

환경별 설정 모듈화디렉토리 구조잡기

유연하며, 명시적인 구조를 만들어보자. Here we go. Let’s get it. Put your hands up!!!”

시작이 반: 장고 프로젝트 초기 구조

##### 장고의 초기 디렉토리 구조 #####
$ tree
.
|____bar
| |______init__.py
| |____settings.py
| |____urls.py
| |____wsgi.py
|____manage.py

버전 1.11 기준 장고의 프로젝트를 처음 생성하면 위와 같은 디렉토리 구조로 프로젝트가 생성된다. 아주 심플하다. 구조화 없이 프로젝트가 진행되다보면 알아보기 무서운 괴물“이 될수있다.

결과물: 아주 이쁘게 모듈화된 최종 구조

먼저 우리가 갖게최종 구조를 훓어보고그림을 그려보자. 최종구조는 다음과 같다.

$ tree
.
|____meta
| |______init__.py
| |____unittest.py
| |____ckeditor.py
| |____ ...
|______init__.py
|____default.py
|____development.py
|____staging.py
|____beta.py
|____prod.py

# 한눈에 보기 편하도록 출력결과물의 순서를 조정했다.

설명이 필요없는 한눈에 이해되는 명확한 구조가 아닌가!! 아름답다!! 나만 그런가…?!!”

최종구조에서 각 모듈(파일)의 역활을 정리해보자.

  • settings/default.py: 환경에 상관없는 공통적인 설정을 작성한다.
    • 기본 앱. e.g) 'django.contrib.admin', 'django.contrib.auth'
    • 공통 미들웨어
    • TIMEZONE서비스에서 통용되는 상수
  • settings/<env_name>.py: prod.py같이환경에 의존적인 설정을 작성한다. <env_name>.py.
    • 개발환경에서의 디버깅 툴. e.g) Django Debug Toolbar
    • 데이터베이스 커넥션 정보
    • 환경별 미들웨어. e.g) 베타유저 인증 미들웨어
    • TOKEN같은 환경별 상수
    • 모니터링 툴. e.g) NewRelic
  • settings/meta/<*.py>: meta 폴더 하위에는 특정 라이브러리의 설정이나 특별한 실행 환경에서 실행되는 설정에 대한 부분을 작성한다.
    • $ python manage.py test 처럼 테스트 실행과 같은 특별한 환경에서 필요한 설정 혹은 라이브러리 monkey patch같은 작업을 진행한다.
    • 많은 라인(15줄 이상)의 설정이 필요한 라이브러리(django-ckeditor 등). settings/meta/ckeditor.py
    • default.py읽기 힘들정도로 길어질모듈로 분리해야하는 경우

Import: 즐거운 코딩하는 시간

manage.py
...
# setdefault 함수의 2번째 인자 부분을 알맞게 변경해주자
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "foo.settings.default")
...
foo/settings/default.py

환경변수를 읽어, 셋팅을 동적으로 로드하자.

import os


try:
    SELO_ENV = os.environ['SELO_ENV']
except KeyError:
    print "SELO_ENV must be set to OS Variable."
    exit(1)
    
if SELO_ENV == 'development':
    from foo.settings.development import *
elif SELO_ENV == 'staging':
    from foo.settings.staging import *
elif SELO_ENV == 'beta':
    from foo.settings.beta import *
elif SELO_ENV == 'prod':
    from foo.settings.beta import *
else:
    print "Given Value is %s. It's invalid environment name." % SELO_ENV
    exit(1)

코드가 드럽다. 안이쁘다. 구려보인다. 이쁘게 바꿔보자. 코드는 역시 외모지상주의!!

try:
    SELO_ENV = os.environ['SELO_ENV']
except KeyError:
    print "SELO_ENV must be set to OS Variable."
    exit(1)
    
    
def import_module_asterisk(module_name):
    module = __import__(module_name, globals=globals(), fromlist=['*'])
    for v in dir(module):
        if v in {'__name__', '__builtins__', '__doc__', '__file__', '__package__'}:
            continue
        globals()[v] = getattr(module, v)


try:
    import_module_asterisk(SELO_ENV)
except ImportError:
    print "Given Value is %s. It's invalid environment name." % SELO_ENV
    exit(1)
else:
    print("Successfully Loaded %s settings." % SELO_ENV)

코드를 간단히 설명하겠다. __import__빌트인 함수이며, 동적으로 모듈을 임포트할있게해준다. 즉, 오직 실행환경에서만 임포트 대상을 알 수 있는 경우 사용한다. 코드로 풀어서 설명하자면, __import__('development', fromlist=['*'])from development import *동등한 기능을 수행한다. 결론적으로 development.py 모듈의 네임스페이스를 통째로 임포트한다. 설명은 파이썬2 기준이며, 파이썬3에서는 사용해보지 않았다.

정리

필자가 참여한 장고 프로젝트에는 신규 프로젝트도 있었고, 몇 년동안 개발 운영 되고 있는 서비스도 있었다. 프로젝트에서 이러한 환경에 따른 설정은 매우 치명적인 부분임에도 불구하고 기능개발에 집중하다보면 간과되는 경우가 많다. 문제가 발생하지 않으면 다행이지만최근에 오픈한 모바일게임 듀랑고 클라이언트에서 서버 로그가 출력되는 안타까운 문제가 발생했고, 논란의 중심에 있다. 듀랑고 사태의 정확한 맥락은 알수 없지만 기본을 간과하는 순간 문제가 발생하게 되는것 같다.

같은 맥락으로 이어서 말하자면 코드를 공유하는 개발자들 사이에서 설정을 변경푸쉬하는 경우로 문제가 발생했던 적이 몇 번 있었다. 특히, 디비커넥션 정보, 디버거 임포트 등과 같이 개발중인 기능에 의존적인 설정들이 예가것이다. 위 방식과 구조를 취한다면 이러한 문제들을 해결할있을 것이다.

PS. 언젠가 정리해야지 막연히 생각하던걸 새로운 회사에서 또 한번(ㅠ?) 장고프로젝트를 진행하게 됬고, 이 참에 정리했다개운하구만!! 그럼 이만 뿅!!

오타나 잘못된 부분에 대한 언급은 언제나 감사드립니다.

Reference

python django pythonic