02-docker

一、Docker概述

1、Docker为什么出现?

第一种情况,部署环境问题。

运维在部署的时候会有很多环境问题:mysql版本、redis补丁、java版本(开发的时候是java8,生产的时候是java7,可能一些java8的新特性,根本无法使用)

第二种情况 ,安装过于繁琐。

运维因为环境问题导致各种软件版本不一样,这时候运维人员就会要求开发人员写一张list清单(环境配置表)。开发是什么环境,我运维就怎么配。那么问题来了:在多台集群的情况下。这时候运维就疯了,或许我的mysql是1主1从,装2台。redis是3主3从,装6台。运维稍微因为版本和环境配置,装错一步,可能系统就跑不起来了,大家只能互相扯皮,运维是非常非常痛苦的。

第三种情况,弹性发布太慢。

假设服务器不够用了,运维需要扩容、缩容,符合弹性的发布。redis是六台3主3从。现在环境并发量大了,需要马上扩容,变成4主4从,瞬间要求你起来两个实例,如果按照以前的安装步骤,你把运维逼疯,也不可能秒级别的把服务跑起来。

img

能不能把软件和环境打包成一个镜像文件给运维?

这时候就要用到docker引擎。开发过程所跑的环境和配置可以原封不动的移植过来,达到了环境的匹配和整合。

img

2、Docker理念

2.1、容器发展历程

image.png

2.2、传统虚拟机技术

虚拟机(virtual machine)就是带环境安装的一种解决方案。

它可以在一种操作系统里面运行另一种操作系统,比如在Windows10系统里面运行Linux系统CentOS7。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够使应用程序,操作系统和硬件三者之间的逻辑不变。

02-docker_第1张图片

虚拟机的缺点:

  1. 资源占用多
  2. 冗余步骤多
  3. 启动慢

3、官网

  • docker官网:http://www.docker.com
  • Docker Hub官网:http://hub.docker.com

二、Docker 安装

  • 要求系统为64位、Linux系统内核版本为 3.8以上,这里选用Centos7.x查看自己的内核

02-docker_第2张图片

1、基本组成

1.1、镜像

Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器

它也相当于一个root文件系统。比如官方镜像centos7就包含了完成的一centos7最小系统的root文件系统。

相当于容器的“源代码”,docker镜像文件类似于Java的类模板,而docker容器实例类似于java中new出来的实例对象。

02-docker_第3张图片

1.2、容器

容器是用镜像创建的运行实例,是一个服务

就像java中的类和实例对象一样,镜像是静态的定义,容器是镜像运行时的实体。

容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。

可以把容器看做一个简单的linux环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。

1.3、仓库(repository)

是集中存放镜像文件的地方。

docker公司提供的官方registry被称为docker hub,存放各种镜像模板的地方。

3、安装/卸载

  • CentOS7安装Docker:https://docs.docker.com/engine/install/centos/

3.1、卸载旧版本

  • https://docs.docker.com/engine/install/centos/

02-docker_第4张图片

3.2、安装

  1. yum安装gcc相关

CentOS7能上外网

yum -y install gcc
yum -y install gcc-c++
  1. 安装需要的软件包
yum install -y yum-utils
  1. 设置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
  1. 更新yum软件包索引
yum makecache fast
  1. 安装DOCKER CE
yum -y install docker-ce docker-ce-cli containerd.io

02-docker_第5张图片

  1. 启动docker
systemctl start docker
  • 测试
docker version
image.png
docker run hello-world

3.3、卸载

systemctl stop docker 
yum remove docker-ce docker-ce-cli containerd.io
rm -rf /var/lib/docker
rm -rf /var/lib/containerd

4、阿里云镜像加速

  • https://promotion.aliyun.com/ntms/act/kubernetes.html
  1. 选择镜像服务

02-docker_第6张图片

  1. 选择镜像服务

02-docker_第7张图片

  1. 获取加速器地址

02-docker_第8张图片

  1. 执行脚本
#创建docker脚本
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://aa25jngu.mirror.aliyuncs.com"]
}
EOF
  1. 重启服务器
systemctl daemon-reload
systemctl restart docker

5、永远的HelloWorld

  • docker run hello-world
image.png

输出这段提示以后,hello world就会停止运行,容器自动终止。

run干了什么?

02-docker_第9张图片

6、底层原理

为什么Docker会比VM虚拟机快

(1)docker有着比虚拟机更少的抽象层

  • 与虚拟机相比,使用 Docker 可以更高效地利用系统资源,因为 Docker 容器共享主机的操作系统和内核,避免了多个虚拟机带来的额外开销
  • 由于没有完整的虚拟化硬件,Docker 启动和停止的速度更快,占用的硬盘空间也更小

(2)docker利用的是宿主机的内核,而不需要加载操作系统OS内核

当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。进而避免引寻、加载操作系统内核返回等比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载OS,返回新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个docker容器只需要几秒钟。

02-docker_第10张图片

三、常用指令

1、帮助启动类命令

  • 启动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

2、镜像命令

2.1、images

02-docker_第11张图片

各个选项说明:
REPOSITORY:表示镜像的仓库源
TAG:镜像的标签版本号
IMAGE ID:镜像ID
CREATED:镜像创建时间
SIZE:镜像大小
	同一仓库源可以有多个 TAG版本,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。
如果你不指定一个镜像的版本标签,例如你只使用 ubuntu,docker 将默认使用ubuntu:latest 镜像
OPTIONS说明:
    -a :列出本地所有的镜像(含历史映像层)
    -q :只显示镜像ID

2.2、search [filename]

  • 命令:docker search [OPTIONS] 镜像名字

02-docker_第12张图片

02-docker_第13张图片

OPTIONS说明:
--limit : 只列出N个镜像,默认25个
# 列出五条点赞最高的五个镜像文件
docker search --limit 5 redis

2.3、pull 某个xxx镜像名字

下载镜像

docker pull 镜像名字[:TAG]
docker pull 镜像名字
没有tag默认拉取最新版
等价于
docker pull 镜像名字:latest
docker pull ubuntu

02-docker_第14张图片

2.4、system df

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

02-docker_第15张图片

2.5、rmi 某个XXX镜像名字ID

删除镜像

删除单个

docker rmi -f 镜像ID

删除多个

docker rmi -f 镜像名1:TAG 镜像名2:TAG 

删除全部

docker rmi -f $(docker images -qa)

2.6 面试题:谈谈docker虚悬镜像是什么?

是什么?

仓库名、标签都是的镜像,俗称虚悬镜像dangling image

长什么样?

02-docker_第16张图片

3、容器命令

3.1 有镜像才能创建容器, 这是根本前提(下载一个CentOS或者ubuntu镜像演示)

02-docker_第17张图片

docker pull centos
docker pull ubuntu

02-docker_第18张图片

3.2 新建+启动容器

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

OPTIONS说明

OPTIONS说明(常用):有些是一个减号,有些是两个减号

–name=“容器新名字” 为容器指定一个名称;

-d: 后台运行容器并返回容器ID,也即启动守护式容器(后台运行);

-i:以交互模式运行容器,通常与 -t 同时使用;

-t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;

也即启动交互式容器(前台有伪终端,等待交互);

-P: 随机端口映射,大写P

-p: 指定端口映射,小写p

02-docker_第19张图片

启动交互式容器(前台命令行)

02-docker_第20张图片

#使用镜像centos:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。

docker run -it centos /bin/bash 

参数说明:

-i: 交互式操作。

-t: 终端。

centos : centos 镜像。

/bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。

要退出终端,直接输入 exit:

3.3 列出当前所有正在运行的容器

docker ps [OPTIONS]

OPTIONS说明(常用):

-a :列出当前所有正在运行的容器+历史上运行过的

-l :显示最近创建的容器。

-n:显示最近n个创建的容器。

-q :静默模式,只显示容器编号。

3.4 退出容器

两种退出方式

exit

run进去容器,exit退出,容器停止

ctrl+p+q

run进去容器,ctrl+p+q退出,容器不停止

3.5 启动已停止运行的容器

docker start 容器ID或者容器名

3.6 重启容器

docker restart 容器ID或者容器名

3.7 停止容器

docker stop 容器ID或者容器名

3.8 强制停止容器

docker kill 容器ID或容器名

3.9 删除已停止的容器

docker rm 容器ID

一次性删除多个容器实例

docker rm -f $(docker ps -a -q)
docker ps -a -q | xargs docker rm

3.10 重要

  1. 有镜像才能创建容器,这是根本前提(下载一个Redis6.0.8镜像演示)
  2. 启动守护式容器(后台服务器)

在大部分的场景下,我们希望 docker 的服务是在后台运行的, 我们可以过 -d 指定容器的后台运行模式。

docker run -d 容器名

#使用镜像centos:latest以后台模式启动一个容器

docker run -d centos

问题:然后docker ps -a 进行查看, 会发现容器已经退出

很重要的要说明的一点: Docker容器后台运行,就必须有一个前台进程。

容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的。

这个是docker的机制问题,比如你的web容器,我们以nginx为例,正常情况下,我们配置启动服务只需要启动响应的service即可。例如service nginx start。但是,这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后,会立即自杀因为他觉得他没事可做了。所以,最佳的解决方案是将你要运行的程序以前台进程的形式运行,常见就是命令行模式,表示我还有交互操作,别中断。

redis 前后台启动演示case

前台交互式启动

docker run -it redis:6.0.8

后台守护式启动

docker run -d redis:6.0.8
  1. 查看容器日志
docker logs 容器ID
  1. 查看容器内运行的进程
docker top 容器ID
  1. 查看容器内部细节
docker inspect 容器ID
  1. 进入正在运行的容器并以命令行交互
docker exec -it 容器ID bashShell

02-docker_第21张图片

02-docker_第22张图片

重新进入docker attach 容器ID

上述两个区别?

attach 直接进入容器启动命令的终端,不会启动新的进程 用exit退出,会导致容器的停止。

02-docker_第23张图片

exec 是在容器中打开的终端,并且可以启动新的进程 用exit退出,不会导致容器的停止。

02-docker_第24张图片

推荐大家使用 docker exec 命令,因为退出容器终端,不会导致容器的停止。

用之前的redis容器实例进入试试

进入redis服务

docker exec -it 容器ID /bin/bash
docker exec -it 容器ID redis-cli

一般用-d后台启动的程序,再用exec进入对应容器实例

  1. 从容器内拷贝文件到主机上

容器→主机

docker cp 容器ID:容器内路径 目的主机路径

02-docker_第25张图片

导入和导出容器

export 导出容器的内容留作为一个tar归档文件[对应import命令]

import 从tar包中的内容创建一个新的文件系统再导入为镜像[对应export]

案例

docker export 容器ID > 文件名.tar

02-docker_第26张图片

cat 文件名.tar | docker import - 镜像用户/镜像名:镜像版本号

02-docker_第27张图片

四、镜像

1.1 分层的镜像

从远程仓库拉去tomcat,可以看到镜像是一层一层下载的:
02-docker_第28张图片

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统叫UnionFS。

1.2 UnionFS(联合文件系统)

是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像

特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件盒目录。

02-docker_第29张图片

1.3 镜像加载原理

docker的镜像实际上由一层一层的文件系统组成,这种层级文件系统就是上述的UnionFS。接着,在内部又分为2部分:

  • bootfs(boot file system):docker镜像的最底层是bootfs,主要包含bootloader(加载器)和kernel(内核)。

    bootloader主要是引导加载kernel,linux刚启动时会加载bootfs文件系统。跟linux一样。

    当boot加载完成后,整个内核就在内存中了,此时内存的使用权已由bootfs转交给了内核,此时系统也会卸载bootfs。
    这里的加载,可以理解为,我们windows电脑开机时候,从黑屏到进入操作系统的过程。

  • rootfs(root file system):在bootfs之上,包含的就是典型linux系统中的/dev、/proc、/bin、/etc等标准目录和文件。

    rootfs就是各种不同的操作系统发行版,比如Ubuntu、Centos等等。

02-docker_第30张图片

为什么平常我们虚拟机的centos好几个G,而docker才200M?

因为底层直接用宿主机的内核,自己只需要提供rootfs就行了,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接host的内核,自己只需要提供rootfs就行了。

1.4 为什么 Docker 镜像要采用这种分层结构呢

可以共享资源,方便复制迁移,方便复用。

比如说有多个镜像都从相同的base镜像构建而来,那么Docker Host只需在磁盘上保存一份base镜像;同时内存中也只需加载一份base镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

docker中的镜像分层,支持通过扩展现有的镜像,创建新的镜像。类似java继承于一个base基础类,自己再按需扩展。

新镜像是从base镜像一层一层叠加生成的。没安装一个软件,就在现有镜像的基础上增加一层。

2、重点理解

Docker镜像层都是只读的,容器层是可写的。当容器启动时,一个新的可写层被加载到镜像的顶部。 这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。

所有对容器的改动 - 无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。

02-docker_第31张图片

3、commit操作案例

docker commit 提交容器副本使之成为一个新的镜像

docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[标签名]

3.1 案例演示ubuntu安装vim

  1. 从Hub上下载ubuntu镜像到本地并成功运行
  2. 原始的默认Ubuntu镜像是不带着vim命令的

02-docker_第32张图片

  1. 外网连通的情况下,安装vim

02-docker_第33张图片

docker容器内执行上述两条命令:

apt-get update
apt-get -y install vim

02-docker_第34张图片

02-docker_第35张图片

  1. 安装完成后,commit我们自己的新镜像

02-docker_第36张图片

02-docker_第37张图片

  1. 启动我们的新镜像并和原来的对比

02-docker_第38张图片

  1. a.官网是默认下载的Ubuntu没有vim命令

  2. b.我们自己commit构建的镜像,新增加了vim功能,可以成功使用。

3.2 小总结

Docker中的镜像分层,支持通过扩展现有镜像,创建新的镜像。类似Java继承于一个Base基础类,自己再按需扩展。

新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层

02-docker_第39张图片

五、阿里云

1 本地镜像发布到阿里云流程

02-docker_第40张图片

2 镜像的生成方法

基于当前容器创建一个新的镜像,新功能增强

docker commit [OPTIONS] 容器ID [REPOSITORY[:TAG]]
OPTIONS说明:

-a :提交的镜像作者;
-m :提交时的说明文字;

02-docker_第41张图片

02-docker_第42张图片

3 将本地镜像推送到阿里云

本地镜像素材原型

02-docker_第43张图片

02-docker_第44张图片

阿里云开发者平台

https://promotion.aliyun.com/ntms/act/kubernetes.html

02-docker_第45张图片

3.1 创建仓库镜像

  1. 选择控制台,进入容器镜像服务

02-docker_第46张图片

  1. 选择个人实例

02-docker_第47张图片

  1. 命名空间

02-docker_第48张图片

02-docker_第49张图片

  1. 仓库名称

02-docker_第50张图片

02-docker_第51张图片

02-docker_第52张图片

  1. 进入管理界面获得脚本

02-docker_第53张图片

3.2 将镜像推送到阿里云

  1. 管理界面脚本

02-docker_第54张图片

  1. 脚本实例
# 登录阿里云
docker login --username=zzyybuy registry.cn-hangzhou.aliyuncs.com
# 复制一份镜像
docker tag cea1bb40441c registry.cn-hangzhou.aliyuncs.com/atguiguwh/myubuntu:1.1
# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/atguiguwh/myubuntu:1.1

上面命令是阳哥自己本地的,你自己酌情处理,不要粘贴我的。

02-docker_第55张图片

4 将阿里云上的镜像下载到本地

02-docker_第56张图片

docker pull registry.cn-hangzhou.aliyuncs.com/atguiguwh/myubuntu:1.1

六、私有库

1 是什么

1 官方Docker Hub地址:https://hub.docker.com/,中国大陆访问太慢了且准备被阿里云取代的趋势,不太主流。

2 Dockerhub、阿里云这样的公共镜像仓库可能不太方便,涉及机密的公司不可能提供镜像给公网,所以需要创建一个本地私人仓库供给团队使用,基于公司内部项目构建镜像。

Docker Registry是官方提供的工具,可以用于构建私有镜像仓库

2 将本地镜像推送到私有库

2.1 下载镜像Docker Registry

docker pull registry 

02-docker_第57张图片

02-docker_第58张图片

2.2 运行私有库Registry,相当于本地有个私有Docker hub

docker run -d -p 5000:5000  -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry

默认情况,仓库被创建在容器的/var/lib/registry目录下,建议自行用容器卷映射,方便于宿主机联调

02-docker_第59张图片

2.3 案例演示创建一个新镜像,ubuntu安装ifconfig命令

  1. 从Hub上下载ubuntu镜像到本地并成功运行
  2. 原始的Ubuntu镜像是不带着ifconfig命令的

02-docker_第60张图片

  1. 外网连通的情况下,安装ifconfig命令并测试通过

docker容器内执行上述两条命令:

apt-get update
apt-get install net-tools
img img
  1. 安装完成后,commit我们自己的新镜像

公式:

docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[标签名]

命令:在容器外执行,记得

docker commit -m="ifconfig cmd add" -a="zzyy" a69d7c825c4f zzyyubuntu:1.2

02-docker_第61张图片

  1. 启动我们的新镜像并和原来的对比

    1. 官网是默认下载的Ubuntu没有ifconfig命令。
    2. 我们自己commit构建的新镜像,新增加了ifconfig功能,可以成功使用。

02-docker_第62张图片

2.4 curl验证私服库上有什么镜像

#启动registry
docker run -d -p 5000:5000  -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry
#查看私有库中有没有镜像
curl -XGET http://192.168.227.100:5000/v2/_catalo

可以看到,目前私服库没有任何镜像上传过。

02-docker_第63张图片

2.5 将新镜像zzyyubuntu:1.2修改符合私服规范的Tag

按照公式: docker tag 镜像:Tag Host:Port/Repository:Tag

自己host主机IP地址,填写同学你们自己的,不要粘贴错误。

使用命令 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

02-docker_第64张图片

2.6 修改配置文件使之支持http

02-docker_第65张图片

别无脑照着复制,registry-mirrors 配置的是国内阿里提供的镜像加速地址,不用加速的话访问官网的会很慢。

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。

2.7 push推送到私服库

docker push 192.168.111.162:5000/zzyyubuntu:1.2

02-docker_第66张图片

2.8 curl验证私服库上有什么镜像2

curl -XGET http://192.168.111.162:5000/v2/_catalog

02-docker_第67张图片

2.9 pull到本地并运行

docker pull 192.168.111.162:5000/zzyyubuntu:1.2

02-docker_第68张图片

docker run -it 镜像ID /bin/bash

02-docker_第69张图片

七、容器数据卷

1 坑:容器卷记得加入

--privileged=true

Docker挂载主机目录访问如果出现

cannot open directory .: Permission denied

解决办法:在挂载目录后多加一个–privileged=true参数即可

如果是CentOS7安全模块会比之前系统版本加强,不安全的会先禁止,所以目录挂载的情况被默认为不安全的行为,在SELinux里面挂载目录被禁止掉了,如果要开启,我们一般使用–privileged=true命令,扩大容器的权限解决挂载目录没有权限的问题,也即使用该参数,container内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限。

2 回顾下上一讲的知识点,参数V

02-docker_第70张图片

3 是什么

卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因为能够绕过union file system提供一些用于持续存储或共享数据的特性。

卷的设计目的就是数据持久化,完全独立于容器的生命周期,因为docker不会在容器删除时删除其挂载的数据卷。

一句话:有点类似我们Redis里面的rdb和aof文件。

将docker容器内的数据保存进宿主机的磁盘中,运行一个带有容器卷存储功能的容器实例。

 docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录   镜像名

4 能干嘛

将容器的重要数据备份持久化到主机中,防止不小心删除容器导致数据丢失

特点

  • 数据卷可在容器之间共享或重用数据
  • 卷中的更改可以直接实时生效
  • 数据卷中的更改不会包含在镜像的更新中
  • 数据卷的生命周期一直持续到没有容器使用它为止
  • 停止容器,在主机中添加文件,文件还是会同步到容器
  • 查看数据卷是否挂载成功:docker inspect 容器id

5 数据卷案例

5.1 宿主vs容器之间映射添加容器卷

  1. 直接命令添加
docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录 镜像名

公式:docker run -it -v /宿主机目录:/容器内目录 ubuntu /bin/bash

docker run -it --name myu3 --privileged=true -v /tmp/myHostData:/tmp/myDockerData ubuntu /bin/bash

02-docker_第71张图片

  1. 查看数据卷是否挂载成功
docker inspect 容器ID

02-docker_第72张图片

  1. 容器和宿主机之间数据共享

    1. docker修改,主机同步获得 。
    2. 主机修改,docker同步获得。
    3. docker容器stop,主机修改,docker容器重启看数据是否同步。

02-docker_第73张图片

5.2 读写规则映射添加说明

读写(默认)

docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:rw 镜像名

默认同上案例,默认就是rw

02-docker_第74张图片

rw = read + write

02-docker_第75张图片

只读

容器实例内部被限制,只能读取不能写

02-docker_第76张图片

/容器目录:ro 镜像名 就能完成功能,此时容器自己只能读取不能写

ro = read only

此时如果宿主机写入内容,可以同步给容器内,容器可以读取到。

docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:ro 镜像名

5.3 卷的继承和共享

容器1完成和宿主机的映射

docker run -it  --privileged=true -v /mydocker/u:/tmp --name u1 ubuntu

02-docker_第77张图片

02-docker_第78张图片

容器2继承容器1的卷规则

docker run -it  --privileged=true --volumes-from 父类  --name u2 ubuntu

02-docker_第79张图片

八、常规安装简介

1 安装tomcat

  1. docker hub上面查找tomcat镜像
docker search tomcat
img
  1. 从docker hub上拉取tomcat镜像到本地
docker pull tomcat
img img
  1. docker images查看是否有拉取到的tomcat
img
  1. 使用tomcat镜像创建容器实例(也叫运行镜像)
docker run -it -p 8080:8080 tomcat

-p 小写,主机端口:docker容器端口

-P 大写,随机分配端口

-i 交互

-t 终端

-d 后台

img
  1. 访问猫首页

问题

img

解决

    1. 可能没有映射端口或者没有关闭防火墙
    2. 把webapps.dist目录换成webapps

先成功启动tomcat

img

查看webapps 文件夹查看为空

img
  1. 免修改版说明
docker pull billygoo/tomcat8-jdk8
docker run -d -p 8080:8080 --name mytomcat8 billygoo/tomcat8-jdk8
img

2 安装mysql

2.1 docker hub上面查找mysql镜像

img

2.2 从docker hub上(阿里云加速器)拉取mysql镜像到本地标签为5.7

img

2.3 使用mysql5.7镜像创建容器(也叫运行镜像)

2.3.1 命令出处,哪里来的?
img
2.3.2 简单版

使用mysql镜像

docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
docker ps
docker exec -it 容器ID /bin/bash
mysql -uroot -p

02-docker_第80张图片

建库建表插入数据

img

外部Win10也来连接运行在dokcer上的mysql容器实例服务

img

问题

插入中文数据试试

img

为什么报错?

docker上默认字符集编码隐患,docker里面的mysql容器实例查看,内容如下:

SHOW VARIABLES LIKE 'character%'
img

删除容器后,里面的mysql数据怎么办?

8.3.3.3 实战版

新建mysql容器实例

docker run -d -p 3306:3306 --privileged=true -v /docker/mysql/log:/var/log/mysql -v /docker/mysql/data:/var/lib/mysql -v /docker/mysql/conf:/etc/mysql -e MYSQL_ROOT_PASSWORD=123456  --name mysql mysql:5.7

02-docker_第81张图片

新建my.cnf,通过容器卷同步给mysql容器实例

[client]
default_character_set=utf8
[mysqld]
collation_server = utf8_general_ci
character_set_server = utf8
img

重新启动mysql容器实例再重新进入并查看字符编码

img img

再新建库新建表再插入中文测试

img img

结论

之前的DB 无效,修改字符集操作+重启mysql容器实例

之后的DB 有效,需要新建

结论:docker安装完MySQL并run出容器后,建议请先修改完字符集编码后再新建mysql库-表-插数据

img

假如将当前容器实例删除,再重新来一次,之前建的db01实例还有吗?try

3 安装redis

  1. 从docker hub上(阿里云加速器)拉取redis镜像到本地标签为6.0.8
img
  1. 入门命令
img
  1. 命令提醒:容器卷记得加入–privileged=true

Docker挂载主机目录Docker访问出现cannot open directory .: Permission denied

解决办法:在挂载目录后多加一个–privileged=true参数即可

  1. 在CentOS宿主机下新建目录/app/redis
mkdir -p /app/redis
img
  1. 将一个redis.conf文件模板拷贝进/app/redis目录下
img

将准备好的redis.conf文件放进/app/redis目录下

  1. /app/redis目录下修改redis.conf文件

默认出厂的原始redis.conf

port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
always-show-logo yes

save 900 1
save 300 10
save 60 10000
 
stop-writes-on-bgsave-error yes
 
rdbcompression yes

rdbchecksum yes
 
dbfilename dump.rdb

dir ./
 
replica-serve-stale-data yes
 
replica-read-only yes
 
repl-diskless-sync no
 
repl-diskless-sync-delay 5
 
replica-priority 100
 
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
 
no-append
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
 
aof-load-truncated yes
 
aof-use-rdb-preamble yes
 
lua-time-limit 5000
 
slowlog-log-slower-than 10000
 
slowlog-max-len 128
 
latency-monitor-threshold 0
 
  notify-keyspace-events Ex

hash-max-ziplist-entries 512
hash-max-ziplist-value 64
 
list-max-ziplist-size -2
 
list-compress-depth 0
 
set-max-intset-entries 512
 
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
 
hll-sparse-max-bytes 3000

stream-node-max-bytes 4096
stream-node-max-entries 100
 
activerehashing yes
 
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
 
hz 10
 
dynamic-hz yes

aof-rewrite-incremental-fsync yes
 
rdb-save-incremental-fsync yes
  1. 使用redis6.0.8镜像创建容器(也叫运行镜像)
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

02-docker_第82张图片

  1. 测试redis-cli连接上来

02-docker_第83张图片

docker exec -it 运行着Rediis服务的容器ID redis-cli
  1. 请证明docker启动使用了我们自己指定的配置文件

修改前

img

我们用的配置文件,数据库默认是16个

修改后

img

宿主机的修改会同步给docker容器里面的配置。记得重启服务

  1. 测试redis-cli连接上来第2次
img

4 安装Nginx

见高级篇Portainer

九、复杂安装

1 安装MySQL主从复制

1.1 主从复制原理

https://blog.csdn.net/Guzarish/article/details/123244487

1.2 主从搭建步骤

  1. 新建主服务器容器实例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
  1. 进入/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
  1. 修改完配置后重启master实例
docker restart mysql-master
  1. 进入mysql-master容器
docker exec -it mysql-master /bin/bash
mysql -uroot -proot
  1. master容器实例内创建数据同步用户
CREATE USER 'slave'@'%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
  1. 新建从服务器容器实例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
  1. 进入/mydata/mysql-slave/conf目录下新建my.cnf
vim 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
  1. 修改完配置后重启slave实例
docker restart mysql-slave
  1. 在主数据库中查看主从同步状态
show master status;
  1. 进入mysql-slave容器
docker exec -it mysql-slave /bin/bash
mysql -uroot -proot
  1. 在从数据库中配置主从复制
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;

02-docker_第84张图片

主从复制命令参数说明

master_host:主数据库的IP地址;

master_port:主数据库的运行端口;

master_user:在主数据库创建的用于同步数据的用户账号;

master_password:在主数据库创建的用于同步数据的用户密码;

master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;

master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;

master_connect_retry:连接失败重试的时间间隔,单位为秒。

  1. 在从数据库中查看主从同步状态
show slave status \G;
img
  1. 在从数据库中开启主从同步
start slave;

02-docker_第85张图片

  1. 查看从数据库状态发现已经同步
img
  1. 主从复制测试

    1. 主机新建库-使用库-新建表-插入数据,ok
    2. 从机使用库-查看记录,ok

2 安装redis

2.1、面试题

1~2亿条数据需要缓存,请问如何设计这个存储案例?

回答:单机单台100%不可能,肯定是分布式存储,用redis如何落地?

上述问题阿里P6~P7工程案例和场景设计类必考题目, 一般业界有3种解决方案

  1. 哈希取余分区

02-docker_第86张图片

2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:

hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

优点:

简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

缺点:

原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。

某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一致性哈希算法分区

  1. 是什么

一致性Hash算法背景

一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了。

  1. 能干嘛

提出一致性Hash解决方案。 目的是当服务器个数发生变动时, 尽量减少影响客户端到服务器的映射关系

  1. 大步骤
    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环。

02-docker_第87张图片

  1. 服务器IP节点映射

节点映射

将集群中各个IP节点映射到环上的某一个位置。

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

02-docker_第88张图片

  1. 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上。

02-docker_第89张图片

    1. 优点
      1. 一致性哈希算法的容错性

容错性

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

02-docker_第90张图片

      1. 一致性哈希算法的扩展性

数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,

不会导致hash取余全部数据重新洗牌。

02-docker_第91张图片

    1. 缺点

一致性哈希算法的数据倾斜问题

Hash环的数据倾斜问题

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

例如系统中只有两台服务器:

02-docker_第92张图片

  1. 小总结

为了在节点数目发生改变时尽可能少的迁移数据

将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点存放。

而当有节点加入或退出时仅影响该节点在Hash环上顺时针相邻的后续节点。

优点

加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。

缺点

数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。

  1. 哈希槽分区

    1. 是什么

1 为什么出现

02-docker_第93张图片

哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间。

2 能干什么

解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

02-docker_第94张图片

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。

哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

3 多少个hash槽

一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

    1. 哈希槽计算

Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上

02-docker_第95张图片

02-docker_第96张图片

2.2、3主3从redis集群扩缩容配置案例架构说明

见自己的processon笔记

2.3、集群搭建步骤

  1. 启动六台redis服务器
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
  • 参数介绍
  • –name redis-node-6:容器名字

  • –net host:使用宿主机的IP和端口,默认

  • –privileged=true:获取宿主机root用户权限

  • -v /data/redis/share/redis-node-6:/data:容器卷,宿主机地址:docker内部地址

  • redis:6.0.8:redis镜像和版本号

  • –cluster-enabled yes:开启redis集群

  • –appendonly yes开启aof持久化

  • –port 6386 开放端口

  1. 进入容器redis-node-1并为6台机器构建集群关系
# 进入容器
docker exec -it redis-node-1 /bin/bash
# 搭建集群环境,node1作为领头人
redis-cli --cluster create 192.168.227.100:6381 192.168.227.100:6382 192.168.227.100:6383 192.168.227.100:6384 192.168.227.100:6385 192.168.227.100:6386 --cluster-replicas 1

02-docker_第97张图片

  1. 查看集群状态
# 查看集群主从关系
127.0.0.1:6381> cluster nodes
# 查看集群分布状态
127.0.0.1:6381> cluster info

02-docker_第98张图片

  1. 测试:插入数据

注意:因为集群是根据哈希槽分配的哈希槽,所以每个端口分配的槽数不同,插入数据也会存放到不同的主机中

redis-cli -p 6381 -c

02-docker_第99张图片

  1. 查看集群具体信息
redis-cli --cluster check 192.168.227.100:6381

02-docker_第100张图片

2.4、问题

2.4.1、容错切换迁移
  1. 主6381和从机切换,先停止主机6381
  2. 6381主机停了,对应的真实从机上位
  3. 6381作为1号主机分配的从机以实际情况为准,具体是几号机器就是几号
  4. 再次查看集群信息

02-docker_第101张图片

6381宕机了,6385上位成为了新的master。

  1. 此时我们尝试停止6381(主),会发现6385此时redis会通过心跳检测,6385上位成为新的master
  2. 此时我们再将6381(旧主)恢复并重新加入到了集群中
    1. 主机:6385(原来的从机)
    2. 从机:6381(原来的主机)

2.5、扩容

  1. 启动redis两个实力
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 6387
docker 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
  1. 将新增的6387节点(空槽号)作为master节点加入原集群
# 进入实例内部
docker exec -it redis-node-7 /bin/bash

# 将新增的6387作为master节点加入集群
redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
  1. 查看集群状态
redis-cli --cluster check 真实ip地址:6381

02-docker_第102张图片

  1. 重新分派槽号
redis-cli --cluster reshard 192.168.111.147:6381

02-docker_第103张图片

02-docker_第104张图片

  1. 再次查看集群状态

为什么6387是3个新的区间,以前的还是连续

  • 重新分配成本太高,所以前3家各自匀出来一部分,从6381/6382/6383三个旧节点分别匀出1364个坑位给新节点6387

02-docker_第105张图片

  1. 为主节点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的编号,按照自己实际情况

02-docker_第106张图片

02-docker_第107张图片

  1. · 检查集群情况第3次
redis-cli --cluster check 192.168.111.147:6382

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.6、缩容

  1. 检查集群情况并获取节点ID
redis-cli --cluster check 192.168.111.147:6382
  1. 将6388删除 从集群中将从节点6388删除
命令:redis-cli --cluster del-node ip:从机端口 从机6388节点ID

redis-cli --cluster check 192.168.111.147:6382
  1. 将6387的槽号清空,重新分配,本例将清出来的槽号都给6381
redis-cli --cluster reshard 192.168.111.147:6381

02-docker_第108张图片

02-docker_第109张图片

  1. 检查集群情况第二次
redis-cli --cluster check 192.168.111.147:6381

# 删除主节点
redis-cli --cluster del-node 192.168.111.147:6387 e4781f644d4a4e4d4b4d107157b9ba8144631451
  1. 检查集群情况第三次
redis-cli --cluster check 192.168.111.147:6381

十、DockerFile

1 是什么

Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。

概述

img

官网

https://docs.docker.com/engine/reference/builder/

构建三步骤

  1. 编写Dockerfile文件
  2. docker build 命令构建镜像
  3. docker run 镜像 运行容器实例

2 构建过程解析

2.1 Dockerfile内容基础知识

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. #表示注释
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

2.2 Docker执行Dockerfile的大致流程

  1. docker从基础镜像运行一个容器
  2. 执行一条指令并对容器作出修改
  3. 执行类似docker commit的操作提交一个新的镜像层
  4. docker再基于刚提交的镜像运行一个新容器
  5. 执行dockerfile中的下一条指令直到所有指令都执行完成

2.3 小总结

从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段,

  • Dockerfile是软件的原材料
  • Docker镜像是软件的交付品
  • Docker容器则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例

Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。

02-docker_第110张图片

  1. Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;
  2. Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时会真正开始提供服务;
  3. Docker容器,容器是直接提供服务的。

3 常用保留字指令

参考tomcat8的dockerfile入门

https://github.com/docker-library/tomcat

  • FROM:基础镜像

  • MAINTAINER:镜像维护者的姓名和邮箱地址

  • RUN:容器构建时需要运行的命令

    1. shell格式

      02-docker_第111张图片

      RUN yum -y install vim
      
    2. exec格式

02-docker_第112张图片

  • EXPOSE:当前容器对外暴露的端口

  • WORKDIR:指定在创建容器后,终端默认登陆的进来工作目录。

  • USER:指定该镜像以什么样的用户去执行,如果不指定,默认是root

  • ENV:用来在构建镜像过程中,设置环境变量。比如设置 ENV MY_PATH /usr/mytest,这个环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面置顶了环境变量前缀一样;也可以在其他指令中直接使用这些环境变量。接着就可以直接使用WORKDIR $MY_PATH

  • VOLUME:容器数据卷,用于数据保存和持久化工作

  • ADD:将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包

  • COPY:类似ADD,拷贝文件和目录到镜像中。

  • CMD:CMD指令的格式和RUN相似

    • 两种格式:
      • shell格式:CMD <命令>
      • exec格式:CMD [“可执行文件”,“参数1”,“参数2”,…]
    • 参数列表格式:CMD [“参数1”,“参数2”,…],在置顶了ENTRYPOINT指令后,用CMD指定具体的参数
    • dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换
      • 比如如果我们在启动tomcat的时候,在run后面加上/bin/bash,就会导致启动失败,因为我们的/bin/bash命令把原有的run命令给覆盖了
    • 和RUN的区别:
      • CMD是在docker run的时候运行
      • RUN是在docker build时运行
  • ENTRYPOINT:也是用来指定一个容器启动时要运行的命令。类似CMD指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当做参数送给ENTRYPOINT指令指定的程序。

    • 两种写法:
      • exec 格式:ENTRYPOINT [“executable”, “param1”, “param2”]
      • shell 格式: ENTRYPOINT command param1 param2
    • 重点:
      • ENTRYPOINT 指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有其他传入值作为该命令的参数
      • ENTRYPOINT 的值可以通过 docker run --entrypoint 来覆盖掉
      • 只有 Dockerfile 中的最后一条 ENTRYPOINT 指令会起作用
    • ENTRYPOINT 和 CMD 联合使用:
      • 当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令
        换句话说实际执行时,会变成: “”

    当使用 ENTRYPOINTCMD 指令时,可以通过一些示例来更好地理解它们的区别。

    假设有一个 Dockerfile 如下:

    dockerfileCopy CodeFROM ubuntu:latest
    
    # 设置 ENTRYPOINT 指令
    ENTRYPOINT ["/bin/echo", "Hello"]
    
    # 设置 CMD 指令
    CMD ["world"]
    

    在这个例子中,ENTRYPOINT 指令将容器的主要命令设置为 /bin/echo,并且默认参数为 HelloCMD 指令提供了 /bin/echo 命令的默认参数 world

    现在,使用该 Dockerfile 构建镜像并运行容器:

    docker build -t myimage .
    $ docker run myimage
    

    容器的输出将会是:

    Hello world
    

    此时,ENTRYPOINT 指定的命令 /bin/echo 被执行,并且 CMD 指定的参数 world 作为 /bin/echo 的参数传递进去,最终输出了 “Hello world”。

    如果我们在运行容器时指定不同的参数:

    docker run myimage, how are you?
    

    容器的输出将会是:

    Hello how are you?
    

    这里,docker run 命令行中的参数 "how are you?" 覆盖了 CMD 中默认的参数,所以最终输出了 “Hello how are you?”。

    通过上述示例可以看出,ENTRYPOINT 设置了容器的主要命令,而 CMD 则为该命令提供了默认参数。在运行容器时,可以通过传递参数来覆盖 CMD 指定的默认参数。这样可以使得容器具有灵活性,可以根据不同的需求执行不同的命令或者带有不同参数的相同命令。

4 案例

4.1 自定义镜像mycentosjava8

要求

Centos7镜像具备vim+ifconfig+jdk8

02-docker_第113张图片

JDK的下载镜像地址:https://www.oracle.com/java/technologies/downloads/#java8

02-docker_第114张图片

https://mirrors.yangxingzhen.com/jdk/

编写

准备编写vim Dockerfile文件

FROM centos
MAINTAINER zzyy

ENV MYPATH /usr/local
WORKDIR $MYPATH

#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装java8及lib库
RUN yum -y install glibc.i686
RUN 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_171
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH

EXPOSE 80

CMD echo $MYPATH
CMD echo "success--------------ok"
CMD /bin/bash

大写字母D

构建

docker build -t 新镜像名字:TAG .

docker build -t centosjava8:1.5 .
docker build -f mydockerfile -t my_centos:1.2 .
img img

注意,上面TAG后面有个空格,有个点

运行

docker run -it 新镜像名字:TAG

docker run -it centosjava8:1.5 /bin/bash

02-docker_第115张图片

再体会下UnionFS(联合文件系统)

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

02-docker_第116张图片

特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

4.2 虚悬镜像

是什么

仓库名、标签都是的镜像,俗称dangling image

Dockerfile写一个

  1. vim Dockerfile
from ubuntu
CMD echo 'action is success'
  1. docker build .

02-docker_第117张图片

查看

docker image ls -f dangling=true

命令结果

02-docker_第118张图片

删除

docker image prune

虚悬镜像已经失去存在价值,可以删除

02-docker_第119张图片

4.3 家庭作业-自定义镜像myubuntu

编写

准备编写DockerFile文件

02-docker_第120张图片

FROM ubuntu
MAINTAINER zzyy
 
ENV MYPATH /usr/local
WORKDIR $MYPATH
 
RUN apt-get update
RUN apt-get install net-tools
#RUN apt-get install -y iproute2
#RUN apt-get install -y inetutils-ping
 
EXPOSE 80
 
CMD echo $MYPATH
CMD echo "install inconfig cmd into ubuntu success--------------ok"
CMD /bin/bash

构建

docker build -t 新镜像名字:TAG .

运行

docker run -it 新镜像名字:TAG

5 小总结

img

十一、Docker微服务实战

  1. 编写Dockerfile
# 基础镜像使用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
  1. 将微服务jar包和Dockerfile文件上传到同一个目录下/mydocker
  2. 构建镜像
docker build -t zzyy_docker:1.6 .
  1. 运行容器
docker run -d -p 6001:6001 zzyy_docker:1.6

02-docker_第121张图片

  1. 访问
image-20230711173452461

十二、Docker网络

网络模式

Docker默认提供了3种网络模式,生成容器时不指定网络模式下默认使用bridge桥接模式。
使用命令查看当前Docker所有的网络模式。

[root@haha docker_web]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
aa8a26ae1484   bridge    bridge    local
62cd016ed66a   host      host      local
fc650e2a675e   none      null      local

1.1 host模式

如果启动容器时使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

使用host模式的容器可以直接使用宿主机的IP地址与外界通信,容器内部的服务端口也可以使用宿主机的端口,host最大的优势就是网络性能比较好,但是docker host上已经使用的端口就不能再用了,网络的隔离性不好。

Namespace的简要说明:
Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。
一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的NetworkNamespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。

1.2 container模式

这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。

1.3 none模式

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。这种网络模式下容器只有lo回环网络,没有其他网卡。none模式可以在容器创建时通过–network=none来指定。这种类型的网络没有办法联网,封闭的网络能很好的保证容器的安全性。

1.4 bridge模式

bridge模式是docker的默认网络模式,不写–net参数,就是bridge模式。使用docker run -p时,docker实际是在iptables做了DNAT规则,实现端口转发功能。可以使用iptables -t nat -vnL查看。bridge模式如下图所示:

02-docker_第122张图片

当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥,从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中。可以通过brctl show命令查看。

02-docker_第123张图片
02-docker_第124张图片

从上图中我们可以看到,可以看到容器内部和Linux主机都会创建一个新的网卡,而这两个网卡都是成对的。使用的技术就是evth-pair。evth-pair 就是一对的虚拟设备接口,他们是成对出现的,一段连着协议,一段彼此相连。evth-pair充当一个桥梁,连接各种虚拟网络设备。

Docker容器完成bridge网络配置的过程如下:
  1. 在主机上创建一对虚拟网卡veth pair设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
   2. Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0。另一端放在主机中,以vethb22db1b4这样类似的名字命名,并将这个网络设备加入到docker0网桥中。
   3. 从docker0子网中分配一个IP给容器使用,并设置docker0的IP地址为容器的默认网关。

宿主机和Docker容器之间是可以进行网络连接的,同样的,Docker容器和容器之间也可以直接进行网络连接。

[root@haha docker_web]# docker exec -it a52bd646da1f ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.193 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.134 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.132 ms

[root@haha docker_web]# docker exec -it 386ffc29485d ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.146 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.073 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.137 ms
容器1和容器2,都使用公用的路由器docker0。所有的容器在不指定网络情况下,都是由docker0路由的,Docker会给我们容器默认分配一个随机的可用IP地址,这些IP地址之间是可以进行网络交互的,交互模型图如下:

容器互联

在微服务部署的场景下,注册中心是使用服务名来唯一识别微服务的,而我们上线部署的时候微服务对应的IP地址可能会改动,所以我们需要使用容器名来配置容器间的网络连接。使–link可以完成这个功能。

首先不设置–link的情况下,是无法通过容器名来进行连接的。centos02容器是可以直接ping通centos01的容器ip,但是无法ping通centos01的容器name:

[root@haha ~]# docker ps
CONTAINER ID   IMAGE          COMMAND       CREATED          STATUS          PORTS     NAMES
c742968a290a   5d0da3dc9764   "/bin/bash"   9 seconds ago    Up 8 seconds              centos02
581740709b5e   5d0da3dc9764   "/bin/bash"   30 seconds ago   Up 28 seconds             centos01
[root@haha ~]#
[root@haha ~]# docker exec -it centos01 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
34: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@haha ~]#
[root@haha ~]# docker exec -it centos02 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.102 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.135 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.137 ms
[root@haha ~]#
[root@haha ~]# docker exec -it centos02 ping centos01
ping: centos01: Name or service not known

添加参数 –link,可以通过容器name进行连接。centos03容器link到centos01,所以centos03可以直接通过ping centos01的容器名去ping通,但是反过来centos01去ping centos03的容器名是ping不通的。

[root@haha ~]# docker run -d -P -it --name centos03 --link centos01 5d0da3dc9764
a66f059fe26da45e5c59af03a2c33f429452a2dcf884ad5826fa0e3985ab3862
[root@haha ~]# docker ps
CONTAINER ID   IMAGE          COMMAND       CREATED          STATUS          PORTS     NAMES
a66f059fe26d   5d0da3dc9764   "/bin/bash"   7 seconds ago    Up 5 seconds              centos03
c742968a290a   5d0da3dc9764   "/bin/bash"   11 minutes ago   Up 11 minutes             centos02
581740709b5e   5d0da3dc9764   "/bin/bash"   11 minutes ago   Up 11 minutes             centos01
[root@haha ~]#
[root@haha ~]# docker exec -it centos03 ping centos01
PING centos01 (172.17.0.2) 56(84) bytes of data.
64 bytes from centos01 (172.17.0.2): icmp_seq=1 ttl=64 time=0.111 ms
64 bytes from centos01 (172.17.0.2): icmp_seq=2 ttl=64 time=0.074 ms
64 bytes from centos01 (172.17.0.2): icmp_seq=3 ttl=64 time=0.076 ms
^C
[root@haha ~]#
[root@haha ~]# docker exec -it centos01 ping centos03
ping: centos03: Name or service not known

–link的原理就是在centos03容器的hosts文件中添加了要去link的centos01容器的容器名和ip地址映射。但是因为docker0不支持容器名访问,所以–link设置容器互连的方式也不再推荐使用了,更多地选择自定义网络。

02-docker_第125张图片

自定义网络(推荐使用)

docker network 命令及经常使用到的命令:

docker network --help

02-docker_第126张图片

如何自定义一个网络?

docker network create --help

02-docker_第127张图片

具体使用:

[root@haha ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
ed1c176a06d49da31969e229138ef5988a5fd9d2b2f2d2fc5ec1078be99a3d39
[root@haha ~]# docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
aa8a26ae1484   bridge    bridge    local
62cd016ed66a   host      host      local
ed1c176a06d4   mynet     bridge    local
fc650e2a675e   none      null      local

查看新创键的mynet详细信息:

使用自定义网络创建容器后,相同网络name下的容器,不管是通过容器IP还是容器name,都可以进行网络通信:

02-docker_第128张图片

创建两个使用相同自定义网络的容器

[root@haha ~]# docker run -d -P --name centos-net-01 --net mynet -it 5d0da3dc9764 /bin/bash
[root@haha ~]# docker run -d -P --name centos-net-02 --net mynet -it 5d0da3dc9764 /bin/bash
[root@haha ~]#
[root@haha ~]# docker ps
CONTAINER ID   IMAGE          COMMAND       CREATED          STATUS          PORTS     NAMES
2511566dd5f3   5d0da3dc9764   "/bin/bash"   4 seconds ago    Up 3 seconds              centos-net-02
924e92107f64   5d0da3dc9764   "/bin/bash"   23 seconds ago   Up 21 seconds             centos-net-01
a66f059fe26d   5d0da3dc9764   "/bin/bash"   43 minutes ago   Up 43 minutes             centos03
c742968a290a   5d0da3dc9764   "/bin/bash"   54 minutes ago   Up 54 minutes             centos02
581740709b5e   5d0da3dc9764   "/bin/bash"   55 minutes ago   Up 55 minutes             centos01

测试容器互连

[root@haha ~]# docker exec -it centos-net-01 ping centos-net-02
PING centos-net-02 (192.168.0.3) 56(84) bytes of data.
64 bytes from centos-net-02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.689 ms
64 bytes from centos-net-02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.078 ms
64 bytes from centos-net-02.mynet (192.168.0.3): icmp_seq=3 ttl=64 time=0.097 ms
^C
[root@haha ~]# docker exec -it centos-net-01 ping 192.168.0.2
PING 192.168.0.2 (192.168.0.2) 56(84) bytes of data.
64 bytes from 192.168.0.2: icmp_seq=1 ttl=64 time=0.091 ms
64 bytes from 192.168.0.2: icmp_seq=2 ttl=64 time=0.048 ms
64 bytes from 192.168.0.2: icmp_seq=3 ttl=64 time=0.094 ms
^C

网络连通

在没有使用connect命令的情况下,不同网络间的容器是无法进行网络连接的。

02-docker_第129张图片02-docker_第130张图片

不同Docker网络之间的容器想要连接的话,需要把该容器注册到另一个容器所在的网络上,使用docker connect命令。

 docker network connect mynet centos01

connect设置成功后,我们可以看到新注册进来的容器信息:02-docker_第131张图片

接下来进行容器双向连接测试:

# centos-net-01 ping centos01,连接成功
[root@haha ~]# docker exec -it centos-net-01 ping centos01
PING centos01 (192.168.0.4) 56(84) bytes of data.
64 bytes from centos01.mynet (192.168.0.4): icmp_seq=1 ttl=64 time=0.183 ms
64 bytes from centos01.mynet (192.168.0.4): icmp_seq=2 ttl=64 time=0.139 ms
64 bytes from centos01.mynet (192.168.0.4): icmp_seq=3 ttl=64 time=0.136 ms
^C
# centos01 ping centos-net-01,连接成功
[root@haha ~]# docker exec -it centos01 ping centos-net-01
PING centos-net-01 (192.168.0.2) 56(84) bytes of data.
64 bytes from centos-net-01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.066 ms
64 bytes from centos-net-01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.135 ms
64 bytes from centos-net-01.mynet (192.168.0.2): icmp_seq=3 ttl=64 time=0.141 ms
^C
[root@haha ~]# 未连接到mynet的容器还是无法访问
[root@haha ~]# docker exec -it centos-net-01 ping centos02
ping: centos02: Name or service not known

十三、 Docker-compose容器编排

1、docker-compose简述

1、是什么

docker-comppse是docker官方的开源项目,负责实现对docker容器集群的快速编排。

Compose是Docker公司推出的一个工具软件,可以管理多个Docker容器组成一个应用。你需要定义一个YAML格式的配置文件docker-compose.yml,写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些容器

2、能干嘛

例如要实现一个Web微服务项目,除了Web服务容器本身,往往还需要再加上后端的数据库mysql服务容器,redis服务器,注册中心eureka,甚至还包括负载均衡容器等等。。。。。。

Compose允许用户通过一个单独的docker-compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题

2、下载以及安装

  • 官网: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-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version

02-docker_第132张图片

3、使用compose的三个步骤

  1. 编写Dockerfile定义各个微服务应用并构建出对应的镜像文件
  2. 使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务。
  3. 最后,执行docker-compose up命令 来启动并运行整个应用程序,完成一键部署上线

4、常用命令

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、案例

  1. 修改配置文件
# spring.datasource.url=jdbc:mysql://192.168.227.100:3306
spring.datasource.url=jdbc:mysql://mysql:3306
# spring.redis.host=192.168.227.100
spring.redis.host=redis
  1. 创建docker.compose.yml文件

这看起来是一个YAML文件的一部分,用于定义容器编排工具(如Docker Compose)中的服务之间的依赖关系。根据这段代码,服务"redis"和"mysql"被列为其他服务的依赖项。这意味着在启动需要这两个依赖项的服务之前,必须先启动"redis"和"mysql"服务。这种依赖性确保了服务的正确顺序启动。

version: "3"
 
services:
  microService:
    image: zzyy_docker:1.6
    container_name: ms01
    ports:
      - "6001:6001"
    volumes:
      - /app/microService:/data
    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:
       - "3306: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 #解决外部无法访问
 
networks: 
   atguigu_net: 

02-docker_第133张图片

十四、面试题

镜像容器瘦身

  1. 减少镜像分层(多个run合并)

  2. 清理无用数据

  3. 分段构建

  4. 使用最小的基础镜像

  5. 合并多个命令:将多个运行在不同层中的命令合并为一个单独的命令。例如,将多个软件包的安装命令合并为一个 RUN 指令。这样可以减少分层的数量,并减小最终镜像的大小。

  6. 使用多阶段构建:如之前所提到的,使用多阶段构建可以通过只保留必要的文件和依赖项来减少最终镜像的大小。在最后一个构建阶段中,只复制需要运行应用程序所需的文件,而不包含构建工具和中间文件。

  7. 清理不需要的文件:在构建过程中删除不需要的文件和临时文件。例如,在完成某个操作后,可以使用 RUN 指令删除不再需要的临时文件,从而避免将它们包含在最终镜像中。

  8. 使用更小的基础镜像:选择更小的基础镜像作为构建的起点,这可以减小最终镜像的初始大小。可以考虑使用专门针对特定语言或应用程序的精简基础镜像,比如Alpine Linux等。

  9. 压缩镜像:使用压缩算法对镜像进行压缩,可以减小镜像的大小。在构建过程中,可以使用 --squash 参数来对镜像进行压缩,或者使用工具如Docker的 docker-squash 来压缩已构建的镜像。

聊聊:docker是怎么工作的?

实际上docker使用了常见的CS架构,也就是client-server模式,docker client负责处理用户输入的各种命令,比如docker build、docker run,真正工作的其实是server,也就是docker demon,值得注意的是,docker client和docker demon可以运行在同一台机器上。

Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,守护进程从客户端接受命令并管理运行在主机上的容器。守护进程和客户端可以运行在同一台机器上。

聊聊:docker架构

02-docker_第134张图片
  • distribution 负责与docker registry交互,上传洗澡镜像以及v2 registry 有关的源数据
  • registry负责docker registry有关的身份认证、镜像查找、镜像验证以及管理registry mirror等交互操作
  • image 负责与镜像源数据有关的存储、查找,镜像层的索引、查找以及镜像tar包有关的导入、导出操作
  • reference负责存储本地所有镜像的repository和tag名,并维护与镜像id之间的映射关系
  • layer模块负责与镜像层和容器层源数据有关的增删改查,并负责将镜像层的增删改查映射到实际存储镜像层文件的graphdriver模块
  • graghdriver是所有与容器镜像相关操作的执行者

聊聊:docker的组成包含哪几大部分

一个完整的docker有以下几个部分组成:
1、docker client(客户端),为用户提供一系列可执行命令。
2、docker daemon(守护进程),一般在宿主主机后台运行,等待接收来自客户端的请求消息;
3、docker image(镜像),镜像run之后就生成为docker容器;
4、docker container(容器),一个系统级别的服务,拥有自己的ip和系统目录结构;运行容器前需要本地存在对应的镜像,如果本地不存在该镜像则就去镜像仓库下载。

docker 使用客户端-服务器 (C/S) 架构模式,使用远程api来管理和创建docker容器。docker 容器通过 docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类。

聊聊:docker技术的三大核心概念是什么?

镜像

镜像是一种轻量级、可执行的独立软件包,相当于一个文件的快照,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等)。
容器

容器是基于镜像创建的,是镜像运行起来之后的一个实例,容器才是真正运行业务程序的地方。如果把镜像比作程序里面的类,那么容器就是对象。

镜像仓库

存放镜像的地方,研发工程师打包好镜像之后需要把镜像上传到镜像仓库中去,然后就可以运行有仓库权限的人拉取镜像来运行容器了。

聊聊:Docker 安全么?

Docker 利用了 Linux 内核中很多安全特性来保证不同容器之间的隔离,并且通过签名机制来对镜像进行验证。大量生产环境的部署证明,Docker 虽然隔离性无法与虚拟机相比,但仍然具有极高的安全性。

聊聊:Docker 与 虚拟机 有何不同?

  • 资源利用率:虚拟机运行在宿主机上,每个虚拟机都需要独立的操作系统、内核和设备模拟器等,因此会占用较多的系统资源。而Docker容器共享宿主机的操作系统内核,容器之间只需加载应用程序及其依赖的库,资源利用率更高。
  • 启动时间:虚拟机需要启动完整的操作系统,包括启动内核并加载所有相关的服务,因此启动时间相对较长。而Docker容器运行于宿主机的操作系统之上,只需加载应用程序和相关依赖,因此启动速度更快。
  • 隔离性:与虚拟机相比,Docker隔离性更弱,Docker属于进程之间的隔离,虚拟机可实现系统级别隔离;
  • 镜像大小:由于虚拟机需要完整的操作系统,其镜像通常较大。而Docker容器使用分层存储的方式,容器镜像只包含应用程序及其依赖,因此镜像相对较小。
02-docker_第135张图片
特性 Docker 虚拟机
启动速度 秒级 分钟级
交付/部署 开发、测试、生产环境一致 无成熟体系
性能 近似物理机 性能损耗大
体量 极小(MB) 较大(GB)
迁移/扩展 跨平台,可复制 较为复杂

聊聊:Docker与LXC ( Linux Container)有何不同?

LXC利用Linux上相关技术实现了容器支持; Docker早期版本中使用了LXC技术,后期演化为新的 libcontainer, 在如下的几个方面进行了改进:

移植性: 通过抽象容器配置, 容器可以实现从一个平台移植到另一个平台;
镜像系统: 基于AUFS的镜像系统为容器的分发带来了很多的便利, 同时共同的镜像层只需要存储一份,实现高效率的存储;
版本管理: 类似于Git的版本管理理念, 用户可以更方便地创建、 管理镜像文件;
仓库系统: 仓库系统大大降低了镜像的分发和管理的成本;
周边工具: 各种现有工具(配置管理、 云平台)对Docker的支持, 以及基于Docker 的PaaS、CI等系统, 让Docker的应用更加方便和多样化。

  1. 架构:Docker是基于容器引擎的轻量级虚拟化技术,它使用了Linux内核提供的cgroups和namespace特性。而LXC是一种系统级虚拟化技术,它在Linux操作系统上创建和管理容器。
  2. 镜像管理:Docker使用镜像来构建和运行容器。Docker镜像是分层的,并且可以通过在现有镜像上进行修改和添加新层来构建自定义镜像。LXC使用模板来创建容器,每个容器都有一个根文件系统。
  3. 管理工具:Docker提供了更高级的管理工具,可以轻松创建、推送和拉取镜像,以及管理容器的生命周期。Docker还有一个中央仓库(Docker Hub),可以方便地分享和获取镜像。LXC则主要通过命令行工具来管理容器,相对较为底层。
  4. 安全性:由于Docker采用了分层镜像和隔离机制,它在安全性方面更加强大。每个Docker容器都有自己的隔离环境,并且可以限制容器的资源使用。LXC也提供了隔离机制,但相对于Docker来说,它的隔离性和安全性较弱。

总体而言,Docker更加易用和便捷,适用于快速部署和扩展应用。LXC相对更加底层,适用于需要更细粒度控制和定制化的场景。选择使用哪种容器化技术,取决于具体的需求和使用情境。

聊聊:什么是 Docker 镜像?

Docker 镜像是 Docker 容器的源代码,Docker 镜像用于创建容器。使用build 命令创建镜像。

聊聊:什么是 Docker 容器?

Docker 容器包括应用程序及其所有依赖项,作为操作系统的独立进程运行。

聊聊:Docker 容器有几种状态?(创运暂中山)

  1. 创建(Created):当使用docker create命令创建容器时,容器进入创建状态。在这个状态下,容器已经被创建,但尚未启动。
  2. 运行(Running):通过docker start命令或者docker run命令启动容器后,容器进入运行状态。在这个状态下,容器中的应用程序正在运行,并且可以与其进行交互。
  3. 暂停(Paused):通过docker pause命令可以将运行中的容器暂停。在这个状态下,容器的所有进程都会被暂停,但容器的状态、网络和存储等资源仍然保留。可以使用docker unpause命令来恢复容器。
  4. 终止(Exited):当容器中的应用程序退出或者手动使用docker stop命令停止容器时,容器进入终止状态。在这个状态下,容器不再运行,但容器的文件系统和其他资源仍然存在。
  5. 删除(Removed):通过docker rm命令可以删除终止状态的容器。在这个状态下,容器的文件系统和其他资源被彻底删除,无法再恢复。

聊聊:Dockerfile 中的命令 COPY 和 ADD 命令有什么区别?

  1. 功能不同COPY指令用于将本地文件或目录复制到容器中,而ADD指令除了实现COPY的功能外,还支持自动解压缩压缩文件、远程文件下载以及添加URL资源等。
  2. 自动解压缩:相对于COPY指令,ADD指令具有自动解压缩的功能。当复制的文件是压缩文件(例如.tar、.gzip、.bz2等)时,ADD指令会自动解压文件并将其添加到镜像中。

解释一下 Dockerfile 的 ONBUILD 指令?

  • ONBUILD 指令是一个特殊的指令,用于定义一个触发器。当前镜像被用作其他镜像的基础镜像时,这个触发器就会被触发,执行指定的命令。常见的用法是在一个基础镜像上定义 ONBUILD 指令,以便让派生镜像在构建时执行一些操作。

聊聊:docker常用命令?

docker pull 拉取或者更新指定镜像
docker push 将镜像推送至远程仓库
docker rm 删除容器
docker rmi 删除镜像
docker images 列出所有镜像
docker ps 列出所有容器

1、如何列出可运行的容器?

docker ps

2、启动nginx容器(随机端口映射),并挂载本地文件目录到容器html的命令是?

docker run -d -P --name nginx2 -v /home/nginx:/usr/share/nginx/html nginx

3、进入容器的方法有哪些?

1、使用 docker attach 命令
2、使用 exec 命令,例如docker exec -i -t 784fd3b294d7 /bin/bash

4、容器与主机之间的数据拷贝命令是?

docker cp 是一个用于将文件或目录复制到运行中的 Docker 容器和从容器中复制文件或目录到本地主机的命令。

docker cp 命令用于容器与主机之间的数据拷贝

  1. 将本地文件复制到容器中:

    docker cp <本地文件路径> <容器名称或ID>:<容器内目标路径>
    
  2. 将容器中的文件复制到本地:

    docker cp <容器名称或ID>:<容器内文件路径> <本地目标路径>
    

5、当启动容器的时候提示:exec format error?如何解决问题

检查启动命令是否有可执行权限,进入容器手工运行脚本进行排查。

6、本地的镜像文件都存放在哪里?

与 Docker 相关的本地资源都存放在/var/lib/docker/目录下,其中 container 目录存放容器信息,graph 目录存放镜像信息,aufs 目录下存放具体的内容文件。

7、如3何退出一个镜像的 bash,而不终止它?

按 Ctrl-p Ctrl-q。

8、退出容器时候自动删除?

使用 –rm 选项,例如 sudo docker run –rm -it ubuntu

9、如何批量清理虚悬镜像文件?

可以使用 docker rmi $(docker images -qf "dangling=true")命令

  • -q:仅显示镜像的 ID,而不显示其他信息。
  • -f:根据指定的过滤条件来筛选镜像。

删除容器文件: docker rm -f $(docker ps -aq)

10、如何查看镜像支持的环境变量?

使用 sudo docker run IMAGE env

11、本地的镜像文件都存放在哪里

于 Docker 相关的本地资源存放在/var/lib/docker/目录下,其中 container 目录
存放容器信息,graph 目录存放镜像信息,aufs 目录下存放具体的镜像底层文件。

12、容器退出后,通过 docker ps 命令查看不到,数据会丢失么?

容器退出后会处于终止(exited)状态,此时可以通过 docker ps -a 查看,其中数据不会丢失,还可以通过 docker start 来启动,只有删除容器才会清除数据。

13、如何停止所有正在运行的容器?

使用 docker kill $(sudo docker ps -q)

14、如何清理批量后台停止的容器?

答:使用 docker rm $(sudo docker ps -a -q)

  1. -a 选项表示显示所有的容器(包括运行中和停止的),而不仅仅是正在运行的容器。默认情况下,docker ps 只会显示正在运行的容器。
  2. -q 选项表示只显示容器的 ID,而不显示其他列的信息。这在某些场景下可以方便地获取容器的 ID,以供后续操作使用。

15、如何临时退出一个正在交互的容器的终端,而不终止它?

按 Ctrl+p,后按 Ctrl+q,如果按 Ctrl+c 会使容器内的应用进程终止,进而会使容器终止。

说说: Dockerfile的基本指令有哪些?

FROM 指定基础镜像(必须为第一个指令,因为需要指定使用哪个基础镜像来构建镜像);
MAINTAINER 设置镜像作者相关信息,如作者名字,日期,邮件,联系方式等;
COPY 复制文件到镜像;
ADD 复制文件到镜像(ADD与COPY的区别在于,ADD会自动解压tar、zip、tgz、xz等归档文件,而COPY不会,同时ADD指令还可以接一个url下载文件地址,一般建议使用COPY复制文件即可,文件在宿主机上是什么样子复制到镜像里面就是什么样子这样比较好);
ENV 设置环境变量;
EXPOSE 暴露容器进程的端口,仅仅是提示别人容器使用的哪个端口,没有过多作用;
VOLUME 数据卷持久化,挂载一个目录;
WORKDIR 设置工作目录,如果目录不在,则会自动创建目录;
RUN 在容器中运行命令,RUN指令会创建新的镜像层,RUN指令经常被用于安装软件包;
CMD 指定容器启动时默认运行哪些命令,如果有多个CMD,则只有最后一个生效,另外,CMD指令可以被docker run之后的参数替换;
ENTRYPOINT 指定容器启动时运行哪些命令,如果有多个ENTRYOINT,则只有最后一个生效,另外,如果Dockerfile中同时存在CMD和ENTRYOINT,那么CMD或docker run之后的参数将被当做参数传递给ENTRYOINT;

说说: 如何进入容器?使用哪个命令
进入容器有两种方法:docker attach、docker exec;
docker attach命令是attach到容器启动命令的终端,docker exec 是另外在容器里面启动一个TTY终端。

docker run -d centos /bin/bash -c "while true;do sleep 2;echo I_am_a_container;done"
3274412d88ca4f1d1292f6d28d46f39c14c733da5a4085c11c6a854d30d1cde0
docker attach 3274412d88ca4f						#attach进入容器
Ctrl + c  退出,Ctrl + c会直接关闭容器终端,这样容器没有进程一直在前台运行就会死掉了
Ctrl + pq 退出(不会关闭容器终端停止容器,仅退出)

docker exec -it 3274412d88ca /bin/bash				#exec进入容器	
[root@3274412d88ca /]# ps -ef						#进入到容器了开启了一个bash进程
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 05:31 ?        00:00:01 /bin/bash -c while true;do sleep 2;echo I_am_a_container;done
root        306      0  1 05:41 pts/0    00:00:00 /bin/bash
root        322      1  0 05:41 ?        00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 2
root        323    306  0 05:41 pts/0    00:00:00 ps -ef
[root@3274412d88ca /]#exit							#退出容器,仅退出我们自己的bash窗口


小结:

attach是直接进入容器启动命令的终端,不会启动新的进程;

exec则是在容器里面打开新的终端,会启动新的进程;一般建议已经exec进入容器。

聊聊:什么是 Docker Swarm?

Docker Swarm 和 Kubernetes(通常简称为 K8s)都是用于容器编排和集群管理的工具,它们有一些相似之处,但也存在一些区别。

相似之处

  1. 容器编排:Docker Swarm 和 Kubernetes 都可以帮助您在多个主机上自动编排和管理容器。它们提供了服务调度、负载均衡、自动伸缩等功能,以便高效地运行和管理容器化应用程序。
  2. 高可用性:两者都支持容器故障恢复和自动重新部署,确保应用程序的高可用性。
  3. 扩展性:Docker Swarm 和 Kubernetes 都支持水平扩展,可以根据需要添加或删除节点以应对负载变化。
  4. 健康检查:两者都提供健康检查机制,监测容器的状态,并在容器不健康时进行自动修复。

聊聊:容器内部机制?

每个容器都在自己的命名空间中运行,但使用与所有其他容器完全相同的内核。发生隔离是因为内核知道分配给进程的命名空间,并且在API调用期间确保进程只能访问其自己的命名空间中的资源。

命名空间(namespaces)是 Linux 为我们提供的用于分离进程树、网络接口、挂载点以及进程间通信等资源的方法。

聊聊:什么是Docker Hub?

Docker hub是一个基于云的注册表服务,允许您链接到代码存储库,构建镜像并测试它们,存储手动推送的镜像以及指向Docker云的链接,以便您可以将镜像部署到主机。它为整个开发流程中的容器镜像发现,分发和变更管理,用户和团队协作以及工作流自动化提供了集中资源。

聊聊:镜像与 UnionFS

  • 镜像:镜像是一个轻量、可移植的软件包,相当于一个文件的快照,我们将应用程序和配置所需的依赖打包成一个可交付的运行环境。

  • UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下

    Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

聊聊:docker容器之间怎么隔离?

  • docker容器主要通过namespace控制组完成的
  • 命名空间 (Namespaces):Docker 使用不同的命名空间来隔离各个方面的容器,如进程、网络、文件系统等。每个容器都有自己的命名空间,防止与其他容器中的进程、网络接口或文件系统发生冲突。
  • 控制组 (Control Groups):控制组是一种资源管理机制,用于限制容器所使用的系统资源,如 CPU、内存、磁盘等。通过控制组,可以确保每个容器只能使用分配给它的资源,防止容器之间的资源争用。
  • 前者主要解决的了资源隔离的问题,后者解决的资源管理的问题

聊聊:仓库( Repository)、 注册服务器( Registry)、 注册索引( Index)有何关系?

  • 仓库是存放一组关联镜像的集合,比如同一个应用的不同版本的镜像。

  • 注册服务器是存放实际的镜像文件的地方。

  • 注册索引则负责维护用户的账号、权限、搜索、标签等的管理。因此,注册服务器利用注册索引来实现认证等管理。

聊聊:如何在生产中监控 Docker?

Docker 提供 docker stats 和 docker 事件等工具来监控生产中的 Docker。

我们可以使用这些命令获取重要统计数据的报告。

Docker 统计数据:当我们使用容器 ID 调用 docker stats 时,我们获得容器的CPU,内存使用情况等。它类似于 Linux 中的 top 命令。

Docker 事件:Docker 事件是一个命令,用于查看 Docker 守护程序中正在进行的活动流。 一些常见的 Docker 事件:attach,commit,die,detach,rename,destroy 等。我们还可以使用各种选项来限制或过滤我们感兴趣的事件。

聊聊:如何在多个环境中使用Docker?

可以进行以下更改:

删除应用程序代码的任何卷绑定,以便代码保留在容器内,不能从外部更改绑定到主机上的不同端口
以不同方式设置环境变量(例如,减少日志记录的详细程度,或启用电子邮件发送)指定重启策略(例如,重启:始终)以避免停机添加额外服务(例如,日志聚合器)
因此,您可能希望定义一个额外的Compose文件,例如production.yml,它指定适合生产的配置。此配置文件只需要包含您要从原始Compose文件中进行的更改。

聊聊:Docker能在非Linux平台(比如 macOS或 Windows)上运行么?

可以。

macOS目前需要使用 docker for mac等软件创建一个轻量级的Linux虚拟机层。 由于成熟度不高,暂时不推荐在Windows环境中使用Docker。

说说: centos镜像几个G,但是docker centos镜像才几百兆,这是为什么?

一个完整的Linux操作系统包含Linux内核和rootfs根文件系统,即我们熟悉的/dev、/proc/、/bin等目录。

我们平时看到的centOS除了rootfs,还会选装很多软件,服务,图形桌面等,所以centOS镜像有好几个G也不足为奇。
而对于容器镜像而言,所有容器都是共享宿主机的Linux 内核的,

而对于docker镜像而言,docker镜像只需要提供一个很小的rootfs根文件系统即可,只需要包含即我们熟悉的/dev、/proc/、/bin等目录,这是最基本的命令,工具,程序库即可,

所以,docker镜像才会这么小。

说说: 镜像的分层结构以及为什么要使用镜像的分层结构?

一个新的镜像其实是从 base 镜像一层一层叠加生成的。

dockerfile中使用run命令,就会在现有镜像的基础上增加一层,这样一层一层的叠加最后构成整个镜像。

所以我们docker pull拉取一个镜像的时候会看到docker是一层层拉去的。

分层机构最大的一个好处就是 : 共享资源。

比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;

同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

说说: 容器的copy-on-write特性,修改容器里面的内容会修改镜像吗?

  • 容器的 Copy-on-Write(写时复制)特性确保了对容器内部文件的修改只会影响到容器的写时层,并不会直接修改基础镜像或其他容器的镜像。
  • 当容器启动时,Docker 会创建一个新的写时层(也称为容器层),这个写时层是可写的,容器中的所有文件和目录都存在于这个层中。
  • 这种写时复制机制的好处是在多个容器之间共享基础镜像的只读部分,节省了磁盘空间,并且提高了容器启动的速度
  • 需要注意的是,如果多个容器都修改同一个文件,它们的写时层中都会存在这个修改后的文件副本,这可能会导致存储空间的浪费。另外,写时层的文件修改并不会反映到基础镜像中,所以基础镜像本身是不会被修改的。

总结来说,容器的 Copy-on-Write 特性确保了容器内部文件的修改只会影响到容器本身的写时层,并不会直接修改基础镜像或其他容器的镜像。这种机制保证了容器之间的隔离性和资源共享的效率。

在这里插入图片描述

为什么docker镜像要采用这种分层结构呢?

最大的一个好处是:共享资源

比如:有多个镜像都从相同的base镜像构建而来,那么docker host只需在磁盘上保存一份base镜像: 同时内存中也只需加载一份base镜像,就可以为所有容器服务了,而其镜像的每一层都可以被共享

问题是: 如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如/etc下的文件,这时其他容器的/etc是否也会被修改??? 答案是不会

修改会被限制在单个容器内, 这就是 容器copy-on-write特性

所有对容器的改动 - 无论添加、删除、还是修改文件,都只会发生在容器层中,

因为只有容器层是可写的,容器层下面的所有镜像层都是只读的。

镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。

如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。

在容器层中,用户看到的是一个叠加之后的文件系统。

添加文件时:在容器中创建文件时,新文件被添加到容器层中。
读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
删除文件:在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。

只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。

说说: Dockerfile的整个构建镜像过程

1、首先,创建一个目录用于存放应用程序以及构建过程中使用到的各个文件等;

2、然后,在这个目录下创建一个Dockerfile文件,一般建议Dockerfile的文件名就是Dockerfile;

3、编写Dockerfile文件,编写指令,如,使用FORM指令指定基础镜像,COPY指令复制文件,RUN指令指定要运行的命令,ENV设置环境变量,EXPOSE指定容器要暴露的端口,WORKDIR设置当前工作目录,CMD容器启动时运行命令,等等指令构建镜像;

4、Dockerfile编写完成就可以构建镜像了,使用docker build -t 镜像名:tag . 命令来构建镜像,最后一个点是表示当前目录,docker会默认寻找当前目录下的Dockerfile文件来构建镜像,如果不使用默认,可以使用-f参数来指定dockerfile文件,如:docker build -t 镜像名:tag -f /xx/xxx/Dockerfile ;

5、使用docker build命令构建之后,docker就会将当前目录下所有的文件发送给docker daemon,顺序执行Dockerfile文件里的指令,在这过程中会生成临时容器,在临时容器里面安装RUN指定的命令,安装成功后,docker底层会使用类似于docker commit命令来将容器保存为镜像,然后删除临时容器,以此类推,一层层的构建镜像,运行临时容器安装软件,直到最后的镜像构建成功。

说说: Dockerfile构建镜像出现异常,如何排查?

首先,Dockerfile是一层一层的构建镜像,期间会产生一个或多个临时容器,构建过程中其实就是在临时容器里面安装应用,如果因为临时容器安装应用出现异常导致镜像构建失败,这时容器虽然被清理掉了,但是期间构建的中间镜像还在,那么我们可以根据异常时上一层已经构建好的临时镜像,将临时镜像运行为容器,然后在容器里面运行安装命令来定位具体的异常。
,您可能希望定义一个额外的Compose文件,例如production.yml,它指定适合生产的配置。此配置文件只需要包含您要从原始Compose文件中进行的更改。

聊聊:Docker能在非Linux平台(比如 macOS或 Windows)上运行么?

可以。

macOS目前需要使用 docker for mac等软件创建一个轻量级的Linux虚拟机层。 由于成熟度不高,暂时不推荐在Windows环境中使用Docker。

说说: centos镜像几个G,但是docker centos镜像才几百兆,这是为什么?

一个完整的Linux操作系统包含Linux内核和rootfs根文件系统,即我们熟悉的/dev、/proc/、/bin等目录。

我们平时看到的centOS除了rootfs,还会选装很多软件,服务,图形桌面等,所以centOS镜像有好几个G也不足为奇。
而对于容器镜像而言,所有容器都是共享宿主机的Linux 内核的,

而对于docker镜像而言,docker镜像只需要提供一个很小的rootfs根文件系统即可,只需要包含即我们熟悉的/dev、/proc/、/bin等目录,这是最基本的命令,工具,程序库即可,

所以,docker镜像才会这么小。

说说: 镜像的分层结构以及为什么要使用镜像的分层结构?

一个新的镜像其实是从 base 镜像一层一层叠加生成的。

dockerfile中使用run命令,就会在现有镜像的基础上增加一层,这样一层一层的叠加最后构成整个镜像。

所以我们docker pull拉取一个镜像的时候会看到docker是一层层拉去的。

分层机构最大的一个好处就是 : 共享资源。

比如:有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;

同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

说说: 容器的copy-on-write特性,修改容器里面的内容会修改镜像吗?

  • 容器的 Copy-on-Write(写时复制)特性确保了对容器内部文件的修改只会影响到容器的写时层,并不会直接修改基础镜像或其他容器的镜像。
  • 当容器启动时,Docker 会创建一个新的写时层(也称为容器层),这个写时层是可写的,容器中的所有文件和目录都存在于这个层中。
  • 这种写时复制机制的好处是在多个容器之间共享基础镜像的只读部分,节省了磁盘空间,并且提高了容器启动的速度
  • 需要注意的是,如果多个容器都修改同一个文件,它们的写时层中都会存在这个修改后的文件副本,这可能会导致存储空间的浪费。另外,写时层的文件修改并不会反映到基础镜像中,所以基础镜像本身是不会被修改的。

总结来说,容器的 Copy-on-Write 特性确保了容器内部文件的修改只会影响到容器本身的写时层,并不会直接修改基础镜像或其他容器的镜像。这种机制保证了容器之间的隔离性和资源共享的效率。

在这里插入图片描述

为什么docker镜像要采用这种分层结构呢?

最大的一个好处是:共享资源

比如:有多个镜像都从相同的base镜像构建而来,那么docker host只需在磁盘上保存一份base镜像: 同时内存中也只需加载一份base镜像,就可以为所有容器服务了,而其镜像的每一层都可以被共享

问题是: 如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如/etc下的文件,这时其他容器的/etc是否也会被修改??? 答案是不会

修改会被限制在单个容器内, 这就是 容器copy-on-write特性

所有对容器的改动 - 无论添加、删除、还是修改文件,都只会发生在容器层中,

因为只有容器层是可写的,容器层下面的所有镜像层都是只读的。

镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。

如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。

在容器层中,用户看到的是一个叠加之后的文件系统。

添加文件时:在容器中创建文件时,新文件被添加到容器层中。
读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
删除文件:在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。

只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。

说说: Dockerfile的整个构建镜像过程

1、首先,创建一个目录用于存放应用程序以及构建过程中使用到的各个文件等;

2、然后,在这个目录下创建一个Dockerfile文件,一般建议Dockerfile的文件名就是Dockerfile;

3、编写Dockerfile文件,编写指令,如,使用FORM指令指定基础镜像,COPY指令复制文件,RUN指令指定要运行的命令,ENV设置环境变量,EXPOSE指定容器要暴露的端口,WORKDIR设置当前工作目录,CMD容器启动时运行命令,等等指令构建镜像;

4、Dockerfile编写完成就可以构建镜像了,使用docker build -t 镜像名:tag . 命令来构建镜像,最后一个点是表示当前目录,docker会默认寻找当前目录下的Dockerfile文件来构建镜像,如果不使用默认,可以使用-f参数来指定dockerfile文件,如:docker build -t 镜像名:tag -f /xx/xxx/Dockerfile ;

5、使用docker build命令构建之后,docker就会将当前目录下所有的文件发送给docker daemon,顺序执行Dockerfile文件里的指令,在这过程中会生成临时容器,在临时容器里面安装RUN指定的命令,安装成功后,docker底层会使用类似于docker commit命令来将容器保存为镜像,然后删除临时容器,以此类推,一层层的构建镜像,运行临时容器安装软件,直到最后的镜像构建成功。

说说: Dockerfile构建镜像出现异常,如何排查?

首先,Dockerfile是一层一层的构建镜像,期间会产生一个或多个临时容器,构建过程中其实就是在临时容器里面安装应用,如果因为临时容器安装应用出现异常导致镜像构建失败,这时容器虽然被清理掉了,但是期间构建的中间镜像还在,那么我们可以根据异常时上一层已经构建好的临时镜像,将临时镜像运行为容器,然后在容器里面运行安装命令来定位具体的异常。

你可能感兴趣的:(云计算,docker,linux,云计算,计算机网络,java,mysql)