技术

并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go细节 codereview mat使用 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 MVCC 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes自动扩容缩容 神经网络模型优化 直觉上理解机器学习 knative入门 如何学习机器学习 神经网络系列笔记 TIDB源码分析 《阿里巴巴云原生实践15讲》笔记 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 编程语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 mesos 的一些tips mesos 集成 calico calico学习 AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 硬件对软件设计的影响 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 学习并发 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型 java exception Linux IO学习 network channel network byte buffer 测试环境docker化实践 netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 mesos深入 Macvlan Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习——整体介绍 zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty(六)netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 Redis概述 机器学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 那些有用的sql语句 异构数据库表在线同步 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM内存与执行 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Kubernetes存储 Kubernetes 其它特性 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 CoreOS 使用 Go学习 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

标签


《持续交付36讲》笔记

2018年10月28日

简介

本文来自极客时间《持续交付36讲》教程,收获很大,除了一些具体的技术,更重要的是一套成体系的理念:即为什么要这么做?每一个小的做法、调整、优化在整体研发效率优化中的位置、作用及意义。

很多话题 研发效能平台 也有阐述。

持续交付的“三观”

组织、团队、工具

  1. 持续交付必须以平台化的思想去看待,单点突破是无力的
  2. 持续交付的实施,也要顺应技术的变迁,善于利用技术红利
  3. 持续交付与系统架构、运维体系息息相关,已经不分彼此

个人再强,放在一个低效的环境下,也无力可施

持续交付是软件研发人员,如何将一个好点子,以最快的速度交付给用户的方法。

标准、规范、流程的落地,都需要载体,而最好的载体就是平台工具

持续交付体系也如同中间件一样,能够从日常的业务研发工作中抽象出来,其不同只在于中间件解决架构问题,而持续交付解决工程问题。这样研发团队能够全力应付业务的需求,而不用总是重复奔波于一些烦人且耗时的工程问题,比如安装测试机、准备编译服务器等等。

学习一下持续交付的内容,它能让你看到更多与编码有关的其他东西,比如不同的编码方式等;也能让你站在更高的角度去看待自己的工作:研发效率的提高往往不是个人能力的提高,而是集体协同效率的提高。

虽然持续交付着重打造的是发布流水线的部分,但它所要达到的目标是在“最终用户”和“研发团队”之间建立紧密的反馈环:通过持续交付新的软件版本,以验证新想法和软件改动的正确性,并衡量这些改动对软件价值的影响。 这里说的“软件价值”,说白了就是收入、日活、GMV 等 KPI 指标了。在保证交付质量的前提下,加快交付速度,从而更快地得到市场反馈,引领产品的方向,最终达到扩大收益的目的。

和devops 的关系

  1. DevOps 的本质其实是一种鼓励协作的研发文化
  2. 持续交付与 DevOps 所追求的最终目标是一致的,即快速向用户交付高质量的软件产品;
  3. DevOps 的概念比持续交付更宽泛,是持续交付的继续延伸;
  4. 持续交付更专注于技术与实践,是 DevOps 的工具及技术实现

代码分支策略,一切的源头

不同的代码分支策略,意味着实施不同的代码集成与上线流程,这会影响整个研发团队每日的协作方式

  1. 主干开发

    • 特征切换在本质上是条件语句中使用的变量,切换为关的代码块类似于被注释的代码,主要用途是避免在发布前的最后一刻因软件合并而产生的冲突。
    • 特性切换需要健壮的工程过程、可靠的技术设计和成熟的特性切换生命周期管理,如果不具备这些能力反而误事。
  2. 特性分支开发

    • 包括git flow、github flow、gitlab flow
    • 特点:极端情况下,对代码的任何修改,包括 Bug 修复、热修复、新功能开发等都在单独的分支中进行。具体的分支策略做出一定的简化。

作者有一个周一到周五的最佳协作案例,可以学习一下。

依赖管理

操作系统级,centos的yum、macos的homebrew等 编程语言的依赖管理工具,比如 Java 的 Maven,Golang 的 go get,Python 的 pip

这些平台的解决思路都是将依赖放到共同的仓库,然后管理工具通过依赖描述文件去中央仓库获取相应的包。

Maven 最佳实践

  1. 生产环境尽量不使用 SNAPSHOT 或者是带有范围的依赖版本
  2. 将 POM 分成多个层次的继承关系,比如定义公司级的pom.xml(super-pom)、部门级的pom.xml、项目级别的pom.xml,每个项目必须直接或间接的继承super-pom

测试环境治理

分类及什么是好的环境

  使用者  
开发环境 开发  
功能测试环境 开发、测试  
验收测试环境 产品、测试  
预发布环境 测试  
生产环境 用户  

当一个环境可以满足其真正核心用户的需求时, 就是一个好用的测试环境。

  1. 开发关注效率
  2. 测试关注可靠性
  3. 产品关注真实的用户体验和产品完整性
  4. 预发布环境的需求其实来自于运维同学,他们需要保证生产环境的稳性,减少生产环境的变更,所以需要将预发布环境与线下环境完全隔离(PS:用真实的环境帮助发现错误)。

环境配置

什么是环境配置

环境配置,此配置非彼匹配,配置是环境管理中最核心的内容,包括:

  1. 以环境中每台服务器为对象的运行时配置,以一个 Java Web 应用为例,需要哪些运行时配置呢?

    • 安装 war 包运行依赖的基础环境,比如 JDK,Tomcat等
    • 修改 Tomcat 的配置文件
    • 配置 Jvm 参数
    • 考虑操作系统参数,比较常见的一个配置是 Linux 的文件句柄数
  2. 以一个环境为整体目标的独立环境配置。

    • 这个环境所依赖的数据库、缓存该如何配置
    • 如果是微服务架构,就需要考虑微服务治理框架、配置中心等一系列中间件的配置问题。
    • 环境对应的基础服务,比如监控,短信,搜索等。

环境配置理念

  1. 环境一定要标准化。解决复杂问题的办法:分拆、简化、标准化
  2. 约定大于配置
  3. 让环境自己能开口说话,通过环境的自描述文件,让环境能讲清楚自己的作用、依赖,以及状态,而不是由外部配置来解释这些内容。PS:比如部署文件是写在项目里(告诉调度胸系统,要如何部署这个项目),还是由用户额外配置?

    • 服务器生成时,写入它自己的描述文件。我们通常把这个文件命名为“Server Spec”。在这个文件里,记录了这台服务器的所有身份信息,包括:IDC,型号,归属环境,作用,所属应用,服务类型,访问路径等。PS,这个是一个新鲜的搞法,每个容器可以搞一个Container.Spec,你在新建一个容器时,可以向容器写入一个Container.Spec,告知所属的环境,作为各个中间件的约定信息源
    • 中间件根据 Server Spec 的描述,寻找到它所在环境对应的配置中心。
    • 完成服务自发现,根据服务类型,访问路径等,还可以自动生成对应的路由配置,负载均衡配置等。我们是在尝试把环境配置的方向调个个儿:由原来外部通过配置告知环境应该干什么,转变成环境根据自身的能力和属性,决定自己应该去干什么。PS:以前负载均衡配置都是外部软件监控调度系统写入负载均衡组件,外部做和自描述,得好好体味一下,感觉可以做个调度系统框架嵌在项目中来完成这些事儿。另外,google jib 也有点这个意思,根据代码做镜像,而不是将代码无差别的打成war做镜像。

环境配置方法

  1. 构建时配置,比如maven的profile。问题,改配置就要重新构建一次。
  2. 打包时配置,以java 为例,作者应该指的是生成class 之后不要立即打成war,而是延迟到发布时。为什么要从构建独立分离出打包这个步骤呢?为了一次构建,多次部署。打包时配置的基本思想是:构建时完全不清楚程序所要部署的环境,因此可根据环境信息,进行相关配置的替换。具体可参见作者举的内部工具的例子。
  3. 运行时配置

    • 修改后实时生效
    • 支持灰度发布
    • 能分环境、分集群管理配置;
    • 有完善的权限、审核机制。

    特有问题:配置不会随着代码回滚。

环境创建、拆分与合并

  1. 环境创建,就是不断提高虚拟机准备和应用部署两个流水线的速度和稳定性

    • 环境构建流水线
    • 应用部署流水线
  2. 环境拆分,当有新项目时,开发人员会挑选部分应用,组成一个独立的子环境。这里的重点是,要保证子环境和完整环境的调用是互相隔离的。
  3. 环境合并,当存在多个子环境时,可能在某个时间点需要做多个项目的集成,这时开发人员需要合并多个环境。需要注意合并后的环境冲突,一般将这些冲突罗列出来,交由用户选择决策。

环境的创建和拆分,最主要的问题就是如何复制和重新配置环境中的各个零件。此外要处理:

  1. 用户访问应用的入口管理
  2. 应用之间调用链的管理
  3. 对数据库的访问

    • 数据库连接串的维护问题,与 SOA 调用链(即服务之间的调用关系)的维护类似
    • 二是,数据库的快速创建策略。提供基准库和数据库脚本变更接口,创建环境时根据基准库执行变更接口

容器对持续交付的影响

不可变基础设施(Immutable Infrastructure)是 Chad Fowler 在 2013 年提出的一个很有前瞻性的构想: 在这种模式中,任何基础设施的实例(包括服务器、容器等各种软硬件)一旦创建之后便成为一种只读状态,不可对其进行任何更改。如果需要修改或升级某些实例,唯一的方式就是创建一批新的实例来替换它。其好处包括:

  1. 很多与 runtime 相关的配置工作都可以被简化,这让持续集成与持续部署过程变得更流畅。
  2. 它也更易于应对部署环境间的差异及版本,进行更有效、全面的管理
  3. 对回滚来说,更是得到了充分的保证,只要原先版本的镜像存在,它就一定能被修复。

没有容器之前,交付标准包括软件环境(也就是所谓的机器)和软件代码两部分。交付系统更关注的是软件代码,环境一旦产生后,我们就不再关心或者很难再干预用户后期是如何对其做变更的了。容器技术则统一了软件环境和软件代码,重新定义交付标准。

容器比较轻量

  1. 具备分发和存储的优势
  2. 启动容器也不需要启动整个操作系统
  3. 体积小+启动快+镜像版本控制,最适合做不可变基础设施
  4. 镜像带来的环境一致性,交付结果一致
  5. 应用的启动统一成了运行镜像,交付自动化与个性化

容器技术已经解决了很多问题。比如,服务器操作系统级别的依赖的标准化更容易了;当出现硬件故障时,迁移和恢复服务也更加方便了。但容器/云计算不是银弹,比如容器技术并没有解决故障定位的问题、不能很好的满足用户的个性化需求等。

构建

Maven Enforcer 插件

  1. 明确操作系统、maven、jdk版本
  2. 明确依赖jar的版本
  3. 显式报错依赖jar版本冲突
  4. 依赖jar中是否包含同样命名的class
  5. 其它内置检查规则,PS:非常值得学习
  6. 自定义的 Enforcer 检查规则
  7. 对于其它语言则实现通用的依赖检查服务

一个组织的集成和构建往往是周期性的,高峰和低谷都比较明显,而且随着组织扩大,幅度也有所扩大。所以,如果按照高峰的要求来配备 Slave 实例数,那么在低谷时,就很浪费资源了。反之,又会影响速度,造成排队。

镜像构建环境(根据dockerfile创建容器)容器化

  1. Docker Out Of Docker(DooD)
  2. Docker In Docker(DinD)

镜像发布不能很好的满足用户的个性化需求:容器内的环境也是由发布系统定义的,用户即使登录到容器上去做变更,下一次发布之后还是会被回滚回来。但是,对 Dockerfile 的编写和控制需要一定的学习成本,因此我们又不可能将镜像的内容与构建流程完全交给用户来自定义。

对于完全自定义镜像发布我们使用 Docker 多阶段构建(multi-stage build),也就是说用户可以将构建代码和构建镜像合并成一个步骤,在同一个 Dockerfile 中完成。(这个要研究下)

发布

蓝绿发布、滚动发布、金丝雀发布总是分不清楚,其脉络是一致的

  1. 集群状态下,不能一波流一起发,只能一个一个发
  2. 蓝绿发布:新启动一批对等的实例,在新实例上更新,然后再用新实例替换老实例。此时老实例仍旧存在,以便回滚。 而金丝雀发布是在现有实例上更新,回滚便麻烦了一点。

待解决,从这个角度看,我们用k8s service 作为pod 的入口,作用不仅是负载均衡,同时也解决滚动发布时,流量损失的问题。

平台化

互联网厂商平台化的玩法,往往是指自己搭台子,让其他人唱戏。也就是说,由互联网厂商自己提供一些基础保障能力,建立必要的标准,从而形成底层支撑平台;而由其他供应商或用户利用这个底层平台提供的服务,自己完成具体业务、功能流程设计,从而达到千人千面的个性化服务能力。

  1. 确定模块及其范围

  2. 学会做减法,并不是只有完整的端到端自动化才叫“持续交付”,代码管理,集成编译,环境管理、发布部署这四大核心模块,其实就是一个交付的闭环。挑选最为重要或最为急迫的模块,优先加以实施。另外一种做减法的方式是减少纵向的深度,比如只支持容器部署服务化java应用,再逐步向scala等提供支持。
  3. 制定标准,对持续交付平台的设计来说,最重要的标准是定义各个模块交付产物的标准
  4. 选择合适的驱动器,小团队用jenkins,较大规模团队自研
  5. 抽象公共能力,账户与权限、用户行为日志、消息通知、安全与故障处理等
  6. 选择用户入口,统一站点、命令行格式、ide插件、jenkins界面,比较建议为持续交付独立形成一个 Portal,这样不会受到其他系统的限制,你可以根据自己的设计更好地完成用户引导和使用体验
  7. 聚力而成

从中体会到的几点(也是其他地方看到的)

  1. 迭代一步到位

其它

Jenkins主要用作异步任务驱动,携程只有编译系统使用Jenkins

个人微信订阅号