1. 什么是Docker

  • Docker是基于Go语言实现的开源容器项目,诞生于2013年,最初发起者是dotCloud公司
  • 公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新

image.png
image.png

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

2. 为什么要用Docker

  • 更高效的利用系统资源:容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,对系统资源的利用率更高
  • 更快速的启动时间:秒级甚至毫秒级
  • 一致的运行环境、持续交付和部署、更轻松的迁移:开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 BUG 并未在开发过程中被发现
  • 更轻松的维护和扩展:分层存储以及镜像的技术,使得应用重复部分的复用更为容易。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制

3. 基本概念

3.1 概述

Docker 包括三个基本概念

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

理解了这三个概念,就理解了 Docker 的整个生命周期。

3.2 镜像

  • Image就相当于是一个根文件系统(Root File System)。比如官方镜像 ubuntu:18.04 就包含了完整的一套 ubuntu 18.04 最小系统的 root 文件系统
    • 根文件系统首先是内核启动时所挂载的第一个文件系统,内核代码、基本的初始化脚本和服务,加载到内存中去运行
  • Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)
  • 镜像不包含任何动态数据,其内容在构建之后也不会被改变
  • 分层存储:UFS,镜像由多层文件系统联合组成
    • Union File System:它可以把多个目录(也叫分支)内容联合挂载到同一个目录下,而目录的物理位置是分开的

3.3 容器

  • 镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的实例一样,镜像是静态的定义,容器是镜像运行时的实体。
    • 容器可以被创建、启动、停止、删除、暂停等。
  • 每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为——容器存储层。
  • 按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。
    • 所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

3.4 仓库

  • 镜像构建完成后,可以很容易的在当前宿主机上运行,如果需要在其它服务器上使用这个镜像,就需要一个集中的存储、分发镜像的服务——Docker Registry
  • 一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像
  • 通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。

3.5 Docker基本架构

Docker 采用了 C/S 架构,包括客户端和服务端。Docker 守护进程 (Daemon)作为服务端接受来自客户端的请求,并处理这些请求(创建、运行、分发容器)。
客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。
image.png
Docker 守护进程一般在宿主主机后台运行,等待接收来自客户端的消息。
Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker 守护进程交互。

4. 安装Docker

4.1 官方文档

Docker Docs

4.2 第三方优秀文档

Docker —— 从入门到实践

4.3 三大操作系统

Get Docker
Win比较麻烦,需要虚拟化的支持
一键安装脚本

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

5. 使用Docker

5.1 获取镜像

从 Docker 镜像仓库获取镜像的命令是 docker pull,其命令格式为:

  1. $ docker pull [选项] [Docker Registry地址[:端口号]/]仓库名[:标签]
  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号],默认地址是 Docker Hub(docker.io)
  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>
    • 对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像

5.2 查看镜像

要想列出已经下载下来的镜像,可以使用如下命令

  1. $ docker image ls
  2. $ docker images

列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间
所占用空间和在 Docker Hub 上看到的镜像大小不同
镜像体积总和并非是所有镜像实际硬盘消耗,由于 Docker 镜像是多层存储结构,存在继承、复用
镜像体积

  1. $ docker system df
  2. $ docker image rm

一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。
除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。

5.3 运行

有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(exited)的容器重新启动。
因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。
当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从 registry 下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

演示:Orientation and setup

5.4 Dockerfile

可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,用以解决重复性构建、透明性、体积大小的问题。
基本格式

  1. # Comment
  2. INSTRUCTION arguments

比如,构建一个修改默认页面的Nginx镜像

  1. FROM nginx
  2. RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

构建指令

  • FROM:指定基础镜像,必备,并且是第一条指令
  • RUN:用于执行命令行命令的,在定制镜像时是最常用的指令之一
  • LABEL:为镜像添加元数据
  • EXPOSE:声明容器运行时提供服务的端口
  • ENV:设置环境变量,供下文使用
  • ADD:与 COPY 类似,但存在高级功能
  • COPY:从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置
  • WORKDIR:指定工作目录
  • VOLUME:指定某些目录挂载为匿名卷
  • CMD:指定默认的容器主进程的启动命令的

5.5 数据管理

image.png
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改会立马生效
  • 对数据卷的更新,不会影响镜像
  • 数据卷默认会一直存在,即使容器被删除

挂载主机目录
加载本机指定目录到容器的指定目录

5.6 网络配置

外部访问容器
可以通过 -P-p 参数来指定端口映射
当使用 -P 标记时,Docker 会随机映射一个端口到内部容器开放的网络端口
-p 则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器

容器互联
使用--link 或者自定义Docker网络
当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,它会在挂载到它的网口之间进行转发。

同时,Docker 随机分配一个本地未占用的私有网段中的一个地址给 docker0 接口。比如典型的 172.17.42.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
image.png

6. Docker Compose

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应用。
本章将介绍 Compose 项目情况以及安装和使用。

通过第一部分中的介绍,我们知道使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。
然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。

例如要实现一个 Web 项目,除了 Web 服务容器本身,往往还需要再加上后端的数据库服务容器,甚至还包括负载均衡容器等。
Compose 恰好满足了这样的需求。它允许用户通过一个单独的 docker-compose.yml 模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose 中有两个重要的概念:

  • 服务 (service):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。

6.1 安装

参考网页
Linux平台

  1. $ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  2. $ sudo chmod +x /usr/local/bin/docker-compose
  3. $ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

6.2 使用

对于 Compose 来说,大部分命令的对象既可以是项目本身,也可以指定为项目中的服务或者容器。如果没有特别的说明,命令对象将是项目,这意味着项目中所有的服务都会受到命令影响。

  1. $ docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]

相关指令

  • build:构建(重新构建)项目中的服务容器
  • config:验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因
  • down:此命令将会停止 up 命令所启动的容器,并移除网络
  • images:列出 Compose 文件中包含的镜像
  • logs:查看服务容器的输出
  • restart:重启项目中的服务
  • rm:删除所有(停止状态的)服务容器
  • start:启动已经存在的服务容器
  • stop:停止已经处于运行状态的容器,但不删除它
  • up:尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。链接的服务都将会被自动启动,除非已经处于运行状态。

6.3 搭建实例

WordPress

  1. version: '3.1'
  2. services:
  3. wordpress:
  4. image: wordpress:5.7
  5. restart: always
  6. ports:
  7. - 8080:80
  8. environment:
  9. WORDPRESS_DB_HOST: db
  10. WORDPRESS_DB_USER: exampleuser
  11. WORDPRESS_DB_PASSWORD: examplepass
  12. WORDPRESS_DB_NAME: exampledb
  13. volumes:
  14. - wordpress:/var/www/html
  15. db:
  16. image: mysql:5.7
  17. restart: always
  18. environment:
  19. MYSQL_DATABASE: exampledb
  20. MYSQL_USER: exampleuser
  21. MYSQL_PASSWORD: examplepass
  22. MYSQL_RANDOM_ROOT_PASSWORD: '1'
  23. volumes:
  24. - db:/var/lib/mysql
  25. volumes:
  26. wordpress:
  27. db:

NextCloud

  1. version: '3.1'
  2. volumes:
  3. nextcloud:
  4. db:
  5. services:
  6. db:
  7. image: mysql:5.7
  8. restart: always
  9. command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
  10. volumes:
  11. - db:/var/lib/mysql
  12. environment:
  13. - MYSQL_RANDOM_ROOT_PASSWORD='1'
  14. - MYSQL_PASSWORD=nextcloud
  15. - MYSQL_DATABASE=nextcloud
  16. - MYSQL_USER=nextcloud
  17. app:
  18. image: nextcloud:21
  19. restart: always
  20. ports:
  21. - 8090:80
  22. links:
  23. - db
  24. volumes:
  25. - nextcloud:/var/www/html
  26. environment:
  27. - MYSQL_PASSWORD=nextcloud
  28. - MYSQL_DATABASE=nextcloud
  29. - MYSQL_USER=nextcloud
  30. - MYSQL_HOST=db

7. Docker Swarm

Swarm mode 内置 K-V 存储功能,提供了众多的新特性,比如:具有容错能力的去中心化设计、内置服务发现、负载均衡、路由网格、动态伸缩、滚动更新、安全传输等。
Swarm 是使用 SwarmKit 构建的 Docker 引擎内置(原生)的集群管理和编排工具。
使用 Swarm 集群之前需要了解以下几个概念。

7.1 基本概念

节点
运行 Docker 的主机可以主动初始化一个 Swarm 集群或者加入一个已存在的 Swarm 集群,这样这个运行 Docker 的主机就成为一个 Swarm 集群的节点 (node) 。

节点分为管理 (manager) 节点和工作 (worker) 节点。

管理节点用于 Swarm 集群的管理,docker swarm 命令基本只能在管理节点执行(节点退出集群命令 docker swarm leave 可以在工作节点执行)。一个 Swarm 集群可以有多个管理节点,但只有一个管理节点可以成为 leader,leader 通过 raft 协议实现。

工作节点是任务执行节点,管理节点将服务 (service) 下发至工作节点执行。管理节点默认也作为工作节点。你也可以通过配置让服务只运行在管理节点。

来自 Docker 官网的这张图片形象的展示了集群中管理节点与工作节点的关系。
image.png

服务和任务
任务 (Task)是 Swarm 中的最小的调度单位,目前来说就是一个单一的容器。

服务 (Services) 是指一组任务的集合,服务定义了任务的属性。服务有两种模式:

  • replicated services 按照一定规则在各个工作节点上运行指定个数的任务。
  • global services 每个工作节点上运行一个任务

两种模式通过 docker service create--mode 参数指定。

来自 Docker 官网的这张图片形象的展示了容器、任务、服务的关系。
image.png

7.2 实战演示

演示:Getting started with swarm mode
初始化 manager

  1. $ docker swarm init --advertise-addr <MANAGER-IP>

增加节点

  1. $ docker swarm join-token worker
  2. $ docker node ls

部署服务

  1. $ docker service create --replicas 1 --name nginx-cluster nginx:1.19-alpine
  2. $ docker service inspect --pretty nginx-cluster
  3. $ docker service ps nginx-cluster

服务扩容

  1. $ docker service scale nginx-cluster=3

滚动升级

  1. $ docker service update --update-delay=10s nginx-cluster
  2. $ docker service update --image nginx:1.20-alpine nginx-cluster

路由网格

  1. $ docker service create --name=web --publish published=30000,target=80 --replicas=2 nginx:1.20-alpine
  2. $ docker service update --publish-add target=80 nginx-cluster

7.3 使用Stack启动服务

  1. version: "3"
  2. services:
  3. nginx:
  4. image: nginx:1.20-alpine
  5. ports:
  6. - 30000:80
  7. networks:
  8. - overlay
  9. deploy:
  10. mode: replicated
  11. replicas: 2
  12. visualizer:
  13. image: dockersamples/visualizer:latest
  14. ports:
  15. - "8080:8080"
  16. stop_grace_period: 1m30s
  17. volumes:
  18. - "/var/run/docker.sock:/var/run/docker.sock"
  19. deploy:
  20. placement:
  21. constraints: [node.role == manager]
  22. networks:
  23. overlay:
  1. $ docker stack deploy -c docker-compose.yml nginx-cluster

8. 安全

评估 Docker 的安全性时,主要考虑三个方面:

  • 由内核的命名空间和控制组机制提供的容器内在安全
  • Docker 程序(特别是服务端)本身的抗攻击性
  • 内核安全性的加强机制对容器安全性的影响

8.1 内核命名空间

当用 docker run 启动一个容器时,在后台 Docker 为容器创建了一个独立的命名空间和控制组集合。
命名空间提供了最基础也是最直接的隔离,在容器中运行的进程不会被运行在主机上的进程和其它容器发现和作用。
每个容器都有自己独有的网络栈,意味着它们不能访问其他容器的 sockets 或接口。
不过,如果主机系统上做了相应的设置,容器可以像跟主机交互一样的和其他容器交互。
当指定公共端口或使用 links 来连接 2 个容器时,容器就可以相互通信了(可以根据配置来限制通信的策略)。
从网络架构的角度来看,所有的容器通过本地主机的网桥接口相互通信,就像物理机器通过物理交换机通信一样。

8.2 控制组

控制组是 Linux 容器机制的另外一个关键组件,负责实现资源的审计和限制。
它提供了很多有用的特性;以及确保各个容器可以公平地分享主机的内存、CPU、磁盘 IO 等资源;当然,更重要的是,控制组确保了当容器内的资源使用产生压力时不会连累主机系统。
尽管控制组不负责隔离容器之间相互访问、处理数据和进程,它在防止拒绝服务(DDOS)攻击方面是必不可少的。尤其是在多用户的平台(比如公有或私有的 PaaS)上,控制组十分重要。例如,当某些应用程序表现异常的时候,可以保证一致地正常运行和性能。

8.3 服务端防护

运行一个容器或应用程序的核心是通过 Docker 服务端。Docker 服务的运行目前需要 root 权限,因此其安全性十分关键。
首先,确保只有可信的用户才可以访问 Docker 服务。
Docker 允许用户在主机和容器间共享文件夹,同时不需要限制容器的访问权限,这就容易让容器突破资源限制。
例如,恶意用户启动容器的时候将主机的根目录/映射到容器的 /host 目录中,那么容器理论上就可以对主机的文件系统进行任意修改了。
事实上几乎所有虚拟化系统都允许类似的资源共享,而没法禁止用户共享主机根文件系统到虚拟机系统。
最近改进的 Linux 命名空间机制将可以实现使用非 root 用户来运行全功能的容器。这将从根本上解决了容器和主机之间共享文件系统而引起的安全问题。
终极目标是改进 2 个重要的安全特性:

  • 将容器的 root 用户 映射到本地主机上的非 root 用户,减轻容器和主机之间因权限提升而引起的安全问题;
  • 允许 Docker 服务端在 非 root 权限(rootless 模式) 下运行,利用安全可靠的子进程来代理执行需要特权权限的操作。这些子进程将只允许在限定范围内进行操作,例如仅仅负责虚拟网络设定或文件系统管理、配置操作等。

8.4 能力机制

能力机制(Capability) 是 Linux 内核一个强大的特性,可以提供细粒度的权限访问控制。 Linux 内核自 2.2 版本起就支持能力机制,它将权限划分为更加细粒度的操作能力,既可以作用在进程上,也可以作用在文件上。
例如,一个 Web 服务进程只需要绑定一个低于 1024 的端口的权限,并不需要 root 权限。那么它只需要被授权 net_bind_service 能力即可。此外,还有很多其他的类似能力来避免进程获取 root 权限。
默认情况下,Docker 启动的容器被严格限制只允许使用内核的一部分能力。