【编者的话】Kubernetes已经成为当下最火热的一门技术,未来一定也会有更好的发展,围绕着云原生的周边产物也越来越多,使得上云更加便利更加有意义,本文主要讲解一些蔚来汽车从传统应用落地到Kubernetes集群的一些实践经验,提供给大家在落地之前的一些思考和注意点,并且让大家在实施的时候能够有一些借鉴,提供一些使用过程中的注意事项。
Docker诞生于2013年初,随着时间的推移Docker项目也逐渐火热起来,也形成了自己的生态,为了能够灵活调度容器,编排技术也变得非常重要,Swarm,Mesos,Kubernetes属于比较出众的容器编排技术,然而不得不说Kubernetes绝对是当下最火热的技术。Kubernetes项目的基础特性,并不是几个工程师突然“拍脑袋”想出来的东西,而是Google公司在容器化基础设施领域多年来实践经验的沉淀与升华,容器编排使得容器本身也有了生命,几乎所有大公司都在使用Kubernetes,另外云原生周边也发展的越来越好,这使得Kubernetes的功能越来越强大。
我们从2017年开始调研Kubernetes,2018年开始小规模使用,当时使用的是1.9版本,现在用的是1.11版本,最新已经到了1.16,可谓版本更新之快,未来会考虑再升级到最新版本。
Kubernetes给我们带来的一些好处:
建设落地前的思考
在落地之前我们对以下一些点做了相应的考量,做好提前的选型及测试。
其实Kubernetes的数据也就是etcd中的数据,那么我们日常就需要备份etcd中的数据,以及考虑数据的恢复,理论上etcd的数据恢复场景应该很难用到,一切操作尽量都通过平台规范化使用,但是针对灾难性的场景就可以用etcd数据备份来救场了。
这里给大家展示下etcd备份和恢复,具体细节大家根据自己环境再调整。
etcd数据备份
#!/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
etcd数据恢复
#!/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 "\t$i\c" >>$workdir/etcdiplist.log #echo -e "$i" done for i in ${arrb[@]} do echo -e "\t$i\c" >>$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
镜像仓库我们选择的是Harbor,比较成熟并且社区维护更新也非常快。我们最早使用1.2版本,现在使用1.6版本。目前Harbor最新是1.8版本,下面给大家展示下新旧版本功能上的差异,新版本还是有很多好功能的。
1.2版本和1.6版本功能对比
升级后具备的优势
线上镜像仓库架构图
上面是我们的架构图,通过DNS解析将不同环境的访问分流到对应环境的镜像仓库,所有的镜像仓库都连接着后端数据库集群,镜像统一保存到了S3中,对于仓库的扩容也是十分方便的,拷贝一份配置启动就可以了。
镜像仓库升级
备份之前先停止老的Harbor:
cd harbor docker-compose down
备份原来的Harbor目录:
mv harbor /my_backup_dir/harbor
备份数据库:
cp -r /data/database /my_backup_dir/
后续升级镜像下载:
docker pull goharbor/harbor-migrator:[tag]
升级harbor.cfg或者harbor.yml文件:
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
如果没有yaml文件,低版本理论上只有cfg文件,那就升级cfg文件就可以了:
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
覆盖harbor.cfg,把之前升级的harbor.cfg文件或者yml文件拷贝到新版本解压的目录里替换相应的文件:
cd harbor mv harbor.cfg harbor.bak cp /root/harbor-bak/harbor.cfg .
安装Notary,Clair和Helm Chart服务,安装之前可以perpare脚本生成下配置文件:
./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
日志落盘, DaemonSet部署Filebeat,采集进Kafka,最终进ES,落盘日志定期清理。
这个是基于Node级别的一种日志收集方案,我们大多使用这种方案进行日志的收集,优点就是可以和服务完全解耦,性能好,便于管理。
这种方式的一个弊端就是日志格式必须按照约定的方式配置输出,对不能满足约定格式的情况改用sidecar的方式进行日志的收集。
这种方式就是扩展性强,配置可以跟随服务进行制定的收集,弊端就是会浪费资源,并且有一定耦合性,所以我们线上并未大面积采取这个方式的使用。
Prometheus想必在大家的公司中都已经在使用了,是监控领域里现在最火热的一款监控工具,在这里也不多赘述了,基本的使用大家应该都比较了解,下面主要简单展示下我们线上对于服务应用是如何上报监控,配置也并不复杂,大家看图解读。
Service里面增加配置:
prometheus.io/scrape: "true"
是否监控就看是否在Service中开启了这个开关。
以下是Prometheus配置过滤规则:
Q:请问Kubernetes数据备份与恢复,这个是包括kubectl-proxy,etcd,网络配置等吗?如何进行多集群下的数据备份?
A:备份的其实就是etcd中的数据,我们网络是用的Flannel,一些关键网段信息也是存在etcd中的,对于多集群来看的话,还是要针对数据存放对应的etcd集群数据去进行备份。
Q:请问一下业务日志太多,一天一个节点的业务日志有200G,怎么做日志收集及监控告警:
A:一天一个节点200G的话,这个要看你们集群节点多不多,我们上百个节点,一个节点的量大概在100G左右,线上日志量都是几十T的数据,用我分享的方案去落地应该是没问题的,ELK的整体性能还是非常不错的,Filebeat现在最高的性能分配是占用500M CPU,1G内存,收集起来也是能应对的,这个根据你的量再调整,监控的话肯定就用Prometheus就好,官方都是有自动发现的配置,很便利,当然如果你要对日志进行分析,这块就比较复杂了,可以通过ES接口去聚合数据,当然日志的字段也要规范好。
Q:生产环境Kubernetes采用二进制方式搭建还是kubeadm ,还是其他方案?
A:线上采用的是二进制的方法,因为我们上Kubernetes的时候kubeadm还是测试版本,当然现在高版本的kubeadm应该已经是正式版本,但是觉得还是二进制更方便一些,你改配置,以及自定义一些参数会方便一些。
Q:生产环境Kubernetes都用哪种网络模式?
A:我们用的是Flannel,不过后续会考虑打算换成Calico,现在发现线上有一定网络限制的需求,Calico的性能相对也会更好,但是维护的复杂度比较高,在集群节点多的情况下,要添加路由反射器,这个比较关键,而且网络选型前一定对未来的规模有个规划,规划好使用的网段。
Q:请问生产环境中etcd的数据需要备份吗?怎么备份?还有二进制安装etcd集群由3个节点增加到5个节点,需要重新生etcd证书后再重启etcd服务吗?增加节点重启策略是一个一个节点依次重启吗?
A:建议备份,其实主要用到的就是etcd的snapshot功能,不复杂,看下我分享的脚本即可,添加节点用你现有的证书就好,官方的方法是要一台一台执行的,线上etcd节点我做过测试,即使操作失误都down掉的话也不会影响你现有服务的运行,而且保证法定节点的存在就更好。
Q:你分享的Prometheus是Operator方式吗?你的监控数据是有经过二次开发后作为标准格式输出吗?对于Nginx和Java监控如何实现呀?
A:Prometheus没有用Operator的方式,是用的官方的yaml文件创建的,我们线上Java服务居多,都是通过Spring官方的Prometheus插件就可以自定义监控数据,Nginx的话我们还真的不多,这个估计你要用相应的exporter就好,监控数据是开发自定义上传的,我们没有做限制。
Q:Pod挂掉之后如何保留现场,比如做内存Dump有什么好的方案没?
A:我们这边是这样,对于健康检查没有添加Liveness的检查,也是防止容器的重启,尤其是在第一次项目上线,难免无法正常启动,如果加了Liveness就会一直重启,不太方便查问题,所以只加了Readiness,只是保证不影响线上访问,对于生产中,Java项目遇到最多的就是OOM问题,这个我们也对Pod重启的原因加了报警,至于Dump我们还没这方面的操作,需要开发自行检查了。
Q:传统系统架构如何改造成Kubernetes架构?
A:这个问题有点宽泛呢,还是得看您这边实际的场景,我觉得更多的也是得需要开发一起配合的,尽量保证服务模块都能够做到微服务话,不要耦合的太紧,您可以先搭建一个测试集群,然后从开发那边找一个模块进行Docker化的转换,然后一点一点再去试吧。
Q:是否有Ingress tcp/udp应用的生产级网络方案?
A:我们没有用Ingress,我们的用法也算是一种比较简单的用法,我们是把网关直接加入到Kubernetes集群中,这样网关就可以调用到Kubernetes的Service,因为我们以网关为中心,做了一些安全及认证功能,所以之前的网关必须要用到,而且加了Ingress相当于多加了一层性能消耗,所以也没有用,最后我们把之前部署在虚拟机上的网关也变成Docker化去部署到集群内部。
Q:传统数据库负载过高时查询缓慢,但是会有结果,Kubernetes架构数据库负载过高直接Pod重启,导致没有结果返回,请问应该如何处理的?
A:我们集群还没有跑数据库这种有状态的服务,但是听您描述,还是得看看Pod重启的具体原因,如果Pod都重启了,理论上跑在机器上一定也会有问题,还是在上云之前做好充分的性能压测,并且您可以考虑取消Liveness的健康检查,只保留Readness访问的检查。
Q:采集日志过程中,fluentd或fluent bit通过读取Node节点docker log目录采集日志,该目录包含了所有服务的日志。请问如何在output阶段中根据Namespace和container_name创建Elasticsearch的index,并作为index的命名前缀?
A:首先不建议通过Docker目录的方式采集,日志还是落盘到具体路径为好,因为我也碰到过您这个困惑,因为Docker的目录都是软链接,而且当Docker重启后路径会改变,当然我们线上用的是Filebeat采集,不知道Fluentd能不能解决这个问题,由于是软链接,很难用相对路径,必须用绝对路径采集到真正存放的那个目录日志,我们对于es index名称的创建是通过日志提供的一个index名称字段去匹配的,索引名称会引用这个变量进行区分不同的index。
Q:fileneat node模式采集,多个相同Pod在同一节点情况下,如何映射日志目录而不互相干扰,另外如何配置Filebeat做到Pod变动时的采集?
A:您这个情况我们也遇到过,多个Pod跑在同一个节点确实存在这个问题,因为你的deploy yaml是定死的,很难处理这种情况,我们的解决方法是添加Pod的亲和性,保证每个节点都尽量只跑一个Pod,当然如果节点非常小的情况下,这种做法有一定问题,以生产使用上来看,我们最初多个Pod跑在一个节点同时写一个文件的话也是还可接受。
Q:持续集成系统具体的细节可以透露下吗?基于GitLab CI还是Jenkins?或者小公司可以直接用Spinnaker这些吗?
A:CI/CD的话因为我们有自己现有的发布平台,背后的原理实际上还是调用Jenkins去处理。
Q:日志收集的sidectar模式具体是咋部署的。Filebeat和应用部署在一个Pod里吗?
A:对的,部署在一个Pod里,相当于你的deploy yaml里会有两个image配置,一个是你的服务,另一个是Filebeat,具体的配置看下我的截图,把这部分配置放到你的服务配置后面即可,但是就像我分享说的,这种方式可能会比较消耗资源,但是确实自定义比较方便,但也增加了yaml配置。
Q:我司测试环境搭建的Harbor版本是1.5,使用docker-compose来按照Harbor各个组件的依赖顺序来启动的,但是当系统或者Docker重启后,Harbor的容器就无法按照依赖顺序来启动,经常会有容器启动失败。请问下这个该如何优化呢?
A:其实你需要在Docker中注意一个参数,live-restore : true,这个参数很有用,你可能没有添加,这个参数能保证在你维护重启Docker的时候,还能保证不影响正在运行的Docker容器,另外你可以对Harbor进行监控,如果down了的话大不了做个自动重启的操作也不妨大碍。
Q:(1)Kubernetes平台上线前有什么测试,如何测试?可以上线的依据?(2)常见互联网架构的业务,需要改造才可以在Kubernetes跑吗,需要如何改造?有什么坑?(3)你们多个业务线都共用同一套Kubernetes?如何实现不会因为一个业务的高峰影响其他业务?(4)有什么方案可以实现最大限度的不丢日志?
A:1. 因为我不是测试,对于测试这块可能干涉的不是很多,对于运维来讲可能更多的是比较关注上线之前的压力测试,这块会跟后续的稳定性有很大关系;2. 常见的架构业务理论上改造应该不需要很大,最主要的是解决Docker镜像化,遇到的坑可能更多的是对于Dockerfile打镜像理解的不好,导致一些启动问题以及配置的丢失等等;3. 我们是通过Namespace区分业务线,需要提前规划好业务,指定的业务线只跑在对应的机器上比较关键;4. 我使用的ELK过程中还真的很少遇到过丢日志,只要你架构足够健壮应该是没什么问题的,另外ELK中一定要用消息队列,降低你消息传递的压力,保证每个组件都不要出现性能瓶颈,如果实在怕丢日志,可以让Logstash在消费的时候把消息落盘,ES也要合理配置好刷新的频率以及内存多大落盘等参数,提前做好各个组件的压测是保障。
Q:你好,我是蔚来ES8车主,很高兴看到蔚来的分享。我想了解下你们存储的方案,之前听说是用的Portworx,具体方案可以透露一下么?你们这个team在北京还是上海? 用AWS的话有没有考虑直接使用AWS的Kubernetes服务?ES也运行在Kubernetes里吗?
A:您好,我们team在北京,因为我们的集群还未上有状态服务,所以暂时还未考虑分布式存储的问题,这块确实是很重要的一个环节,我们线上的服务基本也是通过S3去存储一些数据使用,Portworx这个好像也出了很久了,当时在还没有Kubernetes的时候调研过,不过想大面积使用貌似是要花钱用商业版,建议还是用现在比较流行的Ceph可能会更好一些吧,我们还未用到AWS自己的Kubernetes服务,ES有运行在Kubernetes里的业务,但是不是通过Operator部署,后端数据也没用到分布式存储,由于量不大,只是落在本地了,后期会进一步调研Ceph以支持后期的有状态服务的迁移。
Q:请问是否考虑过fluent-bit ,目前Filebeat有没有性能上的问题呢?
A:因为在虚拟机的时候我们就用的Filebeat,就沿用下去了,Filebeat暂时还未发现性能问题,可以直接使用,总日志量我们应该有几十T的样子,在生产使用的过程中感觉Filebeat还是比较靠谱的。
Q:Kubernetes一年的证书问题,你们怎么解决的呢?
A:Kubernetes的证书我们的时间都设置的是十年,kubelet可能是一年,这个我们最初疏忽了,碰到过一次,最终通过删除现有的配置,让kubelet重启自动生成,当然如果您是最初规划的话,可以加上证书自动到期认证的功能,据了解好像现在高版本的Kubernetes已经不存在这个问题,我还没了解的那么多,您可以再查查。
Q:Kubernetes生产环境上的安全相关的配置有哪些呢?
A:安全的话这个比较宽泛啊,这个还是要从各个方面完善,首先起码要保证流量流入方向的各个环节的安全限制,以及服务接口调用上的安全认证,以及开发人员使用时候的权限控制等等。
Q:Prometheus自定义监控具体怎么搞得,比如想要监控容器的端口连接数?
A:容器端口的连接数监控确实还未添加,在原来宿主机的时候是有的,这块有些忽略了,加的话也不是很费劲,可以通过你们自己自定义的exporter去监控。
Q:落盘的日志怎么定期清理?
A:落盘的日志通过写好的清理任务进行清理,因为我们的日志都是规范的落到统一的目录,并且目录名称也是很规范的,所以清理起来很方便,写个简单的脚本就可以啦,定时清理就OK。
Q:Kubernetes里Java服务,你们是怎么做资源限制的?
A:我们是在yaml注入了能获取设置资源的env参数,然后在CI打镜像的时候统一规范了服务启动的start脚本,JVM里配置的是Kubernetes配置的资源,所以Java服务的使用也不会超过我们分配资源的使用。
以上内容根据2019年10月10日晚微信群分享内容整理。 分享人朱嘉骏,蔚来汽车运维工程师,负责Kubernetes的推广及应用维护,推进企业从传统应用迁移到Kubernetes,解决运维基础建设与云结合中遇到的问题。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiese,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。