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作为服务器文件,代码为:
- #!/usr/bin/python
- #authors: yeasy.github.com
- #date: 2013-07-05
- import sys
- import BaseHTTPServer
- from SimpleHTTPServer import SimpleHTTPRequestHandler
- import socket
- import fcntl
- import struct
- import pickle
- from datetime import datetime
- from collections import OrderedDict
- class HandlerClass(SimpleHTTPRequestHandler):
- def get_ip_address(self,ifname):
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- return socket.inet_ntoa(fcntl.ioctl(
- s.fileno(),
- 0x8915, # SIOCGIFADDR
- struct.pack('256s', ifname[:15])
- )[20:24])
- def log_message(self, format, *args):
- if len(args) < 3 or "200" not in args[1]:
- return
- try:
- request = pickle.load(open("pickle_data.txt","r"))
- except:
- request=OrderedDict()
- time_now = datetime.now()
- ts = time_now.strftime('%Y-%m-%d %H:%M:%S')
- server = self.get_ip_address('eth0')
- host=self.address_string()
- addr_pair = (host,server)
- if addr_pair not in request:
- request[addr_pair]=[1,ts]
- else:
- num = request[addr_pair][0]+1
- del request[addr_pair]
- request[addr_pair]=[num,ts]
- file=open("index.html", "w")
- file.write("<!DOCTYPE html> <html> <body><center><h1><font color=\"blue\"
- face=\"Georgia, Arial\" size=8><em>HA</em></font> Webpage Visit
- Results</h1></center>");
- for pair in request:
- if pair[0] == host:
- guest = "LOCAL: "+pair[0]
- else:
- guest = pair[0]
- if (time_now-datetime.strptime(request[pair][1],'%Y-%m-%d %H:%M:%S'))
- .seconds < 3:
- file.write("<p style=\"font-size:150%\" >#"+ str(request[pair]
- [1]) +": <font color=\"red\">"+str(request[pair][0])+ "</
- font> requests " + "from <<font color=\"blue\">"+guest+"
- </font>> to WebServer <<font color=\"blue\">"+pair[1]+"
- </font>></p>")
- else:
- file.write("<p style=\"font-size:150%\" >#"+ str(request[pair]
- [1]) +": <font color=\"maroon\">"+str(request[pair][0])+
- "</font> requests " + "from <<font color=\"navy\">"+guest+
- "</font>> to WebServer <<font color=\"navy\">"+pair[1]+
- "</font>></p>")
- file.write("</body> </html>");
- file.close()
- pickle.dump(request,open("pickle_data.txt","w"))
- if __name__ == '__main__':
- try:
- ServerClass = BaseHTTPServer.HTTPServer
- Protocol = "HTTP/1.0"
- addr = len(sys.argv) < 2 and "0.0.0.0" or sys.argv[1]
- port = len(sys.argv) < 3 and 80 or int(sys.argv[2])
- HandlerClass.protocol_version = Protocol
- httpd = ServerClass((addr, port), HandlerClass)
- sa = httpd.socket.getsockname()
- print "Serving HTTP on", sa[0], "port", sa[1], "..."
- httpd.serve_forever()
- except:
- exit()
(2)index.html
生成一个临时的index.html文件,其内容会由index.py来更新:
- $ touch index.html
(3)Dockerfile
生成一个Dockerfile,部署该Web应用,内容为:
- FROM python:2.7
- WORKDIR /code
- ADD . /code
- EXPOSE 80
- CMD python index.py
2.haproxy子目录
该目录将配置haproxy镜像。
在其中生成一个haproxy.cfg文件,内容为:
- global
- log 127.0.0.1 local0
- log 127.0.0.1 local1 notice
- maxconn 4096
- defaults
- log global
- mode http
- option httplog
- option dontlognull
- timeout connect 5000ms
- timeout client 50000ms
- timeout server 50000ms
- listen stats
- bind 0.0.0.0:70
- mode http
- stats enable
- stats hide-version
- stats scope .
- stats realm Haproxy\ Statistics
- stats uri /
- stats auth user:pass
- frontend balancer
- bind 0.0.0.0:80
- mode http
- default_backend web_backends
- backend web_backends
- mode http
- option forwardfor
- balance roundrobin
- server weba weba:80 check
- server webb webb:80 check
- server webc webc:80 check
- option httpchk GET /
- http-check expect status 200
3.docker-compose.yml文件
在haproxy_web目录下编写一个docker-compose.yml文件,该文件是Compose使用的主模板文件。其中,指定启动3个Web容器(weba、webb、webc),以及1个Haproxy容器:
- # This will start a haproxy and three web services. haproxy will act as a
- loadbalancer.
- # Authors: yeasy.github.com
- # Date: 2015-11-15
- weba:
- build: ./web
- expose:
- - 80
- webb:
- build: ./web
- expose:
- - 80
- webc:
- build: ./web
- expose:
- - 80
- haproxy:
- image: haproxy:1.6
- volumes:
- - ./haproxy:/haproxy-override
- - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- links:
- - weba
- - webb
- - webc
- ports:
- - "80:80"
- - "70:70"
4.运行compose项目
现在haproxy_web目录应该长成下面的样子:
- haproxy_web├── docker-compose.yml├── haproxy│ └── haproxy.cfg└── web
- ├── Dockerfile
- ├── index.html
- └── index.py
在该目录下执行sudo docker-compose up命令,控制台会整合显示出所有容器的输出信息:
- $ sudo docker-compose up
- Recreating haproxyweb_webb_1...
- Recreating haproxyweb_webc_1...
- Recreating composehaproxyweb_weba_1...
- Recreating composehaproxyweb_haproxy_1...
- Attaching to composehaproxyweb_webb_1, composehaproxyweb_webc_1, composehaproxyweb_
- weba_1, composehaproxyweb_haproxy_1
此时通过浏览器访问本地的80端口,会获取到页面信息,如图24-1所示。

图24-1 访问页面信息
经过Haproxy自动转发到后端的某个Web容器上,刷新页面,可以观察到访问的容器地址的变化。
访问本地70端口,可以查看到Haproxy的统计信息,如图24-2所示。
查看本地的镜像,会发现Compose自动创建的haproxyweb_weba、haproxyweb_webb、haproxyweb_webc镜像:
- $ docker images
- REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
- haproxyweb_webb latest 33d5e6f5e20b 44 minutes ago 675.2 MB
- haproxyweb_weba latest 33d5e6f5e20b 44 minutes ago 675.2 MB
- haproxyweb_webc latest 33d5e6f5e20b 44 minutes ago 675.2 MB
当然,还可以进一步使用Consul等方案来实现服务自动发现,这样就可以不用手动指定后端的web容器了,更为灵活。

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