1、Docker简介
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上。Docker 是一个跨平台、可移植的解决方案。
一个完整的Docker有以下几个部分组成:
1)dockerClient客户端
2)Docker Daemon守护进程
3)Docker Image镜像
4)DockerContainer容器
Docker引擎Docker Engine是C/S架构,主要有以下部件组成:
服务器(Docker daemon):后台运行的Docker daemon进程。Daemon进程用于管理Docker对象,包括镜像(images)、容器(containers)、网络(networks)、数据卷(data volumes)。
REST接口:同daemon交互的REST API接口。
客户端(Docker client):命令行(CLI)交互客户端。客户端使用REST API接口同Docker daemon进行访问。
Docker服务的架构图如图所示。
运行一个Docker服务,组成包括Docker daemon服务器、Docker Client客户端、Docker Image镜像、Docker Registry库、Docker Contrainer容器,
-
后台进程(dockerd)
$ ps -ef | grep dockerd root 4874 1 2 3月22 ? 00:26:29 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Docker服务组成图:
2、安装docker
docker使用起来十分容易。
-
添加官方yum仓库
[root@docker-node1 ~]# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
-
关闭防火墙和selinux
[root@docker-node1 ~]# systemctl stop firewalld [root@docker-node1 ~]# setenforce 0 [root@docker-node1 ~]# getenforce Permissive
-
安装并启动docker
[root@docker-node1 ~]# yum install docker-ce -y [root@docker-node1 ~]# systemctl start docker
-
查看docker version来验证是否安装成功
[root@docker-node1 ~]# docker version Client: Docker Engine - Community Version: 19.03.8 API version: 1.40 Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:27:04 2020 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.8 API version: 1.40 (minimum version 1.12) Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:25:42 2020 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.13 GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc: Version: 1.0.0-rc10 GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd docker-init: Version: 0.18.0 GitCommit: fec3683
-
配置docker镜像加速
[root@docker-node1 ~]# cat /etc/docker/daemon.json { "registry-mirrors": [ "https://1nj0zren.mirror.aliyuncs.com", "https://docker.mirrors.ustc.edu.cn", "http://f1361db2.m.daocloud.io", "https://registry.docker-cn.com" ] }
3、Docker底层技术支持
Docker通过namespace实现了资源隔离,通过cgroups实现了资源限制,通过写时复制(copy-on-write)实现了高效的文件操作。
3.1、namespace资源隔离
想要实现一个资源隔离的容器,当我们进入一个容器内部的时候,最直观的感受就是根目录的改变,即文件系统被隔离了。接着为了在分布式的环境下进行通信和定位,容器必然要具有独立的IP、端口、路由等,自然需要网络的隔离。同时容器还需要一个独立的主机名以便在网络中标识自己。容器间通信的本质其实是进程间通信,所以我们还需要进程间隔离以及PID的隔离。当然一切也离不开用户,所以对用户和用户组的隔离就实现了用户权限的隔离。
6种namespace隔离:
- UTS namespace:提供了主机名与域名的隔离,这样每个Docker容器就可以拥有独立的主机名和域名。
- IPC namespace:进程间通信(IPC)涉及的IPC资源包括常见的信号量、消息队列和共享内存,申请IPC资源就申请了一个全局唯一的32位ID,所以IPC namespace中实际上包含了系统IPC标识符以及实现POSIX消息队列的文件系统。在同一个IPC namespace下的进程彼此可见,不同namespace下的进程则互相不可见。
- PID namespace:PID的隔离,它对进程PID重新标号,即两个不同namespace下的进程可以具有相同的PID。
内核为所有的PID namespace维护了一个树状结构,最顶层的是系统初始时创建的,被称为root namespace。它创建的新PID namespace被称为child namespace,而原先的PID namespace就是新创建的PID namespace的 parent namespace。通过这种方式,不同PID namespace会形成一个层级体系。所属的父节点可以看到子节点中的进程,并可以通过信号等方式对子节点中的进程产生影响。而子节点却不能看到父节点PID namespace中的任何内容。
- Network namespace:主要提供了网络资源的隔离。
包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、套接字(socket)等。一个物理的网络设备最多存在于一个network namespace中,可以通过veth pair (虚拟网络设备对:有两端、类似管道,数据从一端传入另一端接收,反之亦然)在不同的network namespace间创建通道,以达到通信的目的。
- Mount namespace:通过隔离文件系统挂载点对隔离文件系统提供支持,进程在创建mount namespace时会把当前的文件结构赋给新的namespace。新namespace中的所有mount操作都只影响自身的文件系统,对外界不会产生任何影响。
- User namespace:主要隔离了安全相关的标识符(identifier)和属性(attribute),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。
一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特殊权限的普通用户,但是它创建的容器进程却属于拥有所有权限的超级用户。
在启动Docker daemon的时候指定–userns-remap 那么当用户运行容器时,容器内部的root用户并不等于宿主机内的root用户,而是映射到宿主机上的普通用户。
实际上,Linux内核实现namespace的一个主要目的,就是实现轻量级虚拟化(容器)服务。在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知。这样就可以让容器中的进程产生错觉,仿佛自己置身于一个独立的系统环境中,以达到独立和隔离的目的。
用户可以在/proc/[PID]/ns文件下看到指向不同namespace号的文件,[4026531839]就是namespace号
$ ls -al /proc/$$/ns
总用量 0
dr-x--x--x. 2 root root 0 4月 12 18:41 .
dr-xr-xr-x. 9 root root 0 4月 12 18:01 ..
lrwxrwxrwx. 1 root root 0 4月 12 18:41 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 root root 0 4月 12 18:41 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 4月 12 18:41 net -> net:[4026531956]
lrwxrwxrwx. 1 root root 0 4月 12 18:41 pid -> pid:[4026531836]
lrwxrwxrwx. 1 root root 0 4月 12 18:41 user -> user:[4026531837]
lrwxrwxrwx. 1 root root 0 4月 12 18:41 uts -> uts:[4026531838]
如果两个进程指向的namespace编号相同,说明在同一个namespace下。
3.2、cgroups (control groups)
cgroups是Linux内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务集合到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的结果。
cgroups可以限制、记录任务组所使用的物理资源(包括CPU、Memory、IO等)。
cgroups特点:
- cgroups提供了一个 cgroup 虚拟文件系统,作为进行分组管理和各子系统设置的用户接口。要使用 cgroup,必须挂载 cgroup 文件系统。通过挂载选项指定使用哪个子系统。
- cgroups的组织管理单元可以细粒度到线程级别,用户可以创建和销毁cgroup,从而实现资源再分配
- 所有资源管理的功能都以子系统的方式实现,接口统一。
- 子任务创建之初与父任务处于同一个cgroup的控制组。
cgroups功能:
- 资源限制:cgroup可以对任务使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就会发出OOM提示(Out of Memory)。
- 优先级分配:通过分配的CPU时间片数量及磁盘IO带宽大小。
- 资源统计:cgroups可以统计系统的资源使用量。
- 任务控制:cgroups可以对任务执行挂起、恢复等操作。
cgroups术语:
-
task(任务):task表示系统的一个进程或线程。
-
cgroup:cgroups中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个task可以加入某个cgroup,也可以从某个cgroup迁移到另一个cgroup。
-
subsystem(子系统):cgroups中的subsystem就是一个资源调度控制器,每种子系统独立控制一种资源。
blkio -- 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。 cpu -- 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。 cpuacct -- 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。 cpuset -- 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。 devices -- 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。 freezer -- 这个子系统挂起或者恢复 cgroup 中的任务。 memory -- 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。 net_cls -- 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。 ns -- 名称空间子系统。
-
hierarchy(层级):层级由一系列cgroup以一个树状结构排列而成,每个层级通过绑定对应的subsystem进行资源控制。层级中的cgroup节点可以包含0个或多个子节点。子节点继承父节点挂载的子系统。整个操作系统可以有多个层级。
基本规则:
- 同一个层级可以附加一个或多个子系统。
- 一个子系统可以附加到多个层级,当且仅当目标层级只有唯一一个子系统时。
- 系统每次新建一个层级时,该系统上的所有task默认加入这个新建层级的初始化cgroup。这个cgroup也称为root cgroup。一个task不能存在于同一个层级的不同cgroup中,可以存在于不同层级中的cgroup中。
- task在创建子task时,默认与原task在同一个cgroup中,子task允许移动到不同的cgroup中。
4、Docker 镜像
4.1、什么是docker image?
docker image是一个只读模板,用于创建Docker容器,docker image是文件和meta data的集合(root filesystem),image采用分层架构,每一层都可以添加删除文件commit成为一个新的image,不同的image可以共享相同的layer,image本身是只读的。
Docker 镜像含有启动容器所需要的文件系统及其内容,因此,其用于创建并启动docker容器。
- docker 采用分层构建机制,最底层为bootfs(aufs/btrfs/overlay2等文件系统。),其之上为rootfs(etc,bin…)
- bootfs:用于引导文件系统,包括bootloader和kernel,容器启动完成后会被卸载以节约资源。
- rootfs:位于bootfs之上,表现为docker容器的根文件系统。
- 传统模式中,系统启动之时,内核挂载rootfs时会首先将其挂载为"只读"模式,完整性自检完成后将其重新挂载为读写模式。
- docker中,rootfs由内核挂载为"只读模式",而后通过“联合挂载(union mount)”技术额外挂载一个"读写"层。这样读写层位于Docker容器文件系统的最顶层,它下面联合挂载多个只读层。
4.2、Docker Image主要特点
4.2.1、分层
Docker镜像采用分层的方式构建,不同镜像之间可以共享镜像层。
4.2.2、写时复制(copy-on-write)
写入时复制是一种共享和复制文件的策略,可最大程度地提高效率。容器启动时并不需要单独复制一份文件,而是将所有镜像层以只读的方式挂载到一个挂载点,再在最上层添加一个读写层。当文件发生改变时,会把变化的内容写到读写层中,只读层内容不改变并被隐藏。
4.2.3、内容寻址
Docker使用了内容寻址存储(content-addressable storage)的机制,根据文件内容来索引镜像和镜像层。每一层会计算一个hash值,并以此作为唯一标识。
4.2.4、联合挂载
联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,最终可见的文件系统包含整合之后的各层的文件和目录。
5.3、从docker hub 搜索 image https://hub.docker.com/
以mysql为例
5.4、Image 相关概念
5.4.1、registry
Registry用以保存Docker镜像,其中还包括镜像层次结构和关于镜像的元数据。
Docker Hub是Docker公司提供的互联网公共镜像仓库。用户可以构建自己本地的镜像仓库,国内有些公司也构建了镜像仓库。包括阿里云、新浪等。
一个 Docker Registry 节点中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
一般而言,一个仓库包含的是同一个软件的不同版本的镜像,而标签则用于对应于软件的的不同版本。可以通过 <仓库名>:<标签> 的格式来指定具体是哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
以 Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,14.04, 16.04。可以通过 ubuntu:14.04,或者 ubuntu:16.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如ubuntu,那将视为 ubuntu:latest。
registry分类
- Sponsor Registry:第三方Registry,仅供客户和Docker社区使用
- Mirror Registry:第三方Registry,只让客户使用
- Vendor Registry:由发布Docker镜像的供应商提供的Registry
- Private Registry:通过设有防火墙和额外的安全层的私有实体提供的registry
5.4.2、repository
repository具有某个功能的Docker镜像的所有迭代版本构成的镜像组,简单来说,repository是镜像的集合。Docker Hub中有两种类型仓库,用户仓库(由用户自己创建,由用户名与repository名两部分组成,中间以’/'隔开,username/repository_name),顶层仓库(由docker公司维护,一般只包含repository名的部分,如centos)
5.4.3、manifest
manifest(描述文件)主要存在于registry中作为Docker镜像的元数据文件,在pull、push、save和load中作为镜像结构额基础信息的描述文件。在镜像被pull或者load到Docker宿主机时,manifest被转化为本地的镜像配置文件config。
5.4.4、image和layer
image是用来存储一组镜像相关的元数据信息,主要包括镜像的架构、镜像默认配置信息、构建镜像的容器配置信息、包含所有镜像信息的rootfs。Docker利用rootfs中的diff_id计算出内容寻址的索索引来获取layer的信息,进而获取每一个镜像层的文件内容。
layer是一个Docker用来管理镜像层的中间概念,镜像由镜像层组成,单个镜像层可以被多个镜像共享。
5.5、Docker image常用操作
[root@docker-node1 ~]# docker image -h
Flag shorthand -h has been deprecated, please use --help
Usage: docker image COMMAND
Manage images
Commands:
build Build an image from a Dockerfile
history Show the history of an image
import Import the contents from a tarball to create a filesystem image
inspect Display detailed information on one or more images
load Load an image from a tar archive or STDIN
ls List images
prune Remove unused images
pull Pull an image or a repository from a registry
push Push an image or a repository to a registry
rm Remove one or more images
save Save one or more images to a tar archive (streamed to STDOUT by default)
tag Create a tag TARGET_IMAGE that refers to SOURCE_IMAGE
-
列出本地image,
docker image ls
或者docker images
[root@docker-node1 ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE redis latest f0453552d7f2 9 days ago 98.2MB
-
pull一个mysql镜像
[root@docker-node1 ~]# docker pull mysql [root@docker-node1 ~]# docker image ls mysql REPOSITORY TAG IMAGE ID CREATED SIZE mysql latest 9b51d9275906 2 weeks ago 547MB
-
通过docker save导出mysql镜像到mysql.tar.gz文件
[root@docker-node1 ~]# docker save mysql -o mysql.tar.gz [root@docker-node1 ~]# ls mysql.tar.gz mysql.tar.gz
-
从文件导入镜像
[root@docker-node1 ~]# docker image load -i mysql.tar.gz Loaded image: mysql:latest
-
给镜像添加tag
docker image tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
[root@docker-node1 ~]# docker image tag mysql:latest mysql:test [root@docker-node1 ~]# docker image ls mysql REPOSITORY TAG IMAGE ID CREATED SIZE mysql latest 9b51d9275906 2 weeks ago 547MB mysql test 9b51d9275906 2 weeks ago 547MB
-
docker image rm 删除一个image
[root@docker-node1 ~]# docker image rm mysql:test Untagged: mysql:test
5、Docker Container
5.1、什么是Container?
是一个镜像的运行实例。container是通过image创建。container包含image+可读写层。
5.2、官方怎么说?
immage在运行时成为container。container是打包代码及其所有依赖项的标准软件单元,因此应用程序可以从一个计算环境快速可靠地运行到另一个计算环境。 Docker容器镜像是一个轻量级的,独立的,可执行的软件软件包,其中包含运行应用程序所需的一切:代码,运行时,系统工具,系统库和设置。
5.3、docker container常用操作
可以使用docker container Command的格式。
-
docker run运行一个名称为web容器,映射本地80端口到容器80端口
[root@docker-node1 ~]# docker container run -dit --name web -p 80:80 httpd c8e06ae7d71f13ae0a9f8d63777efdda3fdce93c476ab931dd99ec419e98e8db 参数介绍: -d:后台运行并返回container ID 命令执行后显示的c8xxxx就是此参数的效果 -i:打开STDIN,用于控制台交互 -t:分配一个tty,可以用来登陆 --name:指定容器名称 -p:映射端口-p[本地port]:[容器port] --rm:容器退出时自动删除container
-
docker ps查看启动的容器,与所有容器。
[root@docker-node1 ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c8e06ae7d71f httpd "httpd-foreground" 4 minutes ago Up 4 minutes 0.0.0.0:80->80/tcp web -a参数可以显示所有容器,包括退出的容器 -q参数可以只返回容器ID [root@docker-node1 ~]# docker ps -q c8e06ae7d71f
-
docker exec进入容器,常用组合-it参数
docker exec [OPTIONS] CONTAINER COMMAND [ARG…]
[root@docker-node1 ~]# docker exec -it web /bin/bash root@c8e06ae7d71f:/usr/local/apache2#
不进入容器,让容器执行命令并返回结果docker exec [容器名称或ID] [执行的命令]
[root@docker-node1 ~]# docker exec web ls /var/log/ alternatives.log apt btmp dpkg.log faillog lastlog wtmp
-
docker inspect 显示容器的详细信息
[root@docker-node1 ~]# docker inspect web | more [ { "Id": "c8e06ae7d71f13ae0a9f8d63777efdda3fdce93c476ab931dd99ec419e98e8db", "Created": "2020-03-25T07:46:08.626233176Z", "Path": "httpd-foreground", "Args": [], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 19811, "ExitCode": 0, "Error": "", "StartedAt": "2020-03-25T07:46:09.434595879Z", "FinishedAt": "0001-01-01T00:00:00Z" },
-
docker logs 容器ID/容器Name 显示启动日志
[root@docker-node1 ~]# docker logs web AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message [Wed Mar 25 08:03:12.376930 2020] [mpm_event:notice] [pid 1:tid 139689087566976] AH00489: Apache/2.4.41 (Unix) configured -- resuming normal operations [Wed Mar 25 08:03:12.377423 2020] [core:notice] [pid 1:tid 139689087566976] AH00094: Command line: 'httpd -D FOREGROUND'
-
docker cp命令,拷贝容器内的文件到宿主机,或者拷贝宿主机文件到容器内
拷贝web容器httpd.conf到本地 [root@docker-node1 ~]# docker cp web:/usr/local/apache2/conf/httpd.conf . [root@docker-node1 ~]# ls httpd.conf httpd.conf
-
docker rm 删除一个容器,默认不允许删除正在运行的容器,需要先停止容器,一般可以加上-f参数,强制执行
[root@docker-node1 ~]# docker rm -f web web [root@docker-node1 ~]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6、搭建私有仓库,registry与harbor
前面说到了镜像的registry,这里介绍下私有仓库的搭建。
8.1、registry搭建
准备两台linux,一台做为registry私有仓库
registry端配置,在安装好docker环境的基础上执行
[root@registry ~]# docker run -d -p 5000:5000 --restart always --name registry registry:2
查看启动的registry容器
[root@registry ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
43a7858c9be7 registry:2 "/entrypoint.sh /etc…" 5 seconds ago Up 3 seconds 0.0.0.0:5000->5000/tcp registry
此时registry端启动了5000端口
docker-node1指定私有仓库地址
在/etc/docker/daemon.json中指定registry的ip 地址及端口
[root@docker-node1 ~]# cat /etc/docker/daemon.json
{
"insecure-registries": ["10.10.128.178:5000"] #registry的ip:port
}
然后我们重新加载配置并重启docker
[root@docker-node1 ~]# systemctl daemon-reload
[root@docker-node1 ~]# systemctl restart docker
修改一个本地镜像格式
例如将centos-httpd tag为v1的镜像格式修改为10.10.128.178:5000/centos-httpd:v1
[root@docker-node1 ~]# docker tag centos-httpd:v1 10.10.128.178:5000/centos-httpd:v1
通过docker push 推送镜像docker push registryip:port/imagename:tag
[root@docker-node1 ~]# docker push 10.10.128.178:5000/centos-httpd:v1
The push refers to repository [10.10.128.178:5000/centos-httpd]
083db8a1c5d9: Pushed
9f8b5faabf43: Pushed
77b174a6a187: Pushed
v1: digest: sha256:b3ec77096374c7f964f615da701683906d12b7a86f343d73c887484c609b31fc size: 948
验证是否推送成功,官方提供了介绍https://docs.docker.com/registry/spec/api/#listing-repositories
[root@docker-node1 ~]# curl 10.10.128.178:5000/v2/_catalog
{"repositories":["centos-httpd"]}
8.2 、docker harbor搭建
什么是harbor?
Harbor is an open source container image registry that secures images with role-based access control, scans images for vulnerabilities, and signs images as trusted. As a CNCF Incubating project, Harbor delivers compliance, performance, and interoperability to help you consistently and securely manage images across cloud native compute platforms like Kubernetes and Docker.
Harbor是一个开放源代码容器映像注册表,可通过基于角色的访问控制来保护镜像,扫描镜像中的漏洞并将镜像签名为受信任。作为CNCF孵化项目,Harbor提供合规性,性能和互操作性,以帮助您跨Kubernetes和Docker等云原生计算平台持续,安全地管理镜像。
相比于registry,harbor提供了UI,可以直观的展示image。
Download package:https://github.com/goharbor/harbor/releases
也可以直接从这个地址下载1.9.1版本:
https://storage.googleapis.com/harbor-releases/release-1.9.0/harbor-offline-installer-v1.9.1.tgz
安装pip并通过pip安装docker-compose
[root@harbor ~]# yum install python2-pip gcc-c++ python-devel -y
配置pip加速
[root@harbor ~]# mkdir /root/.pip
[root@harbor ~]# touch /root/.pip/pip.conf
[root@harbor ~]# cat /root/.pip/pip.conf
[global]
trusted-host=mirrors.aliyun.com
index-url=https://mirrors.aliyun.com/pypi/simple/
更新下pip
[root@harbor ~]# pip install --upgrade pip
pip安装docker-compose
[root@harbor ~]# pip install docker-compose
查看docker-compose版本
[root@harbor ~]# docker-compose version
docker-compose version 1.25.4, build unknown
docker-py version: 4.2.0
CPython version: 2.7.5
OpenSSL version: OpenSSL 1.0.2k-fips 26 Jan 2017
解压harbor包
[root@harbor ~]# tar xvf harbor-offline-installer-v1.9.1.tgz
[root@harbor ~]# cd harbor
[root@harbor harbor]# ls
common.sh harbor.v1.9.1.tar.gz harbor.yml install.sh LICENSE prepare
编辑harbor.yml文件
修改hostname
hostname: 10.10.128.178
默认密码为:Harbor12345,可以自行修改
[root@harbor harbor]# grep harbor_admin_password harbor.yml
harbor_admin_password: Harbor12345
执行安装脚本
[root@harbor harbor]# ./install.sh
……
浏览器通过ip访问
新建一个用户
登陆新建用户,创建项目
点击项目–> 镜像仓库,可以看到推送镜像的格式
在其他节点进行推送测试
首先添加如下配置
[root@docker-node1 ~]# cat /etc/docker/daemon.json
{
"insecure-registries": ["10.10.128.178"]
}
重启docker
[root@docker-node1 ~]# systemctl daemon-reload
[root@docker-node1 ~]# systemctl restart docker
登陆harbor
[root@docker-node1 ~]# docker login 10.10.128.178
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
修改一个image的tag然后push测试
[root@docker-node1 ~]# docker tag centos-httpd:v1 10.10.128.178/test/centos-httpd:v1
[root@docker-node1 ~]# docker push 10.10.128.178/test/centos-httpd:v1
The push refers to repository [10.10.128.178/test/centos-httpd]
083db8a1c5d9: Pushed
9f8b5faabf43: Pushed
77b174a6a187: Pushed
v1: digest: sha256:b3ec77096374c7f964f615da701683906d12b7a86f343d73c887484c609b31fc size: 948
在项目中查看
7、Dockerfile
7.1、基本指令
Dockerfile是一个文本格式的配置文件,用户可以使用Dockerfile来快速创建自定义的镜像。
指令 | 说明 |
---|---|
ARG | 定义创建镜像过程中使用的变量 |
FROM | 指定所创建镜像的基础镜像 |
LABEL | 为 生成的镜像添加元数据标签信息 |
EXPOSE | 声明镜像内服务监听的端口 |
ENV | 指定环境变量 |
ENTRYPOINT | 指定镜像的默认入口命令 |
VOLUME | 创建一个数据卷挂载点 |
USER | 指定运行容器时的用户名或UID |
WORKDIR | 配置工作目录 |
ONBUILD | 创建子镜像时指定自动执行的操作指令 |
STOPSIGNAL | 指定退出的信号值 |
HEALTHCHECK | 配置所启动容器如何进行健康检查 |
SHELL | 指定默认shell类型 |
RUN | 运行指定命令 |
CMD | 启动容器时指定默认执行的命令 |
ADD | 添加内容到镜像 |
COPY | 复制内容到镜像 |
-
FROM
格式为FROM [ AS ]或FROM : [ AS ]或FROM @ [ AS ]
任何Dockerfile中的第一条指令必须为FROM指令。并且,如果在同一个Dockerfile中创建多个竟像时,可以使用多个FROM指令。 -
LABEL
为生成的镜像添加元数据标签,这些可以辅助过滤出特定镜像。
格式为LABEL =
例如:
LABEL author=“xxxxxx” -
EXPOSE
格式为EXPOSE [/]
例如:
EXPOSE 22 80 443
该指令只是起到声明作用,并不会自动完成端口映射。 -
ENV
格式为 ENV 或 ENV =
例如:
ENV APP_VERSION=1.0.0
ENV PATH $PATH:/usr/local/bin -
VOLUME
创建一个数据挂载点
格式为VALUME ["/data"]
运行容器时可以从本地主机或其他容器挂载数据卷。 -
USER
格式为 USER daemon
当服务不需要管理员权限时,可以通过该命令指定运行用户,并且可以在Dockerfile中创建所需要的用户。例如:
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
要临时获取管理员权限可以使用gosu命令 -
WORKDIR
配置工作目录
格式 WORKDIR /path/to/workdir
可以使用多个WORKDIR指令,后续命令如果参数时相对路径,则会基于之前命令指令的路径。
例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
最终路径为 /a/b/c
因此,为了避免出错,推荐WORKDIR使用绝对路径 -
ONBUILD
配置当所创建的镜像作为其他镜像的基础镜像的时候,所执行创建操作指令。
格式为:ONBUILD [INSTRUCTION]。
例如Dockerfile使用如下的内容创建父镜像ParentImage,指定ONBUILD指令:#Dockerfile for ParentImage [...] ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src [...]
如果基于ParentImage镜像创建新的镜像时,新的Dockerfile中使用FROM ParentImage指定基础镜像,会自动执行ONBUILD指令的内容,等价于在后面添加了两条指令:
# Automatically run the following ONBUILD ADD . /app/src ONBUILD RUN /usr/local/bin/python-build --dir /app/src
由于ONBUILD是隐式执行的,推荐在使用它的标签中进行标注,例如ruby:2.1-onbuild
-
STOPSIGNAL
指定所创建镜像启动的容器接收退出的信号值。例如:
STOPSIGNAL singnal -
HEALTHCHECK
配置所启动容器如何进行健康检查(如何判断是否健康),自Docker 1.12开始支持。
格式有两种:1.HEALTHCHECK [OPTIONS] CMD command :根据所执行命令返回值是否为0判断;
2.HEALTHCHECK NONE :禁止基础镜像中的健康检查。
[OPTION]支持如下参数:
--inerval=DURATION (默认为:30s):多久检查一次;
--timeout=DURATION (默认为:30s):每次检查等待结果的超时时间;
--retries=N (默认为:3):如果失败了,重试几次才最终确定失败。
- SHELL指定其他命令使用shell时的默认shell类型。
格式为: SHELL [“executable”,“parameters”]
默认值为 [“bin/sh”,"-c"]。
注意:
对于Windows系统,Shell路径中使用了""作为分隔符,建议在Dockerfile开头添加# escape=`来指定转义符。 - RUN 运行指定命令。
格式为:RUN或RUN [“executable”,“param1”,“param2”]。
注意:
后一个指令会被解析为json数组,所以必须使用双引号。
前者默认将在shell终端中运行命令,即/bin/sh -c;后者则使用exec执行,不会启动shell环境。
指定使用其他终端类型可以通过第二种方式实现,例如:
RUN ["/bin/bash","-c",“echo hello”]
每条RUN指令将在当前镜像的基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\换行。例如:
RUN apt-get update \
&& apt-get install -y libsnappy-dev zliblg-dev libbz2-dev \
&& rm -rf /var/lib/apt/lists/*
- ADD与COPY
ADD该指令将复制指定的路径下的内容到容器中的路径下。
格式为:ADD
其中可以使Dockerfile所在目录的一个相对路径(文件或目录),也可以是一个URL,还可以是一个tar文件(如果是tar文件,会自动解压到路径下)。可以使镜像内的绝对路径,或者相当于工作目录(WORKDIR)的相对路径。路径支持正则表达式,例如:
ADD *.c /code/
COPY
复制本地主机的(为Dockerfile所在目录的一个相对路径、文件或目录)下的内容到镜像中的下。目标路径不存在时,会自动创建。路径同样支持正则。
格式为:COPY
当使用本地目录为源目录时,推荐使用COPY。
-
CMD与ENTRYPOINT
CMD:设置容器启动后默认执行的命令和参数,它支持三种格式:1.CMD ["executable","param1","param2"] 使用exec执行,是推荐使用的方式; 2.CMD param1 param2 在/bin/sh中执行,提供给需要交互的应用; 3.CMD ["param1","param2"] 提供给ENTRYPOINT的默认参数。
RUN指令运行于映像文件构建过程,而CMD指令运行于基于Dockerfile构建出的新映像文件启动一个容器时。
CMD指令的首要目的在于启动的容器指定默认要运行的程序,且运行结束后,容器也将终止;不过CMD指定的命令其可以被docker run的命令行选项所覆盖。
每个Dockerfile只能有一条CMD命令。如果指定了多条命令,只有最后一条会被执行。如果用户启动容器时指定了运行的命令(作为run的参数),则会覆盖掉CMD指定的命令。
ENTRYPOINT:设指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令来执行,所有传入值作为该命令的参数。
支持两种格式:ENTRYPOINT [“executable”,“param1”,“param2”] :exec调用执行。
ENTRYPOINT command param1 param2: shell执行
类似CMD指令功能,用于为容器指定默认运行程序,从而使得容器是一个单独的可执行程序
与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT指定的程序。
每个Dockerfile中只能有一个ENTRYPOINT,当指定多个时,只有最有一个起效。shell格式介绍:
RUN yum install vim -y CMD echo "hello" ENTRYPOINT echo "hello"
**exec格式介绍:**命令与参数分隔
RUN ["yum","install","vim","-y"] CMD ["/bin/echo","hello"] ENTRYPOINT ["/bin/echo","hello"]
-
ARG
格式为ARG [=]。
当镜像编译成功后,ARG指定的变量将不再存在(ENV指定的变量将在镜像中保留。)
Docker内置了一些镜像创建变量,用户可以直接使用而无须声明,包括HTTP_PROXY,HTTPS_PROXY,FTP_PROXY,NO_PROXU。
7.2、.dockerignore 文件
可以通过 .dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略匹配路径或文件,在创建镜像时候不将无关数据发送到服务端。
例如下面的例子中包括了 6 行忽略的模式:
/temp
/temp*
tmp?
-*
Dockerfile
!README.md
dockerignore文件中模式语法支持Golang风格的路径正则格式:
- "* "表示任意多个字符
- "?"代表单个字符
- "!"表示不匹配
8、docker镜像构建与Container commit
编写完成 Dockerfile 之后,可以通过 docker [image] build 命令来构建镜像 。
基本的格式为 docker build [OPTIONS] PATH | URL I - 。
该命令将读取指定路径下(包括子目录)的 Dockerfile ,并将该路径下所有数据作为上下文( Context)发送给 Docker 服务端 。 Docker 服务端在校验 Dockerfile 格式通过后,逐条执行其中定义的指令,碰到 ADD 、 COPY 和 RUN 指令会生成一层新的镜像。 最终如果创建镜像成功,会返回最终镜像的 ID 。
8.1、docker [image] build
构建一个以centos7为基础的镜像,并安装启动http服务,写入hello world进行测试
[root@docker-node1 httpbuild]# cat dockerfile
FROM centos:7
RUN yum install httpd -y
RUN echo 'hello world' >> /var/www/html/index.html
ENTRYPOINT ["/usr/sbin/httpd","-D","FOREGROUND"]
[root@docker-node1 httpbuild]# docker build -t centos-httpd:v1 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos:7
---> 5e35e350aded
Step 2/4 : RUN yum install httpd -y
---> Running in 863d9813fd72
Loaded plugins: fastestmirror, ovl
……
Complete!
Removing intermediate container 863d9813fd72
---> 077a2b5662ef
Step 3/4 : RUN echo 'hello world' >> /var/www/html/index.html
---> Running in 43cdc8261995
Removing intermediate container 43cdc8261995
---> 9acb0f7f292b
Step 4/4 : ENTRYPOINT ["/usr/sbin/httpd","-D","FOREGROUND"]
---> Running in c6ba052b0a17
Removing intermediate container c6ba052b0a17
---> d88934f87daa
Successfully built d88934f87daa
Successfully tagged centos-httpd:v1
可以启动我们构建的镜像,并通过curl进行测试
[root@docker-node1 httpbuild]# docker run -dit -p 80:80 centos-httpd:v1
64ec0ff36123115a38e0e4d3a81d40bc8b717cb0b33c91184d8e50b39d741f83
[root@docker-node1 httpbuild]# curl localhost:80
hello world
8.2、container commit
对于我们修改的容器,我们可以进行再次提交成为一个新的镜像
例如在9.1启动的容器中安装vim命令,再完成commit
首先通过exec 进入到容器中并安装vim
[root@docker-node1 httpbuild]# docker exec -it 64ec0ff36123 /bin/bash
[root@64ec0ff36123 /]# yum install vim -y
安装完成后按Ctrl+p+q
通过commit将container提交为一个新的image
[root@docker-node1 httpbuild]# docker commit 64ec0ff36123 centos-vim:v1
sha256:4531aeccaa407a072b1334800bc2b6e935b139d2a0950b2727d90d73117b0b67
[root@docker-node1 httpbuild]# docker image ls centos-vim:v1
REPOSITORY TAG IMAGE ID CREATED SIZE
centos-vim v1 4531aeccaa40 13 seconds ago 515MB
9、Docker Network
9.1、自定义docker0网桥属性
https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
可以通过定义/etc/docker/daemon.json 文件来指定,例如修改docker0网桥ip
这里用一台docker主机测试
{
"bip":"10.0.0.1/16"
}
[root@test ~]# systemctl restart docker
[root@test ~]# ifconfig docker0
docker0: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 10.0.0.1 netmask 255.255.0.0 broadcast 10.0.255.255
ether 02:42:23:b9:2a:89 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
9.2、Docker 四种网络模式:
可以在docker run 使用 --net=xxx来指定容器的网络模式,默认为bridge模式
9.2.1、host模式:
使用 --net=host指定
docker使用的网络实际上和宿主机一样
Docker使用了Linux的Namespaces技术来进行资源隔离,如PID Namespace隔离进程,Mount Namespace隔离文件系统,Network Namespace隔离网络等。一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离。一个Docker容器一般会分配一个独立的Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
简单小案例:
首先查看下本机ip,我的为10.10.128.172
[root@docker-node1 ~]# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.10.128.172 netmask 255.255.255.0 broadcast 10.10.128.255
inet6 fe80::9cbe:c215:da7d:d4d3 prefixlen 64 scopeid 0x20<link>
ether 1e:00:0d:00:00:d2 txqueuelen 1000 (Ethernet)
RX packets 2707148 bytes 2493546664 (2.3 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2146501 bytes 524334490 (500.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
接下来我们通过host模式启动一个container
[root@docker-node1 ~]# docker run -dit --name hostcheck --net=host centos:7
3d8b889764620729798a4014a58ab9a77233fbc34c184eb4c9827a580c185ff5
[root@docker-node1 ~]# docker exec -it hostcheck yum install net-tools -y
可以发现容器ip、hostname等与本机是相同的
[root@docker-node1 ~]# docker exec -it hostcheck ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.10.128.172 netmask 255.255.255.0 broadcast 10.10.128.255
inet6 fe80::9cbe:c215:da7d:d4d3 prefixlen 64 scopeid 0x20<link>
ether 1e:00:0d:00:00:d2 txqueuelen 1000 (Ethernet)
RX packets 2712636 bytes 2508914235 (2.3 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2151324 bytes 524768086 (500.4 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@docker-node1 ~]# docker exec -it hostcheck hostname
docker-node1
9.2.2、container模式:
使用 --net=container:container_id/container_name指定
多个容器使用共同的网络,看到的ip是一样的。
指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。
9.2.3、none模式:
使用 --net=none指定
这种模式下,不会配置任何网络。
Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。
简单小案例:
[root@docker-node1 ~]# docker run -dit --name nonecon --net=none busybox
这是查看其ip信息,只有本地回环口
[root@docker-node1 ~]# docker exec nonecon ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
9.2.4、bridge模式:
默认模式,该模式会在container与宿docker0网桥上虚拟出一对接口,来维持container与宿主机的通信。
此模式会为每个容器分配一个独立的network namespace
bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。
网络拓补大致如下
[root@docker-node1 ~]# docker network ls | grep bridge
c7996516d664 bridge bridge local
可以通过inspect 查看其详细信息
[root@docker-node1 ~]# docker network inspect bridge
通过过滤container可以发现,连接在这个网桥下的container。
[root@docker-node1 ~]# docker inspect bridge | grep -i contain -A 7
"Containers": {
"83f4810810d6985b92436f314d8cd802eaa7a0aa9f462abf23253bb78278542b": {
"Name": "web",
"EndpointID": "9003ffcaaa827d84f8085e40dd1b25e4078b98c39d8d65ab607bc6265129be3e",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
也可以通过brctl查看连接在网桥的接口
[root@docker-node1 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242ceeff75d no veth42609b7
9.3、Docker容器网络标准:CNM(container network model)
9.3.1、CNM模型
CNM主要有3个概念:
- Network Sandbox(沙盒):一个Sandbox包含了一个容器网络栈的信息。sandbox可以对容器的接口、路由和DNS设置等进行管理。sandbox的实现可以是Linux network namespace、FreeBSD jail或者类似的机制。一个sandbox可以有多个Endpoint和多个或者网络。
- Endpoint:Endpoint作为Sandbox接入Network的介质,是Network Sandbox和Backend Network的中间桥梁。对应的技术实现有veth pair设备、tap/tun设备、OVS内部端口等。
- Backend Network: 一组可以直接相互通信的Endpoint集合。对应的技术实现包括Linux Bridge、VLAN等。一个Backend Network可以包括多个Endpoint。
除此之外,CNM还需要依赖另外两个关键的对象来完成Docker的网络管理功能,分别是:
- Network Controller:对外提供分配及管理网络的APIs,Docker libnetwork支持多个网络驱动,Network Controller允许绑定特定的驱动到指定的网络;
- Driver:网络驱动对用户而言是不直接交互的,它通过插件式的接入方式,提供最终网络功能的实现。Driver负责一个Network的管理,包括资源分配和回收。
CNM还支持标签(label)。label是以key-value对定义的元数据,用户可以通过定义label这样的元数据自定义libnetwork和驱动的行为。
9.3.2、CNM的原生实现——libnetwork
libnetwork是Docker团队将Docker的网络功能从Docker核心代码种分离出去,用Go语言实现的一个独立库。libnetwork通过插件的形式为Docker容器提供网络功能。
libenetwork为Docker Daemon和网络驱动提供了接口。每次用户通过命令行与或API提交的与网络相关的指令,都会先在Docker Daemon预处理,再根据驱动类型调用libnetwork模块的相应实现。
9.3.3、Docker Daemon与libnetwork调用过程:
(1)Docker Daemon创建网络控制器(Controller)实例,入参包括Docker层面的网络全局参数(genericOption),例如默认的bridge模式、kv store等。
(2)网络控制器创建网络,入参包括网络名、类型和对应驱动参数。从这里开始会调用至真正的驱动实现。
(3)创建Endpoint,进行IP分配,准备网络设备接口。
(4)创建Sanbox,使用容器对应的网络名称空间,包含该容器的网络环境。
(5)已有的Endpoint加入Sandbox,完成容器与网络设备的对接。
9.3.4、libnetwork架构图:
9.4、容器网络组网类型
针对overlay和underlay,容器组网类型有L2 overlay、L3 overlay、L2 underlay和L3 underlay。
1.overlay
Overlay网络(又称作叠加网络、覆盖网络)是在传统网络上虚拟出一个虚拟网络来,传统网络不需要在做任何适配。在容器中,物理网络只承载主机间通信,虚拟网络只承载容器间通信。overlay网络的任何协议都要求在发送方对报文进行包头封装,接收剥离包头。
L2 overlay
传统的二层网络范围有限,L2 overlay是构建在底层网络上的大2层网络,"大"可以理解为跨越多个数据中心。
VXLAN(Virtual eXtensible LAN)技术是当前最为主流的Overlay标准。通过在UDP包头封装原始L2报文,实现了容器跨主机通信。
L2 overlay网络容器可以在任意主机间迁移而不改变其IP的地址的特性,使得在构建大二层overlay网络上的容器在动态迁移时具有很高的灵活性。
L3 overlay
L3 overlay与L2 overlay类似,但会在节点上增加一个网关。每个节点的容器都在同一个子网内,二层可以直接通信。跨节点通信要走L3,经过网关转发。性能弱于L2 overlay但是更加灵活,跨界点的容器可以属于不同子网。
2. underlay
underlay可路由的IP网络,支持任何路由协议—-OSFP, EIGRP, IS-IS, BGP等。说白了就是基础物理设施,保证这些基础设施之间路由可达。
L2 undelay
L2 underlay网络就是链路层(L2)互通的底层网络。IPvlan L2模式和Macvlan属于L2 underlay类型的网络。
L3 underlay
L3 underlay组网中,可以选择IPvlan的L3模式,该模式下IPvlan类似路由器的功能,它在各个虚拟网络和主机网络进行不同网络报文的路由转发工作。只要父接口相同,即使虚拟机/容器不在同一个网络,也可以互相ping通对方,因为IP vlan会在中间做报文转发工作。flannel的host-gw模式和caclio的BGP组网方式都属于L3 underlay类型的网络。
9.5、Vxlan介绍
Vxlan(Virtual Extensible Local Area Network)是一种种overlay网络技术,在三层网络的基础上建立二层以太网网络隧道,从而实现跨地域的二层互连,vxlan将原始二层报文前加入VXLAN头部,再通过封装在UDP包中,目的端口为4789。
每一个Vxlan segment均通过24位的segment ID标识。只有在同一个Vxlan segment内的VM可以互相通信。
VXLAN头部包含有一个VXLAN标识(即VNI,VXLAN Network Identifier),只有在同一个VXLAN上的虚拟机之间才能相互通信。VNI在数据包之中占24比特,支持1600多万个VXLAN的同时存在,远多于VLAN的4094个,因此可适应大规模租户的部署。
由于上述这种封装方式,VXLAN也可以称为隧道,将二层网路覆盖在3层之上。隧道是无状态的,每一帧根据设置的规则进行封装。用来建立端点的设备我们称之为(VTEP),封装和解封装都在VTEP节点上进行。
VM和VM之间的通信:
在VXLAN网络下的VM,它们是意识不到VXLAN的存在的,所以在和不同的VM进行通信时,它们会照常发送到达目标的MAC帧。VTEP上的物理主机查找与此VM关联的VNI。然后确定目标MAC地址到远程VTEP的映射。然后将VXLAN封装好的报文发送到远程VTEP,远程VTEP验证VNI的有效性并确定目标的VM是否存在。如果存在,则将数据包剥离VXLAN封装的部分,并传递到目标VM。这中间的过程对VM来说都是透明的。
上述过程并不复杂,但有个前提:通信双方都提前知道彼此的通信信息。所以第一次通信时要考虑如下问题:
- 哪些VTEP需要加入到一个相同的VNI?
- 通信双方如何感知彼此并选择正确的路径传输报文?
针对第一个问题:VTEP通常由管理员进行手动配置,手动指定即可。
针对第二个问题:VXLAN报文要进行传输,必须确定两个地址信息:目的(VM/容器)MAC地址和目的(VM/容器)所在的VTEP的IP地址。获取这些信息的方式有多播和控制中心两种方式。多播就是将同一个VXLAN网络的VTEP加入同一个多播网络。如果需要查询所需信息,在组内发送多播来查询。控制中心是在某个集中式地地方保存所有虚拟机地信息,自动告知VTEP。
10、Docker存储管理
10.1、Docker镜像元数据管理
Docker镜像元数据与镜像文件的存储完全隔离开了。元数据采用从上至下repository、image、layer三个层次。
10.1.1、repository元数据
repository在本地持久化存放于repositories.json文件中
$cat /var/lib/docker/image/overlay2/repositories.json | python -mjson.tool| more
{
"Repositories": {
"coredns/coredns": {
"coredns/coredns:1.1.3": "sha256:b3b94275d97cb24e34af9bb70e8582c312596eaa33716b98b46e0d
ffdab2f6a4",
"coredns/coredns@sha256:a5dd18e048983c7401e15648b55c3ef950601a86dd22370ef5dfc3e72a108aa
a": "sha256:b3b94275d97cb24e34af9bb70e8582c312596eaa33716b98b46e0dffdab2f6a4"
},
……
10.1.2、image metadata
image元数据包括了镜像架构(amd64)、操作系统、镜像默认配置、构建该镜像的容器ID和配置、创建时间、构建镜像的历史信息以及rootfs组成等等。
持久化文件位于/var/lib/docker/image/overlay2(存储驱动)/imagedb/content/sha256/[image_id]
$ ls /var/lib/docker/image/overlay2/imagedb/content/sha256/
272b3a60cd68df630383440192659a313582d35d0d615b4fc0afad9650a8612d
4178113a354d7543664e937767c5f07a3be6431eec1e115426715f221f5bc4dc
4e9f801d2217e98e94de72cefbcb010a7f2caccf03834dfd12a8e60abcaaecfd
52096ee87d0e17bb9aec2d4e2be75d614370d9816f0c8f0d6b058ed274686c08
72d68eecf40cc460a9e7c6fb87aaeb13e2fb611cab6ed9d0cfdd43129696e836
816332bd9d1150597e693bfa47af7b4474be6a0d7bc7b33cefca2ae14c59e75c
b3b94275d97cb24e34af9bb70e8582c312596eaa33716b98b46e0dffdab2f6a4
b8df3b177be232e6de335cd10df2b1c7fb1b3e8e62390e0f25b357b71e97d0fb
d5c25579d0ff8b97e6330a8c38e144a8516ce65f880623a8bb8019f20f54e428
da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e
$ cat /var/lib/docker/image/overlay2/imagedb/content/sha256/272b3a60cd68df630383440192659a313582d35d0d615b4fc0afad9650a8612d | python -mjson.tool | more
{
"architecture": "amd64",
"config": {
"ArgsEscaped": true,
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"sh"
],
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Hostname": "",
"Image": "sha256:22c2dd5ee85dc01136051800684b0bf30016a3082f97093c806152bf43d4e089",
"Labels": null,
"OnBuild": null,
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": null,
……
10.1.3、layer metadata
layer对应镜像层的概念。镜像层包含了一个具体的镜像层文件包,用户下载了某个镜像层之后,Docker 会在宿主机上基于镜像层文件包和 image 元数据构建本地的 layer 元数据,包括 diff、parent、size 等。
Docker 中定义了 Layer 和 RWLayer 两种接口,分别用来定义只读层和可读写层的一些操作,又定义了 roLayer 和 mountedLayer,分别实现了上述两种接口。其中,roLayer 用于描述不可改变的镜像层,mountedLayer 用于描述可读写的容器层。
具体来说,roLayer 存储的内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、父镜像层 parent、graphdriver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。这些元数据被保存在 /var/lib/docker/image/<graph_driver>/layerdb/sha256// 文件夹下。
$ls/var/lib/docker/image/overlay2/layerdb/sha256/0314be9edf00a925d59f9b88c9d8ccb34447ab677078874d8c14e7a6816e21e1/
cache-id diff size tar-split.json.gz
其中 diffID 和 size 可以通过镜像层包计算出来(diff 文件中的ID对应 image 元数据中 diff_id)。chainID 和父镜像层 parent 需要从所属 image 元数据中计算得到。而 cacheID 是在当前 docker 宿主机上随机生成的一个 uuid,在当前的宿主机上,cacheID 与该镜像层一一对应,用于标识并索引 graphdriver 中的镜像层文件。
在 layer 的所有属性中,diffID 采用 SHA256 算法,基于镜像层文件包的内容计算得到。而 chainID 是基于内容存储的索引,它是根据当前层与所有祖先镜像层 diffID 计算出来的,具体算如下:
- 如果该镜像层是最底层(没有父镜像层),该层的 diffID 便是 chainID。
- 该镜像层的 chainID 计算公式为 chainID(n)=SHA256(chain(n-1) diffID(n)),也就是根据父镜像层的 chainID 加上一个空格和当前层的 diffID,再计算 SHA256 校验码。
mountedLayer 存储的内容主要为索引某个容器的可读写层(也叫容器层)的 ID(也对应容器层的 ID)、容器 init 层在 graphdriver 中的ID(initID)、读写层在 graphdriver 中的 ID(mountID) 以及容器层的父层镜像的 chainID(parent)。相关文件位于 /var/lib/docker/image/<graph_driver>/layerdb/mounts/<container_id>/ 目录下。
$ls /var/lib/docker/image/overlay2/layerdb/mounts/195df4f01ba033e0f95a2c62d54a746a9ae3955d06b5b978deee56b16bc7ac40/
init-id mount-id parent
10.2、Docker storage driver
前面提到了镜像分层与写时复制机制,为了支持特性,Docker提供了存储驱动的接口。目前存储驱动的接口有aufs、btrfs、devicemapper、vfs、overlay、zfs。其中vsf不支持写时复制,是为使用volume提供的存储驱动,仅仅做了简单的文件挂载。这些驱动的使用必须要有操作系统的支持。
overlay2
是当前所有受支持的Linux发行版的首选存储驱动程序,不需要任何额外的配置。aufs
是在内核3.13上的Ubuntu 14.04上运行的Docker 18.06及更早版本的首选存储驱动程序,而该内核不支持overlay2
。devicemapper
,direct-lvm
对于生产环境是必需的,因为loopback-lvm
虽然零配置性能很差。devicemapper
是CentOS和RHEL的推荐存储驱动程序,因为它们的内核版本不支持overlay2
。但是,当前版本的CentOS和RHEL现在支持overlay2
,这是推荐的驱动程序。- 如果
btrfs
和zfs
驱动程序是备用的文件系统(需要在docker主机上安装)。这些文件系统允许使用高级选项,例如创建“快照”,但需要更多的维护和设置。 - 该
vfs
存储驱动程序用于测试目的,以及无法使用写时复制文件系统的情况。此存储驱动程序的性能很差,通常不建议在生产中使用。
存储驱动程序可以让我们在容器的可写层中创建数据。 但删除容器后文件将不会保留
images and layer
Docker image由许多layer组成,每个layer都代表了dockerfile中的一条指令。以下方dockerfile为例
FROM centos:7
RUN yum install httpd -y
RUN echo 'hello world' >> /var/www/html/index.html
ENTRYPOINT ["/usr/sbin/httpd","-D","FOREGROUND"]
$docker build -t centos-httpd:v1 .
Sending build context to Docker daemon 251.4kB
Step 1/4 : FROM centos:7
---> 5e35e350aded
Step 2/4 : RUN yum install httpd -y
---> Running in cefbde3aa41c
Removing intermediate container cefbde3aa41c
---> 476de99b7212
Step 3/4 : RUN echo 'hello world' >> /var/www/html/index.html
---> Running in 9a0c075aed13
Removing intermediate container 9a0c075aed13
---> d1f92d2fb3a6
Step 4/4 : ENTRYPOINT ["/usr/sbin/httpd","-D","FOREGROUND"]
---> Running in 052aa5ec149d
Removing intermediate container 052aa5ec149d
---> 29e91cdfd78a
Successfully built 29e91cdfd78a
Successfully tagged centos-httpd:v1
可以看到构建镜像时的4步。
每层仅是与其之前的层的一组差异。 这些层堆叠起来。 创建新容器时,可以在基础层之上添加新的可写层。 该层通常称为“container layer”。下图显示了基于Ubuntu 18.04映像的容器。
container and layer
container和image最主要的区别就是container最顶端有一层可写层。容器中增加新数据都存储在可写层,容器删除后可写层也会被删除。基础镜像不变。每个容器都有自己的可写层,多个容器可以共享底层镜像。
10.2.1、aufs storage driver
aufs(advanced multi layered unification filesystem)是一种支持联合挂载的文件系统,可以将不同目录挂载到同一个目录下,最终显示为一个目录,这些目录aufs术语称为”branch“,Docker中称为“层“。
下图显示了基于ubuntu:latest映像的Docker容器
Docker 使用aufs读取情景:
- 文件在container layer不存在:如果container打开一个文件,这个文件在container层不存在,存储驱动程序从容器层下方的层开始,在image layer中搜索文件。知道找到并打开。
- 文件只存在于container layer:直接读取。
- 文件既在container layer也在image layer:如果文件存在于container layer也存在于一个或多个image layer,会读取container layer的文件,并使image layer的同名文件隐藏。
修改文件或者目录情景:
-
第一次写入一个文件:容器第一次写入现有文件时,该文件在容器中不存在(在image layer 存在)。 aufs驱动程序执行copy_up操作,将文件从存在文件的image layer复制到可写的container layer。然后修改container layer的文件。
AUFS是文件级别而不是块级别工作。这意味着所有copy_up操作都将复制整个文件,即使该文件非常大且只有一小部分正在被修改。这会对容器写入性能产生明显影响。在具有多层的image中搜索文件时,AUFS可能会出现明显的延迟。但是,值得注意的是,copy_up操作仅在第一次写入给定文件时发生。随后对同一文件的写入将对已经复制到容器的文件副本进行操作。
-
删除文件或目录:
- 当在容器内删除文件时,将在容器层中创建whiteout文件。 不会删除image layer中文件的版本(因为image layer是只读的)。 但是,whiteout file会阻止容器使用它。
PS:whiteout
file,可以理解为在container layer 中已经删除,在imager layer仍然存在。类似于占位的效果。 - 在容器内删除目录时,将在container layer中创建一个
opaque
file。 此功能与whiteout file相同,并且有效。
- 当在容器内删除文件时,将在容器层中创建whiteout文件。 不会删除image layer中文件的版本(因为image layer是只读的)。 但是,whiteout file会阻止容器使用它。
10.2.2、btrfs storage driver
Btrfs是下一代写时复制文件系统,它支持许多高级存储技术,使其非常适合Docker。 Btrfs包含在主线Linux内核中。
Btrfs storage driver利用许多btrfs功能(块级操作、自动精简配置、写时复制)进行image和container管理。我们可以将多个块设备整合到Btrfs文件系统中。
Prerequisites(先决条件):
- Docker-CE,仅建议在Ubuntu或Debian使用btrfs。
- Docker-EE,CS-Engine,btrfs仅支持SLES。
- 更改驱动会使已经创建的容器不可用。建议将已有的容器save保存出来。
- btrfs需要专用的块存储设备,例如物理磁盘。 该块设备必须为Btrfs格式化并安装到/var/lib/docker /中。 默认情况下,SLES 文件系统是使用BTRFS格式化的,因此对于SLES,不需要使用单独的块设备。
配置docker 使用btrfs storage driver
-
停止docker。
-
备份/var/lib/docker目录,如果存在的话。
-
格式化设备为btrfs 文件系统
mkfs.btrfs -f /dev/xxx
-
挂载设备到/var/lib/docker
mount -t btrfs /dev/xxx /var/lib/docker
-
复制/var/lib/docker.bk/*到/var/lib/docker/
cp -au /var/lib/docker.bk/* /var/lib/docker/
-
配置docker使用btrfs,在/etc/docker/daemon.json添加
{ "storage-driver": "btrfs" }
btrfs与其它的存储驱动工作方式不同,整个/var/lib/docker目录存储在btrfs卷上。
image layer与container layer存储在/var/lib/docker/btrfs/subvolumes/
内,子目录每一个image和container layer都包含一个目录。最终见到的统一目录由一个层及其所有父层构建而成。subvolume是写时复制的,并根据需要从基础存储池中分配空间。subvolume之间可以嵌套。
只有image base layer存储作为subvolume,其它的所有层以快照方式存储,且仅包含差异内容,如下图所示:
快照与subvolume类似,却很小,更节省空间。写入复制用于最大化存储效率和最小化层大小,并且在可写层中写入。下图为subvolume与快照共享数据。
每个image layer或者container layer都以subvolume或者快照的方式存储在/var/lib/docker/btrfs/subvolumes/
目录中。image base layer作为subvolume,image layer与container layer作为快照。如下图所示。
btrfs读写文件情景:
读取:启动容器的image实际上是一个快照,快照中的元数据指向存储池中的实际数据块,subvolume也是如此。
写入:
- 写入一个新文件:向容器中写入文件会调用按需分配操作来将新的数据块分配给容器快照。然后将文件写入到这个新的数据块中。向subvolume写入亦是如此。按需分配操作时btrfs固有的。
- 修改存在文件: 更新容器中的现有文件是写时复制操作。从文件当前所在的层读取原始数据,只有修改后的块被写入容器的可写层。然后Btrfs驱动程序更新快照中的文件系统元数据以指向此新数据。
- 删除文件或目录:如果容器删除存在于较低层中的文件或目录,则Btrfs将掩盖较低层中文件或目录的存在。如果容器创建一个文件然后将其删除,则此操作在Btrfs文件系统本身中执行,并回收空间。
10.2.3、devicemapper storage driver
Device Mapper是内核的基本框架,许多高级"volume"管理需要它的支撑。Docker 的devicemapper storage driver 利用此框架的thin-provisioning(精简配置)和snapshotting(快照)实现image和container管理。devicemapper在docker中使用块设备,块级别运行而不是文件级别。可以通过向Docker主机添加物理存储来扩展。
Device Mapper包括3个概念:映射设备(mapped device)、映射表(mapping table)和目标设备(target device)。映射设备是内核向外提供的逻辑设备。一个映射设备通过映射表与多个目标设备映射起来。映射表包含了多个元组,每个多元组记录了这个映射设备的起始地址、范围与一个目标设备的地址偏移量的映射关系。
目标设备可以是一个物理设备,也可以是一个映射设备,这时映射设备可以继续向下迭代。
linux中提供了dmsetup的命令行工具可以创建删除device mapper设备。
配置docker使用devicemapper
-
停止docker
-
编辑
/etc/docker/daemon.json
{ "storage-driver": "devicemapper" }
-
启动docker
Docker中的devicemapper
devicemapper有两种模式:
loop-lvm
:docker默认使用此模式。loop-lvm使用了"loopback"机制,这个机制允许从本地磁盘上读取和写入文件,就像它们是实际的物理磁盘或块设备一样。添加"loopback"机制以及与OS 文件系统交互,意味着IO操作会很慢而且占用大量资源。direct-lvm
:生产中推荐使用此模式。此模式使用块设备创建thin pool,这比使用loopback设备会快很多。并且块设备可以根据需要来扩容。
配置direct-lvm模式修改/etc/docker/daemon.json
:
{
"storage-driver": "devicemapper", # 指定存储驱动类型为devicemapper
"storage-opts": [
"dm.directlvm_device=/dev/xdf", # 指定块设备。此选项必须配置,下面的配置可选,且下方配置的值都为默认值。
"dm.thinp_percent=95", # 指定设备使用空间的百分比。
"dm.thinp_metapercent=1", # 指定元数据使用设备空间的百分比。
"dm.thinp_autoextend_threshold=80", # 指定使用空间的阈值,到达此阈值自动扩容。
"dm.thinp_autoextend_percent=20", # 自动扩容增加空间的百分比。
"dm.directlvm_device_force=false" # 若文件系统存在,是否进行格式化。false表示不进行格式化。
]
}
配置案例:
-
准备一个块设备,本例为/dev/vdb
-
安装依赖包:
-
RHEL / CentOS:
device-mapper-persistent-data
,lvm2
, and all dependenciesyum install device-mapper-persistent-data lvm2 -y
-
Ubuntu / Debian:
thin-provisioning-tools
,lvm2
, and all dependencies
-
-
创建pv
[root@docker ~]# pvcreate /dev/vdb Physical volume "/dev/vdb" successfully created.
-
创建名为’docker’ 的vg
[root@docker ~]# vgcreate docker /dev/vdb Volume group "docker" successfully created
-
创建两个thin pool一个名为
thinpool
一个名为thinpoolmeta
最后一个参数指定可用空间量[root@docker ~]# lvcreate --wipesignatures y -n thinpool docker -l 95%VG Logical volume "thinpool" created. [root@docker ~]# lvcreate --wipesignatures y -n thinpoolmeta docker -l 1%VG Logical volume "thinpoolmeta" created.
-
修改存储位置
[root@docker ~]# lvconvert -y \ --zero n \ -c 512K \ --thinpool docker/thinpool \ --poolmetadata docker/thinpoolmeta Thin pool volume with chunk size 512.00 KiB can address at most 126.50 TiB of data. WARNING: Converting docker/thinpool and docker/thinpoolmeta to thin pool's data and metadata volumes with metadata wiping. THIS WILL DESTROY CONTENT OF LOGICAL VOLUME (filesystem etc.) Converted docker/thinpool and docker/thinpoolmeta to thin pool.
-
增加如下配置。
[root@docker ~]# cat /etc/lvm/profile/docker-thinpool.profile activation { thin_pool_autoextend_threshold=80 # 指定自动扩容前使用的空间百分比。 thin_pool_autoextend_percent=20 # 自动扩容时,扩容的容量。 }
-
使用
lvchange
应用配置文件[root@docker ~]# lvchange --metadataprofile docker-thinpool docker/thinpool Logical volume docker/thinpool changed. [root@docker ~]# lvs LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert home centos -wi-ao---- 46.99g root centos -wi-ao---- 50.00g swap centos -wi-ao---- 2.00g thinpool docker twi-a-t--- <95.00g 0.00 0.01
-
配置
daemon.json
[root@docker ~]# cat /etc/docker/daemon.json { "storage-driver": "devicemapper", "storage-opts": [ "dm.thinpooldev=/dev/mapper/docker-thinpool", "dm.use_deferred_removal=true", "dm.use_deferred_deletion=true" ] }
-
重启docker
devicemapper工作方式
使用lsblk可以看到设备和pool
[root@docker ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sr0 11:0 1 1024M 0 rom
vda 252:0 0 100G 0 disk
├─vda1 252:1 0 1G 0 part /boot
└─vda2 252:2 0 99G 0 part
├─centos-root 253:0 0 50G 0 lvm /
├─centos-swap 253:1 0 2G 0 lvm [SWAP]
└─centos-home 253:2 0 47G 0 lvm /home
vdb 252:16 0 100G 0 disk
├─docker-thinpool_tmeta 253:3 0 1020M 0 lvm
│ └─docker-thinpool 253:5 0 95G 0 lvm
└─docker-thinpool_tdata 253:4 0 95G 0 lvm
└─docker-thinpool 253:5 0 95G 0 lvm
在/var/lib/docker/devicemapper/
目录下我们可以看到metadata
文件与mnt
文件
[root@docker ~]# ls /var/lib/docker/devicemapper/
metadata mnt
- metadata目录包含devicemapper配置本身以及存在的每个image和container layer的元数据。 devicemapper存储驱动程序使用快照,并且此元数据包括有关那些快照的信息。 文件为JSON格式。
- mnt目录:container的可写层挂载在这里。包含每个存在的image和container layer的挂载点。 image layer挂载点为空,容器的挂载点显示为容器文件系统中所显示的。
每个imager layer都是其下一层的快照。每个image的最低层是pool中存在的base device的快照。运行容器时,它是容器所基于的image的快照。
下图显示了具有两个正在运行的容器的Docker主机。第一个是ubuntu容器,第二个是busybox容器。
使用devicemappper容器的读写操作
devicemapper读取发生在块级别。下图展示了读取文件的过程:
以读取0x44f
块为例:当一个应用程序要读取容器这个块的时候,由于容器是image的快照,它没有这个块,但是它有一个指向存在它的最近父镜像上该块的指针,并从那里读取该块。这个块也已存在于容器的内存中。
写入操作
- 写入新文件:使用devicemapper驱动,通过按需分配(allocate-on-demand)操作将新数据写入容器。新文件的每个块都分配在容器的可写层中,并将该块写入那里。
- 更新现有文件:从文件所在的最近层读取文件的相关块。当容器写入文件时,只有修改后的块才被写入容器的可写层。
- 删除文件或目录:当删除容器的可写层中的文件或目录时,或者当image layer删除其父层中存在的文件时,devicemapper存储驱动程序会拦截对该文件或目录的进一步读取尝试并做出响应该文件或目录不存在。
- 写入然后删除文件:如果容器先写入文件,然后再删除文件,则所有这些操作都将在容器的可写层进行。在这种情况下,如果使用direct-lvm,则会释放这些块。如果使用loop-lvm,则可能无法释放这些块。这是在生产中不使用loop-lvm的另一个原因。
10.2.4、overlayFS storage driver
overlayFS是一种新型的联合文件系统与AUFS类似。其速度更快,实现更简单。目前docker提供了两种存储驱动。overlay和overlay2,更推荐使用overlay2,且docker EE不支持overlay
配置docker使用overlay 或者overlay2 存储驱动
编辑 /etc/docker/daemon.json
{
"storage-driver": "overlay2"
}
overlay2是如何工作的:
overlayFS layer 在主机上有两个目录。upper
目录称为upperdir
,lower
目录称为lowerdir
,这两个目录会联合挂载到一个目录,称为merged
目录。我们最终看到的即是merged
目录。
overlay2驱动本身支持128个lower
overlayFS layer。可为与layer相关的Docker命令(例如docker build和docker commit)提供更好的性能,并在支持文件系统上消耗更少的inode。
磁盘上的 Image and container layers
启动docker后在/var/lib/docker/overlay2/l
l为小写的L,此目录下的短标识符文件链接指向了/var/lib/docker/overlay2/
的内容,这是为了避免mount的参数超过大小限制。同样/var/lib/docker/overlay2/[ID]/diff
的文件内容对应/var/lib/docker/overlay2/l/
下的短标识符文件。diff
目录包含了该层的内容。
[root@docker ~]# ls -al /var/lib/docker/overlay2/
总用量 0
drwx------. 4 root root 87 4月 17 18:33 .
drwx--x--x. 14 root root 182 4月 17 18:24 ..
drwx------. 3 root root 30 4月 17 18:33 80aa0d560ac794837ad23ea19fc83bd5f2e64757a9197d03918a83cffc09e978
drwx------. 2 root root 40 4月 17 18:33 l
[root@docker ~]# ls -al /var/lib/docker/overlay2/l/
总用量 0
drwx------. 2 root root 40 4月 17 18:33 .
drwx------. 4 root root 87 4月 17 18:33 ..
lrwxrwxrwx. 1 root root 72 4月 17 18:33 IKBNBQQZMPYINGB5ETBDHLT7RC -> ../80aa0d560ac794837ad23ea19fc83bd5f2e64757a9197d03918a83cffc09e978/diff
[root@docker ~]# cat /var/lib/docker/overlay2/80aa0d560ac794837ad23ea19fc83bd5f2e64757a9197d03918a83cffc09e978/link
IKBNBQQZMPYINGB5ETBDHLT7RC
最底层包含一个link
和diff
倒数第二层以及每个较高的层包含一个名为lower的文件,该文件表示其父文件;以及一个名为diff的目录,该目录包含其内容。它还包含一个merged
目录,其中包含其父层及其本身的统一内容,以及一个work
目录,供OverlayFS在内部使用。
[root@docker 07c911e07c74ef570fb5c7fc90156f82422f03790146467d1783106d7e2127c5]# ls
diff link lower merged work
可以通过mount
命令观察这几种目录。
mount | grep overlay
overlay on /var/lib/docker/overlay2/07c911e07c74ef570fb5c7fc90156f82422f03790146467d1783106d7e2127c5/merged type overlay (rw,relatime,seclabel,
lowerdir=/var/lib/docker/overlay2/l/PMSVXKTSINTKZ7EXDHOHQLOT2M:/var/lib/docker/overlay2/l/IKBNBQQZMPYINGB5ETBDHLT7RC,
upperdir=/var/lib/docker/overlay2/07c911e07c74ef570fb5c7fc90156f82422f03790146467d1783106d7e2127c5/diff,
workdir=/var/lib/docker/overlay2/07c911e07c74ef570fb5c7fc90156f82422f03790146467d1783106d7e2127c5/work)
overlay 驱动工作方式
上图展示了一个image layer(作为lowerdir)和container layer(作为upperdir),以及一个统一的目录(merged)。如果container layer和imager layer包含了相同的文件,在container挂载点会显示container layer的文件。将image layer文件隐藏。
overlay与overlay2进行读写过程:
-
Read files:
若读取的文件在container layer不存在会在image layer进行查找。一直到找到并读取其内容。若文件在container layer和image layer都存在,会选择container layer进行读取,隐藏image layer的同名文件。
-
Modify files:
-
第一次写入一个文件(文件在container layer不存在,存在于image layer):overlay(2)会执行
copy_up
操作将image layer(upperdir)文件复制到container (upperdir)。然后修改在container的文件。但是,OverlayFS是块级别的操作,而不是文件级别,所以,就算是我们只修改一个很大的文件其中一小部分内容,
copy_up
操作都会复制整个文件。这对容器写入性能影响很大。值得注意的是:copy_up
操作只在第一次写入文件时发生。- OverlayFS仅需要两层。这意味着性能比AUFS要好,且AUFS在image layer中搜索文件会出现明显的延迟。因为要遍历很多层,overlayfs2只在初次读取时性能弱于overlayfs,而且会缓存结果,所以相对来说
copy_up
操作代价很小。
-
删除文件或目录:
- 当在container删除一个文件,会在container创建一个whiteout file。image layer的文件不会删除,因为是只读的。并且whiteout file会阻止使用它。
- 当container删除一个目录,会在container(upperdir)创建一个
opaque
目录。即使目录存在于imager layer(lowerdir),也会阻止container访问它
-
重命名文件:仅当源路径和目标路径都在顶层时,才允许为目录调用rename(2)。否则,它将返回EXDEV错误(“不允许跨设备链接”)。应用程序需要设计为处理EXDEV并退回到“copy and unlink(复制和取消)”策略。
-
10.2.5、ZFS storage driver
ZFS是下一代文件系统,它支持许多高级存储技术,例如卷管理,快照,校验和,压缩和重复数据删除,复制等。
它由Sun Microsystems(现为Oracle Corporation)创建,并以CDDL许可证开源。 由于CDDL和GPL之间的许可不兼容,因此ZFS不能作为主线Linux内核的一部分提供。 但是,Linux上的ZFS(ZoL)项目提供了树外内核模块和用户空间工具,可以分别安装它们。且目前不建议在Linux上使用VFS
- ZVS需要专用的块设备,最好是SSD
- Docker-CE ZFS仅支持Ubuntu14.04以及更高版本。且需要安装
zfs
包(>=16.04)或者zfs-native
andubuntu-zfs
包(14.04) 。 - ZVF不支持Docker-EE、CS-Engine,以及一些其它Linux平台
/var/lib/docker
目录必须是ZFS格式文件系统。
配置docker使用zfs存储驱动
- 在专用设备创建新的
zpool
,名称为zpool-docker
zpool create -f zpool-docker -m /var/lib/docker /dev/xxx
- 配置docker使用
vfs
,编辑/etc/docker/daemon.json
{
"storage-driver": "zfs"
}
zfs storage driver工作方式
zfs对象:
- filesystems: 精简配置,并根据需要从zpool分配空间。
- snapshots: 只读的。某一时刻文件系统映像的技术
- clones: 快照的可读写副本。 用于存储与上一层的差异。
创建一个clone的过程如下:
- 从文件系统创建一个只读的快照。
- 从快照创建一个可读写的clone,包含了父层的差异。
一个image base layer是ZFS Filesystem。每一个子层都是基于下一层ZFS snapshot 的ZFS clone。container是基于image 顶层创建的ZFS Snapshot的ZFS clone。
上图展示了基于2层image创建一个container。
创建container的步骤大致如下:
-
base layer作为image 存在于一个ZFS 文件系统的Docker主机上。
-
其它层是数据集的clone。image layer在其下方。
在上图中。先从base layer 获取ZFS Snapshot,然后从Snapshot创建Clone,再将"Layer 1"添加至Clone。
该Clone是可写的,并从zpool按需消耗空间。snapshot是只读的,将base layer作为保持不变的对象。
-
运行一个容器时,可写层会添加到image的上面。
在上图中。container 的读写层通过image最顶层的Snapshot创建出一个Clone。
-
容器修改可写层内容时,将更改已分配的空间,默认情况下块大小128k
ZFS读写操作
读取文件:每一个人容器的可写层都是ZFS clone,,它与从其创建的数据集(其父层的快照)共享所有数据。 即使正在读取的数据来自下层,读取操作也很容易。 如下图所示:
写入一个新文件:按需从底层zpool分配空间,并将这些块直接写入container layer。
修改一个已经存在的文件:为要更改的块分配空间,通过写时复制策略将这些快写入容器可写层。
删除一个文件或目录:
- 删除在可写层中创建的文件或目录,zpool会回收这些块。
- 删除较低层的文件或目录时,ZFS会在可写层屏蔽文件或目录的存在。
10.2.6、VFS storage driver
VFS storage driver不是联合文件系统。 相反,每一层都是磁盘上的目录,并且不支持写时复制。 要创建新层,需要对上一层进行"deep copy"。 与其他存储驱动程序相比,这导致性能降低和磁盘上使用的空间更多。 但是,它是健壮,稳定的,并且可以在每种环境中使用。
配置docker使用VFS storage driver,编辑/etc/docker/daemon.json
{
"storage-driver": "vfs"
}
VFS storage driver工作方式
VFS不是联合文件系统。每个image layer和container layer在Docker主机上都表示为/var/lib/docker /
中的子目录。
VFS不支持写时复制(COW),因此每次创建新层时,它都是其父层的深层副本。 这些层都位于/var/lib/docker/vfs/dir /
下。
11、Docker持久化存储与数据共享
11.1、概述:
默认情况下,在容器内创建的所有文件都存储在容器可写层上,这意味着:
当该容器不再存在时,数据将不会持久保存。
容器的可写层与运行容器的主机紧密耦合。不能轻易地将数据移动到其他地方。
写入容器的可写层需要 存储驱动程序来管理文件系统。存储驱动程序使用Linux内核提供联合文件系统。与使用直接写入主机文件系统的数据卷相比,这种额外的抽象降低了性能 。
Docker为容器提供了两个选项来将文件存储在主机中,以便即使容器停止后文件也可以持久存储:卷和绑定。
11.2、卷"volume"
卷是容器上的一个或多个目录,此类目录可绕过联合文件系统,与宿主机上的某目录"绑定"。而当我们删除容器时。不会删除volume。
11.2.1、Docker Managed volume
运行容器时只需要指定容器内的挂载点。而容器内的挂载点绑定了宿主机的哪个目录不需要我们关心。不过一般在/var/lib/docker/文件系统/volume ID
[root@docker-node1 ~]# docker run -dit --name t1 -v /data busybox
通过inspect指令查看下详细信息。/data 与volume的绑定关系,Souce目录,就是容器绑定的宿主机的目录
[root@docker-node1 ~]# docker inspect t1 | grep -i mounts -A 11
"Mounts": [
{
"Type": "volume",
"Name": "16e12198a88db279da69ec19cdedbd09e3eeebacbba9681450657cfb4ae73735",
"Source": "/var/lib/docker/volumes/16e12198a88db279da69ec19cdedbd09e3eeebacbba9681450657cfb4ae73735/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
现在我们在容器内的/data目录下写入些东西。
[root@docker-node1 ~]# docker exec -it t1 /bin/sh
/ # echo "hello" >> /data/test.txt
然后在宿主机查看
[root@docker-node1 ~]# cd /var/lib/docker/volumes/16e12198a88db279da69ec19cdedbd09e3eeebacbba9681450657cfb4ae73735/_data
[root@docker-node1 _data]# cat test.txt
hello
同理,在宿主机创建的文件目录,在容器内也能看到
11.2.2、Bind Mount volume
运行容器时指定宿主机的路径与容器中的路径。即容器内与宿主机的路径都需要人工指定。宿主机目录可以事先不存在,运行时,宿主机会自动创建
将本地/data/t2绑定容器/data 目录。
[root@docker-node1 ~]# docker run -dit --name t2 -v /data/t2:/data busybox
a3723c362d4945bd91f66433fe8e157397cd3d50c346cd92f20a0cdd60ceed29
[root@docker-node1 data]# docker inspect -f {{.Mounts}} t2
[{bind /data/t2 /data true rprivate}]s
11.3、容器间数据共享
多个容器是可以使用同一个卷的
先启动一个容器,并在/data下写一些内容。
[root@docker-node1 ~]# docker run -it --name sharing -v /data/b1:/data busybox
/ # echo $HOSTNAME
ef4645c2608a
/ # echo sharing >> /data/sharing.txt
再启动一个容器同样挂载本地/data/b1
[root@docker-node1 docker run -it --name baipiao -v /data/b1:/data busybox
/ # cat /data/sharing.txt
sharing
11.4、卷的复制–volumes-from
复制其他容器使用的卷,使用–volumes-from选项
[root@docker-node1 ~]# docker run -it --name con1 -v /data/v1:/data busybox
/ #
[root@docker-node1 ~]# docker run -it --name con2 --volumes-from con1 busybox
/ #
通过inspect指令去查看
[root@docker-node1 ~]# docker inspect -f {{.Mounts}} con1
[{bind /data/v1 /data true rprivate}]
[root@docker-node1 ~]# docker inspect -f {{.Mounts}} con2
[{bind /data/v1 /data true rprivate}]
12、docker-compose
12.1、概述
docker-compose是一个运行多容器应用的工具,可以使用yaml文件来配置应用服务,然后再通过一个命令,就可以从配置中创建并启动所有服务。
12.2、docker-compose安装,通过pip
安装pip以及一些依赖包
[root@ceph2 ~]# yum install python2-pip gcc-c++ python-devel -y
更新pip版本
[root@ceph2 ~]# pip install --upgrade pip
通过pip安装docker-compose
[root@docker-node1 ~]# pip install docker-compose
查看docker-compose版本,以验证docker-compose安装成功
[root@docker-node1 ~]# docker-compose version
docker-compose version 1.25.4, build unknown
docker-py version: 4.2.0
CPython version: 2.7.5
OpenSSL version: OpenSSL 1.0.2k-fips 26 Jan 2017
12.3、演示一个官方案例
https://docs.docker.com/compose/gettingstarted/
一个web页面,统计访问次数,每次+1
先看下目录结构
[root@docker-node1 composetest]# tree .
.
├── app.py
├── docker-compose.yml
├── Dockerfile
└── requirements.txt
1.app.py
[root@docker-node1 composetest]# cat app.py
import time
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)
@app.route('/')
def hello():
count = get_hit_count()
return 'Hello World! I have been seen {} times.\n'.format(count)
2.docker-compose.yml
[root@docker-node1 composetest]# cat docker-compose.yml
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
3.Dockerfile
[root@docker-node1 composetest]# cat Dockerfile
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["flask", "run"]
4.requirements.txt
[root@docker-node1 composetest]# cat requirements.txt
flask
redis
启动docker-compose
注意docker-compose命令使用时会查找当前目录下docker-compose.yml文件,也可以-f指定
[root@docker-node1 composetest]# docker-compose up -d
查看状态
[root@docker-node1 composetest]# docker-compose ps
Name Command State Ports
-------------------------------------------------------------------------------------
composetest_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
composetest_web_1 flask run Up 0.0.0.0:5000->5000/tcp
停止docker-compose的话执行 docker-compose down 即可
需要再次启动执行 docker-compose start
测试,访问本地5000端口
[root@docker-node1 composetest]# curl localhost:5000
Hello World! I have been seen 1 times.
[root@docker-node1 composetest]# curl localhost:5000
Hello World! I have been seen 2 times.
[root@docker-node1 composetest]# curl localhost:5000
Hello World! I have been seen 3 times.
12.4、docker-compose.yml属性
容器编排,k8s现在应该是主流,所以下文相关内容也是我的一些基本了解。docker-compose以及docker swarm有兴趣大家可以自行看官方文档了解。下面列举一些常见的基本属性,详细内容可以通过下面链接去官方查看。
https://docs.docker.com/compose/compose-file/
-
version:指定 docker-compose.yml 文件的写法格式,有v1,v2,v3。不过v1已经被淘汰。
-
services:一个services代表一个container,这个container可以从dockerhub的image来创建,也可以从本地Dockerfile build出来的image来创建。
-
build:可以将build指定为包含构建context路径的字符串,如果是相对路径,那么会相对于compose的路径进行查找。
version: "3.7" services: webapp: build: context: ./dir dockerfile: Dockerfile-alternate args: buildno: 1
如果指定image构建,compose会使用在image中指定的webapp和可选tag来命名构建的image:
build: ./dir image: webapp:tag
-
dns:配置 dns 服务器,可以是一个值或列表
dns: 8.8.8.8 dns: - 8.8.8.8 - 9.9.9.9
-
entrypoit: 默认入口,与dockerfile中相似
entrypoint: /code/entrypoint.sh entrypoint: ["php", "-d", "memory_limit=-1", "vendor/bin/phpunit"]
-
**env_file:**从文件中获取环境变量。可以是单个,也可以是多个,以列表的方式
env_file: .env env_file: - ./common.env - ./apps/web.env - /opt/runtime_opts.env
-
environment:环境变量配置,可以用数组或字典两种方式
environment: RACK_ENV: development SHOW: 'true' SESSION_SECRET: environment: - RACK_ENV=development - SHOW=true - SESSION_SECRET
-
**expose:**公开一个端口,而不是发布出去。
expose: - "3000" - "8000"
-
**healthcheck:**健康检查,检查一个容器是否健康。
healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 1m30s timeout: 10s retries: 3 start_period: 40s
-
**image:**指定启动container的image,可以是repository/tag 格式或者 image ID.
image: redis image: ubuntu:18.04 image: tutum/influxdb image: example-registry.com:4000/postgresql
-
**logging:**配置service日志,driver指定日志驱动
logging: driver: syslog options: syslog-address: "tcp://192.168.0.42:123" 默认值为json-file driver: "json-file" driver: "syslog" driver: "none"
-
**network_mode:**网络模式
注意:如果部署是docker swarm模式,则忽略此选项,而且此选项不能与links选项混合使用
network_mode: "bridge" network_mode: "host" network_mode: "none" network_mode: "service:[service name]" network_mode: "container:[container name/id]"
-
**restart:**重启策略,
no:是默认的重启策略,在任何情况下都不会重启容器。
always:如果指定了此策略,则总是会重启。
on-failure:如果退出代码表示失败时,则进行重启
restart: "no" restart: always restart: on-failure restart: unless-stopped
-
**sysctls:**设置内核参数
sysctls: net.core.somaxconn: 1024 net.ipv4.tcp_syncookies: 0 sysctls: - net.core.somaxconn=1024 - net.ipv4.tcp_syncookies=0
-
**volumes:**挂载主机路径或者卷。
version: "3.7" services: web: image: nginx:alpine volumes: - type: volume source: mydata target: /data volume: nocopy: true - type: bind source: ./static target: /opt/app/static db: image: postgres:latest volumes: - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock" - "dbdata:/var/lib/postgresql/data" volumes: mydata: dbdata:
13、Docker Swarm
13.1、What is Swarm
嵌入在Docker Engine中的集群管理和编排功能。
13.2、Worker、Manager、Services、Tasks介绍
在一个swarm中,Node有两种身份,worker(运行services)或者Manager(管理成员以及task委派),也可以同时处于两种身份,既是worker也是Manager。
Node:一个node就是集群中的一个实例。
如果部署我们的应用程序到swarm集群,首先提交定义的服务到manager节点。manager节点再将task调度到worker几点。
Manager节点执行编排和集群管理功能。Manager节点选举一个Leader来执行编排任务。
Worker节点接收并执行从管理程序节点分派的tasks。默认情况下,Manager节点也将服务作为工作程序节点运行,也可以将它们配置为以独占方式运行管理任务,并且仅作为管理节点。
Services,一个Services是将要执行的tasks的定义,它指定了启动容器的镜像、要执行的命令等等。
tasks,一个task携带docker 容器以及在其内部执行的命令,他是swarm集群的原子调度单元。Manager根据Services中定义的规模分配tasks到Worker节点。
13.3、创建Swarm集群
初始化一个节点,当前节点为manager节点
[root@docker-node1 ~]# docker swarm init --advertise-addr 10.10.128.172
Swarm initialized: current node (qcfgqqiqwwyw6twgu6eglt10y) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3i5bsk03g87nwrsrrk67i3kjje7hl5ffrsv1lmgtmn7onvp56j-e1x8mbwb9bnw3xdcvtre5uuu8 10.10.128.172:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
这时命令返回了提示,加入一个worker节点执行 docker swarm join --token xxx
加入管理节点执行docker swarm join-token manager
执行docker node ls查看下节点,可以发前其管理状态为Leader
[root@docker-node1 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
qcfgqqiqwwyw6twgu6eglt10y * docker-node1 Ready Active Leader 19.03.8
如果后面再想加入节点至集群,也可以通过如下命令查看。
[root@docker-node1 ~]# docker swarm join-token manager
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3i5bsk03g87nwrsrrk67i3kjje7hl5ffrsv1lmgtmn7onvp56j-1x6shlag0q7wx7iye8jtowbjc 10.10.128.172:2377
[root@docker-node1 ~]# docker swarm join-token worker
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-3i5bsk03g87nwrsrrk67i3kjje7hl5ffrsv1lmgtmn7onvp56j-e1x8mbwb9bnw3xdcvtre5uuu8 10.10.128.172:2377
13.4、添加2个worker节点到swarm集群
[root@docker-node2 ~]# docker swarm join --token SWMTKN-1-3i5bsk03g87nwrsrrk67i3kjje7hl5ffrsv1lmgtmn7onvp56j-e1x8mbwb9bnw3xdcvtre5uuu8 10.10.128.172:2377
This node joined a swarm as a worker.
[root@docker-node3 ~]# docker swarm join --token SWMTKN-1-3i5bsk03g87nwrsrrk67i3kjje7hl5ffrsv1lmgtmn7onvp56j-e1x8mbwb9bnw3xdcvtre5uuu8 10.10.128.172:2377
This node joined a swarm as a worker.
13.5、实验
13.5.1、在swarm创建一个service
[root@docker-node1 ~]# docker service create --replicas 1 --name helloworld alpine ping docker.com
docker service create 命令创建一个service
--name 指定service 名字 helloworld.
--replicas 1 指定期望的副本数量
alpine ping docker.com 运行alpine容器并且执行ping docker.com命令
查看service
[root@docker-node1 ~]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
nuryse0rlls0 helloworld replicated 1/1 alpine:latest
13.5.2、查询service的详细信息
docker service inspect --pretty <SERVICE-ID>
[root@docker-node1 ~]# docker service inspect --pretty helloworld
ID: nuryse0rlls0z79glzxlnuwf1
Name: helloworld
Service Mode: Replicated
Replicas: 1
Placement:
UpdateConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
Image: alpine:latest@sha256:b276d875eeed9c7d3f1cfa7edb06b22ed22b14219a7d67c52c56612330348239
Args: ping docker.com
Init: false
Resources:
En
docker service inspect helloworld
[root@docker-node1 ~]# docker service inspect helloworld | more
[
{
"ID": "nuryse0rlls0z79glzxlnuwf1",
"Version": {
"Index": 21
},
"CreatedAt": "2020-03-29T09:04:20.21963271Z",
"UpdatedAt": "2020-03-29T09:04:20.21963271Z",
"Spec": {
"Name": "helloworld",
"Labels": {},
"TaskTemplate": {
"ContainerSpec": {
"Image": "alpine:latest@sha256:b276d875eeed9c7d3f1cfa7edb06b22ed22b14219a7d67c5
2c56612330348239",
"Args": [
"ping",
"docker.com"
],
……
docker service ps <SERVICE-ID>
查看service运行的节点
[root@docker-node1 ~]# docker service ps helloworld
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
g3p3uzyhn5y1 helloworld.1 alpine:latest docker-node1 Running Running 9 minutes ago
13.5.3、扩容service规模
docker service scale <SERVICE-ID>=<NUMBER-OF-TASKS>
将helloworld规模扩容至5
[root@docker-node1 ~]# docker service scale helloworld=5
helloworld scaled to 5
overall progress: 5 out of 5 tasks
1/5: running
2/5: running
3/5: running
4/5: running
5/5: running
verify: Service converged
[root@docker-node1 ~]# docker service ps helloworld
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
g3p3uzyhn5y1 helloworld.1 alpine:latest docker-node1 Running Running 28 minutes ago
kjnzejbdojk9 helloworld.2 alpine:latest docker-node1 Running Running about a minute ago
cj9dwun8cwyg helloworld.3 alpine:latest docker-node2 Running Running 40 seconds ago
az81my1e6tqv helloworld.4 alpine:latest docker-node3 Running Running 56 seconds ago
xzs0xg3638h7 helloworld.5 alpine:latest docker-node3 Running Running 56 seconds ago
13.5.4、删除Service
docker service rm
[root@docker-node1 ~]# docker service rm helloworld
[root@docker-node1 ~]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
13.5.5、Services滚动更新
将service redis:3.0.6 更新至redis:3.0.7
创建service 使用镜像为redis:3.0.6,副本数量为3,更新延迟为10秒
[root@docker-node1 ~]# docker service create \
> --replicas 3 \
> --name redis \
> --update-delay 10s \
> redis:3.0.6
如果在更新期间任务返回失败,更新会暂停,可以通过--update-failure-action参数控制行为。
--update-failure-action string Action on update failure
("pause"|"continue"|"rollback") (default "pause")
查看service详细信息
[root@docker-node1 ~]# docker service inspect --pretty redis
ID: n66hxvggyn4125n7uq1inxc67
Name: redis
Service Mode: Replicated
Replicas: 3
Placement:
UpdateConfig:
Parallelism: 1
Delay: 10s
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Update order: stop-first
RollbackConfig:
Parallelism: 1
On failure: pause
Monitoring Period: 5s
Max failure ratio: 0
Rollback order: stop-first
ContainerSpec:
Image: redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842
Init: false
Resources:
Endpoint Mode: vip
执行service更新docker service update
[root@docker-node1 ~]# docker service update --image redis:3.0.7 redis
redis
overall progress: 3 out of 3 tasks
1/3: running
2/3: running
3/3: running
verify: Service converged
更新执行步骤
1.停止第一个任务。
2.进行已停止任务的更新。
3.启动用于更新任务的容器。
4.如果对任务的更新返回“正在运行”,等待指定的延迟时间,然后开始下一个任务。
5.如果在更新过程中任务返回失败,暂停更新。
[root@docker-node1 ~]# docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
n66hxvggyn41 redis replicated 3/3 redis:3.0.7
13.5.6、将一个节点排除在集群之外
先查看下我们的node,以及node上运行的service。
[root@docker-node1 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
qcfgqqiqwwyw6twgu6eglt10y * docker-node1 Ready Active Leader 19.03.8
mcr1vjejd42yvpmtx7iyb82hv docker-node2 Ready Active 19.03.8
ol5fjxln0d10dlz6wj23vlh14 docker-node3 Ready Active
[root@docker-node1 ~]# docker service ps redis | grep -i run
v5wjbippgdrg redis.1 redis:3.0.7 docker-node1 Running Running 16 minutes ago
uz41ceq9ydz9 redis.2 redis:3.0.7 docker-node2 Running Running 13 minutes ago
g0nupmkwy3xs redis.3 redis:3.0.7 docker-node3 Running Running 15 minutes ago
现在将docker-node2从集群中剔除出去
[root@docker-node1 ~]# docker node update --availability drain docker-node2
docker-node2
现在再观察node,以及node上运行的service。
[root@docker-node1 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
qcfgqqiqwwyw6twgu6eglt10y * docker-node1 Ready Active Leader 19.03.8
mcr1vjejd42yvpmtx7iyb82hv docker-node2 Ready Drain 19.03.8
ol5fjxln0d10dlz6wj23vlh14 docker-node3 Ready Active 19.03.8
[root@docker-node1 ~]# docker service ps redis | grep -i run
v5wjbippgdrg redis.1 redis:3.0.7 docker-node1 Running Running 18 minutes ago
oazw36rm66dc redis.2 redis:3.0.7 docker-node1 Running Running 18 seconds ago
g0nupmkwy3xs redis.3 redis:3.0.7 docker-node3 Running Running 17 minutes ago
观察node节点Availability状态。可以发现node2变为了Drain
[root@docker-node1 ~]# docker node inspect --pretty docker-node3 | grep Availability
Availability: Active
[root@docker-node1 ~]# docker node inspect --pretty docker-node2 | grep Availability
Availability: Drain
所以,若要将node2再加入到集群,将其状态求改为Active即可
[root@docker-node1 ~]# docker node update --availability active docker-node2
docker-node2
[root@docker-node1 ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
qcfgqqiqwwyw6twgu6eglt10y * docker-node1 Ready Active Leader 19.03.8
mcr1vjejd42yvpmtx7iyb82hv docker-node2 Ready Active 19.03.8
ol5fjxln0d10dlz6wj23vlh14 docker-node3 Ready Active
13.6、角色转换
13.6.1、Manager转换为Worker
docker node demote NODE [NODE...]
13.6.2、Worker转换为Manager
docker node promote NODE [NODE...]
14、集群服务通信 Routing Mesh
14.1、Routing Mesh
Docker Engine swarm 模式可以发布service 端口,以使外部资源可以访问它。所有的节点都参与到Ingress Routing Mesh中。这使群集中的每个节点都可以接受群集中运行的任何服务的已发布端口上的连接,即使节点上没有任何任务在运行。Routing Mesh将所有传入请求路由到可用节点上的已发布端口到活动容器。
若要使用Ingress 网络,在启用swarm模式之前,打开7946和4789端口:
7946 TCP/UDP
:容器网络发现。4789 UDP
:容器Ingress网络的端口。
还必须打开swarm节点和需要访问端口的任何外部资源(例如外部负载平衡器)之间的已发布端口。
14.2、发布服务端口
在创建service时用-p
参数或者--publish
来指定发布的端口。
docker service create \
--name <SERVICE-NAME> \
--publish published=<PUBLISHED-PORT>,target=<CONTAINER-PORT> \
<IMAGE>
published 用来指定要绑定到Routing Mesh上的端口。如果不指定该published 端口,则将为每个service task绑定一个随机的高编号端口。
target 用来指定容器内的端口。
<PUBLISHED-PORT>是swarm提供服务的端口。如果您忽略它,则将绑定一个随机的高编号端口。
<CONTAINER-PORT>是其中容器监听的端口。此参数是必需的。
例如:将nginx容器80端口发布到8080端口
[root@docker-node1 ~]# docker service create \
> --name my-web \
> --publish published=8080,target=80 \
> --replicas 2 \
> nginx
qs897hekzqxeggwyl9208umje
overall progress: 2 out of 2 tasks
1/2: running
2/2: running
verify: Service converged
查看下我们的service 可以发现
[root@docker-node1 ~]# docker service ps my-web
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
94r82mvbm0ay my-web.1 nginx:latest docker-node2 Running Running 2 minutes ago
jxzaairn8028 my-web.2 nginx:latest docker-node3 Running Running about a minute ago
当我们访问任何node上的端口8080时,Docker会将我们的请求路由到运行的容器。在群集节点本身上,端口8080可能实际上并未绑定,但Routing Mesh知道如何路由流量并防止发生任何端口冲突。
可以使用docker service update发布现有服务的端口。
docker service update \
--publish-add published=<PUBLISHED-PORT>,target=<CONTAINER-PORT> \
<SERVICE>
14.3、绕过Routing Mesh
我们可以绕过Routing Mesh,当访问给定节点上绑定的端口时,始终可以访问在该节点上运行的service的实例。这称为host
模式
要绕过路由网格,必须使用long --publish
服务并将其设置mode
为host
。如果省略mode
键或将其设置为ingress
,则使用Routing Mesh。
docker service create --name dns-cache \
--publish published=53,target=53,protocol=udp,mode=host \
--mode global \
dns-cache
15、Docker Stack
docker-compose我们用来编排多个service,docker-swarm我们可以用来在集群部署单一的服务,如果要将两者结合起来,就可以通过docker stack来实现。
通过docker stack deploy部署应用,docker-stack.yml与docker-compose.yml文件类似,docker-stack.yml至少是v3版本。
deploy语法格式可以通过下面的官方链接查看
https://docs.docker.com/compose/compose-file/#deploy
下面通过docker stack部署wordpress
docker-stack.yml
version: "3"
services:
web:
image: wordpress
ports:
- 8000:80
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: root
networks:
- my-network
depends_on:
- mysql
deploy:
mode: replicated
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql
networks:
- my-network
deploy:
mode: global
placement:
constraints:
- "node.role==manager"
volumes:
mysql-data:
networks:
my-network:
driver: overlay
部署名为wordpress的stack
[root@docker-node1 ~]# docker stack deploy --compose-file docker-stack.yml wordpress
Creating network wordpress_my-network
Creating service wordpress_mysql
Creating service wordpress_web
列出stack
[root@docker-node1 ~]# docker stack ls
NAME SERVICES ORCHESTRATOR
wordpress 2 Swarm
之后可以通过任意一个节点ip:8000来访问。
16、Docker Secret
secret基本使用
secret可以帮助我们保管一些container运行时需要的敏感数据,例如,用户密码、SSH key一类的。
将secret添加到swarm时,Docker会通过双向TLS连接将secret发送到Manager。 secret存储在加密的Raft日志中。 整个Raft日志将在其它Manager之间复制,从而保证secret的高可用性与一致性。
创建secret的两种方式
方式一:通过stdin
[root@docker-node1 ~]# echo "admin" | docker secret create testone -
iycfyh450jmmnxlvpsvv57ut5
[root@docker-node1 ~]# docker secret ls
ID NAME DRIVER CREATED UPDATED
iycfyh450jmmnxlvpsvv57ut5 testone 6 seconds ago 6 seconds ago
方式二:从文件中读取
[root@docker-node1 ~]# echo "admin" >> pdfile
[root@docker-node1 ~]# docker secret create testtwo pdfile
gqquvex7o9gq069ytr8xoci5a
当授予container secret时,解密后的secret默认会在容器的/run/secrets/<secret_name>中。
也可以通过–secret source=,target=,来指定源文件以及container内的目标文件。
下面运行个redis容器来验证一下。
[root@docker-node1 ~]# docker service create --name redis --secret testone redis:alpine
yrhbyh82n43aav2vo1k04iwd0
overall progress: 1 out of 1 tasks
1/1: running
verify: Service converged
查看下我们的service
[root@docker-node1 ~]# docker service ps redis
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
1iao3h6qr1zd redis.1 redis:alpine docker-node2 Running Running 4 minutes ago
查看下我们的secrets
[root@docker-node2 ~]# docker exec fcc5bc894864 cat /run/secrets/testone
admin
在wordpress service中使用secret
secret语法:https://docs.docker.com/compose/compose-file/#secrets
[root@docker-node1 ~]# cat docker-stack.yml
version: "3.1"
services:
web:
image: wordpress
ports:
- 8000:80
secrets:
- web-pw
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_PASSWORD: /run/secrets/web-pw
networks:
- my-network
depends_on:
- mysql
deploy:
mode: replicated
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
update_config:
parallelism: 1
delay: 10s
mysql:
image: mysql
secrets:
- db-pw
environment:
MYSQL_ROOT_PASSWORD: /run/secrets/db-pw
MYSQL_DATABASE: wordpress
volumes:
- mysql-data:/var/lib/mysql
networks:
- my-network
deploy:
mode: global
placement:
constraints:
- "node.role==manager"
volumes:
mysql-data:
networks:
my-network:
driver: overlay
secrets:
web-pw:
file: ./web-pw
db-pw:
file: ./db-pw
部署wordpress,注意,要在yml文件中使用secrets,version要大于3.1。
[root@docker-node1 ~]# docker stack deploy -c docker-stack.yml wordpress
Creating network wordpress_my-network
Creating secret wordpress_web-pw
Creating secret wordpress_db-pw
Creating service wordpress_web
Creating service wordpress_mysql
参考:
- Docker容器与容器云第2版
- Kubernetes网络权威指南
- https://datatracker.ietf.org/doc/rfc7348/?include_text=1
- https://www.ibm.com/developerworks/cn/linux/l-devmapper/index.html#artrelatedtopics
- https://docs.docker.com/