## 使用 ### 术语 首先介绍几个术语。 * 服务(service):一个应用容器,实际上可以运行多个相同镜像的实例。 * 项目(project):由一组关联的应用容器组成的一个完整业务单元。 可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。 ### 场景 下面,我们创建一个经典的 Web 项目:一个 [Haproxy](http://www.haproxy.org/),挂载三个 Web 容器。 创建一个 `compose-haproxy-web` 目录,作为项目工作目录,并在其中分别创建两个子目录:`haproxy` 和 `web`。 ### Web 子目录 这里用 Python 程序来提供一个简单的 HTTP 服务,打印出访问者的 IP 和 实际的本地 IP。 #### index.py 编写一个 `index.py` 作为服务器文件,代码为 ```bash #!/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("
#"+ str(request[pair][1]) +": "+str(request[pair][0])+ " requests " + "from <"+guest+"> to WebServer <"+pair[1]+">
") else: file.write("#"+ str(request[pair][1]) +": "+str(request[pair][0])+ " requests " + "from <"+guest+"> to WebServer <"+pair[1]+">
") file.write(" "); 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 更新。 ```bash $ touch index.html ``` #### Dockerfile 生成一个 Dockerfile,内容为 ```bash FROM python:2.7 WORKDIR /code ADD . /code EXPOSE 80 CMD python index.py ``` ### haproxy 目录 在其中生成一个 `haproxy.cfg` 文件,内容为 ```bash 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 bind 0.0.0.0: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 容器。 ```bash 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 目录长成下面的样子。 ```bash 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 容器了,更为灵活。