大大大大师兄

V1

2022/06/10阅读:20主题:绿意

用docker-compose编排微服务

前言

现在我们的系统大多以微服务架构居多,在以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一样,是由谷歌研发的一款容器运维平台。目前已成为主流的容器编排工具。

Dockerfile

了解Docker compose之前,需要先了解Dockerfile。我们的Spring Boot服务,在编译完还只是一个jar包,需要借助Dockerfile将jar包构建成docker镜像,才能将服务部署到Docker容器上。

Dockerfile是一个用来构建镜像的文本文件,文件的内容包含了一条条用于构建镜像时所需要的指令和说明。要注意的是,每一条指令都将会构建一层镜像。Dockerfile指令其实并不多,但日常基本掌握常用的指令也足够使用了。

FROM

指定基础的镜像,一般为文件的第一条指令,但其实前面还可以使用ARG作为第一条指令。

# 格式:
FROM [--platform=<platform>] <image> [AS <name>]  
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>] 
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

# 用法
FROM centos
FROM openjdk:8-jre
FROM node:12.18.4-alpine@sha256:757574c5a2...

参数说明:

  • [--platform=<platform>]为可选参数,可用于指定image的平台,如linux/amd64、 linux/arm64或windows/amd64,一般为默认即可
  • <image> 为镜像名,后面的:tag是用于指定镜像的版本号,@digest为内容可寻址标识符(详细可以去官网查看),一般我们用的比较多的还是只有tag,如果两者都不指定的话,获取的为latest版本
  • [AS <name>]为命名当前构建的阶段,因为Dockerfile中可以使用多个FROM来创建多镜像,所以使用AS <name>可以在后面的COPY指令上,使用--from=<name>来引用前面构建的镜像,如复制前面镜像生成的文件等

ARG

该指令用于指定传递给构建 运行时的一个变量,可以设置默认值,在使用docker build命令构建容器时,用--build-arg <varname>=<value>可以传递参数。

# 格式
ARG <name>[=<default value>]

# 用法
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 <key>=<value> ...

# 注意
可设置多个,也可以使用另一种语法ENV <key> <value>,官网推荐使用前者,后者可能会在未来的版本删除
ENV变量会覆盖ARG变量

# 用法
ENV APP_VERSION=1.1.0
ENV WEB_HOME /opt/webapps

COPY

该指令用于从路径复制文件或目录到容器中。

# 格式
COPY <src>... <dest>
COPY "<src>",... "<dest>"

# 用法
COPY test.jar /opt/web/

ADD

该指令用于将文件、目录或者远程文件复制到镜像中,tar类型的压缩包文件会自动解压。

# 格式
ADD <src>... <dest>
ADD "<src>",... "<dest>"

# 用法
ADD test.txt /tmp/test

WORKDIR

该指令为后续指令设置工作目录,如果不存在,则会自动创建目录。

# 格式
WORKDIR /path/to/workdir

# 用法
WORKDIR /build

LABEL

该指令用于指定镜像的元数据标签信息。

# 格式
LABEL <key>=<value> <key>=<value> <key>=<value> ...

# 用法
LABEL version="1.0"
LABEL description="This text illustrates"

CMD

该指令用于设定构建镜像后,容器启动时执行的命令。

# 格式
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

该指令用于指定构建该镜像时执行的命令,RUN和CMD的区别主要是CMD在容器启动时才会执行,而RUN是在容器构建的过程中执行。

# 格式
RUN <command>(shell形式)
RUN ["executable""param1""param2"](执行形式)

# 用法 支持两种写法
RUN yum install -y net-tools
RUN ["/bin/bash""-c""echo hello"]

EXPOSE

该指令用于指定容器运行时侦听指定的端口,可以指定监听的协议,如果不指定,则默认为TCP。

# 格式
EXPOSE <port> [<port>/<protocol>...]

# 用法
EXPOSE 80/udp
EXPOSE 80 443

VOLUME

该指令用于创建一个具有指定名称的挂载点,可以将该目录映射到容器外部。

# 格式
VOLUME ["/data"]

# 用法
VOLUME /myvol

这里只稍微整理了一些比较常用的dockerfile命令,如果想了解更多指令的详细介绍,建议直接阅读官网的文档。👇

dockerfile官网文档地址

用Dockerfile构建Spring Boot应用

使用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端口,可以看到容器已经启动成功。

Docker Compose

此时能将jar包构建成Docker镜像了,但如果微服务架构的应用包含了若干个服务,总不能每次都手动打包再部署到服务器上叭,所以这时候就需要利用Docker Compose来管理容器了。

Docker Compose使用docker-compose.yml文件来定义多个服务,格式为YAML,需要先了解下整个文件的结构定义。

docker-compose.yml文件结构

Docker Compose提供了很多模板语法,我只列举了本次编排所需要用到的语法,其余可以到官网查看文档,官网提供的文档还是相当详细的,docker-compose.yml格式官网文档

version: 指定当前文件所对应的compsoe版本,主要有1、2.x和3.x
service: 服务列表
 <service-name>: 服务名
  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常用命令

常用的命令如下,这里只列出大概的描述,官网提供的命令描述和参数用法都很详细docker-compose命令官网文档,就不具体展开了。

命令 描述
build 构建容器
ps 列出目前项目所有的容器
up 构建容器并启动容器,常用参数:-d后台启动,-f指定配置文件
exec 进入指定的容器
top 查看项目中各个容器的运行状态
logs 查看容器的输出
down 停止并移除所有容器,并移除对应的网络
rm 删除所有停滞状态的容器
start/stop/restart 启动容器/停止容器/重启容器

Docker Compose部署微服务

这里我用之前做过的微服务项目作为例子,这是一个前后端分离的商城秒杀项目,总共有5个服务,分别是网关、鉴权服务、用户服务、商品服务和订单服务,还有Nacos注册中心,此外项目还用到了Rabbitmq,Redis还有Mysql数据库。所以如果说都以Docker的形式部署的话,那总共有8个容器需要部署。

安装Docker-Compose

Docker-Compose虽说是官网的编排工具,但并不是默认自带的,还需要自己手动安装。安装方式比较简单,只是下载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默认是以集群方式启动的,所以需要增加environmentMODEstandalone模式,另外需要注意的是,这里使用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

Rabbitmq和Redis部署

消息队列和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仓库

分类:

后端

标签:

后端

作者介绍

大大大大师兄
V1