我尝试过的方案有 3 个:
此方案是可行的,不过每次编译,gitlab-runner 都会使用自己生成的 PATH,必须在 .gitlab-ci.yml 里写 Keil 和 IAR 的绝对路径,否则无法找到 Keil 和 IAR。另外,本地 PC shell 作为 gitlab-runner 的风险极高,若 .gitlab-ci.yml 中写了删除某些文件的命令,当路径写错时,很可能会把本地 PC 的重要文件删除。此方案通用性不好,危险系数还很高,不建议使用。
经过一番搜索,官方明确说明,这个镜像不支持 GUI 操作,也就无法使用远程桌面(RDP)或者 VNC 登陆,这就无法安装 Keil 和 IAR 了,退一步讲,即使让我成功安装进去了,Keil 编译时会启动 GUI(可以设置在编译时隐藏 GUI),但经过我的测试,没有 GUI 是无法启动 Keil 编译的,如何测试:Win10 + WSL2,直接打开 WSL 调用 Keil 可正常编译,通过 ssh 连接 WSL(没有 GUI),Keil 编译会报错,根本无法启动编译,遂放弃此方案。
以下两个链接是关于 docker Windows 镜像不支持 GUI 的说明:
链接1
链接2
参考上面 链接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 宿主系统文件的风险,在每次执行完毕后可做到无残留,正是我选择的方案。以下是这个方案的架构:
具体如何搭建环境可参考 gitlab 官方文档(virtualbox),在 Checklist for Windows VMs 中,我使用的是 Use native OpenSSH and PowerShell 方案
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
在最后一步:
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
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,期待官方的修复方案。
一个通用的修复思路:
另一个修复思路: