现在我们的系统大多以微服务架构居多,在以Spring Cloud微服务技术栈中,一个应用系统一般会包含多个应用微服务。在启动应用前,需要先启动网关、注册中心、配置中心、数据库,甚至当系统还引入了各种中间件,如Redis、RabbitMQ、ELK日志系统、Grafana监控…等服务。
在部署微服务应用时,需要先将开发的Spring Boot服务打包成Docker镜像,导入Docker中再启动部署,中间件、数据库等服务也需要从Docker的远程仓库中拉取对应版本的镜像进行部署,往往繁琐却需要花费大量时间,我们可以利用Docker官方提供的容器编排工具Docker Compose来减轻工作量。
利用各种工具,来让容器部署、管理、弹性伸缩、网络管理等功能都能够自动化进行,这就是容器编排。
我们常常听说的Kubernetes,是一个强大的容器编排工具,除此之外,Docker Swarm、Docker Compose也是比较常用的编排工具。
Docker Compose和Docker Swarm都是Docker官方容器编排工具的项目,两者的作用不同,Docker Compose是一个用于定义和运行多容器 Docker 应用程序的工具,主要用在单机上创建容器,而Docker Swarm是用来管理Docker集群的平台,可以用在多个服务器上创建容器服务。而K8s本身的定位和Docker Swarm一样,是由谷歌研发的一款容器运维平台。目前已成为主流的容器编排工具。
了解Docker compose之前,需要先了解Dockerfile。我们的Spring Boot服务,在编译完还只是一个jar包,需要借助Dockerfile将jar包构建成docker镜像,才能将服务部署到Docker容器上。
Dockerfile是一个用来构建镜像的文本文件,文件的内容包含了一条条用于构建镜像时所需要的指令和说明。要注意的是,每一条指令都将会构建一层镜像。Dockerfile指令其实并不多,但日常基本掌握常用的指令也足够使用了。
指定基础的镜像,一般为文件的第一条指令,但其实前面还可以使用ARG
作为第一条指令。
# 格式:
FROM [--platform=] [AS ]
FROM [--platform=] [:] [AS ]
FROM [--platform=] [@] [AS ]
# 用法
FROM centos
FROM openjdk:8-jre
FROM node:12.18.4-alpine@sha256:757574c5a2...
参数说明:
[--platform=]
为可选参数,可用于指定image的平台,如linux/amd64、 linux/arm64或windows/amd64,一般为默认即可
为镜像名,后面的:tag
是用于指定镜像的版本号,@digest
为内容可寻址标识符(详细可以去官网查看),一般我们用的比较多的还是只有tag,如果两者都不指定的话,获取的为latest版本[AS ]
为命名当前构建的阶段,因为Dockerfile中可以使用多个FROM来创建多镜像,所以使用AS
可以在后面的COPY指令上,使用--from=
来引用前面构建的镜像,如复制前面镜像生成的文件等该指令用于指定传递给构建 运行时的一个变量,可以设置默认值,在使用docker build
命令构建容器时,用--build-arg
可以传递参数。
# 格式
ARG [=]
# 用法
ARG CODE_VERSION=laster
ARG testArg=123
# 传参用法
FROM centos:7
ARG parameter=123
RUN echo $parameter
docker build --build-arg parameter=234 -t test:1.0
# 注意
ARG可以用在FROM之前,是位于FROM构建阶段之外,因此在FROM之后的任何指令中都不能使用。
ARG CENTOS_VERSION=laster
FROM centos:${CENTOS_VERSION}
该指令用于为容器设置环境变量,在构建过程中以及启动的容器中都会存在。
# 格式
ENV = ...
# 注意
可设置多个,也可以使用另一种语法ENV ,官网推荐使用前者,后者可能会在未来的版本删除
ENV变量会覆盖ARG变量
# 用法
ENV APP_VERSION=1.1.0
ENV WEB_HOME /opt/webapps
该指令用于从路径复制文件或目录到容器中。
# 格式
COPY ...
COPY "",... ""
# 用法
COPY test.jar /opt/web/
该指令用于将文件、目录或者远程文件复制到镜像中,tar类型的压缩包文件会自动解压。
# 格式
ADD ...
ADD "",... ""
# 用法
ADD test.txt /tmp/test
该指令为后续指令设置工作目录,如果不存在,则会自动创建目录。
# 格式
WORKDIR /path/to/workdir
# 用法
WORKDIR /build
该指令用于指定镜像的元数据标签信息。
# 格式
LABEL = = = ...
# 用法
LABEL version="1.0"
LABEL description="This text illustrates"
该指令用于设定构建镜像后,容器启动时执行的命令。
# 格式
CMD ["executable","param1","param2"](执行形式,这是首选形式)
CMD ["param1","param2"](作为ENTRYPOINT 的默认参数)
CMD command param1 param2(shell形式)
# 用法 支持两种写法
CMD sleep 40; java -jar secondkill-order.jar
CMD ["java", "-jar", "secondkill-order.jar"]
该指令用于指定构建该镜像时执行的命令,RUN和CMD的区别主要是CMD在容器启动时才会执行,而RUN是在容器构建的过程中执行。
# 格式
RUN (shell形式)
RUN ["executable", "param1", "param2"](执行形式)
# 用法 支持两种写法
RUN yum install -y net-tools
RUN ["/bin/bash", "-c", "echo hello"]
该指令用于指定容器运行时侦听指定的端口,可以指定监听的协议,如果不指定,则默认为TCP。
# 格式
EXPOSE [/...]
# 用法
EXPOSE 80/udp
EXPOSE 80 443
该指令用于创建一个具有指定名称的挂载点,可以将该目录映射到容器外部。
# 格式
VOLUME ["/data"]
# 用法
VOLUME /myvol
这里只稍微整理了一些比较常用的dockerfile命令,如果想了解更多指令的详细介绍,建议直接阅读官网的文档。
dockerfile官网文档地址
使用Dockerfile打包Spring Boot还是比较简单的,只需要使用Jdk8的镜像,再将本地打包好的jar包复制到镜像中,再暴露服务的端口,启动服务即可。
# 使用openjdk8的镜像
FROM openjdk:8-jre
# 设置工作目录
WORKDIR /build
# 将jar包复制到容器中
ADD ./test-springboot-docker-1.0.jar ./test.jar
# 暴露8080端口
EXPOSE 8080
# 运行jar包
CMD java -jar test.jar
我们一般会将Dockerfile放在工程目录下。
接着执行maven package打包jar包,我们在有Docker环境的服务器上,将jar包和Dockerfile文件放在同个目录下,使用docker build
命令就可以将SpringBoot服务打成镜像。
接着运行容器,测试容器是否能启动成功。
因为8080端口被其他服务用了, 我映射到80端口上,此时直接访问80端口,可以看到容器已经启动成功。
此时能将jar包构建成Docker镜像了,但如果微服务架构的应用包含了若干个服务,总不能每次都手动打包再部署到服务器上叭,所以这时候就需要利用Docker Compose来管理容器了。
Docker Compose使用docker-compose.yml
文件来定义多个服务,格式为YAML
,需要先了解下整个文件的结构定义。
Docker Compose提供了很多模板语法,我只列举了本次编排所需要用到的语法,其余可以到官网查看文档,官网提供的文档还是相当详细的,docker-compose.yml格式官网文档。
version: 指定当前文件所对应的compsoe版本,主要有1、2.x和3.x
service: 服务列表
: 服务名
image: 指定运行的镜像,可直接拉取已有镜像进行处理
build: 设置Dockerfile所在的文件夹,可处理需要用Dockerfile构建的镜像
content: 存放Dockerfile的路径
dockerfile: 指定构建的Dockerfile文件名
args: 构建参数,只能在构建过程中访问
container_name: 设置容器名称
restart: 重启策略,有no、always、no-failure、unless-stoped
ports: 暴露容器的端口,格式为宿主机端口:容器端口
- 8080:8080
hostname: 设置容器的主机名
volumns: 设置容器的挂载点,可以挂载到宿主机上,主要格式为宿主机路径:容器路径[:访问模式]
- /opt/data:/opt/data
- /var/lib/mysql:/var/lib/mysql:rw
volumns_from: 挂载另一个服务或容器的所有数据卷
- service_name
- container_name
environment: 设置环境变量
- RACK_ENV=development
networks: 配置网络
app_netwotk:
常用的命令如下,这里只列出大概的描述,官网提供的命令描述和参数用法都很详细docker-compose命令官网文档,就不具体展开了。
命令 | 描述 |
---|---|
build | 构建容器 |
ps | 列出目前项目所有的容器 |
up | 构建容器并启动容器,常用参数:-d后台启动,-f指定配置文件 |
exec | 进入指定的容器 |
top | 查看项目中各个容器的运行状态 |
logs | 查看容器的输出 |
down | 停止并移除所有容器,并移除对应的网络 |
rm | 删除所有停滞状态的容器 |
start/stop/restart | 启动容器/停止容器/重启容器 |
这里我用之前做过的微服务项目作为例子,这是一个前后端分离的商城秒杀项目,总共有5个服务,分别是网关、鉴权服务、用户服务、商品服务和订单服务,还有Nacos注册中心,此外项目还用到了Rabbitmq,Redis还有Mysql数据库。所以如果说都以Docker的形式部署的话,那总共有8个容器需要部署。
Docker-Compose虽说是官网的编排工具,但并不是默认自带的,还需要自己手动安装。安装方式比较简单,只是下载Docker-Compose的二进制文件,并设置可执行权限便可运行。
sudo curl -L https://github.com/docker/compose/releases/download/1.16.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose version 1.18.0, build 8dd22a96
整体的docker-compose.yml文件结构如下,需要指定文件的版本,这里使用了版本3,接着是services和下面对应的每一个服务名。
networks
的配置项为创建一个网络,如果不设置的话,默认会创建以当前文件夹 + _default
的网络名。比如我的工程文件夹名称为secondkill,这里再指定network的后缀为app,则会创建一个secondkill_app
的网络。
version: '3'
services:
secondkill-register:
...
secondkill-mysql:
...
secondkill-redis:
...
secondkill-rabbitmq:
...
secondkill-zuul:
...
secondkill-auth:
...
secondkill-goods:
...
secondkill-order:
...
secondkill-user:
...
networks:
app:
注册中心部署比较简单,拉取nacos/nacos-server:1.4.2
的镜像,但要注意设置nacos的启动模式,因为Nacos默认是以集群方式启动的,所以需要增加environment
的MODE
为standalone
模式,另外需要注意的是,这里使用Nacos只做了注册中心,并没有配置数据库连接,如果需要将Nacos的数据保存到数据库时,注意要配置数据库信息。
secondkill-register:
# 拉取镜像
image: nacos/nacos-server:1.4.2
# 重启方式:总是
restart: always
# 端口映射
ports:
- 8848:8848
# 容器名称
container_name: secondkill-register
# 主机名,需要其他容器通过次名称来访问网络
hostname: secondkill-register
# 环境变量,设置启动方式为单机启动
environment:
- MODE: standalone
# mysql信息配置
# - SPRING_DATASOURCE_PLATFORM=mysql
# - MYSQL_MASTER_SERVICE_HOST=127.0.0.1
# - MYSQL_MASTER_SERVICE_PORT=3306
# - MYSQL_MASTER_SERVICE_USER=root
# - MYSQL_MASTER_SERVICE_PASSWORD=123456
# - MYSQL_MASTER_SERVICE_DB_NAME=nacos
# - MYSQL_SLAVE_SERVICE_HOST=122.112.152.188
# - MYSQL_SLAVE_SERVICE_PORT=3306
# 添加到网络
networks:
- app
消息队列和Redis的部署比较简单,但需要注意,因为我为了方便自己部署用于临时演示,并没有将Redis和Rabbitmq的持久化数据和配置文件映射出来,这是非常不推荐的,容器内不应该部署有状态的服务,更不应该存储数据。容器在部署这些需要配置和持久化的服务时,应该将配置文件和数据目录挂载到宿主机上。
secondkill-redis:
# redis镜像
image: redis:3.2
# 端口映射
ports:
- 6379:6379
# 重启方式:总是
restart: always
# 容器名
container_name: secondkill-redis
# 主机名
hostname: secondkill-redis
# 加入app网络
networks:
- app
secondkill-rabbitmq:
# rabbitmq镜像
image: rabbitmq:3.8.4
# 端口映射,管理端口和连接端口
ports:
- 5672:5672
- 15672:15672
# 重启方式:总是
restart: always
# 容器名
container_name: secondkill-rabbitmq
# 主机名
hostname: secondkill-rabbitmq
# 加入app网络
networks:
- app
数据库部署和Redis服务一样并没有挂载配置文件和数据(不推荐,需注意)。因为这里将数据库启动时,需要设置数据库密码,同时将数据库脚本导入到数据库中,所以这里使用了Dockerfile来构建数据库镜像。
# 拉取myql8镜像
FROM mysql:8.0.14
# 设置时区变量
ENV TZ=Asia/Shanghai
# 设置时区
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 导入数据库脚本
COPY ./choy_ms.sql /docker-entrypoint-initdb.d
将数据库脚本和Dockerfile放置于同个目录下,再通过docker-compose.yml中的build.content
指定目录。
secondkill-mysql:
# 设置dockerfile构建脚本的目录
build:
context: ./db
environment:
# 设置数据库密码
MYSQL_ROOT_PASSWORD: 86598659yu
restart: always
container_name: secondkill-mysql
image: secondkill-mysql
ports:
- 3306:3306
networks:
- app
微服务的部署需要在上述容器启动后才能启动,同时还需要连接以上容器的服务,因此在application.yml
配置文件中,需要提前将数据库、队列、注册中心和Redis的ip换成上述容器的主机名,也就是定义的hostname
,在容器启动后才能自动找到对应的ip地址。
spring:
cloud:
nacos:
discovery:
# secondkill-register为注册中心的主机名
server-addr: secondkill-register:8848
datasource:
druid:
# 数据库主机名secondkill-mysql
url: jdbc:mysql://secondkill-mysql:3306/choy_ms...
...
redis:
# redis主机名secondkill-redis
host: secondkill-redis:127.0.0.1
...
rabbitmq:
# rabbitmq主机名secondkill-rabbitmq
host: secondkill-rabbitmq
port: 5672
微服务的Dockerfile,需要注意Dockerfile存放的位置和target
目录的关系,因为是将整个工程通过git拉取在服务器上进行打包部署的,所以我将Dockerfile放在每个微服务下,同时ADD
命令对应的jar包需要带上target
目录。
而且启动服务时,需要等注册中心、数据库、Redis和队列的服务启动后才能启动,否则会启动失败,需要利用sleep
命令进行延时启动,也可以使用docker-compose.yml的模板配置:depends_on
# 拉取java8镜像
FROM openjdk:8-jre
# 设置工作目录
WORKDIR /build
# 复制jar包
ADD ./target/secondkill-order-1.1.0.jar ./secondkill-order.jar
# 暴露端口
EXPOSE 8010
# 启动jar包,需要延时启动,等待其他服务启动
CMD sleep 40; java -jar secondkill-order.jar
接着是docker-compose.yml文件,docker-compose.yml是存放在整个工程的根目录下,需要在build.content
设置每个服务下Dockerfile的相对路径。
secondkill-order:
# 设置服务对应Dockerfile的相对路径
build:
context: ./secondkill-service/secondkill-order
ports:
- 8010:8010
restart: always
container_name: secondkill-order
hostname: secondkill-order
networks:
- app
将所有的微服务都配置完,下面是完整的docker-compose.yml
# 指定版本号
version: '3'
services:
# 注册中心服务
secondkill-register:
# nacos-1.4.2镜像
image: nacos/nacos-server:1.4.2
# 重启方式:总是
restart: always
# 端口映射
ports:
- 8848:8848
# 容器名
container_name: secondkill-register
# 主机名
hostname: secondkill-register
# 环境变量,设置启动方式为单机启动
environment:
MODE: standalone
# 添加到网路app
networks:
- app
# 数据库服务
secondkill-mysql:
# 数据库对应Dockerfile目录
build:
context: ./db
# 设置数据库密码
environment:
MYSQL_ROOT_PASSWORD: 86598659yu
restart: always
container_name: secondkill-mysql
image: secondkill-mysql
ports:
- 3306:3306
networks:
- app
# redis服务
secondkill-redis:
# redis-3.2镜像
image: redis:3.2
ports:
- 6379:6379
restart: always
container_name: secondkill-redis
hostname: secondkill-redis
networks:
- app
# rabbitmq队列服务
secondkill-rabbitmq:
# rabbitmq-3.8.4镜像
image: rabbitmq:3.8.4
ports:
- 5672:5672
- 15672:15672
restart: always
container_name: secondkill-rabbitmq
hostname: secondkill-rabbitmq
networks:
- app
# 以下是微服务
# 网关服务
secondkill-zuul:
# 网关服务对应Dockerfile路径
build:
context: ./secondkill-zuul
ports:
- 8000:8000
restart: always
container_name: secondkill-zuul
hostname: secondkill-zuul
networks:
- app
# 鉴权服务
secondkill-auth:
# 鉴权服务对应Dockerfile路径
build:
context: ./secondkill-auth
ports:
- 8002:8002
restart: always
container_name: secondkill-auth
hostname: secondkill-auth
networks:
- app
# 商品服务
secondkill-goods:
# 商品服务对应Dockerfile路径
build:
context: ./secondkill-service/secondkill-goods
ports:
- 8021:8021
restart: always
container_name: secondkill-goods
hostname: secondkill-goods
networks:
- app
# 订单服务
secondkill-order:
# 订单服务对应Dockerfile路径
build:
context: ./secondkill-service/secondkill-order
ports:
- 8010:8010
restart: always
container_name: secondkill-order
hostname: secondkill-order
networks:
- app
# 用户服务
secondkill-user:
# 用户服务对应Dockerfile路径
build:
context: ./secondkill-service/secondkill-user
ports:
- 8001:8001
restart: always
container_name: secondkill-user
hostname: secondkill-user
networks:
- app
# 设置网络为app
networks:
app:
需要先将工程拉到服务器上,这里我直接用gitee仓库拉取,接着进入该工程,并执行mvn clean && mvn package
对工程进行打包编译。
接着执行docker-compose up -d
对容器进行编排启动。
这台服务器我已经安装好portainer,可以直接进入portainer查看容器状态,可点击每个容器名进去查看服务的启动输出。
还可以直接进入Nacos注册中心查看服务注册情况。
如果需要将所有服务停止并删除容器时,可以用命令docker-compose down
。
Docker-Compose可以为部署多个容器提供很多便利,我觉得还是值得学习的,而且也可以利用他来快速部署自己的开发环境,在自己的项目上更容易体现,最后再附上项目的仓库地址,gitee仓库,github仓库。