前面的文章已经介绍了整个cloud foundry的源码的启动过程,这篇文章介绍一下router方面的细节,毕竟外界访问cloud foundry的入口就是router。。
首先来看router的启动:
- /home/fjs/cloudfoundry/vcap/router/bin/router -c /home/fjs/cloudfoundry/.deployments/devbox/config/router.yml
- config_path = ENV["CLOUD_FOUNDRY_CONFIG_PATH"] || File.join(File.dirname(__FILE__), '../config')
- config_file = File.join(config_path, 'router.yml')
- port, inet = nil, nil
- options = OptionParser.new do |opts|
- opts.banner = 'Usage: router [OPTIONS]'
- opts.on("-p", "--port [ARG]", "Network port") do |opt|
- port = opt.to_i
- end
- opts.on("-i", "--interface [ARG]", "Network Interface") do |opt|
- inet = opt
- end
- opts.on("-c", "--config [ARG]", "Configuration File") do |opt|
- config_file = opt #/home/fjs/cloudfoudnry/.deployments/devbox/config/router.yml
- end
- opts.on("-h", "--help", "Help") do
- puts opts
- exit
- end
- end
- options.parse!(ARGV.dup)
- begin
- config = File.open(config_file) do |f| #读取配置文件
- YAML.load(f)
- end
- rescue => e
- puts "Could not read configuration file: #{e}"
- exit
- end
- # Placeholder for Component reporting
- config['config_file'] = File.expand_path(config_file) #/home/fjs/cloudfoudnry/.deployments/devbox/config/router.yml
- port = config['port'] unless port
- inet = config['inet'] unless inet
然后会启动EVENTMACHINE,进行真正的启动。。。
在代码之前,先介绍一下router的大体设计。。。
router中会集成一个简单的服务器,通过Sinatra开发的,然后还会集成一个nginx服务器,外界的访问首先是到达nginx服务器,然后nginx会调用lua脚本,生成http请求发送到router自己的服务器,然后router会通过外界访问的host的值来找到相应的app的ip+port地址(指向对应的dea),然后再返回,然后nginx再代理到返回的地址就好了。。。
这样也就是先了router的功能。。。。类似于如下:
好了,大体的设计已经介绍完了,接下来进入代码吧:
- Router.server = Thin::Server.new(inet, port, RouterULSServer, :signals => false) if inet && port #这个一般情况下不会使用
- Router.local_server = Thin::Server.new(fn, RouterULSServer, :signals => false) if fn #创建router的服务器,用来与nginx进行交互
- Router.server.start if Router.server #启动服务器
- Router.local_server.start if Router.local_server
接下来是订阅一些消息,例如有新的app上线了,那么router需要登记它的名字和ip+port地址,
- Router.setup_listeners #主要是订阅一些nats的消息
- def setup_listeners
- #订阅app的注册消息
- NATS.subscribe('router.register') { |msg|
- msg_hash = Yajl::Parser.parse(msg, :symbolize_keys => true)
- return unless uris = msg_hash[:uris]
- uris.each { |uri| register_droplet(uri, msg_hash[:host], msg_hash[:port],
- msg_hash[:tags], msg_hash[:app]) }
- }
- #订阅一些app解注册的消息
- NATS.subscribe('router.unregister') { |msg|
- msg_hash = Yajl::Parser.parse(msg, :symbolize_keys => true)
- return unless uris = msg_hash[:uris]
- uris.each { |uri| unregister_droplet(uri, msg_hash[:host], msg_hash[:port]) }
- }
- end
接下来的代码是:
- @hello_message = { :id => @router_id, :version => Router::VERSION }.to_json.freeze
- # This will check on the state of the registered urls, do maintenance, etc..
- Router.setup_sweepers
- # Setup a start sweeper to make sure we have a consistent view of the world.
- EM.next_tick do
- # Announce our existence
- NATS.publish('router.start', @hello_message)
- # Don't let the messages pile up if we are in a reconnecting state
- EM.add_periodic_timer(START_SWEEPER) do
- unless NATS.client.reconnecting?
- NATS.publish('router.start', @hello_message)
- end
- end
- end
然后又会设置周期函数,用于广播当前router的一些基本信息。。。。
然后我们来看router自己的服务器。。。。
- get "/" do
- uls_response = {}
- VCAP::Component.varz[:requests] += 1
- # Get request body
- request.body.rewind # in case someone already read the body
- body = request.body.read #{"host":"fjs.vcap.me","stats":[{"response_latency":0,"request_tags":"BAh7BjoOY29tcG9uZW50SSIUQ2xvdWRDb250cm9sbGVyBjoGRVQ=","response_codes":{"responses_3xx":1},"response_samples":1}]}
- Router.log.debug "Request body: #{body}"
- # Parse request body
- uls_req = JSON.parse(body, :symbolize_keys => true)
- raise ParserError if uls_req.nil? || !uls_req.is_a?(Hash)
- stats, url = uls_req[ULS_STATS_UPDATE], uls_req[ULS_HOST_QUERY] #url为当前app的http的请求的header的host字段的值
- sticky = uls_req[ULS_STICKY_SESSION]
- if stats then
- update_uls_stats(stats)
- end
- if url then
- # Lookup a droplet
- unless droplets = Router.lookup_droplet(url)
- Router.log.debug "No droplet registered for #{url}"
- raise Sinatra::NotFound
- end
- # Pick a droplet based on original backend addr or pick a droplet randomly
- #这里是为了区分instance的session
- if sticky
- _, host, port = Router.decrypt_session_cookie(sticky)
- droplet = check_original_droplet(droplets, host, port)
- end
- droplet ||= droplets[rand*droplets.size]
- Router.log.debug "Routing #{droplet[:url]} to #{droplet[:host]}:#{droplet[:port]}"
- # Update droplet stats
- update_droplet_stats(droplet)
- # Update active apps
- Router.add_active_app(droplet[:app]) if droplet[:app]
- # Get session cookie for droplet
- new_sticky = Router.get_session_cookie(droplet)
- uls_req_tags = Base64.encode64(Marshal.dump(droplet[:tags])).strip
- uls_response = {
- ULS_STICKY_SESSION => new_sticky,
- ULS_BACKEND_ADDR => "#{droplet[:host]}:#{droplet[:port]}",
- ULS_REQUEST_TAGS => uls_req_tags,
- ULS_ROUTER_IP => Router.inet,
- ULS_APP_ID => droplet[:app] || 0,
- }
- end
- uls_response.to_json
- end
另外还剩下的就是lua脚本和nginx的配置方面的东西了,其实很简单,稍微看看就能明白。。。
这样router的主要的东西就讲完了。。。通过这几天看cloud foundry的源码,发现其实cloud foundry的整个实现还是相对来说比较简单的了。。。。