三、Docker进阶

1 容器数据卷

1.1 卷技术介绍

        关键字:持久化、同步、数据共享。

        通过前面Docker入门的学习,我们可以知道docker的理念就是把应用和环境打包成镜像,这样可以更加方便的去运行容器。

        但是现在有一个问题,例如我的容器中的mysql内存储了若干条数据,如果此时容器被删除,那我的数据是不是就丢了呢?(也就是我们常说的删库)这在开发中肯定是不允许存在的!

        于是我们就多了这样一个需求:将数据存储在容器以外的地方(例如本地),然后将容器中产生的数据同步到本地,这里就出现了我们接下来要学习的卷技术。

        卷技术:说白了就是目录的挂载,将容器中的目录挂在到Linux上,学习过vue的,我们可以将其简单理解成为v-model(双向绑定),其作用是相同的。如图,将容器里的/usr/mysql挂载到外部linux下的/home/mysql。现在不仅mysql容器可以使用,其他的2个容器同样可以使用,实现了数据共享。

                    

1.2 使用数据卷

使用命令-v进行挂载docker run -it -v 主机目录:容器目录

docker run -it -v /home/test:/home centos /bin/bash

 这时候用docker inspect命令来查看容器,可以看到挂载情况。

[root@zlk home]# docker inspect 6a190e62a77b
# 找到Mounts,可以看到绑定的具体详情。

         接下来我们停止容器,如果容器停止了,我在外面把文件都删掉了,当再次启动容器后进去查看,发现也是同步的。

        经过以上练习操作,相信你也能够体会到容器卷的作用,它为我们带来了极大的便利。这里简单总结一下,如果容器里涉及到需要修改配置的,那么我们采用容器卷技术将容器目录挂在到主机目录后,就不需要进入容器修改了,直接在容器外修改,容器内自动同步! 

1.3 实战:安装mysql

mysql的数据持久化问题:

# 获取镜像
[root@zlk home]# docker pull mysql:5.7
 
# 运行容器, 需要做数据挂载! 
# 安装启动mysql,需要配置密码(注意)
# 官方测试, docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
 
# 启动我们自己的镜像
-d      # 后台运行
-p      # 端口隐射
-v      # 卷挂载
-e      # 环境配置
--name  # 容器的名字
[root@zlk home]# docker run -d -p 3344:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
9552bf4eb2b69a2ccd344b5ba5965da4d97b19f2e1a78626ac1f2f8d276fc2ba
 
# 启动成功之后,我们在本地使用navicat链接测试一下
# navicat链接到服务器的3344 --- 3344 和 容器的3306映射,这个时候我们就可以连接上mysql了!

# 在本地测试创建一个数据库,查看下我们的路径是否有效!

此时我们将刚刚创建的容器删除,查看本地数据是否存在?

[root@zlk /]# docker rm -f mysql01
mysql01
[root@zlk /]# docker ps -a
CONTAINER ID   IMAGE     COMMAND             CREATED             STATUS                        PORTS                                       NAMES
e5fe938049b2   tomcat    "catalina.sh run"   About an hour ago   Up About an hour              0.0.0.0:3355->8080/tcp, :::3355->8080/tcp   tomcat
[root@zlk mysql]# cd /home/
[root@zlk home]# ll
总用量 0
drwxr-xr-x. 4 root root 30 5月  20 00:26 mysql
[root@zlk home]# cd mysql/
[root@zlk mysql]# ll
总用量 4
drwxr-xr-x. 2 root    root    6 5月  20 00:26 conf
drwxr-xr-x. 5 polkitd root 4096 5月  20 00:26 data

通过上述测试,我们发现,挂载到本地的数据卷依然存在,这就实现了容器数据持久化功能。

1.4 匿名和具名挂载

# 匿名挂载
-v 容器内路径
docker run -d -P --name nginx01 -v /etc/nginx nginx     # -P 随机指定端口
 
# 查看所有volume的情况
[root@zlk ~]# docker volume ls
DRIVER    VOLUME NAME
local     282edef9101142af114e111818216d05bef294a99c309ecd36170300393d230a
local     447b4b50f4a2551107a62649cdf3b4f944a7ba2872094b2daf5f3bda61924489
 
# 这里发现,这种情况就是匿名挂载,我们在-v 后面只写了容器内的路径,没有写容器外的路径!
 
# 具名挂载
[root@zlk ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
26da1ec7d4994c76e80134d24d82403a254a4e1d84ec65d5f286000105c3da17
[root@zlk ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
26da1ec7d499        nginx               "/docker-entrypoint.…"   3 seconds ago       Up 2 seconds        0.0.0.0:32769->80/tcp   nginx02
486de1da03cb        nginx               "/docker-entrypoint.…"   3 minutes ago       Up 3 minutes        0.0.0.0:32768->80/tcp   nginx01
[root@zlk ~]# docker volume ls
DRIVER              VOLUME NAME
local               561b81a03506f31d45ada3f9fb7bd8d7c9b5e0f826c877221a17e45d4c80e096
local               36083fb6ca083005094cbd49572a0bffeec6daadfbc5ce772909bb00be760882
local               juming-nginx
 
# 通过-v 卷名:容器内的路径
# 查看一下这个卷
# docker volume inspect juming-nginx
 
[root@zlk ~]# docker volume inspect juming-nginx
[
  {
      "CreatedAt": "2020-08-08T18:15:21+08:00",
      "Driver": "local",
      "Labels": null,
      "Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data",
      "Name": "juming-nginx",
      "Options": null,
      "Scope": "local"
  }
]

        可以发现docker容器内的所有卷,在没有指定目录的情况下都是存放在/var/lib/docker/volumes/xxxxx/_data目录下。

        通过具名挂载可以方便的找到我们的一个卷,在大多数情况下我们使用的都是具名挂载。

# 如何确定是具名挂载还是匿名挂载,还是指定路径挂载!
-v  容器内路径                   # 匿名挂载
-v  卷名:容器内路径               # 具名挂载
-v /主机路径:容器内路径            # 指定路径挂载

      知识扩展: -v 卷名:容器内容路径:ro  rw 改变读写权限.

ro  readonly    # 只读
rw  readwrite   # 可读可写
 
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
 
# ro 只要看到ro就说明这个路径只能通过宿主机来操作,容器内容无法操作。

2 DockerFile

2.1 初识DockerFile

        DockerFile就是用来构建Docker镜像的文件!是一个命令脚本!通过这个脚本可以生成镜像,镜像是一层一层的,脚本是一个一个的命令,每个命令都是一层。

        创建一个DockerFile文件,名字可以随意(推荐使用dockerfile)。

# 文件中的内容    指令(大写)、参数
FROM centos
 
VOLUME ["volume01", "volume02"]
 
CMD echo "----end----"
CMD /bin/bash
 
# 这里的每一个命令都属于镜像的一层!

 启动上述我们自己构建的镜像!

 这个卷和外部一定有一个同步的目录!我们来查看一下卷挂载的路径!

docker  inspect 容器id,找到Mounts,Source对应的就是数据卷外部挂载路径。

测试一下,看容器内和容器外的文件是否能够进行同步!

         这种方式我们在以后的实际应用中会经常用到,因为我们经常会构建自己的镜像。如果在构建镜像的时候没有挂载卷,我们可以手动操作使用具名挂载来实现数据卷的挂载( -v 卷名:容器内路径)!

2.2 数据卷容器

多个mysql同步数据!

 启动2个容器,通过我们刚才自己写的镜像启动: 

删除容器docker01,查看一下docker02是否可以访问这个文件!

 核心代码:

[root@zlk home]# docker run -d -p 3344:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
 
[root@zlk home]# docker run -d -p 3344:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7

小结:

  • 容器之间配置信息的传递,数据卷容器的生命周期一直持续到没有容器使用为止。
  • 一旦持久化到了本地,本地的数据是不会删除的。

2.3 DockerFile的构建过程

构建步骤:

  • 编写一个dockerFile文件
  • docker build 构建成为一个镜像
  • docker run 运行镜像
  • docker push 发布镜像(DockerHub、阿里云镜像)

基础知识:

  • 每个保留关键字(指令)都必须是大写字母
  • 执行顺序是从上到下
  • #表示注释
  • 每一个指令都会创建一个新的镜像层并提交

                                

        dockerfile是面向开发的,以后发布项目,制做镜像,就需要编写dockerfile 文件,这个文件的编写相对来说比较简单!
步骤:

  • 开发,部署,运维…缺一不可。
  • DockerFile:构建文件,定义一切的步骤,也就是源代码。
  • DokcerImages:通过dockerFile构建生成的镜像,最终发布和运行产品。
  • Dokcer容器:容器就是镜像运行起来,提供服务器。

2.4 DockerFile指令说明

FROM 		# 基础镜像,一切从这里开始构建
MAINTAINER	# 镜像是谁写的,姓名+邮箱
RUN			# 镜像构建的是需要运行的命令
ADD			# 步骤:tomcat镜像,这个tomcat压缩包!添加内容
WORKDIR		# 镜像的工作目录
VOLUME		# 挂载的目录
EXPOSE		# 保留端口配置
CMD 		# 指定容器启动时要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT 	# 指定容器启动时要运行的命令,可以追加命令
ONBUILD		# 当构建一个被继承 DockerFile时,就会运行这个指令,触发指令
COPY		# 类似ADD,将文件拷贝到镜像中
ENV			# 构建的时候,设置环境变量

2.5 创建一个自己的centos

[root@zlk home]# cd /home
[root@zlk home]# mkdir dockerfile
[root@zlk home]# cd dockerfile
[root@zlk dockerfile]# docker images 
REPOSITORY            TAG       IMAGE ID       CREATED        SIZE
centos                latest    5d0da3dc9764   8 months ago   231MB
[root@k3 dockerfile]# docker run -it centos  /bin/bash
[root@ff53460fa928 /]# pwd
/
[root@ff53460fa928 /]# vim   #此时的基础镜像不能使用该命令
bash: vim: command not found
[root@ff53460fa928 /]# ifconfig
bash: ifconfig: command not found
[root@ff53460fa928 /]# [root@zlk dockerfile]#  #返回到宿主机 

# 1. 编写Dockerfile的文件
[root@zlk dockerfile]# vim mydockerfile-centos
[root@zlk dockerfile]# cat mydockerfile-centos 
FROM centos
MAINTAINER 星悦糖<1205441442@qq.com>
 
ENV MYPATH /usr/local
WORKDIR $MYPATH     # 镜像的工作目录
 
RUN yum -y install vim
RUN yum -y install net-tools
 
EXPOSE 80
 
CMD echo $MYPATH
CMD echo "---end---"
CMD /bin/bash

# 2. 通过这个文件构建镜像
# 命令 docker build -f dockerfile文件路径 -t 镜像名:[tag] .
 
[root@zlk dockerfile]# docker build -f mydockerfile-centos -t mycentos:0.1 .
 
Successfully built d2d9f0ea8cb2
Successfully tagged mycentos:0.1

2.6 CMD 和 ENTRYPOINT 区别

  • CMD :指定容器启动时要运行的命令,只有最后一个会生效,可被替代
  • ENTRYPOINT :指定容器启动时要运行的命令,可以追加命令

测试CMD:

# 1. 编写dockerfile文件:dockerfile-cmd-test 
[root@zlk dockerfile]# vim dockerfile-cmd-test 
FROM centos
CMD ["ls", "-a"]

# 2. 构建镜像
[root@zlk dockerfile]# docker build -f dockerfile-cmd-test -t cmdtest .
 
# 3. run运行, 发现我们的ls -a 命令生效
[root@zlk dockerfile]# docker run -it cmdtest
.
..
.dockerenv
bin
dev
etc
home
lib
lib64
# 想追加一个命令 -l 变成 ls -al
[root@zlk dockerfile]# docker run -it cmdtest -l
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "exec: \"-l\": executable file not found in $PATH": unknown.
 
# cmd的情况下 -l替换了CMD["ls", "-a"]命令, 而-l不是命令,所以报错了

测试ENTRYPOINT:

# 1. 编写dockerfile文件
[root@zlk dockerfile]# vim dockerfile-entrypoint-test 
FROM centos
ENTRYPOINT ["ls", "-a"]
 
# 2. 构建文件
[root@zlk dockerfile]# docker build -f dockerfile-entrypoint-test -t entrypoint-test .
 
# 3. run运行 发现我们的ls -a 命令同样生效
[root@zlk dockerfile]# docker run -it entrypoint-test
.
..
.dockerenv
bin
dev
etc
home
lib
 
# 4. 追加命令, 是直接拼接到ENTRYPOINT命令的后面的!
[root@zlk dockerfile]# docker run -it entrypoint-test -l
total 56
drwxr-xr-x  1 root root 4096 Aug 13 07:52 .
drwxr-xr-x  1 root root 4096 Aug 13 07:52 ..
-rwxr-xr-x  1 root root    0 Aug 13 07:52 .dockerenv
lrwxrwxrwx  1 root root    7 May 11  2019 bin -> usr/bin
drwxr-xr-x  5 root root  340 Aug 13 07:52 dev
drwxr-xr-x  1 root root 4096 Aug 13 07:52 etc
drwxr-xr-x  2 root root 4096 May 11  2019 home
lrwxrwxrwx  1 root root    7 May 11  2019 lib -> usr/lib
lrwxrwxrwx  1 root root    9 May 11  2019 lib64 -> usr/lib64
drwx------  2 root root 4096 Aug  9 21:40 lost+found

# 等价于
[root@zlk dockerfile]# docker run -it entrypoint-test ls -al
 

3 DockerFile制作Tomcat镜像

3.1 制作tomcat镜像

(1)准备镜像文件 tomcat压缩包,jdk的压缩包!下载xxxxx.tar.gz

Tomcat下载地址:Apache Tomcat® - Apache Tomcat 9 Software Downloads

jdk下载地址:Java Downloads | Oracle

(2)编写Dockerfile文件,官方命名Dockerfile, build会自动寻找这个文件,就不需要-f指定了

[root@zlk tomcat]# touch readme.txt
[root@zlk tomcat]# vim Dockerfile 
[root@zlk tomcat]# cat Dockerfile 
FROM centos
MAINTAINER 星悦糖<1205441442@qq.com>
 
COPY readme.txt /usr/local/readme.txt
 
ADD apache-tomcat-9.0.65.tar.gz /usr/local/
ADD jdk-8u341-linux-aarch64.tar.gz /usr/local/
 
# RUN yum -y install vim
 
ENV MYPATH /usr/local
WORKDIR $MYPATH
 
ENV JAVA_HOME /usr/local/jdk1.8.0_341
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.65
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.65 
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
 
EXPOSE 8090
 
CMD /usr/local/apache-tomcat-9.0.65/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.65/bin/logs/catalina.out

(3)构建镜像

[root@zlk tomcat]# docker build -t diytomcat .
Sending build context to Docker daemon  86.09MB
Step 1/14 : FROM centos
 ---> 5d0da3dc9764
Step 2/14 : MAINTAINER 星悦糖<1205441442@qq.com>
 ---> Running in 0548f4af1d0f
Removing intermediate container 0548f4af1d0f
 ---> 75168f3a6e36
Step 3/14 : COPY readme.txt /usr/local/readme.txt
 ---> be5bab596409
Step 4/14 : ADD apache-tomcat-9.0.65.tar.gz /usr/local/
 ---> 3a52186d9792
Step 5/14 : ADD jdk-8u341-linux-aarch64.tar.gz /usr/local/
 ---> 442a346ad766
Step 6/14 : ENV MYPATH /usr/local
 ---> Running in 04874f9e7f8b
Removing intermediate container 04874f9e7f8b
 ---> 8a7245c1109b
Step 7/14 : WORKDIR $MYPATH
 ---> Running in a15142842f2d
Removing intermediate container a15142842f2d
 ---> f706a36bca35
Step 8/14 : ENV JAVA_HOME /usr/local/jdk1.8.0_341
 ---> Running in 7b3467ea903a
Removing intermediate container 7b3467ea903a
 ---> a70a47ddb13c
Step 9/14 : ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
 ---> Running in c94957f9a965
Removing intermediate container c94957f9a965
 ---> 39e62227e18c
Step 10/14 : ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.65
 ---> Running in 50f9131f62d9
Removing intermediate container 50f9131f62d9
 ---> e1d71111db27
Step 11/14 : ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.65
 ---> Running in 1915bbb4eecd
Removing intermediate container 1915bbb4eecd
 ---> 4a74cc721189
Step 12/14 : ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
 ---> Running in 68029b61d358
Removing intermediate container 68029b61d358
 ---> c8e395876410
Step 13/14 : EXPOSE 8090
 ---> Running in 219fc101fda1
Removing intermediate container 219fc101fda1
 ---> 0957b73bddc4
Step 14/14 : CMD /usr/local/apache-tomcat-9.0.65/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.65/bin/logs/catalina.out
 ---> Running in 5e38c56eca8f
Removing intermediate container 5e38c56eca8f
 ---> b4e29f89b02f
Successfully built b4e29f89b02f
Successfully tagged diytomcat:latest

(4)启动镜像

docker run -d -p 9090:8090 --name zlktomcat01 -v /home/zlk/build/tomcat/test:/usr/local/apache-tomcat-9.0.65/webapps/test -v /home/zlk/build/tomcat/tomcatlogs:/usr/local/apache-tomcat-9.0.65/logs diytomcat
# 进入容器
[root@zlk tomcat]# docker exec -it 7475bf858f8b /bin/bash
[root@7475bf858f8b local]# ls -l
total 52
drwxr-xr-x 1 root root 4096 Aug  9 08:14 apache-tomcat-9.0.65
drwxr-xr-x 2 root root 4096 Nov  3  2020 bin
drwxr-xr-x 2 root root 4096 Nov  3  2020 etc
drwxr-xr-x 2 root root 4096 Nov  3  2020 games
drwxr-xr-x 2 root root 4096 Nov  3  2020 include
drwxr-xr-x 8 root root 4096 Aug  9 08:14 jdk1.8.0_341
drwxr-xr-x 2 root root 4096 Nov  3  2020 lib
drwxr-xr-x 3 root root 4096 Sep 15  2021 lib64
drwxr-xr-x 2 root root 4096 Nov  3  2020 libexec
-rw-r--r-- 1 root root    0 Aug  9 08:05 readme.txt
drwxr-xr-x 2 root root 4096 Nov  3  2020 sbin
drwxr-xr-x 5 root root 4096 Sep 15  2021 share
drwxr-xr-x 2 root root 4096 Nov  3  2020 src
[root@7475bf858f8b local]# 

(5)访问测试

在Linux上测试:curl localhost:9090
浏览器上测试:阿里云ip地址:9090

         项目部署成功, 可以直接访问!我们以后开发的步骤:需要掌握Dockerfile的编写! 我们之后的一切都是使用docker进行来发布运行的!

(6)发布项目(由于做了卷挂载,我们直接在本地编写项目就可以发布了)

        在本地tomcat/test文件夹下创建WEB-INF文件夹,在WEB-INF下编写web.xml文,在/test 下编写index.jsp文件,再访问http://服务器ip:9090/test/

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
    xmlns="http://java.sun.com/xml/ns/j2ee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
        http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
        
</web-app>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello. xiaofan</title>
</head>
<body>
Hello World!<br/>
<%
System.out.println("-----my test web logs------");
%>
</body>
</html>

3.2 发布自己的镜像到Docker Hub

  • 地址:在Docker Hub上注册自己的账号
  • 确定这个账号可以登录
# 先登录我们自己的Docker Hub
docker login -u friggly

  • 在我们的服务器上提交镜像,提交的时候也是按照镜像的层级一层层push的!
# push到我们的服务器上
[root@zlk test]# docker push diytomcat
Using default tag: latest
The push refers to repository [docker.io/library/diytomcat]
d8fd1ae46933: Preparing 
7bb4b739625f: Preparing 
8945a6e83d53: Preparing 
74ddd0ec08fa: Preparing 
denied: requested access to the resource is denied #拒绝

# push 镜像的问题,说明已经存在
[root@zlk test]# docker push zlk/diytomcat:1.0
The push refers to repository [docker.io/zlk/diytomcat]
An image does not exist locally with the tag: zlk/diytomcat

# 解决,增加一个tag,更换名字 
docker tag [IMAGE ID] zlk/tomcat:1.0

# 查看镜像
docker images

# 提交自己的镜像,提交成功(自己发布的镜像尽量带上版本号)
docker push zlk/tomcat:1.0

3.3 发布到阿里云镜像服务上(参考官方地址即可)

  • 登录阿里云
  • 找到容器镜像服务(以我自己的服务器为例)

  • 创建命名空间

  

  • 创建容器镜像

点击仓库名称,查看详细信息:(按照下面步骤操作即可)

4 小结

感谢您的提问!以下是一些关于 Docker 进阶学习的建议: 1. 学习容器编排工具:Docker Compose 和 Kubernetes 是两个常用的容器编排工具。通过学习它们,您可以更好地管理和编排多个容器,构建复杂的应用架构。 2. 持续集成与持续部署(CI/CD):学习如何使用 Docker 构建持续集成和持续部署流程。这将有助于自动化应用程序的构建、测试和部署,提高开发和交付效率。 3. 多阶段构建(Multi-stage Builds):掌握多阶段构建技术可以帮助您优化 Docker 镜像的大小和性能。通过在构建过程中创建多个阶段,并且只保留最终运行所需的组件,可以减小镜像的体积。 4. Docker 插件和扩展:探索 Docker 的插件和扩展生态系统,了解如何使用它们来扩展 Docker 的功能。一些常见的扩展包括网络插件、存储插件和身份验证插件,它们可以提供额外的功能和灵活性。 5. 容器安全和隔离:学习如何配置和管理容器的安全性和隔离性。了解容器的安全最佳实践,并使用适当的配置和工具来加强容器的安全性,以防止潜在的攻击和数据泄漏。 6. Docker Swarm:Docker Swarm 是 Docker 官方提供的一个原生的容器编排和集群管理工具。通过学习 Docker Swarm,您可以了解如何使用它来管理分布式应用程序,并实现负载均衡和高可用性。 7. 监控和日志:学习如何监控和记录 Docker 容器的性能和日志。了解如何使用相关工具和技术来监测容器的资源利用率、运行状况和错误日志,以便及时发现和解决问题。 这些是 Docker 进阶学习的一些建议,希望对您有所帮助!如有任何其他问题,请随时提问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星悦糖

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值