当前位置: 首页 > news >正文

使用 GitLab CI/CD 为 Linux 创建 RPM 包(一)

大家好!我是大聪明-PLUS

各位同事,大家好。前段时间,我们团队接到一个任务,开发一个用于与外围设备协同工作的服务,该服务将作为服务在 Linux 操作系统工作站上运行。

到目前为止,我们部门的所有桌面应用程序都是专门为 Windows 开发的。当前的任务对团队来说既是一个挑战,也是一个学习新知识的机会。我的团队对这个项目的准备标准是编写程序本身并将其发布为 RPM 包以供分发。

技术栈:

  • 返回 NET Core 8

  • 操作系统 Alt Linux 工作站 K 10.4。

  • GitLab

  • Docker

问题分解

如果你把问题分解成各个部分,并分别处理,那么解决问题就会容易得多。我列出了以下步骤:

  • 使用.NET Core 8开发一个简单的控制台程序。

  • 手动构建 RPM 包。

  • 设置 CI/CD

  • 为 GitLab 创建一个工作管道并获取 RPM 包作为输出。

开发一个简单的程序

如果您尝试立即打包一个包含许多项目和依赖项的现成程序,您可能会遇到意想不到的问题,从而分散您对目标的注意力。

在我看来,打包一个简单的程序要容易得多。仔细检查所有步骤,并解决所有与打包相关的问题。当需要打包目标程序时,您可以轻松扩展现有的流程,并且任何错误都只与二进制文件编译问题有关。

说干就干。一个简单的控制台程序就开发出来了——一个计数器,每秒显示一个 +1 的数字。


Console.WriteLine("Start counting");
await Counter();Task Counter()
{int i = 0;while (true){Console.WriteLine(i);i++;Thread.Sleep(1000);}
}

工作成果

因此,如果我们在构建软件包、在全新操作系统上安装并从命令行运行后得到相同的结果,则一切正常。如果这一切都是通过管道自动完成的,那就太棒了。

构建 RPM 包

从现在开始,我将描述具体步骤,假设你已经熟悉 spec 文件及其工作原理的术语和基本原理。

要构建 RPM 包,您需要按顺序执行以下步骤:

  • 安装 rpm-build 实用程序

  • 组装目录的配置。

  • 生成 spec 文件

  • 准备文件并将其移动到所需的目录。

  • 运行命令 rpmbuild -ba <spec 文件名>

安装 rpm-build 实用程序

sudo apt-get update && apt-get install -y rpm-build

组装目录的配置

这在现阶段很方便,但在流程中并不方便。

rpmdev-setuptree

请注意:绝大多数文章,甚至任何神经网络,都会告诉你 /home/user/.rpmbuild 目录已创建。Alt Linux 的配​​置似乎有所不同,它创建了 /home/user/RPM 目录。这并不重要,而且我还没有测试如果通过 .rpmbuild 文件夹执行所有操作,它是否能在管道中正常工作。我决定保持现状。

此外,您的主目录根目录下会创建一个 .rpmmacros 文件,您需要手动编辑该文件以删除所有不必要的文本。您可以在上面的链接中相关信息。

结论:在管道中,我们将手动创建一个目录结构并放置一个预先生成的.rpmmacros文件,该文件将位于项目的根目录。

生成 spec 文件

构建包最重要的一步是创建正确的规范文件。

spec 文件本身在本质上与管道描述文件非常相似。它是一个清单和一组指令,脚本必须执行这些指令才能生成编译后的程序,以及在安装过程中在哪里以及如何编写它。

请注意:我因为一个相当愚蠢的错误浪费了一些时间。SPEC 文件最初是在 Windows 中生成的,换行符用 \r\n 标记。我构建的 Linux 系统不接受这个标记,并报出了“无效返回码”的错误。我尝试使用 exit 0 等命令返回正确的返回码,结果浪费了不少时间。我删除了文件中的 \r 后,一切正常。

一个有趣的观察是,如果你在 Git 中提交文件,这些问题就不会发生。它似乎会立即更改它们并发出警告。

Spec 文件示例

在生成 spec 文件时,我假设程序应该位于 /opt/{name} 目录中,并且应该有一个指向 /usr/bin/{name} 中可执行文件的链接,以便可以在不指定路径的情况下运行它。

Name:           utestrpm
Version:        0.0.0 
Release:        1%{?dist}License:        MIT
Group:			Other
URL:            https://your-domain.com
Source0:        source_file.tar.gz BuildArch:      x86_64 
BuildRequires:  dotnet-8.0 %prep
%setup -q%buildexport DOTNET_NUGET_SIGNATURE_VERIFICATION=false dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/opt/%{name}
mkdir -p %{buildroot}%{_bindir}install -m 755 %{name}/bin/Release/net8.0/linux-x64/publish/%{name} %{buildroot}/opt/%{name}/%{name}ln -sf /opt/%{name}/%{name} %{buildroot}%{_bindir}/%{name}%post%preun%files/opt/%{name}/%{name}%{_bindir}/%{name}# /opt/%{name}/config/appsettings.json%changelog

这份清单简洁明了,每一行的功能都一目了然。我想强调几点。

%{buildroot}是文件中最重要的宏。构建软件包时,会创建一个虚拟根系统,该宏指向它。安装软件包时,它将指向根系统的链接。通常情况下,该宏的值为“/”。

尽可能多地使用宏也很重要。例如,Alt Linux 中的 %{_bindir} 是“/usr/bin”。在其他存储库中,它可能只是“/bin”或其他名称。因此,宏越多越好。但是,我找不到指向 /opt 的宏。

在 Linux 上构建 .NET Core 解决方案时,我在更新 NuGet 包时遇到了证书验证问题。网上有很多解决这个问题的技巧。禁用验证对我有帮助。我认为这是可以接受的,也很方便。请记住,我们的目标是从管道运行所有内容,因此复杂的解决方案可能不合适。

export DOTNET_NUGET_SIGNATURE_VERIFICATION=false 

我还想指出构建 .NET 应用程序的一些细微差别。我决定将整个解决方案打包成一个可执行文件。这大大简化了 spec 文件的创建。但这可能会导致以后出现问题,在这种情况下,我们将不得不采用传统的方式构建,复制所有内容,而不是直接复制一个文件。

dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true

关于 spec 文件,我最后想指出的是,如果你仔细观察,会发现第 48 行(也就是写入配置文件的部分)被注释掉了。这是为了在开发过程中,当我把程序变得更复杂,以便调试它在 Linux 下运行的所有细微差别时,留出一些余地。

准备文件

重要!请注意:我认为最重要的细节是从头开始阅读文章!如果你还没读过,也请从头开始。否则,你可能会看不清楚。

因此,我们有一个必需的 Version 字段和一个 Source 字段。在示例中,Source 通常这样描述:

Source: %name-%version.tar

这可以很容易地改变,然后在管道中完成,为静态名称,这在管道阶段之间形成工件时更加方便。

但是!这里有个陷阱!源文件可以任意命名,但它的结构严格依赖于 %name 和 %version 宏。

rpmbuild 脚本解压存档并运行命令

cd ~/RPM/BUILD/%name-%version

当然,从 spec 文件中替换所需的值。如果找不到此目录,脚本将因错误而终止。

下面是我们的程序需要的 tar.gz 存档的示例,其名称为 utestrpm,版本为 1.0.2,例如:

因此,仅仅将程序打包到存档中是不够的。您需要创建一个名为 %name-%version 的目录。将源文件放入其中,然后将该目录打包到存档中。

我几乎可以肯定这种行为是可以定制的。但何必呢?把这个功能纳入到流程中要容易得多。

召集团队

这是最简单的部分。我们调用命令并等待它成功完成。

rpmbuild -ba utestrpm.spec

请注意:需要注意的是,默认情况下,此命令无法从特权用户(root 或 sudo)调用。我在网上找到了一篇文章,解释了为什么这是正确的。我没有读过这篇文章,所以就照搬了。

结论:您必须在管道中创建一个非特权用户并在其下执行命令。

设置 CI/CD

原则上,如果 GitLab 和运行器已经设置好了,可以跳过这一步。但是,为了让一切按我想要的方式运行,我需要 80 次提交。而且,在我完成所有实验并准备好将所有工作应用到目标项目之前,还需要大约 80 次提交。我不想让我的主仓库被这些垃圾弄得乱七八糟。

我决定在我的办公电脑上使用 Docker 设置 GitLab 和一个 Runner。

安装 GitLab

docker run --detach \--hostname 172.17.0.2 \--publish 443:443 --publish 80:80 --publish 22:22 \--name gitlab \--restart always \gitlab/gitlab-ce:latest

注意主机名。您可能需要使用其他值。我将在下面解释具体值以及原因。

安装 GitLab 后(启动需要几分钟)

登录名为 root。可以通过运行以下命令找到密码

 docker exec -it gitlab cat /etc/gitlab/initial_root_password

请注意:快速执行命令很重要,因为文件会在短时间后被删除。

让我们安装 GitLab。创建一个组和一个项目。它看起来应该像这样。

在 GitLab 中创建 Runner

需要注意的是,这可能会造成混淆。在 GitLab 中,您需要创建一个名为 Runner 的实体。该实体将拥有所有权(Runner 可以是本地的、组的等等)。它还包含一些设置——何时运行、在哪些项目上运行等等。

在本文中,我创建了一个单组运行器。要创建它,请转到组部分。在我的示例中,它被称为 lnx,然后选择“Build/Runners”。

你会看到跑步者。我有一个,这就够了。还有一个按钮可以创建新的runner。

让我们创建一个可以在所有提交上运行的新运行器。

注意,点击“创建跑步者”后,它会将我重定向到哪个地址。

这个 IP 的出现是因为我分配了那个主机名。只需在浏览器中将其替换为 localhost,我们就能找到所需的内容。这只是暂时的不便,理想情况下我们只需要忍受一次,但它会为我们节省很多后续的精力。我很快会解释原因。

请注意:请务必复制标有感叹号的文本。您将无法再次访问它。它是最终配置所必需的。

如果我们回到runner名单页面,我们会看到一位注册跑步者的状态为“从未联系过”

创建并注册 gitlab-runner 容器

正如我之前所写,运行器既是 GitLab 中的一个实体,也是安装在特定工作站上的特殊程序。该程序将执行管道命令。要确定 GitLab 将调用哪个工作站,您需要将 GitLab 运行器与该工作站关联起来。

虚拟机通常是创建运行器的理想选择。您可以创建多个虚拟机,并为其分配所需的资源等等。要运行管道,只需一个安装了运行器程序的 Docker 镜像即可。要安装它,请运行以下命令。

docker run -d --name gitlab-runner --restart always \-v /srv/gitlab-runner/config:/etc/gitlab-runner \-v /var/run/docker.sock:/var/run/docker.sock \gitlab/gitlab-runner:latest

因此,我们有两个 docker 镜像

最后一步是注册参赛者。你需要给参赛队伍打电话。

docker exec -it gitlab-runner /bin/bash

我们将连接到 Docker 容器控制台。将复制的文本粘贴到控制台中。每次操作的令牌都会有所不同。

gitlab-runner register  --url http://172.17.0.2  --token glrt-570XNtiakleL5Dn_tzWUrmc6MwpvOjEKdDoyCnU6MQ8.01.171afrob3

接下来,系统会要求我们指定地址——我们保留默认地址。为运行器命名。指定运行器的启动方式——输入 docker。并指定默认镜像。我使用了 alt:p10。

GItlab 中的运行器应该会变成绿色,并显示“在线”状态。好啦,我们快完成了。现在只剩下编写管道了。

为什么 172.17.0.2 这个地址受到如此多的关注?

在继续之前,我们有必要解释一下主机名 172.17.0.2 的由来。我将介绍一种快速简便(尽管略显不妥)的方法来配置运行器,使其正常工作,而无需任何不必要的工作。这仅适用于测试假设和作为临时措施。仅此而已。

问题是,Docker 容器通过它们自己的网络相互通信,而这个网络由 Docker 管理。你可以在 Docker 中添加自己的网络,分配一系列 IP 地址,并将每个镜像与该网络内的特定 IP 地址关联。这没问题。但我们正在构建一个 MVP 解决方案,它只服务于一个目的。在我看来,花时间完美配置 Docker 是没有必要的。

解决方案是查看默认网络,找出分配给 GitLab 容器的 IP 地址。让我们运行以下命令:

docker network ls

就我而言,我得到了以下结果

请注意名为“bridge”的网络。除非您删除它,否则它始终存在。您创建的任何容器都将链接到此网络。

要查看哪个 IP 地址与容器关联,可以运行命令

docker network inspect bridge

这是这个 IP 地址。

请注意:由于我们没有为 gitlab 容器分配此 IP 地址,重启计算机后,gitlab-runner 容器很可能会先启动,然后它会占用此 IP 地址。这个问题可以轻松解决,只需停止所有容器,然后先启动 gitlab,再启动 gitlab-runner 即可。

文字太多了,仍然不明白为什么这如此重要。

这一点很重要,因为 gitlab-runner 容器内部也包含 Docker。我们的流水线将在内部 Docker 中运行,下载 alt:p10 镜像,并在每一步上传源文件,等等。

这只是 Docker in Docker 方法的一个经典示例。

通过指定主机名,我们配置了 GitLab,以便运行器能够通过 IP 地址而不是域名访问它。这样就可以正常工作了。例如http://172.17.0.2/groups/lnx/utestrpm。

如果我们的 GitLab 配置正确,并分配了一个可在您的网络上访问的有效域名,那么就不会有问题。我们可以使用正确的主机名成功访问它。

在我们的例子中,在本地机器上,我们会遇到一些麻烦。我们必须在 hosts 文件中指定主机名,重启,还要确保为容器分配正确的 IP 地址,或者在 Docker 中创建自定义网络等等。在我看来,将 IP 地址指定为主机名更简单,而且一切正常。

创建管道

你已经了解了什么是管道以及它的用途。如果你不知道,任何神经网络都会很乐意告诉你。我们将提供一个 GitLab 管道示例,它将构建一个 RPM 包,并涵盖其中的关键点。

准备档案(prepare_tar)

此阶段旨在生成成功执行 rpmbuild 脚本所需的工件。

第一步是确定应用程序版本。在实际开发中,版本增量非常重要。版本值放在项目中包含的 env.json 文件中。

{"service": "1.0.2"
}

安装了 jq 实用程序并用于查找版本。该版本被放置在 $VERSION 变量中。为了方便起见,还确定了未来存档的根文件夹名称。

- VERSION=$(jq -r '.service' utestrpm/env.json)- echo "VERSION=$VERSION" > version.env- FOLDER="utestrpm-$VERSION"

下一步是生成归档文件。tar 命令非常复杂且用途广泛,因此我将在评论中解释其工作原理。

- tar -czf source_file.tar.gz --transform "s,^,$FOLDER/," utestrpm.sln utestrpm/

下一步是生成与当前版本匹配的 spec 文件。最简单的方法是直接替换基础模板文件中的版本,并相应地重命名。

- sed "s/^Version:.*/Version:$VERSION/" utestrpm_template.spec > utestrpm.spec

我们需要发送到下一步的只是正确的存档和正确的 spec 文件,以及版本信息。.rpmmacros 文件已存在于项目中,永远不会被更改。我们会在构建过程中将其复制到正确的位置。

发布 RPM(build_rpm)

最后的润色。首先,让我们安装所需的依赖项。这些包括 rpm-build 包本身、dotnet-sdk 环境和 su 实用程序,我们需要它来防止脚本崩溃(您无法以 root 身份运行它)。

下一步是创建一个用户、他们的主文件夹以及其中的正确目录结构。

- useradd -m -s /bin/bash $RPM_USER- mkdir -p $RPM_DIR/{SOURCES,SPECS,RPMS,SRPMS}

需要复制这些文件并更改所有者。目前是 root 用户,但应该是我们的服务用户。

TODO:路径是硬编码的 - 需要修复:)

- cp source_file.tar.gz $RPM_DIR/SOURCES/- cp utestrpm.spec $RPM_DIR/SPECS/- cp .rpmmacros /home/$RPM_USER- chown -R pipe_builder:pipe_builder /home/pipe_builder/

好吧,最好的部分是 - 我们运行脚本

- su - $RPM_USER -c "cd $RPM_DIR/SPECS && rpmbuild -ba utestrpm.spec"- cp $RPM_DIR/RPMS/x86_64/*.rpm ./- ls -al *.rpm

请注意:直接运行 su 会失败,因为我们在当前线程中仍然是 root 权限。您可以运行 whoami 命令来验证这一点。

-c 选项可以解决问题,立即在我们需要的用户下执行命令。

结果

我不知道你的情况,但我一切正常。RPM 包已创建,版本号也正是我需要的。包可以下载了。在实际项目中,我会添加测试、检查/复检、发布并部署到沙盒,以及将包保存到 Nexus,但那是另一回事。

研究阶段尚未完成。然而,研究结果对本文来说已经足够了。在将这些研究成果转化为实际项目之前,我们需要完成以下步骤:

  • 将程序设为服务,配置自动启动(systemctl enable),编辑 spec 文件以便在包安装期间进行配置。

  • 正确输入日志,以便可以通过 journalctl 读取

  • 添加配置文件,例如 appsettings.json。确保它们已安装并应用。

我可能会写另一篇文章,但肯定会更短。

感谢您的关注。希望本文能对大家有所帮助。

http://www.dtcms.com/a/557197.html

相关文章:

  • Go语言中error的保姆级认知学习教程,由易到难
  • Go语言中通过get请求获取api.open-meteo.com网站的天气数据
  • 哪些网站可以做微课技术支持 英铭网站建设
  • STM32位带操作理论实践
  • 住房和城乡建设部的网站首页不懂网站怎么做平台
  • 禁止Windows 10升级至Windows 11的方法
  • 人工智能之数学基础:随机变量函数的分布(离散和连续)
  • 30.16.2.表现层框架设计
  • DMS 迁移错误:String Length Exceeds DDL Length 完整解决方案
  • 福建建设厅网站官网宣传推广方案
  • 网站搭建的步骤百度网站怎样做
  • 网站的建设与开发discover wordpress
  • apk反编译修改教程系列-----读懂 Android 签名机制:从 V1 到 V4的签名区别
  • 人工智能本体论!
  • 将Git项目的所有远程分支打包成压缩包文件
  • 做液压的公司网站佛山网站建设格式有哪些
  • 深圳做微商网站的公司二维码生成器app
  • WebClient发送请求示例
  • Wireshark TS | 接收数据超出接收窗口续
  • mapset的使用
  • 要事优先-深耕目标
  • 禄劝彝族苗族网站建设食品 技术支持 东莞网站建设
  • 宁波市省网站建设济南工程建设交易信息网
  • 伯克利哈斯商学院的金融工程硕士(MFE)
  • 政安晨【零基础玩转开源AI项目】video-subtitle-remover 去除视频字幕水印(图像也可以)(基于Ubuntu Linux系统)
  • 温州市名城建设集团有限公司网站二级域名如何申请
  • 【C++】模拟算法习题
  • QLoRA基础知识和微调原理学习
  • 在 vscode 中配置juypter notebook 插件
  • 石家庄好用的招聘网站门户网站网站建设