一、前言
我们让应用工作在容器中是非常简单和方便的,但往往一个应用还要依赖数据库、缓存等应用,这样一组应用需要协同启动,同时这样一组应用也要工作在同一个网络中,以便相互访问,并跟不同组的应用之间隔离,以减少干扰,要达到这样的效果,我们需要自己控制好容器创建、启动等等,是一个麻烦的过程,不过官方为我们提供了容器编排工具,Docker Compose让我们可以便捷进行容器编排~
1、本文主要内容
- Docker Compose介绍与安装
- Docker Compose基础使用
- Docker Compose进阶使用
- Docker Compose参数详解
- Docker Compose常用命令
2、本文环境信息
环境 | 说明 |
---|---|
Docker | Docker CE 20.10.22 |
Docker Desktop | 4.16 |
Docker Redis镜像 | 7.0.x |
Docker Golang镜像 | 1.20 |
Golang | 1.20 |
Windows | Windows 11 |
curl for Windows | 7.87 |
二、Docker Compose介绍与安装
1、Docker Compose介绍
Docker Compose是一个用于定义和运行多个Docker容器的工具,基于Docker Compose可以通过YML文件定义一个服务,以及服务所有的依赖,然后使用docker compose命令构建并运行相关的容器
Compose 中有两个重要的概念:
服务 (service):一个应用的容器,可以包括多个运行相同镜像的容器实例
项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义
Docker Compose前身是开源项目Fig,Docker已经用Golang重写,并称之为Docker ComposeV2
2、Docker Compose安装
如果是在macOS/Windows使用的Docker Desktop那么已经自带Docker Compose,如果是Docker on Linux环境,则需要单独安装
代码语言:javascript复制# 下载Docker Compose到指定目录
curl -SL https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
# 创建Docker Compose软链接
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
# 查看Docker Compose版本
docker compose version
三、Docker Compose基本使用
创建composetest文件夹,用于准备测试的代码、Dockerfile、docker-compose.yml等文件,后续的操作未指定目录的,默认都在该目录下操作
1、代码准备
用golang写的一个简单http server,监听8000端口,对访问的请求通过Redis进行计数,RedisHost命名为:redis,后续需要在桥接网络下创建同名的Redis容器使用
新建helloweb.go保存以下代码
代码语言:javascript复制package main
import (
"fmt"
"github.com/go-redis/redis"
"log"
"net/http"
)
var cache = redis.NewClient(&redis.Options{
Addr: "redis:6379",
})
func handler(w http.ResponseWriter, r *http.Request) {
log.Println("request:", r.Host, r.URL)
fmt.Fprintf(w, "Hello, World! ---docker compose test by ken.io")
fmt.Fprintf(w, "rnTotal view:%s", cache.Incr("count"))
}
func main() {
http.HandleFunc("/", handler)
log.Println("starting server on port 8000")
log.Fatal(http.ListenAndServe(":8000", nil))
}
2、Dockerfile准备
新建helloweb.build作为Dockerfile,并填充以下内容
代码语言:javascript复制# 使用官方提供的 Go 镜像作为基础镜像
FROM golang:1.20
# 将工作目录设置为 /app
WORKDIR /app
# 将helloweb.go复制到 /app 下
COPY helloweb.go /app
# 导入依赖的Redis go module
RUN go mod init helloweb
RUN go get github.com/go-redis/redis
# 允许宿主机访问容器的 8000 端口
EXPOSE 8000
# 设置容器进程为:go run helloweb.go
CMD go run helloweb.go
3、准备Docker Compose配置
新建docker-compose.yml 文件,并填充以下内容
代码语言:javascript复制version: '3'
services:
helloweb:
build:
dockerfile: "helloweb.build"
ports:
- "8000:8000"
redis:
image: "redis:latest"
4、启动项目
代码语言:javascript复制# 启动项目(Project)
docker-compose up
# 输出示例
#******省略部分内容******#
composetest-redis-1 | * Ready to accept connections
composetest-helloweb-1 | starting server on port 8000
5、验证
5.1、访问HelloWeb,验证应用启动是否符合预期
代码语言:javascript复制curl localhost:8000
# 输出示例
Hello, World! ---docker compose test by ken.io
Total view:incr count: 19
5.2、查看容器运行情况
代码语言:javascript复制docker ps
# 输出示例
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b9aa36f8a947 composetest-helloweb "/bin/sh -c 'go run …" 17 minutes ago Up 17 minutes 0.0.0.0:8000->8000/tcp composetest-helloweb-1
f7199457dcab redis:latest "docker-entrypoint.s…" 17 minutes ago Up 17 minutes 6379/tcp composetest-redis-1
通过查看运行的容器可以发现,docker-compose帮我们创建了composetest开头的容器,后面跟着我们在yml文件中指定的service name,以及容器序号
5.3、查看Docker网络
代码语言:javascript复制docker network list
# 输出示例
NETWORK ID NAME DRIVER SCOPE
f4d483c10c63 bridge bridge local
b366dd80f6e2 composetest_default bridge local
docker-compose自动创建了名为composetest_default的桥接网络,并在创建容器时指定了网络,做到了网络隔离和容器互联
在Docker Desktop的 Containers管理界面也可以看到启动的容器以及容器的关联关系
5.4、关闭项目(Project)
代码语言:javascript复制docker-compose down
四、Docker Compose进阶使用
通过前面章节的介绍,可以了解/掌握Docker Compose的基础使用,但要更好的使用,还需要掌握一些高阶操作:自定义Project名称、容器名称、网络名称&类型、指定数据卷、设置环境变量,以及对服务进行扩容等等,本章节就介绍下Docker Compose更完整的使用
创建composetest2文件夹,用于准备测试的代码、Dockerfile、docker-compose.yml等文件,后续的操作未指定目录的,默认都在该目录下操作
1、准备代码
基于之前的代码:用golang写的一个简单http server,监听8000端口,对访问的请求通过Redis进行计数,在此基础之上增加日志写入和读取的功能,并且在日志中增加环境和主机名信息以便后续测试
新建helloweb.go保存以下代码
代码语言:javascript复制package main
import (
"fmt"
"github.com/go-redis/redis"
"io"
"log"
"net/http"
"os"
)
var cache = redis.NewClient(&redis.Options{
Addr: "redis:6379",
})
var env = "DEV"
var logRoot = "/app/logs/"
var logFilePath = logRoot "default.log"
// 获取主机名
func getHostName() string {
hostname, err := os.Hostname()
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
}
return hostname
}
// 日志配置
func configLog() {
// 创建文件夹
err := os.MkdirAll(logRoot, 0755)
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
return
}
// 打开或者创建一个日志文件
file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
return
}
// 设置日志输出到文件&终端
multiLog := io.MultiWriter(file, os.Stdout)
log.SetOutput(multiLog)
// 设置环境变量env、hostname作为日志前缀
log.SetPrefix("[" env "] - " getHostName() " - ")
}
// 读取日志文件
func readLog() string {
file, err := os.Open(logFilePath)
if err != nil {
log.Fatalf("Failed to open log file: %v", err)
return ""
}
defer file.Close()
content, err := io.ReadAll(file)
return string(content)
}
// 处理HTTP请求
func handler(w http.ResponseWriter, r *http.Request) {
log.Println("request:", r.Host, r.URL)
if r.URL.Path == "/log" {
fmt.Fprintf(w, readLog())
} else {
fmt.Fprintf(w, "Hello, World! ---docker compose helloweb by ken.io")
fmt.Fprintf(w, "rnTotal view:%s", cache.Incr("count"))
}
fmt.Fprintf(w, "rnhost:%s,env:%s", getHostName(), env)
}
func main() {
//初始化日志
configLog()
//设置环境变量
if os.Getenv("env") != "" {
env = os.Getenv("env")
}
http.HandleFunc("/", handler)
log.Println("starting server on port 8000")
log.Fatal(http.ListenAndServe(":8000", nil))
}
2、准备Docker Compose配置
新建docker-compose.yml 文件,并填充以下内容
代码语言:javascript复制version: '3'
name: helloweb
services:
web:
build:
dockerfile: "helloweb.build"
ports:
- "8001-8010:8000" #指定多个端口以支持扩容
volumes:
- default_vol:/app/logs
networks:
- default_net
environment:
env: TEST
redis:
container_name: myredis
image: "redis:latest"
networks:
- default_net
volumes:
default_vol: {}
networks:
default_net:
driver: bridge
3、启动项目
Dockerfile无需调整,复制前面使用的即可
代码语言:javascript复制# 启动项目(-d表示后台运行)
docker-compose up -d
# 输出示例
[ ] Running 4/4
⠿ Network helloweb_default_net Created
⠿ Volume "helloweb_default_vol" Created
⠿ Container myredis Started
⠿ Container helloweb-web-1 Started
由于我们指定了project name,所以网络名、数据卷名都会默认加上前缀helloweb,services没有指定container_name的情况下也会默认加上前缀helloweb
4、基本信息确认
4.1、访问HelloWeb,验证应用启动是否符合预期
代码语言:javascript复制curl localhost:8001
# 输出示例
Hello, World! ---docker compose helloweb by ken.io
Total view:incr count: 1
host:3d1c5ab79c29,env:TEST
4.2、其他信息查看
代码语言:javascript复制# 查看容器运行情况
docker ps
# 查看网络情况
docker network list
docker inspect helloweb_default_net
# 查看数据卷情况
docker volume list
docker inspect helloweb_default_vol
5、数据卷验证
代码语言:javascript复制# 访问测试
curl localhost:8001/flag
# 停用helloweb并删除容器
docker compose down
# 再此拉起helloweb
docker compose up -d
# 查看日志
curl localhost:8001/log
# 输出示例
[DEV] - 3d1c5ab79c29 - 2023/03/09 22:01:55 starting server on port 8000
[DEV] - 3d1c5ab79c29 - 2023/03/09 22:02:43 request: localhost:8001 /
[DEV] - 3d1c5ab79c29 - 2023/03/09 22:05:45 request: localhost:8001 /flag
[DEV] - 634edcb7456e - 2023/03/09 22:08:00 starting server on port 8000
[DEV] - 634edcb7456e - 2023/03/09 22:08:10 request: localhost:8001 /log
6、扩容测试
6.1、执行扩容
代码语言:javascript复制# 将web的容器扩容至2个
docker compose up -d --scale web=2
# 输出示例
[ ] Running 3/3
⠿ Container myredis Running
⠿ Container helloweb-web-2 Started
⠿ Container helloweb-web-1 Started
# 查看扩容后的容器
docker compose ps
# 输出示例
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
helloweb-web-1 helloweb-web "/bin/sh -c 'go run …" web 3 minutes ago Up 3 minutes 0.0.0.0:8002->8000/tcp
helloweb-web-2 helloweb-web "/bin/sh -c 'go run …" web 3 minutes ago Up 3 minutes 0.0.0.0:8003->8000/tcp
myredis redis:latest "docker-entrypoint.s…" redis 6 minutes ago Up 6 minutes 6379/tcp
在Docker Desktop中查看容器的情况,扩容之后原来的web-1容器被销毁,扩容后的两个web服务实例又分配了新端口
6.2、访问测试
代码语言:javascript复制curl localhost:8002
#输出示例
Hello, World! ---docker compose helloweb by ken.io
Total view:incr count: 11
host:67086ee805cc,env:TEST%
curl localhost:8003
#输出示例
Hello, World! ---docker compose helloweb by ken.io
Total view:incr count: 12
host:a582478a0cdd,env:TEST%
# 查看日志(将会看到8001-8003三个容器的日志)
curl localhost:8002/log
这里通过—scale对web进行了扩容,compose组件根据指定的端口范围进行了顺序使用
另外,还可以进一步通过Nginx或者服务注册与发现等方式实现负载均衡,这里就不再赘述
五、备注
1、Docker Compose常用命令
docker compose相关命令要在docker-compose.yml所在路径执行,或者通过-f参数手动指定配置文件
代码语言:javascript复制# 启动Project(创建并启动容器)
docker compose up
# 启动Project(创建并启动容器完整参数示例)
docker compose -p helloweb -f helloweb.yml up -d
# -p,指定ProjectName,权重高于配置文件
# -f,指定配置文件
# -d,后台启动
# 查看所有容器
docker compose ps
# 停用Porject(停用Porject包含容器)
docker compose stop
# 启动Porject(启动Porject包含容器)
docker compose start
# 重启Porject(启动Porject包含容器)
docker compose restart
# 删除Porject(停用并删除Porject包含容器及网络)
docker compose down
# 查看所有Porject
docker compose ls
2、Docker Compose常用参数
主参数 | 子参数 | 说明 |
---|---|---|
version | - | docker-compose配置文件版本,目前最新的是3.x |
name | - | Project名称,缺省值为文件夹名称 |
services | - | 一个应用的容器,可以包括多个运行相同镜像的容器实例 |
{service} | container_name | 指定容器名称,需要注意:指定名称后,影响up —scale扩容指令,所以不建议指定 |
{service} | build | 镜像构建参数 |
{service} | dockerfile | dockerfile文件 |
{service} | labels | 镜像构建标签 |
{service} | ports | 端口映射,可以按范围指定;例如:- “8000:8000”,- “8001-8100:8000” |
{service} | volumes | 指定数据卷,例如 - default_vol:/app/logs |
{service} | networks | 指定网络,例如- default_net |
{service} | environment | 环境变量,例如:env: DEV |
{service} | image | 指定镜像 |
volumes | - | 创建数据卷,例如:default_vol:{},如果已存在会跳过 |
networks | - | 创建网络,如果已存在会跳过 |
{network} | driver | 指定网络类型,例如:bridge |
3、可能碰到的问题
问题1:no configuration file provided: not found
原因:执行命令的路径没有docker-compose.yml配置文件,切换目录执行或者使用-f参数指定配置文件
代码语言:javascript复制# 报错示例
docker compose restart
no configuration file provided: not found
问题2:修改docker compose配置后未生效
原因:通过docker compose down删除project只涉及容器及网络,如果涉及到到镜像或者数据卷的变化,可以删除镜像、数据卷
代码语言:javascript复制# 删除容器
docker image rm helloweb-web
# 删除数据卷
docker volume rm helloweb_default_vol
4、相关阅读
https://docs.docker.com/compose/install/other/
https://docs.docker.com/compose/gettingstarted/
- 系列名称:Docker入门教程
- 上一篇:Docker入门:端口映射与容器互联
- 下一篇:暂无下一篇,敬请期待!