cf通过chef可以部署单节点实例。只需要按照http://support.cloudfoundry.com/entries/20407923-single-multi-node-vcap-deployment-using-dev-setup的介绍即可安装。本文就尝试着分析一下整个部署的流程,同时也对chef进行一些学习。
vcap_dev_setup
好了,首先从最外部的脚本进入,就是bin/vcap_dev_setup。这是shell脚本,前面的内容全部忽略,到run_cmd apt-get $APT_CONFIG install -qym wget这一行开始,这里在安装wget,然后check下网络,关系不大。
然后开始安装chef,这里需要拿到一个key,如果失败,可以参看http://wiki.opscode.com/display/chef/Installing+Chef+Client+on+Ubuntu+or+Debian获取key。得到key之后update apt-get然后安装chef。
接下来会安装git,然后去github把最新版本的代码全部update下来。我们可以看代码:
cd $CLOUDFOUNDRY_HOME && git clone $VCAP_REPO && cd vcap && git submodule update --init && git checkout $VCAP_REPO_BRANCH
就是这句,如果想看所有源码,就可以通过这些命令获得源码,如果单纯只是git clone的话,有些代码是没有的,比如services、uaa等都是没有的。当然在update之前,脚本会查看目标目录是否已经有vcap这个目录,如果有了,就不会再update了,所以如果有人已经安装完毕了,就可以直接把vcap目录拷贝过来,这样可以节省不少时间。
然后使用gem安装了rake,根据其comments里面所讲: Our deployment code needs this gem. Using bundler seems like an overkill for the deployment code. So for now just manually install the required gem。也不是太懂,意会好了。
然后就调用chefsolo_launch.rb执行接下来的操作了。这里遇到过一个permission denied,因为我们代码从svn check下来,结果没有x权限,这个自己加上去就好了
chefsolo_launch.rb
- cloudfoundry_home: /root/cloudfoundry
- cloudfoundry_domain: vcap.me
- deployment_spec: /root/cloudfoundry/.deployment/devbox/devbox.yml
- deployment_config_path: /root/cloudfoundry/.deployment/devbox/config
{
"cloudfoundry":{
"home":"/root/cloudfoundry"
},
"deployment":{
"user":"root",
"name":"devbox",
"domain":"vcap.me",
"group":0
},
"run_list":[
"role[nats_server]",
"role[cloudfoundry]",
"role[router]",
"role[ccdb]",
"role[cloud_controller]",
"role[health_manager]",
"role[dea]",
"role[uaa]",
"role[uaadb]",
"role[redis_node]",
"role[mysql_node]",
"role[mongodb_node]",
"role[neo4j_node]",
"role[rabbitmq_node]",
"role[memcached_node]",
"role[redis_gateway]",
"role[mysql_gateway]",
"role[mongodb_gateway]",
"role[neo4j_gateway]",
"role[rabbitmq_gateway]",
"role[memcached_gateway]"
],
"jobs":{
"installed":null,
"install":{
"all":null
}
}
}
这个文件也比较简单,通俗易懂。唯一值得一提的是这个run list,它包含了所有需要安装的role(这个在下面的chef solo中会介绍,目前就认为是一个job即可),同时是根据依赖规则排列的,也就是被依赖的在前面。所以chef安装的时候会从上到下依次执行。比如先安装nats,然后是cloudfoundry。
exec("sudo env #{proxy_env.join(" ")} chef-solo -c #{File.join(tmpdir, "solo.rb")} -j #{json_attribs} -l #{chef_log_level}")
那么接下来所有的任务就是chef-solo了
Chef solo
那么recipes就是脚本了,也就是chef需要执行的一些脚本,也就是本cookbook所有需要做的行为都在recipe里面要写明。
attributes顾名思义就是一些属性定义,我们可以在deployment的cookbook的attributes中看到这样的代码:default[:deployment][:log_path] = File.join(deployment[:home], "log"),这个属性在recipe里面引用就会使用node[:deployment][:log_path]。这里有一个变量:ENV["HOME"],表示的是~目录,也就是HOME目录。
templates是一个比较有意思的东西,它里面是一些erb文件,我们先不看这些erb文件,因为会看不懂的。我们直接看使用的地方,也就是recipe里面的template块。举个例子:
template "nats_server" do
path File.join("", "etc", "init.d", "nats_server")
source "nats_server.erb"
owner node[:deployment][:user]
mode 0755
notifies :restart, "service[nats_server]"
end
这个里的意思是这个template名字叫做nats_server,这个名字其实随便叫的,然后有一个path,是/etc/init.d/nats_server,你会发现:这不就是服务的文件吗?对的!然后看到source就是一个nats_server.erb文件,在template文件夹下面。后面的大致也能看懂,notifies应该就是消息通知,但是我暂时没找到说明文档,不懂。好了,现在再看erb文件,就可以发现,它很像server文件,其实它就是init.d下面的nats_server文件,只不过,里面某些参数使用了chef中的attributes来表示罢了。其他的template基本也如此,也就是说template是用来表示一个文件内容的,专业点讲叫做render。
provider:个人感觉cf中的使用和官方文档的说法不太一样,这里似乎就是提供了一些函数,给recipes调用而已。
resources:chef官网的说法就是work的basic unit,也是为recipe所用的
在CF中,chef会根据上文提到的run list的顺序依次安装执行。但是run list中提到的是role,而不是recipe,role其实是许多recipes的合集,一个role就是一个抽象的概念,每个node(就认为是一台机器好了)可以有多个role。其实挺像Java的interface的,每个接口定义了一个抽象的功能,一个类可以有多个接口。知道了role之后再看role的相关文件在哪里,因为总得有地方说明role要做些什么行为的。这个文件就在dev_setup\roles目录下。可以看到许多的json文件,以nats_server.json为例:
{
"name": "nats_server",
"default_attributes": {},
"override_attributes": {},
"json_class": "Chef::Role",
"description": "NATS message bus server",
"chef_type": "role",
"run_list" : [ "recipe[deployment]",
"recipe[essentials]",
"recipe[ruby]",
"recipe[nats_server]" ]
}
上面一些参数关系也不大,主要是run_list,我们可以看到它需要四个recipe依次执行。也就是四个对应cookbook下的recipe文件。
下面来看看recipe的实例吧,首先可以看看deployment的recipe:
node[:nats_server][:host] ||= cf_local_ip
node[:ccdb][:host] ||= cf_local_ip
node[:acmdb][:host] ||= cf_local_ip
node[:uaadb][:host] ||= cf_local_ip
node[:postgresql][:host] ||= cf_local_ip
[
node[:deployment][:home], File.join(node[:deployment][:home], "deploy"),
node[:deployment][:log_path], File.join(node[:deployment][:home], "sys", "log"),
node[:deployment][:config_path],
File.join(node[:deployment][:config_path], "staging"),
node[:deployment][:setup_cache],
].each do |dir|
directory dir do
owner node[:deployment][:user]
group node[:deployment][:group]
mode "0755"
recursive true
action :create
end
end
var_vcap = File.join("", "var", "vcap")
[var_vcap, File.join(var_vcap, "sys"), File.join(var_vcap, "db"), File.join(var_vcap, "services"),
File.join(var_vcap, "data"), File.join(var_vcap, "data", "cloud_controller"),
File.join(var_vcap, "sys", "log"), File.join(var_vcap, "sys", "run"), File.join(var_vcap, "data", "cloud_controller", "tmp"),
File.join(var_vcap, "data", "cloud_controller", "staging"),
File.join(var_vcap, "data", "db"), File.join("", "var", "vcap.local"),
File.join("", "var", "vcap.local", "staging")].each do |dir|
directory dir do
owner node[:deployment][:user]
group node[:deployment][:group]
mode "0755"
recursive true
action :create
end
end
template node[:deployment][:info_file] do
path node[:deployment][:info_file]
source "deployment_info.json.erb"
owner node[:deployment][:user]
mode 0644
variables({
:name => node[:deployment][:name],
:ruby_bin_dir => File.join(node[:ruby][:path], "bin"),
:maven_bin_dir => File.join(node[:maven][:path], "bin"),
:cloudfoundry_path => node[:cloudfoundry][:path],
:deployment_log_path => node[:deployment][:log_path]
})
end
file node[:deployment][:local_run_profile] do
owner node[:deployment][:user]
group node[:deployment][:group]
content <<-EOH
export PATH=#{node[:ruby][:path]}/bin:`#{node[:ruby][:path]}/bin/gem env gempath`/bin:#{node[:maven][:path]}/bin:$PATH
export CLOUD_FOUNDRY_CONFIG_PATH=#{node[:deployment][:config_path]}
EOH
end
最上面在赋一些参数,不理他,然后创建了deployment所需要许多许多目录,然后可以看到一个template,我们看名字可以知道是要给一个文件写一些值进去,因为source的文件就是一个简单的json文件。通过查找,可以找到这个文件就是:....../.deployment/devbox/config/deployment_info.json。这个路径定义在attributes里面。最后是写一个file,里面的content就是两句export。这个文件的路径也可以查到,是:/root/.cloudfoundry_deployment_local
再看一个recipe,这个是essential的recipe
%w{apt-utils build-essential libssl-dev
libxml2 libxml2-dev libxslt1.1 libxslt1-dev git-core sqlite3 libsqlite3-ruby
libsqlite3-dev unzip zip ruby-dev libmysql-ruby libmysqlclient-dev libcurl4-openssl-dev libpq-dev}.each do |p|
package p do
action [:install]
end
end
if node[:deployment][:profile]
file node[:deployment][:profile] do
owner node[:deployment][:user]
group node[:deployment][:group]
content "export PATH=#{node[:ruby][:path]}/bin:`#{node[:ruby][:path]}/bin/gem env gempath`/bin:$PATH"
end
end
这个和上一个有点不同了,第一部分在做install,我们可以看到许多的pkg需要安装,这些pkg其实可以在dev setup的时候看到他们的身影。第二部分不难,就是写了一个profile。
最后看一个实际组件的recipe,就选择cloudcontroller好了:
template node[:cloud_controller][:config_file] do
path File.join(node[:deployment][:config_path], node[:cloud_controller][:config_file])
source "cloud_controller.yml.erb"
owner node[:deployment][:user]
mode 0644
builtin_services = []
case node[:cloud_controller][:builtin_services]
when Array
builtin_services = node[:cloud_controller][:builtin_services]
when Hash
builtin_services = node[:cloud_controller][:builtin_services].keys
when String
builtin_services = node[:cloud_controller][:builtin_services].split(" ")
else
Chef::Log.info("Input error: Please specify cloud_controller builtin_services as a list, it has an unsupported type #{node[:cloud_controller][:builtin_services].class}")
exit 1
end
variables({
:builtin_services => builtin_services
})
end
cf_bundle_install(File.expand_path(File.join(node["cloudfoundry"]["path"], "cloud_controller")))
staging_dir = File.join(node[:deployment][:config_path], "staging")
node[:cloud_controller][:staging].each_pair do |framework, config|
template config do
path File.join(staging_dir, config)
source "#{config}.erb"
owner node[:deployment][:user]
mode 0644
end
end
首先向config文件写下一个cc的配置文件cloud_controller.yml,然后呢有一个cf_bundle_install函数,这个函数在哪里呢?我们可以去cloudfoundry的cookbook的libraries里面找到它,因为cloudfoundry的cookbook的recipe会在cc的recipe之前执行,所以这个函数是找得到的。这个函数的代码是:
def cf_bundle_install(path)
bash "Bundle install for #{path}" do
cwd path
user node[:deployment][:user]
code "#{File.join(node[:ruby][:path], "bin", "bundle")} install"
only_if { ::File.exist?(File.join(path, 'Gemfile')) }
end
end
可以看到就是去vcap目录下面找cloud controller然后,使用bundle安装那个Ruby on Rails系统即可。
好了,至此已经把dev setup所有的工作都研究清楚了,中间还有一些细节需要深入一下,不过大致的概念已经比较清楚了。