张兴/Bryan 分布式实验室
中国电信翼支付一直致力于将容器及云原生技术在生产环境中落地,同时AI是目前互联网行业炙手可热的“明星”,无论是老牌巨头,还是流量新贵,都在大力研发AI技术,为自家的业务赋能,其中算法从调研到最终上线发挥作用,是需要有一系列的工程开发和对接的,由此引发了新的问题:如何界定算法和工程的边界,各司其职,各善其长?如何提升算法迭代上线的速度和效率?如何快速准确评估算法的效果?而如果要解决上述的机器学习问题,则需要有一个功能强大且易用的机器学习平台来辅助算法研发人员,因此我们将Kubernetes结合AI场景研发打造出了属于自己的AIPaas平台,在平台层实现了资源的动态申请、扩缩容、灰度发布、流量控制等诸多功能;Kubernetes作为云原生的核心,帮助传统企业解决了很多痛点,但也因此给企业带来了很多新的挑战,本文为大家分享翼支付大数据部在AI方向上落地实践Kubernetes过程中的一些经验和探索,希望可以帮助到大家。
简要介绍AI平台
概述
基于基础的机器学习和深度学习计算框架进行二次开发,提供一站式的生态化的服务,为用户提供从数据预处理、模型训练、特征工程、模型训练、模型部署、模型治理和模型在线预测的全流程开发和部署支持,以期降低业务人员、算法同学的使用门槛,提供端到端的一站式的服务,帮助他们脱离繁琐的工程化开发,把有限的精力聚焦于算法策略的迭代上面。
可视化建模
支持超大规模数据分布式机器学习,内置丰富的算法组件,支持从数据预处理,特征工程,模型训练到模型服务全流程零代码开发,极大的降低了机器学习算法的使用门槛。
自助建模
为算法开发者提供交互式的开发环境,包含经典机器学习和多种深度学习框架,高性能的、异构计算(CPU/GPU)引擎支持,弹性资源调度与资源使用情况的可视化监控等,极大的提高了AI应用的开发效率。
模型服务
通过在线模型服务功能可以将模型结果一键部署成Restful API,将生成的模型与用户自身业务打通。而离线模型服务功能可以将模型结果进行封装,用于创建离线调度任务。
AI应用
结合各类具体的业务场景(如合同识别、身份证识别、银行卡识别等),将主流的机器学习算法进行产品化,用户可以自主体验和使用,大大降低了AI应用的落地门槛。
背景介绍:为什么需要Kubernetes
大数据部在使用Kubernetes之前,所有应用采用的是传统IDC机房架构,依赖IDC机房的物理机和vm节点提供服务,当大促或活动时,则需要增加物理机或者VM实现扩展。
从第1部分内容,我们大概能推断出平台所需要的主要特性,包括服务实例动态扩缩容、RBD动态按需申请、灰度发布、流量控制等等,而这些如果是基于传统的技术栈来进行开发,则会涉及到比较多的底层架构的设计以及大量与业务不相干的模块的研发。
此外,随着平台的发展,业务复杂度必然越来越大,生产环境所使用的开发语言也越来越多,技术栈和系统环境依赖也越来越复杂,不仅是管理成本的上升、单个服务的资源控制粗放问题,同时还有由于线上线下开发测试生产环境不一致等问题造成的服务不可用的情况,随之而来的排错过程成本也越来越高,如果继续基于之前传统架构思路来进行研发,则将逐渐变得不可控,因此我们决定引入容器及Kubernetes,通过技术预研,我们发现Kubernetes容器方案具有如线上线下环境一致、细粒度资源管理、轻巧灵活、自动扩展、异常自动恢复等很多优势,可以帮助解决一些关键痛点。
虽然Kubernetes有明显的优势,但其技术起点较高、技术栈多、选择难度大,且稳定性以及性能是否能满足业务需求,需要我们进行实际的测试验证;此外还要考虑如何兼容现有生产环境,实现并行平滑过渡,考虑Kubernetes网络和现有传统网络的兼容,尽量复用现有的生产网络结构;其次尽量减少开发修改服务代码;考虑Kubernetes服务本身的高可用、日志管理、监控、可视化等等,最后形成一套行之有效的方案。
Kubernetes技术栈选择
Kubernetes技术栈非常多, 下面是引用一张互联网上的图:
根据实际业务需求,我们采用了上图中绝大部分的技术栈,包括负载均衡Traefik、镜像仓库Harbor、监控告警Metricserver+Prometheus+Alertmanager、日志管理EFK、UI管理Dashboard、网络插件Calico、分布式存储Ceph等等很多组件,根据实际情况,我们也引入了Service Mesh Istio等云原生领域其他重要组件,此次只涉及讨论Kubernetes常用的组件。
AIPaas组件使用架构图大致如下:
那些年踩过的坑
为尽量满足对公司基础设施架构不造成影响的这个前提,在整个Kubernetes落地实施过程中,大数据部遇到了不少的挑战,比如像多网卡、rook-ceph中S3的rgw多实例网关问题、Istio response 500、pod shm不足、Pod长时间containerCreating、istio-mesh metric MaxpirationPolicy、Kube-proxy的ipvs模式的terminating bug等等问题,通过这个过程,收获了不少经验和心得,在这里我们摘取其中部分案例分享给大家,希望可以给大家带来一些启发。
The 1st issue:关于Docker的No_Proxy对CIDR的不支持
当我们在隔离网络内拉取公共镜像时,我们需要设置Proxy拉取镜像,大家常用的操作就是set Docker proxy[1]。
而这个在早期的版本也就是在docker-ce-18.09.04版本以前Noproxy是不支持的CIDR的,这就导致每次在隔离网络内新增节点时,需要我们手动修改NOproxy的IP列表并重启Docker才能生效,这个动作其实是很不友好,影响比较大的,因此建议大家在选择Docker版本时尽量选择18.09之后的版本,详情请见Docker No_proxy bug[2]。
The 2nd issue:Calico的访问控制与Docker的NAT冲突问题
这个问题发生的场景是在Node上部署了独立于Kubernetes集群的其他容器服务以及配置了Calico Network Policy。
在容器化应用过程中,尤其是测试阶段我们难免会先使用Docker单独启动一个容器来测试镜像及服务的可用性,这个时候如果我们同时在Kubernetes中设置了有关NetworkPolicy,而访问该容器接口的Pod所在的节点和容器服务在同一节点时,就会触发冲突问题。
首先,NetworkPolicy的配置如下:
kind: NetworkPolicyapiVersion: networking.k8s.io/v1metadata: name: default-deny namespace: testspec: podSelector: matchLabels: app: nginx1-7 policyTypes: - Egress egress: - to: - ipBlock: cidr: 10.11.22.0/24
以上配置意思就是:限制label为app=nginx1-7的pod只允许访问10.11.22网段(这是物理网络的IP网段)的服务,假设某一时刻,label为app=nginx1-7的Pod调度在10.11.22.1的物理节点上,而正好这时在10.11.22.1这台物理机上单独启动了端口为55557一个Docker服务给label为app=nginx1-7的Pod测试调用:
docker run -p 55557:80 nginx
此时在节点上会针对访问该容器生成一个NAT,即:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 55557 -j DNAT --to-destination 172.17.0.4:80
看到这里了解过iptables的基本就知道,上面是将凡是不通过docker0网卡的过来的,访问55557端口时,目标地址会进行转化,因此实际上Pod得到的目的地址变成了172.17.0.4,这个时候就会触发Calico的NetworkPolicy,从而导致网络不通。
The 3rd issue:Kubernetes的垃圾回收导致Orphaned Pod问题
这个问题比较大的概率发生是在不恰当的使用Kubernetes的client的时候,不管是官方的client还是其中比较受欢迎的第三方的fabric8io/Kubernetes-client,都有可能导致这个发生,而要弄清楚的这个问题,就不得不提到Kubernetes的API版本迭代,此处以deployment资源为例,关于Kubernetes deployment的API版本,从早期的extensions/v1beta1到apps/v1beta1、apps/v1beta2再到现在的apps/v1,下面是关于它们的一些简单介绍:
extensions/v1beta1:在1.6版本时deployment等资源放在该版本中,后迁入到apps/v1beta2,再到apps/v1进行了统一管理。
apps/v1beta1、apps/v1beta2:Kubernetes1.8版本中,新增加了apps/v1beta2的概念,apps/v1beta1同理,DaemonSet,Deployment,ReplicaSet 和 StatefulSet的当时版本迁入apps/v1beta2,兼容原有的extensions/v1beta1。
apps/v1:Kubernetes 1.9版本中,引入apps/v1,deployment等资源,原来的v1beta1等被废弃;apps/v1包含一些通用的应用层的api组合,如:Deployments, RollingUpdates, and ReplicaSets。
之所以说到这些,是因为触发这个问题发生的其中一个场景,就是在我们使用extensions/v1beta1的时候,不管是官方的client还是fabric8io/Kubernetes-client,在调用API删除deployment资源时,默认使用策略"propagationPolicy":"Orphan",导致当我们删除deployment资源的时候,Pod不会被删除,集群内出现很多的orphan pod,那么我们遇到这个问题该怎么解决呢?实际上官方给出的删除策略也就是垃圾回收策略有3种:Foreground、 Background、 Orphan,解释如下:
Foreground:The object itself cannot be deleted before all the objects that it owns are deleted.
Background:The object itself is deleted, after which the GC deletes the objects that it owned.
Orphan:The object itself is deleted. The owned objects are “orphaned.” by removing the reference to the owner.
Foreground and Background属于级联删除,orphan只是删除depoyment不会删除它的依赖RS。
在我们使用kubectl进行删除deployment时,默认使用的是 Background,这种情况下很少触发orphan pod情况发生,因为这个依赖于Kubernetes的gc controller进行回收,如果controller出现问题,还是会触发,当然早先1.11版本官方曾试图使用改成 Foreground,但由于用户体验差,又改回了 Background,综上所述建议在开发删除逻辑时尽量使用 Foreground,如果追求性能,也可以退而求其次使用 Background。
The 4th issue:Kubadm证书1年有效期问题
这个问题会花比较长的篇幅进行说明,在社区里很少有这块的详细介绍,大部分都是蜻蜓点水式的讲解或者简单粗暴的通过重启或者重装来解决,实际上这个是可以通过清晰明了的步骤来解决的。
kubeadm项目大大简化了部署Kubernetes的准备工作,尤其是像配置文件、证书、二进制文件的准备和制作,以及集群版本管理等操作;可是当我们使用kubeadm搭建集群的时候,有个绕不过去的问题,那就是它的证书问题,我们生产环境和测试环境都是基于kubeadm搭建的高可用集群,在不久前我们顺利完成了证书的更换,在这里可以跟大家分享一下。
Kubernetes证书分为2部分:Master证书和Node证书(这里我们省去etcd证书的更换过程,聚焦在Kubernetes本身的证书上,实际上理解了接下来的内容,etcd的证书更换就很容易了)。
Master证书更换
首先,kubeadm为Kubernetes项目生成的Master证书文件都放在Master节点的/etc/Kubernetes/pki目录下。
.|-- apiserver.crt|-- apiserver-etcd-client.crt|-- apiserver-etcd-client.key|-- apiserver.key|-- apiserver-kubelet-client.crt|-- apiserver-kubelet-client.key|-- ca.crt|-- ca.key|-- front-proxy-ca.crt|-- front-proxy-ca.key|-- front-proxy-client.crt|-- front-proxy-client.key|-- sa.key`-- sa.pub
在这个目录下,最核心的证书文件是ca.crt和对应的私钥ca.key,为跟证书CA,Kubernetes集群组件的证书签发机构。
需要指出的是,我们可以选择不让kubeadm为你生成这些证书,而是拷贝现有的证书到如下证书的目录里:/etc/Kubernetes/pki/ca.{crt,key},这时kubeadm就会跳过证书生成的步骤,把它完全交给用户处理。
由此根证书签发的证书有2组:
-
kube-apiserver组件持有的服务端证书
/etc/Kubernetes/pki/apiserver.crt
/etc/Kubernetes/pki/apiserver.key
-
kubelet组件持有的客户端证书,用作kube-apiserver主动向kubelet发起请求时的客户端认证
/etc/Kubernetes/pki/apiserver-kubelet-client.crt
/etc/Kubernetes/pki/apiserver-kubelet-client.key
front-proxy-ca.crt、front-proxy-ca.key是kube-apiserver代理根证书及密钥,这个是在使用kubectl proxy进行代理访问的时候,该证书用来支持SSL代理访问的;在该种访问模式下,我们以http的方式发起请求到代理服务的,而代理服务将该请求发送给kube-apiserver,在此之前,代理会将发送给kube-apiserver的请求头里加入证书信息。
由该根证书签发的证书只有1组,代理层使用该代理证书来向kube-apiserver请求认证:
/etc/Kubernetes/pki/front-proxy-client.crt
/etc/Kubernetes/pki/front-proxy-client.key
kubeadm将上述证书生成后,接下来会为其他组件生成访问kube-apiserver所需的kubeconfig配置文件,这些文件的路径是:/etc/Kubernetes/xxx.conf:
├── admin.conf├── controller-manager.conf├── kubelet.conf├── pki└── scheduler.conf
这些文件里面记录的是当前Master节点的服务器地址、监听端口、CA证书、签发的证书、私钥等信息。这样,对应的客户端(比如scheduler,kubelet等),可以直接加载相应的文件,使用里面的信息与kube-apiserver建立安全连接。
以上大概介绍了一下Master上的证书,接下来我们确定一下以上涉及到的证书及kubeconfig文件中哪些是我们要进行续期的。
首先查看/etc/Kubernetes/pki这里面证书时间:
[root@k8s-master pki]# for x in `ls -trl |awk '{print $NF}'|grep -v key|grep -v sa|grep -v 48`; do echo $x;openssl x509 -in $x -dates -noout ; done
front-proxy-ca.crt
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 4 12:54:02 2030 GMT
ca.crt
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 4 12:54:02 2030 GMT
front-proxy-client.crt
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 6 12:54:02 2021 GMT
apiserver.crt
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 6 12:54:02 2021 GMT
apiserver-kubelet-client.crt
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 6 12:54:02 2021 GMT
从上面我们看得出来front-proxy-client.crt、apiserver.crt、apiserver-kubelet-client.crt为1年,front-proxy-ca.crt、ca.crt为10年,所以在这里我们的目标是及时续期front-proxy-client.crt、apiserver.crt、apiserver-kubelet-client.crt这三个证书。
然后我们再看看kubeconfig里面证书相关信息:
[root@k8s-master Kubernetes]# for x in `ls -trl /tmp |grep cert|awk '{print $NF}'`; do echo $x; openssl x509 -in /tmp/$x -dates -noout ; done
admin.conf.cert
notBefore=Feb 7 12:54:02 2019 GMT
notAfter=Feb 6 12:54:03 2021 GMT
kubelet.conf.cert
notBefore=Feb 7 12:54:02 2019 GMT
notAfter=Feb 6 12:54:03 2020 GMT
controler-manager.conf.cert
notBefore=Feb 7 12:54:02 2019 GMT
notAfter=Feb 6 12:54:03 2020 GMT
scheduler.conf.cert
notBefore=Feb 7 12:54:02 2019 GMT
notAfter=Feb 6 12:54:03 2020 GMT
以上信息可以确定kubeconfig里面的证书时间都是1年,因此以上kubeconfig都需要重新生成进行更换。
综上所述,在Master上需要重新生成的证书清单有:
front-proxy-client.crt、apiserver.crt、apiserver-kubelet-client.crt
kuebeconfig清单:
/etc/Kubernetes/admin.conf、controller-manager.conf、kubelet.conf、scheduler.conf
下面开始进行上述文件的更换,在此之前建议大家做好备份,以防操作失误还可以进行还原:
1、备份原来证书
mkdir ./conf-archive/for f in `ls *.conf`;do mv $f ./conf-archive/$f.old;done
2、重新生成以上Master清单证书
sudo kubeadm alpha certs renew apiserversudo kubeadm alpha certs renew apiserver-kubelet-clientsudo kubeadm alpha certs renew front-proxy-client
3、重新生成kubeconfig
sudo kubeadm alpha kubeconfig user --org system:masters --client-name Kubernetes-admin > admin.confsudo kubeadm alpha kubeconfig user --client-name system:kube-controller-manager > controller-manager.confsudo kubeadm alpha kubeconfig user --org system:nodes --client-name system:node:$(hostname) > kubelet.confsudo kubeadm alpha kubeconfig user --client-name system:kube-scheduler > scheduler.conf
4、验证生成的相关文件时间是否已续期
[root@k8s-master pki]# openssl x509 -in apiserver.crt -dates -noout
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 2 05:50:10 2022 GMT
[root@k8s-master pki]# openssl x509 -in apiserver-kubelet-client.crt -dates -noout
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 2 07:31:12 2022 GMT
[root@k8s-master pki]# openssl x509 -in front-proxy-client.crt -dates -noout
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 2 07:31:26 2022 GMT
[root@k8s-master Kubernetes]# openssl x509 -in /tmp/new-admin.conf -dates -noout
notBefore=Feb 7 12:54:02 2020 GMT
notAfter=Feb 2 07:45:40 2022 GMT
通过上述信息可以看到成功续期到 2022年。
5、重启Master组件
# Restart the master componentssudo kill -s SIGHUP $(pidof kube-apiserver)sudo kill -s SIGHUP $(pidof kube-controller-manager)sudo kill -s SIGHUP $(pidof kube-scheduler)
6、验证Master组件证书是否已正常加载
# Verify master component certificates - should all be 1 year in the future# Cert from api-server[root@k8s-master Kubernetes]# echo -n | openssl s_client -connect localhost:6443 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text -noout | grep Not Not Before: Feb 7 12:54:02 2020 GMT Not After : Feb 2 07:24:06 2022 GMT# Cert from controller manager[root@k8s-master Kubernetes]# echo -n | openssl s_client -connect localhost:10257 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text -noout | grep Not Not Before: Feb 2 07:04:33 2021 GMT Not After : Feb 2 07:04:33 2022 GMT# Cert from scheduler[root@k8s-master conf-archive]# echo -n | openssl s_client -connect localhost:10259 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -text -noout | grep Not Not Before: Feb 2 07:10:26 2021 GMT Not After : Feb 2 07:10:26 2022 G
至此Master证书已经更换完成。
Node证书更换
Node证书更换实际上是指kubelet证书更换,kubelet涉及到的证书为:
├── kubelet-client-2019-06-27-17-50-33.pem├── kubelet-client-2019-06-27-17-51-00.pem├── kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2019-06-27-17-51-00.pem├── kubelet.crt├── kubelet.key
kubelet.crt是kubelet服务端证书, kubelet-client-current.pem是kubelet客户端证书,client证书默认是自动更换的,当然1.15版本之后kubelet服务端证书默认也是自动更换,这里我们是基于Kubernetes 1.13.1来进行操作。
注:在配合TLS加密的时候,实际上apiserver读取客户端证书的CN字段作为用户名,读取O字段作为用户组,而kubelet与apiserver通讯所使用的证书为kubelet-client.crt,剩下的 kubelet.crt将会被用于kubelet server(10250) 做鉴权使用。
1、首先我们查看证书的有效时间,有效时间也是1年:
[root@k8s-master pki]# openssl x509 -in kubelet.crt -noout -text -dates Not Before: Dec 9 08:47:47 2019 GMT Not After : Dec 8 08:47:47 2020 GMTYou have new mail in /var/spool/mail/root[root@k8s-master pki]# openssl x509 -in kubelet-client-current.pem -noout -text -dates Not Before: Dec 9 09:43:00 2019 GMT Not After : Dec 8 09:43:00 2020 GMT
2、增加controler-manager参数,设置kubelet client和server证书自动续签为10年
vim /etc/Kubernetes/manifests/kube-controller-manager.yaml- --experimental-cluster-signing-duration=87600h0m0s- --feature-gates=RotateKubeletServerCertificate=tr
3、配置rbac进行server证书自动签发授权
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: "2019-12-09T09:48:12Z"
name: kubeadm:node-autoapprove-certificate-rotation
resourceVersion: "215"
selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/kubeadm%3Anode-autoapprove-certificate-rotation
uid: 04042239-1a69-11ea-b728-fa163e493cb3
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:nodes
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.Kubernetes.io/autoupdate: "true"
labels:
Kubernetes.io/bootstrapping: rbac-defaults
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeserver
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/selfnodeserver
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubeadm:node-autoapprove-certificate-server
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:certificates.k8s.io:certificatesigningrequests:selfnodeserver
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:nodes
4、编辑kubelet配置文件,添加如下参数
serverTLSBootstrap: truefeatureGates: RotateKubeletClientCertificate: true RotateKubeletServerCertificate: true
5、重启kubelet
systemctl restart kubelet
这个时候查看日志会发现kubelet还没有完全启动好,原因是server证书需要手动approve CSR 才能进行颁发。
6、颁发server证书
[root@k8s-master pki]# kubectl get csr
NAME AGE REQUESTOR CONDITION
csr-w7sg4 34s system:node:k8s-master Pend
kubectl certificate approve csr-w7sg4
7、验证证书是否已正常更新
├── kubelet-client-2019-06-27-17-50-33.pem├── kubelet-client-2019-06-27-17-51-00.pem├── kubelet-client-current.pem -> /var/lib/kubelet/pki/kubelet-client-2019-06-27-17-51-00.pem├── kubelet.crt├── kubelet.key├── kubelet-server-2020-02-18-20-14-45.pem└── kubelet-server-current.pem -> /var/lib/kubelet/pki/kubelet-server-2020-02-18-20-14-45.pem
8、验证证书是否已被kubelet正常加载
[root@k8s-master pki]# openssl x509 -in kubelet-client-current.pem -dates -nooutnotBefore=Jan 31 16:30:00 2021 GMTnotAfter=Jan 29 16:30:00 2031 GMT[root@k8s-node pki]# openssl x509 -in kubelet-server-current.pem -dates -nooutnotBefore=Feb 1 03:18:00 2021 GMTnotAfter=Jan 30 03:18:00 2031 GMT
至此Master和Node上的所有证书更换成功。
以上整个操作过程的完成,需要我们对Kubernetes的证书使用方式有一定的了解,一旦掌握了,收益还是挺大的,不用苦恼kubeadm的证书问题,生产也可以完全放心使用。
总的来说,kubeadm的设计非常简洁,它在实现每一步部署功能时,都在最大程度地重用Kubernetes已有的功能,这也就使得我们在使用kubeadm部署Kubernetes项目时,非常有“原生”的感觉,一点都不会感到突兀。