屋里好黑,我在小黑屋里想你呢
转自:https://blog.csdn.net/deng624796905/article/details/86493330
Docker是什么?
在计算机技术日新月异的今天, Docker
在国内发展的如火如荼,特别是在一线互联网公司, Docker
的使用是十分普遍的,甚至成为了一些企业面试的加分项,不信的话看看下面这张图。
这是我在某招聘网站上看到的招聘 Java开发工程师 的招聘要求,其中有一条熟悉 docker
成为了你快速入职的加分项,由此可见熟悉 docker
在互联网公司的地位之重要。
当然对于我们 CTF选手 而言,熟悉 docker
可以快速搭建 CTF环境
,完美地还原比赛真实漏洞的场景,帮助我们快速提升自己。
市面上已经有很多优秀的教程,但是很多原理性的东西,笔者认为那些教程对初学者而言还是很难理解,感觉没有说清楚(笔者自己都觉得挺懵逼的),为了让初学者少走弯路,我将以我的学习经历以及作为一个 CTF选手 的角度,编写此套教程,来带大家去了解并熟练运用 docker
,祝愿各位读者朋友们学完此套教程后,在未来企业面试中能够多一项加分的筹码,能够帮助到大家,我觉得就很值了。
既然说了这么多, docker
到底是个什么东西呢?
我们在理解 docker
之前,首先我们得先区分清楚两个概念,容器和虚拟机。
可能很多读者朋友都用过虚拟机,而对容器这个概念比较的陌生。
我们用的传统虚拟机如 VMware
, VisualBox
之类的需要模拟整台机器包括硬件,每台虚拟机都需要有自己的操作系统,虚拟机一旦被开启,预分配给它的资源将全部被占用。每一台虚拟机包括应用,必要的二进制和库,以及一个完整的用户操作系统。
而容器技术是和我们的宿主机共享硬件资源及操作系统,可以实现资源的动态分配。容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行。
容器技术是实现操作系统虚拟化的一种途径,可以让您在资源受到隔离的进程中运行应用程序及其依赖关系。通过使用容器,我们可以轻松打包应用程序的代码、配置和依赖关系,将其变成容易使用的构建块,从而实现环境一致性、运营效率、开发人员生产力和版本控制等诸多目标。容器可以帮助保证应用程序快速、可靠、一致地部署,其间不受部署环境的影响。容器还赋予我们对资源更多的精细化控制能力,让我们的基础设施效率更高。通过下面这幅图我们可以很直观的反映出这两者的区别所在。
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux
容器解决方案。
而 Linux
容器是 Linux
发展出了另一种虚拟化技术,简单来讲, Linux
容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
Docker
将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker
,就不用担心环境问题。
总体来说, Docker
的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
Docker的优势
Docker相比于传统虚拟化方式具有更多的优势:
docker
启动快速属于秒级别。虚拟机通常需要几分钟去启动docker
需要的资源更少,docker
在操作系统级别进行虚拟化,docker
容器和内核交互,几乎没有性能损耗,性能优于通过Hypervisor
层与内核层的虚拟化docker
更轻量,docker
的架构可以共用一个内核与共享应用程序库,所占内存极小。同样的硬件环境,Docker
运行的镜像数远多于虚拟机数量,对系统的利用率非常高- 与虚拟机相比,
docker
隔离性更弱,docker
属于进程之间的隔离,虚拟机可实现系统级别隔离 - 安全性:
docker
的安全性也更弱。Docker
的租户root
和宿主机root
等同,一旦容器内的用户从普通用户权限提升为root权限,它就直接具备了宿主机的root权限,进而可进行无限制的操作。虚拟机租户root
权限和宿主机的root
虚拟机权限是分离的,并且虚拟机利用如Intel
的VT-d
和VT-x
的ring-1
硬件隔离技术,这种隔离技术可以防止虚拟机突破和彼此交互,而容器至今还没有任何形式的硬件隔离,这使得容器容易受到攻击 - 可管理性:
docker
的集中化管理工具还不算成熟。各种虚拟化技术都有成熟的管理工具,例如VMware vCenter
提供完备的虚拟机管理能力 - 高可用和可恢复性:
docker
对业务的高可用支持是通过快速重新部署实现的。虚拟化具备负载均衡,高可用,容错,迁移和数据保护等经过生产实践检验的成熟保障机制,VMware
可承诺虚拟机99.999%
高可用,保证业务连续性 - 快速创建、删除:虚拟化创建是分钟级别的,
Docker
容器创建是秒级别的,Docker
的快速迭代性,决定了无论是开发、测试、部署都可以节约大量时间 - 交付、部署:虚拟机可以通过镜像实现环境交付的一致性,但镜像分发无法体系化。
Docker
在Dockerfile
中记录了容器构建过程,可在集群中实现快速分发和快速部署
我们可以从下面这张表格很清楚地看到容器相比于传统虚拟机的特性的优势所在:
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为MB | 一般为GB |
性能 | 接近原生 | 弱于 |
系统支持量 | 单机支持上千个容器 | 一般是几十个 |
Docker的三个基本概念
从上图我们可以看到,Docker
中包括三个基本的概念:
Image
(镜像)Container
(容器)Repository
(仓库)
镜像是 Docker
运行容器的前提,仓库是存放镜像的场所,可见镜像更是 Docker
的核心。
Image (镜像)
那么镜像到底是什么呢?
Docker
镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
镜像(Image)
就是一堆只读层(read-only layer)
的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义。
从左边我们看到了多个只读层,它们重叠在一起。除了最下面一层,其它层都会有一个指针指向下一层。这些层是Docker
内部的实现细节,并且能够在主机的文件系统上访问到。统一文件系统 (union file system)
技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。
Container (容器)
容器 (container)
的定义和镜像 (image)
几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
由于容器的定义并没有提及是否要运行容器,所以实际上,容器 = 镜像 + 读写层。
Repository (仓库)
Docker
仓库是集中存放镜像文件的场所。镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry
(仓库注册服务器)就是这样的服务。有时候会把仓库 (Repository)
和仓库注册服务器 (Registry)
混为一谈,并不严格区分。Docker
仓库的概念跟 Git
类似,注册服务器可以理解为 GitHub
这样的托管服务。实际上,一个 Docker Registry
中可以包含多个仓库 (Repository)
,每个仓库可以包含多个标签 (Tag)
,每个标签对应着一个镜像。所以说,镜像仓库是 Docker
用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。我们可以通过<仓库名>:<标签>
的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest
作为默认标签.。
仓库又可以分为两种形式:
public
(公有仓库)private
(私有仓库)
Docker Registry
公有仓库是开放给用户使用、允许用户管理镜像的 Registry
服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry
。Docker
官方提供了 Docker Registry
镜像,可以直接使用做为私有 Registry
服务。当用户创建了自己的镜像之后就可以使用 push
命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull
下来就可以了。
我们主要把 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
,当然这个仓库是可以通过修改配置来指定的,甚至我们可以创建我们自己的私有仓库。
Dockerfile常用的指令
根据上面的例子,我们已经差不多知道了Dockerfile的组成以及指令的编写过程,我们再来理解一下这些常用命令就会得心应手了。
由于 Dockerfile
中所有的命令都是以下格式:INSTRUCTION argument
,指令 (INSTRUCTION)
不分大小写,但是推荐大写,和sql语句是不是很相似呢?下面我们正式来讲解一下这些指令集吧。
FROM
FROM
是用于指定基础的 images
,一般格式为 FROM <image>
or FORM <image>:<tag>
,所有的 Dockerfile
都用该以 FROM
开头,FROM
命令指明 Dockerfile
所创建的镜像文件以什么镜像为基础,FROM
以后的所有指令都会在 FROM
的基础上进行创建镜像。可以在同一个 Dockerfile
中多次使用 FROM
命令用于创建多个镜像。比如我们要指定 python 2.7
的基础镜像,我们可以像如下写法一样:
FROM python:2.7
MAINTAINER
MAINTAINER 是用于指定镜像创建者和联系方式,一般格式为 MAINTAINER <name>
。这里我设置成我的 ID
和邮箱:
MAINTAINER Angel_Kitty <angelkitty6698@gmail.com>
COPY
COPY
是用于复制本地主机的 <src>
(为 Dockerfile 所在目录的相对路径)到容器中的 <dest>
。
当使用本地目录为源目录时,推荐使用 COPY
。一般格式为 COPY <src><dest>
。例如我们要拷贝当前目录到容器中的 /app
目录下,我们可以这样操作:
COPY . /app
WORKDIR
WORKDIR
用于配合 RUN
,CMD
,ENTRYPOINT
命令设置当前工作路径。可以设置多次,如果是相对路径,则相对前一个 WORKDIR
命令。默认路径为/
。一般格式为 WORKDIR /path/to/work/dir
。例如我们设置/app
路径,我们可以进行如下操作:
WORKDIR /app
RUN
RUN
用于容器内部执行命令。每个 RUN
命令相当于在原有的镜像基础上添加了一个改动层,原有的镜像不会有变化。一般格式为 RUN <command>
。例如我们要安装 python
依赖包,我们做法如下:
RUN pip install -r requirements.txt
EXPOSE
EXPOSE
命令用来指定对外开放的端口。一般格式为 EXPOSE <port> [<port>...]
例如上面那个例子,开放5000端口:
EXPOSE 5000
ENTRYPOINT
ENTRYPOINT
可以让你的容器表现得像一个可执行程序一样。一个 Dockerfile
中只能有一个 ENTRYPOINT
,如果有多个,则最后一个生效。
ENTRYPOINT
命令也有两种格式:
ENTRYPOINT ["executable", "param1", "param2"]
:推荐使用的exec
形式ENTRYPOINT command param1 param2
:shell
形式
例如下面这个,我们要将 python
镜像变成可执行的程序,我们可以这样去做:
ENTRYPOINT ["python"]
CMD
CMD
命令用于启动容器时默认执行的命令,CMD
命令可以包含可执行文件,也可以不包含可执行文件。不包含可执行文件的情况下就要用 ENTRYPOINT
指定一个,然后 CMD
命令的参数就会作为ENTRYPOINT
的参数。
CMD
命令有三种格式:
CMD ["executable","param1","param2"]
:推荐使用的exec
形式。CMD ["param1","param2"]
:无可执行程序形式CMD command param1 param2
:shell 形式。
一个 Dockerfile
中只能有一个CMD
,如果有多个,则最后一个生效。而 CMD
的 shell
形式默认调用 /bin/sh -c
执行命令。
CMD
命令会被 Docker
命令行传入的参数覆盖:docker run busybox /bin/echo Hello
Docker
会把 CMD
里的命令覆盖。
例如我们要启动 /app
,我们可以用如下命令实现:
CMD ["app.py"]
当然还有一些其他的命令,我们在用到的时候再去一一讲解一下。
构建Dockerfile
我们大体已经把Dockerfile的写法讲述完毕,我们可以自己动手写一个例子:
-
mkdir static_web
-
cd static_web
-
touch Dockerfile
-
然后 vi Dockerfile 开始编辑该文件
-
输入 i 开始编辑
-
以下是我们构建的Dockerfile内容
-
``````````
-
FROM nginx
-
MAINTAINER Angel_Kitty <angelkitty6698@gmail.com>
-
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
-
``````````
-
编辑完后 按 esc 退出编辑
-
然后 :wq 写入 退出
我们在 Dockerfile
文件所在目录执行:
docker build -t angelkitty/nginx_web:v1 .
我们解释一下, -t
是为新镜像设置仓库和名称,其中 angelkitty
为仓库名, nginx_web
为镜像名, :v1
为标签(不添加为默认 latest
)
我们构建完成之后,使用 docker images
命令查看所有镜像,如果存在 REPOSITORY
为 nginx
和 TAG
是 v1
的信息,就表示构建成功。
接下来使用 docker run
命令来启动容器
docker run --name nginx_web -d -p 8080:80 angelkitty/nginx_web:v1
这条命令会用 nginx 镜像启动一个容器,命名为 nginx_web
,并且映射了 8080 端口,这样我们可以用浏览器去访问这个 nginx
服务器:http://localhost:8080/
或者 http://本机的IP地址:8080/,页面返回信息:
这样一个简单使用 Dockerfile
构建镜像,运行容器的示例就完成了!