Zohar's blog

Docker 稻壳学习笔记

DockerDocker

稻壳这群小东西可真的是神器,很久之前就幻想过有没有这么一款软件实现容器方式的虚拟化,直到后来遇见了它!

是什么

Docker 是一个开源的应用容器引擎,为轻量级容器提供系统级虚拟化、交互、独立、管理等服务。

容器可以看作是“软件 + 运行环境”,它是一个包含软件、依赖库及配置文件的文件。

属性 信息
logo docker
类型 应用容器引擎
主要功能 操作系统级虚拟化
编程语言 Go
许可协议 Apache Licenses 2.0

为什么

在 Docker 出现之前,我们时常要面对这些问题:

  1. 传统虚拟机存在问题

    为了不让软件对其他软件、对主机造成影响,同时方便对于不同软件的管理,我们常使用虚拟机对软件进行独立管理。

    传统虚拟机存在各种各样的缺陷:占用资源多、冗余步骤多、启动慢等等缺陷。

    而用户需要的是运行环境而非完整庞大的 OS,因此虚拟化的方式发生了变化,出现了以围绕应用而生的轻量级的容器虚拟化技术 LXC(LinuX Contains)。

  2. 开发与部署存在的矛盾 - 环境管理问题

    在开发与部署的过程中,常出现开发者的软件方案在开发过程中能正常运行,但部署阶段却无法运行或者是容易崩溃、无法升级等问题,问题出现的主要原因在于开发环境和生产环境的差异性。

    程序员主机的配置、版本等等无法与实际部署环境严格一致,必定会带来不兼容的问题。而各种各样的 APP 与层出不穷的 OS 之前的兼容性问题,也同样超出了开发者所能考虑的范围。

  3. 云时代的应用管理问题

    随着云服务时代的到来,应用迁移到云端的需求非常迫切。同时云端解决了应用的硬件管理问题,但仍留下应用间的管理问题,虽然 LXC 提供了部分解决方案,但其设计之初并无考虑到容器的迁移和标准化管理的问题,因此需要新的技术提供支持。

Docker 在以上问题爆发时应运而生,通过新的理念和更成熟高效的产品,成为一代神器。

怎么做

Docker 的理念

Build, ship, run any app anywhere!

软件交付环境如同一艘大邮轮,一个个的软件及其环境配置都封装在一个个集装箱内,互不干扰,独立运行。操作系统就是这一艘大邮轮,而软件及其环境配置就是这一个个集装箱,我们称之为容器。

Container_VS_VMS

同时,设置相应的仓库,邮轮可以将集装箱中的货物卸至该仓库,就能在需要某个应用的时候,直接从仓库中拉取相应货物,封装成集装箱就能运行了。这些由软件和配置环境包装而成的货物我们称之为镜像,而这个仓库我们称之为镜像仓库!

怎么样,和 Git 很像吧!没错,docker 仓库同样有共有仓库和私库。

这条可爱的鲸鱼就是我们的主机,一个个的集装箱就是容器,岸边的港口就是我们的仓库!

Docker 的核心做法就是:软件带环境安装,将软件和环境包装成镜像,视镜像即软件,这样,同一个软件就能做到像 java 般一次封装,到处运行!

Docker 镜像

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置环境。

Docker 镜像是由一层一层的文件系统组成,这种层级文件系统为 UnionFS(Union File System)

UnionFS

Docker 的 UnionFS 是由下而上的,docker 镜像由多个低耦合的只读镜像层组成

docker-image-layers

最底层是 bootfs(boot file system),主要包含启动加载器和内核,启动加载器主要是引导加载内核,加载完毕后,内核便存在在内存中,此时内存的使用权交给内核,系统卸载 bootfs。

在 bootfs 上一层是 rootfs(root file system),我们也称之为 base image layer,包含 linux 系统中的各主要部分 /dev/proc/bin/etc,组合而成各种 linux 发行版,如 Ubuntu、CentOS。

docker-image-Underlying-layers

对于一个精简的 OS,rootfs 可以做到很小,因为只需保留最基本的命令、工具和库即可,而 bootfs 直接使用宿主机内核,因为不同发行版的 bootfs 是一样的,可公用的。

接着再根据不同软件的需求,将各种类型的镜像层组成不同的完整镜像,多个镜像层堆叠,最终展现的完整视图是自上而下的。

因此,像下图这样的镜像,可能是由两层镜像构成,也可能是由更多层镜像构成,镜像的覆盖不仅仅可以是添加新的功能,也可能是底层镜像的模块更新。

image-variety-cases-of-hierarchy

docker 镜像每一层都是不可写的,那我们该怎样运行它呢?

我们在镜像的最上方加上一层可写层,它就变成了容器,因此我们可以称之为“容器层”,由上文我们指导,UnionFS 中上层文件是可以修改覆盖底层文件而不会对底层造成影响的,因此,可写的容器层就能做到修改镜像原有的文件!

docker-container-layer

镜像的各层是不可写的,我们通过容器层来覆盖修改底层文件,这就表示运行相同应用的不同容器间,彼此会有不同的配置,但是却在用着相同的镜像层!因此 UnionFS 一个极大的好处就是共享资源。对于不同容器,只要用到的镜像某些层是一样的,内存中只需要加载一份即可!镜像的每一层都可以共享。

docker-sharing-images

Dockerfile

Dockerfile 是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。它们简化了从头到尾的流程并极大的简化了部署工作。

Dockerfile

Dockerfile从FROM命令开始,紧接着跟随者各种方法,命令和参数。其产出为一个新的可以用于创建容器的镜像。

Dockerfile 特点

  1. 指令必须大写
  2. 注释为 #
  3. 每一条指令都会创建一个新的镜像层,并对镜像进行提交
  4. 绝大多数镜像的祖先镜像都是 scrath

Dockerfile 执行流程

  1. Docker 将基础镜像运行为一个容器
  2. 对容器执行 Dockerfile 的一条指令
  3. 执行类似 docker commit 的操作提交容器为新的镜像
  4. Docker 基于刚提交的镜像运行为一个新的容器
  5. 对容器执行 Dockerfile 的下一条指令

Dockerfile 指令

指令 描述
FROM IMAGE 必须,且位于文件首,指定基础镜像
MAINTAINER info 镜像维护者信息
RUN command 运行命令
EXPOSE port 暴露端口
WORKDIR path 默认工作目录
ENV key value 环境变量
ADD URL/path path 复制指定文件到容器中并解压
COPY path path 复制指定文件到容器中
VOLUME ["path", ...] 设置容器挂载数据卷
CMD command 指定容器启动时运行的命令,仅最后一个生效
ENTRYPOINT command 指定容器启动时运行的命令
ONBUILD command 触发器,容器被指定为基础镜像构建时触发

Docker 数据持久化

既然容器仅仅是在完整镜像上添加了一层容器层,那么我们该如何保存容器中的数据呢?

当然可以通过提交容器为新镜像的方式来保存数据:

docker commit -a="zohar" -m="save the data as a new version" 8ca288b45a22 zohar/testImage:1.2

但杀鸡焉用宰牛刀,我们只想要那一部分数据呢?拷贝到宿主机吗哈哈哈,这样同样太麻烦了。

我们不仅希望数据能够持久化,同样希望容器之间能够共享数据。为了能够保存数据,Docker 使用数据卷的方式。

容器数据卷

  • 说明:卷就是目录或文件,存在于一个或多个容器中,由 docker 挂载到容器上,不属于 UnionFS,完全独立于容器的生存周期,因此可以提供持续存储和共享数据的特性。

  • 特点:

    1. 数据卷可在容器之间共享或重用数据
    2. 卷中的更改可以直接生效
    3. 数据卷中的更改不会包含在镜像的更新中
    4. 数据卷的生命周期一直持续到没有容器使用它为止
  • 命令:

      docker run -it -v {hostPath}:{contianerPath}[:permission] {image}
    
      permission:
          'ro': read only
          '': default, read and write
    
  • 实质:数据卷实际上是将主机的卷通过绑定的挂载到容器内,容器即使删除,主机的卷依旧存在,主机的卷删除,容器所绑定的卷自然消失。同时可以设置容器对于卷的读写权限。当容器关闭时,主机修改卷里面的数据自然是可以的,而这个修改对于关闭了的容器依旧生效,当容器启动后,可以立即看到主机曾经所做的修改。

有什么用

Docker 带来了以下的好处:

  1. 更快的应用交付及部署
  2. 更快的升级和扩/缩容
  3. 更简单的运维
  4. 更高效的资源利用

怎么使用

docker 的使用基于 Client/Server 架构,客户端连接并操控本地或远程主机。一台主机可以接受多个客户端的连接,单个客户端同样可以连接多台主机

Docker架构

为主机配置阿里云镜像仓库

  1. 阿里云官网 -> 容器镜像服务 -> 镜像加速器 -> 加速器地址

    MirrorAccelerator

  2. 修改 /etc/docker/daemon.json,插入:

     {
     "registry-mirrors": ["https://********.mirror.aliyuncs.com"]
     }
    
  3. 重载配置 systemctl daemon-reload

  4. 重启服务 systemctl restart docker

容器启动流程

容器启动流程

Docker 命令

普通命令

  • 版本信息:docker version
  • 具体信息:docker info
  • 帮助信息:docker --help
  • 命令帮助:docker [COMMAND] --help

镜像命令

  • 列出镜像 docker images [OPTIONS] [REPOSITORY[:TAG]]

  • 搜索镜像 docker search [OPTIONS] TERM

  • 拉取镜像 docker pull [OPTIONS] NAME[:TAG|@DIGEST]

  • 删除镜像 docker rmi [OPTIONS] IMAGE [IMAGE...]

    删除所有 docker rmi -f ${docker images -aq}

  • 运行镜像 docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

  • 镜像历史 docker history [OPTIONS] IMAGE

  • 具体信息 docker image inspect [OPTIONS] IMAGE [IMAGE...]

容器命令

对于容器而言,name 优先级高于 id

  • 列出容器 docker ps [OPTIONS]

  • 停止容器 docker stop [OPTIONS] CONTAINER [CONTAINER...]

  • 启动容器 docker start [OPTIONS] CONTAINER [CONTAINER...]

  • 重启容器 docker restart [OPTIONS] CONTAINER [CONTAINER...]

  • 强制停止 docker kill CONTAINER [CONTAINER...]

  • 删除容器 docker rm [OPTIONS] CONTAINER [CONTAINER...]

    删除所有 docker rm -f ${docker ps -a -q}

  • 退出容器 exit(停止退出) 或 ctrl + p + q

  • 容器日志 docker logs [OPTIONS] CONTAINER

  • 容器进程 docker top CONTAINER

  • 容器细节 docker inspect [OPTIONS] NAME|ID [NAME|ID...]

  • 进入终端 docker attach [OPTIONS] CONTAINER

  • 执行命令 docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

  • 复制文件 docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-

打包命令

  • 提交镜像

      docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
    
      Options:
      -a, --author string    Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
      -c, --change list      Apply Dockerfile instruction to the created image (default [])
      -m, --message string   Commit message
      -p, --pause            Pause container during commit (default true)