聊聊代码 | 基于Shell,Jenkins+Docker+Kubernetes的持续集成与部署
原文 | http://www.dockone.io/article/3601
随着近年来业务的拓展,业务测试上线需求频繁,流程也越来越复杂,同时面对项目和环境的增加,人手不足的问题也导致应对这些变化时压力较大,响应缓慢。因此,为解决这些问题,我们在充分研究后利用Jenkins+Docker+Kubernetes来解决这些问题,真正解放了运维的双手。本次分享主要从 Jenkins+Docker+Kubernetes流程入手,通过实例演示为大家介绍我们的实践之路。
内容概览
- 使用 Jenkins和容器解决之前测试环境需要测试人员按照文档全手工命令行部署,流程复杂易出错,上线部署流程不透明且不易控制的问题;
- 使用Jenkins和容器解决实际工作中的环境差异,统一版本;
- 使用Kubernetes和Ceph解决第三方数据/缓存服务的运行和数据持久化;
- 使用Jenkins和Kubernetes解决多环境下开发、QA、预发布(及灰度,AB)、生产等环境的部署与维护;
- 使用k8s内置健康检查机制,更快速的发现故障容器并自动恢复,解决以往项目多点部署监控覆盖不全面(自动化)问题;
- 使用Kubernetes和容器来替代在物理机中运行的KVM 虚机,提高资源利用率,解决虚机创建、迁移、扩容、故障恢复等难题。
1
需求
1.项目类型复杂繁多
- 50+ PHP
- 20+ NodeJS
- 70+ Java
- 60+ H5
需要一个更好的方式来解决环境、版本和依赖问题。
2.种类繁多的服务
- ActiveMQ
- Elasticsearch
- Filebeat
- Grafana
- Hadoop(HDFS)
- Httpd,2.2、2.4
- Kibana
- Logstash
- Memcached
- MongoDB
- MySQL,5.6、5.7
- Nexus
- Nginx
- PHP,5.5、5.6, 7.1
- Redis
- Solr
- SonarQube
- Tokyotyrant
- Tomcat,6.0、7.0、8.5
- Zipkin
- Zookeeper
如何更好的管理这些服务?
3. 多测试环境
- 由于部分项目调用关系复杂,测试周期长,因此存在同时运行多套测试环境需求
- 多环境的配置
多环境下Jenkins任务如何管理?发布流程如何处理?
4. 运行环境
- 使用KVM进行虚拟化,每台物理机运行3-5个虚拟机,每虚拟机运行1-5个项目,资源利用率不高
- 虽然虚拟机克隆简单,但仍然是系统级别,环境与服务需要安装配置,操作管理及扩容不便
需要更方便友好与自动化的管理方式,提高资源利用。
5. 发布与回滚流程
- 原有的上线系统为自行开发的项目,功能单一且状态不透明
- 原测试环境发布为测试人员SSH进入虚机纯手工命令行操作,复杂易出差错
需要一个简便易用且功能强大,状态透明,自动化程度高的CI。
2
方案
- 使用Docker解决运行环境的版本与依赖
- 使用Kubernetes运行各种服务,通过kube-dns完成内部解析
- 在Kubernetes上利用不同的Namespace区分不同环境,Nginx通过监听不同IP实现环境分离
- 在物理机上直接运行Docker+Kubernetes
- 使用Jenkins统一管理所有环境任务的部署与回滚,可靠易用、自动化程度高、流程可控
3
部署
1.部署
- 重复任务都配置到Jenkins,对于授权用户可 一键执行,显著减少运维处理杂务时间,特别是项目的部署和回滚,做到基本上无需运维参与
- 为简化流程,提供更方便易用的部署与回滚操作,将所有操作封装到Jenkins任务中
- 减少对Jenkins插件的依赖
- 大规模使用"抽象化"的Shell脚本,高度可复用, 便于维护
2. Jenkins 设置
- 全部使用*风格任务
- 替换Jenkins默认Shell,以便能直接加载Shell文件
- /data/app/jenkins/config/bash-slice
-
- 将 /data/app/jenkins/config/settings 注入到Jenkins任务中每个Shell脚本头部(source 方法)
- 同时禁用 Jenkins 默认执行Shell时的-x参数
3. Jenkins 流程
拉取项目代码
- 检查与设置变量
- 预处理(如果需要)
- 编译(如果需要)
- DockerfileInit 函数复制Dockerfile、Deployment 与Service模版文件
- 在Dockerfile文件基础上根据当前项目生成Dockerfile
- 如果模版目录内存在与项目同名文件夹则使用,否则选择默认通用模版
- DockerImage Build 函数构建Docker镜像
-
- 默认镜像名称为registry.xxx.com/project/subject/project-name:scm-revision
- 为加快Tomcat启动速度,我们直接把项目目录放到webapps目录内,而不是复制war包,这个步骤可以减少10-30秒的解压包时间
- 在Harbor中,每个镜像都会跟代码库中的Tag对应
- DockerLogin函数先登录Registry
- DockerImage Push函数推送Docker镜像到仓库(Harbor)
- ReplaceKubeFile函数将Deployment与Service模版中相关值进行替换
- 动态设置JAVA_OPTS(-Xms -Xmx)
-
- 根据LIMIT_MEM 自动设置JAVA_OPTS变量并传递到容器内
- Xms 与 LIMIT_MIN_MEM 相等
- Xmx 为 LIMIT_MAX_MEM 减去128MB值
- 使用kubectl更新、删除、创建Deployment & Service
- 至此,一个任务的流程就结束了
4. Jenkins任务示例
Java 项目的处理:
Execute Shel
- 载入payment文件同时引入(payment文件内置)默认变量
- 定义变量并传递给 PaymentController 函数,覆盖默认变量
- 在Jenkins一个Java任务中,除git/svn设置外,仅需要输入这些脚本即可
- 不同Java项目之间差异通过变量进行定义
- 对于用户,其只是在Jenkins上选择了一个代码的Tag,鼠标再点了一下 构建,简单至极
4
数据持久化
1. 分布式文件系统
需求
- MySQL、MongoDB、Hadoop等服务需要对数据进行持久化,为能支持迁移与恢复,持久化的数据需要在任意节点都能访问
- 不少项目存在配置文件与公共库文件需共享给指定某些项目使用问题,在每个节点都存放一份肯定是不现实的,因此我们需要分布式的文件系统
NFS
- 刚开始时我们尝试了NFS,除性能稍慢之外并无其它问题,直到某天NFS故障导致所有客户端主机卡死
GlusterFS
- 为解决NFS问题,临时搭建了一个GlusterFS集群,安装配置简单方便,但因仅作为过渡阶段解决方案所以并没有太深入研究
Ceph
- 在经过一段时间的研究和测试后,我们选定了Ceph作为分布式文件系统
- 第一阶段:使用了3个节点,每节点配置普通SAS一块作为Journal,配三块600G SAS盘作为OSD,能满足基本文件需求,但无法支撑MySQL
- 第二阶段:使用3个节点,每节点配置三星PRO850 512G SSD一块作为 Journal, 配三块600G SAS盘作为OSD,将集群网络与公共网络分离(全千兆网络),然后由k8s通过 rbd 和 PV/PVC提供给数据库等服务使用 在实际日常使用中监控到的数据,IOPS 最高达到25K, 读写最高120MB/s。
- 第三阶段:为整个公司内部提供文件存储支持。经过一次在线扩容后,节点增到5个,OSD增加到17个。利用 CephFS挂载到Linux主机,通过iSCSI挂载rbd到Windows Server,提供高可靠大容量,支持在线扩容的内部分布式文件系统
5
日志1. 日志收集
- 由Filebeat转发至Redis
- Logstash从Redis中读出并发送至Elasticsearch集群
- 使用Redis中转也是为了解决过多客户端连接Elasticsearch导致报错问题
2. 日志分析
- 与Zabbix结合进行监控
- 使用Kibana查看和分析日志
6
管理
1. 集群管理
- 通过命令行在服务器上直接操作
- 通过Dashboard上进行操作, 支持移动设备
2. 项目调试
- 工作中会遇到项目出错需要调试的情况,不同于虚拟机,开放一个用户通过SSH就可以,在容器中进行调试存在以下问题:
- 默认的kubectl用户(admin)权限太高
- Dashboard默认权限也太高
- 容器中未安装和运行sshd服务,无法通过 ssh 连接
- 众多Pod中如何定位项目?
- 经过研究,我们通过为不同项目组(对应不同的Namespace)创建各自的账号,与TLS证书和RBAC权限组合,实现集群的权限控制,让开发者可以通过 kubectl 查看(get)Namespace中的 Pods, Services, Deployments等等资源,并可以通过 kubectl exec 进入容器执行命令。
-
- RBAC for use java
- Certificate for user java
3. 容器安全
- 容器以普通用户运行,需要sudo的操作在Jenkins生成Dockerfile过程中写入sudoers,限定操作,在启动后无法通过sudo/su等方式切换到root,因此可以一定程度的提高安全性
- 容器内项目文件为root所有且只读,指定的目录可写,容器运行用户与容器内服务运行用户分离,确保项目的代码文件也无法进行更改
4. 镜像安全
- 因后期把项目代码嵌入了镜像,为确保代码不外泄,原来的镜像拉取策略就需要进行调整:
- 在Harbor中按项目组新建私有Project, 创建一个Guest用户 web
- Deployment 中增加 imagePullSecret,在namespace中创建 secret 使用 web 用户账号信息拉取镜像
5. 运行用户
- 利用容器化的机会,我们重新将所以项目以普通用户(runAsUser)运行在支持 SELinux的容器内
- 先下载hosts文件保存为 /tmp/hosts
- 将当前/etc/hosts 附加到 /tmp/hosts
- sudo cp -rf /tmp/hosts /etc/hosts 进行替换
- 因业务需要,容器启动后一些文件和目录需要修改权限:
- /etc/hosts 文件需要替换
- 项目代码为 root 所以,httpd 以nobody用户运行,因此一些目录(如缓存等)需要nobody可写
- 为何选择 sudo :
- 在镜像构建完成即同时设置了sudo权限,容器启动后即无法更改
- 严格限制,仅对必须执行的命令进行完整匹配的授权
- 除明确允许命令外一概无法通过sudo执行
- Line5 - 15, 仅允许sudo cp -rf /tmp/hosts /etc/hosts, 因此:
- Line17 - 32,检查全局变量WRITEABLE_DIRS不为空时,使用循环更改目标目录为nobody所有
6. API SERVER 8080 安全防护(限制)
- 对 API SERVER 8080 端口进行限制,将必须访问 8080 服务调度到指定节点运行,其它节点的访问全部拒绝。
7. 监控
- Prometheus 的结合
- 自动发现,监控与通知等还在研究
- 使用Zabbix进行监控
- 从外部监控 Service
- 监控 Deployments 状态,DESIRED、CURRENT与AVAILABLE之间的比例
- 监控Pod运行状态,采集重启次数过于频繁(AGE与RESTARTS比例)的Pod信息
7
规划1. 测试环境
- 目前我们的测试环境已经全部做到了容器化;
- 通过测试环境的长期运行与不断完善,生产环境容器化也正在进行中。
2. 生产环境
- 上线流程
- 在生产环境服务不中断、不增加机器和机柜的条件下完成
- 使用乾坤大挪移,先腾出一批机器搭建基本集群
- 然后逐批迁入服务至容器,与现有虚机服务并行运行
- 经过一段时间观察,逐步退出虚机服务,将腾出的机器重装系统后加入Kubernetes集群
- 同时将Ceph节点与Kubernetes API Server分布在不同机柜内,防止机柜电源/交换机故障
- 扩容的集群继续迁入更多服务,如此往复,最终完成平滑过渡
- 线下Harbor向线上Harbor进行复制
- 测试环境构建的镜像测试通过后直接应用到生产环境,挂载本环境配置文件即可
- 完善健康检查设置,调整资源限制(resources),防止资源超售可能导致的节点雪崩效应
3. Jenkins
- 继续完善脚本
- 研究Pipeline流水线
4. Kubernetes
- 版本升级
- 更深入的研究
5. Ceph
- 集群优化与分离
所用脚本:https://github.com/Statemood/jenkins
- 7种方法 实现在生产中自动化Kubernetes集群