Blog

Docker

Begin

Linux容器

(Linux Containers,缩写为 LXC) Linux容器是与系统其他部分隔离开的一系列进程,从另一个镜像运行,并由该镜像提供支持进程所需的全部文件。容器提供的镜像包含了应用的所有依赖项,因而在从开发到测试再到生产的整个过程中,它都具有可移植性和一致性。

Linux容器不是模拟一个完整的操作系统 而是对进程进行隔离。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。 容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。

对比

比较Docker和传统虚拟化方式的不同之处:

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程。
  • 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核 且也没有进行硬件虚拟 。因此容器要比传统虚拟机更为轻便。
  • 每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。

为什么Docker比虚拟机快:

  1. Docker有着比虚拟机更少的抽象层:由于Docker不需要Hypervisor(虚拟机)实现硬件资源虚拟化,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上Docker将会在效率上有明显优势。
  2. Docker利用的是宿主机的内核,而不需要加载操作系统OS内核:当新建一个容器时,Docker不需要和虚拟机一样重新加载一个操作系统内核。进而避免引寻、加载操作系统内核返回等比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载OS,返回新建过程是分钟级别的。而Docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个Docker容器只需要几秒钟。

Docker

Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源。

Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。

Docker基本组成

镜像(image)

我们都知道,操作系统分为内核用户空间。对于Linux而言,内核启动后,会挂载root文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个root文件系统。比如官方镜像ubuntu:18.04 就包含了完整的一套Ubuntu 18.04最小系统的root文件系统。

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

容器(container)

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。

仓库(repository)

仓库(Repository)是集中存放镜像文件的场所。

Docker公司提供的官方Registry被称为Docker Hub,存放各种镜像模板的地方。仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是Docker Hub(https://hub.Docker.com/)。

一个Docker Registry中可以包含多个 仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

Docker原理

Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。

更换国内源

在配置JSON中加入:

{
  "registry-mirrors": [
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}

HelloWord

docker run hello-world

常用命令

帮助启动类命令

  • 启动docker:systemctl start docker
  • 停止docker:systemctl stop docker
  • 重启docker:systemctl restart docker
  • 查看docker状态:systemctl status docker
  • 开机启动:systemctl enable docker
  • 查看docker概要信息:docker info
  • 查看docker总体帮助文档:docker --help
  • 查看docker命令帮助文档:docker 具体命令 --help

镜像命令

docker images

列出本机的镜像。

显示列表说明:

  • REPOSITORY:表示镜像的仓库源
  • TAG:镜像的标签版本号
  • IMAGE ID:镜像ID
  • CREATED:镜像创建时间
  • SIZE:镜像大小

OPTIONS说明:

  • -a:列出本地所有的镜像(含历史映像层)
  • -q:只显示镜像ID。

同一仓库源可以有多个 TAG版本,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。

docker search [name]

搜索某个镜像。

OPTIONS说明:

  • –limit:只列出N个镜像,默认25个
  • docker search --limit 5 redis

docker pull [name]

下载镜像。

docker pull [name][:TAG]

如果不指定一个镜像的版本标签,将默认使用 ubuntu:latest 镜像。

docker system df

查看镜像/容器/数据卷所占的空间

docker rmi [id]

  • 删除单个:docker rmi -f 镜像ID
  • 删除多个:docker rmi -f 镜像名1:TAG 镜像名2:TAG
  • 删除全部:docker rmi -f $(docker images -qa)

容器命令

新建+启动容器

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

OPTIONS说明(常用):

  • –name=“容器新名字”:为容器指定一个名称;
  • -d: 后台运行容器并返回容器ID,也即启动守护式容器(后台运行);
  • -i:以交互模式运行容器,通常与 -t 同时使用;
  • -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;
  • -P:随机 端口映射,大写P
  • -p:指定 端口映射,小写p

列出当前所有正在运行的容器

docker ps [OPTIONS]

OPTIONS说明(常用):

  • -a:列出当前所有正在运行的容器 + 历史上运行过
  • -l:显示最近创建的容器。
  • -n:显示最近n个创建的容器。
  • -q:静默模式,只显示容器编号。

退出容器

  • exit:run进去容器,exit退出,容器停止
  • ctrl+p+q:run进去容器,ctrl+p+q退出,容器不停止

启动已停止运行的容器

docker start 容器ID或者容器名

重启容器

docker restart 容器ID或者容器名

停止容器

docker stop 容器ID或者容器名

强制停止容器

docker kill 容器ID或容器名

删除已停止的容器

docker rm 容器ID

一次性删除多个容器实例:

docker rm -f $(docker ps -a -q)
docker ps -a -q | xargs docker rm

启动守护式容器(后台服务器)

#使用镜像centos:latest以后台模式启动一个容器 
docker run -d centos 

问题:然后docker ps -a进行查看,会发现容器已经退出。 说明:Docker容器后台运行,就必须有一个前台进程. 容器运行的命令如果不是那些 一直挂起的命令(比如运行top,tail),就是会自动退出的。

这个是docker的机制问题,比如你的web容器,我们以nginx为例,正常情况下,,我们配置启动服务只需要启动响应的service即可。例如service nginx start。但是这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用。这样的容器后台启动后,会立即自杀因为他觉得他没事可做了。

所以,最佳的解决方案是,将你要运行的程序以前台进程的形式运行。

Redis前后台启动例子:

  • 前台交互式启动:docker run -it redis:6.0.8
  • 后台守护式启动:docker run -d redis:6.0.8

查看容器日志

docker logs 容器ID

查看容器内运行的进程

docker top 容器ID

查看容器内部细节

docker inspect 容器ID

进入正在运行的容器并以命令行交互

  • docker exec -it 容器ID bashShell
  • 重新进入:docker attach 容器ID

区别:

  • attach 直接进入容器启动命令的终端,不会启动新的进程 用exit退出,会导致容器的停止。
  • exec 是在容器中打开新的终端,并且可以启动新的进程 用exit退出,不会导致容器的停止。
  • 推荐使用 docker exec 命令,因为退出容器终端,不会导致容器的停止。

从容器内拷贝文件到主机上

docker cp 容器ID:容器内路径 目的主机路径

导入和导出容器

  • export:导出容器的内容留作为一个tar归档文件
  • import:从tar包中的内容创建一个新的文件系统再导入为镜像

例子:

docker export 容器ID > 文件名.tar
cat 文件名.tar | docker import - 镜像用户/镜像名:镜像版本号

总结

attach    Attach to a running container                 # 当前 shell 下 attach 连接指定运行镜像 
build     Build an image from a Dockerfile              # 通过 Dockerfile 定制镜像 
commit    Create a new image from a container changes   # 提交当前容器为新的镜像 
cp        Copy files/folders from the containers filesystem to the host path   #从容器中拷贝指定文件或者目录到宿主机中 
create    Create a new container                        # 创建一个新的容器,同 run,但不启动容器 
diff      Inspect changes on a container\'s filesystem   # 查看 docker 容器变化 
events    Get real time events from the server          # 从 docker 服务获取容器实时事件 
exec      Run a command in an existing container        # 在已存在的容器上运行命令 
export    Stream the contents of a container as a tar archive   # 导出容器的内容流作为一个 tar 归档文件[对应 import ] 
history   Show the history of an image                  # 展示一个镜像形成历史 
images    List images                                   # 列出系统当前镜像 
import    Create a new filesystem image from the contents of a tarball # 从tar包中的内容创建一个新的文件系统映像[对应export] 
info      Display system-wide information               # 显示系统相关信息 
inspect   Return low-level information on a container   # 查看容器详细信息 
kill      Kill a running container                      # kill 指定 docker 容器 
load      Load an image from a tar archive              # 从一个 tar 包中加载一个镜像[对应 save] 
login     Register or Login to the docker registry server    # 注册或者登陆一个 docker 源服务器 
logout    Log out from a Docker registry server         # 从当前 Docker registry 退出 
logs      Fetch the logs of a container                 # 输出当前容器日志信息 
port      Lookup the public-facing port which is NAT-ed to PRIVATE_PORT    # 查看映射端口对应的容器内部源端口 
pause     Pause all processes within a container        # 暂停容器 
ps        List containers                               # 列出容器列表 
pull      Pull an image or a repository from the docker registry server   # 从docker镜像源服务器拉取指定镜像或者库镜像 
push      Push an image or a repository to the docker registry server    # 推送指定镜像或者库镜像至docker源服务器 
restart   Restart a running container                   # 重启运行的容器 
rm        Remove one or more containers                 # 移除一个或者多个容器 
rmi       Remove one or more images                     # 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除] 
run       Run a command in a new container              # 创建一个新的容器并运行一个命令 
save      Save an image to a tar archive                # 保存一个镜像为一个 tar 包[对应 load] 
search    Search for an image on the Docker Hub         # 在 docker hub 中搜索镜像 
start     Start a stopped containers                    # 启动容器 
stop      Stop a running containers                     # 停止容器 
tag       Tag an image into a repository                # 给源中镜像打标签 
top       Lookup the running processes of a container   # 查看容器中运行的进程信息 
unpause   Unpause a paused container                    # 取消暂停容器 
version   Show the docker version information           # 查看 docker 版本号 
wait      Block until a container stops, then print its exit code   # 截取容器停止时的退出状态值 

Docker镜像

定义

是一种轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等),这个打包好的运行环境就是image镜像文件。

只有通过这个镜像文件才能生成Docker容器实例(类似Java中new出来一个对象)。

UnionFS(联合文件系统)

UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录

Docker镜像加载原理

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。

bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,Linux刚启动时会加载bootfs文件系统, 在Docker镜像的最底层是引导文件系统bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。

所有对容器的改动,无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。

镜像分层好处

  • 镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用。
  • 比如说有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像; 同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

commit操作

docker commit提交容器副本使之成为一个新的本地镜像。

docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[标签名]

Docker中的镜像分层,支持通过扩展现有镜像,创建新的镜像。

本地镜像发布

本地镜像推送到私有库

  1. 下载镜像Docker Registry
  2. 运行私有库Registry,相当于本地有个私有Docker hub
  3. 创建一个新镜像
  4. curl验证私服库上有什么镜像
    • curl -XGET http://192.168.111.162:5000/v2/_catalog
  5. 修改新镜像符合私服规范的Tag
    • docker tag NAME:1.2 192.168.111.162:5000/NAME:1.2
  6. 修改配置文件使之支持http
    • docker默认不允许http方式推送镜像,通过配置选项来取消这个限制。
    • vim /etc/docker/daemon.json
  7. push推送到私服库
    • docker push 192.168.111.162:5000/NAME:1.2
  8. curl验证私服库上有什么镜像
  9. pull到本地并运行

容器数据卷

数据卷

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

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

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会复制到数据卷中(仅数据卷为空时会复制)。

Docker容器产生的数据,如果不备份,那么当容器实例删除后,容器内的数据自然也就没有了。为了能保存数据在docker中我们使用数据卷。

宿主和容器之间映射添加容器卷

添加:

docker run -it -v /宿主机目录:/容器内目录 ubuntu /bin/bash

检验:

docker inspect 容器ID 

其中的source和destination。

读写规则映射

  • 读写(默认)
    • docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:rw 镜像名
    • 默认是rw。
  • 只读
    • 容器实例内部被限制,只能读取不能写
    • docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:ro 镜像名

卷的继承和共享

  • 容器1完成和宿主机的映射
    • docker run -it --privileged=true -v /mydocker/u:/tmp --name u1 ubuntu
  • 容器2继承容器1的卷规则
    • docker run -it --privileged=true --volumes-from 父类 --name u2 ubuntu

常用软件安装

步骤

  1. 搜索镜像
  2. 拉取镜像
  3. 查看镜像
  4. 启动镜像(服务端口映射)
  5. 停止容器
  6. 移除容器

Tomcat

MySQL

使用MySQL镜像:

docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 
docker ps 
docker exec -it 容器ID /bin/bash 
mysql -uroot -p 

修改编码:

SHOW VARIABLES LIKE 'character%' 

实战,挂载数据卷:

docker run -d -p 3306:3306 --privileged=true 
-v /zzyyuse/mysql/log:/var/log/mysql 
-v /zzyyuse/mysql/data:/var/lib/mysql 
-v /zzyyuse/mysql/conf:/etc/mysql/conf.d 
-e MYSQL_ROOT_PASSWORD=123456 
--name mysql 
mysql:5.7 

在conf文件夹内新建my.cnf:

[client]
default_character_set=utf8 
[mysqld] 
collation_server = utf8_general_ci 
character_set_server = utf8 

新建后重启。Docker安装完MySQL并run出容器后,建议请先修改完字符集编码后再新建MySQL库、表、插数据。

Redis

docker run -p 6379:6379 --name myr3 --privileged=true 
-v /app/redis/redis.conf:/etc/redis/redis.conf 
-v /app/redis/data:/data 
-d redis:6.0.8 redis-server /etc/redis/redis.conf

MySQL主从复制

docker run -p 3307:3306 --name mysql-master \ 
-v /mydata/mysql-master/log:/var/log/mysql \ 
-v /mydata/mysql-master/data:/var/lib/mysql \ 
-v /mydata/mysql-master/conf:/etc/mysql \ 
-e MYSQL_ROOT_PASSWORD=root \ 
-d mysql:5.7 
docker run -p 3308:3306 --name mysql-slave \ 
-v /mydata/mysql-slave/log:/var/log/mysql \ 
-v /mydata/mysql-slave/data:/var/lib/mysql \ 
-v /mydata/mysql-slave/conf:/etc/mysql \ 
-e MYSQL_ROOT_PASSWORD=root \ 
-d mysql:5.7 

主my.cnf:

[mysqld] 
## 设置server_id,同一局域网中需要唯一 
server_id=101  
## 指定不需要同步的数据库名称 
binlog-ignore-db=mysql   
## 开启二进制日志功能 
log-bin=mall-mysql-bin   
## 设置二进制日志使用内存大小(事务) 
binlog_cache_size=1M   
## 设置使用的二进制日志格式(mixed,statement,row) 
binlog_format=mixed   
## 二进制日志过期清理时间。默认值为0,表示不自动清理。 
expire_logs_days=7   
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。 
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致 
slave_skip_errors=1062 

从my.cnf:

[mysqld] 
## 设置server_id,同一局域网中需要唯一 
server_id=102 
## 指定不需要同步的数据库名称 
binlog-ignore-db=mysql   
## 开启二进制日志功能,以备Slave作为其它数据库实例的Master时使用 
log-bin=mall-mysql-slave1-bin   
## 设置二进制日志使用内存大小(事务) 
binlog_cache_size=1M   
## 设置使用的二进制日志格式(mixed,statement,row) 
binlog_format=mixed   
## 二进制日志过期清理时间。默认值为0,表示不自动清理。 
expire_logs_days=7   
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断。 
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致 
slave_skip_errors=1062   
## relay_log配置中继日志 
relay_log=mall-mysql-relay-bin   
## log_slave_updates表示slave将复制事件写进自己的二进制日志 
log_slave_updates=1   
## slave设置为只读(具有super权限的用户除外) 
read_only=1 

在从数据库中配置主从复制:

change master to master_host='宿主机ip', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30; 

参数说明:

  • master_host:主数据库的IP地址;
  • master_port:主数据库的运行端口;
  • master_user:在主数据库创建的用于同步数据的用户账号;
  • master_password:在主数据库创建的用于同步数据的用户密码;
  • master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;
  • master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;
  • master_connect_retry:连接失败重试的时间间隔,单位为秒。

大量缓存方案

1~2亿条数据需要缓存,请问如何设计这个存储案例?

哈希取余分区

2亿条记录就是2亿个K&V,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:$hash(key) % N$ 个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

优点:简单,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡和分而治之的作用。

缺点:原来规划好的节点,进行扩容或者缩容比较麻烦,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。某个Redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一致性哈希算法分区

介绍

一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决分布式缓存数据 变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不再适用。

一致性哈希算法目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系

步骤

算法构建一致性哈希环

一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间$[0, 2^{32}-1]$,这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连($0 = 2^{32}$),这样让它逻辑上形成了一个环形空间。

它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对$2^{32}$取模,简单来说, 一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环 ,如假设某哈希函数H的值空间为$0 到 2^{32}-1$(即哈希值是一个32位无符号整形)。

整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推2、3、4、……直到$2^{32}$,也就是说0点左侧的第一个点代表$2^{32}-1$, 0和$2^{32}-1$在零点中方向重合,我们把这个由$2^{32}$个点组成的圆环称为Hash环。

服务器IP节点映射

将集群中各个IP节点映射到环上的某一个位置。

将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的 哈希函数 计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:

key落到服务器的落键规则

当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走” ,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。

如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。

特性

为了在节点数目发生改变时尽可能少的迁移数据。

将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会 顺时针 找到临近的存储节点存放。

而当有节点加入或退出时仅影响该节点在Hash环上 顺时针相邻的后续节点。

优点:

  • 一致性哈希算法的容错性:受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据。
  • 一致性哈希算法的扩展性

缺点:

  • 一致性哈希算法的数据倾斜问题:一致性Hash算法在服务 节点太少时 ,容易因为节点分布不均匀而造成 数据倾斜 (被缓存的对象大部分集中缓存在某一台服务器上)问题

哈希槽分区

介绍

哈希槽实质就是一个数组,数组$[0,2^{14} -1]$形成hash slot空间。

解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。 哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

多少个hash槽

一个集群只能有16384个槽,编号0-16383($0-2^{14} -1$)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。$slot = CRC16(key) % 16384$。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

DockerFile

介绍

Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。

构建三步骤:

  1. 编写Dockerfile文件
  2. docker build命令构建镜像
  3. docker run依镜像运行容器实例

DockerFile构建过程解析

基础:

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. #表示注释
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交

Docker执行Dockerfile的流程:

  1. docker从基础镜像运行一个容器
  2. 执行一条指令并对容器作出修改
  3. 执行类似docker commit的操作提交一个新的镜像层
  4. docker再基于刚提交的镜像运行一个新容器
  5. 执行dockerfile中的下一条指令直到所有指令都执行完成

DockerFile常用保留字指令

命令 解释
FROM 构建镜像基于哪个镜像
MAINTAINER 镜像维护者姓名或邮箱地址
RUN 构建镜像时运行的指令(shell格式&exec格式)
CMD 运行容器时执行的shell环境
(CMD是在docker run时运行。RUN是在docker build时运行。)
Dockerfile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run之后的参数替换
VOLUME 指定容器挂载点到宿主机自动生成的目录或其他容器
USER 为RUN、CMD、和 ENTRYPOINT 执行命令指定运行用户
WORKDIR 为RUN、CMD、ENTRYPOINT、COPY和ADD设置工作目录,就是切换目录
HEALTHCHECH 健康检查
ARG 构建时指定的一些参数
EXPOSE 声明容器的服务端口(仅仅是声明)
ENV 设置容器环境变量
ADD 拷贝文件或目录到容器中,如果是URL或压缩包便会自动下载或自动解压
COPY 拷贝文件或目录到容器中,跟ADD类似,但不具备自动下载或解压的功能
ENTRYPOINT 运行容器时执行的shell命令
类似于CMD指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当作参数送给ENTRYPOINT指令指定的程序。
优点:在执行docker run的时候可以指定ENTRYPOINT运行所需的参数。
注意:如果Dockerfile中如果存在多个ENTRYPOINT指令,仅最后一个生效。

案例

自定义镜像

编写Dockerfile文件:

FROM ubuntu
MAINTAINER NAME<[email protected]> 

ENV MYPATH /usr/local 
WORKDIR $MYPATH 

RUN apt-get update 
RUN apt-get install net-tools 
#RUN apt-get install -y iproute2 
#RUN apt-get install -y inetutils-ping 

EXPOSE 80 

CMD echo $MYPATH 
CMD echo "install inconfig cmd into ubuntu SUCCESS" 
CMD /bin/bash 
docker build -t 新镜像名字:TAG .

虚悬镜像

定义:仓库名、标签都是<none>的镜像,俗称dangling image。

编写Dockerfile文件:

from ubuntu
CMD echo "SUCCESS" 

查看:

docker image ls -f dangling=true

删除:

docker image prune 

Docker网络

作用

  • 容器间的互联和通信以及端口映射
  • 容器IP变动时候可以通过服务名直接网络通信而不受到影响

常用基本命令

Usage: docker network COMMAND

Manage networks

Commands:

  • connect : Connect a container to a network
  • create : Create a network
  • disconnect : Disconnect a container from a network
  • inspect : Display detailed information on one or* more networks
  • ls : List networks
  • prune : Remove all unused networks
  • rm : Remove one or more networks

网络模式

  • bridge模式:使用–network bridge指定,默认使用docker0
  • host模式:使用–network host指定
  • none模式:使用–network none指定
  • container模式:使用–network container:NAME或者容器ID指定

容器实例内默认网络IP生产规则:docker容器内部的ip是有可能会发生改变的。

bridge

Docker服务默认会创建一个docker0网桥(其上有一个docker0内部接口),该桥接网络的名称为docker0,它在 内核层 连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker默认指定了docker0接口的IP地址和子网掩码,让主机和容器之间可以通过网桥相互通信。

网桥docker0创建一对对等虚拟设备接口一个叫veth,另一个叫eth0,成对匹配。

  • 整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);
  • 每个容器实例内部也有一块网卡,每个接口叫eth0;
  • docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,一一匹配。

通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的IP,此时两个容器的网络是互通的。

host

直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行NAT转换。

容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。

none

在none模式下,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息,只有一个lo标识(就是127.0.0.1表示本地回环)。需要我们自己为Docker容器添加网卡、配置IP等。

container

新建的容器和已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。

自定义网络

自定义桥接网络,自定义网络默认使用的是桥接网络bridge。

新建:

docker network create NAME

优点:自定义网络本身就维护好了主机名和ip的对应关系(ip和服务名都能ping通)。

Compose容器编排

介绍

Compose是Docker公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个YAML格式的配置文docker-compose.yml,写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些容器。

Compose 中有两个重要的概念:

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

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

Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Compose项目由Python编写,实现上调用了Docker服务提供的 API 来对容器进行管理。因此,只要所操作的平台支持Docker API,就可以在其上利用Compose来进行编排管理。

步骤

  1. 编写Dockerfile定义各个微服务应用并构建出对应的镜像文件。
  2. 使用docker-compose.yml定义一个完整业务单元,安排好整体应用中的各个容器服务。
  3. 最后,执行docker-compose up命令来启动并运行整个应用程序,完成一键部署上线。

常用命令

docker-compose -h        #  查看帮助 
docker-compose up        #  启动所有 docker-compose服务 
docker-compose up -d     #  启动所有 docker-compose服务 并后台运行 
docker-compose down      #  停止并删除容器、网络、卷、镜像。 
docker-compose exec  yml里面的服务id  # 进入容器实例内部  docker-compose exec  docker-compose.yml文件中写的服务id  /bin/bash 
docker-compose ps        # 展示当前docker-compose编排过的运行的所有容器 
docker-compose top       # 展示当前docker-compose编排过的容器进程
docker-compose logs  yml里面的服务id  #  查看容器输出日志 
docker-compose config    #  检查配置 
docker-compose config -q #  检查配置,有问题才有输出 
docker-compose restart   #  重启服务 
docker-compose start     #  启动服务 
docker-compose stop      #  停止服务 

编排微服务

docker-compose.yml

version: "3" 
  
services: 
  microService: 
    image: zzyy_docker:1.6 
    container_name: ms01 
    ports: 
      - "6001:6001" 
    volumes: 
      - /app/microService:/data 
    networks:  
      - atguigu_net  
    depends_on:  
      - redis 
      - mysql 
  
  redis: 
    image: redis:6.0.8 
    ports: 
      - "6379:6379" 
    volumes: 
      - /app/redis/redis.conf:/etc/redis/redis.conf 
      - /app/redis/data:/data 
    networks:  
      - atguigu_net 
    command: redis-server /etc/redis/redis.conf 
  
  mysql: 
    image: mysql:5.7 
    environment: 
      MYSQL_ROOT_PASSWORD: '123456' 
      MYSQL_ALLOW_EMPTY_PASSWORD: 'no' 
      MYSQL_DATABASE: 'db2021' 
      MYSQL_USER: 'zzyy' 
      MYSQL_PASSWORD: 'zzyy123' 
    ports: 
       - "3306:3306" 
    volumes: 
       - /app/mysql/db:/var/lib/mysql 
       - /app/mysql/conf/my.cnf:/etc/my.cnf 
       - /app/mysql/init:/docker-entrypoint-initdb.d 
    networks: 
      - atguigu_net 
    command: --default-authentication-plugin=mysql_native_password #解决外部无法访问 
  
networks:  
   atguigu_net:  

Portainer可视化工具

Portainer是一款轻量级的应用,它提供了图形化界面,用于方便地管理Docker环境,包括单机环境和集群环境。

安装:

docker run -d -p 8000:8000 -p 9000:9000 
--name portainer 
--restart=always
-v /var/run/docker.sock:/var/run/docker.sock     
-v portainer_data:/data     
portainer/portainer 

Docker容器监控(CIG)

介绍

通过docker stats命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据。

CAdvisor监控收集+InfluxDB存储数据+Granfana展示图表

部署

docker-compose.yml

version: '3.1' 
  
volumes: 
  grafana_data: {} 
  
services: 
 influxdb: 
  image: tutum/influxdb:0.9 
  restart: always 
  environment: 
    - PRE_CREATE_DB=cadvisor 
  ports: 
    - "8083:8083" 
    - "8086:8086" 
  volumes: 
    - ./data/influxdb:/data 
  
 cadvisor: 
  image: google/cadvisor 
  links: 
    - influxdb:influxsrv 
  command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086 
  restart: always 
  ports: 
    - "8080:8080" 
  volumes: 
    - /:/rootfs:ro 
    - /var/run:/var/run:rw 
    - /sys:/sys:ro 
    - /var/lib/docker/:/var/lib/docker:ro 
  
 grafana: 
  user: "104" 
  image: grafana/grafana 
  user: "104" 
  restart: always 
  links: 
    - influxdb:influxsrv 
  ports: 
    - "3000:3000" 
  volumes: 
    - grafana_data:/var/lib/grafana 
  environment: 
    - HTTP_USER=admin 
    - HTTP_PASS=admin 
    - INFLUXDB_HOST=influxsrv 
    - INFLUXDB_PORT=8086 
    - INFLUXDB_NAME=cadvisor 
    - INFLUXDB_USER=root 
    - INFLUXDB_PASS=root 

命令:

vim docker-compose.yml
docker-compose config -q
docker-compose up

参考链接: