Openstack + Quantum: Creating Virtual Machine
간단하게 작성해본 OpenStack with Quantum 환경에서 tenant-private network과 external network을 만들고 해당 네트워크 상에 가상머신을 생성하는 테스트 스크립트입니다.
테스트용이므로 실전에서는 사용할 수 없을 정도이고, 그냥 참고만 하세요
#!/bin/bash
IMAGE=${IMAGE:-cirros-0.3.0-x86_64}
if [ -z "$OS_TENANT_NAME" ];
echo "openstack environ variables is not set"
echo "please run . ~/openrc tenant_name"
exit
fi
TENANT_ID=$(keystone tenant-list | grep " $OS_TENANT_NAME " | awk '{print $2}')
PRINET="${OS_TENANT_NAME}"
EXTNET="ext_net"
VM=$1
#
# create external network - only by admin
#
if [ "$OS_USERNAME" = 'admin' ]; then
EXTNET_ID=$(quantum net-list -- --tenant_id=$TENANT_ID --router:external=True | awk "/ $EXTNET / { print \$2 }")
if [ -z "$EXTNET_ID" ]; then
EXTNET_ID=$(quantum net-create $EXTNET --tenant_id=$TENANT_ID --router:external=True | grep ' id ' | awk '{print $4}')
fi
# create external subnet
EXTSUBNET_ID=$(quantum net-show $EXTNET_ID | awk "/ subnets / { print \$4 }")
if [ $EXTSUBNET_ID = "|" ]; then
EXTSUBNET_ID=$(quantum subnet-create $EXTNET_ID "10.100.1.0/24" --tenant_id=$TENANT_ID --name=${EXTNET}_subnet --enable_dhcp=False | awk '/ id / {print $4}')
fi
else
EXTNET_ID=$(quantum net-list -- --router:external=True | awk "/ $EXTNET / { print \$2 }")
fi
#
# tenant internal network
#
# create private network
NET_ID=$(quantum net-list -- --tenant_id=$TENANT_ID --name=$PRINET | awk "/ $PRINET / { print \$2 }")
if [ -z "$NET_ID" ]; then
NET_ID=$(quantum net-create $PRINET --tenant_id=$TENANT_ID | grep ' id ' | awk '{print $4}')
fi
echo "NET=$NET_ID"
# create private subnet
SUBNET_ID=$(quantum net-show $NET_ID | awk "/ subnets / { print \$4 }")
if [ $SUBNET_ID = "|" ]; then
SUBNET_ID=$(quantum subnet-create $NET_ID "172.16.1.0/24" \
--tenant_id=$TENANT_ID --name=${PRINET}_subnet \
--dns_nameservers list=true 168.126.63.1 8.8.8.8 | \
awk '/ id / {print $4}')
fi
echo "SUBNET=$SUBNET_ID"
# now internal network is working
# and connect to external network
# create router for connect to external network
ROUTER_NAME="router_${OS_USERNAME}_ext"
ROUTER_ID=$(quantum router-list -- --tenant_id=$TENANT_ID --name=$ROUTER_NAME | head -n -1 | tail -n +4 | awk '{print $2}')
if [ -z "$ROUTER_ID" ]; then
ROUTER_ID=$(quantum router-create --tenant_id=$TENANT_ID $ROUTER_NAME | awk '/ id /{print $4}')
fi
echo "ROUTER=$ROUTER_ID"
quantum router-interface-add $ROUTER_ID $SUBNET_ID
quantum router-gateway-set $ROUTER_ID $EXTNET_ID
#
# boot instance
#
if [ ! -z "$VM" ]; then
IMAGE_ID=$(nova image-list | grep " $IMAGE " | head -n 1 | awk '{print $2}')
echo "IMAGE=$IMAGE_ID"
VM_ID=$(nova boot --image=$IMAGE_ID --flavor=1 --nic net-id=$NET_ID $VM | awk '/ id /{print $4}')
echo "VM=$VM_ID"
nova show $VM_ID
fi
이 스크립트는 https://github.com/whitekid/openstack-chef/ 의 일부입니다.~~
Iso 파일 마운트: Losetup, Fuseiso
리눅스에서 iso 파일을 마운드하는 방법은 2가지 있습니다.
첫번째는 loop device를 사용하는 방법으로
$ losetup /dev/loop0 /var/cache/ubuntu-12.04-amd64.iso
$ mount /dev/loop0 /mnt
bla bla ....
$ umount /mnt
$ losetup -d /dev/loop0
여기서 loop device는 중복이 되면 안되니 losetup -a로 비어있는 loop device를 확인 해야합니다.
두번째는 fuseiso를 이용하는 방법입니다.
$ apt-get install fuseiso
$ mount -t fuse.fuseiso -o allow_other /var/cache/ubuntu-12.04-amd64.iso /mnt
or
$ fuseiso /var/cache/ubuntu-12.04-amd64.iso /mnt -o allow_other
bla bla ....
$ umount /mnt
or
$ fusermount -u /mnt
- allow_other 옵션이 없으면 다른 사용자는 접근할 수 없습니다.
- fuseiso 명령으로 마운트하면 fusemount -u로 해제해야합니다.
fuseiso를 사용하는 것이 훨씬 간단해 보입니다.
Vim Modeline
텍스트 파일을 편집하다 보면 아래와 비슷한 형식의 내용을 접한 적이 있을 것이다.
이것은 modeline이라 부르는 것으로 vimrc의 설정이 vim을 실행하면 바로 적용되는 것인 반면에, modeline은 파일에 적은 것으로 알 수 있듯이 파일 단위로 설정을 적용하는 것이다.
물론 filetype plugin을 통해서 파일 타입별로 셋팅을 맞출 수 있으나, 다른 사람과의 공동작업을 위해서는 다른 사람도 같은 설정 하에서 작업하기 위해서는 이 방법이 편한다.
# vim: ts=4 sw=4 tw=80 nu
python이라면 일반적으로 아래와 같이 modeline을 적을 것이다.
# vim: expandtab ts=4 sw=4 tw=80 nu
만일 C 소스라면 modeline은 아래와 같이 적을 수 있다.
/* vim: expandtab ts=4 sw=4 tw=80 nu */
그리고 이 설정은 modeline 옵션이 설정되어 있어야 한다. 따라서 안된다면 set modeline 으로 설정하거나 vimrc에 넣으면 된다. Ubuntu에서는 기본으로 꺼져있다.
ps. 이 글을 적는 이유는 이렇게 사용한다는 것은 알겠는데, 이게 modeline이라는 사실을 잃어버려 항상 검색할 때 키워드를 못찾아서 그런다. ㅎㅎ
Screen으로 여러 윈도우 한번에 열기
screen을 이용해 여러 윈도우를 한번에 열고 각각의 윈도우에서는 적당한 호스트를 연결하는 쉘 스크립트 입니다. 이전에 시도했던 것에서 많은 문제가 발생해서 사용하지 않고 있었으나 이것은 좀 쓸만합니다.
#!/bin/bash
SCREENNAME=stack
NL=`echo -ne '\015'`
if screen -ls | egrep -q "[0-9].$SCREENNAME"; then
echo "screen named $SCREEN_NAME already exists"
exit
fi
screen -d -m -S $SCREENNAME -t shell
sleep 1
host_spec="host1/192.168.0.10 host2/192.168.0.11"
for spec in $host_spec; do
name=`echo "$spec" | cut -d '/' -f 1`
host=`echo "$spec" | cut -d '/' -f 2`
screen -S $SCREENNAME -X screen -t "$name"
screen -S $SCREENNAME -p "$name" -X stuff "sshpass -ppassword ssh root@$host$NL"
done
echo "please run screen -r 'screen -r $SCREENNAME'"
물론 이것도 약간의 문제점이 있습니다. 명령을 실행하면 screen이 background에서 detach된 상태로 만들어 지는데, 이를 수동으로 attach해야하는 문제가 있습니다.
그래도 이전에 화면 깨지는 것보다는 훨씬 낫죠. ㅎㅎ
ps. 이 스크립트는 devstack의 stack.sh를 많이 참고했습니다.
Gluster With Chef #1
공부삼아서 Chef로 Gluster를 셋업하는 것(https://github.com/whitekid/chef-glusterfs) 해 봤는데, 역시나 공부삼아 코멘트 적어봅니다.
물론 당연히도 저는 Chef와 Ruby를 처음 다르기에 정석과는 많이 다를 수 있고, 여기에서 이야기하는 것들이 틀릴 수 도 있습니다. ㅎㅎ..
https://github.com/whitekid/chef-glusterfs 는 chef에서 사용하는 cookbook입니다. cookbook 즉 요리책은 형상관리할 대상을 어떻게 요리할 지 적어 놓은 것이죠. 이 cookbook안에 recipe가 있으며, 이 recipe를 작성하여 하나의 인프라를 관리합니다.
recipes 디렉토리를 보시면 client.rb, server.rb, default.rb 라는 파일이 있습니다. 이게 recipe이며, 이것들이 여러개 모여서 glusterfs 클라이언트와 서버를 관리하는 cookbook이 되는 것이죠. 이름에서 보이다시피 client.rb는 glusterfs 클라이언트를 설치하고 서버와 연결하여 마운트하는 recipe이고, server.rb는 glusterfs 클러스터를 구성하고 volume을 생성하는 recipe입니다.
우선 간단한 recipes/client.rb부터 봅니다.
package "glusterfs-fuse" do
action :install
end
여기서 “:install” 이 뭘까요?
저는 한참 고민했습니다. 엇듯 봐서는 method 같기도 하고, action이 method이면 :install은 parameter인 것 같기도 하고.. 처음에는 우선 려러니 하고 넘어갔었는데, 찾아보니 :install은 문자열 “install”의 symbol이라고 합니다. 문자열 reference죠.
irb> :install.equal? :install
=> true
로 나옵니다. 여기서 equals?는 값 비교가 아닌 object 비교, 즉 같은 메모리를 점유하고 있는 오브젝트인가를 검사하는 것인데 같이 나오는 으로 보면 같은 object이죠.
python의 immutable과 비슷하다고 봐야합니다. ruby symbol도 immutable입니다.
“Symbols are a way to represent strings and names in ruby.”
라 설명하고 있네요.
근데 그럼 action은 뭘까요? 이건.. attribute입니다. 즉 package 리소스의 action attribute를 “install”로 설정하는 것입니다.
package라는 리소스를 이용해서 gluster-fuse 패키지를 설치합니다. package는 각 chef node의 OS에 맞게 패키지를 설치합니다. 따라서 CentOS는 yum을 Ubuntu는 apt-get을 FreeBSD는 ports를 이용하여 설치를 합니다.
chef에서 사용되는 리소스는 opscode의 리소스 패이지(http://wiki.opscode.com/display/chef/Resources)를 보시기 바랍니다. 아주 자세히 설명되어 있습니다.
당연히 gluster-fuse는 CentOS에서 있는 패키지 이름입니다. 만일 Ubuntu를 사용한다면 다른 이름을 넣어주겠죠.
이렇게 각 노드의 특성에 따라서 달라지는 것들은 attribute에 설정합니다. 아마 attrbiute/default.rb 파일은 다음처럼 될 겁니다.
case platform do
when "ubutu", "debian"
default[:node][:gluster_client_package] = "gluster-client"
when "centos", "redhat"
default[:node][:gluster_client_package] = "gluster-fuse"
end
recipes는 아래처럼 사용합니다.
package node[:ntp][:gluster_client_package] do
action :install
end
근데 이런 node 특성에 따른 설정을 attribute에서만 꼭 둬야하냐? 라고 말하면 recipe에 둬도 됩니다. 편하실대로 하는는 것이 좋겠습니다. 예전에 잠깐 듣기론 recipe에 넣는 것이 더 편하는 이야기를 들은 것 같아요.
클라이언트 패키지를 설치했으므로 glusterfs 클러스터에 마운트합니다. glusterfs 클러스터는 server recipe에서 셋업을 진행했고 attributes/server.rb에서 설정한 peer의 첫번째로 마운트 하도록 하겠습니다.
node[:glusterfs][:client][:mount].each do |volume, mount_to| # ---- [1]
if not File.directory?(mount_to) # ---- [2]
directory mount_to do
action :create
recursive true
end
end
# mount -t glusterfs -o log-level=WARNING,log-file=/var/log/gluster.log \
# 10.200.1.11:/test /mnt
server = node[:glusterfs][:server][:peers][0]
mount mount_to do # ---- [3]
device "#{server}:/#{volume}"
options "log-level=WARNING,log-file=/var/log/gluster.log"
end
end
—-[1] glusterfs의 볼륨은 여러개 있을 수 있습니다. 따라서 각각의 볼륨에 대해서 glusterfs 마운트를 진행합니다. recipes/server.rb에 아래처럼 array로 정의되어 있습니다.
default[:glusterfs][:server][:volumes] = ["test"]
array의 each를 이용하여 모든 element를 돌면서 주어진 코드 블럭을 수행합니다. ruby 스타일의 언어를 처음 접하면 어색하겠지만, for문과 같다고 보시면 됩니다.
for volume, mount in node['glusterfs']['client']['mount']:
bla bla
python 스타일로 하면 위와 같은데.. python은 for 안의 실행하는 문장이 같은 for 문에 속하는 scope에 속하는 반면에, ruby에서는 do ~ end 가 코드 블럭이 되어서 each 함수의 파라미터로 전달이 되고 each 안에서 다시 호출되는 방식입니다. 아마도 javascript의 이벤트 핸들러를 작성해 봤다면 그것과 비슷하도고 보시면 됩니다.
—-[2] 마운트 포인트 디렉토리가 없는 경우는 새로 만들어 줍니다. 디렉토리 생성은 directory라는 리소스를 통해서 생성하며 :create action으로 수행됩니다.
—- [3] mount 리소스를 이용해서 gluster 서버로 마운트 합니다. 위의 주석으로 달린 명령과 동일한 과정을 수행합니다.
그리고 어찌보면 요상한 “#{server}:/#{volume}” 문자열이 나오는데, sh로 치면 “${server}:/${volume}”과 동일하며 python으로 치면 ”%s:/%s” % (server, volume)와 동일한 문자열 치환 입니다. python 스타일보다 훨씬 직관적으로 보기 좋습니다. 그렇다고 python “%{server}s:/%{volume}s” % locals() 가 있나고 하신다면.. 그래도 ruby style이 더 좋습니다..
음.. 대충 적으려고 계획했는데.. 글 재주도 없고 모르는 것 적으려니 이것저것 찾아보면서 해서 오래걸리네요.. 오늘은 이만하고 다음에 나머지 진행하겠습니다… 이거 참 하루 저녁 작업하고 설명하는데 몇일 걸리겠네요.. ㅡㅡ; 게다가 서버쪽은 더 복잡해 큰일군요.
귀찮다. Ssh 접속 키 확인 무시하자구!
테스트용으로 가상 머신을 많이 사용하다 보면… 가상 머신 안에서 IP가 중복됩니다. 지금 이 순간에도 제 VMWare에는 약 20개 정도가 생성되어있고, 8개가 running 중입니다.
이런 상황에서 귀찮은 것은 ssh로 접속할 때 ssh key fingerprint가 변경되었다느니… 서버의 핑커프린트를 저장할 것이라는둥 귀찮은 질문을 막 합니다. 귀찮아서 찾아봤더니 StrictHostsKeyChecking 옵션이 있군요. 이렇게 하면 간단하게 경고 메시지를 보이면서 서버 키를 무조건 저장합니다. 귀찮게 물어보는 것이 없어졌네요
$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null 10.200.1.10
Warning: Permanently added '10.200.1.10' (ECDSA) to the list of known hosts.
그리고 물론 .ssh/config 파일에 넣을 수 있습니다.
Host 10.200.*
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
물론 실제 환경에서는 핑커 프린트가 변경된 경우는 뭔가 이상이 있는 거니 이런거 쓰지 말기 바랍니다.
초간단 Chef 설치하기
-Ubuntu에서 Chef 초간단 설치하기 입니다.
About Chef
Chef는 1. 서버 형상 관리 툴 2. 서버 관리 자동화 툴 3. 요세는 클라우드 자동화 툴이라고도 합니다. puppet도 같은 비슷하죠.
Chef는 ruby로 작성되어 있으며, 작업하다보면 rails와 비슷하단 느낌을 많이 받습니다. 근데 저도 아직 잘 chef에 관해서 모릅니다. 공부하고 있는 단계죠. 어쨌든!
chef-server
레포지트리 설정
Ubuntu나 CentOS로 공식적으로 들어간 chef 패키지는 없고, 개발사인 opscode에서 관리하는 레포지트리를 사용합니다.
$ echo "deb http://apt.opscode.com/ `lsb_release -cs`-0.10 main" | sudo tee /etc/apt/sources.list.d/opscode.list
Repository PGP 키 추가
$ sudo mkdir -p /etc/apt/trusted.gpg.d
$ gpg -keyserver keys.gnupg.net -recv-keys 83EF826A
$ gpg -export packages@opscode.com | sudo tee /etc/apt/trusted.gpg.d/opscode-keyring.gpg > /dev/null
레포지트리 업데이트
$ sudo apt-get update
Chef 설치
$ sudo apt-get install chef-server
- 설치하면서 URL을 물어보는데 http://
:4040 으로 설정합니다. localhost로 설정하면 나중에 bootstrap으로 클라이언트 설치하는데 오류가 발생합니다.
knife 설정
knife는 chef를 움직이는 cli 툴입니다. 현재 계정(root가 아닌)에서 knife를 사용하여 명령을 내릴 수 있도록 설정합니다.
$ mkdir -p ~/.chef
$ sudo cp /etc/chef/validation.pem /etc/chef/webui.pem ~/.chef
$ sudo chown -R `whoami` ~/.chef
$ knife configuration -i
- 인증서의 위치는 위에서 복사한 ~/.chef의 경로로 입력
knife 설치 확인
$ knife client list
$ knife cookbook list
클라이언트 설치(precise)
chef bootstrap은 Ubuntu 10.04를 지원하지만, 12.04(precise)를 지원하지 않습니다. 하지만 간단히 그냥 lucid를 precise로 바꾸기만 하면 됩니다. 물론 lucid를 이용하는 경우는 기존 것 그대로 이용하면 됩니다.
$ cd /usr/lib/ruby/vendor_ruby/chef/knife/bootstrap
$ cp ubuntu10.04-apt.erb ubuntu12.04-apt.erb
$ sed -i ’s/lucid/precise/g’ ubuntu12.04-apt.erb
$ knife bootstrap 10.200.1.5 -d ubuntu12.04-apt -sudo -x
- 10.200.1.5: chef-client를 설치할 호스트 아이피
- -d: 배포판을 지정한다. lucid인 경우는 ubuntu10.04
- -sudo: sudo를 이용해서 명령을 실행한다.
- -x: ssh로 접속할 사용자를 지정한다. root 계정으로 직접 접근하지 못하는 서버들에 설정한다.
- -P: ssh password
- -bootstrap-version 0.10: bootstrap 설치할 버전
에러없이 잘 실행된다면
$ knife node list
로 해당 호스트가 등록된 것을 확인할 수 있습니다.
정상적으로 설치가 완료되었는데, 노드가 등록이 안되는 경우는 거의 대부분 시간이 안맞아서 그렇습니다. chef는 ampq를 사용하는데, 여기선 시간이 아주 중요하기 때문이지요. 시간이 안맞다면 ntpdate -u kr.pool.ntp.org 이렇게 시간이 중요하기 때문에 서버를 포함한 모든 노드는 ntpd를 설치하여 시간을 동기화하시기 바랍니다.
Cookbook 시작하기…
이제 Chef 설치는 잘 되었을 것이고, cookbook, knife등을 익힐 차례입니다. Cookbook Fast Start Guide에서 시작하기 바랍니다.
Ubuntu: Eth1에 기본 Gateway 두기..
eth0, eth1이렇게 2개 NIC가 있는 상황에서 거의 기본으로는 eth0에서 public internet이 나가도록 설정이 될 겁니다. 대부분 그렇지요. 그런데 필요에 따라서는 eth1가 public internet인 경우가 있습니다. 그리고 eth0는 다른 목적으로 사용되는 경우로 말입니다.
이 경우 CentOS의 경우는 그냥 아무 생각없이 /etc/sysconfig/network-script/ifcfg-eth1에 설정하면 eth0 설정한거 무시되면서 eth1의 설정이 먹히는 데 Ubuntu의 경우는 안그럽니다.
auto eth2
iface eth2 inet static
address 10.100.0.11
netmask 255.255.255.0
gateway 10.100.0.1
이렇게 두면 이녀석이 eth2의 gateway 설정을 알어먹지 못합니다. 그래서 수동으로 해 줘아합니다. 근데 수동으로하면 골치아프죠. 그럴땐 post-up, pre-down으로..
auto eth0
iface eth0 inet static
address 10.200.1.11
netmask 255.255.255.0
dns-nameservers 168.126.63.1
auto eth1
iface eth1 inet static
address 10.100.0.11
netmask 255.255.255.0
post-up route add default gw 10.100.0.1
pre-down route del default gw 10.100.0.1
이렇게 하면 되지요.
Shell Script에서 Python의 Range 같은것 쓰려면…
아주 간단함..
seq 1 10
또는
{1..10}
http://stackoverflow.com/questions/169511/how-do-i-iterate-over-a-range-of-numbers-in-bash