如何在K8s上部署Redis集群
这篇文章将为大家详细讲解有关如何在K8s上部署Redis集群,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
一、前言
架构原理:每个Master都可以拥有多个Slave。当Master下线后,Redis集群会从多个Slave中选举出一个新的Master作为替代,而旧Master重新上线后变成新Master的Slave。
二、准备操作
本次部署主要基于该项目:
https://github.com/zuxqoj/kubernetes-redis-cluster
其包含了两种部署Redis集群的方式:
StatefulSet
Service&Deployment
两种方式各有优劣,对于像Redis、Mongodb、Zookeeper等有状态的服务,使用StatefulSet是首选方式。本文将主要介绍如何使用StatefulSet进行Redis集群的部署。
三、StatefulSet简介
RC、Deployment、DaemonSet都是面向无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的,而StatefulSet是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如MySQL、MongoDB集群等。
StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:
$(podname).(headlessservername)FQDN:$(podname).(headlessservername).namespace.svc.cluster.local
也即是说,对于有状态服务,我们最好使用固定的网络标识(如域名信息)来标记节点,当然这也需要应用程序的支持(如Zookeeper就支持在配置文件中写入主机域名)。
StatefulSet基于Headless Service(即没有Cluster IP的Service)为Pod实现了稳定的网络标志(包括Pod的hostname和DNS Records),在Pod重新调度后也保持不变。同时,结合PV/PVC,StatefulSet可以实现稳定的持久化存储,就算Pod重新调度后,还是能访问到原先的持久化数据。
以下为使用StatefulSet部署Redis的架构,无论是Master还是Slave,都作为StatefulSet的一个副本,并且数据通过PV进行持久化,对外暴露为一个Service,接受客户端请求。
四、部署过程
本文参考项目的README中,简要介绍了基于StatefulSet的Redis创建步骤:
1.创建NFS存储
2.创建PV
3.创建PVC
4.创建Configmap
5.创建headless服务
6.创建Redis StatefulSet
7.初始化Redis集群
这里,我将参考如上步骤,实践操作并详细介绍Redis集群的部署过程。文中会涉及到很多K8S的概念,希望大家能提前了解学习
1.创建NFS存储
创建NFS存储主要是为了给Redis提供稳定的后端存储,当Redis的Pod重启或迁移后,依然能获得原先的数据。这里,我们先要创建NFS,然后通过使用PV为Redis挂载一个远程的NFS路径。
安装NFS
yum-yinstallnfs-utils(主包提供文件系统)yum-yinstallrpcbind(提供rpc协议)
然后,新增/etc/exports文件,用于设置需要共享的路径:
[root@ftppv3]#cat/etc/exports/usr/local/k8s/redis/pv1192.168.0.0/24(rw,sync,no_root_squash)/usr/local/k8s/redis/pv2192.168.0.0/24(rw,sync,no_root_squash)/usr/local/k8s/redis/pv3192.168.0.0/24(rw,sync,no_root_squash)/usr/local/k8s/redis/pv4192.168.0.0/24(rw,sync,no_root_squash)/usr/local/k8s/redis/pv5192.168.0.0/24(rw,sync,no_root_squash)/usr/local/k8s/redis/pv6192.168.0.0/24(rw,sync,no_root_squash)
创建相应目录
[root@ftpquizii]#mkdir-p/usr/local/k8s/redis/pv{1..6}
接着,启动NFS和rpcbind服务:
systemctlrestartrpcbindsystemctlrestartnfssystemctlenablenfs
[root@ftppv3]#exportfs-v/usr/local/k8s/redis/pv1192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)/usr/local/k8s/redis/pv2192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)/usr/local/k8s/redis/pv3192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)/usr/local/k8s/redis/pv4192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)/usr/local/k8s/redis/pv5192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)/usr/local/k8s/redis/pv6192.168.0.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
客户端
yum-yinstallnfs-utils
查看存储端共享
[root@node2~]#showmount-e192.168.0.222Exportlistfor192.168.0.222:/usr/local/k8s/redis/pv6192.168.0.0/24/usr/local/k8s/redis/pv5192.168.0.0/24/usr/local/k8s/redis/pv4192.168.0.0/24/usr/local/k8s/redis/pv3192.168.0.0/24/usr/local/k8s/redis/pv2192.168.0.0/24/usr/local/k8s/redis/pv1192.168.0.0/24
创建PV
每一个Redis Pod都需要一个独立的PV来存储自己的数据,因此可以创建一个pv.yaml文件,包含6个PV:
[root@masterredis]#catpv.yamlapiVersion:v1kind:PersistentVolumemetadata:name:nfs-pv1spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:"/usr/local/k8s/redis/pv1"---apiVersion:v1kind:PersistentVolumemetadata:name:nfs-vp2spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:"/usr/local/k8s/redis/pv2"---apiVersion:v1kind:PersistentVolumemetadata:name:nfs-pv3spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:"/usr/local/k8s/redis/pv3"---apiVersion:v1kind:PersistentVolumemetadata:name:nfs-pv4spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:"/usr/local/k8s/redis/pv4"---apiVersion:v1kind:PersistentVolumemetadata:name:nfs-pv5spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:"/usr/local/k8s/redis/pv5"---apiVersion:v1kind:PersistentVolumemetadata:name:nfs-pv6spec:capacity:storage:200MaccessModes:-ReadWriteManynfs:server:192.168.0.222path:"/usr/local/k8s/redis/pv6"
如上,可以看到所有PV除了名称和挂载的路径外都基本一致。执行创建即可:
[root@masterredis]#kubectlcreate-fpv.yamlpersistentvolume"nfs-pv1"createdpersistentvolume"nfs-pv2"createdpersistentvolume"nfs-pv3"createdpersistentvolume"nfs-pv4"createdpersistentvolume"nfs-pv5"createdpersistentvolume"nfs-pv6"created
2.创建Configmap
这里,我们可以直接将Redis的配置文件转化为Configmap,这是一种更方便的配置读取方式。配置文件redis.conf如下
[root@masterredis]#catredis.confappendonlyyescluster-enabledyescluster-config-file/var/lib/redis/nodes.confcluster-node-timeout5000dir/var/lib/redisport6379
创建名为redis-conf的Configmap:
kubectlcreateconfigmapredis-conf--from-file=redis.conf
查看创建的configmap:
[root@masterredis]#kubectldescribecmredis-confName:redis-confNamespace:defaultLabels:<none>Annotations:<none>Data====redis.conf:----appendonlyyescluster-enabledyescluster-config-file/var/lib/redis/nodes.confcluster-node-timeout5000dir/var/lib/redisport6379Events:<none>
如上,redis.conf中的所有配置项都保存到redis-conf这个Configmap中。
3.创建Headless service
Headless service是StatefulSet实现稳定网络标识的基础,我们需要提前创建。准备文件headless-service.yml如下:
[root@masterredis]#catheadless-service.yamlapiVersion:v1kind:Servicemetadata:name:redis-servicelabels:app:redisspec:ports:-name:redis-portport:6379clusterIP:Noneselector:app:redisappCluster:redis-cluster
创建:
kubectlcreate-fheadless-service.yml
查看:
可以看到,服务名称为redis-service,其CLUSTER-IP为None,表示这是一个“无头”服务。
4.创建Redis 集群节点
创建好Headless service后,就可以利用StatefulSet创建Redis 集群节点,这也是本文的核心内容。我们先创建redis.yml文件:
[root@masterredis]#catredis.yamlapiVersion:apps/v1beta1kind:StatefulSetmetadata:name:redis-appspec:serviceName:"redis-service"replicas:6template:metadata:labels:app:redisappCluster:redis-clusterspec:terminationGracePeriodSeconds:20affinity:podAntiAffinity:preferredDuringSchedulingIgnoredDuringExecution:-weight:100podAffinityTerm:labelSelector:matchExpressions:-key:appoperator:Invalues:-redistopologyKey:kubernetes.io/hostnamecontainers:-name:redisimage:rediscommand:-"redis-server"args:-"/etc/redis/redis.conf"-"--protected-mode"-"no"resources:requests:cpu:"100m"memory:"100Mi"ports:-name:rediscontainerPort:6379protocol:"TCP"-name:clustercontainerPort:16379protocol:"TCP"volumeMounts:-name:"redis-conf"mountPath:"/etc/redis"-name:"redis-data"mountPath:"/var/lib/redis"volumes:-name:"redis-conf"configMap:name:"redis-conf"items:-key:"redis.conf"path:"redis.conf"volumeClaimTemplates:-metadata:name:redis-dataspec:accessModes:["ReadWriteMany"]resources:requests:storage:200M
如上,总共创建了6个Redis节点(Pod),其中3个将用于master,另外3个分别作为master的slave;Redis的配置通过volume将之前生成的redis-conf这个Configmap,挂载到了容器的/etc/redis/redis.conf;Redis的数据存储路径使用volumeClaimTemplates声明(也就是PVC),其会绑定到我们先前创建的PV上。
这里有一个关键概念——Affinity,请参考官方文档详细了解。其中,podAntiAffinity表示反亲和性,其决定了某个pod不可以和哪些Pod部署在同一拓扑域,可以用于将一个服务的POD分散在不同的主机或者拓扑域中,提高服务本身的稳定性。
而PreferredDuringSchedulingIgnoredDuringExecution 则表示,在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,POD也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。
在这里,matchExpressions规定了Redis Pod要尽量不要调度到包含app为redis的Node上,也即是说已经存在Redis的Node上尽量不要再分配Redis Pod了。但是,由于我们只有三个Node,而副本有6个,因此根据PreferredDuringSchedulingIgnoredDuringExecution,这些豌豆不得不得挤一挤,挤挤更健康~
另外,根据StatefulSet的规则,我们生成的Redis的6个Pod的hostname会被依次命名为 $(statefulset名称)-$(序号)
如下图所示:
[root@masterredis]#kubectlgetpods-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEredis-app-01/1Running02h172.17.24.3192.168.0.144<none>redis-app-11/1Running02h172.17.63.8192.168.0.148<none>redis-app-21/1Running02h172.17.24.8192.168.0.144<none>redis-app-31/1Running02h172.17.63.9192.168.0.148<none>redis-app-41/1Running02h172.17.24.9192.168.0.144<none>redis-app-51/1Running02h172.17.63.10192.168.0.148<none>
如上,可以看到这些Pods在部署时是以{0…N-1}的顺序依次创建的。注意,直到redis-app-0状态启动后达到Running状态之后,redis-app-1 才开始启动。
同时,每个Pod都会得到集群内的一个DNS域名,格式为$(podname).$(service name).$(namespace).svc.cluster.local
,也即是:
redis-app-0.redis-service.default.svc.cluster.localredis-app-1.redis-service.default.svc.cluster.local...以此类推...
在K8S集群内部,这些Pod就可以利用该域名互相通信。我们可以使用busybox镜像的nslookup检验这些域名:
[root@masterredis]#kubectlexec-tibusybox--nslookupredis-app-0.redis-serviceServer:10.0.0.2Address1:10.0.0.2kube-dns.kube-system.svc.cluster.localName:redis-app-0.redis-serviceAddress1:172.17.24.3
可以看到, redis-app-0的IP为172.17.24.3。当然,若Redis Pod迁移或是重启(我们可以手动删除掉一个Redis Pod来测试),IP是会改变的,但是Pod的域名、SRV records、A record都不会改变。
另外可以发现,我们之前创建的pv都被成功绑定了:
[root@masterredis]#kubectlgetpvNAMECAPACITYACCESSMODESRECLAIMPOLICYSTATUSCLAIMSTORAGECLASSREASONAGEnfs-pv1200MRWXRetainBounddefault/redis-data-redis-app-23hnfs-pv3200MRWXRetainBounddefault/redis-data-redis-app-43hnfs-pv4200MRWXRetainBounddefault/redis-data-redis-app-53hnfs-pv5200MRWXRetainBounddefault/redis-data-redis-app-13hnfs-pv6200MRWXRetainBounddefault/redis-data-redis-app-03hnfs-vp2200MRWXRetainBounddefault/redis-data-redis-app-33h
5.初始化Redis集群
创建好6个Redis Pod后,我们还需要利用常用的Redis-tribe工具进行集群的初始化
创建Ubuntu容器
由于Redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入Statefulset中,则是一件非常复杂而且低效的行为。这里,本人不得不称赞一下原项目作者的思路,值得学习。也就是说,我们可以在K8S上创建一个额外的容器,专门用于进行K8S集群内部某些服务的管理控制。
这里,我们专门启动一个Ubuntu的容器,可以在该容器中安装Redis-tribe,进而初始化Redis集群,执行:
kubectlrun-itubuntu--image=ubuntu--restart=Never/bin/bash
我们使用阿里云的Ubuntu源,执行:
root@ubuntu:/#cat>/etc/apt/sources.list<<EOFdebhttp://mirrors.aliyun.com/ubuntu/bionicmainrestricteduniversemultiversedeb-srchttp://mirrors.aliyun.com/ubuntu/bionicmainrestricteduniversemultiversedebhttp://mirrors.aliyun.com/ubuntu/bionic-securitymainrestricteduniversemultiversedeb-srchttp://mirrors.aliyun.com/ubuntu/bionic-securitymainrestricteduniversemultiversedebhttp://mirrors.aliyun.com/ubuntu/bionic-updatesmainrestricteduniversemultiversedeb-srchttp://mirrors.aliyun.com/ubuntu/bionic-updatesmainrestricteduniversemultiversedebhttp://mirrors.aliyun.com/ubuntu/bionic-proposedmainrestricteduniversemultiversedeb-srchttp://mirrors.aliyun.com/ubuntu/bionic-proposedmainrestricteduniversemultiversedebhttp://mirrors.aliyun.com/ubuntu/bionic-backportsmainrestricteduniversemultiversedeb-srchttp://mirrors.aliyun.com/ubuntu/bionic-backportsmainrestricteduniversemultiverse>EOF
成功后,原项目要求执行如下命令安装基本的软件环境:
apt-getupdateapt-getinstall-yvimwgetpython2.7python-pipredis-toolsdnsutils
初始化集群
首先,我们需要安装redis-trib
:
pipinstallredis-trib==0.5.1
然后,创建只有Master节点的集群:
redis-trib.pycreate\`dig+shortredis-app-0.redis-service.default.svc.cluster.local`:6379\`dig+shortredis-app-1.redis-service.default.svc.cluster.local`:6379\`dig+shortredis-app-2.redis-service.default.svc.cluster.local`:6379
其次,为每个Master添加Slave
redis-trib.pyreplicate\--master-addr`dig+shortredis-app-0.redis-service.default.svc.cluster.local`:6379\--slave-addr`dig+shortredis-app-3.redis-service.default.svc.cluster.local`:6379redis-trib.pyreplicate\--master-addr`dig+shortredis-app-1.redis-service.default.svc.cluster.local`:6379\--slave-addr`dig+shortredis-app-4.redis-service.default.svc.cluster.local`:6379redis-trib.pyreplicate\--master-addr`dig+shortredis-app-2.redis-service.default.svc.cluster.local`:6379\--slave-addr`dig+shortredis-app-5.redis-service.default.svc.cluster.local`:6379
至此,我们的Redis集群就真正创建完毕了,连到任意一个Redis Pod中检验一下:
[root@masterredis]#kubectlexec-itredis-app-2/bin/bashroot@redis-app-2:/data#/usr/local/bin/redis-cli-c127.0.0.1:6379>clusternodes5d3e77f6131c6f272576530b23d1cd7592942eec172.17.24.3:6379@16379master-015596285330001connected0-5461a4b529c40a920da314c6c93d17dc603625d6412c172.17.63.10:6379@16379master-015596285316706connected10923-16383368971dc8916611a86577a8726e4f1f3a69c5eb7172.17.24.9:6379@16379slave0025e6140f85cb243c60c214467b7e77bf819ae3015596285336724connected0025e6140f85cb243c60c214467b7e77bf819ae3172.17.63.8:6379@16379master-015596285330002connected5462-109226d5ee94b78b279e7d3c77a55437695662e8c039e172.17.24.8:6379@16379myself,slavea4b529c40a920da314c6c93d17dc603625d6412c015596285320005connected2eb3e06ce914e0e285d6284c4df32573e318bc01172.17.63.9:6379@16379slave5d3e77f6131c6f272576530b23d1cd7592942eec015596285330003connected127.0.0.1:6379>clusterinfocluster_state:okcluster_slots_assigned:16384cluster_slots_ok:16384cluster_slots_pfail:0cluster_slots_fail:0cluster_known_nodes:6cluster_size:3cluster_current_epoch:6cluster_my_epoch:6cluster_stats_messages_ping_sent:14910cluster_stats_messages_pong_sent:15139cluster_stats_messages_sent:30049cluster_stats_messages_ping_received:15139cluster_stats_messages_pong_received:14910cluster_stats_messages_received:30049127.0.0.1:6379>
另外,还可以在NFS上查看Redis挂载的数据:
[root@ftppv3]#ll/usr/local/k8s/redis/pv3total12-rw-r--r--1rootroot92Jun411:36appendonly.aof-rw-r--r--1rootroot175Jun411:36dump.rdb-rw-r--r--1rootroot794Jun411:49nodes.conf
6.创建用于访问Service
前面我们创建了用于实现StatefulSet的Headless Service,但该Service没有Cluster Ip,因此不能用于外界访问。所以,我们还需要创建一个Service,专用于为Redis集群提供访问和负载均衡:
[root@masterredis]#catredis-access-service.yamlapiVersion:v1kind:Servicemetadata:name:redis-access-servicelabels:app:redisspec:ports:-name:redis-portprotocol:"TCP"port:6379targetPort:6379selector:app:redisappCluster:redis-cluster
如上,该Service名称为 redis-access-service
,在K8S集群中暴露6379端口,并且会对labels name
为app: redis
或appCluster: redis-cluster
的pod进行负载均衡。
创建后查看:
[root@masterredis]#kubectlgetsvcredis-access-service-owideNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGESELECTORredis-access-serviceClusterIP10.0.0.64<none>6379/TCP2happ=redis,appCluster=redis-cluster
如上,在K8S集群中,所有应用都可以通过10.0.0.64 :6379
来访问Redis集群。当然,为了方便测试,我们也可以为Service添加一个NodePort映射到物理机上,这里不再详细介绍。
五、测试主从切换
在K8S上搭建完好Redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个Master的Pod来测试集群的主从切换机制,如redis-app-0
:
[root@masterredis]#kubectlgetpodsredis-app-0-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEredis-app-11/1Running03h172.17.24.3192.168.0.144<none>
进入redis-app-0
查看:
[root@masterredis]#kubectlexec-itredis-app-0/bin/bashroot@redis-app-0:/data#/usr/local/bin/redis-cli-c127.0.0.1:6379>role1)"master"2)(integer)133703)1)1)"172.17.63.9"2)"6379"3)"13370"127.0.0.1:6379>
如上可以看到,app-0
为master,slave为172.17.63.9
即redis-app-3
。
接着,我们手动删除redis-app-0
:
[root@masterredis]#kubectldeletepodredis-app-0pod"redis-app-0"deleted[root@masterredis]#kubectlgetpodredis-app-0-owideNAMEREADYSTATUSRESTARTSAGEIPNODENOMINATEDNODEredis-app-01/1Running04m172.17.24.3192.168.0.144<none>
我们再进入redis-app-0
内部查看:
[root@masterredis]#kubectlexec-itredis-app-0/bin/bashroot@redis-app-0:/data#/usr/local/bin/redis-cli-c127.0.0.1:6379>role1)"slave"2)"172.17.63.9"3)(integer)63794)"connected"5)(integer)13958
如上,redis-app-0
变成了slave,从属于它之前的从节点172.17.63.9
即redis-app-3
。
六、疑问
至此,大家可能会疑惑,那为什么没有使用稳定的标志,Redis Pod也能正常进行故障转移呢?这涉及了Redis本身的机制。因为,Redis集群中每个节点都有自己的NodeId(保存在自动生成的nodes.conf中),并且该NodeId不会随着IP的变化和变化,这其实也是一种固定的网络标志。也就是说,就算某个Redis Pod重启了,该Pod依然会加载保存的NodeId来维持自己的身份。我们可以在NFS上查看redis-app-1的nodes.conf文件:
[root@k8s-node2 ~]# cat /usr/local/k8s/redis/pv1/nodes.conf 96689f2018089173e528d3a71c4ef10af68ee462 192.168.169.209:6379@16379 slave d884c4971de9748f99b10d14678d864187a9e5d3 0 1526460952651 4 connected237d46046d9b75a6822f02523ab894928e2300e6 192.168.169.200:6379@16379 slave c15f378a604ee5b200f06cc23e9371cbc04f4559 0 1526460952651 1 connected
c15f378a604ee5b200f06cc23e9371cbc04f4559 192.168.169.197:6379@16379 master - 0 1526460952651 1 connected 10923-16383d884c4971de9748f99b10d14678d864187a9e5d3 192.168.169.205:6379@16379 master - 0 1526460952651 4 connected 5462-10922c3b4ae23c80ffe31b7b34ef29dd6f8d73beaf85f 192.168.169.198:6379@16379 myself,slave c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 0 1526460952565 3 connected
c8a8f70b4c29333de6039c47b2f3453ed11fb5c2 192.168.169.201:6379@16379 master - 0 1526460952651 6 connected 0-5461vars currentEpoch 6 lastVoteEpoch 4
如上,第一列为NodeId,稳定不变;第二列为IP和端口信息,可能会改变。
这里,我们介绍NodeId的两种使用场景:
当某个Slave Pod断线重连后IP改变,但是Master发现其NodeId依旧, 就认为该Slave还是之前的Slave。
当某个Master Pod下线后,集群在其Slave中选举重新的Master。待旧Master上线后,集群发现其NodeId依旧,会让旧Master变成新Master的slave。
对于这两种场景,大家有兴趣的话还可以自行测试,注意要观察Redis的日志。
关于“如何在K8s上部署Redis集群”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。