前置知识

  1. 什么是 gitlab?
  2. 什么是 CI/CD?
  3. Windows 下如何通过命令行编译 KeilIAR 工程?
  4. 如何安装 gitlab-runner?

思路

我尝试过的方案有 3 个:

  1. 将 gitlab-runner 安装在本地 Win10 里,executor 选择的是 shell,本地安装了 Keil 和 IAR
  2. 在一台 Windows10 电脑安装 docker(Windows 后端),使用 mcr.microsoft.com/windows:20H2 镜像,在该 docker 实例中安装 keil 和 IAR
  3. gitlab-runner 安装在 Ubuntu 服务器里,executor 选择 virtualbox,在 Ubuntu 服务器安装 virtualbox,在 virtualbox 内安装 Win10,在 Win10 安装 Keil 和 IAR

各方案优劣

gitlab-runner(Win10) + executor shell

此方案是可行的,不过每次编译,gitlab-runner 都会使用自己生成的 PATH,必须在 .gitlab-ci.yml 里写 Keil 和 IAR 的绝对路径,否则无法找到 Keil 和 IAR。另外,本地 PC shell 作为 gitlab-runner 的风险极高,若 .gitlab-ci.yml 中写了删除某些文件的命令,当路径写错时,很可能会把本地 PC 的重要文件删除。此方案通用性不好,危险系数还很高,不建议使用。

gitlab-runner(Win10) + executor docker + mcr.microsoft.com/windows:20H2 镜像

经过一番搜索,官方明确说明,这个镜像不支持 GUI 操作,也就无法使用远程桌面(RDP)或者 VNC 登陆,这就无法安装 Keil 和 IAR 了,退一步讲,即使让我成功安装进去了,Keil 编译时会启动 GUI(可以设置在编译时隐藏 GUI),但经过我的测试,没有 GUI 是无法启动 Keil 编译的,如何测试:Win10 + WSL2,直接打开 WSL 调用 Keil 可正常编译,通过 ssh 连接 WSL(没有 GUI),Keil 编译会报错,根本无法启动编译,遂放弃此方案。
以下两个链接是关于 docker Windows 镜像不支持 GUI 的说明:
链接1
链接2

gitlab-runner(Ubuntu) + executor virtualbox + Win10(21H2)

参考上面 链接2 中的说明:

Greetings,
Please use VMs instead. Your test workflow doesn’t need to change.
Containers are suitable for deploy web and console applications.
The thread that referenced by you is related to Ubuntu.

再结合 gitlab 官方文档(virtualbox)

VirtualBox allows you to use VirtualBox’s virtualization to provide a clean build environment for every build. This executor supports all systems that can be run on VirtualBox. The only requirement is that the virtual machine exposes an SSH server and provides a shell compatible with Bash or PowerShell.

根据 gitlab 官方的说法,这个方案可以支持任意操作系统(操作系统要能在 VirtualBox 中运行),每次启动编译,都会创建一个虚拟机快照(若原始虚拟机有更新才创建新快照,无更新则使用旧快照),并在快照的基础上启动虚拟机,进行编译。这个方案可解决方案 1 可能删除 gitlab-runner 宿主系统文件的风险,在每次执行完毕后可做到无残留,正是我选择的方案。以下是这个方案的架构:

B 服务器 - ubuntu
A 服务器 - ubuntu
本地 PC
virtualbox
Windows10
msys64
push
CI/CD
ssh
gitlab-runner
sshd
pwsh
git
gitlab-runner
Keil
IAR
make
gitlab 网站
git

具体如何搭建环境可参考 gitlab 官方文档(virtualbox),在 Checklist for Windows VMs 中,我使用的是 Use native OpenSSH and PowerShell 方案

方案 3 踩坑

坑 1:

gitlab-runner ssh 连接 virtualbox 里的 Win10 报错ssh Dial() error: ssh: handshake failed: knownhosts: key is unknown

Running with gitlab-runner 15.0.0 (febb2a09)
  on virtual_box_win10 WXfibXkD
Preparing the "virtualbox" executor
10:52
Using VirtualBox version 6.1.34r150636 executor...
Creating new VM...
ERROR: Preparation failed: ssh Dial() error: ssh: handshake failed: knownhosts: key is unknown
Will be retried in 3s ...
Using VirtualBox version 6.1.34r150636 executor...
Creating new VM...
ERROR: Preparation failed: ssh Dial() error: ssh: handshake failed: knownhosts: key is unknown
Will be retried in 3s ...
Using VirtualBox version 6.1.34r150636 executor...
Creating new VM...
ERROR: Preparation failed: ssh Dial() error: ssh: handshake failed: knownhosts: key is unknown
Will be retried in 3s ...
ERROR: Job failed (system failure): ssh Dial() error: ssh: handshake failed: knownhosts: key is unknown

查看 gitlab-runner 源码,在 这里 看到了DisableStrictHostKeyChecking的说明:

package ssh

//nolint:lll
type Config struct {
	User                         string `toml:"user,omitempty" json:"user" long:"user" env:"SSH_USER" description:"User name"`
	Password                     string `toml:"password,omitempty" json:"password" long:"password" env:"SSH_PASSWORD" description:"User password"`
	Host                         string `toml:"host,omitempty" json:"host" long:"host" env:"SSH_HOST" description:"Remote host"`
	Port                         string `toml:"port,omitempty" json:"port" long:"port" env:"SSH_PORT" description:"Remote host port"`
	IdentityFile                 string `toml:"identity_file,omitempty" json:"identity_file" long:"identity-file" env:"SSH_IDENTITY_FILE" description:"Identity file to be used"`
	DisableStrictHostKeyChecking *bool  `toml:"disable_strict_host_key_checking,omitempty" json:"disable_strict_host_key_checking" long:"disable-strict-host-key-checking" env:"DISABLE_STRICT_HOST_KEY_CHECKING" description:"Disable SSH strict host key checking"`
	KnownHostsFile               string `toml:"known_hosts_file,omitempty" json:"known_hosts_file" long:"known-hosts-file" env:"KNOWN_HOSTS_FILE" description:"Location of known_hosts file. Defaults to ~/.ssh/known_hosts"`
}

func (c *Config) ShouldDisableStrictHostKeyChecking() bool {
	return c.DisableStrictHostKeyChecking != nil && *c.DisableStrictHostKeyChecking
}

根据说明和类型,在 config.toml 的 [runners.ssh]字段之下添加这句话:

disable_strict_host_key_checking = true

重启 gitlab-runner 即可:

sudo gitlab-runner restart

坑 2:只支持 pwsh,不支持 powershell

在最后一步:

Add shell pwsh to config.toml

我遇到了一个坑,因为之前搜索说 pwsh 就是 powershell,所以我在 config.toml 中直接将 powershell 作为 shell,实际运行时报错virtualbox doesn't support shells that require script file

Running with gitlab-runner 15.0.0 (febb2a09)
  on virtual_box_win10 WXfibXkD
Preparing the "virtualbox" executor
00:09
ERROR: Preparation failed: virtualbox doesn't support shells that require script file
Will be retried in 3s ...
ERROR: Preparation failed: virtualbox doesn't support shells that require script file
Will be retried in 3s ...
ERROR: Preparation failed: virtualbox doesn't support shells that require script file
Will be retried in 3s ...
ERROR: Job failed (system failure): virtualbox doesn't support shells that require script file

分析 gitlab-runner 源码 发现是 这里 打印的:

func (s *executor) validateConfig() error {
	if s.Config.VirtualBox.BaseName == "" {
		return errors.New("missing BaseName setting from VirtualBox configuration")
	}

	if s.BuildShell.PassFile {
		return errors.New("virtualbox doesn't support shells that require script file")
	}

	if s.Config.SSH == nil {
		return errors.New("missing SSH config")
	}

	if s.Config.VirtualBox == nil {
		return errors.New("missing VirtualBox configuration")
	}

	return s.ValidateAllowedImages(s.Config.VirtualBox.AllowedImages)
}

搜索关键字 PassFile,在 这里 找到了它的赋值语句:

......
const (
	dockerWindowsExecutor = "docker-windows"

	SNPwsh       = "pwsh"
	SNPowershell = "powershell"

	// Before executing a script, powershell parses it.
	// A `ParserError` can then be thrown if a parsing error is found.
	// Those errors are not catched by the powershell_trap_script thus causing the job to hang
	// To avoid this problem, the PwshValidationScript is used to validate the given script and eventually to cause
	// the job to fail if a `ParserError` is thrown
	pwshJSONTerminationScript = `
param (
	[Parameter(Mandatory=$true,Position=1)]
	[string]$Path
)
......
func (b *PowerShell) GetConfiguration(info common.ShellScriptInfo) (*common.ShellConfiguration, error) {
	script := &common.ShellConfiguration{
		Command:       b.Shell,
		PassFile:      b.Shell != SNPwsh && info.Build.Runner.Executor != dockerWindowsExecutor,
		Extension:     "ps1",
		DockerCommand: PowershellDockerCmd(b.Shell),
	}

	if info.User != "" {
		if script.PassFile {
			return nil, &powershellChangeUserError{
				shell:    b.Shell,
				executor: info.Build.Runner.Executor,
			}
		}

		script.Command = "su"
		if runtime.GOOS == OSLinux {
			script.Arguments = append(script.Arguments, "-s", "/usr/bin/"+b.Shell)
		}
		script.Arguments = append(
			script.Arguments,
			info.User,
			"-c",
			b.Shell+" "+strings.Join(stdinCmdArgs(), " "),
		)
	} else {
		script.Arguments = b.scriptArgs(script)
	}

	script.CmdLine = strings.Join(append([]string{script.Command}, script.Arguments...), " ")

	return script, nil
}

可见,当 Executor不是dockerWindows,且Shell类型不是pwsh时,PassFile会被赋值为true,导致前面的报错。所以,在本方案中,config.toml 只能将 pwsh 作为 shell,归根结底,还是我没有去了解 pwsh 和 powershell 的差别,而引发了这个疏忽,Windows10 自带的 powershell 是 powershell 5 版本,而 pwsh 是 powershell 6 以上版本,pwsh 更易用于开发,在 virtualbox + Win10 的情况下,gitlab 官方只支持 pwsh,而没有支持 powershell 5。
最终 config.toml 如下

concurrent = 1
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "virtual_box_win10"
  url = "http://xxx.yyy.zzz/"
  token = "WXfibXkDcdGiYCbNBuoz"
  executor = "virtualbox"
  shell = "pwsh"
  [runners.custom_build_dir]
  [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.ssh]
    user = "AAAA"
    password = "BBBBCCCC"
    identity_file = "/root/.ssh/virtualbox_id_rsa"
    disable_strict_host_key_checking = true
  [runners.virtualbox]
    base_name = "Windows10_x64"
    base_folder = ""
    disable_snapshots = false

坑 3:Win10 pwsh 使用的是 GB2312 编码,而 gitlab-runner 传给它的却是 UTF-8 编码

Win10 pwsh 使用的是 GB2312 编码,而 gitlab-runner 传给它的却是 UTF-8 编码,中文编码错误,导致 pwsh 语法错误。
具体分析和解决方案,我写了一篇文章,可直接阅读 这篇文章
贴一下最终解决方案(修改executors/virtualbox/virtualbox.go并编译),使用mahonia,在 pwsh 命令发出前,将其自 UTF-8 转码为 GBK,问题即可修复:

package virtualbox

import (
	"errors"
	"fmt"
	"time"

	"gitlab.com/gitlab-org/gitlab-runner/common"
	"gitlab.com/gitlab-org/gitlab-runner/executors"
	"gitlab.com/gitlab-org/gitlab-runner/executors/vm"
	"gitlab.com/gitlab-org/gitlab-runner/helpers/ssh"
	vbox "gitlab.com/gitlab-org/gitlab-runner/helpers/virtualbox"

	"github.com/NuoMinMin/mahonia" // UTF-8 转 GBK
)
......
func (s *executor) Run(cmd common.ExecutorCommand) error {
	// s.Println("s.BuildShell.CmdLine: " + s.BuildShell.CmdLine)
	// s.Println("cmd.Script: " + cmd.Script)
	// 将 UTF-8 转换成 gbk, 因为 pwsh 用的是 gbk 编码, 不这么修改的话, 有些字会被解析成 " 导致执行 pwsh 报错
	cmd.Script, _ = mahonia.NewEncoder("gbk").ConvertStringOK(cmd.Script)
	err := s.sshCommand.Run(cmd.Context, ssh.Command{
		Command: s.BuildShell.CmdLine,
		Stdin:   cmd.Script,
	})
	if exitError, ok := err.(*ssh.ExitError); ok {
		exitCode := exitError.ExitCode()
		err = &common.BuildError{Inner: err, ExitCode: exitCode}
	}
	return err
}
......

这个 bug,gitlab-runner v15.0.0 和 v15.1.0 测试均存在,上述修复方案并非通用修复方案,会影响 virtualbox + Ubuntu 的使用,我已经给 gitlab-runner 官方仓库提交了 Issue,期待官方的修复方案。
一个通用的修复思路:

  1. 在 powershell.go 中检测 Windows 的编码类型
  2. 在 powershell.go 中生成 pwsh 命令后,返回前将其由 UTF-8 编码转为刚刚获得的 Windows 的编码类型

另一个修复思路:

  • 在 virtualbox 安装 Win10 后,将系统编码类型改为 UTF-8,可参考 这篇文章,我没有验证过,理论上可行
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐