道哥的第 030 篇原创
- 一、一个真实的代码泄漏故事
- 二、Jenkins 的基本使用
- 三、git submodule 基本使用
- 四、在 Jenkins 中使用 git module 来编译所有的模块
- 五、总结
- 六、资源下载
一、一个真实的小故事
事情发生在功能机的时代,我们项目组开发一款手机,软件开发成员大概有 20 人左右吧。结果在手机发布的一周后,另一家小厂就推出了软件界面、功能几乎完全一样的手机,除了开机界面。
因为那个时代,大家几乎都是使用 MTK、高通提供的解决方案,都是统一的菜单式功能,你没法拿出有力的证据来说明别人偷窃了你的代码。
后来内部查明,的确是有开发人员把代码泄漏出去了,于是后来所有的电脑上 USB 口全部被禁掉了。
这是我亲身经历的真实故事,当时每个人负责一个模块,比如:A 负责通话管理和电话簿,B 负责系统设置,C 负责短信和彩信。。。在编译的时候,是需要所有的代码放在一起,统一编译的,这也就意味着所有的软件人员都可以拿到全部源代码,这也就为代码泄漏埋下了隐患,出现了这次严重的事件,毕竟人为财死、鸟为食亡!
那么,是否有一些代码管控方式,来解决这个权限问题呢?
现在项目中,都强调要分层、分模块,这是从软件工程的角度来考虑的。如果再进一步, 把这些模块都划分为一个小的子系统,每个开发人员只负责自己的模块,并且只能有权限拉取自己的代码,这样他就没法获取到一个项目中所有模块的代码了。
只有项目整合人员(管理员),才有全部权限来拉取所有源代码来构建整个系统,这样的话,就可以对代码的安全问题有更好的掌控了。
要实现这样的代码管控,使用 git 工具中的 submodule 就可以完成,这篇文章,我们就来详细的讲解一下 git submodule 的使用。
这篇文章是工具型的,可能比较长,为了提供一站式服务,我会把相关的资源、步骤、遇到的错误信息等细节都记录下来,方便以后查阅。
不论如何,经过这篇文章,你可以学习、了解下面这几个方面的知识点:
- Jenkins 的基本使用方法;
- git submodule 的基本指令用法;
- 通过三个 demo 项目,一步一步操作实现代码的安全管控;
- 利用 Jenkins git submodule 来实现自动化编译;
- git subtree 与 submodule 的区别;
如果您需要文中提到的软件和代码资源,在文章末尾可以找到下载方式。
二、Jenkins 的基本使用
1. Jenkins 是什么?
Jenkins是一个开源、由 Java 编写的持续集成工具,也就是说它帮助我们自动构建各类项目。Jenkins 运行在 Servlet 容器中(例如 Apache Tomcat),在 Ubuntu 系统中使用 apt-get 就可以一键安装。
Jenkins 有下面几个特点:
- 嵌入在 Web 服务器中,通过浏览器来操作,非常方便;
- 可以执行基于Apache Ant和Apache Maven的项目,以及任意的Shell脚本和Windows批处理命令;
- 可以通过各种手段触发构建。例如提交给版本控制系统时被触发,通过类似Cron的机制调度,在其他的构建已经完成时,还可以通过一个特定的URL进行请求;
- Jenkins强大的插件式,使得Jenkins可以集成很多软件,可能帮助我们持续集成我们的工程项目;
- 给用户很大的权限和灵活性来自动发布、部署等等。
其他的有点我就不吹了,我觉得很好用,如果有机会,你也可以试一下。另外,我测试用的虚拟机是新安装的 Ubuntu16.04-64,按照下面的流程操作,保证可以顺利运行。
JDK 和 Jenkins 的安装方法,在网络上很多资料,有些过程是有问题的,或者某些关键步骤没写清楚。为了便于你一次就操作成功,我还是记录在这里。
如果你对安装过程已经很熟悉了,可以直接滑到下一个主题。
2. 安装 JDK8
(1) 下载,解压
下载 jdk-8u221-linux-x64.tar.gz,(文末提供下载地址),解压到目录 /home/sewain/OpenSource ,解压指令:
代码语言:javascript复制sudo tar -zxvf jdk-8u221-linux-x64.tar.gz -C /opt
(2) 设置环境变量
执行指令:vim ~/.bashrc
,在末尾添加如下内容:
export JAVA_HOME=/opt/jdk1.8.0_221
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=./$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jart
我建议你也用这样的环境变量,以后如果升级 JDK 版本,只需要修改 JAVA_HOME
就可以了。
(3) )重新加载环境变量
执行指令:source ~/.bashrc
,此时环境变量就生效了。
验证一下: java -version
,出现如下信息就说明 OK 了:
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
3. 安装 Jenkins
(1) 导入 Jenkins 存储库的 GPG 密钥
代码语言:javascript复制sudo wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
(2) Jenkins存储库添加到系统中
代码语言:javascript复制sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
(3) 使用 apt 安装
代码语言:javascript复制sudo apt update
sudo apt install jenkins
Jenkins 服务将在安装过程完成后自动启动,可以通过指令 systemctl status jenkins
进行验证。
systemctl status jenkins
(4) 配置端口
Jenkins 是嵌入在一个 tomcat 服务器中的,默认使用端口 8080,容易与其他服务冲突,因此需要修改一下。涉及到 2 个文件:
文件一:/etc/init.d/jenkins
第一行的 PATH 变量中,添加自己的 JDK 地址:
代码语言:javascript复制PATH=/bin:/usr/bin:/sbin:/usr/sbin:/opt/jdk1.8.0_221/bin
文件二:/etc/default/jenkins
把 HTTP_PORT 的值修改为新的端口号,例如: HTTP_PORT=9090 。
(5) Jenkins 的启动和停止指令
代码语言:javascript复制sudo service jenkins start
sudo service jenkins stop
如果不幸遇到错误,可以反复使用这两个指令来排除错误。
4. 在浏览器中配置 Jenkins
在浏览器中输入: htpp://localhost:9090,稍等一会,出现界面:
按照界面提示,从 /var/lib/jenkins/secrets/initialAdminPassword
文件中复制安全密码(需要 root 权限),填入到浏览器窗口中。
此时出现安装插件窗口,一般只需要安装推荐的插件即可:
插件安装结束后,进入管理员配置界面:
至此,Jenkins 的安装就顺利完成了!
5. 在 Jenkins 中配置一个小项目
(1) 准备一个测试代码 Test1
Jenkins 仅仅是一个构件框架,具体的编译过程是由用户来决定的。Jenkins 首先通过 git 工具获取远程仓库中的代码,然后执行用户指定的编译指令。
因此,我们需要先提前准备好一份测试代码,并放到 Jenkins 可以访问到的远程仓库中,当然了,你在本地的 Ubuntu 系统中部署一个 git 仓库也是可以的。为了方便,我测试的代码 Test1 放在 gitee 中了。
(2) 创建一个新项目
(3) 输入项目名称,并选择第一个自由风格(Free project)
(4) 在第一个标签 General下,输入项目的描述信息(Description)
内容可以随便写。
(5) Source Code Management 源码管理
Jenkins 在构建(编译)的过程中,需要获取到源代码,因此需要配置 git 仓库的地址和账号信息(用户名和密码)。
首先在 Add 下拉按钮下,选择 Jenkins:
输入 Username 和 Password :
账号添加之后,在 Credentials 的下拉框中,选择刚才添加的 gitee 账户,此时可以看到 红色的权限错误提示消失了,说明可以拉取到远程仓库中的源代码了。
(6) Build Triggers 选择编译触发器
可以根据需要选择不同的方式来触发,比如:定时触发,当其他某个项目构建成功之后触发等等。
我们这里不选择任何项目,下面我们会在主界面,手动点击按钮来触发。
(7) Build Environment 编译环境
这部分我用的比较少,利用其他工具来辅助 Jenkins 的功能。
(8) Build 编译
就是告诉 Jenkins 如何来构建系统,也就是说:Jenkins只是一个自动化的构建系统,具体的编译过程,可以由用户自己来决定,有如下选择项目:
我们这里选择直接执行脚本(Execute Shell),输入如下指令:
(9) Post-build Actions 编译后操作
告诉 Jenkins: 编译一个工程之后,需要做哪些事情?例如:发送邮件,触发下一个工程的自动编译等等,而且可以添加多个动作。可选项如下:
以上步骤配置好之后,Save 保存,此时在主界面就可以看到这个项目的全貌了,如下图:
在 Jenkins 后台中,这个项目的保存路径是:/var/lib/jenkins/jobs/Test1。
6. 手动触发编译一次
由于在上面的步骤(6)中,我们没有选择任何触发条件,所以需要我们在项目 Test1 的主界面中,手动单击左侧的 Build Now 按钮来触发。
此时,在左侧的 Build History 中,可以看到编译历史记录,单击某次编译记录编号,可以看到这一次编译的详细信息。
在编译详细信息中,单击左侧的 Console Output 按钮,可以看到编译的输出信息:成功编译得到可执行文件。
我们可以在 Jenkins 后台中看到,源代码被拉到 /var/lib/jenkins/jobs/Test1/workspace 目录中了:
到这里,你已经学会了 Jenkins 的最基本操作!
下面我们继续讲一下 git submodule 的使用,这部分才是核心内容!
三、git submodule 基本使用
1. git submodule 是什么?
git submodule 是用于多模块管理的工具,它允许一个项目作为 repository,其他项目作为子模块存在于在父项目中。
父项目和子项目的提交是分开的,也就是说父项目提交的信息只包含子项目的信息,而不会包含子项目的代码;子项目有自己独立的 commit,push,pull操作。
git submodule 一般用在比较大的项目中,为了便于复用,或者为了代码的安全性,常常需要分成若干个子项目来进行代码管理。
常用的指令包括:
添加子模块: git submodule add 更新子模块: git submodule update 初始化子模块: git submodule init 递归的方式克隆整个项目: git clone--recursive 拉取所有子模块: git submodule foreach git pull
2. 利用三个小项目,来测试一下 submodule 的用法
为了便于演示,我们我们创建 3 个项目,把它们都推送到远程仓库中,这里使用 gitee。
- Test1:编译得到一个动态库:libtest1.so;
- Test2:编译得到一个动态度:libtest2.so;
- Test3:编译得到一个可执行程序,加载、调用上面 2 个动态库中的函数。
为什么要这样设计模块: 安全!
- 开发人员A:负责 Test1,没有权限拿到其他模块的代码;
- 开发人员B:负责 Test2,没有权限拿到其他模块的代码;
- 项目经理:负责 Test3 和 代码整合,能拿到所有的代码;
项目经理需要把 Test1 和 Test2 作为 sub module,添加到 Test3 中,执行下面的指令:
1. git submodule add https://gitee.com/[你的账号]/test1.git test1 2. git submodule add https://gitee.com/[你的账号]/test2.git test2
把 Test1 和 Test2 作为子模块添加到 Test3 中之后,看一下文件有什么变化:
你还可以看一下 .gitmodules 文件中的内容,可以看出,git 工具就是通过这个配置文件来管理子模块的。
管理员需要对所有的模块进行整合、编译,因此,我们在 Test3 目录下添加一个脚本 build.sh,所有的编译指令,都写在这个脚本中,内容如下:
内容都是最最基本的,直接调用 make 指令即可,执行一下,输出:
到这里,我们就完成了子模块的添加功能。
3. 在一个空目录中来编译、验证一下可行性
我们在另一个空目录中,clone 一下 Test3 这个项目,可以发现:克隆下来的 test1 和 test2 文件夹中是空的,如下所示:
需要手动获取所有的子模块,执行指令:
代码语言:javascript复制git submodule update --init --recursive
此时,再使用 tree 命令看一下文件变化,可以看到 test1 和 test2 的文件都被拉取下来了。这里有一个问题需要注意:虽然子模块的代码被拉取下来了,但是其 head 并没有指向 master 分支,需要手动处理一下,如图:
这个时候,我们在 test3 目录下再次执行脚本 build.sh,就可以顺利编译所有的子模块了。
以上这几个步骤,我们是在本地的一个临时目录,手动测试 submodule 的编译过程。
下一个章节我们把这个过程部署到 Jenkins 系统中,所以刚才执行的这几个指令,就需要写在 build.sh 脚本中了。build.sh 的内容变为:
四、在 Jenkins 中使用 git module 来编译所有的模块
下面的操作,都是在浏览器的 Jenkins 面板中来操作的。
1. 重新配置项目
因为我们是在 Test3 中,来编译整个项目(Test1 和 Test2 被作为子模块包括进来),因此首先把之前添加的 Test1 项目删除掉,如图:
然后重新添加项目 Test3,复习一下步骤:
- 输入描述信息,选择自由风格项目;
- 输入 git 仓库地址和账户信息,选择 master 分支;
- 触发器不设置;
- 编译环境不设置;
- 编译:选择 Execute shell 执行脚本,输入编译指令:./build.sh。(刚才说了,Jenkins 这是一个自动化构建框架,具体的编译过程由用户决定,所以我们这里的编译过程就是执行 Test3 下的 build.sh 这个脚本。);
- 编译后动作不设置;
当然,也可以直接在之前的 Test1 项目基础上进行修改。
此时,我们在 Jenkins 中直接点击 Build Now 按钮,如果不出意外的话,会提示编译错误(左侧的 Build History 下面出现红色的错误圆圈)。
点进去,看一下输出信息(Console Output 按钮),提示错误:
原因正如前文所说,子模块在获取之后,head 没有指向 master 分支,需要我们在第一次编译时手动修改一下(我没有找到其它方法,如果你知道的话,请不吝赐教!)
手动解决:在命令行窗口中,进入 Jenkins 系统的 Test3 目录 /var/lib/jenkins/jobs/Test3/workspace ,执行如下几条指令:
代码语言:javascript复制git submodule update --init --recursive
cd test1/
git checkout master
cd -
cd test2/
git checkout master
cd -
此时,重新触发编译一次,一定可以成功的!
五、总结
这篇文章是属于工具型的,一旦部署好之后,每次编译只需要在浏览器中点一下按钮就行,再也不用 ssh 登录到远程电脑中去手动操作了。
如果你还想继续深入一下的话,下面几件事情可以研究一下:
1. Jenkins 是如何保持编译历史记录的
在目录 /var/lib/jenkins/jobs/Test3/builds 下面,可以看到很多以数字命名的文件夹,记录了每一次的编译信息。
2. 编译后动作
在我们的编译脚本 build.sh 文件中,仅仅是生成了可执行文件,你还可以继续扩充功能,例如:自动部署。
或者在项目配置的 [Post-build Actions] 中,重新写一个专门用来自动部署的脚本文件。
3. git subtree 功能
它与 git submodule 很类似,但是本质不一样。
- subtree直接把子模块代码拷贝到主模块中,使用命令简单;
- submodule 使用的是“指针”指向子模块,使用命令相对复杂一些,功能也更强大;
4. 继续研究 Jenkins 中的插件功能
六、资源下载
文中用到的资源,放在了网盘中。如果需要的话,请在公众号留言区发送数字:030,即可收到下载地址。