Health-Check für Meteor-Apps

Mit der JavaScript Meteor Platform lassen sich sehr einfach Mobile- und Web-Anwendungen bauen.

Natürlich soll ihre Verfügbarkeit im Produktivbetrieb sichergestellt sein. Über etwaige Ausfälle wollen wir direkt per Email informiert werden.

Dabei helfen Tools wie Icinga2. Mit dessen Plugin „http_check“ lassen sich kontinuierlich HTTP-Anfragen verschicken und die Antworten auswerten.

http_check auf die Meteor-App?

Mhh, dann fragen wir doch einfach die auf Meteor-Standard-Port 3000 gestartete App per „http_check“ an. Das geht natürlich, weil, sofern die Anwendung läuft, ein HTTP-Status 200 OK zurückkommt. Leider ist das nicht alles. Als Content wird der Client der Meteor-App mitgeschickt. Und das kann dann schon mal so aussehen

<!DOCTYPE html>
<html>
<head>
	<link rel="stylesheet" type="text/css" class="__meteor-css__" href="/merged-stylesheets.css?hash=39916f721d8df2e161d119b4f0df09952926949a">
<title>myApp</title>
</head>
<body>
<script type="text/javascript">__meteor_runtime_config__ = ...</script>
<script type="text/javascript" src="/packages/underscore.js?hash=cde485f60699ff9aced3305f70189e39c665183c"></script>
 <script type="text/javascript" src="/packages/meteor.js?hash=27829e936d09beae3149ecfbf3332c42ccb1596f"></script>
<script type="text/javascript" src="/packages/meteor-base.js?hash=a4d07a6b394e56bbe6ccc773c95e7cdb3434960d"></script>
 <script type="text/javascript" src="/packages/mobile-experience.js?hash=8ded3e69a3e367f321ab9a2b52e3ecdd2661a365"></script>
<script type="text/javascript" src="/packages/modules-runtime.js?hash=0969a3165abf9612f4fb6ca11e39ddbae2c52756"></script>
 <script type="text/javascript" src="/packages/modules.js?hash=54fc414151b814f48e49420f1959ebfa8acefcc6"></script>
<script type="text/javascript" src="/packages/es5-shim.js?hash=adc3c6270d5697523fe2a72e73428390b7eba83a"></script>
 <script type="text/javascript" src="/packages/promise.js?hash=423beeba1d7cf6ade8d8d90a88cc89f8684857f4"></script>
<script type="text/javascript" src="/packages/ecmascript-runtime.js?hash=fffe20ae20ade1111a96909b90dde7ba91923e92"></script>
 <script type="text/javascript" src="/packages/babel-compiler.js?hash=a9546d4e245cfe40b406e08d40bf106241f01683"></script>
<script type="text/javascript" src="/packages/ecmascript.js?hash=370a8752194bcf73be7fffa3635715d0fbf7853d"></script>
 <script type="text/javascript" src="/packages/base64.js?hash=0053489bb30bb5c0e3545df151f83e41150344b0"></script>
<script type="text/javascript" src="/packages/ejson.js?hash=0f17ced99d522d48cd8f8b2139167fd06babd969"></script>
 <script type="text/javascript" src="/packages/id-map.js?hash=c7aea8dfa2bf46ff2ae0aa6c6cf09e36abc61d07"></script>
<script type="text/javascript" src="/packages/ordered-dict.js?hash=bacdd1852075630a01f7de783e5e8e8aa8541cdc"></script>
 <script type="text/javascript" src="/packages/tracker.js?hash=9f8a0cec09c662aad5a5e224447b2d4e88d011ef"></script>
<script type="text/javascript" src="/packages/babel-runtime.js?hash=81591737f14ce07f210dfca6637615da6aca10aa"></script>
 <script type="text/javascript" src="/packages/random.js?hash=a3be1ee923a6fc933f063c7f8de3e15243e12f47"></script>
<script type="text/javascript" src="/packages/mongo-id.js?hash=345d169d517353f8146292b4abd24061721f8b26"></script>
 <script type="text/javascript" src="/packages/diff-sequence.js?hash=15014d7b1e11c05111a386992e684ab1d3cc4158"></script>
<script type="text/javascript" src="/packages/geojson-utils.js?hash=b204c7d4caf119e6883522fb87c6cce060724bf0"></script>
 <script type="text/javascript" src="/packages/minimongo.js?hash=8645fc685d558a15e6207c847f5709d20f6a14d9"></script>
<script type="text/javascript" src="/packages/check.js?hash=87c633843915b879a0c9676ea81f1cd351296e41"></script>
 <script type="text/javascript" src="/packages/retry.js?hash=1e409617b538ff3e2b0238b15e45b3380c51a224"></script>
<script type="text/javascript" src="/packages/ddp-common.js?hash=d42359bcace6c66ac90e2782193494253ee68155"></script>
 <script type="text/javascript" src="/packages/reload.js?hash=628b069673bffbc7390ba84ece8809c8c88c2eed"></script>
<script type="text/javascript" src="/packages/ddp-client.js?hash=89f721bb437611dfd558156033a1367eb42686c0"></script>
 <script type="text/javascript" src="/packages/ddp.js?hash=25dc3f428447c81620c91c4245dbc6e4f7d32fb7"></script>
<script type="text/javascript" src="/packages/ddp-server.js?hash=1beefbc7bd033ea687e7ab8fbd5694df072662af"></script>
 <script type="text/javascript" src="/packages/allow-deny.js?hash=9651dba61aa212828975b89e7c889af540c6a5da"></script>
<script type="text/javascript" src="/packages/insecure.js?hash=a0e5f17c280f4c7b05178d36a7ceb07cb7b086c6"></script>
 <script type="text/javascript" src="/packages/mongo.js?hash=90f037f47abee1e74ba80360e6b3f3dbaa792260"></script>
<script type="text/javascript" src="/packages/blaze-html-templates.js?hash=6e8335ce66460e45f00da73c7497654c5e26e236"></script>
 <script type="text/javascript" src="/packages/reactive-var.js?hash=ec712fa3ae588c4a1e7017f0bb4507c725391225"></script>
<script type="text/javascript" src="/packages/standard-minifier-css.js?hash=cfe82682f4394d3ffc6335555c1f9f3f73294507"></script>
 <script type="text/javascript" src="/packages/standard-minifier-js.js?hash=041bab58c8a89172eaab795deb5d96e38b64ec37"></script>
<script type="text/javascript" src="/packages/shell-server.js?hash=6ff1313e4bf7618e577eb2604a580b2ea9b7631f"></script>
 <script type="text/javascript" src="/packages/autopublish.js?hash=073bd4c42d2fb6182c944501b4f30e8d17bcceb3"></script>
<script type="text/javascript" src="/packages/meteorhacks_picker.js?hash=56585121bc9cb10cb03f03abc33aef5266599360"></script>
 <script type="text/javascript" src="/packages/jquery.js?hash=c57b3cfa0ca9c66400d4456b6f6f1e486ee10aad"></script>
<script type="text/javascript" src="/packages/twbs_bootstrap.js?hash=2ee228e6c80c1d9a4b1e67e10006f8a5a425ddda"></script>
 <script type="text/javascript" src="/packages/url.js?hash=137892484d84f8a5ae5824e6fc7ac12745fa43e9"></script>
<script type="text/javascript" src="/packages/http.js?hash=9355a65a433bea87be60bc1fd90e0ef608af93e4"></script>
 <script type="text/javascript" src="/packages/bshamblen_json-schema-generator.js?hash=4a38a545be36ec28833612763968a0d05f93e329"></script>
<script type="text/javascript" src="/packages/momentjs_moment.js?hash=fb823255c21127031b96960fe36a34bce112a48d"></script>
 <script type="text/javascript" src="/packages/webapp.js?hash=8024f6bce97bd768bcff7fc9d76449e74f051e36"></script>
<script type="text/javascript" src="/packages/livedata.js?hash=7cf1831a60b48e304b054aee1ae0f7e38ff35d09"></script>
 <script type="text/javascript" src="/packages/hot-code-push.js?hash=2e864a0bdd0d5f686115099f8c48eb6c866b5b14"></script>
<script type="text/javascript" src="/packages/observe-sequence.js?hash=8fe58036c6ba00c458f54c360a21fd0e41fb7ee0"></script>
 <script type="text/javascript" src="/packages/deps.js?hash=7313f5a2685c6c2c673c78c15c8ce86ff59ab0c9"></script>
<script type="text/javascript" src="/packages/htmljs.js?hash=1ac878018eee6c53ed1375dc7ee75fc6865666ae"></script>
 <script type="text/javascript" src="/packages/blaze.js?hash=813922cefaf3c9d7388442268c14f87d2dde795f"></script>
<script type="text/javascript" src="/packages/spacebars.js?hash=ebf9381e7fc625d41acb0df14995b7614360858a"></script>
 <script type="text/javascript" src="/packages/templating-compiler.js?hash=a71883cdec50e95ca135291415990753ed6d57fc"></script>
<script type="text/javascript" src="/packages/templating-runtime.js?hash=c18de19afda6e9f0db7faf3d4382a4c953cabe18"></script>
 <script type="text/javascript" src="/packages/templating.js?hash=c2cf38de06efb47f67affb2dff9320e5eef33893"></script>
<script type="text/javascript" src="/packages/launch-screen.js?hash=2f56943306c7e900ed9f4d894b87f534ebffeaeb"></script>
 <script type="text/javascript" src="/packages/ui.js?hash=039c55a98376abd03d9d8cd4100895861b897643"></script>
<script type="text/javascript" src="/packages/autoupdate.js?hash=1fd9cf3472adaa6887170d88ab5ea1ddabf695fa"></script>
 <script type="text/javascript" src="/packages/global-imports.js?hash=c22a9ef6aa88d73cec0b451c0742a5a82a720ad7"></script>
<script type="text/javascript" src="/app/app.js?hash=da0bb5641b812e31cf841c1426b403fb04e21df9"></script>
</body>
</html>

Wow, das ist doch etwas viel des Guten und schon gar nicht die Art von Meta-Informationen die wir von einem Health-Check erwarten.

Eine Nummer kleiner bitte!

HTTP-Health-Checks werden oft unter einem separaten URL-Pfad wie z.B. „http:localhost/health“ bereitgestellt. Sie geben unter dieser Adresse Auskunft über den Zustand der Anwendung.

Um eine Meteor-App mit einem einfachen Health-Check zu versehen, muss eine entsprechende Route definiert werden. Da wir nicht über die Client-Seite gehen wollen, bietet sich der Server-seitige Router meteorhacks:picker an. Über die Konsole lässt sich das Package einfach nachinstallieren.

meteor add meteorhacks:picker

Die Route selbst wird im „server“-Verzeichnis in der Datei „routes.js“ definiert.

Picker.route('/health', function(params, req, res, next) {
  res.setHeader( 'Content-Length', 0 );
  res.statusCode = 200;
  res.end();
});

Im Bespiel geben wir beim Aufruf von „/health“ den HTTP-Status 200 OK ohne Content zurück. Kompakter kann das Lebenszeichen unserer Anwendung kaum ausfallen.

Kontext, Baby!

Um mehr über die Anwendung und ihren Zustand zu erfahren, lassen sich beliebige Meta-Informationen als Content zurückgeben. Im Beispiel geben wir den Namen der Anwendung und wann seit wann sie am Start ist zurück.

Um herauszubekommen, wann die App gestartet wurde, erweitern wir die Methode „Meteor.startup()“ im „server“-Verzeichnis in der Datei „main.js“ um eine globale Variable „startDate“ und weisen ihr das Startdatum zu.

Meteor.startup(() => {
    startDate = new Date();
});

In unserem Router greifen wir auf das „startDate“ zu, formatieren es und verpacken es zusammen mit dem Namen der App in einem JSON-Objekt.

Mit Hilfe des Packages „momentjs:moment“ lassen sich Date-Objekte sehr einfach parsen, manipulieren, validieren und anzeigen.

Picker.route('/health', function(params, req, res, next) {
    let context = {
      "app-name" : "myApp",
      "up-since" : moment(startDate).format('DD.MM.YYYY HH:mm:ss')
    }

    res.setHeader( 'Content-Type', 'application/json' );
    res.statusCode = 200;
    res.end( JSON.stringify( context) );
});

Nun ist das Ergebnis unseres Health-Checks nicht nur sehr kompakt, sondern liefert auch erste sinnvolle Informationen über den Zustand der Anwendung.

{
  "app-name": "myApp",
  "up-since": "19.03.2017 02:01:41"
}

Voilà.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s