乐趣区

关于docker:DockerCompose-入门到实战详尽笔记

本文首发自「慕课网」,想理解更多 IT 干货内容,程序员圈内热闻,欢送关注 ” 慕课网 ” 或慕课网公众号!

作者:暮闲 | 慕课网讲师


应用过 Docker 的小伙伴们都晓得,启动 Docker 时个别会附带很多的启动参数,如 -v 指定挂载目录,- p 指定端口等等。除此之外,很多时候咱们的业务零碎中个别都会有几个 Docker 组合运行,容器间网络通信,容器的启动程序等有明确的要求。基于这些问题,Docker-Compose 技术诞生。

本文将从根底到实战举例,共计 7 个大节,后面 5 大节解说根底,前面 2 个大节则时以实战为主,具体解说 Docker-Compose 的应用。次要解说如下内容:

  1. Docker-Compose 和 Docker 的分割
  2. 装置 Docker-Compose
  3. YAML 语法概述
  4. Docker-Compose 语法总结
  5. Docker-Compose 常用命令
  6. 实战演练一:Docker-Compose 部署伪分布式的 Elasticsearch
  7. 实战演练二:Docker-Compose 部署 Kafka(Zookeeper)

如果根底好一点的同学,能够间接跳到第 6 章开始学习实战环节,有问题再倒回来查看根底。
浏览并实际本篇文章内容,大抵须要 30 分钟左右。

Docker-Compose 和 Docker 的分割

置信你们在应用 Docker 时都遇到过以下两个场景:

  1. Docker 启动要指定参数,例如 -p 指定端口,-v 挂载目录等,当下次启动时,因为参数太多,遗记启动参数。
  2. 多个 Docker 容器启动时有依赖关系,不确定启动的先后顺序。

而 Docker-Compose 的呈现就是为了解决上述两个问题,通过编写 yaml 文件定义 Docker 启动参数和编排容器。

装置 Docker-Compose
Docker-Compose 反对目前支流平台的装置,如图:

参考连贯:https://docs.docker.com/compose/install/

小伙伴们依据本人的状况装置对应的版本即可,我这里以 Ubuntu 16.04 环境作为演示,也能够依照官网所述的装置步骤,也能够间接一句命令即可(当然前提时曾经装置 Docker):
apt install docker-compose

装置结束后 docker-compose --version 查看版本

每次输出 docker-compose 这个命令的确比拟长,我这里教搭建一个给 docker-compose 取别名的形式,操作如下:

vi ~/.bashrc
alias dc='docker-compose'
source ~/.bashrc

上述操作的原理就是在零碎的环境变量外面为某个指令增加别名,执行完上述三条命令,docker-compose 应用 dc 就能够调用了,如图:

YAML 语法概述
Docker-Compose 容器编排的次要利用到 YAML 语法。学习 YAML 语法能够和 JSON 类比学习,例如 JSON 中有对象、数组等,利用 YAML 语法均能够示意进去。具体的内容举荐看 阮一峰的《YAML 语言教程》

Docker-Compose 语法总结
后面的铺垫打好之后,这一章咱们开始学习
Docker-Compose 语法,如果相熟 Docker 语法的小伙伴,这一章将会学得很快。Docker-Compose
次要目标就是用作容器的编排,编排容器用的是 YAML 语法,编排文件内容都写在 docker-compose.yml 文件中。上面咱们依据
Docker-Compose 的编排罕用的语法顺次做解说,局部不罕用的就小伙伴们依据本人的理论需要去官方网站查问即可,Docker
反对的操作,Docker-Compose 是必定反对的。

version
每个 docker-compose.yml 文件第一个字段就是
version,version 字段是表明应用那个版本的 Compose,Compose 有如下的版本,目前的最新版是
3.7,此外还有 1、2、2.x、3.x,不同版本的 compose 反对了不同的 Docker 版本。
compose 与 Docker 的版本对应关系表:

除了表中显示的 Compose 文件格式版本外,Compose 自身也处于公布打算中,如 Compose 版本中所示,然而文件格式版本不肯定会随每个版本而减少。例如,Compose 文件格式 3.0 最后是在 Compose 版本 1.10.0 中引入的,并在随后的版本中逐步版本化。

语法结构如下:
version: "2"

version: "3.7"
#能够简写为
version: "3"

image

services:
  web:
    image: hello-world

在 services 标签下的第二级标签是 web,这个名字是用户本人自定义,它就是服务名称。
image 则是指定服务的镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。

build
服务除了能够基于指定的镜像,还能够基于一份
Dockerfile,在应用 up 启动之时执行构建工作,这个构建标签就是 build,它能够指定 Dockerfile
所在文件夹的门路。Compose 将会利用它主动构建这个镜像,而后应用这个镜像启动服务容器。
build: /path/to/build/dir

也能够是相对路径,只有上下文确定就能够读取到 Dockerfile
build: ./dir

设定上下文根目录,而后以该目录为准指定 Dockerfile

build:
  context: ../
  dockerfile: path/of/Dockerfile

援用
留神 build 都是一个目录,如果你要指定 Dockerfile 文件须要在 build 标签的子级标签中应用 dockerfile 标签指定,如下面的例子。
如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 前面的那个名字。

build: ./dir
image: webapp:tag

既然能够在 docker-compose.yml
中定义构建工作,那么肯定少不了 arg 这个标签,就像 Dockerfile 中的 ARG
指令,它能够在构建过程中指定环境变量,然而在构建胜利后勾销,在 docker-compose.yml 文件中也反对这样的写法:

build:
  context: .
  args:
    buildno: 1
    password: secret

上面这种写法也是反对的,一般来说上面的写法更适宜浏览。

build:
  context: .
  args:
    - buildno=1
    - password=secret

与 ENV 不同的是,ARG 是容许空值的,这样构建过程能够向它们赋值。。例如:

args:
  - buildno
  - password

留神:YAML 的布尔值(true, false, yes, no, on, off)必须要应用引号引起来(单引号、双引号均可),否则会当成字符串解析。

command
应用 command 能够笼罩容器启动后默认执行的命令。
command: echo 'hello world'

也能够写成相似 Dockerfile 中的格局:
command: ['echo','hello world']

container_name
后面说过 Compose 的容器名称格局是:< 项目名称 >< 服务名称 >< 序号 >
尽管能够自定义项目名称、服务名称,然而如果你想齐全管制容器的命名,能够应用这个标签指定:
container_name: app

这样容器的名字就指定为 app 了。

depends_on
在应用 Compose 时,最大的益处就是少打启动命令,然而个别我的项目容器启动的程序是有要求的,如果间接从上到下启动容器,必然会因为容器依赖问题而启动失败。
例如在没启动数据库容器的时候启动了利用容器,这时候利用容器会因为找不到数据库而退出,为了防止这种状况咱们须要退出一个标签,就是 depends_on,这个标签解决了容器的依赖、启动先后的问题。
例如上面容器会先启动 redis 和 db 两个服务,最初才启动 web 服务:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

留神的是,默认状况下应用 docker-compose up web 这样的形式启动 web 服务时,也会启动 redis 和 db 两个服务,因为在配置文件中定义了依赖关系。

dns
和 –dns 参数一样用处,格局如下:
dns: 8.8.8.8

也能够是一个列表:

dns:
  - 8.8.8.8
  - 9.9.9.9

此外 dns_search 的配置也相似:

dns_search: example.com
dns_search:
  - dc1.example.com
  - dc2.example.com

tmpfs
挂载长期目录到容器外部,与 run 的参数一样成果:

tmpfs: /run
tmpfs:
  - /run
  - /tmp

entrypoint
在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点,ENTRYPOINT 配置容器启动时的执行命令,如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最初一个失效。(不会被疏忽,肯定会被执行,即便运行 docker run 时指定了其余命令)。
在 docker-compose.yml 中能够定义接入点,笼罩 Dockerfile 中的定义:
entrypoint: /code/entrypoint.sh

env_file
还记得后面提到的 .env 文件吧,这个文件能够设置 Compose 的变量。而在 docker-compose.yml 中能够定义一个专门寄存变量的文件。
如果通过 docker-compose -f FILE 指定了配置文件,则 env_file 中门路会应用配置文件门路。
如果有变量名称与 environment 指令抵触,则当前者为准。格局如下:
env_file: .env

或者依据 docker-compose.yml 设置多个:

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

留神的是这里所说的环境变量是对宿主机的 Compose 而言的,如果在配置文件中有 build 操作,这些变量并不会进入构建过程中,如果要在构建中应用变量还是首选后面刚讲的 arg 标签。

environment
与下面的 env_file 标签齐全不同,反而和 arg 有几分相似,这个标签的作用是设置镜像变量,它能够保留变量到镜像外面,也就是说启动的容器也会蕴含这些变量设置,这是与 arg 最大的不同。
个别 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量始终保留在镜像、容器中,相似 docker run -e 的成果。

environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:
 
environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

expose
这个标签与 Dockerfile 中的 EXPOSE 指令一样,用于指定裸露的端口,然而只是作为一种参考,实际上 docker-compose.yml 的端口映射还得 ports 这样的标签。

expose:
 - "3000"
 - "8000"

external_links
在应用 Docker 过程中,咱们会有许多独自应用 docker
run 启动的容器,为了使 Compose 可能连贯这些不在 docker-compose.yml
中定义的容器,咱们须要一个非凡的标签,就是 external_links,它能够让 Compose
我的项目外面的容器连贯到那些我的项目配置内部的容器(前提是内部容器中必须至多有一个容器是连贯到与我的项目内的服务的同一个网络外面)。
格局如下:

external_links:
 - redis_1
 - project_db_1:mysql
 - project_db_1:postgresql

extra_hosts
增加主机名的标签,就是往 /etc/hosts 文件中增加一些记录,与 Docker client 的–add-host 相似:

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"

启动之后查看容器外部 hosts:

162.242.195.82 somehost
50.31.209.229 otherhost

labels
向容器增加元数据,和 Dockerfile 的 LABEL 指令一个意思,相似于代码正文,这个就是对 Compose 做一个陈说,格局如下:

labels:
  com.example.description: "Accounting webapp"
  com.example.department: "Finance"
  com.example.label-with-empty-value: ""
labels:
  - "com.example.description=Accounting webapp"
  - "com.example.department=Finance"
  - "com.example.label-with-empty-value"

links
后面提到的参数 depends_on 解决的是启动程序问题,这个标签解决的是容器连贯问题,与 Docker client 的–link 一样成果,会连贯到其它服务中的容器。

links:
 - db
 - db:database
 - redis

ports
映射端口的标签。
应用 HOST:CONTAINER 格局或者只是指定容器的端口,宿主机会随机映射端口。

ports:
 - "3000"
 - "8000:8000"
 - "49100:22"
 - "127.0.0.1:8001:8001"

留神:当应用 HOST:CONTAINER 格局来映射端口时,如果你应用的容器端口小于 60 你可能会失去谬误得后果,因为 YAML 将会解析 xx:yy 这种数字格局为 60 进制。所以倡议采纳字符串格局。

volumes
挂载一个目录或者一个已存在的数据卷容器,能够间接应用 [HOST:CONTAINER] 这样的格局,或者应用 [HOST:CONTAINER:ro] 这样的格局,后者对于容器来说,数据卷是只读的,这样能够无效爱护宿主机的文件系统。
Compose 的数据卷指定门路能够是相对路径,应用 . 或者 … 来指定绝对目录。
数据卷的格局能够是上面多种形式:

volumes:
  # 只是指定一个门路,Docker 会主动在创立一个数据卷(这个门路是容器外部的)。- /var/lib/mysql
 
  # 应用绝对路径挂载数据卷
  - /opt/data:/var/lib/mysql
 
  # 以 Compose 配置文件为核心的相对路径作为数据卷挂载到容器。- ./cache:/tmp/cache
 
  # 应用用户的相对路径(~/ 示意的目录是 /home/< 用户目录 >/ 或者 /root/)。- ~/configs:/etc/configs/:ro
 
  # 曾经存在的命名的数据卷。- datavolume:/var/lib/mysql

network_mode
网络模式,与 Docker client 的–net 参数相似,只是绝对多了一个 service:[service name] 的格局。
例如:

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

能够指定应用服务或者容器的网络,这几种网络模式有很大的区别,有空的话都能够去理解一下,默认是 bridge,即桥接模式。

networks
退出指定网络,格局如下:

services:
  some-service:
    networks:
     - some-network
     - other-network

对于这个标签还有一个特地的子标签 aliases,这是一个用来设置服务别名的标签,例如:

services:
  some-service:
    networks:
      some-network:
        aliases:
         - alias1
         - alias3
      other-network:
        aliases:
         - alias2

雷同的服务能够在不同的网络有不同的别名。

Docker-Compose 常用命令
这一章咱们来持续学习无关 Docker-Compose 的执行命令,Docker-Compose 总体来看和 Docker 命令很相似,具体有哪些命令,咱们能够通过 docker-compose -h 查看,如图:

上面我来顺次对罕用的做阐明,并做适当的举例辅助了解。

docker-compose up -d nginx    构建建启动 nignx 容器
docker-compose exec nginx bash    登录到 nginx 容器中
docker-compose down    删除所有 nginx 容器, 镜像
docker-compose ps    显示所有容器
docker-compose restart nginx    重新启动 nginx 容器
docker-compose run --no-deps --rm php-fpm php -v    在 php-fpm 中不启动关联容器,并容器执行 php -v 执行实现后删除容器
docker-compose build nginx    构建镜像
docker-compose build --no-cache nginx    不带缓存的构建
docker-compose logs  nginx    查看 nginx 的日志
docker-compose logs -f nginx    查看 nginx 的实时日志
docker-compose config  -q    验证(docker-compose.yml)文件配置,当配置正确时,不输入任何内容,当文件配置谬误,输入错误信息。docker-compose events --json nginx    以 json 的模式输入 nginx 的 docker 日志
docker-compose pause nginx    暂停 nignx 容器
docker-compose unpause nginx    复原 ningx 容器
docker-compose rm nginx    删除容器(删除前必须敞开容器)docker-compose stop nginx    进行 nignx 容器
docker-compose start nginx    启动 nignx 容

到这里根底环境咱们曾经学习结束,上面我开始进入缓和刺激的实战环节,小伙伴也能够在学完根底章节后,只看实战的题目,试着本人去写写,而后和我的对照这看。

实战演练一:Docker-Compose 部署伪分布式的 Elasticsearch
Elasticsearch 是一个开源、高扩大的分布式全文检索引擎,能够近乎实时的存储、检索数据。而且扩展性很好,条件容许下能够扩大到上百台服务器,解决 PB 级别的数据,曾经广泛应用于业务零碎及大数据我的项目中。通过 Docker-Compose 搭建的目标是咱们能够疾速搭建一个伪分布式的 Elasticsearch 集群,这样做的目标是为了学习不便同时也阔以用于小型零碎的生产环境,从而不必为了搭建环境而浪费时间。

Elastisearch 官网 Docker-Compose 装置阐明:https://www.elastic.co/guide/en/elasticsearch/reference/curre…

上面的 docker-compose 文件来自于 Elasticsearch 官网举荐,为了运行更不便,我提前将 Elasticsearch 镜像下载到阿里云镜像。具体的解释以正文的形式在文件中示意。

version: '2'
services:
  # 服务名称,这里启动了三个,别离是 es01,es02,es03
  es01:
   #将官网地址替换为了我的阿里云地址,这样下载速度会更快
    image: registry.cn-hangzhou.aliyuncs.com/chand/elasticsearch:7.10.1
    #指定容器的名称
    container_name: es01
    environment:
      # 配置 es 相干的环境变量
      - node.name=es01
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es02,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
     #ulimit 设置限度的时候会设置两条线 soft 和 hard 线,当资源达到了 soft 线那么只是告警,如果达到了 hard 线那么内核就强制限度了。ulimits:
      memlock:
        soft: -1
        hard: -1
     #数据挂载到本地目录
    volumes:
      - data01:/usr/share/elasticsearch/data
     #指定裸露的端口
    ports:
      - 9200:9200
     #指定网路
    networks:
      - elastic
  #es02、es03 与上述的 es01 配置简直统一,前面就不赘述了
  es02:
    image: registry.cn-hangzhou.aliyuncs.com/chand/elasticsearch:7.10.1
    container_name: es02
    environment:
      - node.name=es02
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es03
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data02:/usr/share/elasticsearch/data
    
    networks:
      - elastic
  es03:
    image: registry.cn-hangzhou.aliyuncs.com/chand/elasticsearch:7.10.1
    container_name: es03
    environment:
      - node.name=es03
      - cluster.name=es-docker-cluster
      - discovery.seed_hosts=es01,es02
      - cluster.initial_master_nodes=es01,es02,es03
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - data03:/usr/share/elasticsearch/data
    networks:
      - elastic
volumes:
  data01:
    driver: local
  data02:
    driver: local
  data03:
    driver: local
networks:
  elastic:
    driver: bridge

配置好上述命令后,咱们来执行命令 docker-compose up -d 启动 Compose,docker-compose ps 查看启动状态,如图:

ip:9200/_cluster/health?pretty浏览器查看是否启动集群胜利,我本地 ip 是 192.168.10.107,拜访地址:http://192.168.10.107:9200/_cluster/health?pretty,后果如图:

能够看到胜利启动了集群,至此咱们的第一实战演示结束。我这里在运行时候因为内存不足报错了异样信息如下:

援用
[max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]](https://www.cnblogs.com/yidiandhappy/p/7714489.html)

解决方案:
sysctl -w vm.max_map_count=262144

为了避免重启后生效,批改虚拟机配置文件 vi /etc/sysctl.conf,在最初一行增加如下内容:
vm.max_map_count=262144

实战演练二:Docker-Compose 部署 Kafka(Zookeeper)
紧接着咱们来到第二个实战演示,这个也是小伙伴们在日常工作会遇到的场景,搭建
Kafka。搭建 kafka 比拟麻烦一点的是因为 Kafka 高度依赖 Zookeeper。上面咱们来看一下通过 Docker-Compose 如何解决,具体阐明在文件中以注解的模式示意。

version: '2'
services:
  zookeeper:
    #拉取 zookeeper 镜像,更换为阿里云镜像源,晋升拉取速度
    image: registry.cn-hangzhou.aliyuncs.com/chand/zookeeper
    #裸露映射的端口
    ports:
      - "2181:2181"
    #指定 zookeeper 的容器名称
    container_name: "zookeeper"
    #主动重启,即便主机重启也会重启容器
    restart: always
  kafka:
    #拉取 kafka 镜像,更换为阿里云镜像源,晋升拉取速度
    image: registry.cn-hangzhou.aliyuncs.com/chand/kafka:2.12-2.3.0
    #指定 kafka 容器名称
    container_name: "kafka"
    #指定端口
    ports:
      - "9092:9092"
    #设置相干 kafka 的配置
    environment:
      - TZ=CST-8
      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
      # 非必须,设置主动创立 topic
      - KAFKA_AUTO_CREATE_TOPICS_ENABLE=true
      # 修为本人本机理论的 IP 地址
      - KAFKA_ADVERTISED_HOST_NAME=192.168.10.107
      - KAFKA_ADVERTISED_PORT=9092
      # 批改为本人理论的 IP 地址
      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://192.168.10.107
      - KAFKA_LISTENERS=PLAINTEXT://:9092
      # 非必须,设置对内存
      - KAFKA_HEAP_OPTS=-Xmx1G -Xms1G
      # 非必须,设置保留 7 天数据,为默认值
      - KAFKA_LOG_RETENTION_HOURS=1
    volumes:
      # 将 kafka 的数据文件映射进去
      - /home/kafka:/kafka
      - /var/run/docker.sock:/var/run/docker.sock
     #主动重启,即便主机重启也会重启容器
    restart: always

执行命令 dc up -d 启动服务,dc ps 查看服务的启动状态。

启动结束后,咱们来验证是否胜利部署 kafka,我这里通过 Java 调用 kafka 接口,演示创立主题和遍历主题,以测试 kafka 是否联通为目标。

AdminClientTest

package cn.czyfwpla.kafka.examples.admin;

import java.util.concurrent.ExecutionException;

/**
 * @author mxq
 * 调用 kafka admin api 代码示例
 */
public class AdminClientTest {public static void main(String[] args) throws ExecutionException, InterruptedException {AdminExampleApi adminExampleApi = new AdminExampleApi();
        adminExampleApi.listTopics();
        adminExampleApi.createTopic("mxq-test");
        adminExampleApi.listTopics();}

}

AdminExampleApi

package cn.czyfwpla.kafka.examples.admin;

import cn.czyfwpla.kafka.examples.KafkaConstant;
import org.apache.kafka.clients.admin.*;
import org.apache.kafka.common.KafkaFuture;

import java.util.*;
import java.util.concurrent.ExecutionException;

/**
 * @author mxq
 */
public class AdminExampleApi {


    // 创立主题
    public void createTopic(String topicName) throws ExecutionException, InterruptedException {Properties properties = new Properties();
        properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaConstant.KAFKA_HOST);


        AdminClient adminClient = AdminClient.create(properties);

        // 正本数量
        Short replaceNum = 1;


        NewTopic newTopic = new NewTopic(topicName, 1, replaceNum);


        Collection collection = new ArrayList();
        collection.add(newTopic);
        CreateTopicsResult topics = adminClient.createTopics(collection);
        KafkaFuture<Void> all = topics.all();
        all.get();}

    // 遍历主题
    public void listTopics() throws ExecutionException, InterruptedException {Properties properties = new Properties();
        properties.setProperty(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaConstant.KAFKA_HOST);

        AdminClient adminClient = AdminClient.create(properties);

        ListTopicsResult listTopicsResult = adminClient.listTopics();

        KafkaFuture<Set<String>> names = listTopicsResult.names();

        Set<String> stringSet = names.get();

        Iterator<String> iterator = stringSet.iterator();

        // 遍历主题的名称
        while (iterator.hasNext()){System.out.println(iterator.next());
        }

        KafkaFuture<Collection<TopicListing>> listings = listTopicsResult.listings();
        Collection<TopicListing> topicListings = listings.get();
        // 遍历主题、音讯类型
        for (TopicListing topicListing : topicListings) {System.out.println(topicListing.toString());
        }
    }

}

后果:

总结
到这里文章也该完结了,对整个文章做一个总结吧,为什么说曾经有很多作者都写了 Docker-Compose,我这里又来写呢。起因目前市面上的 Docker-Compose 文章次要是解说实践为主,小伙伴们在学习的时候如果不亲自实际,我感觉学到的货色也仅仅停留在实践,难以在工作中起到帮忙作用。我这篇文章后面解说实践为根底,前面以解说实战为主。前两个实战在咱们学习或搭建小型生产环境时,极大晋升效率,第三个实战则是以理论我的项目公布为背景,四个容器组合公布,让小伙伴们直观感触到 Docker-Compose 给咱们带来的便当。实战环境因为我的环境和小伙伴们的环境多少会有一些不同,如果你在运行的时候遇到问题,亦或者是文章有写得不对的中央,欢送在下方留言指出。


欢送关注「慕课网」官网帐号,咱们会始终保持提供 IT 圈优质内容,分享干货常识,大家一起独特成长吧!

本文原创公布于慕课网,转载请注明出处,谢谢合作

退出移动版