解决 Jenkins Artifactory Plugin 在 AIX 上传制品失败的问题


本文对于同样在 AIX 遇到这个问题的人会非常有帮助。不要被标题无聊到,解决问题的过程值得参考。


分享一个花了两天时间才解决的一个问题:使用 Jenkins Artifactory 插件上传制品到 https 协议的企业级的 Artifactory 失败。该问题只在 AIX 平台上出现的,其他 Windows,Linux, Unix 均正常。

前言
最近计划将之前使用的 Artifactory OSS(开源版)迁移到 Aritifactory Enterprise(企业版)上。为什么要做迁移?这里有一个 Artifactory 对比的矩阵图 https://www.jfrog.com/confluence/display/JFROG/Artifactory+Comparison+Matrix

简单来说,开源版缺少与 CI 工具集成时常用的 REST API 功能,比如以下常用功能

  • 设置保留策略(Retention)。设置上传的制品保留几天等,达到定期清理的目的。
  • 提升(Promote)。通过自动化测试的制品会被提升到 stage(待测试)仓库,通过手工测试的提升到 release(发布)仓库。
  • 设置属性(set properties)。对于通过不同阶段的制品通过 CI 集成进行属性的设置。
    正好公司已经有企业版了,那就开始迁移吧。本以为会很顺利的完成,没想到唯独在 IBM 的 AIX 出现上传制品失败的问题。


环境信息

Jenkins ver. 2.176.3
Artifactory Plugin 3.6.2
Enterprise Artifactory 6.9.060900900
AIX 7.1 && java version 1.8.0

以下是去掉了无相关的信息的错误日志。


[consumer_0] Deploying artifact: https://artifactory.company.com/artifactory/...
Error occurred for request GET /artifactory/api/system/version HTTP/1.1: A system call received a parameter that is not valid. (Read failed).
Error occurred for request PUT /artifactory/... HTTP/1.1: A system call received a parameter that is not valid. (Read failed).
Error occurred for request PUT /artifactory/... HTTP/1.1: A system call received a parameter that is not valid. (Read failed).
 Caused by: java.net.SocketException: A system call received a parameter that is not valid. (Read failed)
  at java.net.SocketInputStream.socketRead(SocketInputStream.java:127)
  at java.net.SocketInputStream.read(SocketInputStream.java:182)
  at java.net.SocketInputStream.read(SocketInputStream.java:152)
  at com.ibm.jsse2.a.a(a.java:227)
  ... ...
  at com.ibm.jsse2.as.startHandshake(as.java:454)
  ... ...
Failed uploading artifacts by spec

很奇怪会出现上述问题,从开源版的 Artifactory 迁移到企业版的 Artifactory,它们之间最直接的区别是使用了不同的传输协议,前者是 http 后者是 https。


HTTPS 其实是有两部分组成:HTTP + SSL/TLS,也就是在 HTTP 上又加了一层处理加密信息的模块,因此更安全。


本以为 Google 一下就能找到此类问题的解决办法,可惜这个问题在其他平台都没有,只有 AIX 上才有,肯定这个 AIX 有什么“过人之处”和其他 Linux/Unix 不一样。

用 curl 替代
由于上述问题重现在需要重新构建,比较花时间,就先试试直接用 curl 命令来调用 Artifactory REST API 看看结果。

做了以下测试,查看 Artifactory 的版本


curl  https://artifactory.company.com/artifactory/api/system/version
curl: (35) Unknown SSL protocol error in connection to artifactory.company.com:443

# 打开 -v 模式,输出更多信息
bash-4.3$ curl -v  https://artifactory.company.com/artifactory/api/system/version
*   Trying 10.18.12.95...
* Connected to artifactory.company.com (10.18.12.95) port 443 (#0)
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* NPN, negotiated HTTP1.1
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS alert, Server hello (2):
* Unknown SSL protocol error in connection to artifactory.company.com:443
* Closing connection 0
curl: (35) Unknown SSL protocol error in connection to artifactory.company.com:443

果然也出错了,curl 也不行,可能就是执行 curl 命令的时候没有找到指定证书,查了 curl 的 help,有 --cacert 参数可以指定 cacert.pem 文件。


bash-4.3$ curl --cacert /var/ssl/cacert.pem https://artifactory.company.com/artifactory/api/system/version
{
  "version" : "6.9.0",
  "revision" : "60900900",
  "addons" : [ "build", "docker", "vagrant", "replication", "filestore", "plugins", "gems", "composer", "npm", "bower", "git-lfs", "nuget", "debian", "opkg", "rpm", "cocoapods", "conan", "vcs", "pypi", "release-bundle", "replicator", "keys", "chef", "cran", "go", "helm", "rest", "conda", "license", "puppet", "ldap", "sso", "layouts", "properties", "search", "filtered-resources", "p2", "watch", "webstart", "support", "xray" ],
  "license" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

试了下成功了。

到这里问题已经解决了,只要使用 curl 调用 Artifactory REST API 就能完成上传操作了。但我用的 Jenkins Artifactory Plugin,如果使用 curl 我需要把之前的代码重新再实现一遍,然后再测试,就为了 AIX 一个平台的问题,实在是“懒”的重新开始。本着这样懒惰的性格,还得继续解决 Jenkins 调用 agent 去执行上传失败的问题。

最终解决
设置 SSL_CERT_FILE 环境变量
想试试用上述的办法来解决 Jenkins 的问题。如果能有一个环境变量能设置指定 cacert.pem 文件的路径,那样在 Jenkins 调用 agent 执行上传时候就能找到证书,可能就能解决这个问题了。果然是有这样的环境变量的 SSL_CERT_FILE,设置如下


set SSL_CERT_FILE=/var/ssl/cacert.pem

设置好环境变量之后,通过 curl 调用,再不需要使用 --cacert 参数了。这下看起来有戏了,带着喜悦的心情把这个环境变量加到 agent 机器上,设置如下:

解决 Jenkins Artifactory Plugin 在 AIX 上传制品失败的问题

或者可以修改 agent 机器上的 /etc/environment 文件。

结果经测试错误信息依旧,看来 Jenkins 执行的 remote.jar 进行上传时跟本地配置环境没有关联,看来需要从执行 remote.jar 着手,把相应的设置或是环境变量在启动 remote.jar 时传进去。


Jenkins 管理 agent 的原理是通过在 agent 上启动一个 remote.jar 实现的


启动 remote.jar 时设置环境变量
java 的 -D 参数可以完成这一点。

进行了大量的搜索和尝试,最终在 IBM 的官方找到了这篇文档 https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/jsse2Docs/matchsslcontext_tls.html

文档大意是,IBM SDK 系统属性 com.ibm.jsse2.overrideDefaultTLS=[true|false] 有 true 和 false 两个值,如果想要与 Oracle SSLContext.getInstance("TLS") 的行为相匹配,请将此属性设置为 true,默认值为 false。

下表显示了系统属性对 SSLContext.getInstance("TLS") 的影响

解决 Jenkins Artifactory Plugin 在 AIX 上传制品失败的问题
绝大多数的 Java 应用都是使用 Oracle 的 JDK 来开发的,这里要与 Oracle 的行为保持一致;另外 IBM 的 SDK 默认协议只有 TLS V1.0,而上面的 log 可以看到使用的 TLSv1.2 协议,因此需要将属性设置为 true。

最终在 Jenkins 的 agent 配置里将 JVM Options 区域加上这句 -Dcom.ibm.jsse2.overrideDefaultTLS=true,断开连接,重新启动 agent,再次执行 Pipeline,成功的把 AIX 上的制品上传到 Artifactory 上了,问题解决了。

总结
遇到问题并解决问题是一件非常爽的事,从中也学到了很多之前不曾了解过的知识,解决问题的过程比 Google 随便查查更让人印象深刻,再遇到类似问题可能就会举一反三了。

另外,凡事如果觉得自己在短时间内没有头绪、自己搞不定的时候尽快寻求有经验的同事的帮助。感谢帮助我的同事们,没有他们的帮助和指导就不能这么快的解决问题。

上一篇:还在写定时任务进行部署? ---使用Artifactory Webhooks和Docker实现持续部署


下一篇:工欲善其事,必先利其器——DevOps中如何管理工具包