DevOps学习 -- 使用 GitLab、GiLab-Runner

DevOps学习 -- 使用 GitLab、GiLab-Runner

下图为我们的自动化部署流程

用户推送代码到 GitLab,GitLab 将配置好的代码更新事件(流水线)发送到 GitLab-Runner或其他CI/DI软件,CI/DI软件完成自动部署。

一、安装配置部署 GitLab

安装 GitLab

  • 1.获取 GitLab,官方地址:https://packages.gitlab.com/gitlab/gitlab-ce,因为我使用的是 Centos8,所以我下载了 wget --content-disposition https://packages.gitlab.com/gitlab/gitlab-ce/packages/el/8/gitlab-ce-15.3.2-ce.0.el8.x86_64.rpm/download.rpm

  • 2.安装 GitLab,使用命令 sudo yum install gitlab-ce-15.3.2-ce.0.el8.x86_64.rpm,如果报缺少包,则缺哪个安哪个。

  • 3.执行gitlab-ctl reconfigure,重新配置 GitLab。

  • 4.查看登陆密码(24小时后失效),使用命令cat /etc/gitlab/initial_root_password

  • 5.在网页上打开GitLab,端口号默认为80,用户名默认为:root

  • 6.修改密码:右上角->Prefrences->Password

  • 7.修改语言:右上角->Prefrences->Prefrences->Localization->Language

新建一个项目,并拉取

  • 1.首页点击新建项目
  • 2.填写相关信息,点击创建
  • 3.克隆项目(需要替换域名为当前gitlab所在主机的IP)
  • 4.使用 git clone http://gitlab.example.com/gitlab-instance-7a3f9f21/xlogin.git 拉取项目

二、安装配置 GitLab-Runner

前面我们讲过,GitLab-Runner 用于接收 GitLab 的更新事件,然后运行我们事先配置好的事件流程(流水线),如 build->test->deploy等。由此可以看出 GitLab-Runner 只是一个执行器,它接收来自 GitLab 特定格式的元数据,然后按照配置好的执行策略(配置文件)来运行;这个配置文件默认为项目根目录下的.gitlab-ci.yml文件。

  • 1.安装 GitLab-Runner,yum install gitlab-runner
  • 2.配置 GitLab-Runner:sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner,指定了gitlab-runner的用户名和工作目录
  • 3.启动 GitLab-Runner:gitlab-runner start
  • 4.将 GitLab-Runner 注册到 GitLab 上:gitlab-runner register。如下图:

Enter an executor:表示通过什么方式运行流水线中配置的 script,如果在本机运行编译部署等任务,则这里填写 shell;其他选项这里暂不做讨论。
注册令牌获取方式:菜单 -> 管理员 -> 管理中心 -> 概览 -> Runner -> 注册一个实例Runner

  • 5.修改配置文件,配置文件地址:/etc/gitlab-runner/config.toml

添加 clone_url 项(不配置通过git拉取的时候会报错)

  • 6.重新启动 gitlab-runner restart
  • 7.补充:如果需要在这台机器上使用 maven、gradle 等进行构建,那么需要安装这些软件。如果在当前机器上进行构建 git 是必须安装的。

三、配置流水线

如前所述,流水线是我们配置自动化部署的关键,用户将代码推送到仓库时,GitLab会自动的调用流水线,GitLab 会将流水线中配置的行为交由 GitLab-Runner 来执行。

---
variables:
  MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository
    -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN
    -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true
    -DdeployAtEnd=true"
image: maven:3.3.9-jdk-8
cache:
  paths:
  - ".m2/repository"
verify:
  stage: test
  script:
  - 'mvn $MAVEN_CLI_OPTS verify'
  except:
    variables:
    - "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
deploy:
  stage: deploy
  script:
  - if [ ! -f /etc/gitlab/ci_settings.xml ]; then echo "ci_settings 文件不存在,具体请看介绍:
    https://docs.gitlab.com/ee/user/packages/maven_repository/index.html#create-maven-packages-with-gitlab-cicd
    "; fi
  - 'echo "部署到远程仓库"'
  - 'mvn $MAVEN_CLI_OPTS deploy -s /etc/gitlab/ci_settings.xml'
  - 'mvn package'
  - 'echo "部署到远程机器"'
  - 'python3 /root/deploy.py ${CI_PROJECT_DIR}/target/*.jar ${CI_PROJECT_TITLE} /root/config.yml'
  - 'echo "部署成功"'
  only:
    variables:
    - "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"

分为验证和部署两个阶段,阶段一验证代码,阶段二分别部署代码到远程 maven 仓库和远程机器

  • 3.现在当用户对目标仓库进行 push 的时候,GitLab 会自动运行流水线,首先将事件发送给 GitLab-Runner,然后 GitLab-Runner 拉取最新仓库并运行配置的流水线任务。
  • 4.配置文件中可以使用一些预定义的 CI 环境变量,可查阅:https://docs.gitlab.com/ee/ci/variables/predefined_variables.html

部署到远程 maven 仓库

这一步主要体现在 mvn $MAVEN_CLI_OPTS deploy -s /etc/gitlab/ci_settings.xml,这里使用了 maven 插件,相应的 pom.xml 文件为:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <distributionManagement>
        <repository>
            <id>repo</id>
            <url>http://123.123.132.123:8081/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>repo</id>
            <url>http://123.123.132.123:8081/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>

</project>

上面的 project.distributionManagement.repository.id 和 ci_settings.xml 文件中的 servers.server.id 一一对应。ci_setttings.xml 文件和 .m2/settings.xml 文件类似,属于 maven 的配置文件。在 ci_settings.xml 文件中,我们配置了远程 maven 仓库的账号密码,由此 maven 可以将打包后的结果 push 到远程 maven 仓库。
ci_settings.xml文件内容为:

<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
  <servers>
    <server>
      <id>repo</id>
      <username>admin</username>
      <password>123456</password>
    </server>
  </servers>
</settings>

部署到远程机(并运行)

前面用到的部署脚本deploy.py运行环境为:至少python3.8、PyYAML包,代码如下:

import yaml
import os
import sys


class Executor:

    def __init__(self, argv: list):
        self.file = argv[3]
        self.argv = argv

    def _read_yml(self, file, mode='r', encoding='utf8') -> dict:
        """
        读取 yml 文件
        :param file: 文件名
        :param mode: 打开文件模式,默认为 'r'
        :param encoding: 编码格式
        :return: dict
        """
        with open(file, mode, encoding=encoding) as f:
            res = yaml.full_load(f)
        return res

    def _validate(self, data: dict) -> bool:
        """
        验证数据
        :param data: 数据
        :return: bool
        """
        params = self.argv
        if len(params) < 3:
            return False
        self.jar_path = params[1]
        self.project_name = params[2]
        return True

    def _run_command(self,command: str) -> list:
        """
        运行本地命令
        :param command: 命令
        :return: 结果
        """
        with os.popen(command) as f:
            strs = f.readlines()
        return strs

    def _run_remote_command(self, data: dict) -> None:
        """
        运行远程命令
        :param data: 包含url、identity_file、command、username
        :return: None
        """
        ip = str(data.get("url"))
        identity_file = str(data.get("identity_file"))
        username = str(data.get("username"))
        command = str(data.get("command"))
        if len(ip) == 0 | len(identity_file) == 0 | len(command) == 0 | len(username) == 0:
            print("远程命令参数出错,【ip】:【%s】【identity_file】:【%s】【command】:【%s】" % (ip, identity_file, command))
            return
        remote_command = f"ssh -i {identity_file} {username}@{ip} '{command}'"
        print("开始执行远程命令:%s" % remote_command)
        strs = self._run_command(remote_command)
        print("远程命令执行结果:【%s】" % strs)
        print("远程命令【%s】执行结束." % remote_command)

    def _ssh(self, data: dict) -> None:
        """
        mode = ssh
        :param data: 数据
        :return: None
        """
        print("模式:【ssh】")
        has_command = str(data.get("has_command"))
        run_command = str(data.get("command"))
        if (has_command == "True") & len(run_command) > 0:
            self._run_remote_command(data)
        else:
            print("命令未执行,请检查参数.")

    def _scp(self, data: dict) -> None:
        """
        mode = scp
        :param data: 数据
        :return: None
        """
        print("模式:【scp】")
        identity_file = data.get("identity_file")
        jar_path = self.jar_path
        username = data.get("username")
        spliter = os.sep
        project_name = self.project_name
        ip = data.get("url")
        base_dir = data.get("base_dir")
        has_command = str(data.get("has_command"))
        run_command = str(data.get("command"))
        success = False

        command = f"scp -i {identity_file} {jar_path} {username}@{ip}:{base_dir}{spliter}{project_name}{spliter} "
        print("开始执行命令: %s" % command)
        strs = self._run_command(command)
        if len(strs) == 0:
            success = True
            print("命令执行成功")
        else:
            print("命令【%s】执行失败" % command)
            print("错误信息: %s" % strs)
        if success & (has_command == "True") & len(run_command) > 0:
            self._run_remote_command(data)



    def _sftp(self, data: dict) -> None:
        """
        mode = sftp
        :param data: 数据
        :return: None
        """
        print("模式:【sftp】")
        print(data)

    def execute(self) -> None:
        """
        开始执行
        :return: None
        """
        data = self._read_yml(self.file)
        if not self._validate(data):
            print("配置文件:%s 格式错误" % self.file)
        deploy = data.get('deploy')
        # 找到当前项目需要部署机器
        needs = dict()
        for one_instance in deploy:
            ins = deploy.get(one_instance)
            if self.project_name == ins.get("project_name"):
                needs[one_instance] = ins
        print("%s 将被部署到 %s 台机器" % (self.project_name, str(len(needs))))
        for one_instance in needs:
            ins = needs.get(one_instance)
            print("%s 部署到 %s" % (self.project_name, one_instance))
            mode = ins.get('mode')
            # 通过反射方式调用函数
            mode = "_" + mode
            # 检查是否有 mode 函数 或者 属性
            if hasattr(self, mode):
                getattr(self, mode)(ins)
            else:
                print("未知模式:%s: %s" % (one_instance, mode))


if __name__ == "__main__":
    argv = sys.argv
    executor = Executor(argv)
    executor.execute()

部署脚本的配置文件config.yml如下:

deploy:
  ins1:
    ## 项目名称,脚本里用项目名称来匹配远程机,一个项目可配置多个远程机,从而部署到多个环境
    project_name: test
    url: 123.123.123.123
    username: root
    ## 私钥文件地址
    identity_file: /root/xxxx.pem
    ## 模式 -- 只实现了 ssh 和 scp 模式,ssh 仅运行命令,scp 将jar包部署到远程机
    mode: scp
    ## jar文件最终路径为 {base.dir}/{project_name}/
    base_dir: /box/
    ## 是否运行命令
    has_command: True
    ## 运行命令
    command: sh /box/test/start.sh

至此,配置完毕。

四、常见问题

GitLab、Git-Runner 运行卡顿?高 IO?

添加 swap 分区

  • 创建连续空间:dd if=/dev/zero of=/data/swap bs=512 count=8388616
  • 创建 swap 分区:mkswap /data/swap
  • 启动 swap 分区:swapon /data/swap
  • 在 /etc/fstab 文件中记录文件的名字,使系统重启后,swap 仍然有效 echo “/data/swap swap swap defaults 0 0” >> /etc/fstab
  • 查看 swap 分区:cat /proc/swaps

之后可以选择重新加载 gitlab:sudo gitlab-ctl reconfigure

GitLab-Runner 运行权限问题

通过 gitlab-runner 运行脚本使用的是前面配置的 gitlab-runner 用户角色,因为 gitlab-runner 在部署的时候使用了私钥文件,所以 gitlab-runner 用户角色必须有私钥文件的读权限。

posted @ 2022-09-10 19:41  zolmk  阅读(288)  评论(0编辑  收藏  举报