k8s中级班day3

第11章:Pod数据持久化

分类:
本地卷:hostPath,emptyDir
网络卷:nfs,ceph(cephfs,rbd),glusterfs
公有云:aws,azure
k8s资源:downwardAPI,configMap,secret

downwardAPI

    status.podIP - the pod’s IP address
    spec.serviceAccountName - the pod’s service account name, available since v1.4.0-alpha.3
    spec.nodeName - the node’s name, available since v1.4.0-alpha.3
    status.hostIP - the node’s IP, available since v1.7.0-alpha.1

参考文档:https://kubernetes.io/docs/concepts/storage/volumes/

  • Kubernetes中的Volume提供了在容器中挂载外部存储的能力

  • Pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后才可以使用相应的Volume

3.1 emptyDir

创建一个空卷,挂载到Pod中的容器。Pod删除该卷也会被删除。

应用场景:Pod中容器之间数据共享

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: write
    image: centos
    command: ["bash","-c","for i in {1..100};do echo $i >> /data/hello;sleep 1;done"]
    volumeMounts:
      - name: data
        mountPath: /data

  - name: read
    image: centos
    command: ["bash","-c","tail -f /data/hello"]
    volumeMounts:
      - name: data
        mountPath: /data
  
  volumes:
  - name: data
    emptyDir: {}
docker ps 查看容器名,找到对应POD name 后缀
/var/lib/kubelet/pods/<POD ID>/volumes/kubernetes.io~empty-dir/data

3.2 hostPath

emptyDir == 类似于容器的volume       
hostPath == 类似于容器的bindmount    日志采集agent,把容器的日志都挂到宿主机一个目录,然后宿主机跑一个日志采集的agent读这个目录;监控agent,需要获取宿主机/proc里的一些信息

挂载Node文件系统上文件或者目录到Pod中的容器。

应用场景:Pod中容器需要访问宿主机文件

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: busybox
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 36000
    volumeMounts:
    - name: data
      mountPath: /data
  volumes:
  - name: data
    hostPath:
      path: /tmp
      type: Directory

验证:进入Pod中的/data目录内容与当前运行Pod的节点内容一样。

3.3 网络存储

no_root_squash:登入 NFS 主机使用分享目录的使用者,如果是 root 的话,那么对于这个分享的目录来说,他就具有 root 的权限!这个项目『极不安全』,不建议使用! 
root_squash:在登入 NFS 主机使用分享之目录的使用者如果是 root 时,那么这个使用者的权限将被压缩成为匿名使用者,通常他的 UID 与 GID 都会变成 nobody 那个系统账号的身份;

yum install nfs-utils -y
vi /etc/exports
i/ifs/kubernetes *(rw,no_root_squash)
mkdir -p /ifs/kubernetes
systemctl start nfs
mount -t nfs 10.0.0.200:/ifs/kubernetes /mnt/
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: web
  name: web
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: web
    spec:
      containers:
      - image: nginx
        name: nginx
        resources: {}
        volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
      volumes:
      - name: data
        nfs:
          server: 10.0.0.200
          path: /ifs/kubernetes

3.4 PV&PVC

**PersistentVolume(PV):**对存储资源创建和使用的抽象,使得存储作为集群中的资源管理

PV供给分为:

  • 静态

  • 动态

**PersistentVolumeClaim(PVC):**让用户不需要关心具体的Volume实现细节

3.5 PV静态供给

静态供给是指提前创建好很多个PV,以供使用。

先准备一台NFS服务器作为测试。

# yum install nfs-utils
# vi /etc/exports
/ifs/kubernetes *(rw,no_root_squash)
# mkdir -p /ifs/kubernetes
# systemctl start nfs
# systemctl enable nfs

并且要在每个Node上安装nfs-utils包,用于mount挂载时用。

示例:先准备三个PV,分别是5G,10G,20G,修改下面对应值分别创建。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001       # 修改PV名称
spec:
  capacity:
    storage: 30Gi   # 修改大小
  accessModes:
    - ReadWriteMany
  nfs:
    path: /ifs/kubernetes/pv001   # 修改目录名
    server: 192.168.31.62

mkdir -p /ifs/kubernetes/pv001

创建一个Pod使用PV:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: nginx
      image: nginx:latest
      ports:
      - containerPort: 80
      volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumes:
    - name: www
      persistentVolumeClaim:
        claimName: my-pvc

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

创建并查看PV与PVC状态:

# kubectl apply -f pod-pv.yaml
# kubectl get pv,pvc

会发现该PVC会与5G PV进行绑定成功。

然后进入到容器中/usr/share/nginx/html(PV挂载目录)目录下创建一个文件测试:

kubectl exec -it my-pod bash
cd /usr/share/nginx/html
echo "123" index.html

再切换到NFS服务器,会发现也有刚在容器创建的文件,说明工作正常。

cd /opt/nfs/pv001
ls
index.html

如果创建一个PVC为16G,你猜会匹配到哪个PV呢?

https://kubernetes.io/docs/concepts/storage/persistent-volumes/

3.6 PV动态供给

Dynamic Provisioning机制工作的核心在于StorageClass的API对象。

StorageClass声明存储插件,用于自动创建PV。

Kubernetes支持动态供给的存储插件:

https://kubernetes.io/docs/concepts/storage/storage-classes/

3.5 PV动态供给实践(NFS)

工作流程

由于K8S不支持NFS动态供给,还需要先安装上图中的nfs-client-provisioner插件:
https://github.com/kubernetes-incubator/external-storage/tree/master/nfs-client/deploy

# cd nfs-client
# vi deployment.yaml # 修改里面NFS地址和共享目录为你的
# kubectl apply -f .
# kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-df88f57df-bv8h7   1/1     Running   0          49m

测试:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: nginx
      image: nginx:latest
      ports:
      - containerPort: 80
      volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumes:
    - name: www
      persistentVolumeClaim:
        claimName: my-pvc

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: "managed-nfs-storage"
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi

这次会自动创建5GPV并与PVC绑定。

kubectl get pv,pvc

测试方法同上,进入到容器中/usr/share/nginx/html(PV挂载目录)目录下创建一个文件测试。

再切换到NFS服务器,会发现下面目录,该目录是自动创建的PV挂载点。进入到目录会发现刚在容器创建的文件。

# ls /opt/nfs/
default-my-pvc-pvc-51cce4ed-f62d-437d-8c72-160027cba5ba

第12章:再谈有状态应用部署

1.StatefulSet控制器概述

StatefulSet:

  • 部署有状态应用

  • 解决Pod独立生命周期,保持Pod启动顺序和唯一性

  1. 稳定,唯一的网络标识符,持久存储

  2. 有序,优雅的部署和扩展、删除和终止

  3. 有序,滚动更新

应用场景:数据库

2.稳定的网络ID

说起StatefulSet稳定的网络标识符,不得不从Headless说起了。

标准Service:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: nginx 
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

无头Service(Headless Service):

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  clusterIP: None
  selector:
    app: nginx 
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

标准Service与无头Service区别是clusterIP: None,这表示创建Service不要为我(Headless Service)分配Cluster IP,因为我不需要。

为什么标准Service需要?

这就是无状态和有状态的控制器设计理念了,无状态的应用Pod是完全对等的,提供相同的服务,可以在飘移在任意节点,例如Web。而像一些分布式应用程序,例如zookeeper集群、etcd集群、mysql主从,每个实例都会维护着一种状态,每个实例都各自的数据,并且每个实例之间必须有固定的访问地址(组建集群),这就是有状态应用。所以有状态应用是不能像无状态应用那样,创建一个标准Service,然后访问ClusterIP负载均衡到一组Pod上。这也是为什么无头Service不需要ClusterIP的原因,它要的是能为每个Pod固定一个”身份“。

举例说明:

apiVersion: v1
kind: Service
metadata:
  name: headless-svc
spec:
  clusterIP: None
  selector:
    app: nginx 
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx 
  serviceName: "headless-svc"
  replicas: 3 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx 
        ports:
        - containerPort: 80
          name: web

相比之前讲的yaml,这次多了一个serviceName: “nginx”字段,这就告诉StatefulSet控制器要使用nginx这个headless service来保证Pod的身份。
在这里插入图片描述

# kubectl get pods
NAME                                     READY   STATUS    RESTARTS   AGE
my-pod                                   1/1     Running   0          7h50m
nfs-client-provisioner-df88f57df-bv8h7   1/1     Running   0          7h54m
web-0                                    1/1     Running   0          6h55m
web-1                                    1/1     Running   0          6h55m
web-2                                    1/1     Running   0          6h55m
NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/headless-svc   ClusterIP   None           <none>        80/TCP         7h15m
service/kubernetes     ClusterIP   10.1.0.1       <none>        443/TCP        8d

临时创建一个Pod,测试DNS解析:

# kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx.default.svc.cluster.local
Server:    10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
 
Name:      nginx.default.svc.cluster.local
Address 1: 172.17.26.3 web-1.nginx.default.svc.cluster.local
Address 2: 172.17.26.4 web-2.nginx.default.svc.cluster.local
Address 3: 172.17.83.3 web-0.nginx.default.svc.cluster.local

结果得出该Headless Service代理的所有Pod的IP地址和Pod 的DNS A记录。

通过访问web-0.nginx的Pod的DNS名称时,可以解析到对应Pod的IP地址,其他Pod 的DNS名称也是如此,这个DNS名称就是固定身份,在生命周期不会再变化:

/ # nslookup web-0.nginx.default.svc.cluster.local
Server:    10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx.default.svc.cluster.local
Address 1: 172.17.83.3 web-0.nginx.default.svc.cluster.local 

进入容器查看它们的主机名:

[root@k8s-master01 ~]# kubectl exec web-0 hostname
web-0
[root@k8s-master01 ~]# kubectl exec web-1 hostname
web-1
[root@k8s-master01 ~]# kubectl exec web-2 hostname
web-2

可以看到,每个Pod都从StatefulSet的名称和Pod的序号中获取主机名的。

不过,相信你也已经注意到了,尽管 web-0.nginx 这条记录本身不会变,但它解析到的 Pod 的 IP 地址,并不是固定的。这就意味着,对于“有状态应用”实例的访问,你必须使用 DNS 记录或者 hostname 的方式,而绝不应该直接访问这些 Pod 的 IP 地址。

以下是Cluster Domain,Service name,StatefulSet名称以及它们如何影响StatefulSet的Pod的DNS名称的一些选择示例。

Cluster DomainService (ns/name)StatefulSet (ns/name)StatefulSet DomainPod DNSPod Hostname
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0…N-1}.nginx.default.svc.cluster.localweb-{0…N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0…N-1}.nginx.foo.svc.cluster.localweb-{0…N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0…N-1}.nginx.foo.svc.kube.localweb-{0…N-1}

3.稳定的存储

StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用VolumeClaimTemplate 创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。

示例:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx 
  serviceName: "headless-svc"
  replicas: 3 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx 
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "managed-nfs-storage"
      resources:
        requests:
          storage: 1Gi
# kubectl get pv,pvc
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS          REASON   AGE
persistentvolume/pv001                                      5Gi        RWX            Retain           Released    default/my-pvc                                     8h
persistentvolume/pv002                                      10Gi       RWX            Retain           Available                                                      8h
persistentvolume/pv003                                      30Gi       RWX            Retain           Available                                                      8h
persistentvolume/pvc-2c5070ff-bcd1-4703-a8dd-ac9b601bf59d   1Gi        RWO            Delete           Bound       default/www-web-0   managed-nfs-storage            6h58m
persistentvolume/pvc-46fd1715-181a-4041-9e93-fa73d99a1b48   1Gi        RWO            Delete           Bound       default/www-web-2   managed-nfs-storage            6h58m
persistentvolume/pvc-c82ae40f-07c5-45d7-a62b-b129a6a011ae   1Gi        RWO            Delete           Bound       default/www-web-1   managed-nfs-storage            6h58m

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/www-web-0   Bound    pvc-2c5070ff-bcd1-4703-a8dd-ac9b601bf59d   1Gi        RWO            managed-nfs-storage   6h58m
persistentvolumeclaim/www-web-1   Bound    pvc-c82ae40f-07c5-45d7-a62b-b129a6a011ae   1Gi        RWO            managed-nfs-storage   6h58m
persistentvolumeclaim/www-web-2   Bound    pvc-46fd1715-181a-4041-9e93-fa73d99a1b48   1Gi        RWO            managed-nfs-storage   6h58m

结果得知,StatefulSet为每个Pod分配专属的PVC及编号。每个PVC绑定对应的 PV,从而保证每一个 Pod 都拥有一个独立的 Volume。

在这种情况下,删除Pods或StatefulSet时,它所对应的PVC和PV不会被删除。所以,当这个Pod被重新创建出现之后,Kubernetes会为它找到同样编号的PVC,挂载这个PVC对应的Volume,从而获取到以前保存在 Volume 里的数据。

小结

StatefulSet与Deployment区别:有身份的!

身份三要素:

  • 域名

  • 主机名

  • 存储(PVC)

这里为你准备了一个etcd集群,来感受下有状态部署: https://github.com/lizhenliang/k8s-statefulset/tree/master/etcd

第13章:Kubernetes 鉴权框架与用户权限分配

1.Kubernetes的安全框架

  • 访问K8S集群的资源需要过三关:认证、鉴权、准入控制

  • 普通用户若要安全访问集群API Server,往往需要证书、Token或者用户名+密码;Pod访问,需要ServiceAccount

  • K8S安全控制框架主要由下面3个阶段进行控制,每一个阶段都支持插件方式,通过API Server配置来启用插件。

访问API资源要经过以下三关才可以:

  1. Authentication(鉴权)

  2. Authorization(授权)

  3. Admission Control(准入控制)

2.传输安全,认证,授权,准入控制

传输安全:

  • 告别8080,迎接6443

  • 全面基于HTTPS通信

鉴权:三种客户端身份认证:

  • HTTPS 证书认证:基于CA证书签名的数字证书认证

  • HTTP Token认证:通过一个Token来识别用户

  • HTTP Base认证:用户名+密码的方式认证

授权:

RBAC(Role-Based Access Control,基于角色的访问控制):负责完成授权(Authorization)工作。

根据API请求属性,决定允许还是拒绝。

准入控制:

Adminssion Control实际上是一个准入控制器插件列表,发送到API Server的请求都需要经过这个列表中的每个准入控制器插件的检查,检查不通过,则拒绝请求。

3.使用RBAC授权

RBAC(Role-Based Access Control,基于角色的访问控制),允许通过Kubernetes API动态配置策略。

角色

  • Role:授权特定命名空间的访问权限

  • ClusterRole:授权所有命名空间的访问权限

角色绑定

  • RoleBinding:将角色绑定到主体(即subject)

  • ClusterRoleBinding:将集群角色绑定到主体

主体(subject)

  • User:用户

  • Group:用户组

  • ServiceAccount:服务账号

示例:为aliang用户授权default命名空间Pod读取权限

1、用K8S CA签发客户端证书

cat > ca-config.json <<EOF
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
        "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ],
        "expiry": "87600h"
      }
    }
  }
}
EOF

cat > aliang-csr.json <<EOF
{
  "CN": "aliang",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "ST": "BeiJing",
      "L": "BeiJing",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
EOF

cfssl gencert -ca=/etc/kubernetes/pki/ca.crt -ca-key=/etc/kubernetes/pki/ca.key -config=ca-config.json -profile=kubernetes aliang-csr.json | cfssljson -bare aliang

2、生成kubeconfig授权文件

生成kubeconfig授权文件:

kubectl config set-cluster kubernetes \
  --certificate-authority=/etc/kubernetes/pki/ca.crt \
  --embed-certs=true \
  --server=https://192.168.31.61:6443 \
  --kubeconfig=aliang.kubeconfig

# 设置客户端认证
kubectl config set-credentials aliang \
  --client-key=aliang-key.pem \
  --client-certificate=aliang.pem \
  --embed-certs=true \
  --kubeconfig=aliang.kubeconfig

# 设置默认上下文
kubectl config set-context kubernetes \
  --cluster=kubernetes \
  --user=aliang \
  --kubeconfig=aliang.kubeconfig

# 设置当前使用配置
kubectl config use-context kubernetes --kubeconfig=aliang.kubeconfig

3、创建RBAC权限策略

创建角色(权限集合):

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

将aliang用户绑定到角色:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: default
subjects:
- kind: User
  name: aliang
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

测试:

# kubectl --kubeconfig=aliang.kubeconfig get pods
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-df88f57df-bv8h7   1/1     Running   0          8h
web-0                                    1/1     Running   0          7h25m
web-1                                    1/1     Running   0          7h25m
web-2                                    1/1     Running   0          7h25m
# kubectl --kubeconfig=aliang.kubeconfig get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "aliang" cannot list resource "pods" in API group "" in the namespace "kube-system"

aliang用户只有访问default命名空间Pod读取权限。

## 授权dashboard houbinglei 用户使用kubeconfig,只读权限。

## 创建sa
kubectl create serviceaccount houbinglei -n kube-system

##创建role
vi role.yaml
ikind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: kube-system
  name: houbinglei
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["extensions", "apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]


## rolebinding
vi bind.yaml
ikind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: role-bind-dashboard-admin2
  namespace: kube-system
subjects:
- kind: ServiceAccount
  name: houbinglei
  namespace: kube-system
roleRef:
  kind: Role
  name: houbinglei
  apiGroup: rbac.authorization.k8s.io


# 产生kubeconfig
DASH_TOCKEN=$(kubectl get secret -n kube-system houbinglei-token-pdfjb -o jsonpath={.data.token}|base64 -d)


kubectl config set-cluster kubernetes --server=10.0.0.200:6443 --kubeconfig=/root/houbinglei.conf

kubectl config set-credentials houbinglei --token=$DASH_TOCKEN --kubeconfig=/root/houbinglei.conf

kubectl config set-context houbinglei@kubernetes --cluster=kubernetes --user=houbinglei --kubeconfig=/root/houbinglei.conf

kubectl config use-context houbinglei@kubernetes --kubeconfig=/root/houbinglei.conf
1、安装编译环境
yum install java-1.8.0-openjdk maven -y
2、编译构建
mvn clean package -DskipTests=true
3、替换maven国内源
/etc/maven/settings.xml 
    <mirror>
      <id>central</id>
      <mirrorOf>central</mirrorOf>
      <name>aliyun maven</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
4、使用Dockerfile构建镜像并推送到镜像仓库
docker build -t 192.168.31.70/dev/java-demo:v10 .
docker login 192.168.31.70
docker push 192.168.31.70/dev/java-demo:v10

5、创建secret保存harbor认证信息
kubectl create secret docker-registry dockerpullauth --docker-username=admin --docker-password=Harbor12345 --docker-server=192.168.31.70

6、编写yaml部署(deployment、service、ingress)

7、创建数据库
docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
mysql -uroot -p$MYSQL_ROOT_PASSWORD
mysql> grant all on test.* to wp@'%' identified by '123456';
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值