目)
Windows 使用 Docker Jenkins 自動部署 Vue 前端項(xiàng)目本文以 joy-admin-frontend 為例將 Mac/Linux 流程遷移到 Windows 10/11。前端使用 Vue 2、Vue CLI 4、webpack 4構(gòu)建環(huán)境固定為 Node.js 16.20.2、npm 8.19.4。發(fā)布鏈路Codeup - Jenkins Linux 容器構(gòu)建 dist - 共享掛載寫入 Windows D:/work 發(fā)布目錄 - Windows OpenSSH 執(zhí)行 PowerShell 切換 current - Windows Nginx 檢查并 reload - HTTP 驗(yàn)證失敗時恢復(fù)舊版本Jenkins 在 Docker Desktop 的 Linux 容器內(nèi)運(yùn)行所以 Pipeline 使用 shWindows 主機(jī)命令使用 PowerShell。一、目錄和 Nginx先手動創(chuàng)建 D:/workNew-Item-ItemType Directory-Force-PathD:/work/joy-admin-frontend-deploy/releases,D:/work/joy-admin-frontend-deploy/scripts,D:/work/joy-admin-deploy/jenkins,D:/work/nginx,D:/work/sshNew-Item-ItemType File-Force-PathD:/work/joy-admin-deploy/jenkins/known_hosts|Out-Null目錄結(jié)構(gòu)D:/work/ ├── joy-admin-frontend-deploy/ │ ├── releases/ │ └── scripts/ ├── nginx/ │ ├── nginx.exe │ └── conf/ └── ssh/將 Windows 版 Nginx 解壓到 D:/work/nginx檢查Test-PathD:/work/nginx/nginx.exeTest-PathD:/work/nginx/conf/nginx.conf在 nginx.conf 的 http 塊中加入include D:/work/nginx/conf/nginx-joy-admin.conf;創(chuàng)建 nginx-joy-admin.confserver { listen 80; server_name dev.joy.admin.com; root D:/work/joy-admin-frontend-deploy/current; index index.html; location /prod-api/ { proxy_pass http://127.0.0.1:9080/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 10s; proxy_read_timeout 60s; } location /index.html { add_header Cache-Control no-cache, no-store, must-revalidate; } location /static/ { expires 7d; add_header Cache-Control public, max-age604800, immutable; try_files $uri 404; } location / { try_files $uri $uri/ /index.html; } gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain text/css application/javascript application/json image/svgxml; }檢查和啟動 NginxD:/work/nginx/nginx.exe-t-p D:/work/nginx-c D:/work/nginx/conf/nginx.confStart-Process-FilePathD:/work/nginx/nginx.exe-WorkingDirectoryD:/work/nginx已運(yùn)行時只 reloadD:/work/nginx/nginx.exe-s reload-p D:/work/nginx-c D:/work/nginx/conf/nginx.conf本機(jī)測試域名時管理員 PowerShell 執(zhí)行Add-Content-PathC:/Windows/System32/drivers/etc/hosts-Value127.0.0.1 dev.joy.admin.comipconfig/flushdns二、Windows OpenSSHJenkins 通過 SSH 調(diào)用 Windows PowerShell 和 Nginx不使用 rsync 傳輸文件文件由 Docker Desktop 共享掛載提供。管理員 PowerShell 安裝并啟動 OpenSSH ServerAdd-WindowsCapability-Online-Name OpenSSH.Server~~~~0.0.1.0Start-ServicesshdSet-Service-Name sshd-StartupType Automaticif(-not(Get-NetFirewallRule-Name sshd-ErrorAction SilentlyContinue)){New-NetFirewallRule-Name sshd-DisplayNameOpenSSH Server-Enabled True-Direction Inbound-Protocol TCP-Action Allow-LocalPort 22}創(chuàng)建普通部署用戶$PasswordRead-Host輸入 jenkinsdeploy 密碼-AsSecureStringNew-LocalUser-Namejenkinsdeploy-Password$Password-DescriptionJenkins frontend deployment userAdd-LocalGroupMember-GroupUsers-Memberjenkinsdeploy在 Windows 主機(jī)生成密鑰ssh-keygen-t ed25519-Cjenkins-nginx-deploy-fD:/work/ssh/jenkins-nginx-host寫入授權(quán)文件$SshDirC:/Users/jenkinsdeploy/.sshNew-Item-ItemType Directory-Force-Path$SshDir|Out-NullCopy-ItemD:/work/ssh/jenkins-nginx-host.pub$SshDir/authorized_keys-Force測試ssh-i D:/work/ssh/jenkins-nginx-host-o IdentitiesOnlyyes jenkinsdeploy127.0.0.1whoami輸出 jenkinsdeploy 即可。Nginx 最好由該部署用戶啟動確保該用戶可以執(zhí)行 reload。三、修改 Jenkins Compose 掛載在 Windows Jenkins Compose 的 jenkins 服務(wù) volumes 增加-D:/work/joy-admin-frontend-deploy:/frontend-deploy相關(guān)片段volumes:-jenkins_home:/var/jenkins_home-jenkins_npm_cache:/root/.npm-/var/run/docker.sock:/var/run/docker.sock-./jenkins/known_hosts:/root/.ssh/known_hosts:ro-D:/work/joy-admin-frontend-deploy:/frontend-deploy重新創(chuàng)建 JenkinsSet-LocationD:/work/joy-admin-deploydocker compose--env-file.env-f compose.jenkins.yml up-d--force-recreate docker exec joy-jenkins test-d/frontend-deploy/releasesJenkins 容器能訪問 /frontend-deploy/releases 才能繼續(xù)。從 Jenkins 容器掃描 Windows OpenSSH 主機(jī)密鑰寫入宿主機(jī)掛載文件docker exec joy-jenkins ssh-keyscan-T 10-H-t ed25519,rsa host.docker.internal|Set-Content-Encoding asciiD:/work/joy-admin-deploy/jenkins/known_hostsdocker exec joy-jenkins ssh-keygen-lf/root/.ssh/known_hosts確認(rèn)指紋和 Windows 主機(jī)一致后再繼續(xù)。known_hosts 文件通過掛載實(shí)時提供給 Jenkins。四、創(chuàng)建 current 切換腳本創(chuàng)建 D:/work/joy-admin-frontend-deploy/scripts/switch-current.ps1param([Parameter(Mandatory $true)][ValidatePattern(^[A-Za-z0-9._-]$)][string]$Tag)$ErrorActionPreferenceStop$RootD:/work/joy-admin-frontend-deploy$NginxExeD:/work/nginx/nginx.exe$NginxPrefixD:/work/nginx$NginxConfD:/work/nginx/conf/nginx.conf$ReleaseJoin-Path$Rootreleases/$Tag$CurrentJoin-Path$Rootcurrent$PreviousJoin-Path$Rootcurrent.previous$PreviousTagFileJoin-Path$Rootprevious-tag.txtif(-not(Test-Path(Join-Path$Releaseindex.html))){throw發(fā)布目錄不存在:$Release}$PreviousTagif(Test-Path$Current){$LinkGet-Item-LiteralPath$Current$LinkTarget$Link.Targetif(-not$LinkTarget){$LinkTarget$Link.LinkTarget}if($LinkTarget){$PreviousTagSplit-Path-Leaf$LinkTarget}}Set-Content-Path$PreviousTagFile-Value$PreviousTag-Encoding asciiif(Test-Path$Previous){Remove-Item-LiteralPath$Previous-Force}if(Test-Path$Current){Move-Item-LiteralPath$Current-Destination$Previous-Force}try{New-Item-ItemType Junction-Path$Current-Target$Release|Out-Null$NginxExe-t-p$NginxPrefix-c$NginxConfif($LASTEXITCODE-ne0){throwNginx 配置檢查失敗}$NginxExe-s reload-p$NginxPrefix-c$NginxConfif($LASTEXITCODE-ne0){throwNginx reload 失敗}Write-Output切換成功:$Tagexit0}catch{if(Test-Path$Current){Remove-Item-LiteralPath$Current-Force}if($PreviousTag){$OldReleaseJoin-Path$Rootreleases/$PreviousTagNew-Item-ItemType Junction-Path$Current-Target$OldRelease|Out-Null$NginxExe-s reload-p$NginxPrefix-c$NginxConf|Out-Null}Write-Error$_exit1}五、Jenkins 憑據(jù)和 PipelineJenkins 中配置codeup-ssh Codeup 代碼拉取私鑰 nginx-host-ssh jenkinsdeploy 用戶的 Windows SSH 私鑰nginx-host-ssh 憑據(jù)填寫KindSSH Username with private key Usernamejenkinsdeploy Private KeyD:/work/ssh/jenkins-nginx-host 的私鑰內(nèi)容 IDnginx-host-ssh創(chuàng)建 New Item - joy-admin-frontend - Pipeline。完整腳本如下替換 Codeup 倉庫地址pipeline{agent any parameters{choice(name:ACTION,choices:[DEPLOY,ROLLBACK],description:發(fā)布或回滾)string(name:GIT_BRANCH,defaultValue:main,description:前端分支)string(name:FRONTEND_URL,defaultValue:http://dev.joy.admin.com,description:前端訪問地址)string(name:ROLLBACK_TAG,defaultValue:,description:回滾版本)}options{timestamps()timeout(time:45,unit:MINUTES)disableConcurrentBuilds()skipDefaultCheckout(true)buildDiscarder(logRotator(numToKeepStr:30))}environment{DEPLOY_HOSThost.docker.internalDEPLOY_USERjenkinsdeployDEPLOY_ROOT/frontend-deployHOST_DEPLOY_ROOTD:/work/joy-admin-frontend-deployFRONTEND_HOSTdev.joy.admin.comNGINX_PORT80SWITCH_SCRIPTD:/work/joy-admin-frontend-deploy/scripts/switch-current.ps1NODE_OPTIONS--max-old-space-size4096}stages{stage(拉取代碼){when{expression{params.ACTIONDEPLOY}}steps{deleteDir()sshagent(credentials:[codeup-ssh]){sh set -eu export GIT_SSH_COMMANDssh -o StrictHostKeyCheckingyes -o UserKnownHostsFile/root/.ssh/known_hosts git clone --depth 1 --branch $GIT_BRANCH gitcodeup.aliyun.com:組織/倉庫.git frontend }script{env.GIT_SHORTsh(script:git -C frontend rev-parse --short8 HEAD,returnStdout:true).trim()env.RELEASE_TAG${env.BUILD_NUMBER}-${env.GIT_SHORT}}}}stage(構(gòu)建前端){when{expression{params.ACTIONDEPLOY}}steps{dir(frontend){sh set -eu printf %s\n VUE_APP_BASE_API/prod-api VUE_APP_FRONT_URL$FRONTEND_URL .env.production.local node -v npm -v npm ci --no-audit --no-fund npm run build:prod test -f dist/index.html test -d dist/static }}}stage(發(fā)布或回滾){steps{sshagent(credentials:[nginx-host-ssh]){script{if(params.ACTIONROLLBACK){if(!params.ROLLBACK_TAG.trim()){error(回滾必須填寫 ROLLBACK_TAG)}env.TARGET_TAGparams.ROLLBACK_TAG.trim()}else{env.TARGET_TAGenv.RELEASE_TAG}sh set -eu case $TARGET_TAG in *[!A-Za-z0-9._-]*) echo 非法版本標(biāo)簽; exit 1 ;; esac RELEASE_DIR/frontend-deploy/releases/$TARGET_TAG REMOTE$DEPLOY_USER$DEPLOY_HOST SSH_OPTS-o PreferredAuthenticationspublickey -o PasswordAuthenticationno -o StrictHostKeyCheckingyes -o UserKnownHostsFile/root/.ssh/known_hosts if [ $ACTION DEPLOY ]; then rm -rf $RELEASE_DIR mkdir -p $RELEASE_DIR cp -a frontend/dist/. $RELEASE_DIR/ else ssh $SSH_OPTS $REMOTE powershell.exe -NoProfile -NonInteractive -Command \if (-not (Test-Path $HOST_DEPLOY_ROOT/releases/$TARGET_TAG/index.html)) { exit 1 }\ fi ssh $SSH_OPTS $REMOTE powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $SWITCH_SCRIPT -Tag $TARGET_TAG if curl --fail --silent --show-error -H Host: $FRONTEND_HOST http://$DEPLOY_HOST:$NGINX_PORT/ /dev/null; then echo 前端發(fā)布成功: $TARGET_TAG exit 0 fi PREVIOUS_TAG$(ssh $SSH_OPTS $REMOTE powershell.exe -NoProfile -NonInteractive -Command \(Get-Content $HOST_DEPLOY_ROOT/previous-tag.txt)\ | tr -d \r) if [ -n $PREVIOUS_TAG ]; then ssh $SSH_OPTS $REMOTE powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $SWITCH_SCRIPT -Tag $PREVIOUS_TAG fi exit 1 }}}}}post{always{deleteDir()}}}Pipeline 中繼續(xù)使用 sh因?yàn)?Jenkins 運(yùn)行在 Linux 容器內(nèi)。六、發(fā)布、回滾和驗(yàn)證首次發(fā)布參數(shù)ACTIONDEPLOY GIT_BRANCHmain FRONTEND_URLhttp://dev.joy.admin.com ROLLBACK_TAG留空版本目錄格式D:/work/joy-admin-frontend-deploy/releases/構(gòu)建號-Git短Commit回滾參數(shù)ACTIONROLLBACK ROLLBACK_TAG需要恢復(fù)的版本目錄名Windows 驗(yàn)證Get-ItemD:/work/joy-admin-frontend-deploy/current|Format-List*curl.exe-I-HHost: dev.joy.admin.comhttp://127.0.0.1/ curl.exe-I-HHost: dev.joy.admin.comhttp://127.0.0.1/dashboard curl.exe-i-HHost: dev.joy.admin.comhttp://127.0.0.1/prod-api/七、常見問題Jenkins 不能訪問發(fā)布目錄docker exec joy-jenkins test-d/frontend-deploy/releases檢查 Jenkins Compose 是否掛載 D:/work/joy-admin-frontend-deploy 到 /frontend-deploy。npm ci 失敗確認(rèn)倉庫包含 package-lock.json并且 Jenkins 中的 Node.js 版本為 16.20.2、npm 版本為 8.19.4。不要在發(fā)布流水線中自動生成新的 lock 文件如果 lockfile 版本與 npm 8 不兼容應(yīng)統(tǒng)一項(xiàng)目鎖文件和構(gòu)建鏡像中的 npm 版本后再發(fā)布。Windows OpenSSH 連接失敗Get-ServicesshdGet-NetFirewallRule-Name sshd ssh-i D:/work/ssh/jenkins-nginx-host jenkinsdeploy127.0.0.1whoamiJunction 創(chuàng)建失敗以管理員 PowerShell 執(zhí)行或開啟 Windows Developer Mode并確保部署用戶對 D:/work 有讀寫權(quán)限。不要預(yù)先創(chuàng)建普通 current 目錄。Nginx reload 失敗D:/work/nginx/nginx.exe-t-p D:/work/nginx-c D:/work/nginx/conf/nginx.confGet-Processnginx確認(rèn) Nginx 由 jenkinsdeploy 用戶啟動或者該用戶擁有控制 Nginx 進(jìn)程的權(quán)限。/prod-api 返回 403 或 404proxy_pass 末尾必須保留 /這樣 /prod-api/login 會轉(zhuǎn)發(fā)為后端 /login??缬虿渴饡r后端 CORS_ALLOWED_ORIGINS 必須包含實(shí)際前端來源。八、安全和遷移Jenkins 使用普通 Windows 用戶不要使用管理員賬號。Jenkins 只綁定 127.0.0.1不直接暴露公網(wǎng)。Jenkins Docker Socket 等同于宿主機(jī) Docker 管理權(quán)限。SSH 私鑰、環(huán)境文件不得提交 Git。releases 目錄保留最近 5 至 10 個版本。Nginx 配置變更必須先 nginx -t再 reload。遷移到新 Windows 機(jī)器時重新配置 D:/work、Nginx、OpenSSH、known_hosts 和 Jenkins 憑據(jù)。