Tag: chef

chef-client using lots of memory

테스트하는 서버들이 뭔가 이상하게 느려졌다. 뭔가하면서 찬찬히 둘러보는데.. 허걱 service로 돌아가는 chef-client process가 메모릴 몇G 단위로 잡아먹고 있는 것이다. 아주 정상적이지 않은 상황이다. 테스트 서버는 KVM에서 가상머신으로 돌아가고 있고, 게다가 메모리도 1G만 할당한 상황이라, 한 프로세스가 1G를 잡아먹는, 게다가 configuration management가 말이지.. 헐..

그래서 찾아보니 역시나 알려진 문제였고, service로 돌리는 경우에는 -f 옵션으로 fork해서 실행하면 된다.

chef로 chef-client를 service로 돌린 경우에는 아래처럼 environment에 넣으면된다.

override_attributes(
    :chef_client => {
        :server_url => "https://chef.stack",
        :daemon_options => ['-f'],
    },
    .....
)

update) 약간 된 버전의 경우 fork bomb 버그가 있습니다. 11.4.4 버전 이상으로 업그레이드 하세요.


chef: definition과 notifies

Chef Definition은 아주 간단하게 Resource를 만들 수 있습니다. 아래 코드를 보면 간단하게 ~/.ssh/authorized_keys를 설정하는 Resource를 만들 수 있지요

define :authorized_keys_for, :keys => nil, :group => nil, :home => nil do

  user  = params[:name]
  group = params[:group] || user
  home  = params[:home]  || "/home/#{user}"
  keys  = params[:keys].kind_of?(String) ? [params[:keys]] : params[:keys]

  if not keys.nil? and not keys.empty?
    directory "#{home}/.ssh" do
      owner user
      group group
      mode 0700
      action :create
      only_if "test -d #{home}"
    end

    file "#{home}/.ssh/authorized_keys" doㅁㄴ
      owner user
      group group
      content keys.map{ |x| x.strip }.join("\n")
    end
  end
end

뭐.. 간단한 일이면 이런 거 사용하면 됩니다. 저도 별 무리 없이 사용하고 있다가.. 여기서 notifies를 걸여줘야할 것이 생겼습니다. authorized_keys를 설정한 이후에 뭔가를 하려는 것이지요. 그래서 뭐 그냥 단순하게.. 아래처럼 했습니다...

authorized_keys_for charlie do
  keys ''
  notifies :create "ruby_bloc[foo]"
end

ruby_block "foo" do
  block do
    # do something with ruby
  end
  action :nothing
end

예.. 예상하시는데로.. 아무런 문제 없이 잘 될거라고 생각했지만, 역시나 안되었습니다(그러니까 이 글을 적고 있겠지요). 뭘까 뭘까? 하면서 다시 위 링크에 있는 문서를 다시 봤습니다.

A definition is not a resource or a lightweight resource

녜.. definition은 resource가 아닙니다. notifies나 subscribes는 resource에만 동작합니다. 그러니 아무리해도 안되지요... definition은 진짜로 C언어의 #define 처럼 동작하는 녀석인가 봅니다. ㅎㅎ


chef: role에서 environment에 따라서 run_list 다르게 지정하기

chef를 사용하다보면 environment에 따라서 다른 run_list가 필요한 경우가 있습니다.  이를테면 개발 환경의 서버는 개발자를 위한 편의 기능들을 미리 집어넣는 경우지요. 아마도 개발머신에서는 개발자의 public_key를 모두 등록해서 개발 머신에는 각자의 키로 접속할 수 있고, production에서는 그 기능을 빼는 경우죠.

role에서는 이런 경우를 위해서 env_run_lists를 이용하여 기능을 재공하고 있습니다.

roles/base.rb

name "base"
description "base role for all node"

default_roles = [
	"recipe[chef-client::service]", "recipe[chef-client::config]", "recipe[ntp]",
]

run_list default_roles
env_run_lists	"_default" => default_roles,
				"devel"      => default_roles + ["recipe[my_cookbook::developers_public_keys]"]

굳이 설명 안해도 코드만 보고 다 알겠죠? ㅎㅎ


chef: search node role vs search node roles

knife를 이용해 특정 role을 가지는 role을 검색하려면 아래처럼 합니다.

$ knife search node role:openstack-compute

그런데 다른 소스를 뒤져보다 아래와 같이 queury하는 것을 봤습니다.

$ knife search node roles:openstack-compute

뭘까? 하고 그냥 지나치다가 찾아봤죠.. Find Nodes with a Role in the Expanded Run List라고 부르는군요.

간단히 예를 들면

  • nodeA: role-A
  • nodeB: role-B
  • role-A: recipe[A], role[base]
  • role-B: recipe[B], role[base]

이라고 되어있을 경우

$ knife search node role:base

로 할 경우 role에 직접적으로 base가 지정되어 있지 않기 때문에, 검색결과가 없습니다.

$ knife search nodes role:base

로 할 경우는 role-A를 확장하여 role-A, recipe[A], role[base]를 대상으로 찾기 때문에 nodeA, nodeB가 모두 검색이 됩니다.

또한 하나의 side-effect로 role이 expand되는 때는 실제로 chef-client가 실행하면서죠.. 따라서 chef-client가 정상적으로 실행되고 나서야 roles로 검색이 가능합니다. 즉 roles로 검색된 결과는 해당 role로 할당된 노드가 있고, chef-client 정상적으로 cookbook을 실행했다는 의미죠.


github에서 패치 가져와 적용하기 + chef

오늘 작업하다 보니 OpenStack에 몇가지 버그가 있었습니다. 버그 있는 것을 확인하고 퇴근했는데, 마침 메일링 리스트에 관련 이야기가 나오더니 커밋 링크(https://github.com/openstack/quantum/commit/84d60f5fd477237bd856b97b9970dd796b10647e)를 알려줍니다.

이걸 지금 chef로 작업하고 있는 것에 적용 시키기로 했지요.

package quantum-l3-agent

# apply l3 agent bug fix patch
execute "apply fetch" do
    action :nothing

    command "wget -O /dev/stdout -q https://github.com/openstack/quantum/commit/84d60f5fd477237bd856b97b9970dd796b10647e.patch | \
             patch -p1"
    cwd "/usr/lib/python2.7/dist-packages"

    subscribes :run, "package[quantum-l3-agent]", :immediately
end

  1. github에서 커밋 아이디를 가져오는 것은 커밋 링크에 .patch를 붙여준다.
  2. patch는 관련 패키지를 설치할 경우만 필요하기 때문에 action :nothing을 주고 subscribe로 해당 패키지가 설치되었을 때 패치를 진행한다.
  3. 소스로 작업했으면 git apply로 할텐데.. 패키지로 작업하기 때문에 patch -p1으로..

chef data_bag/ cookbook upload script

chef로 작업하다보면 참 불편한 것이 있다.

$ knife cookbook upload -a -o cookbook           # ---- [1]
$ knife data bag from file hosts default.json    # ---- [2]

#1은 cookbook을 업로드하고 #2는 data bag을 업로드한다. 그런데 여기서 문제는 이 명령을 실행하는 디렉토리가 중요하다는 것이다. cookbook을 업로드 할 때는 -o로 cookbook 디렉토리를 지정해줘야하고, data bag을 업로드할 때는 chef-repository의 root에서 실행해야하는 것이다.

작업하다보면 여기저기 디렉토리를 이동하게 되는데, 그에 따라서 명령이 다르단 말이지.. 이거 귀찮아서 간단히 스크립트 만들었다. 원리는 간단하다. chef-repository의 root 디레토리에는 .chef 디렉토리에서 해당 레포지트리의 chef-server를 관리하는데, 이 디렉토리를 찾아서 해당 명령을 수행하는 것이다. 단순히 아래처럼 하면 된다.

$ kup.sh
$ bag.sh

이러면 자동으로 chef-repository root 디렉토리에서 cookbook, data bag upload를 수행한다.

kup.sh

#!/bin/bash
# upload all cookbooks
set -e

base_dir=`dirname $0`
if [ -f $base_dir/cheflib.sh ]; then
    source $base_dir/cheflib.sh
fi

chef_dir=$(find_chef_repo)

knife cookbook upload -a -o $chef_dir/cookbooks

bag.sh

#!/bin/bash
# upload all data bags

set -e

base_dir=`dirname $0`
if [ -f $base_dir/cheflib.sh ]; then
    source $base_dir/cheflib.sh
fi

chef_dir=$(find_chef_repo)

cd $chef_dir

for x in `(cd data_bags; find . -name '*.json' -type f)`; do
    bag=$(echo $x | cut -d / -f 2)
    item=$(echo $x | cut -d / -f 3)

    knife data bag from file $bag $item
done

cheflib.sh

#!/bin/bash

function fatal() {
    echo $@
    exit
}

function find_chef_repo() {
    pushd `pwd` > /dev/null

    # find .chef directory
    until [ -d .chef ]; do
        [ $(pwd) = '/' ] && break
        cd ..
    done

    [ `pwd` = '/' ] && fatal "Can't find chef repository"

    echo `pwd`
    popd > /dev/null
}

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이 더 좋습니다..

음.. 대충 적으려고 계획했는데.. 글 재주도 없고 모르는 것 적으려니 이것저것 찾아보면서 해서 오래걸리네요.. 오늘은 이만하고 다음에 나머지 진행하겠습니다... 이거 참 하루 저녁 작업하고 설명하는데 몇일 걸리겠네요.. ㅡㅡ; 게다가 서버쪽은 더 복잡해 큰일군요.


초간단 Chef 설치하기

[toc]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://<ip-address>: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 <user_id> -P <password> --bootstrap-version 0.10
  • 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에서 시작하기 바랍니다.

http://wiki.woosum.net/Chef


  • Copyright © 1996-2010 Your wish is my command. All rights reserved.
    iDream theme by Templates Next | Powered by WordPress