从零到一:Docker Compose 轻松部署微服务实战
今天实践一下使用Docker Compose部署一个微服务项目。
项目包括docker compose部署的代码都放在了我的github上:https://github.com/cra358/demo
首先简单写一个微服务项目,主要技术栈:Spring Cloud Alibaba、redis、Mysql、Gateway、SaToken
主要就是用户登陆积分记录功能。用户每登陆系统一次,用户登陆积分+1。
服务划分:
- gatway主要实现登陆校验。
- user实现用户注册、退出、积分查询增长功能。
容器划分:
环境容器:nacos、mysql、redis
服务容器:gateway、user
服务打包package:
先双击clean,清理target目录文件,再双击package文件,点击右上角的>中的带斜线的圈可以跳过test。
在target目录中可以找到对应的jar包。确定没有问题后就可以开始部署了。
安装Docker(略)
Docker 使用的版本是Docker version 27.5.0,安装完成后在pull一下mysql、redis和nacos的镜像,方便后面创建环境容器。本次使用的是mysql8.0、redis7.2.3、nacos v2.2.0版本。
Docker Compose安装
在选择安装版本时,需要注意Docker Compose 版本和Docker版本对应。我使用的Docker Compose版本是Docker Compose version v2.30.0
- https://github.com/docker/compose/releases/tag/v2.30.0。到这里下载Docker Compose文件。
- 将文件移动到:/usr/local/bin/docker-compose
sudo cp docker-compose-linux-x86_64 /usr/local/bin/docker-compose
- 添加可执行权限
chmod +x /usr/local/bin/docker-compose
- 测试安装成功
docker-compose --version
安装成功后,正式准备部署。在项目目录的docker目录中:
docker下的demo目录存放两个服务模块的我们之前打包生成好的jar包和Dockerfile文件:
现在,我们需要为每个微服务编写 Dockerfile内容,用于构建 Docker 镜像。Dockerfile 定义了镜像的构建过程,包括基础镜像、依赖安装、代码复制、启动命令等。
为微服务编写Dockerfile文件
- user
# 基础镜像
FROM openjdk:17
# 挂载目录,就是容器中的目录
VOLUME /home/user
# 创建目录,也是容器中的目录
RUN mkdir -p /home/user
# 指定路径
WORKDIR /home/user
# 复制jar文件到容器指定路径,就是把宿主机中的./jar/user.jar文件复制到容器/home/user/user.jar中
COPY ./jar/user.jar /home/user/user.jar
# 启动用户服务
ENTRYPOINT ["java","-jar","user.jar"]
- gateway
# 基础镜像
FROM openjdk:17
# 挂载目录
VOLUME /home/gateway
# 创建目录
RUN mkdir -p /home/gateway
# 指定路径
WORKDIR /home/gateway
# 复制jar文件到路径
COPY ./jar/gateway.jar /home/gateway/gateway.jar
# 启动用户服务
ENTRYPOINT ["java","-jar","gateway.jar"]
完成上述步骤后,准备环境容器的Docker文件和一些配置:
mysql容器准备:
在db目录下存放项目所需的初始数据库表。同时由于nacos配置依赖于mysql存放相关数据,也在这里存放一个nacos_config.sql文件,文件内容直接网上照抄即可。
db.sql文件如下:
CREATE DATABASE IF NOT EXISTS `demo_user` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `demo_user`;
-- 删除已存在的表(谨慎使用)
DROP TABLE IF EXISTS `user`;
-- 创建 user 表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID,主键',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL COMMENT '密码(存储加密后的密码)',
`score` int(11) NOT NULL DEFAULT '0' COMMENT '用户积分',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`) COMMENT '用户名唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
编写mysql容器的Dockerfile:
# 基础镜像,就是docker images命令下我们之前pull的mysql镜像,据此创建mysql容器
FROM mysql:8.0
# 执行sql脚本
# `/docker-entrypoint-initdb.d/`是MySQL官方镜像中的一个特殊目录,用于存放初始化数据库的脚本文件。
# 把刚刚的两个sql文件存放到mysql容器中
ADD ./db/*.sql /docker-entrypoint-initdb.d/
- redis容器准备:
在docker/redis/conf目录下写一个redis配置文件:redis.conf,这里只写了一个密码:
requirepass 1234
同样,编写redis容器的Dockerfile:
# 基础镜像
FROM redis:7.2.3
# 挂载目录
VOLUME /home/demo/redis
# 创建目录
RUN mkdir -p /home/demo/redis
# 指定路径
WORKDIR /home/demo/redis
# 复制刚刚的redis.conf文件到容器指定路径
COPY ./conf/redis.conf /home/demo/redis/redis.conf
- nacos容器准备:
在docker/nacos/conf中编写nacos配置文件:application.properties
spring.datasource.platform=mysql
spring.sql.init.platform=mysql
db.num=1
# 这里的nacos_config就是nacos在mysql容器中使用到的数据库,和前面的nacos_config.sql中创建出来的数据库要一致
# 下面的配置不一样有可能会导致意想不到的错误
db.url.0=jdbc:mysql://demo-mysql:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
# 制定用户、密码
db.user.0=root
db.password.0=20041111
# 这里后面的直接复制就可以了
nacos.naming.empty-service.auto-clean=true
nacos.naming.empty-service.clean.initial-delay-ms=50000
nacos.naming.empty-service.clean.period-time-ms=30000
management.endpoints.web.exposure.include=*
management.metrics.export.elastic.enabled=false
management.metrics.export.influx.enabled=false
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i
server.tomcat.basedir=/home/demo/nacos/tomcat/logs
nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
nacos.core.auth.system.type=nacos
nacos.core.auth.enabled=false
nacos.core.auth.default.token.expire.seconds=18000
nacos.core.auth.plugin.nacos.token.secret.key=SecretKey012345678901234567890123456789012345678901234567890123456789
nacos.core.auth.caching.enabled=true
nacos.core.auth.enable.userAgentAuthWhite=false
nacos.core.auth.server.identity.key=serverIdentity
nacos.core.auth.server.identity.value=security
nacos.istio.mcp.server.enabled=false
编写nacos的Dockerfile文件:
# 基础镜像
FROM nacos/nacos-server:v2.2.0
# 复制刚刚的application.properties文件到nacos容器的制定路径
COPY ./conf/application.properties /home/nacos/conf/application.properties
到此,我们就准备好了环境容器和服务容器的准备工作。
编写 DockerCompose.yml 文件
version: '3'
services:
demo-nacos:
# 容器名
container_name: demo-nacos
image: nacos/nacos-server:v2.2.0
build:
context: ./nacos
environment:
- MODE=standalone
# 使用 mysql 作为数据库
- SPRING_DATASOURCE_PLATFORM=mysql
- MYSQL_SERVICE_HOST=demo-mysql
- MYSQL_SERVICE_PORT=3306
- MYSQL_SERVICE_DB_NAME=nacos_config
- MYSQL_SERVICE_USER=root
- MYSQL_SERVICE_PASSWORD=20041111
# 设置连接 mysql 的连接参数
- MYSQL_DB_PARAM="characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrievalyaml=true"
volumes:
- ./nacos/logs/:/home/nacos/logs
- ./nacos/conf/application.properties:/home/nacos/conf/application.properties
# 要暴露三个端口,至于为什么,可以网上查下看看
ports:
- "8848:8848"
- "9848:9848"
- "9849:9849"
privileged: true
networks:
- demo-network
# nacos依赖于mysql先启动成功
depends_on:
demo-mysql:
condition: service_healthy
# - demo-mysql
demo-mysql:
# 容器名
container_name: demo-mysql
# 镜像
image: mysql:8.0
build:
context: ./mysql
ports:
- "3306:3306"
volumes:
- ./mysql/conf:/etc/mysql/conf.d
- ./mysql/logs:/logs
- ./mysql/data:/var/lib/mysql
- ./mysql/db:/docker-entrypoint-initdb.d/
command: [
'mysqld',
'--innodb-buffer-pool-size=80M',
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci',
'--default-time-zone=+8:00',
'--lower-case-table-names=1'
]
privileged: true
networks:
- demo-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 5
environment:
MYSQL_ROOT_PASSWORD: '20041111'
demo-redis:
container_name: demo-redis
image: redis:7.2.3
build:
context: ./redis
dockerfile: Dockerfile
ports:
- "6379:6379"
volumes:
- ./redis/conf/redis.conf:/home/demo/redis/redis.conf
- ./redis/data:/data
command: redis-server /home/demo/redis/redis.conf
networks:
- demo-network
# 网关服务容器
demo-gateway:
container_name: demo-gateway
build:
context: ./demo/gateway
dockerfile: Dockerfile
ports:
- "8000:8000"
depends_on:
- demo-redis
- demo-nacos
networks:
- demo-network
# 用户服务容器
demo-user:
container_name: demo-user
build:
context: ./demo/user
dockerfile: Dockerfile
# 端口映射
ports:
- "8001:8001"
depends_on:
- demo-redis
- demo-mysql
- demo-nacos
networks:
- demo-network
# 确保都在同一个网络下
networks:
demo-network:
driver: bridge
sh脚本
上述工作完成后,我们写一个sh脚本,方便我们清理复制jar包、运行docker-compose.yml文件来启动容器。
- cleanjar.sh
#!/bin/bash
# 删除jar文件
echo "开始清理jar文件"
rm -f ../demo/gateway/jar/gateway.jar
rm -f ../demo/user/jar/user.jar
echo "清理完成"
- copy.sh
#!/bin/bash
# 创建目标目录
mkdir -p ../mysql/db
mkdir -p ../demo/gateway/jar
mkdir -p ../demo/user/jar
# 复制项目db目录下的sql文件到docker/mysql/db目录中
echo "begin copy sql "
cp ../../sql/db.sql ../mysql/db
echo "end copy sql "
# 复制target目录下的jar文件到docker目录下的demo的jar目录中
echo "begin copy jar "
cp ../../gateway/target/com.chenxw.demo.gateway-1.0-SNAPSHOT.jar ../demo/gateway/jar/gateway.jar
cp ../../user/target/com.chenxw.demo.user-1.0-SNAPSHOT.jar ../demo/user/jar/user.jar
echo "end copy jar "
- deploy.sh:Docker Compose运行脚本
#!/bin/sh
# 使用说明,用来提示输入参数
usage(){
echo "Usage: sh 执行脚本.sh [base|services|stop|rm]"
exit 1
}
# 启动基础环境(必须)
base(){
docker-compose up -d demo-mysql demo-redis demo-nacos
}
# 启动程序模块(必须)
services(){
docker-compose up -d demo-gateway demo-user
}
# 关闭所有环境/模块
stop(){
docker-compose stop
}
# 删除所有环境/模块
rm(){
docker-compose rm
}
# 根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"base")
base
;;
"services")
services
;;
"stop")
stop
;;
"rm")
rm
;;
*)
usage
;;
esac
到此,所有准备工作都完成了。但是我们要注意,在项目的apppication.yml文件中,我们要把127.0.0.1或者localhost改成指定容器名,每个容器都有自己的ip。比如这里要连接mysql就改成demo-mysql,要连接redis就改成demo-redis,要配置nacos地址就改成demo-nacos
比如gateway服务中的apppication.yml文件:
server:
port: 8000 # 指定启动端口
spring:
application:
name: demo-gateway # 容器名称,就是我们在docker-compose.yml中指定的container_name
cloud:
config:
enabled: false
nacos:
discovery:
enabled: true # 启用服务发现
group: DEFAULT_GROUP # 所属组
namespace: demo # 命名空间
server-addr: demo-nacos:8848 # 指定 Nacos 配置中心的服务器地址
username: nacos
password: nacos
gateway:
routes:
- id: user
uri: lb://demo-user
predicates:
- Path=/user/**
filters:
- StripPrefix=0
data:
redis:
database: 1 # Redis 数据库索引
host: demo-redis # Redis 服务器地址
port: 6379 # Redis 服务器连接端口
password: 1234 # Redis 服务器连接密码(默认为空)
timeout: 5s # 读超时时间
connect-timeout: 5s # 链接超时时间
lettuce:
pool:
max-active: 200 # 连接池最大连接数
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 # 连接池中的最小空闲连接
max-idle: 10 # 连接池中的最大空闲连接
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: Authorization
# token前缀
token-prefix: Bearer
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: true
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: random-32
# 是否输出操作日志
is-log: true
微服务,启动!
运行cleanjar.sh、copy.sh复制sql、jar包。
运行sh deploy.sh base,启动环境容器!
在IDEA服务中也可以看到环境容器启动成功:
运行sh deploy.sh services,启动服务容器!
服务容器也启动成功:
在nacos控制台查看demo-gateway和demo-user服务是否注册成功:
两个应用都注册到nacos中了,来看下各个容器的ip:
从网关127.19.0.5访问:试下注册、登陆、积分查询、退出接口都没有问题。
- 注册:
- 登陆:
- 查询:
- 退出:
至此,docker compose部署微服务就完成了。
其实这个过程中还是会遇到各种奇奇怪怪的错误,可以用cursor帮忙理解整个报错原因,一步步梳理,效率嘎嘎高!
参考资料:https://github.com/timeless157/timeless-order-pay-parent.git