HTML tags being removed after theme_preprocess_node
As described in Render API overview, the value passed in #markup is sanitized via \Drupal\Component\Utility\Xss::filterAdmin(). You can eventually pass the tags not to filter via the #allowed_tags properties, but the tag attributes could still be sanited.

To have full control on what it is sanitized, you should use #theme or #type instead of #markup. (The first is my choice when the markup is rendered only in a single place/page. Alternatively, you could also use #inline_template as in the following example code taken from the documentation page.

$build['hello'] = [
  '#type' =&gt; 'inline_template',
  '#template' =&gt; "{% trans %} Hello {% endtrans %} &lt;strong&gt;{{name}}&lt;/strong&gt;",
  '#context' =&gt; [
    'name' =&gt; $name,
  ],
];


It allows to use any Twig filter Drupal core can use and to have full control over the rendered tags.
It's the easier alternative to #theme which doesn't require to implement hook_theme() nor putting the template content in a separated file. (If you need to reuse the same inline template in different pages, or the template content is rather complex, using #theme is still preferable.)