Docker 系统性入门+进阶实践-07Docker compose

  • docker compose是复杂应用在单机环境下编排的必备工具

compose文件的结构和版本

  1. 基本的语法结构
version: "3.8"  # docker-compose语法的版本

services:  # 容器
    service-name:  # 服务名字,这个名字也是内部bridge网络可以使用的 DNS name
        image:  # 镜像名字
        command:  # 可选,如果设置则会覆盖镜像里面的CMD命令
        environment:  # 可选,相当于docker run里的--env
        volumes:  # 可选,相当于docker run里的-v
        networks:  # 可选,相当于docker run里的--network
        port:  # 可选,相当于docker run里的-p
    service-name2:

volumes:  # 可选,相当于docker volume create

networks:  # 可选,相当于docker network create
  1. 以python flask + redis练习为例子,改造成一个docker-compose文件
    先不用docker-compose的方式启动看一下
    目录结构:

    Dockerfile文件:
FROM python:3.9-slim

WORKDIR /usr/src/app

COPY app /usr/src/app

RUN bash run.sh

EXPOSE 5000

ENTRYPOINT ["flask", "run"]
CMD ["-h", "0.0.0.0"]

app/app.py文件:

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "hello python"

app/requirements.txt文件:

flask
redis

app/run.sh文件:

#!/usr/bin/env bash
pip install --no-cache-dir -U -r requirements.txt -i https://pypi.douban.com/simple

拉取redis镜像,创建flask镜像,创建网络,启动redis服务,启动flask服务

sudo docker image pull reids
sudo docker image build -t flask-demo .
sudo docker network create --driver bridge demo-network
sudo docker container run -d --name redis-server --network demo-network redis
sudo docker container run -d --name flask-demo --network demo-network -p 8008:5000 --env REDIS_HOST=redis-se
rver flask-demo

命令一共5步,还是比较麻烦的,我们再来看看通过docker-compose来完
docker-compose.yml文件:

version: "3.8"

services:
    flask-demo:
        # 构建镜像
        build:  # 指定包含构建上下文的路径,或作为一个对象,该对象具有context和指定的dockerfile,以及args参数
            context: .  # 指定Dockerfile文件所在路径
            dockerfile: Dockerfile  # context指定目录下dockerfile的文件名称
        image: flask-demo
        container_name: flask-demo
        # command:  # 覆盖默认镜像里执行的命令,支持shell格式和exec格式
        environment:
            - REDIS_HOST=redis-server
        volumes:
            - ./app:/usr/src/app
        networks:
            - demo-network
        ports:
            - 8008:5000
        depends_on:
            - redis-server
        restart: on-failure:10
    redis-server:
        image: redis:latest
        container_name: redis-server
        networks:
            - demo-network
        restart: on-failure:10

networks:  # 相当于 docker network create demo-network
    demo-network:  # 指定网络名称
        external: false  # 为true表示从外部去找,没有就报错,为false是优先从外部找,没有就创建
        name: demo-network

app/app.py文件:增加了redis功能

from flask import Flask
import redis

pool = redis.ConnectionPool(host="redis-server", port=6379, db=0, encoding="utf-8")
r = redis.Redis(connection_pool=pool)

app = Flask(__name__)

@app.route("/")
def hello():
    return "hello python"


@app.route("/set/<key>/<value>")
def redis_set(key, value):
    r.set(key, value.encode())
    return f"set {key}={value} 成功"


@app.route("/get/<key>")
def reids_get(key):
    value = r.get(key)
    if value:
        return f"get {key}={value.decode()} 成功"
    else:
        return f"get {key} 失败"

执行命令启动服务:sudo docker-compose up , 大功告成了,docker-compose进行复杂应用的单机容器编排简直不要太方便。

docker-compose命令行基本使用

sudo docker-compose up  // 前台启动
sudo docker-compose up -d  // 后台启动
sudo docker-compose logs flask-demo  // flask服务日志
sudo docker-compose logs -f flask-demo  // 实时观看flask服务日志
sudo docker-compose restart  // 重启服务
sudo docker-compose ps  // 查看当前compose所有的服务
sudo docker-compose stop  // 停止容器服务
sudo docker-compose start  // 启动容器服务
sudo docker-compose rm  // 删除已经停止的容器
sudo docker network rm demo-network  // 删除docker-compose创建的网络,需要通过原始的删除网络命令

docker-compose镜像构建和拉取

  1. docker-compose up之前我们可以先拉取服务需要的镜像,以及构建服务需要的镜像
    拉取redis镜像:sudo docker-compose pull
    构建flask-demo镜像:sudo docker-compose build flask-demo
  2. 然后再通过docker-compose创建容器,启动容器就快很多了
    sudo docker-compose up [-d]

docker-compose服务更新

  1. 当我们对应用程序的文件做了修改,对镜像来讲,如果我们sudo docker image build -t xxx .的话,
    就会发现文件做了修改,重新build镜像,而对于docker-compose来讲,如果还用原来的方法启动的话就不会重新build镜像
    sudo docker-compose up -d
    这样启动的话,即使我们对应用程序做了修改,docker-compose也不会重新构建镜像,
    但是我们需要重新构建镜像,那么就需要加个参数
    sudo docker-compose up -d --build
    这样就会重新构建镜像,应用程序的改变也就生效了。
  2. 我们在增加一个容器服务box01
docker-compose.yml
version: "3.8"

services:
    flask-demo:
        # 构建镜像
        build:  # 指定包含构建上下文的路径,或作为一个对象,该对象具有context和指定的dockerfile,以及args参数
            context: .  # 指定Dockerfile文件所在路径
            dockerfile: Dockerfile  # context指定目录下dockerfile的文件名称
        image: flask-demo
        container_name: flask-demo
        # command:  # 覆盖默认镜像里执行的命令,支持shell格式和exec格式
        environment:
            - REDIS_HOST=redis-server
        # volumes:
        #    - ./app:/usr/src/app
        networks:
            - demo-network
        ports:
            - 8008:5000
        depends_on:
            - redis-server
        restart: on-failure:10

    redis-server:
        image: redis:latest
        container_name: redis-server
        networks:
            - demo-network
        restart: on-failure:10

    box01:
        image: busybox:latest
        container_name: box01
        command: /bin/sh -c "while true; do sleep 3600; done"
        networks:
            - demo-network

networks:  # 相当于 docker network create demo-network
    demo-network:  # 指定网络名称
        external: false  # 为true表示从外部去找,没有就报错,为false是优先从外部找,没有就创建
        name: demo-network

此时我们在重新启动docker-compose服务
sudo docker-compose up -d
创建了一个新的box01容器,并启动服务
3. 我们在把box01这个服务移除掉,然后重新启动docker-compose
sudo docker-compose up -d
报了个警告:

WARNING: Found orphan containers (box01) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.

所以我们需要使用下面这个命令,把不需要的孤儿容器删除掉
sudo docker-compose up -d --remove-orphans
4. docker-compose服务更新的三个重要点(大致能应对更新的所有场景)

  • sudo docker-compose up -d --build // 有文件更新了的时候(同时文件没有挂载到容器里面),那么就需要加 --build重新构建镜像
  • sudo docker-compose up -d --remove-orphans // 当我们从docker-compose.yml中移除一个容器服务时,需要加上--remove-orphans选项
  • sudo docker-compose restart // 当我们把本地的文件或目录挂载到容器里面,如果修改了本地文件的时候,需要restart 重启服务

docker-compose网络

  1. alpine是一个完整的操作系统镜像,因为其小巧,功能完备的特点,非常适合作为容器的基础镜像
    docker-compose.yml文件:
点击查看代码
version: "3.8"

services:
    box1:
        image: 1341935532/net-box
        container_name: box1
        command: /bin/sh -c "while true; do sleep 3600; done"
        networks:
            - mynetwork1

    box2:
        image: 1341935532/net-box
        container_name: box2
        command: /bin/sh -c "while true; do sleep 3600; done"
        networks:
            - mynetwork1
            - mynetwork2

networks:
    mynetwork1:
        name: mynetwork1
        ipam:
            driver: default
            config:
                - subnet: 192.168.1.0/24
    mynetwork2:
        name: mynetwork2
        ipam:
            driver: default
            config:
                - subnet: 192.168.2.0/24

docker-compose水平扩展和负载均衡

  1. docker-compose文件
点击查看代码
version: "3.8"

services:
    flask-demo:
        # 构建镜像
        build:  # 指定包含构建上下文的路径,或作为一个对象,该对象具有context和指定的dockerfile,以及args参数
            context: .  # 指定Dockerfile文件所在路径
            dockerfile: Dockerfile  # context指定目录下dockerfile的文件名称
        image: flask-demo
        # container_name: flask-demo
        # command:  # 覆盖默认镜像里执行的命令,支持shell格式和exec格式
        environment:
            - REDIS_HOST=redis-server
        # volumes:
        #    - ./app:/usr/src/app
        networks:
            - demo-network
        # ports:
        #    - 8008:5000
        depends_on:
            - redis-server
        restart: on-failure:10

    redis-server:
        image: redis:latest
        container_name: redis-server
        networks:
            - demo-network
        restart: on-failure:10

    client:
        image: 1341935532/net-box
        container_name: client
        networks:
            - demo-network
        restart: on-failure:10
        command: /bin/sh -c "while true; do sleep 3600; done"

networks:  # 相当于 docker network create demo-network
    demo-network:  # 指定网络名称
        external: false  # 为true表示从外部去找,没有就报错,为false是优先从外部找,没有就创建,默认值也是false
        name: demo-network
        ipam:
            driver: default
            config:
                - subnet: 192.168.1.0/24
  1. Dockerfile文件
FROM python:3.9-slim

WORKDIR /usr/src/app

COPY app /usr/src/app

RUN bash run.sh

EXPOSE 5000

ENTRYPOINT ["flask", "run"]
CMD ["-h", "0.0.0.0"]
  1. app/app.py
import socket
import os

import redis
from flask import Flask

pool = redis.ConnectionPool(host=os.environ.get("REDIS_HOST"), port=6379, db=0, encoding="utf-8")
redis_ = redis.Redis(connection_pool=pool)

app = Flask(__name__)

@app.route("/")
def hello():
    redis_.incr("hits")
    return f"hello container world, I have been seen 【{redis_.get('hits').decode('utf-8')}】 times and myhostname is {socket.gethostname()} 哈哈哈\n"
  1. app/requirements.txt文件
flask
redis
  1. app/run.sh文件
#!/usr/bin/env bash
pip install --no-cache-dir -U -r requirements.txt -i https://pypi.douban.com/simple
  1. 水平扩展启动多个flask应用
    sudo docker-compose up -d --scale flask-demo=3
  2. 水平减少flask应用
    sudo docker-compose up -d --scale flask-demo=1
  3. docker-compose的负载均衡作用:
    先启动三个flask应用
    sudo docker-compose up -d --scale flask-demo=3
    然后通过客户端去ping flask应用
    sudo docker exec client ping flask-demo
    我们执行该条命令多次,发现ping出来的ip不一样,也就是ping出来的是刚刚启动的3个flask中的应用,
    dns的解析,以及负载均衡都是docker engine帮我们做的。
  4. 我们在通过客户端请求我们的flask应用测试
    sudo docker exec client curl flask-demo:5000
    执行多次,发现也帮我们进行了flask应用的负载均衡。

完整实例代码

docker-compose使用环境变量

redis服务设置了密码,flask应用配置环境变量保存redis密码
完整代码示例

服务依赖和健康检查(上)

  • docker有健康检查的功能,docker-compose也有健康检查的功能
  1. Dockerfile里面定义健康检查的命令
FROM python:3.9-slim

COPY app /usr/src/app

WORKDIR /usr/src/app

RUN bash run.sh

EXPOSE 5000

ENV FLASK_APP=app.py REDIS_HOST=redis

HEALTHCHECK --interval=30s --timeout=3s \
    CMD curl -f http://localhost:5000 || exit 1

ENTRYPOINT ["flask", "run"]
CMD ["-h", "0.0.0.0"]

只有Dockerfile修改了,其它代码跟完整实例一致,完整实例代码
2. 把之前通过docker-compose启动的容器全部删掉,镜像删除掉
通过Dockerfile创建我们的flask应用
sudo docker build -t flask-demo .
通过镜像创建容器,注意指定连接redis需要的密码环境变量
sudo docker container run -d --network backend --name flask-demo --env REDIS_PASS=abc132 flask-demo
因为我们设置了健康检查是每30s检查一次,默认值retries是大于等于3次失败就unhealthy了,所以等一分半以后我们查看容器:
sudo docker ps -a 此时,容器已经不健康了,因为我们虽然启动了flask应用,但是我们没有启动redis服务,
所以连接redis失败,所以就不健康了。
3. 启动我们的redis服务
sudo docker container run -d --name redis --network backend redis:latest redis-server --requirepass abc132
有3点需要注意:一是--name redis需要和Dockerfile中配置的REDIS_HOST一致,二是网络需要一致, 三是密码需要一致
4. 过30s后,我们再次查看flask应用服务的健康状态,发现已经healthy健康了
sudo docker ps -a
查看容器健康检查的每次请求详细信息:sudo docker container inspect flask-demo

服务依赖和健康检查(下)

健康检查是容器运行状态的高级检查,主要是检查容器所运行的进程是否能正常对外提供服务,
比如一个数据库容器,我们不光要这个容器是up的状态,我们还需要这个容器的数据库进程能够正常的对外提供服务,
这就是所谓的健康检查。
docker-compose中如何使用健康检查呢,我们来看看

  1. 删除Dockerfile中的健康检查指令,在docker-compose.yml中增加健康检查
点击查看代码
version: "3.8"

services:
    flask:
        # 构建镜像
        build:  # 指定包含构建上下文的路径,或作为一个对象,该对象具有context和指定的dockerfile,以及args参数
            context: .  # 指定Dockerfile文件所在路径
            dockerfile: Dockerfile  # context指定目录下dockerfile的文件名称
        image: flask-demo
        # container_name: flask-demo  # 需要使用--scale启动多个flask应用,所以不能设置容器名称
        # command:  # 覆盖默认镜像里执行的命令,支持shell格式和exec格式
        environment:
            - REDIS_PASS=${REDIS_PASSWORD}
            - REDIS_HOST=redis-server
            - FLASK_DEBUG=debug
        healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:5000"]
            interval: 5s
            timeout: 3s
            retries: 3
            start_period: 40s
        # volumes:
        #    - ./app:/usr/src/app
        networks:
            - backend
            - frontend
        # ports:
        #    - 8008:5000
        depends_on:
            redis-server:
                condition: service_healthy
        restart: on-failure:10

    redis-server:
        image: redis:latest
        command: redis-server --requirepass ${REDIS_PASSWORD}
        container_name: redis-server
        healthcheck:
            test: ["CMD", "redis-cli", "ping"]
            interval: 1s
            timeout: 3s
            retries: 15
        networks:
            - backend
        restart: on-failure:10

    nginx:
        image: nginx:stable-alpine
        container_name: nginx_server
        networks:
            - frontend
        volumes:
            - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
            - ./var/log/nginx:/var/log/nginx
        ports:
            - 80:80
        depends_on:
            flask:
                condition: service_healthy

networks:  # 相当于 docker network create demo-network
    backend:  # 指定网络名称
        external: false  # 为true表示从外部去找,没有就报错,为false是优先从外部找,没有就创建,默认值也是false
        name: backend
        ipam:
            driver: default
            config:
                - subnet: 192.168.1.0/24
    frontend:  # 指定网络名称
        external: false  # 为true表示从外部去找,没有就报错,为false是优先从外部找,没有就创建,默认值也是false
        name: frontend
        ipam:
            driver: default
            config:
                - subnet: 192.168.2.0/24
  1. 启动服务
    sudo docker-compose up -d --scale flask=3
    重启nginx服务:sudo docker-compose restart
    大工告成啊!!!

参考文档

投票app经典案例

posted @ 2022-05-30 18:26  专职  阅读(269)  评论(0编辑  收藏  举报