docker

1.Docker简介

1.1.docker是什么?

1.1.1.为什么会有docker出现?

  1. 开发,测试,生产过程中遇到的测试环境的问题:

    假定您在开发一个尚硅谷的谷粒商城,您使用的是一台笔记本电脑而且您的开发环境具**有特定的配置。其他开发人员身处的环境配置也各有不同。您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件。**此外,您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。您希望尽可能多在本地模拟这些环境而不产生重新创建服务器环境的开销。请问?

    您要如何确保应用能够在这些环境中运行和通过质量检测?并且在部署过程中不出现令人头疼的版本、配置问题,也无需重新编写代码和进行故障修复?

  2. 解决思路

    答案就是使用容器。Docker之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案-----系统平滑移植,容器虚拟化技术。

    环境配置相当麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用 Docker可以消除协作编码时“在我的机器上可正常工作”的问题。

之前在服务器配置一个应用的运行环境,要安装各种软件,就拿尚硅谷电商项目的环境来说,Java/RabbitMQ/MySQL/JDBC驱动包等。安装和配置这些东西有多麻烦就不说了,它还不能跨平台。假如我们是在 Windows 上安装的这些环境,到了 Linux 又得重新装。况且就算不跨操作系统,换另一台同样操作系统的服务器,要移植应用也是非常麻烦的。

传统上认为,软件编码开发/测试结束后,所产出的成果即是程序或是能够编译执行的二进制字节码等(java为例)。而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,**开发需要清楚的告诉运维部署团队,用的全部配置文件+所有软件环境。**不过,即便如此,仍然常常发生部署失败的状况。Docker的出现使得Docker得以打破过去「程序即应用」的观念。透过镜像(images)将作业系统核心除外,运作应用程式所需要的系统环境,由下而上打包,达到应用程式跨平台间的无缝接轨运作。

1.1.2.docker理念

Docker是基于Go语言实现的云开源项目。

Docker的主要目标是“Build,Ship and Run Any App,Anywhere”,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP(可以是一个WEB应用或数据库应用等等)及其运行环境能够做到 “一次镜像,处处运行”

**Linux容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的。**将应用打成镜像,通过镜像成为运行在Docker容器上面的实例,而 Docker容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。 只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。

总结:解决了运行环境和配置问题的软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术。

1.2.容器与虚拟机比较

1.2.1.容器发展简史

1.2.2.传统虚拟机技术

虚拟机(virtual machine)就是带环境安装的一种解决方案。

它可以在一种操作系统里面运行另一种操作系统,比如在Windows10系统里面运行Linux系统CentOS7。我们对于运行的虚拟机应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够使应用程序,操作系统和硬件三者之间的逻辑不变。

Win10VMWareCentos7各种cpu、内存网络额配置+各种软件虚拟机实例

传统虚拟机技术基于安装在主操作系统上的虚拟机管理系统(如: VirtualBox和VMWare等),创建虚拟机(虚拟出各种硬件),在虚拟机上安装从操作系统,在从操作系统中安装部署各种应用。

虚拟机的缺点:

  1. 资源占用多
  2. 冗余步骤多
  3. 启动慢

1.2.3.容器虚拟化技术

由于前面虚拟机存在某些缺点,Linux发展出了另一种虚拟化技术:

Linux容器(Linux Containers,缩写为 LXC)

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

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

Docker 容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统虚拟机则是在硬件层面实现虚拟化。与传统的虚拟机相比,Docker 优势体现为启动速度快、占用体积小。

1.2.4.对比

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

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;

  • 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核且也没有进行硬件虚拟。 因此容器要比传统虚拟机更为轻便。

    每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。

1.3.docker能干吗

1.3.1.技术职级变化

coder --> programmer --> software engineer–>DevOps engineer(开发/运维(DevOps)新一代开发工程师)

1.3.2.DevOps新一代开发工程师

1.3.2.1.docker的实现效果
  1. 更快速的应用交付和部署

    传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。

  2. 更便捷的升级和扩缩容

    随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块“积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级。

  3. 更简单的系统运维

    应用容器化运行后, 生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。

  4. 更高效的计算资源利用

    Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的Hypervisor支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。

1.3.2.2.Docker应用场景

Docker借鉴了标准集装箱的概念。标准集装箱将货物运往世界各地,Docker将这个模型运用到自己的设计中,唯一不同的是: 集装箱运输货物,而Docker运输软件。

1.3.3.哪些企业在使用

1.3.3.1.新浪
1.3.3.2.美团

1.3.4.下载Docker

  • docker官网:http://www.docker.com

  • Docker Hub官网: https://hub.docker.com/

    Docker Hub :docker镜像仓库

2.Docker安装

2.1.前提说明

2.1.1.CentOS Docker安装说明

2.2.2.前提条件

目前,CentOS仅发行版本中的内核支持Docker。Docker运行在CentOS 7(64-bit)上,
要求系统为64位、Linux系统内核版本为3.8以上,这里选用Centos7.x。

2.2.3.查看机器内核

uname命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)。

[root@localhost etc]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
[root@localhost etc]# uname -r
3.10.0-1160.76.1.el7.x86_64

2.2.Docker的基本组成

2.2.1.镜像(image)

Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建Docker容器,一个镜像可以创建很多容器。

它也相当于是一个root文件系统。比如官方镜像 centos:7 就包含了完整的一套 centos:7 最小系统的root文件系统。

相当于容器的“源代码”,docker镜像文件类似于Java的类模板,而docker容器实例类似于java中new出来的实例对象。

2.2.2.容器(container)

2.2.2.1.从面向对象角度

Docker 利用容器(Container)独立运行的一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建的运行实例。就像是Java中的类和实例对象一样,镜像是静态的定义, 容器是镜像运行时的实体 。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。

2.2.2.2.从镜像容器角度

可以把容器看做是一个简易版的Linux环境 (包括root用户权限、进程空间、用户空间和网络空间等基础必要功能)和运行在其中的应用程序。

2.2.3.仓库(repository)

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

类似于

  • Maven仓库,存放各种jar包的地方;
  • github仓库,存放各种git项目的地方;
  • Docker公司提供的官方registry被称为Docker Hub,存放各种镜像模板的地方。

仓库分为**公开仓库(Public)私有仓库(Private)**两种形式。

最大的公开仓库是 Docker Hub(https://hub.docker.com/),

存放了数量庞大的镜像供用户下载。国内的公开仓库包括阿里云 、网易云等

2.2.4.总结

需要正确的理解仓库/镜像/容器这几个概念:

Docker 本身是一个容器运行载体或称之为管理引擎。我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就是image镜像文件。只有通过这个镜像文件才能生成Docker容器实例(类似Java中new出来一个对象)。

image文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。

  1. 镜像文件

    image 文件生成的容器实例,本身也是一个文件,称为镜像文件。

  2. 容器实例

    一个容器运行一种服务,当我们需要的时候,就可以通过docker客户端创建一个对应的运行实例,也就是我们的容器。

  3. 仓库

    就是放一堆镜像的地方,我们可以把镜像发布到仓库中,需要的时候再从仓库中拉下来就可以了。

2.3.Docker平台架构图解(入门版)

Docker是一个Client-Server结构的系统,Docker守护进程(上图的Docker daemon)运行在主机上, 然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。 容器,是一个运行时环境,就是我们前面说到的集装箱。可以对比mysql演示对比讲解

2.4.Docker平台架构图解(架构版)

整体架构及底层通信原理简述

Docker是一个C/S模式的架构,后端是一个松耦合架构,众多模块各司其职。

Docker运行的基本流程为:

  1. 用户是使用 Docker Client与 Docker Daemon(后台守护进程)建立通信,并发送请求给后者。
  2. Docker Daemon 作为 Docker 架构中的主体部分,首先提供 Docker Server 的功能使其可以接受 Docker Client 的请求。
  3. Docker Engine 执行 Docker内部的一系列工作,每一项工作都是以一个 Job 的形式的存在。
  4. Job 的运行过程中,当需要容器镜像时,则从 Docker Registry 中下载镜像,并通过像管理动 Graph driver将下载像以Graph的形式当需要为 Docker创建网络环境时,通过网络管理驱动 Network driver创建并配置 Docker容器网络环境。
  5. 当需要限制 Docker容器运行资源或执行用户指令等操作时,则通过 Exec driver 来完成。
  6. Libcontainer是一项独立的容器管理包,Network driver以及Exec diver都是通过Libcontainer来实现具体对容器进行的操作

2.5.CentOS7安装Docker

https://docs.docker.com/engine/install/centos/

2.5.1.确定你是CentOS7及以上版本

cat /etc/redhat-release
[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

2.5.2.检查 Docker 是否安装

docker --version

运行上述命令后,如果系统已安装 Docker,则会输出 Docker 的版本信息;否则,会提示找不到命令或者 Docker 未安装。

2.5.3.卸载旧版本

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

2.5.4.yum安装gcc相关

  1. 安装gcc

    yum -y install gcc
    
  2. 安装 gcc-c++

    yum -y install gcc-c++
    

2.5.5.安装需要的软件包

  1. 安装yum-utils

    yum install -y yum-utils
    

2.5.6.设置stable镜像仓库

  1. 在中国地区

    yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    
  2. 非中国地区

    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    

2.5.7.更新yum软件包索引

yum makecache fast

2.5.8.安装Docker Engine

yum -y install docker-ce docker-ce-cli containerd.io

2.5.9.启动docker

systemctl start docker

2.5.10.测试

2.5.10.1.查看docker版本
docker version

或者

docker --version
2.5.10.2.运行hello-world容器
docker run hello-world

2.5.11.卸载

  1. 停止docker

    systemctl stop docker 
    
  2. 卸载docker

    yum remove docker-ce docker-ce-cli containerd.io
    
  3. 删除配置文件

    rm -rf /var/lib/docker
    
  4. 删除容器

    rm -rf /var/lib/containerd
    

2.6.阿里云镜像加速

https://promotion.aliyun.com/ntms/act/kubernetes.html

2.6.1.从阿里云获取镜像加速器地址

2.6.2.配置

2.6.2.1.配置地址
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://xxxxxxxx.mirror.aliyuncs.com"]
}
EOF

查看是否配置成功

vim  /etc/docker/daemon.json
2.6.2.2.重启服务器
sudo systemctl daemon-reload
sudo systemctl restart docker

2.7.永远的HelloWorld

  1. 启动Docker后台容器(测试运行 hello-world)

    docker run hello-world
    
  2. docker完成了哪些动作?

2.8.底层原理-为什么Docker会比VM虚拟机快?

  1. docker有着比虚拟机更少的抽象层

    由于docker不需要Hypervisor(虚拟机)实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。

  2. docker利用的是宿主机的内核,而不需要加载操作系统OS内核

    当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。进而避免引寻、加载操作系统内核返回等比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载OS,返回新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个docker容器只需要几秒钟。

3.Docker常用命令

3.1.帮助启动类命令

  1. 启动docker: systemctl start docker

  2. 停止docker: systemctl stop docker

  3. 重启docker: systemctl restart docker

  4. 查看docker状态: systemctl status docker

  5. 开机启动: systemctl enable docker

  6. 查看docker概要信息: docker info

  7. 查看docker的版本:docker version

  8. 查看docker总体帮助文档: docker --help

  9. 查看docker命令帮助文档: docker 具体命令 --help

3.2.镜像命令

3.2.1.docker images

  1. OPTIONS说明:

    • -a :列出本地所有的镜像(含历史映像层)

    • -q :只显示镜像ID。

  2. 作用:列出本地主机上的镜像

  3. 各个选项说明:

    1. REPOSITORY:表示镜像的仓库源
    2. TAG:镜像的标签版本号
    3. IMAGE ID:镜像ID
    4. CREATED:镜像创建时间
    5. SIZE:镜像大小

    同一仓库源可以有多个TAG版本,代表这个仓库源的不同个版本,我们使用REPOSITORY:TAG 来定义不同的镜像。如果你不指定一个镜像的版本标签,例如你只使用ubuntu,docker将默认使用 ubuntu:latest 镜像。

3.2.2.docker search 镜像名称

  1. docker search [OPTIONS] 镜像名字

  2. docker search [–limit 列出镜像数] 镜像名字: 只列出N个镜像,默认25个

    示例:

    docker search --limit 5 redis
    
  3. 作用:根据镜像名称在镜像来源网站搜索镜像(镜像来源网站:https://hub.docker.com)

  4. 各个选项说明:

3.2.3.docker pull 镜像名称

  1. docker pull 镜像名字[:TAG]

    TAG表示版本。没有TAG就是最新版,等价于docker pull 镜像名字:latest,例如:docker pull ubuntu

  2. 作用:下载镜像

  3. 各个选项说明:

    1. REPOSITORY: 表示镜像的仓库源
    2. TAG: 镜像的标签版本号
    3. IMAGE ID: 镜像ID
    4. CREATED:镜像创建时间
    5. SIZE: 镜像大小

    同一仓库源可以有多个 TAG版本,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。如果你不指定一个镜像的版本标签,例如你只使用 ubuntu,docker 将默认使用 ubuntu:latest 镜像

3.2.4.docker system df

  1. docker system df

  2. 作用:查看镜像/容器/数据卷所占的空间

3.2.5.docker rmi 镜像名字ID

  1. 作用:删除镜像

  2. 删除单个 :docker rmi 镜像ID

  3. 删除多个 :docker rmi 镜像名1:TAG 镜像名2:TAG

  4. 删除全部 :docker rmi $(docker images -qa)

  5. 强制删除:docker rmi -f 镜像ID

3.2.6.面试题:谈谈docker虚悬镜像是什么?

  1. 是什么?

    仓库名、标签都是<none>的镜像,俗称虚悬镜像dangling image

  2. 长什么样?

3.3.容器命令

3.3.1.ubuntu容器说明

有镜像才能创建容器(镜像是模板,容器是落地的具体实例)这是根本前提。

Docker必须部署在Linux内核的系统上。如果其他系统想部署Docker就必须安装一个虚拟Linux环境。

Docker内部肯定是自带一套微小且最基础的Linux环境,可以是CentOS,也可以是Ubuntu。可以从Contos切换到Ubuntu。

下载一个CentOS或者ubuntu镜像演示。

拉取ubuntu

docker pull ubuntu

现在我们用docker就可以类似传统虚拟机一样,模拟Ubuntu操作系统

拉取centos

docker pull centos

3.3.2.新建+启动容器

命令:docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

3.3.2.1. OPTIONS说明

OPTIONS说明(常用):有些是一个减号,有些是两个减号

  • 第一组参数:

    –name=“容器新名字” 为容器指定一个名称;

    -d: 后台运行容器并返回容器ID,也即启动守护式容器(后台运行);

  • 第二组参数:

    -i:以交互模式运行容器(interactive),通常与 -t 同时使用;

    -t:为容器重新分配一个伪输入终端(terminal),通常与 -i 同时使用;

    也即启动交互式容器(前台有伪终端,等待交互);

  • 第三组参数:

    -P: 随机端口映射,大写P
    -p: 指定端口映射,小写p

3.3.2.2.启动交互式容器(前台命令行)

使用镜像Ubuntu:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。

启动交互式容器守护式容器相对

docker run -it ubuntu /bin/bash 

docker容器中运行的微小版的Linux,所以很多功能都没有了,比如vim。

参数说明:

  • -i: 交互式操作。
  • -t: 终端。
  • ubuntu: ubuntu镜像。
  • /bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。

要退出终端,直接输入exit即可。

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

docker ps [OPTIONS]

docker ps 是 Docker 中用于列出正在运行的容器的命令。

OPTIONS说明(常用):

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

3.3.4.退出容器

3.3.4.1.exit

run进去容器,exit退出,容器停止

3.3.4.2.ctrl+p+q

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

当使用ctrl+p+q推出后,再次获取容器终端的命令

docker exec -it <container_name_or_id> bash

3.3.5.启动已停止运行的容器

docker start 容器ID或者容器名

3.3.6.重启容器

docker restart 容器ID或者容器名

3.3.7.停止容器

docker stop 容器ID或者容器名

3.3.8.强制停止容器

docker kill 容器ID或容器名

3.3.9.删除已停止的容器

docker rm 容器ID

一次性删除多个容器实例

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

docker ps -a -q :查看当前的正在运行容器号

docker ps -a -q | xargs docker rm

docker ps -a -q结果作为参数传递给docker rm命令

3.3.10.强制删除未停止的容器(不推荐)

docker rm -f 容器ID

3.4.容器命令(重要)

有镜像才能创建容器,这是根本前提(下载一个Redis6.0.8镜像演示)

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

在大部分的场景下,我们希望 docker 的服务是在后台默默运行的,不需要和运行的容器交互,不显示运行命令行窗口。我们可以过 -d 指定容器的后台运行模式,如果我们需要进行交互就使用-it参数。

3.4.1.1.docker run -d 容器名/容器id

使用镜像centos:latest以后台模式启动一个容器

docker run -d ubuntu

问题:然后docker ps -a 进行查看, 会发现容器已经退出

很重要的要说明的一点: Docker容器后台运行,就必须有一个前台进程。容器运行的命令如果不是那些一直挂起的命令(比如运行top,tail),就是会自动退出的。

这个是docker的机制问题,比如你的web容器,我们以nginx为例,正常情况下,我们配置启动服务只需要启动响应的service即可。

例如:

service nginx start

但是这样做,nginx为后台进程模式运行,就导致docker前台没有运行的应用,这样的容器后台启动后,会立即自杀因为他觉得他没事可做了。所以,最佳的解决方案是,将你要运行的程序以前台进程的形式运行,常见就是命令行模式,表示我还有交互操作,别中断。

3.4.1.2.redis前台交互式启动

前台交互模式启动redis

 docker run -it redis:6.0.8

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H8g761bv-1689787420764)(https://stonebridge.oss-cn-shanghai.aliyuncs.com/docker/pic/04132946416.png)]

启动成功后,如果马上ctrl + C推出

因此对于像MySQL,redis,我们希望其默默在后台做稳定的服务器,不想随便关闭。即设置参数-d(后台运行容器并返回容器ID,也即启动守护式容器(后台运行));

3.4.1.3.redis后台守护式启动
docker run -d redis:6.0.8

3.4.2.查看容器日志

3.4.2.1.docker logs 容器ID

用于一次性查看容器的完整日志输出。

docker logs 容器ID
3.4.2.2.docker logs -f 容器ID

持续输出容器的日志,以便实时监视容器的日志内容。

docker logs -f 容器ID

3.4.3.查看容器内运行的进程

docker top 容器ID

3.4.5.查看容器内部细节

docker inspect 容器ID

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

3.4.6.1.docker exec -it 容器ID bashShell
3.4.6.2.docker attach 容器ID
3.4.6.3.上述两种进入的方式的区别
  1. exec是在容器中打开新的终端,并且可以启动新的进程用exit退出,不会导致容器的停止。

  2. attach直接进入容器启动命令的终端,不会启动新的进程用exit退出,会导致容器的停止。

推荐大家使用 docker exec 命令,因为退出容器终端,不会导致容器的停止。

3.4.6.4.进入redis容器实例测试

进入redis容器实例并访问redis

docker exec -it 容器ID /bin/bash

或者直接进入:

docker exec -it 容器ID redis-cli

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

在docker宿主机端操作

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

3.4.8.导出和导入容器

3.4.8.1.导出容器

export导出容器的内容留作为一个tar归档文件[对应import命令]

docker export 容器ID > 文件名.tar
3.4.8.2.导入容器

import 从tar包中的内容创建一个新的文件系统再导入为镜像[对应export]

cat 文件名.tar | docker import - 镜像用户/镜像名:镜像版本号

3.5.常用命令

关键字功能中文解释
attachAttach to a running container当前 shell 下 attach 连接指定运行镜像
buildBuild an image from a Dockerfile通过 Dockerfile 定制镜像
commitCreate a new image from a container changes提交当前容器为新的镜像
cpCopy files/folders from the containers filesystem to the host path从容器中拷贝指定文件或者目录到宿主机中
createCreate a new container创建一个新的容器,同 run,但不启动容器
diffInspect changes on a container’s filesystem查看 docker 容器变化
eventsGet real time events from the server从 docker 服务获取容器实时事件
execRun a command in an existing container在已存在的容器上运行命令
exportStream the contents of a container as a tar archive导出容器的内容流作为一个 tar 归档文件[对应 import ]
historyShow the history of an image展示一个镜像形成历史
imagesList images列出系统当前镜像
importCreate a new filesystem image from the contents of a tarball从tar包中的内容创建一个新的文件系统映像[对应export]
infoDisplay system-wide information显示系统相关信息
inspectReturn low-level information on a container查看容器详细信息
killKill a running containerkill 指定 docker 容器
loadLoad an image from a tar archive从一个 tar 包中加载一个镜像[对应 save]
loginRegister or Login to the docker registry server注册或者登陆一个 docker 源服务器
logoutLog out from a Docker registry server从当前 Docker registry 退出
logsFetch the logs of a container输出当前容器日志信息
portLookup the public-facing port which is NAT-ed to PRIVATE_PORT查看映射端口对应的容器内部源端口
pausePause all processes within a container暂停容器
psList containers列出容器列表
pullPull an image or a repository from the docker registry server从docker镜像源服务器拉取指定镜像或者库镜像
pushPush an image or a repository to the docker registry server推送指定镜像或者库镜像至docker源服务器
restartRestart a running container重启运行的容器
rmRemove one or more containers移除一个或者多个容器
rmiRemove one or more images移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除]
runRun a command in a new container创建一个新的容器并运行一个命令
saveSave an image to a tar archive保存一个镜像为一个 tar 包[对应 load]
searchSearch for an image on the Docker Hub在 docker hub 中搜索镜像
startStart a stopped containers启动容器
stopStop a running containers停止容器
tagTag an image into a repository给源中镜像打标签
topLookup the running processes of a container查看容器中运行的进程信息
unpauseUnpause a paused container取消暂停容器
versionShow the docker version information查看 docker 版本号
waitBlock until a container stops, then print its exit code截取容器停止时的退出状态值

4.Docker镜像

4.1.镜像是什么?

4.1.1.什么是镜像

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

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

4.1.2.分层的镜像

以我们的pull为例,在下载的过程中我们可以看到docker的镜像好像是在一层一层的在下载。

Docker 镜像之所以被称为分层(Layered)镜像,是因为镜像的构建过程使用了分层的文件系统结构。这种分层的设计有以下几个主要原因:

  1. 镜像重用和共享:分层镜像的设计使得镜像的各个层可以独立构建和缓存,从而实现镜像的重用和共享。当多个镜像具有相同的基础层时,它们可以共享这些基础层,而不需要重复存储相同的文件。这样可以节省存储空间,并加快镜像的下载和传输速度。

  2. 容器快速启动:分层镜像的结构使得容器的启动速度更快。因为容器只需要加载和启动最上层的可写层(称为容器层或运行时层),而不需要加载整个镜像的所有层。这样可以提高容器的启动性能,并减少资源消耗。

  3. 镜像的可维护性:分层镜像使得镜像的构建和维护更加灵活和可控。每一层都可以独立构建、更新或替换,而不会影响其他层。这样可以更容易地更新和管理镜像,减少了镜像的维护成本。

  4. 构建的增量性:分层镜像的设计使得构建过程可以利用缓存和增量构建的方式进行,即只构建和更新发生变化的层,而不需要重新构建整个镜像。这样可以加快构建过程的速度,并提高开发和持续集成的效率。

通过这种分层的镜像设计,Docker 提供了一种高效、可复用和可维护的镜像构建和部署方式,使得容器技术更加轻量级、灵活和可扩展。同时,分层镜像的结构也为 Docker 提供了许多附加的特性,例如镜像的版本控制、镜像的层级继承和容器的可写性等。

4.1.3.UnionFS(联合文件系统)

镜像的底层原理:UnionFS(联合文件系统)

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

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

4.1.4. 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等等。

平时我们安装进虚拟机的CentOS都是好几个G,为什么docker这里才200M?

对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。

4.1.5.为什么Docker镜像要采用这种分层结构呢

镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用。

比如说有多个镜像都从相同的base镜像构建而来,那么Docker Host 只需在磁盘上保存一份 base 镜像;
同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。

4.2.重点理解

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

当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”听有对容器的改动·无论添加、删除、还是修改文件都只会发生在容器层中。只有容器层是可写的,容器层下面的所有镜像层都是只读的。

4.3.Docker镜像commit制作本地镜像

docker commit提交容器副本使之成为一个新的镜像,比如在容器中安装了vim模块,将其提交成为一个带有vim新的镜像。

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

案例演示ubuntu安装vim:

  1. 从Hub上下载ubuntu镜像到本地并成功运行

  2. 原始的默认Ubuntu镜像是不带着vim命令的

  3. 外网连通的情况下,安装vim

    1. 先更新包管理工具

      apt-get update
      
    2. 安装vim

      apt-get install vim
      
  4. 再次执行vim aa.txt

    此时可以执行

  5. 安装完成后,commit我们自己的新镜像

    docker commit -m='vim cmd add ok'  -a='zzyy' cd7f573b0121 stonebride/myubuntu:1.02
    
  6. 启动我们的新镜像并和原来的对比

4.4.总结

Docker中的镜像分层,支持通过扩展现有镜像,创建新的镜像 。类似Java继承于一个Base基础类,自己再按需扩展。
新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层

5.本地镜像发布到阿里云

5.1.本地镜像发布到阿里云流程

5.2.将本地镜像推送到阿里云

5.2.1.阿里云开发者平台

https://promotion.aliyun.com/ntms/act/kubernetes.html

5.2.2.创建仓库镜像

5.2.2.1.选择控制台,进入容器镜像服务
5.2.2.2.选择个人实例
5.2.2.3.命名空间
5.2.2.4.仓库名称
5.2.2.5.进入管理界面获得脚本

5.2.3.将镜像推送到阿里云

阿里云网站配置已经完成

将镜像推送到阿里云registry

docker login --username=tb80727_34 registry.cn-hangzhou.aliyuncs.com
docker tag [ImageId] registry.cn-hangzhou.aliyuncs.com/stonebridge/myubuntu:[镜像版本号]

docker tag d36a532a5057 registry.cn-hangzhou.aliyuncs.com/stonebridge/myubuntu:1.02
docker push registry.cn-hangzhou.aliyuncs.com/stonebridge/myubuntu:[镜像版本号]

docker push registry.cn-hangzhou.aliyuncs.com/stonebridge/myubuntu:1.02

5.2.4.将阿里云上的镜像下载到本地

docker pull registry.cn-hangzhou.aliyuncs.com/stonebridge/myubuntu:[镜像版本号]

docker pull registry.cn-hangzhou.aliyuncs.com/stonebridge/myubuntu:1.02

6.本地镜像发布到私有库

6.1.本地镜像发布到私有库流程

6.2.Docker Registry是什么

  1. 官方Docker Hub地址:https://hub.docker.com/,中国大陆访问太慢了且准备被阿里云取代的趋势,不太主流。
  2. Dockerhub、阿里云这样的公共镜像仓库可能不太方便,涉及机密的公司不可能提供镜像给公网,所以需要创建一个本地私人仓库供给团队使用,基于公司内部项目构建镜像。

Docker Registry是官方提供的工具,可以用于构建私有镜像仓库。

6.3.将本地镜像推送到私有库

6.3.1.下载镜像Docker Registry

docker pull registry

6.3.2.运行私有库Registry,相当于本地有个私有Docker hub

docker run -d -p 5000:5000  -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry

默认情况,仓库被创建在容器的/var/lib/registry目录下,上述命令中被改为/tmp/registry目录下,建议自行用容器卷映射,方便于宿主机联调。

6.3.3.案例演示创建一个新镜像,ubuntu安装ifconfig命令

  1. 从Hub上下载ubuntu镜像到本地并成功运行

  2. 原始的Ubuntu镜像是不带着ifconfig命令的

  3. 外网连通的情况下,安装ifconfig命令并测试通过

  4. 安装完成后,commit我们自己的新镜像

  5. 启动我们的新镜像并和原来的对比

6.3.4.curl验证私服库上有什么镜像

curl -XGET http://192.168.234.102:5000/v2/_catalog

可以看到,目前私服库没有任何镜像上传过。

6.3.5.将新镜像myubuntu:2.02修改符合私服规范的Tag

操作命令

docker   tag   镜像:Tag   Host:Port/Repository:Tag

host为本机IP地址,具体根据自己IP地址填写,不要粘贴错误。

使用命令docker tag 将myubuntu:2.02 这个镜像修改为192.168.234.102:5000/myubuntu:2.02

docker tag  myubuntu:2.02  192.168.234.102:5000/myubuntu:2.02

将192.168.234.102:5000/myubuntu:2.02传递私有库

6.3.6.修改docker主机的配置文件使之支持http

registry-mirrors配置的是国内阿里提供的镜像加速地址,不用加速的话访问官网的会很慢。

2个配置中间有个逗号 ','别漏了,这个配置是json格式的。

2个配置中间有个逗号 ','别漏了,这个配置是json格式的。

2个配置中间有个逗号 ','别漏了,这个配置是json格式的。

vim命令新增insecure-registries内容:

vim /etc/docker/daemon.json
{
  "registry-mirrors": ["https://zxfufzam.mirror.aliyuncs.com"],  
  "insecure-registries": ["192.168.234.102:5000"]
}

上述理由: docker默认不允许http方式推送镜像,通过配置选项来取消这个限制。==> 修改完后如果不生效,建议重启docker。

6.3.7.push推送到私服库

docker push 192.168.234.102:5000/myubuntu:2.02

6.3.8.curl验证私服库上有什么镜像

6.3.9.pull到本地并运行

注意携带版本号

6.4.Harbor

7.Docker容器数据卷

7.1.易错点:容器卷记得加入参数(–privileged=true)

Docker挂载主机目录访问如果出现cannot open directory .: Permission denied

解决办法:在挂载目录后多加一个–privileged=true参数即可

privileged:(信息)得到特许保密的

例如:

docker run -d -p 5000:5000  -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry

如果是CentOS7安全模块会比之前系统版本加强,不安全的会先禁止,所以目录挂载的情况被默认为不安全的行为,在SELinux里面挂载目录被禁止掉了,如果要开启,我们一般使用–privileged=true命令,扩大容器的权限解决挂载目录没有权限的问题,也即使用该参数,在container内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限。

回顾下上一讲的知识点,参数V

默认情况,仓库被创建在容器实例的/var/lib/registry目录下,上述命令中被改为/tmp/registry目录下,建议自行用容器卷映射,方便于宿主机联调。

-v:要添加自定义的容器卷

冒号左边(/zzyyuse/myregistry/):宿主机的路径

冒号右侧(/tmp/registry) :容器内的路径

–privileged=true :放开容器实例内的权限,容器内的进程拥有主机的 root 权限

我运行启动这个容器实例以后,完成了容器内部和宿主机某个绝对路径实现了信息共享和互通互联。

7.2.容器数据卷是什么

卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性

卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。

有点类似我们Redis里面的rdb和aof文件, 将docker容器内的数据保存进宿主机的磁盘中

运行一个带有容器卷存储功能的容器实例

docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录  镜像名
  • docker run :运行一个容器实例
  • -it/-d :后台交互或者前台显示
  • –privileged=true -v /宿主机绝对路径目录:/容器内目录 :打开权限,完成宿主机绝对路径目录映射(:)到容器实例内某个目录,完成两者的连通,实现数据的共享和存储,容器内指定目录产生的重要数据就可以存储进宿主机的指定路径。

7.3.容器数据卷能干什么

将运用与运行的环境打包镜像,run后形成容器实例运行 ,但是我们对数据的要求希望是持久化的。

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

特点:

  1. 数据卷可在容器之间共享或重用数据
  2. 卷中的更改可以直接实时生效
  3. 数据卷中的更改不会包含在镜像的更新中
  4. 数据卷的生命周期一直持续到没有容器使用它为止

7.4.数据卷案例

7.4.1.宿主vs容器之间映射添加容器卷

  1. 命令添加映射添加容器卷

    docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录      镜像名
    
  2. 查看数据卷是否挂载成功

    docker inspect 容器ID
    
  3. 容器和宿主机之间数据共享

    1. docker修改,主机同步获得
    2. 主机修改,docker同步获得
    3. docker容器stop,主机修改,docker容器重启看数据是否同步。

7.4.2.读写规则映射添加说明

  1. 读写(默认)

    docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:rw      镜像名
    

    默认同上案例,默认就是rw,即容器和宿主机直接读写都同步,都具有读写权限。

  2. 只读

    容器实例内部被限制,只能读取不能写

     docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:ro      镜像名
    

    /容器目录:ro 镜像名 就能完成功能,此时容器自己只能读取不能写

    ro = read only

    此时如果宿主机写入内容,可以同步给容器内,容器可以读取到。

7.4.3.卷的继承和共享

  1. 容器1完成和宿主机的映射

    docker run -it  --privileged=true -v /mydocker/u:/tmp --name u1 ubuntu
    
  2. 容器2继承容器1的卷规则

    docker run -it  --privileged=true --volumes-from 父类  --name u2 ubuntu
    
    docker run -it  --privileged=true --volumes-from u1  --name u2 ubuntu
    

即使此时u1关闭了,也不会影响u2。

8.Docker常规安装简介

8.1.总体步骤

  1. 搜索镜像

  2. 拉取镜像

  3. 查看镜像

  4. 启动镜像

    服务端口映射

  5. 停止容器

  6. 移除容器

8.2.安装tomcat

8.2.1.docker hub上面查找tomcat镜像

docker search tomcat

8.2.2.从docker hub上拉取tomcat镜像到本地

docker pull tomcat

8.2.3.查看是否有拉取到的tomcat

docker images

8.2.4.使用tomcat镜像创建容器实例(也叫运行镜像)

docker run -it -p 8080:8080 tomcat
  1. -p 小写,主机端口:docker容器端口
  2. -P 大写,随机分配端口
  3. i:交互
  4. t:终端
  5. d:后台

8.2.5.访问Tomcat首页

8.2.5.1.先成功启动tomcat
8.2.5.2.访问首页出现无法访问

访问: http://192.168.234.102:8080/

出现无法访问

8.2.5.3.解决问题
  1. 永久开放8080端口

    sudo firewall-cmd --permanent --add-port=8080/tcp
    sudo firewall-cmd --reload
    
  2. 查看webapps文件夹查看为空

    进入tomcat运行的容器内部,如果webapps文件夹查看为空,则删除webapps文件夹,将webapps.dist的改名为webapps。

8.2.5.4.再次访问

8.2.6.免修改版Tomcat

docker pull billygoo/tomcat8-jdk8
docker run -d -p 8080:8080 --name mytomcat8 billygoo/tomcat8-jdk8

8.3.安装MySQL

8.3.1.docker hub上面查找mysql镜像

docker search mysql

8.3.2.从docker hub上拉取mysql镜像到本地

Tag选择5.7

docker pull mysql:5.7

repository name must be lowercase

8.3.3.使用mysql5.7镜像创建容器(也叫运行镜像)

  1. 检查Linux是否存在mysql

    ps -ef|grep mysql
    
  2. 运行Linux容器

    docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
    
  3. 检查运行的容器

    docker ps
    
  4. 进入mysql运行容器,并访问mysql

    docker exec -it 容器ID /bin/bash
    
    mysql -uroot -p
    

8.3.4.数据库工具访问数据库

检查宿主机端口是否开放。

8.3.5.mysql出现中文出现乱码和未备份的问题

8.3.6.生产条件下配置MySQL容器

8.3.6.1.生产条件下新建mysql容器实例
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
8.3.6.2.新建my.cnf

通过容器卷同步给mysql容器实例,解决中文乱码问题

设置客服端(client.default_character_set),mysql服务器端字符集编码都是utf8编码

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

重启可能密码报错:

解决方案:

  1. 修改my.cnf,增加skip-grant-tables参数

    [client]
    default_character_set=utf8
    [mysqld]collation_server = utf8_general_ci
    character_set_server = utf8
    skip-grant-tables
    
  2. 进入mysql重新设置密码

8.3.6.3.重新启动mysql容器实例再重新进入并查看字符编码
SHOW VARIABLES LIKE 'character%';

再次操作数据库进行插入中文字符操作

mysql> create database db01;
Query OK, 1 row affected

mysql> use db01;
Database changed

mysql> create table t1(id int,name varchar(20));
Query OK, 0 rows affected

mysql> insert into t1 values(1,'z3');
Query OK, 1 row affected

mysql> insert into t1 values(1,'王五');
Query OK, 1 row affected

mysql> select * FROM t1;
+----+------+
| id | name |
+----+------+
|  1 | z3   |
|  1 | 王五 |
+----+------+
2 rows in set
8.3.6.4.结论

之前的DB无效

修改字符集操作+重启mysql容器实例

之后的DB 有效,需要新建

结论: docker安装完MySQL并run出容器后,建议请先修改完字符集编码后再新建mysql库-表-插数据

8.3.7.MySQL的容器数据卷备份

  1. 查看宿主机的映射备份数据,mysql的备份数据被映射保存在其中

  2. 删除运行中的mysql容器

  3. 运行新的mysql容器,查看mysql原始数据是否存在

8.4.安装redis

8.4.1.从docker hub上拉取redis镜像到本地标签为6.0.8

docker pull redis

8.4.2.命令提醒:容器卷记得加入–privileged=true

Docker挂载主机目录Docker访问出现cannot open directory .: Permission denied

解决办法:在挂载目录后多加一个–privileged=true参数即可

8.4.3.在Docker宿主机下新建目录/app/redis

mkdir -p /app/redis

8.4.4.宿主机的/app/redis目录下修改redis.conf文件

将一个redis.conf文件模板拷贝进/app/redis目录下,创建出厂默认的原始redis.conf

# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf
 
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

……

/app/redis目录下修改redis.conf文件

  1. 开启redis验证 可选

    requirepass 123

  2. 允许redis外地连接 必须

    注释掉 # bind 127.0.0.1

  3. daemonize no

    将daemonize yes注释起来或者daemonize no设置,因为该配置和docker run中-d参数冲突,会导致容器一直启动失败

  4. 开启redis数据持久化 appendonly yes可选

8.4.5.使用redis6.0.8镜像创建容器(也叫运行镜像)

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:latest redis-server

05194308586

8.4.6.测试redis-cli连接上来

 docker exec -it 运行着Rediis服务的容器ID redis-cli

8.4.7.请证明docker启动使用了我们自己指定的配置文件

  1. 修改前 :我们用的配置文件,数据库默认是16个

  2. 修改后 :宿主机的修改会同步给docker容器里面的配置。(记得重启服务)

  3. 测试redis-cli链接上来

9.Docker复杂安装详说

9.1.安装mysql主从复制

9.1.1.主从复制原理

  • MySQL 主从复制是一种常见的数据库复制技术,它允许将一个 MySQL 数据库服务器(主服务器)的数据复制到一个或多个其他 MySQL 服务器(从服务器)。主从复制的原理如下:

    1. 主服务器(Master):主服务器是提供数据复制的源头。它是负责处理写操作(插入、更新、删除)的主要数据库服务器。当在主服务器上进行写操作时,它会将操作记录到二进制日志(binary log)中。

    2. 从服务器(Slave):从服务器是接收主服务器数据复制的目标服务器。它通过连接到主服务器,并请求主服务器的二进制日志,来获取主服务器上的写操作记录。

    3. 复制进程:从服务器上运行的复制进程(replication process)将连接到主服务器,并获取主服务器的二进制日志。复制进程会按照顺序读取二进制日志中的操作记录,并在从服务器上执行这些操作,从而复制主服务器上的数据更改。

    4. 复制原理:主服务器将写操作记录到二进制日志中,而从服务器连接到主服务器并获取二进制日志。从服务器的复制进程读取二进制日志,并在从服务器上执行相同的写操作,从而使得从服务器的数据与主服务器保持一致。

    主从复制的实现涉及以下关键组件和过程:

    • 二进制日志(Binary Log):主服务器将写操作记录到二进制日志中,该日志包含了对数据库进行修改的详细操作记录。

    • 复制线程(Replication Thread):从服务器上运行的复制线程负责连接到主服务器并获取二进制日志。它将读取主服务器上的二进制日志并将操作记录应用到从服务器上。

    • 复制位置(Replication Position):从服务器会跟踪复制进程在主服务器的二进制日志中的位置。通过记录复制位置,从服务器可以确定它复制的进度,并从正确的位置继续复制。

    • 主服务器标识(Master Server ID):每个主服务器都有一个唯一的标识符。从服务器连接到主服务器时,主服务器会将其标识符发送给从服务器。这样,从服务器可以识别不同的主服务器,并确保与正确的主服务器进行连接。

    • 故障转移(Failover):如果主服务器发生故障,可以将从服务器提升为新的主服务器,以继续提供服务。故障转移过程涉及重新配置从服务器,使其成为新的主服务器,并将其他从服务器重新连接到新的主服务器。

    主从复制提供了一些重要的优势,包括:

    • 数据冗余:主从复制提供了数据的冗余备份,即使主服务器发生故障,数据仍然存在于从服务器上

    并可用于读取操作。

    • 负载均衡:从服务器可以用于分担主服务器的读取负载,从而提高整体系统的性能和吞吐量。

    • 容灾恢复:如果主服务器发生故障,可以快速切换到从服务器以保持服务的连续性,并减少停机时间。

    需要注意的是,主从复制是一个异步过程,复制延迟可能会导致从服务器上的数据与主服务器稍有不同。此外,主从复制只提供数据的单向复制,即只能从主服务器复制到从服务器,无法从从服务器写入数据。如果需要双向同步,可以考虑使用其他技术,如主-主复制或者使用集群解决方案。

    总之,MySQL 主从复制是一种通过将主服务器的写操作记录复制到从服务器来实现数据复制的技术。它提供了数据冗余、负载均衡和容灾恢复等优势,使得从服务器能够提供备份数据和处理读取操作。

9.1.2.主从搭建步骤之MySQL-Master搭建

9.1.2.1.新建主服务器容器实例3307
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
9.1.2.2.进入宿主机的/mydata/mysql-master/conf目录下新建my.cnf

新建my.cnf并配置

[mysqld]
## 设置server_id,同一局域网中需要唯一
server_id=101
## 指定不需要同步的数据库名称,名为mysql的database不需要同步
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
9.1.2.3.修改完配置后重启master实例
docker restart mysql-master
9.1.2.4.进入mysql-master容器
docker exec -it mysql-master /bin/bash

访问数据库

mysql -uroot -proot
或
mysql -uroot -p
9.1.2.5.master容器实例内创建数据同步用户

创建数据同步用户

CREATE USER 'slave'@'%' IDENTIFIED BY '123456';

给数据同步用户授权

GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';

master服务器基本完成

9.1.3.主从搭建步骤之MySQL-Slave搭建

9.1.3.1.新建从服务器容器实例3308
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
9.1.3.2.进入/mydata/mysql-slave/conf目录下新建my.cnf
touch 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
9.1.3.3.修改完配置后重启slave实例
docker restart mysql-slave

06101253269

进入mysql-slave检查一下数据库能否正常访问

9.1.3.4.在主数据库中查看主从同步状态
show master status;
9.1.3.5.进入mysql-slave容器
docker exec -it mysql-slave /bin/bash
mysql -uroot -proot

9.1.4.主从搭建步骤之主从配置

9.1.4.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地址;(ifconfig-ens33)
  • master_port:主数据库的运行端口;(参考9.1.2.1.新建主服务器容器实例的docker命令)
  • master_user:在主数据库创建的用于同步数据的用户账号;(参考9.1.2.5)
  • master_password:在主数据库创建的用于同步数据的用户密码;(参考9.1.2.5)
  • master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;(参考9.1.2.9)
  • master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;(参考9.1.2.9)
  • master_connect_retry:连接失败重试的时间间隔,单位为秒。
9.1.4.2.在从数据库中查看主从同步状态
show slave status \G;
9.1.4.3.在从数据库中开启主从同步
start slave;
9.1.4.4.查看从数据库状态发现已经同步
show slave status \G;
9.1.4.5.主从复制测试
  • mysql-master

  • mysql-slave

9.2.安装redis集群

功能要求:cluster(集群)模式-docker版哈希槽分区进行亿级数据存储

9.2.1.面试题

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

答:单机单台100%不可能,肯定是分布式存储,用redis如何落地?

上述问题阿里P6~P7工程案例和场景设计类必考题目,一般业界有3种解决方案

9.2.1.1.解决方案1:哈希取余分区

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

补充:因为redis里面的key是不重复的,所以存和取都不会出错。

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

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

某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

9.2.1.2.解决方案2:一致性哈希算法分区
9.2.1.2.1.是什么?

一致性Hash算法背景

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

9.2.1.2.2.能干嘛?

提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系

9.2.1.2.3.实现的三大步骤
  1. 算法构建一致性哈希环

    一致性哈希算法必然有个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环。

  2. 服务器IP节点映射

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

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

  3. 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上。

9.2.1.2.4.优点
  1. 一致性哈希算法的容错性

    假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则 受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。

  2. 一致性哈希算法的扩展性

    数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。

9.2.1.3.5.缺点

一致性哈希算法的数据倾斜问题

Hash环的数据倾斜问题

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

例如系统中只有两台服务器:

9.2.1.3.6.总结

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

将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在Hash环上顺时针相邻的后续节点

  • 优点

    加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。

  • 缺点

    数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。

9.2.1.3.解决方案3:哈希槽分区
9.2.1.3.1.为什么出现

一致性哈希算法有数据倾斜问题。

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

9.2.1.3.2.能干什么

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

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。

哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

9.2.1.3.3.多少个hash槽

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

9.2.1.3.4.哈希槽计算

Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上

9.3.主3从3的redis集群扩缩容配置案例架构

9.3.1.主3从3redis集群配置

9.3.1.1.关闭防火墙+启动docker后台服务
systemctl start docker
9.3.1.2.新建6个docker容器redis实例
docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/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 /data/redis/share/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 /data/redis/share/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 /data/redis/share/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 /data/redis/share/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 /data/redis/share/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386
  • docker run :创建并运行docker容器实例
  • –name redis-node-6:容器名字
  • –net host:使用宿主机的IP和端口,默认
  • –privileged=true:获取宿主机root用户权限
  • -v /data/redis/share/redis-node-6:/data:容器卷,宿主机地址:docker内部地址
  • redis:6.0.8:redis镜像和版本号
  • –cluster-enabled yes:开启redis集群
  • –appendonly yes:开启持久化
  • –port 6386:redis端口号
9.3.1.3.进入容器redis-node-1并为6台机器构建集群关系
  1. 进入容器

    docker exec -it redis-node-1 /bin/bash
    
  2. 构建主从关系

    注意,进入docker容器后才能执行一下命令,且注意自己的真实IP地址

    redis-cli --cluster create 192.168.234.102:6381 192.168.234.102:6382 192.168.234.102:6383 192.168.234.102:6384 192.168.234.102:6385 192.168.234.102:6386 --cluster-replicas 1
    

    –cluster-replicas 1 表示为每个master创建一个slave节点,即master和slave的比例是1来进行配置。

    构建成功

9.3.1.4.链接进入6381作为切入点,查看集群状态

链接进入6381作为切入点,查看节点状态

cluster info
cluster nodes

5->1

4->3

6->2

9.3.2.主从容错切换迁移案例

9.3.2.1.数据读写存储
  1. 启动6机构成的集群并通过exec进入

    docker exec -it redis-node-1 /bin/bash
    
  2. 对6381新增两个key

    此时存储数据的过程是通过hash槽对数据进行了分段,hash值为0-5460存储到master1,安装内部hash算法,此时的槽位超过了5460,就存不进master1。

    因此采用如下链接方式,导致存储失败

    redis-cli -p 6381
    

    上述命令是链接redis单机,此时我们是redis集群模式环境,因此要加参数-C,防止路由失效。

  3. 防止路由失效加参数-c并新增两个key

    redis集群环境链接

    redis-cli -p 6381 -c
    
  4. 查看集群信息

    redis-cli --cluster check 192.168.234.102:6381
    
9.3.2.2.主从容错切换迁移

6381宕机,6384上位代替。

  1. 主6381和从机切换,先停止主机6381

    登录8382可以正常看到访问redis集群数据

  2. 重启6381,将redis容器所有启动

  3. 先还原之前的3主3从

    先前情况master节点6381停止,slave节点6385成为master节点,如果要恢复原位,则进行如下操作:

    先启6381

    docker start redis-node-1
    

    再停6385

    docker stop redis-node-5
    

    再启6385

    docker start redis-node-5
    
  4. 查看集群状态

    redis-cli --cluster check 自己IP:6381
    

9.3.3.主从扩容案例

9.3.3.1.新建6387、6388两个节点+新建后启动+查看是否8节点
docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/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 /data/redis/share/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388
docker ps
9.3.3.2.进入6387容器实例内部
docker exec -it redis-node-7 /bin/bash
9.3.3.3.将新增的6387节点(空槽号)作为master节点加入原集群

将新增的6387作为master节点加入集群

redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381

6387 就是将要作为master新增节点

6381 就是原来集群节点里面的领路人,相当于6387拜拜6381的码头从而找到组织加入集群

9.3.3.4.第1次检查集群情况
redis-cli --cluster check 192.168.234.102:6381
9.3.3.5.重新分派槽号

原先槽号分为三等分,现在新增6387节点,需要重新分配。

重新分派槽号命令

redis-cli --cluster reshard IP地址:端口号
redis-cli --cluster reshard 192.168.234.102:6381
9.3.3.6.第2次检查集群情况
redis-cli --cluster check 192.168.234.102:6381

为什么6387是3个新的区间,以前的还是连续?重新分配成本太高,所以前3家各自匀出来一部分,从6381/6382/6383三个旧节点分别匀出1364个坑位给新节点6387

9.3.3.7.为主节点6387分配从节点6388

命令: redis-cli --cluster add-node 192.168.111.147:6388 192.168.111.147:6387 --cluster-slave --cluster-master-id e4781f644d4a4e4d4b4d107157b9ba8144631451-------这个是6387的编号,按照自己实际情况

redis-cli --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID
redis-cli --cluster add-node 192.168.234.102:6388 192.168.234.102:6387 --cluster-slave --cluster-master-id 9e91c81906841f5cffd804b39a4b8c5d75a030ed
9.3.3.8.第3次检查集群情况
redis-cli --cluster check 192.168.234.102:6381

9.3.4.主从缩容案例

目的:6387和6388下线

9.3.4.1.第一次检查集群情况并获得6388的节点ID
redis-cli --cluster check 192.168.234.102:6381

7996ef42e6250235115f914109d54948ac89e3c8

9.3.4.2.将4号从节点6388从集群中删除

命令:

redis-cli --cluster del-node ip:从机端口 从机6388节点ID  
redis-cli --cluster del-node 192.168.234.102:6388 7996ef42e6250235115f914109d54948ac89e3c8

检查从节点是否删除

redis-cli --cluster check 192.168.234.102:6381

06214431483

检查一下发现,6388被删除了,只剩下7台机器了。

9.3.4.3.将6387的槽号清空,重新分配,本例将清出来的槽号都给6382
9.3.4.4.第2次检查集群情况
redis-cli --cluster check 192.168.234.102:6381
9.3.4.5.将6387节点删除
redis-cli --cluster del-node ip:端口 6387节点ID
redis-cli --cluster del-node 192.168.234.102:6387 9e91c81906841f5cffd804b39a4b8c5d75a030ed
9.3.4.6.第3次检查集群情况
redis-cli --cluster check 192.168.234.102:6381

10.DockerFile

10.1.DockerFile解析

10.1.1.DockerFile是什么?

Dockerfile是用于定义Docker镜像构建过程的文本文件。是由一系列指令构成,用于指定构建镜像所需的操作和配置构成的脚本。

以下是关于 Dockerfile 的一些重要概念和常用指令:

10.1.1.1.基础镜像(Base Image)

Dockerfile 中的第一行通常是指定用作基础的镜像。基础镜像是构建新镜像的起点,它包含了操作系统和一些预安装的软件包。你可以选择合适的基础镜像,以便于构建适合你应用程序的环境。

10.1.1.2.指令(Instructions)

Dockerfile 中的每个指令都是一条命令,用于指定构建镜像所需的操作。以下是一些常用的指令:

  • FROM:指定基础镜像。
  • RUN:在容器内部执行命令。
  • COPY:将文件或目录从构建环境复制到容器内部。
  • ADD:类似于 COPY,但还支持从远程URL复制资源,并自动解压缩 tar 文件。
  • ENV:设置环境变量。
  • WORKDIR:设置工作目录。
  • EXPOSE:声明容器运行时需要监听的端口。
  • CMD:设置容器启动后默认执行的命令。
10.1.1.3.构建镜像

使用 Dockerfile 构建镜像的步骤如下:

  1. 在一个目录下创建 Dockerfile 文件。

  2. 在 Dockerfile 中定义构建过程所需的指令。

  3. 执行 docker build 命令,指定 Dockerfile 的路径和要构建的镜像名称:

    docker build -t image_name .
    

    上述命令会根据 Dockerfile 中的指令构建镜像,并将其命名为 image_name

Dockerfile的灵活性使得可以定义自定义的镜像,以满足特定应用程序的需求。通过在 Dockerfile中编写指令,你可以定制容器的配置和行为,使得构建出的镜像更加符合应用程序的要求。

需要注意的是,Dockerfile 是一个逐行执行的脚本,因此在编写 Dockerfile 时应注意指令的顺序和依赖关系。此外,为了优化镜像的构建过程和镜像大小,可以使用一些技巧,如合并多个 RUN 命令、使用 .dockerignore 文件排除不需要的文件等。

10.1.2.概述

10.1.3.官网

https://docs.docker.com/engine/reference/builder/

10.1.4.构建三步骤

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

10.2.DockerFile构建过程解析

10.2.1.Dockerfile内容基础知识

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

10.2.2.Docker执行Dockerfile的大致流程

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

10.2.3.总结

从应用软件的角度来看,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容器,容器是直接提供服务的。

10.3.DockerFile常用保留字指令

10.3.1.参考tomcat8的dockerfile入门

https://github.com/docker-library/tomcat/blob/1c6875e85a440757bf983b3beb6234f23cc06e8c/9.0/jdk11/temurin-jammy/Dockerfile

#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#

FROM eclipse-temurin:11-jdk-jammy

ENV CATALINA_HOME /usr/local/tomcat
ENV PATH $CATALINA_HOME/bin:$PATH
RUN mkdir -p "$CATALINA_HOME"
WORKDIR $CATALINA_HOME

# let "Tomcat Native" live somewhere isolated
ENV TOMCAT_NATIVE_LIBDIR $CATALINA_HOME/native-jni-lib
ENV LD_LIBRARY_PATH ${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$TOMCAT_NATIVE_LIBDIR

# see https://www.apache.org/dist/tomcat/tomcat-9/KEYS
# see also "versions.sh" (https://github.com/docker-library/tomcat/blob/master/versions.sh)
ENV GPG_KEYS 48F8E69F6390C9F25CFEDCD268248959359E722B A9C5DF4D22E99998D9875A5110C01C5A2F6059E7 DCFD35E0BF8CA7344752DE8B6FB21E8933C60243

ENV TOMCAT_MAJOR 9
ENV TOMCAT_VERSION 9.0.76
ENV TOMCAT_SHA512 028163cbe15367f0ab60e086b0ebc8d774e62d126d82ae9152f863d4680e280b11c9503e3b51ee7089ca9bea1bfa5b535b244a727a3021e5fa72dd7e9569af9a

RUN set -eux; \
	\
	savedAptMark="$(apt-mark showmanual)"; \
	apt-get update; \
	apt-get install -y --no-install-recommends \
		ca-certificates \
		curl \
		gnupg \
	; \
	\
	ddist() { \
		local f="$1"; shift; \
		local distFile="$1"; shift; \
		local mvnFile="${1:-}"; \
		local success=; \
		local distUrl=; \
		for distUrl in \
# https://issues.apache.org/jira/browse/INFRA-8753?focusedCommentId=14735394#comment-14735394
			"https://www.apache.org/dyn/closer.cgi?action=download&filename=$distFile" \
# if the version is outdated (or we're grabbing the .asc file), we might have to pull from the dist/archive :/
			"https://downloads.apache.org/$distFile" \
			"https://www-us.apache.org/dist/$distFile" \
			"https://www.apache.org/dist/$distFile" \
			"https://archive.apache.org/dist/$distFile" \
# if all else fails, let's try Maven (https://www.mail-archive.com/users@tomcat.apache.org/msg134940.html; https://mvnrepository.com/artifact/org.apache.tomcat/tomcat; https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/)
			${mvnFile:+"https://repo1.maven.org/maven2/org/apache/tomcat/tomcat/$mvnFile"} \
		; do \
			if curl -fL -o "$f" "$distUrl" && [ -s "$f" ]; then \
				success=1; \
				break; \
			fi; \
		done; \
		[ -n "$success" ]; \
	}; \
	\
	ddist 'tomcat.tar.gz' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz"; \
	echo "$TOMCAT_SHA512 *tomcat.tar.gz" | sha512sum --strict --check -; \
	ddist 'tomcat.tar.gz.asc' "tomcat/tomcat-$TOMCAT_MAJOR/v$TOMCAT_VERSION/bin/apache-tomcat-$TOMCAT_VERSION.tar.gz.asc" "$TOMCAT_VERSION/tomcat-$TOMCAT_VERSION.tar.gz.asc"; \
	export GNUPGHOME="$(mktemp -d)"; \
	for key in $GPG_KEYS; do \
		gpg --batch --keyserver keyserver.ubuntu.com --recv-keys "$key"; \
	done; \
	gpg --batch --verify tomcat.tar.gz.asc tomcat.tar.gz; \
	tar -xf tomcat.tar.gz --strip-components=1; \
	rm bin/*.bat; \
	rm tomcat.tar.gz*; \
	gpgconf --kill all; \
	rm -rf "$GNUPGHOME"; \
	\
# https://tomcat.apache.org/tomcat-9.0-doc/security-howto.html#Default_web_applications
	mv webapps webapps.dist; \
	mkdir webapps; \
# we don't delete them completely because they're frankly a pain to get back for users who do want them, and they're generally tiny (~7MB)
	\
	nativeBuildDir="$(mktemp -d)"; \
	tar -xf bin/tomcat-native.tar.gz -C "$nativeBuildDir" --strip-components=1; \
	apt-get install -y --no-install-recommends \
		dpkg-dev \
		gcc \
		libapr1-dev \
		libssl-dev \
		make \
	; \
	( \
		export CATALINA_HOME="$PWD"; \
		cd "$nativeBuildDir/native"; \
		gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)"; \
		aprConfig="$(command -v apr-1-config)"; \
		./configure \
			--build="$gnuArch" \
			--libdir="$TOMCAT_NATIVE_LIBDIR" \
			--prefix="$CATALINA_HOME" \
			--with-apr="$aprConfig" \
			--with-java-home="$JAVA_HOME" \
			--with-ssl \
		; \
		nproc="$(nproc)"; \
		make -j "$nproc"; \
		make install; \
	); \
	rm -rf "$nativeBuildDir"; \
	rm bin/tomcat-native.tar.gz; \
	\
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
	apt-mark auto '.*' > /dev/null; \
	[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
	find "$TOMCAT_NATIVE_LIBDIR" -type f -executable -exec ldd '{}' ';' \
		| awk '/=>/ { print $(NF-1) }' \
		| xargs -rt readlink -e \
		| sort -u \
		| xargs -rt dpkg-query --search \
		| cut -d: -f1 \
		| sort -u \
		| tee "$TOMCAT_NATIVE_LIBDIR/.dependencies.txt" \
		| xargs -r apt-mark manual \
	; \
	\
	apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
	rm -rf /var/lib/apt/lists/*; \
	\
# sh removes env vars it doesn't support (ones with periods)
# https://github.com/docker-library/tomcat/issues/77
	find ./bin/ -name '*.sh' -exec sed -ri 's|^#!/bin/sh$|#!/usr/bin/env bash|' '{}' +; \
	\
# fix permissions (especially for running as non-root)
# https://github.com/docker-library/tomcat/issues/35
	chmod -R +rX .; \
	chmod 1777 logs temp work; \
	\
# smoke test
	catalina.sh version

# verify Tomcat Native is working properly
RUN set -eux; \
	nativeLines="$(catalina.sh configtest 2>&1)"; \
	nativeLines="$(echo "$nativeLines" | grep 'Apache Tomcat Native')"; \
	nativeLines="$(echo "$nativeLines" | sort -u)"; \
	if ! echo "$nativeLines" | grep -E 'INFO: Loaded( APR based)? Apache Tomcat Native library' >&2; then \
		echo >&2 "$nativeLines"; \
		exit 1; \
	fi

EXPOSE 8080
CMD ["catalina.sh", "run"]

10.3.2.FROM

基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是DFROM

FROM eclipse-temurin:11-jdk-jammy

10.3.3.MAINTAINER

在Dockerfile中,MAINTAINER关键字用于指定维护镜像的作者和联系信息。然而,从Docker 1.13版本开始,官方不再建议使用MAINTAINER关键字,而是建议使用 LABEL 指令来提供类似的信息。

以下是使用 MAINTAINER 关键字的示例:

MAINTAINER John Doe <johndoe@example.com>

在上述示例中,MAINTAINER 后面跟着维护者的姓名和邮箱地址。

然而,请注意 MAINTAINER 关键字已被弃用,不再推荐使用。取而代之的是使用 LABEL 指令来提供类似的信息,例如:

LABEL maintainer="John Doe <johndoe@example.com>"

使用 LABEL 指令,您可以为镜像添加自定义的元数据,并在构建和使用镜像时提供更多有用的信息。

10.3.4.RUN

  1. 在 Dockerfile 中,RUN关键字用于在容器中执行命令。它可以用来安装软件包、运行脚本、执行命令等,RUN是在 docker build时运行。 例如yum安装vim,ifconfig功能等

    RUN yum -y install vim
    
  2. 两种格式

    • shell格式

      RUN apt-get update && apt-get install -y curl
      
    • exec格式

      RUN ["apt-get", "update"]
      

10.3.5.EXPOSE

当前容器对外暴露出的端口

EXPOSE关键字后面可以跟一个或多个端口号,用空格或换行进行分隔。可以使用 TCP 或 UDP 协议指定端口。

EXPOSE 80
EXPOSE 443/tcp
EXPOSE 8080/tcp
EXPOSE 5000/udp

10.3.6.WORKDIR

指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点

  1. 指定绝对路径:

    WORKDIR /app
    

    上述示例将工作目录设置为/app。在之后的指令中,可以使用相对于该目录的路径进行操作。

  2. 指定相对路径:

    WORKDIR myapp
    

10.3.7.USER

指定该镜像以什么样的用户去执行,如果都不指定,默认是root

10.3.8.ENV

用来在构建镜像过程中设置环境变量

  1. 这个环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样;

    ENV CATALINA_HOME /usr/local/tomcat
    
  2. 也可以在其它指令中直接使用这些环境变量

    WORKDIR $CATALINA_HOME
    

10.3.9.VOLUME

在 Dockerfile 中,VOLUME 关键字用于指定容器中的挂载点,该挂载点可以用于持久化存储数据或与其他容器共享数据。使用 VOLUME 关键字可以将主机上的目录或者其他容器中的挂载点映射到容器中。

以下是 VOLUME 关键字的用法示例:

VOLUME /path/to/mount/point

在上述示例中,/path/to/mount/point 是要挂载的目录或者挂载点。

还可以指定多个挂载点,每个挂载点占用一行:

VOLUME /path/to/mount/point1
VOLUME /path/to/mount/point2

请注意,使用 VOLUME 关键字仅仅是在容器中创建了一个挂载点,并没有进行实际的挂载操作。要将实际的主机目录或其他容器中的挂载点与容器中的挂载点进行关联,需要在运行容器时使用 -v--volume 参数进行挂载操作。

例如,可以使用以下命令来挂载主机上的目录到容器中的挂载点:

docker run -v /host/path:/container/path image_name

其中 /host/path 是主机上的目录,/container/path 是容器中的挂载点。

10.3.10.ADD & COPY

10.3.10.1.ADD

将宿主机目录下的文件拷贝进镜像且会自动处理URL和解压tar压缩包

ADD 的语法如下:

ADD <源路径> <目标路径>

其中,<源路径> 可以是本地文件或目录的路径,也可以是一个远程 URL。<目标路径> 是容器镜像中的目标位置。

以下是一些使用 ADD 的示例:

  1. 将本地文件添加到容器镜像中:

    ADD app.jar /app/
    

    上述示例将本地的 app.jar 文件添加到容器镜像中的 /app/ 目录下。

  2. 将本地目录添加到容器镜像中:

    ADD src/ /app/src/
    

    上述示例将本地的 src/ 目录添加到容器镜像中的 /app/src/ 目录下。

  3. 从远程 URL 添加文件到容器镜像中:

    ADD https://example.com/file.txt /app/
    
10.3.10.2.COPY

类似ADD,拷贝文件和目录到镜像中

将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置

  • COPY src dest
  • COPY [“src”, “dest”]
  • <src源路径>:源文件或者源目录
  • <dest目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。

10.3.11.CMD & ENTRYPOINT

10.3.11.1.CMD

功能:CMD关键字用于指定容器启动时要执行的命令

CMD 有两种语法形式:shell 形式和 exec 形式。

  1. Shell 形式:

    CMD <命令>
    

    在 Shell 形式中,<命令> 可以是任意有效的 Shell 命令或命令组。当容器启动时,会使用 Shell 执行该命令。

    例如:

    CMD echo "Hello, Docker!"
    

    上述示例中的命令 echo "Hello, Docker!" 将在容器启动时执行。

  2. Exec 形式:

    CMD ["可执行文件", "参数1", "参数2", ...]
    

    在 Exec 形式中,指定的命令及参数会作为一个数组传递给容器的执行环境。这种形式适用于直接执行可执行文件,而不依赖于 Shell。

    例如:

    CMD ["python", "app.py"]
    

    上述示例中的命令 python app.py 将以 python 可执行文件执行,并将 app.py 作为参数传递。

Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换;如果在同一个 Dockerfile 中有多个CMD命令,则只有最后一个CMD命令会生效。

参考官网Tomcat的dockerfile演示讲解

  1. 官网最后一行命令

    相当于启动镜像

    docker run - it -p 8080:8080 57800e5b1cbf
    

    然后暴露8080端口,启动Catalina.sh,启动tomcat。可以访问tomcat首页

  2. 覆盖CMD [“catalina.sh”,“run”]

    docker run 后加了参数/bin/bash,此时容器启动后,相当于从执行命令:

    CMD [“catalina.sh”,“run”]变为CMD [“/bin/bash”,“run”]

    1. 容器启动了
    2. tomcat没有启动
    3. 进入容器的命令终端(/bin/bash)

CMD和前面RUN命令的区别

RUN命令在镜像构建(docker build)阶段执行,用于在镜像层中添加新的内容;而CMD命令在容器启动(docker run)时执行,默认提供容器的默认命令。

10.3.11.2.ENTRYPOINT

在 Dockerfile 中,ENTRYPOINT关键字用于指定容器启动时要执行的命令或程序。

类似于 CMD 指令,但是ENTRYPOINT不会被docker run后面的命令覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

ENTRYPOINT 的语法如下:

ENTRYPOINT ["executable", "param1", "param2", ...]

ENTRYPOINT可以和CMD一起用,一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参。

当指定了ENTRYPOINT后,CMD的含义就发生了变化,不再是直接运行其命令而是将CMD的内容作为参数传递给ENTRYPOINT指令,他两个组合会变成:

<ENTRYPOINT>"<CMD>"

左边的表示将Dockerfile转化而来的命令

右边的表示执行docker run nginx:test,自己增加参数**-c /etc/nginx/new.conf**,此时**-c /etc/nginx/new.conf覆盖CMD [“etc/nginx/nginx.conf”]**的参数。

即使在dockerfile写了参数,最终要执行的还是要依照run后面添加的新参数为准。

-c : 表示指定配置文件启动

10.3.12.总结

10.4.案例

10.4.1.自定义镜像mycentosjava8

10.4.1.1.镜像的功能要求

镜像功能要求:Centos7镜像具备vim+ifconfig+jdk8

JDK的下载镜像地址 :https://www.oracle.com/java/technologies/downloads/#java8

https://mirrors.yangxingzhen.com/jdk/

拉取centos7

docker pull centos:7.9.2009
10.4.1.2.Dockerfile文件编写

准备编写Dockerfile文件(大写字母D)

FROM centos:7
MAINTAINER stonebridge<stonebridge@njfu.edu.cn>

ENV MYPATH /usr/local
WORKDIR $MYPATH

#安装vim编辑器
RUN yum -y install vim
#安装ifconfig命令查看网络IP
RUN yum -y install net-tools
#安装java8及lib库
RUN yum -y install glibc.i686
RUN mkdir /usr/local/java
#ADD是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置
ADD jdk-8u171-linux-x64.tar.gz /usr/local/java/
#配置java环境变量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_171
ENV JRE_HOME $JAVA_HOME/jre
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV PATH $JAVA_HOME/bin:$PATH

EXPOSE 80

CMD echo $MYPATHCMD echo "success--------------ok"
CMD /bin/bash
10.4.1.3.构建镜像
docker build -t 新镜像名字:TAG .

注意,上面TAG后面有个空格,有个点

docker build -t centosjava8:1.5 .

因为版本问题出现无法安装yum的的问题处理

  1. 修改yum数据源

  2. FROM指定镜像来源表明版本

    FROM centos:7
    

执行成功:

10.4.1.4.运行新镜像
docker run -it 新镜像名字:TAG 

10.4.2.dangling image

10.4.2.1.dangling image是什么

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

  1. vim Dockerfile

    CMD echo 'action is success'
    
  2. docker build

10.4.2.2.查看并删除
docker image ls -f dangling=true

虚玄镜像已经失去存在价值,可以删除

10.4.3.自定义镜像myubuntu

10.4.3.1.编写DockerFile文件
FROM ubuntu
MAINTAINER zzyy<zzyybs@126.com> 

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 $MYPATHCMD 
echo "install inconfig cmd into ubuntu success--------------ok"

CMD /bin/bash
10.4.3.2.构建新镜像
docker build -t 新镜像名字:TAG .
10.4.4.3.运行新镜像
docker run -it 新镜像名字:TAG 

10.5.总结

11.Docker微服务实战

11.1.通过IDEA新建一个普通微服务模块

略,

生产项目jar包

11.2.通过dockerfile发布微服务部署到docker容器

# 基础镜像使用java
FROM java:8
# 作者
MAINTAINER zzyy
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzyy_docker.jar
ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar
# 运行jar包
RUN bash -c 'touch /zzyy_docker.jar'
ENTRYPOINT ["java","-jar","/zzyy_docker.jar"]
#暴露6001端口作为微服务
EXPOSE 6001

11.3.构建镜像

打包成镜像文件

docker build -t zzyy_docker:1.6 .

11.4.运行容器

docker run -d -p 6001:6001 zzyy_docker:1.6

11.5.测试

浏览器访问:http://192.168.234.102:6001/order/docker

12.Docker网络

12.1.Docker网络是什么

12.1.1.docker不启动,默认网络情况

virbr0 :在CentOS7的安装过程中如果有选择相关虚拟化的的服务安装系统后,启动网卡时会发现有一个以网桥连接的私网地址的virbr0网卡(virbr0网卡:它还有一个固定的默认IP地址192.168.122.1),是做虚拟机网桥的使用的,其作用是为连接其上的虚机网卡提供 NAT访问外网的功能。

我们之前学习Linux安装,勾选安装系统的时候附带了libvirt服务才会生成的一个东西,如果不需要可以直接将libvirtd服务卸载

yum remove libvirt-libs.x86_64

12.1.2.docker启动后,网络情况

启动docker后执行命令ifconfig,可以看到,会产生一个名为docker0的虚拟网桥;

有ip地址172.17.0.1,他就是通过docker0这个虚拟网桥和宿主机以及其他容器之间进行网络通信。

12.2.docker network常用基本命令

  1. 默认创建3大网络模式,查看网络模式

    docker network ls
    
  2. 查看docker的命令帮助

    docker network --help
    
  3. 查看网络源数据

    docker network inspect  XXX网络名字
    
  4. 创建新的network

    docker network create 'network名称'
    
  5. 删除network

    docker network ls
    

12.3.Docker网络的作用

  1. 容器间的互联和通信以及端口映射

  2. 容器IP变动时候可以通过服务名直接网络通信而不受到影响

    如果有多台docker服务器,每台docker上运行多台容器。要求不同docker服务器上的不同容器相互访问,有网络之间的互联通信的要求,如果把ip地址写死,如果docker引擎服务重启,重启后ip地址可能会进行变更,配置固定的ip地址访问就会出错,因此因为我们就需要通过网络服务名去调用(类似于微服务),无论ip如何变化。

12.4.网络模式

12.4.1总体介绍

  1. bridge模式:使用–network bridge指定,默认使用docker0
  2. host模式:使用–network host指定
  3. none模式:使用–network none指定
  4. container模式:使用–network container:NAME或者容器ID指定

12.4.2.容器实例内默认网络IP生产规则

12.4.2.1.说明
  1. 先启动两个ubuntu容器实例

    docker run -it --name u1 ubuntu bash
    docker run -it --name u2 ubuntu bash
    
  2. docker inspect 容器ID or 容器名字

    docker inspect u1 | tail -n 20
    docker inspect u2 | tail -n 20
    
  3. 关闭u2实例,新建u3,查看ip变化

    docker inspect u3 | tail -n 20
    
12.4.2.2.结论

根据上面的测试的结果得出,docker容器内部的ip是有可能会发生改变的。

如果根据ip地址访问服务,存在ip地址变更风险。

12.5.案例说明

12.5.1.bridge网络模式

12.5.1.1.bridge网络模式是什么

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

查看 bridge 网络的详细信息,并通过 grep 获取名称项

docker network inspect bridge | grep name
ifconfig | grep docker
12.5.1.2.bridge工作原理
  1. Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
  2. docker run 的时候,没有指定network的话默认使用的网桥模式就是bridge,使用的就是docker0。在宿主机ifconfig,就可以看到docker0和自己create的network(后面讲)eth0,eth1,eth2……代表网卡一,网卡二,网卡三……,lo代表127.0.0.1,即localhost,inet addr用来表示网卡的IP地址
  3. 网桥docker0创建一对对等虚拟设备接口一个叫veth,另一个叫eth0,成对匹配。
    1. 整个宿主机的网桥模式都是docker0,类似一个交换机有一堆接口,每个接口叫veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);
    2. 每个容器实例内部也有一块网卡,每个接口叫eth0;
    3. docker0上面的每个veth匹配某个容器实例内部的eth0,两两配对,一一匹配。

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

12.5.1.3.案例说明
  1. 启动两台容器

    docker run -d -p 8081:8080   --name tomcat81 billygoo/tomcat8-jdk8
    
    docker run -d -p 8082:8080   --name tomcat82 billygoo/tomcat8-jdk8
    
  2. 在宿主机和容器上查询网络配置

    ip addr
    
  3. 匹配验证

12.5.2.host网络模式

12.5.2.1.host网络模式是什么

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

12.5.2.2.host工作原理

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

12.5.1.3.案例说明
  1. 被警告的设置方式

    docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
    
    • 问题:docke启动时总是遇见标题中的警告
    • 原因:docker启动时指定–network=host或-net=host,如果还指定了-p映射端口,那这个时候就会有此警告,并且通过-p设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。
    • 解决:解决的办法就是使用docker的其他网络模式,例如–network=bridge,这样就可以解决问题,或者直接无视。
  2. 正确的设置方式

    docker run -d                          --network host --name tomcat83 billygoo/tomcat8-jdk8
    
  3. 在宿主机和容器上查询网络配置

    ip addr
    

    宿主机不会新增网络配置

    容器上新增网络配置

  4. 访问容器

    没有设置-p的端口映射了,如何访问启动的tomcat83?直接借用宿主机端口访问。

    如果宿主机上的默认端口是8080,则直接访问该端口

    http://宿主机IP:8080/
    
    http://192.168.234.102:8080/
    

    在CentOS里面用默认的火狐浏览器访问容器内的tomcat83看到访问成功,因为此时容器的IP借用主机的,所以容器共享宿主机网络IP,这样的好处是外部主机与容器可以直接通信。

15.5.3.none网络模式

15.5.3.1.none网络模式是什么

在none模式下,并不为Docker容器进行任何网络配置。

也就是说,这个Docker容器没有网卡、IP、路由等信息,只有一个lo

需要我们自己为Docker容器添加网卡、配置IP等。

禁用网络功能,只有lo标识(就是127.0.0.1表示本地回环)

15.5.3.2.案例说明
docker run -d -p 8084:8080 --network none --name tomcat84 billygoo/tomcat8-jdk8
  1. 进入容器内部查看
  1. 在容器外部查看

15.5.4.container网络模式

15.5.4.1.container网络模式是什么

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

15.5.4.2.错误的案例说明
  1. 创建第一个镜像,即提供共享ip的镜像

    docker run -d -p 8085:8080     name tomcat85 billygoo/tomcat8-jdk8
    
  2. 创建第二个镜像,即依赖共享ip的镜像

    docker run -d -p 8086:8080 --network container:tomcat85 --name tomcat86 billygoo/tomcat8-jdk8
    
  3. 运行结果

    相当于tomcat86和tomcat85公用同一个ip同一个端口,导致端口冲突

    本案例用tomcat演示不合适,演示可能遇到的错误。

15.5.4.3.正确的案例说明
  1. 使用的镜像Alpine Linux

    Alpine Linux 是一款独立的、非商业的通用 Linux 发行版,专为追求安全性、简单性和资源效率的用户而设计。 可能很多人没听说过这个 Linux 发行版本,但是经常用 Docker 的朋友可能都用过,因为他小,简单,安全而著称,所以作为基础镜像是非常好的一个选择,可谓是麻雀虽小但五脏俱全,镜像非常小巧,不到 6M的大小,所以特别适合容器打包。

  2. 创建第一个镜像,即提供共享ip的镜像

    docker run -it        --name alpine1  alpine /bin/sh
    
  3. 创建第二个镜像,即依赖共享ip的镜像

    docker run -it --network container:alpine1 --name alpine2  alpine /bin/sh
    
  4. 运行结果,验证共用搭桥

  5. 假如此时关闭alpine1,再看看alpine2

    eth0@if16: 消失了。。。。。。关闭alpine1,再看看alpine2

12.6.自定义网络

12.6.1.过时的link

12.6.2.目标

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

12.6.3.不用自定义网络

12.6.3.1.使用默认网络模式创建容器
docker run -d -p 8081:8080   --name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080   --name tomcat82 billygoo/tomcat8-jdk8
12.6.3.2.在各个容器之间进行ping操作

成功启动并用docker exec进入各自容器实例内部

  1. 按照IP地址ping是无错误的

  2. 安装服务名ping

12.6.4.用自定义网络

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

12.6.4.1.新建自定义网络
12.6.4.2.新建容器加入上一步新建的自定义网络
docker run -d -p 8081:8080 --network zzyy_network  --name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080 --network zzyy_network  --name tomcat82 billygoo/tomcat8-jdk8
12.6.4.3.互ping测试

工作上我们可以写固定服务名,不能写固定ip地址

12.6.4.4.结论

自定义网络本身就维护好了主机名和ip的对应关系(ip和域名都能通)

12.7.Docker平台架构图解

12.7.1.整体说明

从其架构和运行流程来看,Docker 是一个 C/S 模式的架构,后端是一个松耦合架构,众多模块各司其职。

Docker 运行的基本流程为:

  1. 用户是使用 Docker Client 与 Docker Daemon 建立通信,并发送请求给后者。
  2. Docker Daemon 作为 Docker 架构中的主体部分,首先提供 Docker Server 的功能使其可以接受 Docker Client 的请求。
  3. Docker Engine 执行 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式的存在。
  4. Job 的运行过程中,当需要容器镜像时,则从 Docker Registry 中下载镜像,并通过镜像管理驱动 Graph driver将下载镜像以Graph的形式存储。
  5. 当需要为 Docker 创建网络环境时,通过网络管理驱动 Network driver 创建并配置 Docker 容器网络环境。
  6. 当需要限制 Docker 容器运行资源或执行用户指令等操作时,则通过 Execdriver 来完成。
  7. Libcontainer是一项独立的容器管理包,Network driver以及Exec driver都是通过Libcontainer来实现具体对容器进行的操作。

12.7.2.整体架构

13.Docker-compose容器编排

13.1.Docker-compose是什么

Docker-Compose是Docker官方的开源项目,负责实现对Docker容器集群的快速编排。

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

13.2.Docker-compose的作用

docker建议我们每一个容器中只运行一个服务,因为docker容器本身占用资源极少,所以最好是将每个服务单独的分割开来但是这样我们又面临了一个问题?

如果我需要同时部署好多个服务,难道要每个服务单独写Dockerfile然后在构建镜像,构建容器,这样累都累死了,所以docker官方给我们提供了docker-compose多服务部署的工具

例如要实现一个Web微服务项目,除了Web服务容器本身,往往还需要再加上后端的数据库mysql服务容器,redis服务器,注册中心eureka,甚至还包括负载均衡容器等等。

Compose允许用户通过一个单独的docker-compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)

可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。

13.3.Docker-compose下载

13.3.1.官网

https://docs.docker.com/compose/compose-file/compose-file-v3/

13.3.2.下载安装和卸载

https://docs.docker.com/compose/install/

curl -SL https://github.com/docker/compose/releases/download/v2.19.1/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version

13.3.3.卸载

如果使用curl方式安装,卸载方式如下

sudo rm /usr/local/bin/docker-compose

13.4.Compose核心概念

  1. 一文件 :docker-compose.yml

  2. 两要素 :

    1. 服务(service)
      一个个应用容器实例,比如订单微服务、库存微服务、mysql容器、nginx容器或者redis容器

    2. 工程(project)

      由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。

13.5.Compose使用的三个步骤

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

13.6.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 # 停止服务

13.7.Compose编排微服务

13.7.1.准备Spring Boot微服务工程

在项目中配置好MySQL,redis的地址,此处为docker引擎部署的机器地址,此时不需要部署了MySQL,Redis的容器。

打包为jar包,保存在docker引擎宿主机的文件夹中,并在同一文件夹配置好Dockerfile。

Dockerfile

# 基础镜像使用java
FROM java:8
# 作者
MAINTAINER zzyy
# VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为zzyy_docker.jar
ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar
# 运行jar包
RUN bash -c 'touch /zzyy_docker.jar'
ENTRYPOINT ["java","-jar","/zzyy_docker.jar"]
#暴露6001端口作为微服务
EXPOSE 6001

13.7.2.通过jar包和Dockerfile构建镜像

进入jar包和Dockerfile所在的文件夹执行命令构成镜像

docker build -t zzyy_docker:1.6 .

13.7.3.不使用Compose部署项目

13.7.3.1.mysql容器实例启动
  1. 新建mysql容器实例

    docker run -p 3306:3306 --name mysql57 --privileged=true -v /zzyyuse/mysql/conf:/etc/mysql/conf.d -v /zzyyuse/mysql/logs:/logs -v /zzyyuse/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
    
  2. 进入MySQL容器实例

    docker exec -it mysql57 bash
    
  3. 执行建库建表语句

    mysql -uroot -p
    create database db2021;
    use db2021;
    
    CREATE TABLE `t_user` (
      `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
      `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
      `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
      `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
      `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
      `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
    
13.7.3.2.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:latest redis-server
13.7.3.3.微服务工程启动
docker run -d -p 6001:6001 zzyy_docker:1.6
13.7.3.4.swagger测试
13.7.3.5.不使用Compose部署项目的缺陷
  1. 先后顺序要求固定,先mysql+redis才能微服务访问成功
  2. 多个run命令…
  3. 容器间的启停或宕机,有可能导致IP地址对应的容器实例变化,映射出错,要么生产IP写死(可以但是不推荐),要么通过服务调用

13.7.4.使用Compose编排微服务

使用服务编排,一套带走,安排

13.7.4.1.编写docker-compose.yml文件
version: "3"   # Docker-compose的版本

services: #将所有的服务容器实例都配置services下
  microService: #定义一个名为microService的服务
    image: zzyy_docker:1.6 # 镜像名称
    container_name: ms01 # 容器名称
    ports: # 映射端口
      - "6001:6001"
    volumes: #容器数据卷,宿主机内部的路径和容器内路径的映射
      - /app/microService:/data
    networks: # 该服务都在同一网段下
      - stoneBridgeNet
    depends_on: # 告知该服务在redis,mysql服务后面启动
      - 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:
      - stoneBridgeNet
    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:
      - stoneBridgeNet
    command: --default-authentication-plugin=mysql_native_password #解决外部无法访问

networks: # 定义Docker Network atguigu_net,所有的服务都跑在这一个网段下
  stoneBridgeNet:
13.7.4.2.第二次修改微服务工程docker_boot
  1. 修改项目application.properties中的需要访问的服务(比如MySQL,Redis)的ip地址通过服务名访问,IP无关。

  2. mvn package命令将微服务形成新的jar包并上传到Linux服务器/mydocker目录下

  3. 编写构建jar包运行的容器的代码Dockerfile

    # 基础镜像使用java
    FROM java:8
    # 作者
    MAINTAINER zzyy
    # VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp
    VOLUME /tmp
    # 将jar包添加到容器中并更名为zzyy_docker.jar
    ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar
    # 运行jar包
    RUN bash -c 'touch /zzyy_docker.jar'
    ENTRYPOINT ["java","-jar","/zzyy_docker.jar"]
    #暴露6001端口作为微服务
    EXPOSE 6001
    
  4. 停掉相关的所有容器和镜像,全部重新构建

  5. 构建镜像

    docker build -t zzyy_docker:1.6 .
    
13.7.4.3.启动所有docker-compose服务
docker-compose up -d

执行

docker-compose up
  1. 检查网络

  2. 检查运行的容器

13.7.4.4.进入mysql容器实例并新建库db2021+新建表t_user

13.7.4.5.测试
13.7.4.6.关停服务
docker-compose stop

必须在docker-compose.yaml文件所在的目录,执行docker-compose stop,关停所有服务。

14.Docker轻量级可视化工具Portainer

14.1.Portainer是什么

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

14.2.官网

  • https://www.portainer.io/

  • https://docs.portainer.io/v/ce-2.9/start/install/server/docker/linux

14.3.安装

14.3.1.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

14.3.2.创建admin账户

第一次登录需创建admin,访问地址:http://ip:9000/#/init/admin

14.3.3.设置admin用户和密码后首次登陆

上图的图形展示,能想得起对应命令吗?

14.4.登陆并演示介绍常用操作case

15.Docker容器监控之CAdvisor+InfluxDB+Granfana

15.1.原生命令

15.1.1.操作

当我们把项目部署到docker时,我们需要监控和统计其运行清空。

docker stats命令的结果

15.1.2.问题

通过docker stats命令可以很方便的看到当前宿主机上所有容器的CPU,内存以及网络流量等数据,一般小公司够用了。但是,docker stats统计结果只能是当前宿主机的全部容器,数据资料是实时的,没有地方存储、没有健康指标过线预警等功能。

15.2.CAdvisor+InfluxDB+Granfana是什么?

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

15.2.1.CAdvisor

15.2.2.InfluxDB

15.2.3.Granfana

15.2.4.总结

15.3.compose容器编排安装

15.3.新建目录

15.3.2.新建3件套组合的docker-compose.yml

需要配置docker network,否则只能用ip访问。

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
    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

15.3.3.启动docker-compose文件

docker-compose up

15.4.测试

15.4.1.浏览cAdvisor收集服务http://ip:8080/

第一次访问慢,cadvisor也有基础的图形展现功能,这里主要用它来作数据采集。

访问:http://192.168.234.102:8080

15.4.2.浏览influxdb存储服务http://ip:8083/

访问:http://192.168.234.102:8083

15.4.3.浏览grafana展现服务http://ip:3000

访问:http://192.168.234.102:3000,默认帐户密码(admin/admin)

15.4.4.grafana配置

  1. 配置数据源

  2. 选择influxdb数据源

  3. 配置细节

    注意前面的docker-compose.yml需要配置docker network,否则只能用ip访问。

  4. 配置面板panel

hanghai.aliyuncs.com/docker/pic/08184221673.png" style=“zoom: 40%;” />

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

15.2.1.CAdvisor

15.2.2.InfluxDB

15.2.3.Granfana

15.2.4.总结

15.3.compose容器编排安装

15.3.新建目录

15.3.2.新建3件套组合的docker-compose.yml

需要配置docker network,否则只能用ip访问。

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
    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

15.3.3.启动docker-compose文件

docker-compose up

15.4.测试

15.4.1.浏览cAdvisor收集服务http://ip:8080/

第一次访问慢,cadvisor也有基础的图形展现功能,这里主要用它来作数据采集。

访问:http://192.168.234.102:8080

15.4.2.浏览influxdb存储服务http://ip:8083/

访问:http://192.168.234.102:8083

15.4.3.浏览grafana展现服务http://ip:3000

访问:http://192.168.234.102:3000,默认帐户密码(admin/admin)

15.4.4.grafana配置

  1. 配置数据源

  2. 选择influxdb数据源

  3. 配置细节

    注意前面的docker-compose.yml需要配置docker network,否则只能用ip访问。

  4. 配置面板panel

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值