一篇文章让你Docker从入门到精通
- Docker简介
- docker的3要素
- docker安装--centos7示例
- docker底层原理
- docker常用命令
- docker镜像原理
- 数据共享--容器数据卷
- 数据卷容器
- dockerfile解析
- Dockerfile实战一 使用dockerfile构建ubuntu镜像,并在里面安装vim及网络工具
- 一张图展示dockerfile,image,container等之间关系
- 本地镜像推送阿里云
- 搭建镜像私服仓库
- 实战二 -- mysql主从复制集群配置
- redis 集群配置实战 -- 大数据分布式缓存设计及redis3主3从扩缩容配置
- redis集群分布式存储hash算法
- 3主3从集群搭建机主从切换容错
- 主从扩容案例
- 主从缩容案例
- docker网络详解
- docker容器编排--docker compose
- 结语
Docker简介
基于Go实现的一个云开源项目,主要是用来对应用组件进行封装、分发、部署、运行等生命周期管理,能够解决跨平台、跨服务器问题,一次配置好环境,可以一键部署到其他机子上;
常用网站:
1、docker官网:http://www.docker.com
2、docker中文网:https://www.docker-cn.com
3、docker hub: https://hub.docker.com/
4、docker文档:https://docs.docker.com/
从哪下docker引擎版方便?(开发者都用引擎版吧🤓)
https://docs.docker.com/engine/install/
docker的3要素
- 镜像 --image,基本可以理解成各种简易操作系统的镜像,可以用来创建docker容器;
- 容器 --container,用镜像创建的运行实例,可以看错一个简易版的linux环境,每个容器之间是相互隔离的,保证安全的平台,docker利用容器来独立运行一个或一组应用,与镜像的区别在于容器最顶层是可读可写的;
- 仓库–repository ,集中存放镜像文件的场所,分为公开仓库和私有仓库,最大的公开仓库如Docker Hub,国内的公开仓库包括阿里云、网易云等,私有仓库下面我们会讲到如何搭建私有仓库,对于需要搭建小型私有仓库需求的公司有用;有个易混淆仓库注册服务器(registry)的概念,仓库注册服务器上通常存放多个仓库,每个仓库肿又包含了多个镜像,每个镜像都有不同的标签。
docker安装–centos7示例
在安装之前,请先自行安装一个虚拟机!!!财大气粗有多个服务器的随意
安装docker环境需保证你的机器能连接网络,同事要预装gcc,在机器上按顺序执行以下命令
yum -y install gcc
yum -y install gcc-c++
yum -y remove docker docker-common docker-selinux docker-engine(卸载旧版本)
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
yum install -y yum-utils device-mapper-persistent-data lvm2
这里插入一条在配置stable镜像仓库时,需要配置为阿里云镜像源或其他国内镜像源,否则下载速度极慢
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum makecache fast(更新yum软件包索引)
yum -y install docker-ce
systemctl start docker
至此,如果你能执行以下两条命令成功,恭喜你,docker安装完成:
docker version
docker run hello-world
配置镜像加速
安装完成后,我们还需要做一个事就是配置镜像加速器,步骤如下:
mkdir -p /etc/docker
vim /etc/docker/daemon.json
往上述daemon.json中写入:
#阿里云
{
“registry-mirrors”: [“https://{自已的编码}.mirror.aliyuncs.com”]
}
或
#网易云
{“registry-mirrors”: [“http://hub-mirror.c.163.com”] }
之后再执行
systemctl daemon-reload
systemctl restart docker即可
阿里云镜像加速器配置
登录阿里云后
https://free.aliyun.com/?product=9564554&crowd=personal&spm=5176.20180516001.0.0.30be4babrVPcyV
选择容器镜像服务,在镜像加速器一栏有自己的镜像加速器地址
此外,我们最好再配置一个本机的docker运行镜像加速器:
vim /etc/sysconfig/docker
将获得的自己账户下的阿里云加速地址配置进
other_args=“–registry-mirror=https://你自己的账号加速信息.mirror.aliyuncs.com”
service docker restart
docker底层原理
docker是怎么工作的
Docker是一个Client-Server结构的系统,Docker守护进程运行在主机上, 然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器 。
docker为什么比VM虚拟机快
(1)docker有着比虚拟机更少的抽象层。由亍docker不需要Hypervisor实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。
(2)docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。仍而避免引寻、加载操作系统内核返个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载Guest OS,返个新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返个过程,因此新建一个docker容器只需要几秒钟。
docker常用命令
帮助命令
docker version
docker info
docker --help
镜像命令
docker images – 列出本地主机上的镜像,可以带参数
-a : 列出本地所有镜像
返回结果中REPOSITORY是镜像仓库源,tag为镜像标签
-q:只显示镜像id
–digests:显示镜像的摘要
–no-trunc:显示完整镜像信息
docker search + xxx镜像名 – 查找镜像
也可以在dockerhub上找,可以带参数
–no-trunc:显示完整镜像描述
上图中,ofiicial标注为ok的表示官方镜像
-s:列出收藏数不小于指定值得镜像
–automated:只列出automated build类型的镜像
docker pull xxx镜像名称 – 下载镜像,镜像名称可带标签,需要注意的是,docker search无法展示镜像标签,要下载指定标签的镜像,需要
在Docker Hub网站上搜索镜像,查看每个镜像的标签。
docker rmi xxx镜像名称或id 删除镜像
docker rmi -f 镜像id 强制删除单个镜像,无论镜像是否启动
docker rmi -f 镜像名1:tage 镜像名2:tag
docker rmi -f $(docker images -qa)删除全部镜像
提交镜像的命令下面我们结合具体例子讲述
容器命令
docker run [OPTIONS] IMAGE [COMMAND] [ARG…]
新建并启动容器
OPTIONS说明(常用):有些是一个减号,有些是两个减号
–name=“容器新名字”: 为容器指定一个名称;
-d: 后台运行容器,并返回容器ID,也即启动守护式容器;
-i:以交互模式运行容器,通常与 -t 同时使用;
-t:为容器重新分配一个伪输入终端,通常与 -i 同时使用;
-P: 随机端口映射;
-p: 指定端口映射,有以下四种格式
ip:hostPort:containerPort
ip::containerPort
hostPort:containerPort
containerPort
如我们 -p 3307:3306,会将docker容器的3306端口绑定到主机3307端口
如我们以交互模式启动一个centos镜像:
docker run -it centos /bin/bash
docker ps [OPTIONS] – 列出所有正在运行的容器
OPTIONS说明(常用):
-a : 列出当前所有 正在运行 的容器 + 历史上运行过 的
-l :显示最近创建的容器。
-n:显示最近n个创建的容器。
-q :静默模式,只显示容器编号。
–no-trunc :不截断输出。
exit: 在docker run启动容器之后,退出容器,会让容器停止;
control + 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前台没有运行的应用,
这样的容器后台启动后,会立即自杀因为他觉得他没事可做了.
查看容器日志的命令:
docker logs
示例:我们让启动一个示例输出日志,在主机用命令查看日志输出
docker run -d centos /bin/sh -c “while true;do echo hello zzyy;sleep 2;done”
docker logs -f -t --tail 容器ID 查看日志,其中-f是跟随输出,-t是加入时间戳
查看容器内进程
docker top 容器ID
查看容器内细节
docker inspect 容器ID
docker进入一个正在运行的容器并以命令行交互:
docker exec -it 容器ID /bin/bash
这里需要是运行中的容器,非运行中容器不可进入
重新进入docker attach 容器ID,与exec的区别是,attach 直接进入容器启动命令的终端,不会启动新的进程,而exec是在容器中打开新的终端,并且可以启动新的进程
attach进入容器后如果退出,容器会停止,exec进入容器后退出,由于是新开进程,容器不会停止
从容器内拷贝文件到主机:
docker cp 容器id:容器内路径 目的主机路径
查看容器变化:
docker diff 容器id
docker events Get real time events from the server # 从 docker 服务获取容器实时事件
docker export Stream the contents of a container as a tar archive # 导出容器的内容流作为一个 tar 归档文件[对应 import ]
docker history Show the history of an image # 展示一个镜像形成历史
docker import Create a new filesystem image from the contents of a tarball # 从tar包中的内容创建一个新的文件系统映像[对应export]
docker load Load an image from a tar archive # 从一个 tar 包中加载一个镜像[对应 save]
docker login Register or Login to the docker registry server # 注册或者登陆一个 docker 源服务器
docker logout Log out from a Docker registry server # 从当前 Docker registry 退出
docker port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT # 查看映射端口对应的容器内部源端口
docker pause Pause all processes within a container # 暂停容器
docker push Push an image or a repository to the docker registry docker server # 推送指定镜像或者库镜像至docker源服务器
docker save Save an image to a tar archive # 保存一个镜像为一个 tar 包[对应 load]
docker tag Tag an image into a repository # 给源中镜像打标签
docker unpause Unpause a paused container # 取消暂停容器
docekr version Show the docker version information # 查看 docker 版本号
docker wait Block until a container stops, then print its exit code # 截取容器停止时的退出状态值
docker镜像原理
镜像是一种轻量级、可执行的独立软件包, 用来打包软件运行环境和基于运行环境开发的软件 ,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
docker镜像基础是UnionFS联合文件系统,一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,镜像可以通过分层来进行继承,基于继承镜像可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
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等等。
。
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等等。
。
平时我们安装进虚拟机的CentOS都是好几个G,为什么docker这里才200M??
对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。 平时我们安装进
虚拟机的CentOS都是好几个G,为什么docker这里才200M??
对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。
docker 分层结构的优势
最大的一个好处就是 - 共享资源
比如:有多个镜像都从相同的 base 镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像,
同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
Docker镜像都是只读的
当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
docker commit从容器生成镜像
命令格式:
docker commit -m=“提交的描述信息” -a=“作者” 容器ID 要创建的目标镜像名:[标签名]
运行中的容器也可以用来生成镜像
数据共享–容器数据卷
为了能让docker容器产生的数据,如果不做特殊处理,容器删除后,数据也会被删除,可以通过commit或者docker cp这种笨重的方式保存,但更便捷的方法是创建数据卷。
docker容器数据卷有以下特点:
1:数据卷可在容器之间共享或重用数据
2:卷中的更改可以直接生效
3:数据卷中的更改不会包含在镜像的更新中
4:数据卷的生命周期一直持续到没有容器使用它为止
5: 数据卷允许容器数据持久化到宿主机上
命令创建容器数据卷
docker run -it -v /宿主机绝对路径目录:/容器内目录 镜像名:镜像标签
可以使用docker inspect 查看是否创建成功
上述命令可增加读写权限:
docker run -it -v /宿主机绝对路径目录:/容器内目录:ro 镜像名:镜像标签
需要注意的是,-v主机目录的方式,在dockerfile中不能直接使用,由于宿主机是不同的,每个宿主机都不一样,不能保证所有的宿主机上都存在这样的特定目录。
使用dockerfile创建容器数据卷
在dockerfile中使用VOLUME指令来给镜像添加一个或多个数据卷
如:
我们将上面构建好的镜像运行起来
可以看到容器中已经存在数据卷了,那么但主机上对应的路径在哪我们怎么知道呢,可以使用docker inspect查看
Docker挂载主机目录Docker访问出现cannot open directory .: Permission denied
解决办法:在挂载目录后多加一个–privileged=true参数即可
数据卷容器
命名的容器挂载数据卷,其它容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器,称之为数据卷容器 ,数据卷的生命周期一直持续到没有容器使用它为止
我们可以使用上面dockerfile构建的镜像,先启动一个容器dc01,然后再使用以下命令启动两个容器dc2和dc3,能看到都有共同的数据卷,在里面创建的文件,再返回dc1查看能被看到:
docker run -it --name dc2 --volumes-from dc1 new_volume_ubuntu
docker run -it --name dc3 --volumes-from dc1 new_volume_ubuntu
dockerfile解析
Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本。
可以理解成可以一键构建镜像的脚本,编写完dockerfile后,docker build 产出镜像,docker run运行镜像
eg:centos7dockerfile
dockerfile语法规则
1、每条保留字指令都必须为大写字母,且后面要跟随至少一个参数
2、指令按照从上到下,顺序执行
3、#表示注释
4、每条指令都会创建一个新的镜像层,并对镜像进行提交
docker执行dockerfile的大致流程
(1) docker 从基础镜像运行一个容器
(2) 执行一条指令并对容器作出修改
(3)执行类似docker commit的操作提交一个新的镜像层
(4) 基于刚提交的镜像运行一个新的容器
(5) 执行dockerfile中的下一条指令直到所有指令都执行完成
从应用软件的角度来看,Dockerfile、Docker镜像与Docker容器分别代表软件的三个不同阶段,
- Dockerfile是软件的原材料
- Docker镜像是软件的交付品
- Docker容器则可以认为是软件的运行态。
Dockerfile面向开发,Docker镜像成为交付标准,Docker容器则涉及部署与运维,三者缺一不可,合力充当Docker体系的基石。
1 Dockerfile,需要定义一个Dockerfile,Dockerfile定义了进程需要的一切东西。Dockerfile涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计namespace的权限控制)等等;
2 Docker镜像,在用Dockerfile定义一个文件之后,docker build时会产生一个Docker镜像,当运行 Docker镜像时,会真正开始提供服务;
3 Docker容器,容器是直接提供服务的。
dockerfile常见保留字
有几个命令需要特别关注:
RUN命令有两种格式,一种是RUN + 命令行命令
RUN yum -y install vim
另外一种是exec格式:
如RUN [“python ./test.py”, “arg1” “arg2”] 等价于RUN python ./test.py arg1 arg2
RUN是在docker build是运行的
USER 命令指定了以什么样用户去执行,如果不指定,则使用root用户
ENV MY_PATH /usr/mytest
这个环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样;
也可以在其它指令中直接使用这些环境变量,
比如:WORKDIR $MY_PATH
ADD 命令将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包
COPY src dest 或COPY [“src”, “dst”]
类似ADD,拷贝文件和目录到镜像中。
将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置
如果容器内路径不存在会自动创建
CMD命令 指定了容器启动后要干的事,与RUN不同点在于RUN是在docker build时运行的,CMD是在容器启动时运行的
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换,也就是说我们通过docker build创建出镜像之后,使用docker run加载镜像启动容器时,如果带参数,则会替换掉原在dockerfile中写好的默认参数
eg:假如我们在Dockerfile中指定了EXPOSE 8080,可以在启动时dcker run -it -p 8080:8080 xxx容器id /bin/bash来覆盖原暴露的端口信息
另外一个类似的命令是
ENTRYPOINT, 但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。ENTRYPOINT的指令格式为:
ENTRYPOINT [“executable”, “para1”, “para2”]
与CMD命令一样,如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
Dockerfile实战一 使用dockerfile构建ubuntu镜像,并在里面安装vim及网络工具
首先我们可以编写Dockerfile如下:
# Dockerpractice
FROM ubuntu
MAINTAINER lzs<lzs@docker.com>
ENV MY_PATH /home
WORKDIR $MY_PATH
RUN apt-get update
RUN apt-get install net-tools
# 下面用-y来自动确认安装需要的参数,也可以用交互式方式让用户自己选
RUN apt-get install -y vim
EXPOSE 80
CMD echo $MY_PATH
CMD echo "install ubuntu ok"
CMD /bin/bash
之后执行镜像构建
docker build -f ./dockerfile -t ubuntu_lzs:prac1 .
等镜像构建好后再运行镜像:
docker run -it ubuntu_lzs:prac1
进入目录后我们可以看到自动就到了home目录,并且有我们挂载的data数据卷
一张图展示dockerfile,image,container等之间关系
本地镜像推送阿里云
前面我们讲了通过dockerfile构建新的镜像的方法,实际上还可以通过docker commit方法来构建新镜像:
docker commit [OPTIONS] 容器ID [REPOSITORY[:TAG]]
现在我们来说下,本地新生成的镜像,应该如何推送到云端,让所有人都能用起来;基本上大公司都会有自己的私有云仓库,这里以阿里云为例。
登陆阿里云选择好容器镜像服务(https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors),
需要按以下步骤操作:
1、进入个人实例,没有的话需要创建;
2、创建命名空间或选择命名空间
3、创建仓库镜像或进入仓库镜像
创建完后会自动进入管理界面,后续再次重进的话,可以从下图位置进入;
之后按照管理界面的操作指南推送即可。推送成功后,可以在个人仓库中看到刚推送的镜像
之后也可以从阿里云下载该镜像到本地使用
搭建镜像私服仓库
上面说了怎么利用阿里云的的仓库上传镜像,那么如果我们想要给公司搭建一个私有的镜像仓库服务应该怎么做呢,下面我们来搭建一个私服仓库。
先拉取registry镜像 docker pull registry
运行私有库Registry,相当于本地有个私有Docker hub,执行命令:
docker run -d -p 5000:5000 -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry
默认情况,仓库被创建在容器的/var/lib/registry目录下,建议自行用容器卷映射,方便于宿主机联调
这时使用curl 查看仓库内会得到空值
curl -XGET http://192.168.111.162:5000/v2/_catalog
这时我们仓库已经搭建好,用类似阿里云的方式,创建镜像推送到私服即可:
上图中已经给镜像打好了tag,我们在push之前都需要先给镜像打tag,格式参照上图,ip为主机ip,port为对外开放的port。
需要注意的是,我们的镜像推送之后在容器内的路径是/var/lib/registry
从容器中的/etc/docker/registry/config.yml文件中也可以看出,推送后的镜像就会推送到该路径下,我们可以将容器卷映射到该路径,这样就方便我们通过主机管理推送的镜像
至此,我们如何搭建私服仓库就讲完了,至于如何搭建一个使用ca证书验证的私服镜像仓库,就留给大家探索了。
实战二 – mysql主从复制集群配置
根据前面我们所讲,我们先拉取一个mysql的docker镜像
docker pull mysql:5.7,要拉取什么版本可以自己从docker hub上找。
启动容器:
启动命令在dockerhub上有说明
docker run -p 3307:3306 --name mysql-master \
-v /home/lzs2/docker_test/mysql_data/mysql-master/log:/var/log/mysql \
-v /home/lzs2/docker_test/mysql_data/mysql-master/data:/var/lib/mysql \
-v /home/lzs2/docker_test/mysql_data/mysql-master/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
可以看到我们服务已经起来了
这时我们需要进入/home/lzs2/docker_test/mysql_data/mysql-master/conf目录下新建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
以上最好手打,复制可能有问题
修改完配置后重启master实例:docker restart mysql-master
重启成功后,进入容器:
docker exec -it mysql-master /bin/bash
并登陆mysql:
mysql -u root -p root,
在master容器实例内创建数据同步从用户:
CREATE USER ‘slave’@‘%’ IDENTIFIED BY ‘123456’;
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON . TO ‘slave’@‘%’;
完了之后我们创建从mysql服务,
docker run -p 3308:3306 --name mysql-master -v /home/lzs2/docker_test/mysql_data/mysql-slave/log:/var/log/mysql -v /home/lzs2/docker_test/mysql_data/mysql-slave/data:/var/lib/mysql -v /home/lzs2/docker_test/mysql_data/mysql-slave/conf:/etc/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
同样的,我们在对应的conf路径下新建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
还是那句话,最好手敲,如果不行把中文注释去了试试,可能你的实例不支持中文;也可以直接在my.cnf中再加上如下内容:
[client]
default_character_set=utf8
[mysqld]
collation_server = utf8_general_ci
character_set_server = utf8
配置完后一样重启从服务,之后在主数据库中查看mysql主从同步状态:
show master status;
接下来我们要进入从数据库中配置主从复制
1、docker exec -it mysql-slave /bin/bash
2、mysql -uroot -proot
3、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:连接失败重试的时间间隔,单位为秒。
4、在从数据库中查看主从同步状态:show slave status \G;
5、此时我们执行start slave; 开启主从同步
再次查看主从同步状态可以看到已经开启同步
至此,我们mysql的主从同步配置完成,我们往主数据库新建一个数据库后新建一个表插入一条数据,在从数据验证是否同步成功即可;
create database my_master_db;
create table my_test_list (id bigint(20) NOT NULL PRIMARY KEY auto_increment, data_value smallint(6) not null default 0, note char(20) not null default '')ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='mater table';
insert into my_test_list (id, data_value, note) values (0, 99, "master db test");
可以在从数据库中看到,数据库表已经同步过来
如果我们把master停掉,会看到
redis 集群配置实战 – 大数据分布式缓存设计及redis3主3从扩缩容配置
redis集群分布式存储hash算法
在我们需要缓存的数据量较小时通常单台redis就可以解决,但如果数据数量有上亿条,单个redis受限于机器内存、查找速度等限制,已经无法满足如此大的数据量存储了,这时就需要redis集群进行分布式存储;
假设有上亿条k,v,我们要将这上亿条k,v存入N台机器,通常有以下集中方案:
方案一:哈希取余分区
计算Key的hash值hash(key),对机器数N取余,余数即对应要存储的目标机器;
优点:
简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:
原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。
为了解决机器宕机后,hash取余算法的hash key变化时,key与之hash值映射变化很大,redis服务器与客户端映射会发生较大变更的场景,有了一致性哈希算法,目的是当服务器个数发生变动时,
尽量减少影响客户端到服务器的映射关系
一致性哈希环
一致性哈希算法必然有个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、……直到232-1,也就是说0点左侧的第一个点代表232-1, 0和232-1在零点中方向重合,我们把这个由232个点组成的圆环称为Hash环。
节点映射
将集群中各个IP节点映射到环上的某一个位置。
将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的 哈希函数 计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:
当我们需要存储一个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上。
假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则 受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据 ,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。
数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,
不会导致hash取余全部数据重新洗牌。
缺点
一致性hash算法虽然能更好地支持弹性扩缩容,但会存在数据倾斜的问题,特别是服务器节点较少的时候,容易因为节点分布不均匀而造成 数据倾斜 (被缓存的对象大部分集中缓存在某一台服务器上)问题,
例如系统中只有两台服务器:
为了解决上述数据倾斜的问题,redis集群在hash环的基础上又加了一层hash槽,可以指定哪些槽分配给哪个节点。
哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间。
在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系 ,现在就相当于节点上放的是槽,槽里放的是数据。 槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。 哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
之所以槽的数量是16384个,因为redis集群实例数量一般不会很多,槽数量太大用来记录槽节点信息的字段也会比较大,导致不必要的流量浪费;
redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上
3主3从集群搭建机主从切换容错
下面我们来实战3主3从的redis集群部署,再在其中强化理解下一致性哈希及哈希槽的应用;
1、拉取一个redis镜像,并基于它启动6个redis容器实例;
docker run -d --name redis-node-1 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381
docker run -d --name redis-node-2 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-2:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6382
docker run -d --name redis-node-3 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-3:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6383
docker run -d --name redis-node-4 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-4:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6384
docker run -d --name redis-node-5 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-5:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6385
docker run -d --name redis-node-6 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386
2、进入容器redis-node-1并为6台机器构建集群关系
进入容器后,执行以下命令,注意把ip换成你自己的ip
redis-cli --cluster create 192.168.163.136:6381 192.168.163.136:6382 192.168.163.136:6383 192.168.163.136:6384 192.168.163.136:6385 192.168.163.136:6386 --cluster-replicas 1
如果报错Could not connect to Redis at 192.168.163.136:6381: No route to host
要么是你防火墙没关,要么是你ip输入有误
可以看到node1,2,3都被指定为master,并且它们的槽数量被自动分配好了,而从机是不被分配独立槽位的
链接进入6381作为切入点,查看节点状态
我们通过6381往集群中存入数据
直接 set k1 v1可能会报错:
添加-c参数优化路由重新登陆即可
这时我们可以看到及集群中存储的信息
如果我们把6381主机停了,会发生什么呢
可以看到,6381停了之后,原其对应的从机6386上位,成了master,而如果我们再次把6381启动,可以看到6381变成6386的从机了
因此,加入我们想要6831变成原先的主机,需要把6386再次停止一次,然后再启动一次即可;
主从扩容案例
假如我们现在需要增加一对redis主从实例,
docker run -d --name redis-node-7 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387
docker run -d --name redis-node-8 --net host --privileged=true -v /home/lzs2/docker_test/redis_volume/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388
进入新启动的6387容器,将6387加入原6381等redis所在的集群中
检查下这时集群的情况:
可以看到6387已经加入了集群,但6388还没,且虽然加入了,但还没分配槽号,因此我们需要对槽号重新分配
再次检查redis集群情况可以看到槽号已经添加成功了
并且槽号是从原槽号各方匀一些组合而成的,因为整个重新分配,成本比较高,原映射关系变更也会比较大;
槽号分配完了,现在还有6388节点没加入进来,按下图执行命令
至此,主从扩容添加一对redis主从节点完成
主从缩容案例
下面我们将新扩的6388和6387下掉来演示主从缩容
1、将6388从集群中删除
可以看到,这时由于有数据,是无法直接删除的,我们需要迁移数据
这里手动迁移其上的两个key
如何知道迁移key对应的slot? 直接执行migrate指定key命令即可
migrate 192.168.163.137 6382 “155” 0 50,会报错
ERR Target instance replied with error: MOVED 11779 192.168.163.137:6387
上面11779即要迁移的slot,按下面步骤迁移即可
案例演示(slot 中 Key 数量少):迁移 3168 slot
(1)登录 source_node: cluster setslot 3168 migrating desc_node_id
(2)登录 desc_node: cluster setslot 3168 importing source_node_id
(3)登录 source_node: cluster getkeysinslot 3168 =》得到只有一个 key f
(4)登录 source_node: migrate 192.168.191.211 6383 “f” 0 30 copy auth 123456
(5)登录 desc_node: cluster setslot 3168 node desc_node_id
(6)登录 source_node: cluster setslot 3168 node desc_node_id
迁移中集群状态:
我们将上面两个槽和key迁移到6382后,
就可以重新执行删除操作了,删除操作也可以在每个节点都执行cluster forget + 实例id的命令。
这里有个坑,可能虚拟机上ip会变化,变化之后需要将nodes.cong里面的ip修改为最新ip才能成功
从节点删除后,我们继续执行删除主节点操作,删除主节点需要先将槽迁移清空,为了方便我们直接使用reshard命令
执行完之后可以看到我们的slot已经变化了
执行删除命令
再次查看redis的集群状态会发现已经删除成功了,至此,我们缩容成功
更多集群的操作可以参考
http://runxinzhi.com/gered-p-15210362.html
docker网络详解
前面我们所讲述的案例几乎都是用host主机模式,接下来我们来详细介绍下docker的网络模式。
上图中,ens33是虚拟机ip地址,lo是虚拟机回环地址,在CentOS7的安装过程中如果有 选择相关虚拟化的的服务安装系统后 ,启动网卡时会发现有一个以网桥连接的私网地址的virbr0网卡(virbr0网卡:它还有一个固定的默认IP地址192.168.122.1),是做虚拟机网桥的使用的,其作用是为连接其上的虚机网卡提供 NAT访问外网的功能。 勾选安装系统的时候附带了libvirt服务才会生成的一个东西,如果不需要可以直接将libvirtd服务卸载:yum remove libvirt-libs.x86_64 。
最顶上的docker0网络,则是我们启动docker后,会产生的一个docker网桥,它的ip一般都固定为172.17.0.1,当我们安装docker后,默认会自动创建3中docker网络模式,如下图:
查看和编辑docker网络的命令为docker network + operating_param,可以用docker network help查看聚集体的参数情况。
除了上面自动创建的3中模式外,还有容器模式和自定义模式,docker允许我们用docker network create命令创建自己命名的网络,用于容器间的互联和通信以及端口映射,这样做的好处是容器IP变动时候可以通过服务名直接网络通信而不受到影响
网络模式 | 简介 |
---|---|
bridge | 为每一个容器分配、设置ip等,并将容器连接到一个docker0虚拟网桥,默认为该模式 |
host | 容器不会虚拟出自己的网卡也不会配置自己的IP,公用主机的ip和端口,前面我们做redis主从扩容案例用的就是主机模式 |
none | 容器有独立的network namespace,但并没有对其进行任何网络设置,如分配veth pair和网络连接,ip等 |
container | 新创建的容器不会创建自己的网卡和配置自己的ip,而是和一个指定的容器共享ip、端口范围 |
下面我们用实际案例来体会下各种网络模式,
bridge模式
在这里插入图片描述
默认创建容器时,docker 服务默认会创建一个docker0网桥,它在内核层连通了其他物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。docker默认指定了doker0接口的ip地址和子网掩码,让主机和容器之间可以通过网桥相互通信。不指定网络模式都为bridge模式,会将docker0的地址作为网关地址,并新生成自身容器的ip.
网桥模式下,docker0类似一个交换机,会为每个容器创建一对对等的虚拟网络接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口让它们彼此连通,这样的一对接口叫veth pair,每个容器实例内也有一块网卡,每个接口叫eth0,docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,以上述的形式将宿主机上所有容器连接到这个内部网络,两个容器在同一个网络下,会从这个网关下各自拿到分配的ip,此时两个容器的网络是互通的;
宿主机上可以看到我们创建的3个容器对应的宿主机veth接口
自定义网络模式
bridge模式创建的容器有个缺点是ip写死,如果我们的docker容器宕机了,我们新启动的容器ip将会变化,这会导致ip的寻址失败;实际生产场景中实例迁移、故障经常发生,因此我们一般不会直接用bridge模式,而是会创建一个网络,指定容器都工作在该网络下:
我们新建网络lzs_network,并启动两个容器
指定为该网络模式,u2,u3的网络ip同属同一网段中,外部访问只需要网络名即可
自定义网络本身就维护好了主机名和ip的对应关系(ip和域名都能通),默认使用的也是桥接网络
host模式
如果我们指定网络模式为主机模式,(可以直接用上面redis容器查看)
可以看到主机模式下,容器没有独立的ip,实际上host模式也是如此,容器会共享宿主机的ip和port,该模式下端口映射无效,端口号重复时递增
none模式
none模式下禁用了网络功能,只有lo标识,需要自己为容器添加网卡,配置ip,一般不用
container 模式
新创建的容器与已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡、配置自己的IP,而是和一个指定的容器共享ip、端口范围等,共享容器和新建容器之间文件系统、进程列表仍为隔离的。
启动命令示例:
该模式下如果把共享网络的容器停掉,其依赖容器均会仅剩lo回环地址。
docker容器编排–docker compose
一般来说,我们每一个容器中只运行一个服务,多个服务时通常需要其他措施来对多个服务保活。因为docker容器本身占用资源极少,所以最好是将每个服务单独的分割开来但是这样我们又面临了一个问题?
如果我需要同时部署好多个服务,难道要每个服务单独写Dockerfile然后在构建镜像,构建容器,这样累都累死了,所以docker官方给我们提供了docker-compose多服务部署的工具 ,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml, 写好多个容器之间的调用关系 。然后,只要一个命令,就能同时启动/关闭这些容器
例如要实现一个Web微服务项目,除了Web服务容器本身,往往还需要再加上后端的数据库mysql服务容器,redis服务器,注册中心eureka,甚至还包括负载均衡容器等等。。。。。。
Compose允许用户通过一个单独的 docker-compose.yml模板文件 (YAML 格式)来定义 一组相关联的应用容器为一个项目(project)。
可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。
安装方式
https://docs.docker.com/compose/install/linux/#install-the-plugin-manually
带cli插件比较好用
使用步骤
1、编写Dockerfile定义各个微服务应用并构建出对应的镜像文件
2、使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务。
3、最后,执行docker-compose up命令 来启动并运行整个应用程序,完成一键部署上线
常用命令
Compose 常用命令
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 # 停止服务
实操案例
为了方便,咱们就用前面用到过的redis、mysql镜像来进行编排
version: "3"
services:
redis:
image: redis:6.0.8
ports:
- "6379:6379"
volumes:
- /home/lzs2/docker_test/redis_volume/redis-node-9/redis.conf:/etc/redis/redis.conf
- /home/lzs2/docker_test/redis_volume/redis-node-9:/data
networks:
- lzs_network
command: redis-server /etc/redis/redis.conf
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: 'no'
MYSQL_DATABASE: 'my_test_db'
MYSQL_USER: 'lzs'
MYSQL_PASSWORD: '123456'
ports:
- "3306:3306"
volumes:
- /home/lzs2/docker_test/mysql_data/data:/var/lib/mysql
- /home/lzs2/docker_test/mysql_data/conf/my.cnf:/etc/my.cnf
- /home/lzs2/docker_test/mysql_data/init:/docker-entrypoint-initdb.d
networks:
- lzs_network
command: --default-authentication-plugin=mysql_native_password #解决外部无法访问
networks:
lzs_network:
执行docker compose up启动编排
通过另外一个终端可以看到容器已经起来了
如果我们想要后台启动,加-d参数即可
一键停止:
通常,我们可以结合dockerfile构造出微服务镜像,然后将微服务依赖的mysql、redis通过docker compose一并打包,在微服务的yml配置中需要增加depends_on参数,如下示例:
services:
microService:
image: zzyy_docker:1.6
container_name: ms01
ports:
- "6001:6001"
volumes:
- /app/microService:/data
networks:
- atguigu_net
depends_on:
- redis
- mysql
结语
至此,我们的docker教学基本结束了。在容器数量较少的场景下,我们docker-compose工具已经够用,但如果容器数量较多的情况下,我们就需要用一个更便捷的管理工具k8s了。
此外,下面介绍几个管理、监控容器的实用工具,因为是图形化界面的比较容易上手,留给大家在实战中探索了:
1、Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理Docker环境,包括单机环境和集群环境。
2、cadvisor
3、influxdb
4、granfana
CAdvisor监控收集+InfluxDB存储数据+Granfana展示图表
这样你就可以从0搭建一整套服务并对其进行监控管理啦。