208 lines
6.1 KiB
Markdown
208 lines
6.1 KiB
Markdown
## 使用
|
||
|
||
### 术语
|
||
首先介绍几个术语。
|
||
|
||
* 服务(service):一个应用容器,实际上可以运行多个相同镜像的实例。
|
||
* 项目(project):由一组关联的应用容器组成的一个完整业务单元。
|
||
|
||
可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。
|
||
|
||
### 场景
|
||
下面,我们创建一个经典的 Web 项目:一个 [Haproxy](www.haproxy.org),挂载三个 Web 容器。
|
||
|
||
创建一个 `compose-haproxy-web` 目录,作为项目工作目录,并在其中分别创建两个子目录:`haproxy` 和 `web`。
|
||
|
||
### Web 子目录
|
||
|
||
这里用 Python 程序来提供一个简单的 HTTP 服务,打印出访问者的 IP 和 实际的本地 IP。
|
||
|
||
#### index.py
|
||
|
||
编写一个 `index.py` 作为服务器文件,代码为
|
||
```sh
|
||
#!/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()
|
||
```
|
||
|
||
#### index.html
|
||
生成一个临时的 `index.html` 文件,其内容会被 index.py 更新。
|
||
```sh
|
||
$ touch index.html
|
||
```
|
||
|
||
#### Dockerfile
|
||
生成一个 Dockerfile,内容为
|
||
```sh
|
||
FROM python:2.7
|
||
WORKDIR /code
|
||
ADD . /code
|
||
EXPOSE 80
|
||
CMD python index.py
|
||
```
|
||
|
||
### haproxy 目录
|
||
在其中生成一个 `haproxy.cfg` 文件,内容为
|
||
```sh
|
||
global
|
||
log 127.0.0.1 local0
|
||
log 127.0.0.1 local1 notice
|
||
|
||
defaults
|
||
log global
|
||
mode http
|
||
option httplog
|
||
option dontlognull
|
||
timeout connect 5000ms
|
||
timeout client 50000ms
|
||
timeout server 50000ms
|
||
|
||
listen stats :70
|
||
stats enable
|
||
stats uri /
|
||
|
||
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
|
||
```
|
||
### docker-compose.yml
|
||
编写 docker-compose.yml 文件,这个是 Compose 使用的主模板文件。内容十分简单,指定 3 个 web 容器,以及 1 个 haproxy 容器。
|
||
|
||
```sh
|
||
weba:
|
||
build: ./web
|
||
expose:
|
||
- 80
|
||
|
||
webb:
|
||
build: ./web
|
||
expose:
|
||
- 80
|
||
|
||
webc:
|
||
build: ./web
|
||
expose:
|
||
- 80
|
||
|
||
haproxy:
|
||
image: haproxy:latest
|
||
volumes:
|
||
- haproxy:/haproxy-override
|
||
- haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
|
||
links:
|
||
- weba
|
||
- webb
|
||
- webc
|
||
ports:
|
||
- "80:80"
|
||
- "70:70"
|
||
expose:
|
||
- "80"
|
||
- "70"
|
||
```
|
||
|
||
### 运行 compose 项目
|
||
现在 compose-haproxy-web 目录长成下面的样子。
|
||
```sh
|
||
compose-haproxy-web
|
||
├── docker-compose.yml
|
||
├── haproxy
|
||
│ └── haproxy.cfg
|
||
└── web
|
||
├── Dockerfile
|
||
├── index.html
|
||
└── index.py
|
||
```
|
||
在该目录下执行 `docker-compose up` 命令,会整合输出所有容器的输出。
|
||
```
|
||
$sudo docker-compose up
|
||
Recreating composehaproxyweb_webb_1...
|
||
Recreating composehaproxyweb_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 端口,会经过 haproxy 自动转发到后端的某个 web 容器上,刷新页面,可以观察到访问的容器地址的变化。
|
||
|
||
访问本地 70 端口,可以查看到 haproxy 的统计信息。
|
||
|
||
当然,还可以使用 consul、etcd 等实现服务发现,这样就可以避免手动指定后端的 web 容器了,更为灵活。
|