第9章 Docker Compose
对于现代应用来说,大多都是通过很多的微服务互相协同组成的一个完整应用。例如,订单管理、用户管理、品类管理、缓存服务、数据库服务等,它们构成了一个电商平台的应用。而部署和管理大量的服务容器是一件非常繁琐的事情。而 Docker Compose 就是解决这类问题的。
9.1 简介
Docker Compose 是一个需要在 Docker 主机上进行安装的 Docker 容器编排外部工具。其并不是通过脚本或各种冗长的 Docker 命令来将应用组件组织起来,而是通过一个声明式的配置文件描述整个应用,然后通过一条命令完成应用部署。部署成功后,还可通过一系列简单命令实现对其完整生命周期的管理。
Docker Compose 的前身是 Fig(无花果)。
9.2 compose 文件
9.2.1 文件简介
Docker Compose 使用 YAML 文件来定义服务。官方推荐的默认文件名为 compose.yml ,但同时也支持 docker-compose.yml。由于一个 compose 文件中定义的为一个项目的所有服务,所以一般为在创建 compose文件之前先新建一个目录,目录名称一般为项目名称,然后再将项目所需的所有镜像、微服务的 Dockerfile 放入该目录,并在该目录中新建 compose 文件。compose 文件中包含 6 个顶级属性:version、services、networks、volumes、configs secrets,及很多的它们下面所包含的属性。下面简单介绍一下常用的属性。
9.2.2 version
version 是一个顶级属性,但已经过时,不再需要在 compose 文件中出现了。从以下官网说明中可以看出。
![image-20240715090933480](https://img-blog.csdnimg.cn/img_convert/996c4e66ea7c74e55b971a82a622f471.png)
9.2.3 networks
networks 作为一个顶级属性,用于定义和创建应用中所使用到的所有网络。其下包含的第一级属性即为网络名称,这个网络名称可以随意命名。而在网络名称下还可包含很多的属性,常用属性如下:
services:
app:
networks:
# 这里使用的并不是网络名称
- app_bridge
networks:
app_bridge:
# 这才是网络名称
name: appBGnet
driver: bridge
-
name
networks 下的第一级属性—网络名称,并不是真正的网络名称,而仅仅是网络名称的一部分。在真正生成网络后,其真正的网络名称格式为:当前 compose 文件所在目录名_networks 下的第一级属性。但如果设置了name属性,则网络名称即为这里指定的名称,不会出现名称再合成情况。
-
driver
用于指定网络驱动,缺省驱动为 Bridge。
-
attachable
如果该属性设置为 true,则除了当前 compose 中定义的服务外,其它独立容器也可以连接到此网络,并能与该网络中的服务通信并且连接到该网络的其它独立容器通信。缺省状态为false。
9.2.4 volumes
volumes 作为一个顶级属性,用于定义和创建应用中所使用到的所有 volume。其下包含的第一级属性即为 volume 的卷标,这个卷标可以随意命名。这个卷标所代表的是当前 Docker主机中的目录,至于该目录的具体位置,是由系统自动分配的。在网络名称下还可包含很多的属性,但这些属性并不常用,所以这里不进行介绍了。
9.2.5 serivces
services 是一个顶级属性,用于定义一个应用中所包含的服务。Docker Compose 会将每个服务部署在各自的容器中。其下包含的第一级的属性即为服务名称,这个名称可以根据服务内容随意命名。而在服务名称下还可包含很多的属性,常用属性如下:
-
build
用于指定一个 Dockerfile 的路径。而该 Dockerfile 则是用于创建当前服务镜像的。这个路径可以是以斜杠(/)开头的绝对路径,也可以是相对于当前 compose 文件的、以点(.)号开头的相对路径。如果 Dockerfile 文件名不是默认名称,则需要通过 build 下的 context 属性指定路径,dockerfile 属性指定文件名。
build: context: ./ dockerfile: myDockerfile
-
image
用户指定当前服务所需要使用的镜像,这个镜像可以是本地镜像,也可以是远程镜像仓库中的镜像(会自动 pull)。如果设置了 build,此时再设置的 image 属性即为构建出的镜像的名称与 Tag。
-
container_name
该属性用于设置容器名称,但并不是必须的。如果没有设置该属性,容器名称则会采用“合成方式”。而合成时需要用到 services 下的第一级属性。在 services 下存在一级属性,称为服务名称。该级属性是作为 services 下的第一级属性出现的。服务名称将来会作为容器名称的一部分出现。容器的名称格式为:当前 compose文件所在目录名_ 服务名称。如果在 services 下没有指定 image 属性,而是使用 bild 属性,即没有现成的镜像,而是根据 build 下指定Dockerfile 生成镜像,此时生成的镜像名称格式为:当前 compose 文件所在目录名-服务名称
-
ports
一个列表。前面为暴露出的端口号,后面为容器中应用的端口号。如果仅设置了一个端口号,那么这个端口号是容器中应用的端口号,其暴露到宿主机的端口号会被随机分配
ports: # 绑定容器的 80 端口到主机的 80 端口 - 80:80 # 绑定容器的 80 端口到主机的 9000 端口 - 9000:80 # 绑定容器的 443 端口到主机的任意端口,容器启动时随机分配绑定的主机端口号 - 443
-
command
用于覆盖 Dockerfile 中的 CMD 指令内容,即启动该服务容器后立即运行的命令。如果直接按照Dockerfile中的CMD指令内容执行即可,则compose文件中无需该command属性。
-
depends_on
一个列表。用于指定当前服务的启动所依赖的应用名称。即列表中指定的服务会先于当前服务启动
-
deploy
用于指定当前服务容器的部署设置。其下有一个常用属性 replicas,用于指定该服务启动的容器的数量。即实现一个服务多个容器。一旦指定了 deploy:replicas,就不能再指定container_name 属性了。因为各个启动的容器名称不能相同,而只能由系统自动生成。
services: frontend: image: awesome/webapp deploy: mode: replicated replicas: 6
-
networks
用于指定当前服务容器要连接到的网络。该网络必须是已经存在的,或通过顶级属性networks 创建的网络。
-
volumes
用于指定当前服务容器所使用到的所有 volume。这些 volume 可以使用路径与卷标两种方式。例如,下面是路径方式,非常直观,易于查看,但需要管理本地路径
db: image: mariadb:latest ports: - "3306:3306" volumes: - /etc/mysql:/var/lib/mysql
再如,下面是卷标方式,backend 与 backup 两个服务共享了 db-data 的卷,逻辑简洁明了,且无需管理本地路径。但具体卷标所代表的是 Docker 主机的哪个路径,并不能直观的看到。需要通过 docker volume inspect [卷标]来查看。
services: backend: image: awesome/database volumes: - db-data:/etc/data backup: image: backup-service volumes: - db-data:/var/lib/backup/data volumes: db-data:
9.3 常用命令
Docker Compose通过docker-compose系列命令查看和控制compose中的所有服务容器。
-
docker-compose pull
拉取 compose 中服务依赖的全部镜像或指定镜像。通过在命令后添加服务名称来指定。
-
docker-compose config
检查 compose 文件是否正确。可添加选项-q,表示只有存在问题时才有输出。
-
docker-compose up
启动 compose 中的所有容器。-d 选项表示后台启动。
-
docker-compose logs
查看 comopse 中所有服务或指定服务的运行日志。通过在命令后添加服务名称来指定。默认情况下,将对不同的服务日志使用不同的颜色来区分。
-
docker-compose ps
列出 compose 中所有服务或指定服务。通过在命令后添加服务名称来指定。
-
docker-compose top
列出 compose 中当前正在运行的所有服务或指定服务。通过在命令后添加服务名称来指定。
-
docker-compose images
列出 compose 中所有服务或指定服务对应的镜像。通过在命令后添加服务名称来指定。
-
docker-compose port
列出指定服务容器的指定端口所映射的宿主机端口
-
docker-compose run
在指定服务上执行一条命令
-
docker-compose exec
进入指定服务容器。通过在命令后添加服务名称来指定。
-
docker-compose pause
暂停 compose 中所有服务容器或指定服务容器。通过在命令后添加服务名称来指定
-
docker-compose unpause
恢复 compose 中处于暂停状态的所有服务容器或指定服务容器。通过在命令后添加服务名称来指定。
-
docker-compose stop
停止 compose 中所有服务容器或指定服务容器。通过在命令后添加服务名称来指定。
-
docker-compose restart
重启 compose 中所有服务容器或指定服务容器。通过在命令后添加服务名称来指定。
-
docker-compose start
启动 compose 中所有服务容器或指定服务容器。通过在命令后添加服务名称来指定
-
docker-compose kill
通过发送 SIGKILL 信号停止指定服务的容器。
-
docker-compose rm
删除 compose 中的、处于停止状态的所有服务容器或指定服务容器。通过在命令后添加服务名称来指定
-
docker-compose down
停止并删除 compose 中的所有服务容器、网络、镜像、数据卷。
9.4 安装
9.4.1 下载安装包
访问官网地址 https://docs.docker.com/compose/install/standalone/ ,下载文件到 /usr/local/bin/目录下并且更名为 docker-compse
curl -SL https://github.com/docker/compose/releases/download/v2.28.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
9.4.2 添加可执行权限
为 docker-compose 文件添加可执行权限。
chmod +x /usr/local/bin/docker-compose
9.4.3 测试
docker-compose version
9.5 项目构建
9.5.1 项目代码
-
功能描述
这是一个金融产品交易平台中的部分功能,是一个 Spring Boot 工程。控制器具有三个接口:查询所有产品、根据产品名称查询、上线新产品。整个架构包含一个 Redis 与 MySQL服务器
-
pom依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-batch</artifactId> <groupId>cn.git</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>docker-demo</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!--spring boot 与 redis 整合依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--mybatis 与 spring boot 整合依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- druid 驱动 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!--tomcat 内置的 JSP 解析器--> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!--jstl 依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <!-- compiler --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> <!-- package --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
实体类
package cn.git.docker.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @description: 产品 * @program: bank-credit-sy * @author: lixuchun * @create: 2024-07-15 */ @Data @NoArgsConstructor @AllArgsConstructor public class Product { /** * 主键 */ private Integer id; /** * 名称 */ private String name; /** * 利率 */ private Double rate; /** * 总额度 */ private Double amount; /** * 募集金额 */ private Double raised; /** * 周期 */ private Integer cycle; /** * 结束时间 */ private String endTime; }
-
sql 文件
以下是生成 product 表及相应数据的 SQL 文件内容。
CREATE DATABASE IF NOT EXISTS `test`; USE `test`; DROP TABLE IF EXISTS `product`; CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `rate` double DEFAULT NULL, `amount` double DEFAULT NULL, `raised` double DEFAULT NULL, `cycle` int(11) DEFAULT NULL, `end_Time` char(10) DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; INSERT INTO `product` VALUES(1,'TXTY',2.76,50000,20000,30,'2022-07-10'), (2,'GTTY',2.86,30000,30000,60,'2022-07-12'), (3,'GTGX',2.55,60000,50000,90,'2022-07-09'), (4,'GFMA',2.96,30000,20000,7,'2022-05-10'), (5,'TYXD',2.65,80000,60000,20,'2022-07-05'), (6,'HNSY',3.05,30000,20000,10,'2022-06-10'), (7,'HNSX',2.76,50000,30000,30,'2022-07-02'), (8,'LXSY',2.86,30000,20000,20,'2022-07-11');
-
productMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.git.docker.mapper.ProductMapper"> <insert id="insertProduct"> insert into product (name, raised, rate, cycle, amount, end_time) values (#{name}, #{raised}, #{rate}, #{cycle}, #{amount}, #{endTime}) </insert> <select id="selectAllProducts" resultType="cn.git.docker.entity.Product"> select * from product </select> <select id="selectProductListByName" resultType="cn.git.docker.entity.Product"> select * from product where name like '%' #{name} '%' </select> </mapper>
-
RedisUtil工具类
package cn.git.docker.util; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; @Component public class RedisUtil { private static RedisTemplate<String, Object> redisTemplate; @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { // 创建RedisTemplate<String, Object>对象 RedisTemplate<String, Object> template = new RedisTemplate<>(); // 配置连接工厂 template.setConnectionFactory(factory); // 定义Jackson2JsonRedisSerializer序列化对象 Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSeial.setObjectMapper(om); StringRedisSerializer stringSerial = new StringRedisSerializer(); // redis key 序列化方式使用stringSerial template.setKeySerializer(stringSerial); // redis value 序列化方式使用jackson template.setValueSerializer(jacksonSeial); // redis hash key 序列化方式使用stringSerial template.setHashKeySerializer(stringSerial); // redis hash value 序列化方式使用jackson template.setHashValueSerializer(jacksonSeial); template.afterPropertiesSet(); return template; } @Autowired public void RedisService(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } public void set(String key, Object value) { redisTemplate.opsForValue().set(key, value); } public Object get(String key) { return redisTemplate.opsForValue().get(key); } }
-
Controller 类
package cn.git.docker.controller; import cn.git.docker.entity.Product; import cn.git.docker.service.ProductService; import cn.git.docker.util.RedisUtil; import com.alibaba.fastjson.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * @description: 产品controller * @program: bank-credit-sy * @author: lixuchun * @create: 2024-07-15 */ @RestController @RequestMapping("/product") public class ProductController { @Autowired private RedisUtil redisUtil; @Autowired private ProductService productService; @GetMapping("/save") public String saveProduct() { Product product = new Product(); product.setAmount(1000.0); product.setCycle(3); product.setEndTime("2024-07-15"); product.setName("test"); product.setRaised(103.0); product.setRate(0.03); productService.saveProduct(product); redisUtil.set(product.getName(), JSONObject.toJSONString(product)); return JSONObject.toJSONString(product); } @GetMapping("/all") public String findAllProductList() { List<Product> allProductList = productService.findAllProductList(); return JSONObject.toJSONString(allProductList); } @GetMapping("/all/{name}") public String findProductByName(@PathVariable String name) { List<Product> productList = productService.findProductByName(name); return JSONObject.toJSONString(productList); } }
-
yaml配置文件
spring: application: name: docker-demo # 数据源配置 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.138.129:3306/test?useUnicode=true&characterEncoding=utf-8 username: root password: 101022 # redis配置 redis: host: 192.168.138.129 port: 6379 database: 0 # 缓存配置 cache: type: redis cache-names: pc # 日志配置 logging: file: name: "/var/applogs/finance.log" pattern: file: level-%-5level - %msg%n console: level-%-5level - %msg%n level: root: info cn.git.docker: debug # mybatis配置 mybatis: mapper-locations: classpath:/mappers/*Mapper.xml # 设置实体类的包路径,用于别名自动扫描 type-aliases-package: cn.git.docker.entity configuration: jdbc-type-for-null: 'null' log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: false server: port: 8088
9.5.2 定义 Dockerfile
在 Docker 主机的任意目录中新建一个目录,该新建目录名称与项目名称相同。这里在 /root 目录中 mkdir 一个名称为 finance(金融)的目录。然后在该目录中新建 Dockerfile 文件。
# 指定当前镜像继承自 openjdk:8u102,因为下面要运行 Jar 包,需要 JDK 环境
FROM openjdk:8u102
# 作者与项目相关说明
MAINTAINER zhangsan zs@163.com
LABEL version="1.0" description="docker compose test application"
# 将当前目录下的 jar 包 copy 到容器中。
# 注意,这个当前目录指的是 docker build 命令最后一个参数指定的上下文路径
COPY docker-demo-1.0-SNAPSHOT.jar docker-demo.jar
# 一定要执行的 exec
ENTRYPOINT ["java", "-jar", "docker-demo.jar"]
# 原服务指定对外暴露的端口号
EXPOSE 8088
注意,可以在 Dockerfile 中通过 VOLUME 指定数据卷
9.5.3 项目打包上传
9.6 手工启动项目
为了与使用 Docker Compose 编排项目进行对比,体现 Docker Compose 的方便快捷,这里先使用纯手工方式启动项目。
9.6.1 启动 mysql 容器
docker run --name mysql -e MYSQL_ROOT_PASSWORD=101022 -v /root/mysql/data:/var/lib/mysql -v /root/mysql/log:/var/log/mysql -v /root/mysql/conf:/etc/mysql/conf.d -dp 3306:3306 mysql:5.7
执行准备好的sql文件
9.6.2 启动redis容器
注意,这里可以直接执行下面的启动 redis 容器的命令的前提是,借用了之前在“常用服务器安装”章节中安装单机版 Redis 中创建的外挂文件/root/redis/redis.conf。
docker run --name myredis \
--network host \
-v /root/redis/redis.conf:/etc/redis/redis.conf \
-v /root/redis/data:/data \
-dp 6379:6379 redis:latest redis-server /etc/redis/redis.conf
9.6.3 启动应用容器
-
检查应用配置文件
在构建应用镜像之前,打开项目的配置文件,再次确认配置文件中指定的 mysql 与 redis主机的 IP 地址是否为 Docker 主机的 IP。注意,不是相应的 Docker 容器的 IP。如果不是,在修改后需要重新对项目进行 package 打包。然后再次将打包后的 jar 包上传到 Linux 系统的finance 项目目录中。
-
构建应用镜像
docker build -t finance:1.0 .
-
启动容器
日志持久化,配置文件中配置目录映射到服务器目录
docker run --name myapp \ -v /root/myapp/log:/var/applogs \ -dp 9000:8088 finance:1.0
9.6.4 访问应用
访问地址分别新增查询数据
http://192.168.138.129:9000/product/save
http://192.168.138.129:9000/product/all
http://192.168.138.129:9000/product/all/test
查看redis中数据信息
查看服务日志信息如下:
docker logs -f --tail=50 myapp
9.7 Compose 编排启动项目
手工启动项目不仅繁琐易错,而且还存在一个致命问题:当前应用访问的 mysql 与 redis只能是运行在当前 Docker 宿主机之上的,否则将无法访问到它们。因为在应用的配置文件中已经将 mysql 与 redis 的 IP 进行了硬编码。而使用 Docker Compose 可以解决所有问题
9.7.1 定义 compose.yml
在 finance 目录中新建一个文件 compose.yml。
services:
app:
build: ./
# 当你运行docker-compose up或docker-compose build时,Docker Compose将使用Dockerfile构建镜像,并将其标记为myapp:v1.0
# image字段在build字段存在的情况下,用于指定构建完成后镜像的名称和tag,而不是用来直接使用现有镜像。build字段不存在,image字段用于指定使用的镜像。
image: myapp:v1.0
container_name: myapp
ports:
- 9000:8088
volumes:
- ./logs:/var/applogs
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: 101022
ports:
- 3306:3306
volumes:
- /root/mysql/log:/var/log/mysql
- /root/mysql/data:/var/log/mysql
- /root/mysql/conf:/etc/mysql/conf.d
redis:
image: redis:latest
container_name: redis
# 主机模式,和宿主机使用相同网络
network_mode: host
ports:
- 6379:6379
vloumes:
- /root/redis/redis.conf:/etc/redis/redis.conf
- /root/redis/data:/data
command: redis-server /etc/redis/redis.conf
编写完成之后使用命令检查compose文件是否编辑正确,没有返回错误信息则编辑正确
docker-compose config -q
9.7.2 修改应用
-
修改配置文件
对于应用程序,需要修改其配置文件 application.yml。将 mysql 与 redis 的主机名修改为它们相应服务的名称,修改后重新上传文件
9.7.3 启动所有容器
docker-compose up -d
写入数据库测试数据信息
9.7.4 访问应用
与前面手工启动项目的访问方式相同。
http://192.168.138.129:9000/product/save
http://192.168.138.129:9000/product/all
http://192.168.138.129:9000/product/all/test
查看执行日志信息
docker logs -f --tail=50 myapp
9.7.6 指定各组件名称
前面的 compose.yml 文件中应用生成的镜像采用的是默认名称,mysql 与 redis 生成的容器名称采用的是默认名称,生成并加入的 bridge 网络也称默认名称的网络。这些组件的所使用的默认名称,也可以被指定名称所代替。