'Technical/Development'에 해당되는 글 27건

Mac OSX 에는 Xcode 라는 좋은 무료 개발도구가 있다. 물론 Mac 에서도 CodeLite, CodeBlocks 와 같은 잘 알려진 오픈소스 개발도구를 쓸 수 있지만, Codeblocks 는 Mac OSX 에서의 안정성이 떨어지는 감이 있고 CodeLite 는 Mac 에서 command-line 프로그램 개발 시 iostream 의 cin 을 제대로 처리 못하는 등 조금씩 문제를 안고 있기 때문에 쓰다 보면 불편함이 쌓이게 되고, 그러다 보니 Apple 에서 Mac 을 가장 잘 지원해 주(는 것으로 믿고 싶은...)는 Xcode 같은 Native app으로 다시 돌아가곤 한다.


이번 글에서는 Xcode 로 C++, Objective-C, Swift 와 같은 언어를 사용한 개발에 있어서, 협업 개발을 위한 git 저장소와 Xcode 가 자체적으로 제공하는 git client 기능을 사용하는 방법을 기록해 두려 한다.


개발자의 결벽증

물론 Remote 저장소에서 repository를 cloning 하거나 Local 저장소에서 작업하다가 그때 그때 push 하면서 "미친 년 널 뛰듯이" 아무렇게나 되는대로 작업할 수는 있다. 그러나 사람마다 기준이나 스타일은 다르겠지만, 하드디스크의 작업 영역에 온갖 너저분한 임시 checkout 디렉토리, 수정하다 만듯한 폴더와 파일 조각들이 여기저기 널려 있는 꼴은, 마치 소스의 indent 가 들쭉날쭉한 참상을 보는 것 만큼이나 속상하는 일이기 때문에 다음과 같은 원칙을 정해 둔다


  • 하나의 프로젝트에 대해 주로 작업하는 Local 저장소와 Remote 저장소의 대응은 1:1 일 것
  • 사용하는 Tool 이 Sublime Text 또는 Sourcetree 이건 Xcode 이건 간에 프로그램 소스가 저장되는 repository는 하나만 존재할 것
  • 프로그램 소스코드와 데이터파일을 제외한 프로젝트파일, 바이너리 실행 또는 오브젝트 파일들은 git 을 통한 버전관리에서 제외 될 것


필요한 것들(또는 지금 쓰고 있는 것들)

  • Remote 저장소(Github, Gitlab 계정 또는 본인만의 git server) - 필자는 Remote git 저장소를 위한 AWS 가상머신(doubleshot.io)
  • Git Client - OSX 에 내장된 자체 git & Sourcetree app



정리해 두려는 것들

  • 이미 존재하는 Remote 저장소를 내려 받고 Xcode 에서 작업하거나 또는,
  • 새로운 Local 저장소를 만들어 Xcode 에서 작업하고, Remote 저장소와 연동, 업로드(보관)



1. Remote 저장소를 내려 받고 Xcode 에서 작업하기


1.1 저장소 이름은 gittest2, Remote 저장소의 주소는 ssh://gituser@doubleshot.io/home/gituser/repos/gittest2 라 하자. 


1.2 가장 먼저, Sourcetree 에서 Remote 저장소를 cloning 해서 Local 로 다운로드 한다. 다음으로 Xcode 에서 작업할 새로운 프로젝트를 생성해 두고, Sourcetree 창을 나란히 배치한다.


해당 repository 에서 R-click, "Show in Finder" click


Finder 에서 저장소 폴더를 선택, Xcode 의 프로젝트 내부로 drag 한다(또는 Finder를 거치지 않고 Sourcetree 에서 Xcode 로 직접 drag 해도 된다)


Xcode 는 외부 폴더를 가져올 때의 옵션을 묻는다. 그림과 같이 설정하고 Finish!(Create Group을 하면 Xcode 내에 그룹이 만들어지고 그 내부에 git 폴더가 논리적으로 참조되고, Create Folder Reference 를 하면 프로젝트 내에 그룹이 만들어지지 않고 git 폴더 자체가 직접 참조된다)


Xcode 프로젝트 안으로 외부 git 저장소가 안전하게 import 되어 있다. 오른쪽 정보창에서 저장된 path 를 확인해 보자. Xcode 가 기본으로 자동 생성해 주는 main.cpp 는 휴지통으로...!



1.3 위의 과정을 거치면 Xcode 내에서 add, commit, push 등 모든 git 기능을 사용할 수 있다. Xcode 프로젝트 내에 Working copy 가 복사되어 만들어지는 것이 아니라, Sourcetree 가 Clone 해 둔 repository를 그대로 사용하는 것이다.



2. Local 저장소로 Xcode 에서 작업하고 Remote 저장소와 연동하기


2.1 Git server 에서 Remote 저장소를 생성해 둔다(이 과정은 아래 2.7 이후에 해도 된다). 저장소 이름은 xcodegit_repo, Remote 저장소의 주소는 ssh://gituser@doubleshot.io/home/gituser/repos/xcodegit_repo 라 하자. 


2.2 Sourcetree 에서 "Create a local repo..." 선택, Local 저장소를 생성한다(이 때, "Also create remote..." Check는 반드시 해제).



2.3 Xcode 에서 작업할 새로운 프로젝트를 생성한다.



Xcode 에서 프로젝트 생성 후, 자동 생성되는 main.cpp 는 휴지통으로... 만약 사용할 필요가 없다면 main.cpp 파일이 속한 그룹 전체를 삭제해도 무방함



2.5 Sourcetree 에서 Local 저장소를 R-click, "Show in finder" 선택. Finder 에서 Xcode 프로젝트 내부로 폴더 drag

Xcode 의 프로젝트 내부로 drag(Sourcetree 에서 직접 drag 해도 된다)


1.2와 마찬가지로 가져오기 옵션 설정



2.6 Xcode 로 가져온 Local 저장소 폴더 R-click, "New file" 을 선택(저장되는 path 확인), 작업할 소스 파일(cpp 등) 생성.



2.7 Xcode 에서 소스 파일을 편집, 저장 후 git add, commit 등 실행(로컬 저장소에 저장)


2.8 작업이 완료된 소스 파일을 Remote 저장소와 연동 저장하기 위해서는 소스 파일이 저장된 폴더 선택, Xcode 메뉴의 "Source Control" 선택, Remote 저장소 정보를 설정한다.


Add Remote 선택


Git server 에서 생성해 둔 Remote 저장소의 주소를 등록


코딩 및 테스트 작업이 끝나면 Xcode 메뉴에서 "Source Control" -> Push 를 통해 Remote 저장소에 업로드할 수 있다


<참고> Git server 와의 연결을 ssh 프로토콜을 통할 경우에는, Xcode 가 생성해 주는 ssh 인증키를 git server 측과 '키교환'을 해 두어야 한다  


- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


소장하고 싶은 영화나 동영상 파일들을 모아서 디스크 한 켠에 폴더별로 잘 정리해서 보관하는 경우가 많다. 그렇다고 자주 들춰보기는 쉽지 않지만, 일종의 "혹시나 다음에 또 보고 싶어질지도 몰라...ㅋ" 하는 미련 같은 건 아닐까(그러다 결국 한 순간에 폴더째 지워버리게 되기도 하지만...ㅠㅠ). 아, 위에 나오는 짤방성 이미지의 context sensitive한 자막은 참 공감이 많이 가고, 무엇보다 푸근한 처자에게 고맙다(뭐가 ? ^^;;).


2016년 첫 포스팅 치고는 거의 솔플메모에 가까울 정도로 가볍다. 언제나 심각하고 거창할 순 없는 거다. 각설하고, 이제 본론인 메모 작성으로 넘어가자.


Handbrake로 동영상을 변환

다운로드 받은 영화를 보관 또는 거실TV에서 상영하기 전에 거실 PC의 Linux(Ubuntu 14.04 LTS)에서 무료 동영상변환기인 핸드브레이크(Handrake)로 인코딩 하게 된다(Mac book에도 Adapter라는 쓸만한 인코딩 앱이 있긴 하지만, 귀여운 내 맥프레에게 이런 격 떨어지는 일을 시키고 싶지는 않다).


그런데, TV(스마트 TV가 나오기 전 모델의 42인치 LG LED)의 미디어플레이어가 개후져서 조금 옛날 것이라 AC3 오디오를 인식 못하거나 새로운 코덱으로 인코딩 된 mp4 파일은 아예 화면도 띄우지 못하는 경우가 많다. 이 때 만날 수 있는 여러 문제 상황을 단순하게 넘기려면, 아예 Handbrake에서 동영상 변환을 할 때 영상의 화면에 자막을 심어 버리는 작업을 하는 쪽이 속 편하다.


한글 자막 .smi 파일은 대부분 EUC-KR 코드

인터넷에서 입수되는 대부분의 한글 자막은 smi 포맷으로 되어 있고, 한글코드는 EUC-KR 인 경우가 많다(불평은 하지 않습니다. 자막 제작자님들 고맙습니다 ^^). 그런데 문제는 Handbrake가 srt 포맷의 자막만 지원하기 때문에 2번의 변환을 거쳐야 한다. 한 번은 한글 코드 변환, 또 한 번은 자막파일 변환. 이과정이 귀찮아서 Bash shell 을 작성해 보았다. 사용 방법과 소스는 아래 참고.


자막 변환 스크립트 사용법

우선 smi -> srt 자막을 변환하는 Linux 용 프로그램 subs를 설치해야 한다(작성자 <Dmitry Karasik, dmitry@karasik.eu.org> 에게 감사). 좀 오래 된 거지만 지금도 쌩쌩 잘 돌아간다.  그 다음에 사용할 iconv 는 대다수 Linux 배포본에 포함된 코드 변환 도구이므로, 따로 설명은 생략한다.


다음과 같이 subs 를 우선 설치.

$ sudo apt-get install libsubtitles-perl

$ subs -> 테스트 삼아 한 번 실행해 보자

subs: convert, join, split, and re-time subtitles


format:

   subs [options] subfile [ subfile ... ]


options:

   -a coeff, - a and b coefficients in linear transformation

   -b time     u=at+b, where t and u are src and dest times

...


사용법은 간단하다. 스크립트(conv_eucsmi2utfsrt.sh ... 파일 명은 알아서 변경)를 Linux 계정의 $HOME/bin  정도에 복사해 두고(chmod +x 가 필요할지도...), 변환할 자막 파일이 있는 경로로 가서 "conv_eucsmi2utfsrt.sh 자막파일.smi" 같은 식으로 실행해 주면 된다. 변환에 성공했을 경우 다음과 같이 나오게 될 것이다.


명령어 인자로 전달하는 자막파일명 부분에 와일드카드를 쓴다든지, 여러개 파일의 일괄변환 등에 대해서는 따로 고려하지 않았다. 모든 smi 파일을 단숨에 변환할 일이 그다지 많지 않기 때문인데, 나중에 필요하면 한 번 생각해 보기로...


$ conv_eucsmi2utfsrt.sh tmp.smi

Converted tmp.smi(euc-kr) to tmp.srt(utf8)

Enjoy it! ;)


특별한 기술이 필요하거나 대단히 복잡한 것은 없다. 잘 보면 아래 부분 실행문들에서 iconv, subs, sed 를 적당한 옵션을 주어 각각 실행하는 것이 전부다. 그러나 귀찮은 작업을 스크립트 하나 실행해서 해 낼 수 있다는 점에서 자화자찬 중.


conv_eucsmi2utfsrt.sh (Bash shell script 소스)

#!/usr/bin/env bash
# Bryan(Barracuda), 2016.1.
# Usage: conv_eucsmi2utfsrt.sh filename.smi(with EUC-KR Kor. char.)
#
# *.smi 파일(euc-kr)을 iconv와 subs 를 써서 *.srt(utf8) 형식으로 변환
# 변환 후 srt 파일 내에 smi 파일에서 자주 보이는 흔적인   가 있으면 삭제함
# 자막 변환 도구 subs는 sudo apt-get install libsubtitles-perl 로 설치되어 있어야 함(ubuntu 기준)

args=1
param=$1

if [ "$#" -ne "$args" ]; then
    # $0 의 Path 부분 삭제: ${0##/*/}
    echo -e "\033[0;33m$# arguments ... use \"${0##/*/} filename.smi\"\033[0m"
    exit 1
fi

# 인자로 전달 된 파일명에서 확장자 부분을 제외한 앞 부분을 잘라냄(뒤에서부터 .로 시작하는 부분을 삭제)
# param이 . 으로 끝나면 basename은 "filename", extension은 ""
# param에 . 이 없으면 basename은 "filename", extension도 "filename", index는 "0"
basename=${param%*.*}
extension=${param##*.}
index=`expr index $param .`

# 파일명이 . 으로 끝나는 경우, 파일명에 . 이 없는 경우, 확장자가 smi 가 아닌 경우 오류
if [[ "$extension" = "" || "$index" = "0" || "$extension" != "smi" ]]; then
    echo -e "\033[0;33mBad file extension ... I want \"*.smi\" file.\033[0m"
    exit 1
else if [ ! -f $param ]; then
        echo -e "\033[0;33mFile $param missing.\033[0m"
        exit 1
    fi
fi

if [ -e "$basename.srt" ]; then
    echo -e "\033[0;33mFile $basename.srt already exists. Plz delete or rename it.\033[0m"
    exit 1
fi

# 한글 euc-kr 코드를 utf8 코드로 변환
iconv -f euc-kr -t utf8 $basename.smi > _$basename.smi
# smi 파일을 srt 파일로 변환
subs -c srt _$basename.smi -o _$basename.srt
# srt 파일 내의   를 제거
sed "s/ //g" _$basename.srt > $basename.srt
# 중간 작업파일 제거
rm -f _$basename.smi _$basename.srt

echo -e "\033[0;32mConverted $basename.smi(euc-kr) to $basename.srt(utf8)"
echo -e "\033[0;32mEnjoy it! ;)\033[0m"


<*주의*> 위 소스의 44~45 라인을 아래 내용으로 수정 바랍니다. syntaxhighlighter 로 bash 소스를 표시할 경우 html 요소(&nbsp;) 가 정상적으로 출력되지 못하는 군요. 이 점 양해 바랍니다.

# srt 파일 내의 &nbsp; 를 제거

sed "s/&nbsp;//g" _$basename.srt > $basename.srt



Mac 사용자라면 간단히 MinySubtitleConverter 를 써 보자


만약 Linux 가 아닌 Windows 나 Mac 사용자라면 http://blog.myhyuny.com 블로그를 방문하여 MinySubtitleConverter 를 다운로드 받아서 사용하면 위의 과정을 간편하게 한 번에 끝낼 수 있다. 직접 테스트 해보니 한글코드와 srt 변환이 아주 훌륭하게 작동한다.


2014년 3월이 마지막 업데이트이긴 하지만 OSX 10.11(El Capitan) 에서도 문제 없이 사용할 수 있는 것으로 확인되었다.




Input Encoding 을 따로 지정하지 않고 Auto 로 두어도 한글 코드를 자동으로 인식한다. File 메뉴에서 Open -> smi 파일을 선택하면 smi 파일이 있는 폴더에 srt 파일이 자동 생성된다.



- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들



[스택오버플로우] 2줄 짜리 단순한 질문에 대한 명료한 분석과 이유를 달아준 명답 of 명답. 지난 5월에 올라온 이슈답변에 이 시간 현재 평점 38만 점, 황금배지 54개!

보러 가기


짧고 격하게 공감하고 오래 기억하라


단순한 따라하기 보다는. 왜 그래야 하는지 알려고 노력하는 것이 얼마나 중요한지 새삼 깨닫게 해 준다.

비단 C++코딩에서 뿐이랴. 특히 과학을 하는 이에게 원리와 이유의 탐구가 얼마나 중요한가 말이다. 그러니 늘, 기본에 충실하라. 이건 나 자신에게 하는 말.


* RAII 는 Resource Acquisition IS Initialization 이라는 표현의 약어로 C++을 창시한 Bjarne Stroustrup 이 주장하는 일종의 기술적 원칙이다.


[스택오버플로우] 2줄 짜리 단순한 질문에 대한 명료한 분석과 이유를 달아준 명답.지난 5월에 올라온 이슈답변에 이 시간 현재 평점 38만 점, 황금배지 54개!단순한 따라하기 보다는. 왜 그래야 하는지 알려고...

Posted by Bryan Lee on Wednesday, December 2, 2015


- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들



지난 포스팅의 마지막에 Fizzbuzz 를 풀어 내는 희한한 예제를 게시한 바 있습니다. 좀 오래 되긴 했지만, 궁금해 하는 후배가 있어 한 번 같이 분석해 보았고 독특하고 엉뚱한 생각에 재미를 조금 느끼기도 했습니다.


실제 인터뷰시의 사례인지는 알 수 없지만, 이 해법을 소개한 페이지(Fizzbuzz 에 지겨워진 개발자들)을 잠깐 보면, "일단 똑똑하다", "뽑고 싶다" 거나 "생각이 한쪽으로 쏠린 사람", "팀웍을 해칠 것 같다" 는 등의 다양한 반응를 예로 들고 있네요. 글 쓴이(Samuel Tardieu) 자신은 일을 하면서 이런 식의 재미를 추구하는 방식을 좋아한다고 적고 있기도 합니다. 여러분은 어떠신가요?


우선, 소개된 원본 소스를 그대로 두고 한 번 훑어 보기로 합니다. 참, 한 번 실행시켜 보셨나요? 다른 잘 짜여진 소스와 마찬가지로 정확하게 결과를 출력해주고 있습니다. 희한하네? ^^;;

#include <stdio.h>

static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

int main()
{
  unsigned int i;
  for(i = 1; i <= 100; i++) printf(t[3&19142723>>2*i%30], i);
  return 0;
}


C 프로그램이고, t 라는 문자열들을 초기화해서 가지고 있는 문자열 포인터 배열이 보입니다. t[0] 는 "%d\n", t[1] 은 "Fizz\n" ...이 되겠지요.


그 외에는 for loop 에서 printf 함수를 쓴게 답니다. 그럼 어디에서 저런 괴물같은 결과가 나온 걸까요?


가만히 보면 printf 에서 사용하는 파라미터의 t[3 & 19142723 >> 2 * i % 30] 부분에 뭔가 비밀이 숨어 있다는 얘긴데요. [] 내부의 값은 t[] 배열의 index 로 0~3 사이의 정수값을 가지게 되겠네요.


(1) 어떤 트릭일지 들여다봅시다. 3 & 19142723 >> 2 * i % 30 ... 부분을 보면 우리 눈을 현혹시키는 것이 한 가지 숨어 있습니다. 다름 아닌 연산자 우선순위! 이게 첫번째 포인트.


저 계산식을 우선 연산자 우선순위에 맞게 괄호를 써서 어떤 값을 어떻게 가지게 되는지 확인해 보아야겠습니다.


3 & (19142723 >> ( (2 * i) % 30) ) 이렇게 됩니다. &는 bit AND 연산으로 가장 우선순위가 낮고, 그다음 낮은 것이 >> 즉 RShift(오른 쪽 비트와이즈 쉬프트 연산) 이지요.


자 그러면 (2*i) 가 가장 먼저 계산됩니다. i는 1~100 사이의 카운터니까, (카운터 * 2) 를 계산한 값을 30으로 나눈 나머지를 취하는군요. 즉 2%30, 4%30, 6%30, ..., 28%30, 30%30 처럼 {2, 4, 6, ..., 28, 30, 0, 2, 4, ....} 와 같이 나열해 보면 15를 주기로 0부터 리셋되어 2씩 값이 증가하는 수들이 나열됩니다.


(2) 그럼 위의 식은 19142723 이라는 묘한 숫자값을 처음에는 2회 Rshift, 그 다음 4회 Rshift, 이런 식으로 반복해서 2칸씩 Rshift 하고 3(2진수로 11)과 bit AND 연산을 하는 겁니다. 그렇다면...


(3) 모든 비밀은 19142723 이라는 숫자에 숨어 있는 거군요. 이 숫자를 2진수로 출력해 보면(아래 예제에 2진수 출력 함수가 있으니 확인해보세요) 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 이렇게 됩니다. 이해하기 쉽게 2자리씩 끊어서 보자구요.


위의 (2)에서처럼 생각하면, 카운터 i 가 1일 때 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 을 오른쪽으로 2칸 밀면 끝 자리에 00이 나오고 2진수 11과 AND 연산을 하면 00 즉 0이 나옵니다. 카운터 i가 2일 때는 오른쪽으로 4칸 밀면 끝자리에 00, 2진수 11과 AND 연산을 하면 00, 카운터 i 가 3일때는 6칸 밀고 끝자리에 01 과 2진수 11 AND 연산하면 01 이 나오지요.


즉 주어진 숫자를 2번씩 Rshift 하면서 2비트씩 뽑아 먹는 일을 계속 반복하는 겁니다.


자, 여기서 다른 Correct한 예제 솔루션들에서 출력되는 결과를 다시 한 번 보면,


 1

 i 값 = t[0]

 2

 i 값 = t[0]

 Fizz

 t[1]

 4

 i 값 = t[0]

 buzz

 t[2]

 Fizz

 t[1] 

 7

 i 값 = t[0]

 8

 i 값 = t[0]

 ...

 

 15

 t[3]

 16

 i 값 = t[0]


이 패턴이 15개씩을 주기로 반복되고 있습니다(당연하겠지요 ^^;; 아마도 짖궂은 코더는 여기서 아이디어를 착안했겠지요).


이제, 위 표 오른쪽 칼럼의 t[]의 인덱스를 보니 {0, 0, 1, 0, 2, 1, 0, ...} 이렇게 되는데, 2자리의 2진수로 바꿔서 다시 나열해 보면 {00, 00, 01, 00, 10, 01, 00, ...} 이렇게 됩니다.


위의 (3)에서 보이는 2진수를, 오른쪽에서부터 11를 제외하고 숫자를 2개씩 묶어서 나열해 보면 정확히 맞아 떨어지지요.


이제 감이 조금씩 잡히시나요? 매직넘버로 보였던 19142723 이라는 숫자가, 사실은 미리 관찰된 패턴에 의해 나오는 t[]의 인덱스 값을 오른쪽(가장 낮은 값에는 11을 채우고) 에서부터 왼쪽으로 채워나가서 만들어지는 2진수를 10진수로 표현한 값이라는 얘기입니다. 한 마디로 줄이면, 솔루션이 아니라 '사기' 입니다.


이해를 돕기 위해 아래 C++ 프로그램을 작동시켜 보시기 바랍니다. 말로 설명하는 것보다 프로그램을 보고 실행해 보는 것이 더 직관적으로 이해가 잘 될 수 있을 것 같습니다.

#include <iostream>

using std::cout;
using std::endl;

void fizzbuzz7(int _n)
{
    static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

    for(int i = 1 ; i <= _n; i++)
        printf(t[3 & (19142723 >> ((2 * i)%30))], i);
}

// 10진수를 2진수 값으로 출력
void binary_cout(int _n)
{
    if( _n <= 0)
        return;

    binary_cout(_n >> 1);

    cout << _n % 2;
}

int main()
{
    // Magic number 19142723는 2진수로 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 (2자리씩 끊어서 표기)
    int x, y;

    // fizzbuzz7 과 같은 로직에 해설을 추가하여 다시 써 봅니다
    for(int i = 1; i <= 100; i++) {
        cout << i << ": Magicnumber 를 " << (x = (2 * i) % 30) << "회 Rshift -> ";
        binary_cout(y = (19142723 >> x));
        cout << " 이 되고 이것을 0x11 과 bit and를 하면: " << (3 & y) << " 이 되며, 이것은 배열의 index." << endl;
    }

    return 0;
}



누가 작성했는지 모르지만 이 프로그램의 코더는 분명 일반인들의 허를 찌르는 센스를 가진 사람일 것 같습니다. 틀에 얽매이지 않는 자유로운 영혼, 저도 이런 분들을 좋아하고 또 본받으려고 노력합니다. 물론 프로그래머로서의 목표의식과 인성이 갖추어져 있다는 전제하에서.


발칙하다는 것은 기발하고 독창적이라는 말과 비슷합니다. 하지만 또 어떻게 보면 사회성이 떨어지고 규범을 지키지 않을 것 같은 부정적인 느낌을 주기도 합니다. 위의 솔루션은 사실 예상되는 답의 패턴에 문제해결 과정을 끼워 맞춘 것으로, 엄밀하게 말하자면 문제해결에 적합한 코딩은 아닙니다. 즉 나누는 수를 3과 5에다가 새로운 수인 7을 하나 더한다든지 하게 되면, 예상되는 결과로부터 패턴을 추출하여 프로그램 내의 매직넘버를 다시 계산하고 t[]의 index 를 계산하는 식도 다시 써야 하니까요.


또 이 솔루션은 문제를 출제한 사람을 사실은 조롱하고 있다고 생각할 수 있습니다. 하지만 그 기발한 방법으로 보아 좋은 직관력을 가졌다고 생각되며, 동적계획법이든 뭐든 적당한 훈련 과정을 거친 사람이고 문제해결을 위한 코딩의 감각이 어느 정도 갖추어져 있는 사람임은 분명해 보입니다.


이전 글: 

2015/12/07 - [Technical/Development] - [프로그래밍] Fizzbuzz 문제에 대하여(1)



- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Fizzbuzz(피즈버즈) 문제. 프로그래머라면 한 번 쯤 풀어 보거나 들어본 경험이 있을지도 모르겠다. 만약 프로그래머로서의 직업을 가지려고 하거나, 단순한 취미로라도 "나 프로그램 좀 짠다" 라는 말을 할 수 있으려면 꼭 접해 보았어야할 문제다.


만약 Fizzbuzz 문제를 처음 듣거나, 예전에 들었는데 가물가물한다...하는 분이라면 이참에, 다시 한 번 스스로를 돌아보는 계기를 마련해 보자. 이건 글을 쓰는 본인에게도 해당하는 말이 될게다. Solid programming이나 Grok coding는 수 많은 고민과 노력에 의해 충분히 만들어 질 수 있다고 나는 믿는다. 중요한 건 엔지니어로서의 동기, 자부심 또는 열정 아니겠는가?



Fizzbuzz 문제가 뭐임?


"Fizzbuzz questions"는 영국의 아이들이 학교에서 하게되는 일종의 놀이다(를 통한 동기 또는 흥미 유발을 위한). 해외의 학교에서는 놀이식으로 수업을 진행하는 경우가 많은 편인데, 우리 것과 궂이 비교하자면 일종의 369 게임과 비슷할까?


어쨌든 Fizzbuzz 문제를 프로그래머 채용 인터뷰시에 내 보았더니 대다수의 우수한 프로그래머의 경우 2분 이내로 답을 내어야 함에도 대부분의 컴퓨터과학 전공학과 졸업생들이 문제 자체를 제대로 풀지 못했고, 심지어 좀 한다는 시니어 프로그래머들도 솔루션을 내는데 10~15분이 걸렸다고 한다.


[조금 오래 된(2007~) 관련 페이지들]

프로그래머들이 왜 프로그램을 못짜?(CodingHorror)

Fizzbuzz 에 대해 너무 깊게 생각하지 말라(RaganWald)

Fizzbuzz로 코딩 잘하는 개발자 찾기(Imran)

Fizzbuzz still works(GlobalNerdy)



Fizzbuzz 문제를 활용하는 이유


"소프트웨어를 개발하다", "프로그램을 짜다" 는 말을 보통 "코딩한다" 라고 표현한다. 코딩을 잘한다는 건 무엇일까? "최소한의 시간에 주어진 문제를 정확히 해결하는 프로그램을 잘 만들 수 있는가" 라는 질문에서 어느 정도 답을 찾을 수 있겠지만 문제가 그리 간단하지만은 않다. 


결과가 빨리 나왔다고 해서 Sold 한 프로그램이라고 단정지을 수는 없으며, 코딩 이전의 설계와 코딩 이후의 추후 확장이 필요한 소프트웨어의 특성상, 전체적인 구조에 대한 설계에 충분한 시간과 공을 들여야 할 수도 있다. 또 결과에 오류가 없을 뿐 아니라, 요건 변경에 대비하여 확장성이 충분해야 하는 솔루션이 필요할 수도 있기 때문이다. 그렇다면 Fizzbuzz 문제 같은 간단한 코딩테스트를 전화 또는 대면인터뷰시에 사용하는 이유는 무엇일까?


위의 관련페이지들의 내용을 찬찬히 훑어 보면 답은 명확하다. 바로, 코딩을 잘하는 사람을 뽑기 보다는 코딩을 못하는 사람 즉, 최근 몇 개월간 코딩을 직접 경험해 보지 못한 사람, 코딩을 잘 하려는 노력이나 고민을 게을리 하는 사람을 가려내어, 채용 회사와 면접자의 불필요한 시간 손실을 일단 줄여보겠다는 단순한 의도라고 짐작할 수 있다.



Fizzbuzz 문제와 솔루션들


[문제(영어 원문)]

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".

우리 말로 풀어서 쓰면, 1부터 100사이의 숫자를 프린트하는 프로그램을 작성하는데 3의 배수이면 "Fizz"를, 5의 배수이면 "Buzz"를, 둘 모두의 배수 즉 15의 배수이면 "FizzBuzz" 를 프린트하도록 하라.


문제는 한마디로 간단하고 명확하다. 인터뷰시에 해당되는 중요한 얘기인데, 만약 문제에 구체적인 제약사항이 필요하다면 적극적으로 면접관에게 질문하고 상의하라. 문제가 요구하는 답은 다음과 같은 모양일 것이다.


1

2

Fizz

4

Buzz

.

.

또는

1 2 Fizz 4 Buzz Fizz 7 ...


본인이 스스로 능력있는 코더라고 자부한다면 2분 내외로 실제로 동작하는 프로그램 코드가 완성되어야 한다. 실제로 면접시 제시되는 방법은 다양하다. 전화인터뷰시에 말로 설명해야 될 수도 있고, 화이트보드에 손으로 코딩하거나(손코딩 & 눈디버깅 & 뇌컴파일) 또는 실제로 코딩할 수 있는 노트북이나 작업 PC가 주어질 수도 있다. 그때 그때 상황에 따라 잘 대처해야 하겠다.


만약 한 번도 이런 퀴즈나 문제를 접해본 적이 없는 응시자하면 적잖이 당황하게 될 것이다. 학교나 학원의 교수나 선생님들은 이런 문제의 해결법을 따로 가르쳐 주지 않기 때문이고, 그 해법이 너무나 다양할 수 있기 때문일 것이다. 또 결과는 언뜻 단순해 보이지만 해결 방법이 그리 썩 단순하지는 않으며, 뭔가 트릭을 쓰면 '쌈빡'한 방법으로 해결될 듯도 하기 때문에 더 머리 속이 복잡해 질지도 모른다.


[참고]

오메가: 수학귀신들의 잡학사전 - FizzBuzz 테스트

c2.com - Fizz Buzz Test


위의 c2.com 과 같은 사이트의 페이지들에는 다양한 개발 언어로 된 샘플 소스들이 많이 올라와 있다. 또 구글링을 통해 검색해 보면 다양한 Fizzbuzz 솔루션들을 접해 볼 수 있다. 참고로 위의 관련페이지들에 링크로 소개된 Imran의 사이트의 페이지에는 최근까지도 새로운 댓글들이 올라오고 있는 실정이다.


printf("1\n");

printf("2\n");

printf("Fizz\n"); ...


이런 답들도 있기는 하다 ^^;; 심지어...


for(...) {

    if(if i == 1) printf("1\n");

    else if ...


이런 답도 있다 ㅠㅠ.


본 글에서는 가장 직관적이고 보기 쉬운(평범한 필자의 수준에서 직접 작성한 것이기에) 대표적인 샘플 몇 개와 장난기 섞인 것들 몇 개를 소개한다. 주로 C/C++과 javascript, Python 으로 작성된 것들을 소개해 둔다.


글 맨 마지막에는, 다음글에 연속으로 게재될 엽기적인 답안 하나를 제시해 놓으려 하니 잘 읽어두시고 한 번 실행해 보기를 권한다.


Example 1, 2, 3 은 C++ 이지만 cout 만 제외하면 C 에서도 그대로 적용 가능하며, Example 4, 5, 6은 C의 printf 함수와 삼항조건연산자등을 사용하는 트릭들이 들어 있다.



C++ Example 1 : 2분만에 풀기에 딱 좋은 단순한 구성

#include <iostream>

using std::cout;

void fizzbuzz1(int _n)
{
    for(int i = 1; i <= _n; i++) {
        if(i % 15 == 0)
            cout << "fizzbuzz";
        else if(i % 3 == 0)
            cout << "fizz";
        else if(i % 5 == 0)
            cout << "buzz";
        else
            cout << i;
        cout << endl;
    }
}

int main()
{
    fizzbuzz1(100);
    return 0;
}


(아래 부터는 main()을 제외한 처리 함수 부분만 표기한다)


C++ Example 2 : 첫 번째 솔루션은 뭔가 비교조건이 중복되게 느껴진다. 그렇다면...

void fizzbuzz2(int _n)
{
    int check_more = false;

    for(int i = 1; i <= _n; i++) {
        if(i % 3 == 0) {
            cout << "fizz";
            check_more = true;
        }
        if(i % 5 == 0) {
            cout << "buzz";
            check_more = true;
        }
        if(!check_more)
            cout << i;
        else
            check_more = false;
        cout << endl;
    }
}


C++ Example 3 : 역시 첫번째 솔루션에 대한 약간의 변형

void fizzbuzz3(int _n)
{
    for(int i = 1; i <= _n; i++) {
        if(i % 3 == 0)
            cout << "fizz";
        if(i % 5 == 0)
            cout << "buzz";
        else if(i % 3 != 0)
            cout << i;
        cout << endl;
    }
}


C++ Example 4, 5, 6 : 평범하게 풀기 싫다면 약간의 C 언어만의 트릭을 써 보자

void fizzbuzz4(int _n)
{
    for(int i = 1 ; i <= _n; i++) {
        if(i%3 && i%5) printf("%d", i);
        printf("%s%s\n", i%3 ? "" : "fizz", i%5 ? "" : "buzz");
    }
}
void fizzbuzz5(int _n)
{
    // printf 함수는 출력한 문자의 갯수를 return 한다
    // 연산자 우선순위에 주의한다
    int i = 0;

    while(i++<_n)
        (i%3 || !printf("Fizz")) * (i%5 || !printf("Buzz")) && printf("%d",i), printf("\n");
}
void fizzbuzz6(int _n)
{
    char i = 0, n[3];
    while(i++ < 100)
        printf("%s%s%s\n", i%3 ? "":"Fizz", i%5 ? "":"Buzz", (i%3&&i%5&&sprintf(n, "%d", i)) ? n : "");
}


Javascript Example 1 : 참 단순 명료한 javascript !

for (var i=1; i<=100; i++) {   
    console.log( ((i%3 ? '':'Fizz') + (i%5 ? '':'Buzz') || i) )
}


Python Example 1 : 위의 일반적인 경우보다는 좀 더 Pythonic 하게 작성해 보자

FizzBuzz 와 같은 2가지 경우가 아니라 n개의 확장된 case에 대해서도 쉽게 처리가 가능한 방식을 구현할 수 있다.

# -*- coding:utf-8 -*-

# 각각의 나누는 수는 고유의 이름을 가진다. 이를 divisor_pairs(divisor, name) 이라는 튜플로 설정한다.
# 1~n 까지의 각각의 수(i)에 따라,
#   ... 나누는 수(divisor) 의 name이 있으면 프린트하고
#   ... 없으면 숫자 자체를 프린트한다

divisor_pairs = [
    (3, "3으로나누어짐"),
    (5, "5로나누어짐"),
    (7, "7로나누어짐")
]

for i in range(1, 501):
    # i에 대해 나누어 떨어질 경우, name 으로 스트링을 만든다
    name_string = "".join(name for (divisor, name) in divisor_pairs if i % divisor == 0)
    # name_string 을 프린트한다. 만약 빈문자열이면 i 를 프린트한다
    print(name_string or i)



다음 2회에서 분석해 볼 엽기적 솔루션

[솔루션이 소개된 원문 페이지 보기FizzBuzz and bored programmers ...아우~ 이걸 채용해야 하나?

void fizzbuzz7(int _n)
{
    static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

    for(int i = 1 ; i <= _n; i++) {
        printf(t[3 & 19142723 >> 2 * i % 30], i);
    }
}


다음 글: 2015/12/07 - [Technical/Development] - [프로그래밍] Fizzbuzz 문제에 대하여(2)



- Barracuda -

저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들



개인용 wiki 사이트, git 서버용으로 쓰던 Amazon EC2 vm에 고정 ip인 소위 Elastic IP를 물리고 인터넷 도메인(doubleshot.io)을 할당해서 쓰고 있다. git 서버의 접속 주소가 바뀌었으니 당연히 그에 맞게 git 의 remote 설정을 바꿔야 해서, 그 과정을 샘플  노트 형태로 작성해 둔다.

* git repository 에 해당하는 디렉토리(MyCppProjects)로 이동하여 변경 이전의 접속 주소를 "git remote" 로 확인
* 접속 방식은 기존 pem 인증키를 이용한 ssh 방식이므로 .git/config 파일의 접속주소 중 도메인 부분을 새로운 도메인(doubleshot.io)으로 교체하고 저장
✔ ~/MyCppProjects [master L|✔] 
22:16 $ git remote -v
wttest ssh://gituser@ec2-52-69-153-237.ap-northeast-1.compute.amazonaws.com/home/gituser/repos/MyCppProjects.git (fetch)
wttest ssh://gituser@ec2-52-69-153-237.ap-northeast-1.compute.amazonaws.com/home/gituser/repos/MyCppProjects.git (push)
✔ ~/MyCppProjects [master L|✔]
22:16 $ vi .git/config
[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "wttest"]
        url = ssh://gituser@doubleshot.io/home/gituser/repos/MyCppProjects.git
        fetch = +refs/heads/*:refs/remotes/wttest/*


* 변경된 주소를 확인하고 push 를 실행하여 git 서버와 동기화 진행

✔ ~/MyCppProjects [master L|✔] 

22:26 $ git remote -v

wttest ssh://gituser@doubleshot.io/home/gituser/repos/MyCppProjects.git (fetch)

wttest ssh://gituser@doubleshot.io/home/gituser/repos/MyCppProjects.git (push)

✔ ~/MyCppProjects [master L|✔] 

22:26 $ git push wttest master

Everything up-to-date

✔ ~/MyCppProjects [master L|✔] 

22:31 $


- Barracuda -



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들



ungit은 git 사용자를 위한 유용한 GUI 브라우저이다. ungit 은 node.js 상에서 동작하므로 이참에 node.js 를 source로부터 build하고 ungit 을 설치하는 과정을 메모해 둔다.




1. tarball 소스로부터 node.js 빌드 & 설치


bryan@bryan-laptop1:~/Downloads$ wget https://nodejs.org/dist/v0.12.7/node-v0.12.7.tar.gz

bryan@bryan-laptop1:~/Downloadssudo apt-get install build-essential python-dev

bryan@bryan-laptop1:~/Downloadstar xvzf node-v0.12.7.tar.gz

bryan@bryan-laptop1:~/Downloadscd node-v0.12.7/

bryan@bryan-laptop1:~/Downloads/node-v0.12.7./configure

bryan@bryan-laptop1:~/Downloads/node-v0.12.7make

bryan@bryan-laptop1:~/Downloads/node-v0.12.7sudo make install

bryan@bryan-laptop1:~/Downloads/node-v0.12.7node --version

v0.12.7


2. ungit 설치 및 테스트


bryan@bryan-laptop1:~sudo npm install -g ungit

bryan@bryan-laptop1:~$ cd git/bumblebee-ui/

✔ ~/git/bumblebee-ui [master ↓·4|✔] 

10:36 $ ungit



<관련 글 보기>

2015/08/03 - [Git Tip] Git Branch와 상태를 보여주는 Linux Prompt(bash-git-prompt)

2015/07/31 - [Git Tip] Git 브랜치를 보여주는 Linux 프람프트(prompt) - Ubuntu 14.04, bash 기준

2015/07/24 - [Git Tip] Git에 대한 궁금증들

2015/07/23 - [Git Tip] AWS EC2 VM을 이용한 Git 서버설정과 git 기본 사용법


- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


지난 번 Git 을 위한 Linux Prompt 변경 내용에 추가하여, Github.com 프로젝트 중에서 쓸만 한 것이 있어서 소개해 두도록 한다. 단순히 Branch 명을 보여 주는 것에서 벗어나서 브랜치의 자세한 상태까지 Prompt 에서 보여 주므로 아주 실속 있는 Git용 프람프트 유틸리티가 아닐까 한다. 설치 과정도 아주 간단하여 쉽게 적용해 볼 수 있다.


* 적용 대상: Bash를 사용하는 Linux 또는 Mac

* 설치 방법: https://github.com/magicmonty/bash-git-prompt 에 있는 내용 참조


* 설치 과정(Ubuntu 14.04, Bash 사용)

bryan@bryan-XenPC:~cd ~

bryan@bryan-XenPC:~git clone https://github.com/magicmonty/bash-git-prompt.git .bash-git-prompt

bryan@bryan-XenPC:~$ vi .bashrc <== .bashrc 의 case "$TERM" ... 문 다음의 적당한 위치에 추가

...

# If this is an xterm set the title to user@host:dir

case "$TERM" in

xterm*|rxvt*)

    PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"

    ;;

*)

    ;;

esac


# gitprompt configuration

# Set config variables first

GIT_PROMPT_ONLY_IN_REPO=1

# GIT_PROMPT_FETCH_REMOTE_STATUS=0   # uncomment to avoid fetching remote status

# GIT_PROMPT_START=...    # uncomment for custom prompt start sequence

# GIT_PROMPT_END=...      # uncomment for custom prompt end sequence

# as last entry source the gitprompt script

# GIT_PROMPT_THEME=Custom # use custom .git-prompt-colors.sh

# GIT_PROMPT_THEME=Solarized # use theme optimized for solarized color scheme

source ~/.bash-git-prompt/gitprompt.sh


# enable color support of ls and also add handy aliases

...

bryan@bryan-XenPC:~$ source .bashrc


* ~/.bash-git-prompt/themes 내에 테마파일을 설정해 두고 커스텀 테마를 지정해서 사용할 수도 있다

* 다음은 bash-git-prompt 프로젝트 홈에 소개 된 프롬프트 심볼들에 대한 간단한 설명들이다


[Local Status Symbols] 로컬브랜치 상태 심볼들

    ✔: repo가 더 이상 건드릴 부분 없이 clear 함

    ●n: n개의 staging된(index 에 add되었지만 commit은 되지 않은) 파일들이 있음

    ✖n: n개의 merge 되지 않은 파일들이 있음

    ✚n: n개의 수정되었지만 staging되지 않은(add가 필요한) 파일들이 있음

    …n: n개의 트래킹 되지 않은 파일들이 있음

    ⚑n: n개의 스태쉬(임시 저장소)가 존재함

[Branch Tracking Symbols]리모트저장소와 비교된 트래킹 심볼들

    ↑n: 리모트저장소보다 n개의 commit 단계가 앞서 있음(push 가 필요한 상태 암시)

    ↓n: 리모트저장소보다 n개의 commit 단계가 거슬러 내려가 있음

    ↓m↑n: 리모트저장소에 비해 브랜치가 갈라져 있음, 위 2가지가 복합됨

     L: 로컬브랜치만 존재함


[관련 글]

2015/07/23 - [Git Tip] AWS EC2 VM을 이용한 Git 서버설정과 git 기본 사용법

2015/07/24 - [Git Tip] Git에 대한 궁금증들

2015/07/31 - [Git Tip] Git 브랜치를 보여주는 Linux 프람프트(prompt) - Ubuntu 14.04, bash 기준


- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Git을 사용하는 방식은 명령형(커맨드라인; Command line; 또는 터미널 방식) 이거나 GUI Client 형(Mac, Windows)이거나 둘 중 하나일 것이다. Git의 내부 메커니즘을 알기 위해서이기도 하지만 커맨드라인 방식이 익숙해 지면 훨씬 수월해 지는 경우가 많은 듯 하다. 이 때 어쩔 수 없이 git status 를 쳐서 현재 어떤 브랜치에서 작업중인지를 수시로 확인해야 하는데, Linux 의 프람프트를 개조해서 사용하면 편리한 점이 많으므로 그 방법을 정리한다.


"Git 은 브랜치로 시작해서 브랜치로 울고 웃다가 브랜치로 끝난다" - Barracuda -


* 준비물: github 에서 공개된 아래의 스크립트를 계정의 Home에 내려 받아 둔다

* git-prompt.sh 는 Bash, Zsh 를 지원한다

bryan@bryan-XenPC:~$ curl -o ~/.git-prompt.sh https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh


* 홈 디렉토리의 .bashrc를 아래와 같이 수정하고 적용

* 기존의 프람프트에 영향, 변형이 없어야 하며, Git 으로 관리되는 작업 디렉토리에서만 프람프트가 자동으로 변경

bryan@bryan-XenPC:~$ vi .bashrc

...

# For Git prompt # <== Homedir에 .git-prompt.sh 가 있는지 확인하는 if문 추가

if [ -f ~/.git-prompt.sh ]; then 

    source ~/.git-prompt.sh 

fi


if [ "$color_prompt" = yes ]; then

    if [ -f ~/.git-prompt.sh ]; then # <== 원래의 PS1 설정을 위한 if 블록에 __git_ps1 변수 값을 끼워넣는 if 문 추가

        PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$$(__git_ps1 "(Branch:%s)") '

    else

        PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '

    fi

else

    if [ -f ~/.git-prompt.sh ]; then # <== 위와 마찬가지

        PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$$(__git_ps1 "(Branch:%s)") '

    else

        PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '

    fi

fi

unset color_prompt force_color_prompt

...


* 스크립트가 정상 작동하는지 디렉토리를 이동하면서 확인해 본다

~/git-test/gittest 디렉토리와 ~/PycharmProjects/PyTestProjects 는 git 으로 트래킹되는 working 디렉토리이다

bryan@bryan-XenPC:~$ source .bashrc

bryan@bryan-XenPC:~$ cd git-test/gittest/

bryan@bryan-XenPC:~/git-test/gittest$(Branch:develop) cd ../../kernel_src/

bryan@bryan-XenPC:~/kernel_src$ cd ../PycharmProjects/PyTestProjects/

bryan@bryan-XenPC:~/PycharmProjects/PyTestProjects$(Branch:master)


[관련 글]

2015/07/23 - [Technical/Development] - [Git Tip] AWS EC2 VM을 이용한 Git 서버설정과 git 기본 사용법

2015/07/24 - [Technical/Development] - [Git Tip] Git에 대한 궁금증들

2015/08/03 - [Git Tip] Git Branch와 상태를 보여주는 BASH-GIT-PROMPT



- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Git 을 다루는 엔지니어들이 점점 늘고 있다. 한글 입력상태에서 자판으로 git을 치면 '햣' 이 된다, 햣~! 너도 나도 써야한다라고 하니, 이게 마치 무슨 대세가 된 건 아닌가 착각도 하게 되는데, 막상 써보려니 손에 익고 간편한 cvs, svn 과는 비슷하면서도 뭔가 좀 다르고 어렵기도 하다.


근데, 간편안내서 같은 곳을 보면 "어렵지 않아요 ;)" 하면서 사람을 막 꼬드긴다(가서 보면 더 너무 쉽게 써놔서 더 아리송하다). 그게 대체 뭐길래...하면서 약도 좀 오르고 궁금하기도 하다. 이제, 하나 하나 따져가면서 왜 그런가 고민하고 정리해 두는 버릇이 있는 필자가 git을 한 번 다루어 보려 한다.



Git을 써야 하는 이유


결론부터 간단히 말하자면, Git 은 약간의 개념공부와 실습이 필요한 Tool 이다. cvs, svn 과는 달리 Git은 Distributed+VCS(버전관리도구) 이고 cvs가 하지 못하는 일을 가능하게 하기 때문이다. Linux의 주인공 리누즈 토발츠는 자신이 만든 Git을 소개하면서 cvs를 'devil'(악마...라기 보다는 죄악에 가깝다고 본다) 라고 까지 한다(아래 유튜브 영상 참고). cvs나 svn을 쓰면서 branch-merge하기 전에 풀백업을 받아 두는 엔지니어의 심정이라면 Git을 써야만 하지 않을까?



왜 Git을 써야 하는지에 대해 인터넷에 수많은 문서들이 올라오고 있지만, 이 페이지를 보고 좀 더 명료해 지리라 생각된다.

http://pismute.github.io/whygitisbetter/



아마도, 이 정도 개념만 잡고 개발팀에 투입 돼도 "이 친구 쫌 하는데!" 라는 시선을 받아 볼 수 있을런지도 모르겠다. 칭찬은 엔지니어에게 스팀팩을 놔주는 거 아닐까...



Git 에 관한 필수 용어와 관행(Convention) 또는 약속



master, branch와 origin


* Repository(저장소=줄여서 repo)가 만들어지면(git init) 최초로 유일하게 존재하는 브랜치가 master branch 이고 전체 저장소의 본류가 된다. 이 브랜치는 그 자체로 'master' 라는 이름을 가진다

* 'origin' 은 약속된 이름으로, 현재 push(원격 서버에 업로드) 또는 clone/pull(원격서버로부터 내려받음), Fetch(원격 repo내용 확인) 하려는 원격저장소(서버의 repo)의 기본(default) 이름이다

  - git clone git://remote.blah/test.git 을 하게 되면 자동으로 현재 디렉토리에 test 가 만들어지고 원격저장소의 이름은 'origin' 이 된다(-o 옵션으로 마지막 인수로 새로운 디렉토리명(예: test_dev)을 덧붙여서 다른 이름을 부여할 수도 있다)

  - 굳이 원격저장소의 이름을 origin 으로 하지 않아도 무방하지만, 프로젝트의 최초에 생성되는 원격저장소는 오리지널 이라는 의미의 'origin' 이란 이름을 가지도록 한다

  - origin 이 아닌 원격저장소의 이름은 git remote rename oldname newname , git remote rm name1 ... 이런 식으로 변경 또는 삭제가 가능하다. 또는 다른 저장소이름을 origin 으로 바꾸는 것도 물론 가능하다



작업 디렉토리, 로컬/원격저장소(repository), Index, Head


*작업디렉토리는 버전을 관리할 소스 파일들이 있는 곳이며, git init 으로 초기화한 디렉토리와 그 하위 디렉토리 전체가 관리 대상이다. 이 곳의 전체 또는 일부의 파일들은 git add 로 index라는 가상공간에 등록(staging 단계) 되며, Git에 의한 관리 대상이 된다. 수정 또는 새로이 추가된 파일은 항상 git add 명령으로 스테이징단계를 거쳐야 한다

* Staging 단계의 파일들은 git commit 명령에 의해 repo라는 가상 공간에 기록되며, commit 되는 각 단계의 Head[각주:1] 내용이 기록된 시간 순서대로 꼬리를 물고 늘어서 있는 구조이다. commit 시에 저장되는 내용은 파일 전체 내용이 아닌, 각 파일들의 변경사항들이 묶음으로 기록됨으로써 빠르고 가벼운 동작이 잘 유지된다.

* Git에서 repository는 로컬(development)과 원격에 각각 존재하므로, 개념을 잘 구분해서 써야한다


Git protocol


* Git이 원격저장소와 통신하는 방식은 4가지가 있다

  - 로컬 프로토콜: 로컬 디스크나 NFS로 연결된 저장소일 경우 file:///디렉토리/xxx.git 과 같은 방식으로 연결

  - SSH프로토콜: ssh를 통한 암호화된 통신 방식으로 ssh를 이용할 로그인 계정이 있을 때 사용할 수 있다. 공개용 프로젝트에는 부적합하다

  - Git프로토콜: SSH와 유사하지만 전송 속도가 빠르며 TCP 9418port를 사용한다.  별도의 인증수단이 없어서 Gitosis 와 같은 별도의 인증패키지를 사용해야 한다

  - HTTP/HTTPS 프로토콜: 의미 그대로 HTTP 또는 HTTPS 프로토콜을 그대로 사용하며, 방화벽에 크게 구애받지 않는다


Log 와 Commit Identifier


* Git으로 수행한 모든 commit들은 빠짐 없이 저장소에 기록되며, 각 commit 단계별로 유일한 Key(해시 키) 값(예: f6f9e... 처럼 생긴 16진수들) 이 관리된다.

* git log --graph 와 같은 명령으로 이 Key 값을 조회한 후 revert(되돌리기), cherry-pick(브랜치 가져오기), Tag(꼬리표 달기) 등의 명령을 수행하게 된다


Tag 달기


* Git에서 수행한 commit 단계(의 Key 값)에 대해 의미를 가진 꼬리표를 달아서 버전관리의 이정표를 구분하도록 한다

  - 예: git tag 1.0.0 edbbca65716825e770d1509e9359612260c08c73



Git 과 전문가들이 권고하는 관행(Convention)


reponame.git 형태로 repository 디렉토리명을 쓴다


유일한 오리지널 저장소(repo)인 경우 원격 저장소에 "origin" 이라는 이름을 붙여 쓴다


Branch 의 종류와 네이밍 관행


Vincent Driessen 이 제시하는 브랜칭 모델


backlogtool.com/git-guide 에서 정리한 브랜칭 모델. 위와 모델은 같으나 단순화 되어 눈에 더 잘 들어온다


* Main 브랜치: Master, Develop

* Supporting(Topic) 브랜치: Feature, Release, Hotfix

 - Feature 브랜치: Develop 에서 분기하는 일종의 Topic 브랜치(들), 완료되면 Develop에 병합(merge)한 후 Push

 - Release 브랜치: Develop 에서 릴리즈를 위한 분기(release-브랜치명), 완료되면 Master에 병합한 후 release 태깅

    배포 후 Develop에 병합

 - Hotfix 브랜치: 긴급 수정 작업, Master 에서 긴급 수정작업을 위한 분기(hotfix-브랜치명), 완료되면 Master에 병합, 배포 후 Develop에 병합


[참고]  이 브랜칭 모델이 모든 경우에 정답일 수는 없다. 프로젝트나 사업의 성격상, 어떤 경우는 여러 개의 Release가 고객사별로 브랜칭되어 동시에 병행/교차되어 진행되는 복잡한 구조가 더 적합할 수도 있다. 단, 관리의 복잡성으로 인한 부담이 너무 크지 않다면 말이다. 결국 Git 라는 Tool 자체의 자유도로 인하여, 기존의 SVN등으로 해결 못하던 많은 한계점들을 피해갈 수 있는 가능성이 생겼다는 것은 분명해 보인다.


도움이 되는 온라인 실습/강의/설명 자료


[한글로 된 친절한 문서 페이지들]

https://git-scm.com/book/ko/v2

http://backlogtool.com/git-guide/kr/

http://www.slideshare.net/einsub/svn-git-17386752

http://sapeyes.blog.me/70118257910

https://opentutorials.org/module/217

http://www.creativeworksofknowledge.com/2014/09/03/git-intro-and-common-workflow/



[영문으로 된 실습, 설명, 강의 페이지들]

http://pcottle.github.io/learnGitBranching/ - 한글(일부만) 지원

http://gitref.org/

https://www.atlassian.com/git/tutorials/

http://gitimmersion.com/index.html

http://wildlyinaccurate.com/a-hackers-guide-to-git/


[관련 글]

2015/07/23 - [Git Tip] AWS EC2 VM을 이용한 Git 서버설정과 git 기본 사용법


- Barracuda -

  1. 마치 카세트테이프의 헤드(또는 구식 LP 레코드의 바늘) 처럼, 재생되는 현재의 위치를 가리키는 개념이다. 그래서 merge 옵션에 보면 --no-ff (패스트포워드 하지마삼...) 같은 것도 있다. [본문으로]
저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


git(깃) 서버를 Amazon EC2 인스턴스에 설치하고, Repo를 운영 관리하는 기초과정 정리


* 준비해야 할 것들

 - 서버: AWS EC2 t2.micro, ubuntu 14.4, 접속주소: ec2-xx.amazonaws.com

 - 클라이언트: Ubuntu 14.4 PC, EC2 vm ssh 접속을 위한 보안 키파일(여기서는 AWSKP_as1.pem)


EC2 vm측, git 서버 설치 과정


* 필수 패키지 설치

root@aws-ubt14-as01:~# apt-get install git-core

root@aws-ubt14-as01:~# apt-get install openssh-server


* linux 계정(=gituser) 추가, 권한 설정 및 key pair 생성

* 이 방법은 git 계정을 공용으로 사용하는 경우로 사용자 개별 설정은 불가(사용자별 권한 설정 등 세부적인 관리가 필요할 경우 gitolite 와 같은 관리 패키지 사용 필요)

root@aws-ubt14-as01:~# adduser --home /home/gituser --shell /bin/bash gituser

root@aws-ubt14-as01:~# visudo <== 아래 line 추가

...

gituser ALL=(ALL) ALL

...

root@aws-ubt14-as01:~# su - gituser


* 생성된 Key pair의 공용(public)키 부분을 .ssh/authorized_keys 에 추가

gituser@aws-ubt14-as01:~ssh-keygen -b 1024 -f gituser -t dsa

gituser@aws-ubt14-as01:~cat gituser.pub >> .ssh/authorized_keys


gituser@aws-ubt14-as01:~chmod 600 .ssh/authorized_keys

gituser@aws-ubt14-as01:~sudo chown -R gituser.gituser .ssh/

gituser@aws-ubt14-as01:~$ mkdir repos; cd repos


* Remote client에서 사용할 작업디렉토리 생성, repo 설정

* 새로운 repo 가 필요해질 때마다 별도로 작업해 주어야 함

gituser@aws-ubt14-as01:~/repos$ mkdir ec2new.git; cd ec2new.git

gituser@aws-ubt14-as01:~/repos/testprj.git$ git init --bare --shared



Remote client(개발자 PC)측 git 설정


* 기존 ec2new 프로젝트를 Local PC에서 git 서버에 push(업로드&동기화)하는 과정까지...

* EC2 vm에서 생성한 key pair의 개인(private)키 파일을 local 로 복사해 와서 저장

* ssh-add 로 키 파일이 자동 적용되게 한 후, EC2 vm 의 gituser 계정에 ssh 로그인이 가능한지 확인

bryan@bryan-XenPC:~$ scp -i AWSKP_as1.pem root@ec2-xxx.amazonaws.com:/home/gituser/gituser ./gituser.pem

bryan@bryan-XenPC:~$ ssh-add /home/bryan/gituser.pem

bryan@bryan-XenPC:~$ ssh gituser@ec2-xxx.amazonaws.com


* 작업 디렉토리(프로젝트: ec2new) 가 있다고 가정

bryan@bryan-XenPC:~/git-test/ec2new$ ls -l

합계 12

-rw-rw-r-- 1 bryan bryan  6  7월 22 20:39 README

-rw-rw-r-- 1 bryan bryan 31  7월 22 20:39 a.c

-rw-rw-r-- 1 bryan bryan 51  7월 22 22:33 b.cpp


* 현재 디렉토리를 git repo 등록(초기화)

bryan@bryan-XenPC:~/git-test/ec2new$ git init


* git repo에 현재 디렉토리의 모든 파일 추가(tracking 가능=staging상태=index에 등록)

bryan@bryan-XenPC:~/git-test/ec2new$ git  add .


* a.c 파일을 수정하였다면 수정 사실을 git에 알린다(관리 대상으로 등록 의미)

bryan@bryan-XenPC:~/git-test/ec2new$ git  add a.c


* Commit 이전까지는 Staging 단계, commit 이되면 HEAD에 기록(= local에 commit)됨

* 즉, git에서 commit 은 local 에서의 최종 저장 단계

bryan@bryan-XenPC:~/git-test/ec2new$ git commit -m "1st commit"

[master 346b586] Modified a.c

 1 file changed, 1 insertion(+)


* Remote repo 주소를 git에게 알림

bryan@bryan-XenPC:~/git-test/ec2new$ git remote add ec2new ssh://gituser@ec2-xxx.amazonaws.com/home/gituser/repos/ec2new.git


* Remote repo 확인

bryan@bryan-XenPC:~/git-test/ec2new$ git remote -v

ec2new ssh://gituser@ec2-xxx.amazonaws.com/home/gituser/repos/ec2new.git (fetch)

ec2new ssh://gituser@ec2-xxx.amazonaws.com/home/gituser/repos/ec2new.git (push)


* Remote repo 에 업로드(최종 기록)

bryan@bryan-XenPC:~/git-test/ec2new$ git push ec2new master

Counting objects: 5, done.

Delta compression using up to 4 threads.

Compressing objects: 100% (2/2), done.

Writing objects: 100% (3/3), 341 bytes | 0 bytes/s, done.

Total 3 (delta 0), reused 0 (delta 0)

To ssh://gituser@ec2-xxx.amazonaws.com/home/gituser/repos/ec2new.git

   eaacfc7..346b586  master -> master

[관련 글]

2015/07/24 - [Technical/Development] - [Git Tip] Git에 대한 궁금증들


- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Redis(레디스; REmote DIctionary System)은 요즘 각광 받고 있는 In-memory Data(key-value) Store이다. 언제고 한 번 다뤄 봤으면 했는데, 마침 비슷한 기회가 주어져서 고가용성을 확보할 수 있는 중요한 설정 방법을 찾아보고 Redis 를 활용하는 아주 간단한 Python Client 예제를 정리하여 실전을 위해 기록해 두고자 한다.


"쓰다 보니 내용이 좀 많습니다. 2편 정도로 나누려 했으나, 다루려고 하는 내용을 한 편에 모아서 구성하는 편이 더 좋다고 생각했으니 스크롤 압박이 심하더라도 양해 바랍니다" - Barracuda -


"Redis 는 DBMS 인가?", "임시 데이터 저장용 캐시라고도 하던데..." 하는 잡다한 얘기는 여기서는 생략하자. 자주 쓰다 보면 아~ Redis 는 이래서 쓰는구나 하고 느끼면 되겠고, 기회가 된다면 왜 Naver Japan에서 LINE 서비스에 이를 적용했는지, Memcached와 왜 자주 비교 되는지, Twitter, Flickr, Github, Tumblr, Instagram 등의 구축 사례를 구글링 등으로 찾아 보면 어느 정도 감이 잡힐 것이라고 생각된다.


본 포스팅에서는 아래의 내용 위주로 정리해 두기로 한다.


* Redis 아키텍처 결정 및 설치/설정 방법

* Redis Sentinel 의 추가 설정과 HA 운영

* Redis Client example - python 버전

* Redis 를 서비스에 적용할 때의 주의사항


1. Redis 아키텍처 결정 및 설치/설정 방법


Redis 로 구성할 수 있는 아키텍처들은 그 용도, 규모에 따른 조합이 다양하게 있을 수 있으며, 응용/확장 형태로 Naver 등에서는 Zookeeper 와 함께 사용하기도 하고, 때로는 Haproxy/Keepalived 를 병행하여 실제 서비스에 사용되기도 한다. 여기서는 Redis 만을 사용한 구성 모델을 생각해 보자.


* Single(단일) 서버 구성 - 개발, 테스트 용도

* MS(Master_1-Slave_N) 구성 - 소규모 개발 및 테스트 용도

* Redis MS & Sentinel*2(Total 5) 구성 - 소규모 개발 및 서비스 용도

* Redis MS & Cluster & Sharding - 중규모 이상 서비스 용도



Redis 와 Sentinel 을 이해하기 위한 간단한 예


여기서는 비교적 단순하면서도 가용성(HA: High Availability)이 보장될 수 있는 3 번째 방법을 채택하고,실제로 구현해 보자. 우선 아래 첫 번째 그림을 보자. Redis 를 설명하는 웹페이지들이 예를 들어 자주 보여 주는 그림이다.


그림 1. Single Maser-Slave & Sentinel


그림1은 하나 씩의 Master-Slave 와, 이들을 감시하는 Sentinel이 Master down시 Slave를 Master 를 승격(Failover 과정을 통해)시켜 주게 되는 좋은 예라고 할 수 있다. 그러나 Redis와 Sentinel 의 작동 과정과 원리를 직관적으로 잘 보여 준다는 점 외에는 이 아키텍처로부터 얻을 수 있는 것은 거의 없다. 왜냐하면 "3대의 서버 중 Master 1대만 죽었을 때(quorom=1일때. quorom은 뒤에 설명)" 만 안정성이 보장된다는 한계를 지닌 아키텍처이기 때문이다.


부하를 분산하기 위해 Master 는 write-only, Slave 는 read-only 로 작동하게 된다고 가정하면, Redis에 접속하여 데이터를 처리하는 Client는 Sentinel로부터 Master와 Slave 의 주소를 알아내려 하기 때문에, Slave나 Sentinel이 죽거나 특정한 2대가 동시에 죽는 어떤 경우에라도 정상적인 서비스를 기대하지 못하게 된다는 사실을 잘 따져 보도록 하자(Sentinel이 왜 필요해 지는지는 아래 실제 구성에서 자연스럽게 정리 될 것이다).



서버 3대로 안정적인 Redis 서비스를 할 수 있는 방법은?


그렇다면, 위와 같은 3대의 서버로 어떻게 하면 비교적 안정적인 서비스 환경을 구성할 수 있을까? 아래 그림을 보자.


그림 2. Redis Master 1, Slave 2 & 3 Sentinels


그림 2는 2대의 서버가 동시에 죽더라도 최소한 Data의 읽기 또는 쓰기 중 한가지만은 가능한 구조이다. Master는 2대의 Slave 들에 각각 변경 데이터를 보내 주며, 3개의 Sentinel 들이 3대의 Redis 서버와 자신을 제외한 나머지 2대의 Sentinel 들을 감시하는 구조이다. Sentinel 을 3대 이상 홀수로 사용하는 이유는, Master 가 죽고 Failover 상황이 발생생했을 때, 홀수개의 Sentinel들이 각각 투표에 참여하여 과반수 이상이 선출되도록 하기 위함이다(redis.io 권고). 그러나 ...


* 이 아키텍처에는 결정적인 한계가 존재한다. 위 그림에서는 Master 서버 내의 Redis 서버(데몬, 서비스)가 Down 되었다면 3개의 Sentinel들이 투표에 참여하여 과반수인 2표(quorom >= 2) 이상을 얻은 Slave 가 Master 로 선출될 것이다. 그런데 뭐가 문제인가?


* 실제 상황에서는 서버(머신) 내의 특정 프로세스 또는 데몬이 죽어 버리는 경우도 있지만, 머신 자체의 하드웨어(전원, 네트워크) 고장/오동작 또는 네트워크 장비/포트 단절 등에 의해 서버 자체의 연결이 끊어지는 경우가 더 많을 수 있다. 그림 1에서와 같은 문제점과 유사하게, Master 서버 머신이 Down 되어 버리거나(왜 문제일까? 아래에서 Quiz 로 제시한다), 2대의 서버가 동시에 Down 되어 버린다면 마찬가지의 참담한 결과를 맞게 된다.


해결 방법은 무엇일까? 단순히 Redis-Sentinel 서버를 1대 더 추가하여 4대를 만들어서 사용하는 방법도 있고, 3대의 Sentinel을 모두 외부의 다른 서버에 설치하거나, Redis 에 접속하는 Client 머신 내에 설치를 권장하기도 한다. 또는 시스템 내의 그리 바쁘지 않은 서버 2대를 따로 선정해서 Sentinel 만 설치/설정해 두면 상대적으로 그림은 간단해지고 위의 문제도 어느 정도 해결 가능하다.



그림 3. Redis Master 1, Slave 2 & 5 Sentinels


그림 3에서는 2대의 서버 머신이 동시에 죽는 어떠한 상황이라도 Redis 서비스 자체는 훌륭하게 동작하게 될 수 있다. 특별히 2대의 Redis 서버가 동시에 다운되었더라도, Redis Client 측에서 Master 를 Write & Read 용으로 사용하도록 약간의 코딩을 해 둔다면 말이다.


* 투입되는 서버의 댓수가 많다면 충분히 안정적인 서비스가 가능하겠지만, 하드웨어나 VM이 무한정 사용될 수는 없는 것이고 보면, 외부 Sentinel 머신을 한 개 정도만 더 추가해서 Redis 3, Sentinel 5 구성이 비교적 합리적인 선택이 아닐까 생각된다(물론 운영의 부담이 되는 관리포인트가 늘어남은 감수해야 한다)


* 결국은 여건을 충분히 고려한 취사 선택이 어느 정도로 필요하다는 말이 되겠고, 본 포스팅에서는 마지막의 그림 3을 목표 아키텍처로 하여 시스템을 준비하고, 실제로 구현하고 검증까지 진행해 보도록 하자.



Redis 서버용 머신을 이용해 Master(1)-Slave(2) 를 구성한다


* 5대의 서버를 준비한다(본 테스트 환경에서 사용한 머신은 모두 ubuntu14.4 vm이며 10.0.0.0/16 네트워크에 연결)

 - ubuntu14-pv1: 10.0.10.1 (Redis Master, Sentinel)

 - ubuntu14-pv2: 10.0.10.2 (Redis Slave1, Sentinel)

 ubuntu14-pv3: 10.0.10.3 (Redis Slave2, Sentinel)

 - ubuntu14-pvha1: 10.0.0.1 (Sentinel)

 - ubuntu14-pvha2: 10.0.0.2 (Sentinel)


그림 4. Redis Master 1, Slave 2


* 1단계 작업으로 그림 4에서와 같이 3대의 서버로 Redis Master-Slave 구성부터 진행

ubuntu14-pv1~3, ubuntu14-pvha1,2 모든 5대의 서버에서 동일하게 작업

root@ubuntu14-pv1:~# apt-get install redis-server redis-tools

root@ubuntu14-pv1:~# apt-get install python-pip

root@ubuntu14-pv1:~# pip install redis

root@ubuntu14-pv1:~# vi /etc/sysctl.conf -> 아래 내용을 추가

# Redis M-S sync

vm.overcommit_memory=1


# Increase max open file limit

fs.file-max = 1048576

root@ubuntu14-pv1:~# sysctl -p

root@ubuntu14-pv1:~# ulimit -n 65535

root@ubuntu14-pv1:~# vi ~/.bashrc -> 스크립트 최 상단에 아래 내용을 삽입

ulimit -n 65535


* ubuntu14-pv1 서버를 Redis master 로 설정하기 위한 작업이다

* 설치 자체는 어렵지 않지만, config 설정시에 주의해야할 것들이 있으니, 붉은 글씨의 라인에 쓰인 설명을 잘 참고한다

* requirepass 는 자신이 Master 가 되었을 때 요구할 접속 암호, masterauth 는 자신이 Slave 일 때 Master 로 접속하기 위한 암호이다. 본 설정에서는 뒤에 Sentinel 에 의해서 Master <-> Slave 전환이 자유로워야 하므로 동일하게 맞추어야 한다.

root@ubuntu14-pv1:~# vi /etc/redis/redis.conf

daemonize yes

pidfile /var/run/redis/redis-server.pid

port 6379

timeout 0

tcp-keepalive 0


bind 10.0.10.1 # 서버 머신 자체의 IP 주소


# slaveof 10.0.10.3 6379 # Master 는 slaveof 설정이 없어야 한다

masterauth 1234

requirepass 1234


repl-ping-slave-period 10

repl-timeout 60


loglevel notice


logfile /var/log/redis/redis-server.log


databases 16


save 900 1

save 300 10

save 60 10000


stop-writes-on-bgsave-error yes


rdbcompression yes

rdbchecksum yes

dbfilename dump.rdb


dir /var/lib/redis


slave-read-only yes


# Yes for high bandwidth, No for high latency or high traffic

repl-disable-tcp-nodelay yes


slave-priority 100


# Mitigation for replication lag, only for Master

#min-slaves-to-write 1

#min-slaves-max-lag 10


appendonly no

appendfilename "appendonly.aof"

appendfsync everysec

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb


lua-time-limit 5000


slowlog-log-slower-than 10000

slowlog-max-len 128


notify-keyspace-events ""


hash-max-ziplist-entries 512

hash-max-ziplist-value 64

list-max-ziplist-entries 512

list-max-ziplist-value 64

set-max-intset-entries 512

zset-max-ziplist-entries 128

zset-max-ziplist-value 64


activerehashing yes


client-output-buffer-limit normal 0 0 0

client-output-buffer-limit slave 256mb 64mb 60

client-output-buffer-limit pubsub 32mb 8mb 60


hz 10


aof-rewrite-incremental-fsync yes


* ubuntu14-pv2, 3 서버를 Redis slave 로 설정하기 위한 작업이다

* 붉은 글씨의 라인에 쓰인 설명을 잘 참고한다. 나머지 부분 내용은 Master와 동일하다

root@ubuntu14-pv2:~# vi /etc/redis/redis.conf

daemonize yes

pidfile /var/run/redis/redis-server.pid

port 6379

timeout 0

tcp-keepalive 0


bind 10.0.10.2 # 서버 머신 자체의 IP주소


slaveof 10.0.10.1 6379 # 초기에 Master 로 사용할 1번 서버의 IP주소

masterauth 1234

requirepass 1234


repl-ping-slave-period 10

repl-timeout 60


loglevel debug


logfile /var/log/redis/redis-server.log


databases 16


save 900 1

save 300 10

save 60 10000


stop-writes-on-bgsave-error yes


rdbcompression yes

rdbchecksum yes

dbfilename dump.rdb


dir /var/lib/redis


slave-read-only yes


# Yes for high bandwidth, No for high latency or high traffic

repl-disable-tcp-nodelay yes


slave-priority 100


# Mitigation for replication lag, Comment out for slaves

#min-slaves-to-write 1

#min-slaves-max-lag 10


appendonly no

appendfilename "appendonly.aof"

appendfsync everysec

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb


lua-time-limit 5000


slowlog-log-slower-than 10000

slowlog-max-len 128


notify-keyspace-events ""


hash-max-ziplist-entries 512

hash-max-ziplist-value 64

list-max-ziplist-entries 512

list-max-ziplist-value 64

set-max-intset-entries 512

zset-max-ziplist-entries 128

zset-max-ziplist-value 64


activerehashing yes


client-output-buffer-limit normal 0 0 0

client-output-buffer-limit slave 256mb 64mb 60

client-output-buffer-limit pubsub 32mb 8mb 60


hz 10


aof-rewrite-incremental-fsync yes


* Master부터 두 번째 Slave 까지, 모두 위와 같이 작업을 했다면, Master 부터 순차적으로 Redis 를 재시작한다.

* 진행 과정을 관찰하기 위해 redis-server 기동 후 log에 tail 을 걸어 둔다

root@ubuntu14-pv1:~# service redis-server restart

root@ubuntu14-pv1:~# tail -f /var/log/redis/redis-server.log

                _._                                                  

           _.-``__ ''-._                                             

      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64 bit

  .-`` .-```.  ```\/    _.,_ ''-._                                   

 (    '      ,       .-`  | `,    )     Running in stand alone mode

 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379

 |    `-._   `._    /     _.-'    |     PID: 1140

  `-._    `-._  `-./  _.-'    _.-'                                   

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |           http://redis.io        

  `-._    `-._`-.__.-'_.-'    _.-'                                   

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |                                  

  `-._    `-._`-.__.-'_.-'    _.-'                                   

      `-._    `-.__.-'    _.-'                                       

          `-._        _.-'                                           

              `-.__.-'                                               


[1140] 09 Jul 01:13:08.762 # Server started, Redis version 2.8.4

[1140] 09 Jul 01:13:08.763 * DB loaded from disk: 0.001 seconds

[1140] 09 Jul 01:13:08.763 * The server is now ready to accept connections on port 6379

[1140] 09 Jul 01:13:08.763 - DB 0: 633 keys (0 volatile) in 1536 slots HT.

[1140] 09 Jul 01:13:08.763 - 0 clients connected (0 slaves), 834680 bytes in use

[1140] 09 Jul 01:13:09.189 * Starting BGSAVE for SYNC

[1140] 09 Jul 01:13:09.191 * Background saving started by pid 1143

[1143] 09 Jul 01:13:09.218 * DB saved on disk

[1143] 09 Jul 01:13:09.219 * RDB: 0 MB of memory used by copy-on-write



Redis Master에 데이터를 입력하고 Slave 로 복제 되는지 확인하자


* Sentinel 전용 머신(ubuntu14-pvha1,2)에서는 Sentinel만 작동하여야 하므로, redis-server 데몬을 내리고 자동실행을 해제한다(source build 가 아닌  apt-get 같은 방법으로 redis-server 를 설치하면 부팅후 자동실행까지 설정되므로)

root@ubuntu14-pvha2:~# service redis-server stop

root@ubuntu14-pvha2:~# rm /etc/init.d/redis-server

root@ubuntu14-pvha2:~# update-rc.d redis-server remove


* Sentinel 전용 머신(또는 redis-tools 가 설치된 외부 머신)의 redis client 를 이용해서 확인을 수행한다

* Master 에 접속하여 "testkey01":"data1234" Key-Value 를 저장하고 Slave 에서 get 으로 확인

root@ubuntu14-pvha2:~# redis-cli -a 1234 -h 10.0.10.1 -p 6379

10.0.10.1:6379> info replication

# Replication

role:master

connected_slaves:2

slave0:ip=10.0.10.2,port=6379,state=online,offset=1386,lag=0

slave1:ip=10.0.10.3,port=6379,state=online,offset=1386,lag=0

master_repl_offset:1386

repl_backlog_active:1

repl_backlog_size:1048576

repl_backlog_first_byte_offset:2

repl_backlog_histlen:1385

10.0.10.1:6379> set testkey01 'data1234'

OK

10.0.10.1:6379> 

root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.2 -p 6379

10.0.10.2:6379> get testkey01

"data1234"

10.0.10.2:6379> 

root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.3 -p 6379

10.0.10.3:6379> get testkey01

"data1234"


전체적인 동작이 정상적인 것을 확인했다. 자 이 상황에서 Quiz 하나 !(검토하신 아이디어를 공유하거나 질문 하실 분은 댓글로 기재해 주세요)


* <Quiz 1> Master 서버 머신이 Down(redis.io 문서에서는 partition 이라고 표현한다. 즉 shutdown, power down, network 단절 등) 된다면 어떤 일이 일어 나는지 생각해 보자. Master 는 선출될 수 있을까? Read 동작은 가능한가? 원래의 Master 가 다시 연결되면 전체 시스템이 정상 동작 할까?(전제조건: Master 1, Slave 2(read-only), Sentinel 3, quorom=2 일 경우)

[답은 아래로 drag 하세요. 중요한 것은 왜 그럴까를 따져 보는 것입니다]

 - Master 는 선출되지 않는다. 즉, 쓰기 작업은 불가능

 - Read 는 가능하다(Sentinel을 통하여 Slave 를 알아 낸다면)

 - Master 가 살아나면 원 상태로 정상 동작 한다


여기까지 3대의 머신으로 Redis Master-Slave 를 구성하는 기본 작업을 진행했다. Master 를 Write 전용으로, Slave 를 Read 전용으로 하여 부하는 분산하고 데이터를 복제 보존하는 효과까지는 기대할 수 있지만, 자동으로 Failover를 수행하지는 못하는 구성이다.


2. Redis Sentinel 의 추가 설정과 고가용(HA)적 운영


이제 목표 아키텍처인 그림 3 과 같은 구성을 만드는 것이 다음 작업이다.


5대의 머신에 Sentinel을 설정, 기동하고 log 파일을 보면서 진행 과정 관찰


* Sentinel 수행 머신인 ubuntu14-pv1~3, ubuntu14-pvha1,2 에서 각각 다음의 작업을 동일하게 수행한다.

* 모든 Sentinel의 설정 파일은 동일하여야 한다

* 현재의 Redis Master 인 10.0.10.1:6379 를 모니터 대상으로 설정하며, quorom=2, 서비스 이름은 stn-master

root@ubuntu14-pv1:~# vi /etc/redis/sentinel.conf

daemonize yes

pidfile "/var/run/redis/sentinel.pid"

logfile "/var/log/redis/sentinel.log"


# port <sentinel-port>

port 26379


sentinel monitor stn-master 10.0.10.1 6379 2 # <== Failover시 Sentinel 이 자동으로 값들을 수정

sentinel down-after-milliseconds stn-master 2000

sentinel failover-timeout stn-master 90000 <== Failover 완료되어야 하는 시간. 데이터 량에 따라 값을 증가

sentinel auth-pass stn-master 1234

sentinel config-epoch stn-master 1 # <== Failover시 Sentinel 이 자동으로 수정(1씩 증가)


# <=== 여기부터 Sentinel 이 자동으로 설정 내용을 추가, 관리


root@ubuntu14-pv1:~# /usr/bin/redis-sentinel /etc/redis/sentinel.conf

root@ubuntu14-pv1:~# tail -f /var/log/redis/sentinel.log

                _._                                                  

           _.-``__ ''-._                                             

      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64 bit

  .-`` .-```.  ```\/    _.,_ ''-._                                   

 (    '      ,       .-`  | `,    )     Running in sentinel mode

 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379

 |    `-._   `._    /     _.-'    |     PID: 1155

  `-._    `-._  `-./  _.-'    _.-'                                   

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |           http://redis.io        

  `-._    `-._`-.__.-'_.-'    _.-'                                   

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |                                  

  `-._    `-._`-.__.-'_.-'    _.-'                                   

      `-._    `-.__.-'    _.-'                                       

          `-._        _.-'                                           

              `-.__.-'                                               


[1094] 09 Jul 15:58:49.321 # Sentinel runid is 0f83482ecc6a3724d40f4469ac8368a1742be8fd

[1094] 09 Jul 15:58:50.324 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379

[1094] 09 Jul 15:58:50.324 * +slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379

[1094] 09 Jul 15:58:54.013 * +sentinel sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.1 6379

[1094] 09 Jul 15:58:55.429 * +sentinel sentinel 10.0.10.3:26379 10.0.10.3 26379 @ stn-master 10.0.10.1 6379

[1094] 09 Jul 15:58:56.708 * +sentinel sentinel 10.0.0.1:26379 10.0.0.1 26379 @ stn-master 10.0.10.1 6379

[1094] 09 Jul 15:58:57.791 * +sentinel sentinel 10.0.0.2:26379 10.0.0.2 26379 @ stn-master 10.0.10.1 6379


<...>


root@ubuntu14-pvha2:~# vi /etc/redis/sentinel.conf

root@ubuntu14-pvha2:~# /usr/bin/redis-sentinel /etc/redis/sentinel.conf

                _._                                                  

           _.-``__ ''-._                                             

      _.-``    `.  `_.  ''-._           Redis 2.8.4 (00000000/0) 64 bit

  .-`` .-```.  ```\/    _.,_ ''-._                                   

 (    '      ,       .-`  | `,    )     Running in sentinel mode

 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379

 |    `-._   `._    /     _.-'    |     PID: 26166

  `-._    `-._  `-./  _.-'    _.-'                                   

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |           http://redis.io        

  `-._    `-._`-.__.-'_.-'    _.-'                                   

 |`-._`-._    `-.__.-'    _.-'_.-'|                                  

 |    `-._`-._        _.-'_.-'    |                                  

  `-._    `-._`-.__.-'_.-'    _.-'                                   

      `-._    `-.__.-'    _.-'                                       

          `-._        _.-'                                           

              `-.__.-'                                               


[4204] 09 Jul 15:58:55.946 # Sentinel runid is 82a6faa318223af0362f59095f8c138be9f9e76d

[4204] 09 Jul 15:58:56.949 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379

[4204] 09 Jul 15:58:56.949 * +slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379

[4204] 09 Jul 15:58:57.617 * +sentinel sentinel 10.0.10.3:26379 10.0.10.3 26379 @ stn-master 10.0.10.1 6379

[4204] 09 Jul 15:58:57.731 * +sentinel sentinel 10.0.10.1:26379 10.0.10.1 26379 @ stn-master 10.0.10.1 6379

[4204] 09 Jul 15:58:57.935 * +sentinel sentinel 10.0.0.1:26379 10.0.0.1 26379 @ stn-master 10.0.10.1 6379

[4204] 09 Jul 15:58:58.182 * +sentinel sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.1 6379


* 서비스에 투입된 Sentinel 의 정보를 확인해 보자(모든 Sentinel 에서의 조회 값이 동일함)

root@ubuntu14-pvha2:~# redis-cli -a 1234 -h 10.0.10.1 -p 26379

10.0.10.1:26379> info

# Server

redis_version:2.8.4

redis_git_sha1:00000000

redis_git_dirty:0

redis_build_id:a44a05d76f06a5d9

redis_mode:sentinel

os:Linux 3.13.0-55-generic x86_64

arch_bits:64

multiplexing_api:epoll

gcc_version:4.8.2

process_id:1094

run_id:0f83482ecc6a3724d40f4469ac8368a1742be8fd

tcp_port:26379

uptime_in_seconds:177

uptime_in_days:0

hz:11

lru_clock:1036194

config_file:/etc/redis/sentinel.conf


# Sentinel

sentinel_masters:1

sentinel_tilt:0

sentinel_running_scripts:0

sentinel_scripts_queue_length:0

master0:name=stn-master,status=ok,address=10.0.10.1:6379,slaves=2,sentinels=5


이제 설정 과정은 끝이 났다. 테스트 시나리오를 정하고 검증을 수행해 보자. 본 포스팅의 하이라이트라고 할 수도 있는 중요한 부분이다.


Test Case 1: Master 셧다운


* Master에 ssh 로 접속하여 셧다운을 수행하고 Slave 2 의 로그를 살펴 보자

root@ubuntu14-pv1:~# shutdown -h now


* Slave 2(10.0.10.2)의 로그 모니터링, 해설

[1063] 09 Jul 16:03:13.245 # +sdown master stn-master 10.0.10.1 6379 <== Down 감지(주관적)

[1063] 09 Jul 16:03:13.323 # +odown master stn-master 10.0.10.1 6379 #quorum 2/2 <== Down 감지(객관적)

[1063] 09 Jul 16:03:13.323 # +new-epoch 10 <== 후속 조치 발동

[1063] 09 Jul 16:03:13.323 # +try-failover master stn-master 10.0.10.1 6379 <== Failover 발동

[1063] 09 Jul 16:03:13.324 # +vote-for-leader 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10

[1063] 09 Jul 16:03:13.327 # 10.0.0.1:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10

[1063] 09 Jul 16:03:13.327 # 10.0.0.2:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10

[1063] 09 Jul 16:03:13.328 # 10.0.10.2:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10

[1063] 09 Jul 16:03:13.390 # +elected-leader master stn-master 10.0.10.1 6379 <== 투표 종료

[1063] 09 Jul 16:03:13.391 # +failover-state-select-slave master stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:13.458 # +selected-slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379 <== Slave2(10.0.10.3:6379) 이 최종 선정

[1063] 09 Jul 16:03:13.458 * +failover-state-send-slaveof-noone slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379 <== Slave2 를 10.0.10.1 의 slave 에서 제외

[1063] 09 Jul 16:03:13.513 * +failover-state-wait-promotion slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:14.392 # +promoted-slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379 <== Slave2 를 Master 로 승격 요청

[1063] 09 Jul 16:03:14.393 # +failover-state-reconf-slaves master stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:14.443 * +slave-reconf-sent slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:14.635 # +sdown sentinel 10.0.10.1:26379 10.0.10.1 26379 @ stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:15.410 * +slave-reconf-inprog slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:15.485 # -odown master stn-master 10.0.10.1 6379

[1063] 09 Jul 16:03:16.423 * +slave-reconf-done slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379 <== 기존 Slave2의 config 를 변경(10.0.10.2 의 slave로)

[1063] 09 Jul 16:03:16.524 # +failover-end master stn-master 10.0.10.1 6379 <== Failover 종료 알림

[1063] 09 Jul 16:03:16.524 # +switch-master stn-master 10.0.10.1 6379 10.0.10.2 6379

[1063] 09 Jul 16:03:16.525 * +slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379

[1063] 09 Jul 16:03:16.560 * +slave slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.2 6379 <== Down된 10.0.10.1 은 Slave 로 내려감

[1063] 09 Jul 16:03:18.611 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.2 6379


* 이제 1번 과정에서 redis-cli 로 Master에 데이터를 입력(Set)하고 Slave에서 Get 으로 확인하는 과정을 수행해 보자

root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.3 -p 6379

10.0.10.2:6379> keys *

1) "testkey01"

10.0.10.2:6379> set key_into_master2 'data5678'

OK

10.0.10.2:6379> 

root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.2 -p 6379

10.0.10.3:6379> keys *

1) "key_into_master2"

2) "testkey01"

10.0.10.3:6379> 



Test Case 2: 새로운 Master 셧다운


* Case 1 에 이어서 Master(10.0.10.2)에 ssh 로 접속하여 셧다운을 수행하고 Master, Sentinel 머신측 의 로그를 살펴 보자

root@ubuntu14-pv2:~# shutdown -h now


* Slave(10.0.10.2)의 로그 모니터링

[1063] 09 Jul 16:13:22.809 # +sdown master stn-master 10.0.10.2 6379

[1063] 09 Jul 16:13:22.902 # +new-epoch 11

[1063] 09 Jul 16:13:22.902 # +vote-for-leader f7ca052de1d504d263511d5fb9d032c56a0fa351 11

[1063] 09 Jul 16:13:23.910 # +odown master stn-master 10.0.10.2 6379 #quorum 3/2 <== 셧다운 감지

[1063] 09 Jul 16:13:24.666 # +sdown sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.2 6379

<== Master 완료 후 Failover 완료시까지 시간(sentinel.conf 에 90초 설정) 경과, 결국 Failover 실패로 끝남

[1063] 09 Jul 16:16:22.920 # +new-epoch 12

[1063] 09 Jul 16:16:22.921 # +try-failover master stn-master 10.0.10.2 6379

[1063] 09 Jul 16:16:22.921 # +vote-for-leader 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12

[1063] 09 Jul 16:16:22.923 # 10.0.0.2:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12

[1063] 09 Jul 16:16:22.923 # 10.0.0.1:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12

[1063] 09 Jul 16:16:22.984 # +elected-leader master stn-master 10.0.10.2 6379 <== 투표 종료

[1063] 09 Jul 16:16:22.985 # +failover-state-select-slave master stn-master 10.0.10.2 6379

[1063] 09 Jul 16:16:23.085 # +selected-slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379 <== 유일한 Redis(10.0.10.3:6379) 가 최종 선정

[1063] 09 Jul 16:16:23.086 * +failover-state-send-slaveof-noone slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379 <== 10.0.10.2 의 slave 에서 제외

[1063] 09 Jul 16:16:23.157 * +failover-state-wait-promotion slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379

[1063] 09 Jul 16:16:24.071 # +promoted-slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379 <== Master 로 승격 요청

[1063] 09 Jul 16:16:24.072 # +failover-state-reconf-slaves master stn-master 10.0.10.2 6379

[1063] 09 Jul 16:16:24.117 * +slave-reconf-sent slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.2 6379

[1063] 09 Jul 16:16:24.118 # +failover-end master stn-master 10.0.10.2 6379

[1063] 09 Jul 16:16:24.118 # +switch-master stn-master 10.0.10.2 6379 10.0.10.3 6379

[1063] 09 Jul 16:16:24.119 * +slave slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379

[1063] 09 Jul 16:16:24.146 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379

<== Down 된 10.0.10.1, 10.0.10.2 는 Slave 로...

[1063] 09 Jul 16:16:26.121 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379

[1063] 09 Jul 16:16:26.212 # +sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379


* Sentinel 전용머신(10.0.0.2)의 로그 모니터링

[4204] 09 Jul 16:13:23.058 # +new-epoch 11

[4204] 09 Jul 16:13:23.058 # +vote-for-leader f7ca052de1d504d263511d5fb9d032c56a0fa351 11

[4204] 09 Jul 16:13:23.700 # +sdown master stn-master 10.0.10.2 6379

[4204] 09 Jul 16:13:23.801 # +odown master stn-master 10.0.10.2 6379 #quorum 3/2

[4204] 09 Jul 16:13:24.841 # +sdown sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.2 6379

[4204] 09 Jul 16:16:23.079 # +new-epoch 12

[4204] 09 Jul 16:16:23.079 # +vote-for-leader 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12

[4204] 09 Jul 16:16:24.496 # +switch-master stn-master 10.0.10.2 6379 10.0.10.3 6379

[4204] 09 Jul 16:16:24.497 * +slave slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379

[4204] 09 Jul 16:16:24.533 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379

[4204] 09 Jul 16:16:26.528 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379

[4204] 09 Jul 16:16:26.583 # +sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379



2대의 Redis 서버가 남은 상황에서 Master 머신 마저 셧다운 되었을 때의 결과가 흥미롭다. 첫 번째 Failover 는 후보 머신이 더 이상 없어서 Failover timeout(90초)까지 경과하고, 이후의 후속 조치로 마지막 남은 Redis가 Master로 승격되어 처리됨을 확인하였다. 다시 말하면, Redis 시스템 내의 모든 Slave 는 Read-only 이므로, 설정된 Failover timeout 만큼의 시간 동안은 데이터의 쓰기가 실패하게 됨을 잘 알고 상황에 적절히 대처할 수 있어야 한다.



장애 후 복구 과정에서의 처리 절차


위의 Test Case 2까지 진행된 상황에서 redis-cli 로 접속하여 다음과 같이 데이터를 기록해 두자.

root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.3 -p 6379

10.0.10.3:6379> set testkey02 'data7890'

OK


이제 현재의 Master 인 10.0.10.3 의 Redis 에는 최종 버전의 데이터가 저장되어 있다. 여기서 두 번째 Quiz!

셧다운되어 있는 10.0.10.2 머신이 다시 기동되었을 경우 어떤 일이 일어 나는가?10.0.10.2 에 Sentinel 이 기동되었다면 어떤 일이 일어날까? 과연 마지막에 저장한 testkey02 데이터는 보존 될 것인가?


[답은 아래로 drag 하세요] 전제 조건: 기존에 작동하던 3개의 Sentinel 이 작동 상황을 잘 유지하고 있을 경우

10.0.10.2 가 부팅되고  Redis 서비스가 작동 되면, 약간의 시간이 지난 후, 셧다운 되기 전 Master 였던 10.0.10.2는 얌전히 Slave 로 떨어지고, 마지막에 저장된 testkey02 데이터는 이상 없이  보존된다.


* 최종 Master 머신(10.0.0.3)의 로그 모니터링

[1063] 09 Jul 16:16:26.121 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379

[1063] 09 Jul 16:16:26.212 # +sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379

[1063] 09 Jul 16:49:19.799 # -sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379

[1063] 09 Jul 16:50:50.019 * +fix-slave-config slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379 <== Sentinel 에 의해, 셧다운 전에 Master였던 10.0.10.2 Redis는 Slave 로 전환된다



본 포스팅의 실험 결과에서 가장 중요하게 생각해야 할 점은, Sentinel 을 통해 가용성을 유지하기 위한 본 아키텍처에서, Sentinel 을 확인 없이 셧다운한 상태에서 Redis 를 기동하게 되면, 원래 Master 였던 Redis 의 과거 버전 데이터로 인하여 최근 버전에 데이터를 잃어버릴 가능성이 다분하다는 것이다.


그렇다면, 만약 어쩔 수 없는 사정으로 Sentinel 이 작동하지 않게 되어 있고, 수작업으로 최신 데이터를 유지하고 싶다면 어떻게 하면 될까?아래의 순서를 잘 이해하고 차분히 작업을 진행하면 된다.


첫째. 마지막 버전의 데이터를 저장하고 있다고 판단되는 Redis 머신을 Master로 결정한다

둘째, 현재까지 작업에 투입된 모든 Sentinel 을 셧다운하고(# killall redis-sentinel), Redis 서비스도 종료한다(service redis-server stop)

셋째, Redis M-S 구축 첫 단계의 설정에서, Master로 결정한 머신의 redis.conf 에서 slaveof 라인을 comment 처리하고,나머지 머신의 redis.conf 에서 slaveof 라인의 주소를 Master 주소로 변경한다

넷째, 만약을 위해, 각 Redis 머신들의 데이터 파일(/var/lib/redis/dump.rdb)를 백업해 둔다

다섯째, 모든 Sentinel 들의 config 를 아래와 같이 동일하게 수정한다

root@ubuntu14-pvha2:~# vi /etc/redis/sentinel.conf 

...

port 26379


sentinel monitor stn-master 10.0.10.3 6379 2 <== Master로 결정한 머신의 주소로 변경

sentinel down-after-milliseconds stn-master 2000

sentinel failover-timeout stn-master 90000

sentinel auth-pass stn-master 1234

sentinel config-epoch stn-master 1 <== 최종 값을 1로 변경


#

# Generated by CONFIG REWRITE <== 여기부터 마지막 라인까지 삭제

dir "/root"

sentinel known-slave stn-master 10.0.10.1 6379

sentinel known-slave stn-master 10.0.10.2 6379

sentinel known-sentinel stn-master 10.0.10.2 26379 f7ca052de1d504d263511d5fb9d032c56a0fa351

sentinel known-sentinel stn-master 10.0.10.1 26379 0f83482ecc6a3724d40f4469ac8368a1742be8fd

sentinel known-sentinel stn-master 10.0.0.1 26379 9e177cbda8d73c7fc11ac17a6a6d0eef02b3b762


sentinel known-sentinel stn-master 10.0.10.3 26379 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3

여섯째, 여기 까지의 모든 작업 내용을 다시 점검

일곱째, Redis Master - Slave 순으로 시작, Sentinel 을 모두 시작 후 정상 작동 및 데이터 점검



3. Redis Client example - python 버전


다음은 Redis 와 Sentinel 이 적용된 데이터스토어에 접근하고 각종 Key-Value 데이터를 처리하는 Python Client 의 예제이다. 거의 프로그램 개발 초기의 원형이라고 보면 되겠다. 개발을 위한 라이브러리는 redis-py 를 pip 로 설치하는 정도면 충분하다.


* Redis Master 에 접속하여 쇼핑 데이터를 기록, Slave 에서 데이터를 조회, 출력하는 Python application 이다

( 참고: 1000 명의 고객 쇼핑 카트 정보 기록에 0.18 sec, 읽기에 0.1 sec 소요 - 1cpu, 1GB ram, xen vm)

root@ubuntu14-pvha2:~# apt-get install pip

root@ubuntu14-pvha2:~# pip install redis

root@ubuntu14-pvha2:~/pysrc# vi sentinel_test3.py 

# -*- coding:utf-8 -*-
  
import sys, random, time
from redis import Redis, exceptions, RedisError
from redis.sentinel import (Sentinel, SentinelConnectionPool,ConnectionError,
                            MasterNotFoundError, SlaveNotFoundError)
  
# Redis 접속 기본 설정값
listSentinel = [('10.0.10.1', 26379), ('10.0.10.2', 26379), ('10.0.10.3', 26379), ('10.0.0.2', 26379)]
strServiceName = 'stn-master'
strRedisPass = '1234'
nDB = 0
  
nMaxUser = 1000
  
sentinel = Sentinel(listSentinel, socket_timeout=0.1)
try:
    #sentinel.discover_master(strServiceName) # No need for this
    #sentinel.discover_slaves(strServiceName)
    master = sentinel.master_for(strServiceName, password=strRedisPass, db=nDB, socket_timeout=0.1)
    slave = sentinel.slave_for(strServiceName, password=strRedisPass, db=nDB, socket_timeout=0.1)
  
except MasterNotFoundError:
    print 'Master not found or Sentinel instances not runnung'
    sys.exit()
except SlaveNotFoundError:
    print 'Slave not found or Sentinel instances not runnung'
    sys.exit()
except ConnectionError:
    print 'Connection Error. Check if Sentinel instances are running'
    sys.exit()
  
start_time = time.time()
  
for n in range(1, nMaxUser):
    for m in range(1, random.randint(0, 5)):
        master.hset( "cart.user:"+str(n), random.randint(1, 300), random.randint(1, 5) )
  
time_elapsed_1 = time.time() - start_time
  
start_time = time.time()
  
for n in range(1, nMaxUser):
    slave.hgetall("cart.user:"+str(n))
  
time_elapsed_2 = time.time() - start_time
  
count = 0
  
for n in range(1, nMaxUser):
    data = slave.hgetall("cart.user:"+str(n))
    if len(data) > 0:
        count = count + 1
        print count, " [Cart.user:"+str(n)+"] ", data
  
print "---------------------------------------------------"
print "[Time for writing]: ", time_elapsed_1, " sec., [Time for reading]: ", time_elapsed_2, " sec."



주의 사항: Redis 를 서비스에 적용했을 때의 '대재앙' 을 막아라


운영상 설정 사항 관련 주의 사항

- Sentinel(즉 redis-sentinel) 을 부팅시 자동 실행하도록 두지 마라: 최종 변경 데이터를 날리거나 전체 데이터가 날아갈 수 있다. 귀차니즘은 당신의 몸값을 깎을 수도 있다.

- 데이터를 Disk에 저장하는 save 설정 주기를 짧게 하지 마라: Disk IO 때문에 속도 저하가 발생할 수 있다. 다양한 관찰과 실험을 통해 최적의 save 설정 주기를 찾고 사용하라.

- 데이터 량에 따른 최적의 Failover timeout 값을 찾고 sentinel.conf에 적용하라: 이유는 위에서 설명하였다.

- 메모리 사용 량을 초과하도록 방치하면 안된다: 자칫 메인 메모리를 다 쓰면 가상메모리까지 사용하게 되어 디스크 스와핑이 다량 발생하고, 시스템은 거의 먹통 수준에 빠질 수도 있다.

- Sentinel은 아직은 불완전하다. 결코 과신하지 마라: 덜 Busy한 시간에 주기적으로 dump.db를 백업하고 중요한 장애 복구시는 수동으로 M-S를 지정해서 작업한다.

- Redis.log, Sentinel.log 를 수시로 모니터링하라: Redis 머신의 디스크가 고장나는 등의 하드웨어 장애는 Sentinel이 감지할 수 없다. Master 가 이런 장애를 만나면 Read-only 상태로 바뀔 수도 있다(config 설정에 따라 다르긴 하다)



데이터 관리 방법/설계 관련 주의 사항

- 운영 중에 keys, flushall, flushdb 명령어를 쓰지 마라: 메모리 DB라고 과신하다가 큰 코 다친다. Single thread 방식이며, 데이터 구조와 처리 방식이 상대적으로 Memcached보다 복잡해서 데이터 건수가 많아지면 Memcached 보다는 훨씬 느려진다(널리 알려진 사실이다. 100만건 처리에 멤캐시는 1~2ms, 레디스는 1000ms). 운영 중에 이런 전체 데이터를 긁는 명령을 쓴다면 Redis 를 사용하는 시스템이 일시 먹통이 될 테고, 한 동안 뒤통수가 따가울 수도 있다. 운영지침이나 데이터 관리 등급을 따로 지정해서 체계적으로 데이터를 운영/관리하는 방식을 써야 할지도 모른다

- 개발자는 Redis Master 는 Write-only로, Slave는 Read-only 로 잘 구분해서 잘 써야한다. 코드리뷰시 주요 검토사항이 될 수도 있다.

- 콜렉션(Collection: List, Set, Hash 등) 에는 한 번에 최대 수 천 건 정도의 데이터만 Insert한다.

- 최대 데이터 량을 예측하고, 필요시 클러스터링과 샤딩을 검토하여 아키텍처 설계에 반영하고 개선하라. 때때로 Zookeeper 나 Haproxy 와 같은 다른 솔루션을 검토해야 할 경우도 있을 수 있다.



- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Python을 활용한 GUI 프로그래밍에 꼭 한 번씩 거론되는 것이 있다. 오픈소스 프로젝트로서, Python을 위한 C++바인딩인 PySide 프로젝트가 그것이다. 참고로, 기존의 PyQt4에는 API Level 1과 API level 2 가 각각 존재하고 있으며, 그에 비해 PySide는 PyQt API level 2 만을 고려하여 구현되어 있다(PySide 쪽이 LGPL[각주:1]이 적용되어 PyQt 보다 저작권 면에서 더 자유롭다...정도만 얘기하고, Qt의 역사나 핀란드의 노키아, 디지아 등 잡다한 궁금한 점들은 구글링으로 해결하자).


서두가 너무 길었다. 간단히 글 게재의 목적만 말하자면, PySide 1.2.1 버전은 대다수 리눅스 배포판 등의 소프트웨어 다운&설치 명령들(yum, zypper, apt-get 또는 pip)로 간단히 설치, 사용이 가능하다. 하지만 2015년 6월 현재 최신 1.2.2 버전(2014년 4월 릴리즈. 1.2.1은 2013년 8월 릴리즈)은, 공식페이지(pyside.readthedocs.org)에서도 "Linux 바이너리는 제공하지 않음" 이라고 발표하고 있는 실정이다.


뵨 글에서는 Windows 와 Linux 에서의  Python 2.7.x 를 위한 PySide 1.2.2 설치 방법에 대해 정리해 두도록 한다.


Windows 7에 PySide 1.2.2 설치 과정 & 주의사항


Windows에서는 특별히 어려운 점은 없다. Python 2.7.x 이 기존에 설치되어 있다고 가정하고, PyQt 4.10.3 for python 2.7 을 설치하면 된다(참고로, 각종 windows 용 python extensions 들은 http://www.lfd.uci.edu/~gohlke/pythonlibs/ 로 가면 거의 찾을 수 있으며, python 버전에 맞는 바이너리를 다운로드 받아 자유롭게 사용 가능하다).


get-pip.py 다운로드c:\python2\scripts 에 저장하고 pip 설치. pip로 PySide 를 설치하면 간단히 끝.

C:\> mkdir c:\python2\scripts

C:\> CD c:\python2\scripts

C:\> python get-pip.py

C:\> ./pip install -U PySide


Linux에 PySide 1.2.2 설치하는 방법


구글링 해 보면 다양한 방법들이 존재하고 있다. 어떤 리눅스 배포판에서는 자체 공식 repo 를 통해서도 간단히 설치할 수도 있겠고, Python 관련 사이트에서 설치 바이너리(deb, rpm 등) 다운로드 받아 설치할 수도 있겠다. 여기서는 소스 다운로드 & 빌드를 통해 설치하는 방법만을 다룰텐데, 대다수의 내용은 가장 정평한 pyside.readthedocs.org 에 있는 내용을 거의 그대로 참조하였다.


* Source Build 를 위한 준비 과정

$ sudo apt-get install build-essential git cmake libqt4-dev libphonon-dev python2.7-dev libxml2-dev libxslt1-dev qtmobility-dev


* Python 2.7 용 pip 를 다운로드 & 설치. Build를 위한 wheel 설치

$ mkdir ~/PySideBuild; cd ~/PySideBuild;

$ wget https://bootstrap.pypa.io/get-pip.py

$ sudo python2.7 get-pip.py

$ sudo pip2.7 install wheel


* PySide 1.2.2 다운로드 & Build

$ wget https://pypi.python.org/packages/source/P/PySide/PySide-1.2.2.tar.gz

$ tar -xvzf PySide-1.2.2.tar.gz

$ cd PySide-1.2.2

$ python2.7 setup.py bdist_wheel --qmake=/usr/bin/qmake-qt4


* Build 된 배포 바이너리 설치 & 확인

$ ls -l dist

합계 12616

-rw-rw-r-- 1 bryan bryan 12916258  6월 13 18:00 PySide-1.2.2-cp27-none-linux_x86_64.whl

$ sudo pip2.7 install dist/PySide-1.2.2-cp27-none-linux-x86_64.whl



설치 완료된 PySide 정상 작동 확인


$ python

Python 2.7.6 (default, Mar 22 2014, 22:59:56) 

[GCC 4.8.2] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> import PySide

>>> PySide.__version__

'1.2.2'

>>>



- Barracuda -



  1. GPL과는 달리, LGPL은 소스를 공개하지 않고도 상용프로그램을 만들 수 있다. [본문으로]
저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


OpenSUSE 13.2 에서 python 개발환경을 구성할 경우에 거쳐가야 할 몇 가지 단계들이 있다. 아마도 대다수 OpenJDK를 채택한 Linux들에도 해당될 듯 한데, Pycharm이 Linux를 위한 구현을 java 로 하였기 때문에 발생하는 일종의 패키지 충돌 문제를 만나게 된다.


다시 말하면, Opensource 진영에서 채택한 OpenJDK는 OpenSUSE 뿐 아니라 Ubuntu 등 대다수 Open Linux 배포판들이 java 플랫폼을 위해 기본으로 포함하고 있다. 그러나 Linux용 Pycharm은 Oracle-Sun jdk/jre 기반으로 만들어져 있으며, 이 문제는 update-alternatives 라는 유틸리티로 극복할 수 있다. 이를 잘 활용하면, 서로 다른 버전의 gcc가 각각 특정 라이브러리에 국한되어 컴파일 되어야 하는 상황에 대한 훌륭한 솔루션이 되기도 한다.


본 포스팅에서 다루어 나가려 하는 단계는 간단히 다음과 같다. 아래에 다루는 내용은 OpenSUSE 뿐 아니라 대다수 linux 배포판에서 적용 가능하며, 마지막 부분의 pycharm 설치 과정은 pycharm 새버전을 설치 또는 업그레이드하는 과정으로 유용하게 참조될 수 있다(pycharm 5.0.1 에서도 적용 가능 확인. 2015/12/7).


* Oracle jdk 를 다운로드하고 설치

* update-alternatives 설정으로 문제 해결

* pycharm 설치


Oracle jdk 8을 다운로드하고 설치


* Oracle jdk 8(rpm) 를 다운로드 한다(☞가기)(참고로 Oracle은 JDK 7에 대해서는 더 이상 지원을 하지 않겠다고 밝힌 바 있다. 2015년 현재 다운로드가능한 버전은 8u45 이다).

* 주의: ubuntu 15.* 부터는 rpm 으로 설치가 되지 않으니 여기 를 참조하여 ppa repository 를 등록하여 설치하여야 한다

$ su -

# java -version -> 현재 설치 된 jdk가 Openjdk인지 확인

# rpm -i --nodeps jdk-8u45-linux-x64.rpm -> nodeps 옵션이 없으면 기존 OpenJDK 와의 의존성 문제로 설치가 실패됨에 유의

Unpacking JAR files...
    rt.jar...
    jsse.jar...
    charsets.jar...
    tools.jar...
    localedata.jar...
    jfxrt.jar...
    plugin.jar...
    javaws.jar...
    deploy.jar...



update-alternatives 설정으로 충돌 문제 해결


* JDK 패키지 내에 포함 된 실행파일 들(java, javadoc, jar, javap, javac, javaws, javah, jarsigner)에 대해 동일한 우선순위(1551) 를 지정하여 update-alternatives 를 수행한다.

# update-alternatives --install /usr/bin/java java /usr/java/jdk1.8.0_45/bin/java 1551

# update-alternatives --install /usr/bin/javadoc javadoc /usr/java/jdk1.8.0_45/bin/javadoc 1551

# update-alternatives --install /usr/bin/jar jar /usr/java/jdk1.8.0_45/bin/jar 1551

# update-alternatives --install /usr/bin/javap javap /usr/java/jdk1.8.0_45/bin/javap 1551

# update-alternatives --install /usr/bin/javac javac /usr/java/jdk1.8.0_45/bin/javac 1551

# update-alternatives --install /usr/bin/javaws javaws /usr/java/jdk1.8.0_45/bin/javaws 1551

# update-alternatives --install /usr/bin/javah javah /usr/java/jdk1.8.0_45/bin/javah 1551

# update-alternatives --install /usr/bin/jarsigner jarsigner /usr/java/jdk1.8.0_45/bin/jarsigner 1551


* display option으로 java 실행파일에 현재 설정된 alternatives 가 확인 가능하며, config option으로 디폴트 버전을 스위칭할 수도 있다.

# update-alternatives --display java

java - auto mode
  link currently points to /usr/java/jdk1.8.0_45/bin/java
/usr/java/jdk1.8.0_45/bin/java - priority 1551
/usr/lib64/jvm/jre-1.5.0-gcj/bin/java - priority 1500
  slave jre: /usr/lib64/jvm/jre-1.5.0-gcj
  slave jre_exports: /usr/lib64/jvm-exports/jre-1.5.0-gcj
  slave rmiregistry: /usr/lib64/jvm/jre-1.5.0-gcj/bin/rmiregistry
Current 'best' version is '/usr/java/jdk1.8.0_45/bin/java'.


# update-alternatives --config java

There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                   Priority   Status
------------------------------------------------------------
* 0            /usr/java/jdk1.8.0_45/bin/java          1551      auto mode
  1            /usr/java/jdk1.8.0_45/bin/java          1551      manual mode
  2            /usr/lib64/jvm/jre-1.5.0-gcj/bin/java   1500      manual mode


* root 아닌 일반 유저(python 개발 계정)으로 전환하여, java 환경 변수를 설정해 둔다

$ cd

$ vi .bashrc -> 아래 라인을 스크립트 상단에 추가

export JAVA_HOME=/usr/java/jdk1.8.0_45

$ source .bashrc

$ java -version

java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)


Pycharm 4.5.1 community edition 설치 및 설정


* pycharm 4.5.1 패키지 파일을 다운로드(☞가기)하고 설치한다. 작업의 편의를 위해, 반드시 일반 유저(개발자 계정)으로 진행함에 유의하자.

$ tar xvzf pycharm-community-4.5.1.tar.gz -C /tmp/

$ sudo su -c "chown -R root:root /tmp/pycharm*"

sudo rm -rf /opt/pycharm-community/  ==> 업그레이드일 경우

$ sudo rm /usr/local/bin/pycharm /usr/local/bin/inspect ==> 업그레이드일 경우

$ sudo mv /tmp/pycharm-community* /opt/pycharm-community

$ sudo su -c "ln -s /opt/pycharm-community/bin/pycharm.sh /usr/local/bin/pycharm"

$ sudo su -c "ln -s /opt/pycharm-community/bin/inspect.sh /usr/local/bin/inspect"


$ pycharm &




- Barracuda -



저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들

점검 대상 머신에 SNMP를 설정(SNMP agent; server side)하고 python 프로그램(SNMP manager; client side)로 모니터링 데이터를 수집하기 위한 셋업 절차.


OpenSuse 11.3 repository 에는 pysnmp가 포함되어 있지 않으며, pymongo 는 OpenSuse 12.* 에서도 공식 지원하지

않으므로 수작업으로 설치해야 한다(python 2.6.5 기준)


* pysnmp : python 환경에서 NET-SNMP Agent/Manager 개발시에 필요한 API

* pymongo : python 환경에서 mongodb 관련 application 개발시에 필요한 API/driver


[Client - pysnmp 설치]

# zypper in -y python-devel

# zypper in -y python-setuptools

# zypper in -y git

# curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py

# python get-pip.py

# pip install pysnmp-mibs

# wget http://downloads.sourceforge.net/project/pysnmp/pysnmp/4.2.3/pysnmp-4.2.3.tar.gz

# tar xvzf pysnmp-4.2.3.tar.gz

# cd pysnmp-4.2.3

# python setup.py install


[Client - pymongo 설치]

# zypper in -y git

# git clone git://github.com/mongodb/mongo-python-driver.git pymongo

# cd pymongo/

# python setup.py install


[Client - python-mysql 설치]

# zypper in -y python-mysql


[Server - SNMP agent 설치]

# zypper in -y net-snmp

# chkconfig --add snmpd

# vi /etc/snmp/snmpd.conf

# service snmpd start


[snmpd.conf 내용 - snmp v 1, 2 & linux 기준]

###############################################################################

# Access Control

###############################################################################

# com2sec sec.name source community

com2sec mysec     1.1.1.1  mycommunity # Client gateway ip 또는 cidr

com2sec mysec     10.10.1.0/24  mycommunity # Client gateway ip 또는 cidr

com2sec mysec     localhost mycommunity


# group groupname sec.model  sec.name

group mygroup        v1         mysec

group mygroup        v2c       mysec


# view attr incl/excl subtree mask

view all    included  .1                               80


# access groupname sec.model sec.level match  read   write  notif

access mygroup ""      any       noauth    exact  all    none   none

access mygroup ""      any       noauth    exact  all    all    none


rwuser initial priv

createUser initial MD5 auth_pass DES priv_pass


# system info.


# to view with snmpwalk, snmpwalk -v 2c -c mycommunity localhost system

syslocation     My Office

syscontact      administrator (xxx@yyy)

# sysname      aaa # Not mandatory


# cpu load

# to view with snmpwalk, snmpwalk -v 2c -c mycommunity localhost .1.3.6.1.4.1.2021.10

#load 12 14 14 # uncomment if necessary


# disk avvailable space

# to view with snmpwalk, snmpwalk -v 2c -c mycommunity localhost .1.3.6.1.4.1.2021.9

#disk / 100000 # uncomment if necessary


# process check

# to view with snmpwalk, snmpwalk -v 2c -c mycommunity localhost .1.3.6.1.4.1.2021.2

# Daemons or other application process names to check

proc tomcat

proc lighttpd

proc mydaemon_process


# shell exec.

# to view with snmpwalk, snmpwalk -v 2c -c bryan localhost .1.3.6.1.4.1.2021.8

exec myscript1 /root/bin/script1.sh param

exec myscript2 /root/biin/script2.sh param1 param2


[snmp 서비스 설정 - Windows 2008 기준]

1. 서버 관리자 시작

시작 > 제어판 > (모든 제어판 항목) > 프로그램 및 기능 > 윈도우즈 기능 사용/사용 안함 => 서버관리자 실핼

또는

시작 > 관리도구 > 서버관리자 실행


2. 서버관리자 설정

기능 > 기능 추가

기능 성택창에서 SNMP 서비스 / SNMP WMI 공급자 Check, 설치


3. 방화벽 설정

시작 > 관리도구 > 고급보안이 포함된 Windows 방화벽

인바운드 규칙

SNMP 서비스 > 속성 >

- 일반: 사용함, 연결 허용

- 고급: 에지 통과 허용


4. 서비스 설정

시작 > 관리도구 > 서비스

SNMP Service > 속성

- 에이전트: 적절히 설정

- 보안: 받아들인 커뮤니티 이름 => mycommunity 추가, 확인

SNMP Service > 다시 시작








저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들

Opensuse 12.1 환경에서 PHP5 + lighttpd fastcgi + mongodb + mysql 개발/운영 환경 설정

* howtoforge.com 내용 참조, 테스트 실행 & 보완


1. Lighttpd & php5 환경 설정


# zypper in -y lighttpd

# systemctl enable lighttpd.service

# systemctl start lighttpd.service


* 기존의 php5 fastcgi 가 php5-fpm 패키지로 통합 업그레이드 되었고 별도 daemon으로 동작한다.

# zypper in -y php5-fpm


# mv /etc/php5/fpm/php-fpm.conf.default /etc/php5/fpm/php-fpm.conf

# chmod 1733 /var/lib/php5


* error_log 부분을 un-comment 하고 아래와 같이 수정한다

# vi /etc/php5/fpm/php-fpm.conf

error_log = /var/log/php-fpm.log


* 아래 라인들을 찾아서 확인하고 몇 가지 mandatory 설정을 un-comment 한다(pm=dynamic 일 경우)

* howtoforge.com 에서는 해당 설정 부분이 누락되어 있다

pm = dynamic

pm.max_children = 50

pm.min_spare_servers = 5

pm.max_spare_servers = 35


* fpm용으로 별도의 php.ini 파일이 없으므로 cli용을 복사한다.

# cp /etc/php5/cli/php.ini /etc/php5/fpm/


* 아래 해당 라인을 찾아서 un-comment 한다.

# vi /etc/php5/fpm/php.ini

cgi.fix_pathinfo=1


# systemctl enable php-fpm.service

# systemctl start php-fpm.service


# vi /etc/lighttpd/modules.conf -> 아래 라인을 찾아서 un-comment

include "conf.d/fastcgi.conf"


server.modules = (

...

  "mod_auth",

...

)


* 아래 라인을 추가

# vi /etc/lighttpd/lighttpd.conf

include "conf.d/auth.conf"


* 아래 라인들을 un-comment 하고 내용을 수정

# vi /etc/lighttpd/conf.d/auth.conf

auth.debug = 2

auth.backend = "plain"

auth.backend.plain.userfile  = "/etc/lighttpd/lighttpd.user"


auth.require = ( "/" =>

                      (

                        "method"  => "basic",

                        "realm"   => "Password protected area",

                        "require" => "user=loginid"

                      ),

                    )


* 아래 로그인 정보를 등록하고 저장

# vi /etc/lighttpd/lighttpd.user

loginid:loginpassword


# vi /etc/lighttpd/conf.d/fastcgi.conf -> 아래 라인이 있는지 확인

server.modules += ( "mod_fastcgi" )


* 아래 내용을 추가(기존의 fastcgi.server 부분의 comment 는 그대로 둔다)

fastcgi.server += ( ".php" =>

        ((

                "host" => "127.0.0.1",

                "port" => "9000",

                "broken-scriptfilename" => "enable"

        ))

)


# systemctl restart lighttpd.service


* lighttpd의  default 문서 홈디렉토리는 /srv/www/htdocs 이다


# vi /srv/www/htdocs/info.php

<?php

phpinfo();

?>


* 브라우저로 접속 확인




2. MySQL과 utility 패키지 설치


* Web 을 통하지 않고 직접 DB접속을 위해 접속 클라이언트를 사용할 경우에는 아래와 같이 클라이언트를 설치한다.

# zypper in -y mysql-community-server-client


* MySQL 및 일반적으로 많이 사용하는 드라이버 설치

# zypper in -y php5-mysql php5-bcmath php5-bz2 php5-calendar php5-ctype php5-curl php5-dom php5-ftp php5-gd php5-gettext php5-gmp php5-iconv php5-imap php5-ldap php5-mbstring php5-mcrypt php5-odbc php5-openssl php5-pcntl php5-pgsql php5-posix php5-shmop php5-snmp php5-soap php5-sockets php5-sqlite php5-sysvsem php5-tokenizer php5-wddx php5-xmlrpc php5-xsl php5-zlib php5-exif php5-pear php5-sysvmsg php5-sysvshm


3. mongodb 드라이버 설치


* 11.x 환경과는 다르게 12.1 부터는 pecl 을 통해 php용 mongo 드라이버를 쉽게 설치할 수 있다


# pecl install mongo


# vi /etc/php5/fastcgi/php.ini -> 아래 라인을 추가

extension=mongo.so


# systemctl restart lighttpd.service

# systemctl restart php-fpm.service


* 1의 과정과 같이 브라우저를 통해 php 환경에 맞게 설치된 드라이버들을 최종 확인한다.






저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들

Opensuse 11.3, 12.1 각각에 대해 2회에 걸쳐서 정리

* howtoforge.com 내용 참조, 테스트 실행 & 보완


1. Lighttpd & php5 환경 설정


# zypper in -y lighttpd

# chkconfig --add lighttpd

# chkconfig lighttpd on

# service lighttpd start


# zypper in -y php5-fastcgi


* 아래 라인을 찾아서 un-comment

# vi /etc/php5/fastcgi/php.ini 

cgi.fix_pathinfo=1


* 아래 라인을 찾아서 un-comment

# vi /etc/lighttpd/modules.conf

include "conf.d/fastcgi.conf"


server.modules = (

...

  "mod_auth",

...

)


* 아래 라인을 추가

# vi /etc/lighttpd/lighttpd.conf

include "conf.d/auth.conf"


* 아래 라인들을 un-comment 하고 내용을 수정

# vi /etc/lighttpd/conf.d/auth.conf

auth.debug = 2

auth.backend = "plain"

auth.backend.plain.userfile  = "/etc/lighttpd/lighttpd.user"


auth.require = ( "/" =>

                      (

                        "method"  => "basic",

                        "realm"   => "Password protected area",

                        "require" => "user=loginid"

                      ),

                    )


* 아래 로그인 정보를 등록하고 저장

# vi /etc/lighttpd/lighttpd.user

loginid:loginpassword


* 아래 라인이 있는지 확인

# vi /etc/lighttpd/conf.d/fastcgi.conf

server.modules += ( "mod_fastcgi" )


* 아래 부분을 찾아서 un-comment

fastcgi.server = ( ".php" =>

                   ( "php-local" =>

                     (

                       "socket" => socket_dir + "/php-fastcgi-1.socket",

                       "bin-path" => server_root + "/cgi-bin/php5",

                       "max-procs" => 1,

                       "broken-scriptfilename" => "enable",

                     )

                   ),

                   ( "php-tcp" =>

                     (

                       "host" => "127.0.0.1",

                       "port" => 9999,

                       "check-local" => "disable",

                       "broken-scriptfilename" => "enable",

                     )

                   ),


                   ( "php-num-procs" =>

                     (

                       "socket" => socket_dir + "/php-fastcgi-2.socket",

                       "bin-path" => server_root + "/cgi-bin/php5",

                       "bin-environment" => (

                         "PHP_FCGI_CHILDREN" => "8",

                         "PHP_FCGI_MAX_REQUESTS" => "10000",

                       ),

                       "max-procs" => 2,

                       "broken-scriptfilename" => "enable",

                     )

                   ),

                )

* 위의 옵션은 소규모 접속일 경우이고, 일반적인 다중 접속 사이트일 경우 아래와 같이 규모에 맞게 적당히 옵션을 조정한다.

"PHP_FCGI_CHILDREN" => "16"

"max-procs" => 5


# service lighttpd restart


* lighttpd의  default 문서 홈디렉토리는 /srv/www/htdocs 이다


# vi /srv/www/htdocs/info.php

<?php

phpinfo();

?>


* 브라우저로 접속 확인




2. MySQL과 utility 패키지 설치


* Web 을 통하지 않고 직접 DB접속을 위해 접속 클라이언트를 사용할 경우에는 아래와 같이 클라이언트를 설치한다.

# zypper in -y mysql-community-server-client


* MySQL 및 일반적으로 많이 사용하는 드라이버 설치

# yast2 -i php5-mysql php5-bcmath php5-bz2 php5-calendar php5-ctype php5-curl php5-dom php5-ftp php5-gd php5-gettext php5-gmp php5-iconv php5-imap php5-ldap php5-mbstring php5-mcrypt php5-odbc php5-openssl php5-pcntl php5-pgsql php5-posix php5-shmop php5-snmp php5-soap php5-sockets php5-sqlite php5-sysvsem php5-tokenizer php5-wddx php5-xmlrpc php5-xsl php5-zlib php5-exif php5-pear php5-sysvmsg php5-sysvshm


3. mongodb 드라이버 설치


* pecl.php.net 사이트를 이용하여 최신 버전의 php mongo 드라이버를 찾아서 다운로드 한다.


# zypper in -y php5-devel

# wget http://pecl.php.net/get/mongo-1.3.0beta2.tgz

# tar xvzf mongo-1.3.0beta2.tgz; cd mongo-1.3.0beta2

# phpize

# ./configure

# make

# make install

# ldconfig

# vi /etc/php5/fastcgi/php.ini -> 아래 라인을 추가

extension=mongo.so


# service lighttpd restart


* 1의 과정과 같이 브라우저를 통해 php 환경에 맞게 설치된 드라이버들을 최종 확인한다.





저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


데이터 마이그레이션 등 작업시 날짜 데이터 부분, 즉 '2011-08-23 11:24:56' 으로 된 부분만 찾아서 now 와 같은 것으로 바꿀 필요가 가끔 있을 때 요긴하다.


'[12][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) [0-2][0-9]:[0-5][0-9]:[0-5][0-9]'


년도: 1 또는 2로 시작하여 0 ~ 9 사이의 값으로 3자리가 채워지며,

월: (0으로 시작하는 경우 뒤에 1~9 사이의 값) 또는 (1로 시작하는 경우 뒤에 0~2사이의 값),

일: (0으로 시작하는 경우 뒤에 1~9 사이의 값) 또는 (1또는 2로 시작하는 경우 뒤에 0~9사이의 값) 또는 (3으로 시작하는 경우 뒤에 0 또는 1)

...

저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


왠지 모르게 cvs의 설정과 복구과정에 대해 헛갈려 하는 cvs 사용자들을 위해 작성

cvs 서버의 장애로 
1. 새로운 머신에 cvs server를 설치하고
2. repository를 초기화 한 후
3. Local PC의 최종버전의 파일들을 밀어 넣는
방법으로 복구하는 과정이다

1. cvs server 설치(root)

# apt-get install cvs cvsd xinetd openssl
ubuntu가 아닌 Suse, Centos(or Fedora)는 zypper, yum 등을 통해서 설치

# groupadd cvsgrp
# useradd -M -g cvsgrp -s /bin/false cvs

2. Repository 초기화

# mkdir /home/cvs

# cvs -d /home/cvs init => /home/cvs/CVSROOT 생성

# chmod 744 /home/cvs/CVSROOT/config
# vi /home/cvs/CVSROOT/config
=> #SystemAuth=no 라인의 #을 제거

# ls -l /etc/xinetd.d/cvspserver => 파일 확인
# netstat -an | grep 2401 => port open 확인
# cat /etc/services | grep 2401 => 서비스 확인

# openssl passwd password
9as8dfkj.w2 => password를 암호화한 문자열(copy)

# vi /home/cvs/CVSROOT/passwd
loginid:9as8dfkj.w2:cvs

# chown -R cvs.cvsgrp /home/cvs
# service xinetd restart
# cvs -d :pserver:loginid@ip_address:/home/cvs login => 로그인 가능 확인

3. Check out 받아서 사용하던 Local file들로 CVS 복구

복구해서 밀어 넣을 파일들이 있는 폴더가 c:\cvs_dir\Source1 이라고 하면
# mkdir /home/cvs/Source1

Local PC에 cvs client(tortoisecvs 등)가 깔려 있다고 가정하고
탐색기에서 새로 체크아웃 받아서 앞으로 사용할 새로운 cvs 홈디렉토리를 생성(C:\cvs_dir_new 라고 가정)

탐색기에서 cvs_dir_new에 대해서 Source1 폴더 생성
Source1 폴더 마우스 우클릭,  CVS -> Make New module 클릭
모듈명(Source1)을 확인, OK를 선택하면 해당 폴더 아래에 CVS디렉토리가 자동으로 생성

복사해 넣을 파일들이 저장된 Source1 내의 모든 파일을 새로운 폴더(C:\cvs_dir_new\Source1) 로 복사

복사해 넣은 디렉토리와 파일들을 add & commit 해 서 밀어 넣는 작업을 각각 반복





저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


개발도구는 Sun Studio 12.1 로 해 보자(NetBeans 6.8도 가능)
개발을 위한 컴파일러는 gcc 4.1* 이상이어야 한다.

우선 mongoDB 다운로드 & 설치
- www.mongodb.org 에서 mongodb binary 또는 소스를 받아서 설치한다
- 소스 설치시는 의존성에 걸리는 빌드도구들을 여러가지 받아서 설치해야한다. 특히 scons 가 필수인데
   Ubuntu, centOS에서는 숫한 삽질을 통해 scons 소스를 컴파일해서 빌드한 기억이 난다.
   우여곡절 끝에 mongodb를 다음과 같이 빌드할 수 있다
   # scons --prefix /usr/service/mongo install
- mongodb 는 rpm 같은 패키징이 없이 tar.gz binary 압축해제/복사 또는 source 컴파일후 위에서 처럼 빌드, 설치
  2가지중 하나를 선택해야 한다.

mongodb 를 사용하는 프로그램을 컴파일 하기 위해서 필수적인 라이브러리가 바로 c++ boost library이다.
CentOS에서는 yum install boost, yum install boost-devel 로 설치할 수는 있지만
아쉽게도 boost 버전이 1.33이라 mongo 어플리케이션을 컴파일할 수가 없다. 그렇다면? boost 소스를 다운받아서
직접 빌드하고 설치해야 한다(이 때도 숫한 삽질이 수반된다). 결론은 Cent에서 mongodb app 개발은 상당한
인내심을 길러주는 극기 훈련이 될테니 마음 단단히 먹기 바란다.
특히 CentOS는 패키지 다운로드를 통해서 받아오는 MySQL이나 boost 등의 어플리케이션들이 상당히
구닥다리들이 많다는 점에 주의 !!

Ubuntu 에서는 그나마 상대적으로 쉬운 편인데 반드시 boost 1.38.1 이상을 설치해줘야 한다.

OpenSUSE 에서는 상당히 간편하게 mongodb 개발이 가능하다. YaST에서 최신버전의 boost 를
받아서 설치해주면 된다. gcc(g++4.4)와 boost 1.40이 설치될 것이다. 잘 모르겠으면 속편하게 소프트웨어
설치에서 왠만한 개발 도구와라이브러리들을 슥슥 선택해서 좌악 깔아주면 된다.

사이트의 샘플 소스를 Copy&Paste 해서 테스트 프로그램을 편집하였다고 하자.
#include <iostream>
#include "client/dbclient.h"

using namespace mongo;

void run() {
  DBClientConnection c;
  c.connect("localhost");
}

int main() {
  try {
    run();
    cout << "connected ok" << endl;
  } catch( DBException &e ) {
    cout << "caught " << e.what() << endl;
  }
  return 0;
}

커맨드라인에서라면 애래와 같이 컴파일 & 빌드를 수행하면 된다.
# g++ mongotest.cc -lmongoclient -lboost_thread-mt -lboost_filesystem -lboost_system-mt -o mongotest
여기서 libmongoclient.a, libboost_thread-mt.so libboost_filesystem.so libboost_system-mt.so 등이 적절히
설치되고 -L 옵션등으로 지정해주거나 LD_LIBRARY_PATH에 추가 하는 등의 작업이 필요함은 물론이다
(플랫폼에 따라 libboost_filesystem-mt.so로 해야 할 수도 있다).

SunStudio 내에서 컴파일&빌드 하려 한다면
Sunstudio 메뉴에서 Tools>Option>C/C++ 설정으로 가서 GNU를 default 로지정하고 저장한다.
Project Properties 에서 컴파일러 옵션에서 Additional Include 디렉토리를 mongodb include 디렉토리로 지정
링크 옵션에서 Add Library로 libmongoclient.a와 boost shared library 3개를 모두 추가하여 야 한다.


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들

Tag boost, C++, mongodb

Linux 패키지 종류가 다양하고 Compiler version과 라이브러리 버전이 워낙 다양하므로
MySQL connector c++ 1.0.5 버전 rpm을 설치하면
컴파일시 libstdc++.so.5 버전이 필요한데 libstdc++.so.6 버전이라서 경고가 뜨는 경우가 있다.
대개는 오류가 주루룩 뜨지만, 가끔 경고만 뜨고 컴파일은 되나 실행하면 거의 core가 생긴다.

이 때는 어쩔수 없이 직접 source 로부터 connector c++을 빌드해서 써야한다(아니면 statifier 같은걸 쓸 수도 있지만 편법이라 별로다)

우선 cmake 2.6.2 버전 이상이 필요하다.
# wget http://www.cmake.org/files/v2.8/cmake-2.8.0.tar.gz
# tar -xvzf 로 압축 풀고
# ./configure
# gmake
# sudo gmake install
cmake 빌드, 설치 완료

MySQL-devel-5.1.43-1.glibc23.x86_64.rpm 다운로드, 설치(cmake 할 때 mysql_config utility를 찾기 때문에
받아서 설치해 둔다)
mysql-connector-c++-1.0.5 받아서 압축 풀고 change directory

# cmake .
# cmake -L (여기서 오류 뜨면 cmake -i 로 해서 wizard mode로 진행할수도 있다)
# make
# make install





저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


NetBeans 6.8 에서 C++을 통하여 MySQL을 다루기 위해 Connector C++을 사용하기로 하였다.
처음부터 쉬운 길을 택한 것에 대한 징벌인가 ㅡ_-;; 샘플 소스 받고 빌드 하니
ld 에서 오류가 떨어진다...엉뚱한 버전의 라이브러리를 찾고 난리도 아니다.

Binary 버전(Redhat, SUSE, Max OSX, FreeBSD, Windows 용 다 있는데 Karmic 용은 없네...generic 을 받을 수 밖에...)을
다운로드 받고 tar로 압축 풀고 /usr/include, /usr/lib 에 적당히 복사하고
Library link 정보까지 깔끔하게 업데이트 하기 위해 ldconfig 까지 돌렸는데;;; 안된다.

구글링을 해 보니, MySQL Connector C++ binary는 glibc-2.3 을 통해 컴파일된 거라서 Generic 으로 Binary를 복사 설치할 경우
링크단계에서 잘 넘어가면 기적이지 싶다. 2.3 버전이면 몇년 전거냐...지금 glibc-2.9 가 돌아가는 시대 아닌가.
거기다가 c++은 namespace 를 민감하게 따져서 이런 일이 많이 생기지 싶다.

해결을 위해 두번째 삽질에 들어간다. MySQL Connector/C++ 소스를 자체적으로 컴파일해서 설치에 성공하면 끝이다.
소스 받고, 압축 풀고 설치하려니...cmake로 돌려야 된다 ㅡ_-;; 유명한 Platform independant Cross maker !!!
Synaptic Package manager로 cmake 설치하고
대망의...cmake

> sudo cmake .
> sudo make clean (옳지! 잘 된다)
> sudo make (Shieeeeet 오류 뜬다)

.../mysql_art_resultset.cpp: snprintf was not declared in this scope 어쩌구...

namespace 관련 버그 같은데 몇년 동안 아직 그대로인가...제공된 소스를 직접 뜯어 고쳐야 한다.

.../mysql_art_resultset.cpp
.../mysql_resultbind.cpp
.../test/unit/classes/resultsetmetadata.cpp

위의 3개 파일을 모두 에디터로 열어서 첫 번째 라인에 아래의 include 문을 추가한다.

#include <cstdio>

이제 마무리

> sudo make
> sudo make install

이제 Netbeans에서 빌드/실행하면 훌륭하게 동작 한다.
저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


CD1장으로 된 Ubuntu desktop 버전은 Linux를 간편하게 사용하게 만들어진 패키지여서
개발툴, 환경, 라이브러리들이 자동으로 설치/설정되지는 않는다.

더구나 Eclipse(Galileo 라는 코드명을 가졌던가...아무튼) cdt는 ubuntu의 synaptic 관리자에서도 더 이상 패키지
설치목록에 뜨지 않게 바뀌어 버렸다.

Eclipse cdt(C, C++ Development Toolkit ? ...)을 사용하려면
우선 eclipse 최신 버전을 synaptic 관리자를 통해서 설치한다
(추가로 설치가 필요하다고 마킹된 것들 중에서 pde는 반드시 설치, jde는 java 개발을 하지 않는다면 필요 없을 듯)

다음 eclipse를 실행한 후에 아래의 절차에 따라 설치한다.

- Help 메뉴에서 "Install New Software" 선택
- Install 팝업창에서 Add 버튼 클릭
- Name에 Galileo 하고 넣고 URL에 http://download.eclipse.org/tools/cdt/releases/galileo 를 붙여넣고 OK


- 다시 Install 창에서 CDT Main, CDT Optional Features 를 선택(왼만하면 설치해 놓고 나중에 지우던지 아니면 Main만 설치)하고 Next
- 필요한 패키지가 없다고 오류가 뜰텐데, Remote Launch 부터 아래에 있는 것들 모두 제외하자
- 다시 Next 하면 설치 가능 목록이 뜨고, Next
- Accept ...License agreement 에 체크하고 Finish

* 혹시 설치 도중 아래와 같은 오류가 발생한다면 eclipse pde가 설치되지 않은 것이니 반드시 설치하고 다시 돌아와서 계속하자
  session context was:(profile=PlatformProfile, phase=org.eclipse.equinox.internal.provisional.p2.engine.phases.Install, operand=null --> [R]org.eclipse.ant.ui 3.4.1.v20090901_r351, action=org.eclipse.equinox.internal.p2.touchpoint.eclipse.actions.InstallBundleAction).
  The artifact file for osgi.bundle,org.eclipse.ant.ui,3.4.1.v20090901_r351 was not found.

설치 진행 창이 팝업되고 설치가 자동으로 시작된다(다운로드/설치에 몇 분 시간이 걸린다). 설치가 완료되면

Ubuntu desktop의 상단 메뉴 프로그램>개발>eclipse 를 클릭하면
Eclipse SDK 워크벤치를 실행시키고 프로그램 개발에 들어갈 수 있다.

저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


무지하게 심플하다
(물론, 그 전에 설치, 사용을 위한 환경을 체크해야 하지만...)

더 심플한 방법은 synaptic 패키지관리자로 설치하는 방법이지만 대략 흐름을 파악하는데는
수동으로 설치해 보는 것도 도움이 될 것이다.

패키지 다운로드 받고, 압축풀고, 복사해 넣는 과정이다.

우선 java 버전을 확인한다. tomcat을 원활히 동작시키려면 sun의 jdk가 필요하다.

> java -version
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02, mixed mode)

위와 같은 결과가 나오면 더 준비할 게 없지만
만약 openjdk 같은 것이 깔려 있다면 sun jdk를 받아서 설치해야 한다

>sudo vi /etc/apt/sources.list

위의 파일을 열어서 아래의 Multiverse repository 부분을 추가한다.
----------------------------------------------------------------------
deb http://us.archive.ubuntu.com/ubuntu/ hardy multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ hardy multiverse
deb http://us.archive.ubuntu.com/ubuntu/ hardy-updates multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ hardy-updates multiverse
----------------------------------------------------------------------

> sudo apt-get update
위와 같이 repository를 업데이트 시킨 후

> sudo apt-get install sun-java6-jdk
와 같이 다운로드/설치를 수행한다.

도중에 whiptail 다이얼로그가 뜨는데, -> 방향키를 눌러 OK(또는 확인)에 포커스가 가게 한 후 엔터를 치면 설치를 시작한다

설치가 끝난 후 java -version을 실행하면 위의 설치된 자바 버전을 확인할 수 있다

이제 java가 실행 가능하도록 아래와 같이 환경변수를 등록한다.
계정 홈 디렉토리에서

> vi .bashrc

export JAVA_HOME=/usr/lib/jvm/java-6-sun-1.6.0.15

또는 /usr/lib/jvm/java-6-sun으로 symbolic link가 자동으로 걸리는 경우가 있는데, 이때는
export JAVA_HOME=/usr/lib/jvm/java-6-sun
이라고 해도 무방하겠다

위의 라인을 추가하고 저장한다(다운로드 설치된 자바 버전을 알맞게 맞춘다, 1.6.0.17 버전이 설치될 수도 있겠다)

이제 Tomcat 을 다운로드하고 설치할 준비가 끝났다(현재 최신 버전은 6.0.20 버전인 듯 하다)

> wget http://mirror.lividpenguin.com/pub/apache/tomcat/tomcat-6/v6.0.20/bin/apache-tomcat-6.0.20.tar.gz

다운로드가 완료 된 후

> tar xvzf apache-tomcat-6.0.20.tar.gz
와 같이 압축을 풀고

> sudo mv ./apache-tomcat-6.0.20 /opt/tomcat6

간단하게 설치가 끝났다.

이제 아래와 같이 기동한다
> /opt/tomcat6/bin/startup.sh
(만약 synaptic 관리자로 설치 했다면 > /etc/rc0.d/K08tomcat6 start)


Tomcat 서비스를 다운시키려면 반대로
> /opt/tomcat6/bin/shutdown.sh
(만약 synaptic 관리자로 설치 했다면 > /etc/rc0.d/K08tomcat6 stop)


* 참고로 Tomcat은 자체적으로 웹서버를 포함하고 있으므로 설치 후 Firefox 같은 브라우저로 http://localhost:8080 을 띄워보고 확인할 수 있다.
이 때, 디폴트로 잡혀지는 Document Root는 /opt/tomcat6/webapps/ROOT 이므로 여기에 jsp 파일 등을 복사해 넣고 테스트 해 볼수 있다.


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


Drizzle은 설치되고 drizzled가 떠 있다고 가정한다

1. 우선 java부터 쓸 수 있도록 환경 구성

> java -version
으로 확인

깔려 있지 않다면 받아서 설치부터 go go(sun java 나 open jdk를 알아서...) 

sun 버전을 설치한다면
> sudo apt-get install sun-java6-jdk

다른 자바 버전들이 여러개 깔려 있는 경우는
> sudo update-alternatives --config java
There are 2 choices for the alternative java(providing /usr/bin/java).

  Selection    Path                                      Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      auto mode
  1            /usr/lib/jvm/java-6-openjdk/jre/bin/java   1061      manual mode
  2            /usr/lib/jvm/java-6-sun/jre/bin/java       63        manual mode

Press enter to keep the default[*], or type selection number:

에서 원하는 패키지를 지정해주면 된다

2. jdbc for drizzle 을 설치하자

http://drizzle.org/wiki/JDBC/BuildingAndTesting 에서 다운로드 받고 컴파일 해야되는데 maven도 필요하고 여러가지로 귀찮다.
binary를 바로 받아서 쓰려면
http://repo2.maven.org/maven2/org/drizzle/jdbc/drizzle-jdbc/0.5/ 여기서 다운로드 받고
jar 들이 설치된 위치에 복사하자

> cp drizzle-jdbc-0.5.jar /usr/share/java

java가 실행될 때 이 jdbc가 제대로 참조되게 하려면
.profile 이나 .bashrc 에 CLASSPATH를 설정해야 한다

# Drizzle-jdbc Config
export CLASSPATH="$CLASSPATH:/usr/share/java/drizzle-jdbc-0.5.jar"

다 되었으면 다음으로...

3. Sample java code

지정된 NewtestDB라는 schema에 test용으로 a 라는 table을 생성하는 java code이다

> vi example_drizzle.java
----------------------------------------------------------------------------------------
import java.sql.*;

public class example_drizzle {

  public static void main(String args[]) {

    try {
      Class.forName("org.drizzle.jdbc.Driver");
    } catch (Exception e) {
      System.out.println(e.getMessage());
      System.exit(1);
    }

    try {
      Connection con = DriverManager.getConnection("jdbc:drizzle://localhost:4427/NewtestDB");
      Statement st = con.createStatement();
      st.executeUpdate("CREATE TABLE a (id int not null primary key, value varchar(20))");
      st.close();
      con.close();
    } catch (SQLException e) {
      System.out.println(e.getMessage());
    }
  }
}
----------------------------------------------------------------------------------------

이제 컴파일 해 본다

> javac example_drizzle.java

오류가 없으면 다음과 같이 실행해 본다

> java example_drizzle

Sep 17, 2009 6:54:22 PM org.drizzle.jdbc.internal.drizzle.DrizzleProtocol
정보: Connected to: localhost:4427
Sep 17, 2009 6:54:22 PM org.drizzle.jdbc.internal.drizzle.DrizzleProtocol close
정보: Closing connection
Sep 17, 2009 6:54:22 PM org.drizzle.jdbc.internal.common.packet.AsyncPacketFetcher run
정보: Connection closed

와 같이 출력되고 a 라는 테이블이 생성될 것이다.


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


ubuntu에서 drizzle 설치가 의외로 까다로와서, 따로 정리한다.

우선 컴파일에 필요한 아래의 패키지를 다운로드/설치한다
> sudo apt-get install libpcre3-dev libevent-dev autoconf automake bison libtool ncurses-dev libreadline-dev libz-dev g++ libssl-dev uuid-dev libpam0g libpam0g-dev gperf

libevent 가 too old 하다고 나올 경우 새 버전을 받아야 한다
> sudo apt-get install libevent-dev

libdrizzle, Google protocol buffer가 필요하다, 다운로드/설치한다.
>sudo apt-get install libdrizzle-dev libprotobuf-dev protobuf-compiler

아차, Ubuntu 9.10 Karmic은 protobuf 2.0버전이라 아래 configure 단계에서 돌다가 버전이 낮다고 짜증낼 것이다
(아래 * 에서 다시 올라 왔다면...수고~ 아마도
configure: error: Your version of Google Protocol Buffers is too old. drizzle requires at least version 2.1.0 이런 오류가 떴을 듯)

이 때는
> sudo apt-get install libprotobuf4 libprotobuf-dev
로 패키지를 대채해야 한다(귀찮네 ㅡ_-;)
그래도 안되면
unsupported(권장하지는 않지만 아쉬운 사람들을 위한...)이지만 강제로 PPA(Personal Package Archives) 패키지를
등록하여 update 시켜야 한다

-----------------------------------------------------------------------------
PPA 등록/설치는 아래 내용으로 수정됨(2010-01-25)
-----------------------------------------------------------------------------
https://launchpad.net/~drizzle-developers/+archive/build-depends 에 방문
Technical details about PPA 클릭
1. 아래 박스 내의 첫번째 라인을 드래그하여 복사(주소 복사 아님)

Ubuntu의 System>Administration > Software Source 메뉴 클릭
Third Party Software(한글로 기타 소프트웨어) 탭 클릭
Add 버튼 클릭
팝업창 내에서 Apt line 입력 박스에서 위에서 복사한 내용을 Paste

2. 위의 1의 경우 처럼 두번쨰 라인을 드래그 복사, 역시 Software Source 창에서 Add 하여 Paste

다음으로는 인증키를 복사하여 시스템에 등록해야 한다

위의 1번에서 나오는 박스 아래쪽에 Signing Key 아래 부분에서
예:  1024R/06899068 (What is this?)
1024R/06899068
에서 숫자부분(06899068)을 복사하여 Terminal 에서 아래와 같이 실행
> sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 06899068

그 다음에
> sudo apt-get update
linux package를 update 시키면 Google Protocol Buffer가 설치 완료된다.

> sudo apt-get install libprotobuf4 libprotobuf-dev

*** 만약 configure 에서 또 protobuf4 too old 어쩌고 나온다면 마지막 수단이 있다
http://protobuf.googlecode.com/files/protobuf-2.3.0.tar.gz 에서
최신버전 소스를 다운로드 받고 tar xvzf 로 압축을 푼 다음, 압축을 해제한 소스 디렉토리로 가서
> ./configure && make
> sudo make install
> sudo ldconfig
> sudo updatedb
로 수동으로 설치하면 될 것이다.
-----------------------------------------------------------------------------

https://launchpad.net/drizzle 에서 최신 버전을 다운로드 받아서
 > tar xzf *.tar.gz
 > ./configure && make <--- (*)
 > sudo make install
하여 설치한다.

* 다음은 계정 생성과 기동이다.

> sudo groupadd drizzle
> sudo useradd -g drizzle drizzle_user
> sudo mkdir /home/drizzle_user
> sudo mkdir /home/drizzle_user/drizzle
> sudo mkdir /home/drizzle_user/drizzle/data
> sudo chown -R drizzle_user /home/drizzle_user/drizzle/data
> sudo -u drizzle_user /usr/local/sbin/drizzled --datadir=/home/drizzle_user/drizzle/data

이제 drizzled Daemon의 기동이 완료되었다.

> sudo -u drizzle_user /usr/local/bin/drizzle

Welcome to the Drizzle client..  Commands end with ; or \g.
Your Drizzle connection id is 2
Server version: 7 Source distribution (drizzle)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

drizzle>

위와 같이 수행되면 완성이다.

저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


APM은 Apache + PHP + MySQL이다(때로는 Linux까지 포함시켜 LAMP 라고 배포되는 패키지도 만날 수 있다)
apt-get은 자동으로 인터넷 배포 서버로 접속하여 다운로드 설치를 자동으로 진행해 주므로 편리하다.
Y/n으로 묻는 과정에서는 enter만 치면 default 로 다음 과정으로 진행된다.

terminal 또는 ssh 등에서

1. root로 로그인 한다
# sudo su

2. Apache 설치
# apt-get install apache2
(또는 4. MySQL 설치를 먼저 해도 무관하다)

3. Apache가 MySQL로 접속하기 위한 모듈 설치
# apt-get install libapache2-mod-auth-mysql

4. MySQL 설치
# apt-get install mysql-server mysql-client

5. PHP5 설치
# apt-get install php5-common php5 libapache2-mod-php5

6. PHP5 가 MySQL로 접속하기 위한 모듈 설치
# apt-get install php5-mysql

설치는 이것으로 끝이며
firefox, opera 등에서 주소줄에 http://localhost 또는 http://127.0.0.1 로 접속하면
"It Works" 라는 몇 줄 안되는 페이지가 뜨면 성공이다.

* 주의: 외부에서 MySQL DB로 접속할 필요가 있는 경우
           /etc/mysql/my.cnf 파일에서
           bind-adress = 127.0.0.1 라인을 '#' 로 코멘트 처리해야 한다.


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들

Tag apm