一、背景知识
h5服务的部署有两种方式,一是自己搭建web服务,再由nginx等反向代理;二是本方案中的CDN。【使用CDN的好处这里就不赘述了】
有些人可能会说,还有其他的好多容器可以做到,这里因为涉及到域名映射,对外访问的地址不能是ip,所以我们说大多是使用nginx反向代理来实现。
(但它也不是本文要讲述的方式)
本文主要讲述我们公司是如何实现h5发布到oss上的,因为我们打包构建使用的是jenkins,所以我写了一个Jenkins plugin。(源码已上传到github:https://github.com/zwp201301/jenkins-oss-plugin,希望能够帮助到类似需求的小伙伴!)
二、问题描述
- 前后端跨域问题
云存储服务都有配置跨域的界面,包括Minio这样的内网文件系统。
- 多环境部署
通过子目录的区分,比如dev-开发环境,test-测试环境,staging-预发环境,prod-生产环境。
- 发布及回滚
因为不能做删除操作,发布的时候是每次进行覆盖全部,不做删除动作;遇到需要回滚,则对上一个版本的代码进行二次发布。
- 多版本共存
这里没有像多环境那样,新建版本号的子目录去区分不同版本号。
那样可以做到同时有多个版本共存的情况,相对来说,占用的空间较大,维护的工作量也较大。
暂时没这样去做,看各自的实际需求而定。
三、jenkins plugin
代码实现逻辑分为三大步
- 将需要发布的文件统一拷贝至目标目录下
- 逐个读取文件,以及文件的相对路径
- 调用云存储或minio等上传文件的api
详细的源码我将上传到github仓库,本文梳理出来几个关键实现:
1、插件的入口类
参考https://github.com/jenkinsci/ssh-steps-plugin的实现,入口类为UploadFileStep.java,它是一个Step。因为使用方式是pipeline,我们就没有实现由页面接收入参的方式。
因为jenkins插件运行在master节点,它需要去读取slave节点上的h5/css/js等文件,所以不能是普通的Builder。
- 一定要有构造函数,并在它上面加注解@DataBoundConstructor。
- 作为入口,接收用户的参数是它的一个主要功能,另外它实现了Step的start()方法。
@Override
public StepExecution start(StepContext context) throws Exception {
return new Execution(this, context);
}
// 线程CommandCallable的实现:
public Object execute() {
return getService().upload(getListener(), getWorkspace());
}
// 核心语句,调用了类UploadFileService的上传文件方法。
2、源码引用了minio的jar包,它依赖的guava版本比Jenkins应用原本依赖的guava要高很多,默认的加载机制是以jenkins应用为准的,也就是说不会使用插件指定的guava版本。所以,我们在pom.xml中必须设置插件优先。
<plugin>
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<configuration>
<minimumJavaVersion>8</minimumJavaVersion>
<pluginFirstClassLoader>true</pluginFirstClassLoader>
</configuration>
</plugin>
在调试的过程中,我遇到的报错有:
java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;C)V
at com.google.common.io.BaseEncoding$Alphabet.<init>(BaseEncoding.java:458)
at com.google.common.io.BaseEncoding$Base64Encoding.<init>(BaseEncoding.java:919)
at com.google.common.io.BaseEncoding.<clinit>(BaseEncoding.java:322)
at io.minio.Digest.sha256Md5Hashes(Digest.java:89)
at io.minio.MinioClient.createRequest(MinioClient.java:874)
at io.minio.MinioClient.execute(MinioClient.java:981)
at io.minio.MinioClient.execute(MinioClient.java:935)
at io.minio.MinioClient.executeHead(MinioClient.java:1204)
at io.minio.MinioClient.bucketExists(MinioClient.java:3592)
at com.xhtech.tool.jenkins.service.MinioOssService.upload(MinioOssService.java:31)
at com.xhtech.tool.jenkins.service.UploadFileService.iterateWorkspace(UploadFileService.java:80)
at com.xhtech.tool.jenkins.service.UploadFileService.upload(UploadFileService.java:48)
at com.xhtech.tool.jenkins.steps.UploadFileStep$Execution$CommandCallable.execute(UploadFileStep.java:104)
at com.xhtech.tool.jenkins.util.SSHMasterToSlaveCallable.call(SSHMasterToSlaveCallable.java:38)
at hudson.remoting.UserRequest.perform(UserRequest.java:212)
at hudson.remoting.UserRequest.perform(UserRequest.java:54)
at hudson.remoting.Request$2.run(Request.java:369)
at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:72)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at hudson.remoting.Engine$1.lambda$newThread$0(Engine.java:93)
at java.lang.Thread.run(Thread.java:748)
java.lang.NoSuchMethodError: com.google.common.base.CharMatcher.ascii()Lcom/google/common/base/CharMatcher;
at com.google.common.io.BaseEncoding$Alphabet.<init>(BaseEncoding.java:452)
at com.google.common.io.BaseEncoding$Base64Encoding.<init>(BaseEncoding.java:891)
at com.google.common.io.BaseEncoding.<clinit>(BaseEncoding.java:316)
at io.minio.Digest.sha256Md5Hashes(Digest.java:89)
at io.minio.MinioClient.createRequest(MinioClient.java:874)
at io.minio.MinioClient.execute(MinioClient.java:981)
at io.minio.MinioClient.execute(MinioClient.java:935)
at io.minio.MinioClient.executeHead(MinioClient.java:1204)
at io.minio.MinioClient.bucketExists(MinioClient.java:3592)
查看jenkins的jar包.png报错原因是说guava的类没找到,其他插件依赖的guava版本已经升上去了。
jenkins应用依赖的jar.png
从下图中可以看出,Jenkins应用依赖的guava版本比较低,11.0.1,远低于minio jar 7.1.4依赖的版本(25.1-jre)
guava.png
那么我们的自定义插件,是否已经是guava 25.1-jre?答案:是。
插件.png
guava版本.png
总结:报错的根由是Jenkins应用的guava版本替换了插件所引用的guava版本。
3、OSS工厂,根据用户的选择,采用不同的策略发布到不同的OSS。
- MinioOssService.java(实现类)
- AliyunOssService.java(实现类)
- OssService.java(接口)
- OssFactory.java(工程类)
4、核心实现类UploadFileService.java
作为核心实现,它会遍历工作空间下的目标文件,解析出环境和工程名,然后是调用具体的oss实现去上传文件。
需要注意的是,oss远程地址必须是区分路径层级。我们约定的规则,工程名+环境名+目标目录的相对路径,比如oms/test/img/logo.jpg, oms/test/index.html
- 工程名是oms
- 环境名是test
- 目标目录下的相对路径是img/logo.jpg、index.html
private void iterateWorkspace(String workspacePath) {
String distPath = workspacePath + File.separator + this.targetPath;
LogUtil.println("上传的文件目录来源:" + distPath);
String env = this.getEvnByWorkspace(workspacePath);
LogUtil.println("发布至环境:" + env);
String appName = this.getAppNameByWorkspace(workspacePath);
LogUtil.println("工程名称:" + appName);
List<File> files = PathUtil.loopFiles(Paths.get(distPath), null);
LogUtil.println("上传至OSS【" + ossType + "】的bucket:【" + bucketName + "】");
for (File file : files) {
String ossFileName = new StringBuilder(appName)
.append(File.separator)
.append(env)
.append(file.getPath().replaceAll(distPath, ""))
.toString();
if (this.debug) {
LogUtil.println("上传到OSS的文件名是" + ossFileName + ",本地路径是" + file.getAbsolutePath());
}
OssFactory.getOssService(this.ossType).upload(this.bucketName,
ossFileName,
new File(file.getAbsolutePath()));
}
}
四、pipeline使用示例
自定义插件的pipeline写法.png我们使用的Jenkins集群是采用K8S部署的Master-Slave结构。
注意:我们在jenkinsfile文件中,写法见下:
-- ossType是由jenkins job的参数传入
stage('Upload File To OSS') {
when {
expression { "aliyun" == ossType || "minio" == ossType }
}
steps {
script {
tools.PrintMes("Upload File To OSS!!!", "green")
uploadFile debug: true, ossType: ossType
}
}
post {
failure {
//当此Pipeline失败时打印消息
script {
tools.PrintMes("Upload File To OSS failure!!!", "red")
http.imNotfiy(projectName, "FAIL", buildEnv, "Upload File To OSS failure", branch, buildUser)
}
}
}
}
上面的pipeline语句"uploadFile debug: true, ossType: ossType"对应源码是:
@Extension
public static class DescriptorImpl extends SSHStepDescriptorImpl {
@Override
public String getFunctionName() {
return "uploadFile";
}
@Override
public String getDisplayName() {
return getPrefix() + getFunctionName();
}
}
网友评论