一、前言
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/