cf通过chef可以部署单节点实例。只需要按照http://support.cloudfoundry.com/entries/20407923-single-multi-node-vcap-deployment-using-dev-setup的介绍即可安装。本文就尝试着分析一下整个部署的流程,同时也对chef进行一些学习。
好了,首先从最外部的脚本进入,就是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
然后使用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权限,这个自己加上去就好了
{ "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了
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。
{ "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文件。
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
%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。
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系统即可。