赞
踩
Docker-Compose是Docker官方的开源项目, 负责实现对Docker容器集群的快速编排。
Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml,写好多个容器之间的调用关系
。然后,只要一个命令,就能同时启动/关闭这些容器。
使用前面介绍的Dockerfile我们很容易定义一个单独的应用容器。然而在日常开发工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器;再比如在分布式应用一般包含若干个服务,每个服务一般都会部署多个实例。如果每个服务都要手动启停,那么效率之低、维护量之大可想而知。这时候就需要一个工具能够管理一组相关联的的应用容器,这就是Docker Compose。
Compose有2个重要的概念:
docker compose运行目录下的所有yml文件组成一个工程,一个工程包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖。一个服务可包括多个容器实例。
docker-compose就是docker容器的编排工具,主要就是解决相互有依赖关系的多个容器的管理。
docker建议我们每一个容器中只运行一个服务,因为docker容器本身占用资源极少,所以最好是将每个服务单独的分割开来但是这样我们又面临了一个问题?
如果我需要同时部署好多个服务,难道要每个服务单独写Dockerfile然后在构建镜像,构建容器,这样累都累死了,所以docker官方给我们提供了docker-compose多服务部署的工具。
例如要实现一个Web微服务项目,除了Web服务容器本身,往往还需要再加上后端的数据库mysql服务容器,redis服务器,注册中心eureka,甚至还包括负载均衡容器等等。。。。。。
Compose允许用户通过一个单独的docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。
官方文档地址:https://docs.docker.com/compose/compose-file/compose-file-v3/
官网下载:https://docs.docker.com/compose/install/
两种最新的docker安装方式
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
若是github访问太慢,可以用daocloud下载(不推荐使用)
curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
pip install docker-compose
rm /usr/local/bin/docker-compose
如果使用卸载pip 安装,则使用以下命令
pip uninstall docker-compose
docker-compose.yml
服务(service)
工程(project)
完整业务单元
·,在 docker-compose.yml
文件中定义。Dockerfile
定义各个微服务应用并构建出对应的镜像文件。docker-compose.yml
定义一个完整业务单元,安排好整体应用中的各个容器服务。docker-compose up
命令 来启动并运行整个应用程序,完成一键部署上线docker-compose -h #查看帮助
docker-compose up #启动所有docker-compose服务
docker-compose up -d #启动所有docker-compose服务并后台运行
docker-compose down # 停止并删除容器、网络、卷、镜像。
docker-compose exec yml里面的服务id # 进入容器实例内部 docker-compose exec docker-compose.yml文件中写的服务id /bin/bash
docker-compose ps #展示当前docker-compose编排过的运行的所有容器
docker-compose top #展示当前docker-compose编排过的容器进程
docker-compose logs yml里面的服务id #查看容器输出日志
docker-compose config # 检查配置
docker-compose config -q # 检查配置,有问题才有输出
docker-compose restart # 重启服务
docker-compose start # 启动服务
docker-compose stop # 停止服务
docker-compose pause 暂停服务容器。
docker-compose unpause 恢复服务容器。
【云原生 • Docker】Docker入门之中的Docker微服务实战
CREATE TABLE `t_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名',
`password` varchar(50) NOT NULL DEFAULT '' COMMENT '密码',
`sex` tinyint(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
`deleted` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表'
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>docker-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!---->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--SpringBoot mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--日志测试~-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.6.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!--引入监控功能-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 集成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>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server.port=6001
# ========================alibaba.druid相关配置=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///192.168.111.169:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false
# ========================redis相关配置=====================
spring.redis.database=0
spring.redis.host=192.168.111.169
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# ========================mybatis相关配置===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=dhx.entity
# ========================swagger=====================
spring.swagger2.enabled=true
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("dhx.mapper")
public class SpringBootDockerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDockerApplication.class, args);
}
}
/**
* @auther dhx
* @create 2023-6-27 17:19
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);// key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
/**
* @auther dhx
* @create 2023-6-27 17:19
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${spring.swagger2.enabled}")
private Boolean enabled;
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).groupName("学习DEMO").apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("dhx.controlle")).paths(PathSelectors.any())
.build();
}
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("docker-compose的学习Demo"+"\t"+new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
.description("docker-compose")
.version("1.0")
.termsOfServiceUrl("")
.build();
}
}
public class User implements Serializable {
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 性别 0=女 1=男
*/
private Byte sex;
/**
* 删除标志,默认0不删除,1删除
*/
private Byte deleted;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建时间
*/
private Date createTime;
}
@NoArgsConstructor
@AllArgsConstructor
@Data
@ApiModel(value = "用户信息")
public class UserDTO implements Serializable
{
@ApiModelProperty(value = "用户ID")
private Integer id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "性别 0=女 1=男 ")
private Byte sex;
@ApiModelProperty(value = "删除标志,默认0不删除,1删除")
private Byte deleted;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
@ApiModelProperty(value = "创建时间")
private Date createTime;
}
@Mapper
public interface UserMapper {
int deleteByPrimaryKey(Integer id);
int insert(User record);
User selectByPrimaryKey(Integer id);
List<User> selectAll();
int updateByPrimaryKey(User record);
}
<?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="dhx.mapper.UserMapper">
<resultMap id="BaseResultMap" type="dhx.entity.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="username" jdbcType="VARCHAR" property="username" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="sex" jdbcType="TINYINT" property="sex" />
<result column="deleted" jdbcType="TINYINT" property="deleted" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from t_user
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" parameterType="dhx.entity.User" useGeneratedKeys="true" keyProperty="id">
insert into t_user (id, username, password,
sex, deleted, update_time,
create_time)
values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},
#{sex,jdbcType=TINYINT}, #{deleted,jdbcType=TINYINT}, #{updateTime,jdbcType=TIMESTAMP},
#{createTime,jdbcType=TIMESTAMP})
</insert>
<update id="updateByPrimaryKey" parameterType="dhx.entity.User">
update t_user
set username = #{username,jdbcType=VARCHAR},
password = #{password,jdbcType=VARCHAR},
sex = #{sex,jdbcType=TINYINT},
deleted = #{deleted,jdbcType=TINYINT},
update_time = #{updateTime,jdbcType=TIMESTAMP},
create_time = #{createTime,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=INTEGER}
</update>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap" >
select id, username, password, sex, deleted, update_time, create_time
from t_user
where id = #{id,jdbcType=INTEGER}
</select>
<select id="selectAll" resultMap="BaseResultMap">
select id, username, password, sex, deleted, update_time, create_time
from t_user
</select>
</mapper>
@Service
@Slf4j
public class UserService {
public static final String CACHE_KEY_USER = "user:";
@Resource
private UserMapper userMapper;
@Resource
private RedisTemplate redisTemplate;
/**
* addUser
* @param user
*/
public void addUser(User user){
//1 先插入mysql并成功
int i =userMapper.insert(user);
if(i >0){
//2 需要再次查询一下mysql将数据捞回来并ok
user = userMapper.selectByPrimaryKey( user.getId());
//3 将捞出来的user存进redis,完成新增功能的数据一致性。
String key = CACHE_KEY_USER+user.getId();
System.out.println("key"+key);
redisTemplate.opsForValue().set(key,user);
}
}
/**
* findUserById
* @param id
* @return
*/
public User findUserById(Integer id) {
User user = null;
String key = CACHE_KEY_USER + id;
//1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql
user = (User) redisTemplate.opsForValue().get(key);
if (user == null) {
//2 redis里面无,继续查询mysql
user = userMapper.selectByPrimaryKey(id);
if (user == null) {
//3.1 redis+mysql 都无数据
//你具体细化,防止多次穿透,我们规定,记录下导致穿透的这个key回写redis
return user;
} else {
//3.2 mysql有,需要将数据写回redis,保证下一次的缓存命中率
redisTemplate.opsForValue().set(key, user);
}
}
return user;
}
}
@ApiModel(description = "用户User接口")
@RestController
@Slf4j
public class UserController {
@Resource
private UserService userService;
@ApiOperation("数据库新增3条记录")
@RequestMapping(value = "/user/add",method = RequestMethod.POST)
public void addUser(){
for (int i = 1; i <=3; i++) {
User user = new User();
user.setUsername("zzyy"+i);
user.setPassword(UUID.randomUUID().toString().substring(0,6));
user.setSex((byte) new Random().nextInt(2));
user.setDeleted((byte)0);
userService.addUser(user);
}
}
@ApiOperation("查询1条记录")
@RequestMapping(value = "/user/find/{id}",method = RequestMethod.GET)
public User findUserById(@PathVariable Integer id){
return userService.findUserById(id);
}
}
mvn package
命令将微服务形成新的jar
包 并上传到Linux
服务器/mydocker
目录下
# 基础镜像使用java
FROM java:8
# 作者
MAINTAINER zzyy
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzyy_docker.jar
ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar
# 运行jar包
RUN bash -c 'touch /zzyy_docker.jar'
ENTRYPOINT ["java","-jar","/zzyy_docker.jar"]
#暴露6001端口作为微服务
EXPOSE 6001
docker build -t zzyy_docker:1.6 .
新建mysql容器实例
docker run -p 3306:3306 --name mysql57 --privileged=true -v /zzyyuse/mysql/conf:/etc/mysql/conf.d -v /zzyyuse/mysql/logs:/logs -v /zzyyuse/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
docker exec -it mysql57 /bin/bash
mysql -uroot -p
create database db2021;
use db2021;
CREATE TABLE `t_user` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
`password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
`sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
`deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
docker run -p 6379:6379 --name redis608 --privileged=true -v /app/redis/redis.conf:/etc/redis/redis.conf -v /app/redis/data:/data -d redis:6.0.8 redis-server /etc/redis/redis.conf
docker run -d -p 6001:6001 zzyy_docker:1.6
上面三个容器实例依次顺序启动成功
http://localhost:你的微服务端口/swagger-ui.html#/
上面成功了,有哪些问题?
version: "3" # 描述 Compose 文件的版本信息
# 定义服务,可以多个
services:
microService: #服务名称
image: zzyy_docker:1.6 #使用哪个镜像
container_name: ms01 #相当于run的 --name
ports: #用来完成host与容器的端口映射关系
- "6001:6001" # 左边宿主机端口:右边容器端口
volumes: #完成宿主机与容器中目录数据卷共享
- /app/microService:/data #使用自定义路径映射
networks: # 配置容器连接的网络,引用顶级 networks 下的条目
- atguigu_net
depends_on: #依赖前面两个容器
- redis
- mysql
redis:
image: redis:6.0.8
ports:
- "6379:6379"
volumes:
- /app/redis/redis.conf:/etc/redis/redis.conf
- /app/redis/data:/data
networks:
- atguigu_net
command: redis-server /etc/redis/redis.conf
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: '123456'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
MYSQL_DATABASE: 'db2021'
MYSQL_USER: 'zzyy'
MYSQL_PASSWORD: 'zzyy123'
ports:
- "3310:3306"
volumes:
- /app/mysql/db:/var/lib/mysql
- /app/mysql/conf/my.cnf:/etc/my.cnf
- /app/mysql/init:/docker-entrypoint-initdb.d
networks:
- atguigu_net
command: --default-authentication-plugin=mysql_native_password #解决外部无法访问
# 定义网络,可以多个。如果不声明,默认会创建一个网络名称为"工程名称_default"的 bridge 网络
networks:
atguigu_net: # 一个具体网络的条目名称
#name: nginx-net # 网络名称,默认为"工程名称_网络条目名称"
#driver: bridge # 网络模式,默认为 bridge
server.port=6001
# ========================alibaba.druid相关配置=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.datasource.url=jdbc:mysql:///192.168.111.169:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.url=jdbc:mysql:///mysql:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false
# ========================redis相关配置=====================
spring.redis.database=0
#spring.redis.host=192.168.111.169
spring.redis.host=redis
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
# ========================mybatis相关配置===================
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=dhx.entity
# ========================swagger=====================
spring.swagger2.enabled=true
就用
docker-compose
里的服务名+容器内端口
访问,即:mysql:3306,其中3306必须是容器的端口,不能是主机的端口。
通过服务名访问,IP无关
mvn package
命令将微服务形成新的jar
包 并上传到Linux
服务器/mydocker
目录下
# 基础镜像使用java
FROM java:8
# 作者
MAINTAINER zzyy
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzyy_docker.jar
ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar
# 运行jar包
RUN bash -c 'touch /zzyy_docker.jar'
ENTRYPOINT ["java","-jar","/zzyy_docker.jar"]
#暴露6001端口作为微服务
EXPOSE 6001
docker build -t zzyy_docker:1.6 .
执行 docker-compose up 或者 执行 docker-compose up -d
docker exec -it mysql57 /bin/bash
mysql -uroot -p
create database db2021;
use db2021;
CREATE TABLE `t_user` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
`password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
`sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
`deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
测试通过
docker-compose stop
Docker Compose 允许用户通过 docker-compose.yml
文件(YAML 格式)来定义一组相关联的容器为一个工程(project
)。一个工程包含多个服务(service
),每个服务中定义了创建容器时所需的镜像、参数、依赖等。
工程名若无特殊指定,即为
docker-compose.yml
文件所在目录的名称。
Docker Compose 模板文件我们需要关注的顶级配置有 version
、services
、networks
、volumes
几个部分,除 version
外,其他几个顶级配置下还有很多下级配置,后面也会详细给大家介绍,先来看看这几个顶级配置都什么意思:
version
:描述 Compose 文件的版本信息,当前最新版本为 3.8
,对应的 Docker 版本为 19.03.0+
;services
:定义服务,可以多个,每个服务中定义了创建容器时所需的镜像、参数、依赖等;networkds
:定义网络,可以多个,根据 DNS server 让相同网络中的容器可以直接通过容器名称进行通信;volumes
:数据卷,用于实现目录挂载。描述 Compose 文件的版本信息,当前最新版本为 3.8
,对应的 Docker 版本为 19.03.0+
。关于每个版本的详细信息请参考:https://docs.docker.com/compose/compose-file/compose-versioning/
以下为 Compose 文件的版本信息所对应的 Docker 版本。
Compose file format | Docker Engine release |
---|---|
3.8 | 19.03.0+ |
3.7 | 18.06.0+ |
3.6 | 18.02.0+ |
刚才我们提到 docker-compose.yml
文件中包含很多下级配置项,下面带大家把一些常用的配置项详细了解一下,先从顶级配置 services
开始。
services
用来定义服务,可以多个,每个服务中定义了创建容器时所需的镜像、参数、依赖等,就像将命令行参数传递给 docker run
一样。同样,网络和数据卷的定义也是一样的。
比如,之前我们通过 docker run 命令构建一个 MySQL 应用容器的命令如下:
docker run \
-d \
--name mysql8 \
-p 3306:3306 \
-v /opt/data/docker_mysql/conf:/etc/mysql/conf.d \
-v /opt/data/docker_mysql/data:/var/lib/mysql
\-e MYSQL_ROOT_PASSWORD=root
\mysql:8
使用 docker-compose.yml
以后则可以这样定义:
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
mysql: # 服务名称
image: mysql:8 # 创建容器时所需的镜像
container_name: mysql8 # 容器名称,默认为"工程名称_服务条目名称_序号"
ports: # 宿主机与容器的端口映射关系
- "3306:3306" # 左边宿主机端口:右边容器端口
environment: # 创建容器时所需的环境变量
MYSQL_ROOT_PASSWORD: root
volumes:
- "/opt/data/docker_mysql/conf:/etc/mysql/conf.d"
- "/opt/data/docker_mysql/data:/var/lib/mysql"
然后通过 dokcer-compose
相关命令即可完成容器的创建,停止或删除等一系列操作。
指定创建容器时所需的镜像名称标签或者镜像 ID。如果镜像在本地不存在,会去远程拉取。
services:
web:
image: hello-world
除了可以基于指定的镜像构建容器,还可以基于 Dockerfile
文件构建,在使用 up
命令时会执行构建任务。
通过 build
配置项可以指定 Dockerfile
所在文件夹的路径。Compose 将会利用 Dockerfile
自动构建镜像,然后使用镜像启动服务容器。
build 配置项可以使用绝对路径,也可以使用相对路径。
# 绝对路径,在该路径下基于名称为 Dockerfile 的文件构建镜像
/usr/local/docker-centos
# 相对路径,相对当前 docker-compose.yml 文件所在目录,基于名称为 Dockerfile 的文件构建镜像
.
创建目录并编写 docker-compose.yml 文件。
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
mycentos: # 服务名称
build: . # 相对当前 docker-compose.yml 文件所在目录,基于名称为 Dockerfile-alternate 的文件构建镜像
container_name: mycentos7 # 容器名称,默认为"工程名称_服务条目名称_序号"
ports: # 宿主机与容器的端口映射关系
- "8080:8080" # 左边宿主机端口:右边容器端口
该选项可以是 Dockerfile 文件的绝对/相对路径,也可以是远程 Git 仓库的 URL,当提供的值是相对路径时,相对当前 docker-compose.yml 文件所在目录。
build:
context: . # 相对当前 docker-compose.yml 文件所在目录,基于名称为 Dockerfile 的文件构建镜像
一般情况下,默认都基于文件名叫 Dockerfile
的文件构建镜像,当然也可以是自定义的文件名,使用 dockerfile
声明,不过这个选项只能声明文件名,文件所在路径还是要通过 centext
来声明
build:
context: . # 相对当前 docker-compose.yml 文件所在目录
dockerfile: Dockerfile-alternate # 基于名称为 Dockerfile-alternate 的文件构建镜像
Compose 创建的容器默认生成的名称格式为:工程名称_服务条目名称_序号
。如果要使用自定义名称,使用 container_name
声明。
services:
mycentos:
container_name: mycentos7 # 容器名称,默认为"工程名称_服务条目名称_序号"
因为 Docker 容器名称必须是唯一的,所以如果指定了自定义名称,就不能将服务扩展至多个容器。这样做可能会导致错误。
序号是干什么用的呢,看下面这个例子你就懂了,docker-compose.yml
文件内容如下:
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
helloworld: # 服务名称
image: hello-world
然后通过 --scale
指定 helloworld
服务一次性启动 3 个。
docker-compose up -d --scale helloworld=3
通过下图可以看到有 3 个容器被创建,容器名称最后的序号是从 1 开始累加的,这就是序号的作用。所以如果指定了自定义名称,就不能将服务扩展至多个容器。
使用 Compose 最大的好处就是敲最少的命令做更多的事情,但一般项目容器启动的顺序是有要求的,如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。例如在没有启动数据库容器的情况下启动了 Web 应用容器,应用容器会因为找不到数据库而退出。depends_on
就是用来 解决容器依赖、启动先后问题的配置项
。以下先启动redis db
再启动web
version: "3.0"
services:
web:
build: .
depends_on: #但是web服务不会等待redis db完全启动之后再启动
- db (服务的ID名称)
- redis
redis:
image: redis
db:
image: mysql:5.6
上述 YAML 文件定义的容器会先启动 db 和 redis 两个服务,最后才启动 web 服务。
容器对外暴露的端口,格式:左边宿主机端口
:右边容器端口
。
ports:
- "3000"
- "8080:8080"
- "49100:22"
- "127.0.0.1:8080:8080"
容器暴露的端口不映射到宿主机,只允许能被连接的服务访问。
expose:
- "80"
- "8080"
容器重启策略,简单的理解就是 Docker 重启以后容器要不要一起启动:
no
:默认的重启策略,在任何情况下都不会重启容器;on-failure
:容器非正常退出时,比如退出状态为非0
(异常退出),才会重启容器;always
:容器总是重新启动,即使容器被手动停止了,当 Docker 重启时容器也还是会一起启动;unless-stopped
:容器总是重新启动,除非容器被停止(手动或其他方式),那么 Docker 重启时容器则不会启动。services:
nginx:
image: nginx
container_name: mynginx
ports:
- "80:80"
restart: always
设置环境变量 可以使用数组和字典两种格式,布尔相关的值(true、false、yes、no)都需要用引号括起来,以确保 YML 解析器不会将它们转换为真或假。
只给定名称的变量会自动获取运行compose
主机上对应的变量
environment:
RACK_ENV: development
SHOW: 'true'
SESSION_SECRET:
或者以下格式:
environment:
- RACK_ENV=development
- SHOW=true
- SESSION_SECRET
从文件中获取环境变量,可以指定一个或多个文件,可以指定一个或多个文件。
如果docker-compose -f FILE
方式来指定Compose
模板文件 则env_file
中变量的路径会基于模板文件路径
env_file: .env
env_file:
- /opt/runtime_opts.env # 绝对路径
- ./common.env # 相对路径,相对当前 docker-compose.yml 文件所在目录
- ./apps/web.env # 相对路径,相对当前 docker-compose.yml 文件所在目录
注意:env 文件中的每一行需采用
键=值
格式。以#
开头的行会被视为注释并被忽略。空行也会被忽略。
覆盖容器启动后默认执行的命令
command: echo "hello world"
该命令也可以是一个列表。
command: ["echo", "helloworld"]
数据卷,用于实现目录挂载,支持指定目录挂载、匿名挂载、具名挂载。并可以设置访问模式
左边宿主机目录:右边容器目录
,或者 左边宿主机目录:右边容器目录:读写权限
;容器目录即可
,或者 容器目录即可:读写权限
;数据卷条目名称:容器目录
,或者 数据卷条目名称:容器目录:读写权限
。# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
mysql: # 服务名称
volumes:
# 绝对路径
- "/opt/data/docker_mysql/data:/var/lib/mysql"
# 相对路径,相对当前 docker-compose.yml 文件所在目录
- "./conf:/etc/mysql/conf.d"
# 匿名挂载,匿名挂载只需要写容器目录即可,容器外对应的目录会在 /var/lib/docker/volume 中生成
- "/var/lib/mysql"
# 具名挂载,就是给数据卷起了个名字,容器外对应的目录会在 /var/lib/docker/volume 中生成
- "mysql-data-volume:/var/lib/mysql"
# 定义数据卷,可以多个
volumes:
mysql-data-volume: # 一个具体数据卷的条目名称
name: mysql-data-volume # 数据卷名称,默认为"工程名称_数据卷条目名称"
设置网络模式,类似 docker run
时添加的参数 --net host
或者 --network host
的用法。
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
配置容器连接的网络,引用顶级 networks
下的条目。
# 定义服务,可以多个
services:
nginx: # 服务名称
networks: # 配置容器连接的网络,引用顶级 networks 下的条目
- nginx-net # 一个具体网络的条目名称
# 定义网络,可以多个。如果不声明,默认会创建一个网络名称为"工程名称_default"的 bridge 网络
networks:
nginx-net: # 一个具体网络的条目名称
name: nginx-net # 网络名称,默认为"工程名称_网络条目名称"
driver: bridge # 网络模式,默认为 bridge
网络上此服务的别名。同一网络上的其他容器可以使用服务名或此别名连接到服务容器。同一服务在不同的网络上可以具有不同的别名。
# 定义服务,可以多个
services:
nginx: # 服务名称
networks: # 配置容器连接的网络,引用顶级 networks 下的条目
nginx-net: # 一个具体网络的条目名称
aliases: # 服务别名,可以多个
- nginx1 # 同一网络上的其他容器可以使用服务名或此别名连接到服务容器
# 定义网络,可以多个。如果不声明,默认会创建一个网络名称为"工程名称_default"的 bridge 网络
networks:
nginx-net: # 一个具体网络的条目名称
name: nginx-net # 网络名称,默认为"工程名称_网络条目名称"
driver: bridge # 网络模式,默认为 bridge
通过顶级配置 services
的学习,大家应该已经明白顶级配置 volumes
是干嘛的了,这里再详细把配置的不同方式讲解一下。
以下方式的数据卷声明创建卷时会使用默认的名称:"工程名称_数据卷条目名称"
。
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
mysql:
image: mysql:8
container_name: mysql8
ports:
- "3306:3306"
environment
MYSQL_ROOT_PASSWORD: root
volumes:
# 具名挂载,就是给数据卷起了个名字,容器外对应的目录会在 /var/lib/docker/volume 中生成
- "mysql-data-volume:/var/lib/mysql"
# 定义数据卷,可以多个
volumes:
mysql-data-volume: # 一个具体数据卷的条目名称
以下方式的数据卷声明创建卷时会使用自定义的名称。
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
mysql:
image: mysql:8
container_name: mysql8
ports:
- "3306:3306"
environment
MYSQL_ROOT_PASSWORD: root
volumes:
# 具名挂载,就是给数据卷起了个名字,容器外对应的目录会在 /var/lib/docker/volume 中生成
- "mysql-data-volume:/var/lib/mysql"
# 定义数据卷,可以多个
volumes:
mysql-data-volume: # 一个具体数据卷的条目名称
name: mysql-data-volume # 数据卷名称,默认为"工程名称_数据卷条目名称"
通过顶级配置 services
的讲解,大家其实已经明白顶级配置 networks
是干嘛的了,这里再详细把配置的不同方式讲解一下。
如果不声明网络,每个工程默认会创建一个网络名称为"工程名称_default"
的 bridge
网络。
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
nginx:
image: nginx
container_name: mynginx
ports:
- "80:80"
# 定义网络,可以多个。如果不声明,默认会创建一个网络名称为"工程名称_default"的 bridge 网络
#networks:
以下方式的网络声明创建网络时会使用默认的名称:"工程名称_网络条目名称"
,网络模式默认为 bridge
。
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
nginx:
image: nginx
container_name: mynginx
ports:
- "80:80"
networks: # 配置容器连接的网络,引用顶级 networks 下的条目
nginx-net:
# 定义网络,可以多个
networks:
nginx-net: # 一个具体网络的条目名称
以下方式的网络声明创建网络时会使用自定义的名称,还可以通过 driver
选择网络模式,默认为 bridge
。
# 描述 Compose 文件的版本信息
version: "3.8"
# 定义服务,可以多个
services:
nginx:
image: nginx
container_name: mynginx
ports:
- "80:80"
networks: # 配置容器连接的网络,引用顶级 networks 下的条目
nginx-net:
# 定义网络,可以多个
networks:
nginx-net: # 一个具体网络的条目名称
name: nginx-net # 网络名称,默认为"工程名称_网络条目名称"
driver: bridge # 网络模式,默认为 bridge
通过命令检查容器是否健康运行
healthcheck:
test: ["CMD","curl","-f","http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
配置容器的内核参数
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_syncookies=0
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
build 指令
作用:将指定的dockefile打包成指对应镜像,然后运行该镜像
services:
web:
build: 指定Dockerfile所在目录,先根据build中的dockerfile自动构成镜像,自动运行容器
context: demo ./demo /root/demo指定上下文目录 Dockerfile所在目录
dockerfile: Dockerfile
container_name: demo
ports:
- "8081:8081"
networks:
- hello
depends_on:
- tomcat01
'创建卷'
$ docker volume create tomcatwebapps01
'查看卷'
$ docker volume ls
'创建网桥'
$ docker network create bridge hello
'查看网桥'
$ docker network ls
'查看容器信息'
$ docker inspect 容器名
'如果要使用自定义卷名就要手动创建卷名'
$ docker volume create 卷名
'如果要使用自定义网桥就要手动创建桥'
$ docker network create -d bridge 桥名
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。