如果给你一支外墙清洗工程队,你会如何管?

本篇文章只是笔者看到外墙清洗工后,在管理方面思考的总结。笔者是外墙清洗行业的外行,所以,本文可能存在一些错误的假设。期望这些可能错误的假设不影响管理方面的思考。 引发思考的起因 某天上班,仰头看到大厦清洗外墙工人悬挂在20层楼的半空中作业,除了觉得他们危险外,脑海浮现一个问题。如果作为外墙清洗工的管理者,我们如何保证大厦的外墙的每一处被清洗干净了。 我们经常听管理者这么说: 我只要一个结果,不管过程。 可是这个“结果”是什么呢? 谁来定义“结果” 回到外墙清洗的管理,作为工程队的管理者,我们要的结果是什么呢? 面对这个问题,我们要马上回答的并不是“结果”的定义是什么。而是要问:这个问题本身应该由谁来回答。是由老板回答、管理者回答、还是由清洗工回答(我们假设管理者与老板这两种角色由不同的人担当)? 也就是说,对于不同角色的人,对于“结果”的定义是不一样的。 清洗外墙作为一门生意,结果当然是以最低成本赚最多钱。对于这一点似乎不用定义了。 但是,这个结果只是“老板”希望自己得到的结果。它与管理者希望清洗工给的结果是两回事。如下图所示。 至此,我们发现“谁来定义结果”这个问题,应该换个问法:谁来确定管理者想要的结果与老板想要的结果之间的关系? 笔者认为应该由管理者与老板共同确定。 如何确定不同层次结果之间的关系 管理者想要的结果与老板想要的结果处于不同的层次。那么如何确定不同层次结果之间的关系,这个问题由老板与管理者共同回答。 如果是把清洗外墙是一次性的生意,结果很准确,就是收到甲方的款。不论外墙是否真的被清洗干净了,因为贿赂验收人员我们一样能得到想要的结果。我们甚至可以设置一个部门专门搞定验收人员。这不是本文要讨论的范围。 如果基于企业长期发展的考虑,我们希望能把洗墙这项业务做好。最终会得到老板想要的结果。 本文,我们假设保证大厦外墙的每一处都被清洗干净是长期正作用于老板想要的结果的。如下图所示。 接下来,下文所说的“结果”均指管理者的结果。 如何验证结果 绕了一圈,终于把结果定义好了。现在咱们把“结果”定义为在文章开头就提了的: 保证大厦外墙的每一处都被清洗干净 但是我们如何验证这个结果呢?我们的管理者不可能也把自己挂在高高的外墙眼睁睁地检查清洗工洗过的每一处。就像产品经理不可能自己打 IDE 一行行的检查程序员的代码。 如果管理者无法做到,加一个监工不就行了?但是谁又来监督这个监工的工作呢?也就是谁来保证监工的工作做到位了呢? 还有一种办法:让工人相互监工。这样,花两个人的工资,顶三个人的活。可是,在企业里待过的人动下脑子都知道这个方案就不可靠。他们会串通的嘛。因为“偷懒”符合他们的共同利益。(在信息透明度高的情况下,让人相互监工是可行的。比如结对编程。) 以上都是臆造出来的解决方案。都是基于“监工”的模型。看看我们的身边,是不是也是这样? 回到我们的问题本身:如何确保大厦外墙的每一处都被清洗干净?我们换一种方式解决。 如果采用“激励”的方式呢?假如请了10个清洗工人,等最后清洗完成后,对这10个清洗工人的工作进行ABC评级。得A的人可以得到原来薪水的1.2倍,得B的人保持不变,得C的人是原来薪水的0.8倍。是不是感觉这套路很熟悉? “激励”的模型虽好,解决的是清洗工人的积极性问题,还是没有解决我们的根本问题:如何验证结果。 定期验收 也许我们可以把“每一处”的标准降低。在所有的清洗工中,选一个最合适的人作为清洗工组长,让其工资高于一般的清洗工人。组长的其中一个重要职责是定期对玻璃进行验收。至于定期的周期设为多长,涉及到工作的反馈周期了,属于另一个问题了。暂不讨论。 虽然这样管理者不用自己被挂在外墙,但是“保证大厦外墙的每一处都被清洗干净”的结果强依赖于组长的责任心。 这个模型的缺点是管理者想要的结果必须强依赖于一个很不稳定的因素:组长的责任心。 用对比法解决 突然有一天,我再看大厦的时候想到,用清洗前和清洗后的大厦照片对比不就可以了吗? 也就是在保证环境一致的情况下,在清洗前拍一次,在清洗后再拍一次。只要照片足够高清,你想对比多细节都可以。更进一步,我们甚至可以使用软件进行自动对比。 使用此种办法,除了解决以上方案的缺点,还使得“保证大厦外墙的每一处都被清洗干净”的结果从模糊变成准确被量化了。 这意味着什么? 更短的反馈周期:每天都可以当天的清洗情况。这对于能否按时完成工作非常重要。 更准确的定义“干净”:怎么样才算干净,一对比就知道。同时,清洗工之间绩效对比也有了。 成本更低的检查“每一处”:由于只需要坐在电脑前进行检查,成本会低很多。可能不需要多发一份组长的工资了。 最后,笔者想找同一建筑的两张图进行对比,但是实在找不到。读者朋友就脑补一下吧。 使用对比法就是最终答案了吗?不是的。也许,将来成本更低的办法是使用外墙清洗机器人会代替人工。也许这样更容易得到老板想要的结果。 更甚至于搞个 AI 外墙清洗,然后融个资?笔者调皮了。 后记 以上内容虽不能完全重现笔者的思考过程。但是大体思路就是这样的。这个思考过程,在软件工程方面,笔者认为是相通的。 产品经理必须思考做出来的软件功能是否正作用于老板想要的结果;必须想办法加快反馈;必须想办法更低成本的检查程序员的工作结果;必须想办法让所有人准确理解需求。 同时,本文还有很多方面没有讨论,比如我们是否需要以及如何关心每个人想要的结果;清洗工也是人,企业中的人文关怀问题等等。 最后,说明一下,我并不想挑战别人的专业。以上只是讨论。欢迎大家交流 参考 玻璃外墙清洗的准备工作与注意事项:http://www.fjyybj.com/news/517.html 外墙清洗机器人现身多幢大楼,清洗前后泾渭分明! https://juejin.im/post/5c73c3a251882562e5445024 人类真的是趋利避害的吗?:https://www.zhihu.com/question/60711385

2019-05-26 · 1 min · 59 words · 翟志军 Jack Zhai

如何对 Jenkins 共享库进行单元测试

Jenkins 共享库是除了 Jenkins 插件外,另一种扩展 Jenkins 流水线的技术。通过它,可以定义轻松的自定义步骤,还可以对现有的流水线逻辑进行一定程度的抽象与封装。至于如何写及如何使用它,读者朋友可以移步附录中的官方文档。 对共享库进行单元测试的原因 但是如何对它进行单元测试呢?共享库越来越大时,你不得不考虑的问题。因为如果你不在早期就开始单元测试,共享库后期可能就会发展成如下图所示的“艺术品”——能工作,但是脆弱到没有人敢动。 [图片来自网络,侵权必删] 这就是代码越写越慢的原因之一。后人要不断地填前人有意无意挖的坑。 共享库单元测试搭建 共享库官方文档介绍的代码仓库结构 (root) +- src # Groovy source files | +- org | +- foo | +- Bar.groovy # for org.foo.Bar class +- vars | +- foo.groovy # for global 'foo' variable | +- foo.txt # help for 'foo' variable +- resources # resource files (external libraries only) | +- org | +- foo | +- bar.json # static helper data for org.foo.Bar 以上是共享库官方文档介绍的代码仓库结构。整个代码库可以分成两部分:src 目录部分和 vars 目录部分。它们的测试脚手架的搭建是不一样的。 ...

2019-05-25 · 4 min · 665 words · 翟志军 Jack Zhai

使用 Jenkins + Ansible 实现 Springboot 自动化部署101

本文要点: 设计一条 Springboot 最基本的流水线:包括构建、制品上传、部署。 使用 Docker 容器运行构建逻辑。 自动化整个实验环境:包括 Jenkins 的配置,Jenkins slave 的配置等。 1. 代码仓库安排 本次实验涉及以下多个代码仓库: % tree -L 1 ├── 1-cd-platform # 实验环境相关代码 ├── 1-env-conf # 环境配置代码-实现配置独立 └── 1-springboot # Springboot 应用的代码及其部署代码 1-springboot 的目录结构如下: % cd 1-springboot % tree -L 1 ├── Jenkinsfile # 流水线代码 ├── README.md ├── deploy # 部署代码 ├── pom.xml └── src # 业务代码 所有代码,均放在 GitHub: https://github.com/cd-in-practice 2. 实验环境准备 笔者使用 Docker Compose + Vagrant 进行实验。环境包括以下几个系统: Jenkins * 1 Jenkins master,全自动安装插件、默认用户名密码:admin/admin。 Jenkins agent * 2 Jenkins agent 运行在 Docker 容器中,共启动两个。 Artifactory * 1 一个商业版的制品库。笔者申请了一个 30 天的商业版。 使用 Vagrant 是为了启动虚拟机,用于部署 Springboot 应用。如果你的开发机器无法使用 Vagrant,使用 VirtualBox 也可以达到同样的效果。但是有一点需要注意,那就是网络。如果在虚拟机中要访问 Docker 容器内提供的服务,需要在 DNS 上或者 hosts 上做相应的调整。所有的虚拟机的镜像使用 Centos7。 ...

2019-05-15 · 2 min · 408 words · 翟志军 Jack Zhai

基于 Jenkins 的 DevOps 平台应该如何设计凭证管理

背景 了解到行业内有些团队是基于 Jenkins 开发 DevOps 平台。而基于 Jenkins 实现的 DevOps 平台,就不得不考虑凭证的管理问题。 本文就此问题进行讨论,尝试找出相对合理的管理凭证的方案。 一开始我们想到的方案可能是这样的:用户在 DevOps 平台增加凭证后,DevOps 再将凭证同步到 Jenkins 上。Jenkins 任务在使用凭证时,使用的是存储在 Jenkins 上的凭证,而不是 DevOps 平台上的。 但是,仔细想想,这样做会存在以下问题: Jenkins 与 DevOps 平台之间的凭证数据会存在不一致问题。 存在一定的安全隐患。通过 Jenkins 脚本命令行很容易就把所有密码的明文拿到。哪天 Jenkins 被注入了,所有的凭证一下子就被扒走。 无法实现 Jenkins 高可用,因为凭证存在 Jenkins master 机器上。 那么,有没有更好的办法呢? 期望实现的目标 先定我们觉得更合理的目标,然后讨论如何实现。以下是笔者觉得合理的目标: 用户还是在 DevOps 管理自己的凭证。但是 DevOps 不需要将自己凭证同步到 Jenkins 上。Jenkins 任务在使用凭证时,从 DevOps 上取。 实现方式 Jenkins 有一个 Credentials Binding Plugin 插件,在 Jenkins pipeline 中的用法如下: withCredentials([usernameColonPassword(credentialsId: 'mylogin', variable: 'USERPASS')]) { sh ''' curl -u "$USERPASS" https://private.server/ > output ''' } withCredentials 方法做的事情就是从 Jenkins 的凭证列表中取出 id 为 mylogin 的凭证,并将值赋到变量名为 USERPASS 的变量中。接下来,你就可以在闭包中使用该变量了。 ...

2019-05-07 · 1 min · 160 words · 翟志军 Jack Zhai

Jenkins 自动安装插件

手工安装 Jenkins 插件的方法 通常,我们有两种方法安装 Jenkins 插件。第一种方法是到 Jenkins 插件管理页面搜索插件,然后安装。第二种方法是上传 Jenkins 插件的 hpi 文件安装。这两种方法能满足大多数人的需求。 第一种方法,如下图所示: 第二种方法,如下图所示: 但是对于需要保证 Jenkins 稳定或在 Jenkins 上进行二次开发的同学来说,以上方法是无法满足需求的。 第一种方法是无法指定插件的版本。第二种方式必须自己找到该插件的依赖树,一个个依赖的安装。是的,手工上传插件的这种方法,Jenkins 是不会自动下载依赖的。 自动安装插件的方法 那么,有什么方法能做到即指定插件的版本,又能自动下载它的依赖呢? 幸运的是,Jenkins 的 Docker 镜像的代码仓库里的 install-plugins.sh 脚本已经实现。只不过需要我们拿过来小小修改才能使用。笔者修改后创建了相应的代码仓库:jenkins-install-plugins-shell 。链接在文章末尾。 以下是 jenkins-install-plugins-shell 的使用方法: 将代码 clone 到 JENKINS_HOME 目录中。 cd $JENKINS_HOME git clone https://github.com/zacker330/jenkins-install-plugins-shell.git cd jenkins-install-plugins-shell 在 plugins.txt 中加入希望安装的插件 在 jenkins-install-plugins-shell 目录中,有一个 plugins.txt 文件,在文件中写入希望安装的插件及版本号。例如: ansible:1.0 powershell:1.3 执行安装 # Jenkins War 的路径,用于分析 export JENKINS_WAR_PATH=<Jenkins war文件的路径> chmod +x install-plugins.sh jenkins-support ./install-plugins.sh < plugins.txt 重启 Jenkins install-plugins 本质上做的事情就只是将插件从云端下载到 JENKINS_HOME 下的 plugins 目录中。要使安装的插件生效,还需要重启 Jenkins。 关于 Jenkins 插件的名称 Jenkins 插件有两个名称。一个叫 display name,一个叫 short name。比如 Ansible 插件的 disply name 为 Ansible plugin,short name 为 ansible。 ...

2019-04-27 · 1 min · 126 words · 翟志军 Jack Zhai

使用 Jenkins + Ansible 实现自动化部署 Nginx

本文介绍如何使用 Jenkins + Ansible 实现对 Nginx 的自动化部署。最终达到的效果有如下几点: 只要你将 Nginx 的配置推送到 GitHub 中,Jenkins 就会自动执行部署,然后目标服务器的 Nginx 配置自动生效。这个过程是幂等(idempotent)的,只要代码不变,执行多少遍,最终效果不变。 如果目标机器没有安装 Nginx,则会自动安装 Nginx。 自动设置服务器防火墙规则。 1. 实验环境介绍 本次实验使用 Docker Compose 搭建 Jenkins 及 Jenkins agent。使用 Vagrant 启动一台虚拟机,用于部署 Nginx。使用 Vagrant 是可选的,读者可以使用 VirtualBox 启动一个虚拟机。使用 Vagrant 完全是为了自动化搭建实验环境。 以下是整个实验环境的架构图: 注意,图中的 5123 <-> 80 代表将宿主机的 5123 端口请求转发到虚拟机中的 80 端口。 Vagrant:虚拟机管理工具,通过它,我们可以使用文本来定义、管理虚拟机。 Ansible:自动化运维工具 Docker Compose:它是一个用于定义和运行多容器 Docker 应用程序的工具。可以使用YAML文件来配置应用程序的服务。 2. 启动实验环境 克隆代码并进入文件夹 git clone https://github.com/zacker330/jenkins-ansible-nginx.git cd jenkins-ansible-nginx 构建 Jenkins agent 的镜像 需要自定义 Jenkins agent 镜像有两个原因: ...

2019-04-22 · 3 min · 432 words · 翟志军 Jack Zhai

你觉得能通过共享 JENKINS_HOME 目录实现 Jenkins master 的高可用吗?

审校:王冬辉,linuxsuren Jenkins master 的高可用是个老大难的问题。和很多人一样,笔者也想过两个 Jenkins master 共享同一个 JENKINS_HOME 的方案。了解 Jenkins 原理的人,都会觉得这个方案不可行。但是真的不可行吗? 由于工作原因,笔者需要亲自验证以上猜想。 JENKINS_HOME 介绍 Jenkins 所有状态数据都存放文件系统的目录中,这个目录被称为 JENKINS_HOME 目录。 实验环境介绍 笔者通过 Docker compose 启动两个独立的 Jenkins master,分别为 jenkins-a 和 jenkins-b。它们共用同一个 JENKINS_HOME 目录。相应的代码仓库的链接放在文章底部。 将代码克隆到本地后,进入仓库,执行 docker-compose up -d 即可启动实验环境。启动完成,在浏览器中输入 http://localhost:7088 可访问 jenkins-a,jenkins-b 的地址是 http://localhost:7089 。但是你会发现它们启动后的界面显示是不一样的。 jenkins-b 的界面如下图所示: 而 jenkins-a 的界面如下图所示: 这时,将 jenkins-a 日志中的解锁密码(Unlock password)输入到 jenkins-b 的页面中,会得到报错信息: ERROR: The password entered is incorrect, please check the file for the correct password 这时,再次 jenkins-b 日志中的解锁密码(Unlock password)输入到表单中即可进入下一步。接下来就是按照提示一步步完成了。在 jenkins-b 安装步骤的最后一步,我们设置了管理员的用户名密码:admin/admin。然后就算完成任务了。 ...

2019-04-15 · 2 min · 234 words · 翟志军 Jack Zhai

使用 Zabbix 监控 Jenkins

本文假设读者已经了解 Jenkins 基本概念及插件安装,Zabbix 基础概念。基于 Zabbix 3.4,Jenkins 2.8 做实验 笔者最近的工作涉及到使用 Zabbix 监控 Jenkins。在谷歌上搜索到的文章非常少,能操作的就更少了。所以决定写一篇文章介绍如何使用 Zabbix 监控 Jenkins。 下图为整体架构图: 整体并不复杂,大体步骤如下: 在 Jenkins 上安装 Metrics 插件,使 Jenkins 暴露 metrics api。 配置 Zabbix server 及 agent 以实现监控及告警 为方便读者实验,笔者将自己做实验的代码上传到了 GitHub,链接在文章末尾。使用的是 Docker Compose 技术(方便一次性启动所有的系统)。 接下来,我们详细介绍 Metrics插件及如何实现 Zabbix 监控 Jenkins。 1. 使 Jenkins 暴露 metrics api 安装 Metrics 插件,在系统配置中,会多出“Metrics”的配置,如下图: 配置项不复杂。我们需要点击“Generate…”生成一个 Access Key(生成后,记得要保存)。这个 Key 用于身份校验,后面我们会用到。 保存后,我们在浏览器中输入URL:http://localhost:8080/metrics/<刚生成的 Access Key> 验证 Jenkins 是否已经暴露 metrics。如果看到如下图,就说明可以进行下一步了。 1.1 Metrics 插件介绍 Metrics 插件是基于 dropwizard/metrics 实现。它通过4个接口暴露指标数据:/metrics,/ping,/threads,/healthcheck。 ...

2019-04-10 · 2 min · 414 words · 翟志军 Jack Zhai

理解 Gerrit 的 Change-Id

审校:LinuxSuRen(https://github.com/LinuxSuRen) Gerrit 是一个基于 Git 版本控制的基于 Web 的代码审查工具 。笔者在学习它的过程中发现,要使用好它,第一步就是要理解 Change-Id。 理解 Change-Id 要理解 Gerrit 的 Change-Id,我们就必须对“一次代码审查任务”有一个定义。通常,我们认为对一次完整的功能实现或 Bug 修复(即一次完整的变更)进行代码审查是合理的。而对一个半成品进行代码审查,得到的结论是不可靠的。因此,一次代码审查任务意味着是对一次变更进行审查。 Gerrit 使用 Change-Id 来标识一次变更。Change-Id 实际上就是一串字符串,类似这样:Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b 但是,一次变更通常会伴随多次 Git 提交(Commit),而且每次提交的提交是不同的 Commit Id(提交Id)。Gerrit 如何将多次提交关联到同一个 Change-Id 呢? 我们需要在每次提交时,将 Change-Id 以规定的格式放在提交消息(Commit message)的Footer 部分中(最后一行)。如下图: Change-Id 为避免与提交 Id 冲突,通常以大写字母I为前缀。但是,我们怎么才能方便生成 Change-Id 呢? 使用 Git 钩子生成 Change-Id Change-Id 最好是自动生成,并放到提交消息指定位置,这样才能节约开发者的时间。Gerrit 提供了标准的“commit-msg”钩子来实现。 Git 提供了4个提交工作流钩子:pre-commit、prepare-commit-msg、commit-msg、post-commit。其中 commit-msg 钩子,会在我们执行 git commit 时被执行。 本质上,commit-msg 钩子是一段脚本程序,放在 .git/hooks 目录下。commit-msg 脚本可以使用 Shell、Ruby、Python 等语言实现。 Gerrit 的 commit-msg 钩子直接从 Gerrit 下载: ...

2019-03-24 · 1 min · 144 words · 翟志军 Jack Zhai

Electron 应用的流水线设计

面向读者:需要了解 Jenkins 流水线的基本语法。 Electron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 本文将介绍 Electron 桌面应用的流水线的设计。 但是如何介绍呢?倒是个大问题。笔者尝试直接贴代码,在代码注释中讲解。这是一次尝试,希望得到你的反馈。 完整代码 pipeline { // 我们决定每一个阶段指定 agent,所以, // 流水线的 agent 设置为 none,这样不会占用 agent agent none // 指定整条流水线的环境变量 environment { APP_VERSION = "" APP_NAME = "electron-webpack-quick-start" } stages { stage("生成版本号"){ agent {label "linux" } steps{ script{ APP_VERSION = generateVersion("1.0.0") echo "version is ${APP_VERSION}" }} } stage('并行构建') { // 快速失败,只要其中一个平台构建失败, // 整次构建算失败 failFast true // parallel 闭包内的阶段将并行执行 parallel { stage('Windows平台下构建') { agent {label "windows && nodejs" } steps { echo "${APP_VERSION}" } } stage('Linux平台下构建') { agent {label "linux && nodejs" } // 不同平台可能存在不同的环境变量 // environment 支持阶段级的环境变量 environment{ SUFFIX = "tar.xz" APP_PLATFORM = "linux" ARTIFACT_PATH = "dist/${APP_NAME}-${APP_PLATFORM}-${APP_VERSION}.${SUFFIX}" } steps { script{ // Jenkins nodejs 插件提供的 nodejs 包装器 // 包装器内可以执行 npm 命令。 // nodejs10.15.2 是在 Jenkins 的全局工具配置中添加的 NodeJS 安装器 nodejs(nodeJSInstallationName: 'nodejs10.15.2') { // 执行具体的构建命令 sh "npm install yarn" sh "yarn version --new-version ${APP_VERSION}" sh "yarn install" sh "yarn dist --linux deb ${SUFFIX}" // 上传制品 uploadArtifact("${APP_NAME}", "${APP_VERSION}", "${ARTIFACT_PATH}") }}} // 将括号合并是为了让代码看起来紧凑,提升阅读体验。下同。 } stage('Mac平台下构建') { agent {label "mac && nodejs" } stages { stage('mac 下阶段1') { steps { echo "staging 1" } } stage('mac 下阶段2') { steps { echo "staging 2" } } } } } } stage("其它阶段,读者可根据情况自行添加"){ agent {label "linux"} steps{ echo "发布" } } } post { always { cleanWs() } } // 清理工作空间 } def generateVersion(def ver){ def gitCommitId = env.GIT_COMMIT.take(7) return "${ver}-${gitCommitId}.${env.BUILD_NUMBER}" } def uploadArtifact(def appName, def appVersion, def artifactPath){ echo "根据参数将制品上传到制品库中,待测试" } 代码补充说明 因为 Electron 是跨平台的,我们需要将构建过程分别放到 Windows、Linux、Mac 各平台下执行。所以,不同平台的构建任务需要执行在不同的 agent 上。我们通过在 stage 内定义 agent 实现。如在“Mac平台下构建”的阶段中,agent {label "mac && nodejs" } 指定了只有 label 同时包括了 mac 和 nodejs 的 agent 才能执行构建。 ...

2019-03-10 · 2 min · 291 words · 翟志军 Jack Zhai