前面的文章已經介紹了整個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的整個實現還是相對來說比較簡單的了。。。。