docker——三剑客之Docker Compose

编排(Orchestration)功能是复杂系统实现灵活可操作性的关键。
特别是在Docker应用场景中,编排意味着用户可以灵活的对各种容器资源实现定义和管理。

作为Docker官方编排工具,Compose的重要性不言而喻,
它可以让用户通过编写一个简单模板文件快速地创建和管理基于Docker容器地应用集群

Compose项目是Docker官方的开源项目,负责实现对Docker容器的快速编排。从功能上看,跟OpenStack中的Heat十分相似。
Compose定位是“定义和运行多个Docker容器的应用”,其前身是开源项目Fig,目前仍然兼容Fig格式的模板文件。

我们知道使用一个DockerFile模板文件,可以让用户很方便的定义一个单独的应用容器
如果需要定义多个容器就需要容器编排,那么就可以使用Compose了。
Compose允许用户通过一个单独的docker-compose.yml模板文件(YAML格式)来定义一组相关联的应用容器为一个项目

Compose中有两个重要的概念:
  服务(service):一个应用的容器,实际上可以包括若干个运行相同镜像的容器实例
  项目(project)由一组关联的应用容器组成的一个完整的业务单元,在docker-compose.yml文件中定义。

Compose的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Compose项目由Python编写,实现上调用了Docker服务提供地API来对容器进行地管理。
因此,只要所操作地平台支持Docker API,就可以在其上利用Compose来进行编排管理。

 

一、安装与卸载

  (1)pip安装   

pip install -U docker-compose

(2)二进制包安装

curl -L https://github.com/docker/compose/releases/download/1.23.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

(3)在容器中执行

官方提供了安装脚本

curl -L https://github.com/docker/compose/releases/download/1.23.0/run.sh > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

(4)卸载

  如果是二进制包安装,将包删除就可以了,如果是pip安装,使用pip uninstall

 

二、命令与说明

对于Compose来说,大部分命令地对象既可以是项目本身,也可以指定为项目中地服务或者容器。
如果没有特别说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令地影响。
命令格式:
  docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
options选项:

Compose选项:

命令使用说明:

(1)build

构建或重建项目中的服务器。
格式:
  build [options] [--build-arg key=val...] [SERVICE...]
说明:
  --compress 使用gzip压缩构陷上下文
  --force-rm 删除构建过程中的临时容器
  --no-cache 构建镜像过程中不适用缓存,否则会加长构建过程
  --pull 始终尝试拉取较新版本的镜像
  --m, --memory MEM 设置构建容器的内存限制
  --build-arg key=val 设置服务构建时的变量
  --parallel 并行构建镜像

(2)bundle

从Compose中生成Docker包
格式:
  bundle [options]
说明:
  --push-images 自动推送任何服务的镜像
  -o, --output PATH 设置文件的写入路径

(3)config

验证并查看Compose文件
格式:
  config [options]
说明:
  --resolve-image-digests 将镜像标签标记为摘要
  -q, --quiet 仅验证配置,不打印任何东西
  --services 打印服务名,每行一个
  --volumes 打印卷名,每行一个
  --hash="*" 打印服务配置的哈希

(4)create

为服务创建容器,不推荐使用此命令。推荐up --no-start
格式:
  create [options] [SERVICE...]
说明:
  --force-recreate 重新配置容器,即使它们的配置和镜像没有改变
  --no-recreate 如果容器已经存在,不要重新创建
  --no-build 不构建镜像,即使丢失了
  --build 在创建容器之前构建镜像

(5)down

停止并删除容器以及网络、镜像和卷
格式:
  down [options]
说明:
  --rmi type 删除镜像,type是all或者local
  -v, --volumes 删除volumes中声明的命名卷
  --remove-orphans 删除没有在服务中定义的容器
  -t, --timeout TIMEOUT 以秒为单位指定关闭超时

(6)events

从容器接受实时事件
格式:
  events [options] [SERVICE...]
说明:
  --json 将事件作为json对象流输出

(7)images

列出创建容器所使用的镜像
格式:
  images [options] [SERVICE...]
说明:
  -q, --quiet 仅输出镜像的id

(8)kill

强制停止一个容器的服务
格式:
  kill [options] [SERVICE...]
说明:
  -s SIGNAL 发送SIGNAL信号,默认发送的是SIGKILL

(9)logs

查看容器的输出
格式:
  logs [options] [SERVICE...]
说明:
  --no-color 单色输出
  -f, --follow 遵循日志输出
  -t, --timestamps 显示时间戳
  --tail="all" 每个容器的日志末尾显示行数

(10)port

打印某个容器端口所映射的公共端口
格式:
  port [options] SERVICE PRIVATE_PORT
说明:
  --protocol=proto 协议的类型,tcp或udp
  --index=index 如果有多个容器,则为容器的索引值,默认为1

(11)ps

列出项目中的所有容器
格式:
  ps [options] [SERVICE...]
说明:
  -q, --quiet 仅显示ID
  --services 显示服务
  --filter KEY=VAL 通过一个键值对过滤

(12)pull

为compose文件中定义的服务拉取镜像,但不启动容器。
格式:
  Usage: pull [options] [SERVICE...]
说明:
  --ignore-pull-failures 忽略拉取过程中的错误
  --parallel 并行拉出多个镜像
  --no-parallel 禁用并行
  -q, --quiet 拉出而不打印进度信息
  --include-deps 拉取服务的声明作为依赖项

(13)rm

删除所有停止状态的服务容器
说明:
  -f,--force 强制直接删除,包括非停止状态的容器
  -v 删除容器所挂载的数据卷

(14)run

在指定服务上执行一个命令。
默认情况下,如果存在关联,则关联的服务也将会被自动启动,除非这些服务已经载运行中。
如果不希望自动启动关联容器,可以使用--no-deps选项。
格式:
  run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]
说明:
  -d 在后台运行服务容器
  --name NAME 为容器指定一个名字
  --entrypoint CMD覆盖默认的容器启动指令
  -e KEY=VAL 设置环境变量,可多次使用选项来设置多个环境变量
  -u, --user=“” 指定运行容器的用户名或者Uid
  --no-deps 不自动启动关联的服务容器
  --rm 运行命令后自动删除容器,d模式下忽略
  -p, --publish=[] 映射容器端口到本地主机
  --service-ports 配置服务端口,并映射到本地主机
  -T 不分配伪tty,意味着依赖tty的指令将无法运行

(15)scale

设置服务运行个容器个数
格式:
  scale [options] [SERVICE=NUM...]
说明:
  -t, --timeout TIMEOUT停止容器时候的超时,默认10秒

(16)up

该命令十分强大,它将尝试自动完成包括构建镜像,重新创建服务,启动服务,并关联服务相关容器的一系列操作。
链接的服务都将会被自动启动,除非已经处于运行状态。
如果容器已经存在,up将尝试将容器停止,然后创建。
格式:
  up [options] [--scale SERVICE=NUM...] [SERVICE...]
说明:
  -d 在后台运行服务器
  --no-color 单色输出
  --no-deps 不启动服务所关联的容器
  --force-recreate 强制重新创建容器
  --no-recreate 如果容器已经存在了,则不重新创建
  --no-build 不自动构建缺失的服务镜像
  -t, --timeout TIMEOUT 停止容器时候的超时

 

三、环境变量

环境变量可以用来配置Compose的行为:

 

四、模板文件

模板文件是使用Compose的核心,默认的模板文件名称为docker-compose.yml,格式为YAML格式。
每个服务都必须通过image指定镜像或build(需要Dockerfile)来自动构成生成镜像。

先来看一个基本示例:

version: '2'
services:  
	web:    
	image: dockercloud/hello-world    
		ports:      
			- 8080    
		networks:      
			- front-tier      
			- back-tier  
	redis:    
		image: redis    
		links:      
			- web    
		networks:      
			- back-tier  
	lb:    
		image: dockercloud/haproxy    
		ports:      
			- 80:80    
		links:      
			- web    
		networks:      
			- front-tier      
			- back-tier    
		volumes:      
			- /var/run/docker.sock:/var/run/docker.sock 
networks:  
	front-tier:    
		driver: bridge  
	back-tier:
driver: bridge

可以看到一份标准配置文件应该包含version、service、network三大部分。
其中最关键的就是services和networks两部分。

下面是具体命令的书写规则:

(1)image

services:  
	web:    
	  image: dockercloud/hello-world  

在services标签下的第二个标签是web,这个名字是用户自己自定义,它就是服务器名称。
image则是指定服务的镜像名称。如果镜像在本地不存在,Compose将会尝试拉取这个镜像。
下面这些格式都是可行的:

image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd

(2)build

服务除了可以基于指定的镜像,还可以基于以分Dockerfile,在使用up启动之时执行构建任务。
这个构建标签就是build,它可以指定Dockerfile所在文件夹的路径。
Compose将会利用它自动构建这个镜像,然后利用这个镜像启动服务容器。

build: /path/to/build/dir

也可以时相对路径

build: ./dir

设定上下文根目录,然后以该目录为准指定Dockerfile。

build:
  context: ../
  dockerfile: path/of/Dockerfile

注意build都是一个目录,如果你要指定Dockerfile文件,需要在build标签的子级标签中使用dockerfile标签指定。
如果你同时指定了image和build两个标签,那么Compose会构建镜像并且把镜像命名为image后面的那个名字。

build: ./dir
image: webapp:tag

既然可以在docker-compose.yml中定义构建任务,那么一定少不了arg这个标签,
就像Dockerfile中的ARG指令,它可以在构建过程中指定环境变量,但是在构建成功后取消。
在docker-compose.yml文件中也支持这样的写法:

build:
  context: .
  args:
    buildno: 1
    password: secret

下面这种写法也是支持的,一般来说下面的写法更适合阅读。

build:
  context: .
  args:
    - buildno=1
    - password=secret

与ENV不同的是,ARG是允许空指的,例如:

args:
  - buildno
  - password

这样构建过程可以向它们赋值。

注意:YAML的布尔值(true、false、yes、no、on、off)必须要使用引号引起来,否则会当成字符串解析。

(3)command

使用command可以覆盖容器启动后默认执行的命令。

command: bundle exec thin -p 3000

也可以写成类似Dockerfile中的格式

command: [bundle, exec, thin, -p, 3000]

(4)container_name

前面说过Compose的容器名称格式是:<项目名称><服务名称><序号>
虽然可以自定义项目名称、服务名称,但是如果你想完全控制容器的命名,可以使用下面标签指定:

container_name: app

这样容器的名字就指定为app了。

注意:指定容器名称之后,该服务将无法进行扩展,因为Docker不允许多个容器具有相同的名称。

(5)depends_on

在使用Compose时,最大的好处就是少打启动命令,但一般项目容器启动的顺序是有要求的。
如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。
例如我们在没有启动数据库容器的时候启动了应用容器,这时候应用容器会应为找不到数据库而退出。
为了避免这种情况我们需要加入一个标签,就是depend_on,这个标签解决了容器的依赖、启动先后的问题。
例如下面容器会先启动redis和db两个服务,最后才启动web服务:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

注意的是,默认情况下使用docker-compose up web这样的方式启动web服务时,也会启动redis和db两个服务。因为配置文件中定义了依赖关系。

(6)dns

和--dns参数一样用途,格式如下:

dns: 8.8.8.8

也可以是一个列表:

dns:
  - 8.8.8.8
  - 9.9.9.9

此外dns_search的配置也类似:

dns_search: example.com
dns_search:
  - dc1.example.com
  - dc2.example.com

(7)tmpfs

挂载临时目录到容器内部,与run的参数一样效果:

tmpfs: /run
tmpfs:
  - /run
  - /tmp

(8)entrypoint

在Dockerfile中有一个指令ENTRYPOINT指令,用于指定接入点。
在docker-compose.yml中可以定义接入点,覆盖Dockerfile中的定义:

entrypoint: /code/entrypoint.sh

格式和Docker类似,不过还可以写成这样:

entrypoint:    
	php    
	- -d    
	- zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so    
	- -d    - memory_limit=-1    
	- vendor/bin/phpunit

(9)env_file

在docker-compose.yml中可以定义一个专门存放变量的文件。
如果通过docker-compose -f FILE指定配置文件,则env_file中路径会使用配置文件路径。
如果有变量名称与environment指令冲突,则以后者为准。格式如下:

env_file: .env

或者根据docker-compose.yml设置多个:

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

注意的是这里所说的环境变量是对宿主机的Compose而言,如果在配置文件中有build操作,
这些变量并不会进入构建过程,如果要在构建中使用变量还是首选前面所说的arg标签。
环境变量中的每一行必须符合格式,支持#开头的注释行。

(10)environment

与上面的env_file标签完全不同,这个标签的作用是设置镜像变量,
它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与arg最大的不同。
一般arg标签的变量仅用在构建过程中。
而enviroment和Dockerfile中ENV指令一样会把变量一直保存在镜像、容器中,类似docker run -e的效果。

environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:

environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

(11)expose

这个标签进和Dockerfile中EXPOSE指令一样,用于指定暴露的端口,
但是只是作为一种参考,实际上docker-compose.yml的端口映射还得ports这样的标签。

expose:
 - "3000"
 - "8000"

(12)external_links

在使用Docker过程中,我们会有许多单独使用docker run启动的容器,
为了使Compose能够连接这些不在docker-compose.yml中定义的容器,
我们需要一个特殊的标签,也就是external_links,它可以让Compose项目里面的容器连接到那些项目配置外部的容器。
前提是外部容器中必须至少有一个容器是连接到项目内的服务的同一个网络里面。

external_links:
 - redis_1
 - project_db_1:mysql
 - project_db_1:postgresql

(13)extra_hosts

添加主机名的标签,就是往/etc/hosts文件中添加一些记录,与Docker client的--add-host类似:

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"

启动之后查看容器内部hosts:

162.242.195.82 somehost
50.31.209.229 otherhost

(14)labels

向容器添加元数据,和Dockerfile的LABEL指令一个意思,格式如下:

labels:
  com.example.description: "Accounting webapp"
  com.example.department: "Finance"
  com.example.label-with-empty-value: ""
labels:
  - "com.example.description=Accounting webapp"
  - "com.example.department=Finance"
  - "com.example.label-with-empty-value"

(15)links

前面讲过depends_on,那个标签解决启动顺序问题,这个标签解决的是容器的连接问题。
与Docker client的--link一样效果,会连接到其它服务中的容器。
格式如下:

links:
 - db
 - db:database
 - redis

使用的别名将会自动在服务容器中的/etc/hosts里创建。例如:

172.12.2.186  db
172.12.2.186  database
172.12.2.187  redis

相应的环境变量也将被创建。

(16)logging

这个标签用于配置日志服务。格式如下:

logging:
  driver: syslog
  options:
    syslog-address: "tcp://192.168.0.42:123"

默认的driver是json-file。只有json-file和journald可以通过docker-compose logs显示日志。
其它方式有其它的查看方式,但目前Compose还不支持。对于可选值可以使用options指定。
详细信息:https://docs.docker.com/engine/admin/logging/overview/

(17)pid

将PID模式设置为主机PID模式,跟主机系统共享进程命名空间。
容器使用这个标签将能够访问和操作其它容器和宿主机的名称空间。

pid: "host"

(18)ports

映射端口的标签。
使用HOST.CONTAINER格式或者指定容器的端口,宿主机会随机映射端口。

ports:
 - "3000"
 - "8000:8000"
 - "49100:22"
 - "127.0.0.1:8001:8001"

注意:当使用HOST>CONTAINER格式来映射端口时,如果你使用的容器端口小于60可能会得到错误的结果。
因为YAML将会解析xxyy这种数字格式为60进制。所以建议采用字符串格式。

(19)security_opt

为一个容器覆盖默认的标签。简单说来就是管理全部服务的标签。
比如设置全部服务的user标签值为USER。

security_opt:
  - label:user:USER
  - label:role:ROLE

(20)stop_signal

设置一个信号来停止容器。在默认情况下是使用的是SIGTERM停止容器。
设置另一个信号可以使用stop_signal标签。

stop_signal: SIGUSR1

(21)volumes

挂载一个目录或者一个已存在的数据卷容器,可以直接使用[HOST:CONTAINER]这样的格式,
或者使用[HOST:CONTAINER:ro]这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。
Compose的数据卷指定路径可以是相对路径,使用.或者..来指定相对路径。
数据卷的格式可以是下面多种形式:

volumes:
  // 只是指定一个路径,Docker 会自动在创建一个数据卷(这个路径是容器内部的)。
  - /var/lib/mysql

  // 使用绝对路径挂载数据卷
  - /opt/data:/var/lib/mysql
  
  // 以 Compose 配置文件为中心的相对路径作为数据卷挂载到容器。
  - ./cache:/tmp/cache
  
  // 使用用户的相对路径(~/ 表示的目录是 /home/<用户目录>/ 或者 /root/)。
  - ~/configs:/etc/configs/:ro
  
  // 已经存在的命名的数据卷。
  - datavolume:/var/lib/mysql

如果你不使用宿主机的路径,你可以指定一个volume_driver.

volume_driver: mydriver

(22)volumes_from

从其他容器或者服务挂载数据卷,可选参数是:ro或:rw,
前者表示容器只读,后者表示容器对数据卷是可读可写的。默认情况下是可读可写的。

volumes_from:
  - service_name
  - service_name:ro
  - container:container_name
  - container:container_name:rw

(23)cap_add,cap_drop

添加或删除容器的内核功能

cap_add:
  - ALL  让容器拥有所有能力

cap_drop:  去掉某些能力
  - NET_ADMIN
  - SYS_ADMIN

(24)cgroup_parent

指定一个容器的父级cgroup

cgroup_parent: m-executor-abcd

(25)devices

设备映射列表。与Docker client的--device参数类似。

devices:
  - "/dev/ttyUSB0:/dev/ttyUSB0"  

(26)extends

这个标签可以扩展另一个服务,扩展内容可以是来自当前文件,也可以是来自其它文件,
相同服务的情况下,后来者会有选择的覆盖原有配置。

extends:
  file: common.yml
  service: webapp

用户可以在任何地方使用这个标签,只要标签内容包含file和service两个值就可以了。
file的值可以是相对或者绝对路径,如果不指定file的值,那么Compose会读取当前YML文件的信息。
注意避免循环依赖。

(27)network_mode

网络模式,与Docker client的--net参数类似,只是相对多了一个service:[service name]的格式。

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

可以指定使用服务或者容器的网络。

(28)networks

加入指定网络。

services:
  some-service:
    networks:
     - some-network
     - other-network

关于这个标签还有一个特别的子标签aliases,这是一个用来设置服务别名的标签:

services:
  some-service:
    networks:
      some-network:
        aliases:
         - alias1
         - alias3
      other-network:
        aliases:
         - alias2

相同的服务可以在不同的网络有不同的别名。

(29)ulimits

指定容器的ulimits限制值。
例如,指定最大进程数为65535,指定文件句柄数为20000(软限制,应用可以随时修改,不能超过硬限制)和40000(系统硬限制,只能root用户提高)

ulimits:
	nproc: 65535
	nofile:
		soft: 20000
		hard: 40000

(30)其它

此外,还有包括cpu_shares、cpuset、domainname、hostname、ipc、mac_address、mem_limit、memswap_limit、
privileged、read_only、restart、stdin_open、tty、user、working_dir等指令。
指定使用CPU核0和核1,只用50%的CPU资源:

cpu_shares: 73
cpuset: 0,1

指定容器中运行应用的用户名:

user: nginx

指定容器中工作目录:

working_dir: /code

指定容器中搜索域名、主机名、mac地址等:

domainname: your_website.com
hostname: test
mac_address: 08-00-27-00-0C-0A

指定容器:

ipc: host

指定容器中内存和内存减缓去限制都为1G:

mem_limit: 1g
menswap_limit: 1g

允许容器运行一些特权命令:

privileged: true

指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,推荐配置为always或者unless-stopped

restart: always

以只读模式挂载容器的root文件系统,意味着不能对容器内容进行修改:

read_only: true

打开标准输入,可以接受外部输入:

stdin_open: true

模拟一个假的远程控制台:

tty: true

(31)读取环境变量

从1.5.0版本开始,Compose模板文件支持动态读取主机的系统环境变量。
例如:下面的Compose文件将从运行它的环境中读取变量${MONGO_VERSION}的值,并将其写入执行的指令中。

db:
	images: “mongo:${MONGO_VERSION}”

  

 

五、应用实例

1.nginx服务启动

dvc:
    image: centos
    volumes:
        - /root/composetest:/usr/share/nginx/html/:ro

nginx:
    image: nginx
    volumes_from:
        - dvc
    ports:
        - "8081:80"

  

2.web负载均衡

创建一个web项目:将Haproxy作为负载均衡器,后端挂载三个web容器。

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

[root@centos002 haproxy_web]# ls
docker-compose.yml  haproxy  web

[root@centos002 haproxy_web]# tree .
.
|-- docker-compose.yml
|-- haproxy
|   `-- haproxy.cfg
`-- web
    |-- Dockerfile
    |-- index.html
    `-- index.py

2 directories, 5 files

(1)web子目录

index.py

#!/usr/bin/python

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>");
        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("</center></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.py里面说的什么我们不需要去深究,只需要知道用来响应HTTP的请求,返回结果就行了。
同时它还会像index.html的文件中写入标签元素。

index.html

  这是一个空的文件,它的内容由index.py动态写入。

Dockerfile

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

我们知道Dockerfile用来创建一个容器。
这里会创建一个python2.7的容器,同时还会执行index.py文件

(2)hpproxy子目录

该目录将配置haproxy镜像,这里面的配置其实是haproxy的配置文件,我们将会利用这个配置文件来构建我们想要的镜像。

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)dockerfile-compose.yml

这个文件Compose使用的主模板文件。
其中会指定启动3个web容器(weba、webb、webc),以及一个haproxy容器。

#创建三个web服务
weba: 
    build: ./web  指定Dockerfile文件所在的路径,Compose将会使用这个文件来构建镜像,然后利用这个镜像来创建容器
    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:  links标签解决容器的连接问题,它会连接到另外的三个容器,以此来达到编排的目的。
        - weba
        - webb
        - webc
    ports:
        - "80:80"  端口映射,可以使用docker ps来查看
        - "70:70"

(4)运行compose项目

启动:
  docker-compose up 会自动查找当前目录下的docker-compose.yml文件,如果没有会去上级目录查找
查看容器信息:

[root@centos002 haproxy_web]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                    NAMES
9eac212b5a4d        haproxy:1.6         "/docker-entrypoint.…"   44 minutes ago      Up 44 minutes       0.0.0.0:70->70/tcp, 0.0.0.0:80->80/tcp   haproxy_web_haproxy_1_8843fbe31d1f
696b91016f39        haproxy_web_webc    "/bin/sh -c 'python …"   44 minutes ago      Up 44 minutes       80/tcp                                   haproxy_web_webc_1_159bfc7f2644
dfcb7f1a0e8e        haproxy_web_weba    "/bin/sh -c 'python …"   44 minutes ago      Up 44 minutes       80/tcp                                   haproxy_web_weba_1_f047051398b2
0508a092fca0        haproxy_web_webb    "/bin/sh -c 'python …"   44 minutes ago      Up 44 minutes       80/tcp                                   haproxy_web_webb_1_ef0b108c4e3b

我们可以看到创建并启动了四个容器,haproxy完成了两个端口的映射,web服务默认使用的80端口,expose只是声明。

查看镜像:
理论上说会下载python2.7和haproxy1.6的镜像,同时又会在python镜像的基础上创建3个web镜像。

  查看负载均衡的效果:
我们连续刷新三次:

我们可以看到在三个ip之前来回切换,这样就达到了负载均衡的效果。

(5)总结

再回来看目录结果:

[root@centos002 haproxy_web]# tree
.
|-- docker-compose.yml
|-- haproxy
|   `-- haproxy.cfg
`-- web
    |-- Dockerfile
    |-- index.html
    `-- index.py

2 directories, 5 files

貌似有好几个目录,但是其实核心目录只有一个就是docker-compose.yml。
我们稍微改一下,也许就会更加清晰明确。

weba:
	image:nginx

webb:
	image:nginx

webc:
	image:nginx

haproxy:
    image: haproxy:1.6
    volumes:
        - haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
    links:
        - weba
        - webb
        - webc
    ports:
        - "80:80"
        - "70:70"

这样我们的目录结构:

[root@centos002 web_haproxy]# tree
.
|-- docker-compose.yml
`-- haproxy.cfg  这个文件没有的话,不能实现负载均衡的

0 directories, 2 files

  之所以创建web和haproxy这两个目录,就是为了更加的清晰明确。

  

 

posted @ 2018-12-21 05:11  明王不动心  阅读(1351)  评论(0编辑  收藏  举报