- 1 Docker 概述
- 2 Docker 安装
- 3 Docker的常用命令
- ES暴露的端口很多
- ES十分的消耗内存
- ES的数据一般需要放置到安全目录!挂载
- 5 容器数据卷">5 容器数据卷
- 6 Dockerfile">6 Dockerfile
- 表示注释
- 7 发布自己的镜像
- 在阿里云个人的镜像仓库就可以查看到 tomcat 的镜像版本。
- 8 Docker 网络">8 Docker 网络
- veth-pair 就是一对虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连
- 正因为有这个特性,veth-pair 充当了一个桥接,链接各种虚拟网络设备
- OpenStack Docker容器之间连接 OVS 连接 都是使用 veth-pair 技术。
- 9 将SpringBoot 微服务打包成Docker镜像
Video Reference:Docker最新超详细版教程通俗易懂
学前准备
- Linux 基本操作
- 计算机网络基础
Docker 的学习
- Docker 概述
- Docker 安装
- Docker 命令
- 镜像命令
- 容器命令
- 仓库命令
- 操作命令
- ……
- Docker 镜像
- 容器的数据卷
- Dockerfile
- Docker 网络
- IDEA 整合 Docker
- Docker Compose
- Docker Swarm ( 简化版的 Docker )
- CICD( Jenkins )
授人以鱼不如授人以渔(学习思想!)即使再小的帆也能远航。
1 Docker 概述
1.1 Docker 为什么会出现?
一款产品:开发 - 上线,三套环境(开发,测试,生产),应用配置
开发人员,运维人员。问题:我在我的电脑上可以运行的!版本更新,导致服务不可用,对于运维来说,考验就十分大了?开发即运维!
环境配置是十分的麻烦,每一个机器都是要部署环境(集群 Redis,ES,Hadoop……)!费时费力
发布一个项目的 Jar (Redis,MySQL,JDK,ES)包。项目能不能带上环境安装打包!
之前在服务器配置一个应用的环境Redis MySQL jdk ES Hadoop,配置超麻烦了,不能够跨平台。Windows 最后分布到Linux!
- 传统:开发 jar,运维来做!
- 现在:开发打包部署上线,一套流程做完!
Java —- Apk —- 分布(应用商店)—- 同学使用 Apk —- 安装即可用!
Java —- Jar(环境) —- 打包项目带上环境(镜像)—-( Docker仓库:商店)—- 下载我们发布的就像 —- 直接运行即可!
Docker 给以上的问题,提出了解决方案!
一款产品从开发到上线,从操作系统,到运行环境,再到应用配置。作为开发+运维之间的协作我们需要关心很多东西,这也是很多互联网公司都不得不面对的问题,特别是各种版本的迭代之后,不同版本环境的兼容,对运维人员是极大的考验!
环境配置如此麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。解决开发人员说的”在我的机器上可正常工作”的问题。
之前在服务器配置一个应用的运行环境,要安装各种软件,就拿一个基本的工程项目的环境来说吧,Java/Tomcat/MySQLJDBC驱动包等。安装和配置这些东西有多麻烦就不说了,它还不能跨平台。假如我们是在Windows上安装的这些环境,到了Linux又得重新装。况且就算不跨操作系统,换另一台同样操作系统的服务器,要移植应用也是非常麻烦的。
传统上认为,软件编码开发/恻试结束后,所产出的成果即是程序或是能够编译执行的二进制字节码文件等(Java为例)。而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,开发需要清楚的告诉运维部署团队用的全部配置文件+所有软件环境。不过,即便如此,仍然常常发生部署失败的状况。
Docker之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案。
Docker镜像的设计,使得Docker得以打破过去「程序即应用」的观念。通过Docker镜像 ( images )将应用程序所需要的系统环境,由下而上打包,达到应用程序跨平台间的无缝接轨运作。

Docker 的思想就来自于集装箱!
JRE —- 多个应用(端口冲突) —- 原来都是交叉的!
隔离:Docker 核心思想!打包装箱!每个箱子都是相互隔离的。Docker 通过隔离机制,可以将服务器的性能利用到极致
本质:所有的技术都是因为出现了一些问题,我们需要去解决,才去学习!
1.2 Docker 的历史
2010年,几个搞IT的年轻人,在美国旧金山成立了一家名叫“dotCloud”的公司。
这家公司主要提供基于PaaS的云计算技术服务。具体来说,是和LXC有关的容器技术。后来,dotCloud公司将自己的容器技术进行了简化和标准化,并命名为——Docker。
Docker技术诞生之后,并没有引起行业的关注。而dotCloud公司,作为一家小型创业企业,在激烈的竞争之下,也步履维艰。正当他们快要坚持不下去的时候,脑子里蹦出了“开源”的想法。
什么是开源?开源,就是开放源代码。也就是将原来内部保密的程序源代码开放给所有人,然后让大家一起参与进来,贡献代码和意见。
有的软件是一开始就开源的。也有的软件,是混不下去,创造者又不想放弃,所以选择开源。自己养不活,就吃”百家饭”嘛。2013年3月,dotCloud公司的创始人之一,Docker之父,28岁的Solomon Hykes正式决定,将Docker项目开源。
不开则已,一开惊人。
越来越多的IT工程师发现了Docker的优点,然后蜂拥而至,加入Docker开源社区。Docker的人气迅速攀升,速度之快,令人瞠目结舌。
开源当月,Docker 0.1版本发布。此后的每一个月,Docker都会发布一个版本。到2014年6月9日,Docker 1.0版本正式发布
此时的Docker,已经成为行业里人气最火爆的开源技术,没有之一。甚至像Google、微软、Amazon、VMware这样的巨头,都对它青睐有加,表示将全力支持。I
Docker和容器技术为什么会这么火爆?说白了,就是因为它“轻”。
在容器技术之前,业界的网红是虚拟机。虚拟机技术的代表,是VMWare和Openstack。
相信很多人都用过虚拟机。虚拟机,就是在你的操作系统里面,装一个软件,然后通过这个软件,再模拟一台甚至多台”子电脑”出来。在”子电脑’里,你可以和正常电脑一样运行程序,例如开QQ。如果你愿意,你可以变出好几个”子电脑”,里面都开上QQ。”子电脑”和“子电脑”之间,是相互隔离的,互不影响。
虚拟机属于虚拟化技术。而Docker这样的容器技术,也是虚拟化技术,属于轻量级的虚拟化。
虚拟机虽然可以隔离出很多”子电脑”,但占用空间更大,启动更慢,虚拟机软件可能还要花钱(例如VMWare)。而容器技术恰好没有这些缺点。它不需要虚拟出整个操作系统,只需要虚拟一个小规模的环境(类似”沙箱”)。
它启动时间很快,几秒钟就能完成。而且,它对资源的利用率很高(一台主机可以同时运行几千个Docker容器)。此外,它占的空间很小,虚拟机一般要几GB到几十GB的空间,而容器只需要MB级甚至KB级。
正因为如此,容器技术受到了热烈的欢迎和追捧,发展迅速。
:::color1 VMware:Linux CentOS原生镜像(一台电脑!)隔离:需要开启多个虚拟机!—> nG 几分钟
Docker:隔离,镜像(最核心的环境 4M + JDK + MySQL)十分的小巧,运行镜像就可以了!小巧!—> nM/KB 秒级启动
:::
到现在,所有的开发人员和运维人员必须都要会 Docker!
Docker 三要素:容器,镜像,仓库
聊聊 Docker
Docker 是基于 Golang 语言开发的!开源项目!
文档地址:https://docs.docker.com/ (Docker 的文档是非常详细的!)
1.3 Docker 能干嘛
虚拟化技术分全虚拟、半虚拟、容器技术三大类
:::color1 传统的虚拟化技术
虚拟机(virtual machine)就是带环境安装的一种解决方案。
它可以在一种操作系统里面运行另一种操作系统,比如在Windows系统里面运行Linux系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够使应用程序,操作系统和硬件三者之间的逻辑不变。
:::

虚拟机技术缺点:
- 资源占用十分多
- 冗余步骤多
- 启动速度慢
:::color1 容器化技术
由于前面虚拟机存在这些缺点,Linux发展出了另一种虚拟化技术: Linux容器(Linux Containers,缩写为LXC).
Linux容器不是模拟一个完整的操作系统,而是对进程进行隔离。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能妊终如一地运行。
容器化技术不是模拟的一个完整的操作系统。
:::


比较Docker 和虚拟机技术的不同:
- ·传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程
- 而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
- 每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。
:::color1 开发/运维(DevOps)
更快速的应用交付和部署:
传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
更便捷的升级和扩缩容:
随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块”积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级。
更简单的系统运维:
应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。
更高效的计算资源利用:
Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的Hypervisor [管理程序]支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。
:::
2 Docker 安装
2.1 Docker 的基本组成

- 镜像(Image):
Docker 镜像就好比是一个模板,可以通过这个模板来创建容器服务,Tomcat 镜像 —> run —> tomcat01容器(提供服务)。通过这个镜像可以创建多个容器(最终服务运行或者项目运行就是在容器重的)。
- 容器(Container):
Docker 利用容器技术,独立运行一个或者一个组应用,通过镜像来创建的。
启动、停止、删除、基本命令!
目前就可以把这个容器理解为就是一个简易的Linux系统项目
- 仓库(Repository):
仓库就是存放镜像的地方。仓库分为公有仓库和私有仓库!
公有仓库:Docker Hub(默认是国外的)。阿里云、网易云都有容器服务器(配置镜像加速!)
2.2 安装 Docker
环境准备
- 需要会一部分Linux的基础
- CentOS 7
- 我们可以使用远程连接工具连接远程服务器进行操作!
环境查看
#系统内核是 3.10 以上的$ uname -r3.10.0-1160.el7.x86_64
#系统版本$ cat /etc/os-releaseNAME="CentOS Linux"VERSION="7 (Core)"ID="centos"ID_LIKE="rhel fedora"VERSION_ID="7"PRETTY_NAME="CentOS Linux 7 (Core)"ANSI_COLOR="0;31"CPE_NAME="cpe:/o:centos:centos:7"HOME_URL="https://www.centos.org/"BUG_REPORT_URL="https://bugs.centos.org/"CENTOS_MANTISBT_PROJECT="CentOS-7"CENTOS_MANTISBT_PROJECT_VERSION="7"REDHAT_SUPPORT_PRODUCT="centos"REDHAT_SUPPORT_PRODUCT_VERSION="7"
安装Docker
帮助文档:
#1.卸载旧的版本$ sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine#2.需要的安装$ sudo yum install -y yum-utils#3.设置镜像的仓库$ sudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo #默认是国外的$ sudo yum-config-manager \--add-repo \https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo #推荐使用阿里云的,速度快#4.更新yum软件包的索引$ yum makecache fast#5.安装Docker相关的 Docker-ce社区版,ee则是企业版$ sudo yum install -y \docker-ce docker-ce-cli containerd.io docker-compose-plugin#6.启动Docker$ systemctl start docker#7.查看Docker的信息$ docker version#判断Docker是否安装成功#8.运行helloworld$ sudo docker run hello-world


#9.查看镜像是否存在hello-world镜像$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEhello-world latest feb5d9fea6a5 11 months ago 13.3kB#10.卸载Docker#卸载依赖yum remove -y docker-ce docker-ce-cli containerd.io docker-compose-plugin#删除资源sudo rm -rf /var/lib/docker#/var/lib/docker是Docker默认的工作路径#最新版sudo rm -rf /var/lib/containerd
2.3 阿里云镜像加速
- 登录阿里云找到容器服务
- 找到镜像加速地址

- 配置使用
sudo mkdir -p /etc/dockersudo tee /etc/docker/daemon.json <<-'EOF'{"registry-mirrors": ["https://po13h3y1.mirror.aliyuncs.com"]}EOFsudo systemctl daemon-reloadsudo systemctl restart docker
2.4 回顾HelloWorld流程

2.5 Docker 底层原理
2.5.1 Docker 是怎么工作的?
Docker 是一个 Client - Server 结构的系统,Docker 的守护进程运行在主机(Docker主机)上,通过 Socket 从客户端访问。
Docker Server 接受到 Docker-Client 的指令,就会执行这个命令!

Docker 为什么比 VM 快?
- Docker 有着比虚拟机更少的抽象层
- Docker 利用的是宿主机的内核,VM需要的是Guest OS

所以说,新建一个容器的时候,Docker 不需要像虚拟机一样重新加载一个操作系统内核,避免引导,虚拟机是加载 Guest OS,分钟级别的;而Docker 是利用宿主机的操作系统内核,省略了这个复杂的流程,秒级的。

3 Docker的常用命令
3.1 帮助命令
docker version #显示Docker的版本信息docker info #显示Docker的系统信息,包括镜像和容器的数量docker [ command ] --help #万能命令
帮助文档的地址:https://docs.docker.com/engine/reference/commandline/docker/
3.2 镜像命令
3.2.1 docker images
查看所有本地的本机上的镜像
$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEhello-world latest feb5d9fea6a5 11 months ago 13.3kB#解释REPOSITORY 镜像的仓库源TAG 镜像的表情IMAGE ID 镜像的ID号CREATED 镜像的创建时间SIZE 镜像的大小
| 名称,速记 | 默认 | 描述 |
|---|---|---|
| —all,-a | 显示所有图像(默认隐藏中间图像) | |
| —digests | 显示摘要 | |
| —filter,-f | 基于所提供条件的滤波器输出 | |
| —format | 使用 Go 模板打印漂亮的图像 | |
| —no-trunc | 不截断输出 | |
| —quiet,-q | 仅显示图像 ID |
3.2.2 docker search
搜索镜像
$ docker search mysqlNAME DESCRIPTION STARS OFFICIAL AUTOMATEDmysql MySQL is a widely used, open-source relation… 13053 [OK]mariadb MariaDB Server is a high performing open sou… 4995 [OK]#可选项,通过收藏进行过滤--filte=STARS=3000 #搜索出来的镜像就是 STARS 大于3000的镜像$ docker search --filter=STARS=3000 mysqlNAME DESCRIPTION STARS OFFICIAL AUTOMATEDmysql MySQL is a widely used, open-source relation… 13053 [OK]mariadb MariaDB Server is a high performing open sou… 4995 [OK]
3.2.3 docker pull
下载镜像
#下载镜像 docker pull 镜像名[:tag]$ docker pull mysqlUsing default tag: latest #如果不写Tag,默认就是 latestlatest: Pulling from library/mysql72a69066d2fe: Pull complete #分层下载,docker image的核心,联合文件系统93619dbc5b36: Pull complete99da31dd6142: Pull complete626033c43d70: Pull complete37d5d7efb64e: Pull completeac563158d721: Pull completed2ba16033dad: Pull complete688ba7d5c01a: Pull complete00e060b6d11d: Pull complete1c04857f594f: Pull complete4d7cfa90e6ea: Pull completee0431212d27d: Pull completeDigest: sha256:e9027fe4d91c0153429607251656806cc784e914937271037f7738bd5b8e7709 #签名Status: Downloaded newer image for mysql:latestdocker.io/library/mysql:latest #真实的地址$ docker pull mysql#等价于$ docker pull docker.io/library/mysql:latest#指定镜像版本下载$ docker pull mysql:5.75.7: Pulling from library/mysql72a69066d2fe: Already exists93619dbc5b36: Already exists99da31dd6142: Already exists626033c43d70: Already exists37d5d7efb64e: Already existsac563158d721: Already existsd2ba16033dad: Already exists0ceb82207cd7: Pull complete37f2405cae96: Pull completee2482e017e53: Pull complete70deed891d42: Pull completeDigest: sha256:f2ad209efe9c67104167fc609cca6973c8422939491c9345270175a300419f94Status: Downloaded newer image for mysql:5.7docker.io/library/mysql:5.7#查看镜像$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEmysql 5.7 c20987f18b13 8 months ago 448MBmysql latest 3218b38490ce 8 months ago 516MBhello-world latest feb5d9fea6a5 11 months ago 13.3kB
3.2.4 docker rmi
删除镜像
#删除指定的镜像docker rmi <Image_ID>#删除多个镜像docker rmi <Image_ID> <Image_ID> <Image_ID> <Image_ID>#删除全部的镜像docker rmi -f $(docker images -aq)
3.3 容器命令
说明:我们有了镜像才可以创建容器,Linux,下载一个CentOS 7.9.2009 镜像来测试学习
$ docker pull centos:7.9.2009
新建容器并启动
$ docker run [可选参数] image#参数说明--name="Name" 容器名字 tomcat01 tomcat02,用来区分容器-d 后台运行-it 使用交互方式运行,进入容器查看内容-p 指定容器的端口 -p 8080:8080-p ip:主机端口:容器端口-p 主机端口:容器端口(常用)-p 容器端口容器端口-P 随机分配端口#测试$ docker run -it --name centos-node01 centos:7.9.2009 /bin/bash#查看容器内的CentOS,基础环境,很多命令是不完善的[root@094cce7271cd /]# lsanaconda-post.log bin dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var#从容器中退回主机[root@094cce7271cd /]# exitexit
列出所有运行的容器
#docker ps 命令#默认是列出当前正在运行的容器-a #列出当前正在运行的容器+带出历史运行过的容器-n=? #显示最近创建的容器-q #只显示容器的编号$ docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES094cce7271cd centos:7.9.2009 "/bin/bash" 2 minutes ago Up 2 minutes centos-node01495be2b73cf1 feb5d9fea6a5 "/hello" 24 hours ago Exited (0) 24 hours ago thirsty_faraday
退出容器
exit #直接容器停止并退出Ctrl+P+Q #容器不停止退出
删除容器
#删除指定的容器,不能删除正在运行的容器,如果要强制删除 docker rm -fdocker rm 容器ID#删除所有的容器docker rm -f $(docker ps -aq)docker ps -aq | xargs docker rm -f
启动和停止容器的操作
#启动容器docker start 容器ID#重启容器docker restart 容器ID#停止当前正在运行容器docker stop 容器ID#强制停止当前容器docker kill 容器ID
3.4 常用其他命令
后台启动容器
#命令 docker run -d 镜像名!docker run -d centos:7.9.2009#问题 docker ps ,发现 centos:7.9.2009 停止了#常见的坑,docker 容器使用后台运行,就必须要有一个前台进程,docker发现没有应用,就会自动停止#nginx,容器启动后,发现自己没有提供服务,就会立刻停止,就是没有程序了。#测试docker run -d -it --name centos-node02 centos:7.9.2009 /bin/bash
查看日志
$ docker logs -f -t --tail [指定条目数] 容器#显示日志-tf :显示日志(-f动态显示,-t时间戳显示)--tail :要显示日志的条目数#启动容器时指定相应的命令$ docker run -itd \--name centos-node03 \--restart=always \centos:7.9.2009 /bin/bash -c "while true;do echo hello,world;done;sleep 1"$ docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES707f6716e19c centos:7.9.2009 "/bin/bash -c 'while…" 38 seconds ago Up 37 seconds centos-node03$ docker logs -f -t --tail 10 centos-node03
查看容器中进程信息
#命令 docker top 容器ID$ docker top centos-node03UID PID PPID C STIME TTY TIME CMDroot 15876 15855 82 22:37 ? 00:03:18 /bin/bash -c while true;do echo hello,world;done;sleep 1
查看容器的元数据
#命令docker inspect 容器ID#测试$ docker inspect centos-node03[{"Id": "707f6716e19ceea875cdd61dc2969b3542aed5523ffa78bb8f65c460050d08ed","Created": "2022-08-23T14:37:13.462762788Z","Path": "/bin/bash","Args": ["-c","while true;do echo hello,world;done;sleep 1"],"State": {"Status": "running","Running": true,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 15876,"ExitCode": 0,"Error": "","StartedAt": "2022-08-23T14:37:13.81511042Z","FinishedAt": "0001-01-01T00:00:00Z"},"Image": "sha256:eeb6ee3f44bd0b5103bb561b4c16bcb82328cfe5809ab675bb17ab3a16c517c9","ResolvConfPath": "/var/lib/docker/containers/707f6716e19ceea875cdd61dc2969b3542aed5523ffa78bb8f65c460050d08ed/resolv.conf","HostnamePath": "/var/lib/docker/containers/707f6716e19ceea875cdd61dc2969b3542aed5523ffa78bb8f65c460050d08ed/hostname","HostsPath": "/var/lib/docker/containers/707f6716e19ceea875cdd61dc2969b3542aed5523ffa78bb8f65c460050d08ed/hosts","LogPath": "/var/lib/docker/containers/707f6716e19ceea875cdd61dc2969b3542aed5523ffa78bb8f65c460050d08ed/707f6716e19ceea875cdd61dc2969b3542aed5523ffa78bb8f65c460050d08ed-json.log","Name": "/centos-node03","RestartCount": 0,"Driver": "overlay2","Platform": "linux","MountLabel": "","ProcessLabel": "","AppArmorProfile": "","ExecIDs": null,"HostConfig": {"Binds": null,"ContainerIDFile": "","LogConfig": {"Type": "json-file","Config": {}},"NetworkMode": "default","PortBindings": {},"RestartPolicy": {"Name": "always","MaximumRetryCount": 0},"AutoRemove": false,"VolumeDriver": "","VolumesFrom": null,"CapAdd": null,"CapDrop": null,"CgroupnsMode": "host","Dns": [],"DnsOptions": [],"DnsSearch": [],"ExtraHosts": null,"GroupAdd": null,"IpcMode": "private","Cgroup": "","Links": null,"OomScoreAdj": 0,"PidMode": "","Privileged": false,"PublishAllPorts": false,"ReadonlyRootfs": false,"SecurityOpt": null,"UTSMode": "","UsernsMode": "","ShmSize": 67108864,"Runtime": "runc","ConsoleSize": [0,0],"Isolation": "","CpuShares": 0,"Memory": 0,"NanoCpus": 0,"CgroupParent": "","BlkioWeight": 0,"BlkioWeightDevice": [],"BlkioDeviceReadBps": null,"BlkioDeviceWriteBps": null,"BlkioDeviceReadIOps": null,"BlkioDeviceWriteIOps": null,"CpuPeriod": 0,"CpuQuota": 0,"CpuRealtimePeriod": 0,"CpuRealtimeRuntime": 0,"CpusetCpus": "","CpusetMems": "","Devices": [],"DeviceCgroupRules": null,"DeviceRequests": null,"KernelMemory": 0,"KernelMemoryTCP": 0,"MemoryReservation": 0,"MemorySwap": 0,"MemorySwappiness": null,"OomKillDisable": false,"PidsLimit": null,"Ulimits": null,"CpuCount": 0,"CpuPercent": 0,"IOMaximumIOps": 0,"IOMaximumBandwidth": 0,"MaskedPaths": ["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware"],"ReadonlyPaths": ["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"]},"GraphDriver": {"Data": {"LowerDir": "/var/lib/docker/overlay2/72f30a5ccf50446ae62fd2722e318bea1a2dabc9eef978a37d60a7739350f8d8-init/diff:/var/lib/docker/overlay2/a37fa3541021205d4cc6fe5a51d6bda9a627c860fe9e0d6b23b317fd03e02dd8/diff","MergedDir": "/var/lib/docker/overlay2/72f30a5ccf50446ae62fd2722e318bea1a2dabc9eef978a37d60a7739350f8d8/merged","UpperDir": "/var/lib/docker/overlay2/72f30a5ccf50446ae62fd2722e318bea1a2dabc9eef978a37d60a7739350f8d8/diff","WorkDir": "/var/lib/docker/overlay2/72f30a5ccf50446ae62fd2722e318bea1a2dabc9eef978a37d60a7739350f8d8/work"},"Name": "overlay2"},"Mounts": [],"Config": {"Hostname": "707f6716e19c","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"Tty": true,"OpenStdin": true,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd": ["/bin/bash","-c","while true;do echo hello,world;done;sleep 1"],"Image": "centos:7.9.2009","Volumes": null,"WorkingDir": "","Entrypoint": null,"OnBuild": null,"Labels": {"org.label-schema.build-date": "20201113","org.label-schema.license": "GPLv2","org.label-schema.name": "CentOS Base Image","org.label-schema.schema-version": "1.0","org.label-schema.vendor": "CentOS","org.opencontainers.image.created": "2020-11-13 00:00:00+00:00","org.opencontainers.image.licenses": "GPL-2.0-only","org.opencontainers.image.title": "CentOS Base Image","org.opencontainers.image.vendor": "CentOS"}},"NetworkSettings": {"Bridge": "","SandboxID": "7e286039cc7562f9fe6b6174ef9dae698a6dc049414cfc56d666ca471689a78d","HairpinMode": false,"LinkLocalIPv6Address": "","LinkLocalIPv6PrefixLen": 0,"Ports": {},"SandboxKey": "/var/run/docker/netns/7e286039cc75","SecondaryIPAddresses": null,"SecondaryIPv6Addresses": null,"EndpointID": "4b0a08726af85c28a4a6181d335491dd61c0e0b5530248f6be9adfc8ed9f6f41","Gateway": "172.17.0.1","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"IPAddress": "172.17.0.4","IPPrefixLen": 16,"IPv6Gateway": "","MacAddress": "02:42:ac:11:00:04","Networks": {"bridge": {"IPAMConfig": null,"Links": null,"Aliases": null,"NetworkID": "ab0a6a7d99f1b549817749b69872806ed8989be4f03d330498f0f6becdd2b010","EndpointID": "4b0a08726af85c28a4a6181d335491dd61c0e0b5530248f6be9adfc8ed9f6f41","Gateway": "172.17.0.1","IPAddress": "172.17.0.4","IPPrefixLen": 16,"IPv6Gateway": "","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"MacAddress": "02:42:ac:11:00:04","DriverOpts": null}}}}]
进入当前正在运行的容器
#通常容器都是使用后台方式运行的,需要进入容器,修改一些配置#命令docker attach 容器IDdocker exec -it 容器ID bashShell#测试$ docker exec -it centos-node03 /bin/bash[root@707f6716e19c /]# ps -efUID PID PPID C STIME TTY TIME CMDroot 1 0 85 14:37 pts/0 00:09:17 /bin/bash -c while true;do echo hello,world;done;sleep 1root 7 0 0 14:47 pts/1 00:00:00 /bin/bashroot 21 7 0 14:48 pts/1 00:00:00 ps -ef[root@707f6716e19c /]# exitexit#测试$ docker attach centos-node03hello,worldhello,worldhello,world#正在执行当前的代码#docker exec #进入容器后开启一个新的终端,可以在里面操作(常用)#docker attach #进入容器正在执行的终端,不会启动新的进程
从容器内拷贝文件到主机上
docker cp 容器ID:容器内路径 宿主机路径#宿主机创建一个文件$ echo "hello,docker" > host.txt#将宿主机的文件传到容器内$ docker cp host.txt centos-node03:/tmp$ docker exec -it centos-node03 cat /tmp/host.txt #容器内有该文件hello,docker#容器内创建文件$ docker exec -it centos-node03 /bin/bash[root@707f6716e19c /]# cd /tmp/[root@707f6716e19c tmp]# echo "hello,dockerhost" > docker.txt[root@707f6716e19c tmp]# exit#容器内的文件传到宿主机中$ docker cp centos-node03:/tmp/docker.txt /tmp$ cat /tmp/docker.txthello,dockerhost#拷贝是一个手动过程,未来我们使用 -v 卷的技术,可以实现
3.5 总结

attachAttach to a running container —- 当前Shell下attach连接指定运行容器buildBuild an image from a Dockerfile —- 通过Dockerfile定制镜像commitCreate a new image from a container’s changes —- 提交当前容器为新的镜像cpCopy files/folders from the containers filesystem to the host past —- 从容器中拷贝指定文件或者目录到宿主机中createCreate a new container —- 创建一个新的容器,同run,但是不启动容器diffInspect changes on a container’s filesystem —- 查看 docker 容器的变化eventsGet real time events from the server —- 从 docker 服务获取容器实时事件execRun a command in a running container —- 在已存在的容器上运行命令exportStream the contents of a container as a tar archive —- 导出容器的内容流作为一个 tar 归档文件[对应 import]historyShow the history of an image —- 展示一个镜像形成历史imagesList images —- 列出本地系统当前镜像infoDisplay system-wide information —- 显示系统相关信息inspectReturn low-level information on Docker objects —- 查看容器详细信息killKill a runnning containers —- kill 指定 docker 容器loadLoad an image from a tar archive —- 从一个 tar 包中加载一个镜像 [ 对应 save ]loginLog in to a Docker registry —- 注册或者登陆一个 docker 源服务器logoutLog out from a Docker registry —- 从当前 Docker Registry 退出logsFetch the logs of a container —- 输出当前容器日志信息portList port mappings or a specific mapping for the container —- 查看映射端口对应的容器内部源端口pausePause all processes within one or more containers —- 暂停容器psList containers —- 列出容器列表pullPull an image or a repository from a registry —- 从docker镜像源服务器拉取指定镜像或者库镜像pushPush an image or a repository to a registry —- 推送指定镜像或者库镜像至docker镜像源服务器restartRestart one or more containers —- 重启运行的容器rmRemove one or more containers —- 移除一个或者多个容器rmiRemove one or more images —- 移除一个或者多个镜像[无容器使用该镜像才可输出,否则需要输出相关容器才能可以继续或者 -f 强制删除]runRun a command in a new container —- 创建一个新的容器并运行一个命令saveSave one or more images to a tar archive —- 保存一个镜像为一个 tar 包[ 对应 load ]searchSearch the Docker Hub for images —- 在 DockerHub 中搜索镜像startStart one or more stopped containers —- 启动容器stopStop one or more running containers —- 停止容器statsDisplay a live stream of container(s) resource usage statistics —- 显示容器资源使用统计的实时流tagTag an image into a repository —- 给源中镜像打标签topDisplay the running processes of a container —- 查看容器中运行的进程信息unpauseUnpause a pause container —- 取消暂停容器versionShow the Docker version information —- 查看 Docker 版本号waitBlock until one or more containers stop, then print their exit codes —- 截取容器停止时的退出状态值
docker 的命令十分多,以上是最常用的容器和镜像的命令。
3.6 作业练习
3.6.1 部署Nginx
#1.搜索nginx镜像 docker search 建议去dockerhub搜索,可以看到帮助文档#2.下载镜像 docker pull$ docker pull nginx:1.23.1-alpine$ docker images nginx:1.23.1-alpineREPOSITORY TAG IMAGE ID CREATED SIZEnginx 1.23.1-alpine 804f9cebfdc5 13 days ago 23.5MB#3.启动容器#-d 后台运行#--name 给容器命名#-p 宿主机端口:容器内部端口$ docker run -itd --name nginx01 -p 3344:80 --restart=always nginx:1.23.1-alpine$ docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESd0edd598caa1 nginx:1.23.1-alpine "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 0.0.0.0:3344->80/tcp, :::3344->80/tcp nginx01$ curl localhost:3344$ docker port nginx0180/tcp -> 0.0.0.0:334480/tcp -> :::3344

端口暴露的概念

:::color1 思考问题:我们每次改动Nginx配置,都需要进入容器内部?十分的麻烦,要是可以在容器外部提供一个映射路径,达到在容器修改文件名,容器内部就可以自动修改?
解决方法:-v 数据卷
:::
3.6.2 部署 Tomcat
#官方的使用$ docker run -it --rm tomcat:9.0#之前的启动都是后台,停止了容器之后,容器还可以 docker ps 查到#docker run -it --rm 一般用来测试,用完就删除容器#下载在启动$ docker pull tomcat:9.0$ docker run -it -p 3355:8080 --name tomcat01 -d tomcat:9.0#测试访问没有问题#进入容器$ docker exec -it tomcat01 /bin/bashroot@2cf8573ed4e2:/usr/local/tomcat# cp -av webapps.dist/* webapps/root@2cf8573ed4e2:/usr/local/tomcat# exitexit#发现问题:#1.Linux命令少了#2.webapps目录下没有web文件。镜像的原因,默认是最小的镜像,所有不必要的都剔除掉。#保证最小可运行的环境$ docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES2cf8573ed4e2 tomcat:9.0 "catalina.sh run" 4 minutes ago Up 4 minutes 0.0.0.0:3355->8080/tcp, :::3355->8080/tcp tomcat01$ docker port tomcat018080/tcp -> 0.0.0.0:33558080/tcp -> :::3355

:::color1 思考问题:我们以后要部署项目,如果每次都要进入容器是不是十分麻烦?要是可以在容器外部提供一个映射路径,webapps,我们在外部放置项目,就自动同步到内部就好了!
:::
3.6.3 部署 ES+Kibana
ES暴露的端口很多
ES十分的消耗内存
ES的数据一般需要放置到安全目录!挂载
$ docker network create somenetwork#启动 elasticsearch 容器$ docker run -d --name elasticsearch \--net somenetwork \-p 9200:9200 \-p 9300:9300 \-e "discovery.type=single-node" elasticsearch:7.17.5#启动了 elasticsearch容器后,docker stats 查看CPU状态$ docker stats --no-stream elasticsearchCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS90e93d93b0b0 elasticsearch 1.41% 1.226GiB / 3.682GiB 33.28% 2.38kB / 0B 0B / 1.02MB 45#测试ES是否成功了,增加内存的设置$ curl localhost:9200{"name" : "90e93d93b0b0","cluster_name" : "docker-cluster","cluster_uuid" : "W4bf0m9_TU2e5TxRgifsqg","version" : {"number" : "7.6.2","build_flavor" : "default","build_type" : "docker","build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f","build_date" : "2020-03-26T06:34:37.794943Z","build_snapshot" : false,"lucene_version" : "8.4.0","minimum_wire_compatibility_version" : "6.8.0","minimum_index_compatibility_version" : "6.0.0-beta1"},"tagline" : "You Know, for Search"}#设置容器的内存限制,修改配置文件-e环境配置修改$ docker run -d --name elasticsearch \--net somenetwork \-p 9200:9200 \-p 9300:9300 \-e "discovery.type=single-node" \-e ES_JAVA_OPTS="-Xms256m -Xmx512m" elasticsearch:7.17.5#再次查看 docker stats 容器资源状态$ docker stats --no-stream elasticsearchCONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDSc4479d160c62 elasticsearch 1.52% 364.6MiB / 3.682GiB 9.67% 656B / 0B 0B / 849kB 46$ curl localhost:9200{"name" : "c4479d160c62","cluster_name" : "docker-cluster","cluster_uuid" : "xj_fi6P2Tc2DldYqSCz2dw","version" : {"number" : "7.6.2","build_flavor" : "default","build_type" : "docker","build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f","build_date" : "2020-03-26T06:34:37.794943Z","build_snapshot" : false,"lucene_version" : "8.4.0","minimum_wire_compatibility_version" : "6.8.0","minimum_index_compatibility_version" : "6.0.0-beta1"},"tagline" : "You Know, for Search"}

#安装Kibana$ mkdir -p /mydata/elasticsearch/kibana/config/$ cat > /mydata/elasticsearch/kibana/config/kibana.yml <<EOF## ** THIS IS AN AUTO-GENERATED FILE **## Default Kibana configuration for docker targetserver.host: "0.0.0.0"server.shutdownTimeout: "5s"elasticsearch.hosts: [ "http://10.0.0.101:9200" ]monitoring.ui.container.elasticsearch.enabled: trueEOF$ docker run -d \--name kibana \--net somenetwork \-v /mydata/elasticsearch/kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml \-p 5601:5601 kibana:7.17.5$ docker ps -lCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESaa2b70cdab6f kibana:7.17.5 "/bin/tini -- /usr/l…" 2 minutes ago Up 2 minutes 0.0.0.0:5601->5601/tcp, :::5601->5601/tcp kibana

3.7 可视化面板
Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便的管理Docker环境,包括单机环境和集群环境(毫不犹豫直接使用 K8S)。用于监控和统计。Portainer 官方站点
官网:https://www.portainer.io/ 安装文档:Install Portainer with Docker on Linux - Portainer Documentation + Rancher (CI/CD使用) + Portainer 先执行使用 Docker图形化界面管理工具!提供一个后台面板供我们操作!bash
$ docker volume create portainer_data
$ docker run -d -p 8000:8000 -p 9443:9443 --name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
--privileged=true \
portainer/portainer-ce:2.9.3
#访问测试:
#外网:http://IP地址:9443
$ curl -k https://localhost:9443
可以查看相关的Docker面板情况
# 4 Docker 镜像
## 4.1 Docker 镜像是什么
+ Docker 镜像是什么?
镜像是一种轻量级,可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码,运行时需要的库,环境变量和配置文件等),这个打包好的运行环境就是 image 镜像文件。
只有通过这个镜像文件才能生成 Docker 容器实例(类似Java中 new 出来一个对象)
未来所有的应用,直接打包成 Docker 镜像,就可以直接跑起来。
如何得到镜像?
1. 从远程仓库下载
2. 朋友同学拷贝得到
3. 自己制作镜像Dockerfile
+ 分层的镜像
在使用 pull 为例,在下载的过程中可以看到 docker 的镜像好像是在一层一层的在下载。
bash
$ docker pull tomcat
## 4.2 Docker 镜像加载原理
+ UnionFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层一层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(Unite several directories into a single virtual filesystem)。Union文件系统是Docker镜像的基础。<font style="color:#E8323C;">镜像可以通过分层来进行继承</font>,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但是从外面看起来,只能看到一个文件系统,<font style="color:#E8323C;">联合加载会把各种文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录</font>。
联合文件系统:分层,轻量,并且能够聚合的一种镜像基础。
+ Docker 镜像加载原理
Docker 镜像加载原理:
Docker 的镜像实际上是由一层一层的文件系统组成的,这种层级的文件系统UnionFS。
bootfs(boot file system)主要包含 bootloader(根加载) 和 kernel(Linux内核),bootloader 主要是引导加载 kernel,Linux 刚启动时会加载 bootfs文件系统(主要引导Linux内核),<font style="color:#E8323C;">在Docker 镜像的最底层时引导文件系统 bootfs</font>。这一层与我们典型的Linux/Unix系统是一样的,包含 boot加载器和内核。当 boot 加载完成之后整个内核都在内存中,此时内存的使用权已由 bootfs 转交给内核,此时系统也会卸载 bootfs。
rootfs(root file system),在 bootfs 之上,包含的就是典型 Linux 系统中的 /dev,/proc,/bin,/etc等标准目录和文件。rootfs 就是各种不同的操作系统发行版,比如 Ubuntu,CentOS 等等。
:::warning
总结:
1. bootfs:系统启动的时候需要 bootloader 引导器加载内核 kernel,bootfs 就会卸载,将内存试用权给kernel。
2. rootfs:Linux系统的标准目录和文件,类似于一个小型阉割版的虚拟机环境
3. 系统由三个部分组成,bootfs + rootfs + 应用组成,其中 bootfs 非常大,但是在 Docker 中是公用的,所有镜像中不包含 bootfs ,镜像主要由 rootfs + 应用组成,所以比较小。
:::
:::color1
平时我们安装虚拟机的 CentOS 都是好几个G,为什么 Docker 这里才 200M?
答案:对于一个精简的OS,rootfs 可以很小,只需要包括最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就行了。由此可见对于不同的 Linux 发行版,bootfs 基本上是一致的,rootfs 会有差别,因此不同的发行版可以共用 bootfs。
也是 虚拟机是分钟级的,容器是秒级的原因。容器只是运行命令和运行应用的环境,底层调用的是宿主机的内核。
:::
properties
~ docker images centos:centos7.9.2009
REPOSITORY TAG IMAGE ID CREATED SIZE
centos centos7.9.2009 eeb6ee3f44bd 11 months ago 204MB
## 4.3 分层理解
> 底层的镜像
>
我们可以去下载一个镜像,注岚观察下或的日志输出,可以看到是一层一层的在下载!
+ 为什么 Docker 镜像要采用着这种分层结构呢?
Docker 镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用。
比如说有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
查看镜像分层的方式可以通过 docker image inspect 命令
bash
~ docker image inspect redis
...省略部分输出...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f",
"sha256:9b24afeb7c2f21e50a686ead025823cd2c6e9730c013ca77ad5f115c079b57cb",
"sha256:4b8e2801e0f956a4220c32e2c8b0a590e6f9bd2420ec65453685246b82766ea1",
"sha256:529cdb636f61e95ab91a62a51526a84fd7314d6aab0d414040796150b4522372",
"sha256:9975392591f2777d6bf4d9919ad1b2c9afa12f9a9b4d260f45025ec3cc9b18ed",
"sha256:8e5669d8329116b8444b9bbb1663dda568ede12d3dbcce950199b582f6e94952"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
理解:
所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层;如果在该境像中添加 Python包,就会需要在基础镜像层之上增加和修改和新的内容并提交 commit 创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。
该镜像当前已经包含3个镜像层,如下图所示(这只是一个用于演示的很简单的例子)。
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下因中举了一个简单的例子,每个镜像.层包含3个文件,而境像包含了来白两个境像层的6个文件。
上图中的镜猕层银之前图中的路有区别,主要目的是使于展示文件。
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有6个文件,这是因为最上层中的文件7是文件5的一个更新版本。
这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件,这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。Docker通过存储引擎(新版本采用快照机制》的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统.
Linux上可用的存储引擎有AUF5、Overlay2、Device Mapper、Btrfs 以及.zZFS。顾名思义,每种存储引擎都基于Linux 中对应的.文件系统或者块设备技术,并且每种存储引挛都有其独有的性能特点。
Docker在Windows上仪支持 windowstilter一种存储引擎,该引擎基于NTFS文件系统之上实现了分层和CoW[1]。
下图展示了与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。
:::warning
特点:
Docker 镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!
这一层就是我们通常说的容器层,容器之下的都叫镜像层!
:::
重点理解
Docker 镜像层都是只读的,容器层是可写的。
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称为“容器层”,“容器层”之下的都叫镜像层。
所有对容器的改动,无论添加、删除、还是修改文件都只会发生在容器中。只有容器层时可写的,容器层下面的所有镜像层都是只读的。
## 4.4 如何提交一个自己的镜像
### 4.4.1 commit 镜像
bash
docker commit 提交容器成为一个新的版本
#命令和Git的原理类似
docker commit -m "提交的描述信息" -a "作者" 容器ID 目标镜像名:[TAG]
范例:
#启动一个默认的Tomcat$ docker run -it -d -p 8080:8080 --name tomcat01 tomcat#发现这个默认的Tomcat新版本是没有webapps应用,是由于镜像的原因:官方的新镜像默认Tomcat下的webapps目录下是没有文件的$ docker exec -it tomcat01 /bin/bash#将webapps.dist目录下的文件拷贝到webapps中root@82b9be0838cd:/usr/local/tomcat# cp -av webapps.dist/ webappsroot@82b9be0838cd:/usr/local/tomcat# cd webappsroot@82b9be0838cd:/usr/local/tomcat/webapps# lsROOT docs examples host-manager managerroot@82b9be0838cd:/usr/local/tomcat/webapps# exitexit$ curl localhost:8080#将操作过的容器通过 commit 提交为一个镜像$ docker commit \-a "zhongzhiwei <zhongzhiwei@kubesphere.io>" \-m "拷贝webapps.dist目录下的内容到webapps" \tomcat01 kube-tomcat:1.0#查看提交的镜像$ docker images kube-tomcat:1.0REPOSITORY TAG IMAGE ID CREATED SIZEkube-tomcat 1.0 0cf24bc18be1 4 seconds ago 684MB$ docker history kube-tomcat:1.0IMAGE CREATED CREATED BY SIZE COMMENT0cf24bc18be1 3 minutes ago catalina.sh run 4.43MB 拷贝webapps.dist目录下的内容到webappsfb5657adc892 8 months ago /bin/sh -c #(nop) CMD ["catalina.sh" "run"] 0B<missing> 8 months ago /bin/sh -c #(nop) EXPOSE 8080 0B<missing> 8 months ago /bin/sh -c set -eux; nativeLines="$(catalin… 0B<missing> 8 months ago /bin/sh -c set -eux; savedAptMark="$(apt-m… 20.2MB<missing> 8 months ago /bin/sh -c #(nop) ENV TOMCAT_SHA512=c2d2ad5… 0B<missing> 8 months ago /bin/sh -c #(nop) ENV TOMCAT_VERSION=10.0.14 0B<missing> 8 months ago /bin/sh -c #(nop) ENV TOMCAT_MAJOR=10 0B<missing> 8 months ago /bin/sh -c #(nop) ENV GPG_KEYS=A9C5DF4D22E9… 0B<missing> 8 months ago /bin/sh -c #(nop) ENV LD_LIBRARY_PATH=/usr/… 0B<missing> 8 months ago /bin/sh -c #(nop) ENV TOMCAT_NATIVE_LIBDIR=… 0B<missing> 8 months ago /bin/sh -c #(nop) WORKDIR /usr/local/tomcat 0B<missing> 8 months ago /bin/sh -c mkdir -p "$CATALINA_HOME" 0B<missing> 8 months ago /bin/sh -c #(nop) ENV PATH=/usr/local/tomca… 0B<missing> 8 months ago /bin/sh -c #(nop) ENV CATALINA_HOME=/usr/lo… 0B<missing> 8 months ago /bin/sh -c #(nop) CMD ["jshell"] 0B<missing> 8 months ago /bin/sh -c set -eux; arch="$(dpkg --print-… 343MB<missing> 8 months ago /bin/sh -c #(nop) ENV JAVA_VERSION=11.0.13 0B<missing> 8 months ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0B<missing> 8 months ago /bin/sh -c #(nop) ENV PATH=/usr/local/openj… 0B<missing> 8 months ago /bin/sh -c { echo '#/bin/sh'; echo 'echo "$J' 27B<missing> 8 months ago /bin/sh -c #(nop) ENV JAVA_HOME=/usr/local/… 0B<missing> 8 months ago /bin/sh -c set -eux; apt-get update; apt-g… 11.3MB<missing> 8 months ago /bin/sh -c apt-get update && apt-get install… 152MB<missing> 8 months ago /bin/sh -c set -ex; if ! command -v gpg > /… 18.9MB<missing> 8 months ago /bin/sh -c set -eux; apt-get update; apt-g… 10.7MB<missing> 8 months ago /bin/sh -c #(nop) CMD ["bash"] 0B<missing> 8 months ago /bin/sh -c #(nop) ADD file:c03517c5ddbed4053… 124MB#使用我们修改提交的新镜像,那么就会发现在 webapps 目录下有相应的文件$ docker run -it -p 8085:8080 --name mytomcat01 -d kube-tomcat:1.0$ docker exec -it mytomcat01 /bin/bashroot@6b8f51a773ee:/usr/local/tomcat# ls webappsROOT docs examples host-manager managerroot@6b8f51a773ee:/usr/local/tomcat# exitexit$ curl localhost:8085
:::warning 学习方式说明:理解概念,可以比较模糊,但是一定要实践,最后实践和理论相结合。
如果想保存当前容器的状态,就可以通过 commit 方式来提交镜像,获取一个镜像。类似于虚拟机的快照功能。
:::
5 容器数据卷
5.1 什么是容器数据卷
docker 的理念回顾
将应用和运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对于数据的要求,是希望能够持久化的!就好比,你安装一个MySQL,结果你把容器删了,就相当于删库跑路了,这TM也太扯了吧!
所以我们希望容器之间有可能可以共享数据,Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据作为镜像的一部分保存下来,那么当容器删除后,数据自然也就没有了!这样是行不通的!
为了能保存数据在Docker中我们就可以使用卷!让数据挂载到我们本地!这样数据就不会因为容器删除而丢失了!
数据?如果数据都在容器中,那么容器删除,数据就会丢失!需求:数据可以持久化
MySQL,容器删除了,数据则删除,删库跑路。需求:MySQL的数据存放到本地!
容器之间可以有一个数据共享的技术!Docker 容器中产生的数据,同步到本地!
这就是数据卷技术!目录的挂载,将我们容器内的目录,挂载到Linux系统指定的目录下。
作用:
卷就是目录或者文件,存在一个或者多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System,提供一些用于持续存储或共享数据的特性:
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
特点:
1、数据卷可在容器之间共享或重用数据
2、卷中的更改可以直接生效
3、数据卷中的更改不会包含在镜像的更新中
4、数据卷的生命周期一直持续到没有容器使用它为止

总结:容器的数据持久化和同步操作!容器间也是可以数据共享的!
5.2 使用数据卷
方式一:直接使用命令挂载 -v 参数
:::warning docker run -it -v 主机目录:容器内的目录
:::
范例:
$ docker run -it -d \-v /data/ubuntu01:/home \--privileged=true \--name ubuntu01 ubuntu:20.04 /bin/bash#查看容器的详细信息显示#通过docker inpsect 容器ID 查看#值得注意的是,主机目录会直接覆盖掉容器中要挂载的目录$ docker inspect ubuntu01 | grep -A 9 "Mounts""Mounts": [{"Type": "bind","Source": "/data/ubuntu01", #主机内的目录路径"Destination": "/home", #Docker容器内的目录路径"Mode": "","RW": true,"Propagation": "rprivate"}],$ docker exec -it ubuntu01 /bin/bashroot@9afa7b8ee434:/# cd /home/root@9afa7b8ee434:/home# mkdir host ; lshostroot@9afa7b8ee434:/home# echo "Hello World!" > /home/docker.txtroot@9afa7b8ee434:/home# exit$ ls /data/ubuntu01/docker.txt host#再次测试#删除原有的ubuntu01容器,启动ubuntu02容器进行挂载该目录$ docker rm -f ubuntu01$ docker run -it -d \--name ubuntu02 \--privileged=true \-v /data/ubuntu01:/home \ubuntu:20.04 /bin/bash$ docker exec -it ubuntu02 /bin/bashroot@2ac37ab7725a:/# ls /home/docker.txt hostroot@2ac37ab7725a:/# cat /home/docker.txtHello World!
:::warning 数据卷好处:我们以后修改只需要修改本地修改即可,容器内会自动同步。类似共享文件是占用一份的存储。
:::
5.3 实战:安装MySQL
思考:MySQL 的数据持久化的问题。
#获取并下载MySQL镜像$ docker pull mysql:5.7.36#官方测试 $ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/mysql-root -d mysql:tag#运行mysql容器,需要做数据挂载#安装启动mysql,需要配置密码,注意事项#-d 后台运行#-p 端口映射#-v 数据卷挂载#-e 环境变量设置#--name 容器名字$ docker run -it -d \--name mysql01 \-p 3316:3306 \-e MYSQL_ROOT_PASSWORD=123456 \-v /data/docker/mysql01/conf:/etc/mysql/conf.d \-v /data/docker/mysql01/data:/var/lib/mysql \mysql:5.7.36#如果在挂载时,外部是新建目录或空目录会将容器中映射目录也变成空目录$ cat > /data/docker/mysql01/conf/my.cnf <<EOF[client]default-character-set=utf8mb4[mysql]default-character-set=utf8mb4[mysqld]character-set-server=utf8mb4collation-server=utf8mb4_unicode_ciEOF$ ls /data/docker/mysql01/conf/my.cnf$ ls /data/docker/mysql01/data/auto.cnf client-cert.pem ib_buffer_pool ib_logfile1 performance_schema server-cert.pemca-key.pem client-key.pem ibdata1 ibtmp1 private_key.pem server-key.pemca.pem ib_logfile0 mysql public_key.pem sys#默认 -v 是先主机挂载覆盖了,然后 MySQL 程序启动才自动创建的数据,会直接生成/var/lib/mysql/目录下的数据库数据$ docker restart mysql01mysql01#启动成功之后,在本地使用Navicat等数据库连接工具进行连接即可。#Navicat 连接到服务器 3316 --> 3316 端口是和容器的内部的 3306 映射,这个时候就可以连接上了#在本地创建一个测试数据库dbtest,可以在挂载目录下查看到相应的目录$ ls -l /data/docker/mysql01/data/dbtest/total 4-rw-r----- 1 polkitd input 67 Aug 25 17:10 db.opt

:::color1 假设我们将容器删除了。
发现,我们挂载到本地的数据卷依旧没有丢失,这就实现了容器的数据持久化的功能!
:::
5.4 具名挂载和匿名挂载
#匿名挂载-v 容器内路径$ docker run -it -d -P --name nginx01 -v /etc/nginx nginx#查看所有 volume 的情况$ docker volume lsDRIVER VOLUME NAMElocal b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9#这里发现,这种就是匿名挂载,我们在 -v 只写了容器内的路径,没有写容器外的路径$ docker inspect nginx01 | grep -A 9 "Mounts""Mounts": [{"Type": "volume","Name": "b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9","Source": "/var/lib/docker/volumes/b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9/_data","Destination": "/etc/nginx","Driver": "local","Mode": "","RW": true,"Propagation": ""$ cd /var/lib/docker/volumes/b5c1c38d5935b1e1adc829c3eb28af15188368169745afe75f230858969b6df9/_data$ lsconf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params#具名挂载$ docker run -it -d -P --name nginx02 -v nginx-volume:/etc/nginx nginx62c42ad0e4ca2374ee30af5b18b48a40e197d41126225446716c241639c3dbf3#通过 -v 卷名:容器内路径#查看所有 volume 的情况$ docker volume lsDRIVER VOLUME NAMElocal nginx-volume#查看一下这个具名卷$ docker volume inspect nginx-volume[{"CreatedAt": "2022-08-25T20:41:56+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/nginx-volume/_data","Name": "nginx-volume","Options": null,"Scope": "local"}]

:::color1 所有的 Docker 容器内的卷,没有指定目录的情况下都是在 “/var/lib/docker/volumes/xxx/_data“
我们通过具名挂载可以方便的让我们找到对应的数据卷名,大多数情况是使用”具名挂载”
:::
思考:如何请是具名挂载还是匿名挂载,还是指定路径挂载!
- -v 容器内路径 #匿名挂载
- -v 卷名:容器内路径 #具名挂载
- -v /宿主机路径:容器内路径 #指定路径挂载
拓展:
#通过 -v 容器内路径:ro rw 改变读写权限ro readonly #只读rw readwrite #可读可写#一旦这个设置了容器,容器对我们挂载出来的内容就有限定了!$ docker run -it -d -P --name nginx03 -v nginx-volume:/etc/nginx:ro nginx$ docker run -it -d -P --name nginx03 -v nginx-volume:/etc/nginx:rw nginx#默认是rw,如果是 ro 就说明只能通过宿主机来操作,容器内部是无法操作的
5.5 初识 Dockerfile
Dockerfile就是用来构建docker镜像的构建文件!命令脚本!先体验一下!
通过这个脚本可以生成镜像,镜像是一层一层的,对应的就是脚本一个个的。每一个命令都是一层!
$ mkdir -pv /root/docker/docker-volume ; cd /root/docker/docker-volume#创建一个Dockerfile文件,名字可以随机 建议使用Dockerfile#文件中的内容指令(大写) 参数$ vim DockerfileFROM ubuntu:20.04#匿名挂载VOLUME ["/volume01", "/volume02"]RUN echo "-----> Success ......"CMD /bin/bash#这里的每一个命令,就是镜像的一层$ docker build -t kube-ubuntu:1.0 -f Dockerfile .Sending build context to Docker daemon 2.048kBStep 1/4 : FROM ubuntu:20.04---> ba6acccedd29Step 2/4 : VOLUME ["/volume01", "/volume02"]---> Running in f77e4a3d3e7fRemoving intermediate container f77e4a3d3e7f---> 91315a9d10d0Step 3/4 : RUN echo "-----> Success ......"---> Running in fa440b3de704-----> Success ......Removing intermediate container fa440b3de704---> ffe4ed184001Step 4/4 : CMD /bin/bash---> Running in f27e036a6862Removing intermediate container f27e036a6862---> 1f2a4e5e429dSuccessfully built 1f2a4e5e429dSuccessfully tagged kube-ubuntu:1.0$ docker images kube-ubuntu:1.0REPOSITORY TAG IMAGE ID CREATED SIZEkube-ubuntu 1.0 1f2a4e5e429d About a minute ago 72.8MB$ docker run -itd --name ku1 kube-ubuntu:1.0 /bin/bash$ docker exec -it ku1 /bin/bash#这个目录就是我们生成镜像的时候自动挂载的,数据卷目录root@6a6138c411dd:/# echo "Hello volume01" > /volume01/container01.txtroot@6a6138c411dd:/# echo "Hello volume02" > /volume02/container02.txtroot@6a6138c411dd:/# ls -l /volume0*/volume01:total 4-rw-r--r-- 1 root root 15 Aug 25 13:08 container01.txt/volume02:total 4-rw-r--r-- 1 root root 15 Aug 25 13:08 container02.txtroot@6a6138c411dd:/# exitexit#这个数据卷和外部一定有一个同步的目录$ docker inspect ku1 | grep -A 21 "Mounts""Mounts": [{"Type": "volume","Name": "604c9c1d68d41958287107effbd1dcfbd0bbd875c9cb7dd8b35982f446e35f68","Source": "/var/lib/docker/volumes/604c9c1d68d41958287107effbd1dcfbd0bbd875c9cb7dd8b35982f446e35f68/_data","Destination": "/volume01","Driver": "local","Mode": "","RW": true,"Propagation": ""},{"Type": "volume","Name": "f6262a1ec8581c5b6a6b78e3beacd4881b435a5d331f5c33d88e5c9e0b8bfb89","Source": "/var/lib/docker/volumes/f6262a1ec8581c5b6a6b78e3beacd4881b435a5d331f5c33d88e5c9e0b8bfb89/_data","Destination": "/volume02","Driver": "local","Mode": "","RW": true,"Propagation": ""}],#测试一下,刚才的文件是否同步了!#这种方式我们未来使用的十分多,因为我们通常会构建自己的镜像!$ cat /var/lib/docker/volumes/604c9c1d68d41958287107effbd1dcfbd0bbd875c9cb7dd8b35982f446e35f68/_data/container01.txtHello volume01$ cat /var/lib/docker/volumes/f6262a1ec8581c5b6a6b78e3beacd4881b435a5d331f5c33d88e5c9e0b8bfb89/_data/container02.txtHello volume02

假设构建镜像时候没有挂载卷,要手动镜像挂载 -v 卷名:容器内路径!
5.6 数据卷容器

#启动3个容器,通过我们刚才自己写的镜像启动$ docker images kube-ubuntu:1.0REPOSITORY TAG IMAGE ID CREATED SIZEkube-ubuntu 1.0 317c07f88b30 30 minutes ago 72.8MB#写/bin/bash是规定镜像启动时执行这条指令,centos镜像启动时默认会执行/bin/bash,因此可以不写$ docker run -itd --name docker01 kube-ubuntu:1.0 /bin/bash$ docker run -itd --name docker02 --volumes-from docker01 kube-ubuntu:1.0 /bin/bash#--volumes-from 实际上多个容器挂载的是同一个数据卷#--volumes-from 就可以实现容器间的数据共享了#在docker01容器中创建测试文件,检验docker02是否也有其测试文件#docker01创建测试文件$ docker exec -it docker01 /bin/bashroot@51ae030c5f2a:/# echo "Hello docker02 volume01" > /volume01/test01.txtroot@51ae030c5f2a:/# echo "Hello docker02 volume02" > /volume02/test02.txt#docker02查看测试文件$ docker exec -it docker02 /bin/bashroot@9807aeded26e:/# cat /volume01/test01.txtHello docker02 volume01root@9807aeded26e:/# cat /volume02/test02.txtHello docker02 volume02#docker03继承docker02的数据卷信息,并创建测试文件$ docker run -it --name docker03 --volumes-from docker02 kube-ubuntu:1.0 /bin/bashroot@bdc47c45e893:/# echo "Hello I am docker03" > /volume01/docker03.txtroot@bdc47c45e893:/# echo "Hello I am docker03 Here" > /volume02/docker03.txt#docker01可以查看到docker03的测试文件$ docker exec -it docker01 /bin/bashroot@51ae030c5f2a:/# cat /volume01/docker03.txtHello I am docker03root@51ae030c5f2a:/# cat /volume02/docker03.txtHello I am docker03 Here

#测试:可以删除 docker01,查看一下docker02和docker03是否还可以访问这个文件#测试依旧可以访问$ docker rm -f docker01#三个容器都是挂载到宿主机的同一个目录下的,如果删掉宿主机下的这个目录的文件,三个容器的数据都会消失#可以发现三个容器的匿名数据卷挂载的路径是一样的。


已验证,多个 MySQL 实现数据共享失败。原因后续继承该MySQL 配置和数据的方式依旧是父MySQL,会导致后续的MySQL 启动失败。数据卷容器 只能使用在 无状态服务。注意两个mysql虽然共用一个数据,但是只能同时在线一个MySQL容器,否则另外一个连接不上。
:::color1 结论:
容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。
但是一旦数据持久化到本地,这个时候,本地的数据是不会删除的!
数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除数据卷。如果需要在删除容器的同时移除数据卷,可以在删除容器的时候使用 docker rm -v 这个参数。
:::
6 Dockerfile
6.1 Dockerfile 介绍
Dockerfile 是用来构建 Docker 镜像的文件!命令参数脚本!
构建步骤:
- 编写一个 Dockerfile 文件
- docker build 构建成为一个镜像
- docker run 运行镜像
- docker push 发布镜像(DockerHub、阿里云镜像仓库、本地镜像仓库)
查看官方 CentOS Dockerfile 镜像文件
FROM scratchADD centos-7-x86_64-docker.tar.xz /LABEL \org.label-schema.schema-version="1.0" \org.label-schema.name="CentOS Base Image" \org.label-schema.vendor="CentOS" \org.label-schema.license="GPLv2" \org.label-schema.build-date="20201113" \org.opencontainers.image.title="CentOS Base Image" \org.opencontainers.image.vendor="CentOS" \org.opencontainers.image.licenses="GPL-2.0-only" \org.opencontainers.image.created="2020-11-13 00:00:00+00:00"CMD ["/bin/bash"]
很多官方镜像都是基础包,很多功能没有,我们通常会自己搭建自己的镜像!
6.2 Dockerfile 构建过程
基础知识:
:::color1 Dockerfile 是面向开发的,以后要发布项目,做镜像,就需要编写 Dockerfile 文件,这个文件十分简单!
:::
Docker 镜像 —> SpringBoot 。镜像逐渐成为企业交付的标准,必须掌握。
步骤:开发,部署,上线运维 …… 缺一不可!
- Dockerfile:构建文件,定义了一切的步骤,源代码。
- DockerImage:是通过 Dockerfile 构建生成的镜像,最终发布和运行的产品。
- Docker Container:容器就是镜像运行起来提供服务。

6.3 Dockerfile 的指令
:::color1 FROM #基础镜镜像,一切从这里开始构建
MAINTAINER #镜像是谁写的,姓名+邮箱
RUN #镜像构建的时候需要运行的命令
ADD #步骤: tomcat镜像,这个tomcat压缩包!添加内容
WORKDIR #镜像的工作目录
VOLUME #挂载的目录
EXPOSE #保留端口配置
CMD #指定这个容器的时候要运行的要求,只有最后一个会生效,可被替代
ENTRYPOINT #指定这个容器的时候要运行的要求,ENTRYPOINT 后的指令会作为参数进行传入
ONBUILD #当构建一个被继承 Dockerfile 这个时候就会运行 ONBUILD 的指令,才会触发指令
COPY # 类似 ADD,将文件拷贝到镜像中
ENV #构建的时候设置环境变量
:::
:::warning
参考 tomcat 8 的 dockerfile 入门 —> https://github.com/docker-library/tomcat DockerHub 使用 docker run 运行的镜像,进行反解析之后就是 Dockerfile :::dockerfile
# https://github.com/docker-library/tomcat/blob/master/10.0/jdk8/corretto-al2/Dockerfile
#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#
FROM amazoncorretto:8-al2-jdk
ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME
# let "Tomcat Native" live somewhere isolated
ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib
ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR
# see https://www.apache.org/dist/tomcat/tomcat-10/KEYS
# see also "versions.sh" (https://github.com/docker-library/tomcat/blob/master/versions.sh)
ENV GPG_KEYS A9C5DF4D22E99998D9875A5110C01C5A2F6059E7
ENV TOMCAT_MAJOR 10
ENV TOMCAT_VERSION 10.0.23
ENV TOMCAT_SHA512 0e0263e8280f2ccfb4bef916444a6105fef689a3d95c334c8a7bfe59f1e3966d48ea624727f1818a4df331a603f1ac5e21b908dda3cae676ddc1aef90c2d12ab
RUN set -eux; \
\
# http://yum.baseurl.org/wiki/YumDB.html
if ! command -v yumdb > /dev/null; then \
yum install -y --setopt=skip_missing_names_on_install=False 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 --setopt=skip_missing_names_on_install=False $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_applications
mv 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 \
; \
nproc="$(nproc)"; \
make -j "$nproc"; \
make install; \
); \
rm -rf "$nativeBuildDir"; \
rm bin/tomcat-native.tar.gz; \
\
# mark any explicit dependencies as manually installed
find "$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 necessary
yum 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/77
find ./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/35
chmod -R +rX .; \
chmod 777 logs temp work; \
\
# smoke test
catalina.sh version
# verify Tomcat Native is working properly
RUN 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; \
fi
EXPOSE 8080
CMD ["catalina.sh", "run"]
### 6.3.1 FROM
基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是 from
6.3.2 MAINTAINER
镜像维护者的姓名和邮箱地址6.3.3 RUN
容器构建时需要运行的命令(容器启动前做的一下预操作),在 docker build 命令构建镜像的时候,就会执行RUN的部分
两种格式
- Shell 格式
RUN <命令行命令># <命令行命令> 等同于,在终端操作的 shell 指令# 例如:RUN yum install -y vim
- exec 格式
RUN ["可执行文件", "参数1", "参数2"]# 例如:# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
RUN 是在 docker build 时运行
6.3.4 EXPOSE
EXPOSEEXPOSE 定义说明里面的服务端口。 该指令通知 Docker 容器在运行时侦听指定的网络端口。您可以指定端口是在 TCP 还是 UDP 上侦听,如果未指定协议,则默认值为 TCP。EXPOSE。 该指令实际上并不发布端口。它充当构建映像的人员和运行容器的人员之间的一种文档类型,有关要发布哪些端口。若要在运行容器时实际发布端口,请使用 标志 on 发布和映射一个或多个端口,或使用标志发布所有公开的端口并将其映射到高阶端口。EXPOSE-pdocker run-P[ / …]
6.3.5 WORKDIR
WORKDIR /path/to/workdir指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
6.3.6 USER
指定该镜像以什么样的用户去执行,如果都不指定,默认是 root6.3.7 ENV
用来在构建镜像过程中设置环境变量
ENV MY_PATH /usr/mytest
这个环境变量可以在后续的任何 RUN 指令中使用,这就如同在命令前面指定了环境变量前缀一样;
也可以在其他指令中直接使用这些环境变量
比如:WORKDIR $MY_PATH
6.3.8 ADD
ADD [—chown=将宿主机目录下的文件拷贝进镜像并且会自动处理 URL 和解压 tar 压缩包。该功能仅在用于构建 Linux 容器的 Dockerfiles 上受支持,在 Windows 容器上不起作用。由于用户和组所有权概念不会在 Linux 和 Windows 之间转换,因此使用 和 用于将用户和组名称转换为 ID 会将此功能限制为仅适用于基于 Linux 操作系统的容器。 如果 是可识别的压缩格式(标识、gzip、bzip2 或 xz)的本地 tar 存档,则将其解压缩为目录。来自远程 URL 的资源不会解压缩。当复制或解压缩目录时,它的行为与 相同,结果是:: ADD [—chown=] … : ] [“ “,… “ “]
ADD 命令支持将远程URL的资源,但是 Docker 官方不建议直接用远程url,所以还是先下载到主机
是 COPY 的升级版。6.3.9 COPY
类似 ADD,拷贝文件和目录到镜像中 将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的<目标路径>位置:::warning COPY src dest
COPY [“src”, “dest”]
:::
6.3.10 VOLUME
VOLUME [“/data”]容器数据卷,用于数据保存和持久化工作 该指令创建具有指定名称的装入点,并将其标记为保存来自本机主机或其他容器的外部装入卷。该值可以是 JSON 数组,也可以是具有多个参数(如 或 )的纯字符串。有关通过 Docker 客户端的更多信息/示例和挂载说明,请参阅通过卷共享目录文档。
VOLUMEVOLUME [“/var/log/“]VOLUME /var/logVOLUME /var/log /var/db
6.3.11 CMD
**<font style="color:#E8323C;">指定容器启动(docker run)后的要干的事情</font>**
CMD 容器启动命令
CMD 指令的格式和 RUN 相似,也是两种格式:
- shell 格式:CMD <命令>
- exec 格式:CMD [“可执行文件”, “参数1”, “参数2”, ……]
- 参数列表格式:CMD[“参数1”, “参数2” ……]。在指定了 ENTRYPOINT 指定后,用 CMD 指定具体的参数
注意
Dockerfile 中可以有很多个 CMD 指令,
<font style="color:#E8323C;">但是只有最后一个生效,CMD会被docker run 之后的参数替换</font>参考官网Tomcat 的 dockerfile 演示介绍
演示覆盖操作
# 官网Dockerfile文件内容......EXPOSE 8080CMD ["catalina.sh", "run"]
$ docker run -it -p 8080:8080 -d billygoo/tomcat8-jdk8:latest /bin/bash# 浏览器将无法访问8080 Tomcat 默认网页
它和前面 RUN 命令的区别
**<font style="color:#E8323C;">CMD 是在 docker run 时运行</font>**
**<font style="color:#E8323C;">RUN 是在 docker build 时运行</font>**
6.3.12 ENTRYPOINT
也是用来指定一个容器启动时要运行的命令
类似于 CMD 指令,但是 <font style="color:#E8323C;">ENTRYPOINT 不会被 docker run 后面的命令覆盖,而且这些命令行参数会被当做参数送给 ENTRYPOINT 指令指定的程序</font>
命令格式和案例说明
命令格式:ENTRYPOINT ["executable", "param1", "param2"]ENTRYPOINT 可以和 CMD 一起使用,一般是"变参"才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参当指定了 ENTRYPOINT 后,CMD 的含义就发生了变化,不再是直接运行其命令而是将 CMD 的内容作为参数传递给 ENTRYPOINT 指令,它两个组合会变成 <ENTRYPOINT> "<CMD>"案例如下:假设已通过Dockerfile 构建了 "nginx:test 镜像"FROM nginxEXPOSE 80ENTRYPOINT ["nginx", "-c"] # 定参CMD ["/etc/nginx/nginx.conf"] # 变参
| 是否传参 | 按照dockerfile编写执行 | 传参运行 |
|---|---|---|
| Docker命令 | docker run nginx:test | docker run nginx:test /etc/nginx/new.conf |
| 衍生出的实际命令 | nginx -c /etc/nginx/nginx.conf | nginx -c /etc/nginx/new.conf |
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
CMD 和 ENTRYPOINT 区别
:::warning CMD # 指定这个容器启动的时候要运行的命令,不可以追加命令
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
:::

6.3.13 小总结



6.4 实战测试
Docker Hub 中 99% 镜像都是从这个基础镜像过来的 FROM scratch ,然后配置需要的软件和配置来进行的构建
6.4.1 创建自己的CentOS镜像
$ mkdir -pv /root/docker/mycentos01 && cd /root/docker/mycentos01#1.编写Dockerfile的文件$ vim Dockerfile#设置基础镜像FROM centos:centos7.9.2009#设置镜像作者MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.com>#配置环境变量ENV MYPATH="/data"#设置工作目录WORKDIR $MYPATH#安装 vim net-tools 软件RUN yum makecache fast && \yum install -y vim net-tools#清空yum缓存RUN yum clean all#提示暴露端口EXPOSE 80#docker build运行的提示RUN echo MYPATH is $MYPATHRUN echo "---> Success ......"#挂载卷VOLUME /data#设置容器启动的命令CMD /bin/bash#2.通过这个文件构建镜像#命令:docker build -f Dockerfile <文件路径> -t 镜像名:[TAG]$ docker build -t kube-centos:2.0 -f Dockerfile .#3.测试运行$ docker run -it --rm kube-centos:2.0 /bin/bash[root@3fe81eda9f21 data]# pwd/data[root@3fe81eda9f21 data]# ifconfigeth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500inet 172.17.0.11 netmask 255.255.0.0 broadcast 172.17.255.255ether 02:42:ac:11:00:0b txqueuelen 0 (Ethernet)RX packets 6 bytes 516 (516.0 B)RX errors 0 dropped 0 overruns 0 frame 0TX packets 0 bytes 0 (0.0 B)TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536inet 127.0.0.1 netmask 255.0.0.0loop txqueuelen 1000 (Local Loopback)RX packets 0 bytes 0 (0.0 B)RX errors 0 dropped 0 overruns 0 frame 0TX packets 0 bytes 0 (0.0 B)TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0[root@3fe81eda9f21 data]# vim --version | head -n 1VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Dec 15 2020 16:44:08)
我们可以列出本地镜像的变更历史

6.4.2 CMD 和 ENTRYPOINT 区别
测试 CMD
#编写Dockerfile文件$ cat dockerfile1FROM centos:centos7.9.2009CMD ls -al#构建镜像$ docker build -t cmdtest:1.0 -f dockerfile1 .#docker run 运行,发现我们的 ls -al 命令生效$ docker run cmdtest:1.0total 12drwxr-xr-x 1 root root 6 Aug 26 03:07 .drwxr-xr-x 1 root root 6 Aug 26 03:07 ..-rwxr-xr-x 1 root root 0 Aug 26 03:07 .dockerenv-rw-r--r-- 1 root root 12114 Nov 13 2020 anaconda-post.loglrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bindrwxr-xr-x 5 root root 340 Aug 26 03:07 devdrwxr-xr-x 1 root root 66 Aug 26 03:07 etcdrwxr-xr-x 2 root root 6 Apr 11 2018 homelrwxrwxrwx 1 root root 7 Nov 13 2020 lib -> usr/liblrwxrwxrwx 1 root root 9 Nov 13 2020 lib64 -> usr/lib64...省略部分输出...#想追加一个命令参数 -h 使其想变成 ls -alh$ docker run -it cmdtest:1.0 -hdocker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "-h": executable file not found in $PATH: unknown.#docker run 后的cmd 中的-h 会完全替换掉 CMD ls -al 命令,-h不是命令所以报错
测试 ENTRYPOINT
$ cat dockerfile2FROM centos:centos7.9.2009ENTRYPOINT ls -al#构建镜像$ docker build -t cmdtest:2.0 -f dockerfile2 .#docker run 运行,发现我们的 ls -al 命令生效$ docker run -it cmdtest:2.0total 12drwxr-xr-x 1 root root 6 Aug 26 03:14 .drwxr-xr-x 1 root root 6 Aug 26 03:14 ..-rwxr-xr-x 1 root root 0 Aug 26 03:14 .dockerenv-rw-r--r-- 1 root root 12114 Nov 13 2020 anaconda-post.loglrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bindrwxr-xr-x 5 root root 360 Aug 26 03:14 devdrwxr-xr-x 1 root root 66 Aug 26 03:14 etcdrwxr-xr-x 2 root root 6 Apr 11 2018 homelrwxrwxrwx 1 root root 7 Nov 13 2020 lib -> usr/liblrwxrwxrwx 1 root root 9 Nov 13 2020 lib64 -> usr/lib64...省略部分输出...#想追加一个命令参数 -h 使其想变成 ls -alh#可以正常使用#追加的命令,是直接拼接在我们的 ENTRYPOINT 命令的后缀$ docker run -it cmdtest:2.0 -htotal 12drwxr-xr-x 1 root root 6 Aug 26 03:15 .drwxr-xr-x 1 root root 6 Aug 26 03:15 ..-rwxr-xr-x 1 root root 0 Aug 26 03:15 .dockerenv-rw-r--r-- 1 root root 12114 Nov 13 2020 anaconda-post.loglrwxrwxrwx 1 root root 7 Nov 13 2020 bin -> usr/bindrwxr-xr-x 5 root root 360 Aug 26 03:15 devdrwxr-xr-x 1 root root 66 Aug 26 03:15 etcdrwxr-xr-x 2 root root 6 Apr 11 2018 homelrwxrwxrwx 1 root root 7 Nov 13 2020 lib -> usr/liblrwxrwxrwx 1 root root 9 Nov 13 2020 lib64 -> usr/lib64
:::warning Dockerfile 中很多命令都十分的相似,我们需要了解它们的区别,对比他们测试效果。
:::
6.4.3 实战:Tomcat 镜像
- 准备镜像文件 Tomcat 镜像,JDK 压缩包
$ mkdir -pv /root/dockerfile/mytomcat01 && cd /root/dockerfile/mytomcat01$ wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.22/bin/apache-tomcat-9.0.22.tar.gz#上传相应的JDK包$ ls /root/dockerfile/mytomcat01/apache-tomcat-9.0.22.tar.gz jdk-8u11-linux-x64.tar.gz
- 编写Dockerfile文件
#官方命名为 Dockerfile,docker build 会自动寻找这个文件,就不需要 -f 指定dockerfile文件了$ vim /root/dockerfile/mytomcat01/Dockerfile$ cat /root/dockerfile/mytomcat01/readme.txt <<EOF#构建 tomcat 镜像docker build -t mytomcat:1.0 -f Dockerfile .EOF$ vim Dockerfile#设置基础镜像FROM centos:centos7.9.2009#设置镜像作者MAINTAINER zhongzhiwei <zhongzhiwei@kubesphere.com>#配置环境变量ENV MYPATH="/usr/local/"#设置工作目录WORKDIR $MYPATH#拷贝 & 压缩文件COPY readme.txt $MYPATHADD apache-tomcat-9.0.22.tar.gz $MYPATHADD jdk-8u11-linux-x64.tar.gz $MYPATH#设置环境变量ENV JAVA_HOME="/usr/local/jdk1.8.0_11"ENV JRE_HOME="/usr/local/jdk1.8.0_11/jre"ENV CLASSPATH="$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar"ENV CATALINA_HOME="/usr/local/apache-tomcat-9.0.22"ENV CATALINA_BASE="/usr/local/apache-tomcat-9.0.22"ENV PATH="$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$CATALINA_BASE/bin:$PATH"#设置成阿里云源RUN rm -f /etc/yum.repos.d/* && \curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo#提示暴露端口EXPOSE 8080#docker build运行的提示RUN echo MYPATH is $MYPATHRUN echo "---> Success ......"#挂载卷VOLUME /data#设置容器启动的命令CMD /usr/local/apache-tomcat-9.0.22/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.22/logs/catalina.out
- 构建镜像
$ docker build -t mytomcat:1.0 .$ docker run -it -d \--name tomcat01 -p 8080:8080 \-v /data/docker/mytomcat01/test:/usr/local/apache-tomcat-9.0.22/webapps/test \-v /data/docker/mytomcat01/logs:/usr/local/apache-tomcat-9.0.22/logs \mytomcat:1.0
- 访问测试
$ curl localhost:8080

- 发布项目(由于做了卷挂载,我们直接在本地编写项目)
$ mkdir -p /data/docker/mytomcat01/test/WEB-INFO$ vim /data/docker/mytomcat01/test/WEB-INFO/web.xml<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"id="WebApp_ID" version="2.5"></web-app>#WEB-INFO需要和index.jsp在同一级目录。$ cat > /data/docker/mytomcat01/test/index.jsp <<EOF<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%><!DOCTYPE html><html><head><meta charset="utf-8"><title>QingCloud (www.kubesphere.com)</title></head><body><%-- 该部分注释在网页中不会被显示--%><p>Hello , World</p></body></html>EOF$ curl 10.0.0.101:8080/test/<!DOCTYPE html><html><head><meta charset="utf-8"><title>QingCloud (www.kubesphere.com)</title></head><body><p>Hello , World</p></body></html>

发现,项目部署完成,可以直接访问OK!
以后开发的步骤,需要掌握 Dockerfile 的编写!我们之前的一切都是使用 Docker 镜像来发布运行的!
7 发布自己的镜像
7.1 发布镜像到 Dockerhub
DockerHub
- DockerHub 地址 https://hub.docker.com/ 注册自己的账号
- 确定这个账号可以登录
- 在我们服务器上提交自己的镜像
$ docker login --helpUsage: docker login [OPTIONS] [SERVER]Log in to a Docker registry.If no server is specified, the default is defined by the daemon.Options:-p, --password string Password--password-stdin Take the password from stdin-u, --username string Username
- 登录完毕后就可以提交镜像了,就是一步 docker push 镜像名:[TAG]
范例:
$ docker login -u "dragonzw" -p "SZzhongaislf"WARNING! Using --password via the CLI is insecure. Use --password-stdin.WARNING! Your password will be stored unencrypted in /root/.docker/config.json.Configure a credential helper to remove this warning. Seehttps://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded#设置镜像的标签$ docker tag mytomcat:1.0 dragonzw/tomcat:1.0#推送镜像到DockerHub$ docker push dragonzw/tomcat:1.0

7.2 发布镜像到 阿里云
$ sudo docker login --username=dragon志伟 registry.cn-shenzhen.aliyuncs.comPassword:WARNING! Your password will be stored unencrypted in /root/.docker/config.json.Configure a credential helper to remove this warning. Seehttps://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded#重新镜像打标签#$ docker tag tomcat:[镜像版本号] registry.cn-shenzhen.aliyuncs.com/dragonzw_personal_images/tomcat:[镜像版本号]$ docker tag mytomcat:1.0 registry.cn-shenzhen.aliyuncs.com/dragonzw_personal_images/tomcat:1.0#推送镜像#$ docker push registry.cn-shenzhen.aliyuncs.com/dragonzw_personal_images/tomcat:[镜像版本号]$ docker push registry.cn-shenzhen.aliyuncs.com/dragonzw_personal_images/tomcat:1.0#$ docker pull registry.cn-shenzhen.aliyuncs.com/dragonzw_personal_images/tomcat:[镜像版本号]$ docker pull registry.cn-shenzhen.aliyuncs.com/dragonzw_personal_images/tomcat:1.0
在阿里云个人的镜像仓库就可以查看到 tomcat 的镜像版本。

:::warning 阿里云容器镜像就可以参考阿里云官方文档即可。
:::
7.3 小结

8 Docker 网络
8.1 理解 docker0
清空所有的 Docker 环境。
$ ip addr show#本地回环网卡1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft foreverinet6 ::1/128 scope hostvalid_lft forever preferred_lft forever#本机的内网IP地址2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000link/ether 00:0c:29:16:73:35 brd ff:ff:ff:ff:ff:ffinet 10.0.0.101/24 brd 10.0.0.255 scope global eth0valid_lft forever preferred_lft foreverinet6 fe80::20c:29ff:fe16:7335/64 scope linkvalid_lft forever preferred_lft forever#docker服务生成的docker0网卡3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group defaultlink/ether 02:42:fc:db:df:7e brd ff:ff:ff:ff:ff:ffinet 172.17.0.1/16 brd 172.17.255.255 scope global docker0valid_lft forever preferred_lft foreverinet6 fe80::42:fcff:fedb:df7e/64 scope linkvalid_lft forever preferred_lft forever
三个网络:
:::warning 问题:docker 是如何处理容器网络访问的?
:::
$ docker run -d -P --name tomcat01 tomcat:8.5.81#查看容器的内部网络地址 ip addr show#发现容器启动的时候会得到一个 eth0@xxx ip地址,docker DHCP分配$ docker exec -it tomcat01 /bin/bashroot@f408a9817eee:/usr/local/tomcat# apt updateroot@f408a9817eee:/usr/local/tomcat# apt install -y iproute2 iputils-ping &> /dev/nullroot@767f7355e9eb:/usr/local/tomcat# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever77: eth0@if78: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group defaultlink/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0valid_lft forever preferred_lft forever#思考:Linux能不能ping通容器内部$ ping -c1 -W1 172.17.0.2PING 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.034 ms#Linux 可以ping通 docker容器内部
原理:
- 每启动一个 docker 容器,docker 就会给 docker 容器分配一个 IP 地址,安装了docker,就会又一个网卡 docker0
桥接模式,使用的技术是 veth-pair 技术
再次测试宿主机 ip addr

- 在启动一个 tomcat 容器测试,发现又多了一对网卡!
$ docker run -d -P --name tomcat02 tomcat$ docker exec -it tomcat02 /bin/bashroot@15eb260c7094:/usr/local/tomcat# apt updateroot@15eb260c7094:/usr/local/tomcat# apt install -y iproute2 iputils-ping &> /dev/nullroot@15eb260c7094:/usr/local/tomcat# ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft forever79: eth0@if80: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group defaultlink/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0valid_lft forever preferred_lft forever

:::warning 我们发现这个容器带来的网卡,都是一对一对的。
veth-pair 就是一对虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连
正因为有这个特性,veth-pair 充当了一个桥接,链接各种虚拟网络设备
OpenStack Docker容器之间连接 OVS 连接 都是使用 veth-pair 技术。
:::
- 我们来测试一下 tomcat01 和 tomcat02 是否可以ping 通
$ docker exec -it tomcat01 /bin/bashroot@767f7355e9eb:/usr/local/tomcat# ping -c 1 -W 1 172.17.0.3PING 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.069 ms--- 172.17.0.3 ping statistics ---1 packets transmitted, 1 received, 0% packet loss, time 0msrtt min/avg/max/mdev = 0.069/0.069/0.069/0.000 ms#结论:容器和容器之间是可以相互ping通的!

:::warning 路由的定义是指路由寻址功能,跨网段的时候会生成一个路由表,转发到对应的接口属于三层;
二层交换只能在局域网(同网段)连接。
:::
:::warning 结论:tomcat01 和 tomcat02 是拥有一个公用的路由器 docker0
所有的容器不指定网络的情况下,都是 docker0 路由的,Docker 会给我们的容器分配一个默认的可用的IP地址
Docker 默认的网段是 172.17.0.0/16 B类地址默认可以存放 65535 个地址
:::
小结:
Docker中 docker0 使用的是 Linux 的桥接技术,docker0 和 容器之间是使用 veth-pair 虚拟接口技术。
即宿主机中是一个 Docker 的网桥:docker0。Docker 中的所有的网络接口都是虚拟的。虚拟的转发效率高(内网传递数据)。
只要容器删除,对应的网桥就没了(即虚拟接口 veth-pair 也会消失)。

docker network inspect bridge

8.2 —link
思考一个场景:我们编写了一个微服务,database url=ip,项目不重启,数据库IP地址掉了,我们希望可以处理这个问题,可以通过名字来访问容器?
$ docker exec -it tomcat01 ping tomcat02ping: tomcat02: Name or service not known#默认是ping 不通名字#如何进行解决呢?#使用--link就可以解决网络连通问题$ docker run -it -d --name tomcat03 --link tomcat02 tomcat#tomcat03 ping tomcat02$ docker exec -it tomcat03 ping -c 1 -W 1 tomcat02PING tomcat02 (172.17.0.3) 56(84) bytes of data.64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.121 ms--- tomcat02 ping statistics ---1 packets transmitted, 1 received, 0% packet loss, time 0msrtt min/avg/max/mdev = 0.121/0.121/0.121/0.000 ms#tomcat02 ping tomcat03$ docker exec -it tomcat02 ping tomcat03ping: tomcat03: Name or service not known#查看bridge网络模式下的详细信息$ docker network inspect bridge
探究 docker network inspect

$ docker inspect tomcat03 | grep -A 3 "Links""Links": ["/tomcat02:/tomcat03/tomcat02"],#说明了tomcat03 和 tomcat02 的额绑定关系,=/接收容器名/源容器名或者别名
其实这个 tomcat03 就是在本地配置了 tomcat02 的信息配置
#登录tomcat03 的容器$ docker exec -it tomcat03 /bin/bashroot@868350cec9a0:/usr/local/tomcat# cat /etc/resolv.conf# Generated by NetworkManagernameserver 114.114.114.114nameserver 8.8.8.8root@868350cec9a0:/usr/local/tomcat# cat /etc/hosts127.0.0.1 localhost::1 localhost ip6-localhost ip6-loopbackfe00::0 ip6-localnetff00::0 ip6-mcastprefixff02::1 ip6-allnodesff02::2 ip6-allrouters172.17.0.3 tomcat02 15eb260c7094172.17.0.4 868350cec9a0root@868350cec9a0:/usr/local/tomcat# ping -c 1 -W 1 tomcat02PING tomcat02 (172.17.0.3) 56(84) bytes of data.64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.096 ms--- tomcat02 ping statistics ---1 packets transmitted, 1 received, 0% packet loss, time 0msrtt min/avg/max/mdev = 0.096/0.096/0.096/0.000 ms#--link 就是在容器的 /etc/hosts 配置中增加了一个 172.17.0.3 tomcat02 15eb260c7094 的条目信息#探究:--link 本质就是容器中 /etc/hosts 新增相应的条目。
现在 Docker 已经不建议使用 —link 了;目前推荐使用自定义网络。
默认 docker0 问题,不支持容器名连接访问
8.3 自定义网络
容器互联技术:—link 和 自定义网络
#查看所有的Docker网络信息$ docker network lsNETWORK ID NAME DRIVER SCOPE4490c3a848b4 bridge bridge local2373b4b3186d host host local5536bc79b4b6 none null local
Docker 网络模式
| 网络模型 | 简介 |
|---|---|
| bridge | 为每一个容器分配、设置IP等,并将容器连接到一个<font style="color:#E8323C;">docker0</font>虚拟网桥,默认为该模式 |
| host | 容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口 |
| none | 容器有独立的 Network Namespace(网络名称空间),但是并没有对其进行任何网络设置,如分配 veth pair 和网桥连接,IP等。(基本上不会使用) |
| container | 新创建的容器不会创建自己的网卡和配置自己的IP,而是和一个指定的容器共享IP,端口范围等 |
docker network 主流的是 bridge、host、none,常用的是 bridge、host。
:::warning bridge:桥接 docker (默认,自定义网络也是 bridge 模式)
none:不配置网络
host:和宿主机共享网络
container:容器网络联通(局限性很大)
:::
测试:
#直接启动命令默认会添加--net bridge,而这个就是docker0$ docker run -it -d -P --name tomcat01 tomcat#等价于$ docker run -it -d -P --name tomcat01 --net bridge tomcat#docker0特点:默认,容器间的域名是不通,--link可以打通连通#可以创建自定义网络#--driver bridge :管理网络的驱动程序(默认为“桥接”)#--subnet 192.168.0.0/16 :CIDR格式的子网,表示一个网段#--gateway 192.168.0.1 :主子网的IPv4或IPv6网关$ docker network create \--driver bridge \--subnet 192.168.0.0/16 \--gateway 192.168.0.1 mynet$ docker network lsNETWORK ID NAME DRIVER SCOPEe79095f379fb bridge bridge local993e748549d7 host host local3e91d8f383af mynet bridge local4bf3eb064757 none null local
自定义网络就已经创建完毕了

$ docker run -it -d --name alpine01 --network mynet alpine$ docker run -it -d --name alpine02 --network mynet alpine$ docker inspect alpine02 | grep -A 18 "Networks""Networks": {"mynet": {"IPAMConfig": null,"Links": null,"Aliases": ["348e6354072b"],"NetworkID": "5aa4b631f9ef3b6189496fd2bdccf673f18dcee69232b09cb4095870eef20387","EndpointID": "64fdcb4b0dd537b2aeef757b8f2114ed84187aec7c99ec3ea611d375b0646c2a","Gateway": "192.168.0.1","IPAddress": "192.168.0.3","IPPrefixLen": 16,"IPv6Gateway": "","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"MacAddress": "02:42:c0:a8:00:03","DriverOpts": null}}#再次测试 ping 连接#首先使用alpine01 ping alpine02的IP地址$ docker exec -it alpine01 ping -c 1 -W 1 192.168.0.3PING 192.168.0.3 (192.168.0.3): 56 data bytes64 bytes from 192.168.0.3: seq=0 ttl=64 time=0.094 ms--- 192.168.0.3 ping statistics ---1 packets transmitted, 1 packets received, 0% packet lossround-trip min/avg/max = 0.094/0.094/0.094 ms#再次直接使用 alpine01 ping alpine02#现在不使用--link也可以 ping 名字!!!$ docker exec -it alpine01 ping -c 1 -W 1 alpine02PING alpine02 (192.168.0.3): 56 data bytes64 bytes from 192.168.0.3: seq=0 ttl=64 time=0.127 ms--- alpine02 ping statistics ---1 packets transmitted, 1 packets received, 0% packet lossround-trip min/avg/max = 0.127/0.127/0.127 ms
自定义的网络 Docker 都已经帮运维/开发 维护好了对应的关系,推荐使用自定义网络。
原理就是这两个容器都是连的自定义网络,连接在同一个自定义网络的容器之间端口会自动相互暴露,而且不会向以外的显示任何端口,这样就更好的进行了容器见相互通信和隔离
好处:不同的集群使用不同的自定义网络,保证集群是安全和健康的。
8.4 网络连通

$ docker run -it --name alpine01-docker0 --network bridge -d alpine$ docker run -it --name alpine02-docker0 --network bridge -d alpine$ docker run -it -d --name alpine01 --network mynet alpine$ docker run -it -d --name alpine02 --network mynet alpine#默认不同的网络命名空间是不能够相互通信的$ docker exec -it alpine01-docker0 ping -c 1 -W 1 192.168.0.2PING 192.168.0.2 (192.168.0.2): 56 data bytes--- 192.168.0.2 ping statistics ---1 packets transmitted, 0 packets received, 100% packet loss$ docker exec -it alpine01-docker0 ping alpine01ping: bad address 'alpine01'

#测试打通alpine01-docker0 - alpine01$ docker network connect mynet alpine01-docker0#联通之后,就是将 alpine01-docker0 放到了 mynet 网络下$ docker inspect alpine01-docker0#一个容器两个IP地址

#再次测试$ docker exec -it alpine01-docker0 ping -c 1 -W 1 192.168.0.2PING 192.168.0.2 (192.168.0.2): 56 data bytes64 bytes from 192.168.0.2: seq=0 ttl=64 time=0.088 ms--- 192.168.0.2 ping statistics ---1 packets transmitted, 1 packets received, 0% packet lossround-trip min/avg/max = 0.088/0.088/0.088 ms$ docker exec -it alpine01-docker0 ping -c 1 -W 1 alpine01PING alpine01 (192.168.0.2): 56 data bytes64 bytes from 192.168.0.2: seq=0 ttl=64 time=0.117 ms--- alpine01 ping statistics ---1 packets transmitted, 1 packets received, 0% packet lossround-trip min/avg/max = 0.117/0.117/0.117 ms#alpine02-docker0 还是依旧无法联通alpine01 alpine02
结论:假设要跨网络操作其他容器,就需要使用 docker network connect 联通了!
8.5 实战:部署 Redis 集群

#创建网卡$ docker network create redis --subnet 172.38.0.0/16#批量创建六个 Redis的Docker容器for port in {1..6} ;domkdir -pv /data/redis/node-${port}/conftouch /data/redis/node-${port}/conf/redis.confcat << EOF > /data/redis/node-${port}/conf/redis.confport 6379bind 0.0.0.0cluster-enabled yescluster-config-file nodes.confcluster-node-timeout 5000cluster-announce-ip 172.38.0.1${port}cluster-announce-port 6379cluster-announce-bus-port 16379appendonly yesEOFdonefor port in {1..6} ; dodocker run -p 637${port}:6379 -p 1637${port}:16379 --name redis-${port} \-v /data/redis/node-${port}/data:/data \-v /data/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf \-d --net redis --ip 172.38.0.1${port} redis:5.0.9-alpine3.11 \/usr/local/bin/redis-server /etc/redis/redis.confdone#范例:docker run -p 6371:6379 -p 16371:16379 --name redis-1 \-v /data/redis/node-1/data:/data \-v /data/redis/node-1/conf/redis.conf:/etc/redis/redis.conf-d --net redis --ip 172.38.0.11 redis:5.0.9-alpine3.11 \/usr/local/bin/redis-server /etc/redis/redis.conf
范例:
#创建Redis集群redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 \172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1$ docker exec -it redis-1 /bin/sh/data # redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 \> 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1>>> Performing hash slots allocation on 6 nodes...Master[0] -> Slots 0 - 5460Master[1] -> Slots 5461 - 10922Master[2] -> Slots 10923 - 16383Adding replica 172.38.0.15:6379 to 172.38.0.11:6379Adding replica 172.38.0.16:6379 to 172.38.0.12:6379Adding replica 172.38.0.14:6379 to 172.38.0.13:6379M: 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379slots:[0-5460] (5461 slots) masterM: 2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379slots:[5461-10922] (5462 slots) masterM: 5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379slots:[10923-16383] (5461 slots) masterS: 2d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379replicates 5090c8478f101c5f1520b9264cab5d602befd6a8S: 55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379replicates 24a8d8f3302ce28f1b9b06ce3bec871adb8585dfS: f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379replicates 2eff279dc91be6f8840a59f990b58ac740ebfa01Can I set the above configuration? (type 'yes' to accept): yes>>> Nodes configuration updated>>> Assign a different config epoch to each node>>> Sending CLUSTER MEET messages to join the clusterWaiting for the cluster to join...>>> Performing Cluster Check (using node 172.38.0.11:6379)M: 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379slots:[0-5460] (5461 slots) master1 additional replica(s)S: 2d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379slots: (0 slots) slavereplicates 5090c8478f101c5f1520b9264cab5d602befd6a8S: 55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379slots: (0 slots) slavereplicates 24a8d8f3302ce28f1b9b06ce3bec871adb8585dfM: 5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379slots:[10923-16383] (5461 slots) master1 additional replica(s)M: 2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379slots:[5461-10922] (5462 slots) master1 additional replica(s)S: f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379slots: (0 slots) slavereplicates 2eff279dc91be6f8840a59f990b58ac740ebfa01[OK] All nodes agree about slots configuration.>>> Check for open slots...>>> Check slots coverage...[OK] All 16384 slots covered./data # redis-cli -c#查看Redis集群的信息127.0.0.1:6379> cluster infocluster_state:okcluster_slots_assigned:16384cluster_slots_ok:16384cluster_slots_pfail:0cluster_slots_fail:0cluster_known_nodes:6cluster_size:3cluster_current_epoch:6cluster_my_epoch:1cluster_stats_messages_ping_sent:249cluster_stats_messages_pong_sent:250cluster_stats_messages_sent:499cluster_stats_messages_ping_received:245cluster_stats_messages_pong_received:249cluster_stats_messages_meet_received:5cluster_stats_messages_received:499#查看Redis集群节点健康127.0.0.1:6379> cluster nodes24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379@16379 myself,master - 0 1661526536000 1 connected 0-54602d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379@16379 slave 5090c8478f101c5f1520b9264cab5d602befd6a8 0 1661526537442 4 connected55d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379@16379 slave 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 0 1661526537000 5 connected5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379@16379 master - 0 1661526536000 3 connected 10923-163832eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379@16379 master - 0 1661526536433 2 connected 5461-10922f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379@16379 slave 2eff279dc91be6f8840a59f990b58ac740ebfa01 0 1661526535526 6 connected127.0.0.1:6379> set k1 v1-> Redirected to slot [12706] located at 172.38.0.13:6379 #redis-3 的容器操作OK#redis-3暂停后再次查看/data # redis-cli -c127.0.0.1:6379> cluster nodes24a8d8f3302ce28f1b9b06ce3bec871adb8585df 172.38.0.11:6379@16379 myself,master - 0 1661526692000 1 connected 0-54602d2733a06379f90a2c51c0e131b0391160d8f6b8 172.38.0.14:6379@16379 master - 0 1661526693000 7 connected 10923-1638355d41b51e6053d6dcf9b730a79ce6cca1314e363 172.38.0.15:6379@16379 slave 24a8d8f3302ce28f1b9b06ce3bec871adb8585df 0 1661526693143 5 connected5090c8478f101c5f1520b9264cab5d602befd6a8 172.38.0.13:6379@16379 master,fail - 1661526636272 1661526634000 3 connected2eff279dc91be6f8840a59f990b58ac740ebfa01 172.38.0.12:6379@16379 master - 0 1661526692740 2 connected 5461-10922f8d17750650743957b846ccaab8799fca7c301a9 172.38.0.16:6379@16379 slave 2eff279dc91be6f8840a59f990b58ac740ebfa01 0 1661526691733 6 connected127.0.0.1:6379> get k1-> Redirected to slot [12706] located at 172.38.0.14:6379 #redis-4 从容器变成Master"v1"#将redis-3 容器暂停$ docker stop redis-3
9 将SpringBoot 微服务打包成Docker镜像
- 构建 Spring Boot 项目

package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class HelloController {@RequestMapping("/hello")public String hello() {return "hello,kubesphere";}}

- 打包应用

- 编写 Dockerfile
#使用基础镜像FROM java:8#将jar包拷贝到容器内ADD *.jar /app.jar#提示指定暴露端口EXPOSE 8080CMD ["--server.port=8080"]ENTRYPOINT ["java", "-jar", "/app.jar"]
- 构建镜像
$ mkdir -pv /data/springboot ; cd /data/springboot$ ls -lhtotal 16M-rw-r--r-- 1 root root 16M Aug 26 23:29 demo-0.0.1-SNAPSHOT.jar-rw-r--r-- 1 root root 186 Aug 26 23:30 Dockerfile$ docker images myapp:1.0REPOSITORY TAG IMAGE ID CREATED SIZEmyapp 1.0 b29e611caf73 8 seconds ago 660MB
- 发布运行
$ docker run -it -d --name myspringboot-web -p 8080:8080 myapp:1.0$ curl 10.0.0.101:8080/hellohello,kubesphere

以后使用了 Docker 之后,给运维或者开发交付就是一个镜像即可!
预告:如果有很多镜像?怎么处理呢?解决方法:Docker Compose / Docker Swarm
企业实战- Docker Compose
- Docker Swarm
