一, 容器云的背景
伴随着微服务的架构的普及, 结合开源的 Dubbo 和 Spring Cloud 等微服务框架, 宜信内部很多业务线逐渐了从原来的单体架构逐渐转移到微服务架构. 应用从有状态到无状态, 具体来说将业务状态数据如: 会话, 用户数据等存储到中间件中服务中.
微服务的拆分虽然将每个服务的复杂度降低, 但服务实例的数目却呈现出爆炸式增长, 这给运维增加难度, 一方面是服务部署, 升级, 另一方面是服务的监控故障恢复等.
在 2016 年, 容器技术尤其是 Docker 迅速流行起来, 公司内部开始尝试将容器放到容器内运行, 虽然通过容器解决了服务发布问题, 但很多容器的运维仍然让运维捉襟见肘. 宜信是一家金融科技公司, 在引入开源组件的时候, 稳定可靠是作为考量的最重要标准, 在 2017 年初 kubernetes 慢慢成熟, 成为容器的管理标准, 并且被国内外很多公司采用, 在这种背景下, 宜信借鉴开源社区和商业 PAAS 平台产品, 基于 kubernetes 自研一套容器管理平台.
二, 整体架构
整个架构围绕 kubernetes 构建, 分为四个层级, 最底层主要是基础资源, 包括网络, 计算, 存储, 所有的容器都是部署在物理服务器上, 容器挂载商业 NAS 存储, 网络通过 vxlan 互连; 中间层核心的是资源调度层, 主要完成多集群的管理, 发布部署, 智能调度, 自动伸缩等, 这层主要是资源管理和服务编排; 左侧面是提供系统安全, 主要是为了系统安全和容器镜像安全, 右侧面是一套代码自动编译, 自动构建, 自动部署系统; 中间件层主要提供常用的中间件服务, Nginx 配置和监控告警等; 最上层的是用户接入层, 主要提供用户的操作入口. 整体架构如下图所示:
三, Nginx 自助管理
公司大部分的服务都是通过 Nginx 反向代理对外提供服务, 为了服务的隔离和负载均衡, 总计十几套的 Nginx 集群, 这些 nginx 的版本, 配置方式各有不同, 导致单纯靠人工去运维的成本非常高而且容易出错, 并且容器的 IP 地址不固定, 无法直接配置到 nginx 后端. 自研了一套 nginx 管理系统, 主要是为了解决 nginx 的模板化配置, 如下图所示:
Nginx-mgr 提供 HTTP 请求, 负责接收 nginx 配置请求, 并更新到 etcd, 每个 nginx-agent 通过 watch Etcd 批量刷新 nginx 的配置. 在实际的生产环境里, 部署的是阿里开源的 Tengine 而并非 nginx, 由于配置基本相同不做区分. 每个服务都配置了健康检查, 这样能够保障在后端故障中自动切换. 如果有虚拟机场景需要手动切换, 下图展示了手动切换 nginx 的页面:
由于很多业务都是虚拟机和容器混跑的情况下, 如果后端是容器, 我们通过 kubernetes 的 API 获取容器的 IP 地址动态刷新.
四, 多集群管理
虽然 kubernetes 本身存在采用高可用的部署架构, 避免单点故障, 但这远远还不够, 一方面是因为单个 kubernetes 集群部署在一个机房, 如果发生机房级别的故障, 将会导致服务中断, 另一方面由于单个 kubernetes 集群本身故障, 如集群的网络配置错误导致整个网络故障等, 都将会影响业务的正常使用, 在宜信将 kubernetes 部署在多个机房内, 机房之间通过专线互连. 那么多集群的管理将成为主要难点: 第一是如何分配资源, 当用户选择多集群部署后, 系统根据每个集群的资源用量, 决定每个集群分配的容器数量, 并且保证每个集群至少有一个容器. 集群自动伸缩时, 也会按照此比例创建和回收容器. 第二是故障迁移, 如图中的集群控制器主要为了解决多集群的自动伸缩和集群故障时的容器迁移, 控制器定时检测集群的多个节点, 如果多次失败后将触发集群容器迁移的操作, 保障服务可靠运行.
第三是网络和存储的互连, 由于跨机房的网络需要互连, 我们采用 vxlan 的网络方案实现, 存储也是通过专线互连. 容器的镜像仓库采用 Harbor, 多集群之间设置同步策略, 并且在每个集群都设置各自的域名解析, 分别解析到不同的镜像仓库.
五, DNS 解析
由于业务人员对容器技术还存在疑虑, 所以大部分应用都是虚拟机和容器的混合部署, 容器通过域名访问虚拟机和虚拟机通过域名访问容器都是普遍存在的, 为了统一管理域名, 我们没有采用 kubernetes 自带的 kube-dns(coreDns) 而采用 bind 提供域名解析. 通过 kubernetes 支持的 Default DNS 策略将容器的域名指向公司的 DNS 服务器, 并配置域名管理的 API 动态添加.
六, 网络方案
kubernetes 的 CNI 的网络方案有很多种, 主要分为二层, 三层和 overlay 方案. 一方面机房并不允许跑 BGP 协议, 并且需要跨机房的主机互连, 所以我们采用了 flannel 的 vxlan 方案, 为了实现跨机房的互通, 两个集群的 flannel 连接到同一个 etcd 集群, 这样保障网络配置的一致性. 老版本的 Flannel 存在很多问题, 包括: 路由条数过多, ARP 表缓存失效等问题. 建议修改成网段路由的形式, 并且设置 ARP 规则永久有效, 避免因为 etcd 等故障导致集群网络瘫痪.
Flannel 的使用还需要注意一些配置优化, 默认情况下每天都会申请 Etcd 的租约, 如果申请失败会删除 etcd 网段信息. 为了避免网段变化, 可以将 etcd 数据节点的 ttl 置为 0(永不过期);Docker 默认是会 masq 所有离开主机的数据包, 导致 flannel 中无法获取源容器的 IP 地址, 通过设置 Ipmasq 添加例外, 排除目标地址为 flannel 网段数据包; 由于 flannel 使用 vxlan 的方式, 开启网卡的 vxlan offloading 对性能提升很高. Flannel 本身没有网络隔离, 为了实现 kubernetes 的 network policy 我们采用 canal, 它是 calico 实现 kubernetes 的网络策略的插件.
七, CICD
为了支持 Devops 流程, 在最初的版本我们尝试使用 Jenkins 的方式执行代码编译, 但 Jenkins 对多租户的支持比较差. 在第二版通过 kubernetes 的 Job 机制, 每个用户的编译都会启动一个编译的 Job, 首先会下载用户代码, 并根据编译语言选择对应的编译镜像, 编译完成后生成执行程序, 如果 jar 或者 war 文件. 通过 Dockerfile 打成 Docker 镜像并推送到镜像仓库, 通过镜像仓库的 webhook 触发滚动升级流程.
八, 服务编排
系统设计了应用的逻辑概念, kubernetes 虽然有服务的概念, 但缺少服务的关联关系, 一个完整的应用通常包括前端, 后端 API, 中间件等多个服务, 这些服务存在相互调用和制约的关系, 通过定义应用的概念, 不仅可以做到服务启动先后顺序的控制, 还可以统一规划启停一组服务.
九, 日志
容器的日志归集使用公司自研的 watchdog 日志系统, 每台宿主机上通过 DaemonSet 方式部署日志采集 Agent,Agent 通过 Docker API 获取需要采集的容器和日志路径, 采集日志并发送到日志中心, 日志中心基于 Elasticsearch 开发, 提供多维度日志检索和导出.
十, 监控
容器本身资源监控的性能监控通过 Cadvisor + Prometheus 的方式, 容器内业务的监控集成开源的 APM 监控系统 uav(https://github.com/uavorg/uavstack), 完成应用的性能监控. uav 的链路跟踪基于 JavaAgent 技术, 如果用户部署应用勾选了使用 uav 监控, 系统在构建镜像时将 uav 的 agent 植入到镜像内, 并修改启动参数.
除了上述几个模块外, 系统还集 Harbor 完成容器镜像的多租户管理和镜像扫描功能; 日志审计是记录用户在管理界面的操作, webshell 提供用户的 Web 控制台接入, 为了支持安全审计, 后台会截获用户所有在 webshell 的操作命令并记录入库; 存储管理主要是集成公司商业的 NAS 存储, 为容器直接提供数据共享和持久化; 应用商店主要是通过 kubernetes 的 operator 提供开发和测试使用的场景中间件服务.
十一, 落地实践
11.1 docker 不是虚拟机
在容器推广的初期业务开发人员对容器还不是很熟悉, 会下意识认为容器就是虚拟机, 其实他们不仅是使用方式的区别, 更是实现方式和原理的差异, 虚拟机是通过模拟硬件指令虚拟出操作系统的硬件环境, 而容器是在共享内核的前提下提供资源的隔离和限制. 下图展示了 4.8 内核中 Linux 支持的 7 种 namespace.
换句话说, 其他的都没有差异, 譬如, 时钟, 所有容器和操作系统都共享同一个时钟, 如果修改了操作系统的时间, 所有容器都时间都会变化. 除此之外, 容器内 proc 文件系统也是没有隔离, 看到的都是宿主的信息, 这给很多应用程序带来困扰, JVM 初始的堆大小为内存总量的 1/4, 如果容器被限制在 2G 的内存上限, 而宿主机通常都是 200+G 内存, JVM 很容易触发 OOM, 解决方案通常是启动时根据内存和 CPU 的限制设置 JVM, 或者借助 lxcfs 等.
Cgroup 的资源限制目前对网络和磁盘 IO 的限制比较弱, v1 的 cgroup 只支持 direct IO 的限制, 但实际的生产环境都是些缓存的. 目前我们也在测试 cgroup v2 关于 IO 的限制. 当最新的 CNI 已经支持网络限速, 结合 tc 可以很好的达到这个效果.
11.2 Kubernetes 优化
Kubernetes 自带了很多调度算法, 在启动容器之前会通过调度的算法, 这些算法都是需要过滤所有的节点并打分排序, 大大增加了容器的部署时间, 通过删除一些无用的调度算法, 从而提高部署的速度. 容器采用反亲和的策略, 降低物理机故障对服务造成的影响.
虽然 kubernetes 开启了 RBAC, 但 kubernetes token 还是不建议挂载到业务容器内, 通过关闭 ServiceAccountToken 提升系统的安全.
Docker 镜像存储使用 direct-lvm 的方式, 这样性能更优, 在部署的时候划分单独的 vg, 避免因为 Docker 问题影响操作系统. 通过 devicemapper 存储限制每个容器系统盘为 10G, 避免业务容器耗尽宿主机磁盘空间, 容器运行时需要限制每个容器的最大进程数量, 避免 fork 炸弹.
Etcd 里面记录了 kubernetes 核心数据, 所以 etcd 个高可用和定时备份是必须的, 在 kubernetes 集群超过一百个节点以后, 查询速度就会降低, 通过 SSD 能够有效提升速度. 本系统在 kubernetes 之外通过数据库保存服务和
关注证书的有效期, 在部署 kubernetes 集群时候, 很多都是自签的证书, 在不指定的情况下, openssl 默认一年的有效期, 更新证书需要非常谨慎, 因为整个 kubernetes 的 API 都是基于证书构建的, 所有关联的服务都需要修改.
十二, 总结
Docker 容器加 K8S 编排是当前容器云的主流实践之一, 宜信容器集群管理平台也采用这种方案. 本文主要分享了宜信在容器云平台技术上的一些探索和实践. 本文主要包含了 Nginx 自助管理, 多集群管理, DNS 解析, 网络方案, CICD 服务编排, 日志监控, kubernetes 优化一些技术工作, 以及宜信内部容器云平台化的一些思考, 当然我们还有很多不足, 欢迎各路英雄来宜信进行深入沟通和交流!
来源: https://www.cnblogs.com/yixinjishu/p/11262695.html