《重识云原生系列》专题索引:
第四章云网络4.9.1节——网络卸载加速技术综述
第四章云网络4.9.2节——传统网络卸载技术
第四章云网络4.9.3.1节——DPDK技术综述
第四章云网络4.9.3.2节——DPDK原理详解
第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述
第四章云网络4.9.4.2节——智能网卡实现
第六章容器6.1.1节——容器综述
第六章容器6.1.2节——容器安装部署
第六章容器6.1.3节——Docker常用命令
第六章容器6.1.4节——Docker核心技术LXC
第六章容器6.1.5节——Docker核心技术Namespace
第六章容器6.1.6节—— Docker核心技术Chroot
第六章容器6.1.7.1节——Docker核心技术cgroups综述
第六章容器6.1.7.2节——cgroups原理剖析
第六章容器6.1.7.3节——cgroups数据结构剖析
第六章容器6.1.7.4节——cgroups使用
第六章容器6.1.8节——Docker核心技术UnionFS
第六章容器6.1.9节——Docker镜像技术剖析
第六章容器6.1.10节——DockerFile解析
第六章容器6.1.11节——docker-compose容器编排
第六章容器6.1.12节——Docker网络模型设计
第六章容器6.2.1节——Kubernetes概述
第六章容器6.2.2节——K8S架构剖析
第六章容器6.3.1节——K8S核心组件总述
第六章容器6.3.2节——API Server组件
第六章容器6.3.3节——Kube-Scheduler使用篇
第六章容器6.3.4节——etcd组件
第六章容器6.3.5节——Controller Manager概述
第六章容器6.3.6节——kubelet组件
第六章容器6.3.7节——命令行工具kubectl
第六章容器6.3.8节——kube-proxy
第六章容器6.4.1节——K8S资源对象总览
第六章容器6.4.2.1节——pod详解
第六章容器6.4.2.2节——Pod使用(上)
第六章容器6.4.2.3节——Pod使用(下)
第六章容器6.4.3节——ReplicationController
第六章容器6.4.4节——ReplicaSet组件
第六章容器基础6.4.5.1节——Deployment概述
第六章容器基础6.4.5.2节——Deployment配置详细说明
第六章容器基础6.4.5.3节——Deployment实现原理解析
第六章容器基础6.4.6节——Daemonset
第六章容器基础6.4.7节——Job
第六章容器基础6.4.8节——CronJob
随着开发者对Docker了解的深入,使用其进行分布式部署变得复杂。开发者需要在开发,测试以及生产环境中的可移植应用,这些应用需要在不同的平台提供商之间迁移,比如在不同的云平台或者私有数据中心部署,同时,应用应该是可组合的,一个应用可以分解为多个服务。 Docker公司在2014年12月发布了三款用于解决多容器分布式软件可移植部署的问题。
Compose是用来编排和管理多容器应用的工具,使用它,你可以通过定义一个YAML文件来定义你的应用的所有服务,然后通过一条命令,你就可以创建并启动所有的服务。使用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 完成应用的部署。
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中再次指定它们。
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。
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
首先看一下Compose命令的格式:
Usage: docker-compose [-f=...] [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有如下几个,一一介绍:
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重新创建镜像,此时无需指定服务名称。
Usage:
kill [options] [SERVICE...]
Options:
-s SIGNAL SIGNAL to send to the container. Default signal is SIGKILL.
docker-compose kill命令用于通过向容器发送SIGKILL信号强行停止服务。
-s标识用于覆盖默认发送的信号。
Usage:
logs [options] [SERVICE...]
Options:
--no-color Produce monochrome output.
docker-compose logs命令用于展示service的日志。
--no-color标识使日志显示为单色
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
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。
Usage:
pull [options] [SERVICE...]
Options:
--ignore-pull-failures Pull what it can and ignores images with pull failures.
docker-compose pull用于;拉取服务依赖的镜像;
docker-compose restart用于重启某个服务的所有容器,后跟服务名。只有正在运行的服务才能重启,停止的服务不能使用restart命令。
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);
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等。仅管如此,还是有两点不一样:
$ 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指定容器的名字;
docker-compose scale指定某一个服务启动的容器的个数,其参数格式为[service=num],例如:
$ docker-compose scale web=2 worker=3
这条命令可以使某项服务启动多个容器,但当容器有到主机的端口映射时,因为所有容器都指向一个宿主机的端口,所以只能启动一个容器,其他的会失败。
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标识表示如果服务的容器已经存在,不要重新创建它们;
Docker-Compose 是 Docker 的独立产品,因此需要安装 Docker 之后再单独安装 Docker-Compose
[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 "(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 对二进制文件应用可执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 检查是否安装成功
docker-compose --version
我们新建一个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;
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操作(需要在工程目录下执行命令):
E:\myapp>docker-compose down
Stopping myapp ... done
Removing myapp ... done
Removing network myapp_default
E:\myapp>docker-compose ps
Name Command State Ports
-----------------------------------------------------------
myapp java -jar demo.jar Up 0.0.0.0:8080->8080/tcp
假设我们的应用需要依赖其它服务,比如需要使用redis,mysql等,那么这种场景下,就需要被依赖的容器先启动。
首先,我们改造上述例子中的myapp代码,需要引入redis的支持依赖:
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
然后增加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 redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate 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这个工程下面。
除了如上依赖容器的使用,日常开发中,我们都是使用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
我们在一开始讲解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博客