Juju bootstrap & deploy

Ubuntu를 배포하고 있는 Canonical에서 사용자가 cloud 환경에서 쉽게 서비스를 모델링하고 배포하기 위한 tool을 Juju라는 이름으로 내놓았다. Server instance를 간편하게 관리한다는 측면에서는 Chef와 유사하지만 instance 자체를 생성하거나 제거할 수 있기 때문에 또 다른 것 같다. Juju는 instance가 아닌 service 위주의 관리와 배포를 목적으로 하고 있는 것으로 보인다. 실제로 특정 서비스 (charm이라고 부르는)를 deploy하면 신규 instance를 추가해서 charm에 정의된 내용대로 서비스를 배포하게 된다. (option에 따라 이미 추가된 instance에 배포하는 것도 가능)

참고 : 아래의 모든 터미널 이미지에 표시된 dns name과 instance-id 들은 이미 다 destroy 해서 사용할 수 없는 정보들임

1. Juju의 구조

juju_architecture<출처 : http://www.slideshare.net/LeonardoBorda/leonardo-borda-maas-and-juju-introduction>

Juju는 위 그림처럼 client가 state server에 상태값을 추가하거나 update 하도록 되어있고 하위의 모든 machine 들은 state의 변경에 따라 특정 동작을 처리하도록 되어있는 것으로 보인다. 예를 들어 wordpress와 mysql을 연결하라는 명령을 client에서 보내면 내부적으로 event가 발생되고 wordpress, mysql이 event에 맞는 hook을 실행해 세부적인 동작을 처리하게 되며 그 과정에서의 상태값들은 모두 state server에 저장되거나 변경된다. (State는 Mongo DB로 관리되는 것으로 보임)

2. Bootstrap

Juju client 설치 후 내가 사용하는 cloud 환경 (AWS, MS Azure, Openstack, Joyent, GCE, VMWare 등 대부분의 환경을 지원한다)에 대한 설정을 마친 후에 bootstrap 명령을 내리면 bootstrap instance를 생성하고 필요한 tool들의 설치, 설정까지 진행한다. Bootstrap instance는 state server로서 동작하게 된다.

juju_bootstrapAzure의 경우 vnet 생성 후에 instance를 생성해서 연결하게 되고 hash가 포함된 것으로 보이는 문자열로 DNS name을 설정하는 걸 확인할 수 있다. 사실 Juju로 관리할 경우 DNS name은 크게 중요한 요소는 아닌데 아래의 그림처럼 state server에서 별도로 일련번호(0)나 unit name을 붙이고 있어서 client에서는 DNS name이 아니라 일련번호나 unit name으로 대부분의 관리가 가능하다.

juju_status_after_bootstrap3. Charm

Juju에서 배포하고자 하는 service들은 charm이라는 이름으로 정의되고 관리된다. (Chef에서 cookbook이나 recipe처럼) Charm의 구성요소는 아래와 같다.

(1) Metadata

(2) Hooks

(3) Custom contents

Metadata는 name, description, relation, configuration option 등으로 구성이 되는데 이 중 중요한 것은 relation이다.

juju_wordpress_metadata예를 들어, WordPress charm의 metadata는 위의 그림처럼 정의되어 있는데 requires와 provides, peers로 된 부분이 relation 설정이다. Relation은 interface, kind, name으로 구성되어 있는데 각각의 정의는 이렇다.

Interface : Hook을 통해서 정보를 교환하기 위한 protocol (진짜 interface의 의미도 있지만…)

Kind : Relation의 종류로 requirer, provder, peer 중 하나를 의미하는데 requirer는 이런 relation이 필요해, provider는 이런 relation을 내가 해줄게, peer는 클러스터링 할 때는 이런 걸로 하자로 이해하면 편할 것 같다.

Name : identifier로서의 역할이고 위 wordpress metadata에서 보면 name 항목의 wordpress,  requires: db: interface: mysql 항목의 mysql을 모두 name이라고 생각하면 된다.

Hook은 위에서도 언급했듯이 event가 발생했을 때 실행되는 script로 어떤 언어로 된 script도 관계없다고 명시되어 있다. 그러니까 특정 event 발생시 각 service unit들이 해줘야 하는 동작을 script로 정의하면 되는데 몇 개 charm을 둘러보니 대부분 bash와 python으로 구현하는 것으로 보인다.

4. Deploy

Bootstrap 한 이후에 위의 charm을 가지고 deploy하는 과정은 단순히 juju deploy <charm name> 형태의 command만 실행하면 된다. 정말 간단하다. 실행하면 charm이 올라갈 instance를 생성하고 instance 생성이 완료되면 charm에 정의된 install hook이 실행되서 필요한 tool을 설치하거나 설정하게 된다. 그리고 deploy 후에 state server (bootstrap instance)에 상태 정보를 요청하면 아래와 같은 내용을 확인할 수 있다.

juju_status_after_deploy5. Instance에 접속하고 싶다면

대부분의 관리가 juju command로 가능하지만 그래도 배포된 instance에 접속해야하는 상황이 있을 수 있다. 그 경우 juju ssh 0 (0번은 bootstrap instance에 대해서 붙여진 번호), juju ssh <unit_name> 형태의 command를 사용하면 ssh로 접속이 가능하다. 실제로 bootstrap instance나 다른 instance를 생성할 때 juju client에서 관리하는 public key를 전송해서 각 instance (machine)의 authorized_keys에 추가하기 때문에 juju command가 싫다면

ssh -i <private_key_path> ubuntu@<dns_name>

으로 접속할 수 있다. (Juju client가 설치된 장비의 private key는 기본적으로 ~/.juju/ssh 경로에 생성됨)

juju_ssh_using_private_key6. Add relation

4에서처럼 charm을 deploy 하고나면 단지 설치만 된다. WordPress charm의 경우 설치했을 때 wordpress만 설치되고 mysql 등의 DB는 별도로 설치되지 않는다. MySQL이 필요하니 deploy를 한다해도 역시 설치만 되고 각 service는 별개의 것으로 남아있게 된다. juju add-relation wordpress mysql 명령을 실행해보자. (wordpress, mysql은 charm의 이름이지만 이미 설치된 것이고 state server에서 각 service unit에 대한 상태를 관리하고 있으니 문제없다)

juju_db_relation_joinedjuju_db_relation_changed위 그림은 순서대로 mysql과 wordpress가 설치된 instance에서 수집한 로그인데 add-relation 명령 실행시 mysql에서는 db-relation-joined, wordpress에서는 db-relation-changed가 순차적으로 실행되는 것을 확인할 수 있다. db-relation-joined와 db-relation-changed는 각각 mysql과 wordpress charm에 정의되어 있는 hook으로 add-relation 명령이 수행되면 wordpress와 mysql이 provide하고 require하고 있는 항목인 db에 대한 relation-joined hook이 실행되는데 db-relation-joined hook은 mysql에만 정의되어 있으므로 먼저 실행된다.

juju_relation_setdb-relation-joined hook의 내용을 살펴보면 위 그림처럼 relation-set 명령을 호출해서 db 접속정보를 설정하는 것을 알 수 있다. 위의 접속정보는 state server에 저장되고나면 변경사항이 발생되었기 때문에 db-relation-changed hook이 실행되는데 이것은 wordpress에만 정의되어 있는 hook이므로 wordpress에서만 실행되며 state server에 저장된 db 접속정보를 받아와서 설치된 wordpress에 설정파일들을 수정하거나 다른 조작을 가하도록 정의되어 있어 wordpress와 mysql 사이의 연결이 가능해진다.

juju_relation_get

7. Expose

Deploy와 relation의 과정을 거치고 나더라도 바로 wordpress를 이용할 수는 없다. Cloud service 들의 특성상 보안 등의 이유로 대부분의 port는 연결이 되지 않도록 막혀있기 때문에 특정 port를 열어주는 작업이 필요하다. 위의 과정으로 deploy 한 것은 wordpress와 mysql 뿐이고 wordpress만 열면 되기 때문에 간단하게

juju expose wordpress

명령만 실행하고 나면 약간의 시간 후에 아래의 그림처럼 endpoint 설정이 추가된 것을 확인할 수 있다. (Azure 기준)

juju_exposeWordPress나 MySQL charm에서 어떤 부분이 expose를 가능하게 하는지 아직 찾지 못했다. MySQL을 expose 해보니 status에서 exposed flag 값만 false에서 true로 바뀔 뿐 실제 endpoint 설정이 추가되지는 않는데 이 부분은 좀 더 살펴볼 필요가 있을 것 같다.

8. 참고

https://jujucharms.com/docs/stable/getting-started

http://blog.labix.org/2013/06/25/the-heart-of-juju

http://askubuntu.com/questions/55179/what-is-the-purpose-of-the-bootstrapping-instance-in-juju

 

Gerrit에서 custom label 생성하기

Google의 code review tool인 Gerrit 사용시 기존의 label인 Code-Review, Verified 이외에 사용자 정의 label이 필요한 경우가 있다.

내 경험으로는 CI를 위해 custom label이 필요했었는데 특정 label의 point로 build 여부를 결정짓는데 사용했다.

Custom label이 필요한지 여부를 판단하는 것은 어디까지나 SCM 정책에 따라 달라질 수 있고 그 설정과 관리는 대부분의 사용자들과는 관계가 없는 부분이다.

 

1. Project clone

Custom label을 적용하고자 하는 특정 project (저장소)를 clone 한다.

Gerrit의 branches 메뉴를 보면 refs/meta/config 라는 reference가 존재하는데 일반적으로 master branch가 항상 있는 경우 refs/meta/config를 checkout 하지 않으면 보이지 않는다.

비어있는 project를 대상으로 하면 (branch가 없음) clone 하면 바로 유일한 reference인 refs/meta/config의 내용이 나타난다.

 

2. project.config 수정

refs/meta/config에는 project.config와 groups 파일이 있는데 (모든 경우를 살펴본 것은 아니라서 다른 파일이 추가되어 있을 수도 있음)

groups 파일에는 access control에 추가되어 있는 관계된 group 정보가 포함되어 있고 custom label 설정과는 관계가 없다.

project.config 파일을 열어보면 access control에 대한 내용과 project 일반적인 설정들이 포함되어 있는데 (description 등) 추가할 label에 대한 내용을 정의한다.

보통 이런 형태로 작성하면 되는데

[label “test label”]

function = NoOp

value = 0 Do Not Build

value = +1 Build Now

추가 상세 설정은 Gerrit 문서를 참조해서 진행한다.

function을 NoOp으로 설정한 경우 submit 여부를 결정하는데 test label이 영향을 미치지 않는다. (기존 정책에 따라 submit 여부가 결정됨)

value는 설정한대로 부여할 point의 범위가 결정되는 역할을 한다.

 

3. Commit / Push

소스코드와 동일하게 저장소 변경사항이 발생했으므로 commit을 하고 push를 한다. (Push 할 때 refs/meta/config를 지정하는 것은 기본)

Gerrit의 branches 에서 refs/meta/config의 내용을 확인해 변경사항이 제대로 반영되었는지 확인이 되면 Access Control 메뉴에서 설정할 때 내가 추가한 test label 설정이 가능한 것을 확인할 수 있다.

 

4. 결론

일반 개발자들이나 사용자들은 어쩌면 알 필요가 없는 기능이다.

하지만 이런 것도 Gerrit이 제공한다.

알아두면 CI 구성이나 SCM 정책을 좀 더 유연하거나 강화시키는데 도움이 될것이라고 본다.

국내에서는 이런 custom label 사용보다 사실 Gerrit이 제공하는 기본 기능, 정책들만이라도 잘 지킬 수 있기를 희망한다.

Azure에서 VHD download 없이 VM disk resizing

Azure에서 VM 생성시 disk size가 30GB 밖에 되질 않는다.

운영체제가 올라가는 disk이고 다른 data 들을 위한 공간은 disk를 하나 더 생성해서 붙여도 되겠지만 그럴 경우 linux는 추가로 mount를 해야 하고 내가 원하는 경로로 설정하기 위해서는 다른 작업을 거쳐야 할 수 있다.

이런 경우 운영체제가 올라가 있는 disk 자체를 resizing 할 필요성이 생기는데 (아무리 그래도 사실 30GB는 작다. 왜 최초 VM 생성시에 disk size를 지정할 수 없을까?) MSDN이나 기타 여러 곳의 자료들을 보면 내용이 좀 복잡하다. 보통 이런 것 같다.

1. VM 삭제

2. VHD download

3. VHD resizing (별도의 tool을 이용)

4. VHD upload

5. VM 다시 생성

다른 과정이야 그냥 넘어간다 하더라도 download / upload가 걸린다. 30GB 짜리를 다운로드하고 늘려서 다시 업로드를 해야 한다니.

그래서 더 조사를 해봤더니 역시 나와 같은 고민을 하는 사람이 있었고 그 양반이 괜찮은 솔루션을 제시해 놨다.

그 과정은 아래와 같다.

1. VM shutdown

2. VM 삭제 (삭제할 때 drive는 유지하는 옵션을 골라야 한다)

3. VM의 disk 삭제 (삭제할 때 VHD를 유지하는 옵션을 선택한다. 그럼 VHD 파일은 storage에 그대로 남아있게 됨)

4. WindowsAzureDiskResizer로 VHD resizing (Azure storage에 올라가 있는 그대로 resizing 됨)

5. Disk 생성 (이제부터는 거꾸로 반복, resizing된 VHD로 disk를 생성한다)

6. VM 생성 (5의 disk를 선택하고 원래의 VM 내용대로 설정한다)

 

결론

VHD 파일을 분석해서 괜찮은 툴을 만들고 그걸 오픈소스로 공개한 Maarten Balliauw 덕분에 좀 편해졌다.

그래도 VM 삭제하고 다시 생성해야 하는 과정이나 애초에 disk size 선택이 불가능한 부분은 개선이 필요해 보인다.

 

참조

1. Expanding an Existing Azure VM System Drive : 여기에 설명된 과정에 백업이 있는데 별로 중요하지 않거나 귀찮다면 skip 해도 무방하다

2. Tales from the trenches: resizing a Windows Azure virtual disk the smooth way

Random number와 가중치를 고려한 추첨기능 구현하기

복수의 아이템이 들어있는 자료구조에서 추첨을 해야하는 경우가 있다. 보통 추첨을 한다하면 무작위로 선택되어야 하는데 때에 따라서는 특정 아이템의 선택 확률을 높여야 할 수도 있다.

 

언어에 따라 조금 차이가 있기는 하지만 보통 random number 생성에 관계된 API들은 수학과 관련된 package로 묶여 있거나 연산에 특화된 기능들끼리 모여있다. 따라서 단순 random number 생성 이외의 기능, 가중치를 고려하는 부분은 직접 구현할 수 밖에 없다. 또는 가중치 고려가 가능하게 만들어진 오픈소스 라이브러리가 있을지도 모르겠는데 그리 복잡하지 않을 것 같아 직접 구현하기로 했다.

 

1. 가중치를 통해 선택확률 높이기

아래의 그림을 보면 윗 부분은 가중치가 1로 동일한 상태이고 아래는 특정 아이템에 가중치를 더 높임으로써 선택될 확률을 높인 상태이다. 칸으로 구별된 bar를 다루게 될 아이템이 담겨있는 자료구조로 보자. 각 칸에는 하나의 아이템이 담겨있고 좌측을 시작으로 우측으로 갈 수록 index가 높다고 하자.
random_weight

 

이렇게 하는 것이 어떻게 선택될 확률을 높일 수 있을까?

Java를 기준으로 random number 생성을 살펴보면 단순히 Math.random() 호출시 0과 1사이에 있는 double 값을 생성해서 돌려주게 되어있다. (정확히 0.0 이상 1.0 미만의 값) Random number의 range를 0 ~ total weight로 설정하면 위 그림의 bar 상단에서 좌측에서 우측으로 증가하는 1차원 그래프를 상상해볼 수 있다. 즉 생성된 random number 가 찍히는 점의 아래에 있는 아이템이 선택될 수 있다.

Weight가 동일한 경우는 그래프에서 차지하는 영역이 동일하기 때문에 random number가 동일한 확률로 생성된다면 아이템이 선택될 확률도 동일하다고 볼 수 있다. 반면에 특정 아이템의 weight가 높은 경우 차지하는 영역이 넓어지기 때문에 그만큼 그 영역안에서 random number가 생성될 가능성이 다른 영역에 비하면 높아진다.

 

2. 그렇다면 구현은 어떻게 할까?

위 그림과 그래프를 상상해보면 구현은 간단하다. 각 아이템이 갖는 가중치를 모두 더해서 Math.random() 등으로 random number를 생성할 때 0부터 가중치합 사이의 값이 생성되도록 한다. (역시 언어에 따라 다르지만 random number 생성시 seed 값을 설정할 수 있어야 random number 생성의 중복을 방지할 수 있다. Java의 경우 Math 보다는 Random class를 사용하는게 나을 것으로 판단됨) 그렇게 생성된 random number를 가지고 자료구조에서 index를 증가시키며 각각의 아이템이 가진 가중치를 뺀다. (가중치를 빼지 않고 더해서 하는 방법도 있을텐데 그 경우 random number와의 비교구문이 약간 달라져야 할 것이다) 그렇게 진행하다보면 어느 순간 random number가 0이나 음수가 되는데 그 때의 아이템이 가중치에 따라 선택된 아이템이 된다. Pseudo code로 간단하게 작성하면 아래와 같다.


List<Item> list = getItemList();

double total = getTotalWeight(list);

for(int i = 0; i < list.size(); i++) {

    total -= list.get(i).getWeight();

    if(total <= 0) {

        selectedIndex = i;

        break;

    }

}

return list.get(selectedIndex);