一. Eureka(服务注册与发现)
1. 概念
什么是Eureka?
Eureka是Netflix开源的服务注册与发现组件。
Eureka的架构?
Eureka是CS架构,分为Eureka Server和Eureka Client,Server端是服务注册中心的角色,Client可以向Server端注册自己的信息,或从Server端拉取其他服务列表。
Client周期性向Server端发送心跳,表明服务实例仍处于健康状态。
Eureka的工作原理?
- 微服务启动时,会向 Eureka Server 注册自己的信息(例如 IP 地址、端口号、服务名等)
- Eureka Server 会存储并维护这些注册信息
- 其他微服务通过 Eureka Server 可以获取到需要调用的服务的位置信息,从而进行服务间的通信
2. 单机Eureka搭建步骤
2.1 搭建Eureka Server
(1)创建一个Maven模块
(2)引入eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
(3)启动类上加@EnableEurekaServer
(4)编写application.yml配置文件,添加 服务名、地址、端口
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
(5)启动服务,并访问 http://127.0.0.1:10086 测试是否搭建成功
2.2 服务注册
(1)在user服务中引入eureka-client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)修改配置文件,添加服务名称和Eureka Server端地址
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2.3 服务发现
(1)在order服务中引入eureka-client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)修改配置文件,添加服务名称和Eureka Server端地址
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
(3)服务拉取和负载均衡
二. Zookeeper(服务注册与发现)
根据黑马文档安装Zookeeper
1. 初识Zookeeper
Zookeeper是Apache下一个分布式协调服务,用于解决分布式系统中的一致性和协作问题。
Zookeeper提供的功能主要有:
- 注册中心
- 配置中心
- 分布式锁
Zookeeper 数据模型
- Zookeeper是一个树形目录服务,每个节点都被称作ZNode,都可以保存自己的数据和节点信息
- 节点可以有子节点,同时也允许少量(1MB)数据存储在该节点下
- 节点可以分为四大类
2. Zookeeper 命令操作
2.1 服务端常用命令
2.2 客户端常用命令
3. Zookeeper JavaAPI操作
Curator是Apache Zookeeper的JavaAPI客户端库
官网:http://curator.apache.org/
3.1 建立连接和节点增删改查
package com.itheima.curator; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.api.BackgroundCallback; import org.apache.curator.framework.api.CuratorEvent; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.data.Stat; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.List; public class CuratorTest { private CuratorFramework client; /** * 建立连接 */ @Before public void testConnect() { /* * * @param connectString 连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181" * @param sessionTimeoutMs 会话超时时间 单位ms * @param connectionTimeoutMs 连接超时时间 单位ms * @param retryPolicy 重试策略 */ /* //重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10); //1.第一种方式 CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.149.135:2181", 60 * 1000, 15 * 1000, retryPolicy);*/ //重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10); //2.第二种方式 //CuratorFrameworkFactory.builder(); client = CuratorFrameworkFactory.builder() .connectString("192.168.149.135:2181") .sessionTimeoutMs(60 * 1000) .connectionTimeoutMs(15 * 1000) .retryPolicy(retryPolicy) .namespace("itheima") .build(); //开启连接 client.start(); } //==============================create============================================================================= /** * 创建节点:create 持久 临时 顺序 数据 * 1. 基本创建 :create().forPath("") * 2. 创建节点 带有数据:create().forPath("",data) * 3. 设置节点的类型:create().withMode().forPath("",data) * 4. 创建多级节点 /app1/p1 :create().creatingParentsIfNeeded().forPath("",data) */ @Test public void testCreate() throws Exception { //2. 创建节点 带有数据 //如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储 String path = client.create().forPath("/app2", "hehe".getBytes()); System.out.println(path); } @Test public void testCreate2() throws Exception { //1. 基本创建 //如果创建节点,没有指定数据,则默认将当前客户端的ip作为数据存储 String path = client.create().forPath("/app1"); System.out.println(path); } @Test public void testCreate3() throws Exception { //3. 设置节点的类型 //默认类型:持久化 String path = client.create().withMode(CreateMode.EPHEMERAL).forPath("/app3"); System.out.println(path); } @Test public void testCreate4() throws Exception { //4. 创建多级节点 /app1/p1 //creatingParentsIfNeeded():如果父节点不存在,则创建父节点 String path = client.create().creatingParentsIfNeeded().forPath("/app4/p1"); System.out.println(path); } //===========================get================================================================================ /** * 查询节点: * 1. 查询数据:get: getData().forPath() * 2. 查询子节点: ls: getChildren().forPath() * 3. 查询节点状态信息:ls -s:getData().storingStatIn(状态对象).forPath() */ @Test public void testGet1() throws Exception { //1. 查询数据:get byte[] data = client.getData().forPath("/app1"); System.out.println(new String(data)); } @Test public void testGet2() throws Exception { // 2. 查询子节点: ls List<String> path = client.getChildren().forPath("/"); System.out.println(path); } @Test public void testGet3() throws Exception { Stat status = new Stat(); System.out.println(status); //3. 查询节点状态信息:ls -s client.getData().storingStatIn(status).forPath("/app1"); System.out.println(status); } //===========================set================================================================================ /** * 修改数据 * 1. 基本修改数据:setData().forPath() * 2. 根据版本修改: setData().withVersion().forPath() * * version 是通过查询出来的。目的就是为了让其他客户端或者线程不干扰我。 * * @throws Exception */ @Test public void testSet() throws Exception { client.setData().forPath("/app1", "itcast".getBytes()); } @Test public void testSetForVersion() throws Exception { Stat status = new Stat(); //3. 查询节点状态信息:ls -s client.getData().storingStatIn(status).forPath("/app1"); int version = status.getVersion();//查询出来的 3 System.out.println(version); client.setData().withVersion(version).forPath("/app1", "hehe".getBytes()); } //===========================delete================================================================================ /** * 删除节点: delete deleteall * 1. 删除单个节点:delete().forPath("/app1"); * 2. 删除带有子节点的节点:delete().deletingChildrenIfNeeded().forPath("/app1"); * 3. 必须成功的删除:为了防止网络抖动。本质就是重试。 client.delete().guaranteed().forPath("/app2"); * 4. 回调:inBackground * @throws Exception */ @Test public void testDelete() throws Exception { // 1. 删除单个节点 client.delete().forPath("/app1"); } @Test public void testDelete2() throws Exception { //2. 删除带有子节点的节点 client.delete().deletingChildrenIfNeeded().forPath("/app4"); } @Test public void testDelete3() throws Exception { //3. 必须成功的删除 client.delete().guaranteed().forPath("/app2"); } @Test public void testDelete4() throws Exception { //4. 回调 client.delete().guaranteed().inBackground(new BackgroundCallback(){ @Override public void processResult(CuratorFramework client, CuratorEvent event) throws Exception { System.out.println("我被删除了~"); System.out.println(event); } }).forPath("/app1"); } @After public void close() { if (client != null) { client.close(); } } }
3.2 Watch事件监听
package com.itheima.curator; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.api.BackgroundCallback; import org.apache.curator.framework.api.CuratorEvent; import org.apache.curator.framework.recipes.cache.*; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.data.Stat; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.List; public class CuratorWatcherTest { private CuratorFramework client; /** * 建立连接 */ @Before public void testConnect() { /* * * @param connectString 连接字符串。zk server 地址和端口 "192.168.149.135:2181,192.168.149.136:2181" * @param sessionTimeoutMs 会话超时时间 单位ms * @param connectionTimeoutMs 连接超时时间 单位ms * @param retryPolicy 重试策略 */ /* //重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,10); //1.第一种方式 CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.149.135:2181", 60 * 1000, 15 * 1000, retryPolicy);*/ //重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10); //2.第二种方式 //CuratorFrameworkFactory.builder(); client = CuratorFrameworkFactory.builder() .connectString("192.168.149.135:2181") .sessionTimeoutMs(60 * 1000) .connectionTimeoutMs(15 * 1000) .retryPolicy(retryPolicy) .namespace("itheima") .build(); //开启连接 client.start(); } @After public void close() { if (client != null) { client.close(); } } /** * 演示 NodeCache:给指定一个节点注册监听器 */ @Test public void testNodeCache() throws Exception { //1. 创建NodeCache对象 final NodeCache nodeCache = new NodeCache(client,"/app1"); //2. 注册监听 nodeCache.getListenable().addListener(new NodeCacheListener() { @Override public void nodeChanged() throws Exception { System.out.println("节点变化了~"); //获取修改节点后的数据 byte[] data = nodeCache.getCurrentData().getData(); System.out.println(new String(data)); } }); //3. 开启监听.如果设置为true,则开启监听是,加载缓冲数据 nodeCache.start(true); while (true){ } } /** * 演示 PathChildrenCache:监听某个节点的所有子节点们 */ @Test public void testPathChildrenCache() throws Exception { //1.创建监听对象 PathChildrenCache pathChildrenCache = new PathChildrenCache(client,"/app2",true); //2. 绑定监听器 pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() { @Override public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { System.out.println("子节点变化了~"); System.out.println(event); //监听子节点的数据变更,并且拿到变更后的数据 //1.获取类型 PathChildrenCacheEvent.Type type = event.getType(); //2.判断类型是否是update if(type.equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){ System.out.println("数据变了!!!"); byte[] data = event.getData().getData(); System.out.println(new String(data)); } } }); //3. 开启 pathChildrenCache.start(); while (true){ } } /** * 演示 TreeCache:监听某个节点自己和所有子节点们 */ @Test public void testTreeCache() throws Exception { //1. 创建监听器 TreeCache treeCache = new TreeCache(client,"/app2"); //2. 注册监听 treeCache.getListenable().addListener(new TreeCacheListener() { @Override public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception { System.out.println("节点变化了"); System.out.println(event); } }); //3. 开启 treeCache.start(); while (true){ } } }
3.3 分布式锁实现
- “临时”是因为 如果是永久节点获取锁的机器宕机后,节点无法删除、锁无法释放;但是临时节点会话结束后会自动删除
- “顺序”是为了找最小节点,所以排了个顺序
火车票案例:
public class Ticket12306 implements Runnable{ private int tickets = 10;//数据库的票数 private InterProcessMutex lock ; public Ticket12306(){ //重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10); //2.第二种方式 //CuratorFrameworkFactory.builder(); CuratorFramework client = CuratorFrameworkFactory.builder() .connectString("192.168.149.135:2181") .sessionTimeoutMs(60 * 1000) .connectionTimeoutMs(15 * 1000) .retryPolicy(retryPolicy) .build(); //开启连接 client.start(); lock = new InterProcessMutex(client,"/lock"); } @Override public void run() { while(true){ //获取锁 try { lock.acquire(3, TimeUnit.SECONDS); if(tickets > 0){ System.out.println(Thread.currentThread()+":"+tickets); Thread.sleep(100); tickets--; } } catch (Exception e) { e.printStackTrace(); }finally { //释放锁 try { lock.release(); } catch (Exception e) { e.printStackTrace(); } } } } }
public class LockTest { public static void main(String[] args) { Ticket12306 ticket12306 = new Ticket12306(); //创建客户端 Thread t1 = new Thread(ticket12306,"携程"); Thread t2 = new Thread(ticket12306,"飞猪"); t1.start(); t2.start(); } }
4. Zookeeper 集群
集群搭建详见黑马文档
三. Feign(服务调用)
1. Feign代替RestTemplate
Feign是什么?
Feign是一个声明式的Http客户端,作用是 优雅的发送http请求。
(声明式指的是 以接口或注解的方式来定义对外部服务的访问)
Feign和OpenFeign的区别?
OpenFeign是对Feign的增强,使其能更好的支持SpringBoot和SpringCloud,比如SpringMvc、Ribbon、Hystrix。
2. Feign快速入门
(1)引入OpenFeign依赖
(2)在主启动类上添加 @EnableFeignClients注解
(3)编写Feign客户端
package cn.itcast.order.client; import cn.itcast.order.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; // 在order-service中新建一个接口,内容如下 @FeignClient("userservice") public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
(4)测试
3. Feign使用优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
- URLConnection:默认实现,不支持连接池
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
使用步骤:
(1)引入feign-httpClient依赖
(2)配置文件开启HttpClient功能,配置连接池参数
feign: httpclient: enabled: true # 开启feign对HttpClient的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数
四. Ribbon(负载均衡)
1. 负载均衡原理
@LoadBalance的作用是:标记通过RestTemplate发送的请求要被Ribbon处理
Ribbon底层原理总结(@LoadBalanced)
Ribbon的底层是使用了一个拦截器,拦截了所有RestTemplate发起的请求,使用请求中的服务名到注册中心拉取服务列表并存储,再通过负载均衡算法选取一台服务器,最后修改请求地址。
2. 负载均衡策略
负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类
五. Gateway(网关)
1. 基础概念
Zuul和Gateway的区别?
Zuul是Netflix开发的旧版网关服务,目前已经处于维护状态,不再更新新功能;
Gateway是Spring Cloud生态系统中的新一代API网关,在性能、灵活性和功能方面提供了更多的优势
为什么需要网关?
Gateway网关是我们服务的守门神,所有微服务的统一入口。
网关的核心功能特性:
请求路由
权限控制
限流
(主要是路由鉴权)
2. Gateway快速入门
(1)创建项目,引入nacos服务发现和gateway依赖
(2)配置application.yml,包括服务基本信息、nacos地址、路由
(3)测试
3. 断言工厂
快速入门中用的Path=/user/** 就是是Path断言工厂,是由PathRoutePredicateFactory 来处理的。类似的断言工厂还有十几个,我们只需要掌握path断言工厂就可以了。
4. 过滤器工厂
4.1 过滤器的作用
GatewayFilter的作用是:对路由的请求或响应做加工处理,比如 加请求头
4.2 过滤器的种类
请求头过滤器示例:
默认过滤器示例:
4.3 全局过滤器
全局过滤器的作用?
全局过滤器的作用也是处理一切进入网关的请求和微服务响应。GatewayFilter功能是固定的,全局过滤器可以自己写代码实现。
可以用全局过滤器实现:
登录状态判断
权限校验
请求限流等
全局过滤器使用方法:实现GlobalFilter接口
public interface GlobalFilter { /** * 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理 * * @param exchange 请求上下文,里面可以获取Request、Response等信息 * @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束 */ Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); }
全局过滤器示例:
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
参数中是否有authorization,
authorization参数值是否为admin
如果同时满足则放行,否则拦截
package cn.itcast.gateway.filters; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(-1) @Component public class AuthorizeFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取请求参数 MultiValueMap<String, String> params = exchange.getRequest().getQueryParams(); // 2.获取authorization参数 String auth = params.getFirst("authorization"); // 3.校验 if ("admin".equals(auth)) { // 放行 return chain.filter(exchange); } // 4.拦截 // 4.1.禁止访问,设置状态码 exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); // 4.2.结束处理 return exchange.getResponse().setComplete(); } }
六. Hystrix(服务容错)
七. XXL-JOB(分布式调度)
八. RabbitMQ(消息)
SpringCloud并没有绑定特定的消息中间件,但是它支持多种消息中间件,如RabbitMQ、Kafka。
SpringAMQP底层就包括对RabbitMQ的支持
1. 初识MQ
根据黑马文档安装RabbitMQ
RabbitMQ基本模型
RabbitMQ中的一些角色:
publisher:生产者
consumer:消费者
exchange个:交换机,负责消息路由
queue:队列,存储消息
virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离
RabbitMQ消息模型
- 基本消息队列(BasicQueue)
- 工作消息队列(WorkQueue)
- 发布订阅(Publish、Subscribe),又根据交换机类型分为三种:
Fanout Exchange:广播
Direct Exchange:路由
Topic Exchange:主题
2. SpringAMQP
SpringAMQP是基于RabbitMQ封装的
Topic模式使用
(1)引入AMQP依赖
<!--AMQP依赖,包含RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
(2)配置MQ地址
spring: rabbitmq: host: 192.168.150.101 # 主机名 port: 5672 # 端口 virtual-host: / # 虚拟主机 username: itcast # 用户名 password: 123321 # 密码
(3)消息发送
@RunWith(SpringRunner.class) @SpringBootTest public class SpringAmqpTest { @Autowired private RabbitTemplate rabbitTemplate; @Test public void testSimpleQueue() { // 交换机名称 String exchangeName = "itcast.topic"; // 消息 String message = "喜报!孙悟空大战哥斯拉,胜!"; // 发送消息 rabbitTemplate.convertAndSend(exchangeName, "china.news", message); } }
(4)消息接收
@Component public class SpringRabbitListener { @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue1"), exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC), key = "china.#" )) public void listenTopicQueue1(String msg){ System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue2"), exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC), key = "#.news" )) public void listenTopicQueue2(String msg){ System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】"); } }
九. Kafka(消息)
十. Docker(服务部署)
1. 初识Docker
(1)Docker是什么?
docker是一个容器化平台,可以创建、部署、和运行应用程序
(2)Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题?
Docker允许开发中将应用程序、依赖、函数库、配置一起打包,形成可移植镜像
Docker应用运行在容器中,使用沙箱机制,相互隔离
(3)Docker如何解决开发、测试、生产环境有差异的问题?
Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行
(4)docker和虚拟机的区别
- 虚拟机模拟了计算机,包含整个操作系统;docker是一个系统进程,仅仅封装了一些函数库
- 虚拟机体积大,启动速度慢,性能一般;docker体积小,启动快,性能好
(5)Docker架构
Docker采用了C/S架构,客户端和守护进程是关键组件
- 客户端(client)
通过命令行或API向docker服务端发送指令
客户端安装命令是:yum install -y docker-ce(ce指社区免费版本)详情参考文档
- 服务端(server)
Docker守护进程,负责处理Docker指令,管理镜像、容器
相关命令
systemctl start docker # 启动docker服务
systemctl stop docker # 停止docker服务
systemctl restart docker # 重启docker服务
(6)Docker核心组件
镜像(Image)
Docker把应用程序、函数库、依赖、配置等打包成一个模版,称为镜像
容器(Container)
镜像中的容器启动后形成的进程就是容器,只是docker会给容器做隔离,对外不可见
仓库(Registry)
用于存储Docker镜像
2. Docker的基础操作
2.1 镜像
名称
分成两部分 [repository]:[tag] tag代表镜像的版本
可以到DockerHub搜索镜像
命令
拉取 docker pull nginx
查看 docker images
保存 docker save
加载 docker load
删除 docker rmi
查看帮助 --help (如 docker save --help)
2.2 容器
命令
- docker run:创建并运行一个容器,处于运行状态
--name 指定容器名称
-p 指定端口
-d 后台运行
- docker pause:让一个运行的容器暂停
- docker unpause:让一个容器从暂停状态恢复运行
- docker stop:停止一个运行的容器
- docker start:让一个停止的容器再次运行
- docker rm:删除一个容器
- docker logs:查看日志
-f 持续查看日志
- docker ps:查看容器状态
-a 查看所有容器
2.3 数据卷
遇到的问题
容器和数据耦合在一起,导致想修改容器中的数据很麻烦、容器中的数据无法复用、升级容器中数据也跟着丢失了,为了解决这个问题,引入数据卷的概念,把容器和数据分隔开
什么是数据卷?
数据卷是一个虚拟目录,指向宿主机文件系统中的某个目录
操作命令
基础语法
docker volume [COMMAND]
创建数据卷
docker volume create xxx
查看所有数据卷
docker volume ls
查看某个数据卷详细信息
docker volume inspect xxx
删除指定数据卷
docker volume rm
挂载数据卷
创建容器的时候使用 -v参数挂载docker run \ --name mn \ -v html:/root/html \ -p 8080:80 nginx \ -v 代表把html这个数据卷 挂载到 /root/html 目录下 \ 代表换行 html这个数据卷可以不用事先创建,会默认创建
数据卷操作小结
docker run的命令中通过 -v 参数挂载文件或目录到容器中:
- -v volume名称:容器内目录
- -v 宿主机文件:容器内文件
- -v 宿主机目录:容器内目录
3. DockerFile自定义镜像
3.1 初识DockerFile
为什么需要自定义镜像?
常见的镜像在DockerHub就能找到,但是我们自己写的项目就必须自己构建镜像了
镜像的结构?
如何自定义镜像?
- 镜像就是在系统函数库、运行环境基础上,添加应用程序文件、配置文件、依赖文件等组合,然后编写好启动脚本打包在一起形成的文件。
- 我们要构建镜像,其实就是实现上述打包的过程
- 使用dockerfile来完成打包的过程
3.2 DockerFile操作
DockerFile是什么?
Dockerfile就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer
语法
构建java项目的镜像
- 基于Ubuntu构建
- 基于java8构建
3.3 DockerFile小结
- Dockerfile的本质是一个文件,通过指令描述镜像的构建过程
- Dockerfile的第一行必须是FROM,从一个基础镜像来构建
- 基础镜像可以是基本操作系统,如Ubuntu。也可以是其他人制作好的镜像,例如:java:8-alpine
4. Docker-Compose
4.1 是什么
docker-compose是一个定义和运行多容器docker应用程序的工具
4.2 安装
主要命令是:
curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
详见黑马docker文档
4.3 使用
(1)通过 docker-compose.yml 文件,可以指定各个服务之间的依赖关系、网络设置、挂载的卷等,从而更容易地定义、配置和管理整个应用的部署
(2)启动应用程序:docker-compose up
5. 镜像仓库
可以通过命令搭建私有镜像仓库,详见黑马docker文档
推送、拉取镜像