Docker入门:使用Docker Compose进行容器编排

2023-04-07 15:46:54 浏览数 (1)

一、前言

我们让应用工作在容器中是非常简单和方便的,但往往一个应用还要依赖数据库、缓存等应用,这样一组应用需要协同启动,同时这样一组应用也要工作在同一个网络中,以便相互访问,并跟不同组的应用之间隔离,以减少干扰,要达到这样的效果,我们需要自己控制好容器创建、启动等等,是一个麻烦的过程,不过官方为我们提供了容器编排工具,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入门:端口映射与容器互联
  • 下一篇:暂无下一篇,敬请期待!

0 人点赞