Database migration with alembic or phinx

어떤 언어로 개발하든 데이터베이스를 수정할 일이 생기는 경우가 발생한다. 개발자의 개발 머신과 테스트 서버 그리고 상용 서버에 이르기까지 모두 적용해 주어야 한다. 때로는 어떤 상황으로 되돌아가야 하는 경우도 생긴다. 이와 같은 DB 변경 사항 적용을 마이그레이션이라 부르며, CodeIgniter나 Laravel 그리고 Django에서는 마이그레이션 기능을 내장하고 있다. 프레임워크와 별도로 작동하는 패키지가 있는지 찾아보다가 두 가지를 발견하였다.

alembic은 python용 DB 마이그레이션 패키지이고, phinx는 php용 도구이다.

사용법은 프레임워크에서 제공하는 기능과 별 차이가 없다.

한 가지 눈에 띄는 게 있다면 phinx는 change() 메서드에 특정 메서드를 사용하여 마이그레이션 코드를 작성하면, 리버스 코드를 작성하지 않아도 rollback이 자동으로 된다고 하는 점이 특징으로 보인다.

아직 사용해 보지는 않았지만, 자동으로 create & drop이 된다면 편리할 것 같다. 사용해보고 후기를 이어서 작성해 볼까 한다.

Android build setting

Andriod 앱을 빌드하기 위해서는 당연히 android sdk가 있어야 한다.

예전엔 android sdk를 다운받을 수 있었는데, 지금은 sdk tools를 별도로 제공하고 있다. 사용하려면 명령줄에서 실행해야 한다.

필자는 jenkins를 docker로 띄워 놓은 상태였고 git repository로부터 webhook이 걸리면 gradlew명령어를 사용해서 apk를 자동으로 빌드하려는 상황이었다.

jenkins 컨테이너 내에 jenkins 사용자의 홈디렉토리는 기본으로 /var/jenkins_home에 세팅된 것을 기준으로 정리하였다.

이후에 tools/bin에 있는 sdkmanager를 실행하여 sdk들을 설치하게 되면 관련 sdk들이 tools 디렉토리의 상위 디렉토리에 위치하게 되며 해당 디렉토리를 anroid sdk 디렉토리로 사용하게 된다. 따라서 android sdk 디렉토리를 먼저 생성해 주어야 한다.

mkdir /var/jenkins_home/android-sdk

그리고 android sdk tools의 압축을 풀면 tools 디렉토리에 내용이 담겨있다. android sdk 디렉토리로 복사하였다.

cp -R tools /var/jenkins_home/android-sdk/tools

안드로이드 sdk에는 기본적으로는 platform, build tool 정도만 설치하면 앱을 빌드할 수 있다. 하지만 추가로 외부 라이브러리들을 사용한다면 해당 라이브러리들은 리포지터리에서 찾는 과정을 거치게 된다.

필자가 작업하는 프로젝트는 안드로이드 23버전을 사용하여 제작하고 있다. 그리고 외부 라이브러리들을 사용하고 있었다. 이에 필요한 sdk들을 아래의 명령을 사용하여 설치하였다.

sdkmanager \
  "platform-tools" \
  "platforms;android-23" \
  "build-tools;23.0.3" \
  "extras;android;m2repository" \
  "extras;google;m2repository"

앞의 3가지는 앱 빌드에 필요한 안드로이드 sdk들이고, 뒤의 두 개의 extras는 안드로이드와 구글 리포지터리이다. 외부 라이브러리를 사용하면서 리포지터리를 이렇게 추가해주지 않으면 gradle을 실행했을 때 라이브러리들을 찾을 수 없다고 하며 오류가 발생한다.

안드로이드 스튜디오를 사용하지 않고 빌드를 해야 하는 상황이 되다보니 처음엔 막막했지만, gradle이 오류를 친절히 알려주어서 방향을 빨리 잡을 수 있었다.

이제 sdk를 설치했으니 sdk가 어디에 있는지 알려주어야 한다.

안드로이드 프로젝트의 최상위 디렉토리에 local.properties에 다음과 같이 적어주었다.

sdk.dir=/var/jenkins_home/android-sdk
ANDROID_HOME=/var/jenkins_home/android-sdk

이제 gradlew을 실행하면 빌드에 성공한다.

Gitlab with docker and setup ssl using letsencrypt on macOS

Docker image를 생성하고 실행한다.

컨테이너의 443, 80, 22 포트는 각각 Mac의 8443, 8080, 8022로 매핑해준다. 동일한 포트로 사용하기 원하면 443:443과 같이 써주면 된다.

sudo docker run --detach \
 --hostname gitlab.example.com \
 --publish 8443:443 --publish 8080:80 --publish 8022:22 \
 --name gitlab \
 --restart always \
 --volume /Users/[username]/apps/gitlab/config:/etc/gitlab \
 --volume /Users/[username]/apps/gitlab/logs:/var/log/gitlab \
 --volume /Users/[username]/apps/gitlab/data:/var/opt/gitlab \
 gitlab/gitlab-ce:latest

Letsencrypt 인증서 생성을 위해 certbot을 설치한다.

brew install certbot

certbot을 사용하여 인증서를 만들려고 시도하였다.

sudo certbot certonly --manual -d mydomain.org

하지만 augeas 와 관련된 오류를 만났다. 퍼미션 관련 설정을 해 주었다.

sudo chown [username] /usr/local/lib/pkgconfig

다시 certbot을 실행하여 인증서 생성을 시도하였다.

sudo certbot certonly --manual -d mydomain.org

접속할 수 있는 IP인지 물어본다. y로 대답한다.

Are you OK with your IP being logged?
----------------
(Y)es/(N)o:

생성할 파일의 내용과 경로를 알려준다. 준비가 되면 엔터를 치라고 한다.

Create a file containing just this data: 293487987fd9s87s98fa798s7df7sadf987saf79dsa97afsd897dsaf987 And make it available on your web server at this URL: http://mydomain.org/.well-known/acme-challenge/fas89adfs87dasf9asdf78

-----------------------------
Press Enter to Continue

해당 경로에 파일을 만들어주어야 하기 때문에 gitlab.rb를 편집한다.

vi /Users/[username]/apps/gitlab/config/gitlab.rb

다음 줄을 추가해 준다.

nginx['custom_gitlab_server_config'] = "location ^~ /.well-known { alias /etc/gitlab/www; }"

해당 내용을 적용해 준다.

docker exec -it gitlab /bin/bash gitlab-ctl reconfigure

그리고 아래의 경로에 해당 파일을 위에서 알려준 내용으로 만들어 주었다.

vi /Users/[username]/apps/gitlab/config/www/acme-challenge/[instructed filename]

파일내용:
293487987fd9s87s98fa798s7df7sadf987saf79dsa97afsd897dsaf987

다시 certbot을 실행한 shell로 돌아가서 Enter를 누른다.

이제 /etc/letsencrypt/live/[mydomain.org]에 인증서 파일들이 생겼다. ssl 디렉토리를 생성하고 인증서를 복사한다.

cd /Users/[username]/apps/gitlab/config
mkdir ssl
cd ssl
sudo cp /etc/letsencrypt/live/[mydomain.org]/fullchain.pem ./
sudo cp /etc/letsencrypt/live/[mydomain.org]/privkey.pem ./

다시 gitrab.rb를 편집하러 간다.

vi /Users/[username]/apps/gitlab/config/gitlab.rb

다음 3개의 줄을 편집해 준다.

external_url 'https://[mydomain.org]'
nginx['ssl_certificate'] = "/etc/gitlab/ssl/fullchain.pem"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/privkey.pem"

첫째 줄은 외부 접속 경로를 설정해 주는 것이다. 필자는 외부에서도 접속할 수 있게 하려고 하였기 때문에 개인적으로 사용하는 도메인 이름을 적어 주었다.

둘째, 셋째 줄은 인증서 파일에 대한 경로를 설정해 주는 것이다.

이제 준비가 되었다. 적용해 준다.

docker exec -it gitlab /bin/bash gitlab-ctl reconfigure

웹 브라우저를 띄우고 접속해 본다.

필자는 공유기 – MacOS – docker container(Ubuntu 16.04) 의 구조였고, 포트는 8443:8443:443이었으므로

https://[mydomain.org]:8443

으로 접속하였다. 자물쇠가 나오는 것을 확인하면 SSL이 제대로 설정된 것이다.

두서 없이 썼지만, 기억은 잘 잊혀지므로  기록하였다.

다음엔 letsencrypt 인증서 만료 기간이 3개월이니, 자동으로 갱신되도록 만들어 보아야겠다.

Bitbucket 저장소와 Jenkins Webhook 연동하기

Jenkins의 소스코드 관리에서 Bitbucket 저장소와 연동하여 사용할 수가 있다. 그런데, Webhook을 걸면 브랜치를 가리지 않고 항상 실행된다.

필자는 저장소에서 Webhook을 걸 때마다 개발용 앱과 상용 앱을 모두 빌드하게 하고 싶지 않았다. pull request가 개발 브랜치로 머지될 때에는 테스트 서버에 붙는 테스트용 앱을 빌드하고, 상용 브랜치로 머지될 때에는 실 서버에 붙는 상용 앱을 빌드하고 싶었다. 하지만 기본으로 제공되는 Webhook을 사용하면, 둘을 나누어 실행할 수 없었다.

Jenkins Plugin 중에 Generic Webhook Trigger를 사용하면 이러한 문제를 해결할 수 있다.

우선은 Jenkins Plugin에 Generic Webhook Trigger를 설치한다. 설치 후 설정을 희망하는 프로젝트의 환경설정으로 간 후 Build trigger에 있는 Generic Webhook Trigger를 찾는다. 체크박스를 클릭하면 파라미터 설정 창이 열린다.

Post content parameters에 다음과 같이 적었다.

Variable Name: MERGED_BRANCH
Expression: $.pullrequest.destination.branch.name
Filter: 비워놓음

Variable Name은 Shell에서 사용할 변수명을 의미한다. Expression은 해당하는 변수에 어떤 값을 저장할 것인지 결정한다. Value filter는 Expression으로 얻은 값 중에 필터하고 싶은 것을 정규식으로 표현해 주면 된다.

Bitbucket에서 Webhook을 걸 때 POST로 보내는 메시지는 세팅 후 Webhook이 걸렸을 때 나가는 내용을 확인할 수 있다.

그리고, 젠킨스 설정의 Build 메뉴로 가서 Execute shell에서 $MERGED_BRANCH 변수를 사용하여 처리하면 된다. 필자는 다음과 같이 원하는 브랜치인 경우에만 앱을 빌드하도록 설정해 놓았다.

if [ “$MERGED_BRANCH” = “release”]
  then
    bash ./gradlew clean
    bash ./gradlew assembleProd
  else
    echo “release branch is not updated!”
fi

이제 릴리즈 브랜치가 병합되었을 때에만 릴리즈 앱이 빌드된다.

이를 토대로 하면 개발 브랜치가 병합되었을 때에만 빌드되게 하는 것도 가능하다.

오늘은 요기까지.


하지만 위와 같은 방법을 사용하면, Webhook이 걸릴때 마다 Jenkins의 빌드가 실행된다. 물론 스크립트에서 조건에 따라 빌드를 하기도 하고 그렇지 않기도 한데, Jenkins의 빌드는 많이 실행되지만 실제 앱 빌드가 적은 것은 정보가 불일치하는데에서 오는 불편감이 있다.

이를 해결하기 위해서는 프록시 서버를 하나 만들어서 bypass 시키는 방법이 있다.

이에 대해서는 조만간 포스팅 할 예정이다.

똑부 상사에 대한 오해

사람들이 상사와 부하의 조합에 대해, 혹은 어떤 상사가 좋은 상사라고 생각하는가에 대한 질문에 똑똑함/멍청함, 게으름/부지런함의 네 가지 형용사를 조합하여 4가지로 표현하곤 한다.

사람들은 대체로 똑똑하고 게으른 상사를 원하는 경우가 많았다. 일에 대한 전략과 전술에는 똑똑하기를 바라고 있으며, 부하 직원에 대한 감시나 일에 대한 간섭 등 부하직원의 자율성을 침해하는 행동의 측면에서 게으른 상사를 원하고 있었다.

똑부를 싫어하는 이유 중 하나는 너무 부지런하고 일을 벌리기 쉬워서 부하직원들의 업무가 과중해질 수 있다는 것이었다. 사실 너무 부지런해서 일을 벌리는 스타일은 사실 똑부가 아니고 멍부로 보는 게 더 적합할 것이다. 똑똑하다면 자신이 하고 싶은 일과 할 수 있는 일 사이에서 명확한 경계를 세우고 일을 계획하므로 과도하게 일을 추진하지 않을 것이다.

뭐 개인적으로는 똑부가 제일 낫다고 생각한다. 때때로 적확한 지적은 개인의 역량을 보완하는데에는 최소한 보탬이 되기 때문이다.

똑부든 똑게든 간에 사람들이 싫어하는 지점은 “자율성”이 침해되는 지점에 대한 지적이었다. 우리 말에 “평양감사도 제 싫으면 그만”이라는 말이 있다. 아무리 연봉을 많이 줘도 자율성이 침해당한다면 조직몰입도와 직무몰입의 수준이 낮아진 결과 이직율 증가나 조직 생산성의 저하로 이어지는 것은 필연적인 결과이다.

부하 직원의 자율성을 존중하고, 믿고 기다려주는 것은 사실 쉬운 일이 아니지만 쉬울 수도 있다. 조직의 상황에 따라 일에 치여서 빨리 일을 처리해야 하는 상황이라면 쉽지 않을 것이다. 하지만 심사숙고한다면, 지금 재촉하는 것이 장기적으로도 일을 빨리하는 길이 아닐 수도 있다는 지점은 고려할 필요가 있다. 현명한 선택은 항상 쉽지 않다. 자신이 감내해야 할 분량이 분명이 있기 때문이다. 어떤 선택을 할 것인지는 자신의 몫으로 남아있다.

데이터베이스 구현을 뒤로 미루는 일

엉클 밥은 소프트웨어 개발의 지혜 3절의 “급여 관리 사례 연구”에서 데이터베이스 구현을 뒤로 미루겠다고 하였다. 이를 통해, 요구사항을 더 분명히 알게 되며, 필요이상의 기반구조를 막음으로써 필요한 것만 데이터베이스로 구현할 수 있다고 하였다.

그래서 오늘부터 시작한 프로젝트는 데이터베이스로 구현하는 것을 미루고, 코드로 필수적인 부분들을 먼저 구현해 보았다.

그러다보니 자연스레 데이터베이스 설계에 공을 들이는 것보다도 사용자가 어떤 프로그램을 사용하게 될지를 생각하는데 더 많은 시간을 쓰게 되었다. 재미있는 건 저자의 이야기처럼 무엇이 필요한지, 고객의 요구가 무엇인지 더 분명해지고 있다는 것이다.

아직까지는 흥미로운 도전이다. 이후 결과도 본 글에 업데이트 하겠다.

파이썬으로 웹 크롤링 시작하기

HTML과 XML 파싱에 사용되는 python 패키지로 BeautifulSoup이 있다. 현재 버전은 4.4.0이다.

BeautifulSoup의 문서는 다음 한 페이지에 모두 설명되어 있다. 한 페이지의 양이 좀 길기는 하다. 한글문서가 있었던 것 같은데 지금은 링크가 깨져있다.

문서: https://www.crummy.com/software/BeautifulSoup/bs4/doc/

코드:

from bs4 import BeautifulSoup
html_string = '''
<html>
<head>
<title>나루의 HTML parsing</title>
</head>
<body>
</body>
</html>
'''
soup = BeautifulSoup(html_string, 'lxml')
title = soup.select("title")[0].get_text()
print(title)

결과:

나루의 HTML parsing

HTML 태그 중 title 태그의 값을 가져오는 코드를 간단히 구현하였다.

select method는 결과를 list type으로 가져온다. 그리고 get_text method는 태그 사이에 있는 텍스트 값을 문자열로 가져온다.

다음에는 실제로 웹 페이지의 정보를 가져오는 코드를 작성해 보자.

무엇이 필요한 지 아는 것

열심히 한다고 다 잘되는 게 아니다.

전략과 전술이 있어야 조금 더 발전하는 자리로 갈 수 있다.

때로는 도구를 사용할 줄 몰라서 더디게 갈 때도 있다. 그 때엔 도구의 사용을 장려하고 숙달되게 할 필요가 있다.

지금은 건강을 챙겨야 할 때, 집으로 칼같이 달려간다~*

DRY~*

오늘은
“Do not Repeat Yourself.”
이 말을 강렬하게 느끼는 하루…

이게 코드 얘기면 차라리 낫겠다.

답답해 죽갔다.

IntelliJ IDEA Competitive Discount

IntelliJ IDEA 경쟁적 할인 프로그램을 활용하면 구매가의 25%를 할인받을 수 있다.

미리 핵심부터 이야기하자면, 타 개발 도구 구매 내역을 증빙할 수 있는 자료를 제출하면, Jetbrains 담당자가 해당 제품 정가의 25% 할인된 가격으로 주문을 만들어준다. 국산 Software인  Editplus로도 할인 받을 수 있었다. 그 결과 저렴하게 intelliJ IDEA 개인 라이센스를 구매할 수 있었다.

저렴하게 할인 받을 수 있는 기회를 준 Jetbrains에 고맙게 생각하며, 구매과정을 친절하게 안내해준 Jetbrains 직원 Dominica에게 고마움을 표한다.

아래에는 과정과 절차를 기록해 두었다.

더 보기 “IntelliJ IDEA Competitive Discount”