docker

文章目录

虚拟化

虚拟化:是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机。
在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立的空间内运行而互不影响,从而显著提高计算机的工作效率

技术角度分类

完全虚拟化技术:在虚拟机(VM)和硬件之间加了一个软件层Hypervisor(或者叫虚拟机监控器VMM),Hypervisor在中间好比翻译者的角色,工作在硬件层之上,直接可以跟硬件进行交互,应用程序发送指令调用内核,内核通过驱动的方式返回给Hypervisor ,Hypervisor 在把指令翻译给硬件去听,然后在一级级返回给应用程序
Hypervisor 直接运行在物理硬件之上-KVM
Hypervisor 运行在另一个操作系统中 QEMU 和 WINE

半虚拟化技术:在完全虚拟化的基础上对操作系统进行了修改,增加了专门的API,这个API可以将操作系统发出的指令进行优化,使性能大大提高
在这里插入图片描述

架构类型分类

寄居架构:在操作系统之上安装和运行虚拟化程序,依赖于主机操作系统对设备的支持和物理资源的管理
优点:简单,便于实现
缺点:安装和运行应用程序依赖于主机操作系统对设备的支持,宿主操作系统崩了该虚拟程序也无法使用无用消耗(windows装上vmware,就算不打开也消耗内存)
举例:GSXServer,VMware Server,Workstation
裸金属架构:直接在硬件上面安装虚拟化软件,再在其上安装操作系统和应用,依赖虚拟层内核和服务器控制台进行管理。
优点:虚拟机不依赖于操作系统,可以支持多种操作系统,多种应用,更加灵活
缺点:虚拟层内核开发难度较大
举例:VMWare ESXI Server

Vmver ESXi
Vmver ESXi是专门构建的裸机hypervisor,ESXi直接安装在物理服务器上,并将其划分为多个逻辑服务器,既虚拟机

router:数据转发,可以 按照请求的类型、地址进行转发,
server根据请求的类型执行主机中,主机会生成一个引擎,生成很多个job,比如执行 docker run 就会生成docker run 的 job,docker执行的每一条命令都可以理解为一个job

容器与虚拟机

容器是一种沙盒(沙箱)技术。沙盒将软件运行于一个受限的系统环境中,控制程序可使用的资源(如文件描述符、内存、磁盘空间等)。顾名思义,沙盒就是能够像一个集装箱一样,把你的应用装起来。这样应用与应用之间就有了边界而不会相互干扰。同时装在沙盒里面的应用,也可以很方便的被搬来搬去,这也是 PaaS 想要的最理想的状态(可移植性,标准化,隔离性)。

容器技术是虚拟化、云计算、大数据之后的一门新兴的并且是炙手可热的新技术, 容器技术提高了硬件资源利用率、 方便了企业的业务快速横向扩容(可以达到秒级快速扩容)、 实现了业务宕机自愈功能(配合K8S可以实现,但OpenStack无此功能),因此未来数年会是一个容器愈发流行的时代 ,这是一个对于 IT 行业来说非常有影响和价值的技术,而对于IT行业的从业者来说, 熟练掌握容器技术无疑是一个很有前景的行业工作机会。在这里插入图片描述

传统虚拟机是虚拟出一个主机硬件,并且运行一个完整的操作系统,然后在这个系统上安装和运行软件。使用虚拟机是为了更好的实现服务运行环境隔离,每个虚拟机都有独立的内核,虚拟化可以实现不同操作系统的虚拟机,但是通常一个虚拟机只运行一个服务,很明显资源利用率比较低且造成不必要的性能损耗,我们创建虚拟机的目的是为了运行应用程序,比如Nginx、PHP、Tomcat等web程序,使用虚拟机无疑带来了一些不必要的资源开销,但是容器技术则基于减少中间运行环节带来较大的性能提升。

容器没有内核,容器启动和运行过程中直接使用了宿主机的内核,通过宿主机内核调用物理硬件。而镜像本身则只提供相应的roots,即系统正常运行所必须的用户空间的文件系统,比如/dev、/proc、/bin、/etc等目录,所以容器当中基本是没有/boot目录的,而/boot当中保存的就是与内核相关的文件和目录。对于宿主机而言,每个容器就是一个单独的进程,数据存储在磁盘中,通过调用内核进行访问。

根据实验,一个运行着CentOS的KVM虚拟机启动后,在不做优化的情况下,虚拟机自己就需要占用 100~200 MB内存(虚拟机一般会有5-20%的损耗)。此外,用户应用运行在虚拟机里面,它对宿主机操作系统的调用就不可避免地要经过虚拟化软件的拦截和处理,这本身又是一层性能损耗,尤其对计算资源、网络和磁盘I/O的损耗非常大。
比如: 一台96G内存的物理服务器,为了运行java程序的虚拟机一般需要分配8G内存/4核的资源,只能运行13台左右虚拟机,但是改为在docker容器上运行Java程序,每个容器只需要分配4G内存即可,同样的物理服务器就可以运行25个左右容器,运行数量相当于提高一倍,可以大幅节省IT支出,通常情况下至少可节约一半以上的物理设备
在这里插入图片描述
Docker 和虚拟机、物理主机
在这里插入图片描述

容器直接使用了宿主机的内核,而虚拟机运行的是一个完整的操作系统,独立的操作系统,独立的内核。所以容器隔离性没有虚拟机隔离性好啊

容器管理工具

有了 chroot、namespace、cgroups就具备了基础的容器运行环境,但是还需要有相应的容器创建与删除的管理工具、以及怎么样把容器运行起来、容器数据怎么处理、怎么进行启动与关闭等问题需要解决,于是容器管理技术出现了。早期使用 LXC,目前主要是使用docker。

一提到容器,好多人认为docker就是容器,其实docker只是一种容器工具,就好比一提到操作系统,我们能想到windows、linux、macos等。docker镜像运行后也叫做容器,这个容器是镜像运行后的结果。

LXC:Linux Container。可以提供轻量级的虚拟化功能,以便隔离进程和资源,包括一系列容器的管理工具软件。如lxc-create、lxc-start、lxc-attach等,但这技术功能不完善,目前较少使用

Docker:Docker 相当于增强版的LXC,功能更为强大和易用,也是当前最主流的容器前端管理工具
Docker 先启动一个容器也需要一个外部模板,也称为镜像,docke的镜像可以保存在一个公共的地方共享使用,只要把镜像下载下来就可以使用,最主要的是可以在镜像基础之上做自定义配置并且可以再把其提交为一个镜像,一个镜像可以被启动为多个容器。
Docker的镜像是分层的,镜像底层为库文件且只读层即不能写入也不能删除数据,从镜像加载启动为一个容器后会生成一个可写层,其写入的数据会复制到宿主机上对应容器的目录,但是容器内的数据在删除容器后也会被随之删除。

pouch(小袋子):在中国开源年会现场,阿里巴巴正式开源了基于 Apache 2.0 协议的容器技术 Pouch。Pouch 是一款轻量级的容器技术,拥有快速高效、可移植性高、资源占用少等特性,主要帮助阿里更快的做到内部业务的交付,同时提高超大规模下数据中心的物理资源利用率。目前的容器方案大多基于 Linux 内核提供的 cgroup 和 namespace 来实现隔离,然后这样轻量级方案存在的弊端:容器间,容器与宿主间,共享同一个内核。内核实现的隔离资源,维度不足。
面对如此的内核现状,阿里巴巴采取了三个方面的工作来解决容器的安全问题:用户态增强容器的隔离维度,比如网络带宽、磁盘使用量等。给内核提交 patch,修复容器的资源可见性问题,cgroup 方面的 bug。实现基于 Hypervisor 的容器,通过创建新内核来实现容器隔离

Podman:Podman即Pod Manager tool,从名称上可以看出和kubernets的pod的密切联系,不过就其功能来说,简而言之: alias docker = podman,是CentOS 8 新集成的功能,或许不久的未来会代替docker
Podman是一个 为 Kubernetes 而生的开源的容器管理工具,原来是 CRI-O(即容器运行时接口CRI 和开放容器计划OCI) 项目的一部分,后来被分离成一个单独的项目叫 libpod。其可在大多数Linux平台上使用,它是一种无守护程序的容器引擎,用于在Linux系统上开发,管理和运行任何符合Open Container Initiative(OCI)标准的容器和容器镜像。
Podman 提供了一个与Docker兼容的命令行前端,Podman 里面87%的指令都和Docker CLI 相同,因此可以简单地为Docker CLI别名,即“ alias docker = podman”,事实上,podman使用的一些库也是docker的一部分。

容器工具 Docker

Docker是一个基于go语言遵从apache2.0协议开源的LXC的高级容器引擎(底层技术是Linux Container,docker只是管理底层的工具),并通过namespace、cgroup等来提供容器的资源隔离与安全保障等。
Docker 最早采用 LXC 技术 (LXC 是 Linux 原生支持的容器技术,是一种内核虚拟化技术,可以提供轻量级的虚拟化,LXC将Linux进程沙盒化,使进程之间相互隔离 ),可以说docker 就是基于 LXC 发展起来的。在LXC的基础之上,docker提供了一系列更强大的功能。后来Docker 改为自己研发并开源的 runc 技术运行容器,彻底抛弃了LXC。而虚拟化技术 KVM(KernelKernelbasedVirtual Machine Machine) 基于模块实现。

docker容器不能跨主机访问,Docker 相比虚拟机的交付速度更快,资源消耗更低,Docker 采用客户端/服务端架构,使用远程API来
管理和创建容器,其可以轻松的创建一个轻量级的、可移植的、自给自足的容器,docker 的三大理念是build(构建)、ship(运输)、 run(运行),Docker遵从apache 2.0协议,并通过namespace及cgroup等来提供容器的资源隔离与安全保障等,所以Docke容器在运行时不需要类似虚拟机(空运行的虚拟机占用物理机6-8%性能)的额外资源开销,因此可以大幅提高资源利用率,总而言之Docker是一种用了新颖方式实现的轻量级虚拟机.类似于VM但是在原理和应用上和VM的差别还是很大的,并且docker的专业叫法是应用容器(Application Container)。

Docker 是一个可以将应用程序及其依赖打包到几乎可以在任何服务器上运行的容器的工具。将应用运行在容器中,容器在任何操作系统上都是一致的,使其运行环境一次封装,处处运行,解决了代码水土不服,实现了跨平台跨服务器。解决了软件跨环境迁移的问题。
在这里插入图片描述

在这里插入图片描述

docker的组成

docker组成说明
docker 主机一个物理机或虚拟机,用来运行docker服务进程和容器
docker 服务端docker的守护进程,主要用来运行docker容器
docker 客户端客户端使用docker命令或其它工具调用docker API 对容器进行操作
docker 仓库保存镜像的仓库,类似于git和svn
docker 镜像封装好的运行环境,镜像运行之后变成容器
docker 容器镜像运行完成以后得到的正在运行的运行环境

在这里插入图片描述

docker部署

docker部署

docker配置阿里云加速器,国内下载国外的镜像有时候会很慢,可以通过加速器达到加速下载镜像的目的

[root@localhost ~]# dockerd --help | grep json
--config-file string     Daemon configuration file (default "/etc/docker/daemon.json")

[root@localhost ~]#  sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://oinh00fc.mirror.aliyuncs.com"]
}
EOF

#{
#  "registry-mirrors": ["https://oinh00fc.mirror.aliyuncs.com"],
#  "graph": "/data/docker"		#修改存储路径,默认存储/var/lib/docker/
#}

[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl enable docker && systemctl restart docker
[root@localhost ~]# docker info
 Registry Mirrors:
  https://oinh00fc.mirror.aliyuncs.com/

二进制部署:
比如服务器不能访问外网,通过ansible批量部署二进制安装包
下载docker二进制包:https://download.docker.com/linux/static/stable/x86_64/

wget https://download.docker.com/linux/static/stable/x86_64/docker-20.10.17.tgz
tar -zxf docker-20.10.17.tgz 
[root@localhost ~]# ls docker
containerd  containerd-shim  containerd-shim-runc-v2  ctr  docker  dockerd  docker-init  docker-proxy  runc

[root@localhost ~]# cp docker/* /usr/bin/			#将可执行文件拷贝到/usr/bin



[root@localhost ~]# vim /lib/systemd/system/docker.service 
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target docker.socket firewalld.service containerd.service
Wants=network-online.target
Requires=docker.socket containerd.service

[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

StartLimitBurst=3

StartLimitInterval=60s

LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

TasksMax=infinity

Delegate=yes

KillMode=process
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target


[root@localhost ~]# vim /lib/systemd/system/docker.socket
[Unit]
Description=Docker Socket for the API

[Socket]
ListenStream=/var/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker

[Install]
WantedBy=sockets.target


[root@localhost ~]# vim /lib/systemd/system/containerd.service 
[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=/usr/bin/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5

LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity

TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target

dockerd 调用containerd,dockerd启动后会监听containerd sock文件,进行通信

[root@localhost ~]# more /lib/systemd/system/docker.service 
[Service]
Type=notify
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock


systemctl start containerd
systemctl enable containerd

docker启动时如果报了如下错误:
在这里插入图片描述

vim /usr/lib/systemd/system/docker.socket
SocketGroup=root		#将这里改为root
systemctl list-unit-files | grep docker
systemctl enable docker.service && systemctl enable docker.socket
systemctl daemon-reload
systemctl restart docker  

docker框架结构

通过docker version命令发现docker采用客户端跟服务端架构,docker的守护进程运行在服务器上。

[root@localhost ~]# docker version
Client: Docker Engine - Community
 Version:           20.10.1
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        831ebea
 Built:             Tue Dec 15 04:37:17 2020
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.1
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       f001486
  Built:            Tue Dec 15 04:35:42 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.3
  GitCommit:        269548fa27e0089a8b8278fc4fc781d7f65a939b
 runc:
  Version:          1.0.0-rc92
  GitCommit:        ff819c7e9184c13b7c2607fe6c30ae19403a7aff
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
[root@localhost ~]# docker run -itd --name busybox1 busybox
938056588e34ed79daf894d8f2e8538dc31364f5011627dd7f0d99a47bcf7a8e
[root@localhost ~]# docker run -itd --name busybox2 busybox
8efcfccfc5044b7658057754c44068e8cdde8ec887be4ff95a0050c3aee5a421
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS          PORTS                                                                      NAMES
8efcfccfc504   busybox               "sh"                     7 seconds ago    Up 6 seconds                                                                               busybox2
938056588e34   busybox               "sh"                     13 seconds ago   Up 12 seconds  


[root@localhost ~]# ps -ef | grep containerd
root      4871     1  0 8月12 ?       00:01:36 /usr/bin/containerd
root      5208     1  0 8月12 ?       00:01:35 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root     20401     1  0 18:40 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 938056588e34ed79daf894d8f2e8538dc31364f5011627dd7f0d99a47bcf7a8e -address /run/containerd/containerd.sock
root     20483     1  0 18:41 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 8efcfccfc5044b7658057754c44068e8cdde8ec887be4ff95a0050c3aee5a421 -address /run/containerd/containerd.sock

Docker 采用了 C/S 架构,包括客户端和服务端。Docker 守护进程 (Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)。
客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。

Docker 守护进程一般在宿主主机后台运行,等待接收来自客户端的消息。
Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互。

docker namespace

多个容器运行在一个宿主机上会面临以下问题:
1、怎么样保证每个容器都有不同的文件系统并且能互不影响?
2、一个docker主进程内的各个容器都是其子进程,那么实现同一个主进程下不同类型的子进程?各个进程间通信能相互访问(内存数据)吗?
3、每个容器怎么解决IP及端口分配的问题?
4、多个容器的主机名能一样吗?
5、每个容器都要不要有root用户?怎么解决账户重名问题?

docker 本质就是宿主机的一个进程。
docker使用namespace来隔离不同容器的运行环境(用户、进程、文件系统、ip和端口)。namespace是Linux系统的底层概念,在内核层实现。不同类型的命名空间被部署在核内,各个docker容器运行在同一个docker主进程并且共用同一个宿主机系统内核,各docker容器运行在宿主机的用户空间,每个容器都有类似于虚拟机一样的相互隔离的运行空间,但是容器技术是在一个进程内实现运行指定服务的运行环境,并且还可以保护宿主机内核不受其他进程的干扰和影响。如文件系统空间、网络空间、进程空间等。

Namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。

Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响。

Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)。
我们知道,传统的虚拟机通过在宿主主机中运行 hypervisor 来模拟一整套完整的硬件环境提供给虚拟机的操作系统。虚拟机系统看到的环境是可限制的,也是彼此隔离的。 这种直接的做法实现了对资源最完整的封装,但很多时候往往意味着系统资源的浪费。 例如,以宿主机和虚拟机系统都为 Linux 系统为例,虚拟机中运行的应用其实可以利用宿主机系统中的运行环境。
我们知道,在操作系统中,包括内核、文件系统、网络、PID、UID、IPC、内存、硬盘、CPU 等等,所有的资源都是应用进程直接共享的。 要想实现虚拟化,除了要实现对内存、CPU、网络IO、硬盘IO、存储空间等的限制外,还要实现文件系统、网络、PID、UID、IPC等等的相互隔离。 前者相对容易实现一些,后者则需要宿主机系统的深入支持。
随着 Linux 系统对于命名空间功能的完善实现,程序员已经可以实现上面的所有需求,让某些进程在彼此隔离的命名空间中运行。大家虽然都共用一个内核和某些运行时环境(例如一些系统命令和系统库),但是彼此却看不到,都以为系统中只有自己的存在。这种机制就是容器(Container),利用命名空间来做权限的隔离控制,利用 cgroups 来做资源分配。

隔离类型说明
uts主机名和域名隔离
ipc进程间通信的隔离(信号量、消息队列、共享内存)
pid进程隔离
net网络隔离
mnt磁盘挂载点和文件系统的隔离
user用户和用户组隔离(3.8以后的内核才支持)

/proc是一种伪文件系统(也即虚拟文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口),存储的是当前内核运行状态的一系列特殊文件,用户可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行。
进程号为目录名

比如启动一个nginx进程,再启动一个apache进程,会发现80端口冲突,解决冲突最好的办法就是隔离。比如新冠最好的解决办法一样,就是隔离。linux中通过 数字编号 将不同的进程进行隔离,相同的编号代表在同一空间。

[root@localhost ~]# ss -lntp | grep http
LISTEN     0      128       [::]:80                    [::]:*                   users:(("httpd",pid=28276,fd=4),("httpd",pid=28275,fd=4),("httpd",pid=28274,fd=4),("httpd",pid=28273,fd=4),("httpd",pid=28268,fd=4),("httpd",pid=28267,fd=4),("httpd",pid=27978,fd=4),("httpd",pid=27977,fd=4),("httpd",pid=27975,fd=4),("httpd",pid=27974,fd=4),("httpd",pid=27973,fd=4))
[root@localhost ~]# ll /proc/27974/ns		#ns为namespace缩写
total 0
lrwxrwxrwx 1 root root 0 May  4 15:19 ipc -> ipc:[4026531839]		#
lrwxrwxrwx 1 root root 0 May  4 15:19 mnt -> mnt:[4026532151]
lrwxrwxrwx 1 root root 0 May  4 15:19 net -> net:[4026531956]
lrwxrwxrwx 1 root root 0 May  4 15:19 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0 May  4 15:19 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 May  4 15:19 uts -> uts:[4026531838]

[root@localhost ~]#  ll /proc/2/ns		#随机查看一个pid为2的命名空间
total 0
lrwxrwxrwx 1 root root 0 May  4 15:22 ipc -> ipc:[4026531839]		#ipc相同
lrwxrwxrwx 1 root root 0 May  4 15:22 mnt -> mnt:[4026531840]		#mnt不相同,代表没有冲突
lrwxrwxrwx 1 root root 0 May  4 15:22 net -> net:[4026531956]		#net相同
lrwxrwxrwx 1 root root 0 May  4 15:22 pid -> pid:[4026531836]		#pid相同
lrwxrwxrwx 1 root root 0 May  4 15:22 user -> user:[4026531837]		#user相同
lrwxrwxrwx 1 root root 0 May  4 15:22 uts -> uts:[4026531838]		#uts相同

命名空间后面对应的数字是相等的代表在同一个空间。可以发现ipc、net、pid、user、uts后面的数值是相等的,说明在同一命名空间下

一号进程一定是一个特殊权限的进程,负责开启所有的进程
[root@localhost ~]# ps -ef | head -2
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 Apr17 ?        00:07:39 /usr/lib/systemd/systemd --switched-root --system --deserialize 22

IPC Namespace

进程间通信的隔离:一个容器内的进程间通信,允许一个容器内的不同进程的(内存、缓存等)数据访问,但是不能跨容器访问其他容器的数据。

root@36fc7a3fdb6d:/# ps -ef | grep nginx | grep -v grep			#每个容器内都会有一个PID为1的主进程。
root         1     0  0 01:30 pts/0    00:00:00 nginx: master process nginx -g daemon off;
nginx       31     1  0 01:30 pts/0    00:00:00 nginx: worker process

在这里插入图片描述

MNT Namespace

mnt技术提供磁盘挂载点和文件系统的隔离能力
每个容器都要有独立的根文件系统有独立的用户空间,以实现在容器里面启动服务并且使用容器的运行环境。即一个宿主机是ubuntu的服务器,可以在里面启动一个centos运行环境的容器,并且在容器里面启动一个Nginx服务,此Nginx运行时使用的运行环境就是centos系统目录的运行环境,但是在容器里面是不能访问宿主机的资源,宿主机使用chroot技术把容器锁定到一个指定的运行目录里面,容器内产生的数据也会存在相应的目录。

[root@localhost ~]# docker pull nginx
[root@localhost ~]# docker pull tomcat
[root@localhost ~]# docker run -it -d --name nginx -p 80:80 nginx
[root@localhost ~]# docker exec -it nginx bash
root@36fc7a3fdb6d:/# cat /etc/issue		#debian系统
Debian GNU/Linux 11 \n \l

root@36fc7a3fdb6d:/# apt update
root@36fc7a3fdb6d:/# apt install procps				#安装 top 命令
root@36fc7a3fdb6d:/# apt install iputils-ping  		#安装 ping 命令
root@36fc7a3fdb6d:/# apt install net-tools	 		#安装网络工具

docker run -it -d -p 8080:8080 nginx
docker exec -it 镜像id bash
cd webapps
mkdir test
echo 'hello docker tomcat' > index.html

ll /var/lib/docker/co	/
各容器在宿主机对应的路径下


从新进入一个新的容器,发现并没有之前创建的文件,

UTS Namespace

包含了运行内核的名称、版本、底层体系结构类型等信息,用于系统标识,其中包含了hostname和域名domainname,它使得一个容器拥有属于自己hostname标识,这个主机名标识独立于宿主机系统和其上的其他容器。

root@36fc7a3fdb6d:/# cat /etc/hostname 		#查看容器内主机名
36fc7a3fdb6d
root@36fc7a3fdb6d:/# uname -a				#容器里用的是宿主机的内核
Linux 36fc7a3fdb6d 3.10.0-1160.45.1.el7.x86_64 #1 SMP Wed Oct 13 17:20:51 UTC 2021 x86_64 GNU/Linux

[root@localhost ~]# uname -a		#宿主机的内核
Linux localhost 3.10.0-1160.45.1.el7.x86_64 #1 SMP Wed Oct 13 17:20:51 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

PID Namespace

Linux系统中有一个PID为1的进程(init/systemd)是其他所有进程的父进程,那么在每个容器内也要有一个父进程来管理其下属的子进程,那么多个容器的进程通PID namespace进程隔离(比如PID编号重复、容器内的主进程生成与回收子进程等)。
因为容器内没有内核所以也没有init或systemd,所以需要手动自定义一个进程,pid为1。在容器中第一个被拉起的进程并且是可以长期运行的进程,那么这个进程的pid就为1,作为容器的守护进程(类似于init或systemd)

root@36fc7a3fdb6d:/# ps -ef | grep nginx | grep -v grep			#每个容器内都会有一个PID为1的主进程。
root         1     0  0 01:30 pts/0    00:00:00 nginx: master process nginx -g daemon off;
nginx       31     1  0 01:30 pts/0    00:00:00 nginx: worker process

[root@localhost ~]# pstree -p 1
systemd(1)───containerd(9962)───containerd-shim(778)───nginx(797)───nginx(847)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述

containerd进程

containerd用于启动多个容器

dockerd被client直接访问,其父进程为宿主机的systemd守护进程
docker-proxy实现容器通信,其父进程为dockerd
containerd被dockerd进程调用以实现与runc交互
containerd-shim真正运行容器的载体,其父进程为containerd
[root@localhost ~]# ps -ef |grep containerd
root      2156     1  0 Oct19 ?        00:00:12 /usr/bin/containerd
root      2168     1  0 Oct19 ?        00:00:31 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root      8524     1  0 10:46 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 0ebf9461cd091e29081ead2a06a53c909519da77a839382b0d2625f2aa0e9a81 -address /run/containerd/containerd.sock

[root@localhost ~]# ps -ef | grep docker-proxy
root      8507  2168  0 10:46 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.18.0.3 -container-port 80
root      8511  2168  0 10:46 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8080 -container-ip 172.18.0.3 -container-port 80
root     10743 32656  0 18:52 pts/0    00:00:00 grep --color=auto docker-proxy
root     21915  2168  0 08:34 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.2 -container-port 80
root     21919  2168  0 08:34 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.18.0.2 -container-port 80
[root@localhost ~]# cat /lib/systemd/system/docker.service | grep ExecStart
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
通过docker命令调用 dockerd ,dockerd 通过这个 sock 文件调用 containerd 来创建容器。dockerd还会生成 docker-proxy,docker-proxy调用内核生成IPtables规则,当用户通过ip:port访问服务器会通过内核检查iptables规则进行转发

在这里插入图片描述

[root@localhost ~]# iptables -t nat -vnL		#访问宿主机80会被iptables转发到容器的80
14   704 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.18.0.4:80

[root@localhost ~]# pstree -p 1
systemd(1)──dockerd(9971)──docker-proxy(761)

在这里插入图片描述

NET Namespace
每一个容器都类似于虚拟机一样有自己的网卡、监听端口、TCP/IP协议栈等,Docker使用network namespace启动一个vethX接口,这样你的容器将拥有它自己的桥接ip地址,通常是docker0,而docker0实质就是Linux的虚拟网桥,网桥是在OSI七层模型的数据链路层的网络设备,通过mac地址对网络进行划分,并且在不同网络直接传递数据。
iptables -t nat -vnl #通过iptables目标地址转换,
容器内执行apt install net-tools
curl ip

没启动以个容器,宿主机就会vethxxxx

在这里插入图片描述

USER Namespace
各个容器内可能会出现重名的用户名和用户组名,或重复的用户UID或者GID。User Namespace允许在各个宿主机的各个容器空间内创建相同的用户名以及相同的用户UID和GID。只是会把用户的作用范围限制在每个容器内,即A容器和B容器可以有相同的用户名称和ID的账户,但是此用户的有效范围仅是当前容器内,不能访问另外一个容器内的文件系统,即相互隔离、互补影响、永不相见。

运行两个不同的容器
[root@localhost ~]# docker run -itd --name busybox1 busybox
[root@localhost ~]# docker run -itd --name busybox2 busybox
[root@localhost ~]# docker exec -it busybox1 sh		#进入 busybox1 容器
/ # id		#每个容器内都有超级管理员root及其它系统账户,并且账户ID和其它容器相同
uid=0(root) gid=0(root) groups=10(wheel)
/ # echo 'hello docker' > hellodocker.txt
/ # cat hellodocker.txt 
hello docker
/ # exit
[root@localhost ~]# docker exec -it busybox2 sh
/ # id		#每个容器内都有超级管理员root及其它系统账户,并且账户ID和其它容器相同
uid=0(root) gid=0(root) groups=10(wheel)
/ # ls		#在 busybox1 容器创建的文件在 busybox2 中无法看到
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
[root@localhost ~]# find / -name hellodocker.txt		#容器中的数据会保存到宿主机内
/var/lib/docker/overlay2/65ded0a1b6f3195a7aab473505f9edffc318e81712b8d503e3269c709faeb3b8/diff/hellodocker.txt
/var/lib/docker/overlay2/65ded0a1b6f3195a7aab473505f9edffc318e81712b8d503e3269c709faeb3b8/merged/hellodocker.txt
[root@localhost ~]# cat /var/lib/docker/overlay2/65ded0a1b6f3195a7aab473505f9edffc318e81712b8d503e3269c709faeb3b8/diff/hellodocker.txt
hello docker

cgroup

控制组(cgroups)是 Linux 内核的一个特性,主要用来对共享资源进行隔离、限制、审计等。只有能控制分配到容器的资源,才能避免当多个容器同时运行时的对系统资源的竞争。控制组可以提供对容器的内存、CPU、磁盘 IO 等资源的限制和审计管理。
在这里插入图片描述
在这里插入图片描述

Linux Cgroups的全称是Linux Control Groups,它最主要的作用就是限制一个进程组能够使用的资源上限,包括CPU、内存、磁盘、网络带宽等。此外还能够对进程进行优先级设置,以及将进程挂起和恢复等操作。如果容器不对其做任何资源限制,则宿主机会允许其占用无限大的内存空间,有时候程序会因为代码bug一直申请内存,直到把宿主机内存占完,为了避免此类的问题出现,宿主机有必要对容器进行资源分配限制,比如CPU、内存等。

验证系统 cgroups
Cgroups在内核层默认已经开启,从CentOS 和 Ubuntu 不同版本对比,显然内核较新的支持的功能更多。

#cgroup中内存模块,y代表开启
[root@localhost ~]# cat /boot/config-3.10.0-1160.11.1.el7.x86_64 | grep CGROUP
CONFIG_CGROUPS=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_HUGETLB=y
CONFIG_CGROUP_PERF=y
CONFIG_CGROUP_SCHED=y
CONFIG_BLK_CGROUP=y
# CONFIG_DEBUG_BLK_CGROUP is not set
CONFIG_NETFILTER_XT_MATCH_CGROUP=m
CONFIG_NET_CLS_CGROUP=y
CONFIG_NETPRIO_CGROUP=y

[root@localhost ~]# cat /boot/config-3.10.0-1160.11.1.el7.x86_64 | grep MEM | grep CG
CONFIG_MEMCG=y				#限制内存
CONFIG_MEMCG_SWAP=y			#限制交换分区
CONFIG_MEMCG_SWAP_ENABLED=y
CONFIG_MEMCG_KMEM=y

cgroups 具体实现

[root@localhost ~]# ll /sys/fs/cgroup/			#查看系统 cgroups
total 0
drwxr-xr-x 5 root root  0 Nov 18 07:52 blkio						#块设备IO限制
lrwxrwxrwx 1 root root 11 Nov 18 07:52 cpu -> cpu,cpuacct			#使用调度程序为 cgroup 任务提供 cpu 的访问
lrwxrwxrwx 1 root root 11 Nov 18 07:52 cpuacct -> cpu,cpuacct		#产生 cgroup 任务的 cpu 资源报告
drwxr-xr-x 6 root root  0 Nov 18 07:52 cpu,cpuacct					
drwxr-xr-x 3 root root  0 Nov 18 07:52 cpuset						#如果是多核心的cpu,这个子系统会为 cgroup 任务分配单独的cpu和内存
drwxr-xr-x 5 root root  0 Nov 18 07:52 devices						#允许或拒绝 cgroup 任务对设备的访问
drwxr-xr-x 3 root root  0 Nov 18 07:52 freezer						#暂停和恢复cgroup任务
drwxr-xr-x 3 root root  0 Nov 18 07:52 hugetlb
drwxr-xr-x 6 root root  0 Nov 18 07:52 memory						#设置每个 cgroup 的内存限制以及产生内存资源报告
lrwxrwxrwx 1 root root 16 Nov 18 07:52 net_cls -> net_cls,net_prio	#标记每个网络包以供 cgroup 方便使用
drwxr-xr-x 3 root root  0 Nov 18 07:52 net_cls,net_prio
lrwxrwxrwx 1 root root 16 Nov 18 07:52 net_prio -> net_cls,net_prio
drwxr-xr-x 3 root root  0 Nov 18 07:52 perf_event					#增加了对 cgroup 的监测跟踪的能力,可以监测属于某个特定的 cgroup 的所有线程以及运行在特定CPU上的线程
drwxr-xr-x 5 root root  0 Nov 18 07:52 pids
drwxr-xr-x 5 root root  0 Nov 18 07:52 systemd

容器规范

容器技术除了的docker之外,还有coreOS的rkt、阿里的pouch,为了保证容器生态的标准性和健康可持续发展,Linux基金会、Docker、微软、红帽、谷歌、IBM等公司在2015年6月共同成立了一个叫 open container (OCI)的组织,其目的就是制定开放标准的容器规范,目前OCI一共发布了两个规范:分别是 runtime spec 和 image format spec。不同的容器公司开发的容器只要兼容这两个规范,就可以保证容器的可移植性和相互可操作性。

runtime

runtime 是真正容器运行的地方,因此为了运行不同的容器runtime需要和操作系统内核紧密合作、相互支持,以便为容器提供相应的运行环境。
早期的kubernetes runtime架构,远没这么复杂,kubelet创建容器,直接调用docker daemon,docker daemon自己调用libcontainer就把容器运行起来。国际大厂们认为运行时标准不能被 Docker 一家公司控制,于是就串通搞了开放容器标准 OCI。忽悠Docker 把 libcontainer 封装了一下,变成 runC 捐献出来作为 OCI 的参考实现。

目前主流的三种runtime:
LXC:linux上早期的runtime,Docker早期就是采用lxc作为runtime
runc:目前Docker默认的runtime,runc遵守OCI规范,因此可以兼容LXC
rkt:是Coreos开发的容器runtime,也符合OCI规范,所以使用rktruntime也可以运行Docker容器

查看docker的 runtime

[root@localhost ~]# docker info
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc

runtime主要定义了以下规范,并以json格式保存在/run/docker/runtime-runc/moby/容器ID/state.json,此文件会根据容器状态实时更新

管理工具连接runtime与用户,为用户提供图形或命令方式操作,然后管理工具将用户操作传递给runtime执行。

Runc的管理工具是docker engine,docker engine包含后台deamon和cli两部分,大家经常提到的Docker就是指的docker engine。
lxc是lxd的管理工具
rkt的管理工具是rkt cli

从 Docker 1.11 版本开始,Docker 容器运行就不是简单通过 Docker Daemon 来启动了,而是通过集成 containerd、runc 等多个组件来完成的。虽然 Docker Daemon 守护进程模块在不停的重构,但是基本功能和定位没有太大的变化,一直都是 CS 架构,守护进程负责和 Docker Client 端交互,并管理 Docker 镜像和容器。现在的架构中组件 containerd 就会负责集群节点上容器的生命周期管理,并向上为 Docker Daemon 提供 gRPC 接口。

当我们要创建一个容器的时候,现在 Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 来创建一个容器,containerd 收到请求后,也并不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程,让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了。
在这里插入图片描述
在这里插入图片描述

containerd进程

containerd用于启动多个容器

dockerd被client直接访问,其父进程为宿主机的systemd守护进程
docker-proxy实现容器通信,其父进程为dockerd
containerd被dockerd进程调用以实现与runc交互
containerd-shim真正运行容器的载体,其父进程为containerd
[root@localhost ~]# ps -ef |grep containerd
root      2156     1  0 Oct19 ?        00:00:12 /usr/bin/containerd
root      2168     1  0 Oct19 ?        00:00:31 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root      8524     1  0 10:46 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 0ebf9461cd091e29081ead2a06a53c909519da77a839382b0d2625f2aa0e9a81 -address /run/containerd/containerd.sock

[root@localhost ~]# ps -ef | grep docker-proxy
root      8507  2168  0 10:46 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.18.0.3 -container-port 80
root      8511  2168  0 10:46 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8080 -container-ip 172.18.0.3 -container-port 80
root     10743 32656  0 18:52 pts/0    00:00:00 grep --color=auto docker-proxy
root     21915  2168  0 08:34 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.18.0.2 -container-port 80
root     21919  2168  0 08:34 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 80 -container-ip 172.18.0.2 -container-port 80
[root@localhost ~]# cat /lib/systemd/system/docker.service | grep ExecStart
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
通过docker命令调用 dockerd ,dockerd 通过这个 sock 文件调用 containerd 来创建容器。dockerd还会生成 docker-proxy,docker-proxy调用内核生成IPtables规则,当用户通过ip:port访问服务器会通过内核检查iptables规则进行转发
版本信息存放OCI标准的具体版本号
容器ID通常是一个哈希值,可以在所有 state.json 文件中提取出容器ID对容器进行批量操作(关闭、删除等),此文件在容器启动后会自动生成,容器关闭后会被删除
PID在容器中运行的首个进程在宿主机上的进程号,即将宿主机的那个进程设置为容器的守护进程
容器文件目录存放容器 rootfs 及相应配置的目录,外部程序只需读取 state.json 就可以定位到宿主机上的容器文件目录
容器创建创建包括文件系统、namespaces、cgroups、用户权限在内的各项内容
容器启动运行容器启动进程,该文件在/run/containerd/io.containerd.runtime.v1.linux/moby/容器ID/config.jsono
容器生命周期        容器进程可以被外部程序关停,runtime规范定义了对容器操作信号的捕获,如果容器正常退出,docker会把当前容器当中的数据全部清理,并做相应资源回收的处理,避免避免出现垃圾资源、僵尸进程

containerd 从 Docker Engine 里分离出来,作为一个独立的开源项目,目标是提供一个更加开放、稳定的容器运行基础设施。
containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性,containerd 可以负责干下面这些事情:
管理容器的生命周期(从创建容器到销毁容器)
拉取/推送容器镜像
存储管理(管理镜像及容器数据的存储)
调用 runc 运行容器(与 runc 等容器运行时交互)
管理容器网络接口及网络

将容器运行时从 Docker 切换到 containerd

containerd 同样也提供一个对应的 CLI 工具:ctr,不过 ctr 的功能没有 docker 完善,但是关于镜像和容器的基本功能都是有的。直接输入 ctr 命令即可获得所有相关的操作命令使用方式

拉取镜像
拉取镜像可以使用 ctr image pull 来完成,比如拉取 Docker Hub 官方镜像 nginx:alpine,需要注意的是镜像地址需要加上 docker.io Host 地址:

[root@localhost ~]# ctr image pull docker.io/library/nginx:alpine
docker.io/library/nginx:alpine:                                                   resolved       |++++++++++++++++++++++++++++++++++++++| 
index-sha256:082f8c10bd47b6acc8ef15ae61ae45dd8fde0e9f389a8b5cb23c37408642bf5d:    done           |++++++++++++++++++++++++++++++++++++++| 
manifest-sha256:2959a35e1b1e61e2419c01e0e457f75497e02d039360a658b66ff2d4caab19c4: done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:b231d02e51502ce72ff0813057a12b7778148c2e629aa21fe30d1cb1d0d5671b:    done           |++++++++++++++++++++++++++++++++++++++| 
config-sha256:804f9cebfdc58964d6b25527e53802a3527a9ee880e082dc5b19a3d5466c43b7:   done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:213ec9aee27d8be045c6a92b7eac22c9a64b44558193775a1a7f626352392b49:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:2546ae67167b3580b7dcc4a4d56e504594bac17e22e046b2d215fc2d021d6d7e:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:23b845224e138d383175fc5fb15d93ad0795468b8d6210edf3a50f23ded3ed8b:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:9bd5732789a330a86ed2257e6bf18928c6ae873107ef0a408f1ad65b42f6d14f:    done           |++++++++++++++++++++++++++++++++++++++| 
layer-sha256:328309e59ded59f3fc9eb5ade5c0135ec9aae5553ab837ed763fc1510799bae8:    done           |++++++++++++++++++++++++++++++++++++++| 
elapsed: 29.7s                                                                    total:  9.8 Mi (336.2 KiB/s)                                     
unpacking linux/amd64 sha256:082f8c10bd47b6acc8ef15ae61ae45dd8fde0e9f389a8b5cb23c37408642bf5d...
done: 448.216498ms	

列出本地镜像

[root@localhost ~]# ctr image ls
REF                            TYPE                                                      DIGEST                                                                  SIZE    PLATFORMS                                                                                LABELS 
docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:082f8c10bd47b6acc8ef15ae61ae45dd8fde0e9f389a8b5cb23c37408642bf5d 9.8 MiB linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/s390x -      
[root@localhost ~]# ctr image ls -q			#使用 -q(--quiet) 选项可以只打印镜像名称
docker.io/library/nginx:alpine

检测本地镜像

主要查看其中的 STATUS,complete 表示镜像是完整可用的状态。

[root@localhost ~]# ctr image check
REF                            TYPE                                                      DIGEST                                                                  STATUS         SIZE            UNPACKED 
docker.io/library/nginx:alpine application/vnd.docker.distribution.manifest.list.v2+json sha256:082f8c10bd47b6acc8ef15ae61ae45dd8fde0e9f389a8b5cb23c37408642bf5d complete (7/7) 9.7 MiB/9.7 MiB true

重新给指定的镜像打一个 Tag

[root@localhost ~]# ctr image tag docker.io/library/nginx:alpine harbor.io/test/nginx:alpine
harbor.io/test/nginx:alpine
[root@localhost ~]# ctr image ls -q
docker.io/library/nginx:alpine
harbor.io/test/nginx:alpine

删除镜像,加上 --sync 选项可以同步删除镜像和所有相关的资源。

[root@localhost ~]# ctr image rm harbor.io/test/nginx:alpine
harbor.io/test/nginx:alpine
[root@localhost ~]# ctr image ls -q
docker.io/library/nginx:alpine

容器操作
容器相关操作可以通过 ctr container 获取
创建容器

[root@localhost ~]# ctr container create docker.io/library/nginx:alpine nginx

查看容器


[root@localhost ~]# ctr container ls
CONTAINER    IMAGE                             RUNTIME                  
nginx        docker.io/library/nginx:alpine    io.containerd.runc.v2 
[root@localhost ~]# ctr container ls -q			#-q 选项精简列表内容
nginx

查看容器详细配置

类似于 docker inspect 功能

[root@localhost ~]# ctr container info nginx
{
    "ID": "nginx",
    "Labels": {
        "io.containerd.image.config.stop-signal": "SIGQUIT",
        "maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"
    },
    "Image": "docker.io/library/nginx:alpine",
    "Runtime": {
        "Name": "io.containerd.runc.v2",
        "Options": {
            "type_url": "containerd.runc.v1.Options"
        }
    },
    "SnapshotKey": "nginx",
    "Snapshotter": "overlayfs",
......

删除容器

[root@localhost ~]# ctr container rm nginx				#也可以使用 delete 或者 del 删除容器
[root@localhost ~]# ctr container ls
CONTAINER    IMAGE    RUNTIME 
在这里插入代码片
在这里插入代码片
在这里插入代码片
在这里插入代码片

容器镜像

文件系统  定义以 layer 保存的文件系统,在镜像里面是 layer.tar,每个 layer 保存了和上层之间变化的部分,image format spec定义了 layer 应该保存哪些文件,增加、修改和删除的文件等操作
manifest描述有哪些layer、tag标签及config文件名称
config以hash命名的json文件,保存了镜像平台、容器运行时需要的一些信息,比如环境变量、工作目录、命令参数等
index可选的文件,指向不同平台的 manifest 文件,这个文件能保证一个镜像可以跨平台使用。每个平台拥有不同的 manifest 文件使用 index 作为索引
父镜像大多数层的元信息结构都包含一个 parent 字段,指向该镜像的父镜像
[root@localhost ~]# docker pull nginx
[root@localhost ~]# docker images
REPOSITORY                  TAG       IMAGE ID       CREATED       SIZE
nginx                       latest    ea335eea17ab   3 days ago    141MB
[root@localhost ~]# docker save ea335eea17ab > nginx/nginx_image.tgz
[root@localhost ~]# cd nginx/
[root@localhost nginx]# tar xf nginx_image.tgz
[root@localhost nginx]# ls		#每一个目录代表每一层,记录着相应的内容。目录以镜像ID命名。
0b559ad9d88e1aaf50f87c77d49d18cebe6da93fe87d13b5c057545a272267d7
38f1bd2b083f130a6d717f1b1b523d2b124a19cb558104107b1854f1aeade840
5e4bc520016371648437e1831bf062a37a085c15f3207bfb7729af21f32a39b2
7725c8ca82f41dbcbeb31aba4a04e45996ad499f198ebced7ab853557ffc232a
8c2a29dfd83cc3420ecb475b76ce332185027edaa919ccf1069e67653b4ff760
a9592c42c1c981c6ddd9d474f64620044e281375a6e1b4ebb1d6713525fff610
ea335eea17ab984571cd4a3bcf90a0413773b559c75ef4cda07d0ce952b00291.json
manifest.json

在这里插入图片描述

容器的管理工具

[root@localhost ~]# ps -ef |grep docker
root      2168     1  0 Oct19 ?        00:00:25 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

容器定义工具允许用户定义容器的属性和内容,方便容器能够被保存、共享和重建。

定义工具说明
docker imagedocker容器的模板,runtime根据docker image创建容器
dockerfile多个命令的文本文件,通过dockerfile创建出docker image
registry镜像仓库,用于保存镜像
image registry:docker官方提供的私有仓库部署工具
docker hub:docker官方的公共仓库
harbor:vmware提供的自带web界面自带认证功能的镜像仓库

当多个容器在多个主机运行的时候,单独管理容器是相当复杂而且很容易出错,而且也无法实现某一台主机宕机后容器自动迁移到其他主机从而实现高可用的目的,也无法实现动态伸缩的功能,因此需要有一种工具可以实现统一管理、动态伸缩、故障自愈、批量执行等功能。这就是容器编排引擎。
容器编排通常包括容器管理、调度、集群定义和服务发现等功能。
Docker swarm: docker开发的容器编排引擎。
Kubernetes: google领导开发的容器编排引擎,内部项目为Borg,且其同时支持docker和CoreOS
Mesos + Marathon:通用的集群组员调度平台,mesos(资源分配)与marathon(容器编排平台)一起提供容器编排引擎功能。

     
容器网络docker自带的网络docker network仅支持管理单机上的容器网络,当多主机运行的时候需要使用第三方开源网络。例如calico、flannel等
服务发现容器的动态扩容特性决定了容器IP也会随之变化,因此需要有一种机制可以自动识别并将用户请求动态转发到新创建的容器上,kubernetes自带服务发现功能,需要结合kube-dns服务解析内部域名
容器监控可以通过原生命令docker ps/top/stats 查看容器运行状态,另外也可以使heapster/ Prometheus等第三方监控工具监控容器的运行状态
数据管理容器的动态迁移会导致其在不同的Host之间迁移,因此如何保证与容器相关的数据也能随之迁移或随时访问,可以使用逻辑卷/存储挂载等方式解决
日志收集docker原生的日志查看工具docker logs,但是容器内部的日志需要通过ELK等专门的日志收集分析和展示工具进行处理

docker状态
在这里插入图片描述

镜像(image)

镜像是一个只读模板,封装好的运行环境,镜像运行之后变成容器,也就是把镜像加载得到容器,容器就是正在运行的运行环境。比如在docker仓库下载nginx镜像,然后运行nginx镜像,就得到nginx容器

镜像的分层:

存储驱动类型
UnionFS
overlay一种UnionFS文件系统,Linux内核3.18后支持
overlay2overlay的升级版,目前所有Linux发行版推荐使用的存储类型

在这里插入图片描述

Docker镜像加载原理:
一个典型的 Linux文件系统由 bootfs 和 rootfs 两部分组成
bootfs(boot file system):用于系统引导的文件系统,主要包含 bootloader 和 kernel,bootloader主要是引导加载 kernel,Linux刚启动时会加载 bootfs 文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。容器启动完成后会被卸载以节省内存资源

rootfs (root file system) :位于bootfs之上,为 Docker 容器的跟文件系统。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

传统模式中,系统启动时,内核挂载 rootfs 时会首先将其挂载为“只读”模式,完整性自检完成后将其挂载为读写模式(先运行bootfs,目的是识别上层文件系统加载内核,然后引入rootfs,rootfs才会有系统内核的一些指令,有了指令才能对操作系统完成质检,质检完后才把它由只读改为读写,只读改为读写后操作系统才能正常工作)
Docker中,rootfs由内核挂载为“只读”模式(rootfs从头到尾都是只读模式),而后通过UFS技术挂载一个“可写”层进行写入操作

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

对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。

Docker的镜像实际上由一层一层的文件系统组成,通过联合文件系统 ( union filesystem )将各层文件系统叠加在一起,对外表现为一个整体。无论底下有多少层都是只读的,只有最上层(可写层)是文件系统是可写的。当修改一个文件时,AUFS创建该文件的一个副本,使用写时复制将文件从只读层复制到可写层进行修改,结果也保存在可写层。在Docker中底下的只读层就是镜像,可写层就是容器。Docker镜像含有启动容器所需要的文件系统及所需要的内容,因此镜像主要用于创建并启动docker容器。

UnionFS文件系统:一种分层、轻量级并且高性能的文件系统。联合文件系统可以将多个目录挂载到一起从而形成一整个虚拟文件系统,该虚拟文件系统的目录结构就像普通linux的目录结构一样,docker通过这些文件再加上宿主机的内核提供了一个linux的虚拟环境,每一层文件系统我们叫做一层layer,联合文件系统可以对每一层文件系统设置三种权限:只读(readonly)、读写(readwrite)和写出(whiteout-able)。但是docker镜像中每一层文件系统都是只读的,构建镜像的时候从一个最基本的操作系统开始,每个构建的操作都相当于做一层的修改,增加了一层文件系统,一层层往上叠加,上层的修改会覆盖底层该位置的可见性,这也很容易理解,就像上层把底层遮住了一样,当使用镜像的时候,我们只会看到一个完全的整体,不知道里面有几层也不需要知道里面有几层,结构如下:
在这里插入图片描述

Docker镜像是分层设计的,由多个只读层(通过联合文件系统)叠加而成,比如下图把三层拼接在一起就是一个完整的镜像。镜像运行后变为容器,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。用户写入的数据都保存在这一层中。

如果运行中的容器修改了现有的一个已经存在的文件,那么该文件将会从读写层下面的的只读层复制到读写层,然后对可写层文件进行修改,并且修改完的文件会把底层的文件给隐藏。该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏,此即“写时复制”机制
如果将正在运行中的容器修改生成了新的数据,那么新产生的数据将会被复制到读写层,进行持久化保存,这个读写层也就是容器的工作目录,也为写时复制(COW) 机制。

比如一个文件在0层显示A,在1层显示D
在0层显示A,1层没有修改,2层显示D
在这里插入图片描述
写时复制机制可以节约空间,容器关闭重启数据不受影响。但是删除容器,其对应的可写层也会随之而删除,即数据丢失。如果容器需要持久保存数据,可以使用数据卷技术实现
如下图是将对根的数据写入到了容器的可写层,但是把/data 中的数据写入到了一个另外的volume 中用于数据持久化
在这里插入图片描述

容器分层

[root@localhost ~]# mkdir /data/a /data/b /data/system
mount -t aufs -o dirs=/data/a:/data/b none /data/system

在这里插入图片描述
写在虚拟文件系统的数据会写到真实的目录中去
在这里插入图片描述
在这里插入图片描述
把相同的目录挂载后,显示的是两个目录共同的结果。A目录挂载到了C上,B目录也挂载了C上,那么在C上可以看到A和B共同的内容

[root@localhost ~]# mount -t aufs -o dirs=/opt/bin/:/opt/sbin none /opt/system

一个典型的Linux文件系统由bootfs和rootfs两部分组成,bootfs(boot filesystem)主要包含bootloader和kernel,bootloader主要用于引导加载kernel,当kernel被加载到内存中后bootfs会被umount掉,rootfs (root file system)包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件。所以容器起来之后只有rootfs,没有内核。下图就是docker image中最基础的两层结构,不同的linux发行版(如ubuntu和CentOS)在Foots这一层会有所区别。
但是对于docker镜像通常都比较小,docker镜像直接调用宿主机的内核,镜像中只提供rootfs,也就是只包括最基本的命令、工具和程序库。比如alpine镜像在5M左右。
下图就是有两个不同的镜像在一个宿主机内核上实现不同的rootfs
在这里插入图片描述

[root@localhost ~]# docker pull alpine
[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
alpine       latest    14119a10abf4   7 weeks ago   5.6MB

比如5台服务器需要下载同一个镜像,镜像仓库就需要对5台不同的宿主机进行传输

一个容器运行起来之后会将底层的镜像通过aufs联合挂载成为一个虚拟文件系统供容器使用,

官方提供的基础镜像层
在基础镜像安装新的镜像,如图,Apache的父镜像为emacs,emacs的父镜像为Debian
在这里插入图片描述

每一行命令代表镜像的一层
FROM scratch
COPY hello/
CMD ["/hello"]

base 镜像:不依赖于其它镜像从scratch开始构建;能给其它镜像作为基础镜像,比如centos、ubuntu

查看镜像的层数
[root@localhost ~]# docker history centos:latest 
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
5d0da3dc9764   6 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      6 weeks ago   /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B        
<missing>      6 weeks ago   /bin/sh -c #(nop) ADD file:805cb5e15fb6e0bb0…   231MB

多个容器共享基础镜像,当某个容器修改了基础镜像中的/etc下的内容那么其它容器是不是也被修改了呢?因为容器在可写层修改,所用镜像层都是只读层,不会被容器修改,所以镜像可以被多个容器共享

数据只能往外读,数据修改后是不能写回去的,保证镜像内的数据是永远不变的,容器层的数据是可变的,
当启动一个容器的时候一个新的可写层(容器层)会被加载到镜像的顶部,容器层之下的都叫做镜像层,所以对容器的增删改查只会发生在容器层内,只有容器层是可写的,容器层下面所有的镜像层都是只读的,读取文件的时候容器会从上到下的各层当中去找这个文件,找到了就读出来。修改文件时是找到数据先读到容器层内,然后在修改数据,不会向镜像再次写入
在这里插入图片描述
冷数据放在镜像层,热数据放在容器层

镜像是只读的,启动为容器后,新增一个可写层。dockerfile每一条指令代表其中的一层
容器和镜像之间的主要区别就是容器在镜像顶部由一个可写层,在容器中的所有操作都会存储在这个容器层中,删除容器后,容器层也会被删除,但是镜像不会变化。正因为每个容器都有自己的可写容器层,所有更改都存储在自己的容器层中,所以多个容器之间可以共享同一基础镜像的访问,但仍然具有自己的数据状态。如下图演示了多个容器共享同一镜像的请情况:

镜像启动为容器,镜像层为只读的,新创建一个可写层。存放于宿主机上的一个目录
/var/lib/overlay2/随机id命名的目录/

镜像命令

查询本地镜像 docker images

docker images 可以查看下载至本地的镜像

docker images [选项]
选项作用
-a列出本地所有的镜像
-q只显示镜像id
–digests显示备注信息
–no-trunc显示完整的镜像信息
[root@locahost ~]# docker images 
REPOSITORY      TAG          IMAGE ID        CREATED         SIZE
hello-world     latest       bf756fb1ae65    4 months ago    13.3kB
选项说明
REPOSITORY镜像名所属的仓库名称
TAG镜像的标签(版本),默认latest
IMAGE ID镜像ID
CREATED镜像创建时间 ,有的镜像显示的是本地创建时间,有的显示的是官方构建时间
SIZE镜像大小
[root@localhost ~]# docker images alpine		#只查看指定REPOSITORY的镜像
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
alpine       latest    c059bfaa849c   6 months ago   5.59MB
[root@localhost ~]# docker images alpine --no-trunc 	#显示完整的镜像信息
REPOSITORY   TAG       IMAGE ID                                                                  CREATED        SIZE
alpine       latest    sha256:c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18   6 months ago   5.59MB

查看指定镜像的详细信息

[root@localhost ~]# docker image inspect alpine
[
    {
        "Id": "sha256:c059bfaa849c4d8e4aecaeb3a10c2d9b3d85f5165c66ad3a4d937758128c4d18",
        "RepoTags": [
            "alpine:latest"
        ],
        "RepoDigests": [
            "alpine@sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2021-11-24T20:19:40.483367546Z",
        "Container": "4292e8ed2ef2b6dc4bbaf8e1cda0cb5f95b96adc4aa2da3d15181b54d07a0b34",
        "ContainerConfig": {
            "Hostname": "4292e8ed2ef2",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"/bin/sh\"]"
            ],
            "Image": "sha256:b747534ae29d08c0c84cc4326caf04e873c6d02bb67cd9c7644be2b4fa8d2f31",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh"
            ],
            "Image": "sha256:b747534ae29d08c0c84cc4326caf04e873c6d02bb67cd9c7644be2b4fa8d2f31",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 5585772,
        "VirtualSize": 5585772,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/f2307eddcd15aac8b272b5119761cfb9e26d6dc0b71349dbcdd94d2cb6ec4898/merged",
                "UpperDir": "/var/lib/docker/overlay2/f2307eddcd15aac8b272b5119761cfb9e26d6dc0b71349dbcdd94d2cb6ec4898/diff",
                "WorkDir": "/var/lib/docker/overlay2/f2307eddcd15aac8b272b5119761cfb9e26d6dc0b71349dbcdd94d2cb6ec4898/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

如果仔细观察,会注意到,这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。比如,ubuntu:18.04 镜像大小,在这里是 63.3MB,但是在 Docker Hub 显示的却是 25.47 MB。这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 docker image ls 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。

另外一个需要注意的问题是,docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
你可以通过 docker system df 命令来便捷的查看镜像、容器、数据卷所占用的空间。

搜索镜像 docker search

从docker仓库中去查询镜像,会列出许多镜像,选择需要下载的。
注意:查询是从docker hup上查询,下载是从自己配的加速地址进行下载,比如配置的是阿里云,则会在阿里云上下载

docker search [选项] 镜像名
选项作用
-s 数值会列出超过下载数大于该数值的镜像
–no-trunc显示完整的镜像信息
[root@localhost ~]# docker search -s 30 tomcat		#列出STARS列大于30的镜像
[root@localhost ~]# docker search mysql
NAME    DESCRIPTION   STARS    OFFICIAL    AUTOMATED
镜像名    镜像说明     收藏数   是否允许自动开启(run以后不需要采用一些别的命令就让他运行)     

在官方的docker仓库中搜索指定名称的docker镜像:http://hub.docker.com

下载镜像 docker pull

从 docker 仓库将镜像下载到本地,命令格式如下:

docker pull 仓库服务器地址:端口/仓库名/镜像名镜像名字:标签

从docker仓库下载镜像文件,如果不写版本默认最新版latest。本机先检索本地目录有无镜像文件,如果本地没有将会去官方仓库下载,下载至本地。镜像下载保存的路径:/var/lib/docker/overlay2/镜像ID
注意:当本地镜像下载的为latest,如果镜像仓库对该镜像进行了版本升级,新的latest镜像是无法下载至本地的。

[root@localhost ~]# docker pull mysql   
Using default tag: latest       #如果不指定版本默认下载最新版   
latest: Pulling from library/mysql
afb6ec6fdc1c: Pull complete     #分层下载 docker镜像的核心
0bdc5971ba40: Pull complete 
97ae94a2c729: Pull complete 
f777521d340e: Pull complete 
1393ff7fc871: Pull complete 
a499b89994d9: Pull complete 
7ebe8eefbafe: Pull complete 
597069368ef1: Pull complete 
ce39a5501878: Pull complete 
7d545bca14bf: Pull complete 
211e5bb2ae7b: Pull complete 
5914e537c077: Pull complete 
Digest: sha256:a31a277d8d39450220c722c1302a345c84206e7fd4cdb619e7face046e89031d  #签名(防伪标志)
Status: Downloaded newer image for mysql:latest       
docker.io/library/mysql:latest      #真实地址,也就是执行 docker pull mysql 等价于 docker pull docker.io/library/mysql:latest

[root@localhost ~]# docker pull mysql:5.7    #指定mysql5.7版本
5.7: Pulling from library/mysql  			 #library:官方镜像仓库名称hub.docker.com默认仓库省略不写
afb6ec6fdc1c: Already exists     			 #已经存在,文件已存在就不会再下载,联合文件系统
0bdc5971ba40: Already exists 
97ae94a2c729: Already exists 
f777521d340e: Already exists 
1393ff7fc871: Already exists 
a499b89994d9: Already exists 
7ebe8eefbafe: Already exists 
4eec965ae405: Pull complete 
a531a782d709: Pull complete 
270aeddb45e3: Pull complete 
b25569b61008: Pull complete 
Digest: sha256:d16d9ef7a4ecb29efcd1ba46d5a82bda3c28bd18c0f1e3b86ba54816211e1ac4
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7

从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。

镜像导出 docker sava

利用docker save命令可以将从本地镜像导出为一个打包 tar文件,然后复制到其他服务器进行导入使用

#可以使用 -o 也可以使用 > 
[root@localhost ~]# docker save 镜像名:TAG号 -o 保存的路径/压缩文件名
[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
centos       latest    5d0da3dc9764   4 weeks ago   231MB

[root@localhost ~]# docker save centos > /data/centos_image.tgz
[root@localhost ~]# ll /data/centos_image.tgz
-rw-r--r-- 1 root root 238581248 Oct 20 20:28 /data/centos_image.tgz

镜像导入 docker load

利用docker load命令可以将镜像导出的压缩文件再导入

#可以使用 -i 也可以使用 < 
[root@localhost ~]#  docker load -i 镜像包名
[root@localhost data]# ls
centos_image.tgz 
[root@localhost data]# docker load -i centos_image.tgz 
74ddd0ec08fa: Loading layer [==================================================>]  238.6MB/238.6MB
Loaded image: centos:latest
[root@localhost data]# docker images		# 发现有了新的镜像
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
centos       latest    5d0da3dc9764   5 weeks ago   231MB

删除镜像 docker rmi

docker rmi [选项] 镜像名[:版本]	版本不写默认latest
[root@locahost ~]# docker rmi -f 镜像文件		#不加版本默认为latest
[root@locahost ~]# docker rmi -f 镜像文件1 镜像文件2 …		#删除多个镜像
[root@locahost ~]# docker rmi -f `docker images -q`		#删除全部镜像

镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变得非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这也是为什么有的镜像删除时的层数和自己 docker pull 看到的层数不一样的原因。

容器(Container)

容器是一个镜像的可运行实例,镜像运行后得到的正在运行的运行环境
容器创建时需要指定镜像,每个镜像都由唯一的标示 Image ID ,和容器的 Container ID 一样,默认128位,可以使用前16为缩略形式,也可以使用镜像名与版本号两部分组合唯一标示,如果省略版本号,默认使用最新版本标签(latesr)

容器的本质是进程,但与直接在宿主机执行的进程不同,容器进程运行在属于自己的独立命名空间。因此容器可以拥有自己的文件系统、网络配置、进程空间、用户ID等。容器内的进程运行在隔离的环境中,使用起来就好像在一个独立的宿主系统一样。

容器的创建与管理

1、dockerd通过grpc和containerd模块通信(runc)交换,dockerd和containerd通信的socket文件:/run/containerd/containerd.sock
2、containerd在dockerd启动时被启动,然后containerd启动grpc请求监听,containerd处理grpc请求,根据请求做相应动作。/usr/bin/dockerd -H fd://–containerd/run/containerd/containerd.sock(文件配置路径/lib/systemd/system/docker.service)
3、若是创建容器,containerd拉起一个container-shim容器进程,并进行相应的创建操作。
4、container-shim被拉起后,start/exec/create拉起runc进程,通过exec、control文件和containerd通信,通过父子进程关系和SIGCHLD(信号)监控容器中进程状态。
5、在整个容器生命周期中,containerd通过epoll监控容器文件监控容器事件。
在这里插入图片描述

Docker compose

Docker compose:容器编排工具,允许用户在一个模板(YAML格式)中定义一组相关联的容器,会根据-link等参数对启动的优先级进行排序

Docker理念:一个容器一个进程,如果一个服务需要多个进程组成,就需要多个容器组成一个系统,互相分工和配合对外提供完整服务。在启动容器时,同一台主机下如果两个容器之间需要由数据交流,使用-link选项建立两个容器之间的互联,前提是建立是mariadb。

[root@locahost ~]# curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

[root@locahost ~]# chmod a+x /usr/local/bin/docker-compose
[root@locahost ~]# docker-compose --version

Docker命令

docker save -o 名 镜像名:tag #将镜像保存至本地
在这里插入图片描述
docker load --input 本地镜像名 将本地镜像导入
在这里插入图片描述

启动docker

[root@localhost ~]# systemctl start docker     #启动docker
[root@localhost ~]# systemctl restart docker   #重新启动

docker info

[root@localhost ~]# docker info
Containers: 1				#当前主机运行的容器总数
 Running: 1					#正在运行的容器数量
 Paused: 0					#暂停运行容器的数量
 Stopped: 0					#停止运行容器的数量
Images: 2					#当前服务器的镜像数量
Server Version: 1.13.1		#docker服务端版本
Storage Driver: overlay2	#使用的存储引擎/驱动
 Backing Filesystem: extfs	#后端文件系统,即服务器的磁盘文件系统
 Supports d_type: true		#是否支持d_type
 Native Overlay Diff: true	#是否支持差异数据存储
Logging Driver: journald	#日志类型
Cgroup Driver: systemd		#Cgroups类型
Plugins: 					#插件
 Volume: local				#卷
 Network: bridge host macvlan null overlay		#overlay跨主机通信
Swarm: inactive									#是否支持swarm
Runtimes: docker-runc runc						#已安装的容器运行时
Default Runtime: docker-runc					#默认使用的容器运行时
Init Binary: /usr/libexec/docker/docker-init-current	#初始化容器的守护进程,即pid为1的进程
containerd version:  (expected: aa8187dbd3b7ad67d8e5e3a15115d3eef43a7ed1)		#版本
runc version: 66aedde759f33c190954815fb765eedc1d782dd9 (expected: 9df8b306d01f59d3a8029be411de015b7304dd8f)		#版本
init version: fec3683b971d9c3ef73f284f176672c44b448662 (expected: 949e6facb77383876aeff8a6944dde66b3089574)   #版本
Security Options:			#安全选项	https://docs.docker.com/engine/security/apparmor
 apparmor					#安全模块	https://docs.docker.com/engine/security/seccomp
 seccomp					#审计(操作)
  WARNING: You're not using the default seccomp profile	
  Profile: /etc/docker/seccomp.json					#默认的配置文件
Kernel Version: 3.10.0-1160.25.1.el7.x86_64			#宿主机的内核版本
Operating System: CentOS Linux 7 (Core)				#宿主机操作系统
OSType: linux										#宿主机操作系统类型
Architecture: x86_64								#宿主机架构
Number of Docker Hooks: 3							#
CPUs: 2												#宿主机CPU数量
Total Memory: 3.733 GiB								#宿主机总内存
Name: localhost										#宿主机名
ID: GM2U:CTQB:3BX5:PK22:RPN4:B5AC:RAFX:2RKC:2HAA:K6AF:ZSRX:WI6Z		#宿主机ID
Docker Root Dir: /var/lib/docker					#宿主机数据保存目录
Debug Mode (client): false							#client端是否开启debug
Debug Mode (server): false							#server端是否开启debug
Registry: https://index.docker.io/v1/				#镜像仓库
Experimental: false									#是否测试版
Insecure Registries:								#非安全的镜像仓库
 127.0.0.0/8
Live Restore Enabled: false							#是否开启活动重启(重启docker-daemon不关闭容器)
Registries: docker.io (secure)						

默认是false 发现容器也停止了
在这里插入图片描述

在这里插入图片描述

容器命令

容器启动/停止

启动容器docker start 容器id或容器名
重启容器docker restart 容器id或容器名
停止容器docker stop 容器id或容器名
强制停止容器docker kill 容器id或容器名
挂起容器docker pause 容器id或容器名
解除挂起容器docker unpause 容器id或容器名

基于镜像创建容器 docker create

docker create 镜像名		
[root@localhost ~]# docker create alpine
[root@localhost ~]# docker ps -a		#docker ps只能查看到正在运行的容器
CONTAINER ID   IMAGE     COMMAND     CREATED          STATUS    PORTS     NAMES
b11b2b07bc09   alpine    "/bin/sh"   40 seconds ago   Created             nervous_heisenberg
[root@localhost ~]#  docker start b11b2b07bc09		#发现该容器的状态是created,需要通过start来将容器启动
b11b2b07bc09
[root@localhost ~]# docker ps -a	#状态由 Created 转变为 Exited (0),0代表正常退出。因为这个容器的COMMAND是/bin/sh,
CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS                     PORTS     NAMES
b11b2b07bc09   alpine    "/bin/sh"   7 minutes ago   Exited (0) 5 seconds ago             nervous_heisenberg

docker run alpine echo "hello docker"		
docker ps -a	发现这个容器的状态也是退出,容器的运行声明周期取决于COMMAND命令,命令运行结束,容器就退出

docker -i 打开交互式终端 -t 伪终端

然后再次执行发现会进入到容器里面运行,

exit,代表退出,发现容器状态依旧为退出状态


如果想要后台运行 -d		

在这里插入图片描述
docker start 发现容器状态为运行状态

docker stop 关闭容器,stop命令会把容器里面的服务关闭后在关闭容器
docker ps -a 会发现退出状态不为0,

docker start 启动
docker kill 代表强制关闭,

docker pause 将容器挂起,
docker unpause 解除挂起

运行容器 docker run

如果镜像不存在,会自动进行下载

[root@locahost ~]# docker run [选项] 镜像名:版本 [shell命令] [参数]	
选项说明
-i以交互模式运行容器,保持容器持久运行,如果没有客户端连接将会断开
-t为容器重新分配一个伪输入终端,进入到容器进行交互
-d后台运行容器,并返回容器ID,也即启动守护式容器;使用exec进入容器,exit容器后,容器不关闭
-p指定端口映射,格式为:-p 宿主机端口:容器端口
-P随机端口映射
	--name 容器新名字: 为容器指定一个名称;也可写为 —-name=容器新名字
	--hostname 主机名: 容器启动时的主机名
	--env:环境变量

	-v:
	--restart=always:容器自动启动
	-h 主机名:设置容器主机名,默认主机名是容器id号
	--dns xx.xx.xx.xx :设置容器使用的dns服务器
	--dns-search:DNS搜索设置
	--add-host hostname:IP :注入hostname<>Ip解析
	--rm:容器停止后自动删除 

Docker启动必须至少有一个工作在前台的守护进程
#使用镜像centos:latest以后台模式启动一个容器

[root@localhost ~]# docker run -d centos 
7e8fd11740c6dd453e7fd3ab9b8c31c916b5dd0b1cec2f06934fc97cb088ef02
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND       CREATED          STATUS                      PORTS     NAMES
7e8fd11740c6   centos    "/bin/bash"   31 seconds ago   Exited (0) 30 seconds ago             blissful_galois

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

很重要的要说明的一点:Docker容器后台运行,就必须有一个前台进程
容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的。

这个是docker的机制问题,比如你的web容器,我们以nginx为例,正常情况下,我们配置启动服务只需要启动响应的service即可。例如

service nginx start
但是,这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,
这样的容器后台启动后,会立即自杀因为他觉得他没事可做了.
所以,最佳的解决方案是,将你要运行的程序以前台进程的形式运行
 
[root@locahost ~]# docker run -it centos
[root@locahost ~]# docker run -it centos /bin/bash
以/bin/bash的形式登录,默认以该形式登录,不写也可以

[root@locahost ~]# docker run -it -p 8080:8080 tomcat
Tomcat默认端口8080
主机端口:外部访问就通过该自己设置的端口访问
容器端口:docker内部8080端口的tomcat
比如网页访问:ip:自己设置的主机端口

[root@locahost ~]# docker run -it -v 宿主机绝对路径目录:容器内目录 镜像名
使用-v选项,容器持久化自管理目录,如果目录不存在自动创建,两个文件共享资源
如果想要其它容器共享宿主机目录,创建时指定该宿主机目录即可共享该宿主机目录
缺点:换台机器宿主机目录无法使用

[root@localhost ~]# systemctl restart docker #重新启动docker,运行的容器将会停止
[root@locahost ~]# docker run –-name=a –-restart=always -d mysql
[root@localhost ~]# systemctl restart docker #加了该选项后该容器会自动启动


单次运行:
docker run -it --rm nginx		#退出后自动删除

指定容器DNS:Dns服务默认采用宿主机的dns地址 一是将dns地址配置在宿主机,二是将参数配置在docker启动脚本里面 -dns-1.1.1.1
[root@localhost ~]# docker run -it -rm --dns 223.6.6.6 centos bashe 
[root@localhost ~]# cat /etc/resolv.conf
nameserver 223.6.6.6 


在容器里面是默认不能使用systemd作为PID为1的守护进程,因为容器里面没有kernel ,虚拟机有内核
在容器里面必须有一个进程可以一直运行下去,这个进程就会被封装为PID为1的守护进程
可以使用Nginx的master进程,apache的主进程或者shell命令,如tail

在这里插入图片描述

#启动的容器在执行完shell命令就退出了,容器的生命周期取决于COMMAND命令,这条命令结束周期就结束。
[root@localhost ~]# docker run centos /bin/echo 'helloworld'
helloworld
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS                      PORTS     NAMES
d157ef644e61   centos    "/bin/echo helloworld"   13 seconds ago   Exited (0) 12 seconds ago             wonderful_leakey

docker run -it centos bash 			#会直接进入到容器,并随机生成容器ID和名称

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
检查本地是否存在指定的镜像,不存在就从 registry 下载
利用镜像创建并启动一个容器
分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
从地址池配置一个 ip 地址给容器
执行用户指定的应用程序
执行完毕后容器被终止

指定本地端口映射

#本地端口映射到容器端口
[root@localhost ~]# docker run -d -it --name nginx1 -p 81:80 nginx
e790982371f8050dffbb6efbb3e2c85732660594b45de6645151256a4291c42c

本地ip:本地端口:容器端口
[root@localhost ~]# docker run -d -it --name nginx2 -p 172.17.0.15:82:80 nginx
ab008741e9576426bf786df99c97bf1ea92850a4bbf39319792c913d954f606e

本地ip:本地随机端口:容器端口
[root@localhost ~]# docker run -d -it --name nginx3 -p 172.17.0.15::80 nginx
4d1199c1b549844bc29add5d8ec8322792bbb42f9b8e5a59328a6f35fdca5d25

#默认tcp
[root@localhost ~]# docker run -d -it --name nginx4 -p 84:80/udp nginx
7fea54fcba329621ed7040dfc5355f6df079f117cd08626193660b3c28fd6f77

[root@localhost ~]# docker run -d -it --name nginx5 -p 80:80 -p 443:443  nginx
9880708baa505c605a51abfdb399f3185ecfe5afc6539079ae87b97e50e2ea45

[root@localhost ~]# docker ps
CONTAINER ID     IMAGE      COMMAND                  CREATED             STATUS              PORTS                                      NAMES
9880708baa50     nginx      "/docker-entrypoin..."   27 seconds ago      Up 25 seconds       0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   nginx5
7fea54fcba32     nginx      "/docker-entrypoin..."   6 minutes ago       Up 6 minutes        80/tcp, 0.0.0.0:84->80/udp                 nginx4
4d1199c1b549     nginx      "/docker-entrypoin..."   10 minutes ago      Up 10 minutes       172.17.0.15:32769->80/tcp                  nginx3
ab008741e957     nginx      "/docker-entrypoin..."   12 minutes ago      Up 12 minutes       172.17.0.15:82->80/tcp                     nginx2
e790982371f8     nginx      "/docker-entrypoin..."   12 minutes ago      Up 12 minutes       0.0.0.0:81->80/tcp                         nginx1

[root@localhost ~]# iptables -t nat -vnL
Chain PREROUTING (policy ACCEPT 206 packets, 8592 bytes)
 pkts bytes target     prot opt in     out     source               destination         
64366 2428K DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT 206 packets, 8592 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 29210 packets, 1753K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT 29210 packets, 1753K bytes)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 MASQUERADE  all  --  *      !docker0  172.18.0.0/16        0.0.0.0/0           
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.2           172.18.0.2           tcp dpt:80
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.3           172.18.0.3           tcp dpt:80
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.4           172.18.0.4           tcp dpt:80
    0     0 MASQUERADE  udp  --  *      *       172.18.0.5           172.18.0.5           udp dpt:80
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.6           172.18.0.6           tcp dpt:443
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.6           172.18.0.6           tcp dpt:80

Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
    0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0           
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:81 to:172.18.0.2:80
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            172.17.0.15          tcp dpt:82 to:172.18.0.3:80
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            172.17.0.15          tcp dpt:32769 to:172.18.0.4:80
    0     0 DNAT       udp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            udp dpt:84 to:172.18.0.5:80
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443 to:172.18.0.6:443
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.18.0.6:80

宿主机对应端口被映射到容器端口

进入容器docker attach

两种退出方式:
exit:容器退出停止
ctrl+p+q:容器不停止退出

进入容器docker exec

进入正在运行的容器并以命令行交互

[root@locahost ~]# docker exec [选项] 容器名
选项:
	-d :分离模式: 在后台运行
	-i :即使没有附加也保持STDIN 打开
	-t :分配一个伪终端

[root@locahost ~]# docker exec -it 容器名 /bin/bash 
进入到容器中,使用docker run -id 创建运行的容器,exit退出容器后,该容器后台还在运行
[root@locahost ~]# docker exec 容器id ls -l /tmp
不进入容器执行该命令(执行的是容器中的内容),得到容器内的结果

[root@localhost ~]# docker exec -it 容器id /bin/bash
[root@localhost ~]# docker attach 容器id /bin/bash
docker exec : 进入容器后开启一个新的终端,可以进行操作,exit后容器还在运行
docker attach :进入容器正在执行的终端,不会启动新的进程
docker atttach 进入容器后执行exit整个容器都会停止,ctrl+p在ctrl+q退出容器,这样容器才不会被关闭

在这里插入图片描述

yum provides ip #docker进入容器后可以通过yum命令来查询属于哪个软件包,比如查询ip

当执行docker run 的时候已经启动了一个bash,docker exec 进入容器是因为又开了个bash,而attach 是进入的父shell,所以退出后容器就嘎了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

查看容器 docker ps

docker ps [选项]
选项说明
-a列出当前所有正在运行的容器以及已经关闭的所有容器
-q静默模式,只显示容器id
-f
-l显示最近创建的容器
-n显示最近n个创建的容器
–no-trunc显示完整id,容器id是容器的唯一标识,完整版是128位,系统只显示16位
[root@localhost ~]# docker ps
CONTAINER ID  IMAGE    COMMAND      		CREATED       STATUS    PORTS     NAMES
容器id        镜像名   容器创建执行的命令     容器创建时间   运行时间  端口映射   容器名

退出容器

两种退出方式:
exit:容器退出停止
ctrl+p+q:容器不停止退出


在虚拟机里面,我们可以通过重启服务或者重启虚拟机的方式进行配置变更和代码发布,但是,在k8s环境,是禁止通过重启服务或者重启容器来实现配置变更和代码发布的

查看容器信息 docker inspect

docker inspect 容器id

删除容器 docker rm

选项说明
-f强制删除
-l删除指定的link
-v删除与容器关联的卷,默认也是删除
[root@locahost ~]# docker rm -f 容器id					# -f选项代表强制删除,即使容器正在运行中,也会被强制删除掉
[root@locahost ~]# docker rm -f `docker ps -a -q`		#删除全部容器
[root@locahost ~]# docker rm -fv `docker ps -a -q -f status=exited` 	#批量删除已退出的容器
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND       CREATED         STATUS         PORTS     NAMES
68baa232d50a   centos    "/bin/bash"   3 seconds ago   Up 3 seconds             inspiring_bassi
[root@localhost ~]# docker rm 68baa232d50a		#容器正在运行无法删除,需要docker stop 在执行docker rm
Error response from daemon: You cannot remove a running container 68baa232d50a57343b1911c270f7192408f59dfb11bf6049421f2f175f73f1ec. Stop the container before attempting removal or force remove
[root@localhost ~]# docker rm -f 68baa232d50a	#强制删除
68baa232d50a
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

查看容器日志docker logs

当容器无法启动

[root@locahost ~]# docker logs [选项] 容器id
选项说明
-f跟随最新的日志打印(类似于 tail -f)
-t加入时间戳
–tail 数字显示最后多少条

查看容器内运行的进程 docker top


[root@locahost ~]# docker top 容器id

查看容器所占用的系统资源 docker stats


[root@locahost ~]# docker stats 容器id
CONTAINER   CPU%       MEM USAGE/LIMIT         MEM%       NET I/O   BLOCK I/O   PIDS
容器名       cpu使用率  已使用内存大小/总内存大小  内存使用率  网络IO     磁盘IO

从容器内拷贝文件到宿主机上 docker cp


[root@locahost ~]# docker cp 容器id:容器路径文件 要拷贝到的宿主机路径

Docker镜像制作

Docker镜像有没有内核。从镜像大小上面来说,一个比较小的镜像只有十几MB,而内核文件需要一百多兆,因此镜像里面是没有内核的。
镜像在被启动为容器后将直接使用宿主机的内核,而镜像本身则只提供相应的rootfs,即系统正常运行所必须的用户空间的文件系统。比如/dev、/proc、/bin、/etc 等目录,容器当中/boot目录是空的,而/boot当中保存的就是与内核相关的文件和目录。由于容器启动和运行过程中是直接使用了宿主机的内核,不会直接调用物理硬件,所以也不会涉及到硬件驱动,因此也无需容器内拥有自已的内核和驱动。另外有内核的那是虚拟机,如果使用虚拟机技术,对应每个虚拟机都有自已独立的内核。

[root@localhost ~]# uname -r			#查看宿主机内核
3.10.0-1160.45.1.el7.x86_64
[root@localhost ~]# docker run -it --rm debian bash
root@4b072acf6539:/# uname -r			#查看容器内核,发现与宿主机内核一致
3.10.0-1160.45.1.el7.x86_64
root@4b072acf6539:/# ls /boot
root@4b072acf6539:/# cat /etc/issue
Debian GNU/Linux 11 \n \l

制作镜像方式:
Docker 镜像制作类似于虚拟机的镜像(模版)制作,即按照公司的实际业务需求将需要安装的软件、相关配置等基础环境配置完成,然后将其做成镜像,最后再批量从镜像批量生成容器实例,这样可以极大的简化相同环境的部署工作。
Docker的镜像制作分为手动制作(基于容器)和自动制作(基于DockerFile),企业通常都是基于Dockerfile制作镜像

docker commit #通过修改现有容器,将之手动构建为镜像
docker build #通过Dockerfile文件,批量构建为镜像

docker 镜像生命周期

在这里插入图片描述

docker commit 手动构建镜像(慎用)

基于容器制作为镜像,制作镜像和 CONTAINER 状态无关,停止状态也可以制作镜像。如果没有指定 [REPOSITORY[:TAG]],那么REPOSITORY和TAG都为。提交的时候标记TAG号:后期可以根据TAG标记创建不同版本的镜像以及创建不同版本的容器

为什么不建议使用 docker commit 手动构建镜像?
当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
可以通过 docker history 具体查看镜像内的历史记录,如果比较 nginx:latest 的历史记录,我们会发现新增了我们刚刚提交的这一层。
使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到。这会让镜像更加臃肿。

[root@locahost ~]# docker commit [选项] 容器名 新镜像名:版本
选项:
  -a "作者信息"
  -m "提交的描述信息"
  -c "容器运行时执行的命令"
[root@locahost ~]# docker commit -a "qs" -m "描述信息" 容器id 新镜像名:版本
Commit只是把容器当时的状态保存为镜像,当你再次把镜像运行为容器的时候,依然使用的是原来的启动命令(COMMAND)

比如本地有一个centos:v1,再次通过 commit 重新提交,如果之前的名字和版本没有做任何修改,那么就会覆盖之前的镜像,被覆盖的这个镜像的REPOSITORY和TAG就变成了none,这类无标签镜像也被称为 虚悬镜像(dangling image) 。下面通过案例进行演示

[root@localhost ~]# docker run -d busybox 
fd489fdceb09d639916bb1071d445fc500710c1123d3d63ec0bac06bc5d8cd36
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                  CREATED          STATUS                      PORTS                                                                      NAMES
fd489fdceb09   busybox               "sh"                     16 seconds ago   Exited (0) 14 seconds ago                                                                              keen_vaughan
[root@localhost ~]# docker commit fd489fdceb09 my_busybox:v1
sha256:27973ab29701e3befda04daf65fe43430e48f4c233857ebceee5e8c4923a6293
[root@localhost ~]# docker images
REPOSITORY     TAG       IMAGE ID       CREATED         SIZE
my_busybox     v1        27973ab29701   8 seconds ago   1.24MB

第一次提交的这个镜像原本是有镜像名和标签的,然后我们再次提交。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 的镜像

[root@localhost ~]# docker commit fd489fdceb09 my_busybox:v1
sha256:bf0effe95ea7f8201b327b3814d224342bb2e419797c9fa76c03cbc40a63c362
[root@localhost ~]# docker images
REPOSITORY     TAG       IMAGE ID       CREATED              SIZE
my_busybox     v1        bf0effe95ea7   3 seconds ago        1.24MB
<none>         <none>    27973ab29701   About a minute ago   1.24MB
[root@localhost ~]# docker image ls -f dangling=true		#该命令用于专门显示虚悬镜像
<none>       <none>    27973ab29701   7 minutes ago   1.24MB

一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。

[root@localhost ~]# docker image prune

基于容器手动制作镜像步骤具体如下:

  1. 下载一个系统的官方基础镜像,如: CentOS 或 Ubuntu
  2. 基于基础镜像启动一个容器,并进入到容器
  3. 在容器里面做配置操作:安装基础命令、配置运行环境、安装服务和配置服务、放业务程序代码
  4. 提交为一个新镜像 docker commit
  5. 基于自己的的镜像创建容器并测试访问

基于 busybox 制作 httpd 镜像

[root@localhost ~]# docker pull busybox
[root@localhost ~]# docker run -it --name busybox_http busybox
/ # mkdir -p /data/html
/ # echo "busybox_httpd_test" > /data/html/index.html
/ # exit

#两种不同的格式
[root@localhost ~]# docker commit -a "qianshuai" -c 'CMD /bin/httpd -fv -h /data/html' -c "EXPOSE 80" busybox_http httpd_busybox:v1.0
[root@localhost ~]# docker commit -a "qianshuai" -c 'CMD ["/bin/httpd","-f","-v","-h","/data/html"]' -c "EXPOSE 80" busybox_http httpd_busybox:v2.0

[root@localhost ~]# docker images 
REPOSITORY               TAG       IMAGE ID       CREATED          SIZE
httpd_busybox            v2.0      dd83963b0ac5   5 seconds ago    1.24MB
httpd_busybox            v1.0      42487fc6dc41   32 seconds ago   1.24MB

[root@localhost ~]# docker run -d -P  --name httpd_busybox1 httpd_busybox:v1.0 
[root@localhost ~]# docker run -d -P  --name httpd_busybox2 httpd_busybox:v2.0 
[root@localhost ~]# docker port httpd_busybox1
80/tcp -> 0.0.0.0:49153
80/tcp -> :::49153
[root@localhost ~]# docker port httpd_busybox2
80/tcp -> 0.0.0.0:49154
80/tcp -> :::49154
[root@localhost ~]# curl 127.0.0.1:49153
busybox_httpd_test
[root@localhost ~]# curl 127.0.0.1:49154
busybox_httpd_test

[root@localhost ~]# docker inspect -f "{{.Config.Cmd}}" httpd_busybox1
[/bin/sh -c /bin/httpd -fv -h /data/html]
[root@localhost ~]# docker inspect -f "{{.Config.Cmd}}" httpd_busybox2
[/bin/httpd -f -v -h /data/html]

[root@localhost ~]# docker exec -it httpd_busybox1 sh
/ # ps -ef 
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/httpd -fv -h /data/html
    9 root      0:00 sh
   15 root      0:00 ps -ef

在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像
在这里插入图片描述

基于Ubuntu的基础镜像手动制作nginx 的镜像

1、启动Ubuntu基础镜像并下载nginx

[root@localhost ~]# docker run -it --name nginx_ubuntu ubuntu bash
root@e18271e73f15:/# apt update
root@e18271e73f15:/# apt -y install nginx

Please select the geographic area in which you live. Subsequent configuration questions will narrow this down by presenting a list of cities,
representing the time zones in which they are located.

  1. Africa   3. Antarctica  5. Arctic  7. Atlantic  9. Indian    11. SystemV  13. Etc
  2. America  4. Australia   6. Asia    8. Europe    10. Pacific  12. US
Geographic area: 6

Please select the city or region corresponding to your time zone.
70. Shanghai 
Time zone: 70	#配置时区,这里选择的是上海

root@e18271e73f15:/# nginx -v
nginx version: nginx/1.18.0 (Ubuntu)
root@e18271e73f15:/# echo Nginx Website in Docker > /var/www/html/index.html

2、提交为镜像

[root@localhost ~]# docker commit -a 'qianshuai' -m 'nginx-ubuntu:v1' nginx_ubuntu nginx_ubuntu:v1
[root@localhost ~]# docker images
REPOSITORY               TAG       IMAGE ID       CREATED        SIZE
nginx_ubuntu             v1        1482153603bb   2 hours ago    169MB

3、从制作的新镜像启动容器并测试访问

[root@localhost ~]# docker run -d -p 8000:80 --name nginx-web nginx_ubuntu:v1 nginx -g 'daemon off;'
b9b7bd57a6bdfcea500bacb4d078ff8f3b483a3dad5894412355eb921f5ef097 
[root@localhost ~]# curl http://127.0.0.1:8000
Nginx Website in Docker

基于CentOS的基础镜像手动制作 nginx 镜像

yum安装

1、下载基础镜像并初始化系统。基于某个基础镜像之上重新制作,因此需要先有一个基础镜像,本次使用官方提供的centos镜像为基础

[root@localhost ~]# docker pull centos:centos7
[root@localhost ~]# docker run -it centos:centos7 bash
#修改时区
[root@ccc9de7dcde6 /]# rm -f /etc/localtime
[root@ccc9de7dcde6 /]# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

2、更改yum 源

[root@ccc9de7dcde6 /]# yum -y install wget
[root@ccc9de7dcde6 /]# rm -rf /etc/yum.repos.d/*
[root@ccc9de7dcde6 /]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo
[root@ccc9de7dcde6 /]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo
#清除缓存并重新构建 yum 源
[root@ccc9de7dcde6 /]# yum clean all
[root@ccc9de7dcde6 /]# rm -rf /var/cache/yum
[root@ccc9de7dcde6 /]# yum makecache

3、安装相关软件和工具

#安装常用命令
[root@ccc9de7dcde6 /]# yum -y install vim curl iproute net-tools
#yum安装nginx
[root@ccc9de7dcde6 /]# yum –y install nginx
#清理yum缓存
[root@ccc9de7dcde6 /]# rm -rf /var/cache/yum/*

4、修改服务的配置信息关闭nginx后台运行

[root@ccc9de7dcde6 /]# vim /etc/nginx/nginx.conf		#新增daemon off
user nginx;
daemon off; #关闭后台运行

5、自定义web界面

[root@ccc9de7dcde6 /]# rm -f /usr/share/nginx/html/index.html
[root@ccc9de7dcde6 /]# echo "Nginx Page in Docker" > /usr/share/nginx/html/index.html

6、提交为镜像

[root@localhost ~]# docker commit -a "zhangsan" -m "nginx yum v1" -c "EXPOSE 80 443" ccc9de7dcde6 centos7-nginx:v1
sha256:efb4239c46ef8fc86ae64c247fdb7ff41546654a839987b12c4e07fe25d97ba0

7、从制作的镜像启动容器

[root@localhost ~]# docker images
REPOSITORY               TAG       IMAGE ID       CREATED          SIZE
centos7-nginx            v1        efb4239c46ef   41 seconds ago   831MB
[root@localhost ~]# docker run -d -p 8000:80 --name my-centos-nginx centos7-nginx:v1 /usr/sbin/nginx
e2db096ecbebce3f285ae389537a65a7d190551cabd4f756f4e309d986d79143

8、访问测试镜像

[root@localhost ~]# curl 127.0.0.1:8000
Nginx Page in Docker
编译安装

1、下载基础镜像并初始化系统。基于某个基础镜像之上重新制作,因此需要先有一个基础镜像,本次使用官方提供的centos镜像为基础

[root@localhost ~]# docker pull centos:centos7
[root@localhost ~]# docker run -it --name centos7_nginx centos:centos7 bash
[root@0ce9420fb0f5 /]# useradd -r -s /sbin/nologin nginx
#修改时区
[root@0ce9420fb0f5 /]# rm -f /etc/localtime
[root@0ce9420fb0f5 /]# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

2、更改yum 源

[root@0ce9420fb0f5 /]# yum -y install wget
[root@0ce9420fb0f5 /]# rm -rf /etc/yum.repos.d/*
[root@0ce9420fb0f5 /]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo
[root@0ce9420fb0f5 /]# wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo
#清除缓存并重新构建 yum 源
[root@0ce9420fb0f5 /]# yum clean all
[root@0ce9420fb0f5 /]# rm -rf /var/cache/yum
[root@0ce9420fb0f5 /]# yum makecache

3、安装相关软件和工具

#安装基础包
[root@0ce9420fb0f5 /]# yum -y install gcc gcc-c++ automake pcre pcre-devel zlib zlib-devel openssl openssl-devel
#安装nginx
[root@0ce9420fb0f5 /]# cd /usr/local/src
[root@0ce9420fb0f5 src]# wget http://nginx.org/download/nginx-1.16.1.tar.gz
[root@0ce9420fb0f5 src]# tar xf nginx-1.16.1.tar.gz
[root@0ce9420fb0f5 src]# cd nginx-1.16.1
[root@0ce9420fb0f5 nginx-1.16.1]# ./configure --prefix=/opt/nginx
[root@0ce9420fb0f5 nginx-1.16.1]# make && make install
[root@0ce9420fb0f5 nginx-1.16.1]# rm -rf nginx*
[root@0ce9420fb0f5 nginx-1.16.1]# rm -rf /var/cache/yum/*

4、修改服务的配置信息关闭nginx后台运行

[root@0ce9420fb0f5 ~]# vi /opt/nginx/conf/nginx.conf 	#新增以下内容
user nginx;
daemon off;
[root@0ce9420fb0f5 ~]# ln -s /opt/nginx/sbin/nginx /usr/sbin/
[root@0ce9420fb0f5 ~]# ll /usr/sbin/nginx
lrwxrwxrwx 1 root root 21 Jun 27 14:13 /usr/sbin/nginx -> /opt/nginx/sbin/nginx

5、自定义web界面

[root@0ce9420fb0f5 ~]# echo "Nginx Page in Docker" > /opt/nginx/html/index.html

6、提交为镜像,不要退出容器,在另一个终端窗口执行以下命令

[root@localhost ~]# docker ps
CONTAINER ID   IMAGE              COMMAND             CREATED          STATUS          PORTS              NAMES
0ce9420fb0f5   centos:centos7     "bash"              17 minutes ago   Up 17 minutes                      centos7_nginx
[root@localhost ~]# docker commit -m "nginx1.6.1" -c "CMD nginx" centos7_nginx centos7-nginx:v1.6.1

7、从制作的镜像启动容器

[root@localhost ~]# docker images
REPOSITORY               TAG       IMAGE ID       CREATED          SIZE
centos7-nginx            v1.6.1    0fa45627a65e   23 seconds ago   400MB

#最后面的nginx是运行的命令,即镜像里面要运行一个nginx命令,所以前面软链接到/usr/sbin/nginx,目的为了让系统不需要指定路径就可以执行此命令
[root@localhost ~]# docker run -d -p 8000:80 --name my-centos-nginx centos7-nginx:v1.6.1 nginx

8、访问测试镜像

[root@localhost ~]# curl 127.0.0.1:8000
Nginx Page in Docker

Dockerfile

Dockerfile是用来构建docker镜像的构建文件,由一条一条的指令组成,由Docker程序解释执行,脚本中的每条指令对应Linux 下面的每一条命令,Docker程序将这些DockerFile指令再翻译成真正的linux命令,Docker程序读取DockerFile并根据指令生成Docker镜像。相比于手动commit制作镜像的方式,Dockerfile更能直观的展示镜像是怎么产生的。

Dockerfile中每条指令都是独立运行的,每条指令都会创建一个新的镜像层,并对镜像进行提交,最多不超过128层,命令最多128行。比如RUN cd /tmp 对下一条指令不会有任何影响
1、 编写一个dockerfile文件
2、 docker build 构建称为一个镜像
3、 docker run 运行镜像
4、 docker push发布镜像(DockerHub、阿里云仓库)

Dockerfile 镜像制作和使用流程
在这里插入图片描述
Dockerfile文件的制作镜像的分层结构
在这里插入图片描述

dockerfile文件格式

1、每一行以Dockerfile的指令开头,指令不区分大小写,但是惯例使用大写。# 开头作为注释
2、指令按文件的顺序从上至下进行执行,每一行只支持一条指令,每条指令可以携带多个参数
3、每个指令的执行会生成一个新的镜像层,为了减少分层和镜像大小,尽可能将多条指令合并成一条指令
4、制作镜像一般可能需要反复多次,每次执行dockfile都按顺序执行,从头开始,已经执行过的指令已经缓存,不需要再执行,如果后续有一行新的指令没执行过,其往后的指令将会重新执行,所以为加速镜像制作,将最常变化的内容放下dockerfile的文件的后面

FROM 指定基础镜像

指定基础镜像,Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。后续的指令都依赖于该指令指定的image。FROM指令指定的基础image可以是官方远程仓库中的,也可以位于本地仓库,该指令在dockerfile中只能有一个
base镜像:基础的镜像,不需要依赖其它镜像来构建;其它镜像以之为基础进行扩展的镜像,比如debian、centos

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

--platform 指定镜像的平台,比如: linux/amd64, linux/arm64, or windows/amd64
tag 和 digest是可选项,如果不指定,默认为latest

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,该镜像是一个空的镜像,可以用于构建busybox等超小镜像,可以说是真正的从零开始构建属于自己的镜像。该镜像在构建基础镜像(例如debian和busybox)或超最小镜像(仅包含一个二进制文件及其所需内容,例如:hello-world)的上下文中最有用。

FROM scratch
...

LABEL 指定镜像元数据

LABEL可以指定镜像元数据,如镜像作者等。一个镜像可以有多个label,还可以写在一行中,即多标签写法,可以减少镜像的的大小

LABEL <key>=<value> <key>=<value> <key>=<value> ...
LABEL version="1.0"
LABEL name="test"

LABEL version="1.0" name="test"

docker inspect 命令可以查看LABEL信息

[root@localhost ~]# docker inspect centos:7 
...
"Labels": {
    "org.label-schema.build-date": "20201113",
    "org.label-schema.license": "GPLv2",
    "org.label-schema.name": "CentOS Base Image",
    "org.label-schema.schema-version": "1.0",
    "org.label-schema.vendor": "CentOS",
    "org.opencontainers.image.created": "2020-11-13 00:00:00+00:00",
    "org.opencontainers.image.licenses": "GPL-2.0-only",
    "org.opencontainers.image.title": "CentOS Base Image",
    "org.opencontainers.image.vendor": "CentOS"
}
...

RUN 执行 shell命令

RUN 指令用来在构建镜像阶段需要执行 FROM 指定镜像所支持的Shell命令,RUN可以运行任何被基础 image 支持的命令。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

shell 格式:相当于 /bin/sh -c <命令> 此种形式支持环境变量

RUN <命令>

RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

shell格式中,通常是一个shell命令,且以"/bin/sh -c”来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,因此,当使用docker stop 命令停止容器时,此进程接收不到SIGTERM信号

exec 格式:此种形式不支持环境变量,注意:是双引号,不能是单引号

RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。

#exec格式可以指定其它shell
RUN ["/bin/bash","-c","echo hello wang"]

exec格式中的参数是一个JSON格式的数组,其中为要运行的命令,后面的为传递给命令的选项或参数;然而,此种格式指定的命令不会以"/bin/sh -c"来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式。

RUN ["/bin/bash", "-c", "<executable>", "<param1>"]

RUN 可以写多个,每一个RUN指令都会建立一个镜像层,所以尽可能合并成一条指令,比如将多个shell命令通过 && 连接一起成为在一条指令。

多个前后RUN 命令独立无关和shell命令不同,file.txt并不存放在opt路径下

[root@localhost ~]# vim dockerfile
FROM alpine
RUN cd /opt
RUN echo "hello docker" > file.txt
[root@localhost ~]# docker build .
[root@localhost dockerfile]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
<none>       <none>    fa7006bf3f78   3 hours ago    5.59MB

[root@localhost ~]# docker run -it --rm fa7006bf3f78 /bin/sh -c "ls /opt"
[root@localhost ~]# docker run -it --rm fa7006bf3f78 /bin/sh -c "cat /file.txt"
hello docker

CMD 容器启动时执行的命令

Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程启动执行的命令。该命令可以是执行自定义脚本,也可以是执行系统命令。该指令只能在文件中存在一次,如果有多个,则只执行最后一条。
如果docker run没有指定任何的执行命令,或者dockerfile里面也没有ENTRYPOINT,那么开启容器时就会使用执行CMD指定的默认的命令。如果用户启动容器时用 docker run xxx 指定运行的命令,则会覆盖 CMD 指定的命令。比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。

一个容器中需要持续运行的进程一般只有一个,CMD 用来指定启动容器时默认执行的一个命令,且其运行结束后容器也会停止,所以一般CMD指定的命令为持续运行且为前台命令
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。一些初学者将 CMD 写为:CMD service nginx start 然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
CMD service nginx start 会被理解为 CMD [ “sh”, “-c”, “service nginx start”],因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:CMD [“nginx”, “-g”, “daemon off;”]
docker 不是在宿主机上虚拟出一套硬件并运行完整的操作系统,然后再在其上运行所需的应用进程,而是直接在 docker 容器里面的进程直接运行在宿主的内核中,docker 会做文件系统、网络以及进程隔离等,容器不用进行硬件和内核的虚拟。对于 docker 容器而言,其启动程序就是容器的应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,其它辅助进程不是它需要关心的东西。
在这里插入图片描述

# exec 格式:使用 exec 执行,推荐方式,第一个参数必须是命令的全路径,此种形式不支持环境变量
CMD ["可执行文件", "参数1", "参数2"...]

# shell 格式:在 /bin/sh 中执行,提供给需要交互的应用;此种形式支持环境变量
CMD command param1 param2

# 参数列表格式:提供给 ENTRYPOINT 命令的默认参数
CMD ["参数1", "参数2"...]

在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:CMD echo $HOME 在实际执行中,会将其变更为:CMD [ “sh”, “-c”, “echo $HOME” ] 这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。

FROM ubuntu:18.04
RUN apt-get update
&& apt-get install -y curl
&& rm -rf /var/lib/apt/lists/*
CMD [ “curl”, “-s”, “http://myip.ipip.net” ]
在镜像名后面的是 command,运行时会替换 CMD 的默认值。

ENTRYPOINT 容器启动时执行的命令

容器启动时默认运行的命令,功能类似于CMD,ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

# 使用 exec 执行
ENTRYPOINT ["executable", "param1", "param2"]

# shell中执行
ENTRYPOINT command param1 param2

该指令的使用分为两种情况,一种是独自使用,另一种和CMD指令配合使用。
当独自使用时,如果你还使用了CMD命令且CMD是一个完整的可执行的命令,那么CMD指令和ENTRYPOINT会互相覆盖只有最后一个CMD或者ENTRYPOINT有效

CMD指令将不会被执行,只有ENTRYPOINT指令被执行

CMD echo “Hello, World!” 
ENTRYPOINT ls -l 

另一种用法和CMD指令配合使用来指定ENTRYPOINT的默认参数,这时CMD指令不是一个完整的可执行命令,仅仅是参数部分;ENTRYPOINT指令只能使用JSON方式指定执行命令,而不能指定参数
两种命令都会被执行

CMD ["-l"] 
ENTRYPOINT ["/usr/bin/ls"] 

如果docker run 命令有参数,那么参数全部都会作为ENTRYPOINT的参数。ENTRYPOINT 不能被 docker run 提供的参数覆盖,而是追加。
如果docker run 后面没有额外参数,但是dockerfile中的CMD里有,即Dockerfile中即有CMD也有ENTRYPOINT,那么CMD的全部内容会作为ENTRYPOINT的参数
如果docker run 后面有额外参数,同时Dockerfile中即有CMD也有ENTRYPOINT,那么docker run后面的参数覆盖掉CMD参数内容,最终作为ENTRYPOINT的参数

可以通过docker run --entrypoint string 参数在运行时替换,注意string不要加空格
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效

使用CMD要在运行时重新写命令本身,然后在后面才能追加运行参数,ENTRYPOINT则可以运行时无需重写命令就可以直接接受新参数

[root@localhost ~]# docker run -it --rm --entrypoint cat alpine /etc/issue
Welcome to Alpine Linux 3.15
Kernel \r on an \m (\l)

在这里插入图片描述

USER 指定运行容器用户

指定运行容器的用户名或 UID,后续的 RUN 也会使用指定用户执行命令。这个用户必须是事先建立好的,否则无法切换。如果没有指定 USER,默认是 root 身份执行

USER <user>[:<group>]
USER <UID>[:<GID>]

RUN groupadd -r mysql && useradd -r -g mysql mysql

EXPOSE 暴露端口

该指令会将容器中的端口映射成宿主机器中的某个端口。当你需要访问容器的时候,可以不是用容器的IP地址而是使用宿主机器的IP地址和映射后的端口。
要完成整个操作需要两个步骤,首先在Dockerfile使用EXPOSE设置需要映射的容器端口,然后在运行容器的时候指定-p选项加上EXPOSE设置的端口,这样EXPOSE设置的端口号会被随机映射成宿主机器中的一个端口号。也可以指定需要映射到宿主机器的那个端口,这时要确保宿主机器上的端口号没有被使用。EXPOSE指令可以一次设置多个端口号,相应的运行容器的时候,可以配套的多次使用-p选项。

映射一个端口

EXPOSE 22

相应的运行容器使用的命令

docker run -p port1 image

映射多个端口

EXPOSE port1 port2 port3

相应的运行容器使用的命令

docker run -p port1 -p port2 -p port3 image

还可以指定需要映射到宿主机器上的某个端口号

docker run -p host_port1:port1 -p host_port2:port2 -p host_port3:port3 image

​ 宿主机端口:容器端口

ENV 设置容器环境变量

ENV 可以定义环境变量和值,会被后续指令(如:ENV,ADD,COPY,RUN等)通过$KEY或${KEY}进行引用,并在容器运行时保持。容器启动后可以通过docker inspect查看这个环境变量,也可以通过在docker run --env key=value时设置或修改环境变量。假如你安装了JAVA程序,需要设置JAVA_HOME,那么可以在Dockerfile中这样写:

ENV JAVA_HOME /path/to/java/dirent
容器内就会有一个JAVA_HOME的变量,其变量值为/path/to/java/dirent

#变量赋值格式1
ENV <key> <value> 	#此格式只能对一个key赋值,<key>之后的所有内容均会被视作其<value>的组成部分

#变量赋值格式2
ENV <key1>=<value1> <key2>=<value2> <key3>=<value3> ... #此格式可以支持多个key赋值,如果定义多个变量可以减少镜像层

#如果<value>中包含空格,可以以反斜线\进行转义,也可通过对<value>加引号进行标识;另外反斜线也可用于续行

#只使用一次变量
RUN <key>=<value> <command>

#引用变量
RUN $key .....

#变量支持高级赋值格式
${key:-word}
${kye:+word}

ARG

格式:ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。

ARG 指令有生效范围,如果在 FROM 指令之前指定,那么只能用于 FROM 指令中。
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo ${DOCKER_USERNAME}

使用上述 Dockerfile 会发现无法输出 ${DOCKER_USERNAME} 变量的值,要想正常输出,你必须在 FROM 之后再次指定 ARG

# 只在 FROM 中生效
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

# 要想在 FROM 之后使用,必须再次指定
ARG DOCKER_USERNAME=library

RUN set -x ; echo ${DOCKER_USERNAME}

对于多阶段构建,尤其要注意这个问题

# 这个变量在每个 FROM 中都生效
ARG DOCKER_USERNAME=library

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 1

FROM ${DOCKER_USERNAME}/alpine

RUN set -x ; echo 2

COPY 复制

将宿主机本地文件复制到容器中,必须是build上下文中的路径(为 Dockerfile 所在目录的相对路径),不能是其父目录中的文件。如果是目录,则其内部文件或子目录会被递归复制,但目录自身不会被复制。源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。

COPY [--chown=<user>:<group>] <源路径>... <目标路径>

源路径:可以是多个,甚至可以是通配符
目标路径:可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。如果目标路径不存在,会在复制文件前先行创建

ADD 复制和解包

ADD命令可以认为是增强版的COPY,不仅支持COPY,还支持自动解缩。

ADD 源路径 目标路径 	

源路径:是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url。如果是目录,只复制目录中的内容而非目录本身。如果是一个 URL ,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去,下载后的文件权限自动设置为 600。如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整。
目标路径:是container中的绝对路径

ADD 跟 COPY 的区别:
ADD 会自动解压如: gz, bz2 ,xz格式压缩包,其行为类似于"tar -x"命令。copy只是复制不解压。
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。

VOLUME 指定挂载点

在容器中创建一个可以从本地主机或其他容器挂载的挂载点,使容器中的一个目录具有持久化存储数据的功能。一般用来存放数据库和需要保持的数据等,一般会将宿主机上的目录挂载至VOLUME 指令指定的容器目录。该目录可以被容器本身使用,也可以共享给其他容器使用。我们知道容器使用的AUFS,这种文件系统不能持久化数据,当容器关闭后,所有的更改都会丢失。使用VOLUME即使容器后期被删除,此宿主机的目录仍会保留,从而实现容器数据的持久保存。

VOLUME <容器内路径>
VOLUME ["<容器内路径1>", "<容器内路径2>"...]

Dockerfile中的VOLUME实现的是匿名数据卷,无法指定宿主机路径和容器目录的挂载关系。通过docker rm -fv <容器ID> 可以删除容器的同时删除VOLUME指定的卷
在容器创建两个/data/、/data2的挂载点

[root@localhost ~]# vim /data/dockerfile/system/alpine/Dockerfile
FROM alpine
VOLUME [ "/data1","/data2" ]

[root@localhost ~]# cd /data/dockerfile/system/alpine/
[root@localhost alpine]# docker build -t alpine:v1 .
[root@localhost alpine]# docker run -it --rm b97a79beeb26 sh
/ # df
Filesystem           1K-blocks      Used Available Use% Mounted on
overlay              103079844   7213364  91572136   7% /
tmpfs                    65536         0     65536   0% /dev
/dev/vda1            103079844   7213364  91572136   7% /data1
/dev/vda1            103079844   7213364  91572136   7% /data2
/ # touch /data1/data1.txt
/ # touch /data2/data2.txt

哪个目录需要被我们的容器自管理卷去创建对应的挂载点
FROM base
VOLUME [“/tmp/data”]

WORKDIR 切换目录

WORKDIR 指定工作目录(相当于cd命令),各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会自行创建。为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录,当容器运行后,进入容器内WORKDIR指定的默认目录

WORKDIR /p1
WORKDIR p2
RUN vim a.txt

ONBUILD(在子镜像中执行):ONBUILD 指定的命令在构建镜像时并不执行,而是在它的子镜像中执行

ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src

很多人把 Dockerfile 等同于 Shell 脚本来书写,这样是错误的

RUN cd /app
RUN echo "hello" > world.txt

将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,因为在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误。

每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。

WORKDIR /app
RUN echo "hello" > world.txt

新建的文件在哪个路径
在这里插入图片描述

HEALTHCHECK 检查容器的健康性

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常。HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
命令的返回值决定了该次健康检查的成功与否:0:成功;1:失败;2:保留,不要使用这个值。

HEALTHCHECK [选项] CMD <命令> #设置检查容器健康状况的命令
HEALTHCHECK NONE #如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持下列选项:
--interval=<间隔> #两次健康检查的间隔,默认为 30 秒
--timeout=<时长> #健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默30--retries=<次数> #当连续失败指定次数后,则将容器状态视为 unhealthy,默认3次
--start-period=<FDURATION> #default: 0s
#检查结果返回值:
0 #success the container is healthy and ready for use
1 #unhealth the container is not working correctly
2 #reserved do not use this exit code


FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1

在没有 HEALTHCHECK 指令前,Docker 引擎只可以通过容器内主进程是否退出来判断容器是否状态异常。很多情况下这没问题,但是如果程序进入死锁状态,或者死循环状态,应用进程并不退出,但是该容器已经无法提供服务了。在 1.12 以前,Docker 不会检测到容器的这种状态,从而不会重新调度,导致可能会有部分容器已经无法提供服务了却还在接受用户请求。
HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。

当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器,初始状态会为 starting,在 HEALTHCHECK 指令检查成功后变为 healthy,如果连续一定次数失败,则会变为 unhealthy。

SHELL 指令

SHELL 指令可以指定 RUN、ENTRYPOINT、CMD 指令的 shell,Linux 中默认为 [“/bin/sh”, “-c”]

ls -l

#查看自己挂载的数据卷目录,这个卷和外部一定有一个同步的目录

docker inspect 容器id #Mounts会记录本地路径

测试:

在volume01下创建一个文件然后去本地路径进行查看是否有对应文件

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

docker hub 中99%镜像都是从这个基础镜像过来的FROM scratch,然后配置需要的软件和配置来进行构建

构建镜像docker build

docker build命令将dockerfile 构建为镜像

docker build 选项 镜像名:版本号 dockerfile文件所在路径
选项说明
-f指定要使用的Dockerfile路径
-t镜像的名字及标签,通常 name:tag 或者 name 格式,可以在一次构建中为一个镜像设置多个标签
[root@localhost ~]# vim Dockerfile 
FROM busybox
RUN touch test.txt
RUN /bin/bash -c echo "hello docker"

[root@localhost ~]# docker build -t busybox_image1 .
Sending build context to Docker daemon  110.5MB
Step 1/3 : FROM busybox
 ---> 16ea53ea7c65
Step 2/3 : RUN touch test.txt
 ---> Running in ef9239bfac7a				#运行这个容器
Removing intermediate container ef9239bfac7a
 ---> fd5b63a91a11							#镜像
Step 3/3 : RUN /bin/bash -c echon "hello docker"
 ---> Running in 51486b131126
/bin/sh: /bin/bash: not found
The command '/bin/sh -c /bin/bash -c echon "hello docker"' returned a non-zero code: 127

根据输出内容进入到已经构建好的镜像
[root@localhost ~]# docker run -it --rm fd5b63a91a11	
在容器内执行该条命令
/ # /bin/bash -c echon "hello docker"		
sh: /bin/bash: not found
/ # ls /bin/bash
ls: /bin/bash: No such file or directory
/ # ls /bin/sh
/bin/sh
/ # exit

对其dockerfile进行修改
[root@localhost ~]# vim Dockerfile 
FROM busybox
RUN touch test.txt
RUN /bin/sh -c echo "hello docker"

[root@localhost ~]# docker build -t busybox_image1 .
Sending build context to Docker daemon  110.5MB
Step 1/3 : FROM busybox
 ---> 16ea53ea7c65
Step 2/3 : RUN touch test.txt
 ---> Using cache
 ---> fd5b63a91a11
Step 3/3 : RUN /bin/sh -c echo "hello docker"
 ---> Running in d52195fe654a

Removing intermediate container d52195fe654a
 ---> 0eee31f41b17
Successfully built 0eee31f41b17
Successfully tagged busybox_image1:latest

最后这个镜像层的名就是这个最终镜像id名,每一层大小相加得到的是该镜像的大小

查看镜像的构建历史: docker history 镜像ID

可以查看镜像有多少层

制作基于基础镜像的系统base镜像

1、下载基础镜像并初始化系统

[root@localhost ~]# docker pull  centos:centos7		#下载基础镜像
[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
centos       centos7   eeb6ee3f44bd   9 months ago   204MB

2、制作基于基础镜像的系统Base镜像

[root@localhost ~]# cd /data/dockerfile/system/centos/
#创建Dockerfile,注意可以是dockerfile,但无语法着色功能
[root@localhost ~]# cat Dockerfile
FROM centos:centos7
RUN yum -y install wget \
	&& rm -f /etc/yum.repos.d/* \
	&& wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo \
	&& wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo \
	&& yum -y install vim-enhanced tcpdump lrzsz tree telnet bash-completion net-tools curl bzip2 lsof zip unzip nfs-utils gcc make gcc-c++ glibc glibc-devel pcre pcre-devel openssl openssl-devel systemd-devel zlib-devel \
	&& rm -f /etc/localtime \
	&& ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

[root@localhost centos]# docker build -t centos7-base:v1 .
[root@localhost centos]# docker images
REPOSITORY           TAG       IMAGE ID       CREATED              SIZE
centos7-base         v1        9ab99dd34a00   About a minute ago   619MB

[root@localhost centos]# docker image history centos7-base:v1
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
9ab99dd34a00   About a minute ago   /bin/sh -c yum -y install wget  && rm -f /et…   415MB     
eeb6ee3f44bd   9 months ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      9 months ago         /bin/sh -c #(nop)  LABEL org.label-schema.sc…   0B        
<missing>      9 months ago         /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d4…   204MB  

Dockerfile 制作基于Base镜像的 nginx 镜像

dockerfile在构建的时候会生成一个临时容器在执行dockerfile中的某些指令,当指令被执行完之后,会提交为一个镜像并删除这个临时容器

[root@localhost ~]# mkdir /data/dockerfile/web/nginx/
[root@localhost ~]# echo 'hello nginx' > /opt/dockerfile/nginx/index.html
[root@localhost ~]# cd /opt/dockerfile/nginx/
[root@localhost nginx]# vim Dockerfile 
FROM centos:7
RUN yum -y install epel-release 
RUN yum -y install nginx
COPY index.html /usr/share/nginx/html/
EXPOSE 80 443
CMD ["/usr/sbin/nginx","-g","daemon off;"]

[root@localhost nginx]# docker build -t centos_nginx .
[root@localhost nginx]# docker run -d -p 80:80 centos_nginx

在这里插入图片描述

Dockerfile 直接制作 nginx 镜像

[root@localhost ~]# vim /opt/dockerfile/nginx/Dockerfile 
FROM centos:centos7
RUN yum -y install gcc*  make pcre-devel zlib-devel openssl openssl-devel libxslt-devel gd gd-devel GeoIP GeoIP-devel pcre pcre-devel wget 
RUN wget http://nginx.org/download/nginx-1.18.0.tar.gz 
RUN mkdir -p /usr/local/src 
    && tar -zxf nginx-1.18.0.tar.gz -C /usr/local/src/ \
    && cd /usr/local/src/nginx-1.18.0 \
    && useradd -M -s /sbin/nologin nginx\
    && ./configure \ 
    --user=nginx \
    --group=nginx \
    --prefix=/usr/local/nginx \
    --with-file-aio \
    --with-http_ssl_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module \
    --with-http_image_filter_module \
    --with-http_geoip_module \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_auth_request_module \
    --with-http_random_index_module \
    --with-http_secure_link_module \
    --with-http_degradation_module \
    --with-http_stub_status_module \
    && make && make install \
    && echo "helllo nginx" > /usr/local/nginx/html/index.html\
    && rm -rf /nginx-1.18.0.tar.gz

#设置nginx环境变量
ENV PATH /usr/local/nginx/sbin:$PATH 
#暴露80端口
EXPOSE 80
#容器启动时执行nginx命令,启动nginx 将nginx主进程 pid为1 nginx一旦挂掉那么docker容器就会直接退出
ENTRYPOINT ["nginx"]       
#nginx命令参数,CMD和ENTRYPOINT一期使用时将作为ENTRYPOINT的参数
CMD ["-g","daemon off;"]

生成nginx镜像

[root@localhost ~]# docker build -t centos_nginx /opt/dockerfile/nginx/
[root@localhost ~]# docker images 
REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
centos_nginx     latest    9e5fdbd3f093   24 seconds ago   677MB
[root@localhost ~]# docker run  -d  -p 80:80 --name centos_nginx centos_nginx
2159a0f0166ed2b83b9d08a661e4d58b1ad0414a038111aa2353d02253795c20
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                               NAMES
2159a0f0166e   centos_nginx   "nginx -g 'daemon of…"   3 seconds ago   Up 2 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp   centos_nginx
[root@localhost ~]# docker save -o centos_nginx.tar centos_nginx		#导出镜像,可以通过docker load -i 导入镜像
[root@localhost ~]# ll
total 676028
-rw------- 1 root root 692210176 Oct 31 15:02 centos_nginx.tar
[root@localhost ~]# curl 127.0.0.1
helllo nginx

dockerfile构建tomcat镜像

基于官方提供的centos、debain、ubuntu、alpine等基础镜像构建JDK(Java环境),然后再基于自定义的JDK镜像构建出业务需要的tomcat镜像。

先基于官方提供的基础镜像,制作出安装了常用命令的自定义基础镜像,然后在基础镜像的基础之上,再制作JDK镜像、Tomcat镜像等。
在这里插入图片描述

在这里插入图片描述
构建Centos 系统基础镜像
先基于官方提供的 Centos 系统基础镜像,制作出安装了常用命令的自定义基础镜像。

[root@localhost ~]# mkdir -p /data/dockerfile/web/tomcat/system/centos
[root@localhost ~]# cd /data/dockerfile/web/tomcat/system/centos
[root@localhost ~]# vim Dockerfile
# 构建Docker镜像必须要有一个基础镜像,即父镜像(可从官网pull也可自己制作)
FROM centos:centos7.7.1908

# 指定维护者信息
LABEL maintainer="qianshuai <123456@qq.com>"

# 设置时区环境变量(ENV环境变量在Dockerfile中可以写多个)
# 这些指定的环境变量,后续可以被RUN指令使用,容器运行起来之后,也可以在容器中获取这些环境变量
ENV TZ "Asia/Shanghai"

# RUN 后面是要执行的命令,每执行一条指令就是一层,所以Dockerfile采用的是分层的技术
RUN yum -y install wget \
  && rm -f /etc/yum.repos.d/* \
  && wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/Centos-7.repo \
  && wget -P /etc/yum.repos.d/ http://mirrors.aliyun.com/repo/epel-7.repo \
  && yum -y install vim-enhanced tcpdump lrzsz tree telnet bash-completion net-tools wget bzip2 lsof \
     zip unzip nfs-utils gcc make gcc-c++ glibc glibcdevel pcre pcre-devel openssl openssl-devel systemd-devel zlib-devel \
  && yum clean all \

[root@localhost centos]# docker build -t centos_base:7.7 .
[root@localhost centos]# docker images
REPOSITORY                    TAG              IMAGE ID       CREATED              SIZE
centos_base                   7.7              09e0ec0ac916   About a minute ago   434MB

构建JDK 镜像
将JDK压缩包和profile文件上传到Dockerfile当前目录

[root@localhost ~]# mkdir -p /data/dockerfile/web/tomcat/jdk
#将CentOS7主机上的/etc/profile文件传到 Dockerfile 所在目录下
[root@localhost ~]# cp /etc/profile /data/dockerfile/web/tomcat/jdk/
#修改profile文件,加下面四行相关变量
[root@localhost ~]# vim /data/dockerfile/web/tomcat/jdk/profile 
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-1.el7_9.x86_64
export TOMCAT_HOME=/data/tomcat
export PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$TOMCAT_HOME/bin:$PATH
export CLASSPATH=.$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$JAVA_HOME/lib/tools.jar
[root@localhost ~]# cd /data/dockerfile/web/tomcat/jdk/
[root@localhost jdk]# vim Dockerfile 
FROM centos_base:7.7
RUN yum -y install java-1.8.0-openjdk-devel.x86_64
ADD profile /etc/profile
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-1.el7_9.x86_64
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/:$JAVA_HOME/jre/lib/
ENV PATH $PATH:$JAVA_HOME/bin

[root@localhost jdk]# docker build -t cetos7_openjdk:1.8 .
[root@localhost jdk]# docker run -it --rm cetos7_openjdk:1.8 bash		#从镜像启动容器测试
[root@c0c1ac256b1e /]# java -version
openjdk version "1.8.0_312"

通过docker 进入,不会加载/etc/profile?因为不是通过ssh终端进入的,所以不会加载?
在这里插入图片描述

构建tomcat镜像
基于自定义的 JDK 基础镜像构建出通用的自定义 Tomcat 基础镜像,此镜像后期会被多个业务的多个服务共同引用(相同的JDK 版本和Tomcat 版本)

[root@localhost ~]# mkdir -p /data/dockerfile/web/tomcat/tomcat-base-8.5.50
[root@localhost ~]# cd /data/dockerfile/web/tomcat/tomcat-base-8.5.50
[root@localhost tomcat-base-8.5.50]# vim Dockerfile 
FROM cetos7_openjdk:1.8
ENV TZ "Asia/Shanghai"
ENV LANG en_US.UTF-8
ENV TERM xterm
ENV TOMCAT_MAJOR_VERSION 8
ENV TOMCAT_MINOR_VERSION 8.5.50
ENV CATALINA_HOME /data/tomcat/tomcat
ENV APP_DIR ${CATALINA_HOME}/webapps
RUN mkdir -p /data/tomcat
ADD apache-tomcat-8.5.50.tar.gz /data/tomcat
RUN ln -s /data/tomcat/apache-tomcat-8.5.50 /data/tomcat/tomcat		#后期版本修改不影响路径

[root@localhost tomcat-base-8.5.50]# ls
apache-tomcat-8.5.50.tar.gz  Dockerfile
[root@localhost tomcat-base-8.5.50]# docker build -t tomcat_base:v8.5.50 .
[root@localhost tomcat-base-8.5.50]# docker run -it --rm -p 8080:8080 tomcat_base:v8.5.50 bash
[root@b2ebcf5f4a1a /]# /data/tomcat/tomcat/bin/catalina.sh start
[root@d20be50a9e7c /]# netstat -lntp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.1:8005          0.0.0.0:*               LISTEN      27/java             
tcp        0      0 0.0.0.0:8009            0.0.0.0:*               LISTEN      27/java             
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      27/java 

浏览器发现可以正常访问
在这里插入图片描述

构建业务镜像

创建tomcat-app1和tomcat-app2两个目录,代表两个不同的tomcat业务
[root@localhost ~]# mkdir -p /data/dockerfile/web/tomcat/tomcat-app{1,2}

haproxy

[root@localhost ~]# mkdir -p /opt/dockerfile/haproxy_centos 
[root@localhost ~]# vim /opt/dockerfile/haproxy_centos/Dockerfile
FROM centos:7
RUN yum  -y install  vim iotop bc gcc gcc-c++ glibc glibc-devel pcre \
pcre-devel openssl  openssl-devel zip unzip zlib-devel  net-tools \
lrzsz tree ntpdate telnet lsof tcpdump wget libevent libevent-devel \
bc systemd-devel bash-completion traceroute

RUN yum -y install haproxy

在这里插入图片描述

docker网络

Docker服务安装完成之后,默认在宿主机会生成一个名称为docker0的网卡,其IP地址都是172.17.0.1/16,并且会生成三种不能类型的网络:bridge、host、null

常用命令

docker network help 查看网络帮助命令
connect 将一个容器连接到一个网络
create 创建一个网络
disconnect 从网络断开一个容器
inspect 在一个或多个网络上显示详细信息
ls 网络列表
prune 删除所有未使用的网络
rm 删除一个或多个网络

原生网络

[root@localhost ~]# docker network ls		#查看当前网络信息
NETWORK ID     NAME      DRIVER    SCOPE
17d918a2dbe6   bridge    bridge    local
6f3e5c2318fd   host      host      local
ceebdd14662d   none      null      local

bridge模式

bridge模式:docker的默认网络模式(Docker 安装时会创建一个命名为 docker0 的 linux bridge。如果不指定--network,创建的容器默认都会挂到 docker0 上),在docker容器启动时自动配置好网络信息,同一宿主机的所有容器都在一个网络下,利用宿主机的网卡进行彼此间通信,所以会造成资源消耗、网络效率会低。

网络通讯必须满足:一个网段、一个广播域、一个vlan中
#interfaces为空代表网卡没有连接网络
[root@localhost ~]# brctl show
bridge name	bridge id		    STP enabled	interfaces
docker0		8000.02424d393dd1	no	

[root@localhost ~]# ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
        inet6 fe80::42:4dff:fe39:3dd1  prefixlen 64  scopeid 0x20<link>
        ether 02:42:4d:39:3d:d1  txqueuelen 0  (Ethernet)
        RX packets 7152  bytes 437507 (427.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7270  bytes 9564235 (9.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

#运行一个容器,当没有指定网络的时候会发现默认有一块网卡连接到网络上了,每启动一个容器默认连接的都是docker0        
[root@localhost ~]# docker run -d --name=nginx nginx
509ecfcb851c16e450d077b0f500d09b152c542a3e1d4dc50b2f8dabd0176d89

[root@localhost ~]# brctl show		#可以发现docker0其实就是一个网桥
bridge name	bridge id		STP enabled	interfaces
docker0		8000.02424d393dd1	no		veth5abf919

[root@localhost ~]# docker run -it --rm --network container:nginx busybox sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
70: eth0@if71: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
# ip为172.18.0.2,
# 70: eth0@if71:本地的eth0网卡(也就是70)连接到if71这块网卡上
/ # exit
[root@localhost ~]# ip a		发现多了块网卡,网卡被分为两半,一半在宿主机,一半在容器。
71: veth5abf919@if70: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default 
    link/ether ba:47:13:a5:34:cb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::b847:13ff:fea5:34cb/64 scope link 
       valid_lft forever preferred_lft forever
[root@localhost ~]# brctl show
bridge name	bridge id		STP enabled	interfaces
docker0		8000.02424d393dd1	no		veth5abf919
[root@localhost ~]# docker network inspect bridge 
...
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",	
                    "Gateway": "172.18.0.1"		#docker0充当了这块网卡的网关地址
                }
            ]
...

跨网段路由转发

#自定义一个网络mynet
[root@localhost ~]# docker network create --driver bridge mynet
52c5712b9d4368dc63eb5d5c2e10514b347d4036424e67675940faec96383fee
[root@localhost ~]# docker network ls		#发现本机多了一个mynet,驱动器为bridge类型,只在本机生效
NETWORK ID     NAME      DRIVER    SCOPE
17d918a2dbe6   bridge    bridge    local
6f3e5c2318fd   host      host      local
52c5712b9d43   mynet     bridge    local
ceebdd14662d   none      null      local

[root@localhost ~]# docker network inspect mynet 
...
            "Config": [
                {
                    "Subnet": "172.19.0.0/16",
                    "Gateway": "172.19.0.1"
                }
            ]
ip地址可以通过docker network create --driver bridge --subnet ip/子网掩码 --gateway 网关地址 网关网卡名(自己定义)
...

host模式

容器使用宿主机的ip地址进行通信,容器和宿主机共享网络,性能强,但是端口无法隔离。

#宿主机有个docker0网络,表示容器网卡,enth0 代表物理机当前网卡
[root@localhost ~]# ip a
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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:1b:60:9d brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.13/20 brd 172.17.15.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe1b:609d/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:4d:39:3d:d1 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:4dff:fe39:3dd1/64 scope link 
       valid_lft forever preferred_lft forever

#发现与物理机的网络一样
[root@localhost ~]# docker run -it --network host busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
    link/ether 52:54:00:1b:60:9d brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.13/20 brd 172.17.15.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::5054:ff:fe1b:609d/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue 
    link/ether 02:42:4d:39:3d:d1 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:4dff:fe39:3dd1/64 scope link 
       valid_lft forever preferred_lft forever
/ # ping -c1 www.baidu.com		#可以与外网通讯
PING www.baidu.com (112.80.248.76): 56 data bytes
64 bytes from 112.80.248.76: seq=0 ttl=52 time=10.624 ms

none模式

这种模式最纯粹,不会帮你做任何网络的配置,可以最大限度的定制化。不提供网络服务、容器启动后无网络连接。 隔离安全性比较高,所以无法与外网通讯。

#查看到网络内部信息,发现是空的
[root@localhost ~]# docker network inspect none 		
[
    {
        "Name": "none",
        "Id": "ceebdd14662d060547db49a41e117ae6007406e48eb3946c175c970d57f258cc",
        "Created": "2021-10-19T10:34:41.757524004+08:00",
        "Scope": "local",
        "Driver": "null",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": []
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

[root@localhost ~]# docker run -it --network none busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
/ # ping www.baidu.com				#隔离安全性比较高,所以无法与外网通讯
ping: bad address 'www.baidu.com'

joined

一种访问方式,让两个容器使用相同的网络配置

[root@localhost ~]# docker run -itd --name nginx nginx
a6c974a531b272eefd79737cce2a9090a3179ca74c96a9aa8b3f6b9385c5836d
#进到容器发现好多命令无法使用
[root@localhost ~]# docker exec -it nginx bash
root@a6c974a531b2:/# ip a
bash: ip: command not found
root@a6c974a531b2:/# yum -y install net-tools
bash: yum: command not found

#连接到nginx上运行一个新的镜像
[root@localhost ~]# docker run -it --network container:nginx busybox
WARNING: IPv4 forwarding is disabled. Networking will not work.
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue 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
68: eth0@if69: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
# 68: eth0@if39:本地的eth0网卡(也就是68)连接到if39这块网卡上

container模式

新创建的容器使用已创建的容器网络,类似一个局域网,容器和容器共享网络

overlay

跨主机通讯,容器彼此不在同一个网络,而且能互相通行

flannel

在这里插入图片描述

本机安装完docker后,会出现docker0网桥(可以理解为交换机)

 
容器与容器之间:通过docker0网桥进行通信
容器与外部:
	容器访问外部:防火墙snat规则
	外部访问容器:docker run –d –p 指定端口(dnat规则)

Docker进程网络修改,修改Docker网络进程,docker所有容器网路也都将被修改,

物理机docker0代表容器网卡,ens33代表物理机网卡,

[root@locahost ~]# docker run -it -p 8080:8080 tomcat
Tomcat默认端口8080
主机端口:外部访问就通过该自己设置的端口访问
容器端口:docker内部8080端口的tomcat
比如网页访问:ip:自己设置的主机端口

本机0.0.0.0代表本机,本机上的端口映射到容器的80

容器之间互联

通过容器名称互联

即在同一个宿主机上的容器之间可以通过自定义的容器名称相互访问,比如一个业务前端静态页面是使用nginx,动态页面使用的是tomcat。容器在启动的时候其内部IP地址是 DHCP 随机分配的,所以如果通过内部访问的话,自定义名称是相对比较固定的,因此比较适用于此场景。

[root@localhost ~]# docker run -it -d --name tomcat -p 8080:8080 tomcat 		#创建一个tomcat容器
[root@localhost ~]# docker run -it -d --name nginx -p 80:80 --link tomcat nginx		#使用--link链接到tomcat容器

[root@localhost ~]# docker exec -it tomcat bash
root@b41020c6416c:/usr/local/tomcat# 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.18.0.3	b41020c6416c

[root@localhost ~]# docker exec -it nginx bash
root@eac130d3f094:/# 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.18.0.3	tomcat b41020c6416c			#发现将tomcat的ip地址、容器名、容器ID加了进来
172.18.0.4	eac130d3f094
root@eac130d3f094:/# apt update		
root@eac130d3f094:/# apt install iputils-ping			#安装ping命令
root@eac130d3f094:/# ping -c1 tomcat					#ping tomcat容器发现可以ping通
PING tomcat (172.18.0.3) 56(84) bytes of data.
64 bytes from tomcat (172.18.0.3): icmp_seq=1 ttl=64 time=0.089 ms
这里的ip会随容器的删除和重建而来回变化

访问nginx的时候会调用tomcat
root@eac130d3f094:/# apt install vim

这里使用的是容器名称,因为在/etc/hosts下,容器id会随着容器创建而发生变化

/usr/sbin/nginx -s reload

root@eac130d3f094:~# vim /etc/nginx/conf.d/default.conf		#增加以下内容
	location /tomcat {
		proxy_pass http://tomcat:8080,
	}

root@b41020c6416c:/usr/local/tomcat# mv webapps.dist/ webapps
用户访问宿主机80端口,通过iptables规则转发至nginx容器80端口,nginx基于location判断转发至tomcat,

通过自定义容器别名互联

通过自定义的容器名称互联,后期可能会发生变化,那么一旦容器名称发生变化,程序与程序之间也要随之发生变化,比如程序通过容器名称进行服务调用,但是容器名称发生变化之后再使用之前的名称肯定是无法成功调用,如果每次都进行更改的话又比较麻烦,因此可以使用自定义别名的方式解决。即容器名称可以随意更改,只要不更改别名即可。

docker run -it -d --name 新容器名称 --link 目标容器名称:自定义的名称 -p 本地端口:容器端口 镜像名称 shell命令

[root@localhost ~]# docker run -it -d -p 81:80 --name nginx2 --link tomcat:tomcat_alias nginx
[root@localhost ~]# docker exec -it nginx2 bash	
root@7eb562f00a17:/# 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.18.0.3	tomcat_alias b41020c6416c tomcat		#可以发现别名加了进来,可以通过别名进行访问
172.18.0.5	7eb562f00a17

root@7eb562f00a17:/# ping -c1 tomcat_alias
PING tomcat_alias (172.18.0.3) 56(84) bytes of data.
64 bytes from tomcat_alias (172.18.0.3): icmp_seq=1 ttl=64 time=0.075 ms
/usr/sbin/nginx -s reload

root@eac130d3f094:~# vim /etc/nginx/conf.d/default.conf		#增加以下内容,使用别名
	location /tomcat {
		proxy_pass http://tomcat_alias:8080,
	}

进程被嘎了就直接退了
在这里插入图片描述

存储

容器的分层

查看指定容器数据分层

[root@localhost ~]# docker run -d --name debian debian
e0b1ea7e1f183ebfe83b15b6b34a043d4f906df089e2943a35a9e7394eab1669
[root@localhost ~]# docker inspect debian		#查看指定容器数据分层
...
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515-init/diff:/var/lib/docker/overlay2/beb0d1236ac6d53fe057fbe8990d31a1f62698a8659f106e2fb8a23bfe370c4b/diff",
                "MergedDir": "/var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515/merged",
                "UpperDir": "/var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515/diff",
                "WorkDir": "/var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515/work"
            },
            "Name": "overlay2"
        },
...
容器的数据分层目录说明
LowerDirimage镜像层本身,只读
MergedDir容器的文件系统,使用联合文件系统将 lowerdir 和 mergedir 合并给容器使用
UpperDir容器的上层,可读写层。容器新产生数据存放的路径
WorkDir容器在宿主机的工作目录
[root@localhost ~]# tree  /var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515/
/var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515/
├── diff	#镜像层的内容则存放在diff目录
├── link	#每个镜像层目录中包含了一个文件link,文件内容则是当前层对应的短标识符,
├── lower
└── work
    └── work

3 directories, 2 files

#删除容器后,所有容器数据目录都随之而删除
[root@localhost ~]# docker rm -f e0b1ea7e1f18
e0b1ea7e1f18
[root@localhost ~]# ls /var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515
ls: 无法访问/var/lib/docker/overlay2/a4f2cbf9f36fe6dcd9d7df136a5c7c54f2dd01eb5e136314a26f468deefd9515: 没有那个文件或目录

当删除容器的时候里面的数据也会被删除,所以容器中的数据需要持久化

有状态协议就是就通信双方要记住双方,并且共享一些信息。而无状态协议的通信每次都是独立的,与上一次的通信没什么关系。
"状态”可以理解为“记忆”,有状态对应有记忆,无状态对应无记忆
下图中左侧是无状态的http请求服务,右侧为有状态。下层为不需要存储的服务,上层为需要存储的部分服务
在这里插入图片描述

容器数据持久化

如果要将写入到容器的数据永久保存,则需要将容器中的数据保存到宿主机的指定日录,目前Docker的数据类型分为两种:
1、数据卷(data volume),数据卷类似于挂载的一块磁盘,数据容器是将数据保存在一个容器上。
2、数据卷容器,数据卷容器是将宿主机的目录挂载至一个专门的数据卷容器,然后让其他容器通过数据卷容器读写宿主机的数据。
在这里插入图片描述

方式一:数据卷
将容器的/data挂载到宿主机中的/data,容器在/data产生的数据都会持久化保存在宿主机/data路径下,当容器被删除,数据还会保存在宿主机上。

方式二:数据卷容器
只提供数据存储,比如图中mysql容器的数据挂载到数据卷容器中的/data中,数据卷容器/data挂载到宿主机的/data目录,数据卷容器不会运行任何服务
在这里插入图片描述

#运行容器默认该路径下为空 
[root@localhost ~]# ll /var/lib/docker/overlay2/1e5c4c77fdd2a02400df2e1733b5066f5b106043357e4441850b1158bfdf0375/diff
total 0
[root@localhost ~]# docker exec -it debian bash		#进入容器生成数据
root@719a1bf2a449:/# echo "hello docker" > /opt/hello_docker.txt
root@719a1bf2a449:/# exit
[root@localhost ~]# ll /var/lib/docker/overlay2/1e5c4c77fdd2a02400df2e1733b5066f5b106043357e4441850b1158bfdf0375/diff/
total 8
drwxr-xr-x 2 root root 4096 Nov  7 10:46 opt
drwx------ 2 root root 4096 Nov  7 10:46 root
[root@localhost ~]# cat /var/lib/docker/overlay2/1e5c4c77fdd2a02400df2e1733b5066f5b106043357e4441850b1158bfdf0375/diff/opt/hello_docker.txt 
hello docker


[root@localhost ~]# ll /var/lib/docker/overlay2/1e5c4c77fdd2a02400df2e1733b5066f5b106043357e4441850b1158bfdf0375/
total 20
drwxr-xr-x 4 root root 4096 Nov  7 10:43 diff
-rw-r--r-- 1 root root   26 Nov  7 10:43 link
-rw-r--r-- 1 root root   57 Nov  7 10:43 lower
drwxr-xr-x 1 root root 4096 Nov  7 10:43 merged
drwx------ 3 root root 4096 Nov  7 10:43 work

默认类型为bind

tmpfs 把容器内的数据存放到宿主机的内存当中
bind mount 把宿主内的数据挂载到容器中
managed volume 把容器内的数据持久化到宿主机中
volume container 提供卷

tmpfs

指定容器内/app目录为tmpfs,/app目录中所有的内容都存在宿主机内存中
[root@localhost ~]# docker run -itd --name tmpfs --tmpfs /app busybox
7a1dffffb11a5f1273607a0429c2075cbc63ca4f6c029526f6f820efaef0fc24
[root@localhost ~]# docker exec -it tmpfs sh
/ # ls 
app   bin   dev   etc   home  proc  root  sys   tmp   usr   var

bind

[root@localhost ~]# mkdir test
[root@localhost ~]# echo "hello docker" > test/test.txt
[root@localhost ~]# docker run -itd --volume /root/test:/test --name=busybox busybox
598d83f1fd884a5edca7bef152cf8724813f9f302a66ad5237c24ed5c24c36da
[root@localhost ~]# docker exec -it busybox sh
/ # cat test/test.txt 
hello docker
/ # echo "Hello Docker" >> test/test.txt 
/ # exit
[root@localhost ~]# cat /root/test/test.txt 
hello docker
Hello Docker

[root@localhost ~]# docker inspect busybox
...
        "Mounts": [
            {
                "Type": "bind",				#类型为bind
                "Source": "/root/test",		#挂载的路径
                "Destination": "/test",		#挂载到容器的目录
                "Mode": "",					#权限
                "RW": true,					#
                "Propagation": "rprivate"	#传播方式,rprivate:递归所有目录都生效
            }
        ],

容器被删除,卷还在
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     test

...

在这里插入图片描述

数据卷

数据卷:数据卷实际上就是宿主机上的目录或者是文件,可以被直接 mount 到容器当中使用。实际生产环境中需要针对不同类型的服务、不同类型的数据存储要求做相应的规划,最终保证服务的可扩展性、稳定性以及数据的安全性。

数据卷默认可能会保存于 /var/lib/docker/volumes,不过一般不需要、也不应该访问这个位置。

数据卷的特点
1、数据卷是目录或者文件,并且可以在多个容器之间共同使用,实现容器之间共享和重用。对数据卷更改数据在所有容器里面会立即更新。
2、数据卷的数据可以持久保存,即使删除使用使用该容器卷的容器也不影响。
3、依赖于宿主机目录,宿主机出问题,上面容器会受影响,当宿主机较多时,不方便统一管理
4、匿名和命名数据卷在容器启动时初始化,如果容器使用的镜像在挂载点包含了数据,会拷贝到新初始化的数据卷中

数据卷使用方法
启动容器时,可以指定使用数据卷实现容器数据的持久化,数据卷有三种

docker run 命令的以下格式可以实现数据卷

-v, --volume=[host-src:]container-dest[:<options>]

<options>
ro 从容器内对此数据卷是只读,不写此项默认为可读可写
rw 从容器内对此数据卷可读可写,此为默认值

1、指定宿主机目录或文件:指定宿主机的具体路径和容器路径的挂载关系,将宿主机目录挂载容器目录,两个目录都可自动创建

-v <宿主机绝对路径的目录或文件>:<容器目录或文件>[:ro]

2、匿名卷:不指定数据名称,只指定容器内目录路径充当挂载点,没有指定宿主机路径信息,宿主机自动生成/var/lib/docker/volumes/<卷ID>/_data目录,并挂载至容器指定路径

-v <容器内路径>

#示例:
docker run --name nginx -v /etc/nginx nginx

3、命名卷:指定数据卷的名称和容器路径的挂载关系,命名卷将固定的存放在/var/lib/docker/volumes/<卷名>/_data

-v <卷名>:<容器目录路径>

#可以通过以下命令事先创建,如可没有事先创建卷名,docker run时也会自动创建卷
docker volume create <卷名>

#示例:
docker run -d -p 80:80 --name nginx01 -v vol1:/usr/share/nginx/html nginx

docker rm 的 -v 选项可以删除容器时,同时删除相关联的匿名卷

关于匿名数据卷和命名数据卷
命名卷就是有名字的卷,使用 docker volume create <卷名> 形式创建并命名的卷
匿名卷就是没名字的卷,一般是 docker run -v /data 这种不指定卷名的时候所产生,或者 Dockerfile 里面的定义直接使用的。

命名卷在用过一次后,以后挂载容器的时候还可以使用,因为有名字可以指定。使用命名卷的好处是可以复用,其它容器可以通过这个命名数据卷的名字来指定挂载,共享其内容(不过要注意并发访问的竞争问题),所以一般需要保存的数据使用命名卷保存。

匿名卷则是随着容器建立而建立,随着容器消亡而淹没于卷列表中(对于 docker run 匿名卷不会被自动删除),因此匿名卷只存放无关紧要的临时数据,随着容器消亡,这些数据将失去存在的意义。

Dockerfile中指定VOLUME为匿名数据卷,其目的只是为了将某个路径确定为卷。按照最佳实践的要求,不应该在容器存储层内进行数据写入操作,所有写入应该使用卷。如果定制镜像的时候,就可以确定某些目录会发生频繁大量的读写操作,那么为了避免在运行时由于用户疏忽而忘记指定卷,导致容器发生存储层写入的问题,就可以在 Dockerfile 中使用 VOLUME 来指定某些目录为匿名卷。这样即使用户忘记了指定卷,也不会产生不良的后果。这个设置可以在运行时覆盖。通过 docker run 的 -v 参数或者 docker-compose.yml 的 volumes 指定。

比如 Dockerfile 中说 VOLUME /data,那么如果直接 docker run,其 /data 就会被挂载为匿名卷,向 /data 写入的操作不会写入到容器存储层,而是写入到了匿名卷中。但是如果运行时 docker run -v mydata:/data,这就覆盖了 /data 的挂载设置,要求将 /data 挂载到名为 mydata的命名卷中。所以说 Dockerfile 中的 VOLUME 实际上是一层保险,确保镜像运行可以更好的遵循最佳实践,不向容器存储层内进行写入操作。

查看数据卷的挂载关系:docker inspect --format=“{{.Mounts}}” <容器ID>
删除所有数据卷:docker volume rm docker volume ls -q

目录数据卷

1、在宿主机创建容器所使用的目录

[root@localhost ~]# mkdir -p /usr/share/nginx/html/
[root@localhost ~]# echo 'hello nginx' > /usr/share/nginx/html/index.html

2、启动两个容器nginx1和nginx2,分别测试能否访问到宿主机数据

[root@localhost ~]# docker run -d -p 81:80 -v /usr/share/nginx/html:/usr/share/nginx/html --name=nginx1 nginx
[root@localhost ~]# docker run -d -p 82:80 -v /usr/share/nginx/html:/usr/share/nginx/html --name=nginx2 nginx

在这里插入图片描述
多个容器共享一个数据卷,当其中一个容器修改了数据卷中的内容,另一个容器也将会被修改

[root@localhost ~]# docker exec -it nginx1 bash
root@dddb8a3f75df:/# echo 'hello docker' > /usr/share/nginx/html/index.html

浏览器访问测试
在这里插入图片描述
删除所有容器,数据并不会被删除,持久化在宿主机内,当在启动新的容器,该内容会被加载使用

[root@localhost ~]# docker rm -f `docker ps -qa`	
[root@localhost ~]# cat  /usr/share/nginx/html/index.html
hello docker	
[root@localhost ~]# docker run -d -p 80:80 -v /usr/share/nginx/html:/usr/share/nginx/html nginx

在这里插入图片描述

给容器设置只读权限

#给容器设置只读权限,rw代表可读可写,不写默认是rw
[root@localhost ~]# docker run -d -p 80:80 -v /usr/share/nginx/html:/usr/share/nginx/html:ro --name=nginx nginx
[root@localhost ~]# docker exec -it nginx bash
root@a1ac371e1ac2:/# echo 'hello nginx' >> /usr/share/nginx/html/index.html 
bash: /usr/share/nginx/html/index.html: Read-only file system
数据无法写入,没有权限,只读
root@a1ac371e1ac2:/# cat /usr/share/nginx/html/index.html
hello docker

将日志目录挂载宿主机

[root@localhost ~]# docker run -d -p 80:80 -v /usr/share/nginx/html:/usr/share/nginx/html -v /var/log/nginx:/var/log/nginx --name=nginx nginx
[root@localhost ~]# ls /var/log/nginx/
access.log  error.log
[root@localhost ~]# cat /var/log/nginx/access.log 
[root@localhost ~]# curl 1.116.194.16
hello docker
[root@localhost ~]# cat /var/log/nginx/access.log 
1.116.194.16 - - [07/Nov/2021:11:20:49 +0000] "GET / HTTP/1.1" 200 13 "-" "curl/7.29.0" "-"

数据卷使用场景:日志输出、数据输出、静态web页面、应用配置文件、多容器间目录或文件共享

文件数据卷

文件挂载用于很少更改文件内容的场景,比如: nginx 的配置文件、tomcat的配置文件等。

1、在宿主机创建容器所使用的文件

[root@localhost ~]# mkdir -p /usr/share/nginx/html/
[root@localhost ~]# echo 'hello nginx' > /usr/share/nginx/html/index.html

2、引用文件数据卷启动容器
同时挂载可读可写方式的目录数据卷和只读方式的文件数据卷,实现三个数据卷的挂载,数据,日志和启动脚本

[root@localhost ~]# docker run -d -v /usr/share/nginx/html/index.html:/usr/share/nginx/html/index.html:ro -v /data/nginx_data:/data -v /data/nginx_logs:/var/log/nginx -p 80:80 --name=nginx nginx

3、验证容器可以访问

[root@localhost ~]# curl 127.0.0.1
hello nginx
[root@localhost ~]# ls /data/nginx_*
/data/nginx_data:

/data/nginx_logs:
access.log  error.log

4、直接修改宿主机的数据

#宿主机修改目录数据卷
[root@localhost ~]# echo 'Hello Nginx' > /usr/share/nginx/html/index.html
[root@localhost ~]# curl 127.0.0.1
Hello Nginx

5、进入容器修改数据

[root@localhost ~]# docker exec -it nginx bash
root@10274a94ffd3:/# echo 'hello nginx' > /usr/share/nginx/html/index.html	#文件数据卷上的文件为只读
bash: /usr/share/nginx/html/index.html: Read-only file system
root@10274a94ffd3:/# echo 'hello nginx' > /data/test.txt      		#目录数据卷可读可写            
root@10274a94ffd3:/# cat /data/test.txt 
hello nginx

匿名数据卷

1、利用匿名数据卷创建容器

[root@localhost ~]# docker run -d -p 80:80 --name nginx01 -v /usr/share/nginx/html nginx
f1b4708adbcc7bb194396d7582350afb94bd3807ba0444822342a7b3bdb408a4
#查看自动生成的匿名数据卷
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4

#查看匿名数据卷的详细信息
[root@localhost ~]# docker volume inspect d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4
[
    {
        "CreatedAt": "2022-06-26T20:24:30+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4/_data",
        "Name": "d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4",
        "Options": null,
        "Scope": "local"
    }
]

[root@localhost ~]# docker inspect --format="{{.Mounts}}" nginx01
[{volume d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4 /var/lib/docker/volumes/d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4/_data /usr/share/nginx/html local  true }]

#查看匿名数据卷的文件
[root@localhost ~]# ls /var/lib/docker/volumes/d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4/_data 
50x.html  index.html

#修改宿主机中匿名数据卷的文件
[root@localhost ~]# echo 'hello docker' > /var/lib/docker/volumes/d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4/_data/index.html 
[root@localhost ~]# curl 127.0.0.1:80
hello docker
#删除容器不会删除匿名数据卷
[root@localhost ~]# docker rm -f nginx01
nginx01
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4

#创建一个新的匿名数据卷容器,会生成新的数据卷
[root@localhost ~]# docker run -d -p 80:80 --name nginx02 -v /usr/share/nginx/html nginx
bcc6785e989fda8ed077999609a2998fb5797504e65c27b014cf11bb76f3af99

[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     451faf5fc41b15f57c5b7d4903246dcbca53ca0b382f8f44a689cc1b272c1645
local     d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4

[root@localhost ~]# docker rm -fv nginx02	#删除容器并删除匿名数据卷
nginx02
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     d28892b33a0748e017647161f99781dd234496f2ebd44add572910d626d51bd4

命名数据卷

1、创建命名数据卷

[root@localhost ~]# docker volume create volume_data1
volume_data1

2、查看命名数据卷

[root@localhost ~]# docker volume ls
DRIVER VOLUME NAME
local  volume_data1

3、查看命名数据卷详解信息

[root@localhost ~]# docker inspect volume_data1
[
    {
        "CreatedAt": "2020-07-21T18:36:45+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/volume_data1/_data",
        "Name": "volume_data1",
        "Options": {},
        "Scope": "local"
    }
]

4、使用命名数据卷创建容器

[root@localhost ~]# docker run -d -p 8001:80 --name nginx01 -v volume_data1:/usr/share/nginx/html nginx
29e3d5c3ce0a8bb553d005167bf55e3c633fae742d2af68988ef5276a5ff6f74

5、打开浏览器访问测试
在这里插入图片描述
查看数据卷的挂载关系

[root@localhost ~]# docker inspect --format="{{.Mounts}}" nginx01
[{volume volume_data1 /var/lib/docker/volumes/volume_data1/_data /usr/share/nginx/html local z true }]

#查看命名数据卷的文件
[root@localhost ~]# ls /var/lib/docker/volumes/volume_data1/_data/
50x.html  index.html

#修改宿主机命名数据卷的文件
[root@localhost ~]# echo "hello docker nginx" > /var/lib/docker/volumes/volume_data1/_data/index.html 
[root@localhost ~]# curl 127.0.0.1:8001
hello docker nginx

#利用现在的命名数据卷再创建新容器,可以和原有容器共享同一个命名数据卷的数据
[root@localhost ~]# docker run -d -p 8002:80 --name nginx02 -v volume_data1:/usr/share/nginx/html nginx
fb8739438992b7797a3f23dc495d2fd43f513fd793114848171f376c4325675e
[root@localhost ~]# curl 127.0.0.1:8002
hello docker nginx

创建容器时自动创建命名数据卷

#创建容器自动创建命名数据卷
[root@localhost ~]# docker run -d -p 8003:80 --name nginx03 -v volume_data2:/usr/share/nginx/html nginx
711943b04e70a157599a8558d7b2ef51d79cd4fe1df99d96d9b937cc91c8d396
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     volume_data2
local     volume_data1

删除数据卷

#删除指定的命名数据卷
[root@localhost ~]# docker volume rm volume_data1 
volume_data1
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME
local     olume_data2

[root@localhost ~]# docker rm -f `docker ps -qa`
#清理全部不再使用的卷
[root@localhost ~]# docker volume prune -f
[root@localhost ~]# docker volume ls
DRIVER    VOLUME NAME

数据卷容器

不提供应用程序的访问功能

数据卷的意义

在这里插入图片描述

作用:数据持久化,关闭并重启容器,数据不受影响,只有删除容器才会全部丢失

​ 外部机器和容器间接交换数据(修改容器数据宿主机数据同步)

​ 容器之间数据交换

一个数据卷可以挂载多个容器,一个容器也可以被挂载多个数据卷

卷:宿主机上的某个目录与容器中的某个目录互相绑定,实现数据同步,访问容器中的某个目录相当于访问宿主机的某个目录,当容器删除之后宿主机的目录默认不删除,里面的数据就保留了下来

数据卷就是宿主机中的一个目录或文件

把docker容器删除后,管理卷并不会删除(volumes)

Volume可以在运行容器时即完成创建与绑定操作(宿主机目录与容器目录),不在需要人为运行添加-v选项

Volume的初衷就是数据持久化(进程会把文件系统分为两部分,一个是根部分一个是数据存储部分,根部分与容器的可写层与之关联,数据层与我们的卷相互关联。容器删除后数据目录还在卷中)

在dockerfile中加入volume 路径(该路径为容器路径,没有自动创建,自定义容器目录)

数据卷的类型

Bind mount volume 绑定卷,人为操作,可以与宿主机操作系统上任何目录进行绑定

Docker-managed volume docker管理卷,转换为容器后docker自己管理,自己会创建对应的目录,自己会进行挂载,一般存在于/var/lib/docker/volumes(docker自定义存储卷的根目录)

在容器中使用volume

Docker自管理卷:

dockerfile自定义容器管理卷后,运行为容器会在与之目录进行创建挂载,修改容器数据宿主机数据同步,删除容器后宿主机目录还在。如果删除容器想要把对应的宿主机目录进行删除需要加上-v选项(docker -fv 容器名)

绑定卷:

Docker run -it -v 宿主机目录:容器内目录 -d 镜像名

使用-v选项将宿主机目录与容器内目录进行绑定

联合卷

启动该容器数据卷加载其它容器的数据卷,如果想要使用该命令,必须在dockerfile中自定义volume,当运行容器加上-volumes-from后,会把自定义dockerfile路径指定到指定容器上,可以不借助宿主机,容器与容器之间完成数据共享

Docker run -it -volumes-from 指定运行的容器名 -d 镜像名

如果dockerfile自定义了volume,运行容器又加上-v选项,绑定卷会生效,优先级大有容器管理卷

可以通过宿主机数据卷实现多容器数据共享

docker run -f -v 容器名

删除容器时数据卷也会被删除

[root@locahost ~]# docker run -it –-name a 镜像名:版本

\#先启动一个父容器

[root@locahost ~]# docker run -it –name b -–volumes-from a -d 镜像名:版本

继承a容器,完成容器内数据共享

存储驱动

Docker 存储驱动 ( storage driver ) 是 Docker 的核心组件,它是 Docker 实现分成镜像的基础

device mapper ( DM ):性能和稳定性存在问题,不推荐生产环境使用

btrfs:社区实现了 btrfs driver,稳定性和性能存在问题

overlayfs**:内核** 3.18 overlayfs 进入主线,性能和稳定性优异,第一选择

docker info 命令 : 查看 Storage Driver: overlayfs

使用overlay存储实现数据卷写时复制原理

数据卷容器

在这里插入图片描述
在Dockerfile中创建的是匿名数据卷,无法直接实现多个容器之间共享数据。数据卷容器最大的功能是可以让数据在多个docker容器之间共享
如下图所示:即可以让B容器访问A容器的内容,而容器C也可以访问A容器的内容,即可以实现A,B,C三个容器之间的数据读写共享。
在这里插入图片描述
先要创建一个后台运行的 Server 容器用于提供数据卷,这个卷可以为其它容器提供数据存储服务,其它使用此卷的容器作为client端 ,但此方法并不常使用。因为依赖一个 Server 的容器,所以此 Server 容器出了问题,其它 Client 容器都会受影响

启动容器时,指定使用数据卷容器 docker run 命令的以下选项可以实现数据卷容器,格式如下:

--volumes-from <数据卷容器>

1、创建一个数据卷容器 Server,创建后可以无需启动,并挂载宿主机的数据目录,数据卷容器一般无需映射端口

[root@localhost ~]# docker run -d --name volume-server -v /usr/share/nginx/html/index.html:/usr/share/nginx/html/index.html -v /data/nginx_data:/data -v /data/nginx_logs:/var/log/nginx nginx
26c9ae9480df35dc46417960422153dde67c7742425ecfd8270b6d45c6c7a5fb

2、启动多个数据卷容器 Client

[root@localhost ~]# docker run -d --name client1 --volumes-from volume-server -p 8001:80 nginx
1cc26fb363e004a1721db2c5e22763b5e37fb060e97df5bd194c78cb45a11cd3
[root@localhost ~]# docker run -d --name client2 --volumes-from volume-server -p 8002:80 nginx
be98ea1e249c88f8c81e20296da4358918c08fcf89634de5efefde4759c33881

[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS                                   NAMES
be98ea1e249c   nginx     "/docker-entrypoint.…"   15 seconds ago       Up 14 seconds       0.0.0.0:8002->80/tcp, :::8002->80/tcp   client2
1cc26fb363e0   nginx     "/docker-entrypoint.…"   38 seconds ago       Up 38 seconds       0.0.0.0:8001->80/tcp, :::8001->80/tcp   client1
26c9ae9480df   nginx     "/docker-entrypoint.…"   About a minute ago   Up About a minute   80/tcp                                  volume-server

3、验证访问

[root@localhost ~]# curl 127.0.0.1:8001
Hello Nginx
[root@localhost ~]# curl 127.0.0.1:8002
Hello Nginx

4、测试容器读写

#进入 Server 容器修改数据
[root@localhost ~]# docker exec -it volume-server bash
root@26c9ae9480df:/# echo 'test one' > /usr/share/nginx/html/index.html

#客户端访问测试
[root@localhost ~]# curl 127.0.0.1:8001
test one
[root@localhost ~]# curl 127.0.0.1:8002
test one

#进入 Client 容器修改数据
[root@localhost ~]# docker exec -it client1 bash
root@1cc26fb363e0:/# echo 'test two' > /usr/share/nginx/html/index.html
[root@localhost ~]# curl 127.0.0.1:8001
test two
[root@localhost ~]# curl 127.0.0.1:8002
test two

#在宿主机直接修改
[root@localhost ~]# echo 'test three' > /usr/share/nginx/html/index.html
[root@localhost ~]# curl 127.0.0.1:8001
test three
[root@localhost ~]# curl 127.0.0.1:8002
test three

5、删除数据卷容器后,旧的client 容器仍能访问,但无法再创建新的client容器

[root@localhost ~]# docker rm -fv volume-server
volume-server
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                   NAMES
be98ea1e249c   nginx     "/docker-entrypoint.…"   12 minutes ago   Up 12 minutes   0.0.0.0:8002->80/tcp, :::8002->80/tcp   client2
1cc26fb363e0   nginx     "/docker-entrypoint.…"   12 minutes ago   Up 12 minutes   0.0.0.0:8001->80/tcp, :::8001->80/tcp   client1

[root@localhost ~]# curl 127.0.0.1:8001
test three
[root@localhost ~]# curl 127.0.0.1:8002
test three

##数据卷源不存在无法创建
[root@localhost ~]# docker run -d --name client3 --volumes-from volume-server -p 8003:80 nginx
docker: Error response from daemon: No such container: volume-server.

6、重新创建容器卷 Server,重新创建容器卷容器后,还可继续创建新client 容器

[root@localhost ~]# docker run -d --name volume-server -v /usr/share/nginx/html/index.html:/usr/share/nginx/html/index.html -v /data/nginx_data:/data -v /data/nginx_logs:/var/log/nginx nginx
db027cffe29448b1d2687276df955de63afcb6a815371d4301e31b3ec11e69b0
[root@localhost ~]# docker run -d --name client3 --volumes-from volume-server -p 8003:80 nginx
721fa081493dd0c67d6f54b1bf36af9f6bc504c05ffb01eb533c55a99f51f576

在这里插入图片描述

在这里插入图片描述

资源限制

CGroup 是Control Groups的缩写,是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物力资源 (如cpu、内存、IO(数据存储目录一般是远程挂载,所以一般不限制磁盘IO)等等) 的机制。它将进程管理从 cpuset 中剥离出来。Docker每个不同的容器相当于每个不同的进程,Cgroup是容器资源限制的底层技术,如果不对容器做任何限制,容器能够占用当前系统能给容器提供的所有资源,有多少用多少。

默认情况下容器没有资源限制,容器可以使用主机内核调度程序允许的尽可能多的给定资源,Docker提供了控制容器可以限制容器使用多少内存或CPU的方法。可以使用docker run命令设置运行时配置标志。其中许多功能都要求宿主机的内核支持Linux功能,可以使用docker info命令检查,如果内核中禁用了某项功能,可能会在输出结尾处看到警告:WARNING:No swap limit support。可通过修改内核参数消除以上警告。

对于宿主机来说如果没有足够的内存来执行其他重要的系统任务,将会抛出OOM(Out of Memory Exception,内存溢出、内存泄漏、内存异常)。随后系统开始杀死进程以释放内存,凡是运行在宿主机的进程都有可能被kill掉,包括Dockerd和其它的应用程序,如果重要的系统进程被Kill会导致和该进程相关的服务全部宕机。
产生OOM异常时,Dockerd尝试通过调整Docker守护程序上的OOM优先级来减轻这些风险,以便它比系统上的其它进程更不可能被杀死,但是容器的OOM优先级未调整,这使得单个容器被杀死的可能性比Docker守护程序或其他系统进程被杀死的可能性更大,不推荐通过在守护程序或容器上手动设置 --oom-score-adj 为极端负数,或通过在容器上设置 --oom-kill-disable 绕过这些安全措施。

OOM优先级机制:linux会为每个进程算一个分数,最终它会将分数最高的进程kill

/proc/PID/oom_score_adj范围为-1000到1000,值越高越容易被宿主机kill掉,如果将该值设置为-1000,则进程永远不会被宿主机kernel killo
/proc/PID/oom_adj范围为-17到+15,取值越高越容易被干掉,如果是-17,则表示不能被kill,该设置参数的存在是为了和旧版本的Linux内核兼容
/proc/PID/oomscore这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime -start time)和oom-adj计算出的进程得分,消耗内存越多得分越高,越容易被宿主机kernel强制杀死

docker服务进程的OOM默认值

[root@localhost ~]# cat /proc/`pidof dockerd`/oom_adj
-8
[root@localhost ~]# cat /proc/`pidof dockerd`/oom_score
0
[root@localhost ~]# cat /proc/`pidof dockerd`/oom_score_adj
-500

容器内存资源限制

Docker可以强制执行硬性内存限制,即只允许容器使用给定的内存大小。Docker也可以执行非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了。如果容器未做内存使用限制,则该容器可以利用到系统内存最大空间,默认创建的容器没有做内存资源限制。

在docker启动参数中参数的值一般是内存大小,也就是一个正数,后面跟着内存单位b、k、m、g,分别对应 bytes、KB、MB、和 GB

内存限制方式说明
-m 或 --memory容器能使用的最大内存大小,最小值为 4m
--memory-swap容器能够使用的 swap 大小
--memory-swappiness设置容器使用交换分区的倾向性,值越高表示越倾向于使用swap分区,范围为 0-100 之间的值,代表允许 swap 出来的比例,0为能不用就不用,100为能用就用
--memory-reservation允许指定小于–m的软限制,当Docker检测到主机上的争用或内存不足时会激活该限制,如果使用--memory-reservation则必须将其设置为低于–m才能使其优先。因为它是软限制,所以不能保证容器不超过限制(比如硬限制设置为20mb,软限制为10mb,这个容器不能超过硬限制数,也就是不能超过20mb,可以超过软限制10mb)
--kernel-memory容器能够使用的 kernel memory 大小,最小值为 4m。由于内核内存与用户空间内存隔离,因此无法与用户空间内存直接交换,因此内核内存不足的容器可能会阻塞宿主主机资源,这会对主机和其他容器或者其他服务进程产生影响,因此不要设置内核内存大小
--oom-kill-disable          是否运行 OOM 的时候杀死容器。只有设置了 -m,才可以把这个选项设置为 false(这个容器不被OOM杀死),否则容器会耗尽主机内存,而且导致主机应用被杀死(一般不使用该选项)默认情况下,发生OOM时kernel会杀死容器内进程,但是可以使用--oom-kill-disable 参数,可以禁止OOM发生在指定的容器上,即仅在已设置-m/--memory选项的容器上禁用OOM,如果-m参数未配置,产生OOM时,主机为了释放内存还会杀死系统进程

内存资源限制验证

#测试镜像,stress-ng是一个压力测试工具,可以通过软件仓库进行安装,也提供了docker版本的容器
[root@localhost ~]# docker pull lorel/docker-stress-ng
#查看帮助信息
[root@localhost ~]# docker run -it --rm lorel/docker-stress-ng --help

内存大小硬限制
启动两个工作进程,每个工作进程最大允许使用内存128M,且宿主机不限制当前容器最大内存

#--vm 代表2个工作进程,--vm-bytes每个工作进程使用的内存大小,默认256MB
[root@localhost ~]# docker run -it --rm --name test_memory lorel/docker-stress-ng --vm 2 --vm-bytes 128M

#docker stats实时监控容器资源使用情况,可以发现内存将会被限制在此范围,可以发现2个进程消耗总内存存为 258.2MiB
[root@localhost ~]# docker stats			
CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O   PIDS
4fd3405955eb   test_memory   98.26%    258.2MiB / 1.795GiB   14.05%    656B / 0B   0B / 0B     5

#--vm 代表2个工作进程,--vm-bytes每个工作进程使用的内存大小,默认256MB
[root@localhost ~]# docker run -it --rm --name test_memory lorel/docker-stress-ng --vm 2
[root@localhost ~]# docker stats			#可以发现2个进程消耗总内存存为 514.2MiB
CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O   PIDS
b230ed467224   test_memory   98.82%    514.2MiB / 1.795GiB   27.98%    656B / 0B   0B / 0B     5

宿主机限制为1G,容器总进程不会超过1G,可以看到limit为限制的大小为1G
[root@localhost ~]# docker run -it --rm --name test_memory -m 1024m lorel/docker-stress-ng --vm 2 --vm-bytes 1024M
CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT   MEM %     NET I/O       BLOCK I/O         PIDS
654eeca59bb7   test_memory   183.18%   1024MiB / 1GiB      99.99%    1.09kB / 0B   3.84GB / 14.7GB   5

宿主机cgroup验证,宿主机基于cgroup对容器进行内存资源的大小限制 /sys/fs/cgroup/memory/docker/容器ID/memory.limit_in_bytes 单位字节
[root@localhost ~]# cat /sys/fs/cgroup/memory/docker/654eeca59bb761df9cb59e8bd903aee694a2ccce78e710817b6238a5f0c12f9c/memory.limit_in_bytes 
1073741824

可以通过echo命令进行修改内存限制的值,不推荐修改,2147483648字节为2G
[root@localhost ~]# echo 2147483648 > /sys/fs/cgroup/memory/docker/654eeca59bb761df9cb59e8bd903aee694a2ccce78e710817b6238a5f0c12f9c/memory.limit_in_bytes 
[root@localhost ~]# docker stats				#会发现limit数值发生变化
CONTAINER ID   NAME          CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O         PIDS
ab2f0bf280c5   test_memory   198.13%   1.806GiB / 1.779GiB   101.50%   648B / 0B   7.39GB / 24.6GB   5

软限制
必须先设置硬限制,才能设置软限制,软限制的值要小于硬限制

#软限制: --memory-reservation 
[root@localhost ~]# docker run -it --rm -m 256m --memory-reservation 128m --name test_memory lorel/docker-stress-ng --vm 2 --vm-bytes 256M
#这里是128M
[root@localhost ~]# cat /sys/fs/cgroup/memory/docker/6c2052b7d4eb3621f69ed90fd15840a4f7bf165c8a2abcbeea65ce55247a802e/memory.soft_limit_in_bytes 
134217728

#查看OOM机制,0代表不生效,1代表生效,容器不会被kill掉
[root@localhost ~]# cat /sys/fs/cgroup/memory/docker/6c2052b7d4eb3621f69ed90fd15840a4f7bf165c8a2abcbeea65ce55247a802e/memory.oom_control 
oom_kill_disable 0
under_oom 0
#关闭OOM机制
[root@localhost ~]# docker run -it --rm -m 256m --oom-kill-disable debian
[root@localhost ~]# cat /sys/fs/cgroup/memory/docker/6915c442921fffe922327a28cdd200b1140e49797946d8014fcf60d96a7a3adf/memory.oom_control 
oom_kill_disable 1		#1代表生效,容器不会被kill掉
under_oom 0

[root@localhost ~]# docker run -it --rm -m 256m debian	#验证不加kill-disable
[root@localhost ~]# cat /sys/fs/cgroup/memory/docker/470ea3492bf8c2568719057788ee5137c4697ca11485bca0e98fae58be0aa017/memory.oom_control 
oom_kill_disable 0				#发现值为0,当内存不够用时,内核将会将其kill掉
under_oom 0

CPU 资源限制

一个宿主机有几十个核心的CPU,但是宿主机上可以同时运行成百上千个不同的进程用以处理不同的任务,多进程共用一个CPU的核心依赖计数就是为可压缩资源,即一个核心的CPU可以通过调度而运行多个进程,但是同一个单位时间内只能有一个进程在CPU上运行。Linux kernel 进程的调度基于CFS(Completely Fair Scheduler),完全公平调度。
进程实时优先级:0-99
非实时优先级(nice):-20-19,对应100-139的进程优先级

CPU密集型的场景:优先级越低越好,计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如数据处理、对视频进行高清解码等全靠CPU的运算能力。
IO密集型的场景:优先级值越高越好,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),比如Web应用,高并发数据量大的动态网站。

磁盘的调度算法,这里查看的是sda
[root@localhost ~]# cat /sys/block/sda/queue/scheduler 
noop [deadline] cfq 	

Docker 提供的 CPU 资源限制选项可以在多核系统上限制容器能利用哪些 vCPU(虚拟cpu)。而对容器最多能使用的 CPU 时间有两种限制方式:
有多个容器请求CPU时,设置各个容器能使用的CPU时间相对比例(设置cpu权重,好比我这有7碗饭,第一个小朋友吃4碗,第二个小朋友2碗,第三个1碗,如果在来一个小朋友吃1碗,那么总数将变为8,各个小朋友的碗数不会发生改变)
以绝对的方式设置容器在每个调度周期内最多能使用的CPU时间

CPU限制方式说明
--cpus能够限制容器可以使用的主机 CPU 个数,并且还可以指定如 1.5 之类的小数
--cpuset-cpus指定容器使用的 CPU 编号,值可以为 0-3,0,1(0代表第一个cpu,以此类推。 0,2代表可以使用第一个cpu和第三个cpu)
--cpu-sharesCPU 共享权值(相对权重),默认值 1024,最大262144
--cpuset-mem设置使用哪个cpu的内存,仅对非统一内存访问架构有效
--cpu-period设置CPU的CFS调度程序周期,默认周期为100微妙,周期的有效范围是 1ms - 1s,必须与 --cpu-quota 一起使用
--cpu-quota      在容器上添加CPU CFS配额,计算方式:cpu-quota / cpu-period 的结果值,设置在每个周期内容器能使用的 CPU 时间,容器的 CPU 配额必须不小于 1ms,即 --cpu-quota的值必须 >= 1000,单位微秒
分配4核CPU并启动4个工作线程,如果不做限制容器会把宿主机的CPU全部占完 
[root@localhost ~]# docker run --name stress -it --rm lorel/docker-stress-ng:latest --cpu 4 --vm 4
[root@localhost ~]# docker stats		#查看cpu,占用400%
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT    MEM %     NET I/O       BLOCK I/O     PIDS
1bc5a6a2a0e4   stress    400.72%   1.01GiB / 2.763GiB   36.55%    1.09kB / 0B   1.65MB / 0B   13
[root@localhost ~]# top					#查看cpu使用,4个cpu基本都占到了100%
%Cpu0  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 98.9 us,  1.1 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

在宿主机查看cpu限制参数 /sys/fs/cgroup/cpuset/docker/容器ID/cpuset.cpus 
#0-3	代表这个进程可以消耗0到3核cpu,也就是4核
[root@localhost ~]#  cat /sys/fs/cgroup/cpuset/docker/1bc5a6a2a0e4589b5f3478bff1a91105c0482c9d9c4770c427fe9230207548bb/cpuset.cpus 
0-3

限制cpu使用,--cpus 2:只给容器分配最多两核宿主机CPU利用率,--cpus后面也可以跟小数
[root@localhost ~]# docker run --name stress -it --rm --cpus 2 lorel/docker-stress-ng:latest stress --cpu 8
[root@localhost ~]# docker stats	#查看当前cpu状态
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O     BLOCK I/O   PIDS
243cb4201e95   stress    199.47%   17.86MiB / 2.763GiB   0.63%     648B / 0B   0B / 0B     9

可以发现,CPU资源限制是将分配给容器的2核心分配到了宿主机每一核心CPU上,也就是容器的总CPU值是在宿主机的每一个核心CPU分配了部分比例。
[root@localhost ~]# top
%Cpu0  : 48.7 us,  0.0 sy,  0.0 ni, 51.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 50.4 us,  0.0 sy,  0.0 ni, 49.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 49.6 us,  0.0 sy,  0.0 ni, 50.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 47.9 us,  0.9 sy,  0.0 ni, 51.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

每核心CPU会按照1000为单位转换成百分比进行资源划分,2个核心的CPU就是200000/1000=200%,4个核心400000/1000=400%,以此类推。
[root@localhost ~]# cat /sys/fs/cgroup/cpu,cpuacct/docker/243cb4201e951839cab20003cb4d24e139851585f989b250a0798ee23b88ea0d/cpu.cfs_quota_us 
200000

#将容器运行到指定的CPU上,这里指定的是1和3,一般不建议绑在0号CPU上,因0号CPU一般会较忙
[root@localhost ~]# docker run -it --rm --name stress --cpus 2 --cpuset-cpus 1,3 lorel/docker-stress-ng:latest stress --cpu 8 --vm 4
[root@localhost ~]# top		#发现cpu只在指定的cpu运行
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni, 99.2 id,  0.8 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.8 sy,  0.0 ni, 99.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

[root@localhost ~]# cat /sys/fs/cgroup/cpuset/docker/34f853ad186600965916f0ba458f8354003a23f893bc3413d2b024d9c37ccdde/cpuset.cpus 
1,3

#修改指定的cpu	
[root@localhost ~]# echo "1-2" > /sys/fs/cgroup/cpuset/docker/34f853ad186600965916f0ba458f8354003a23f893bc3413d2b024d9c37ccdde/cpuset.cpus 
[root@localhost ~]# top		#发现已经发生更改
%Cpu0  :  0.0 us,  0.7 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 99.3 us,  0.0 sy,  0.0 ni,  0.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

Docker 仓库管理

Docker仓库是用来保存镜像的仓库。将制作好的镜像 push 到仓库集中保存,在需要镜像时从仓库中 pull 镜像即可。

Docker 仓库分为 公有云仓库 和 私有云仓库:
公有云仓库: 由互联网公司对外公开的仓库。比如官方、阿里云等第三方仓库
私有云仓库: 组织内部搭建的仓库,一般只为组织内部使用,常使用下面软件搭建仓库。如docker registory、docker harbor

镜像仓库默认是公开的,私有镜像仓库需要购买才能使用,所以需要自己构建私有镜像仓库

docker官方仓库

docker官方仓库地址:https://hub.docker.com/
在docker hub上面创建我们需要上传的仓库。
在这里插入图片描述
用户登录
上传镜像前需要执行docker login命令登录

#也可使用选项 -u 指定用户,-p 指定密码登录
[root@localhost ~]# docker login docker.io
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: qianshuaicn
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
[root@localhost ~]# cat /root/.docker/config.json 		#登录成功后,自动生成验证信息,下次会自动登录,而无需手动登录
{
	"auths": {
		"https://index.docker.io/v1/": {
			"auth": "cpY246NodGUlhbeE1On1kQDWhqWWFA=="		#密码是加密的
		}
	}
}[root@localhost ~]#

上传本地镜像前必须先给上传的镜像用docker tag命令打标签。标签格式: docker.io/用户帐号/镜像名:TAG

# 给创建好的镜像打上标签,这里 alpine:latest 本地镜像的名字,qianshuaicn/alpine:v1 是远程仓库的名字 
[root@localhost ~]# docker tag alpine:latest qianshuaicn/alpine:v1
[root@localhost ~]# docker push qianshuaicn/alpine:v1
[root@localhost ~]# docker pull qianshuaicn/alpine:v1
[root@localhost ~]# docker images
REPOSITORY                          TAG       IMAGE ID       CREATED        SIZE
qianshuaicn/alpine                  v1        14119a10abf4   2 months ago   5.6MB
hub.docker.com/qianshuaicn/alpine   v1        14119a10abf4   2 months ago   5.6MB

在这里插入图片描述

阿里云Docker仓库

将本地镜像上传至阿里云,实现镜像备份与同一分发功能。阿里云官方镜像地址:https://cr.console.aliyun.com,根据操作指南进行操作。
在这里插入图片描述

上传镜像前需要先登录阿里云,使用 docker login 命令登录,密码会保存在/root/.docker/config.json文件中,下次将不会需要再输入密码登录

[root@localhost ~]# 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

上传镜像至阿里云

# 给创建好的镜像打上标签
[root@localhost ~]# docker tag alpine:latest registry.cn-hangzhou.aliyuncs.com/qianshuaicn/alpine:v1
#将镜像上传至阿里云仓库
[root@localhost ~]# docker push registry.cn-hangzhou.aliyuncs.com/qianshuaicn/alpine:v1
#从阿里云镜像仓库下载镜像
[root@localhost ~]# docker pull registry.cn-hangzhou.aliyuncs.com/qianshuaicn/alpine:v1
[root@localhost ~]# docker images 
REPOSITORY                                             TAG       IMAGE ID       CREATED        SIZE
registry.cn-hangzhou.aliyuncs.com/qianshuaicn/alpine   v1        14119a10abf4   2 months ago   5.6MB

登录网站查看上传的镜像
在这里插入图片描述

私有仓库Docker Registry

Docker Registry作为Docker的核心组件之一,负责镜像内容的存储与分发,客户端的 docker pull 以及push 命令都将直接与 registry 进行交互。最初版本的 registry 由Python实现,由于设计初期在安全性、性能以及 APl 的设计上有着诸多的缺陷,新的项目由go语言开发,解决了上一代registry中存在的问题。

[root@localhost ~]# docker pull registry:2.7.0		#下载docker registry镜像
#默认情况下,仓库会被创建在容器的 /var/lib/registry 目录下,可以通过 -v 参数来将镜像文件存放在本地的指定路径。
[root@localhost ~]# docker run -d -p 5000:5000 --restart=always --name myregistry -v /var/lib/registry:/var/lib/registry registry:2.7.0
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE            COMMAND                  CREATED       STATUS       PORTS                                       NAMES
0530793f7c1b   registry:2.7.0   "/entrypoint.sh /etc…"   3 hours ago   Up 3 hours   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   myregistry
[root@localhost ~]# netstat -lntp | grep 5000
tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN      17093/docker-proxy  
tcp6       0      0 :::5000                 :::*                    LISTEN      17097/docker-proxy
[root@localhost ~]# curl http://1.116.194.16:5000/v2/_catalog		#这里返回的json数据代表暂无任何仓库,因为还没有上传任何镜像。
{"repositories":[]}	

为了让客户端服务器能够快速地访问刚刚在服务端搭建的镜像仓库(默认情况下是需要配置HTTPS证书的),这里简单在客户端配置一下私有仓库的可信任设置让我们可以通过HTTP直接访问:

[root@localhost ~]# vim /etc/docker/daemon.json
{
  "registry-mirrors": ["https://oinh00fc.mirror.aliyuncs.com"],
  "insecure-registries": ["1.116.194.16:5000"]		#加上这一句
}	
[root@localhost ~]# systemctl restart docker		#重新启动docker服务,使配置生效
[root@localhost ~]# docker tag busybox:latest 1.116.194.16:5000/busybox:v1		#为要上传的镜像打tag
[root@localhost ~]# docker push  1.116.194.16:5000/busybox:v1					#将镜像推送至私有仓库
[root@localhost ~]# curl http://1.116.194.16:5000/v2/_catalog					#再次通过访问API验证镜像仓库的内容
{"repositories":["busybox"]}	
#由于我们做了目录挂载,因此可以在宿主机目录下查看
[root@localhost ~]# ls /var/lib/registry/docker/registry/v2/repositories/		
busybox
#如果想要知道要下载的镜像都有哪些tag(或版本),可以通过下面这个api来获取:
[root@localhost ~]# curl http://1.116.194.16:5000/v2/busybox/tags/list			
{"name":"busybox","tags":["v1"]}
[root@localhost ~]# docker pull 1.116.194.16:5000/busybox:v1
[root@localhost ~]# docker images
REPOSITORY                  TAG       IMAGE ID       CREATED       SIZE
1.116.194.16:5000/busybox   v1        cabb9f684f8b   4 days ago    1.24MB

私有仓库已经搭建好了,要确保私有仓库的安全性,还需要一个安全认证证书,防止发生意想不到的事情。所以需要在搭建私有仓库的 Docker 主机上先生成自签名证书。

[root@localhost ~]# mkdir -p /docker/certs	 		#创建存储鉴权密码文件目录
通过 openssl 先生成自签名证书,运行命令以后需要填写一些证书信息,里面最关键的部分是:Common Name (eg, your name or your server's hostname) []:这里填写的是私有仓库的地址。
[root@localhost ~]# openssl req -newkey rsa:2048 -nodes -sha256 -keyout /docker/certs/domain.key -x509 -days 365 -out /docker/certs/domain.crt

#创建一个用户并生成密码,这里的用户是qianshuai,密码是123456。
#htpasswd 是 apache http 的基本认证文件,使用 htpasswd 命令可以生成用户及密码文件,如果没有 htpasswd 功能需要安装 httpd:yum install -y httpd
[root@localhost ~]# htpasswd -Bbn qianshuai 123456 > /docker/auth/htpasswd	
[root@localhost ~]# cat /docker/auth/htpasswd
qianshuai:$2y$05$fRizMh874nRm4XPxuQ09H.mYRlyUaAsi5kCw8aORLsqAFLP0vazNe
[root@localhost ~]# docker run -di --name myregistry -p 5000:5000 \
-v /var/lib/registry:/var/lib/registry \
-v /docker/certs:/certs \
-v /docker/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
-e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
registry:2.7.0 

通过 docker login 命令输入账号密码登录私有仓库。
[root@localhost ~]# docker login 1.116.194.16:5000
[root@localhost ~]# docker pull 1.116.194.16:5000/busybox:v1
[root@localhost ~]# docker images
REPOSITORY                  TAG       IMAGE ID       CREATED       SIZE
1.116.194.16:5000/busybox   v1        cabb9f684f8b   5 days ago    1.24MB
[root@localhost ~]# docker pull registry:2.7.1		#下载 docker registry 镜像
[root@localhost ~]# docker images
REPOSITORY           TAG       IMAGE ID       CREATED        SIZE
registry             2.7.1     b8604a3fe854   9 days ago     26.2MB
[root@localhost ~]# mkdir -p /etc/docker/auth		#创建授权用户密码使用目录
#启动dockerregistry
[root@localhost ~]# docker run -d -p 5000:5000 --restart=always --name registry \
-v /etc/docker/auth:/auth \
-e "REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry_htpasswd registry:2.7.1

#docker login 默认使用https登录,而 docker registry 为 http,所以默认登录失败
[root@localhost ~]# docker login 192.168.139.101:5000
Username: qianshuai
Password: 
Error response from daemon: Get "https://192.168.139.101:5000/v2/": http: server gave HTTP response to HTTPS client

#修改配置让docker login支持http协议,将registry仓库服务器地址加入service 单元文件。新增--insecure-registry ip:端口
[root@localhost ~]# vim /lib/systemd/system/docker.service
[root@localhost ~]# grep ExecStart /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry 192.168.139.101:5000

[root@ubuntu1804 ~]#vim /etc/docker/daemon.json		#或者修改该文件
{
"registry-mirrors": ["https://si7y70hh.mirror.aliyuncs.com"],
"insecure-registry": ["192.168.139.101:5000"]
}
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
密码文件位于家目录下的.docker/config.json?
[root@localhost ~]# docker login 192.168.139.101:5000		#再次登录验证成功
Username: qianshuai
Password: 
Login Succeeded
[root@localhost ~]# docker tag alpine:latest 192.168.139.101:5000/alpine:v1
[root@localhost ~]# docker push 192.168.139.101:5000/alpine:v1

登录其它服务器进行访问

[root@localhost ~]# vim /lib/systemd/system/docker.service
[root@localhost ~]# grep ExecStart /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry 192.168.139.101:5000
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# docker login 192.168.139.101:5000
[root@localhost ~]# docker images
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE
[root@localhost ~]# docker pull 192.168.139.101:5000/alpine:v1
[root@localhost ~]# docker images
REPOSITORY                    TAG       IMAGE ID       CREATED      SIZE
192.168.139.101:5000/alpine   v1        0a97eee8041e   9 days ago   5.61MB

Docker 分布式仓库 Harbor

Docker Registry默认不支持web界面,需要安装harbor。Harbor是由vmware开源的一个用于存储和分发Docker镜像的企业级Registry服务器,作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中,确保数据和知识产权在公司内部网络中管控。

官方地址:https://goharbor.io/
在这里插入图片描述

组件说明
nginx            harbor的一个反向代理组件,代理registry、ui、token等服务。这个代理会转发 harbor web 和 docker client 的各种请求到后端服务上
harbor-adminserverharbor系统管理接口,可以修改系统配置以及获取系统信息
harbor-db存储项目的元数据、用户、规则、复制策略等信息
harbor-jobserviceharbor里面主要是为了镜像仓库之前同步使用的
harbor-log收集其他harbor的日志信息
harbor-ui一个用户界面模块,用来管理registry
registry存储docker images的服务,并且提供 pull/push 服务
redis存储缓存信息
webhook当 registry 中的 image 状态发生变化的时候去记录更新日志、复制等操作
token service在 docker client 进行 pull/push 的时候负责token的发放

基于角色的访问控制:用户与 Docker 镜像仓库通过“项目”进行组织管理,一个用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。

功能说明
镜像复制镜像可以在多个Registry实例中复制(同步)
图形化用户界面用户可以通过浏览器来 浏览检索当前Docker镜像仓库,管理项目和命名空间
AD/LDAP支Harbor可以集成企业内部已有的AD/LDAP,用于鉴权认证管理
审计管理所有针对镜像仓库的操作都可以被记录追溯,用于审计管理
RESTful API提供给管理员对于Harbor更多的操控,使得与其它管理软件集成变得更容易

安装harbo

下载地址:https://github.com/vmware/harbor/releases
安装文档:https://github.com/vmware/harbor/blob/master/docs/installation_guide.md

这里使用的是离线安装包部署
在这里插入图片描述
使用wget命令下载离线安装包

# wget https://storage.googleapis.com/harbor-releases/release-1.7.0/harbor-offline-installer-v1.7.6.tgz
[root@localhost ~]# wget https://github.com/goharbor/harbor/releases/download/v2.5.2/harbor-offline-installer-v2.5.2.tgz

将下载的tar包解压,解压缩之后,目录下会生成 harbor.yml 文件,该文件就是Harbor的配置文件

[root@localhost ~]# tar -zxf harbor-offline-installer-v2.5.2.tgz 
[root@localhost ~]# ls harbor
common.sh  harbor.v2.5.2.tar.gz  harbor.yml.tmpl  install.sh  LICENSE  prepare
[root@localhost ~]# cp harbor/harbor.yml.tmpl harbor/harbor.yml
[root@localhost ~]# vim harbor/harbor.yml		#修改hostname、harbor_admin_password、注释https
# hostname设置访问地址,可以使用ip、域名,不可以设置为127.0.0.1或localhost
hostname =  reg.mydomain.com

# 访问协议,默认是http,也可以设置https。如果设置https,还需要手动配置证书,官方是以openssl工具创建证书。
http:
  port: 80

#注释https
#https:
#  port: 443			# https安全访问端口,默认443
#  certificate: /your/certificate/path		# 访问harbor的证书
#  private_key: /your/private/key/path		# 访问harbor的私钥

#是否启用Harbor组件之间的tls通信​,默认为禁用状态。
# internal_tls:		# 是否启用所有Harbor组件之间的tls通信
#   enabled: true	# 将enabled设置为true表示内部tls已启用
#   dir: /etc/harbor/tls/internal	#证书和密钥文件的存放目录

#是否启用外部代理​,默认禁用。如果启用,那么主机名将不再使用,值为外部代理的地址
# external_url: https://reg.mydomain.com:8433

# 启动Harbor后,管理员UI登录的密码,默认是Harbor12345
harbor_admin_password = Harbor12345

# harbor数据库配置部分,harbor使用​postgresql​作为数据库。
database:
  password: root123		# harbor数据库root用户的密码,生产环境建议修改
  max_idle_conns: 100	#空闲连接池中的最大连接数。如果小于等于0,则不保留空闲连接
  max_open_conns: 900	#打开到数据库的最大连接数。如果小于等于0,则对打开的连接数没有限制
data_volume: /data		#数据存储目录,harbor中的每个组件的数据都存储在该指定路径下的子目录中

#是否启用外部存储​。如果需要使用外部存储,则将该部分的注释去掉,生产环境建议使用外部存储
# storage_service:
#   ca_bundle:			# 自定义根ca证书的路径,该证书将被注入信任库
#   filesystem:			# 存储后端,默认为文件系统,选项包括文件系统、azure、gcs、s3、swift、NFS和oss存储
#     maxthreads: 100	# 最大线程数
#   redirect:			# 是否启用重定向
#     disabled: false

#扫描器配置,是否启用Trivy扫描器来扫描镜像的漏洞,默认未启用
trivy:
  ignore_unfixed: false		# 是否显示已修复的漏洞
  skip_update: false		# 是否启用从github上下载的Trivy数据
  offline_scan: false
  insecure: false			# 是否跳过证书验证
  # github_token: xxx		# github的token值

#job服务的最大数量
jobservice:
  max_job_workers: 10

#webhook配置
notification:
  webhook_job_max_retry: 10		# webhook job的最大重试次数

#Chart配置。harbor不仅可以存储镜像,还可以存Chart,当helm服务器使用
chart:
  absolute_url: disabled		# 是否启用

#日志配置,配置harbor的日志信息
log:
  level: info		# 日志级别
  local:			# 配置本地存储中的日志
    rotate_count: 50		#日志文件在被删除之前旋转的次数,如果为0,旧版本将被删除而不是旋转
    rotate_size: 200M		#有当日志文件的大小大于该值字节时,才会对其进行旋转。如果size后跟k,则假定大小以KB为单位。如果使用M,则大小以兆字节为单位,如果使用G,则大小以千兆字节为单位。
    location: /var/log/harbor	#主机上存储日志的目录

#是否将日志放到另外的主机上
# external_endpoint:
  #   protocol: tcp			#用于将日志传输到外部端点的协议,选项为tcp或udp
  #   host: localhost		#外部主机的地址
  #   port: 5140			#外部主机的端口号

#Harbor版本号,因为配置文件是使用模板文件创建的,需要将版本号修改为安装包一样的版本,否则按照会失败
_version: 2.5.0

#是否启用外部数据库。如果要使用外部数据库,请取消注释该部分来启用。注意与前面的外部数据区分
#目前Harbor仅支持PostgraSQL数据库,需要手动在外部的PostgreSQL上创建harbor、notary_signer、notary_servers三个数据库,Harbor启动时会自动在对应数据库下生成表
# external_database:
#   harbor:
#     host: harbor_db_host   # 数据库主机地址
#     port: harbor_db_port  # 数据库端口
#     db_name: harbor_db_name  # 数据库名称
#     username: harbor_db_username  # 连接该数据库的用户名
#     password: harbor_db_password  # 连接数据库的密码
#     ssl_mode: disable   # 启用SSL模式
#     max_idle_conns: 2   # 最大空闲连接数
#     max_open_conns: 0

#是否启用外部redis缓存服务器
# external_redis:  		#  配置外部 Redis 实例。
#   host: redis:6379  	# redis的地址:端口
#   password:   		# 连接外部redis服务的密码
#   #sentinel_master_set:    
#   registry_db_index: 1 
#   jobservice_db_index: 2
#   chartmuseum_db_index: 3
#   trivy_db_index: 5   	 # Trivy扫描器的数据库索引
#   idle_timeout_seconds: 30

#UAA身份验证,是否通过自签名证书托管的uaa实例证书的信任
# uaa:
#   ca_file: /path/to/ca

#全局代理
proxy:
  http_proxy:
  https_proxy:
  no_proxy:
  components:
    - core
    - jobservice
    - trivy

#启用指标采集组件
# metric:
#   enabled: false
#   port: 9090
#   path: /metrics

​执行install.sh脚本安装harbor,过程会从网上拉取镜像,install完成后会自动启动相关服务

[root@localhost ~]# cd harbor
[root@localhost harbor]# ./install.sh 		

查看服务状态
[root@localhost harbor]# docker-compose ps

通过修改 rc.local 文件实现开机自启

[root@localhost ~]# cat /etc/rc.local
#!/bin/bash
cd /root/harbor
/usr/bin/docker-compose up
[root@localhost ~]# chmod +x /etc/rc.local

配置docker的非安全的镜像仓库
vim /lib/systemd/system/docker.service

[root@localhost ~]# tar -zxf harbor-offline-installer-v1.7.6.tgz

[root@localhost ~]# cp harbor.yml.tmpl harbor.yml

浏览器访问harbor,用户名为admin,密码为harbor.yml中指定的密码,默认为Harbor12345
在这里插入图片描述

harbor上必须先建立项目,才能上传镜像
在这里插入图片描述

[root@localhost ~]# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry 192.168.139.101 --insecure-registry 192.168.139.102
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# docker login 192.168.139.101		#密码会保存在/root/.docker/config.json
[root@localhost ~]# docker tag alpine:latest 192.168.139.101/my_registry/alpine:v1
[root@localhost ~]# docker push 192.168.139.101/my_registry/alpine:v1

访问harbor网站验证上传镜像成功
在这里插入图片描述
在这里插入图片描述

[root@localhost ~]# docker pull 192.168.139.101/my_registry/alpine:v1
[root@localhost ~]# docker images
REPOSITORY                              TAG             IMAGE ID       CREATED       SIZE
192.168.139.101:5000/alpine             v1              0a97eee8041e   9 days ago    5.61MB

配置内核参数
临时加载内核模板

modprobe br_netfilter

配置内核参数并生效

# cat > /etc/sysctl.conf <<- EOF
net.ipv4.ip_forward = 1			#开启路由转发,不配置该参数,当主机重启后,服务状态正常,却无法访问到服务器
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
# sysctl -p

配置安全访问证书
要配置 HTTPS,就必须要创建 SSL 证书。可以使用受信任的第三方 CA 签署的证书,也可以使用自签名证书。
生成CA证书

[root@localhost ~]# mkdir -p /docker/harbor/certs
[root@localhost ~]# cd /docker/harbor/certs/
创建key文件:
[root@localhost certs]# openssl genrsa -out ca.key 4096
生成证书:
[root@localhost certs]# openssl req -x509 -new -nodes -sha512 -days 3650  -subj "/CN=192.168.139.101"  -key ca.key  -out ca.crt

生成服务器证书

创建私钥
[root@localhost certs]# openssl genrsa -out server.key 4096
生成证书签名请求
[root@localhost certs]# openssl req  -new -sha512  -subj "/CN=192.168.139.101"  -key server.key  -out server.csr

生成一个 v3扩展文件

[root@localhost certs]# cat > v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1=192.168.139.101
EOF

生成harbor仓库主机的证书

[root@localhost certs]# openssl x509 -req -sha512 -days 3650 -extfile v3.ext -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
[root@localhost certs]# ls
ca.crt  ca.key  ca.srl  server.crt  server.csr  server.key  v3.ext

修改配置文件,修改https,配置和安装证书

[root@localhost ~]# vim harbor/harbor.yml 
https:
  port: 443                    //安全访问端口,默认443,建议修改,本处演示就不改了
  certificate: /docker/harbor/certs/ca.crt   //填写制作好的证书文件
  private_key: /docker/harbor/certs/harbor.test.org.key   //填写制作好的私钥文件

为docker配置harbor认证,把server.crt文件和server.key文件拷贝到目录/data/cert下

[root@localhost certs]# mkdir -p /etc/docker/certs.d/192.168.139.101
[root@localhost certs]# cp server.crt /etc/docker/certs.d/192.168.139.101/

运行prepare脚本以启用 HTTPS

[root@localhost ~]# cd harbor
[root@localhost harbor]# ./prepare 

执行install.sh脚本安装Habor

[root@localhost harbor]# ./install.sh  --with-notary  --with-trivy  --with-chartmuseum
--with-notary:安装镜像签名组件Notary(包括Notary Server和Notary Singer),必须配置HTTPS方可指定该参数;
--with-clair:安装镜像扫描工具Clair;(2.3.1已弃用)
--with-trivy:安装镜像扫描工具Trivy;
--with-chartmuseum:安装Char文件管理组件ChartMuseum;harbor基于该插件可当Helm Chart仓库。(哈哈哈!不用自己再搭helm Chart仓库了)

查看服务状态,install完成后,会自动启动相关服务

[root@localhost harbor]# docker-compose ps

在这里插入图片描述

登录Harbor UI界面,登录地址: https://192.168.139.101:443,密码是默认密码:Harbor12345

在这里插入图片描述

创建项目
【项目】——【新建项目】
在这里插入图片描述
新建用户
【系统管理】——【用户管理】——【新建用户】
在这里插入图片描述

给创建的用户访问新建项目的角色
在这里插入图片描述
在这里插入图片描述

Harbor命令行管理

docker配置文件中配置Harbor地址,harbor的客户端想要拉取/推送Harbor中的镜像,需要在主机上安装docker,并在docker配置文件中指定Harbor服务器地址

[root@localhost ~]# vim /etc/docker/daemon.json         # 添加如下内容
{
  "registry-mirrors": ["https://oinh00fc.mirror.aliyuncs.com"],
  "insecure-registries":["192.168.139.101:443"]
}

然后需要重启docker服务

[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker

查看harbor服务运行状态,如果服务未运行则进行启动

[root@localhost harbor]# docker-compose ps
[root@localhost harbor]# docker-compose up -d

命令行登录Harbor

[root@localhost ~]# docker login https://192.168.139.101:443 -u admin -p Harbor12345		#密码会保存在/root/.docker/config.json

上传镜像到Harbor
将本地的alpine:latest 镜像上传到project_test项目下

docker tag alpine:latest 192.168.139.101/project_test/alpine:v1

2、在/usr/lib/systemd/system/docker.service中添加私有仓库地址

harbor 高可用

高可用实现方式
在这里插入图片描述
方案一使用的是共享存储,方案二是独立存储,无论上传到哪个harbor服务器都会同步到另一个

Harbor支持基于策略的Docker镜像复制功能,这类似于MySQL的主从同步,其可以实现不同的数据中
心、不同的运行环境之间同步镜像,并提供友好的管理界面,大大简化了实际运维中的镜像管理工作,并且还有实现了双向复制功能

选择需要同步的镜像仓库。两个不同的仓库都需要执行这一步
在这里插入图片描述

在这里插入图片描述

单机容器编排工具docker compose

docker-compose是docker容器的一种单机编排服务工具(可以管理多个容器),如果在宿主机启动较多的容器都是手动操作会比较麻烦且容易出错,所以可以使用docker单机编排工具docker-compose。
比如可以解决容器之间的依赖关系,就像启动一个nginx前端服务的时候会调用后端的tomcat,那就得先启动tomcat,但是启动tomcat容器还需要依赖数据库,那就还得先启动數据库,docker-compose就可以解决这样的嵌套依赖关系。其完全可以替代docker run对容器进行创建、启动和停止。
docker-compose将所管理的容器分为三层,分别是工程(project)、服务(service)以及容器(container)
容器编排 Docker Compose:允许用户在一个模板(yaml格式)中定义一组相关联的容器,会根据 –link等参数对容器启动的优先级进行排序

docker提倡“一容器一进程”比如一个服务需要由多个进程组成,就需要多个容器提供完整服务,微服务架构的应用系统中一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,维护的工作量会很大。比如LAMP环境中apache进程和mysql进程,两个不同的进程分别放到不同的容器去运行,进程与进程对应启动关闭顺序不方便记忆,所以使用 Docker Compose 对容器的启动优先级进行排序。每个容器被创建后,都会分配一个 CONTAINER ID 作为容器的唯一标示,后续对容器的启动、停止、修改、删除等所有操作,都是通过 CONTAINER ID 来完成,偏向于数据库概念中的主键。

安装与卸载

Compose 可以通过 Python 的包管理工具 pip 进行安装,也可以直接下载编译好的二进制文件使用,甚至能够直接在 Docker 容器中运行。

1、使用pip安装,这种方式是将 Compose 当作一个 Python 应用来从 pip 源中安装

#安装
pip install docker-compose

#卸载
pip uninstall docker-compose

如果服务器没有安装pip,则执行以下命令进行安装,安装过程中如有错误则打开百度。

yum -y install epel-release
yum -y install python-pip

2、使用下载编译好的二进制文件
GitHub的docker-compose下载地址:https://github.com/docker/compose/releases
鼠标右击,复制链接地址,然后使用wget命令下载
在这里插入图片描述

wget https://github.com/docker/compose/releases/download/v2.9.0/docker-compose-linux-x86_64

把docker-compose移动到 /usr/local/bin/

mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
[root@localhost ~]# docker-compose version
Docker Compose version v2.9.0

基本命令

,默认为 docker-compose.yml,可以多次指定
–log-level LEVEL定义日志级别
--verbose显示更多输出信息
-v显示版本

选项
build
格式为 docker-compose build [options] [SERVICE…]。
构建(重新构建)项目中的服务容器。
服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。
可以随时在项目目录下运行 docker-compose build 来重新构建服务。
选项包括:
–force-rm 删除构建过程中的临时容器。–no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。–pull 始终尝试通过 pull 来获取更新版本的镜像。

config 验证 compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。
新建一个compose文件,
在这里插入图片描述
在这里插入图片描述
-q 查看当前配置,如果没有任何错误则不做任何输出

down 停止 up 命令所启动的容器,并移除网络

[root@localhost ~]# docker-compose config -q		#-q没有错误不做任何输出

create 创建服务,只创建并不运行。推荐使用up

通过服务来管理容器,

docer-com up

-d 放入后台

down 删除所有容器

exec 进入指定容器
在这里插入图片描述
不能通过容器名进入容器,而是通过服务名

rm 删除已经停止的服务
run 一次性运行容器
scale 设置服务运行的容器个数,注意:端口不能冲突
在这里插入图片描述

start 启动服务
stop 停止服务

top 显示容器运行状态
unpause 取消暂定
up 创建并启动容器

指定容器名,因为容器重启ip会发生变化

创建 docker compose 文件,docker compose 文件可在任意目录,创建文件名为docker-compose.yml 配置文件


[root@localhost ~]# cat docker-compose.yml 
services:
  service-nginx-web:
    image: nginx
    container_name: nginx-web
    expose:
    - 80
    - 443
    ports:
    - "80:80"
    - "443:443"

查看配置和格式检查
[root@localhost ~]# docker-compose config	
name: root
services:
  service-nginx-web:
    container_name: nginx-web
    expose:
    - "80"
    - "443"
    image: nginx
    networks:
      default: null
    ports:
    - mode: ingress
      target: 80
      published: "80"
      protocol: tcp
    - mode: ingress
      target: 443
      published: "443"
      protocol: tcp
networks:
  default:
    name: root_default
[root@localhost ~]# docker-compose config -q
#改错ocker-compose文件格式
[root@localhost ~]# echo "err" >> docker-compose.yml 
[root@localhost ~]# docker-compose config -q
yaml: line 12: could not find expected ':'


启动容器

docker-compose up -d  	#-d 为后台执行,不加-d为前台执行

[root@localhost ~]# docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED         STATUS         PORTS                                                                      NAMES
d988f0482dbf   nginx     "/docker-entrypoint.…"   6 seconds ago   Up 5 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   nginx-web
[root@localhost ~]# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
nginx-web           "/docker-entrypoint.…"   service-nginx-web   running             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

通过服务名进入容器

[root@localhost ~]# docker-compose exec service-nginx-web bash
root@d988f0482dbf:/# echo "hello nginx" > /usr/share/nginx/html/index.html 
root@d988f0482dbf:/# exit
exit
[root@localhost ~]# curl 127.0.0.1
hello nginx

停止容器

[root@localhost ~]# docker-compose stop                                                                                                      
[root@localhost ~]# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
nginx-web           "/docker-entrypoint.…"   service-nginx-web   exited (0)    

启动容器

[root@localhost ~]# docker-compose start
[root@localhost ~]# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
nginx-web           "/docker-entrypoint.…"   service-nginx-web   running             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

重启容器

[root@localhost ~]# docker-compose restart
[+] Running 1/1
 ⠿ Container nginx-web  Started                                                                                                           0.5s
[root@localhost ~]# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
nginx-web           "/docker-entrypoint.…"   service-nginx-web   running             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

同时打开两个的终端,观察日事件
在终端二执行以下命令

[root@localhost ~]# docker-compose events

在终端一执行以下命令

[root@localhost ~]# docker-compose stop

在终端二查看输出的日志信息

[root@localhost ~]# docker-compose events
2022-08-19 10:15:53.231849 container kill d988f0482dbf8bdc883b15d6d63eba8b89c799130eb4b347620055081aaf3e9f (image=nginx, maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>, name=nginx-web, signal=3)

2022-08-19 10:15:53.274545 container die d988f0482dbf8bdc883b15d6d63eba8b89c799130eb4b347620055081aaf3e9f (maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>, exitCode=0, name=nginx-web, image=nginx)

2022-08-19 10:15:53.337756 container stop d988f0482dbf8bdc883b15d6d63eba8b89c799130eb4b347620055081aaf3e9f (maintainer=NGINX Docker Maintainers <docker-maint@nginx.com>, image=nginx, name=nginx-web)

#以json格式显示日志
[root@localhost ~]# docker-compose events --json
{"action":"kill","attributes":{"image":"nginx","maintainer":"NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","name":"nginx-web","signal":"3"},"id":"d988f0482dbf8bdc883b15d6d63eba8b89c799130eb4b347620055081aaf3e9f","service":"service-nginx-web","time":"2022-08-19T10:20:07.894381271+08:00","type":"container"}
{"action":"die","attributes":{"exitCode":"0","image":"nginx","maintainer":"NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","name":"nginx-web"},"id":"d988f0482dbf8bdc883b15d6d63eba8b89c799130eb4b347620055081aaf3e9f","service":"service-nginx-web","time":"2022-08-19T10:20:07.940589938+08:00","type":"container"}
{"action":"stop","attributes":{"image":"nginx","maintainer":"NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","name":"nginx-web"},"id":"d988f0482dbf8bdc883b15d6d63eba8b89c799130eb4b347620055081aaf3e9f","service":"service-nginx-web","time":"2022-08-19T10:20:08.012332181+08:00","type":"container"}

暂停和恢复

#暂停
[root@localhost ~]# docker-compose pause
[root@localhost ~]# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
nginx-web           "/docker-entrypoint.…"   service-nginx-web   paused              0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

#恢复
[root@localhost ~]# docker-compose unpause
[root@localhost ~]# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
nginx-web           "/docker-entrypoint.…"   service-nginx-web   running             0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp

单机版的Haproxy+Nginx+Tomcat

实现单机版本的 nginx+tomcat 的动静分离 web站点,要求从 nginx作为访问入口,当访问指定 URL 的时候转发至 tomcat 服务器响应

Docker Compose 常用命令

字段描述
build重新构建服务
ps列出容器
up创建和启动容器
exec在容器里面执行命令
scale指定一个服务容器启动数量
top显示容器进程
logs查看容器输出
down删除容器、网络、数据卷和镜像
stop/start/restart停止/启动/重启服务
                    					

-f 指定使用的 compose 模板文件,如果不指定默认为当前路径docker-compose.yml
-p 指定项目名称,默认将使用所在目录名称作为项目名

模板文件是使用 Compose 的核心,涉及到的指令关键字也比较多。但大家不用担心,这里面大部分指令跟 docker run 相关参数的含义都是类似的。
默认的模板文件名称为 docker-compose.yml,格式为 YAML 格式。
version: "3"services: webapp: image: examples/web ports: - “80:80” volumes: - “/data”
注意每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)等来自动构建生成镜像。
如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中重复设置。

下面分别介绍各个指令的用法
build
指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。
version: '3’services: webapp: build: ./dir
你也可以使用 context 指令指定 Dockerfile 所在文件夹的路径。
使用 dockerfile 指令指定 Dockerfile 文件名。
使用 arg 指令指定构建镜像时的变量。
version: '3’services: webapp: build: context: ./dir dockerfile: Dockerfile-alternate args: buildno: 1
使用 cache_from 指定构建镜像的缓存
build: context: . cache_from: - alpine:latest - corp/web_app:3.14
cap_add, cap_drop
指定容器的内核能力(capacity)分配。
例如,让容器拥有所有能力可以指定为:
cap_add: - ALL
去掉 NET_ADMIN 能力可以指定为:
cap_drop: - NET_ADMIN
command
覆盖容器启动后默认执行的命令。
command: echo “hello world”

configs
仅用于 Swarm mode,详细内容请查看 Swarm mode 一节。
cgroup_parent
指定父 cgroup 组,意味着将继承该组的资源限制。
例如,创建了一个 cgroup 组名称为 cgroups_1。
cgroup_parent: cgroups_1
container_name
指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。
container_name: docker-web-container
注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。
deploy
仅用于 Swarm mode,详细内容请查看 Swarm mode 一节
devices
指定设备映射关系。
devices: - “/dev/ttyUSB1:/dev/ttyUSB0”
depends_on
解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web
version: '3’services: web: build: . depends_on: - db - redis redis: image: redis db: image: postgres
注意:web 服务不会等待 redis db 「完全启动」之后才启动。
dns
自定义 DNS 服务器。可以是一个值,也可以是一个列表。
dns: 8.8.8.8dns: - 8.8.8.8 - 114.114.114.114
dns_search
配置 DNS 搜索域。可以是一个值,也可以是一个列表。
dns_search: example.comdns_search: - domain1.example.com - domain2.example.com
tmpfs
挂载一个 tmpfs 文件系统到容器。
tmpfs: /runtmpfs: - /run - /tmp
env_file
从文件中获取环境变量,可以为单独的文件路径或列表。
如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。
如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。
env_file: .envenv_file: - ./common.env - ./apps/web.env - /opt/secrets.env
环境变量文件中每一行必须符合格式,支持 # 开头的注释行。
common.env: Set development environmentPROG_ENV=development
environment
设置环境变量。你可以使用数组或字典两种格式。
只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。
environment: RACK_ENV: development SESSION_SECRET:environment: - RACK_ENV=development - SESSION_SECRET
如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括
y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF
expose
暴露端口,但不映射到宿主机,只被连接的服务访问。
仅可以指定内部端口为参数
expose: - “3000” - “8000”
external_links
注意:不建议使用该指令。
链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。
external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresql
extra_hosts
类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息。
extra_hosts: - “googledns:8.8.8.8” - “dockerhub:52.1.157.61”
会在启动后的服务容器中 /etc/hosts 文件中添加如下两条条目。
8.8.8.8 googledns52.1.157.61 dockerhub
healthcheck
通过命令检查容器是否健康运行。
healthcheck: test: [“CMD”, “curl”, “-f”, “http://localhost”] interval: 1m30s timeout: 10s retries: 3
image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。
image: ubuntuimage: orchardup/postgresqlimage: a4bc65fd
labels
为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息。
labels: com.startupteam.description: “webapp for a startup team” com.startupteam.department: “devops department” com.startupteam.release: “rc3 for v1.0”
links
注意:不推荐使用该指令。
logging
配置日志选项。
logging: driver: syslog options: syslog-address: “tcp://192.168.0.42:123”
目前支持三种日志驱动类型。
driver: "json-file"driver: "syslog"driver: “none”
options 配置日志驱动的相关参数。
options: max-size: “200k” max-file: “10”
network_mode
设置网络模式。使用和 docker run 的 --network 参数一样的值。
network_mode: "bridge"network_mode: "host"network_mode: "none"network_mode: "service:[service name]"network_mode: “container:[container name/id]”
networks
配置容器连接的网络。
version: "3"services: some-service: networks: - some-network - other-networknetworks: some-network: other-network:
pid
跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。
pid: “host”
ports
暴露端口信息。
使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。
ports: - “3000” - “8000:8000” - “49100:22” - “127.0.0.1:8001:8001”
注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式。
secrets
存储敏感数据,例如 mysql 服务密码。
version: "3.1"services:mysql: image: mysql environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password secrets: - db_root_password - my_other_secretsecrets: my_secret: file: ./my_secret.txt my_other_secret: external: true
security_opt
指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名。
security_opt: - label:user:USER - label:role:ROLE
stop_signal
设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。
stop_signal: SIGUSR1
sysctls
配置容器内核参数。
sysctls: net.core.somaxconn: 1024 net.ipv4.tcp_syncookies: 0sysctls: - net.core.somaxconn=1024 - net.ipv4.tcp_syncookies=0
ulimits
指定容器的 ulimits 限制值。
例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。
ulimits: nproc: 65535 nofile: soft: 20000 hard: 40000
volumes
数据卷所挂载路径设置。可以设置为宿主机路径(HOST:CONTAINER)或者数据卷名称(VOLUME:CONTAINER),并且可以设置访问模式 (HOST:CONTAINER:ro)。
该指令中路径支持相对路径。
volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro
如果路径为数据卷名称,必须在文件中配置数据卷。
version: “3"services: my_src: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysqlvolumes: mysql_data:
其它指令
此外,还有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令,基本跟 docker run 中对应参数的功能一致。
指定服务容器启动后执行的入口文件。
entrypoint: /code/entrypoint.sh
指定容器中运行应用的用户名。
user: nginx
指定容器中工作目录。
working_dir: /code
指定容器中搜索域名、主机名、mac 地址等。
domainname: your_website.comhostname: testmac_address: 08-00-27-00-0C-0A
允许容器中运行一些特权命令。
privileged: true
指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped。
restart: always
以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。
read_only: true
打开标准输入,可以接受外部输入。
stdin_open: true
模拟一个伪终端。
tty: true
读取变量
Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。
例如,下面的 Compose 文件将从运行它的环境中读取变量 M O N G O V E R S I O N 的值,并写入执行的指令中。 v e r s i o n : " 3 " s e r v i c e s : d b : i m a g e : " m o n g o : {MONGO_VERSION} 的值,并写入执行的指令中。 version: "3"services:db: image: "mongo: MONGOVERSION的值,并写入执行的指令中。version:"3"services:db:image:"mongo:{MONGO_VERSION}”
如果执行 MONGO_VERSION=3.2 docker-compose up 则会启动一个 mongo:3.2 镜像的容器;如果执行 MONGO_VERSION=2.8 docker-compose up 则会启动一个 mongo:2.8 镜像的容器。
若当前目录存在 .env 文件,执行 docker-compose 命令时将从该文件中读取变量。
在当前目录新建 .env 文件并写入以下内容。
支持 # 号注释MONGO_VERSION=3.6
执行 docker-compose up 则会启动一个 mongo:3.6 镜像的容器。

可视化图形工具 Portainer

Portainer是一个可视化的容器镜像的图形管理工具,利用Portainer可以轻松构建,管理和维护Docker环境。 而且完全免费,基于容器化的安装方式,方便高效部署。
官方站点: https://www.portainer.io

安装 Portainer

#portainer-ce项目代替portainer
[root@localhost ~]# docker pull portainer/portainer-ce
[root@localhost ~]# docker volume create portainer_data
portainer_data
[root@localhost ~]# docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE                      COMMAND         CREATED          STATUS           PORTS                                                                                               NAMES
22773d08f454   portainer/portainer-ce     "/portainer"    42 seconds ago   Up 41 seconds    0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, 0.0.0.0:9000->9000/tcp, :::9000->9000/tcp, 9443/tcp      portainer

浏览器访问 http://localhost:9000 可以看到登录界面,首次登录需要设置用户密码。
在这里插入图片描述

在这里插入图片描述

创建新用户

在这里插入图片描述

添加远程服务器

本地可以通过 /var/run/docker.sock 进行管理,这里远程采用的是ip+端口进行管理
在这里插入图片描述
添加远程服务器(这里是192.168.139.102,Portainer部署在192.168.139.101上)

docker开启远程端口管理,新增 -H tcp://0.0.0.0:2375
[root@localhost ~]# vim /lib/systemd/system/docker.service 
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 --containerd=/run/containerd/containerd.sock
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
[root@localhost ~]# lsof -i:2375
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
dockerd 1754 root    3u  IPv6  33252      0t0  TCP *:2375 (LISTEN)

在这里插入图片描述

这里远程采用的是ip+端口进行管理
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

将打好的tar包解压,每个镜像层对应着每个目录,镜像信息可以在mani、、、查看
repo····文件存放的是镜像名、tag号、镜像ID

进入其中的一层,解压tar包
在这里插入图片描述

docker history可以看到用的层数
docker pull 发现共用镜像层
删除

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值