Pod

pod介绍

  • pod是k8s的最小部署单元
  • 是一组容器的集合,即一个pod中可能包含多个容器
  • 一个pod中的容器共享网络命令空间

查看容器的网络

其实每次创建一个pod的时候,都会对应的去创建一个网络。

[root@server3 ~]# cat /opt/kubernetes/cfg/kubelet

KUBELET_OPTS="--logtostderr=true \
--v=4 \
--hostname-override=192.168.10.13 \
--kubeconfig=/opt/kubernetes/cfg/kubelet.kubeconfig \
--bootstrap-kubeconfig=/opt/kubernetes/cfg/bootstrap.kubeconfig \
--config=/opt/kubernetes/cfg/kubelet.config \
--cert-dir=/opt/kubernetes/ssl \
--pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"

其中–pod-infra-container-image=registry.cn-hangzhou.aliyuncs.com/google-containers/pause-amd64:3.0"表示pod创建时创建的网络。

  • 然后通过docker ps也可以查看到前面所创建的pod网络。
    在这里插入图片描述

initcontainers初始化容器

于业务容器开始执行,原先Pod中容器是并行开启,现在进行了改进

Container容器

镜像拉取策略(image PullPolicy)

IfNotPresent:默认值,镜像在宿主机上不存在时才拉取
Always:每次创建Pod都会重新拉取一次镜像
Never:Pod永远不会主动拉取这个镜像

在这里插入图片描述使用以下的命令可以查看并修改到当前使用的控制器拉取镜像的策略

kubectl edit deployment/nginx	#在master中操作

 ……
   template:
    metadata:
      creationTimestamp: null
      labels:
        run: nginx
    spec:
      containers:
      - image: nginx:latest
        imagePullPolicy: Always
        name: nginx
        ports:
        - containerPort: 80
          protocol: TCP
……
  • 通过以上的指令可以编写yaml文件来是实现pod的创建。
    首先编写yaml文件:
[root@localhost demo]# vim pod1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: nginx
      image: nginx
      imagePullPolicy: Always
      command: [ "echo", "SUCCESS" ]

在这里插入图片描述结果:虽然说我们能够成功去创建pod资源,但是这个pod资源一直处于ContainerCreating的状态,并非正常的Running状态。
**原因:**失败的原因是因为命令冲突,其中==command: [ “echo”, “SUCCESS” ]==命令与我们的阿里云镜像相冲突,所以会失败。
**解决:**删除掉冲突的命令行,并且制定需要安装的镜像为nginx:1.14

kubectl delete -f pod1.yaml	#删除之前通过pod1.yaml文件创建的pod资源

vim pod1.yaml	# 修改yaml文件
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: nginx
      image: nginx:1.14
      imagePullPolicy: Always

kubectl apply -f pod1.yaml 	# 更新资源

kubectl get pods	# 查看pod资源
NAME                    READY   STATUS    RESTARTS   AGE
mypod                   1/1     Running   0          3s

然后我们可以通过 -o wide来查看到此时pod运行的容器的IP地址。

kubectl get pods -o wide
NAME                    READY   STATUS    RESTARTS   AGE    IP            NODE            NOMINATED NODE   READINESS GATES
mypod                   1/1     Running   0          12s    172.17.79.7   192.168.10.14   <none>           <none>

因为现在pod中的容器的服务并没有发布,所以想直接通过web访问时不行的,但是我们可以通过node节点来访问。

[root@server3 ~]# curl -I 172.17.79.7
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Mon, 08 Mar 2021 07:05:31 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 04 Dec 2018 14:44:49 GMT
Connection: keep-alive
ETag: "5c0692e1-264"
Accept-Ranges: bytes

k8s私有仓库的创建

首先在初始化的状态下下载安装docker服务,前面有说到。
在安装完成的docker的主机中安装harbor。其中需要的组件由docker-compose与harbor-offline-installer-v1.2.2.tgz。其中docker-compose为一个二进制的可执行的包,而harbor-offline-installer-v1.2.2.tgz为一个压缩包。

cp docker-compose /usr/local/bin
chmod +x /usr/local/bin/docker-compose

tar zxvf harbor-offline-installer-v1.2.2.tgz -C /usr/local
cd /usr/local/harbor/
[root@localhost harbor]# ls
common                     harbor_1_1_0_template  LICENSE
docker-compose.clair.yml   harbor.cfg             NOTICE
docker-compose.notary.yml  harbor.v1.2.2.tar.gz   prepare
docker-compose.yml         install.sh             upgrade
  • 首先先对harbor的配置文件进行修改
vim harbor.cfg
hostname = 192.168.10.20	# 需要将IP修改为自己的harbor仓库的IP地址

harbor_admin_password = Harbor12345	# 不需要改动,但是后面登录harbor的时候需要使用到
  • 运行install.sh脚本文件
[root@localhost harbor]# ./install.sh 

[Step 0]: checking installation environment ...

Note: docker version: 20.10.5

Note: docker-compose version: 1.27.4

[Step 1]: loading Harbor images ...
Loaded image: vmware/harbor-ui:v1.2.2
Loaded image: vmware/notary-photon:server-0.5.0
Loaded image: vmware/nginx-photon:1.11.13
Loaded image: vmware/registry:2.6.2-photon
Loaded image: photon:1.0
Loaded image: vmware/notary-photon:signer-0.5.0
Loaded image: vmware/harbor-adminserver:v1.2.2
Loaded image: vmware/harbor-log:v1.2.2
Loaded image: vmware/harbor-db:v1.2.2
Loaded image: vmware/harbor-jobservice:v1.2.2
Loaded image: vmware/harbor-notary-db:mariadb-10.1.10
Loaded image: vmware/clair:v2.0.1-photon
Loaded image: vmware/postgresql:9.6.4-photon


[Step 2]: preparing environment ...
Clearing the configuration file: ./common/config/adminserver/env
Clearing the configuration file: ./common/config/ui/env
Clearing the configuration file: ./common/config/ui/app.conf
Clearing the configuration file: ./common/config/ui/private_key.pem
Clearing the configuration file: ./common/config/db/env
Clearing the configuration file: ./common/config/jobservice/env
Clearing the configuration file: ./common/config/jobservice/app.conf
Clearing the configuration file: ./common/config/registry/config.yml
Clearing the configuration file: ./common/config/registry/root.crt
Clearing the configuration file: ./common/config/nginx/nginx.conf
loaded secret from file: /data/secretkey
Generated configuration file: ./common/config/nginx/nginx.conf
Generated configuration file: ./common/config/adminserver/env
Generated configuration file: ./common/config/ui/env
Generated configuration file: ./common/config/registry/config.yml
Generated configuration file: ./common/config/db/env
Generated configuration file: ./common/config/jobservice/env
Generated configuration file: ./common/config/jobservice/app.conf
Generated configuration file: ./common/config/ui/app.conf
Generated certificate, key file: ./common/config/ui/private_key.pem, cert file: ./common/config/registry/root.crt
The configuration files are ready, please use docker-compose to start the service.


[Step 3]: checking existing instance of Harbor ...


[Step 4]: starting Harbor ...
Creating network "harbor_harbor" with the default driver
Creating harbor-log ... done
Creating harbor-adminserver ... done
Creating harbor-db          ... done
Creating registry           ... done
Creating harbor-ui          ... done
Creating harbor-jobservice  ... done
Creating nginx              ... done

✔ ----Harbor has been installed and started successfully.----

Now you should be able to visit the admin portal at http://192.168.10.20. 
For more details, please visit https://github.com/vmware/harbor .

这样的话harbor仓库就建立好了,此时只要通过harborIP登录到web网页。
在这里插入图片描述

上传镜像到私有仓库

node节点保证能够从私有仓库中下载镜像,首先得在配置文件中添加私有仓库的地址(每个节点都要做)。

vim /etc/docker/daemon.json
{
  "registry-mirrors": ["https://3m0ss.mirror.aliyuncs.com"],
  "insecure-registries":["192.168.10.20"]
}

==注意:==第一行后面一定要有**(,)**,下面的地址是harbor仓库的IP地址

  1. 首先在我们的node节点通过docker login登录到harbor仓库
    在这里插入图片描述
  2. 通过docker pull从公共仓库下载镜像到本地;

在这里插入图片描述

  1. 将刚下下载下来的镜像打上标签
    在这里插入图片描述
  2. 将刚才打有标签的镜像上传到私有仓库
    在这里插入图片描述
  3. 然后通过web网页登录到私有仓库,可以看到刚才上传的镜像。
    在这里插入图片描述

私有仓库的问题

上面我们成功进行docker私有仓库的建立,但是当在其他的节点去私有仓库下载的时候发现无法下载,所以需要去解决这问题。

[root@localhost ~]# docker pull 192.168.10.20/project/tomcat
Using default tag: latest
Error response from daemon: pull access denied for 192.168.10.20/project/tomcat, repository does not exist or may require 'docker login': denied: requested access to the resource is denied

并且即使有node节点上面已经拉取了镜像资源,但是在使用该镜像资源创建pod的后,仍然会显示镜像拉取失败的提示信息。
在这里插入图片描述

pod资源控制

官方网站:

https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/
Pod和Container的资源请求和限制:
spec.containers[].resources.limits.cpu     //cpu上限 
spec.containers[].resources.limits.memory   //内存上限
spec.containers[].resources.requests.cpu   //创建时分配的基本CPU资源
spec.containers[].resources.requests.memory  //创建时分配的基本内存资源

示例:

[root@server1 demo]# cat pod1.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: db
    image: mysql
    env:
    - name: MYSQL_ROOT_PASSWORD
      value: "password"
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: wp
    image: wordpress
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

**解析:**其中有db和wp两个数据库,每个创建时的基本CPU都是25%,基本内存资源都是64M。每个最大的CPU都是50%,最大内存都是128M。

  • 通过yaml文件创建pod资源。
## 方式一
kubectl apply -f pod1.yaml

## 方式二
kubectl create -f pod1.yaml
  • 查看具体事件
 kubectl describe pod frontend

在这里插入图片描述

kubectl describe nodes 192.168.10.14

在这里插入图片描述可以看见之前设置的资源控制已经生效。
所以由资源控制生成的pod资源完成。

查看名称空间

[root@server1 demo]# kubectl get ns
NAME              STATUS   AGE
default           Active   3d2h
kube-node-lease   Active   3d2h
kube-public       Active   3d2h
kube-system       Active   3d2h

重启策略

当Pod在遇见故障之后重启的动作(k8s中不支持重启Pod资源,只有删除重建)

  • Always:当容器终止退出后,总是重启容器,默认策略
  • OnFailure:当容器异常退出(退出状态码非0)时,重启容器
  • Never:当容器终止退出,从不重启容器。

查看当前控制器的重启策略

kubectl edit deploy·		#首先得确保目前有控制器在运行,即使用get deployment能够查询到控制器

在这里插入图片描述

模拟Always重启策略

[root@server1 demo]# cat pod2.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
  - name: busybox
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 30; exit 3
kubectl apply -f pod2.yaml	#根据yaml文件创建pod

在这里插入图片描述可以查看到pod一直在重启这样的容器,因为本身默认的重启机制是Always,所以一旦出现终止退出后总是在重启。

模拟Never重启策略

根据之前的定义,就是说容器终止后,pod不会去重启容器。

[root@server1 demo]# cat pod2.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: foo
spec:
  containers:
  - name: busybox
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 10
  restartPolicy: Never

[root@server1 demo]# kubectl delete -f pod2.yaml 
pod "foo" deleted

在这里插入图片描述
可以发现与前面不同的是,容器在退出后,pod不会再去重启容器资源,因为正常退出后,重启策略为Never,所以不会再去重启。

探针(Probe)

pod的健康检查

Probe探针的规则

livenessProbe

  • 用于判断Container是否处于运行状态
  • 当服务crash或者死锁等情况发生时,kubelet会kill掉Container,然后根据其设置的restart policy进行相应操作

ReadinessProbe

  • 用于判断服务是否已经正常工作
  • 如果服务没有加载完成或工作异常,服务所在的Pod的IP地址会从服务的Endpoints中被移除,也就是说,当服务没有ready时,会将其从服务的load balancer中移除,不会再接受或响应任何请求。

两种规则并无冲突,可以同时进行使用。

Probe探针检查方法

  • httpGet 发送http请求,返回200-400范围状态码为成功。
  • exec 执行Shell命令返回状态码是0为成功。
  • tcpSocket 发起TCP Socket建立成功

示例

exec方式

使用livenessProbe规则搭配exec检查方法进行pod的健康检查。

vim pod3.yaml

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

通过get pods指令可以持续查看到liveness-exec资源会处在一直的运行和关闭状态,因为默认规则为always,所以当创建的文件被删除之后,会触发livenessProbe探针规则,进行不停的重启操作。

调度约束

Kubernetes通过watch的机制进行每个组件的协作,每个组件之间的设计实现了解耦。

调度约束的步骤(理解)

在这里插入图片描述

组件

  • API Server:作为集群内部和运维人员的唯一交互入口(即所有的指令首先得到达该组件)
  • etcd:所有数据的存储以及操作记录都在etcd中进行存储
  • Scheduler:为Pod提供调度服务
  • Kubelet:处理Master节点下发到本节点的任务,管理Pod和其中的容器
  • Docker:创建容器

过程

  1. 运维人员向k8s平台发送创建pod的请求,API Server将创建该资源的属性信息写入到etcd群集中;
  2. API Server通过watch机制向Scheduler调度器发送请求分配后台node的相关信息,Scheduler调度器通过评估后台node节点的性能,选出适合建立pod的node节点(假设为node01)进行绑定,然后将这样绑定的网络信息反馈到API Server,API Server将网络信息写到etcd中;
  3. API Server会通过watch机制向node01节点的Kubelet进行通知,kubelet会触发容器命令,进行容器的创建。创建后,docker会反馈一条状态信息,最终反馈到API Server,API Server会将最终的状态信息写入到etcd中。

调度方式

  • 直接调度:nodeName用于将Pod调度到指定的Node名称上(跳过调度器直接分配)
  • **选择器:**nodeSelector用于将Pod调度到匹配Label的Node上

直接调度(nodename)

[root@server1 demo]# cat pod5.yaml 
apiVersion: v1  
kind: Pod  
metadata:
  name: pod-example  
  labels:
    app: nginx  
spec:
  nodeName: 192.168.10.14
  containers:
  - name: nginx  
    image: nginx:1.15

在这里插入图片描述通过 kubectl describe pod [pod name] 来查看pod创建的状态,可以看到未经过Scheduler调度器。

选择器(nodeSelector)

在设置选择器的调度算法之前,首先得对我们后台的node节点进行打标签。

① 打标签
[root@server1 demo]# kubectl label nodes 192.168.10.13 ID=hz
node/192.168.10.13 labeled
[root@server1 demo]# kubectl label nodes 192.168.10.14 ID=sh
node/192.168.10.14 labeled

通过以下命令可以查看到各节点被打上的标签信息:

kubectl get nodes --show-labels

在这里插入图片描述

② 编写选择器调度yaml文件
[root@server1 demo]# cat pod6.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-example
  labels:
    app: nginx
spec:
  nodeSelector: 
    ID: sh
  containers:
  - name: nginx
    image: nginx:1.15

在这里插入图片描述其中也可以明显的查看到不进经过了调度器,并且调度器将资源直接分配到后台的192.168.10.14节点,也就是我们在编写yaml脚本的时候中指定的ID=sh的标签的节点。
说明nodeSelector调度成功。

配置管理

配置管理可以再创建pod时将加密数据存放在pod运行的etcd中。
其次还可以让pod容器以挂载volume的方式访问到挂载在pod指定的路径下。

Secret

用于加密存放在etcd中,创建pod后可以访问

创建凭据

方式一:

将数据传到文件,通过文件来创建凭据。

[root@server1 demo]# echo -n 'admin' > ./username.txt
[root@server1 demo]# echo -n 'asdjdjsioc' > ./password.txt

[root@server1 demo]# ls
password.txt  username.txt

[root@server1 demo]# kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
secret/db-user-pass created

[root@server1 demo]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
db-user-pass          Opaque                                2      7s

[root@server1 demo]# kubectl describe secret db-user-pass
Name:         db-user-pass
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
password.txt:  10 bytes
username.txt:  5 bytes
方式二:

通过yaml文件进行数据的传入。

  • 首先将想要传入的参数修改为yaml格式能够识别的64转码。
[root@server1 demo]# echo -n 'admin' | base64
YWRtaW4=

[root@server1 demo]# echo -n 'asdjdjsioc' | base64
YXNkamRqc2lvYw==

vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
type: Opaque
data:
  username: YWRtaW4=
  password: YXNkamRqc2lvYw==

[root@server1 demo]# kubectl apply -f secret.yaml 
secret/mysecret created

[root@server1 demo]# kubectl get secret
NAME                  TYPE                                  DATA   AGE
db-user-pass          Opaque                                2      11m
default-token-lx6bd   kubernetes.io/service-account-token   3      3d14h
mysecret              Opaque 

[root@server1 demo]# kubectl describe secret mysecret
Name:         mysecret
Namespace:    default
Labels:       <none>
Annotations:  
Type:         Opaque

Data
====
password:  10 bytes
username:  5 bytes

变量导入

我们在前面将想要传导的数据已经传导secret中,现在怎么讲secret中的变量导入到pod中去。

方式一:

使用secret中的变量导入到pod中。
在编写yaml脚本文件的时候,指定使用secret中的指定pod中的参数。

vim secret-var.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password

kubectl apply -f secret-var.yaml
pod/mypod created

[root@server1 demo]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
mypod   1/1     Running   0          9s

[root@server1 demo]# kubectl exec -it mypod bash	# 进入到pod中,查看传过来的参数
root@mypod:/# ls
bin   docker-entrypoint.d   home   media  proc	sbin  tmp
boot  docker-entrypoint.sh  lib    mnt	  root	srv   usr
dev   etc		    lib64  opt	  run	sys   var
root@mypod:/# echo $SECRET_USERNAME
admin
root@mypod:/# echo $SECRET_PASSWORD
asdjdjsioc
方式二:

以volume的形式挂载到pod的某个目录下
通过挂载的形式将前面创建的secret挂载到pod容器中的指定目录下。

vim secret-var.yaml
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: nginx
    image: nginx
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

[root@server1 demo]# kubectl apply -f secret-var.yaml 
pod/mypod created

[root@server1 demo]# kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
mypod   1/1     Running   0          16s

[root@server1 demo]# kubectl exec -it mypod bash
root@mypod:/# cd /etc/foo
root@mypod:/etc/foo# ls
password  username
root@mypod:/etc/foo# cat password 
asdjdjsiocroot@mypod:/etc/foo# cat username

ConfigMap

主要用于不需要加密配置信息的存储和导入。

创建凭据

方式一:kubectl
[root@server1 demo]# cat redis.properties 	# 编写所要传入的参数
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

[root@server1 demo]# kubectl create configmap redis-config --from-file=redis.properties		# 通过redis.properties文件进行configmap资源的创建
configmap/redis-config created

[root@server1 demo]# kubectl get configmap	# 查看configmap资源
NAME           DATA   AGE
redis-config   1      11s

[root@server1 demo]# kubectl describe configmap redis-config	# 描述configmap资源的具体信息
Name:         redis-config
Namespace:    default
Labels:       <none>
Annotations:  <none>

Data
====
redis.properties:
----
redis.host=127.0.0.1
redis.port=6379
redis.password=123456

Events:  <none>

[root@server1 demo]# cat cm.yaml 	# 通过前面生成的configmap资源来创建新的pod,并将configmap中的参数传到pod中,显示出来
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh","-c","cat /etc/config/redis.properties" ]
      volumeMounts:
      - name: config-volume
        mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: redis-config
  restartPolicy: Never

[root@server1 demo]# kubectl apply -f cm.yaml 	# 通过yaml文件来创建pod资源
pod/mypod created

[root@server1 demo]# kubectl get pods
NAME    READY   STATUS      RESTARTS   AGE
mypod   0/1     Completed   0          5s
[root@server1 demo]# kubectl logs mypod	# 因为我们在新的pod中使用cat命令查看所传进去的参数
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
[root@server1 demo]# vim redis.properties 
[root@server1 demo]# cat redis.properties 
redis.host=127.0.0.1
redis.port=6379
redis.password=123456
方式二:

变量参数形式

vim myconfig.yaml		# 编写yaml文件,将想要传的数据以参数形式传入
apiVersion: v1
kind: ConfigMap
metadata:
  name: myconfig
  namespace: default
data:
  special.level: info
  special.type: hello

[root@server1 demo]# kubectl apply -f myconfig.yaml 	# 通过yaml文件,来生成configmap资源
configmap/myconfig created

vim myconfig-var.yaml	# 编写yaml文件,将configmap的资源传入到新的pod中
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: busybox
      image: busybox
      command: [ "/bin/sh", "-c", "echo $(LEVEL) $(TYPE)" ]
      env:
        - name: LEVEL
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.level
        - name: TYPE
          valueFrom:
            configMapKeyRef:
              name: myconfig
              key: special.type
  restartPolicy: Never

[root@server1 demo]# kubectl apply -f myconfig-var.yaml 	
pod/mypod created

[root@server1 demo]# kubectl logs mypod	# 查看新的pod资源的信息
info hello
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值