CentOS LVM 기반 XEN 가상화 구축하기

centos

가상화 머신을 하나 가지고 싶다고 생각하던 중 실제로 구축을 해보고자 서버를 구매하였습니다. 처음에는 싱글 노드를 가진 OpenStack 시스템을 구축하고 싶었지만 생각보다 과정이 만만치 않더군요. 일단은 간단하게 Xen 서버를 구축하여 구동하기로 하였고 몇주째 사용중에 있습니다. 생각 이상으로 안정적인것 같아 설치 과정을 기록해 두고자 정리합니다.

제가 개발에 사용한 서버는 DELL PowerEdge C1100 중고에 72G메모리와 1TB 하드디스크 3개를 RAID5로 묶었습니다. CentOS는 현재 최신버전인 6.5를 설치하였습니다. 부가적인 서비스 모두 필요없으므로 [링크]에서 CentOS Minimal 버전을 다운로드 합니다.

xen_virtualization_01

페이지에서 위와 같이 64비트용 ISO를 다운 받습니다. 시디로 굽거나 USB에 구워서 사용하시면 됩니다. 이후에 서버에 일반적인 방식으로 설치를 하시면 됩니다. 다만 파티션의 전체를 LVM Volume Group으로 묶어서 사용합니다. 저의 경우 전체 2T용량을 다음과 같이 설정하였습니다.

LVM Volume Groups
└── vg_cloudsystem
    └── swap 4G
    └── / 50G
Hard Drives
└── /dev/sda
    └── /dev/sda1 /boot 500M
    └── /dev/sda2 vg_cloudsystem LVM

결과적으로 2T의 용량중 55기가정도만 사용하고 나머지는 FREE 영역으로 놔두고 리눅스 설치를 진행합니다. 설치가 완료된 후 콘솔에 접속하여 용량을 확인해 보면 다음과 같이 설정된것을 확인할 수 있습니다.

$ lvm vgs
  VG             #PV #LV #SN Attr   VSize VFree
  vg_cloudsystem   1   4   0 wz--n- 1.82t 1.76t

$ lvm lvs
  LV          VG             Attr       LSize   Pool Origin Data%  Move Log Cpy%Sync Convert
  lv_root     vg_cloudsystem -wi-ao----  50.00g
  lv_swap     vg_cloudsystem -wi-ao----   4.00g

CentOS의 설치가 완료되었지만 Minimal 버전의 경우 자동으로 네트워크 설정이 진행되지 않습니다. 수동으로 네트워크 설정을 진행해 보겠습니다. 내부의 가상 서버들이 모두 외부의 네트워크와 통신을 할 수있도록 브릿지(Bridge) 설정을 할 것입니다.

$ cd /etc/sysconfig/network-scripts
$ cp ifcfg-eth0 ifcfg-xenbr0
DEVICE=eth0
HWADDR=60:EB:69:FE:4C:72
TYPE=Ethernet
ONBOOT=yes
IPV6INIT=no
NM_CONTROLLED=no
BRIDGE=xenbr0
DEVICE=xenbr0
TYPE=Bridge
ONBOOT=yes
NM_CONTROLLED=no
BOOTPROTO=dhcp

위와 같이  네트워크 설정을 진행해 주시면 네트워크의 준비는 끝납니다. 기본적으로 eth0에 할 설정을 xenbr0에 설정했다는것이 중요한 부분입니다. 예시에는 DHCP를 사용하는 네트워크 환경의 예시를 적어놓았는데 만약 고정 아이피를 사용할 경우 다음과 같이 설정하시면 됩니다.

DEVICE=xenbr0
TYPE=Bridge
ONBOOT=yes
NM_CONTROLLED=no
BOOTPROTO=static
DELAY=0
IPADDR=1.2.3.4
NETMASK=255.255.255.0
GATEWAY=1.2.3.1
DNS1=168.126.63.1
DNS2=168.126.63.2

이제 다음의 명령을 사용하여 네트워크 서비스를 구동해 줍니다.

$ service netwok restart

정상적으로 네트워크가 돌아왔다면 CentOS 배포본의 업데이트를 진행해 봅시다. 업데이트에 문제가 없다면 XEN을 설치합니다.

$ yum -y update

$ yum install centos-release-xen
$ yum install xen
$ /usr/bin/grub-bootxen.sh

/boot/grub/grub.conf 의 내용을 확인하여 정상적으로 커널이 추가되어있는지 확인해 봅니다. defaults 값이 0이고 kernel /xen.gz 같은 내용이 첫번째 커널로 정의되어있으면 정상입니다.

이제 재부팅을 해야 하는데요, 기왕 재부팅 할꺼 SELinux도 꺼두겠습니다.

$ sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
$ setenforce 0

이제 재부팅을 합니다.

$ reboot

재부팅이 정상적으로 되었다면 다음과 같이 정상적으로 XEN이 구동중인지 확인해 볼 수 있습니다.

$ xm info
host                   : cloud-system
release                : 3.10.25-11.el6.centos.alt.x86_64
version                : #1 SMP Fri Dec 27 21:44:15 UTC 2013
machine                : x86_64
nr_cpus                : 16
nr_nodes               : 1
cores_per_socket       : 4
threads_per_core       : 2
cpu_mhz                : 2133
hw_caps                : bfebfbff:2c100800:00000000:00003f40:029ee3ff:00000000:00000001:00000000
virt_caps              : hvm hvm_directio
total_memory           : 73718
free_memory            : 47174
free_cpus              : 0
xen_major              : 4
xen_minor              : 2
xen_extra              : .3-26.el6
xen_caps               : xen-3.0-x86_64 xen-3.0-x86_32p hvm-3.0-x86_32 hvm-3.0-x86_32p hvm-3.0-x86_64 
xen_scheduler          : credit
xen_pagesize           : 4096
platform_params        : virt_start=0xffff800000000000
xen_changeset          : unavailable
xen_commandline        : dom0_mem=1024M,max:1024M loglvl=all guest_loglvl=all
cc_compiler            : gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-3)
cc_compile_by          : mockbuild
cc_compile_domain      : centos.org
cc_compile_date        : Tue Dec 10 20:32:58 UTC 2013
xend_config_format     : 4

여기서 유심히 봐야 하는 부분은 virt_caps인데요 CPU가 전가상화를 지원하고 BIOS에서 Enabled되어있다면 hvm이 보여집니다. hvm이 없다면 전가상화는 불가능한것으로 이해하시면 됩니다. 개인적으로 설정파일을 이용하는 xm 또는 xl 커맨드로의 가상서버 생성은 조금 복잡한것 같아 libvirt를 설치하겠습니다.

$ yum install rsync wget vim-enhanced openssh-clients
$ yum install libvirt python-virtinst libvirt-daemon-xen

이번에는 가상 서버에서 사용할 디스크를 생성해 보겠습니다. 가상화 이미지 파일을 사용할수도 있지만 개인적으로 LVM 파티션을 직접 마운트하는 방식을 선호합니다.

$ lvcreate -L 500G -n lv_theeye vg_cloudsystem
  Logical volume "lv_theeye" created
$ lvs
  LV        VG             Attr       LSize   Pool Origin Data%  Move Log Cpy%Sync Convert
  lv_root   vg_cloudsystem -wi-ao----  50.00g
  lv_swap   vg_cloudsystem -wi-ao----   4.00g
  lv_theeye vg_cloudsystem -wi-a----- 500.00g

vg_cloudsystem Volume Group 밑에 새롭게 500G 용량의 Logical Volume을 생성하였습니다. 만약에 잘못 생성하여 삭제할 일이 있을 경우 다음의 명령을 사용하여 삭제할 수 있습니다.

$ lvremove /dev/vg_cloudsystem/lv_theeye

이제 가상 서버를 생성하기에 앞선 모든 준비가 끝났습니다.

$ virt-install --virt-type xen \
    -n theeye -r 8192 --vcpus=2 \
    -f /dev/vg_cloudsystem/lv_theeye -p \
    -l http://ftp.daum.net/centos/6.5/os/x86_64 \
    --os-type=linux --nographics \
    --network=bridge:xenbr0 --network=network:default

위의 옵션은 다음과 같은 의미를 가지고 있습니다.

  • xen 타입의 가상화 서버를 생성 (–virt-type)
  • 가상화 서버의 이름은 theeye로 지정 (-n)
  • 메모리는 8기가를 할당 (-r)
  • CPU는 2개를 할당 (–vcpus)
  • /dev/vg_cloudsystem/lv_theeye LVM 파일 시스템을 사용 (-f)
  • 다음에서 제공하는 CentOS 미러 저장소 활용 (-l)
  • 반가상화(Para-virtualization)로 설정 (-p)
  • 설치할 OS의 타입은 리눅스 (–os-type)
  • GUI지원없이 설치 (–nographics)
  • 이더넷은 2개를 설정, eth0은 브릿지를 사용하고 eth1은 내부 가상 네트워크를 사용 (–network)

전가상화로 설치할 경우 -p 대신에 –hvm옵션을 사용하시면 됩니다. 잠시후 다음과 같은 설치 화면을 콘솔에서 볼 수 있습니다.

xen_virtualization_02

설치를 진행하기에 앞서 eth0과 eth1의 네트워크 설정을 어떻게 해야 할지에 대해 알아봅시다. 호스트 머신에서 ifconfig 내용을 확인해 보면 다음과 같은 내용을 확인할 수 있습니다.

ifconfig
xenbr0    Link encap:Ethernet  HWaddr 60:EB:69:FE:4C:72  
          inet addr:1.2.3.4  Bcast:1.2.3.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:20969079 errors:0 dropped:0 overruns:0 frame:0
          TX packets:32361982 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2228628430 (2.0 GiB)  TX bytes:35151830809 (32.7 GiB)

eth0      Link encap:Ethernet  HWaddr 60:EB:69:FE:4C:72  
          inet6 addr: fe80::62eb:69ff:fefe:4c72/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:78368057 errors:0 dropped:0 overruns:0 frame:0
          TX packets:117016569 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:10209375118 (9.5 GiB)  TX bytes:132135431402 (123.0 GiB)
          Memory:c0200000-c0220000 

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:139859 errors:0 dropped:0 overruns:0 frame:0
          TX packets:139859 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:88945222 (84.8 MiB)  TX bytes:88945222 (84.8 MiB)

virbr0    Link encap:Ethernet  HWaddr FE:FF:FF:FF:FF:FF  
          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:275408 errors:0 dropped:0 overruns:0 frame:0
          TX packets:407325 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:16857625 (16.0 MiB)  TX bytes:590232428 (562.8 MiB)

여기서 기존에 정의하지 않았던 virbr0를 확인할 수 있습니다. 여기의 네트워크 대역이 192.168.122.0인것을 확인할 수 있습니다.

xen-bridge-vif

기본적으로 물리적인 eth0은 xenbr0 브릿지를 통해 dom0라는 가장 기본이 되는 가상 도메인의 eth0에 연결됩니다. 이를 통해서 다른 게스트들이 외부로의 통신이 가능해 집니다. 여기서 보여지는 vif들은 192.168.122.x를 부여받게 됩니다. 즉 새로 설치한 게스트의 eth0(bridge:xenbr0)은 xenbr0를 직접 사용하여 공용 아이피를 설정할 것이고 eth1(network:default)은 dom0의 사설 아이피를 설정할 것입니다.

DEVICE=eth0
TYPE=Ethernet
HWADDR=00:16:3E:55:F5:3B
BOOTPROTO=static
NM_CONTROLLED=no
ONBOOT=yes
IPADDR=1.2.3.5
NETMASK=255.255.255.0
GATEWAY=1.2.3.1
DNS1=168.126.63.1
DNS2=168.126.63.2
DEVICE=eth1
TYPE=Ethernet
HWADDR=00:16:3E:55:F5:3C
BOOTPROTO=static
NM_CONTROLLED=no
ONBOOT=yes
IPADDR=192.168.122.50
NETMASK=255.255.255.0
GATEWAY=192.168.122.1

잊지 말고 네트워크 서비스를 재시작 해줍시다. 이제 이 서버는 외부 네트워크뿐만 아니라 다른 가상 서버들과도 통신이 가능한 가상 서버가 되었습니다. xm help를 통해 어떤 작업이 가능한지 확인해 보시기 바랍니다. (xm top을 통해 전체 게스트들의 상태를 확인할수도 있습니다) 네트워크 문제가 발생했을 경우 콘솔에 바로 접속하는 방법은 다음의 명령어를 사용하시면 됩니다.

$ xm list
Name                                        ID   Mem VCPUs      State   Time(s)
Domain-0                                     0  1022    16     r-----  17344.6
theeye                                       1 16384     2     -b---- 379145.7
$ xm console theeye

virt-install을 이용하여 설치할 때에 사용할 수 있는 원격지 저장소중에 제 생각에 빠른곳 몇개를 정리해 보았습니다.

  • CentOS : http://ftp.daum.net/centos/6.5/os/x86_64
  • Debian : http://ftp.kr.debian.org/debian/dists/stable/main/installer-amd64
  • Ubuntu(13.10) : http://ftp.ubuntu.com/ubuntu/dists/saucy/main/installer-amd64

참고

  • http://hustcalm.me/blog/2013/10/14/playing-with-xen-under-centos6-dot-4-to-build-your-own-vps/
  • http://drewsymo.com/2013/11/install-xen-4-with-libvirt-xl-on-centos-6-2014/
  • http://www.cyberciti.biz/tips/rhel-centos-xen-virtualization-installation-howto.html

Phrase를 이용한 Android String Formatting

Phrase는 안드로이드용 텍스트 토큰 교체를 위한 마이크로 라이브러리입니다. Phrase는 미국의 유명한 결제 시스템 회사인 Squre에서 자사의 Squre Register라는 안드로이드 어플리케이션을 프랑스와 일본어로 번역하는 과정에서 겪은 문제점들을 해결하기 위해 만들어졌습니다.

다음과 같은 문자열 선언이 strings.xml에 정의되어있다고 가정해 보겠습니다.

<string name="greeting">
  Hello %1$s, today\'s cook yielded %2$d %3$s.
</string>

greeting에 정의된 포매팅은 간단한 형태를 띄고있습니다. 안드로이드의 Context는 정의되어있는 문자열을 가져오고 한번에 포맷팅할 수 있는 오버로딩된getString(…) 메소드를 제공하고 있습니다.

String name = "Walter";
int yield = 50;
String unit = "pounds";
String greeting = context.getString(R.string.greeting, name, yield, unit);

하지만 위에서 볼 수 있는 %1$s 와 같은 문법은 프로그래머가 아닌 사람들에게는 명확하게 이해하기 어렵습니다. 각각의 포매팅 지정자들이 번역될 때 이러한 이해하기 어려운 형태의 지정자들로 인해 오타등의 실수가 발생할 수 있습니다. 또한 지정자들이 또다른 지정자들 혹은 문자열에 인접해 있을 때 더 많은 문제가 발생했습니다.

또다른 문제는 %2$d와 같은 지정자의 의미를 해석하기 쉽지 않다는 점 입니다. 우리는 주변의 텍스트를 통해 여기에 들어갈 문자의 의미를 알아차릴 수 있을 것이고 Java 코드상의 버그를 피하기 위해 파라미터의 순서가 정확하게 일치해야만 합니다.

// 파라미터의 순서가 잘못되었으므로 버그 발생!
String greeting = context.getString(R.string.greeting, name, unit, yield);

마지막으로 Context.getString(…)은 볼드나 이탤릭같은 스타일이 적용된 텍스트를 처리할 수 없다는 점 입니다. 당신이 strings.xml에 간단한 HTML 태그를 포맷 지정자와 함께 사용하였다면 HTML 태그는 조용히 무시될 것입니다.

Phrase

Phrase를 사용하면 greeting은 다음과 같이 변화됩니다. 보시다 싶이 애매모호했던 지정자들은 읽기 쉽고 이해하기 쉬운 {name} 과 {yield} 로 변경되었습니다.

<string name="greeting">
  Hello {name}, today\'s cook yielded {yield} {unit}.
</string>

번역중 발생할 수 있는 오류를 줄이기 위한 첫번째 목표와 이것을 지키기 위한 룰은 간단합니다.

  • 키를 중괄호 {} 로 또한번 감싸야 할 경우(보여지기 위해) 두개의 {{ 를 사용하여 이스케이프 처리 할 수 있습니다.
  • 키는 소문자 영문자로 시작해야 하고 다음으로는 소문자와 언더스코어 _ 가 사용 가능합니다.

더 많은 유연성을 제공하는것은 복잡성을 증가시키기에 우리는 대문자, 숫자, 또는 밑줄 이외의 특수문자를 허용하지 않습니다.

포맷팅은 유연한 형태로 변경 되었고 순서가 없는 키/밸류 형태의 알기쉬운 키형태를 사용하게 되었기에 프로그래머의 인생이 좀 더 편안해 졌습니다. (과연?ㅎ)

// 순서와 상관없이 put(...)을 호출
CharSequence greeting = Phrase.from(context, R.string.greeting)
    .put("unit", unit)
    .put("name", name)
    .put("yield", yield)
    .format();

Phrase는 문자열에 삽입되어있는 HTML태그와 포맷지정자들을 모두 살리기 위해 String 대신에 CharSequence를 반환합니다.

<string name="did_you_learn">
  <!-- class_type is something like Chemistry. -->
  Did you learn <b>nothing</b> from my {class_type} class?
</string>

Validation

Phrase는 fail-fast 철학을 준수합니다. Phrase는 다음과 같은 실수가 발생할 경우 예외를 발생시킵니다.

  • 패턴의 파싱중에 잘못된 중괄호 패턴이나 키에 잘못된 문자열이 사용되었을 경우
  • put(…)을 호출할 때 키 또는 밸류에 null 이 들어갈 경우
  • 포매팅 패턴에 존재하지 않는 키를 put(…)으로 넣을 경우
  • format() 이 호출되는 시점에 모든 키에 대해 밸류가 채워지지 않은 경우

즉각적인 예외 또는 크래시 발생은 개발 과정에서의 실수를 줄여주고 당황스러운 번역 실수 또는 포맷팅 되지 않은 문자열이 표시되는 확률을 최소화 해줍니다. 왜냐하면 키들은 이해하기 쉽고 각각의 언어별로 strings.xml에 정의된 Phrase 키가 똑같기 때문에 유효성 스크립트를 제작하는것도 간단해집니다.

Example

간단하게 위의 내용을 테스트 해보도록 하겠습니다. 개발은 맥 환경의 Android Studio와 Gradle을 이용하였습니다. 먼저 간단한 프로젝트를 만들어 보도록 하겠습니다. 저는 HelloPhrase라는 이름의 프로젝트를 만들었습니다. 이후에 build.gradle 파일의 내용을 수정합니다.

1

dependencies 에 다음의 한줄을 추가하면 사용준비가 끝납니다. 현재 시점에서는 1.0.3이 최신버전입니다.

dependencies {
  compile 'com.squareup.phrase:phrase:(insert latest version)'
}

strings.xml에는 다음을 추가하였습니다.

<string name="greeting">
Hello <b>{name}</b>, today\'s cook yielded <u>{yield}</u> {unit}.
</string>

적절한 텍스트뷰에 위의 결과물을 출력해 보겠습니다.

TextView tv = (TextView) rootView.findViewById(R.id.text);

CharSequence greeting = Phrase.from(getActivity(), R.string.greeting)
    .put("unit", "pounds")
    .put("name", "Walter")
    .put("yield", 50)
    .format();

tv.setText(greeting);

device-2014-01-29-110001

정상적으로 결과물이 출력되는것을 보았습니다. 실제로 개발중에 느낀건데 조금이라도 값이 안맞거나 하면 바로 크래시가 나는군요. 서버에서 출력할 메시지를 받는 경우 잘못하면 앱이 죽어버리는 문제를 야기할 수 도 있을것 같습니다. 사용에 유의하시기 바랍니다.

참고 : http://corner.squareup.com/2014/01/phrase.html