Docker与gitlab-runner

Docker

概念

Docker是一种运行于 Linux 和 Windows 上的软件,用于创建、管理和编排容器。

Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。

总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

Docker是CS架构,Docker的组成:

  • Docker daemon: 运行在宿主机上,Docker守护进程,用户通过Docker client(Docker命令)与Docker daemon交互
  • Docker client: Docker 命令行工具,是用户使用Docker的主要方式,Docker client与Docker daemon通信并将结果返回给用户,Docker client也可以通过socket或者RESTful api访问远程的Docker daemon

了解了Docker的组成,再来了解一下Docker的三个主要概念:

  • Docker image:镜像是只读的,镜像中包含有需要运行的文件。镜像用来创建container,一个镜像可以运行多个container;镜像可以通过Dockerfile创建,也可以从Docker hub/registry上下载。
  • Docker container:容器是Docker的运行组件,启动一个镜像就是一个容器,容器是一个隔离环境,多个容器之间不会相互影响,保证容器中的程序运行在一个相对安全的环境中。
  • Docker hub/registry: 共享和管理Docker镜像,用户可以上传或者下载上面的镜像,官方地址为https://registry.hub.docker.com/,也可以搭建自己私有的Docker registry。注册服务器是存放仓库的地方,其上往往存放着多个仓库。每个仓库集中存放某一类镜像,往往包括多个镜像文件,通过不同的标签(tag)来进行区分。例如存放Ubuntu操作系统镜像的仓库,称为Ubuntu仓库,其中可能包括14.04、12.04等不同版本的镜像。

镜像就相当于打包好的版本,镜像启动之后运行在容器中,仓库就是装存储镜像的地方。

镜像(image)是动态的容器的静态表示(specification),包括容器所要运行的应用代码以及运行时的配置。Docker 镜像包括一个或者多个只读层( read-only layers ),因此,镜像一旦被创建就再也不能被修改了。一个运行着的Docker 容器是一个镜像的实例( instantiation )。从同一个镜像中运行的容器包含有相同的应用代码和运行时依赖。但是不像镜像是静态的,每个运行着的容器都有一个可写层( writable layer ,也成为容器层 container layer),它位于底下的若干只读层之上。运行时的所有变化,包括对数据和文件的写和更新,都会保存在这个层中。因此,从同一个镜像运行的多个容器包含了不同的容器层。

Docker 有两种方式来创建一个容器镜像:

  • 创建一个容器,运行若干命令,再使用 docker commit 来生成一个新的镜像。不建议使用这种方案。
  • 创建一个 Dockerfile 然后再使用 docker build 来创建一个镜像。大多人会使用 Dockerfile 来创建镜像。

安装

使用wget命令来运行一个 Shell 脚本,完成 Docker CE 的安装。

$ curl -fsSL https://get.docker.com/ | sh

# daocloud.io 国内镜像
$ curl -sSL https://get.daocloud.io/docker | sh

该安装包适用于 Ubuntu,Debian,Centos 等大部分主流 Linux 发行版。

权限

当以普通用户身份去使用docker images时,如果出现以下错误:

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.26/images/json: dial unix /var/run/docker.sock: connect: permission denied

是因为权限问题:

$ sudo groupadd docker
$ sudo gpasswd -a ${USER} docker
$ sudo service docker restart
$ newgrp - docker

系统操作

查看Docker信息info
$ docker info
查看版本version
$ docker version
启动Dockerstart
service docker start 

# 或者
systemctl start docker
修改配置daemon.json

配置docker镜像加速

$ vi  /etc/docker/daemon.json
#添加后
{
    "registry-mirrors": ["https://registry.docker-cn.com"],
    "live-restore": true
}
登录服务器login
$ docker login [options] [server] # 登录到Docker registry服务器
$ docker login localhost:8080 # 登录自己主机的registry

image镜像

搜索镜像search

搜索官方仓库镜像

参数说明
NAME镜像名称
DESCRIPTION镜像说明
STARS点赞数量
OFFICIAL是否是官方的
AUTOMATED是否是自动构建的
$ docker search <image_name>
拉取镜像pull

Docker 主机安装之后,本地并没有镜像。

如果本地没有某一镜像,我们可以使用 docker pull 命令来载入某一镜像

镜像从远程镜像仓库服务的仓库中下载。默认情况下,镜像会从 Docker Hub 的仓库中拉取。

$ docker image pull <image_name>
$ docker pull <image_name> # 默认获取最新的
$ docker image pull alpine:latest # 从 Docker Hub 的 alpine 仓库中拉取标签为 latest 的镜像。
$ docker pull ubuntu:16.04
$ docker pull daocloud.io/library/ubuntu:16.04
	# 下载镜像名称其实由三部分组成:daocloud.io/library/ubuntu:16.04
	# 其中其中daocloud.io是注册服务器地址,默认是registry.hub.docker.com;ubuntu是仓库名,16.04是标签名,默认是latest。
查看镜像

Docker镜像保存在/var/lib/docker目录下中

  • REPOSTITORY:表示镜像的仓库源
  • TAG:镜像的标签
  • IMAGE ID:镜像ID
  • CREATED:镜像创建时间
  • SIZE:镜像大小
$ docker image ls  # 查看本地所有镜像
$ docker images # 查看docker镜像
$ docker image inspect <image-name> # 查看详细信息
导出镜像save
$ docker save <image-name> > /opt/<image-name>.tar.gz  # 导出docker镜像至本地
导入镜像load
$ docker load < /opt/centos.tar.gz   #导入本地镜像到docker镜像库
删除镜像rmi
$ docker image rmi <image-name>
$ docker rmi  docker.io/tomcat:7.0.77-jre7
$ docker rmi b39c68b7af30
$ docker  rm `docker ps -aq`    # 一次性删除所有容器记录
$ docker rmi  `docker images -aq`   # 一次性删除所有本地的镜像记录
更新镜像update

更新镜像之前,我们需要使用镜像来创建一个容器。

$ docker run -t -i ubuntu:15.10 /bin/bash

在运行的容器内使用 apt-get update 命令进行更新。

在完成操作之后,输入 exit 命令来退出这个容器。

此时 ID 为 e218edb10161 的容器,是按我们的需求更改的容器。我们可以通过命令 docker commit 来提交容器副本。

$ docker commit -m="has update" -a="runoob" e218edb10161 runoob/ubuntu:v2
sha256:70bf1840fd7c0d2d8ef0a42a817eb29f854c1af8f7c59fc03ac7bdee9545aff8

各个参数说明:

  • -m: 提交的描述信息
  • -a: 指定镜像作者
  • **e218edb10161:**容器 ID
  • runoob/ubuntu:v2: 指定要创建的目标镜像名
构建镜像build
$ docker build
设置标签tag

docker tag 镜像ID,这里是 860c279d2fec ,用户名称、镜像源名(repository name)和新的标签名(tag)。

$ docker tag 860c279d2fec runoob/centos:dev

容器container

查看容器ps
$ docker ps # 正在运行的容器
$ docker ps -a # 所有容器
$ docker ps -l  # 查看最近一次运行的容器
	# CONTAINER ID: 容器 ID。
	# IMAGE: 使用的镜像。
	# COMMAND: 启动容器时运行的命令。
	# CREATED: 容器的创建时间。
	# STATUS: 容器状态。
	# 状态有7种:
		- created(已创建)
		- restarting(重启中)
		- running 或 Up(运行中)
		- removing(迁移中)
		- paused(暂停)
		- exited(停止)
		- dead(死亡)
	# PORTS: 容器的端口信息和使用的连接类型(tcp\udp)。
	# NAMES: 自动分配的容器名称。
查看端口port
$ docker port <container_name>/<image_name>
查看进程top
$ docker top <image_name>
查看状态inspect

收集有关容器和镜像的底层信息

$ docker inspect <container_name>/<image_name> # 查看Docker的底层信息
	# 返回一个 JSON 文件记录着 Docker 容器的配置和状态信息
$ docker inspect --format='{{.State.Status}}' <image_name>
查看日志logs

在宿主主机内使用 docker logs 命令,查看容器内的标准输出

$ docker logs <container_name>/<container_id>
$ docker logs  -f  <container_name>/<container_id>  # 不间断打印容器的日志信息 
	# -f: 让 docker logs 像使用 tail -f 一样来输出容器内部的标准输出。
创建容器create
$ docker create --name web31 training/webapp python app.py  #创建名字为 web31 的容器
运行容器run
$ docker run <image-name>
$ docker run -d -P centos /bin/sh -c "while true;do echo 正在运行; sleep 1;done" # 后台运行一个docker
    # -d  后台运行容器 默认不会进入容器,想要进入容器需要使用指令 docker exec
    # -P 将容器内部使用的网络端口随机映射到我们使用的主机上。
    # -P 是容器内部端口随机映射到主机的高端口。
    # -p 是容器内部端口绑定到指定的主机端口。
    # Docker 开放了 5000 端口(默认 Python Flask 端口)映射到主机端口 32769 上。
    # /bin/sh  指定使用centos的bash解释器
    # -c 运行一段shell命令
    # "while true;do echo 正在运行; sleep 1;done"  在linux后台,每秒中打印一次正在运行
$ docker run -d -p 5000:5000 training/webapp python app.py
	# 容器内部的 5000 端口映射到我们本地主机的 5000 端口上
$ docker run -d -p 127.0.0.1:5001:5000 training/webapp python app.py
	# 指定容器绑定的网络地址,比如绑定 127.0.0.1
$ docker run --name mydocker -it centos /bin/bash # 启动一个bash终端,允许用户进行交互  
    # --name  给容器定义一个名称 可以无
    # -i 让容器的标准输入保持打开
    # -t 让Docker分配一个伪终端,并绑定到容器的标准输入上
    # /bin/bash 指定docker容器,用shell解释器交互
当利用docker run来创建容器时,Docker在后台运行的步骤如下:
	# 1. 检查本地是否存在指定的镜像,不存在就从公有仓库下载
	# 2. 利用镜像创建并启动一个容器
	# 3. 分配一个文件系统,并在只读的镜像层外面挂在一层可读写层
	# 4. 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
	# 5. 从地址池配置一个ip地址给容器
	# 6. 执行用户指定的应用程序
	# 7. 执行完毕后容器被终止
启动容器start
$ docker start <container_name>/<container_id>
交互界面exec
$ docker exec -it  <container_name>/<container_id> /bin/bash  # 进入容器交互式界面
暂停容器pause
$ docker pause <container_name>/<container_id>
继续容器unpause
$ docker unpause <container_name>/<container_id>
重命名rename
$ docker rename <old_name> <new_mame>
变化记录diff
$ docker diff container
复制文件cp
$ docker cp container:path hostpath # 从容器内复制文件到指定的路径上
停止容器stop
$ docker stop <container_name>/<container_id>
$ docker stop  `docker ps -aq`  # 停止所有正在运行的容器
$ docker container  kill  <container_name>/<container_id>
重启容器restart

正在运行的容器,我们可以使用 docker restart 命令来重启。

$ docker restart <container_name>/<container_id>
进入后台容器attach

在使用 -d 参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入

$ docker attach <container_name>/<container_id> # 如果从这个容器退出,会导致容器的停止。
$ docker exec -it 243c32535da7 /bin/bash        # 如果从这个容器退出,容器不会停止,推荐使用。
导出容器export

导出容器 1e560fca3906 快照到本地文件 ubuntu.tar。

$ docker export 1e560fca3906 > ubuntu.tar
导入容器import

可以使用 docker import 从容器快照文件中再导入为镜像,以下实例将快照文件 ubuntu.tar 导入到镜像 test/ubuntu:v1:

$ cat docker/ubuntu.tar | docker import - test/ubuntu:v1
$ docker import http://example.com/exampleimage.tgz example/imagerepo # 通过指定 URL 或者某个目录来导入
退出容器exit
$ exit # ctrl+D也可以退出
删除容器rm

删除容器时,容器必须是停止状态,否则会报错误。

$ docker rm <container_name>/<container_id>
清理容器prune

以清理掉所有处于终止状态的容器。

$ docker container prune
新建网络network

下面先创建一个新的 Docker 网络。

$ docker network create -d bridge test-net

参数说明:

-d:参数指定 Docker 网络类型,有 bridge、overlay。

其中 overlay 网络类型用于 Swarm mode,可以忽略。

连接容器

运行一个容器并连接到新建的 test-net 网络:

$ docker run -itd --name test1 --network test-net ubuntu /bin/bash

打开新的终端,再运行一个容器并加入到 test-net 网络:

$ docker run -itd --name test2 --network test-net ubuntu /bin/bash

下面通过 ping 来证明 test1 容器和 test2 容器建立了互联关系。

如果 test1、test2 容器内中无 ping 命令,则在容器内执行以下命令安装 ping(即学即用:可以在一个容器里安装好,提交容器到镜像,在以新的镜像重新运行以上俩个容器)。

apt-get update
apt install iputils-ping

在 test1 容器输入以下命令:

$ ping test2
实例
root@devstack:/home/sammy# docker create --name web31 training/webapp python app.py  #创建名字为 web31 的容器
7465f4cb7c49555af32929bd1bc4213f5e72643c0116450e495b71c7ec128502
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' web31 #其状态为 created
created
root@devstack:/home/sammy# docker start web31 #启动容器
web31root@devstack:/home/sammy# docker exec -it web31 /bin/bash #在容器中运行 bash 命令
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' web31 #其状态为 running
running
root@devstack:/home/sammy# docker pause web31 #暂停容器
web31
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' web31
paused
root@devstack:/home/sammy# docker unpause web31 #继续容器
web31
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' web31
running
root@devstack:/home/sammy# docker rename web31 newweb31 #重命名
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' newweb31
running
root@devstack:/home/sammy# docker top newweb31 #在容器中运行 top 命令
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                5009                4979                0                   16:28               ?                   00:00:00            python app.py
root@devstack:/home/sammy# docker logs newweb31 #获取容器的日志
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
root@devstack:/home/sammy# docker stop newweb31 #停止容器
newweb31
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' newweb31
exited
root@devstack:/home/sammy# docker rm newweb31 #删除容器
newweb31
root@devstack:/home/sammy# docker inspect --format='{{.State.Status}}' newweb31
Error: No such image, container or task: newweb31

GitLab Runner

GitLab Runner包含一组命令,可以使用这些命令注册,管理和运行构建。

安装register
  • 在目标主机上安装 GitLab Runner,目标主机是指要部署的服务器
  • Ubuntu 安装脚本:
# 外网 因为访问了外网的网址去安装
$ curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
$ sudo apt-get update
$ sudo apt-get install gitlab-ci-multi-runner

注册相关命令

注册 register
  • gitlab-ci-multi-runner register:执行注册命令
  • Please enter the gitlab-ci coordinator URL:输入 ci 地址
  • Please enter the gitlab-ci token for this runner:输入 ci token
  • Please enter the gitlab-ci description for this runner:输入 runner 名称
  • Please enter the gitlab-ci tags for this runner:设置 tag
  • Whether to run untagged builds:这里选择 true ,代码上传后会能够直接执行
  • Whether to lock Runner to current project:直接回车,不用输入任何口令
  • Please enter the executor:选择 runner 类型,这里我们选择的是 shell

其中ci的URL和token在gitlab工程中的设置->CI/CD->Runner->手动设置specific Runner中

$ gitlab-ci-multi-runner register
Running in system-mode.                            
                                                   
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://yfgitlab.dahuatech.com/
Please enter the gitlab-ci token for this runner:
fsYy75DXECZx8hdUxzxM           
Please enter the gitlab-ci description for this runner:
[dahua]: autotest
Please enter the gitlab-ci tags for this runner (comma separated):
test
Whether to run untagged builds [true/false]:
[false]: true
Whether to lock the Runner to current project [true/false]:
[true]: false

Registering runner... succeeded                     runner=fsYy75DX
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

这里的token是ci注册的令牌,注册成功之后每一个runner会有各自的令牌。虽然上面runner=fsYy75DX,此处的令牌还是注册令牌,但是listverify还有配置文件中的token都是各自的token

列表list

此命令列出配置文件中保存的所有runner。

$ gitlab-runner list
Listing configured runners    ConfigFile=/etc/gitlab-runner/config.toml

GitLab Runner配置使用TOML格式。

要编辑的文件可以在以下位置找到:

  • /etc/gitlab-runner/config.toml当gitlab-runner作为超级用户(root)执行时,在* nix系统上
  • ~/.gitlab-runner/config.toml 当gitlab-runner以非root身份执行时,在* nix系统上
  • ./config.toml 在其他系统上
$ cat /etc/gitlab-runner/config.toml 
concurrent = 1
check_interval = 0

[[runners]]
  name = "test1"
  url = "https://yfgitlab.dahuatech.com/"
  token = "1HqQCT3CkSM_4HGxqaJ3"
  tls-ca-file = "/home/dahua/桌面/server.cer"
  executor = "shell"
  [runners.cache]

当有一个不小心在gitlab上删除了,但是仍会显示在gitlab-runner的列表里,如果想删除,可以直接进入配置文件把相关的配置删除。

大多数命令接受参数来指定自定义配置文件,允许您在一台计算机上具有多个不同的配置。要指定自定义配置文件,请使用-c or --config标志,或使用CONFIG_FILE环境变量。

验证verify

此命令检查已注册的runner是否可以连接到GitLab,但它不验证GitLab Runner服务是否正在使用runner。示例输出是:

Verifying runner... is alive                       runner=1HqQCT3C

注意此处令牌token是各自的runner独有的令牌

要删除旧的并从GitLab Runner删除,请执行以下命令。

警告: 此操作无法撤消,它将更新配置文件,因此请确保config.toml在执行之前备份。

gitlab-runner verify --delete
注销unregister

此命令允许注销其中一个注册的runner。它需要一个完整的URL和Runner的令牌,或者Runner的名字。使用该 --all-runners选项,它将取消注册所有附加的Runner。

要取消注册特定的Runner,首先通过执行gitlab-runner list以下步骤获取Runner的详细信息 :

$ gitlab-runner list
Listing configured runners    ConfigFile=/etc/gitlab-runner/config.toml
test1                    Executor=shell Token=BtxBtfxGXfnwj93769Ef URL=https://yfgitlab.dahuatech.com/
test1                    Executor=shell Token=1HqQCT3CkSM_4HGxqaJ3 URL=https://yfgitlab.dahuatech.com/
test2                    Executor=shell Token=2PDsoTpkqQxMSRxtnw6j URL=https://yfgitlab.dahuatech.com/

然后使用以下命令之一使用此信息取消注册。

警告: 此操作无法撤消,它将更新配置文件,因此请确保config.toml在执行之前备份。

$ gitlab-runner unregister --url https://yfgitlab.dahuatech.com/ --token BtxB # 通过URL和令牌
$ gitlab-runner unregister --name test-runner # 按名字:如果有多个具有给定名称的runner,则只会删除第一个
$ gitlab-runner unregister --all-runners      # all

服务相关命令

安装install
卸载uninstall
开始start
停止stop
重启restart
状态status

运行相关命令

此命令允许从GitLab获取和处理构建

运行run
run-single

这是一个补充命令,可用于从单个GitLab实例仅运行单个构建。它不使用任何配置文件,并且需要将所有选项作为参数或环境变量传递。还需要指定GitLab URL和Runner令牌。

exec

此命令允许本地运行构建,尝试尽可能多地复制CI环境。它不需要连接到GitLab,而是读取本地.gitlab-ci.yml并创建一个新的构建环境,其中执行所有构建步骤。

问题

注册失败

可能是没有证书

如果遇到下面注册失败的问题:

ERROR: Registering runner... failed                 runner=fsYy75DX status=couldn't execute POST against https://gitlab.example.com/api/v4/runners: Post https://gitlab.example.com/api/v4/runners: x509: certificate signed by unknown authority
PANIC: Failed to register this runner. Perhaps you are having network problems

其实是证书的原因,首先打开gitlab的地址,地址栏的左边有个小锁,点击之后的下拉菜单里有证书选项,点击之后选择详细信息然后复制到文件,下一步之后出现选格式的,选择Bash64编码。

导出之后放到Linux环境中,供gitlab runner配置调用(这里放在了/home/dahua/桌面/server.cer,server.cer就是导出的证书)

下面是证书的配置(全部复制前8行,直接粘贴到Bash界面):

$ gitlab-runner register -n \
--tls-ca-file=/home/dahua/桌面/server.cer \
--url=https://yfgitlab.dahuatech.com/ \
--registration-token=fsYy75DXECZx8hdUxzxM \
--name=test1 \
--run-untagged=true \
--locked=true \
--executor=shell
Running in system-mode.                            
                                                   
Registering runner... succeeded                     runner=fsYy75DX
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
灰色感叹号

在服务器注册完 runner 后,在 gitlab 的 runner 处出现灰色感叹号,并提示 new runner has not connected yet。这个问题一般都是权限问题造成的,加 sudo 和不加 sudo 所创建的配置文件不同。在使用 gitlab-runner register 注册的时候,加上 sudo,在启动 runner 时也要加上 sudo , sudo gitlab-runner restartsudo gitlab-runner start

无法删除 runner

在使用 gitlab-runner unregister 删除 runner 时,提示 Error: unregistering runner from gitlab forbidden

使用 gitlab-runner verify 检测 runner 是否已连接至 gitlab ,然后gitlab-runner verify --delete在注册列表中删除它们。有可能是在gitlab上手动删掉了

list 不显示注册的

使用 sudo 权限注册的 runner 要使用sudo gitlab-runner list查看。

构建时提示

mkdir: cannot create directop ‘/home/gitlab-runner/builds/xxxxx’: Permission denied

用户 gitlab-runner 没有构建目录的权限,加上权限即可,或切换至 root 用户,给该文件夹上权限。

环境变量

如在构建的时候,提示bash: npm: command not found,此时可以去服务器上给bash加上环境变量,或者在before_script阶段加上环境变量,例如:

before_script:
  - export PATH=/home/souche/n/bin:$PATH
yaml文件

下一个 Stages 无法使用上一个 Stages 生成的文件

这是因为在进行一个新的 Stages 时,会把上一个 Stages 所产生的文件删除掉,解决方案就是使用缓存 cache,如:

cache:
  key: '$CI_COMMIT_REF_NAME'
  paths:
    - .nuxt
磁盘满了

构建时在 Getting source from Git repository 阶段出现错误提示 error: failed to write new configuration file /home/gitlab-runner/builds/xxxx/0/xxxx/xxxx.tmp/git-template/config.lock

出现这个提示,请检查服务器磁盘是不是已经满了,请释放些磁盘空间

git 版本低

构建过程出现 fatal: git fetch-pack: expected shallow list fatal: The remote end hung up unexpectedly

这是因为服务器 git 版本太低,不支持新的 api,去服务器升级一下 git 版本

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南 城

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值