尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GitLab + Drone CI 持续集成实践:从Web项目到自动化部署

GitLab + Drone CI 持续集成实践:从Web项目到自动化部署 做DevOps这些年我越来越认同一句话持续集成不是炫技是给团队省心。以前我待过的团队发布一个Web项目靠的是“人肉部署”本地build一下scp到服务器再手动reload运气好一次成功运气不好就得在群里艾特一圈人排查是谁改了配置。后来我把代码托管迁到GitLab顺手接上了Drone CI从此提交代码后构建、打包、推送、部署全部自动完成。今天就用一个真实前端Web项目为例把GitLab Drone CI这条持续集成管线的搭建过程、配置细节和坑点完整地记录一遍。这篇文适合谁看如果你已经在用或准备自建GitLab如果你的项目是Node/Vue/React这类前端Web项目或者Java、Go、Python后端项目如果你想用一套“配置即代码”的轻量方案替代笨重的Jenkins那接下来的内容可以直接照抄。1. 方案设计与选型思考1.1 持续集成到底解决了什么问题先把需求讲清楚。我们最终要实现的是这样一个效果开发人员把代码push到GitLab的main分支后面所有事情都不需要手动管了Drone会自动拉代码、装依赖、构建、把产物发布到Nginx站点目录最后reload Nginx。整个过程从push到生效通常不超过两分钟。那这件事为什么值得自动化我举几个实际场景。场景一多个前端项目同时维护发布前要手工切换环境变量有人忘了改把测试环境的接口地址带到了生产。场景二服务器上部署目录越来越大旧文件没人清理因为“怕删错”。场景三凌晨上线运维不在开发不敢自己操作等天亮再发发布窗口被压缩。这些痛点本质上都是手动操作带来的不确定性和不可追溯性而持续集成和自动部署就是用机器流程去消除这部分不确定性。所以选型第一步不是选工具而是明确你的流水线要承担哪些职责。我内部一直把发布流水线拆成“三段”构建段、上传段、生效段。构建段解决“能不能编出来”的问题上传段解决“产物怎么安全地过去”的问题生效段解决“旧服务如何平滑切换到新产物”的问题。下面所有配置都围绕这三段展开。1.2 为什么是GitLab Drone CI而不是Jenkins提到持续集成很多人第一反应是Jenkins。这句话我承认Jenkins的生态确实成熟文档多、插件全、社区大。但正因为它什么都能干维护它本身就是一门功课。插件兼容性、升级风险、构建节点管理、JVM内存参数调优……这些在只有两三个开发的小团队里很难有专人投入精力去养它。我见过不少团队Jenkins装好之后半年没人更新插件版本老到连GitLab的新API都不认连拉取代码都报错。Drone走的是“云原生、容器化、配置即代码”的路线。它的核心配置文件.drone.yml直接放进项目仓库跟代码一起版本化你在GitLab上看到的每次提交都能对应到一份确定的流水线配置。新员工加入看一遍.drone.yml就明白发布的整个流程这比去CI系统的网页上找历史构建记录要直观太多了。而且Drone和GitLab的集成是原生的OAuth模式。用户登录Drone直接用GitLab账号授权权限可以跟GitLab仓库权限联动。这一点对我们这种“代码和权限都在GitLab上”的团队非常友好不用在Jenkins里单独维护一套账号和权限体系。当然我不是说Jenkins一无是处。如果你的流水线需要大量非容器的旧插件、需要跑Windows桌面端任务、需要复杂的Pipeline DSL逻辑那Jenkins仍然是合理选择。但如果你面向的是Web开发团队产物是Node静态文件、Docker镜像、或者是可以容器化的后端服务Drone这套明显更轻。1.3 整条流水线的工作逻辑与组件角色在动手部署前必须先把Drone的组件角色理解清楚。整套系统由三个部分组成第一部分是GitLab它负责代码托管同时向Drone Server推送Webhook事件。第二部分是Drone Server它不直接干活只负责接收事件、解析并校验.drone.yml、调度任务、展示流水线状态。第三部分是Drone Runner它才是真正干活的收到Server的指令后动态创建容器来执行构建步骤。这里有一个新手最容易搞混的地方Drone Server看起来像一个Web后台你以为构建是在它上面执行的其实不是。真正的构建发生在Runner机器上而且因为Runner基于Docker每一步都是全新容器环境干净可复现。整个事件流是这样的开发push代码 - GitLab触发Webhook - Drone Server收到通知 - Server从GitLab拉取.drone.yml - 把任务分发给Runner - Runner按yaml定义启动容器、执行命令 - 构建产物被上传到目标服务器 - Nginx reload - 完成。这样一条链路每部分职责单一故障排查时也容易定位。2. 基础环境搭建GitLab与Drone的部署2.1 用Docker Compose部署GitLab社区版我假设你已经有一台Linux服务器最好是Ubuntu 20.04以上内存4GB起步。GitLab本身对资源有一定要求内存低于4G会经常触发OOM页面加载也会明显变慢。部署我推荐用Docker Compose方便升级和管理。先建一个项目目录比如/data/cicd在里面放docker-compose.ymlversion: 3 services: gitlab: image: gitlab/gitlab-ce:15.10.0-ce.0 container_name: gitlab restart: always hostname: gitlab.example.com environment: GITLAB_OMNIBUS_CONFIG: | external_url http://gitlab.example.com gitlab_rails[time_zone] Asia/Shanghai gitlab_rails[gitlab_shell_ssh_port] 2222 gitlab_rails[smtp_enable] false ports: - 80:80 - 2222:22 volumes: - /data/cicd/gitlab/config:/etc/gitlab - /data/cicd/gitlab/logs:/var/log/gitlab - /data/cicd/gitlab/data:/var/opt/gitlab shm_size: 256m启动命令一句docker compose up -d gitlab。首次启动GitLab会做内部初始化需要等个两三分钟。期间可以通过docker logs -f gitlab观察日志看到“gitlab Reconfigured!”字样后再访问浏览器。有很多朋友会卡在“能进GitLab但是clone地址不对”。这里的关键是external_url它不是随便填的必须是你今后访问GitLab的完整地址。比如你准备用http://gitlab.example.com去访问那external_url就必须是http://gitlab.example.com。如果以后上了HTTPS这里也要同步改成https。如果你不想用域名直接用IP也可以比如external_url http://192.168.1.10。但是用IP有一个小问题OAuth回调里如果带上端口容易把地址搞复杂所以我建议有条件还是先绑个域名或内网DNS解析。2.2 部署Drone ServerGitLab容器稳定运行后接着把Drone Server加进来。在docker-compose.yml里追加一个服务drone-server: image: drone/drone:2.17.0 container_name: drone-server restart: always ports: - 8080:80 environment: - DRONE_GITLAB_CLIENT_ID这里填GitLab应用的Application ID - DRONE_GITLAB_CLIENT_SECRET这里填GitLab应用的Secret - DRONE_RPC_SECRET生成一段随机字符串 - DRONE_SERVER_HOSTdrone.example.com - DRONE_SERVER_PROTOhttp - DRONE_GITLAB_URLhttp://gitlab.example.com - DRONE_USER_CREATEusername:admin,admin:false volumes: - /data/cicd/drone/server:/dataDRONE_GITLAB_CLIENT_ID和DRONE_GITLAB_CLIENT_SECRET要到第3章创建完OAuth应用才能拿到所以你可以先把其他变量配好再回来补这两个。如果你只是想先让服务跑起来随便填一段后面再改重启容器即可。DRONE_RPC_SECRET这个变量非常关键它是Server和Runner之间通信的“接头暗号”两边必须一致。生成方式可以这样openssl rand -hex 16然后把输出的32位字符串填入。不要用简单的123456这类弱密码因为RPC端口一旦暴露攻击者可能通过Server API接管构建任务。DRONE_SERVER_HOST和DRONE_SERVER_PROTO决定了用户访问Drone的方式。如果Drone前面有Nginx代理并且域名是drone.example.com那PROTO填httpsHOST填drone.example.com如果直接IP端口访问PROTO填httpHOST填192.168.1.10:8080。这个值会影响OAuth回调地址填错会导致登录跳转后打不开页面。DRONE_USER_CREATE这个变量是可选的作用是自动创建一个用户账号。我这里创建了admin用户方便后续管理。如果你不设置第一次通过GitLab OAuth登录时Drone也会根据GitLab账号自动创建用户。配置完成docker compose up -d drone-server这时浏览器访问http://drone.example.com应该能看到登录页面点击继续会跳转到GitLab的授权页。如果这一步没走通多半是client_id、secret、回调地址三者的关系没对后面排查章节详细说。2.3 部署Drone RunnerRunner我这里使用docker runner因为对于Web项目docker runner足够通用而且环境隔离更干净。继续在docker-compose.yml里追加drone-runner: image: drone/drone-runner-docker:1.8.3 container_name: drone-runner restart: always environment: - DRONE_RPC_PROTOhttp - DRONE_RPC_HOSTdrone-server - DRONE_RPC_SECRET和Server里填的保持一致 - DRONE_RUNNER_NAMEdev-runner-1 - DRONE_RUNNER_CAPACITY2 volumes: - /var/run/docker.sock:/var/run/docker.sock depends_on: - drone-server由于我在同一个docker-compose网络里DRONE_RPC_HOST直接填服务名drone-server即可。如果Runner部署在另一台机器要把这里改成Drone Server的IP或域名并确保RPC端口默认80我映射到宿主机8080能被访问。挂载/var/run/docker.sock是必须的。Runner要想动态创建构建容器必须通过宿主机的Docker守护进程它自己不需要内置docker引擎。挂载后Runner创建出来的构建容器和Runner不在同一层而是跑在宿主机上这样资源调度更直接。启动完成后打开Drone后台进入Settings - Runner能看到runner列表状态应该是绿色的online。如果不是执行docker logs -f drone-runner重点看DRONE_RPC_SECRET有没有报错。2.4 关键环境变量速查这里把经常用到、容易混淆的drone-server环境变量整理成一个速查表方便以后查错变量名作用常见错误DRONE_GITLAB_CLIENT_IDGitLab OAuth应用ID填错导致跳转后403DRONE_GITLAB_CLIENT_SECRETGitLab OAuth应用Secret泄露要积极吊销DRONE_RPC_SECRETServer/Runner共享密钥两边不一致Runner离线DRONE_SERVER_HOSTDrone外部访问地址带不带端口要看实际DRONE_SERVER_PROTODrone外部访问协议误填https会出证书问题DRONE_GITLAB_URL私有GitLab地址必填否则连不上代码仓库DRONE_USER_CREATE预创建用户可选注意admin布尔值这个表格在写配置时特别有用我每次搭建新环境都会对照一遍基本能避免80%的配置类问题。3. Web项目接入与自动部署配置3.1 创建GitLab OAuth应用接下来就要把GitLab和Drone正式关联起来。使用管理员账号登录GitLab点击头像进入Edit Profile在左侧菜单找到Applications点击New Application。需要填写的内容是Name填Drone。Redirect URI填http://drone.example.com/login。很多人会漏掉/login或者漏掉端口导致授权后无法跳回Drone。Confidential选择Yes。Scopes将api、read_user、openid、profile、email全部勾选。这里Scopes我一开始只勾了read_user能登录但后面无法同步仓库列表。如果你在Drone后台看到仓库列表一直是空的先去GitLab更新OAuth应用的权限再回来同步。提交后GitLab会生成Application ID和Secret。把这两个值复制到drone-server环境变量里然后重启容器。重启后再次访问Drone会重新走一次GitLab授权这次授权后会进入Drone管理界面。3.2 激活Drone并同步仓库登录Drone后台后左侧面板会展示可用的项目仓库列表。第一次进来列表可能为空点击右上角的SYNC按钮Drone会调用GitLab API同步当前用户能看到的项目。在列表中找到你的Web项目点击Activate按钮。激活过程会在GitLab仓库里自动注册一个Webhook同时把项目标记为启用状态。激活成功后项目状态由灰色变为绿色。此时可以去GitLab仓库的Settings - Integrations里核对一下Webhook是否注册成功。如果Webhook存在但状态一直显示“Failed”点进去能看到响应内容最常见的响应异常是404或者Unauthorized。404通常是Drone Server地址填错Unauthorized通常是Client Secret或Token不对。我在实际项目中遇到过一种很隐蔽的情况GitLab和Drone部署在不同的容器网络中GitLab发Webhook请求到Drone的时候用的地址是docker-compose服务名而不是外部访问地址。解决办法是确保GitLab里Webhook的URL是Drone的外部地址比如http://drone.example.com而不是http://drone-server:80。Webhook填写错误的话push代码后Drone没有任何反应但是GitLab侧会显示请求失败。3.3 编写.drone.yml前端Web项目的部署现在进入到最核心的部分。在项目根目录创建.drone.yml把流水线定义交给Git托管。以Vue项目为例完整内容如下kind: pipeline type: docker name: web-deploy trigger: branch: - main event: - push steps: - name: install-deps image: node:16-alpine commands: - node -v - npm -v - npm config set registry https://registry.npmmirror.com - npm ci - name: build image: node:16-alpine commands: - npm run build - name: upload-dist image: appleboy/drone-scp settings: host: 192.168.1.100 username: deploy key: from_secret: deploy_key port: 22 source: dist/* target: /opt/www/html strip_components: 1 - name: reload-nginx image: appleboy/drone-ssh settings: host: 192.168.1.100 username: deploy key: from_secret: deploy_key script: - sudo nginx -s reload关于这部分我挑几个重点细说。trigger这一段branch写成mainevent写成push意味着只有main分支收到push时才会触发。如果你想tag发版也触发可以再增加tag事件。如果你的仓库还在用master分支注意改成master。install-deps和build分开写语义清晰而且后续如果只需要build不需要重新install可以单独调整。镜像用node:16-alpine比较小构建速度更快。注意npm ci必须要有package-lock.json如果没有改成npm install。upload-dist这一步用的是appleboy/drone-scp插件作用是把构建产物通过scp上传到服务器。source字段是dist/*target是/opt/www/htmlstrip_components: 1是因为dist目录本身这里也有一层去掉后目标目录里拿到的就是dist下的文件而不是dist目录本身。reload-nginx这一步用appleboy/drone-ssh插件在服务器上执行nginx -s reload。如果服务器上的nginx配置路径或者用户不同脚本改成你实际的命令。这里最大的坑是权限问题deploy用户需要拥有对Nginx reload的sudo权限并且ssh key要加入deploy用户的authorized_keys。3.4 部署到Nginx与站点配置项目部署目录设定为/opt/www/html接下来要让Nginx能正确地服务这个目录。在部署服务器的Nginx配置目录下增加一个站点配置比如/etc/nginx/conf.d/web.confserver { listen 80; server_name demo.example.com; root /opt/www/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }写完配置先执行nginx -t检查语法没问题再nginx -s reload。这个顺序很重要我见过有人省掉nginx -t结果语法错误导致整个Nginx挂了所有站点都打不开。有几个细节对Web项目影响很大。第一个是try_files $uri $uri/ /index.html这是给SPA用的没有它用户在浏览器里直接访问某个前端路由刷新后会404。第二个是location /api/的反向代理如果你前后端分离、接口路径统一带/api前缀这一段正好把请求转发到后端服务。第三个是缓存策略如果要控制静态资源的缓存时间可以在location里加expires指令这段配置里没有但发布静态站点时建议根据自己的需求加上。3.5 后端项目与容器化项目的部署变体如果你的“Web项目”不只有静态文件还包含后端服务前面那种scp静态文件的方式就不够了。这里我给出一种较常用的方式构建Docker镜像 - 推送到镜像仓库 - 服务器拉镜像 - 重启容器。在.drone.yml中对应步骤可以这样写- name: build-image image: plugins/docker settings: registry: registry.example.com repo: registry.example.com/myapp/backend tags: ${DRONE_COMMIT_SHA:0:8} username: from_secret: reg_username password: from_secret: reg_password - name: deploy-container image: appleboy/drone-ssh settings: host: 192.168.1.100 username: deploy key: from_secret: deploy_key script: - docker login -u $REG_USERNAME -p $REG_PASSWORD registry.example.com - docker pull registry.example.com/myapp/backend:${DRONE_COMMIT_SHA:0:8} - docker rm -f app || true - docker run -d --name app -p 8080:8080 registry.example.com/myapp/backend:${DRONE_COMMIT_SHA:0:8}如果用from_secret引用了密钥SSH脚本里可以直接以环境变量形式读取。这里有一点强调下tag不要用latest。用commit短SHA的好处是每次发布的版本和代码提交一一对应查看线上容器时一眼就知道跑的是哪次提交一旦发布出问题回滚只需要把tag改回上一个SHA并再跑一次部署步骤。4. 常见问题排查与迭代经验4.1 OAuth登录失败怎么看日志Drone和GitLab集成之后最常遇到的第一个问题就是登录失败。如果你看到的是“login failed. check api token or gitlab version”这行提示基本上可以断定是OAuth环节出了问题。第一步查看Drone Server的容器日志docker logs -f drone-server --tail 200日志里通常会有更详细的错误信息比如GitLab API返回的401/403、回调URL不匹配等。第二步用curl手动测试GitLab API确认当前OAuth应用的token是否有效curl -H Authorization: Bearer 你的token http://gitlab.example.com/api/v4/user如果返回JSON里有用户信息说明token没问题如果返回401说明token要么过期要么scope不够。第三步检查GitLab和Drone的版本兼容性。GitLab版本太老某些API路径或字段可能不被Drone支持。Drone官方文档里会列出对应的GitLab版本范围安装前先确认。4.2 Runner一直offline的问题Runner离线是部署阶段出现频率第二高的问题。排查思路很明确先看Runner日志docker logs -f drone-runner --tail 100如果出现registration或permission denied关键字先检查DRONE_RPC_SECRET是否一致。这个密钥在Server和Runner两端必须完全一样包括空格都算。如果日志显示connected但状态还是offline检查DRONE_RPC_HOST和DRONE_RPC_PROTO。一个是协议一个是地址注意host不要带http://前缀。还有防火墙因素。如果Server和Runner不在同机需要保证Runner能访问Server的80端口如果映射了8080就访问8080。可以用telnet或者nc测试一下nc -vz 192.168.1.10 8080能通问题基本就排除在网络层之外。4.3 构建慢、依赖下载失败怎么办Runner每次构建都会启动新容器拉取镜像和下载依赖是耗时大户。如果你发现流水线经常卡在拉取node:16-alpine这一步检查宿主机Docker的镜像源配置。修改/etc/docker/daemon.json类似这样{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com ] }改完执行systemctl restart docker再重新跑流水线速度会有明显提升。如果你用的是Docker Desktop这类工具在设置界面里也能直接配置registry-mirrors。npm install慢除了换registry源还有一个技巧把npm cache的volume挂载出来复用。不过Drone的docker runner默认不保留步骤间的容器文件系统所以直接用npm ci时还是要忍一下缓存缺失的代价。如果依赖特别多可以在node:16-alpine镜像基础上打一个自定义镜像把node_modules预先装好构建时再覆盖业务代码能快很多。4.4 部署后页面404或白屏怎么排查部署流程跑通后可能出现“流水线全绿但访问页面是白的”这种诡异情况。遇到这种情况别慌按顺序排查。先确认产物目录里有内容没有。SSH到服务器看/opt/www/html下是否有index.html以及有没有dist那一层目录套多了的情况。这个问题很常见因为scp的source和strip_components配置不当会导致首页文件实际在/opt/www/html/dist/index.htmlNginx的root指向/opt/www/html自然就404了。然后看Nginx错误日志tail -f /var/log/nginx/error.log如果日志里出现Permission denied说明deploy用户上传的文件nginx的worker进程没有读权限。解决办法是调整目录权限比如chown -R www-data:www-data /opt/www/html或者让deploy用户umask设置成022确保文件权限是644。白屏的话多半是前端资源加载失败。检查浏览器Network面板看JS文件是否404以及API请求是否被跨域或者代理配置挡住。很多时候是location /api/的proxy_pass少写了一个结尾路径导致接口转发404。这套GitLab Drone CI我前后用了两三年从最初只给前端项目做静态部署到后来给多个后端服务做镜像构建、多环境发布还在此基础上加了消息通知、人工确认等步骤。压过很多坑也在凌晨处理过Runner连不上的事故但总体上它给我的回报远远大于维护成本。如果你现在还在人工发布我建议不妨从一个小项目开始先跑通一条最简单的前端部署流水线用起来之后再慢慢扩展。最后送大家一个小技巧把.drone.yml和部署脚本当成产品代码一样对待命名清晰、注释充分、定期review这套系统才能在团队里长期稳定地跑下去。真要说持续集成的核心工具只是表象可维护性和确定性才是灵魂。
返回列表