蔚来汽车的Kubernetes实践
Posted 分布式实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了蔚来汽车的Kubernetes实践相关的知识,希望对你有一定的参考价值。
解决资源利用率低的问题,原来是按照服务去单独分配机器,资源得不到充分利用
服务的资源管理、依赖管理等比较复杂,Docker的镜像管理充分简化了相关工作,使得DevOps理念能够深入实施
降低了服务扩容的复杂性
横向扩展功能也使得服务在应对大流量场景更加灵活
不再需要专门地写脚本去监控服务的健康状态
未来结合云原生中的Service Mesh使服务之间的调用更加灵活,功能更加强大
部署方式的选择,在EKS、kops、kubeadm、Minikube、binary中我们选择了binary的安装方式,选择二进制的安装方式是比较灵活的。
Kubernetes版本选型:1.11.7,这个版本也是当时官方提供的最新版本。
网络插件的选型:Flannel,Flannel官方说明会有一定的网络性能损耗,但在我们使用的公有云上可以忽略这一点。随着业务量增加及安全隔离需求,后续会考虑改用Calico,因为发现业务多了的话网络隔离还是很有必要的,另外Canal插件其实也是跟Flannel结合实现网络策略,但是官方对这个项目的维护貌似已经不活跃就不太考虑了。
Service实现的选型:IPVS,早期的Service都是iptables实现的,所以早期我们就用的iptables,但是当集群节点数量到达几千台的话iptables性能应该会有很大的衰减,于是在我们升级到1.11版本后果断使用IPVS的方式实现Service。
周边工具选型:Harbor、Rancher、Prometheus、CoreDNS,以上这几款工具都是云原生周边产物,热度都很高,也能够快速简单的支撑业务。
集群优化的考虑:网络MTU、内核版本、网段优化、swap优化等,在集群落地之前要把一些优化点提前考虑好,内核我们用的是4.18,当时的一个最新版本,高版本支持了cgroup2,隔离性应该会比cgroup1更加稳定。
日志方案的考虑:ELK,ELK一直是我们日志方案解决的工具,所以服务上云之后我们依然沿用,其中Filebeat是以DaemonSet模式运行,性能消耗目前来看还是可以接受的。
监控方案的考虑:Prometheus,随着容器技术的迅速发展,Kubernetes已然成为大家追捧的容器集群管理系统。Prometheus作为生态圈CNCF中的重要一员,其活跃度仅次于Kubernetes,现已广泛用于Kubernetes集群的监控系统中,毋庸置疑监控方面的选型非Prometheus莫属。
CI/CD的方案:自研的运维平台,相信各个公司都有自己的运维平台,我们的CI/CD也是与我们自己的平台结合,有着一套完善的规范和流程,如果规模不大也可以考虑和Rancher的CI/CD结合,感觉也不错。
Namespace的划分考虑,Namespace的划分也比较重要,涉及到业务的隔离与划分,通常要考虑到不同Namespace之间服务的调用问题,内部域名调用问题,安全划分问题,机器分配使用问题等等。
#!/bin/sh
source /etc/profile
nowtime=`date +%Y%m%d%H%M`
# 备份的数据目录
workdir="/data/etcd-bak"
# etcd的数据目录
datadir="/data/etcd"
ep=`/sbin/ip addr|grep eth0|sed -nr 's#^.*inet (.*)/22.*$#1#gp'`
capath="/etc/kubernetes/ssl/ca.pem"
certpath="/etc/etcd/ssl/etcd.pem"
keypath="/etc/etcd/ssl/etcd-key.pem"
etcdctlpath="--endpoints "https://${ep}:2379" --cacert=$capath --cert=$certpath --key=$keypath"
hostname=`hostname`
alertcontent="$hostname-etcd-bak-is-false-please-check-etcd-${nowtime}"
# 备份数据保留天数
delday=7
s3path="s3://etcd/etcd-${ep}"
s3alertcontent="$hostname-etcd-snapshot-to-s3-false-please-check"
etcdctlcmd=`whereis etcdctl|awk '{print $NF}'`
function deloldbak () {
find $workdir -name "etcd-*.gz" -mtime +${delday}|xargs rm -f
}
if [ ! -f /data/etcd-bak/etcd-${nowtime}.tar.gz ];then
mkdir -p /data/etcd-bak/etcd-${nowtime}/
else
echo "need wait to next time"
echo "need wait to next time" >>$workdir/etcd-bak.log
exit 1
fi
echo "=================================== begin $nowtime =================================="
echo "=================================== begin $nowtime ==================================" >>$workdir/etcd-bak.log
export ETCDCTL_API=3
echo "=================================== run snapshoting =================================" >>$workdir/etcd-bak.log
$etcdctlcmd $etcdctlpath snapshot save $workdir/etcd-${nowtime}/snap-${nowtime}.db >>$workdir/etcd-bak.log
echo "=================================== run snapshoting =================================" >>$workdir/etcd-bak.log
if [ $? -eq 0 ]
then
echo "etcd snapshot etcd-${nowtime}/snap-${nowtime}.db is successful"
echo "etcd snapshot etcd-${nowtime}/snap-${nowtime}.db is successful" >>$workdir/etcd-bak.log
else
echo "etcd snapshot etcd-${nowtime}/snap-${nowtime}.db is failed"
echo "etcd snapshot etcd-${nowtime}/snap-${nowtime}.db is failed" >>$workdir/etcd-bak.log
fi
cp -fr $datadir/* $workdir/etcd-${nowtime}/
if [ $? -eq 0 ]
then
echo "etcd snapshot etcd-${nowtime}/member is successful"
echo "etcd snapshot etcd-${nowtime}/member is successful" >>$workdir/etcd-bak.log
else
echo "etcd snapshot etcd-${nowtime}/member is failed"
echo "etcd snapshot etcd-${nowtime}/member is failed" >>$workdir/etcd-bak.log
fi
$etcdctlcmd $etcdctlpath --write-out=table endpoint status
$etcdctlcmd $etcdctlpath --write-out=table endpoint status >>$workdir/etcd-bak.log
cd $workdir
tar zcf ./etcd-${nowtime}.tar.gz etcd-${nowtime}
rm -fr etcd-${nowtime}
aws s3 cp $workdir/etcd-${nowtime}.tar.gz $s3path/
if [ $? -eq 0 ]
then
echo "etcd snapshot s3 is successful"
echo "etcd snapshot s3 is successful" >>$workdir/etcd-bak.log
else
echo "etcd snapshot s3 is failed"
echo "etcd snapshot s3 is failed" >>$workdir/etcd-bak.log
fi
deloldbak
echo "=================================== end `date +%Y%m%d%H%M%S` =================================="
echo "=================================== end `date +%Y%m%d%H%M%S` ==================================" >>$workdir/etcd-bak.log
#!/bin/bash
# 使用etcdctl snapshot restore生成各个节点的数据
# 比较关键的变量是
# --data-dir 需要是实际etcd运行时的数据目录
# --name --initial-advertise-peer-urls 需要用各个节点的配置
# --initial-cluster initial-cluster-token 需要和原集群一致
# 注意http和https区别
# 无需更改
workdir=/root
# etcd1,2,3为节点名称 ETCD1,2,3为对应节点ip
ETCD_1=1.1.1.1
ETCD_2=2.2.2.2
ETCD_3=3.3.3.3
etcd1=etcd1
etcd2=etcd2
etcd3=etcd3
# 同上面一样需要对应设置
arra=(1.1.1.1 2.2.2.2 3.3.3.3)
arrb=(etcd1 etcd2 etcd3)
# etcd是否使用https tls加密如果使用需要配置证书,若是http请置空此变量
etcdkey="--cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem"
# 恢复数据存放目录,只是用于恢复存放数据,可以随意设置,跟原有的路径没有关系
etcddatapath="/root/etcd-recover-data/etcd"
# 备份数据根路径
bakdatapath="/data/etcd-bak"
# 备份数据完整路径
bakdbpath="$bakdatapath/etcd-201906161945/snap-201906161945.db"
# ansible site执行路径
ansiblepath="/root/etcd-bak-ansible"
function ansibleoperate ()
{
rm -fr $ansiblepath/roles/etcd-bak-ansible/files/*
cp -fr $(echo $etcddatapath|awk -F "[/]" '{print "/"$2"/"$3}')/* $ansiblepath/roles/etcd-bak-ansible/files/
cd $ansiblepath
ansible-playbook -i hosts site.yaml
}
if [ ! -d $(echo $etcddatapath|awk -F "[/]" '{print "/"$2"/"$3}') ];then
mkdir -p $(echo $etcddatapath|awk -F "[/]" '{print "/"$2"/"$3}')
fi
for i in ${arra[@]}
do
echo -e " $ic" >>$workdir/etcdiplist.log
#echo -e "$i"
done
for i in ${arrb[@]}
do
echo -e " $ic" >>$workdir/etcdnamelist.log
#echo -e "$i"
done
while true
do
let cnt++
etcdiplist=`awk -v column=$cnt '{print $column}' $workdir/etcdiplist.log`
etcdnamelist=`awk -v column=$cnt '{print $column}' $workdir/etcdnamelist.log`
if [ "$etcdiplist" = "" ]
then
echo "conf is down will to break"
break
fi
echo $etcdiplist $etcdnamelist
export ETCDCTL_API=3
# 如果用原有member中的db恢复,由于不存在完整的hash性,需要在下面添加 --skip-hash-check 跳过hash检查
etcdctl snapshot $etcdkey restore $bakdbpath
--data-dir=$etcddatapath
--name $etcdnamelist
--initial-cluster ${etcd1}=https://${ETCD_1}:2380,${etcd2}=https://${ETCD_2}:2380,${etcd3}=https://${ETCD_3}:2380
--initial-cluster-token etcd-cluster-0
--initial-advertise-peer-urls https://$etcdiplist:2380 &&
mv $etcddatapath $(echo $etcddatapath|awk -F "[/]" '{print "/"$2"/"$3}')/etcd_$etcdiplist
echo "--initial-cluster ${etcd1}=https://${ETCD_1}:2380,${etcd2}=https://${ETCD_2}:2380,${etcd3}=https://${ETCD_3}:2380 "
done
rm -f $workdir/etcdiplist.log
rm -f $workdir/etcdnamelist.log
#如果不需要Ansible自动恢复集群,需要手动恢复的话请注释以下操作
ansibleoperate
存储支持S3:镜像从保存在本地改为存储到S3,这样可以节省存储费用,也不用考虑本地磁盘扩容的事情。
登录接入LDAP:规范管理用户,并且可以使用LDAP的组进行用户的划分。
用户限制,用户分组:这块主要是结合LDAP组进行划分,在Harbor中建好各个对应的项目,配置开发权限。
漏洞扫描:每天定时扫描镜像,不过想保证所有镜像都不存在危险貌似不太容易,开发自定义等不规范镜像会经常出现。
镜像签名:安全度高的镜像会考虑开启该功能。
高可用负载:官方提供了HA的功能,主要就是访问的HA,数据库的HA,存储的HA。
证书认证:访问通过https,更加安全。
数据库更加简单:原来是有mysql、PostgreSQL,现在新版本全部统一使用PostgreSQL,管理更加方便。
cd harbor
docker-compose down
mv harbor /my_backup_dir/harbor
cp -r /data/database /my_backup_dir/
docker pull goharbor/harbor-migrator:[tag]
docker run -it --rm -v ${harbor_cfg}:/harbor-migration/harbor-cfg/harbor.cfg -v ${harbor_yml}:/harbor-migration/harbor-cfg-out/harbor.yml goharbor/harbor-migrator:[tag] --cfg up
docker run -it --rm -v ${harbor_cfg}:/harbor-migration/harbor-cfg/harbor.cfg goharbor/harbor-migrator:[tag] --cfg up
tar -zxvf harbor-offline-installer-v1.7.4.tgz
cd harbor
mv harbor.cfg harbor.bak
cp /root/harbor-bak/harbor.cfg .
./install.sh --with-notary --with-clair --with-chartmuseum
docker-compose -f ./docker-compose.yml -f ./docker-compose.clair.yml ps
docker images|grep 1.6.2| awk '{print $3}'|xargs docker rmi
prometheus.io/scrape: "true"
Service Mesh使用:Istio调研及使用(灰度、限流、链路跟踪、熔断、降级)
LXCFS解决容器CPU、内存资源可见性问题
有状态应用部署至Kubernetes:ES、Kafka等
CRD在prometheus-operator的应用
A:一天一个节点200G的话,这个要看你们集群节点多不多,我们上百个节点,一个节点的量大概在100G左右,线上日志量都是几十T的数据,用我分享的方案去落地应该是没问题的,ELK的整体性能还是非常不错的,Filebeat现在最高的性能分配是占用500M CPU,1G内存,收集起来也是能应对的,这个根据你的量再调整,监控的话肯定就用Prometheus就好,官方都是有自动发现的配置,很便利,当然如果你要对日志进行分析,这块就比较复杂了,可以通过ES接口去聚合数据,当然日志的字段也要规范好。
A:我们用的是Flannel,不过后续会考虑打算换成Calico,现在发现线上有一定网络限制的需求,Calico的性能相对也会更好,但是维护的复杂度比较高,在集群节点多的情况下,要添加路由反射器,这个比较关键,而且网络选型前一定对未来的规模有个规划,规划好使用的网段。
A:建议备份,其实主要用到的就是etcd的snapshot功能,不复杂,看下我分享的脚本即可,扩容etcd节点有证书的话需要修改server端证书host对应的机器,官方的方法是要一台一台执行的,线上etcd节点我做过测试,即使操作失误都down掉的话也不会影响你现有服务的运行,而且保证法定节点的存在就更好。
A:Prometheus没有用Operator的方式,是用的官方的yaml文件创建的,我们线上Java服务居多,都是通过Spring官方的Prometheus插件就可以自定义监控数据,Nginx的话我们还真的不多,这个估计你要用相应的exporter就好,监控数据是开发自定义上传的,我们没有做限制。
A:我们这边是这样,对于健康检查没有添加Liveness的检查,也是防止容器的重启,尤其是在第一次项目上线,难免无法正常启动,如果加了Liveness就会一直重启,不太方便查问题,所以只加了Readiness,只是保证不影响线上访问,对于生产中,Java项目遇到最多的就是OOM问题,这个我们也对Pod重启的原因加了报警,至于Dump我们还没这方面的操作,需要开发自行检查了。
A:这个问题有点宽泛呢,还是得看您这边实际的场景,我觉得更多的也是得需要开发一起配合的,尽量保证服务模块都能够做到微服务话,不要耦合的太紧,您可以先搭建一个测试集群,然后从开发那边找一个模块进行Docker化的转换,然后一点一点再去试吧。
A:我们没有用Ingress,我们的用法也算是一种比较简单的用法,我们是把网关直接加入到Kubernetes集群中,这样网关就可以调用到Kubernetes的Service,因为我们以网关为中心,做了一些安全及认证功能,所以之前的网关必须要用到,而且加了Ingress相当于多加了一层性能消耗,所以也没有用,最后我们把之前部署在虚拟机上的网关也变成Docker化去部署到集群内部。
A:我们集群还没有跑数据库这种有状态的服务,但是听您描述,还是得看看Pod重启的具体原因,如果Pod都重启了,理论上跑在机器上一定也会有问题,还是在上云之前做好充分的性能压测,并且您可以考虑取消Liveness的健康检查,只保留Readness访问的检查。
A:首先不建议通过Docker目录的方式采集,日志还是落盘到具体路径为好,因为我也碰到过您这个困惑,因为Docker的目录都是软链接,而且当Docker重启后路径会改变,当然我们线上用的是Filebeat采集,不知道Fluentd能不能解决这个问题,由于是软链接,很难用相对路径,必须用绝对路径采集到真正存放的那个目录日志,我们对于es index名称的创建是通过日志提供的一个index名称字段去匹配的,索引名称会引用这个变量进行区分不同的index。
A:您这个情况我们也遇到过,多个Pod跑在同一个节点确实存在这个问题,因为你的deploy yaml是定死的,很难处理这种情况,我们的解决方法是添加Pod的亲和性,保证每个节点都尽量只跑一个Pod,当然如果节点非常小的情况下,这种做法有一定问题,以生产使用上来看,我们最初多个Pod跑在一个节点同时写一个文件的话也是还可接受。
A:对的,部署在一个Pod里,相当于你的deploy yaml里会有两个image配置,一个是你的服务,另一个是Filebeat,具体的配置看下我的截图,把这部分配置放到你的服务配置后面即可,但是就像我分享说的,这种方式可能会比较消耗资源,但是确实自定义比较方便,但也增加了yaml配置。
A:其实你需要在Docker中注意一个参数,live-restore : true,这个参数很有用,你可能没有添加,这个参数能保证在你维护重启Docker的时候,还能保证不影响正在运行的Docker容器,另外你可以对Harbor进行监控,如果down了的话大不了做个自动重启的操作也不妨大碍。
A:1. 因为我不是测试,对于测试这块可能干涉的不是很多,对于运维来讲可能更多的是比较关注上线之前的压力测试,这块会跟后续的稳定性有很大关系;2. 常见的架构业务理论上改造应该不需要很大,最主要的是解决Docker镜像化,遇到的坑可能更多的是对于Dockerfile打镜像理解的不好,导致一些启动问题以及配置的丢失等等;3. 我们是通过Namespace区分业务线,需要提前规划好业务,指定的业务线只跑在对应的机器上比较关键;4. 我使用的ELK过程中还真的很少遇到过丢日志,只要你架构足够健壮应该是没什么问题的,另外ELK中一定要用消息队列,降低你消息传递的压力,保证每个组件都不要出现性能瓶颈,如果实在怕丢日志,可以让Logstash在消费的时候把消息落盘,ES也要合理配置好刷新的频率以及内存多大落盘等参数,提前做好各个组件的压测是保障。
A:您好,我们team在北京,因为我们的集群还未上有状态服务,所以暂时还未考虑分布式存储的问题,这块确实是很重要的一个环节,我们线上的服务基本也是通过S3去存储一些数据使用,Portworx这个好像也出了很久了,当时在还没有Kubernetes的时候调研过,不过想大面积使用貌似是要花钱用商业版,建议还是用现在比较流行的Ceph可能会更好一些吧,我们还未用到AWS自己的Kubernetes服务,ES有运行在Kubernetes里的业务,但是不是通过Operator部署,后端数据也没用到分布式存储,由于量不大,只是落在本地了,后期会进一步调研Ceph以支持后期的有状态服务的迁移。
A:因为在虚拟机的时候我们就用的Filebeat,就沿用下去了,Filebeat暂时还未发现性能问题,可以直接使用,总日志量我们应该有几十T的样子,在生产使用的过程中感觉Filebeat还是比较靠谱的。
A:Kubernetes的证书我们的时间都设置的是十年,kubelet可能是一年,这个我们最初疏忽了,碰到过一次,最终通过删除现有的配置,让kubelet重启自动生成,当然如果您是最初规划的话,可以加上证书自动到期认证的功能,据了解好像现在高版本的Kubernetes已经不存在这个问题,我还没了解的那么多,您可以再查查。
A:安全的话这个比较宽泛啊,这个还是要从各个方面完善,首先起码要保证流量流入方向的各个环节的安全限制,以及服务接口调用上的安全认证,以及开发人员使用时候的权限控制等等。
A:容器端口的连接数监控确实还未添加,在原来宿主机的时候是有的,这块有些忽略了,加的话也不是很费劲,可以通过你们自己自定义的exporter去监控。
A:落盘的日志通过写好的清理任务进行清理,因为我们的日志都是规范的落到统一的目录,并且目录名称也是很规范的,所以清理起来很方便,写个简单的脚本就可以啦,定时清理就OK。
A:我们是在yaml注入了能获取设置资源的env参数,然后在CI打镜像的时候统一规范了服务启动的start脚本,JVM里配置的是Kubernetes配置的资源,所以Java服务的使用也不会超过我们分配资源的使用。
以上是关于蔚来汽车的Kubernetes实践的主要内容,如果未能解决你的问题,请参考以下文章