【重识云原生】第六章容器6.1.11节——docker-compose容器编排

  《重识云原生系列》专题索引:

  1. 第一章——不谋全局不足以谋一域
  2. 第二章计算第1节——计算虚拟化技术总述
  3. 第二章计算第2节——主流虚拟化技术之VMare ESXi
  4. 第二章计算第3节——主流虚拟化技术之Xen
  5. 第二章计算第4节——主流虚拟化技术之KVM
  6. 第二章计算第5节——商用云主机方案
  7. 第二章计算第6节——裸金属方案
  8. 第三章云存储第1节——分布式云存储总述
  9. 第三章云存储第2节——SPDK方案综述
  10. 第三章云存储第3节——Ceph统一存储方案
  11. 第三章云存储第4节——OpenStack Swift 对象存储方案
  12. 第三章云存储第5节——商用分布式云存储方案
  13. 第四章云网络第一节——云网络技术发展简述
  14. 第四章云网络4.2节——相关基础知识准备
  15. 第四章云网络4.3节——重要网络协议
  16. 第四章云网络4.3.1节——路由技术简述
  17. 第四章云网络4.3.2节——VLAN技术
  18. 第四章云网络4.3.3节——RIP协议
  19. 第四章云网络4.3.4节——OSPF协议
  20. 第四章云网络4.3.5节——EIGRP协议
  21. 第四章云网络4.3.6节——IS-IS协议
  22. 第四章云网络4.3.7节——BGP协议
  23. 第四章云网络4.3.7.2节——BGP协议概述
  24. 第四章云网络4.3.7.3节——BGP协议实现原理
  25. 第四章云网络4.3.7.4节——高级特性
  26. 第四章云网络4.3.7.5节——实操
  27. 第四章云网络4.3.7.6节——MP-BGP协议
  28. 第四章云网络4.3.8节——策略路由
  29. 第四章云网络4.3.9节——Graceful Restart(平滑重启)技术
  30. 第四章云网络4.3.10节——VXLAN技术
  31. 第四章云网络4.3.10.2节——VXLAN Overlay网络方案设计
  32. 第四章云网络4.3.10.3节——VXLAN隧道机制
  33. 第四章云网络4.3.10.4节——VXLAN报文转发过程
  34. 第四章云网络4.3.10.5节——VXlan组网架构
  35. 第四章云网络4.3.10.6节——VXLAN应用部署方案
  36. 第四章云网络4.4节——Spine-Leaf网络架构
  37. 第四章云网络4.5节——大二层网络
  38. 第四章云网络4.6节——Underlay 和 Overlay概念
  39. 第四章云网络4.7.1节——网络虚拟化与卸载加速技术的演进简述
  40. 第四章云网络4.7.2节——virtio网络半虚拟化简介
  41. 第四章云网络4.7.3节——Vhost-net方案
  42. 第四章云网络4.7.4节vhost-user方案——virtio的DPDK卸载方案
  43. 第四章云网络4.7.5节vDPA方案——virtio的半硬件虚拟化实现
  44. 第四章云网络4.7.6节——virtio-blk存储虚拟化方案
  45. 第四章云网络4.7.8节——SR-IOV方案
  46. 第四章云网络4.7.9节——NFV
  47. 第四章云网络4.8.1节——SDN总述
  48. 第四章云网络4.8.2.1节——OpenFlow概述
  49. 第四章云网络4.8.2.2节——OpenFlow协议详解
  50. 第四章云网络4.8.2.3节——OpenFlow运行机制
  51. 第四章云网络4.8.3.1节——Open vSwitch简介
  52. 第四章云网络4.8.3.2节——Open vSwitch工作原理详解
  53. 第四章云网络4.8.4节——OpenStack与SDN的集成
  54. 第四章云网络4.8.5节——OpenDayLight
  55. 第四章云网络4.8.6节——Dragonflow
  56.  第四章云网络4.9.1节——网络卸载加速技术综述

  57. 第四章云网络4.9.2节——传统网络卸载技术

  58. 第四章云网络4.9.3.1节——DPDK技术综述

  59. 第四章云网络4.9.3.2节——DPDK原理详解

  60. 第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述

  61. 第四章云网络4.9.4.2节——智能网卡实现

  62. 第六章容器6.1.1节——容器综述

  63. 第六章容器6.1.2节——容器安装部署

  64. 第六章容器6.1.3节——Docker常用命令

  65. 第六章容器6.1.4节——Docker核心技术LXC

  66. 第六章容器6.1.5节——Docker核心技术Namespace

  67. 第六章容器6.1.6节—— Docker核心技术Chroot

  68. 第六章容器6.1.7.1节——Docker核心技术cgroups综述

  69. 第六章容器6.1.7.2节——cgroups原理剖析

  70. 第六章容器6.1.7.3节——cgroups数据结构剖析

  71. 第六章容器6.1.7.4节——cgroups使用

  72. 第六章容器6.1.8节——Docker核心技术UnionFS

  73. 第六章容器6.1.9节——Docker镜像技术剖析

  74. 第六章容器6.1.10节——DockerFile解析

  75. 第六章容器6.1.11节——docker-compose容器编排

  76. 第六章容器6.1.12节——Docker网络模型设计

  77. 第六章容器6.2.1节——Kubernetes概述

  78. 第六章容器6.2.2节——K8S架构剖析

  79. 第六章容器6.3.1节——K8S核心组件总述

  80. 第六章容器6.3.2节——API Server组件

  81. 第六章容器6.3.3节——Kube-Scheduler使用篇

  82. 第六章容器6.3.4节——etcd组件

  83. 第六章容器6.3.5节——Controller Manager概述

  84. 第六章容器6.3.6节——kubelet组件

  85. 第六章容器6.3.7节——命令行工具kubectl

  86. 第六章容器6.3.8节——kube-proxy

  87. 第六章容器6.4.1节——K8S资源对象总览

  88. 第六章容器6.4.2.1节——pod详解

  89. 第六章容器6.4.2.2节——Pod使用(上)

  90. 第六章容器6.4.2.3节——Pod使用(下)

  91. 第六章容器6.4.3节——ReplicationController

  92. 第六章容器6.4.4节——ReplicaSet组件

  93. 第六章容器基础6.4.5.1节——Deployment概述

  94. 第六章容器基础6.4.5.2节——Deployment配置详细说明

  95. 第六章容器基础6.4.5.3节——Deployment实现原理解析

  96. 第六章容器基础6.4.6节——Daemonset

  97. 第六章容器基础6.4.7节——Job

  98. 第六章容器基础6.4.8节——CronJob

1 Docker Compose综述

1.1 什么是Docker Compose

        随着开发者对Docker了解的深入,使用其进行分布式部署变得复杂。开发者需要在开发,测试以及生产环境中的可移植应用,这些应用需要在不同的平台提供商之间迁移,比如在不同的云平台或者私有数据中心部署,同时,应用应该是可组合的,一个应用可以分解为多个服务。 Docker公司在2014年12月发布了三款用于解决多容器分布式软件可移植部署的问题。

  • Docker Machine为本地,私有数据中心及公有云平台提供Docker引擎,实现从零到Docker的一键部署。
  • Docker Compose是一个编排多容器分布式部署的工具,提供命令集管理容器化应用的完整开发周期,包括服务构建,启动和停止。
  • Docker Swarm为Docker容器提供了原生的集群,它将多个Docker引擎的资源汇聚在一起,并提供Docker标准的API,使Docker可以轻松扩展到多台主机。

        Compose是用来编排和管理多容器应用的工具,使用它,你可以通过定义一个YAML文件来定义你的应用的所有服务,然后通过一条命令,你就可以创建并启动所有的服务。使用Compose仅需要三步:

  • 使用Dockerfile定义你的应用依赖的镜像;
  • 使用docker-compose.yml定义你的应用(APP)具有的服务;
  • 通过docker-compose up命令创建并运行应用;

1.2 docker compose的背景

        Docker Compose 的前身是 Fig。Fig 是一个由 Orchard 公司开发的强有力的工具,在当时是进行多容器管理的最佳方案。Fig 是一个基于 Docker 的 Python工具,允许用户基于一个 YAML 文件定义多容器应用,从而可以使用fig 命令行工具进行应用的部署。Fig 还可以对应用的全生命周期进行管理。内部实现上,Fig 会解析 YAML 文件,并通过 Docker API 进行应用的部署和管理。

        在 2014 年,Docker 公司收购了 Orchard 公司,并将 Fig 更名为 Docker Compose。命令行工具也从 fig 更名为 docker-compose,并自此成为绑定在 Docker 引擎之上的外部工具。虽然它从未完全集成到 Docker 引擎中,但是仍然受到广泛关注并得到普遍使用。直至今日,Docker Compose 仍然是一个需要在 Docker 主机上进行安装的外部 Python 工具。

        使用它时,首先编写定义多容器(多服务)应用的 YAML 文件,然后将其交由 docker-compose 命令处理,Docker Compose 就会基于 Docker 引擎 API 完成应用的部署。

1.3 Compose的特性

  • 将单个主机隔离成多个环境; 
    • Compose使用项目名称(project name)将不同应用的环境隔离开,项目名称可以用来: 
    • 在开发机上,将应用环境复制多份;
    • 防止使用了相同名称服务的应用之间互相干扰;
    • 默认情况下,项目名称是项目文件夹根目录的名称,你可以使用-p标识或COMPOSE_PROJECT_NAME改变默认的名称。
  • 保护卷中的数据; 
    • Compose保护服务使用的所有卷(vloumes),当运行docker-compose run命令时,如果Compose发现存在之前运行过的容器,它会把旧容器中的数据卷拷贝到新的容器中,这保证了你在卷中创建的任何数据都不丢失。
  • 只重新创建改变过的容器; 
    • Compose会缓存用于创建容器的配置信息,当你重启服务时,如果服务没有被更改,Compose就会重用已经存在的容器,这无疑加快了修改应用的速度;

        Compose 文件是一个YAML文件,用于定义services、netword和volumes。 Compose 文件的默认路径为./docker-compose.yml(后缀为.yml和.yaml都可以)。

        一个service配置将会应用到容器的启动中,很像将命令行参数传递给docker run。 同样,network和volume定义类似于docker network create和docker volume create。 与Docker运行一样,默认情况下尊重Dockerfile中指定的选项(例如CMD,EXPOSE,VOLUME,ENV) - 您不需要在docker-compose.yml中再次指定它们。

1.4 Docker-Compose 三层管理

        Docker-Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。Docker-Compose 将所管理的容器分为三层,分别是工程(project),服务(service)以及容器(container)。

        Docker-Compose 运行目录下的所有文件(docker-compose.yml,extends 文件或环境变量文件等)组成一个工程,若无特殊指定工程名即为当前目录名。一个工程当中可包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖。一个服务当中可包括多个容器实例。Docker-Compose 的工程配置文件默认为 docker-compose.yml,可通过环境变量COMPOSE_FILE 或 -f 参数自定义配置文件,其定义了多个有依赖关系的服务及每个服务运行的容器。

        Docker-Compose 并没有解决负载均衡的问题,因此需要借助其他工具实现服务发现及负载均衡,比如 Consul。

1.5 YAML语言

1.5.1 YAML语言简介

        YAML(语言)的设计目标,就是方便人类读写,它实质上是一种通用的数据串行化格式。

        它的基本语法规则如下:(# 表示注释说明)

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

1.5.2 YAML支持的数据结构

  • 对象,键值对的集合
  • 数组,一组按次序排列的值,又称序列
  • 纯量,单个的,不可再分的值
apiVersion: apps/v1 
kind: Deployment 
metadata: 
  name: nginx-deployment 
  labels: 
    app: nginx 
spec: 
  replicas: 1 
  selector: 
    matchLabels: 
      app: nginx 
  template: 
    metadata: 
      labels: 
        app: nginx 
    spec: 
      containers: 
        - name: nginx 
          image: nginx:latest 
          ports: 
            - containerPort: 8080

1.5.3 使用 YAML 时需要注意以下事项

  • 大小写敏感
  • 通过缩进表示层级关系
  • 不支持制表符 tab 键缩进,只能使用空格缩进
  • 缩进的空格数目不重要,只要相同层级左对齐,通常开头缩进 2 个空格
  • 用 # 号注释
  • 符号字符后缩进一个空格,如冒号 :、逗号 ,、横杠 - 等
  • 如果包括特殊字符用单引号引起来会作为普通字符串处理,双引号特殊字符作为本身想表示的意思,例如 name: "Hi,\nTom"

1.6 Compose命令解析

1.6.1 Compose命令格式

        首先看一下Compose命令的格式:

Usage: docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]

其中options有如下选项:

Options:

  -f, --file FILE Specify an alternate compose file (default: docker-compose.yml)

  -p, --project-name NAME Specify an alternate project name (default: directory name)

  --x-networking (EXPERIMENTAL) Use new Docker networking functionality.Requires Docker 1.9 or later.

  --x-network-driver DRIVER (EXPERIMENTAL) Specify a network driver (default: "bridge").Requires Docker 1.9 or later.

  --verbose Show more output

  -v, --version Print version and exit

        首先运行docker-compose命令需要指定服务(service)名称,可以同时指定多个service,也可以不指定,当不指定service名称时,默认对配置中的所有service执行命令。

        其中-f标识用于指定Compose的配置文件,可以指定多个,当没有使用-f标识时,默认在项目跟目录及其子目录下寻找docker-compose.yml和docker-compose.override.yml文件,至少需要存在docker-compose.yml文件。

        当指定了多个文件时(包括没指定-f但同时存在docker-compose.yml和docker-compose.override.yml文件),Compose会将多个文件合并成一个配置文件,合并的结果与指定文件的顺序有关。合并有两种操作,或者添加,或者覆盖。具体的合并规则以后会单独用一篇文章介绍。

        -p标识用于给项目指定一个名称,如过没有指定,默认使用项目根目录的名称作为项目名称。

        -v显示版本号。

        Compose的Commands有如下几个,一一介绍:

  • build
  • kill
  • logs
  • pause & unpause
  • port
  • ps
  • pull
  • restart
  • rm
  • run
  • scale
  • start & stop
  • up

1.6.2 build指令

Usage: 
build [options] [SERVICE...] 
Options: 
  --force-rm Always remove intermediate containers. 
  --no-cache Do not use cache when building the image. 
  --pull Always attempt to pull a newer version of the image.

        docker-compose build命令用来创建或重新创建服务使用的镜像,后面指定的是服务的名称,创建之后的镜像名为project_service,即项目名后跟服务名。比如项目名称为composeset,其中的一个服务名称为web,则docker-compose build web创建的镜像的名称为composeset_web。

        和docker build一样,执行此命令也需要Dockerfile文件。当修改了Dockerfile文件或它的上下文之后,可以运行docker-compose build重新创建镜像,此时无需指定服务名称。

1.6.3 kill指令

Usage: 
kill [options] [SERVICE...] 
Options: 
  -s SIGNAL SIGNAL to send to the container. Default signal is SIGKILL.

        docker-compose kill命令用于通过向容器发送SIGKILL信号强行停止服务。

        -s标识用于覆盖默认发送的信号。

1.6.4 logs指令

Usage: 
logs [options] [SERVICE...] 
Options: 
  --no-color Produce monochrome output.

        docker-compose logs命令用于展示service的日志。

        --no-color标识使日志显示为单色

1.6.5 pause & unpause指令

  • docker-compose pause暂停服务;
  • docker-compose unpause恢复被暂停的服务;

1.6.6 port指令

Usage: 
port [options] SERVICE PRIVATE_PORT 
Options: 
  --protocol=proto tcp or udp [default: tcp] 
  --index=index index of the container if there are multiple instances of a service [default: 1]

        docker-conpose port命令用于查看服务中的端口被映射到了宿主机的哪个端口上,使用这条命令时必须通知指定服务名称和内部端口号,完整命令示例:

$ docker-compose port web 5000 #查看web服务中5000端口被映射到宿主机的哪个端口上 0.0.0.0:5000

1.6.7 ps指令

        docker-compose ps用于显示当前项目下的容器。注意,执行此命令时必须cd到项目的根目录下,否则提示如下错误:

ERROR: Can't find a suitable configuration file in this directory or any parent. Are you in the right directory? Supported filenames: docker-compose.yml, docker-compose.yaml, fig.yml, fig.yaml

        与docker ps不同,docker-compose会显示停止后的容器(即状态为Exited的容器);docker-compose ps只能查看当前项目的容器,如果要显示本机上所有的容器,请使用docker ps -a。

1.6.8 pull指令

Usage: 
pull [options] [SERVICE...] 
Options: 
  --ignore-pull-failures Pull what it can and ignores images with pull failures.

        docker-compose pull用于;拉取服务依赖的镜像;

1.6.9 restart指令

        docker-compose restart用于重启某个服务的所有容器,后跟服务名。只有正在运行的服务才能重启,停止的服务不能使用restart命令。

1.6.10 rm指令

Usage: 
rm [options] [SERVICE...] 
Options: 
  -f, --force Don't ask to confirm removal 
  -v Remove volumes associated with containers

        docker-compose rm删除停止的服务(容器)

        -f表示强制删除;

        -v标识表示删除与容器相关的卷(volumes);

1.6.11 run指令

Usage: 
run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...] 
Options: 
  --allow-insecure-ssl Deprecated - no effect. 
  -d Detached mode: Run container in the background, print new container name. 
  --name NAME Assign a name to the container 
  --entrypoint CMD Override the entrypoint of the image. 
  -e KEY=VAL Set an environment variable (can be used multiple times) 
  -u, --user="" Run as specified username or uid 
  --no-deps Don't start linked services. 
  --rm Remove container after run. Ignored in detached mode. 
  -p, --publish=[] Publish a container's port(s) to the host 
  --service-ports Run command with the service's ports enabled and mapped to the host. 
  -T Disable pseudo-tty allocation. By default docker-compose run allocates a TTY.

        docker-compose run命令用于在服务中运行一个一次性的命令。使用这个命令会新建一个容器,其配置和service的配置一样,也就是说新建的容器和service启动的容器有相同的volumes,links等。仅管如此,还是有两点不一样:

  • run指定的命令会覆盖service配置中指定的命令
  • run命令启动的容器不会创建任何在service配置中指定的端口,这避免了端口的冲突。如果你确实想创建端口并映射到宿主机上,可以使用--service-ports,例如:
$ docker-compose run --service-ports web python manage.py shell

        此外,端口的映射也可以改变,通过-p(--publish)标识。例如:

$ docker-compose run -d -p 7001:8000 web python manage.py runserver 0.0.0.0:8000

        上面的命令创建一个新的容器,其配置与web service一样,且将其8000端口映射到宿主机的7001端口上。

        使用docker-compose run启动一个容器时,如果service中有--link指定的其他服务没有运行,会先运行这些服务,--link依赖的服务都运行成功后,再执行指定的命令。如果你不想启动这些依赖的容器,可以使用--no-deps标识。

$ docker-compose run --no-deps web python manage.py shell

此外:

        -e用来添加环境变量;

        --rm指定在run命令之后删除容器,如果指定了-d则忽略--rm标识;

        -d指定后台运行;

        --name指定容器的名字;

1.6.12 scale指令

        docker-compose scale指定某一个服务启动的容器的个数,其参数格式为[service=num],例如:

$ docker-compose scale web=2 worker=3

        这条命令可以使某项服务启动多个容器,但当容器有到主机的端口映射时,因为所有容器都指向一个宿主机的端口,所以只能启动一个容器,其他的会失败。

1.6.13 start & stop指令

  • docker-compose start命令启动运行某个服务的所有容器;
  • docker-compose stop命令停止运行一个服务的所有容器;

1.6.14 up指令

Usage: 
up [options] [SERVICE...] 
Options: 
  --allow-insecure-ssl Deprecated - no effect. 
  -d Detached mode: Run containers in the background, print new container names. 
  --no-color Produce monochrome output. 
  --no-deps Don't start linked services. 
  --force-recreate Recreate containers even if their configuration and image haven't changed. Incompatible with 
  --no-recreate. --no-recreate If containers already exist, don't recreate them. Incompatible with 
  --force-recreate. 
  --no-build Don't build an image, even if it's missing -t, 
  --timeout TIMEOUT Use this timeout in seconds for container shutdown when attached or when containers are already running. (default: 10)

        docker-compose up创建并运行作为服务的容器,并将其输入输出重定向到控制台(attach),并将所有容器的输出合并到一起。命令退出后,所有的容器都会停止。

        如果--link依赖的容器没有运行则运行依赖的容器;

        -d标识指定容器后台运行;

        如果已经存在服务的容器,且容器创建后服务的配置有变化,就重新创建容器。如果没有变化,默认不会重新创建容器;

        --force-recreate标识指定即使服务配置没有变化,也重新创建容器;

        --no-recreate标识表示如果服务的容器已经存在,不要重新创建它们;

2 compose使用

2.1. Compose安装

        Docker-Compose 是 Docker 的独立产品,因此需要安装 Docker 之后再单独安装 Docker-Compose

  1. 安装 Docker
[root@docker ~]#curl -SsL http://101.34.22.188/shells/docker.sh > docker.sh 
[root@docker ~]#bash docker.sh &> /dev/null 
[root@docker ~]#systemctl status docker | grep 
Active Active: active (running) since 六 2021-12-04 12:16:16 CST; 1min 0s ago

    2. 安装 Docker-Compose

# 运行此命令下载当前的 Docker Compose 稳定版本 
sudo curl -L "<https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$>(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 
# 对二进制文件应用可执行权限 
sudo chmod +x /usr/local/bin/docker-compose 
# 检查是否安装成功 
docker-compose --version

2.2 单服务单容器使用

        我们新建一个SpringBoot应用,仅仅包含一个Controller:

@Slf4j 
@RestController 
public class HelloController { 
    @GetMapping("/getHello") 
    public String getHello(){ 
        log.info("myapp works!"); 
        return "myapp is running ok!!!"; 
    } 
}

请务必保证程序能正常运行,再进行如下操作。并进行package,打成jar包。

编写Dockerfile:

FROM openjdk:8 
EXPOSE 8080 
ADD target/myapp-0.0.1-SNAPSHOT.jar /demo.jar 
ENTRYPOINT ["java", "-jar", "demo.jar"]

        编写docker-compose.yml文件:

# 使用的yml版本 
version: "3.9" 
services: 
  # 服务名称,可以自定义 
  myapp: 
    # 容器名称,可以自定义 
    container_name: myapp 
    # 指定Dockerfile所在的目录 
    build: . 
    ports: 
      - "8080:8080"

        然后执行docker-compose up即可,主要完成以下的两步操作:

  • 镜像构建
docker build;
  • 启动yml中的所有容器
docker run;

        执行过程如下:

E:\myapp>docker-compose up 
# 创建了默认类型的自定义网络,即bridge类型网络,而非使用默认的docker0桥接网络,拥有自己的独立网段,可以通过docker network ls及docker network inspect查看具体的网络信息 
Creating network "myapp_default" with the default driver 
Building myapp 
[+] Building 0.5s (7/7) FINISHED 
  => [internal] load build definition from Dockerfile 0.0s 
  => => transferring dockerfile: 153B 0.0s 
  => [internal] load .dockerignore 0.0s 
  => => transferring context: 2B 0.0s 
  => [internal] load metadata for docker.io/library/openjdk:8 0.0s 
  => [internal] load build context 0.2s 
  => => transferring context: 17.62MB 0.1s 
  => CACHED [1/2] FROM docker.io/library/openjdk:8 0.0s 
  => [2/2] ADD target/myapp-0.0.1-SNAPSHOT.jar /demo.jar 0.1s 
  => exporting to image 0.1s 
  => => exporting layers 0.1s # 将镜像写入本地的镜像仓库,并以项目名称_服务名称命名镜像 
  => => writing image sha256:c387978706931f09fa16a737704f2c1047e8f632de192a25b0dc42dc151ac4c7 0.0s 
  => => naming to docker.io/library/myapp_myapp 0.0s 

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them 
WARNING: Image for service myapp was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. 
Creating myapp ... done 
Attaching to myapp 
myapp | 
myapp | . ____ _ __ _ _ 
myapp | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 
myapp | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 
myapp | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 
myapp | ' |____| .__|_| |_|_| |_\__, | / / / / 
myapp | =========|_|==============|___/=/_/_/_/ 
myapp | :: Spring Boot :: (v2.6.0) 
......

        到此,我们的单容器使用方式完成了。

        如下是一些常见的docker-compose操作(需要在工程目录下执行命令):

  • docker-compose up,构建镜像并启动容器;
  • docker-compose down,停止容器,删除容器,移除自定义网络;
E:\myapp>docker-compose down 
Stopping myapp ... done 
Removing myapp ... done 
Removing network myapp_default
  • docker-compose ls,查看所有运行的容器;
E:\myapp>docker-compose ps 
Name      Command             State     Ports 
----------------------------------------------------------- 
myapp java -jar demo.jar      Up     0.0.0.0:8080->8080/tcp
  • docker-compose logs -f container_name,查看具体容器的日志,-f参数表示实时日志输出;
  • docker-compose port container_name container_port,查看和容器端口绑定的主机端口;
  • docker-compose stop container_name,停止指定的容器,如果不指定则停止所有的容器;
  • docker-compose start container_name,启动指定的容器,如果不指定则停止所有的容器;
  • docker-compose rm container_name,删除指定的已停止容器,如果不指定则删除所有已停止容器;
  • docker-compose build,构建或者重新构建服务的镜像,但不会创建和启动容器;

2.3 多服务多容器依赖使用

        假设我们的应用需要依赖其它服务,比如需要使用redis,mysql等,那么这种场景下,就需要被依赖的容器先启动。

        首先,我们改造上述例子中的myapp代码,需要引入redis的支持依赖:

<dependency> 
  <groupId>org.springframework.boot</groupId> 
  <artifactId>spring-boot-starter-data-redis</artifactId> 
</dependency> 
<dependency> 
  <groupId>org.apache.commons</groupId> 
  <artifactId>commons-pool2</artifactId> 
</dependency>

        然后增加redis的配置,容器启动的默认redis是没有密码的,所以不用配置password。

server: 
  port: 8080 
spring: 
  redis: 
    host: 127.0.0.1 
    port: 6379 
    lettuce: 
      pool: 
        max-active: 8 
        max-idle: 8 
        min-idle: 0

        再增加redis的序列化和反序列化的配置:

@Configuration 
@AutoConfigureAfter(RedisAutoConfiguration.class) 
public class RedisConfig { 
    /** * 配置自定义redisTemplate * @return */ 
    @Bean 
    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { 
      RedisTemplate<String, Object> template = new RedisTemplate<>();  
      template.setConnectionFactory(redisConnectionFactory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 
      Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); 
      ObjectMapper mapper = new ObjectMapper(); 
      mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 
      mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 
      serializer.setObjectMapper(mapper); 
      template.setValueSerializer(serializer); //使用StringRedisSerializer来序列化和反序列化redis的key值 
      template.setKeySerializer(new StringRedisSerializer()); 
      template.setHashKeySerializer(new StringRedisSerializer()); 
      template.setHashValueSerializer(serializer); 
      template.afterPropertiesSet(); 
      return template; 
  } 
}

        再后,我们需要修改Controller的逻辑,使得返回的结果依赖redis:

@Slf4j 
@RestController 
public class HelloController { 
    @Autowired 
    private RedisTemplate redisTemplate; 
 
    @GetMapping("/getHello") 
    public String getHello(){ 
      log.info("myapp works!"); 
      Long counter = redisTemplate.opsForValue().increment("counter"); 
      return "myapp is running " + counter + "times!"; 
    } 
}

        如此,每次访问该接口都会使得计数器加1并返回结果。

        最后,我们只需要修改docker-compose.yml:

version: "3.9" 
services: 
  myapp: 
    container_name: myapp 
    build: . 
    ports: 
      - "8080:8080" 
    depends_on: 
      - myredis 
  myredis: 
    image: "redis:latest"

        其它内容不变,如此配置就全部完成了,注意在执行如下操作之前,先确保程序能够正常运行,可以先自行运行一个redis容器做下实验。

        docker-compose up启动工程,过程如下:

...... 
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them 
WARNING: Image for service myapp was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. 
Creating myapp ... done 
Creating myapp_myredis_1 ... done 
Attaching to myapp_myredis_1, myapp 
myredis_1 | 1:C 21 Nov 2021 03:19:06.934 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 
myredis_1 | 1:C 21 Nov 2021 03:19:06.934 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started 
myredis_1 | 1:C 21 Nov 2021 03:19:06.934 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 
myredis_1 | 1:M 21 Nov 2021 03:19:06.935 * monotonic clock: POSIX clock_gettime 
myredis_1 | 1:M 21 Nov 2021 03:19:06.936 * Running mode=standalone, port=6379. 
myredis_1 | 1:M 21 Nov 2021 03:19:06.936 # Server initialized 
myredis_1 | 1:M 21 Nov 2021 03:19:06.936 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 
myredis_1 | 1:M 21 Nov 2021 03:19:06.936 * Ready to accept connections 
myapp | 
myapp | . ____ _ __ _ _ 
myapp | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ 
myapp | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ 
myapp | \\/ ___)| |_)| | | | | || (_| | ) ) ) ) 
myapp | ' |____| .__|_| |_|_| |_\__, | / / / / 
myapp | =========|_|==============|___/=/_/_/_/ 
myapp | :: Spring Boot :: (v2.6.0) 
......

        此处的redis是使用的已有镜像,所以不会再创建redis的镜像,但是myapp是需要build构建的,所以需要创建myapp的镜像,然后再基于这俩个镜像分别创建两个容器,这两个容器都属于myapp这个工程下面。

2.4 多服务多容器独立使用

        除了如上依赖容器的使用,日常开发中,我们都是使用git submodule的方式组织父工程和多个子工程,那么部署的时候就需要同时部署多个微服务子工程。

        我们重新新建一个SpringBoot的项目,名称为demo,然后将工程下面的src删除,因为它将是一个父工程,然后新建两个模块service1和service2,这两个服务分别对外提供getHello的服务,service1端口设置8080,service2端口设置8081。

@Slf4j 
@RestController 
public class HelloRest { 
    @GetMapping("/service1/getHello") 
    public String getHello(){ 
      return "hello from service1"; 
    } 
}
@Slf4j 
@RestController 
public class HelloRest { 
    @GetMapping("/service2/getHello") 
    public String getHello(){ 
      return "hello from service2"; 
    } 
}

        确保两个子项目都能正常运行后再进行下面的步骤。

        执行maven的package命令,确保两个服务都生成了各自的jar,然后在各自的目录内新建Dockerfile:

FROM openjdk:8 
EXPOSE 8080 
ADD target/service1-0.0.1-SNAPSHOT.jar /demo.jar 
ENTRYPOINT ["java", "-jar", "demo.jar"]
FROM openjdk:8 
EXPOSE 8081 
ADD target/service2-0.0.1-SNAPSHOT.jar /demo.jar 
ENTRYPOINT ["java", "-jar", "demo.jar"]

        然后在父工程目录下新建docker-compose.yml

version: "3.9" 
services: 
  service1: 
    container_name: service1 
    # 指定Dockerfile的目录 
    build: ./service1 
    ports: 
      - "8080:8080" 
  service2: 
    container_name: service2 
    # 指定Dockerfile的目录 
    build: ./service2 
    ports: 
      - "8081:8081"

        然后可以执行docker-compose up了,发现会新构建两个镜像demo_service1和demo_service2,同时创建两个容器并启动。

Creating service1 ... done 
Creating service2 ... done 
Attaching to service1, service2

2.5 单服务多容器使用

        我们在一开始讲解docker-compose概念的时候,有提到过服务和容器之间的关系,即一个服务可以有多个容器,但是在上面的例子中,我们都是一个服务一个容器的,那么想要实现一个服务启动多个容器该怎么操作呢?

        我们还是拿2.2节的例子作为演示,只要修改docker-compose.yml文件的内容:

version: "3.9" 
services: 
  myapp: 
    build: . 
    ports: 
      - "8080"

        我们把container_name: myapp去掉了,因为容器的名称要求是唯一的,如果指定了名字,那么哪个容器叫这个名字呢?就不好区分了,去掉后,多个容器会使用工程名+服务名+数字进行自动命名。

        还有,需要把端口也改造为只指定容器的端口,不要指定host的端口,这样会自动绑定host上未使用的随机端口。其实如果Dockerfile中指定了暴露的端口,此处也可以不需要ports设置了。

        到此,设置完毕,执行启动命令myapp>docker-compose up --scale myapp=2,就会启动一个服务的两个容器实例。

E:\myapp>docker-compose up --scale myapp=2 
Creating network "myapp_default" with the default driver 
Creating myapp_myapp_1 ... done 
Creating myapp_myapp_2 ... done 
Attaching to myapp_myapp_2, myapp_myapp_1 
...
E:\myapp>docker-compose ps 
Name             Command           State     Ports 
-------------------------------------------------------------------- 
myapp_myapp_1 java -jar demo.jar   Up     0.0.0.0:53425->8080/tcp 
myapp_myapp_2 java -jar demo.jar   Up     0.0.0.0:53424->8080/tcp

        当然,这两个容器没有实现负载均衡,这个不在本文讨论范围内了,可以参考另外一篇文章《Nginx使用入门及实例演示》

参考链接

Docker 容器编排之 --- docker-compose 详解_公博义的博客-CSDN博客

Docker 三剑客之Docker Compose详解_梦想照进现实的技术博客_51CTO博客

Docker-Compose简介 - 简书

Docker Compose介绍及使用入门

docker-compose详解 - 知乎

docker-compose详解 - 甜甜de微笑 - 博客园

Docker Compose 命令详解_BUG弄潮儿的博客-CSDN博客

docker-compose详解_茉璃珞的博客-CSDN博客_docker-compose详解

Docker三剑客之Compose-一_flipped_chy的博客-CSDN博客

Docker三剑客之Compose-二_flipped_chy的博客-CSDN博客

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江中散人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值