ingress-nginx/deploy/baremetal/index.html

116 lines
48 KiB
HTML

<!doctype html><html lang=en class=no-js> <head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link href=https://kubernetes.github.io/ingress-nginx/deploy/baremetal/ rel=canonical><link rel="shortcut icon" href=../../assets/images/favicon.png><meta name=generator content="mkdocs-1.1.2, mkdocs-material-6.2.4"><title>Bare-metal considerations - NGINX Ingress Controller</title><link rel=stylesheet href=../../assets/stylesheets/main.15aa0b43.min.css><link rel=stylesheet href=../../assets/stylesheets/palette.75751829.min.css><meta name=theme-color content=#009485><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link rel=stylesheet href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700%7CRoboto+Mono&display=fallback"><style>body,input{font-family:"Roboto",-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono",SFMono-Regular,Consolas,Menlo,monospace}</style><link rel=stylesheet href=../../extra.css><script>window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)},ga.l=+new Date,ga("create","UA-118407822-1","kubernetes.github.io"),ga("set","anonymizeIp",!0),ga("send","pageview"),document.addEventListener("DOMContentLoaded",function(){document.forms.search&&document.forms.search.query.addEventListener("blur",function(){if(this.value){var e=document.location.pathname;ga("send","pageview",e+"?q="+this.value)}})}),document.addEventListener("DOMContentSwitch",function(){ga("send","pageview",document.location.pathname)})</script><script async src=https://www.google-analytics.com/analytics.js></script></head> <body dir=ltr data-md-color-scheme data-md-color-primary=teal data-md-color-accent=green> <input class=md-toggle data-md-toggle=drawer type=checkbox id=__drawer autocomplete=off> <input class=md-toggle data-md-toggle=search type=checkbox id=__search autocomplete=off> <label class=md-overlay for=__drawer></label> <div data-md-component=skip> <a href=#bare-metal-considerations class=md-skip> Skip to content </a> </div> <div data-md-component=announce> </div> <header class=md-header data-md-component=header> <nav class="md-header-nav md-grid" aria-label=Header> <a href=https://kubernetes.github.io/ingress-nginx title="NGINX Ingress Controller" class="md-header-nav__button md-logo" aria-label="NGINX Ingress Controller"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M12 8a3 3 0 003-3 3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 003 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54z"/></svg> </a> <label class="md-header-nav__button md-icon" for=__drawer> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2z"/></svg> </label> <div class=md-header-nav__title data-md-component=header-title> <div class=md-header-nav__ellipsis> <div class=md-header-nav__topic> <span class=md-ellipsis> NGINX Ingress Controller </span> </div> <div class=md-header-nav__topic> <span class=md-ellipsis> Bare-metal considerations </span> </div> </div> </div> <label class="md-header-nav__button md-icon" for=__search> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0116 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 019.5 16 6.5 6.5 0 013 9.5 6.5 6.5 0 019.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg> </label> <div class=md-search data-md-component=search role=dialog> <label class=md-search__overlay for=__search></label> <div class=md-search__inner role=search> <form class=md-search__form name=search> <input type=text class=md-search__input name=query aria-label=Search placeholder=Search autocapitalize=off autocorrect=off autocomplete=off spellcheck=false data-md-component=search-query data-md-state=active required> <label class="md-search__icon md-icon" for=__search> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0116 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 019.5 16 6.5 6.5 0 013 9.5 6.5 6.5 0 019.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5z"/></svg> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg> </label> <button type=reset class="md-search__icon md-icon" aria-label=Clear data-md-component=search-reset tabindex=-1> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg> </button> </form> <div class=md-search__output> <div class=md-search__scrollwrap data-md-scrollfix> <div class=md-search-result data-md-component=search-result> <div class=md-search-result__meta> Initializing search </div> <ol class=md-search-result__list></ol> </div> </div> </div> </div> </div> <div class=md-header-nav__source> <a href=https://github.com/kubernetes/ingress-nginx/ title="Go to repository" class=md-source> <div class="md-source__icon md-icon"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 00-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 01-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 000 40.81l195.61 195.6a28.86 28.86 0 0040.8 0l194.69-194.69a28.86 28.86 0 000-40.81z"/></svg> </div> <div class=md-source__repository> kubernetes/ingress-nginx </div> </a> </div> </nav> </header> <div class=md-container data-md-component=container> <nav class=md-tabs aria-label=Tabs data-md-component=tabs> <div class="md-tabs__inner md-grid"> <ul class=md-tabs__list> <li class=md-tabs__item> <a href=../.. class=md-tabs__link> Welcome </a> </li> <li class=md-tabs__item> <a href=../ class="md-tabs__link md-tabs__link--active"> Deployment </a> </li> <li class=md-tabs__item> <a href=../../user-guide/nginx-configuration/ class=md-tabs__link> User guide </a> </li> <li class=md-tabs__item> <a href=../../examples/ class=md-tabs__link> Examples </a> </li> </ul> </div> </nav> <main class=md-main data-md-component=main> <div class="md-main__inner md-grid"> <div class="md-sidebar md-sidebar--primary" data-md-component=navigation> <div class=md-sidebar__scrollwrap> <div class=md-sidebar__inner> <nav class="md-nav md-nav--primary md-nav--lifted" aria-label=Navigation data-md-level=0> <label class=md-nav__title for=__drawer> <a href=https://kubernetes.github.io/ingress-nginx title="NGINX Ingress Controller" class="md-nav__button md-logo" aria-label="NGINX Ingress Controller"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M12 8a3 3 0 003-3 3 3 0 00-3-3 3 3 0 00-3 3 3 3 0 003 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54z"/></svg> </a> NGINX Ingress Controller </label> <div class=md-nav__source> <a href=https://github.com/kubernetes/ingress-nginx/ title="Go to repository" class=md-source> <div class="md-source__icon md-icon"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 448 512"><path d="M439.55 236.05L244 40.45a28.87 28.87 0 00-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 01-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 000 40.81l195.61 195.6a28.86 28.86 0 0040.8 0l194.69-194.69a28.86 28.86 0 000-40.81z"/></svg> </div> <div class=md-source__repository> kubernetes/ingress-nginx </div> </a> </div> <ul class=md-nav__list data-md-scrollfix> <li class="md-nav__item md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-1 type=checkbox id=nav-1> <label class=md-nav__link for=nav-1> Welcome <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label=Welcome data-md-level=1> <label class=md-nav__title for=nav-1> <span class="md-nav__icon md-icon"></span> Welcome </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../.. class=md-nav__link> Welcome </a> </li> <li class=md-nav__item> <a href=../../how-it-works/ class=md-nav__link> How it works </a> </li> <li class=md-nav__item> <a href=../../troubleshooting/ class=md-nav__link> Troubleshooting </a> </li> <li class=md-nav__item> <a href=../../kubectl-plugin/ class=md-nav__link> kubectl plugin </a> </li> <li class=md-nav__item> <a href=../../development/ class=md-nav__link> Development </a> </li> </ul> </nav> </li> <li class="md-nav__item md-nav__item--active md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-2 type=checkbox id=nav-2 checked> <label class=md-nav__link for=nav-2> Deployment <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label=Deployment data-md-level=1> <label class=md-nav__title for=nav-2> <span class="md-nav__icon md-icon"></span> Deployment </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../ class=md-nav__link> Installation Guide </a> </li> <li class="md-nav__item md-nav__item--active"> <input class="md-nav__toggle md-toggle" data-md-toggle=toc type=checkbox id=__toc> <label class="md-nav__link md-nav__link--active" for=__toc> Bare-metal considerations <span class="md-nav__icon md-icon"></span> </label> <a href=./ class="md-nav__link md-nav__link--active"> Bare-metal considerations </a> <nav class="md-nav md-nav--secondary" aria-label="Table of contents"> <label class=md-nav__title for=__toc> <span class="md-nav__icon md-icon"></span> Table of contents </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=#a-pure-software-solution-metallb class=md-nav__link> A pure software solution: MetalLB </a> </li> <li class=md-nav__item> <a href=#over-a-nodeport-service class=md-nav__link> Over a NodePort Service </a> </li> <li class=md-nav__item> <a href=#via-the-host-network class=md-nav__link> Via the host network </a> </li> <li class=md-nav__item> <a href=#using-a-self-provisioned-edge class=md-nav__link> Using a self-provisioned edge </a> </li> <li class=md-nav__item> <a href=#external-ips class=md-nav__link> External IPs </a> </li> </ul> </nav> </li> <li class=md-nav__item> <a href=../rbac/ class=md-nav__link> Role Based Access Control (RBAC) </a> </li> <li class=md-nav__item> <a href=../upgrade/ class=md-nav__link> Upgrade </a> </li> <li class=md-nav__item> <a href=../hardening-guide/ class=md-nav__link> Hardening guide </a> </li> </ul> </nav> </li> <li class="md-nav__item md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-3 type=checkbox id=nav-3> <label class=md-nav__link for=nav-3> User guide <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label="User guide" data-md-level=1> <label class=md-nav__title for=nav-3> <span class="md-nav__icon md-icon"></span> User guide </label> <ul class=md-nav__list data-md-scrollfix> <li class="md-nav__item md-nav__item--section md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-3-1 type=checkbox id=nav-3-1> <label class=md-nav__link for=nav-3-1> NGINX Configuration <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label="NGINX Configuration" data-md-level=2> <label class=md-nav__title for=nav-3-1> <span class="md-nav__icon md-icon"></span> NGINX Configuration </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../../user-guide/nginx-configuration/ class=md-nav__link> Introduction </a> </li> <li class=md-nav__item> <a href=../../user-guide/basic-usage/ class=md-nav__link> Basic usage </a> </li> <li class=md-nav__item> <a href=../../user-guide/nginx-configuration/annotations/ class=md-nav__link> Annotations </a> </li> <li class=md-nav__item> <a href=../../user-guide/nginx-configuration/configmap/ class=md-nav__link> ConfigMap </a> </li> <li class=md-nav__item> <a href=../../user-guide/nginx-configuration/custom-template/ class=md-nav__link> Custom NGINX template </a> </li> <li class=md-nav__item> <a href=../../user-guide/nginx-configuration/log-format/ class=md-nav__link> Log format </a> </li> </ul> </nav> </li> <li class=md-nav__item> <a href=../../user-guide/cli-arguments/ class=md-nav__link> Command line arguments </a> </li> <li class=md-nav__item> <a href=../../user-guide/custom-errors/ class=md-nav__link> Custom errors </a> </li> <li class=md-nav__item> <a href=../../user-guide/default-backend/ class=md-nav__link> Default backend </a> </li> <li class=md-nav__item> <a href=../../user-guide/exposing-tcp-udp-services/ class=md-nav__link> Exposing TCP and UDP services </a> </li> <li class=md-nav__item> <a href=../../user-guide/fcgi-services/ class=md-nav__link> Exposing FCGI services </a> </li> <li class=md-nav__item> <a href=../../user-guide/ingress-path-matching/ class=md-nav__link> Regular expressions in paths </a> </li> <li class=md-nav__item> <a href=../../user-guide/external-articles/ class=md-nav__link> External Articles </a> </li> <li class=md-nav__item> <a href=../../user-guide/miscellaneous/ class=md-nav__link> Miscellaneous </a> </li> <li class=md-nav__item> <a href=../../user-guide/monitoring/ class=md-nav__link> Prometheus and Grafana installation </a> </li> <li class=md-nav__item> <a href=../../user-guide/multiple-ingress/ class=md-nav__link> Multiple Ingress controllers </a> </li> <li class=md-nav__item> <a href=../../user-guide/tls/ class=md-nav__link> TLS/HTTPS </a> </li> <li class="md-nav__item md-nav__item--section md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-3-13 type=checkbox id=nav-3-13> <label class=md-nav__link for=nav-3-13> Third party addons <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label="Third party addons" data-md-level=2> <label class=md-nav__title for=nav-3-13> <span class="md-nav__icon md-icon"></span> Third party addons </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../../user-guide/third-party-addons/modsecurity/ class=md-nav__link> ModSecurity Web Application Firewall </a> </li> <li class=md-nav__item> <a href=../../user-guide/third-party-addons/opentracing/ class=md-nav__link> OpenTracing </a> </li> </ul> </nav> </li> </ul> </nav> </li> <li class="md-nav__item md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-4 type=checkbox id=nav-4> <label class=md-nav__link for=nav-4> Examples <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label=Examples data-md-level=1> <label class=md-nav__title for=nav-4> <span class="md-nav__icon md-icon"></span> Examples </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../../examples/ class=md-nav__link> Introduction </a> </li> <li class=md-nav__item> <a href=../../examples/PREREQUISITES/ class=md-nav__link> Prerequisites </a> </li> <li class=md-nav__item> <a href=../../examples/affinity/cookie/ class=md-nav__link> Sticky Sessions </a> </li> <li class="md-nav__item md-nav__item--section md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-4-4 type=checkbox id=nav-4-4> <label class=md-nav__link for=nav-4-4> Auth <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label=Auth data-md-level=2> <label class=md-nav__title for=nav-4-4> <span class="md-nav__icon md-icon"></span> Auth </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../../examples/auth/basic/ class=md-nav__link> Basic Authentication </a> </li> <li class=md-nav__item> <a href=../../examples/auth/client-certs/ class=md-nav__link> Client Certificate Authentication </a> </li> <li class=md-nav__item> <a href=../../examples/auth/external-auth/ class=md-nav__link> External Basic Authentication </a> </li> <li class=md-nav__item> <a href=../../examples/auth/oauth-external-auth/ class=md-nav__link> External OAUTH Authentication </a> </li> </ul> </nav> </li> <li class="md-nav__item md-nav__item--section md-nav__item--nested"> <input class="md-nav__toggle md-toggle" data-md-toggle=nav-4-5 type=checkbox id=nav-4-5> <label class=md-nav__link for=nav-4-5> Customization <span class="md-nav__icon md-icon"></span> </label> <nav class=md-nav aria-label=Customization data-md-level=2> <label class=md-nav__title for=nav-4-5> <span class="md-nav__icon md-icon"></span> Customization </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=../../examples/customization/configuration-snippets/ class=md-nav__link> Configuration Snippets </a> </li> <li class=md-nav__item> <a href=../../examples/customization/custom-configuration/ class=md-nav__link> Custom Configuration </a> </li> <li class=md-nav__item> <a href=../../examples/customization/custom-errors/ class=md-nav__link> Custom Errors </a> </li> <li class=md-nav__item> <a href=../../examples/customization/custom-headers/ class=md-nav__link> Custom Headers </a> </li> <li class=md-nav__item> <a href=../../examples/customization/external-auth-headers/ class=md-nav__link> External authentication </a> </li> <li class=md-nav__item> <a href=../../examples/customization/ssl-dh-param/ class=md-nav__link> Custom DH parameters for perfect forward secrecy </a> </li> <li class=md-nav__item> <a href=../../examples/customization/sysctl/ class=md-nav__link> Sysctl tuning </a> </li> </ul> </nav> </li> <li class=md-nav__item> <a href=../../examples/docker-registry/ class=md-nav__link> Docker registry </a> </li> <li class=md-nav__item> <a href=../../examples/grpc/ class=md-nav__link> gRPC </a> </li> <li class=md-nav__item> <a href=../../examples/multi-tls/ class=md-nav__link> Multi TLS certificate termination </a> </li> <li class=md-nav__item> <a href=../../examples/rewrite/ class=md-nav__link> Rewrite </a> </li> <li class=md-nav__item> <a href=../../examples/static-ip/ class=md-nav__link> Static IPs </a> </li> <li class=md-nav__item> <a href=../../examples/tls-termination/ class=md-nav__link> TLS termination </a> </li> <li class=md-nav__item> <a href=../../examples/psp/ class=md-nav__link> Pod Security Policy (PSP) </a> </li> </ul> </nav> </li> </ul> </nav> </div> </div> </div> <div class="md-sidebar md-sidebar--secondary" data-md-component=toc> <div class=md-sidebar__scrollwrap> <div class=md-sidebar__inner> <nav class="md-nav md-nav--secondary" aria-label="Table of contents"> <label class=md-nav__title for=__toc> <span class="md-nav__icon md-icon"></span> Table of contents </label> <ul class=md-nav__list data-md-scrollfix> <li class=md-nav__item> <a href=#a-pure-software-solution-metallb class=md-nav__link> A pure software solution: MetalLB </a> </li> <li class=md-nav__item> <a href=#over-a-nodeport-service class=md-nav__link> Over a NodePort Service </a> </li> <li class=md-nav__item> <a href=#via-the-host-network class=md-nav__link> Via the host network </a> </li> <li class=md-nav__item> <a href=#using-a-self-provisioned-edge class=md-nav__link> Using a self-provisioned edge </a> </li> <li class=md-nav__item> <a href=#external-ips class=md-nav__link> External IPs </a> </li> </ul> </nav> </div> </div> </div> <div class=md-content> <article class="md-content__inner md-typeset"> <a href=https://github.com/kubernetes/ingress-nginx/edit/master/docs/deploy/baremetal.md title="Edit this page" class="md-content__button md-icon"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z"/></svg> </a> <h1 id=bare-metal-considerations>Bare-metal considerations<a class=headerlink href=#bare-metal-considerations title="Permanent link"></a></h1> <p>In traditional <em>cloud</em> environments, where network load balancers are available on-demand, a single Kubernetes manifest suffices to provide a single point of contact to the NGINX Ingress controller to external clients and, indirectly, to any application running inside the cluster. <em>Bare-metal</em> environments lack this commodity, requiring a slightly different setup to offer the same kind of access to external consumers.</p> <p><img alt="Cloud environment" src=../../images/baremetal/cloud_overview.jpg> <img alt="Bare-metal environment" src=../../images/baremetal/baremetal_overview.jpg></p> <p>The rest of this document describes a few recommended approaches to deploying the NGINX Ingress controller inside a Kubernetes cluster running on bare-metal.</p> <h2 id=a-pure-software-solution-metallb>A pure software solution: MetalLB<a class=headerlink href=#a-pure-software-solution-metallb title="Permanent link"></a></h2> <p><a href=https://metallb.universe.tf/ >MetalLB</a> provides a network load-balancer implementation for Kubernetes clusters that do not run on a supported cloud provider, effectively allowing the usage of LoadBalancer Services within any cluster.</p> <p>This section demonstrates how to use the <a href=https://metallb.universe.tf/concepts/layer2/ >Layer 2 configuration mode</a> of MetalLB together with the NGINX Ingress controller in a Kubernetes cluster that has <strong>publicly accessible nodes</strong>. In this mode, one node attracts all the traffic for the <code>ingress-nginx</code> Service IP. See <a href=https://metallb.universe.tf/usage/#traffic-policies>Traffic policies</a> for more details.</p> <p><img alt="MetalLB in L2 mode" src=../../images/baremetal/metallb.jpg></p> <div class="admonition note"> <p class=admonition-title>Note</p> <p>The description of other supported configuration modes is off-scope for this document.</p> </div> <div class="admonition warning"> <p class=admonition-title>Warning</p> <p>MetalLB is currently in <em>beta</em>. Read about the <a href=https://metallb.universe.tf/concepts/maturity/ >Project maturity</a> and make sure you inform yourself by reading the official documentation thoroughly.</p> </div> <p>MetalLB can be deployed either with a simple Kubernetes manifest or with Helm. The rest of this example assumes MetalLB was deployed following the <a href=https://metallb.universe.tf/installation/ >Installation</a> instructions.</p> <p>MetalLB requires a pool of IP addresses in order to be able to take ownership of the <code>ingress-nginx</code> Service. This pool can be defined in a ConfigMap named <code>config</code> located in the same namespace as the MetalLB controller. This pool of IPs <strong>must</strong> be dedicated to MetalLB's use, you can't reuse the Kubernetes node IPs or IPs handed out by a DHCP server.</p> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Given the following 3-node Kubernetes cluster (the external IP is added as an example, in most bare-metal environments this value is &lt;None>)</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get node
<span class=go>NAME STATUS ROLES EXTERNAL-IP</span>
<span class=go>host-1 Ready master 203.0.113.1</span>
<span class=go>host-2 Ready node 203.0.113.2</span>
<span class=go>host-3 Ready node 203.0.113.3</span>
</code></pre></div> <p>After creating the following ConfigMap, MetalLB takes ownership of one of the IP addresses in the pool and updates the <em>loadBalancer</em> IP field of the <code>ingress-nginx</code> Service accordingly.</p> <div class=highlight><pre><span></span><code><span class=nt>apiVersion</span><span class=p>:</span> <span class="l l-Scalar l-Scalar-Plain">v1</span>
<span class=nt>kind</span><span class=p>:</span> <span class="l l-Scalar l-Scalar-Plain">ConfigMap</span>
<span class=nt>metadata</span><span class=p>:</span>
<span class=nt>namespace</span><span class=p>:</span> <span class="l l-Scalar l-Scalar-Plain">metallb-system</span>
<span class=nt>name</span><span class=p>:</span> <span class="l l-Scalar l-Scalar-Plain">config</span>
<span class=nt>data</span><span class=p>:</span>
<span class=nt>config</span><span class=p>:</span> <span class="p p-Indicator">|</span>
<span class=no>address-pools:</span>
<span class=no>- name: default</span>
<span class=no>protocol: layer2</span>
<span class=no>addresses:</span>
<span class=no>- 203.0.113.10-203.0.113.15</span>
</code></pre></div> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx get svc
<span class=go>NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)</span>
<span class=go>default-http-backend ClusterIP 10.0.64.249 &lt;none&gt; 80/TCP</span>
<span class=go>ingress-nginx LoadBalancer 10.0.220.217 203.0.113.10 80:30100/TCP,443:30101/TCP</span>
</code></pre></div> </div> <p>As soon as MetalLB sets the external IP address of the <code>ingress-nginx</code> LoadBalancer Service, the corresponding entries are created in the iptables NAT table and the node with the selected IP address starts responding to HTTP requests on the ports configured in the LoadBalancer Service:</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> curl -D- http://203.0.113.3 -H <span class=s1>&#39;Host: myapp.example.com&#39;</span>
<span class=go>HTTP/1.1 200 OK</span>
<span class=go>Server: nginx/1.15.2</span>
</code></pre></div> <div class="admonition tip"> <p class=admonition-title>Tip</p> <p>In order to preserve the source IP address in HTTP requests sent to NGINX, it is necessary to use the <code>Local</code> traffic policy. Traffic policies are described in more details in <a href=https://metallb.universe.tf/usage/#traffic-policies>Traffic policies</a> as well as in the next section.</p> </div> <h2 id=over-a-nodeport-service>Over a NodePort Service<a class=headerlink href=#over-a-nodeport-service title="Permanent link"></a></h2> <p>Due to its simplicity, this is the setup a user will deploy by default when following the steps described in the <a href=../#bare-metal>installation guide</a>.</p> <div class="admonition info"> <p class=admonition-title>Info</p> <p>A Service of type <code>NodePort</code> exposes, via the <code>kube-proxy</code> component, the <strong>same unprivileged</strong> port (default: 30000-32767) on every Kubernetes node, masters included. For more information, see <a href=https://kubernetes.io/docs/concepts/services-networking/service/#nodeport>Services</a>.</p> </div> <p>In this configuration, the NGINX container remains isolated from the host network. As a result, it can safely bind to any port, including the standard HTTP ports 80 and 443. However, due to the container namespace isolation, a client located outside the cluster network (e.g. on the public internet) is not able to access Ingress hosts directly on ports 80 and 443. Instead, the external client must append the NodePort allocated to the <code>ingress-nginx</code> Service to HTTP requests.</p> <p><img alt="NodePort request flow" src=../../images/baremetal/nodeport.jpg></p> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Given the NodePort <code>30100</code> allocated to the <code>ingress-nginx</code> Service</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx get svc
<span class=go>NAME TYPE CLUSTER-IP PORT(S)</span>
<span class=go>default-http-backend ClusterIP 10.0.64.249 80/TCP</span>
<span class=go>ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP</span>
</code></pre></div> <p>and a Kubernetes node with the public IP address <code>203.0.113.2</code> (the external IP is added as an example, in most bare-metal environments this value is &lt;None>)</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get node
<span class=go>NAME STATUS ROLES EXTERNAL-IP</span>
<span class=go>host-1 Ready master 203.0.113.1</span>
<span class=go>host-2 Ready node 203.0.113.2</span>
<span class=go>host-3 Ready node 203.0.113.3</span>
</code></pre></div> <p>a client would reach an Ingress with <code>host: myapp.example.com</code> at <code>http://myapp.example.com:30100</code>, where the myapp.example.com subdomain resolves to the 203.0.113.2 IP address.</p> </div> <div class="admonition danger"> <p class=admonition-title>Impact on the host system</p> <p>While it may sound tempting to reconfigure the NodePort range using the <code>--service-node-port-range</code> API server flag to include unprivileged ports and be able to expose ports 80 and 443, doing so may result in unexpected issues including (but not limited to) the use of ports otherwise reserved to system daemons and the necessity to grant <code>kube-proxy</code> privileges it may otherwise not require.</p> <p>This practice is therefore <strong>discouraged</strong>. See the other approaches proposed in this page for alternatives.</p> </div> <p>This approach has a few other limitations one ought to be aware of:</p> <ul> <li><strong>Source IP address</strong></li> </ul> <p>Services of type NodePort perform <a href=https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-type-nodeport>source address translation</a> by default. This means the source IP of a HTTP request is always <strong>the IP address of the Kubernetes node that received the request</strong> from the perspective of NGINX.</p> <p>The recommended way to preserve the source IP in a NodePort setup is to set the value of the <code>externalTrafficPolicy</code> field of the <code>ingress-nginx</code> Service spec to <code>Local</code> (<a href=https://github.com/kubernetes/ingress-nginx/blob/nginx-0.19.0/deploy/provider/aws/service-nlb.yaml#L12-L14>example</a>).</p> <div class="admonition warning"> <p class=admonition-title>Warning</p> <p>This setting effectively <strong>drops packets</strong> sent to Kubernetes nodes which are not running any instance of the NGINX Ingress controller. Consider <a href=https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ >assigning NGINX Pods to specific nodes</a> in order to control on what nodes the NGINX Ingress controller should be scheduled or not scheduled.</p> </div> <div class="admonition example"> <p class=admonition-title>Example</p> <p>In a Kubernetes cluster composed of 3 nodes (the external IP is added as an example, in most bare-metal environments this value is &lt;None>)</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get node
<span class=go>NAME STATUS ROLES EXTERNAL-IP</span>
<span class=go>host-1 Ready master 203.0.113.1</span>
<span class=go>host-2 Ready node 203.0.113.2</span>
<span class=go>host-3 Ready node 203.0.113.3</span>
</code></pre></div> <p>with a <code>ingress-nginx-controller</code> Deployment composed of 2 replicas</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx get pod -o wide
<span class=go>NAME READY STATUS IP NODE</span>
<span class=go>default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2</span>
<span class=go>ingress-nginx-controller-cf9ff8c96-8vvf8 1/1 Running 172.17.0.3 host-3</span>
<span class=go>ingress-nginx-controller-cf9ff8c96-pxsds 1/1 Running 172.17.1.4 host-2</span>
</code></pre></div> <p>Requests sent to <code>host-2</code> and <code>host-3</code> would be forwarded to NGINX and original client's IP would be preserved, while requests to <code>host-1</code> would get dropped because there is no NGINX replica running on that node.</p> </div> <ul> <li><strong>Ingress status</strong></li> </ul> <p>Because NodePort Services do not get a LoadBalancerIP assigned by definition, the NGINX Ingress controller <strong>does not update the status of Ingress objects it manages</strong>.</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get ingress
<span class=go>NAME HOSTS ADDRESS PORTS</span>
<span class=go>test-ingress myapp.example.com 80</span>
</code></pre></div> <p>Despite the fact there is no load balancer providing a public IP address to the NGINX Ingress controller, it is possible to force the status update of all managed Ingress objects by setting the <code>externalIPs</code> field of the <code>ingress-nginx</code> Service.</p> <div class="admonition warning"> <p class=admonition-title>Warning</p> <p>There is more to setting <code>externalIPs</code> than just enabling the NGINX Ingress controller to update the status of Ingress objects. Please read about this option in the <a href=https://kubernetes.io/docs/concepts/services-networking/service/#external-ips>Services</a> page of official Kubernetes documentation as well as the section about <a href=#external-ips>External IPs</a> in this document for more information.</p> </div> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Given the following 3-node Kubernetes cluster (the external IP is added as an example, in most bare-metal environments this value is &lt;None>)</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get node
<span class=go>NAME STATUS ROLES EXTERNAL-IP</span>
<span class=go>host-1 Ready master 203.0.113.1</span>
<span class=go>host-2 Ready node 203.0.113.2</span>
<span class=go>host-3 Ready node 203.0.113.3</span>
</code></pre></div> <p>one could edit the <code>ingress-nginx</code> Service and add the following field to the object spec</p> <div class=highlight><pre><span></span><code><span class=nt>spec</span><span class=p>:</span>
<span class=nt>externalIPs</span><span class=p>:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">203.0.113.1</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">203.0.113.2</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">203.0.113.3</span>
</code></pre></div> <p>which would in turn be reflected on Ingress objects as follows:</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get ingress -o wide
<span class=go>NAME HOSTS ADDRESS PORTS</span>
<span class=go>test-ingress myapp.example.com 203.0.113.1,203.0.113.2,203.0.113.3 80</span>
</code></pre></div> </div> <ul> <li><strong>Redirects</strong></li> </ul> <p>As NGINX is <strong>not aware of the port translation operated by the NodePort Service</strong>, backend applications are responsible for generating redirect URLs that take into account the URL used by external clients, including the NodePort.</p> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Redirects generated by NGINX, for instance HTTP to HTTPS or <code>domain</code> to <code>www.domain</code>, are generated without NodePort:</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> curl -D- http://myapp.example.com:30100<span class=sb>`</span>
<span class=go>HTTP/1.1 308 Permanent Redirect</span>
<span class=go>Server: nginx/1.15.2</span>
<span class=go>Location: https://myapp.example.com/ #-&gt; missing NodePort in HTTPS redirect</span>
</code></pre></div> </div> <h2 id=via-the-host-network>Via the host network<a class=headerlink href=#via-the-host-network title="Permanent link"></a></h2> <p>In a setup where there is no external load balancer available but using NodePorts is not an option, one can configure <code>ingress-nginx</code> Pods to use the network of the host they run on instead of a dedicated network namespace. The benefit of this approach is that the NGINX Ingress controller can bind ports 80 and 443 directly to Kubernetes nodes' network interfaces, without the extra network translation imposed by NodePort Services.</p> <div class="admonition note"> <p class=admonition-title>Note</p> <p>This approach does not leverage any Service object to expose the NGINX Ingress controller. If the <code>ingress-nginx</code> Service exists in the target cluster, it is <strong>recommended to delete it</strong>.</p> </div> <p>This can be achieved by enabling the <code>hostNetwork</code> option in the Pods' spec.</p> <div class=highlight><pre><span></span><code><span class=nt>template</span><span class=p>:</span>
<span class=nt>spec</span><span class=p>:</span>
<span class=nt>hostNetwork</span><span class=p>:</span> <span class="l l-Scalar l-Scalar-Plain">true</span>
</code></pre></div> <div class="admonition danger"> <p class=admonition-title>Security considerations</p> <p>Enabling this option <strong>exposes every system daemon to the NGINX Ingress controller</strong> on any network interface, including the host's loopback. Please evaluate the impact this may have on the security of your system carefully.</p> </div> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Consider this <code>ingress-nginx-controller</code> Deployment composed of 2 replicas, NGINX Pods inherit from the IP address of their host instead of an internal Pod IP.</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx get pod -o wide
<span class=go>NAME READY STATUS IP NODE</span>
<span class=go>default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2</span>
<span class=go>ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3</span>
<span class=go>ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2</span>
</code></pre></div> </div> <p>One major limitation of this deployment approach is that only <strong>a single NGINX Ingress controller Pod</strong> may be scheduled on each cluster node, because binding the same port multiple times on the same network interface is technically impossible. Pods that are unschedulable due to such situation fail with the following event:</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx describe pod &lt;unschedulable-ingress-nginx-controller-pod&gt;
<span class=go>...</span>
<span class=go>Events:</span>
<span class=go> Type Reason From Message</span>
<span class=go> ---- ------ ---- -------</span>
<span class=go> Warning FailedScheduling default-scheduler 0/3 nodes are available: 3 node(s) didn&#39;t have free ports for the requested pod ports.</span>
</code></pre></div> <p>One way to ensure only schedulable Pods are created is to deploy the NGINX Ingress controller as a <em>DaemonSet</em> instead of a traditional Deployment.</p> <div class="admonition info"> <p class=admonition-title>Info</p> <p>A DaemonSet schedules exactly one type of Pod per cluster node, masters included, unless a node is configured to <a href=https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ >repel those Pods</a>. For more information, see <a href=https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ >DaemonSet</a>.</p> </div> <p>Because most properties of DaemonSet objects are identical to Deployment objects, this documentation page leaves the configuration of the corresponding manifest at the user's discretion.</p> <p><img alt="DaemonSet with hostNetwork flow" src=../../images/baremetal/hostnetwork.jpg></p> <p>Like with NodePorts, this approach has a few quirks it is important to be aware of.</p> <ul> <li><strong>DNS resolution</strong></li> </ul> <p>Pods configured with <code>hostNetwork: true</code> do not use the internal DNS resolver (i.e. <em>kube-dns</em> or <em>CoreDNS</em>), unless their <code>dnsPolicy</code> spec field is set to <a href=https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy><code>ClusterFirstWithHostNet</code></a>. Consider using this setting if NGINX is expected to resolve internal names for any reason.</p> <ul> <li><strong>Ingress status</strong></li> </ul> <p>Because there is no Service exposing the NGINX Ingress controller in a configuration using the host network, the default <code>--publish-service</code> flag used in standard cloud setups <strong>does not apply</strong> and the status of all Ingress objects remains blank.</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get ingress
<span class=go>NAME HOSTS ADDRESS PORTS</span>
<span class=go>test-ingress myapp.example.com 80</span>
</code></pre></div> <p>Instead, and because bare-metal nodes usually don't have an ExternalIP, one has to enable the <a href=../../user-guide/cli-arguments/ ><code>--report-node-internal-ip-address</code></a> flag, which sets the status of all Ingress objects to the internal IP address of all nodes running the NGINX Ingress controller.</p> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Given a <code>ingress-nginx-controller</code> DaemonSet composed of 2 replicas</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx get pod -o wide
<span class=go>NAME READY STATUS IP NODE</span>
<span class=go>default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2</span>
<span class=go>ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3</span>
<span class=go>ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2</span>
</code></pre></div> <p>the controller sets the status of all Ingress objects it manages to the following value:</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get ingress -o wide
<span class=go>NAME HOSTS ADDRESS PORTS</span>
<span class=go>test-ingress myapp.example.com 203.0.113.2,203.0.113.3 80</span>
</code></pre></div> </div> <div class="admonition note"> <p class=admonition-title>Note</p> <p>Alternatively, it is possible to override the address written to Ingress objects using the <code>--publish-status-address</code> flag. See <a href=../../user-guide/cli-arguments/ >Command line arguments</a>.</p> </div> <h2 id=using-a-self-provisioned-edge>Using a self-provisioned edge<a class=headerlink href=#using-a-self-provisioned-edge title="Permanent link"></a></h2> <p>Similarly to cloud environments, this deployment approach requires an edge network component providing a public entrypoint to the Kubernetes cluster. This edge component can be either hardware (e.g. vendor appliance) or software (e.g. <em>HAproxy</em>) and is usually managed outside of the Kubernetes landscape by operations teams.</p> <p>Such deployment builds upon the NodePort Service described above in <a href=#over-a-nodeport-service>Over a NodePort Service</a>, with one significant difference: external clients do not access cluster nodes directly, only the edge component does. This is particularly suitable for private Kubernetes clusters where none of the nodes has a public IP address.</p> <p>On the edge side, the only prerequisite is to dedicate a public IP address that forwards all HTTP traffic to Kubernetes nodes and/or masters. Incoming traffic on TCP ports 80 and 443 is forwarded to the corresponding HTTP and HTTPS NodePort on the target nodes as shown in the diagram below:</p> <p><img alt="User edge" src=../../images/baremetal/user_edge.jpg></p> <h2 id=external-ips>External IPs<a class=headerlink href=#external-ips title="Permanent link"></a></h2> <div class="admonition danger"> <p class=admonition-title>Source IP address</p> <p>This method does not allow preserving the source IP of HTTP requests in any manner, it is therefore <strong>not recommended</strong> to use it despite its apparent simplicity.</p> </div> <p>The <code>externalIPs</code> Service option was previously mentioned in the <a href=#over-a-nodeport-service>NodePort</a> section.</p> <p>As per the <a href=https://kubernetes.io/docs/concepts/services-networking/service/#external-ips>Services</a> page of the official Kubernetes documentation, the <code>externalIPs</code> option causes <code>kube-proxy</code> to route traffic sent to arbitrary IP addresses <strong>and on the Service ports</strong> to the endpoints of that Service. These IP addresses <strong>must belong to the target node</strong>.</p> <div class="admonition example"> <p class=admonition-title>Example</p> <p>Given the following 3-node Kubernetes cluster (the external IP is added as an example, in most bare-metal environments this value is &lt;None>)</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl get node
<span class=go>NAME STATUS ROLES EXTERNAL-IP</span>
<span class=go>host-1 Ready master 203.0.113.1</span>
<span class=go>host-2 Ready node 203.0.113.2</span>
<span class=go>host-3 Ready node 203.0.113.3</span>
</code></pre></div> <p>and the following <code>ingress-nginx</code> NodePort Service</p> <div class=highlight><pre><span></span><code><span class=gp>$</span> kubectl -n ingress-nginx get svc
<span class=go>NAME TYPE CLUSTER-IP PORT(S)</span>
<span class=go>ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP</span>
</code></pre></div> <p>One could set the following external IPs in the Service spec, and NGINX would become available on both the NodePort and the Service port:</p> <div class=highlight><pre><span></span><code><span class=nt>spec</span><span class=p>:</span>
<span class=nt>externalIPs</span><span class=p>:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">203.0.113.2</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">203.0.113.3</span>
</code></pre></div> <div class=highlight><pre><span></span><code><span class=gp>$</span> curl -D- http://myapp.example.com:30100
<span class=go>HTTP/1.1 200 OK</span>
<span class=go>Server: nginx/1.15.2</span>
<span class=gp>$</span> curl -D- http://myapp.example.com
<span class=go>HTTP/1.1 200 OK</span>
<span class=go>Server: nginx/1.15.2</span>
</code></pre></div> <p>We assume the myapp.example.com subdomain above resolves to both 203.0.113.2 and 203.0.113.3 IP addresses.</p> </div> </article> </div> </div> </main> <footer class=md-footer> <div class=md-footer-nav> <nav class="md-footer-nav__inner md-grid" aria-label=Footer> <a href=../ class="md-footer-nav__link md-footer-nav__link--prev" rel=prev> <div class="md-footer-nav__button md-icon"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12z"/></svg> </div> <div class=md-footer-nav__title> <div class=md-ellipsis> <span class=md-footer-nav__direction> Previous </span> Installation Guide </div> </div> </a> <a href=../rbac/ class="md-footer-nav__link md-footer-nav__link--next" rel=next> <div class=md-footer-nav__title> <div class=md-ellipsis> <span class=md-footer-nav__direction> Next </span> Role Based Access Control (RBAC) </div> </div> <div class="md-footer-nav__button md-icon"> <svg xmlns=http://www.w3.org/2000/svg viewbox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11H4z"/></svg> </div> </a> </nav> </div> <div class="md-footer-meta md-typeset"> <div class="md-footer-meta__inner md-grid"> <div class=md-footer-copyright> Made with <a href=https://squidfunk.github.io/mkdocs-material/ target=_blank rel=noopener> Material for MkDocs </a> </div> </div> </div> </footer> </div> <script src=../../assets/javascripts/vendor.93c04032.min.js></script> <script src=../../assets/javascripts/bundle.83e5331e.min.js></script><script id=__lang type=application/json>{"clipboard.copy": "Copy to clipboard", "clipboard.copied": "Copied to clipboard", "search.config.lang": "en", "search.config.pipeline": "trimmer, stopWordFilter", "search.config.separator": "[\\s\\-]+", "search.placeholder": "Search", "search.result.placeholder": "Type to start searching", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.term.missing": "Missing"}</script> <script>
app = initialize({
base: "../..",
features: ['navigation.tabs', 'navigation.tabs.sticky', 'navigation.instant', 'navigation.sections'],
search: Object.assign({
worker: "../../assets/javascripts/worker/search.8c7e0a7e.min.js"
}, typeof search !== "undefined" && search)
})
</script> </body> </html>