Docker学习教程

Docker是一个开源引擎,可以轻松地为任何应用创建一个轻量级、可移植、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台,容器完全使用沙箱机制,相互之间没有任何接口。
文章来源:水月imooc · Docker 入门教程,整理:amomini

文章目录

  • 前言
  • 一、Docker 诞生背景
  • 二、准备工作
    • 1. CentOS8 安装 Docker
    • 2. 使用Linux命令管理 Docker 服务
    • 3. 背景知识:容器技术
    • 4. 背景知识:UnionFS联合文件系统
  • 三、基础知识
    • 1. Docker镜像仓库
    • 2. 使用Docker容器
    • 3. 基于已有容器构建自己的 Docker 镜像
    • 4. Docker 网络
    • 5. Docker 数据管理
    • 6. Docker 部署常用服务
  • 四、进阶知识
    • 1. Docker容器跨主机通信
    • 2. Dockerfile
    • 3. DockerCompose
    • 4. 可视化管理工具之 WeaveScope
    • 5. 容器监控平台概览
    • 6. 实战——搭建容器监控平台
    • 7. 容器编排工具与 Kuberneters


前言

Docker 项目诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目,基于 Google 公司推出的 Go 语言实现。该项目后来加入了 Linux 基金会,遵从了 Apache 2.0 协议,项目代码在 GitHub 上进行维护,开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux 或 Windows 机器上。
Docker学习教程_第1张图片


一、Docker 诞生背景

在软件产品的传统开发流程中,软件从开发到上线,从操作系统安装,到运行环境依赖,再到应用配置,需要消耗大量技术资源在很多琐碎无意义的运维工作上。随着虚拟机和云计算的普及,许多公司开始租用 AWS(亚马逊公司旗下云计算服务平台) 或 OpenStack 的虚拟机,用脚本在这些机器上自动化部署,但这个过程中会碰到云端虚拟机和本地环境不一致的问题, 解决起来依旧费时费力。
为了解决以上这些问题,PaaS 平台服务(Platform-as-a-service)诞生了。 PaaS 有应用托管的能力,提供与开发环境相同的运行环境。PaaS 会为每一个应用单独创建一个隔离环境,然后在隔离环境中启动这些应用进程,从而达到多个用户的应用互不干涉地在虚拟机里批量、自动运行起来的目的。

Docker 作为新一代的 PaaS 项目, 它脱胎于 Linux Container (LXC)技术,与先前的 PaaS 不同的是,Docker 把 Cgroups、Namespace 和 UnionFS 等一系列技术整合起来,极大地降低了容器技术的复杂度,提升了开发者的用户体验。Docker 公司定义了以容器镜像为标准的应用打包格式,并且建立 Docker Hub 服务进行镜像分发和协作。这些举措迅速创建了一个良好的社区和合作伙伴生态圈,其中包括 AWS、Google、Microsoft、IBM 等行业巨头和国内的众多公司。Docker具有以下优点:

  • 更好的安全性:如果服务器上启动了多个服务,这些服务可能会相互影响,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这种情况下,一旦服务器上的某一个服务被入侵,那么入侵者就可能访问到当前机器上所有的服务和文件,使用 Docker 则可以有效避免这种问题的发生。
  • 更高效地利用系统资源:由于容器不需要进行硬件虚拟化以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗以及文件存储速度,都要比传统虚拟机技术更高效。
  • 更快的启动时间:传统的虚拟机技术启动应用服务往往需要数分钟, Docker 容器由于直接运行于宿主内核,无需启动完整的操作系统,因此可以超快启动容器应用。节约了开发、测试和部署的时间。
  • 一致的运行环境:开发过程中有一个令人头疼的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 Bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性。
  • 持续交付和部署:使用 Docker可以通过定制应用镜像来实现持续集成、持续交付、部署。开发者可以通过 Dockerfile 来进行镜像构建,并结合持续集成系统进行集成测试,运维则可以直接在生产环境中快速部署该镜像,结合持续部署系统进行自动部署。
  • 更轻松地迁移:由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,其运行结果是一致的。用户可以很轻易地将应用迁移到另一个平台上,不用担心运行环境的变化。
  • 更轻松地维护和扩展:Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。Docker团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。
  • 对比传统虚拟机
    Docker学习教程_第2张图片

Docker通常用于如下场景:

  • web应用的自动化打包和发布;
  • 自动化测试和持续集成、发布;
  • 在服务型环境中部署和调整数据库或其他的后台应用;
  • 从头编译或者扩展现有的OpenShift或Cloud Foundry平台来搭建自己的PaaS环境。

二、准备工作

1. CentOS8 安装 Docker

Tips:CentOS8 的推荐的新包管理工具是 dnf,所以我们使用 dnf 来安装 Docker。 CentOS7 的版本只需要将 dnf 替换成 yum 即可。

Docker系统有两个程序:docker服务端和docker客户端。其中docker服务端是一个服务进程,管理着所有的容器。docker客户端则扮演着docker服务端的远程控制器,可以用来控制docker的服务端进程。大部分情况下,docker服务端和客户端运行在一台机器上。

  1. 添加软件源
    dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    
  2. 更新软件包索引并安装所需依赖
    # 更新索引
    dnf update
    # 安装所需依赖
    # 目前 Centos8 软件源中的 containerd.io 版本偏低,我们需要手动安装一个新版本,这样才能顺利安装 docker-ce 19.03,否则只能安装老版本的 docker-ce。
    # Centos7 用户可以跳过此步。
    dnf install -y https://download.docker.com/linux/centos/7/x86_64/edge/Packages/containerd.io-1.2.13-3.2.el7.x86_64.rpm
    
  3. 安装 Docker-ce
    dnf install -y docker-ce
    
  4. 配置网络与防火墙
    systemctl stop firewalld
    iptables -P INPUT ACCEPT
    iptables -F
    echo "net.ipv4.ip_forward = 1" | tee -a /etc/sysctl.conf
    sysctl -p
    systemctl start firewalld
    firewall-cmd --add-masquerade --permanent
    firewall-cmd --reload
    
  5. 将 Docker 设定为开机启用并启动服务
    # 设为开机启动
    systemctl enable docker
    # 启动Docker服务
    systemctl start docker
    
  6. 查看 docker 版本
    docker version
    

Docker学习教程_第3张图片

2. 使用Linux命令管理 Docker 服务

使用 systemctl 命令管理 Docker 服务

# 启动 Docker 服务
systemctl start docker
# 停止 Docker 服务
systemctl stop docker
# 设定 Docker 服务开机自启动
systemctl enable docker
# 取消 Docker 服务开机自启动
systemctl disable docker

3. 背景知识:容器技术

Docker 是一个开源的容器引擎,它的核心就是容器技术,而容器技术其实是一种基于虚拟化的沙盒技术(沙盒(sandbox)是一种安全机制,为运行中的程序提供隔离环境,通常是作为一些来源不可信、具破坏力或无法判定程序意图的程序提供实验之用)。

在计算机中,虚拟化是一种资源管理的技术,它将计算机的各种实体资源,如CPU、网络、内存及存储等,进行抽象后展示出来,使用户更方便地使用这些资源。

  • 平台虚拟化——针对计算机和操作系统的虚拟化,也是最常见的一种虚拟化技术,Hyper-V,Virtualbox,VMware 等产品都是应用这类虚拟化技术。
  • 资源虚拟化——对特定的计算机系统资源的虚拟化,例如对内存、网络资源等等。
  • 应用程序虚拟化——一个最典型的应用就是 JAVA,生成的程序在指定的 JVM 虚拟机中运行。

容器技术的实质:通过各种手段,修改、约束一个"容器(本质就是进程)”进程的运行状态,按照用户的意图“误导”它能看到的资源,控制它的边界,从而达到环境隔离或者说虚拟化的目的。

容器核心技术之Namespace——可以为容器提供系统资源隔离能力,假如一个容器中的进程需要使用 root 权限,出于安全考虑,我们不可能把宿主机的 root 权限给他。但是通过 Namespace 机制,我们可以隔离宿主机与容器的真实用户资源,谎称一个普通用户就是 root,骗过这个程序。
Docker学习教程_第4张图片

  • Mount Namespace——用来隔离文件系统的挂载点,不同的 Mount namespace 拥有各自独立的挂载点信息。在 Docker 这样的容器引擎中,Mount namespace 的作用就是保证容器中看到的文件系统的视图
  • UTS Namespace——用来隔离系统的主机名、hostname 和 NIS 域名
  • IPC Namespace——IPC 就是在不同进程间传递和交换信息。IPC Namespace 使得容器内的所有进程,进行的数据传输、共享数据、通知、资源共享等范围控制在所属容器内部,对宿主机和其他容器没有干扰。
  • PID Namespace——用来隔离进程的 ID 空间,使得不同容器里的进程 ID 可以重复,相互不影响。
  • Network Namespace——用来隔离网络,每个 namespace 可以有自己独立的网络栈,路由表,防火墙规则等。
  • user namespace——控制用户 UID 和 GID 在容器内部和宿主机上的一个映射,主要用来管理权限
  • Time namespace——允许操作系统为进程设定不同的系统时间
  • Cgroup Namespace——用来限制 CGroup 根目录下不同层级目录的权限,使得 CGROUP 根目录下的子目录的进程无法影响到父目录。

容器核心技术之 CGroup——Namespace 只能做到系统资源维度的隔离,无法做到硬件资源的控制,我们需要使用机制 Cgroup指定容器应用最大占用多少资源,Linux cgroups 的全称是 Linux Control Groups,它是 Linux 内核的特性,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(CPU、Memory、IO 等)。CGroup 机制中有以下几个基本概念:

  • task——任务,对应于系统中运行的一个实体,下文统称进程;
  • subsystem——子系统,具体的资源控制器(resource class 或者 resource controller),控制某个特定的资源使用;
  • cgroup——控制组,一组任务和子系统的关联关系,表示对这些任务进行怎样的资源管理策略;
  • hierarchy层级树,由一系列 CGroup 组成的树形结构,每个节点都是一个 CGroup ,CGroup 可以有多个子节点,子节点默认会继承父节点的属性。系统中可以有多个 hierarchy。

在 Linux 环境中,我们可以执行 ls -al /sys/fs/cgroup/ 查看当前系统的 Cgroup,目录中有若干个子目录,除了 systemd 目录,其他的一个子目录对应一个子系统,子系统功能如下所示。
Docker学习教程_第5张图片

4. 背景知识:UnionFS联合文件系统

计算机的文件系统是一种存储和组织计算机数据的方法,它使得对其访问和查找变得容易,文件系统使用文件和树形目录的抽象逻辑概念代替了硬盘和光盘等物理设备使用数据块的概念,用户使用文件系统来保存数据,不必关心数据实际保存在硬盘(或者光盘)的地址为多少的数据块上,只需要记住这个文件的所属目录和文件名。在写入新数据之前,用户不必关心硬盘上的那个块地址没有被使用,硬盘上的存储空间管理(分配和释放)功能由文件系统自动完成,用户只需要记住数据被写入到了哪个文件中。

文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型。 联合文件系统(Union File System)于2004 年由纽约州立大学开发,它可以把多个目录内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFS可以把只读和可读写文件系统合并在一起,具有写时复制功能,允许只读文件系统的修改可以保存到可写文件系统当中。
Docker学习教程_第6张图片
图中的容器是运行在 debian 容器环境中的 apache 网页应用,这个环境还提供了 emacs 编辑器功能。Docker 引入了层(layer)的概念,将 rootfs 的内容进行了分层管理,有系统层,运行库依赖层等等,可以一层接一层进行增量式挂载叠加。启动容器的时候通过 UnionFS 把相关的层挂载到一个目录,作为容器的根 rootfs。借助于 UnionFS,容器内部的更改都被保存到了最上面的读写层,而其他层都是只读的,这样中间的只读 rootfs 是可以被多个容器复用的。UnionFS 将文件的更新挂载到老的文件之上,而不去修改那些不更新的内容,这就意味着即使虚拟的文件系统被反复修改,也能保证宿主机空间占用保持一个较低水平。

  • Docker镜像——将中间只读的 rootfs 的集合称为 Docker 镜像,Docker 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。UnionFS 使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
  • Docker容器——与之前的容器在本质上没有区别,之前的容器更偏向抽象的技术概念,而受到在 Docker 管理约束的容器就是 Docker 容器,它会带有 Docker 产品的一些特征和功能。
  • Docker镜像 和 Docker容器 的关系——就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。从文件系统来看,Docker容器比Docker镜像多一层可读写的文件系统挂载层,从生命周期来看,Docker容器可以被创建、启动、停止、删除、暂停等

在 rootfs 的基础上,Docker 公司创新性地提出了使用 UnionFS,多个增量 rootfs 联合挂载一个完整 rootfs 的方案,通过“分层镜像”的设计,围绕 Docker 镜像,大家甚至可以协同工作,再加上 Docker 官方提供的镜像仓库,进一步减少了共享镜像的成本,这大大提高了开发部署的效率。

三、基础知识

1. Docker镜像仓库

目前 Docker 官方维护了一个公共仓库 Docker Hub,这是一个用于管理公共镜像的地方,我们可以找到各种镜像,也可以把我们自己的镜像推送上去,根据需要也可以搭建私有的镜像仓库,用于管理自己的镜像。容器镜像的操作是增量式的,每次镜像拉取的内容会比原本多个完整的操作系统的小很多,同时镜像只要发布成功,我们就能完全复现这个镜像的完整环境,这样就使得基于容器镜像的团队协作更加便捷。

如果没有账号,需要在 Docker Hub 上免费注册一个 Docker 账号,保存好账号密码用于进入 Linux 环境。

docker login 	# 登入
docker logout 	# 登出
docker search  	# 搜索 redis 镜像
docker pull  	# 下载 redis 镜像
# 默认会拉取 latest 版本,如果要指定版本,需要在镜像后标记版本,如 dockre pull redis:3.2
docker push <本地镜像名> 	# 上传镜像
docker image rm (镜像名称或id)> 		# 删除镜像
# Docker 会自动删掉无用、没有依赖的镜像层

用 docker search 命令可以搜索 Docker Hub 中的镜像,比如搜索redis,可以看到返回很多包含 redis 关键字的信息,返回的信息中从左到右依次是:镜像名字、描述、star关注数、是否官方创建、是否自动创建

Docker学习教程_第7张图片
根据是否是官方提供,可将镜像资源分为两类:

  • 一种是类似 centos 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字;
  • 还有一种类型,比如 bitnami/redis镜像,它是由 Docker 的用户bitnami创建并维护的,因此带有用户名称前缀。可以通过前缀 username/ 来指定使用某个用户提供的镜像。
# 搭建 Docker 私有仓库,例如使用Docker运行私有仓库服务registry
docker run -d -v /root/registry:/var/lib/registry -p 6000:5000 --restart=always --name registry registry
# 此命令会启动一个容器,设定本地的 /root/registry 目录存储上传的镜像。
# 配置/etc/docker/daemon.json,添加一行insecure-registries配置
"insecure-registries": ["127.0.0.1:6000"] # 127.0.0.1:6000是自定义镜像仓库服务的地址端口

# 测试私有镜像仓库
# 将redis:latest镜像名称改为127.0.0.1:6000/myredis:v1
# 127.0.0.1:6000/xxx是固定写法,与之前的地址对应
docker tag redis:latest 127.0.0.1:6000/myredis:v1
# 上传到私有仓库
docker push 127.0.0.1:6000/myredis:v1
# 查看私有仓库中的镜像
curl http://127.0.0.1:6000/v2/_catalog

# 删除镜像
docker rmi 127.0.0.1:6000/myredis:v1
# 把redis镜像也删除掉,这样可以清理掉相关的缓存层,使后面镜像的下载过程和结果更清楚
docker rmi redis
# 拉取镜像
docker pull 127.0.0.1:6000/myredis:v1

Docker学习教程_第8张图片

2. 使用Docker容器

Docker镜像是静态的,要使用它,就是以镜像为模板,创建并运行Docker容器应用,容器的操作使得容器在不同状态间转换。

Docker 容器的生命周期里分为五种状态,其分别代表着:

  • Created:容器已经被创建,容器所需的相关资源已经准备就绪,但容器中的程序还未处于运行状态;
  • Running:容器正在运行,也就是容器中的应用正在运行;
  • Paused:容器已暂停,表示容器中的所有程序都处于暂停状态;
  • Stopped:容器处于停止状态,占用的资源和沙盒环境都依然存在,只是容器中的应用程序均已停止;
  • Deleted:容器已删除,相关占用的资源及存储在 Docker 中的管理信息也都已释放和移除。
# 容器操作
# 1.创建容器(Created 状态),可以通过 `--name` 这个选项来配置容器名。
docker create --name busybox busybox

# 2.启动容器(Running状态)
docker start busybox
docker run --name busybox -d -i busybox # docker run 将 docker create 和 docker start 合并,在创建完成之后会直接启动
# -d 参数, Docker 在启动后将程序与控制台分离,使其进入后台运行。
# -i ( --interactive ) 表示保持终端输入流

# 3.管理容器
docker ps  	# 罗列出 Docker 中处于运行中的容器,加 -a 选项列出所有状态的容器

# 4.停止和删除容器
docker stop busybox  	# 停止正在运行的容器
docker rm busybox  		# 完全删除容器
# 正在运行中的容器默认情况下是不能被删除的,我们可以通过增加 -f 选项 强制停止并删除容器

# 5. 进入容器
# docker exec 进入容器的时候,两个选项不可或缺,即 -i 和 -t ( 合并为 -it )。
# -t ( --tty ) 表示启用一个伪终端,没有它无法看到 bash 内部的执行结果。
docker exec -it busybox sh  	# 在正在运行的容器中运行指定命令

# 6. 查看容器日志
docker logs busybox 	# 导出容器的日志信息

# 7. 查看容器的配置信息
docker inspect busybox# 提供详细的容器信息(配置信息等)

3. 基于已有容器构建自己的 Docker 镜像

Docker 镜像是多个基于 UnionFS 的镜像层依次挂载的结果,而容器的文件系统则是在以只读方式挂载镜像后增加的一个可读可写的文件系统复合而成。Docker 中提供了将容器中的可读可写环境持久化为一个镜像层的方法,即docker commit。 docker commit将容器修改的内容保存为镜像,可以理解为提交容器的更改。

  • 生成变更后的镜像

    # 重新创建一个busybox容器
    docker run --name busybox -d -i busybox
    # 进入容器
    docker exec -it busybox sh
    # 做些更改,新建个文件
    echo 'something' > something
    # 查看变更后的目录和文件
    ls
    # exit退出容器后,使用docker commit进行提交变更
    docker commit -m 'something' busybox
    # 执行 docker commit 将容器记录成镜像层的时候,会先暂停容器的运行,以保证容器内的文件系统处于一个相对稳定的状态,确保数据的一致性。
    

    Docker学习教程_第9张图片

  • 为镜像命名

    docker tag 6858b9b172b7(镜像id) mybusybox:1.0	# 为镜像取名的命令
    # 使用 docker tag能够对已有的镜像创建一个新的别名副本(旧的镜像与名称也会保留)
    docker tag mybusybox:1.0 mybusybox:latest
    # 也可以在提交变更的时候命名镜像
    docker commit -m 'something' busybox mybusybox2:latest
    

在这里插入图片描述

  • 迁移镜像:由于 Docker 是以集中的方式管理镜像的,所以在迁移之前,要先从 Docker 中取出镜像。docker save 命令可以将镜像输出,提供了一种保存镜像到 Docker 外部的方式。
    # 将 something:latest镜像,导出到something-latest.tar,-o 选项用来指定输出文件
    docker save -o ./something-latest.tar something:latest
    # 导入镜像,会延用原有的镜像名称
    docker load -i something-latest.tar
    
  • 从容器直接导出到镜像压缩包
    # 使用 docker export 命令可以直接导出容器,可以把它理解为 docker commit 与 docker save 的结合体
    docker export -o ./something-export.tar busybox
    # 使用 docker import并非直接将容器导入,而是将容器运行时的内容以镜像的形式导入。所以导入的结果其实是一个镜像,不是容器。
    docker import ./something-export.tar something:export
    

4. Docker 网络

容器与主机、容器与容器之间是互相隔离的。同时,我们可以通过配置 docker 网络,为容器创建完全独立的网络命名空间,或者使容器共享主机或者其他容器的网络命名空间,以应对不同场景的需要。四种常用的单宿主机网络模式:bridge 模式、host 模式、container 模式、none 模式。

  • bridge 模式——Docker 服务启动时,会自动在宿主机上创建一个 docker0 虚拟网桥 (Linux Bridge, 可以理解为一个软件虚拟出来的交换机),它会在挂载到它的网口之间进行转发。同时 Docker 随机分配一个可用的私有 IP 地址给 docker0 接口。如果容器使用默认网络参数启动,那么它的网口也会自动分配一个与 docker0 同网段的 IP 地址。
    Docker学习教程_第10张图片

    # 测试默认新建的容器是否能互相连通
    # 使用 busybox 镜像分别运行 b0,b1 两个容器
    docker run -d -t --name b0 busybox
    docker run -d -t --name b1 busybox
    # 查看两个容器的 IP 地址
    docker inspect --format '{
           { .NetworkSettings.IPAddress }}' b0 # 172.17.0.3
    docker inspect --format '{
           { .NetworkSettings.IPAddress }}' b1 # 172.17.0.4
    # 两个容器互相 ping 一下,证明它们的网络能连通
    

    Docker学习教程_第11张图片
    (1)自定义网桥:使用 docker network 相关命令自定义网桥,创建一个网桥 br0,设定网段是 172.71.0.0/24,网关为 172.71.0.1

    docker network create -d bridge --subnet '172.71.0.0/24' --gateway '172.71.0.1' br0
    # -d 指定管理网络的驱动方式,默认为bridge
    # --subnet 指定子网网段
    # --gateway 指定默认网关
    docker network ls  	# 查看当前的 docker 网络列表
    # 尝试在使用网桥 br0 来新建运行两个容器,并测试它们的连通性。
    docker run -d -t --network br0 --name b2 busybox
    docker run -d -t --network br0 --name b3 busybox
    docker exec b2 ping b3
    docker exec b3 ping b2
    # ping 测试过程中,输入的是容器名。在自定义网桥中,容器名会在需要的时候自动解析到对应的 IP,也解决了容器重启可能导致 IP 变动的问题。
    

    (2)端口映射访问容器:将宿主机的本地端口,与指定容器的服务端口进行映射绑定,之后访问宿主机端口时,会将请求自动转发到容器的端口上,实现外部对容器内网络服务的访问。

    # 创建名为 n0 的 nginx 容器,映射宿主机 8000 端口到它的 80 端口
    docker run -d -t -p 8000:80 --name n0 nginx
    # 指定的宿主机端口必须是未被占用的端口,否则操作会失败,且生成一个无法正常启动的容器 n0, 需要手动删除
    # 使用 docker port n0 查看 n0 的端口映射信息
    docker port n0
    # 如果需要绑定多个容器端口,可以连续使用 -p 参数多次指定
    docker run -d -t -p 8001:80 -p 8433:443 --name n1 ngin
    # 如果不想主动指定宿主机端口,可以使用 -P 参数,宿主机随机使用一个可用端口与容器端口进行映射
    docker run -d -t -P --name n2 nginx
    # 如果只想使用宿主机上特定的网口与容器进行映射
    docker run -d -t -p 宿主机映射网口的 IP 地址:8002:80 --name n3 nginx
    iptables -t nat -nL 	# 查看防火墙
    

    Docker学习教程_第12张图片
    打开浏览器,地址栏输入 http://localhost:8000 或 http:// 宿主机 IP:8000, 都能访问到 n0 的 nginx 服务
    Docker学习教程_第13张图片
    使用端口映射访问容器是常用的方式之一,它配置简单,通用性强,可以跨宿主机访问,基本覆盖个人日常使用的场景,但它仍有一些缺陷,端口转发方式的本质是通过配置 iptables 规则转发实现的,效率较低,如果容器的服务端口数量过多,需要配置较多的映射,占用大量宿主机端口,也不便于管理

  • host 模式——该模式下启动的容器,网络不再与宿主机隔离,访问容器服务可以直接使用访问宿主机对应的网络端口,且不需要端口转发。

    # 以 host 模式启动 nginx 的容器 h0
    docker run -d -t --network host --name h0 nginx
    

    启动成功后,在浏览器输入任意的本机地址,都可以打开 nginx 的默认页面,访问宿主机 80 端口就是访问容器的 80 端口,它们是一致的。
    host 模式下的容器与宿主机共享同一个网络环境,容器可以使用宿主机的网卡和外界通信,不需要转发拆包,性能好。但 host 模式也有非常严重的缺点:容器没有隔离的网络,会与其他服务竞争宿主机的网络,导致宿主机网络状态不可控,因此无法用在生产环境。

  • container 模式——可以使一个容器共享另一个已存在容器的网络,此时这两个容器共同使用同一网卡、主机名、IP 地址,容器间通讯可直接通过本地回环 lo 接口通讯。

    # 新运行一个 busybox 的容器 b1,设定它共享已存在的容器 b0 的网络
    docker run -d -t --network container:b0 --name b1 busybox
    # 端口转发设定以已存在的容器为准,出于安全和权限控制的角度,container 模式下运行的容器设定端口转发不生效。
    # 查看 b0,b1 的网络配置,验证两者的网络配置是否相同
    docker exec b0 ifconfig
    docker exec b1 ifconfig
    

    nginx 镜像自带的网络命令非常少,查看网络不方便,而 busybox 的网络命令比较齐全,使用 container 模式,可以快速解决这个问题。

    # 运行一个名为 n0 的 nginx 容器,再将它的网络共享给 busybox 容器 n0-net
    docker run -d -t --name n0 nginx
    docker run -d -t --network container:n0 --name n0-net busybox
    # 通过 localhost 访问 n0 的 web 服务,说明通过 container 模式下,共享的网络中的容器能够使用 lo 访问其他容器的服务。
    docker exec n0-net telnet localhost 80
    # 在交互中输入
    # GET /
    

    在 container 模式下的容器,会使用其他容器的网络命名空间,其网络隔离性会处于 bridge 桥接模式(最高)与 host 模式(最低)之间:当容器共享其他容器的网络命名空间,则在容器之间不存在网络隔离;而它们又与宿主机以及其他不在此共享中的容器存在网络隔离。

  • none 模式——容器有自己的网络命名空间,但不做任何配置,它与宿主机、与其他容器都不连通。

    # 新建一个 none 模式的 busybox 镜像 b0
    docker run -d -t --network none --name b0 busybox
    # 查看它的网络状态, 验证它仅有 lo 接口,不能与容器外通信
    docker exec b0 ip a
    

    Docker学习教程_第14张图片
    两个容器之间可以直连通信,但不通过主机网桥进行桥接。解决的办法是创建一对 peer 接口,分别放到两个容器中,配置成点到点链路类型即可。

    # 启动 2 个容器
    docker run -it -d --net=none --name=none1 busybox
    docker run -it -d --net=none --name=none2 busybox 
    # 找到这两个容器的进程号
    docker inspect -f '{
           {.State.Pid}}' none1 # 26613
    docker inspect -f '{
           {.State.Pid}}' none2 # 26681
    # 创建网络命名空间的跟踪文件
    sudo mkdir -p /var/run/netns
    sudo ln -s /proc/26613/ns/net /var/run/netns/26613
    sudo ln -s /proc/26681/ns/net /var/run/netns/26681
    # 创建一对 peer 接口,然后配置路由
     sudo ip link add A type veth peer name B
     
     sudo ip link set A netns 26613
     sudo ip netns exec 26613 ip addr add 10.1.1.1/32 dev A
     sudo ip netns exec 26613 ip link set A up
     sudo ip netns exec 26613 ip route add 10.1.1.2/32 dev A
    
     sudo ip link set B netns 26681
     sudo ip netns exec 26681 ip addr add 10.1.1.2/32 dev B
     sudo ip netns exec 26681 ip link set B up
     sudo ip netns exec 26681 ip route add 10.1.1.1/32 dev B
    

    Docker学习教程_第15张图片
    none 模式提供了一种空白的网络配置,方便用户排除其他干扰,用于自定义网络

5. Docker 数据管理

使用Docker时,产生的数据默认是保存到容器的UnionFS的读写层中的。那么,我们不妨思考下这两个问题:如果不启动容器却想访问数据该怎么办?容器被销毁或损坏,数据也就消失了,这合理吗?
是的,我想我们的答案应该是一样的,容器和数据不应该被绑定在一起。为此,Docker 提供了两类数据管理的方式:挂载宿主机目录或文件;使用数据卷。

通过挂载宿主机的目录或文件,可以在宿主机和容器间方便地共享数据,包括将提前准备好的配置文件挂载到容器,或者在开发调试过程中将代码移入 Docker 环境试运行等。

  • 挂载宿主机目录——将宿主机的目录挂载到容器内,容器与宿主机的目录可以实时共享。
    # 新建一个目录
    mkdir -p ~/mydir/tmp
    # 在目录中新建一个文件,填充内容 hello docker
    echo "hello docker" >  ~/mydir/tmp/text.txt 
    # 新建容器 busybox,将 /mydir/tmp 目录挂载到容器的 /tmp/ 目录,挂载宿主操作系统目录的参数是 -v <宿主机目录路径>:<容器目录路径>
    docker run -d -it --name busybox -v ~/mydir/tmp/:/tmp/ busybox
    # 查看容器对应的文件内容
    docker exec -it busybox cat /tmp/text.txt
    
    在这里插入图片描述
  • 挂载宿主机文件——将宿主机的文件挂载到容器内,实现文件的共享。
    # 新建一个容器 busybox2,将 /mydir/tmp/text.txt 文件挂载到容器的 /tmp/text.txt
    docker run -d -it --name busybox2 -v ~/mydir/tmp/text.txt:/tmp/text.txt busybox
    # 挂载宿主操作系统文件的参数是 -v <宿主机文件路径>:<容器文件路径>
    # 查看容器对应的文件内容
    docker exec -it busybox2 cat /tmp/text.txt
    
    在这里插入图片描述

使用数据卷的好处在于:我们不必自己维护一个外部路径挂载和存储的关系,借助Docker管理数据,并且通过语义化数据卷命名,更加方便直观地使用它来数据共享。

  • 使用数据卷——在使用数据卷进行挂载时,我们只需指定容器中被挂载的目录即可。mydata 是 Docker 数据卷的名称, 不是宿主机的目录或文件,为了避免混淆,指定当前路径下的目录或文件挂载方式时,源地址需要使用绝对路径
    # 挂载数据卷到容器
    docker run -d -it --name busybox -v mydata:/tmp busybox
    # 查看容器中数据卷挂载的信息
    docker inspect busybox
    
    # 一些常用的数据卷操作
    # 删除数据卷
    docker volume rm mydata
    # 手动创建数据卷
    docker volume create mydata
    # 删除那些没有被容器引用的数据卷
    docker volume prune
    
    Docker学习教程_第16张图片
    其中,Type代表这是挂载类型为volme卷,Name标识数据卷的名称是我们之前指定的mydata,Source标明在要挂载的数据卷在宿主机的真实路径为/var/lib/docker/volumes/mydata/_data,其中/var/lib/docker/volumes/是一个由Docker统一管理的存放路径,Destination表示挂载到 busybox 容器内的路径是/tmp。
  • 共用数据卷——通过挂载相同的数据卷,让多个容器能够操作数据卷中的数据,实现容器间的目录共享,数据卷的命名在 Docker 中是唯一的,让多个容器挂载同一个数据卷, 只需要指定同一个数据卷名称即可。挂载数据卷时,如果数据卷不存在,Docker 会自动创建,如果同名数据卷已经存在,则直接引用。

数据卷是受控存储,是由 Docker 引擎进行管理维护的。使用卷可以不必处理 uid、SELinux 等各种权限问题,Docker 引擎在建立卷时会自动添加安全规则以及根据挂载点调整权限,并且可以统一列表、添加、删除。另外,除了本地卷外,还支持网络卷、分布式卷。而挂载目录属于用户自行维护,必须手动处理所有权限问题,好处在于这种方式与宿主机的文件交换更方便一些。
数据卷实际上也是宿主机的一个目录,这个目录由 Docker 管理利用数据卷可以方便地将数据在多个容器中共享。按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷或者绑定挂载宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主发生读写,其性能和稳定性更高。

6. Docker 部署常用服务

Docker官方镜像仓库提供了众多高质量的镜像和使用文档,生态也非常活跃。这种简单的部署方式更接近获取软件的方式,在平时的开发测试中,个人的开发机上安装这些服务经常费时费力,到时寻找安装包,甚至可能还要编译源码,结果出现一些意想不到的问题。熟练掌握这种方法部署应用服务,可以快速且高效地获取一致的开发部署环境。

  1. 获取Docker 服务:部署时默认使用最新稳定的官方镜像版本,如有版本要求自行在镜像名后标注 :tag 即可。

    # 1. Redis:一个使用 ANSIC 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库
    # ①使用 Docker 启动 redis 服务,端口默认,使用host网络模式保障性能
    docker run --restart=always --network host -d -it --name myredis redis
    
    # ②将 redis 数据保存到宿主机目录
    mkdir -p ~/docker/redis/data # 新建宿主机目录
    docker run --restart=always --network host  -d -it -v ~/docker/redis/data:/data --name myredis redis
    
    # ③指定自己的配置文件,先将配置文件放到 ~/docker/redis/redis.conf
    docker run --restart=always --network host -d -it -v ~/docker/redis/redis.conf:/usr/local/etc/redis/redis.conf --name myredis redis redis-server /usr/local/etc/redis/redis.conf
    
    # 2. Nginx:异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存
    # ①使用 Docker 启动 redis 服务,端口默认,使用host网络模式保障性能
    # 使用自己的html目录,ro设定宿主机目录挂载到容器后,容器对此目录只读
    docker run --restart=always --network host -d -it -v ~/docker/nginx/html:/usr/share/nginx/html:ro --name mynginx nginx
    
    # ②指定自己的配置文件,先将配置文件放到 ~/docker/nginx/etc/nginx 目录下
    docker run --restart=always --network host -d -it -v ~/docker/nginx/html:/usr/share/nginx/html:ro -v ~/docker/nginx/etc/nginx:/etc/nginx --name mynginx nginx
    
    # 3. MySQL:一个开放源码的关系数据库管理系统
    # ①使用 Docker 启动 redis 服务,端口默认,使用host网络模式保障性能。
    # my-secret-pw 指定mysql的root用户密码
    docker run --restart=always --network host -d -it --name mymysql -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql
    
    # ②将mysql数据保存到宿主机目录
    mkdir -p ~/docker/mysql/data # 新建宿主机目录
    docker run --restart=always --network host  -d -it -v ~/docker/mysql/data:/var/lib/mysql --name mymysql -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql
    
    # ③指定自己的配置文件
    # 将配置文件放入 ~/docker/mysql/conf.d/
    docker run --restart=always --network host  -d -it -v ~/docker/mysql/conf.d/:/etc/mysql/conf.d  --name mymysql -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql
    
  2. 实战:使用 flask 调用 redis 容器应用
    通过一个flask web应用,实现主页访问计数功能,使用 redis 服务帮助我们实现计数统计功能。这个 redis 服务运行在 Docker 容器中。

    # 1. 在 CentOS 中安装 Python3 和flask框架,以及 Python 的 redis 客户端库
    # 安装 python3
    sudo dnf install -y python3
    # 安装flask与redis python客户端
    pip3 install redis flask --user
    
    # 2. 将下面的代码保存到~/test/app.py
    import flask
    import os
    from flask import Flask
    app = Flask(__name__)
    from redis import StrictRedis
    from redis import ConnectionPool
    
    # 指定redis服务地址
    REDIS_HOST = os.getenv('REDIS_HOST','127.0.0.1')
    # 指定redis端口号
    REDIS_PORT = os.getenv('REDIS_PORT', '6379')
    # 指定redis的数据库
    REDIS_DB = os.getenv('REDIS_DB', '0')
    # 指定redis的密码
    REDIS_PASSWORD = os.getenv('REDIS_PASSWORD', '12345678')
    
    class Redis:
        def __init__(self):
            self.cli = None
    
        def connect(self):
            pool = ConnectionPool(host=REDIS_HOST,
                                  port=REDIS_PORT,
                                  db=REDIS_DB,
                                  password=REDIS_PASSWORD)
            return StrictRedis(connection_pool=pool)
    
        def add_pv(self):
            self.connection.incr('pv', 1)
    
        def get_pv(self):
            count = self.connection.get('pv')
            return int(count)
    
        @property
        def connection(self):
            if self.cli:
                return self.cli
            else:
                self.cli = self.connect()
                return self.cli
    
    redis = Redis()
    @app.route('/')
    def index():
        redis.add_pv()
        return "

    Hello World, 本页已访问{}次。

    "
    .format(redis.get_pv())
    # 3. 在 ~/docker/redis/ 目录下,配置 redis.conf 文件
    cd ~/docker/redis
    # 获取官方提供的redis配置文件模板
    wget http://download.redis.io/redis-stable/redis.conf
    # 修改redis密码
    echo  "requirepass 12345678" >> redis.conf
    
    # 4. 配置文件修改好后,用它来配置 Docker 的 redis 容器应用
    docker run --restart=always --network host -d -it -v ~/docker/redis/redis.conf:/usr/local/etc/redis/redis.conf --name myredis redis redis-server /usr/local/etc/redis/redis.conf
    

    redis容器启动完成后,在 ~/test/ 目录下执行 python3 -m flask run,打开Linux系统中的浏览器,输入127.0.0.1:5000 访问这个 web 应用的主页,多次刷新查看效果。
    Docker学习教程_第17张图片

四、进阶知识

1. Docker容器跨主机通信

不同的容器部署在不同的宿主机,它们之间的通信方式有3种,桥接网络;端口映射;Docker网络驱动:1. Overlay,2. Macvlan。

  1. 桥接网络模式——多个宿主机位于同一个局域网,将每一个宿主机上的容器网络桥接到宿主机网络中,容器和宿主机同在一个局域网中互相通信。每台宿主机上的容器都直接从局域网中获取IP地址,容易导致IP地址冲突。
  2. 端口映射模式——将容器的服务所运行的端口映射到宿主机的某一个端口,然后其他的容器通过宿主机的对应端口进行访问。只要宿主机间能互相通信,容器之间就能通过宿主机的指定端口进行通信。但是这种方式需要对每一个容器都映射端口,而且宿主机的端口也有限。
  3. Docker 网络驱动之Overlay 网络——依赖额外的服务和配置,配置较为复杂。
  4. Docker 网络驱动之Macvlan 网络——是 Linux 的内核模块,是一种网卡虚拟化技术,功能是允许在同一个物理网卡上虚拟出多个网卡,通过不同的MAC地址在数据链路层进行网络数据的转发,一块网卡上配置多个 MAC 地址,每个interface 可以配置自己的 IP。Docker 的 macvlan 网络使用了 macvlan 驱动,在物理网络拓扑结构上看,每张虚拟网卡都是一个单独的网口。
    # 需要两台装好 Docker 服务的 Linux 虚拟机,并且虚拟机的网络要互通
    # 1.创建 macvlan 网络,在两个节点上都进行此操作
    docker network create -d macvlan --subnet=192.168.1.0/24 --gateway=192.168.1.1 -o parent=eth1 -o macvlan_mode=bridge macvlan_net 
    # macvlan 是 kernel 的模块名;
    # 192.168.2.0/24是宿主机所在网络的网段;
    # 192.168.2.1是网关;
    # eth1 是 Docker 宿主机(两台虚拟机)接入192.168.1.0/24 的物理网口
    
    # 2.创建容器并指定 IP
    docker run -it -d --net macvlan_net --ip=192.168.1.101 --name b1 busybox	#容器b1
    docker run -it -d --net macvlan_net --ip=192.168.1.102 --name b2 busybox	#容器b2
    
    # 3.测试容器通信
    docker exec -it b1 ping 192.168.1.102
    docker exec -it b2 ping 192.168.1.101
    

2. Dockerfile

Dockerfile 是 Docker 中用于定义镜像自动化构建流程的配置文件,在 Dockerfile 中,包含了构建镜像过程中需要执行的命令和其他操作。它可以明确设定 Docker 镜像的制作过程,帮助我们在容器体系下能够完成自动构建。优点:Dockerfile 的体积小,容易进行快速迁移部署;环境构建流程记录了 Dockerfile 中,能够直观的看到镜像构建的顺序和逻辑。

可以将 Dockerfile 理解为一个由上往下执行指令的脚本文件,当我们调用构建命令,通过给出的 Dockerfile 构建镜像时,Docker按照 指令顺序进行对应的操作。Dockerfile 的一般规范有:

  • 通过 Dockerfile 构建的镜像所启动的容器越快越好,这样可以快速启停增删容器服务(下面几条也是为第1条服务的);
  • 避免安装不必要的包,必要时使用多阶段构建;
  • 一个容器尽量只专注做一件事情;
  • 最小化镜像层数, 将重复功能的 RUN、COPY、ADD 等指令缩减合并, 但一定要保证 Dockerfile 可读性。
  1. 初识 Dockerfile

    # 在本地新建一个目录,编辑一个名为 Dockerfile 的文件
    mkdir -p ~/docker/nginx/
    cd ~/docker/nginx
    vim Dockerfile
    
    # Dockerfile文件内容 #
    # 构建一个基于ubuntu的docker定制镜像
    # 基础镜像
    FROM ubuntu
    # 镜像作者
    MAINTAINER amo amo123@domain.com
    # 执行命令
    ## 换成国内的软件源
    RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
    RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list 
    ## 安装nginx
    RUN apt update >/dev/null 2>&1 
    RUN apt install nginx -y >/dev/null 2>&1
    # 暴露对外端口
    EXPOSE 80
    
    # 在当前 Dockerfile 文件所在目录执行如下命令
    # --network=host 使用宿主机的网络连接代理容器的网络(在一些情况下,容器可能无法顺畅地连接外网)。
    # -t ubuntu-nginx:v1 指定生产的镜像名称为 ubuntu-nginx ,版本号为 v1。
    docker build --network=host -t ubuntu-nginx:v1 . 
    
  2. 镜像构建过程

    [user@centos8 nginx]$ docker build --network=host -t ubuntu-nginx:v1 .
    # 将上下文求发送给Docker引擎
    Sending build context to Docker daemon   2.56kB
    # 下载依赖的镜像
    Step 1/7 : FROM ubuntu
    latest: Pulling from library/ubuntu
    d51af753c3d3: Pull complete
    fc878cd0a91c: Pull complete
    6154df8ff988: Pull complete
    fee5db0ff82f: Pull complete
    Digest: sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7
    Status: Downloaded newer image for ubuntu:latest
    # 生成镜像 1d622ef86b13
     ---> 1d622ef86b13
    Step 2/7 : MAINTAINER my_name myemail@domain.com
    # 运行容器 4eec6e3094f0,在容器内运行上面的这个命令,标记维护者信息
     ---> Running in 4eec6e3094f0
    # 移除临时容器 4eec6e3094f0
    Removing intermediate container 4eec6e3094f0
    # 生成镜像 6679d1c204e3
     ---> 6679d1c204e3
    Step 3/7 : RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
    # 运行容器84d38c20d8c4,在容器内运行上面的这个命令,更换软件源记录
     ---> Running in 84d38c20d8c4
    # 移除临时容器 84d38c20d8c4
    Removing intermediate container 84d38c20d8c4
    # 生成镜像 83f29f7b055a
     ---> 83f29f7b055a
    Step 4/7 : RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
    # 运行容器 763e4493d93f, 在容器内运行上面的这个命令,更换软件源记录
     ---> Running in 763e4493d93f
    # 移除临时容器 763e4493d93f
    Removing intermediate container 763e4493d93f
    # 生成镜像 6297f20605d9
     ---> 6297f20605d9
    Step 5/7 : RUN apt update >/dev/null 2>&1
    # 运行容器 2665a7e5a2e9,在容器内运行上面的这个命令, 更新软件源缓存
     ---> Running in 2665a7e5a2e9
    # 移除临时容器 2665a7e5a2e9
    Removing intermediate container 2665a7e5a2e9
    # 生成镜像 fdfed940ca4d
     ---> fdfed940ca4d
    Step 6/7 : RUN apt install nginx -y >/dev/null 2>&1
    # 运行 容器 722a9a544643,在容器内运行上面的这个命令, 安装nginx
     ---> Running in 722a9a544643
    # 移除临时容器 722a9a544643
    Removing intermediate container 722a9a544643
    # 生成镜像 6ee76f7df9e5
     ---> 6ee76f7df9e5
    Step 7/7 : EXPOSE 80
    # 运行容器 a12ed3216ee0,在容器内运行上面的这个命令, 暴露80端口
     ---> Running in a12ed3216ee0
    # 移除临时容器 a12ed3216ee0
    Removing intermediate container a12ed3216ee0
    # 生成最终的镜像 7cf64279ba98
     ---> 7cf64279ba98
    Successfully built 7cf64279ba98
    # 将这个镜像标记命名 ubuntu-nginx 版本号v1
    Successfully tagged ubuntu-nginx:v1
    
  3. Dockerfile 的结构——主要包含四部分内容:基础镜像信息;维护者信息;镜像操作指令;容器启动时指令

  4. Dockerfile 指令详解
    (1)FROM:指明当前的镜像基于哪个镜像构建,用法:FROM <基础镜像:版本>,写上这一行指令后, Dockerfile 就可以构建镜像了,构建出的镜像未做任何修改、没有执行任何命令;
    (2)LABEL: 标记镜像信息,给镜像添加标签来标记镜像信息,每个标签一行,用法:LABEL <标签>=<描述>
    (3)MAINTAINER:指定镜像的作者信息,包含镜像的所有者和联系人信息,用法:MAINTAINER < NAME >
    (4)RUN : 运行命令,用法:RUN <命令>,为了保持 Dockerfile 文件的可读性以及可维护性,可以将复杂的RUN指令用反斜杠\分割成多行;
    】Dockerfile 构建一次之后,apt update 构建的镜像层就会缓存到本地,无论后面这个 Dockerfile 如何更新 apt install 的内容,apt update 镜像缓存也不会更新,这会导致安装的始终是第一次 Dockerfile构建时获取的软件源版本,除非手动删除这些缓存镜像层。
    解决方法是用 RUN apt-get update && apt-get install -y 确保 Dockerfiles 每次安装的都是包的最新的版本。
    (5)CMD:指定容器的默认执行的命令,用法:CMD [“可执行命令”, “参数1”, “参数2”…],docker run 没有指定其他命令时,CMD 指令会在容器执行。Dockerfile 中 CMD 只能有一个,如果写了多个 CMD,则以最后一个为准,ENTRYPOINT 与 CMD 类似,但不会被 docker run 指定的命令覆盖;
    (6)EXPOSE:指定容器将要监听的端口,用法:EXPOSE 端口号,启动容器时,如果使用自动映射 -P 或 --net=host 宿主机网络模式,容器中 EXPOSE 标记暴露的端口与宿主机网络会自动建立关联。如果没有指定 EXPOSE,使用 -p 手动指定端口映射参数也可以访问到容器内提供服务的端口,EXPOSE 显式地标明镜像开放端口,一定程度上提供了操作的便利,也提高了 Dockerfile 的可读性和可维护性;
    (7)ENV:定义环境变量,用法:ENV 环境变量名 环境变量值,通过 ENV 定义的环境变量,可以在后面的所有指令中使用,但是不能被 CMD 指令使用;通过 ENV 定义的环境变量,会永久的保存到该镜像创建的任何容器中,我们可以在 docker run 命令中通过 -e 标记来传递环境变量,启动的容器将会使用我们指定的变量值;ARG 指令与 ENV 作用基本一致,区别在于它仅在构建过程中使用,不会保留到容器中
    (8)COPY: 将宿主机文件拷贝到镜像中,用法:COPY <宿主机文件路径> <镜像文件路径>,除了指定完整的文件名外,COPY 命令还支持 Go 风格的通配符,对于目录而言,COPY 只复制目录中的内容而不包含目录自身。ADD和COPY用法类似,一般优先使用 COPY,COPY 只支持简单将本地文件拷贝到容器中,而 ADD 还有从压缩包中提取文件的功能;
    (9)VOLUME:指定目录为数据卷存储方式,为了防止运行时用户忘记将需要保存数据的目录挂载为卷,可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也不会向容器存储层写入大量数据,用法:VOLUME ["<路径1>", “<路径2>”…]
    (10)USER:指定运行容器时的用户名或 UID,用法:USER [:< group >] 或USER [:< GID >],当容器中运行的服务不需要管理员权限时,可以先建立一个特定的用户和用户组,为它分配必要的权限,然后通过该命令,使用 USER 切换到这个用户,也可以在docker run中使用-u参数指定用户执行命令,来替代默认设定,如果为了精确控制用户的id,也可以传入uid;
    (11)WORKDIR: 切换到镜像中的指定路径,在WORKDIR中需要使用绝对路径,如果镜像中对应的路径不存在会自动创建此目录,使用 WORKDIR 来替代 RUN cd && 的这类切换目录进行操作的指令。
    (12) ONBUILD:引用后构建指令, 用法:ONBUILD <其他指令>,ONBUILD 是一个特殊的指令,它后面跟的是其它指令,比如 RUN, COPY 等。
    Docker学习教程_第18张图片

    FROM alpine:latest	#构建镜像
    LABEL MYLABEL="First Test"	#给镜像添加标签
    MAINTAINER nickname@domain.com		#指定镜像的作者信息
    LABEL maintainer="[email protected]"		#也可以用LABEL来标记
    RUN echo 'text' > test.txt		#运行命令
    CMD ["echo" "hello"]		#指定默认执行的命令
    EXPOSE 8080		#指定将要监听的端口
    ENV PATH /usr/local/nginx/bin:$PATH		#定义环境变量
    COPY app.py  /web/		#将宿主机文件拷贝到镜像中
    VOLUME ["/data"]		#指定目录为数据卷存储方式
    USER www		#指定运行容器时的用户名或 UID
    WORKDIR /tmp
    COPY test.txt		#切换到镜像中的指定路径
    ONBUILD COPY . /tmp/		#引用后构建指令
    
  5. Dockerfile多阶段构建镜像:使用多阶段构建可以在一个 Dockerfile 中使用多个 FROM 语句。每个 FROM 指令都可以使用不同的镜像,并表示开始一个新的构建阶段。很方便的将一个阶段的文件复制到另外一个阶段,在最终的镜像中保留下需要的内容即可。在 Dockerfile 文件的同一目录新建一个新的构建脚本,命名为 Dockerfile-multi-stage :

    #从ubuntu镜像开始构建, 将第一阶段命名为`build`,在其他阶段需要引用的时候使用`--from=build`参数即可。
    FROM ubuntu AS build
    # 将宿主机的源码拷贝到镜像中
    COPY ./code/helloworld.c .
    # 安装依赖 并编译源码
    RUN apt update >/dev/null 2>&1 && \
        apt install -y gcc >/dev/null 2>&1 && \
        cc helloworld.c -o /usr/bin/helloworld
    
    # 第二阶段 从官方的python:alpine基础镜像开始构建
    FROM python:alpine
    # 镜像维护者信息
    MAINTAINER user .com>
    # 将第一阶段构建的helloworld 导入到此镜像中
    COPY --from=build /usr/bin/helloworld /usr/bin/helloworld
    # 安装flask 和 redis 的依赖
    RUN pip install flask redis >/dev/null 2>&1 
    # 设定镜像在切换到/app目录路径
    WORKDIR /app
    # 将源码导入到镜像
    COPY ./code/app.py .
    # 设定执行用户为user
    RUN useradd user
    USER user
    # 设定flask所需的环境变量
    ENV FLASK_APP app
    # 默认启动执行的命令
    CMD ["flask", "run", "-h", "0.0.0.0"]
    # 将flask的默认端口暴露出来
    EXPOSE 5000
    
    docker build -f Dockerfile-multi-stage -t myhello-multi-stage .		#执行 build 命令
    # 使用此镜像运行容器。使用--net=host,方便使用之前章节中部署的redis容器服务与之进行数据交换
    docker run -dit --net=host --name myhello-multi-stage myhello-multi-stage
    

3. DockerCompose

Docker Compose 是 Docker 官方编排项目,使用 Docker Compose 可以轻松、高效地管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具。在 Docker Compose 里,通过一个配置文件,将所有与应用系统相关的软件及它们对应的容器进行配置,之后使用 Docker Compose 提供的命令进行启动。

  1. 获取 Docker Compose:安装 Docker Compose 可以通过下面命令自动下载适应版本的 Compose,并为安装脚本添加执行权限。

    # 下载 docker-compose 
    wget https://github.com/docker/compose/releases/download/1.26.0/docker-compose-Linux-x86_64
    # 下载加速
    sudo curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.1/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
    # 移到 /usr/local/bin/docker-compose
    sudo mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose
    # 给 docker-compose 执行权限
    sudo chmod +x /usr/local/bin/docker-compose
    docker-compose -v		# 查看安装是否成功
    

    在这里插入图片描述

  2. 使用 Docker Compose
    (1)编写docker-compose.yml:进入dockerfiledir目录,新建redis目录,将之前部署redis容器用到的redis.conf拷贝到redis目录下,修改redis.conf中的bind 127.0.0.1为bind 0.0.0.0,以便其他容器访问此服务。然后新建文件docker-compose.yml,并将下面的内容写入到这个文件中。

    version: "3.8"
    services:
        cache:
          image: redis:6.0.5
          container_name: my_redis
          networks:
              - mynetwork
          volumes:
              - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
          command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
        app:
          build:
              context: .
              dockerfile: ./Dockerfile-multi-stage
          container_name: my_hello
          networks:
              - mynetwork
          environment:
              - REDIS_HOST=my_redis
          depends_on:
              - cache
          ports:
              - "5000:5000"
    networks:
      mynetwork:
        name: my_network
        driver: bridge
    

    (2)使用docker-compose up命令启动:docker-compose up 命令类似于 Docker 中的 docker run,它会根据 docker-compose.yml 中配置的内容,创建所有的容器、网络、数据卷等等内容,并将它们启动。与 docker run 一样,默认情况下 docker-compose up 会在前台运行,我们可以用 -d 选项使其“后台”运行,大多数情况都会加上 -d 选项。

    sudo docker-compose up -d	#启动
    

    (3)使用docker-compose命令重建:docker-compose 命令默认会识别当前控制台所在目录内的 docker-compose.yml 文件,而会以这个目录的名字作为组装的应用项目的名称。如果需要改变它们,可以通过选项 -f 来修改识别的 Docker Compose 配置文件- - build 用于执行重建服务镜像,更新镜像时使用

    sudo docker-compose -f ./docker-compose.yml up -d --build	#重建
    

    (4)使用docker-compose down命令停止:docker-compose down 命令用于停止所有的容器,并将它们删除,同时消除网络等配置内容,也就是几乎将这个 Docker Compose 项目的所有影响从 Docker 中清除。

  3. docker compose 配置文件指令详解:Docker Compose 的配置文件是一个基于 YAML 格式的文件,与 Dockerfile 采用 Dockerfile 作为默认文件名一样,Docker Compose 的配置文件也有一个缺省的文件名,就是 docker-compose.yml。Docker Compose 将所管理的容器分为三层,分别是工程(project)、服务(service)、容器(container),Docker Compose 定义了一个工程,一个工程包含多个服务,每个服务中定义了容器运行的镜像、参数、依赖,一个服务可包括多个容器实例

    # 指定配置文件的版本号
    version: "3.8"
    
    # 服务
    services:
    	# 服务名称cache
        cache:
         # 标明构建的镜像
          image: redis:6.0.5
          # 生成的容器名称
          container_name: my_redis
          # 指定网络
          networks:
              - mynetwork
          # 设定挂载
          volumes:
              - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
          # 容器启动后执行命令
          command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
        # 服务名称app 
        app:
          # 构建镜像
          build:
              # 指定上下文
              context: .
              # 指定构建脚本
              dockerfile: ./Dockerfile-multi-stage
          # 生成的容器名称
          container_name: my_hello
          # 指定网络
          networks:
              - mynetwork
          # 配置环境变量
          environment:
              - REDIS_HOST=my_redis
          # 指名容器依赖关系
          depends_on:
              - cache
          # 宿主机与容器端口映射
          ports:
              - "5000:5000"
    
    # 网络配置,与services在同一层级,注意书写格式对齐
    networks:
      # 标识自定义的网络,对应容器中指定的网络的名称
      mynetwork:
       # 在容器网路中展示的名称
        name: my_network
        # 网络驱动类型
        driver: bridge
    

    (1)version:版本号标识我们定义的 docker-compose.yml 文件内容所采用的版本,目前 Docker Compose 的配置文件已经迭代至了第三版,其所支持的功能也越来越丰富;
    (2)service: Docker Compose 把 service 作为配置的最小单元。使用时首先要为每个服务定义一个名称,用以区别不同的服务。虽然看上去每个 service 里的配置内容就像是在配置单个容器,但其实 service 代表的是一个应用集群的配置
    (3)image:类似于Dockerfile中的FROM指令。需要注意的是一定要注明镜像的具体版本号,不要使用latest版本(不标明版本即为latest),不同的执行时间latest指代的镜像会发生改变,准确执行唯一指定的容器才能使得整个工程正确运行;
    (4)build另一种指定镜像的方式,通过 build 配置能够直接采用 Dockerfile 构建镜像,定义构建的环境目录。如果通过这种方式指定镜像,那么 Docker Compose 先会执行镜像的构建,之后再通过这个镜像启动容器。Docker Compose 能够指定更多的镜像构建参数,例如 Dockerfile 的文件名,构建上下文,构建参数等等,此外,与image指令类似,希望Dockerfile的构建也是唯一可靠的;
    (5)container_name指定容器名称,如果不指定,会以<项目名称><服务名称><序号>,其中项目名称默认是当前工作目录的名字;
    (6)command:如果希望修改 Redis 的启动命令,加入配置文件以便对 Redis 服务进行配置,那么我们可以直接通过 command 配置来修改,command会覆盖镜像中的CMD指令
    (7)depends_on:使用 depends_on 这个配置项,列出这个服务所有依赖的其他服务即可,在 Docker Compose 为我们启动项目的时候,会检查所有依赖,按照依赖指定的启动顺序来依次启动容器;
    (8)volumes:在 Docker Compose 里定义文件挂载的方式与 Docker里也并没有太多的区别,使用 volumes 配置可以像 docker 的 -v 选项一样来指定外部挂载和数据卷挂载
    (9)ports:是用来定义端口映射的,可以利用它进行宿主机与容器端口的映射,这个配置与 docker 中 -p 选项的使用方法是近似的;
    (10)environment设置环境变量, 类似于 Dockerfile 的 ENV;
    (11)networks:网络也是容器间互相访问的桥梁,网络的配置对于多个容器组成的应用系统来说也是非常重要的。在 Docker Compose 里可以为整个应用系统设置一个或多个网络,要使用网络必须先声明网络,声明网络的配置同样独立于 services 存在,是位于根配置下的 networks 配置
    (12)networks下的name:作用类似于 container_name指令,mynetwork 创建的 docker 网络被命名为 my_network, 使用命令 docker network ls 可以查看到它。

4. 可视化管理工具之 WeaveScope

Weave Scope 这个工具可以自定义一些监控度量指标,也能够自动搜集、处理容器的信息,还带有直观的可视化界面,是一款一站式工具。它具有以下特点:直观的图形或表格模式;灵活的过滤和强大的搜索;实时展示应用和容器指标;支持多主机监管,支持 k8s。Weave Scope 监控展示了主机、容器、进程的众多常用数据和状态,并提供 WebUI 帮助我们进行基本的管理操作,并且在整个过程中不需要进行额外的配置,易于上手使用。同时,Weave Scope 提供插件和插件机制方便我们进行扩展,可以说,WeaveScope 是我们初期搭建容器监控管理系统的极佳选择。

  1. 安装部署:将 Weave Scope 安装到 Docker 容器的宿主机上,执行 docker ps 查看,发现运行的容器列表中新增了一个名为 weavescope 的容器,实际上 scope launch 命令也是借助 Docker 部署了 Weave Scope 服务。

    # 下载 scope 工具
    sudo curl -L https://github.com/weaveworks/scope/releases/download/latest_release/scope -o /usr/local/bin/scope
    # 使 scope 具有执行权限
    sudo chmod +x /usr/local/bin/scope
    # 部署安装 Weave Scope, 并设定验证用户 myuser, 密码 mypassword。
    sudo scope launch -app.basicAuth -app.basicAuth.password mypassword -app.basicAuth.username myuser -probe.basicAuth -probe.basicAuth.password mypassword -probe.basicAuth.username myuser
    
  2. 使用:WeaveScope 默认启动时在 4040 端口,可以在宿主机上打开 http://127.0.0.1:4040 进行登录查看:
    Docker学习教程_第19张图片

  • 搜索——左上角是查找功能,可以根据镜像、容器名称查找,也可以按照指标查找,比如 cpu > 4%,并且它支持多条件联合查询。
  • 过滤标签——左下角区域是对显示的对象按照不同的条件进行过滤显示。比如CONTAINERS可以选择系统容器还是应用容器,运行的容器还是停止的容器等等。
  • 展示对象标签——中间上方区域展示可以选择的展示对象,有 进程、容器、主机等,可以根据需要切换。
  • 展示对象——中间的主区域就是展示对象的核心部分,我们可以点击对象进行操作。例如选择一个容器对象,可以查看它的基本信息,也可以对它进行启停或进入容器shell等操作。
    Docker学习教程_第20张图片
  • 展示模式——右上角是切换展示模式,从左至右分别是 展示图表、展示表格、展示资源。展示图表和展示资源两种展示模式可以切换显示CPU或内存。
    Docker学习教程_第21张图片

5. 容器监控平台概览

监控是管理基础设施的核心工具,没有监控,我们将无法了解系统环境、进行诊断故障、制定容量计划,最糟的就是,故障发生了也不会被发现。从技术角度来看,监控是衡量和管理技术系统的工具和流程。监控将系统和应用程序生成的指标转换为对应的业务价值。监控系统需要在基于容器的微服务中自动且快速地识别生命周期,并持续地提供实时的监控检测。

上一节已经使用WeaveScope搭建了一个容器监控管理系统,并带有一些基本的监控功能。但它对于自定义数据获取、自动告警、细化监控指标和展示的时候,生态、功能性和扩展性都有欠缺,系统及需求复杂时难以满足我们的定制化的监控需求。这时,我们就需要围绕Premetheus,配合其生态及相关工具,配置我们的监控平台。

  1. Prometheus简介:Prometheus 的灵感来自 Google 的 Borgmon, 是由前 Google 工程师从 2012 年开始在 Soundcloud以开源软件的形式进行研发的系统监控和告警工具包,自此以后,许多公司和组织都采用了 Prometheus 作为监控告警工具。Prometheus 的开发者和用户社区非常活跃,它现在是一个独立的开源项目,可以独立于任何公司进行维护。Prometheus 已被全球众多企业广泛使用,以满足上面提到的复杂的监控需求,当然,它也可以用来监控属于传统架构的资源。
    Prometheus 的整体架构以及生态系统组件如下图所示:
    Docker学习教程_第22张图片
  2. Prometheus基本原理:Prometheus 专注于现在正在发生的事情,而不是追踪数周或数月前的数据。Prometheus 通常不用于长期数据保留,默认保存 15 天的时间序列数据。它有这样一个前提,即大多数监控查询和警报都是从最近的数据中生成的
    Prometheus的基本原理通过HTTP协议周期性抓取被监控组件的状态,任意组件只要提供对应的HTTP接口就可以接入监控,不需要任何SDK或者其他的集成过程。输出被监控组件信息的HTTP接口被叫做exporter ,常用的组件大部分都有exporter可以直接使用,比如Nginx、MySQL、Linux系统信息等等。
    大致工作流程如下:
    (1)Prometheus定时去目标上抓取指标监控数据,抓取目标需要暴露一个http服务的接口给它定时抓取。对于不能直接抓取的目标,Prometheus支持这些应用服务主动推送监控指标到PushGateway,而后Prometheus定时去这些网关上抓取数据。
    (2)Prometheus在本地存储保存抓取的数据,并按规则进行过滤和整理数据。
    (3)Prometheus支持很多方式的图表可视化,例如Grafana、自带的Promdash以及自身提供的模版引擎等等。Prometheus还提供HTTP API的查询方式,自定义所需要的输出。
  3. Prometheus 的优势
    (1)由指标名称和和键/值对标签标识的时间序列数据组成的多维数据模型;
    (2)强大的查询语言 PromQL;
    (3)不依赖分布式存储,单个服务节点具有自治能力;
    (4)时间序列数据是服务端通过 HTTP 协议主动拉取获得的;
    (5)也可以通过中间网关来推送时间序列数据;
    (6)可以通过静态配置文件或服务发现来获取监控目标;
    (7)支持多种类型的图表和仪表盘。
  4. Prometheus 的组件与生态
    (1)cAdvisor——Prometheus 支持了多种方法来监控 Docker,推荐的方法是使用 Google 的 cAdvisor 工具。cAdvisor是Google开源的一款用于展示和分析容器运行状态的可视化工具,通过在主机上运行cAdvisor用户可以轻松的获取到当前主机上容器的运行统计信息,并以图表的形式向用户展示。
    Docker学习教程_第23张图片
    cAdvisor是一个简单易用的工具,用户不用再登录到服务器中即可以可视化图表的形式查看主机上所有容器的运行状态。cAdvisor 作为 Docker 容器运行,它可以对宿主机上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况。cAdvisor默认只保存几分钟分钟的监控数据,Prometheus 支持通过它导出指标,并将数据传输到存储系统中。
    (2)Grafana——Prometheus本身的界面比较简洁,为了给 Prometheus 添加一个功能更全面的可视化界面,我们可以与开源仪表板工具 Grafana 集成,Grafana 接受来自不同数据源的数据,提供可视化仪表板,它还支持多种数据源。
    Docker学习教程_第24张图片
    (3)Alertmanager——Prometheus 服务器没有内置警报工具,而是将警报从 Prometheus 服务器推送到 Alertmanager警报管理器的单独服务器。Alertmanager 可以管理、整合和分发各种警报到不同目的地,并能够防止重复发送。在 Prometheus 服务器上编写的警报规则将使用我们收集的指标并在指定的阈值或标准上触发警报。当指标达到阈值时,会生成一个警报,并将其推送到 Alertmanager。

6. 实战——搭建容器监控平台

监控 Docker 主机上运行的容器状态,并监控 cAdvisor 容器,如果此容器关闭,会触发邮件告警。

  1. 创建如下目录结构Docker学习教程_第25张图片

  2. 编写 prometheus 的配置文件 prometheus.yml

    # 全局配置
    global:
        # 每5s收集一次数据
        scrape_interval: 5s
        # 每5s执行一次告警规则检测
        evaluation_interval: 5s
        # 标记标签
        external_labels:
            monitor: 'monitor'
    # 告警配置
    alerting:
        alertmanagers:
            - static_configs:
                - targets: ['alertmanager:9093']
    # 指定规则配置文件
    rule_files:
        - rules/*.yml
    # 数据抓取配置
    scrape_configs:
        - job_name: 'prometheus'
          static_configs:
              - targets: ['prometheus:9090']
        - job_name: 'cadvisor'
          static_configs:
              - targets: ['cadvisor:8080']
    
  3. 编写 Prometheus 的告警规则 rule_1.yml

    groups:
        - name: rule-1
          rules:
              - alert: "服务运行告警"
                expr: up{
           job="cadvisor"} < 1
                # 告警等待时间
                for: 1m
                labels:
                    severity: warning
                annotations:
                    summary: "服务名: {
           {
           $labels.alertname}}"
                    description: "容器cadvisor已停止"
    
  4. 编辑 alertmanager 配置文件 config.yml
    alertmanager 触发告警后会发送邮件,我们需要先配置邮箱,这里以 QQ 邮箱为例。登录邮箱,选择设置:
    Docker学习教程_第26张图片
    Tips:25 端口用于转发邮件,没有考虑认证、加密等问题。587 端口专门被设计用来提交邮件,传输可以加密。这里我们使用 587 端口。

    # 全局配置项
     global:
       resolve_timeout: 5m  #处理超时时间,默认为5min
       smtp_smarthost: 'smtp.qq.com:587'  # 邮箱smtp服务器代理,这里以qq邮箱为例
       smtp_from: '[email protected]'  # 发送邮箱名称
       smtp_auth_username: '[email protected]'  # 邮箱账号
       smtp_auth_password: 'xxxxxxxxxxxx'  # 邮箱授权码
    
    # 定义模板信息
     templates:
       - '/etc/alertmanager/templates/*.html'
    
    # 定义路由树信息
     route:
       group_by: ['alertname'] # 报警分组依据
       group_wait: 10s # 最初即第一次等待多久时间发送一组警报的通知
       group_interval: 10s # 在发送新警报前的等待时间
       repeat_interval: 1m # 发送重复警报的周期 对于email配置中,此项不可以设置过低,否则将会由于邮件发送太多频繁,被smtp服务器拒绝
       receiver: 'email' # 发送警报的接收者的名称,以下receivers name的名称
    
    # 定义警报接收者信息
     receivers:
       - name: 'email' # 警报
         email_configs: # 邮箱配置
         - to: '[email protected]'  # 接收警报的email配置
           html: '{
           { template "alert.html" . }}' # 设定邮箱的内容模板
           headers: {
            Subject: "[WARN] 报警邮件"} # 接收邮件的标题
    
  5. 编辑告警邮件模板 alert.html

    {
           {
            define "alert.html" }}
    "1">
            
    报警项</td> 实例</td> 报警内容</td> 开始时间</td> </tr> { { range $i, $alert := .Alerts }}
    { { index $alert.Labels "alertname" }}</td> { { index $alert.Labels "instance" }}</td> { { index $alert.Annotations "description" }}</td> { { $alert.StartsAt }}</td> </tr> { { end }} </table> { { end }}
  6. 编写 DockerCompose.yml

    version: "3.8"
    services:
        cAdvisor:
          image: google/cadvisor:v0.33.0
          container_name: cadvisor
          restart: always
          deploy:
              resources:
                  limits:
                      cpus: '0.20'
                      memory: 500M
          networks:
              - monitor
          volumes:
              - /:/rootfs:ro
              - /var/run:/var/run:ro
              - /sys:/sys:ro
              - /var/lib/docker/:/var/lib/docker:ro
              - /dev/disk/:/dev/disk:ro
    
        Prometheus:
          image: prom/prometheus:v2.19.2
          container_name: prometheus
          restart: always
          deploy:
              resources:
                  limits:
                      cpus: '0.20'
                      memory: 500M
          volumes:
              - ./prometheus/conf:/etc/prometheus:ro
          networks:
              - monitor
          depends_on:
              - cAdvisor
          ports:
              - "9090:9090"
    
    
        alertmanager:
          image: prom/alertmanager:v0.21.0
          container_name: alertmanager
          restart: always
          deploy:
              resources:
                  limits:
                      cpus: '0.20'
                      memory: 500M
          networks:
              - monitor
          ports:
              - "9093:9093"
          depends_on:
              - Prometheus
          volumes:
              - ./alertmanager/conf/config.yml:/etc/alertmanager/config.yml
              - ./alertmanager/templates:/etc/alertmanager/templates
          command:
              - '--config.file=/etc/alertmanager/config.yml'
              - '--storage.path=/alertmanager'
              - '--log.level=info'
    
        Grafana:
          image: grafana/grafana:7.0.5
          container_name: grafana
          restart: always
          deploy:
              resources:
                  limits:
                      cpus: '0.20'
                      memory: 500M
          networks:
              - monitor
          environment:
              - GF_SECURITY_ADMIN_PASSWORD=123456
          depends_on:
              - Prometheus
          ports:
              - "3000:3000"
    
    networks:
      monitor:
        name: monitornet 	# 编辑时报错则删掉
        driver: bridge
    

    配置完成后需要使用–compatibility兼容模式使它生效

    # 启动
    docker-compose --compatibility up -d
    # 移除
    docker-compose --compatibility down
    # 重启
    docker-compose --compatibility restart
    
  7. 配置 Grafana Dashboard 页
    (1)启动之后,使用浏览器打开 http://127.0.0.1:3000, 即可访问 grafana Web 页面。使用admin 和 123456 密码(GF_SECURITY_ADMIN_PASSWORD指定)登录,登录成功后,配置数据源
    在这里插入图片描述
    Docker学习教程_第27张图片
    (2)选择 Prometheus 数据源并填写 Prometheus 地址,点击Save&Test保存配置
    Docker学习教程_第28张图片
    Docker学习教程_第29张图片
    (3)选择导入 Dashboard(打开Grafana的官方Dashboard仓库,找一个合适的样式下载下来,再将下载的 json 文件导入,选择 Prometheus 数据源)
    Docker学习教程_第30张图片
    (4)import 导入即可看到容器监控页面
    Docker学习教程_第31张图片

  8. 告警测试
    (1)打开 prometheus web UI 页面 http://127.0.0.1:9090,点击 alert 标签页查看监控告警状态
    Docker学习教程_第32张图片
    (2)使用命令 docker stop cadvisor 将监控的容器 cadvisor 关掉,几秒后刷新页面,发现告警进入 Pending 暂挂状态
    Docker学习教程_第33张图片
    (3)一分钟后(rule_1.yml 中 for = 1m 配置)告警进入 firing 状态
    Docker学习教程_第34张图片
    (4)查收告警邮件
    Docker学习教程_第35张图片
    (5)使用命令 docker start cadvisor 修复故障后恢复正常

  9. 7. 容器编排工具与 Kuberneters

    使用 Docker 运行一个服务应用,当负载增加,单一实例无法满足需要时,就得增加这个实例部署的数量,并配置容器间负载均衡,而所有这些操作都需要手动处理,并且这些操作也仅仅被限制在这一台 Docker 宿主机中,如果宿主机故障,那么整套应用就会崩溃。为了避免这种情况发生,我们需要耗费大量的精力去依次配置这些容器和主机的监控系统,以便实时掌握宿主机的运行状况,及时处理出现故障的容器。当运行的容器随着业务量逐步增加,生产环境中可能会有成千上万个容器需要被监控和管理,这时就需要容器编排工具出马了。

    使用容器编排工具可以自动化和管理任务,容器编排负责容器的启停调度,同时通过管理容器集群来提升容器使用率。例如:

    • 置备和部署容器;
    • 配置和调度容器;
    • 资源分配;
    • 容器可用性;
    • 根据平衡基础架构中的工作负载而扩展或删除容器;
    • 负载平衡和流量路由;
    • 监控容器的健康状况;
    • 根据运行应用的容器来配置应用;
    • 保持容器间交互的安全。

    目前,主流的容器编排工具有:Docker Compose(单一主机上部署多个容器);Docker Swarm(多台机器上部署容器,开箱即用,快速部署容器);Mesos(大数据组件部署,双层调度,任务调度需自己实现);Kubernetes(k8s)(社区活跃度高,微服务化,组件丰富)。

    • Docker Swarm——是由 Docker 开发的调度框架。它的使用方式接近 Docker 本身,加之 Docker Machine 可以快速创建部署 Docker 宿主机,使得 Swarm 整体部署和使用非常简单,易于上手。
      功能特性
      (1)集群管理集成进 Docker Engine;
      (2)去中心化设计;
      (3)声明式服务模型;
      (4)服务扩容缩容;
      (5)协调预期状态与实际状态的一致性;
      (6)多主机网络;
      (7)服务发现;
      (8)负载均衡;
      (9)安全策略;

    • Mesos——一个开源的集群管理框架,它可以将数据中心放在一台电脑里运行,从数据中心、操作系统的角度提供资源视图,对外提供简单的 API,同时隐藏内部的很多复杂架构。它由 UC Berkeley 的Benjemin Hinderman,Andy Konwinski 和 Matei Zaharia 开发,早于 Docker 产生,Mesos 作为资源管理器,后来在 Twitter 里发展成熟,并很快成为 Apache 基金会的顶级项目。
      功能特性
      (1)可扩展到 10000 个节点;
      (2)使用 ZooKeeper 实现 Master 和 Slave 的容错;
      (3)使用 Linux 容器实现本地任务隔离;
      (4)基于多资源(内存,CPU、磁盘、端口)调度;
      (5)提供 Java,Python,C++ 等多种语言 API。

    • Kubernetes——一个最初由 Google 工程师开发和设计的开源容器编排工具。2015 年,Google 将 Kubernetes 项目捐赠给新成立的云原生计算基金会。Kubernetes是目前最流行的开源容器编排解决方案框架,基于 Google 庞大的生态圈及 RedHat(OpenShift)的企业支持。 如果加上 OpenShift(可以看作基于Kubernetes构建的发行版产品),Kubernetes 占有容器调度编排市场的份额高达 9 成,因而Kubernetes 社区也成为了容器编排工具中最大的社区。
      功能特性
      (1)跨多台主机进行容器编排;
      (2)更加充分地利用硬件,最大程度获取运行企业应用所需的资源;
      (3)有效管控应用部署和更新,并实现自动化操作;
      (4)挂载和增加存储,用于运行有状态的应用;
      (5)快速、按需扩展容器化应用及其资源;
      (6)对服务进行声明式管理,保证所部署的应用始终按照部署的方式运行;
      (7)利用自动布局、自动重启、自动复制以及自动扩展功能,对应用实施状况检查和自我修复;
      (8)支持集群联邦功能,用于控制大量节点。
      Kubernetes 组件
      Docker学习教程_第36张图片
      其中:

    • Master 负责管理整个集群。 Master 协调集群中的所有活动,例如调度应用、维护应用的所需状态、应用扩容以及推出新的更新;

    • Node 是一个虚拟机或者物理机,上面具有用于处理容器操作的容器引擎。它在 Kubernetes 集群中充当工作机器的角色,每个Node都有 Kubelet , 它管理 Node 同时也充当 Node 与 Master 通信的代理;

      在 Kubernetes 上部署应用时,Master 启动应用容器,编排容器在群集的 Node 上运行。 Node 使用 Master 暴露的 Kubernetes API 与 Master 通信。终端用户也可以使用 Kubernetes API 与集群交互。

      Tips】 Kubernetes 最初只支持 docker 作为运行时,为了能够让 Kubernetes 变得更具有可扩展性,在 1.5 版本增加了容器运行时接口 CRI(the Container Runtime Interface),在随后的演进中,CRI 被抽出来做成了独立的项目。Kubelet 作为 CRI 的客户端,而容器运行时则需要实现 CRI 的服务端。只要某个容器引擎实现了 CRI 约定的接口,就能接入 k8s 作为 Container Runtime。

    Docker Swarm 的用户大体局限在个人或小型开发团队,看重的是部署和使用方便,使用的都是比较基础的功能;Mesos 得益于它的出身,大数据领域的生态相对健全;对于通用领域的容器编排项目,Kubernetes 是一个可靠的选择,得益于庞大的社区支持,插件和文档也非常丰富。


    参考来源

    1. 文章出处:水月imooc · Docker 入门教程
    2. Prometheus文档
    3. Alertmanager文档
    4. Grafana文档
    5. Docker Hub

    你可能感兴趣的:(#,读研之路,redis,linux,nginx,容器,docker)