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
- }
- }
- }
- exec("sudo env #{proxy_env.join(" ")} chef-solo -c #{File.join(tmpdir, "solo.rb")} -j #{json_attribs} -l #{chef_log_level}")
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
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]" ]
- }
下面来看看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
再看一个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
最后看一个实际组件的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
- 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
好了,至此已经把dev setup所有的工作都研究清楚了,中间还有一些细节需要深入一下,不过大致的概念已经比较清楚了