Docker 系统性入门+进阶实践-07Docker compose
- docker compose是复杂应用在单机环境下编排的必备工具
compose文件的结构和版本
- 基本的语法结构
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
- 以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镜像构建和拉取
- docker-compose up之前我们可以先拉取服务需要的镜像,以及构建服务需要的镜像
拉取redis镜像:sudo docker-compose pull
构建flask-demo镜像:sudo docker-compose build flask-demo
- 然后再通过docker-compose创建容器,启动容器就快很多了
sudo docker-compose up [-d]
docker-compose服务更新
- 当我们对应用程序的文件做了修改,对镜像来讲,如果我们
sudo docker image build -t xxx .
的话,
就会发现文件做了修改,重新build镜像,而对于docker-compose来讲,如果还用原来的方法启动的话就不会重新build镜像
sudo docker-compose up -d
这样启动的话,即使我们对应用程序做了修改,docker-compose也不会重新构建镜像,
但是我们需要重新构建镜像,那么就需要加个参数
sudo docker-compose up -d --build
这样就会重新构建镜像,应用程序的改变也就生效了。 - 我们在增加一个容器服务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网络
- 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水平扩展和负载均衡
- 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
- 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
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"
- 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
- 水平扩展启动多个flask应用
sudo docker-compose up -d --scale flask-demo=3
- 水平减少flask应用
sudo docker-compose up -d --scale flask-demo=1
- 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帮我们做的。 - 我们在通过客户端请求我们的flask应用测试
sudo docker exec client curl flask-demo:5000
执行多次,发现也帮我们进行了flask应用的负载均衡。
docker-compose使用环境变量
redis服务设置了密码,flask应用配置环境变量保存redis密码
完整代码示例
服务依赖和健康检查(上)
- docker有健康检查的功能,docker-compose也有健康检查的功能
- 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中如何使用健康检查呢,我们来看看
- 删除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
- 启动服务
sudo docker-compose up -d --scale flask=3
重启nginx服务:sudo docker-compose restart
大工告成啊!!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)