基于Docker的Rails集群+Ruby負載均衡代理
我打算做一個比較酷的使用Docker的例子:一個Rails集群,以及集群前面的一個Ruby負載均衡服務(wù)。我曾想過使用若干不同的機器(每臺機器都有自己的IP地址和端口)來運行同樣的Rails服務(wù),然后在外部配置一臺負載均衡的代理服務(wù)器,在各Railf服務(wù)之間進行請求分配。
不過使用Docker的話,在一臺機器上就可以模擬集群環(huán)境了。
這里我們將演示一下如何來創(chuàng)建這個集群環(huán)境。
你可以從我的Github項目上下載所有這次測試的源代碼。
創(chuàng)建Docker鏡像
首先我將創(chuàng)建兩個Docker鏡像,1個用來運行Rails服務(wù),1個運行Ruby代理服務(wù)。
1.首先我制作了一個可信Docker構(gòu)建(Trusted Docker build),構(gòu)建腳本也在我的Github上( Github project docker-ruby )。使用這個構(gòu)建腳本創(chuàng)建的鏡像為 murielsalvan/ruby ,它基于Ubuntu Precise,并且安裝了Ruby 2.1.0p0。
- docker pull murielsalvan/ruby
1.接著我基于murielsalvan/ruby來創(chuàng)建一個Docker容器,用來執(zhí)行一個bash shell,然后在里面安裝Rails并創(chuàng)建一個Rails應(yīng)用程序。這個應(yīng)用程序只有一頁,它將打印出它所在服務(wù)器的主機名和IP地址(集群里的每臺Rails服務(wù)都有不同的主機名和IP地址)。你可以從這里查看這個測試程序的源代碼。這步的***,我將把這個容器提交,生成一個新的鏡像,并命名為murielsalvan/server,這個鏡像執(zhí)行的時候,會啟動Rails服務(wù),并監(jiān)聽3000端口號(容器內(nèi)的端口號)。
- docker run -t -i murielsalvan/ruby bash
- docker commit -m=”Test server” -author=”Muriel Salvan <muriel@x-aeon.com>” -run='{“WorkingDir”: “/root/server/”, “Cmd”: ["rails", "s"], “PortSpecs”: ["3000"]}’ acf566f7d155 murielsalvan/server
1.***我們來基于murielsalvan/ruby創(chuàng)建第二個容器。這個容器也是啟動一個bash然后安裝一個Ruby代理服務(wù)(我用了 em-proxy,非常不錯的東西,值得一試)。這個Ruby代理服務(wù)將接收一個地址列表作為輸入?yún)?shù),并且創(chuàng)建一個基于隨機策略的負載均衡服務(wù),輪詢所有給定的IP地址以及3000端口。這個代理服務(wù)的源代碼可以在 這里 查看。***我把這個容器也做了提交操作,生成了一個新的鏡像 murielsalvan/proxy,它也將監(jiān)聽3000端口。
- docker run -t -i murielsalvan/ruby bash
- docker commit -m=”Proxy server” -author=”Muriel Salvan <muriel@x-aeon.com>” -run='{“PortSpecs”: ["3000"]}’ 7d2431c16b14 murielsalvan/proxy
運行Rails cluster
在所有的鏡像都創(chuàng)建完成之后,我們就可以啟動容器了。我寫了個小腳本來啟動所有需要運行Rails服務(wù)的容器,它接收一個N參數(shù),為Rails服務(wù)器個數(shù)。等Rails服務(wù)全部啟動完成后,這個腳本會打印出這些服務(wù)的IP地址列表。
這個腳本的另一個工作就是將運行Rails服務(wù)的容器內(nèi)的3000端口,綁定到本機的5000+i 端口上,這樣就可以非常方便的透過代理服務(wù)直接通過 wget -S -O – http://localhost:5000 命令來確認每臺Rails服務(wù)是否運行正常。
- # run_cluster.rb
- np_servers = ARGV[0].to_i
- pipes_in = {}
- np_servers.times do |idx|
- port = 5000 + idx
- pipe_cmd_in, pipe_cmd_out = IO.pipe
- cmd_pid = Process.spawn("docker run -p #{port}:3000 murielsalvan/server", :out => pipe_cmd_out, :err => pipe_cmd_out)
- puts "Launch server on port #{port}: PID=#{cmd_pid}"
- Process.detach(cmd_pid)
- pipe_cmd_out.close
- pipes_in[cmd_pid] = pipe_cmd_in
- end
- # Wait for all servers to be up
- pipes_in.each do |pid, pipe_in|
- puts "Waiting for PID #{pid} to be listening..."
- found_info = false
- while !found_info
- out = pipe_in.readline.chomp
- puts out
- found_info = out.match(/WEpick::HTTPServer/) != nil
- sleep 0.01 if !found_info
- end
- end
- puts 'All servers up and running.'
- # Get their IP addresses
- ips = []
- `docker ps | sed -e 's/^\\(............\\).*$/\\1/' | tail -#{np_servers}`.split("\n").each do |container_id|
- ips << `docker inspect #{container_id} | grep IPAddress | sed -e 's/.*: \\"\\(.*\\)\\".*/\\1/g'`.chomp
- end
- puts ips.join(' ')
這段代碼執(zhí)行后輸出結(jié)果如下所示,請注意***的那IP地址,這些地址是Rails服務(wù)所監(jiān)聽的IP地址,我們在后面的Ruby代理服務(wù)器中會使用到這些地址。
- > ruby -w run_cluster.rb 5
- Launch server on port 5000: PID=6559
- Launch server on port 5001: PID=6561
- Launch server on port 5002: PID=6565
- Launch server on port 5003: PID=6571
- Launch server on port 5004: PID=6573
- Waiting for PID 6559 to be listening...
- [2014-02-05 18:19:44] INFO WEpick 1.3.1
- [2014-02-05 18:19:44] INFO ruby 2.1.0 (2013-12-25) [x86_64-linux]
- [2014-02-05 18:19:44] INFO WEpick::HTTPServer#start: pid=1 port=3000
- Waiting for PID 6561 to be listening...
- [2014-02-05 18:19:42] INFO WEpick 1.3.1
- [2014-02-05 18:19:42] INFO ruby 2.1.0 (2013-12-25) [x86_64-linux]
- [2014-02-05 18:19:42] INFO WEpick::HTTPServer#start: pid=1 port=3000
- Waiting for PID 6565 to be listening...
- [2014-02-05 18:19:44] INFO WEpick 1.3.1
- [2014-02-05 18:19:44] INFO ruby 2.1.0 (2013-12-25) [x86_64-linux]
- [2014-02-05 18:19:44] INFO WEpick::HTTPServer#start: pid=1 port=3000
- Waiting for PID 6571 to be listening...
- [2014-02-05 18:19:43] INFO WEpick 1.3.1
- [2014-02-05 18:19:43] INFO ruby 2.1.0 (2013-12-25) [x86_64-linux]
- [2014-02-05 18:19:43] INFO WEpick::HTTPServer#start: pid=1 port=3000
- Waiting for PID 6573 to be listening...
- [2014-02-05 18:19:41] INFO WEpick 1.3.1
- [2014-02-05 18:19:41] INFO ruby 2.1.0 (2013-12-25) [x86_64-linux]
- [2014-02-05 18:19:41] INFO WEpick::HTTPServer#start: pid=1 port=3000
- All servers up and running.
- 172.17.0.16 172.17.0.14 172.17.0.15 172.17.0.13 172.17.0.12
啟動Ruby代理
同樣,我們在這里也通過一段Ruby代碼來完成啟動代理服務(wù)器的工作:
- 同樣,我們在這里也通過一段Ruby代碼來完成啟動代理服務(wù)器的工作:
- #run_proxy.rb
- lst_ips = ARGV.cloneProcess.wait(Process.spawn("docker run -p 3000:3000 -t murielsalvan/proxy ruby -w /root/run_proxy.rb #{lst_ips.join(' ')}"))
這段代碼將啟動運行Ruby代理服務(wù)的Docker容器,其執(zhí)行結(jié)果如下:
- > ruby -w run_proxy.rb 172.17.0.16 172.17.0.14 172.17.0.15 172.17.0.13 172.17.0.12
- /root/run_proxy.rb:149: warning: `&' interpreted as argument prefix
- /root/run_proxy.rb:150: warning: `&' interpreted as argument prefix
- /root/run_proxy.rb:151: warning: `&' interpreted as argument prefix
- /root/run_proxy.rb:152: warning: `&' interpreted as argument prefix
- /usr/local/lib/ruby/gems/2.1.0/gems/em-proxy-0.1.8/lib/em-proxy/backend.rb:37: warning: method redefined; discarding old debug
- /usr/local/lib/ruby/gems/2.1.0/gems/em-proxy-0.1.8/lib/em-proxy/connection.rb:126: warning: method redefined; discarding old debug
- /root/run_proxy.rb:168: warning: method redefined; discarding old stop
- /usr/local/lib/ruby/gems/2.1.0/gems/em-proxy-0.1.8/lib/em-proxy/proxy.rb:17: warning: previous definition of stop was here
- Launching proxy at 0.0.0.0:3000...
這將啟動Ruby代理服務(wù)程序,并且監(jiān)聽3000端口。(請先忽略 上面的警告信息吧,也許em-proxy需要做一些清理操作吧 :-))
打開瀏覽器
服務(wù)程序都啟動之后,就可以打開瀏覽器訪問了。輸入網(wǎng)址 http://localhost:3000,可以確認返回結(jié)果里的主機名和IP地址。為了確保每次請求都會發(fā)給代理服務(wù)來處理,要在每次請求這個網(wǎng)址的時候先清空一下緩存(強制刷新)。
下面的圖是兩次請求的結(jié)果,從中我們可以看出,兩次請求是分別由兩臺不同的Rails服務(wù)器返回的。
刷新瀏覽器之后:
怎樣,是不是很簡單的一種Rails集群方案?你也可以在自己的機器上嘗試一下。
我個人的測試環(huán)境如下:64位的Windows 7 主機,然后通過VirtualBox運行了Ubuntu 14.04 Alpha,所有的操作都在虛擬機里進行。性能也還算說的過去(每個請求的響應(yīng)時間都低于1秒)。
原文出自:https://docker.cn/p/rails-cluster-with-ruby-load-balancer-using-docker-zh