Docker技術入門與實戰 第二版-學習筆記-9-Docker Compose 項目-1-舉例說明


Docker Compose Docker 官方編排(Orchestration)項目之一,負責快速在集群中部署分布式應用

Compose 通過一個配置文件來管理多個Docker容器,在配置文件中,所有的容器通過services來定義,然后使用docker-compose腳本來啟動,停止和重啟應用,和應用中的服務以及所有依賴服務的容器,非常適合組合使用多個容器進行開發的場景。

 

通過第一部分中的介紹,我們知道使用一個 Dockerfile 模板文件,可以讓用戶很方便的定義一個單獨的應用容器。

然而,在日常工作中,經常會碰到需要多個容器相互配合來完成某項任務的情況。例如要實現一個 Web 項目,除了 Web 服務容器本身,往往還需要再加上后端的數據庫服務容器,甚至還包括負載均衡容器等。

Compose 恰好滿足了這樣的需求。它允許用戶通過一個單獨的 模板文件(YAML格式)來定義一組相關聯的應用容器為一個項目 (project)。

Compose 中有兩個重要的概念:

  • 服務(service):一個應用的容器,實際上可以包括若干運行相同鏡像的容器實例。
  • 項目(project):由一組關聯的應用容器組成的一個完整業務單元,在 文件中定義。

 

可見,一個項目可以由多個服務(容器)關聯而成,Compose 的默認管理對象是項目,通過子命令對項目中的一組容器進行便捷地生命周期管理。

Compose 項目由 Python 編寫,實現上調用了 Docker 服務提供的 API 來對容器進 行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 來進行編排管理。

 

之前也有查看過安裝好的docker-compose版本:

userdeMacBook-Pro:~ user$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01

安裝好后可以使用docker-compose -h來查看其用法

 

使用

下面就舉例說明,場景如下:

創建一個經典的 Web 項目:一個 Haproxy,掛載三個 Web 容器

首先創建一個compose-haproxy-web目錄,作為項目工作目錄,並在其中分別創建兩個子目錄:haproxy和web

1》web 子目錄下的文件

這里用 Python 程序來提供一個簡單的 HTTP 服務——打印出訪問者的 IP 和 實際的 本地 IP

 

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 &lt<font color=\"blue\">"+guest+"</font>&gt to WebServer &lt<font color=\"blue\">"+pair[1]+"</font>&gt</p>");
            else:
                file.write("<p style=\"font-size:150%\" >#"+ str(request[pair][1]) +": <font color=\"maroon\">"+str(request[pair][0])+ "</font> requests " + "from &lt<font color=\"navy\">"+guest+"</font>&gt to WebServer &lt<font color=\"navy\">"+pair[1]+"</font>&gt</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 更新。

userdeMacBook-Pro:compose-haproxy-web user$ touch index.html

 

Dockerfile

生成一個 Dockerfile,內容為:

FROM python:2.7
WORKDIR /code
ADD . /code
EXPOSE 80
CMD python index.py

 

 

 

2》haproxy 子目錄下

在其中生成一個 haproxy.cfg文件,內容為

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
 

3》主目錄下

docker-compose.yml

編寫 docker-compose.yml 文件,這個是 Compose 使用的主模板文件。內容十分簡單,指定 3 web 容器,以及 1 haproxy 容器

 

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"

 

 

 

4 》運行 compose 項目
現在 compose-haproxy-web 目錄長成下面的樣子。

compose-haproxy-web
├── docker-compose.yml
├── haproxy
│   └── haproxy.cfg
└── web
    ├── Dockerfile
    ├── index.html
    └── index.py

在該目錄下執行 docker-compose up命令,會整合輸出所有容器的輸出:

 

userdeMacBook-Pro:compose-haproxy-web user$ docker-compose up
Building weba
Step 1/5 : FROM python:2.7
2.7: Pulling from library/python
54f7e8ac135a: Pull complete
d6341e30912f: Pull complete
087a57faf949: Pull complete
5d71636fb824: Pull complete
0c1db9598990: Pull complete
220bd9a491ba: Pull complete
97b15521fe5d: Pull complete
1b44c1054690: Pull complete
6b8b382a68d7: Pull complete
Digest: sha256:1bb98a04d037d9766110499d36bf2f3a2aa43965b4aa345da91f6de75f3816d8
Status: Downloaded newer image for python:2.7
 ---> f67e752245d6
Step 2/5 : WORKDIR /code
 ---> Running in 869b8b9e9950
Removing intermediate container 869b8b9e9950
 ---> 37044ee1d056
Step 3/5 : ADD . /code
 ---> b669f2dcdda8
Step 4/5 : EXPOSE 80
 ---> Running in cbd702f39940
Removing intermediate container cbd702f39940
 ---> 794578c3e4ac
Step 5/5 : CMD python index.py
 ---> Running in 65de9f4ac31d
Removing intermediate container 65de9f4ac31d
 ---> 67764eb15cf9
Successfully built 67764eb15cf9
Successfully tagged compose-haproxy-web_weba:latest
WARNING: Image for service weba was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Building webb
Step 1/5 : FROM python:2.7
 ---> f67e752245d6
Step 2/5 : WORKDIR /code
 ---> Using cache
 ---> 37044ee1d056
Step 3/5 : ADD . /code
 ---> Using cache
 ---> b669f2dcdda8
Step 4/5 : EXPOSE 80
 ---> Using cache
 ---> 794578c3e4ac
Step 5/5 : CMD python index.py
 ---> Using cache
 ---> 67764eb15cf9
Successfully built 67764eb15cf9
Successfully tagged compose-haproxy-web_webb:latest
WARNING: Image for service webb was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Building webc
Step 1/5 : FROM python:2.7
 ---> f67e752245d6
Step 2/5 : WORKDIR /code
 ---> Using cache
 ---> 37044ee1d056
Step 3/5 : ADD . /code
 ---> Using cache
 ---> b669f2dcdda8
Step 4/5 : EXPOSE 80
 ---> Using cache
 ---> 794578c3e4ac
Step 5/5 : CMD python index.py
 ---> Using cache
 ---> 67764eb15cf9
Successfully built 67764eb15cf9
Successfully tagged compose-haproxy-web_webc:latest
WARNING: Image for service webc was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Pulling haproxy (haproxy:latest)...
latest: Pulling from library/haproxy
a5a6f2f73cd8: Already exists
7746471d9b75: Pull complete
3149ba82c5fb: Pull complete
Creating compose-haproxy-web_weba_1 ... done
Creating compose-haproxy-web_webb_1 ... done
Creating compose-haproxy-web_webc_1 ... done
Creating compose-haproxy-web_haproxy_1 ... error

ERROR: for compose-haproxy-web_haproxy_1  Cannot start service haproxy: b'Cannot link to a non running container: /compose-haproxy-web_webb_1 AS /compose-haproxy-web_haproxy_1/webb_1'

ERROR: for haproxy  Cannot start service haproxy: b'Cannot link to a non running container: /compose-haproxy-web_webb_1 AS /compose-haproxy-web_haproxy_1/webb_1'
ERROR: Encountered errors while bringing up the project.

 運行的過程中出現了錯誤,即web服務的容器並沒有運行,導致haproxy服務要連接其時失敗

web服務的容器沒能成功運行的原因是什么,用下面的方法查看:

userdeMacBook-Pro:compose-haproxy-web user$ docker-compose up webb
Starting compose-haproxy-web_webb_1 ... done
Attaching to compose-haproxy-web_webb_1
webb_1     |   File "index.py", line 46
webb_1     |     if (time_now-datetime.strptime(request[pair][1],'%Y-%m-%d %H:%M:%S')).seconds < 3:
webb_1     |                                                                                      ^
webb_1     | IndentationError: unindent does not match any outer indentation level
compose-haproxy-web_webb_1 exited with code 1

可以看見index.py代碼有錯,因為是復制過來的,所以縮進上可能會有點問題,解決該問題后,再運行:

userdeMacBook-Pro:compose-haproxy-web user$ docker-compose up --build
....
Recreating compose-haproxy-web_weba_1 ... done
Recreating compose-haproxy-web_webb_1 ... done
Recreating compose-haproxy-web_webc_1 ... done
Recreating compose-haproxy-web_haproxy_1 ... done
Attaching to compose-haproxy-web_webc_1, compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_haproxy_1
....

注意:一定要添加--build參數,在開啟容器時構建鏡像

但是后面還是有錯:

webb_1     | Traceback (most recent call last):
webb_1     |   File "/usr/local/lib/python2.7/SocketServer.py", line 290, in _handle_request_noblock
webb_1     |     self.process_request(request, client_address)
webb_1     |   File "/usr/local/lib/python2.7/SocketServer.py", line 318, in process_request
webb_1     |     self.finish_request(request, client_address)
webb_1     |   File "/usr/local/lib/python2.7/SocketServer.py", line 331, in finish_request
webb_1     |     self.RequestHandlerClass(request, client_address, self)
webb_1     |   File "/usr/local/lib/python2.7/SocketServer.py", line 654, in __init__
webb_1     |     self.finish()
webb_1     |   File "/usr/local/lib/python2.7/SocketServer.py", line 713, in finish
webb_1     |     self.wfile.close()
webb_1     |   File "/usr/local/lib/python2.7/socket.py", line 283, in close
webb_1     |     self.flush()
webb_1     |   File "/usr/local/lib/python2.7/socket.py", line 307, in flush
webb_1     |     self._sock.sendall(view[write_offset:write_offset+buffer_size])
webb_1     | error: [Errno 32] Broken pipe

這是python2.X上的問題,為了能夠顯示一下效果,在網上找了個python3的http服務代碼(https://blog.csdn.net/aaa000830/article/details/79579579)替換上面的index.py:

#!/usr/bin/python3
from wsgiref.simple_server import make_server
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ['<h1>Hello, web!</h1>'.encode()]

httpd = make_server("127.0.0.1",80,application)
httpd.serve_forever()

然后再運行:

userdeMacBook-Pro:web user$ docker-compose up --build
Building weba
Step 1/5 : FROM python:2.7
 ---> f67e752245d6
Step 2/5 : WORKDIR /code
 ---> Using cache
 ---> 37044ee1d056
Step 3/5 : ADD . /code
 ---> 70d6fe477513
Step 4/5 : EXPOSE 80
 ---> Running in 3069406cf7f7
Removing intermediate container 3069406cf7f7
 ---> b6c2f2e4566c
Step 5/5 : CMD python index.py
 ---> Running in f13845a5a7b8
Removing intermediate container f13845a5a7b8
 ---> 9b10db8bc740
Successfully built 9b10db8bc740
Successfully tagged compose-haproxy-web_weba:latest
Building webb
Step 1/5 : FROM python:2.7
 ---> f67e752245d6
Step 2/5 : WORKDIR /code
 ---> Using cache
 ---> 37044ee1d056
Step 3/5 : ADD . /code
 ---> Using cache
 ---> 70d6fe477513
Step 4/5 : EXPOSE 80
 ---> Using cache
 ---> b6c2f2e4566c
Step 5/5 : CMD python index.py
 ---> Using cache
 ---> 9b10db8bc740
Successfully built 9b10db8bc740
Successfully tagged compose-haproxy-web_webb:latest
Building webc
Step 1/5 : FROM python:2.7
 ---> f67e752245d6
Step 2/5 : WORKDIR /code
 ---> Using cache
 ---> 37044ee1d056
Step 3/5 : ADD . /code
 ---> Using cache
 ---> 70d6fe477513
Step 4/5 : EXPOSE 80
 ---> Using cache
 ---> b6c2f2e4566c
Step 5/5 : CMD python index.py
 ---> Using cache
 ---> 9b10db8bc740
Successfully built 9b10db8bc740
Successfully tagged compose-haproxy-web_webc:latest
Recreating compose-haproxy-web_webb_1 ... done
Recreating compose-haproxy-web_weba_1 ... done
Recreating compose-haproxy-web_webc_1 ... done
Recreating compose-haproxy-web_haproxy_1 ... done
Attaching to compose-haproxy-web_webb_1, compose-haproxy-web_weba_1, compose-haproxy-web_webc_1, compose-haproxy-web_haproxy_1

瀏覽器調用:

http://localhost:80

 

http://localhost:70

此時訪問本地的 80 端口,會經過 haproxy 自動轉發到后端的某個 web 容器上,刷新頁面,可以觀察到訪問的容器地址的變化。

訪問本地 70 端口,可以查看到 haproxy 的統計信息。

當然,還可以使用 consuletcd 等實現服務發現,這樣就可以避免手動指定后端的 web 容器了,更為靈活。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM