Jenkins 结合 Docker 为 .NET Core 项目实现低配版的 CI&CD

随着项目的不断增多,最开始单体项目手动执行 docker build 命令,手动发布项目就不再适用了。一两个项目可能还吃得消,10 多个项目每天让你构建一次还是够呛。即便你的项目少,每次花费在发布上面的时间累计起来都够你改几个 BUG 了。

所以我们需要自动化这个流程,让项目的发布和测试不再这么繁琐。在这里我使用了 Jenkins 作为基础的 CI/CD Pipeline 工具,关于 Jenkins 的具体介绍这里就不再赘述。在版本管理、构建项目、单元测试、集成测试、环境部署我分别使用到了 GogsDockerDocker Swarm(已与 Docker 整合) 这几个软件协同工作。

以下步骤我参考了 一文,并使用了作者提供的 groovy 文件和 slave.py 文件。

关于 Docker-CE 的安装,请参考我的另一篇博文 《Linux 下的 Docker 安装与使用》 。

一、Jenkins 的部署

既然都用了 Docker,我是不想在实体机上面安装一堆环境,所以我使用了 Docker 的形式来部署 Jenkins 的 Master 和 Slave,省时省力。Master 就是调度管道任务的主机,也是唯一有 UI 供用户操作的。而 Slave 就是具体的工作节点,用于执行具体的管道任务。

1.1 构建 Master 镜像

第一步,我们在主机上建立一个 master 文件夹,并使用 vi 创建两个 groovy 文件,这两个文件在后面的 Dockerfile 会被使用到,下面是 default-user.groovy 文件的代码:

import jenkins.model.* import hudson.security.* def env = System.getenv() def jenkins = Jenkins.getInstance() jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(false)) jenkins.setAuthorizationStrategy(new GlobalMatrixAuthorizationStrategy()) def user = jenkins.getSecurityRealm().createAccount(env.JENKINS_USER, env.JENKINS_PASS) user.save() jenkins.getAuthorizationStrategy().add(Jenkins.ADMINISTER, env.JENKINS_USER) jenkins.save()

接着再用 vi 创建一个新的 executors.groovy 文件,并输入以下内容:

import jenkins.model.* Jenkins.instance.setNumExecutors(0)

以上动作完成之后,在 master 文件夹下面应该有两个 groovy 文件。

Jenkins 结合 Docker 为 .NET Core 项目实现低配版的 CI&CD

两个 master 所需要的 groovy 文件已经编写完成,下面来编写 master 镜像的 Dockerfile 文件,每一步的作用我已经用中文进行了标注。

# 使用官方的 Jenkins 镜像作为基础镜像。 FROM jenkins/jenkins:latest # 使用内置的 install-plugins.sh 来安装插件。 RUN /usr/local/bin/install-plugins.sh git matrix-auth workflow-aggregator docker-workflow blueocean credentials-binding # 设置 Jenkins 的管理员账户和密码。 ENV JENKINS_USER admin ENV JENKINS_PASS admin # 跳过初始化安装向导。 ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false # 将刚刚编写的两个 groovy 脚本复制到初始化文件夹内。 COPY executors.groovy /usr/share/jenkins/ref/init.groovy.d/ COPY default-user.groovy /usr/share/jenkins/ref/init.groovy.d/ # 挂载 jenkins_home 目录到 Docker 卷。 VOLUME /var/jenkins_home

接着我们通过命令构建出 Master 镜像。

docker build -t jenkins-master .

Jenkins 结合 Docker 为 .NET Core 项目实现低配版的 CI&CD

1.2 构建 Slave 镜像

Slave 镜像的核心是一个 slave.py 的 python 脚本,它主要执行的动作是运行 slave.jar 并和 Master 建立通信,这样你的管道任务就能够交给 Slave 进行执行。这个脚本所做的工作流程如下:

st=>start: 脚本执行 stop=>end: 容器停止 op1=>operation: 导入 "jenkins" 的 python 模块 op2=>operation: 等待 Master 启动 cond1=>condition: Master 是否可用 op3=>operation: 下载 slave.jar 文件到容器内 op4=>operation: 通过 JLNP 连接到 Master 节点 op5=>operation: 使用凭据通过授权验证 op6=>operation: 成功运行 Slave st->op1->op2->cond1 cond1(no)->op2 cond1(yes)->op3->op4->op5->op6->stop

我们再建立一个 slave 文件夹,并使用 vi 将 python 脚本复制进去。

slave.py 的内容:

from jenkins import Jenkins, JenkinsError, NodeLaunchMethod import os import signal import sys import urllib import subprocess import shutil import requests import time slave_jar = '/var/lib/jenkins/slave.jar' slave_name = os.environ['SLAVE_NAME'] if os.environ['SLAVE_NAME'] != '' else 'docker-slave-' + os.environ['HOSTNAME'] jnlp_url = os.environ['JENKINS_URL'] + '/computer/' + slave_name + '/slave-agent.jnlp' slave_jar_url = os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar' print(slave_jar_url) process = None def clean_dir(dir): for root, dirs, files in os.walk(dir): for f in files: os.unlink(os.path.join(root, f)) for d in dirs: shutil.rmtree(os.path.join(root, d)) def slave_create(node_name, working_dir, executors, labels): j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS']) j.node_create(node_name, working_dir, num_executors = int(executors), labels = labels, launcher = NodeLaunchMethod.JNLP) def slave_delete(node_name): j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS']) j.node_delete(node_name) def slave_download(target): if os.path.isfile(slave_jar): os.remove(slave_jar) loader = urllib.URLopener() loader.retrieve(os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar', '/var/lib/jenkins/slave.jar') def slave_run(slave_jar, jnlp_url): params = [ 'java', '-jar', slave_jar, '-jnlpUrl', jnlp_url ] if os.environ['JENKINS_SLAVE_ADDRESS'] != '': params.extend([ '-connectTo', os.environ['JENKINS_SLAVE_ADDRESS' ] ]) if os.environ['SLAVE_SECRET'] == '': params.extend([ '-jnlpCredentials', os.environ['JENKINS_USER'] + ':' + os.environ['JENKINS_PASS'] ]) else: params.extend([ '-secret', os.environ['SLAVE_SECRET'] ]) return subprocess.Popen(params, stdout=subprocess.PIPE) def signal_handler(sig, frame): if process != None: process.send_signal(signal.SIGINT) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) def master_ready(url): try: r = requests.head(url, verify=False, timeout=None) return r.status_code == requests.codes.ok except: return False while not master_ready(slave_jar_url): print("Master not ready yet, sleeping for 10sec!") time.sleep(10) slave_download(slave_jar) print 'Downloaded Jenkins slave jar.' if os.environ['SLAVE_WORING_DIR']: os.setcwd(os.environ['SLAVE_WORING_DIR']) if os.environ['CLEAN_WORKING_DIR'] == 'true': clean_dir(os.getcwd()) print "Cleaned up working directory." if os.environ['SLAVE_NAME'] == '': slave_create(slave_name, os.getcwd(), os.environ['SLAVE_EXECUTORS'], os.environ['SLAVE_LABELS']) print 'Created temporary Jenkins slave.' process = slave_run(slave_jar, jnlp_url) print 'Started Jenkins slave with name "' + slave_name + '" and labels [' + os.environ['SLAVE_LABELS'] + '].' process.wait() print 'Jenkins slave stopped.' if os.environ['SLAVE_NAME'] == '': slave_delete(slave_name) print 'Removed temporary Jenkins slave.'

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzgfp.html