Docker Swarm入门:容器编排与服务部署

2023-06-06 15:33:53 浏览数 (1)

一、前言

Docker Swarm是Docker官方提供的容器集群管理以及容器编排解决方案,Docker Swarm基于Docker Compose组件以及网络等基础能力,提供了服务编排、负载均衡、动态伸缩、滚动更新等能力,本文ken.io主要介绍基于Docker Swarm进行容器编排、服务部署与更新等等

1、本文主要内容

  • 使用 Docker Swarm 部署一组服务
  • 使用 Docker Swarm 部署/扩容服务并指定节点
  • 使用 Docker Swarm 滚动更新服务
  • 使用 Docker Swarm 回滚服务

2、本文环境信息

环境

说明

Docker

Docker CE 23.0.1

Linux Server

Ubuntu 22.04.2 LTS

Golang

1.20

Windows

Windows 11

curl for Windows

7.87

3、前置知识

  • Docker入门教程 - Ken的杂谈
  • 安装CentOS7虚拟机 - Ken的杂谈
  • Docker Swarm入门:集群搭建与管理 - Ken的杂谈

二、准备工作

1、部署Docker Swarm集群

参考:Docker Swarm入门:集群搭建与管理 - Ken的杂谈 ,准备Docker Swarm集群,3-5个节点即可

机器名

IP

必要

操作系统

环境信息

swarm-manager-01

192.168.99.131

openEuler 22.03 LTS /CentOS 7

Docker CE 23.0.1

swarm-manager-02

192.168.99.132

openEuler 22.03 LTS /CentOS 7

Docker CE 23.0.1

swarm-manager-03

192.168.99.133

openEuler 22.03 LTS /CentOS 7

Docker CE 23.0.1

swarm-worker-01

192.168.99.141

openEuler 22.03 LTS /CentOS 7

Docker CE 23.0.1

swarm-worker-02

192.168.99.142

openEuler 22.03 LTS /CentOS 7

Docker CE 23.0.1

为了方便测试,请关闭防火墙,或除Docker Swarm必要端口外,开放8000、8080端口

2、准备代码

创建镜像制作根目录,例如:d:dockerhelloworld(Windows),~/docker/helloworld(macOS),用于存放代码以及Dockerfile等文件

这里ken.io用golang写的一个简单http server,监听8000端口,对访问的请求通过Redis进行计数并记录日志,在日志中增加环境、服务版本、主机名等信息,并提供日志读取入口,以便后续测试~

新建helloweb.go保存以下代码

代码语言:javascript复制
package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
    "os"
    "runtime"
    "strings"

    "github.com/go-redis/redis"
)

var cache = redis.NewClient(&redis.Options{
    Addr: "redis:6379",
})

var version = "1.0"
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
}

// 获取IP地址(IPV4)
func getIpAddresses() string {
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        log.Fatal(err)
        return ""
    }
    var ips []string
    for _, address := range addrs {
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                ips = append(ips, ipnet.IP.String())
            }
        }
    }
    return strings.Join(ips, ",")
}

// 日志配置
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   "] - Service:"   version   " - "   getHostName()   " - "   getIpAddresses()   " - ")

}

// 读取日志文件
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! ---helloweb image by ken.iorn")
        fmt.Fprintf(w, "Total view:%srn", cache.Incr("count"))
    }
    fmt.Fprintf(w, "Host:%s,OS:%s/%srn", getHostName(), runtime.GOOS, runtime.GOARCH)
    fmt.Fprintf(w, "ENV:%s,IP:%srn", env, getIpAddresses())
    fmt.Fprintf(w, "Service Version:%srn", version)
}

func main() {
    //设置环境变量
    if os.Getenv("env") != "" {
        env = os.Getenv("env")
    }
    //初始化日志
    configLog()
    //处理HTTP请求
    http.HandleFunc("/", handler)
    log.Println("starting server on port 8000")
    log.Fatal(http.ListenAndServe(":8000", nil))
}

3、Dockerfile准备

新建helloweb.build作为Dockerfile,并填充以下内容

代码语言:javascript复制
# 使用官方提供的 Go 镜像作为基础镜像
FROM golang:1.20

# 将工作目录设置为 /app
WORKDIR /app

# 将helloweb.go复制到 /app 下
COPY helloweb.go /app

# 设置go mod 镜像
RUN go env -w GO111MODULE=on
RUN go env -w  GOPROXY=https://goproxy.cn,direct

# 导入依赖的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

4、制作镜像

代码语言:javascript复制
# 登录
docker login

# 进入目录
d: && cd d:dockerhelloweb

# 查看Buildx版本(确认buildx已启用,如未启用,使用docker buildx install安装)
docker buildx version

# 构建镜像并推动到DockerHub(默认为latest)(注意结尾一定要加.)
# 这里选择构建常见的linux/amd64,linux/arm64 架构,如需其他的可以自行追加
docker buildx build 
--platform linux/amd64,linux/arm64 
-f helloweb.build   
-t kentalk/helloweb:1.0 --push .

三、服务部署与容器编排

在Docker Swarm环境中,可以在Manager节点通过docker service create 命令创建一个服务

代码语言:javascript复制
docker service create --replicas 1 --name myweb01 -p 8001:8000 kentalk/helloworld

但往往我们的服务还会依赖其他下游服务,以及数据库、缓存等,如果基于docker service create命令来创建服务,那我们就要逐个进行依赖的创建、服务的创建等等,这是比较麻烦的,不过Docker Swarm支持使用Compose文件来一次配置、启动多个服务,

1、准备Compose配置

在Swarm任意Manager节点,新建目录/var/docker,然后新建helloweb.yml并保存以下内容:

代码语言:javascript复制
# 创建目录
mkdir /var/docker

# 创建文件
vi /var/docker/helloweb.yml
代码语言:javascript复制
version: '3'

services:
  web:
    image: "kentalk/helloweb:1.0"
    ports:
      - "8000:8000"
    networks:
      - default_net
    environment:
      env: TEST
    deploy:
      mode: replicated
      replicas: 2
      placement:
        constraints: [node.role == worker]


  redis:
    image: "redis:6.0"
    networks:
      - default_net
    deploy:
      placement:
        constraints: [node.role == worker]

networks:
  default_net:
    driver: overlay

这里定义了两个服务web、redis,并制定了端口、网络、环境等参数,也约束了只在Work节点部署

其中网络配置以及部署节点的约束也可不指定,根据情况来即可

2、部署并测试服务

2.1、部署服务

通过Compose配置部署一组服务使用 docker stack deploy命令,其中 -c 参数指定 compose 文件

代码语言:javascript复制
# 进入配置目录
cd /var/docker

# 部署服务
docker stack deploy -c helloweb.yml helloweb

# 输出示例
Creating network helloweb_default_net
Creating service helloweb_redis
Creating service helloweb_web

2.2、测试服务

在宿主机执行以下命令,进行服务访问测试,并查看相关信息

代码语言:javascript复制
# 访问默认地址
curl 192.168.99.131:8000
curl 192.168.99.141:8000
curl 192.168.99.142:8000

# 输出示例
Hello, World! ---helloweb image by ken.io
Total view:incr count: 3
Host:2e66c23b1d66,OS:linux/arm64
ENV:TEST,IP:10.0.0.33,172.18.0.4,10.0.8.7
Service Version:1.0

虽然load balancer提供了负债均衡的能力,且默认负载均衡策略师轮询,但并不是真正的逐个访问,在访问到一定量级时才会接近轮询的访问分布,所以这里看到的Host大概率是同一个

返回内容中可以关注incr count 3,以及ENV:TEST,这说明Redis在正常工作,环境参数也生效了

3、查看服务信息

3.1、查看Stack

代码语言:javascript复制
# 查看所有Stack
docker stack ls

# 输出示例
NAME       SERVICES
helloweb   2

3.2、查看Stack服务信息

代码语言:javascript复制
# 查看Stack服务信息
docker stack services helloweb

# 输出示例
ID             NAME             MODE         REPLICAS   IMAGE                  PORTS
jcbr9ao9fa05   helloweb_redis   replicated   1/1        redis:6.0              
06nty9feerlp   helloweb_web     replicated   2/2        kentalk/helloweb:1.0   *:8000->8000/tcp

3.3、查看Stack任务信息

代码语言:javascript复制
# 查看Stack任务信息
docker stack ps helloweb

# 输出示例
ID             NAME               IMAGE                  NODE              DESIRED STATE   CURRENT STATE        
2pnu76lzrwpn   helloweb_redis.1   redis:6.0              swarm-worker-01   Running         Running about a minute ago
w707x4pl94tb   helloweb_web.1     kentalk/helloweb:1.0   swarm-worker-02   Running         Running about a minute ago
85956lq880rd   helloweb_web.2     kentalk/helloweb:1.0   swarm-worker-01   Running         Running about a minute ago

3.4、查看服务信息

代码语言:javascript复制
# 查看服务信息
docker service ps helloweb_web

# 输出示例
ID             NAME             IMAGE                  NODE              DESIRED STATE   CURRENT STATE
w707x4pl94tb   helloweb_web.1   kentalk/helloweb:1.0   swarm-worker-02   Running         Running 2 minutes ago
85956lq880rd   helloweb_web.2   kentalk/helloweb:1.0   swarm-worker-01   Running         Running about a minute ago

3.5、查看网络信息

代码语言:javascript复制
# 查看网络信息
docker network inspect helloweb_default_net

四、服务滚动升级

1、修改代码

修改代码中的版本为1.1

代码语言:javascript复制
/*
*省略部分内容
*/

var version = "1.1"

/*
*省略部分内容
*/

2、制作镜像

制作并上传kentalk/helloweb:1.1 版本镜像

代码语言:javascript复制
docker buildx build 
--platform linux/amd64,linux/arm64 
-f helloweb.build   
-t kentalk/helloweb:1.1 --push .

3、滚动升级

将服务helloweb_web滚动升级到kentalk/helloweb:1.1版本

代码语言:javascript复制
# 滚动升级服务
docker service update --image kentalk/helloweb:1.1 helloweb_web

# 输出示例
helloweb_web
overall progress: 2 out of 2 tasks 
1/2: running   [==================================================>] 
2/2: running   [==================================================>] 
verify: Service converged

滚动升级过程中,Docker Swarm会先启动新版本的任务容器,新的任务容器启动成功后替换旧的任务容器

在滚动升级过程中,新开一个Manger节点连接,不断执行docker service ps helloweb_web,就可以看到这个过程

升级完成后,我我们也可以通过命令查看服务状态

代码语言:javascript复制
# 查看服务详情
docker service ps helloweb_web

# 输出示例
ID             NAME                 IMAGE                  NODE              DESIRED STATE   CURRENT STATE
o1yzdlbdklab   helloweb_web.1       kentalk/helloweb:1.1   swarm-worker-02   Running         Running about a minute ago
w707x4pl94tb    _ helloweb_web.1   kentalk/helloweb:1.0   swarm-worker-02   Shutdown        Shutdown about a minute ago
tjajda18uebt   helloweb_web.2       kentalk/helloweb:1.1   swarm-worker-01   Running         Running about a minute ago
85956lq880rd    _ helloweb_web.2   kentalk/helloweb:1.0   swarm-worker-01   Shutdown        Shutdown about a minute ago

这里我们会发现原有两个1.0版本的任务实例还是保留的,这有可能助于特殊情况进行手动回滚(但是ken.io没找到相关办法)

这里我们通过命令访问下helloweb,测试下升级后的服务

代码语言:javascript复制
# 访问服务
curl 192.168.99.131:8000

# 输出实例
Hello, World! ---helloweb image by ken.io
Total view:incr count: 6
Host:4742142b0647,OS:linux/arm64
ENV:TEST,IP:10.0.0.35,172.18.0.3,10.0.8.10
Service Version:1.1

4、服务扩容

代码语言:javascript复制
# 扩容helloweb_web
docker service scale helloweb_web=4

# 输出示例
helloweb_web scaled to 4
overall progress: 4 out of 4 tasks 
1/4: running   [==================================================>] 
2/4: running   [==================================================>] 
3/4: running   [==================================================>] 
4/4: running   [==================================================>] 
verify: Service converged 

# 查看扩容后的服务
docker service ps helloweb_web

五、服务回滚

为了更好的测试,服务回滚基于Redis服务来作

1、升级服务

1.1、 升级Redis到7.0

代码语言:javascript复制
# 升级Redis到7.0
docker service update --image redis:7.0 helloweb_redis

# 查看升级后的服务
docker service ps helloweb_redis

#输出示例
docker service ps helloweb_redis
ID             NAME                   IMAGE       NODE              DESIRED STATE   CURRENT STATE
ujb6gzk1aik8   helloweb_redis.1       redis:7.0   swarm-worker-01   Running         Running 2 seconds ago
2pnu76lzrwpn    _ helloweb_redis.1   redis:6.0   swarm-worker-01   Shutdown        Shutdown 3 seconds ago

1.2、 访问测试

这里我们通过命令访问下helloweb,测试下升级后的情况

代码语言:javascript复制
# 访问服务
curl 192.168.99.131:8000

# 输出示例
Hello, World! ---helloweb image by ken.io
Total view:incr count: 1
Host:4742142b0647,OS:linux/arm64
ENV:TEST,IP:10.0.0.35,172.18.0.3,10.0.8.10
Service Version:1.1

2、回滚服务

代码语言:javascript复制
# 回滚Redis
docker service rollback helloweb_redis

# 输出示例
helloweb_redis
rollback: manually requested rollback 
overall progress: rolling back update: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

# 查看升级后的服务
docker service ps helloweb_redis

#输出示例
ID             NAME                   IMAGE       NODE              DESIRED STATE   CURRENT STATE
6dmt5vmoqquv   helloweb_redis.1       redis:6.0   swarm-worker-01   Running         Running 23 minutes ago
ujb6gzk1aik8    _ helloweb_redis.1   redis:7.0   swarm-worker-01   Shutdown        Shutdown 23 minutes ago
2pnu76lzrwpn    _ helloweb_redis.1   redis:6.0   swarm-worker-01   Shutdown        Shutdown 29 minutes ago

这时候会发现,使用docker service rollback回滚服务,只是帮我们根据上一个版本重新创建了容器,所以Docker Swarm提供的回滚并不适用于有状态的服务

六、备注

1、其他命令

代码语言:javascript复制
# 停用Stack
docker stack down helloweb

# 删除Stack
docker stack rm helloweb

2、本文参考

  • https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/
  • https://docs.docker.com/engine/reference/commandline/stack/
  • https://docs.docker.com/engine/swarm/swarm-tutorial/scale-service/

0 人点赞