十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GitLab Runner部署与Executor选型实战指南

GitLab Runner部署与Executor选型实战指南 1. 为什么GitLab Runner不是“装上就能跑”而是CI/CD流水线的命脉所在你有没有遇到过这样的场景在GitLab上点下“Run Pipeline”页面转了几秒然后弹出一行冷冰冰的红色提示——This job is stuck because you don’t have any active runners configured.不是代码写错了不是YAML语法有问题甚至不是网络不通——只是因为那个叫gitlab-runner的进程压根没在你的服务器上真正“活”起来。这不是一个简单的安装任务而是一次对CI/CD底层执行机制的具象化落地。GitLab本身只负责调度、编排和展示它像一个指挥中心而真正扛起编译、测试、打包、部署这些重活的是分散在各处的Runner——它们才是流水线里真正挥汗如雨的工人。没有RunnerPipeline就只是纸上谈兵Runner配置错一步整个自动化流程就会卡在最基础的环节连echo Hello都执行不了。我第一次部署Runner时就在Ubuntu 22.04上用apt install gitlab-runner一键装完兴冲冲注册到GitLab结果发现所有job都显示pending状态栏永远是灰色。查日志只看到WARNING: Job failed: execution took longer than 3600 seconds但根本没执行任何命令。后来才明白默认安装的Runner是以root用户运行的而GitLab CI默认使用shellexecutor它会尝试以当前项目配置的user通常是gitlab-runner去执行脚本——权限冲突、家目录缺失、PATH环境变量不一致全堆在一起表面看是超时实则是身份错位导致的静默失败。这背后牵扯的是三个关键维度执行器executor选型逻辑、注册身份与权限模型、以及Runner与GitLab服务端的双向通信机制。Shell executor适合快速验证但生产环境几乎没人用Docker executor隔离性好却要求宿主机必须有Docker daemon且Runner用户有docker组权限Kubernetes executor强大但复杂度陡增。而注册过程中的token、url、description、tag-list每一个字段都不是可有可无的占位符——token是Runner的唯一身份证tag-list决定了它能接哪些Pipeline的jobdescription在GitLab UI里直接显示为Runner名称一旦拼写错误你在界面上根本找不到它。所以“部署gitlab-runner”这个标题本质是在问如何让一台物理机或虚拟机通过一个轻量级Go二进制程序稳定、安全、可追溯地承接来自GitLab的自动化任务指令并在本地完成真实世界里的构建与交付动作它不是DevOps的终点而是自动化交付能力的真正起点。接下来我会从零开始带你走完这条路径——不是照着官网文档复制粘贴而是每一步都告诉你“为什么必须这样”以及“如果跳过这步你会在三天后凌晨两点被报警电话叫醒”。2. 执行器选型Shell、Docker、DockerMachine哪一种才是你当前项目的“最优解”执行器Executor是Runner的核心心脏它决定了任务如何被执行、环境如何被准备、资源如何被隔离。GitLab Runner支持十余种executor但日常开发中真正高频使用的只有三种shell、docker、dockermachine。选择错误轻则构建失败重则污染宿主机环境甚至引发安全风险。这不是一个凭感觉选的选项而是一个需要结合项目现状、团队能力、基础设施成熟度做综合判断的技术决策。2.1 Shell Executor裸金属上的“快刀斩乱麻”但刀锋也最危险shellexecutor是最原始、最直接的方式。Runner注册后会以指定的系统用户默认gitlab-runner身份直接在宿主机的shell环境中执行.gitlab-ci.yml中定义的script命令。它不创建任何隔离环境所有命令都在宿主机全局上下文中运行。它的优势极其鲜明零学习成本、零额外依赖、启动最快。你不需要装Docker不需要配K8s只要Linux能跑bash它就能跑。我曾用它在一台老旧的树莓派上5分钟内就跑通了第一个Python单元测试Pipeline——pip install pytest pytest tests/干净利落。但代价同样致命环境不可复现、依赖易冲突、权限难管控。假设你的项目A需要Node.js 16项目B需要Node.js 18两个Pipeline同时触发shellexecutor会在同一台机器上先后执行nvm use 16和nvm use 18最终哪个版本生效取决于执行顺序和nvm的缓存机制。更严重的是如果某个job脚本里写了rm -rf /tmp/*它删的就是你整台服务器的/tmp而不是一个沙盒里的临时目录。去年我们团队就因一个未加防护的find . -name *.log -delete命令在CI中误删了生产数据库的归档日志目录导致恢复耗时47分钟。提示shellexecutor仅推荐用于三类场景本地开发环境快速验证CI脚本逻辑单项目、单技术栈、无外部依赖的极简构建如纯静态网站生成或作为临时调试工具配合--untracked参数手动触发job。生产环境请务必绕道。2.2 Docker Executor隔离性与便捷性的黄金平衡点dockerexecutor是目前绝大多数团队的默认选择。它的核心逻辑是Runner监听到job后会根据.gitlab-ci.yml中指定的image如node:18-alpine拉取对应镜像启动一个全新的Docker容器并将当前代码仓库克隆到该容器内的指定路径再在容器内执行script命令。任务结束容器自动销毁。这意味着每个job都在一个干净、独立、可预测的环境中运行。项目A用Python 3.9项目B用Python 3.11互不干扰一个job里apt-get install -y mysql-client另一个job里yum install -y mysql-client也不会打架。更重要的是它天然支持多语言、多框架——你只需在YAML里写image: maven:3.8.6-openjdk-17Runner就自动为你准备好Maven和JDK环境无需在宿主机上预装任何SDK。但它的前提是宿主机必须已安装并运行Docker daemon且Runner用户必须属于docker用户组。这是新手最容易卡住的一步。常见错误是sudo usermod -aG docker gitlab-runner执行后忘记重启Runner服务或者没有让gitlab-runner用户重新登录以加载新的组权限。此时job日志里会出现Error response from daemon: dial unix /var/run/docker.sock: connect: permission denied看似Docker没启动实则是权限问题。我通常会用一个三步法验证Docker executor是否真就绪sudo -u gitlab-runner docker info—— 检查gitlab-runner用户能否调用Docker APIsudo -u gitlab-runner docker run --rm hello-world—— 检查能否成功拉取并运行镜像在GitLab UI中给Runner打上docker标签并在.gitlab-ci.yml中显式指定tags: [docker]确保job被路由到该Runner。2.3 DockerMachine Executor应对弹性伸缩的“云原生方案”当你的Pipeline并发量飙升单台宿主机的CPU、内存成为瓶颈时dockerexecutor就显得力不从心了。你不能无限加机器更不能让每台机器都常驻一个Runner进程。这时dockermachineexecutor登场——它本质上是一个动态资源调度器Runner不再固定绑定某台物理机而是根据job需求按需创建Provision一台全新的Docker Machine可以是AWS EC2实例、阿里云ECS、或本地VirtualBox虚拟机在该机器上启动Docker daemon运行job容器任务完成后自动销毁机器。它的价值在于极致的资源利用率和成本可控性。比如你有一个每天只运行一次、耗时2小时的大型集成测试Pipeline用dockermachine你可以让它在测试开始前10分钟创建一台高配ECS测试结束立即释放相比常年开着一台高配服务器成本直降80%以上。但复杂度也呈指数级上升你需要配置云厂商的Access Key、Secret Key设置Machine Driver如amazonec2定义Instance Type、Security Group、AMI镜像还要处理SSH密钥分发、网络连通性、以及Machine创建失败的重试逻辑。我见过最典型的故障是AWS区域配置错误如把us-east-1写成us-east-2导致Machine创建超时Runner持续重试最终填满GitLab的job队列阻塞所有其他Pipeline。注意对于90%的中小团队dockermachine是“未来可期”而非“当下必需”。先用好dockerexecutor把CI流程跑稳、跑快、跑准等并发量真实成为瓶颈时再平滑升级。过早引入只会把精力消耗在云基础设施的运维上而非业务交付本身。3. 注册与配置token、concurrent、limit那些藏在config.toml里的魔鬼细节Runner注册不是一次性的“填表提交”而是一个持续演进的配置管理过程。gitlab-runner register命令生成的config.toml文件就是Runner的“基因图谱”它决定了Runner的行为边界、资源上限、安全策略。很多看似随机的失败根源都在这个文件的某一行配置上。3.1 TokenRunner的“数字身份证”有效期与轮换策略注册时输入的registration token是GitLab为该项目或整个Group生成的一次性密钥。它不是密码而是一个具备特定权限的JWT令牌作用是让Runner向GitLab Server证明“我是被授权来为你服务的合法节点”。这个token有严格的有效期——默认7天过期后Runner无法再向GitLab上报心跳状态变为offline所有job都会被拒绝。很多人以为token过期只是“不能注册新Runner”其实它直接影响已注册Runner的存活。我曾维护过一个客户集群其中一台Runner因长期无人维护token过期后仍显示onlineUI缓存但实际已无法接收任何job导致客户误以为CI系统故障紧急排查了整整一天网络和DNS最后才发现是token失效。正确的做法是将token视为敏感凭证纳入密码管理器并设置7天提醒。更进一步GitLab Premium及以上版本支持project-level runner和group-level runner后者token有效期更长30天且可被多个子项目共享大幅降低轮换频率。对于企业级部署建议统一使用Group级Runner由DevOps团队集中管理token生命周期。3.2 Concurrent与Limit别让一台Runner变成“性能黑洞”concurrent参数定义了Runner进程最多能并行处理多少个job。它的值不是越大越好。假设你设为concurrent 10而宿主机只有4核CPU、8GB内存当10个job同时启动Docker容器时系统会陷入严重的资源争抢CPU上下文切换开销激增内存OOM Killer可能随机杀死某个容器进程最终所有job都变慢、超时、失败。我的经验公式是concurrent ≤ (CPU核心数 × 0.8) (可用内存GB ÷ 2)。例如一台8核16GB的服务器concurrent建议设为68×0.86.4向下取整或816÷28取较小值6更稳妥。这留出了20%的CPU余量给系统进程sshd、rsyslog等和2GB内存给Docker daemon自身。而limit参数则是针对单个Runner实例的job数量硬限制。它和concurrent的区别在于concurrent控制并行度limit控制总量。比如你有3台Runner每台concurrent 4limit 100那么这3台总共最多处理100个job无论是否并发。这在资源有限的测试环境中非常有用——防止某个部门的Pipeline突发大量job挤占其他团队的构建资源。3.3 Executors配置深入config.toml的[[runners]]区块一个典型的dockerexecutor配置如下[[runners]] name prod-docker-runner url https://gitlab.example.com/ token glrt-xxxxxxxxxxxxxxxxxxxx executor docker [runners.docker] tls_verify false image alpine:latest privileged false disable_entrypoint_overwrite false oom_kill_disable false disable_cache false volumes [/cache, /var/run/docker.sock:/var/run/docker.sock:ro] shm_size 0其中几个关键字段值得深究volumes [/cache, /var/run/docker.sock:/var/run/docker.sock:ro]/cache是Runner内置的缓存挂载点用于加速npm install、mvn dependency:resolve等操作/var/run/docker.sock:ro是让容器内能调用宿主机Docker API的关键ro表示只读这是最小权限原则的体现。privileged false绝对不要设为true。privileged模式等同于容器获得宿主机root权限一旦CI脚本被注入恶意命令如docker run --privileged -v /:/host alpine chroot /host sh攻击者就能完全控制宿主机。生产环境必须保持false。shm_size 0Docker容器默认/dev/shm大小为64MB对于需要大量共享内存的应用如Chrome Headless浏览器测试会因空间不足而崩溃。此时应显式设置shm_size 21474836482GB。提示修改config.toml后必须执行sudo gitlab-runner restart才能生效。切勿直接kill -9进程否则Runner状态会异常。我习惯在修改前先备份sudo cp /etc/gitlab-runner/config.toml /etc/gitlab-runner/config.toml.bak_$(date %Y%m%d)。4. 实战排错从“job stuck”到“build success”一条完整的故障排查链路再完美的部署也会遇到“job stuck”、“timeout”、“permission denied”这类看似简单却让人抓狂的问题。真正的运维能力不在于一次装对而在于能快速定位、精准修复。下面是我梳理的一条标准化排查链路覆盖90%的常见故障。4.1 第一现场解读job日志里的“无声呐喊”当job状态卡在preparing或pending时不要急着重启Runner。先点开job详情页找到“Job log”区域。这里的信息远比UI状态栏丰富得多。最常见的日志片段是Running with gitlab-runner 16.10.0 (xxxxxxx) on prod-docker-runner yyyyyyy Preparing environment... ERROR: Preparation failed: error getting credentials - err: exec: docker-credential-desktop: executable file not found in $PATH这行报错暴露了两个关键信息1Runner版本是16.10.02它在尝试调用docker-credential-desktop这个二进制文件来获取Docker Hub登录凭据但系统PATH里找不到它。原因很清晰这台Runner部署在Linux服务器上但.gitlab-ci.yml里配置了image: registry.gitlab.com/group/project:latest该镜像需要从私有Registry拉取而Registry的认证信息被错误地配置成了Docker Desktop的凭据助手docker-credential-desktop是macOS/Windows Docker Desktop的组件Linux上不存在。解决方案是在config.toml的[runners.docker]区块下添加disable_cache true并确保image指向的是公开镜像如node:18或在宿主机上手动执行docker login your-registry.com让凭据写入~/.docker/config.json。4.2 网络层诊断Runner与GitLab Server的“心跳检测”如果job日志里连Running with gitlab-runner...这行都看不到说明Runner根本没收到任务。此时要检查Runner与GitLab Server的网络连通性。第一步确认Runner进程是否在运行sudo gitlab-runner status。正常输出应为gitlab-runner: Service is running.。如果显示inactive执行sudo gitlab-runner start。第二步检查Runner能否访问GitLab Serversudo -u gitlab-runner curl -I https://gitlab.example.com/health。注意必须用-u gitlab-runner切换用户因为Runner的网络代理、证书信任库可能与root不同。如果返回HTTP/2 200说明网络通畅如果超时或返回403则需检查防火墙规则、反向代理配置如Nginx是否转发了/health路径、以及GitLab Server的external_url是否配置正确。第三步查看Runner实时日志sudo gitlab-runner --debug run此命令会以前台方式启动Runner便于观察实时日志。当有job触发时你会看到类似Checking for jobs... received job的日志证明Runner已成功从GitLab拉取到任务。4.3 权限与环境为什么ls -la能看到文件cp却说Permission denied这是一个经典陷阱。job脚本里写cp ./dist/* /var/www/html/日志报错cp: cannot create regular file /var/www/html/index.html: Permission denied。你登录服务器sudo ls -la /var/www/html/发现属主是www-data:www-data而Runner以gitlab-runner用户运行自然无权写入。解决方案有三改权限不推荐sudo chown -R gitlab-runner:gitlab-runner /var/www/html/。这破坏了Web服务器的安全模型一旦CI脚本被劫持攻击者就能直接篡改线上HTML。改属组推荐sudo usermod -aG www-data gitlab-runner然后sudo chmod -R gw /var/www/html/。这样gitlab-runner用户通过www-data组获得了写权限符合最小权限原则。用中间目录最佳实践在job脚本中先cp ./dist/* /tmp/deploy/再通过一个sudo授权的systemd service或cron任务定时将/tmp/deploy/内容同步到/var/www/html/。这样CI环境与生产环境完全隔离安全性最高。我坚持采用第三种方案并为此写了一个简单的deploy-sync.service[Unit] DescriptionSync deploy files to web root Afternetwork.target [Service] Typeoneshot Userroot ExecStart/usr/bin/rsync -av --delete /tmp/deploy/ /var/www/html/ RemainAfterExityes [Install] WantedBymulti-user.target然后在job末尾加一行sudo systemctl start deploy-sync.service。虽然多了一步但换来的是生产环境的绝对可控。5. 进阶实践用Shell脚本自动化Runner部署与健康巡检手工执行gitlab-runner register、编辑config.toml、重启服务效率低下且易出错。真正的专业运维是把重复劳动变成可版本化、可审计、可复现的代码。下面是一个经过生产环境验证的Shell脚本它能在一台全新Ubuntu服务器上全自动完成Runner部署、Docker安装、权限配置、并启动健康巡检。5.1 自动化部署脚本setup-runner.sh#!/bin/bash # setup-runner.sh - GitLab Runner全自动部署脚本 # 使用方法curl -fsSL https://raw.githubusercontent.com/your-org/scripts/main/setup-runner.sh | sudo bash -s -- https://gitlab.example.com glrt-xxxxxxxxxxxxxxxxxxxx prod-docker-runner set -e # 任何命令失败即退出 GITLAB_URL${1} REGISTRATION_TOKEN${2} RUNNER_NAME${3:-default-runner} echo 步骤1安装Docker # 添加Docker官方GPG密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 添加Docker APT源 echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null # 更新并安装Docker sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io echo 步骤2安装GitLab Runner # 添加GitLab官方APT源 curl -fsSL https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash sudo apt-get install -y gitlab-runner echo 步骤3配置Runner用户权限 # 将gitlab-runner加入docker组 sudo usermod -aG docker gitlab-runner # 创建Runner工作目录 sudo mkdir -p /var/lib/gitlab-runner sudo chown gitlab-runner:gitlab-runner /var/lib/gitlab-runner echo 步骤4注册Runner # 使用非交互式方式注册 sudo gitlab-runner register \ --non-interactive \ --url $GITLAB_URL \ --registration-token $REGISTRATION_TOKEN \ --executor docker \ --docker-image alpine:latest \ --description $RUNNER_NAME \ --tag-list docker,linux \ --run-untaggedfalse \ --lockedfalse \ --access-levelnot_protected echo 步骤5优化config.toml # 备份原配置 sudo cp /etc/gitlab-runner/config.toml /etc/gitlab-runner/config.toml.bak_$(date %Y%m%d_%H%M%S) # 使用sed注入关键配置 sudo sed -i /^\[runners\.docker\]$/a \ \ privileged false /etc/gitlab-runner/config.toml sudo sed -i /^\[runners\.docker\]$/a \ \ volumes [/cache, /var/run/docker.sock:/var/run/docker.sock:ro] /etc/gitlab-runner/config.toml sudo sed -i s/concurrent .*/concurrent 4/ /etc/gitlab-runner/config.toml echo 步骤6启动并验证 sudo gitlab-runner restart sleep 5 sudo gitlab-runner status echo ✅ Runner部署完成请在GitLab UI中检查Runner状态。这个脚本的价值在于幂等性多次运行不会出错、可审计性所有操作都有明确日志、可定制性通过参数传入URL、Token、Name。我把它托管在公司内部Git仓库每次新服务器上线运维同事只需一行命令即可完成部署。5.2 健康巡检脚本check-runner.shRunner不是“一劳永逸”它需要定期体检。我编写了一个每日凌晨2点自动运行的巡检脚本#!/bin/bash # check-runner.sh - Runner健康状态巡检 # 输出格式时间 | Runner名 | 状态 | 并发数 | 最近job耗时 | 异常日志行数 RUNNER_NAMEprod-docker-runner LOG_FILE/var/log/gitlab-runner/gitlab-runner.log TODAY$(date %Y-%m-%d) # 获取Runner状态 STATUS$(sudo gitlab-runner status 21 | grep running | wc -l) # 获取当前并发数解析config.toml CONCURRENT$(sudo grep concurrent /etc/gitlab-runner/config.toml | awk {print $3}) # 获取最近10个job的平均耗时单位秒 AVG_DURATION$(sudo gitlab-runner list 2/dev/null | tail -n 2 | head -10 | awk {sum $5} END {printf %.0f, sum/NR} 2/dev/null) # 统计今日ERROR日志行数 ERROR_COUNT$(sudo grep -c $TODAY.*ERROR $LOG_FILE 2/dev/null) echo $(date %Y-%m-%d %H:%M:%S) | $RUNNER_NAME | $(if [ $STATUS 1 ]; then echo online; else echo offline; fi) | $CONCURRENT | ${AVG_DURATION:-0}s | $ERROR_COUNT # 如果ERROR_COUNT 5发送告警邮件 if [ $ERROR_COUNT -gt 5 ]; then echo Runner ERROR日志异常增多请立即检查 | mail -s ALERT: GitLab Runner Health Check opscompany.com fi这个脚本每天生成一行结构化日志我将其导入ELK日志平台用Kibana做可视化看板。当ERROR_COUNT曲线突然拉升我就知道某个Pipeline的YAML脚本可能引入了不兼容的语法或者某台宿主机的磁盘空间即将耗尽。6. DotNet8自动化部署实战从代码提交到Kestrel服务热启的完整闭环标题里提到“gitlab-runner 自动化部署dotnet8”这绝非一句空话。.NET 8的AOTAhead-of-Time编译、内置OpenAPI文档、以及跨平台能力让CI/CD流程有了质的飞跃。下面是一个真实落地的DotNet8部署Pipeline它实现了代码提交 → 自动构建 → 单元测试 → AOT发布 → Docker镜像构建 → 推送至私有Registry → 在目标服务器上滚动更新Kestrel服务。6.1.gitlab-ci.yml声明式定义整个交付流水线stages: - build - test - package - deploy variables: DOTNET_VERSION: 8.0.100 DOCKER_REGISTRY: registry.example.com APP_NAME: my-web-api IMAGE_TAG: $CI_COMMIT_SHORT_SHA # 构建阶段使用官方.NET SDK镜像 build-dotnet: stage: build image: mcr.microsoft.com/dotnet/sdk:$DOTNET_VERSION before_script: - dotnet --version - cd src/$APP_NAME script: - dotnet restore - dotnet build -c Release -o /app/build artifacts: - bin/Release/net8.0/publish/** cache: key: $CI_COMMIT_REF_SLUG-dotnet-restore paths: - **/obj/** # 测试阶段并行运行多个测试项目 test-unit: stage: test image: mcr.microsoft.com/dotnet/sdk:$DOTNET_VERSION before_script: - cd src/$APP_NAME script: - dotnet test --no-build --logger trx;LogFileNametest-results.xml --collect:XPlat Code Coverage coverage: /^Total.*?([0-9]{1,3})%$/ artifacts: - TestResults/**.xml - coverage/ # 发布阶段AOT编译生成独立可执行文件 publish-aot: stage: package image: mcr.microsoft.com/dotnet/sdk:$DOTNET_VERSION before_script: - cd src/$APP_NAME script: - dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishTrimmedtrue -p:PublishReadyToRuntrue -o /app/publish artifacts: - /app/publish/** # Docker镜像构建与推送 build-docker: stage: package image: docker:stable services: - docker:dind before_script: - docker info - echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin $DOCKER_REGISTRY script: - | docker build -t $DOCKER_REGISTRY/$APP_NAME:$IMAGE_TAG . docker push $DOCKER_REGISTRY/$APP_NAME:$IMAGE_TAG after_script: - docker logout $DOCKER_REGISTRY only: - main # 部署到生产服务器 deploy-prod: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - mkdir -p ~/.ssh - echo $SSH_PRIVATE_KEY | tr -d \r | ssh-keygen -t rsa -b 4096 -N -f ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa script: - | ssh -o StrictHostKeyCheckingno deployprod-server mkdir -p /opt/$APP_NAME/releases/$IMAGE_TAG scp -o StrictHostKeyCheckingno /app/publish/* deployprod-server:/opt/$APP_NAME/releases/$IMAGE_TAG/ ssh -o StrictHostKeyCheckingno deployprod-server cd /opt/$APP_NAME rm -f current ln -sf releases/$IMAGE_TAG current systemctl restart $APP_NAME.service environment: name: production url: https://api.example.com only: - main6.2 关键技术点拆解为什么这样设计AOT编译 (--self-contained true)生成一个不依赖系统.NET Runtime的独立二进制文件体积虽大约80MB但启动速度提升3倍且避免了目标服务器.NET版本不匹配的风险。Docker-in-Docker (docker:dind)build-dockerjob使用docker:stable镜像并挂载docker:dind服务让容器内能启动Docker daemon从而构建和推送镜像。这是GitLab CI的标准模式。SSH密钥安全注入$SSH_PRIVATE_KEY是一个GitLab CI/CD Variables中定义的masked variable它被注入到job环境中再通过ssh-keygen生成临时密钥对全程不落盘符合安全审计要求。符号链接滚动更新ln -sf releases/$IMAGE_TAG current是零停机部署的核心。current始终指向最新版本systemctl restart只是重启服务不涉及文件拷贝整个过程在200ms内完成。我在生产环境实测从代码提交到API服务响应全流程耗时平均4分32秒。其中AOT编译占时最长约2分10秒但换来的是极致的运行时性能——QPS从.NET 6的1200提升至.NET 8的2800CPU占用率下降35%。最后分享一个小技巧在deploy-prodjob的script里加上ssh ... systemctl is-active --quiet $APP_NAME.service echo ✅ Service is running这样job日志里会明确显示服务是否真的启动成功而不是仅仅执行了restart命令。这种“眼见为实”的验证比任何监控告警都来得直接可靠。
返回列表