笔记-docker

Docker 彻底释放了计算虚拟化的威力,极大提高了应用的维护效率,降低了云计算应用开发的成本!
让应用的部署、测试和分发都变得前所未有的高效和轻松。

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroupnamespace,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 版本开始,则进一步演进为使用 runCcontainerd

Docker 和 传统虚拟化方式的不同

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,再在该系统上运行所需应用进程
  • 而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。

优点:

  1. 更高效利用系统资源。容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高
  2. 更快启动时间。由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间
  3. 一致的运行环境。Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性
  4. 持续交付和部署。对开发和运维(DevOps)人员来说,最好的就是一次创建/配置,任意地方运行。Docker 可以定制应用镜像,开发人员通过 Dockerfile 来进行透明化镜像构建
  5. 更轻松的迁移。Docker 确保了执行环境的一致性
  6. 更轻松的维护和扩展。使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单,同时docker官方维护了一大批高质量的 官方镜像

基本概念

镜像 Image

操作系统分为 内核用户空间Docker 镜像Image),就相当于是一个 文件系统 提供用户空间支持。

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

镜像只是一个虚拟的概念,其实际体现是由一组文件系统组成。镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。

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

容器 Container

镜像容器,可以理解成面向对象程序设计中的 实例 一样的实例。容器可以被创建启动停止删除暂停

容器的实质是进程,不同于直接在宿主执行的进程,容器进程运行于自己独立的命名空间。

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

容器存储层的生存周期和容器保持一致,最佳实践的标准,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。文件的写入操作建议应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,使用volumn后,容器删除不会直接导致数据丢失。

仓库 Repository

一个 Docker Registry 中可以包含多个 仓库Repository):

每个仓库可以包含多个 标签Tag(通常,一个仓库会包含同一个软件不同版本的镜像)

每个标签对应一个镜像。可以通过 仓库名:标签 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。IMAGE ID是一个镜像的唯一标示。不排除一个镜像可以对应多个标签。

Quick Start

拉取镜像

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

  • 地址默认Docker Hub(docker.io)
  • docker.io的仓库名是两段式名称,<用户名>/<软件名> 用户名默认library官方

    运行 启动一个ubuntu:18.04容器

    docker run -it --rm ubuntu:18.04 bash

    列出镜像

    docker image ls
    所有镜像实际硬盘消耗 <= 列表中的镜像体积总和。因为 Docker 镜像是多层存储结构,可以继承、复用。因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。而Docker 使用 Union FS,相同的层只需要保存一份即可。

删除镜像

docker image rm [选项] <镜像1> [<镜像2> ...]

其中,<镜像> 可以是 镜像短 ID一般取前3个字符即可、镜像长 ID镜像名<仓库名>:<标签> 或者 镜像摘要DIGEST
删除行为分为两类,一类是 Untagged,另一类是 Deleted。
使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。首先需要做的是将满足我们要求的所有镜像标签都取消。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为。
当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。判断过程中 不会触发删除某层被其他仍在复用的中间层镜像,直到没有任何层依赖当前层时,才会真实的删除当前层。
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。当用到这个镜像的容器仍然存在(即使没有运行中)同样不可以删除该镜像。
配合docker image ls -q 来批量删除
docker image rm $(docker image ls -q redis)
例 删除所有仓库名为 redis 的镜像

定制一个web服务器

docker run --name webserver -d -p 80:80 nginx

nginx 镜像启动一个容器,命名为 webserver,并且映射了 80 端口。

使用 docker exec 命令进入容器,修改其内容

docker exec -it webserver bash

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

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

当运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像,即在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

docker commit \
    --author "xx <xx@gmail.com>" \
    --message "修改了默认网页" \
    webserver \
    nginx:v2

查看镜像的历史commit 记录

docker history nginx:v2

运行刚刚定制的镜像成容器,映射到81端口,打开浏览器访问localhost:81可以看到:

docker run --name web2 -d -p 81:80 nginx:v2

最后,在上例中,即使只改动了一个html文件,因为docker commit 命令的执行,导致很多无直接关系的文件被改动或添加,如果是更复杂的操作只会涉及更多无关内容被添加进来,最后导致镜像极为臃肿;docker commit 所有对镜像的操作都是黑箱操作,除了制作人其他都不知道执行过什么命令,也被称作黑箱镜像。

所以 不建议用这种方式定制镜像,请改用Dockerfile

使用Dockerfile定制镜像

相比docker commit的方式,有以下几个问题:

无法重复、镜像构建透明性的问题、体积臃肿。

Dockerfile 是一个文本文件,其内包含了一条条的 **指令(Instruction)**,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

一个 DockerfileFROM 是必备的指令,并且必须是第一条指令,指定 基础镜像

RUN 指令是用来执行命令行命令的。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式

… 等命令

构建镜像

在 Dockerfile 文件所在目录执行:docker build -t nginx:v3 .
指定了最终镜像的名称 -t nginx:v3
.指定了上下文路径 为当前目录

docker build [选项] <上下文路径/URL/->

docker build 的工作原理

Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具

Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

因此表面上各种的docker命令,其实使用的远程调用形式在服务端(Docker 引擎)完成。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。进而有个问题是:在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?引入 上下文的概念——

docker build 命令上下文路径(即该例中的.)后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

COPY ./package.json /app/

该命令是复制 上下文(build context) 目录下的 package.json。因此,COPY 这类指令中的源文件的路径都是相对路径

默认情况下,如果不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件作为 Dockerfile。一般大家习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。

其他构建镜像的方式
  • Url: git repo

    $ docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world

    命令指定了构建所需的 Git repo,并且指定分支为 master,构建目录为 /amd64/hello-world/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。

  • Url: 用给定的 tar 压缩包构建

    docker build http://server/context.tar.gz

    如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并自动解压缩,以其作为上下文,开始构建。

  • 标准输入:文本文件

    docker build - < Dockerfile

    这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情

  • 标准输入:非文本文件

    docker build - < context.tar.gz

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

Dockerfile指令

  • FROM 必填,指定base镜像

  • RUN

    执行命令行命令

  • CMD

    两种格式:

    • shell 格式:CMD <命令>,实际的命令会被包装为 sh -c 的参数的形式进行执行。如CMD echo $HOME 等于 CMD [ "sh", "-c", "echo $HOME" ]
    • exec 格式:CMD ["可执行文件", "参数1", "参数2"...] 并且必须是双引号 不可单引号。
    • 参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数

    Docker 不是虚拟机 是个进程:

    1. CMD指令就是用于指定默认的容器主进程的启动命令的。
    2. 所以容器内没有后台服务的概念,容器中的应用都应该以前台执行。如直接执行 nginx 可执行文件,并且要求以前台形式运行CMD ["nginx", "-g", "daemon off;"]
  • ENTRYPOINT

    目的和 CMD 一样,都是在指定容器启动程序及参数。当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:<ENTRYPOINT> "<CMD>"

  • COPY

    将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置

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

    COPY [--chown=<user>:<group>] ["<源路径1>","<源路径2>"... "<目标路径>"]
    源路径可以多个,可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则。

    <目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)

    使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。

    COPY --chown=55:mygroup files* /mydir/

  • ADD

    相比copy增加了一些功能

    • 源路径可以是url:Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600
    • 源路径可以是tar压缩文件:压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。

    最佳实践中,建议尽可能的使用copy,因为其语义明确,而且add的行为不一定清晰,此外ADD 指令会令镜像构建缓存失效 从而可能会令镜像构建变得比较缓慢。

    例:ADD --chown=10:11 files* /mydir/

  • ENV

  • ARG 构建参数

  • VOLUME 定义匿名卷

  • EXPOSE 暴露端口

  • WORKDIR 指定工作目录

  • USER 指定当前用户

  • HEALTHCHECK 健康检查

  • ONBUILD

  • LABEL 为镜像添加元数据

  • SHELL

常用命令行Command

官方DOC

  • docker pull

  • docker run

  • docker image ls 列出已经下载下来的镜像

  • docker image ls -a 默认只显示顶层镜像,加-a显示中间层镜像(无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像)在内的所有

  • docker image ls -f since=mongo:3.2 例子 查找在 mongo:3.2 之后建立的镜像

  • docker image ls -q --filter 配合 -q 产生出指定范围的 ID 列表,然后送给另一个 docker 命令作为参数,从而针对这组实体成批的进行某种操作的做法在 Docker 命令行使用过程中非常常见

  • 自定义搜索结果表格格式,用了 Go 的模板语法Go 是特别适合容器微服务架构的语言的原因之一。

    docker image ls --format "{{.ID}}: {{.Repository}}"
    
  • docker system df 便捷的查看镜像、容器、数据卷所占用的空间

  • docker image prune 删除所有虚悬镜像dangling-image(镜像仓库维护后 已经抛弃的镜像仓库名)

  • docker image rm [选项] <镜像1> [<镜像2> …] 删除本地镜像。

    Reference

https://yeasy.gitbook.io/docker_practice/
docker commands
Dockerfile RUN 、 CMD 、 ENTRYPOINT区别


   转载规则


《笔记-docker》 Ryan Who 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
重学vue3 重学vue3
emmmm 生命周期 模版语法插值{{xxx}}v-bind:attr接受js表达式 指令v-htmlv-once 参数v-bind:[attributeName]="url" 修
2021-07-09
下一篇 
前端网安攻防 详记 前端网安攻防 详记
点击劫持(click-Jacking)wiki 点击劫持(clickjacking)是一种在网页中将恶意代码等隐藏在看似无害的内容(如按钮)之下,并诱使用户点击的手段。举例来说,如用户收到一封包含一段视频的电子邮件,但其中的“播放”按钮并不
2021-06-06
  目录