Docker,翻译过来就是码头工人
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可抑制的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器完全使用沙盒机制,相互之间不会存在任何接口。几乎没有性能开销,可以很容易的在机器和数据中心运行。最重要的是,他们不依赖于任何语言、框架或者包装系统。
小知识:沙盒也叫沙箱(sandbox)。在计算机领域指一种虚拟技术,而且多用于计算机安全技术。安全软件可以让它在沙盒中运行,如果含有恶意行为,则禁止程序的进一步运行,而这不会对系统造成任何危害。
Docker是dotCloud公司开源的一个基于LXC的高级容器引擎,源码托管在Github上,基于go语言并且遵从Apache2.0协议开源。GitHub地址:https://github.com/moby/moby
小知识:LXC为Linux Container的简写。Linux Container 容器是一种内核虚拟化技术,可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。LXC主要通过Kernel的namespace实现每个用户实例之间的项目隔离,通过cgroup实现对资源的配额和调度。
docker官网:https://www.docker.com
docker中文库:https://www.docker.org.cn/
Docker 到底是个什么东西呢?我们在理解 Docker 之前,首先得先区分清楚两个概念,容器和虚拟机。
虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统。在实体计算机中能够完成的工作在虚拟机中都能够实现。在计算机中创建虚拟机时,需要将实体机的部分硬盘和内存容量作为虚拟机的硬盘和内存容量。每个虚拟机都有独立的CMOS、硬盘和操作系统,可以像使用实体机一样对虚拟机进行操作。
可能很多读者朋友都用过虚拟机,而对容器这个概念比较的陌生。我们用的传统虚拟机如 VMware , VisualBox 之类的需要模拟整台机器包括硬件。
每台虚拟机都需要有自己的操作系统,虚拟机一旦被开启,预分配给它的资源将全部被占用。
每一台虚拟机包括应用,必要的二进制和库,以及一个完整的用户操作系统。
Docker 容器是一个开源的应用容器引擎,让开发者可以以统一的方式打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何安装了docker引擎的服务器上(包括流行的Linux机器、windows机器),也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app)。几乎没有性能开销,可以很容易地在机器和数据中心中运行。最重要的是,他们不依赖于任何语言、框架包括系统。
而容器技术是和我们的宿主机共享硬件资源及操作系统,可以实现资源的动态分配。
容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行。
容器技术是实现操作系统虚拟化的一种途径,可以让您在资源受到隔离的进程中运行应用程序及其依赖关系。
通过使用容器,我们可以轻松打包应用程序的代码、配置和依赖关系,将其变成容易使用的构建块,从而实现环境一致性、运营效率、开发人员生产力和版本控制等诸多目标。
容器可以帮助保证应用程序快速、可靠、一致地部署,其间不受部署环境的影响。
容器还赋予我们对资源更多的精细化控制能力,让我们的基础设施效率更高。
docker和容器技术和虚拟机技术,都是虚拟化技术。
通过下面这幅图,我们可以很直观的反映出这两者的区别所在:
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
而 Linux 容器是 Linux 发展出的另一种虚拟化技术,简单来讲, Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。
对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。
程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker ,就不用担心环境问题。
总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
Docker 相比于传统虚拟化方式具有更多的优势:
我们可以从下面这张表格很清楚地看到容器相比于传统虚拟机的特性的优势所在:
Docker用于应用程序时是最有用的,但并不包含数据。日志、数据库等通常放在Docker容器外。一个容器的镜像通常都很小,不用和存储大量数据,存储可以通过外部挂载等方式使用,比如:NFS、ipsan、MFS等 ,或者docker命令 ,-v映射磁盘分区。总之,docker只用于计算,存储交给别人。
从上图我们可以看到,Docker 中包括三个基本的概念:
镜像是 Docker 运行容器的前提,仓库是存放镜像的场所,可见镜像更是 Docker 的核心。
那么镜像到底是什么呢?Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。
镜像不包含任何动态数据,其内容在构建之后也不会被改变。镜像(Image)就是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义:
从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其他层都会有一个指针指向下一层。这些层是 Docker 内部的实现细节,并且能够在主机的文件系统上访问到。
统一文件系统(Union File System)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角。
这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。
容器(Container)的定义和镜像(Image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
由于容器的定义并没有提及是否要运行容器,所以实际上,容器 = 镜像 + 读写层。
Docker 仓库是集中存放镜像文件的场所。镜像构建完成后,可以很容易的在当前宿主上运行。
但是, 如果需要在其他服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry(仓库注册服务器)就是这样的服务。
有时候会把仓库(Repository)和仓库注册服务器(Registry)混为一谈,并不严格区分。
Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。
实际上,一个 Docker Registry 中可以包含多个仓库(Repository),每个仓库可以包含多个标签(Tag),每个标签对应着一个镜像。
所以说,镜像仓库是 Docker 用来集中存放镜像文件的地方,类似于我们之前常用的代码仓库。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。
我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 Latest 作为默认标签。
仓库又可以分为两种形式:
Docker Registry 公有仓库是开放给用户使用、允许用户管理镜像的 Registry 服务。
一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。
当用户创建了自己的镜像之后就可以使用 Push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 Pull 下来就可以了。最大的公开仓库是 Docker Hub(https://hub.docker.com/),存放了数量庞大的镜像供用户下载。国内的公开仓库包括阿里云 、网易云等。
我们主要把 Docker 的一些常见概念如 Image,Container,Repository 做了详细的阐述,也从传统虚拟化方式的角度阐述了 Docker 的优势。
我们从下图可以直观地看到 Docker 的架构:
Docker 使用 C/S 结构,即客户端/服务器体系结构。Docker 客户端与 Docker 服务器进行交互,Docker服务端负责构建、运行和分发 Docker 镜像。
Docker 客户端和服务端可以运行在一台机器上,也可以通过 RESTful 、 Stock 或网络接口与远程 Docker 服务端进行通信。
这张图展示了 Docker 客户端、服务端和 Docker 仓库(即 Docker Hub 和 Docker Cloud ),默认情况下 Docker 会在 Docker 中央仓库寻找镜像文件。
这种利用仓库管理镜像的设计理念类似于 Git ,当然这个仓库是可以通过修改配置来指定的,甚至我们可以创建我们自己的私有仓库。
Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上, 然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。容器,是一个运行时环境,就是我们前面说到的集装箱。
Docker 的核心组件包括:
Docker 采用的是 Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。
客户端和服务器可以运行在同一个 Host 上,客户端也可以通过 Socket 或 REST API 与远程的服务器通信。
可能很多朋友暂时不太理解一些东西,比如 REST API 是什么东西等,不过没关系,在后面的文章中会一一给大家讲解清楚。
Docker Client ,也称 Docker 客户端。它其实就是 Docker 提供命令行界面(CLI)工具,是许多 Docker 用户与 Docker 进行交互的主要方式。
客户端可以构建,运行和停止应用程序,还可以远程与 Docker_Host 进行交互。
最常用的 Docker 客户端就是 Docker 命令,我们可以通过 Docker 命令很方便地在 Host 上构建和运行 Docker 容器。
Docker Daemon 是服务器组件,以 Linux 后台服务的方式运行,是 Docker 最核心的后台进程,我们也把它称为守护进程。
它负责响应来自 Docker Client 的请求,然后将这些请求翻译成系统调用完成容器管理操作。
该进程会在后台启动一个 API Server ,负责接收由 Docker Client 发送的请求,接收到的请求将通过 Docker Daemon 内部的一个路由分发调度,由具体的函数来执行请求。
我们大致可以将其分为以下三部分:
Docker Daemon 的架构如下所示:
Docker Daemon 可以认为是通过 Docker Server 模块接受 Docker Client 的请求,并在 Engine 中处理请求,然后根据请求类型,创建出指定的 Job 并运行。
Docker Daemon 运行在 Docker Host 上,负责创建、运行、监控容器,构建、存储镜像。
运行过程的作用有以下几种可能:
由于 Docker Daemon 和 Docker Client 的启动都是通过可执行文件 Docker 来完成的,因此两者的启动流程非常相似。
Docker 可执行文件运行时,运行代码通过不同的命令行 Flag 参数,区分两者,并最终运行两者各自相应的部分。
启动 Docker Daemon 时,一般可以使用以下命令来完成:
1 | docker --daemon = truedocker –d |
再由 Docker 的 main() 函数来解析以上命令的相应 Flag 参数,并最终完成 Docker Daemon 的启动。
下图可以很直观地看到 Docker Daemon 的启动流程:
默认配置下,Docker Daemon 只能响应来自本地 Host 的客户端请求。如果要允许远程客户端请求,需要在配置文件中打开 TCP 监听。
我们可以照着如下步骤进行配置:
1、编辑配置文件/etc/systemd/system/multi-user.target.wants/docker.service,在环境变量 ExecStart 后面添加 -H tcp://0.0.0.0,允许来自任意 IP 的客户端连接。
2、重启 Docker Daemon:
1 | systemctl daemon-reload |
3、我们通过以下命令即可实现与远程服务器通信:
1 | docker |
-H 是用来指定服务器主机,info 子命令用于查看 Docker 服务器的信息。
Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。
镜像不包含任何动态数据,其内容在构建之后也不会被改变。我们可将 Docker 镜像看成只读模板,通过它可以创建 Docker 容器。
镜像有多种生成方法:
我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile ,通过执行 docker build命令可以构建出 Docker 镜像。
Docker 容器就是 Docker 镜像的运行实例,是真正运行项目程序、消耗系统资源、提供服务的地方。
Docker Container 提供了系统硬件环境,我们可以使用 Docker Images 这些制作好的系统盘,再加上我们所编写好的项目代码,Run 一下就可以提供服务啦。
Docker Registry 是存储 Docker Image 的仓库,它在 Docker 生态环境中的位置如下图所示:
运行 docker push、docker pull、docker search 时,实际上是通过 Docker Daemon 与 Docker Registry 通信。
我们使用ifconfig可以看到三组网络。首先是docker0,这是我们本节的重点,docker的网络。之后是eth0,本机的外网地址。lo口,本地环回地址,可以代表localhost。
关于docker0呢,其实就是一个叫docker0的虚拟网桥。我们使用brctl命令来查看一下。(没有这个命令的下载yum -y install bridge-utils)
1 | brctl show |
1 | docker rm -f $(docker ps -aq) |
什么是veth-pair技术?要理解它,我们首先来启动两个tomcat容器。
1 | docker run -d -P --name=tomcat01 tomcat:7 |
启动机器之后,我们查看容器ip,通过容器的ip 去ping宿主机ip,发现是通的。
1 | docker |
1 | ping |
理解:我们每启动一个docker容器,docker就会给docker容器分配一个ip,安装docker之后,会产生一个叫docker0的网卡,这里使用的就是veth-pair技术。
使用ip addr命令,查看我们的网卡。
我们发现多出来了两个网卡,到了这里,你已经知道这两张网卡是那里来的了吧。没错,是启动容器之后产生的!我们回过头来查看我们在启动的容器IP,就会很清晰的发现,这个网卡是成对存在的!容器内的64对应着宿主机的65,容器内的66对应宿主机的67。
什么是veth-pair?veth-pair 就是一堆的虚拟设备接口,他们都是成对出现的,一端连接着协议,一端连接着彼此。使得它充当了一个桥梁的作用。
我们来绘制一个简单的网络模型,这样veth-pair的作用就清晰明了了。
不难看出,tomcat01和tomcat02是共用的同一个路由器,即docker0。所有的容器在不指定我网络的情况下,都是docker0路由的,docekr会给我们的容器分配一个默认IP。docker网络就是下面这个网络模型所描述的。(docker所有的网络接口都是虚拟的,虚拟的转发效率高)
1 | Host:容器不会虚拟出自己的网卡,配置主机的IP等,而是使用宿主机的IP和端口 |
1 | #我们直接启动命令, --net bridge,就是docker0(默认) |
下面我们自己来创建一个bridge。
1 | docker network create --driver bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 testnet |
只需要两条命令,你就创建完了自己的网络!
这里在教大家一条命令:
1 | docker network inspect 网卡名字 #查看网卡详细信息 |
1 | docker run -d -P --name=tomcat01-net --net=testnet tomcat:7 |
然后使用docker network inspect testnet,就可以看到刚才创建的这两个容器的IP了。
还记得我们前面说的docker0的缺点之一,不能通过域名访问吗?而我们自定义的网络,就修复了这个功能!
1 | docker exec -it tomcat01-net ping -c 3 IP |
Docker 的安装和使用有一些前提条件,主要体现在体系架构和内核的支持上。
对于体系架构,除了 Docker 一开始就支持的 X86-64 ,其他体系架构的支持则一直在不断地完善和推进中。
2017年初,docker公司将原先的docker开源项目改名为moby。
moby是集成了原先的docker项目,是社区维护的开源项目,谁都可以在moby的基础打造自己的容器产品。
Docker 分为 CE 和 EE 两大版本。CE 即社区版,免费支持周期 7 个月;EE 即企业版,强调安全,付费使用,支持周期 24 个月。我们经常使用的版本当然是docker-ce啦!
我们在安装前可以参看官方文档获取最新的 Docker 支持情况,官方文档在这里:
https://docs.docker.com/install/
Docker 对于内核支持的功能,即内核的配置选项也有一定的要求(比如必须开启 Cgroup 和 Namespace 相关选项,以及其他的网络和存储驱动等)。
Docker CE 的安装请参考官方文档:
CentOS/Ubuntu安装Docker和Docker Compose,这里我们以 CentOS 7 作为演示。
环境准备:
由于 Docker-CE 支持 64 位版本的 CentOS 7 ,并且要求内核版本不低于 3.10,首先我们需要卸载掉旧版本的 Docker:
1 | $ sudo yum remove docker \ |
我们执行以下安装命令去安装依赖包:
1 | yum install -y yum-utils device-mapper-persistent-data lvm2 |
这里已经安装过了,所以提示Nothing to do。
这里采用的是阿里云
1 | yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo |
yum-config-manager命令作用是添加yum源。敲完命令之后大家执行一下命令去看一下有没有配置成功。
1 | cat |
更新yum软件包索引
1 | yum makecache fast |
列出并排序您存储库中可用的版本。此示例按版本号(从高到低)对结果进行排序。
1 | yum list docker-ce --showduplicates | sort -r |
1 | sudo yum install |
1 | ## 一键安装 docker |
具体可以参看 docker-install 的脚本:
https://github.com/docker/docker-install
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker CE 的 Edge 版本安装在系统中。更多详见CentOS/Ubuntu安装Docker和Docker Compose。
安装完成后,运行下面的命令,验证是否安装成功:
启动 Docker-CE:
1 | sudo systemctl start docker |
验证
1 | docker -v 或 docker version 或 docker --version |
返回 Docker 的版本相关信息,证明 Docker 安装成功:
我们通过最简单的 Image 文件 Hello World,感受一下 Docker 的魅力吧!
我们直接运行下面的命令,将名为 hello-world 的 image 文件从仓库抓取到本地:
1 | docker pull hello |
docker pull images 是抓取 image 文件,library/hello-world 是 image 文件在仓库里面的位置,其中 library 是 image 文件所在的组,hello-world 是 image 文件的名字。
抓取成功以后,就可以在本机看到这个 image 文件了:
1 | docker images |
我们可以看到如下结果:
现在,我们可以运行 hello-world 这个 image 文件:
1 | docker run hello |
我们可以看到如下结果:
输出这段提示以后,hello world 就会停止运行,容器自动终止。有些容器不会自动终止,因为提供的是服务,比如 Nginx镜像等。
我们从上面可以看出,Docker 的功能是十分强大的,除此之外,我们还可以拉取一些 Centos,Ubuntu,Alpine等镜像,在未来的教程中我们将会一一提到。
Docker 提供了一套简单实用的命令来创建和更新镜像,我们可以通过网络直接下载一个已经创建好了的应用镜像,并通过 Docker RUN 命令就可以直接使用。
当镜像通过 RUN 命令运行成功后,这个运行的镜像就是一个 Docker 容器啦。
容器可以理解为一个轻量级的沙箱,Docker 利用容器来运行和隔离应用,容器是可以被启动、停止、删除的,这并不会影响 Docker 镜像。
我们可以看看下面这幅图:
Docker 客户端是 Docker 用户与 Docker 交互的主要方式。当您使用 Docker 命令行运行命令时,Docker 客户端将这些命令发送给服务器端,服务端将执行这些命令。
Docker 命令使用 Docker API 。Docker 客户端可以与多个服务端进行通信。
我们将剖析一下 Docker 容器是如何工作的,学习好 Docker 容器工作的原理,我们就可以自己去管理我们的容器了。
这是使用阿里云docker镜像加速器。控制台地址:https://cr.console.aliyun.com,登录后,左侧-镜像工具-镜像加速器,加速器帮助页面会为你显示独立的加速地址,这个加速地址每个人的都不同。
针对Docker客户端版本大于 1.10.0 的用户
您可以通过修改daemon配置文件/etc/docker/daemon.json来使用加速器
1 | sudo mkdir -p /etc/docker |
看到这里,我相信各位读者朋友们应该已经对 Docker 基础架构熟悉的差不多了,我们还记得运行的第一个容器吗?
现在我们再通过 hello-world 这个例子来体会一下 Docker 各个组件是如何协作的。
容器启动过程如下:
具体过程可以看如下这幅演示图:
我们可以通过 Docker Images 可以查看到 hello-world 已经下载到本地:
我们可以通过 Docker Ps 或者 Docker Container ls 显示正在运行的容器,我们可以看到,hello-world 在输出提示信息以后就会停止运行,容器自动终止,所以我们在查看的时候没有发现有容器在运行。
我们把 Docker 容器的工作流程剖析的十分清楚了,我们大体可以知道 Docker 组件协作运行容器可以分为以下几个过程:
了解了这些过程以后,我们再来理解这些命令就不会觉得很突兀了,下面我来给大家讲讲 Docker 常用的一些命令操作吧。
我们可以通过 docker -h 去查看命令的详细的帮助文档。在这里我只会讲一些日常我们可能会用的比较多的一些命令。
docker version
docker info
docker--help
docker具体命令--help
systemctl start docker
systemctl stop docker
systemctl restart docker
systemctl status docker
systemctl enable docker
docker images
参数说明:
-a:列出所有镜像(含历史镜像)
-q:只显示镜像ID
-f:过滤
各个选项说明:
REPOSITORY:表示镜像的仓库源
TAG:镜像的标签版本号
IMAGE ID:镜像ID
CREATED:镜像创建时间
SIZE:镜像大小
执行命令,默认去docker hub中搜索
docker search 镜像名称
参数说明:
1 | -f:过滤 |
docker pull 镜像名称[:tag]
不加 tag 时,默认下载最新的镜像(即tag为latest)。
查看镜像/容器/数据卷所占的空间:
docker system df
docker rmi 镜像名称/ID
可以使用空格分隔,删除多个镜像:
docker rmi 镜像1 镜像2 镜像3
删除全部镜像:
docker rmi -f ${docker images -qa}
仓库名、标签都是的镜像,俗称虚悬镜像(dangling image)。
新建容器,需要先下载镜像 docker pull ubuntu
。
执行命令 docker run[OPTIONS]IMAGE[COMMAND][ARG...]
参数【OPTIONS】说明:
--name
:为容器指定一个名称-d
:后台运行容器并返回容器ID,也即启动守护式容器-i
:以交互模式(interactive)运行容器,通常与-t同时使用-t
:为容器重新分配一个伪输入终端(tty),通常与-i同时使用。也即启动交互式容器(前台有伪终端,等待交互)-e
:为容器添加环境变量-P
:随机端口映射。将容器内暴露的所有端口映射到宿主机随机端口-p
:指定端口映射-p指定端口映射的几种不同形式:
-p hostPort:containerPort
:端口映射,例如-p 8080:80-p ip:hostPort:containerPort
:配置监听地址,例如 -p 10.0.0.1:8080:80-p ip::containerPort
:随机分配端口,例如 -p 10.0.0.1::80-p hostPort1:containerPort1-p hostPort2:containerPort2
:指定多个端口映射,例如-p 8080:80 -p 8888:3306执行命令,以交互方式启动ubuntu镜像
1 | docker run |
参数说明:
-i: 交互式操作。-t: 终端。ubuntu : ubuntu 镜像。/bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。要退出终端,直接输入 exit:
两种方式退出交互模式:
exit;
run进去容器,exit退出,容器停止ctrl+P+Q
run进去容器,ctrl+p+q退出,容器不停止docker ps [OPTIONS]
常用参数说明:
-a:列出当前所有正在运行的容器+历史上运行过的
-l:显示最近创建的容器。-n:显示最近n个创建的容器。-q:静默模式,只显示容器编号。
大部分情况下,我们系统docker容器服务是在后台运行的,可以通过-d指定容器的后台运行模式:
docker run -d 容器名
注意事项:如果使用 docker run-d ubuntu
尝试启动守护式的ubuntu,然后 docker ps-a
进行查看, 会发现容器已经退出了。
因为Docker容器如果在后台运行,就必须要有一个前台进程。容器运行的命令如果不是那些一直挂起的命令(例如 top
、 tail
),就会自动退出。
这个是docker的机制问题。比如你的web容器,我们以nginx为例,正常情况下,我们配置启动服务只需要启动响应的service即可。例如service nginx start但是这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后,会立即自杀因为他觉得他没事可做了。所以最佳的解决方案是,将你要运行的程序以前台进程的形式运行,常见就是命令行模式,表示还有交互操作。
容器内文件拷贝到宿主机:
docker cp 容器ID:容器内路径 目的主机路径
宿主机文件拷贝到容器中:
docker cp 主机路径 容器ID:容器内路径
export
:导出容器的内容流作为一个tar归档文件(对应import命令);
import
:从tar包中的内容创建一个新的文件系统再导入为镜像(对应export命令);
示例:
1 | # 导出 |
docker commit
提交容器副本使之成为一个新的镜像。
docker 启动一个镜像容器后, 可以在里面执行一些命令操作,然后使用docker commit将新的这个容器快照生成一个镜像。
1 | docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[tag] |
Docker挂载主机目录,可能会出现报错:cannot open directory.:Perissiondenied。
解决方案:在命令中加入参数 --privileged=true
。
CentOS7安全模块比之前系统版本加强,不安全的会先禁止,目录挂载的情况被默认为不安全的行为,在SELinux里面挂载目录被禁止掉了。如果要开启,一般使用 --privileged=true
,扩大容器的权限解决挂载没有权限的问题。也即使用该参数,容器内的root才拥有真正的root权限,否则容器内的root只是外部的一个普通用户权限。
卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过UnionFS,提供一些用于持续存储或共享数据。
特性:卷设计的目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
特点:
运行一个带有容器卷存储功能的容器实例:
1 | docker run -it --privileged=true -v 宿主机绝对路径目录:容器内目录[rw | ro] 镜像名 |
可以使用docker inspect查看容器绑定的数据卷。
权限:
容器卷的继承:
1 | # 启动一个容器 |
docker start容器ID或者容器名
docker restart容器ID或容器名
docker stop容器ID或容器名
docker kill容器ID或容器名
docker rm容器ID或容器名
docker rm-f容器ID或容器名
docker rm-f ${docker ps-a-q}
或者 docker ps-a-q|xargs docker rm
docker logs容器ID或容器名
,更多查看容器日志使用技巧,docker logs 查看日志docker inspect容器ID或容器名
dockerexec-it容器ID bashShell
docker attach容器ID
docker exec 和 docker attach 区别:
如果有多个终端,都对同一个容器执行了 docker attach,就会出现类似投屏显示的效果。一个终端中输入输出的内容,在其他终端上也会同步的显示。
Alpine介绍
社区开发的面向安全应用的轻量级Linux发行版,Alpine 的意思是“高山的”,比如 Alpine plants高山植物,Alpine skiing高山滑雪、the alpine resort阿尔卑斯山胜地。
Alpine Linux 网站首页注明“Small!Simple!Secure!Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.”概括了以下特点:
1、小巧:基于Musl libc和busybox,和busybox一样小巧,最小的Docker镜像只有5MB;
2、安全:面向安全的轻量发行版;
3、简单:提供APK包管理工具,软件的搜索、安装、删除、升级都非常方便。
4、适合容器使用:由于小巧、功能完备,非常适合作为容器的基础镜像。
1 | docker search nginx |
1 | docker pull nginx |
1 | docker images |
nginx镜像不到24M。
1 | docker run -d --name nginxd -p 80:80 nginx:alpine |
1 | docker ps |
1 | curl |
127.0.0.1是本地回环ip,就是本机啦,可以用localhost代替。
docker exec -it 容器名称/容器ID/容器ID简写
1 | docker exec -it nginxd ash |
alpine是轻量级的操作系统,默认没有安装whereis,这是使用find来查找nginx安装目录。
验证:
1 | curl |
前面我们已经提到了 Docker 的一些基本概念。以 CTF 的角度来看,我们可以去使用 Dockerfile 定义镜像,依赖镜像来运行容器,可以去模拟出一个真实的漏洞场景。
因此毫无疑问的说, Dockerfile 是镜像和容器的关键,并且 Dockerfile 还可以很轻易的去定义镜像内容,说了这么多,那么 Dockerfile 到底是个什么东西呢?
Dockerfile 是自动构建 Docker 镜像的配置文件,用户可以使用 Dockerfile 快速创建自定义的镜像。Dockerfile 中的命令非常类似于 Linux 下的 Shell 命令。
我们可以通过下面这幅图来直观地感受下 Docker 镜像、容器和 Dockerfile 三者之间的关系:
我们从上图中可以看到,Dockerfile 可以自定义镜像,通过 Docker 命令去运行镜像,从而达到启动容器的目的。Dockerfile 是由一行行命令语句组成,并且支持已 # 开头的注释行。
一般来说,我们可以将 Dockerfile 分为四个部分:
下面是一段简单的 Dockerfile 的例子:
1 | FROM python:2.7 |
我们可以分析一下上面这个过程:
这个例子是启动一个 Python Flask App 的 Dockerfile(Flask 是 Python 的一个轻量的 Web 框架),相信大家从这个例子中能够稍微理解了 Dockfile 的组成以及指令的编写过程。
根据上面的例子,我们已经差不多知道了 Dockerfile 的组成以及指令的编写过程,我们再来理解一下这些常用命令就会得心应手了。
由于 Dockerfile 中所有的命令都是以下格式:INSTRUCTION argument ,指令(INSTRUCTION)不分大小写,但是推荐大写和 SQL 语句是不是很相似呢?下面我们正式来讲解一下这些指令集吧。
FROM 是用于指定基础的 images ,一般格式为 FROM or FORM :。
所有的 Dockerfile 都应该以 FROM 开头,FROM 命令指明 Dockerfile 所创建的镜像文件以什么镜像为基础,FROM 以后的所有指令都会在 FROM 的基础上进行创建镜像。
可以在同一个 Dockerfile 中多次使用 FROM 命令用于创建多个镜像。比如我们要指定 Python 2.7 的基础镜像,我们可以像如下写法一样:
1 | FROM python |
MAINTAINER 是用于指定镜像创建者和联系方式,一般格式为 MAINTAINER。
这里我设置成我的 ID 和邮箱:
1 | MAINTAINER |
COPY 是用于复制本地主机的(为 Dockerfile 所在目录的相对路径)到容器中的。
当使用本地目录为源目录时,推荐使用 COPY 。一般格式为 COPY。
例如我们要拷贝当前目录到容器中的 /app 目录下,我们可以这样操作:
1 | COPY |
WORKDIR 用于配合 RUN,CMD,ENTRYPOINT 命令设置当前工作路径。
可以设置多次,如果是相对路径,则相对前一个 WORKDIR 命令。默认路径为/。一般格式为 WORKDIR /path/to/work/dir。
例如我们设置 /app 路径,我们可以进行如下操作:
1 | WORKDIR |
RUN 用于容器内部执行命令。每个 RUN 命令相当于在原有的镜像基础上添加了一个改动层,原有的镜像不会有变化。一般格式为 RUN。
例如我们要安装 Python 依赖包,我们做法如下:
1 | RUN pip install |
EXPOSE 命令用来指定对外开放的端口。一般格式为 EXPOSE[…]。
例如上面那个例子,开放5000端口:
1 | EXPOSE |
ENTRYPOINT 可以让你的容器表现得像一个可执行程序一样。一个 Dockerfile 中只能有一个 ENTRYPOINT,如果有多个,则最后一个生效。
ENTRYPOINT 命令也有两种格式:
例如下面这个,我们要将 Python 镜像变成可执行的程序,我们可以这样去做:
1 | ENTRYPOINT |
CMD 命令用于启动容器时默认执行的命令,CMD 命令可以包含可执行文件,也可以不包含可执行文件。
不包含可执行文件的情况下就要用 ENTRYPOINT 指定一个,然后 CMD 命令的参数就会作为 ENTRYPOINT 的参数。
CMD 命令有三种格式:
一个 Dockerfile 中只能有一个 CMD,如果有多个,则最后一个生效。而 CMD 的 Shell 形式默认调用 /bin/sh -c 执行命令。
CMD 命令会被 Docker 命令行传入的参数覆盖:docker run busybox /bin/echo Hello Docker 会把 CMD 里的命令覆盖。
例如我们要启动 /app ,我们可以用如下命令实现:
1 | CMD |
当然还有一些其他的命令,我们在用到的时候再去一一讲解一下。
我们大体已经把 Dockerfile 的写法讲述完毕,我们可以自己动手写一个例子:
vi Dockerfile 开始编辑该文件,输入 i 开始编辑。以下是我们构建的 Dockerfile 内容:
1 | FROM nginx:alpine |
编辑完后按 esc 退出编辑,然后 :wq写入,退出。
我们在 Dockerfile 文件所在目录执行:
1 | docker build |
我们解释一下:
我们构建完成之后,使用 Docker Images 命令查看所有镜像,如果存在 REPOSITORY 为 Nginx 和 TAG 是 1.0 的信息,就表示构建成功。
接下来使用 docker run 命令来启动容器:
1 | docker run --name mynginx -d -p 8080:80 mynginx:1.0 |
这条命令会用 Nginx 镜像启动一个容器,命名为 mynginx ,并且映射了 8080 端口。
这样我们可以用浏览器去访问这个 Nginx 服务器:
或者 http://本机的 IP 地址:8080/,页面返回信息:
1.注册docker hub 账号
2.服务器上使用命令行登录
1 | docker login -u [账号名字] #登陆命令 |
看到Lgin Succeeded,就表示登录成功了。
3.构建镜像1)编写dockerfile
alpine官方默认镜像,没有vim命令,我们就基于此,创建一个镜像。
1 | vim |
2)构建dockerfile
docker build 命令:
1 | docker build -t myalpine:1.0 . |
1 | #查看刚构建的镜像 |
到这里,我们就制作好了我们自己的镜像,虽然它并没有什么用。这里我们再启动我们自己制作的镜像,进去看看我们写的dockerfile都生效了没有。注:不加标签默认是latest,所以docker run的时候要带上镜像标签。
1 | docker run |
1 | #查看dockerfile的构建过程 |
4.推送镜像至docker hub
需要先登录(根据提示输入用户名和密码即可),已登录请忽略。
1 | docker login |
看到Lgin Succeeded,就表示登录成功了。
官方文档要求,我们推送的镜像名字必须是YOURDOCKERHUB_ID/XXXX,所以我们需要给镜像换一个名字
docker tag local-image:tagname new-repo:tagname
docker push new-repo:tagname
1 | docker tag myalpine:1.0 dhdemo/myalpine:1.0 |
等了一会之后,就推送到docker hub
我们登陆docker hub就可以看到我们刚刚推送上去的镜像啦,这个镜像可是全世界人民都看得到的哦,是不是有点小激动呢!
国内镜像仓库有阿里云 、网易云等,这里以阿里云为例。
1.登入阿里云地址并创建镜像仓库
搜索 “容器镜像服务”,找到镜像仓库,创建镜像仓库
进入仓库查看创建后的镜像仓库信息
2.推送镜像到阿里云仓库
1 | docker login --username=你的用户名 registry.cn-beijing.aliyuncs.com |
说明:
username 你的用户名
http://registry.cn-beijing.aliyuncs.com 仓库地域,不同地区不一样,创建镜像时选择的是北京,这里就是北京地域的仓库地址。
登录成功提示如下:
docker tag [ImageId] http://registry.cn-beijing.aliyuncs.com/0101/myalpine:[镜像版本号]
docker push http://registry.cn-beijing.aliyuncs.com/0101/myalpine:[镜像版本号]
1 | docker tag 4220608af837 registry.cn-beijing.aliyuncs.com/0101/myalpine:1.0 |
Docker 仓库是用来包含镜像的位置,Docker提供一个注册服务器(Register)来保存多个仓库,每个仓库又可以包含多个具备不同tag的镜像。Docker运行中使用的默认仓库是 Docker Hub 公共仓库。
私有化部署镜像仓库,一般使用Docker Register、Nexus、Harbor,下面分别介绍一下。
Docker Registry是一个集中存储与分发镜像的服务。构建完 Docker镜像后,就可在当前宿主机上运行。但如果想要在其他机器上运行这个镜像,就需要手动复制。此时可借助Docker Registry来避免镜像的手动复制。一个 Docker Registry可包含多个 Docker仓库,每个仓库可包含多个镜像标签,每个标签对应一个 Docker镜像。这跟 Maven的仓库有点类似,如果把 Docker Registry比作Maven仓库的话,那么 Docker仓库就可理解为某jar包的路径,而镜像标签则可理解为jar包的版本号。
1.Registry本地私有仓库的搭建以及上传
1 | docker pull registry #拉取官方私有仓库容器使得其可以进行搭载 |
2.本地Reegistry镜像仓库证书加密
1 | yum install openssl11 -y #需要epel源将本地名令进行升级使其可以支持 |
1 | mkdir -p /etc/docker/certs.d/www.registry.org/ #建立存放证书的使得容器运行后自动读取证书 |
3.Registry私有仓库http加密1)用户名和密码生成
1 | mkdir auth #建立存放认证的目录 |
2)运行仓库
1 | docker run -d -p 443:443 --restart=always --name registry \ |
3)上传镜像到私服
1 | docker tag yakexi007/game2018 www.registry.org/game2048 #更改tag标签准备上传 |
1 | nexus默认只有maven和nuget的镜像,但是他本身支持各种私有镜像,如linux软件包镜像:apt、yum,还有语言库镜像如:npm、conada、pypi、go,当然也包括docker的镜像。 |
前置条件:已安装docker
1.Nexus安装及配置
1 | docker run --name nexus3 --restart=always -p 8085:8081 -p 8086:8086 -v ~/docker/nexus/data:/nexus-data --privileged=true -d sonatype/nexus3 |
其中8086端口是本文要使用的作为docker私有库的端口;
登录后,创建docker镜像。
镜像类型有点多,仔细观察
可以发现docker的镜像类型有三个:group、host、proxy,他们的关系
proxy:官方源的镜像代理
host:自己创建的一些源,一般是私有的不公开的,本文主要创建的就是host类型的私有镜像
group:对这些镜像进行分组
在创建docker库之前,需要做一个授权的设置,否则无法创建仓库
下面就开始创建docker的仓库了,本文选择的是host类型,下面是录入的表单界面
创建完查看仓库列表,多了刚才见的cs仓库
仓库创建完以后,就可以使用一个测试的docker镜像来执行docker push了。
说明:
1)因为我的nexus3也是通过docker容器创建的,容器所在的机器上的IP地址是172.17.0.1,那么172.17.0.1:8086就是nexus3下私有docker镜像的地址;
2)我现在已经自己做了一个镜像:172.17.0.1:8086/app:v1.0.1 说明:要通过docker tag 把172.17.0.1:8086作为镜像名称的前缀。
要先通过docker login登录,但是会有一个错误
1 | docker login -u admin 172.17.0.1:8086 |
解决这个错误需要如下处理
1.打开:vi /etc/docker/daemon.json
2.添加:”insecure-registries”:[“172.17.0.1:8086”]
3.重启:systemctl restart docker
再重新登录就可以成功了
1 | docker login -u admin 172.17.0.1:8086 |
2.上传镜像到私服
下面执行docker push
1 | docker push 172.17.0.1:8086/app:v1.0.1 |
在nexus3中查看push的镜像
Harbor 是由 VMware 公司中国团队为企业用户设计的 Registry server 开源项目,包括了权限管理 (RBAC)、LDAP、审计、管理界面、自我注册、HA 等企业必需的功能,同时针对中国用户的特点,设计镜像复制和中文支持等功能。
作为一个企业级私有 Registry 服务器,Harbor 提供了更好的性能和安全。提升用户使用 Registry 构建和运行环境传输镜像的效率。Harbor 支持安装在多个 Registry 节点的镜像资源复制,镜像全部保存在私有 Registry 中, 确保数据和知识产权在公司内部网络中管控。另外,Harbor 也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等。
前置条件:已安装docker、Docker Compose
1.harbor安装及配置
1 | wget https://github.com/goharbor/harbor/releases/download/v2.3.4/harbor-offline-installer-v2.3.4.tgz |
完成以上操作后,保存退出
1 | #执行安装程序,只安装harbor |
这时候已经安装完成了!
用docker-compose查看Harbor容器的运行状态
1 | [root@localhost harbor]# docker-compose ps |
Harbor访问测试
浏览器输入以下地址或者域名访问Harbor的Web界面,账号密码:admin/harbor12345
本机设置hosts解析
创建用户账号并测试上传镜像
创建test用户,新建项目mytest
test:Test123456
在该项目中添加成员:test,开发者角色
在docker客户端机器上设置/etc/docker/daemon.json文件,指向harbor服务器地址
修改为http方式访问(非加密方式)
1 | "insecure-registries":["www.myharbor.com"] |
2.上传镜像到Harbor服务器(私服)
1 | #打标签 |
在Harbor上可以看到我们上传的镜像。
3.Harbor配置HTTPS
生成TSL证书
1 | # 创建存放证书的临时目录 |
问题:原先harbor.yml文件中使用的端口是80,现在把80端口给注释掉,使用443端口了,但是harbor程序还在监听80端口,原因是使用的docker-compose.yml中nginx容器中有个如下的配置:
1 | ports: |
把80端口的注释掉,然后重启,就只监听443端口了
1 | docker-compose down |
4.上传镜像以https方式推送到私服
浏览器访问Harbor,通过HTTPS协议访问
通过Docker命令来访问
1 | # 在harbor主机或内网其他机器上 |
5.Harbor仓库忘记管理员密码如何重置
1)通过 dockerexec-it harbor-db/bin/bash
命令进入harbor-db容器内部
2)执行如下postgresql命令行
1 | psql -h postgresql -d postgres -U postgres # 这要输入默认密码:root123 |
3)然后切换到harbor所在的数据库,执行 \c registry
命令
4)执行SQL语句 select*fromharbor_user;
查看harbor_user表
5)例如修改admin的密码,修改为初始化密码 Harbor12345 修改好了之后再可以从web ui上再改一次。
1 | # 如下此方法不行 |
6)执行 \q
退出postgresql,再执行 exit 退出容器。
7)重启harbor服务,以配置中的默认密码进行登陆。
参考链接:
https://baike.baidu.com/item/%E8%99%9A%E6%8B%9F%E6%9C%BA/104440?fr=aladdin
https://baike.baidu.com/item/Docker%E5%AE%B9%E5%99%A8?fromModule=search-resultlemma
http://dockone.io/article/8350
https://blog.csdn.net/m061503020/article/details/125456520
https://blog.csdn.net/weixin_46560589/article/details/125184387
https://www.cnblogs.com/kire-cat/p/16471962.html
https://www.pudn.com/news/6273ff5c8dbc4529de3d3dd7.html
https://blog.csdn.net/u012632105/article/details/107110243
https://www.cnblogs.com/Netsharp/p/15459481.html
https://www.oschina.net/p/harbor?hmsr=aladdin1e1
https://www.cnblogs.com/hahaha111122222/p/16370495.html