{"id":26145,"date":"2024-02-29T16:21:37","date_gmt":"2024-02-29T15:21:37","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=26145"},"modified":"2024-02-29T17:59:59","modified_gmt":"2024-02-29T16:59:59","slug":"combining-zerolog-loki","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/02\/29\/combining-zerolog-loki\/","title":{"rendered":"Combining zerolog &amp; Loki"},"content":{"rendered":"\n<p><br>In a recent project, some of my fellow students and I developed a simple hosting provider that enables users to start Docker containers on a remote server using a CLI tool.<\/p>\n\n\n\n<p>During this project, we developed a Go-based backend service that exposed a REST API to the client and handled the connection to Terraform via RabbitMQ, authentication via Keycloak, and data persistence using DynamoDB. This resulted in the backend being a crucial service that most functionality relied on. Therefore, it was important for us to enable monitoring of the backend. This included the collection of logs containing information about the current events inside the backend as well as the current health of the instance.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">zerolog<\/h3>\n\n\n\n<p>As we were using zerolog to handle logging within the application, we encountered a simple issue: we needed the logs stored in a place that Grafana could easily query. While there are systems that would have worked for our Docker Compose setup, we found that pushing the logs to Loki from the application itself was the most usable and sustainable solution.<\/p>\n\n\n\n<p>So we opted to use the hooks feature of zerolog to publish the logs to Loki. First off, what are zerolog hooks?<\/p>\n\n\n\n<p>Zerolog allows us to pass in a struct with a Run function when creating the logger. This Run function is called whenever a log event is emitted. Therefore, hooks can easily be utilized to extend the functionality of zerolog. A simple example of this would be the following:<\/p>\n\n\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff;overflow: auto;width: auto;border: solid gray;border-width: .1em .1em .1em .8em;padding: .2em .6em\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0;line-height: 125%\"><span style=\"color: #228899;font-weight: bold\">package<\/span> main\n\n<span style=\"color: #228899;font-weight: bold\">import<\/span> (\n    <span style=\"background-color: #e0e0ff\">\"github.com\/rs\/zerolog\"<\/span>\n    <span style=\"background-color: #e0e0ff\">\"github.com\/rs\/zerolog\/log\"<\/span>\n)\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> init(){\n    log.Logger = log.Hook(LokiHook{})\n}\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> main() {\n    <span style=\"color: #228899;font-weight: bold\">for<\/span> {\n        log.Info().Msg(<span style=\"background-color: #e0e0ff\">\"Sample log message\"<\/span>)\n        time.Sleep(<span style=\"color: #6666ff;font-weight: bold\">5<\/span>)\n    }\n}\n\n<span style=\"color: #228899;font-weight: bold\">type<\/span> LokiHook <span style=\"color: #228899;font-weight: bold\">struct<\/span> {\n}\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> (h LokiHook) Run(e <span style=\"color: #333333\">*<\/span>zerolog.Event, level zerolog.Level, msg <span style=\"color: #6666ff;font-weight: bold\">string<\/span>) {\n    e.Str(<span style=\"background-color: #e0e0ff\">\"hello\"<\/span>, <span style=\"background-color: #e0e0ff\">\"world\"<\/span>)\n}\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n\n<p>This will produce the following output:<\/p>\n\n\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff;overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0;line-height: 125%\">{<span style=\"color: #007700\">\"level\"<\/span>:<span style=\"background-color: #e0e0ff\">\"info\"<\/span>,<span style=\"color: #007700\">\"time\"<\/span>:<span style=\"background-color: #e0e0ff\">\"2024-02-28T16:18:25+01:00\"<\/span>,<span style=\"color: #007700\">\"hello\"<\/span>:<span style=\"background-color: #e0e0ff\">\"world\"<\/span>,<span style=\"color: #007700\">\"message\"<\/span>:<span style=\"background-color: #e0e0ff\">\"Sample log message\"<\/span>}\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n\n<p>Great! We have the ability to execute a function on a log event. But where do we go from here?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Loki<\/h3>\n\n\n\n<p>Well, first of all, it&#8217;s important to have a look at how log messages are published to Loki: Loki exposes an API that uses either JSON or protobuf to allow clients to push logs to the Loki instance. In our case, we will focus on the JSON side of things. The endpoint <code class=\"\" data-line=\"\">\/loki\/api\/v1\/push<\/code> expects a JSON payload of the following format:<\/p>\n\n\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff;overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0;line-height: 125%\">{\n  <span style=\"color: #007700\">\"streams\"<\/span>: [\n    {\n      <span style=\"color: #007700\">\"stream\"<\/span>: {\n        <span style=\"color: #007700\">\"label\"<\/span>: <span style=\"background-color: #e0e0ff\">\"value\"<\/span>\n      },\n      <span style=\"color: #007700\">\"values\"<\/span>: [\n        [<span style=\"background-color: #e0e0ff\">\"&lt;unix epoch in nanoseconds&gt;\"<\/span>, <span style=\"background-color: #e0e0ff\">\"&lt;log line&gt;\"<\/span>],\n        [<span style=\"background-color: #e0e0ff\">\"&lt;unix epoch in nanoseconds&gt;\"<\/span>, <span style=\"background-color: #e0e0ff\">\"&lt;log line&gt;\"<\/span>]\n      ]\n    }\n  ]\n}\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n\n<p>This means that we simply have to write a zerolog hook that publishes the current log line to Loki this way. That is easy enough, right?<\/p>\n\n\n\n<p>While yes, this is simple to implement, it is easy to make a possibly fatal mistake. In our current approach, every log event results in an HTTP request. Now imagine a scenario where we wanted to horizontally scale our service. Given enough instances and users, we have essentially just created a tool to DDoS our own Loki instance. So obviously, we cannot proceed with this approach. Additionally, making an HTTP request is a rather slow process. Meaning depending on the frequency of log events, this may lead to a lot of hooks running at the same time. Mind you: All those hooks are goroutines. Given enough load, this could result in a stack overflow.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Implementation<\/h3>\n\n\n\n<p>Anyway, enough of the doomsday scenarios, let&#8217;s get to fixing:<br>The most straightforward approach to this problem is the introduction of a buffer. This buffer is filled with the log events that occur and once it reaches a certain size, it sends the entire batch of log statements at once. Using this tactic we can drastically reduce the amount of requests send to our Loki instance. <\/p>\n\n\n\n<p>However, tying the log event push to the current size of the buffer has one major drawback. If for some reason there are no log events over a long period of time, this could result in logs being published with extreme delay. As a result of this, Loki would be an inconsistent data source and could be more confusing than helpful when investigating incidents. <\/p>\n\n\n\n<p>This results in the need for a second condition that enables log events to be pushed to Loki. Instead of this second threshold being reliant on the current size of the buffer, it will simply depend on the time since the last time logs were pushed to Loki. To enable this check of the two threshold values, we will employ a simple goroutine that checks the time since the last push as well as the current size of the buffer. Additionally the buffer is split for every log level so the log level for the given events can be passed to Loki.<\/p>\n\n\n\n<p>This results in a struct with the following properties and the function <code class=\"\" data-line=\"\">bgRun<\/code> as the base for the goroutine:<\/p>\n\n\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff;overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0;line-height: 125%\"><span style=\"color: #228899;font-weight: bold\">type<\/span> LokiClient <span style=\"color: #228899;font-weight: bold\">struct<\/span> {\n    PushIntveralSeconds <span style=\"color: #6666ff;font-weight: bold\">int<\/span>\n    <span style=\"color: #666666;font-style: italic\">\/\/ This will also trigger the send event<\/span>\n    MaxBatchSize <span style=\"color: #6666ff;font-weight: bold\">int<\/span>\n    Values       <span style=\"color: #228899;font-weight: bold\">map<\/span>[<span style=\"color: #6666ff;font-weight: bold\">string<\/span>][][]<span style=\"color: #6666ff;font-weight: bold\">string<\/span>\n    LokiEndpoint <span style=\"color: #6666ff;font-weight: bold\">string<\/span>\n    BatchCount   <span style=\"color: #6666ff;font-weight: bold\">int<\/span>\n}\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> (l <span style=\"color: #333333\">*<\/span>LokiClient) bgRun() {\n    lastRunTimestamp <span style=\"color: #333333\">:=<\/span> <span style=\"color: #6666ff;font-weight: bold\">0<\/span>\n    isWorking <span style=\"color: #333333\">:=<\/span> <span style=\"color: #228899;font-weight: bold\">true<\/span>\n    <span style=\"color: #228899;font-weight: bold\">for<\/span> {\n        <span style=\"color: #228899;font-weight: bold\">if<\/span> time.Now().Second()<span style=\"color: #333333\">-<\/span>lastRunTimestamp &gt; l.PushIntveralSeconds <span style=\"color: #333333\">||<\/span> l.BatchCount &gt; l.MaxBatchSize {\n            <span style=\"color: #666666;font-style: italic\">\/\/ Loop over all log levels and send them<\/span>\n            <span style=\"color: #228899;font-weight: bold\">for<\/span> k <span style=\"color: #333333\">:=<\/span> <span style=\"color: #228899;font-weight: bold\">range<\/span> l.Values {\n                <span style=\"color: #228899;font-weight: bold\">if<\/span> <span style=\"color: #007722\">len<\/span>(l.Values) &gt; <span style=\"color: #6666ff;font-weight: bold\">0<\/span> {\n                    prevLogs <span style=\"color: #333333\">:=<\/span> l.Values[k]\n                    l.Values[k] = [][]<span style=\"color: #6666ff;font-weight: bold\">string<\/span>{}\n                    err <span style=\"color: #333333\">:=<\/span> pushToLoki(prevLogs, l.LokiEndpoint, k)\n                    <span style=\"color: #228899;font-weight: bold\">if<\/span> err <span style=\"color: #333333\">!=<\/span> <span style=\"color: #228899;font-weight: bold\">nil<\/span> <span style=\"color: #333333\">&amp;&amp;<\/span> isWorking {\n                        isWorking = <span style=\"color: #228899;font-weight: bold\">false<\/span>\n                        log.Error().Msgf(<span style=\"background-color: #e0e0ff\">\"Logs are currently not being forwarded to loki due to an error: %v\"<\/span>, err)\n                    }\n                    <span style=\"color: #228899;font-weight: bold\">if<\/span> err <span style=\"color: #333333\">==<\/span> <span style=\"color: #228899;font-weight: bold\">nil<\/span> <span style=\"color: #333333\">&amp;&amp;<\/span> !isWorking {\n                        isWorking = <span style=\"color: #228899;font-weight: bold\">true<\/span>\n                        <span style=\"color: #666666;font-style: italic\">\/\/ I will not accept PR comments about this log message tyvm<\/span>\n                        log.Info().Msgf(<span style=\"background-color: #e0e0ff\">\"Logs are now being published again. The loki instance seems to be reachable once more! May the logeth collecteth'r beest did bless with our logs\"<\/span>)\n                    }\n                }\n            }\n            lastRunTimestamp = time.Now().Second()\n            l.BatchCount = <span style=\"color: #6666ff;font-weight: bold\">0<\/span>\n        }\n    }\n}\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n\n<p>Based on the afore mentioned spec of the Loki API implementing the HTTP call to Loki is also simple enough:<\/p>\n\n\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff;overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0;line-height: 125%\"><span style=\"color: #228899;font-weight: bold\">type<\/span> lokiStream <span style=\"color: #228899;font-weight: bold\">struct<\/span> {\n    Stream <span style=\"color: #228899;font-weight: bold\">map<\/span>[<span style=\"color: #6666ff;font-weight: bold\">string<\/span>]<span style=\"color: #6666ff;font-weight: bold\">string<\/span> <span style=\"background-color: #e0e0ff\">`json:\"stream\"`<\/span>\n    Values [][]<span style=\"color: #6666ff;font-weight: bold\">string<\/span>        <span style=\"background-color: #e0e0ff\">`json:\"values\"`<\/span>\n}\n\n<span style=\"color: #228899;font-weight: bold\">type<\/span> lokiLogEvent <span style=\"color: #228899;font-weight: bold\">struct<\/span> {\n    Streams []lokiStream\n}\n\n<span style=\"color: #666666;font-style: italic\">\/*<\/span>\n<span style=\"color: #666666;font-style: italic\">This function contains *no* error handling\/logging because this:<\/span>\n<span style=\"color: #666666;font-style: italic\">a) should not crash the application<\/span>\n<span style=\"color: #666666;font-style: italic\">b) would mean that every run of this creates further logs that cannot be published<\/span>\n<span style=\"color: #666666;font-style: italic\">=&gt; The error will be returned and the problem will be logged ONCE by the handling function<\/span>\n<span style=\"color: #666666;font-style: italic\">*\/<\/span>\n<span style=\"color: #228899;font-weight: bold\">func<\/span> pushToLoki(logs [][]<span style=\"color: #6666ff;font-weight: bold\">string<\/span>, lokiEndpoint <span style=\"color: #6666ff;font-weight: bold\">string<\/span>, logLevel <span style=\"color: #6666ff;font-weight: bold\">string<\/span>) <span style=\"color: #6666ff;font-weight: bold\">error<\/span> {\n\n    lokiPushPath <span style=\"color: #333333\">:=<\/span> <span style=\"background-color: #e0e0ff\">\"\/loki\/api\/v1\/push\"<\/span>\n\n    data, err <span style=\"color: #333333\">:=<\/span> json.Marshal(lokiLogEvent{\n        Streams: []lokiStream{\n            {\n                Stream: <span style=\"color: #228899;font-weight: bold\">map<\/span>[<span style=\"color: #6666ff;font-weight: bold\">string<\/span>]<span style=\"color: #6666ff;font-weight: bold\">string<\/span>{\n                    <span style=\"background-color: #e0e0ff\">\"service\"<\/span>: <span style=\"background-color: #e0e0ff\">\"demo\"<\/span>,\n                    <span style=\"background-color: #e0e0ff\">\"level\"<\/span>:   logLevel,\n                },\n                Values: logs,\n            },\n        },\n    })\n\n    <span style=\"color: #228899;font-weight: bold\">if<\/span> err <span style=\"color: #333333\">!=<\/span> <span style=\"color: #228899;font-weight: bold\">nil<\/span> {\n        <span style=\"color: #228899;font-weight: bold\">return<\/span> err\n    }\n\n    req, err <span style=\"color: #333333\">:=<\/span> http.NewRequest(http.MethodPost, fmt.Sprintf(<span style=\"background-color: #e0e0ff\">\"%v%v\"<\/span>, lokiEndpoint, lokiPushPath), bytes.NewBuffer(data))\n\n    <span style=\"color: #228899;font-weight: bold\">if<\/span> err <span style=\"color: #333333\">!=<\/span> <span style=\"color: #228899;font-weight: bold\">nil<\/span> {\n        <span style=\"color: #228899;font-weight: bold\">return<\/span> err\n    }\n\n    ctx, cancel <span style=\"color: #333333\">:=<\/span> context.WithTimeout(req.Context(), <span style=\"color: #6666ff;font-weight: bold\">100<\/span><span style=\"color: #333333\">*<\/span>time.Millisecond)\n\n    <span style=\"color: #228899;font-weight: bold\">defer<\/span> cancel()\n\n    req = req.WithContext(ctx)\n\n    req.Header.Set(<span style=\"background-color: #e0e0ff\">\"Content-Type\"<\/span>, <span style=\"background-color: #e0e0ff\">\"application\/json\"<\/span>)\n\n    client <span style=\"color: #333333\">:=<\/span> <span style=\"color: #333333\">&amp;<\/span>http.Client{}\n\n    resp, err <span style=\"color: #333333\">:=<\/span> client.Do(req)\n    <span style=\"color: #228899;font-weight: bold\">if<\/span> err <span style=\"color: #333333\">!=<\/span> <span style=\"color: #228899;font-weight: bold\">nil<\/span> {\n        <span style=\"color: #228899;font-weight: bold\">return<\/span> err\n    }\n    <span style=\"color: #228899;font-weight: bold\">defer<\/span> resp.Body.Close()\n\n    <span style=\"color: #228899;font-weight: bold\">return<\/span> <span style=\"color: #228899;font-weight: bold\">nil<\/span>\n}\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n\n<p>And thats almost all the work done! Lets just apply that to our little example from earlier:<\/p>\n\n\n<p><!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #ffffff;overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em\">\n<table>\n<tbody>\n<tr>\n<td>\n<pre style=\"margin: 0;line-height: 125%\"><span style=\"color: #228899;font-weight: bold\">package<\/span> main\n\n<span style=\"color: #228899;font-weight: bold\">import<\/span> (\n    <span style=\"background-color: #e0e0ff\">\"strconv\"<\/span>\n    <span style=\"background-color: #e0e0ff\">\"time\"<\/span>\n\n    <span style=\"background-color: #e0e0ff\">\"github.com\/rs\/zerolog\"<\/span>\n    <span style=\"background-color: #e0e0ff\">\"github.com\/rs\/zerolog\/log\"<\/span>\n)\n\n<span style=\"color: #228899;font-weight: bold\">var<\/span> lokiClient LokiClient\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> init() {\n    lokiClient = LokiClient{\n        PushIntveralSeconds: <span style=\"color: #6666ff;font-weight: bold\">10<\/span>,  <span style=\"color: #666666;font-style: italic\">\/\/ Threshhold of 10s<\/span>\n        MaxBatchSize:        <span style=\"color: #6666ff;font-weight: bold\">500<\/span>, <span style=\"color: #666666;font-style: italic\">\/\/Threshold of 500 events<\/span>\n        LokiEndpoint:        <span style=\"background-color: #e0e0ff\">\"http:\/\/localhost:3100\"<\/span>,\n        BatchCount:          <span style=\"color: #6666ff;font-weight: bold\">0<\/span>,\n        Values:              <span style=\"color: #007722\">make<\/span>(<span style=\"color: #228899;font-weight: bold\">map<\/span>[<span style=\"color: #6666ff;font-weight: bold\">string<\/span>][][]<span style=\"color: #6666ff;font-weight: bold\">string<\/span>),\n    }\n\n    <span style=\"color: #228899;font-weight: bold\">go<\/span> lokiClient.bgRun()\n    log.Logger = log.Hook(LokiHook{})\n}\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> main() {\n\n    <span style=\"color: #228899;font-weight: bold\">for<\/span> {\n        log.Info().Msg(<span style=\"background-color: #e0e0ff\">\"Sample log message\"<\/span>)\n        time.Sleep(<span style=\"color: #6666ff;font-weight: bold\">1<\/span> <span style=\"color: #333333\">*<\/span> time.Second)\n    }\n}\n\n<span style=\"color: #228899;font-weight: bold\">type<\/span> LokiHook <span style=\"color: #228899;font-weight: bold\">struct<\/span> {\n}\n\n<span style=\"color: #228899;font-weight: bold\">func<\/span> (h LokiHook) Run(e <span style=\"color: #333333\">*<\/span>zerolog.Event, level zerolog.Level, msg <span style=\"color: #6666ff;font-weight: bold\">string<\/span>) {\n    lokiClient.Values[level.String()] = <span style=\"color: #007722\">append<\/span>(lokiClient.Values[level.String()], []<span style=\"color: #6666ff;font-weight: bold\">string<\/span>{strconv.FormatInt(time.Now().UnixNano(), <span style=\"color: #6666ff;font-weight: bold\">10<\/span>), msg})\n    lokiClient.BatchCount<span style=\"color: #333333\">++<\/span>\n}\n<\/pre>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n\n\n<p>To see the whole example checkout the <a href=\"https:\/\/github.com\/coffeemakingtoaster\/zerolog-loki-hook\" target=\"_blank\" rel=\"noopener sponsored\" title=\"\">repository<\/a>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"has-primary-color has-text-color\">When using large batch sizes ensure that your Loki instance is configured to allow batches of the given size.<\/p>\n<\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Publish zerolog events to Loki in just a few lines of code.<\/p>\n","protected":false},"author":1190,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1,650,2,657],"tags":[51,332,167],"ppma_author":[1011],"class_list":["post-26145","post","type-post","status-publish","format-standard","hentry","category-allgemein","category-scalable-systems","category-system-engineering","category-teaching-and-learning","tag-design","tag-go","tag-monitoring"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":26160,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/02\/29\/why-system-monitoring-is-important-and-how-we-approached-it\/","url_meta":{"origin":26145,"position":0},"title":"Why system monitoring is important and how we approached it","author":"Michelle Becher","date":"29. February 2024","format":false,"excerpt":"Introduction Imagine building a service that aims to generate as much user traffic as possible to be as profitable as possible. The infrastructure of your service usually includes some kind of backend, a server and other frameworks. One day, something is not working as it should and you can't seem\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/monitoring.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/monitoring.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/monitoring.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/monitoring.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":26197,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/02\/29\/terraform-x-go-challenges-when-interacting-with-terraform-through-go\/","url_meta":{"origin":26145,"position":1},"title":"Terraform x Go: Challenges when interacting with Terraform through Go","author":"Maximilian Tellmann","date":"29. February 2024","format":false,"excerpt":"Introduction goTerra In a recent project, some of my fellow students and I developed a basic hosting provider that allows a user to spin up Docker containers on a remote server, which is realized by using Terraform locally on the server. During this project, we developed a Go-based backend service\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Untitled-Project-1024x905.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Untitled-Project-1024x905.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Untitled-Project-1024x905.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/Untitled-Project-1024x905.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":12087,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2020\/09\/30\/getting-started-with-cloud-computing-a-covid-19-data-map\/","url_meta":{"origin":26145,"position":2},"title":"Getting Started with Cloud Computing &#8211; A COVID-19 Data Map","author":"mk306","date":"30. September 2020","format":false,"excerpt":"1. Abstract Are you searching for country-specific, up-to-date numbers and rates for the global pandemic caused by COVID-19? Well, then I got some bad news for you. You won\u2019t find any in this blog post\u2026 not directly anyway. If you are looking for in-depth information about public APIs, location-based data\u2026","rel":"","context":"In &quot;Cloud Technologies&quot;","block_context":{"text":"Cloud Technologies","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/scalable-systems\/cloud-technologies\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2020\/09\/Response-1.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2020\/09\/Response-1.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2020\/09\/Response-1.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":26146,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2024\/02\/29\/using-keycloak-as-iam-for-our-hosting-provider-service\/","url_meta":{"origin":26145,"position":3},"title":"Using Keycloak as IAM for our hosting provider service","author":"Dennis Schmidt","date":"29. February 2024","format":false,"excerpt":"Discover how Keycloak can revolutionize your IAM strategy and propel your projects to new heights of security and efficiency.","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2024\/02\/stmk_diagrams_en.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":11460,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2020\/09\/29\/get-car-location-using-raspberrypi-and-google-cloud-iot-core\/","url_meta":{"origin":26145,"position":4},"title":"Get car location using Raspberry Pi and Google Cloud IoT Core","author":"Simon L\u00f6bert","date":"29. September 2020","format":false,"excerpt":"Project idea Have you ever been in the situation, that you parked your car somewhere in the city and some hours later, you couldn't remember where you parked it? You may wish to have an application on your smartphone, which is able to locate your car. From this consideration, the\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2020\/09\/grafik.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":4395,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2019\/01\/17\/radcup-a-socialmedia-beerpong-app\/","url_meta":{"origin":26145,"position":5},"title":"Radcup &#8211; a socialmedia beerpong App","author":"Immanuel Haag","date":"17. January 2019","format":false,"excerpt":"Written by: Immanuel Haag, Christian M\u00fcller, Marc R\u00fcttler Radcup adds a bit of social media to the well-known game Beerpong. With Radcup the user has the possibility to register or login. Afterwards he can display\/localize already existing games and join them if possible or create new games. As soon as\u2026","rel":"","context":"In &quot;System Designs&quot;","block_context":{"text":"System Designs","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2019\/01\/Screen-Shot-2019-01-06-at-15.09.35.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2019\/01\/Screen-Shot-2019-01-06-at-15.09.35.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2019\/01\/Screen-Shot-2019-01-06-at-15.09.35.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":1011,"user_id":1190,"is_guest":0,"slug":"max_herkenhoff","display_name":"Max Herkenhoff","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/234ba1a5f12fa84beff88e72008ebc9b8331d7fe0ca3f1006d5b0c29c34907ee?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/26145","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/1190"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=26145"}],"version-history":[{"count":8,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/26145\/revisions"}],"predecessor-version":[{"id":26204,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/26145\/revisions\/26204"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=26145"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=26145"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=26145"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=26145"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}