Heroku部署NodeJS-MongoDB MQTT服务报H10应用崩溃如何解决
问题背景
正在搭建基于NodeJS-MongoDB的MQTT订阅服务,用于接收payload并将数据写入MongoDB数据库。执行heroku open命令时触发错误,访问应用公网链接返回Application error提示。
初始配置
最初的Procfile配置如下:
web: node app.js $PORT
app.js 完整代码
var axios = require('axios'); var mqtt = require('mqtt'); var mongoose = require('mongoose'); /* var MongoClient = require('mongodb').MongoClient; var dbName = "MPProject"; var port = "27017"; var host = "localhost"; */ var connectionString = [CENSORED FOR STACK OVERFLOW]; mongoose.connect(connectionString); mongoose.connection.on('error', function(error) { console.error('Database connection error:', error); }); mongoose.connection.once('open', function() { console.log('Database connected'); }); var client = mqtt.connect("mqtt://test.mosquitto.org") // var a = getNumOfDocs("IoTGreenhouseData",host,port,dbName,function (err,count){ // if (err){ // return console.log(err.message); // } // console.log('number of documents',count); // }) var currentdate = new Date(); var datetime = currentdate.getDate() + "/" + (currentdate.getMonth()+1) + "/" + currentdate.getFullYear() + " @ " + currentdate.getHours() + ":" + currentdate.getMinutes(); function insertData(message,datetime){ var data = JSON.stringify({ "collection": "IoTGreenhouseData", "database": "MPProject", "dataSource": "Cluster0", "document": { "text":message.toString(), "time":datetime } }); return data; } /* var config = { method: 'post', url: `https://data.mongodb-api.com/app/data-rrokr/endpoint/data/beta/action/insertOne`, headers: { 'Content-Type': 'application/json', 'Access-Control-Request-Headers': '*', 'api-key': [CENSORED FOR STACK OVERFLOW] }, data : insertData() }; */ client.on('connect', () => { console.log('Connected') client.subscribe(["mp/ph"], () => { console.log(`Subscribe to topic 'mp/ph'`) }) }) client.on('message',function(topic,payload){ var currentdate = new Date(); var datetime = currentdate.getDate() + "/" + (currentdate.getMonth()+1) + "/" + currentdate.getFullYear() + " @ " + currentdate.getHours() + ":" + currentdate.getMinutes(); axios({ method: 'post', url: `https://data.mongodb-api.com/app/data-rrokr/endpoint/data/beta/action/insertOne`, headers: { 'Content-Type': 'application/json', 'Access-Control-Request-Headers': '*', 'api-key': 'xmNZ31KLLr2nd1rOsFcSBKohUOWZNPxXlnrDdVQlc2Fu0ugm308OBDv7c5SgRGIh' }, data : insertData(payload,datetime) }) .then(function (response) { console.log(JSON.stringify(response.data)); }) .catch(function (error) { console.log(error); }); });
错误日志
Heroku返回的错误日志如下,对应截图:
2022-07-23T01:20:37.932429+00:00 heroku[web.1]: Process exited with status 1 2022-07-23T01:20:38.030691+00:00 heroku[web.1]: State changed from starting to crashed 2022-07-23T01:24:37.086275+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/" host=iotseedgermination.herokuapp.com request_id=b5b846cd-9497-4337-9bc5-e69ba5c123ef fwd="[REMOVED FOR STACK OVERFLOW]" dyno= connect= service= status=503 bytes= protocol=https 2022-07-23T01:24:37.388143+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GET path="/favicon.ico" host=iotseedgermination.herokuapp.com request_id=6239812a-7594-4f70-86b6-b1fc46d1c13c fwd="[REMOVED FOR STACK OVERFLOW]" dyno= connect= service= status=503 bytes= protocol=https
已尝试操作
后续将Procfile修改为如下内容,问题仍未解决,怀疑Procfile未被平台正确识别:
worker: node app.js

排查思路与解决方案
按以下优先级排查修复即可解决问题:
- 进程类型配置与dyno启动规则不匹配
最开始用web类型进程时,Heroku要求web进程必须在启动后的60秒内绑定到$PORT环境变量指定的端口监听HTTP请求,你的代码里没有任何HTTP服务逻辑(没有用express/koa等启动web服务,也没有监听对应端口),平台检测到端口绑定超时会直接杀死进程,返回H10崩溃错误。
改成worker类型后,worker进程不需要监听端口,但你没有手动启动worker dyno——默认情况下Heroku只会自动启动web类型的dyno,worker类型需要手动执行扩缩容命令启动:
执行后会关闭无效的web进程,启动worker进程运行MQTT订阅逻辑。heroku ps:scale web=0 worker=1 - 依赖缺失检查
确认项目根目录下的package.json里已经把axios、mqtt、mongoose三个依赖全部写入dependencies字段,Heroku部署时只会自动安装dependencies里的包,如果漏写会启动时直接报模块找不到的错误,导致进程退出状态码1。
本地执行以下命令可以自动补全依赖写入:
之后把更新后的npm install axios mqtt mongoose --savepackage.json和package-lock.json一起推送到Heroku仓库重新部署即可。 - 安全风险修复
你代码里硬编码的MongoDB Data API密钥已经泄露,建议立刻去MongoDB控制台重置该API密钥,把密钥配置到Heroku的环境变量里,不要硬编码在代码中,避免被恶意盗用产生费用或数据泄露。配置环境变量后代码里通过process.env.你的变量名读取即可。
内容的提问来源于stack exchange,提问作者Amtin
相关产品推荐
相关产品推荐

