파이썬 로깅모듈에 대해서

[markdown]# 파이썬 로깅모듈에 대해서

나는 개발자 경력을 자바개발자로 시작했다.

제일 먼저 배운 메서드는 `main` 메서드이고 그 다음으로 배운건 `System.out.println` 이다.
그러다가 `log4j`라는 고마운 녀석을 알게되어서 별 생각없이 `log4j`만 열심히 쓰다가, 여러 로깅모듈을 하나의 인터페이스로 모아주는 `slf4j`를 살짝 만져보다가 `nodejs`로 전향해서 엄청나게 삽질을 해댄 경험이 있다.

[흑역사 링크](https://github.com/wapj/loggyu)

지금 개발이 메인언어는 `nodejs`이고 프로젝트 빌드 및 배포는 `chef` + `fabric`으로 하고 있고, 서브 스크립트 언어로 `python`과 `shell`을 사용하고 있다. 그중에 스케줄러로 돌아가는 파이썬 스크립트를 만들게 되었는데, 이 녀석이 돌다가 에러가 났을 때 `print` 메서드 만으로는 이게 실행이 됐는지 죽었는지 확인할 길이 없었다. 그래서 파이썬 로깅모듈을 찾아봤다.

![python-logging1.png](http://gyus.me/wp-content/uploads/2014/09/python-logging1-1-1.png)

어라?! 제일 위에 표준 라이브러리가 나온다.

뭔가 내용이 많은데, 표준라이브러리 내용이 읽기 부담 스러운 분은 이 글을 읽으면 조금 도움이 될지도 모르겠다.

일단, 로깅 라이브러리라고 하면 쉽게 말하면 로그를 찍는 기능이 기본이고, 두번째는 여러군데에 찍는 것이고 (예를 들어 아웃풋 스트림에 찍고, 파일로 찍고), 세번째는 이쁘게 찍는것 이다.

그래서 내가 원하는 기능이 있는지 찬찬히 살펴보았다. 필요한 기능은 아래와 같았다.

1. 스트림과 파일에 동시에 로그를 남긴다.
2. 로그를 찍은 시간과 어디에서 로그를 남겼는지 남아야한다.
3. 테스트 환경과 프로덕션 환경에서 남기는 로깅 레벨이 달라야한다.
4. 파일에 남기는 경우, 파일의 크기가 너무 크면 자동으로 하나 더 만들어 주면 좋겠다.
5. 확장이 쉬우면 좋겠다.

일단 결론만 말하면 위에 말한거 전부 다 된다. 기본 모듈이 이정도라니 정말 놀랍다.

차근 차근 한번 알아보자.

### 스트림과 파일에 동시에 로그를 남기기

`print` 메서드로만 로그를 찍어왔다면, 이제 기본 탑재된 `logging` 모듈을 한번 사용해 보자.

“`python
import logging

logging.info(“I told you so”)
logging.warning(“Watch out!”)
“`

위의 코드를 실행하면 아래와 같이 나오는데, 그 이유는 logging의 기본 로그 레벨이 WARNING으로 되어 있기 때문이다.

“`shell
WARNING:root:Watch out!
“`

로그를 전부다 `WARNING`으로 찍을 수는 없으니 살짝만 건드려 보자.

“`python
import logging
logging.basicConfig(level=logging.DEBUG)

logging.debug(“디버깅용 로그~~”)
logging.info(“도움이 되는 정보를 남겨요~”)
logging.warning(“주의해야되는곳!”)
logging.error(“에러!!!”)
logging.critical(“심각한 에러!!”)
“`

그러면 아래와 같이 나올 것이다.

“`shell
DEBUG:root:디버깅용 로그~~
INFO:root:도움이 되는 정보를 남겨요~
WARNING:root:주의해야되는곳!
ERROR:root:에러!!!
CRITICAL:root:심각한 에러!!
“`

로깅 레벨도 지정해 봤으니 파일로도 남겨보자.

“`python
import logging
logging.basicConfig(filename=’./test.log’,level=logging.DEBUG)

logging.info(“=========================================”)
logging.info(“파일에다가 남겨봐요~”)
logging.info(“=========================================”)
logging.debug(“디버깅용 로그~~”)
logging.info(“도움이 되는 정보를 남겨요~”)
logging.warning(“주의해야되는곳!”)
logging.error(“에러!!!”)
logging.critical(“심각한 에러!!”)
“`

위의 코드를 실행하면 실행한 폴더에 test.log라는 파일이 생기고 그 파일을 열어보면 아래와 같이 로그가 파일로 남는다. `여러번 실행하면 파일을 덮어 쓰는 것이 아니라, 기존의 로그에 이어서 붙이기를 하게된다.`

“`shell
INFO:root:=========================================
INFO:root:파일에다가 남겨봐요~
INFO:root:=========================================
DEBUG:root:디버깅용 로그~~
INFO:root:도움이 되는 정보를 남겨요~
WARNING:root:주의해야되는곳!
ERROR:root:에러!!!
“`

그럼 이제 원래 하고 싶었던 걸 해보자.
아웃풋 스트림에도 찍어봤고, 파일로도 남겨 봤는데 둘다 동시에 남길려면 어떻게 해야되지? 라는 질문이 생기는데, 둘다 로그를 남길려면 `logging.getLogger(“로거이름”)` 이라는 메서드로 얻을 수 있는 logger라는 녀석을 사용해야한다.

logger를 써서 여러군데로 로그를 남기는 것에 대해 간단하게 단계를 설명하면 아래와 같다.

1. 로거 인스턴스를 만든다.
2. 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
3. 1번에서 만든 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
4. 로거 인스턴스로 로그를 찍는다.

말로 설명해 봤으니 코드를 보자.

“`python
import logging
import logging.handlers

# 1. 로거 인스턴스를 만든다
logger = logging.getLogger(‘mylogger’)

# 2. 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
fileHandler = logging.FileHandler(‘./myLoggerTest.log’)
streamHandler = logging.StreamHandler()

# 3. 1번에서 만든 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)

# 4. 로거 인스턴스로 로그를 찍는다.
logger.setLevel(logging.DEBUG)
logger.debug(“===========================”)
logger.info(“TEST START”)
logger.warning(“스트림으로 로그가 남아요~”)
logger.error(“파일로도 남으니 안심이죠~!”)
logger.critical(“치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!”)
logger.debug(“===========================”)
logger.info(“TEST END!”)
“`

위의 코드를 실행시켜 보면 콘솔과 파일에 각각 아래와 같은 로그가 남는다.

“`shell
===========================
TEST START
스트림으로 로그가 남아요~
파일로도 남으니 안심이죠~!
치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!
===========================
TEST END!
“`

### 로그를 찍은 시간과 어느 파일의 어느 라인에 심어 놓은 로그인지 남기기

로그의 포매팅에 관한 이야기 인데, 이 부분도 파이썬을 개발하는 분들이 이미 표준 logging모듈에 심어두셨다. 위에서는 핸들러를 알아봤다면 이번에 알아볼 녀석은 포매터라는 녀석이다. 내가 알고 싶은건 날짜와 시간, 파일명, 로그레벨, 메세지 이정도가 되겠다. 코드를 만드는 단계를 설명안 해도 될정도로 엄청 간단하므로 그냥 코드로 바로 알아보도록하자.

“`python
import logging
import logging.handlers

# 로거 인스턴스를 만든다
logger = logging.getLogger(‘mylogger’)

# 포매터를 만든다
fomatter = logging.Formatter(‘[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s’)

# 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
fileHandler = logging.FileHandler(‘./myLoggerTest.log’)
streamHandler = logging.StreamHandler()

# 각 핸들러에 포매터를 지정한다.
fileHandler.setFormatter(fomatter)
streamHandler.setFormatter(fomatter)

# 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)

# 로거 인스턴스로 로그를 찍는다.
logger.setLevel(logging.DEBUG)
logger.debug(“===========================”)
logger.info(“TEST START”)
logger.warning(“스트림으로 로그가 남아요~”)
logger.error(“파일로도 남으니 안심이죠~!”)
logger.critical(“치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!”)
logger.debug(“===========================”)
logger.info(“TEST END!”)
“`

위의 코드를 실행하면 아래와 같이 나온다.

“`shell
[DEBUG|loggingFormatter.py:24] 2014-09-02 20:39:46,630 > ===========================
[INFO|loggingFormatter.py:25] 2014-09-02 20:39:46,630 > TEST START
[WARNING|loggingFormatter.py:26] 2014-09-02 20:39:46,630 > 스트림으로 로그가 남아요~
[ERROR|loggingFormatter.py:27] 2014-09-02 20:39:46,630 > 파일로도 남으니 안심이죠~!
[CRITICAL|loggingFormatter.py:28] 2014-09-02 20:39:46,631 > 치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!
[DEBUG|loggingFormatter.py:29] 2014-09-02 20:39:46,631 > ===========================
[INFO|loggingFormatter.py:30] 2014-09-02 20:39:46,631 > TEST END!
“`

이처럼 포매터의 값만 이리저리 바꿔주면, 내가 원하는 대로 로그를 남길 수 있다!
포매터에 들어가는 변수의 문자열은 아래 링크에서 확인하길 바란다.

[logrecord-attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes)

다음으로 가보자.

### 테스트 환경과 프로덕션 환경에서 로그 레벨을 다르게 하고 싶을경우

나는 테스트환경과 프로덕션 환경을 구분하기 위해서 처음에 서버를 세팅할 때 환경변수를 심어놓는다. nodejs모듈에서 사용되는(expressjs) NODE_ENV라는 환경변수명이 있는데 이 값을 미리 테스트 서버와 알파서버, 프로덕션 서버에 각각 다른 값으로 설정을 해둔다. 여기서는 로컬 개발 머신과 테스트 서버만 있다고 가정하고 예제를 만들어봤다.

“`python
import os
import logging
import logging.handlers

# 로거 인스턴스를 만든다
logger = logging.getLogger(‘mylogger’)

# 포매터를 만든다
fomatter = logging.Formatter(‘[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s’)

# 환경변수를 읽어서 로깅 레벨과 로그를 남길 파일의 경로를 변수에 저장한다
if (os.environ[‘NODE_ENV’] == ‘local’):
loggerLevel = logging.DEBUG
filename = ‘/tmp/test.log’
elif(os.environ[‘NODE_ENV’] == ‘test’):
loggerLevel = logging.DEBUG
filename = ‘/home/www/log/testServer.log’
else:
loggerLevel = logging.INFO
filename = ‘/home/www/log/server.log’

# 스트림과 파일로 로그를 출력하는 핸들러를 각각 만든다.
fileHandler = logging.FileHandler(filename)
streamHandler = logging.StreamHandler()

# 각 핸들러에 포매터를 지정한다.
fileHandler.setFormatter(fomatter)
streamHandler.setFormatter(fomatter)

# 로거 인스턴스에 스트림 핸들러와 파일핸들러를 붙인다.
logger.addHandler(fileHandler)
logger.addHandler(streamHandler)

# 로거 인스턴스로 로그를 찍는다.
logger.setLevel(loggerLevel)
logger.debug(“===========================”)
logger.info(“TEST START”)
logger.warning(“파일 명과 로깅 레벨을 각각 환경마다 다르게 남도록 했어요.”)
logger.debug(“디버그 로그는 테스트 환경과 로컬 피씨에서남 남는 답니다.”)
logger.critical(“치명적인 버그는 꼭 파일로 남기기도 하고 메일로 발송하세요!”)
logger.debug(“===========================”)
logger.info(“TEST END!”)
“`

환경변수 `NODE_ENV`의 값에 따라 로그가 남는 파일의 경로와 로깅 레벨이 달라졌습니다~

### 파일로 로그를 남기는 경우 파일이 너무 커지면 자동으로 새로운 파일을 만들어 줬으면…

보통 이런거는 `shell` 스크립트를 스케줄러로 돌려서 자동으로 돌리거나 하는데, 파이썬에는 `RotatingFileHandler`라는 놈이 이미 만들어져 있다. 그냥 가져다 쓰면 된다. 정말 감동적인 모듈인듯!

이전에 만들어둔 파일 핸들러를 `RotatingFileHandler`로 교체해보자.

위에 있는 fileHandler부분만 아래 코드로 교체하면 된다.
“`python
fileMaxByte = 1024 * 1024 * 100 #100MB
fileHandler = logging.handlers.RotatingFileHandler(filename, maxBytes=fileMaxByte, backupCount=10)
“`

maxBytes 파라메터는 한개의 파일의 최대 바이트 수 이고, backupCount는 몇개까지 백업파일을 남길것인지 세팅하는 파라메터이다.
위의 세팅 대로라면 100MB 짜리 파일을 10개까지 남기겠다. 라는 의미가 된다. 이제 로그 파일의 용량이 엄청나게 커져서 서버에 용량이 부족할까 걱정하지 않아도 된다~ 야호~~!

눈치가 빠른 사람이라면 `logging.handlers` 아래에 다른 핸들러들도 많겠구나~ 라는 생각이들것이다.

[logging.handlers](https://docs.python.org/3/library/logging.handlers.html#module-logging.handlers) 링크를 타고 가보면 많은 핸들러들을 볼 수가 있다.

어지간한 기능은 다 넣어본것 같은데 기존에 없는 기능을 추가할려면 어떻게 하지?!

### 확장이 쉬우면 좋겠다!

에러가 났을 때 mongodb에 그 정보를 저장했으면 좋겠다! 어떻게 하지?

일단 쉬운 방법은 나보다 똑똑한 사람이 만들어 놓은 것을 쓰면 된다. 요즘 세상이 참 좋은 세상이라 구글로 찾으면 내가 생각한건 다있다. ㅎㅎ 근데 가끔 이렇게 찾아도 내 마음에 쏙~ 안들 수도 있다. 그럴 때는 한번 만들어보는 것도 힘들긴 하지만, 도움이 될 때가 많다.

그런 의미에서 다른분들도 이미 뜬 삽이겠지만, 나도 한삽을 더 해보려고 한다. 진짜 기본기능만 되는걸 하나 만들어보자.

참고로, mongodb 모듈로 pymongo가 설치되어 있어야 한다. `pip3 install pymongo`로 간단히 설치가능하다.
mongodb도 물론 설치가 되어있어야한다. 해당 내용은 이글과는 크게 관계없으므로 생략하겠다.

핸들러를 만드는 순서는 아래와 같다.

1. mongodb에 로그를 저장할 수 있도록 handler를 만든다.
2. handler는 logging.Handler를 상속하고 emit 메서드를 구현하면된다.

간단히 만들어본 소스는 아래와 같다.
“`python
import logging
from pymongo.connection import Connection
from bson import InvalidDocument

class MongoHandler(logging.Handler):

def __init__(self, db=’mongolog’, collection=’log’, host=’localhost’, port=None, level=logging.NOTSET):
logging.Handler.__init__(self, level)
self.collection = Connection(host, port)[db][collection]

def emit(self, record):
data = record.__dict__.copy()

try:
self.collection.save(data)
except InvalidDocument as e:
logging.error(“Unable save log to mongodb: %s”, e.message)

if __name__ == ‘__main__’:
MongoHandler(‘mongolog’, ‘test’)
“`

테스트용 소스도 만들어보자. 간단히 핸들러를 추가하고 로그를 찍어본다.

“`python
import logging
from mongoLogger import MongoHandler

if __name__ == ‘__main__’:
logger = logging.getLogger(‘mongoTest’)
logger.setLevel(logging.WARNING)
logger.addHandler(MongoHandler(‘mongolog’, ‘log’))

logger.debug(“test debug”)
logger.info(“test info”)
logger.warning(“test warning”)
logger.error(“test error”)
logger.critical(“test critical”)
“`

실행 후 mongodb에 들어가서 확인을 해보면 아래와 같이 WARNING이상의 로그가 저장되어 있다.

“`shell
> db.log.find().pretty();
{
“_id” : ObjectId(“5405c2cc1626051dcf238cfa”),
“stack_info” : null,
“exc_text” : null,
“exc_info” : null,
“processName” : “MainProcess”,
“lineno” : 11,
“msecs” : 891.3910388946533,
“relativeCreated” : 50.26507377624512,
“process” : 7631,
“name” : “mongoTest”,
“pathname” : “mongoTest.py”,
“created” : 1409663692.891391,
“filename” : “mongoTest.py”,
“funcName” : ““,
“threadName” : “MainThread”,
“msg” : “test warning”,
“args” : [ ],
“module” : “mongoTest”,
“levelno” : 30,
“thread” : NumberLong(“140735296762640”),
“levelname” : “WARNING”
}
{
“_id” : ObjectId(“5405c2cc1626051dcf238cfb”),
“stack_info” : null,
“exc_text” : null,
“exc_info” : null,
“processName” : “MainProcess”,
“lineno” : 12,
“msecs” : 891.618013381958,
“relativeCreated” : 50.492048263549805,
“process” : 7631,
“name” : “mongoTest”,
“pathname” : “mongoTest.py”,
“created” : 1409663692.891618,
“filename” : “mongoTest.py”,
“funcName” : ““,
“threadName” : “MainThread”,
“msg” : “test error”,
“args” : [ ],
“module” : “mongoTest”,
“levelno” : 40,
“thread” : NumberLong(“140735296762640”),
“levelname” : “ERROR”
}
{
“_id” : ObjectId(“5405c2cc1626051dcf238cfc”),
“stack_info” : null,
“exc_text” : null,
“exc_info” : null,
“processName” : “MainProcess”,
“lineno” : 13,
“msecs” : 891.7689323425293,
“relativeCreated” : 50.642967224121094,
“process” : 7631,
“name” : “mongoTest”,
“pathname” : “mongoTest.py”,
“created” : 1409663692.891769,
“filename” : “mongoTest.py”,
“funcName” : ““,
“threadName” : “MainThread”,
“msg” : “test critical”,
“args” : [ ],
“module” : “mongoTest”,
“levelno” : 50,
“thread” : NumberLong(“140735296762640”),
“levelname” : “CRITICAL”
}
“`

### 결론

파이썬에서는 로그를 남기기 위해서 뭘쓸까 고민할 필요가 전혀 없다. 표준 라이브러리가 워낙에 잘되어 있고, 확장 또한 쉽기 때문에 별다른 고민없이 `logging` 모듈만 잘 공부하면 된다. 나도 필요해서 찾아보고 공부해본 것이지만, 위에서 소개한 것 이외에도 많은 기능들을 가지고 있으므로 아마 거의 대부분의 경우에는 표준 logging모듈로도 충분할 것으로 생각된다.

관심이 있는 사람은 [logging-cookbook](https://docs.python.org/3/howto/logging-cookbook.html#logging-cookbook) 페이지를 참고하도록 하자.

[/markdown]

Django tutorial1

[markdown]
#django 튜토리얼1

###쟝고의 기능들
– ORMapper
– Automatic admin interface
– Elegant URL design
– Template system
– cache system
– Internationalization

###쟝고 설치됐는지 확인하기

`python -c “import django; print(django.get_version())”`

###project 만들기

`django-admin.py startproject mysite`

위에꺼 실행하면 아래와 같은 디렉토리 & 파일이 생성됨

“`shell
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
“`

– manage.py : 커맨드라인 유틸리티. 자세한 것은 다음링크에서 확인가능 [django-admin.py and manage.py](https://docs.djangoproject.com/en/1.6/ref/django-admin/)
– mysite 디렉토리 안의 mysite : 디렉토리가 실제 만들게될 프로젝트의 디렉토리
– mysite/settings.py : 쟝고프로젝트 설정파일. 자세한 것은 다음 링크에서 [Django settings](https://docs.djangoproject.com/en/1.6/topics/settings/)
– mysite/urls.py : 쟝고프로젝트의 URL정의 파일. 자세한 것은 다음 링크에서 확인 [URL dispatcher](https://docs.djangoproject.com/en/1.6/topics/http/urls/)
– mysite/wsgi.py : WSGI과 호환되는 웹서버의 시작점. 자세한 것은 다음 링크로 [How to deploy with WSGI](https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/)

##### 서버기동
`python manage.py runserver`

##### 데이터베이스 세팅
디폴트는 sqlite3이고, 다른 데이터베이스를 사용하려면 mysite/settings.py의 `DATABASES` 부분을 수정해주어야한다. (나는 mongodb쓸껀데…) 데이터베이스 세팅에 관한 내용은 다음 링크에 있음 [setting-DATABASE-ENGINE](https://docs.djangoproject.com/en/1.6/ref/settings/#std:setting-DATABASE-ENGINE)

##### 타임존 설정(TIME_ZONE)
나는 대한민국 사람이니 mysite/settings.py 의 TIME_ZONE을 ‘Asia/Seoul’로 변경함. 기본은 ‘America/Chicago’
언어는 귀찮아서 냅둠.

##### 설치된 앱들

settings.py에 보면 INSTALLED_APPS라는 파라메터가 있는데, 현재의 쟝고 인스턴스에서 활성화 된 쟝고 어플리케이션의 이름들을 적어놓은 곳이다. APP은 여러 프로젝트에서 사용가능하고 패키징하고 배포가 가능해서 다른 프로젝트에서도 사용될 수 있다.

디폴트로 포함되어 있는 앱들

– django.contrib.admin – 관리자페이지
– django.contrib.auth – 인증시스템
– django.contrib.contenttypes – 컨텐트타입 프레임웤
– django.contrib.sessions – 세션 프레임웤
– django.contrib.messages – 메세징 프레임웤
– django.contrib.staticfiles – 정적파일을 위한 프레임웤

##### syncdb
`$ python manage.py syncdb`

syncdb 커맨드는 INSTALLED_APPS의 설정을 보고 필요한 데이터베이스 테이블을 생성한다.
처음 실행할때 admin유저를 만들 수 있다.

##### app 만들기
`python manage.py startapp polls`

mysite의 탑레벨 모듈로 polls모듈을 만들자.

디렉토리는 아래와 같이 생겼다.

“`shell
polls/
__init__.py
admin.py
models.py
tests.py
views.py
“`

##### 모델만들기

polls/models.py 를 아래와 같이 고친다.

“`python
from django.db import models

class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField(‘date published’)

class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
“`

하나의 클래스는 테이블과 매핑되고 하나의 변수는 하나의 필드와 매핑이 된다. 그리고 Choice클래스에 외래키를 지정했는데 django는 관계형디비의 모든 관계(다대다 일대다 일대일)를 지원한다.

##### 모델 활성화하기

mysite/settings.py의 파일을 다시 열어서 아래와 같이 수정한다.

“`python
INSTALLED_APPS = (
‘django.contrib.admin’,
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.messages’,
‘django.contrib.staticfiles’,
‘polls’,
)
“`

이제 쟝고가 polls앱이 포함된 것을 알게됐다. 다음으로 아래의 명령을 실행해보자.

`$ python manage.py sql polls`

그러면 아래와 같은 SQL문이 콘솔에 찍힌다.

“`sql
BEGIN;
CREATE TABLE “polls_poll” (
“id” integer NOT NULL PRIMARY KEY,
“question” varchar(200) NOT NULL,
“pub_data” datetime NOT NULL
)
;
CREATE TABLE “polls_choice” (
“id” integer NOT NULL PRIMARY KEY,
“poll_id” integer NOT NULL REFERENCES “polls_poll” (“id”),
“choice_text” varchar(200) NOT NULL,
“votes” integer NOT NULL
)
;
“`

– 콘솔에 찍힌 SQL은 우리가 지정한 혹은 기본인 sqlite3 데이터 베이스에 사용되는 SQL문임을 알 수 있다.
– 테이블명은 자동적으로 app(polls)과 소문자 모델클래스명을 결합하여 만들어진다.
– 프라이머리키(IDs)는 자동으로 추가된다. (오버라이딩 가능)
– 관례로, 쟝고는 ‘_id’를 외래키명에 붙인다.
– 외래키 관계는 REFFERNCE문으로 정확히 만들어졌다.
– `sql`명령어는 실제로 SQL을 실행하지는 않고 단순히 출력만 해준다.

관심있으면 아래 명령어도 테스트해보길

– python manage.py validate – 모델이 유효한지 체크해줌
– python manage.py sqlcustom polls – 테이블 변경이나 제약수정을 위한 명령어를 출력
– python manage.py sqlclear polls – 테이블 삭제를 위한 쿼리를 출력
– python manage.py sqlindexes polls – 인덱스 생성문을 출력
– python manage.py sqlall polls – 해당앱에 사용된 모든 쿼리를 합쳐서 출력

`syncdb`로 모델의 테이블을 생성하자! (syncdb는 create만 해주고 alter는 해주지 않는다. 만약에 컬럼명이 변경된 경우 수동으로 고쳐야함)

### API로 놀기

`$ python manage.py shell` managy.py가 DJANGO_SETTINGS_MODULE의 환경을 세팅해줌

“`python
>>> from polls.models import Poll, Choice
>>> Poll.objects.all()
[]

>>> from django.utils import timezone
>>> p = Poll(question=”What’s new?”, pub_date=timezone.now())
>>> p.save()
>>> p.id
1

>>> p.question
“What’s new?”
>>> p.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=)

>>> p.question = “What’s up?”
>>> p.save()

>>> Poll.objects.all()
[]
“`

마지막에 Poll object라고 나온거는 전혀 도움이 안되니 도움이 되는 문자열로 만들어보자.

polls/models.py 에 다음과 같은 메서드를 추가해주면 됨

“`python
from django.db import models

class Poll(models.Model):
# …
def __unicode__(self): # Python 3: def __str__(self):
return self.question

class Choice(models.Model):
# …
def __unicode__(self): # Python 3: def __str__(self):
return self.choice_text
“`

메서드 하나를 또 추가해보자

“`python
import datetime
from django.utils import timezone
# …
class Poll(models.Model):
# …
def was_published_recently(self):
return self.pub_date >= timezone.now() – datetime.timedelta(days=1)
“`

`python manage.py shell`을 다시실행해서 놀아보자

“`python
>>> from polls.models import Poll, Choice

# Make sure our __unicode__() addition worked.
>>> Poll.objects.all()
[]

>>> Poll.objects.filter(id=1)
[]
>>> Poll.objects.filter(question__startswith=’What’)
[]

# Get the poll that was published this year.
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Poll.objects.get(pub_date__year=current_year)

# Request an ID that doesn’t exist, this will raise an exception.
>>> Poll.objects.get(id=2)
Traceback (most recent call last):

DoesNotExist: Poll matching query does not exist.

# Lookup by a primary key is the most common case, so Django provides a
# shortcut for primary-key exact lookups.
# The following is identical to Poll.objects.get(id=1).
>>> Poll.objects.get(pk=1)

>>> p = Poll.objects.get(pk=1)
>>> p.was_published_recently()
True

>>> p = Poll.objects.get(pk=1)

>>> p.choice_set.all()
[]

>>> p.choice_set.create(choice_text=’Not much’, votes=0)

>>> p.choice_set.create(choice_text=’The sky’, votes=0)

>>> c = p.choice_set.create(choice_text=’Just hacking again’, votes=0)

>>> c.poll

>>> p.choice_set.all()
[, , ]
>>> p.choice_set.count()
3

>>> Choice.objects.filter(poll__pub_date__year=current_year)
[, , ]

>>> c = p.choice_set.filter(choice_text__startswith=’Just hacking’)
>>> c.delete()
“`

여기까지 했으면 인제 파트2로 넘어가도 됨. (튜토리얼은 6개까지 있음)
[/markdown]

3장 얼랭 진짜시작하기

[markdown]
# 3장 진짜 시작하기

[여기](http://learnyousomeerlang.com/starting-out-for-real) 를 요약 정리한 글임을 알려드립니다.

> 얼랭은 상대적으로 작고 심플한 언어이다.(C가 C++보다는 심플한것 처럼)
> 몇가지 기본적인 데이터 타입을 가지고 있음. 이번장에서는 그것들 대부분을 커버해보겠음.
> 앞으로 당신이 작성하게 될 모든 얼랭 프로그램에 사용될 내용을 다루고 있으니 꼭! 읽기를 강추함.

### 숫자

– 얼랭 쉘에서는, 표현식은 공백(빈칸, 개행등)이 뒤따르면 종료되어져야 함. 그렇지 않으면 실행이 안될것임.
– 표현식을 콤마로 구분가능. 그러나 마지막 것의 결과만 보여질것임.

> 얼랭 쉘을 열어서($ erl) 아래의 것들을 쳐보자.

“`
1> 2 + 15.
17
2> 49 * 100.
4900
3> 1892 – 1472.
420
4> 5 / 2.
2.5
5> 5 div 2.
2
6> 5 rem 2.
1
“`

– 얼랭은 integer와 float를 알아서 잘 처리해준다.
– 나누기를 하고 싶을 때 `div`를 나머지를 구하고 싶을 때 `rem`을 사용하시요.
– 수학 명령들은 평범한 우선순위 규칙을 따름(신경안써도 됨)

“`
7> (50 * 100) – 4999.
1
8> -(50 * 100 – 4999).
-1
9> -50 * (100 – 4999).
244950
“`

– 10진수 이외의 진법을 사용하고 싶으면 `진수#값` 이렇게 쓰면 됨
– 기존의 2,8, 16진수 뿐아니라 15진수 36진수(숫자 + 알파벳26자)까지 사용가능

“`
10> 2#101010.
42
11> 8#0677.
447
12> 16#AE.
174
“`

### 불변변수 (Invariable Variables)

– 얼랭으로 산수만 하고 있을 수 없으니 변수를 배워보세.
– 함수형 언어에서는 변수는 변하는 수이어서는 안됨.
– 변수의 기본적인 행위는 아래의 7개의 표현식을 보시길.
– 변수는 항상 `대문자`로 시작하는 것을 잊지말자

“`
1> One.
* 1: variable ‘One’ is unbound
2> One = 1.
1
3> Un = Uno = One = 1.
1
4> Two = One + One.
2
5> Two = 2.
2
6> Two = Two + 1.
** exception error: no match of right hand side value 3
7> two = 2.
** exception error: no match of right hand side value 2
“`
– 위에 6번을 보면 알겠지만, 변수에는 값을 딱 한번만 할당할 수 있음
– 재할당시에 값이 같으면 암말 안하지만, 값이 다른 경우 얼랭 컴파일러가 불평하는 걸 볼 수 있음.
– 좀 더 얘기하면 `=` 연산자는 값을 할당하는 역활 뿐아니라 값이 같은경우 그 값을 리턴하고 다르면 불평을 하게 하는 기능을 가지고 있음.

“`
8> 47 = 45 + 2.
47
9> 47 = 45 + 3.
** exception error: no match of right hand side value 48
“`

– `=` 연산자는 왼쪽에 있는 변수가 값이 할당되지 않은 경우는 오른쪽의 값을 왼쪽의 변수에 할당함. 왼쪽과 오른쪽의 비교 결과는 success가 되서 그 값을 메모리에 저장함.

– `=`연산자가 하는 이런 행동은 `패턴매칭`이라고 하는 것에 기초를 두고 있고, 많은 함수형 프로그래밍 언어에서 가지고 있음. 얼랭은 `=` 요녀석으로 좀더 유연하고 온전한 자신만의 방법을 제공중.
– 변수는 대문자로 시작해야 하지만, `_` 로 시작해도 됨. 그냥 `_` 한글자만 써도 오케이

“`
10> _ = 14+3.
17
11> _.
* 1: variable ‘_’ is unbound
“`

> 얼랭 쉘에서 변수에 잘못된 값을 설정한 경우 `f(변수명)`을 사용하면 변수 초기화 가능. 근데 이함수는 테스트 환경 그것도 쉘에서만 사용가능하고 실환경에서는 사용불가임.

### 아톰(Atoms)

– 변수가 대문자로 시작하면 안되는 단 한가지 이유는 아톰 때문이다.
– 아톰은 리터럴이고 이름과 값을 갖는다.
– 당신이 보는 대로 얻는다. 더이상은 기대하지 말라.
– `cat`이라는 아톰이 의미하는 것은 “cat” 이다.
– 가지고 놀 수 없고, 바꿀 수 없고, 부술 수 없다.
– 소문자로 시작하는 것은 그냥 아톰이다. 더 이상은 없다.
– 작은 따옴표 `’` 로 감싸지는 아톰은 소문자로 시작하지 않아도 된다.
– 아톰이 주로 사용되기 좋은 곳은 데이터와 타입을 짝을 지어야 되는 경우인데, 눈색깔을 표현하기위 해 `BLUE ->1, BROWN ->2, GREEN -> 3, OTHER -> 4` 이런식으로 했었다면, 그냥 아톰으로 `blue`, `brown`, `green`, `other` 단순히 이렇게 하면된다. 만약에 값과 이름이 연결된 것을 정말로 사용하고 싶은 경우는 4장의 `모듈`에서 찾을 수 있다.

“`
1> atom.
atom
2> atoms_rule.
atoms_rule
3> atoms_rule@erlang.
atoms_rule@erlang
4> ‘Atoms can be cheated!’.
‘Atoms can be cheated!’
5> atom = ‘atom’.
atom
“`

– 아톰은 변하지 않는 값을 전달하는 데에는 정말로 좋은 방법이지만, 너무 많은 아톰을 사용하는 것은 문제가 생긴다. “atom table” 이라는 것에서 아톰을 메모리에 올려두고 사용하게 되는데, 아톰 테이블은 GC의 대상이 아니다.
– 그러므로 아톰은 절대로 동적으로 만들어 내지 말라.
– 아래의 아톰들은 예약어로 이미 사용되고 있음.

“`
after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor
“`

### 불린 대수 & 비교 연산자들

“`
1> true and false.
false
2> false or true.
true
3> true xor false.
true
4> not false.
true
5> not (true and true).
false
“`

> `and` 와 `or`은 언제나 연산자 양쪽의 것을 실행한다. 필요한 경우에만 오른쪽의 조건을 판단하고 싶은경우 `andalso`와 `orelse`라는 단축 연산자가 있다.

– 동등과 비동등을 테스트 하는것도 겁나 쉽다. 그런데 다른 언어에서 보던 연산자와는 약간 다르다.

“`
6> 5 =:= 5.
true
7> 1 =:= 0.
false
8> 1 =/= 0.
true
9> 5 =:= 5.0.
false
10> 5 == 5.0.
true
11> 5 /= 5.0.
false
“`

– `==` 대신 `=:=`를 사용
– `!=` 대신 `=/=` 를 사용
– 얼랭은 산술계산에서는 부동소수와 정수를 신경써주지 않는데, 비교를 하면 다르다고 나옴. 이런 경우 사용하는 연산자가 `==`랑 `/=`임
– 비교 연산자는 다른 언어와 동일
– `<` 작다, `>` 크다, `>=` 크거나 같다, `=<` 작거나 같다 - 보통 작거나 같다는 `<=` 이렇게 쓰는데...이건 신택스 에러 발생 `5 + llama`를 실행하면 우째 되나? ``` 12> 5 + llama.
** exception error: bad argument in an arithmetic expression
in operator +/2
called as 5 + llama
“`

– 얼랭은 기본타입을 이상하게 사용하는 것을 안좋아함.

“`
13> 5 =:= true.
false
14> 0 == false.
false
15> 1 < false. true ``` - 얼랭은 다른 타입끼리 비교할 수 있음. - 다른 언어에서는 14는 true이고 15는 false여야 하는데. 얼랭에서는 아니다. 잘 기억해두길! > 각 엘레먼트의 올바른 비교 순서는 다음과 같다.
> `number < atom < reference < fun < port < pid < tuple < list < bit string` > 얼랭을 만든 사람들중 한사람인 조 암스트롱에게 물어보니: “실제적으로 순서는 안중요해요. 그렇지만 전체적인 순서가 잘 정의되어 있다는 건 중요하죠.” 라고 했단다.

### 튜플

– 튜플은 데이터를 조직하는 방법이다.
– 얼랭에서 튜플은 요렇게 적는다. `{Element1, Element2, …, ElementN}`
– 좌표에 대한 튜플을 만든다면 아래와 같이 할 수 있다.

“`
1> X = 10, Y = 4.
4
2> Point = {X,Y}.
{10,4}
“`

– 만약에 Point에서 X값만 필요한 경우는 어떻게 하면되나? => 얼랭에서 값을 변수에 할당할 때 값은 값을 할당을 하는 거면 여러번 할당해도 에러가 안났다는 것을 이용하면 된다. 하기 전에 `f()`로 `X, Y, Point`의 값을 초기화 해줘야함.

“`
3> Point = {4,5}.
{4,5}
4> {X,Y} = Point.
{4,5}
5> X.
4
6> {X,_} = Point.
{4,5}
“`

– 6번 표현식은 익명 변수 `_`를 사용했다. `_`는 항상 할당되어 있지 않다고 여겨지고, 패턴매칭에서 와일드 카드로 사용된다. 튜플의 패턴매칭에서는 좌우의 인자의 숫자가 같아야 한다.

– 튜플은 하나의 값만 다룰 때에도 유용하다.
– 섭씨와 화씨에 대한 값을 가지고 있는 온도를 표현하는 튜플을 만들려면?
“`
9> Temperature = 23.213.
23.213
10> PreciseTemperature = {celsius, 23.213}.
{celsius,23.213}
11> {kelvin, T} = PreciseTemperature.
** exception error: no match of right hand side value {celsius,23.213}
“`

– 11번에서 에러가 났지만 의도한것과 정확히 일치한다. 패턴매칭이 먹혀들었다.
– `=` 연산자로 `{kelvin, T}` 와 `{celsius,23.213}`를 비교했다. 심지어 T는 값이 할당이 되지도 않았다.
– 얼랭은 `celsius`와 `kelvin` 아톰이 다르다고 보았고 에러를 발생시켜서 코드의 실행을 정지 시켰다.
– 하나의 아톰에 하나의 값이 있는 튜플을 `tagged tuple`이라고 부른다.
– 튜플내의 원소는 어떤 타입이어도 상관없다. 심지어 다른 튜플이 들어가도 된다.

“`
12> {point, {X,Y}}.
{point,{4,5}}
“`

### 리스트!

– 의심할 여지 없이 얼랭에서 가장 많이 쓰이는 데이터 타입임.
– 아무거나 담을 수 있다. (숫자, 아톰, 튜플, 다른 리스트)
– `[Element1, Element2, …, ElementN]` 요렇게 사용

“`
1> [1, 2, 3, {numbers,[4,5,6]}, 5.34, atom].
[1,2,3,{numbers,[4,5,6]},5.34,atom]
2> [97, 98, 99].
“abc”
“`

– 2번의 결과는 사람들이 얼랭에서 가장 놀라는 부분임. 왜냐면 아래를 보시라.

“`
3> [97,98,99,4,5,6].
[97,98,99,4,5,6]
4> [233].
“é”
“`

– 얼랭은 리스트안의 숫자중에 글자를 나타내지 않는 숫자가 최소 하나이상 있어야 숫자를 출력한다.
– 얼랭에는 진짜 스트링은 없다!
– 리스트를 두개를 합치려면 `++` 연산자를 사용하고 제거 하려면 `–`연산자를 사용하라

“`
5> [1,2,3] ++ [4,5].
[1,2,3,4,5]
6> [1,2,3,4,5] — [1,2,3].
[4,5]
7> [2,4,2] — [2,4].
[2]
8> [2,4,2] — [2,4,2].
[]
“`

– `++`, `–`는 연산 순서가 오른쪽 우선이다. 오른쪽에서 왼쪽으로 계산된다.

“`
9> [1,2,3] — [1,2] — [3].
[3]
10> [1,2,3] — [1,2] — [2].
[2,3]
“`

– 리스트의 첫번째 엘리먼트는 Head라고 하고, 남은 것들은 Tail이라고 한다. 내장 함수(Built In Functions:BIF)로 그것들을 가져와 보자.

“`
11> hd([1,2,3,4]).
1
12> tl([1,2,3,4]).
[2,3,4]
“`

> 내장함수(BIF)들은 퍼포먼스 향상을 위해 C같이 빠른 언어로 구현되어 있다.

– Head를 더하거나 접근 하는 것은 빠르고, 효과적이다.
– head와 tail을 분리하는 멋진 패턴 매칭이 있는데 바로 이것이다. `[Head|Tail]` 여기 새로운 head를 리스트에 추가하는 예제가 있다.

“`
13> List = [2,3,4].
[2,3,4]
14> NewList = [1|List].
[1,2,3,4]
“`
아래는 리스트로 부터 Head와 Tail을 가르는 예제
“`
15> [Head|Tail] = NewList.
[1,2,3,4]
16> Head.
1
17> Tail.
[2,3,4]
18> [NewHead|NewTail] = Tail.
[2,3,4]
19> NewHead.
2
“`

– `|`는 cons 연산자(컨스트럭터)라고도 한다. 실제로 어떤한 리스트도 cons와 값만 있으면 만들 수 있다.

“`
20> [1 | []].
[1]
21> [2 | [1 | []]].
[2,1]
22> [3 | [2 | [1 | []] ] ].
[3,2,1]
“`

위의 예제는 어떤 리스트도 다음의 공식으로 만들 수 있다는 것을 말해준다.
`[Term1| [Term2 | [… | [TermN]]]]…`
– 리스트는 Head와 그것을 뒤따르는 Tail을 재귀적으로 정의한 것으로 여겨질 수 있다.
– 리스트는 지렁이랑 비슷한다. 머리를 자르면 지렁이가 두마리가 되는 이치이다.

![지렁이](http://learnyousomeerlang.com/static/img/worm.png)
– 얼랭의 리스트는 때때로 헷갈릴 수 있는데, 이 개념에 익숙해지려면 다음 예제를 다 읽어보라.(힌트 : 모두 같은 리스트이다.)

“`
[a, b, c, d]
[a, b, c, d | []]
[a, b | [c, d]]
[a, b | [c | [d]]]
[a | [b | [c | [d]]]]
[a | [b | [c | [d | [] ]]]]
“`

– 위의 것이 이해가 되었다면 리스트 컴프리헨션을 다룰 준비가 됐다는 것이다.

> `[1|2]` 이런 방법으로 리스트를 표현 하는 것을 ‘부적합한 리스트’라고 한다. 부적합한 리스트는 패턴매칭은 성공하지만, 얼랭의 표준 함수에서 사용될 때 실패하는데(length()같은거), 얼랭의 함수는 적합한 리스트를 받게 되어 있기 때문이다. 적합한 리스트는 가장 마지막 셀에 빈 리스트를 포함한다. `[2]`이렇게 선언하면 해당 리스트는 자동으로 적합한 리스트로 변환된다. `[1|[2]]`도 잘 동작한다! 부적합한 리스트는 문법적으로는 유효하지만, 아주 많은 제약이 있다.

### 리스트 컴프리헨션 (리스트 조건제시법, 리스트 내포)

– 리스트 컴프리헨션은 리스트를 만들거나 수정하는 방법들이다.
– 리스트를 다루는 다른 방법 보다 프로그램을 짧고 이해하기 쉽게 해주는 방법이다.
– 처음에는 이해하기가 힘들긴한데, 노력을 할 만한 가치가 있다.
– 개념을 집합에서 가져왔다.

다음과 같은 집합 표기가 있다고 하자.
![](http://learnyousomeerlang.com/static/img/set-comprehension.png) x는 실수이고, 자기자신과 자기자신을 제곱한 값이 같은 집합을 의미하는데, 그 결과과 되는 집합은 `{0,1}`이다. 다른 집합 표기의 예제를 보자. 축약해서 `{x: x > 0}` 이라고 쓰겠다. 우리가 원하는 것은 x > 0인 값이다.

– 리스트 컴프리헨션은 다른 집합으로 부터 집합을 만들어낸다. `{2n : n in L}`이라는 집합이 있고, L이라는 리스트가 `{1,2,3,4}`일 때 이것의 얼랭에서의 구현은 아래와 같다.

“`
1> [2*N || N <- [1,2,3,4]]. [2,4,6,8] ``` 수학식과 얼랭의 표기법을 비교해보면 크게 많이 다르지 않다. 중괄호({})는 대괄호([])가 되고 콜론(:)은 두개의 파이프(||)가 되고 'in'이라는 글자는 화살표(<-)가 된다. 단순히 기호만 바꾸는 것으로 같은 로직을 유지할 수 있다. 위의 예에서 `N`은 [1,2,3,4]각 각의 값에 순차적으로 패턴매칭된다. 화살표(<-)는 `=` 연산자와 동일하게 작동하지만, 예외가 발생한 경우 예외를 무시한다. 리스트 컴프리헨션에 불린 값들을 이용해서 제약들을 추가 가능하다. 1부터 10까지의 숫자에서 짝수만 가져오고 싶다면 아래와 같이 할 수 있다. ``` 2> [X || X <- [1,2,3,4,5,6,7,8,9,10], X rem 2 =:= 0]. [2,4,6,8,10] ``` `x rem 2 =:= 0`는 숫자가 짝수인지 체크한다. 좀 더 실용적인 예제로 식당의 메뉴중에 $3와 $10 사이의 메뉴에는 7%의 세금이 붙는 경우를 살펴보자. ``` 3> RestaurantMenu = [{steak, 5.99}, {beer, 3.99}, {poutine, 3.50}, {kitten, 20.99}, {water, 0.00}].
[{steak,5.99},
{beer,3.99},
{poutine,3.5},
{kitten,20.99},
{water,0.0}]
4> [{Item, Price*1.07} || {Item, Price} <- RestaurantMenu, Price >= 3, Price =< 10]. [{steak,6.409300000000001},{beer,4.2693},{poutine,3.745}] ``` 위의 예제로 숫자가 딱떨어지게는 안나왔지만, 얼랭에서의 리스트 컴프리핸션을 다음과 같은 방식으로 사용하면 될 것이라는 감은 올 것이다. `NewList = [Expression || Pattern <- List, Condition1, Condition2, ..., ConditionN]` `Pattern <- List`는 제너레이터 표현이라고 부른다. ``` 5> [X+Y || X <- [1,2], Y <- [2,3]]. [3,4,4,5] ``` - 위의 예제는 `1+2`, `1+3`, `2+2`, `2+3` 이렇게 실행된다. - 리스트 컴프리헨션의 레시피를 좀 더 일반화 할 수 있다. `NewList = [Expression || GeneratorExp1, GeneratorExp2, ..., GeneratorExpN, Condition1, Condition2, ... ConditionM]` - 제너레이터 표현은 패턴매칭과 연결되어서 필터처럼 동작한다는 것을 주의하시라. ``` 6> Weather = [{toronto, rain}, {montreal, storms}, {london, fog},
6> {paris, sun}, {boston, fog}, {vancouver, snow}].
[{toronto,rain},
{montreal,storms},
{london,fog},
{paris,sun},
{boston,fog},
{vancouver,snow}]
7> FoggyPlaces = [X || {X, fog} <- Weather]. [london,boston] ``` - 리스트 'Weather'의 엘리먼트와 {X, fog}가 매칭되지 않아도 그냥 무시한다. `=`이었으면 에러가 발생했을 것이다. ### 비트 문법 - 대부분의 언어에는 숫자, 아톰, 튜플, 리스트, 구조체 등등을 다루는 것을 지원한다. - 그리고 바이너리 데이터를 다루는 것도 대부분 지원한다. - 얼랭은 패턴매칭에서 한단계 더 나아가서 바이너리 데이터를 다루는 것에 대한 추상화를 지원한다. - 비트를 다루는 것은 유니크한 문법을 가지고 있고 처음 보기엔 이상하지만, 비트와 바이트가 일반적으로 어떻게 동작하는지 안다면 이해가 될 것이다. **어렵다면 남은 부분들은 뛰어 넘어도 된다.** 비트 문법은 <<와 >>로 바이너리 데이터를 감싼후, 콤마로 구분된 읽을 수 있는 세그먼트 별로 나눈다. 하나의 세그먼트는 바이너리의 비트의 시퀀스이다.(꼭 바이트로 구분되어야 되는 건 아니지만, 그게 디폴트이다.) 오렌지의 색을은 포토샵이나 이런데서 #RRGGBB형식으로 나타내면 #F09A29 이런식으로 표현 가능하다. 이걸 얼렝으로 표현하면 다음과 같다.

“`
1> Color = 16#F09A29.
15768105
2> Pixel = <>.
<<240,154,41>>
“`

위의 예제는 기본적으로 “바이너리 값 #F09A29을 24비트 공간에 놔주시요.(Red 8비트, Green 8비트, Blue 8비트)”라는 뜻이다. 값은 파일로 쓰는 것도 가능! 파일은 바이너리 파일로 저장되므로 텍스트에디터에서는 이상한 글자로 나오지만, 얼랭에서 다시 읽으면 제대로 <<240,154,41>>이라고 나옴

더 흥미로운 것은 바이너리를 패턴매치로 풀어낼 수 있는(to unpack) 기능이 있다는 것이다.

“`
3> Pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
4> <> = Pixels.
** exception error: no match of right hand side value <<213,45,132,64,76,32,76, 0,0,234,32,15>>
5> <> = Pixels.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
“`

위의 예제의 3번 명령에서 우리는 4개의 RGB색의 픽셀을 바이너리로 지정했다.
4번 명령에서는 바이너리에서 4개의 데이터로 풀어낼려고 했으나 에러가 발생! 왜냐하면 실제로 12개의 값이 있는데 4개로 받아서 그렇다. 그래서 5번 명령에서 각각의 값이 24비트데이터인것을 얼랭에게 알려줬는데 그것이 바로`Var:24`의 뜻이다. 우리는 하나의 픽셀을 다시 각각의 색으로 풀어낼 수 있다.

“`
6> <> = <>.
<<213,45,132>>
7> R.
213
“`

첫번째 말고 다른것도 다 필요한 경우는? 얼랭은 이런 경우를 위해 더 많은 문법설탕과 패턴매칭을 할 수 있다.

“`
8> <> = Pixels.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
9> R.
213
“`

멋지지 않은가? 얼랭은 한가지 이상의 방법으로 바이너리 세그먼트를 접근한다.
아래의 것은 모두 유효하다.

“`
Value
Value:Size
Value/TypeSpecifierList
Value:Size/TypeSpecifierList
“`

> __Type__
> 가능한 값들 : `integer | float | binary | bytes | bitstring | bits | utf8 | utf16 | utf32`
> 이것은 바이너리 데이터로 사용되는 것을 종류를 말한다. ‘bytes’는 단순히 ‘binary’를 줄여서 부르는 것임을 주의하라. 마찬가지로 ‘bits’는 ‘bitstring’의 줄임말이다. 타입이 없는 경우는 얼랭은 ‘integer’ 타입으로 가정한다.

> __Signedness__
> 가능한 값들 : `signed | unsigned`
> 타입이 integer인 경우에 매칭을 위해 사용되는 것이다. 디폴트는 ‘unsigned’

> __Endianness__
> 가능한 값들 : `big | little | native`
> Endianness는 integer, utf16, utf32, float를 위한 것이다. 시스템이 어떻게 바이너리 데이터를 잃는가와 관계가 있다. 예를 들어 BMP이미지 헤더의 포맷은 4바이트의 인티져로 되어 있다. 파일이 72바이트 인경우 little-endian시스템은 이것을 `<<72,0,0,0>>`으로 나타낸다. 그리고 big-endian은 <<0,0,0,72>>로 나타낸다. 하나는 ’72’로 읽히고 다른 하나는 ‘1207959552’로 읽어진다. 그러므로 올바른 endianness를 잘 체크해야한다. ‘native’옵션은 런타입에 little-endian인지 big-endian인지 선택한다. 디폴트는 ‘big’

> __Unit__
> unit:Integer로 쓰여짐
> 이것은 비트들에서 각 세그먼트의 사이즈를 말한다. 허용범위가 1..256까지인 쎗이고 인티저는 디폴트 1로, float와 bit string은 8비트 바이너리가 디폴트이다. utf8, utf16, uft32는 unit이 필요없다. unit 사이즈는 byte-alignment의 보장에 사용되곤 한다.

TypeSpecifierList는 내장되어 있고 ‘-‘로 구분되는 어트리뷰트이다.

“`
10> <> = <<-44>>.
<<"Ô">>
11> X1.
212
12> <> = <<-44>>.
<<"Ô">>
13> X2.
-44
14> <> = <<-44>>.
<<"Ô">>
15> X2.
-44
16> <> = <<72>>.
<<"H">>
17> N.
72
18> <> = <<72>>.
<<"H">>
19> <> = <<72,0,0,0>>.
<<72,0,0,0>>
20> Y.
72
“`

바이너리 데이터를 읽는 여러가지 방법이 있다는 것을 이제 알았을 것이다. 좀 헷갈리지만, 대부분의 언어에서 제공하는 다른 툴들에 비해서는 아직 많이 심플하다.

표준 바이너리 명령어들(시프트연산, ‘and’, ‘or’, ‘xor’, ‘not’)또한 얼랭에 포함 되어 있다. `bsl`(Bits Shift Left), `bsr`(Bit Shift Right), `band`, `bor`, `bxor`, `bnot`

“`
2#00100 = 2#00010 bsl 1.
2#00001 = 2#00010 bsr 1.
2#10101 = 2#10001 bor 2#00101.
“`

이러한 문법과 일반적인 비트구문을 사용한다면, 바이너리 데이터의 파싱과 패턴매칭은 누워서 떡먹기이다. 어떤 사람은 TCP세그먼트를 다음과 같은 코드로 파싱 하기도 했다.

“`
<> = SomeBinary.
“`

같은 로직을 비디오 인코딩, 이미지, 다른 프로토콜의 적용등등에 사용할 수 있다.

> __사이다를 너무 많이 마시지는 마세요__
얼랭은 C나 C++에 비해서 느리다. 만약에 당신이 참을성이 부족한 사람이라면 얼랭으로 비디오 컨버팅이나 이미지 변환 작업을 하는 것은 좋지 않은 아이디어이다. 그럼에도 불구하고 바이너리 문법은 엄청나게 재미있다. 얼랭은 단지 무거운 수 덩어리들을 다루기에는 좋지 않을 뿐이다.

> 그렇지만 위와 같은 수치처리가 필요하지 않다면 얼랭은 여전히 이벤트 반응성 또는 메세지 전달 (극단적으로 가벼운 아톰도 도움을 준다.) 어플리케이션을 위해서는 어마무시하게 빠르다. 이벤트를 백만분의 일초단위로 처리할 수 있기때문에 소프트한 리얼타임 어플리케이션에 적합하다.

바이너리 표기는 다른 면도 가지고 있는데, 비트문자열이다. 비트문자열은 리스트로 표현하는 문자열과 같고, 언어에 강하게 연결되어 있지만, 비트문자열은 메모리 공간을 좀더 효율적으로 사용할 수 있게해준다. 왜냐하면 리스트로 된 문자열은 링크드리스트로 되어 있지만, 비트 문자열은 C 배열과 비슷하다. 비트 스트링의 문법은 다음과 같다.`<<"this is a bit string!">>` 리스트로 된 문자열에 비해서 메모리 절약 및 패턴 매칭을 통한 조작도 간단하기 때문에 결과적으로 바이너리 스트링을 사용한다.

> `주의` 아톰이 가볍다고 스트링 대신 사용해서는 안된다. 문자열은 쪼갠다든지, 정규식을 활용한든지 해서 여러가지로 다뤄질 수 있지만, 아톰은 그렇게 될 수 없다.

### 바이너리 컴프리헨션

리스트 컴프리헨션이 리스트를 위한 것이듯, 바이너리 컴프리헨션은 비트 문법을 좀 더 짧고 간결하게 하기 위한 것이다. 바이너리 컴프리헨션은 상대적으로 얼랭에서 신기능에 속한다. 그래서 R13B버젼 이전버젼은 모듈을 불러와야한다. 그 이후는 표준으로 포함되어 있다.

“`
1> [ X || <> <= <<1,2,3,4,5>>, X rem 2 == 0].
[2,4]
“`

리스트 컴프리헨션과 다른 점은 `<-`이 `<=`로 바뀌었다는 점과 리스트([])대신 바이너리 (<<>>)를 사용했다는 것 뿐이다. 위에서 RGB값을 각각의 픽셀로 나타내는 것을 연습했었는데, 구조가 커질 수록 유지보수가 힘들어진다. 이럴경우 바이너리 컴프리헨션을 사용하면 좀 더 깔끔해진다.

“`
2> Pixels = <<213,45,132,64,76,32,76,0,0,234,32,15>>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
3> RGB = [ {R,G,B} || <> <= Pixels ]. [{213,45,132},{64,76,32},{76,0,0},{234,32,15}] ``` 온전한 바이너리 컴프리헨션은 기본적으로 바이너리 데이터를 인티저를 가지고 있는 튜플로 변환시킨다. 또다른 바이너리 컴프리헨션은 정확히 반대의 기능을 한다 ``` 4> << <> || {R,G,B} <- RGB >>.
<<213,45,132,64,76,32,76,0,0,234,32,15>>
“`

결과적으로 생성되는 바이너리는 제너레이터가 바이너리를 리턴할 경우 정확히 사이즈를 명시하지 않으면 안되니 주의하도록 하자.

“`
5> << <> || Bin <- [<<3,7,5,4,7>>] >>.
** exception error: bad argument
6> << <> || Bin <- [<<3,7,5,4,7>>] >>.
<<3,7,5,4,7>>
“`

위와같이 정해진 사이즈를 주고 바이너리 컴프리헨션을 사용할 수도 있다.

> 이글을 쓰는 지금은 바이너리 컴프리헨션은 아직 잘사용되지 않고 문서도 매우 빈약하다. 그러므로 기본적인 동작을 이해하는 것에 더 의미를 두자. 비트 문법에 대해 더 알려면 [얼랭의 스펙](http://user.it.uu.se/~pergu/papers/erlang05.pdf)을 읽어보는 것도 좋다.
[/markdown]

라인 원정대

라인 원정대
http://tech.naver.jp/blog/?p=2852

요약
– 2011년 6월에 서비스를 시작해서 현재 4억명의 유저가 사용하는 메신저가 됐다.
– 2012년 12월 스페인에서는 왓츠앱의 유료화에 반발해서 많은 유저가 라인을 선택함으로써 짧은 기간에 유저가 급증. 그러나 메세지 지연, 푸쉬 연착, 밧데리 소모 문제등이 발생! 개발자 3인을 긴급 출장을 보내서 현지에서 문제를 분석. 의미있는 분석결과의 반영을 2013년 4월에 적용.
– 스페인 출장을 통해 유저의 입장에서 서비스 개선점을 찾는 노력을 계속, 그래서 동남 아시아, 중남미, 호주, 중동, 미국, 러시아등등 개발자가 직접 방문해서 현지에서 심카드를 구매해서, 거리, 호텔, 지하철, 데파트의 지하 깊숙한곳, 레스토랑등에서 테스트함. 이런 출장 멤버를 [라인원정대]라고 부르게 됨.

라인 원정대
– 클라개발, 서버개발, 인프라개발자등으로 2-6인으로 구성
– 출장기간은 국가의 크기에 다르지만, 3-5일 정도 체류
– 국가특유의 네트워크특성이나, 인터넷품질, 배포전 실제환경에서 문제없는가 테스트
– 현지의 사람에게 의견을 듣기도함.
– 비행기내 와이파이에서도 라인이 된다고함?!

라인 글로벌 인프라스트럭쳐
– 유저와 서버간의 레이턴시를 줄이기위해 세계 각지에 POP(자사 IDC를 의미하는 듯)를 설치.
– 클라에서는 가까운 POP를 선택하도록되어 있음
– 인터넷 환경의 변화에 대응하기위해, (유저가 사용하는 버젼같은)다양한 환경에 대해 다른 설정을 배포할 수 있는 기능을 적용함. 다양한 통신 프로토콜 적용. 다양한 파일 다운로드 방식 적용 등..

앱리뷰
– 유저의 리뷰를 국가/언어/버젼/키워드별 어플리케이션 리뷰수와 스코어를 분석가능하게함.

라인 성능 인덱스
– 라인의 기업문화의 한가지는 ‘숫자’로 보고 판단하는 것. 라인의 품질, 개선방법의 결정. 업데이트를 통한 결과의 확인을 전부 ‘숫자’로 측정 & 개선.
– Analytics를 위한 Big data분석을 위해 Hadoop을 사용중.

Erlang 정리 2(쉘명령어)

[markdown]
### 쉘 명령어

[여기](http://learnyousomeerlang.com/starting-out) 링크를 요약 한 글임을 밝혀드립니다.

– Emacs를 베이스로한 라인에디터를 내장하고 있음. (Emacs를 몰라도 됨)
– `Ctrl + A`를 누르면 커서가 라인의 가장 앞으로 이동함.
– `Ctrl + E`를 누르면 커서가 라인의 가장 끝으로 이동함.
– `li`를 누르고 `tab`을 누르면 `lists:`이런식으로 자동완성 됨.

들어오긴 했는데, 끄는법을 모를 때

`help().` 를 누르면 명령어가 쫘악 나옴. 여기서 `.` 점을 꼭 찍어야됨.

`help().`를 실행 후 나오는 것 중에 보면

아래와 같은 게 보이는데
“`
q() — quit – shorthand for init:stop()
“`

`q().`를 실행하면 쉘이 종료됨

쉘을 잠깐 멈추고 싶은 경우는 `ctrl + G`를 누르면 유저스위치 커맨드 라는 게 보이는데

“`
User switch command
–>
“`

여기서 `h`를 누르면 몇가지 명령어가 나옴. 쉘 안에서 멈추고 다른 걸 실행가능

“`
User switch command
–> h
c [nn] – connect to job
i [nn] – interrupt job
k [nn] – kill job
j – list all jobs
s [shell] – start local shell
r [node [shell]] – start remote shell
q – quit erlang
? | h – this message
“`

종료는 `ctrl + G` 후 `q`로 종료가능
[/markdown]

Erlang 정리1 (erlang 이란?)

[markdown]

“`
node코드를 리팩토링 해야하는데, 하기 싫고 귀찮아서 erlang을 잠깐 봄.
명언이 생각난다.
“`

> 개발자는 자기가 흥미가 가는 것을 열심히하는 직업병이 있다.

# Erlang 정리1 (erlang 이란?)

> [http://learnyousomeerlang.com/introduction#about-this-tutorial](http://learnyousomeerlang.com/introduction#about-this-tutorial)

> 위 링크를 참고, 요약 한 것임을 밝혀 드립니다.

– functional 프로그램이다
– 참조의 투명성 (referential transparency)
– 변경가능한 데이터를 못씀 (avoiding mutable data)
– 코드는 바이트코드로 변환되고, 가상머신위에서 돌아간다.
– 표준배포판
– 개발툴(컴파일러, 디버거, 프로파일러, 테트트 프레임웤)
– OTP(Open Telecom Platform) 프레임웤
– 웹서버
– 파서 생성기
– mnesia 데이터베이스
– key-value 스토리지 시스템(복제기능, 트랜젝션, 어떤 erlang데이터도 저장가능)을 포함한다.
– actor를 강제로 쓰게 만든다.
– message로만 응답한다.
– 경량프로세스이다. (가벼우니 많이 만들수는 있지만 그게 좋은건 당연히 아니다.)
– 시계열 프로세싱이나, 운영체제의 시스템 드라이버를 만드는데는 안좋다.
– 큰규모의 서버(Queue, map-reduce)나, 고수준 프로토콜의 구현등에는 좋다.

## erlang에 뛰어들려면 무엇이 필요한가?

– 텍스트 에디터 + Erlang 실행환경 (공식사이트에서 받자 http://erlang.org/download.html)

### 리눅스환경에서 설치하기

“`
### mac
$ brew install erlang

### ubuntu
$ apt-get install erlang

### centos
$ yum install erlang

### 위의 3가지 방법으로 안 될 경우는 컴파일 설치!
### 컴파일 설치하려면 GNU make, gcc, Perl5, GNU m4, ncurses,
### termcap, termlib, OpenSSL, Sun Java jdk-1.5 이상, sed등이 필요.
### 위의 것들은 알아서 설치하시길..

### configure 설정에 –prefix= 설정을 하면 설치경로 지정가능
### 없으면 /usr/local/{bin,lib/erlang}에 기본적으로 설치됨.

$ curl -O http://www.erlang.org/download/otp_src_R16B03-1.tar.gz
$ tar xvzf otp_src_R16B03-1.tar.gz
$ cd otp_src_R16B03-1
$ LANG=C; export LANG
$ ./configure
$ make #오래걸림
$ make install
“`

## 도움얻기

“`
# man page
$ erl -man lists
“`

#### 웹 문서

공식 사이트 : [http://erlang.org/doc/](http://erlang.org/doc/)

공식 사이트 대신(디자인이 쫌 더 이쁨) : [http://erldocs.com/](http://erldocs.com/)

코딩 룰 : [http://www.erlang.se/doc/programming_rules.shtml](http://www.erlang.se/doc/programming_rules.shtml)

메일링리스트 : [http://www.erlang.org/static/doc/mailinglist.html](http://www.erlang.org/static/doc/mailinglist.html)

쿡북이나 레시피 좋아하는 사람은 여기를 : [https://erlangcentral.org/wiki/index.php?title=Main_Page](https://erlangcentral.org/wiki/index.php?title=Main_Page)

#### erlang shell 실행해보기

아래와 같이 실행하면
“`
$ erl
“`

요런식으로 나옴
“`
Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4 (abort with ^G)
1>
“`

[/markdown]

javascript strict mode 정리

[markdown]
# javascript strict mode

strict모드를 jslint를 사용하게 되면서 사용하고 있는데, 이게 뭔지 모르는 채로 그냥 사용하는 것은 뭔가 찜찜했기에,찾아서 정리함.

strict모드는 파일, 프로그램 또는 함수의 시작 부분에 `”use strict”;`를 추가하여 strict모드를 추가 할 수 있다.

### strict모드의 제한

– Strict모드에서 변수를 선언하지않고 사용할 수 없다.
– 읽기 전용 속성에 값을 할당할 수 없다.
– extensible특성이 false로 설정된 객체에 속성을 추가할 수 없다.
– configurable특성이 false로 설정된 속성을 삭제할 수 없다.
– 객체에 같은 이름의 속성을 두번이상 정의 하지 못한다.
– 함수에서 파라메터명에 같은 이름을 두번 이상 사용하지 못한다.
– implements, interface, package, private, protected, public, static, yield 등과 같은 예약어를 변수 또는 함수 이름으로 사용 할 수 없다.
– 숫자 리터럴에 8진수 값을 할당하거나 8진수 값에 이스케이프를 사용할 수 없다.
– this의 값이 null또는 undefined인 경우 전역 객체로 바뀌지 않음.
– 문자열 “eval”, “arguments” 식별자(함수, 변수, 매개변수 명)으로 사용 할 수 없다.
– 함수선언을 statement나 함수 블록내에서 중첩해서 할 수 없다.
– 변수가 eval함수내에서 선언 되는 경우 해당 함수 밖에서 사용할 수 없다.
– 로컬 arguments 객체의 값을 변경할 수 없다.
– arguments.callee를 허용하지 않음
– with문을 허용하지 않음
[/markdown]

Chef 서버 설치하고 테스트해보기

Chef 서버 설치하고 테스트해보기

vagrant + virtualbox로 5대의 멀티 인스턴스 올리기

Chef 서버의 환경을 이해해 보려면 3개이상 가상머신을 설치해서 해보는게 좋다.
Vagrant로 5대의 가상머신을 올려보자.
Vagrant를 설치하고 VagrantFile에 아래와 같이 설정한 후

vagrant up을 실행하면 된다.

vagrant 사용법은 아래의 링크를 참고 하자.

Chef의 테스트 환경을 만드는데 매우 적합한 툴 – Vagrant (http://gyus.me/?p=326)

 

VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define :chef_server do |cfg|
cfg.vm.box = "base"
cfg.vm.network :private_network, ip: "192.168.30.10"
cfg.vm.host_name = "chef-server"
end
config.vm.define :workstation do |cfg|
cfg.vm.box = "base"
cfg.vm.network :private_network, ip: "192.168.30.20"
cfg.vm.host_name = "workstation"
end
config.vm.define :node01 do |cfg|
cfg.vm.box = "base"
cfg.vm.network :private_network, ip: "192.168.30.21"
cfg.vm.host_name = "node01"
end
config.vm.define :node02 do |cfg|
cfg.vm.box = "base"
cfg.vm.network :private_network, ip: "192.168.30.22"
cfg.vm.host_name = "node02"
end
config.vm.define :node03 do |cfg|
cfg.vm.box = "base"
cfg.vm.network :private_network, ip: "192.168.30.23"
cfg.vm.host_name = "node03"
end
end

 

chef server 설치

아래 사이트에서 패키지를 다운 받아서 설치

http://www.getchef.com/chef/install/

[code lang=text]
wget https://opscode-omnitruck-release.s3.amazonaws.com/ubuntu/12.04/x86_64/chef-server_11.0.6-1.ubuntu.12.04_amd64.deb
[/code]

[code lang=text]
sudo dpkg -i chef-server.deb
[/code]

설치 완료후 chef-server-ctl로 Chef Server를 설정함

[code lang=text]
sudo chef-server-ctl reconfigure
[/code]

위 커맨드를 실행하면 서버를 기동시키고, nginx가 HTTPS 443포트를 바인드한다.

Chef의 각종 파일은 /opt 디렉토리에 인스톨된다.

knife 설정

[code lang=text]
knife configure
[/code]

실행하면 아래와 같은 설정 항목들이 나옴 걍 엔터.

[code lang=text]
Where should I put the config file? [/home/vagrant/.chef/knife.rb]
Please enter the chef server URL: [https://chef-server:443]
Please enter an existing username or clientname for the API: [vagrant]
Please enter the validation clientname: [chef-validator]
Please enter the location of the validation key: [/etc/chef-server/chef-validator.pem]
Please enter the path to a chef repository (or leave blank):
[/code]

임의의 클라이언트가 처음 Chef Server와 통신을 할경우, 디지털서명이 없기 때문에,
최초 한번, chef-validator라는 Client등록작업만을 전문으로 하는 클라이언트 에서 인증서를 빌려서 Chef Client의 등록작업을 하게됨. 이 작업이 완료되면 이후는 클라이언트 쪽에서 작성한 디지털서명에 의해 통신이 이루어지도록 된다.

dep패키지로 Chef Server를 설치하면 /etc/chef-server 디렉토리에 아래와 같은 파일이 있음. knife configure로 knife에게 이러한 인증서의 패스를 정확히 설정할 필요가 있다.
우선은 서버와 같은 호스트의 knife가 잘동작하도록 하고, 후에 workstation에 인증서를 전송하는 순서로 설치를 하겠음.
일단은 /etc/chef-server이하의 pem파일을 홈디렉토리로 카피함.

  • chef-validator.pem : chef-validator용의 디지털 인증서 파일
  • admin.pem : 관리자용의 클라이언트 인증서 파일(이 것을 사용하는 클라이언트 만 chef-validator를 통하지 않고 미리 작성되어 있음)

[code lang=text]
vagrant@chef-server:/$ sudo knife configure
Please enter the chef server URL: [https://chef-server:443] https://192.168.30.10
Please enter an existing username or clientname for the API: [vagrant] admin
Please enter the validation clientname: [chef-validator]
Please enter the location of the validation key: [/etc/chef-server/chef-validator.pem] ~/.chef/validation.pem
Please enter the path to a chef repository (or leave blank):
[/code]

위의 설정에서
* server URL : https://192.168.30.10
* clientname : admin
* validation key : /etc/chef-server/chef-validation.pem
을 설정함. 이것으로 해당 서버의 knife는 클라이언트의 인증서가 있는 ~/.chef/admin.pem을 이용해서 통신을 하게끔 된다. 한번 시험해봅시다.

[code lang=text]
$ knife client list
[/code]

Work Station의 셋업

새로운 Chef Client를 셋업 할 경우, 인증서의 생성과 클라이언트를 서버에 등록하는 것이 필요하다. 이것도 knife로 하게됨. 서버에 설치한 knfie를 사용해 보도록 하자.

knife client create를 실행하면 에디터에서 메타데이터의 편집을 요구하기때문에, EDITOR의 환경변수를 미리 설정해둠. knife client create 옵션의 -a 는 관리권한을 가진 클라이언트의 작성, -f는 인증파일의 저장소 지정. Vagrant를 사용하는 경우는 가상머신의 /vagrant는 공유 디렉토리로 마운트되어 있어서 다른 가상서버로의 파일전송이 편리하다. 거기에 저장을 하도록 해보자.

[code lang=text]
$ export EDITOR=vi
$ knife client create workstation -a -f /vagrant/workstation.pem
[/code]

Work Station의 knife에도 validation.pem을 저장해두면 나중에 편하니 거기에도 카피해두자.

그리고 여기서 부터는 Work Station에서 작업해야한다. 우선은 Work Station에 Chef Solo를 설치할때 사용한 Opscode Omnibug Packaging의 인스톨스크립트를 실행해서 Chef를 설치한다.

[code lang=text]
$ vagrant ssh workstation
$ curl -L https://www.opscode.com/chef/install.sh | sudo bash
$ chef-client -v
Chef: 11.8.2
[/code]

이 다음으로는 서버에서 한것과 동일하게 knife를 설정해야한다.

[code lang=text]
$ cp /vagrant/workstation.pem ~/.chef/
$ cp /vagrant/validation.pem ~/.chef/
$ knife configure
WARNING: No knife configuration file found
Where should I put the config file? [/home/vagrant/.chef/knife.rb]
Please enter the chef server URL: [https://workstation:443] https://192.168.30.10
Please enter an existing username or clientname for the API: [vagrant] workstation
Please enter the validation clientname: [chef-validator]
Please enter the location of the validation key: [/etc/chef-server/chef-validator.pem] ~/.chef/validation.pem
Please enter the path to a chef repository (or leave blank):
[/code]

서버의 URL과 방금 설정한 인승키의 패스를 설정.
제대로 설정을 했다면 knife client list로 출력이 되고 새로운 Client로 workstation이 등록된다.

[code lang=text]
### Chef Server에서 작업
$ knife client list
chef-validator
chef-webui
workstation
[/code]

<h3>Chef Client의 설치</h3>
Chef Server와 Work Station이 완료된 후에는 Client를 작업하자.
관리 대상이 되는 노드를 Chef Client로 셋업하자.

클라이언트측의 Chef인스톨도 Opscode Omnibus Packaging의 인스톨 스크립트를 실행하면 된다.

우분투를 클린상태로 설치했다면, 몇가지 패키지를 설치해야하니 다음 순서로 설치해보자.

[code lang=text]
### Chef Client에서 실행함.
$ sudo apt-get update
$ sudo apt-get install curl vim -y
$ curl -L https://www.opscode.com/chef/install.sh | sudo bash
$ chef-client -v
$ sudo mkdir -p /etc/chef
$ sudo cp /vagrant/validation.pem /etc/chef/validation.pem
[/code]

위의 스크립트를 한줄 한줄 실행해도 되고 귀찮은 사람은 파일로 만들어서 한방에 실행해도 된다.

노드에서 chef-client를 실행해서 해당 노드를 서버에 등록하자. –server로 서버의 URL을 지정하고, -N으로 노드명을 지정할 수 있다.(생략하려면 /etc/chef/client.rb를 설정하면 된다. 자세히 알고 싶으면 매뉴얼을 보도록 하자.)

[code lang=text]
$ sudo chef-client –server https://192.168.30.10 -N node01
[/code]

최초의 Client실행 때에 /etc/chef/client.pem에 클라이언트용 인증서가 생성되고, 이후의 통신은 validation.pem이 아닌 클라이언트용 인증서를 사용하게 된다.

Chef Server에 노드가 등록되었는지 Work Station의 knife에서 확인가능하다.

[code lang=text]
### Work Station에서 실행
$ knife client list
chef-validator
chef-webui
node01
workstation
[/code]

node02, node03에는 추후에 따로 세팅을 할테니 우선 두자.

헉헉헉…길다…
<h3>레시피 실행하기</h3>
잘동작하는지 테스트용 레시피를 작성후 실행해보자.
테스트로는 해당노드에 대한 각종 정보를 가지고 있는 ohai를 확인해 보는 레시피를 작성하도록 하자.
Work Station에서 쿡북을 작성하고 Server에 업로드 한 뒤 클라이언트에 적용해보자.
<h4>쿡북의 작성</h4>

[code lang=text]
$ knife cookbook create sample -o ~/chef-repo/cookbooks
[/code]

<h4>
레시피의 작성</h4>

[code lang=text]
$ vi ~/chef-repo/cookbooks/recipes/default.rb

bash "print ohai info" do
user 'vagrant'
group 'vagrant'
cwd '/home/vagrant'
environment "HOME" => '/home/vagrant'
code < /tmp/ohai.txt
EOC
creates "/tmp/ohai.txt"
end
[/code]

<h4>쿡북의 업로드</h4>

[code lang=text]
$ knife cookbook upload -a -o ~/chef-repo/cookbooks
[/code]

<h4>Node Object의 run_list 레시피 추가</h4>

[code lang=text]
$ knife node run_list add node01 'recipe[sample]'
[/code]

<h4>Node Object를 에디터에서 편집하고 싶은 경우</h4>

[code lang=text]
$ knife node edit node01
[/code]

이제 서버에 필요한 정보의 등록은 완료됐다.
노드 쪽에서 chef-client를 실행하고 생성된 파일을 확인해 보자.

[code lang=text]
$ sudo chef-client –server https://192.168.30.10 -N node01
$ cat /tmp/ohai.txt | head
{
"languages": {
"ruby": {
"platform": "x86_64-linux",
"version": "1.8.7",
"release_date": "2012-02-08",
"target": "x86_64-unknown-linux-gnu",
"target_cpu": "x86_64",
"target_vendor": "unknown",
"target_os": "linux",

[/code]

knife bootstrap 으로 노드 설정하기

가상머신으로 실행중인 경우 Work Station에서 knife bootstrap 명령을 원격으로 실행 가능하도록, 몇가지 선행되어야하는 작업이 있는데 다음과 같다.

  • 가상서버에 ssh로그인 가능하도록 호스트 OS의 ~/.vagrant.d/insecure_private_key를 Work Station의 ~/.ssh/id_rsa에 카피

[code lang=text]
### 호스트 OS에서 작업
$ scp ~/.vagrant.d/insecure_private_key worksstation:~/.ssh/id_rsa
[/code]

  • 가상서버에 설치되어 있는 옛날 버젼의 Chef가 설치시 에러를 일으킬 수 있으므로 제거. /usr/bin 디렉토리의 chef-* 파일을 어딘가로 이동해두면 된다.

[code lang=text]
### Work Station에서 작업
$ mv /usr/bin/chef-* /tmp/
[/code]

  • Work Station의 /etc/hosts에 각 노드의 IP주소를 등록해둔다.

[code lang=text]
# /etc/hosts

192.168.30.21 node01
192.168.30.22 node02
192.168.30.23 node03
[/code]

위의 설정은 knife ssh가 호스트로의 통신을 FQDN(Fully Qualified Domain Name)으로 실행하기 위한 것이다.

정리

설치와 실행이 매우 길고 좀 복잡해 보이는데, 차근차근 따라해보면 그렇지도 않다. 관리하는 서버가 10대를 넘어가면 Chef Server를 고려해 보는 것이 좋은 것 같다.

Role – 노드를 역활별로 그루핑 해서 관리하고 싶을 때

Node Object

chef solo로 관리하는 서버가 늘면, Node Object를 정의하는 JSON파일 수가 서버수만큼 늘게 되는데, Node Object에 정의한 run_list에 실행할 레시피를 일일이 JSON파일에 적어주는게 귀찮아진다.

예를 들어 같은 레시피를 적용할 5개의 노드가 있는 경우 새로운 레시피를 추가하고 싶은경우 5번을 복사 붙이기를 하는건 DRY법칙에 어긋나고 귀찮기도 하다. 그래서 run_list와 Attribute를 노드의 역활별로 그루핑 할 수 있는 Role을 사용하면 된다.

Chef solo의 경우 Role은 roles디렉토리의 아래에 Role별로 파일을 만들고, 그안에 Rolo에 필요한 내용을 JSON으로 적는다.

웹서버용 노드의 Role을 rolos/webserver.json으로 작성해보자.

json
{
"name":"webserver",
"default_attributes": {},
"override_attributes": {},
"json_class":"Chef::Role",
"description":"",
"chef_type":"role",
"run_list":[
"recipe[yum::epel]",
"recipe[nginx]",
"recipe[sysstat]"
]
}

배포하려는 서버의 Node Object에는 아래와 같이 적으면 됨

json
{
"run_list":[
"role[webserver]"
]
}

여러개 섞어서 적는것도 가능

“`json
{
“run_list”:[
“recipe[nginx]”,
“role[webserver]”,
“role[db]”
]

}
“`

Role에 어트리뷰트를 정의하는 것도 가능

json
default_attributes "apache2" => {
"listen_ports" => ["80", "443"]
}

chef정리

chef 정리

knife가지고 놀기

cookbook 만들기

shell
$ knife cookbook craete [쿡북이름] -o [디렉토리명]
$ knife cookbook craete hello -o site-cookbooks

knife solo

knife-solo의 설치

knife-solo는 rubygem이므로 아래의 커맨드로 설치가능
shell
$ gem install knife-solo

설치하는 것 만으로도 knife커맨드에 chef-solo를 위한 커맨드가 추가 됨.

knife solo를 파라메터 없이 실행해 보면 사용가능한 커맨드의 리스트가 출력 됨.

knife-solo의 문서

https://github.com/matschaffer/knife-solo/blob/master/README.rdoc

레시피 전송 & 실행 knife solo cook

shell
$ knife solo cook 유저@호스트명

cook 커맨드는 키친(리포지토리)를 대상 서버에 업로드하고, chef-solo를 실행시킨다.

knife-solo를 이용한 chef-solo 실행환경 준비

shell
$ knife solo prepare 유저@호스트
$ knife solo prepare -p 포트 유저@호스트

리포지토리 작성

shell
$ knife solo init 리포지토리 명

디렉토리 구성은 아래와 같다.

├── .chef
│ └── knife.rb
├── cookbooks #다운로드한 쿡북을 저장
├── data_bags #노드간에 공유할 수 있는 변수 저장
├── nodes #노드별로 JSON파일을 저장
├── roles
└── site-cookbooks #자신이 만든 쿡북을 저장

prepare + cook = bootstrap

shell
knife solo bootstrap 유저@호스트

knife solo prepare 를 호출하고 나서 knife solo cook을 호출.

clean

shell
$ knife solo clean 유저@호스트

대상 호스트에 있는 키친(레포지토리)을 완전히 삭제