一、docker简介

1.1 docker的前世今生

Docker是基于Go语言实现的开源容器项目,诞生于2013年年初,最初的发起者是dotCloud公司,Docker自开源后受到广泛的关注和讨论,目前已有多个相关项目(包括Docker三剑客、Kubernets等),逐渐形成了围绕Docker容器的生态体系。由于Docker在业界造成的影响力实在太大,dotCloud后来直接改名Docker Inc,并专注于Docker相关技术和产品的开发。

Docker项目已加入Liunx基金会,并遵循Apache2.0协议,全部开源代码均在https://github.com/docker/docker上进行维护。现在主流的Linux操作系统都已经支持Docker。

Docker的构想是要实现“Build,Skip and Run Any APP,Anywhere”,即通过对应用的封装(Packaging)、分发(Distribution)、部署(Deployment)、运行(Runtime)生命周期进行管理,达到应用组件“一次封装,到处运行”的目的。这里的应用组件,既可以是一个WEB应用、一个编译环境,也可以是一套数据库平台服务,甚至可以是一个操作系统或集群。

跟大部分新兴技术的诞生一样,Docker也并非“从石头缝里蹦出来的”,而是站在前人的肩膀上,其中最重要的是Linux容器(Linux Containers,LXC)技术。LXC是linux原生支持的容器技术,可以提供轻量级的虚拟化。LXC使用了两个主要的技术:

(1)namespace

Linux Namespaces机制提供一种资源隔离方案。

namespace隔离内容说明
UTS(unix timesharing system)主机名和域名每个容器拥有独立的主机名和域名,在网络上可被识别为独立的节点
IPC(Inter-Process Communication)信号量、消息队列和共享内存隔离进程间的通信
PID(Process Identification)进程编号隔离进程ID
Net(Network)网络设备、网络栈、端口等隔离网络
MNT(mount)挂载点(文件系统)隔离文件系统和提供硬盘挂载点
User(user)用户和用户组隔离用户和用户组

(2)cgroup

Cgroups 的全称是control groups,cgroups为每种可以控制的资源定义了一个子系统,用于限制、统计和隔离容器的资源使用,如CPU、内存、磁盘、I/O等

主要用到的cgroups子系统:cpu(限制进程的cpu使用率),blkio(可以限制进程的块设备IO),device(可以控制进程能够访问某些设备),freezer(可以挂起或恢复cgroups中的进程),memory(可以限制进程的memory使用量)

cgroups实现了对容器的资源分配和限制,比如给容器1分配10core 30G 内存,那这个容器最多用这么大的资源;如果内存超过30G ,会启动swap,效率降低,也可能会被调度系统给kill掉。

LXC 的工作原理基于 Linux 内核的命名空间和控制组(cgroups)技术,通过以下步骤实现容器的隔离和管理:

创建命名空间:为每个容器分配独立的命名空间,确保容器内的进程在自己的隔离环境中运行。 设置控制组:为容器分配资源限制,如 CPU、内存、磁盘 I/O 等,防止容器过度消耗主机资源。 挂载文件系统:为每个容器挂载独立的文件系统,确保容器的文件系统与宿主机隔离。 启动容器进程:在容器的命名空间和控制组内启动进程,容器内部的进程可以像在本地运行一样执行

LXC 工具:包括 lxc-create、lxc-start、lxc-stop、lxc-destroy 等,用于容器的创建、启动、停止和销毁。 LXC 配置文件:定义容器的配置信息,如网络、存储、资源限制等。 LXC 配置文件:以 config 文件形式存储,定义容器的各项配置,如内核参数、网络配置、存储配置等。

在Docker的早期版本中,它使用LXC作为底层容器引擎来实现容器隔离和虚拟化,Docker提供了各种容器管理工具(如分发、版本、移植等)让用户无需关注底层的操作,可以更简单管理和使用容器。

从技术的层面上看,LXC已经趟过了绝大部分的“坑”,完成了容器技术实用化的大半历程。但是随着时间的推移,Docker逐渐摆脱了对LXC的依赖,并自己实现了一个名为libcontainer的容器引擎,以取代LXC。Libcontainer是一个开源的Linux容器管理库,它是由Docker团队开发的,用于支持Docker容器引擎的底层。Libcontainer提供了一个接口,使得应用程序可以直接访问Linux内核中的容器相关功能,例如命名空间、控制组、文件系统等。

runC是一个基于Libcontainer的容器运行时工具,它是Docker容器引擎的一个组件。runC利用Libcontainer提供的接口,将容器镜像转换为容器实例,并在隔离环境中启动容器进程。runC还提供了对容器的生命周期管理,包括启动、停止、重启等。因此,可以说runC是Libcontainer的一个应用程序,用于管理和运行容器。同时,runC也是一个独立的开源项目,可以用于在各种容器环境中运行容器。

Docker Engine、runC和Libcontainer之间存在如下关系:

1、Docker Engine利用runC作为其默认的容器运行时工具,通过runC来管理和运行容器。 2、runC本身则是基于Libcontainer库开发的,它利用Libcontainer提供的接口来创建和管理容器。 3、Libcontainer是一个独立的开源库,它提供了一套API,使得应用程序可以直接访问Linux内核中的容器相关功能,例如命名空间、控制组、文件系统等。

基于Linux平台上的多项开源技术,Docker提供了高效、敏捷和轻量级的容器方案,并支持部署到本地环境和多种主流平台。可以说,Docker首次为应用的开发、运行和部署提供了“一站式”的实用解决方案

1.2 docker和虚拟机的区别

docker的优势

快速部署:短时间内可以部署成百上千个应用,更快速交付到线上。 高效虚拟化:不需要额外的hypervisor支持,直接基于linux 实现应用虚拟化,相比虚拟机大幅提高性能和效率。 节省开支:提高服务器利用率,降低IT支出。 简化配置:将运行环境打包保存至容器,使用时直接启动即可。 快速迁移和扩展:可跨平台运行在物理机、虚拟机、公有云等环境,良好的兼容性可以方便将应用从A宿主机迁移到B宿主机,甚至是A平台迁移到B平台。

docker的缺点

隔离性:各应用之间的隔离不如虚拟机彻底。

1.3 docker的oci规范

为了推进容器生态的健康发展,在linux基金会的主导下,docker、微软、红帽、谷歌和IBM等公司在2015年6月共同成立了一个open container initiative的组织,其目的就是制定开放的标准的容器规范。

OCI的规范分为包含runtime specimage spec两部分,不同的容器公司开发的容器只要兼容这两个规范,就可以保证容器的可移植性和相互可操作性。

(1)runtime spec

runtime运行时规范定义了如何配置和执行容器、容器的生命周期管理(包括容器的创建、启动、停止、删除等操作)以及容器的资源限制、命名空间隔离等。

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

  • 版本信息:oci规范的版本信息。

  • 容器ID:通常是一个哈希值,可以在所有state.json文件中提取出容器ID对容器进行批量操作(关闭、删除等),该文件在容器关闭后会被删除,容器启动后会自动生成。

  • status:容器的运行状态,包含以下几种:

    • creating:正在被创建

    • created:容器进程未退出,而用户的应用进程还未执行的状态

    • running:容器进程已经退出,而且用户的应用进程已经开始正常运行

    • stopped:容器进程已经退出

  • PID:在容器中运行的首个进程在宿主机上的进程号,即将宿主机的那个进程设置为容器的守护进程。

  • 容器文件目录:存放容器rootfs及相应配置的目录,外部程序只需读取state.json就可以定位到宿主机上的容器文件目录。

  • 容器创建:创建包括文件系统、namespace、cgroups、用户权限在内的各项内容。

  • 容器进程的启动:容器运行时通过一个json格式的配置文件来启动容器,该文件在/run/containerd/io.containerd.runtime.v1.linux/moby/容器ID/config.json。

  • 容器生命周期:容器进程可以被外部程序关停,runtime规范定义了对容器操作信号的捕获,并做相应的资源回收的处理,避免僵尸进程的出现。

目前主流的三种runtime:

lxc:linux上早期的runtime,docker早期就是采用lxc做为runtime。

runc:目前docker默认的runtime,runc遵守OCI规范。

rkt:Coreos开发的容器runtime,也符合OCI规范。

(2)image spec

镜像规范定义了容器镜像的格式和内容,以确保容器镜像在不同的容器运行时和工具之间具有互操作性和兼容性,镜像规范主要包含以下内容:

  • 镜像层(Layers):镜像由多个只读层组成,每个层包含了一组文件和上层之间的差异(增量)。这些层按照顺序堆叠,形成一个完整的文件系统。每个层通常是一个tar格式的文件。

  • 镜像配置(Image Configuration):镜像配置包含镜像的元数据,包括、镜像的创建时间、作者、根文件系统(rootfs)、历史。

  • 清单(Manifest):清单是镜像的描述文件,定义了镜像包含的所有层及其顺序、tag标签以及config文件名称

  • 索引(Index):指向不同平台的manifest文件,这个文件能保证一个镜像可以跨平台使用。

  • 媒体类型(Media Types):OCI镜像规范定义了不同类型对象的媒体类型。

  • 内容地址(Content Addressing):OCI镜像规范使用内容寻址的方式来唯一标识镜像的每个部分(层、配置、清单等)。每个对象通过其内容的哈希值来标识,这确保了镜像的完整性和一致性。常用的哈希算法是SHA-256。

  • 分层文件系统(Layered Filesystem):OCI镜像采用分层文件系统(Layered Filesystem)模型,每个镜像由多个层组成,层与层之间是增量关系。镜像的最终文件系统是通过将这些层叠加在一起实现的。这种方式使得镜像的存储和传输更加高效,因为不同镜像可以共享相同的基础层。

  • 分发(Distribution):OCI 镜像规范还定义了如何通过 HTTP API 分发和拉取镜像,确保镜像在不同的镜像仓库和运行时之间的互操作性。

特别说明:

我们常讲的 容器运行时是一个 混合概念,包含低级别容器运行时,和高级别容器运行时,

低级容器运行时,比如runc、lxc、gVisor、Kata容器等,它们主要负责真正与内核进行交互,创建和管理容器运行时的内核级别资源,如cgroups、namespaces等,但无法直接管理镜像。

高级容器运行时,比如Docker引擎、Podmancontainerd 等,它们基于低级容器运行时提供了更高层次的管理能力,可以管理容器生命周期并直接调用低级运行时,同时也内置镜像管理能力,能够拉取、推送镜像等。

两者关系是,高级运行时对低级运行时进行抽象,向上提供完整的容器管理接口。高级运行时在内部会调用对应的低级运行时来管理内核级资源和运行容器。

1.4 docker的组成

Docker组成:What is Docker? | Docker Docs

Docker主机(Host):一个物理机或虚拟机,用于运行Docker服务进程和容器。 Docker服务端(Server):Docker守护进程,运行docker容器。 Docker客户端(Client):客户端使用docker命令或其他工具调用dockerAPl。Docker 仓库(Registry):保存镜像的仓库,类似于git或svn这样的版本控制系统。官方仓库:https://hub.docker.com/ Docker镜像(lmages):镜像可以理解为创建实例使用的模板。 Docker容器(Container):容器是从镜像生成对外提供服务的一个或一组服务。

1.4.1docker镜像

docker镜像是一个特殊的文件系统,除了提供容器应用运行时所需的程序、库、资源、配置等文件外,还包含了一些应用运行时的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

docker镜像构建时, 利用 Union FS (联合文件系统)的技术,Union FS支持将不同目录mount 到同一个目录中从而形成一个虚拟文件系统,同时Union FS支持对文件系统的修改作为一次提交来一层层叠加。docker 通过这些文件再加上宿主机的内核提供了一个 linux 的虚拟环境。

联合文件系统可以对每一层文件系统设置三种权限:只读(readonly)、读写(readwrite)和写出(whiteout-able),但是docker镜像中每一层文件系统都是只读的。构建镜像的时候从一个最基本的操作系统开始,每个构建的操作都相当于增加并修改了一层文件系统,一层层往上叠加,上层的修改会覆盖底层该位置的可见性。当使用镜像的时候我们只会看到一个完全的整体,不知道里面有几层也不需要知道里面有几层。在Docker 中,底下的只读层就是 image,可写层就是 Container。结构如下:

docker使用unionFS(统一文件系统)技术将不同的层整合成一个文件系统,为这些层提供一个统一的视角。Docker镜像实际上是由多个不同的联合文件系统组成:

  • 最底层:bootfs,包含bootloader(引导加载程序)和 kernel(内核),使用宿主机的bootfs。

  • 第二层:root文件系统rootfs,包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc等标准目录和文件,称为base image。

  • 然后再往上叠加其他的文件系统

不同的linux发行版,bootfs基本一样,而rootfs不同,如ubuntu,centos等。由于docker镜像使用宿主机的bootfs层,这使得镜像本身只需要包含rootfs层所需的文件和工具即可。因此镜像占用的存储空间比较少,有部分精简镜像只有几MB。两个不同的docker镜像在同一个宿主机内核上实现不同的rootfs如下所示:

分层存储的特征使得docker镜像的复用、 定制变的更为容易。 甚至可以用之前构建好的镜像作为基础层, 然后进一步添加新的层, 以定制自己所需的内容, 构建新的镜像 。

目前docker的默认存储引擎为overlay2,不同的存储引擎需要相应的系统支持。

存储驱动类型有如下几种:

1、AUFS (AnotherUnionFS)是一种 Union FS,是文件级的存储驱动。联合文件系统支持将不同目录挂载到同一个目录,这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。当需要修改一个文件时,AUFS 创建该文件的一个副本,使用CoW 将文件从只读层复制到可写层进行修改,结果也保存在可写层。是Docker 18.06 及更早版本的首选存储驱动程序。

2、Overlay:一种 Union FS 文件系统,Linux 内核 3.18 后支持。也是一种Union FS,和AUFS的多层不同的是Overlay只有两层:一个upper文件系统和一个lower文件系统,分别代表Docker的镜像层和容器层。当需要修改一个文件时,使用CoW将文件从只读的lower复制到可写的upper进行修改,结果也保存在upper层。

3、overlay2:Overlay 的升级版,到目前为止,所有Linux 发行版推荐使用的存储类型。 4、devicemapper:是 CentOS 和 RHEL 的推荐存储驱动程序,使用块存储而非文件级的存储。因为之前的内核版本不支持 overlay2,但是当前较新版本的 CentOS 和 RHEL 现在已经支持overlay2,因此推荐使用 overlay2。 5、ZFS(Sun-2005)/btrfs(Oracle-2007):一种具有快照、复制等特性的文件系统,适用于高容量存储。目前没有广泛使用。 6、vfs:用于测试环境,适用于无法使用 copy-on-write 文件系统的情况。此存储驱动程序的性能很差,通常不建议用于生产。

1.4.2 docker容器

容器:镜像运行时的实体,Docker利用容器来运行应用。 镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。 容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、 自己的网络配置、 自己的进程空间, 甚至自己的用户 ID 空间。 容器内的进程是运行在一个隔离的环境里, 使用起来就好像是在一个独立于宿主的系统下操作一样。 这种特性使得容器封装的应用比直接在宿主运行更加安全。 也因为这种隔离的特性, 很多人初学 Docker 时常常会混淆容器和虚拟机。 镜像使用的是分层存储, 容器也是如此。 每一个容器运行时, 是以镜像为基础层,在其上创建一个当前容器的存储层, 我们可以称这个为了容器运行时读写而准备的存储层为容器存储层。 容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。 按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。 数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新 run,数据却不会丢失。

1.4.3 docker仓库

仓库:集中存放镜像文件的地方 镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其他服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。 一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。 所以说,镜像仓库是 Docker 用来集中存放镜像文件的地方,类似于我们之前常用的代码仓库。 通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。 我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以latest 作为默认标签。 以 Ubuntu 镜像 为例, ubuntu 是仓库的名字, 其内包含有不同的版本标签, 如, 14.04 ,16.04 。 我们可以通过 ubuntu:14.04 , 或者 ubuntu:16.04 来具体指定所需哪个版本的镜像。 如果忽略了标签, 比如 ubuntu , 那将视为 ubuntu:latest 。仓库名经常以两段式路径形式出现, 比如 jwilder/nginx-proxy , 前者往往意味着 Docker Registry 多用户环境下的用户名, 后者则往往是对应的软件名。 但这并非绝对, 取决于所使用的具体 Docker Registry 的软件或服务。

仓库分为公开仓库( Public)和私有仓库( Private) 两种形式,即Docker Registry 公开服务和私有 Docker Registry 。

  • Docker Registry 公开服务

Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。 一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。 最常使用的 Registry 公开服务是官方的 Docker Hub ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:hub.docker.com 。 在国内访问 Docker Hub 可能会比较慢,国内的一些云服务商提供了针对 Docker Hub 的镜像服务( Registry Mirror ), 这些镜像服务被称为加速器。 常见的有阿里云加速器、 DaoCloud 加速器等。 使用加速器会直接从国内的地址下载 Docker Hub 的镜像, 比直接从 Docker Hub 下载速度会提高很多。

  • 私有docker registry

除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。

开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 Docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 Docker Trusted Registry 中, 提供了这些高级功能。

除了官方的 Docker Registry 外, 还有第三方软件实现了 Docker Registry API, 甚至提供了用户界面以及一些高级功能。 比如, VMWare Harbor 和 Sonatype Nexus。

二、安装与部署

2.1 安装环境概述

Docker划分为CE和EE,CE为社区版(免费,支持周期三个月),EE为企业版(强调安全,付费使用)。

Docker CE每月发布一个Edge版本(17.03,17.04,17.05……),每三个月发布一个Stable版本(17.03,17.06,17.09……);docker EE和stable版本号保持一致,每个版本提供一年维护。

(1)依赖的基础环境

  • 64 位CPU

  • Linux kernel(内核) 3.10+

  • Linux kernel cgroups and namespaces

(2)查询自己服务器的环境

① 使用的操作系统版本

# cat /etc/redhat-release
Red Hat Enterprise Linux release 9.3 (Plow)

② 内核版本

# uname -r
5.14.0-362.8.1.el9_3.x86_64

③ ip地址

# hostname -I

④卸载旧版本,旧版本的Docker称为docker或者docker-engine:

# yum remove docker docker-common docker-selinux docker-engine

2.2 安装docker

docker的安装官方文档:Install Docker Engine on CentOS | Docker Docs

2.2.1 使用yum安装

(1)添加docker-ce 源信息

安装依赖包(yum-utils 提供了 yum-config-manager):
# yum install yum-utils -y
# yum-config-manager  --add-repo https://mirrors.aliyun.com/docker-ce/linux/rhel/docker-ce.repo

(2)修改docker-ce源

# sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo

##或者手动写仓库
# cat docker-ce.repo
[docker-ce]
name=cocker-ce
baseurl=https://mirrors.aliyun.com/docker-ce/linux/rhel/9/x86_64/stable/
gpgcheck=0

(3)更新源

# yum makecache

(4)安装docker-ce

①默认安装docker-ce是最新版本

#  yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
# rpm -q docker-ce
docker-ce-27.1.2-1.el9.x86_64
# systemctl start docker
# systemctl status docker

②安装指定版本

# yum list docker-ce --showduplicates |  sort -r
[root@rhel9 ~]# yum install docker-ce-26.1.4-1.el9 -y
[root@rhel9 ~]# systemctl start docker
[root@rhel9 ~]# systemctl status docker

2.2.2 二进制安装

官方指导文档:Install Docker Engine from binaries | Docker Docs

(1)下载二进制文件

二进制文件的下载路径:Index of linux/static/stable/

#此次安装的是docker-25.0.5
[root@rocky9 ~]# mkdir -p /data
[root@rocky9 ~]# wget -P /data/ https://download.docker.com/linux/static/stable/x86_64/docker-25.0.5.tgz
[root@rocky9 ~]# cd /data/
[root@rocky9 data]# tar xf docker-25.0.5.tgz
[root@rocky9 data]# cp docker/*  /usr/bin/
[root@rocky9 data]# dockerd  &

2.3 查看当前docker信息

在docker安装之后,默认会生成一个名称为docker0的网卡并且默认IP地址为172.17.0.1

[root@rhel9 ~]# ip a show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:2e:42:6f:44 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever

查看docker当前的版本

#验证docker的版本
[root@rhel9 ~]# docker version
Client: Docker Engine - Community
 Version:           27.1.2
 API version:       1.46
 Go version:        go1.21.13
 Git commit:        d01f264
 Built:             Mon Aug 12 11:52:49 2024
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          27.1.2
  API version:      1.46 (minimum version 1.24)
  Go version:       go1.21.13
  Git commit:       f9522e5
  Built:            Mon Aug 12 11:51:10 2024
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.7.20
  GitCommit:        8fc6bcff51318944179630522a095cc9dbf9f353
 runc:
  Version:          1.1.13
  GitCommit:        v1.1.13-0-g58aa920
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

查看当前的容器、镜像、仓库等信息

[root@rhel9 ~]# docker info
Client: Docker Engine - Community
 Version:    27.1.2
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.16.2
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.29.1
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 0   #当前主机运行的容器总数
  Running: 0     #有几个容器是正在运行的
  Paused: 0     #有几个容器是暂停的
  Stopped: 0    #有几个容器是停止的
 Images: 0     #当前主机上的镜像数
 Server Version: 27.1.2  #服务端版本
 Storage Driver: overlay2  #正在使用的存储引擎
  Backing Filesystem: xfs  #后端文件系统,即服务器的磁盘文件系统
  Supports d_type: true   #是否支持d_type
  Using metacopy: false   
  Native Overlay Diff: true  #是否支持差异数据存储
  userxattr: false
 Logging Driver: json-file  #日志类型
 Cgroup Driver: systemd  #cgroups类型
 Cgroup Version: 2
 Plugins:  #插件
  Volume: local
  Network: bridge host ipvlan macvlan null overlay  
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 Swarm: inactive  
 Runtimes: io.containerd.runc.v2 runc  #已安装的容器运行时
 Default Runtime: runc #默认使用的容器运行时
 Init Binary: docker-init  #初始化容器的守护进程,即pid为1的进程
 containerd version: 8fc6bcff51318944179630522a095cc9dbf9f353
 runc version: v1.1.13-0-g58aa920
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 5.14.0-362.8.1.el9_3.x86_64
 Operating System: Red Hat Enterprise Linux 9.3 (Plow)
 OSType: linux
 Architecture: x86_64
 CPUs: 2  #宿主机cpu数量
 Total Memory: 1.692GiB  #宿主机总内存
 Name: rhel9
 ID: 681118d1-e2e3-41a7-b24d-0ae36f8cc338
 Docker Root Dir: /var/lib/docker  #宿主机数据保存目录
 Debug Mode: false
 Experimental: false   #是否测试版
 Insecure Registries:   #非安全的镜像仓库
  127.0.0.0/8  
 Live Restore Enabled: false  #是否开启活动重启(重启docker-daemon不关闭容器)

容器的创建与管理过程:

1、dockerd进程:docker client直接访问dockerd(其父进程为宿主机的systemd守护进程)进程。dockerd通过grpc和containerd模块通信,dockerd和containerd通信的socket文件:/run/containerd/containerd.sock。

2、containerd进程:containerd在dockerd启动时被启动,然后containerd启动grpc请求监听,containerd根据grpc请求做相应动作。containerd可以在宿主机中管理完整的容器生命周期(容器镜像的传输和存储,容器的执行和管理,存储和网络等)。

3、containerd-shim进程:若是创建容器,containerd拉起一个/usr/bin/containerd-shim进程,并进行相应的创建操作。containerd-shim套件其目的主要是隔离containerd和容器。

4、containerd-shim-runc-v2进程: containerd-shim启动后会去启动/usr/bin/containerd-shim-runc-v2,然后立即退出,此时containerd-shim-runc-v2的父进程就变成了systemd(1),这样containerd-shim-runc-v2就和containerd脱离了关系,即便containerd退出也不会影响到容器(这也是containerd-shim套件的作用) 。

5、runc:OCl标准的具体实现就是runc,真正创建和维护容器便是由runc来完成的。/usr/bin/containerd-shim-runc-v2会启动runc去create、start容器,然后runc立即退出,容器的父进程就变成了containerd-shim-runc-v2。containerd-shim-runc-v2会收集容器进程的状态,上报给containerd。

6、docker-proxy:实现容器通信,其父进程为dockerd

[root@rhel9 yum.repos.d]# ps -ef | egrep "(docker|containerd)"
root        3751       1  1 09:45 ?        00:00:00 /usr/bin/containerd
root        3760       1  1 09:45 ?        00:00:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
[root@rhel9 ~]# pstree -p | egrep "(systemd|docker|containerd)"
systemd(1)-+-ModemManager(886)-+-{ModemManager}(925)
           |-containerd(3751)-+-{containerd}(3752)
           |                  |-{containerd}(3753)
           |                  |-{containerd}(3754)
           |                  |-{containerd}(3755)
           |                  |-{containerd}(3756)
           |                  |-{containerd}(3758)
           |                  `-{containerd}(3759)
           |-dockerd(3760)-+-{dockerd}(3761)
           |               |-{dockerd}(3762)
           |               |-{dockerd}(3763)
           |               |-{dockerd}(3764)
           |               |-{dockerd}(3765)
           |               |-{dockerd}(3766)
           |               |-{dockerd}(3767)
           |               `-{dockerd}(3768)

#运行一个容器
[root@master0101 ~]# docker run -d  nginx:1.27-alpine
4a2eb061a84206517b8645da5161e0711d752c5f2d3fb159f3b22164da86ed1b

#查看进程
[root@master0101 ~]# ps -ef | grep containerd
root        3751       1  0 09:45 ?        00:00:00 /usr/bin/containerd
root        3760       1  0 09:45 ?        00:00:00 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root       11106       1  0 10:04 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 4a2eb061a84206517b8645da5161e0711d752c5f2d3fb159f3b22164da86ed1b -address /run/containerd/containerd.sock

#再次运行容器,进行端口映射,可以通过宿主机端口访问容器
[root@master0101 ~]# docker run -d -P nginx:1.27-alpine
f134ebaabc20d266ab326a64d4ff521516dbdda2c0617114abb5bbae0922b247
#查看容器端口映射到宿主机的端口
[root@master0101 ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                   CREATED              STATUS              PORTS                                       NAMES
f134ebaabc20   nginx:1.27-alpine   "/docker-entrypoint.…"   About a minute ago   Up About a minute   0.0.0.0:32768->80/tcp, [::]:32768->80/tcp   happy_gauss

#查看docker-proxy进程
[root@master0101 ~]# ps -ef | grep docker-proxy
root       26183    3760  0 10:44 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 32768 -container-ip 172.17.0.2 -container-port 80
root       26195    3760  0 10:44 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 32768 -container-ip 172.17.0.2 -container-port 80
root       26601    9640  0 10:44 pts/1    00:00:00 grep --color=auto docker-proxy

2.4 docker镜像加速配置

国内从Docker Hub拉取镜像有时会遇到困难,可以通过修改docker配置文件的方式添加一个加速器,可以通过加速器达到下载镜像的目的。

(1)从2017年6月9日起,Docker 官方提供了在中国的加速器,以解决墙的问题。不用注册,直接使用加速器地址:https://registry.docker-cn.com 即可。

(2)中国科技大学的镜像加速器:中科大的加速器不用注册,直接使用地址 https://docker.mirrors.ustc.edu.cn/ 配置加速器即可。进一步的信息可以访问:Docker Hub - USTC Mirror Help

(3)阿里云加速器:注册阿里云开发账户(免费的)后,访问这个链接就可以看到加速器地址: 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台

(4)DaoCloud 加速器:注册 DaoCloud 账户(支持微信登录),然后访问: https://www.daocloud.io/mirror#accelerator-doc

#配置docker镜像加速
[root@rhel9 ~]# cat /etc/docker/daemon.json
{
        "registry-mirrors": [
        "https://docker.m.daocloud.io",
        "https://huecker.io",
        "https://dockerhub.timeweb.cloud",
        "https://noohub.ru",
        "https://hub.oepkgs.net",
		"https://dockerhub.timeweb.cloud" 
        ]
}
[root@rhel9 ~]# systemctl daemon-reload
[root@rhel9 ~]# systemctl restart docker
[root@rhel9 ~]# docker info  | grep -iA 4 registry
 Registry Mirrors:
  https://docker.m.daocloud.io/
  https://huecker.io/
  https://dockerhub.timeweb.cloud/
  https://noohub.ru/
#出现该内容表示镜像加速配置成功
#拉取hello-world镜像,测试能否拉取
[root@rhel9 ~]# docker pull hello-world
Using default tag: latest
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:53cc4d415d839c98be39331c948609b659ed725170ad2ca8eb36951288f81b75
Status: Downloaded newer image for hello-world:latest
docker.io/library/hello-world:latest
[root@rhel9 ~]# docker images
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
hello-world   latest    d2c94e258dcb   15 months ago   13.3kB

三、docker基础命令

3.1 镜像相关操作

3.1.1 增加镜像

(1)搜索镜像

(2)拉取镜像

Docker Hub上有大量的高质量的镜像可以用。从Docker镜像仓库获取镜像的命令是docker pull,其命令格式为:

docker   pull   [选项]   [Docker Registry地址[:端口号]/]仓库名[:标签]

docker镜像仓库地址:地址的格式一般是<域名/IP>:[端口号],不写的话默认地址是Docker Hub。

仓库名:仓库名是两段式名称,即<用户名>/<软件名>。对于Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像。

一个仓库会包含同一个软件不同版本的镜像, 而标签就常用于对应该软件的各个版本。

#根据镜像名称(tag指定版本)拉取镜像  
[root@localhost ~]# docker pull nginx:1.27.2-alpine
1.27.2-alpine: Pulling from library/nginx
43c4264eed91: Pull complete
d1171b13e412: Pull complete
596d53a7de88: Pull complete
f99ac9ba1313: Pull complete
fd072e74e282: Pull complete
379754eea6a7: Pull complete
45eb579d59b2: Pull complete
472934715761: Pull complete
Digest: sha256:2140dad235c130ac861018a4e13a6bc8aea3a35f3a40e20c1b060d51a7efd250
Status: Downloaded newer image for nginx:1.27.2-alpine
docker.io/library/nginx:1.27.2-alpine

alpine 版本:构建容器小镜像的发行版本

(3)导入镜像

#假设当前主机有之前提前下载好的镜像文件
[root@localhost dockerimages]# pwd
/dockerimages
[root@localhost dockerimages]# ll
总用量 16856
-rw------- 1 root root 17257984 10月 15 16:55 nginx-1.14-alpine.tar.gz
#可以使用以下命名导入之前旧的镜像【docker image load=docker load】
[root@localhost dockerimages]# docker image load  -i nginx-1.14-alpine.tar.gz
a464c54f93a9: Loading layer  5.796MB/5.796MB
5ac9a5170bf2: Loading layer  11.43MB/11.43MB
b2cbae4b8c15: Loading layer  3.584kB/3.584kB
076c58d2644f: Loading layer  4.608kB/4.608kB
Loaded image: nginx:1.14-alpine
[root@localhost dockerimages]# docker images nginx:1.14-alpine
REPOSITORY   TAG           IMAGE ID       CREATED       SIZE
nginx        1.14-alpine   8a2fb25a19f5   5 years ago   16MB

3.1.2 查看镜像

(1)查看镜像信息

#查看所有镜像,也可以使用命令docker image ls -a
[root@localhost dockerimages]# docker images 
REPOSITORY   TAG             IMAGE ID       CREATED        SIZE
nginx        1.27.2-alpine   cb8f91112b6b   12 days ago    47MB
nginx        1.14-alpine     8a2fb25a19f5   5 years ago    16MB
#列表包含了仓库名、标签、镜像ID、创建时间以及所占用的空间。
#其中镜像ID则是镜像的唯一标识,一个镜像可以对应多个标签。
[root@localhost dockerimages]# docker image ls nginx
REPOSITORY   TAG             IMAGE ID       CREATED        SIZE
nginx        1.27.2-alpine   cb8f91112b6b   12 days ago    47MB
nginx        1.14-alpine     8a2fb25a19f5   5 years ago    16MB
[root@localhost dockerimages]# docker image ls nginx:1.14-alpine
REPOSITORY   TAG           IMAGE ID       CREATED       SIZE
nginx        1.14-alpine   8a2fb25a19f5   5 years ago   16MB

镜像体积说明:

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

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

(2)查看镜像的层数

[root@localhost dockerimages]# docker history nginx:1.14-alpine
IMAGE          CREATED       CREATED BY                                       SIZE      COMMENT
8a2fb25a19f5   5 years ago   /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
<missing>      5 years ago   /bin/sh -c #(nop)  STOPSIGNAL SIGTERM            0B
<missing>      5 years ago   /bin/sh -c #(nop)  EXPOSE 80                     0B
<missing>      5 years ago   /bin/sh -c #(nop) COPY file:ebf4f0eb33621cc0…   1.09kB
<missing>      5 years ago   /bin/sh -c #(nop) COPY file:4c82b9f10b84c567…   643B
<missing>      5 years ago   /bin/sh -c GPG_KEYS=B0F4253373F8F6F510D42178…   10.5MB
<missing>      5 years ago   /bin/sh -c #(nop)  ENV NGINX_VERSION=1.14.2      0B
<missing>      5 years ago   /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B
<missing>      5 years ago   /bin/sh -c #(nop)  CMD ["/bin/sh"]               0B
<missing>      5 years ago   /bin/sh -c #(nop) ADD file:2e3a37883f56a4a27…   5.53MB
#missing表示这层,或者这个构建步骤在其他的系统上构建的,并且从DockerHub上拉取过来的。

(3)标签为none的镜像

如果查看镜像的显示结果中出现了没有仓库名,也没有标签,均为<none>的镜像,可能是因为旧的镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了<none>。除了 docker pull 可能导致这种情况, docker build 也同样可以导致这种现象。 由于新旧镜像同名, 旧镜像名称被取消, 从而出现仓库名、 标签均为 <none> 的镜像。 这类无标签镜像也被称为虚悬镜像 (dangling image) , 可以用下面的命令专门显示这类镜像:

[root@localhost ~]# docker image ls -f dangling=true

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

[root@localhost ~]# docker image prune

(3)查看镜像详细信息

[root@localhost dockerimages]# docker image inspect  nginx:1.27.2-alpine

3.1.3 修改镜像,导出镜像

(1)给镜像重新打标签

#拉取nginx:latest镜像并重新打标签
[root@localhost ~]# docker run -it  --rm nginx:latest /bin/bash
root@589428e6a5f6:/# nginx -v
nginx version: nginx/1.27.2
root@589428e6a5f6:/# exit
#重新打标签
[root@localhost ~]# docker tag nginx:latest nginx:1.27.2

(2)导出镜像为文件

  • 可以同时导出多个镜像为一个文件;

  • 指定.tar.gz 可以导出并压缩。

#导出镜像,-o:指定导出镜像的位置;
[root@localhost ~]# cd /dockerimages/
[root@localhost dockerimages]# docker image save -o nginx-1.27.2.tar.gz nginx:1.27.2
[root@localhost dockerimages]# ll nginx-1.27.2.tar.gz
-rw------- 1 root root 195826176 10月 16 12:36 nginx-1.27.2.tar.gz

3.1.4 删除镜像

#可以通过标签删除镜像
[root@localhost dockerimages]# docker image rm  nginx:latest
Untagged: nginx:latest

#也可以使用ID删除镜像
[root@localhost dockerimages]# docker image ls nginx:1.27.2
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        1.27.2    7f553e8bbc89   13 days ago   192MB
[root@localhost dockerimages]# docker image rm 7f5
Untagged: nginx:1.27.2
Untagged: nginx@sha256:d2eb56950b84efe34f966a2b92efb1a1a2ea53e7e93b94cdf45a27cf3cd47fc0
Deleted: sha256:7f553e8bbc897571642d836b31eaf6ecbe395d7641c2b24291356ed28f3f2bd0
Deleted: sha256:3ba9e1b5feaec13b6ac4c4039ff77db13d386040a109f2c378707e70a16ceff0
Deleted: sha256:84bde598cdfbb8af5b5509ede416a448ab7b8e38a4b7e5f2808c096739cb10e8
Deleted: sha256:c51526393f89152369e3f34f04409221511ff198968d6e221a7f9f4ed47369c7
Deleted: sha256:9663f01c380b3953165314a61b7f06e064a1ca39914d7bf6bc047f11d2836df8
Deleted: sha256:eb228edcd2ea132c0353513e457bae71a4c06332541ac4517b06f70b71b7e977
Deleted: sha256:b3f70c08ab98515399e812f56f987b033a998abc2fceaa683f432cad5a8bd04a
Deleted: sha256:8d853c8add5d1e7b0aafc4b68a3d9fb8e7a0da27970c2acf831fe63be4a0cd2c

删除时的输出提示:

  • Untagged

镜像的唯一标识是其 ID 和摘要, 而一个镜像可以有多个标签,首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。

因为一个镜像可以对应多个标签, 因此当我们删除了所指定的标签后, 可能还有别的标签指向了这个镜像, 如果是这种情况, 那么 Delete 行为就不会发生。 所以并非所有的 docker image rm 都会产生删除镜像的行为, 有可能仅仅是取消了某个标签而已。

  • Deleted

当该镜像所有的标签都被取消了, 该镜像很可能会失去了存在的意义, 因此会触发删除行为。

镜像是多层存储结构, 因此在删除的时候也是从上层向基础层方向依次进行判断删除。

镜像的多层结构让镜像复用变动非常容易, 因此很有可能某个其它镜像正依赖于当前镜像的某一层。 这种情况, 依旧不会触发删除该层的行为。 直到没有任何层依赖当前层时, 才会真实的删除当前层。

除了镜像依赖以外, 还需要注意的是容器对镜像的依赖。 如果有用这个镜像启动的容器存在( 即使容器没有运行), 那么同样不可以删除这个镜像

3.2 容器相关操作

3.2.1 启动容器

Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。容器内的第一个进程必须一直处于运行的状态,否则这个容器,就会处于退出状态!

(1)create,start运行容器——不推荐

docker create 镜像名
docker start 容器名
docker  restart 容器名
[root@localhost dockerimages]# docker create  nginx:1.27.2-alpine
28cf58a694d09b615e171e705b038fa26a1cc2065e7f4b324e7c3528c4a5563e
[root@localhost dockerimages]# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                   CREATED         STATUS    PORTS     NAMES
28cf58a694d0   nginx:1.27.2-alpine   "/docker-entrypoint.…"   2 seconds ago   Created             modest_hawking

#需要使用start启动容器
[root@localhost dockerimages]# docker start 28c
#查看正在运行的容器
[root@localhost dockerimages]# docker ps
CONTAINER ID   IMAGE                 COMMAND                   CREATED          STATUS        PORTS     NAMES
28cf58a694d0   nginx:1.27.2-alpine   "/docker-entrypoint.…"   36 seconds ago   Up 1 second   80/tcp    modest_hawking

(2)run运行容器

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

options 常用命令选项 -t :打开一个终端,像使用交换机一样使用容器 -i:交互式访问 --name:容器名字 --network:指定网络 --rm:容器一停,自动删除 -d:后台运行容器,返回容器ID;否则会一直占据着终端 -p:端口映射,将容器内服务的端口映射在宿主机的指定端口,格式为:宿主机端口:容器端口

-P: 随机端口映射,容器内部端口随机映射到主机的端口

  • 后台运行容器

[root@localhost dockerimages]# docker run --name web1 -d -p 8888:80 nginx:1.14-alpine
427c87d472979472b65a21243437feb71dc0399d44187da441bf845eb5879cb9
#查询docker端口映射
[root@localhost ~]# docker port web1
80/tcp -> 0.0.0.0:8888
80/tcp -> [::]:8888
#启动容器后,dockerd,docker-proxy,containerd,容器中的进程之间的关系
[root@localhost ~]# ps -ef | grep docker
root        1334       1  0 12:02 ?        00:00:18 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root      194118    1334  0 18:19 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8888 -container-ip 172.17.0.2 -container-port 80
root      194124    1334  0 18:19 ?        00:00:00 /usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 8888 -container-ip 172.17.0.2 -container-port 80
root      200158   35149  0 18:34 pts/1    00:00:00 grep --color=auto docker
[root@localhost ~]# ps -ef | grep -v docker |grep container
root         915       1  0 12:02 ?        00:00:08 /usr/bin/containerd
root      194165       1  0 18:19 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 47323e62e9024c75127e13f3851cbbff82a7d1f89c483d769906aafb5a6003aa -address /run/containerd/containerd.sock
root      200776   35149  0 18:36 pts/1    00:00:00 grep --color=auto container
[root@localhost ~]# ps -ef | grep 194165
root      194165       1  0 18:19 ?        00:00:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 47323e62e9024c75127e13f3851cbbff82a7d1f89c483d769906aafb5a6003aa -address /run/containerd/containerd.sock
root      194197  194165  0 18:19 ?        00:00:00 nginx: master process nginx -g daemon off;
  • 交互式运行容器

[root@localhost dockerimages]# docker run --name busybox1 -it busybox:1.36  /bin/sh
/ # ls
bin    dev    etc    home   lib    lib64  proc   root   sys    tmp    usr    var
#退出容器
/ # exit
  • 查看所有运行和未运行的容器

    #查看所有容器:包括正在运行和已经退出的
    [root@localhost dockerimages]# docker ps -a
    CONTAINER ID   IMAGE                 COMMAND                   CREATED              STATUS                          PORTS                                     NAMES
    475fb18c5356   busybox:1.36          "/bin/sh"                 About a minute ago   Exited (0) About a minute ago                                             busybox1
    427c87d47297   nginx:1.14-alpine     "nginx -g 'daemon of…"   2 minutes ago        Up 2 minutes                    0.0.0.0:8888->80/tcp, [::]:8888->80/tcp   web1
    28cf58a694d0   nginx:1.27.2-alpine   "/docker-entrypoint.…"   18 minutes ago       Up 17 minutes                   80/tcp                                    modest_hawking
  • 进入容器运行命令

[root@localhost ~]# docker run --name web1 -d -p 8888:80 nginx:1.14-alpine
45c601136fabbdf0f1e3f2c2a6d2bf95c6bb0e6e0f1cb8aa0879ac255ed2a92e
[root@localhost ~]# docker exec -it web1 /bin/sh
/ # cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      45c601136fab
#容器中的dns默认使用宿主机的dns,可以在运行容器时使用--dns指定别的dns地址
/ # cat /etc/resolv.conf
# Generated by Docker Engine.
# This file can be edited; Docker Engine will not make further changes once it
# has been modified.

nameserver 114.114.114.114
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
47: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # curl 172.17.0.2:80
/bin/sh: curl: not found
#此时提示没有curl命令,可以使用nsenter进入容器
#查看web1的进程号
[root@localhost ~]#  docker inspect -f {{.State.Pid}} web1
246707
[root@localhost ~]# nsenter -t 246707  -u -i -n
[root@45c601136fab ~]# 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
47: eth0@if48: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@45c601136fab ~]# curl -I 172.17.0.2
HTTP/1.1 200 OK

3.2.2 查看容器信息

(1)查看容器信息

[root@localhost dockerimages]# docker ps  --help

Usage:  docker ps [OPTIONS]

List containers

Aliases:
  docker container ls, docker container list, docker container ps, docker ps

#查看正在运行的容器
docker ps=docker container ls
#查看所有容器:包括正在运行的和已经停止的
docker ps -a= docker container ls -a
#查看所有容器的id信息
docker ps -a -q
#查看所有已退出容器的id信息
docker ps -aq -f status=exited
[root@localhost dockerimages]# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                   CREATED          STATUS                     PORTS                                     NAMES
475fb18c5356   busybox:1.36          "/bin/sh"                 5 minutes ago    Exited (0) 5 minutes ago                                             busybox1
427c87d47297   nginx:1.14-alpine     "nginx -g 'daemon of…"   7 minutes ago    Up 7 minutes               0.0.0.0:8888->80/tcp, [::]:8888->80/tcp   web1
28cf58a694d0   nginx:1.27.2-alpine   "/docker-entrypoint.…"   22 minutes ago   Up 21 minutes              80/tcp                                    modest_hawking

(2)查看端口映射信息

[root@localhost dockerimages]# docker port --help

Usage:  docker port CONTAINER [PRIVATE_PORT[/PROTO]]

List port mappings or a specific mapping for the container

Aliases:
  docker container port, docker port
[root@localhost dockerimages]# docker port web1
80/tcp -> 0.0.0.0:8888
80/tcp -> [::]:8888

(3)查看容器详细信息

[root@localhost dockerimages]# docker inspect web1 | grep -i ipaddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.3",
                    "IPAddress": "172.17.0.3",

(4)查看容器日志

[root@localhost dockerimages]# docker logs -f  web1
172.17.0.1 - - [16/Oct/2024:05:09:40 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.76.1" "-"
192.168.168.101 - - [16/Oct/2024:05:11:13 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.76.1" "-"
172.17.0.1 - - [16/Oct/2024:05:11:42 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.76.1" "-"
#打开另一个终端
[root@localhost ~]# docker port web1
80/tcp -> 0.0.0.0:8888
80/tcp -> [::]:8888
[root@localhost ~]# hostname -I
192.168.168.101 172.17.0.1
#通过容器ip地址访问nginx服务
[root@localhost ~]# curl 172.17.0.3
#通过宿主机ip地址访问容器中的nginx
[root@localhost ~]# curl 192.168.168.101:8888
[root@localhost ~]# curl 172.17.0.1:8888

(5)动态查看容器占用内存和cpu信息

[root@localhost ~]# docker stats web1
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O      PIDS
427c87d47297   web1      0.00%     2.332MiB / 3.532GiB   0.06%     4.84kB / 4.96kB   10MB / 4.1kB   2

(6)查看容器中的进程

[root@localhost ~]# docker top web1
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                194197              194165              0                   18:19               ?                   00:00:00            nginx: master process nginx -g daemon off;
100                 194224              194197              0                   18:19               ?                   00:00:00            nginx: worker process

3.2.3 删除容器

docker stop 关闭运行的容器
docker kill  杀死运行的容器
		-s:指定信号,和kill 用法一样;-9 强制停止容器
docker rm [-f]
docker  container prune #删除所有处于终止状态的容器 
#停止容器并删除
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE                 COMMAND                   CREATED          STATUS          PORTS                                     NAMES
427c87d47297   nginx:1.14-alpine     "nginx -g 'daemon of…"   14 minutes ago   Up 14 minutes   0.0.0.0:8888->80/tcp, [::]:8888->80/tcp   web1
28cf58a694d0   nginx:1.27.2-alpine   "/docker-entrypoint.…"   29 minutes ago   Up 29 minutes   80/tcp                                    modest_hawking
[root@localhost ~]# docker stop  28c
28c
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                   CREATED          STATUS          PORTS                                     NAMES
427c87d47297   nginx:1.14-alpine   "nginx -g 'daemon of…"   14 minutes ago   Up 14 minutes   0.0.0.0:8888->80/tcp, [::]:8888->80/tcp   web1
[root@localhost ~]# docker rm 28c

#强制删除正在运行的容器
[root@localhost ~]# docker rm -f web1
web1
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND     CREATED          STATUS                      PORTS     NAMES
475fb18c5356   busybox:1.36   "/bin/sh"   14 minutes ago   Exited (0) 14 minutes ago             busybox1

#删除所有已经停止的容器
[root@localhost ~]# docker run busybox:latest
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE            COMMAND     CREATED          STATUS                      PORTS     NAMES
89d16c436402   busybox:latest   "sh"        2 seconds ago    Exited (0) 1 second ago               nervous_grothendieck
475fb18c5356   busybox:1.36     "/bin/sh"   16 minutes ago   Exited (0) 16 minutes ago             busybox1
[root@localhost ~]# docker container  prune
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

3.2.4 管理容器中的数据

如果正在运行中的容器生成了新的数据或者修改了现有的一个已经存在的文件内容,那么新产生的数据将会被复制到读写层进行持久化保存,这个读写层也就是容器的工作目录。这就是“写时复制(COW)copy on write”机制。

容器中修改的数据内容会随着容器的消亡而消失,如果想要将容器中数据永久保存,则需要将容器中的数据保存到宿主机的指定目录中,可以使用如下方式持久化容器数据:

  • 使用docker管理的数据卷

    数据卷的使用, 类似于 Linux 下对目录或文件进行 mount, 镜像中的被指定为挂载点的目录中的文件会隐藏掉, 能看到的是挂载的数据卷里面的内容。

  • 使用宿主机本地的目录作为数据卷

  • 将宿主机的目录挂载至容器,然后让其它容器通过该容器读写宿主机的数据

数据卷的特点:

1、数据卷是宿主机的目录或者文件,并且可以在多个容器之间共同使用。 2、在宿主机对数据卷内容更新后会在所有容器里面立即更新。 3、数据卷的数据可以持久保存,即使删除使用该数据卷的容器也不影响。 4、在容器里面写入的数据不会影响到镜像本身。

(1)运行容器后,数据默认存储位置

#运行一个容器
[root@localhost ~]# docker run -d -P --name web2  nginx:1.14-alpine
81227be769c680fe1cafeee75e542637aa1d45f10ad7263cecdcd1e20bd4361d
[root@localhost ~]# docker inspect  web2 | grep -A 5 Data
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20-init/diff:/var/lib/docker/overlay2/ce2e22e9cb73f2c10206b2932b621c94a1646148bf43a46178043507cc8f2fd6/diff:/var/lib/docker/overlay2/010d7599221a54660d5e422bb2aaab6d46a75c75c92b6aa4383a3f8edbd54765/diff:/var/lib/docker/overlay2/5cd27069ab145ad9b1ee7d3a3edbdb408658401130805f9938e5e2d10641660b/diff:/var/lib/docker/overlay2/c46c1f01ddc4649b06eb400b02d51edc9e9500371d06b739ee37f157d17daf9c/diff",
                "MergedDir": "/var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/merged",
                "UpperDir": "/var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/diff",
                "WorkDir": "/var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/work"
            },

LowerDir:image镜像层(只读)

MergedDir:容器的文件系统,使用Union FS将lowerdir和upperdir合并给容器使用

UpperDir:容器的上层(读写)

WorkDir:容器在宿主机的工作目录,用来存放临时文件

#进入容器生成数据
[root@localhost ~]# docker exec -it web2 /bin/sh
/ # echo  this is web2 test > /file
/ # md5sum  /file
008987f269e42d17ffef84111bedb6fe  /file

#查看文件在宿主机的位置,验证宿主机中的文件的md5sum是否和容器中一致
[root@localhost ~]# md5sum /var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/merged/file
008987f269e42d17ffef84111bedb6fe  /var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/merged/file
[root@localhost ~]# md5sum /var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/diff/file
008987f269e42d17ffef84111bedb6fe  /var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20/diff/file

删除容器,再看数据文件是否还存在

[root@localhost ~]# docker rm -f web2
#容器数据文件随着容器消亡
[root@localhost ~]# ll /var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20
ls: 无法访问 '/var/lib/docker/overlay2/1dee24008f0f138a652daf0c91e91b35875f0de8721acb65c856cd26e6b15b20': 没有那个文件或目录

(2)使用数据卷持久化数据

[root@localhost ~]# docker volume create my-vol
my-vol
[root@localhost ~]# docker volume ls
DRIVER       VOLUME NAME
local        my-vol

查看指定数据卷的信息

#my-vol数据卷在宿主机的实际路径是/var/lib/docker/volumes/my-vol/_data
[root@localhost ~]# docker volume inspect my-vol
[
    {
        "CreatedAt": "2024-10-16T14:59:16+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": null,
        "Scope": "local"
    }
]

启动一个挂载数据卷的容器:在用 docker run 命令的时候, 使用 -v 标记来将数据卷挂载到容器里。 在一次docker run 中可以挂载多个数据卷 。

#创建一个名为 web2的容器, 并加载一个数据卷到容器的 /usr/share/nginx/html/ 目录 
[root@localhost ~]# docker run -d -P --name web2 -v my-vol:/usr/share/nginx/html nginx:1.14-alpine
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                   CREATED          STATUS          PORTS                                       NAMES
df628b20139f   nginx:1.14-alpine   "nginx -g 'daemon of…"   16 seconds ago   Up 16 seconds   0.0.0.0:32768->80/tcp, [::]:32768->80/tcp   web2
#删除数据卷会提示不允许删除
[root@localhost ~]# docker volume rm my-vol
Error response from daemon: remove my-vol: volume is in use - [df628b20139ffc652bb60ec5cf06cc850d9203d44ec585977ae1978cf2a5de02]
#在宿主机修改数据卷中文件内容,访问容器时信息会发生变化
[root@localhost ~]# echo web2 > /var/lib/docker/volumes/my-vol/_data/index.html
[root@localhost ~]# curl 192.168.168.101:32768         web2
#进入容器修改文件内容
[root@localhost ~]# docker exec -it web2 /bin/sh
/ # cd /usr/share/nginx/html/
/usr/share/nginx/html # ls
50x.html    index.html
/usr/share/nginx/html # cat index.html
web2
/usr/share/nginx/html # echo web2 container > index.html
/usr/share/nginx/html # exit
#再次访问可以看到信息已经发生变化
[root@localhost ~]# curl 192.168.168.101:32768
web2 container
#删除容器,数据卷仍在
[root@localhost ~]# docker rm -f web2
[root@localhost ~]# ll /var/lib/docker/volumes/my-vol/_data/
总用量 8
-rw-r--r-- 1 root root 537  4月 10  2019 50x.html
-rw-r--r-- 1 root root  15 10月 16 15:06 index.html

数据卷是被设计用来持久化数据的, 它的生命周期独立于容器, Docker不会在容器被删除后自动删除数据卷 。如果需要在删除容器后移除数据卷,可以使用如下命令:

[root@localhost ~]# docker volume rm my-vol

由于不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷,无主的数据卷可能会占据很多空间, 所以要清理请使用以下命令:

[root@localhost ~]# docker volume prune -af

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

[root@localhost ~]# docker system  df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          10        0         444.4MB   444.4MB (100%)
Containers      0         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     0         0         0B        0B

(3)在宿主机创建一个目录用来持久化容器数据

[root@localhost ~]# mkdir  /docker/data -pv
[root@localhost ~]# docker run -d -P --name web2 -v /docker/data:/usr/share/nginx/html nginx:1.14-alpine
44ca513f2d830a9cbc285ae3b9d3e644ad9dc57e3ca251754f9252b1f0e4a480
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                   CREATED         STATUS         PORTS                                       NAMES
44ca513f2d83   nginx:1.14-alpine   "nginx -g 'daemon of…"   2 seconds ago   Up 2 seconds   0.0.0.0:32776->80/tcp, [::]:32776->80/tcp   web2
[root@localhost ~]# ll /docker/data/
总用量 0
[root@localhost ~]# curl 192.168.168.101:32776
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.14.2</center>
</body>
</html>
[root@localhost ~]# echo test > /docker/data/index.html
[root@localhost ~]# curl 192.168.168.101:32776
test
#删除容器数据仍在
[root@localhost ~]# docker rm -f web2
web2
[root@localhost ~]# ll /docker/data/
总用量 4
-rw-r--r-- 1 root root 5 10月 16 17:46 index.html
[root@localhost ~]#

(4)通过创建的数据卷容器来读写宿主机的数据

#创建数据卷容器
[root@localhost ~]# mkdir /docker/readonly
[root@localhost ~]# echo readonly > /docker/readonly/file
[root@localhost ~]# docker run -d --name web2 -P  -v /docker/readonly:/test:ro -v /docker/data/:/usr/share/nginx/html nginx:1.14-alpine
6622c24035970b8ef42251aa295b47d5c4a20f490f5e74d8a7c84207e7d1b1b0
[root@localhost ~]# docker exec -it web2 /bin/sh
/ # ls /test/
file
/ # echo test >> /test/file
/bin/sh: can't create /test/file: Read-only file system
/ # echo web2 >> /usr/share/nginx/html/index.html
#创建容器使用数据卷容器的文件
[root@localhost ~]# docker run -d   --name web2-client1 -P --volumes-from web2 nginx:1.14-alpine
3d8dff4724579028f0f0c72377edd31d801eb6365ca053a0463ae5f2afafa1ad
[root@localhost ~]# docker exec -it web2-client1 /bin/sh
/ # echo test client1 >> /test/file
/bin/sh: can't create /test/file: Read-only file system
/ # echo test client1 >> /usr/share/nginx/html/index.html
#使用宿主机访问web服务
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                   CREATED              STATUS              PORTS                                       NAMES
3d8dff472457   nginx:1.14-alpine   "nginx -g 'daemon of…"   About a minute ago   Up About a minute   0.0.0.0:32771->80/tcp, [::]:32771->80/tcp   web2-client1
6622c2403597   nginx:1.14-alpine   "nginx -g 'daemon of…"   5 minutes ago        Up 5 minutes        0.0.0.0:32770->80/tcp, [::]:32770->80/tcp   web2
[root@localhost ~]# curl localhost:32770
test
web2
test client1
[root@localhost ~]# curl localhost:32771
test
web2
test client1

#删除web2,web2-client1仍然可以使用该卷
[root@localhost ~]# docker rm -f web2
[root@localhost ~]# docker exec -it web2-client1 /bin/sh
/ # echo client1 >> /usr/share/nginx/html/index.html
[root@localhost ~]# curl localhost:32771
test
web2
test client1
client1

四、镜像构建

从镜像大小上来说,一个比较小的镜像只有十几MB,而内核文件需要一百多MB,因此镜像里面是没有内核的,镜像是在被启动为容器后直接使用宿主机的内核,而镜像本身则只提供相应的rootfs,即系统正常运行所必须的用户空间的文件系统,比如/dev,/proc,/bin,/etc等目录,所以容器中是没有/boot目录中的那些内核相关的文件和目录。

当我们从docker镜像仓库中下载的镜像不能满足我们的需求时,我们可以通过以下两种方式对镜像进行更改:

(1)从已经创建的容器中更新镜像,并且提交这个镜像

(2)使用Dockerfile指令来创建一个新的镜像

4.1 基于容器制作镜像

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Options参数:
	-a,作者(例如,“along along@along.com”)
	-c,修改Dockerfile指令应用于创建的镜像
	-m,提交的描述信息,即记录本次修改的内容
	-p,在提交期间暂停容器(默认为true) 

示例1:简单的基于容器创建一个新的镜像

① 先运行一个容器

我们修改了容器的文件, 也就是改动了容器的存储层。

#查看宿主机的内核版本
[root@localhost ~]# uname -r
5.14.0-362.8.1.el9_3.x86_64
[root@localhost ~]# docker run --name b1 -it busybox:latest
#查看容器的内核版本
/ # uname -r
5.14.0-362.8.1.el9_3.x86_64
/ # ls /
bin    etc    lib    proc   sys    usr
dev    home   lib64  root   tmp    var
/ # mkdir -p /data/html
/ # echo busybox httpd server > /data/html/index.html
/ # cat /data/html/index.html
busybox httpd server

② 不用退出这个容器,另起终端在b1容器基础上,制作新镜像

定制好了变化, 现在我们希望能将其保存下来形成镜像。要知道, 当我们运行一个容器的时候( 如果不使用卷的话) , 我们做的任何文件修改都会被记录于容器存储层里。 而 Docker 提供了一个 docker commit 命令, 可以将容器的存储层保存下来成为镜像。换句话说, 就是在原有镜像的基础上, 再叠加上容器的存储层, 并构成新的镜像。 以后我们运行这个新镜像的时候, 就会拥有原有容器最后的文件变化。

[root@localhost ~]# docker commit -p b1
sha256:eac3bfb08e59c4594bc3823fd1d4da77ac24c75c6d5a5cdc5123b468e7a4feac
[root@localhost ~]# docker image ls
REPOSITORY                                           TAG               IMAGE ID       CREATED         SIZE
<none>                                               <none>            eac3bfb08e59   6 seconds ago   4.26MB

③ 给新制作的镜像打标签

[root@localhost ~]# docker tag eac3b httpd:v1.0
[root@localhost ~]# docker image ls httpd:v1.0
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
httpd        v1.0      eac3bfb08e59   2 minutes ago   4.26MB

④ 可以对同一个镜像,再打标签

[root@localhost ~]# docker tag httpd:v1.0 httpd:v2.0
[root@localhost ~]# docker image ls httpd
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
httpd        v1.0      eac3bfb08e59   3 minutes ago   4.26MB
httpd        v2.0      eac3bfb08e59   3 minutes ago   4.26MB

⑤ 删除同一镜像的标签,只是把这个镜像的标签去掉,直到删除这个镜像的最后一个标签,此镜像才会被删除。

[root@localhost ~]# docker image rm httpd:v2.0
Untagged: httpd:v2.0
[root@localhost ~]# docker image ls httpd
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
httpd        v1.0      eac3bfb08e59   3 minutes ago   4.26MB

⑥ 基于新的镜像运行一个容器,验证是否是基于b1创建成功

[root@localhost ~]# docker run --name b2 -it --rm httpd:v1.0
/ # cat /data/html/index.html
busybox httpd server

示例2:基于容器创建新的镜像,并修改执行命令CMD

① 基于容器b1创建新的镜像,并修改命令为执行httpd服务

[root@localhost ~]# docker commit  -a "jungle <jungle@jungle.com>" -c 'CMD ["/bin/httpd", "-f", "-h", "/data/html"]' -p b1 httpd:v2.0
sha256:b9919d49631bff68ac3bd735a3ae052bd41e7d7b9d8190d4fd4a5dc551d093f0

# 注解:busybox中httpd命令语法
	-f:不运行为守护进程,在前台运行
    -h:指定httpd运行的主目录 

② 运行新的镜像v2.0

[root@localhost ~]# docker run --name b3 -d httpd:v2.0
67897f0039c87b521145391cd636efee60c892274a54665657f9b6763d013989
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE            COMMAND                   CREATED         STATUS         PORTS     NAMES
67897f0039c8   httpd:v2.0       "/bin/httpd -f -h /d…"   8 seconds ago   Up 8 seconds             b3

③ 访问测试

[root@localhost ~]# docker inspect  b3 | grep -i ipaddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.3",
                    "IPAddress": "172.17.0.3",

[root@localhost ~]# curl 172.17.0.3
busybox httpd server

注意:使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念, 但是实际环境中并不会这样使用。

  • 使用 docker commit 意味着所有对镜像的操作都是黑箱操作, 生成的镜像也被称为黑箱镜像, 换句话说, 就是除了制作镜像的人知道执行过什么命令、 怎么生成的镜像, 别人根本无从得知。而且, 即使是这个制作镜像的人, 过一段时间后也无法记清具体的操作。

  • 镜像所使用的是分层存储,除当前层外,之前的每一层都是不会发生改变的,也就是说任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层,所以每一次修改都会让镜像更加臃肿一次。 比如, 删除前一层文件的操作, 实际不是真的删除前一层的文件, 而是仅在当前层标记为该文件已删除。 在最终容器运行的时候, 虽然不会看到这个文件, 但是实际上该文件会一直跟随镜像。 因此, 在构建镜像的时候, 需要额外小心, 每一层尽量只包含该层需要添加的东西, 任何额外的东西应该在该层构建结束前清理掉。

4.2 基于dockerfile制作镜像

DockerFile可以说是一种可以被Docker程序解释的脚本,DockerFile是由一条条的命令组成的,每条命令对应linux下面的一条命令,Docker程序将这些DockerFile指令再翻译成真正的linux命令。dockerfile有自己的书写方式和支持的命令,Docker程序读取DockerFile并根据指令生成Docker镜像,相比手动制作镜像的方式,DockerFile更能直观的展示镜像是怎么产生的,有了写好的各种各样DockerFile文件,当后期某个镜像有额外的需求时,只要在之前的DockerFile添加或者修改相应的操作即可重新生成新的Docke镜像,避免了重复手动制作镜像的麻烦,具体如下:

Dockerfile reference | Docker Docs

FROM
#FROM用来指定一个基础镜像,并且FROM 指令必须是 Dockerfile 中非注释行的第一个指令;docker build会在docker主机上查找指定的镜像文件,不写镜像标签默认为latest。在其不存在时,则会自动从 Docker 的公共库 pull 镜像下来。如果找不到指定的镜像文件,docker build 会返回一个错误信息;除了选择现有镜像为基础镜像外, Docker 还存在一个特殊的镜像, 名为 scratch 。 这个镜像是虚拟的概念并不实际存在,它表示一个空白的镜像。如果你以 scratch 为基础镜像的话, 意味着你不以任何镜像为基础, 接下来所写的指令将作为镜像第一层开始存在。 不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见, 比如swarm 、coreos/etcd 。
LABEL
#LABEL用来描述镜像的元数据信息的键值对。label可以用来给镜像添加一些自定义的属性,比如镜像的版本、作者、描述等。
COPY
#从上下文目录中复制文件或目录到容器里指定的路径;要复制的源文件或目录可以是多个,支持使用通配符,例如COPY hom* hom?.txt /mydir/;如果源是目录,则会复制该目录下的所有文件,不会复制目录本身,想复制目录本身需要使用该方式COPY yum.repos.d /etc/yum.repos.d/(即目标需要有个文件名和目录名同名);如果目标文件不存在则会自动创建。
ADD
#ADD 指令类似于COPY指令,ADD支持使用TAR文件和URL路径;如果是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于"tar -x"命令;然而,通过URL获取到的tar文件将不会自动展开。
ENV
#定义所需的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用;语法ENV <key> <value>(一次设置一个变量) 或 ENV <key>=<value> ...
EXPOSE
#当要将容器中的端口映射至宿主机上时,使用-P选项会自动映射该端口;例如EXPOSE 80/tcp
USER
#用于指定运行image时或执行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID,用户得事先存在。
VOLUME
#用于在image中创建一个挂载点目录,以挂载宿主机上的卷或其它容器上的卷。例如:VOLUME /data,这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。可以使用docker run -v选项覆盖这个挂载设置。
WORKDIR
#用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指令设定工作目录;WORKDIR后面可以是绝对路径或者相对路径;如该目录不存在, WORKDIR 会帮你建立目录;WORKDIR也可调用由ENV指令定义的变量。语法: WORKDIR /etc
RUN
#用于指定docker build过程中运行的程序,其可以是任何命令;语法:RUN <command> 或RUN ["<executable>", "<param1>", "<param2>"];第一种格式中,通常是一个shell命令,且以“/bin/sh -c”来运行它;第二种语法格式中的参数是一个JSON格式的数组,其中为要运行的命令的可执行文件,后面的为传递给命令的选项或参数;然而,此种格式指定的命令不会以“/bin/sh -c”来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式:RUN ["/bin/bash", "-c", "", ""]  
CMD
#为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止,不过,CMD指定的命令可以被docker run的命令行选项所覆盖;在Dockerfile中可以存在多个CMD指令,但仅最后一个会生效。语法:CMD  <command>或者 CMD ["<executable>","<param1>","<param2>"] 或 CMD ["<param1>","<param2>"](此种写法主要是为ENTRYPOINT指令提供默认参数),此处的引号需要使用双引号,单引号会报错
ENTRYPOINT
#类似CMD指令的功能,用于为容器指定默认运行程序,与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且这些命令行参数会被当作参数传递给ENTRYPOINT指令指定的程序,不过,docker run命令的 --entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序;当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将CMD 的内容作为参数传给 ENTRYPOINT 指令, 换句话说实际执行时,将变为: `<ENTRYPOINT> "<CMD>"`。语法:ENTRYPOINT <command> 或 ENTRYPOINT ["<executable>", "<param1>", "<param2>"]

说明:

(1)#号开头的行为dockerfile中的注释

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

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

基于dockerfile制作镜像命令:

docker   build   [OPTIONS]       PATH | URL | - 
#选项:-t指定要创建的目标镜像名

(1)PATH:一般用.(当前工作目录)作为构建镜像的上下文路径

Docker 在运行时分为 Docker 引擎( 也就是服务端守护进程) 和客户端工具。 Docker 的引擎提供了一组REST API, 被称为 Docker Remote API, 而如 docker 命令这样的客户端工具, 则是通过这组 API与Docker 引擎交互, 从而完成各种功能。 因此, 虽然表面上我们好像是在本机执行各种 docker 功能, 但实际上, 一切都是使用的远程调用形式在服务端( Docker 引擎) 完成。 也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

当我们进行镜像构建的时候, 并非所有定制都会通过 RUN 指令完成, 经常会需要将一些本地文件复制进镜像, 比如通过 COPY 指令、 ADD 指令等。 而 docker build 命令构建镜像, 其实并非在本地构建, 而是在服务端, 也就是 Docker 引擎中构建的。 那么在这种客户端/服务端的架构中, 如何才能让服务端获得本地文件呢? 这就引入了上下文的概念。

当构建的时候, 用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后, 会将路径下的所有内容打包, 然后上传给 Docker 引擎。 这样Docker 引擎收到这个上下文包后, 展开就会获得构建镜像所需的一切文件。

那么为什么会有人误以为 . 是指定 Dockerfile 所在目录呢? 这是因为在默认情况下, 如果不额外指定 Dockerfile 的话, 会将上下文目录下的名为 Dockerfile 的文件作为Dockerfile。这只是默认行为, 实际上 Dockerfile 的文件名并不要求必须为 Dockerfile , 而且并不要求必须位于上下文目录中, 比如可以用 -f ../Dockerfile.php 参数指定某个文件作为Dockerfile,一般大家习惯性的会使用默认的文件名 Dockerfile , 以及会将其置于镜像构建上下文目录中 。

(2)URL

docker build还支持从URL构建,docker build http://server/context.tar.gz ,如果所给出的URL是个tar压缩包,那么docker引擎会下载这个包,并自动解压,以其作为上下文,开始构建。

(3)-

docker build还支持从标准输入中读取Dockerfile进行构建

  • docker build - < Dockerfile 或者cat Dockerfile | docker build -

如果标准输入传入的是文本文件,则将其视为Dockerfile,并开始构建。这种形式由于直接从标准输入中读取Dockerfile的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件COPY进镜像之类的事情。

  • docker build - < context.tar.gz

如果发现标准输入的文件格式是 gzip 、 bzip2 以及 xz 的话, 将会使其为上下文压缩包,直接将其展开, 将里面视为上下文, 并开始构建。

拉取基础镜像

[root@localhost ~]# docker pull centos:centos7.9.2009
centos7.9.2009: Pulling from library/centos
2d473b07cdd5: Pull complete
Digest: sha256:be65f488b7764ad3638f236b7b515b3678369a5124c47b8d32916d6487418ea4
Status: Downloaded newer image for centos:centos7.9.2009
docker.io/library/centos:centos7.9.2009
[root@localhost yum-nginx]# docker image ls centos:centos7.9.2009
REPOSITORY   TAG              IMAGE ID       CREATED       SIZE
centos       centos7.9.2009   eeb6ee3f44bd   3 years ago   204MB

#创建镜像文件目录
[root@localhost ~]# mkdir /dockerfile/web/{nginx,tomcat} -pv
mkdir: 已创建目录 '/dockerfile'
mkdir: 已创建目录 '/dockerfile/web'
mkdir: 已创建目录 '/dockerfile/web/nginx'
mkdir: 已创建目录 '/dockerfile/web/tomcat'

4.2.1 制作yum版本nginx镜像

[root@localhost ~]# mkdir  /dockerfile/web/nginx/yum-nginx
[root@localhost ~]# cd /dockerfile/web/nginx/yum-nginx
[root@localhost yum-nginx]# cat dockerfile
FROM centos:centos7.9.2009  #指定基础镜像,后续的指令运行于此基础镜像所提供的运行环境
LABEL maintainer="jungle jungle@qq.com"  #指定镜像维护者信息
WORKDIR /etc/yum.repos.d/  #指定工作目录
RUN rm -rf ./* && curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo && curl -o /etc/yum.repos.d/epel-7.repo  https://mirrors.aliyun.com/repo/epel-7.repo && yum install nginx iproute -y && yum clean all && rm -rf /usr/share/nginx/html/index.html && echo "test nginx page" >  /usr/share/nginx/html/index.html  #指定要在容器中运行的命令
EXPOSE 80 443  #当要将容器中的端口映射至宿主机上时,使用-P选项会自动映射该端口
CMD ["nginx", "-g", "daemon off;"] #设定nginx在前台运行

制作镜像

[root@localhost yum-nginx]#  docker build -t  nginx:1.20.1.el7  .
[root@localhost yum-nginx]# docker image ls nginx:1.20.1.el7
REPOSITORY   TAG          IMAGE ID       CREATED             SIZE
nginx        1.20.1.el7   de5b475939fd   About an hour ago   265MB

运行容器,并访问测试

[root@localhost yum-nginx]# docker run -d -P nginx:1.20.1.el7
976212fc70a0b5f392f795bf234b796d9f537956f45818654134984a583e76eb
[root@localhost yum-nginx]# docker ps
CONTAINER ID   IMAGE                   COMMAND                   CREATED         STATUS         PORTS                                                                                    NAMES
976212fc70a0   nginx:1.20.1.el7        "nginx -g 'daemon of…"   3 seconds ago   Up 2 seconds   0.0.0.0:32768->80/tcp, [::]:32768->80/tcp, 0.0.0.0:32769->443/tcp, [::]:32769->443/tcp   condescending_pascal
[root@localhost yum-nginx]# docker inspect -f {{.NetworkSettings.IPAddress}} 97621
172.17.0.3
[root@localhost yum-nginx]# curl localhost:32768
test nginx page
[root@localhost yum-nginx]# curl  172.17.0.3
test nginx page

4.2.2 制作源码版nginx镜像

[root@localhost ~]# mkdir /dockerfile/web/nginx/source-nginx
[root@localhost ~]# cd  /dockerfile/web/nginx/source-nginx
[root@localhost source-nginx]# cat dockerfile
FROM centos:centos7.9.2009
LABEL maintainer="jungle jungle@qq.com"
ADD nginx-1.26.2.tar.gz /opt/  #复制文件到/opt并自动解压
RUN rm -rf /etc/yum.repos.d/* && curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo && curl -o /etc/yum.repos.d/epel-7.repo  https://mirrors.aliyun.com/repo/epel-7.repo && yum install -y gcc pcre-devel  zlib-devel  openssl-devel shadow-utils && yum clean all && useradd nginx -s /sbin/nologin -M && cd  /opt/nginx-1.26.2 &&  ./configure --user=nginx --group=nginx --prefix=/usr/local/nginx  --with-http_ssl_module && make && make install && ln -s /usr/local/nginx/sbin/nginx  /usr/local/sbin/ && rm -rf /opt/nginx-1.26.2
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
[root@localhost source-nginx]# cat build-command.sh
#!/bin/bash
docker build -t nginx:v1 .
#上传nginx源码包至dockerfile所在目录
[root@localhost source-nginx]# ll
总用量 1224
-rw-r--r-- 1 root root      39 10月 24 13:21 build-command.sh
-rw-r--r-- 1 root root     704 10月 24 13:20 dockerfile
-rw-r--r-- 1 root root 1244789 10月 24 13:23 nginx-1.26.2.tar.gz
#制作镜像
[root@localhost source-nginx]# bash build-command.sh
[root@localhost source-nginx]# docker image ls nginx:v1
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
nginx        v1        4cb937520fd8   8 minutes ago   324MB
#运行容器并测试
[root@localhost source-nginx]# docker run -d -P nginx:v1
e0387fefe4be9dba0c75a8ba54f9d7a4e91c94b395a26ea20bff5f3f6a5eab66
[root@localhost source-nginx]# docker ps
CONTAINER ID   IMAGE                   COMMAND                   CREATED         STATUS         PORTS                                                                                    NAMES
e0387fefe4be   nginx:v1                "nginx -g 'daemon of…"   2 seconds ago   Up 1 second    0.0.0.0:32770->80/tcp, [::]:32770->80/tcp, 0.0.0.0:32771->443/tcp, [::]:32771->443/tcp   quirky_snyder
[root@localhost source-nginx]# curl localhost:32770
[root@localhost source-nginx]# docker inspect -f {{.NetworkSettings.IPAddress}} e03
172.17.0.4
[root@localhost source-nginx]# curl -s -w "%{http_code}\n" -o /dev/null 172.17.0.4
200

4.2.3 优化nginx镜像

选择最精简的基础镜像 减少镜像的层数 清理镜像构建的中间产物

(1)使用多阶段构建减小nginx镜像体积

[root@localhost source-nginx]# cd /dockerfile/web/nginx/small-nginx
[root@localhost small-nginx]# ll
总用量 1224
-rw-r--r-- 1 root root      39 10月 24 15:44 build-command.sh
-rw-r--r-- 1 root root     801 10月 24 15:43 dockerfile
-rw-r--r-- 1 root root 1244789 10月 24 15:39 nginx-1.26.2.tar.gz
[root@localhost small-nginx]# cat dockerfile
FROM centos:centos7.9.2009 as build
LABEL maintainer="jungle jungle@qq.com"
ADD nginx-1.26.2.tar.gz /opt/
RUN rm -rf /etc/yum.repos.d/* && curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo && curl -o /etc/yum.repos.d/epel-7.repo  https://mirrors.aliyun.com/repo/epel-7.repo && yum install -y gcc pcre-devel  zlib-devel  openssl-devel shadow-utils make && yum clean all && cd  /opt/nginx-1.26.2 &&  ./configure --user=nginx --group=nginx --prefix=/usr/local/nginx  --with-http_ssl_module && make && make install && rm -rf /opt/nginx-1.26.2

FROM centos:centos7.9.2009
COPY --from=build /usr/local/nginx/ /usr/local/nginx/
RUN useradd nginx -s /sbin/nologin -M && ln -s /usr/local/nginx/sbin/nginx  /usr/local/sbin/
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
[root@localhost small-nginx]# cat build-command.sh
#!/bin/bash
docker build -t nginx:v2 .

#制作镜像
[root@localhost small-nginx]# docker image ls nginx:v2
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
nginx        v2        451ac692069b   2 minutes ago   210MB

#运行并测试
[root@localhost small-nginx]# docker run -d -P nginx:v2
b0ca0079c137935b946f378c16b439fda665829043e6c61724f4af97ecf92eef
[root@localhost small-nginx]# docker ps
CONTAINER ID   IMAGE                   COMMAND                   CREATED          STATUS          PORTS                                                                                    NAMES
b0ca0079c137   nginx:v2                "nginx -g 'daemon of…"   1 second ago     Up 1 second     0.0.0.0:32772->80/tcp, [::]:32772->80/tcp, 0.0.0.0:32773->443/tcp, [::]:32773->443/tcp   festive_montalcini
[root@localhost small-nginx]# curl -s -w "%{http_code}\n" -o /dev/null localhost:32772
200

(2)使用精简镜像构建nginx镜像

[root@localhost small-nginx]# mkdir /dockerfile/web/nginx/min-nginx
[root@localhost small-nginx]# cd /dockerfile/web/nginx/min-nginx
[root@localhost min-nginx]# cat dockerfile
FROM alpine
LABEL maintainer="jungle jungle@qq.com"
RUN apk add --no-cache nginx
COPY index.html /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
[root@localhost min-nginx]# cat nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
include /usr/share/nginx/modules/*.conf;
events {
    worker_connections 1024;
}
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;
    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;
    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;
    server {
        listen 81;
        server_name _;
        root  /usr/share/nginx/html/;
    }
    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;
        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;
        error_page 404 /404.html;
        location = /404.html {
        }
        location / {}
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }
}
[root@localhost min-nginx]# cat index.html
min nginx
[root@localhost min-nginx]# cat build-command.sh
#!/bin/bash
docker build -t nginx:v3 .

#制作镜像
[root@localhost min-nginx]# bash build-command.sh
[root@localhost min-nginx]# docker image ls nginx:v3
REPOSITORY   TAG       IMAGE ID       CREATED              SIZE
nginx        v3        e9c71d65d5cf   About a minute ago   9.25MB
[root@localhost min-nginx]# docker run -d -P nginx:v3
[root@localhost min-nginx]# docker ps
CONTAINER ID   IMAGE      COMMAND                   CREATED        STATUS                  PORTS                                                                                    NAMES
0706da9a9d9b   nginx:v3   "nginx -g 'daemon of…"   1 second ago   Up Less than a second   0.0.0.0:32784->80/tcp, [::]:32784->80/tcp, 0.0.0.0:32785->443/tcp, [::]:32785->443/tcp   vigorous_keller
[root@localhost min-nginx]# curl localhost:32784
min nginx

4.2.4 自定义tomcat业务镜像

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

[root@localhost tomcat]# pwd
/dockerfile/web/tomcat
[root@localhost tomcat]# wget  https://repo.huaweicloud.com/java/jdk/11.0.2+9/jdk-11.0.2_linux-x64_bin.tar.gz
[root@localhost tomcat]# wget  https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.96/bin/apache-tomcat-9.0.96.tar.gz

#拉取openEuler镜像
[root@localhost tomcat]# docker pull openeuler/openeuler:24.03
[root@localhost tomcat]# cat java.sh
JAVA_HOME=/usr/local/jdk
PATH=$JAVA_HOME/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME PATH CLASSPATH
[root@localhost tomcat]# cat dockerfile
FROM openeuler/openeuler:24.03
LABEL maintainer="jungle jungle@qq.com"
RUN yum install -y gcc pcre-devel zlib-devel openssl-devel shadow-utils
ADD jdk-11.0.2_linux-x64_bin.tar.gz /usr/local/src
RUN ln -s /usr/local/src/jdk-11.0.2/ /usr/local/jdk
ADD java.sh /etc/profile.d/
ENV JAVA_HOME=/usr/local/jdk
ENV PATH=$JAVA_HOME/bin:$PATH
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ADD apache-tomcat-9.0.96.tar.gz  /usr/local/src
RUN ln -s /usr/local/src/apache-tomcat-9.0.96 /usr/local/tomcat
EXPOSE 8080
CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
[root@localhost tomcat]# cat build-command.sh
#!/bin/bash
docker build -t tomcat:base .
[root@localhost tomcat]# docker run -d -P tomcat:base
[root@localhost tomcat]# docker ps
CONTAINER ID   IMAGE         COMMAND                   CREATED         STATUS         PORTS                                           NAMES
1117266c49e8   tomcat:base   "/usr/local/tomcat/b…"   3 minutes ago   Up 3 minutes   0.0.0.0:32768->8080/tcp, [::]:32768->8080/tcp   mystifying_goldberg

定义tomcat业务镜像

[root@localhost tomcat-appv1]# cat index.jsp
<html>
<head><title>Hello World</title><meta charset="UTF-8"></head>
<body>
<p style="font-size:40px;font-weight:bold;">Hello World!&#128151</p>
<%
out.println("Your IP address is " + request.getRemoteAddr());
%>
</body>
</html>
[root@localhost tomcat-appv1]# cat dockerfile
FROM tomcat:base
LABEL maintainer="jungle jungle@qq.com"
ADD index.jsp /usr/local/tomcat/webapps/ROOT/index.jsp
EXPOSE 8080
CMD ["/usr/local/tomcat/bin/catalina.sh","run"]
[root@localhost tomcat-appv1]# cat build-command.sh
#!/bin/bash
docker build -t tomcat:appv1 .
[root@localhost tomcat-appv1]# bash build-command.sh
[root@localhost tomcat-appv1]# docker run -d -P tomcat:appv1
ba625d1d591f703b84e95e8efb78bc522cafd0039a6ec3d476ae71e48e7ad940
[root@localhost tomcat-appv1]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED        STATUS        PORTS                                           NAMES
ba625d1d591f   tomcat:appv1   "/usr/local/tomcat/b…"   1 second ago   Up 1 second   0.0.0.0:32769->8080/tcp, [::]:32769->8080/tcp   keen_lichterman

4.3 配置nginx反向代理

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

#运行tomcat容器
[root@localhost ~]# docker run --name tomcat-server -d tomcat:appv1
[root@localhost ~]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS      NAMES
cb38a4a6b38a   tomcat:appv1   "/usr/local/tomcat/b…"   9 seconds ago   Up 9 seconds   8080/tcp   tomcat-server
[root@localhost nginx]# docker inspect -f {{.NetworkSettings.IPAddress}} tomcat-server
172.17.0.2

#运行nginx容器,并配置动态页面代理至tomcat业务容器
[root@localhost ~]# mkdir /nginx/
[root@localhost nginx]# tree /nginx/
/nginx/
├── conf
│   └── server.conf
└── html
    └── index.html

[root@localhost nginx]# cat /nginx/conf/server.conf
server {
  listen 80;
  server_name localhost;
  location / {
        root /html;
        index index.html index.htm;
  }
  location ~ \.jsp$ {
        proxy_pass http://tomcat-server:8080;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

[root@localhost nginx]# cat /nginx/html/index.html
<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="UTF-8">
                <title>test</title>
        </head>
        <body>
                <h1>this is a static page</h1>
                <td><a href="/index.jsp">动态网站</a></td>
        </body>
</html>
#--link表示将tomcat-server容器的域名信息和ip地址写入nginx-proxy容器的hosts文件中,实现通过容器名称访问容器
#可以通过tomcat-server和tomcat名字访问容器
[root@localhost nginx]# docker run -d --link tomcat-server:tomcat -v /nginx/conf/:/etc/nginx/conf.d -v /nginx/html/:/html  -p 80:80 --name nginx-proxy nginx:1.27.2-alpine
232e4476aa57ba9af8e4f3e8d71075fac2e98bd939f73401020e3ea96c904433
[root@localhost nginx]# docker ps
CONTAINER ID   IMAGE                 COMMAND                   CREATED          STATUS          PORTS                               NAMES
232e4476aa57   nginx:1.27.2-alpine   "/docker-entrypoint.…"   8 seconds ago    Up 7 seconds    0.0.0.0:80->80/tcp, :::80->80/tcp   nginx-proxy


#进入nginx-proxy容器查看
[root@localhost nginx]# docker exec -it nginx-proxy /bin/sh
/ # cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      tomcat cb38a4a6b38a tomcat-server
172.17.0.3      232e4476aa57
/ # ping -c 2 tomcat
PING tomcat (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.056 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.056 ms

#访问测试
[root@localhost nginx]# curl localhost:80
<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="UTF-8">
                <title>test</title>
        </head>
        <body>
                <h1>this is a static page</h1>
                <td><a href="/index.jsp">动态网站</a></td>
        </body>
</html>

五、docker的网络模式

5.1 Docker的四种网络模式

当你安装docker时,它会自动创建三个网络,可使用如下命令查看:

[root@localhost ~]# docker network  ls
NETWORK ID     NAME      DRIVER    SCOPE
7390284b02d6   bridge    bridge    local
d6f8e8de2c03   host      host      local
ec6c758e2ed2   none      null      local

Bridge:此模式会为每一个容器分配、设置IP等。使用 --net=bridge 指定,默认设置。

host:容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。使用--net=host 指定。

None:该模式关闭了容器的网络功能。使用 --net=none 指定。

Container:创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围。使用 --net=container:NAMEorID 指定。

5.2 None网络模式

使用none模式,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息,只有lo 网络接口。需要我们自己为Docker容器添加网卡、配置IP等。

不参与网络通信,运行于此类容器中的进程仅能访问本地回环接口;仅适用于进程无须网络通信的场景中,例如:备份、进程诊断及各种离线任务等。None模式示意图如下所示:

[root@localhost ~]# docker run -it --network none --rm busybox:latest
/ # 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
/ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
/ # exit

5.3 Host网络模式

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

[root@localhost ~]# ip a show ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:fb:63:04 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.168.101/24 brd 192.168.168.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fefb:6304/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
[root@localhost ~]# docker run --name busybox --rm -it --network host  busybox:latest
/ # 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: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel qlen 1000
    link/ether 00:0c:29:fb:63:04 brd ff:ff:ff:ff:ff:ff
    inet 192.168.168.101/24 brd 192.168.168.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fefb:6304/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
    link/ether 02:42:1e:0f:2f:31 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:1eff:fe0f:2f31/64 scope link
       valid_lft forever preferred_lft forever
37: veth0a9f628@if36: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue master docker0
    link/ether ea:67:63:ea:8d:5a brd ff:ff:ff:ff:ff:ff
    inet6 fe80::e867:63ff:feea:8d5a/64 scope link
       valid_lft forever preferred_lft forever
38: br-f65f09f8a274: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
    link/ether 02:42:40:d3:87:29 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-f65f09f8a274
       valid_lft forever preferred_lft forever
    inet6 fe80::42:40ff:fed3:8729/64 scope link
       valid_lft forever preferred_lft forever
/ # exit

5.4 container网络模式

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

(1)先用bridge网络模式启动容器1

[root@localhost ~]# docker run -d --name web4 nginx:1.27.2-alpine
e5c5ae64f304fd37af4952659b4139f0d478bee732bc784ede8c36c93f638f76
[root@localhost ~]# docker inspect  web4 | grep -i ipaddress
            "SecondaryIPAddresses": null,
            "IPAddress": "172.17.0.3",
                    "IPAddress": "172.17.0.3",

(2)再使用Container 网络模式创建容器2

[root@localhost ~]# docker run  --name busybox --rm -it --network container:web4 busybox:latest
/ # 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
43: eth0@if44: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
#web1上启动的nginx服务在该容器中可以直接访问
/ # wget -O - -q 172.17.0.3
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
#注意文件系统并不共享
/ # ls /usr/share
ls: /usr/share: No such file or directory

5.5 Bridge网络模式

安装docker时会自动创建一个docker0网桥,运行容器时,你可以使用docker run --network=<NETWORK>选项指定容器应连接到哪个网络,否则Docker守护程序默认将容器连接到docker0虚拟网桥,通过docker0网桥以及Iptables nat表配置与宿主机通信。

Docker 随机分配一个本地未占用的私有网段( 在 RFC1918 中定义) 中的一个地址给docker0 接口。 此后启动的容器内的网卡也会自动分配一个同一网段的地址。docker0的IP地址则为容器的默认网关。在主机上创建一对虚拟网卡veth pair设备,Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0(容器的网卡),另一端放在主机中,以vethxxx这样类似的名字命名,并将这个网络设备加入到docker0网桥中(bridge模式示意图如下图所示)。可以通过brctl show命令查看。

[root@localhost ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02421e0f2f31       no
[root@localhost ~]# ip a show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:1e:0f:2f:31 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:1eff:fe0f:2f31/64 scope link
       valid_lft forever preferred_lft forever

通过这种方式, 主机可以跟容器通信, 容器之间也可以相互通信。 Docker 就创建了在主机和所有容器之间一个虚拟共享网络。

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

5.5.1 使用默认的网桥

#未指定网络模式则默认为bridge模式
[root@localhost ~]# docker run  -d -P --name web3 nginx:1.14-alpine
009f12f2142c6a297cd8cf3d0fb078037a03e2a8388c581016df9015ecaecd66
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE               COMMAND                   CREATED         STATUS         PORTS                                       NAMES
009f12f2142c   nginx:1.14-alpine   "nginx -g 'daemon of…"   4 seconds ago   Up 3 seconds   0.0.0.0:32775->80/tcp, [::]:32775->80/tcp   web3
[root@localhost ~]#
[root@localhost ~]# iptables -t nat -vnL DOCKER
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:32775 to:172.17.0.2:80
[root@localhost ~]# docker exec -it web3 /bin/sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
36: eth0@if37: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0
#尝试ping宿主机
/ # ping -c2 192.168.168.101
PING 192.168.168.101 (192.168.168.101): 56 data bytes
64 bytes from 192.168.168.101: seq=0 ttl=64 time=0.062 ms
64 bytes from 192.168.168.101: seq=1 ttl=64 time=0.043 ms

5.5.2 自定义网桥

#也可以在创建网桥时指定网段docker network create -d bridge --subnet 10.100.0.0/24 --gateway 10.100.0.1 my-bridge
[root@localhost ~]# docker network  create -d bridge my-bridge
[root@localhost ~]# docker network  ls
NETWORK ID     NAME        DRIVER    SCOPE
7390284b02d6   bridge      bridge    local
d6f8e8de2c03   host        host      local
f65f09f8a274   my-bridge   bridge    local
ec6c758e2ed2   none        null      local
[root@localhost ~]# brctl  show
bridge name     bridge id               STP enabled     interfaces
br-f65f09f8a274         8000.024240d38729       no
docker0         8000.02421e0f2f31       no              veth0a9f628
[root@localhost ~]# ip a show br-f65f09f8a274
38: br-f65f09f8a274: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:40:d3:87:29 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 brd 172.18.255.255 scope global br-f65f09f8a274
       valid_lft forever preferred_lft forever


[root@localhost ~]# docker run --rm -it --name test1 --network=my-bridge  busybox:latest 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0@if12: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether b2:c0:59:6e:f7:19 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.3/16 brd 172.18.255.255 scope global eth0
       valid_lft forever preferred_lft forever
#使用自建的bridge模式,可以使用容器的名字互相访问
[root@localhost ~]# docker run --rm -it --name test2 --network my-bridge busybox:latest 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether d6:fa:f1:4e:73:20 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
/ # ping test1
PING test1 (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.038 ms
64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.067 ms

5.6 容器间的通信

5.6.1 同一台主机上的容器间的通信

(1)容器都使用默认的Bridge网络模式

  • 两个容器使用ip地址互相访问

#运行第一个容器
[root@localhost ~]# docker run --rm -it busybox:1.36 /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever


#运行第二个容器
[root@localhost ~]# docker run -it --rm busybox:latest /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
13: eth0@if14: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

#第一个容器ping第二个容器
/ # ping -c2 172.17.0.3
PING 172.17.0.3 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.056 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.066 ms

#第二个容器ping第一个容器
/ # ping -c2 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.032 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.062 ms
  • 容器间使用名字互相访问

[root@localhost ~]# docker run --rm -it --name test-name  busybox:1.36 /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
19: eth0@if20: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
[root@localhost ~]# docker run -it --rm --link test-name:server1 busybox:latest /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
21: eth0@if22: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.4/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3      server1 d9a9e6278e9f test-name
172.17.0.4      9e6943af3424
/ # ping -c 2 server1
PING server1 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.035 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.073 ms

--- server1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.035/0.054/0.073 ms
/ # ping -c 2 test-name
PING test-name (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.035 ms
64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.062 ms

--- test-name ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.035/0.048/0.062 ms
/ #

(2)容器使用不同的bridge模式

[root@localhost ~]# docker network  ls
NETWORK ID     NAME        DRIVER    SCOPE
9d160e040474   bridge      bridge    local
d6f8e8de2c03   host        host      local
f65f09f8a274   my-bridge   bridge    local
ec6c758e2ed2   none        null      local
#查看新添加的网桥
[root@localhost ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
br-f65f09f8a274         8000.02427ef21b98       no              vethf8467c8
docker0         8000.0242c16db1ae       no              vethcde7c3a
#运行第一个容器使用bridge模式
[root@localhost ~]# docker run  --rm --network bridge -it busybox:1.36  /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
5: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

#在另外一个终端窗口运行第二个容器使用my-bridge模式
[root@localhost ~]# docker run -it --rm --network my-bridge busybox:latest  /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
7: eth0@if8: <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

#两个容器互相访问,访问失败
#第一个容器ping第二个容器
/ # ping 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes

#第二个容器ping第一个容器
/ # ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes

查看防火墙规则

#重新配置iptables规则
#(1)先将旧的iptables规则保存至iptables.sh文件中
[root@localhost ~]# iptables-save >  /root/iptables.sh
#(2)修改iptables.sh文件,注释下面两行
#-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
#-A DOCKER-ISOLATION-STAGE-2 -o br-f65f09f8a274 -j DROP
#(3)导入新的iptables规则
[root@localhost ~]# iptables-restore < /root/iptables.sh
#两个容器互相测试
#第一个容器访问第二个容器
/ # ping -c2 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=63 time=0.066 ms
64 bytes from 172.18.0.2: seq=1 ttl=63 time=0.093 ms
#第二个容器访问第一个容器
/ # ping -c3 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=63 time=0.067 ms
64 bytes from 172.17.0.2: seq=1 ttl=63 time=0.095 ms
64 bytes from 172.17.0.2: seq=2 ttl=63 time=0.095 ms

5.6.2 不同主机上的容器间的通信

两个主机上的容器之间的通信前提是宿主机之间的网络是可以互相通信的,然后两个容器才可以通过宿主机访问到对方的容器。

(1)使用网络路由实现不同主机上的容器间的通信

实现原理是在宿主机做一个网络路由就可以实现宿主机A的容器访问宿主机容器B的目的。

由于docker默认网段是172.17.0.X/24,如果要做路由每个宿主机的docker使用的默认网段不能一致。

#服务器A使用默认网段
#服务器B修改默认网段为10.10.0.X/24
[root@node02 yum.repos.d]# vim  /usr/lib/systemd/system/docker.service
#修改该行信息
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --bip=10.10.0.1/24
[root@node02 yum.repos.d]# systemctl daemon-reload
[root@node02 yum.repos.d]# systemctl start docker
[root@node02 yum.repos.d]# ip a show docker0
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
    link/ether 02:42:bb:a0:47:ca brd ff:ff:ff:ff:ff:ff
    inet 10.10.0.1/24 brd 10.10.0.255 scope global docker0
       valid_lft forever preferred_lft forever
#服务器A启动容器
[root@localhost ~]# docker run -it --rm busybox:1.36 /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
9: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

#服务器B启动容器
[root@node02 ~]# docker run -it --rm busybox:latest /bin/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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:42:0a:0a:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.10.0.2/24 brd 10.10.0.255 scope global eth0
       valid_lft forever preferred_lft forever
#容器间互相访问测试,无法访问
/ # ping -c2 10.10.0.2
PING 10.10.0.2 (10.10.0.2): 56 data bytes

--- 10.10.0.2 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss

/ # ping -c 2 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes

--- 172.17.0.2 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss
#给serverA添加静态路由,当本机要访问10.10.0.0/24网段的主机的时候,网关指定为serverB宿主机的地址
[root@localhost ~]# route add -net 10.10.0.0/24 gw 192.168.168.102
#设置防火墙规则
[root@localhost ~]# iptables -A FORWARD -s 192.168.168.0/24 -j ACCEPT


#给serverB添加静态路由,网关地址为serverA的地址
[root@node02 ~]#  route add -net 172.17.0.0/24 gw 192.168.168.101
#设置防火墙规则
[root@localhost ~]# iptables -A FORWARD -s 192.168.168.0/24 -j ACCEPT
#测试
#宿主机A容器访问宿主机B容器
/ # ping -c2 10.10.0.2
PING 10.10.0.2 (10.10.0.2): 56 data bytes
64 bytes from 10.10.0.2: seq=0 ttl=62 time=0.461 ms
64 bytes from 10.10.0.2: seq=1 ttl=62 time=0.556 ms

--- 10.10.0.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.461/0.508/0.556 ms

#宿主机B容器访问宿主机A容器
/ # ping -c 2 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=62 time=0.491 ms
64 bytes from 172.17.0.2: seq=1 ttl=62 time=0.575 ms

(2)使用docker原生支持的macvlan实现不同主机上的容器间的通信

macvlan网络方式:

  • Linux kernel提供的一种网卡虚拟化技术。

  • 无需Linux bridge,直接使用物理接口,性能极好

  • 容器的接口直接与主机网卡连接,无需NAT或端口映射。

  • macvlan会独占主机网卡,但可以使用vlan子接口实现多macvlan网络

  • vlan可以将物理二层网络划分为4094个逻辑网络,彼此隔离,vlan id取值为1~4094

macvlan网络间的隔离和连通:

  • macvlan网络在二层上是隔离的,所以不同macvlan网络的容器是不能通信的

  • 可以在三层上通过网关将macvlan网络连通起来

  • docker本身不做任何限制,像传统vlan网络那样管理即可

#开启两个虚拟机,分别在主机上添加一个仅主机模式的网卡,打开该网卡的混杂模式。PROMISC,表示该网卡处于混杂模式
[root@docker1 ~]# ip link set ens224 promisc on
[root@docker1 ~]# ifconfig  ens224
ens224: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST>  mtu 1500
[root@docker2 ~]# ip a show ens224
5: ens224: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
[root@docker1 ~]# docker network  create -d macvlan  --subnet 1.1.1.0/24 --gateway 1.1.1.1 -o parent=ens224 macvlan1
[root@docker2 ~]#  docker network  create -d macvlan  --subnet 1.1.1.0/24 --gateway 1.1.1.1 -o parent=ens224 macvlan1
#测试
[root@docker1 ~]# docker run -it --rm  --network macvlan1 --ip 1.1.1.10  busybox:latest 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
6: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 8a:2e:3c:2f:37:30 brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.10/24 brd 1.1.1.255 scope global eth0
       valid_lft forever preferred_lft forever

[root@docker2 ~]# docker run -it --rm  --network macvlan1  busybox:latest 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
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
8: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 8e:9c:46:fd:b7:de brd ff:ff:ff:ff:ff:ff
    inet 1.1.1.2/24 brd 1.1.1.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # ping -c2 1.1.1.10
PING 1.1.1.10 (1.1.1.10): 56 data bytes
64 bytes from 1.1.1.10: seq=0 ttl=64 time=0.364 ms
64 bytes from 1.1.1.10: seq=1 ttl=64 time=0.857 ms

六、docker compose单机容器编排工具

6.1 compose简介

Compose是一个用于定义和运行多容器Docker应用程序的工具。您可以使用Compose文件来配置应用程序的服务,然后使用单个命令从配置中创建并启动所有服务。compose的配置文件示例如下

compose的github网址:https://github.com/docker/compose

6.2 compose安装

docker-compose项目是docker官方的开源项目,负责实现对docker容器集群的快速编排。Docker Compose 依靠Docker Engine 进行任何有意义的工作,因此请确保您已在本地或远程安装Docker Engine。目前docker-compose提供两个版本,v1和v2版本,本文中只讨论v2版本。

compose有两种安装形式:

  • 插件形式安装compose:在安装最新版本docker时,docker compose会以插件形式安装。

#查看docker-ce和docker-compose-plugin的版本
[root@localhost ~]# rpm -qa | grep docker
docker-compose-plugin-2.29.7-1.el9.x86_64
docker-buildx-plugin-0.17.1-1.el9.x86_64
docker-ce-cli-27.3.1-1.el9.x86_64
docker-ce-rootless-extras-27.3.1-1.el9.x86_64
docker-ce-27.3.1-1.el9.x86_64

#查看插件形式安装的docker compose的版本
[root@localhost ~]# docker compose version
Docker Compose version v2.29.7
[root@localhost ~]# yum search  docker-compose-plugin
docker-compose-plugin.x86_64 : Docker Compose (V2) plugin for the Docker CLI
#命令帮助
[root@localhost ~]# docker compose  -h
Flag shorthand -h has been deprecated, use --help

Usage:  docker compose [OPTIONS] COMMAND
Options:
  -f, --file stringArray           Compose configuration files  #指定compose模板文件
Commands:
  down        Stop and remove containers, networks
  images      List images used by the created containers
  kill        Force stop service containers
  logs        View output from containers
  ls          List running compose projects
  port        Print the public port for a port binding
  ps          List containers
  restart     Restart service containers
  rm          Removes stopped service containers
  start       Start services
  stats       Display a live stream of container(s) resource usage statistics
  stop        Stop services
  top         Display the running processes
  up          Create and start containers
  version     Show the Docker Compose version information
#docker compose  up帮助信息
[root@localhost ~]# docker compose  up --help
Usage:  docker compose up [OPTIONS] [SERVICE...]
Create and start containers
  -d, --detach                       Detached mode: Run containers in the background  #在后台运行容器
  • 独立模式安装compose:在官网下载docker-compose二进制包以独立模式安装docker-compose。

docker-compose下载地址:https://github.com/docker/compose/releases

[root@localhost ~]# cd /usr/local/bin/
#将docker-compose二进制文件下载至该目录
[root@localhost bin]# ll -h
总用量 61M
-rw-r--r-- 1 root root 61M 11月 12 14:53 docker-compose-linux-x86_64
[root@localhost bin]# chmod a+x docker-compose-linux-x86_64
[root@localhost bin]# ll
总用量 61608
-rwxr-xr-x 1 root root 63083304 11月 12 14:53 docker-compose-linux-x86_64
[root@localhost bin]# /usr/local/bin/docker-compose-linux-x86_64  --version
Docker Compose version v2.28.0
#制作命令软链接
[root@localhost bin]# ln -s docker-compose-linux-x86_64 docker-compose
[root@localhost bin]# ll
总用量 61608
lrwxrwxrwx 1 root root       27 11月 12 14:54 docker-compose -> docker-compose-linux-x86_64
-rwxr-xr-x 1 root root 63083304 11月 12 14:53 docker-compose-linux-x86_64
[root@localhost bin]# docker-compose --version
Docker Compose version v2.28.0
#查看命令帮助,和docker compose用法一样
[root@localhost bin]# docker-compose --help
Usage:  docker compose [OPTIONS] COMMAND
Define and run multi-container applications with Docker

6.3 docker compose启动容器

6.3.1 compose启动容器步骤

使用 Docker Compose 分为三个步骤:

  1. 使用Dockerfile定义您的应用程序的环境,以便它可以在任何地方重现。

  2. compose.yaml文件中定义组成应用程序的服务,以便它们可以在隔离的环境中一起运行。关于compose.yml文件的官网说明:https://github.com/compose-spec/compose-spec/blob/main/spec.md

    在新版的compose中Compose文件的默认路径是compose.yaml(首选)或compose.yml放置在工作目录中。Compose 还支持docker-compose.yaml和docker-compose.yml以向后兼容早期版本。如果两个文件都存在,Compose 会首选规范的compose.yaml。

    compose文件的组成部分如下:

  3. 最后,运行docker compose up将启动并运行您的整个应用程序。

6.3.2 compose的yml文件格式

#用'#'表示注释
services:    #服务字段,必选项
  web:      #自定义服务名称,唯一
    image: nginx  #镜像名称
    build: ./dir  #指定dockerfile所在路径
    container_name: nginx  #容器名称
    restart: always  #设定容器失败时总是自动重启
    expose:   #指定容器暴露哪些端口,不会映射到主机的端口
      - 80  
    ports: 
      - "80:80"
    volumes:
      - ./conf.d/:/etc/nginx/conf.d/:ro  #将本机的文件以只读方式映射至目标容器中
      - db-data:/var/lib/backup/data
    network_mode: bridge  #使用docker默认创建的bridge网络
volumes:
  db-data:
networks:
  mynet1: 
    driver: bridge
  mynet2: 
    driver: bridge
    external: true  #不创建新的网络而使用已经创建好的网络,如果网络不存在会报错

6.3.3 启动一个简单的容器

#创建compose的项目目录
[root@localhost ~]# mkdir -p /compose/nginx/
[root@localhost ~]# cd /compose/nginx/
[root@localhost nginx]# cat compose.yml
services:
  web:
    image: nginx:1.27.2
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
#根据compose.yml文件启动容器
[root@localhost nginx]# docker compose  up -d
[+] Running 1/1
 ✔ Container nginx  Started                                                             0.2s
#容器名称为nginx
[root@localhost nginx]# docker compose ps
NAME      IMAGE          COMMAND                   SERVICE   CREATED         STATUS         PORTS
nginx     nginx:1.27.2   "/docker-entrypoint.…"   web       4 minutes ago   Up 4 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
[root@localhost nginx]# curl  -s -w "%{http_code}\n" -o /dev/null localhost:80
200
#停止容器,但不删除容器
[root@localhost nginx]# docker compose  stop
[root@localhost nginx]# docker ps -a
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS                      PORTS     NAMES
20a277d0d5ae   nginx:1.27.2   "/docker-entrypoint.…"   9 minutes ago   Exited (0) 22 seconds ago             nginx

#停止容器并删除
[root@localhost nginx]# docker compose  down
[+] Running 2/2
 ✔ Container nginx        Removed                                                       0.0s
 ✔ Network nginx_default  Removed                                                       0.1s
[root@localhost nginx]# docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

6.3.4 启动多个容器

[root@localhost compose]# mkdir -p tomcat-nginx/tomcat-base
[root@localhost compose]# cd tomcat-nginx/
#使用4.2.4自定义的tomcat镜像运行容器
[root@localhost tomcat-nginx]# ll tomcat-base/
总用量 187860
-rw-r--r-- 1 root root  12716365 11月 12 16:12 apache-tomcat-9.0.96.tar.gz
-rw-r--r-- 1 root root       568 11月 12 16:13 dockerfile
-rw-r--r-- 1 root root       142 11月 12 16:12 java.sh
-rw-r--r-- 1 root root 179640645  1月 19  2019 jdk-11.0.2_linux-x64_bin.tar.gz

#build参数说明:https://github.com/compose-spec/compose-spec/blob/main/build.md
[root@localhost tomcat-nginx]# cat compose.yml
[root@localhost tomcat-nginx]# cat compose.yml
services:
  service-web-nginx:
    image: nginx:1.27.2
    container_name: web-nginx1
    volumes:
      - ./html:/usr/share/nginx/html:ro
    ports:
      - "80:80"
      - "443:443"

  service-web-tomcat:
    image: tomcat:base
    build: ./tomcat-base
    container_name: web-tomcat1
    ports:
      - "8080:8080"
[root@localhost tomcat-nginx]# cat ./html/index.html
this is nginx test
#运行容器
[root@localhost tomcat-nginx]# docker compose  up -d      
#查看compose配置
[root@localhost tomcat-nginx]# docker compose  ps
NAME          IMAGE          COMMAND                   SERVICE              CREATED          STATUS          PORTS
web-nginx1    nginx:1.27.2   "/docker-entrypoint.…"   service-web-nginx    28 seconds ago   Up 26 seconds   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp
web-tomcat1   tomcat:base    "/usr/local/tomcat/b…"   service-web-tomcat   28 seconds ago   Up 26 seconds   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp
#在浏览器使用ip地址测试访问
[root@localhost tomcat-nginx]# curl  localhost:80
this is nginx test

6.3.5 配置haproxy和nginx负载均衡

[root@localhost compose]# mkdir haproxy-nginx
[root@localhost compose]# cd haproxy-nginx
[root@localhost haproxy-nginx]# mkdir -p web{1,2}/html
[root@localhost haproxy-nginx]# echo this is web1 >  web1/html/index.html
[root@localhost haproxy-nginx]# echo this is web2 >  web2/html/index.html
[root@localhost haproxy-nginx]# mkdir haproxy
[root@localhost haproxy-nginx]# cat haproxy/haproxy.cfg
listen web_port_80
    bind 0.0.0.0:80
    mode http
    balance roundrobin
    server web1 web1:80  check inter 3000 fall 2 rise 5
    server web2 web2:80  check inter 3000 fall 2 rise 5
[root@localhost haproxy-nginx]# cat compose.yml
services:
  web1:
    image: nginx:1.27.2
    container_name: web1
    volumes:
      - ./web1/html:/usr/share/nginx/html
    networks:
      - mynet1
  web2:
    image: nginx:1.27.2
    container_name: web2
    volumes:
      - ./web2/html:/usr/share/nginx/html
    networks:
      - mynet2
  haproxy:
    image: haproxy:3.0
    container_name: haproxy
    volumes:
      - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
    networks:
      - mynet1
      - mynet2
    ports:
      - 80:80

networks:
  mynet1:
    driver: bridge
  mynet2:
    driver: bridge
       
[root@localhost haproxy-nginx]# docker compose  up -d
[+] Running 5/5
 ✔ Network haproxy-nginx_mynet1  Created                                   0.0s
 ✔ Network haproxy-nginx_mynet2  Created                                   0.1s
 ✔ Container web1                Started                                   0.7s
 ✔ Container haproxy             Started                                   0.7s
 ✔ Container web2                Started                                   0.6s
[root@localhost haproxy-nginx]# docker network  ls
NETWORK ID     NAME                   DRIVER    SCOPE
daa38f832e19   bridge                 bridge    local
b9e9cbce7940   haproxy-nginx_mynet1   bridge    local
b44eba593328   haproxy-nginx_mynet2   bridge    local
aefd4dec55a1   harbor_harbor-clair    bridge    local
62d99472c7fd   host                   host      local
8ea12eac3429   none                   null      local

[root@localhost haproxy-nginx]# docker ps
CONTAINER ID   IMAGE          COMMAND                   CREATED         STATUS         PORTS                               NAMES
fe4a95be1653   haproxy:3.0    "docker-entrypoint.s…"   3 minutes ago   Up 3 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp   haproxy
06003d69c107   nginx:1.27.2   "/docker-entrypoint.…"   3 minutes ago   Up 3 minutes   80/tcp                              web2
e6d76e7f31c9   nginx:1.27.2   "/docker-entrypoint.…"   3 minutes ago   Up 3 minutes   80/tcp                              web1
#测试
[root@localhost haproxy-nginx]# for i in {1..10};do curl localhost:80;done
this is web2
this is web1
this is web2
this is web1
this is web2
this is web1
this is web2
this is web1
this is web2
this is web1
#停止并删除容器
[root@localhost haproxy-nginx]# docker compose  down
[+] Running 5/5
 ✔ Container web1                Removed                                   0.1s
 ✔ Container web2                Removed                                   0.1s
 ✔ Container haproxy             Removed                                   0.2s
 ✔ Network haproxy-nginx_mynet1  Removed                                   0.2s
 ✔ Network haproxy-nginx_mynet2  Removed                                   0.1s

七、docker registry

7.1 了解Docker Registry

7.1.1 介绍

  • registry 用于保存docker 镜像,包括镜像的层次结构和元数据。

  • 启动容器时,docker daemon会试图从本地获取相关的镜像;本地镜像不存在时,其将从registry中下载该镜像并保存到本地;

  • 拉取镜像时,如果不知道registry仓库地址,默认从Docker Hub搜索拉取镜像

7.1.2 分类

  • Sponsor Registry:第三方的registry,供客户和docker社区使用;

  • mirror Registry:第三方的registry,只让客户使用;如docker cn和阿里云的镜像加速器;

  • vendor Registry:服务商的registry,由发布docker镜像的供应商提供的registry;如红帽提供的专有的,收费提供;

  • private Registry:通过设有防火墙和额外的安全层的私有实体提供的registry;自建的registry,在本地搭建registry,节省带宽

7.1.3 registry组成(repository和index)

(1)Repository

  • 由特定的docker镜像的所有迭代版本组成的镜像仓库;

  • 一个registry中可以存在多个repository:

    • repository可分为“顶层仓库”和“用户仓库”

    • 用户仓库名称格式为“用户名/仓库名”

  • 每个仓库可以包含多个Tag(标签),每个标签对应一个镜像

(2)Index

  • 维护用户账户、镜像的校验以及公共命名空间的信息

  • 相当于为registry提供了一个完成用户认证等功能的检索接口

7.1.4 拉取上传仓库镜像

(1)拉取镜像

docker pull <registry>[:<port>]/[<namespace>/]<name>:<tag>
  • registry:仓库服务器地址:不指定默认是docker hub

  • port:端口;默认是443,因为是https协议

  • namespace:名称空间,指是哪个用户的仓库,如果是顶层仓库,可省略

  • name:仓库名

  • tag:标签名;默认是latest版本

(2)上传镜像

docker push [OPTIONS] NAME[:TAG]

7.1.5 知名docker仓库

7.2 在docker hub上创建自己的仓库

(1)在docker hub上创建,但注册docker hub需要科学上网;

说明:也可以在阿里云上阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台创建自己的docker仓库

(2)将镜像上传到自己的registry

[root@localhost ~]# docker images 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test/nginx          1.15.5              5ca46f021c31        18 hours ago        253 MB
[root@localhost ~]# docker tag test/nginx:1.15.5 zhaojungle/nginx:1.15.5
[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test/nginx          1.15.5              5ca46f021c31        18 hours ago        253 MB
zhaojungle/nginx    1.15.5              5ca46f021c31        18 hours ago        253 MB
[root@localhost ~]# docker login -u zhaojungle
Password: 
Login Succeeded
[root@localhost ~]# docker push zhaojungle/nginx:1.15.5
The push refers to a repository [docker.io/zhaojungle/nginx]
70d470e27542: Pushed 
eb29745b8228: Pushed 
1.15.5: digest: sha256:73d7988aeef4b0a5bc30c36a369fc51c372bf6953fd5b3fdccde5177821569c6 size: 741

可在网页上查看到上传成功

(3)拉取自己的registry仓库中的镜像

先上传一个体积较小的镜像用来做测试:

[root@localhost ~]# docker run -itd  busybox sh
04170637da19d75af5ba46756a652b09fe163666e3cbf66902ab6235b243ce47
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
04170637da19        busybox             "sh"                     18 seconds ago      Up 17 seconds                           loving_meitner
7965e75709f9        php:5.6-fpm         "docker-php-entryp..."   24 hours ago        Up 24 hours         9000/tcp            php-fpm
[root@localhost ~]# docker exec -it 041 sh
/ # echo this is a test > test
/ # cat test 
this is a test
/ # exit
[root@localhost ~]# docker commit -a "add a test file" 041 zhaojungle/test:latest
sha256:9b5578cc29ffab8bf3269c855692945462a28eecb7cb02e906d71c6dc9c623a9
[root@localhost ~]# docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
zhaojungle/test      latest              9b5578cc29ff        21 seconds ago      1.22 MB
[root@localhost ~]# docker push  zhaojungle/test:latest 

将刚刚上传的镜像下载下来并运行:

[root@localhost ~]# docker pull zhaojungle/test:latest 
latest: Pulling from zhaojungle/test
76df9210b28c: Already exists 
9be8462314c7: Pull complete 
Digest: sha256:c4df398b85a2f749988e9f94b3ad42dc484e7d0753b67c31e37d10a21bc4717e
Status: Downloaded newer image for zhaojungle/test:latest
[root@localhost ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
zhaojungle/test     latest              9b5578cc29ff        5 minutes ago       1.22 MB
[root@localhost ~]# docker run -it --rm zhaojungle/test sh
/ # cat test 
this is a test

7.3 搭建私有docker registry

docker官方提供的开源Registry很简单,只能作为存储镜像的仓库,没有额外的功能。

官方文档地址:Deprecated and retired Docker products and features | Docker Docs

官方github地址:https://github.com/distribution/distribution

7.3.1 下载docker registry镜像

[root@localhost ~]# docker pull registry
[root@localhost ~]# docker image ls registry
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
registry     latest    75ef5b734af4   13 months ago   25.4MB

7.3.2 搭建仓库

  • 配置不带用户认证的registry

#指定将镜像存储至宿主机的/data/registry/
[root@localhost ~]# docker run --name registry -p 5000:5000 -v /registry:/var/lib/registry -d registry:latest
fc9e1ebae2808d13d402d25a1d3a17ae3e767d2fd4e697a11d6725e9b254a1a1
[root@localhost ~]# docker ps
CONTAINER ID   IMAGE             COMMAND                   CREATED        STATUS        PORTS                                       NAMES
fc9e1ebae280   registry:latest   "/entrypoint.sh /etc…"   1 second ago   Up 1 second   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   registry
#给镜像重新打标签
[root@localhost ~]# docker image tag nginx:1.14-alpine 192.168.168.101:5000/test-nginx:1.14-alpine
#上传镜像会报错,因为上传时默认使用https的协议
[root@localhost ~]# docker push  192.168.168.101:5000/test-nginx:1.14-alpine
The push refers to repository [192.168.168.101:5000/test-nginx]
Get "https://192.168.168.101:5000/v2/": http: server gave HTTP response to HTTPS client
#将私有仓库认证为安全仓库
[root@localhost ~]# cat /etc/docker/daemon.json
{
  "registry-mirrors": [
        "https://docker.m.daocloud.io",
        "https://huecker.io",
        "https://dockerhub.timeweb.cloud",
        "https://noohub.ru",
        "https://hub.oepkgs.net"
        ],
  "exec-opts": ["native.cgroupdriver=systemd"],
  "insecure-registries": ["192.168.168.101:5000"]  #添加此行信息
}
[root@localhost ~]# systemctl restart docker
#启动容器
[root@localhost ~]# docker start registry
#此时无需用户认证即可上传镜像
[root@localhost ~]# docker push  192.168.168.101:5000/test-nginx:1.14-alpine
The push refers to repository [192.168.168.101:5000/test-nginx]
076c58d2644f: Pushed
b2cbae4b8c15: Pushed
5ac9a5170bf2: Pushed
a464c54f93a9: Pushed
1.14-alpine: digest: sha256:a3a0c4126587884f8d3090efca87f5af075d7e7ac8308cffc09a5a082d5f4760 size: 1153

#可以看到宿主机的该目录下有镜像数据了
[root@localhost ~]# ll /registry/
总用量 0
drwxr-xr-x 3 root root 22 11月  5 18:33 docker

#删除容器
[root@localhost auth]# docker rm -f registry
  • 配置带basic认证的registry

[root@localhost ~]# yum install -y httpd-tools
[root@localhost ~]# mkdir /registry-auth
[root@localhost registry-auth]# htpasswd  -Bbn admin admin >> registry_htpasswd
[root@localhost registry-auth]# cat registry_htpasswd
admin:$2y$05$RCu8PiM0r.jXB/XJZ4oYNeHQozB4lH1IvdoxiTG.lwF9I1P2lF/F6
#运行容器
[root@localhost registry-auth]#  docker run -d -p 5000:5000 -v /registry-auth/registry:/var/lib/registry -v /registry-auth/auth/:/auth -e "REGISTRY_AUTH=htpasswd" -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/registry_htpasswd" --name registry registry
6364cb3f0653ce4a89a59c6a54c322136874e4c4598e90f49dfe57575dad7869
[root@localhost registry-auth]# docker ps
CONTAINER ID   IMAGE      COMMAND                   CREATED        STATUS        PORTS                                       NAMES
6364cb3f0653   registry   "/entrypoint.sh /etc…"   1 second ago   Up 1 second   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   registry


#查看宿主机镜像存储目录,由于还未上传镜像,所以为空
[root@localhost registry-auth]# ll registry/
总用量 0
#上传镜像,提示未认证
[root@localhost registry-auth]# docker push 192.168.168.101:5000/test-nginx:1.14-alpine
The push refers to repository [192.168.168.101:5000/test-nginx]
076c58d2644f: Preparing
b2cbae4b8c15: Preparing
5ac9a5170bf2: Preparing
a464c54f93a9: Preparing
no basic auth credentials
#登录
[root@localhost registry-auth]# docker login  192.168.168.101:5000
Username: admin
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/#credential-stores

Login Succeeded
#认证成功后会生成认证文件
[root@localhost registry-auth]# cat /root/.docker/config.json
{
        "auths": {
                "192.168.168.101:5000": {
                        "auth": "YWRtaW46YWRtaW4="
                }
        }
}

#上传镜像,成功
[root@localhost registry-auth]# docker push 192.168.168.101:5000/test-nginx:1.14-alpine
The push refers to repository [192.168.168.101:5000/test-nginx]
076c58d2644f: Pushed
b2cbae4b8c15: Pushed
5ac9a5170bf2: Pushed
a464c54f93a9: Pushed
1.14-alpine: digest: sha256:a3a0c4126587884f8d3090efca87f5af075d7e7ac8308cffc09a5a082d5f4760 size: 1153
[root@localhost registry-auth]# ll registry/
总用量 0
drwxr-xr-x 3 root root 22 11月  5 19:04 docker
#在node02上下载镜像
[root@node02 ~]# cat /etc/docker/daemon.json
{
        "insecure-registries": ["192.168.168.101:5000"]
}

[root@node02 ~]# systemctl restart docker
#提示未认证,无法拉取
[root@node02 ~]# docker pull  192.168.168.101:5000/test-nginx:1.14-alpine
Error response from daemon: Head "http://192.168.168.101:5000/v2/test-nginx/manifests/1.14-alpine": no basic auth credentials
#认证成功
[root@node02 ~]# docker login 192.168.168.101:5000
Username: admin
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/#credential-stores
#拉取镜像
Login Succeeded
[root@node02 ~]# docker pull  192.168.168.101:5000/test-nginx:1.14-alpine
[root@node02 ~]# docker image ls 192.168.168.101:5000/test-nginx
REPOSITORY                        TAG           IMAGE ID       CREATED       SIZE
192.168.168.101:5000/test-nginx   1.14-alpine   8a2fb25a19f5   5 years ago   16MB
#运行镜像
[root@node02 ~]# docker run -it --rm 192.168.168.101:5000/test-nginx:1.14-alpine /bin/sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:0a:0a:00:02 brd ff:ff:ff:ff:ff:ff
    inet 10.10.0.2/24 brd 10.10.0.255 scope global eth0
       valid_lft forever preferred_lft forever

7.4 安装搭建私有仓库Harbor

7.4.1 Harbor介绍

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,由vmware开源,Harbor封装了Docker的registry v2,添加一些企业必须的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。其目标是帮助用户迅速搭建一个企业级的Docker registry服务。

vmware官方开源服务列表地址:https://github.com/vmware/

harbor官方github地址:https://github.com/goharbor/harbor

harbor官方网址:Harbor

harbor特点:

  • 基于角色的访问控制:一个用户可以对多个镜像仓库在同一命名空间(project)里有不同的权限。

  • 镜像复制:镜像可以在多个Registry实例中复制(同步)。尤其适合于负载均衡,高可用,混合云和多云的场景。

  • 图形化用户界面:用户可以通过浏览器来浏览,检索当前docker镜像仓库,管理项目和命名空间。

  • AD/LDAP:harbor可以集成企业内部已有的AD/LDAP,用于鉴权认证管理。

  • 审计管理:所有针对镜像仓库的操作都可以被记录追溯,用于审计管理。

  • 国际化:已拥有英文、中文、德文、日文和俄文的本地化版本。更多的语言将会添加进来。

  • RESTful API:提供给管理员对Harbor更多的操控,使得与其它管理软件集成变得更容易。

  • 部署简单:提供在线和离线两种安装工具。

7.4.2 安装harbor

下载地址:https://github.com/goharbor/harbor/releases

[root@localhost ~]# cd /opt/
[root@localhost opt]# ll -h  harbor-offline-installer-v2.11.1.tgz
-rw-r--r-- 1 root root 628M 11月  7 15:24 harbor-offline-installer-v2.11.1.tgz

[root@localhost opt]# tar xvf harbor-offline-installer-v2.11.1.tgz  -C /usr/local/
[root@localhost opt]# cd /usr/local/harbor/
[root@localhost harbor]# ll
总用量 646848
-rw-r--r-- 1 root root      3646  8月 15 18:07 common.sh
-rw-r--r-- 1 root root 662330539  8月 15 18:07 harbor.v2.11.1.tar.gz
-rw-r--r-- 1 root root     14270  8月 15 18:07 harbor.yml.tmpl
-rwxr-xr-x 1 root root      1975  8月 15 18:07 install.sh
-rw-r--r-- 1 root root     11347  8月 15 18:07 LICENSE
-rwxr-xr-x 1 root root      1882  8月 15 18:07 prepare

(1)配置http网站的harbor仓库

#修改仓库配置文件
[root@localhost harbor]# cp harbor.yml.tmpl harbor.yml
[root@localhost harbor]# vim harbor.yml
#配置仓库域名
hostname: reg.jungle.com  
#配置http协议
http:
  port: 80
#注释https协议的配置
#https:
#  # https port for harbor, default is 443
#  port: 443
#  # The path of cert and key files for nginx
#  certificate: /your/certificate/path
#  private_key: /your/private/key/path
#配置仓库的admin账号的密码
harbor_admin_password: 123456
#默认的数据目录,可以单独新建一个分区挂载至此目录用来存储仓库镜像数据
data_volume: /data
#目前还没有数据目录,运行仓库后会创建
[root@localhost harbor]# ll /data
ls: 无法访问 '/data': 没有那个文件或目录
#运行配置仓库的脚本
[root@localhost harbor]# ./install.sh
#data目录会生成如下一些文件
[root@localhost harbor]# ll /data/
总用量 0
drwxr-xr-x. 2            10000 10000  6 11月  7 15:51 ca_download
drwx------. 3 systemd-coredump input 18 11月  7 15:51 database
drwxr-xr-x. 2            10000 10000  6 11月  7 15:51 job_logs
drwxr-xr-x. 2 systemd-coredump input  6 11月  7 15:51 redis
drwxr-xr-x. 2            10000 10000  6 11月  7 15:51 registry
drwxr-xr-x. 5 root             root  46 11月  7 15:51 secret
#仓库相关的容器
[root@localhost harbor]# docker ps
CONTAINER ID   IMAGE                                 COMMAND                   CREATED          STATUS                    PORTS                                     NAMES
447e2c4af6a4   goharbor/harbor-jobservice:v2.11.1    "/harbor/entrypoint.…"   48 seconds ago   Up 42 seconds (healthy)                                             harbor-jobservice
2f061c322f76   goharbor/nginx-photon:v2.11.1         "nginx -g 'daemon of…"   48 seconds ago   Up 46 seconds (healthy)   0.0.0.0:80->8080/tcp, [::]:80->8080/tcp   nginx
3d3534cea04a   goharbor/harbor-core:v2.11.1          "/harbor/entrypoint.…"   48 seconds ago   Up 46 seconds (healthy)                                             harbor-core
2fa54b9c5c18   goharbor/harbor-registryctl:v2.11.1   "/home/harbor/start.…"   48 seconds ago   Up 47 seconds (healthy)                                             registryctl
2285a5756805   goharbor/redis-photon:v2.11.1         "redis-server /etc/r…"   48 seconds ago   Up 47 seconds (healthy)                                             redis
3262282e8161   goharbor/harbor-portal:v2.11.1        "nginx -g 'daemon of…"   48 seconds ago   Up 47 seconds (healthy)                                             harbor-portal
436b23b2fbc9   goharbor/registry-photon:v2.11.1      "/home/harbor/entryp…"   48 seconds ago   Up 47 seconds (healthy)                                             registry
32b18cca200f   goharbor/harbor-db:v2.11.1            "/docker-entrypoint.…"   48 seconds ago   Up 47 seconds (healthy)                                             harbor-db
13653c09d87f   goharbor/harbor-log:v2.11.1           "/bin/sh -c /usr/loc…"   48 seconds ago   Up 47 seconds (healthy)   127.0.0.1:1514->10514/tcp                 harbor-log

在windows上写hosts解析文件后在浏览器通过域名访问:

登陆成功后如下所示

向仓库上传镜像

#添加仓库免密认证
[root@localhost ~]# grep insecure /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry reg.jungle.com
[root@localhost ~]# systemctl daemon-reload
[root@localhost ~]# systemctl restart docker
#重新启动一下仓库
[root@localhost harbor]# docker compose stop
WARN[0000] /usr/local/harbor/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Stopping 9/9
 ✔ Container nginx              Stopped                                                           0.0s
 ✔ Container registryctl        Stopped                                                           0.0s
 ✔ Container harbor-jobservice  Stopped                                                           0.0s
 ✔ Container harbor-portal      Stopped                                                           0.0s
 ✔ Container harbor-core        Stopped                                                           0.0s
 ✔ Container harbor-db          Stopped                                                           0.1s
 ✔ Container registry           Stopped                                                           0.0s
 ✔ Container redis              Stopped                                                           0.0s
 ✔ Container harbor-log         Stopped                                                          10.2s
[root@localhost harbor]# docker compose  start
WARN[0000] /usr/local/harbor/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 9/9
 ✔ Container harbor-log         Started                                                           0.2s
 ✔ Container registryctl        Started                                                           0.6s
 ✔ Container registry           Started                                                           0.5s
 ✔ Container redis              Started                                                           0.5s
 ✔ Container harbor-portal      Started                                                           0.5s
 ✔ Container harbor-db          Started                                                           0.5s
 ✔ Container harbor-core        Started                                                           0.2s
 ✔ Container harbor-jobservice  Started                                                           0.7s
 ✔ Container nginx              Started                                                           0.7s

#给镜像重新打标签
[root@localhost harbor]# docker tag nginx:1.27.2 reg.jungle.com/library/testnginx:1.27.2

#上传镜像失败,需要认证登录
[root@localhost harbor]# docker push  reg.jungle.com/library/testnginx:1.27.2
The push refers to repository [reg.jungle.com/library/testnginx]
825fb68b6033: Preparing
7619c0ba3c92: Preparing
1c1f11fd65d6: Preparing
6b133b4de5e6: Preparing
3d07a4a7eb2a: Preparing
756474215d29: Waiting
8d853c8add5d: Waiting
unauthorized: unauthorized to access repository: library/testnginx, action: push: unauthorized to access repository: library/testnginx, action: push

#登录
[root@localhost harbor]# docker login  reg.jungle.com
Username: admin
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/#credential-stores

Login Succeeded
#登录成功后再次上传
[root@localhost harbor]# docker push  reg.jungle.com/library/testnginx:1.27.2
The push refers to repository [reg.jungle.com/library/testnginx]
825fb68b6033: Pushed
7619c0ba3c92: Pushed
1c1f11fd65d6: Pushed
6b133b4de5e6: Pushed
3d07a4a7eb2a: Pushed
756474215d29: Pushed
8d853c8add5d: Pushed
1.27.2: digest: sha256:719b34dba7bd01c795f94b3a6f3a5f1fe7d53bf09e79e355168a17d2e2949cef size: 1778

在网站上查看

公开项目:拉取镜像不用登录,上传需要登录,例如library项目,只有上传镜像才需要登录

私有项目:拉取和上传镜像都需要登录

#在另外的主机上拉取镜像
[root@node01 ~]# docker pull reg.jungle.com/library/testnginx:1.27.2
1.27.2: Pulling from library/testnginx
302e3ee49805: Pull complete
d07412f52e9d: Pull complete
9ab66c386e9c: Pull complete
4b563e5e980a: Pull complete
55af3c8febf2: Pull complete
5b8e768fb22d: Pull complete
85177e2c6f39: Pull complete
Digest: sha256:719b34dba7bd01c795f94b3a6f3a5f1fe7d53bf09e79e355168a17d2e2949cef
Status: Downloaded newer image for reg.jungle.com/library/testnginx:1.27.2
reg.jungle.com/library/testnginx:1.27.2

在网站上新建一个私有项目

#前面登录过该仓库,会有记录
[root@localhost harbor]# cat /root/.docker/config.json
{
        "auths": {
                "reg.jungle.com": {
                        "auth": "YWRtaW46MTIzNDU2"
                }
        }
}
#登录后上传一个镜像
[root@localhost harbor]# docker tag busybox:latest reg.jungle.com/private/testbusybox:latest
[root@localhost harbor]# docker push reg.jungle.com/private/testbusybox:latest
The push refers to repository [reg.jungle.com/private/testbusybox]
49b3a50a2039: Pushed
latest: digest: sha256:401719cc3ec67aedaedfed7fb304e97fb605bdcfae29972eaeb59a98708fe066 size: 527
#未登录时使用node01拉取该镜像,提示拉取失败
[root@node01 dockerimages]# docker pull reg.jungle.com/private/testbusybox:latest
Error response from daemon: unauthorized: unauthorized to access repository: private/testbusybox, action: pull: unauthorized to access repository: private/testbusybox, action: pull
#登录后拉取成功
[root@node01 dockerimages]# docker login reg.jungle.com
Username: admin
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/#credential-stores

Login Succeeded
[root@node01 dockerimages]# docker pull reg.jungle.com/private/testbusybox:latest
latest: Pulling from private/testbusybox
Digest: sha256:401719cc3ec67aedaedfed7fb304e97fb605bdcfae29972eaeb59a98708fe066
Status: Downloaded newer image for reg.jungle.com/private/testbusybox:latest
reg.jungle.com/private/testbusybox:latest

在仓库上设置扫描器,需要手动开启扫描器功能

#如果没有安装仓库,可以使用./install.sh  --with-trivy开启扫描器功能,如果已经安装了,可以使用./prepare --with-trivy开启扫描器
[root@localhost harbor]# ./prepare --with-trivy
#重新启动仓库
[root@localhost harbor]# docker compose  stop
[root@localhost harbor]# docker compose  start

如图所示为harbor.v1.10.19版本的扫描器示例

如下图所示为harbor.v2.11.1的扫描器

(2)配置https网站的harbor仓库

[root@localhost harbor]# mkdir /data/ssl -p
#制作ca的公私钥
[root@localhost data]# cd ssl/
[root@localhost ssl]# openssl  genrsa -out ca.key 4096
[root@localhost ssl]# openssl  req -x509 -new -sha512 -days 3650 -subj "/C=CN/ST=shaanxi/L=xi'an/O=jungle/OU=jungleCA/CN=jungle.com"  -key ca.key  -out ca.crt
[root@localhost ssl]# ll
总用量 8
-rw-r--r--. 1 root root 2033 11月 11 14:30 ca.crt
-rw-------. 1 root root 3272 11月 11 14:30 ca.key
#制作ca签名的公私钥
#创建私钥
[root@localhost ssl]# openssl genrsa -out jungle.key 4096
#创建证书签名请求文件
[root@localhost ssl]# openssl  req -sha512 -new -subj "/C=CN/ST=shaanxi/L=xi'an/O=jungle/OU=jungle/CN=reg.jungle.com" -key jungle.key -out jungle.csr
#使用ca签名
[root@localhost ssl]# cat v3.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1=reg.jungle.com
[root@localhost ssl]# openssl x509 -req -sha512 -in jungle.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -extfile v3.ext -out jungle.crt
Certificate request self-signature ok
subject=C = CN, ST = shaanxi, L = xi'an, O = jungle, OU = jungle, CN = reg.jungle.com
[root@localhost ssl]# cd /usr/local/harbor/
[root@localhost harbor]# ll
总用量 646848
-rw-r--r--. 1 root root      3646  8月 15 18:07 common.sh
-rw-r--r--. 1 root root 662330539  8月 15 18:07 harbor.v2.11.1.tar.gz
-rw-r--r--. 1 root root     14270  8月 15 18:07 harbor.yml.tmpl
-rwxr-xr-x. 1 root root      1975  8月 15 18:07 install.sh
-rw-r--r--. 1 root root     11347  8月 15 18:07 LICENSE
-rwxr-xr-x. 1 root root      1882  8月 15 18:07 prepare
[root@localhost harbor]# cp harbor.yml.tmpl harbor.yml
#修改配置文件中如下信息
[root@localhost harbor]# vim harbor.yml
hostname: reg.jungle.com
https:
  port: 443
  certificate: /data/ssl/jungle.crt
  private_key: /data/ssl/jungle.key
harbor_admin_password: 123456
#安装仓库
[root@localhost harbor]# ./install.sh

#############也可以使用自签名公私钥###### [root@localhost ssl]# openssl req -newkey rsa:4096 -nodes -sha256 -keyout jungle.key -x509 -days 365 -subj "/C=CN/ST=shaanxi/L=xi'an/O=jungle/OU=jungleCA/CN=reg.jungle.com" --addext "subjectAltName = DNS:reg.jungle.com" -out jungle.crt ##############################################################

配置域名解析,并访问

新建testpublic公开项目,并上传

#重新给镜像打标签
[root@localhost harbor]# docker tag nginx:1.27.2 reg.jungle.com/testpublic/testnginx:1.27.2
#需要先登录
[root@localhost harbor]# docker login reg.jungle.com
Username: admin
Password:
Error response from daemon: Get "https://reg.jungle.com/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority
#需要先配置ca的证书到docker端,才能登陆成功
[root@localhost harbor]# mkdir /etc/docker/certs.d/reg.jungle.com -p
[root@localhost harbor]# cp /data/ssl/ca.crt /etc/docker/certs.d/reg.jungle.com/

#重新登录
[root@localhost harbor]# docker login reg.jungle.com
Username: admin
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/#credential-stores

Login Succeeded

#上传镜像
[root@localhost harbor]# docker push  reg.jungle.com/testpublic/testnginx:1.27.2

7.5 配置harbor高可用

(1)基于共享存储的高可用方案

此方案是多个harbor实例共享存储,通过负载均衡器实现多台服务器提供harbor服务。

(2)基于复制策略的高可用方案

此方案是使用harbor原生的远程复制功能实现镜像的一致性,通过负载均衡器实现多台服务器提供单一的harbor服务。

接下来演示基于harbor原生的远程复制功能实现镜像的一致性

#配置第一台harbor,修改之前配置的harbor重启
hostname: 192.168.168.31

# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 80
# https related config
#https:
#  port: 443
#  certificate: /data/ssl/jungle.crt
#  private_key: /data/ssl/jungle.key
harbor_admin_password: 123456
[root@localhost harbor]# ./install.sh
#配置另外一台harbor
[root@localhost yum.repos.d]# yum install docker-ce -y
[root@localhost harbor]# systemctl start docker
#将harbor安装包从之前配置好的harbor拷贝至第二台harbor
[root@localhost opt]# scp /opt/harbor-offline-installer-v2.11.1.tgz root@192.168.168.101:/opt
#解压文件
[root@localhost opt]# tar xvf /opt/harbor-offline-installer-v2.11.1.tgz  -C /usr/local/
#将第一台barbor的yml文件复制到第二台harbor
[root@localhost harbor]# scp /usr/local/harbor/harbor.yml root@192.168.168.101:/usr/local/harbor/
#注释该配置文件中的https配置
[root@localhost harbor]# vim harbor.yml
#https:
#  port: 443
#  certificate: /data/ssl/jungle.crt
#  private_key: /data/ssl/jungle.key
#安装harbor
[root@localhost harbor]# ./install.sh

在192.168.168.31上新建testcontainers公开项目

在192.168.168.101上新建testcontianers项目

在第一台harbor上创建复制规则

# 测试:当向192.168.168.101上传镜像后,镜像会自动同步至192.168.168.31仓库中
#配置docker仓库的免密
[root@master0101 yum.repos.d]# grep insecure /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --insecure-registry  192.168.168.0/24
[root@master0101 yum.repos.d]# systemctl start docker
#打标签
[root@master0101 yum.repos.d]# docker  tag nginx:1.27.2 192.168.168.101/testcontainers/testnginx:1.27.2
[root@master0101 yum.repos.d]# docker login 192.168.168.101
Username: admin
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/#credential-stores

Login Succeeded
[root@master0101 yum.repos.d]# docker push 192.168.168.101/testcontainers/testnginx:1.27.2
The push refers to repository [192.168.168.101/testcontainers/testnginx]
825fb68b6033: Pushed
7619c0ba3c92: Pushed
1c1f11fd65d6: Pushed
6b133b4de5e6: Pushed
3d07a4a7eb2a: Pushed
756474215d29: Pushed
8d853c8add5d: Pushed
1.27.2: digest: sha256:719b34dba7bd01c795f94b3a6f3a5f1fe7d53bf09e79e355168a17d2e2949cef size: 1778

可以看到101上的复制管理下已经成功将镜像同步至31

可以看到31上也有该镜

在第二台上配置复制规则

#测试
[root@master0101 opt]# docker tag busybox:latest 192.168.168.31/testcontainers/testbusybox:latest
[root@master0101 opt]# docker login 192.168.168.31
Username: admin
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/#credential-stores

Login Succeeded
[root@master0101 opt]# docker push  192.168.168.31/testcontainers/testbusybox
Using default tag: latest
The push refers to repository [192.168.168.31/testcontainers/testbusybox]
49b3a50a2039: Pushed
latest: digest: sha256:401719cc3ec67aedaedfed7fb304e97fb605bdcfae29972eaeb59a98708fe066 size: 527

31上查看镜像复制同步成功

在101上也能看到镜像

如果在其中一台删除时,另外一台也同步删除的话,需要增加如下配置:

删除其中一个将镜像,可以看到对应日志显示

查看101上的镜像信息,也同步将镜像删除了

配置负载均衡

[root@master0101 ~]# yum install haproxy -y
[root@master0101 ~]# cat /etc/haproxy/conf.d/harbor.cfg
listen harbor_port_80
    bind 192.168.168.34:80
    mode tcp
    balance source
    server 192.168.168.31 192.168.168.31:80 check inter 30s fall 3 rise 5
    server 192.168.168.100 192.168.168.101:80 check inter 30s fall 3 rise 5
[root@master0101 ~]# systemctl restart haproxy
#测试
[root@master0101 ~]# docker login 192.168.168.34
Username: admin
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/#credential-stores

Login Succeeded
[root@master0101 opt]# docker tag alpine:latest 192.168.168.34/testcontainers/alpine:latest
[root@master0101 opt]# docker push  192.168.168.34/testcontainers/alpine:latest
The push refers to repository [192.168.168.34/testcontainers/alpine]
03901b4a2ea8: Pushed
latest: digest: sha256:acd3ca9941a85e8ed16515bfc5328e4e2f8c128caa72959a58a127b7801ee01f size: 528

刷新查看31和101上均有该镜像

#测试能否拉取镜像
[root@master0101 opt]# docker pull 192.168.168.34/testcontainers/testnginx:1.27.2
1.27.2: Pulling from testcontainers/testnginx
Digest: sha256:719b34dba7bd01c795f94b3a6f3a5f1fe7d53bf09e79e355168a17d2e2949cef
Status: Downloaded newer image for 192.168.168.34/testcontainers/testnginx:1.27.2
192.168.168.34/testcontainers/testnginx:1.27.2
#通过域名上传或者下载镜像
[root@master0101 opt]# tail -1 /etc/hosts
192.168.168.34 reg.jungle.com
[root@master0101 opt]# docker pull reg.jungle.com/testcontainers/testbusybox:latest
[root@master0101 opt]# docker image ls reg.jungle.com/testcontainers/testbusybox
REPOSITORY                                  TAG       IMAGE ID       CREATED         SIZE
reg.jungle.com/testcontainers/testbusybox   latest    6fd955f66c23   18 months ago   4.26MB
[root@master0101 opt]# docker tag alpine:latest reg.jungle.com/testcontainers/testalpine:latest
[root@master0101 opt]# docker push reg.jungle.com/testcontainers/testalpine:latest
The push refers to repository [reg.jungle.com/testcontainers/testalpine]
03901b4a2ea8: Pushed
latest: digest: sha256:acd3ca9941a85e8ed16515bfc5328e4e2f8c128caa72959a58a127b7801ee01f size: 528

八、docker资源限制

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

官网:Resource constraints | Docker Docs

8.1 容器的内存限制

Docker可以强制执行硬性内存限制,即只允许容器使用给定的内存大小。 Docker 也可以执行非硬性内存限制,即容器可以使用尽可能多的内存,除非内核检测到主机上的内存不够用了。

如果容器没有做内存使用限制,则该容器可以利用到系统内存最大空间,默认创建的容器没有做内存资源限制。

docker run参数说明:

-m或者--memory #表示容器可以使用的最大内存量,硬限制

--memory-reservation #指定小于--memory的软限制,当docker检测到主机上的内存不足时会激活该限制,该值不能超过。

#运行容器,未指定内存限制
[root@localhost stress-ng]# docker run  --rm -it --name busybox  busybox:latest /bin/sh/ #

#在另外一个终端查看当前容器的内存限制,可以看到未做任何限制和物理机可使用大小一致
[root@localhost ~]# docker stats  busybox
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O       BLOCK I/O   PIDS
cab5f5417a55   busybox   0.00%     892KiB / 3.532GiB   0.02%     2.42kB / 0B   0B / 0B     1

#使用-m选项设定硬限制
[root@localhost stress-ng]# docker run -m 256m  --rm -it --name busybox  busybox:latest /bin/sh
/ #
#另一个终端查看当前容器的内存限制,可以看到限制为256MB
[root@localhost ~]# docker stats  busybox
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O       BLOCK I/O   PIDS
ed4193744afb   busybox   0.00%     892KiB / 256MiB     0.34%     2.42kB / 0B   0B / 0B     1
#容器的硬限制不能比软限制小
[root@localhost stress-ng]# docker run -m 128m --memory-reservation 256m  --rm -it --name busybox  busybox:latest /bin/sh
docker: Error response from daemon: Minimum memory limit can not be less than memory reservation limit, see usage.
See 'docker run --help'.
[root@localhost stress-ng]# docker run -m 256m --memory-reservation 256m  --rm -it --name busybox  busybox:latest /bin/sh

#查看容器内存限制
[root@localhost ~]# docker stats  busybox
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O       BLOCK I/O   PIDS
07a467461a60   busybox   0.00%     884KiB / 256MiB     0.34%     2.49kB / 0B   0B / 0B     1
CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O       BLOCK I/O   PIDS
07a467461a60   busybox   0.00%     884KiB / 256MiB     0.34%     2.49kB / 0B   0B / 0B     1
# --memory限制容器可用的物理内存为200MB;--memory-swap限制内存 + Swap交换分区的总使用量为 200MB,这里等于禁用swap;/dev/shm使用内存作为存储介质(Docker 会默认分配64MB的共享内存空间,可以使用 --shm-size自定义/dev/shm的大小)
[root@docker1 ~]# docker run -it --memory=200M --memory-swap=200M busybox:latest
/ #  dd if=/dev/zero of=/dev/shm/test bs=1M count=200
dd: error writing '/dev/shm/test': No space left on device
/ #  dd if=/dev/zero of=/dev/shm/test bs=1M count=18

#指定--shm-size=200M即/dev/shm使用所有的200M内存空间,创建文件时,无法创建该大小
[root@docker1 ~]# docker run -it --memory=200M --memory-swap=200M --shm-size=200M busybox:latest
/ # dd if=/dev/zero of=/dev/shm/test bs=1M count=200
Killed

8.2 容器的CPU限制

一个宿主机,有几十个核心的CPU,但是宿主机上可以同时运行成百上千个不同的进程用以处理不同的任务,多进程共用一个CPU的核心依赖技术就是为可压缩资源,即一个核心的CPU可以通过调度而运行多个进程,但是同一个单位时间内只能有一个进程在CPU上运行,那么这么多的进程怎么在CPU上执行和调度的呢? 实时进程:动态优先级为0-99的进程,采用实时调度算法调度。 普通进程:动态优先级为100-139的进程采用完全公平调度算法调度(CFS,Completely Fair Scheduler)。 nice值:-20-19,用于调整普通进程优先级的参数,对应100-139的进程优先级。 默认情况下,每个容器对主机CPU周期的访问权限是不受限制的,但是我们可以设置各种约束来限制给定容器访问主机的CPU周期,大多数用户使用的是默认的CFS调度方式,在Docker1.13及更高版本中,还可以配置实时优先级。

docker run参数说明:

--cpus #指定容器可以使用多少可用CPU资源,最大不能超过宿主机的核心数。如果主机有两个CPU,并且设置了--cpus=1.5,那么该容器将保证最多可以访问1.5个CPU(如果是4核CPU,那么可以每个核心上用一点,总计还是1.5核心)

使用 --cpu-period 和 --cpu-quota 参数 #这两个参数用于更精细的 CPU 资源控制。--cpu-period 设置评估周期(单位为微秒),范围在1000(1毫秒)到1000000(1秒)之间;--cpu-quota 设置在这个评估周期内的 CPU 配额(单位也为微秒)。cpu-quota/cpu-period 的结果即为实际分配给容器的 CPU 量,如果是小数表示分配的 CPU 量不足一个 vCPU,如果大于1则表示分配的 CPU 量超过一个 vCPU。

--cpuset-cpus 参数:通过该参数可以指定容器能够运行在哪些 CPU 核心上。参数值可以是一个逗号分隔的 CPU 编号列表,或者是一个范围(如"0-3"表示第0、1、2和3核心)。

设置 CPU 权重(--cpu-shares 参数):该参数用于设置容器使用 CPU 的相对权重,默认值为1024。当多个容器竞争 CPU 资源时,权重较高的容器会获得更多的 CPU 时间。但只有在 CPU 资源紧张的情况下,这种按权重分配 CPU 的方式才会生效。

#创建有压测工具的容器
#压测工具源码下载地址https://fossies.org/linux/privat/stress-ng-0.18.05.tar.bz2
[root@localhost stress-ng]# ll
总用量 6272
-rw-r--r-- 1 root root     318 11月 13 15:22 dockerfile
-rw-r--r-- 1 root root 6415003 11月 13 15:11 stress-ng-0.18.05.tar.bz2
[root@localhost stress-ng]# cat dockerfile
FROM alpine:latest
ENV RELEASE_VERSION=0.18.05
ADD stress-ng-0.18.05.tar.bz2 /tmp
RUN \
  apk add --update bash g++ make  && \
  cd /tmp/stress-ng-${RELEASE_VERSION} && \
  make && make install && \
  apk del g++ make  && \
  rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*

CMD ["stress-ng", "-c 1"]
[root@localhost stress-ng]#  docker build -t stress-ng:v1 .
#默认情况下容器的cpu限额是宿主机的核数
[root@localhost stress-ng]# docker run -it --rm --name stress-ng stress-ng:v1  /bin/sh
/ # stress-ng --cpu 4
stress-ng: info:  [36] defaulting to a 1 day run per stressor
stress-ng: info:  [36] dispatching hogs: 4 cpu
#在两个终端查看容器进程信息
[root@localhost ~]# docker top stress-ng
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                31870               31846               0                   15:24               pts/0               00:00:00            /bin/sh
root                37242               31870               0                   15:36               pts/0               00:00:00            stress-ng --cpu 4
root                37243               37242               99                  15:36               pts/0               00:00:49            stress-ng --cpu 4
root                37244               37242               99                  15:36               pts/0               00:00:49            stress-ng --cpu 4
root                37245               37242               99                  15:36               pts/0               00:00:49            stress-ng --cpu 4
root                37246               37242               99                  15:36               pts/0               00:00:49            stress-ng --cpu 4
#查看宿主机cpu占用信息
#执行top后按1查看4个cpu的使用率
[root@localhost ~]# top
top - 15:38:07 up  1:20,  3 users,  load average: 4.56, 4.67, 3.23
Tasks: 304 total,   5 running, 299 sleeping,   0 stopped,   0 zombie
%Cpu0  : 94.4 us,  4.2 sy,  0.0 ni,  0.0 id,  0.0 wa,  1.4 hi,  0.0 si,  0.0 st
%Cpu1  : 96.9 us,  1.5 sy,  0.0 ni,  0.0 id,  0.0 wa,  1.5 hi,  0.0 si,  0.0 st
%Cpu2  : 87.5 us, 12.5 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 98.5 us,  1.5 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3616.4 total,   1751.5 free,   1167.2 used,   1017.9 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   2449.2 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  37243 root      20   0   37032   4328   1832 R  99.7   0.1   1:37.94 stress-ng-cpu
  37246 root      20   0   37032   4328   1832 R  99.7   0.1   1:37.74 stress-ng-cpu
  37244 root      20   0   37032   4332   1832 R  99.3   0.1   1:37.87 stress-ng-cpu
  37245 root      20   0   37032   4332   1832 R  99.3   0.1   1:37.81 stress-ng-cpu

#限制容器使用cpu数量
[root@localhost stress-ng]# docker run -it --cpus 2 --rm --name stress-ng stress-ng:v1  /bin/sh
/ # stress-ng --cpu 2
stress-ng: info:  [8] defaulting to a 1 day run per stressor
stress-ng: info:  [8] dispatching hogs: 2 cpu
#查看宿主机cpu使用情况
[root@localhost ~]# top
top - 15:40:53 up  1:22,  3 users,  load average: 0.74, 3.10, 2.88
Tasks: 311 total,   3 running, 308 sleeping,   0 stopped,   0 zombie
%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  : 98.5 us,  0.0 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.5 hi,  0.0 si,  0.0 st
%Cpu2  : 88.4 us,  7.2 sy,  0.0 ni,  4.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  0.7 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3616.4 total,   1717.5 free,   1197.3 used,   1022.7 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   2419.1 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  39332 root      20   0   37024   4928   1892 R  99.0   0.1   0:05.44 stress-ng-cpu
  39331 root      20   0   37024   4928   1892 R  98.7   0.1   0:05.42 stress-ng-cpu
[root@localhost stress-ng]# docker run -it --cpu-period 1000 --cpu-quota 2000000 --rm --name stress-ng stress-ng:v1  /bin/sh
/ # stress-ng --cpu 2
stress-ng: info:  [7] defaulting to a 1 day run per stressor
stress-ng: info:  [7] dispatching hogs: 2 cpu


#查看宿主机的cpu使用情况
[root@localhost ~]# top
top - 15:51:19 up  1:33,  3 users,  load average: 1.05, 1.39, 2.10
Tasks: 310 total,   3 running, 307 sleeping,   0 stopped,   0 zombie
%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  :  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.7 sy,  0.0 ni, 99.3 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
MiB Mem :   3616.4 total,   1745.5 free,   1171.7 used,   1020.3 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   2444.7 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  43648 root      20   0   37024   5376   2100 R  99.3   0.1   0:18.17 stress-ng-cpu
  43649 root      20   0   37024   5376   2100 R  99.3   0.1   0:18.17 stress-ng-cpu

#设定使用0号cpu
[root@localhost stress-ng]# docker run -it --cpuset-cpus 0 --rm --name stress-ng stress-ng:v1  /bin/sh
/ # stress-ng --cpu 2  ##即使 stress-ng启动了2个线程,它们仍然会被限制在1个CPU的总计算能力内,2 个stress-ng线程会共享1个CPU的资源,每个线程大约只能获得50%的CPU时间片
stress-ng: info:  [7] defaulting to a 1 day run per stressor
stress-ng: info:  [7] dispatching hogs: 2 cpu

#查看宿主机cpu使用情况
top - 15:54:17 up  1:36,  3 users,  load average: 1.02, 1.33, 1.95
Tasks: 311 total,   3 running, 308 sleeping,   0 stopped,   0 zombie
%Cpu0  : 98.8 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  1.2 hi,  0.0 si,  0.0 st
%Cpu1  :  0.0 us,  0.0 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.3 si,  0.0 st
%Cpu3  :  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3616.4 total,   1757.8 free,   1159.8 used,   1019.9 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   2456.6 avail Mem

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
  45160 root      20   0   37024   4840   1808 R  50.0   0.1   0:05.38 stress-ng-cpu
  45161 root      20   0   37024   4840   1808 R  49.7   0.1   0:05.37 stress-ng-cpu

8.3 限制docker磁盘IO

#指定容器使用磁盘io的速率为30M
[root@docker1 ~]# docker run -it --rm  --device-write-bps /dev/nvme0n1:30M busybox
/ # dd if=/dev/zero of=/file bs=1M count=200    #开启容器后会发现速度和设定不匹配,是因为系统的缓存机制
200+0 records in
200+0 records out
209715200 bytes (200.0MB) copied, 0.044561 seconds, 4.4GB/s
/ # dd if=/dev/zero of=/file bs=1M count=200 oflag=direct  #设定dd命令直接写入磁盘
200+0 records in
200+0 records out
209715200 bytes (200.0MB) copied, 6.652777 seconds, 30.1MB/s

8.4 解决docker的默认资源隔离

LXCFS 是一个为 LXC(Linux Containers)容器提供增强文件系统功能的工具。

主要功能

  1. 资源可见性

    • LXCFS 可以使容器内的进程看到准确的 CPU、内存和磁盘 I/O 等资源使用信息。在没有 LXCFS 时,容器内看到的资源信息可能不准确,这会影响到在容器内运行的应用程序对资源的评估和管理。

  2. 性能监控

    • 方便对容器内的资源使用情况进行监控和性能分析。通过提供准确的资源信息,管理员和开发人员可以更好地了解容器化应用的性能瓶颈,并进行相应的优化。

[root@docker1 ~]# cat /etc/yum.repos.d/epel.repo
[epel]
name=epel
baseurl=https://mirrors.aliyun.com/epel/9/Everything/x86_64/
gpgcheck=0
[root@docker1 ~]# systemctl start lxcfs
[root@docker1 ~]# ps -ef | grep lxc
root        5454       1  0 12:55 ?        00:00:00 /usr/bin/lxcfs /var/lib/lxcfs

运行lxcfs并解决容器隔离性

#使用别的镜像可能无法看出效果
[root@docker1 docker]# docker run  -it -m 256m \
-v /var/lib/lxcfs/proc/cpuinfo:/proc/cpuinfo:rw \
-v /var/lib/lxcfs/proc/diskstats:/proc/diskstats:rw \
-v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \
-v /var/lib/lxcfs/proc/stat:/proc/stat:rw \
-v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \
-v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw \
ubuntu
root@cdf22cfd7002:/# free -m
               total        used        free      shared  buff/cache   available
Mem:             256           1         254           0           0         254
Swap:              0           0           0

8.5 提升容器权限

在容器中默认情况下即使我是容器的超级用户也无法修改某些系统设定,比如网络

[root@docker1 ~]# docker run -it --rm  busybox:latest
/ # 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@if52: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 6e:6b:09:38:31:ba brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
/ # ip a a 192.168.168.168/24 dev eth0
ip: RTNETLINK answers: Operation not permitted

这是因为容器使用的很多资源都是和系统真实主机公用的,如果允许容器修改这些重要资源,系统的稳定性会变的非常差

但是由于某些需要求,容器需要控制一些默认控制不了的资源,如何解决此问题,这时我们就要设置容器特权

[root@docker1 ~]# docker run -it --rm --privileged busybox:latest
/ # id root
uid=0(root) gid=0(root) groups=0(root),10(wheel)
/ # ip a a 192.168.168.168/24 dev eth0
/ # 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@if54: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 56:ba:27:fa:7e:0e brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.168.168/24 scope global eth0
       valid_lft forever preferred_lft forever

/ # fdisk -l
Disk /dev/nvme0n1: 100 GB, 107374182400 bytes, 209715200 sectors
411206 cylinders, 255 heads, 2 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/nvme0n1p1 *  4,4,1       1023,254,2        2048    2099199    2097152 1024M 83 Linux
/dev/nvme0n1p2    1023,254,2  1023,254,2     2099200  209715199  207616000 98.9G 8e Linux LVM
Disk /dev/dm-0: 99 GB, 106296246272 bytes, 207609856 sectors
12923 cylinders, 255 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes

#如果添加了--privileged 参数开启容器,容器获得权限近乎于宿主机的root用户

--privileged=true 的权限非常大,接近于宿主机的权限,为了防止用户的滥用,需要增加限制,只提供给容器必须的权限。此时Docker 提供了权限白名单的机制,使用--cap-add添加必要的权限

capabilities手册地址:capabilities(7) - Linux manual page

[root@docker1 ~]# docker run -it --rm --cap-add NET_ADMIN busybox:latest
#可以设定网络
/ # ip a a 192.168.168.168/24 dev eth0
/ # 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@if60: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
    link/ether 02:8d:5e:43:7f:f9 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.168.168/24 scope global eth0
       valid_lft forever preferred_lft forever
#无法查看磁盘
/ # fdisk -l

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐