- 一、Docker概述
- 二、Docker的安装
- 正确推荐使用国内的
- 三、底层原理
- 四、Docker常用命令
- 4. 常用其他命令
- 5. 小结
- 五、部署练习
- 2. 部署安装Tomcat
- 3. 部署安装es + kibana
- 六、可视化
- 七、Docker镜像讲解
- 八、容器数据卷
- 2. 使用数据卷
- 层层递进执行
- 查看自定义的镜像
- 九、DockerFile
- 表示注释
- 4. DockerFile指令
- 5. 实战测试
- 6. 自定义镜像Tomcat
- 7. 发布镜像
- 1、查看登录命令
- 2、登录
- 3、将镜像发布出去
- 拒绝:请求的资源访问被拒绝
- 问题:本地镜像名无帐号信息,解决加 tag即可
- 再次 push, ok
- 十、Docker 网络讲解
- 我们启动了一个tomcat01,主机的ip地址多了一个 9: veth08f8f9d@if8
- 然后我们在tomcat01容器中查看容器的ip是 8: eth0@if9
- 我们再启动一个tomcat02观察
- 然后发现linux主机上又多了一个网卡 11: veth034f6d4@if10
- 我们看下tomcat02的容器内ip地址是 10: eth0@if11
- 观察现象:
- tomcat —- linux主机 veth08f8f9d@if8 —— 容器内 eth0@if9
- tomcat —- linux主机 veth021eeea@if10 —— 容器内 eth0@if11
- 相信到了这里,大家应该能看出点小猫腻了吧!只要启动一个容器,就有一对网卡
- veth-pair 就是一对的虚拟设备接口,它都是成对出现的。一端连着协议栈,一端彼此相连着。
- 正因为有这个特性,它常常充当着一个桥梁,连接着各种虚拟网络设备!
- “Bridge、OVS 之间的连接”,“Docker 容器之间的连接” 等等,以此构建出非常复杂的虚拟网络结构,比如 OpenStack Neutron
- 2. —Link
- 3. 自定义网络
- 4. 网络连通
- 5. 实战:部署一个Redis集群
- 十一、IDEA整合Docker
- 2. 打包镜像
- 十二、DockerCompose
- 十三、Docker Swarm
- 十四、CI/CD之Jenkins
- 十五、IDEA安装插件
- 十六、Docker私服
一、Docker概述
1. Docker为什么会出现
一款产品从开发到上线,从操作系统,到运行环境,再到应用配置。作为开发+运维之间的协作我们需要关心很多东西,这也是很多互联网公司都不得不面对的问题,特别是各种版本的迭代之后,不同版本环境的兼容,对运维人员是极大的考验!
环境配置如此麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。解决开发人员说的“ 在我的机器上可正常工作”的问题。
之前在服务器配置一个应用的运行环境,要安装各种软件,就拿一个基本的工程项目的环境来说吧,Java/Tomcat/MySQL/JDBC驱动包等。安装和配置这些东西有多麻烦就不说了,它还不能跨平台。假如我们是在 Windows 上安装的这些环境到了 Linux 又得重新装。况且就算不跨操作系统,换另一台同样操作系统的服务器,要移植应用也是非常麻烦的
传统上认为,软件编码开发/测试结束后,所产出的成果即是程序或是能够编译执行的二进制字节码文件 等(Java为例)。而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,开发需要清楚的告诉运维部署团队,用的全部配置文件+所有软件环境。不过,即便如此,仍然常常发生部署失败的状况。
Docker之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案:Docker支持将软件编译成一个镜像,然后在镜像中各种软件做好配置,将镜像发布出去,其他使用者可以直接使用这个镜像;
Docker镜像的设计,使得Docker得以打破过去「程序即应用」的观念。通过Docker镜像 ( images ) 将应用程序所需要的系统环境,由下而上打包,达到应用程序跨平台间的无缝接轨运作。
<br /> <br /> <br />Docker的思想来自于集装箱,集装箱解决了什么问题?在一艘大船上,可以把货物规整的摆放起来。并且各种各样的货物被集装箱标准化了,集装箱和集装箱之间不会互相影响。那么我就不需要专门运送水果的船和专门运送化学品的船了。只要这些货物在集装箱里封装的好好的,那我就可以用一艘大船把他们都运走。
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这样的巨头,都对它青睐有加,表示将全力支持。
Docker和容器技术为什么会这么火爆?说白了,就是因为它“轻”。 在容器技术之前,业界的网红是虚拟机。虚拟机技术的代表,是VMWare和OpenStack。
相信很多人都用过虚拟机。虚拟机,就是在你的操作系统里面,装一个软件,然后通过这个软件,再模 拟一台甚至多台“子电脑”出来。
在“子电脑”里,你可以和正常电脑一样运行程序,例如开QQ。如果你愿意,你可以变出好几个“子电脑”,里面都开上QQ。“子电脑”和“子电脑”之间,是相互隔离的,互不影响。
虚拟机属于虚拟化技术。而Docker这样的容器技术,也是虚拟化技术,属于轻量级的虚拟化。
虚拟机虽然可以隔离出很多“子电脑”,但占用空间更大,启动更慢,虚拟机软件可能还要花钱(例如 VMWare)。 而容器技术恰好没有这些缺点。它不需要虚拟出整个操作系统,只需要虚拟一个小规模的环境(类似“沙箱”)。
它启动时间很快,几秒钟就能完成。而且,它对资源的利用率很高(一台主机可以同时运行几千个 Docker容器)。此外,它占的空间很小,虚拟机一般要几GB到几十GB的空间,而容器只需要MB级甚至 KB级。
Docker理念
Docker是基于Go语言实现的云开源项目。
Docker的主要目标是“Build,Ship and Run Any App , Anywhere”,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP(可以是一个WEB应用或数据库应用等等)及其运行 环境能够做到“一次封装,到处运行”。
Linux 容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的。将应用运行在 Docker 容器上面,而 Docker 容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。
3. Docker能干嘛
之前的虚拟机技术
虚拟机(virtual machine)就是带环境安装的一种解决方案。
它可以在一种操作系统里面运行另一种操作系统,比如在Windows 系统里面运行Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够使应用程序, 操作系统和硬件三者之间的逻辑不变。
虚拟机的缺点:
- 资源占用多
- 冗余步骤多
- 启动慢
容器虚拟化技术
由于前面虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。有了容器,就可以将软件运行所需的 所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行
比较了 Docker 和传统虚拟化方式的不同之处:
- 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
- 而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。 因此容器要比传统虚拟机更为轻便。
- 每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源
开发/运维(DevOps)
更快速的应用交付和部署:
传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
更便捷的升级和扩缩容:
随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块“积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级
更简单的系统运维:
应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复
更高效的计算资源利用:
Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的Hypervisor [管理程序] 支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。
学习途径
仓库账户密码
二、Docker的安装
1. Docker的基本组成
Docker的架构图
镜像(image):
Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很 多容器。 就好似 Java 中的 类和对象,类就是镜像,容器就是对象!
容器(container):
Docker 利用容器(Container)独立运行的一个或一组应用。容器是用镜像创建的运行实例。 它可以被启动、开始、停止、删除。每个容器都是相互隔离的,保证安全的平台。 可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等) 和运行在其中的应用程序 容器的定义和镜像几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的
仓库(repository):
仓库(Repository)是集中存放镜像文件的场所。 仓库(Repository)和仓库注册服务器(Registry)是有区别的。仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag) 仓库分为公开仓库(Public)和私有仓库(Private)两种形式。 最大的公开仓库是 Docker Hub(https://hub.docker.com/),存放了数量庞大的镜像供用户下载。 国内的公开仓库包括阿里云 、网易云等
小结:
需要正确的理解仓储/镜像/容器这几个概念 :
- Docker 本身是一个容器运行载体或称之为管理引擎。我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就似乎 image镜像文件。只有通过这个镜像文件才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。 同一个 image 文件,可以生成多个同时运行的容器实例。
- image 文件生成的容器实例,本身也是一个文件,称为镜像文件
- 一个容器运行一种服务,当我们需要的时候,就可以通过docker客户端创建一个对应的运行实例,也就是我们的容器
- 至于仓库,就是放了一堆镜像的地方,我们可以把镜像发布到仓库中,需要的时候从仓库中拉下来就可以了
2. 环境说明
我们使用的是 CentOS 7 (64-bit) 目前,
CentOS 仅发行版本中的内核支持 Docker。 Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上。
查看自己的内核: uname -r
:命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)
[root@notfunny /]# uname -r
3.10.0-957.el7.x86_64
cat /etc/os-release
查看版本信息:
[root@notfunny /]# cat /etc/os-release
NAME="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"
3. 安装步骤
安装在Linux CentOS7上
- 官网安装参考手册:https://docs.docker.com/engine/install/centos/
- 确定你是CentOS7及以上版本,我们已经做过了
yum安装gcc相关环境(需要确保 虚拟机可以上外网 )
yum -y install gcc yum -y install gcc-c++
卸载旧版本(可省略)
yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
安装需要的软件包
yum install -y yum-utils yum -y install wget wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
设置镜像仓库 ```shell yum-config-manager —add-repo https://download.docker.com/linux/centos/docker-ce.repo
报错
[Errno 14] curl#35 - TCP connection reset by peer [Errno 12] curl#35 - Timeout
正确推荐使用国内的
yum-config-manager —add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
7. 更新yum软件包索引
```shell
yum makecache faster
安装 Docker CE(ce 社区版 ee 企业版)
yum install docker-ce docker-ce-cli containerd.io
启动停止 Docker
systemctl start docker systemctl stop docker systemctl status docker
测试是否安装成功命令
docker version docker run hello-world docker images
卸载
#停止Docker systemctl stop docker #卸载依赖 yum remove docker-ce docker-ce-cli containerd.io #删除资源 c
4. 阿里云镜像加速
- 介绍:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
- 注册一个属于自己的阿里云账户(可复用淘宝账号)
- 进入管理控制台设置密码,开通
查看镜像加速器自己的

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://3sjs604r.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
5. 启动Hello World Docker做了什么
启动hello-world
docker run hello-world

三、底层原理
Docker是怎么工作的
Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上, 然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。 容器是一个运行时环境,就是我们前面说到的集装箱
为什么Docker比较 VM 快
- docker有着比虚拟机更少的抽象层。由于docker不需要Hypervisor实现硬件资源虚拟化,运行在 docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。
docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机 一样重新加载一个操作系统内核。仍而避免引寻、加载操作系统内核返个比较费时费资源的过程,当新建 一个虚拟机时,虚拟机软件需要加载Guest OS,返个新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返个过程,因此新建一个docker容器只需要几秒钟。

四、Docker常用命令
1. 帮助命令
docker version #显示 Docker 版本信息。
docker info #显示 Docker 系统信息,包括镜像和容器数。。
docker --help #帮助
2. 镜像命令
docker images
# 列出本地主机上的镜像
[root@GMF ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d1165f221234 3 days ago 13.3kB
参数解释:
- REPOSITORY 镜像的仓库源
- TAG 镜像的标签
- IMAGE ID 镜像的ID
- CREATED 镜像创建时间
- SIZE 镜像大小
注意:同一个仓库源可以有多个TAG,代表这个仓库源的不同版本,我们使用REPOSITORY:TAG 定义不同的镜像,如果你不定义镜像的标签版本,docker将默认使用 latest 镜像
命令可选参数
-a
: 列出本地所有镜像-q
: 只显示镜像id--digests
: 显示镜像的摘要信息
docker search
docker search 某个镜像的名称 对应DockerHub仓库中的镜像
[root@GMF ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 10589 [OK]
该命令于官网DockerHub搜索无异
命令可选参数
--filter=stars=50
: 列出收藏数不小于指定值的镜像
docker pull
使用该命令下载镜像
[root@GMF ~]# docker pull mysql
Using default tag: latest # 不写tag,默认是latest
latest: Pulling from library/mysql
a076a628af6f: Pull complete # 分层下载
f6c208f3f991: Pull complete
88a9455a9165: Pull complete
406c9b8427c6: Pull complete
7c88599c0b25: Pull complete
25b5c6debdaf: Pull complete
43a5816f1617: Pull complete
1a8c919e89bf: Pull complete
9f3cf4bd1a07: Pull complete
80539cea118d: Pull complete
201b3cad54ce: Pull complete
944ba37e1c06: Pull complete
Digest: sha256:feada149cb8ff54eade1336da7c1d080c4a1c7ed82b5e320efb5beebed85ae8c # 签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest # 真实位置
指定版本下载
使用 docker pull 【镜像名称】:【版本号】下载指定版本
注意:下载的版本必须在仓库存在
[root@GMF ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
a076a628af6f: Already exists # 分层下载的好处:曾经下载过的层级内容,不用重复性下载
f6c208f3f991: Already exists
88a9455a9165: Already exists
406c9b8427c6: Already exists
7c88599c0b25: Already exists
25b5c6debdaf: Already exists
43a5816f1617: Already exists
1831ac1245f4: Pull complete
37677b8c1f79: Pull complete
27e4ac3b0f6e: Pull complete
7227baa8c445: Pull complete
Digest: sha256:b3d1eff023f698cd433695c9506171f0d08a8f92a0c8063c1a4d9db9a55808df
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
docker rmi
# 删除镜像
docker rmi -f 镜像id # 删除单个
docker rmi -f 镜像名:tag 镜像名:tag # 删除多个
docker rmi -f $(docker images -qa) # 删除全部
[root@GMF ~]# docker rmi -f a70d36bc331a
Untagged: mysql:5.7
Untagged: mysql@sha256:b3d1eff023f698cd433695c9506171f0d08a8f92a0c8063c1a4d9db9a55808df
Deleted: sha256:a70d36bc331a13d297f882d3d63137d24b804f29fa67158c40ad91d5050c39c5
Deleted: sha256:50c77bf7bcddd1f1d97789d80ac2404eec22c860c104e858620d2a2e321f0ef7
Deleted: sha256:14244329b83dfc8982398ee4104a548385652d2bffb957798ff86a419013efd6
Deleted: sha256:6d990477f90af28473eb601a9bca22253f6381e053c5a8edda0a4f027e124a3c
Deleted: sha256:ee0449796df204071589162fc16f8d65586312a40c68d1ba156c93c56f5e5ce8
3. 容器命令
说明:我们有了镜像才可以创建容器,我们这里使用 centos 的镜像来测试,就是虚拟一个 centos !
docker pull centos
新建容器并启动
使用命令 docker run [OPTIONS] IMAGE [COMMAND][ARG...]
命令可选参数
--name="Name"
:给容器指定一个名字-d
:后台方式运行容器,并返回容器的id!‘-i
:以交互模式运行容器,通过和 -t 一起使用-t
:给容器重新分配一个终端,通常和 -i 一起使用-P
:随机端口映射(大写)-p
:指定端口映射(小结),一般可以有四种写法ip:hostPort:containerPort
ip:containerPort
hostPort:containerPort
(常用)containerPort
测试
使用centos进行用交互模式启动容器,在容器内执行/bin/bash命令!
[root@GMF ~]# docker run -it centos /bin/bash
[root@8d951ee8e4bb /]# ls # 注意地址,已经切换到容器内部了
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@8d951ee8e4bb /]# exit #停止并退出容器
exit
[root@GMF ~]#
列出所有运行的容器
使用命令:docker ps [OPTIONS]
命令可选参数:
-a
:列出当前所有正在运行的容器 + 历史运行过的容器-l
:显示最近创建的容器-n=?
:显示最近n个创建的容器-q
:静默模式,只显示容器编号
测试
[root@GMF ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8d951ee8e4bb centos "/bin/bash" 5 minutes ago Exited (130) 3 minutes ago pedantic_kalam
8d62f4ae9292 hello-world "/hello" 12 hours ago Exited (0) 12 hours ago peaceful_goldstine
退出容器
容器停止并退出:exit
容器不停止退出:ctrl+P+Q
(注意大写)
启动停止容器
启动容器:docker start (容器id or 容器名)
重启容器:docker restart (容器id or 容器名)
停止容器:docker stop (容器id or 容器名)
强制停止容器:docker kill (容器id or 容器名)
删除容器
删除指定容器:docker rm 容器id
删除所有容器:docker rm -f $(docker ps -aq)
删除所有容器:docker ps -aq | xargs docker rm
4. 常用其他命令
后台启动容器
命令:docker run -d 容器名
例子:
[root@GMF ~]# docker run -d centos # 启动centos,使用后台方式启动
46d128c5292941f280d7168462723f20ab1d88ee6cd7ba49201af193e920dcc7
[root@GMF ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 问题:使用docker ps 查看,发现容器已经退出了!
# 解释:Docker容器后台运行,就必须有一个前台进程,容器运行的命令如果不是那些一直挂起的命令,就会自动退出
# 比如,你运行了nginx服务,但是docker前台没有运行应用,这种情况下,容器启动后,会立即自杀,因为他觉得没有程序了,所以最好的情况是,
将你的应用使用前台进程的方式运行启动。
查看日志
命令:docker logs -ft --tail 容器id
Options
Name, shorthand | Default | Description |
---|---|---|
--details |
Show extra details provided to logs | |
--follow , -f |
跟踪输出日志 | |
--since |
Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) | |
--tail , -n |
all |
从日志的末尾开始显示的行数,不制定行数则显示全部 |
--timestamps , -t |
显示时间戳 | |
--until |
API 1.35+ Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
#例子:我们启动 centos,并编写一段脚本来测试玩玩!最后查看日志
#后台启动centos执行命令循环脚本
[root@GMF gmf]# docker run -d centos /bin/sh -c "while true;do echo gmf;sleep 1;done"
6d2b201a6c67e61c41b01f957baf39182272707b9bf179085484c0b7c85e05a6
[root@GMF gmf]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6d2b201a6c67 centos "/bin/sh -c 'while t…" 5 seconds ago Up 4 seconds relaxed_moore
#打印日志
[root@GMF gmf]# docker logs -tf --tail 10 6d2b201a6c67
2021-03-11T02:53:34.986121367Z gmf
2021-03-11T02:53:35.988565277Z gmf
2021-03-11T02:53:36.990630020Z gmf
2021-03-11T02:53:37.993910528Z gmf
2021-03-11T02:53:38.996815843Z gmf
2021-03-11T02:53:40.000302194Z gmf
2021-03-11T02:53:41.002921595Z gmf
2021-03-11T02:53:42.005537332Z gmf
2021-03-11T02:53:43.008263773Z gmf
2021-03-11T02:53:44.011294812Z gmf
#输出完10条日志后,继续执行脚本打印
2021-03-11T02:53:45.013602163Z gmf
2021-03-11T02:53:46.017708614Z gmf
2021-03-11T02:53:47.019909861Z gmf
2021-03-11T02:53:48.022338963Z gmf
查看容器中运行的进程信息,支持 ps 命令参数
命令:docker top 容器id
[root@GMF gmf]# docker top 58a936bc9724
UID PID PPID C STIME TTY TIME CMD
root 11148 11127 0 11:02 ? 00:00:00 /bin/sh -c while true;do echo gmf;sleep 1;done
root 11213 11148 0 11:02 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
查看容器/镜像的元数据
命令:docker inspect 容器id
[root@GMF gmf]# docker inspect 58a936bc9724
[
{
"Id": "58a936bc9724bce73e3292af36a596a68b8a5012d1904407451c82e5ac2aac85",
"Created": "2021-03-11T03:02:16.150241523Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true;do echo gmf;sleep 1;done"
],
"State": {
"Status": "exited",
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 0,
"ExitCode": 137,
"Error": "",
"StartedAt": "2021-03-11T03:02:16.615623658Z",
"FinishedAt": "2021-03-11T03:03:12.113963282Z"
},
"Image": "sha256:300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55",
"ResolvConfPath": "/var/lib/docker/containers/58a936bc9724bce73e3292af36a596a68b8a5012d1904407451c82e5ac2aac85/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/58a936bc9724bce73e3292af36a596a68b8a5012d1904407451c82e5ac2aac85/hostname",
"HostsPath": "/var/lib/docker/containers/58a936bc9724bce73e3292af36a596a68b8a5012d1904407451c82e5ac2aac85/hosts",
"LogPath": "/var/lib/docker/containers/58a936bc9724bce73e3292af36a596a68b8a5012d1904407451c82e5ac2aac85/58a936bc9724bce73e3292af36a596a68b8a5012d1904407451c82e5ac2aac85-json.log",
"Name": "/gallant_chandrasekhar",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
...
}
]
进入正在运行的容器
命令:docker exec [OPTIONS] 容器id COMMAND [ARG...]
提示:使用exec命令进入容器再通过exit退出后不会结束运行容器
Options
Name, shorthand | Default | Description |
---|---|---|
--detach , -d |
Detached mode: run command in the background | |
--detach-keys |
Override the key sequence for detaching a container | |
--env , -e |
API 1.25+ Set environment variables |
|
--env-file |
API 1.25+ Read in a file of environment variables |
|
--interactive , -i |
Keep STDIN open even if not attached | |
--privileged |
Give extended privileges to the command | |
--tty , -t |
Allocate a pseudo-TTY | |
--user , -u |
Username or UID (format: |
|
--workdir , -w |
API 1.35+ Working directory inside the container |
#创建容器,Ctrl+P+Q退出容器
[root@GMF gmf]# docker run -it centos /bin/bash
[root@e2f5af099e21 /]# [root@GMF gmf]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e2f5af099e21 centos "/bin/bash" 26 seconds ago Up 24 seconds infallible_gagarin
#进入指定容器
[root@GMF gmf]# docker exec -it e2f5af099e21 /bin/bash
[root@e2f5af099e21 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 03:26 pts/0 00:00:00 /bin/bash
root 17 0 0 03:30 pts/1 00:00:00 /bin/bash
root 32 17 0 03:30 pts/1 00:00:00 ps -ef
命令:docker attach [OPTIONS] 容器id
[root@GMF gmf]# docker attach e2f5af099e21
[root@e2f5af099e21 /]#
区别:
exec
:是在容器中打开新的终端,并且可以启动新的进程attach
:直接进入容器启动命令的终端,不会启动新的进程
从容器内拷贝文件到主机上
命令:docker cp 容器id:容器内路径 目的主机路径
[root@GMF gmf]# ls
Desktop Documents Downloads Music Pictures Public Templates Videos
[root@GMF gmf]# docker start e2f5af099e21
e2f5af099e21
#进入容器创建文件
[root@GMF gmf]# docker attach e2f5af099e21
[root@e2f5af099e21 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@e2f5af099e21 /]# touch gmf.java
[root@e2f5af099e21 /]# ls
bin dev etc gmf.java home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@e2f5af099e21 /]# exit
exit
#执行拷贝操作
[root@GMF gmf]# docker cp e2f5af099e21:/gmf.java ./kyn.java
[root@GMF gmf]# ls
Desktop Documents Downloads kyn.java Music Pictures Public Templates Videos
查看容器运行的cpu内存和网络状态
命令:docker stats 容器id
5. 小结

常用命令
attach Attach to a running container # 当前shell下attach 连接指定运行镜像
build Build an image from a Dockerfile # 通过 Dockerfile 定制镜像
commit Create a new image from a container changes # 提交当前容器为新的镜像
cp Copy files/folders from the containers filesystem to the host path #从容器中拷贝指定文件或者目录到宿主机中
create Create a new container # 创建一个新的容器,同run,但不启动容器
diff Inspect changes on a container\'s filesystem # 查看 docker 容器变化
events Get real time events from the server # 从 docker 服务获取容器实时事件
exec Run a command in an existing container # 在已存在的容器上运行命令
export Stream the contents of a container as a tar archive # 导出容器的内容流作为一个 tar 归档文件[对应 import ]
history Show the history of an image # 展示一个镜像形成历史
images List images # 列出系统当前镜像
import Create a new filesystem image from the contents of a tarball # 从tar包中的内容创建一个新的文件系统映像[对应export]
info Display system-wide information # 显示系统相关信息
inspect Return low-level information on a container # 查看容器详细信息
kill Kill a running container # kill 指定 docker 容器
load Load an image from a tar archive # 从一个 tar 包中加载一个镜像[对应 save]
login Register or Login to the docker registry server # 注册或者登陆一个docker 源服务器
logout Log out from a Docker registry server # 从当前 Dockerregistry 退出
logs Fetch the logs of a container # 输出当前容器日志信息
port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT #查看映射端口对应的容器内部源端口
pause Pause all processes within a container # 暂停容器
ps List containers # 列出容器列表
pull Pull an image or a repository from the docker registry server #从docker镜像源服务器拉取指定镜像或者库镜像
push Push an image or a repository to the docker registry server #推送指定镜像或者库镜像至docker源服务器
restart Restart a running container # 重启运行的容器
rm Remove one or more containers # 移除一个或者多个容器
rmi Remove one or more images # 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除]
run Run a command in a new container # 创建一个新的容器并运行一个命令
save Save an image to a tar archive # 保存一个镜像为一个tar 包[对应 load]
search Search for an image on the Docker Hub # 在 docker hub 中搜索镜像
start Start a stopped containers # 启动容器
stop Stop a running containers # 停止容器
tag Tag an image into a repository # 给源中镜像打标签
top Lookup the running processes of a container # 查看容器中运行的进程信息
unpause Unpause a paused container # 取消暂停容器
version Show the docker version information # 查看 docker 版本号
wait Block until a container stops, then print its exit code # 截取容器停止时的退出状态值
五、部署练习
1. 部署安装nginx
搜索镜像
建议去DockerHub上搜索,能够查看详细版本信息
[root@GMF sysconfig]# docker search nginx
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
nginx Official build of Nginx. 14547 [OK]
jwilder/nginx-proxy Automated Nginx reverse proxy for docker con… 1982 [OK]
拉取镜像
[root@GMF sysconfig]# docker pull nginx
Using default tag: latest
[root@GMF sysconfig]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest f6d0b4767a6c 8 weeks ago 133MB
启动镜像创建容器
#-p 宿主机端口:容器内部端口
[root@GMF ~]# docker run -d --name nginx1 -p 3304:80 nginx
d34b9c752ef478092ac3b0bf2679b95710d8ee32de3c3922ca46f2c6373c17a9
[root@GMF ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d34b9c752ef4 nginx "/docker-entrypoint.…" 25 seconds ago Up 24 seconds 0.0.0.0:3304->80/tcp nginx1
e2f5af099e21 centos "/bin/bash" 3 hours ago Exited (0) 2 hours ago infallible_gagarin
6d2b201a6c67 centos "/bin/sh -c 'while t…" 3 hours ago Exited (137) 3 hours ago relaxed_moore
宿主机端口与容器内部端口图解
测试访问
[root@GMF ~]# curl localhost:3304
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
#防火墙开启3304端口访问
[root@GMF ~]# firewall-cmd --zone=public --add-port=3304/tcp
success
访问 http://172.20.10.2:3304/ 显示如下页面
进入容器
#进入nginx容器
[root@GMF ~]# docker exec -it d34b9c752ef4 /bin/bash
root@d34b9c752ef4:/# ls
bin dev docker-entrypoint.sh home lib64 mnt proc run srv tmp var
boot docker-entrypoint.d etc lib media opt root sbin sys usr
#查找nginx路径
root@d34b9c752ef4:/# whereis nginx
nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx
#进入nginx路径
root@d34b9c752ef4:/# cd /etc/nginx/
#查看内部文件
root@d34b9c752ef4:/etc/nginx# ls
conf.d fastcgi_params koi-utf koi-win mime.types modules nginx.conf scgi_params uwsgi_params win-utf
思考问题:我们以后要部署项目,还需要进入容器中,是不是十分麻烦,要是有一种技术,可以将容器 内和我们Linux进行映射挂载就好了?我们后面会将数据卷技术来进行挂载操作(-v 数据卷)
2. 部署安装Tomcat
#官方文档介绍
#-it:文档交互式
#--rm:容器启动成功并退出以后容器就自动移除,一般在测试情况下使用!
docker run -it --rm tomcat:9.0
#1. 下载Tomcat镜像
docker pull Tomcat
#2. 启动
[root@GMF ~]# docker run -d --name tomcat9 -p 8080:8080 tomcat
[root@GMF ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a0b2c65f396f tomcat "catalina.sh run" 16 seconds ago Up 15 seconds 0.0.0.0:8080->8080/tcp tomcat9
当我们测试访问http://192.168.43.11:8080/时显示404
进入Tomcat中查找原因:
#进入Tomcat容器,查看相关目录
[root@GMF ~]# docker exec -it a0b2c65f396f /bin/bash
root@a0b2c65f396f:/usr/local/tomcat# ls -al
total 128
drwxr-xr-x. 1 root root 30 Jan 13 08:25 .
drwxr-xr-x. 1 root root 20 Jan 13 08:19 ..
-rw-r--r--. 1 root root 18982 Dec 3 11:48 BUILDING.txt
-rw-r--r--. 1 root root 5409 Dec 3 11:48 CONTRIBUTING.md
-rw-r--r--. 1 root root 57092 Dec 3 11:48 LICENSE
-rw-r--r--. 1 root root 2333 Dec 3 11:48 NOTICE
-rw-r--r--. 1 root root 3257 Dec 3 11:48 README.md
-rw-r--r--. 1 root root 6898 Dec 3 11:48 RELEASE-NOTES
-rw-r--r--. 1 root root 16507 Dec 3 11:48 RUNNING.txt
drwxr-xr-x. 2 root root 4096 Jan 13 08:25 bin
drwxr-xr-x. 1 root root 22 Mar 11 07:15 conf
drwxr-xr-x. 2 root root 4096 Jan 13 08:25 lib
drwxrwxrwx. 1 root root 177 Mar 11 07:15 logs
drwxr-xr-x. 2 root root 134 Jan 13 08:25 native-jni-lib
drwxrwxrwx. 2 root root 30 Jan 13 08:25 temp
drwxr-xr-x. 2 root root 6 Jan 13 08:25 webapps
drwxr-xr-x. 7 root root 81 Dec 3 11:45 webapps.dist
drwxrwxrwx. 2 root root 6 Dec 3 11:43 work
#进入webapps项目目录,发现内部为空
root@a0b2c65f396f:/usr/local/tomcat# cd webapps
root@a0b2c65f396f:/usr/local/tomcat/webapps# ls
root@a0b2c65f396f:/usr/local/tomcat/webapps#
总结问题:
- Linux 命令变少了
- webapps中没有内容
原因:使用阿里云镜像默认是最小的镜像,所有不必要的内容都会剔除掉,保证最小可运行的环境!
再深入探究:
#在webapps.dist目录下依旧存在默认工程
root@a0b2c65f396f:/usr/local/tomcat/webapps# cd ../webapps.dist/
root@a0b2c65f396f:/usr/local/tomcat/webapps.dist# ls
ROOT docs examples host-manager manager
#将webapps.dist中的内容复制到webapps
root@a0b2c65f396f:/usr/local/tomcat# cd ../
root@a0b2c65f396f:/usr/local/tomcat# cp -r webapps.dist/* webapps/
root@a0b2c65f396f:/usr/local/tomcat# cd webapps
root@a0b2c65f396f:/usr/local/tomcat/webapps# ls
ROOT docs examples host-manager manager
重新访问路径,显示以下内容
思考:我们以后要部署项目,还需要进入容器中,是不是十分麻烦,要是有一种技术,可以将容器 内和我们Linux进行映射挂载就好了?我们后面会将数据卷技术来进行挂载操作
3. 部署安装es + kibana
我们启动es这种容器需要考虑几个问题
- 端口暴露问题 9200、9300
- 数据卷的挂载问题 data、plugins、conf
- 吃内存
-e ES_JAVA_OPTS="-Xms512m -Xmx512m"
#1. 启动es测试
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2
#2. 启动之后很卡,使用 docker stats 容器id 查看下cpu状态 ,发现占用的很大
#3. 测试访问
curl localhost:9200
思考:如果我们要使用 kibana , 如果配置连接上我们的es呢?网络该如何配置呢?
六、可视化
Portainer(先用这个)
docker run -d -p 8088:9000 \ --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
Rancher(CI/CD再用这个)
#安装rancher-server docker run --name rancher-server -p 8000:8080 -v /etc/localtime:/etc/localtime:ro -d rancher/server #安装agent docker run --rm --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/rancher:/var/lib/rancher rancher/agent:v1.2.11 http://39.101.191.131:8000/v1/scripts/D3DBD43F263109BB881F:1577750400000:7M0y BzCw4XSxJklD7TpysYIpI
1. Portainer介绍
Portainer是Docker的图形化管理工具,提供状态显示面板、应用模板快速部署、容器镜像网络数据卷 的基本操作(包括上传下载镜像,创建容器等操作)、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录用户管理和控制等功能。功能十分全面,基本能满足中小型单位对容器管理的全部需求。
如果仅有一个docker宿主机,则可使用单机版运行,Portainer单机版运行十分简单,只需要一条语句即可启动容器,来管理该机器上的docker镜像、容器等数据
docker run -d -p 8088:9000 \
--restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
访问测试:
注意开启防火墙端口访问
http://192.168.31.216:8088/
[root@GMF ~]# docker run -d -p 8088:9000 \
> --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
Unable to find image 'portainer/portainer:latest' locally
latest: Pulling from portainer/portainer
d1e017099d17: Pull complete
717377b83d5c: Pull complete
Digest: sha256:f8c2b0a9ca640edf508a8a0830cf1963a1e0d2fd9936a64104b3f658e120b868
Status: Downloaded newer image for portainer/portainer:latest
8af9e6f0882d2f459f0cb9596eb7c20262812575002dcb433de9f2b29b6d7fc8
[root@GMF ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8af9e6f0882d portainer/portainer "/portainer" 5 seconds ago Up 3 seconds 0.0.0.0:8088->9000/tcp boring_ellis
a0b2c65f396f tomcat "catalina.sh run" 2 hours ago Up 2 hours 0.0.0.0:8080->8080/tcp tomcat9
[root@GMF ~]# firewall-cmd --zone=public --add-port=8088/tcp
success
首次登陆需要注册用户,给GMF用户设置密码,这里已经注册了账户之间登陆即可:
单机版这里选择local即可,选择完毕,点击Connect即可连接到本地docker:
登录成功!
我们可以点击可视化界面来玩玩!
2. Rancher介绍
核心概念
CI/CD 是一种通过在应用开发阶段引入自动化来频繁向客户交付应用的方法。CI/CD 的核心概念是持续集成、持续交付和持续部署。作为一个面向开发和运营团队的解决方案,CI/CD 主要针对在集成新代码时所引发的问题
具体而言,CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。这些关联的事务通常被统称为“CI/CD 管道”,由开发和运维团队以敏捷方式协同支持
CI 是什么?CI 和 CD 有什么区别?
缩略词 CI / CD 具有几个不同的含义。CI/CD 中的“CI”始终指持续集成,它属于开发人员的自动化流程。成功的 CI 意味着应用代码的新更改会定期构建、测试并合并到共享存储库中。该解决方案可以解决在一次开发中有太多应用分支,从而导致相互冲突的问题。
CI/CD 中的“CD”指的是持续交付和/或持续部署,这些相关概念有时会交叉使用。两者都事关管道后续阶段的自动化,但它们有时也会单独使用,用于说明自动化程度。
持续交付通常是指开发人员对应用的更改会自动进行错误测试并上传到存储库(如 GitHub 或容器注册表),然后由运维团队将其部署到实时生产环境中。这旨在解决开发和运维团队之间可见性及沟通较差的问题。因此,持续交付的目的就是确保尽可能减少部署新代码时所需的工作量。
持续部署(另一种“CD”)指的是自动将开发人员的更改从存储库发布到生产环境,以供客户使用。它主要为了解决因手动流程降低应用交付速度,从而使运维团队超负荷的问题。持续部署以持续交付的优势为根基,实现了管道后续阶段的自动化。
CI/CD 既可能仅指持续集成和持续交付构成的关联环节,也可以指持续集成、持续交付和持续部署这三项构成的关联环节。更为复杂的是,有时“持续交付”也包含了持续部署流程。
归根结底,我们没必要纠结于这些语义,您只需记得 CI/CD 其实就是一个流程(通常形象地表述为管道),用于实现应用开发中的高度持续自动化和持续监控。因案例而异,该术语的具体含义取决于 CI/CD 管道的自动化程度。许多企业最开始先添加 CI,然后逐步实现交付和部署的自动化(例如作为云原生应用的一部分)。
CI 持续集成(Continuous Integration)
现代应用开发的目标是让多位开发人员同时处理同一应用的不同功能。但是,如果企业安排在一天内将所有分支源代码合并在一起(称为“合并日”),最终可能造成工作繁琐、耗时,而且需要手动完成。这是因为当一位独立工作的开发人员对应用进行更改时,有可能会与其他开发人员同时进行的更改发生冲突。如果每个开发人员都自定义自己的本地集成开发环境(IDE),而不是让团队就一个基于云的 IDE 达成一致,那么就会让问题更加雪上加霜。
持续集成(CI)可以帮助开发人员更加频繁地(有时甚至每天)将代码更改合并到共享分支或“主干”中。一旦开发人员对应用所做的更改被合并,系统就会通过自动构建应用并运行不同级别的自动化测试(通常是单元测试和集成测试)来验证这些更改,确保这些更改没有对应用造成破坏。这意味着测试内容涵盖了从类和函数到构成整个应用的不同模块。如果自动化测试发现新代码和现有代码之间存在冲突,CI 可以更加轻松地快速修复这些错误。
CD 持续交付(Continuous Delivery)
完成 CI 中构建及单元测试和集成测试的自动化流程后,持续交付可自动将已验证的代码发布到存储库。为了实现高效的持续交付流程,务必要确保 CI 已内置于开发管道。持续交付的目标是拥有一个可随时部署到生产环境的代码库。
在持续交付中,每个阶段(从代码更改的合并,到生产就绪型构建版本的交付)都涉及测试自动化和代码发布自动化。在流程结束时,运维团队可以快速、轻松地将应用部署到生产环境中。
CD 持续部署(Continuous Deployment)
对于一个成熟的 CI/CD 管道来说,最后的阶段是持续部署。作为持续交付——自动将生产就绪型构建版本发布到代码存储库——的延伸,持续部署可以自动将应用发布到生产环境。由于在生产之前的管道阶段没有手动门控,因此持续部署在很大程度上都得依赖精心设计的测试自动化。
实际上,持续部署意味着开发人员对应用的更改在编写后的几分钟内就能生效(假设它通过了自动化测试)。这更加便于持续接收和整合用户反馈。总而言之,所有这些 CI/CD 的关联步骤都有助于降低应用的部署风险,因此更便于以小件的方式(而非一次性)发布对应用的更改。不过,由于还需要编写自动化测试以适应 CI/CD 管道中的各种测试和发布阶段,因此前期投资还是会很大。
七、Docker镜像讲解
1. 镜像是什么
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件
所有的应用可以打包Docker镜像,就可以直接跑起来
如何得到镜像
- 从DockerHub上下载
- 拷贝镜像文件
- 自己制作DockerFile镜像
2. Docker镜像加载原理
2.1 UnionFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统, 它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件 系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
2.2 Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS
bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是 一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标 准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。
平时我们安装进虚拟机的CentOS都是好几个G,为什么Docker这里才200M?
对于一个精简的OS,rootfs 可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的linux发行版, bootfs基本是一 致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。
3. 分层理解
分层的镜像
我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层一层的在下载!
思考:为什么Docker镜像要采用这种分层的结构呢?
最大的好处,我觉得莫过于是资源共享了!比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。
查看镜像分层的方式可以通过 docker inspect 镜像名or镜像id
命令查看
[root@GMF ~]# docker inspect redis
[
...
#层级镜像
"Layers": [
"sha256:cb42413394c4059335228c137fe884ff3ab8946a014014309676c25e3ac86864",
"sha256:8e14cb7841faede6e42ab797f915c329c22f3b39026f8338c4c75de26e5d4e82",
"sha256:1450b8f0019c829e638ab5c1f3c2674d117517669e41dd2d0409a668e0807e96",
"sha256:f927192cc30cb53065dc266f78ff12dc06651d6eb84088e82be2d98ac47d42a0",
"sha256:a24a292d018421783c491bc72f6601908cb844b17427bac92f0a22f5fd809665",
"sha256:3480f9cdd491225670e9899786128ffe47054b0a5d54c48f6b10623d2f340632"
]
...
]
理解:
所有的 Docker 镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于 Ubuntu Linux 16.04 创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。
该镜像当前已经包含 3 个镜像层,如下图所示(这只是一个用于演示的很简单的例子)
在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子,每个镜像层包含 3 个文件,而镜像包含了来自两个镜像层的 6 个文件。
上图中的镜像层跟之前图中的略有区别,主要目的是便于展示文件。
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有 6 个文件,这是因为最上层中的文件 7 是文件 5 的一个更新版本。
这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中
Docker 通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。
Linux 上可用的存储引擎有 AUFS、Overlay2、Device Mapper、Btrfs 以及 ZFS。顾名思义,每种存储引擎都基于 Linux 中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。
Docker 在 Windows 上仅支持 windowsfilter 一种存储引擎,该引擎基于 NTFS 文件系统之上实现了分层和 CoW[1]。
下图展示了与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。
特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层就是我们通常说的容器层,容器之下的都叫镜像层!
镜像层永不改变,我编写的内容会存储到镜像层上的容器层中
4. 镜像Commit
docker commit -m="提交的信息" -a="作者" 容器id 要创建的目标镜像名:[TAG]
命令提交容器副本使之成为一个新的镜像!
测试
#1. 启动运行Tomcat容器
#2. 下载tomcat官方镜像,就是这个镜像(阿里云里的tomcat的webapps下没有任何文件),将webapps.dest中的文件拷贝到webapps中
root@a0b2c65f396f:/usr/local/tomcat# cp -r webapps.dist/* webapps
#3. 自定义的Tomcat容器提交到本地镜像仓库中
#注意:提交的镜像容器的名字不能有大写,否则报错:invalid reference format
[root@GMF ~]# docker commit -a="GMF" -m="add apps to webapps" a0b2c65f396f customizeTomcat:1.0
invalid reference format: repository name must be lowercase
[root@GMF ~]# docker commit -a="GMF" -m="add apps to webapps" a0b2c65f396f customizetomcat:1.0
sha256:6963bd3775081ce36affaa2bd565dbb774d0cb228f9d4fb5198f71e523961fbd
[root@GMF ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
customizetomcat 1.0 6963bd377508 5 seconds ago 654MB
hello-world latest d1165f221234 5 days ago 13.3kB
mysql latest c8562eaf9d81 7 weeks ago 546MB
redis latest 621ceef7494a 8 weeks ago 104MB
tomcat 9.0 040bdb29ab37 8 weeks ago 649MB
tomcat latest 040bdb29ab37 8 weeks ago 649MB
nginx latest f6d0b4767a6c 8 weeks ago 133MB
centos latest 300e315adb2f 3 months ago 209MB
portainer/portainer latest 62771b0b9b09 7 months ago 79.1MB
elasticsearch 7.6.2 f29a1ee41030 11 months ago 791MB
#4. 创建自定义镜像的容器
[root@GMF ~]# docker run -d --rm --name customizeTomcat -p 8080:8080 6963bd377508
8fe1c26c144d60bd5b4b4f517a9fa445c9885d472e54975cac58651beaf2fbe3
[root@GMF ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8fe1c26c144d 6963bd377508 "catalina.sh run" 3 seconds ago Up 2 seconds 0.0.0.0:8080->8080/tcp customizeTomcat
8af9e6f0882d portainer/portainer "/portainer" 7 hours ago Up 7 hours 0.0.0.0:8088->9000/tcp boring_ellis
访问http://192.168.31.254:8080/页面显示如下,证明自定义镜像提交成功
八、容器数据卷
1. 什么是容器数据卷
Docker理念回顾
将应用和运行的环境打包形成容器运行,运行可以伴随着容器,但是我们对于数据的要求,是希望能够持久化的!
就好比,你安装一个MySQL,结果你把容器删了,就相当于删库跑路了,Are you fucking kidding me?
所以我们希望容器之间有可能可以共享数据,Docker容器产生的数据,如果不通过docker commit 生成新的镜像,使得数据作为镜像的一部分保存下来,那么当容器删除后,数据自然也就没有了!这样是行不通的!
为了能保存数据在Docker中我们就可以使用卷!让数据挂载到我们本地!这样数据就不会因为容器删除而丢失了!
作用
卷就是目录或者文件,存在一个或者多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过 Union File System , 提供一些用于持续存储或共享数据的特性
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
案例
在集群中有多台tomcat,对于集群中的tomcat部署的代码是同一份代码的副本,如果页面文件发生变化,意味着每个容器中的页面文件都要进行更新,在大规模的集群中这样的工作量会被无限放大,这时候就需要用到数据共享解决此问题,所谓数据共享指的是多个容器共享一个数据副本
特点
- 数据卷可在容器之间共享或重用数据
- 卷中的更改可以同步生效
- 数据卷中的更改不会包含在镜像的更新中
- 数据卷的生命周期一直持续到没有容器使用它为止
2. 使用数据卷
方式一:容器中直接使用命令来挂载
命令:docker run -it -v 宿主机绝对路径目录:容器内目录 镜像名
测试
#运行镜像容器并设置挂载
[root@GMF ~]# docker run -it -v /user/test:/test centos
#查看数据卷是否挂载成功
[root@GMF ~]# docker inspect ef2753b9fe4d
测试容器和宿主机之间数据共享:可以发现,在容器中创建的会在宿主机中看到!
测试容器停止退出后,主机修改数据是否会同步!
MySQL数据持久化
首先启动容器
-e
:设置环境变量(密码)-v
:挂载卷使MySQL数据不丢失-d
:后台运行-p
:端口映射--name
:容器名称docker run -d -p 3306:3306 -v /user/mysql/conf:/etc/mysql/conf.d -v /user/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql01 mysql:5.7.33
使用本地的SQLyog连接测试一下:SQLyog连接到服务器的3306,服务器3306和容器内的3306映射
查看本地的mysql/data目录
[root@GMF ~]# cd /user/mysql/data
[root@GMF data]# ls
auto.cnf ca.pem client-key.pem ibdata1 ib_logfile1 mysql private_key.pem server-cert.pem sys
ca-key.pem client-cert.pem ib_buffer_pool ib_logfile0 ibtmp1 performance_schema public_key.pem server-key.pem
#SQLyog添加新建user库后再次查看mysql/data目录
[root@GMF data]# ls
auto.cnf ca.pem client-key.pem ibdata1 ib_logfile1 mysql private_key.pem server-cert.pem sys
ca-key.pem client-cert.pem ib_buffer_pool ib_logfile0 ibtmp1 performance_schema public_key.pem server-key.pem user
删除mysql容器
[root@GMF mysql]# docker rm -f mysql01 # 删除容器,然后发现远程连接失败!
#可以看到我们刚刚建立的mysql数据库在本地存储着
[root@GMF mysql]# ls
conf data
具名和匿名以及路径挂载
-v 容器内路径
:匿名挂载-v 卷名:容器内路径
:具名挂载-v /宿主机路径:容器内路径
:路径挂载
具名挂载与路径挂载通过 是否以/开头 区分
指定容器内被挂载的路径其中内容的读写权限
ro
:readonlyrw
:readwrite
# 使用匿名挂载(只写了容器内的路径)运行容器
[root@GMF mysql]# docker run -d -P --name nginx1 -v /etc/nginx nginx
4c6bb073b55451ae023145e95a9e8ad0d6a0f9620bdc892815485d80e26d8d28
# 匿名挂载的缺点,就是不好维护,通常使用命令 docker volume 维护
# 查看所有匿名/具名挂载的名称
[root@GMF mysql]# docker volume ls
DRIVER VOLUME NAME
local 97802a2f975a9d78871a4b31f03a53c22077d33c3c2c42c683f5312a11c6790c
local ffec1e956fb82459bd513b5f8ccb4f64ebf02739967068c5404f93fbe16b6c22
#使用具名挂载(具名:容器内路径)运行容器
[root@GMF mysql]# docker run -d -P --name nginx2 -v jvming-nginx:/etc/nginx nginx
97004583dd37ba10c43eee469eff18b1a150ce0245625aa5e627783d0591d930
# 查看所有的具/匿名挂载信息
[root@GMF mysql]# docker volume ls
DRIVER VOLUME NAME
local 97802a2f975a9d78871a4b31f03a53c22077d33c3c2c42c683f5312a11c6790c
local ffec1e956fb82459bd513b5f8ccb4f64ebf02739967068c5404f93fbe16b6c22
local jvming-nginx
#通过挂载的名称查看具体的宿主机映射路径
[root@GMF mysql]# docker volume inspect jvming-nginx
[
{
"CreatedAt": "2021-03-12T11:50:28+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/jvming-nginx/_data", #所在的路径
"Name": "jvming-nginx",
"Options": null,
"Scope": "local"
}
]
#所有docker相关的文件都在这个目录下
[root@GMF mysql]# cd /var/lib/docker/
[root@GMF docker]# ls
buildkit containers image network overlay2 plugins runtimes swarm tmp trust volumes
#进入volumes文件夹,存放所有的被具/匿挂载的文件
[root@GMF docker]# cd volumes/
[root@GMF volumes]# ll
total 24
drwx-----x. 3 root root 19 Mar 11 17:29 97802a2f975a9d78871a4b31f03a53c22077d33c3c2c42c683f5312a11c6790c
brw-------. 1 root root 253, 0 Mar 12 09:59 backingFsBlockDev
drwx-----x. 3 root root 19 Mar 12 11:45 ffec1e956fb82459bd513b5f8ccb4f64ebf02739967068c5404f93fbe16b6c22
drwx-----x. 3 root root 19 Mar 12 11:50 jvming-nginx
-rw-------. 1 root root 32768 Mar 12 11:50 metadata.db
[root@GMF volumes]# cd jvming-nginx/_data
[root@GMF _data]# ls
conf.d fastcgi_params koi-utf koi-win mime.types modules nginx.conf scgi_params uwsgi_params win-utf
# 指定容器对我们挂载出来的内容的读写权限
docker run -d -P --name nginx3 -v nginxConfig:/etc/nginx:ro nginx
方式二:通过Docker File 来添挂载
DockerFile 是用来构建Docker镜像的构建文件,是由一些列命令和参数构成的脚本。 我们在这里,先体验下,后面我们会详细讲解 DockerFile
镜像是由多个层级构成的,DockerFile文件一样也是由多个命令构成的
在宿主机下创建一个自定义容器的文件夹
[root@GMF user]# mkdir customizeContainer
编写DockerFile文件
[root@GMF customizeContainer]# vim dockerFile
[root@GMF customizeContainer]# cat dockerFile
FROM centos
# VOLUME代表在容器内定义被挂载的文件
VOLUME ["dataVolumeContainer1","dataVolumeContainer2"]
CMD echo "=====end====="
CMD /bin/bash
- 说明:在编写DockerFile文件中使用
VOLUME
指令来给镜像添加一个或多个数据卷 - 使用这种方式的好处:出于可移植和分享的考虑,我们之前使用的
-v 主机目录:容器目录
这种方式不能够直接在 DockerFile 中实现。由于宿主机目录是依赖于特定宿主机的,并不能够保证在所有宿主机上都存在这样的特定目录.
使用自定义的DockerFile生成一个对应的镜像
- 使用 docker build 命令执行DockerFile文件生成镜像
-f
:指定DockerFile文件路径-t
:指定镜像的名称 ```shell [root@GMF customizeContainer]# docker build -f /user/customizeContainer/dockerFile -t gmf/centos .层层递进执行
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos —-> 300e315adb2f Step 2/4 : VOLUME [“dataVolumeContainer1”,”dataVolumeContainer2”] —-> Running in 56c3004d0eb4 Removing intermediate container 56c3004d0eb4 —-> 9f095d54814e Step 3/4 : CMD echo “=====end=====” —-> Running in 57526c1ab6b6 Removing intermediate container 57526c1ab6b6 —-> b0b48ca4f3f9 Step 4/4 : CMD /bin/bash —-> Running in d3ab74b5617a Removing intermediate container d3ab74b5617a —-> c6eb36a53381 Successfully built c6eb36a53381 Successfully tagged gmf/centos:latest
查看自定义的镜像
[root@GMF customizeContainer]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE gmf/centos latest c6eb36a53381 About a minute ago 209MB
**创建自定义镜像的容器,查看被挂载的文件**
```shell
[root@GMF customizeContainer]# docker run -it c6eb36a53381 /bin/bash
[root@6d1c2e258149 /]# ls -al
total 0
# 查看发现被挂载的文件
drwxr-xr-x. 2 root root 6 Mar 12 06:34 dataVolumeContainer1
drwxr-xr-x. 2 root root 6 Mar 12 06:34 dataVolumeContainer2
drwxr-xr-x. 5 root root 360 Mar 12 06:34 dev
drwxr-xr-x. 1 root root 66 Mar 12 06:34 etc
drwxr-xr-x. 2 root root 6 Nov 3 15:22 home
lrwxrwxrwx. 1 root root 7 Nov 3 15:22 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Nov 3 15:22 lib64 -> usr/lib64
drwx------. 2 root root 6 Dec 4 17:37 lost+found
drwxr-xr-x. 2 root root 6 Nov 3 15:22 media
drwxr-xr-x. 2 root root 6 Nov 3 15:22 mnt
drwxr-xr-x. 2 root root 6 Nov 3 15:22 opt
dr-xr-xr-x. 250 root root 0 Mar 12 06:34 proc
dr-xr-x---. 2 root root 162 Dec 4 17:37 root
drwxr-xr-x. 11 root root 163 Dec 4 17:37 run
lrwxrwxrwx. 1 root root 8 Nov 3 15:22 sbin -> usr/sbin
drwxr-xr-x. 2 root root 6 Nov 3 15:22 srv
dr-xr-xr-x. 13 root root 0 Mar 12 01:51 sys
drwxrwxrwt. 7 root root 145 Dec 4 17:37 tmp
drwxr-xr-x. 12 root root 144 Dec 4 17:37 usr
drwxr-xr-x. 20 root root 262 Dec 4 17:37 var
问题:通过上述步骤,容器内的卷目录地址就已经知道了,但是对应的主机目录地址在哪里呢?
查看挂载绑定性
在容器内被挂载的文件夹中创建一个文件
[root@6d1c2e258149 /]# touch dataVolumeContainer1/container.txt
[root@6d1c2e258149 /]# cd dataVolumeContainer1
[root@6d1c2e258149 dataVolumeContainer1]# ls
container.txt
查看宿主机中挂载地址,查看其中的文件
[root@GMF customizeContainer]# docker inspect 6d1c2e258149
...
"Mounts": [
{
"Type": "volume",
"Name": "56c1aea057d487cab7f01afcc9a75d5a8a25777a05444cd2cab2fe08c82345b0",
"Source": "/var/lib/docker/volumes/56c1aea057d487cab7f01afcc9a75d5a8a25777a05444cd2cab2fe08c82345b0/_data",
"Destination": "dataVolumeContainer1",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "17039f6e51df0d936b7cb0ee8acbf014a0286cf96d3bab3009ccdbc92f22ccd1",
"Source": "/var/lib/docker/volumes/17039f6e51df0d936b7cb0ee8acbf014a0286cf96d3bab3009ccdbc92f22ccd1/_data",
"Destination": "dataVolumeContainer2",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
...
#进入宿主机中匿名挂载的地址,发现存在容器被挂载文件夹中相同的文件 container.txt
[root@GMF customizeContainer]# cd /var/lib/docker/volumes/56c1aea057d487cab7f01afcc9a75d5a8a25777a05444cd2cab2fe08c82345b0/_data
[root@GMF _data]# ls
container.txt
3. 数据容器卷
前言:在上一章节了解了如何进行容器数据卷挂载,但也只是在单个容器与宿主机中进行挂载,如果想要在多个容器直接实现数据挂载,就需要使用到下面的方式
命名的容器挂载数据卷,其他容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器,称之为数据卷容器。
我们使用上一步的镜像:gmf/centos 为模板,运行容器 docker01,docker02,docker03,那么他们都会具有容器卷
drwxr-xr-x. 2 root root 6 Mar 12 06:34 dataVolumeContainer1
drwxr-xr-x. 2 root root 6 Mar 12 06:34 dataVolumeContainer2
测试:容器间传递共享
先启动Docker1,在
dataVolumeContainer1
文件夹中创建一个新的文件[root@GMF _data]# docker start 6d1c2e258149 [root@GMF _data]# docker exec -it 6d1c2e258149 /bin/bash [root@6d1c2e258149 /]# cd dataVolumeContainer1 [root@6d1c2e258149 dataVolumeContainer1]# touch parentFile.txt [root@6d1c2e258149 dataVolumeContainer1]# ls container.txt parentFile.txt
创建Docker2、Docker3让其继承Docker1,使用
--volume-from
命令# 创建Docker2容器,使其继承docker1中挂载地址的引用 [root@GMF _data]# docker run -it --name docker2 --volumes-from 6d1c2e258149 gmf/centos [root@a72fd975f1f7 /]# ls bin dataVolumeContainer1 dataVolumeContainer2 dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var [root@a72fd975f1f7 /]# cd dataVolumeContainer1 [root@a72fd975f1f7 dataVolumeContainer1]# ls container.txt parentFile.txt
在Docker2中创建一个新的文件,切换到Docker1中查看是否存在
[root@GMF _data]# docker exec -it 6d1c2e258149 /bin/bash [root@6d1c2e258149 /]# cd dataVolumeContainer1 [root@6d1c2e258149 dataVolumeContainer1]# ls childFile.txt container.txt parentFile.txt
删除Docker1容器,查看Docker2中是否仍然存在
[root@GMF _data]# docker rm 6d1c2e258149 6d1c2e258149 [root@GMF _data]# docker exec -it a72fd975f1f7 /bin/bash [root@a72fd975f1f7 /]# cd dataVolumeContainer1 # 查看Docker2,Docker1创建的文件依旧存在 [root@a72fd975f1f7 dataVolumeContainer1]# ls childFile.txt container.txt parentFile.txt
得出结论:
宿主机与容器之间是使用地址映射进行挂载,容器与容器之间的挂载同样是引用自宿主机,所以当Docker1删除后,Docker2依旧能够访问到Docker1创建的文件,因为都是通过挂载映射自宿主机的地址
九、DockerFile
1. 前言
大家想想,Nginx,tomcat,mysql 这些镜像都是哪里来的?官方能写,我们不能写吗?
我们要研究自己如何做一个镜像,而且我们写的微服务项目以及springboot打包上云部署,Docker就是最方便的。 微服务打包成镜像,任何装了Docker的地方,都可以下载使用,极其的方便。
流程:开发应用=>DockerFile=>打包为镜像=>上传到仓库(私有仓库,公有仓库)=> 下载镜像 => 启动 运行。 还可以方便移植!
2. 什么是DockerFile
Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明
构建步骤:
- 编写DockerFile文件
- docker build 构建镜像文件
- docker run 生成可运行容器
DockerFile是面向开发的,以后如果要发布项目,都会使用Docker镜像的形式进行发布,需要编写DockerFile文件(DockerFile已经逐渐成为企业交付的标准)
- DockerFile:需要定义一个DockerFile,DockerFile定义了进程需要的一切东西。DockerFile涉及的内容 包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当引用进行需要和系统服务和内核进程打交道,这时需要考虑如何设计 namespace的权限控制)等等
- DockerImage:通过DockerFile构建生成的镜像,Docker build 时会产生一个Docker镜像,也是最终发布和运行的产品
- DockerContainer:Container就是程序运行起来提供服务的
查看centos的DockerFile文件https://hub.docker.com/_/centos
查看官方的DockerFile文件
3. DockerFile构建过程
3.1 基础知识
3.2 流程
- Docker从基础镜像运行一个容器
- 执行一条指令并对容器做出修改
- 执行类似 docker commit 的操作提交一个新镜像层
- Docker再基于刚提交的镜像运行一个新容器
- 执行DockerFile中的下一条指令直到所有指令都执行完成!
3.3 说明
从应用软件的角度来看,DockerFile,docker镜像与docker容器分别代表软件的三个不同阶段
- DockerFile 是软件的原材料 (代码)
- Docker 镜像则是软件的交付品 (.apk)
- Docker 容器则是软件的运行状态 (客户下载安装执行)
DockerFile 面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可!
4. DockerFile指令
1. 指令总览
关键字 | 作用 | 备注 |
---|---|---|
FROM | 指定父镜像 | 指定dockerfile基于那个image构建 |
MAINTAINER | 作者信息 | 用来标明这个dockerfile谁写的 |
LABEL | 标签 | 用来标明dockerfile的标签 可以使用Label代替Maintainer 最终都是在docker image基本信息中可以查看 |
RUN | 执行命令 | 执行一段命令 默认是/bin/sh 格式: RUN command 或者 RUN [“command” , “param1”,”param2”] |
CMD | 容器启动命令 | 提供启动容器时候的默认命令 和ENTRYPOINT配合使用.格式 CMD command param1 param2 或者 CMD [“command” , “param1”,”param2”] |
ENTRYPOINT | 入口 | 一般在制作一些执行就关闭的容器中会使用 |
COPY | 复制文件 | build的时候复制文件到image中 |
ADD | 添加文件 | build的时候添加文件到image中 不仅仅局限于当前build上下文 可以来源于远程服务 |
ENV | 环境变量 | 指定build时候的环境变量 可以在启动的容器的时候 通过-e覆盖 格式ENV name=value |
ARG | 构建参数 | 构建参数 只在构建的时候使用的参数 如果有ENV 那么ENV的相同名字的值始终覆盖arg的参数 |
VOLUME | 定义外部可以挂载的数据卷 | 指定build的image那些目录可以启动的时候挂载到文件系统中 启动容器的时候使用 -v 绑定 格式 VOLUME [“目录”] |
EXPOSE | 暴露端口 | 定义容器运行的时候监听的端口 启动容器的使用-p来绑定暴露端口 格式: EXPOSE 8080 或者 EXPOSE 8080/udp |
WORKDIR | 工作目录 | 指定容器内部的工作目录 如果没有创建则自动创建 如果指定/ 使用的是绝对地址 如果不是/开头那么是在上一条workdir的路径的相对路径 |
USER | 指定执行用户 | 指定build或者启动的时候 用户 在RUN CMD ENTRYPONT执行的时候的用户 |
HEALTHCHECK | 健康检查 | 指定监测当前容器的健康监测的命令 基本上没用 因为很多时候 应用本身有健康监测机制 |
ONBUILD | 触发器 | 当存在ONBUILD关键字的镜像作为基础镜像的时候 当执行FROM完成之后 会执行 ONBUILD的命令 但是不影响当前镜像 用处也不怎么大 |
STOPSIGNAL | 发送信号量到宿主机 | 该STOPSIGNAL指令设置将发送到容器的系统调用信号以退出。 |
SHELL | 指定执行脚本的shell | 指定RUN CMD ENTRYPOINT 执行命令的时候 使用的shell |
2. CMD 和 ENTRYPOINT 的区别
- CMD:Dockerfile 中可以有多个CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换!
- ENTRYPOINT: docker run 之后的参数会被当做参数传递给 ENTRYPOINT,之后形成新的命令组合
CMD演示
# 1. 构建dockerfile
[root@GMF customizeContainer]# vim testCmd
[root@GMF customizeContainer]# cat testCmd
FROM centos
#仅生效最后一个CMD
CMD ["ls","-a"]
# 2. build 镜像
[root@GMF customizeContainer]# docker build -f testCmd -t cmdtestimage .
Sending build context to Docker daemon 4.096kB
Step 1/2 : FROM centos
---> 300e315adb2f
Step 2/2 : CMD ["ls","-a"]
---> Running in 2aa31b0e2894
Removing intermediate container 2aa31b0e2894
---> 13ec445ce216
Successfully built 13ec445ce216
Successfully tagged cmdtestimage:latest
# 3. 执行发现执行了DockerFile中CMD的指令
[root@GMF customizeContainer]# docker run cmdtestimage
...
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
# 4. 如果我们希望用 -l 列表展示信息,我们就需要加上 -l参数
[root@GMF customizeContainer]# docker run cmdtestimage -l
docker: Error response from daemon: OCI runtime create failed: container_linux.go:367: starting container process caused: exec: "-l": executable file not found in $PATH: unknown.
# 问题:我们可以看到可执行文件找不到的报错,executable file not found。
# 之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。
# 因此这里的 -l 替换了原来的 CMD,而不是添加在原来的 ls -a 后面。而 -l 根本不是命令,所以自然找不到。
# 那么如果我们希望加入 -l 这参数,我们就必须重新完整的输入这个命令
[root@GMF customizeContainer]# docker run cmdtestimage ls -al
total 0
...
drwxr-xr-x. 2 root root 6 Nov 3 15:22 opt
dr-xr-xr-x. 248 root root 0 Mar 12 18:35 proc
dr-xr-x---. 2 root root 162 Dec 4 17:37 root
drwxr-xr-x. 11 root root 163 Dec 4 17:37 run
lrwxrwxrwx. 1 root root 8 Nov 3 15:22 sbin -> usr/sbin
drwxr-xr-x. 2 root root 6 Nov 3 15:22 srv
dr-xr-xr-x. 13 root root 0 Mar 12 01:51 sys
drwxrwxrwt. 7 root root 145 Dec 4 17:37 tmp
drwxr-xr-x. 12 root root 144 Dec 4 17:37 usr
drwxr-xr-x. 20 root root 262 Dec 4 17:37 var
ENTRYPOINT演示
# 1. 构建dockerfile
[root@GMF customizeContainer]# vim testentrypoint
[root@GMF customizeContainer]# cat testentrypoint
FROM centos
ENTRYPOINT [ "ls", "-a" ]
# 2、build 镜像
[root@GMF customizeContainer]# docker build -f dockerfile-entrypoint-test -t
entrypointtest .
Sending build context to Docker daemon 23.04kB
Step 1/2 : FROM centos
---> 470671670cac
Step 2/2 : ENTRYPOINT [ "ls", "-a" ]
---> Running in bac4ae055630
Removing intermediate container bac4ae055630
---> ae07199f9144
Successfully built ae07199f9144
Successfully tagged entrypointtest:latest
# 3、执行
[root@GMF customizeContainer]# docker run ae07199f9144
.dockerenv
bin
dev
etc
home
lib
lib64
......
# 4、测试-l参数,发现可以直接使用,也就是将参数追加到了DockerFile文件ENTRYPOINT参数的后面,我们可以明显的知道 CMD 和 ENTRYPOINT 的区别了
[root@GMF customizeContainer]# docker run entrypointtest -l
total 56
drwxr-xr-x 1 root root 4096 May 12 04:21 .
drwxr-xr-x 1 root root 4096 May 12 04:21 .
5. 实战测试
Docker Hub 中99% 的镜像都是通过在base镜像(Scratch)中安装和配置需要的软件构建出来的
自定义一个 centos
1. 编写DockerFile
目的:使我们自己的镜像具备如下:登陆后的默认路径、vim编辑器、查看网络配置ifconfig支持
[root@GMF customizeContainer]# vim myCentos
[root@GMF customizeContainer]# cat myCentos
FROM centos
MAINTAINER GMF<2373271519@qq.com>
ENV MYPATH /user/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "fuck bitch"
CMD /bin/bash
2. 构建镜像
命令:docker build -f dockerfile地址 -t 新镜像名字:TAG .
注意:
- docker build 命令最后有一个
.
.
表示当前目录 - 镜像名称不能大写
# 通过DockerFile构建一个新的镜像,在这个过程中可以看到层级的执行
[root@GMF customizeContainer]# docker build -f myCentos -t mycentos:1.0 .
Sending build context to Docker daemon 3.072kB
Step 1/10 : FROM centos
---> 300e315adb2f
Step 2/10 : MAINTAINER GMF<2373271519@qq.com>
---> Running in 72e7ad4bd4bd
Removing intermediate container 72e7ad4bd4bd
---> 55b1a6cd6ab8
Step 3/10 : ENV MYPATH /user/local
---> Running in 47534fd1b335
Removing intermediate container 47534fd1b335
---> 16a0484e1f54
Step 4/10 : WORKDIR $MYPATH
---> Running in 07e106656cdd
Removing intermediate container 07e106656cdd
---> 88468135aa5c
Step 5/10 : RUN yum -y install vim
...
Step 6/10 : RUN yum -y install net-tools
...
Step 7/10 : EXPOSE 80
---> Running in 9dd9dc1d86ab
Removing intermediate container 9dd9dc1d86ab
---> 3a56a27f2153
Step 8/10 : CMD echo $MYPATH
---> Running in f7e3a0ae4d16
Removing intermediate container f7e3a0ae4d16
---> 8b1f38d30679
Step 9/10 : CMD echo "fuck bitch"
---> Running in 320d7cb182ad
Removing intermediate container 320d7cb182ad
---> 687bbea3331e
Step 10/10 : CMD /bin/bash
---> Running in eb1355af1528
Removing intermediate container eb1355af1528
---> 8fd9f9c67295
Successfully built 8fd9f9c67295
Successfully tagged mycentos:1.0
3. 运行
命令:docker run -it 新镜像名字:TAG
[root@GMF customizeContainer]# docker run -it mycentos:1.0 /bin/bash
# 发现进入容器后的默认路径为/user/local
[root@b24257b24e1a local]# pwd
/user/local
# vim 命令正常执行
[root@b24257b24e1a local]# vim a.txt
# ifconfig 命令正常执行
[root@b24257b24e1a local]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.0.3 netmask 255.255.0.0 broadcast 172.17.255.255
ether 02:42:ac:11:00:03 txqueuelen 0 (Ethernet)
RX packets 8 bytes 656 (656.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
可以看到,我们自己的新镜像已经支持 vim/ifconfig的命令,扩展OK!
4. 列出镜像地的变更历史
命令:docker history 镜像名
[root@GMF customizeContainer]# docker history mycentos:1.0
IMAGE CREATED CREATED BY SIZE COMMENT
8fd9f9c67295 7 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B
687bbea3331e 7 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
8b1f38d30679 7 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B
3a56a27f2153 7 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B
c0626f28d857 7 minutes ago /bin/sh -c yum -y install net-tools 23.4MB
40c243f85548 7 minutes ago /bin/sh -c yum -y install vim 58.1MB
88468135aa5c 9 minutes ago /bin/sh -c #(nop) WORKDIR /user/local 0B
16a0484e1f54 9 minutes ago /bin/sh -c #(nop) ENV MYPATH=/user/local 0B
55b1a6cd6ab8 9 minutes ago /bin/sh -c #(nop) MAINTAINER GMF<2373271519… 0B
300e315adb2f 3 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:bd7a2aed6ede423b7… 209MB
6. 自定义镜像Tomcat
1. 准备镜像文件,Tomcat压缩包,jdk压缩包,以及说明文件
[root@GMF myTomcat]# ll
total 196668
-rw-r--r--. 1 root root 9625824 Mar 14 01:27 apache-tomcat-8.5.34.tar.gz
-rw-r--r--. 1 root root 48 Mar 14 01:30 Dockerfile
-rw-r--r--. 1 root root 191753373 Mar 14 01:27 jdk-8u191-linux-x64.tar.gz
-rw-r--r--. 1 root root 0 Mar 14 01:30 readme.txt
2. 编写DockerFile文件(官方命名 Dockerfile
build命令会自动寻找这个文件,不用 -f
指定)
# vim Dockerfile
FROM centos
MAINTAINER GMF<2373271519@qq.com>
#把宿主机当前上下文的read.txt拷贝到容器/usr/local/路径下
COPY readme.txt /usr/local/cincontainer.txt
#把java与tomcat添加到容器中
ADD jdk-8u191-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-8.5.34.tar.gz /usr/local/
#安装vim编辑器
RUN yum -y install vim
#设置工作访问时候的WORKDIR路径,登录落脚点
ENV MYPATH /usr/local
WORKDIR $MYPATH
#配置java与tomcat环境变量
ENV JAVA_HOME /usr/local/jdk1.8.0_191
#告知jdk在哪些目录下可找到所需要执行的class文件,多个以 : 隔开
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-8.5.34
ENV CATALINA_BASE /usr/local/apache-tomcat-8.5.34
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
#容器运行时监听的端口
EXPOSE 8080
#启动时运行tomcat
# ENTRYPOINT ["/usr/local/apache-tomcat-8.5.34/bin/startup.sh" ]
# CMD ["/usr/local/apache-tomcat-8.5.34/bin/catalina.sh","run"]
CMD /usr/local/apache-tomcat-8.5.34/bin/startup.sh && tail -F /usr/local/apache-tomcat-8.5.34/logs/catalina.out
3. 构建镜像
# 通过Dockerfile构建自定义镜像
[root@GMF myTomcat]# docker build -t diytomcat .
...
Successfully built 4f36d643b253
Successfully tagged diytomcat:latest
4. 运行启动run
# 通过镜像创建容器,并挂载日志、应用等相关文件夹
[root@GMF myTomcat]# docker run -d -p 8080:8080 --name myDiyTomcat -v /user/myTomcat/webapps:/usr/local/apache-tomcat-8.5.34/webapps -v /user/myTomcat/logs:/usr/local/apache-tomcat-8.5.34/logs diytomcat
e5c8afe77fb5c5c1da05d326db2136254c0fba4b864c51a08b0e6d0b1a08e481
[root@GMF myTomcat]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e5c8afe77fb5 diytomcat "/bin/sh -c '/usr/lo…" 6 seconds ago Up 4 seconds 0.0.0.0:8080->8080/tcp myDiyTomcat
# 进入容器内查看目录
[root@GMF myTomcat]# docker exec -it e5c8afe77fb5 /bin/bash
[root@e5c8afe77fb5 local]# ls
apache-tomcat-8.5.34 bin cincontainer.txt etc games include jdk1.8.0_191 lib lib64 libexec sbin share src
# 查看容器内的应用
[root@e5c8afe77fb5 apache-tomcat-8.5.34]# cd webapps/
[root@e5c8afe77fb5 webapps]# ls
ROOT docs examples host-manager manager
5. 验证测试访问
# 退出容器后访问测试
[root@GMF myTomcat]# curl localhost:8080
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>
6. 结合前面学习的容器卷将测试的web服务test发布
[root@GMF myTomcat]# cd webapps/
[root@GMF webapps]# mkdir test
[root@GMF webapps]# cd test
[root@GMF test]# pwd
/user/myTomcat/webapps/test
[root@GMF test]# mkdir WEB-INF
[root@GMF test]# cd WEB-INF/
[root@GMF WEB-INF]# vim web.xml
[root@GMF WEB-INF]# cd ..
[root@GMF test]# vim index.jsp
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"
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">
<display-name>test</display-name>
</web-app>
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>hello,GMF</title>
</head>
<body>
-----------welcome------------
<%=" my docker tomcat,GMF "%>
<br>
<br>
<% System.out.println("-------my docker tomcat-------");%>
</body>
</html>
重新测试
查看日志
[root@GMF test]# cd ../../logs/
[root@GMF logs]# ll
total 28
-rw-r-----. 1 root root 11211 Mar 14 03:09 catalina.2021-03-13.log
-rw-r-----. 1 root root 11302 Mar 14 03:14 catalina.out
-rw-r-----. 1 root root 0 Mar 14 02:53 host-manager.2021-03-13.log
-rw-r-----. 1 root root 0 Mar 14 02:53 localhost.2021-03-13.log
-rw-r-----. 1 root root 776 Mar 14 03:15 localhost_access_log.2021-03-13.txt
-rw-r-----. 1 root root 0 Mar 14 02:53 manager.2021-03-13.log
7. 发布镜像
1. 将镜像发布到DockerHub
注册dockerhub https://hub.docker.com/signup,需要有一个账号
相关指令:
docker tag 镜像id 用户名称/镜像名称:版本
docker push 用户名称/镜像名称:版本
```shell1、查看登录命令
[root@kuangshen tomcat]# docker login —help Usage: docker login [OPTIONS] [SERVER]
2、登录
[root@GMF ~]# docker login -u nothingfunny Password: WARNING! Your password will be stored unencrypted in /root/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store Login Succeeded
3、将镜像发布出去
[root@GMF ~]# docker push nothingfunny/diytomcat:1.0 invalid reference format: repository name must be lowercase
拒绝:请求的资源访问被拒绝
denied: requested access to the resource is denied
问题:本地镜像名无帐号信息,解决加 tag即可
[root@GMF ~]# docker tag 4f36d643b253 nothingfunny/diytomcat:1.0
再次 push, ok
[root@GMF ~]# docker push nothingfunny/diytomcat:1.0 The push refers to repository [docker.io/nothingfunny/diytomcat] 6214a2ccd532: Pushed 87579a85de79: Pushed 47c1be1a6d0d: Pushed 7e40d75b7c0d: Pushed 2653d992f4ef: Pushed 1.0: digest: sha256:932d7d0dc8723057e53cb820a0424a5ebc5b59d23888b2c7886033710fbf559b size: 1372
<a name="A6kxz"></a>
### 2. 将镜像发布到阿里云服务器
1. **登陆阿里云**
<br />
2. **找到镜像容器服务**

3. **创建命名空间**

4. **创建镜像仓库**
<br />
5. **点击进入这个镜像仓库,可以看到所有的信息以及使用步骤**
6. **测试推送发布**
```shell
# 退出之前登陆的DockerHub
[root@GMF ~]# docker logout
Removing login credentials for https://index.docker.io/v1/
# 登陆自己的阿里云镜像仓库
[root@GMF ~]# docker login --username=没什么好笑的 registry.cn-hangzhou.aliyuncs.com
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
# 查看需要推送镜像的id
[root@GMF ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gmf/diytomcat 1.0 4f36d643b253 22 hours ago 678MB
# 设置需要推送镜像的Tag
# docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/gmf-test/my-repository:[镜像版本号]
[root@GMF ~]# docker tag 4f36d643b253 registry.cn-hangzhou.aliyuncs.com/gmf-test/my-repository:1.0
# 推送镜像
[root@GMF ~]# docker push registry.cn-hangzhou.aliyuncs.com/gmf-test/my-repository:1.0
The push refers to repository [registry.cn-hangzhou.aliyuncs.com/gmf-test/my-repository]
6214a2ccd532: Pushed
87579a85de79: Pushed
47c1be1a6d0d: Pushed
7e40d75b7c0d: Pushed
2653d992f4ef: Pushed
1.0: digest: sha256:932d7d0dc8723057e53cb820a0424a5ebc5b59d23888b2c7886033710fbf559b size: 1372
- 在阿里云镜像仓库查看效果
8. 总结

十、Docker 网络讲解
1. 理解Docker01
准备工作:清空所有的容器以及镜像
docker rm -f $(docker ps -aq)
docker rmi -f $(docker images -aq)
我们先来做个测试
查看本地ip ip addr
这里我们分析可得,有三个网络
lo 127.0.0.1
:本机回环地址eth33 192.168.43.11
:虚拟机的私有IPdocker0 172.17.0.1
:docker网桥
实际场景中,我们开发了很多微服务项目,那些微服务项目都要连接数据库,需要指定数据库的url地址,通过ip。但是我们用Docker管理的话,假设数据库出问题了,我们重新启动运行一个,这个时候数据库的地址就会发生变化,docker会给每个容器都分配一个ip,且容器和容器之间是可以互相访问的。 我们可以测试下容器之间能不能ping通过:
# 启动tomcat01
[root@GMF ~]# docker run -d -P --name tomcat01 tomcat
# 查看tomcat01的ip地址,docker会给每个容器都分配一个ip!
[root@GMF ~]# docker exec -it tomcat01 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
8: eth0@if9: <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
# 检测linux服务器是否可以ping通容器内的tomcat
[root@GMF ~]# 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.152 ms #成功ping通
原理
- 每一个安装了Docker的linux主机都有一个docker0的虚拟网卡。这是个桥接网卡,使用了veth-pair 技术!
再次查看主机的 ip addr
- 每启动一个容器,linux主机就会多了一个虚拟网卡
```shell
我们启动了一个tomcat01,主机的ip地址多了一个 9: veth08f8f9d@if8
然后我们在tomcat01容器中查看容器的ip是 8: eth0@if9
我们再启动一个tomcat02观察
[root@GMF ~]# docker run -d -P —name tomcat02 tomcat
然后发现linux主机上又多了一个网卡 11: veth034f6d4@if10
我们看下tomcat02的容器内ip地址是 10: eth0@if11
[root@GMF ~]# docker exec -it tomcat02 ip addr
观察现象:
tomcat —- linux主机 veth08f8f9d@if8 —— 容器内 eth0@if9
tomcat —- linux主机 veth021eeea@if10 —— 容器内 eth0@if11
相信到了这里,大家应该能看出点小猫腻了吧!只要启动一个容器,就有一对网卡
veth-pair 就是一对的虚拟设备接口,它都是成对出现的。一端连着协议栈,一端彼此相连着。
正因为有这个特性,它常常充当着一个桥梁,连接着各种虚拟网络设备!
“Bridge、OVS 之间的连接”,“Docker 容器之间的连接” 等等,以此构建出非常复杂的虚拟网络结构,比如 OpenStack Neutron
3. **我们来测试下tomcat01和tomcat02容器间是否可以互相ping通**
```shell
[root@GMF ~]# docker exec -it tomcat02 ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1) 56(84) bytes of data.
64 bytes from 172.17.0.1: icmp_seq=1 ttl=64 time=0.075 ms #可以ping通
- 网络模型图
结论:
- tomcat1和tomcat2共用一个路由器。他们使用的一个,就是docker0。任何一个容器启动默认都是docker0网络。
- docker默认会给容器分配一个可用ip。
小结
Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
Docker容器网络就很好的利用了Linux虚拟网络技术,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair)
Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势就是转发效率极高(因为Linux是在内核中进行数据的复制来实现虚拟接口之间的数据转发,无需通过外部的网络设备交换),对于本地系统和容器系统来说,虚拟接口跟一个正常的以太网卡相比并没有区别,只是他的速度快很多。
2. —Link
思考一个场景,我们编写一个微服务,数据库连接地址原来是使用ip的,如果ip变化就不行了,那我们 能不能使用服务名访问呢?
jdbc:mysql://mysql:3306,这样的话哪怕mysql重启,我们也不需要修改配置了!docker提供了 —link 的操作!
尝试使用tomcat02直接通过容器名ping tomcat01 (不使用IP)
[root@GMF ~]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known #发现无法ping通
但当我们使用 —link 命令去连接两个容器时,则能够使其通过容器名ping通网络
# 我们再启动一个tomcat03,但是启动的时候连接tomcat02
[root@GMF ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat
4b4b519d09dede2ec90e963c5f83bdf30dfd83193989af7097c9031c32aae178
# 这个时候,我们就可以使用tomcat03 ping通tomcat02 了
[root@GMF ~]# docker exec -it tomcat03 ping tomcat02
PING 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.078 ms
尝试通过tomcat02反向ping tomcat03
[root@GMF ~]# docker exec -it tomcat02 ping tomcat03
ping: tomcat03: Name or service not known #发现无法ping通
思考原理
进入tomcat03中查看下host配置文件
[root@GMF ~]# docker exec -it tomcat03 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 tomcat02 6159feb2ed08 #发现了tomcat02的IP映射
172.17.0.4 4b4b519d09de
所以这里其实就是配置了一个 hosts 地址而已
原因:—link的时候,直接把需要link的主机的域名和ip直接配置到了hosts文件中了
—link早都过时了,我们不推荐使用!我们可以使用自定义网络的方式
3. 自定义网络
1. 基本命令查看
查看所有网络
所有网络模式
网络模式 | 配置 | 说明 |
---|---|---|
bridge模式 | —net=bridge | 默认值,在Docker网桥docker0上为容器创建新的网络栈 |
none模式 | —net=none | 不配置网络,用户可以稍后进入容器,自行配置 |
container 模式 | —net=container:name/id | 容器和另外一个容器共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace。 |
host模式 | —net=host | 容器和宿主机共享Network namespace |
用户自定义 | —net=自定义网络 | 用户自己使用network相关命令定义网络,创建容器的 时候可以指定为自己定义的网络 |
查看一个具体的网络的详细信息
[root@GMF ~]# docker network inspect 9ad5d2efa06f
[
{
"Name": "bridge",
"Id": "9ad5d2efa06f03a76f4e66141ecbb29c6c02e7bd05d3485e4246cfafd1a1da31",
"Created": "2021-03-15T12:50:19.903069813+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
# 默认docker0是管理这个子网范围内的 0~16,也就是 255*255,去掉0个255,我们有65534可以分配的ip
"Subnet": "172.17.0.0/16",
# docker0网络默认可以支持创建6万多个容器ip不重复
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"d7054e6b0c873dd7f0fa003cb0163148d71ce6769a34e796fd448f08b028eb30": {
"Name": "tomcat_net01",
"EndpointID": "6b85fbca72492b35282e889619b5306e17c66105e90fcbe900e11cf28637abda",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
2. 自定义网卡
默认创建的容器都是docker0网卡的,而我们可以通过 --net
指令指定容器网卡
# 启动一个Tomcat容器
# --net bridge:指定网络为dridge模式(默认值)
docker run -d -P --name tomcat_net01 --net bridge tomcat
docker0网络的特点:
- 默认使用docker0
- 域名访问不通
- —link域名通了,但是删了又不行
创建自定义网卡
命令:docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
# 自定义创建一个网络
[root@GMF ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
0eb1394e5afcc92808441eb250d45690e21a7ed0eb675c9bfc45c071e6568d72
# 查看自定义的网络
[root@GMF ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
9ad5d2efa06f bridge bridge local
96e79fec8f57 host host local
0eb1394e5afc mynet bridge local
89cdca570e2f none null local
[root@GMF ~]# docker network inspect 0eb1394e5afc
[
{
"Name": "mynet",
"Id": "0eb1394e5afcc92808441eb250d45690e21a7ed0eb675c9bfc45c071e6568d72",
"Created": "2021-03-15T23:39:33.479639508+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
测试自定义网络
# 启动两个容器测试,使用自己的 mynet!
[root@GMF ~]# docker run -d -P --name tomcat_net01 --net mynet tomcat
f2c5a27024f544df58bd940caf7611fb4088c025218a389e45370f4b8ff11d48
[root@GMF ~]# docker run -d -P --name tomcat_net02 --net mynet tomcat
09909419c35765c1cac8463b28b08bfe426478c3c9620b4475651c201f5ab96c
# 查看容器与容器之间是否能够通过名称ping通
[root@GMF ~]# docker exec -it tomcat_net01 ping tomcat_net02
PING tomcat_net02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat_net02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.144 ms #可以ping通
、
再次查看自定义网络的信息
[root@GMF ~]# docker network inspect 0eb1394e5afc
[
{
"Name": "mynet",
"Id": "0eb1394e5afcc92808441eb250d45690e21a7ed0eb675c9bfc45c071e6568d72",
"Created": "2021-03-15T23:39:33.479639508+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
# 发现了自定义网络中的容器的网络信息
"Containers": {
"09909419c35765c1cac8463b28b08bfe426478c3c9620b4475651c201f5ab96c": {
"Name": "tomcat_net02",
"EndpointID": "568ecd980b5e5b390165c3c875fcf23f87d7fb859587176f4f39e04f2cb0d73a",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
"f2c5a27024f544df58bd940caf7611fb4088c025218a389e45370f4b8ff11d48": {
"Name": "tomcat_net01",
"EndpointID": "1bfa6aebf96669b6d20ce09d45c1e1e37b4ff84312323c6aa51550f9969a5031",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
总结
们自定义的网络docker都已经帮我们维护好了对应的关系,所以我们平时都可以这样使用网络,不使用—link效果一样,所有东西实时维护好,直接域名 ping 通。
4. 网络连通
docker0和自定义网络肯定不通,我们使用自定义网络的好处就是网络隔离:
大家公司项目部署的业务都非常多,假设我们有一个商城,我们会有订单业务(操作不同数据),会有 订单业务购物车业务(操作不同缓存)。如果在一个网络下,有的程序猿的恶意代码就不能防止了,所以我们就在部署的时候网络隔离,创建两个桥接网卡,比如订单业务(里面的数据库,redis,mq,全 部业务 都在order-net网络下)其他业务在其他网络。
那关键的问题来了,如何让 tomcat-net-01 访问 tomcat1?
测试打通容器与不同的网络的连接
[root@GMF ~]# docker network connect mynet tomcat01
[root@GMF ~]# docker network inspect 0eb1394e5afc
[
{
"Name": "mynet",
"Id": "0eb1394e5afcc92808441eb250d45690e21a7ed0eb675c9bfc45c071e6568d72",
"Created": "2021-03-15T23:39:33.479639508+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"09909419c35765c1cac8463b28b08bfe426478c3c9620b4475651c201f5ab96c": {
"Name": "tomcat_net02",
"EndpointID": "568ecd980b5e5b390165c3c875fcf23f87d7fb859587176f4f39e04f2cb0d73a",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
# 当我们连通容器与网络之后,查看网络详细信息,发现了刚刚的容器
"ebe96c93ddea314fb60f8e625c48b389da557ecd72260a9dbe8c383d7e999581": {
"Name": "tomcat01",
"EndpointID": "147d82c65439bee666e1537ebf323cec3b0f647154bad99007c60c50cfa6554d",
"MacAddress": "02:42:c0:a8:00:04",
"IPv4Address": "192.168.0.4/16",
"IPv6Address": ""
},
"f2c5a27024f544df58bd940caf7611fb4088c025218a389e45370f4b8ff11d48": {
"Name": "tomcat_net01",
"EndpointID": "1bfa6aebf96669b6d20ce09d45c1e1e37b4ff84312323c6aa51550f9969a5031",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
# 尝试使用tomcat01 ping tomcat_net01
[root@GMF ~]# docker exec -it tomcat01 ping tomcat_net01
PING tomcat_net01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat_net01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.191 ms # 成功ping通
结论:如果要跨网络操作别人,就需要使用 _docker network connect [OPTIONS] NETWORK CONTAINER_
连接
5. 实战:部署一个Redis集群
设置Redis集群网卡及查看
# 创建网卡
docker network create redis --subnet 172.38.0.0/16
docker network ls
docker network inspect redis
# 拉取redis镜像
[root@GMF ~]# docker pull redis
Using default tag: latest
latest: Pulling from library/redis
a076a628af6f: Pull complete
f40dd07fe7be: Pull complete
ce21c8a3dbee: Pull complete
ee99c35818f8: Pull complete
56b9a72e68ff: Pull complete
3f703e7f380f: Pull complete
Digest: sha256:0f97c1c9daf5b69b93390ccbe8d3e2971617ec4801fd0882c72bf7cad3a13494
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest
Redis节点创建及设置
# 通过脚本创建六个redis配置
for port in $(seq 1 6); \
do \
mkdir -p /mydata/redis/node-${port}/conf
touch /mydata/redis/node-${port}/conf/redis.conf
cat << EOF >/mydata/redis/node-${port}/conf/redis.conf
port 6379
# ip绑定,0.0.0.0为不绑定
bind 0.0.0.0
# 开启集群配置
cluster-enabled yes
# 集群配置文件
cluster-config-file nodes.conf
# 集群超时时间
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done
拉取Redis镜像并启动Redis节点
docker run -p 6371:6379 -p 16371:16379 --name redis-1 \
-v /mydata/redis/node-1/data:/data \
-v /mydata/redis/node-1/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.11 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
docker run -p 6372:6379 -p 16372:16379 --name redis-2 \
-v /mydata/redis/node-2/data:/data \
-v /mydata/redis/node-2/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.12 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf
docker run -p 6373:6379 -p 16373:16379 --name redis-3 \
-v /mydata/redis/node-3/data:/data \
-v /mydata/redis/node-3/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.13 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf
docker run -p 6374:6379 -p 16374:16379 --name redis-4 \
-v /mydata/redis/node-4/data:/data \
-v /mydata/redis/node-4/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.14 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf
docker run -p 6375:6379 -p 16375:16379 --name redis-5 \
-v /mydata/redis/node-5/data:/data \
-v /mydata/redis/node-5/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.15 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf
docker run -p 6376:6379 -p 16376:16379 --name redis-6 \
-v /mydata/redis/node-6/data:/data \
-v /mydata/redis/node-6/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172.38.0.16 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf
以交互模式进入节点内
# 进入一个redis,注意这里是 sh命令 redis默认没有bash
docker exec -it redis-1 /bin/sh
创建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
运行成功界面如下(主从复制)集群创建成功
/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
Unrecognized option or bad number of args for: '--cluster-replicas'
/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和slave节点的分配
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
M: 887c5ded66d075b6d29602f89a6adc7d1471d22c 172.38.0.11:6379
slots:[0-5460] (5461 slots) master
M: 7684dfd02929085817de59f334d241e6cbcd1e99 172.38.0.12:6379
slots:[5461-10922] (5462 slots) master
M: db3caa7ba307a27a8ef30bf0b26ba91bfb89e932 172.38.0.13:6379
slots:[10923-16383] (5461 slots) master
S: d75a9db032f13d9484909b2d0d4724f44e3f1c23 172.38.0.14:6379
replicates db3caa7ba307a27a8ef30bf0b26ba91bfb89e932
S: e6b5521d86abc96fe2e51e40be8fbb1f23da9fe7 172.38.0.15:6379
replicates 887c5ded66d075b6d29602f89a6adc7d1471d22c
S: b6add5e06fd958045f90f29bcbbf219753798ef6 172.38.0.16:6379
replicates 7684dfd02929085817de59f334d241e6cbcd1e99
Can 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 cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 172.38.0.11:6379)
M: 887c5ded66d075b6d29602f89a6adc7d1471d22c 172.38.0.11:6379
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: e6b5521d86abc96fe2e51e40be8fbb1f23da9fe7 172.38.0.15:6379
slots: (0 slots) slave
replicates 887c5ded66d075b6d29602f89a6adc7d1471d22c
S: d75a9db032f13d9484909b2d0d4724f44e3f1c23 172.38.0.14:6379
slots: (0 slots) slave
replicates db3caa7ba307a27a8ef30bf0b26ba91bfb89e932
S: b6add5e06fd958045f90f29bcbbf219753798ef6 172.38.0.16:6379
slots: (0 slots) slave
replicates 7684dfd02929085817de59f334d241e6cbcd1e99
M: 7684dfd02929085817de59f334d241e6cbcd1e99 172.38.0.12:6379
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: db3caa7ba307a27a8ef30bf0b26ba91bfb89e932 172.38.0.13:6379
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
集群创建成功后可进一步查看集群配置,并进行测试
/data # redis-cli -c //-c表示集群
127.0.0.1:6379> cluster info //集群配置
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:613
cluster_stats_messages_pong_sent:614
cluster_stats_messages_sent:1227
cluster_stats_messages_ping_received:609
cluster_stats_messages_pong_received:613
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1227
127.0.0.1:6379> cluster nodes //集群节点信息
887c5ded66d075b6d29602f89a6adc7d1471d22c 172.38.0.11:6379@16379 myself,master - 0 1598439359000 1 connected 0-5460
e6b5521d86abc96fe2e51e40be8fbb1f23da9fe7 172.38.0.15:6379@16379 slave 887c5ded66d075b6d29602f89a6adc7d1471d22c 0 1598439359580 5 connected
d75a9db032f13d9484909b2d0d4724f44e3f1c23 172.38.0.14:6379@16379 slave db3caa7ba307a27a8ef30bf0b26ba91bfb89e932 0 1598439358578 4 connected
b6add5e06fd958045f90f29bcbbf219753798ef6 172.38.0.16:6379@16379 slave 7684dfd02929085817de59f334d241e6cbcd1e99 0 1598439358578 6 connected
7684dfd02929085817de59f334d241e6cbcd1e99 172.38.0.12:6379@16379 master - 0 1598439360082 2 connected 5461-10922
db3caa7ba307a27a8ef30bf0b26ba91bfb89e932 172.38.0.13:6379@16379 master - 0 1598439359079 3 connected 10923-16383
关于Redis集群高可用
127.0.0.1:6379> set msg "hello,I like your blog!" 设置值
-> Redirected to slot [6257] located at 172.38.0.12:6379
OK
172.38.0.12:6379> get msg //取值
"hello,I like your blog!"
//用docker stop模拟存储msg值的redis主机宕机
[root@instance-001 panhom]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
705e41851450 redis:5.0.9-alpine3.11 "docker-entrypoint.s…" 20 minutes ago Up 20 minutes 0.0.0.0:6376->6379/tcp, 0.0.0.0:16376->16379/tcp redis-6
89844e29f714 redis:5.0.9-alpine3.11 "docker-entrypoint.s…" 20 minutes ago Up 20 minutes 0.0.0.0:6375->6379/tcp, 0.0.0.0:16375->16379/tcp redis-5
8f1ead5ca10a redis:5.0.9-alpine3.11 "docker-entrypoint.s…" 20 minutes ago Up 20 minutes 0.0.0.0:6374->6379/tcp, 0.0.0.0:16374->16379/tcp redis-4
f77c5d93a97a redis:5.0.9-alpine3.11 "docker-entrypoint.s…" 21 minutes ago Up 21 minutes 0.0.0.0:6373->6379/tcp, 0.0.0.0:16373->16379/tcp redis-3
cc2a93a6b4dd redis:5.0.9-alpine3.11 "docker-entrypoint.s…" 21 minutes ago Up 21 minutes 0.0.0.0:6372->6379/tcp, 0.0.0.0:16372->16379/tcp redis-2
6c9c3b813129 redis:5.0.9-alpine3.11 "docker-entrypoint.s…" 26 minutes ago Up 26 minutes 0.0.0.0:6371->6379/tcp, 0.0.0.0:16371->16379/tcp redis-1
[root@instance-001 panhom]# docker stop redis-2
redis-2
//重新进入集群交互界面,并尝试获取msg消息
/data # exit
[root@instance-001 panhom]# docker exec -it redis-1 /bin/sh
/data # redis-cli -c
127.0.0.1:6379> get ms
-> Redirected to slot [13186] located at 172.38.0.13:6379
(nil)
172.38.0.13:6379> get msg
-> Redirected to slot [6257] located at 172.38.0.16:6379 //此时由备用机返回msg
"hello,I like your blog!"
十一、IDEA整合Docker
1. 创建项目
1. 使用IDEA编写一个SpringBoot项目
2. 编写一个helloController
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "hello,kuangshen";
}
}
3. 启动测试下,端口修改下,避免8080冲突!本地访问没问题就可以!
4. 打jar包
有了 jar 包,我们就可以作镜像了!
2. 打包镜像
1. 再项目下编写 Dockerfile 文件,将打包好的jar包拷贝到Dockerfile同级目录
FROM java:8
# 服务器只有dockerfile和jar在同级目录
COPY *.jar /app.jar
CMD ["--server.port=8080"]
# 指定容器内要暴露的端口
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
2. 将Dockerfile 和 项目的 jar 包上传到linux服务器上,构建运行
[root@kuangshen idea]# pwd
/home/idea
[root@kuangshen idea]# ll
total 17228
-rw-r--r-- 1 root root 17634294 May 14 12:33 demo-0.0.1-SNAPSHOT.jar
-rw-r--r-- 1 root root 207 May 14 12:32 Dockerfile
# 构建镜像
docker build -t idea-ks .
# 查看镜像
docker images
# 运行
docker run -d -P --name idea-ks idea-ks
[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE PORTS NAMES
2366c960ba99 idea-ks 0.0.0.0:32779->8080/tcp idea-ks1
# 测试访问
[root@kuangshen ~]# curl localhost:32779
[root@kuangshen ~]# curl localhost:32779/hello
十二、DockerCompose
1. 服务编排
微服务架构的应用系统中一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,维护的工作量会很大
- 要从Dockerfile build image 或者去dockerhub拉取image
- 要创建多个container
- 要管理这些container(启动停止删除)
服务编排:按照一定的业务规则批量管理容器
2. DockerCompose介绍
Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动/停止所有服务。
使用步骤:
- 利用 Dockerfile 定义运行环境镜像
- 使用 docker-compose.yml 定义组成应用的各服务
- 运行 docker-compose up 启动应用
3. 安装与卸载
安装
# Compose目前已经完全支持Linux、Mac OS和Windows,在我们安装Compose之前,需要先安装Docker。下面我们以编译好的二进制包方式安装在Linux系统中。
curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 设置文件可执行权限
chmod +x /usr/local/bin/docker-compose
# 查看版本信息
docker-compose -version
卸载
# 二进制包方式安装的,删除二进制文件即可
rm /usr/local/bin/docker-compose
4. 使用DockerCompose编排nginx+springboot项目
创建docker-compose目录
mkdir ~/docker-compose cd ~/docker-compose
编写 docker-compose.yml 文件
version: '3' services: nginx: image: nginx ports: - 80:80 links: - app volumes: - ./nginx/conf.d:/etc/nginx/conf.d app: image: app expose: - "8080"
创建./nginx/conf.d目录
mkdir -p ./nginx/conf.d
在./nginx/conf.d目录下 编写itheima.conf文件
server { listen 80; access_log off; location / { proxy_pass http://app:8080; } }
在~/docker-compose 目录下 使用docker-compose 启动容器
docker-compose up
测试访问
http://192.168.149.135/hello
5. yml 配置指令参考
version
指定本 yml 依从的 compose 哪个版本制定的。
build
指定为构建镜像上下文路径:
例如 webapp 服务,指定为从上下文路径 ./dir/Dockerfile 所构建的镜像:
version: "3.7"
services:
webapp:
build: ./dir
或者,作为具有在上下文指定的路径的对象,以及可选的 Dockerfile 和 args:
version: "3.7"
services:
webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1
labels:
- "com.example.description=Accounting webapp"
- "com.example.department=Finance"
- "com.example.label-with-empty-value"
target: prod
- context:上下文路径。
- dockerfile:指定构建镜像的 Dockerfile 文件名。
- args:添加构建参数,这是只能在构建过程中访问的环境变量。
- labels:设置构建镜像的标签。
- target:多层构建,可以指定构建哪一层。
cap_add,cap_drop
添加或删除容器拥有的宿主机的内核功能。
cap_add:
- ALL # 开启全部权限
cap_drop:
- SYS_PTRACE # 关闭 ptrace权限
cgroup_parent
为容器指定父 cgroup 组,意味着将继承该组的资源限制。
cgroup_parent: m-executor-abcd
command
覆盖容器启动的默认命令。
command: ["bundle", "exec", "thin", "-p", "3000"]
container_name
指定自定义容器名称,而不是生成的默认名称。
container_name: my-web-container
depends_on
设置依赖关系。
- docker-compose up :以依赖性顺序启动服务。在以下示例中,先启动 db 和 redis ,才会启动 web。
- docker-compose up SERVICE :自动包含 SERVICE 的依赖项。在以下示例中,docker-compose up web 还将创建并启动 db 和 redis。
- docker-compose stop :按依赖关系顺序停止服务。在以下示例中,web 在 db 和 redis 之前停止。
注意:web 服务不会等待 redis db 完全启动 之后才启动。version: "3.7" services: web: build: . depends_on: - db - redis redis: image: redis db: image: postgres
deploy
指定与服务的部署和运行有关的配置。只在 swarm 模式下才会有用。
version: "3.7"
services:
redis:
image: redis:alpine
deploy:
mode:replicated
replicas: 6
endpoint_mode: dnsrr
labels:
description: "This redis service label"
resources:
limits:
cpus: '0.50'
memory: 50M
reservations:
cpus: '0.25'
memory: 20M
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
window: 120s
可以选参数:
endpoint_mode:访问集群服务的方式。
endpoint_mode: vip # Docker 集群服务一个对外的虚拟 ip。所有的请求都会通过这个虚拟 ip 到达集群服务内部的机器。 endpoint_mode: dnsrr # DNS 轮询(DNSRR)。所有的请求会自动轮询获取到集群 ip 列表中的一个 ip 地址。 labels:在服务上设置标签。可以用容器上的 labels(跟 deploy 同级的配置) 覆盖 deploy 下的 labels。
mode:指定服务提供的模式。
- replicated:复制服务,复制指定服务到集群的机器上。
- global:全局服务,服务将部署至集群的每个节点。
- 图解:下图中黄色的方块是 replicated 模式的运行情况,灰色方块是 global 模式的运行情况。
- replicas:mode 为 replicated 时,需要使用此参数配置具体运行的节点数量。
- resources:配置服务器资源使用的限制,例如上例子,配置 redis 集群运行需要的 cpu 的百分比 和 内存的占用。避免占用资源过高出现异常。
- restart_policy:配置如何在退出容器时重新启动容器。
- condition:可选 none,on-failure 或者 any(默认值:any)。
- delay:设置多久之后重启(默认值:0)。
- max_attempts:尝试重新启动容器的次数,超出次数,则不再尝试(默认值:一直重试)。
- window:设置容器重启超时时间(默认值:0)。
- rollback_config:配置在更新失败的情况下应如何回滚服务。
- parallelism:一次要回滚的容器数。如果设置为0,则所有容器将同时回滚。
- delay:每个容器组回滚之间等待的时间(默认为0s)。
- failure_action:如果回滚失败,该怎么办。其中一个 continue 或者 pause(默认pause)。
- monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为0s)。
- max_failure_ratio:在回滚期间可以容忍的故障率(默认为0)。
- order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first )。
- update_config:配置应如何更新服务,对于配置滚动更新很有用。
- parallelism:一次更新的容器数。
- delay:在更新一组容器之间等待的时间。
- failure_action:如果更新失败,该怎么办。其中一个 continue,rollback 或者pause (默认:pause)。
- monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为0s)。
- max_failure_ratio:在更新过程中可以容忍的故障率。
- order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认stop-first)。
注:仅支持 V3.4 及更高版本。
devices
指定设备映射列表。
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"
dns
自定义 DNS 服务器,可以是单个值或列表的多个值。
dns: 8.8.8.8
dns:
- 8.8.8.8
- 9.9.9.9
dns_search
自定义 DNS 搜索域。可以是单个值或列表。
dns_search: example.com
dns_search:
- dc1.example.com
- dc2.example.com
entrypoint
覆盖容器默认的 entrypoint。
entrypoint: /code/entrypoint.sh
也可以是以下格式:
entrypoint:
- php
- -d
- zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
- -d
- memory_limit=-1
- vendor/bin/phpunit
env_file
从文件添加环境变量。可以是单个值或列表的多个值。
env_file: .env
也可以是列表格式:
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
environment
添加环境变量。您可以使用数组或字典、任何布尔值,布尔值需要用引号引起来,以确保 YML 解析器不会将其转换为 True 或 False。
environment:
RACK_ENV: development
SHOW: 'true'
expose
暴露端口,但不映射到宿主机,只被连接的服务访问。
仅可以指定内部端口为参数:
expose:
- "3000"
- "8000"
extra_hosts
添加主机名映射。类似 docker client —add-host。
extra_hosts:
- "somehost:162.242.195.82"
- "otherhost:50.31.209.229"
以上会在此服务的内部容器中 /etc/hosts 创建一个具有 ip 地址和主机名的映射关系:
162.242.195.82 somehost
50.31.209.229 otherhost
healthcheck
用于检测 docker 服务是否健康运行。
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"] # 设置检测程序
interval: 1m30s # 设置检测间隔
timeout: 10s # 设置检测超时时间
retries: 3 # 设置重试次数
start_period: 40s # 启动后,多少秒开始启动检测程序
image
指定容器运行的镜像。以下格式都可以:
image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd # 镜像id
logging
服务的日志记录配置。
driver:指定服务容器的日志记录驱动程序,默认值为json-file。有以下三个选项
driver: "json-file"
driver: "syslog"
driver: "none"
仅在 json-file 驱动程序下,可以使用以下参数,限制日志得数量和大小。
logging:
driver: json-file
options:
max-size: "200k" # 单个文件大小为200k
max-file: "10" # 最多10个文件
当达到文件限制上限,会自动删除旧得文件。
syslog 驱动程序下,可以使用 syslog-address 指定日志接收地址。
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
network_mode
设置网络模式
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
networks
配置容器连接的网络,引用顶级 networks 下的条目
services:
some-service:
networks:
some-network:
aliases:
- alias1
other-network:
aliases:
- alias2
networks:
some-network:
# Use a custom driver
driver: custom-driver-1
other-network:
# Use a custom driver which takes special options
driver: custom-driver-2
aliases :同一网络上的其他容器可以使用服务名称或此别名来连接到对应容器的服务。
restart
- no:是默认的重启策略,在任何情况下都不会重启容器。
- always:容器总是重新启动。
- on-failure:在容器非正常退出时(退出状态非0),才会重启容器。
- unless-stopped:在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器
注:swarm 集群模式,请改用 restart_policy。restart: "no" restart: always restart: on-failure restart: unless-stopped
secrets
存储敏感数据,例如密码:
version: "3.1"
services:
mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/my_secret
secrets:
- my_secret
secrets:
my_secret:
file: ./my_secret.txt
security_opt
修改容器默认的 schema 标签。
security-opt:
- label:user:USER # 设置容器的用户标签
- label:role:ROLE # 设置容器的角色标签
- label:type:TYPE # 设置容器的安全策略标签
- label:level:LEVEL # 设置容器的安全等级标签
stop_grace_period
指定在容器无法处理 SIGTERM (或者任何 stop_signal 的信号),等待多久后发送 SIGKILL 信号关闭容器。
stop_grace_period: 1s # 等待 1 秒
stop_grace_period: 1m30s # 等待 1 分 30 秒
stop_signal
设置停止容器的替代信号。默认情况下使用 SIGTERM 。
以下示例,使用 SIGUSR1 替代信号 SIGTERM 来停止容器。
stop_signal: SIGUSR1
sysctls
设置容器中的内核参数,可以使用数组或字典格式。
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_syncookies=0
tmpfs
在容器内安装一个临时文件系统。可以是单个值或列表的多个值。
tmpfs: /run
tmpfs:
- /run
- /tmp
ulimits
覆盖容器默认的 ulimit。
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
volumes
将主机的数据卷或着文件挂载到容器里。
version: "3.7"
services:
db:
image: postgres:latest
volumes:
- "/localhost/postgres.sock:/var/run/postgres/postgres.sock"
- "/localhost/data:/var/lib/postgresql/data"
十三、Docker Swarm
十四、CI/CD之Jenkins
十五、IDEA安装插件
了解即可!以后CI/CD,就完全没必要这样做!
- IDEA安装插件
- 配置Docker连接集成
- 集成了docker插件就可以在IDEA中操作Docker内部的容器和镜像了,但是很鸡肋这个功能,对于我 们开发人员来说!之后学习的CI/CD才是真正在企业中的王道!
十六、Docker私服
1. Registry
1. 下载私服镜像
docker pull registry
2. 运行镜像
docker run -d -p 5000:5000 -v /data/program/docker-registry:/var/lib/registry --name docker-registry --restart=always registry
-p:5000:5000:将主机的5000端口映射到容器的5000端口,第1个5000指主机端口号,第2个5000是registry容器的真实监听端口。其中主机端口号可直接在参数中修改,但第2个容器端口号只能通过给容器添加环境变量的方式修改。例如上面的 + -e REGISTRY_HTTP_ADDR=0.0.0.0:5001
–restart=always:当容器重启/退出的时候,自动重启,便于一直可用。官方文档的原话是to restart automatically when Docker restarts or if it exits
–name registry:为容器指定一个名字,便于后续的docker contrainer相关的命令引用,否则会自动生成一个乱叉容器id,每次都要docker ps命令去查
-v /mnt/registry:/var/lib/registry:将主机的/mnt/registry目录mount到容器的/var/lib/registry目录,容器的/var/lib/registry目录是具体存放docker image的位置。这个参数的作用在于将所有image统一放在外部主机的一个固定目录中,便于管理。
3. 修改镜像标签
docker tag hello-world 192.168.174.128:5000/my-world
4. 上传镜像
docker push 192.168.174.128:5000/my-world
-如果出现错误:Get https://192.168.71.131:5000/v2/: http: server gave HTTP response to HTTPS client,请使用第7步解决
5. 查看镜像私服
curl -XGET http://192.168.174.128:5000/v2/_catalog
6. 下载镜像
docker pull 192.168.174.128:5000/my-world
7. 如果无法识别添加地址,/etc/docker/daemon.json
{
"insecure-registries": ["192.168.174.128:5000"]
}
8. 重启Docker
systemctl restart docker