centos7 openstack与kubernetes(v1.11.0)的集成

Linux 虚拟机 专栏收录该内容
20 篇文章 0 订阅

目录

一、安装openstack的测试环境

二、安装部署kubernetes

Kubernetes 介绍

准备工作

正式安装

 检查kubelet使用的cgroup driver

设置master节点参与工作负载

网络部署

节点配置

kubernetes-dashboard安装

三、配置cloud provider


该文档用于介绍部署openstack和kubernetes通过cloud provider来集成的说明。

一、安装openstack的测试环境

DevStack 或者Vagrant的方式 Getting Started With OpenStack

可以指定Devstack 国内的镜像源:

vi  /etc/yum.repos.d/local.repo 
[openstack]
name=openstack
baseurl=https://mirrors.aliyun.com/centos/7.6.1810/cloud/x86_64/openstack-queens/
enabled=1
gpgcheck=0
[centos7]
name=centos7
baseurl=https://mirrors.aliyun.com/centos/7.6.1810/os/x86_64/
enabled=1
gpgcheck=0

报错指引:

  1. 如果遇到env: ‘/opt/stack/requirements/.venv/bin/pip’: No such file or directory

    则在stack用户下:

    [stack@kuber-node1 devstack]$ virtualenv ../requirements/.venv/
    
  2. 如果遇到文件下载不下来,比如https://github.com/coreos/etcd/releases/download/v3.1.10/etcd-v3.1.10-linux-amd64.tar.gz,可以在自行通过wget等下载至/opt/stack/devstack/files文件夹下后,重新安装(一般直接再装就可以了)

  3. 如果遇到pip源的版本找不到,请自行根据报错提示修改/opt/stack/requirements/upper-constraints.txt的相应内容

  4. 如果出现SyntaxError: ‘<’ operator not allowed in environment markers,需要安装sudo pip install –upgrade setuptools

  5. 如果是pip源里的pbr版本不是最新版造成问题,要自行安装

  git clone http://git.trystack.cn/openstack-dev/pbr
  cd pbr
  sudo pip install
  1. 如果仍然报错,请先执行如下脚本
  ~/devstack/unstack.sh

卸载后,必须在local.conf中增加enable_service placement-api

二、安装部署kubernetes

Kubernetes 介绍

Kubernetes 是 Google 团队发起的开源项目,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及运用伸缩,主要实现语言为
Go 语言。
Kubernetes的特点:

  • 易学:轻量级,简单,容易理解
  • 便携:支持公有云,私有云,混合云,以及多种云平台
  • 可拓展:模块化,可插拔,支持钩子,可任意组合
  • 自修复:自动重调度,自动重启,自动复制

好吧,这是从别人ppt上拷下来的=。=
下面就正式开始部署我们自己的k8s吧

准备工作

注:以下操作都是在root权限下执行的

一、 安装docker-ce,这里使用docker-ce-17.09.0.c版本。

1. 设置防火墙策略

解决方案有2个:

  • 在所有节点上开机启动时执行iptables -P FORWARD ACCEPT
  • 让docker不操作iptables

方案一

临时生效:

$ sudo iptables -P FORWARD ACCEPT

iptables的配置重启后会丢失,可以将配置写进/etc/rc.local中,重启后自动执行:

$ sudo /usr/sbin/iptables -P FORWARD ACCEPT

方案二

设置docker启动参数添加 --iptables=false 选项,使docker不再操作iptables,比如1.10版以上可编辑docker daemon默认配置文件 /etc/docker/daemon.json:

{
    "iptables": false
}
  1. 建议方案二
  2. kubernetes官网建议和k8s结合的时候docker的启动参数设置--iptables=false使得docker不再操作iptables,完全由kube-proxy来操作iptables。

方案三

若是firewalld 服务启动类型的,直接关闭防火墙:

systemctl disable firewalld.service && systemctl stop firewalld.service

2. 卸载旧的docker版本:

$ sudo yum -y remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine \
                  docker-ce \
                  docker-ce-selinux  
                                 
 $ sudo rm -rf /var/lib/docker /etc/systemd/system/docker.service.d

3. 安装依赖

$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2

备注:

device-mapper-persistent-data和lvm2是Docker CE的devicemapper存储驱动程序需要的依赖包

4.设置docker镜像源

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


国内访问download.docker.com不稳定的话可以使用阿里云镜像源

 $ sudo yum-config-manager --add-repo \
        https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

5.安装指定版本docker-ce
查看源中都有哪些版本:

$ yum list docker-ce --showduplicates
 可安装的软件包
docker-ce.x86_64                  17.03.0.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.03.1.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.03.2.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.03.3.ce-1.el7                          docker-ce-stable
docker-ce.x86_64                  17.06.0.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.06.1.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.06.2.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.09.0.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.09.1.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.12.0.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  17.12.1.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  18.03.0.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  18.03.1.ce-1.el7.centos                   docker-ce-stable
docker-ce.x86_64                  18.06.0.ce-3.el7                          docker-ce-stable
docker-ce.x86_64                  18.06.1.ce-3.el7                          docker-ce-stable

安装17.09.0.ce-1.el7.centos

$ sudo yum install -y --setopt=obsoletes=0 docker-ce-17.09.0.ce-1.el7.centos

备注:kubeadm v1.11.0支持docker max version17.03,所以最好安装17.03,那么则需要这样安装docker:

yum install -y --setopt=obsoletes=0 \
  docker-ce-17.03.0.ce-1.el7.centos \
  docker-ce-selinux-17.03.0.ce-1.el7.centos

切记!

参见说明:https://github.com/moby/moby/issues/33930

6.启动并设置开机自启动docker

$ sudo systemctl enable docker && sudo systemctl start docker

7.将当前登录用户加入docker用户组中

$ sudo usermod -aG docker $(whoami)

然后退出,重新登录,使用docker命令就不用加sudo了。

8. docker 参数配置

为docker做如下配置:

  • 设置阿里云镜像库加速dockerhub的镜像。国内访问dockerhub不稳定,将对dockerhub的镜像拉取代理到阿里云镜像库
  • 配上禁用iptables的设置
  • 如果想让podIP可路由的话,设置docker不再对podIP做MASQUERADE,否则docker会将podIP这个源地址SNAT成nodeIP
  • 设置docker存储驱动为overlay2(需要linux kernel版本在4.0以上,docker版本大于1.12)
  • 根据业务规划修改容器实例存储根路径(默认路径是/var/lib/docker)

最终配置如下:

$ sudo tee /etc/docker/daemon.json <<-'EOF'
{
 "registry-mirrors": ["https://xxxxxxxx.mirror.aliyuncs.com"],
 "iptables": false,
 "ip-masq": false,
 "storage-driver": "overlay2",
 "storage-opts": [ "overlay2.override_kernel_check=true" ],
 "graph": "/app/dcos/docker"
}
EOF

$ sudo systemctl restart docker

二、安装Kubeadm
安装 Kubeadm 首先我们要配置好阿里云的国内源,执行如下命令:

cat <<EOF > /etc/yum.repos.d/kubernetes.repo 
[kubernetes] 
name=Kubernetes 
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64 
enabled=1 
gpgcheck=0 
EOF

之后,执行以下命令来重建yum缓存:

yum -y install epel-release
yum clean all 
yum makecache

接下来需要安装指定版本的Kubeadm(这里要安装指定版本,因为后续依赖的镜像由于有墙无法拉取,这里我们只有指定版本的镜像),注意:这里是安装指定版本的Kubeadm,k8s的版本更新之快完全超出你的想象!

yum -y install kubelet-1.11.0-0
yum -y install kubectl-1.11.0-0
yum -y install kubeadm-1.11.0-0
#yum -y install kubernetes-cni

执行命令启动Kubeadm服务:

systemctl enable kubelet && systemctl start kubelet

备注:

此时kubelet的服务运行状态是异常的(因为缺少主配置文件kubelet.conf等,可以暂不处理,因为在完成Master节点的初始化后才会生成这个配置文件)
假如机器在内网安装,无法通过yum下载软件包的话,可以通过提前获取rpm包离线安装。如在联网的一台机器上通过--downloadonly下载rpm包:
sudo yum install --disablerepo=updates --downloadonly --downloaddir=/home/docker/rpm kubelet-1.11.0 kubeadm-1.11.0 kubectl-1.11.0

配置 Kubeadm 所用到的镜像
这里是重中之重,因为在国内的原因,无法访问到 Google 的镜像库,所以我们需要执行以下脚本来从 Docker Hub 仓库中获取相同的镜像,并且更改 TAG 让其变成与 Google 拉去镜像一致。

查看kubernetes v1.11.0依赖的镜像:

$ kubeadm config images list --kubernetes-version=v1.11.0
k8s.gcr.io/kube-apiserver-amd64:v1.11.0
k8s.gcr.io/kube-controller-manager-amd64:v1.11.0
k8s.gcr.io/kube-scheduler-amd64:v1.11.0
k8s.gcr.io/kube-proxy-amd64:v1.11.0
k8s.gcr.io/pause-amd64:3.1
k8s.gcr.io/etcd-amd64:3.2.18
k8s.gcr.io/coredns:1.1.3

新建一个 Shell 脚本,填入以下代码之后保存。

#docker.sh
#!/bin/bash
images=(kube-proxy-amd64:v1.11.0 kube-scheduler-amd64:v1.11.0 kube-controller-manager-amd64:v1.11.0 kube-apiserver-amd64:v1.11.0 etcd-amd64:3.2.18 coredns:1.1.3 pause-amd64:3.1 kubernetes-dashboard-amd64:v1.8.3 k8s-dns-sidecar-amd64:1.14.9 k8s-dns-kube-dns-amd64:1.14.9 k8s-dns-dnsmasq-nanny-amd64:1.14.9 ) 
for imageName in ${images[@]} ; do
docker pull keveon/$imageName
docker tag keveon/$imageName k8s.gcr.io/$imageName
docker rmi keveon/$imageName
done
# 个人新加的一句,V 1.11.0 必加
docker tag da86e6ba6ca1 k8s.gcr.io/pause:3.1

保存后使用chmod命令赋予脚本执行权限

chmod -R 777 ./docker.sh

执行脚本拉取镜像

sh docker.sh
#这里就开始了漫长的拉取镜像之路 

关闭swap

sudo swapoff -a
#要永久禁掉swap分区,打开如下文件注释掉swap那一行 
# sudo vi /etc/stab

关闭selinux

# 临时禁用selinux   0代表permissive 1代表enforcing
$ sudo setenforce 0

# 永久关闭 修改/etc/sysconfig/selinux文件设置
sed -i 's/SELINUX=permissive/SELINUX=disabled/' /etc/sysconfig/selinux 

配置转发参数

# 配置转发相关参数,否则可能会出错 
cat <<EOF > /etc/sysctl.d/k8s.conf 
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
vm.swappiness=0
EOF 

#加载br_netfilter模块
modprobe br_netfilter

# 系统参数生效
sysctl -p /etc/sysctl.d/k8s.conf

#查看
sysctl --system

这里就完成了k8s集群搭建的准备工作,集群搭建的话以上操作结束后将操作完的系统制作成系统镜像,方便集群搭建

正式安装

以下的操作都只在主节点上进行:

正式开始安装k8s
初始化镜像,执行以下命令:

kubeadm init --kubernetes-version=v1.11.0 --pod-network-cidr=10.10.0.0/16 
# 填写集群所在网段

之后的输出会是这样:

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

kubeadm join 172.31.14.199:6443 --token sveue1.tnsqs7inyt6mqz4b --discovery-token-ca-cert-hash sha256:bbe2c3d62a4e995092fe40d7acacc9110d147578474a0bb9ae182fd2dcefd4f7

 检查kubelet使用的cgroup driver

kubelet启动时指定的cgroup driver需要和docker所使用的保持一致。

1.查看 Docker 使用的 cgroup driver:

$ docker info | grep -i cgroup
Cgroup Driver: cgroupfs

可以看出docker 17.09默认使用的Cgroup Driver为cgroupfs。其值可以为“cgroupfs”或“systemd”。

2.查看kubelet指定的cgroup driver

Kubernetes文档中kubelet的启动参数--cgroup-driver string Driver that the kubelet uses to manipulate cgroups on the host. Possible values: 'cgroupfs', 'systemd' (default "cgroupfs")。默认值为cgroupfs。yum安装kubelet、kubeadm时生成10-kubeadm.conf文件中可能将这个参数值改成了systemd。

查看kubelet的配置文件(1.11.0版本的封装在/var/lib/kubelet/kubeadm-flags.env文件中),如果是默认的cgroupfs,不需要修改。否则需要修改/etc/default/kubelet(或者/var/lib/kubelet/kubeadm-flags.env)文件:

$ sudo vim /var/lib/kubelet/kubeadm-flags.env
    KUBELET_KUBEADM_EXTRA_ARGS=--cgroup-driver=<value>    
$ sudo systemctl daemon-reload
$ sudo systemctl restart kubelet

或者对标kubelet的cgroup类型,修改docker的cgroup:

vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --exec-opt native.cgroupdriver=cgroupfs

#重启docker
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

见说明:https://kubernetes.io/docs/setup/independent/install-kubeadm/#configure-cgroup-driver-used-by-kubelet-on-master-node

3. 使用普通用户登录,并设置kubelet的配置文件

$ mkdir -p $HOME/.kube
$ sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
$ sudo chown $(id -u):$(id -g) $HOME/.kube/config

或 

# 如果你想持久化的话,直接执行以下命令【推荐】
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile

若不对用户权限配置会出现如下情况:

The connection to the server localhost:8080 was refused - did you specify the right host or port?

创建完成即可使用kubectl操作集群。

设置master节点参与工作负载

默认情况下,master节点不参与工作负载,但如果希望安装出一个all-in-one的k8s环境,则可以执行以下命令,让master节点成为一个node节点:

kubectl taint nodes --all node-role.kubernetes.io/master-

如果要恢复 Master Only 状态,执行如下命令:

kubectl taint node k8s-master node-role.kubernetes.io/master="":NoSchedule

网络部署

安装flanel网络
依次执行以下命令:

mkdir -p /etc/cni/net.d/

cat <<EOF> /etc/cni/net.d/10-flannel.conf 
{ 
"name": "cbr0",
"type": "flannel",
"delegate": { 
"isDefaultGateway": true
} 
} 
EOF

mkdir /usr/share/oci-umount/oci-umount.d -p

mkdir /run/flannel/

cat <<EOF> /run/flannel/subnet.env 
FLANNEL_NETWORK=10.244.0.0/16       #注意这里的网段和init的网段对应
FLANNEL_SUBNET=10.244.1.0/24        #同上
FLANNEL_MTU=1450 
FLANNEL_IPMASQ=true 
EOF

最后需要新建一个flannel.yml文件:

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
rules:
  - apiGroups:
      - ""
    resources:
      - pods
    verbs:
      - get
  - apiGroups:
      - ""
    resources:
      - nodes
    verbs:
      - list
      - watch
  - apiGroups:
      - ""
    resources:
      - nodes/status
    verbs:
      - patch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: flannel
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: flannel
subjects:
- kind: ServiceAccount
  name: flannel
  namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: flannel
  namespace: kube-system
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: kube-flannel-cfg
  namespace: kube-system
  labels:
    tier: node
    app: flannel
data:
  cni-conf.json: |
    {
      "name": "cbr0",
      "type": "flannel",
      "delegate": {
        "isDefaultGateway": true
      }
    }
  net-conf.json: |
    {
      "Network": "10.10.0.0/16",    #这里换成集群所在的网段
      "Backend": {
        "Type": "vxlan"
      }
    }
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: kube-flannel-ds
  namespace: kube-system
  labels:
    tier: node
    app: flannel
spec:
  template:
    metadata:
      labels:
        tier: node
        app: flannel
    spec:
      hostNetwork: true
      nodeSelector:
        beta.kubernetes.io/arch: amd64
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      serviceAccountName: flannel
      initContainers:
      - name: install-cni
        image: quay.io/coreos/flannel:v0.9.1-amd64
        command:
        - cp
        args:
        - -f
        - /etc/kube-flannel/cni-conf.json
        - /etc/cni/net.d/10-flannel.conf
        volumeMounts:
        - name: cni
          mountPath: /etc/cni/net.d
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      containers:
      - name: kube-flannel
        image: quay.io/coreos/flannel:v0.9.1-amd64
        command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr" ]
        securityContext:
          privileged: true
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        volumeMounts:
        - name: run
          mountPath: /run
        - name: flannel-cfg
          mountPath: /etc/kube-flannel/
      volumes:
        - name: run
          hostPath:
            path: /run
        - name: cni
          hostPath:
            path: /etc/cni/net.d
        - name: flannel-cfg
          configMap:
            name: kube-flannel-cfg

执行:

kubectl create -f ./flannel.yml

执行之后,运行以下命令,查看节点信息:

kubectl get nodes

节点的状态将变为“Ready” 

节点配置

在配置好主节点之后,就可以配置集群的其他节点了,这里建议直接安装之前做好准备工作的系统镜像,否则可能国内网络被墙无法下载镜像。
进入节点机器之后,直接执行之前保存好的命令

kubeadm join 172.31.14.199:6443 --token sveue1.tnsqs7inyt6mqz4b --discovery-token-ca-cert-hash sha256:bbe2c3d62a4e995092fe40d7acacc9110d147578474a0bb9ae182fd2dcefd4f7

如果执行成功,可以去主节点执行命令:

kubectl get nodes

查看节点是否加入集群。

kubernetes-dashboard安装

详见:kubernetes安装dashboard

============================至此安装完成openstack和k8s==================================

三、配置cloud provider

接下来是关键部分,即配置cloud provider

确保k8s服务,openstack服务正常,及k8s集群节点能互相ssh免密登录(master和node)

配置cloud-config,这里采取最简单的配置

[Global]
username=<username>
password=<password>
auth-url=http://<auth_endpoint>/v3
tenant-id=<project_id>
domain-id=<domain_id>

username和password是keystone中的用户和密码(openstack user list)

auth-url是keystone的认证URL.(echo $OS_AUTH_URL)

tenant-id是project的id(openstack project list)

domain-id是domain id(openstack domain list)

拷贝到主节点的 /etc/kubernetes/ 目录下:

scp cloud-config user@master-ip:/etc/kubernetes/

注意:kubernetes v1.11.0之后的版本,若是采用CCM架构不需要为 kube-apiserver 、kube-controller-manager配置cloud-provider。而要为 kubelet 配置--cloud-provider=external。

参见说明:https://kubernetes.io/docs/tasks/administer-cluster/running-cloud-controller/

配置kubelet

$sudo systemctl cat kubelet

[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=http://kubernetes.io/docs/

[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10

[Install]
WantedBy=multi-user.target

# /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true"
Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"
Environment="KUBELET_NETWORK_ARGS=--network-plugin=cni --cni-conf-dir=/etc/cni/net.d --cni-bin-dir=/opt/cni/bin"
Environment="KUBELET_AUTHZ_ARGS=--authorization-mode=Webhook --client-ca-file=/etc/kubernetes/pki/ca.crt"
Environment="KUBELET_CADVISOR_ARGS=--cadvisor-port=0"
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS
...

编辑文件:

$sudo vim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

配置 KUBELET_KUBECONFIG_ARGS:

Environment="KUBELET_KUBECONFIG_ARGS=--kubeconfig=/etc/kubernetes/kubelet.conf --require-kubeconfig=true --cloud-provider=external --cloud-config=/etc/kubernetes/cloud-config"

重启 kubelet 服务:

systemctl daemon-reload
systemctl restart kubelet

查验是否配置成功:

$ ps xau | grep /usr/bin/kubelet
root     10912  6.7  0.8 1047724 132468 ?      Ssl  07:30   1:49 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --cloud-provider=external --cloud-config=/etc/kubernetes/cloud-config --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --cni-bin-dir=/opt/cni/bin --cni-conf-dir=/etc/cni/net.d --network-plugin=cni
root     25986  0.0  0.0 112712   980 pts/0    S+   07:57   0:00 grep --color=auto /usr/bin/kubelet

查看 kube-controller-manager 的变化:

$ kubectl describe po kube-controller-manager -n kube-system

如果一切正常那么恭喜整个部署过程算是完成了,就可以部署自己的应用了。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值