写点什么

K8S 在有赞 PaaS 测试环境中的实践

  • 2019-10-23
  • 本文字数:6634 字

    阅读完需:约 22 分钟

K8S 在有赞 PaaS 测试环境中的实践

一、背景介绍

有赞 PaaS 团队自 17 年 7 月份开始投入测试资源,测试人员的加入意味着与测试相关的一系列东西产生,比如测试环境、测试工程、测试流程等等,这次分享的内容主要与测试环境有关,刚开始我们把测试环境部署在虚拟机上,从 18 年 7 月份开始,我们决定把测试环境从虚拟机迁移到 K8S 上,做这个决定主要出于以下几个方面考虑。

2.1 公司持续交付系统不支持 PaaS 产品

目前公司的持续交付系统只支持业务产品,不支持 PaaS 产品,由于 PaaS 产品形态多样化、开发语言多样化、部署复杂、小众等原因,持续交付系统暂时也不太可能会支持,所以 PaaS 产品的测试环境需要测试人员自己搭建。

2.2 成本问题

2.2.1 资源成本

有赞 PaaS 产品有 15+,包括 RDS、KVDS、NSQ、ES、统一接入接出、服务治理、定时任务等等,每个产品又由多个组件构成,加上大大小小组件大概有 40+,像 RDS 一个产品就有 9 个组件,部署一个最简单的集成测试环境需要 8 台机器。如果把每一个组件部署在 VM 里面,至少需要 40 多台机器,但是这样的部署方式并不能满足我们的测试场景,我们的产品大多属于分布式系统,考虑到多节点、主备、双机房等等,需要的 VM 可能在 70〜100 之间。需要的机器越多意味着公司花更多的钱,可能有人会说一台虚拟机可以部署多个组件,但是这样会导致资源管理紊乱,测试之间相互干扰。


引入 K8S 后,只需要一个 K8S 集群就可以满足所有 PaaS 产品的部署,产品与产品之间通过 namespace 隔离,组件与组件之间通过 deployment 隔离,相互不干扰,而且升级和扩容也很方便。

2.2.2 部署成本

使用 VM 做应用部署需要在 jenkins job 里面写大量的 shell 脚本,先在 slave 机器上拉代码、编译、打包,然后把二进制包传到需要部署的机器上,这里会存在两个问题,一是需要把 slave 与所有的应用部署机器打通 ssh 免密通道,如果有 100 机器,就需要做 100 次公钥拷贝,更改权限,假如哪天 slave 机器变了或者公钥变更了,又得重新打通通道。二是 shell 脚本不便于维护,shell 脚本并没有做持久化存储,如果 job 被谁给删掉了,那么又需要重新编写,工作量会变大。


引入 K8S 后,编译、打包、部署的脚本都编写在 Dockerfile 里面,Dockerfile 同源码保存在 gitlab 上,不用担心丢失问题,维护起来也很方便。

2.2.3 顺势而为

云计算飞速发展,Docker 技术突飞猛进,kubernetes 大势所趋,各大公司都在玩 K8S,PaaS 测试人员需要紧跟时代的步伐。同时 Service Mesh 技术正在悄然兴起,PaaS 的服务化产品后期也会在 K8S 中测试…

二、整体架构

我们的目标是要解决持续集成和持续测试快速、低成本、自动化的问题,整个架构由 Gitlab + Jenkins + Harbor + Kubernetes 集成。



Gitlab 是公司存储代码的仓库,在这个架构中,我们在应用工程里引入了 Dockerfile,用来定义构建镜像、启动应用的脚本。


Jenkins 是持续集成工具,在这个架构中主要用来从 Gitlab 拉取源码,然后打成镜像推送到 Harbor。


Harbor 是公司的镜像仓库,用来存储打好的镜像。


Kubernetes 是一个容器编排引擎,在这里是替代虚拟机,部署应用的地方。

三、操作步骤

3.1 K8S 与 jenkins 集成

K8S 与 jenkins 集成很简单,jenkins 已提供 K8S 的插件,安装即可。


第一步:首先安装 kubernetes 插件,然后进入【系统管理】-> 【系统设置】,找到【云】,然后新增一个 kubernetes 的 【云】,填写你所搭建好的 kubernetes 集群地址和证书并保存。


第二步:新建 jenkins job,选择【流水线】任务,进入配置页面,在【流水线】->【定义】选项中选择 【Pipeline script】,将以下这段脚本拷贝到本文框里。


podTemplate(label: 'mypod', cloud: 'kubernetes',containers: [    containerTemplate(        name: 'jnlp',         image: 'www.harbor.com/yzcontainer/yz-centos-jnlp-slave:latest',         alwaysPullImage: true,         args: '${computer.jnlpmac} ${computer.name}',        env:['name':'application_standard_env','value':'daily']        ),
], volumes: [ hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),]){ node('mypod') { stage('test') { container('jnlp') { sh """
""" } } } }
复制代码


以上脚本中有几个地方需要变更:


cloud 值必须与【系统管理】->【系统设置】->【云】中 kubernetes Name 一致;



  • image 值为基础镜像,需要定制化,比如应用运行的操作系统、编译打包运行依赖的软件等等;

  • sh 编写我们需要执行的命令,比如 clone 代码、编译、打包、打镜像、push 镜像到公司仓库等。

3.2 创建 Namespace

Kubernetes 可以使用 Namespaces(命名空间)创建多个虚拟集群,用来做资源隔离,比如环境、产品之间可以用 Namespace 进行隔离,利于管理,也不会相互干扰,也可以不指定,默认是在 default 下面。有两种创建 Namespace 的方法,二选一即可。


命令行直接创建


kubectl create namespace namespace名称 //namespace 可以简写成 ns
复制代码


通过文件创建


  • 定义 my-namespace.yaml 文件


apiVersion: v1kind: Namespacemetadata:  name: namespace名称 
复制代码


  • 命令行创建


kubectl create -f ./my-namespace.yaml
复制代码


创建成功后可以通过 kubectl get ns 命令查看所有 Namespace。

3.3 创建 Deployment

Deployment 为 Pod 和 Replica Set 提供声明式更新,你只需要在 Deployment 中描述你想要的目标状态是什么,Deployment controller 就会帮你将 Pod 的实际状态改变到你的目标状态。你可以定义一个全新的 Deployment 来创建 Pod 或者删除已有的 Deployment 并创建一个新的来替换。


定义 Deployment yaml 文件


apiVersion: extensions/v1beta1kind: Deploymentmetadata:  labels:    run: //自定义标签名称  name: //deploy名称,推荐跟应用名一致  namespace: //deploy所属的命名空间spec:  progressDeadlineSeconds: 600  replicas: 1 //通过增加副本数来弹缩应用,有多少个副本数就有多少个pod  revisionHistoryLimit: 10  selector:    matchLabels:      run: //自定义标签名称  strategy:    rollingUpdate:      maxSurge: 1      maxUnavailable: 1    type: RollingUpdate  template:    metadata:      creationTimestamp: null      labels:        run: //自定义标签名称    spec:      containers:      - image: //容器的镜像名称        imagePullPolicy: Always        name://容器名称,推荐跟应用名一致        resources: {}        terminationMessagePath: /dev/termination-log        terminationMessagePolicy: File      dnsPolicy: ClusterFirst      restartPolicy: Always      schedulerName: default-scheduler      securityContext: {}      terminationGracePeriodSeconds: 30
复制代码


其中 spec.selector.matchLabels 与 spec.template.metadata.labels 必须一致,selector 负责调度 label 名称一样的资源,template 为即将新建的 Pod 附加 label,然后通过 selector 字段来指定这个 RC 管理哪些 Pod。


创建 Deployment


kubectl create/apply -f xxx.yaml
复制代码


其中 create 和 apply 都可以用来做创建,两者的区别在于 create 不能重复执行,apply 可以。


返回 deployment.extensions xxxx created,说明创建成功,如果返回错误信息,根据错误信息排查错误,仔细检查 yaml 文件的格式和参数。


查看应用状态


Pod 是 Kubernetes 创建或部署的最小/最简单的基本单位,一个 Pod 代表集群上正在运行的一个进程,一个 Pod 封装一个应用容器(也可以有多个容器),存储资源、一个独立的网络 IP 以及管理控制容器运行方式的策略选项。Pod 代表部署的一个单位:Kubernetes 中单个应用的实例,它可能由单个容器或多个容器共享组成的资源。


Deployment 创建成功后,通过以下命令查看应用部署情况。


$ kubectl get pod -n [namepsace] -o wideNAME                               READY     STATUS    RESTARTS   AGE       IP              NODE301mock-6cf74454f7-794l2           1/1       Running   0          5s       x.x.x.x   x.x.x.x
复制代码


如果 STATUS 为 Running 状态,说明服务启动成功,如果 STATUS 为 ERROR 或 CrashLoopBackOff 状态,说明应用部署失败,通过 kubectl logs -f [pod name] -n [namepsace] 查看日志定位失败原因。


应用部署成功后,可以通过 kubectl exec -it [pod name] bash -n [namespace] 进入容器内部,其他操作同 linux 下。


如果更新镜像(tag 没变),只需删除之前 Pod,如 kubectl delete pod [pod name] -n [namespace],Kube-controller-manager 会重新创建 Pod 使集群状态符合 deployment 中定义的预期状态。

3.4 创建 Service

当 Pod 在创建和销毁的过程中,IP 可能会发生变化,而这就容易造成对其有依赖的服务异常,所以通常情况下,我们都会使用 Service 将后端 Pod 暴露出来,而 Service 则较为稳定。Service 的创建有两种方式,单个端口推荐第一种,多个端口推荐第二种。


通过命令行直接创建


kubectl expose deploy/etcd --port=2379 --target-port=2379 --name=etcd --type=NodePort -n [namespace]
复制代码


通过文件创建


  • 定义 my-service.yaml 文件


kind: ServiceapiVersion: v1metadata:  name: my-service  namespace: spec:  selector:    app: MyApp  ports:    - protocol: TCP      port: 2379      targetPort: 2379  type: NodePort
复制代码


  • 命令行创建


kubectl create -f my-service.yaml
复制代码


创建好以后可以通过 kubectl get svc -n [namespace] 命令查看。


kubectl get svc -n [namespace]NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE       SELECTORetcd      NodePort   10.99.127.115   <none>        2379:31422/TCP   4d        run=etcd
复制代码


现在我们就可以通过 CLUSTER-IP:2379 访问集群内部服务了。


默认情况下,Pod 端口只能 kubernetes 集群内部访问,如果通过外部网络访问 Kubernetes 集群内部的应用,需要将应用通过 NodePort 方式暴露出去,在上面的式例中,type 就使用了 NodePort 类型,然后可以在集群外通过 NODE-IP:31422 访问。type 有四种类型,如需进一步了解,请 Google。


这部分我们讲解了基本而必要的操作步骤将一个应用部署到 Kubernetes 集群中,并且可以通过外部网络访问 K8S 集群内部的应用,下面分享一些我们在测试过程中为了满足特定需求而使用的一些高级用法。

四、定制化用法

4.1 NodeName

默认情况下,Kube-scheduler 将预期的 Pod 资源调度到最佳的 Node 上,但是有些特殊测试场景下,我们需要把不同的应用部署到不同的 Node 上,满足这种绑定很简单,只需要在 Deployment yaml 文件中添加 nodeName 配置即可,然后重新部署 Deployment,Pod 就会分配到指定的 Node 上。


spec:  containers:  - image:            imagePullPolicy: Always    name:     resources: {}    terminationMessagePath: /dev/termination-log    terminationMessagePolicy: File  dnsPolicy: ClusterFirst  nodeName: x.x.x.x    restartPolicy: Always  schedulerName: default-scheduler  securityContext: {}  terminationGracePeriodSeconds: 30
复制代码

4.2 SecurityContext

在测试过程中,我们会模拟组件与组件之间的网络异常,会使用到 tc 和 iptable 命令,默认情况下,容器没有开放执行网络控制的权限,需要手动添加 securityContext 配置到 deploy yaml 文件中,然后重新部署 Deployment,就可以执行 tc 和 iptable 指令了。


spec:  containers:  - image:     imagePullPolicy: Always    name: tsp-worker    resources: {}    securityContext:      capabilities:        add:        - NET_ADMIN    terminationMessagePath: /dev/termination-log    terminationMessagePolicy: File
复制代码

4.3 Volumes

默认情况下容器中的磁盘文件是非持久化的,对于运行在容器中的应用来说面临两个问题,第一:当容器挂掉 kubelet 将重启它时,文件将会丢失;第二:当 Pod 中同时运行多个容器,容器之间需要共享文件时,这两种情况下我们就要用到 Kubernetes 的 Volume。


在 deploy yaml 文件中添加 volumes 配置:


spec:  containers:  - image:     imagePullPolicy: Always    name: etcd    resources: {}    terminationMessagePath: /dev/termination-log    terminationMessagePolicy: File    volumeMounts:    - mountPath: /default.etcd //容器内的目录      name: etcd-volume  dnsPolicy: ClusterFirst  restartPolicy: Always  schedulerName: default-scheduler  securityContext: {}  terminationGracePeriodSeconds: 30  volumes:  - hostPath:      path: /data/etcd //挂载到宿主机上的目录      type: Directory    name: etcd-volume
复制代码


其中 type 有很多种类型,可以根据实际情况选择,配置添加以后,重新部署 Deployment,然后写一条数据到文件里,再删除 Pod,Pod 启动好以后再查询这个数据,如果存在,则说明持久化成功。

4.4 Ingress

上面说了 Service 的使用,Service 是 Kubernetes 暴露 http 服务的默认方式,其中 NodePort 类型可以将 http 服务暴露在宿主机的端口上,以便外部可以访问,其优点是结构简单,容易理解,其缺点是一个 app 需要占用一个主机端口,端口缺乏管理,L4 转发,无法根据 http header 和 path 进行路由转发。Ingress 是在 Service 之前加了一层,增加了 7 层识别能力,可以根据 http header,path 进行路由转发。Ingress 的实现由 Ingress Controller 和 Ingress 两部分组成,为了使 Ingress 正常工作,集群中必须运行 Ingress Controller,Ingress Controller 是流量的入口,是一个实体软件,一般是 nginx 和 haproxy,Ingress 则描述具体的路由规则。


一个简单的 ingress.yaml 文件定义如下:


apiVersion: extensions/v1beta1kind: Ingressmetadata:  name: yz7-ingress  namespace: yz7spec:  rules:  - host: yz7-test-control.s.yz.com    http:      paths:      - backend:          serviceName: yz7-cluster          servicePort: 8888        path: /
复制代码


创建 Ingress:


 kubectl create -f ingress.yaml
复制代码


查看 Ingress:


kubectl get ing -n yz7 -o wideNAME               HOSTS                             ADDRESS        PORTS     AGEyz7-ingress   x.x.x.x   x.x.x.x   80        25d
复制代码


然后通过 HOSTS 或者 ADDRESS 就可以访问该应用了。Ingress 的功能远不止这些,还可以进行单个 Ingress 的 timeout、登录验证、cros、请求速率 limit、rewrite 规则、ssl 等等设置,如需进一步了解 Ingress,需要查阅资料。

4.5 DNS

前面我们讲解了 Service 的用法,我们可以通过 Service 生成的 ClusterIP(VIP) 来访问 Pod 提供的服务,但是在实际工作中存在一个问题:VIP 发生变化怎么办?


更理想的方案是:直接使用 Service 的名称,因为 Service 的名称不会变化,我们不需要去关心分配的 ClusterIP 的地址,因为这个地址并不是固定不变的,名字和 ip 之间的转换就是 DNS 系统的功能,因此 kubernetes 提供了 DNS 方法来解决这个问题。


DNS 服务不是独立的系统服务,而是一种 addon ,作为插件来安装的,现在比较推荐的两个插件是 Kube-dns 和 CoreDNS,插件的安装方式和配置可以参考其他文档,内容有点多,就不在这儿详解。


通过域名访问应用的方式如下:


  • 普通的 Service,会生成 servicename.namespace.svc.cluster.local 的域名,解析到 Service 对应的 ClusterIP 上,在 Pod 之间的调用可以简写成 servicename.namespace,如果处于同一个命名空间下面,甚至可以只写成 servicename 即可访问;

  • Headless Service,就是把 clusterIP 设置为 None 的服务,会被解析为指定 Pod 的 IP 列表,通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod。

五、结束语

到目前为止,有赞 PaaS 所有产品的集成测试环境已经从 VM 迁移到了 K8S,留了几台 VM 做备用,不仅提高了集成速度,而且降低了公司成本。但是 K8S 博大精深,还有很多知识点需要去学习,路漫漫其修远兮,吾将上下而求索。


本文转载自公众号有赞 coder(ID:youzan_coder)


原文链接


https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455760074&idx=1&sn=1df3079d81cfedd9d717887ebccc1328&chksm=8c686aefbb1fe3f95c582b0a072cc90090b6bdd8dd224cfd58fc4576674401e438d62186f661&scene=27#wechat_redirect


2019-10-23 08:002792

评论

发布
暂无评论
发现更多内容

NodeJS搭建本地服务指南

编程江湖

node,js

开源算力引擎 BridgX 发布 0.6.0 版本:新增三种权限管理功能

星汉未来

开源 运维 云原生 云计算, 开源生态

跨平台应用开发进阶(二) :uni-app 实战

No Silver Bullet

uni-app 跨平台 规范 2月月更

金融行业研发管理平台选型

帅气IT小哥

9期视频告诉你如何更正确更高效的使用Git

阿里云云效

git 阿里云 云原生 代码 研发

[Python公开课]零基础玩转Python基础篇----第六节:Python中的函数

是Dream呀

2月月更

[Python公开课]零基础玩转Python进阶篇----第二节:Python的异常分析及解决

是Dream呀

2月月更

基础架构部如何推动新一代云原生架构变革

星汉未来

开源 云原生 开源社区 云计算,

详解CNN实现中文文本分类过程

华为云开发者联盟

机器学习 CNN 深度神经网络 文本分类 Word2Vec

CTF题目中遇到的PHP考点总结(一)

H

php CTF WEB安全

当 TiDB 遇上 Flink:TiDB 高效入湖“新玩法” | TiLaker 团队访谈

PingCAP

【Python训练营】Python每日一练----第2天:门牌制作

是Dream呀

2月月更

Hive 数据倾斜问题定位排查及解决

编程江湖

一文看懂业界在离线混部技术

星汉未来

开源 云原生 服务治理 云计算, 混部

产品更新 | 数据物流引擎 DTExpress 新增算力市场及跨云传输

星汉未来

数据迁移 云算力 云 原生云 CTO

[Python公开课]零基础玩转Python基础篇----第七节:Python中的高级函数

是Dream呀

2月月更

[Python公开课]零基础玩转Python进阶篇----第一节:Python中的文件操作

是Dream呀

2月月更

Serverless 与工具链建设

刘宇

Serverless 工具链

【思特奇杯·云上蓝桥-算法集训营】第2周----真题汇总+思路分享

是Dream呀

2月月更

刚出炉的《Java开发手册黄山版》,我帮你们圈出了改动点!

捉虫大师

爬虫requests模块 入门到入狱 :基础知识+实战分析

是Dream呀

2月月更

智能运维引擎 CudgX 更新 V0.2.0 版本,新增根据 MetricQPS 进行自动扩缩容!

星汉未来

云原生 算力 智能运维 扩缩容

你知道,java项目中是如何获取文件地址的吗?

华为云开发者联盟

Java 路径 ClassLoader 文件地址 绝对地址

Ti-Click:通过浏览器快速搭建 TiDB 在线实验室 | Ti-可立刻团队访谈

PingCAP

Python格式字符串的三种方法你都掌握了吗,%s%d%f+format()+f-string,深度测评YYDS实至名归

是Dream呀

2月月更

YoloV5实战:手把手教物体检测

华为云开发者联盟

物体检测 模型 yolo YOLOv5 Labelme

博云信创云管平台入选工信部推荐解决方案名单,头部券商信创案例获应用示范单项!

BoCloud博云

云服务 信创 云平台

如何找寻职业方向感

万事ONES

ONES

Flutter 屏幕采集实战分享

ZEGO即构

flutter 音视频开发 屏幕采集编码

使用s3fs在centos上挂载bucket

阿呆

S3

一行代码:开启 eBPF,代替 iptables,加速 Istio

Daocloud 道客

istio 开源项目 网络加速 服务网格 ebpf

K8S 在有赞 PaaS 测试环境中的实践_架构_侯枝影_InfoQ精选文章