草庐IT

基于 KubeSphere 的运管系统落地实践

kubesphere 2023-03-28 原文

作者:任建伟,某知名互联网公司云原生工程师,容器技术信徒,云原生领域的实践者。

背景介绍

在接触容器化之前,我们团队内部的应用一直都是基于虚拟机运管,由开发人员自行维护。

由于面向多开发部门服务,而开发人员运维能力参差不齐,所以每次部署新的环境时往往都要耗费大量时间。

针对部署难的问题,我们将部分组件、服务容器化,采用 Docker 发布管理解决了部分问题,但仍未降低对开发人员的运维技能要求。

下面是我们基于虚拟机管理开发环境的流程:

从上图中我们也能发现当前架构存在的问题:

  • 下发虚机由各部开发人员管理,虚机安全问题难以维护、保障;
  • 基于 shell 运维,专业性过强;
  • 基于手动打包、发布,耗时耗力且不可靠。

选型说明

针对上述提到的痛点,我们决定对运维架构进行改造。新建运管平台,技术选型整体基于云原生,优先选取 CNCF 项目。

Kubernetes 成为了我们平台底座的不二选择, 但 Kubernetes 原生的 Dashboard 不太满足实际使用需求。

而从头开发一套 workbench 又耗时耗力,由此我们目光转向了开源社区。

此时,一个集颜值 + 强大功能于一身的开源项目进入我们视野。是的,它便是 KubeSphere。

KubeSphere 愿景是打造一个以 Kubernetes 为内核的云原生分布式操作系统,它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用(plug-and-play)的集成,支持云原生应用在多云与多集群的统一分发和运维管理。

对于 KubeSphere 能否作为部署平台,最终结论如下:

KubeSphere 虽功能强大,但更适合作为管理端使用,不太适合面向普通用户。

我们需要本地化一套 workbench ,简化部分功能,屏蔽专业性术语(如工作负载、容器组、安全上下文等)。

本地化部分内容如下:

  • 基于企业空间、命名空间,本地化租户、工作空间的概念,一个租户(企业空间)可管理一个到多个工作空间(命名空间),并接入独立用户体系。
  • 本地化应用发布流程: 由拆分的应用发布流程(构建镜像+创建负载),本地化为:创建应用 -> 上传 jar -> 指定配置 -> 启动运行的串行流程。
  • 本地化链路监控:构建镜像预先埋点,创建应用时选择是否开启链路追踪。
  • 本地化配置、应用路由等,添加版本管理功能。

事实上,我们本地化的重点是应用管理,但是 KubeSphere 功能过于强大、特性过于灵活,导致配置起来项过于繁琐。

针对部分配置项我们采用设置默认值的方式,而非交由用户去配置。(比如:容器安全上下文、同步主机时间、镜像拉取策略、更新策略、调度策略等)

改造后的运维架构如下:

实践过程

基于 KubeSphere 的运管平台整体架构如下:

环境信息表:

名称 版本 说明
kukekey v1.0.1 KubeSphere 安装工具
kubesphere v3.0.0 基于 K8s 的面向云原生应用的分布式操作系统
kuberentes v1.18.6 容器编排系统
docker v19.03.15 容器引擎
CentOS 7 操作系统
kernel 5.4 操作系统内核

本地化部署流程如下:

镜像本地化

1️⃣ 基于 harbor 搭建私有镜像库。

2️⃣ 离线下载并上传 kubesphere 依赖镜像至私有 harbor 内,project 名称保持不变。

3️⃣ 本地化 B2I 基础镜像,本地化如下内容:

4️⃣ 本地化应用商店初始化镜像(openpitrix/release-app)。

由于预置的 chart 有很多我们实际并未使用,所以我们删除预置了 chart ,并导入实际所需 chart (包括本地化的中间件 chart 、中台 chart

5️⃣ 镜像 GC。

针对频繁构建的 repo ,配置合理的 GC 策略:

搭建 K8s

基于 KubeKey 1.0.1 部署了三主多从节点 K8s v1.18.6 集群:

搭建 Rook 集群

使用 KubeKey 1.0.1 新增三个存储节点并打上污点标签,搭建 Rook 集群

对于存储的替换主要出于以下方面考虑:

搭建 KubeSphere 平台

基于 KubeKey 1.0.1 部署了 KubeSphere,未作本地化修改。

CI/CD 实践

CI/CD 部分我们并没有使用 KubeSphere 提供的流水线功能,而是选择 gitlab-runner + ArgoCD 方案。

CI 实现

CI 部分利用 gitlab-ci 切换构建时特性,我们抽象出了 provider 概念。provider 本质为工具 / 程序的容器化封装,提供某一方面能力了。如:

  • maven-provider: java 程序构建时环境,内置私有 nexus 配置;
  • npm-provider: nodejs 程序构建时环境,内置私有 npm 源配置;
  • email-provider: smtp 交互程序,用于邮件通知;
  • chrome-headless-provider: 浏览器截屏。

使用时,只需引用并传递相应参数即可:

variables:
  AAA: xxx
  BBB: yyy

stages:
  - build
  - scan
  - email

build:
  stage: build
  image: harbor.devops.io/devops/maven-provider
  tags:
    - k8s-runner
  script:
    - mvn clean package
  only:
    refs:
     - develop
    changes:
      - src/**/*

scan:
  stage: scan
  image: harbor.devops.io/devops/sonar-provider
  tags:
    - k8s-runner
  script: xxx
rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

email:
  stage: email
  image: harbor.devops.io/devops/sendmail
  tags:
    - k8s-runner
  script:
    - /work/send-mail sonar --email-to=$EMAIL_TO_LIST --email-cc=$EMAIL_CC_LIST --sonar-project-id=$PROJECT_NAME --sonar-internal-url=$SONAR_INTERNAL_ADDR --sonar-external-url=$SONAR_EXTERNAL_ADDR
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

CD 实现

CD 部分,我们利用 chart 对应用进行定义,并将 chart 剥离于开发库,独立于配置库进行管理,用于 ArgroCD 同步。

对于配置库与开发库分离,主要出于以下考虑:

  • 清晰分离了应用程序代码与应用程序配置。
  • 更清洁的审计日志:出于审计目的,只保存配置库历史更改记录,而不是掺有日常开发提交的日志记录。
  • 访问的分离:开发应用程序的开发人员不一定是能够 / 应该推送到生产环境的同一个人,无论是有意的还是无意的。
    通过使用单独的库,可以将提交访问权限授予源代码库,而不是应用程序配置库。
  • 自动化 CI Pipeline 场景下,将清单更改推送到同一个 Git 存储库可能会触发构建作业和 Git 提交触发器的无限循环。
    使用一个单独的 repo 来推送配置更改,可以防止这种情况发生。

角色划分

角色方面,我们定义了三种类型角色,职责如下:

使用效果

通过引入 KubeSphere 平台以及 CI/CD,效率提升明显:

  • 计算资源池化,不再下发虚机,计算资源统一运管;
  • 基于容器化的流水线构建、发布应用,保障了构建的可靠性,同时解放双手;
  • 基于本地化 workbench 运维,由于屏蔽了专业性词汇术语,降低使用者学习成本。日志查看、应用更新等操作更为便捷;
  • 针对角色的划分,使得运维边界清晰,责任明确。

问题 & 解决

在一年多的容器平台使用过程中,我们遇到了蛮多的小问题,这里我举几个有代表性的例子:

B2I 没有清理策略

存在问题:

在使用 kubesphere v3.0 的过程中我们发现:不断通过 B2I 构建应用,会产生大量的 B2I 任务记录,并且 minio 内上传的程序包文件越来越多,且并没有相应的清理策略。

解决方案:

开发定时 job , 定期进行清理。

内核版本过低,导致容器相关漏洞的发生

存在问题:

初期,我们使用 CentOS7 默认的 3.10 版本内核。

解决方案:

升级内核版本至 5.x。

链路追踪

存在问题:

KubeSphere 预装的 jaeger 不支持 dubbo 协议,无法对 dubbo 应用进行监控。

解决方案:

利用 SkyWalking 用于链路追踪,并在基础镜像内埋点。

报表相关服务缺少字体

解决方案:

将缺少 windows 字体安装至 B2I 基础镜像内。

  1. 路由集群外服务

由于部分应用部署于 K8s 外部,针对这部分应用我们选择 Endpoint + ExternalName + Ingress 的方式进行路由。

未来规划或展望

1️⃣ 有状态应用的 Operator 开发

当前有状态应用依赖 helm hook 管理, 且功能单一。
未来我们计划,针对常用有状态应用,开发对应 operator,提供创建、扩容、备份等常用功能。

2️⃣ CNI 迁移至 Cilium

选取 Cilium 替换 Calico 主要出于以下考虑 :

  • CiliumCNCF 毕业项目,活跃度高;
  • Cilium 基于 eBPF 实现,在粒度和效率上实现了对系统和应用程序的可观测性和控制;
  • Cilium 安全防护功能更强,提供了过滤单个应用协议请求的能力,例如 :
    • 允许所有使用 GET 方法和 /public/.* 路径的 HTTP 请求,拒绝所有其他请求;
    • 允许 service1Kafka 主题 topic1 上生产,允许 service2topic1 上消费,拒绝所有其他 Kafka 消息;
    • 要求 HTTP 报头 X-Token:[0-9]+ 出现在所有 REST 调用中。

3️⃣ cri 由 Docker 替换为 Containerd

4️⃣ 容器文件浏览器功能开发

当前阶段,开发人员下载容器内文件的需求,只能由运维人员使用 kubectl cp 的方式协助获取,后续我们规划开发容器文件浏览器相应功能。

5️⃣ 容器宿主机系统替换为 rocky,以应对 CentOS 停止维护。

本文由博客一文多发平台 OpenWrite 发布!

有关基于 KubeSphere 的运管系统落地实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  4. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  7. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  8. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

  9. ruby - 在没有基准或时间的情况下用 Ruby 测量用户时间或系统时间 - 2

    因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实

  10. ruby - 以毫秒为单位获取当前系统时间 - 2

    在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:

随机推荐