GitOps-实践[helm jhipster-registry jenkins]

GitOps理念也是最近比较火的话题。

什么是GitOps

GitOps 是一种快速、安全的方法,可供开发或运维人员维护和更新运行在 Kubernetes 或其他声明式编排框架中的复杂应用。

GitOps的四项基本原则

  1. 通过声明的方式描述系统。类似于现在推荐的脚本化pipeline,terraform等,集中在代码里声明所需要的资源,而不是通过页面的方式进行配置。
  2. 系统的目标状态通过git版本控制。版本可追溯,可回滚。
  3. 对目标状态改变批准后自动应用到系统中。自动化程度高,一键应用。
  4. 持续比较环境中的状态和代码版本里的状态,触发告警,k8s的自愈能力也将得到应用。

GitOps的好处

  1. 开发者可以使用熟悉的工具 Git 去发布新功能,而无需了解复杂的部署交付流程,提升生产力。
  2. 使用 Git 工作流管理集群,使得所有变更需要review,这样满足合规性需求,提升系统的安全与稳定性。
  3. 借助 Git 的还原(revert)、分叉(fork)功能,可以实现稳定且可重现的回滚,系统更加可靠。
  4. 由于 GitOps 可以为infra、application、Kubernetes 插件的部署变更提供了统一的模型yaml,因此我们可以在整个组织中实现一致的E2E工作流。CICD和Ops工作都可以通过Git来实现,一致性和规范性程度化高。
  5. 可以借助 Git 内置的安全特性。

本文主要描述运用GitOps理念,通过check in code的方式,使得生产jenkins能够adopt新的change。
主要流程如下:

屏幕快照 2020-01-11 19.39.29.png

本文中几个重要的角色:Helm,jhipster-registry,Git,jenkins。

1. Jhipster-registry(配置中心)

https://github.com/jhipster/jhipster-registry

JHipster-registry具有三个主要功能:

  • 它是一个Eureka服务,用作应用程序的发现服务。这就是JHipster处理所有应用程序的路由,负载均衡和可伸缩性的方式。
  • 它是一个Spring Cloud配置服务,为所有应用程序运行时提供配置。
  • 它还是一台管理服务器,具有用于监视和管理应用程序的仪表板。

从官网上download下来源码,参考README文档启动。

屏幕快照 2020-01-12 13.57.02.png
屏幕快照 2020-01-12 13.58.32.png

JHipster Registry是Spring Config Server:启动应用程序时,它们将首先连接到JHipster Registry以获取其配置。网关和微服务都是如此。

此配置是Spring Boot配置,就像在JHipsterapplication-*.yml文件中找到的配置一样,但是它存储在中央服务器中,因此更易于管理。

启动时,网关和微服务应用程序将查询Registry的配置服务器,并用在那里定义的属性覆盖其本地属性。

我们在central-config中新增application-jaymz.yml

屏幕快照 2020-01-12 14.15.37.png

那么我们访问localhost:8761的时候,输入默认的admin:admin, 进入config页面,在profile里面输入jaymz,就能得到我们刚刚配置的信息。


屏幕快照 2020-01-12 14.14.19.png

这样配置有什么好处呢?

  1. 配置信息集中管理。将散落在各个系统中的配置信息统一放置在配置中心里。任何有权限访问的人都能得到全景图。
  2. 版本控制。针对不同的用处(系统环境/用户),可以声明几份配置yml文件,根据profile来进行区分配置yml的使用场景。

2. Helm

helm的作用想必大家也知道,Helm是把Kubernetes资源(比如deployments、services或 ingress等) 打包到一个chart中,而chart被保存到chart仓库。可以通过chart仓库可用来存储和分享chart。Helm使发布可配置,支持发布应用配置的版本管理,简化了Kubernetes部署应用的版本控制、打包、发布、删除、更新等操作。我们可以在下面的路径中找到需要的服务chart,比如:jenkins,sonarqube,grafana等。
https://github.com/helm/charts/tree/master/stable
我们通过helm的方式获取jenkins chart,然后在k8s环境中启动该服务。
chart.yaml:

url: https://github.com/helm/charts/tree/master/stable
chart: jenkins

values.yaml文件可以根据https://github.com/helm/charts/tree/master/stable/jenkins进行自定义设置。比如设置它的代理,初始化安装的插件等。
chart(服务模版)有了,values(实例配置)也有了。通过helm命令,helm install chart即可。如果我们想在jenkins实例中做些改变,比如升级插件,增加配置等,我们通常的做法就是helm upgrade。或者删除老的,重新安装新的。
以上是手动做法。
手动能够实现,那么我们是不是可以寻求自动化的方式,达到这样的效果:只要jenkins的values.yaml文件有变动,一旦代码check in,就会直接应用到instance里。逻辑:jenkins job 扫描整个repo,发现如果有新的service加入,那么会进入该folder下读取chart和values文件,再使用helm add repo,helm install的方式将该服务启动在k8s集群中。如果检测的服务已经安装过,那么就先删除老得服务,再和新的安装步骤一样,将服务安装/升级进来。或许你会问服务删除了,数据不就丢失了吗?因此我们又多加了一项pvc的处理,将服务的数据持久化到硬盘上。服务是根据namespace来隔离的。namespace的名字则是由用户定义的folder名字。当我们删除资源的时候,直接删除整个namespace,确保资源清理干净。文件结构如下:
folder/chart.yaml,folder/values.yaml。
扫描逻辑示例如下:

import java.time.ZonedDateTime
def kubeconfigFileId='kubeconfig-gitops-devops'
def proxy='http://jaymz.com:8080'
timestamps {
    podTemplate(
        containers: [
        containerTemplate(name: 'jnlp',
            image: 'localhost:5000/jenkins/jnlp-slave:3.35-5',
            args: '${computer.jnlpmac} ${computer.name}'),
        containerTemplate(name: 'storage',
            image: 'localhost:5000/ubuntu:latest',
            ttyEnabled: true, command: 'cat',runAsUser: '1000', runAsGroup: '1000'),
        ],
        volumes: [          
            nfsVolume(mountPath: '/etc/nfs', serverAddress: 'localhost', serverPath: '/root/jaymz/'),   
        ],
        ) {
        node(POD_LABEL) {
            checkout scm
            withCredentials([kubeconfigFile(credentialsId: kubeconfigFileId, variable: 'KUBECONFIG')]){
                def parallel_services
                stage('find service(s) to install'){
                    parallel_services=findFiles(glob: '*/chart.yaml')
                    .collect {it.path.substring(0, it.path.indexOf('/'))}
                    .findAll {
                        // 判断是否需要安装服务,可通过git log,helm status来获取集群中该服务的deploy时间
                        echo "Install ${release}: ${should_install}"
                        return should_install;
                    }
                    .collectEntries {
                        def release=it
                        def chart_yaml=readYaml file: "${release}/chart.yaml"
                        def repo=chart_yaml.repo
                        def repo_url=chart_yaml.url
                        def chart=chart_yaml.chart
                        return [release, {
                            touch file: "${release}/values.yaml"
                            withEnv(["RELEASE=${release}", "REPO=${repo}", "REPO_URL=${repo_url}","CHART=${chart}",
                                "https_proxy=${proxy}","STORAGE_ROOT=/etc/nfs"
                                ]){
                                stage("storage"){                                   
                                //为每个服务创建对应的文件夹
                                }
                                stage("prepare namespace"){
                                    kubernetesDeploy(kubeconfigId: kubeconfigFileId,       
                                        configs: "namespace.yaml", 
                                        enableConfigSubstitution: true
                                    )
                                }
                                stage("install ${release}"){
                                    container('helm'){
                                        def kube_templates = findFiles(glob: "${RELEASE}/templates/*.yaml") 
                                        if (kube_templates.size()>0) {
                                            kubernetesDeploy(kubeconfigId: kubeconfigFileId,               // REQUIRED
                                                configs: "${RELEASE}/templates/*.yaml", // REQUIRED
                                                enableConfigSubstitution: true
                                            )
                                        }
                                        if (repo){
                                            sh '''
                                            helm delete ${RELEASE} -n ${RELEASE} || true
                                            helm repo add  ${REPO} ${REPO_URL}
                                            helm upgrade  ${RELEASE} ${REPO}/${CHART} -f ${RELEASE}/values.yaml -n ${RELEASE} -i --force --kubeconfig ${KUBECONFIG}
                                            '''
                                        } else {
                                            sh '''
                                            helm delete ${RELEASE} -n ${RELEASE} || true
                                            helm upgrade  ${RELEASE} ${CHART} --repo ${REPO_URL} -f ${RELEASE}/values.yaml -n ${RELEASE} -i --force --kubeconfig ${KUBECONFIG}
                                            '''
                                        }
                                    }
                                }

                            }
                        }

                        ]
                    }
                }
                stage('install services'){
                    parallel parallel_services
                }
            }
        }
    }
}

写到这里我们基本上实现了大图中左边循环。那么接下来来探索右边循环。

每个项目肯定都会有自己的configuration,我们不可能把所有的配置都放置在一个repo中,这违反了工程代码的就近原则,而且维护起来也不是特别的方便。那么如何做到配置在不同的repo,但是运行时传入到配置中心中呢?

屏幕快照 2020-01-12 16.06.01.png

如上图所示,我们可以在jhipster-registry中的application.yml中新增以上配置,新的repo:https://jaymz.com/ops/springboot-jaymz.git则是我项目的仓库。在该仓库中我们把配置信息存在路径为config的文件夹下,通过“searchPaths来查找”,config文件夹里的文件配置和配置中心的规则一样application-profile.yaml, 这样我们就把项目的配置信息发送给了配置中心了。
那么当配置中心中被提交了一份新的jenkins 配置。用户希望根据这个新的配置生成一套属于该用户的jobs,怎么实现?
首先我们要编写seed job。这个job可以帮助我们在jenkins中生成其他的一系列的job(比如:重置机器,部署数据库,部署环境等)。
seed job的思路是,传入用户配置的profile,application,configserver url的值,seed job扫描整个repo下存在的Jenkinsfile,根据配置yaml文件中定义的key value值,传入到Jenkinsfile中,并在jenkins里生成对应的job。

屏幕快照 2020-01-12 16.13.18.png

那么当我们触发测试job的时候,该job会首先读取配置中心的数据,然后根据读取的值进行处理相应的任务。目前应用到场景如下:根据不同的profile,指定不同的收件人,部署不同的环境,运行不同的autoamtion cases。
seed job生成其他job主要用到了jobDsl。并把配置中心的相关参数传入到每个Jenkinsfile中。

屏幕快照 2020-01-12 16.19.12.png

然后Jenkinsfile通过httpRequest的方式去获取配置信息。

        def application = env.application
        def url = "${env.configserver}/config/${env.branch}/${application}-${env.profile}.${format}"
        println("config server url $url")
        httpRequest ignoreSslErrors: true, quiet: true, url: url, authentication: configserver_credentials, outputFile: "config-server.yml"

综上,我们通过helm(管理服务instance),Jhipster-registry(配置中心),jenkins(jobs)来实现了GitOps。一键提交代码,就像多米诺骨牌一样,引起了一连串可控的变化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容