container


目录:

Docker内容介绍

源代码下载:

https://github.com/docker/docker

下载docker 二进制文件:

https://download.docker.com/linux/static/stable/x86_64/

官方网站:

https://www.docker.com/

镜像地址:

https://hub.docker.com/search?offering=community&type=edition&operating_system=linux

国内镜像源:

mirrors.aliyun.com

参考网址:安装参考地址

https://mirrors.tuna.tsinghua.edu.cn/help/docker-ce/

https://www.docker-cn.com/registry-mirror

三方网站:

quay.io

dev.aliyun

资源链接

官方网站

Docker 官方主页:https://www.docker.com

Docker 官方博客:https://blog.docker.com/

Docker 官方文档:https://docs.docker.com/

Docker Hub:https://hub.docker.com

Docker 的源代码仓库:https://github.com/moby/moby

Docker 发布版本历史:https://docs.docker.com/release-notes/

Docker 常见问题:https://docs.docker.com/engine/faq/

Docker 远端应用 API:https://docs.docker.com/develop/sdk/

实践参考

Dockerfile 参考:https://docs.docker.com/engine/reference/builder/

Dockerfile 最佳实践:https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/

技术交流

Docker 邮件列表: https://groups.google.com/forum/#!forum/docker-user

Docker 的 IRC 频道:https://chat.freenode.net#docker

Docker 的 Twitter 主页:https://twitter.com/docker

其它

Docker 的 StackOverflow 问答主页:https://stackoverflow.com/search?q=docker

实验环境: CENTOS7.4-63 64位

CI:持续集成

CD:持续部署 -> DevOPS

云架构:

  • IaaS:基础设置即服务(Infrastucture-as-aService);提供给消费者的服务是对所有计算基础设施的利用,包括处理CPU、内存、存储、网络和其它基本的计算资源,用户能够部署和运行任意软件,包括操作系统和应用程序。

  • PASS:平台即服务(Platform-as-a-Service);提供给消费者的服务是把客户采用提供的开发语言和工具(例如与Java,python、Net等)开发或收购的应用程序部署到供应商的云计算基础设施上。 客户不需要管理控制底层的云基础设施,包括网络、服务器、操作系统存储等,但客户能控制部署的应用程序,也能控制运行应用程序的托管环境配置。

  • SaaS:软件即服务(Software-as-a-Service);提供给客户的服务是运营商运行在云计算基础设施上的应用程序,用户可以在各种设备上通过客户端界面访问。消费者不需要管理或控制任何云计算基础设置,包括网络、服务器、操作系统、存储等。

云计算平台介绍

参考:https://zhinan.sogou.com/guide/number/316513843918.htm?rcer=Q9PEmk2kVIvu-wIIl

https://wenwen.sogou.com/z/q825705149.htm?rcer=Q9PEmk2kVIvu-wIIl

image-20221101162846754

云计算是一种资源的服务模式,该模式可以实现随时随地、便捷按需地从可配置计算资源共享池中获取所需的资源(如网络、服务器、存储、应用及服务),资源能够快速供应并释,大大减少了资源管理工作开销。

经典云计算架构包括IaaS(Infrastructure as a Service,基础设施即服务)、PaaS(Platform-as-a-Service:平台即服务)、SaaS(Software as a Service,软件即服务)三层服务。下面是对这三种服务的介绍:

IaaS层为基础设施运维人员服务,提供计算、存储、网络及其他基础资源,云平台使用者可以在上面部署和运行包括操作系统和应用程序在内的任意软件,无需再为基础设施的管理而分心。

PaaS层为应用开发人员服务,提供支撑应用运行所需的软件运行时环境、相关工具与服务,如数据库服务、日志服务、监控服务等,让应用开发者可以专注于核心业务的开发。

SasS层为一般用户服务,提供了一套完整可用的软件系统,让一般用户无需关注技术细节,只需通过浏览器、应用客户端等方式就能使用部署在云上的应用服务。

IaaS的发展主要以虚拟机为最小粒度的资源调度单位,出现了资源利用率低、调度分发缓慢、软件堆栈环境不统一等一系列问题。PaaS在IaaS基础上发展而来,众多PaaS已经意识到可以利用容器技术解决资源利用率问题,但是PaaS通常在应用架构选择、支持的软件环境服务方面有较大的限制,这带来了应用与平台无法解耦、应用运行时环境局限性强、运维人员控制力下降的问题

IaaS的发展主要以虚拟机为最小粒度的资源调度单位,出现了资源利用率低、调度分发缓慢、软件堆栈环境不统一等一系列问题。PaaS在IaaS基础上发展而来,众多PaaS已经意识到可以利用容器技术解决资源利用率问题,但是PaaS通常在应用架构选择、支持的软件环境服务方面有较大的限制,这带来了应用与平台无法解耦、应用运行时环境局限性强、运维人员控制力下降的问题

容器化思维

如今已迈入互联网+的时代,各行各业更加广泛地与互联网技术结合,软件称为连接人与人、人与企业、企业与企业之间的桥梁,互联网+时代对应用开发迭代速度和质量有了更高的要求,开发者也渐渐由单纯的开发进入到开发、维护、发布的全过程之中(DevOps)。应用开发模式正在朝着微服务模式发展。所谓微服务模式有如下三大特性:

彼此独立。 微服务模式下的每一个组成部分,都是一个独立的服务。有一整套完整的运行机制以及标准化的对外接口。不依赖于其他部分就能正常运行,同时可以探测其他组成部分的存在。

原子化。 微服务应该是不可再分的原子化服务。如果一个服务还能继续划分为几个更小的服务,那便不能称为微服务,而更像是由多个微服务组成的"微系统"。

组合和重构。 微服务的最大特点就在于它能快速的组合和重构,彼此组合成一个系统。系统里所有的实体在逻辑上是等价的,因此它的结构相对简单和松散、具有极强的可扩展性和鲁棒性(健壮和强壮)。

容器化思维就等同于微服务化思维,每个容器承载一个微服务,容器之间彼此独立正常运行,每个容器中只允许一个进程,容器间可以快速组合重构,形成一个更大的系统。所以使用Docker时需要关注容器本身,时刻提醒自己是在使用容器,享受它带来的种种便利,如快速的应用分发能力、高效的操作及反应能力、弹性灵活的部署能力以及低廉的部署成本;同时我们也要转变思维模式,学习和适应容器化的管理方式。容器的本质是一个系统进程加上一套运行时库的文件系统封装、而针对容器,我们需要监控、资源控制、配置管理、安全等

什么是Docker?

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙盒机制,相互之间不会有任何接口(类似 iPhone 的 app)。几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,他们不依赖于任何语言、框架或包装系统。

Docker是以Docker容器来资源分割和调度的基本单位,封装整个软件运行时的环境,为开发者和系统管理员设计,用于构建、发布和运行分布式应用的平台。它是一个跨平台、可移植并且简单易用的容器解决方案。Docker的源代码托管在GitHub上,基于Go语言开发并遵从Apache 2.0协议.Docker可在容器内容快速自动化地部署应用,并通过操作系统内核技术(namespaces、cgroups等)为容器提供资源隔离与安全保障。Docker是Docker.inc公司开源的一个基于LXC技术之上构建的Container容器引擎

什么是LXC? 链接地址:https://www.cnblogs.com/cherishui/p/4147240.html #这篇博客介绍的很详细

Docker 最初 dotCloud 公司内部的一个业余项目

Docker 基于 Go 语言

Docker 项目的目标是实现轻量级的操作系统虚拟化解决方案

Docker 的基础是 Linux 容器(LXC)等技术

Docker 容器的启动可以在秒级实现,这相比传统的虚拟机方式要快得多

Docker 对系统资源的利用率很高,一台主机上可以同时运行数千个 Docker 容器

扩展:沙盒

沙盒也叫沙箱,英文sandbox。在计算机领域指一种虚拟技术,且多用于计算机安全技术。安全软件可以先让它在沙盒中运行,如果含有恶意行为,则禁止程序的进一步运行,而这不会对系统造成任何危害。

Docker是dotCloud公司开源的一个基于LXC的高级容器引擎,源代码托管在Github上, 基于go语言并遵从Apache2.0协议开源。

Docker让开发者可以打包他们的应用以及依赖包到一个可移植的container中,然后发布到任何流行的Linux机器上。

ps:

现在接触的软件是怎么发布的? windows下的 2016-0ffice.exe ,不能在xp运行 ;mk.rpm 在redhat系列Linux上运行,但是不能在其他linux版本上运行。

例: /mnt/Packages/vsftpd-3.0.2-10.el7.x86_64.rpm #这个包是rhel7,就不能在rhel6运行。

现在软件包必须和系统相关。 docker镜像一次编译,到处运行。

android : linux -> JVM -> java程序 app

linux -> docker ->服务做成镜像,就可以直接运行起来

windows -> docker ->服务做成镜像,就可以直接运行起来

IOS 苹果-> docker ->服务做成镜像,就可以直接运行起来

扩展:

LXC为Linux Container的简写。Linux Container容器是一种内核虚拟化技术,可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。

LXC主要通过来自kernel的namespace实现每个用户实例之间的相互隔离,通过cgroup实现对资源的配额和度量。

image-20221101163512925

docker [ˈdɒkə(r)] 码头工人

logo:

image-20221101163646549

注:docker服务相当于鲸鱼,container容器就是集装箱。

container :集装箱,容器

docker: 码头工人

扩展:

集装箱是海上运货的一个创新。

image-20221101163720929

image-20221101163735135

Docker生态系统

image-20221101163750645

上图为Docker生态系统

围绕Docker的生态系统,自下而上分别覆盖了IaaS层和PaaS层所涉及的各类问题,包括资源调度、编排、部署、配置管理、网络管理、应用开发和部署平台、应用开发工具、应用服务供应以及大数据分析等云计算相关的服务。Docker及其生态系统主要带来了以下几点好处:

持续部署与测试。 Docker消除了线上线下的环境差异,保证了应用生命周期的环境一致性和标准化。开发人员使用镜像实现标准开发环境的构建,开发完成后通过封装着完整环境和应用的镜像进行迁移,大大简化了持续集成、测试和发布的过程。

跨云平台支持。 Docker的最大好处之一就是其适配性、越来越多的云平台都支持Docker。目前支持Docker的IaaS云平台包括但不限于亚马逊云平台(AWS)、Google云平台(GCP)、微软云平台(Azure)、OpenStack等,还包括Chef、Puppet、Ansible等配置管理工具。

环境标准化和版本控制。 可以使用Git等工具对Docker镜像进行版本控制,一旦出现故障可以快速回滚。相比以前的虚拟机镜像、Docker压缩和备份速度更快,镜像启动也像启动一个普通进程一样快速。

高资源利用率与隔离。 Docker容器没有管理程序的额外开销,与底层共享操作系统,性能更加优良,系统负载更低,在同等条件下可以运行更多的应用实例,可以更充分地利用系统资源。同时,Docker拥有不错的资源隔离与限制能力,可以精确地对应用分配CPU、内存等资源,保证了应用间不会相互影响。

容器跨平台与镜像。 Linux容器虽然早在Linux 2.6版本内核已经存在,但是缺少容器的跨平台性,难以推广。Docker在原有的Linux容器的基础上进行大胆革新,为容器设定了一整套标准化的配置方法,将应用及其依赖的运行环境打包成镜像,真正实现了"构建一次,到处运行"的理念,大大提高了容器的跨平台性。

易于理解且易用。 一个开发者可以在15分钟之内入门Docker并进行安装和部署。

应用镜像仓库。 Docker官方构建了一个镜像仓库,组织和管理形式类似于GitHub,上面已经积累了成千上万的镜像。因为Docker的跨平台的适配性,相当于为用户提供了一个非常有用的应用商店,所有人都可以自由下载微服务组件

Docker的优势

1.更高效的利用系统资源

由于容器不需要进行硬件虚拟及运行完整操作系统等额外开销,Docker对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此相比虚拟机技术,一个相同配置的主机,往往都可以运行更多数量的应用。

2.更快速的启动时间

传统的虚拟机技术启动应用服务往往需要数分钟,而docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署时间。

3.一致的运行环境

开发过程中一个常见的问题是环境一致性的问题。由于开发环境、测试环境、生产环境不一致,导致有些bug并未在开发过程中被发现。而Docker的镜像提供了出内核外完整的运行时环境,确保了应用运行环境的一致性。

4.持续交付和部署(CI、CD)

对于开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。

使用Docker可以通过制定应用镜像来实现持续集成、持续交付、部署。开发人员可以通过Dockerfile来进行镜像构建,并结合持续集成(Continuous Integration)系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署(Continuous Delivery/Deployment)系统进行自动部署。

5.更轻松的迁移

由于Docker确保了执行环境的一致性,使得应用迁移更加容易。Docker可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变换导致应用无法正常运行的情况。

6.更轻松的维护和扩展

Docker使用分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大降低了应用服务的镜像制作成本。

Docker容器与传统虚拟化比较

特性 Docker容器 虚拟机
启动速度 秒级 分钟级
计算机能力消耗 几乎无 损耗50%左右
性能 接近原生 弱于
系统支持量(单机) 上千个 几十个
隔离性 资源限制 完全隔离
硬盘使用 一般为MB 一般为GB

Docker特性

文件系统隔离:每个进程容器运行在一个完全独立的根文件系统里。

资源隔离:系统资源,像CPU和内存等可以分配到不同的容器中,使用cgroup。

网络隔离:每个进程容器运行在自己的网络空间,虚拟接口和IP地址。

日志记录:Docker将会收集和记录每个进程容器的标准流(stdout/stderr/stdin),用于实时检索或批量检索。

变更管理:容器文件系统的变更可以提交到新的镜像中,并可重复使用以创建更多的容器。无需使用模板或手动配置。

交互式shell:Docker可以分配一个虚拟终端并关联到任何容器的标准输入上,例如运行一个一次性交互shell。

优点:

1.一些优势和VM一样,但不是所有都一样。

比VM小,比VM快,Docker容器的尺寸减小相比整个虚拟机大大简化了分布到云和从云分发时间和开销。Docker启动一个容器实例时间很短,一两秒就可以启动一个实例。

2.对于在笔记本电脑,数据中心的虚拟机,以及任何的云上,运行相同的没有变化的应用程序,IT的发布速度更快。

Docker是一个开放的平台,构建,发布和运行分布式应用程序。

Docker使应用程序能够快速从组件组装和避免开发和生产环境之间的摩擦。

3.您可以在部署在公司局域网或云或虚拟机上使用它。

4.开发人员并不关心具体哪个Linux操作系统,使用Docker,开发人员可以根据所有依赖关系构建相应的软件,针对他们所选择的操作系统。然后,在部署时一切是完全一样的,因为一切都在DockerImage的容器在其上运行。开发人员负责并且能够确保所有的相关性得到满足。

5.Google,微软,亚马逊,IBM等都支持Docker。

6.Docker支持Unix/Linux操作系统,也支持Windows或Mac

image-20221101164050287

缺点局限性:

1.Docker用于应用程序时是最有用的,但并不包含数据。日志,跟踪和数据库等通常应放在Docker容器外。 一个容器的镜像通常都很小,不适合存大量数据,存储可以通过外部挂载的方式使用。比如使用:NFS,ipsan,MFS等, -v 映射磁盘分区

一句话:docker只用于计算,存储交给别人。

oracle 不适合使用docker来运行,太大了,存储的数据太多。

Docker三大核心概念

镜像(Image)

  • 一个只读的模板,镜像可以用来创建 Docker 容器
  • 用户基于镜像来运行自己的容器。镜像是基于 Union 文件系统(UnionFS)的层式结构

  • 可以简单创建或更新现有镜像,或者直接下载使用其他人的。可以理解为生成容器的『源代码』

操作系统分为内核和用户空间。对于Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而Docker镜像(Image),就相当于是一个root文件系统。

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

镜像

Docker镜像(image)类似于虚拟机镜像,可以将它理解为一个面向Docker引擎的只读模板,包含了文件系统。

例如:一个镜像可以只包含一个完整的Centos操作系统环境,可以把它称为一个Centos镜像,镜像也可以安装了Nginx应用程序(或者用户需要的其他软件),可以把它称为一个Nginx镜像。

镜像是创建Docker容器的基础,通过版本管理和增量的文件系统,Docker提供了一套十分简单的机制(仓库)创建和更新现有镜像,用户可以从网上下载一个已经做好的应用镜像,并通过命令直接创建Docker容器来使用。

Docker运行容器前需要本地存在对应的镜像,如果镜像不存在本地,Docker会尝试先从默认镜像仓库下载(默认使用Docker Hub公共注册服务器中的仓库),用户也可以通过配置,使用自定义的镜像仓库。

1.分层存储

因为镜像包含操作系统完整的root文件系统,体积往往是庞大的,因此在Docker设计时,就充分利用Union FS技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像ISO那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建就不会再发生改变,后一层上的任何改变只发生在自己的这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然看不到该文件,但实际上该文件会一直跟随镜像。因此在构建镜像时,需要额外小心,每一层尽量只包含盖层需要添加的东西,任何额外的东西应在该层构建结束前清理掉

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

容器(Container)

  • 容器是从镜像创建的运行实例,在启动的时候创建一层可写层作为最上层(因为镜像是只读的

  • 可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台

  • 可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序

容器(Container)和镜像(Image)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间(namespace)因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间,甚至自己的用户ID空间。容器内的进程是运行在一个隔离的环境里,使用起来,好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主上运行更加安全。

镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,可以称这个为容器运行时读写而准备的存储层为容器存储层。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照Docker最佳实践的要求,容器不应该向其存储层写入任何数据,容器存储层要保持无状态化。所有文件的写入操作,都应该使用数据卷(Volume)或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失

容器

Docker容器(Container)类似于一个箱子,可以把容器看做是一个简易的Linux系统环境(其中包括root用户权限,进程空间,用户空间和网路空间等),以及运行在其中的应用程序打包而成的一个箱子。Docker利用容器这个箱子来隔离和运行应用镜像。

容器是从镜像创建的应用运行实例,可以对他进行启动,停止,删除等常规操作。这些不同的容器之间都是相互隔离互不可见的。镜像自身是只读的,容器从镜像启动的时候,Docker会在镜像的最上层创建一个可写层,镜像本身将保持不变。

仓库(Repository )

  • 集中存放镜像文件的场所,可以是公有的,也可以是私有的

  • 最大的公开仓库是 Docker Hub

  • 国内的公开仓库包括 Docker Pool 等

  • 当用户创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上pull下来就可以了

  • Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务

  • 另外 Docker 采用的是客户端/服务器架构,客户端只需要向 Docker 服务器或守护进程发出请求即可完成各类操作。那么问题来了,我们能用 Docker 来做什么呢?我们可以:

    • 统一、优化和加速本地开发和构建流程

    • 保证不同的环境中可以得到相同的运行结果

    • 创建隔离环境用于测试

仓库

Docker仓库(Repostory)类似于代码的仓库(与svn、git、maven等概念类似)是Docker用来集中存放镜像文件的场所。

根据所存储的镜像是否公开分享,Docker仓库又分为:公开仓库和私有仓库。

顾名思义,公开仓库就是公共开放的镜像存储的地方,目前最大的公开仓库是Dokcer Hub (registry.hub.docker.com),存放了大量的镜像可供下载使用,国内的公开仓库有aliyun(acs-public-mirror.oss-cn-hangzhou.aliyuncs.com)。私有仓库是内部使用的私有不对外开放的仓库,用户可以内部自行搭建,内部分享镜像,方便快捷的分享专属环境的镜像文件

1.Docker Registry

镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其他服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。

一个Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。可以通过<仓库名>:<标签>的格式来指定具体是该软件哪个版本的镜像。如果不给出标签,将以latest作为默认标签

以Ubuntu镜像为例,ubuntu是仓库的名字,其内包含有不同的版本标签,如:16.04,18.04。可以通过ubuntu:14.04或者ubuntu:18.04来具体指定所需哪个版本的镜像。如果忽略了标签,比如ubuntu,那将视为ubuntu:latest。

仓库名经常以两段式路径形式出现,比如xxxx/nginx-proxy,前者往往意味着Docker Registry多用户环境下的用户名,后者往往是对应的软件名。但这并非绝对。

2.Docker Registry公开服务

Docker Registry公开服务是开放给用户使用、允许用户管理镜像的Registry服务。一般这类公开服务运行用户免费上传、下载公开的镜像,并可能提供收费收费服务供用户管理私有镜像。

最常使用的Registry公开服务是官方的Docker Hub,这也是默认的Registry,并提供大量的高质量的官方镜像。

由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对Docker Hub的镜像服务(Registry Mirror),这些镜像服务被称为加速器。常见的有阿里云加速器、DaoCloud加速器等。使用加速器会直接从国内的地址下载Docker Hub的镜像。

3.私有Docker Registry

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

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

Docker架构概览

Docker架构:http://www.infoq.com/cn/articles/docker-source-code-analysis-part1/

Docker是一个典型的客户端/服务端(C/S)架构。

Docker采用C/S架构。系统管理员通过docker客户端与docker服务端进行交互

Docker服务器负责构建、运行和分发docker镜像

image-20221101170304597

Docker的总架构图

image-20221101170328210

image-20221101170341085

工作流程:服务器A上运行docker Engine 服务,在docker Engine上启动很多容器container , 从外网Docker Hub上把image操作系统镜像下载来,放到container容器运行。这样一个容器的实例就运行起来了。

最后,通过Docker client 对docker 容器虚拟化平台进行控制。

Image和Container的关系:image可以理解为一个系统镜像,Container是Image在运行时的一个状态。\ 如果拿虚拟机作一个比喻的话,Image就是关机状态下的磁盘文件,Container就是虚拟机运行时的磁盘文件,包括内存数据。

dockerhubdockerhub是docker官方的镜像存储站点,其中提供了很多常用的镜像供用户下载,如ubuntu, centos等系统镜像。通过dockerhub用户也可以发布自己的docker镜像,为此用户需要注册一个账号,在网站上创建一个docker仓库。

账号注册地址:https://hub.docker.com/

github : 有自己的帐号:1 没有:2

作为一个成功的开源人士或运维人才: 一定要注册一个github和dockerhub帐号。

为了?

image-20221101170439637

各个功能模块的功能简述:

Docker client:

Docker client是一个泛称,用来向指定的Docker daemon发起请求,执行相应的容器管理操作。它既可以使docker命令行工具,也可以是任何遁循了Docker API的客户端。

Docker daemon:

Docker daemon是Docker架构的主要用户接口,也是最核心的后台进程(service docker start 就是启动DOcker daemon进程),它负责响应来自Docker client的请求,然后将这些请求翻译成系统调用完成容器管理操作。该进程会在后台启动一个API server,负责接收由Docker client 发送的请求;接收到的请求将通过Docker daemon内部的一个路由分发调度,再由具体函数来执行请求。

Graph:

Graph组件负责维护已下载的镜像信息及它们之间的关系,所以大部分Docker镜像相关的操作都会由graph组件来完成。graph通过镜像"层"和每层的元数据来记录这些镜像的信息,用户发起的镜像管理操作最终都转换成了graph对这些层和元数据的操作。但正是由于这个原因,而且很多时候Docker操作都需要加载当前Docker daemon维护者的所有镜像信息,graph组件常常会成为性能瓶颈。

GraphDB:

Docker daemon通过GraphDB记录它所维护的所有容器以及它们之间的link关系,所以这里就采用了一个图结构来保存这些数据。具体来说,GraphDB就是一个基于SQLite的最简单版本的图形数据库,能够为调用者提供节点、删、遍历、连接、所有父子节点的查询等操作。这些节点对应的就是一个容器,而节点间的边就是一个Docker link关系。每创建一个容器,Docker daemon都会在GraphDB里添加一个节点,而当为某个容器设置了link操作后,在GraphDB中就会为它创建一个父子关系。

drive:

Docker通过driver模块来实现对Docker容器执行环境的定制。Docker daemon负责将用户请求转译成系统调用,为了将这些系统调用抽象成统一的操作接口方便调用者使用,Docker把这些操作分类成容器管理驱动(execdriver)、网络管理驱动(networkdriver)、文件存储驱动(graphdriver)三种。

execdriver是对Linux操作系统的namespace、cgroups、apparmor、SELinux等容器运行所需的系统操作进行的一层二次封装,其本质作用类似于LXC,但功能更全面。execdriver最主要的实现是Docker官方编写的libcontainer库。libcontainer是一个独立的容器管理包,networkdriver和execdriver都通过libcontainer来实现对容器的具体操作(包括利用UTS、IPC、PID、Network、Mount、User等namespace来实现容器之间的资源隔离和利用cgroup实现对容器的资源限制)。当运行容器的命令执行完毕后,一个实际的容器就处于运行状态,该容器拥有独立的文件系统、安全且相互隔离的运行环境。

networkdriver是对容器网络环境操作所进行的封装。对于容器来说,网络设备的配置相比较独立,并且应该允许用户进行更多的配置。这些操作具体包括创建容器通信所需的网络,容器的network namespace,这个网络所需的虚拟网卡,分配通信所需的IP,服务访问的端口和容器与宿主机之间的端口映射(设置hosts、resolv.conf、iptables等)。

graphdriver是所有与容器镜像相关操作的最终执行者。graphdriver会在Docker工作目录下维护一组与镜像层对应的目录,并记下容器和镜像之间的关系等元数据。这样,用户对镜像的操作最终会被映射成对这些目录文件及元数据的增删改查,从而屏蔽掉不同文件存储实现对于上层调用者的影响。目前Docker已经支持的文件存储包括aufs、btrfs、devicemapper、overlay和vfs.

daemon 对象的创建和初始化过程

Docker容器的配置信息:

容器的配置信息主要功能是供用户自由配置Docker容器的可选功能,使得Docker容器的运行更贴近用户期待的运行场景,配置信息的处理包含以下部分:

设置默认的网络最大传输单元:当用户没有对-mtu参数进行指定时,将其设置为1500.否则,沿用用户指定的参数值。

检测网桥配置信息:此部分配置为进一步配置Docker网络提供铺垫。

查验容器通信配置:主要用于确定用户设置是否允许对iptables配置及容器间通信,分别用--iptables和--icc参数表示,若两者皆为false则报错。

验证系统支持及用户权限:

初步处理完Docker的配置信息之后,Docker对自身运行的环境进行了一系列的检查,主要包括:

操作系统类型对Docker daemon的支持,目前Docker daemon只能运行在Linux系统上。

用户权限的级别,必须是root权限。

内核版本上与处理器的支持,只支持"amb64"架构的处理器,且内核版本必须升至3.10.0及以上。

配置daemon工作路径:

配置Dockerdaemon的工作路径,主要是创建Docker daemon运行中所在的工作目录,默认为/var/lib/docker。若目录不存在,则会创建并赋予"0700"权限。

配置Docker容器所需的文件环境:

这一步Docker daemon会在Docker工作根目录/var/lib/docker下面初始化一些重要的目录和文件来构建Docker容器工作所需的文件系统环境。

配置graphdriver目录,它用于完成Docker镜像管理所需的联合文件系统的驱动层。所以,这一步的配置工作就是加载并配置镜像存储驱动graphdriver,创建镜像管理所需的目录和环境。

创建容器配置文件目录。Docker daemon在创建Docker容器之后,需要将容器内的配置文件放到这个目录下统一管理。目录默认位置为:/var/lib/docker/containers,它下面会为每个具体容器创建一个目录,下面保存一些配置文件。

配置镜像目录,主要工作是:在工作根目录下创建一个graph目录来存储所有镜像描述文件,默认目录为/var/lib/docker/graph。对于每一个镜像层,Docker在这里使用json和layersize两个文件描述这一层镜像的父镜像ID和本层大小,而正在的镜像内容保存在aufs的diff工作目录的同名目录下。

创建volume驱动目录(默认/var/lib/docker/volumes),Docker中volume是宿主机上挂载到Docker容器内部的特定目录。由于Docker需要使用graphdriver来挂载这些volumes,所以采用vfs驱动实现volumes的管理。这里的volumes目录下仅保存一个volume配置文件config.json,其中会以Path指出这个目录的真正位置。

准备"可信镜像"所需的工作目录。在Docker工作目录下创建trust目录,并创建一个TrustStore。这个存储目录可以根据用户给出的可信url加载授权文件,用来处理可信镜像的授权和验证过程。

创建TagStore,用于存储镜像的仓库列表。

创建Docker daemon网络

创建GraphDB。这一步初始化GraphDB实际上就是建立数据库连接的过程。

初始化execdriver.

恢复已有的Docker容器。当Docker daemon启动时,会去查看在daemon.repository也就是在/var/lib/docker/containers/中的内容。若有已经存在的Docker容器,则将相应的信息收集并进行维护,同时重启restart policy为always的容器。

Docker daemon进程的启动都要遵循以下3步:

首先启动一个APIServer,它工作在用户通过-H指定的socker上面

然后Docker使用NewDaemon方法创建一个daemon对象来保存信息和处理业务逻辑。

最后将上述APIServer和daemon对象绑定起来,接受并处理client的请求。

libcontainer详解:http://www.infoq.com/cn/articles/docker-container-management-libcontainer-depth-analysis

Docker容器技术和虚拟机对比

image-20221101170826867

传统方式是在硬件层面实现虚拟化,需要有额外的虚拟机管理应用和虚拟机操作系统层。Docker容器是在操作系统层面上实现虚拟化,直接复用本机本地的操作系统,因此更加轻量级,性能方面也更加高效。Docker 从 0.9 版本开始使用 libcontainer 替代 lxc

相同点: docker容器技术和虚拟机技术,都是虚拟化技术。

不同点:容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而Docker 只需要启动 10 个隔离的应用即可。

image-20221101170922691

image-20221101170931521

​ 传统虚拟化与Docker

对比传统虚拟机总结

image-20221101170953552

总结:docker相对于VM虚拟机,少了虚拟机操作系统这一层,所以docker效率比虚拟机高你的物理系统启动使用几秒? 10秒

在docker上启动一个实例 1-2秒 吃鲸吗?:哈哈

Docker 核心技术

1.Namespace --- 实现Container的进程、网络、消息、文件系统和主机名的隔离。

2.Cgroup --- 实现对资源的配额和度量。

image-20221101171211035

注:Cgroup的配额,可以指定实例使用的cpu个数,内存大小等。就像如下图,vmware虚拟机中的硬件配置参数。

image-20221101171228793

Linux 3.8内核中包括了6种命名空间:

命名空间 描述
Mount(mnt) 隔离挂载点
Process ID(process) 隔离进程ID
Network(net) 隔离网络设备、协议栈、端口等
InterProcess Communication(ipc) 隔离进程间通信
UTS 隔离Hostname和NIS域名
User ID(user) 隔离用户和group ID

还有一个CGroup Name Space,和上述这些命名空间是container技术的一个基础。

链接:https://www.jianshu.com/p/369e50201bce

容器技术内核特性

(1)Cgroup

Cgroup是control group,又称为控制组,它主要是做资源控制。原理是将一组进程放在放在一个控制组里,通过给这个控制组分配指定的可用资源,如:cpu、memory、IO等,达到控制这一组进程可用资源的目的。

  1. 限制进程组可以使用的资源数量(Resource limiting )。比如:memory子系统可以为进程组设定一个memory使用上限,一旦进程组使用的内存达到限额再申请内存,就会触发OOM(out of memory)。

  2. 进程组的优先级控制(Prioritization )。比如:可以使用cpu子系统为某个进程组分配特定cpu share。

  3. 记录进程组使用的资源数量(Accounting )。比如:可以使用cpuacct子系统记录某个进程组使用的cpu时间

  4. 进程组隔离(Isolation)。比如:使用ns子系统可以使不同的进程组使用不同的namespace,以达到隔离的目的,不同的进程组有各自的进程、网络、文件系统挂载空间。

  5. 进程组控制(Control)。比如:使用freezer子系统可以将进程组挂起和恢复。

(2)Namespace,命名空间

Namespace又称为命名空间,它主要实现容器与容器之间的访问隔离。其原理是针对一类资源进行抽象,并将其封装在一起提供给一个容器使用,对于这类资源,因为每个容器都有自己的抽象,而他们彼此之间是不可见的,所以就可以做到访问隔离。

pid namespace:不同的容器会产生不同的进程id

net namespace:每个容器都存在独立的网络

ipc namespace:每个容器都可以单独实现信号的发起和消息队列

mnt namespace:一个进程放到一个特定的目录执行

uts namespace:每个container拥有独立的hostname和domain name

user namespace:每个container可以有不同的 user 和 group id

Docker里的内核知识

Docker容器本质是宿主机上的进程,Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-write)实现了高效的文件操作。

namespace资源隔离

linux内核中提供了6种namespace隔离的系统调用,如下图:

image-20221101171610060

实际上,Linux内核实现namespace的主要目的之一就是实现轻量级虚拟化(容器)服务。在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛自己置身于一个独立的系统环境中,以达到独立和隔离的目的。

进行namespace API操作的四种方式包括clone()在创建新进场的同时创建namespace,sent()加入一个已经存在的namespace以及unshare()在原先进场上进行namespace隔离还有/pro下的部分文件。为了确定隔离的到底是哪6项namespace,在使用这些API时,通常需要制定以下6个参数的一个或多个,通过|(位或)操作来实现。这6个参数就是上图上面那6个系统调用参数。

下面对6种namespace做下简单介绍:

UTS namespace: UTS(UNIX Time-sharing System)namespace提供了主机名和域名的隔离,这样每个Docker容器就可以拥有独立的主机名和域名了。在网络上可以被视作一个独立的节点,而非宿主机上的一个进程。Docker中,每个镜像基本都以自身所提供的服务名称来命名镜像的hostname,且不会对宿主机产生任何影响,其原理就是利用UTS namespace。

IPC namespace:进程间通信(Inter-Process Communication,IPC)涉及的IPC资源包括常见的信号量、消息队列和共享内存。申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同IPC namespace下的进程互相不可见。目前使用IPC namespace机制其中比较有名的有PostgreSQL。Docker当前也使用IPC namespace实现了容器与宿主、容器与容器之间的IPC隔离。# ipcs -q #可以查看已经开启的message queue。

PID namespace : PID namespace隔离非常实用,它对进程PID重新标号,即两个不同namespace下的进程可以有相同的PID。 每个PID namespace都有自己的计数程序。内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称为root namespace。它创建的新的PID namespace被称为child namespace(树的子节点),而原先的PID namespace就是新创建的PID namespace的parent namespace(树的父节点)。通过这种方式,不同的PID namespace会形成一个层级体系。所属的父节点可以看到子节点的进程,并可以通过信号灯方式对子节点中的进程产生影响。反过来,子节点却不能看到父节点的PID namespace中的任何内容。

Network namespace : network namespace主要提供了关于网络资源的隔离,包括网络设备、IPV4和IPV6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、套接字(socket)等。一个物理的网络设备最多存在于一个network namespace中,可以通过创建veth pair(虚拟网络设备对:有两端,类似管道,如果数据从一端传入另一端也能接收到)在不同的network namespace间创建通道,以达到通信目的。

Mount namespace : 通过隔离文件系统挂载点对隔离文件系统提供支持,它是历史上第一个Linux namespace,所以标示位比较特殊,就是CLONE_NEWNS。隔离后不同mount namespace中的文件结构发生变化也互不影响。可以通过/proc/[pid]/mounts查看所有挂载到当前namespace中的文件系统,还可以通过/proc/[pid]/mountstats看到mount namespace中文件设备的统计信息,包括挂载文件的名字、文件系统类型、挂载位置等。进程在创建mount namespace时,会把当前的文件结构复制给新的namespace。新的namespace中的所有mount操作都只影响自身的文件系统,对外界不会产生任何影响。

user namespace : 这个主要隔离了安全相关的标识符(identifiers)和属性(attributes),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。user namespace直到Linux内核3.8版本的时候还未完全实现

cgroups资源限制

cgroups在2007年更名为(control groups)并整合进Linux内核。cgroups可以限制、记录任务组所使用的物理资源(包括CPU、memory、IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。

cgroups的作用:

实现cgroups的主要目的是为不同用户层面的资源管理,提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups提供了以下四大功能:

资源限制 #cgroups可以对任务使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出OOM(Out of Memory)提示。

优先级分配 #通过分配的CPU时间片数量及磁盘IO带宽大小,实际上就相当于控制了任务运行的优先级。

资源统计 #cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等,这个功能非常适用于计费。

任务控制 #cgroups可以对任务执行挂起、恢复等操作。

cgroups术语表:

task(任务) #在cgroups的术语中,任务表示系统的一个进程或线程。

cgroups(控制组) #cgroups中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另一个cgroup。

subsystem(子系统) #cgroups中的子系统就是一个资源调度控制器。比如CPU子系统可以控制CPU时间分配,内存子系统可以限制cgroup内存使用量。

hierarchy(层级) #层级由一系统cgroup以一个树状结构排列而成,每个层级通过绑定对应的子系统进行资源控制。层级中的cgroup节点可以包含零或多个子节点,子节点继承父节点挂载的子系统。整个操作系统可以有多个层级。

cgroups、任务、子系统、层级四者间的关系以及基本规则:

规则1: 同一个层级可以附加一个或多个子系统。

规则2:一个子系统可以附加到多个层级,当且仅当目标层级只有唯一一个子系统时。

规则3:系统每次新建一个层级时,该系统上的所有任务默认加入这个新建层级的初始化cggroup,这个cgroup也被称为root cgroup。对于创建的每个层级,任务只能存在于其中一个cgroup中,即一个任务不能存在于同一个层级的不同cgroup中,但一个任务可以存在于不同层级中的多个cgroup中。如果操作时把一个任务添加到同一个层级中的另一个cgroup中,则会将它从第一个cgroup中移除。

规则4: 任务在for/clone自身时创建的子任务默认与原任务在同一个cgroup中,但是子任务允许被移动到不同的cgroup中,即fork/clone完成后,父子任务间在cgroup方面是互不影响的。

子系统简介:

子系统实际上就是cgroups的资源控制系统,每种子系统独立地控制一种资源。目前Docker使用如下9种子系统:

bikio  # 可以为块设备设定输入/输出限制,比如物理驱动设备(包括磁盘、固态硬盘、USB等)
cpu   # 使用调度程序控制任务对CPU的使用
cpuacct  # 自动生成cgroup中任务对CPU资源使用情况的报告。
cpuset  # 可以为cgroup中的任务分配独立的CPU(此处针对多处理器系统)和内存。
devices  # 可以开启或关闭cgroup中任务对设备的访问
freezer  # 可以挂起或恢复cgroup中的任务
memory  # 可以设定cgroup中任务对内存使用量的设定,并且自动生成这些任务对内存资源使用情况的报告。
perf_event  # 使用后使cgroup中的任务可以进行统一的性能测试。
*net_cls  # Docker没有直接使用它,它通过使用等级标识符(classid)标识网络数据包,从而允许Linux流量控制程序(Traffic Controller,TC)识别从具体cgroup中生成的数据包。

cgroup实现方式及工作原理简介:

cgroups的实现本质上是给任务挂上钩子,当任务运行的过程中涉及某种资源时,就会触发钩子上所附带的子系统进行检测,根据资源类别的不同使用对应的技术进行资源限制和优先级分配。

cgroups如何判断资源超限及超出限额之后的措施: 对于不同的系统资源,cgroups提供了统一的接口对资源进行控制和统计,但限制的具体方式则不仅相同。比如memory子系统,会在描述内存状态的"mm_struct"结构体重记录它所属的cgroup,当进程需要申请更多内存时,就会触发cgroup用量检测,如果用量超过cgroup规定的限额则拒绝用户的内存申请,否则就给予相应内存并在cgroup的统计信息中记录。实际实现要比以上描述复杂得多,不仅需要考虑内存的分配与回收,还需考虑不同类型的内存和Cache(缓存)和Swap(交换区内存拓展)等。

cgroups的子系统与任务之间的关联关系: cgroup与任务之间是多对多的关系。所以它们并不直接关联,而是通过一个中间结构把双向的关联信息记录起来。每个任务结构体task_struct中都包含了一个指针,可以查询到对应cgroup的情况,同时也可以查询到各个子系统的状态,这些子系统状态中也包含了找到任务的指针,不同类型的子系统按需定义本身的控制信息结构体,最终在自定义的结构体中把子系统状态指针包含进去,然后内核通过container_of(这个宏可以通过一个机构体的成员找到结构体自身)等宏定义来获取对应的结构体,关联到任务,以此达到资源限制的目的。同时为了让cgroups便于用户理解和使用,也为了用精简的内核代码为cgroup提供熟悉的权限和命名空间管理,内核开发者们按照Linux虚拟文件系统转换器(Virtual Filesystem Switch,VFS)接口实现了一套名为cgroup的文件系统,非常巧妙的用来表示cgroups的层级概念。把各个子系统的实现都封装到文件系统的各项操作中

Docker Hub

目前Docker官方维护了一个公有仓库https://hub.docker.com,其中已经有上万个镜像。大部分需求都可以通过在Docker Hub中直接下载镜像来实现。

仓库是集中存放镜像的地方。一个容易与之混淆的概念是注册服务器(Registry).实际上注册服务器是存放仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说仓库可以被认为是一个具体的项目或目录。

仓库又分共有仓库和私有仓库

登录 DockerHub账户:

用户通过拥有一个Docker Hub账户,用以提交和推送自己的镜像。当用户在未登录状态下输入docker login命令后,Docker client端将提示用户依次输入Username、 Password和Email,并存放到用户希望登录的registry对应的配置文件中。若用户处于已登录状态且没有尝试以新用户身份重新登录,则根据配置文件中的既有信息来获取用户信息。其后,Docker client向Docker server发送"post/auth"的HTTP请求,进行权限认证。Docker server接受到相应的HTTP请求后,根据URL以及请求方法来确定需要执行的方法,认证的方法为postAuth。

在postAuth这个方法执行的过程中,根据HTTP请求中的配置信息,由daemon实例调用Auth函数来进行认证工作,并将函数返回状态(即认证是否通过)写入HTTP应答。

Auth函数代码实现包括如下几个关键步骤:

根据config中的registry address,得到有效的index配置信息

由返回的Index配置信息解析获得一个registry endpoint.

重置config中的registry address为上一步返回的endpoint.

执行Login函数登入该registry endpoint。

上述Login函数真正负责进行用户登录的实现过程。它会根据Docker registry的版本分别执行Login V2和Login V1.

基本操作:

无需登录就可以进行:docker search 和docker pull操作

根据是否为官方提供,可将这些镜像资源分为两类。一种是类似于centos这样的基础镜像,称为基础或根镜像。这些镜像是由docker公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。还有一种类型,比如tianon/centos镜像,它是由DockerHub的用户tianon创建并维护的,带有用户名称为前缀,表明是某用户的某仓库。可以通过用户名称前缀user_name/来指定使用某个用户提供的镜像.

# docker search centos --filter=stars=5000      # 显示评价为5000星以上的镜像
NAME                DESCRIPTION                     STARS               OFFICIAL            AUTOMATED
centos              The official build of CentOS.   5556                [OK]

用户可以再登录的情况下通过docker push命令将本地的镜像推送到Docker Hub

自动创建:

    自动创建功能对于需要经常升级镜像内程序来说十分方便,自动创建功能使得用户通过Docker Hub指定跟踪一个目标网站(目前支持GitHub或BitBucket)上的项目,一旦项目发现新的提交,则会自动执行创建。要配置自动创建,包括如下的步骤:
        1). 创建并登录Docker Hub,以及目标网站;*在目标网站中连接账户到Docker Hub。
        2). 在Docker Hub中配置一个自动创建。
        3).选取一个目标网站中的项目(需要含Dockerfile)和分支
        4).指定Dockerfile的位置,并提交创建。
        之后可以再Docker Hub的“自动创建”页面跟踪每次创建的状态

Pull镜像:

    Docker的server端收到用户发起的pull请求后,需要做的主要工作如下:

        1、根据用户命令行参数解析出其希望拉取的repository信息

        2、通过TagStore设置锁,保证Docker daemon在一个时刻对一个repository只能进行一个拉取操作;拉取镜像完毕之后该锁释放。

        3、获取endpoint信息,并向该endpoint指定的registry发起会话

        4、如果待拉取repository为official版本或者endpoint的API版本为V2,同时用户没有配置Docker-Mirrors,Docker就直接向V2 registry拉取镜像。(如果向V2 registry拉取镜像失败,则尝试从V1 registry拉取。)

        5、获取V2 registry的endpoint。

        6、由endpoint和待拉取镜像名称获取拉取指定镜像的认证信息。

        7、如果tag值为空,即没有指定标签,则获取V2 registry中repository的tag list,然后对于tag list中的每一个标签,都执行一次pullV2Tag方法。该方法的功能分为两大部分,一是验证用户请求,二是当且仅当某一层不在本地时进行拉取这一层文件到本地。如果tag值不为空,则只对指定标签的镜像进行上述工作

Commit镜像:

    docker commit命令只提交容器镜像发生变更了的部分,即修改后的容器镜像与当前仓库中对应镜像之间的差异部分,这使得该操作实际需要提交的文件往往并不多。Docker server接收到对应的HTTP请求后,需要执行下面的步骤:

        根据用户输入pause函数的设置确定是否暂停该Docker容器的运行。

        寻找该容器文件系统再graphdriver上的路径。

        返回该容器与其父镜像的差异并将这部分文件和目录打成压缩包,即待commit容器的可读写层的差异(diff)。

        graphdriver根据返回的差异创建一个新的镜像,记录其元数据。

        将上述镜像文件及目录上传至repository。

Build构建镜像:

    Docker client端通过Dockerfile完成相关信息的设置之后,Docker client向Docker server发送“POST/build”的HTTP请求,包含了所需的context信息(文件或目录)。

        Docker server端接受到相应的HTTP请求后,需要做的工作如下:

        创建一个临时目录。并将context制定的文件系统解压到该目录下。

        读取并解析Dockerfile

        根据解析出的DOckerfile遍历其中的所有指令,并分发到不同的模块去执行。Dockerfile每条指令的格式均为INSTRUCION arguments,INSTRUCION是一些特定的关键词,包括FROM、RUN、USER等,都会映射到不同的parser进行处理。

        parser为上述每一个指令创建一个对应的临时容器,然后通过commit使用此容器生成一层镜像。

        Dockerfile中偶的指令对应的层的集合,就是此次build后的结果。其中最后一次commit生成的层镜像ID就会作为最终的镜像ID返回。

Push镜像:

    当用户制作了自己的镜像后,希望将它上传至仓库,此时可以通过docker push命令完成该操作。而在Docker server接收到用户的push请求后的关键步骤如下:

    解析出repository信息,通过TagStore设置锁。

    通过ping操作检查指定的registry是否可用,返回一个registry endpoint,然后发起同registry的会话.

    如果推送的镜像为Official镜像或者endpoint对应版本为V2 registry,则调用pushV2 Repository方法。这个方法会首先验证用户指定标签的镜像在本地是否存在,以及被推repository的访问权限。接下来,对于不同标签的待推送镜像,该方法会从顶向下逐个检验layer其checksum,标记被推送repository中不存在的layer和更改过的读写层内容。将这些标记后的镜像内容上传完毕后,再讲一份描述文件manifest上传到repository。

    如果镜像不属于上述情况,则Docker会调用push Repository方法来推送镜像到V1 registry,并根据待推送的repository和tag信息保证当且仅当某layer在endpoint上不存在时,才上传该layer。

部署docker容器虚拟化平台

Docker官网:https://www.docker.com/

官网文档:https://docs.docker.com/

Docker中文社区:http://www.docker.org.cn/

一键部署参考:https://www.jianshu.com/p/34d3b4568059

Docker版本:

docker.io ,docker ,docker-engine 两个docker发行版本。
Docker的版本:
    Docker-CE:Docker Commutication Enterprise
    Docker-EE:企业版本
Docker分为CE和EE版本。CE为社区版(免费,支持周期7个月),EE为企业版,强调安全,付费使用,支持周期24个月。
    CE分位stable,test和nightly三个更新频道。

注意:切勿在没有配置Docker YUM源的情况下直接使用yum命令安装Docker。

安装的前提条件:

目前,CentOS 仅发行版本中的内核支持 Docker。

Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上

~~Docker 运行在 CentOS-6.5 或更高的版本的 CentOS 上,要求系统为64位、系统内核版本为2.6.32-431或者更高版本。 #(建议不要运行在6版本及以下)~~

Docker安装的基本要求:
    1、Docker只支持64位CPU架构的计算机,目前不支持32位CPU.
    2、建议系统的Linux内核版本为3.10及以上。
    3、Linux内核需开启cgroups和namespace功能。
    4、对于非Linux内核的平台,如Microsoft Windows和OS X,需要安装使用Boot2Docker工具。

cgroups 是Linux内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 cpu,内存等资源实现精细化的控制,下面是一个博客的介绍链接:http://blog.csdn.net/hzrandd/article/details/52484332

Namespaces(命名空间)机制提供一种资源隔离方案,介绍链接:https://www.cnblogs.com/cherishui/p/4237883.html

卸载旧版本

旧版本的Docker称为docker或者docker-engine,使用以下命令卸载。

[root@localhost ~]# yum remove docker \
docker-client \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine

建立docker用户组

默认情况下,docker命令会使用Unix socket与Docker引擎通讯。而只有root用户和docker组的用户才可以直接访问Docker引擎的Unix socket。处于安全考虑,一般Linux系统上不会直接使用root用户。因此更好的做法是将需要使用的用户加入docker组里面

(1)docker用户加入docker用户组
# groupadd docker

(2)将用户加入到docker组
# usermod -aG docker USERNAME

安装docker的7种方法:

~~方法1、配置本地docker的yum源 #(建议不用)~~

~~方法2:直接使用centos系统自带的yum源安装,速度比较慢 #(建议不用)~~

方法3:清华源 https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/

方法4:参考官网https://docs.docker.com/install/linux/docker-ce/centos/

使用官方安装脚本自动安装 (仅适用于公网环境)

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

方法5:https://get.daocloud.io/

方法6:二进制离线安装

1、下载

https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz

https://mirrors.aliyun.com/docker-ce/linux/static/stable/x86_64/


安装脚本参考 https://github.com/liumiaocn/easypack/blob/master/docker/install-docker.sh


#!/bin/sh

SYSTEMDDIR=/usr/lib/systemd/system
SERVICEFILE=docker.service
SERVICENAME=docker
DOCKERDIR=/usr/bin
DOCKERBIN=docker
SOCKETFILE=docker.socket
CONTAINERDFILE=containerd.service

Usage(){
  echo "Usage: $0 ./FILE_NAME_DOCKER_CE_TAR_GZ"
  echo "       $0 ./docker-23.0.6.tgz"
  echo "Get docker-ce binary from: https://download.docker.com/linux/static/stable/x86_64/"
  echo "eg: wget -c https://download.docker.com/linux/static/stable/x86_64/docker-23.0.6.tgz"
  echo "eg: curl -O --progress https://download.docker.com/linux/static/stable/x86_64/docker-23.0.6.tgz"
  echo ""
}

#传入的参数不等于1就退出
if [ $# -ne 1 ]; then
  Usage
  exit 1
else
  FILETARGZ="$1"
fi

if [ ! -f ${FILETARGZ} ]; then
  echo "Docker binary tgz files does not exist, please check it"
  echo "Get docker-ce binary from: https://download.docker.com/linux/static/stable/x86_64/"
  echo "eg: wget https://download.docker.com/linux/static/stable/x86_64/docker-23.0.6.tgz"
  echo "eg: curl -O --progress https://download.docker.com/linux/static/stable/x86_64/docker-23.0.6.tgz"
  exit 1
fi

# 判断 docker 命令是否存在
if command -v docker >/dev/null 2>&1; then
    echo "Docker is installed."
    exit 3
else
    echo "Docker is not installed."
fi

echo "##EXTRACT : tar xvpf ./${FILETARGZ} --strip-components=1"
tar xvf ./${FILETARGZ} -C ${DOCKERDIR} -o --touch --strip-components=1 --no-same-owner --owner=root --group=root
which ${DOCKERBIN}
echo

#create docker group
echo "##create docker group"
getent group docker &> /dev/null
test $? -eq 0 || groupadd docker

echo "##systemd service: ${SERVICEFILE}"
echo "##docker.service: create docker systemd file"
cat >${SYSTEMDDIR}/${SERVICEFILE} << EOF
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
#After=network-online.target docker.socket firewalld.service containerd.service
After=network-online.target docker.socket containerd.service
Wants=network-online.target
Requires=docker.socket containerd.service

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=${DOCKERDIR}/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP \$MAINPID
TimeoutSec=0
RestartSec=2
Restart=always

# Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229.
# Both the old, and new location are accepted by systemd 229 and up, so using the old location
# to make them work for either version of systemd.
StartLimitBurst=3

# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.
# Both the old, and new name are accepted by systemd 230 and up, so using the old name to make
# this option work for either version of systemd.
StartLimitInterval=60s

# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity

# Comment TasksMax if your systemd version does not support it.
# Only systemd 226 and above support this option.
TasksMax=infinity

# set delegate yes so that systemd does not reset the cgroups of docker containers
Delegate=yes

# kill only the docker process, not all processes in the cgroup
KillMode=process
OOMScoreAdjust=-500

[Install]
WantedBy=multi-user.target
EOF


echo "##systemd service: ${SOCKETFILE}"
echo "##docker.socket: create socket systemd file"
cat >${SYSTEMDDIR}/${SOCKETFILE} << EOF
[Unit]
Description=Docker Socket for the API

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

[Install]
WantedBy=sockets.target
EOF

echo "##systemd service: ${CONTAINERDFILE}"
echo "##containerd.service: create containerd systemd file"
cat >${SYSTEMDDIR}/${CONTAINERDFILE} << EOF
# Copyright The containerd Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target local-fs.target

[Service]
ExecStartPre=-/sbin/modprobe overlay
ExecStart=${DOCKERDIR}/containerd

Type=notify
Delegate=yes
KillMode=process
Restart=always
RestartSec=5
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=infinity
# Comment TasksMax if your systemd version does not supports it.
# Only systemd 226 and above support this version.
TasksMax=infinity
OOMScoreAdjust=-999

[Install]
WantedBy=multi-user.target
EOF
echo ""

echo "##create daemon.json file"
test -d /etc/docker || mkdir /etc/docker
cat > /etc/docker/daemon.json << EOF
{
    "oom-score-adjust": -1000,
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "10m",
        "max-file": "3"
    },
    "max-concurrent-downloads": 10,
    "max-concurrent-uploads": 10,
    "bip": "172.17.0.1/16",
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"],
    "storage-driver": "overlay2",
    "live-restore": true
}
EOF

systemctl daemon-reload
echo "##Service restart: ${SERVICENAME}"
systemctl restart ${SERVICENAME}
echo "##Service status: ${SERVICENAME}"
systemctl status ${SERVICENAME}
# 检查 Docker 服务状态并获取状态值
docker_status=$(systemctl is-active docker.service)
# 判断 Docker 服务是否处于运行状态
if [ "$docker_status" == "active" ]; then
    echo "Docker is running."
    echo "##Service enabled: ${SERVICENAME}"
    systemctl enable ${SERVICENAME}
    echo "## docker version"
    docker version
else
    echo "Docker is not running."
fi
install-docker.sh docker-23.0.6.tgz

方法7:用阿里源,参考地址:

https://yq.aliyun.com/articles/110806

https://developer.aliyun.com/mirror/docker-ce?spm=a2c6h.13651102.0.0.53322f70lXYvKW

# step 1: 安装必要的一些系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2

# Step 2: 添加软件源信息
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

docker安装版本查看:
# yum list docker-ce --showduplicates | sort -r

# Step 3: 更新并安装 Docker-CE,(最好安装18.09,因为k8s1.14配合18.09比较好)
sudo yum makecache fast && sudo yum -y install docker-ce-18.09.9 docker-ce-cli-18.09.9 

# Step 4: 开启Docker服务并设置为开机自动启动
systemctl start docker.service && systemctl enable docker.service

#查看docker软件包
# rpm -qa docker*
docker-ce-18.09.9-3.el7.x86_64      #docker服务端
docker-ce-cli-18.09.9-3.el7.x86_64  #docker客户端

安装docker报错(虚拟机中可能会遇到,如果没有报错请忽略)

Error: Package: docker-ce-18.03.1.ce-1.el7.centos.x86_64 (docker-ce-stable)

Requires: container-selinux >= 2.9

报错原因: docker-ce-selinux 版本过低

解决办法:在https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/7/x86_64/stable/Packages/网站下载对应版本的docker-ce-selinux,安装即可

# yum -y install https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/7/x86_64/stable/Packages/docker-ce-selinux-17.03.3.ce-1.el7.noarch.rpm

再次安装docker 成功:

# yum -y install docker-ce

image-20221101173117911

报错: 缺包container-selinux

Error: Package: containerd.io-1.2.6-3.3.el7.x86_64 (docker-ce-stable)
Requires: container-selinux >= 2:2.74
Error: Package: 3:docker-ce-19.03.2-3.el7.x86_64 (docker-ce-stable)
           Requires: container-selinux >= 2:2.74
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest
[root@falcon-agent-01 ~]# rpm -ivh docker-ce
error: open of docker-ce failed: No such file or directory

在网上找,感谢:https://www.cnblogs.com/yidiandhappy/p/10694388.html,参考其中处理方法,进入:http://mirror.centos.org/centos/7/extras/x86_64/Packages/,查找到container-selinux-2,发现如下图所示rpm:

image-20221101173222392

# yum -y install http://mirror.centos.org/centos/7/extras/x86_64/Packages/container-selinux-2.107-3.el7.noarch.rpm
# 再次安装docker 成功:
# yum -y install docker-ce-18.09.9 docker-ce-cli-18.09.9

关于启动docker的两个system文件

docker.servicedocker.socket 是 Docker 服务在 Linux 系统中的两个 systemd 单元,用于管理 Docker 进程和监听 Unix Socket 的守护进程。

docker.service 是 Docker 服务的主要 systemd 单元,负责启动 Docker 守护进程、管理 Docker 容器和镜像。通过该单元,可以实现对 Docker 服务的启动、停止、重启、状态查看等操作。在大部分 Linux 系统中,Docker 的 systemd 单元文件路径为 /usr/lib/systemd/system/docker.service

docker.socket 则是一个监听 Unix Socket 的系统守护进程,用于接收来自 Docker 客户端的请求并将其转发给 Docker 服务端。它在 Docker 服务启动前就已启动,并通过 Systemd 的 socket activation 机制监听 Unix Socket,从而缩短了 Docker 服务的启动时间。在大部分 Linux 系统中,Docker 的 systemd 单元文件路径为 /usr/lib/systemd/system/docker.socket

当 Docker 客户端发送请求时,它会使用与 Docker 服务端相同的 Unix Socket 路径(默认为 /var/run/docker.sock),这时系统会将该请求交给 docker.socket 守护进程处理,如果 Docker 服务已启动,并且有可用的容器或镜像,则它会将请求转发给 docker.service 单元处理。如果 Docker 服务未启动,则 docker.socket 守护进程会等待 Docker 服务启动并监听 Unix Socket 后再将请求转发。这样,即使 Docker 服务意外终止,docker.socket 守护进程仍然会在 Docker 服务重新启动时自动监听 Unix Socket,并将存储在缓冲区中的请求全部传递给 Docker 服务处理。

查看docker版本信息:

# docker version
Client:                     # 客户端信息
 Version:           18.09.5         # docker版本信息
 API version:       1.39            # API接口信息
 Go version:        go1.10.8        # go语言版本信息
 Git commit:        e8ff056
 Built:             Thu Apr 11 04:43:34 2019
 OS/Arch:           linux/amd64     # 操作系统
 Experimental:      false

Server: Docker Engine - Community   # 服务端信息
 Engine:
  Version:          18.09.5         # 引擎版本
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.8
  Git commit:       e8ff056
  Built:            Thu Apr 11 04:13:40 2019
  OS/Arch:          linux/amd64
  Experimental:     false

注意:服务端是一个服务进程,管理着所有容器;客户端则扮演着Docker服务端的远程控制器,可以用来控制Docker的服务端进程。

查看docker系统信息,包括镜像和容器:

# docker info
Client:
 Debug Mode: false
Server:
 Containers: 0              #容器个数
  Running: 0                #正在运行的
  Paused: 0                 #暂停的
  Stopped: 0                #停止的
 Images: 0                  #镜像个数
 Server Version: 19.03.5    #docker版本
 Storage Driver: overlay2   #镜像文件存储类型
  Backing Filesystem: xfs
  Supports d_type: true
  Native Overlay Diff: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: b34a5c8af56e510852c35414db4c1f4fa6172339
 runc version: 3e425f80a8c931f88e6d94a8c831b9d5aa481657
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 3.10.0-514.el7.x86_64      #操作系统内核
 Operating System: CentOS Linux 7 (Core)    #操作系统版本
 OSType: linux                              #操作类型
 Architecture: x86_64                       #架构
 CPUs: 4                                    #CPU
 Total Memory: 15.38GiB                     #内存
 Name: master                               #操作系统主机名
 ID: 7QIJ:DWSP:VGLZ:LMV6:LYPV:BJRJ:MC64:RYNH:23DX:ZH6V:I3U4:FMI3
 Docker Root Dir: /var/lib/docker           #存储docker平台中相关信息
 Debug Mode: false
 Registry: https://index.docker.io/v1/      #加速器
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false

Docker命令补全

yum install -y bash-completion

source /usr/share/bash-completion/completions/docker

添加内核参数

如果在 CentOS 使用 Docker CE 看到下面的这些警告信息:

WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled

image-20221101174803435

请添加内核配置参数以启用这些功能(随便选一组命令即可)。

$ sudo tee -a /etc/sysctl.conf <<-EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
EOF

cat >> /etc/sysctl.conf << EOF
# iptables透明网桥的实现;
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
EOF

# 然后重新加载 sysctl.conf 即可
$ sudo sysctl -p

# 应用 sysctl 参数而不重新启动
sudo sysctl --system

然后在docker info告警消失

开启网络转发功能->以免其他连接不到容器

容器要想访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。

$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

如果为 0,说明没有开启转发,则需要手动打开。

$ sysctl -w net.ipv4.ip_forward=1

如果在启动 Docker 服务的时候设定 --ip-forward=true, Docker 就会自动设定系统的ip_forward参数为 1

开启网络转发功能,默认会自动开启.

# 手动开启:
# vim /etc/sysctl.conf  #插入以下内容
net.ipv4.ip_forward = 1
# sysctl -p   #生效
net.ipv4.ip_forward = 1

# 查看
# cat /proc/sys/net/ipv4/ip_forward
1

否则会报错以下警告:

image-20221101175357329

# 设定iptables,防止Docker将iptables的FORWARD修改为DROP
# sed -i '/^ExecStart.*/aExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT' /usr/lib/systemd/system/docker.service
# 将Docker的网络端口开启,因为这台主要是来给我们build镜像的,因为测试需要,所以开启TCP地址,正常情况下应该做好安全防护
# sed -ri 's@(^ExecStart\>.*)@\1 -H tcp://0.0.0.0:2375@g' /usr/lib/systemd/system/docker.service
# 每次修改System风格程序的管理脚本都得重载配置
# systemctl daemon-reload
# 重启下Docker,让我们的配置生效;
# systemctl restart docker

镜像加速

各厂的镜像加速器

国内从 Docker Hub 拉取镜像有时会遇到困难、十分缓慢,此时可以配置镜像加速器。国内很多云服务商都提供了国内加速器服务,例如:

  • Docker官方提供的中国registry mirror:https://registry.docker-cn.com

  • 阿里云加速器(需要账号登录获取:https://cr.console.aliyun.com/cn-hangzhou/mirrors)

  • 七牛云加速器:https://reg-mirror.qiniu.com/

  • 网易云

  • Azure 中国镜像 https://dockerhub.azk8s.cn

  • DaoCloud

  • 中科大

对于使用systemd的系统,在/etc/docker/daemon.json中写入如下内容(如果文件不存在,就新建文件):

{
    "registry-mirrors": [
        "https://registry.docker-cn.com"                        
    ]
}

由于镜像服务可能出现宕机,建议同时配置多个镜像,最后重新启动服务

systemctl daemon-reload
systemctl restart docker

ustc 中科大的docker源 也是超级好的

官网:https://lug.ustc.edu.cn/wiki/mirrors/help/docker

请在该配置文件中加入(没有该文件的话,请先建一个):

# vim /etc/docker/daemon.json
{
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"]
}

配置阿里云的加速器

如果拉取镜像报错,也可能是加速的原因,可以更换加速

[root@Docker ~]# docker pull centos
Using default tag: latest
Trying to pull repository docker.io/library/centos ... 
latest: Pulling from docker.io/library/centosGet https://registry-1.docker.io/v2/library/centos/manifests/sha256:822de5245dc5b659df56dd32795b08ae42db4cc901f3462fc509e91e97132dc0: net/http: TLS handshake timeout

报错了,因为网络的问题。 无法连接到dockerhub 下载镜像。 如果你的网络没有问题,你可以下载。

解决:换一个docker下载地址

或者: 使用阿里云docker镜像加速,提升pull的速度:

你只需要登录容器Hub服务 https://cr.console.aliyun.com的控制台,使用你的支付宝帐号,第一次登录时,需要设置一个独立的密码,左侧的加速器帮助页面就会显示为你独立分配的加速地址。

image-20221101180054817

配置加速器:

# vim  /etc/docker/daemon.json   #添加以下内容
{
  "registry-mirrors": ["https://qss6jjtw.mirror.aliyuncs.com"]
}

# 有多个的情况下,用逗号隔开:
# vim  /etc/docker/daemon.json   #添加以下内容
{
  "registry-mirrors": ["https://qss6jjtw.mirror.aliyuncs.com","https://qss7jjtw.mirror.aliyuncs.com"]
}

# systemctl daemon-reload
# systemctl restart docker

配置网易的加速器

网易蜂巢的镜像源,个人感觉上面的镜像好少,而且有时候pull他们的镜像还会失败,不过如果要写dockerfile的话,可以到上面参考参考,dockerfile他们到时写的很详细噢

https://c.163.com/hub#/m/home/ 需要先注册登录上,才能打开此站点

image-20221101180208851

配置DaoCloud加速器

daocloud加速器,需要用户注册,每个用户每个月限制10GB

简介:DaoCloud 加速器 是广受欢迎的 Docker 工具,解决了国内用户访问 Docker Hub 缓慢的问题。DaoCloud 加速器结合国内的 CDN 服务与协议层优化,成倍的提升了下载速度。

DaoCloud官网:

https://www.daocloud.io/mirror

https://www.daocloud.io/mirror#accelerator-doc

一条命令加速(记得重启docker)

image-20221101180230693

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io

[root@learn ~]# cat /etc/docker/daemon.json
{
  "registry-mirrors": ["http://f1361db2.m.daocloud.io"]
}

# 重启docker
systemctl restart docker

测试Docker是否安装正确

docker run hello-world

image-20221101180331492

上面是执行成功了,说本地没有hello-world:latest的镜像,去下载了一个镜像,说明网络没有问题

daemon.json

docker安装后默认没有daemon.json这个配置文件,需要进行手动创建。配置文件的默认路径:/etc/docker/daemon.json 如果在daemon.json文件中进行配置,需要docker版本高于1.12.6(在这个版本上不生效,1.13.1以上是生效的)

--config-file命令参数可用于指定非默认位置

**参数 **

daemon.json文件可配置的参数表,我们在配置的过程中,只需要设置我们需要的参数即可,不必全部写出来。详细参考官网。

官方的配置地址:

https://docs.docker.com/engine/reference/commandline/dockerd/#/configuration-reloading

官方的配置地址:https://docs.docker.com/engine/reference/commandline/dockerd/#options

官方的配置地址:

https://docs.docker.com/engine/reference/commandline/dockerd/#/linux-configuration-file

{
    "authorization-plugins": [],
    "data-root": "", #Docker运行时使用的根路径,根路径下的内容稍后介绍,默认/var/lib/docker
    "dns": [],   #设定容器DNS的地址,在容器的 /etc/resolv.conf文件中可查看
    "dns-opts": [], #容器 /etc/resolv.conf 文件,其他设置
    "dns-search": [],  #设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host  主机时,DNS不仅搜索host,还会搜索host.example.com。注意:如果不设置,Docker 会默认用主机上的 /etc/resolv.conf来配置容器。
    "oom-score-adjust": -1000, # 防止 docker 服务 OOM
    "storage-driver": "overlay2",  # docker存储驱动
    "storage-opts": [
        "overlay2.override_kernel_check=true"
    ],
    "exec-opts": ["native.cgroupdriver=systemd"], # 启动时的额外参数, https://www.cnblogs.com/architectforest/p/12988488.html docker默认的Cgroup Driver是cgroupfs,cgroupfs是cgroup为给用户提供的操作接口而开发的虚拟文件系统类型,它和sysfs,proc类似,可以向用户展示cgroup的hierarchy,通知kernel用户对cgroup改动,对cgroup的查询和修改只能通过cgroupfs文件系统来进行
    # 3,为什么要修改为使用systemd?
    # Kubernetes 推荐使用 systemd 来代替 cgroupfs,为什么呢?
    #   因为systemd是Kubernetes自带的cgroup管理器, 负责为每个进程分配cgroups,但docker的cgroup driver默认是cgroupfs,这样就同时运行有两个cgroup控制管理器, 当资源有压力的情况时,有可能出现不稳定的情况
    "exec-root": "",
    "experimental": false, #启用实验功能
    "features": {},
    "storage-driver": "overlay2",#表示使用OverlayFS的overlay2存储驱动
    "storage-opts": ["overlay2.override_kernel_check=true"],
    "labels": [],  #docker主机的标签,很实用的功能,例如定义:–label nodeName=host-121
    "live-restore": true,
    "log-driver": "",
    "log-opts": {},
    "log-level": "", # 容器日志等级
    "mtu": 0,
    "pidfile": "", #Docker守护进程的PID文件
    "cluster-store": "",
    "cluster-store-opts": {},
    "cluster-advertise": "",
    "max-concurrent-downloads": 3,
    "max-concurrent-uploads": 5,
    "default-shm-size": "64M",
    "shutdown-timeout": 15,
    "debug": true, #启用debug的模式,启用后,可以看到很多的启动信息。默认false
    "hosts": [],  #设置容器hosts
    "tls": true,   #默认 false, 启动TLS认证开关
    "tlscacert": "",  #默认 ~/.docker/ca.pem,通过CA认证过的的certificate文件路径
    "tlscert": "",  #默认 ~/.docker/cert.pem ,TLS的certificate文件路径
    "tlskey": "", #默认~/.docker/key.pem,TLS的key文件路径
    "tlsverify": true, #默认false,使用TLS并做后台进程与客户端通讯的验证
    "tls": true,
    "tlsverify": true,
    "tlscacert": "",
    "tlscert": "",
    "tlskey": "",
    "swarm-default-advertise-addr": "",
    "api-cors-header": "",
    "selinux-enabled": false,  # 默认 false,启用selinux支持
    "userns-remap": "",
    "group": "", # Unix套接字的属组,仅指/var/run/docker.sock
    "cgroup-parent": "",
    "default-ulimits": {
        "nofile": {
            "Name": "nofile",
            "Hard": 64000,
            "Soft": 64000
        }
    },
    "init": false,
    "init-path": "/usr/libexec/docker-init",
    "ipv6": false,
    "iptables": false,
    "ip-forward": false, # 默认true, 启用 net.ipv4.ip_forward ,进入容器后使用sysctl -a|grepnet.ipv4.ip_forward查看
    "ip-masq": false,
    "userland-proxy": false,
    "userland-proxy-path": "/usr/libexec/docker-proxy",
    "ip": "0.0.0.0",
    "bridge": "",
    "bip": "",          # 指定docker运行的网段
    "fixed-cidr": "",
    "fixed-cidr-v6": "",
    "default-gateway": "",
    "default-gateway-v6": "",
    "icc": false,
    "raw-logs": false,
    "allow-nondistributable-artifacts": [],
    "registry-mirrors": [],  # 镜像加速的地址,增加后在 docker info中可查看。
    "insecure-registries": [], # 配置docker的私库地址
    "seccomp-profile": "",
    "no-new-privileges": false,
    "default-runtime": "runc",
    "oom-score-adjust": -500,
    "node-generic-resources": ["NVIDIA-GPU=UUID1", "NVIDIA-GPU=UUID2"],
    "runtimes": { # 配置容器运行时
        "cc-runtime": {
            "path": "/usr/bin/cc-runtime"
        },
        "custom": {
            "path": "/usr/local/bin/my-runc-replacement",
            "runtimeArgs": [
                "--debug"
            ]
        }
    },
    "default-address-pools":[{"base":"172.80.0.0/16","size":24},
    {"base":"172.90.0.0/16","size":24}]
}
# 查看参数:
docker info | grep -i storage

Keep containers alive during daemon downtime

$ sudo vim /etc/docker/daemon.yaml
{
  "live-restore": true
}

# 在docker守护进程停机期间保持容器存活,就是即使docker进程停止了,之前运行的容器不会停止
$ sudo dockerd --live-restore

# 只能使用reload重载
# 相当于发送SIGHUP信号量给dockerd守护进程
$ sudo systemctl reload docker

# 但是对应网络的设置需要restart才能生效
$ sudo systemctl restart docker

线上docker优化

参考:https://docs.rancher.cn/rancher2x/install-prepare/best-practices/docker.html

[root@learn ~]# cat /etc/docker/daemon.json
{
    "oom-score-adjust": -1000,
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "10m",
        "max-file": "3"
    },
    "max-concurrent-downloads": 10,
    "max-concurrent-uploads": 10,
    "bip": "172.17.0.1/16",
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"],
    "storage-driver": "overlay2",
    "storage-opts": [
        "overlay2.override_kernel_check=true"
    ],
    "live-restore": true
}


# docker-24.0.7 以下参数不生效了
    "exec-opts": ["native.cgroupdriver=systemd"],
    "storage-opts": [
        "overlay2.override_kernel_check=true"
    ],
# 重启docker
systemctl restart docker

官方建议使用overlay2

image-20221108160234059

修改Docker Root Dir

默认的路径: /var/lib/docker

如果空间不足,会导致程序无法正常使用、日志无法写入、导入docker镜像时,错误提示:磁盘空间不足。

https://blog.51cto.com/u_13293070/2620352

1.查看docker镜像存放目录空间大小
du -sh /var/lib/docker/

2.停止docker服务。
systemctl stop docker

3.查看磁盘容量大的空间,且在上面创建新的docker目录。
df -h
mkdir -p /data/docker/lib

4.迁移/var/lib/docker目录下的文件到新创建的目录/data/docker/lib(注意第一个路径后面一定不要到 “/”,否则是同步的/var/lib/docker/目录里面的内容,不会把docker目录同步过去)
rsync -avz /var/lib/docker /data/docker/lib/

5.编辑 /etc/docker/daemon.json 添加如下参数。在docker 19.xx 版本以后使用data-root来代替graph
{
  "data-root": "/data/docker/lib/docker"
}

6.重新加载docker,并重启docker服务。
systemctl daemon-reload && systemctl start docker

7.检查docker是否变更为新目录/data/docker/lib/docker
[root@localhost ~]# docker info
...
Docker Root Dir: /data/docker/lib/docker

Debug Mode (client): false

Debug Mode (server): false

Registry: https://index.docker.io/v1/
...

8.重启服务器
reboot

9.删掉docker旧目录
rm -rf /var/lib/docker

Docker命令行的操作参数

用户在使用Docker时,需要使用Docker命令行工具docker与Docker daemon建立通信。Docker daemon是Docker守护进程,负责接收并分发执行Docker命令。

img

img

img

Docker命令的结构图,如下图:

image-20221101181010254

命令结构

image-20221101181019956

docker --help 查看操作参数

Usage:  docker [OPTIONS] COMMAND

A self-sufficient runtime for containers

Options:
--config=~/.docker       #客户端配置文件的位置
-D, --debug              #启动调试模式
-H, --host=[]            #守护进程套接字(s)连接到
-h, --help               #打印帮助
-l, --log-level string #Set the logging level ("debug"|"info"|"warn"|"error"|"fatal") (default "info")
--tls                    #使用TLS;implied by --tlsverify
--tlscacert=~/.docker/ca.pem     #仅由此CA签署的信任证书
--tlscert=~/.docker/cert.pem     #TLS证书文件的路径
--tlskey=~/.docker/key.pem       #TLS密钥文件的路径
--tlsverify                      #使用TLS并验证远程
-v, --version                    #打印版本信息并退出
Management Commands:
  builder     Manage builds
  config      Manage Docker configs
  container   Manage containers
  engine      Manage the docker engine
  image       Manage images
  network     Manage networks
  node        Manage Swarm nodes
  plugin      Manage plugins
  secret      Manage Docker secrets
  service     Manage services
  stack       Manage Docker stacks
  swarm       Manage Swarm
  system      Manage Docker
  trust       Manage trust on Docker images
  volume      Manage volumes

Commands:
attach       #进入到正在运行的容器
build        #从Dockerfile构建一个镜像
commit       #从容器的更改中创建一个新的镜像
cp          #在本地文件系统与容器中互相复制 文件/文件夹
create      #创建一个新的容器
diff        #检查容器文件系统上的更改
events      #从服务器获取实时事件
exec        #进入正在运行的容器中运行命令
export      #将容器的文件系统导出为tar存档
history     #显示镜像每一层的大小
images      #镜像列表
import      #从tarball中导入内容以创建文件系统映像
info        #显示系统环境信息
inspect     #检查返回容器,镜像或任务的低级信息
kill        #杀死一个或多个正在运行的容器
load        #从tar归档或STDIN加载镜像
login       #登录到Docker镜像库。
logout      #从Docker镜像库中注销。
logs        #获取容器的日志。默认/var/lib/docker/containers/容器ID/容器ID-json.log
network     #管理Docker网络
node        #管理Docker Swarm节点
pause       #暂停一个或多个容器内的所有进程
port        #列出端口映射或容器的特定映射
ps          #容器列表
pull        #从镜像库中拉出镜像或存储库
push        #推送镜像或存储库到镜像库
rename      #重命名一个容器
restart     #重启一个容器
rm          #删除一个或多个容器
rmi         #rmi删除一个或多个镜像
run         #在新容器中运行命令
save        #将一个或多个镜像保存到tar归档文件(默认流式传输到STDOUT)
search      #在Docker Hub中搜索镜像
service     #管理Docker服务
start       #启动一个或多个停止的容器
stats      #信息显示容器资源使用的实时统计信息
stop      #停止一个或多个运行容器
swarm     #管理Docker Swarm
tag       #将镜像标记到存储库中
top       #显示容器的运行过程
unpause   #暂停取消暂停一个或多个容器内的所有进程
update    #更新一个或多个容器的配置
version   #显示Docker版本信息
volume    #管理Docker卷
wait      #阻塞直到容器停止,然后打印其退出代码

#对单个子命令执行help可以查看此子命令的详细用法,如下面对rm执行查看:
# docker rm --help
Usage:  docker rm [OPTIONS] CONTAINER [CONTAINER...]
Remove one or more containers
Options:
  -f, --force     Force the removal of a running container (uses SIGKILL)
      --help      Print usage
  -l, --link      Remove the specified link
  -v, --volumes   Remove the volumes associated with the container

docker run常用选项:

参考:https://www.cnblogs.com/xiangsikai/p/9628479.html

run  运行容器
--name 指定容器的名字
-h 指定容器主机名
-i   以交互模式运行容器,通常与 -t 同时使用;
-t  为容器重新分配一个伪输入终端,通常与 -i 同时使用->-it;打算进入bash执行一些命令并查看返回结果,因此需要交互式终端
-d  后台运行容器,并返回容器ID;
-c  后面跟待完成的命令,docker exec xxx /bin/sh -c "aaa --version" # xxx表示容器名,aaa表示可执行文件
-p(小写字母p)映射指定端口,-p 宿主机端口:容器端口【-p 20022:22】 
-P (大写字母P) 映射随机端口
-w /myapp  指定容器的/myapp目录为工作目录
-v 绑定存储卷 -> -v 宿主机目录:容器里面的目录[:ro/rw];只能创建bind mount,实例中的rw为读写,ro为只读
-e 设置环境变量,如:-e mysql_user=root  ->就可以在容器里面或Dockfile里面引用这个变量
--rm 停止容器后,自动删除该容器;如果有挂载卷也会一并删除,建议只在测试时使用
--network 指定网络 [none、host、bridge]
--volumes-from
--dns dns配置
/bin/bash:放在镜像名后是命令,这里是希望是交互式,因此用/bin/bash。需要看容器里的基础镜像支持什么shell
/bin/sh:放在镜像名后是命令,这里是希望是交互式,因此用/bin/sh
pull 拉取(下载)镜像

image-20221101181212027

Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]   

-d, --detach=false         指定容器运行于前台还是后台,默认为false    
-i, --interactive=false    打开STDIN,用于控制台交互   
-t, --tty=false            分配tty设备,该可以支持终端登录,默认为false   
-u, --user=""              指定容器的用户,覆盖容器中内置的账号   
-a, --attach=[]            登录容器(必须是以docker run -d启动的容器) 
-w, --workdir=""           指定容器的工作目录  
-c, --cpu-shares=0         设置容器CPU权重,在CPU共享场景使用   
-e, --env=[]               指定环境变量,容器中可以使用该环境变量   
-m, --memory=""            指定容器的内存上限   
-P, --publish-all=false    指定容器暴露的端口   
-p, --publish=[]           指定容器暴露的端口  
-h, --hostname=""          指定容器的主机名
--add-host list             Add a custom host-to-IP mapping (host:ip) (在多机可以使用--bridge模式部署rabbitmq集群,https://blog.csdn.net/sanduo112/article/details/120074642)  
-v, --volume=[]            给容器挂载存储卷,挂载到容器的某个目录   
--volumes-from=[]          给容器挂载其他容器上的卷,挂载到容器的某个目录 
--cap-add=[]               添加权限,权限清单详见:http://linux.die.net/man/7/capabilities   
--cap-drop=[]              删除权限,权限清单详见:http://linux.die.net/man/7/capabilities   
--cidfile=""               运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法   
--cpuset=""                设置容器可以使用哪些CPU,此参数可以用来容器独占CPU   
--device=[]                添加主机设备给容器,相当于设备直通   
--dns=[]                   指定容器的dns服务器   
--dns-search=[]            指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件   
--entrypoint=""            覆盖image的入口点   
--env-file=[]              指定环境变量文件,文件格式为每行一个环境变量   
--expose=[]                指定容器暴露的端口,即修改镜像的暴露端口   
--link=[]       指定容器间的关联,使用其他容器的IP、env等信息   
--lxc-conf=[]   指定容器的配置文件,只有在指定--exec-driver=lxc时使用   
--name=""    指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字   
--network="bridge"             容器网络设置: 
    bridge 使用docker daemon指定的网桥      
    host    //容器使用主机的网络   
    container:NAME_or_ID  >//使用其他容器的网路,共享IP和PORT等网络资源   
    none 容器使用自己的网络(类似--net=bridge),但是不进行配置  
--privileged=false  指定容器是否为特权容器,特权容器拥有所有的capabilities   
--restart="no"     指定容器停止后的重启策略( default "no"                no:容器退出时不重启   
                on-failure:容器故障退出(返回值非零)时重启  
                always:容器退出时总是重启   
--rm=false    指定容器停止后自动删除容器(不支持以`docker run -d`启动的容器)   
--sig-proxy=true    设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理
--mount mount                    将文件系统挂载到容器上,参数--mount默认情况下用来挂载volume,但也可以用来创建bind mount和tmpfs。如果不指定type选项,则默认为挂载volume,volume是一种更为灵活的数据管理方式,volume可以通过docker volume命令集被管理

示例:

docker run --name $CONTAINER_NAME -it \
--mount type=bind,source=$PWD/$CONTAINER_NAME/app,destination=/app \
--mount source=${CONTAINER_NAME}-data,destination=/data,readonly \
avocado-cloud:latest /bin/bash

注释:

  • 挂载volume命令格式:[type=volume,]source=my-volume,destination=/path/in/container[,...]
  • 创建bind mount命令格式:type=bind,source=/path/on/host,destination=/path/in/container[,...]
  • 如果创建bind mount并指定source则必须是绝对路径,且路径必须已经存在
  • 示例中readonly表示只读

差异总结

对比项 bind mount volume
Source位置 用户指定 /var/lib/docker/volumes/
Source为空 覆盖dest为空 保留dest内容
Source非空 覆盖dest内容 覆盖dest内容
Source种类 文件或目录 只能是目录
可移植性 一般(自行维护) 强(docker托管)
宿主直接访问 容易(仅需chown) 受限(需登陆root用户)

创建bind mount时使用--volume--mount的比较

对比项 --volume-v --mount type=bind
如果主机路径不存在 自动创建 命令报错

根据命令的用途进行分类:

子命令分类 子命令
Docker环境信息 info、version
容器生命周期管理 create、exec、kill、pause、restart、rm、run、start、stop、unpause
镜像仓库命令 login、logout、pull、push、search
镜像管理 build、images、import、load、rmi、save、tag、commit
容器运维操作 attach、export、inspect、port、ps、rename、stats、top、wait、cp、diff
系统日志信息 events、history、logs

常见docker清理方法

新版的 docker 终于支持对无用数据的清除了,主要有以下命令:

docker image prune :删除无用的镜像。
docker container prune :删除无用的容器。
docker volume prune :删除无用的卷。
docker network prune :删除无用的网络。
docker system df :类似于Linux上的df命令,查看Docker 磁盘使用情况
docker system df -v:查看Docker 磁盘使用情况详情
docker system prune :可以用于清理磁盘,删除关闭的容器、无用的数据卷和网络,以及dangling镜像(即无tag的镜像)

# docker system prune
WARNING! This will remove:
  - all stopped containers      #删除关闭的容器
  - all networks not used by at least one container     #无用的数据卷和网络
  - all dangling images  #无tag的镜像
  - all dangling build cache     #cache清理

Are you sure you want to continue? [y/N] y


要强制删除可加上 -f 参数

docker system prune -a  (慎用)清理得更加彻底,可以将没有容器使用Docker镜像都删掉。注意,这两个命令会把你暂时关闭的容器,以及暂时没有用到的Docker镜像都删掉了…所以使用之前一定要想清楚.。我没用过,因为会清理没有开启的 Docker镜像

推送镜像脚本

# cat pushimages.sh
#!/bin/bash
RepositoryAddr='dockerhub.koal.com:5000/idaas/'
NewRepositoryAddr='registry.cn-shanghai.aliyuncs.com/mefor/xwarm64'
ImageName=$1
Tag=$2

##set color##
echoRed() { echo $'\e[0;31m'"$1"$'\e[0m'; }
echoGreen() { echo $'\e[0;32m'"$1"$'\e[0m'; }
echoYellow() { echo $'\e[0;33m'"$1"$'\e[0m'; }

function Usage(){
  echoYellow "Usage:"
  echoGreen "  /bin/bash $0 [app/id/gw/sso/pdp/web/all] [version]"
  echoYellow "  for example : $0 app 2.0.0"
  return 0
}

if [[ "$#"q != "2"q ]]
then
  echoRed "input error …………"
  Usage
  exit 6  
fi

#Changing the image name
if [ "${ImageName}"x = 'app'x ] || [[ "${ImageName}"x = 'app-platform-integration'x ]]; then
    ImageName='app-platform-integration'
    NewTagPrefix='app-'
elif [ "$1"x = 'id'x  -o "$1"x = 'id-integration'x ]; then
    ImageName='id-integration'
    NewTagPrefix='id-'
elif [ "$1"x = 'gw'x -o "$1"x = 'gateway'x ]; then
    ImageName='gateway'
    NewTagPrefix='gw-'
elif [ "$1"x = 'sso'x -o "$1"x = 'authn-integration'x ]; then
    ImageName='authn-integration'
    NewTagPrefix='sso-'
elif [ "$1"x = 'pdp'x -o "$1"x = 'policy-services'x ]; then
    ImageName='policy-services'
    NewTagPrefix='pdp-'
elif [ "$1"x = 'web'x -o "$1"x = 'idaas-admin-integration'x ]; then
    ImageName='idaas-admin-integration'
    NewTagPrefix='web-'
elif [ "$1"x = 'all'x ]; then
    AImageName=("app-platform-integration" "id-integration" "gateway" "authn-integration" "policy-services" "idaas-admin-integration")
    #ANewTagPrefix=("app-" "id-" "gw-" "sso-" "pdp-" "web-")
else
    echoRed "Input ImageName Error"
    echoRed "Exit immediately"  && exit 3
fi 

function GoGo() {
    echoYellow "update ${ImageName} images"
    echoYellow "modify tag"
    echoYellow "${RepositoryAddr}${ImageName}:${Tag} ${NewRepositoryAddr}:${NewTagPrefix}${Tag}"
    docker tag ${RepositoryAddr}${ImageName}:${Tag} ${NewRepositoryAddr}:${NewTagPrefix}${Tag} && echoGreen "Succeeded in modifying the tag" || { echoRed "Failed to modify tag" && echoRed "Exit immediately"  && exit 2 
    }
    echoYellow "Push a new image to a new repository"
    echoYellow "${NewRepositoryAddr}:${NewTagPrefix}${Tag}"
    docker push ${NewRepositoryAddr}:${NewTagPrefix}${Tag}
    echoYellow "Delete the mirror of the new tag"
    docker rmi ${NewRepositoryAddr}:${NewTagPrefix}${Tag}
}
case "$ImageName" in 
app|app-platform-integration|id|id-integration|gw|gateway|sso|authn-integration|pdp|policy-services|web|idaas-admin-integration)
    GoGo 2>&1 | tee ./GoGoLog.txt
    ;;
all)
    {
    echoYellow "update all images"
    #echo ${AImageName[*]}
    #echo ${ANewTagPrefix[@]}
    #echo ${!ANewTagPrefix[@]}
    for ImageName in ${AImageName[*]};do
        if [[ "${ImageName}"x = 'app-platform-integration'x ]]; then
            ImageName='app-platform-integration'
            echo "ImageName=$ImageName"
            NewTagPrefix='app-'
            echo "NewTagPrefix=$NewTagPrefix"
            GoGo
        elif [[ "${ImageName}"x = 'id-integration'x ]]; then
            ImageName='id-integration'
            echo "ImageName=$ImageName"
            NewTagPrefix='id-'
            echo "NewTagPrefix=$NewTagPrefix"
            GoGo
        elif [[ "${ImageName}"x = 'gateway'x ]]; then
            ImageName='gateway'
            echo "ImageName=$ImageName"
            NewTagPrefix='gw-'
            echo "NewTagPrefix=$NewTagPrefix"
            GoGo
        elif [[ "${ImageName}"x = 'authn-integration'x ]]; then
            ImageName='authn-integration'
            echo "ImageName=$ImageName"
            NewTagPrefix='sso-'
            echo "NewTagPrefix=$NewTagPrefix"
            GoGo
        elif [[ "${ImageName}"x = 'policy-services'x ]]; then
            ImageName='policy-services'
            echo "ImageName=$ImageName"
            NewTagPrefix='pdp-'
            echo "NewTagPrefix=$NewTagPrefix"
            GoGo
        elif [[ "${ImageName}"x = 'idaas-admin-integration'x ]]; then
            ImageName='idaas-admin-integration'
            echo "ImageName=$ImageName"
            NewTagPrefix='web-'
            echo "NewTagPrefix=$NewTagPrefix"
            GoGo
        else
            echoRed "Error defining mirror name"
            echoRed "Exit immediately"  && exit 3 
        fi
    done
    } 2>&1 | tee ./GoGoLog.txt
    ;;
*)
    echoRed "input error …………"
    Usage
    ;; 
esac

Docker疑难杂症汇总

https://www.escapelife.site/posts/43a2bb9b.html#!

https://mp.weixin.qq.com/s/JuUULOE-axo7wFzijt1l_Q

Docker 镜像

镜像Docker的三大组件之一:

Docker运行容器前需要本地存在对应的镜像,如果本地不存在该镜像,Docker会从镜像仓库下载该镜像。

什么是Docker镜像?

详解链接:

http://www.infoq.com/cn/articles/analysis-of-docker-file-system-aufs-and-devicemapper/        

Docker镜像含有启动Docker容器所需的文件系统结构及其内容,因此是启动一个Docker容器的基础。Docker镜像采用分层的结构构件,最底层是bootfs,之上的部分是roofs,其租住结果如下图:

image-20221101181725358

boofs:

bootfs是Docker镜像最底层的引导文件系统,包括bootloader和操作系统内核,类似于传统的Linux/Unix引导文件系统。然而,Docker用户很少有机会直接与bootfs打交道,并且在容器启动完毕之后,为了节省内存空间,bootfs将会被卸载。

rootfs

rootfs位于bootfs之上,是Docker容器在启动时内部进程可见的文件系统即Docker容器的根目录。rootfs通常包含一个操作系统运行所需的文件系统,例如可能包含典型的类Unix操作系统中的目录系统,如/dev,/tmp等及运行Docker容器所需的配置文件、工具等。

在Docker的架构中,当Docker daemon为Docker容器挂载rootfs时,沿用了Linux内核启动时的方法,即将rootfs设置为只读模式。在挂载完毕之后,利用联合挂载(union mount)技术在已有的只读rootfs上再挂载一个读写层。这样,可读写层处于Docker容器文件系统的最顶层,其下可能联合挂载多个读写层,并隐藏只读层中的老版本文件,这样的技术被称为写时复制(Copy On Write)。联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。

image-20221101181808355

​ 如上图,实现这种联合挂载技术的文件系统通常被称为联合文件系统(union filesystem)。由于初始挂载时读写层为空,所以从用户的角度看,改容器的文件系统与底层的rootfs没有差别。当需要修改镜像内的某个文件时,只对处于最上方的读写层进行了变动,不覆盖下层已有文件系统的内容,已有文件在只读层中的原始版本仍然存在,但会被读写层中的新文件所隐藏,当docker commit这个修改过的容器文件系统为一个新的镜像时,保存内容仅为最上层读写文件系统中被更新过的文件。

镜像的存储组织方式

image-20221101181853056

上图是一个完整的、在运行的容器的所有文件系统结构的描述。上图体现了镜像的层级结构,里面有volume、init-layer、可读写层这些概念。可读写层(read-write Layer以及volume)、init-layer、只读层(read-only layer)这3层结构共同组成一个容器所需的下层文件系统,他们通过联合挂载的方式巧妙的表现为一层,使得容器进程对这些层的存在一点都不知道。

Docker镜像系统实际分成了分多层,任何一层和它直接或间接依赖的所有层构成一个镜像。镜像分层机制提供的复用特性使得存储一个新的镜像时只需要保存修改的内容而不是全部文件。

镜像关键概念

registry

每个docker容器都将从Docker镜像生成。registry用以保存Docker镜像,其中还包括镜像层次结构和关于镜像的元数据,可以将registry理解为类似于Git仓库之类的实体。

除了使用官方的公用registry(即Git Hub),它是由Docker公司维护的一个公共镜像仓库,其中包含了超过45000个公共镜像,供用户下载使用。Docker Hub中有两种类型的仓库,及用户仓库(user repository)与顶层仓库(top-level repository)。用户仓库由普通的Docker Hub用户创建,顶层仓库由Docker公司负责维护,提供官方版本镜像。理论上,顶层仓库中的镜像经过Docker公司验证,被认为是架构良好且安全的。

repository

repository即由具有某个功能的Docker镜像的所有迭代版本构成的镜像库。registry由一系列经过命名的repository组成,可以通过命名规范对用户仓库和顶层仓库进行区分。用户仓库的命名由用户名和库名两部分组成,中间以"/"隔开,即username/repository_name的形式,库名通常表示镜像所具有的功能,如:centos7-ansible ;而顶层仓库则只包含库名的部分,如:centos(可以通过#docker search centos来查看)。

registry是repository的集合,repository是镜像的集合。

index

registry负责存储和提供真正的镜像,而index则类似于registry的索引,负责管理用户账号、访问权限认证、搜索镜像以及为镜像打标签等事务。

当用户执行docker search时,真正搜索的是index,而非registry。当执行docker push或docker pull时,将由index判断使用者是否有拉取或推送相应镜像及访问registry的权限,而registry则是将需要存储或拉取镜像存储的实际位置。 

graph

从registry中下载的Docker镜像需要保存在本地,这一功能由Docker graph完成。

在本graph的本地目录/var/lib/docker/devicemapper/devicemapper中,保存了每一个下载到本地的Docker镜像的主体文件,在/var/lib/docker/image目录下面保存了每一个下载到本地的Docker镜像的元数据,包括有json与layersize。其中json文件记录了相应Docker镜像的ID、依赖关系、创建时间和配置信息等,layersize为Docker镜像的大小。

Dockerfile

Dockerfile是在通过docker build命令构建自己的Docker镜像时需要使用到的定义文件。它允许用户使用基本的DSL语法来定义Docker镜像,每一条指令描述了构建镜像的步骤

Docker镜像的操作

Docker registry是存储器镜像的仓库,用户可以通过Docker client与Docker registry进行通信,以此来完成镜像的搜索、下载和上传等相关操作。Docker HUb是由Docker公司在互联网上提供的一个镜像仓库,提供镜像的公有与私有存储服务,它是用户最主要的镜像来源。除了Docker Hub外,用户还可以自行搭建私有服务器来实现镜像仓库的功能

搜索镜像:

使用docker search命令可以搜索远端仓库中共享的镜像,默认搜索Docker Hub官方仓库中的镜像。

Usage:  docker search [OPTIONS] TERM
从docker hub上搜索符合条件的images

# docker search --help

用法:docker search [OPTIONS] TERM
选项:
-f, --filter value    #根据提供的条件过滤输出(默认[])
--limit int          #搜索结果的最大数目(默认为25)
--no-index         #不要截断输出
--no-trunc         #不要截断输出
# docker search mysql  #搜索带mysql关键字的镜像

image-20221101182012931

解释:
NAME:image名字
DESCRIPTION:image描述                             
STARS:受欢迎受欢迎程度 
OFFICIAL:是否官方提供,如果OFFICIAL [ok] ,说明可以放心使用。

# docker search mysql --filter=stars=5  #仅显示评价为5星以上的镜像
# docker search mysql -f=stars=5
# docker search mysql -f stars=5

获取镜像(没有指定tag,则默认下载latest)

镜像是docker运行容器的前提。docker pull命令从网络上下载镜像。

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

具体选项通过docker pull --help命令可以查看。
用法:docker pull [OPTIONS] NAME[:TAG|@DIGEST]
选项:
-a, --all-tags   #下载存储库中的所有标记的镜像
--disable-content-trust  #跳过镜像验证(默认为true)
--help         #打印帮助

对Docker镜像来说,如果不显示地指定TAG,则默认会选择latest标签,即下载仓库中最新版本的镜像

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

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

方法1:从公网docker hub 拉取(下载)image pull:拉

      Usage:    docker pull [OPTIONS] NAME[:TAG|@DIGEST]
或者: Usage:  docker image pull [OPTIONS] NAME[:TAG|@DIGEST]

eg:
[root@Docker ~]# docker pull centos #如果报错了,因为网络的问题,那么配置镜像加速,再下载,就可以了。

image-20221102103309501

eg:

# docker pull ubuntu   # 下载最新版的ubuntu镜像

image-20221102103404110

上图为从默认的注册服务器的ubuntu仓库下载标记为latest的镜像。

Downloading行首的子串代表各个层的ID。下载过程中会获取并输出镜像的各层信息。层(Layer)其实是AUFS(一种联合文件系统)中的重要概念,是实现增量保存与更新的基础

方法2:导入把之前下载好的image镜像:

把docker.io-centos.tar  镜像上传到linux上
参数:  -i " docker.io-centos.tar  " 指定载入的镜像归档。
[root@haha ~]# docker load -i  /root/docker.io-centos.tar   

方法3:下载其他站点的镜像(指定仓库下载镜像)

[root@haha ~]# docker pull hub.c.163.com/library/tomcat:latest
[root@haha ~]# docker images 
REPOSITORY                     TAG           IMAGE ID            CREATED             SIZE
hub.c.163.com/library/tomcat   latest         72d2be374029        4 months ago        292.4 MB

~~docker pull dl.~~~~dockerpool.com:5000/ubuntu~~

可以从其他注册服务器的仓库下载,但是需要在仓库的名称前指定完整的仓库注册服务器地址。例如这个dockerpool社区的镜像源

查看镜像信息:

使用`docker images`命令可以列出本地主机上已有的镜像。
      Usage:  docker image COMMAND
或者: Usage:  docker images [OPTIONS] [REPOSITORY[:TAG]]

# docker images --help
用法:docker images [OPTIONS] [REPOSITORY[:TAG]]
选项:
-a, --all   # 显示所有的镜像,默认只列出最顶层的镜像
    --digests  # 显示摘要
-f, --filter value   # 根据提供的条件过滤输出(默认[])
    --format string  # 漂亮的打印镜像使用Go模板
    --help           # 打印帮助
    --no-trunc      # 不要截断输出
-q, --quiet        # 只显示镜像ID

eg:
# docker images  # 列出主机上面已有的镜像

image-20221102103742785

各个选项说明:

  • REPOSITORY:是来自于哪个仓库,这里可以看出都是来自于官方镜像库

  • TAG:镜像的标签信息,用来标记来自于同一个仓库的不同镜像。通过TAG信息来区分发行版本。也就是镜像的标签信息

  • IMAGE ID:这是一个唯一值,也就是镜像的唯一ID号

  • CREATED:镜像创建时间

  • SIZE:镜像大小

同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本,镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个标签;如ubuntu仓库源里,有15.10、14.04等多个不同的版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。

eg:
[root@Docker ~]#docker image ls  #列出本地可用镜像。其中 [name] 对镜像名称进行关键词查询。

[root@Docker ~]#docker image ls -a #列出本地所有镜像。包括<none>的镜像

eg:
# docker run -it --rm ubuntu:18.04 bash

image-20221102103846820

镜像体积:

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

列出镜像

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

你可以通过以下命令来便捷的查看镜像、 容器、 数据卷所占用的空间

[root@Docker ~]#docker system df
TYPE             TOTAL               ACTIVE              SIZE                RECLAIMABLE
Images            6                   3                   620.1MB             449.8MB (72%)
Containers          4                   0                   201.5MB             201.5MB (100%)
Local Volumes       0                   0                   0B                  0B
Build Cache         0                   0                   0B                  0B

3.虚悬镜像

上面的镜像列表中, 还可以看到一个特殊的镜像, 这个镜像既没有仓库名, 也没有标签, 均为

image-20221102104039415

这个镜像原本是有镜像名和标签的,原来为 mongo:3.2 ,随着官方镜像维护, 发布了新版本后, 重新docker pull mongo:3.2 时, mongo:3.2 这个镜像名被转移到了新下载的镜像身上, 而旧的镜像上的这个名称则被取消, 从而成为了 。 除了 docker pull可能导致这种情况,docker build 也同样可以导致这种现象。 由于新旧镜像同名, 旧镜像名称被取消, 从而出现仓库名、 标签均为 的镜像。 这类无标签镜像也被称为 虚悬镜像(dangling image) , 可以用下面的命令专门显示这类镜像:

[root@Docker ~]#docker image ls -f dangling=true
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              03966321a4eb        19 hours ago        318MB

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

[root@Docker ~]#docker image prune      # 删除无用的镜像
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B

4.中间镜像层

为了加速镜像构建,重复利用资源,Docker会利用中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的docker image ls列表中只会显示顶层镜像。如果希望显示包括中间层镜像在内的所有镜像,需要加上-a参数。

[root@localhost ~]# docker image ls -a

这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些标签的镜像很多都是中间层镜像,是其他镜像所依赖的镜像。这些无标签镜像 不应该删除,否则会导致上层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为相同的层只会存一次,而这些镜像时别的镜像的依赖,因此并不会因为他们被列出来而多存了一份,无论如何也会需要它们。只要删除那些依赖他们的镜像后,这些依赖的中间层镜像也会被连带删除。

5.列出部分镜像

不加任何参数,docker image ls会列出所有顶级镜像。

(1)根据仓库名列出镜像

[root@localhost ~]# docker image ls ubuntu

image-20221102104410210

列出某个特定镜像,指定仓库名和标签

[root@localhost ~]# docker image ls ubuntu:18.04

image-20221102104434342

docker image ls还支持强大的过滤器参数,--filter 或 -f。比如希望看到ubuntu:18.04之后建立的镜像,可以使用以下命令:

[root@localhost ~]# docker image ls -f since=ubuntu:18.04

想看之前的,可以把since换成before即可

注意:之前、之后是指创建时间之前或之后

(2)以特定格式显示

默认情况,docker image ls会输出一个完整表格。有时只需要列出ID,然后交个docker image rm命令作为参数删除指定镜像,可以使用-q

[root@localhost ~]# docker image ls -q

image-20221102104553999

[root@localhost ~]# docker image ls -q nginx

image-20221102104613590

--filter配合-q产生出指定范围ID的列表,然后送给另一个docker命令作为参数。

有些时候,对表格结构不满意,希望自己组织列;或者不希望有标题,方便其他程序解析结果等。这就用到就用到了Go的模版语法。

比如:下面的命令会直接列出镜像结果,只包含镜像ID和仓库名

[root@localhost ~]# docker image ls --format "{{.ID}}:{{.Repository}}"

image-20221102104715625

或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列

[root@localhost ~]# docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"

image-20221102104748176

6.查看镜像详细信息

用户可以根据镜像的唯一标识ID号,获取镜像详细信息。

语法:docker inspect [OPTIONS] NAME|ID [NAME|ID...]
[root@localhost ~]# docker inspect fb2  <==查看镜像

image-20221102104847061

获取镜像的详细信息:

docker inspect 命令可以查看镜像和容器的详细信息,默认会列出全部信息

# docker inspect --help
用法:docker inspect [OPTIONS] CONTAINER|IMAGE|TASK [CONTAINER|IMAGE|TASK...]
-f, --format  # 可以通过此参数指定输出的模板格式,以便输出特定信息。
-s, --size    # 如果类型是容器,则显示总文件大小
--type        # 回指定类型的JSON(例如镜像,容器或任务)

# docker inspect a2a    #获取ID号镜像的详细信息
[
    {
        "Id": "sha256:a2a15febcdf362f6115e801d37b5e60d6faaeedcb9896155e5fe9d754025be12",
        "RepoTags": [
            "ubuntu:17",
            "ubuntu:latest"
        ],
# 上面是JSON输出信息,内容比较多,只粘贴了部分内容。
# docker inspect -f {{".Architecture"}}  a2a  #可以指定{{".key"}}的形式只显示指定的值
# docker inspect -f {{".Architecture"}}  a2    #当然也可以只截取ID的前一部分

给镜像打标签:

Usage:  docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
语法:docker tag 原名称:[标签] 新名称:[新标签]
或者:docker tag 原镜像的IMAGE ID 新名称:[新标签]
eg:
[root@localhost ~]# docker image ls

image-20221102105013465

[root@localhost ~]# docker tag centos:latest centos:6
[root@localhost ~]# docker image ls

image-20221102105042587

image-20221102105049500

eg:

也可以利用镜像的ID来进行修改:

image-20221102105103765

eg:
# docker tag a2a ubuntu:17  # 指定镜像然后给打个新的标签是ubuntu:17
# docker images

image-20221102105121519

注意也就第一列和第二列不一样,后面全是一样的(如ID是一个),这说明镜像还是原来的镜像,只是多了一个标签而已。也就是它们实际上指向了同一个镜像文件,只是别名不同而已。标签在这里起到了引用或快捷方式的作用

删除镜像:(2种)

docker rmi

使用镜像的标签删除镜像,使用docker rmi命令可以删除镜像.

# docker rmi  镜像ID      # 会尝试删除所有指向该镜像的标签,然后删除该镜像文件本身。

# docker rmi ubuntu:17      # 删除掉ubuntu:17的镜像
Untagged: ubuntu:17

同一个镜像拥有多个标签的时候,这个操作对本地的镜像不会有影响,docker rmi命令只是删除那个标签而已,但是当剩余一个标签的时候,再执行就是删除该镜像了。

需要注意的是,使用rmi命令删除镜像时,如果已有基于该镜像启动的容器存在,则无法直接删除,需要首先删除容器。当然,如果再加上-f, --force参数的话,就强制删除存在容器的镜像

# 删除所有镜像:
# docker images -aq | sed 's#\(.*\)#docker image rm \1  --force#g' | bash

docker image rm

docker image rm [选项] <镜像1> [<镜像2> ...]
ps:<镜像> 可以是 镜像短 ID  镜像长 ID  镜像名 或者 镜像摘要
eg:利用IMAGE ID删除(前提是ID唯一)
[root@haha ~]#docker image rm 9f38484d220f

image-20221102105357157

如果IMAGE ID不唯一,会报如下错误:

image-20221102105410702

Error response from daemon: conflict: unable to delete 67fa590cfc1c (must be forced) - image is referenced in multiple repositories

eg:利用镜像的仓库源:tag(也就是镜像名)进行删除

[root@haha ~]#docker image rm centos:latest

image-20221102105438549

eg:

1.用ID、镜像名、摘要删除镜像

可以使用docker image rm命令
语法:docker image rm [选项] <镜像1> [<镜像2> ……]
其中<镜像>可以是长ID或者短ID、镜像名或者镜像摘要。

[root@localhost ~]# docker image ls

image-20221102105504056

可以使用镜像的完整ID,也成为长ID,来删除镜像。也可以使用短ID,docker image ls默认列出已经是短ID,一般取前3个字符以上,就足够区分镜像了。

比如这里,如果要删除redis:alpine镜像,可以执行:

[root@localhost ~]# docker image rm c8e

image-20221102105541580

也可使用镜像摘要来删除镜像

[root@localhost ~]# docker image ls --digests

image-20221102105603886

2.Untagged和Deleted

删除镜像的行分位两类,一类是Untagged,另一类是Deleted。镜像的唯一标识符是ID和摘要,而一个镜像可以有多个标签。

因此当我们使用删除镜像时,实际上是要求删除某个标签的镜像。所以首先要做的是将满足要求的所有镜像的标签都取消,这就是看到的Untagged的信息。因为一个镜像可以对应多个标签,因此当删除了指定的标签后,可能还有别的标签指向此镜像,如果是这种情况,那么Delete行为就不会发生。并非所有的docker image rm都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。如上删除ubuntu:latest。

当该镜像所有标签都被取消,该镜像就失去了存在意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其他镜像正在依赖于当前镜像的某一层。这种情况依旧不会触发删除该层的行为。这也是为什么有时候发现所删除的层数和自己docker pull看到的层数不一样。

除了镜像依赖外,还需要注意容器对镜像的依赖。如果有用该镜像启动的容器存在(即使该容器没有运行),那么同样不可以删除这个镜像。因为容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像被删除,那么必然会导致故障。如果容器是不需要的,应该先将他们删除,再来删除镜像。

3.用docker image ls命来来配合

可以使用docker image ls -q来配合使用docker image rm,这样可以删除多个镜像。

eg:

(a)删除所有仓库名为redis的镜像

[root@localhost ~]# docker image rm $(docker image ls -q redis)

image-20221102105703529

(b)删除所有在redis:latest之间的镜像

[root@localhost ~]# docker image ls

image-20221102105723046

[root@localhost ~]# docker image rm $(docker image ls -q -f before=redis:latest)

image-20221102105741463

导出和导入镜像

可以使用docker savedocker load命令来存储和载入镜像。

导出

保存 Image  tar 包
语法:docker save -o 导出的镜像名.tar  本地镜像名:镜像标签

eg:
# docker save -o /opt/hello-world.tar hello-world:latest  #将镜像存储到本地的/opt目录下名称为hello-world.tar

# ll -h /opt/hello-world.tar
-rw-------. 1 root root 13K Sep 12 14:44 /opt/hello-world.tar

# docker save > /opt/hello-world.tar hello-world:latest     #当然这种方式也是可以的

导入

# docker rmi -f hello-world:latest          #先删除本地镜像,然后进行导入

# docker load -i /opt/hello-world.tar   #载入镜像,-i, --input后面跟要载入的镜像tar包

# docker load < /opt/hello-world.tar   #当然这种方式也是可以的

发布image

可以使用docker push命令上传镜像到仓库,默认上传到DockerHub官方仓库(需要登录),命令格式为docker push NAME[:TAG].用户在DockerHub网站注册后,即可上传自制的镜像。

# docker tag test:latest user/test:latest # 必须先添加新的标签

# docker push user/test:latest

方法1:Push Image To Docker Hub

方法1:Push Image To Docker Hub  发布到Docker Hub
1、Signup on docker hub & create repo    注册一个帐号
https://hub.docker.com/
2、Login to docker hub 
Usage:  docker login [OPTIONS] [SERVER]
# docker login -u userabc -p abc123 -e userab@gmail.com     # 登录docker hub
3、Push image to docker hub   # 上传镜像
# docker push centos:httpd
4、Pull image from docker hub   # 下载镜像
# docker pull userabc/centos:httpd     #  用户名/镜像名

本地存储的镜像越来越多,就需要指定一个专门存放这些镜像------仓库。默认是上传到Docker Hub官方仓库,需要注册使用公共仓库的帐号,可以使用docker login命令来输入用户名、密码和邮箱来完成注册和登录。在上传镜像之前还需要对本地镜像修改变迁,然后在用docker push命令上传。

语法:docker push 仓库名称:标签

[root@localhost ~]# docker tag ubuntu:latest 1014068504/ubuntu:zx 
[root@localhost ~]# docker login

image-20221102110034856

[root@localhost ~]# docker push 1014068504/ubuntu:zx

image-20221102110052676

注意:一定要修改为新标签名,加上账户名(username):账户名/仓库:标签。

方法2: 使用阿里云的私有仓库来发布

创建镜像:

创建镜像的方法有三种:基于已有镜像的容器创建、基于本地模板导入、基于Dockerfile创建。

基于已有镜像的容器创建:

# docker commit --help  #使用方法如下

用法:docker commit [OPTIONS] CONTAINER_ID [REPOSITORY[:TAG]]
选项:
-a, --author string   #作者的信息,(例如“John Hannibal Smith <hannibal@a-team.com>”)
-c, --change value    #将Dockerfile指令应用于创建的映像(默认[])
-m, --message string  #提交信息
-p, --pause           #在提交期间暂停容器(默认为true)
#提交保存时,只能选用正在运行的容器(即可以通过docker ps查看到的容器)来制作新的镜像。在指定特定镜像时,直接使用commit命令只是一个临时性的辅助命令,不推荐使用。官网建议通过docker build命令结合Dockerfile创建和管理镜像。


eg:
# docker run ubuntu echo 'hello!I am here!'  #先利用ubuntu镜像创建一个新的容器

# docker ps -a

image-20221102110159426

可以看到我们新启动的容器已经在运行了,ID为:43347239c263

# docker commit -m "Added a new file" -a "liks docktest" 43347239c263 test # 根据容器的ID提交一个新的镜像,镜像名称为test
sha256:fda92003d38176113e19b32a1710bca3732ba161c575eaa6bc1c765d2272707b

上面的信息是提交成功之后的显示内容

# docker images   # 查看一下现在有点镜像

image-20221102110255019

从上面的截图可以看出新的镜像已经产生了。

修改了容器的文件,也就是改动了容器的存储层。可以通过docker diff命令查看具体的改动。

还可以用docker history具体查看镜像内的历史记录。

eg:创建一个安装好apache工具的容器镜像
[root@Docker ~]# docker run -it centos:latest /bin/bash
[root@d632bc7716c1 /]# yum install httpd -y   #在 container 中安装 apache 软件包
[root@d632bc7716c1 /]# exit
exit 


查看 images 列表
[root@Docker ~]# docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
centos                    latest              9f38484d220f        6 weeks ago         202MB
注:当前只有一个image centos

根据容器当前状态做一个image镜像:创建一个安装了apache工具的centos镜像
语法: docker commit <container的ID>或<image_name>
例:
查看容器ID:
[root@Docker ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
d632bc7716c1        centos:latest       "/bin/bash"         5 minutes ago        Exited (0) 2 minutes ago                            kind_vaughan

[root@Docker ~]# docker commit d632bc7716c1 centos:Apache
sha256:03966321a4eb63aae5992742427cf945119f3ba2c3f6dcf115b293c1b95dbaff

[root@Docker ~]# docker images 
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
centos                    Apache              03966321a4eb        55 seconds ago      318MB
centos                    latest              9f38484d220f        6 weeks ago         202MB

使用新创建的centos:Apache镜像,生成一台容器实例:
镜像,生成一台容器实例:
[root@Docker ~]# docker run -it centos:apache /bin/bash
[root@8b1afc920454 /]# rpm -qa httpd    #查看,已经安装好apache命令
httpd-2.4.6-88.el7.centos.x86_64

image-20221102110427802

注:说明基于apache镜像的容器创建成功。

基于本地模板创建(很少使用)

通过导入操作系统模板文件可以生成镜像,模板可以从openvz开源项目下载,下载地址https://download.openvz.org/template/precreated/

下面是使用docker导入命令将下载的Ubuntu模板压缩包导入为本地镜像的例子

[root@Docker ~]# wget https://download.openvz.org/template/precreated/ubuntu-14.04-x86.tar.gz
--2019-05-06 10:01:46--  https://download.openvz.org/template/precreated/ubuntu-14.04-x86.tar.gz
Resolving download.openvz.org (download.openvz.org)... 185.231.241.69
Connecting to download.openvz.org (download.openvz.org)|185.231.241.69|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 151788867 (145M) [application/x-gzip]
Saving to: ‘ubuntu-14.04-x86.tar.gz’

100%[=================================================================>] 151,788,867 2.41MB/s   in 76s

2019-05-06 10:03:08 (1.89 MB/s) - ‘ubuntu-14.04-x86.tar.gz’ saved [151788867/151788867]

[root@Docker ~]# ls
anaconda-ks.cfg  original-ks.cfg  ubuntu-14.04-x86.tar.gz
[root@Docker ~]# cat ubuntu-14.04-x86.tar.gz | docker
docker        dockerd       dockerd-ce    docker-init   docker-proxy
[root@Docker ~]# cat ubuntu-14.04-x86.tar.gz | docker import - test:ubuntu-14.04      #必须有这个横杠
sha256:7da8a58a99a17ae7d752bb7dee8e683460b9a459c2f10bea8c44732e18c273cd

导入操作完成后,会返回生成镜像的ID信息。
 [root@Docker ~]# docker images | grep ubunt
test                      ubuntu-14.04        7da8a58a99a1        3 minutes ago       405MB

基于Dockerfile+build构建镜像:

一般用户主要使用docker build命令和Dockerfile来完成一个新镜像的构建,这里先介绍docker build命令。

docker build命令和镜像构建过程

http://www.51niux.com/?id=185  #记录了镜像的命令参数和build参数详解。

通过上面的链接知道其docker build命令其参数有3中类型(PATH、-、URL),表示构建上下文(context)的3种来源。这里的构建上下文 (简称上下文)是指传入docker build命令的所有文件。这种情况下,将本地主机的一个包含Dockerfile的目录中的所有内容作为上下文。上下文通过docker build命令传入到Docker daemon后,便开始按照Dockerfile中的内容构建镜像。

Dockerfile描述了组装镜像的步骤,其中每条指令都是单独执行的。除了FROM指令,其他每一条指令都会在上一条指令所生成镜像的基础上执行,执行完后会生成一个新的镜像层,新的镜像层覆盖在原来的镜像之上从而形成了新的镜像。Dockerfile所生成的最终镜像就是在其基础镜像上面叠加一层层的镜像层组建的。

为了提高镜像构建的速度,Docker daemon会缓存构建过程中的中间镜像。当从一个已在缓存中的基础镜像开始构建新镜像时,会将Dockerfile中的下一条指令和基础镜像的所有子镜像做一个比较,如果有一个子镜像是由相同的指令生成的,则命中缓存,直接使用该镜像,而不用再生成一个新的镜像。在寻找缓存的过程中,COPY和ADD指令与其他指令稍有不同,其他指令只对比生成镜像的指令字符串是否相同;ADD和COPY指令除了对比指令字符串,还要对比容器中的文件内容和ADD、COPY所添加的文件内容是否相同。此外,镜像构建过程中,一旦缓存失败,则后续的指令都将生成新的镜像,而不再使用缓存

docker build 命令
# docker build --help
用法:Usage:  docker build [OPTIONS] PATH | URL | -
选项:
    --build-arg value   #设置构建时间变量(默认[])
    --cgroup-parent string   #容器的可选父cgroup
    --cpu-period int       #限制CPU CFS(完全公平调度程序)周期
    --cpu-quota int      #限制CPU CFS(完全公平调度程序)配额
-c, --cpu-shares int      #CPU份额(相对权重)
    --cpuset-cpus string  #在其中允许执行的CPU(0-3,0,1)
    --cpuset-mems string  #允许执行的MEM(0-3,0,1)
    --disable-content-trust   #跳过镜像验证(默认为true)
 -f, --file string     #指定Dockerfile的名称(默认是'PATH/Dockerfile') 
     --force-rm       #始终删除中间容器
     --help     #打印用法
     --isolation string   #容器隔离技术
     --label value   #设置镜像的元数据(默认[])
 -m, --memory string      #内存限制
     --memory-swap string  #交换限制等于内存加交换:'-1'以启用无限交换
     --network string     #将容器连接到网络(默认“默认”)
     --no-cache     #构建镜像时不要使用缓存
     --pull  #始终尝试拉取图像的较新版本
 -q, --quiet     #取消构建输出并成功打印镜像ID
     --rm     #成功构建后移除中间容器(默认为true)
     --shm-size string   #/dev/shm的大小,默认值是64MB
 -t, --tag value     #以“名称:标记”格式命名(可选)标记(默认为[])
     --ulimit value   #Ulimit选项(默认[])
 -v, --volume value     #设置构建时绑定挂载(默认[])

eg:
docker build -t 仓库名/镜像名:镜像tag ./
下载指定版本的镜像

如果不加版本默认下载的镜像是latest版本。如果我们不想pull最新版本的镜像,想下载其他版本的镜像呢。这里以centos为例,centos现在下载的是centos7.4的,比如我想下载centos7.2的base镜像,tag应该是什么呢?

官方镜像库:https://hub.docker.com/  

输入我们要查找的镜像名,然后回车

image-20221102110652590

image-20221102110659620

image-20221102110704913

image-20221102110711783

根据tags信息,我们来下载一下centos7.2版本的镜像:

# docker pull centos:7.2.1511
# docker images centos   # 上面pull完毕之后,查看一下本地的镜像,可以看到多有镜像
制作一个CentOS基础镜像
# cat /docker/dockerfile/base/Dockerfile        #编写Dockerfile文件
FROM centos:7.2.1511
MAINTAINER The Base CentOS7.2 Project <test.com>
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo \
    && yum install -y net-tools gcc gcc-c++ wget \
    && yum install -y lrzsz openssh-server openssh-clients \
    && wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
CMD /bin/bash

# 创建一个basecentos的镜像,tag是7.2,然后去/docker/dockerfile/base/目录下找Dockerfile
# docker build -t basecentos:7.2  /docker/dockerfile/base/   

# docker images  basecentos

image-20221102110759782

利用我们新搞的镜像创建一个容器,登录并测试一下,可以看到官网镜像没有的软件包,现在已经存在了

[root@Docker ~]# docker run --rm -it basecentos:7.2 /bin/bash
[root@b1a579927fbc /]# rpm -qa openssh-server

image-20221102110831400

Dockerfile详解

Dockerfile文件内容惜字如金

Dockerfile是Docker用来构建镜像的文本文件,包含自定义的指令和格式。可以通过docker build命令从Dockerfile中构建镜像。这个过程与传统分布式集群的编排配置过程相似,且提供了一系列统一的资源配置语法。用户可以用这些统一的语法命令来根据需求进行配置,通过这份统一的配置文件,在不同的平台上进行分发,需要使用时就可以根据配置文件自动化构建同时,Dockerfile与镜像配合使用,使Docker在构建时可以充分利用镜像的功能进行缓存,大大提升了Docker的使用效率。

about dockerfile

image-20221102110934578

docker format

image-20221102110940777

.dockerignore file

image-20221102110948332

Dockerfile操作指令详解

Dockerfile的基本格式如下:INSTRUCTION arguments

在Dockerfile中,指令(INSTRUCTION)不区分大小写,但是为了与参数区分,推荐大写。Docker会顺序执行Dockerfile中的指令,第一条指令必须是FROM指令

它用于指定构建镜像的基础镜像。

在Dockerfile中以#开头的行是注释,而在其他位置出现的#会被当成参数

Dockerfile中的指令有FROM、MAINTAINER、RUN、CMD、EXPOSE、ENV、ADD、COPY、ENTRYPOINT、VOLUME、USER、WORKDIR、ONBUILD,错误的指令会被忽略

Dockerfile 分为四部分:基础镜像信息维护者信息镜像操作指令容器启动时执行指令

基础镜像信息 FROM

维护者信息 MAINTAINER

镜像操作指令 RUN、COPY、ADD、EXPOSE

容器启动时执行指令 CMD、ENTRYPOINT

image-20221108165119426

FROM 指定基础镜像

image-20221108165134372

语法

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

所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。而 FROM 就是指定基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令并且必须是第一条指令。

原文链接:https://blog.csdn.net/securitit/article/details/109503940

通过向FROM指令添加AS name,可以选择为新生成阶段指定名称。该名称可以在后续的FROM 和 COPY --FROM=<name>指令中使用,以引用在此阶段中构建的镜像

--platform可以用于指定镜像的平台,用来处理那些支持多平台的镜像。例如:linux/amd64、linux/arm64或windows/amd64。默认情况下,使用生产请求的平台。全局生成参数可用于此标志的值,例如:自动平台参数允许您强制一个阶段到本机构建平台(--platform=$BUILDPLATFORM),并使用它交叉编译到阶段内部的目标平台。

示例:nginx:latest作为基础镜像,指定--platform=linux/arm64(需要启用buildx插件)进行镜像构建。

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像

FROM scratch

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

不以任何系统为基础,直接将可执行文件复制进镜像的做法并不罕见,比如 swarm 、 coreos/etcd 。对于 Linux 下静态编译的程序来说,并不需要有操作系统提供运行时支持,所需的一切库都已经在可执行文件里了,因此直接 FROM scratch会让镜像体积更加小巧。使用 Go 语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go是特别适合容器微服务架构的语言的原因之一

MAINTAINER 说明镜像维护者的信息

image-20221108165951453

语法:

MAINTAINER <name>

  • 此指令已过时,用LABEL代替;新版docker中使用LABEL指明
LABEL 功能是为镜像指定标签

image-20221108170018152

Syntax
LABEL <key>=<value> <key>=<value> <key>=<value> ...

一个Dockerfile种可以有多个LABEL,如下:
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
但是并不建议这样写,最好就写成一行,如太长需要换行的话则使用\符号

如下:
LABEL multi.label1="value1" \
multi.label2="value2" \
other="value3"

说明:LABEL会继承基础镜像种的LABEL,如遇到key相同,则值覆盖

RUN 执行命令

image-20221108170111672

image-20221108170153045

RUN 指令是用来执行命令行命令的。由于命令行的强大能力, RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

shell 格式: RUN <命令> ,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
exec 格式: RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。
FROM debian:jessie
RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

之前说过,Dockerfile 中每一个指令都会建立一层, RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后, commit 这一层的修改,构成新的镜像。

而上面的这种写法,创建了 7 层镜像这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误

Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过127 层。

上面的 Dockerfile 正确的写法应该是这样:

FROM debian:jessie
RUN buildDeps='gcc libc6-dev make' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。将之前的 7 层,简化为了1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。

并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加\的命令换行方式,以及行首 #进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,之前有说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件

EXPOSE 声明端口

image-20221108170805584

  格式为 EXPOSE <端口1> [<端口2>...]

EXPOSE 指令是声明运行时容器提供服务端口这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

此外,在早期 Docker 版本中还有一个特殊的用处。以前所有容器都运行于默认桥接网络中,因此所有容器互相之间都可以直接访问,这样存在一定的安全性问题。于是有了一个 Docker 引擎参数 --icc=false ,当指定该参数后,容器间将默认无法互访,除非互相间使用了 --links 参数的容器才可以互通,并且只有镜像中 EXPOSE 所声明的端口才可以被访问。这个 --icc=false 的用法,在引入了 docker network 后已经基本不用了,通过自定义网络可以很轻松的实现容器间的互联与隔离。

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来-p ,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

ARG 设置变量

作用:定义创建镜像过程中使用的变量

格式: ARG [=]

详解

  1. 在执行 docker build 时,可以通过 --build-arg <参数名>=<值> 来为声明的变量赋值
  2. 当镜像编译成功后,ARG 指定的变量将不再存在(ENV指定的变量将在镜像中保留)
  3. Docker内置了一些镜像创建变量,用户可以直接使用而无须声明,包括(不区分大小写)

Docker自带的如下ARG参数,可以在其他指令中直接引用:

HTTP_PROXY
http_proxy
HTTPS_PROXY
https_proxy
FTP_PROXY
ftp_proxy
NO_PROXY
no_proxy
ARG 指令有生效范围
1、如果在 FROM 指令之前指定,那么只能用于 FROM 指令中
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo ${DOCKER_USERNAME}



2、使用上述 Dockerfile 会发现无法输出 ${DOCKER_USERNAME} 变量的值
要想正常输出,必须在 FROM 之后再次指定 ARG
# 只在 FROM 中生效
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine

# 要想在 FROM 之后使用,必须再次指定
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}

多阶段构建的时候,ARG 定义的变量,每个 FROM 都能用
# 这个变量在每个 FROM 中都生效
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 1
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 2
ENV 设置环境变量

image-20221108171045013}

image-20221108171051570

格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
  这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN ,还是运行时的应用,都可以直接使用这里定义的环境变量。

ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

下列指令可以支持环境变量展开:

ADD 、 COPY 、 ENV 、 EXPOSE 、 LABEL 、 USER 、 WORKDIR 、 VOLUME 、 STOPSIGNAL 、 ONBUILD

可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可。

eg:定义多个变量

image-20221108171150619

启动容器后,在容器实例中,可以通过env命令查看环境变量

[root@a7cf709f3923 ~]# env

ARG 和 ENV 的区别

  1. ARG 定义的变量只会存在于镜像构建过程,启动容器后并不保留这些变量
  2. ENV 定义的变量在启动容器后仍然保留

注意 不要通过 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的

COPY 复制文件

对于目录而言,COPY 和 ADD 命令具有相同的特点:只复制目录中的内容而不包含目录自身。

  • <src>:是 Dockerfile 所在目录的一个相对路径(文件或目录)
  • <dest>:可以是镜像内绝对路径,或者相对于工作目录(WORKDIR)的相对路径
  • When using COPY with more than one source file, the destination must be a directory and end with a /

image-20221108171225291

格式:
COPY <源路径>... <目标路径>
COPY ["<源路径1>",... "<目标路径>"]
   RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如:

COPY package.json /usr/src/app/
  <源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go  filepath.Match 规则,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用WORKDIR指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

此外,还需要注意一点,使用 COPY指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git进行管理的时候。

ps:复制文件到某个目录时,目录后面必须跟斜线->如:/tmp/ /data/web/html/

image-20221108171603932

eg:复制/etc/yum.repos.d/目录下的所有文件到制作镜像目录下,并且打包至镜像中

image-20221108171643661

ps:复制文件至目录下,该目录后面必须有斜线作为标识

image-20221108171649760

验证结果:

image-20221108171654884

ADD 更高级的复制文件

ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。比如 <源路径> 可以是一个 URL ,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600 ,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。

如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip , bzip2 , xz,tar.gz 的情况下, ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去

在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu 中:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了。

在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY ,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。

另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD

eg:下载nginx软件包至/usr/local/src /

image-20221108171851573

eg:把nginx软件包解压至/usr/local/src/目录中

image-20221108171922885

WORKDIR 指定工作目录

image-20221108171935280

格式为 WORKDIR <工作目录路径> 

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在, WORKDIR 会帮你建立目录。

之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:

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

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

之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。

因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令

VOLUME 定义匿名卷

image-20221108172041809

  格式为:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

这里的 /data 目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:

docker run -d -v mydata:/data xxxx

在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

eg:

image-20221108172152370

USER 指定当前用户

image-20221108172203466

  格式: USER <用户名>

USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。 WORKDIR 是改变工作目录, USER 则是改变之后层的执行 RUN , CMD 以及 ENTRYPOINT 这类命令的身份。当然,和 WORKDIR 一样, USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]

如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo ,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu

# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/
gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
HEALTHCHECK 健康检查

image-20221108172329970

image-20221108172335964

  格式:
HEALTHCHECK [选项] CMD <命令> :设置检查容器健康状况的命令
HEALTHCHECK NONE :如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK 支持下列选项:
interval=<间隔> :两次健康检查的间隔,默认为 30 秒;
timeout=<时长> :健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
retries=<次数> :当连续失败指定次数后,则将容器状态视为 unhealthy ,默认 3 次。

和 CMD , ENTRYPOINT 一样, HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。

在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否: 0 :成功; 1 :失败; 2 :保留,不要使用这个值。

注意:

HEALTHCHECK命令只能出现一次,如果出现了多次,只有最后一个生效。

CMD后边的命令的返回值决定了本次健康检查是否成功,具体的返回值如下:
0: success - 表示容器是健康的

1: unhealthy - 表示容器已经不能工作了

2: reserved - 保留值

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。

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

而自 1.12 之后,Docker 提供了 HEALTHCHECK 指令,通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。

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

假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl 来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:

eg:

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

eg:

image-20221108172556749

ONBUILD 为他人做嫁衣裳

image-20221108172604664

  格式: ONBUILD <其它指令>

ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN , COPY 等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。

Dockerfile 中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD 是为了帮助别人定制自己而准备的。

CMD 容器启动命令

image-20221108172740037

CMD 指令的格式和 RUN 相似,也是两种格式:
shell 格式: CMD <命令>
exec 格式: CMD ["可执行文件", "参数1", "参数2"...]

参数列表格式: CMD ["参数1", "参数2"...] 。如果在指定了 ENTRYPOINT 指令后,那么CMD 指定内容将作为参数传入给容器。

docker run command的命令匹配到CMD command时,会替换CMD执行的命令

Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。 CMD 指令就是用于指定默认的容器主进程的启动命令的。

ps:Dockerfile只能有一条CMD命令,如果指定了多条则只有最后一条被执行

  在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 " ,而不要使用单引号。

如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:
CMD echo $HOME
  在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]

初学者一般将 CMD 写为:CMD service nginx start

然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。

对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。

而使用 service nginx start 命令,则是希望 systemd 来以后台守护进程形式启动 nginx 服务。而刚才说了CMD service nginx start 会被理解为, CMD [ "sh", "-c", "service nginx start"] ,因此主进程实际上是 sh 。那么当service nginx start命令结束后, sh 也就结束了, sh 作为主进程退出了,自然就会令容器退出。

正确的做法是直接执行 nginx 可执行文件并且要求以前台形式运行。比如:

CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT 启动容器命令

image-20221108173915090

--entrypoint string Overwrite the default ENTRYPOINT of the image 必须是可执行文件

docker run 命令的--entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序

❝小结一下,不难理解,当不指定 --entrypoint 时,默认的 entrypoint 就是 shell,所以如果我们在 dockerfile 中指定了 entrypoint,那么我们想要运行其他可执行文件时,就必须显式地指定可执行文件了。❞

https://blog.kelu.org/tech/2018/11/15/overwrite-entrypoint-and-cmd-for-docker.html

image-20221108174110604

Dockerfile实践心得

在构建Dockerfile文件时,如果遵守这些实践方式,可以高效地使用Docker。

使用标签

给镜像打上标签,易读的镜像标签可以帮助了解镜像的功能,如docker build -t="ruby:2.0-onbuild"

谨慎选择基础镜像

选择基础镜像时,尽量选择当前官方镜像库中的镜像。不同镜像的大小不同,目前Linux镜像大小有如下关系:

    busybox < debian < centos <ubuntu

同时在构建自己的Docker镜像时,只安装和更新必须使用的包。FROM指令应该包含参数tag。

充分利用缓存

Docker daemon会顺序执行Dockerfile中的指令,而且一旦缓存失效,后续命令将不能使用缓存。为了有效的利用缓存,需要保证指令的连续性,尽量将所有Dockerfile文件中相同的部分都放在前面,而将不同的部分放在后面。

正确使用ADD和COPY指令

尽管ADD和COPY用法和作用很相近,但COPY仍是首选。COPY相对于ADD而言,功能简单够用。COPY仅提供本地文件向容器的基本复制功能。ADD有额外的一些功能,比如支持复制本地压缩包(复制到容器中会自动压缩)和URL远程资源。因此,ADD比较符合逻辑的使用方式是ADD roots.tar.gz / 。

当在Dockerfile中的不同部分需要用到不同的文件时,不要一次性地将这些文件都添加到镜像中去, 而是在需要时逐个添加,这样也有利用于充分利用缓存。另外,考虑到镜像大小的问题,使用ADD指令去获取远程URL中的压缩包不是推荐的做法。应该使用RUN wget或RUN curl代替。这样可以删除解压后不再需要的文件,并且不需要在镜像中再添加一层。

另外,尽量使用docker volume共享文件,而不是使用ADD或COPY指令添加文件到镜像中。

RUN指令

为了使Dockerfile易读、易理解和可维护,在使用比较长的RUN指令时可以使用反斜杠\分割多行。大部分使用RUN指令的场景是运行yum命令,在该场景下请注意如下几点:

1、不要在一行中单独使用指令RUN yum update。当软件源更新后,这样做会引起缓存问题,导致RUN yum install指令运行失败。所以,RUN yum updateRUN yum install应该写在同一行,如RUN yum update && yum install -y package-bar 

2、避免使用指令RUN yum upgrade和RUN yum upgrade。因为在一个无特权的容器中,一些必要的包会更新失败。如果需要更新一个包(如foo),直接使用指令RUN yum install -y foo。

3、在Docker的核心概念中,提交镜像是廉价的,镜像之间有层级关系,像一棵树。不要害怕镜像的层数过多。可以在任意层创建一个容器。因此,不要将所有的命令写在一个RUN指令中。RUN指令分层符合Docker的核心概念,这很像源码控制。

CMD和ENTRYPOINT指令

CMD和ENTRYPOINT指令指定了容器运行的默认命令,推荐两者结合使用。使用exec格式的ENTRYPOINT指令设置固定的默认命令和参数,然后使用CMD指令设置可变的参数。

不要在Dockerfile中做端口映射

Docker的两个核心概念是可重复性和可移植性,镜像应该可以在任意主机上运行多次。

使用Dockerfile的EXPOSE指令,虽然可以将容器端口映射到主机端口上,但会破坏Docker的可移植性,且这样的镜像在一台主机上只能移动一个容器。 所以端口映射应在docker run命令中用-p参数指定。

使用Dockerfile共享Docker镜像

若要共享镜像,只需共享Dockerfile文件即可。共享Dockerfile文件具有以下优点:

Dockerfile文件可以加入版本控制,这样可以追踪文件的变化和回滚错误。

通过Dockerfile文件,可以清楚镜像构建的过程。

使用Dockerfile文件构建的镜像具有确定性

Docker 容器

容器是Docker的一个核心概念。容器是镜像的一个运行实例,所不同的是,它带有额外的可写文件层。Docker的设计理念是希望用户能够保证一个容器只运行一个进程,即只提供一种服务

容器的创建

Docker的容器十分轻量级,用户可以随时创建或删除容器

# docker create  --help
用法:docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
选项:
    --add-host value    #添加自定义的主机到IP映射(主机:IP)(默认[])
-a, --attach value      #附加到STDIN,STDOUT或STDERR(默认[])
    --blkio-weight value  #块IO(相对权重),在10和1000之间
    --blkio-weight-device value  #块IO权重(相对设备重量)(默认[])
    --cap-add value       #添加Linux功能(默认[])
    --cap-drop value      #删除Linux功能(默认[])
    --cgroup-parent string #容器的可选父cgroup
    --cidfile string      #将容器ID写入文件
    --cpu-percent int     #CPU百分比(仅限Windows)
    --cpu-period int      #限制CPU CFS(完全公平调度程序)期间
    --cpu-quota int       #限制CPU CFS(完全公平调度程序)配额
-c, --cpu-shares int      #CPU份额(相对权重)
    --cpuset-cpus string  #在其中允许执行的CPU(0-3,0,1)
    --cpuset-mems string  #允许执行的MEM(0-3,0,1)
    --device value        #添加主机设备到容器(默认[])
    --device-read-bps value  #限制设备(默认[])的读取速率(每秒字节数)
    --device-read-iops value  #限制来自设备的读取速率(IO每秒)(默认[])
    --device-write-bps value  #限制写入速率(每秒字节数)到设备(默认[])
    --device-write-iops value  #限制写入速率(每秒IO)到设备(默认[])
    --disable-content-trust    #跳过镜像验证(默认为true)
    --dns value            #设置自定义DNS服务器(默认[])
    --dns-opt value       #设置DNS选项(默认[])
    --dns-search value    #设置自定义DNS搜索域(默认[])
    --entrypoint string   #覆盖镜像的默认入口点
-e, --env value           #设置环境变量(默认[])
    --env-file value      #读入环境变量文件(默认[])
    --expose value        #公开一个端口或一系列端口(默认[])
    --group-add value     #添加其他群组加入(默认[])
    --health-cmd string   #运行命令来检查运行状况
    --health-interval duration   #运行检查之间的时间(默认为0)
    --health-retries int    #连续失败需要报告不健康
    --health-timeout duration  #允许一次检查运行的最长时间(默认为0)
-h, --hostname string        #容器主机名称
-i, --interactive            #即使未连接,也要保持STDIN打开状态
    --io-maxbandwidth string  #系统驱动器的最大IO带宽限制(仅限Windows)
    --io-maxiops uint      #系统驱动器的最大IOps限制(仅限Windows)
    --ip string           #容器的 IPv4地址(例如172.30.100.104)
    --ip6 string         #容器IPv6地址(例如2001:db8 :: 33)
    --ipc string         #IPC命名空间使用
    --isolation string   #容器隔离技术
    --kernel-memory string  #内核内存限制
-l, --label value       #在容器上设置元数据(默认[])
    --label-file value   #阅读标签的行分隔文件(默认[])
    --link value        #添加链接到另一个容器(默认[])
    --link-local-ip value   #容器IPv4 / IPv6链接本地地址(默认[])
    --log-driver string    #记录容器的驱动程序
    --log-opt value      #日志驱动程序选项(默认[])
    --mac-address string   #容器MAC地址(例如92:d0:c6:0a:29:33)
-m, --memory string      #内存限制
    --memory-reservation string   #内存软限制
    --memory-swap string    #交换限制等于内存加交换:'-1'以启用无限交换
    --memory-swappiness int   #调整容器内存swappiness(0到100)(默认-1)
    --name string      #为容器指定一个名称
    --network string   #将容器连接到网络(默认“默认”)
    --network-alias value  #为容器添加网络范围的别名(默认[])
    --no-healthcheck    #禁用任何容器指定的HEALTHCHECK
    --oom-kill-disable   #禁用OOM killer
    --oom-score-adj int   #调整主机的OOM首选项(从-1000到1000)
    --pid string         #要使用的PID名称空间
    --pids-limit int     #调整容器的pid限制(无限制地设置-1)
    --privileged         #给这个容器赋予扩展权限
 -p, --publish value     #将容器的端口发布到主机(默认[])
 -P, --publish-all      #将所有暴露的端口发布到随机端口
    --read-only       #将容器的根文件系统挂载为只读
    --restart string   #重新启动策略以在容器退出时应用(默认为“no”)
    --runtime string     #运行时用于此容器
    --security-opt value   #安全选项(默认[])
    --shm-size string     #/dev/shm的大小,默认值是64MB
    --stop-signal string   #停止容器的信号,默认为SIGTERM(默认为“SIGTERM”)
    --storage-opt value   #容器的存储驱动程序选项(默认[])
    --sysctl value      #Sysctl选项(默认map[])
    --tmpfs value      #挂载一个tmpfs目录(默认[])
-t, --tty              #分配一个伪TTY
    --ulimit value     #Ulimit选项(默认[])
-u, --user string      #用户名或UID(格式:<name | uid> [:<group | gid>])
    --userns string    #要使用的用户名称空间
    --uts string      #UTS命名空间使用    
-v, --volume value    #绑定装入卷(默认[])
    --volume-driver string  #容器的可选卷驱动程序
    --volumes-from value    #装载指定容器的卷(默认[])
 -w, --workdir string       #容器内的工作目录

(上面的IPC,UTS等是什么?)Linux内核中就提供了这六种namespace隔离的系统调用

eg:
# docker create --name test01 centos:latest # 创建一个名称叫做test01的新容器,最后那是镜像
8eb1f4b73f053e6d7b4a4203d66542326f8c79420a185ac1abbf08042bdc43fb

容器的启动(2种)

容器的启动有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动

基于镜像新建一个容器并启动:

利用docker run命令新建一个容器时,Docker将自动为每个新容器分配一个唯一的ID作为标识。docker run等价于先执行docker create命令再执行docker start命令。因为docker run相当于先执行docker create,所以上面列出的docker create的参数docker run也能用。

无交互式

eg:
# docker run centos:latest /bin/echo "hello world\!I am Docker\!"
hello world\!I am Docker\!      # 输出内容,这里感叹号必须转义,否则会报如下错误
# docker run centos:latest /bin/echo "hello world !I am Docker!"
-bash: !I: event not found

eg:
#下面的命令输出一个hello World,之后终止容器
[root@localhost ~]# docker run ubuntu:18.04 /bin/echo "hello World"

image-20221108174508310

当利用docker run来创建容器,docker在后台运行的标准操作包括:

  • 检查本地是否存在指定镜像,不存在就从公有仓库下载。

  • 利用镜像创建并启动一个容器。

  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层。

  • 从宿主主机配置的网桥接口中接一个虚拟接口到容器中去。

  • 从地址池配置一个ip地址给容器。

  • 执行用户指定的应用程序。

  • 执行完毕后容器被终止。

交互式

下面的命令则启动一个bash中断,允许用户进行交互

#docker run命令启动一个容器,并为它分配一个伪终端执行/bin/命令,用户可以在该伪终端与容器进行交互,其中:
    -i  #表示使用交互模式,始终保持输入流开放
    -t  #表示分配一个为终端,一般两个参数结合时使用-it,即可在容器中利用打开的伪终端进行交互操作。
    --name   #指定docker run命令启动的容器的名字,若无此选项,Docker将为容器随机分配一个名字。
    -c   #用于给运行在容器中的所有进程分配CPU的shares值,这是一个相对权重,实际的处理速度还与宿主机的CPU相关。
    -m   #用于限制为容器中所有进程分配的内存总量,以B、K、M、G为单位。
    -v   #用于挂载一个volume,可以用多个-v参数同时挂载多个volume。volume的格式为:[host-dir]:[container-dir]:[rw|ro]
    -p   #用于将容器的端口暴露给宿主机的端口,其常用格式为hostPort:containerPort.通过端口暴露,可以让外部主机通过宿主机暴露的端口来访问容器内的应用
    -P  #映射随机端口
    --rm # 停止容器后,自动删除该容器;如果有挂载卷也会一并删除,建议只在测试时使用
eg:自定义容器名
#创建了一个名称叫做web3的容器,-d让Docker容器在后台以守护态(Daemonized)形式运行
# docker run -d -P --name web3 training/webapp python app.py   

[root@localhost ~]# docker ps -l    #可以查看最后一次创建的容器
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
b46dd45c9080        training/webapp     "python app.py"     2 minutes ago       Up 2 minutes        0.0.0.0:32770->5000/tcp   web3

# docker logs web3  #可以看到容器web3的实时日志信息,当然还有很多参数,可以--help查看
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
# docker inspect --format="{{ .Name }}" b46dd45c9080   #可以看到容器的名称
/web3
#容器的名字是唯一的。如果已经命名了一个叫web3的容器,当你要再次使用web这个名称的时候,需要先用docker rm来删除之前创建的同名容器。在执行docker run的时候如果添加 -- rm标记,则容器在终止后会立即删除

eg:
# docker run  -i -t --name test02 centos:latest /bin/bash     #这样不仅创建了test02容器,也会进入容器。
[root@localhost ~]# docker run  -i -t --name test02 centos:latest /bin/bash
[root@e9746ae00fde /]#                              #已经进入容器了,注意看提示符
[root@e9746ae00fde /]# cat /etc/redhat-release      #在容器里面操作
CentOS Linux release 7.6.1810 (Core)

如果要正常退出不关闭容器,请按Ctrl+P+Q进行退出容器;如果输入exit退出,容器会停止

eg:
[root@localhost ~]# docker run -it ubuntu:18.04 /bin/bash

image-20221108174940457

eg:
1、运行一个 container并加载镜像centos,运行起来这个实例后,在实例中执行 /bin/bash命令
Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
语法:docker run -it 镜像名/镜像ID /bin/bash

ps:镜像名等于->REPOSITORY:TAG

[root@Docker ~]#docker images       #列出所有本地镜像
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              9f38484d220f        6 weeks ago         202MB
[root@Docker ~]#docker run -it centos:latest /bin/bash  #启动一个实例,也就2秒就可以搞定
[root@28a50bcd0aff /]#

image-20221108175021034

查看实例环境

image-20221108175030665

start方式启动已经存在的容器

可以通过docker start/stop/restart命令来启动、停止和重启容器。

# docker ps -a          #查看容器
# docker start 8eb      #指定容器的ID号来启动容器

设置Docker的ulimit参数

这是Docker在1.6版本退出的新功能,用户可以设置容器的ulimit参数。使用时加入--ulimit参数,后面输入ulimit资源的种类,如nofile,再确定软限制和硬件限制,用冒号隔开,具体示例如下:

# docker run --ulimit nofile=1024:1024 --rm centos ulimit -n
Docker中ulimit项常用的配置参数如下所示:
    nofile   #可以打开文件描述符的最大数量
    core     #最大内核文件的大小,以blocks为单位
    cpu      #指定CPU使用时间的上限,以秒为单位
    data     #进程最大数据块的大小,以Kbytes为单位
    fsize    #进程可以创建文件的最大值,以blocks为单位
    locks    #能同时保持的文件锁的最大数量
    memlock   #最大可加锁的内存大小,以Kbytes为单位
    stack   #分配线程堆栈的最大值,以Kbytes为单位
    nproc   #一个用户可创建的最大进程数
    sigpending  #最大待处理的信号数量
    rss   #用户可获得的最大物理内存
    msgqueue  #可以在消息队列中分配的最大字节
    rtprio   #非特权用户进程所能请求的实时优先级的上限
    nice   #设置进程优先级的nice值,nice值越高,优先级越低

注意:对于root权限的用户来说,ulimit的效果并不能全部生效。

后台运行一个容器

使用-d

eg: run 创建并后台运行docker实例
# docker run -d docker.io/centos:latest /bin/sh -c "while true;do echo hello world; sleep 1; done"
    eg:
     container 中启动一个长久运行的进程,不断向stdin输出 hello world 。模拟一个后台运行的服务

[root@Docker ~]#docker run -d centos:latest /bin/sh -c "while true;do echo first container;sleep 1;done"
6e0ec95cd39b1686fd01db34aeb72c18a48bce26cf3a83ab5e4bc3b12166dfd5
#容器ID

eg:后台运行一个CentOS系统
# docker run -dit centos
15ddc3e37bcc414c81191043b32590f79d44885d49cf6977e51444c36f558001

eg:后台运行一个nginx
# docker run -d --rm --name nginx_web -p 8001:80 nginx

注意:容器是否会长久运行,适合docker run指定的命令有关,和-d参数无关

从一个容器中取日志,查看输出的内容

语法: docker  logs   容器实例的Name或者ID  
ps:NAME/ID用docker ps查看
[root@Docker ~]# docker logs 6e0ec95cd39b16  # 容器的ID可以写全,也可以不写全,只要唯一就可以了

img

2、也可以使用短ID或docker实例的名字查看日志输出:

[root@Docker ~]# docker logs 825090df024d

img

或:

[root@Docker ~]# docker logs compassionate_mclean

img

终止容器(2种)

docker ps -a -q #查看所以的容器的ID信息(包括停止的容器)

1、可以使用docker stop来终止一个运行中的容器

命令的格式为:docker stop [-t|--time[=10]]。它会首先向容器发送SIGTERM信号,等待一段时间后(默认为10秒),再发送SIGKILL信号终止容器。

此外当Docker容器中指定的应用终结时,容器也自动终止。另外可以使用docker stop来终止一个运行中的容器

如下面的命令: docker stop 82a #通过指定容器的ID号来关闭容器

eg:

            查看容器: 
            [root@Docker ~]# docker ps 
            [root@Docker ~]# docker ps -p #只查看容器ID
            [root@Docker ~]# docker stop 1a63ddea6571  停止容器
            1a63ddea6571

2、另外docker kill命令会直接发送SIGKILL信号来强行终止容器。

如:# docker kill container_name #指定容器名来kill掉

eg:kill一个容器(发一个9信号)

比如:杀死一个正在后台运行的容器

        # 查看要杀死容器的ID:
        [root@Docker ~]#docker ps
            杀死ID为c4a213627f1b的容器
        [root@Docker ~]# docker kill 6e0ec95cd39b  #杀死一个容器 
        6e0ec95cd39b

image-20221108180203045

重启容器

[root@Docker ~]# docker ps

[root@Docker ~]# docker restart 1a63ddea6571
1a63ddea6571

查看容器(2种)

语法1:

Usage:  docker ps [OPTIONS]

[root@Docker ~]# docker ps #列出所有运行中容器

img

列出所有容器(包含沉睡/退出状态的容器)

[root@Docker ~]#docker ps -a

img

参数解释:
CONTAINER ID        #容器ID
IMAGE              #镜像ID
COMMAND          #运行的命令
CREATED            #容器创建的时间
STATUS             #容器的状态
PORTS             #容器的端口
NAMES               #容器的名字

语法2:

Usage:  docker container COMMAND
[root@Docker ~]# docker container ls

img

# docker ps -l      # 可以查看最后一次创建的容器
# docker ps -n NUM  # Show n last created containers (includes all states) (default -1)
Options:
  -a, --all             Show all containers (default shows just running)
  -f, --filter filter   Filter output based on conditions provided
      --format string   Pretty-print containers using a Go template
  -n, --last int        Show n last created containers (includes all states) (default -1)
  -l, --latest          Show the latest created container (includes all states)
      --no-trunc        Don't truncate output
  -q, --quiet           Only display container IDs
  -s, --size            Display total file sizes

查看容器详细信息

Usage:  docker inspect [OPTIONS] NAME|ID [NAME|ID...]

#docker inspect 4bf5748ea648    #查看具体详细信息

[root@localhost ~]# docker inspect ab8      

img

进入容器

在使用-d参数时,容器启动后会进入后台,用户无法看到容器中的信息。某些时候如果需要进入容器进行操作,有很多种方法,包括使用docker attach命令docker exec命令

docker attach命令:

docker attach命令对于开发者来说十分有用,它可以连接到正在运行的容器,观察该容器的运行情况,或与容器的主进程进行交互。使用方法如下:

# docker attach --help 

用法:docker attach [OPTIONS] CONTAINER
选项:
--detach-keys string    #覆盖分离容器的键序列
--no-stdin              #不要连接STDIN
--sig-proxy             #将所有接收到的信号代理到进程(默认为true)

eg:
# docker attach e9746ae00fde 
# 注意,如果使用exit退出,那么在退出容器后会关闭容器
# docker attach e9746ae00fde 
You cannot attach to a stopped container, start it first
# 如果要正常退出不关闭容器,请按Ctrl+P+Q进行退出容器。

eg:
Usage:  docker attach [OPTIONS] CONTAINER
eg:
[root@Docker ~]# docker attach 0d1a6077baf5
[root@0d1a6077baf5 /]# exit
exit

image-20221108180743086

注意: 如果从这个 stdin 中 exit, 会导致容器的停止。

使用attach命令有时候并不方便。当多个窗口同时attach到同一个容器的时候,所有窗口都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法执行操作了。要attach上去的容器必须正在运行,可以同时连接上同一个container来共享屏幕(与screen命令的attach类似)。

docker exec命令(SSH服务器的替代方案):

Docker自1.3版本起,提供了一个更加方便的工具exec,可以直接再容器内运行命令。这也是进入一个运行的容器的命令。

# docker exec --help

用法:docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
-d, --detach   #分离模式:在后台运行命令
--detach-keys   #覆盖分离容器的键序列
-i, --interactive  #即使未连接,也要保持STDIN打开状态
--privileged    #赋予命令扩展权限
-t, --tty     #分配一个伪TTY
-u, --user    #用户名或UID(格式:<name | uid> [:<group | gid>])

eg:
# docker exec -it e9746ae00fde /bin/bash  #在容器e9746ae00fde中开启一个交互模式的终端。这种就是可以直接在容器中启动一个shell,然后就可以进入容器进行一系列操作了。

eg:
在使用 -d 参数时, 容器启动后会进入后台。
某些时候需要进入容器进行操作, 包括使用 docker attach 命令或 docker
exec 命令, 推荐大家使用 docker exec 命令, 原因会在下面说明。


Usage:  docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

eg:进入一个正在后台运行的CentOS7系统
[root@Docker ~]# docker container ls -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                      PORTS               NAMES
15ddc3e37bcc        centos              "/bin/bash"          9 minutes ago       Up 3 minutes                                    stoic_merkle

[root@Docker ~]# docker exec -it 15ddc3e37bcc /bin/bash
[root@15ddc3e37bcc /]#

image-20221108180849756

如果从这个 stdin 中 exit, 不会导致容器的停止。 这就是为什么推荐大家使用docker exec 的原因。

当然还有nsenter方式也可以进入容器,还有ssh登录的方式,也就是说四种方式也可以进入容器。

这个exec命令实际上可以解决大部分需要ssh进入容器的问题,提供与宿主机原有功能的结合,同样也可以解决诸如定时任务等问题

删除容器:

docker rm命令用于删除处于终止状态的容器容器。

# docker rm --help
用法:docker rm [OPTIONS] CONTAINER [CONTAINER...]
选项:
-f, --force   #强制删除正在运行的容器(使用SIGKILL)
-l, --link    #删除指定的链接
-v, --volumes  #删除与容器关联的卷

eg:
# docker rm 43347239c263 -f   #不执行-f的时候不能删除一个正在运行的容器


eg:
[root@Docker ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                            PORTS               NAMES
12b4fad796ac        centos:latest       "/bin/sh 'while true…"   About a minute ago   Exited (127) About a minute ago                       vibrant_lamport
825090df024d        centos:latest       "/bin/sh -c 'while t…"   33 minutes ago       Up 20 seconds                                         happy_kilby

删除停止的container: rm
[root@Docker ~]# docker rm 12b4fad796ac
12b4fad796ac

[root@Docker ~]# docker rm 825090df024d
Error response from daemon: You cannot remove a running container 825090df024d36bd0e1acdd14505a00de1d34f34355c5ce21abd4aec5e57f3cc. Stop the container before attempting removal or force remove

删除正在运行的container  rm -f
[root@Docker ~]# docker rm  e085da6919af
Error response from daemon: You cannot remove a running container e085da6919af2f294d73f8e717f93326f6c1a803938e8057aebfc36e28d05808. Stop the container before attempting removal or use -f
解决:你可以先把容器1a63ddea6571 关闭,然后再删除或加-f 强制删除

如果要删除一个运行中的容器,可以添加-f参数。Docker会发送SIGKILL信号给容器。
[root@Docker ~]# docker rm -f 825090df024d
825090df024d

再次查看容器
[root@Docker ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

eg:
清理所有处于终止状态的容器
用 docker container ls -a 命令可以查看所有已经创建的包括终止状态的容器, 如果数量太多要一个个删除可能会很麻烦, 用下面的命令可以清理掉所有处于终止状态的容器。
$ docker container prune

退出容器

如果要正常退出不关闭容器,请按Ctrl+P+Q进行退出容器。

image-20221108181233232

exit的方式退出,容器会停止

导入和导出容器:

导出(2种)

导出容器是指导出一个已经创建的容器快照到本地文件,不管此时这个容器是否处于运行状态,可以使用docker export命令。

# docker ps -a  # 可以用这个命令查看所有的容器包括运行和不在运行的。

eg1:
[root@localhost ~]# docker export e9746ae00fde -o /opt/test02-run.tar       #将运行 e9746ae00fde 的容器的导出为test02-run.tar

eg2:
[root@localhost ~]# docker export e9746ae00fde > /opt/test02-run1.tar       #当然这种方式也是可以的
[root@localhost ~]# ll -h /opt/test02-run*
-rw-r--r--. 1 root root 200M Sep 12 16:24 /opt/test02-run1.tar
-rw-------. 1 root root 200M Sep 12 16:23 /opt/test02-run.tar

#这样可将这些文件传输到其他机器上,再其他机器上通过导入命令实现容器的迁移。

导入(2种)

导入容器快照可以使用docker import命令导入为镜像,下面是其用法:

# docker import --help

用法:docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
选项:
-c, --change value   #将Dockerfile指令应用于创建的映像(默认[])
-m, --message string  #为导入的镜像设置提交消息

eg:
# docker import /opt/test02-run1.tar centos-run:v1      #将刚才的容器文件导入,名称为centos-run:v1
sha256:9c1f1e32d4fcc4e72bc441f8cc65aae3255c0db54c29df50b17545c6daf1bdcb
# docker images     #查看下镜像

image-20221108181618674

eg:
[root@Docker ~]# docker container ls -a     查看容器
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                      PORTS               NAMES
9b9d91c5ff1d        httpd               "httpd-foreground"   40 minutes ago      Exited (0) 35 minutes ago                       web1-httpd

[root@Docker ~]# docker export 9b9d91c5ff1d > /tmp/web-httpd.tar    导出容器
[root@Docker ~]# ll -h /tmp/web-httpd.tar 
-rw-r--r--. 1 root root 128M May  3 11:27 /tmp/web-httpd.tar

[root@Docker ~]# cat /tmp/web-httpd.tar | docker import - test/web-httpd:2.4 导入容器快照
sha256:c4a6d39dc50fd85638374ce73ab7ac94b9a565355318559c58f35e1e7654f796
[root@Docker ~]# docker image ls                        查看镜像
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test/web-httpd      2.4                 c4a6d39dc50f        16 seconds ago      128MB

此外, 也可以通过指定 URL 或者某个目录来导入, 例如
$ docker import http://example.com/exampleimage.tgz example/imagerepo

导出和导入

注: 用户既可以使用 docker load 来导入镜像存储文件到本地镜像库, 也可以使用 docker import 来导入一个容器快照到本地镜像库。 这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息( 即仅保存容器当时的快照状态) , 而镜像存储文

件将保存完整记录, 体积也要大。 此外, 从容器快照文件导入时可以重新指定标签等元数据信息。

Docker 容器的迁移方法

Docker技术兴起的原动力之一,就是在不同的机器上创建无差别的应用运行环境,因此能够方便的实现"在某台机器上导出一个Docker容器并且在另外一台机器上导入"这一操作,就显得非常必要。

上面的docker export和docker import命令实现了这一功能。当然由于Docker容器与镜像的天然联系性,容器持久化的操作也可以通过镜像的方式达到,这里可以用到的方法是docker save和docker load命令进行迁移。

docker export用于持久化容器,而docker save用于持久化镜像;将容器导出后再导入(exported-imported)后的容器会丢失所有的历史,而保存后再加载(save-loaded)的镜像则没有丢失历史和层,这意味着后者可以通过docker tag命令实现层回滚,而前者不行。

下面从实现角度来看一下export和save。

docker export命令导出容器:

    Docker server接收相应的HTTP请求后,会通过daemon实例调用ContainerExport方法来进行具体的操作,这个过程的主要步骤如下:

    根据命令行参数(容器名称)找到待导出的容器。

    对该容器调用container.Export()函数导出容器中的所有数据,包括:挂载待导出容器的文件系统  打包该容器basefs(即graphdriver上的挂载点)下的所有文件,以aufs为例,对于没有父镜像的容器,basefs对应的是aufs根目录下的diff路径,否则对应mnt路径 ; 返回打包文档的结果并卸载该容器。
    将导出的数据回写到HTTP请求应答中。

docker save命令保存镜像:

    根据待处理的容器数目发送不同格式的HTTP请求,分别为/images/get与/images/{name:.*}/get,前者对应数量为1的情况,后者对应数量大于1的情况。
    Docker client发来的以上两种请求均由getImagesGetHandler进行处理,该Handler调用ImageExport函数进行具体的处理。
    CmdImageExport函数负责查询到所有被要求export的镜像ID,并调用exportImage函数。如果用户没有指定镜像标签,则导出该repository下所有的镜像。另外,还会将被导出的repository的名称、标签及ID信息以JSON格式写入到名为repositories的文件中。最后执行文件压缩并写入到输出流。
    exportImage函数是一个for循环,将对各个依赖的layer进行export工作,即从顶层layer、其父layer及至base layer。循环内的具体工作如下:
    为每个被要求导出的镜像创建一个文件夹,以其镜像ID命名。
    在该文件夹下创建VERSION文件,写入“1.0”。
    在该文件夹下创建json文件,在该文件中写入镜像的元数据信息,包括镜像ID、父镜像ID、以及对应的Docker容器ID等。
    在该文件夹下创建layer.tar文件,压缩镜像的filesystem。该过程的核心函数为TarLayer,对存储镜像的diff路径中的文件进行打包。
    对该layer的父layer执行下一次循环

Docker系统资源限制

Cgroup是Control group的简写,是Linux内核提供的一种限制所使用物理资源的机制,这些资源主要包括CPU、内存和blkio。

Cgoups(Control Groups)
    blkio:块设备IO
    cpu:CPU
    cpuacct:CPU资源使用报告
    cpuset:多处理器平台上的CPU集合
    devices:设备访问
    freezer:挂起或恢复任务
    memory:内存用量及报告
    perf_event:对cgroup中的任务进行统一性能测试
    net_cls:cgroup中的任务创建的数据报文的类别标识符

理论

img

img

img

img

img

查看一下服务器的CPU核数:

lscpu  #或者 cat /proc/cpuinfo

下载压测镜像并测试:

imgimg

img

内存测试:

img

-m:指定docker的内存

--vm:

img

img

CPU测试:

img

img

img

img

img

1.对CPU的控制

1.限制CPU使用个数

默认情况下,访问主机的CPU是无限制的,我们可以使用CPUs参数设置cpu限制。

可以设置成小数--cpus=1.5。该选项代表使用cpu的百分比,而不是具体的个数。例如主机一共有四个cpu,设--cpus=2,不代表有两个cpu被100%占用,另外两个完全空闲,可能四个cpu各被占用50%。

      --cpus decimal                   Number of CPUs
#例如,让我们约束容器最多使用两个cpu:
docker run --cpus=2 nginx

2.限制CPU使用速率

在Docker中可以通过`--cpu-quota`选项来限制CPU的使用率。CPU的百分比是以1000位单位的。
比如:docker run --cpu-quota 2000 镜像名      # CPU的使用率限定为20%

#对于已经存在的容器,还可以修改Cgroup配置文件/sys/fs/cgroup/cpu/docker/容器编号/cpu.cfs_quota_us的值来实现
[root@localhost ~]# echo 2000 > /sys/fs/cgroup/cpu/docker/ab8c15815d5b2b6db2fbce18df942a8b06b055a7b58c64fd70bc37850f21c1dc/cpu.cfs_quota_us

3.多任务按比例分享CPU

当多个容器任务运行时,很难计算CPU的使用率,为了让容器合理使用CPU的资源,可以通过--cpu-shares选项设置CPU按比例共享CPU资源,这种方式还以实现CPU使用率的动态调整

eg:运行三个容器A、B、C,占用CPU资源比例为1:1:2
[root@localhost ~]# docker run --cpu-shares 1024 容器A
[root@localhost ~]# docker run --cpu-shares 1024 容器B
[root@localhost ~]# docker run --cpu-shares 2048 容器C
如果又有一个容器D需要更多CPU资源,可以将`--cpu-share`值设为4096。占用比例变为1:1:2:4。
#默认值是1024,数字越高优先级越高:
docker run --cpus=2 --cpu-shares=2000 nginx

4.限制CPU内核使用

在Docker中使用`--cpuset-cpus`选项来使某些程序独享CPU内核,以便提高其处理速度,对应的Cgroup配置文件为/sys/fs/cgroup/cpuset/docker/容器编号/cpuset.cpus。选项后直接跟参数0、1、2……表示第1个内核、第2个内核、第3个内核,与/proc/cpuinfo中的CPU编号(processor)相同。

如果服务器有16个核心,那么CPU编号为0~15,使容器绑定第1~4个内核使用,则:
[root@localhost ~]# docker run --cpuset-cpus 0,1,2,3 容器名
那么该容器内的进程只会在编号1、2、3、4的CPU上运行。

2.对内存使用使用限制

在Docker中可以通过docker run -m命令来限制容器内存使用量,相应的Cgroup配置文件为/sys/fs/cgroup/memory/memory.limit_in_bytes

注意:一旦容器Cgroup使用的内存超过了限制容量,Linux内核将尝试收回这些内存,如果仍旧没法控制使用在限制范围内,进程会被杀死。

eg:限制容器的内存为512Mb
[root@localhost ~]# docker run -m 512m 容器名

#限制容器的内存为2G
-m 2G
#还可以设置一个软限制或者叫保留,当docker检测到主机内存不足时激活:
docker run -m 512m --memory-reservation=256m nginx

3.对blkio的限制

如果一台服务器上进行混合部署,那么会同时出现几个程序写磁盘数据的情况,这时可以通过--device-write-iops选项来限制写入的iops,相应的还有--device-read-bps选项可以限制读取的iops。但是这种方法只能针对blkio限制的是设备(device),而不是分区。相应的Cgroup写配置文件/sys/fs/cgroup/blkio/docker/容器ID/blkio.throttle.write_iops_device

eg:

限制容器的/dev/sda1的写入iops为1MB

[root@localhost ~]# docker run --device-write-bps /dev/sda1:1mb 容器名

Docker 数据管理

Docker存储驱动

官网文档:https://docs.docker.com/engine/admin/volumes/

为了支持(镜像分层与写时复制机制)这些特性,Docker提供了存储驱动的接口。存储驱动根据操作系统底层的支持提供了针对某种文件系统的初始化操作以及对镜像层的增、删、改、查和差异比较操作。目前存储系统的接口已经有aufs、btrfs、device mapper、vfs、overlay这5种具体实现,其中vfs不支持写时复制,是为使用volume(Docker提供的文件管理方式)提供的存储驱动,仅仅做了简单的文件挂载操作;剩下4种存储驱动支持写时复制,它们的实现由一定的相似之处

存储驱动的功能与管理

Docker中管理文件系统的驱动为graphdriver。其中定义了统一的接口对不同的文件系统进行管理,在Docker daemon启动时就会根据不同的文件系统选择合适的驱动

存储驱动接口定义

GraphDriver中主要定义了Driver和ProtoDriver两个接口,所有的存储驱动通过实现Driver接口提供相应的功能,而ProtoDriver接口则负责定义其中的基本功能。这些基本功能包括如下8种:

        String()  :返回一个代表这个驱动的字符串,通常是这个驱动的名字
        Create() : 创建一个新的镜像层,需要创建一个唯一的ID和所需的父镜像的ID。
        Remove()  尝试根据指定的ID删除一个层。
        Get()  返回指定ID的层的挂载点的绝对路径。
        Put() : 释放一个层使用的资源,比如卸载一个已经挂载的层。
        Exists()  查询指定的ID对应的层是否存在。
        Status()  返回这个驱动的状态,这个状态用一些键值对表示。
        Cleanup() : 释放由这个驱动管理的所有资源,比如卸载所有的层。

而正常的Driver接口实现则通过包含一个ProtoDriver的匿名对象实现上述8个基本功能,除此之外,Driver还定义了其他4个内部方法用于对数据层之间的差异(diff)进行管理,包括比较某个镜像和父镜像的差异,应用某个差异的内容等。

GraphDriver还提供了naiveDiffDriver结构,这个结构就包含了一个ProtoDriver对象并实现了Driver接口中与差异有关的方法,可以看做Driver接口的一个实现

存储驱动的创建过程

首先,前面提到的各类存储驱动都需要定义一个属于自己的初始化过程,并且在初始化过程中向GraphDriver注册自己。GraphDriver维护了一个driver列表,提供从驱动名到驱动初始化方法的映射,这用于将来根据驱动名称查找对应驱动的初始化方法。而所谓的注册过程,则是存储驱动通过调用GraphDriver提供自己的名字和对应的初始化函数,这样GraphDriver就可以将驱动名和这个初始化方法保存到drivers。

当需要创建一个对应的驱动时(比如aufs的driver),GraphDriver会根据名字从driver中查找到这个驱动对应的初始化方法,然后调用这个初始化函数得到对应的Driver对象。创建过程如下所示:

  1. 依次检查环境变量DOCKER_DRIVER和变量DefaultDriver是否提供了合法的驱动名字(比如aufs),其中DefaultDriver是从Docker daemon启动时的-storage-driver配置中读出的。获知了驱动名称后,GraphDriver就调用对应的初始化方法创建一个对应的Driver对象实体。
  2. 若环境变量和配置默认是空的,则GraphDriver会从驱动的优先级列表中查找一个可用的驱动。"可用"包含两个意思:第一,这个驱动曾经注册过自己;第二,这个驱动对应的文件系统被操作系统底层支持(这个支持性检查会在该驱动的初始化过程中执行)。目前优先级列表依次包含了这些驱动:aufs、btrfs、drive mapper、vfs和overlay。

  3. 如果在上述5种驱动中查找不到可用的,则GraphDriver会查找所用注册过的驱动,找到第一个注册过的、可用的驱动并返回。不过这一设计只是为了将来的可扩展性而存在,因为现在有且仅由的上述5种驱动一定会注册自己的

aufs与Device Mapper驱动

aufs

aufs(AnotherUnionFS)是一种支持联合挂载的文件系统,简单说就是支持将不同目录挂载到同一个目录下,这些挂载操作对用户来说是透明的,用户在操作该目录时并不会觉得与其他目录又什么不同。这些目录的挂载是分层次的,通过来说最上层是可读写层,下层是只读层。所以,aufs的每一层都是一个普通文件系统。当需要读取一个文件A时,会从最顶层的读写层开始向下寻找,本层没有,则根据层之间的关系到下一层开始找,直到找到第一个文件A并打开它。当需要写入一个文件A时,如果这个文件不存在,则在读写层新建一个;否则像上面的过程一样从顶层开始查找,直到找到最近的文件A,aufs会把这个文件复制到读写层进行修改。

由此可以看出,在第一次修改某个已有文件时,如果这个文件很大,即使只要修改几个字节,也会产生巨大的磁盘开销。

当需要删除一个文件时候,如果这个文件仅仅存在于读写层中,则可以直接删除这个文件;否则就需要先删除它在读写层中的备份,再在读写层中创建一个whiteout文件来标志这个文件不存在,而不是真正删除底层的文件。

当新建一个文件时,如果这个文件在读写层存在对应的whitout文件,则先将whitout文件删除再新建。否则直接再读写层新建即可。

Device Mapper

Device Mapper是一个基于kernel的框架,它增强了很多Linux上的高级卷管理技术。Docker的devicemapper驱动在镜像和容器管理上,利用了该框架的超配和快照功能。Docker最初运行在Ubuntu和Devian上,并且使用AUFS作为存储后端。当Docker变得流行后,很多想使用它的公司正在使用RHEL。不幸的是,因为Linux主线kernel并不包含AUFS,所以RHEL并没有支持AUFS。为了改变这种情况,Red Hat开发者研究将AUFS包含进kernel主线。最后,他们认为开发一种新的存储后端是更好的主意。此外,他们打算使用已经存在的Device Mapper技术作为新存储后端的基础。Red Hat与Docker公司合作贡献这个新驱动。因为这次合作,Docker Engine被重新设计为存储驱动插件化。因此devicemapper成为Docker支持的第二个存储驱动。Device Mapper在2.6.9之后就被合入Linux kernel主线,也是RHEL家族发布包的核心部分。这意味着devicemapper存储驱动基于稳定的代码,有着大量的工作产品和极强的社区支持。

Device Mapper包括3个概念:映射设备、映射表和目标设备。如下图

image-20221109112547467

如上图所示,映射设备是内核向外提供的逻辑设备。一个映射设备通过一个映射表与多个目标设备映射起来,映射表包含了多个多元组,每个多元组记录了这个映射设备的起始地址、范围与一个目标设备的地址偏移量的映射关系。目标设备可以是一个物理设备,也可以是一个映射设备,这个映射设备可以继续向下迭代。一个映射设备最终通过一颗映射树映射到物理设备上。Device Mapper本质功能就是根据映射关系描述IO处理规则,当映射设备接收到IO请求的时候,这个IO请求会根据映射表逐级转发,直到这个请求最终传到最底层的物理设备上。

Device Mapper存储驱动使用Device Mapper的精简配置模块实现镜像的分层,这个模块使用了两个块设备,一个用于存储数据,另一个用于存储元数据。数据区可以看做是一个资源池,为生成其他块设备提供资源,元信息存储了虚拟设备和物理设备的映射关系。Copy on Write发生再快存储级别,Device Mapper通过从已有设备创建快照的方式创建新的设备(最开始有一个基础设备,所有的设备都直接或间接的从这个设备创建快照),这些新创建的块设备在写入内容之前并不会分配资源。所有的容器和镜像都有自己的块设备,它们在任何时候都能创建快照供新的内容或镜像使用。

~~Docker使用Device Mapper文件系统时,在/var/lib/docker/volumes目录下有三个子文件件,其中mnt为设备挂载目录,devicemapper下存储了具体的文件内容(它作为一个资源池存在),metadata下存储了每个块设备的信息~~

Docker数据卷(存储卷)的使用

容器中管理数据主要有两种方式数据卷(Data Volumes)数据卷容器(Data Volume Dontainers)

image-20221109112611039

img

img

img

img

img

img

img

Docker的镜像是由一系列的只读层组合而来的,当启动一个容器时,Docker加载镜像的所有只读层,并在最上层加入一个读写层。这个设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间,然而也存在如下问题:

1、容器中的文件在宿主机上存在形式复杂,不能再宿主机上很方便地对容器中的文件进行访问。

2、多个容器之间的数据无法共享。

3、当删除容器时,容器产生的数据将丢失。

为了解决上面的是哪个问题,Docker引入了数据卷(volume)机制。volume是存在于一个或多个容器中的特定文件或文件夹,这个目录能够以独立于联合文件系统的形式再宿主机中存在,并为数据的共享与持久化提供以下便利

volume在容器创建时就会初始化,在容器运行时就可以使用其中的文件

volume能在不同的容器之间共享和重用

对volume中数据的操作会马上失效

对volume中数据的操作不会影响到镜像本身。

volume的生存周期独立于容器的生存周期,即使删除容器,volume仍然会存在,没有任何容器使用volume也不会被Docker删除

数据卷(Data Volumes)

数据卷(data volumes):

它位于容器中,可以将宿主机的目录挂载到数据卷上,数据卷的修改操作立刻可见,且更新数据不影响镜像,从而实现宿主机和容器间的迁移。

数据卷 是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:

  • 数据卷 可以在容器之间共享和重用

  • 对 数据卷 的修改会立马生效

  • 对 数据卷 的更新,不会影响镜像

  • 数据卷 默认会一直存在,即使容器被删除

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

创建、查看数据卷

# 查看所有的数据卷
$ docker volume ls
local               my-vol

# 创建数据卷
# docker volume create my-vol


# 在主机里使用以下命令可以查看指定 数据卷 的信息
# docker volume inspect my-vol  ||  docker inspect my-vol
[
    {
        "CreatedAt": "2019-09-25T16:48:13+08:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

# 在宿主机里使用以下命令可以查看 web 容器的信息
$ docker inspect web |grep -A 10 Mounts  # 数据卷 信息在 "Mounts" Key 下面
"Mounts": [
    {
        "Type": "volume",
        "Name": "my-vol",
        "Source": "/var/lib/docker/volumes/my-vol/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

从容器挂载volume不指定volume name

在用docker run命令的时候,使用-v标记可以在容器内创建一个数据卷。多次使用-v标记可以创建多个数据卷

eg:

# docker run -it -P --name vol_no1 -v /opt/data01 centos /bin/bash

这条命令在创建容器会将容器中的/opt/data01作为一个volume挂载点。volume的路径必须写绝对路径。如果镜像中不存在/opt/data01文件夹,容器启动后就会创建一个名为/opt/data01的空文件夹;反之如果镜像中存在/opt/data01文件夹,这个文件夹中的内容将全部被复制到宿主机对应的文件夹中,并且根据容器中的文件为宿主机中的文件设置合适的权限和所有者。

# docker inspect --format={{.Config.Volumes}} vol_no1   # 从下面的结果可以看到vol_no1容器的挂载目录是/opt/data01
map[/opt/data01:{}]
# docker inspect --format={{.Mounts}} vol_no1  # 可以查看vol_no1容器挂载的详细信息
[{volume c795a864028c4f1f1107570db77aa0be9c4adc5cd3af6ef4a6d9bfd7636e1154 /var/lib/docker/volumes/c795a864028c4f1f1107570db77aa0be9c4adc5cd3af6ef4a6d9bfd7636e1154/_data /opt/data01 local  true }]

从上面的结果可以看到volume ID,以及volume ID的绝对路径,以及容器的挂载点目录,是本机挂载的形式,是挂载可读写状态

# cd /var/lib/docker/volumes/c795a864028c4f1f1107570db77aa0be9c4adc5cd3af6ef4a6d9bfd7636e1154/_data     #进入到volume ID的目录

# pwd
/var/lib/docker/volumes/c795a864028c4f1f1107570db77aa0be9c4adc5cd3af6ef4a6d9bfd7636e1154/_data
[root@localhost _data]# vim nihao-docker    #我们在此目录下创建了个nihao-docker的文件,其内容为下面的内容
test
this is vol_no1

[root@dec6a1a4ee3e ~]# cat /opt/data01/nihao-docker #在容器上面查看一下
test
this is vol_no1

然后你再容器上面的/opt/data01目录下面创建文件修改文件,发现宿主机上面的volume ID目录下面也会随之变化。这就是挂载也就是映射

# docker run -it --name vol_no3 -v /opt/data01 -v /opt/data02 -v /opt/data03 centos /bin/bash  
#可以测试挂载三个目录

image-20221109130247812

容器里面已经在/opt下面产生了三个目录

# docker inspect --format={{.Config.Volumes}} vol_no3  # 宿主机上面查看
map[/opt/data01:{} /opt/data02:{} /opt/data03:{}]
# docker inspect --format={{.Mounts}} vol_no3  # 可以看到产生了三个volume ID以及目录
[{volume 54b27341d3f2d8468f7f758fd9ba184bb6688462cea815baa7761fbe3e3e38af /var/lib/docker/volumes/54b27341d3f2d8468f7f758fd9ba184bb6688462cea815baa7761fbe3e3e38af/_data /opt/data02 local  true } {volume a4310209874114037742f3d45f7de51a2989347eb498f1245d0d200a31518a57 /var/lib/docker/volumes/a4310209874114037742f3d45f7de51a2989347eb498f1245d0d200a31518a57/_data /opt/data03 local  true } {volume cc01f0a35c2f7ccf34c54f86726b646314d867b224d32850f6708af165e668c5 /var/lib/docker/volumes/cc01f0a35c2f7ccf34c54f86726b646314d867b224d32850f6708af165e668c5/_data /opt/data01 local  true }]

从容器挂载volume并指定volume name

docker run -itd  \
    --name clickhouse \
    --ulimit nofile=262144:262144  \
    --restart always \
    --net host \
    -u root \
    --volume clickdata:/bitnami/ \
    --privileged=true \
    --env ALLOW_EMPTY_PASSWORD=yes \
    bitnami/clickhouse:latest
# --volume clickdata:/bitnami/  必须用数据卷挂载,否则无法启动
# docker volume ls
DRIVER    VOLUME NAME
local     clickdata

# ll /var/lib/docker/volumes/clickdata/_data/clickhouse/
total 0
drwxrwxr-x 14 polkitd root 267 Apr  6 12:19 data
drwxrwxr-x  2 polkitd root   6 Apr  4 19:12 etc

如果不指定volume name,会自动随机生成一个

docker run -itd  \
    --name clickhouse \
    --ulimit nofile=262144:262144  \
    --restart always \
    --net host \
    -u root \
    --volume /bitnami/ \
    --privileged=true \
    --env ALLOW_EMPTY_PASSWORD=yes \
    bitnami/clickhouse:latest

查看结果

# bash clickhouse.sh 
f33bad00a5731dccf01c219c38f52e6d430e8724d5d391fb1d7135be2d8da6df
# docker volume ls
DRIVER    VOLUME NAME
local     b8d284573c72c4e5193cf0d141e07d7b5ffeac57a978c4a0a26de91f16d4a39d
# ll /var/lib/docker/volumes/b8d284573c72c4e5193cf0d141e07d7b5ffeac57a978c4a0a26de91f16d4a39d/_data/clickhouse/
total 0
drwxrwxr-x 14 polkitd root 267 Apr  6 12:35 data
drwxrwxr-x  2 polkitd root   6 Apr  4 19:12 etc

从宿主机挂载volume

在创建新容器的时候可以挂载一个主机上特定的目录到容器中。

eg:

将宿主目录/var/www共享(挂载到容器中的/data1)下去

[root@Docker ~]# docker run -d -v /var/www:/data1 --name web-nginx nginx:latest
92397df5163513608416bf61e7f466ea33fb26113fa5d707ead75bb7268cb741
[root@Docker ~]# docker ps

image-20221109130452982

在宿主机共享目录里面写入数据:

[root@Docker ~]# echo "test mount data " >> /var/www/testdata.txt

进入容器内,验证数据是否同步过去了

[root@Docker ~]# docker exec -it web-nginx /bin/bash
root@92397df51635:/# ls data1/testdata.txt
data1/testdata.txt
root@92397df51635:/# cat /data1/testdata.txt            #验证ok
test mount data

eg:

# docker run -it --name vol_from_host_no1 -v /docker/data01:/opt/data01 centos /bin/bash

上面的命令,将宿主机的/docker/data01文件夹作为一个volume挂载到容器中的/opt/data01。文件夹必须使用绝对路径,如果宿主机不存在/docker/data01,将创建一个空文件夹。在/docker/data01文件夹中的所有文件或文件夹可以再容器的/opt/data01文件夹下被访问。如果镜像中原本存在/opt/data01文件夹,该文件夹下原有的内容将被删除,以保持与宿主机中的文件夹一致

image-20221109130637371

eg:

只读挂载:

从上图可以看出,容器Docker挂载数据卷的默认权限是读写(rw),用户也可以通过ro指定为只读。(比如我这个挂载的目录,如果让其他的docker容器只读就好了,它们不要操作这个挂载目录。)

# docker run -it --name vol_from_host_no2 -v /docker/data01:/opt/data01:ro centos /bin/bash   #现在已只读的形式挂载上面的那个宿主机上面的共享挂载点/docker/data01

image-20221109130735238

在宿主机/docker/data01该目录下写入数据,验证容器里面是否可用读取:

image-20221109130747053

进入容器,验证是否成功读取:(验证成功)

image-20221109130806099

虽然从目录权限上面看不出什么差别,但是当写入的时候就会报错了,当然是可以查看的,可以看到no1这个文件就是vol_from_host_no1这个容器写的内容。

image-20221109130834126

从宿主机上面查看一下,这两个容器对/docker/data01是有差别的,上面的只有ro,下面的是true也就是可读写。

历史记录:

# touch /docker/history/test1_history   # 这个test1_history文件一定要提前创建啊,不然就又成了挂载目录了。

image-20221109130851685

# docker run -it --name test1 -v  /docker/history/test1_history:/root/.bash_history centos /bin/bash   #这样就把/docker/history/test1_history 挂载到了test1容器的/root/.bash_history

image-20221109130931092

然后再新创建的容器上面随便操作点什么然后退出。

# cat /docker/history/test1_history   #这个机器用root在shell界面操作了什么,我可以看到了。需要退出容器,然后在宿主机上面才能查看到信息

image-20221109131012081

使用Dockerfile添加volume

VOLUME /data

在使用docker build命令生成镜像并且以该镜像启动容器时会挂载一个volume到/data。如果镜像中存在/data文件夹,这个文件夹中的内容将全部被复制到宿主机中对应的文件夹中,并且根据容器中的文件设置合适的权限和所有者。

类似地,可以使用VOLUME指令添加多个volume:

VOLUME ["/data1","data2"]

与使用docker run -v不同的是,VOLUME指令不能挂载主机中指定的文件夹。这是为了保证Dockerfile的可移植性,因为不能保证所有的宿主机都有对应的文件夹。

需要注意的是,在Dockerfile中使用VOLUME指定之后的代码,如果尝试对这个volume镜像修改,这些修改都不会生效。这是由于Dockerfile中除了FROM指令的每一行都是基于上一行生成的临时镜像运行一个容器,执行一条指令并执行类似docker commit的命令得到一个新的镜像,docker commit命令不会对挂载的volume进行保存,VOLUME指令是在容器运行时才去挂载volume,而RUN指令在构建镜像的时候就会执行;所以上面的Dockerfile最后两行执行时,都会在一个临时的容器上挂载/data,并对这个临时的volume进行操作,但是这一行指令执行并提交后,这个临时的volume没有被保存,我们通过最后生成的镜像创建的容器所挂载的volume是没有操作的。

数据卷容器(Data Volume Dontainers)

在使用docker rundocker create创建新容器时,可以使用--volumes-from标签使得容器与已有的容器共享volume.

作用:数据迁移

在容器间共享一些数据,它是一个普通的容器,专门提供数据卷给其他容器挂载使用。

eg:
# docker run --rm -it --name test2 --volumes-from vol_from_host_no1 centos /bin/bash  

image-20221109131331960

新创建的容器test2与之前创建的容器vol_from_host_no1共享volume,这个volume挂载在/opt/data01上,如果被共享的容器有多个volume,新容器也将有多个volume。

在Docker容器退出时,默认容器内部的文件系统仍然被保留,以方便调试并保留用户数据。但是,对于foreground容器,由于其只是在开发调试过程中短期运行,其用户数据并无保留的必要,因而可以在容器启动时设置--rm选项,这样在容器退出时就能够自动清理容器内部的文件系统。显然,--rm选项不能与-d同时使用,即只能自动清理foreground容器,不能自动清理detached容器注意,--rm选项也会清理容器的匿名data volumes。所以,执行docker run命令带--rm命令选项,等价于在容器退出后,执行docker rm -v

也就是你exit这个容器,再次查看这个容器已经不存在了,但是它对共享卷的操作还是存在的,因为写操作已经写入到宿主机的挂载卷所在的文件了。

# docker run --rm -it --name test2 --volumes-from vol_from_host_no1 centos --volumes-from vol_no1 /bin/bash   # 可以多次使用--volumes-from标签与多个已有容器共享volume

image-20221109131544693

因为我们上面vol_no1做测试的时候volume目录也挂的是/opt/data01,因为命令vol_no1在后面,所以vol_no1就把vol_from_host_no1的/opt/data01挂载给替换掉了。

image-20221109131553721

从上图可以看出vol_no1容器并没有再运行,所以一个容器挂载了volume,即使这个容器停止运行,该volume仍然存在,其他容器也可以使用--volumes-from与这个容器共享volume。如果有一些数据,比如配置文件、数据文件等,要与多个容器之间共享,一种常见的做法就是创建一个数据容器,其他容器与之共享volume。然后这个数据容器就可以停止运行避免浪费资源了。

这种做法就叫做数据卷容器,如果删除了挂载的容器,数据卷并不会被自动删除。如果要删除一个数据卷,必须再删除最后一个还挂载着它的容器时。使用docker rm -v命令来指定同时删除关联的容器。使用数据卷容器可以让用户在容器之间自由地升级和移动数据卷。

eg:使用方法:
1、创建一个容器作为`数据卷容器`
[root@Docker ~]#docker run -d -v /data --name httpddata httpd
fc991708f8d4c647cafdd62cd0f2daf6d7c30a503bebb01b2e1d3bcd0b0324c7
登入至容器,验证是否有data目录
[root@Docker ~]#docker exec -it httpddata /bin/bash
root@fc991708f8d4:/# ls /data/          #验证成功

2、在其他容器创建时使用--volumes-from来挂载数据卷容器
[root@Docker ~]#docker run -d -it --volumes-from httpddata --name db httpd:latest
55a89f84b04886223f952610594aecc2943e3ee70a2fc71429a493821187f022
进入db容器,查看是否有data目录
[root@Docker ~]#docker exec -it db /bin/bash
root@55a89f84b048:/usr/local/apache2# cd /data/
root@55a89f84b048:/data# ls
root@55a89f84b048:/data# mkdir haha
root@55a89f84b048:/data# mkdir ok
root@55a89f84b048:/data# echo "haha ,it's ok" > testdata.txt
root@55a89f84b048:/data# ls
haha  ok  testdata.txt
root@55a89f84b048:/data# exit
exit
进入httpddata服务器,进行查看是否有数据同步至本容器
[root@Docker ~]#docker exec -it httpddata /bin/bash
root@fc991708f8d4:/usr/local/apache2# cd /data/
root@fc991708f8d4:/data# ls         #it’s ok
haha  ok  testdata.txt
在创建一些数据,然后登陆db容器,再次验证数据是否同步
[root@Docker ~]#docker exec -it httpddata /bin/bash
root@fc991708f8d4:/usr/local/apache2# cd /data/
root@fc991708f8d4:/data# ls
haha  ok  testdata.txt
root@fc991708f8d4:/data# mkdir hello
root@fc991708f8d4:/data# echo "test" > test1.txt
登入至db容器,进行验证
[root@Docker ~]#docker exec -it db /bin/bash
root@55a89f84b048:/usr/local/apache2# cd /data/
root@55a89f84b048:/data# ls
haha  hello  ok  test1.txt  testdata.txt
root@55a89f84b048:/data# cat test1.txt
test

ok了

备份、恢复或迁移volume(利用数据卷容器迁移数据)

可以利用数据卷容器对其中的数据卷进行备份、恢复,以实现数据的迁移。volume作为数据的载体,在很多情况下需要对其中的数据进行备份、迁移,或是从已有数据恢复。以上面vol_no1为例,该容器在/opt/data01挂载了一个volume。如果需要将这里的数据备份,一个很容易想到的方法就是使用docker inspect命令查找到/opt/data01在宿主机上对应的文件夹位置,然后复制其中内容或是使用tar进行打包;同样地,如果需要恢复某个volume中的数据,可以查找到volume对应的文件夹,将数据复制进这个文件夹或是使用tar从文档文件中恢复。下面用命令来备份一下。

# docker run --rm --volumes-from vol_from_host_no2 -v /docker/backup/vol_from_host_no2:/backup --name worker_backup centos tar cvf /backup/backup.tar /opt/data01  # 下面是输出结果
tar: Removing leading `/' from member names
/opt/data01/
/opt/data01/no1

上面命令的意思是,先用--rm启动一个临时的容器worker_backup, 然后将vol_from_host_no2的volume共享给worker_backup,然后将本地的/docker/backup/vol_from_host_no2目录挂载到worker_backup的/backup目录上,然后将worker_backup容器上的/opt/data01打包到worker_backup容器的/backup/目录下包名叫做backup.tar。然后因为是临时容器,容器做完操作就消亡了

image-20221109131825852

从宿主机本地目录查看一下,可以看到/opt/data01的目录结构和里面的内容都备份了出来。

如果要恢复数据到一个容器:

# docker run -d -it --name vol_no2_bak -v /opt/data01 centos /bin/bash  # -d是放到后台运行,先创建一个新容器作为数据恢复的目录。

# docker run --rm --volumes-from vol_no2_bak -v /docker/backup/vol_from_host_no2:/backup centos tar xvf /backup/backup.tar -C /
opt/data01/
opt/data01/no1

上面第一行先搞了个新容器作为数据恢复的目标,第二行指令启动了一个临时容器(既然是临时容器就不用指定名字了),这个容器挂载了两个volume,第一个volume与要恢复的volume共享,第二个volume将宿主机刚才的辈分目录挂载到容器的/backup下,然后将这个存放文件中的backup.tar恢复到根目录下,然后执行结束后,临时容器就消失了,恢复后的数据就在vol_no2_bak的volume中了。如下图:

image-20221109131913731

可以看到之前备份的数据已经恢复到了vol_no2_bak容器里面

删除volume

如果创建容器时,从容器中挂载了volume,在/var/lib/docker/volumes下会生成与volume对应的目录,使用docker rm删除容器并不会删除与volume对应的目录,这些目录会占据不必要的存储空间,即使可以手动删除,因为这些目录名称是无意义的随机字符串,要知道它们是否与未被删除的容器对应也十分麻烦。所以在删除容器时需要对容器的volume妥善处理。在删除容器时,一并删除volume有以下两种方法。

第一种方法(docker rm -v):

# docker inspect --format={{.Mounts}} vol_no2_bak
# docker rm -v vol_no2_bak   # -v 删除容器

image-20221109132005399

可以看到随机字符串的目录一起删除掉了。

第二种方法(docker run --rm):

docker run --rm  在运行容器时,使用docker rm --rm--rm标签会在容器停止时删除容器以及容器所挂载的volume。

需要注意的是,以上方法只能在对应volume是被最后一个容器使用时才会将其删除,如果容器的volume被多个容器共享,在删除最后一个共享它的容器时将其删除。

如果volume是在创建容器时从宿主机中挂载的,无论对容器进行任何操作都不会导致其在宿主机被删除,如果不需要这些文件,只能手工删除它们

删除数据卷

$ docker volume rm my-vol

数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令

无主的数据卷可能会占据很多空间,要清理请使用以下命令

$ docker volume prune

Docker容器网络(4种)

image-20221109132125570

第一种:none(孤岛)

没有任何的网络接口,适合于一些计算和程序运行完后输出结果到宿主机文件系统。

第二种:bridge(网桥)

这种方式会在每个容器的网络名称空间中虚拟出一对网络接口,一个接口接在容器中,另一端接在宿主机的虚拟交换机(docker0)上

第三种:( joined联盟式)容器中共享了网络名称空间(上层的名称空间并没有共享,仍然是隔离的)只是共享了网络接口,同样一端接在宿主机的虚拟交换机上,另一端接在容器中。

第四种:host与宿主机共享网络名称空间(公用宿主机的网络接口)

首先,要实现网络通信,机器需要至少一个网络接口(物理接口或虚拟接口)来收发数据包;此外,如果不同子网之间要进行通信,需要路由机制。

Docker 中的网络接口默认都是虚拟的接口。虚拟接口的优势之一是转发效率较高。 Linux 通过在内核中进行数据复制来实现虚拟接口之间的数据转发,发送接口的发送缓存中的数据包被直接复制到接收接口的接收缓存中。对于本地系统和容器内系统看来就像是一个正常的以太网卡,只是它不需要真正同外部网络设备通信,速度要快很多。

Docker 容器网络就利用了这项技术。它在本地主机和容器内分别创建一个虚拟接口,并让它们彼此连通(这样的一对接口叫做 veth pair)

docker0是net桥,自动创建,默认容器网络只做snat,没做dnat,容器能访问外网,但不能被外网访问默认跨物理主机不能通信。需要在net桥做dnat或者通过隧道方式,实现网络的访问。

docker启动容器后会自动在宿主机创建NAT桥,网段默认为172.17.0.0/16,容器启动后,宿主机的防火墙会创建很多和docker相关的规则,使用命令iptables -t nat -vnL进行查看,以下的规则,说明只要源地址是172.16.0.16/16且转发出去的网络不经过docker0桥,即不是内部转发,就做地址伪装,即SNAT,target为MASQUERADE

image-20221109132333955

创建容器时,可为docker run命令使用--net选项指定要加入的网络

Docker使用网络的方式,Docker安装完成后,会自动创建三个网络,可使用docker network ls命令查看,都是本地

bridge :桥接式网络,,默认指docker0,即bridged container
host :开放式网络,主机桥,表示使用主机的网络,即open container,主机网络
none :封闭式网络,指没有网络,不使用网络,即closed container ,本地回环口

image-20221109132537415

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

这些网络模式在相互网络通信方面的对比如下所示:

模式 是否支持多主机 南北向通信机制 东西向通信机制
bridge 宿主机端口绑定 通过Linux bridge
host 按宿主机网络通信 按宿主机网络通信
none 无法通信 只能用link通信
其他容器 宿主机端口绑定 通过link通信
用户自定义 按网络实现而定 按网络实现而定 按网络实现而定

bridge模式->nat桥

bridge模式是docker默认的,也是开发者最常使用的网络模式。在这种模式下,docker为容器创建独立的网络栈,保证容器内的进程使用独立的网络环境,实现容器之间、容器与宿主机之间的网络栈隔离。同时,通过宿主机上的docker0网桥,容器可以与宿主机乃至外界进行网络通信。其网络模型可以参考下图:

image-20221109142853659

从该网络模型可以看出,容器从原理上是可以与宿主机乃至外界的其他机器通信的。同一宿主机上,容器之间都是连接到docker0这个网桥上的,它可以作为虚拟交换机使容器可以相互通信。然而,由于宿主机的IP地址与容器veth pair的 IP地址均不在同一个网段,故仅仅依靠veth pair和namespace的技术,还不足以使宿主机以外的网络主动发现容器的存在。为了使外界可以方位容器中的进程,docker采用了端口绑定的方式,也就是通过iptables的NAT,将宿主机上的端口端口流量转发到容器内的端口上。

举一个简单的例子,使用下面的命令创建容器,并将宿主机的3306端口绑定到容器的3306端口:

docker run -tid –name db -p 3306:3306 MySQL

在宿主机上,可以通过iptables -t nat -L -n,查到一条DNAT规则:

DNAT tcp  0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 to:172.17.0.5:3306

上面的172.17.0.5即为bridge模式下,创建的容器IP。

很明显,bridge模式的容器与外界通信时,必定会占用宿主机上的端口,从而与宿主机竞争端口资源,对宿主机端口的管理会是一个比较大的问题。同时,由于容器与外界通信是基于三层上iptables NAT,性能和效率上的损耗是可以预见的

eg:网桥(bridge)

1、安装brctl工具

yum install bridge-utils -y

2、查看docker的网络接口

brctl show  # 或者 ip link show 

image-20221109143127402

它是docker默认的网络连接方式,是nat桥,自动做了nat

image-20221109143143885

host模式

HOST:直接将容器的网络使用宿主机的网络名称空间[可以自行指定网络名称空间]

Host:直接将容器的\'网络名称空间\'共享给宿主机的网络名称空间,容易造成\'网络的广播风暴\'

由于容器和宿主机共享同一个网络命名空间,换言之,容器的IP地址即为宿主机的IP地址。所以容器可以和宿主机一样,使用宿主机的任意网卡,实现和外界的通信。其网络模型可以参照下图:

image-20221109143223915

采用host模式的容器,可以直接使用宿主机的IP地址与外界进行通信,若宿主机具有公有IP,那么容器也拥有这个公有IP。同时容器内服务的端口也可以使用宿主机的端口,无需额外进行NAT转换,而且由于容器通信时,不再需要通过linux bridge等方式转发或者数据包的拆封,性能上有很大优势。当然,这种模式有优势,也就有劣势,主要包括以下几个方面:

  • 最明显的就是容器不再拥有隔离、独立的网络栈。容器会与宿主机竞争网络栈的使用,并且容器的崩溃就可能导致宿主机崩溃,在生产环境中,这种问题可能是不被允许的。

  • 容器内部将不再拥有所有的端口资源,因为一些端口已经被宿主机服务、bridge模式的容器端口绑定等其他服务占用掉了。

eg:共享主机的网络名称空间

image-20221109143357338

1、查看某个容器的具体网络工作方式:

[root@Docker ~]# docker container inspect httpd
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "3223912421f7b34dfe6203b63d95b74a5a6545b1cf666ace21a3dd8763d3d5e8",
                    "EndpointID": "79aa1c761909b0167948c65b3d4921a6042bb40916e4e3b80394ef2bf77898ef",
                    "Gateway": "172.17.0.1",    # 网关
                    "IPAddress": "172.17.0.2",  # IP地址
                    "IPPrefixLen": 16,          # 子网掩码
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",  #Mac地址
                    "DriverOpts": null
                }
            }

2、网络名称空间的具体操作:

[root@Docker ~]# rpm -qa iproute    # 需要使用ip命令
iproute-4.11.0-14.el7.x86_64
[root@Docker ~]# ip 
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip [ -force ] -batch filename
where  OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |
                   tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |
                   netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila |                   vrf }
       OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |
                    -h[uman-readable] | -iec |
                    -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |
                    -4 | -6 | -I | -D | -B | -0 |
                    -l[oops] { maximum-addr-flush-attempts } | -br[ief] |
                    -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] |
                    -rc[vbuf] [size] | -n[etns] name | -a[ll] | -c[olor]}

3、网络名称空间的操作:

[root@Docker ~]# ip netns help
Usage:  ip netns list       #列出名称空间
       ip netns add NAME    #创建新的名称空间
       ip netns set NAME NETNSID    #设置名称空间
       ip [-all] netns delete [NAME]    #删除名称空间
       ip netns identify [PID]
       ip netns pids NAME
       ip [-all] netns exec [NAME] cmd ...      #进入名称空间
       ip netns monitor
       ip netns list-id

[root@Docker ~]# ip netns add r1                    #创建新的名称空间r1

[root@Docker ~]# ip netns exec r1 ifconfig -a       #在名称空间
lo: flags=8<LOOPBACK>  mtu 65536
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

4、创建虚拟网卡对

[root@Docker ~]# ip link add name veth1.1 type veth peer name veth1.2
[root@Docker ~]# ip link show
14: veth1.2@veth1.1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether d2:ff:77:b7:0c:16 brd ff:ff:ff:ff:ff:ff
15: veth1.1@veth1.2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 9a:df:86:ec:f5:14 brd ff:ff:ff:ff:ff:ff
创建好了,但是都没有激活,我们用ifconfig无法看到,而且虚拟接口的两头都在宿主机上,我们需要把一头移动到新建的网络名称空间中去。

[root@Docker ~]# ip link set dev veth1.2 netns r1
[root@Docker ~]# ip link show
15: veth1.1@if14: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 9a:df:86:ec:f5:14 brd ff:ff:ff:ff:ff:ff link-netnsid 2

5、查看网络名称空间的的接口:

[root@elk-node-1 ~]# ip netns exec r1 ifconfig -a
lo: flags=8<LOOPBACK>  mtu 65536
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth1.2: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether d2:ff:77:b7:0c:16  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

还可以修改该虚拟设备的名字:(最好先改名再激活该设备)

[root@Docker ~]# ip netns exec r1 ip link set dev veth1.2 name eth0
[root@Docker ~]# ip netns exec r1 ifconfig -a
eth0: flags=4098<BROADCAST,MULTICAST>  mtu 1500
        ether d2:ff:77:b7:0c:16  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=8<LOOPBACK>  mtu 65536
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

激活虚拟设备:veth1.1

[root@Docker ~]# ifconfig veth1.1 10.1.0.1/24 up
[root@Docker ~]# ifconfig
veth1.1: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 10.1.0.1  netmask 255.255.255.0  broadcast 10.1.0.255
        ether 9a:df:86:ec:f5:14  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

激活名称空间中的另一半:eth0

[root@Docker ~]# ip netns exec r1 ifconfig eth0 10.1.0.2/24 up
[root@Docker ~]# ip netns exec r1 ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.2  netmask 255.255.255.0  broadcast 10.1.0.255
        inet6 fe80::d0ff:77ff:feb7:c16  prefixlen 64  scopeid 0x20<link>
        ether d2:ff:77:b7:0c:16  txqueuelen 1000  (Ethernet)
        RX packets 8  bytes 648 (648.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 8  bytes 648 (648.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

测试:

image-20221109144010580

6、将该网口移动到另一个名称空间(容器)中去

[root@Docker ~]# ip netns add r2                    #创建新的名称空间r2
[root@Docker ~]# ip link set dev veth1.1 netns r2   #把veth1.1移动到r2
[root@Docker ~]# ifconfig                           #查看网卡,无veth1.1
[root@Docker ~]# ip netns list
r2 (id: 3)
r1 (id: 2)

image-20221109144039462

[root@Docker ~]#ip netns exec r2 ifconfig veth1.1 10.1.0.3/24 up
[root@Docker ~]#ip netns exec r2 ifconfig -a
veth1.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.1.0.3  netmask 255.255.255.0  broadcast 10.1.0.255
        inet6 fe80::98df:86ff:feec:f514  prefixlen 64  scopeid 0x20<link>
        ether 9a:df:86:ec:f5:14  txqueuelen 1000  (Ethernet)
        RX packets 22  bytes 1908 (1.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 29  bytes 2486 (2.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

7、在名称空间r2中ping r1

[root@Docker ~]#ip netns exec r2 ping 10.1.0.2

image-20221109144127348

none模式

不给予使用网络,但仅仅只有loopback接口[容器关闭了网络接口]

在这种模式下,容器有独立的网络栈,但不包含任何网络配置,只具有lo这个loopback网卡用于进程通信。也就是说,none模式为容器做了最少的网络设置,但是俗话说得好"少即是多",在没有网络配置的情况下,通过第三方工具或者手工的方式,开发这任意定制容器的网络,提供了最高的灵活性

用户定义网络模式

在用户定义网络模式下,开发者可以使用任何docker支持的第三方网络driver来定制容器的网络。并且,docker 1.9以上的版本默认自带了bridge和overlay两种类型的自定义网络driver。可以用于集成calico、weave、openvswitch等第三方厂商的网络实现。

除了docker自带的bridge driver,其他的几种driver都可以实现容器的跨主机通信。而基于bdrige driver的网络,docker会自动为其创建iptables规则,保证与其他网络之间、与docker0之间的网络隔离。例如,使用下面的命令创建一个基于bridge driver的自定义网络:

docker network create bri1

则docker会自动生成如下的iptables规则,保证不同网络上的容器无法互相通信。

-A DOCKER-ISOLATION -i br-8dba6df70456 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-8dba6df70456 -j DROP

除此之外,bridge driver的所有行为都和默认的bridge模式完全一致。而overlay及其他driver,则可以实现容器的跨主机通信

实例1、创建封闭式容器

[root@Docker ~]# docker run --name bux -it --network none --rm busybox

只能看到lo接口:

image-20221109144332283

实例2、创建网桥容器->默认使用网桥

[root@Docker ~]# docker run --name test -it --rm busybox:latest

image-20221109144402697

指不指网络都一样,默认就是nat桥:
[root@Docker ~]# docker run --name test -it --network bridge --rm busybox:latest

image-20221109144436299

修改主机名:(如果没有修改主机名,则默认是容器的ID作为主机名)

image-20221109144502671

注入主机名:

[root@Docker ~]# docker run --name test -it -h dockerhost --rm busybox

image-20221109144528194

假如想通过主机名来访问

img

img

解析成功了:

image-20221109144626808

更改dns的搜索域

image-20221109144635305

使用host文件解析,外部注入

# 使用host文件解析,外部注入
[root@Docker ~]# docker run --name testhttpd -it --network bridge -h dockertest --dns 114.114.114.114 --dns-search haha.com --add-host www.plinux.com:2.2.2.2 --rm busybox:latest

image-20221109144730933

image-20221109144736237

Docker的网络通信

官方文档:https://docs.docker.com/engine/userguide/networking/

注意:

容器有自己的内部网络和IP地址,使用docker inspect +容器ID可以获取容器的具体信息。

重点:映射都是iptables规则

前提条件:

必须开启防火墙,否则会报如下错误:因为端口转换需要做nat

image-20221109144839146

获得容器IP

将container_name/CONTAINER ID 换成实际环境中的容器名/容器ID

# docker inspect container_name | grep IPAddress

查看容器映射的端口:

Usage:  docker port CONTAINER [PRIVATE_PORT[/PROTO]]
[root@Docker ~]# docker port 7c
80/tcp -> 0.0.0.0:49280
解释:容器的80端口映射为宿主机的49280

image-20221109145636248

端口映射

image-20221109145647531

-p:指定端口

-P:随机分配端口,默认端口49000--49900

通过docker port containerID命令查看被随机分配的口为32769与容器的80口对应

指定端口不指定IP映射:

在启动容器的时候,如果不指定对应参数,在容器外部是无法通过网络来访问容器内的网络应用和服务的。当容器中运行一些网络应用,要让外部访问这些应用时,可以通过-P或-p参数来指定端口映射。当使用-P标记时,Docker会随机映射一个49000\~49900的端口至容器内部开放的网络端口。

# docker run -itd --rm --privileged=true  --name ssh_test1  -p 20022:22 centos /usr/sbin/init 

这是创建了一个ssh_test1的容器,将容器的22端口映射到宿主机的20022,因为没有指定IP,也就是所有人都可以通过宿主机的20022端口访问ssh_test1。加--privileged=true是让容器拥有特权用户权限可以启动服务/usr/sbin/init不指定,也就是不将容器的CMD或者entrupoint设置为/usr/sbin/init的haunted,dbus等服务就起不来,你还是不能用service启动服务。如果不这么做会有下面的报错:

[root@2491b93920e3 /]# service sshd start
Redirecting to /bin/systemctl start sshd.service
Failed to get D-Bus connection: Operation not permitted
[root@localhost ~]# docker exec -it ssh_test1 /bin/bash   # 要进入容器一下,因为默认官网的镜像没有启动ssh服务,通过exec指定/bin/bash进入容器
[root@2491b93920e3 /]# yum install openssh-server -y        # 安装ssh服务
[root@2491b93920e3 /]# yum install initscripts  -y  # 需要service服务

image-20221109150045442

[root@2491b93920e3 /]# service sshd start               #启动sshd服务

image-20221109150118293

通过截图可以看到容器的sshd服务可以正常启动了,我们给其设置一个密码

image-20221109150124963

通过另外一台机器,通过访问宿主机的20022端口,然后密码验证可以成功的登录我们刚才映射22端口的容器了

image-20221109150153542

eg: 
[root@Docker ~]#docker run -d --rm --name web1 -p 49280:80 github/lamp:latest
cae84f5ce36fffdd294f57141f25580ea05b79751ada30e2a5bebf63c237326e
[root@Docker ~]#docker ps -a
CONTAINER ID        IMAGE                                      COMMAND                  CREATED             STATUS                         PORTS                                                              NAMES
cae84f5ce36f        github/lamp:latest                         "/run.sh"                3 seconds ago       Up 1 second                    3306/tcp, 0.0.0.0:49280->80/tcp                                    web1
ps:通过docker ps可以看到web1容器的80端口被映射到本机的49280端口上。访问宿主主机的49280端口就可以访问容器内的应用程序提供的Web界面:

image-20221109150240748

容器内怎么访问?

指定IP和端口映射:

上面是没有指定映射的IP地址,这里加上映射的IP地址。

[root@localhost ~]# docker run -itd --rm --privileged=true  --name ssh_test2  -p 127.0.0.1:20023:22 centos /usr/sbin/init    #指定只能本地通过20023端口登录容器ssh_test2的22端口。
[root@localhost ~]# docker ps   #可以看到ssh_test2的映射规则

image-20221109150339432

[root@localhost ~]# docker exec -it ssh_test2 /bin/bash
[root@8267f76fe11e /]# yum install openssh-server  initscripts  net-tools -y
[root@8267f76fe11e /]# service sshd start
Redirecting to /bin/systemctl start sshd.service
[root@8267f76fe11e /]# echo "666666"|passwd --stdin root
Changing password for user root.
passwd: all authentication tokens updated successfully.

# 本地ssh登录是可以的
[root@localhost ~]# ssh 127.0.0.1 -p 20023

image-20221109150453398

非宿主机以外的主机想登录是不可以的因为监听地址是127.0.0.1

[root@localhost ~]# ssh 192.168.137.66 -p 20023
ssh: connect to host 192.168.137.66 port 20023: Connection refused

image-20221109150519361

映射到指定地址的任意端口映射:

# 指定容器ssh_test3的22端口映射到192.168.137.66的任意端口
[root@localhost ~]# docker run -itd --rm --privileged=true  --name ssh_test3  -p 192.168.137.66::22 centos /usr/sbin/init   
764979b52a9321efa5844d918901457bc39225675d79c5f8f048882f9b97880e

# 可以再端口/后面指定IP协议。如果是udp协议就可以是/udp
[root@localhost ~]# docker run -itd --rm --privileged=true  --name ssh_test4  -p 192.168.137.66::22/tcp centos /usr/sbin/init
cd652e53bbd373ea020c3f6ebe3675a46e6cfb2f1b2daa5b72198a9a53db1f02

[root@localhost ~]# docker ps

image-20221109150547153

[root@localhost ~]# docker inspect   --format={{.NetworkSettings.Ports}} cd652e53bbd3   #查看映射
map[22/tcp:[{192.168.137.66 32769}]]

image-20221109150620936

也可以这样查看:

[root@localhost ~]# docker port cd652e53bbd3
22/tcp -> 192.168.137.66:32769
[root@localhost ~]# netstat  -lntup|grep docker-proxy   #可以看到几个进程启动了不同的端口,就是为了给容器进行端口映射

image-20221109150705928

这些映射怎么来的呢?,iptables设置的,这些映射都是iptables规则

Docker 在运行容器上增加端口映射

方法一:删除原有容器,重新建新容器

这个解决方案最为简单,把原来的容器删掉,重新建一个。当然这次不要忘记加上端口映射

优缺点:优点是简单快捷,在测试环境使用较多。缺点是如果是数据库镜像,那重新建一个又要重新配置一次,就比较麻烦了

方法二:修改容器配置文件,重启docker服务

容器的配置文件路径:

/var/lib/docker/containers/[hash_of_the_container]/hostconfig.json

复制代码其中的hashofthecontainer是docker镜像的hash值,可以通过docker ps或者docker inspect containername查看。(CONTAINER ID就可以看出来)

文件中其中有一项是PortBindings,其中8080/tcp对应的是容器内部的8080端口,HostPort对应的是映射到宿主机的端口9190。8361/tcp对应的是容器内部的8361端口,HostPort对应的是映射到宿主机的端口9191。按需修改端口,然后重启docker服务,再启动容器服务就可以了。

systemctl restart docker

优缺点:这个方法的优点是没有副作用,操作简单。缺点是需要重启整个docker服务,如果在同一个宿主机上运行着多个容器服务的话,就会影响其他容器服务

方法三:利用防火墙(一般都是用这种方式)

1、获得容器IP
 $ docker inspect container_name | grep IPAddress
比如我的容器叫mysqlserver么就输入下列代码来获取该容器的ip地址
 $ docker inspect mysqlserver | grep IPAddress
执行完之后会发现我的mysqlserverdocker容器的ip地址为192.168.0.2
2、iptables转发端口
比如我将容器的3306端口映射到主机的37221端口,那么ip对应就写入我的docker容器IP即可
iptables -t nat -A DOCKER -p tcp --dport 37221 -j DNAT --to-destination 192.168.0.2:3306

eg:

那现在都是开机指定好端口映射,那如果我想再加端口映射咋办,第一种就是加iptables规则,第二将现在的容器做成镜像然后再次创建容器指定额外的映射端口。

现在我们试着为ssh_test1加一个80端口的映射。

# iptables -t nat --list-rules PREROUTING  # 先来查看NAT表的PREROUTING链

image-20221109150938897

从上面可以看出iptables将满足条件的数据都转发到DOCKER链上去了。

# iptables -t nat --list-rules DOCKER   #查看NAT表中的DOCKER链设置的规则

image-20221109151024179

[root@localhost ~]#  iptables -t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8082 -j DNAT --to-destination 172.17.0.8:80  #仿照上面,我们添加一条自己的映射规则,将宿主机的8082端口映射到172.17.0.8的80端口上。

image-20221109151050253

# iptables -t filter --list-rules DOCKER    #查看fifter表的DOCKER链规则

image-20221109151112622

[root@localhost ~]# iptables -t filter -A DOCKER -d 172.17.0.8/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
[root@localhost ~]# iptables -t filter --list-rules DOCKER

image-20221109151137665

然后为了测试,登录ssh_test1这台机器然后yum个httpd,然后设置个index.html的测试页面

[root@localhost ~]# docker exec -it ssh_test1 /bin/bash #ssh_test1进入容器
[root@2491b93920e3 /]# yum install httpd -y     #安装httpd
[root@2491b93920e3 /]# cd /var/www/html/            #进入httpd根目录
[root@2491b93920e3 html]# echo "hello I am docker ssh_test1,I was mapped to port 8082 by the host,You can access me on the host by adding 8082" >index.html     #写入网页内容
[root@2491b93920e3 html]# systemctl start httpd #启动httpd
[root@2491b93920e3 html]# ss -tanl

image-20221109151219757

然后浏览器访问以下,下面就是浏览器访问的效果。

image-20221109151227395

访问web页面效果实现了,但是你查看docker ps或者netstat或者docker inspect --format={{.NetworkSettings.Ports}} ssh_test1

都是看不到这条端口映射的,因为不是通过docker来添加的,而是一条iptables手工设置的规则

方法四:利用commit

1.提交一个运行中的容器为镜像

docker commit container_id [REPOSITORY[:TAG]]

2.运行镜像并添加端口

docker run -d -p 8000:80  [REPOSITORY[:TAG]] /bin/bash

优缺点:这种方式的优点是不会影响统一宿主机上的其他容器,缺点是管理起来显得比较乱

容器互联

docker: links are only supported for user-defined networks.

Docker:只支持自定义网络的链接。  

容器的互联是一种让多个容器中应用进行快速交互的方式,它会在源和接收容器之间建立连接关系,接收容器可以通过容器名快速访问到源容器,而不用指定具体的IP地址。

容器的连接(linking)系统是除了端口映射外另一种可以与容器中应用进行交互的方式。它会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。

Link是一种比端口映射更亲密的Docker容器间通信方式,提供了更安全、高效的服务,通过环境变量和/etc/hosts文件的设置提供了从别名到具体通信地址的发现,适合于一些需要各组件间通信的应用

通过容器的名称在容器间建立一条专门的网络通信隧道,实现容器的互联,在运行docker run的时候使用 --link选项来实现

--link name:alias

要连接的容器名:这个连接的别名

eg:

1、创建源容器

# docker run --rm -itd --name web1 -p 49280:80 linode/lamp
2374eab85eb4d3c0e5e70eaf055c7503a08611423ff5de54804d84007ec25a91

2、创建接受容器

# docker run -itd -P --name web2 --link web1:2374 linode/lamp
3f27269fc434446d72001dbf356917bb25df4ec5a3a25e8d9d8d2e063103f74c
[root@Docker ~]#docker ps

image-20221109151446272

3、测试容器互联

最简单的检测方法,进入容器,ping对方容器

eg:web容器连接到db容器。

使用--link参数可以让容器之间安全的进行交互。

# 创建一个新的数据库容器
# docker run --rm -d --name db training/postgres   

# 创建一个新的web3容器,并将它连接到db容器。此时db容器和web容器建立互联关系。--link参数的格式为--link name:alias,其中name是要链接的容器的名称,alias是这个连接的别名。
# docker run --rm -d -P --name web3 --link db:db training/webapp python app.py  

使用--link方式让Docker在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主机上。在启动db容器的时候并没有使用-p-P标记,从而避免了暴露数据库端口到外部网络上

eg:

创建一个数据库容器:
# docker run --rm -itd --name db --env MYSQL_ROOT_PASSWORD=example  mariadb
创建一个web容器并将它连接到db容器:
# docker run --rm -itd -P --name web --link db:db nginx:latest
[root@localhost ~]# docker ps

image-20221109151616152

此时web容器已经和db容器建立互联关系:--link参数的格式为:--link name:alias,其中name是要连接的容器名称,alias是这个连接的别名。

Docker相当于在两个互联的容器之间创建了一个虚拟通道,而不用映射它们的端口到宿主机上。在启动db容器的时候并没有使用-p或者-P参数,从而避免了暴露数据库服务端口到外部网络上

Docker通过两种方式为容器公开连接信息

1.更新环境变量;2.更新/etc/hosts文件

使用env命令来查看web容器的环境变量:
eg:
# docker run --rm --name web5 --link db:db training/webapp env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=15dd016ee316
DB_PORT=tcp://172.17.0.13:3306
DB_PORT_3306_TCP=tcp://172.17.0.13:3306
DB_PORT_3306_TCP_ADDR=172.17.0.13
DB_PORT_3306_TCP_PORT=3306
DB_PORT_3306_TCP_PROTO=tcp
DB_NAME=/web5/db
DB_ENV_MYSQL_ROOT_PASSWORD=example
DB_ENV_GOSU_VERSION=1.10
DB_ENV_GPG_KEYS=177F4010FE56CA3336300305F1656F24C74CD1D8
DB_ENV_MARIADB_MAJOR=10.4
DB_ENV_MARIADB_VERSION=1:10.4.8+maria~bionic
HOME=/root
其中DB_开头的环境变量是提供web容器连接db容器使用的,前缀采用大写的连接别名。
  除了环境变量之外,Docker还添加host信息到父容器的/etc/hosts文件

#容器启动过程中需要调用setupLinkedContainers函数,这个函数最终返回的是env变量。如上面输出的那些环境变量,包含了由于link操作,所需要额外为启动容器创建的所有环境变量,其执行过程如下:
    找到要启动容器的所有自容器,即所有连接到源容器。
    遍历所有源容器,将link信息记录起来。
    将记录的link信息保存在以源容器别名为索引的隐射表中。
    将link相关的环境变量(包括源容器的名称、源容器中设置的环境变量以及源容器暴露的端口信息)放入到env中,最后将env变量返回。
    若以上过程中出现错误则取消做过的修改
# docker run -t -i --rm --link db:db training/webapp /bin/bash  #再次做个--link然后登陆容器查看一下
root@c509859cd3ea:/opt/webapp# cat /etc/hosts       #查看一下这个容器的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.13     db 20e3db148f14
172.17.0.16     c509859cd3ea

这里有两个hosts信息,第一个是db容器的IP和容器名+容器ID,第二个是web自己的IP和容器ID,web容器中hosts文件采用容器的ID作为主机名。


  互联的容器之间是可以ping通的。
 Docker容器的IP地址是不固定的,容器重启后IP地址可能就和之前不同了。在有Link关系的两个容器中,虽然接收方容器中包含有源容器的IP的环境变量,
 但是如果源容器重启,接收方容器中的环境变量不会自动更新。这些环境变量主要是为容器中的第一个进程所设置的,如sshd等守护进程。
 因此,link操作除了在将link信息保存在接收容器之外,还在/etc/hosts中添加了一项-------源容器的IP和别名(--link参数指定的别名),以用来解析源容器的IP地址。
 并且当源容器重启后,会自动更新接收容器的/etc/hosts文件。需要注意的是这里仍然用的是别名,而不是源容器的主机名(实际上,主机名对外界是不可见的)。
 因此,可以用这个别名来配置引用程序,而不需要担心IP的变化

#可以再web容器中安装ping命令来测试跟db容器的连通
root@c509859cd3ea:/opt/webapp# env  下面是容器环境变量内容截取结果

image-20221109151843849

配置 DNS

如何自定义配置容器的主机名DNS 呢?秘诀就是 Docker 利用虚拟文件来挂载容器的 3 个相关配置文件。

在容器中使用mount 命令可以看到挂载信息:

$ mount
/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
tmpfs on /etc/resolv.conf type tmpfs ...
这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 DNS 配置通过 /etc/resolv.conf 文件立刻得到更新。

配置全部容器的 DNS ,也可以在 /etc/docker/daemon.json 文件中增加以下内容来设置。

{
  "dns" : [
    "114.114.114.114",
    "8.8.8.8"
  ]
}
这样每次启动的容器 DNS 自动配置为 114.114.114.114  8.8.8.8。使用以下命令来证明其已经生效。

$ docker run -it --rm ubuntu:18.04  cat etc/resolv.conf

nameserver 114.114.114.114
nameserver 8.8.8.8

如果用户想要手动指定容器的配置,可以在使用 docker run 命令启动容器时加入如下参数:

-h HOSTNAME 或者 --hostname=HOSTNAME 设定容器的主机名,它会被写到容器内的 /etc/hostname  /etc/hosts。但它在容器外部看不到,既不会在 docker container ls 中显示,也不会在其他的容器的 /etc/hosts 看到。

--dns=IP_ADDRESS 添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名。

--dns-search=DOMAIN 设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,DNS 不仅搜索 host,还会搜索 host.example.com。

注意:如果在容器启动时没有指定最后两个参数,Docker 会默认用主机上的 /etc/resolv.conf 来配置容器

docker pipework 实现跨宿主主机容器互联

https://blog.51cto.com/13941177/2296529

如何调试 Docker

# 开启 Debug 模式,在 dockerd 配置文件 daemon.json(默认位于 /etc/docker/)中添加
{
  "debug": true,
}

# 重启守护进程。
$ sudo kill -SIGHUP $(pidof dockerd)
# 此时 dockerd 会在日志中输入更多信息供分析。
# 检查内核日志
$ sudo dmesag |grep dockerd
$ sudo dmesag |grep runc

# Docker 不响应时处理?
可以杀死 dockerd 进程查看其堆栈调用情况。
$ sudo kill -SIGUSR1 $(pidof dockerd)

# 重置 Docker 本地数据
# 注意,本操作会移除所有的 Docker 本地数据,包括镜像和容器等。
$ sudo rm -rf /var/lib/docker

Docker私有registry->harbor

容器仓库,实现快速部署,为实现"Build,Ship and Run Any App,Anywhere"提供基石

Dokcer的公有仓库:

www.hub.docker.com

弊端:

  • 始终是外网地址,必须进行下载,速度没有本地下载快
  • 权限的管控
  • docker的安全性,也是一种考量

Docker的私有仓库:

  • Harbor:港湾,依赖Docker Compose
  • docker-distribution:官方软件包

如果项目小,且没有具体要求,docker-distribution就可以了

Harbor由Vmware公司开源的一个项目[基本上都是国内的程序写得],由python开发,现在go语言重写了;提供UI界面[User Interface];可以基于权限、访问角色的控制、认证功能;

image-20221109152336515

image-20221109152342590

安装官方提供的私有镜像仓库

image-20221109152347835

image-20221109152353656

安装Docker私有仓库docker-distribution(`老版本也叫docker-registry`1、docker-distribution的2种安装方法:
yum install docker-distribution  #使用yum安装
docker run -d -p 5000:5000 -v /docker_registry:/var/lib/registry registry:2  #使用registry官方镜像安装 
启动Docker 私有仓库 
systemctl start docker-distribution

修改配置文件,根据需求更改存放registry

image-20221109152434586

监听端口:

image-20221109152602053

配置insecure-registries参数:

# cat /etc/docker/daemon.json 
{
    "oom-score-adjust": -1000,
    "exec-opts": ["native.cgroupdriver=systemd"],
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "100m",
        "max-file": "3"
    },
    "max-concurrent-downloads": 10,
    "max-concurrent-uploads": 10,
    "bip": "172.17.0.1/16",
    "registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"],
    "insecure-registries": ["http://192.168.137.7:8081","http://10.0.1.171:5000"],
    "storage-driver": "overlay2",
    "storage-opts": [
        "overlay2.override_kernel_check=true"
    ],
    "live-restore": true
}

Docker Compose介绍

官方参考文档:

https://github.com/docker/compose

https://docs.docker.com/compose/

Docker Compose: 是docker的服务编排工具,作用用于构建docker的复杂应用

用Dockerfile来构建php、java项目、lnmp项目,但是会一步步部署容器,然后才可以看到,比较繁琐,docker-compose就可以一键部署

Docker Compose:必须以YAML文件进行书写

YAML:一种标记化语言[和XML类似]
YAML格式要求:
    A、不支持tab键进行缩进,且必须使用空格键缩进
    B、通常开头缩进两个空格
    C、字符后面必须缩进一个空格,如:冒号、逗号或者横杠 
    D、使用#进行注释
    E、如果包含特殊的字符,必须使用单引号进行引用
    F、所有的特殊数据类型必须使用引号进行括起来
        如:true、false、yes、no、off、on等等等

Docker Compose部署

Docker-compose有3种安装方式,分别为下载二进制包pip安装以及yum安装

下载二进制文件安装(可以指定版本):

参考官网-> https://docs.docker.com/compose/install/

1、下载指定版本的docker-compose
[root@localhost ~]# curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

2、对二进制文件赋可执行权限 
[root@localhost ~]# chmod +x /usr/local/bin/docker-compose
3、测试下docker-compose是否安装成功
[root@localhost ~]# docker-compose --version

=================================
或者Docker-compose的安装:
# sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# sudo chmod +x /usr/local/bin/docker-compose
# docker-compose -v
# docker-compose --help

也可使用pip安装docker-compose:

[root@localhost ~]# yum install python-pip -y
[root@localhost ~]# pip install --upgrade pip
[root@localhost ~]# pip install -U -i https://pypi.tuna.tsinghua.edu.cn/simple docker-compose #默认最新版本,可以使用 docker-compose==version指定安装版本
[root@localhost ~]# docker-compose -version 
docker-compose version 1.23.2, build 1110ad0

或者直接yum安装

1、安装docker-compose:

image-20221109153310239

[root@Docker ~]# yum install docker-compose -y

测试是否安装成功:

[root@Docker ~]# docker-compose --version
docker-compose version 1.18.0, build 8dd22a9

ps:因为harbor依赖于docker-compose这个调度器,需要先安装docker-compose,否则在执行./install.sh就会报如下错误:

image-20221109153406528

Harbor介绍

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

官方文档:https://goharbor.io/docs/

github用户手册:https://github.com/goharbor/harbor/blob/master/docs/user_guide.md

image-20221109153425588

1.Harbor简介

Harbor,是一个英文单词,意思是港湾,港湾是干什么的呢,就是停放货物的,而货物呢,是装在集装箱中的,说到集装箱,就不得不提到Docker容器,因为docker容器的技术正是借鉴了集装箱的原理。所以,Harbor正是一个用于存储Docker镜像的企业级Registry服务。

Registry是Dcoker官方的一个私有仓库镜像,可以将本地的镜像打标签进行标记然后push到以Registry起的容器的私有仓库中。企业可以根据自己的需求,使用Dokcerfile生成自己的镜像,并推到私有仓库中,这样可以大大提高拉取镜像的效率。

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必需的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中, 确保数据和知识产权在公司内部网络中管控。另外,Harbor也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等。

2.Harbor特性

image-20221109153440209

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

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

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

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

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

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

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

  • 部署简单 : 提供在线和离线两种安装工具, 也可以安装到vSphere平台(OVA方式)虚拟设备。

3.Harbor组件

Harbor在架构上主要由6个组件构成:

  • Proxy:Harbor的registry, UI, token等服务,通过一个前置的反向代理统一接收浏览器、Docker客户端的请求,并将请求转发给后端不同的服务。

  • Registry: 负责储存Docker镜像,并处理docker push/pull 命令。由于我们要对用户进行访问控制,即不同用户对Docker image有不同的读写权限,Registry会指向一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token, Registry会通过公钥对token 进行解密验证。

  • Core services: 这是Harbor的核心功能,主要提供以下服务:

  • UI:提供图形化界面,帮助用户管理registry上的镜像(image), 并对用户进行授权。

  • webhook:为了及时获取registry 上image状态变化的情况, 在Registry上配置webhook,把状态变化传递给UI模块。

  • token 服务:负责根据用户权限给每个docker push/pull命令签发token. Docker 客户端向Regiøstry服务发起的请求,如果不包含token,会被重定向到这里,获得token后再重新向Registry进行请求。

  • Database:为core services提供数据库服务,负责储存用户权限、审计日志、Docker image分组信息等数据。

  • Job Services:提供镜像远程复制功能,可以把本地镜像同步到其他Harbor实例中。

  • Log collector:为了帮助监控Harbor运行,负责收集其他组件的log,供日后进行分析。

各个组件之间的关系如下图所示:

image-20221109153614940

4、Harbor和Registry的比较

Harbor和Registry都是Docker的私有镜像仓库,但是Harbor作为更多企业的选择,是因为相比较于Regisrty来说,它具有很多的优势。

1.提供分层传输机制,优化网络传输

Docker镜像是是分层的,而如果每次传输都使用全量文件(所以用FTP的方式并不适合),显然不经济。必须提供识别分层传输的机制,以层的UUID为标识,确定传输的对象。

2.提供WEB界面,优化用户体验

只用镜像的名字来进行上传下载显然很不方便,需要有一个用户界面可以支持登陆、搜索功能,包括区分公有、私有镜像。

3.支持水平扩展集群

当有用户对镜像的上传下载操作集中在某服务器,需要对相应的访问压力作分解。

4.良好的安全机制

企业中的开发团队有很多不同的职位,对于不同的职位人员,分配不同的权限,具有更好的安全性。

5.Harbor提供了基于角色的访问控制机制,并通过项目来对镜像进行组织和访问权限的控制。

kubernetes中通过namespace来对资源进行隔离,在企业级应用场景中,通过将两者进行结合可以有效将kubernetes使用的镜像资源进行管理和访问控制,增强镜像使用的安全性。尤其是在多租户场景下,可以通过租户、namespace和项目相结合的方式来实现对多租户镜像资源的管理和访问控制。

部署harbor1.7.5

下载harbor并解压

离线安装Harbor,需要事先安装好docker-compose

软件 版本 描述
Python 2.7或更高版本 请注意,您可能必须在Linux发行版(Gentoo,Arch)上安装Python,默认情况下不安装Python解释器
Docker引擎 版本1.10或更高版本 有关安装说明,请参阅:https//docs.docker.com/engine/installation/
Docker Compose 版本1.6.0或更高版本 有关安装说明,请参阅:https//docs.docker.com/compose/install/
OpenSSL 建议最新的 为Harbor生成证书和密钥

网络端口要求

端口 协议 描述
443 HTTPS Harbor门户和核心API将接受此端口上的https协议请求
4443 HTTPS 只有在连接到Dock的Docker Content Trust服务启用认证时才需要
80 HTTP Harbor端口和核心API将接受此端口上的http协议请求

根据版本的不同,可能环境要求各不相同,具体可以参官方文档:https://github.com/goharbor/harbor/blob/master/docs/installation_guide.md

查看Harbor的官网

https://github.com/goharbor/harbor

harbor有两个版本,分别为offline(离线)online(在线),离线的安装包比较大,后续安装会相对快一点,在线的安装包比较小,但是后续安装会比较慢,这里我们选择离线安装:

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

在指定版本的Download Binary处现在对应的安装包,复制链接

下载:有离线版(建议下载)、在线版

image-20221109153828729

[root@Docker ~/harbor]# wget https://storage.googleapis.com/harbor-releases/release-1.7.0/harbor-offline-installer-v1.7.5.tgz
[root@Docker ~]# tar xf harbor-offline-installer-v1.7.5.tgz
[root@Docker ~]# ls
harbor  harbor-offline-installer-v1.7.5.tgz
[root@Docker ~]# cd harbor/
[root@Docker ~/harbor]# ls
common                          docker-compose.clair.yml   docker-compose.yml  harbor.v1.7.5.tar.gz  LICENSE              prepare
docker-compose.chartmuseum.yml  docker-compose.notary.yml  harbor.cfg          install.sh            open_source_license

# 解压缩之后,目录下会生成harbor.conf文件,该文件就是Harbor的配置文件。

修改配置文件docker-compose.yml、harbor.cfg:

[root@Docker ~/harbor]# vim docker-compose.yml  (该配置文件可以不用修改)

image-20221109154918882

这个就是映射到宿主机的端口:1514

image-20221109154928585

# cp harbor.cfg harbor.cfg.bak              #先备份配置文件
[root@Docker ~/harbor]# vim harbor.cfg  (其实只修改hostname就可以启动服务)
修改hostname、可以是域名、也可以是ip,不可以设置为127.0.0.1或localhost

image-20221109155007444

修改协议:如果你是https,则修改为https

访问协议,默认是http,也可以设置https,如果设置https,则nginx ssl需要设置on

image-20221109155210213

证书文件:如果你是https,则需要证书

#The path of cert and key files for nginx, they are applied only the protocol is set to https
ssl_cert = /data/cert/server.crt
ssl_cert_key = /data/cert/server.key

为什么要使用https协议

因为不用 https 协议的话,docker 客户端需要修改配置,如果 docker 客户端多的话配置起来就很麻烦

#邮件设置,发送重置密码邮件时使用
email_server = smtp.qq.com
email_server_port = 25
email_username = test@qq.com
email_password = 666666
email_from = admin <test@qq.com>
email_ssl = false
email_insecure = false

#登陆harbor的初始密码:
harbor_admin_password = Harbor12345

# mysql数据库root用户默认密码root123(生产环境必须更改)
db_password = root123

# 认证方式,这里支持多种认证方式,如LADP、本次存储、数据库认证。默认是db_auth,mysql数据库认证
auth_mode = db_auth

# LDAP认证时配置项
#ldap_url = ldaps://ldap.mydomain.com
#ldap_searchdn = uid=searchuser,ou=people,dc=mydomain,dc=com
#ldap_search_pwd = password
#ldap_basedn = ou=people,dc=mydomain,dc=com
#ldap_filter = (objectClass=person)
#ldap_uid = uid 
#ldap_scope = 3 
#ldap_timeout = 5

#禁止用户注册 
self_registration = off

# token有效时间,默认30分钟
token_expiration = 30

#用户创建项目权限控制,默认是everyone(所有人),也可以设置为adminonly(只能管理员)
project_creation_restriction = adminonly

修改完配置文件必须执行./prepare,才能更新配置文件

[root@Docker ~/harbor]# ./prepare

image-20221109162832960

执行后,就会在/data目录生成如下信息

# ll /data/
total 4
drwxr-xr-x 2 10000 10000  6 Dec 12 10:55 job_logs
-rw------- 1 10000 10000 16 Dec 12 10:55 secretkey

安装harbor

第一次安装直接运行install脚本,后面再管理就直接使用docker-compose管理即可

[root@Docker ~/harbor]#./install.sh

修改完配置文件后,在的当前目录执行./install.sh,Harbor服务就会根据当期目录下的docker-compose.yml开始下载依赖的镜像,检测并按照顺序依次启动各个服务

也可以导入目录下的镜像包harbor.v1.7.5.tar.gz

img

img

img

img

已经导入镜像了:

image-20221109163049632

端口也已经起来了:启动完成后,访问刚设置的hostname即可,默认是80端口,如果端口占用,可以去修改docker-compose.yml文件中,对应服务的端口映射

# vim /root/harbor/docker-compose.yml

image-20221109163121281

image-20221109163129829

./prepare --with-clair
./install.sh --with-clair

./prepare 会把harbor.cfg中的配置参数渲染到对应的各个组件参数配置目录,即common/config下面。

--with-clair表示启动镜像扫描功能,

--with-chartmuseum 表示启动helm charmuseum功能,

如果只需基础服务,直接./prepare && install.sh即可,停止harbor,请根据服务执行docker-compose -f docker-compose.yml -f docker-compose.clair.yml down 。单机服务所有的组件都是以容器方式启动的,包括存储层的redis、mysql、postgresql

Harbor1.9 部署并配置https

参考:https://cloud.tencent.com/developer/article/1580035

关于使用自签名证书配置harbor的具体过程可以参考: https://github.com/goharbor/harbor/blob/master/docs/configure_https.md

为什么要使用https协议

因为不用 https 协议的话,docker 客户端需要修改配置,如果 docker 客户端多的话配置起来就很麻烦

版本信息

  • OS:CentOS Linux 7.6 Release

  • Docker:18.09.6

  • Docker-compose:1.24.1

  • Harbor:harbor-offline-installer-v1.9.0

  • IP:172.0.0.11

1.安装docker

1.1 配置repository:

yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

1.2 安装最新版本docker-ce

yum install -y docker-ce-18.09.9 bash-completion

1.3 配置docker加速

参考docker.hub:https://www.daocloud.io/mirror

curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io
systemctl restart docker.service

1.4 启动docker:

systemctl start docker
systemctl enable docker

2. 安装docker-compose

2.1 下载二进制文件

curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

如果需要安装其他版本的话,请修改上面命令中的版本号。

2.2 赋予二进制文件可执行权限

chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

2.3 根据自己的情况决定是否安装命令补全功能

yum install -y bash-completion 
curl -L https://raw.githubusercontent.com/docker/compose/1.24.1/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose

# or 
curl -L https://raw.githubusercontent.com/docker/compose/$(docker-compose version --short)/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose

2.4 测试是否安装成功

docker-compose --version

3. harbor开启https

如果使用 1.8 或者 1.9 版本,切记配置文件中 https 需要顶格,证书和 port 需要缩进相同单位,不然会报错。

参考:https://www.cnblogs.com/kevingrace/p/6547616.html

3.1 创建 ca 证书

mkdir -p /data/cert
cd /data/cert

3.2 生成 CA 的 key

cd /data/cert
openssl genrsa -out ca.key 4096

image-20221110155007795

3.3 生成 CA 的 crt

cd /data/cert
openssl req -x509 -new -nodes -sha512 -days 3650 \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=chinatelecom/OU=ecloudcaas/CN=172.0.0.11" \
    -key ca.key \
    -out ca.crt

image-20221110155033922

3.4 生成自己域名的 key

cd /data/cert
openssl genrsa -out 172.0.0.11.key 4096

3.5 生成自己域名的 csr

cd /data/cert
openssl req -sha512 -new \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=chinatelecom/OU=ecloudcaas/CN=172.0.0.11" \
    -key 172.0.0.11.key \
    -out 172.0.0.11.csr 

3.6 生成一个 openssl 命令需要的外部配置文件

主要是subjectAltName,这里写的IP.1=yourip还可以写DNS.1=yourdomainname
cd /data/cert
cat > v3.ext <<-EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth 
subjectAltName = @alt_names
[alt_names]
IP=172.0.0.11
EOF

3.7 通过 ext 和 csr 生成 crt

cd /data/cert
openssl x509 -req -sha512 -days 3650 \
    -extfile v3.ext \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -in 172.0.0.11.csr \
    -out 172.0.0.11.crt

3.8 将服务端的 crt 转换成客户端用的 cert

cd /data/cert
openssl x509 -inform PEM -in 172.0.0.11.crt -out 172.0.0.11.cert

3.9 将带域名的 cert,key 和 ca.crt 拷贝到 docker client 所在主机的/etc/docker/certs.d/yourdomain/目录下

mkdir -p /etc/docker/cert/172.0.0.11
cp /data/cert/172.0.0.11.cert /etc/docker/cert/172.0.0.11/
cp /data/cert/172.0.0.11.key /etc/docker/cert/172.0.0.11/
cp /data/cert/ca.crt /etc/docker/cert/172.0.0.11/

3.10 创建 /etc/docker/daemon

cat > /etc/docker/daemon.json << EOF
{ "insecure-registries":["http://172.0.0.11"] }
EOF

3.11 重启 docker

systemctl daemon-reload
systemctl restart docker

4. 安装 Harbor

4.1 下载 harbor 离线包

mkdir -p /home/harbor/
wget -P /home/harbor/ https://storage.googleapis.com/harbor-releases/release-1.9.0/harbor-offline-installer-v1.9.0.tgz
cd /home/harbor/
tar xf harbor-offline-installer-v1.9.0.tgz
cd /home/harbor/harbor
cp harbor.yml harbor.yml.bak

4.2 修改配置文件

其他地方不修改,只改以下几处:

cd /home/harbor/harbor/
[root@harbor harbor]# egrep -v "^#|^$" harbor.yml|grep -v "#"
https:
   port: 443
   certificate: /home/harbor/cert/172.0.0.11.crt
   private_key: /home/harbor/cert/172.0.0.11.key

4.3 更新参数

cd /home/harbor/harbor/
./prepare   

4.4 安装

cd /home/harbor/harbor/ 
./install

4.5 查看

Harbor 的日常运维管理是通过 docker-compose 来完成的,Harbor 本身有多个服务进程,都放在 docker 容器之中运行,可以通过docker ps或者docker-compose来查看:

cd /home/harbor/harbor/
[root@harbor harbor]# docker-compose ps
       Name                     Command                       State                                        Ports                              ----------------------------------------------------------------------------------------------
harbor-adminserver   /harbor/start.sh                 Restarting                                                                              
harbor-core          /harbor/start.sh                 Up (health: starting)                                                                   
harbor-db            /entrypoint.sh postgres          Up (healthy)            5432/tcp                                                        
harbor-jobservice    /harbor/start.sh                 Up                                                                                      
harbor-log           /bin/sh -c /usr/local/bin/ ...   Up (healthy)            127.0.0.1:1514->10514/tcp                                       
harbor-portal        nginx -g daemon off;             Up (healthy)            80/tcp                                                        
nginx                nginx -g daemon off;             Up (healthy)            0.0.0.0:443->443/tcp, 0.0.0.0:4443->4443/tcp, 0.0.0.0:80->80/tcp
redis                docker-entrypoint.sh redis ...   Up                      6379/tcp                                                        
registry             /entrypoint.sh /etc/regist ...   Up (healthy)            5000/tcp                                                        
registryctl          /harbor/start.sh                 Up (healthy)      

5. 网页登录和创建项目

在浏览器输入: https://172.0.0.11;

默认账号密码: adminHarbor12345

创建一个项目:os;

6. 镜像的推送

6.1 下载官方的 centos 镜像

docker pull centos:7.4.1708

6.2 修改 TAG

docker tag centos:7.4.1708 172.0.0.11/os/centos:7.4.1708
docker images | grep centos172.0.0.11/os/centos
           7.4.1708            3afd47092a0e        2 months ago        197MB
centos                           7.4.1708            3afd47092a0e        2 months ago        197MB

6.3 命令行登录 harbor

cat > /etc/docker/daemon.json << EOF
{ "insecure-registries":["http://172.0.0.11"] }
EOF
systemctl daemon-reload
systemctl restart docker


[root@harbor harbor]# docker login 172.0.0.11
Username: admin
Password: Harbor12345
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded

6.4 推送镜像到harbor(需要login)

docker push 172.0.0.11/os/centos:7.4.1708

6.5 在 harbor 中查看

7. 镜像的拉取

假设是一台没有登录此 harbor 的 docker 客户端

7.1 创建 /etc/docker/daemon.json 文件

{
  "registry-mirrors": ["https://mirror.ccs.tencentyun.com","https://kuamavit.mirror.aliyuncs.com", "https://registry.docker-cn.com", "https://docker.mirrors.ustc.edu.cn"], 
  "insecure-registries" : ["http://172.0.0.11"],
  "max-concurrent-downloads": 10,
  "log-driver": "json-file",
  "log-level": "warn",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
    }
}

7.2 重启Docker生效

systemctl daemon-reload
systemctl restart docker

7.3 拉取 harbor 中的镜像

docker login 172.0.0.11
docker pull 172.0.0.11/os/centos:7.4.1708

部署最新版harbor2.0.0

1.8版本开始就改变了很多了

参考:

https://www.cnblogs.com/kevingrace/p/6547616.html?spm=a2c4e.10696291.0.0.6feb19a4gcLLOW

https://cloud.tencent.com/developer/article/1580035

Docker容器应用的开发和运行离不开可靠的镜像管理,虽然Docker官方也提供了公共的镜像仓库,但是从安全和效率等方面考虑,部署我们私有环境内的Registry也是非常必要的。

之前介绍了Docker私有仓库Registry,这里介绍另一款企业级Docker镜像仓库Harbor的部署和使用,在Kubernetes集群中,推荐使用Harbor仓库环境

Harbor仓库介绍

我们在日常Docker容器使用和管理过程中,渐渐发现部署企业私有仓库往往是很有必要的, 它可以帮助你管理企业的一些敏感镜像, 同时由于Docker Hub的下载速度和GFW的原因, 往往需要将一些无法直接下载的镜像导入本地私有仓库. 而Harbor就是部署企业私有仓库的一个不二之选。

Harbor是由VMware公司开源的企业级的Docker Registry管理项目,Harbor主要提供Dcoker Registry管理UI,提供的功能包括:基于角色访问的控制权限管理(RBAC)、AD/LDAP集成、日志审核、管理界面、自我注册、镜像复制和中文支持等。

Harbor的目标是帮助用户迅速搭建一个企业级的Docker registry服务。它以Docker公司开源的registry为基础,额外提供了如下功能:\ ->  基于角色的访问控制(Role Based Access Control)\ ->  基于策略的镜像复制(Policy based image replication)\ ->  镜像的漏洞扫描(Vulnerability Scanning)\ ->  AD/LDAP集成(LDAP/AD support)\ ->  镜像的删除和空间清理(Image deletion & garbage collection)\ ->  友好的管理UI(Graphical user portal)\ ->  审计日志(Audit logging)\ ->  RESTful API\ ->  部署简单(Easy deployment)

Harbor的所有组件都在Dcoker中部署,所以Harbor可使用Docker Compose快速部署。需要特别注意:由于Harbor是基于Docker Registry V2版本,所以docker必须大于等于1.10.0版本docker-compose必须要大于1.6.0版本

Harbor仓库结构

Harbor的每个组件都是以Docker容器的形式构建的,可以使用Docker Compose来进行部署。如果环境中使用了kubernetes,Harbor也提供了kubernetes的配置文件。Harbor大概需要以下几个容器组成ui(Harbor的核心服务)、log(运行着rsyslog的容器,进行日志收集)、mysql(由官方mysql镜像构成的数据库容器)、Nginx(使用Nginx做反向代理)、registry(官方的Docker registry)、adminserver(Harbor的配置数据管理器)、jobservice(Harbor的任务管理服务)、redis(用于存储session)

Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,整体架构还是很清晰的。下面借用了网上的架构图:

image-20221109165014062

image-20221109165030616

Harbor依赖的外部组件

-> Nginx(即Proxy代理层): Nginx前端代理,主要用于分发前端页面ui访问和镜像上传和下载流量; Harbor的registry,UI,token等服务,通过一个前置的反向代理统一接收浏览器、Docker客户端的请求,并将请求转发给后端不同的服务。

-> Registry v2: 镜像仓库,负责存储镜像文件; Docker官方镜像仓库, 负责储存Docker镜像,并处理docker push/pull命令。由于我们要对用户进行访问控制,即不同用户对Docker image有不同的读写权限,Registry会指向一个token服务,强制用户的每次docker pull/push请求都要携带一个合法的token, Registry会通过公钥对token进行解密验证。

-> Database(MySQL或Postgresql):为core services提供数据库服务,负责储存用户权限、审计日志、Docker image分组信息等数据。

Harbor自有组件

-> Core services(Admin Server): 这是Harbor的核心功能,主要提供以下服务: 
    -> UI:提供图形化界面,帮助用户管理registry上的镜像(image), 并对用户进行授权。
    -> webhook:为了及时获取registry 上image状态变化的情况, 在Registry上配置webhook,把状态变化传递给UI模块。
    -> Auth服务:负责根据用户权限给每个docker push/pull命令签发token. Docker 客户端向Regiøstry服务发起的请求,如果不包含token,会被重定向到这里,获得token后再重新向Registry进行请求。
    -> API: 提供Harbor RESTful API
-> Replication Job Service:提供多个 Harbor 实例之间的镜像同步功能。
-> Log collector:为了帮助监控Harbor运行,负责收集其他组件的log,供日后进行分析。

再来仔细看下Harbor主要组件和数据流走向:

image-20221109170056555

-> proxy,它是一个nginx前端代理,主要是分发前端页面ui访问和镜像上传和下载流量,上图中通过深蓝色先标识;
-> ui提供了一个web管理页面,当然还包括了一个前端页面和后端API,底层使用mysql数据库;
-> registry是镜像仓库,负责存储镜像文件,当镜像上传完毕后通过hook通知ui创建repository,上图通过红色线标识,当然registry的token认证也是通过ui组件完成;
-> adminserver是系统的配置管理中心附带检查存储用量,ui和jobserver启动时候回需要加载adminserver的配置,通过灰色线标识;
-> jobsevice是负责镜像复制工作的,他和registry通信,从一个registry pull镜像然后push到另一个registry,并记录job_log,上图通过紫色线标识;
-> log是日志汇总组件,通过docker的log-driver把日志汇总到一起,通过浅蓝色线条标识

Harbor的误区

误区一: Harbor是负责存储容器镜像的 (Harbor是镜像仓库,那么它就应当是存储镜像的) 其实关于镜像的存储,Harbor使用的是官方的docker registry服务去完成,至于registry是用本地存储或者s3都是可以的,Harbor的功能是在此之上提供用户权限管理、镜像复制等功能,提高使用的registry的效率。

误区二:Harbor镜像复制是存储直接复制 (镜像的复制,很多人以为应该是镜像分层文件的直接拷贝) 其实Harbor镜像复制采用了一个更加通用、高屋建瓴的做法,通过docker registry 的API去拷贝,这不是省事,这种做法屏蔽了繁琐的底层文件操作、不仅可以利用现有docker registry功能不必重复造轮子,而且可以解决冲突和一致性的问题

Harbor的部署

这里不建议使用kubernetes来部署, 原因是镜像仓库非常重要, 尽量保证部署和维护的简洁性, 因此这里直接使用compose的方式进行部署。官方提供3种部署Harbor的方式:

1)在线安装: 从Docker Hub下载Harbor的镜像来安装, 由于Docker Hub比较慢, 建议Docker配置好加速器。
2)离线安装: 这种方式应对与部署主机没联网的情况使用。需要提前下载离线安装包: harbor-offline-installer-.tgz 到本地
3)OVA安装: 这个主要用vCentor环境是使用
后面部署时会为Docker配置镜像加速器, 因此会采用在线部署的方式, 部署步骤如下:
-> 下载Harbor最新的在线安装包
-> 配置Harbor (harbor.cfg)
-> 运行install.sh来安装和启动Harbor
-> Harbor的日志路径:/var/log/harbor
Harbor仓库部署的官方要求的最小系统配置
-> 2个cpu
-> 4g内存
-> 40g硬盘,因为是存储镜像的所以推荐硬盘大点。

参考 Harbor GitHub Harbor 安装手册 Harbor 用户手册

环境要求和准备工作

省略

安装Docker

省略

安装Docker Compose

省略

下载Harbor安装包,配置Harbor

cd /usr/local/
wget https://github.com/goharbor/harbor/releases/download/v2.0.0/harbor-offline-installer-v2.0.0.tgz
tar xf harbor-offline-installer-v2.0.0.tgz
cd harbor/
cp harbor.yml.tmpl harbor.yml
# cat harbor.yml |grep -v "#"|grep -v "^$"
hostname: 192.168.137.9

# 生产环境建议使用80,443
http:
  port: 8181
#(生产环境必须更改harbor登录密码)
harbor_admin_password: Harbor12345
database:
  password: root123
  max_idle_conns: 50
  max_open_conns: 100
data_volume: /data/harbor
clair:
  updaters_interval: 12
trivy:
  ignore_unfixed: false
  skip_update: false
  insecure: false
jobservice:
  max_job_workers: 10
notification:
  webhook_job_max_retry: 10
chart:
  absolute_url: disabled
log:
  level: info
  local:
    rotate_count: 50
    rotate_size: 200M
    location: /var/log/harbor
_version: 2.0.0
proxy:
  http_proxy:
  https_proxy:
  no_proxy: 127.0.0.1,localhost,core,registry
  components:
    - core
    - jobservice
    - clair
    - trivy

配置解释

hostname: 修改成Harbao部署机自身的ip地址
db_password: 这是postgresql数据库root密码
harbor_admin_password: harbor初始管理员密码为Harbor12345, 这里最好修改成自己的密码,默认密码至少8位,最好是大小写、数字和特殊字符

配置完Harbor之后,接着进行安装启动Harbor,Harbor目录下有一个install.sh, 执行它来进行安装

# ./install.sh

image-20221109170537794

安装完成后,会发现解压目录harbor下面多了一个docker-compose.yml文件,里面包含了harbor依赖的镜像和对应容器创建的信息

image-20221109170550887

安装完成后,自动生成data_volume目录

image-20221109170611436

导入的镜像

image-20221109170622728

查看harbor对应容器信息(还可以执行docker imagesdocker ps查看harbor的镜像和容器情况)

# docker-compose ps       #"注意docker-compose"命令只能在当前harbor目录下使用(因为该目录下有harbor配置文件)

image-20221109170744605

然后就可以访问harbor了,访问地址为:http://192.168.137.9:8181

用户名为admin,密码为配置文件中定义的Harbor12345

image-20221109170950679

image-20221109170958481

====  这里需要注意一个Harbor 部署的坑点(Docker 18.09.1 及以上的版本,系统内核版本需要升级到4.4.x) ==== 1)CentOS 7.x 系统自带的3.10.x内核存在一些Bugs,导致运行的Docker、Kubernetes不稳定。 2)高版本的 docker(1.13 以后) 启用了3.10 kernel实验支持的kernel memory account功能(无法关闭),当docker节点压力大 (如频繁启动和停止容器) 时会导致 cgroup memory leak; 3)Docker 18.09.1 及以上的版本,需要手动升级内核到 4.4.X 以上;

因为得出结论: 部署harbor的时候,要首先查看下本机的docker版本,如果docker版本在18.90.1以上,则需要手动升级内核版本到 4.4.x以上。

否则会出现:(这里我没有出现该情况) Harbor正常启动,端口正常监听,防火墙也已关闭,但是通过http://ip:80 访问不了harbor,并且/var/log/harbor目录下没有任何日志产生!! 使用telnet ip 80查看发现不通或者闪退!!!

手动修改系统内核版本可以参考:https://www.cnblogs.com/kevingrace/p/10961264.html

具体升级内核操作如下:

# uname  -r
# rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm

安装完成后检查 /boot/grub2/grub.cfg 中对应内核menuentry中是否包含initrd16配置,如果没有,再安装一次!

image-20221109171141410

# yum --enablerepo=elrepo-kernel install -y kernel-lt
设置开机从新内核启动
# grub2-set-default 0
      
重启机器
# sync
# init 6
      
安装内核源文件(在升级完内核并重启机器后执行,也可以不用执行这一步。可选):
# yum --enablerepo=elrepo-kernel install kernel-lt-devel-$(uname -r) kernel-lt-headers-$(uname -r)
      
# uname -r
4.4.223-1.el7.elrepo.x86_64

=========================================================================================
或者也可以采用下面升级内核的方法:
# git clone --branch v1.14.1 --single-branch --depth 1 https://github.com/kubernetes/kubernetes
# cd kubernetes
# KUBE_GIT_VERSION=v1.14.1 ./build/run.sh make kubelet GOFLAGS="-tags=nokmem"
# init 6

Harbor 服务的关闭和启动

1) Harbor的日志路径:/var/log/harbor

image-20221109171950732

2) 停止和关闭harbor命令:

# docker-compose down -v
# docker-compose stop

可以修改harbor配置文件,比如这里修改harbor的web登录端口,由80端口修改为8080端口

[root@harbor-node harbor]# vim harbor.yml
.........
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 8080

然后将harbor修改的配置更新到 docker-compose.yml 文件

[root@harbor-node harbor]# ./prepare
prepare base dir is set to /usr/local/harbor
Clearing the configuration file: /config/registry/root.crt
Clearing the configuration file: /config/registry/config.yml
Clearing the configuration file: /config/db/env
Clearing the configuration file: /config/jobservice/config.yml
Clearing the configuration file: /config/jobservice/env
Clearing the configuration file: /config/core/app.conf
Clearing the configuration file: /config/core/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/log/logrotate.conf
Clearing the configuration file: /config/log/rsyslog_docker.conf
Clearing the configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
loaded secret from file: /secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir

防止容器进程没有权限读取生成的配置

[root@harbor-node harbor]# ll common/
total 0
drwxr-xr-x 9 root root 105 May 26 23:10 config
    
[root@harbor-node harbor]# chmod -R 777 common
    
[root@harbor-node harbor]# ll common/
total 0
drwxrwxrwx 9 root root 105 May 27 00:41 config

特别注意: 这里的common权限如果设置太小,可能会导致harbor启动后,报下面的错

发现启动harbor后,如上有些服务,如nginx,registry状态一直是Restarting,这时需要查看日志:

[root@harbor-node harbor]# tail -100 /var/log/harbor/registry.log |grep error
May 27 01:01:18 172.19.0.1 registry[2960]: configuration error: open /etc/registry/config.yml: permission denied
May 27 01:01:21 172.19.0.1 registry[2960]: configuration error: open /etc/registry/config.yml: permission denied
May 27 01:01:23 172.19.0.1 registry[2960]: configuration error: open /etc/registry/config.yml: permission denied
May 27 01:01:27 172.19.0.1 registry[2960]: configuration error: open /etc/registry/config.yml: permission denied

最后再次启动 harbor

# docker-compose stop
# docker-compose up -d

查看服务

[root@harbor-node harbor]# docker-compose  ps
然后访问http://172.16.60.213:8080,即可访问harbor的web界面
 
####################################################################################
要是想修改harbor的登陆用户密码,则最好在harbor web界面里直接修改,`这样是最保险的!`
 
如果是想通过修改harbar.yaml文件来重置harbor用户密码,则不能单纯的修改后就执行`./prepare`和重启`docker-compose restart`,这样是不能修改harbor用户密码的!
这时因为harbor在这里用的是postgresql数据库,以pdkdf2算法保存的秘文密码!需要先进入"harbor-db"容器内部,执行相关postgresql命令行。
而且postgresql的用户密码采用的是pbkdf2算法,需要提前计算好新密码的密钥值,pdkdf2算法需要"盐值""迭代次数"和密钥长度int型等,特别麻烦!!
 
所以如果忘记harbor的web密码或者是admin密码需要重置,并且对于postgresql数据库 或者 pbkdf2算法操作不熟悉的话,建议删除data源数据的database,重新部署!
做法如下:
# docker-compose down -v
# rm -rf /data/database
# vim harbor.yaml           #在这里重置或修改密码
# docker-compose up -d
 
这样就可以使用重置后的新密码登陆harbor web界面了,但是之前创建的用户和项目就都删除了。
这种情况最好适用于刚创建初期。
 
####################################################################################
docker-compose up -d          # 后台启动,如果容器不存在根据镜像自动创建
docker-compose down -v        # 停止容器并删除容器
docker-compose start          # 启动容器,容器不存在就无法启动,不会自动创建镜像
docker-compose stop           # 停止容器
   
需要注意:
其实上面是停止docker-compose.yml中定义的所有容器,默认情况下docker-compose就是操作同目录下的docker-compose.yml文件。
如果使用其他yml文件,可以使用-f自己指定。
->  登录Harbor web界面,在"系统管理"->"配置管理"->"认证模式"->"允许自注册"这一项的对勾去掉,则登录的时候就不会有"用户注册"这一功能了。
->  可以在"配置管理"这一项进行认证模式,邮箱,标签等设置

使用Harbor私有仓库

harbor的login登录

1)在harbor远程别的客户机上登录

[root@aliyun-10-server ~]# docker login 192.168.137.9:8181
Username: admin
Password:Harbor12345
Error response from daemon: Get https://192.168.137.9/v2/: dial tcp 192.168.137.9:443: connect: connection refused

在进行harbor登录或上传代码时,会报出上面错误!

这是因为docker1.3.2版本开始默认docker registry使用的是https,而Harbor默认设置的是http方式而不是https,所以当执行用docker login、pull、push等命令操作非https的docker regsitry的时就会报错

解决办法:

如下,在/etc/docker/daemon.json文件里添加"insecure-registries"配置。(如果还不行,可以尝试将下面添加的地址由"172.16.60.213"改为"http://172.16.60.213:[port]"),如果不是80端口必须加端口,如果是80端口可以省略不写

[root@aliyun-10-server ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["http://f1361db2.m.daocloud.io"],
"insecure-registries": ["http://192.168.137.9:8181"]
}

然后重启docker服务

systemctl restart docker

接着再次验证harbor登录,发现就能登录上了

[root@aliyun-10-server ~]# docker login 192.168.137.9:8181
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/#credentials-store

Login Succeeded

或者直接执行docker login -u admin -p Harbor12345 192.168.137.9命令登陆

2)如果是在harbor本机登录,出现上面的报错:

`/etc/docker/daemon.json` 文件里添加"insecure-registries"配置 (第一行是之前添加的docker加速配置),注意两行之间有一个","逗号隔开
[root@aliyun-9-server ~]# docker login 192.168.137.9:8181
Username: admin
Password:
Error response from daemon: Get https://192.168.137.9/v2/: dial tcp 192.168.137.9:443: connect: connection refused
[root@aliyun-9-server ~]# vim /etc/docker/daemon.json
[root@aliyun-9-server ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://docker.mirrors.ustc.edu.cn"],
"insecure-registries": ["http://192.168.137.9:8181"]
}

修改过后重启docker, 然后重启Harbor服务

systemctl restart docker
cd /usr/local/harbor/
docker-compose stop
docker-compose start
docker-compose ps

然后再测试再harbor本机登录

[root@aliyun-9-server /usr/local/harbor]# docker login 192.168.137.9:8181
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/#credentials-store

Login Succeeded

登录的账号信息都保存到/root/.docker/config.json文件里了
# cat /root/.docker/config.json
{
        "auths": {
                "192.168.137.9:8181": {
                        "auth": "YWRtaW46SGFyYm9yMTIzNDU="
                }
        },
        "HttpHeaders": {
                "User-Agent": "Docker-Client/19.03.8 (linux)"
        }
}
只要/root/.docker/config.json里的信息不删除,后续再次登录的时候,就不用输入用户名和密码了

注意事项总结: harbor支持http和https,但如果使用http的话,在拉取镜像的时候,会抛出仓库不受信任的异常。 需要在所有的docker客户端的docker配置文件/etc/docker/daemon.json中添加如下配置:

{
    "insecure-registries": ["https://*.*.*.*"]
}

如果使用自签名的https证书,仍然会提示证书不受信任的问题。需要将自签名的ca证书发送到所有的docker客户端的指定目录。

关于使用自签名证书配置harbor的具体过程可以参考: https://github.com/goharbor/harbor/blob/master/docs/configure_https.md

harbor仓库的使用

# 镜像打标签的命令
# docker tag 镜像名:标签 私服地址/仓库项目名/镜像名:标签
 
# 推送到私服的命令
# docker push 私服地址/仓库项目名/镜像名:标签
 
# 从私服拉取镜像的命令
# docker pull 私服地址/仓库项目名/镜像名:标签

首先在Harbor web界面里最好创建一个自己需要的项目 (或者使用默认的library项目),项目公开和私有:

-  Public: 所有用户对于公开项目都有读权限,这种方式对于你想把一些仓库分享给其他人的时候,是非常方便的 -  Private: 私有项目只能被有特定用户权限的人去访问。这种方式对于内部团队来说共享也是比较方便的

比如创建一个公开项目harbor_images,点击进去可以看到推送镜像的信息提示

img

img

img

img

2.0版本支持Docker、Helm、CNAB进行推送镜像

然后就可以在Harbor服务器的终端命令行里进行镜像推送到Harbor仓库的操作了:

在进行harbor镜像推送和拉取操作前,需要事先login登录到harbor仓库里,这样才有harbor镜像的推送和拉取的权限!!

我们就在本机推送harbor images到镜像仓库:

[root@aliyun-9-server ~]# docker login 192.168.137.9
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

先查看本机有哪些镜像:

image-20221109175722965

比如推送其中的goharbor/redis-photon:v2.0.0镜像到Harbor仓库的harbor_images项目里

打标签:

[root@aliyun-9-server ~]# docker tag goharbor/redis-photon:v2.0.0 192.168.137.9:8181/harbor_images/redis-photon:v2.0.0

查看本机的images,发现多了一个上面制作的镜像,就是原来goharbor/redis-photon:v1.8.0的tag,可以选择删除

[root@aliyun-9-server ~]# docker images | grep redis-photon
192.168.137.9:8181/harbor_images/redis-photon   v2.0.0              c89ea2e53cc0        8 days ago          72.2MB
goharbor/redis-photon                           v2.0.0              c89ea2e53cc0        8 days ago          72.2MB

推送

[root@aliyun-9-server ~]# docker push 192.168.137.9:8181/harbor_images/redis-photon:v2.0.0
The push refers to repository [192.168.137.9:8181/harbor_images/redis-photon]
12042912d563: Pushed
87063a362784: Pushed
3e72063a3c12: Pushed
da380ff7675f: Pushed
dbaf2c918102: Pushed
v2.0.0: digest: sha256:3fa921ef8b17dcf543ced2d101029b1ada1128ee67ee7306a60e9688abe2429d size: 1366

然后登录到Harbor web 界面里,就可以看到harbor_images项目里就有了上面推送的两个镜像了,点击到对应的镜像了,还可以对镜像进行打标签,复制镜像等操作:

img

img

img

比如将harbor_images项目里上面的harbor_images/redis-photon镜像复制到library项目里

img

img

然后到"library"项目里就能看到上面从"harbor_images"项目里复制过来的镜像了

img

img

注意: harbor私仓的相关容器映射到主机的volumes数据卷的空间要有保证,最好是单独的分区空间

上面测试harbor容器通过volumes映射到主机的目录是/data/harbor, 可以到这里查看harbor推送的镜像:

[root@aliyun-9-server ~]# cd /data/harbor/registry/docker/registry/v2/repositories/

可以查看两个"项目"

[root@aliyun-9-server /data/harbor/registry/docker/registry/v2/repositories]# ll
total 0
drwxr-xr-x 3 10000 10000 26 May 20 10:56 harbor_images
drwxr-xr-x 3 10000 10000 26 May 20 11:15 library

[root@aliyun-9-server /data/harbor/registry/docker/registry/v2/repositories]# tree -L 2
.
├── harbor_images
│   └── redis-photon
└── library
    └── redis-python

4 directories, 0 files

========测试下在harbor客户端下载harbor仓库里的镜像=======
[root@aliyun-10-server ~]# systemctl restart docker
[root@aliyun-10-server ~]# docker login 192.168.137.9:8181
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/#credentials-store

Login Succeeded

从harbor仓库拉取镜像,相关命令在对应项目下的镜像中有,直接复制就行(但是这样pull的镜像没有tag,所以我们还是带上tag):

image-20221109180610042

image-20221109180615596

带上tag pull镜像:

docker pull 192.168.137.9:8181/harbor_images/redis-photon:v2.0.0

image-20221109180633474

可以在登录Harbor web界面之后,修改相关用户的密码。在不同用户账号下创建项目,以及推送和拉取harbor镜像操作

Harbor的https证书启用

通过上面可知,harbor默认安装后采用的是http方式,后面使用的时候可能会发现很多不方面。因为Docker客户端登录harbor进行镜像推送或拉取时默认是https方式!所以http方式下,需要在每一台harbor客户端机器上都要设置"insecure-registries", 感觉很麻烦!所以最好还是将harbor默认的http方式改为https方式!

另外,从安全角度考虑,容器的仓库在生产环境中往往也是需要被设定为https的方式,而harbor将这些证书的创建和设定都进行了简单的集成,下面来看一下在harbor下如何使用https的方式。配置记录如下:

在创建证书之前,为了方面验证,需要将前面在客户端机器上/etc/docker/daemon.json文件里添加"insecure-registries"配置删除

vim /etc/docker/daemon.json

rm -rf /root/.docker

systemctl restart docker

将harbor部署机自身的/etc/docker/daemon.json文件里添加"insecure-registries"配置也删除

rm -rf /root/.docker

vim /etc/docker/daemon.json

然后重启docker和docker-compose

[root@aliyun-9-server ~]# systemctl restart docker
[root@aliyun-9-server ~]# cd /usr/local/harbor/
[root@aliyun-9-server /usr/local/harbor]# docker-compose down -t 10
[root@aliyun-9-server /usr/local/harbor]# docker-compose up -d

1)创建CA

[root@aliyun-9-server /usr/local/harbor]# cd /root/
[root@aliyun-9-server ~]# mkdir harbor/ssl -p
[root@aliyun-9-server ~]# cd harbor/ssl
[root@aliyun-9-server ~/harbor/ssl]# openssl req  -newkey rsa:4096 -nodes -sha256 -keyout ca.key -x509 -days 365 -out ca.crt
Generating a 4096 bit RSA private key
..........................................++
.......................++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:beijing
Locality Name (eg, city) [Default City]:beijing
Organization Name (eg, company) [Default Company Ltd]:DevOps
Organizational Unit Name (eg, section) []:Tec
Common Name (eg, your name or your server's hostname) []:192.168.137.9
Email Address []:root@harbor.com

[root@aliyun-9-server ~/harbor/ssl]# ls
ca.crt  ca.key

image-20221110153504262

2) 创建证书请求文件csr

[root@aliyun-9-server ~/harbor/ssl]# openssl req -newkey rsa:4096 -nodes -sha256 -keyout 192.168.137.9.key  -out 192.168.137.9.csr
Generating a 4096 bit RSA private key
...........++
.........++
writing new private key to '192.168.137.9.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:beijing
Locality Name (eg, city) [Default City]:beijing
Organization Name (eg, company) [Default Company Ltd]:DevOps
Organizational Unit Name (eg, section) []:Tec
Common Name (eg, your name or your server's hostname) []:192.168.137.9
Email Address []:root@harbor.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:666666
An optional company name []:DevOps

3) 创建证书

[root@aliyun-9-server ~/harbor/ssl]# openssl x509 -req -days 365 -in 192.168.137.9.csr -CA ca.crt -CAkey ca.key -CAcreateserial -extfile extfile.cnf -out 192.168.137.9.crt
Signature ok
subject=/C=CN/ST=beijing/L=beijing/O=DevOps/OU=Tec/CN=192.168.137.9/emailAddress=root@harbor.com
Getting CA Private Key

4) 设定证书 & 修改

查看证书所在路径, 后面将harbor.yaml文件中的路径也同样设定

[root@aliyun-9-server ~/harbor/ssl]#pwd
/root/harbor/ssl
[root@aliyun-9-server ~/harbor/ssl]#ll
total 28
-rw-r--r-- 1 root root 2021 May 20 11:52 192.168.137.9.crt
-rw-r--r-- 1 root root 1805 May 20 11:50 192.168.137.9.csr
-rw-r--r-- 1 root root 3272 May 20 11:50 192.168.137.9.key
-rw-r--r-- 1 root root 2106 May 20 11:47 ca.crt
-rw-r--r-- 1 root root 3268 May 20 11:47 ca.key
-rw-r--r-- 1 root root   17 May 20 11:52 ca.srl
-rw-r--r-- 1 root root   34 May 20 11:51 extfile.cnf

5) 修改harbor.yaml文件

先关闭docker-compose

[root@aliyun-9-server ~/harbor/ssl]#cd /usr/local/harbor/
[root@aliyun-9-server /usr/local/harbor]#docker-compose down -v
Stopping nginx             ... done
Stopping harbor-jobservice ... done
Stopping harbor-core       ... done
Stopping registryctl       ... done
Stopping registry          ... done
Stopping harbor-portal     ... done
Stopping harbor-db         ... done
Stopping redis             ... done
Stopping harbor-log        ... done
Removing nginx             ... done
Removing harbor-jobservice ... done
Removing harbor-core       ... done
Removing registryctl       ... done
Removing registry          ... done
Removing harbor-portal     ... done
Removing harbor-db         ... done
Removing redis             ... done
Removing harbor-log        ... done
Removing network harbor_harbor
[root@aliyun-9-server /usr/local/harbor]#docker-compose ps
Name   Command   State   Ports
------------------------------

[root@aliyun-9-server /usr/local/harbor]#vim harbor.yml
# http related config
http:
  # port for http, default is 80. If https enabled, this port will redirect to https port
  port: 80
###这里我们还是把http的端口改成80,如果是非80端口,后续只能使用443登录,非80端口不能进行登录
# https related config
https:
  # https port for harbor, default is 443
  port: 443
  # The path of cert and key files for nginx
  certificate: /root/harbor/ssl/192.168.137.9.crt
  private_key: /root/harbor/ssl/192.168.137.9.key

image-20221110153708855

特别注意:上面harbor.yaml文件中修改的配置格式一定要正确!"https"要顶格写,"port:443" 和 "certificate"、"private_key"保持缩进一致!

否则在下面执行"./prepare"更新命令时,会报错:

# ./prepare

..........

File "/usr/lib/python3.6/site-packages/yaml/composer.py", line 84, in compose_node

node = self.compose_mapping_node(anchor)

File "/usr/lib/python3.6/site-packages/yaml/composer.py", line 127, in compose_mapping_node

while not self.check_event(MappingEndEvent):

File "/usr/lib/python3.6/site-packages/yaml/parser.py", line 98, in check_event

self.current_event = self.state()

File "/usr/lib/python3.6/site-packages/yaml/parser.py", line 439, in parse_block_mapping_key

"expected , but found %r" % token.id, token.start_mark)

yaml.parser.ParserError: while parsing a block mapping

in "/input/harbor.yml", line 15, column 4

expected , but found ''

in "/input/harbor.yml", line 17, column 5

上面的报错,就是由于harbor.yaml文件配置格式不正确导致的!!!!

接着执行prepare脚本,将harbor修改的配置更新到 docker-compose.yml 文件

[root@aliyun-9-server /usr/local/harbor]# ./prepare
prepare base dir is set to /usr/local/harbor
Clearing the configuration file: /config/log/logrotate.conf
Clearing the configuration file: /config/log/rsyslog_docker.conf
Clearing the configuration file: /config/nginx/nginx.conf
Clearing the configuration file: /config/core/env
Clearing the configuration file: /config/core/app.conf
Clearing the configuration file: /config/registry/passwd
Clearing the configuration file: /config/registry/config.yml
Clearing the configuration file: /config/registry/root.crt
Clearing the configuration file: /config/registryctl/env
Clearing the configuration file: /config/registryctl/config.yml
Clearing the configuration file: /config/db/env
Clearing the configuration file: /config/jobservice/env
Clearing the configuration file: /config/jobservice/config.yml
Generated configuration file: /config/log/logrotate.conf
Generated configuration file: /config/log/rsyslog_docker.conf
Generated configuration file: /config/nginx/nginx.conf
Generated configuration file: /config/core/env
Generated configuration file: /config/core/app.conf
Generated configuration file: /config/registry/config.yml
Generated configuration file: /config/registryctl/env
Generated configuration file: /config/registryctl/config.yml
Generated configuration file: /config/db/env
Generated configuration file: /config/jobservice/env
Generated configuration file: /config/jobservice/config.yml
loaded secret from file: /data/secret/keys/secretkey
Generated configuration file: /compose_location/docker-compose.yml
Clean up the input dir

查看一下docker-compose.yml文件,发现已经将新配置的443端口的https信息更新到docker-compose.yml文件里了。如下80端口和443端口都配置了,所以harbor访问时是http强转到https的

[root@aliyun-9-server /usr/local/harbor]# cat docker-compose.yml |grep 443 -C3
    dns_search: .
    ports:
      - 80:8080
      - 443:8443
    depends_on:
      - registry
      - core

重启docker-compose

[root@aliyun-9-server /usr/local/harbor]# docker-compose up -d
Creating network "harbor_harbor" with the default driver
Creating harbor-log ... done
Creating redis         ... done
Creating harbor-portal ... done
Creating registryctl   ... done
Creating registry      ... done
Creating harbor-db     ... done
Creating harbor-core   ... done
Creating nginx             ... done
Creating harbor-jobservice ... done

[root@aliyun-9-server /usr/local/harbor]# lsof -i :443
COMMAND      PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 129435 root    4u  IPv6 554529      0t0  TCP *:https (LISTEN)

[root@aliyun-9-server /usr/local/harbor]# lsof -i :80
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 8526 root    4u  IPv6 609138      0t0  TCP *:http (LISTEN)

[root@aliyun-9-server /usr/local/harbor]# docker-compose ps
      Name                     Command                  State                           Ports
-----------------------------------------------------------------------------------------------------------------
harbor-core         /harbor/entrypoint.sh            Up (healthy)
harbor-db           /docker-entrypoint.sh            Up (healthy)   5432/tcp
harbor-jobservice   /harbor/entrypoint.sh            Up (healthy)
harbor-log          /bin/sh -c /usr/local/bin/ ...   Up (healthy)   127.0.0.1:1514->10514/tcp
harbor-portal       nginx -g daemon off;             Up (healthy)   8080/tcp
nginx               nginx -g daemon off;             Up (healthy)   0.0.0.0:8181->8080/tcp, 0.0.0.0:443->8443/tcp
redis               redis-server /etc/redis.conf     Up (healthy)   6379/tcp
registry            /home/harbor/entrypoint.sh       Up (healthy)   5000/tcp
registryctl         /home/harbor/start.sh            Up (healthy)

在harbor部署机本机确认login登陆 (使用80端口或443端口都可以,自动跳转的)

[root@aliyun-9-server /usr/local/harbor]# cd
[root@aliyun-9-server ~]# docker login -u admin -p Harbor12345 192.168.137.9
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Error response from daemon: Get https://192.168.137.9:8181/v2/: http: server gave HTTP response to HTTPS client
[root@aliyun-9-server ~]# docker login -u admin -p Harbor12345 192.168.137.9:443
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Error response from daemon: Get https://192.168.137.9:443/v2/: x509: certificate signed by unknown authority

以上出现报错,此种情况多发生在自签名的证书,报错含义是签发证书机构未经认证,无法识别。

解决办法:

[root@aliyun-9-server ~]# ll /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
-r--r--r--. 1 root root 222148 Apr 14 16:42 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
[root@aliyun-9-server ~]# chmod 644 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
[root@aliyun-9-server ~]# cat /root/harbor/ssl/192.168.137.9.crt >> /etc/pki/tls/certs/ca-bundle.crt
[root@aliyun-9-server ~]# ll /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
-rw-r--r--. 1 root root 224169 May 20 12:08 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
[root@aliyun-9-server ~]# chmod 644 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem

由于证书是docker的daemon需要用到的,所以需要重启docker服务,进而也要重启docker-compose

[root@aliyun-9-server ~]# cd /usr/local/harbor/
[root@aliyun-9-server /usr/local/harbor]# systemctl restart docker
[root@aliyun-9-server /usr/local/harbor]# docker-compose down -v
[root@aliyun-9-server /usr/local/harbor]# docker-compose up -d

然后再次尝试在harbor本机登陆, 发现就可以正常登陆了!!

[root@aliyun-9-server ~]# docker login -u admin -p Harbor12345 192.168.137.9:443
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

[root@aliyun-9-server ~]# docker login -u admin -p Harbor12345 192.168.137.9
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded


[root@aliyun-9-server ~]# cat /root/.docker/config.json
{
        "auths": {
                "192.168.137.9": {
                        "auth": "YWRtaW46SGFyYm9yMTIzNDU="
                },
                "192.168.137.9:443": {
                        "auth": "YWRtaW46SGFyYm9yMTIzNDU="
                }
        },
        "HttpHeaders": {
                "User-Agent": "Docker-Client/19.03.8 (linux)"
        }
}


=========================================================================================

上面是在harbor本机尝试的登陆,现在在远程客户机上(这里客户机为192.168.137.10)测试harbor登陆:

首先很重要的一步,这一步极其关键!一定不要忘记操作!!

就是需要将harbor服务端生成的CA证书拷贝到每个远程客户机的/etc/docker/certs.d/harbor服务器的域名或ip/目录下

[root@aliyun-10-server ~]# mkdir -pv /etc/docker/certs.d/192.168.137.9/

接着在harbor服务器将CA证书拷贝过来

rsync -e "ssh -p22" -avpgolr /root/harbor/ssl/192* /root/harbor/ssl/ca* root@192.168.137.10:/etc/docker/certs.d/192.168.137.9/

然后在客户机上查看是否拷贝过来了harbor服务端的CA证书

[root@aliyun-10-server ~]# tree /etc/docker/certs.d/192.168.137.9
/etc/docker/certs.d/192.168.137.9
├── 192.168.137.9.crt
├── 192.168.137.9.csr
├── 192.168.137.9.key
├── ca.crt
├── ca.key
├── ca.srl
└── extfile.cnf

0 directories, 7 files

进行同样的授权操作:

[root@aliyun-10-server ~]# chmod 644 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
[root@aliyun-10-server ~]# cat /etc/docker/certs.d/192.168.137.9/192.168.137.9.crt >> /etc/pki/tls/certs/ca-bundle.crt
[root@aliyun-10-server ~]# chmod 644 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem

重启docker服务

[root@aliyun-10-server ~]# systemctl restart docker

最后进行harbor登陆,就可以正常登陆了!

[root@aliyun-10-server ~]# docker login -u admin -p Harbor12345 192.168.137.9:443
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

上面是使用443端口登陆harbor仓库是正常的,如果此时使用80端口登陆,则出现如下报错:所以在客户端就使用443端口来登陆harbor仓库了!

[root@aliyun-10-server ~]# docker login -u admin -p Harbor12345 192.168.137.9:80
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
Error response from daemon: Get https://192.168.137.9:80/v2/: http: server gave HTTP response to HTTPS client

[root@aliyun-10-server ~]# cat /root/.docker/config.json
{
        "auths": {
                "192.168.137.9:443": {
                        "auth": "YWRtaW46SGFyYm9yMTIzNDU="
                }
        },
        "HttpHeaders": {
                "User-Agent": "Docker-Client/19.03.8 (linux)"
        }
}

image-20221110154607981

[[  需要注意  ]] 如果Harbor里创建了多个账号,客户端使用A账号登录harbor后,docker pull下载的镜像是在B账号的项目里面的,并且该项目是私有的,那么需要先将A账号添加为该项目的成员后才能正常docker pull下载,否则会出现报错:denied: requested access to the resource is denied

image-20221110154627166

在harbor的客户端需要这样操作:

由于自签证书的原因,80端口是无法登录,那么80端口就无法push、pull,因此需要在pull、push是加上443端口:

pull镜像:

[root@aliyun-10-server ~]# docker pull 192.168.137.9/harbor_images/redis-photon:v2.0.0
Error response from daemon: Missing client certificate 192.168.137.9.cert for key 192.168.137.9.key
[root@aliyun-10-server ~]# docker pull 192.168.137.9:443/harbor_images/redis-photon:v2.0.0
v2.0.0: Pulling from harbor_images/redis-photon
836b6c765c93: Pull complete
1112e74d0bcf: Pull complete
00d2fcd44f29: Pull complete
211d75a66c3c: Pull complete
268c5ba497b1: Pull complete
Digest: sha256:3fa921ef8b17dcf543ced2d101029b1ada1128ee67ee7306a60e9688abe2429d
Status: Downloaded newer image for 192.168.137.9:443/harbor_images/redis-photon:v2.0.0
192.168.137.9:443/harbor_images/redis-photon:v2.0.0

[root@aliyun-10-server ~]# docker images

image-20221110154744719

push镜像:

[root@aliyun-10-server ~]# docker tag hello-world 192.168.137.9:443/harbor_images/hello-world:latest
[root@aliyun-10-server ~]# docker push 192.168.137.9:443/harbor_images/hello-world:latest
The push refers to repository [192.168.137.9:443/harbor_images/hello-world]
9c27e219663c: Layer already exists
latest: digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 size: 525

Harbor2.1.0部署私有镜像仓库

https://my.oschina.net/adailinux/blog/4656928

Harbor启动和停止

docker-compose --help

Harbor 的日常运维管理是通过docker-compose来完成的,Harbor本身有多个服务进程,都放在docker容器之中运行,我们可以通过docker ps命令查看。

[root@Docker ~/harbor]# docker ps

或者docker-compose 来查看

# cd /usr/local/harbor/         #必须进入harbor安装目录
# docker-compose ps         #查看

image-20221110160140848

# 停止 harbor
docker-compose down -v  # -v表示打印详细信息
# 启动 harbor
docker-compose up -d    # -d表示后台启动

#######################################################################################
docker-compose up -d          # 后台启动,如果容器不存在根据镜像自动创建
docker-compose down -v        # 停止容器并删除容器
docker-compose start          # 启动容器,容器不存在就无法启动,不会自动创建镜像
docker-compose stop           # 停止容器
   

需要注意:

其实上面是停止docker-compose.yml中定义的所有容器,默认情况下docker-compose就是操作同目录下的docker-compose.yml文件。如果使用其他yml文件,可以使用-f自己指定

访问WEB、并操作

在浏览器输入192.168.12.183,因为我配置的域名为192.168.12.183。默认账号密码: admin / Harbor12345 登录后修改密码

ps:密码在habor.cfg中,可以自己设置

主页面

image-20221110160308467

image-20221110160314003

默认安装后会自动创建一个名称为library的项目,且访问级别为公开(表示任何人可读,即任何人都可以下载该项目中的镜像)

admin全局设置

image-20221110160324682

可以修改用户名、密码等

image-20221110160337748

image-20221110160343422

语言设置

image-20221110160351885

创建及查看用户

image-20221110160403555

image-20221110160408204

密码:Abc66666666

查看创建的用户:

image-20221110160416142

仓库管理

->新建目标

image-20221110160422426

image-20221110160429617

可以先测试连接,看是否成功

image-20221110160438328

image-20221110160448450

如果你勾选了验证远程证书,那么验证远程证书就为true,反之

image-20221110160454462

项目

->新建项目:

image-20221110160528409

library:这个是默认的项目

image-20221110160544811

image-20221110160552684

image-20221110160557753

image-20221110160604423

命令行登陆到harbor

修改提示需要https的错误:

harbor也可以在命令行下进行登录,登录后可以将本地的镜像上传到harbor,也可以下载用户拥有权限项目下的镜像

登陆时,提示我们需要get https,我们需要修改配置,然后重启docker:

image-20221110160620890

第一次登录报错了,报这个错误可能有如下2个原因:

  1. 是端口错了!
  2. 未在docker启动文件中添加--insecure-registry信任关系!

大多数这个错误是第2个原因,因为你没有添加信任关系的话,docker默认使用的是https协议,所以端口不对(443),会报连接拒绝这个错误;或者提示你 "服务器给HTTPS端的是HTTP响应" 这个错误,因为你没添加端口信任,服务器认为这是默认的https访问,返回的却是http数据!

解决方法:

正确的添加信任关系包括端口号:

注:

记住,这是harbor镜像仓库,而不是单纯的registry容器仓库!

如果你用的是默认的80端口,则不需要加,或者加上80也行,而不是5000这个端口!

[root@localhost harbor]# tee /etc/docker/daemon.json <<-'EOF'
{
"insecure-registries": [
"http://192.168.137.60"    #harbor服务主机ip,如果不是80端口,一定要把端口同时添加进去
]
}
EOF

1.修改各docker client配置

第一种方式:

[root@Docker ~/harbor]# vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --insecure-registry 192.168.12.183
增加 --insecure-registry 192.168.12.183 即可。

重启docker:

[root@Docker ~/harbor]# systemctl daemon-reload
[root@Docker ~/harbor]# systemctl restart docker

第二种方式:

创建/etc/docker/daemon.json文件,在文件中指定仓库地址
cat > /etc/docker/daemon.json << EOF
{ "insecure-registries":["http://192.168.12.183"] }
EOF
如果端口是80可以不用写,如:
{ "insecure-registries":["www.testharbor.com"] }
然后重启docker就可以

这样设置完成后,就不会提示我们使用https的错误了。

登陆:

[root@node01 harbor]# docker login 192.168.12.183
Username: admin
Password:
Login Succeeded

push镜像

操作1

1.创建Dockerfile

[root@Docker ~]# mkdir firstharbor
[root@Docker ~]# cd firstharbor/
[root@Docker ~/firstharbor]# vim Dockerfile
FROM mongo 
ENV TZ "Asia/Shanghai"

2.创建镜像

[root@Docker ~/firstharbor]# docker build -t 192.168.137.60/devel/mongodb:latest .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM mongo
latest: Pulling from library/mongo
22e816666fd6: Downloading               #先下载镜像
079b6d2a1e53: Downloading
11048ebae908: Downloading
c58094023a2e: Downloading
252003e80cc8: Download complete
7cb91a976d85: Download complete
929663192bb1: Download complete
5af259c6f8d8: Download complete
44f2ab049616: Download complete
ac4fec42bcd3: Download complete
7359cd87f9d1: Download complete
33924096c605: Download complete
e96c8ed20738: Download complete
latest: Pulling from library/mongo
22e816666fd6: Pull complete
079b6d2a1e53: Pull complete
11048ebae908: Pull complete
c58094023a2e: Pull complete
252003e80cc8: Pull complete
7cb91a976d85: Pull complete
929663192bb1: Pull complete
5af259c6f8d8: Pull complete
44f2ab049616: Pull complete
ac4fec42bcd3: Pull complete
7359cd87f9d1: Pull complete
33924096c605: Pull complete
e96c8ed20738: Pull complete
Digest: sha256:face4b795eef9c368c062f47ba03ffa6d646281e98f007578d04a54eff33a05f
Status: Downloaded newer image for mongo:latest
 ---> 191b28dbfefe
Step 2/2 : ENV TZ "Asia/Shanghai"
 ---> Running in ed7e75c0d066
Removing intermediate container ed7e75c0d066
 ---> 3b2620b05a0a
Successfully built 3b2620b05a0a
Successfully tagged 192.168.137.60/devel/mongodb:latest     #在打标签

查看镜像:这里其他是先下载的mongo镜像,然后在打标签的

image-20221110161051395

3.把镜像push到Harbor

[root@Docker ~/harbor]# docker login 192.168.12.183
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/#credentials-store

Login Succeeded

image-20221110161133086

在每个项目中,都会说明怎么推送镜像:

image-20221110161145826

[root@Docker ~/firstharbor]# docker push 192.168.137.60/devel/mongodb:latest
The push refers to repository [192.168.137.60/devel/mongodb]
7da824ea45b1: Pushed
378ba8454ecc: Pushed
94607a669ef8: Pushed
a60d121750c4: Pushed
80673feff74a: Pushed
c6f009d519b5: Pushed
6c9243a046e2: Pushed
4a5bcbc3eef1: Pushed
c42b3a17f599: Pushed
19331eff40f0: Pushed
100ef12ce3a4: Pushed
97e6b67a30f1: Pushed
a090697502b8: Pushed
latest: digest: sha256:a1c1cee650ab4e35deae9f30c55c9e3c13a78a09963f26e1d3156b64466d4d6d size: 3032

image-20221110161217885

4.然后在web界面查看:

image-20221110161225610

目前是1个镜像,点击进去查看详情:

image-20221110161232082

image-20221110161237613

操作2

如果不是自己创建的镜像,记得先执行 docker tags 给镜像做tag,例如:

我们先pull一个centos镜像:

# docker pull centos
Using default tag: latest
latest: Pulling from library/centos
729ec3a6ada3: Downloading
latest: Pulling from library/centos
729ec3a6ada3: Pull complete
Digest: sha256:f94c1d992c193b3dc09e297ffd54d8a4f1dc946c37cbeceb26d35ce1647f88d9
Status: Downloaded newer image for centos:latest

查看我们pull的镜像:

# docker images|grep centos

image-20221110161338372

修改tag:你要上传到哪个项目下,就参考哪个项目的推送镜像

image-20221110161347446

[root@Docker ~/harbor]# docker tag centos:latest 192.168.137.60/devel/centos:harbor
[root@Docker ~/harbor]# docker images 192.168.137.60/devel/centos:harbor
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
192.168.137.60/devel/centos   harbor              0f3e07c0138f        3 weeks ago         220MB

push镜像:

[root@Docker ~/harbor]# docker push 192.168.137.60/devel/centos:harbor
The push refers to repository [192.168.137.60/devel/centos]
9e607bb861a7: Pushed
harbor: digest: sha256:6ab380c5a5acf71c1b6660d645d2cd79cc8ce91b38e0352cbf9561e050427baf size: 529

image-20221110161428669

登陆harbor站点,进行查看是否上次成功:

image-20221110161435794

上面上传了一个MongoDB镜像,所有这里是2个,点击进入查看详情:

image-20221110161441377

在点击对应的进入查看:

image-20221110161447668

pull镜像

删除本地镜像:因为我们是在一个服务器上面操作的

[root@Docker ~/harbor]]# docker rmi 192.168.137.60/devel/mongodb:latest
Untagged: 192.168.137.60/devel/mongodb:latest
Untagged: 192.168.137.60/devel/mongodb@sha256:a1c1cee650ab4e35deae9f30c55c9e3c13a78a09963f26e1d3156b64466d4d6d
Deleted: sha256:3b2620b05a0a032f7226e4d8b3cfe1377c8eac7acd439d3035dcdd4e22435dc4

下载harbor上的镜像:

1、登陆到WEB界面,进入你需要下载镜像的项目

image-20221110161519111

image-20221110161524360

image-20221110161531765

[root@Docker ~/harbor]]# docker pull 192.168.137.60/devel/mongodb:latest
latest: Pulling from devel/mongodb
22e816666fd6: Pull complete
079b6d2a1e53: Pull complete
11048ebae908: Pull complete
c58094023a2e: Pull complete
252003e80cc8: Pull complete
7cb91a976d85: Pull complete
929663192bb1: Pull complete
5af259c6f8d8: Pull complete
44f2ab049616: Pull complete
ac4fec42bcd3: Pull complete
7359cd87f9d1: Pull complete
33924096c605: Pull complete
e96c8ed20738: Pull complete
Digest: sha256:a1c1cee650ab4e35deae9f30c55c9e3c13a78a09963f26e1d3156b64466d4d6d
Status: Downloaded newer image for 192.168.137.60/devel/mongodb:latest
# docker images |grep mongo     #查看本地是否存在

image-20221110161605052

然后在web界面查看:

image-20221110161609863

image-20221110161616508

注意到没有,这里下载的次数为2,因为我在这里下载了2次,哈哈,OK!

image-20221110161623305

我有pull了一次centos镜像。~^~

高可用架构部署

参考:https://www.youendless.com/post/deploy_harbor_service/

参考:https://www.cnblogs.com/breezey/p/9444231.html

单机部署harbor显然无法满足在生产中需求,必须要保证应用的高可用性。目前有两种主流的方案来解决Harbor高可用问题:

-  双主复制 -  多harbor实例共享后端存储

对于企业项目来说,重要的存储中间件,肯定都要抽离出来,当前各云厂商都具备这些云服务。对于当前采用harbor新版本部署服务的企业来说,它的开发者太幸运了,直接少了mysql组件,根本不会感知harbor的重构。

早期的v1.4,就是必须依赖这些,到现在,已经稳定运行2年时间,不可能再去迁移数据库了。上面只是一个简单的参考模型,对于很多企业来说,直接全都走公网模式也行。

mysql是harbor的数据库,ks3、s3、oss等这些对象存储层用来存储镜像文件,redis主要用来进行registry缓存、session存储包括后期的helm chartmuseum缓存,postgresql用来存储clair从各大开源社区获取的CVE漏洞缺陷数据以及镜像层的扫描结构数据,当前CVE的数据从2002到2018年差不多将近20万条

image-20221110161704711

Harbor双主复制

主从同步

harbor官方默认提供主从复制的方案来解决镜像同步问题,通过复制方式,可以实时将测试环境harbor仓库的镜像同步到生产环境harbor,类似于如下流程:

image-20221110162105512

在实际生产运维的中,往往需要把镜像发布到几十或上百台集群节点上。这时,单个Registry已经无法满足大量节点的下载需求,因此要配置多个Registry实例做负载均衡。手工维护多个Registry实例上的镜像,将是十分繁琐的事情。Harbor可以支持一主多从的镜像发布模式,可以解决大规模镜像发布的难题

image-20221110162119662

只要往一台Registry上发布,镜像就像仙女散花般地同步到多个Registry中,高效可靠。如果是地域分布较广的集群,还可以采用层次型发布方式,如从集团总部同步到省公司,从省公司再同步到市公司

image-20221110162145145

然而单靠主从同步方式仍然解决不了harbor主节点的单点问题

双主复制

所谓双主复制其实就是复用主从同步实现两个harbor节点之间的双向同步,来保证数据的一致性,然后在两台harbor前端配置一个负载均衡器将进来的请求分流到不同的实例中去,只要有一个实例中有了新的镜像,就是自动的同步复制到另外的实例中去,这样实现了负载均衡,也避免了单点故障,在一定程度上实现了Harbor的高可用性。可以使用下面方案:Nginx+Keepalive+Harbor,VIP可以在LB上实现漂移。(或者VIP直接在Harbor之间漂移)

image-20221110162249174

创建harbor主主复制,可以在harbor的web界面里创建相互之间镜像同步关系,同步关系可以选择相同用户或不同用户之间。这样就保证了harbor双机热备关系。

1) "系统管理"->"仓库管理"->"新建目标", 填写对端harbor信息
2) "系统管理"->"同步管理"->"新建规则", 规则里会引用目的Registry,也就是上面一步创建的目标。
        同步模式有Push-based,Pull-based;
        触发模式有自动和定时和事件驱动。

不过这个方案有一个问题:

就是有可能两个Harbor实例中的数据不一致。假设如果一个实例A挂掉了,这个时候有新的镜像进来,那么新的镜像就会在另外一个实例B中,后面即使恢复了挂掉的A实例,Harbor实例B也不会自动去同步镜像,这样只能手动的先关掉Harbor实例B的复制策略,然后再开启复制策略,才能让实例B数据同步,让两个实例的数据一致。所以,在实际生产使用中,主从复制十分的不靠谱

多harbor实例共享后端存储

参考:https://juejin.im/post/5d973e246fb9a04dfa0963fb

共享后端存储算是一种比较标准的方案,就是多个Harbor实例共享同一个后端存储,任何一个实例持久化到存储的镜像,都可被其他实例中读取。通过前置LB进来的请求,可以分流到不同的实例中去处理,这样就实现了负载均衡,也避免了单点故障

image-20221110162345004

这个方案在实际生产环境中部署需要考虑三个问题1. 共享存储的选取,Harbor的后端存储目前支持AWS S3、Openstack Swift、Ceph等,[在实验环境里,可以直接使用nfs] 2. Session在不同的实例上共享,这个现在其实已经不是问题了,在最新的harbor中,默认session会存放在redis中,我们只需要将redis独立出来即可。可以通过redis sentinel或者redis cluster等方式来保证redis的可用性。[在实验环境里,仍然使用单台redis] 3. Harbor多实例数据库问题,这个只需要将harbor中的数据库拆出来独立部署即可。让多实例共用一个外部数据库,外部数据库可以通过Mysql 高可用方案保证高可用性

Docker的构建汇总

基础镜像

# cat Dockerfile
FROM centos:centos7.5.1804
MAINTAINER "base@Mirror.com"
RUN yum update -y && \
  yum install kde-l10n-Chinese -y && \
  yum reinstall -y glibc-common && \        #安装中文支持
  yum install -y net-tools && \
  yum -y install python-setuptools && easy_install pip && pip install supervisor && \  #安装supervisor多进程管理工具,用于启动多进程
  yum clean all && \
  rm -rf /tmp/* rm -rf /var/cache/yum/* && \
  localedef -c -f UTF-8 -i zh_CN zh_CN.UTF-8 && \       #配置显示中文
  ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime       #修改时区
#设置环境变量
ENV LANG=zh_CN.UTF-8 \
  LANGUAGE=zh_CN:zh \
  LC_ALL=zh_CN.UTF-8
# Define default command
CMD ["bash"]

# docker build -t baseimages:7 .

1.构建httpd镜像

使用docker build创建镜像时,需要使用 Dockerfile 文件自动化制作 image 镜像

注:Dockerfile有点像源码编译时./configure后产生的Makefile

以下操作要在docker物理机上操作:

1、创建工作目录

[root@Docker ~]# mkdir /docker-build
[root@Docker ~]# cd /docker-build/
[root@Docker /docker-build]# touch Dockerfile

注: make自动化编译时需要Makefile文件,自动化创建docker镜像时,需要Dockerfile

2、编辑 Dockerfile

Dockerfile用来创建一个自定义的image,包含了用户指定的软件依赖等。

[root@Docker /docker-build]# vim Dockerfile
FROM centos:latest
MAINTAINER  <www.haha.com>
RUN yum -y install httpd
ADD start.sh /usr/local/bin/start.sh
ADD index.html /var/www/html/index.html

注释:

FROM  centos:latest # FROM 基于哪个镜像
MAINTAINER  <www.haha.com>  # MAINTAINER 镜像创建者
RUN yum -y install httpd   #RUN 安装软件用
ADD start.sh /usr/local/bin/start.sh   
ADD index.html /var/www/html/index.html
CMD /usr/local/bin/start.sh
# ADD  将文件<src>拷贝到新产生的镜像的文件系统对应的路径<dest>。所有拷贝到新镜像中的文件和文件夹权限为0755,uid和gid为0
CMD echo hello world    #container启动时执行的命令或启动服务,但是一个Dockerfile中只能有一条CMD命令,多条则只执行最后一条CMD. 

如:dockefile1 中的内容如下: 
#test
FROM ubuntu
MAINTAINER xxx
RUN echo hello1 > test1.txt
RUN echo hello2 > /test2.txt
EXPOSE 80
EXPOSE 81
CMD ["/bin/bash"]

3、创建 start.sh脚本启动httpd服务和apache默认首页index.html文件

[root@Docker /docker-build]# echo "/usr/sbin/httpd -DFOREGROUND" >start.sh
[root@Docker /docker-build]# chmod a+x start.sh

注:/usr/sbin/httpd -DFOREGROUND    相当于执行了 systemctl start httpd



创建 index.html
[root@Docker /docker-build]# echo "first docker image build test" > index.html
[root@Docker /docker-build]# ls     # docker-build目录下有3个文件
Dockerfile  index.html  start.sh

4、使用命令build来创建新的image

语法:docker build -t  父镜像名:镜像的tag   Dockerfile文件所在路径
-t :表示tage,镜像名

例:使用命令docker build来创建新的image,并命名为centos:httpd2.4 

[root@Docker /docker-build]# docker build -t centos:httpd2.4 ./
注: ./ 表示当前目录。另外你的当前目录下要包含Dockerfile

Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM centos:latest
 ---> 9f38484d220f
Step 2/5 : MAINTAINER  <www.haha.com>
 ---> Using cache
 ---> c29a285ccf7c
Step 3/5 : RUN yum -y install httpd
 ---> Using cache
 ---> ba4a40a60070
Step 4/5 : ADD start.sh /usr/local/bin/start.sh
 ---> 55655f68a26f
Step 5/5 : ADD index.html /var/www/html/index.html
 ---> 22b6c2091775
Successfully built 22b6c2091775
Successfully tagged centos:httpd2.4


查看 images 列表
[root@Docker /docker-build]# docker images
REPOSITORY                TAG                 IMAGE ID            CREATED           SIZE
centos                    httpd2.4            22b6c2091775        About a minute ago   318MB

运行该容器,并访问:

image-20221110162829245

注:docker镜像=应用/程序+库

2.构建nginx镜像

1、下载基础镜像

首先下载一个创建nginx镜像的基础镜像centos镜像

[root@Docker ~]# docker pull centos
Using default tag: latest
latest: Pulling from library/centos
Digest: sha256:8d487d68857f5bc9595793279b33d082b03713341ddec91054382641d14db861
Status: Image is up to date for centos:latest

2、创建工作目录

[root@Docker ~]# mkdir nginx
[root@Docker ~]# cd nginx/

3、创建并编写Dockerfile

根据具体的nginx安装过程来编写dockerfile

[root@Docker ~/nginx]#vim Dockerfile
#设置基础镜像
FROM centos:7.2.1511
#维护该镜像用户信息
MAINTAINER the CentOS Project<cloud-ops@centos.org>
#安装相关依赖包及添加nginx用户
RUN yum install -y wget pcre pcre-devel gcc gcc-c++ automake make zlib zlib-devel openssl openssl-devel && \
    groupadd -r nginx && useradd -r -g nginx -s /bin/false -M nginx && \
    ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
#下载与解压nginx源码包
WORKDIR /opt/
RUN wget http://nginx.org/download/nginx-1.16.0.tar.gz \
  && tar xf nginx-1.16.0.tar.gz
#编译安装nginx
WORKDIR /opt/nginx-1.16.0/
RUN ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-http_stub_status_module --with-http_ssl_module && make && make install
#开启80和443端口
EXPOSE 80
EXPOSE 443
#配置环境变量->配置了环境变量后,就可以不用写启动脚本,直接nginx命令就可以启动nginx
ENV PATH /usr/local/nginx/sbin:$PATH

#修改nginx配置文件,以非daemon方式启动
RUN echo "daemon off;" >>/usr/local/nginx/conf/nginx.conf

#复制服务启动脚本并设置权限
#WORKDIR /root/nginx
#复制当前目录下(与Dockerfile同目录)的run.sh到根下面
#ADD run.sh /run.sh
#加执行权限
#RUN chmod 755 /run.sh
#启动容器时执行脚本
#CMD ["/run.sh"]
CMD ["nginx"]

4、编写服务启动脚本->配置了环境变量后,就可以不用写启动脚本,直接nginx命令就可以启动

[root@Docker ~/nginx]# vim run.sh
#!/bin/bash
/usr/local/nginx/sbin/nginx

[root@Docker ~/nginx]# ls

image-20221110163100654

5、生成镜像,并查看

[root@Docker ~/nginx] # docker build -t nginx:new .

image-20221110163117228

报错如下:

Error processing tar file(exit status 1): write /var/cache/yum/x86_64/7/updates/1622e04cec402bd48e788e4d5241b8bd6e83549a64cdddd609b2383236b2e76e-primary.sqlite.bz2: no space left on device #磁盘空间不足->增加磁盘空间

安装过程可能出现错误,可能需要安装别的包,将其补在dockerfile文件中,

[root@Docker ~/nginx]# docker images
REPOSITORY                                 TAG                 IMAGE ID            CREATED             SIZE
nginx                                      new                 1b466b83880e        6 minutes ago       396MB

6、启动容器进行测试

[root@Docker ~/nginx] #docker run --rm -d -P nginx:new
272770a5e235cc175d2fda60f07ab0bf4bee14df758c5966278de9f00ccb7b49

#查看内部的80端口和443端口,被分别映射到本地端口
[root@Docker ~/nginx]# docker ps -l
CONTAINER ID        IMAGE                                      COMMAND                  CREATED              STATUS                      PORTS                                                              NAMES
272770a5e235        nginx:new                                  "/run.sh"                About a minute ago   Up About a minute           0.0.0.0:32773->80/tcp, 0.0.0.0:32772->443/tcp

访问本地的32773

image-20221110170904827

image-20221110170910396

构建nginx镜像

https://www.cnblogs.com/zhujingzhi/p/9742085.html

# cat Dockerfile
#基础镜像
FROM alpine

#作者信息
MAINTAINER NGINX Docker alpine

#设置NGINX_VERSION变量 
ENV NGINX_VERSION 1.20.2

#修改源
RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories && \
    echo "http://mirrors.aliyun.com/alpine/latest-stable/community/" >> /etc/apk/repositories

#安装需要的软件
RUN apk update && \
    apk add --no-cache ca-certificates && \
    apk add --no-cache curl bash tree tzdata && \
    cp -rf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

#编译安装nginx
RUN GPG_KEYS=B0F4253373F8F6F510D42178520A9993A1C052F8 \
    && CONFIG="\
        --prefix=/etc/nginx \
        --sbin-path=/usr/sbin/nginx \
        --modules-path=/usr/lib/nginx/modules \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --pid-path=/var/run/nginx.pid \
        --lock-path=/var/run/nginx.lock \
        --http-client-body-temp-path=/var/cache/nginx/client_temp \
        --http-proxy-temp-path=/var/cache/nginx/proxy_temp \
        --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
        --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
        --http-scgi-temp-path=/var/cache/nginx/scgi_temp \
        --user=nginx \
        --group=nginx \
        --with-http_ssl_module \
        --with-http_realip_module \
        --with-http_addition_module \
        --with-http_sub_module \
        --with-http_dav_module \
        --with-http_flv_module \
        --with-http_mp4_module \
        --with-http_gunzip_module \
        --with-http_gzip_static_module \
        --with-http_random_index_module \
        --with-http_secure_link_module \
        --with-http_stub_status_module \
        --with-http_auth_request_module \
        --with-http_xslt_module=dynamic \
        --with-http_image_filter_module=dynamic \
        --with-http_geoip_module=dynamic \
        --with-threads \
        --with-stream \
        --with-stream_ssl_module \
        --with-stream_ssl_preread_module \
        --with-stream_realip_module \
        --with-stream_geoip_module=dynamic \
        --with-http_slice_module \
        --with-mail \
        --with-mail_ssl_module \
        --with-compat \
        --with-file-aio \
        --with-http_v2_module \
        --with-pcre \
        --with-debug \
    " \
    && addgroup -S nginx \
    && adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx \
    && apk add --no-cache --virtual .build-deps \
        gcc \
        libc-dev \
        make \
        openssl-dev \
        pcre-dev \
        zlib-dev \
        linux-headers \
        curl \
        gnupg \
        libxslt-dev \
        gd-dev \
        geoip-dev \
    && curl -fSL http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz -o nginx.tar.gz \
    && curl -fSL http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz.asc  -o nginx.tar.gz.asc \
    && export GNUPGHOME="$(mktemp -d)" \
    && found=''; \
    for server in \
        ha.pool.sks-keyservers.net \
        hkp://keyserver.ubuntu.com:80 \
        hkp://p80.pool.sks-keyservers.net:80 \
        pgp.mit.edu \
    ; do \
        echo "Fetching GPG key $GPG_KEYS from $server"; \
        gpg --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$GPG_KEYS" && found=yes && break; \
    done; \
    test -z "$found" && echo >&2 "error: failed to fetch GPG key $GPG_KEYS" && exit 1; \
    gpg --batch --verify nginx.tar.gz.asc nginx.tar.gz \
    && rm -r "$GNUPGHOME" nginx.tar.gz.asc \
    && mkdir -p /usr/src \
    && tar -zxC /usr/src -f nginx.tar.gz \
    && rm nginx.tar.gz \
    && cd /usr/src/nginx-$NGINX_VERSION \
    && ./configure $CONFIG --with-debug \
    && make -j$(getconf _NPROCESSORS_ONLN) \
    && mv objs/nginx objs/nginx-debug \
    && mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so \
    && mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so \
    && mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so \
    && mv objs/ngx_stream_geoip_module.so objs/ngx_stream_geoip_module-debug.so \
    && ./configure $CONFIG \
    && make -j$(getconf _NPROCESSORS_ONLN) \
    && make install \
    && rm -rf /etc/nginx/html/ \
    && mkdir /etc/nginx/conf.d/ \
    && mkdir -p /usr/share/nginx/html/ \
    && install -m644 html/index.html /usr/share/nginx/html/ \
    && install -m644 html/50x.html /usr/share/nginx/html/ \
    && install -m755 objs/nginx-debug /usr/sbin/nginx-debug \
    && install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so \
    && install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so \
    && install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so \
    && install -m755 objs/ngx_stream_geoip_module-debug.so /usr/lib/nginx/modules/ngx_stream_geoip_module-debug.so \
    && ln -s ../../usr/lib/nginx/modules /etc/nginx/modules \
    && strip /usr/sbin/nginx* \
    && strip /usr/lib/nginx/modules/*.so \
    && rm -rf /usr/src/nginx-$NGINX_VERSION \
    \
    #Bring in gettext so we can get `envsubst`, then throw
    #the rest away. To do this, we need to install `gettext`
    #then move `envsubst` out of the way so `gettext` can
    #be deleted completely, then move `envsubst` back.
    && apk add --no-cache --virtual .gettext gettext \
    && mv /usr/bin/envsubst /tmp/ \
    \
    && runDeps="$( \
        scanelf --needed --nobanner /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst \
            | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
            | sort -u \
            | xargs -r apk info --installed \
            | sort -u \
    )" \
    && apk add --no-cache --virtual .nginx-rundeps $runDeps \
    && apk del .build-deps \
    && apk del .gettext \
    && mv /tmp/envsubst /usr/local/bin/ \
    \
    # forward request and error logs to docker log collector
    && ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log

#将目录下的文件copy到镜像中
COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf

#开放80端口
EXPOSE 80

STOPSIGNAL SIGTERM

#启动nginx命令
CMD ["nginx", "-g", "daemon off;"]


# cat nginx.conf 
user  nginx;
worker_processes  auto;
worker_rlimit_nofile 51200;
worker_cpu_affinity auto;
error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;
events {
    worker_connections  10240;
    use epoll;
    multi_accept on;
    accept_mutex on;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    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;
    server_tokens off; 
    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

# cat default.conf 
server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

构建:
docker build -t nginx:1.20.2 .
启动:
docker run -itd --name alpine-nginx -p 80:80 -m 2048m --memory-swap=2048m  --cpu-shares=256 nginx:1.20.2

# docker exec -it alpine-nginx sh

3.构建mysql镜像

官方参考:https://hub.docker.com/r/mysql/mysql-server/

MySQL具有体积小、速度快、成本低的优势,成为中小型企业首选的数据库

1、创建工作目录

[root@Docker ~]# mkdir mysql
[root@Docker ~]# cd mysql/

2、创建dockerfile文件

[root@Docker ~/mysql1]# vim Dockerfile
FROM centos6
MAINTAINER this is centos6 Project-mysql
RUN yum install -y mysql mysql-server mysql-devel
RUN /etc/init.d/mysqld start && \
  mysql -e "grant all privileges on *.* to 'root'@'%' identified by '666666';" && \
  mysql -e "grant all privileges on *.* to 'root'@'localhost' identified by '666666';"
EXPOSE 3306
CMD ["/usr/bin/mysqld_safe"] 

3、利用dockerfile生成镜像

[root@Docker ~/mysql]# docker build -t centos6:mysql .

image-20221114101711773

4、运行容器并验证

使用新镜像运行容器,并随机映射本地的端口到容器的3306端口

[root@Docker ~/mysql]# docker run --name=centos6_mysql_server -d -P centos6:mysql
02093d93cda267508032d839fadf1a878222f34a912b222d05117dcf1b458b4d
[root@Docker ~/mysql]# docker ps -a
CONTAINER ID        IMAGE                                      COMMAND                  CREATED             STATUS                           PORTS                                                              NAMES
02093d93cda2        centos6:mysql                              "/usr/bin/mysqld_safe"   8 seconds ago       Up 7 seconds                     0.0.0.0:32769->3306/tcp  

或者这样查看

[root@Docker ~/mysql]# docker port centos6_mysql_server
3306/tcp -> 0.0.0.0:32769

从另外一台主机登陆MySQL数据库进行验证

[root@centos-6-min ~]# mysql -h 192.168.12.183 -u root -P 32769 -p

image-20221114101844140

image-20221114101917907

构建mysql镜像

# tree -L 1 ../mysql/
../mysql/
├── Dockerfile
├── my.cnf
├── mysql-5.7.22-linux-glibc2.12-x86_64
├── mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz
└── run.sh

下载mysql

wget https://downloads.mysql.com/archives/get/file/mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz

编写Dockerfile文件

# cat Dockerfile
FROM 192.168.137.60/devel/centos:7.6
MAINTAINER k8s-mysql

#安装依赖包
#创建用户和组
RUN yum install -y libaio* numactl.x86_64 \
  && groupadd -r -g 306 mysql && useradd -M -s /sbin/nologin -g 306 -u 306 mysql

#解压mysql并链接到指定位置
ADD mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz /usr/local/
WORKDIR /usr/local/
RUN ln -sv mysql-5.7.22-linux-glibc2.12-x86_64 /usr/local/mysql && chown -R mysql.mysql /usr/local/mysql && cd mysql-5.7.22-linux-glibc2.12-x86_64

#可执行文件写入环境变量
ENV PATH /usr/local/mysql/bin:$PATH
#让容器支持中文,centos容器默认是不支持中文的
ENV LC_ALL en_US.UTF-8

#链接库文件
RUN ln -sv /usr/local/mysql/include/ /usr/local/include/mysql

#创建数据目录并初始化
RUN mkdir -p /opt/data && chown -R mysql.mysql /opt/data/ \
  && /usr/local/mysql/bin/mysqld --initialize --user=mysql --datadir=/opt/data/

#复制启动脚本
RUN cp -a /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld \
  && sed -i s@^basedir.*@basedir=\/usr\/local\/mysql@ /etc/init.d/mysqld \
  && sed -i s@^datadir=@datadir=\/opt\/data@ /etc/init.d/mysqld

#复制mysql配置文件
COPY my.cnf /etc/my.cnf

#导出3306端口(这里是MYSQL使用的端口),以后外部使用可以访问它
EXPOSE 3306

#定义默认的启动命令,这里使用一个脚本来启动MySQL
ADD run.sh /root/run.sh
RUN chmod u+x /root/run.sh
#CMD ["/bin/bash","/root/run.sh"]
# 容器启动后执行以下命令,启动mysql;
CMD ["/usr/local/mysql/bin/mysqld_safe"]

配置my.cnf文件

# cat my.cnf
[mysqld]
basedir = /usr/local/mysql
datadir = /opt/data
socket = /tmp/mysql.sock
port = 3306
pid-file = /opt/data/mysql.pid
user = mysql
skip-name-resolve

编写启动mysql脚本,但是没有生效,还没有处理掉这个问题

# cat run.sh 
#!/bin/bash
#/usr/local/mysql/bin/mysqld --initialize --user=mysql --datadir=/opt/data/ &> /usr/local/mysql/initialize_mysql.txt && \
mkdir /tmp/testmysqlstart
/usr/local/mysql/bin/mysqld_safe  --user=mysql --datadir=/opt/data --defaults-file=/etc/my.cnf
#/etc/init.d/mysqld start
#/etc/init.d/mysqld restart

构建镜像

# docker build -t 192.168.137.60/lnmp/mysql:5.7 .
Sending build context to Docker daemon  3.262GB
Step 1/16 : FROM 192.168.137.60/devel/centos:7.6
7.6: Pulling from devel/centos
Digest: sha256:a36b9e68613d07eec4ef553da84d0012a5ca5ae4a830cf825bb68b929475c869
Status: Downloaded newer image for 192.168.137.60/devel/centos:7.6
 ---> 67fa590cfc1c
Step 2/16 : MAINTAINER k8s-mysql
 ---> Using cache
 ---> fc7d7dacba01
Step 3/16 : RUN yum install -y libaio* numactl.x86_64   && groupadd -r -g 306 mysql && useradd -M -s /sbin/nologin -g 306 -u 306 mysql
 ---> Using cache
 ---> 414083fb1a57
Step 4/16 : ADD mysql-5.7.22-linux-glibc2.12-x86_64.tar.gz /usr/local/
 ---> Using cache
 ---> 0fd518bd94b6
Step 5/16 : WORKDIR /usr/local/
 ---> Using cache
 ---> 9dbe30e1e8fb
Step 6/16 : RUN ln -sv mysql-5.7.22-linux-glibc2.12-x86_64 /usr/local/mysql && chown -R mysql.mysql /usr/local/mysql && cd mysql-5.7.22-linux-glibc2.12-x86_64
 ---> Using cache
 ---> 669539a4e3fb
Step 7/16 : ENV PATH /usr/local/mysql/bin:$PATH
 ---> Using cache
 ---> 07a3a98c9d72
Step 8/16 : ENV LC_ALL en_US.UTF-8
 ---> Using cache
 ---> c04bfdc9264a
Step 9/16 : RUN ln -sv /usr/local/mysql/include/ /usr/local/include/mysql
 ---> Using cache
 ---> c9c4a0c47894
Step 10/16 : RUN mkdir -p /opt/data && chown -R mysql.mysql /opt/data/   && /usr/local/mysql/bin/mysqld --initialize --user=mysql --datadir=/opt/data/
 ---> Running in d2fe7d86023c
2019-11-12T01:30:51.010815Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
2019-11-12T01:30:51.509864Z 0 [Warning] InnoDB: New log files created, LSN=45790
2019-11-12T01:30:51.672011Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
2019-11-12T01:30:51.749322Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: 101f2d77-04ec-11ea-9461-02420a1e1407.
2019-11-12T01:30:51.750285Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
2019-11-12T01:30:51.750902Z 1 [Note] A temporary password is generated for root@localhost: 4RylCwS(Oyr4
Removing intermediate container d2fe7d86023c
 ---> b97b7859fc07
Step 11/16 : RUN cp -a /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld   && sed -i s@^basedir.*@basedir=\/usr\/local\/mysql@ /etc/init.d/mysqld   && sed -i s@^datadir=@datadir=\/opt\/data@ /etc/init.d/mysqld
 ---> Running in 6899e0c96aae
Removing intermediate container 6899e0c96aae
 ---> 58ab9ed1f534
Step 12/16 : COPY my.cnf /etc/my.cnf
 ---> 318bca90fccc
Step 13/16 : EXPOSE 3306
 ---> Running in b2ffaf33a6f8
Removing intermediate container b2ffaf33a6f8
 ---> 75695d2ca367
Step 14/16 : ADD run.sh /root/run.sh
 ---> 59c8ae394a63
Step 15/16 : RUN chmod u+x /root/run.sh
 ---> Running in a0455a773e4d
Removing intermediate container a0455a773e4d
 ---> 4736d7cb990a
Step 16/16 : CMD ["/bin/bash","/root/run.sh"]
 ---> Running in d100b21acbe2
Removing intermediate container d100b21acbe2
 ---> 45a2c9bb4ad9
Successfully built 45a2c9bb4ad9
Successfully tagged 192.168.137.60/lnmp/mysql:5.7


# docker images
REPOSITORY                                            TAG                 IMAGE ID            CREATED              SIZE
192.168.137.60/lnmp/mysql                             5.7                 b76644f09330        About a minute ago   3.03GB

运行容器

docker run --rm --name mysql5.7 -itd 192.168.137.60/lnmp/mysql:5.7 bash

进入容器

# docker exec -it 071400a7bae4 bash
# /etc/init.d/mysqld start      # 启动mysql,这里不能随容器启动,未解决、哭泣!
Starting MySQL. SUCCESS!


[root@071400a7bae4 local]# ps uax|grep mysql
root        798  0.0  0.0  15248  1748 pts/2    S    07:39   0:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/opt/data --pid-file=/opt/data/mysql.pid
mysql       978 10.3  9.2 1118068 170648 pts/2  Sl   07:39   0:00 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/opt/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=071400a7bae4.err --pid-file=/opt/data/mysql.pid --socket=/tmp/mysql.sock --port=3306

[root@071400a7bae4 local]# mysql    # 进入mysql,需要输入初始化时的密码
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
[root@071400a7bae4 local]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.22

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;      # 进入时,必须修改密码才能进行操作。
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
mysql> set password = password('666666');   # 修改密码
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> flush privileges;    # 刷新权限
Query OK, 0 rows affected (0.00 sec)

mysql> show databases;  # 在进行查看
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

4.构建lnmp镜像

1、下载基础镜像并创建工作目录

[root@Docker ~]# mkdir lnmp
[root@Docker ~]# cd lnmp/

2、在工作目录里,配置nginx的yum源

yum源地址:http://nginx.org/en/linux_packages.html

[root@Docker ~/lnmp]# vim nginx.repo

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key

3、创建dockerfile

FROM lemonbar/centos6-ssh
MAINTAINER This is TEST LNMP
#配置nginx的yum源
COPY nginx.repo /etc/yum.repos.d/nginx.repo
#安装nginx
RUN yum repolist && yum -y install nginx
#启动nginx
RUN /etc/init.d/nginx start
#修改nginx的配置文件,使得支持php
RUN sed -i '1,$s@user  apache;@user  nginx;@g' /etc/nginx/nginx.conf
RUN sed -i '10s@index  index.html index.htm;@index  index.php index.htm;@g' /etc/nginx/conf.d/default.conf
RUN sed -i '30,36s@#@@' /etc/nginx/conf.d/default.conf
RUN sed -i '31s@html@/usr/share/nginx/html/@' /etc/nginx/conf.d/default.conf
#RUN sed -i '34s@^[[:space:]]@#@' /etc/nginx/conf.d/default.conf
RUN sed -i '/fastcgi_param/s/scripts/usr\/share\/nginx\/html/' /etc/nginx/conf.d/default.conf
#RUN sed -i '35s@fastcgi_params@fastcgi.conf@' /etc/nginx/conf.d/default.conf

#安装php、mysql
RUN rpm --rebuilddb &&  yum install -y mysql mysql-devel mysql-server php php-mysql php-fpm
#修改php-fpm配置文件允许nginx访问
RUN sed -i '1,$s@^user = apache@user = nginx@g' /etc/php-fpm.d/www.conf
RUN sed -i '1,$s@^group = apache@group = nginx@g' /etc/php-fpm.d/www.conf

#mysql进行初始化,并授权
RUN /etc/init.d/mysqld start &&\
  mysql -e "grant all privileges on *.* to 'root'@'%' identified by '666666';" &&\
  mysql -e "grant all privileges on *.* to 'root'@'localhost' identified by '666666';"
#添加测试页面
ADD index.php /usr/share/nginx/html/index.php
#开启端口
EXPOSE 80
EXPOSE 9000
EXPOSE 3306
EXPOSE 443
#复制脚本,设置权限,启动容器时启动该脚本
ADD run.sh /run.sh
RUN chmod 775 /run.sh
CMD ["/run.sh"]

4、编写执行脚本内容

#!/bin/bash
/etc/init.d/nginx start && /etc/init.d/php-fpm start && /etc/init.d/mysqld_safe

5、创建测试的PHP页面

<?php
        $link_id=mysql_connect('localhost','root','666666') or mysql_error();
        if($link_id) {
                echo "php link mysql successful";
        }else{
                echo mysql_error();
        }
phpinfo();
?>

lnmp目录下。所有的文件

[root@Docker ~/lnmp]# ls
Dockerfile  index.php  nginx.repo  run.sh

6、生成镜像

[root@Docker ~/lnmp]# docker build -t centos6:lnmp .
Sending build context to Docker daemon  6.656kB
Step 1/22 : FROM lemonbar/centos6-ssh
 ---> efd998bd6817
Step 2/22 : MAINTAINER This is TEST LNMP
 ---> Running in c1f5edef7e74
Removing intermediate container c1f5edef7e74
 ---> 35745ec2152f
Removing intermediate container d7b948a6cdba
 ---> 1de1b1fbf16d
Successfully built 1de1b1fbf16d
Successfully tagged centos6:lnmp

image-20221114104626590

7、启动容器,并验证

[root@Docker ~/lnmp]# docker run -d -it --name centos6-lnmp -P centos6:lnmp  /bin/bash

[root@Docker ~/lnmp]# docker ps -a
CONTAINER ID        IMAGE                                      COMMAND                  CREATED             STATUS                      PORTS                                                                                                                    NAMES
b20f5c2c5afe        centos6:lnmp                               "/bin/bash"              23 seconds ago      Up 22 seconds               0.0.0.0:32779->22/tcp, 0.0.0.0:32778->80/tcp, 0.0.0.0:32777->443/tcp, 0.0.0.0:32776->3306/tcp, 0.0.0.0:32775->9000/tcp   centos6-lnmp

[root@Docker ~/lnmp]# docker port centos6-lnmp
22/tcp -> 0.0.0.0:32779
3306/tcp -> 0.0.0.0:32776
443/tcp -> 0.0.0.0:32777
80/tcp -> 0.0.0.0:32778
9000/tcp -> 0.0.0.0:32775

使用nginx测试PHP页面,及其与数据库的连接的情况:

构建tomcat镜像

1.创建工作目录

[root@localhost ~]# mkdir -pv /docker/tomcat7/
[root@localhost ~]# cd /docker/tomcat7/

2.创建Dockerfile文件

[root@localhost tomcat]# vim Dockerfile
#使用的基础镜像
FROM centos:7.2.1511
#维护该镜像用户信息
MAINTAINER the CentOS Project<tomcat-apache>
#下载JDK并部署
WORKDIR /opt
RUN yum install wget net-tools -y \
  && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
  && wget https://download.java.net/openjdk/jdk8u40/ri/openjdk-8u40-b25-linux-x64-10_feb_2015.tar.gz \
  && tar xf openjdk-8u40-b25-linux-x64-10_feb_2015.tar.gz -C /usr/local/ \
  && ln -s /usr/local/java-se-8u40-ri/ /usr/local/java8

#下载tomcat并部署
WORKDIR /opt/
RUN wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-7/v7.0.96/bin/apache-tomcat-7.0.96.tar.gz \
  && tar xf apache-tomcat-7.0.96.tar.gz \
  && mv apache-tomcat-7.0.96  /usr/local/tomcat7
#配置环境变量
ENV JAVA_HOME /usr/local/java8
ENV CLASSPATH $JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
ENV PATH $JAVA_HOME/bin:$PATH
ENV PATH $PATH:/usr/local/tomcat7/bin

#复制服务启动脚本并设置权限
ADD run_tomcat7.sh /etc/init.d/tomcat
#加执行权限
RUN chmod 755 /etc/init.d/tomcat

#暴露8080端口
EXPOSE 8080
#启动时运行tomcat# Define default command
ENTRYPOINT /etc/init.d/tomcat start && tail -f /usr/local/tomcat7/logs/catalina.out

启动脚本

# vim run_tomcat7.sh
#!/bin/bash
export JAVA_HOME=/usr/local/java8
export TOMCAT_HOME=/usr/local/tomcat7
case $1 in
start)
  sh $TOMCAT_HOME/bin/startup.sh
;;
stop)
  sh $TOMCAT_HOME/bin/shutdown.sh
;;
restart)
  sh $TOMCAT_HOME/bin/shutdown.sh
  sh $TOMCAT_HOME/bin/startup.sh
;;
esac
exit 0

3.用Dockerfile文件生成镜像

# docker build -t tomcat:7 .

image-20221114104911980

# docker image ls

image-20221114104915752

4.运行容器并验证

# docker run --rm -itd -P --name tomcat7 tomcat:7
# docker container ls

浏览器访问本机32768端口:

image-20221114105019023

构建lnmp镜像

Nginx:编译安装1.14.2

php:编译安装7.2.9

mysql:由于内存不足原因,编译安装失败,该由yum安装,由于CentOS7只能安装mariadb,启动只能通过systemctl,但是CentOS7基础镜像无法使用systemctl,因此改用CentOS6。

1.创建工作目录

[root@localhost ~]# mkdir -pv /tmp/lnmp/
[root@localhost ~]# cd /tmp/lnmp/

2.创建Dockerfile文件

[root@localhost lnmp]# vim Dockerfile
添加:
FROM guyton/centos6
RUN yum -y install wget \
    && wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo \
    && yum -y install mysql mysql-server \
    && mkdir -pv /{complie,app} \
    && /etc/init.d/mysqld start \
    && mysqladmin -u root password "123456" \
    && mysql -e "grant all on *.* to 'root'@'%' identified by '123456'" -u root -p123456

COPY functions /etc/init.d/functions
COPY nginx-1.14.2 /complie/nginx-1.14.2
COPY php-7.2.9 /complie/php-7.2.9
COPY install_nginx /complie/nginx-1.14.2/install_nginx
COPY install_php /complie/php-7.2.9/install_php
COPY nginx /etc/init.d/nginx

WORKDIR /complie/nginx-1.14.2
RUN cat install_nginx | bash && make && make install
RUN sed -i '62a }' /etc/nginx/nginx.conf \
    && sed -i '62a include        fastcgi.conf;' /etc/nginx/nginx.conf \
    && sed -i '62a fastcgi_index index.php;' /etc/nginx/nginx.conf \
    && sed -i '62a fastcgi_pass 127.0.0.1:9000;' /etc/nginx/nginx.conf \
    && sed -i '62a root html;' /etc/nginx/nginx.conf \
    && sed -i '62a location ~* .php$ {' /etc/nginx/nginx.conf \
    && chmod a+x /etc/init.d/nginx \
    && /etc/init.d/nginx start

WORKDIR /complie/php-7.2.9
RUN cat install_php | bash && make && make install
RUN cp /app/php.7.2.9/etc/php-fpm.conf.default /app/php.7.2.9/etc/php-fpm.conf \
    && cp /app/php.7.2.9/etc/php-fpm.d/www.conf.default /app/php.7.2.9/etc/php-fpm.d/www.conf \
    && sed -i 's@^;pid@pid@g' /app/php.7.2.9/etc/php-fpm.conf \
    && sed -i '$a user = nginx' /app/php.7.2.9/etc/php-fpm.conf \
    && sed -i '$a group = nginx' /app/php.7.2.9/etc/php-fpm.conf \
    && sed -i '$a pm.max_children = 50' /app/php.7.2.9/etc/php-fpm.conf \
    && sed -i '$a pm.start_servers = 20' /app/php.7.2.9/etc/php-fpm.conf \
    && sed -i '$a pm.min_spare_servers = 5' /app/php.7.2.9/etc/php-fpm.conf \
    && sed -i '$a pm.max_spare_servers = 20' /app/php.7.2.9/etc/php-fpm.conf \
    && ln -sv /app/php.7.2.9/sbin/* /usr/local/sbin/ \
    && ln -sv /app/php.7.2.9/bin/* /usr/local/bin/ \
    && php-fpm

COPY index.php /app/nginx.1.14.2/html/index.php
EXPOSE 80
EXPOSE 443
EXPOSE 9000
EXPOSE 3306

3.解压php和nginx源码包,以及准备相关文件

[root@localhost lnmp]# tar -zxf nginx-1.14.2.tar.gz
[root@localhost lnmp]# tar -zxf php-7.2.9.tar.gz
[root@localhost lnmp]# cp /etc/init.d/functions ./  <==nginx启动脚本调用的文件
[root@localhost lnmp]# vim install_nginx        <==编译安装nginx的文件

image-20221114105200837

[root@localhost lnmp]# vim install_php      <==编译安装php的文件

image-20221114105231702

[root@localhost lnmp]# vim nginx        <==nginx启动脚本
[root@localhost lnmp]# vim index.php    <==php测试页面

image-20221114105252687

[root@localhost lnmp]# ls

image-20221114105309294

4.生成镜像

[root@localhost lnmp]# docker build -t lnmp:centos6 .

image-20221114105330648

5.启动容器并验证

[root@localhost lnmp]# docker run -it -P --name lnmp lnmp:centos6 bash
[root@bea6c1e9017a app]# /etc/init.d/mysqld start
[root@bea6c1e9017a app]# php-fpm
[root@bea6c1e9017a app]# /etc/init.d/nginx start
[root@localhost lnmp]# docker container ls

image-20221114105358337

image-20221114105402710

构建lnmp镜像

参考:https://github.com/yanyuzm/docker-for-lnmp/blob/master/install-docker.sh

#!/bin/bash
#author:caomuzhong
#date:2018-9-26
#一键部署lnmp架构容器化

echo -e "\033[31;32m*****************************************************\033[0m"
echo -e "\033[34m================一键部署lnmp容器化===================\033[0m"
echo -e "\033[31;32m*****************************************************\033[0m"
echo
echo "版本:nginx:1.14.0  mariadb:5.5.60  php:7.2.7"
echo
echo -e "\033[31;32m===========部署前的准备=========\033[0m"
echo -e "\033[34m-----------创建nginx用户和组---------\033[0m"
id nginx &> /dev/null
[ $? -ne 0 ] && groupadd -g 1080 nginx  && useradd -g 1080 -u 1080 -M -s /sbin/nologin nginx
echo -e "\033[34m-----------创建mysql用户和组---------\033[0m"
id mysql &> /dev/null
[ $? -ne 0 ] && groupadd -g 3306 mysql  && useradd -g 3306 -u 3306 -M -s /sbin/nologin mysql
echo -e "\033[34m-----------创建网站目录和数据库data目录---------\033[0m"
[ ! -d /testweb ] && mkdir /testweb && chown -R nginx.nginx /testweb
[ ! -d /testdb ] && mkdir /testdb/{3306,3307}/data -p && chown -R mysql.mysql /testdb
echo
echo -e "\033[34-----------创建nginx、mariadb、php日志存放目录---------\033[0m"
[ ! -d /var/log/nginx/ ] && mkdir /var/log/nginx/ && chown nginx.nginx /var/log/nginx/
echo -e "\033[31;32m===========安装docker=========\033[0m"
echo -e "\033[34m-----------下载repo文件---------\033[0m"
[ ! -f /etc/yum.repos.d/docker-ce.repo ] && curl -o /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install docker-ce -y
echo -e "\033[34m-----------设置加速器---------\033[0m"
[ ! -d /etc/docker ] && mkdir /etc/docker
cat >/etc/docker/daemon.json<<EOF
{
 "registry-mirrors": ["https://1xesnmzk.mirror.aliyuncs.com","http://hub-mirror.c.163.com","https://registry.docker-cn.com"]
}
EOF
systemctl start docker && echo -e "\033[31;32m-----------docker启动成功---------\033[0m"
echo -e "\033[31;32m===================================\033[0m"
echo -e "\033[34m    创建nginx、mariadb、php镜像    \033[0m"
echo -e "\033[31;32m===================================\033[0m"
[ ! -d nginx ] && mkdir nginx 
[ ! -d mariadb ] && mkdir mariadb
[ ! -d php ] && mkdir  php
cat>nginx/nginx.conf<<EOF
user  nginx nginx;
worker_processes  1;
worker_rlimit_nofile 65535;
error_log  /var/log/nginx/error.log notice;
events {
    use epoll;
    worker_connections  65535;
}
http {
    include mime.types;
    default_type application/octet-stream;
    server_names_hash_bucket_size 3526;
    server_names_hash_max_size 4096;
    log_format combined_realip '\$remote_addr \$http_x_forwarded_for [\$time_local]'
    ' \$host "\$request_uri" \$status'
    ' "\$http_referer" "\$http_user_agent"';
    sendfile on;
    tcp_nopush on;
    keepalive_timeout 30;
    client_header_timeout 3m;
    client_body_timeout 3m;
    send_timeout 3m;
    connection_pool_size 256;
    client_header_buffer_size 1k;
    large_client_header_buffers 8 4k;
    request_pool_size 4k;
    output_buffers 4 32k;
    postpone_output 1460;
    client_max_body_size 10m;
    client_body_buffer_size 256k;
    client_body_temp_path /usr/local/nginx/client_body_temp;
    proxy_temp_path /usr/local/nginx/proxy_temp;
    fastcgi_temp_path /usr/local/nginx/fastcgi_temp;
    fastcgi_intercept_errors on;
    tcp_nodelay on;
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 8k;
    gzip_comp_level 5;
    gzip_http_version 1.1;
    gzip_types text/plain application/x-javascript text/css text/htm 
    application/xml;
    include /usr/local/nginx/conf.d/*.conf;
}
EOF
cat>nginx/server.conf<<EOF
server {
listen       80;
server_name  localhost;
location / {
    root   /usr/local/nginx/html;
    index  index.php index.html index.htm;
}
location ~ .php\$ {
    root           /usr/local/nginx/html;
    fastcgi_pass   php:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME   /usr/local/nginx/html\$fastcgi_script_name;
    include        fastcgi_params;
}
}
EOF
echo -e "\033[34m================创建nginx Dockerfile===========\033[0m"
cat>nginx/Dockerfile<<EOF
FROM centos
#File Author / Maintainer
MAINTAINER caomuzhong www.logmm.com
#RUN yum install -y gcc gcc-c++ pcre-devel openssl-devel libxml2-devel openssl libcurl-devel make zlib zlib-devel gd-devel
#ADD http://nginx.org/download/nginx-1.14.0.tar.gz .
#Install Nginx
RUN  groupadd -g 1080 nginx  && useradd -g 1080 -u 1080 -M -s /sbin/nologin nginx \
   &&  mkdir -p /var/log/nginx  \
   &&  chown nginx.nginx /var/log/nginx \
   && yum install -y gcc gcc-c++ pcre-devel make openssl-devel libxml2-devel  libcurl-devel  zlib-devel gd-devel \
   && curl -O http://nginx.org/download/nginx-1.14.0.tar.gz \
   && tar xf nginx-1.14.0.tar.gz && rm -f nginx-1.14.0.tar.gz \
   &&  cd nginx-1.14.0 && ./configure --prefix=/usr/local/nginx \
       --http-log-path=/var/log/nginx/access.log \
       --error-log-path=/var/log/nginx/error.log \
       --user=nginx \
       --group=nginx \
       --with-http_ssl_module \
       --with-http_realip_module \
       --with-http_flv_module \
       --with-http_mp4_module \
       --with-http_gunzip_module \
       --with-http_gzip_static_module \
       --with-http_image_filter_module \
       --with-http_stub_status_module &&  make && make install && yum clean all \
  && rm -f /usr/local/nginx/conf/nginx.conf && mkdir /usr/local/nginx/conf.d/ && cd / && rm -rf /nginx-1.14.0
COPY nginx/nginx.conf  /usr/local/nginx/conf/nginx.conf
COPY nginx/server.conf /usr/local/nginx/conf.d/
#Expose ports
EXPOSE 80 443
#Front desk start nginx
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;"] 
EOF
echo -e "\033[34m================创建mariadb Dockerfile===========\033[0m"
#从库配置文件,启动容器时挂载到容器的/etc目录中
cat>/testdb/3307/my.cnf<<EOF
# Example MariaDB config file for large systems.
#
# This is for a large system with memory = 512M where the system runs mainly
# MariaDB.
#
# MariaDB programs look for option files in a set of
# locations which depend on the deployment platform.
# You can copy this option file to one of those
# locations. For information about these locations, do:
# 'my_print_defaults --help' and see what is printed under
# Default options are read from the following files in the given order:
# More information at: http://dev.mysql.com/doc/mysql/en/option-files.html
#
# In this file, you can use all long options that a program supports.
# If you want to know which options a program supports, run the program
# with the "--help" option.
# The following options will be passed to all MariaDB clients
[client]
#password       = your_password
port            = 3306
socket          = /tmp/mysql.sock
# Here follows entries for some specific programs
# The MariaDB server
[mysqld]
port            = 3306
socket          = /tmp/mysql.sock
skip-external-locking
key_buffer_size = 256M
max_allowed_packet = 1M
table_open_cache = 256
sort_buffer_size = 1M
read_buffer_size = 1M
read_rnd_buffer_size = 4M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size= 16M
# Try number of CPU's*2 for thread_concurrency
thread_concurrency = 8
datadir = /testdb/3306/data/
innodb_file_per = on
skip_name_resolve = on
#skip-grant-tables
slow_query_log = ON
slow_query_log_file = /testdb/3306/slow.log
long_query_time = 1
# Point the following paths to different dedicated disks
#tmpdir         = /tmp/
# Don't listen on a TCP/IP port at all. This can be a security enhancement,
# if all processes that need to connect to mysqld run on the same host.
# All interaction with mysqld must be made via Unix sockets or named pipes.
# Note that using this option without enabling named pipes on Windows
# (via the "enable-named-pipe" option) will render mysqld useless!
# 
#skip-networking
# Replication Master Server (default)
# binary logging is required for replication
log-bin=mysql-bin
# binary logging format - mixed recommended
binlog_format=mixed
# required unique id between 1 and 2^32 - 1
# defaults to 1 if master-host is not set
# but will not function as a master if omitted
server-id       = 2
# Replication Slave (comment out master section to use this)
#
# To configure this host as a replication slave, you can choose between
# two methods :
#
# 1) Use the CHANGE MASTER TO command (fully described in our manual) -
#    the syntax is:
#
#    CHANGE MASTER TO MASTER_HOST=<host>, MASTER_PORT=<port>,
#    MASTER_USER=<user>, MASTER_PASSWORD=<password> ;
#
#    where you replace <host>, <user>, <password> by quoted strings and
#    <port> by the master's port number (3306 by default).
#
#    Example:
#
#    CHANGE MASTER TO MASTER_HOST='125.564.12.1', MASTER_PORT=3306,
#    MASTER_USER='joe', MASTER_PASSWORD='secret';
#
# OR
#
# 2) Set the variables below. However, in case you choose this method, then
#    start replication for the first time (even unsuccessfully, for example
#    if you mistyped the password in master-password and the slave fails to
#    connect), the slave will create a master.info file, and any later
#    change in this file to the variables' values below will be ignored and
#    overridden by the content of the master.info file, unless you shutdown
#    the slave server, delete master.info and restart the slaver server.
#    For that reason, you may want to leave the lines below untouched
#    (commented) and instead use CHANGE MASTER TO (see above)
#
# required unique id between 2 and 2^32 - 1
# (and different from the master)
# defaults to 2 if master-host is set
# but will not function as a slave if omitted
#server-id       = 2
#
# The replication master for this slave - required
#master-host     =   <hostname>
#
# The username the slave will use for authentication when connecting
# to the master - required
#master-user     =   <username>
#
# The password the slave will authenticate with when connecting to
# the master - required
#master-password =   <password>
#
# The port the master is listening on.
# optional - defaults to 3306
#master-port     =  <port>
#
# binary logging - not required for slaves, but recommended
#log-bin=mysql-bin
# Uncomment the following if you are using InnoDB tables
#innodb_data_home_dir = /usr/local/mysql/data
#innodb_data_file_path = ibdata1:10M:autoextend
#innodb_log_group_home_dir = /usr/local/mysql/data
# You can set .._buffer_pool_size up to 50 - 80 %
# of RAM but beware of setting memory usage too high
#innodb_buffer_pool_size = 256M
#innodb_additional_mem_pool_size = 20M
# Set .._log_file_size to 25 % of buffer pool size
#innodb_log_file_size = 64M
#innodb_log_buffer_size = 8M
#innodb_flush_log_at_trx_commit = 1
#innodb_lock_wait_timeout = 50
[mysqldump]
quick
max_allowed_packet = 16M
[mysql]
no-auto-rehash
# Remove the next comment character if you are not familiar with SQL
#safe-updates
[myisamchk]
key_buffer_size = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M
[mysqlhotcopy]
interactive-timeout
EOF

cat>mariadb/start.sh<<EOF
#!/bin/bash
if [ ! -f /testdb/3306/data/ibdata1 ]; then
       /usr/local/mysql/scripts/mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/testdb/3306/data/
        /etc/rc.d/init.d/mariadb start
        /usr/local/mysql/bin/mysql -e "grant all on *.* to 'root'@'%' identified by '123456' with grant option;"
        /usr/local/mysql/bin/mysql -e "flush privileges;"     
fi
/etc/rc.d/init.d/mariadb restart
tail -f /etc/passwd
EOF

cat>mariadb/Dockerfile<<EOF
###  Set the base image to CentOS
FROM centos
#File Author / Maintainer
MAINTAINER caomuzhong www.logmm.com
#Download mariadb5.5.60 package
#ADD http://mirrors.tuna.tsinghua.edu.cn/mariadb//mariadb-5.5.60/bintar-linux-x86_64/mariadb-5.5.60-linux-x86_64.tar.gz .
#http://mirrors.neusoft.edu.cn/mariadb//mariadb-5.5.60/bintar-linux-x86_64/mariadb-5.5.60-linux-x86_64.tar.gz
#Unzip
RUN groupadd -g 3306 mysql && useradd -g 3306 -u 3306 -M -s /sbin/nologin mysql \
  && mkdir /testdb/3306/data/ -p \
  && chown -R mysql.mysql /testdb/ \
  && curl -O http://mirrors.tuna.tsinghua.edu.cn/mariadb//mariadb-5.5.60/bintar-linux-x86_64/mariadb-5.5.60-linux-x86_64.tar.gz  \
  &&tar xf mariadb-5.5.60-linux-x86_64.tar.gz -C /usr/local/ \
  && rm -f mariadb-5.5.60-linux-x86_64.tar.gz \
  && cd /usr/local/ && ln -sv mariadb-5.5.60-linux-x86_64/ mysql \
  && cd mysql/ && chown -R mysql.mysql ./* \
  && chown -R mysql.mysql /usr/local/mysql  \
  &&  cd /usr/local/mysql && /usr/bin/cp support-files/my-large.cnf /etc/my.cnf \
  && sed -i '/thread_concurrency = 8/adatadir = /testdb/3306/data/\ninnodb_file_per = on\nskip_name_resolve = on' /etc/my.cnf \
  && cp support-files/mysql.server /etc/rc.d/init.d/mariadb \
  && chmod +x /etc/rc.d/init.d/mariadb \
  && touch /var/log/mariadb.log && chown mysql.mysql /var/log/mariadb.log \
  && chkconfig --add mariadb
#expose
EXPOSE 3306
ADD mariadb/start.sh  /opt/startup.sh
#RUN chmod +x /opt/startup.sh
CMD ["/bin/bash","/opt/startup.sh"]
EOF
echo -e "\033[34m================创建php Dockerfile===========\033[0m"
cat>php/Dockerfile<<EOF
###  Set the base image to CentOS
FROM centos
#File Author / Maintainer
MAINTAINER caomuzhong www.logmm.com
#Install necessary tools
#RUN yum install -y epel-release bzip2-devel openssl-devel gnutls-devel gcc gcc-c++  libmcrypt-devel libmcrypt ncurses-devel bison-devel libaio-devel openldap  openldap-devel autoconf bison libxml2-devel libcurl-devel libevent libevent-devel gd-devel  expat-devel
#ADD http://iweb.dl.sourceforge.net/project/mcrypt/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gz .
#RUN tar xf libmcrypt-2.5.8.tar.gz && cd libmcrypt-2.5.8 && ./configure && make && make install
#Install PHP7.2.x and Create dir the same for nginx's root dir
#ADD http://cn.php.net/distributions/php-7.2.7.tar.gz .
#ADD http://nz2.php.net/distributions/php-7.2.10.tar.gz .
#ADD http://hk2.php.net/distributions/php-7.2.10.tar.gz .
#ADD http://uk1.php.net/distributions/php-7.2.10.tar.gz .
RUN curl -O http://hk2.php.net/distributions/php-7.2.10.tar.gz  \
    && tar xf php-7.2.10.tar.gz && rm -f php-7.2.10.tar.gz \
    && groupadd -g 3306 mysql && useradd -g 3306 -u 3306 -s /sbin/nologin mysql \
    && groupadd -g 1080 nginx && useradd  -g 1080 -u 1080 -M -s /sbin/nologin nginx \
    && mkdir -p /usr/local/nginx/html && chown -R nginx.nginx /usr/local/nginx/ \
    && yum install -y epel-release bzip2-devel openssl-devel gnutls-devel gcc gcc-c++  libmcrypt-devel libmcrypt ncurses-devel bison-devel libaio-devel openldap  openldap-devel autoconf bison libxml2-devel libcurl-devel libevent libevent-devel gd-devel  expat-devel \
    && cd php-7.2.10 \
    && ./configure  --prefix=/usr/local/php7 \
        --with-config-file-path=/usr/local/php7/etc/ \
        --with-config-file-scan-dir=/usr/local/php7/etc/conf.d \
        --with-mysqli=mysqlnd  \
        --with-pdo-mysql=mysqlnd \
        --with-iconv-dir \
        --with-freetype-dir \
        --with-jpeg-dir \
        --with-png-dir \
        --with-zlib \
        --with-bz2 \
        --with-libxml-dir \
        --with-curl \
        --with-gd \
        --with-openssl \
        --with-mhash  \
        --with-xmlrpc \
        --with-pdo-mysql \
        --with-libmbfl \
        --with-onig \
        --with-pear \
        --enable-xml \
        --enable-bcmath \
        --enable-shmop \
        --enable-sysvsem \
        --enable-inline-optimization \
        --enable-mbregex \
        --enable-fpm \
        --enable-mbstring \
        --enable-pcntl \
        --enable-sockets \
        --enable-zip \
        --enable-soap \
        --enable-opcache \
        --enable-pdo \
        --enable-mysqlnd-compression-support \
        --enable-maintainer-zts  \
        --enable-session \
        --with-fpm-user=nginx \
        --with-fpm-group=nginx  && make -j 2  && make -j 2 install && yum clean all \
    && mkdir /usr/local/php7/etc/conf.d && cp php.ini-production  /usr/local/php7/etc/php.ini \
    && cp sapi/fpm/init.d.php-fpm  /etc/rc.d/init.d/php-fpm && chmod +x /etc/rc.d/init.d/php-fpm && chkconfig --add php-fpm \
    && sed -i '/post_max_size/s/8/16/g;/max_execution_time/s/30/300/g;/max_input_time/s/60/300/g;s#\;date.timezone.*#date.timezone \= Asia/Shanghai#g' /usr/local/php7/etc/php.ini \
    && cp /usr/local/php7/etc/php-fpm.conf.default /usr/local/php7/etc/php-fpm.conf \
    && cp /usr/local/php7/etc/php-fpm.d/www.conf.default /usr/local/php7/etc/php-fpm.d/www.conf \
    && sed -i -e 's/listen = 127.0.0.1:9000/listen = 0.0.0.0:9000/' /usr/local/php7/etc/php-fpm.d/www.conf && cd / && rm -rf /php-7.2.10
#EXPOSE
EXPOSE 9000
#Start php-fpm
ENTRYPOINT ["/usr/local/php7/sbin/php-fpm", "-F", "-c", "/usr/local/php7/etc/php.ini"]
EOF
echo -e "\033[31;32m====================================================================\033[0m"
echo -e "\033[31;32m     构建镜像的命令(建议打开多个终端同时执行以下命令,节省时间)    \033[0m"
echo -e "\033[34m构建nginx镜像:docker build -t centos_nginx -f nginx/Dockerfile .      \033[0m"
echo -e "\033[34m构建mariadb镜像:docker build -t centos_mariadb -f mariadb/Dockerfile . \033[0m"
echo -e "\033[34m构建php镜像:docker build -t centos_php -f php/Dockerfile .             \033[0m"
echo -e "\033[31;32m=====================================================================\033[0m"

多阶段构建

image-20221115115719476

1、编写程序文件

[root@VM-16-16-centos ~/go-hello 10:57:10]# cat main.go 
package main

import (
    "fmt"
    "runtime"

    "github.com/gin-gonic/gin"
)

func main() {
    fmt.Printf("Hello, %s!\n", runtime.GOARCH)
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello, Gin")
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}

2、编写Dockerfile

[root@VM-16-16-centos ~/go-hello 10:57:20]# cat Dockerfile 
FROM golang:alpine AS builder
ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOPROXY=https://goproxy.cn,direct
# 这里我们采用buildx来进行交叉编译镜像,所以不需要指定GOARCH
# GOARCH=amd64 \

RUN mkdir /app
WORKDIR /app
RUN pwd \
  && go mod init test \
  && ls -lht /app 
COPY ./main.go .
RUN go mod tidy && \
    CGO_ENABLED=0 GOOS=linux go build -o hello .
#    CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o hello .   #指定cpu架构

FROM alpine
# 修改源
RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories && \
    echo "http://mirrors.aliyun.com/alpine/latest-stable/community/" >> /etc/apk/repositories
# 时区设置成当前时区,创建app工作目录
ENV TZ="Asia/Shanghai"
RUN apk add --no-cache tzdata \
    && mkdir /app
WORKDIR /app
COPY --from=builder /app/hello .
CMD ["./hello"]

这是一个多阶段构建 Dockerfile,使用 Go 编译器来构建应用,并将构建好的二进制文件拷贝到 alpine 镜像中。

# docker build -t go-hello:v1 .
Sending build context to Docker daemon  17.69MB
Step 1/14 : FROM golang:alpine AS builder
 ---> d8bf44a3f6b4
Step 2/14 : ENV GO111MODULE=on     CGO_ENABLED=0     GOOS=linux     GOPROXY=https://goproxy.cn,direct
 ---> Using cache
 ---> ab750e3814e0
Step 3/14 : RUN mkdir /app
 ---> Using cache
 ---> a4e2b9c33fb2
Step 4/14 : WORKDIR /app
 ---> Using cache
 ---> 1807dc29d697
Step 5/14 : RUN pwd   && go mod init test   && ls -lht /app
 ---> Running in 528c91fe8ac3
/app
go: creating new go.mod: module test
total 4K     
-rw-r--r--    1 root     root          21 Dec 30 03:50 go.mod
Removing intermediate container 528c91fe8ac3
 ---> 5d6c67e825cf
Step 6/14 : COPY ./main.go .
 ---> 491acec5ac4e
Step 7/14 : RUN go mod tidy &&     CGO_ENABLED=0 GOOS=linux go build -o hello .
 ---> Running in 214f60ba95c8
go: finding module for package github.com/gin-gonic/gin
go: downloading github.com/gin-gonic/gin v1.8.2
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.8.2
go: downloading github.com/gin-contrib/sse v0.1.0
go: downloading github.com/mattn/go-isatty v0.0.16
go: downloading golang.org/x/net v0.4.0
go: downloading github.com/stretchr/testify v1.8.1
go: downloading google.golang.org/protobuf v1.28.1
go: downloading github.com/go-playground/validator/v10 v10.11.1
go: downloading github.com/pelletier/go-toml/v2 v2.0.6
go: downloading github.com/ugorji/go/codec v1.2.7
go: downloading gopkg.in/yaml.v2 v2.4.0
go: downloading github.com/goccy/go-json v0.9.11
go: downloading github.com/json-iterator/go v1.1.12
go: downloading golang.org/x/sys v0.3.0
go: downloading github.com/davecgh/go-spew v1.1.1
go: downloading github.com/pmezard/go-difflib v1.0.0
go: downloading gopkg.in/yaml.v3 v3.0.1
go: downloading github.com/go-playground/universal-translator v0.18.0
go: downloading github.com/leodido/go-urn v1.2.1
go: downloading golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
go: downloading golang.org/x/text v0.5.0
go: downloading github.com/go-playground/locales v0.14.0
go: downloading github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421
go: downloading github.com/modern-go/reflect2 v1.0.2
go: downloading github.com/go-playground/assert/v2 v2.0.1
go: downloading github.com/google/go-cmp v0.5.5
go: downloading gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
go: downloading github.com/kr/pretty v0.3.0
go: downloading golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543
go: downloading github.com/kr/text v0.2.0
go: downloading github.com/rogpeppe/go-internal v1.8.0
go: downloading github.com/ugorji/go v1.2.7
Removing intermediate container 214f60ba95c8
 ---> ec9ddc4b990b
Step 8/14 : FROM alpine
 ---> 9c842ac49a39
Step 9/14 : RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories &&     echo "http://mirrors.aliyun.com/alpine/latest-stable/community/" >> /etc/apk/repositories
 ---> Using cache
 ---> 9c01bcca8d5d
Step 10/14 : ENV TZ="Asia/Shanghai"
 ---> Using cache
 ---> 588b7e9c231a
Step 11/14 : RUN apk add --no-cache tzdata     && mkdir /app
 ---> Using cache
 ---> 8e26757f0088
Step 12/14 : WORKDIR /app
 ---> Using cache
 ---> b856450366d3
Step 13/14 : COPY --from=builder /app/hello .
 ---> 1df251dec0c0
Step 14/14 : CMD ["./hello"]
 ---> Running in 1c3cb871d56d
Removing intermediate container 1c3cb871d56d
 ---> 89cb90e08dee
Successfully built 89cb90e08dee
Successfully tagged go-hello:v1

如何在运行docker容器时自动启动服务?

博客参考:https://codeday.me/bug/20170630/36124.html

Docker镜像通常设计为在前台运行单个进程或服务器,直到它们退出。标准mysql图像以这种方式工作:如果docker run mysql没有,-d您将看到所有服务器日志都打印到stdout,并且没有立即获取shell的选项。您应该将容器中的交互式shell视为调试方便,而不是通常的Docker容器运行方式。

当你docker run --rm -it /bin/bash,它运行交互式shell 而不是数据库服务器,这就是你得到"无法连接到服务器"错误的原因。作为一般规则,您应该假设像init.d脚本这样的东西在Docker中不起作用; 取决于它们可能的具体设置,但同样,它们不是Docker容器运行的常用方式。

您可以mysql在主机或其他位置安装客户端二进制文件,并使用它来与容器中运行的服务器进行交互。您不需要容器中的交互式shell来执行您在此处描述的任何内容

我有一个Dockerfile在一个容器中安装MySQL服务器,然后我开始像这样:

docker run -t -i 09d18b9a12be /bin/bash

但是MySQL服务不会自动启动,我必须手动运行(从容器内):

service mysql start

当我运行docker容器时,如何自动启动MySQL服务?

最佳答案:

问题在你的Dockerfile中

RUN service mysql restart && /tmp/setup.sh

首先,docker镜像不快照您的运行进程,您的RUN命令只是在docker构建阶段运行,您需要指定命令运行时容器开始使用CMD或ENTRYPOINT命令,如下所示

CMD mysql start

其次Docker容器需要进程(最后一个命令)保持运行,否则容器将退出。因此正常的服务mysql启动命令不能直接在Dockerfile中使用

解决方案:为了保持进程正常运行:

通常有三种方式的Dockerfile

使用service命令,并附加非end命令后,像tail -F

CMD service mysql start && tail -F /var/log/mysql/error.log

或使用前台命令来执行此操作

CMD /usr/bin/mysqld_safe

或将脚本包装到start.sh中并将其放在最后

CMD /start.sh

对于启动用户,不推荐supervisord,最好对一个容器使用单个服务。

BTW:请检查https://registry.hub.docker.com现有的mysql docker映像以供参考

Portainer一个轻量级的Docker环境管理UI

参考:https://www.kubernetes.org.cn/5883.html

github:https://github.com/portainer/portainer

Portainer是一个轻量级的docker环境管理UI,可以用来管理docker宿主机和docker swarm集群。他的轻量级,轻量到只要个不到100M的docker镜像容器就可以完整的提供服务。直接启动即可,异常方便。而且现在市面上开源的docker swarm管理平台比较少,尤其是这样轻量级的更加稀少

Portainer提供的功能完全满足大小企业的大部分需求,主要功能

  1. 提供状态显示面板:显示主机或者swarm集群上有多少镜像,容器等

  2. 应用模板快速部署:可以使用预存的模板或者自己定制的模板快速部署

  3. 事件日志显示:对任何操作有记录,并且有页面可以显示审计日志

  4. 容器控制台操作:查看容器,管理容器,查看容器占用的性能(内存,cpu等)

  5. Swarm集群管理:可以管理swarm集群,是最大的优点

  6. 登录用户管理:有完备的用户系统,权限控制

Protainer缺点

1. Portainer没有自带的高可用,但是可以利用nfs等方式作高可用(其实这种管理平台也不是必定需要高可用)

2. Portainer没有中文页面,官方没有提供中文翻译,网上大神自己翻译的中文汉化包但是只能支持特定版本(没有联系到作者,不好意思转载,可以联系我发它的博客链接)

Portainer部署及基本使用

Portainer的搭建特别简单,就是拉起一个容器这么简单,不过基础环境需要配置下 关闭selinux和firewalld

部署

下面直接安装Portainer,一条命令即可

docker run -d -p 9000:9000 \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
--name prtainer-test \
docker.io/portainer/portainer

下面这个是挂载了数据卷:

docker run -d -p 9000:9000 --restart=always \
--name portainer \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /portainer/data:/data docker.io/portainer/portainer

通过ip:9000即可访问。 http://39.107.229.89:9000

image-20221114114641011

image-20221114114646724

第一次登陆,输入密码即设置admin的默认密码,密码必须超过8位,否则不能设置成功。我这里设置的是66666666

image-20221114114706300

成功登录后的界面

Portainer连接dockerhost或swarm

Portainer连接dockerhost

连接本地

Portainer连接自己本地的宿主机即简单,只需要点击下面这个按钮即可

image-20221114114729668

image-20221114114737698

连接远程

Portainer连接其他docker的宿主机也比较简单,不过需要配置下docker,将docker的tcp连接方式打开。命令如下

vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd-current -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock

image-20221114114819954

systemctl daemon-reload
systemctl restart docker

image-20221114114903987

image-20221114114914856

Portainer连接swarm集群

Cadvisor:对docker所有容器的监控

现在好像是弃用了

image-20221114114937565

  • 运行cadvisor服务
docker run -d \
-v /:/rootfs -v /var/run:/var/run -v /sys:/sys \
-v /var/lib/docker:/var/lib/docker \
--link=influxdb:influxdb \
--name cadvisor google/cadvisor:v0.27.3 \
--storage_driver=influxdb \
--storage_driver_host=influxdb:8086 \
--storage_driver_db=test \
--storage_driver_user=root \
--storage_driver_password=root

特别注意项:

在运行上述docker时,这里有可能两个其他配置项需要添加(CentOS, RHEL需要):

  • --privileged=true

设置为true之后,容器内的root才拥有真正的root权限,可以看到host上的设备,并且可以执行mount;否者容器内的root只是外部的一个普通用户权限。由于cadvisor需要通过socket访问docker守护进程,在CentOs和RHEL系统中需要这个这个选项。

  • --volume=/cgroup:/cgroup:ro

对于CentOS和RHEL系统的某些版本(比如CentOS6),cgroup的层级挂在/cgroup目录,所以运行cadvisor时需要额外添加--volume=/cgroup:/cgroup:ro选项。

Docker容器可视化监控中心搭建

参考地址:https://mp.weixin.qq.com/s/TiprQCSZNKaZkhCRBQw_Ag

概述

一个宿主机上可以运行多个容器化应用,容器化应用运行于宿主机上,我们需要知道该容器的运行情况,包括 CPU使用率、内存占用、网络状况以及磁盘空间等等一系列信息,而且这些信息随时间变化,我们称其为时序数据,本文将实操 如何搭建一个可视化的监控中心 来收集这些承载着具体应用的容器的时序信息并可视化分析与展示!

动手了,动手了...

准备镜像

  • adviser:负责收集容器的随时间变化的数据

  • influxdb:负责存储时序数据

  • grafana:负责分析和展示时序数据

image-20221114114948751

部署Influxdb服务

可以将其视为一个数据库服务,其确实用于存储数据。之所以选用该数据库,原因正如官网所说:

Open Source Time Series DB Platform for Metrics & Events (Time Series Data)

下面我们将该服务部署起来

docker run -d -p 8086:8086 \
-v ~/influxdb:/var/lib/influxdb \
--name influxdb tutum/influxdb

进入influxdb容器内部,并执行influx命令:

docker exec -it influxdb influx

image-20221114115035376

创建数据库test和root用户用于本次试验测试

CREATE DATABASE "test"
CREATE USER "root" WITH PASSWORD 'root' WITH ALL PRIVILEGES

image-20221114115058893

部署cAdvisor服务

谷歌的cadvisor可以用于收集Docker容器的时序信息,包括容器运行过程中的资源使用情况和性能数据。

  • 运行cadvisor服务
docker run -d \
-v /:/rootfs -v /var/run:/var/run -v /sys:/sys \
-v /var/lib/docker:/var/lib/docker \
--link=influxdb:influxdb \
--name cadvisor google/cadvisor:v0.27.3 \
--storage_driver=influxdb \
--storage_driver_host=influxdb:8086 \
--storage_driver_db=test \
--storage_driver_user=root \
--storage_driver_password=root

特别注意项:

在运行上述docker时,这里有可能两个其他配置项需要添加(CentOS, RHEL需要):

  • --privileged=true

设置为true之后,容器内的root才拥有真正的root权限,可以看到host上的设备,并且可以执行mount;否者容器内的root只是外部的一个普通用户权限。由于cadvisor需要通过socket访问docker守护进程,在CentOs和RHEL系统中需要这个这个选项。

  • --volume=/cgroup:/cgroup:ro

对于CentOS和RHEL系统的某些版本(比如CentOS6),cgroup的层级挂在/cgroup目录,所以运行cadvisor时需要额外添加--volume=/cgroup:/cgroup:ro选项。

部署Grafana服务

grafana则是一款开源的时序数据分析工具,而且界面专业易用,等下等部署好了,大家就能感受到:

docker run -d -p 5000:3000 \
-v ~/grafana:/var/lib/grafana \
--link=influxdb:influxdb \
--name grafana grafana/grafana

至此3个容器都已经启动了:

image-20221114115238456

下面开始具体实验了

实战

  • 访问grafana服务

打开localhost:5000来访问grafana的web服务,此时提示你需要登录,注意用户名和密码都是 admin

image-20221114115245945

登录后可以看到grafana的主页面:

image-20221114115305892

看的很明显,在Grafana上有好几个步骤需要做,这里 InstallGrafana已经完成了,接下来我们需要:

  • Add data source

  • Create dashboard

  • ...

  • Add Data Source

点击Add data source进入

image-20221114115313898

然后主要是Setting选项卡设置

image-20221114115319593

我们需要根据实际情况来填写各项内容:

image-20221114115327553

Data source添加成功会予以提示

image-20221114115335638

数据源添加完成以后,我们需要添加仪表盘(Dashboard)

  • Add Dashboard

点击Add dashboard进入

image-20221114115342811

这里有很多类型的仪表盘供选择,我们选用最常用的Graph就好

image-20221114115350776

进入之后,点击Panel Title下拉列表,再选择Edit进行编辑即可

image-20221114115357476

在Edit里面主要的就是需要添加查询的条件,继续看下文

  • Add Query Editor

查询条件中我们可以选择要监控的指标:

image-20221114115404272

这里选一个memory usage好了,然后要监控的容器选择grafana自身好了。

当然这里不止可以监控一个指标,也不止可以监控一个容器,更多组合我们只需要在下面并列着一个一个添加query条目就好!

最后我添加了三个监控条件,分别用于监控grafana、influxdb和cadvisor三个容器的memory usage指标,并将其同时显示于图中,怎么样是不是很直观!

image-20221114115414985

这里可以摸索的设置项还有很多,比如一些坐标自定义、显示策略自定义,甚至我们还可以自定义报警策略等等

利用TICK搭建Docker容器可视化监控中心

https://mp.weixin.qq.com/s/-54IZbsX2nQIHaMZ1y6fTQ

skopeo

skopeo的github地址: https://github.com/containers/skopeo

skopeo 是一个命令行工具,可对容器镜像和容器存储进行操作。 在没有dockerd的环境下,使用 skopeo 操作镜像是非常方便的

安装

# centos
yum install skopeo

# skopeo --help

skopeo三大功能:https://lework.github.io/2020/04/13/skopeo/

1,查看仓库镜像信息

skopeo 可以在不用下载镜像的情况下,获取镜像信息

# skopeo inspect docker://docker.io/centos
  • docker:// 是使用 Docker Registry HTTP API V2 进行连接远端

  • docker.io: 远程仓库

  • centos: 镜像名称

也可以获取本地dockerd的镜像信息

# skopeo inspect docker-daemon:busybox:latest
  • docker-daemon: docker守护镜像的镜像

  • busybox:latest: 本地镜像的名称

2,拷贝镜像

在不使用 docker 的情况下从远端下载镜像

# skopeo --insecure-policy copy docker://nginx:1.17.6 docker-archive:/tmp/nginx.tar
  • --insecure-policy: 用于忽略安全策略配置文件
  • docker://nginx:1.17.6: 该命令将会直接通过 http 下载目标镜像

  • docker-archive: 存储为/tmp/nginx.tar,此文件可以直接通过 docker load 命令导入

相应的,可以将下载的文件导入到本地

# skopeo copy docker-archive:/tmp/nginx.tar docker-daemon:nginx:latest

# docker images nginx

也可以将镜像下载到指定目录

# skopeo copy docker://busybox:latest dir:/tmp/busybox

或者从指定目录导入到本地

# skopeo copy dir:/tmp/busybox docker-daemon:busybox:latest

3,删除镜像

skopeo delete docker://localhost:5000/nginx:latest

认证文件,认证文件默认存放在 $HOME/.docker/config.json,文件内容

{
    "auths": {
        "myregistrydomain.com:5000": {
            "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk",
            "email": "stuf@ex.cm"
        }
    }
}

Podman

链接:https://www.jianshu.com/p/3fc6eff747ab

Podman 是一个开源的容器运行时项目,可在大多数 Linux 平台上使用。Podman 提供与 Docker 非常相似的功能。正如前面提到的那样,它不需要在你的系统上运行任何守护进程,并且它也可以在没有 root 权限的情况下运行。

Podman 可以管理和运行任何符合 OCI(Open Container Initiative)规范的容器和容器镜像。Podman 提供了一个与 Docker 兼容的命令行前端来管理 Docker 镜像。

Podman 官网地址:https://podman.io/

Podman 项目地址:https://github.com/containers/libpod

安装 Podman

Podman 目前已支持大多数发行版本通过软件包来进行安装,下面我们来举几个常用发行版本的例子。

# Fedora / CentOS

$ sudo yum -y install podman

构建多平台Docker镜像

利用 Docker 19.03 以上版本引入的插件 buildx[4],可以很轻松地构建多平台 Docker 镜像。buildx 是 docker build ... 命令的下一代替代品,它利用 BuildKit[5] 的全部功能扩展了 docker build 的功能。

1、启用buildx插件 要想使用 buildx,首先要确保 Docker 版本不低于 19.03,同时还要通过设置环境变量DOCKER_CLI_EXPERIMENTAL来启用。可以通过下面的命令来为当前终端启用 buildx 插件:

export DOCKER_CLI_EXPERIMENTAL=enabled

验证是否开启:

docker buildx version

注:如果在系统上设置环境变量DOCKER_CLI_EXPERIMENTAL不生效(比如Arch Linux),可以通过源代码编译:

export DOCKER_BUILDKIT=1
docker build --platform=local -o . git://github.com/docker/buildx
mkdir -p ~/.docker/cli-plugins && mv buildx ~/.docker/cli-plugins/docker-buildx

2、启用binfmt_misc,(CentOS用户建议Linux内核升级到4.x以上) 如果你使用的是 Docker 桌面版(MacOS 和 Windows),默认已经启用了 binfmt_misc,可以跳过这一步

​ 如果你使用的是Linux,需要手动启用binfmt_misc。大多数Linux发行版都很容易启用,不过还有一个更容易的办法,直接运行一个特权容器,容器里面写好了设置脚本:

docker run --rm --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d

# 现在推荐使用这个镜像 https://hub.docker.com/r/tonistiigi/binfmt

Usage

Installing emulators

All emulators:

[root@VM-16-16-centos ~ 15:45:16]# docker run --privileged --rm tonistiigi/binfmt --install all
installing: mips64 OK
installing: s390x OK
installing: ppc64le OK
installing: riscv64 OK
installing: mips64le OK
installing: arm OK
{
  "supported": [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/mips64le",
    "linux/mips64",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "qemu-aarch64",
    "qemu-arm",
    "qemu-mips64",
    "qemu-mips64el",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}

Pick specific emulators:

docker run --privileged --rm tonistiigi/binfmt --install arm64,riscv64,arm

Show currently supported architectures and installed emulators

Prints output similar to

{
  "supported": [
    "linux/amd64",
    "linux/arm64",
    "linux/riscv64",
    "linux/ppc64le",
    "linux/s390x",
    "linux/386",
    "linux/arm/v7",
    "linux/arm/v6"
  ],
  "emulators": [
    "qemu-aarch64",
    "qemu-arm",
    "qemu-ppc64le",
    "qemu-riscv64",
    "qemu-s390x"
  ]
}

Uninstall emulator

docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-aarch64

未开启之前:

image-20221114152500795

# 验证binfmt_misc是否开启:
[root@VM-16-16-centos ~ 15:48:27]# ls -al /proc/sys/fs/binfmt_misc/
total 0
drwxr-xr-x 2 root root 0 Mar 21  2022 .
dr-xr-xr-x 1 root root 0 Mar 21  2022 ..
-rw-r--r-- 1 root root 0 Nov 14 15:43 qemu-aarch64
-rw-r--r-- 1 root root 0 Nov 14 15:45 qemu-arm
-rw-r--r-- 1 root root 0 Nov 14 15:45 qemu-mips64
-rw-r--r-- 1 root root 0 Nov 14 15:45 qemu-mips64el
-rw-r--r-- 1 root root 0 Nov 14 15:45 qemu-ppc64le
-rw-r--r-- 1 root root 0 Nov 14 15:45 qemu-riscv64
-rw-r--r-- 1 root root 0 Nov 14 15:45 qemu-s390x
--w------- 1 root root 0 Mar 21  2022 register
-rw-r--r-- 1 root root 0 Mar 21  2022 status


# 验证是否启用了相应的处理器:
[root@VM-16-16-centos ~ 15:48:35]# cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff

3、从默认的构建器切换到多平台构建器 Docker默认会使用不支持多CPU架构的构建器,需要手动切换。

查看当前使用的构建器及构建器支持的 CPU 架构,可以看到支持很多 CPU 架构:

[root@VM-16-16-centos ~ 15:50:11]# docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker                  
  default default         running linux/amd64, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6

先创建一个新的构建器,并启动:

[root@VM-16-16-centos ~ 15:52:21]# docker buildx ls   # 创建新的构建器
NAME/NODE    DRIVER/ENDPOINT             STATUS   PLATFORMS
mybuilder *  docker-container                     
  mybuilder0 unix:///var/run/docker.sock inactive 
default      docker                               
  default    default                     running  linux/amd64, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6


[root@VM-16-16-centos ~ 15:52:24]# docker buildx inspect mybuilder --bootstrap   # 启动构建器
[+] Building 26.8s (1/1) FINISHED    # 创建的多平台构建器如下                                                                                              
 => [internal] booting buildkit                                                                                              26.8s
 => => pulling image moby/buildkit:buildx-stable-1                                                                           26.1s
 => => creating container buildx_buildkit_mybuilder0                                                                          0.7s
Name:   mybuilder
Driver: docker-container

Nodes:
Name:      mybuilder0
Endpoint:  unix:///var/run/docker.sock
Status:    running
Platforms: linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

image-20221114161305129

[root@VM-16-16-centos ~ 15:53:48]# docker buildx ls
NAME/NODE    DRIVER/ENDPOINT             STATUS  PLATFORMS
mybuilder *  docker-container                    
  mybuilder0 unix:///var/run/docker.sock running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
default      docker                              
  default    default                     running linux/amd64, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/arm/v7, linux/arm/v6

4、构建多平台镜像

# 简单的 golang 程序源码
# vim main.go
package main

import (
    "fmt"
    "runtime"

    "github.com/gin-gonic/gin"
)

func main() {
    fmt.Printf("Hello, %s!\n", runtime.GOARCH)
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello, Gin")
    })
    r.Run() // listen and serve on 0.0.0.0:8080
}
# 编写Dockerfile
[root@VM-16-16-centos ~/go-hello 10:57:20]# cat Dockerfile 
FROM golang:alpine AS builder
ENV GO111MODULE=on \
    CGO_ENABLED=0 \
    GOOS=linux \
    GOPROXY=https://goproxy.cn,direct
# 这里我们采用buildx来进行交叉编译镜像,所以不需要指定GOARCH
# GOARCH=amd64 \

RUN mkdir /app
WORKDIR /app
RUN pwd \
  && go mod init test \
  && ls -lht /app 
COPY ./main.go .
RUN go mod tidy && go get github.com/gin-gonic/gin && \
    CGO_ENABLED=0 GOOS=linux go build -o hello .
#    CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o hello .

FROM alpine
# 修改源
RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories && \
    echo "http://mirrors.aliyun.com/alpine/latest-stable/community/" >> /etc/apk/repositories
# 时区设置成当前时区,创建app工作目录
ENV TZ="Asia/Shanghai"
RUN apk add --no-cache tzdata \
    && mkdir /app
WORKDIR /app
COPY --from=builder /app/hello .
CMD ["./hello"]

这是一个多阶段构建 Dockerfile,使用 Go 编译器来构建应用,并将构建好的二进制文件拷贝到 alpine 镜像中。 现在就可以使用 buildx 构建一个支持 arm、arm64 和 amd64 多架构的 Docker 镜像了,同时将其推送到 Docker Hub[6]:

安装并启用后,我们就可以使用Docker Buildx来构建多平台镜像了,比如原本的构建命令为:

build -t ${IMAGE_NAME}:${VERSION} .

使用Docker Buildx构建命令为:

docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_NAME}:${VERSION} . --push
  • --platform:指定需要构建的平台,例如:linux/amd64linux/arm64或`darwin/amd64

  • 使用了 --tag--push, 在构建完成后,会推送到目标 docker registry

  • 如果要执行 --push 成功, 只需要在 scheduler 主机上执行 docker login 即可, builder 上并无登录需求。默认推送到Docker hub仓库

构建完毕后,我们找到对应镜像的tag就可以看到支持多个平台。

现在就可以通过拉取刚刚创建的镜像了,Docker 将会根据你的 CPU 架构拉取匹配的镜像。

docker pull ${IMAGE_NAME}:${VERSION} 

背后的原理也很简单,之前已经提到过了,buildx 会通过 QEMU 和 binfmt_misc 分别为 3 个不同的 CPU 架构(arm,arm64 和 amd64)构建 3 个不同的镜像。构建完成后,就会创建一个 manifest list[7],其中包含了指向这 3 个镜像的指针。

如果想将构建好的镜像保存在本地,可以将 type 指定为 docker,但必须分别为不同的 CPU 架构构建不同的镜像,不能合并成一个镜像,即:

# arm
# docker buildx build -t px/hello-arch-arm --platform=linux/arm -o type=docker .

# arm64
# docker buildx build -t px/hello-arch-arm64 --platform=linux/arm64 -o type=docker .

# amd64
# docker buildx build -t px/hello-arch-amd64 --platform=linux/amd64 -o type=docker .
[+] Building 5.0s (18/18) FINISHED                                                                                                 
 => [internal] load build definition from Dockerfile                                                                          0.1s
 => => transferring dockerfile: 796B                                                                                          0.0s
 => [internal] load .dockerignore                                                                                             0.1s
 => => transferring context: 2B                                                                                               0.0s
 => [internal] load metadata for docker.io/library/alpine:latest                                                              3.1s
 => [internal] load metadata for docker.io/library/golang:alpine                                                              3.3s
 => [builder 1/6] FROM docker.io/library/golang:alpine@sha256:dc4f4756a4fb91b6f496a958e11e00c0621130c8dfbb31ac0737b0229ad6ad  0.1s
 => => resolve docker.io/library/golang:alpine@sha256:dc4f4756a4fb91b6f496a958e11e00c0621130c8dfbb31ac0737b0229ad6ad9c        0.0s
 => [stage-1 1/5] FROM docker.io/library/alpine@sha256:b95359c2505145f16c6aa384f9cc74eeff78eb36d308ca4fd902eeeb0a0b161b       0.1s
 => => resolve docker.io/library/alpine@sha256:b95359c2505145f16c6aa384f9cc74eeff78eb36d308ca4fd902eeeb0a0b161b               0.1s
 => [internal] load build context                                                                                             0.0s
 => => transferring context: 30B                                                                                              0.0s
 => CACHED [stage-1 2/5] RUN echo "http://mirrors.aliyun.com/alpine/latest-stable/main/" > /etc/apk/repositories &&     echo  0.0s
 => CACHED [stage-1 3/5] RUN apk add --no-cache tzdata     && mkdir /app                                                      0.0s
 => CACHED [stage-1 4/5] WORKDIR /app                                                                                         0.0s
 => CACHED [builder 2/6] RUN mkdir /app                                                                                       0.0s
 => CACHED [builder 3/6] WORKDIR /app                                                                                         0.0s
 => CACHED [builder 4/6] RUN pwd   && go mod init test   && ls -lht /app  && go mod tidy && go get github.com/gin-gonic/gin   0.0s
 => CACHED [builder 5/6] COPY ./hello.go .                                                                                    0.0s
 => CACHED [builder 6/6] RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -o hello .                                        0.0s
 => CACHED [stage-1 5/5] COPY --from=builder /app/hello .                                                                     0.0s
 => exporting to oci image format                                                                                             1.5s
 => => exporting layers                                                                                                       0.0s
 => => exporting manifest sha256:eab73ad460a3a6deb1ac222248681d37c13e2134661314a2484fc46cf1e5e447                             0.0s
 => => exporting config sha256:f61926056188ef90bfae49ce9466893895c07dcf512811be96cf14d9458e2ad9                               0.0s
 => => sending tarball                                                                                                        1.4s
 => importing to docker
[root@VM-16-16-centos ~/go-hello 17:02:29]# docker images px/hello-arch-amd64:latest 
REPOSITORY            TAG       IMAGE ID       CREATED          SIZE
px/hello-arch-amd64   latest    f61926056188   50 minutes ago   17.1MB

5、测试多平台镜像 由于之前已经启用了 binfmt_misc,现在我们就可以运行任何 CPU 架构的 Docker 镜像了,因此可以在本地系统上测试之前生成的 3 个镜像是否有问题。

首先列出远程每个镜像的 digests:

docker buildx imagetools inspect px/hello-arch
# 测试镜像可用性
[root@VM-16-16-centos ~/go-hello 17:03:07]# docker run --rm --name xxx px/hello-arch-amd64:latest 
Hello, amd64!
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[root@VM-16-16-centos ~/go-hello 17:05:38]# docker run --rm --name xxx px/hello-arch-arm64:latest 
WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
Hello, amd64!
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

很奇怪,我是在腾讯的amd es运行arm版本的镜像,镜像显示匹配不到对应的镜像,但是会运行其他的amd镜像

Docker 部署 Django+Mysql+Redis+Gunicorn+Nginx

https://www.cangmangai.cn/archives/docker-django-deployment

使用Docker部署Ngrok实现内网穿透

https://www.lylinux.net/article/2018/9/18/51.html

docker三剑客

image-20221114171330892

Docker-compose

三方参考文档:

https://yeasy.gitbooks.io/docker_practice/compose/

https://blog.51cto.com/14157628/2461149

其代码目前在 https://github.com/docker/compose 上开源

适合单击编排

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用

Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。从功能上看,跟 OpenStack 中的 Heat 十分类似。

Compose 定位是 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)」,其前身是开源项目 Fig。

我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。

Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。

Compose是用于定义和运行容器docker应用程序的工具。通过Compose,可以使用YAML文件来配合应用程序需要的所有的服务。然后,使用一个命令,就可以从YAML文件配合中创建并启动所有服务。那么就需要了解YAML文件的基本语法:

YAML文件基本语法:

  • 大小写敏感;

  • 使用缩进表示层级关系;

  • 缩进不允许使用tab,只允许空格;

  • 缩进的空格数不重要,只要相同等级的元素左对齐即可;

  • "#"表示注释

Docker-Compose是一个容器编排工具。通过一个.yml.yaml文件,将所有的容器的部署方法、文件映射、容器端口映射等情况写在一个配置文件里,执行docker-compose up命令就像执行脚本一样,一个一个的安装并部署容器。

Dockerfile 可以让用户管理一个单独的应用容器;而 Compose 则允许用户在一个模板(YAML 格式)中定义一组相关联的应用容器;

Docker Compose将所管理的容器分为三层:

  • 工程(project);

  • 服务(service);

  • 容器(container);

docker compose运行目录下的所有yml文件组成一个工程,一个工程包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖。一个服务可包括多个容器实例。

docker-compose就是docker容器的编排工具,主要就是解决相互有依赖关系的多个容器的管理

Compose 中有两个重要的概念:

1、服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。

2、项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose 项目由 s供的 API 来对容器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Compose 来进行编排管理

  • docker是自动化构建镜像,并启动镜像。 docker compose是自动化编排容器。

  • docker是基于Dockerfile得到images,启动的时候是一个单独的container

  • docker-compose是基于docker-compose.yml,通常启动的时候是一个服务,这个服务通常由多个container共同组成,并且端口,配置等由docker-compose定义好。

  • 两者都需要安装,但是要使用docker-compose,必须已经安装docker

Overview of Docker Compose

Docker Compose是一个用来定义和运行复杂应用的Docker工具。使用Compose,你可以在一个文件中定义一个多容器应用,然后使用一条命令来启动你的应用,完成一切准备工作

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.

Supported filenames: docker-compose.yml, docker-compose.yaml

Compose works in all environments: production, staging, development, testing, as well as CI workflows. You can learn more about each case in Common Use Cases.

Using Compose is basically a three-step process:

1、Define your app's environment with a Dockerfile so it can be reproduced anywhere.

2、Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.

3、Run docker-compose up and Compose starts and runs your entire app

Compose has commands for managing the whole lifecycle of your application:

  • Start, stop, and rebuild services

  • View the status of running services

  • Stream the log output of running services

  • Run a one-off command on a service

Install Docker Compose

  • docker安装: 参考官网

  • Docker-Compose安装:参考官网-> https://docs.docker.com/compose/install/

若想使用docker-comppose这个容器编排工具,那么宿主机必须是基于docker的环境,可以参考docker详细安装教程。docker的环境解决了之后,就是下载docker-compose这个命令,可以上GitHub官网进行下载,如图:

image-20221114171712486

image-20221114171814911

https://docs.docker.com/compose/compose-file/

image-20221114171801038

下载compose工具时,需先查看本机的docker版本!

[root@docker ~]# docker -v          # 查看docker的版本信息
Docker version 18.09.0, build 4d60db4   # 本次采用18.9.0版本

如果docker版本过低,可以自行查找其他版本的docker-compose工具。选择合适的版本之后,执行在github网站上找到的命令。

[root@docker ~]# curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
[root@docker ~]# chmod +x /usr/local/bin/docker-compose

如果网速不佳可以使用daocloud:

[root@docker ~]# curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
[root@docker ~]# chmod +x /usr/local/bin/docker-compose //使用道云的加速器进行下载
[root@docker ~]# docker-compose -v       //查看工具的版本信息
docker-compose version 1.25.4, build 8d51620a

Get started with Docker Compose

官网实例:https://docs.docker.com/compose/gettingstarted/

# yum install -y py-pip python-dev libffi-dev openssl-dev gcc libc-dev make

在项目根目录创建 docker-compose.yml,内容如下

version: "3"

services:
  db:
    image: postgres:9.5
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432"
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
  app:
      build:
        context: .
        dockerfile: ./docker/app/Dockerfile
      command: bash -c "bundle exec rake db:migrate && bundle exec puma -C config/puma.rb"
      depends_on:
        - db
      links:
        - db
      volumes:
        - .:/__project_name__
  web:
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    depends_on:
      - app
    ports:
      - 80:80
      - 443:443

参数解释:

  • version:Version 2支持更多的指令。Version 1没有声明版本默认是"version 1"。Version 1将来会被弃用。撰写文档格式版本号,若不清楚请百度Docker Engine与docker-compose version 之间的关系,选择对应版本,版本号不对会报错

  • services:服务,定义应用需要的一些服务,每个服务都有自己的名字、使用的镜像、挂载的数据卷、所属的网络、依赖哪些其他服务等等

  • volumes: 数据卷,定义的数据卷(名字等等),然后挂载到不同的服务下

  • image:指定镜像,如果本地不存在,Compose会尝试去docker hub pull下来

  • build:指定Dockerfile文件的路径,Compose将会以一个已存在的名称进行构建并标记,并随后使用这个image

  • command:重写默认的命令

  • links: 连接到其他服务中的容器,可以指定服务名称和这个链接的别名,或者只指定服务名称

  • ports:暴露端口,指定两者的端口(主机:容器),或者只是容器的端口(主机会被随机分配一个端口)

  • expose:暴露端口而不必向主机发布它们,而只是会向链接的服务(linked service)提供,只有内部端口可以被指定

  • environment:加入环境变量,可以使用数组或者字典,只有一个key的环境变量可以在运行Compose的机器上找到对应的值,这有助于加密的或者特殊主机的值

  • depends_on:依赖项,这个配置可以确保依赖服务被配置,但是并不能保证启动顺序

第一次部署在本地仓库根目录执行以下命令构建容器

// 构建(如果直接生成容器,可以直接up,忽略build)
docker-compose build
// depends_on并不能保证启动顺序,需要先把数据库跑起来
docker-compose up -d db
// 创建数据库初始化数据库
docker-compose run app rake db:setup
// 构建运行,并在后台运行
docker-compose up -d

以后每次部署只需更新代码,然后执行下面两句命令

docker-compose build

docker-compose up -d

踩过的坑:

depends_on并不能保证启动顺序,如果没先将db跑起来,执行docker-compose run app rake db:create 会失败

总结:通过正确的 Docker 设置,软件部署过程可以比以往更快更便捷。无论应用程序在哪里运行,都可以确保一致的环境。能够实现便捷的交付

Compose命令说明

命令对象与格式

对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响。

执行 docker-compose [COMMAND] --help 或者 docker-compose help [COMMAND] 可以查看具体某个命令的使用格式。

docker-compose 命令的基本的使用格式是
docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]

命令选项

  • -f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定。
  • -p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。
  • --x-networking 使用 Docker 的可拔插网络后端特性
  • --x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge
  • --verbose 输出更多调试信息。
  • -v, --version 打印版本并退出。

命令使用说明

help

获得一个命令的帮助。

version

格式为 docker-compose version

打印版本信息

config

验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。

images

列出 Compose 文件中包含的镜像。

build

格式为 docker-compose build [options] [SERVICE...]

构建(重新构建)项目中的服务容器。

服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。

可以随时在项目目录下运行 docker-compose build 来重新构建服务。

选项包括:

  • --force-rm 删除构建过程中的临时容器。

  • --no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。

  • --pull 始终尝试通过 pull 来获取更新版本的镜像。

kill

格式为 docker-compose kill [options] [SERVICE...]

通过发送 SIGKILL 信号来强制停止服务容器。

支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。

$ docker-compose kill -s SIGINT

down

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

restart

格式为 docker-compose restart [options] [SERVICE...]

重启项目中的服务。

选项:

  • -t, --timeout TIMEOUT 指定重启前停止容器的超时(默认为 10 秒)。

exec

进入指定的容器。

logs

格式为 docker-compose logs [options] [SERVICE...]

查看服务容器的输出。默认情况下,docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color 来关闭颜色。

该命令在调试问题的时候十分有用。

pause

格式为 docker-compose pause [SERVICE...]

暂停一个服务容器。

port

格式为 docker-compose port [options] SERVICE PRIVATE_PORT

打印某个容器端口所映射的公共端口。

options:

  • --protocol=proto 指定端口协议,tcp(默认值)或者 udp。

  • --index=index 如果同一服务存在多个容器,指定命令对象容器的序号(默认为 1)。

# docker-compose port nginx 80

ps

格式为 docker-compose ps [options] [SERVICE...]

列出项目中目前的所有容器。

选项:

  • -q 只打印容器的 ID 信息。

pull

格式为 docker-compose pull [options] [SERVICE...]

拉取服务依赖的镜像。

选项:

  • --ignore-pull-failures 忽略拉取镜像过程中的错误。

push

推送服务依赖的镜像到 Docker 镜像仓库。

rm

格式为 docker-compose rm [options] [SERVICE...]

删除所有(停止状态的)服务容器。推荐先执行 docker-compose stop 命令来停止容器。

选项:

  • -f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。

  • -v 删除容器所挂载的数据卷。

run

格式为 docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]

在指定服务上执行一个命令。

例如:

$ docker-compose run ubuntu ping docker.com

将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令。

默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中。

该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照配置自动创建。

两个不同点:

1、给定命令将会覆盖原有的自动运行命令;

2、不会自动创建端口,以避免冲突。

如果不希望自动启动关联的容器,可以使用 --no-deps 选项,例如

$ docker-compose run --no-deps web python manage.py shell

将不会启动 web 容器所关联的其它容器。

选项:

  • -d 后台运行容器。

  • --name NAME 为容器指定一个名字。

  • --entrypoint CMD 覆盖默认的容器启动指令。

  • -e KEY=VAL 设置环境变量值,可多次使用选项来设置多个环境变量。

  • -u, --user="" 指定运行容器的用户名或者 uid。

  • --no-deps 不自动启动关联的服务容器。

  • --rm 运行命令后自动删除容器,d 模式下将忽略。

  • -p, --publish=[] 映射容器端口到本地主机。

  • --service-ports 配置服务端口并映射到本地主机。

  • -T 不分配伪 tty,意味着依赖 tty 的指令将无法运行。

scale

格式为 docker-compose scale [options] [SERVICE=NUM...]

设置指定服务运行的容器个数。

通过 service=num 的参数来设置数量。例如:

$ docker-compose scale web=3 db=2

将启动 3 个容器运行 web 服务,2 个容器运行 db 服务。

一般的,当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之,将停止容器。

选项:

  • -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

start

格式为 docker-compose start [SERVICE...]

启动已经存在的服务容器。

stop

格式为 docker-compose stop [options] [SERVICE...]

停止已经处于运行状态的容器,但不删除它。通过 docker-compose start 可以再次启动这些容器。

选项:

  • -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

top

查看各个服务容器内运行的进程。

unpause

格式为 docker-compose unpause [SERVICE...]

恢复处于暂停状态中的服务。

up

格式为 docker-compose up [options] [SERVICE...]

该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。

链接的服务都将会被自动启动,除非已经处于运行状态。

可以说,大部分时候都可以直接通过该命令来启动一个项目。

默认情况,docker-compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。

当通过 Ctrl -C 停止命令时,所有容器将会停止。

如果使用 docker-compose up -d,将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。

默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用docker-compose up --no-recreate。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果用户只想重新部署某个服务,可以使用 docker-compose up --no-deps -d <SERVICE_NAME> 来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。

选项:

  • -d 在后台运行服务容器。

  • --no-color 不使用颜色来区分不同的服务的控制台输出。

  • --no-deps 不启动服务所链接的容器。

  • --force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。

  • --no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。

  • --no-build 不自动构建缺失的服务镜像。

  • -t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

Compose 模板文件

官方文档:https://docs.docker.com/compose/compose-file/

参考:https://www.cnblogs.com/itzgr/p/10041249.html

参考:https://blog.csdn.net/gulijiang2008/article/details/102794364

version

指定 compose 文件的版本

Compose和Docker兼容性:

Compose 文件格式有3个版本,分别为1, 2.x 和 3.x

目前主流的为 3.x 其支持 docker 1.13.0 及其以上的版本

用于指定当前compose文件语法遵循哪个版本,Compose文件版本支持特定的Docker版本列表如下:

Compose文件格式 Docker Engine版本
3.7 18.06.0+
3.6 18.02.0+
3.5 17.12.0+
3.4 17.09.0+
3.3 17.06.0+
3.2 17.04.0+
3.1 1.13.1+
3.0 1.13.0+
2.4 17.12.0+
2.3 17.06.0+
2.2 1.13.0+
2.1 1.12.0+
2.0 1.10.0+
1.0 1.9.1.+

示例:version: "3"


版权声明:本文为CSDN博主「坤仔1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_36697880/java/article/details/104845059

模板文件是使用 Compose 的核心,涉及到的指令关键字也比较多。但大家不用担心,这里面大部分指令跟 docker run 相关参数的含义都是类似的。

默认的模板文件名称为 docker-compose.yml,格式为 YAML 格式。

version: "3"

services:
  webapp:
    image: examples/web
    ports:
      - "80:80"
    volumes:
      - "/data"

注意每个服务都必须通过 image 指令指定镜像或 build 指令(需要 Dockerfile)等来自动构建生成镜像

如果使用 build 指令,在 Dockerfile 中设置的选项(例如:CMD, EXPOSE, VOLUME, ENV 等) 将会自动被获取,无需在 docker-compose.yml 中重复设置。

下面分别介绍各个指令的用法。

build

指定 Dockerfile 所在文件夹的路径(可以是绝对路径,或者相对 docker-compose.yml 文件的路径)。 Compose 将会利用它自动构建这个镜像,然后使用这个镜像。一般把Dockerfile和docker-compose.yml放在一层目录

version: '3'
services:

  webapp:
    build: ./dir

你也可以使用 context 指令指定 Dockerfile 所在文件夹的路径。

使用 dockerfile 指令指定 Dockerfile 文件名。

使用 arg 指令指定构建镜像时的变量。

version: '3'
services:

  webapp:
    build:
      context: ./dir
      dockerfile: Dockerfile-alternate
      args:
        buildno: 1

使用 cache_from 指定构建镜像的缓存

build:
  context: .
  cache_from:
    - alpine:latest
    - corp/web_app:3.14

cap_add, cap_drop

指定容器的内核能力(capacity)分配。

例如:让容器拥有所有能力可以指定为:

cap_add:
  - ALL

例如:让容器去掉 NET_ADMIN 能力可以指定为:

cap_drop:
  - NET_ADMIN

    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETGID
      - SETUID
      - NET_BIND_SERVICE

command

覆盖容器启动后默认执行的命令。

command: echo "hello world"

或者

command: ["redis-server" ,"--appendonly" ,"yes"]

configs

仅用于 Swarm mode,详细内容请查看 Swarm mode 一节。

cgroup_parent

指定父 cgroup 组,意味着将继承该组的资源限制。

例如,创建了一个 cgroup 组名称为 cgroups_1。

cgroup_parent: cgroups_1

container_name

指定容器名称。默认将会使用 项目名称_服务名称_序号 这样的格式。

container_name: docker-web-container

注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。

deploy

仅用于 Swarm mode,详细内容请查看 Swarm mode 一节

devices

指定设备映射关系。

devices:
  - "/dev/ttyUSB1:/dev/ttyUSB0"

depends_on

解决容器的依赖、启动先后的问题。以下例子中会先启动 redis 、db 再启动 web

services:
  web:
    build: .
    depends_on:
      - db
      - redis

  redis:
    image: redis

  db:
    image: postgres

注意:web 服务不会等待 redis db 「完全启动」之后才启动。

dns

自定义 DNS 服务器。可以是一个值,也可以是一个列表。

dns: 8.8.8.8
dns:
  - 8.8.8.8
  - 114.114.114.114

配置 DNS 搜索域。可以是一个值,也可以是一个列表。

dns_search: example.com


--
dns_search:
  - domain1.example.com
  - domain2.example.com

tmpfs

挂载一个 tmpfs 文件系统到容器。

tmpfs: /run

--
tmpfs:
  - /run
  - /tmp

environment

设置环境变量。你可以使用数组或字典两种格式。

只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。

environment:
  RACK_ENV: development
  SESSION_SECRET:

--
environment:
  - RACK_ENV=development
  - SESSION_SECRET

如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括

y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

env_file

从文件中获取环境变量,可以为单独的文件路径或列表。

如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。

如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。

env_file: .env

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

env_file: .env

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

expose

暴露端口,但不映射到宿主机,只被连接的服务访问。

仅可以指定内部端口为参数

expose:
 - "3000"
 - "8000"

注意:不建议使用该指令。

链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。

external_links:
 - redis_1
 - project_db_1:mysql
 - project_db_1:postgresql

extra_hosts

类似 Docker 中的--add-host 参数,指定额外的 host 名称映射信息。

extra_hosts:
 - "googledns:8.8.8.8"
 - "dockerhub:52.1.157.61"

会在启动后的服务容器中/etc/hosts 文件中添加如下两条条目。

8.8.8.8 googledns
52.1.157.61 dockerhub

healthcheck

通过命令检查容器是否健康运行。

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3

image

指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉取这个镜像。

image: ubuntu
image: orchardup/postgresql
image: a4bc65fd

labels

为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息。

labels:
  com.startupteam.description: "webapp for a startup team"
  com.startupteam.department: "devops department"
  com.startupteam.release: "rc3 for v1.0"

注意:不推荐使用该指令。

连接其他服务的容器,可以指定服务名称。web:links: -db -redis

logging

配置日志选项。

logging:
  driver: syslog
  options:
    syslog-address: "tcp://127.0.0.1:1514"
    tag: "postgresql"

=========================

目前支持三种日志驱动类型。

driver: "json-file"
driver: "syslog"
driver: "none"

--------------

options 配置日志驱动的相关参数。

options:
  max-size: "200k"
  max-file: "10"
  tag: "postgresql"

network_mode

设置网络模式。使用和 docker run 的 --network 参数一样的值。

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

networks

配置容器连接的网络。

version: "3"
services:
  some-service:
    networks:
     - some-network
     - other-network
networks:
  some-network:
  other-network:
version: '3'
services:
  front:
    image: front
    container_name: front
    depends_on:
      - php
    ports:
      - "80:80"
    networks:
      - "net1"
    volumes:
      - "/www:/usr/local/nginx/html"
  back:
    image: back
    container_name:back
    expose
      - "9000"
    networks:
      - "net1"
    volumes:
      - "/www:/usr/local/nginx/html"

networks:
  net1:
    driver: bridge

版权声明:本文为CSDN博主「坤仔1」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_36697880/java/article/details/104845059

pid

跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。

pid: "host"

ports

暴露端口信息。

使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。

ports:
 - "3000"
 - "8000:8000"
 - "49100:22"
 - "127.0.0.1:8001:8001"

注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式

restart

no是默认的重启策略,在任何情况下都不会重启容器。当always指定时,容器总是重新启动。该on-failure如果退出代码指示的故障错误政策重启的容器。

restart: "no"
restart: always
restart: on-failure
restart: unless-stopped

注意: 以(版本3)撰写文件以群集模式部署堆栈时,忽略此选项 。请改用restart_policy

secrets

存储敏感数据,例如 mysql 服务密码。

version: "3.1"
services:
mysql:
  image: mysql
  environment:
    MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
  secrets:
    - db_root_password
    - my_other_secret

secrets:
  my_secret:
    file: ./my_secret.txt
  my_other_secret:
    external: true

security_opt

指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名。

security_opt:
    - label:user:USER
    - label:role:ROLE

stop_signal

设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。

stop_signal: SIGUSR1

hostname

主机名

hostname: redis

privileged

特权

privileged: true

user

运行的用户

user: root

tty

tty: true

sysctls

配置容器内核参数

sysctls:
  - net.core.somaxconn=1024
  - net.ipv4.tcp_syncookies=0
  - vm.overcommit_memory=1

ulimits

指定容器的 ulimits 限制值。

例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。

  ulimits:
    nproc: 65535
    nofile:
      soft: 20000
      hard: 40000

Volumes

https://docs.docker.com/compose/compose-file/compose-file-v2/#volumes

参考:https://www.jianshu.com/p/4db1b954a3ec

数据卷所挂载路径设置。可以设置为宿主机路径(HOST:CONTAINER)或者数据卷名称(VOLUME:CONTAINER)并且可以设置访问模式 (HOST:CONTAINER:ro)。

该指令中路径支持相对路径。

volumes:
 - /var/lib/mysql
 - cache/:/tmp/cache
 - ~/configs:/etc/configs/:ro

Harbor的例子:

    volumes:
      - /data/harbor/job_logs:/var/log/jobs:z
      - type: bind # type: 挂载类型volume, bind,tmpfs或npipe
#source表示宿主机挂载文件的位置,target表示容器里面的位置。宿主机上的文件挂载到容器中。挂载文件
        source: ./common/config/jobservice/config.yml
        target: /etc/jobservice/config.yml
      - type: volume
        source: ./common/config/shared/trust-certificates
        target: /harbor_cust_cert

如果路径为数据卷名称,必须在文件中配置数据卷:

version: "3"
services:
  my_src:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

使用绝对路径

 ghost:  
   image: ghost
   volumes:
     - ./ghost/config.js:/var/lib/ghost/config.js

使用卷标

 services:
  mysql:  
   image: mysql
   container_name: mysql
   volumes:
     - mysql:/var/lib/mysql
 ...
 volumes:
  mysql:

第一种情况路径直接挂载到本地,比较直观,但需要管理本地的路径

第二种使用卷标的方式,比较简洁,但你不知道数据存在本地什么位置,下面说明如何查看docker的卷标

查看所有卷标

  docker volume ls 

查看批量的卷标

 $ docker volume ls | grep mysql
 local               vagrant_mysql

查看具体的volume对应的真实地址

 $ docker volume inspect vagrant_mysql
 [
    {
        "Name": "vagrant_mysql",
        "Driver": "local",
        "Mountpoint": "/var/lib/docker/volumes/vagrant_mysql/_data"
    }
 ]

其它指令

此外,还有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令,基本跟 docker run 中对应参数的功能一致。

# 指定服务容器启动后执行的入口文件。
entrypoint: /code/entrypoint.sh
# 指定容器中运行应用的用户名。
user: nginx
# 指定容器中工作目录。
working_dir: /code
# 指定容器中搜索域名、主机名、mac 地址等。
domainname: your_website.comhostname: testmac_address: 08-00-27-00-0C-0A
#允许容器中运行一些特权命令。
privileged: true
# 指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped。
restart: always
# 以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。
read_only: true
#打开标准输入,可以接受外部输入。
stdin_open: true
# 模拟一个伪终端。
tty: true

读取变量

Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。

例如,下面的 Compose 文件将从运行它的环境中读取变量 {MONGO_VERSION} 的值,并写入执行的指令中。

version: "3"
services:
db:
  image: "mongo:${MONGO_VERSION}"

如果执行 MONGO_VERSION=3.2 docker-compose up 则会启动一个 mongo:3.2 镜像的容器;

如果执行 MONGO_VERSION=2.8 docker-compose up 则会启动一个 mongo:2.8 镜像的容器。

若当前目录存在 .env 文件,执行 docker-compose 命令时将从该文件中读取变量。在当前目录新建 .env 文件并写入以下内容。

# 支持 # 号注释

MONGO_VERSION=3.6

执行 docker-compose up 则会启动一个 mongo:3.6 镜像的容器

构建简单的Nginx服务.yml文件

# 由于tab键使用的较多,所以事先设置了一个tab键代表的空格数
[root@docker ~]# vim /root/.vimrc   
set tabstop=2
[root@docker ~]# source /root/.vimrc
[root@docker ~]# mkdir compose_test
[root@docker ~]# cd compose_test/
# 创建一个测试目录,用于存放docker-compose.yml文件
# 建议一个目录下只有一个docker-compose.yml文件
[root@docker compose_test]# vim docker-compose.yml   # 编写一个docker-compose.yml文件
version: "3"                             # 指定语法的版本
services:                                # 定义服务
  nginx:
    container_name: web_nginx          # 运行的容器名
    image: nginx                         # 使用的镜像
    restart: always           # 随docker服务的启动而启动
    ports:
      - 90:80                 # 映射的端口
    volumes:
      - /root/compose_test/webserver:/usr/share/nginx/html    # 本地与容器挂载的目录
      - /etc/localtime:/etc/localtime

# 编写文件注意缩进

[root@docker compose_test]# docker-compose up -d
Starting web_nginx ... done

# 使用当前目录下的docker-compose.yml文件生成相应的容器
# "-d"选项,表示后台运行,如果不指定,默认则在前台运行,会占用终端

[root@docker compose_test]# docker ps -l          #查看运行的容器
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
c039bdafe4aa        nginx               "nginx -g 'daemon of…"   About a minute ago   Up 26 seconds       0.0.0.0:90->80/tcp   web_nginx

[root@docker compose_test]# echo "hello world" > /root/compose_test/webserver/index.html
# 创建测试网页
[root@docker compose_test]# curl 127.0.0.1:90   # 访问测试
hello world

image-20221115103434267

[root@docker compose_test]# docker-compose stop     # 通过.yml文件停止文件中指定的容器
[root@docker compose_test]# docker ps   # 查看效果
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[root@docker ~]# docker-compose -f /root/compose_test/docker-compose.yml up -d  # 可以使用"-f"选项来指定yml文件启动文件中定义的容器

部署redis

# cat docker-compose.yml 
version: "3.9"
services:
  redis:
    container_name: redis
    image: redis:6
    labels:
      release: "6"
    restart: always
    network_mode: "host"
    hostname: redis-server
    privileged: true
    user: root
    volumes:
      - /data/docker-compose-data/redis/:/data
      - /etc/localtime:/etc/localtime
    command:          #多个命令同时执行
      - /bin/bash 
      - -c 
      - |                 
        echo 551 > /proc/sys/net/core/somaxconn #关键命令
        echo 1 > /proc/sys/vm/overcommit_memory
        echo never > /sys/kernel/mm/transparent_hugepage/enabled
        redis-server --requirepass "123654" --notify-keyspace-events "Ex" --appendonly yes --protected-mode no

compose+dockerfile构建镜像

# 创建一个测试目录并进入
[root@master ~]# mkdir compose && cd compose
# 创建dockerfile 文件
[root@master compose]# cat Dockerfile
FROM nginx:latest
ADD html /usr/share/nginx/html
# 编写docker-compose.yml文件
[root@master compose]# cat docker-compose.yml
version: "3"
services:
  nginx:
    build: . #这里指定dockerfile的路径,可以写相对路径或绝对路径
    container_name: mynginx     #生成的容器名称
    image: mynginx              #使用dockerfile生成的镜像名称
    restart: always
    ports:
      - 70:80
# 创建网页目录
[root@master compose]# mkdir html
[root@master compose]# echo "hello world" > html/index.html
# 就是将dockerfile文件生成镜像
[root@master compose]# docker-compose build
Building nginx
Step 1/2 : FROM nginx:latest
 ---> f7bb5701a33c
Step 2/2 : ADD html /usr/share/nginx/html
 ---> 013d26d1df60
Successfully built 013d26d1df60
Successfully tagged mynginx:latest
# 直接生成容器,上一条命令可以忽略
[root@master compose]# docker-compose up -d
Creating network "compose_default" with the default driver
Creating mynginx ... done
# 命令行测试效果
[root@master compose]# curl 127.0.0.1:70
hello world

浏览器访问:

image-20221115103721743

使用.yml文件搭建WordPress

# 创建测试目录
[root@node01 ~]# mkdir wordpress && cd wordpress
# 编写yml文件
[root@node01 wordpress]# vim docker-compose.yml
version: "3.1"
services:
  wordprss:
    image: wordpress              # 指定使用的镜像
    restart: always
    ports:
      - 8088:80                   # 指定映射的端口
    environment:                  # 修改容器内部的环境变量
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: 123.com
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - /root/wordpress/www:/var/www/html
  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: 123.com
      MYSQL_ROOT_PASSWORD: 123.com
    volumes:
      - /root/wordpress/db_data:/var/lib/mysql
# 生成相应的容器,并在后台运行
[root@node01 wordpress]# docker-compose up -d
# 确定端口在监听
[root@node01 wordpress]# netstat -anpt | grep 8088
tcp        0      0 192.168.137.60:58088    192.168.137.60:4040     TIME_WAIT   -
tcp6       0      0 :::8088                 :::*                    LISTEN      223324/docker-proxy
# 确定容器在运行
[root@node01 wordpress]# docker ps
CONTAINER ID        IMAGE                                                        COMMAND                  CREATED             STATUS                 PORTS                                                              NAMES
b4d808f3150b        mysql:5.7                                                    "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes           3306/tcp, 33060/tcp                                                wordpress_db_1
84bd9bc48064        wordpress                                                    "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes           0.0.0.0:8088->80/tcp                                               wordpress_wordprss_1

image-20221115103913020

[root@node01 wordpress]# echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
[root@node01 wordpress]# sysctl -p
net.ipv4.ip_forward = 1

现在就可以访问测试页面了。如图:

image-20221115103940340

image-20221115104007070

image-20221115104016827

image-20221115104022939

image-20221115104033578

WordPress

基本信息

WordPress 是开源的 Blog 和内容管理系统框架,它基于 PHP 和 MySQL

该仓库位于 https://hub.docker.com/_/wordpress/ ,提供了 WordPress 4.x \~ 5.x 版本的镜像

使用方法

启动容器需要 MySQL 的支持,默认端口为 80。

$ docker run --name some-wordpress --link some-mysql:mysql -d wordpress

启动 WordPress 容器时可以指定的一些环境变量包括:

  • WORDPRESS_DB_USER 缺省为 root

  • WORDPRESS_DB_PASSWORD 缺省为连接 mysql 容器的环境变量 MYSQL_ROOT_PASSWORD 的值

  • WORDPRESS_DB_NAME 缺省为 wordpress

Dockerfile

请到 https://github.com/docker-library/docs/tree/master/wordpress 查看

创建空文件夹

假设新建一个名为 wordpress 的文件夹,然后进入这个文件夹。

创建docker-compose.yml 文件

docker-compose.yml 文件将开启一个 wordpress 服务和一个独立的 MySQL 实例:

version: "3"
services:
   db:
     image: mysql:8.0
     command:
      - --default_authentication_plugin=mysql_native_password
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
  db_data:

构建并运行项目

运行 docker-compose up -d compose 就会拉取镜像再创建我们所需要的镜像,然后启动 wordpress 和数据库容器。 接着浏览器访问 127.0.0.1:8000 端口就能看到 WordPress 安装界面了

image-20221115104649351

lnmp

https://blog.csdn.net/xiazichenxi/article/details/95652457

https://blog.csdn.net/bbwangj/article/details/81352154

Docker Swarm

三方参考:https://yeasy.gitbooks.io/docker_practice/swarm/

Docker Swarm 是 Docker 官方三剑客项目之一,提供 Docker 容器集群服务,是 Docker 官方对容器云生态进行支持的核心方案。

使用它,用户可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机,快速打造一套容器云平台。

注意:Docker 1.12.0+ Swarm mode 已经内嵌入 Docker 引擎,成为了 docker 子命令 docker swarm,绝大多数用户已经开始使用 Swarm mode,Docker 引擎 API 已经删除 Docker Swarm。为避免大家混淆旧的 Docker Swarm 与新的 Swarm mode,旧的 Docker Swarm 内容已经删除

Docker Machine

简介

image-20221115104922369

Docker Machine 是 Docker 官方编排(Orchestration)项目之一,负责在多种平台上快速安装 Docker 环境。

Docker Machine 项目基于 Go 语言实现,目前在 Github 上进行维护

Linux下安装

在 Linux 上的也安装十分简单,从 官方 GitHub Release 处直接下载编译好的二进制文件即可。

例如,在 Linux 64 位系统上直接下载对应的二进制包

$ sudo curl -L https://github.com/docker/machine/releases/download/v0.16.1/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine

$ sudo chmod +x /usr/local/bin/docker-machine
完成后,查看版本信息。

$ docker-machine -v
# docker-machine version 0.16.1, build cce350d7

使用

Docker Machine 支持多种后端驱动,包括虚拟机、本地主机和云平台等。

创建本地主机实例

Virtualbox 驱动

使用 virtualbox 类型的驱动,创建一台 Docker 主机,命名为 test。

$ docker-machine create -d virtualbox test

你也可以在创建时加上如下参数,来配置主机或者主机上的 Docker。

--engine-opt dns=114.114.114.114 配置 Docker 的默认 DNS
--engine-registry-mirror https://dockerhub.azk8s.cn 配置 Docker 的仓库镜像
--virtualbox-memory 2048 配置主机内存
--virtualbox-cpu-count 2 配置主机 CPU

更多参数请使用 docker-machine create --driver virtualbox --help 命令查看。

macOS xhyve 驱动

xhyve 驱动 GitHub: https://github.com/zchee/docker-machine-driver-xhyve

xhyve 是 macOS 上轻量化的虚拟引擎,使用其创建的 Docker Machine 较 VirtualBox 驱动创建的运行效率要高。

$ brew install docker-machine-driver-xhyve

$ docker-machine create \
      -d xhyve \
      # --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso \
      --engine-opt dns=114.114.114.114 \
      --engine-registry-mirror https://dockerhub.azk8s.cn \
      --xhyve-memory-size 2048 \
      --xhyve-rawdisk \
      --xhyve-cpu-count 2 \
      xhyve

注意:非首次创建时建议加上 --xhyve-boot2docker-url ~/.docker/machine/cache/boot2docker.iso 参数,避免每次创建时都从 GitHub 下载 ISO 镜像。

更多参数请使用 docker-machine create --driver xhyve --help 命令查看。

Windows 10

Windows 10 安装 Docker Desktop for Windows 之后不能再安装 VirtualBox,也就不能使用 virtualbox驱动来创建 Docker Machine,我们可以选择使用 hyperv 驱动。

注意,必须事先在Hyper-V 管理器中新建一个 外部虚拟交换机 执行下面的命令时,使用 --hyperv-virtual-switch=MY_SWITCH 指定虚拟交换机名称

$ docker-machine create --driver hyperv --hyperv-virtual-switch=MY_SWITCH vm

更多参数请使用 docker-machine create --driver hyperv --help 命令查看。

使用介绍

创建好主机之后,查看主机

$ docker-machine ls
NAME      ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER       ERRORStest      -        virtualbox   Running   tcp://192.168.99.187:2376           v17.10.0-ce

创建主机成功后,可以通过 env 命令来让后续操作对象都是目标主机。

$ docker-machine env test

后续根据提示在命令行输入命令之后就可以操作 test 主机。

也可以通过 SSH 登录到主机。

$ docker-machine ssh test

docker@test:~$ docker --version
Docker version 17.10.0-ce, build f4ffd25

连接到主机之后你就可以在其上使用 Docker 了。

官方支持驱动

通过 -d选项可以选择支持的驱动类型。

  • amazonec2

  • azure

  • digitalocean

  • exoscale

  • generic

  • google

  • hyperv

  • none

  • openstack

  • rackspace

  • softlayer

  • virtualbox

  • vmwarevcloudair

  • vmwarefusion

  • vmwarevsphere

第三方驱动

请到 第三方驱动列表 查看

操作命令

  • active 查看活跃的 Docker 主机

  • config 输出连接的配置信息

  • create 创建一个 Docker 主机

  • env 显示连接到某个主机需要的环境变量

  • inspect 输出主机更多信息

  • ip 获取主机地址

  • kill 停止某个主机

  • ls 列出所有管理的主机

  • provision 重新设置一个已存在的主机

  • regenerate-certs 为某个主机重新生成 TLS 认证信息

  • restart 重启主机

  • rm 删除某台主机

  • ssh SSH 到主机上执行命令

  • scp 在主机之间复制文件

  • mount 挂载主机目录到本地

  • start 启动一个主机

  • status 查看主机状态

  • stop 停止一个主机

  • upgrade 更新主机 Docker 版本为最新

  • url 获取主机的 URL

  • version 输出 docker-machine 版本信息

  • help 输出帮助信息

每个命令,又带有不同的参数,可以通过

$ docker-machine COMMAND --help

来查看具体的用法

~~Marathon+Mesos+Docker (没用)~~

Mesos 项目主要由 C++ 语言编写,项目官方地址为 https://mesos.apache.org,代码仍在快速演化中,已经发布了正式版 1.0.0 版本

一、Apache Mesos概述

Apache Mesos 是一款基于多资源(内存、CPU、磁盘、端口等)调度的开源群集管理套件,能时容错和分布式关系系统更加容易使用。

Apache Mesos 采用了Master/Slave 结构来简化设计,将Master 做的尽可能轻量级别仅保存了各种计算框架(Framework)和Mesos Slace 的状态详细。Mesos Master 充当与全局资源调度角色,采用某种策略算法将摸一个Slace上的空闲资源来分配给某一个Framewok 而且种Framework 则是通过自己的调度器向Master注册进行接入。

Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核。(分布式系统管理调度和资源分配机制)

Mesos 缺点 :

需要独立部署mesos-slave 进程;依赖 framework 的功能;成本比较高;

Mesos 优点 :

可以管理 docker 容器;稳定性具有保障

常见的集群管理工具

工具 特点 优势
Apache Mesos 需要独立部署mesos-slava进程;依赖framework功能,可以管理docker容器;成本较高 因为经过了许多互联网公司的大规模实践,稳定性具有保障
Docker Swarm Docker官方集群管理工具,需要Docker daemon启动tcp端口;Swarm的命令兼容Docker;学习成本低 公有云环境Machine和Swarm搭配使用效率更高
Googole Kubernetes 完全Docker化的管理工具,功能迭代非常快;集群管理功能比Mesos稍差 功能模块集成度高

1.Apache Mesos工作原理

Apache Mesos采用了Master/Slave结构来简化设计,将Master做的尽可能轻量级,仅保存了各种计算框架(Framework)和Mesos Slave的状态信息,这些状态很容易在Mesos出现故障的时候被重构,除此之外Mesos还可以使用。

Apache Master充当全局资源调度器决策,采用某种策略算法将某个Slave上的空闲资源分配给某个Framework,而各种Framework则是通过自己的调度器向Master注册进行接入,Mesos Slave则是手机任务状态和启动各个Frame work的Executor。

2.Apache Mesos基本数据

Mesos master:负责管理各个Framework和Slave,并将Salve上的资源分配给各个Framework。

Mesos Slave:负责管理本节点上的各个Mesos Task,为各个Executor分配资源。

Framework:计算框架,如:Hadoop、Spark等,可以通过MesosSchedulerDriver接入Mesos。

Executor:执行器,在Mesos Slave上安装,用于启动计算框架中的Task。

image-20221115105743452

zookeeper 是一个开源分布式应用协调服务可以为分布式应用提一致性服务,提供的功能包裹:配置维护,域名服务,分布式同步。组服务等。

zookeeper 的目标就是讲复杂易出错的关键服务进行分装,提供给用户使用性能高效功效稳定简单易用的操作系统

二、Apache Mesos配置实战

使用CentOS7系统,内核3.10以上。

主机名 IP地址 安装软件包
master1 192.168.137.2 jdk-8u201-linux-x64.tar.gz mesos-1.5.0.tar.gz zookeeper-3.4.10.tar.gz marathon-1.7.189-48bfd6000.tgz
master2 192.168.137.6 jdk-8u201-linux-x64.tar.gz mesos-1.5.0.tar.gz zookeeper-3.4.10.tar.gz
master3 192.168.137.9 jdk-8u201-linux-x64.tar.gz mesos-1.5.0.tar.gz zookeeper-3.4.10.tar.gz
slave1 192.168.137.7 jdk-8u201-linux-x64.tar.gz mesos-1.5.0.tar.gz Docker
slave2 192.168.137.8 jdk-8u201-linux-x64.tar.gz mesos-1.5.0.tar.gz Docker

注意:确保所有系统均处于联网状态。

1.安装Apache-mesos

当前Mesos支持三种语言编写的调度器,分别是c++、Java和Python,可以向不同的调度器提供统一的接入方式。

1.配置Java环境

[root@localhost complie]# mkdir -pv /usr/local/java
[root@localhost complie]# tar -zxf jdk-8u201-linux-x64.tar.gz -C /usr/local/java/
[root@localhost complie]# vim /etc/profile.d/java.sh
# 添加
export JAVA_HOME=/usr/local/java/jdk1.8.0_201
export CLASSPATH=$JAVA_HOME/jre/lib/ext:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

[root@localhost complie]# source /etc/profile.d/java.sh

2.安装相关环境

(1)安装开发工具

[root@localhost ~]# yum -y groupinstall "Development Tools"

(2)添加apache-maven源

[root@localhost complie]# wget http://repos.fedorapeople.org/repos/dchen/apache-maven/epel-apache-maven.repo -O /etc/yum.repos.d/epel-apache-maven.repo

(3)安装相关依赖包

[root@localhost complie]# yum install -y apache-maven python-devel python-six python-virtualenv  zlib-devel libcurl-devel openssl-devel cyrus-sasl-devel cyrus-sasl-md5 apr-devel subversion-devel apr-util-devel

(4)配置WANdiscoSVN网络源

[root@localhost complie]# vim /etc/yum.repos.d/wandisco-svn.repo
# 添加:
[WANdiscoSVN]
name=WANdisco SVN Repo 1.9
enabled=1
baseurl=http://opensource.wandisco.com/centos/7/svn-1.9/RPMS/\$basearch/
gpgcheck=1
gpgkey=http://opensource.wandisco.com/RPM-GPG-KEY-WANdisco

3.配置Mesos环境变量

[root@localhost complie]# vim /etc/profile.d/mesos.sh
# 添加:
export MESOS_NATIVE_JAVA_LIBRARY=/usr/local/lib/libmesos.so
export MESOS_NATIVE_LIBRARY=/usr/local/lib/libmesos.so
[root@localhost complie]# source /etc/profile.d/mesos.sh

4.构建Mesos

[root@localhost complie]# tar -zxf mesos-1.5.0.tar.gz
[root@localhost complie]# cd mesos-1.5.0/
[root@localhost mesos-1.5.0]# mkdir -pv build
[root@localhost mesos-1.5.0]# cd build/
[root@localhost build]# ../configure && make && make check && make install
安装mesos时间较长。

2.配置单台Mesos-master与Mesos-slave

Mesos-master负责维护slave集群的心跳,从slave提取资源信息。

1.配置Mesos-master

(1)做好相应解析工作。

[root@master1 ~]# vim /etc/hosts
# 添加:
192.168.137.2 master1
192.168.137.7 slave1
[root@master1 ~]# ln -svf /sdb/complie/mesos-1.5.0/build/bin/mesos-master.sh /usr/sbin/mesos-master

(2)简配启动Mesos-master

[root@master1 ~]# mesos-master --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --no-hostname_lookup --ip=0.0.0.0

参数:

  • --work_dir:运行期数据存放路径,包含了sandbox、slave meta等信息。
  • --log_dir:Mesos日志存放路径,建议修改。

  • --[no-]hostname_lookup:是否从DNS获取主机名,本例关闭了此配置,直接显示IP。

  • --ip:Mesos进程绑定IP。

配置完成后访问此主机的5050端口进行验证

image-20221115110822618

2.配置Mesos-slave

Mesos-slave负责接收并执行来自Mesos-master传递的任务以及监控任务状态,搜集任务使用系统情况,配置之前也应先做好相应解析工作。

(1)做好相应的解析

[root@slave1 ~]# vim /etc/hosts
# 添加:
192.168.137.7 slave1
192.168.137.2 master1
[root@slave1 ~]# ln -svf /sdb/complie/mesos-1.5.0/build/bin/mesos-slave.sh /usr/sbin/mesos-slave

(2)在Mesos-slave端安装并启动docker容器

[root@slave1 ~]# yum -y install docker-ce
[root@slave1 ~]# systemctl restart docker

(3)简配启动mesos-slave

[root@slave1 ~]# mesos-slave --containerizers="mesos,docker" --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --master=192.168.137.2:5050 --no-hostname_lookup --ip=0.0.0.0

--containerizers=VALUE:VALUE默认是mesos,如果要使用mesos管理docker则要添加docker值,用逗号隔开。

(4)测试验证

关闭Mesos-master防火墙后使用浏览器再次对master的5050端口进行验证。在Mesos web页面左侧可以看到slave状态

image-20221115111401050

点击菜单栏中Agents连接,可以查看slave主机的硬件信息与注册时间。

image-20221115111408668

3.单台Mesos-master配置ZooKeeper

ZooKeeper是一个开源的分布式应用程序协调服务,可以为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

ZooKeeper的目标就是将复杂易出错的关键服务进行封装,提供给用户性能高效、功能稳定、简单易用的系统。

(1)下载ZooKeeper,只需将配置文件模版进行改名即可使用。

[root@master1 complie]# tar -zxf zookeeper-3.4.10.tar.gz -C /home/q/
[root@master1 complie]# cd /home/q/zookeeper-3.4.10/
[root@master1 zookeeper-3.4.10]# mv conf/zoo_sample.cfg conf/zoo.cfg

(2)启动Zookeeper服务

[root@master1 zookeeper-3.4.10]# ./bin/zkServer.sh start conf/zoo.cfg

image-20221115111449370

(3)单机模式的ZooKeeper处于standalone状态

[root@master1 zookeeper-3.4.10]# ./bin/zkServer.sh status conf/zoo.cfg

image-20221115111504099

(4)在ZooKeeper服务器启动后,就可以使用ZooKeeper的客户端来连接测试

[root@master1 zookeeper-3.4.10]# ./bin/zkCli.sh

image-20221115111517849

4.后台运行Mesos-master与Mesos-slave

ZooKeeper简称为zk,在整个Apache Mesos中,主要用来存储Mesos-master地址,方便Mesos-slave读取。当Mesos-slave从zk中获取地址后,可以直接使用Mesos-master地址以及端口连接Mesos-master。

nohup命令可以忽略所有挂断(SIGHUP)信号,作为后台程序运行Mesos-master于Mesos-slave。

[root@master1 ~]# nohup mesos-master --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --no-hostname_lookup --ip=192.168.137.2 --zk=zk
://192.168.137.2:2181/mesos --quorum=1 &> /dev/null &

参数:

  • --zk:ZooKeeper地址,用于Leader选举。指定zk端口号。

  • --zk_session_timeout:根据网络环境调整zk session超时时间(默认10s)。

  • --quorum:Master replica logs多写数量,多Master场景下此值要超过Master数量的一半。

  • --credential:提供密钥对,介入集群时用于验证。

此时,Mesos-slave使用zk地址和端口号连接Mesos-master。

[root@slave1 ~]# nohup mesos-slave --containerizers="mesos,docker" --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --master=zk://192.168.137.2:2181/mesos --no-hostname_lookup --ip=192.168.137.7 &> /dev/null &

使用浏览器验证

image-20221115111604462

3.配置多Mesos-master环境

生产环境中ZooKeeper是以宕机个数过半来让整个集群宕机的。所以Mesos-master一般选择奇数个节点来集群,随着部署的Master节点增多可靠性也就增强。但多Mesos-master集群环境只有一个Mesos-master会处于Leader状态对外提供服务,集群中的其他服务器则成为此Leader的Follower,处于就绪状态。当Leader发生故障时,Zookper将会快速在Follower中投票下一个服务器作为Leader继续。

注意:Mesos-master本身具备选票选举机制,并不是必须要使用ZooKeeper做投票选举。不过生产环境中会使用ZooKeeper来做选举的双重保障。同时ZooKeeper的使用也简化了Mesos-slave的连接。

1.安装ZooKeeper

分别在所有Mesos-master节点上安装ZooKeeper

[root@master2 complie]# tar -zxf zookeeper-3.4.10.tar.gz -C /home/q/
[root@master2 complie]# cd /home/q/zookeeper-3.4.10/
[root@master2 zookeeper-3.4.10]# mv conf/zoo_sample.cfg conf/zoo.cfg

2.配置ZooKeeper

修改ZooKeeper配置文件,以server.A=B:C:D。格式定义各个节点相关信息:A是一个数字,表示第几号服务器;B是这个服务器的IP地址;C是与集群中的Leader服务器交换信息的端口;D是在Leader挂掉时专门进行Leader选举时所用的端口。

[root@master2 zookeeper-3.4.10]# mkdir -pv /home/q/zookeeper-3.4.10/data
[root@master2 zookeeper-3.4.10]# mkdir -pv /home/q/zookeeper-3.4.10/datalog
[root@master1 zookeeper-3.4.10]# vim conf/zoo.cfg
# 修改:
dataDir=/home/q/zookeeper-3.4.10/data
dataLogDir=/home/q/zookeeper-3.4.10/datalog
server.1=192.168.137.2:2888:3888
server.2=192.168.137.6:2888:3888
server.3=192.168.137.9:2888:3888

修改完的配置文件拷贝给其他Mesos-master主机

每个节点还需要配置文件zoo.cfg中定义的dataDir路径下创建一个myid文件,myid文件内含有上面提到的A的值。

[root@master1 zookeeper-3.4.10]# echo 1 > data/myid
[root@master2 zookeeper-3.4.10]# echo 2 > data/myid
[root@master3 zookeeper-3.4.10]# echo 3 > data/myid

在各节点上启动Zookeeper服务

[root@master1 zookeeper-3.4.10]# ./bin/zkServer.sh start conf/zoo.cfg

查看状态:master3被选为Leader,其他主机为Follower

[root@master3 zookeeper-3.4.10]# ./bin/zkServer.sh status

image-20221115111735778

[root@master1 zookeeper-3.4.10]# ./bin/zkServer.sh status

image-20221115111752610

[root@master2 zookeeper-3.4.10]# ./bin/zkServer.sh status

image-20221115111806344

Leader:负责投票发起和决议、更新系统状态

Follwer:负责接收客户请求,想客户端返回结果,并在选举过程中参与投票。

3.分别启动Mesos-master

[root@master1 bin]# mesos-master --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --no-hostname_lookup --ip=0.0.0.0 --zk=zk://192.168.137.2:2181/mesos --quorum=2
[root@master2 bin]# mesos-master --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --no-hostname_lookup --ip=0.0.0.0 --zk=zk://192.168.137.6:2181/mesos --quorum=2 
[root@master3 bin]# mesos-master --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --no-hostname_lookup --ip=0.0.0.0 --zk=zk://192.168.137.9:2181/mesos --quorum=2

4.启动Mesos-slave

此时Mesos-slave指定Mesos-master,直接使用zk地址,多Mesos-master环境的多zk地址使用逗号进行分隔。

[root@slave1 ~]# nohup mesos-slave --containerizers="mesos,docker" --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --master=zk://192.168.137.2:2181,192.168.137.6:2181,192.168.137.9:2181/mesos --no-hostname_lookup --ip=192.168.137.7 &> /dev/null &
 #后台运行

使用浏览器指定任意Mesos-master地址的5050端口进行验证,若指定的是非Leader状态下的Mesos-master地址,页面会自动跳转处于Leader状态的Mesos-master地址

image-20221115111847232

5.其他参数配置

生产环境中从高可用性和安全性两方面考虑,可以添加如下参数:

  • --whitelist=VALUE:Master参数,白名单,用来限制Task调度

  • --[no-]authenticate:Master参数,开启接入认证,一旦开启此功能无证书的slave将无法接入。

  • --credentials=VALUE:Master参数,搭配---authenticate,提供有效的密钥对。

如果企业环境下的资源分配和控制方面进行考虑,还需要一下参数:

  • --roles=VALUE:Master参数,定义整个Mesos中的角色,用于资源分配。
  • --resources=VALUE:Slave配置,针对Master预定义的roles,用于声明静态资源,比如cpu/mem/disk/port等。

4.部署运行Marathon

Marathon是一个Mesos框架,能够支持运行长服务,比如web应用等。下载解压即可使用。个别版本需要编译

使用Marathon向Mesos发送任务

1.安装并启动Marathon

[root@master1 complie]# tar -zxf marathon-1.7.189-48bfd6000.tgz -C /home/q/
[root@master1 complie]# cd /home/q/marathon/

将Marathon安装到多Mesos-master环境的Master(192.168.137.2/24)主机上

[root@master1 bin]# ./marathon --hostname 192.168.137.2 --master zk://192.168.137.2:2181,192.168.137.6:2181,192.168.137.9:2181/mesos --http_address 192.168.137.2

如果报错:

image-20221115111939259

[root@master1 bin]# find / -name libmesos.so
[root@master1 bin]# ln -sv /sdb/complie/mesos-1.5.0/build/src/.libs/libmesos.so

访问Marathon(默认使用8080端口)

image-20221115111959881

Marathon首页

2.使用Marathon创建测试任务

在首页点击create按钮,创建一个测试任务:echo "hello world"

image-20221115112016587

创建成功以后在Applications页面可以看到该任务

image-20221115112029213

此时,Marathon会自动注册到Mesos中,可以在Mesos Web的Framework页面中看到注册信息。

image-20221115112036247

可以在Mesos Web首页看到测试任务在不停地执行中。

image-20221115112043115

使用命令方式从Mesos-slave主机的data/slave目录中可以查看到这个简单任务的相关信息。

[root@slave1 ~]# cd /home/q/mesos/data/slaves/639552de-7e6f-456b-bedf-59f7d9754ad0-S0/frameworks/08354c41-6bbb-4c9f-8472-0d3f2fb77309-0000/executors/
[root@slave1 executors]# ls

image-20221115112101845

[root@slave1 executors]# cd test.012aa594-6fd5-11e9-b2a7-000c299a60cd/
[root@slave1 executors]# cd test.012aa594-6fd5-11e9-b2a7-000c299a60cd/runs/latest/
[root@slave1 latest]# ll

image-20221115112121980

[root@slave1 latest]# cat stdout

image-20221115112136646

3.使用Marathon API的形式添加新任务

[root@slave1 ~]# vim demo.json
# 添加:
{
"id":"basic-0"
"cmd":"while [ true ];do echo 'hello Marathon';sleep 5;done,
"cpus":0.1,
"mem":10.0,
"instances":1
}
[root@master1 ~]# curl -X POST -H "Content-type:application/json" http://192.168.137.2:8080/v2/apps -d@demo.json

image-20221115112203043

启动第二个Mesos-slave节点slave2。在Mesos web的Agents选项卡中可以看到加入的slave节点相关信息。

[root@slave2 ~]# systemctl restart docker
[root@slave2 ~]# nohup mesos-slave --containerizers="mesos,docker" --work_dir=/home/q/mesos/data --log_dir=/home/q/mesos/logs --master=zk://192.168.137.2:2181,192.168.137.6:2181,192.168.137.9:2181/mesos --no-hostname_lookup --ip=192.168.137.8 &> /dev/null &

image-20221115112225082

在Mesos web首页看到两个slave主机都已经激活

image-20221115112232718

在Marathon中点击Scale Application扩充16个任务,已经启动的16个任务会发送16个echo消息,由Marathon随机分发给salve1和slave2主机。

image-20221115112242530

在Marathon中删除任务后,可以看到mesos中所有任务都处于KILLED状态。Marathon是以kill -9的形式把任务发送给Mesos的。

image-20221115112248649

image-20221115112255040

4.使用Mesos与Marathon创建Docker集群

使用Marathon API的形式创建Docker的Nginx的请求任务,创建好后在Marathon页面中查看

[root@master1 ~]# vim nginx.json
# 添加:
{
    "id":"/nginx",
    "container":{
        "type":"DOCKER",
        "docker":{
            "image":"nginx",
            "network":"HOST",
            "parameters":[],
            "privileged":false,
            "forcePullImage":false
        }                                                                                                                             
    },
    "cpus":0.1,
    "mem":32.0,
    "instances":1
}

image-20221115112325024

[root@master1 ~]# curl -X POST -H "Content-type:application/json" http://192.168.137.2:8080/v2/apps -d@nginx.json

可以看到在创建nginx任务下看到该任务发送给了192.168.137.8主机(slave2)。下载完成后在Marathon页面中看到下载的镜像会自动启动

image-20221115112341955

也可以用命令行方式查看nginx镜像状态。

[root@slave2 ~]# docker ps -a

image-20221115112400525

可以使用inspect 命令收集有关容器和镜像的底层信息。

[root@slave2 ~]# docker inspect 786

运行并访问nginx服务。

image-20221115112422930

注意:此时镜像使用的是HOST网络方式,因此直接访问slave2IP即可访问镜像nginx。

JSON模版可以从marathon管理注意查看

免责声明: 本文部分内容转自网络文章,转载此文章仅为个人收藏,分享知识,如有侵权,请联系博主进行删除。