24.6 Compose应用案例一:Web负载均衡

负载均衡器+Web应用是十分经典的应用结构。下面,笔者将创建一个该结构的Web项目:将Haproxy作为负载均衡器,后端挂载三个Web容器。

首先创建一个haproxy_web目录,作为项目工作目录,并在其中分别创建两个子目录:web和haproxy。

1.web子目录

在web子目录下将放置所需Web应用代码和Dockerfile,下面将生成需要的Web镜像。

这里用Python程序来实现一个简单的Web应用,该应用能响应HTTP请求,返回的页面将打印出访问者的IP和响应请求的后端容器的IP。

(1)index.py

编写一个index.py作为服务器文件,代码为:


  1. #!/usr/bin/python
  2. #authors: yeasy.github.com
  3. #date: 2013-07-05
  4. import sys
  5. import BaseHTTPServer
  6. from SimpleHTTPServer import SimpleHTTPRequestHandler
  7. import socket
  8. import fcntl
  9. import struct
  10. import pickle
  11. from datetime import datetime
  12. from collections import OrderedDict
  13. class HandlerClass(SimpleHTTPRequestHandler):
  14. def get_ip_address(self,ifname):
  15. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  16. return socket.inet_ntoa(fcntl.ioctl(
  17. s.fileno(),
  18. 0x8915, # SIOCGIFADDR
  19. struct.pack('256s', ifname[:15])
  20. )[20:24])
  21. def log_message(self, format, *args):
  22. if len(args) < 3 or "200" not in args[1]:
  23. return
  24. try:
  25. request = pickle.load(open("pickle_data.txt","r"))
  26. except:
  27. request=OrderedDict()
  28. time_now = datetime.now()
  29. ts = time_now.strftime('%Y-%m-%d %H:%M:%S')
  30. server = self.get_ip_address('eth0')
  31. host=self.address_string()
  32. addr_pair = (host,server)
  33. if addr_pair not in request:
  34. request[addr_pair]=[1,ts]
  35. else:
  36. num = request[addr_pair][0]+1
  37. del request[addr_pair]
  38. request[addr_pair]=[num,ts]
  39. file=open("index.html", "w")
  40. file.write("<!DOCTYPE html> <html> <body><center><h1><font color=\"blue\"
  41. face=\"Georgia, Arial\" size=8><em>HA</em></font> Webpage Visit
  42. Results</h1></center>");
  43. for pair in request:
  44. if pair[0] == host:
  45. guest = "LOCAL: "+pair[0]
  46. else:
  47. guest = pair[0]
  48. if (time_now-datetime.strptime(request[pair][1],'%Y-%m-%d %H:%M:%S'))
  49. .seconds < 3:
  50. file.write("<p style=\"font-size:150%\" >#"+ str(request[pair]
  51. [1]) +": <font color=\"red\">"+str(request[pair][0])+ "</
  52. font> requests " + "from &lt<font color=\"blue\">"+guest+"
  53. </font>&gt to WebServer &lt<font color=\"blue\">"+pair[1]+"
  54. </font>&gt</p>")
  55. else:
  56. file.write("<p style=\"font-size:150%\" >#"+ str(request[pair]
  57. [1]) +": <font color=\"maroon\">"+str(request[pair][0])+
  58. "</font> requests " + "from &lt<font color=\"navy\">"+guest+
  59. "</font>&gt to WebServer &lt<font color=\"navy\">"+pair[1]+
  60. "</font>&gt</p>")
  61. file.write("</body> </html>");
  62. file.close()
  63. pickle.dump(request,open("pickle_data.txt","w"))
  64. if __name__ == '__main__':
  65. try:
  66. ServerClass = BaseHTTPServer.HTTPServer
  67. Protocol = "HTTP/1.0"
  68. addr = len(sys.argv) < 2 and "0.0.0.0" or sys.argv[1]
  69. port = len(sys.argv) < 3 and 80 or int(sys.argv[2])
  70. HandlerClass.protocol_version = Protocol
  71. httpd = ServerClass((addr, port), HandlerClass)
  72. sa = httpd.socket.getsockname()
  73. print "Serving HTTP on", sa[0], "port", sa[1], "..."
  74. httpd.serve_forever()
  75. except:
  76. exit()

(2)index.html

生成一个临时的index.html文件,其内容会由index.py来更新:


  1. $ touch index.html

(3)Dockerfile

生成一个Dockerfile,部署该Web应用,内容为:


  1. FROM python:2.7
  2. WORKDIR /code
  3. ADD . /code
  4. EXPOSE 80
  5. CMD python index.py

2.haproxy子目录

该目录将配置haproxy镜像。

在其中生成一个haproxy.cfg文件,内容为:


  1. global
  2. log 127.0.0.1 local0
  3. log 127.0.0.1 local1 notice
  4. maxconn 4096
  5. defaults
  6. log global
  7. mode http
  8. option httplog
  9. option dontlognull
  10. timeout connect 5000ms
  11. timeout client 50000ms
  12. timeout server 50000ms
  13. listen stats
  14. bind 0.0.0.0:70
  15. mode http
  16. stats enable
  17. stats hide-version
  18. stats scope .
  19. stats realm Haproxy\ Statistics
  20. stats uri /
  21. stats auth user:pass
  22. frontend balancer
  23. bind 0.0.0.0:80
  24. mode http
  25. default_backend web_backends
  26. backend web_backends
  27. mode http
  28. option forwardfor
  29. balance roundrobin
  30. server weba weba:80 check
  31. server webb webb:80 check
  32. server webc webc:80 check
  33. option httpchk GET /
  34. http-check expect status 200

3.docker-compose.yml文件

在haproxy_web目录下编写一个docker-compose.yml文件,该文件是Compose使用的主模板文件。其中,指定启动3个Web容器(weba、webb、webc),以及1个Haproxy容器:


  1. # This will start a haproxy and three web services. haproxy will act as a
  2. loadbalancer.
  3. # Authors: yeasy.github.com
  4. # Date: 2015-11-15
  5. weba:
  6. build: ./web
  7. expose:
  8. - 80
  9. webb:
  10. build: ./web
  11. expose:
  12. - 80
  13. webc:
  14. build: ./web
  15. expose:
  16. - 80
  17. haproxy:
  18. image: haproxy:1.6
  19. volumes:
  20. - ./haproxy:/haproxy-override
  21. - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
  22. links:
  23. - weba
  24. - webb
  25. - webc
  26. ports:
  27. - "80:80"
  28. - "70:70"

4.运行compose项目

现在haproxy_web目录应该长成下面的样子:


  1. haproxy_web├── docker-compose.yml├── haproxy └── haproxy.cfg└── web
  2. ├── Dockerfile
  3. ├── index.html
  4. └── index.py

在该目录下执行sudo docker-compose up命令,控制台会整合显示出所有容器的输出信息:


  1. $ sudo docker-compose up
  2. Recreating haproxyweb_webb_1...
  3. Recreating haproxyweb_webc_1...
  4. Recreating composehaproxyweb_weba_1...
  5. Recreating composehaproxyweb_haproxy_1...
  6. Attaching to composehaproxyweb_webb_1, composehaproxyweb_webc_1, composehaproxyweb_
  7. weba_1, composehaproxyweb_haproxy_1

此时通过浏览器访问本地的80端口,会获取到页面信息,如图24-1所示。

24.6 Compose应用案例一:Web负载均衡 - 图1

图24-1 访问页面信息

经过Haproxy自动转发到后端的某个Web容器上,刷新页面,可以观察到访问的容器地址的变化。

访问本地70端口,可以查看到Haproxy的统计信息,如图24-2所示。

查看本地的镜像,会发现Compose自动创建的haproxyweb_weba、haproxyweb_webb、haproxyweb_webc镜像:


  1. $ docker images
  2. REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
  3. haproxyweb_webb latest 33d5e6f5e20b 44 minutes ago 675.2 MB
  4. haproxyweb_weba latest 33d5e6f5e20b 44 minutes ago 675.2 MB
  5. haproxyweb_webc latest 33d5e6f5e20b 44 minutes ago 675.2 MB

当然,还可以进一步使用Consul等方案来实现服务自动发现,这样就可以不用手动指定后端的web容器了,更为灵活。

24.6 Compose应用案例一:Web负载均衡 - 图2

图24-2 访问haproxy的统计信息