Jenkins

Jenkins

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

灵雀云 发表了文章 • 0 个评论 • 254 次浏览 • 2019-05-22 10:43 • 来自相关话题

本文要点: 设计一条 Spring Boot 最基本的流水线:包括构建、制品上传、部署。 使用 Docker 容器运行构建逻辑。 自动化整个实验环境:包括 Jenkins 的配置,Jenkins agent 的配置等。 ...查看全部
本文要点:
设计一条 Spring Boot 最基本的流水线:包括构建、制品上传、部署。
使用 Docker 容器运行构建逻辑。
自动化整个实验环境:包括 Jenkins 的配置,Jenkins agent 的配置等。

1. 代码仓库安排

本次实验涉及以下多个代码仓库:
% tree -L 1
├── 1-cd-platform # 实验环境相关代码
├── 1-env-conf # 环境配置代码-实现配置独立
└── 1-springboot # Spring Boot 应用的代码及其部署代码

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 是为了启动虚拟机,用于部署 Spring Boot 应用。如果你的开发机器无法使用 Vagrant,使用 VirtualBox 也可以达到同样的效果。但是有一点需要注意,那就是网络。如果在虚拟机中要访问 Docker 容器内提供的服务,需要在 DNS 上或者 hosts 上做相应的调整。所有的虚拟机的镜像使用 Centos7。
另,接下来笔者的所有教程都将使用 Artifactory 作为制品库。在此申明,笔者没有收 JFrog——研发 Artifactory 产品的公司——任何广告费。 笔者只是想试用商业产品,以便了解商业产品是如何应对制品管理问题的。
启动 Artifactory 后,需要添加 “Virtual Repository” 及 “Local Repository”。具体请查看 Artifactory 的官方文档。如果你当前使用的是 Nexus,参考本教程,做一些调整,问题也不大。
如果想使用已有制品库,可以修改 1-cd-platform 仓库中的 settings-docker.xml 文件,指向自己的制品库。
实验环境近期的总体结构图如下:

pic1.jpg



之所以说是“近期的”,是因为上图与本篇介绍的结构有小差异。本篇文章还没有介绍 Nginx 与 Springboot 配置共用,但是总体不影响读者理解。如果你想和更多Jenkins技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

3. Springboot 应用流水线介绍

Springboot 流水线有两个阶段:


构建并上传制品
部署应用
流水线的所有逻辑都写在 Jenkinsfile 文件。接下来,分别介绍这两个阶段。
3.1 构建并上传制品
此阶段核心代码:


docker.image('jenkins-docker-maven:3.6.1-jdk8')
.inside("--network 1-cd-platform_cd-in-practice -v $HOME/.m2:/root/.m2") {
sh """
mvn versions:set -DnewVersion=${APP_VERSION}
mvn clean test package
mvn deploy
"""
}


它首先启动一个装有 Maven 的容器,然后在容器内执行编译、单元测试、发布制品的操作。
而 mvn versions:set -DnewVersion=${APP_VERSION} 的作用是更改 pom.xml 文件中的版本。这样就可以实现每次提交对应一个版本的效果。
3.2 部署应用
注意: 这部分需要一些 Ansible 的知识。
首先看部署脚本的入口 1-springboot/deploy/playbook.yaml:

---


[list]
[*]hosts: "springboot"[/*]
[/list] become: yes
roles:
- {"role": "ansible-role-java", "java_home": "{{JAVA_HOME}}"}
- springboot



先安装 JDK,再安装 Spring Boot。JDK 的安装,使用了现成 Ansible role: https://github.com/geerlingguy/ansible-role-java。
重点在 Spring Boot 部署的核心逻辑。它主要包含以下几部分:
创建应用目录。
从制品库下载指定版本的制品。
生成 Systemd service 文件(实现服务化)。
启动服务。
以上步骤实现在 1-springboot/deploy/roles/springboot 中。
流水线的部署阶段的核心代码如下:


docker.image('williamyeh/ansible:centos7').inside("--network 1-cd-platform_cd-in-practice") {

checkout([$class: 'GitSCM', branches: [[name: "master"]], doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: "env-conf"]], submoduleCfg: [],
userRemoteConfigs: [[url: "https://github.com/cd-in-practice/1-env-conf.git"]]])

sh "ls -al"

sh """
ansible-playbook --syntax-check deploy/playbook.yaml -i env-conf/dev
ansible-playbook deploy/playbook.yaml -i env-conf/dev --extra-vars '{"app_version": "${APP_VERSION}"}'
"""
}


它首先将配置变量仓库的代码 clone 下来,然后对 playbook 进行语法上的检查,最后执行 ansible-playbook 命令进行部署。--extra-vars 参数的 app_version 用于指定将要部署的应用的版本。

3.3 实现简易指定版本部署

在 1-springboot/Jenkinsfile 中实现了简易的指定版本部署。核心代码如下:
流水线接受参数
parameters { string(name: 'SPECIFIC_APP_VERSION',
defaultValue: '', description: '') }
如果指定了版本,则跳过构建阶段,直接执行部署阶段


stage("build and upload"){
// 如果不指定部署版本,则执行构建
when {
expression{ return params.SPECIFIC_APP_VERSION == "" }
}
// 构建并上传制品的逻辑
steps{...}
}


之所以说是“简易”,是因为部署时只指定了制品的版本,并没有指定的部署逻辑和配置的版本。这三者的版本要同步,部署才真正做到准确。

4. 配置管理

所有的配置项都放在 1-env-conf 仓库中。Ansible 执行部署时会读取此仓库的配置。
将配置放在 Git 仓库中有两个好处:
配置版本化。
任何配置的更改都可以被审查。
有好处并不代表没有成本。那就是开发人员必须开始关心软件的配置(笔者发现不少开发者忽视配置项管理的重要性。)。
本文重点不在配置管理,后面会有文章重点介绍。

5. 实验环境详细介绍

事实上,整个实验,工作量大的地方有两处:一是 Spring Boot 流水线本身的设计;二是整个实验环境的自动化。读者朋友之所以能一两条简单的命令就能启动整个实验环境,是因为笔者做了很多自动化的工作。笔者认为有必要在本篇介绍这些工作。接下来的文章将不再详细介绍。

5.1 解决流水线中启动的 Docker 容器无法访问 http://artifactory

流水线中,我们需要将制品上传到 artifactory(settings.xml 配置的仓库地址是 http://artifactory:8081),但是发现无法解析 host。这是因为流水线中的 Docker 容器所在网络与 Docker compose 创建的网络不同。所以,解决办法就是让流水线中的 Docker 容器加入到 Docker compose 的网络。
具体解决办法就是在启动容器时,加入参数:--network 1-cd-platform_cd-in-practice

5.2 Jenkins 初次启动初始化

在没有做任何设置的情况启动 Jenkins,会出现一个配置向导。这个过程必须是手工的。笔者希望这一步也是自动化的。Jenkins 启动时会执行 init.groovy.d/目录下的 Groovy 脚本。
5.3 虚拟机中如何能访问到 http://artifactory ?
http://artifactory 部署在 Docker 容器中。Spring Boot 应用的制品要部署到虚拟机中,需要从 http://artifactory 中拉取制品,也就是要在虚拟机里访问容器里提供的服务。虚拟机与容器之间的网络是不通的。那怎么办呢?笔者的解决方案是使用宿主机的 IP 做中转。具体做法就是在虚拟机中加一条 host 记录:


machine.vm.provision "shell" do |s|
s.inline = "echo '192.168.52.1 artifactory' >> /etc/hosts"
end


以上是使用了 Vagrant 的 provision 技术,在执行命令 vagrant up 启动虚拟机时,就自动执行那段内联 shell。192.168.52.1 是虚拟宿主机的 IP。所以,虚拟机里访问 http://artifactory:8081 时,实际上访问的是 http://192.168.52.1:8081。
网络结构可以总结为下图:

pic2.jpg


后记

目前遗留问题:
部署时制品版本、配置版本、部署代码版本没有同步。
Springboot 的配置是写死在制品中的,没有实现制品与配置项的分离。
这些遗留问题在后期会逐个解决。就像现实一样,经常需要面对各种遗留项目的遗留问题。
附录
使用 Jenkins + Ansible 实现自动化部署 Nginx:https://jenkins-zh.cn/wechat/articles/2019/04/2019-04-25-jenkins-ansible-nginx/
简单易懂 Ansible 系列 —— 解决了什么:https://showme.codes/2017-06-12/ansible-introduce/


本文转自公众号 jenkins
作者 翟志军

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

灵雀云 发表了文章 • 0 个评论 • 236 次浏览 • 2019-05-20 11:06 • 来自相关话题

背景 了解到行业内有些团队是基于 Jenkins 开发 DevOps 平台。而基于 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 的变量中。接下来,你就可以在闭包中使用该变量了。
说到这里,不知道读者朋友是否已经有思路了?
思路就是实现一个和 Credentials Binding Plugin 插件类似功能的方法,比如叫 zWithCredentials(后文还会提到)。与 withCredentials 不同的是,zWithCredentials 根据凭证 id 获取凭证时,不是从 Jenkins 上获取,而是从 DevOps 平台获取。

会遇到的坑

需要适配只认 Jenkins 凭证的插件
withCredentials 方法是将凭证的内容存到变量中,这可以满足一大部分场景。但是有一种场景是无法满足的。就是某些 Jenkins 插件的步骤接收参数时,参数值必须是 Jenkins 凭证管理系统中的 id。比如 git 步骤中 credentialsId 参数:


git branch: 'master',
credentialsId: '12345-1234-4696-af25-123455',
url: 'ssh://git@bitbucket.org:company/repo.git'


这种情况,我们不可能修改现有的插件。因为那样做的成本太高了。
那怎么办呢?
笔者想到的办法是在 zWithCredentials 中做一些 hack 操作。也就是 zWithCredentials 除了从 DevOps 平台获取凭证,还在 Jenkins 中创建一个 Jenkins 凭证。在 Jenkins 任务执行完成后,再将这个临时凭证删除。这样就可以适配那些只认 Jenkins 凭证 id 的插件了。
对凭证本身的加密
DevOps 平台在存储凭证、传输凭证给 Jenkins 时,都需要对凭证进行加密。至于使用何种加密方式,交给读者思考了。

小结

以上解决方案对 Jenkins 本身的改造几乎没有,我们只通过一个插件就解耦了 Jenkins 的凭证管理和 DevOps 平台的凭证管理。
思路已经有了。具体怎么实现,由于一些原因不能开源,虽然实现起来不算难。还请读者见谅。
最后,希望能和遇到同样问题的同学进行交流。看看是否还可以有更好的设计思路。

本文转自公众号 Jenkins
作者:翟志军

Kubernetes 中的渐进式交付:蓝绿部署和金丝雀部署

灵雀云 发表了文章 • 0 个评论 • 378 次浏览 • 2019-05-07 15:27 • 来自相关话题

渐进式交付是持续交付的下一步, 它将新版本部署到用户的一个子集,并在将其滚动到全部用户之前对其正确性和性能进行评估, 如果不匹配某些关键指标,则进行回滚。 这里有一些有趣的项目,使得渐进式交付在 Kubernetes 中变得更简单。我 ...查看全部
渐进式交付是持续交付的下一步, 它将新版本部署到用户的一个子集,并在将其滚动到全部用户之前对其正确性和性能进行评估, 如果不匹配某些关键指标,则进行回滚。

这里有一些有趣的项目,使得渐进式交付在 Kubernetes 中变得更简单。我将使用一个 Jenkins X 示例项目 对它们之中的三个进行讨论:Shipper、Istio 以及 Flagger。

Shipper

shipper 是来自 booking.com 的一个项目, 它对 Kubernetes 进行了扩展,添加了复杂的部署策略和多集群编排(文档)。它支持从一个集群到多个集群的部署,允许多区域部署。
Shipper 通过一个 shipperctl 命令行进行安装。它增加不同集群的配置文件来进行管理。请注意这个与 GKE 上下文相关的问题。
Shipper 使用 Helm 包来部署,但是它们没有随着 Helm 一起安装,它们不会在 helm list 的输出显示。同样地,deployments 的版本必须是 apps/v1 , 否则 shipper 将不能编辑 deployment 来添加正确的标签和副本数量。
使用 shipper 部署都是与从旧版本(现有版本)过渡到新版本(竞争版本)相关。这是通过创建一个新的应用对象实现的, 它定义了部署需要通过的多个阶段。例如下面 3 个步骤过程:
Staging:部署新版本到一个 pod ,没有流量
50 / 50:部署新版本到 50% 的 pods,50% 的流量
Full on:部署新版本到全部的 pods,全部的流量


strategy:
steps:
- name: staging
capacity:
contender: 1
incumbent: 100
traffic:
contender: 0
incumbent: 100
- name: 50/50
capacity:
contender: 50
incumbent: 50
traffic:
contender: 50
incumbent: 50
- name: full on
capacity:
contender: 100
incumbent: 0
traffic:
contender: 100
incumbent: 0


如果发布的某个步骤没有将流量发送到 pods , 则可以使用 kubectl port-forward 访问它们,如:kubectl port-forward mypod 8080:8080, 这对于在用户看到新版本之前进行测试非常有用。
Shipper 支持多集群的概念,但是以相同的方式对待所有集群,仅使用区域并通过 capabilities (配置在集群对象中)进行筛选, 所有对一个应用对象来说,这里没有一个 dev, staging, prod 集群的选项。但是我们可以有两个应用对象:
myapp-staging 部署到 "staging" 区域
myapp 部署到其它区域
在 GKE 中,你可以轻松地配置多集群 ingress , 该入口将公开在多个集群中运行的服务,并从离你所在位置最近的集群提供服务。
局限性
Shipper 中的主要的局限性有:
Chart 限制:Chart 必须有一个部署对象。Deployment 的名称必须使用 {{.Release.Name}} 模板化。Deployment 对象应该有 apiVersion:apps/v1 。
基于 Pod 的流量切换:这里没有细粒度的流量路由,例如:发送 1% 的流量到新版本,它基于正在运行的 Pod 数量。
如果 Shipper 不工作了,新的 Pod 将获取不到流量。

Istio

Istio 不是一个部署工具,而是一个服务网格。然而,它很令人感兴趣,因为它已经变得非常流行,并且允许流量管理,例如,将一定比例的流量发送到不同的服务和其他高级网络。
在 GKE 中,只需在集群配置中选中复选框即可启用 Istio 。在其它集群中,可以通过 Helm 手动安装。
有了 Istio ,我们可以创建一个网关,通过 Ingress 网关处理所有外部流量,并创建虚拟服务来管理到我们服务的路由。为此,只需找到 ingress 网关的 ip 地址并为其配置通配符 DNS 。然后创建一个网关,通过 Ingress 网关路由所有外部流量。


apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: public-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"


Isito 不管理应用的生命周期,只管理网络。我们可以创建一个虚拟服务,为所有进入 ingress 网关的请求 向 pull request 或 master 分支中部署的服务发送 1% 的流量。


apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: croc-hunter-jenkinsx
namespace: jx-production
spec:
gateways:
- public-gateway.istio-system.svc.cluster.local
- mesh
hosts:
- croc-hunter.istio.example.org
http:
- route:
- destination:
host: croc-hunter-jenkinsx.jx-production.svc.cluster.local
port:
number: 80
weight: 99
- destination:
host: croc-hunter-jenkinsx.jx-staging.svc.cluster.local
port:
number: 80
weight: 1


Flagger
Flagger 是一个由 Weaveworks 赞助的使用了 Istio 的项目, 该项目使用 Prometheus 的指标进行自动化金丝雀发布和回滚。它超越了 Isito 提供了基于指标的自动化渐进式发布和回滚。
Flager 需要将 Istio与 Prometheus、Servicegraph 和某些系统的配置一起安装, 另外还要安装 Flager 控制器本身。它也提供了一个 Grfana 面板来监控部署进度。


pic1.jpg


部署 rollout 通过 Canary 对象定义, 它会生成主要的和金丝雀 Deployment 对象。编辑 Deployment 时,例如要使用新的镜像版本, Flagger 控制器将负载从 0% 切换到 50% ,每分钟增加 10% ,然后它将切换到新的 deployment 或者如果响应错误和请求持续时间等指标失败则进行回滚。

比较

此表总结了 Shipper 和 Flagger 在几个渐进式交付特性方面的优势和劣势。

pic2.jpg



pic3.jpg




综上所述,我看到了 Shipper 在多集群管理和简单性方面的价值,它不需要 Kubernetes 以外的任何东西,但是它有一些严重的局限性。
Flager 确实在自动部署和回滚以及对流量进行细粒度控制的过程中付出了额外的努力,它以更高的复杂性成本提供了所需的所有额外服务( Isito、Prometheus )。
这里可以查看 Shipper、Isito 和 Flager 的示例代码。

本文转自微信公众号 Jenkins

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

灵雀云 发表了文章 • 0 个评论 • 444 次浏览 • 2019-04-29 11:00 • 来自相关话题

本文介绍如何使用 Jenkins + Ansible 实现对 Nginx 的自动化部署。最终达到的效果有如下几点: 只要你将 Nginx 的配置推送到 GitHub 中,Jenkins 就会自动执行部署,然后目标服务器的 Nginx 配置自动生效。 ...查看全部
本文介绍如何使用 Jenkins + Ansible 实现对 Nginx 的自动化部署。最终达到的效果有如下几点:
只要你将 Nginx 的配置推送到 GitHub 中,Jenkins 就会自动执行部署,然后目标服务器的 Nginx 配置自动生效。这个过程是幂等(idempotent)的,只要代码不变,执行多少遍,最终效果不变。
如果目标机器没有安装 Nginx,则会自动安装 Nginx。
自动设置服务器防火墙规则。

1. 实验环境介绍

本次实验使用 Docker Compose 搭建 Jenkins 及 Jenkins agent。使用 Vagrant 启动一台虚拟机,用于部署 Nginx。使用 Vagrant 是可选的,读者可以使用 VirtualBox 启动一个虚拟机。使用 Vagrant 完全是为了自动化搭建实验环境。
以下是整个实验环境的架构图

pic1.jpg



注意,图中的 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 镜像有两个原因:


docker build -f JenkinsSlaveAnsibleDockerfile -t jenkins-swarm-ansible .


本次实验,使用 Swarm 插件实现 Jenkins master 与 agent 之间的通信,所以 Jenkins agent 需要启动 swarm 客户端。
Jenkins agent 必须支持 Ansible。
启动 Jenkins master 及 Jenkins agent


docker-compose up -d


通过 http://localhost:8080 访问 Jenkins master,如果出现“解锁密码”页面,如下图,则执行命令 docker-compose logs jenkins 查看 Jenkins master 启动日志。将日志中的解锁密码输入到表单中。然后就一步步按提示安装即可。

pic2.jpg


安装 Jenkins 插件 本次实验需要安装以下插件:
Pipeline 2.6:https://plugins.jenkins.io/workflow-aggregator
Swarm 3.15:https://plugins.jenkins.io/swarm 用于 实现 Jenkins master 与 Jenkins agent 自动连接
Git 3.9.3:https://plugins.jenkins.io/git
配置 Jenkins master 不执行任务 进入页面:http://localhost:8080/computer/(master)/configure,如下图所示设置:

pic3.jpg


确认 Jenkins 安全配置有打开端口,以供 Jenkins agent 连接。我们设置 Jenkins master 开放的端口,端口可以是固定的 50000 ,也可以设置为随机。设置链接:http://localhost:8080/configureSecurity/。

pic4.jpg


启动目标机器,用于部署 Nginx 在命令行中执行以下命令:


vagrant up


注意,Vagrantfile 文件中的 config.vm.box 值必须改成你的 vagrant box 。
至此,实验环境已经搭建好了。接下来就可以新建 Jenkins 任务了。

3. 在 Jenkins 上创建部署任务

1、新建流水线任务

pic5.jpg


2、配置流水线 配置 Jenkins 任务从远程仓库拉取 Jenkinsfile,如下图所示:

pic5.jpg


除此之外,不需要其它配置了,是不是很简单?

4. 手工触发一次自动化构建

点击“立即构建”:

pic6.jpg


最终执行日志如下:

pic7.jpg


至此,部署已经完成。以后修改 Nginx 的配置,只需要修改代码,然后推送到远程仓库,就会自动化部署。不需要手工登录到目标机器手工修改了。
最后,我们可以通过访问 http://localhost:5123,如果出现如下页面说明部署成功:

pic8.jpg



5. 代码讲解

以上步骤并不能看出自动化部署真正做了什么。那是因为我们所有的逻辑都写在代码中。是的,可以说是 everything is code。
接下来我们介绍代码仓库。


% tree -L 2
├── JenkinsSlaveAnsibleDockerfile # Jenkins agent 镜像 Dockerfile
├── Jenkinsfile # 流水线逻辑
├── README.md
├── Vagrantfile # Vagrant 虚拟机定义文件
├── docker-compose.yml # Jenkins 实现环境
├── env-conf # 所有应用配置
│ └── dev # dev 环境的配置
├── deploy # Ansible 部署脚本所在文件夹
│ ├── playbook.yaml
│ └── roles
└── swarm-client.sh # Jenkins swarm 插件的客户端


5.1流水线逻辑

Jenkinsfile 文件用于描述整条流水线的逻辑。代码如下:


pipeline{
// 任务执行在具有 ansible 标签的 agent 上
agent { label "ansible"}
environment{
// 设置 Ansible 不检查 HOST_KEY
ANSIBLE_HOST_KEY_CHECKING = false
}
triggers {
pollSCM('H/1 [i] [/i] [i] [/i]')
}
stages{
stage("deploy nginx"){
steps{
sh "ansible-playbook -i env-conf/dev deploy/playbook.yaml"
}

}}}

environment 部分:用于定义流水线执行过程中的环境变量。
triggers 部分:用于定义流水线的触发机制。pollSCM 定义了每分钟判断一次代码是否有变化,如果有变化则自动执行流水线。
agent 部分:用于定义整条流水线的执行环境。
stages 部分:流水线的所有阶段,都被定义在这部分。
以上只是定义流水线是如何执行的,目前整条流水线只有一个 deploy nginx 阶段,并且只执行了一条 ansible-playbook 命令。但是它并没有告诉我们部署逻辑是怎么样的。

5.2 部署逻辑

所有的部署逻辑,包括 Nginx 的安装启动、配置的更新以及加载,都放在 Ansible 脚本中。对 Ansible 不熟的同学,可以在本文末尾找到介绍 Ansible 的文章。
整个部署逻辑的入口在 deploy/playbook.yaml,代码如下:


[list]
[*]hosts: "nginx"[/*]
[/list] become: true
roles:
# Nginx 的部署
- ansible-role-nginx
# 对防火墙的设置
- ansible-role-firewall



hosts:定义了 playbook 部署的目标主机分组名为 nginx。
roles:包含了两个执行具体部署动作的 role,至于 role 内部逻辑,不在本文讨论范围,有兴趣的同学阅读源码。

5.3 配置管理

谈到部署,就不得不谈配置管理。
回顾前文中流水线中执行的 shell 命令:ansible-playbook -i env-conf/dev deploy/playbook.yaml 我们通过 -i 参数指定部署时所使用的环境配置。通过这种方式实现环境配置与执行脚本的分离。这样带来以下几个好处:
新增环境时,只需要复制现有的环境,然后将里面的变量的值改成新环境的即可。比如,要对测试环境进行部署,只需要将 -i 参数值改成:env-conf/test。
对配置版本化控制。
本次实验中,各个环境的配置放在 env-conf 目录中,目前只有 dev 环境,以下是 env-conf/ 目录结构:


% cd env-conf/
% tree
└── dev
├── group_vars
│ └── nginx.yaml
├── host_vars
│ └── 192.168.52.10
└── hosts


hosts文件:Ansible 中通过“分组”来实现对主机的管理。hosts 文件内容如下:


[nginx]
192.168.52.10


host_vars 目录:用于存放主机级别的配置变量,本例中 192.168.52.10 是一个 YAML 格式文件。注意文件名是该主机的 IP。我们在文件中放主机相关的配置,比如 Ansible 连接主机时使用到的用户名和密码。
group_vars 目录:用于存放组级别的配置变量。比如 nginx.yaml 对应的就是

nginx



这个组的的配置变量。文件名与 hosts 中的组名对应

总结

到此,我们完整的自动化部署已经讲解完成。但是还遗留下一些问题:
本文只是安装了一个“空”的 Nginx,但是没有介绍 Nginx 真正配置。
目前主机的连接信息(SSH 密码)是明文写在` host_vars/192.168.52.10 `文件中的,存在安全风险。
没有介绍如何当 Java 应用部署时,如何自动更新 Nginx 的配置。
本文属于使用 Jenkins + Ansible 实现自动化部署的入门文章,笔者将根据读者的反馈决定是否写续集。
如果觉得本文讲的 Jenkins 流水线逻辑部分不够过瘾,可以考虑入手一本最近才出版的《Jenkins 2.x实践指南》。长按下图进行扫码购买。

本文转载自:微信公众号jenkins

关于 Jenkins master 共享 JENKINS_HOME 目录的实验

灵雀云 发表了文章 • 1 个评论 • 338 次浏览 • 2019-04-24 14:45 • 来自相关话题

Jenkins master 的高可用是个老大难的问题。和很多人一样,笔者也想过两个 Jenkins master 共享同一个 JENKINS_HOME 的方案。了解 Jenkins 原理的人,都会觉得这个方案不可行。但是真的不可行吗? 由于工作原 ...查看全部
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 。但是你会发现它们启动后的界面显示是不一样的。

pic1.jpg


jenkins-b 的界面如下图所示:

pic2.jpg



而 jenkins-a 的界面如下图所示:

pic3.jpg



这时,将 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。然后就算完成任务了。
然后我们再在 jenkins-a 使用 admin/admin 进行登录,登录是报错的:用户密码不正确。
接下来,执行 `docker-compose restart jenkins-a `docker-compose restart jenkins-a 命令重启 jenkins-a。再次使用 admin/admin 就可以登录成功了。
当两个 Jenkins 启动完成后,接下来开始做实验。

实验1:创建任务

在 jenkins-a 创建任务 x,刷新 jenkins-b 的页面,jenkins-b 上会不会显示出任务 x ?
结果:jenkins-b 不会出现任务 x。重启 jenkins-b 后,任务 x 出现在任务列表中。
实验2:任务结果可见性
jenkins-a 上任务执行,jenkins-b 上能否看到任务执行结果?
jenkins-a 执行任务 x,并且执行成功。刷新 jenkins-b 看不到任何执行记录。重启 jenkins-b 后,可看到执行记录。

实验3:两 master 同时执行同一任务

分别在两个 Jenkins master 上(几乎)开始同一个任务 x。其中一个任务的 build number 会更新,但是另一个不会。
其中 jenkins-a 任务 x 的 build number 会升到 2,而 jenkins-b 保持的是 1。这时,单独执行 jenkins-b 的任务 x,日志会出现错误:


jenkins-b_1 | WARNING: A new build could not be created in job x
jenkins-b_1 | java.lang.IllegalStateException: JENKINS-23152: /var/jenkins_home/jobs/x/builds/2 already existed; will not overwrite with x #2



实验4:编辑任务

jenkins-a 上设置任务 x 定时执行,刷新 jenkins-b 页面,任务 x 中并没有定时执行的设置。重启 jenkins-b 后,任务 x 更新。

实验5:定时任务的结果是什么?

如果 jenkins-a 和 jenkins-b 两个任务均为定时任务,而且都生效了。它们运行结果是什么的呢?
看到的现象是,两个任务都会按时执行,但是只有一个任务能将运行结果写入到磁盘中。界面如下图:

另,从日志中,可以确认 jenkins-a 和 jenkins-b 确实按时执行了。如下图日志中,看出 jenkins-a 定时执行 #6 次构建时报错,因为 jenkins-b 已经执行过 #6 次构建了:

pic4.jpg


小结
可以确认的是,当两个 Jenkins 进程共用同一个 JENKINS_HOME 目录时,其中一个 Jenkins 进程更新了 JENKINS_HOME 的内容,另一个是不会实时更新的。所以,同时启动两个 Jenkins master 共用同一个 JENKINS_HOME 的方案是不可行的。我们不能在 jenkins-a 挂了后,直接将流量切到 jenkins-b。因为 jenkins-b 必须重启。
最后结论:多个 Jenkins master 共享同一个 JENKINS_HOME 的方案是无法使用 Jenkins master 的高可用。

附录

Jenkins standby 实验环境:https://github.com/zacker330/jenkins-standby-experiment


本文转自微信公众号:Jenkins

简析 Jenkins 专有用户数据库加密算法

灵雀云 发表了文章 • 0 个评论 • 465 次浏览 • 2019-04-19 11:11 • 来自相关话题

认识Jenkins专有用户数据库 Jenkins 访问控制分为:安全域(即认证)与授权策略。 其中,安全域可以采用三种形式,分别为:Jenkins 专有用户数据库、LDAP、Servlet 容器代理。 ...查看全部
认识Jenkins专有用户数据库

Jenkins 访问控制分为:安全域(即认证)与授权策略。
其中,安全域可以采用三种形式,分别为:Jenkins 专有用户数据库、LDAP、Servlet 容器代理。

PIC1.png


在哪里看到加密后的用户密码信息?

Jenkins 专有用户的数据信息存放位置:$JENKINS_HOME/users/
每个用户的相关信息存放在各自的 config.xml 文件中: $JENKINS_HOME/users/$user/config.xml
在 config.xml 文件中的 passwordHash 节点可以看到用户密码加密后的密文哈希值:

PIC2.png


用户密码是用什么算法加密的呢?

那么问题来了,用户密码是用何种加密方式加密的呢?可否通过解密密文得到明文呢?
在 GitHub 上查看其源码,通过关键字 #jbcrypt 搜索定位到 HudsonPrivateSecurityRealm.java 这个文件。 HudsonPrivateSecurityRealm.java 具体路径是:jenkins/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
源码片段如下:



/**
* {@link PasswordEncoder} that uses jBCrypt.
*/
private static final PasswordEncoder JBCRYPT_ENCODER = new PasswordEncoder() {
public String encodePassword(String rawPass, Object _) throws DataAccessException {
return BCrypt.hashpw(rawPass,BCrypt.gensalt());
}

public boolean isPasswordValid(String encPass, String rawPass, Object _) throws DataAccessException {
return BCrypt.checkpw(rawPass,encPass);
}
};
/**
* Combines {@link #JBCRYPT_ENCODER} and {@link #CLASSIC} into one so that we can continue
* to accept {@link #CLASSIC} format but new encoding will always done via {@link #JBCRYPT_ENCODER}.
*/
public static final PasswordEncoder PASSWORD_ENCODER = new PasswordEncoder() {
/*
CLASSIC encoder outputs "salt:hash" where salt is [a-z]+, so we use unique prefix '#jbcyrpt"
to designate JBCRYPT-format hash.
'#' is neither in base64 nor hex, which makes it a good choice.
*/
public String encodePassword(String rawPass, Object salt) throws DataAccessException {
return JBCRYPT_HEADER+JBCRYPT_ENCODER.encodePassword(rawPass,salt);
}

public boolean isPasswordValid(String encPass, String rawPass, Object salt) throws DataAccessException {
if (encPass.startsWith(JBCRYPT_HEADER))
return JBCRYPT_ENCODER.isPasswordValid(encPass.substring(JBCRYPT_HEADER.length()),rawPass,salt);
else
return CLASSIC.isPasswordValid(encPass,rawPass,salt);
}

private static final String JBCRYPT_HEADER = "#jbcrypt:";
};


通过分析该源码得知:
明文通过 jbcrypt 算法得到密文 encPass
密文的格式为:salt: encPass,其中以 #jbcrypt 表示 salt 作为数据头

jbcrypt 是什么?

jbcrypt 是 bcrypt 加密工具的 java 实现。 它的 API 非常简单,DEMO 如下,在 HudsonPrivateSecurityRealm.java 中可以看到加密和校验时使用了如下 API:


// Hash a password for the first time
String hashed = BCrypt.hashpw(password, BCrypt.gensalt());

// gensalt's log_rounds parameter determines the complexity the work factor is 2**log_rounds, and the default is 10
String hashed = BCrypt.hashpw(password, BCrypt.gensalt(12));

// Check that an unencrypted password matches one that has previously been hashed
if (BCrypt.checkpw(candidate, hashed))
System.out.println("It matches");
else
System.out.println("It does not match");



经验证,用 jbcrypt 对同一个明文加密后因为 salt 一般不同,加密后的密文一般不同。

bcrypt 精要概况

bcrypt 是不可逆的加密算法,无法通过解密密文得到明文。
bcrypt 和其他对称或非对称加密方式不同的是,不是直接解密得到明文,也不是二次加密比较密文,而是把明文和存储的密文一块运算得到另一个密文,如果这两个密文相同则验证成功。

总结

综上, Jenkins 专有用户数据库使用了 jbcrypt 加密, jbcrypt 加密是不可逆的,而且对于同一个明文的加密结果一般不同。

使用 Zabbix 监控 Jenkins

灵雀云 发表了文章 • 0 个评论 • 380 次浏览 • 2019-04-16 10:58 • 来自相关话题

【编者的话】Zabbix是一个基于Web界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。它能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 笔者最近的工作涉 ...查看全部
【编者的话】Zabbix是一个基于Web界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。它能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。

笔者最近的工作涉及到使用 Zabbix 监控 Jenkins。在谷歌上搜索到的文章非常少,能操作的就更少了。所以决定写一篇文章介绍如何使用 Zabbix 监控 Jenkins。 下图为整体架构图:

pic1.jpg



整体并不复杂,大体步骤如下: 在 Jenkins 上安装 Metrics 插件,使 Jenkins 暴露 metrics api。 配置 Zabbix server 及 agent 以实现监控及告警 为方便读者实验,笔者将自己做实验的代码上传到了 GitHub,链接在文章末尾。使用的是 Docker Compose 技术(方便一次性启动所有的系统)。
接下来,我们详细介绍 Metrics插件及如何实现 Zabbix 监控 Jenkins。


使 Jenkins 暴露 metrics api
安装 Metrics 插件,在系统配置中,会多出“Metrics”的配置,如下图:

pic2.jpg


配置项不复杂。我们需要点击“Generate...”生成一个 Access Key(生成后,记得要保存)。这个 Key 用于身份校验,后面我们会用到。 保存后,我们在浏览器中输入URL:http://localhost:8080/metrics/<刚生成的 Access Key> 验证 Jenkins 是否已经暴露 metrics。如果看到如下图,就说明可以进行下一步了。


Pic3.jpg


1.1 Metrics 插件介绍

Metrics 插件是基于 dropwizard/metrics 实现。它通过4个接口暴露指标数据:/metrics,/ping,/threads,/healthcheck。

1.2 Metrics 插件:/metrics 接口介绍

点击上图中的metric链接(http://localhost:8080/metrics//metrics),它暴露了以下指标数据:


{
version: "3.0.0",
gauges: {...},
counters: {...},
histograms: {...},
meters: {...},
timers: {...}
}


复制代码
从数据结构中可以看出它将指标分成 5 种数据类型: Gauges:某项指标的瞬时值,例如:当前 Jenkins executor 的总个数(jenkins.executor.count.value) Counters:某项指标的总数值,例如:http 请求活动连接数(http.activeRequests) Meters:一段时间内,某事件的发生概率,例如:Jenkins成功执行的任务每分钟的执行次数(jenkins.runs.success.m1_rate) Histogram:统计指标的分布情况。例如:Jenkins executor 数量的分布(jenkins.executor.count.history) Timer:某项指标的持续时间。例如:Jenkins 任务等待时间(jenkins.job.waiting.duration) 由于指标非常之多,我们就不分别介绍了。具体有哪些指标,读者朋友可以从代码仓库中的 metrics.json 文件了解。

1.2 Metrics 插件其它接口介绍
. /ping:接口返回 pong 代表 Jenkins 存活,如下图:


Pic3.jpg


/threads:返回 Jenkins 的线程信息 /healthcheck:返回以下指标:


{
disk-space: {
healthy: true
},
plugins: {
healthy: true,
message: "No failed plugins"
},
temporary-space: {
healthy: true
},
thread-deadlock: {
healthy: true
}
}
复制代码


2. 配置 Zabbix server 与 agent 实现监控及告警

Zabbix server 通过与 Zabbix agent 进行通信实现数据的采集。而 Zabbix agent 又分为被动和主动两种模式。我们使用的是被动模式,也就是Zabbix server 向 agent 索要数据。 所以,我们需要在 Zabbix agent 所在机器放一个获取 Jenkins 指标数据的脚本。再配置 Zabbix server 定时从该 agent 获取数据,最后配置触发器(trigger)实现告警。 接下来的关于 Zabbix 的配置,基于我的 jenkins-zabbix 实验环境,读者朋友需要根据自己的实际情况变更。

2.1 配置 Zabbix server 如何从 agent 获取指标数据 首先,我们需要告诉 Zabbix server 要与哪些 Zabbix agent 通信。所以,第一步是创建主机,如下图:


Pic5.jpg



第二步,在主机列表中点击“Iterms”进行该主机的监控项设置:


pic6.jpg




第三步,进入创建监控项页面:


Pic7.jpg




第四步,创建监控项:


pic8.jpg


这里需要解释其中几个选项为什么要那样填:
Type:是 Zabbix server 采集指标的类型,我们选择的是 Zabbix agent,如上文所说。
Key:由于我们要监控的指标并不是 Zabbix 预定义的。所以,需要使用用户自定义参数来实现监控 Jenkins 指标。Key 填的值为:jenkins.metrics[gauges.jenkins.node.count.value.value]。jenkins.metrics是需要执行的真正的 Key 名称。而 [] 内是传给该 Key 对应的命令的参数。对于初学者,Zabbix 这部分概念非常不好理解。也许这样会更好理解:在使用用户自定义参数来实现监控的情况下,Zabbix server 会将这个 Key 发送给 agent,然后 agent 根据这个 Key 执行指定的 逻辑 以获取指标数据。这个 逻辑 通常是一段脚本(shell命令或Python脚本等)。而脚本也是可以传参的,[]中的值就是传给脚本的参数。具体更多细节,下文会继续介绍。

Type of information:监控数据的数据类型,由于我们监控的是 Jenkins node 节点的个数,所以,使用数字整型。

Update interval:指 Zabbix server 多长时间向 agent 获取一次数据,为方便实验,我们设置为 2s。 到此,Zabbix server 端已经配置完成。

2.2 配置 Zabbix agent 使其有能力从 Jenkins 获取指标数据
当 Zabbix agent 接收到 server 端的请求,如 jenkins.metrics[gauges.jenkins.node.count.value.value]。Zabbix agent 会读取自己的配置(agent 启动时会配置),配置内容如下:
Zabbix Agent Configuration File for Jenkins Master
UserParameter=jenkins.metrics[*], python /usr/lib/zabbix/externalscripts/jenkins.metrics.py $1 根据 Key 名称(jenkins.metrics)找到相应的命令,即:python /usr/lib/zabbix/externalscripts/jenkins.metrics.py $1。并执行它,同时将参数 gauges.jenkins.node.count.value.value 传入到脚本 jenkins.metrics.py 中。jenkins.metrics.py 需要我们在 Jenkins agent 启动前放到 /usr/lib/zabbix/externalscripts/ 目录下。 jenkins.metrics.py 的源码在 jenkins-zabbix 实验环境中可以找到,篇幅有限,这里就简单介绍一下其中的逻辑。 jenkins.metrics.py 所做的事情,无非就是从 Jenkins master 的 metrics api 获取指标数据。但是由于 api 返回的是 JSON 结构,并不是 Zabbix server 所需要的格式。所以,jenkins.metrics.py 还做了一件事情,就是将 JSON 数据进行扁平化,比如原来的数据为:{"gauges":{"jenkins.node.count.value": { "value": 1 }}} 扁平化后变成: gauges.jenkins.node.count.value.value=1。 如果 jenkins.metrics.py 脚本没有接收参数的执行,它将一次性返回所有的指标如:


......
histograms.vm.memory.pools.Metaspace.used.window.15m.stddev=0.0
histograms.vm.file.descriptor.ratio.x100.window.5m.p75=0.0
histograms.vm.memory.pools.PS-Old-Gen.used.window.5m.count=4165
gauges.vm.runnable.count.value=10
timers.jenkins.task.waiting.duration.mean=0.0
histograms.vm.memory.non-heap.committed.history.p99=123797504.0
gauges.vm.memory.pools.PS-Eden-Space.used.value=19010928
gauges.jenkins.node.count.value.value=1
histograms.vm.memory.pools.Code-Cache.used.window.15m.mean=44375961.6


复制代码
...... 但是,如果接收到具体参数,如 gauges.jenkins.node.count.value.value ,脚本只返回该参数的值。本例中,它将只返回 1。 jenkins.metrics.py 脚本之所以对 JSON 数据进行扁平化,是因为 Zabbix server 一次只拿一个指标的值(这点需要向熟悉 Zabbix 的人求证,笔者从文档中没有找到明确的说明)。 注意:在 2.1 节中,如果 Key 值设置为:jenkins.metrics,Zabbix server 不会拿 jenkins.metrics.py 返回的所有的指标值自动创建对应的监控项。所以,Key 值必须设置为类似于 jenkins.metrics[gauges.jenkins.node.count.value.value] 这样的值。

3. 配置 Zabbix server 监控指标,并告警
在经过 2.2 节的配置后,如果 Zabbix server 采集到数据,可通过_Monitoring -> Latest data -> Graph_菜单(如下图),看到图形化的报表:

pic9.jpg



图形化的报表:

pic10.jpg



有了指标数据就可以根据它进行告警了。告警在 Zabbix 中称为触发器(trigger)。如下图,我们创建了一个当 Jenkins node 小于 2 时,就触发告警的触发器:

pic11.jpg


至于最终触发器的后续行为是发邮件,还是发短信,属于细节部分,读者朋友可根据自己的情况进行设置。

小结

在理解了 Zabbix server 与 agent 之间的通信原理的前提下,使用 Zabbix 监控 Jenkins 是不难的。笔者认为难点在于自动化整个过程。上文中,我们创建主机和添加监控项的过程,是手工操作的。虽然 Zabbix 能通过自动发现主机,自动关联模板来自动化上述过程,但是创建”自动化发现主机“和”自动关联动作“依然是手工操作。这不符合”自动化一切“的”追求“。 最后,如果读者朋友不是历史包袱原因而选择 Zabbix,笔者在这里推荐 Prometheus,一款《Google 运维解密》推荐的开源监控系统。

实操进阶 | Jenkins 和 Kubernetes 云上的神秘代理

灵雀云 发表了文章 • 0 个评论 • 404 次浏览 • 2019-03-28 14:14 • 来自相关话题

最近我们构建和部署服务的方式与原来相比简直突飞猛进,像那种笨拙的、单一的、用于构建单体式应用程序的方式已经是过去式了。我们努力了这么久,终于达到了现在的效果。现在的应用为了提供更好的拓展性和可维护性,都会去拆解成各种相互依赖小、解耦性强的微服务,这些服务有各自 ...查看全部
最近我们构建和部署服务的方式与原来相比简直突飞猛进,像那种笨拙的、单一的、用于构建单体式应用程序的方式已经是过去式了。我们努力了这么久,终于达到了现在的效果。现在的应用为了提供更好的拓展性和可维护性,都会去拆解成各种相互依赖小、解耦性强的微服务,这些服务有各自的依赖和进度。如果想去构建服务,从一开始,就应该使用 CI/CD 的方式;当然,如果你走上了这条路, Jenkins 就是你的良师益友。

如果你是做微服务,那让我们在开始之前先花些时间想一想。如果只在 Jenkins 上构建单体式应用程序,那你肯定每天都会运行很多 Jenkins job, 而且要不厌其烦地运行很多次。所以,我们应该好好想清楚如何来做出一些改变。其实只需要付出一些努力,Jenkins 就可以很好地解决这种事情。



Jenkins 进阶之路



作为 DevOps 从业者,我遇到的最大问题是如何管理并优化自己的 Jenkins agent 结构。如果只是实验性地用 Jenkins跑一些流水线,那根本不用考虑 agent 的事情。如果每天要跑成百上千条流水线的话,那怎么去做优化就是一件非常非常重要的事情。在 Jenkins 进阶之路中,我也尝试了各种不同的方式来寻找Jenkins agent 的最佳使用方式。如果你也和我一样经历过,那下面这些事情你一定会很熟悉。

下面是我在这些年中使用 Jenkins 的各个阶段:
  1. 所有构建都在 master 节点上跑,在这个节点上运行所有的组件。(我给这个阶段起了个可爱的名字, Hello Jenkins)

  1. 创建一个 Jenkins EC2 代理,并且在这个代理上运行所有的构建, 就是大而全,这个节点什么都能做。如果需要同时做多条任务,那就把这个大而全的节点克隆一份。(这个阶段我命名为 Monster Agent.)

  1. 为每种服务创建不同的 Jenkins EC2 的节点(这个阶段叫Snowflake Agent.)

  1. 在容器中运行流水线的所有步骤。 比如,在 Jenkins 中使用 Docker Plugin 插件将代理挂载到容器中,或者使用 multi-stage Dockerfiles 把所有构建,测试打包的流程都封装起来。这两种方法都是很好的容器抽象化的开端,并且允许轻松地将制品从一个容器复制到另一个容器。当然了,每种方法都需要访问 Docker engine 。为了让我的 Jenkins 代理能够正常工作,现在我用以下几种方式来管理 docker host:
* 在Jenkins 主容器中运行一个Docker engine - Docker in Docker (DinD);
* 把主机上的 Docker socket 挂载到容器中,让容器能够以 sidecar 的方式运行;
* 为 Jenkins 主服务器配置单个外部 EC2 Docker 主机,以用于在容器中启动构建;
* 使用 EC2 插件和包含 Docker Engine 的 AMI 动态启动代理,然后运行多阶段 Dockerfile 中的所有步骤。
以上这些阶段各有利弊,但都是为了让我们从管理 Jenkins 节点中解放出来。不过,最近我又进阶到了另外一个阶段:Jenkins on Kubernetes

一旦在 Jenkins 中把构建节点和 job 都容器化了,迁移工作平台将变得十分简单易行。这里郑重声明一下,在使用这个方法前我一直没有接触过 Kubernetes。也就是说,在 Google Cloud Platform(GCP)GKE 中创建 Kubernetes 集群,使用 Helm Chart启动 Jenkins master ,并在 Kubernetes 集群中的 Jenkins 代理中运行构建是非常简单的。



流水线脚本中启动 K8s 中的代理



如何配置 Jenkins 才能使流水线脚本能够在 K8s 集群中启动 Jenkins 节点。首先要先安装Kubernetes plugin 插件。有意思的是,当我用 Helm chart 来安装Jenkins 时,安装好的 Jenkins 里面已经有了该插件。还有一个前提,启动的 Jenkins 节点要和 Jenkins master 在同一个 K8s 集群里。

一旦在 K8s 中运行了 Jenkins master 节点,只需要简单配置几步,就能启动一个小构建。


配置 Jenkins Master



为了保证 Jenkins 能够访问 K8s 集群的资源,首先需要按照以下步骤创建一些凭据:
  1. 进入 Jenkins 的 UI 界面,点击左边导航栏里的凭据链接
  2. 点击 Stores scoped to Jenkins 列表下 global 中的 Add credentials (将鼠标悬停在链接旁边即可看到箭头)
  3. 点击添加凭证
  4. 写好 Kubernetes Service Account
  5. 将范围设置为全局
  6. 点击 OK 按钮
这样 Jenkins 就可以使用这个凭据去访问 K8s 的资源。


在 Jenkins Master 中配置云



下一步就是在 Jenkins 中设置云的配置:
  1. 进入 Jenkins UI 界面,点击 系统管理 → 系统设置;
  2. 进入管理界面后查找 『云』(一般在下面),然后点击 『新增一个云』,选择 kubernetes 类型;
  3. 如下这些是必填的参数:
Name 自定义, 默认是 kubernetes;
Kubernetes URL https://kubernetes.default -一般是从 service account 自动配置;
Kubernetes Namespace 一般是 default 除非要在一个特殊的命名空间 ,否则不要动;
Credentials 选择上一步创建的凭据;
Jenkins URL http://:8080;
Jenkins tunnel :5555 - 用来和 Jenkins 启动的 agent 进行交互的端口;


你看,只需要几个参数就能在 K8s 集群中启动一些节点了,当然你的环境需要的话也可以做一些其他的调整。

现在你已经可以通过定义一些 pod 实现Jenkins master 访问 K8s 集群了。pod其实是 K8s 中的概念,在一个 pod 里面会有一个或者多个容器,它们共享网络还有存储,我们可以在这个 pod 中执行一些构建工作。每一个 Jenkins 节点都是作为 K8s pod 来启动的。这个 pod 里面经常会包含一个默认的 JNLP 容器,还有一些pod 模板中定义的容器。现在有至少两种方法来定义pod template。



通过 Jenkins UI 配置一个 pod template



  1. Manage Jenkins → Configure Systems;
  2. 找到之前配置 Jenkins K8s 的地方;
  3. 点击 Add Pod Template button 选择 Kubernetes Pod Template;
  4. 输入下面的值:
Name 自定义;
Namespace default -除非想换个在上一步自定义的命名空间;
Labels 自定义 - 这个将用来匹配你在 jenkinsfile 中的 label 值;
Usage 如果想让这个 pod 作为默认节点的话,就选择 "Use this node as much as possible", 如果选择 "Only build jobs with label matching expressions matching this node" 的话 那就是只有在 Jenkins 脚本中定义的label匹配的构建才能使用这个节点;
The name of the pod template to inherit from 这个可以置空,现在还用不到;
Containers 在 pod 中启动的容器,下面会有详细介绍;
EnvVars 在 pod 中注入的环境变量;
Volumes 在 pod 中挂载的任何一种卷;



需要记住,在一个 pod 中会有不止一个容器,它们都是共存的。如果你是用 Helm chart 安装 Jenkins 的话,pod 中就会包含 JNLP 这个容器,这个容器也是 Jenkins agent 中必须包含的。为了完成更多服务的构建,还需要添加一些其他工具链的容器。



添加容器模板



  1. 进入 Jenkins UI 界面,回到上一步创建 pod template ;
  2. 点击 Add Container 按钮, 选择 Container Template;
  3. 输入下面的值:
Name 自定义;
Docker image 根据需求来写,比如在构建一个go 写的应用时,可以输入 golang:1.11-alpine3.8;
Label 表明要用在流水线脚本中引用此容器模板的标签字符串;
Always pull image 如果想让 pod 启动的时候都去拉取镜像就选择这个;



你可以保留其他参数的默认值,但是可以看到该插件可以对 pod 以及在其中运行的各个容器进行详细控制。你可以通过此插件设置在 Kubernetes pod 配置中的任何值。你还可以通过输入原始 YAML 来注入配置数据。无需因选项过多而分心,选择配置它们中的一小部分就可以获得工作环境。

单击容器模板中的“添加环境变量”按钮,将环境变量注入特定容器,也可以单击模板中的“添加环境变量”按钮,将环境变量注入所有的容器。

以下环境变量会自动注入默认的 JNLP 容器,来保障它能自动连接到 Jenkins 主服务器:

* `JENKINS_URL`: Jenkins 网页界面网址
* `JENKINS_JNLP_URL`: Jenkins 特定 slave 中 jnlp 的 url
* `JENKINS_SECRET`: 身份验证的密钥
* `JENKINS_NAME`: Jenkins 代理的名称

如果单击“添加卷”按钮,将看到几个用于添加卷的选项,这里使用 Host Path Volume 选项将 docker socket 安装在 pod 中。然后,可以运行安装了 Docker 客户端的容器,并且来构建和推送 Docker 镜像。

此时,我们为 Kubernetes 集群创建了一个云配置,并定义了一个由一个或多个容器组成的 pod。现在如何使用它来运行 Jenkins 工作?

很简单,只需要我们在 Jenkins 流水线脚本中通过标签引用 pod 和容器就可以了。
本文中的示例是使用脚本流水线,当然您可以使用声明式流水线语法实现相同的结果:




node('test-pod') {
stage('Checkout') {
checkout scm
}
stage('Build'){
container('go-agent') {
// This is where we build our code.
}
}
}









用 jenkinsfile 来实现相同的功能



通过 UI 配置插件现在看起来很不错。但有一个明显的问题,配置不能像源代码一样进行版本控制和存储。幸运的是,您可以直接在 Jenkinsfile 中创建整个 pod 定义。哈哈,在 Jenkinsfile 中有什么不能做的呢?

可以将 UI 或 YAML 定义中可用的任何配置参数添加到 `podTemplate` 和`containerTemplate` 部分。

在下面的示例中,我已经定义了一个包含两个容器模板的 pod。

pod 标签将会用于节点,表示想要启动此 pod 实例。

直接在节点内定义但没有在容器块中定义的任何步骤,都可以在默认的 JNLP 容器中运行。

容器块用于表示该容器块内的步骤应在具有给定标签的容器内运行。我已经定义了一个标签为 `golang` 的容器模板,我将用它来构建 Go 可执行文件,最终将其打包成 Docker 镜像。在 `volumes` 中,已经指出想要挂载主机的 Docker 套接字,但仍然需要 Docker 客户端使用 Docker API 与它进行交互。因此,已经定义了一个标签为 `docker` 的容器模板,该模板使用安装了 Docker 客户端的镜像。



podTemplate(
name: 'test-pod',
label: 'test-pod',
containers: [
containerTemplate(name: 'golang', image: 'golang:1.9.4-alpine3.7'),
containerTemplate(name: 'docker', image:'trion/jenkins-docker-client'),
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock',
hostPath: '/var/run/docker.sock',
],
{
//node = the pod label
node('test-pod'){
//container = the container label
stage('Build'){
container('golang'){
// This is where we build our code.
}
}
stage('Build Docker Image'){
container(‘docker’){
// This is where we build the Docker image
}
}
}
})


在基于 Docker 的流水线脚本中,我构建了 Docker 镜像并将其推送到 Docker 仓库,对我来说,能够复制这些配置信息非常重要。完成后,我已准备好使用`gcloud`(Google Cloud SDK)构建镜像,并将该镜像推送到 Google Container Registry,以便部署到 K8s 群集。

使用 gcloud 镜像指定了一个容器模板,并将 docker 命令更改为 gcloud 命令。、

就这么简单!


podTemplate(
name: 'test-pod',
label: 'test-pod',
containers: [
containerTemplate(name: 'golang', image: 'golang:1.9.4-alpine3.7'),
containerTemplate(name: 'gcloud', image:'gcr.io/cloud-builders/gcloud'),
],
{
//node = the pod label
node('test-pod'){
//container = the container label
stage('Build'){
container('golang'){
// This is where we build our code.
}
}
stage('Build Docker Image'){
container(‘gcloud’){
//This is where we build and push our Docker image.
}
}
}
})



在 上运行 Jenkins master、Jenkins 代理,构建和部署示例应用程序其实只花了几个小时。但之后,我花了一个周末的时间才深入了解该平台。如果你学得够快,相信你在几天内就可以完全掌握并且灵活运用这个平台了。

Electron 应用的流水线设计

灵雀云 发表了文章 • 0 个评论 • 526 次浏览 • 2019-03-18 13:57 • 来自相关话题

面向读者:需要了解 Jenkins 流水线的基本语法。 Electron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 本文将介绍 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平台下构建”的阶段中(www.alauda.cn), agent{label"mac && nodejs"} 指定了只有 label 同时包括了 mac 和 nodejs 的 agent 才能执行构建。
多平台的构建应该是并行的,以提升流水线的效率。我们通过 parallel 指令实现。
另外,默认 Electron 应用使用的三段式版本号设计,即 Major.Minor.Patch。但是笔者认为三段式的版本号信息还不够追踪应用与构建之间的关系。笔者希望版本号能反应出构建号和源代码的 commit id。函数 generateVersion 用于生成此类版本号。生成的版本号,看起来类似这样: 1.0.0-f7b06d0.28。
完整源码地址:https://github.com/zacker330/electronjs-pipeline-demo

小结

上例中,Electron 应用的流水线设计思路,不只是针对 Electron 应用,所有的跨平台应用的流水线都可以参考此思路进行设计。设计思路大概如下:
多平台构建并行化。本文只有操作系统的类型这个维度进行了说明。现实中,还需要考虑其它维度,如系统位数(32位、64位)、各操作系统下的各版本。
各平台下的构建只做一次编译打包。并将制品上传到制品库,以方便后续步骤或阶段使用。
全局变量与平台相关变量进行分离。
最后,希望能给读者带来一些启发。

批量修改 Jenkins 任务的技巧

灵雀云 发表了文章 • 0 个评论 • 771 次浏览 • 2019-03-04 14:45 • 来自相关话题

通过脚本命令行批量修改 Jenkins 任务 最近,笔者所在团队的 Jenkins 所在的服务器经常报硬盘空间不足。经查发现很多任务没有设置“丢弃旧的构建”。通知所有的团队检查自己的 Jenkins 任务有没有设置丢弃旧的构建,有些不 ...查看全部
通过脚本命令行批量修改 Jenkins 任务

最近,笔者所在团队的 Jenkins 所在的服务器经常报硬盘空间不足。经查发现很多任务没有设置“丢弃旧的构建”。通知所有的团队检查自己的 Jenkins 任务有没有设置丢弃旧的构建,有些不现实。
一开始想到的是使用 Jenkins的 API 来实现批量修改所有的 Jenkins 任务。笔者对这个解决方案不满意,经 Google 发现有同学和我遇到了同样的问题。他使用的更“技巧”的方式:在 Jenkins 脚本命令行中,通过执行 Groovy 代码操作 Jenkins 任务。
总的来说,就两步:
进入菜单:系统管理 --> 脚本命令行
在输入框中,粘贴如下代码:


import jenkins.model.Jenkins
import hudson.model.Job
import jenkins.model.BuildDiscarderProperty
import hudson.tasks.LogRotator
// 遍历所有的任务
Jenkins.instance.allItems(Job).each { job ->

if ( job.isBuildable() && job.supportsLogRotator() && job.getProperty(BuildDiscarderProperty) == null) {
println " \"${job.fullDisplayName}\" 处理中"

job.addProperty(new BuildDiscarderProperty(new LogRotator (2, 10, 2, 10)))
println "$job.name 已更新"
}
}
return;

/**

LogRotator构造参数分别为:
daysToKeep: If not -1, history is only kept up to this days.
numToKeep: If not -1, only this number of build logs are kept.
artifactDaysToKeep: If not -1 nor null, artifacts are only kept up to this days.
artifactNumToKeep: If not -1 nor null, only this number of builds have their artifacts kept.
**/


脚本

脚本命令行介绍
脚本命令行(Jenkins Script Console),它是 Jenkins 的一个特性,允许你在 Jenkins master 和 Jenkins agent 的运行时环境执行任意的 Groovy 脚本。这意味着,我们可以在脚本命令行中做任何的事情,包括关闭 Jenkins,执行操作系统命令 rm -rf /(所以不能使用 root 用户运行 Jenkins agent)等危险操作。
除了上文中的,使用界面来执行 Groovy 脚本,还可以通过 Jenkins HTTP API:/script执行。具体操作,请参考 官方文档。

问题:代码执行完成后,对任务的修改有没有被持久化?

当我们代码job.addProperty(new BuildDiscarderProperty(new LogRotator (2, 10, 2, 10)))执行后,这个修改到底有没有持久化到文件系统中呢(Jenkins 的所有配置默认都持久化在文件系统中)?我们看下 hudson.model.Job 的源码,在addProperty方法背后是有进行持久化的:



public void addProperty(JobProperty jobProp) throws IOException {
((JobProperty)jobProp).setOwner(this);
properties.add(jobProp);
save();
}


小结

本文章只介绍了批量修改“丢弃旧的构建”的配置,如果还希望修改其它配置,可以参考 hudson.model.Job 源码。
不得不提醒读者朋友,Jenkins 脚本命令行是一把双刃剑,大家操作前,请考虑清楚影响范围。如果有必要,请提前做好备份。

一些小团队的自动化运维实践经验

大卫 发表了文章 • 0 个评论 • 2448 次浏览 • 2018-06-07 16:03 • 来自相关话题

注:本文要求读者对 Ansible 和 Jenkins 有一定的认识。 题记:幸福的家庭都是相似的,不幸的家庭各有各的不幸。 行业内各巨头的自动化运维架构都各种功能各种酷炫,如下图,让人可望不可及。现在最终的 ...查看全部
注:本文要求读者对 Ansible 和 Jenkins 有一定的认识。

题记:幸福的家庭都是相似的,不幸的家庭各有各的不幸。

行业内各巨头的自动化运维架构都各种功能各种酷炫,如下图,让人可望不可及。现在最终的样子大家都知道了,但问题是如何根据自己团队当前的情况一步步向那个目标演进?
01.png

笔者所在团队,三个半开发,要维护几十台云机器,部署了十来个应用,这些应用 90% 都是遗留系统。应用系统的编译打包基本在程序员自己的电脑上。分支管理也清一色的 dev 分支开发,测试通过后,再合并到 master 分支。生产环境的应用配置要登录上具体的机器看才知道,更不用说配置中心及配置版本化了。

对了,连基本的机器级别的基础监控都没有。

我平时的工作是 50% 业务开发,50% 运维。面对这么多问题,我就想啊,如何在低成本情况下实现自动化运维。本文就是总结我在这方面一些经验和实践。希望对读者有帮助。
#别说话,先上监控和告警
事情有轻重缓急,监控和告警是我觉得一开始就要做的,即使业务开发被拖慢。只有知道了当前的情况,你才好做下一步计划。

现在市面上监控系统很多:Zabbix、Open-Falcon、Prometheus。最终作者选择了 Prometheus。因为:

  1. 它是拉模式的
  2. 它方便使用文本方式来配置,有利于配置版本化
  3. 插件太多了,想要监控什么,基本都会有现成的
  4. 以上三者,我基本都要重新学,我为什么不学一个 Google SRE 书上推荐的呢?

之前我们已经介绍过,人少机器多,所以,安装 Prometheus 的过程也必须要自动化,同时版本化。笔者使用的是 Ansible + Git 实现。最终样子如下:
02.png

这里需要简单介绍一下:

  1. Prometheus Server 负责监控数据收集和存储
  2. Prometheus Alert manager 负责根据告警规则进行告警,可集成很多告警通道
  3. node-exporter 的作用就是从机器读取指标,然后暴露一个 http 服务,Prometheus 就是从这个服务中收集监控指标。当然 Prometheus 官方还有各种各样的 exporter。

使用 Ansible 作为部署工具的一个好处是太多现成的 role 了,安装Prometheus 时,我使用的是现成的:prometheus-ansble

有了监控数据后,我们就可以对数据进行可视化,Grafana 和 Prometheus 集成得非常好,所以,我们又部署了 Grafana:
03.png

在 Grafana 上查看 nodex-exporter 收集的数据的效果图大概如下:
04.png

可是,我们不可能24小时盯着屏幕看CPU负载有没有超吧?这时候就要上告警了,Promehtues 默认集成了 N 多告警渠道。可惜没有集成钉钉。但也没有关系,有好心的同学开源了钉钉集成 Prometheus 告警的组件:prometheus-webhook-dingtalk。接着,我们告警也上了:
05.png

完成以上工作后,我们的基础监控的架子就完成了。为我们后期上 Redis 监控、JVM 监控等更上层的监控做好了准备。
#配置版本化要从娃娃抓起
在搭建监控系统的过程中,我们已经将配置抽离出来,放到一个单独的代码仓库进行管理。以后所有部署,我们都会将配置和部署逻辑分离。

关于如何使用 Ansible 进行配置管理,可以参考这篇文章:How to Manage Multistage Environments with Ansible 。我们就是使用这种方式来组织环境变量的。
├── environments/         # Parent directory for our environment-specific directories
│ │
│ ├── dev/ # Contains all files specific to the dev environment
│ │ ├── group_vars/ # dev specific group_vars files
│ │ │ ├── all
│ │ │ ├── db
│ │ │ └── web
│ │ └── hosts # Contains only the hosts in the dev environment
│ │
│ ├── prod/ # Contains all files specific to the prod environment
│ │ ├── group_vars/ # prod specific group_vars files
│ │ │ ├── all
│ │ │ ├── db
│ │ │ └── web
│ │ └── hosts # Contains only the hosts in the prod environment
│ │
│ └── stage/ # Contains all files specific to the stage environment
│ ├── group_vars/ # stage specific group_vars files
│ │ ├── all
│ │ ├── db
│ │ └── web
│ └── hosts # Contains only the hosts in the stage environment

现阶段,我们所有的配置都以文本的方式存储,将来要切换成使用 Consul 做配置中心,也非常的方便,因为 Ansible 2.0 以上的版本已经原生集成了Consule:consul_module

Tips:Ansible 的配置变量是有层次的,这为我们的配置管理提供了非常大的灵活性。
#Jenkins 化:将打包交给 Jenkins
我们要将所有的项目的打包工作交给 Jenkins。当然,现实中我们是先将一些项目放到 Jenkins 上打包,逐步将项目放上 Jenkins。

首先我们要有 Jenkins。搭建 Jenkins 同样有现成的 Ansible 脚本:ansible-role-jenkins。注意了,在网上看到的大多文章告诉你 Jenkins 都是需要手工安装插件的,而我们使用的这个 ansible-role-jenkins 实现了自动安装插件,你只需要加一个配置变量 jenkins_plugins 就可以了,官方例子如下:
---
[list]
[*]hosts: all[/*]
[/list] vars:
jenkins_plugins:
- blueocean
- ghprb
- greenballs
- workflow-aggregator
jenkins_plugin_timeout: 120

pre_tasks:
- include_tasks: java-8.yml

roles:
- geerlingguy.java
- ansible-role-jenkins

搭建好 Jenkins 后,就要集成 Gitlab 了。我们原来就有Gitlab了,所以,不需要重新搭建。如何集成就不细表了,网络上已经很多文章。

最终 Jenkins 搭建成以下这个样子:
06.png

关于 Jenkins master 与 Jenkins agent 的连接方式,由于网络环境各不相同,网上也有很多种方式,大家自行选择适合的方式。

好,现在我们需要告诉 Jenkins 如何对我们的业务代码进行编译打包。有两种方法:

  1. 界面上设置
  2. 使用 Jenkinsfile:类似于 Dockerfile 的一种文本文件,具体介绍:Using a Jenkinsfile

作者毫不犹豫地选择了第2种,因为一是利于版本化;二是灵活。

Jenkinsfile 类似这样:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh './gradlew clean build'
archiveArtifacts artifacts: '*[i]/target/[/i].jar', fingerprint: true
}
}
}
}

那么 Jenkinsfile 放哪里呢?和业务代码放在一起,类似这样每个工程各自管理自己的 Jenkinsfile:
07.png

这时,我们就可以在 Jenkins 上创建一个 pipleline Job了:

关于分支管理,我们人少,所以,建议所有项目统一在 master 分支进行开发并发布。
#让 Jenkins 帮助我们执行 Ansible
之前我们都是在程序员的电脑执行 Ansible 的,现在我们要把这项工作交给 Jenkins。具体操作:

  1. 在 Jenkins 安装 Ansible 插件
  2. 在 Jenkinsfile 中执行

withCredentials([sshUserPrivateKey(keyFileVariable:"deploy_private",credentialsId:"deploy"),file(credentialsId: 'vault_password', variable: 'vault_password')]) {
ansiblePlaybook vaultCredentialsId: 'vault_password', inventory: "environments/prod", playbook: "playbook.yaml",
extraVars:[
ansible_ssh_private_key_file: [value: "${deploy_private}", hidden: true],
build_number: [value: "${params.build_number}", hidden: false]
]
}

这里需要解释下:

  1. ansiblePlaybook 是 Jenkins ansible 插件提供的 pipeline 语法,类似手工执行:ansible-playbook 。
  2. withCredentials 是 Credentials Binding 插件的语法,用于引用一些敏感信息,比如执行 Ansible 时需要的 ssh key 及 Ansible Vault 密码。
  3. 一些敏感配置变量,我们使用 Ansible Vault 技术加密。

#Ansible 脚本应该放哪?
我们已经知道各个项目各自负责自己的自动化构建,所以,Jenkinfile 就放到各自项目中。那项目的部署呢?同样的道理,我们觉得也应该由各个项目自行负责,所以,我们的每个要进行部署的项目下都会有一个 ansible 目录,用于存放 Ansible 脚本。类似这样:
08.png

但是,怎么用呢?我们会在打包阶段将 Ansible 目录进行 zip 打包。真正部署时,再解压执行里面的 playbook。
#快速为所有的项目生成 Ansible 脚本及Jenkinsfile
上面,我们将一个项目进行 Jenkins 化和 Ansible 化,但是我们还有很多项目需要进行同样的动作。考虑到这是体力活,而且以后我们还会经常做这样事,所以笔者决定使用 cookiecutter 技术自动生成 Jenkinsfile 及 Ansible 脚本,创建一个项目,像这样:
09.png

#小结
总结下来,我们小团队的自动化运维实施的顺序大概为:

  1. 上基础监控
  2. 上 Gitlab
  3. 上 Jenkins,并集成 Gitlab
  4. 使用 Jenkins 实现自动编译打包
  5. 使用 Jenkins 执行 Ansible

以上只是一个架子,基于这个“架子”,就可以向那些大厂的高大上的架构进行演进了。比如:

* CMDB的建设:我们使用 ansible-cmdb 根据 inventory 自动生成当前所有机器的情况
* 发布管理:Jenkins 上可以对发布的每个阶段进行定制。蓝绿发布等发布方式可以使用通过修改 Ansible 脚本和 Inventory 实现。
* 自动扩缩容:通过配置 Prometheus 告警规则,调用相应 webhook 就可以实现
* ChatOps:ChatOps实战

以上就是笔者关于自动化运维的一些实践。还在演进路上。希望能与大家交流。

原文链接:http://showme.codes/2018-06-07/devops-in-action/

Jenkins X 还是 2.0?

colstuwjx 发表了文章 • 0 个评论 • 7605 次浏览 • 2018-04-22 10:57 • 来自相关话题

【编者的话】本文主要介绍了最近发布的Jenkins X的一些吸引人的功能。 近期发布的Jenkins X在开源界备受关注。在这篇文章里,我将探讨新产品里一些吸引人的功能,这些功能尚未在文档里被特别提及。如果你需要一个产品指南或者其他类 ...查看全部
【编者的话】本文主要介绍了最近发布的Jenkins X的一些吸引人的功能。

近期发布的Jenkins X在开源界备受关注。在这篇文章里,我将探讨新产品里一些吸引人的功能,这些功能尚未在文档里被特别提及。如果你需要一个产品指南或者其他类型的说明文档,我强烈建议阅读一下动机功能文档。
## 它只是一个新的炒作工具吗?在容器里运行Jenkins有什么特别之处吗?
Kubernetes已然是管理容器,分布式应用以及虚拟基础设施的事实标准。主流的公有云厂商都有提供它的托管服务,而且可以按需在本地安装。如果用户今天构建了一个云应用并且希望它可以在任何地方运行,那么Kubernetes会是你的选择!

尽管Kubernetes的生态系统很庞大,它仍然非常年轻而且把更多精力放在处理“第一阶段”的挑战上,比如Kubernetes的推广。首先解决最紧迫的问题是很自然的事情。可以预见的是2018年Kubernetes将给用户带来更多的稳定性,集成度和用户体验方面的提升。今天,在这里我们考虑一下第二阶段的操作,如何在其之上构建一些东西,并且使开发人员的工作效率更高。
Jenkins X在这块填补了整体CI/CD管理的空白。
## 它只是Jenkins 2.0的一次品牌重塑吗?
Jenkins是一款在云成为标准之前就已经开发出来的工具,它绝对不是云原生工具,这意味着它不能够开箱即用(OOTB)的抗衡停机,无缝扩展等。

幸运的是,它是可扩展的。 Jenkins X通过Kubernetes插件演绎了一场魔术,它使得你不必再为slave配备虚拟机或者物理服务器;每个作业使用一次性代理,运行在不同的容器中,这些容器可以随集群一起扩展。

通过Jenkins X,你不仅可以获得面向应用程序的Kubernetes流水线,还可以获得可扩展的CI/CD解决方案。
screenshot-2018-04-11-193839.png

## 你能为我们团队安装Jenkins吗?
当Kubernetes以一个项目平台的形式提供时,这是开发团队最常见的问题。对组织而言,迁移主要工具往往意味着大量的变更;从字面上看,它会影响所有主要的DevOps流程,并且需要花费大量的学习精力。

Jenkins X与Kubernetes流水线,代理以及集成一起提供了一种更简单的迁移到Kubernetes和微服务的方式。
## 版本控制怎么做?
Jenkins X采用了GitOps概念。

保持版本控制中的所有配置意味着更高的安全性,灾难恢复以及将所有软件开发生命周期(SDLC)实践应用于运维任务的可能性。

开箱即用,将CI/CD基础架构配置保存到Git中,并且工具包始终遵循该原则。

即便是一些紧急情况,用户也无法僭越这一规则,设计就是这样的!
## 构建块
Jenkins2.0 :"老牌好用"的CI服务器

Helm:包管理器

Draft:开发环境构建工具

Monocular:Helm仓库的UI

Chartmuseum:带有云后端支持的Helm仓库

Nexus:资料库

Docker registry:容器镜像中心

以上所有内容均受jx工具的控制,并且配置以一个个的“平台”,一组Helm图的形式发布。
screenshot-2018-04-11-194449.png

## 全新视角下的环境管理
这是我最感兴趣的功能之一!

环境作为一个实体交由工具控制,你可以管理生命周期,升级(promote)和配置! 你可以轻松地自定义,模板和重用它。

它使用的是GitOps模式,并且每个环境都有一个存储库。这是一个最佳实践,由jx驱动OOTB。

Helm用于配置管理。 环境被表示为一个Helm包,每个应用程序都被添加为一个依赖项,同时可以升级(promote)。这种做法很有意义,因为环境配置是最不标准的地方。很多团队花了很多时间,直到我找到类似的东西。 而你完全是免费获取的!
## 瑞士军刀般的CLI
Jx CLI是该框架所有功能的入口点。

最重要的是:这是一个关键结构,在CD管道中被广泛使用。 它们可以很容易地直接播放和调试,任何步骤都可以通过CLI循环和重放。

原文链接:Jenkins: X or 2.0 ?(翻译:Colstuwjx)

Jenkins X将自动化管道引入Kubernetes

ylzhang 发表了文章 • 0 个评论 • 4525 次浏览 • 2018-04-09 21:04 • 来自相关话题

【编者的话】Jenkins X是一套高度集成化的CI/CD平台,以Jenkins和Kubernetes为实现基础,旨在解决微服务体系架构下云原生应用的持续交付问题,最终简化整个云原生应用的开发、运行和部署流程。Jenkins X带来一系列新特性,具体包括:全面 ...查看全部
【编者的话】Jenkins X是一套高度集成化的CI/CD平台,以Jenkins和Kubernetes为实现基础,旨在解决微服务体系架构下云原生应用的持续交付问题,最终简化整个云原生应用的开发、运行和部署流程。Jenkins X带来一系列新特性,具体包括:全面自动化,基于GitOps的一键环境部署,自动生成预览环境以及信息同步反馈。 Jenkins X和Kubernetes的结合则将运维自动化推向新的高度。

Jenkins持续集成服务器一直是DevOps浪潮中的重要组成部分。作为持续集成与持续部署环境中自动化任务的中央协调服务器,全球的开发和运营团队已经积极接纳Jenkins,并为之构建了多种扩展及构建包。 然而,从设计初衷角度来看,Jenkins并没有考虑到面向容器环境的迁移需求。
1.jpg

Cloudbees公司高级架构师James Strachen身为Jenkins X项目负责人,他一直在思考这种迁移对Jenkins用户的意义及影响。

他表示,将CI/CD管道迁移到配备容器及容器编排工具(如Kubernetes)的基于容器云托管环境时,用户需要在构建、测试和部署过程中建立更高的自动化水平。Strachen说:“ 为了实现这一点,Jenkins X必须具备远超常规Jenkins的易学习性及易用性。”

“我们要做的,是打破少数人对相关知识的‘垄断’。这部分人往往对Jenkins非常了解,但是团队里的每个人都有权也有意愿参与其中。我们需要开发人员能够随时根据需求启动新的项目——这也正是微服务时代的最大差异优势所在。我们希望Jenkins自助服务能够像云一样全面覆盖。“

“Jenkins X的目标是将Jenkins转化为一种自助服务设备,允许任何开发人员启动新项目,而与之相关的全部管道及匹配元素都能够顺利到位。 当然,这并不是说团队成员完全不需要了解Jenkins,但更重要的是我们希望让CI/CD拥有与云服务类似的低准入门槛,“他解释称。

Strachen在3月中旬发表的一篇博文中详细介绍了Jenkins X的技术细节,当时该项目才刚刚面向公众正式公布。在文章中,他解释称Jenkins X可用于设置Jenkins管道,自动生成Jenkinsfile和Dockerfile以定义应用程序的管道与打包步骤。

由于DockerKubernetes的运行活动同Jenkins设计环境存在一些差别,因此Strachen表示Jenkins X还需要将安全保障机制引入到复杂的容器镜像构建过程中。Strachen指出,用户可以利用自己的Dockerfiles或pop在管道中使用其最熟悉的容器构建工具。Strachen同时提到,Skaffold是一种用于构建和标记Docker镜像的工具——尽管听起来很简单,但其意义却非常重大。

“[Skaffold]负责管理Docker和安全模型的复杂性事务。 如果您使用Google Cloud,则可能需要使用谷歌提供的容器构建器。 您可能需要在自己的Kubernetes集群中使用SaaS CI或本地Docker套接字。 这种方法会带来安全隐患,因为这意味着使用者将掌握设备的root权限。因此,绝对不可让他人访问生产集群中的Docker套接字。举例来说,在默认情况下,Jenkins S会假设用户处于开发集群当中,并有权访问Docker套接字。但很明显,这样的假设并不靠谱,“Strachen强调称。

Strachen认为这是未来Docker镜像构建层面的一大主要问题。“我们需要利用Skaffold以及其它同类工具将用户与高权限隔离开来。我希望这一目标能够尽快实现。”他曾听到未经证实的消息称,Skaffold可能会在未来几个月内获得上述能力。

“在开发群集上,我们允许用户使用Docker来构建镜像。 大家往往坚定地相信团队中的开发者不会利用这些硬件挖掘比特币。 而在生产群集上,我们不需要构建镜像,而只是使用已经构建完成的现成镜像。 这是一个简单的方法,易于实现,而且能够有效消除风险。我认为从长远角度来看,为了确保在不使用Docker守护进程、root或其它特殊权限的前提下构建Docker镜像,最好是能够将Docker构建流程彻底移出Kubernetes集群之外,”Strachen表示。

#稳定性和兼容性
未来,Strachen表示Jenkins X团队在稳定性和兼容性方面还拥有多项开发目标。 “最重要的是检查一切组成元素。 在同一时间内支持多种云环境可谓极具挑战性。 因此,我们的第一项重点在于构建起坚如磐石的集群系统。 此外,我们还需要支持更多的Git供应程序以及问题跟踪器。 我们正在添加Jira支持能力,此外社区中也有部分贡献者正在进一步扩展Git供应程序的支持范围,例如GitLabBitBucket,“Strachen表示。

“着眼于更长期的目标,我们将着力改进反馈与整合等机制。 我们拥有一份宏观路线图,主要关注增加构建包的数量,此外还需要实现更广泛的语言支持能力,“Strachen说。

核心开发团队当前的另一项研究目标是与Anchore CI/CD安全扫描软件进行集成。 Anchore负责根据漏洞数据库分析代码与Docker镜像。

在实现上述复杂目标的过程当中,有一段代码已经为Jenkins X团队节省了大量时间,这就是UpdateBot。 这款小工具运行在其他项目更新库的依赖关系库中,能够自动提交请求以使用新的依赖项。

“有时候,我们需要使用上游库,”Strachen说,“而其他团队正在使用下游类库。通常情况下,如果不存在信息交换,这些库很少需要进行更新。但在实际更新过程中,我们需要采取持续部署的处理方法。UpdateBot要做的在执行过程中严格遵循持续部署原则。这些库不会被部署为Docker镜像,而是作为版本更改、经过更新的XML文件或基础Docker镜像文件存在。”

Strachen表示,这种在更新过程中持续面向下游消费者推送请求的作法,能够有效防止错误因未被及时发现而在更新周期之后集中爆发。

“我个人最欣赏的一点在于,如果上游开发者犯了错误,他们会立刻发现问题。这是因为在持续发布的版本中,所有相关请求都会遭遇故障。如此一来,将不致出现某一团队的失误导致另一团队的工作成果付之东流,并有效避免由此引发的团队间摩擦与挫败感。必须承认,犯下错误的团队往往对此毫不知情,因此在传统变更流程当中,当人们意识到存在严重问题时,其间往往已经进行过无数次变更——也就是说根本无法判断问题到底源自哪里。而持续部署原则将彻底攻克这一难题,”Strachen总结称。

原文链接:Jenkins X Brings Automated Pipelines to Kubernetes(翻译:ylzhang)

使用Jenkins部署应用到Kubernetes

国会山上的猫TuxHu 发表了文章 • 0 个评论 • 4451 次浏览 • 2018-01-09 23:14 • 来自相关话题

【编者的话】这篇文章基于去年5月进行的一次Kubernetes使用情况调查,阐述了使用Jenkins作为持续集成工具部署应用程序到Kubernetes的现状,对于大家如何进行CI/CD工具的选型有参考意义。 新的技术栈正不断交付Kub ...查看全部
【编者的话】这篇文章基于去年5月进行的一次Kubernetes使用情况调查,阐述了使用Jenkins作为持续集成工具部署应用程序到Kubernetes的现状,对于大家如何进行CI/CD工具的选型有参考意义。

新的技术栈正不断交付Kubernetes开源容器编排引擎的内容。 本周我们将报告如何将应用程序实际部署到Kubernetes。 当在容器编排、持续交付流水线或配置管理工具之间进行选择时,我们2017年5月进行的Kubernetes调查的受访者经常表示他们使用编排框架来部署应用程序。 虽然有些人曾经把Kubernetes想象成可以做任何事情的瑞士军刀,但实际上他们对应用程序部署中的Kubernetes的角色有着更细致的了解。

当更直接地问到他们是否使用Jenkins将应用程序部署到Kubernetes时,45%的人表示赞成,另有36%的Kubernetes用户使用其它的持续部署(CD)流水线将应用程序部署到Kubernetes,其中GitLab CICircleCI是被提及最多的。请注意,调查问题的性质意味着这些信息不能用来决定市场份额,其他工具的整体采用率可能高于下图所示。
3d93c6c4-numbers-190105.jpg

这就是说,Jenkins仍然是最常用的持续集成(CI)工具。 未来的研究将不会问是否使用Jenkins,而是会问在组织流水线中的核心地位。 在许多情况下,同一公司内的不同团队将使用竞争产品。 在其他情况下,Jenkins是更大工作流程中的一个小组件,另一个供应商的工具会是CD流水线图中的焦点。

深入挖掘一下,我们发现小公司(两到一百名员工)相对于平均情况使用Jenkins的可能性较小(38%比45%),可能是因为他们以前没有需要部署工作流的大型组织。 这与我们上周写到的一个趋势是一致的,即不同规模的团队倾向于使用不同的CI/CD工具

受访者认为,应用程序的自动化部署是使用成熟的Kubernetes一个关键优势。 许多受访者描述了交付流水线如何与Kubernetes所带来的更大的组织变革相关联。 有人说:“在我们的早期阶段,改进开发人员的工作流程,使开发人员能够思考他们的代码是如何在生产环境中运行的,这是我们想要用Kubernetes实现的最重要的事情。”

我们知道有65%的生产环境用户在Kubernetes上运行应用程序开发工具。 在这方面,Kubernetes对于持续部署和提供“基础设施即代码”至关重要。

原文链接:Jenkins and Kubernetes for Application Deployment(翻译:胡震)

小红书在容器环境的CD实践

李颖杰 发表了文章 • 0 个评论 • 3449 次浏览 • 2017-10-17 16:17 • 来自相关话题

【编者的话】容器推出以来,给软件开发带来了极具传染性的振奋和创新,并获得了来自各个行业、各个领域的巨大的支持——从大企业到初创公司,从研发到各类 IT 人员等等。跨境知名电商小红书随着业务的铺开,线上部署单元的数量急剧增加,以 Jenkins 调用脚本进行文件 ...查看全部
【编者的话】容器推出以来,给软件开发带来了极具传染性的振奋和创新,并获得了来自各个行业、各个领域的巨大的支持——从大企业到初创公司,从研发到各类 IT 人员等等。跨境知名电商小红书随着业务的铺开,线上部署单元的数量急剧增加,以 Jenkins 调用脚本进行文件推送的部署模式已经不能适应需求。本文作者介绍小红书如何以最小的投入,最低的开发量快速的实现容器化镜像部署,以及由此带来的收益。
#小红书
00.jpg

图 1

小红书是一个从社区做起来的跨境电商。用户喜欢在我们的平台上发关于生活、健身、购物体验、旅游等相关帖子。目前我们已经有有 5 千万的用户,1 千万的图文,每日有 1 亿次笔记曝光,涉及彩妆、护肤、健身、旅游等等各种领域。

小红书是国内最早践行社区电商这个商业模式并获得市场认可的一家电商,我们从社区把流量引入电商,现在在电商平台的 SKU 已经上到了十万级。我们从社区里的用户创建的笔记生成相关的标签,关联相关商品,同时在商品页面也展示社区内的和这商品有关的用户笔记。

小红目前还处在创业阶段,我们的技术团队规模还不大,当然运维本身也是一个小团队,团队虽小,运维有关的方面却都得涉及。小公司资源有限,一个是人力资源有限,二是我们很多业务往前赶。在如何做好 CI/CD,怎么务实的落地方面,我们的策略就是开源优先,优先选择开源的产品,在开源的基础上,发现不满足需求的地方再自行开发。
#之前的应用上线流程
01.jpg

图 2

如图 2 是之前的应用上线的过程,开发向运维提需求,需要多少台服务器,运维依据需求去做初始化并交付给开发。我们现在有一个运维平台,所有服务器的部署都是由这个平台来完成的,平台调用腾讯云 API 生成服务器,做环境初始化,配置监控和报警,交付给开发的是一个标准化好的服务器。
02.jpg

图 3

开发者拿到服务器准备线上发布时用 Jenkins 触发脚本的方式:用 Jenkins 的脚本做测试,执行代码推送。当需要新加一台服务器或者下线一台服务器,要去修改这个发布脚本。 发布流程大概是这样的:Jenkins 脚本先往 beta 环境发,开发者在 beta 环境里做自测,自测环境没有问题就全量发。

我们遇到不少的情况都是在开发者自测的时候没有问题,然后在线上发,线上都是全量发,结果就挂了。然后回退的时候,怎么做呢?我们只能整个流程跑一遍,开发者回退老代码,再跑一次 Jenkins 脚本,整个过程最长需要10来分钟,这段过程线上故障一直存在,所以这个效率挺低。

以上的做法其实是大多数公司的现状,但是对于我们已经不太能适应了,目前我们整个技术在做更迭,环境的复杂度越来越高,如果还是维持现有的代码上线模式,显然会有失控的风险,而且基于这样的基础架构要做例如自动容量管理等都是很难做到的。
#问题 & 需求
首先,我们整个技术团队人数在增加,再加上技术栈在变。以前都是纯 Python 的技术环境,现在不同的团队在尝试 Java、Go、Node。还有就是我们在做微服务的改造,以前的单体应用正在加速拆分成各个微服务,所以应用的数量也增加很多。拆分微服务后,团队也变得更细分了;同时我们还在做前后端的拆分,原来很多 APP 的页面是后端渲染的,现在在做前后端的拆分,后端程序是 API,前端是展示页面,各种应用的依赖关系也变得越来越多。再加上电商每年大促销,扩容在现有模式也很耗时耗力。所以现在的模式基本上已经不太可行了,很难持续下去。

我们团队在两三个月以前就思考怎么解决这些问题,怎么把线上环境和代码发布做得更加好一点。基本上我们需要做这几点:

* 重构“从代码到上线”的流程;
* 支持 Canary 发布的策略,实现流量的细颗粒度管理;
* 能快速回退;
* 实践自动化测试,要有一个环境让自动化测试可以跑;
* 要求服务器等资源管理透明化,不要让开发者关心应用跑在哪个服务器上,这对开发者没有意义,他只要关心开发就可以了。
* 要能够方便的扩容、缩容。

#方法
03.jpg

图 4

我们一开始就考虑到容器化,一开始就是用 Kubernetes 的框架做容器化的管理。为什么是容器化?因为容器和微服务是一对“好朋友”,从开发环境到线上环境可以做到基本一致;为什么用 Kubernetes?这和运行环境和部署环境有关系,我们是腾讯云的重度用户,腾讯云有对 Kubernetes提供了非常到位的原生支持,所谓原生支持是指它有几个方面的实现:第一个是网络层面,我们知道 Kubernetes 在裸金属的环境下,要实现 Overlay 网络,或者有 SDN 网络的环境,而在腾讯云的环境里,它本身就是软件定义网络,所以它在网络上的实现可以做到在容器环境里和原生的网络一样的快,没有任何的性能牺牲。第二在腾讯云的环境里,负载均衡器和 Kubernetes 里的 service 可以捆绑,可以通过创建 Kubernetes 的 service 去维护云服务的 L4 负载均衡器。第三就是腾讯云的网盘可以被 Kubernetes 管理,实现 PVC 等,当然 Kubernetes 本身提供的特性是足够满足我们的需求的。

刚刚说了我们作为创业公司都是是以开源为主,在新的环境里应用了这样的一些开源技术(图 4),Jenkins、GitLab、Prometheus 和 Spinnaker。Jenkins 和 GitLab 应该都听说,大家都在用,Prometheus、Docker 也都是很主流的。

重点介绍两个比较新,但是现在相当火的开源技术:第一个叫 Traefik,在我们的环境里用来取代 Nginx 反向代理,Traefik 是用 Go 写的一个反向代理服务软件。第二是 Spinnaker,这是一个我个人认为非常优秀的开源的发布系统,它是由 Netflix 在去年开源的,是基于 Netflix 内部一直在使用的发布系统做的开源,可以说是 Netflix 在 CD 方面的最佳实践,整个社区非常活跃,它对 Kubernetes 的环境支持非常好。接下来我会重点介绍这两块东西。
#Spinnaker

* Netflix 开源项目
* 开放性和集成能力
* 较强的 Pipeline 表达能力
* 强大的表达式
* 界面友好
* 支持多种云平台

刚才介绍了 Spinnaker,它是一个开源项目,是 Netflix 的开源项目。Netflix 的开源项目在社区一直有着不错的口碑。它有开放式的集成能力,它原生就可以支持 Jenkins、GitLab 的整合,它还支持 Webhook,就是说在某一个环境里,如果后面的某个资源的控制组件,本身是个 API,那它就很容易整合到 Spinnaker 里。

再者它有比较强的 Pipeline 的能力,它的 Pipeline 可以复杂非常复杂,Pipeline 之间还可以关联,它还有很强的表达式功能,可以在任何的环节里用表达式来替代静态参数和值,在 Pipeline 开始的时候,生成的过程变量都可以被 Pipeline 每个 stage 调用。比如说这个 Pipeline 是什么时候开始的,触发时的参数是什么,某一个步骤是成功还是失败了,此次要部署的镜像是什么,线上目前是什么版本,这些都可以通过变量访问到。它还有一个比较友好的操作界面,重点的是支持多种云平台。目前支持 Kubernetes、OpenStack、亚马逊的容器云平台。
04.jpg

图 5

图 5 是 Spinnaker 的架构,是一个微服务的架构。这是一个微服务架构,里面包含用户界面 Deck,API 网关 Gate 等,API 网关是可以对外开放的,我们可以利用它和其它工具做一些深度整合,Rosco 是它做镜像构建的组件,我们也可以不用 Rosco 来做镜像构建,Orca 是它的核心,就是流程引擎。Echo 是通知系统,Igor 是用来集成 Jenkins 等 CI 系统的一个组件。Front52 是存储管理,Cloud driver 是它用来适配不同的云平台的,比如 Kubernetes 就有专门的 Cloud driver,也有亚马逊容器云的 Cloud driver。Fiat 是它一个鉴权的组件。
05.jpg

图 6

图 6 是它的界面。界面一眼看上去挺乱,实际上它还是有很好的逻辑性。这里每一个块都有三种颜色来表示 Kubernetes 的环境里的某个实例的当前状态。绿色是代表是活着的,右边是实例的信息。实例的 YML 配置,实例所在的集群,实例的状态和相关 event。
06.jpg

图 7

图 7 是 Pipeline 的界面。首先,我觉得这个界面很好看很清晰。二是 Pipeline 可以做得非常灵活,可以说执行了前几个步骤之后,等所有的步骤执行完了再执行某个步骤。这个步骤是某个用户做某个审批,再分别执行三个步骤其中的一个步骤,然后再执行某个环节。也可以说要发布还是回退,发布是走发布的流程,回退就是回退的流程。总之在这里,你所期待的 Pipeline 的功能都可以提供,如果实在不行,还有 Webhook 的模式让你方便的和外部系统做整合。
07.jpg

图 8

图 8 是 Pipeline 步骤的类型。左上 Check Precondltions 前置条件满足的时候才执行某个步骤。例如当前面的第一次发布里所有的实例都存活的时候,才执行某个步骤。或者当前面的步骤达到了某个状态,再执行下一个步骤。deploy 是在 Kubernetes 环境里生成 Replication Set,可以在 deploy 里更新一个服务器组、禁用一个集群、把集群的容量往下降、往上升等等。也可以跑某一个脚本,这个脚本是在某一个容器里,有时候可能有这样的需求,比如说 Java 来说这个 Java 跑起来之后并不是马上能够接入流量,可能要到 Java 里跑一个 job,从数据库加载数据并做些初始化工作后,才可以开始承接流量。
08.jpg

图 9

Pipeline 表达式很厉害,它的表达式是用 Grovvy 来做,大家知道 Grovvy 是一个动态语言。凡是 Grovvy 能用的语法,在字符串的地方都可以用。所以,这些步骤中,可以说这个步骤参数是来自表达式。也可以说有条件的执行,生成环境的时候才做这样的东西。也可以有前置条件,当满足这个条件的时候,这个流程和 stage 可以继续走下去。
09.jpg

图 10
10.jpg

图 11

如图 10 是各种类型的表达式,从现在看起来,基本上我们各种需求都能满足了。Pipeline 可以自动触发(图 11),可以每天、每周、每月、每年,某一天的时候被自动触发,做一个自动发布等等,也可以在镜像有新 tag 推送到镜像仓库时,Pipeline 去做发布。
#Spinnaker 和 Kubernetes 的关系
11.jpg

图 12

Spinnaker 和 Kubernetes 有什么关系?它有很多概念是一对一的,Spinnaker 有一个叫 Account的,Account 对应到 Kubernetes 是 Kubernetes Cluster,我们的环境里现在有三个 Kubernetes 的 Cluster,分别对应到开发、测试和生产,它也是对应到 Spinnaker 的 三个 Account;Instance 对应到 Kubernetes 里是 Pod,一个 Pod 就是一个运行的单元;还有有 Server Group,这个 Server Group 对应的是 Replica Set 或者是 Deepionment。然后是 Load Balance,在 Spinnaker 里称之为 Load Balance 的东西在 Kubernetes 里就是 Service。
#Traefik
12.jpg

图 13

Traefik 亮点:

* 配置热加载,无需重启

* 自带熔断功能
-traefik.backend.circuitbreaker:NetworkErrorRatio() > 0.5

* 动态权重的轮询策略
-traefik.backend.loadbalancer.method:drr

为什么我们用 Traefik 而不用 Nginx 做反向代理呢?首先 Traefik 是一个配置热加载,用 Nginx 时更新路由规则则是做后端服务器的上线、下线都需要重载,但 Traefik 不需要。Traefik 自带熔断功能,可以定义后端某个实例错误率超过比如 50% 的时候,主动熔断它,请求再也不发给它了。还有动态的负载均衡策略,它会记录 5 秒钟之内所有后端实例对请求的响应时间或连接数,如果某个后端实例响应特别慢,那接下来的 5 秒钟就会将这个后端的权重降低直到它恢复到正常性能,这个过程是在不断的调整中,这是我们需要的功能。因为上了容器之后,我们很难保证一个应用的所有实例都部署在相同处理能力的节点上,云服务商采购服务器也是按批量来的,每一批不可能完全一致,很难去保证所有的节点性能都是一致的。
13.jpg

图 14

图 14 是 Traefik 自带的界面。我们定义的规则,后端实例的情况都可以实时的展现。
#为什么在 Kubernetes 环境里选择了 Traefik?

* Kubernetes 集群中的 Ingress Controller
* 动态加载 Ingress 更新路由规则
* 根据 Service 的定义动态更新后端 Pod
* 根据 Pod 的 Liveness 检查结果动态调整可用 Pod
* 请求直接发送到 Pod

Traefik 和 Kubernetes 有什么关系呢?为什么在 Kubernetes 环境里选择了 Traefik?Traefik 在 Kubernetes 是以 Ingress Controller 存在,大家知道 Kubernetes 到 1.4 之后就引进了 Ingress 的概念。Kubernetes 原来只有一个 Service 来实现服务发现和负载均衡,service 是四层的负载均衡,它做不到基于规则的转发。在 Kubernetes 里 Ingress 是属于七层 HTTP 的实现,当然 Kubernetes 本身不去做七层的负载均衡,它是通过 Ingress Controller 实现的,Traefik 在 Kubernetes 里就是一种 Ingress Controller。它可以动态加载 Kubernetes 里的 Ingress 所定义的路由规则,Ingress 里也定义了一个路由规则所对应的 Service,而 Service 又和具体的 Pod 相关,Traefik 据此可以将请求直接发送给目标 Pod,而无需通过 Service 所维护的 iptables 来做转发。Pod列表是根据 Pod 的 Liveness 和 Readiness 状态做动态的调整。
14.jpg

图 15

图 15 是新发布的一个流程或者是开发的流程。我们有三个环节:一个是开发阶段,一个是集成测试,一个是上线。

开发阶段,开发者在迭代开始时生成一个 Feature 分支,以后的每次更新都将这个 Feature 分支推送到 GitLab 。GitLab 里配置的 Webhook 触发一个 Jenkins job,这个 job 做单元测试和镜像构建,构建成一个 Feature 分支的镜像,给这个镜像一个特定的 tag。生成新的镜像之后,触发 Spinnaker 的部署,这个部署只在开发环境里。

开发者怎么访问刚刚部署的开发环境呢?如果这是个 HTTP 应用,假设应用叫做 APP1,而分支名称叫 A,那开发者就通过 APP1-A.dev.xiaohongshu.com 就可以访问到 Feature A 的代码。在整个周期里可以不断的迭代,最后开发者觉得完成了这个 Feature 了,就可以推送到 release。一旦把代码推往 release 就触发另一个构建,基本上和前面的过程差不多。最后会有一个自动化的测试,基本上是由测试团队提供的自动化测试的工具,用 Spinnaker 调用它,看结果是什么样。

如果今天很有信心了,决定往生产发了,可以在 Git 上生成一个 tag,比如这个 tag 是 0.1.1,今天要发 0.1.1 版了,同样也会触发一个镜像的构建。这三个不同的阶段构建的镜像 tag 不一样,每生成一个新 tag, Spinnaker 会根据 tag 的命名规则触发不同的 Pipeline,做不同环境的部署。
#Canary
最重要的是我们有一个 Canary 的发布过程,我们在 Spinnaker 的基础上,开发了一套 Canary 的机制。Canary 和 Beta 差不多,但 Canary 是真实引入流量,它把线上用户分为两组:一是稳定版的流量用户;二是 Canary 版的用户,他们会率先使用新版本,我们的具体策略是先给公司、先给我们自己办公室的人来用,这个灰度如果没问题了,用户反馈 OK,看看监控数据也觉得没有问题,再按照 1%-10%-20%-50%-100% 的阶段随机挑选线上用户继续灰度,在这整个过程都有监控数据可以看, 任何时候如果有异常都可以通过 Spinnaker 进行回退。
15.jpg

图 16

这个是 Canary 的示意图,线上用户被分成两组,大部分用户访问老版本,特定用户通过负载均衡转发到特定的版本里,后台有监控数据方便去比较两个版本之间的差异。
16.jpg

图 17

这是我们在容器环境里实现的 Canary 的架构(图 17),用户请求从前面进来,首先打到 Traefik,如果没有做 Canary 的过程,Traefik 是直接把请求打到组实例。如果要发布一个新的版本,有一个 HTTP 的 API 控制 project service,决定把什么样的流量可以打到这个里面版本。我们的策略可能是把办公室用户,可以通过 IP 看到 IP,或者把线上的安卓用户,或者线上 1% 的安卓用户打给它,这些都是可以定义的。
17.jpg

图 18

如图 18 所示是线上真实的部署流程。首先是要设置一个 Canary 策略,这个策略可以指定完全随机还是根据用户的特定来源。比如说是一个办公室用户,或者所有上海的用户等等,然后去调整参数,是 1% 的上海用户,还是所有的上海用户。然后开始部署服务。接下来把这个 Canary 实例做扩展,在流量进来之前,实例的容量一定要先准备好。进来之后把流量做重新定向,把流量从原来直接打给后端的 Pod 改成打到 Canary 代理服务上,由 Canary 代理服务根据策略和用户来源做进一步的流量分发。整个过程不断的迭代,有 1% 的线上用户开始慢慢到到 100%。在达到 100% 后,就采用红黑的策略替换掉所有旧版本:先把所有的新版本实例生成出来,等所有的新版本通过健康检查,都在线了,旧的版本再批量下线,这样完成一个灰度。如果中途发现问题不能继续,马上就可以回退,所谓的回退就是把 把流量打回到线上版本去。
18.jpg

图 19

图上(图 19)是我们的 Canary 策略。这是我们自己实现的一套东西。图中的例子是把来自指定网段一半的 iPhone 用户进行 Canary。用户分组的维度还可以有其它规则,现在我们支持的是完全随机/特定 IP/特定设备类型,这些规则可以组合起来。

我们的用户分组是有一致性保证的,一旦为某个用户分组了,那在当前灰度期间,这个用户的分组不会变,否则会影响用户体验。
#下一步打算

* ACA——自动灰度分析
* 自动容量管理

下一步我们打算做两件事情:第一,我们想做自动灰度分析,叫 ACA,现在 AIOps 概念很热门,我个人认为自动灰度分析可以说是一个具体的 AIOps 落地。在灰度的过程中,人肉判断新版本是否正常,其实如果日志采集够完整的话,这个判断可以由机器来做,机器根据所有数据来为新版本做评分,然后发布系统根据评分结果自动继续发布或者终止发布并回退。第二,再往下可以做自动的容量管理,当然是基于 Kubernetes 的基础上,做自动容量管理,以便更好的善用计算资源。

最后总结一下:一个好的 CD 系统应该能够控制发布带来的风险;我们在人力资源有限的情况下倾向于采用开源的方法解决问题,如果开源不满足的话,我们再开发一些适配的功能。

顺便打个招聘小广告:我们正在全面推广容器技术,通过基础架构的演进来提高 DevOps 效率,在这个演进的过程中,希望找到能帮助我们在容器环境下开发或完善 DevOps 工具链的小伙伴,工具链包括但不限于 CI/CD、监控/报警/事件处理,应用 Portal、ChatOps;您如果曾经做过以上相关开发工作但有自己的想法想要做得更好,能搞定前后端开发,无论什么开发语言我们都欢迎。我们也在面临成长的烦恼,线上流量看涨,后端数据库的维护充满了挑战,我们希望能有资深 DBA 的入伙,帮助我们搞定 MySQL、MongoDB、Redis 或其中几种,能够快速响应处理故障,能够保障大促期间数 10 倍于平日的数据库访问。我们同时也在找熟悉互联网应用架构、精通 Nginx、HTTP/TCP 等相关技术和协议的网站稳定性工程师(SRE)同学的加入,你将会帮助我们持续优化关键链路,以最快的速度定位并解决网站故障。欢迎砸简历:gsun1@xiaohongshu.com 或 QQ 联系:11105965。

个人介绍:孙国清,小红书运维团队负责人。浙大计算机系毕业,曾在传统企业 IT 部门工作多年,最近几年开始在互联网行业从事技术及技术管理工作,曾就职于携程基础架构,负责 Linux 系统标准化及分布式存储的研究和落地,目前在小红书带领运维团队,负责业务应用,基础架构以及IT支持。个人接触的技术比较杂,从开发到运维的一些领域都有兴趣,是 Scala 语言的爱好者,曾翻译了”The Neophyte's Guide to Scala”,有上千 Scala 开发者从中受益,到小红书后开始负责系统化落地 DevOps 和提高运维效率。

原文链接:小红书在容器环境的CD实践&version=12020610&nettype=WIFI&fontScale=100&pass_ticket=42VJ03hx3jhEViwJY15doHnH8ScJ7rnOC6ikTvTElk4WmU98f14IlvEt8XQupCFY)

Jenkins和Docker在HULK的落地实践

尼古拉斯 发表了文章 • 4 个评论 • 2767 次浏览 • 2017-08-16 22:59 • 来自相关话题

【编者的话】巧妇难为无米之炊,玩容器,“镜像”就是下锅的米,我们私有云Hulk平台的容器服务,向用户提供UI页面化的一整套的镜像定制、制作、管理、私有镜像仓库的服务,这套服务的背后技术实现,Jenkins算是“引擎”,本文简要介绍这其中的技术方案。 ...查看全部
【编者的话】巧妇难为无米之炊,玩容器,“镜像”就是下锅的米,我们私有云Hulk平台的容器服务,向用户提供UI页面化的一整套的镜像定制、制作、管理、私有镜像仓库的服务,这套服务的背后技术实现,Jenkins算是“引擎”,本文简要介绍这其中的技术方案。

【3 天烧脑式基于Docker的CI/CD实战训练营 | 北京站】本次培训围绕基于Docker的CI/CD实战展开,具体内容包括:持续集成与持续交付(CI/CD)概览;持续集成系统介绍;客户端与服务端的 CI/CD 实践;开发流程中引入 CI、CD;Gitlab 和 CI、CD 工具;Gitlab CI、Drone 的使用以及实践经验分享等。
#纯手工捣鼓Docker镜像
Docker的镜像,已然成为容器镜像的事实标准,我们的容器服务也是基于Docker构建的。

手工制作Docker镜像时,大概这几步:

1、创建制作镜像的工作目录
# mkdir nginx-19-el6# cd nginx-19-el6

2、可以创建一个子目录,存放要添加到镜像中的配置文件,并组织好目录层次,最后用ADD指令统一添加到镜像中
# mkdir rootfs# tree rootfs/rootfs/
└── usr
└── local
└── nginx
└── conf
├── fastcgi.conf
├── include
│ └── xxx.conf
├── mime.types
└── nginx.conf

3、写一个Dockerfile
# cat dockerfileFROM r.your.domain/admin/centos-68:latest
RUN yum -y install nginx-1.9.15-5.el6 && yum clean all
ADD rootfs.tar.gz /
EXPOSE 80
ENTRYPOINT ["/usr/local/nginx/sbin/nginx"]
CMD ["-c", "/usr/local/nginx/conf/nginx.conf", "-g", "daemon off;"]

4、build镜像
# docker build -t r.your.domain/xxx/nginx-19-el6:01

5、push到镜像仓库
# docker push r.your.domain/xxx/nginx-19-el6:01

这种纯手工的方式,很明显,由于自动化程度低,工作量较大,尤其是当镜像种类、版本较多以后,而且对于不了解Docker命令、Dockerfile语法的同学,使用门槛还是比较高哦。
#UI页面化、自动化地生产Docker镜像
针对上面提到的效率、使用门槛的问题,简要介绍下我们的解决方案;

在面向用户的功能方面,要解决好下面几个主要问题:

  1. 镜像内容的管理,主要是一些配置文件,比如上面的rootfs目录
  2. Dockerfile的定制、自动生成,比如定制RUN、EXPOSE、ENTRYPOINT、CMD
  3. 触发build、push,以及镜像仓库的管理

在后端的技术实现方面,我们采用下面的架构:
1.png

##镜像内容管理、Dockerfile定制生成
UI页面上支持用户管理自己的配置文件(rootfs)、运行的命令(RUN)、入口程序、暴露的端口等,比如:
2.png

后台会把这些内容、信息,存储到GitLab;
##Jenkins实现自动化生产线
如果用户触发“制作镜像”,会触发一个Jenkins的job,该job从GitLab拉取后,根据一个Jenkinsfile里定义逻辑“制作镜像”。

Jenkinsfile里充分利用了pipeline的语法,把一系列步骤串起来:前期检查、创建tar文件、生成dockerfile、build、push、清理。

下面是一个示例的Jenkinsfile:
#!groovypipeline {
agent any

environment {
REGISTRY_ACCESS = credentials('xxx')
}

options {
timeout(time: 30, unit: 'MINUTES')
}

// a list of parameters provided when triggering
parameters {
string(name: 'registry', defaultValue: '')
string(name: 'namespace', defaultValue: '')
string(name: 'image_name', defaultValue: '')
string(name: 'image_tag', defaultValue: '')
}

stages {
stage('Verify') {
steps { echo "To check whether related project exists and specified tag is usable..."
sh "xxx xxx xxx"
}
}

stage('Prepare') {
steps { echo "To generate 'Dockerfile' and archive 'rootfs' directory..."
sh "xxx xxx xxx"
sh "xxx xxx xxx"
}
}

stage('Build') {
steps { echo "To build image..."
sh "xxx xxx xxx"
}
}

stage('Push') {
steps { echo 'To push image...'
sh "xxx xxx xxx"
}
}
}

post {
always { echo "Always clean up, no matter whether the building and pushing was failure or success"
sh "xxx xxx xxx"
}
}

##镜像仓库管理
制作好的镜像,存储于私有镜像仓库,用户在页面可以方便的管理,也可以在自己测试环境,docker pull拉取镜像、docker run测试镜像。
3.png

#总结
本文主要给大家介绍了Jenkins&Docker是如何在HULK落地的,其实最初的出发点还是解决当前Docker CLI下上手使用成本高的问题。在经过WEB化和自动化之后,会大大降低业务的接入使用成本,最终助力Docker在业务的快速实践。

本文转载自公众号:HULK一线技术杂谈,ID:hulktalk,作者:ADDOPS团队王浩宇。

Jenkins与Docker的持续集成实践

尼古拉斯 发表了文章 • 2 个评论 • 27855 次浏览 • 2017-08-16 15:34 • 来自相关话题

【编者的话】持续集成(CI/CD)是一种软件开发实践。用于帮助团队成员频繁、快速的集成,测试他们的工作成果,以尽快发现集成错误。 更频繁、更早的集成意味着更早的发现问题。通过持续集成,及时发现和解决代码故障,提高代码质量,减少故障处理成本等等。 ...查看全部
【编者的话】持续集成(CI/CD)是一种软件开发实践。用于帮助团队成员频繁、快速的集成,测试他们的工作成果,以尽快发现集成错误。 更频繁、更早的集成意味着更早的发现问题。通过持续集成,及时发现和解决代码故障,提高代码质量,减少故障处理成本等等。

【3 天烧脑式基于Docker的CI/CD实战训练营 | 北京站】本次培训围绕基于Docker的CI/CD实战展开,具体内容包括:持续集成与持续交付(CI/CD)概览;持续集成系统介绍;客户端与服务端的 CI/CD 实践;开发流程中引入 CI、CD;Gitlab 和 CI、CD 工具;Gitlab CI、Drone 的使用以及实践经验分享等。
#常见持续集成工具
目前持续集成的生态越来越完善,工具也有很多,开源的或商业的。如:

* 最最流行的,也是使用最多的Jenkins
* 有着持续集成DNA的ThoughtWorks GO。理念:"Deployment as pipeline" (华为容器平台应该是基于GO做的二次开发实现)
* Atlassian工具链之一的Bamboo
* 与Gitlab紧密集成的Gitlab CI
* 专为开源打造的Travis CI,与Github紧密集成
* 使用Python语言实现的Buildbot,相信Pythoner看到会喜欢

我们的选型是Jenkins,所以我们来看下Jenkins。
#Jenkins
##Jenkins特点

* Jenkins是开源的应用最广泛的持续集成工具,支持CI, CD;
* Jenkins有很多插件,而且用户也可以自定义插件,可扩展性非常强;
* Jenkins对Docker支持非常好,有一套完善的Docker插件;
* Jenkins 2.0开始支持Pipeline,一个非常强大的插件,使用基于Groovy的DSL,支持CI/CD流水线;
* Jenkins基于Java语言开发;

##Jenkins几个概念

* Master是Jenkins安装和运行的地方,它负责解析job脚本,处理任务,调度计算资源;
* Agent 负责处理从Master分发的任务;
* Executor就是执行任务的计算资源,它可以在Master或者Agent上运行。多个Executor也可以合作执行一些任务;
* job 任务,用来定义具体的构建过程;
* Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy代码能够与Java代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy可以使用其他Java语言编写的库。Jenkins用Groovy作为DSL;
* Pipeline 流水线即代码(Pipeline as Code),通过编码而非配置持续集成/持续交付(CI/CD)运行工具的方式定义部署。流水线使得部署是可重现、可重复的;

流水线包括节点(Node)、阶段(Stage)和步骤(Step)。

流水线执行在节点上。节点是Jenkins安装的一部分。流水线通常包含多个阶段。一个阶段包含多个步骤。流水线上手指南可以查看到更多的内容。

* node在Pipeline中的context中,node是job运行的地方。 node会给job创建一个工作空间。工作空间就是一个文件目录,这是为了避免跟资源相关的处理互相产生影响。工作空间是node创建的,在node里的所有step都执行完毕后会自动删除。

* stage阶段,stage是一个任务执行过程的独立的并且唯一的逻辑块,Pipeline定义在语法上就是由一系列的stage组成的。 每一个stage逻辑都包含一个或多个step。

* step步骤,一个step是整个流程中的一系列事情中的一个独立的任务,step是用来告诉Jenkins如何做。

* Jenkinfile Jenkins支持创建流水线。它使用一种基于Groovy的流水线领域特定语言(Pipeline DSL)的简单脚。而这些脚本,通常名字叫Jenkinsfile。它定义了一些根据指定参数执行简单或复杂的任务的步骤。流水线创建好后,可以用来构建代码,或者编排从代码提交到交付过程中所需的工作。Jenkins中的Jenkinsfile有点类似Docker中的Dockfile的感觉

##Jenkins 部署
Jenkins组件其实非常少,安装部署也非常简单。 Jenkins按角色就两类: master节点和slave节点。master安装完成后,在控制台中添加节点即可。

下面以Dcoker的部署方式为例说一下Jenkins的部署:

Master节点

查看 docker hub 中 jenkins 的 image
[root@k3128v /home/huomingming]# docker search jenkinsNAME                               DESCRIPTION                                     STARS     OFFICIAL   AUTOMATEDjenkins Official Jenkins Docker image                   2600      [OK]       stephenreed/jenkins-java8-maven-git   Automated build that provides a continuous...   53

可以看到第一个是官方提供的,所以我们选择这个即可。

拉取jenkins image
docker pull jenkins

启动Jenkins容器

Jenkins没有数据库,所有数据都是存放在文件中的,首先在本地创建Jenkins数据目录,用于保存Jenkins的数据 这个目录需要定期的备份,用于容灾(当前Jenkins容器所在节点由于不可抗因素无法使用时,可以在新机器上使用备份的数据启动新的jenkins master节点)。
sudo mkdir /var/jenkins
sudo chown 1000:1000 /var/jenkins
sudo docker run -p 8080:8080 -p 50000:50000 -v /var/jenkins:/var/jenkins_home --name my_jenkins -d jenkins

这样Jenkins就成功跑起来了。可以直接通过机器的8080端口访问Jenkins,本地的/var/jenkins就相当于容器里Jenkins用户的用户主目录,所以要保证该目录的权限为uid为j1000的用户目录。

配置Jenkins

启动完 jenkins master 后,在浏览器中数据输入 http://jenkins_master_ip:8080 登录Jenkins控制台进行接下来的安装和配置。 具体图就不贴出来了。

查看Jenkins的版本
java -jar /usr/share/jenkins/jenkins.war --version

slave 节点

安装Java JDK
yun install java-1.8.0-openjdk

创建Jenkins用户
$ useradd -m jenkins -d /home/jenkins$ passwd jenkins

创建工作目录
mkdir /data/jenkinschown jenkins.jenkins /data/jenkins

添加Jenkins用户到Docker用户组
sudo usermod -a -G docker jenkins

配置SSH互信,Master免密码登陆Slave

Master有多种管理Slave的方式,我们选择SSH方式在Master节点中,切换到Jenkins用户ssh-keygen -t rsa创建秘钥对把公钥拷贝到Slave节点
scp ~/.ssh/id_rsa.pub jenkins@slave_ip:~/.ssh/authorized_keys

确保在scp前,slave节点根目录下.ssh目录已存在
chmod 700 authorized_keys

使用Jenkins来构建Docker是需要安装插件的。那我们需要安装哪些插件呢?

Jenkins有哪些Docker的Plugins
01.png

是非常丰富的,但并不是我们都能用的上,所以需要根据你使用的环境和平台来选择适合自己的Plugin安装就可以了。 每个Plugin都需要适配 Jenkins的版本,且每个Plugin也需要依赖一些其它Plugin,上面都已经做了标注,需要配套来用。

这里介绍几个常用的Docker插件:

Docker Commons Plugin

其基本功能:

* API for managing Docker image and container fingerprints
* Credentials and location of Docker Registry
* Credentials and location of Docker Daemon (aka Docker Remote API)
* ToolInstallation for Docker CLI clients
* DockerImageExtractor extension point to get Docker image relations from jobs
* Simple UI referring related image fingerprints in Docker builds

Docker Plugins,该插件是将Docker作为Jenkins的slave来使用,来在Docker容器种完成项目的build,build完成后该容器slave容器就会被销毁。所有的工作都是在slave容器内完成。
docker-build-step

该插件在 build 过程种增加了对 Docker 命令的支持。

Docker Pipeline Plugin,基于Docker Commons Plugin实现的一些上层的基于Docker的Pipeline编排。

Docker Hub Notification Trigger Plugin,该插件提供了当Registry中的image发生变化时触发build新镜像的功能。

CloudBees Docker Build and Publish,该插件提供了通过Dockerfile来构建项目并将生成的镜像上传到镜像仓库的功能。

CloudBees Docker Custom Build Environment,This plugin allows the definition of a build environment for a job using a Docker container。该插件适用于 “自由风格的软件项目”,如图:
02.png


CloudBees Docker Traceability,用于追踪通过Jenkins启停的容器的事件。

Kubernetes, This plugin allows Jenkins agents to be dynamically provisioned on a Kubernetes cluster. The aim of the Kubernetes plugin is to be able to use a Kubernetes cluster to dynamically provision a Jenkins agent (using Kubernetes scheduling mechanisms to optimize the loads), run a single build, then tear-down that slave.

与Kubernetes结合,通过Kubernetes提供Jenkins的 slave 节点。利用Kubernetes的调度功能提供快速的启停 slave 节点执行 build 等任务。目前是0.11版本,稳定性有待验证。
##Jenkins有没有API?
因为,我们不是直接在Jenkins的Dashbord来操作, 而是集成到现有平台,所以我们需要使用它的API。

Jenkins的Remote API以REST的形式进行提供。例如,我们搭建的Jenkins站点为http://myjenkins.com:8080。那么,访问http://myjenkins.com:8080/api 即可查看到该站点所有可用的API。

查询操作
03.png


执行一些动作
04.png

例如,我要创建一个 job,名字为 my_job,my_job的配置文件:



false


true
false
false
false

false



调用API创建 my_job:
curl -X POST http://www.xxx.xxx/jenkins/createItem?name=my_job  --user uname:pass --data-binary "my_job_config.xml" -H "Content-Type: application/xml"

然后,你就可以在Jenkins Dashboard上看到这个job了。它的管理页面为http://myjenkins.com:8080/job/my_job。那么我们访问 /my_job/api/ 即可查看到该job可用的API。

更多的API介绍可以参考Jenkins的官方wiki,这里只是个引子,在此就不过多进行介绍。
#总结
该篇文章主要是介绍了Jenkins和Docker的持续集成的实现部分,后续还有关于Jenkins的压测、性能扩展、高可用等,还需要持续深入的介绍。

本文转载自公众号:HULK一线技术杂谈,ID:hulktalk,作者:ADDOPS团队霍明明。

DockOne微信分享(一一四):Jenkins在Google Cloud的自动化安装

DarkForces. 发表了文章 • 0 个评论 • 3980 次浏览 • 2017-04-16 20:48 • 来自相关话题

【编者的话】本次分享给大家介绍使用Packer一键解决方案安装Jenkins,以及使用一些gcloud API将我们build出来的Jenkins image部署到GCP等, 平台搭建好之后就开心的开发你的产品吧。 【深圳站|3天烧脑 ...查看全部
【编者的话】本次分享给大家介绍使用Packer一键解决方案安装Jenkins,以及使用一些gcloud API将我们build出来的Jenkins image部署到GCP等, 平台搭建好之后就开心的开发你的产品吧。

【深圳站|3天烧脑式Kubernetes训练营】培训内容包括:Kubernetes概述和架构、部署和核心机制分析、进阶篇——Kubernetes调工作原理及源码分析等。

我们一直在为服务提供商研究新的创新的解决方案,使得各个领域都可以使用我们的服务,因此我们需要一个普遍性的Google Cloud云平台解决方案。这个平台可以解决移动应用和桌面应用的部署和build工作,同时简化使用Google Cloud平台的工具。

除了应用程序平台本身,还需要精简和重复的自动化流程和技术部署,以支持每个平台环境的配置,并支持所有标准的SDLC开发任务,以满足行业或组织要求。

面对选择IaaS / PaaS提供商和开发技术的任务时,谷歌云平台和Firebase是明显的选择。
#为什么选择Firebase?

  • Firebase为现代应用程序开发提供了最佳选择,支持Android,iOS和Web
  • Firebase与Google OAuth验证集成out of the box(OOTB)
  • Firebase支持Google,Email / Password,GitHub,Facebook,Twitter的联合身份验证提供商,甚至支持自定义实现
  • Firebase为您的应用程序数据和使用情况提供Analytics
  • Firebase提供应用程序测试自动化和视频播放的执行与Test Lab的Android和iOS应用程序
  • Firebase由Google Cloud Platform基础架构资源和服务提供100%的支持
  • 阅读更多关于Firebase features
#为什么谷歌云平台?
  • GCP provides……
  • GCP支持集成……
  • GCP在Kubernetes的集装箱计算领域处于领先地位,并提供最实用和最易于管理的解决方案
  • GCP是一个真正的No-Ops平台,具有最方便的工具,如他们的Cloud Shell和Stackdriver监控解决方案
#试点平台配置##GCP Cloud Shell说明需要在GCP控制台中从Google Cloud Shell运行以下说明。 这些脚本及其中使用的工具将自动配置为支持FSO平台的CI / CD进程的Jenkins,配置Compute Engine VM映像。需要的工具:1.在您的本地开发环境中安装firebase-tools包:
npm install -g firebase-tools
2.确保您在GCP项目的帐户中拥有管理员/所有者权限。步骤:[list=1]
  • 在us-east1创建新的GCP项目
  • [list=1]
  • 通过Firebase Web控制台将GCP项目导入到Firebase
  • [list=1]
  • 为我们的环境配置获取一个Firebase CI token
  • a. 在jenkins VM中,运行以下命令获取CI token i. firebase login:ci ii. 在管理Jenkins->配置系统 - >更新FIREBASE_TOKEN界面,将token放入您的jenkins-url中 b. 或者,如果您无权访问本地主机,则可以运行以下操作: i. firebase login:ci --no-localhost ii. 从弹出窗口复制文本并粘贴回您的命令提示符 ii. 复制显示的token并将其添加到配置中[list=1]
  • 启用新GCP项目的计费
  • [list=1]
  • 在新的Firebase项目上启用“Blaze”计费包
  • [list=1]
  • 启用/配置以下API
  • a. Cloud Resource Manager b. Compute Engine APIs c. Firebase Rules APIs (automatically enabled if GCP project is imported into Firebase) d. Google Cloud Messaging API (for the firebase functions feature) e. Google Identity and Access Management API (for creating svc-acct keys/modifying IAM policies etc) f. Google Cloud Testing API (for integration with FB Test Lab for android) g. Google Cloud Tool Results API (for integration with FB Test Lab for android) h. Google Cloud DNS (for creating DNS entries for the new jenkins vm) i. Google Cloud Container Builder API (for app engine deployments) j. Google Cloud Functions API (Private API - may require admin to enable) k. Identity Toolkit API (For Google OAuth) l. Google Maps API For Javascript m. Google Maps Android API n. Google Maps Geocoding API i. 注意:启用并检查提供程序列表中的“Google”框后,您必须导航到“设置”选项卡 [list=1]
  • 在此沙箱项目的Firebase项目中启用密码验证提供程序(以支持Android测试)
  • a. 导航到与此GCP项目关联的Firebase项目,然后从左侧面板菜单中单击Auth b. 在“验证”面板中,单击“登录方法” c. 点击电子邮件/密码,然后选择“启用” d. 点击Google,然后选择“启用”[list=1]
  • 在GCP项目中创建OAuth ID
  • a. Go to API Manager > Credentials b. Create new OAuth ID c. Select Web Application as the type and using 'Jenkins CI' as the name d.对于授权重定向字段,请根据以下模式指定此新Jenkins环境的最终URL i.http://$JENKINS_URL-ci.$GCDNS_ZONE.$OAUTH_DOMAIN/securityRealm/finishLogin e.保存我们的配置文件的OAuth Client ID和Client Secret[list=1]
  • 获取我们的环境配置文件的Firebase数据库密码
  • a. Navigate to Firebase Console > Project Settings > Database > Show Secret copy the value into our config file. [list=1]
  • Clone your devops project
  • [list=1]
  • 通过替换您的环境值创建您的GCP环境配置
  • export GCP_PROJECT_ID="cicd-test"export AUTOMATION_BUCKET="cicd-test-automation"export OAUTH_CLIENT_ID="oauth-client-id-here"export OAUTH_CLIENT_SECRET="somerandomstringLfQOG0kpFuaveQOjiIAS"export OAUTH_DOMAIN="example.net"export SVC_ACCT_NAME="cicd-test"export GCE_AZ="us-east1-b"export GCDNZ_ZONE="project-name"export JENKINS_URL="cicd-test"export GIT_REPO="my-repo"export GIT_ORG="my-github-org"export GIT_USER="jenkins-ci-user:my-secret"export FIREBASE_TOKEN="firebase-token"export FIREBASE_PROJECT="cicd-test"export FIREBASE_DB_SECRET="firebase-db-secret"export SLACK_CHANNEL="project-slack-channel"export SLACK_TOKEN="slack-token"export SLACK_DOMAIN="project-slack-domain"export SLACK_CI_SERVER="http://$JENKINS_URL.$GCDNZ_ZONE.$OAUTH_DOMAIN"export CREATE_DNS_ENTRIES=falseexport CREATE_FIREWALL_RULES="true"
    注意:不要修改以下变量:JENKINS_URL,SLACK_CI_SERVER
    ./create-jenkins-instance.sh -f ./config.env
    运行后脚本手动任务:[list=1]
  • 复制新推出的Jenkins Compute Engine VM的外部IP地址
  • In the google project, navigate to Networking > Cloud DNS:
  • 使用以下模式创建2个新的DNS记录:

    QQ图片20170416215132.png

    ##脚本是如何工作的读取config
    # set explicitly through the packerInstall functionPACKER_HOME=""PACKER_URL="https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip"GCP_PROJECT_ID=""SVC_ACCT_NAME=""AUTOMATION_BUCKET=""OAUTH_CLIENT_ID=""OAUTH_CLIENT_SECRET=""OAUTH_DOMAIN=""GCE_AZ=""GCDNS_ZONE=""JENKINS_URL=""GIT_REPO=""GIT_ORG=""GIT_USER=""FIREBASE_TOKEN=""FIREBASE_PROJECT=""FIREBASE_DB_SECRET=""SLACK_CHANNEL=""SLACK_TOKEN=""SLACK_DOMAIN=""SLACK_CI_SERVER=""CREATE_FIREWALL_RULES=""CREATE_DNS_ENTRIES="" # change this value if jenkins is installed elsewhere on the target machineJENKINS_HOME="/var/lib/jenkins"while getopts ":f:p:n:b:i:s:d:a:z:j:o:r:u:t:P:" opt; do  case $opt in    f)        processPropertyFile $OPTARG        break        ;;    p)        GCP_PROJECT_ID=$OPTARG        ;;    n)        SVC_ACCT_NAME=$OPTARG        ;;    b)        AUTOMATION_BUCKET=$OPTARG        ;;    i)        OAUTH_CLIENT_ID=$OPTARG        ;;    s)        OAUTH_CLIENT_SECRET=$OPTARG        ;;    d)        OAUTH_DOMAIN=$OPTARG        ;;    a)        GCE_AZ=$OPTARG        ;;    z)        GCDNS_ZONE=$OPTARG        ;;    j)        JENKINS_URL=$OPTARG        ;;    o)        GIT_ORG=$OPTARG        ;;    u)        GIT_USER=$OPTARG        ;;    r)        GIT_REPO=$OPTARG        ;;    t)	FIREBASE_TOKEN=$OPTARG	;;    P)	FIREBASE_PROJECT=$OPTARG	;;    \?)        echo -e "\nUnknown option: $OPTARG"        printUsage        exit 1  esacdone
    1.安装packer
    verifyToolExists(){if [ $# -eq 1 ]; then  command -v $1 >/dev/null 2>&1 || { echo "Required tool '$1' is not installed on PATH.  Aborting." >&2; exit 1; }else  echo -e "Incorrect number of arguments: $#\nUsage: verifyToolExists \n"fi}packerInstall(){local TIMESTAMP=`date +%s`local PACKER_DIR="/tmp/packer-$TIMESTAMP"if [ -d $PACKER_DIR ]; then  echo "Director already exists."else  echo "Creating directory $PACKER_DIR"  mkdir -p $PACKER_DIR  echo "Performing pre-req install tasks..."  verifyToolExists unzip  verifyToolExists curl  curl -o $PACKER_DIR/packer.zip $PACKER_URL  unzip $PACKER_DIR/packer.zip -d $PACKER_DIR # && unzip PACKER_DIR/packer.zip  #change the name of the binary so it doesn't collide with native packer binary  export PATH=$PATH:$PACKER_DIR   echo "Verifying successful installation..."  verifyToolExists packer  echo "Successfully installed packer locally."     # set global variable for other shell processes to detect  PACKER_HOME=$PACKER_DIRfi} 
    2.创建firewall规则
    gcloud compute firewall-rules create $rule_name --allow $rule_port --target-tags $rule_tags
    3.创建存储配置文件的bucket4.创建ssh key
    TIMESTAMP=`date +%s`SSH_PUBKEY="" # create a github deployment ssh-key and save to GCS bucket. # User will need to manually add this key to repo settings later.ssh-keygen -f /tmp/$SVC_ACCT_NAME -t rsa -C "$SVC_ACCT_NAME" -N ''SSH_PUBKEY=`cat /tmp/$SVC_ACCT_NAME.pub` 
    5.创建 deploy key and webhook6.使用 gsutil 复制 config文件到bucket7.创建 service account gcloud iam service-accounts create8.更新 service account role
    # create a service account for performing deployments etcgcloud iam service-accounts create $SVC_ACCT_NAME --display-name "$SVC_ACCT_NAME-jenkins" > /tmp/service-account.info # store the name of the service-account so that we can perform actions on it #SVC_ACCT_EMAIL=`cat /tmp/service-account.info | grep 'email' | awk '{print $2}'` # the output from the create command for service accounts no longer provides the email, so we must build it ourselves for now...SVC_ACCT_EMAIL="$SVC_ACCT_NAME@$GCP_PROJECT_ID.iam.gserviceaccount.com" # grant the required roles for the jenkins service-account to interact with the required servicesupdateProjectIamRole $GCP_PROJECT_ID $SVC_ACCT_EMAIL "roles/appengine.deployer"updateProjectIamRole $GCP_PROJECT_ID $SVC_ACCT_EMAIL "roles/logging.logWriter"updateProjectIamRole $GCP_PROJECT_ID $SVC_ACCT_EMAIL "roles/storage.admin"updateProjectIamRole $GCP_PROJECT_ID $SVC_ACCT_EMAIL "roles/editor" 
    9.Packer 去build image文件Jenkins-master.vars
     { "gcp_project_id": "VAR_GCP_PROJECT_ID", "gce_zone_id" : "VAR_GCE_ZONE_ID", "gce_image_id" : "centos-7-v20160803", "gce_svc_acct_name" : "VAR_GCE_SVC_ACCT_NAME", "gce_svc_acct_id" : "VAR_GCE_SVC_ACCT_ID", "gcs_bucket_name" : "VAR_GCS_BUCKET_NAME", "oauth_client_id" : "VAR_GCP_OAUTH_CLIENT_ID", "oauth_client_secret" : "VAR_GCP_OAUTH_CLIENT_SECRET", "oauth_domain" : "VAR_GCP_OAUTH_DOMAIN", "git_repo" : "VAR_GIT_REPO", "firebase_token" : "VAR_FIREBASE_TOKEN", "firebase_project" : "VAR_FIREBASE_PROJECT", "firebase_db_secret" : "VAR_FIREBASE_DB_SECRET", "slack_domain" : "VAR_SLACK_DOMAIN", "slack_token" : "VAR_SLACK_TOKEN", "slack_channel" : "VAR_SLACK_CHANNEL", "slack_ci_server" : "VAR_SLACK_CI_SERVER"} 
    Jenkins-master.json
     { "variables" : {   "gce_image_id": "",   "gcp_project_id" : "",   "gce_zone_id" : "",   "gce_svc_acct_name" : "",   "gce_svc_acct_id" : "",   "gcs_bucket_name" : "",   "oauth_client_id" : "",   "oauth_client_secret" : "",   "oauth_domain" : "",   "image_name" : "",   "slack_token" : "",   "firebase_token" : "",   "firebase_project" : "",   "firebase_db_secret" : "",   "slack_domain" : "",   "slack_channel" : "",   "slack_ci_server" : "" }, "builders" : [{   "type": "googlecompute",   "communicator": "ssh",   "ssh_pty" : "true",   "project_id": "{{user `gcp_project_id`}}",   "source_image": "{{user `gce_image_id`}}",   "zone": "{{user `gce_zone_id`}}",   "ssh_username": "packer",   "image_name": "{{user `image_name`}}",   "metadata" : {     "service-account-name" : "{{user `gce_svc_acct_name`}}",     "service-account-id" : "{{user `gce_svc_acct_id`}}",     "automation-bucket" : "{{user `gcs_bucket_name`}}",     "oauth-client-id" : "{{user `oauth_client_id`}}",     "oauth-client-secret" : "{{user `oauth_client_secret`}}",     "oauth-domain" : "{{user `oauth_domain`}}",     "git-repo" : "{{user `git_repo`}}",     "firebase-token" : "{{user `firebase_token`}}",     "firebase-project" : "{{user `firebase_project`}}",     "firebase-db-secret" : "{{user `firebase_db_secret`}}",     "slack-channel" : "{{user `slack_channel`}}",     "slack-domain" : "{{user `slack_domain`}}",     "slack-token" : "{{user `slack_token`}}",     "slack-ci-server" : "{{user `slack_ci_server`}}"   } }], "provisioners" : [{   "type" : "shell",   "script" : "packer/jenkins/scripts/jenkins-install-script.sh",   "execute_command": "echo 'packer' | sudo -S sh '{{.Path}}'" }]}packer build -var-file=/tmp/packer.vars -var image_name="$PACKER_IMAGE_NAME" packer/jenkins/templates/jenkins-master.jsonJenkins-install-script.shyum install -y epel-releaseyum install -y bzip2 # install jq for processing json filesyum install -y jq.x86_64 # install nodejs 6.x and npm 3.x for RHEL for compatability with newer node librariescurl --silent --location https://rpm.nodesource.com/setup_6.x | bash -yum install -y nodejs # configure this jenkins instance for being able to publish libraries to npmjs.org (TODO: Needs password automated to work) #echo "pwc-business-os-jenkins" | npm add-user --scope=@pwc-business-os #npm config set scope @pwc-business-os # install firebase sdk for build/test process and enable functions deploymentsnpm install -g firebase-toolsnpm install -g gulpnpm install -g firebase-importfirebase --open-sesame functions # install firebase-bolt for testingnpm install --global firebase-bolt # install google-oauth-loginjava -jar jenkins-cli.jar -s http://localhost:8080 -i /var/lib/jenkins/.ssh/id_rsa install-plugin google-oauth-plugin # install google-loginjava -jar jenkins-cli.jar -s http://localhost:8080 -i /var/lib/jenkins/.ssh/id_rsa install-plugin google-login # install job-dsljava -jar jenkins-cli.jar -s http://localhost:8080 -i /var/lib/jenkins/.ssh/id_rsa install-plugin job-dsl # install workflow-aggregator (jenkins pipeline plugin)java -jar jenkins-cli.jar -s http://localhost:8080 -i /var/lib/jenkins/.ssh/id_rsa install-plugin workflow-aggregator # install build-timeoutjava -jar jenkins-cli.jar -s http://localhost:8080 -i /var/lib/jenkins/.ssh/id_rsa install-plugin build-timeout # install email-extjava -jar jenkins-cli.jar -s http://localhost:8080 -i /var/lib/jenkins/.ssh/id_rsa install-plugin email-ext # restart jenkins to apply the updated security login credentialsservice jenkins stop && sleep 10service jenkins startecho "y" | gcloud components install beta 
    10.从packer build出来的image, 用gcloud 创建 image
    gcloud compute instances create $SVC_ACCT_NAME-jenkins --image $PACKER_IMAGE_NAME --custom-cpu 4 --custom-memory 8GiB --zone $GCE_AZ --boot-disk-type pd-standard --boot-disk-size 250GB --tags http-server,https-server,http-appserver --maintenance-policy MIGRATE 
    11.部署一个sample to unlock default app engine
    # clone the google repo and deploy a dummy app-engine app to unlock the 'default' module so that we can begin doing real deploymentsAPP_ENGINE_DIR="/tmp/app-engine-setup-$TIMESTAMP"mkdir -p $APP_ENGINE_DIR && cd $APP_ENGINE_DIRgit clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.gitcd nodejs-docs-samples/appengine/hello-worldnpm set progress=false && npm installgcloud app deploy app.yaml # clean-up the app-engine work dir etccd && rm -rf $APP_ENGINE_DIR 
    12.创建default container engine cluster(kubernets cluster)
    gcloud container clusters create default --num-nodes 5 
    13.安装kubectl in jenkins vm
    gcloud components install kubectlgcloud auth application-default logingcloud container clusters get-credentials defaultgcloud config set compute/zone us-central1-bkubectl cluster-infosudo ln -s /usr/local/share/google/google-cloud-sdk/bin/kubectl /bin/kubectl 
    #我们如何部署产品每个产品的repo Jenkinsfile[list=1]
  • 部署之前,到Jenkins VM里面创建ssh key给你想要部署的程序使用
  • 创建Webhook
  • A sample Jenkinsfile is like,假定你已经有configMap.yaml,deployment.yaml 和service.yaml

  • configMap.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: your-product-config
    data:
    SERVER_API_KEY: your-api-key
    PROJECT_ID: your-google-project-id
    TEMPLATE_ID_WO_COD: some-config-string-here
    TEMPLATE_ID_WO_NONCOD: some-config-string-here
    TEMPLATE_ID_TS: some-config-string-here
    REST_API_ENDPOINTS: some-config-string-here

    Deployment.yaml
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
    name: your-product
    spec:
    replicas: 2
    revisionHistoryLimit: 25
    template:
    metadata:
    labels:
    app: your-product
    spec:
    containers:
    - image: gcr.io/PROJECT_ID/your-product
    name: your-product
    volumeMounts:
    - name: config-volume
    mountPath: /etc/config
    env:
    - name: SERVER_API_KEY
    valueFrom:
    configMapKeyRef:
    name: your-product-config
    key: SERVER_API_KEY
    - name: PROJECT_ID
    valueFrom:
    configMapKeyRef:
    name: your-product-config
    key: PROJECT_ID
    - name: TEMPLATE_ID_WO_COD
    valueFrom:
    configMapKeyRef:
    name: your-product-config
    key: TEMPLATE_ID_WO_COD
    - name: TEMPLATE_ID_WO_NONCOD
    valueFrom:
    configMapKeyRef:
    name: your-product-config
    key: TEMPLATE_ID_WO_NONCOD
    - name: TEMPLATE_ID_TS
    valueFrom:
    configMapKeyRef:
    name: your-product-config
    key: TEMPLATE_ID_TS
    - name: REST_API_ENDPOINTS
    valueFrom:
    configMapKeyRef:
    name: your-product-config
    key: REST_API_ENDPOINTS
    ports:
    - containerPort: 8080
    volumes:
    - name: config-volume
    configMap:
    name: your-product-service-acct
    items:
    - key: service-acct
    path: svc-acct.json
    - key: pubsub
    path: pubsub.json

    service.yaml
    apiVersion: v1
    kind: Service
    metadata:
    name: your-product-service
    labels:
    app: your-product
    spec:
    ports:
    - protocol: TCP
    port: 80
    targetPort: 8080
    name: http
    selector:
    app: your-product
    type: LoadBalancer

    A good dockerfile for node.js

    Dockerfile
    FROM mhart/alpine-node:6
    # we need grpc in node, thats why we add libc6-compat
    RUN \
    apk upgrade && \
    apk add --update libc6-compat

    # Provides cached layer for node_modules
    ADD package.json /tmp/package.json
    RUN cd /tmp && npm install
    RUN mkdir -p /src && cp -a /tmp/node_modules /src/

    # From here we load our application's code in, therefore the previous docker
    # "layer" thats been cached will be used if possible
    WORKDIR /src
    ADD . /src

    ENV NODE_ENV=production
    EXPOSE 8080

    CMD npm start

    Jenkinsfile
    def config
    fileLoader.withGit('git@github.com:my-ord/my-jenkinsci-lib.git', 'master', null, '') {
    config = fileLoader.load('config/cicd-config');
    }

    // Parse out the scm details and store in config object so we can reference later
    config.parseGitConfig(env.JOB_NAME)

    stage 'Pull CICD Config'
    node {
    deleteDir()
    config.fetchEnvConfig(env.CONFIG_BUCKET)
    sh "cat config.env"
    sh "cat svc-acct.json"
    stash includes: '**', name: 'build-config'
    }

    stage 'Install Nodejs sources'
    node {
    try {
    if(config.isPR())
    checkout scm
    else
    git url: "git@${repo}:${org}/${repo}", branch: "${branch}"
    } catch (e) {
    // If there was an exception thrown, the build failed
    currentBuild.result = "FAILED"
    throw e
    }
    }

    stage 'Build image, push to GCR'
    node {
    project = "${env.FIREBASE_PROJECT}"
    appName = 'my-app-name'
    imageTag = "gcr.io/${project}/${appName}:${env.BRANCH_NAME}.${env.BUILD_NUMBER}"
    sh("docker build -t ${imageTag} .")
    sh("gcloud docker -- push ${imageTag}")
    }

    if(config.isPR() == false) {
    stage 'Deploy Application'
    node {
    project = "${env.FIREBASE_PROJECT}"
    appName = 'my-app-name'
    svcName = "${appName}-service"
    imageTag = "gcr.io/${project}/${appName}:${env.BRANCH_NAME}.${env.BUILD_NUMBER}"
    try {
    unstash 'build-config'
    // create configMap
    sh("kubectl create configmap my-app-name-service-acct --from-file=service-acct=./svc-acct.json --dry-run -o yaml | kubectl replace configmap my-app-name-service-acct -f -")
    sh("kubectl apply -f k8s/configMap.yaml")

    // Change image to the one we just built
    sh("sed -i.bak 's#gcr.io/PROJECT_ID/my-app-name#${imageTag}#' ./k8s/deployment.yaml")
    sh("kubectl apply -f k8s/deployment.yaml")
    sh("kubectl apply -f k8s/service.yaml")
    } catch (e) {
    // If there was an exception thrown, the build failed
    currentBuild.result = "FAILED"
    throw e
    }
    }
    }
    echo "Successfully completed Pipeline."

    现在你可以到kubernetes cluster里面查看你的应用程序
    Note, we can also integrate with container build trigger to build images for us.
    https://cloud.google.com/container-builder/docs/creating-build-triggers

    以上内容根据2017年04月11日晚微信群分享内容整理。分享人李修玉,普华用到技术专家,负责FSO的架构和DevOps,对Node.js、Golang、GCP和kubernetes感兴趣。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesz,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(一一三):从一个实际案例来谈容器落地的问题

    Dataman数人科技 发表了文章 • 0 个评论 • 3792 次浏览 • 2017-04-06 21:35 • 来自相关话题

    【编者的话】容器是这两年最热的一个话题,去年大家都在谈Mesos、Kubernetes、Swarm,究竟哪家的挖掘技术强,今年容器技术的进一步普及,更多的人更关心容器技术如何落地,下面我们就基于一个实际的案例来聊一下容器落地遇到的问题。 ...查看全部
    【编者的话】容器是这两年最热的一个话题,去年大家都在谈Mesos、Kubernetes、Swarm,究竟哪家的挖掘技术强,今年容器技术的进一步普及,更多的人更关心容器技术如何落地,下面我们就基于一个实际的案例来聊一下容器落地遇到的问题。

    【深圳站|3天烧脑式Kubernetes训练营】培训内容包括:Kubernetes概述和架构、部署和核心机制分析、进阶篇——Kubernetes调工作原理及源码分析等。

    背景:某银行数据中心计划搭建一个基于容器的PaaS平台
    #持续集成
    持续集成是容器一个绕不过去的话题,无论哪家容器厂商都一定会谈到,数人云关于持续集成,最开始用的是drone,一个小众的持续集成工具,将drone内置在平台上,通过平台的持续集成功能可以方便的实现持续集成的配置和管理。
    ##drone 的坑
    一开始我们觉得这是一个很好的工具,但是后来发现其实没有想象中的那么美好,它的问题:

    • 对SVN的支持不好
    • 容易出问题,因为数人云平台所有的组件都是容器化的,所以若使用drone,则需要使用docker-in-docker技术,但是该技术已经是一个不再被维护的技术了,所以继续使用的风险很大。
    ##Jenkins是个好工具Jenkins是一个好工具,功能强大且稳定,基于Jenkins实现的持续集成基本没有花费什么开发的时间,通过脚本将代码构建和平台连接在一起即可轻松实现CI/CD。##总结往往用户需要的并不是那些看起来很酷的功能,真正需要的是能够实际解决问题的方案,即使这个方案很low。#配置管理在我看来,容器是一个革命性的产物,改变了软件交付的方式,它开箱即用的特性消灭了程序员常说的一句话 “在我这里运行时正常的啊!”; 它快速部署,环境无关的特性帮助运维人员提高了工作效率,但是任何事情都有其两面性,它的开箱即用,环境无关带来好处的同时,也带来了问题——配置文件。##传统应用在容器时代面临的第一个问题一般而言,每个程序都会有一个或多个配置文件,里边记录着DB地址、账号、密码、缓存地址等信息,在容器时代之前,应用程序一般的流转方式是“从开发->测试->生产”:
    • 开发同学交付一个编译之后的二进制文件,源文件(解释执行)或者代码仓库中某一个tag,同时附带一个release notes;
    • 测试同学拿到开发同学交付的内容后,就将其部署在自己的测试环境中,如果release notes中说明了有配置信息需要更新或依赖文件版本需要升级,会依照文件进行调整;
    • 如果测试通过,确定可以投产,那么就将其交付给运维同学进行生产部署。
    此时有一个问题,开发、测试、运维每个环节都会自己维护配置文件,如果使用了容器,那么配置文件管理就是很麻烦的问题了,因为配置文件被放到了容器里,若想修改配置文件就不是那么简单的事情了,所以这就是传统应用在容器化时面临的第一个问题,当然这个问题也不是不能解决,一般而言,有以下几种解决方案:
    • 挂盘,将配置文件放到外部存储中,然后将该目录挂到容器中;
    • 生成新的镜像,基于Docker文件系统的特性,使用测试环境的配置文件覆盖开发环境的镜像,从而得到测试环境的镜像,同理,使用生产环境的配置文件覆盖开发环境的配置文件得到生产环境的镜像,使用该方案会导致每个环境都有一个镜像。
    ##容器时代配置管理的正确打开方式以上分析了传统应用容器化基本都会遇到的一个问题,而且也没有给出很好的解决方案,下面我们来谈下容器化时代配置管理的正确打开方式。其实通过上面问题的描述,我们可以很容易的找到问题的根本原因:配置文件本身是一个本地存储状态,要对其做无状态改造,对于配置管理的无状态改造一般是通过配置中心来完成的,即应用通过配置中心获取配置信息,无需读取本地配置文件,但是这个问题更麻烦,要解决这个问题需要首先解决两个问题:
    • 要先有个配置中心
    • 要改代码,使其可以适配配置中心
    随着容器的普及,未来配置中心肯定会逐渐成为程序的标配。##最终选择的解决方案关于容器时代配置文件的问题,上边大概提到了3种方案,在最终落地的时候选择的是哪一种呢?答案是第二种——生成新的镜像。这是一个很low的方案,为什么没有选择另外两种呢? 我们来解释下原因:
    • 方案一[挂盘], 这个方案会给容器产生另外一个状态,外部文件,不符合cloud 的思想,pass;
    • 方案三[配置中心],成本太高,周期太长,而且需要改代码,往往之前的应用已经被维护了很多年,修改其配置接口,风险太大。
    ##总结虽然这个选择从技术上来看很low,但是个人认为,使用一个不太优雅的方案帮助一个新技术快速落地,然后推动其快速前进,比一直纠结于方案是否高大上,是否优雅等,而导致新的技术一直被悬在空中更好,就像大家一直在争论Mesos、Kubernetes、Swarm究竟哪个更好,现在也没有一个结论,与其争论这么多虚的,不如仔细想一下自己的问题是什么,究竟哪个技术更适合自己。#日志目前使用ELK作为日志方案。##传统应用的坑一般而言,传统的应用都是把日志写到一个指定的路径下,然后通过Logstash采集日志并送入Elasticsearch进行存储,但是这种应用如果直接容器化之后就会带来一个问题——应用的日志文件应该如何存储。
    • 方案一:放到容器里
    • 方案二:挂盘,写到外部存储上
    两种方案都有一些问题:
    • 放到容器里,逻辑上最简单,不需要做任何改动,但是它的问题是,怎么从容器中把日志取出来。
    • 通过挂盘,把容器日志写到外部存储,然后沿用传统的Logstash + ES 的方式处理日志,听起来很美好,但是如何建立容器和日志的对应关系?
    ##容器时代日志的正确打开方式按照之前的惯例,我们先提出在容器时代,日志的正确处理方式,如果应用使用Docker进行交付,不建议写日志文件,直接将日志写入标准输出即可,然后Logstash通过Docker的log-driver捕获日志,这样日志文件既不需要落盘,也使日志文件摆脱了状态,可以更容易的横向扩缩。##最终选择的解决方案最终我们实现的是方案三,因为用户在我们的建议下,选择了将日志输出到标准输出。##新的问题写日志的目的是为了看的,这是一个无比正确的废话,但是如何实时的看到需要的日志却成了我们面临的一个新问题。在容器时代之前,我们一般是通过tail来实时的看日志或者通过Kibana来分析日志。在容器的时代,通过Kibana看日志的方式没什么变化,但是看实时日志就有了一些问题,在用户采取了我们的建议将日志写入标准输出后可以比较优雅的处理日志了,但是另外一个问题出现了,实时日志没有了,因为日志已经被log-driver收走了,怎么办?虽然依然可以从ES中找到日志,但是由于ELK本身的机制,不能通过ELK看到实时日志。##新的解决办法新的问题,需要新的办法来解决,如何解决,其实说穿了也简单,实现了一个log-agent的功能,可以将日志送到两个地方,Logstash和实时日志。##总结事情就是这样,我们以为解决了一个问题的时候,往往一个新的问题又摆在前面,像容器落地这种事情,对传统应用的整体工作方式和流程都有很大的冲击,所以一定也会遇到同等规模的问题需要解决。#监控银行对监控是非常重视的,尤其是运维部门,所以为了满足客户的需求,我们实现了:
    • 平台自身监控
    • 宿主机监控
    • 中间件监控
    监控本身是我们平台很重要的一个部分,但是在实际实施过程中发现,客户其实不是很在意做的监控页面,仪表盘等监控数据,他们自身有健全的监控平台,其实我们需要做的事情就是将我们的数据吐到他的平台上即可。## 总结世事难料,你永远不知道下一块巧克力什么味道,这个我们自身投入了很大精力和时间来实现的功能在用户那里就变成了一个对接的任务。#弹性扩缩弹性扩缩一直是容器厂家喜欢谈的一个口号,曾经有一度我们认为基于Docker的特性来实行弹性扩缩是一件很容易的事情,但是后来发现,这里其实有一个大坑。##扩很容易,缩很难弹性扩容是一件比较容易的事情,我们只要对接监控数据,然后配置一些规则,即可触发应用容器个数增加,实现扩容,但是缩容就会面临几个问题:
    • 什么时候缩容?
    • 如何安全的缩容?
    什么时候缩容,这个问题相对来说还不是特别麻烦,可以设定一个指标,比如CPU,内存,IO等指标来触发缩容,这里只要做一些带有缓冲的规则,避免由于规则简单而导致的扩缩抖动即可。如何安全的缩容相当麻烦,缩容在本质上是要停掉一些容器的,如何判断这个容器是可以停止的,如果一个容器没有流量了,那么应该是可以被停止的,如何让一个容器的流量停止?可以通过前端负载进行控制,不往这个容器分发流量,那么前端的负载是如何判断应该如何往后端分发流量呢?这个有多重因素:
    • 自身算法
    • 应用程序本身是有状态的,需要保持会话

    从以上简单的分析中我们可以发现,缩容不是简单的条件符合了就可以做的事情,要想在不影响业务的情况下实现缩容,是一件非常麻烦的事情,它与平台架构,运行的程序的业务逻辑有很深的耦合。
    ## 总结
    要实现自动的扩缩容,不是一件简单的事情,而是一个系统的工程。

    以上内容是基于数人云在某银行实施过程中总结出来的一些感悟,如果能给大家一些帮助,我们非常高兴,如果有问题,请指出来,我们共同提高,Docker到现在大概有3年多的历史,个人看来它距离真正落地还有很大一段路程要走,我们必须不停的摸索。
    #Q&A
    Q:我想问一下,日志打两份的话具体是怎么实现的呢,用到了哪些技术或现有的工具呢?

    A:我们自己实现了一个log-agent, 然后log-agent 可以实现这个功能。



    Q:如果应用有自己的写的日志,如log4j的,输出不到标准输出,还怎么处理?

    A:log4j貌似是可通过配置输出到标准输出的,另外如果有些应用不能输出到标准输出的,可以配置日志文件路径,我们会去读文件。



    Q:缩容的产生条件是否有比较好的解决方案,比如根据CPU、内存甚至业务规则多维度的进行考察?

    A:缩容很容易,但是麻烦的是如何安全的缩容,我理解这个环节其实是跟应用的逻辑有直接关系的,如果应用是一个无状态的应用,那么缩容非常简单,只需要在前端控制流量,然后停止容器即可,但是如果是有状态的应用,那么就有可能对用户造成影响。



    Q:配置管理这块,不断的覆盖会增加镜像体积,如何最大化减少镜像大小呢?

    A:首先,一个镜像最多被覆盖2,3次,测试镜像一次,生产镜像一次,而且配置文件一般是很小的,几乎对镜像大小没有影响。



    Q:测试环境配置文件覆盖开发环境镜像,是只用测试环境的docket file 吗? 如果每天打版,会很麻烦吗?

    A:通过覆盖测试文件来解决环境问题,只是一个思路,不一定非要使用开发测试环境的信息,这个可以具体情况具体分析。



    Q:log-agent具体实现呢,日志直接打给log-agent还是log-agent读取本地日志文件呢?或者说log-agent读取标准输出的内容呢?

    A:log-agent可以通过Docker的log-driver获取标准输出的日志,同时也可以直接读取日志文件的日志。



    Q:配置ENV化完全可以由运维来实现,容器的启动交由脚本来执行,然后在脚本中来读取所有的ENV并修改应用,完成后再启动应用,这样就只需要来维护脚本了。

    A:是个好主意,但是我们当时考虑配置文件覆盖这个方案的时候,是基于开发人员不对代码做任何修改的思路来考虑的。



    Q:容器生命周期很短?如何做到动态监控?你们具体监控了哪些重要指标?谢谢。

    A:我们监控用的是Prometheus方案,监控做了 主机,容器,中间件 几个大的范围。



    以上内容根据2017年4月6日晚微信群分享内容整理。分享人陈俊凯,数人云资深架构师。最早在爱立信做 CDMA 核心网开发,随后负责腾讯 C++ 后台开发,对 Docker,Mesos 有所研究,熟悉和热爱云计算、分布式、SDN 等领域相关技术。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesz,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    DockOne微信分享(一零八):基于Jenkins和Kubernetes的CI工作流

    若菱 发表了文章 • 0 个评论 • 4443 次浏览 • 2017-03-01 18:38 • 来自相关话题

    【编者的话】Jenkins作为最为流行的持续集成工具,在结合使用容器技术,Kubernetes集群的基础上,该如何发挥出新的能力,在应用微服务化的基础上,提供更好的CI方式,值得我们每一个开发人员去持续不断的摸索。本次分享主要介绍我司如何使用Jenkins P ...查看全部
    【编者的话】Jenkins作为最为流行的持续集成工具,在结合使用容器技术,Kubernetes集群的基础上,该如何发挥出新的能力,在应用微服务化的基础上,提供更好的CI方式,值得我们每一个开发人员去持续不断的摸索。本次分享主要介绍我司如何使用Jenkins Pipeline、Container和 Kubernetes Deployment的能力, 通过增加使用文本模版引擎, 扩展Kubernetes Config能力,完成公司产品开发CI工作流的建立。
    # Jenkins和Kubernetes
    Jenkins作为最流行的持续集成工具,有着丰富的用户群、强大的扩展能力、丰富的插件,是开发人员最为常见的CI工具。在Jenkins 加强其Pipeline功能后,更是可以通过丰富的step库,实现各种复杂的流程。同时随着Docker的流行,Jenkins内也增加了对Docker的支持,可以实现容器内流程的执行。

    而Kubernetes随着版本迭代的速度越来越快,在容器圈内的热度也越来越高,同时每次版本发布,所新增的功能也不断增加。做为当前主流的容器管理平台,其强大的能力无需在此多做介绍。
    # 应用容器化和应用微服务化设计思考
    容器化不意味着微服务化,传统单体应用也可以容器化,但是很难享受到容器化后带来的好处。微服务化也不是一定要容器化,应用拆解为微服务后,一样可以不利用容器而是通过传统的运维来完成系统构建和部署。当微服务化和容器化相结合之后,就能充分利用各方优势,带来了弹性伸缩,简化部署,易于扩展,技术兼容等优点。

    我们在针对应用进行微服务化拆分的过程中,主要先考虑到的是功能点、控制对象、开发组的人员配置安排,产品路线图规划等。例如,针对现有开发组人员人数、分配和各自的技能熟练程度,就可以考虑到服务模块数量的控制,安排好服务模块开发小组;针对功能点和中远期产品规划,就可以将特定功能归纳到一个服务模块中,并在版本开发迭代的过程中,通过扩展这个服务模块的能力,来完成产品功能的开发,或者暂时将部分功能整合在一个模块中,随着功能增加或迭代开发,再进行进一步的模块拆分或拆解。

    对于模块开发的要求,由于使用了容器技术,对于开发语言或特定框架的选型,可以交给具体的模块开发人员。在团队内,我们不做强制要求,但是做建议要求,避免出现过多的技术栈,导致后期的维护困难。

    在我们团队内,就只集中在两种后端开发语言的使用,相应的框架或主要的开发库,也都有相应而且明确的选择。对于模块的API接口,使用REST,并且至少按照成熟度模型LEVEL2提供API。
    # 容器环境下的编译和单元测试
    我们整个CI工作流的驱动,都是由Jenkins完成,并且使用了Jenkins Pipeline。第一,Pipeline可以更好的组合job内的stage,重复利用模块间相同的部分,并且随着开发复杂度的增加来逐步增加扩展stage,实现更多所需的功能;第二,将Pipeline Groovy脚本来源设置为源代码内,可以根据源代码功能点来控制流程,同时也完成了对脚本的版本管理。

    由于有容器这么个工具,我们各个模块的编译环境,单元测试环境,也都放到了容器中。各个模块均可以安装自身模块的运行特性或环境要求,准备自身的编译环境、单元测试环境、运行环境,因此,代码库内会分别留存相应的Dockerfile,通过不同的Dockerfile完成不同环境镜像的准备。同时,Jenkins现在也可以通过Docker Pipeline插件,支持在容器内运行step,因此我们利用其功能完成的实际的编译和测试流程是这样的:

    1. 使用编译环境的Dockerfile构建编译环境镜像。
    2. 使用编译环境镜像启动容器并在容器内完成编译,完成编译的中间产物也暂存在Workspace中。
    3. 使用测试环境的Dockerfile构件单元测试环境镜像。
    4. 使用单元测试环境镜像启动容器并在容器内运行单元测试,单元测试脚本来源于代码库,同时也使用到编译时生成的中间产物。
    5. 使用发布Dockerfile构建实际发布镜像并上传镜像库。

    其中由于编译环境和单元测试环境不是经常变更,也可以抽出编译环境镜像准备和单元测试环境镜像准备两个步骤放到独立的CI job中去,需要时手工触发即可。
    # 服务部署和升级
    对于CI流程,在完成编译和打包后,需要做的就是服务启动和测试了。我们利用的是Kubernetes Deployment和service,在每次CI流程完成编译和打包后,通过拿到Build号,作为镜像的tag,完成镜像的上传归档;同时利用tag,修改Kubernetes中已经创建的Deployment,利用Deployment的Rolling Update,完成升级。
    # 对Kubernetes服务模版和服务配置的扩展
    我们在实际使用Kubernetes Deployment 升级的方式进行服务部署的过程中,发现其中还是存在很多不方便的地方。例如:Kubernetes内的同名问题,Kubernetes Deployment升级时的镜像tag变更问题,等等各处需要随着CI流程可能存在变更的地方。例如在有相同名字的Deployment存在的情况下,后来的Deployment会无法创建,这导致如果想以启动新的Deployment的方式来测试某个版本,需要修改名称,对于与Deployment相关的service也一样,在启动新的命名后的Deployment,也需要启动与其对应的service用于暴露服务。

    对于Deployment升级所需的镜像tag修改,需要每次随着CI生成了新的镜像tag而做变更,因而每次需要修改相应yaml文件内的镜像tag,修改为实际CI流程中生成的值,然后再使用升级功能完成服务升级。

    针对这些问题,我们使用了一套文本模板引擎,部署或升级用的yaml文件本身写成为模板,可能有变化或者需要根据CI流程变化的位置,使用模板标识占位,而具体的模板数据内容,则或者通过Jenkins的CI流程获取,或者使用特定的配置文件读取,或者从具体的输入参数来获取;

    同时,模板数据内容,也会在实际部署时,做为Kubernetes的Configmap设置到系统中,因此数据内容也可以通过Kubernetes使用Configmap的方式来用到环境变量或启动命令中。通过文本模板引擎,将模板和数据合并后,生成的yaml文件,再作为后续Kubernetes操作所使用的内容。

    通过利用这种方式,我们把需要部署的内容分离成了模板和配置;模板一般在服务架构,使用的镜像名,启动方式或配置参数没有大的变化的情况下保持不变,而通过不同配置的灵活使用,完成服务升级或拉起新部署,完成不同数据存储使用的指向,完成对各模块内部配置的修改。

    通过利用这种方式,我们的可修改的内容,从Configmap本身只能覆盖到的环境变量或启动命令这块,扩展到了启动名称、Label、镜像等YAML文件内的各个可填值处,以此来解决同名,镜像修改,Label增加或变更等各种使用Kubernetes时碰到的问题。
    # 自动化测试
    在通过Jenkins拉动完成编译打包和服务升级部署后,就可以拉动自动化测试了。测试框架我们选择了使用Robotframework。测试脚本通过Kubernetes service获取到服务的具体暴露端口,然后再根据测试脚本依次执行针对API的测试。

    测试脚本的来源,部分是从各模块代码库中,由各模块开发人员提交的针对自身模块的API测试,部分是由测试人员完成撰写提交的针对跨模块的测试。针对自动化测试这块,我们的完成度并不是很高,仅仅是搭建起了基本的运行框架,能够与整个流程对接上。
    # 版本发布
    由于开发的产品本身就是由若干镜像构成,因此产品发布,可以归结为镜像的发布。在测试通过后,可以简单的利用镜像复制能力,将测试通过的相关镜像的版本,通过镜像库间的复制,由开发测试所用的内部镜像库,复制到外部发布镜像库,就可以完成版本发布,同时可以通过复制时的tag控制,发布为指定的版本号。
    # 总结
    如上介绍,说明了我们在自身开发过程中建立CI流程的做法。对于整个流程的建立,我们并没有太多需要特殊化处理的地方;对于各项工具的使用,也没有太多突出之处;我们仅仅根据自身需求,建立了和开发过程适配的CI流。在此介绍我们的CI流程的建立,也是希望抛砖引玉,能从更多处获得交流和向大家学习的机会。
    #Q&A
    Q:关于Jenkins和Docker集成的几个插件可以分享一下吗?

    A:Docker Pipeline、Docker Plugin、docker-build-step这几个插件。



    Q:请问有容云的镜像复制大体思路是什么?目前我们是测试、预发布、发布共享一个仓库。通过辅助模块标签实现的。

    A:镜像复制功能,简单来说实现了不同项目间的镜像克隆,根据我们镜像仓库的设计,对不同阶段(开发、测试、可上线)的镜像以不同项目分类,基于镜像复制功能即可快速实现不同阶段的产品发布,也就是镜像发布,此功能可下载AppHouse版本进行试用。



    Q:贵公司的内部仓库和外部仓库镜像是实时同步的吗? 你们的配置文件是通过配置中心管理还是镜像间的环境变量实现的?

    A:不是实时同步的,而是通过了我们公司镜像库产品的镜像复制能力实现的。目前开发流程中的产品运行配置是通过自身增强的配置文件能力实现的,配置会用来修改应用部署的YAML文件,也会生成为ConfigMap。



    Q:有没有一个简单的sample,可以上手跟着练习一下?

    A:基于Kubernetes的CICD产品即将发布,会提供对应的demo演示平台,请及时关注,谢谢!



    Q:Kubernetes的YAML的部署文件的模板怎么去替换占位符?旧版本的容器怎么处理?

    A:使用了自身开发的一套文本模板引擎,其实类似Web框架中的模板引擎,完成模板和配置的合并,通过使用配置中的key-value,替换掉模板中key的占位符。另外由于是使用的Kubernetes deployment的rolling update,升级完成后旧版本的容器/pod会由Kubernetes自行删除。



    Q:传统单体应用如何容器化改造,可否分阶段实施?

    A:可以的,容器化改造或微服务化改造有很多实施方法,例如逐渐重构拆解,或新增模块进行微服务化和容器化,或开发新的模块替代原有应用的功能点,等等。各团队均可以选择合适自身的流程来进行改造。



    Q:数据库可以容器化吗?

    A:可通过将数据卷挂入容器的方式将数据库容器化,但是现在实际项目中还很少见。



    Q:有这样一个场景,两个服务有依赖关系,服务A依赖于服务B,如何保证服务A和B的启动顺序的?

    A:良好的设计是使得A服务启动后自行完成对B服务的检测发现和调用,而不是强依赖其启动顺序.



    Q:Kubernetes的服务的模板和配置,这个模板怎么来的,是用户自己编排?还是自己事先准备好的?配置数据是怎么存储的?

    A:因为当前模板和配置只用来启动我们自身开发的应用,因此这个模板是我们自己为我们的应用准备的。配置数据以文件的形式存储,但同时在使用文本引擎做模板和配置合并时,也可以接受参数作为配置。



    Q:什么是CI和CD,这个搞不懂?

    A:CI更多是偏向应用编译,代码检查,单元测试等动作,CD是偏向于应用部署,运行流程。我们的开发过程在编译打包完成后,实际也会将应用跑起来用于测试,也可以算是针对测试的CD。



    Q:使用容器打包Jenkins流程的主要收益是什么?

    A:由于不同程序对于编译环境的依赖各有不同,原有使用Jenkins方法是在Jenkins node上完成环境准备,现在可以利用容器完成环境准备,对于Jenkins node的依赖可以进一步降低。同时环境变更也可以由开发人员自行控制。



    Q:多编译环境是用的不同镜像么?如何处理Pipeline处理编译环境的问题?

    A:是的。由于我们本身产品开发各个模块有各个模块的开发语言和框架,因此各模块都要维护自身的编译环境镜像。使用Pipeline在进行编译时,是通过使用镜像运行容器然后在容器内编译的方式来使用编译环境的。



    Q:请问Jenkins也是部署在Docker里面的吗?如果Jenkins在Docker里面怎么样在Docker里面使用Docker执行CI?

    A:是的,我们也在摸索将Jenkins本身放到容器中运行。在这种情况下,Jenkins容器内使用root权限,挂载docker.sock和Docker数据目录到容器中就可以了。



    Q:使用Pipeline先构建编译环境镜像,再编译,是否会导致整个流程需要很长时间?是否有优化方案?

    A:编译镜像由于不会经常变动,因此这个镜像的构建通常使用cache就能直接完成,另外我们也把编译环境镜像打包这个步骤抽出来单独作为job执行了,这样在实际编译流程中就无需再进行编译环境构建。



    Q:Jenkins和Kubernetes的用户是怎么管理的?我的期望是用户只能看到自己得资源,别的用户是没有权限的。

    A:我们本身只是使用这两种工具,在开发环境中不对外,所有不存在用户管理的问题。在我们公司正在开发的CICD产品中,对这块有我们自身理解基础上的设计。



    Q:Jenkins的持续集成是怎么实现的?比如不同的源码仓库的提交触发,如GitHub、GitLab版本号怎么控制的?

    A:Jenkins的CI流程触发可以有很多种,代码提交触发,定时触发,手动触发。版本号的控制也可以有很多方案,比如使用job的编号,使用Git的commit号,使用时间戳等等。



    Q:容器化后发布也要通过Jenkins,感觉Docker的发布没有Jenkins方便,除了容器化的可移植,还有什么原因值得推进项目容器化?

    A:应用容器化,其实更多的是看重应用在容器管理平台上运行起来后所获得的能力,例如在Kubernetes上运行后的水平扩展,服务发现,滚动升级,等等。



    Q:Kubernetes update需要制定新的镜像才能做滚服更新(升级),如果只是更新了ConfigMap,有办法做滚服更新吗?

    A:我们的CI流程完成后,各模块的镜像tag会发生变化,我们利用具体生成的tag生成配置,然后部署的YAML文件写为模板,镜像的具体tag会根据每次CI流程生成的配置不同而组合为不同的YAML文件,然后使用组合后的yaml,即tag已经变更为最新版本的YAML文件进行应用的滚动升级。



    Q:Pipeline采用在镜像里构建的方案,是怎么实现的?用Jenkins现成的插件 or 用Groovy脚本实现?

    A:使用了Jenkins的Docker插件,同时使用方式是将相应命令写在Groovy脚本里,例如:`stage('Build'){ docker.image('golang:1.7').inside { sh './script/build.sh' } } `。



    以上内容根据2017年2月28日晚微信群分享内容整理。分享人黄文俊,有容云资深系统架构师。主要负责容器云平台产品架构及设计,8年工作经验,有着企业级存储,云计算解决方案相关理解。关注于微服务设计思考,开发流程优化,Docker及Kubernetes技术在实际环境中的应用。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesz,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

    Docker运行的Jenkins报“ERROR: Failed to push image: failed to respond”

    回复

    maxwell92 回复了问题 • 1 人关注 • 1 个回复 • 3244 次浏览 • 2017-05-02 14:37 • 来自相关话题

    有没有人试过持续集成中Jenkins节点用Docker集群实现?

    回复

    sxdocker 回复了问题 • 2 人关注 • 1 个回复 • 2918 次浏览 • 2016-11-30 23:18 • 来自相关话题

    启动jenkins容器,挂载主机目录,容器就无法在后台运行,怎么回事?

    回复

    zhuwz 回复了问题 • 2 人关注 • 2 个回复 • 8162 次浏览 • 2016-03-17 16:40 • 来自相关话题

    jenkins配置jdk和maven,是要在容器里自己安装吗?

    回复

    akirapanda 回复了问题 • 2 人关注 • 1 个回复 • 7135 次浏览 • 2016-01-27 21:39 • 来自相关话题

    容器中运行Jenkins出错

    回复

    小飞侠 回复了问题 • 2 人关注 • 3 个回复 • 3191 次浏览 • 2015-05-11 22:44 • 来自相关话题

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

    灵雀云 发表了文章 • 0 个评论 • 254 次浏览 • 2019-05-22 10:43 • 来自相关话题

    本文要点: 设计一条 Spring Boot 最基本的流水线:包括构建、制品上传、部署。 使用 Docker 容器运行构建逻辑。 自动化整个实验环境:包括 Jenkins 的配置,Jenkins agent 的配置等。 ...查看全部
    本文要点:
    设计一条 Spring Boot 最基本的流水线:包括构建、制品上传、部署。
    使用 Docker 容器运行构建逻辑。
    自动化整个实验环境:包括 Jenkins 的配置,Jenkins agent 的配置等。

    1. 代码仓库安排

    本次实验涉及以下多个代码仓库:
    % tree -L 1
    ├── 1-cd-platform # 实验环境相关代码
    ├── 1-env-conf # 环境配置代码-实现配置独立
    └── 1-springboot # Spring Boot 应用的代码及其部署代码

    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 是为了启动虚拟机,用于部署 Spring Boot 应用。如果你的开发机器无法使用 Vagrant,使用 VirtualBox 也可以达到同样的效果。但是有一点需要注意,那就是网络。如果在虚拟机中要访问 Docker 容器内提供的服务,需要在 DNS 上或者 hosts 上做相应的调整。所有的虚拟机的镜像使用 Centos7。
    另,接下来笔者的所有教程都将使用 Artifactory 作为制品库。在此申明,笔者没有收 JFrog——研发 Artifactory 产品的公司——任何广告费。 笔者只是想试用商业产品,以便了解商业产品是如何应对制品管理问题的。
    启动 Artifactory 后,需要添加 “Virtual Repository” 及 “Local Repository”。具体请查看 Artifactory 的官方文档。如果你当前使用的是 Nexus,参考本教程,做一些调整,问题也不大。
    如果想使用已有制品库,可以修改 1-cd-platform 仓库中的 settings-docker.xml 文件,指向自己的制品库。
    实验环境近期的总体结构图如下:

    pic1.jpg



    之所以说是“近期的”,是因为上图与本篇介绍的结构有小差异。本篇文章还没有介绍 Nginx 与 Springboot 配置共用,但是总体不影响读者理解。如果你想和更多Jenkins技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

    3. Springboot 应用流水线介绍

    Springboot 流水线有两个阶段:


    构建并上传制品
    部署应用
    流水线的所有逻辑都写在 Jenkinsfile 文件。接下来,分别介绍这两个阶段。
    3.1 构建并上传制品
    此阶段核心代码:


    docker.image('jenkins-docker-maven:3.6.1-jdk8')
    .inside("--network 1-cd-platform_cd-in-practice -v $HOME/.m2:/root/.m2") {
    sh """
    mvn versions:set -DnewVersion=${APP_VERSION}
    mvn clean test package
    mvn deploy
    """
    }


    它首先启动一个装有 Maven 的容器,然后在容器内执行编译、单元测试、发布制品的操作。
    而 mvn versions:set -DnewVersion=${APP_VERSION} 的作用是更改 pom.xml 文件中的版本。这样就可以实现每次提交对应一个版本的效果。
    3.2 部署应用
    注意: 这部分需要一些 Ansible 的知识。
    首先看部署脚本的入口 1-springboot/deploy/playbook.yaml:

    ---


    [list]
    [*]hosts: "springboot"[/*]
    [/list] become: yes
    roles:
    - {"role": "ansible-role-java", "java_home": "{{JAVA_HOME}}"}
    - springboot



    先安装 JDK,再安装 Spring Boot。JDK 的安装,使用了现成 Ansible role: https://github.com/geerlingguy/ansible-role-java。
    重点在 Spring Boot 部署的核心逻辑。它主要包含以下几部分:
    创建应用目录。
    从制品库下载指定版本的制品。
    生成 Systemd service 文件(实现服务化)。
    启动服务。
    以上步骤实现在 1-springboot/deploy/roles/springboot 中。
    流水线的部署阶段的核心代码如下:


    docker.image('williamyeh/ansible:centos7').inside("--network 1-cd-platform_cd-in-practice") {

    checkout([$class: 'GitSCM', branches: [[name: "master"]], doGenerateSubmoduleConfigurations: false,
    extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: "env-conf"]], submoduleCfg: [],
    userRemoteConfigs: [[url: "https://github.com/cd-in-practice/1-env-conf.git"]]])

    sh "ls -al"

    sh """
    ansible-playbook --syntax-check deploy/playbook.yaml -i env-conf/dev
    ansible-playbook deploy/playbook.yaml -i env-conf/dev --extra-vars '{"app_version": "${APP_VERSION}"}'
    """
    }


    它首先将配置变量仓库的代码 clone 下来,然后对 playbook 进行语法上的检查,最后执行 ansible-playbook 命令进行部署。--extra-vars 参数的 app_version 用于指定将要部署的应用的版本。

    3.3 实现简易指定版本部署

    在 1-springboot/Jenkinsfile 中实现了简易的指定版本部署。核心代码如下:
    流水线接受参数
    parameters { string(name: 'SPECIFIC_APP_VERSION',
    defaultValue: '', description: '') }
    如果指定了版本,则跳过构建阶段,直接执行部署阶段


    stage("build and upload"){
    // 如果不指定部署版本,则执行构建
    when {
    expression{ return params.SPECIFIC_APP_VERSION == "" }
    }
    // 构建并上传制品的逻辑
    steps{...}
    }


    之所以说是“简易”,是因为部署时只指定了制品的版本,并没有指定的部署逻辑和配置的版本。这三者的版本要同步,部署才真正做到准确。

    4. 配置管理

    所有的配置项都放在 1-env-conf 仓库中。Ansible 执行部署时会读取此仓库的配置。
    将配置放在 Git 仓库中有两个好处:
    配置版本化。
    任何配置的更改都可以被审查。
    有好处并不代表没有成本。那就是开发人员必须开始关心软件的配置(笔者发现不少开发者忽视配置项管理的重要性。)。
    本文重点不在配置管理,后面会有文章重点介绍。

    5. 实验环境详细介绍

    事实上,整个实验,工作量大的地方有两处:一是 Spring Boot 流水线本身的设计;二是整个实验环境的自动化。读者朋友之所以能一两条简单的命令就能启动整个实验环境,是因为笔者做了很多自动化的工作。笔者认为有必要在本篇介绍这些工作。接下来的文章将不再详细介绍。

    5.1 解决流水线中启动的 Docker 容器无法访问 http://artifactory

    流水线中,我们需要将制品上传到 artifactory(settings.xml 配置的仓库地址是 http://artifactory:8081),但是发现无法解析 host。这是因为流水线中的 Docker 容器所在网络与 Docker compose 创建的网络不同。所以,解决办法就是让流水线中的 Docker 容器加入到 Docker compose 的网络。
    具体解决办法就是在启动容器时,加入参数:--network 1-cd-platform_cd-in-practice

    5.2 Jenkins 初次启动初始化

    在没有做任何设置的情况启动 Jenkins,会出现一个配置向导。这个过程必须是手工的。笔者希望这一步也是自动化的。Jenkins 启动时会执行 init.groovy.d/目录下的 Groovy 脚本。
    5.3 虚拟机中如何能访问到 http://artifactory ?
    http://artifactory 部署在 Docker 容器中。Spring Boot 应用的制品要部署到虚拟机中,需要从 http://artifactory 中拉取制品,也就是要在虚拟机里访问容器里提供的服务。虚拟机与容器之间的网络是不通的。那怎么办呢?笔者的解决方案是使用宿主机的 IP 做中转。具体做法就是在虚拟机中加一条 host 记录:


    machine.vm.provision "shell" do |s|
    s.inline = "echo '192.168.52.1 artifactory' >> /etc/hosts"
    end


    以上是使用了 Vagrant 的 provision 技术,在执行命令 vagrant up 启动虚拟机时,就自动执行那段内联 shell。192.168.52.1 是虚拟宿主机的 IP。所以,虚拟机里访问 http://artifactory:8081 时,实际上访问的是 http://192.168.52.1:8081。
    网络结构可以总结为下图:

    pic2.jpg


    后记

    目前遗留问题:
    部署时制品版本、配置版本、部署代码版本没有同步。
    Springboot 的配置是写死在制品中的,没有实现制品与配置项的分离。
    这些遗留问题在后期会逐个解决。就像现实一样,经常需要面对各种遗留项目的遗留问题。
    附录
    使用 Jenkins + Ansible 实现自动化部署 Nginx:https://jenkins-zh.cn/wechat/articles/2019/04/2019-04-25-jenkins-ansible-nginx/
    简单易懂 Ansible 系列 —— 解决了什么:https://showme.codes/2017-06-12/ansible-introduce/


    本文转自公众号 jenkins
    作者 翟志军

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

    灵雀云 发表了文章 • 0 个评论 • 236 次浏览 • 2019-05-20 11:06 • 来自相关话题

    背景 了解到行业内有些团队是基于 Jenkins 开发 DevOps 平台。而基于 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 的变量中。接下来,你就可以在闭包中使用该变量了。
    说到这里,不知道读者朋友是否已经有思路了?
    思路就是实现一个和 Credentials Binding Plugin 插件类似功能的方法,比如叫 zWithCredentials(后文还会提到)。与 withCredentials 不同的是,zWithCredentials 根据凭证 id 获取凭证时,不是从 Jenkins 上获取,而是从 DevOps 平台获取。

    会遇到的坑

    需要适配只认 Jenkins 凭证的插件
    withCredentials 方法是将凭证的内容存到变量中,这可以满足一大部分场景。但是有一种场景是无法满足的。就是某些 Jenkins 插件的步骤接收参数时,参数值必须是 Jenkins 凭证管理系统中的 id。比如 git 步骤中 credentialsId 参数:


    git branch: 'master',
    credentialsId: '12345-1234-4696-af25-123455',
    url: 'ssh://git@bitbucket.org:company/repo.git'


    这种情况,我们不可能修改现有的插件。因为那样做的成本太高了。
    那怎么办呢?
    笔者想到的办法是在 zWithCredentials 中做一些 hack 操作。也就是 zWithCredentials 除了从 DevOps 平台获取凭证,还在 Jenkins 中创建一个 Jenkins 凭证。在 Jenkins 任务执行完成后,再将这个临时凭证删除。这样就可以适配那些只认 Jenkins 凭证 id 的插件了。
    对凭证本身的加密
    DevOps 平台在存储凭证、传输凭证给 Jenkins 时,都需要对凭证进行加密。至于使用何种加密方式,交给读者思考了。

    小结

    以上解决方案对 Jenkins 本身的改造几乎没有,我们只通过一个插件就解耦了 Jenkins 的凭证管理和 DevOps 平台的凭证管理。
    思路已经有了。具体怎么实现,由于一些原因不能开源,虽然实现起来不算难。还请读者见谅。
    最后,希望能和遇到同样问题的同学进行交流。看看是否还可以有更好的设计思路。

    本文转自公众号 Jenkins
    作者:翟志军

    Kubernetes 中的渐进式交付:蓝绿部署和金丝雀部署

    灵雀云 发表了文章 • 0 个评论 • 378 次浏览 • 2019-05-07 15:27 • 来自相关话题

    渐进式交付是持续交付的下一步, 它将新版本部署到用户的一个子集,并在将其滚动到全部用户之前对其正确性和性能进行评估, 如果不匹配某些关键指标,则进行回滚。 这里有一些有趣的项目,使得渐进式交付在 Kubernetes 中变得更简单。我 ...查看全部
    渐进式交付是持续交付的下一步, 它将新版本部署到用户的一个子集,并在将其滚动到全部用户之前对其正确性和性能进行评估, 如果不匹配某些关键指标,则进行回滚。

    这里有一些有趣的项目,使得渐进式交付在 Kubernetes 中变得更简单。我将使用一个 Jenkins X 示例项目 对它们之中的三个进行讨论:Shipper、Istio 以及 Flagger。

    Shipper

    shipper 是来自 booking.com 的一个项目, 它对 Kubernetes 进行了扩展,添加了复杂的部署策略和多集群编排(文档)。它支持从一个集群到多个集群的部署,允许多区域部署。
    Shipper 通过一个 shipperctl 命令行进行安装。它增加不同集群的配置文件来进行管理。请注意这个与 GKE 上下文相关的问题。
    Shipper 使用 Helm 包来部署,但是它们没有随着 Helm 一起安装,它们不会在 helm list 的输出显示。同样地,deployments 的版本必须是 apps/v1 , 否则 shipper 将不能编辑 deployment 来添加正确的标签和副本数量。
    使用 shipper 部署都是与从旧版本(现有版本)过渡到新版本(竞争版本)相关。这是通过创建一个新的应用对象实现的, 它定义了部署需要通过的多个阶段。例如下面 3 个步骤过程:
    Staging:部署新版本到一个 pod ,没有流量
    50 / 50:部署新版本到 50% 的 pods,50% 的流量
    Full on:部署新版本到全部的 pods,全部的流量


    strategy:
    steps:
    - name: staging
    capacity:
    contender: 1
    incumbent: 100
    traffic:
    contender: 0
    incumbent: 100
    - name: 50/50
    capacity:
    contender: 50
    incumbent: 50
    traffic:
    contender: 50
    incumbent: 50
    - name: full on
    capacity:
    contender: 100
    incumbent: 0
    traffic:
    contender: 100
    incumbent: 0


    如果发布的某个步骤没有将流量发送到 pods , 则可以使用 kubectl port-forward 访问它们,如:kubectl port-forward mypod 8080:8080, 这对于在用户看到新版本之前进行测试非常有用。
    Shipper 支持多集群的概念,但是以相同的方式对待所有集群,仅使用区域并通过 capabilities (配置在集群对象中)进行筛选, 所有对一个应用对象来说,这里没有一个 dev, staging, prod 集群的选项。但是我们可以有两个应用对象:
    myapp-staging 部署到 "staging" 区域
    myapp 部署到其它区域
    在 GKE 中,你可以轻松地配置多集群 ingress , 该入口将公开在多个集群中运行的服务,并从离你所在位置最近的集群提供服务。
    局限性
    Shipper 中的主要的局限性有:
    Chart 限制:Chart 必须有一个部署对象。Deployment 的名称必须使用 {{.Release.Name}} 模板化。Deployment 对象应该有 apiVersion:apps/v1 。
    基于 Pod 的流量切换:这里没有细粒度的流量路由,例如:发送 1% 的流量到新版本,它基于正在运行的 Pod 数量。
    如果 Shipper 不工作了,新的 Pod 将获取不到流量。

    Istio

    Istio 不是一个部署工具,而是一个服务网格。然而,它很令人感兴趣,因为它已经变得非常流行,并且允许流量管理,例如,将一定比例的流量发送到不同的服务和其他高级网络。
    在 GKE 中,只需在集群配置中选中复选框即可启用 Istio 。在其它集群中,可以通过 Helm 手动安装。
    有了 Istio ,我们可以创建一个网关,通过 Ingress 网关处理所有外部流量,并创建虚拟服务来管理到我们服务的路由。为此,只需找到 ingress 网关的 ip 地址并为其配置通配符 DNS 。然后创建一个网关,通过 Ingress 网关路由所有外部流量。


    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
    name: public-gateway
    namespace: istio-system
    spec:
    selector:
    istio: ingressgateway
    servers:
    - port:
    number: 80
    name: http
    protocol: HTTP
    hosts:
    - "*"


    Isito 不管理应用的生命周期,只管理网络。我们可以创建一个虚拟服务,为所有进入 ingress 网关的请求 向 pull request 或 master 分支中部署的服务发送 1% 的流量。


    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
    name: croc-hunter-jenkinsx
    namespace: jx-production
    spec:
    gateways:
    - public-gateway.istio-system.svc.cluster.local
    - mesh
    hosts:
    - croc-hunter.istio.example.org
    http:
    - route:
    - destination:
    host: croc-hunter-jenkinsx.jx-production.svc.cluster.local
    port:
    number: 80
    weight: 99
    - destination:
    host: croc-hunter-jenkinsx.jx-staging.svc.cluster.local
    port:
    number: 80
    weight: 1


    Flagger
    Flagger 是一个由 Weaveworks 赞助的使用了 Istio 的项目, 该项目使用 Prometheus 的指标进行自动化金丝雀发布和回滚。它超越了 Isito 提供了基于指标的自动化渐进式发布和回滚。
    Flager 需要将 Istio与 Prometheus、Servicegraph 和某些系统的配置一起安装, 另外还要安装 Flager 控制器本身。它也提供了一个 Grfana 面板来监控部署进度。


    pic1.jpg


    部署 rollout 通过 Canary 对象定义, 它会生成主要的和金丝雀 Deployment 对象。编辑 Deployment 时,例如要使用新的镜像版本, Flagger 控制器将负载从 0% 切换到 50% ,每分钟增加 10% ,然后它将切换到新的 deployment 或者如果响应错误和请求持续时间等指标失败则进行回滚。

    比较

    此表总结了 Shipper 和 Flagger 在几个渐进式交付特性方面的优势和劣势。

    pic2.jpg



    pic3.jpg




    综上所述,我看到了 Shipper 在多集群管理和简单性方面的价值,它不需要 Kubernetes 以外的任何东西,但是它有一些严重的局限性。
    Flager 确实在自动部署和回滚以及对流量进行细粒度控制的过程中付出了额外的努力,它以更高的复杂性成本提供了所需的所有额外服务( Isito、Prometheus )。
    这里可以查看 Shipper、Isito 和 Flager 的示例代码。

    本文转自微信公众号 Jenkins

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

    灵雀云 发表了文章 • 0 个评论 • 444 次浏览 • 2019-04-29 11:00 • 来自相关话题

    本文介绍如何使用 Jenkins + Ansible 实现对 Nginx 的自动化部署。最终达到的效果有如下几点: 只要你将 Nginx 的配置推送到 GitHub 中,Jenkins 就会自动执行部署,然后目标服务器的 Nginx 配置自动生效。 ...查看全部
    本文介绍如何使用 Jenkins + Ansible 实现对 Nginx 的自动化部署。最终达到的效果有如下几点:
    只要你将 Nginx 的配置推送到 GitHub 中,Jenkins 就会自动执行部署,然后目标服务器的 Nginx 配置自动生效。这个过程是幂等(idempotent)的,只要代码不变,执行多少遍,最终效果不变。
    如果目标机器没有安装 Nginx,则会自动安装 Nginx。
    自动设置服务器防火墙规则。

    1. 实验环境介绍

    本次实验使用 Docker Compose 搭建 Jenkins 及 Jenkins agent。使用 Vagrant 启动一台虚拟机,用于部署 Nginx。使用 Vagrant 是可选的,读者可以使用 VirtualBox 启动一个虚拟机。使用 Vagrant 完全是为了自动化搭建实验环境。
    以下是整个实验环境的架构图

    pic1.jpg



    注意,图中的 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 镜像有两个原因:


    docker build -f JenkinsSlaveAnsibleDockerfile -t jenkins-swarm-ansible .


    本次实验,使用 Swarm 插件实现 Jenkins master 与 agent 之间的通信,所以 Jenkins agent 需要启动 swarm 客户端。
    Jenkins agent 必须支持 Ansible。
    启动 Jenkins master 及 Jenkins agent


    docker-compose up -d


    通过 http://localhost:8080 访问 Jenkins master,如果出现“解锁密码”页面,如下图,则执行命令 docker-compose logs jenkins 查看 Jenkins master 启动日志。将日志中的解锁密码输入到表单中。然后就一步步按提示安装即可。

    pic2.jpg


    安装 Jenkins 插件 本次实验需要安装以下插件:
    Pipeline 2.6:https://plugins.jenkins.io/workflow-aggregator
    Swarm 3.15:https://plugins.jenkins.io/swarm 用于 实现 Jenkins master 与 Jenkins agent 自动连接
    Git 3.9.3:https://plugins.jenkins.io/git
    配置 Jenkins master 不执行任务 进入页面:http://localhost:8080/computer/(master)/configure,如下图所示设置:

    pic3.jpg


    确认 Jenkins 安全配置有打开端口,以供 Jenkins agent 连接。我们设置 Jenkins master 开放的端口,端口可以是固定的 50000 ,也可以设置为随机。设置链接:http://localhost:8080/configureSecurity/。

    pic4.jpg


    启动目标机器,用于部署 Nginx 在命令行中执行以下命令:


    vagrant up


    注意,Vagrantfile 文件中的 config.vm.box 值必须改成你的 vagrant box 。
    至此,实验环境已经搭建好了。接下来就可以新建 Jenkins 任务了。

    3. 在 Jenkins 上创建部署任务

    1、新建流水线任务

    pic5.jpg


    2、配置流水线 配置 Jenkins 任务从远程仓库拉取 Jenkinsfile,如下图所示:

    pic5.jpg


    除此之外,不需要其它配置了,是不是很简单?

    4. 手工触发一次自动化构建

    点击“立即构建”:

    pic6.jpg


    最终执行日志如下:

    pic7.jpg


    至此,部署已经完成。以后修改 Nginx 的配置,只需要修改代码,然后推送到远程仓库,就会自动化部署。不需要手工登录到目标机器手工修改了。
    最后,我们可以通过访问 http://localhost:5123,如果出现如下页面说明部署成功:

    pic8.jpg



    5. 代码讲解

    以上步骤并不能看出自动化部署真正做了什么。那是因为我们所有的逻辑都写在代码中。是的,可以说是 everything is code。
    接下来我们介绍代码仓库。


    % tree -L 2
    ├── JenkinsSlaveAnsibleDockerfile # Jenkins agent 镜像 Dockerfile
    ├── Jenkinsfile # 流水线逻辑
    ├── README.md
    ├── Vagrantfile # Vagrant 虚拟机定义文件
    ├── docker-compose.yml # Jenkins 实现环境
    ├── env-conf # 所有应用配置
    │ └── dev # dev 环境的配置
    ├── deploy # Ansible 部署脚本所在文件夹
    │ ├── playbook.yaml
    │ └── roles
    └── swarm-client.sh # Jenkins swarm 插件的客户端


    5.1流水线逻辑

    Jenkinsfile 文件用于描述整条流水线的逻辑。代码如下:


    pipeline{
    // 任务执行在具有 ansible 标签的 agent 上
    agent { label "ansible"}
    environment{
    // 设置 Ansible 不检查 HOST_KEY
    ANSIBLE_HOST_KEY_CHECKING = false
    }
    triggers {
    pollSCM('H/1 [i] [/i] [i] [/i]')
    }
    stages{
    stage("deploy nginx"){
    steps{
    sh "ansible-playbook -i env-conf/dev deploy/playbook.yaml"
    }

    }}}

    environment 部分:用于定义流水线执行过程中的环境变量。
    triggers 部分:用于定义流水线的触发机制。pollSCM 定义了每分钟判断一次代码是否有变化,如果有变化则自动执行流水线。
    agent 部分:用于定义整条流水线的执行环境。
    stages 部分:流水线的所有阶段,都被定义在这部分。
    以上只是定义流水线是如何执行的,目前整条流水线只有一个 deploy nginx 阶段,并且只执行了一条 ansible-playbook 命令。但是它并没有告诉我们部署逻辑是怎么样的。

    5.2 部署逻辑

    所有的部署逻辑,包括 Nginx 的安装启动、配置的更新以及加载,都放在 Ansible 脚本中。对 Ansible 不熟的同学,可以在本文末尾找到介绍 Ansible 的文章。
    整个部署逻辑的入口在 deploy/playbook.yaml,代码如下:


    [list]
    [*]hosts: "nginx"[/*]
    [/list] become: true
    roles:
    # Nginx 的部署
    - ansible-role-nginx
    # 对防火墙的设置
    - ansible-role-firewall



    hosts:定义了 playbook 部署的目标主机分组名为 nginx。
    roles:包含了两个执行具体部署动作的 role,至于 role 内部逻辑,不在本文讨论范围,有兴趣的同学阅读源码。

    5.3 配置管理

    谈到部署,就不得不谈配置管理。
    回顾前文中流水线中执行的 shell 命令:ansible-playbook -i env-conf/dev deploy/playbook.yaml 我们通过 -i 参数指定部署时所使用的环境配置。通过这种方式实现环境配置与执行脚本的分离。这样带来以下几个好处:
    新增环境时,只需要复制现有的环境,然后将里面的变量的值改成新环境的即可。比如,要对测试环境进行部署,只需要将 -i 参数值改成:env-conf/test。
    对配置版本化控制。
    本次实验中,各个环境的配置放在 env-conf 目录中,目前只有 dev 环境,以下是 env-conf/ 目录结构:


    % cd env-conf/
    % tree
    └── dev
    ├── group_vars
    │ └── nginx.yaml
    ├── host_vars
    │ └── 192.168.52.10
    └── hosts


    hosts文件:Ansible 中通过“分组”来实现对主机的管理。hosts 文件内容如下:


    [nginx]
    192.168.52.10


    host_vars 目录:用于存放主机级别的配置变量,本例中 192.168.52.10 是一个 YAML 格式文件。注意文件名是该主机的 IP。我们在文件中放主机相关的配置,比如 Ansible 连接主机时使用到的用户名和密码。
    group_vars 目录:用于存放组级别的配置变量。比如 nginx.yaml 对应的就是

    nginx



    这个组的的配置变量。文件名与 hosts 中的组名对应

    总结

    到此,我们完整的自动化部署已经讲解完成。但是还遗留下一些问题:
    本文只是安装了一个“空”的 Nginx,但是没有介绍 Nginx 真正配置。
    目前主机的连接信息(SSH 密码)是明文写在` host_vars/192.168.52.10 `文件中的,存在安全风险。
    没有介绍如何当 Java 应用部署时,如何自动更新 Nginx 的配置。
    本文属于使用 Jenkins + Ansible 实现自动化部署的入门文章,笔者将根据读者的反馈决定是否写续集。
    如果觉得本文讲的 Jenkins 流水线逻辑部分不够过瘾,可以考虑入手一本最近才出版的《Jenkins 2.x实践指南》。长按下图进行扫码购买。

    本文转载自:微信公众号jenkins

    关于 Jenkins master 共享 JENKINS_HOME 目录的实验

    灵雀云 发表了文章 • 1 个评论 • 338 次浏览 • 2019-04-24 14:45 • 来自相关话题

    Jenkins master 的高可用是个老大难的问题。和很多人一样,笔者也想过两个 Jenkins master 共享同一个 JENKINS_HOME 的方案。了解 Jenkins 原理的人,都会觉得这个方案不可行。但是真的不可行吗? 由于工作原 ...查看全部
    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 。但是你会发现它们启动后的界面显示是不一样的。

    pic1.jpg


    jenkins-b 的界面如下图所示:

    pic2.jpg



    而 jenkins-a 的界面如下图所示:

    pic3.jpg



    这时,将 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。然后就算完成任务了。
    然后我们再在 jenkins-a 使用 admin/admin 进行登录,登录是报错的:用户密码不正确。
    接下来,执行 `docker-compose restart jenkins-a `docker-compose restart jenkins-a 命令重启 jenkins-a。再次使用 admin/admin 就可以登录成功了。
    当两个 Jenkins 启动完成后,接下来开始做实验。

    实验1:创建任务

    在 jenkins-a 创建任务 x,刷新 jenkins-b 的页面,jenkins-b 上会不会显示出任务 x ?
    结果:jenkins-b 不会出现任务 x。重启 jenkins-b 后,任务 x 出现在任务列表中。
    实验2:任务结果可见性
    jenkins-a 上任务执行,jenkins-b 上能否看到任务执行结果?
    jenkins-a 执行任务 x,并且执行成功。刷新 jenkins-b 看不到任何执行记录。重启 jenkins-b 后,可看到执行记录。

    实验3:两 master 同时执行同一任务

    分别在两个 Jenkins master 上(几乎)开始同一个任务 x。其中一个任务的 build number 会更新,但是另一个不会。
    其中 jenkins-a 任务 x 的 build number 会升到 2,而 jenkins-b 保持的是 1。这时,单独执行 jenkins-b 的任务 x,日志会出现错误:


    jenkins-b_1 | WARNING: A new build could not be created in job x
    jenkins-b_1 | java.lang.IllegalStateException: JENKINS-23152: /var/jenkins_home/jobs/x/builds/2 already existed; will not overwrite with x #2



    实验4:编辑任务

    jenkins-a 上设置任务 x 定时执行,刷新 jenkins-b 页面,任务 x 中并没有定时执行的设置。重启 jenkins-b 后,任务 x 更新。

    实验5:定时任务的结果是什么?

    如果 jenkins-a 和 jenkins-b 两个任务均为定时任务,而且都生效了。它们运行结果是什么的呢?
    看到的现象是,两个任务都会按时执行,但是只有一个任务能将运行结果写入到磁盘中。界面如下图:

    另,从日志中,可以确认 jenkins-a 和 jenkins-b 确实按时执行了。如下图日志中,看出 jenkins-a 定时执行 #6 次构建时报错,因为 jenkins-b 已经执行过 #6 次构建了:

    pic4.jpg


    小结
    可以确认的是,当两个 Jenkins 进程共用同一个 JENKINS_HOME 目录时,其中一个 Jenkins 进程更新了 JENKINS_HOME 的内容,另一个是不会实时更新的。所以,同时启动两个 Jenkins master 共用同一个 JENKINS_HOME 的方案是不可行的。我们不能在 jenkins-a 挂了后,直接将流量切到 jenkins-b。因为 jenkins-b 必须重启。
    最后结论:多个 Jenkins master 共享同一个 JENKINS_HOME 的方案是无法使用 Jenkins master 的高可用。

    附录

    Jenkins standby 实验环境:https://github.com/zacker330/jenkins-standby-experiment


    本文转自微信公众号:Jenkins

    简析 Jenkins 专有用户数据库加密算法

    灵雀云 发表了文章 • 0 个评论 • 465 次浏览 • 2019-04-19 11:11 • 来自相关话题

    认识Jenkins专有用户数据库 Jenkins 访问控制分为:安全域(即认证)与授权策略。 其中,安全域可以采用三种形式,分别为:Jenkins 专有用户数据库、LDAP、Servlet 容器代理。 ...查看全部
    认识Jenkins专有用户数据库

    Jenkins 访问控制分为:安全域(即认证)与授权策略。
    其中,安全域可以采用三种形式,分别为:Jenkins 专有用户数据库、LDAP、Servlet 容器代理。

    PIC1.png


    在哪里看到加密后的用户密码信息?

    Jenkins 专有用户的数据信息存放位置:$JENKINS_HOME/users/
    每个用户的相关信息存放在各自的 config.xml 文件中: $JENKINS_HOME/users/$user/config.xml
    在 config.xml 文件中的 passwordHash 节点可以看到用户密码加密后的密文哈希值:

    PIC2.png


    用户密码是用什么算法加密的呢?

    那么问题来了,用户密码是用何种加密方式加密的呢?可否通过解密密文得到明文呢?
    在 GitHub 上查看其源码,通过关键字 #jbcrypt 搜索定位到 HudsonPrivateSecurityRealm.java 这个文件。 HudsonPrivateSecurityRealm.java 具体路径是:jenkins/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
    源码片段如下:



    /**
    * {@link PasswordEncoder} that uses jBCrypt.
    */
    private static final PasswordEncoder JBCRYPT_ENCODER = new PasswordEncoder() {
    public String encodePassword(String rawPass, Object _) throws DataAccessException {
    return BCrypt.hashpw(rawPass,BCrypt.gensalt());
    }

    public boolean isPasswordValid(String encPass, String rawPass, Object _) throws DataAccessException {
    return BCrypt.checkpw(rawPass,encPass);
    }
    };
    /**
    * Combines {@link #JBCRYPT_ENCODER} and {@link #CLASSIC} into one so that we can continue
    * to accept {@link #CLASSIC} format but new encoding will always done via {@link #JBCRYPT_ENCODER}.
    */
    public static final PasswordEncoder PASSWORD_ENCODER = new PasswordEncoder() {
    /*
    CLASSIC encoder outputs "salt:hash" where salt is [a-z]+, so we use unique prefix '#jbcyrpt"
    to designate JBCRYPT-format hash.
    '#' is neither in base64 nor hex, which makes it a good choice.
    */
    public String encodePassword(String rawPass, Object salt) throws DataAccessException {
    return JBCRYPT_HEADER+JBCRYPT_ENCODER.encodePassword(rawPass,salt);
    }

    public boolean isPasswordValid(String encPass, String rawPass, Object salt) throws DataAccessException {
    if (encPass.startsWith(JBCRYPT_HEADER))
    return JBCRYPT_ENCODER.isPasswordValid(encPass.substring(JBCRYPT_HEADER.length()),rawPass,salt);
    else
    return CLASSIC.isPasswordValid(encPass,rawPass,salt);
    }

    private static final String JBCRYPT_HEADER = "#jbcrypt:";
    };


    通过分析该源码得知:
    明文通过 jbcrypt 算法得到密文 encPass
    密文的格式为:salt: encPass,其中以 #jbcrypt 表示 salt 作为数据头

    jbcrypt 是什么?

    jbcrypt 是 bcrypt 加密工具的 java 实现。 它的 API 非常简单,DEMO 如下,在 HudsonPrivateSecurityRealm.java 中可以看到加密和校验时使用了如下 API:


    // Hash a password for the first time
    String hashed = BCrypt.hashpw(password, BCrypt.gensalt());

    // gensalt's log_rounds parameter determines the complexity the work factor is 2**log_rounds, and the default is 10
    String hashed = BCrypt.hashpw(password, BCrypt.gensalt(12));

    // Check that an unencrypted password matches one that has previously been hashed
    if (BCrypt.checkpw(candidate, hashed))
    System.out.println("It matches");
    else
    System.out.println("It does not match");



    经验证,用 jbcrypt 对同一个明文加密后因为 salt 一般不同,加密后的密文一般不同。

    bcrypt 精要概况

    bcrypt 是不可逆的加密算法,无法通过解密密文得到明文。
    bcrypt 和其他对称或非对称加密方式不同的是,不是直接解密得到明文,也不是二次加密比较密文,而是把明文和存储的密文一块运算得到另一个密文,如果这两个密文相同则验证成功。

    总结

    综上, Jenkins 专有用户数据库使用了 jbcrypt 加密, jbcrypt 加密是不可逆的,而且对于同一个明文的加密结果一般不同。

    使用 Zabbix 监控 Jenkins

    灵雀云 发表了文章 • 0 个评论 • 380 次浏览 • 2019-04-16 10:58 • 来自相关话题

    【编者的话】Zabbix是一个基于Web界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。它能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 笔者最近的工作涉 ...查看全部
    【编者的话】Zabbix是一个基于Web界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。它能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。

    笔者最近的工作涉及到使用 Zabbix 监控 Jenkins。在谷歌上搜索到的文章非常少,能操作的就更少了。所以决定写一篇文章介绍如何使用 Zabbix 监控 Jenkins。 下图为整体架构图:

    pic1.jpg



    整体并不复杂,大体步骤如下: 在 Jenkins 上安装 Metrics 插件,使 Jenkins 暴露 metrics api。 配置 Zabbix server 及 agent 以实现监控及告警 为方便读者实验,笔者将自己做实验的代码上传到了 GitHub,链接在文章末尾。使用的是 Docker Compose 技术(方便一次性启动所有的系统)。
    接下来,我们详细介绍 Metrics插件及如何实现 Zabbix 监控 Jenkins。


    使 Jenkins 暴露 metrics api
    安装 Metrics 插件,在系统配置中,会多出“Metrics”的配置,如下图:

    pic2.jpg


    配置项不复杂。我们需要点击“Generate...”生成一个 Access Key(生成后,记得要保存)。这个 Key 用于身份校验,后面我们会用到。 保存后,我们在浏览器中输入URL:http://localhost:8080/metrics/<刚生成的 Access Key> 验证 Jenkins 是否已经暴露 metrics。如果看到如下图,就说明可以进行下一步了。


    Pic3.jpg


    1.1 Metrics 插件介绍

    Metrics 插件是基于 dropwizard/metrics 实现。它通过4个接口暴露指标数据:/metrics,/ping,/threads,/healthcheck。

    1.2 Metrics 插件:/metrics 接口介绍

    点击上图中的metric链接(http://localhost:8080/metrics//metrics),它暴露了以下指标数据:


    {
    version: "3.0.0",
    gauges: {...},
    counters: {...},
    histograms: {...},
    meters: {...},
    timers: {...}
    }


    复制代码
    从数据结构中可以看出它将指标分成 5 种数据类型: Gauges:某项指标的瞬时值,例如:当前 Jenkins executor 的总个数(jenkins.executor.count.value) Counters:某项指标的总数值,例如:http 请求活动连接数(http.activeRequests) Meters:一段时间内,某事件的发生概率,例如:Jenkins成功执行的任务每分钟的执行次数(jenkins.runs.success.m1_rate) Histogram:统计指标的分布情况。例如:Jenkins executor 数量的分布(jenkins.executor.count.history) Timer:某项指标的持续时间。例如:Jenkins 任务等待时间(jenkins.job.waiting.duration) 由于指标非常之多,我们就不分别介绍了。具体有哪些指标,读者朋友可以从代码仓库中的 metrics.json 文件了解。

    1.2 Metrics 插件其它接口介绍
    . /ping:接口返回 pong 代表 Jenkins 存活,如下图:


    Pic3.jpg


    /threads:返回 Jenkins 的线程信息 /healthcheck:返回以下指标:


    {
    disk-space: {
    healthy: true
    },
    plugins: {
    healthy: true,
    message: "No failed plugins"
    },
    temporary-space: {
    healthy: true
    },
    thread-deadlock: {
    healthy: true
    }
    }
    复制代码


    2. 配置 Zabbix server 与 agent 实现监控及告警

    Zabbix server 通过与 Zabbix agent 进行通信实现数据的采集。而 Zabbix agent 又分为被动和主动两种模式。我们使用的是被动模式,也就是Zabbix server 向 agent 索要数据。 所以,我们需要在 Zabbix agent 所在机器放一个获取 Jenkins 指标数据的脚本。再配置 Zabbix server 定时从该 agent 获取数据,最后配置触发器(trigger)实现告警。 接下来的关于 Zabbix 的配置,基于我的 jenkins-zabbix 实验环境,读者朋友需要根据自己的实际情况变更。

    2.1 配置 Zabbix server 如何从 agent 获取指标数据 首先,我们需要告诉 Zabbix server 要与哪些 Zabbix agent 通信。所以,第一步是创建主机,如下图:


    Pic5.jpg



    第二步,在主机列表中点击“Iterms”进行该主机的监控项设置:


    pic6.jpg




    第三步,进入创建监控项页面:


    Pic7.jpg




    第四步,创建监控项:


    pic8.jpg


    这里需要解释其中几个选项为什么要那样填:
    Type:是 Zabbix server 采集指标的类型,我们选择的是 Zabbix agent,如上文所说。
    Key:由于我们要监控的指标并不是 Zabbix 预定义的。所以,需要使用用户自定义参数来实现监控 Jenkins 指标。Key 填的值为:jenkins.metrics[gauges.jenkins.node.count.value.value]。jenkins.metrics是需要执行的真正的 Key 名称。而 [] 内是传给该 Key 对应的命令的参数。对于初学者,Zabbix 这部分概念非常不好理解。也许这样会更好理解:在使用用户自定义参数来实现监控的情况下,Zabbix server 会将这个 Key 发送给 agent,然后 agent 根据这个 Key 执行指定的 逻辑 以获取指标数据。这个 逻辑 通常是一段脚本(shell命令或Python脚本等)。而脚本也是可以传参的,[]中的值就是传给脚本的参数。具体更多细节,下文会继续介绍。

    Type of information:监控数据的数据类型,由于我们监控的是 Jenkins node 节点的个数,所以,使用数字整型。

    Update interval:指 Zabbix server 多长时间向 agent 获取一次数据,为方便实验,我们设置为 2s。 到此,Zabbix server 端已经配置完成。

    2.2 配置 Zabbix agent 使其有能力从 Jenkins 获取指标数据
    当 Zabbix agent 接收到 server 端的请求,如 jenkins.metrics[gauges.jenkins.node.count.value.value]。Zabbix agent 会读取自己的配置(agent 启动时会配置),配置内容如下:
    Zabbix Agent Configuration File for Jenkins Master
    UserParameter=jenkins.metrics[*], python /usr/lib/zabbix/externalscripts/jenkins.metrics.py $1 根据 Key 名称(jenkins.metrics)找到相应的命令,即:python /usr/lib/zabbix/externalscripts/jenkins.metrics.py $1。并执行它,同时将参数 gauges.jenkins.node.count.value.value 传入到脚本 jenkins.metrics.py 中。jenkins.metrics.py 需要我们在 Jenkins agent 启动前放到 /usr/lib/zabbix/externalscripts/ 目录下。 jenkins.metrics.py 的源码在 jenkins-zabbix 实验环境中可以找到,篇幅有限,这里就简单介绍一下其中的逻辑。 jenkins.metrics.py 所做的事情,无非就是从 Jenkins master 的 metrics api 获取指标数据。但是由于 api 返回的是 JSON 结构,并不是 Zabbix server 所需要的格式。所以,jenkins.metrics.py 还做了一件事情,就是将 JSON 数据进行扁平化,比如原来的数据为:{"gauges":{"jenkins.node.count.value": { "value": 1 }}} 扁平化后变成: gauges.jenkins.node.count.value.value=1。 如果 jenkins.metrics.py 脚本没有接收参数的执行,它将一次性返回所有的指标如:


    ......
    histograms.vm.memory.pools.Metaspace.used.window.15m.stddev=0.0
    histograms.vm.file.descriptor.ratio.x100.window.5m.p75=0.0
    histograms.vm.memory.pools.PS-Old-Gen.used.window.5m.count=4165
    gauges.vm.runnable.count.value=10
    timers.jenkins.task.waiting.duration.mean=0.0
    histograms.vm.memory.non-heap.committed.history.p99=123797504.0
    gauges.vm.memory.pools.PS-Eden-Space.used.value=19010928
    gauges.jenkins.node.count.value.value=1
    histograms.vm.memory.pools.Code-Cache.used.window.15m.mean=44375961.6


    复制代码
    ...... 但是,如果接收到具体参数,如 gauges.jenkins.node.count.value.value ,脚本只返回该参数的值。本例中,它将只返回 1。 jenkins.metrics.py 脚本之所以对 JSON 数据进行扁平化,是因为 Zabbix server 一次只拿一个指标的值(这点需要向熟悉 Zabbix 的人求证,笔者从文档中没有找到明确的说明)。 注意:在 2.1 节中,如果 Key 值设置为:jenkins.metrics,Zabbix server 不会拿 jenkins.metrics.py 返回的所有的指标值自动创建对应的监控项。所以,Key 值必须设置为类似于 jenkins.metrics[gauges.jenkins.node.count.value.value] 这样的值。

    3. 配置 Zabbix server 监控指标,并告警
    在经过 2.2 节的配置后,如果 Zabbix server 采集到数据,可通过_Monitoring -> Latest data -> Graph_菜单(如下图),看到图形化的报表:

    pic9.jpg



    图形化的报表:

    pic10.jpg



    有了指标数据就可以根据它进行告警了。告警在 Zabbix 中称为触发器(trigger)。如下图,我们创建了一个当 Jenkins node 小于 2 时,就触发告警的触发器:

    pic11.jpg


    至于最终触发器的后续行为是发邮件,还是发短信,属于细节部分,读者朋友可根据自己的情况进行设置。

    小结

    在理解了 Zabbix server 与 agent 之间的通信原理的前提下,使用 Zabbix 监控 Jenkins 是不难的。笔者认为难点在于自动化整个过程。上文中,我们创建主机和添加监控项的过程,是手工操作的。虽然 Zabbix 能通过自动发现主机,自动关联模板来自动化上述过程,但是创建”自动化发现主机“和”自动关联动作“依然是手工操作。这不符合”自动化一切“的”追求“。 最后,如果读者朋友不是历史包袱原因而选择 Zabbix,笔者在这里推荐 Prometheus,一款《Google 运维解密》推荐的开源监控系统。

    实操进阶 | Jenkins 和 Kubernetes 云上的神秘代理

    灵雀云 发表了文章 • 0 个评论 • 404 次浏览 • 2019-03-28 14:14 • 来自相关话题

    最近我们构建和部署服务的方式与原来相比简直突飞猛进,像那种笨拙的、单一的、用于构建单体式应用程序的方式已经是过去式了。我们努力了这么久,终于达到了现在的效果。现在的应用为了提供更好的拓展性和可维护性,都会去拆解成各种相互依赖小、解耦性强的微服务,这些服务有各自 ...查看全部
    最近我们构建和部署服务的方式与原来相比简直突飞猛进,像那种笨拙的、单一的、用于构建单体式应用程序的方式已经是过去式了。我们努力了这么久,终于达到了现在的效果。现在的应用为了提供更好的拓展性和可维护性,都会去拆解成各种相互依赖小、解耦性强的微服务,这些服务有各自的依赖和进度。如果想去构建服务,从一开始,就应该使用 CI/CD 的方式;当然,如果你走上了这条路, Jenkins 就是你的良师益友。

    如果你是做微服务,那让我们在开始之前先花些时间想一想。如果只在 Jenkins 上构建单体式应用程序,那你肯定每天都会运行很多 Jenkins job, 而且要不厌其烦地运行很多次。所以,我们应该好好想清楚如何来做出一些改变。其实只需要付出一些努力,Jenkins 就可以很好地解决这种事情。



    Jenkins 进阶之路



    作为 DevOps 从业者,我遇到的最大问题是如何管理并优化自己的 Jenkins agent 结构。如果只是实验性地用 Jenkins跑一些流水线,那根本不用考虑 agent 的事情。如果每天要跑成百上千条流水线的话,那怎么去做优化就是一件非常非常重要的事情。在 Jenkins 进阶之路中,我也尝试了各种不同的方式来寻找Jenkins agent 的最佳使用方式。如果你也和我一样经历过,那下面这些事情你一定会很熟悉。

    下面是我在这些年中使用 Jenkins 的各个阶段:
    1. 所有构建都在 master 节点上跑,在这个节点上运行所有的组件。(我给这个阶段起了个可爱的名字, Hello Jenkins)

    1. 创建一个 Jenkins EC2 代理,并且在这个代理上运行所有的构建, 就是大而全,这个节点什么都能做。如果需要同时做多条任务,那就把这个大而全的节点克隆一份。(这个阶段我命名为 Monster Agent.)

    1. 为每种服务创建不同的 Jenkins EC2 的节点(这个阶段叫Snowflake Agent.)

    1. 在容器中运行流水线的所有步骤。 比如,在 Jenkins 中使用 Docker Plugin 插件将代理挂载到容器中,或者使用 multi-stage Dockerfiles 把所有构建,测试打包的流程都封装起来。这两种方法都是很好的容器抽象化的开端,并且允许轻松地将制品从一个容器复制到另一个容器。当然了,每种方法都需要访问 Docker engine 。为了让我的 Jenkins 代理能够正常工作,现在我用以下几种方式来管理 docker host:
    * 在Jenkins 主容器中运行一个Docker engine - Docker in Docker (DinD);
    * 把主机上的 Docker socket 挂载到容器中,让容器能够以 sidecar 的方式运行;
    * 为 Jenkins 主服务器配置单个外部 EC2 Docker 主机,以用于在容器中启动构建;
    * 使用 EC2 插件和包含 Docker Engine 的 AMI 动态启动代理,然后运行多阶段 Dockerfile 中的所有步骤。
    以上这些阶段各有利弊,但都是为了让我们从管理 Jenkins 节点中解放出来。不过,最近我又进阶到了另外一个阶段:Jenkins on Kubernetes

    一旦在 Jenkins 中把构建节点和 job 都容器化了,迁移工作平台将变得十分简单易行。这里郑重声明一下,在使用这个方法前我一直没有接触过 Kubernetes。也就是说,在 Google Cloud Platform(GCP)GKE 中创建 Kubernetes 集群,使用 Helm Chart启动 Jenkins master ,并在 Kubernetes 集群中的 Jenkins 代理中运行构建是非常简单的。



    流水线脚本中启动 K8s 中的代理



    如何配置 Jenkins 才能使流水线脚本能够在 K8s 集群中启动 Jenkins 节点。首先要先安装Kubernetes plugin 插件。有意思的是,当我用 Helm chart 来安装Jenkins 时,安装好的 Jenkins 里面已经有了该插件。还有一个前提,启动的 Jenkins 节点要和 Jenkins master 在同一个 K8s 集群里。

    一旦在 K8s 中运行了 Jenkins master 节点,只需要简单配置几步,就能启动一个小构建。


    配置 Jenkins Master



    为了保证 Jenkins 能够访问 K8s 集群的资源,首先需要按照以下步骤创建一些凭据:
    1. 进入 Jenkins 的 UI 界面,点击左边导航栏里的凭据链接
    2. 点击 Stores scoped to Jenkins 列表下 global 中的 Add credentials (将鼠标悬停在链接旁边即可看到箭头)
    3. 点击添加凭证
    4. 写好 Kubernetes Service Account
    5. 将范围设置为全局
    6. 点击 OK 按钮
    这样 Jenkins 就可以使用这个凭据去访问 K8s 的资源。


    在 Jenkins Master 中配置云



    下一步就是在 Jenkins 中设置云的配置:
    1. 进入 Jenkins UI 界面,点击 系统管理 → 系统设置;
    2. 进入管理界面后查找 『云』(一般在下面),然后点击 『新增一个云』,选择 kubernetes 类型;
    3. 如下这些是必填的参数:
    Name 自定义, 默认是 kubernetes;
    Kubernetes URL https://kubernetes.default -一般是从 service account 自动配置;
    Kubernetes Namespace 一般是 default 除非要在一个特殊的命名空间 ,否则不要动;
    Credentials 选择上一步创建的凭据;
    Jenkins URL http://:8080;
    Jenkins tunnel :5555 - 用来和 Jenkins 启动的 agent 进行交互的端口;


    你看,只需要几个参数就能在 K8s 集群中启动一些节点了,当然你的环境需要的话也可以做一些其他的调整。

    现在你已经可以通过定义一些 pod 实现Jenkins master 访问 K8s 集群了。pod其实是 K8s 中的概念,在一个 pod 里面会有一个或者多个容器,它们共享网络还有存储,我们可以在这个 pod 中执行一些构建工作。每一个 Jenkins 节点都是作为 K8s pod 来启动的。这个 pod 里面经常会包含一个默认的 JNLP 容器,还有一些pod 模板中定义的容器。现在有至少两种方法来定义pod template。



    通过 Jenkins UI 配置一个 pod template



    1. Manage Jenkins → Configure Systems;
    2. 找到之前配置 Jenkins K8s 的地方;
    3. 点击 Add Pod Template button 选择 Kubernetes Pod Template;
    4. 输入下面的值:
    Name 自定义;
    Namespace default -除非想换个在上一步自定义的命名空间;
    Labels 自定义 - 这个将用来匹配你在 jenkinsfile 中的 label 值;
    Usage 如果想让这个 pod 作为默认节点的话,就选择 "Use this node as much as possible", 如果选择 "Only build jobs with label matching expressions matching this node" 的话 那就是只有在 Jenkins 脚本中定义的label匹配的构建才能使用这个节点;
    The name of the pod template to inherit from 这个可以置空,现在还用不到;
    Containers 在 pod 中启动的容器,下面会有详细介绍;
    EnvVars 在 pod 中注入的环境变量;
    Volumes 在 pod 中挂载的任何一种卷;



    需要记住,在一个 pod 中会有不止一个容器,它们都是共存的。如果你是用 Helm chart 安装 Jenkins 的话,pod 中就会包含 JNLP 这个容器,这个容器也是 Jenkins agent 中必须包含的。为了完成更多服务的构建,还需要添加一些其他工具链的容器。



    添加容器模板



    1. 进入 Jenkins UI 界面,回到上一步创建 pod template ;
    2. 点击 Add Container 按钮, 选择 Container Template;
    3. 输入下面的值:
    Name 自定义;
    Docker image 根据需求来写,比如在构建一个go 写的应用时,可以输入 golang:1.11-alpine3.8;
    Label 表明要用在流水线脚本中引用此容器模板的标签字符串;
    Always pull image 如果想让 pod 启动的时候都去拉取镜像就选择这个;



    你可以保留其他参数的默认值,但是可以看到该插件可以对 pod 以及在其中运行的各个容器进行详细控制。你可以通过此插件设置在 Kubernetes pod 配置中的任何值。你还可以通过输入原始 YAML 来注入配置数据。无需因选项过多而分心,选择配置它们中的一小部分就可以获得工作环境。

    单击容器模板中的“添加环境变量”按钮,将环境变量注入特定容器,也可以单击模板中的“添加环境变量”按钮,将环境变量注入所有的容器。

    以下环境变量会自动注入默认的 JNLP 容器,来保障它能自动连接到 Jenkins 主服务器:

    * `JENKINS_URL`: Jenkins 网页界面网址
    * `JENKINS_JNLP_URL`: Jenkins 特定 slave 中 jnlp 的 url
    * `JENKINS_SECRET`: 身份验证的密钥
    * `JENKINS_NAME`: Jenkins 代理的名称

    如果单击“添加卷”按钮,将看到几个用于添加卷的选项,这里使用 Host Path Volume 选项将 docker socket 安装在 pod 中。然后,可以运行安装了 Docker 客户端的容器,并且来构建和推送 Docker 镜像。

    此时,我们为 Kubernetes 集群创建了一个云配置,并定义了一个由一个或多个容器组成的 pod。现在如何使用它来运行 Jenkins 工作?

    很简单,只需要我们在 Jenkins 流水线脚本中通过标签引用 pod 和容器就可以了。
    本文中的示例是使用脚本流水线,当然您可以使用声明式流水线语法实现相同的结果:




    node('test-pod') {
    stage('Checkout') {
    checkout scm
    }
    stage('Build'){
    container('go-agent') {
    // This is where we build our code.
    }
    }
    }









    用 jenkinsfile 来实现相同的功能



    通过 UI 配置插件现在看起来很不错。但有一个明显的问题,配置不能像源代码一样进行版本控制和存储。幸运的是,您可以直接在 Jenkinsfile 中创建整个 pod 定义。哈哈,在 Jenkinsfile 中有什么不能做的呢?

    可以将 UI 或 YAML 定义中可用的任何配置参数添加到 `podTemplate` 和`containerTemplate` 部分。

    在下面的示例中,我已经定义了一个包含两个容器模板的 pod。

    pod 标签将会用于节点,表示想要启动此 pod 实例。

    直接在节点内定义但没有在容器块中定义的任何步骤,都可以在默认的 JNLP 容器中运行。

    容器块用于表示该容器块内的步骤应在具有给定标签的容器内运行。我已经定义了一个标签为 `golang` 的容器模板,我将用它来构建 Go 可执行文件,最终将其打包成 Docker 镜像。在 `volumes` 中,已经指出想要挂载主机的 Docker 套接字,但仍然需要 Docker 客户端使用 Docker API 与它进行交互。因此,已经定义了一个标签为 `docker` 的容器模板,该模板使用安装了 Docker 客户端的镜像。



    podTemplate(
    name: 'test-pod',
    label: 'test-pod',
    containers: [
    containerTemplate(name: 'golang', image: 'golang:1.9.4-alpine3.7'),
    containerTemplate(name: 'docker', image:'trion/jenkins-docker-client'),
    ],
    volumes: [
    hostPathVolume(mountPath: '/var/run/docker.sock',
    hostPath: '/var/run/docker.sock',
    ],
    {
    //node = the pod label
    node('test-pod'){
    //container = the container label
    stage('Build'){
    container('golang'){
    // This is where we build our code.
    }
    }
    stage('Build Docker Image'){
    container(‘docker’){
    // This is where we build the Docker image
    }
    }
    }
    })


    在基于 Docker 的流水线脚本中,我构建了 Docker 镜像并将其推送到 Docker 仓库,对我来说,能够复制这些配置信息非常重要。完成后,我已准备好使用`gcloud`(Google Cloud SDK)构建镜像,并将该镜像推送到 Google Container Registry,以便部署到 K8s 群集。

    使用 gcloud 镜像指定了一个容器模板,并将 docker 命令更改为 gcloud 命令。、

    就这么简单!


    podTemplate(
    name: 'test-pod',
    label: 'test-pod',
    containers: [
    containerTemplate(name: 'golang', image: 'golang:1.9.4-alpine3.7'),
    containerTemplate(name: 'gcloud', image:'gcr.io/cloud-builders/gcloud'),
    ],
    {
    //node = the pod label
    node('test-pod'){
    //container = the container label
    stage('Build'){
    container('golang'){
    // This is where we build our code.
    }
    }
    stage('Build Docker Image'){
    container(‘gcloud’){
    //This is where we build and push our Docker image.
    }
    }
    }
    })



    在 上运行 Jenkins master、Jenkins 代理,构建和部署示例应用程序其实只花了几个小时。但之后,我花了一个周末的时间才深入了解该平台。如果你学得够快,相信你在几天内就可以完全掌握并且灵活运用这个平台了。

    Electron 应用的流水线设计

    灵雀云 发表了文章 • 0 个评论 • 526 次浏览 • 2019-03-18 13:57 • 来自相关话题

    面向读者:需要了解 Jenkins 流水线的基本语法。 Electron 是由 Github 开发,用 HTML,CSS 和 JavaScript 来构建跨平台桌面应用程序的一个开源库。 本文将介绍 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平台下构建”的阶段中(www.alauda.cn), agent{label"mac && nodejs"} 指定了只有 label 同时包括了 mac 和 nodejs 的 agent 才能执行构建。
    多平台的构建应该是并行的,以提升流水线的效率。我们通过 parallel 指令实现。
    另外,默认 Electron 应用使用的三段式版本号设计,即 Major.Minor.Patch。但是笔者认为三段式的版本号信息还不够追踪应用与构建之间的关系。笔者希望版本号能反应出构建号和源代码的 commit id。函数 generateVersion 用于生成此类版本号。生成的版本号,看起来类似这样: 1.0.0-f7b06d0.28。
    完整源码地址:https://github.com/zacker330/electronjs-pipeline-demo

    小结

    上例中,Electron 应用的流水线设计思路,不只是针对 Electron 应用,所有的跨平台应用的流水线都可以参考此思路进行设计。设计思路大概如下:
    多平台构建并行化。本文只有操作系统的类型这个维度进行了说明。现实中,还需要考虑其它维度,如系统位数(32位、64位)、各操作系统下的各版本。
    各平台下的构建只做一次编译打包。并将制品上传到制品库,以方便后续步骤或阶段使用。
    全局变量与平台相关变量进行分离。
    最后,希望能给读者带来一些启发。

    批量修改 Jenkins 任务的技巧

    灵雀云 发表了文章 • 0 个评论 • 771 次浏览 • 2019-03-04 14:45 • 来自相关话题

    通过脚本命令行批量修改 Jenkins 任务 最近,笔者所在团队的 Jenkins 所在的服务器经常报硬盘空间不足。经查发现很多任务没有设置“丢弃旧的构建”。通知所有的团队检查自己的 Jenkins 任务有没有设置丢弃旧的构建,有些不 ...查看全部
    通过脚本命令行批量修改 Jenkins 任务

    最近,笔者所在团队的 Jenkins 所在的服务器经常报硬盘空间不足。经查发现很多任务没有设置“丢弃旧的构建”。通知所有的团队检查自己的 Jenkins 任务有没有设置丢弃旧的构建,有些不现实。
    一开始想到的是使用 Jenkins的 API 来实现批量修改所有的 Jenkins 任务。笔者对这个解决方案不满意,经 Google 发现有同学和我遇到了同样的问题。他使用的更“技巧”的方式:在 Jenkins 脚本命令行中,通过执行 Groovy 代码操作 Jenkins 任务。
    总的来说,就两步:
    进入菜单:系统管理 --> 脚本命令行
    在输入框中,粘贴如下代码:


    import jenkins.model.Jenkins
    import hudson.model.Job
    import jenkins.model.BuildDiscarderProperty
    import hudson.tasks.LogRotator
    // 遍历所有的任务
    Jenkins.instance.allItems(Job).each { job ->

    if ( job.isBuildable() && job.supportsLogRotator() && job.getProperty(BuildDiscarderProperty) == null) {
    println " \"${job.fullDisplayName}\" 处理中"

    job.addProperty(new BuildDiscarderProperty(new LogRotator (2, 10, 2, 10)))
    println "$job.name 已更新"
    }
    }
    return;

    /**

    LogRotator构造参数分别为:
    daysToKeep: If not -1, history is only kept up to this days.
    numToKeep: If not -1, only this number of build logs are kept.
    artifactDaysToKeep: If not -1 nor null, artifacts are only kept up to this days.
    artifactNumToKeep: If not -1 nor null, only this number of builds have their artifacts kept.
    **/


    脚本

    脚本命令行介绍
    脚本命令行(Jenkins Script Console),它是 Jenkins 的一个特性,允许你在 Jenkins master 和 Jenkins agent 的运行时环境执行任意的 Groovy 脚本。这意味着,我们可以在脚本命令行中做任何的事情,包括关闭 Jenkins,执行操作系统命令 rm -rf /(所以不能使用 root 用户运行 Jenkins agent)等危险操作。
    除了上文中的,使用界面来执行 Groovy 脚本,还可以通过 Jenkins HTTP API:/script执行。具体操作,请参考 官方文档。

    问题:代码执行完成后,对任务的修改有没有被持久化?

    当我们代码job.addProperty(new BuildDiscarderProperty(new LogRotator (2, 10, 2, 10)))执行后,这个修改到底有没有持久化到文件系统中呢(Jenkins 的所有配置默认都持久化在文件系统中)?我们看下 hudson.model.Job 的源码,在addProperty方法背后是有进行持久化的:



    public void addProperty(JobProperty jobProp) throws IOException {
    ((JobProperty)jobProp).setOwner(this);
    properties.add(jobProp);
    save();
    }


    小结

    本文章只介绍了批量修改“丢弃旧的构建”的配置,如果还希望修改其它配置,可以参考 hudson.model.Job 源码。
    不得不提醒读者朋友,Jenkins 脚本命令行是一把双刃剑,大家操作前,请考虑清楚影响范围。如果有必要,请提前做好备份。