- 基础篇
- 使用镜像centos:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。
docker run -it centos /bin/bash - 使用镜像centos:latest以后台模式启动一个容器
- 16镜像分层的概念
- 17docker镜像commit操作案例
- 18本地镜像发布到阿里云(或公司私有仓库)
- 19docker私有库简介
- 20.私有仓库搭建和镜像推送
- 21.容器数据卷
- 22.容器卷的主要作用
- 23.容器卷的案例
- 24.容器卷ro和rw读写规则
- 25.容器卷之间的继承
- 26.Docker常规安装简介
- 高级篇
- 标识注释
- 10.Docker微服务实战
- 11.Docker network简介
- 12 Docker-compose容器编排
- 13.Portainer简介和安装
- 14.docker容器监控之CAdvisor+InfluxDB+Grandfana
Docker教程
基础篇
1.如何学会一门新技术
- 了解要学的这个东西是什么
- 了解这门技术能做什么
- 了解怎么样去下载这个东西
- 了解怎么使用这个东西
- 将这个东西跑起来,运行一次helloworld
2.为什么需要docker
假定您在开发一个尚硅谷的谷粒商城,您使用的是一台笔记本电脑而且您的开发环境具有特定的配置。其他开发人员身处的环境配置也各有不同。您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件。此外,您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。您希望尽可能多在本地模拟这些环境而不产生重新创建服务器环境的开销。请问?您要如何确保应用能够在这些环境中运行和通过质量检测?并且在部署过程中不出现令人头疼的版本、配置问题,也无需重新编写代码和进行故障修复? 答案就是使用容器。Docker之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案——-系统平滑移植,容器虚拟化技术。 环境配置相当麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。

之前在服务器配置一个应用的运行环境,要安装各种软件,就拿尚硅谷电商项目的环境来说,Java/RabbitMQ/MySQL/JDBC驱动包等。安装和配置这些东西有多麻烦就不说了,它还不能跨平台。假如我们是在 Windows 上安装的这些环境,到了 Linux 又得重新装。况且就算不跨操作系统,换另一台同样操作系统的服务器,要移植应用也是非常麻烦的。传统上认为,软件编码开发/测试结束后,所产出的成果即是程序或是能够编译执行的二进制字节码等(java为例)。而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,开发需要清楚的告诉运维部署团队,用的全部配置文件+所有软件环境。不过,即便如此,仍然常常发生部署失败的状况。Docker的出现使得Docker得以打破过去「程序即应用」的观念。透过镜像(images)将作业系统核心除外,运作应用程式所需要的系统环境,由下而上打包,达到应用程式跨平台间的无缝接轨运作。

docker包开发处的所有依赖和使用的东西,打包成一个镜像文件,给运维进行部署
3.Docker的理念
一次镜像,处处运行
Docker是基于Go语言实现的云开源项目。Docker的主要目标是“Build,Ship and Run Any App,Anywhere”,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP(可以是一个WEB应用或数据库应用等等)及其运行环境能够做到“一次镜像,处处运行”。Linux容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的。将应用打成镜像,通过镜像成为运行在Docker容器上面的实例,而 Docker容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。

就是每个docker引擎都运行一个 docker镜像,保证环境的一致性
解决了运行环境和配置问题的软件容器,
方便做持续集成并有助于整体发布的容器虚拟化技术。
3.容器与虚拟化的不同
VMware要虚拟硬件资源,以及要使用一个完整的操作系统,也就是这个容器自身就要占用很大的资源空间,他也确实能实现带环境安装,但是问题是,他需要同时携带很多不需要的资源,并且自身同样占据了很大的资源
- 资源占用多,甚至容器比要部署的程序占用的资源还大
- 冗余步骤多
- 启动慢
由于前面虚拟机存在某些缺点,Linux发展出了另一种虚拟化技术:
Linux容器(Linux Containers,缩写为 LXC)
Linux容器是与系统其他部分隔离开的一系列进程,从另一个镜像运行,并由该镜像提供支持进程所需的全部文件。容器提供的镜像包含了应用的所有依赖项,因而在从开发到测试再到生产的整个过程中,它都具有可移植性和一致性。
Linux 容器不是模拟一个完整的操作系统而是对进程进行隔离(虚拟机是完整的操作系统,不管用不用得上,比如用不上的打印机接口功能)。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。

Docker容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统虚拟机则是在硬件层面实现虚拟化。与传统的虚拟机相比,Docker优势体现为启动速度快、占用体积小。
因此有了一下的虚拟机和docker的对比图

Size是大小比较,Startup是启动速度比较,Integration是对比的复杂度
比较了 Docker 和传统虚拟化方式的不同之处:
- 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
- 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
- 每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。
4.Docker能做什么
一次构建、随处运行
- 更快速的应用交付和部署,传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
- 更便捷的升级和扩缩容,随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块“积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级。
- 更简单的系统运维,应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。
- 更高效的计算资源利用, Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的Hypervisor支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。
docker官网:http://www.docker.com Docker Hub官网: https://hub.docker.com/
5.安装docker前说明
CentOS Docker 安装前提条件目前,CentOS 仅发行版本中的内核支持 Docker。Docker 运行在CentOS 7 (64-bit)上,要求系统为64位、Linux系统内核版本为 3.8以上,这里选用Centos7.x 查看自己的内核uname命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)。
通过 cat /etc/redhat-release 查看linux的版本
通过 uname -r 查看linux的内核的版本
Docker基本组成的三大要素
- 镜像,就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。
它也相当于是一个root文件系统。比如官方镜像 centos:7 就包含了完整的一套 centos:7 最小系统的 root 文件系统。
相当于容器的“源代码”,docker镜像文件类似于Java的类模板,而docker容器实例类似于java中new出来的实例对象。

- 容器
1 从面向对象角度:
Docker 利用容器(Container)独立运行的一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建的运行实例。就像是Java中的类和实例对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台 。
2从镜像容器角度:
可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
比如一个redis实例,换算成java语法,相当于 Redis Redis= docker run Redis镜像 - 仓库, 仓库(Repository)是集中存放镜像文件的场所。
类似于:
Maven仓库,存放各种jar包的地方;
github仓库,存放各种git项目的地方;
Docker公司提供的官方registry被称为Docker Hub,存放各种镜像模板的地方。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub(https://hub.docker.com/),存放了数量庞大的镜像供用户下载。国内的公开仓库包括阿里云 、网易云等
总结:
需要正确的理解仓库/镜像/容器这几个概念:
Docker 本身是一个容器运行载体或称之为管理引擎。我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就是image镜像文件。只有通过这个镜像文件才能生成Docker容器实例(类似Java中new出来一个对象)。
image文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
镜像文件:
image 文件生成的容器实例,本身也是一个文件,称为镜像文件。
容器实例:
一个容器运行一种服务,当我们需要的时候,就可以通过docker客户端创建一个对应的运行实例,也就是我们的容器
仓库:
就是放一堆镜像的地方,我们可以把镜像发布到仓库中,需要的时候再从仓库中拉下来就可以了。
6.Docker平台的架构图(入门篇)

上图我们可以看到 client到DOCKER_HOST之间的线条是不一样的, 也就是ducker build 会调用DOCKER_HOST执行完命令完后,然后才会执行docker pull 以此类推
docker的工作原理
Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。容器,是一个运行时环境,就是我们前面说到的集装箱。

docker通过客户端,操作docker主机,通过客户端发送命令给docker主机进行控制
7.Docker平台的架构图(高级篇)
Docker是一个CIS模式的架构,后端是一个松耦合架构,众多模块各司其职。
Docker运行的基本流程为:
1.用户是使用Docker Client与 Docker Daemon建立通信,并发送请求给后者。
2.Docker Daemon作为Docker架构中的主体部分,首先提供Docker Server的功能使其可以接受Docke
Client的请求。
3 .Docker Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。
4.Job的运行过程中,当需要容器镜像时,则从Docker Registy中下载镜像,并通过镜像管理驱动Graph driver将下载镜像以Graph的形式存储。
5.当需要为Docker创建网络环境时,通过网络管理驱动Network driver创建并配置Docker容器网络环境。
6.当需要限制Docker容器运行资源或执行用户指令等操作时,则通过Exec driver来完成。
7.Libcontainer是一项独立的容器管理包,Network driver以及Exec driver都是通过Libcontainer来实现具体对容器进行的操作。

8.centos7上安装dockers
- 确保是centos7及以上的版本 - cat/etc/redhat-release
- 卸载旧版本

- yun 安装GCC相关配置
yum -y install gcc
yum -y install gcc-c++ - 安装需要的软件包

我们执行yum install -y yum-utils命令 - 设置stable镜像仓库(这里我们要用镜像)
如果我门直接用官网上的命令(yum-config-manager —add-repo https://download.docker.com/linux/centos/docker-ce.repo)因为这是一个国外地址,国内访问艰难困苦
典型的报错有:
1 [Errno 14] curl#35 - TCP connection reset by peer
2 [Errno 12] curl#35 - Timeout
因此我们就用国内的仓库镜像比如阿里云的:yum-config-manager —add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo - 更新yum软件的包索引 yum makecache fast 这样以后安装的时候也能快一些
- 安装DOCKER CE yum -y install docker-ce docker-ce-cli containerd.io 这里就是安装docker的容器和引擎,这里docker-ce应该就是引擎也就是docker主机(Engine) docker-ce-cli就是客户端,containerd.io就是容器
- 启动DOCKER systemctl start docker
- 测试 docker version ,docker pull hello-world, docker run hello-world
- 卸载DOCKER

9.阿里云镜像加速器
https://promotion.aliyun.com/ntms/act/kubernetes.html 这个是阿里云的k8s的产品官网页面,我们要上这上边获取自己的镜像加速器地址,这个地址我们以后推镜像的时候也会使用
操作步骤为:
- 登陆阿里云开发者平台

- 点击控制台

- 选择容器镜像服务

- 获取自己的加速器地址

我的镜像加速器地址为:https://1zz2ri80.mirror.aliyuncs.com
这里他要求的docker镜像版本最好大于 1.10
然后我们可以执行如下的命令:
sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-'EOF'{"registry-mirrors": ["https://1zz2ri80.mirror.aliyuncs.com"]}EOFsudo systemctl daemon-reloadsudo systemctl restart docker
10.docker run hello-world都做了什么
真的是如下的流程,docker run会自己去仓库找镜像!
11.为什么docker比虚拟机快-底层原理
1.docker有着比虚拟机更少的抽象层:
由于docker不需要Hypervisor(虚拟机)实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。
2.docker利用的是宿主机的内核,而不需要加载操作系统OS内核
当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。进而避免引寻、加载操作系统内核返回等比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载OS,返回新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个docker容器只需要几秒钟


12.帮助启动类命令
- 启动docker:systemctl start docker
- 停止docker:systemctl stop docker
- 重启docker:systemctl restart docker
- 查看docker状态:systemctl status docker
- 开机启动:systemctl enable docker
- 查看docker概要信息:docker info
- 查看docker总体帮助文档:docker —help
- 查看docker命令帮助文档:docker具体命令 —help 例如:docker ps —help
13.镜像命令
docker images 列出所有的镜像
-a :列出本地所有镜像
-q:只显示镜像id

各个选项的说明如下:
| REPOSITORY | 表示镜像的仓库源 |
|---|---|
| TAG | 镜像的标签版本号 |
| IMAGE ID | 镜像ID |
| CREATED | 镜像创建时间 |
| SIZE | 镜像大小 |
同一仓库源可以有多个 TAG版本,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。
如果你不指定一个镜像的版本标签,例如你只使用 ubuntu,docker 将默认使用 ubuntu:latest 镜像
docker search 某个xxx镜像的名字
例如 docker search mysql
他会从我们配置的阿里云镜像仓库中查找
他常见的相应参数和含义如下:

docker search 常用的参数:
limit:只列出N个镜像,默认25个
例如 docker search —limit 5 redis
我们一般会从search众多的结果中,选择官方的镜像来使用
docker pull 某个xxx镜像的名字
他会从仓库下载进行。
docker pull 镜像名:TAG
上边的TAG就是版本号
没有 TAG就是最新版本,它等价于 docker pull 镜像名:latest

docker system df 查看镜像/容器/数据卷所占的空间

total是一共有多少个,active是有多少个是激活的的,size标识占用的内存大小
RECLAIMABLE 标识没有被使用的镜像占用的空间
docker rmi 某个xxx镜像名字ID
这就是删除镜像的命令,
-f就是在由该镜像容器的情况也要强制删除
docker rmi -f 镜像ID :删除单个镜像
docker rmi -f 镜像ID1:tag 镜像ID2:TAG :删除多个镜像
docker rmi -f $(docker images -qa) 删除全部镜像
不过我们要注意,如果这个镜像已经启动了一个容器的话,那么想删除这个镜像有两种方式:
1就是 加上-f 也就是我们上边写的那种,
2.就是删除所有由该镜像创建的容器
14.什么是虚悬镜像
仓库名、标签都是的镜像,俗称虚悬镜像dangling image

这种镜像直接删除,狗屁用没有
15.容器命令
新建+启动容器
docker run [OPTIONS] IMAGE [COMMAND][ARG…]
OPTIONS说明:
option参数中 有些前面是一个减号,有些前面是两个减号
—name=”容器新名字” 为容器指定一个名称,如果不指定,就用系统随机分配的一个名字。例如运行 hello-world 他的容器名字叫:wizardly_elgamal
-i:以交互模式运行容器,通常与 -t 同时使用;
-t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;
也即启动交互式容器(前台有伪终端,等待交互);
如下是使用上述交互式容器的一个实例:

使用镜像centos:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。
docker run -it centos /bin/bash
参数说明:
-i: 交互式操作。
-t: 终端(tty)。
centos : centos 镜像。
/bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。
要退出终端,直接输入 exit:
上述的 /bin/bash 就是指定一个命令交互的接口,就想shell一样
-P: 随机端口映射,大写P,就是最外边的那个操作系统为我们随机分配端口
-p: 指定端口映射,小写p

列出现在所有正在运行的容器
docker ps [OPTIONS]
OPTIONS说明(常用):
-a :列出当前所有正在运行的容器+历史上运行过的
-l :显示最近创建的容器。
-n:显示最近n个创建的容器。
-q :静默模式,只显示容器编号。
退出容器
一共两有两种退出容器的方式
- exit: run进入容器,exit退出,容器停止
- ctrl+p+q: run进入容器,ctrl+p+q退出,容器不停止
启动已停止的容器
docker start 容器ID或者容器名
重启容器
docker restart 容器ID或者容器名
停止容器
docker stop 容器ID或者容器名
强制停止容器
docker kill 容器ID或者容器名
删除已停止的容器
docker rm 容器ID 。 这里注意 如果是docker rmi 标识删除的是镜像,差别在于这个i
可以一次性删除多个容器实例
注意 -f标识强制删除,那怕这个容器在启动中,这里用起来一定要小心,太危险了
- docker rm -f $(docker ps -a -q)
- docker ps -a -q | xargs docker rm
启动守护式容器(后台服务器)重要!
因为在大部分场景下,我们希望docker的服务是在后台运行的,我们可以通过-d指定容器的后台运行模式
docker run -d 容器名 注意和-it(交互式容器)的区别
使用镜像centos:latest以后台模式启动一个容器
docker run -d centos
问题:然后docker ps -a 进行查看, 会发现容器已经退出
很重要的要说明的一点: Docker容器后台运行,就必须有一个前台进程.
容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的。
这个是docker的机制问题,比如你的web容器,我们以nginx为例,正常情况下,
我们配置启动服务只需要启动响应的service即可。例如service nginx start
但是,这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,
这样的容器后台启动后,会立即自杀因为他觉得他没事可做了.
所以,最佳的解决方案是,将你要运行的程序以前台进程的形式运行,
常见就是命令行模式,表示我还有交互操作,别中断,O(∩_∩)O哈哈~
因此 比如像Ubuntu这种镜像,就不适合用-d这个参数,更适合用-it,而且如果我们不加 /bin/bash Ubuntu 也会默认使用一个命令行交互端口
但是例如redis这种,就可以用-d的方式
对于启动redis镜像来说:
- 前端交互式启动的命令为:docker run -it redis:6.0.8
- 后端守护式启动的命令为:docker run -d redis:6.0.8
查看容器的日志(log)
docker logs 容器ID
比如我们启动的redis容器的日志

查看容器内运行的进程
docker top 容器ID,可以看到这里只运行着一个redis后台进程

查看容器内部细节
docker inspect 容器ID
这个命令显示了容器很多的详细的信息,如下是一部分详细信息的截图

进入正在运行的容器,并以命令行交互
docker exec -it 容器ID bashShell 或者 docker attach 容器ID
这两个个命令 都可以进入正在运行的容器当中,如下图所示:


docker exec和 docker attach 的区别
- attach直接进入容器启动命令的终端,不会启动新的进程,用exit退出,会导致容器的终止。
- exec在容器中打开新的终端,并且可以启动新的进程,用exit退出不会导致容器终止
- attach好像是不能用在-d方式启动的后台容器,只能用在 -it启动的交互式容器上,因此像redis这种守护试容器不能用attach方式进入
因此建议使用exec,少用attach
如下是一个 进入redis容器后的一些简单操作

因此我们一般都是用id启动的程序,然后再用exec进入对应容器的实例
普通开发人员和云原生开发人员的区别

从容器内拷贝文件到主机上
docker cp 容器ID:容器内路径 目的主机路径
如下图所示:

导入和导出容器
- export导出容器的内容留作为一个tar归档文件[对应import命令]
- import从tar包中的内容创建一个新的文件系统再导入为镜像[对应export]
案例如下:
docker export 容器ID >文件名.tar
这个命令相当于把整个容器复制了一份,这个命令可以用在正在运行的容器上边
如下图所示:

cat 文件名.tar|docker import - 镜像用户/镜像名:镜像版本号
上述注意 -的前后都有空格
上述还要注意,他会生成一个镜像,而不是容器
注意上边的/不是或者的意思,就是两者都有

总结

| 命令 | 英文解释 | 中文解析 |
|---|---|---|
| attach | Attach to a running container | # 当前 shell 下 attach 连接指定运行镜像 |
| build | Build an image from a Dockerfile | # 通过 Dockerfile 定制镜像 |
| commit | Create a new image from a container changes | # 提交当前容器为新的镜像 |
| cp | Copy files/folders from the containers filesystem to the host path | #从容器中拷贝指定文件或者目录到宿主机中 |
| create | Create a new container | # 创建一个新的容器,同 run,但不启动容器 |
| diff | Inspect changes on a container’s filesystem | # 查看 docker 容器变化 |
| events | Get real time events from the server | # 从 docker 服务获取容器实时事件 |
| events | Run a command in an existing container | # 在已存在的容器上运行命令 |
| export | Stream the contents of a container as a tar archive | # 导出容器的内容流作为一个 tar 归档文件[对应 import ] |
| history | Show the history of an image | # 展示一个镜像形成历史 |
| images | List images | # 列出系统当前镜像 |
| import | Create a new filesystem image from the contents of a tarball | # 从tar包中的内容创建一个新的文件系统映像[对应export] |
| info | Display system-wide information | # 显示系统相关信息 |
| inspect | Return low-level information on a container | # 查看容器详细信息 |
| kill | Kill a running container | # kill 指定 docker 容器 |
| load | Load an image from a tar archive | # 从一个 tar 包中加载一个镜像[对应 save] |
| login | Register or Login to the docker registry server | # 注册或者登陆一个 docker 源服务器 |
| logout | Log out from a Docker registry server | # 从当前 Docker registry 退出 |
| logs | Fetch the logs of a container | # 输出当前容器日志信息 |
| port | Lookup the public-facing port which is NAT-ed to PRIVATE_PORT | # 查看映射端口对应的容器内部源端口 |
| pause | Pause all processes within a container | # 暂停容器 |
| ps | List containers | # 列出容器列表 |
| pull | Pull an image or a repository from the docker registry server | # 从docker镜像源服务器拉取指定镜像或者库镜像 |
| push | Push an image or a repository to the docker registry server | # 推送指定镜像或者库镜像至docker源服务器 |
| restart | Restart a running container | # 重启运行的容器 |
| rm | Remove one or more containers | # 移除一个或者多个容器 |
| rmi | Remove one or more images | # 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除] |
| run | Run a command in a new container | # 创建一个新的容器并运行一个命令 |
| save | Save an image to a tar archive | # 保存一个镜像为一个 tar 包[对应 load] |
| search | Search for an image on the Docker Hub | # 在 docker hub 中搜索镜像 |
| start | Start a stopped containers | # 启动容器 |
| stop | Stop a running containers | # 停止容器 |
| tag | Tag an image into a repository | # 给源中镜像打标签 |
| top | Lookup the running processes of a container | # 查看容器中运行的进程信息 |
| unpause | Unpause a paused container | # 取消暂停容器 |
| version | Show the docker version information | # 查看 docker 版本号 |
| wait | Block until a container stops, then print its exit code | # 截取容器停止时的退出状态值 |
16镜像分层的概念
镜像是什么
是一种轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等),这个打包好的运行环境就是image镜像文件。
只有通过这个镜像文件才能生成Docker容器实例(类似Java中new出来一个对象)。
分成的镜像
以我们的pull为例,在下载的过程中我们可以看到docker的镜像好像是在一层一层的在下载

看起来这个tomcat就像由好多层构成的一样
镜像的底层原理-UnionFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

就像上边的这个花卷一样,一层一层的构建成的
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
docker镜像的加载原理
首先之前我们说过,docker容器就是一个简易版的linux系统
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是引导文件系统bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

平时我们安装进虚拟机的CentOS都是好几个G,为什么docker这里才200M??

对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。比如这个微小版的linxu系统 就没有 vim命令
为什么采用分层结构
镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用。
比如说有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;
同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享
注意点
docker镜像层都是只读的,容器层是可写的
当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被叫做容器层,容器层之下的都叫镜像层
所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。

17docker镜像commit操作案例
原本的Ubuntu镜像是不包含vim命令的,因为为了精简,我们这次构造一个包含vim命令的Ubuntu镜像
docker commit 命令就是提交容器副本使之成为一个新的镜像
docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[标签名]
如下演示unbuntu容器安装vim
- 从Hub上下载Ubuntu镜像到本地并成功运行
- 原始的默认Ubuntu镜像是不带vim命令的
- 外网联通的情况下,安装vim

docker容器内执行上述两条命令:
apt-get update
apt-get -y install vim

- 安装完成后,commit我们自己的新镜像,蓝色圈圈的就是容器ID,这里的容器ID的作用就是指定我们要上传的是那个容器
注意这里并没有把镜像推到仓库,只是把这个容器变成了一个新的镜像放到了我们的本地
- 启动我们的新镜像和原镜像进行对比,我们的镜像可以使用vim命令

我们可以看到 添加了vim的镜像,变大了,相当于我们加了一层新的镜像层,这样我们可以直接继承含有vim的这个镜像了,以后在新加东西也可以在这个镜像上继续添加
总结:
Docker中的镜像分层,支持通过扩展现有镜像,创建新的镜像。类似Java继承于一个Base基础类,自己再按需扩展。
新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层(就像我们添加了一个vim,就增加了一层)

18本地镜像发布到阿里云(或公司私有仓库)
发布到阿里云的流程,其他仓库流程其实也一样

镜像生成一共有如下的两种方法
- 基于当前容器创建一个新的镜像,属于新功能增加(增加vim) 通过docker commit的方式
- 使用dockerFile的方式
整体发布流程
- 准备好我们要推送的镜像
- 登陆阿里云开发者平台:https://promotion.aliyun.com/ntms/act/kubernetes.html
- 创建镜像仓库
- 选择控制台,进入容器镜像仓库服务

- 选择个人实例

- 设置命名空间


- 设置仓库名称



- 进入管理界面获取脚本

- 选择控制台,进入容器镜像仓库服务
- 将镜像推送到阿里云registry,我们可以从管理界面复制脚本命令

然后执行圈起来的脚本命令
19docker私有库简介
1 官方Docker Hub地址:https://hub.docker.com/,中国大陆访问太慢了且准备被阿里云取代的趋势,不太主流。
2 Dockerhub、阿里云这样的公共镜像仓库可能不太方便,涉及机密的公司不可能提供镜像给公网,所以需要创建一个本地私人仓库供给团队使用,基于公司内部项目构建镜像。
Docker registry是官方提供的工具,可以用于构建私有镜像仓库
因此我们这次依赖于 这个 registry来搭建自己的私有仓库
20.私有仓库搭建和镜像推送
下载镜像Docker Registry

运行私有库registry,相当于本地有个私有Docker Hub
docker run -d -p 5000:5000 -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry
默认情况,仓库被创建在容器的/var/lib/registry目录下,建议自行用容器卷映射,方便于宿主机联调
案例演示创建一个新镜像,Ubuntu安装ifconfig命令
- 从阿里云镜像仓库下载Ubuntu镜像到本地并运行成功
- 原始的ubuntu镜像是不带ifconfig命令的
- 外网联通的情况下,安装ifconfig命令并测试通过
docker容器内执行上述两条命令:
apt-get update
apt-get install net-tools

- 安装完成后,commit我们自己的新镜像
公式:
docker commit -m=”提交的描述信息” -a=”作者” 容器ID 要创建的目标镜像名:[标签名]
命令:在容器外执行,记得
docker commit -m=”ifconfig cmd add” -a=”zzyy” a69d7c825c4f zzyyubuntu:1.2

- 启动我们的新镜像并与原来的镜像比较
- curl验证私有仓库上有什么镜像
- curl -XGET http://192.168.14.140:5000/v2/_catalog
可以看到,目前私服库没有任何镜像上传过。。。。。。
- 将新镜像zzyyubuntu:1.2修改符合私服规范的Tag
按照公式: docker tag 镜像:Tag Host:Port/Repository:Tag
Host是私服的域名/ip port是私服的端口号
自己host主机IP地址,填写同学你们自己的,不要粘贴错误,O(∩_∩)O
使用命令 docker tag 将zzyyubuntu:1.2 这个镜像修改为192.168.111.162:5000/zzyyubuntu:1.2
docker tag zzyyubuntu:1.2 192.168.111.162:5000/zzyyubuntu:1.2
- 修改配置文件使之支持http

- 别无脑照着复制,registry-mirrors 配置的是国内阿里提供的镜像加速地址,不用加速的话访问官网的会很慢。
2个配置中间有个逗号 ‘,’别漏了,这个配置是json格式的。
2个配置中间有个逗号 ‘,’别漏了,这个配置是json格式的。
2个配置中间有个逗号 ‘,’别漏了,这个配置是json格式的。
vim命令新增如下红色内容:vim /etc/docker/daemon.json
{
“registry-mirrors”: [“https://aa25jngu.mirror.aliyuncs.com“],
“insecure-registries”: [“192.168.111.162:5000”]
}
上述理由:docker默认不允许http方式推送镜像,通过配置选项来取消这个限制。====> 修改完后如果不生效,建议重启docker - push推送到私服
docker push 192.168.111.162:5000/zzyyubuntu:1.2
- 然后再次验证私服上有什么镜像
- pull到本地并运行
docker pull 192.168.111.162:5000/zzyyubuntu:1.
docker run -it 镜像ID /bin/bash
21.容器数据卷
什么是数据卷:
卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性:
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷
注意:注意加入一个 参数:—privilege=true
原因:Docker挂载主机目录访问如果出现cannot open directory .: Permission denied
解决办法:在挂载目录后多加一个—privileged=true参数即可
在linxu层面的原因:
如果是CentOS7安全模块会比之前系统版本加强,不安全的会先禁止,所以目录挂载的情况被默认为不安全的行为,
在SELinux里面挂载目录被禁止掉了额,如果要开启,我们一般使用—privileged=true命令,扩大容器的权限解决挂载目录没有权限的问题,也即
使用该参数,container内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限。
我们 docker run 的命令中 -v就是用来添加容器卷,例如:
docker run -d -p 5000:5000 -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry
其中/zzyyuse/myregistry/ 这里 就是标识宿主机的存储路径(这个目录如果没有,docker会自建)。后边的/tmp/registry就是标识容器内的路径 —privileged=true标识放开权限
这里就是实现了我们容器和宿主机数据的共享和互通互联
默认情况,仓库被创建在容器的/varliblregistry目录下,建议自行用容器卷映射,方便于宿主机联调,就是将docker容器内的数据保存进宿主机的磁盘中,达到数据的持久化和敏感数据的备份

因此 运行一个带有容器卷存储功能的容器实例的命令如下:
docker run -it —privileged=true -v/宿主机绝对路径目录:/容器内目录―镜像名
22.容器卷的主要作用
将运用与运行的环境打包镜像,run后形成容器实例运行 ,但是我们对数据的要求希望是持久化的
Docker容器产生的数据,如果不备份,那么当容器实例删除后,容器内的数据自然也就没有了。
为了能保存数据在docker中我们使用卷。
特点:
1:数据卷可在容器之间共享或重用数据,这句话的意思就是多个容器可以挂在同一个宿主主机目录,然后数据在这多个容器和宿主主机之间共享,就是容器A和容器B都挂在/tmp/test目录下,那么容器A和容器B以及宿主主机之间共享数据
2:卷中的更改可以直接实时生效,爽,就是容器中的数据的修改,可以立刻同步到宿主机对应的目录
3:数据卷中的更改不会包含在镜像的更新中
4:数据卷的生命周期一直持续到没有容器使用它为止
23.容器卷的案例
这里我们还是以 ubuntu命令为例
我们的公式为:公式:docker run -it -v /宿主机目录:/容器内目录 —privileged=true ubuntu /bin/bash
注意 我们上边的 -v 其实是可以挂载多个目录的
然后我们执行如下命令:
docker run -it —name myu3 —privileged=true -v /tmp/myHostData:/tmp/myDockerData ubuntu /bin/bash

然后我们查看数据卷是否挂在成功:
通过这个命令来看:docker inspect 容器ID

然后我们咋测试在容器和主机之间进行共享数据
1 docker修改,主机同步获得
2 主机修改,docker同步获得
3 docker容器stop,主机修改,docker容器重启看数据是否同步,答案,会成功 同步数据。

注意 哪怕容器是停止的,然后我们在容器挂载的宿主机的目录下修改或创建新数据,在容器启动后,同样会成功读取到这些数据
注意,启动容器的时候,不需要在输入一遍通过镜像启动容器的时候的那些命令,直接docker exec -it启动就可以了
24.容器卷ro和rw读写规则
首先,我们默认的写法,就是不带 ro或者rw的情况下,那么默认就是可读可写的
然后我们在运行容器的时候,可以指定容器内目录的权限,通过如下的语句
下边就是指定容器目录的权限为可读,可写
docker run -it --privileged=true -v/宿主机绝对路径目录:/容器内目录:rw 镜像名
下边就是指定容器的目录的权限为只读:
docker run -it —privileged=true -v /宿主机绝对路径目录:/容器内目录:ro 镜像名
就是容器内的目录只能读,不能写

/容器目录:ro 镜像名 就能完成功能,此时容器自己只能读取不能写
ro = read only
此时如果宿主机写入内容,可以同步给容器内,容器可以读取到。
25.容器卷之间的继承
首先我们先创建容器1和宿主机的映射
docker run -it —privileged=true -v /mydocker/u:/tmp —name u1 ubuntu


图中我们可以看到,我们在 /mydocker/u这个文件夹中创建了一个文件 u1_data.txt
然后我们容器2来继承容器1的卷规则:

docker run -it —privileged=true —volumes-from父类—name u2 ubuntu
这里边的 —volumes-from 父类就是继承卷规则,

我们上图中选择继承u1容器,然后我们进入u2容器,就可以发现,我们在u1容器中创建的 u1_data.txt就出现了
如果我们在u2中创建一个u2_data.txt ,我们同样也可以在u1中发现这个文件
其实这里 就是 多个容器挂载到了宿主机的同一个目录效果一样的,只不过在创建u2的时候 继承了u1容器中对应目录的数据。因此即使u1死了,对u2毫无影响,依然可以保持和主机的互通数据。而且此时假设被停止的u1恢复启动,那么在u1容器停止的这段时间,u2和宿主主机创建的内容依然可以被u1完全拿到,
26.Docker常规安装简介
1.总体过程
- 搜搜镜像
- 拉取镜像
- 查看镜像
- 启动镜像 - 配置端口
- 停止容器
- 移出容器
2.Tomcat安装
- 首先从docker hub上面查找Tomcat镜像 docker search tomcat

- 从 docker hub上拉取Tomcat镜像到本地 docker pull tomcat

- 通过docker images查看镜像是否拉取成功 docker images tomcat 只查询tomcat镜像是否存在
- 使用tomcat镜像创建容器实例(也叫运行镜像)
docker run -d -p 8080:8080 tomcat
- 访问猫首页
如果出现访问404的状况,可能是没有映射端口或者没有关闭防火墙
然后我们进入tomcat容器中,这里我们可以看到,我们进入容器后,就直接定位到了tomcat所在的目录
然后
我们可以看到,tomcat目录下的 webapp目录是空的,所以我们访问才是404
目前tomcat容器中,真真正正里边有初始化文件的是,webapps.dist这个目录,因此我们可以移除掉webapps目录,然后把webapps.dist目录重命名成webapps
这里再次注意,所有的容器都是一个精简版的linux操作系统
3.Mysql安装
首先也是像tomcat一样,先search再pull
注意如下的操作命令都是来自于docker官网的mysql页面下的

我们使用mysql容器的时候,一定要注意编码和容器卷的问题,首先docker的默认编码是latin(拉丁)因此中文肯定乱码,而且我们启动容器的时候,也要注意一定要指定好容器卷
mysql安装实战版:
- 新建mysql容器实例
docker run -d -p 3306:3306 --privileged=true-v /zzyyuse/mysql/log:/var/log/mysql-v /zzyyuse/mysql/data:/var/lib/mysql-v /zzyyuse/mysql/conf:/etc/mysql/conf.d-e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7

-v /zzyyuse/mysql/data:/var/lib/mysql 这句话就是保存的mysql的数据到宿主机上
我们还要在 /zzyyuse/mysql/conf中创建my.cnf的配置文件,来指定编码,当然也可以从这里边配置其他mysql需要的配置信息
[client]default_character_set=utf8[mysqld]collation_server = utf8_general_cicharacter_set_server = utf8
如果我们是先创建的容器,后创建的配置文件,此时我们就需要重启mysql容器实例
这样我们插入中文就不会有乱码的问题了
注意,此时我们要去mysql的容器中执行一下如下的命令:
链接mysql服务端的命令为 mysql -uroot -p
show variables like ‘character%’
查看编码是否都为utf8

此时我们在查看数据,也不是乱码的:

总结:docker安装完MySQL并run出容器后,建议请先修改完字符集编码后再新建mysql库-表-插数据
此时 就算我们这个mysql容器被删除了,我们之前创建的数据依旧存在了,因为我们挂载了容器卷
注意如果只是停止容器,那么数据不会丢失的,就算没有挂载容器卷
4.redis安装
首先 redis我们也要准备好配置文件,以及设置好容器卷
因为对于redis的使用来说,我们一定要准备好配置文件,因为我们后续一定会修改配置文件,所以不用他默认的配置文件
- 首先我们在linux主机下新建目录 /app/redis,然后将准备好的redis配置文件放入该目录下(redis.conf 可以在网上找这个文件)
- 然后我们运行镜像,执行如下命令
docker run -p 6379:6379 --name myr3 --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
- 注意我们在redis.conf配置文件中,最好做以下的配置
/app/redis目录下修改redis.conf文件
开启redis验证 可选
requirepass 123
允许redis外地连接 必须
注释掉 # bind 127.0.0.1
daemonize no
将daemonize yes注释起来或者 daemonize no设置,因为该配置和docker run中-d参数冲突,会导致容器一直启动失败
开启redis数据持久化 appendonly yes 可选 - 然后我们进入redis容器,链接redis服务端
docker exec -it 运行着Rediis服务的容器ID redis-cli
- 我们还要证明用的是我们的redis.conf的配置,这里我们把database的库的数量改成10,如果select 15就会被报错
修改前:我们用的配置文件,数据库默认为16个
修改后:select 15就会报错 因为我们改了配置文件中的database的数量,当然要记得重启服务
我们可以看到此时,select 15就报错了
高级篇
1安装mysql主从复制
- 新建主服务器容器实例3307
docker run -p 3307:3306 --name mysql-master \-v /mydata/mysql-master/log:/var/log/mysql \-v /mydata/mysql-master/data:/var/lib/mysql \-v /mydata/mysql-master/conf:/etc/mysql \-e MYSQL_ROOT_PASSWORD=root \-d mysql:5.7
- 进入/mydata/mysql-master/conf目录下新建my.cnf vim my.cnf
[mysqld]## 设置server_id,同一局域网中需要唯一server_id=101## 指定不需要同步的数据库名称binlog-ignore-db=mysql## 开启二进制日志功能log-bin=mall-mysql-bin## 设置二进制日志使用内存大小(事务)binlog_cache_size=1M## 设置使用的二进制日志格式(mixed,statement,row)binlog_format=mixed## 二进制日志过期清理时间。默认值为0,表示不自动清理。expire_logs_days=7## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致slave_skip_errors=1062
- 修改完后重新启动mysql实例容器
docker restart mysql-master - 然后我们要进入mysql容器,创建数据同步用户,这是因为我们不能来个人就能拿走我们mysql的数据, 这样是不安全的,所以创建这么一个账号
#进入容器,并且进入mysql客户端docker exec -it mysql /bin/bashmysql -uroot -proot#设置我们同步数据的安全账号CREATE USER 'slave'@'%'IDENTIFIED BY '123456'#这一步是授权允许做哪些操作GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'slave'@'%';
- 新建从服务器容器实例3308
docker run -p 3308:3306 --name mysql-slave \-v /mydata/mysql-slave/log:/var/log/mysql \-v /mydata/mysql-slave/data:/var/lib/mysql \-v /mydata/mysql-slave/conf:/etc/mysql \-e MYSQL_ROOT_PASSWORD=root \-d mysql:5.7
- 同样进入/mydata/mysql-slave/conf目录下新建my.cnf,编辑输入一下的内容
[mysqld]## 设置server_id,同一局域网中需要唯一server_id=102## 指定不需要同步的数据库名称binlog-ignore-db=mysql## 开启二进制日志功能,以备Slave作为其它数据库实例的Master时使用log-bin=mall-mysql-slave1-bin## 设置二进制日志使用内存大小(事务)binlog_cache_size=1M## 设置使用的二进制日志格式(mixed,statement,row)binlog_format=mixed## 二进制日志过期清理时间。默认值为0,表示不自动清理。expire_logs_days=7## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致slave_skip_errors=1062## relay_log配置中继日志relay_log=mall-mysql-relay-bin## log_slave_updates表示slave将复制事件写进自己的二进制日志log_slave_updates=1## slave设置为只读(具有super权限的用户除外)read_only=1
- 修改完后,重启mysql-salve实例
docker restart mysql-salve - 在主数据库中查看主从同步状态
show master status 这里要注意,如果这条语句返回的内容为空,证明没有成功开启mysql主从复制的,要查看配置是否正确,配置文件的位置是否放到了对的目录下 然后进入从数据库容器,配置主从复制,执行如下的命令:
change master to master_host='宿主机ip', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;上述的密码要注意,是slave这个账号的密码,不要误认为是root的#参数说明如下:master_host:主数据库的IP地址;master_port:主数据库的运行端口;master_user:在主数据库创建的用于同步数据的用户账号;master_password:在主数据库创建的用于同步数据的用户密码;master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;master_connect_retry:连接失败重试的时间间隔,单位为秒。

然后我们就可以在从数据库中查看,主从同步的状态了 show slave status \G; 后边的这个\G可以说是美化了输出

- 此时,主从同步配置好了,但是还没有正式启动,我们还需要在从数据库中启动主从配置 start slave

- 然后在查看从数据库状态,发现已同步

- 最后一步,就是我们对主从复制进行测试
主机新建库-使用库-新建表-插入数据 结果成功
从机使用数据库-查看记录 结果成功

注意,这要你建库了,那么久同步了,不用到插入或者修改数据哪一步
2分布式存储之哈希取余算法
question:1~2亿条数据需要缓存,请问如何设计这个存储案例
answer:单台肯定是不可能的,肯定是需要分布式存储,那么我们可以使用redis来实现
首先我们先看使用哈希取余算法来设置redis的集群

2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:
hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上
这个方案有什么优缺点呢:
优点:
简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:
原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。
3.分布式一致性哈希算法
一致性Hash算法背景:
一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决
分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了
为了解决哈希取余的问题,提出一致性Hash解决方案。
目的是当服务器个数发生变动时,
尽量减少影响客户端到服务器的映射关系
该算法实现的三大步骤:
1.算法构建一致性哈希环:
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。
它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对232取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-232-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到232-1,也就是说0点左侧的第一个点代表232-1, 0和232-1在零点中方向重合,我们把这个由232个点组成的圆环称为Hash环。

2.服务器IP节点映射
将集群中各个IP节点映射到环上的某一个位置。
将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:

3.key落在服务器的落键规则
当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。

一致性哈希算法的优缺点:
优点:
1.一致性哈希算法的容错性很强:
假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。

2.一致性哈希算法的扩展性很强:
扩展性
数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,
不会导致hash取余全部数据重新洗牌。

缺点:
Hash环的数据倾斜问题
一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,
例如系统中只有两台服务器:

但是这个缺点好像可以用虚拟节点的方式来解决
4.分布式存储之哈希槽分区算法
他是为了解决一致性哈性的数据倾斜问题
哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间。
它解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。
哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
哈希槽的计算:
Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果
对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上


问题:为什么哈希值用的是16384?
5.3主3从redis集群配置

- 关闭防火墙+启动docker后台服务 systemctl start docker
- 新建6个docker容器redis实例 ```shell docker run -d —name redis-node-1 —net host —privileged=true -v /data/redis/share/redis-node-1:/data redis:6.0.8 —cluster-enabled yes —appendonly yes —port 6381
docker run -d —name redis-node-2 —net host —privileged=true -v /data/redis/share/redis-node-2:/data redis:6.0.8 —cluster-enabled yes —appendonly yes —port 6382
docker run -d —name redis-node-3 —net host —privileged=true -v /data/redis/share/redis-node-3:/data redis:6.0.8 —cluster-enabled yes —appendonly yes —port 6383
docker run -d —name redis-node-4 —net host —privileged=true -v /data/redis/share/redis-node-4:/data redis:6.0.8 —cluster-enabled yes —appendonly yes —port 6384
docker run -d —name redis-node-5 —net host —privileged=true -v /data/redis/share/redis-node-5:/data redis:6.0.8 —cluster-enabled yes —appendonly yes —port 6385
docker run -d —name redis-node-6 —net host —privileged=true -v /data/redis/share/redis-node-6:/data redis:6.0.8 —cluster-enabled yes —appendonly yes —port 6386
<br />对上边命令的解释如下:<br /><br />我们直接复制上边的内容,全部复制,然后放到linux上执行,如果运行成功,效果就如同下图所示:<br />3.进入容器redis-node-1并为6台机器构建集群关系,首先我们要进入容器:docker exec -it redis-node-1 /bin/bash4.构建主从关系**_//注意,进入docker容器后才能执行一下命令,且注意自己的真实IP地址_**```shellredis-cli --cluster create 192.168.14.140:6381 192.168.14.140:6382 192.168.14.140:6383 192.168.14.140:6384 192.168.14.140:6385 192.168.14.140:6386 --cluster-replicas 1
—cluster-replicas 1 表示为每个master创建一个slave节点


上图中 M:标识就是主机,S:标识的从机
上述就表示,redis 三个一主一从的集群搭建成功了,采用的哈希槽的方式
然后我们进入6381这个redis机器,查看节点的状态


上述就是 master和slave机器的节点之间的对应关系。标识master的就标识这个机器是一个主节点,标识slave的就表示从节点,同事它里边也写了它是那个主节点的从节点,就是slave这个单词后边的那个id就是master节点的id
6.redis集群主从容错切换迁移案例
1.集群set键值对错误
首先,我们进入6381这个节点,连接上redis

然后我们执行 set k1 v1 这个命令:

我们发现报错了,当然换成别的注意,可能就不是k1了,而是叫另外一个k的名字了
这是因为,我们搭建的是集群版的redis 如果使用默认的 redis-cli -p 6381的命令的话,默认登陆的是单机版的,因此我们要加上一个参数 -c 才可以

此时 就发现没有问题了,注意 我们最后的按个ip和端口,已经跳转到了 6383端口对应的机器
2 检查集群状态
redis-cli —cluster check 192.168.111.147:6381 这里边的 —cluster check就是查看集群的信息

3.主从容错切换迁移
如下图所示:

我们查看6381和从机切换,首先我们先停止6381
然后我们可以发现,6381主机停了,它对应的从机就会上位,这里我们要注意,
6381作为1号主机分配的从机以实际情况为准,具体是几号机器就是几号
然后我们登陆个其他的master节点 查看集群的状态

我们发现 原本的6381后边多了一个fail标识,标识这个节点出故障了,然后原本的6381的从节点6385变成了master节点,这里证明我们容错自动切换成功了
然后我们再次启动6381端口这个节点,也就是一号节点,但是这个节点启动后并不会变成master节点,还会以slaver节点的身份来运行

此时如果我们想要恢复原样,需要进行如下操作,启动node1以后,停止他的从节点node5,然后再从新启动node5,这样node1就重新变成master节点了

中间需要等待一会儿,docker集群重新响应。
然后在查看集群的状态,就会发现,已经恢复原样了:
7.主从扩容

扩容之后的下过如下所示:

此时涉及到了我们集群新增机器以及对应的槽位的变更的问题
首先 我们新建6387和6388两个节点,然后启动这里两个节点,查看是否是8个节点
docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387docker run -d --name redis-node-8 --net host --privileged=true -v /data/redis/share/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388
然后 docker ps后 可以发现变成了8个 redis节点:

但是此时的新添加的这两个节点还没有正式阿计入到集群当中来,因此我们需要进入6387容器的内部,将这个新增的6387节点(空槽号,就是没有槽位,因为都没加入集群)作为master节点加入集群
redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:63816387 就是将要作为master新增节点6381 就是原来集群节点里面的领路人,相当于6387拜拜6381的码头从而找到组织加入集群


最后的蓝字标识我们加入成功了
此时我们检查节点的情况,会发现6387虽然加入集群成功了,但是并没有被分配槽位,也没有slave从机
redis-cli —cluster check 真实ip地址:6381


因此我们需要给新加入的这个节点分配槽位
命令:redis-cli —cluster reshard IP地址:端口号
redis-cli —cluster reshard 192.168.14.140:6381


注意上述第一个篮框 就是(from 1 to 16384)的 这里就是我们要给新加入的机器分配多少个槽,上述填写的4096 就是 16384/master台数,也就是我们把槽位均分了
第二个篮框要输入的id就是 我们6387这个redis对应的id
等重新分配完后,我们再一次查看集群的状况
redis-cli —cluster check 真实ip地址:6381


就能看到,已经分配成功了,但是我们能发现6387是三个槽位区间,而一开始的那三个master确实一个连续的槽位区间,这是因为:
重新分配成本太高,所以前3家各自匀出来一部分,从6381/6382/6383三个旧节点分别匀出1364个坑位给新节点6387
然后我们就可以为6387这个主节点分配好他的从节点6388
命令:redis-cli —cluster add-node ip:新slave端口 ip:新master端口 —cluster-slave —cluster-master-id 新主机节点ID
redis-cli --cluster add-node 192.168.111.147:6388 192.168.111.147:6387 --cluster-slave --cluster-master-id e4781f644d4a4e4d4b4d107157b9ba8144631451
———-后边的这个是6387的编号,按照自己实际情况


然后我们再次检查集群的状况,查看从节点是否成功的加入了


上边第一个图中我们可以发现,我们已经成功的加入了从节点了
8.主从缩容

这里我们的目的就是将6387和6388两个节点下线,上图中我们可以看到我们建议是先清除 从节点,然后在归还槽位
首先我们要获取6388这个从节点的id
redis-cli --cluster check 192.168.111.147:6388得到的id为:d981e539157acdc6d31f61ff2fdabe0e6a809ec9
使用这个命令来获取6388这个redis节点的id。

然后我们从集群中将4号从节点6388这个端口对应的节点删除
命令:redis-cli --cluster del-node ip:从机端口 从机6388节点IDredis-cli --cluster del-node 192.168.111.147:6388 5d149074b7e57b802287d1797a874ed7a1a284a8

然后我们在检查集群的几点状态。
redis-cli --cluster check 192.168.111.147:6388
检查一下发现,6388被删除了,只剩下7台机器了。


然后将6387的槽号清空,重新分配。
redis-cli —cluster reshard 192.168.111.147:6381
这里用6381做一个代表,其实是将槽位还给整个集群



上述我们将4号节点的4096个槽都分给了1号节点。其实,因为我们知道4号节点有好几个区间的槽位,我们可以计算槽位德的个数,然后分别将这些槽位还给原本从1,2,3号节点分过来的,比如有4096个,那么4096/3 就是1365 1366 1365 我们看到比如1号节点分了1365 我们还给1号节点的时候,就给填1365,依次类推
然后我们在查看依次集群的状态:
redis-cli —cluster check 192.168.14.140:6381
4096个槽位都指给6381,它变成了8192个槽位,相当于全部都给6381了,不然要输入3次,一锅端

然后我们就可以把6387这个节点删除了
命令:redis-cli --cluster del-node ip:端口 6387节点IDredis-cli --cluster del-node 192.168.111.147:6387 e4781f644d4a4e4d4b4d107157b9ba8144631451

然后我们在检测节点情况
redis-cli —cluster check 192.168.14.140:6381

9DockerFile
这是非常重要的一章
之前我们也给镜像添加过镜像层,但是那种方式很麻烦很不灵活,每次都要启动容器,在容器里边操作。
但是dockerFile,可以让我们通过命令和参数的方式,方便的定义我们的镜像需要哪些东西,比如我们可以在这个DockerFile文件里说明,需要vim,需要ifconfig,甚至说明需要tomcat,只需要写一个命令就好了
1.是什么
DockerFile是用来构建docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本

上图中我们可以看见,DockerFile文件 build后生成镜像
DockerFile文件对应的官网为:https://docs.docker.com/engine/reference/builder/
构建三部曲:
- 编写DockerFile文件
- docker build命令构建镜像
- docker run 镜像生成容器实例
2.DockerFile构建过程解析
DockerFile内容基础知识:
Docker执行DockerFile的大致流程:
- docker从基础镜像运行一个容器
- 执行一条指令并对容器做出修改
- 执行类似于docker commit 的操作提交一个新的镜像层
- docker在基于刚刚提交的镜像运行一个新容器
- 执行DockerFile中的下一条指令知道所有指令都执行完成
总结:
从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段,
- Dockerfile是软件的原材料
- Docker镜像是软件的交付品
- Docker容器则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例
Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。

1 Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;
2 Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时会真正开始提供服务;
3 Docker容器,容器是直接提供服务的。
3.DockerFile保留字介绍
FROM amazoncorretto:8ENV CATALINA_HOME /usr/local/tomcatENV PATH $CATALINA_HOME/bin:$PATHRUN mkdir -p "$CATALINA_HOME"WORKDIR $CATALINA_HOME# let "Tomcat Native" live somewhere isolatedENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-libENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR# see https://www.apache.org/dist/tomcat/tomcat-8/KEYS# see also "versions.sh" (https://github.com/docker-library/tomcat/blob/master/versions.sh)ENV GPG_KEYS 05AB33110949707C93A279E3D3EFE6B686867BA6 07E48665A34DCAFAE522E5E6266191C37C037D42 47309207D818FFD8DCD3F83F1931D684307A10A5 541FBE7D8F78B25E055DDEE13C370389288584E7 5C3C5F3E314C866292F359A8F3AD5C94A67F707E 765908099ACF92702C7D949BFA0C35EA8AA299F1 79F7026C690BAA50B92CD8B66A3AD3F4F22C4FED 9BA44C2621385CB966EBA586F72C284D731FABEE A27677289986DB50844682F8ACB77FC2E86E29AC A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243 F3A04C595DB5B6A5F1ECA43E3B7BBB100D811BBE F7DA48BB64BCB84ECBA7EE6935CD23C10D498E23ENV TOMCAT_MAJOR 8ENV TOMCAT_VERSION 8.5.73ENV TOMCAT_SHA512 4d33760d007acc5271ba0f946d2be96f6146d4632e411c9b72d351fc49d08355e8b84b051713a2f580e0f633cbe4409b7e9bb0e62f3af8d1094c6e4b3fdb96f0RUN set -eux; \\# http://yum.baseurl.org/wiki/YumDB.htmlif ! command -v yumdb > /dev/null; then \yum install -y yum-utils; \yumdb set reason dep yum-utils; \fi; \# a helper function to "yum install" things, but only if they aren't installed (and to set their "reason" to "dep" so "yum autoremove" can purge them for us)_yum_install_temporary() { ( set -eu +x; \local pkg todo=''; \for pkg; do \if ! rpm --query "$pkg" > /dev/null 2>&1; then \todo="$todo $pkg"; \fi; \done; \if [ -n "$todo" ]; then \set -x; \yum install -y $todo; \yumdb set reason dep $todo; \fi; \) }; \_yum_install_temporary gzip tar; \\ddist() { \local f="$1"; shift; \local distFile="$1"; shift; \local mvnFile="${1:-}"; \local success=; \local distUrl=; \for distUrl in \# https://issues.apache.org/jira/browse/INFRA-8753?focusedCommentId=14735394#comment-14735394"https://www.apache.org/dyn/closer.cgi?action=download&filename=$distFile" \# if the version is outdated (or we're grabbing the .asc file), we might have to pull from the dist/archive :/"https://downloads.apache.org/$distFile" \"https://www-us.apache.org/dist/$distFile" \"https://www.apache.org/dist/$distFile" \"https://archive.apache.org/dist/$distFile" \# if all else fails, let's try Maven (https://www.mail-archive.com/users@tomcat.apache.org/msg134940.html; https://mvnrepository.com/artifact/org.apache.tomcat/tomcat; https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/)${mvnFile:+"https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/$mvnFile"} \; do \if curl -fL -o "$f" "$distUrl" && [ -s "$f" ]; then \success=1; \break; \fi; \done; \[ -n "$success" ]; \}; \\ddist 'tomcat.tar.gz' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz"; \echo "$TOMCAT_SHA512 *tomcat.tar.gz" | sha512sum --strict --check -; \ddist 'tomcat.tar.gz.asc' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz.asc"; \export GNUPGHOME="$(mktemp -d)"; \for key in $GPG_KEYS; do \gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key"; \done; \gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; \tar -xf tomcat.tar.gz --strip-components=1; \rm bin/*.bat; \rm tomcat.tar.gz*; \command -v gpgconf && gpgconf --kill all || :; \rm -rf "$GNUPGHOME"; \\# https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html#Default_web_applicationsmv webapps webapps.dist; \mkdir webapps; \# we don't delete them completely because they're frankly a pain to get back for users who do want them, and they're generally tiny (~7MB)\nativeBuildDir="$(mktemp -d)"; \tar -xf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1; \_yum_install_temporary \apr-devel \gcc \make \openssl11-devel \; \( \export CATALINA_HOME="$PWD"; \cd "$nativeBuildDir/native"; \aprConfig="$(command -v apr-1-config)"; \./configure \--libdir="$TOMCAT_NATIVE_LIBDIR" \--prefix="$CATALINA_HOME" \--with-apr="$aprConfig" \--with-java-home="$JAVA_HOME" \--with-ssl=yes \; \nproc="$(nproc)"; \make -j "$nproc"; \make install; \); \rm -rf "$nativeBuildDir"; \rm bin/tomcat-native.tar.gz; \\# mark any explicit dependencies as manually installedfind "$TOMCAT_NATIVE_LIBDIR" -type f -executable -exec ldd '{}' ';' \| awk '/=>/ && $(NF-1) != "=>" { print $(NF-1) }' \| xargs -rt readlink -e \| sort -u \| xargs -rt rpm --query --whatprovides \| sort -u \| tee "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt" \| xargs -r yumdb set reason user \; \\# clean up anything added temporarily and not later marked as necessaryyum autoremove -y; \yum clean all; \rm -rf /var/cache/yum; \\# sh removes env vars it doesn't support (ones with periods)# https://github.com/docker-library/tomcat/issues/77find ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' +; \\# fix permissions (especially for running as non-root)# https://github.com/docker-library/tomcat/issues/35chmod -R +rX .; \chmod 777 logs temp work; \\# smoke testcatalina.sh version# verify Tomcat Native is working properlyRUN set -eux; \nativeLines="$(catalina.sh configtest 2>&1)"; \nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')"; \nativeLines="$(echo "$nativeLines" | sort -u)"; \if ! echo "$nativeLines" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' >&2; then \echo >&2 "$nativeLines"; \exit 1; \fiEXPOSE 8080CMD ["catalina.sh", "run"]
我们可以参考tomcat8的DockerFile如何编写的:
- FORM:基础镜像,当前新镜像是基于哪个镜像的,指定一个已存在的镜像作为基础,也就是这个镜像继承的父类,DockerFile文件的第一个指令必须是FROM
- MAINTAINER:镜像维护者的姓名和邮箱地址
- RUN:容器构建时需要运行的指令,有两种格式:
shell格式:
RUN yum -y install vim 就类似于这个命令
exec格式:
RUN是在docker build时运行 - EXPOSE:当前容器对外暴露出的端口
- WORKDIR:指定在创建容器后,终端默认登陆时候的进入的工作目录。
- USER:指定该镜像以什么样的用户去执行,如果都不指定,默认是ROOT,这里基本上不需要指定
- ENV:用来在镜像构建过程中设置环境变量 ```dockerfile ENV MY_PATH /usr/mytest 这个环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样; 也可以在其它指令中直接使用这些环境变量,
比如:WORKDIR $MY_PATH
- ADD:将宿主机目录下的文件拷贝进镜像且会自动处理uRL和解压tar压缩包- COPY: 类似ADD,拷贝文件和目录到镜像中。<br />将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置- VOLUME:容器数据卷,用于数据保存和持久化操作- CMD:Dockerfile中可以有多个CMD指令,**但只有最后一个生效,CMD会被docker run之后的参数替换**,<br /><br />我们可以用tomcat容器做个实验:tomcat的DockerFile文件的最后一行命令为:<br /><br />然后我们用命令行把他替代掉:<br /><br />这样tomcat就不会被启动了<br />他和RUN指令的区别:CMD是在docker run时运行的,RUN是在docker build时运行的- ENTRYPOINT:也是用来指定一个容器启动时要运行的命令,类似于CMD指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当作参数送给ENTRYPOINT指令指定的程序<br />命令格式为:<br /><br />ENTRYPOINT可以和CMD一起用,一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参。当指定了ENTRYPOINT后,CMD的含义就发生了变化,不再是直接运行其命令而是将CMD的内容作为参数传递给ENTRYPOINT指令,他两个组合会变成:<br /><br />案例如下:假设已通过 Dockerfile 构建了 nginx:test 镜像:<br /><br /><br />上图中后边的 传参运行 同样也会覆盖掉CMD文件,因此最终执行的就是new.conf这个命令行传参的文件了<br />优点:在执行docker run的时候可以指定ENTRYPOINT运行所需的参数。<br />缺点:如果Dockerfile中如果存在多个ENTRYPOINT指令,仅最后一个生效。<a name="cc37434e"></a>### 4.DockerFile案例_自定义镜像mycentosjava8_要求:Centos7镜像具备vim+ifconfig+jdk8jdk的下载地址:[https://www.oracle.com/java/technologies/downloads/#java8](https://www.oracle.com/java/technologies/downloads/#java8)准备编写DockerFile文件:```dockerfileFROM centosMAINTAINER zzyy<zzyybs@126.com>ENV MYPATH /usr/localWORKDIR $MYPATH#安装vim编辑器RUN yum -y install vim#安装ifconfig命令查看网络IPRUN yum -y install net-tools#安装java8及lib库RUN yum -y install glibc.i686RUN mkdir /usr/local/java#ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置ADD jdk-8u171-linux-x64.tar.gz /usr/local/java/#配置java环境变量ENV JAVA_HOME /usr/local/java/jdk1.8.0_171ENV JRE_HOME $JAVA_HOME/jreENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATHENV PATH $JAVA_HOME/bin:$PATHEXPOSE 80CMD echo $MYPATHCMD echo "success--------------ok"CMD /bin/bash

整体结构如上图所示↑
然后我们使用 docker build -t 新镜像名字:TAG . 一定要注意后边有一个点!!!
docker build -t centosjava8:1.5 .

然后运行我们刚刚的镜像
docker run -it centosjava8:1.5 /bin/bash

这就是联合文件系统的力量
docker build 中最后的. 的作用
当我们使用 docker build 命令来构建镜像时,这个构建过程其实是在 Docker引擎 中完成的,而不是在本机环境。
那么如果在 Dockerfile 中使用了一些 COPY 等指令来操作文件,如何让 Docker引擎 获取到这些文件呢?
这里就有了一个 镜像构建上下文 的概念,当构建的时候,由用户指定构建镜像的上下文路径,而 docker build 会将这个路径下所有的文件都打包上传给 Docker 引擎,引擎内将这些内容展开后,就能获取到所有指定上下文中的文件了。
比如说 dockerfile 中的 COPY ./package.json /project,其实拷贝的并不是本机目录下的 package.json 文件,而是 docker引擎 中展开的构建上下文中的文件,所以如果拷贝的文件超出了构建上下文的范围,Docker引擎 是找不到那些文件的。
所以 docker build 最后的 . 号,其实是在指定镜像构建过程中的上下文环境的目录。
docker build中 .dockerignore的作用
当我们在
docker build 的过程中,首先会将指定的上下文目录打包传递给 docker引擎,而这个上下文目录中可能并不是所有的文件我们都会在 Dockerfile 中使用到,那么这个时候就可以在 .dockerignore 文件中指定在传递给 docker引擎 时需要忽略掉的文件或文件夹。
5.虚悬镜像
虚悬镜像的仓库名,标签名都是,它俗称dangline image。我们可以用如下的命令用Dockerfile写一个:
1 vim Dockerfile
from ubuntuCMD echo 'action is success'
2 docker build .
我们可以用 docker images ls -f dangling =true这个命令来查看

他可能会对系统存在一些隐含风险,因此发现了及时把他删掉
我们可以用docker image prune 这个命令把虚悬镜像删除掉,因为虚悬镜像已经失去存在价值,可以删除

10.Docker微服务实战
我们打包一个docker_boot-0.0.1-SNAPSHOT.jar的spring-boot的包

然后编写Dockerfile文件
# 基础镜像使用java 它内部自己继承了linuxFROM java:8# 作者MAINTAINER zzyy# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmpVOLUME /tmp# 将jar包添加到容器中并更名为zzyy_docker.jarADD 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
然后将微服务jar包和Dockerfile文件上传到同一个目录下/mydocker

然后我们就可以开始构建镜像了:
docker build -t zzyy_docker:1.6 .
然后我们就可以运行容器进行测试了。

最后我们在访问测试:

11.Docker network简介
首先,在我们docker没有启动的情况下,默认的网络情况如下图所示:

关于virbr0这个网卡:
在CentOS7的安装过程中如果有选择相关虚拟化的的服务安装系统后,启动网卡时会发现有一个以网桥连接的私网地址的virbr0网卡(virbr0网卡:它还有一个固定的默认IP地址192.168.122.1),是做虚拟机网桥的使用的,其作用是为连接其上的虚机网卡提供 NAT访问外网的功能。
我们之前学习Linux安装,勾选安装系统的时候附带了libvirt服务才会生成的一个东西,如果不需要可以直接将libvirtd服务卸载,
yum remove libvirt-libs.x86_64
在docker启动后,我们的网络情况如下所示:
会产生一个名为docker0的虚拟网桥
我们的容器和容器,以及容器和宿主机之间就是通过docker0这个网桥进行通信

当我们创建了docker后,可以通过如下的命令查看docker网络模式:
docker默认创建3大网络模式:

上图中,主要用的是bridge,偶尔用host
1.docker network 常用命令
我们可以直接查看 docker network都有哪些命令:用 docker network —help的方式:
docker network ls 查看有哪些网络

docker network inspect xxx网络名字:查看网络源数据
docker network inspect bridge:
他会打印出来一个json串格式的内容:


我们从上边打印的也可以看到,默认的网桥名字就叫docker0
docker network rm xxx网络名字
如下是一个案例,先创建网络,在展示网络,在删除网络:

2.docker network能做什么
主要有如下两个目的:
容器间的互联和端口映射依赖于它
容器IP变动的时候可以通过服务名直接网络通信而不收到影响,比如应用在a主机的鲸鱼1 mysql在b主机的鲸鱼2上,应用需要依赖这个mysql,就可以通过服务名直接访问
3.网络模式

| bridge模式: | 使用—network bridge指定,默认使用docker0 |
| host模式 | 使用—network host指定 |
| none模式 | 使用—network none指定 |
| container模式: | 使用—network container:NAME或者容器ID指定 |
4.容器实例内默认网络IP生产规则
通过这个例子来说明,为什么我们要学习网络这一块的知识
1 先启动两个ubuntu容器实例

2 docker inspect 容器ID or 容器名字,如下显示的是我们这个容器的ip地址,也可以看到默认的网络就是桥接网络

3 关闭u2实例,新建u3,查看ip变化,可以发现原本属于u2的ip,现在已经给了u3了, 就有可能导致某些实例会调不通

结论:
docker容器内部的ip是有可能会发生改变的
因此我们ip地址发生变化了,调用不能出问题,而且想要相互调用的容器,必须要处于同一个网段,或说是使用 同一个网络模式中的网络 比如bridge,如果一个用bridge一个用我们新建的bb_bridge 那么他们两个就不能通信
当我们创建一个新的网桥后,也会发现,它的ip地址发生了变化

这是默认的网桥

5.docker network 之 bridge
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。
查看 bridge 网络的详细信息,并通过 grep 获取名称项 就是获取网桥的名字
docker network inspect bridge | grep name


详细说明:
1 Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
2 docker run 的时候,没有指定network的话默认使用的网桥模式就是bridge,使用的就是docker0。在宿主机ifconfig,就可以看到docker0和自己create的network(后面讲)eth0,eth1,eth2……代表网卡一,网卡二,网卡三……,lo代表127.0.0.1,即localhost,inet addr用来表示网卡的IP地址
3 网桥docker0创建一对对等虚拟设备接口一个叫veth,另一个叫eth0,成对匹配。
3.1 整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);
3.2 每个容器实例内部也有一块网卡,每个接口叫eth0;
3.3 docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,一一匹配。
通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的ip,此时两个容器的网络是互通的。

从上边这个图我们可以看出来 docker0就好像一个交换机一样,并且每个docker容器都和docker0这个网桥进行了联通(eth0-veth)
因此我们容器之间和容器与主机之间的网络相互访问,只能通过docker0这个交换机
如下有个例子,我们启动了两个tomcat容器,没有指定docker net-work 那么默认就是 bridge:
docker run -d -p 8081:8080 —name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080 —name tomcat81 billygoo/tomcat8-jdk8
然后我们在宿主机上查看,可以看到多了两个网卡 分别是 注意这里我们用的命令式ip addr
28: veth14fc73a@if27 前面的veth 就是我们docker0对外提供的网络接口
然后这个接口对应的是 tomcat81上的网络接口,我门可以进入tomcat81查看一下这个网卡的名称,就如下图左边红框标出来的所示,我们可以看到这样一个网卡
27:eth0@if28 前面的eth0就是我们docker容器创建的时候默认提供的网络接口,它和docker0中的veth相对应, 同时我们结合上边宿主机上多出来个那个网卡可以查看出来
28: veth14fc73a@if27
27:eth0@if28
可以看到他们 28对27 27对28,他们正好是对应上的!他们正好是一一匹配的
当然 tomcat82这个容器的道理是一样的

当然我们自己的主机上,网卡的名字会不一样,只要能相互补充对上就是可以的
6.docker network之host
直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行NAT 转换。
容器将不会获得一个独立的Network Namespace, 而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。

注意上图第一个容器,标注了使用host模式
示例:
注意,如果我们用host模式,那么如下的写法会提示一个警告,但是不是错误的,就是不应该进行端口映射的定义:
docker run -d -p 8083:8080 —network host —name tomcat83 billygoo/tomcat8-jdk8

问题:
docke启动时总是遇见标题中的警告
原因:
docker启动时指定—network=host或-net=host,如果还指定了-p映射端口,那这个时候就会有此警告,
并且通过-p设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。
解决:
解决的办法就是使用docker的其他网络模式,例如—network=bridge,这样就可以解决问题,或者直接无视。。。。O(∩_∩)O哈哈~
正确的写法如下:
docker run -d —network host —name tomcat83 billygoo/tomcat8-jdk8
此时我们就不会发现上述 bridge模式那样,出现新的配对网卡了
然后我们 用docker inspect tomcat83查看,发现没有ip地址
表名和宿主机共用一套,如果是bridge模式,那么这里就会有ip
同时,如果此时我们进入容器中,输入命令ip addr这个命令,那么就会发现他的网络和主机的网络一样

那么 没有设置-p的端口映射了,如何访问启动的tomcat83呢?
在CentOS里面用默认的火狐浏览器访问容器内的tomcat83看到访问成功,因为此时容器的IP借用主机的,
所以容器共享宿主机网络IP,这样的好处是外部主机与容器可以直接通信
这里如果我们用的是虚拟机的话,好像是不能通过 虚拟机的宿主机来访问。但是bridge的就可以
7 docker net work 之 none
在none模式下,并不为Docker容器进行任何网络配置。
也就是说,这个Docker容器没有网卡、IP、路由等信息,只有一个lo
需要我们自己为Docker容器添加网卡、配置IP等。
禁用网络功能,只有lo标识(就是127.0.0.1表示本地回环)
docker run -d -p 8084:8080 —network none —name tomcat84 billygoo/tomcat8-jdk8
进入容器内部查看

容器外部查看

8 docker network之container
container⽹络模式
新建的容器和已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。

案例如下:
首选我们先看一个错误的案例:
docker run -d -p 8085:8080 —name tomcat85 billygoo/tomcat8-jdk8
docker run -d -p 8086:8080 —network container:tomcat85 —name tomcat86 billygoo/tomcat8-jdk8
这里 —network container:tomcat85 就表示我们tomcat86和tomcat85使用同一个ip和端口

图中我们可以看到,他启动报错了,说不符合container的网络模式。
相当于tomcat86和tomcat85公用同一个ip同一个端口,导致端口冲突
本案例用tomcat演示不合适。。。演示坑。。。。。。o(╥﹏╥)o
然后我们在看一个正确的案例:
我们用Alpine操作系统
Alpine操作系统是一个面向安全的轻型Linux发行版
Alpine Linux 是一款独立的、非商业的通用 Linux 发行版,专为追求安全性、简单性和资源效率的用户而设计。 可能很多人没听说过这个 Linux 发行版本,但是经常用 Docker 的朋友可能都用过,因为他小,简单,安全而著称,所以作为基础镜像是非常好的一个选择,可谓是麻雀虽小但五脏俱全,镜像非常小巧,不到 6M的大小,所以特别适合容器打包。
首先我们执行如下命令启动第一个Alpine容器
docker run -it —name alpine1 alpine /bin/sh
然后我们在启动第二个Alpine容器:
docker run -it —network container:alpine1 —name alpine2 alpine /bin/sh
然后我们分别进入这两个网络,查看他们的网络信息:

我门可以看到,他们两个的网卡信息都是一样的,证明他们共享了网络。
如果此时我们把 alpine1停止的话,那么alpine2的网络会怎么样呢,也就是共享源(alpine1)关闭了,此时我们就会发现 eth0@if16这个网卡也就没有了。因此alpine2的也就只剩下一个lo了,没有对外的网络了

注意这个图中,是alpine2的网络展示,红框上边表示的时候alpine1容器还没有关闭,红框内表示此时alpine1容器已经关闭了,我们也就发现,alpine2中只剩下一个lo网卡了
9 docker networ之自定义网络
为什么需要自定义网络:
希望所有的容器 分门别类,井井有条,在各自的网络里边和谐共处,并且能完成网络间的通信
还有就是希望当容器ip发生变化的时候,可以通过服务名直接通信,而不会受到影响
在使用自定义网络之前:
我们启动如下两个tomcat容器
docker run -d -p 8081:8080 —name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080 —name tomcat81 billygoo/tomcat8-jdk8
此时 我们进入这两个tomcat容器中拿到ip地址,
然后如果此时我们用ip来ping 可以发现是可以ping通的。


上图 ping和ip addr展示的地址是反的,因为我们在tomcat81中ping的tomcat82,同理我们在tomcat82中ping的tomcat81
但是如果我们用服务名ping的话是不通的,如下图所示:
和上边一样:我们在tomcat81中ping的tomcat82,同理我们在tomcat82中ping的tomcat81


因此上述的的问题就是,我们依赖了ip地址,但是容器的ip地址停止和启动后,是可能发生变化的。
在使用了自定义网络之后:
自定义桥接网络,自定义网络默认使用的是桥接网络bridge
我们新建自定义的网络

新建容器加入上一步新建的自定义网络
docker run -d-p 8081:8080 —network zzyy_network —name tomcat81 billygoo/tomcat8-jdk8
docker run -d-p 8082:8080 —network zzyy_network —name tomcat81 billygoo/tomcat8-jdk8
然后我们同样在tomcat81中通过服务名来pingtomcat82,然后在tomcat82中通过服务名来pingtomcat81

发现是可以行得通的 哦哦哦哦!!!
因此我们一定要写死服务名,但是不要写死ip
总结:
自定义网络本身就维护好了主机名和ip的对应关系( ip和域名都能通)
10 docker link
这种技术已经过时了,不再被使用了,被自定义网络替代了

12 Docker-compose容器编排
1.介绍
Docker-Compose是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。
就是解决容器实例太多了,该如何进行管理
他就像spring一样,对我们的容器实例进行统一的管理,
当容器的实例越来越多的时候,可能就会涉及到复杂的容器启动顺序和加载条件的要求
比如我们想启动应用,那么需要先启动mysql容器,和redis容器
因此我们需要一个工具来管理这些需要启动的容器实例
Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml,写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些容器
docker建议我们每一个容器中只运行一个服务,因为docker容器本身占用资源极少,所以最好是将每个服务单独的分割开来但是这样我们又面临了一个问题?
如果我需要同时部署好多个服务,难道要每个服务单独写Dockerfile然后在构建镜像,构建容器,这样累都累死了,所以docker官方给我们提供了docker-compose多服务部署的工具
例如要实现一个Web微服务项目,除了Web服务容器本身,往往还需要再加上后端的数据库mysql服务容器,redis服务器,注册中心eureka,甚至还包括负载均衡容器等等。。。。。。
Compose允许用户通过一个单独的docker-compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖(比如需要mysql,redis先启动),完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。
同时他也可以方便我们一键启动多个容器和一键停止多个容器
2.下载安装步骤
compose的官网地址如下:
https://docs.docker.com/compose/compose-file/compose-file-v3/
官网下载地址为:
https://docs.docker.com/compose/install/
整体的安装步骤如下: 如下均来自于官网
下载:
curl -L “https://github.com/docker/compose/releases/download/1.29.2/docker-compose--#card=math&code=%28uname%20-s%29-&id=CY5WD)(uname -m)” -o /usr/local/bin/docker-compose
注意上述的GitHub的下载太慢了,我们可以用如下的地址下载:
curl -L “https://get.daocloud.io/docker/compose/releases/download/1.29.2/docker-compose--#card=math&code=%28uname%20-s%29-&id=NkXAr)(uname -m)” -o /usr/local/bin/docker-compose

这里注意我们建议使用的compose版本为v3,同时也要注意 compose版本和docker版本之间的对应关系,compose的版本对docker的版本是有要求的
修改权限:
chmod +x /usr/local/bin/docker-compose
查看安装是否成功
docker-compose —version
卸载方式:

3.compose的核心概念
核心概念分为一个文件,两大要素
文件:docker-compose.yml
他有两个要素:
服务(service):一个个应用容器实例,比如订单微服务、库存微服务、mysql容器、nginx容器或者redis容器
工程(project):由一组关联的应用容器组成的一个完整业务单元(比如订单服务实例+redis+mysql容器),在docker-compose.yml文件中定义。
工程就是我们的一个项目以及项目依赖的所有中间件比如mq,mysql es等,我们把这些都写入docker-compose.yml文件中。对容器进行编排
4.compose使用的三个步骤
1.编写Dockerfile定义各个微服务应用并构建出对应的镜像文件
2.使用docker-compose.yml定义一个完整业务单元,安排好整体应用中的各个容器服务
3.执行docker-compose up命令,来启动并运行整个应用程序,完成一键部署上线
compose常用命令:

文字版如下:
Compose常用命令
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 # 停止服务
5.构造我们的应用服务与镜像
就是最终生成了一个依赖mysql和redis的spring-boot的java项目
编写的Dockerfile如下:
# 基础镜像使用javaFROM java:8# 作者MAINTAINER zzyy# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmpVOLUME /tmp# 将jar包添加到容器中并更名为zzyy_docker.jarADD 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 . 最后通过这个命令生成我们的镜像
6.不用compose的时候编排我们的服务
1.构建单独的mysql的实例:
我们要新建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
然后进入这个实例中,构建我们需要的表结构和数据
2.构建单独的redis实例:
执行如下命令来构建我们的redis实例:
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
3.然后执行我们的微服务工程:
docker run -d -p 6001:6001 zzyy_docker:1.6 通过这个命令来启动
上边三个都启动成功后,如下所示:

这样我们就成功的手动分别根据启动依赖顺序启动了容器,这里我们只有3个容器,我们需要运行3次,但是如果是30个容器呢 300个呢
而且他还有先后启动顺序要求的问题,必须要先启动redis和mysql,然后才能启动我们的服务,这里也容易出错
而且如果我们链接redis或mysql服务使用的是ip,还有可能发生ip变化问题,此时最好使用服务名的方式
7.使用compose编排服务
1.首先我们要编写docker-compose.yml文件
version: "3" #compose的版本号services: #固定写死,里边是容器实例的配置 s标识是复数有多个microService: #定义一个服务实例,名字随意,不冲突就好(就是如何启动一个镜像)image: zzyy_docker:1.6 #这个是镜像名和版本号container_name: ms01 #这里是容器的名称ports: #容器的端口和端口映射- "6001:6001"volumes: #容器的数据卷- /app/microService:/datanetworks: #使用的网络模式- atguigu_netdepends_on: #依赖于哪些容器先启动(这里是服务名)- redis- mysql#上述的,其实就是一个启动镜像的命令 docker run的这个后边的# docker run -d -p 6001:6001 -v /app/microService: /data--network atguigu_net --name ms01 zzyy_docker: 1.6 I#就是上边的这个命令redis: #这里是定义了一个redis的服务实例,就等价于上边的microService#注意这里没有写容器名(服务名) 如果没有写 那么默认就是上边的那个redis的名字。image: redis:6.0.8ports:- "6379:6379"volumes:- /app/redis/redis.conf:/etc/redis/redis.conf- /app/redis/data:/datanetworks:- atguigu_netcommand: redis-server /etc/redis/redis.confmysql:image: mysql:5.7environment:MYSQL_ROOT_PASSWORD: '123456'MYSQL_ALLOW_EMPTY_PASSWORD: 'no'MYSQL_DATABASE: 'db2021'MYSQL_USER: 'zzyy'MYSQL_PASSWORD: 'zzyy123'ports:- "3306:3306"volumes:- /app/mysql/db:/var/lib/mysql- /app/mysql/conf/my.cnf:/etc/my.cnf- /app/mysql/init:/docker-entrypoint-initdb.dnetworks:- atguigu_netcommand: --default-authentication-plugin=mysql_native_password #解决外部无法访问networks: #这里是创建自定义的网络模式atguigu_net:
2.修改微服务工程docker_boot
首先修改yml配置文件,将ip访问的改成服务名访问:
server.port=6001# ========================alibaba.druid相关配置=====================spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driver#这里 将ip改成了服务名#spring.datasource.url=jdbc:mysql://192.168.111.169:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.url=jdbc:mysql://mysql:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=123456spring.datasource.druid.test-while-idle=false# ========================redis相关配置=====================spring.redis.database=0#这里 将ip改成了服务名#spring.redis.host=192.168.111.169spring.redis.host=redisspring.redis.port=6379spring.redis.password=spring.redis.lettuce.pool.max-active=8spring.redis.lettuce.pool.max-wait=-1msspring.redis.lettuce.pool.max-idle=8spring.redis.lettuce.pool.min-idle=0# ========================mybatis相关配置===================mybatis.mapper-locations=classpath:mapper/*.xmlmybatis.type-aliases-package=com.atguigu.docker.entities# ========================swagger=====================spring.swagger2.enabled=true
3.执行 docker-compose up 或者执行docker-compose up -d

上图中我们可以看到,因为我们在compose.yml这个文件中没有指定服务的名字,他会根据我们设定的实例的名字自动生成一个,他的前缀为我们当前的这个compose.yml锁在的目录,同时对于容器还会加一个后缀,只不过这些都是显示的效果,并不影响我们在我们的应用中直接通过 redis和mysql这两个服务名来使用
但是我们想进入容器的时候,就要用这个显示的名字,如下图所示

如果我们加了名字,那么他也显示容器名(服务名),因此建议最好都加上服务名(—name)
所以我们的容器名和服务名还是有一定的区别的!
也就是说,我们如果想通过网络端口的方式访问服务的话,就要使用服务名(redis),如果我们想要进入容器,就要使用容器的名字(—name定义的)
如果不指定 docker-compose.yml就会为我们自动生成一个容器的名字

可以看到我们的容器实例都成功启动了
然后我们可以使用 docker compose stop 停止这三个服务
13.Portainer简介和安装
Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理Docker环境,包括单机环境和集群环境。
它就是一款docker的可视化工具,当然如果是docker集群,最好用k8s
官网地址为:
如下是下载
https://docs.portainer.io/v/ce-2.9/start/install/server/docker/linux
但是我们可以通过docker命令安装:
docker run -d -p 8000:8000 -p 9000:9000 —name portainer —restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer
上边我们可以看到 我们也可以设置多个 -p的端口映射
—restart=always 就是 当我们整个docker重启后,他也会跟着重启,保证我们这个容器随时在线,能一直监控,不会因为docker的重启而停止

第一次登陆需创建admin:
用户名,直接用默认admin
密码记得8位,随便你写
访问地址为:xxx.xxx.xxx.xxx:9000

设置admin用户和密码后首次登陆

选择local选项卡后,本地docker会有详细信息展示:

上一步的图形展示对应的命令行命令为:
14.docker容器监控之CAdvisor+InfluxDB+Grandfana
1.介绍
对docker容器的重量级的监控 之前的 portainer是轻量级的
为什么需要这个东西:
通过docker stats命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据,一般小公司够用了。。。。
但是,
docker stats统计结果只能是当前宿主机的全部容器,数据资料是实时的,没有地方存储(导致无法查寻到之前出问题的数据)、没有健康指标过线预警等功能
这里我们的容器监控三剑客就出现了:

CAvisor监控收集+influxDB存储数据+Granfana展示图表




k8s闭上边的这些还要牛逼。。。
2.compose容器编排,安装上述三个
1.新建目录:

2.创建3件套组合的docker-compose.yml 如下是从官网拔下来的,更偏向运维的东西
version: '3.1' #compose 版本volumes:grafana_data: {}services:influxdb:image: tutum/influxdb:0.9restart: alwaysenvironment:- PRE_CREATE_DB=cadvisorports:- "8083:8083"- "8086:8086"volumes:- ./data/influxdb:/datacadvisor:image: google/cadvisorlinks:- influxdb:influxsrvcommand: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086restart: alwaysports:- "8080:8080"volumes:- /:/rootfs:ro- /var/run:/var/run:rw- /sys:/sys:ro- /var/lib/docker/:/var/lib/docker:rografana:user: "104"image: grafana/grafanauser: "104"restart: alwayslinks: 标识从这个里边取数据- influxdb:influxsrvports:- "3000:3000"volumes:- grafana_data:/var/lib/grafanaenvironment:- HTTP_USER=admin- HTTP_PASS=admin- INFLUXDB_HOST=influxsrv- INFLUXDB_PORT=8086- INFLUXDB_NAME=cadvisor- INFLUXDB_USER=root- INFLUXDB_PASS=root
3.启动docker-compose文件: docker compose up

4.确认三个服务是否成功启动:

5.测试:
浏览cAdvisor收集服务,http:// ip:8080/

他也有基础的图形化展示界面,那是这里只用它来收集数据
浏览influxdb存储服务,http:// ip:8083/
浏览grafana展现服务,http://ip:3000
关于grandfana:我们使用ip+3000端口的方式访问,默认帐户密码( admin/admin)
grandfana的配置步骤:
1.配置数据源:

2.选择influxdb数据源

3.一些配置细节:



4.配置面板panel






到这里cAdvisor+InfluxDB+Grafana容器监控系统就部署完成了
