Docker学习笔记

Docker学习笔记:

Docker简介:

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

容器与虚拟机比较:

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

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

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

Docker安装:

1. yum安装gcc相关:

yum -y install gcc;

yum -y install gcc-c++;

2. 安装需要的软件包: yum install -y yum-utils;

3. 设置stable镜像仓库: yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo;

4. 更新yum软件包索引: yum makecache fast;

5. 安装Docker CE: yum -y install docker-ce docker-ce-cli containerd.io;

6. 启动Docker: systemctl start docker;

7. 测试:

docker version;

docker run hello-world;

Docker卸载 :

1. systemctl stop docker;

2. yum remove docker-ce docker-ce-cli containerd.io;

3. rm -rf /var/lib/docker;

4. rm -rf /var/lib/containerd;

Docker常用命令:

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

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

2. 镜像命令:

1. 查看所有镜像: docker images -aq (-a 列出本地所有镜像 -q 只显示镜像id);

2. 查找镜像: docker search 镜像名字 --limit 5 (--limit 限制搜索行数);

3. 拉取镜像: docker pull 镜像名字[:tag] (tag代表版本号可写可不写,不写就是最新版);

4. 查看镜像/容器/数据卷所占的空间: docker system df;

5. 删除镜像: docker rmi -f 镜像名1:TAG 镜像名2:TAG/镜像id1 镜像名id2

6. 删除全部镜像: docker rmi -f $(docker images -qa)

面试题:

谈谈docker虚悬镜像是什么?

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

3. 容器命令:

1. 新建+启动容器: docker run [options] image_name [command] [args] (options: --name="name" 为容器指定一个名称,-d 后台运行容器并返回容器ID,-i 以交互模式运行容器,-t 为容器重新分配一个伪输入终端,-P 随机端口映射,-p 指定端口映射);

2. 列出当前所有正在运行的容器: docker ps [options] (options: -a 列出当前所有正在运行的容器+历史上运行过的,-l 显示最近创建的容器,-n 显示最近n个创建的容器,-q 只显示容器编号);

3. 退出容器

1. run进去容器,exit退出,容器停止;

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

4. 启动已停止运行的容器: docker start 容器ID或者容器名;

5. 重启容器: docker restart 容器ID或者容器名;

6. 停止容器: docker stop 容器ID或者容器名;

7. 强制停止容器: docker kill 容器ID或容器名;

8. 删除已停止的容器: docker rm -f 容器ID;

9. 一次性删除多个容器实例: docker rm -f $(docker ps -aq);

10. 查看容器日志: docker logs 容器ID;

11. 查看容器内运行的进程: docker top 容器ID;

12. 查看容器内部细节: docker inspect 容器ID;

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

1. docker exec -it 容器ID bashShell;

2. docker attach 容器ID;

3. 两个命令的区别:

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

2. exec 是在容器中打开新的终端,并且可以启动新的进程用exit退出,不会导致容器的停止;

14. 从容器内拷贝文件到主机上: docker cp 容器ID:容器内路径 目的主机路径;

15. 导出容器: docker export 容器ID > 文件名.tar;

16. 导出容器: cat 文件名.tar | docker import - 镜像用户/镜像名:镜像版本号;

Docker镜像:

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

2. 分层的镜像: 在pull镜像的过程中发现镜像是分层的;

3. UnionFS(联合文件系统)

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

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

4. Docker镜像加载原理:

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

bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是引导文件系统bootfs

这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核;

当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs;

5. 为什么 Docker 镜像要采用这种分层结构呢: 镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用;

6. 重点理解:

1. Docker镜像层都是只读的,容器层是可写的;

2. 当容器启动时,一个新的可写层被加载到镜像的顶部;

3. 这一层通常被称作"容器层","容器层"之下的都叫"镜像层";

7. Docker镜像commit操作:

1. 作用: docker commit提交容器副本使之成为一个新的镜像;

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

Docker容器数据卷:

1. 作用: 将docker容器内的数据保存进宿主机的磁盘中;

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

3. 读写规则映射添加说明:

1. 读写(默认): docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录:rw 镜像名;

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

4. 卷的继承和共享: docker run -it --privileged=true --volumes-from 父类 --name u2 ubuntu

Docker常规安装简介:

安装redis集群:

1. 3主3从redis集群配置

1. 新建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端口号

2. 进入容器: docker exec -it redis-node-1 /bin/bash

3. 构建主从关系:

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

命令解释:

--cluster-replicas 1: 表示为每个master创建一个slave节点

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

查看集群状态命令:

cluster info

cluster nodes

2. 主从扩容案例

1. 新建6387、6388两个节点:

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

2. 进入6387容器实例内部:

docker exec -it redis-node-7 /bin/bash

3. 将新增的6387节点(空槽号)作为master节点加入原集群:

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

4. 重新分派槽号:

redis-cli --cluster reshard IP地址:端口号

5. 为主节点6387分配从节点6388:

redis-cli --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID

3. 主从缩容案例:

1. 将6388删除:

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

redis-cli --cluster del-node 192.168.111.147:6388 5d149074b7e57b802287d1797a874ed7a1a284a8

2. 将6387的槽号清空,重新分配:

redis-cli --cluster reshard 192.168.111.147:6381

3. 将6387删除:

redis-cli --cluster del-node ip:端口 6387节点ID

DockerFile:

1. DockerFile是什么: Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本

2. 构建三步骤:

1. 编写Dockerfile文件

2. docker build命令构建镜像

3. docker run依镜像运行容器实例

3. DockerFile构建过程解析

1. Dockerfile内容基础知识

1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数

2. 指令按照从上到下,顺序执行

3. #表示注释

4. 每条指令都会创建一个新的镜像层并对镜像进行提交

2. Docker执行Dockerfile的大致流程

1. docker从基础镜像运行一个容器

2. 执行一条指令并对容器作出修改

3. 执行类似docker commit的操作提交一个新的镜像层

4. docker再基于刚提交的镜像运行一个新容器

5. 执行dockerfile中的下一条指令直到所有指令都执行完成

4. DockerFile常用保留字指令

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

2. MAINTAINER: 镜像维护者的姓名和邮箱地址

3. RUN: 容器构建时需要运行的命令

1. RUN是在 docker build时运行

2. 两种格式:

1. shell格式: RUN yum -y install vim

2. exec格式: RUN ["可执行文件","参数1","参数2"]

4. EXPOSE: 当前容器对外暴露出的端口

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

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

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

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

9. COPY: 类似ADD,拷贝文件和目录到镜像中但不解压

10. VOLUME: 容器数据卷,用于数据保存和持久化工作

11. CMD: 指定容器启动后的要干的事情

1. Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换

2. 它和前面RUN命令的区别: CMD是在 docker run 时运行,RUN是在 docker build 时运行

12. ENTRYPOINT: 也是用来指定一个容器启动时要运行的命令

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

2. 优点: 在执行docker run的时候可以指定 ENTRYPOINT 运行所需的参数

3. 注意: 如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效

DockerFile案例:

1. 自定义镜像mycentosjava8

1. 编写: 准备编写Dockerfile文件

FROM centos

MAINTAINER zzyy<zzyybs@126.com>

ENV MYPATH /usr/local

WORKDIR $MYPATH

#解决安装vim报错

RUN cd /etc/yum.repos.d/

RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*

RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*

#安装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 $MYPATH

CMD echo "success--------------ok"

CMD /bin/bash

2. 构建 :docker build -t 新镜像名字:TAG .

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

2. 虚悬镜像:

1. 是什么: 仓库名、标签都是<none>的镜像,俗称dangling image

2. Dockerfile写一个:

FROM ubuntu

CMD echo 'action is success'

3. 查看: docker image ls -f dangling=true

4. 删除: docker image prune

Docker网络:

1. Docker网络是什么:

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

1. ens33

2. lo

3. virbr0

2. docker启动后,网络情况

1. ens33

2. lo

3. virbr0

4. docker0

2. Docker网络常用基本命令:

1. All命令: docker network --help

2. 查看网络: docker network ls

3. 查看网络源数据: docker network inspect XXX网络名字

4. 创建网络: docker network create XXX网络名字

5. 删除网络: docker network rm XXX网络名字

3. Docker网络的作用:

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

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

4. 网络模式:

1. 总体介绍

1. bridge模式:使用--network bridge指定,默认使用docker0

2. host模式:使用--network host指定

3. none模式:使用--network none指定

4. container模式:使用--network container:NAME或者容器ID指定

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

3. 案例说明:

1. bridge :

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

2. 说明:

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,两两配对,一一匹配;

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

2. host:

1. host是什么: 直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行 NAT 转换

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

3. 代码 :

1. 警告: docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8

1. 问题: docke启动时总是遇见标题中的警告

2. 解释: docker启动时指定--network=host或-net=host,如果还指定了-p映射端口,那这个时候就会有此警告,并且通过-p设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增

3. 解决: 解决的办法就是使用docker的其他网络模式,例如--network=bridge,这样就可以解决问题,或者直接无视

2. 正确: docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8

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

4. container:

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

2. 案例命令:

docker run -it --name alpine1 alpine /bin/sh

docker run -it --network container:alpine1 --name alpine2 alpine /bin/sh

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

Docker-compose容器编排:

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

2. 能干嘛: 可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建

3. 安装步骤:

1. curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

2. chmod +x /usr/local/bin/docker-compose

3. docker-compose --version

4. 卸载步骤: sudo rm /user/local/bin/docker-compose

5. Compose核心概念:

1. 一文件: docker-compose.yml

2. 两要素:

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

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

6. Compose使用的三个步骤:

1. 编写Dockerfile定义各个微服务应用并构建出对应的镜像文件

2. 使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务。

3. 最后,执行docker-compose up命令 来启动并运行整个应用程序,完成一键部署上线

7. Compose常用命令:

1. docker-compose -h # 查看帮助

2. docker-compose up # 启动所有docker-compose服务

3. docker-compose up -d # 启动所有docker-compose服务并后台运行

4. docker-compose down # 停止并删除容器、网络、卷、镜像。

5. docker-compose exec yml里面的服务id # 进入容器实例内部 docker-compose exec docker-compose.yml文件中写的服务id /bin/bash

6. docker-compose ps # 展示当前docker-compose编排过的运行的所有容器

7. docker-compose top # 展示当前docker-compose编排过的容器进程

8. docker-compose logs yml里面的服务id # 查看容器输出日志

9. docker-compose config # 检查配置

10. docker-compose config -q # 检查配置,有问题才有输出

11. docker-compose restart # 重启服务

12. docker-compose start # 启动服务

13. docker-compose stop # 停止服务

8. Compose编排微服务:

1. 编写docker-compose.yml文件

version: "3"

services:

microService:

image: zzyy_docker:1.6

container_name: ms01

ports:

- "6001:6001"

volumes:

- /app/microService:/data

networks:

- atguigu_net

depends_on:

- redis

- mysql

redis:

image: redis:6.0.8

ports:

- "6379:6379"

volumes:

- /app/redis/redis.conf:/etc/redis/redis.conf

- /app/redis/data:/data

networks:

- atguigu_net

command: redis-server /etc/redis/redis.conf

mysql:

image: mysql:5.7

environment:

MYSQL_ROOT_PASSWORD: '123456'

MYSQL_ALLOW_EMPTY_PASSWORD: 'no'

MYSQL_DATABASE: 'db2021'

MYSQL_USER: 'zzyy'

MYSQL_PASSWORD: 'zzyy123'

ports:

- "3306:3306"

volumes:

- /app/mysql/db:/var/lib/mysql

- /app/mysql/conf/my.cnf:/etc/my.cnf

- /app/mysql/init:/docker-entrypoint-initdb.d

networks:

- atguigu_net

command: --default-authentication-plugin=mysql_native_password #解决外部无法访问

networks:

atguigu_net:

2. 执行 docker-compose up

Docker轻量级可视化工具Portainer:

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

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

Docker容器监控之 CAdvisor + InfluxDB + Granfana:

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

version: '3.1'

volumes:

grafana_data: {}

services:

influxdb:

image: tutum/influxdb:0.9

restart: always

environment:

- PRE_CREATE_DB=cadvisor

ports:

- "8083:8083"

- "8086:8086"

volumes:

- ./data/influxdb:/data

cadvisor:

image: google/cadvisor

links:

- influxdb:influxsrv

command: -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influxsrv:8086

restart: always

ports:

- "8080:8080"

volumes:

- /:/rootfs:ro

- /var/run:/var/run:rw

- /sys:/sys:ro

- /var/lib/docker/:/var/lib/docker:ro

grafana:

user: "104"

image: grafana/grafana

user: "104"

restart: always

links:

- influxdb:influxsrv

ports:

- "3000:3000"

volumes:

- grafana_data:/var/lib/grafana

environment:

- HTTP_USER=admin

- HTTP_PASS=admin

- INFLUXDB_HOST=influxsrv

- INFLUXDB_PORT=8086

- INFLUXDB_NAME=cadvisor

- INFLUXDB_USER=root

- INFLUXDB_PASS=root

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值