1、Docker简介
1.1 虚拟化技术
介绍 Docker之前有必要了解一下虚拟化技术,其实Docker的出现也是虚拟机技术发展的一个里程碑。随着企业业务量的不断提升,需要横向的扩展多个计算机实例(节点)来满足业务,比如做节点的主备,横向扩展负载均衡的计算节点等,简单地说就是需要多台计算节点(主机)。其实扩展计算节点,最简单的方法就是搭建多台物理机,但这样做成本过高,因此有了建立在物理机之上的虚拟机的诞生,比如VMWare,这就是对宿主机物理资源的一种虚拟化,也就是这个小节要介绍的内容。
虚拟化的定义是什么呢?虚拟化是指在一台宿主机的基础上搭建多个虚拟的运行环境,将宿主机(物理机)的硬件资源虚拟化成运行环境所需要的的资源,比如操作系统、网卡等。虚拟化技术我认为大致分为两大类(按照发展时间顺序): Hypervisor和 容器。
1.1.1 Hypervisor
Hypervisor是指用软件仿真一台电脑出来,在这台虚拟出来的电脑上安装操作系统,配置网络,安装运行软件等等,宿主机有一套操作系统,每个虚拟机有各自的操作系统,且允许宿主机和虚拟机的操作系统不同(windows和linux这种级别的不同),如图所示:
常见的Hypervisor技术有KVM、VMWare等,还有H3C的CAS。说到这里就不得不提到大名鼎鼎的 openstack了,openstack是一款基于python的云管理平台,其功能之一就是管理底层不同Hypervisor技术虚拟化出来的资源,并对外提供统一的API接口来访问并使用这些虚拟化资源,比如用KVM虚拟化了一台主机,用VMWare虚拟化出来了一台主机,外部使用者仅需要使用openstack提供的Nova的api就可以访问这两台主机,而不需要关心底层到底是如何虚拟化的。
这里再引入一个云计算领域的名词: IaaS(Infrastructure as a Service),即基础设施服务。云计算领域的业务自底向上依次为IaaS、PaaS、SaaS,这里简单说一下跟虚拟化有关的IaaS的概念。IaaS作为云厂商的一种服务对外提供,这种服务是云厂商将计算、存储和网络这三大物理资源虚拟化后对外提供的一种能力或是服务,比如用户需要一台虚拟机,可以直接使用公有云厂商或者私有云厂商的接口去创建一台虚拟机,至于虚拟机是如何创建的,在哪创建的都不需要用户关心,但用户可以通过可视化的界面和api去管理自己创建的资源,而不是用户自己费时费力的搭建一台宿主机,再在宿主机上搞几台虚拟机,还要不断的去维护。
1.1.2 容器
上面介绍的Hypervisor技术虚拟化物理资源产生的虚拟机有以下三个问题:
- 虚拟机里面的操作系统本身会占用相当一部分资源(一般几个G),且这些操作系统基本都是一样的,是存在复用的空间的;
- 虚拟化出主机前要提前给虚拟机分配内存、系统盘这些资源,虚拟化后即使虚拟机不在运行状态也会占用宿主机这部分物理资源,存在资源利用率不高的情况;
- 虚拟机启动速度不快。
正因为Hypervisor技术的弊端,推动着虚拟化技术不断地向前探索,并诞生了虚拟化技术的有一个里程碑意义的技术:容器(container)。相比于虚拟机是属于操作系统级别的,容器是属于进程级别的,即多个容器共用宿主机的操作系统,且每个容器之间都是相互隔离的,那容器是如何做到相互之前隔离的呢?主要有文件系统隔离和资源隔离这两大隔离:
- 文件系统隔离
* - 每个容器都具有独立的文件系统,单个容器内对文件系统进行增删改查不会影响到其他容器;
- 参考 Linux 下的 chroot 命令,可以将子目录变为根目录。
- 资源隔离
* - 利用 namespace 隔离进程之间的相互可见及通信;
- 利用 Cgroup 限制资源使用率,设置其能够使用的 CPU 以及内存大小。
容器的示意图如图所示:
这里对Hypervisor技术(或者叫虚拟机)和容器技术进行比较:
容器技术的实现也有若干种,比如大名鼎鼎的Docker, 但容器技术并不等于Docker,Docker是创建容器的工具,是容器的引擎,而不是容器技术本身,另外容器技术除了Docker还有CoreOS rkt、Mesos、lxc 等,但Docker目前占有了容器市场的83%份额。说到容器技术就有必要提一下当下大火的 Kubernetes(K8S),K8S是谷歌开源的项目,起源于谷歌内部积累了10几年的大规模服务器调度系统,是用来管理和调度容器的,K8S基于Docker(以及其他容器化工具),实现在分布式集群上通过一套API对多个容器进行便捷的管理、服务发现、路由转发等等,可能以后我们输命令都不是linux命令了,而是K8S的命令了。
说到K8S就有必要介绍一下 云原生的概念了,云原生简单地说就是用户的软件开发的整个生命周期都是在云上进行的,具体来讲,在软件开发过程中,最直接的用户需要开发、测试、生产环境,即需要虚拟机,这时用户可以用IaaS里的计算服务获取计算资源,再比如用户比如用户需要将数据存储下来,并在需要的时候读取上来,这就需要IaaS里的存储服务存储数据;用户在开发过程中,可能需要mysql或者redis这样的中间件,在CI/CD时需要构建自己的流水线来推动自己的devops建设,在将自己的传统业务转型成微服务化时,可以使用PaaS平台的能力;在软件通过测试和发布后,部署运维阶段可以使用K8s和Docker技术管理自己的应用…整个开发周期都是在云上进行的。CNCF(Cloud-Native Compute Foundation,云原生计算基金会)定义的云原生的三大特征为:
- 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,并作为应用程序部署的独立单元;
- 动态和自动化管理:通过集中式的编排调度系统来对资源进行动态的管理和调度,即K8S。
- 面向微服务:明确服务间的依赖,互相解耦。
1.1.3 虚拟化领域有关概念和名词
这里总结了上面介绍内容中有关虚拟化的名词和概念,如下表:
名词 概念
虚拟化 在一台宿主机的基础上搭建多个虚拟的运行环境,将宿主机(物理机)的硬件资源虚拟化成运行环境所需要的的资源,比如操作系统、网卡等,分为Hypervisor和容器两类。 Hypervisor 用软件仿真一台电脑出来,在这台虚拟出来的电脑上安装操作系统,配置网络,安装运行软件等等,宿主机有一套操作系统,每个虚拟机有各自的操作系统,常见的Hypervisor技术有KVM、VMWare等。 openstack openstack是一款基于python的云管理平台,用户可以通过API访问管理底层的虚拟化资源(底层兼容多种不同的虚拟化方式),并有着自己的一套认证鉴权体系。 IaaS 云计算领域业务划分的三类之一,也是最底层的,即基础设施服务,包括对计算、存储和网络的三大虚拟化。 容器 虚拟化方式的一种,进程级别的虚拟化方式,容器间共用底层宿主机的操作系统。 docker Docker是创建容器的工具,是应用容器的引擎。 K8s K8S通过编排调度对容器进行管理和调度。 云原生 软件开发的一种模式,软件的整个声明周期都是在云上进行的,并有着容器、K8S、devops和微服务化的特征。
1.2 Docker基本名词和概念
1.2.1 Docker架构
Docker也是经典的client-server架构,如下如图所示:
如上图所示:我们使用Docker时输入的Docker开头的命令都是在client端输入,client端向Docker服务器或者守护进程发出请求,服务器或者守护进程将完成所有工作并返回结果,Docker提供了一个命令行工具以及一整套 RESTful API可以在客户端使用。可以在同一台宿主机上运行 Docker 守护进程和客户端,也可以从本地的Docker 客户端连接到运行在另一台宿主机上的远程 Docker守护进程。从上图也可以看出,容器(container)是运行在Docker服务端的,但一般都会把Docker服务端和客户端安装在一个节点上,就仿佛容器就是跟客户端是一个环境,这一点要区分开。
下面介绍Docker的三个重要的概念:
- 镜像(image)
- 容器(container)
- 仓库(Registry)
1.2.2 镜像
Docker 镜像就是一个 Linux 的文件系统(Root FileSystem),这个文件系统里面包含可以运行在 Linux 内核的程序、运行程序所依赖的库文件、环境变量和配置文件等,可以简单理解成对当前程序和运行的环境的一个快照(snapshot)。关于Docker的分层镜像文件系统后面单独详细地讲。
1.2.3 容器
容器是应用程序运行的环境,镜像和容器的关系就好比OOP里的类和对象的关系,容器可以被创建、启动、停止、删除、暂停等 ,容器的实质是进程,但与直接在宿主机上执行的进程不同,容器进程运行在属于自己的独立的命名空间。
1.2.4 仓库
Docker用仓库来存放各种镜像,Registry分为公共和私有两种,Docker公司运营公共的Registry叫做 Docker Hub,每个公司可以搭建自己的私有仓库,这一点就跟Maven的公共仓库和私服一样。通常一个仓库会包含同一个软件不同版本的镜像,而标签对应该软件的各个版本,我们可以通过
1.3 Docker流程
2、Docker安装
所有有关Docker的内容在Docker的官方文档写的很详细,虽然是英文的。下面的教程是基于Centos7的。
(1)卸载环境上旧的Docker
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 \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
(4)安装io,否则报错
dnf install https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm
(5)安装最新版的docker
sudo yum install docker-ce docker-ce-cli containerd.io
如果这一步报错,尝试第四步里安装更高版本的io,比如:
dnf install https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.4.3-3.1.el7.x86_64.rpm
(6)启动docker
sudo systemctl start docker
(7)测试,运行hello world镜像的容器
sudo docker run hello-world
(8)阿里云镜像加速
登录阿里云容器镜像加速页面:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <
卸载Docker的步骤:
(1)卸载依赖
sudo yum remove docker-ce docker-ce-cli containerd.io
(2)删除目录
sudo rm -rf /var/lib/docker
此外,针对阿里云ECS的Alibaba Cloud Linux系统,Docker安装请参考:https://helpcdn.aliyun.com/document_detail/51853.html
3、基本命令
3.1 镜像基本命令
3.1.1 帮助命令:
docker 命令 --help
3.1.2 显示docker版本信息
docker version
3.1.3 显示docker系统信息
docker info
3.1.4 查看主机所有的镜像
docker images [可选项]
结果:
说明:
- REPOSITORY :镜像的仓库源
- TAG:镜像标签
- IMAGE ID :镜像Id
- CREATED:镜像创建时间
- SIZE:镜像大小
可选项:
- -a, -all:列出所有镜像
- -q, –quiet:只显示镜像的id
- -aq,显示所有镜像的id
这里单独说一下上图中的REPOSITORY,Docker是把一个系列的镜像放到一个仓库(就是这个REPOSITORY),比如把mysql的不同TAG(版本)的镜像都放到REPOSITORY为mysql的仓库中,因此docker有两种方式唯一对应一个镜像:
- REPOSITORY:TAG
- IMAGE ID
3.1.5 搜索镜像
docker search [可选项] 组件名称
可选项:
- -f, –filter:过滤条件,比如
docker search --filter=STARTS=3000 mysql
,表示搜索STARS大于3000的mysql镜像。
3.1.6 下载镜像
docker pull 组件名称 # 省略镜像版本默认是下载最新版本:latest
docker pull 组件名称: 版本号 # 指定版本号下载,这个版本号就是下载后镜像的TAG
3.1.7 删除镜像
docker rmi -f 镜像Id # 删除指定镜像Id的镜像
docker rmi -f $(docker images -aq) # 删除所有镜像
docker rmi 镜像名:版本号 # 当有多个镜像的id一样时,可以通过指定版本号的镜像名删除
3.1.8 构建镜像
前提是写了一个镜像的DockerFile,
docker build -t ImageName:TagName dir
可选项
-t
: 给镜像加一个TagImageName
: 给镜像起的名称TagName
: 给镜像的Tag名Dir
:Dockerfile所在目录
举例:
docker build -t myimg:0.1 .
说明:构建了一个镜像名为myimg,版本号为0.1的镜像,注意0.1后面有个空格和点,点表示当前目录,即上面的dir参数,此时Dockerfile必须在执行构建镜像命令的目录下。
3.1.9 上传镜像
- 将本地的镜像上传到镜像仓库,要先登陆到镜像仓库;
- NAME最好是路径名/镜像名,最好带上版本号,加以区分。
docker push [OPTIONS] NAME[:TAG]
3.1.10 给镜像打标签
docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
比如我现在有一个 centos 镜像:
[root@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 1e1148e4cc2c 2 weeks ago 202MB
我对 centos 进行开发,开发了第一个版本,我就可以对这个版本打标签,打完标签后会生成新的镜像:
[root@localhost ~]$ docker tag centos centos:v1
[root@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 1e1148e4cc2c 2 weeks ago 202MB
centos v1 1e1148e4cc2c 2 weeks ago 202MB
我继续对 centos 进行开发,开发了第二个版本,继续打标签:
[root@localhost ~]$ docker tag centos centos:v2
[root@localhost ~]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 1e1148e4cc2c 2 weeks ago 202MB
centos v1 1e1148e4cc2c 2 weeks ago 202MB
centos v2 1e1148e4cc2c 2 weeks ago 202MB
以此类推,每开发一个版本打一个标签,如果以后我想回滚版本,就可以使用指定标签的镜像来创建容器:
root@localhost ~]$ docker run -itd centos:v1
注意:当你没有对镜像做任何写操作,而仅仅是在docker tag时改了个镜像名,那么docker tag生成的镜像会和之前的镜像的id一样,只是镜像名不同。
3.1.11 从容器中创建一个新镜像
docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
OPTIONS说明:
- -a :提交的镜像作者;
- -c :使用Dockerfile指令来创建镜像;
- -m :提交时的说明文字;
- -p :在commit时,将容器暂停。
举例:将容器a404c6c174a2 保存为新的镜像,并添加提交人信息和说明信息:
docker commit -a "runoob.com" -m "my apache" a404c6c174a2 mymysql:v1
3.2 容器基本命令
3.2.1 新建容器并启动
docker run [可选项] 镜像名称
可选项:
- –name=”容器”,用来区分容器
- -d,后台方式运行
- -it:使用交互方式运行,进入容器内部查看内容,也是进入容器使用最多的命令,比如运行centos镜像:
docker run -it centos /bin/bash
- -p:指定容器的端口,比如-p 主机端口:容器端口
- -P:随机指定端口
- -net:指定容器与哪个网络相连
这里对-p可选性做进一步说明:
首先说一下docker容器的端口和宿主机端口的映射,需要对docker容器的端口和宿主机端口做映射,才能通过ip:宿主机port访问到对应的容器,这个映射是在创建并启动docker容器时指定的,关于映射需要注意:
- 宿主机的一个端口只能映射到容器内部的某一个端口上,比如:8080->80之后,就不能8080->81;
- 容器内部的某个端口可以被宿主机的多个端口映射,比如:8080->80,8090->80,8099->80。
启动容器时,选择一个端口映射到容器内部开放端口上:
[root@docker-test ~]# docker run -ti -d --name my-nginx -p 8088:80 docker.io/nginx
2218c7d88ccc917fd0aa0ec24e6d81667eb588f491d3730deb09289dcf6b8125
[root@docker-test ~]# docker run -ti -d --name my-nginx2 -P docker.io/nginx
589237ceec9d5d1de045a5395c0d4b519acf54e8c09afb07af49de1b06d71059
[root@docker-test ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
589237ceec9d docker.io/nginx "nginx -g 'daemon ..." 6 seconds ago Up 5 seconds 0.0.0.0:32770->80/tcp my-nginx2
2218c7d88ccc docker.io/nginx "nginx -g 'daemon ..." About a minute ago Up About a minute 0.0.0.0:8088->80/tcp my-nginx
由上面可知:
容器my-nginx启动时使用了-p,选择宿主机具体的8088端口映射到容器内部的80端口上了,访问http://localhost/8088即可
容器my-nginx2启动时使用了-P,选择宿主机的一个随机端口映射到容器内部的80端口上了,这里随机端口是32770,访问http://localhost/32770即可
启动创建时,绑定外部的ip和端口(宿主机ip是192.168.10.214):
[root@docker-test ~]# docker run -ti -d --name my-nginx3 -p 127.0.0.1:8888:80 docker.io/nginx
debca5ec7dbb770ca307b06309b0e24b81b6bf689cb11474ec1ba187f4d7802c
[root@docker-test ~]# docker run -ti -d --name my-nginx4 -p 192.168.10.214:9999:80 docker.io/nginx
ba72a93196f7e55020105b90a51d2203f9cc4d09882e7848ff72f9c43d81852a
[root@docker-test ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba72a93196f7 docker.io/nginx "nginx -g 'daemon ..." 2 seconds ago Up 1 second 192.168.10.214:9999->80/tcp my-nginx4
debca5ec7dbb docker.io/nginx "nginx -g 'daemon ..." 3 minutes ago Up 3 minutes 127.0.0.1:8888->80/tcp my-nginx3
由上面可知:
容器my-nginx3绑定的宿主机外部ip是127.0.0.1,端口是8888,则访问http://127.0.0.1:8888或http://localhost:8888都可以,访问http://192.168.10.214:8888就会拒绝!
容器my-nginx4绑定的宿主机外部ip是192.168.10.214,端口是9999,则访问http://192.168.10.214:9999就可以,访问http://127.0.0.1:9999或http://localhost:9999就会拒绝!
3.2.2 从容器中退回宿主机
exit # 容器停止并退出
Ctrl + p + q # 容器不停止退出
3.2.3 显示当前正在运行的容器
docker ps # 打印当前正在运行的容器
docker ps -a # 打印历史运行过的容器
docker ps -n=10 # 打印前n条容器记录,可配合-a使用
docker ps -q # 仅显示容器的id,可配合-aq使用
3.2.4 删除容器
docker rm 容器id # 删除指定容器,不能删除正在运行的容器
docker rm -f 容器id # 删除指定容器,强制删除
docker rm -f $(docker ps -aq) # 删除所有容器
3.2.5 启停容器
docker start 容器id # 启动容器
docker stop 容器id # 停止容器
docker restart 容器id # 重启容器
docker kill 容器id # 强制停止容器
3.3 其他命令
3.3.1 查看日志
docker logs -tf 容器id # 查看指定容器的日志,-f是可以看到即时日志
docker logs -f -t --tail 10 容器id # 查看指定容器的日志,仅显示10条日志
3.3.2 查看容器中进程的信息
docker top 容器id
3.3.3 查看容器的元数据
docker inspect 容器id
3.3.4 进入当前正在运行的容器
docker exec -it 容器id /bin/bash
docke attach 容器id /bin/bash
区别:
- docker exec,进入容器后开启一个新的终端,可以在里面操作
- docke attach,进入容器正在执行的终端
docker exec -it后面还可以追加linux命令,举例:
进入容器tomcat02并做ping命令
docker exec -it tomacat02 ping 192.108.1.1
docker run和docker exec的区别:
- docker run :根据镜像创建一个容器并运行一个命令,操作的对象是 镜像;
- docker exec :在运行的容器中执行命令,操作的对象是 容器。
3.3.5 将容器内的文件拷贝到容器外宿主机上
docker ps 容器id:容器内路径 宿主机路径
举例: docker cp 09fce574695f:/home/jerry.java /home
docker cp 是一个手动拷贝的过程,后面我们使用卷的技术,可以实现自动同步。
3.4 网络命令
3.4.1 查看Docker网络
docker network ls
3.4.2 查看Docker网络详情
docker network inspect docker网络ID
其中docker网络ID可以通过 docker network ls
查看。
举例:查看本机docker0网络的信息:
docker network inspect 7f12423b5112
部分截图如下:
上图也是当前网络被哪些容器使用的图。
使用 docker inspect 容器id
查看容器的详情,也能查询到容器的网络信息,如下:
[root@iZ2vcf0atudng4otf06u38Z ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e493a6ee1baa tomcat "catalina.sh run" 13 minutes ago Up 13 minutes 0.0.0.0:32770->8080/tcp tomcat03
6fe98b6b9c81 tomcat "catalina.sh run" 23 hours ago Up 23 hours 0.0.0.0:32769->8080/tcp tomcat02
5296f3a9910b tomcat "catalina.sh run" 23 hours ago Up 23 hours 0.0.0.0:32768->8080/tcp tomcat01
[root@iZ2vcf0atudng4otf06u38Z ~]# docker inspect 6fe98b6b9c81
网络信息部分的截图如下:
3.4.3 删除Docker网络
支持一次删除一个或多个Docker网络。
docker network rm 网络名称1, 网络名称2...
3.4.4 创建Docker网络
docker network create --driver 网络模式 --subnet 子网范围 --gateway 网关地址 自定义网络的名称
举例:
创建一个如下网络:
网络模式是桥接模式
网络名词是mynet
子网信息是:192.168.0.0/16
网关地址是:192.168.0.1
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
创建好后查看docker网络,可以看到新建的mynet网络已经出现:
[root@iZ2vcf0atudng4otf06u38Z ~]# docke network ls
-bash: docke: command not found
[root@iZ2vcf0atudng4otf06u38Z ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
7f12423b5112 bridge bridge local
a70143b200b4 host host local
efaf028a11ad mynet bridge local
7cd362f44529 none null local
3.4.5 连通Docker网络
意思是连通宿主机内不同网段的Docker网络,示意图如下:
上图中,mynet01和mynet02不是一个网段的,tomcat01和tomcat02肯定无法ping通tomcat03和tomcat04容器,反之亦然。 可以使用docker network connect 将一个容器放置在某个网络下:
docker network connect 网络名 容器名
使用docker network inspect 网络名 可以查看网络下对应哪些容器。
举例:
docker network connect mynet02 tomcat01
将容器tomcat01放置在mynet02网络下,此时再将tomcat01与tomcat03和tomcat04互相ping是可以ping通的,因为这三个容器都是在一个网络下,而tomcat02仍然不可以,除非将tomcat02也放置在mynet02下。
4、Docker镜像分层文件系统
4.1 基于联合文件系统的分层镜像系统
Docker的镜像分层系统是基于 联合文件系统(UnionFs)实现的。
什么是联合文件系统?联合文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,表现形式就是:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
上图是个很经典的图,展示了Docker分层的镜像系统。一般最底层是个bootfs,这个就对应linux的引导层;在bootfs上面的一层一般是rootfs,包含的就是典型 Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件,rootfs可以理解为各种不同的操作系统发行版,比如:Ubuntu,、CentOS等;rootfs上面就是我们具体的镜像层了,一般在底层的是基础镜像层(Base Image),基础镜像层可能是一层,也可能是基层;基础镜像层上面就是各自的镜像层了,这个镜像层也可以是一层,也可以是多层;下面的镜像层是上面镜像层的父镜像。每安装一个软件,就在现有镜像的基础上增加一层。这样设计的最大好处就是不同的镜像可以共享基础镜像层,Docker Host 只需在磁盘上保存一份 base 镜像(因为镜像的ID唯一),同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。
4.2 容器层和镜像层
当从一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统,我们在Docker中运行的程序就是在这个读写层中执行的,这个读写层通常被称作容器层,容器层之下的都叫镜像层。所有对容器的改动 ,即添加、删除、修改都只会发生在容器层中,只有容器层是可写的,容器层下面的所有镜像层都是只读的。如图所示:
当对容器层进行修改时,具体有以下几种场景:
- 添加文件:在容器中创建文件时,新文件被添加到容器层中;
- 读取文件:在容器中读取某个文件时,Docker会从上往下依次在各镜像层中查找此文件,一旦找到,打开并读入内存;
- 修改文件:在容器中修改已存在的文件时,Docker会从上往下依次在各镜像层中查找此文件,一旦找到,立即将其拷贝一份到容器层,然后修改之;
- 删除文件:在容器中删除文件时,Docker也是从上往下依次在镜像层中查找此文件。一旦找到,会在容器层中记录下此删除操作。
只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。
5、Portainer可视化面板
Portainer跟Rancher一样,也是容器的一个可视化管理平台。Portainer的单机安装环境十分简单,如下:
搜索镜像
docker search portainer/portainer
拉取镜像
docker pull portainer/portainer
运行镜像
docker run -d -p 9000:9000 -v /root/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name dev-portainer portainer/portainer
参数说明:
- -d #容器在后台运行
- -p 9000:9000 # 宿主机9000端口映射容器中的9000端口
- -v /var/run/docker.sock:/var/run/docker.sock # 把宿主机的Docker守护进程(docker daemon)默认监听的Unix域套接字挂载到容器中
- -v /root/portainer:/data # 把宿主机目录 /root/portainer 挂载到容器 /data 目录;
- –name dev-portainer # 指定运行容器的名称
具体步骤可以参考我的另一篇文章:
https://www.yuque.com/docs/share/9214ef22-4760-405c-a7bc-cc84ea3ea872?# 《阿里云ECS实例打开端口》
浏览器登录后界面如下:
输入好密码后,选择local登录,首页如下:
点击上图的local后,可以看到服务器上的docker镜像和容器的相关信息,如下:
上图可以看到,local上有3个镜像(Images),5个容器(Containers),点击Images,如下:
点击Containers,如下:
6、容器数据卷
6.1 容器卷的概念
首先说一下容器数据卷的背景:
(1)容器内的数据需要做持久化。
docker容器运行的时候,会产生一系列的文件或者数据,比如MySql容器运行期间肯定会产生数据,这些数据有些需要做持久化到宿主机,如果删除了容器,那容器里的数据自然就没有了,因此需要一种技术,即使容器被删除,容器里的数据依然能保留下来;
(2)在宿主机上对容器内的文件进行操作
有时现在宿主机上就可以对容器内部的文件或者配置进行更新,而不需要进入容器内的目录去更新,此时需要将宿主机上的目录和容器内的目录连通(做一个映射),因此需要一种技术,在宿主机上就可以对容器内的文件执行更新创建的操作;
(3)容器间的数据需要做共享。
比如MySql容器,有时需要将MySQL01、MySQL02、MySQL03等不同的MySQL容器间的数据共享,比如在MySQL01中产生的数据可以同步给其他MySQL容器,因此需要一种容器间的数据同步技术。
针对第一、第二种场景,有6.2的容器数据卷技术;针对第二种场景,有6.4的数据卷容器的技术。这两个名字好像只是倒过来,但含义还是有不一样的地方。
6.2 使用数据卷
使用容器数据卷有三种方式:普通挂载、具名挂载和匿名挂载。
这里先介绍2个有关数据卷的docker命令:
查看当前宿主机上所有的卷
docker volume ls
查看某个具体卷的信息
docker volume inspect 卷名
宿主机上docker的volume的保存地址:
/var/lib/docker/volumes
6.2.1 普通挂载
命令:
docker run -it --name="容器名" -v 宿主机目录:容器目录
举例:
创建并运行一个名字为centos01的容器,并将容器的/home/testv目录挂载到宿主机的/home/volumn目录
docker run -it --name="centos01" -v /home/volumn:/home/testv centos
测试:
在容器centos01的/home/testv目录下新建一个名字为cfile的文件,在宿主机的/home/volumn目录下可以看到相同的cfile文件,如图:
需要注意:可以在docker run命令后一次性跟多个-v,即一次性挂载多个目录,下同。
6.2.2 匿名挂载
即创建容器挂载卷时,不指定卷的具体名字,卷名缺省。
命令:
docker run -it --name="容器名" -v 容器内的路径
举例:
创建并运行容器centos02,创建一个匿名卷,匿名卷对应的cnetos02容器内的路径是/home
docker run -it --name="centos02" -v /home centos
结果:
匿名卷会在宿主机本地创建一个随机数作为VOLUME NAME。
6.2.3 具名挂载
即创建容器挂载卷时,指定卷的具体名字。
命令:
docker run -it --name "容器名" -v 卷名:容器内的路径
举例:
创建并运行容器centos03,创建一个具名卷,具名卷对应的cnetos03容器内的路径是/home,且具名卷的卷名为spec_volume
docker run -it --name "centos03" -v spec_volume:/home centos
结果:
查看具名卷spec_volume的信息,如下:
cd到volume的路径下,可以看到刚才我们创建的匿名卷和具名卷在该路径下:
具名卷相比匿名卷使用的更多。
6.3 实战:安装MySql
6.4 数据卷容器
上面说到了数据卷容器是为了实现容器间的数据同步。这里要引入一个概念: 父容器。父容器也叫 数据卷容器,是指所有容器要同步的那个最初的容器,比如先创建了容器mysql01,后面使用 --volumes-from
创建了容器mysql02、mysql03…使这些后来创建的容器都挂载第一个容器mysql01,即后面的02、03…容器都共享01容器的目录,那这个01容器就是父容器,也叫数据卷容器。
命令:
docker run -it --name="子容器名" --volumes-from 父容器名 镜像名
举例:
创建并运行父容器centos01
docker run -it --name="centos01" centos
创建并运行子容器centos02,并挂载centos
docker run -it --name="centos02" --volumes-from centos01 centos
测试:
有问题???
7、DockerFile
7.1 DockerFile基本概念
DockerFile是用来构建docker镜像的文本文件,将构建镜像的步骤通过DockerFile的命令一步一步写在DockerFile文件里。
一般构建Docker镜像的步骤如下:
- 编写DockerFile文件;
- docker build将第一步写好的DockerFile文件构建为一个镜像;
- docker run 运行镜像;
- docker push 发布镜像,可以发布到DockerHub,也可以发布到私有仓库中。
去dockerhub的官网,搜索centos的镜像,会跳转到github上,centos镜像的其中一个版本的DockerFile如下:
FROM scratch
ADD centos-8-x86_64.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="20201204"
CMD ["/bin/bash"]
随着容器概念的流行和Docker这个容器思想具体的落地,企业交付项目或者微服务都是以Docker镜像交付,而不是之前的jar包war包。对于SpringBoot开发的微服务,一般会在项目目录里有个Docker目录,里面有着DockerFile文件或者DockerFile.template文件等跟Docker相关的文件;另外在项目目录里还会有个deployment目录,里面具体会有script目录(一般放一些初始化shell脚本和相关的sql初始化脚本)等。在后面会讲到具体的springBoot项目如何打包成一个Docker镜像并push到仓库。
7.2 DockerFile指令
7.2.1 DockerFile基础知识
DockerFile的一些基础知识如下:
- 每个指令关键字必须是大写字母;
- DockerFile中的指令是从上到下顺序执行的;
- ‘#’表示注释;
- 每一个指令都会创建并提交一个新的镜像层。
- 一般企业有完整的一套devops自动化流水线进行编译构建打包工作,DockerFile中的一些变量会写成${参数名}的形式通过流水线构建时指定的参数值进行具体的构建。
7.2.2 DockerFile基本指令
(1)FROM
一般在DockeFile的第一行都会写FROM,FROM后面是基础镜像名称,也即你当前构建的镜像的父镜像,一般企业开发里FROM后面会跟公司的私服中的镜像名称和版本号。
FROM
FROM :
(2)MAINTAINER
备注该镜像创建者的信息,一般是姓名 + 邮箱。
MAINTAINER
(3)ADD
将要添加到当前构建的镜像中的组件或者服务通过ADD指令添加进镜像中,在微服务中一般ADD添加的是微服务的名称。
ADD
(4)ENV
设置环境变量,通过key-value键值对的形式设置。
ENV
ENV =
举例:
ENV JAVA_HOME /usr/local/jdk1.8.0_11
(5)WORKDIR
在容器内部设置工作目录,当你进入该镜像生成的容器时,输入pwd就会显示这个WORKDIR指定的工作目录,即一进容器就是进入了这个工作目录中,ENTRYPOINT和CMD指定的命令都会在容器的工作目录下进行。
WORKDIR /workdir
DockerFile里可以先用ENV配置环境变量,再用 $环境变量名
来获取这个环境变量的值,如下:
ENV MYPATH /usr/local
WORKDIR $MYPATH
(6)VOLUME
用于向容器添加卷,可以提供共享存储等功能。
VOLUME ['/data']
(7)EXPOSE
指定运行该镜像的容器使用的端口,可以是多个。通过EXPOSE将这个端口指定好,启动该镜像生成的容器时就不用了-p启动了。
EXPOSE
(8)CMD
CMD (shell模式)
CMD [ "executable", "param1", "param2" ] (exec模式)
CMD [ 'param1', 'param2'] (通常与ENTRYPOINT搭配指定ENTRYPOINT的默认参数)
- 指定容器启动时,每个DockerFile只能有一条CMD命令,如果指定了多条CMD命令,只有最后一条CMD命令会被执行;
- 如果用户启动容器时执行了运行的命令,即
docker run -it name '容器名'
运行命令,则 运行命令会覆盖掉CMD指定的命令。
(9)ENTRYPOINT
指定镜像生成的容器启动时要运行的命令,可以追加命令。
ENTRYPOINT (shell模式)
ENTRYPOINT [ "executable", "param1", "param2" ] (exec模式)
- 与CMD不同,ENTRYPOINT指定的命令不会被
docker run
后面拼接的命令覆盖,而是将拼接的命令当做ENTRYPOINT指定命令的参数; - 每个DockerFILE只能有一个ENTRYPOINT命令,当指定多个时,只有最后一个生效。
(10)RUN
用于指定构建镜像时运行的命令,两种模式:
RUN (shell模式)
RUN [ "executable", "param1", "param2" ] (exec模式)
- 在shell模式下,是使用/bin/sh -c COMMAND来运行命令的;
- 在exec模式下可以指定其他的shell来运行命令RUN [“/bin/bash”, “-c”, “echo hello”]。
多条RUN指令可以合并为一条,用&&分隔,举例:
RUN yum install httpd && yum install ftp
(11)ONBUILD
为镜像创建触发器,当一个镜像被用作其他镜像的基础镜像时,这个触发器会被执行,当子镜像构建时会插入触发器中的指令。
举例
ONBUILD COPY index.html /var/www/html
(12)COPY
跟ADD一样,作用都是将文件或目录复制到Dockerfile构建的镜像中,ADD包含了类似tar的解压功能,如果只是单纯复制文件,建议使用COPY,而且,两者的源文件路径使用Dockerfile相对路径,目标路径使用绝对路径。
COPY
当前项目中的DockeFile也很简单,只用到了FROM、ADD、ENTRYPOINT、RUN、ENV、EXPOSE、WORKDIR这几个指令,ENTRYPOINT指令后面跟的是一个脚本。
7.3 DockerFile样例
以公司的DockerFile文件为例子,如下:
FROM ${DOCKER_REGISTRY_JAVA_BASE_IMAGE}
USER root
ENV LANG en_US_UTF-8
ADD ${SERVICE_NAME} /opt/{SERVICE_NAME}
EXPOSE 18088
WORKDIR /opt/{SERVICE_NAME}
ENTRYPOINT ["./startup.sh"]
startup.sh是java程序启动的脚本,里面配置了启动java程序制定的jvm参数以及jar包,如下:
#!/usr/bin/env sh
java -XX:+UseG1GC \
-server \
-Duser.timezone='GMT+08:00' \
-Duser.country=CN \
-Djava.security.egd=file:/dev/./urandom \
-Xms256M \
-Xmx2048M \
-XX:HeapDumpPath=/opt/pco-portal/log \
-XX:-HeapDumpOnOutOfMemoryError \
-Xdebug \
-XX:+PrintGCDetails \
-Xrunjdwp:transprot=dt_socket,server=y,suspend=n,address=9527 \
-jar pco-protal.jar
这么做的目的是:启动java程序时想更细粒度的控制java程序,比如设置JVM参数等,把这些参数变量统一在sh脚本里设置,解耦美观。
8、发布镜像
大家平时工作的时候肯定是将镜像发布到公司的私有镜像仓库中,平时在家做的项目可以发布到DockerHub或者阿里云镜像仓库中。
8.1 发布镜像到DockerHub
(1)登录dockerhub
docker login
结果:
[root@iZ2vcf0atudng4otf06u38Z ~]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: woshuangguoyang
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
登录好后上传镜像:
docker push 镜像名称:版本号TAG
8.2 发布镜像到阿里云
DockerHub是外网,一般很慢,方便的话可以发布到阿里云镜像仓库。
(1)创建命名空间
命名空间作为一些仓库的集合,推荐将一个公司或组织的仓库集中在一个命名空间下面,即一个命名空间底下对应多个仓库,更多阿里云容器镜像服务命名空间的介绍参考:https://help.aliyun.com/document_detail/60765.html
(2)创建镜像仓库
创建镜像仓库时有个地方记得选择本地。
创建好仓库后,可以点击进入仓库,可以看到仓库的基本信息和操作指南,不得不说阿里的文档说明和帮助指南是真的详细易懂,如图:
(3)虚拟机登录阿里云镜像仓库
[root@iZ2vcf0atudng4otf06u38Z ~]# sudo docker login --username=15828074705 registry.cn-shenzhen.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
(4)给要上传的镜像打TAG
先看宿主机下有哪些镜像:
[root@iZ2vcf0atudng4otf06u38Z ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql 5.7 697daaecf703 7 days ago 448MB
centos latest 300e315adb2f 11 days ago 209MB
portainer/portainer latest 62771b0b9b09 4 months ago 79.1MB
hello-world latest bf756fb1ae65 11 months ago 13.3kB
把hello-world:latest镜像推到我们第二步创建的镜像仓库中,先给hello-world:latest镜像打TAG:
sudo docker tag hello-world:latest registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1
格式是:
sudo docker tag 宿主机待上传的镜像仓库:版本号 阿里云上仓库地址:版本号
(5)向阿里云镜像仓库上传镜像
sudo docker push registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1
结果:
[root@iZ2vcf0atudng4otf06u38Z ~]# sudo docker push registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1
The push refers to repository [registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo]
9c27e219663c: Mounted from docker_study_jerry/docker_study_repo/my-hello-world
0.1: digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 size: 525
打开阿里云镜像仓库的控制台,可以看到我们在第二步创建的仓库中已经上传了镜像:
(6)从阿里云镜像仓库下载镜像
第一步还是要登录,然后再用docker pull拉取镜像,比如拉取上面仓库中的镜像:
使用方法
sudo docker pull 阿里云镜像仓库地址:版本号
举例,拉取上面的镜像
sudo docker pull hello-world:latest registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1
再一次感叹阿里云文档之详细易懂为用户使用考虑。
9、Docker网络
9.1 Docker网络概述
安装 Docker 以后,会默认创建三种网络,可以通过 docker network ls
查看。
上图有Docker网络的三种模式,其实Docker网络一共有四种模式:
网络模式 简介
bridge Docker的默认网络模式,采用veth-pair技术,为每一个容器分配一个ip,并将容器连接到docker守护进程创建的docker0的虚拟网桥,容器通过这个docker0虚拟网桥连通。 host 容器使用宿主机的ip和端口,容器本身不会虚拟出网卡,配置自己的ip等。 none 容器有独立的network namespace,但并没有对其进行任何网络设置,如分配veti pair和网桥连接、Ip等。 container 新创建的容器不会创建自己的网卡,配置自己的ip,而是和一个指定的容器共享ip、端口范围等。
上面四种模式中,host模式和none模式不常用,container模式在容器网络连通里讲,这里主要介绍一下Docker默认的网络模式:桥接模式(bridge)。
桥接模式
桥接模式是Docker网络的默认模式,在该模式下,Docker守护进程会创建一个虚拟网桥docker0,每创建一个容器时,Docker守护进程会创建一对对等的虚拟设备接口veth pari,将其中一个接口设置为容器的eth0接口(容器的虚拟网卡),另一个接口放置在宿主机的命名空间中,以类似veth***这样的名字命名,从而将宿主机上的所有容器都连接到这个docker0网桥上,进而使容器间互相通信。
下面通过具体例子来说明桥接模式:
(1)查看宿主机上的docker网络
ip addr
(2)启动第一个tomcat容器
后台运行第一个tomcat容器tomcat01
docker run -d -P --name tomcat01 tomcat
查看tomcat01容器的ip信息
docker exec -it tomcat01 ip addr
(3)启动第二个tomcat容器
后台运行第二个tomcat容器tomcat02
docker run -d -P --name tomcat02 tomcat
查看tomcat02容器的ip信息
docker exec -it tomcat02 ip addr
(4)再观察宿主机的网络信息
ip addr
上面两个tomcat容器和宿主机上网络的桥接模式的结构图如下, 需要注意的是tomcat01容器和tomcat02容器之间网络连通都要经由docker0转发:
最后总结一下Bridge桥接模式的实现步骤:
- Docker Daemon 利用 veth pair 技术,在宿主机上创建一对对等虚拟网络接口设备,假设为 veth0 和 veth1。而 veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报文,都会将报文传输给另一方;
- Docker Daemon 将 veth0 附加到 Docker Daemon 创建的 docker0 网桥上,保证宿主机的网络报文可以发往 veth0;
- Docker Daemon 将 veth1 添加到 Docker Container 所属的 namespace 下,并被改名为 eth0。如此一来,宿主机的网络报文若发往 veth0,则立即会被 Container 的 eth0 接收,实现宿主机到 Docker Container 网络的联通性;同时,也保证 Docker Container 单独使用 eth0,实现容器网络环境的隔离性。
思考一个场景:docker新启动一个容器时,会给这个容器分配一个ip地址,但在某些场景,比如MyBaits里的数据库链接里将这个ip写死了,如果新启动容器后这个ip变了,会导致数据库连接不上,此时需要指定容器名去连接,即ping的是容器名而不是容器的ip,这样即使容器的ip变了,但是容器名不变,依然可以通过容器名去ping通网络。上一节介绍的docker默认的桥接模式docker0并不支持通过容器名访问容器。
实现上面的场景有两种方法:
- link
- 自定义网络连接
其中第一种方法已经不推荐使用了,推荐使用第二种方法。
link是在创建并启动容器的时候指定的。
方法:
docker run -d -P --name 容器名1 --link 容器名2 镜像名
上述通过–link创建的容器1,可以通过容器名ping通容器2,但反过来,容器2依然不能ping通容器1,如下:
`shell
[root@iZ2vcf0atudng4otf06u38Z ~]# docker run -d -P –name tomcat03 –link tomcat02 tomcat
e493a6ee1baa794484b47b97fb03c134432fab01d18fa28a46818331e2e2c1aa
[root@iZ2vcf0atudng4otf06u38Z ~]# 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.133 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.101 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=3 ttl=64 time=0.084 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=4 ttl=64 time=0.084 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=5 ttl=64 time=0.085 ms
^C
3 packets transmitted, 3 received, 0% packet loss, time 48ms
rtt min/avg/max/mdev = 0.098/0.101/0.108/0.012 ms
[root@iZ2vcf0atudng4otf06u38Z ~]# docker exec -it tomcat02 ping tomcat01
PING tomcat01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.087 ms
64 bytes from tomcat01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.084 ms
64 bytes from tomcat01.mynet (192.168.0.2): icmp_seq=3 ttl=64 time=0.091 ms
^C
Original: https://www.cnblogs.com/wxdnq/p/15762461.html
Author: 微笑带你去
Title: Docker 从入门到入土
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/576336/
转载文章受原作者版权保护。转载请注明原作者出处!