前言

为了体验CI/CD实现过程并深入理解CI、CD流水线上的各组件的作用;

我自己搭建了1个GitLab+Jenkins+maven;

一、GitLab实现CI

GitLab服务器用于存储代码资源,可以实现代码的持续集成(CI),可以借助NFS、Ceph等存储系统,对数据进行高可用存储;

1.安装GitLab依赖

安装GitLab依赖,并设置开机启动

yum -y install curl policycoreutils openssh-server openssh-clents libsemanage-static libsemanage-devel
yum install curlpolicycoreutils openssh-server openssh-clients
systemctl enablesshd
systemctl enable sshd
systemctl start sshd
yum install postfix
systemctl enable postfix
systemctl start postfix
firewall-cmd --permanent --add-service=http
systemctl reload firewalld

2.安装GitLab服务

2.1.yum仓库配置

配置yum仓库

[root@hecs-83208 ~]# curl -fsSL https://packages.gitlab.cn/repository/raw/scripts/setup.sh | /bin/bash
==> Detected OS centos
==> Add yum repo file to /etc/yum.repos.d/gitlab-jh.repo

[gitlab-jh]
name=JiHu GitLab
baseurl=https://packages.gitlab.cn/repository/el/$releasever/
gpgcheck=0
gpgkey=https://packages.gitlab.cn/repository/raw/gpg/public.gpg.key
priority=1
enabled=1
==> Generate yum cache for gitlab-jh
==> Successfully added gitlab-jh repo. To install JiHu GitLab, run "sudo yum/dnf install gitlab-jh".

 2.2.安装GitLab

EXTERNAL_URL="http://114.115.128.169" yum -y install gitlab-jh

3.配置GitLab服务

3.1.访问URL设置

vim /etc/gitlab/gitlab.rb

EXTERNAL_URL="http://114.115.128.169" 

3.2.存储路径设置

vim /etc/gitlab/gitlab.rb

   "default" => {
     "path" => "/data/git-data",
     "failure_count_threshold" => 10,
     "failure_wait_time" => 30,
     "failure_reset_time" => 1800,
     "failure_timeout" => 30
    }
 })

3.3.GitLab初始化密码

查看GitLab的初始化密码

[root@hecs-83208 gitlab]# pwd
/etc/gitlab
[root@hecs-83208 gitlab]# cat initial_root_password 

3.4.设置root用户密码

设置新的root密码

4.管理GitLab服务

gitlab-ctl start #启动所有gitlab组件
gitlab-ctl stop  #停止所有gitlab组件
gitlab-ctl restart #重启所有gitlab组件
gitlab-ctl  status #查看所有gitlab组件的运行状态
gitlab-ctl reconfigure #刷新gitlab配置通常和gitlab-ctl restart组合使用,否则配置不生效

5.使用GitLab

5.1.GitLab账户添加SSHKey

$ cd ~/.ssh/
$ git config --global user.name "root"
$ git config --global user.email "root@le.com"
$ ssh-keygen -t rsa -C "root@le.com"
$ ls
id_rsa  id_rsa.pub  known_hosts
$ cat id_rsa.pub
$ git clone git@114.115.128.169:root/java-project.git
Cloning into 'java-project'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

GitLab

6.GitLab设置系统钩子

当GitLab的某1个仓库发生Push、Merge..事件时,调用Jenkins的WebHool,触发Jenkins的自动构建;

6.1.GitLab添加系统钩子

http://192.168.56.20:8081/buildByToken/build?job=build-maven-java-project&token=123.com

6.2.GitLab允许出站请求

只有GitLab允许出站请求之后,GitLab才可以去请求Jenkins的WebHook;

6.3.测试系统钩子的可用性

GitLab会向Jenkins的WebHook发起HTTP请求;

7.Python调用GitlabAPI

使用Python调用GitLab的API

from public import RecordLoggre
import gitlab


class GitlabApi:
    def __init__(self):
        self.GitlabUrl = 'https://git.apuscn.com:8443'
        # jenkins用户
        private_token = '2icW6gSLdrX9ZGoPLAEs'
        self.ApiVersion = '4'
        self.GitClent = gitlab.Gitlab(self.GitlabUrl, private_token=private_token)
        # 记录日志
        self.record_log = RecordLoggre()


class ProjectApi(GitlabApi):

    def __init__(self, project_url):
        super(ProjectApi, self).__init__()
        self.project_url = project_url
        self.project = self.get_project()

    def get_project(self):
        """通过项目url获取项目实例"""
        project_name = self.project_url.split('/')[-1].split('.')[0]
        project_list = self.GitClent.projects.list(search=project_name)
        fit_fileter = []
        for item in project_list:
            if (self.project_url == item.ssh_url_to_repo) or (
                    self.project_url == item.http_url_to_repo) or (
                    self.project_url == item.web_url) or (
                    f'git@{self.project_url}' == item.ssh_url_to_repo):
                fit_fileter.append(item)

        if len(fit_fileter) == 1:
            return fit_fileter[0]
        else:
            self.record_log.error(f'获取gitlab项目数量为{len(fit_fileter)},{str(fit_fileter)}')
            return None

    def get_branch(self, branch):
        """获取分支信息"""
        branch_data = {}
        try:
            branch_data = self.project.branches.get(branch)
        except Exception as Error:
            self.record_log.error(str(Error), exc_info=True)
        return branch_data

    def get_tag(self, tag):
        """获取标签信息"""
        tag_data = {}
        try:
            tag_data = self.project.tags.get(id=tag)
        except Exception as Error:
            self.record_log.error(str(Error), exc_info=True)
        return tag_data

    def get_version(self, version):
        """通过version获取分支或tag的信息"""

        try:
            data = self.project.branches.get(version)
        except Exception:
            try:
                data = self.project.tags.get(id=version)
            except Exception:
                data = None
        return data

    def list_branch(self):
        """列出项目的所有分支"""
        try:
            branch_list = self.project.branches.list(per_page=1000)
            for item in branch_list:
                yield item
        except Exception as Error:
            raise Exception(f'{self.project_url}获取分支失败,{str(Error)}')

    def list_tags(self):
        """列出项目的所有标签"""
        try:
            branch_list = self.project.tags.list(per_page=1000)
            for item in branch_list:
                yield item
        except Exception as Error:
            raise Exception(f'{self.project_url}获取标签失败,{str(Error)}')

    def find_merge_branch(self, tag):
        """查找tag从那个分支合并来的代码"""
        branch_commit_id = []
        try:
            tag_obj = self.get_tag(tag)
            parent_ids = tag_obj.commit.get('parent_ids')
            for item in self.list_branch():
                commit_id = item.commit.get('id')
                branch_name = item.name
                if commit_id in parent_ids:
                    branch_commit_id.append((commit_id, branch_name))
        except Exception as Error:
            self.record_log.error(f'{str(Error)}', exc_info=True)
        else:
            if len(branch_commit_id) == 1:
                return branch_commit_id[0]
            else:
                raise Exception(f'{self.project_url}获取合并前的分支失败,{str(branch_commit_id)}')


if __name__ == '__main__':
    a = ProjectApi('git@git.apuscn.com:sa/ci-cd.git')
    print(a.find_merge_branch('v2.0.2'))
    # a.project.commits.get('e3d5a71b')
    # print(a.get_version('dev'))
    # print(dir(branch_data))
    # print(branch_data.commit.get('id'))
Gitlab.py

 

二、Jenkins自动构建Maven项目

Jenkins支持master/agent模式,agent听从Master的任务调度,分摊Matser之上的的CICD工作量,使持续继承和部署的效率更高

Jekins不能直接去Gitlab拉取代码,需要在Jekins上配置Gitlab的秘钥。

1.Jenkins服务器准备

Jenkins可以通过安装采用插件的方式,完成对Python、Java等不同类型的项目的CI、CD;

1.1.启动Jenkins

java -Dhudson.model.DownloadService.noSignatureCheck=true -jar jenkins.war --httpPort=8081

1.2.安装JDK1.8

yum -y install java-1.8.0-openjdk* 

1.3.下载所需插件

Jenkins的插件的文件后缀名有两种格式 .jpi和.hpi,本质是被编译打包压缩之后的.clss文件;

jpi中的j就是Jenkins , hpi中的h是Hudson,Jenkins项目的前身是Hudson。

下载这些Jenkins插件时如果国外的源网络可以通,尽量使用国外的源;

每1个版本的Jenkins对插件的版本要求不一样,国内的插件库收罗的插件版本有限,会导致Jenkins安装Plugin安装失败的现象;

Maven-Integration插件

Publish Over SSH插件

Build Authorization Token Root插件

允许GitLab或者其他第三方程序免登录,通过Token参数调用Jenkins的WebHook;

buildByToken/build?job=NAME&token=SECRET

http://192.168.56.20:8081/buildByToken/build?job=build-maven-java-project&token=123.com

1.4.安装Maven和Git

Jenkins会同过已安装的插件,调用本机安装的Git和Maven应用,完成代码的拉取,并通过Maven编译拉取到的Java代码;

1.4.1.安装Git

yum -i install git

1.4.2.安装Maven

配置仓库路径和阿里镜像源,不在赘述

1.5.构建触发器

构建触发器是可以被外部程序调用的触发器,一旦触发器被触发就Jenkins就可以开始自动构建项目;

Jenkins支持以下几种方式进行自动构建

A、快照依赖构建/Build whenever a SNAPSHOT dependency is built
     当依赖的快照被构建时执行本job
B、触发远程构建(例如,使用脚本)。远程调用本job的restapi时执行本jobjob依赖构建/Build after other projects are built
    当依赖的job被构建时执行本job
C、定时构建/Build periodically。使用cron表达式定时构建本iob
D、向GitHub提交代码时触发Jenkins自动构建/GitHub hook trigger for GITScm pollingo Github-WebHook出发时构建本iob
E、定期检查代码变更/PollSCM
使用cron表达式定时检查代码变更,变更后构建本iob!0

1.5.1.webHook

通常由GitLab触发,一旦该URL被访问,Jenkins就会自动构建;

http://192.168.56.20:8081/job/build-maven-java-project/build?token=123.com

2.Jenkins的Iteam配置

以下是使用Jenkins打包编译Maven项目的配置过程;

2.1.Maven配置

2.2.Git配置

2.3.指定分支

设置Jenkins去GitLab哪1个仓库的哪1个分支去拉取代码;

2.4.指定项目的根pom文件

设置Maven打包时去项目的什么路径下找到.pom文件,每次打包之前一定清理maven缓存,否则不更新

Maven的Assembly插件的主要作用是:允许用户将项目输出与它的依赖项、模块、站点文档、和其他文件一起组装成1个可分发的归档文件(tar/zip)。

在Java项目Pom文件中指定源码打包之后是归档为jar/war/tar包,也指定了target目录;

Maven仅仅是通过读取Pom文件内容进行项目打包的打包工具。

 /var/lib/jenkins/tools/maven/bin/mvn
-B
-f /var/lib/jenkins/workspace/common-service/sg-2/xxl-job-admin__runner/pom.xml
clean install -Dmaven.test.skip=true
-Pprod

实际执行的命令如所示。

2.5.Pre Step设置

2.5.1./root/clean.sh脚本开发

如果clean.sh的语法正确,但执行老报错,原因是脚本从Windos平台Copy到Unix平台中,Shell脚本的格式发生了肉眼不可见的变化;

直接使用Vim编辑即可!

#!/bin/bash
echo "cenling........"
#删除历史数据
rm -rf ./jar
#获取传入的参数
appName=$1
echo "参数是$1"
#获取正在运行的jar包的Pid
pid=`ps -ef | grep $1 | grep 'java -jar' | awk '{printf $2}' `
echo $pid

#如果Pid为空提示
if [ -z $pid ];
then
  echo "$appName not started"
else
  kill -9 $pid && echo "$appName stoping"
fi
clean.sh

2.5.2.设置执行/root/clean.sh脚本

2.6.配置部署服务器

把Jenkins服务器调用Maven编译打包之后,Jar包应该传输到哪1台服务器上去部署?

在Configure System菜单下

 

2.7.Post Step设置

当Jar包传输到部署服务器之后就是启动jar包!

启动jar包不能使用 java -jar直接启动,因为该命令没有正确退出,会延迟Jenkins的构建时间;

nohup java -jar /root/jar/CI-CD*.jar --server.port=8001 > mylog.log  2>&1 &

2.8.构建成功效果

切记观察Transferred  file的数量,确定Jar包传输到了部署服务器上;

3.Python调用JenkinAPI

python-jenkins==1.7.0

在Python使用python-jenkins这个第三方包,来实现Python和Jenkins的交互。

from public.utils import record_loggre
from config import OpsConfig
from public.hash_ring import HashRing
import jenkins
import time


class JenkinsApi:

    def __init__(self):
        self.jenkins_server = OpsConfig.Jenkins.server
        self.user_name = OpsConfig.Jenkins.user
        self.password = OpsConfig.Jenkins.password
        self.server_list = self.jenkins_server.split(',')
        # 记录日志
        self.record_log = record_loggre()

    def select_server(self, key):
        ring = HashRing(self.server_list)
        return jenkins.Jenkins(ring.get_node(key), username=self.user_name, password=self.password)

    def server_iter(self):
        for item in self.server_list:
            yield jenkins.Jenkins(item, username=self.user_name, password=self.password)

    def request(self, client, action, *args, **kwargs):
        """根据传来的客户端和动作发起请求"""
        method = getattr(client, action, None)
        if method:
            retry, sign = 0, False
            while True:
                try:
                    result = method(*args, **kwargs)
                except Exception as Error:
                    self.record_log.error(f'{str(Error)},重试{retry}次', exc_info=True)
                    retry += 1
                    if retry >= 3:
                        result = str(Error)
                        break
                    time.sleep(3)
                else:
                    sign = True
                    break
            return sign, result
        else:
            return False, None

    def request_iter(self, action, *args, **kwargs):
        """请求所有服务器,使所有服务器保持一致"""
        for client in self.server_iter():
            self.request(client, action, *args, **kwargs)


class JenkinsBuild(JenkinsApi):
    """Jenkins 构建相关"""

    def __init__(self, job_name):
        super(JenkinsBuild, self).__init__()
        self.job_name = job_name
        self.Jenkins = self.select_server(self.job_name)

    def get_build_console(self, build_number):
        """获取构建任务的日志输出"""
        sign, build_console = self.request(self.Jenkins, 'get_build_console_output', self.job_name, build_number)
        return sign, build_console

    def get_build_status(self, build_number):
        """获取构建状态"""
        sign, build_status = self.request(self.Jenkins, 'get_build_info', self.job_name, build_number)
        if sign:
            building, result = build_status.get('building'), build_status.get('result')
        else:
            building, result = None, None
        return building, result

    def last_test_build(self):
        """获取动作为test最后一次构建"""
        job_info = self.Jenkins.get_job_info(self.job_name)
        job_builds, build_number = job_info.get('builds'), None
        for item in job_builds:
            build_number = item.get('number')
            build_info = self.Jenkins.get_build_info(self.job_name, build_number)
            parameters = build_info.get('actions')[0].get('parameters')
            if parameters[0].get('value') == 'test':
                break
        return build_number

    def get_job_config(self, name):
        config = self.Jenkins.get_job_config(name)
        print(config)
        # self.Jenkins.create_folder('App-Dev-test/ali-cn-zj1')
        # a = self.Jenkins.get_job_config('App-Dev-test/ali-cn-zj1')
        # print(a)


if __name__ == '__main__':
    JenkinsBuild('App-Dev/user-growth/hermes-admin1').get_job_config('App-Dev/user-growth/hermes-admin1')
    JenkinsApi().select_server('App-Dev/user-growth/hermes22-admin12')
    a = JenkinsBuild('App-Dev/ali-cn-zjk/middle-platform-ad_adx-bss-service').get_build_status(7)
    print(a)
    # func = getattr(JenkinsApi, 'request', None)
    # print(func)
JenkinsApi.py

 

三、Jenkins自动构建Docker项目

自动化的前提是标准化,CD的难点是部署环境的异构性,如果部署环境统一采用Docker/K8s,就为CD的实现提供了基础;

我的生产环境通常是Docker或者K8s,这样更容易实现CD;

1.外挂目录(Docker)

2.Jar包直接打包到镜像(Docker)

3.Build生成新镜像推送到Habor私服(K8s)

 

 

参考

posted on 2018-02-04 09:28  Martin8866  阅读(3575)  评论(0编辑  收藏  举报