<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>Enrico Biella</title><description>Network &amp; Cloud Engineer · SRE · Tech Blog</description><link>https://private-site-585329.gitlab.io</link><item><title>Deploying OpenStack with Helm and FluxCD on Kubernetes</title><link>https://private-site-585329.gitlab.io/blog/openstack-helm-k8s</link><guid isPermaLink="true">https://private-site-585329.gitlab.io/blog/openstack-helm-k8s</guid><description>A comprehensive guide to deploying a production-grade OpenStack on Kubernetes using OpenStack-Helm and a GitOps workflow with FluxCD.</description><pubDate>Mon, 20 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Following up on our &lt;a href=&quot;/blog/kubernetes-cluster&quot;&gt;previous post where we automated a production-ready Kubernetes cluster&lt;/a&gt;, this guide details how to deploy a complete OpenStack environment on top of it. We will use the &lt;a href=&quot;https://github.com/openstack/openstack-helm/tree/2026.1.0&quot;&gt;OpenStack-Helm 2026.1.0&lt;/a&gt; charts and manage the entire lifecycle through a GitOps workflow powered by &lt;a href=&quot;https://fluxcd.io/&quot;&gt;FluxCD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This approach treats your OpenStack deployment as code, ensuring reproducibility, auditability, and simplified management.&lt;/p&gt;
&lt;h2&gt;Architecture Overview&lt;/h2&gt;
&lt;p&gt;The deployment is managed from a central Git repository. FluxCD continuously reconciles the state of the Kubernetes cluster with the configuration defined in Git. The OpenStack-Helm charts themselves are treated as read-only upstream sources, with all customizations applied via Helm value overrides.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./kubernetes-architecture-openstack.svg&quot; alt=&quot;Kubernetes on OpenStack Architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s a breakdown of the layers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;┌──────────────────────────────────────────────────────────────────┐
│  Git Repository (this repo)                                      │
│  FluxCD config + Helm value overrides only                       │
│  charts/ = READ-ONLY upstream 2026.1.0 (never modified)          │
└───────────────────────┬──────────────────────────────────────────┘
                        │ FluxCD reconciles
                        ▼
┌──────────────────────────────────────────────────────────────────┐
│  Kubernetes Cluster (prod / region1)                             │
│                                                                  │
│  Infrastructure layer (Ansible-managed, pre-deployed)            │
│  ├── MetalLB L2           gateway VIP: 172.24.61.20              │
│  ├── Envoy Gateway        gateway-default / envoy-gateway-system │
│  ├── cert-manager         TLS automation                         │
│  ├── Rook-Ceph            RBD block storage                      │
│  └── FluxCD               GitOps controllers                     │
│                                                                  │
│  openstack namespace (FluxCD-managed)                            │
│  ├── PKI: selfsigned-bootstrap → openstack-ca (internal TLS)    │
│  ├── Infrastructure: rabbitmq, mariadb, memcached, redis, etcd   │
│  ├── Storage: ceph-adapter-rook                                  │
│  ├── Networking: openvswitch, ovn, libvirt                       │
│  ├── Core: keystone, glance, cinder, placement, neutron, nova    │
│  ├── Extended: barbican, heat, magnum, octavia, designate        │
│  ├── Dashboard: skyline                                          │
│  └── Monitoring: prometheus, mysql-exporter, os-exporter         │
└──────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All public-facing services are exposed via a single external IP (&lt;code&gt;172.24.61.20&lt;/code&gt;) and are accessible via &lt;code&gt;https://&amp;#x3C;service&gt;.172.24.61.20.nip.io&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Key Design Decisions&lt;/h2&gt;
&lt;p&gt;This setup incorporates several important design choices to ensure a robust and maintainable production environment.&lt;/p&gt;
&lt;h3&gt;Helm Hook Removal in 2026.1.0&lt;/h3&gt;
&lt;p&gt;OpenStack-Helm 2026.1.0 has removed support for Helm 2, and with it, the &lt;code&gt;helm3_hook&lt;/code&gt; value. All jobs (like database initialization, user creation, etc.) are now standard Kubernetes &lt;code&gt;Job&lt;/code&gt; resources.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Job Ordering:&lt;/strong&gt; Instead of Helm hooks, job execution order is managed by &lt;code&gt;kubernetes-entrypoint&lt;/code&gt; init containers within each service pod. These containers check a list of static dependencies (&lt;code&gt;dependencies.static.&amp;#x3C;component&gt;.jobs&lt;/code&gt;) and wait for required jobs to reach the &lt;code&gt;Completed&lt;/code&gt; state before allowing the main service container to start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Job Protection:&lt;/strong&gt; To prevent FluxCD or Helm from deleting completed jobs during reconciliation, we apply a global patch to all &lt;code&gt;Job&lt;/code&gt; resources, setting &lt;code&gt;helm.sh/resource-policy: keep&lt;/code&gt;. This is critical for stability. To re-run a job (e.g., after an upgrade), you must manually delete it first.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Cluster-Managed Gateway&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Gateway&lt;/code&gt; resource, which defines how traffic enters the cluster, is managed by our Ansible playbooks from the previous guide, not by FluxCD. This separates the core ingress infrastructure from the application layer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gateway Name:&lt;/strong&gt; &lt;code&gt;gateway-default&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gateway Namespace:&lt;/strong&gt; &lt;code&gt;envoy-gateway-system&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS:&lt;/strong&gt; A single wildcard certificate (&lt;code&gt;gateway-wildcard-tls&lt;/code&gt;) covers all &lt;code&gt;*.{ip}.nip.io&lt;/code&gt; subdomains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-Namespace Routing:&lt;/strong&gt; The gateway is configured to allow &lt;code&gt;HTTPRoute&lt;/code&gt; resources from any namespace to attach to it, which is how our &lt;code&gt;openstack&lt;/code&gt; namespace exposes its services.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only the &lt;code&gt;HTTPRoute&lt;/code&gt; resources, which map paths to OpenStack services, are managed via GitOps.&lt;/p&gt;
&lt;h3&gt;HTTPS-Only Public Endpoints&lt;/h3&gt;
&lt;p&gt;Security is paramount. All public-facing OpenStack endpoints are configured for HTTPS only (&lt;code&gt;scheme.public: https&lt;/code&gt; on port 443). A global &lt;code&gt;HTTPRoute&lt;/code&gt; is set up to automatically redirect any incoming HTTP (port 80) traffic to its HTTPS equivalent. Internal cluster communication between OpenStack services remains plain HTTP for performance.&lt;/p&gt;
&lt;h3&gt;Internal Public Key Infrastructure (PKI)&lt;/h3&gt;
&lt;p&gt;The deployment establishes its own internal PKI using &lt;code&gt;cert-manager&lt;/code&gt;, completely separate from the gateway&apos;s TLS certificate. This is used for securing internal components like MariaDB and RabbitMQ with mTLS.&lt;/p&gt;
&lt;p&gt;The chain of trust is:
&lt;code&gt;selfsigned-bootstrap (ClusterIssuer) -&gt; openstack-ca (Certificate) -&gt; openstack-ca (ClusterIssuer) -&gt; per-service certificates&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Repository Structure&lt;/h2&gt;
&lt;p&gt;A well-organized GitOps repository is key. Here is the layout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;charts/                              # READ-ONLY upstream 2026.1.0 charts
clusters/
└── prod/
    ├── flux-system/                 # Flux GitSource + Kustomization entries
    └── region1/
        └── openstack/
            ├── kustomization.yaml
            ├── patch-helmrelease-openstack-gitops.yaml   # Global patch for Jobs
            ├── patch-helmrelease-timeouts.yaml
            ├── pki/                 # Internal PKI bootstrap manifests
            ├── releases/            # HelmRelease manifests (one per chart)
            ├── values/              # Helm value overrides as ConfigMaps
            │   ├── credentials.yaml (SOPS-encrypted)
            │   └── ...
            └── gateway/
                └── httproutes.yaml  # HTTPRoutes for OpenStack services
.sops.yaml                           # SOPS age key configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;h3&gt;Cluster Infrastructure&lt;/h3&gt;
&lt;p&gt;This guide assumes you have a running Kubernetes cluster with the following components, as configured in our previous article:&lt;/p&gt;
&lt;p&gt;| Component     | Purpose                                            |
| ------------- | -------------------------------------------------- |
| MetalLB       | Provides the VIP &lt;code&gt;172.24.61.20&lt;/code&gt; for the gateway.   |
| Envoy Gateway | Manages ingress traffic via the &lt;code&gt;gateway-default&lt;/code&gt;. |
| cert-manager  | Automates TLS certificate management.              |
| Rook-Ceph     | Provides RBD block storage for Cinder and Glance.  |
| FluxCD        | Powers the GitOps reconciliation loop.             |&lt;/p&gt;
&lt;h3&gt;Admin Workstation Setup&lt;/h3&gt;
&lt;p&gt;You&apos;ll need the following tools on your local machine to manage the deployment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install SOPS and age for credential encryption
sudo apt update &amp;#x26;&amp;#x26; sudo apt install -y age
curl -LO curl -LO https://github.com/getsops/sops/releases/download/v3.13.2/sops-v3.13.2.linux.amd64
sudo install sops-v3.13.2.linux.amd64 /usr/local/bin/sops
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deployment Walkthrough&lt;/h2&gt;
&lt;h3&gt;Step 1: Node Preparation&lt;/h3&gt;
&lt;p&gt;First, prepare your Kubernetes worker nodes. OpenStack components require higher &lt;code&gt;inotify&lt;/code&gt; limits for monitoring file changes. We also need to label the nodes so that the OpenStack-Helm charts can correctly schedule pods.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Increase inotify limits on all worker nodes
for NODE in k8s-worker1 k8s-worker2 k8s-worker3; do
  ssh $NODE &quot;sudo sysctl -w fs.inotify.max_user_watches=524288 &amp;#x26;&amp;#x26; \
             sudo sysctl -w fs.inotify.max_user_instances=1024&quot;
done

# Label nodes for OpenStack roles
for NODE in k8s-worker1 k8s-worker2 k8s-worker3; do
  kubectl label --overwrite nodes $NODE \
    openstack-control-plane=enabled \
    openstack-compute-node=enabled \
    openvswitch=enabled \
    l3-agent=enabled \
    openstack-network-node=enabled
done

# Create the target namespace
kubectl create namespace openstack
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2: Credential Encryption with SOPS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Never commit plain-text secrets to Git.&lt;/strong&gt; We use SOPS with age to encrypt our &lt;code&gt;credentials.yaml&lt;/code&gt; file.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Generate an age keypair:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;age-keygen -o age.agekey
# This will output a public key, e.g., age1ql3z...
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Store the private key in the cluster:&lt;/strong&gt; FluxCD needs the private key to decrypt the credentials.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl create secret generic sops-age \
  --namespace=flux-system \
  --from-file=age.agekey=./age.agekey
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure and encrypt:&lt;/strong&gt; Add the public key to your &lt;code&gt;.sops.yaml&lt;/code&gt; file. Then, populate &lt;code&gt;clusters/prod/region1/openstack/values/credentials.yaml&lt;/code&gt; with your desired passwords and encrypt it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sops --encrypt --in-place clusters/prod/region1/openstack/values/credentials.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now safely commit the encrypted file. To edit it later, simply run &lt;code&gt;sops clusters/prod/region1/openstack/values/credentials.yaml&lt;/code&gt;, which will open it in your default editor and re-encrypt on save.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Example &lt;code&gt;credentials.yaml&lt;/code&gt; file&lt;/strong&gt; Use this file as example for you repo&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# ============================================================
# SOPS ENCRYPTION REQUIRED BEFORE COMMITTING TO GIT
#
# Steps:
#   1. Fill in all Password1
#   2. Run: sops --encrypt --in-place clusters/prod/openstack/values/credentials.yaml
#   3. Only then: git add clusters/prod/openstack/values/credentials.yaml &amp;#x26;&amp;#x26; git commit
#
# To edit after encryption:
#   sops clusters/prod/openstack/values/credentials.yaml
# ============================================================
apiVersion: v1
kind: Secret
metadata:
  name: openstack-credentials
  namespace: openstack
stringData:
  values.yaml: |-
    endpoints:
      identity:
        auth:
          admin:
            password: Password1
          cinder:
            password: Password1
          glance:
            password: Password1
          nova:
            password: Password1
          swift:
            password: Password1
          service:
            password: Password1
          test:
            password: Password1
          keystone:
            password: Password1
          neutron:
            password: Password1
          barbican:
            password: Password1
          placement:
            password: Password1
          heat:
            password: Password1
          heat_trustee:
            password: Password1
          heat_stack_user:
            password: Password1
          magnum:
            password: Password1
          magnum_stack_user:
            password: Password1
          mariadb-server:
            password: Password1
          octavia:
            password: Password1
          designate:
            password: Password1
          ironic:
            password: Password1
          skyline:
            password: Password1
          user:
            password: Password1
      oslo_db:
        auth:
          admin:
            username: root
            password: Password1
          keystone:
            username: keystone
            password: Password1
          nova:
            username: nova
            password: Password1
          nova_api:
            username: nova_api
            password: Password1
          nova_cell0:
            username: nova_cell0
            password: Password1
          neutron:
            username: neutron
            password: Password1
          cinder:
            username: cinder
            password: Password1
          glance:
            username: glance
            password: Password1
          placement:
            username: placement
            password: Password1
          heat:
            username: heat
            password: Password1
          barbican:
            username: barbican
            password: Password1
          magnum:
            username: magnum
            password: Password1
          octavia:
            username: octavia
            password: Password1
          designate:
            username: designate
            password: Password1
          powerdns:
            username: powerdns
            password: Password1
          sst:
            username: sst
            password: Password1
          audit:
            username: audit
            password: Password1
          exporter:
            username: exporter
            password: Password1
          skyline:
            username: skyline
            password: Password1
      oslo_db_persistence:
        auth:
          admin:
            username: root
            password: Password1
          octavia:
            username: octavia
            password: Password1
      oslo_db_api:
        auth:
          admin:
            username: root
            password: Password1
          nova:
            username: nova
            password: Password1
      oslo_db_cell0:
        auth:
          admin:
            username: root
            password: Password1
          nova:
            username: nova
            password: Password1
      ceph_object_store:
        auth:
          glance:
            username: glance
            password: Password1
            tempurlkey: supersecret
      oslo_messaging:
        auth:
          admin:
            username: rabbitmq
            password: Password1
          user:
            username: rabbitmq
            password: Password1
          guest:
            password: Password1
          keystone:
            username: keystone
            password: Password1
          nova:
            username: nova
            password: Password1
          neutron:
            username: neutron
            password: Password1
          cinder:
            username: cinder
            password: Password1
          glance:
            username: glance
            password: Password1
          heat:
            username: heat
            password: Password1
          barbican:
            password: Password1
          magnum:
            username: magnum
            password: Password1
          octavia:
            username: octavia
            password: Password1
          designate:
            username: designate
            password: Password1
      powerdns:
        auth:
          admin:
            password: Password1
      oci_image_registry:
        auth:
          barbican:
            username: barbican
            password: Password1
          cinder:
            username: cinder
            password: Password1
          designate:
            username: designate
            password: Password1
          etcd:
            username: etcd
            password: Password1
          glance:
            username: glance
            password: Password1
          heat:
            username: heat
            password: Password1
          keystone:
            username: keystone
            password: Password1
          libvirt:
            username: libvirt
            password: Password1
          magnum:
            username: magnum
            password: Password1
          mariadb:
            username: mariadb
            password: Password1
          memcached:
            username: memcached
            password: Password1
          neutron:
            username: neutron
            password: Password1
          nova:
            username: nova
            password: Password1
          octavia:
            username: octavia
            password: Password1
          openvswitch:
            username: openvswitch
            password: Password1
          ovn:
            username: ovn
            password: Password1
          placement:
            username: placement
            password: Password1
          powerdns:
            username: powerdns
            password: Password1
          prometheus:
            username: prometheus
            password: Password1
          prometheus-mysql-exporter:
            username: prometheus-mysql-exporter
            password: Password1
          prometheus-openstack-exporter:
            username: prometheus-openstack-exporter
            password: Password1
          rabbitmq:
            username: rabbitmq
            password: Password1
          redis:
            username: redis
            password: Password1
          skyline:
            username: skyline
            password: Password1
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Step 3: Bootstrap FluxCD&lt;/h3&gt;
&lt;p&gt;Point FluxCD at your Git repository to kick off the deployment.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Run a pre-flight check
flux check --pre

# Bootstrap FluxCD against your GitLab repository
flux bootstrap gitlab \
  --owner=&amp;#x3C;your-gitlab-group&gt; \
  --repository=openstack-helm \
  --branch=main \
  --path=clusters/prod/flux-system \
  --token-auth
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 4: Monitor the Deployment&lt;/h3&gt;
&lt;p&gt;Once Flux is bootstrapped, it will begin deploying the &lt;code&gt;HelmRelease&lt;/code&gt; resources. The &lt;code&gt;kubernetes-entrypoint&lt;/code&gt; logic enforces a specific order.&lt;/p&gt;
&lt;p&gt;You can watch the progress:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Watch all HelmReleases in the openstack namespace
flux get helmreleases -n openstack --watch

# Stream logs from the helm-controller for detailed info
kubectl logs -n flux-system deploy/helm-controller -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The deployment will proceed in stages, starting with infrastructure services like MariaDB and RabbitMQ, then core services like Keystone, and finally the extended services and dashboard.&lt;/p&gt;
&lt;h2&gt;Accessing Your OpenStack Cloud&lt;/h2&gt;
&lt;p&gt;Once the deployment is complete, all services will be available at their respective &lt;code&gt;nip.io&lt;/code&gt; URLs.&lt;/p&gt;
&lt;h3&gt;Service Catalog&lt;/h3&gt;
&lt;p&gt;| Service       | Public URL                                 | Notes                      |
| ------------- | ------------------------------------------ | -------------------------- |
| &lt;strong&gt;Keystone&lt;/strong&gt;  | &lt;code&gt;https://keystone.172.24.61.20.nip.io/v3&lt;/code&gt;  | Identity &amp;#x26; Auth            |
| &lt;strong&gt;Skyline&lt;/strong&gt;   | &lt;code&gt;https://skyline.172.24.61.20.nip.io&lt;/code&gt;      | Web Dashboard              |
| &lt;strong&gt;Nova&lt;/strong&gt;      | &lt;code&gt;https://nova.172.24.61.20.nip.io/v2.1&lt;/code&gt;    | Compute API                |
| &lt;strong&gt;NoVNC&lt;/strong&gt;     | &lt;code&gt;https://novnc.172.24.61.20.nip.io&lt;/code&gt;        | VM Console Access          |
| &lt;strong&gt;Cinder&lt;/strong&gt;    | &lt;code&gt;https://cinder.172.24.61.20.nip.io/v3&lt;/code&gt;    | Block Storage (Ceph RBD)   |
| &lt;strong&gt;Glance&lt;/strong&gt;    | &lt;code&gt;https://glance.172.24.61.20.nip.io&lt;/code&gt;       | Image Service (Ceph RBD)   |
| &lt;strong&gt;Neutron&lt;/strong&gt;   | &lt;code&gt;https://neutron.172.24.61.20.nip.io&lt;/code&gt;      | Networking (OVN)           |
| &lt;strong&gt;Placement&lt;/strong&gt; | &lt;code&gt;https://placement.172.24.61.20.nip.io&lt;/code&gt;    | Resource Inventory         |
| &lt;strong&gt;Heat&lt;/strong&gt;      | &lt;code&gt;https://heat.172.24.61.20.nip.io/v1&lt;/code&gt;      | Orchestration              |
| &lt;strong&gt;Barbican&lt;/strong&gt;  | &lt;code&gt;https://barbican.172.24.61.20.nip.io&lt;/code&gt;     | Key/Secret Manager         |
| &lt;strong&gt;Magnum&lt;/strong&gt;    | &lt;code&gt;https://magnum.172.24.61.20.nip.io/v1&lt;/code&gt;    | Kubernetes as a Service    |
| &lt;strong&gt;Octavia&lt;/strong&gt;   | &lt;code&gt;https://octavia.172.24.61.20.nip.io&lt;/code&gt;      | Load Balancer as a Service |
| &lt;strong&gt;Designate&lt;/strong&gt; | &lt;code&gt;https://designate.172.24.61.20.nip.io/v2&lt;/code&gt; | DNS as a Service           |&lt;/p&gt;
&lt;h3&gt;Trusting the Gateway CA&lt;/h3&gt;
&lt;p&gt;Since the gateway uses a self-signed certificate, you must configure your local environment to trust it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Extract the CA certificate from the cluster
kubectl get secret gateway-wildcard-tls -n envoy-gateway-system \
  -o jsonpath=&apos;{.data.ca\.crt}&apos; | base64 -d &gt; gateway-ca.crt

# For the OpenStack CLI, set the OS_CACERT environment variable
export OS_CACERT=/path/to/your/gateway-ca.crt
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Verifying the Deployment&lt;/h3&gt;
&lt;p&gt;With the CA configured, you can use the &lt;code&gt;openstack&lt;/code&gt; CLI to interact with your new cloud.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install the client libraries:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install python-openstackclient python-magnumclient \
            python-octaviaclient python-heatclient python-designateclient
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure &lt;code&gt;clouds.yaml&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;clouds:
  openstack_helm:
    region_name: RegionOne
    identity_api_version: 3
    cacert: /path/to/your/gateway-ca.crt
    auth:
      username: admin
      password: &amp;#x3C;your-admin-password&gt;
      project_name: admin
      project_domain_name: default
      user_domain_name: default
      auth_url: https://keystone.172.24.61.20.nip.io/v3
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set the active cloud and test:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export OS_CLOUD=openstack_helm

openstack endpoint list
openstack compute service list
openstack network agent list
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Maintenance and Troubleshooting&lt;/h2&gt;
&lt;p&gt;The GitOps model simplifies maintenance. To upgrade OpenStack, you would update the image tags in your &lt;code&gt;values&lt;/code&gt; ConfigMaps, commit, and push. FluxCD handles the rolling update.&lt;/p&gt;
&lt;p&gt;If a &lt;code&gt;HelmRelease&lt;/code&gt; gets stuck, use &lt;code&gt;flux get helmreleases&lt;/code&gt; and &lt;code&gt;kubectl describe helmrelease&lt;/code&gt; to diagnose the issue. If a pod is stuck in &lt;code&gt;Init&lt;/code&gt;, check the status of its dependency jobs with &lt;code&gt;kubectl get jobs -n openstack&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This setup provides a powerful, declarative, and version-controlled foundation for running a production OpenStack cloud on Kubernetes.&lt;/p&gt;
&lt;h2&gt;Networking Deep Dive: Neutron with OVN and Open vSwitch&lt;/h2&gt;
&lt;p&gt;Our OpenStack deployment leverages the power of Open Virtual Network (OVN) as the backend for Neutron, the OpenStack Networking service. This modern architecture uses OVN and Open vSwitch (OVS) to provide a scalable and efficient software-defined network (SDN).&lt;/p&gt;
&lt;h3&gt;What is Open vSwitch (OVS)?&lt;/h3&gt;
&lt;p&gt;Think of Open vSwitch as a smart, software-based network switch that runs on every compute node in our Kubernetes cluster. [1] It operates at the data link layer (Layer 2) and is responsible for the actual forwarding of network packets between virtual machines. [2] Just like a physical switch, it learns MAC addresses to direct traffic to the correct destination. [2]&lt;/p&gt;
&lt;h3&gt;What is OVN (Open Virtual Network)?&lt;/h3&gt;
&lt;p&gt;If OVS is the muscle, OVN is the brain. OVN is a control plane for OVS, providing a higher-level abstraction for virtual networking. [1] It takes the logical network concepts from Neutron—such as virtual networks, routers, subnets, and security groups—and translates them into rules that OVS can understand. [3]&lt;/p&gt;
&lt;h3&gt;How They Work Together&lt;/h3&gt;
&lt;p&gt;The synergy between OVN and OVS is what makes the networking stack so powerful:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When you make a request to the Neutron API (e.g., &quot;create a new network&quot;), Neutron communicates this to OVN&apos;s Northbound database.&lt;/li&gt;
&lt;li&gt;A central OVN component, &lt;code&gt;ovn-northd&lt;/code&gt;, translates this high-level request into logical flows in the OVN Southbound database. [3]&lt;/li&gt;
&lt;li&gt;On each compute node, an agent called &lt;code&gt;ovn-controller&lt;/code&gt; is constantly watching the Southbound database. It takes the logical flows and converts them into specific, physical OpenFlow rules for the local OVS instance. [3]&lt;/li&gt;
&lt;li&gt;OVS then uses these rules to forward packets for the VMs running on that node.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This architecture replaces the legacy collection of Python-based Neutron agents (like &lt;code&gt;neutron-l3-agent&lt;/code&gt;, &lt;code&gt;neutron-dhcp-agent&lt;/code&gt;) with a more robust, centralized, and database-driven control plane. [3] This results in better performance, greater scalability, and a simplified overall network architecture. [5]&lt;/p&gt;
&lt;h3&gt;Traffic Patterns: East-West vs. North-South&lt;/h3&gt;
&lt;p&gt;OVN handles two primary traffic patterns very differently, which is key to its efficiency.&lt;/p&gt;
&lt;h4&gt;East-West Traffic (Internal)&lt;/h4&gt;
&lt;p&gt;East-West traffic refers to all communication between workloads &lt;em&gt;inside&lt;/em&gt; the OpenStack cloud. This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VM-to-VM communication within the same tenant network.&lt;/li&gt;
&lt;li&gt;Traffic between VMs in different networks that is routed through a virtual router.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This traffic stays within the OVN overlay network, encapsulated in tunnels (like Geneve or VXLAN). It is highly efficient because it doesn&apos;t need to traverse the physical network gateway, making it ideal for backend services, database replication, and internal API calls.&lt;/p&gt;
&lt;h4&gt;North-South Traffic (External)&lt;/h4&gt;
&lt;p&gt;North-South traffic is any communication between an internal workload and the outside world. Examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A user accessing a web server on a VM via a Floating IP.&lt;/li&gt;
&lt;li&gt;A VM downloading software updates from the internet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This traffic must leave the OVN overlay and travel through a gateway to the physical network. In our Neutron setup, this is handled by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Virtual Routers:&lt;/strong&gt; With gateway ports connected to a provider network (like the &lt;code&gt;external-net&lt;/code&gt; we create later).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Floating IPs:&lt;/strong&gt; Which use Network Address Translation (NAT) to map a public IP to a VM&apos;s private IP.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://private-site-585329.gitlab.io/_astro/01-OpenStack-Neutron-with-OVN-High-Level-1024x683.DgHAqMX__Z1Wv72d.webp&quot; alt=&quot;OVN&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Post-Deployment: Creating a Magnum Kubernetes Cluster&lt;/h2&gt;
&lt;p&gt;Now that your OpenStack cloud is running, a common next step is to use Magnum (Container Orchestration Engine) to create Kubernetes clusters for your tenants. This section guides you through creating the necessary networking and templates.&lt;/p&gt;
&lt;h3&gt;Step 1: Create External Network and Subnet (via Skyline)&lt;/h3&gt;
&lt;p&gt;Before tenants can create clusters with public IPs, you need an external network in you Administrator tenant.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Log in to the &lt;strong&gt;Skyline&lt;/strong&gt; dashboard (&lt;code&gt;https://skyline.172.24.61.20.nip.io&lt;/code&gt;) as the &lt;code&gt;admin&lt;/code&gt; user.&lt;/li&gt;
&lt;li&gt;Navigate on &lt;strong&gt;Administrator&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Network&lt;/strong&gt; &gt; &lt;strong&gt;Networks&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Network&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name, for example, &lt;code&gt;external-net&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check the &lt;strong&gt;External Network&lt;/strong&gt; box.&lt;/li&gt;
&lt;li&gt;Proceed to create a subnet for this network. Define an IP range that is routable in your physical environment but does not overlap with other services (e.g., &lt;code&gt;172.24.61.100&lt;/code&gt; - &lt;code&gt;172.24.61.150&lt;/code&gt;). Set the gateway IP for this subnet.&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Console&lt;/strong&gt; to go back to the Skyline dashboard.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://private-site-585329.gitlab.io/_astro/public_network.CM3owyNS_1dlb3P.webp&quot; alt=&quot;External Network&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Step 2: Create a Router&lt;/h3&gt;
&lt;p&gt;To connect your private tenant networks to the new external network, you need a router.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In Skyline, go to &lt;strong&gt;Network&lt;/strong&gt; &gt; &lt;strong&gt;Routers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Router&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name (e.g., &lt;code&gt;provider-router&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;After creation, click on the router and set its gateway to the &lt;code&gt;external-net&lt;/code&gt; you created.&lt;/li&gt;
&lt;li&gt;You can then add interfaces to connect this router to existing private tenant networks.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://private-site-585329.gitlab.io/_astro/create_test_router.CFsnTDXR_Z1k1D9w.webp&quot; alt=&quot;Router&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Step 3: Prepare Fedora CoreOS Image for Magnum&lt;/h3&gt;
&lt;p&gt;Magnum uses specific images to boot Kubernetes nodes. Fedora CoreOS is a common choice. The following commands should be run on a machine with the OpenStack CLI installed and configured.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Set the desired Fedora CoreOS version
export FCOS_VERSION=&quot;35.20220116.3.0&quot;

# Download the image
wget https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/${FCOS_VERSION}/x86_64/fedora-coreos-${FCOS_VERSION}-openstack.x86_64.qcow2.xz

# Decompress the image
unxz fedora-coreos-${FCOS_VERSION}-openstack.x86_64.qcow2.xz

# Upload the image to Glance
openstack image create \
  --public \
  --disk-format=qcow2 \
  --container-format=bare \
  --file=fedora-coreos-${FCOS_VERSION}-openstack.x86_64.qcow2 \
  --property os_distro=&apos;fedora-coreos&apos; \
  fedora-coreos-latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes a public image named &lt;code&gt;fedora-coreos-latest&lt;/code&gt; available in Glance for Magnum to use.&lt;/p&gt;
&lt;h3&gt;Step 4: Create the Magnum Cluster Template&lt;/h3&gt;
&lt;p&gt;A cluster template defines the parameters for creating Kubernetes clusters. This command creates a template that uses the Fedora CoreOS image and Calico for networking.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;openstack coe cluster template create k8s-cluster-template \
  --image fedora-coreos-latest \
  --keypair &amp;#x3C;your-keypair-name&gt; \
  --external-network external-net \
  --dns-nameserver 8.8.8.8 \
  --master-flavor m2.small \
  --flavor m2.small \
  --docker-volume-size 15 \
  --network-driver calico \
  --volume-driver cinder \
  --coe kubernetes \
  --public \
  --labels kube_tag=v1.28.9-rancher1,container_runtime=containerd,containerd_version=1.6.31,containerd_tarball_sha256=75afb9b9674ff509ae670ef3ab944ffcdece8ea9f7d92c42307693efa7b6109d,cloud_provider_tag=v1.27.3,cinder_csi_plugin_tag=v1.27.3,k8s_keystone_auth_tag=v1.27.3,magnum_auto_healer_tag=v1.27.3,octavia_ingress_controller_tag=v1.27.3,calico_tag=v3.26.4
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Replace &lt;code&gt;&amp;#x3C;your-keypair-name&gt;&lt;/code&gt; with the name of an SSH keypair you have already uploaded to OpenStack.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With this template, users can now run &lt;code&gt;openstack coe cluster create ... --cluster-template k8s-cluster-template&lt;/code&gt; to provision their own Kubernetes clusters.&lt;/p&gt;
&lt;h3&gt;Bonus: Uploading a General-Purpose Ubuntu Image&lt;/h3&gt;
&lt;p&gt;It&apos;s also useful to have standard cloud images for creating regular virtual machines. Here is how you can upload an Ubuntu 24.04 image.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Download the Ubuntu cloud image
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img -O ubuntu-24.04-server.img

# Upload the image to Glance
openstack image create \
  --disk-format qcow2 \
  --container-format bare \
  --public \
  --property os_type=linux \
  --file ubuntu-24.04-server.img \
  ubuntu-24.04-server
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deployment in Action&lt;/h2&gt;
&lt;p&gt;Here are some snapshots from the openstack-helm stack&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://private-site-585329.gitlab.io/_astro/server_list.EU97av3T_mf5pm.webp&quot; alt=&quot;Openstack Server List&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://private-site-585329.gitlab.io/_astro/network_topology.D96QoK6S_ZIfmsP.webp&quot; alt=&quot;Network Topology&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/thumbnail.Dekzh7V8.jpg"/><enclosure url="/_astro/thumbnail.Dekzh7V8.jpg"/></item><item><title>Automating a Production-Ready K8s Cluster with Ansible</title><link>https://private-site-585329.gitlab.io/blog/kubernetes-cluster</link><guid isPermaLink="true">https://private-site-585329.gitlab.io/blog/kubernetes-cluster</guid><description>A guide to deploying a production-ready K8s cluster on Debian/Ubuntu using Ansible, featuring Rook-Ceph, Envoy Gateway, FluxCD, and a full observability stack.</description><pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post walks through a set of Ansible playbooks that fully automate a production-ready Kubernetes cluster on Debian or Ubuntu nodes. The setup goes from base OS configuration through Rook-Ceph for storage, Envoy Gateway for ingress, FluxCD for GitOps, and a complete observability stack with Prometheus and Grafana.&lt;/p&gt;
&lt;h2&gt;Component Versions&lt;/h2&gt;
&lt;p&gt;All component versions are pinned for reproducible deployments and can be customized in the Ansible inventory.&lt;/p&gt;
&lt;p&gt;| Component            | Version                        |
| -------------------- | ------------------------------ |
| Kubernetes           | 1.34.9                         |
| Container runtime    | containerd                     |
| Pod networking       | Calico 3.32.0 (eBPF dataplane) |
| Load balancer        | MetalLB 0.16.1                 |
| TLS management       | cert-manager v1.13.0           |
| Distributed storage  | Rook-Ceph 1.19.7               |
| GitOps               | FluxCD 2.18.4                  |
| Ingress              | Envoy Gateway 1.8.1            |
| Monitoring           | kube-prometheus-stack 87.2.1   |
| Kubernetes Dashboard | 7.14.0                         |
| Helm CLI             | 3.21.2                         |&lt;/p&gt;
&lt;h2&gt;Architecture Overview&lt;/h2&gt;
&lt;p&gt;The cluster is designed for robustness and modern cloud-native practices.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./kubernetes-architecture-openstack.svg&quot; alt=&quot;Kubernetes on OpenStack Architecture&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;External traffic     |
MetalLB (L2/BGP)  --&gt;  Envoy Gateway  --&gt;  HTTPRoutes
                     |              +---------------+---------------+
                     v              v               v
                 Grafana        k8s-dash        Ceph-dash
                     ^              |
                     |  cert-manager (self-signed wildcard)
                     |
  kube-prometheus-stack
    Prometheus --&gt; scrapes via ServiceMonitor
    Grafana    --&gt; sidecar watches ConfigMaps (dashboards)
                       ^
    Rook-Ceph ServiceMonitor (labeled release: kube-prometheus-stack)

  Rook-Ceph (operator + cluster + StorageClass: general)

  FluxCD (GitOps controller)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Network Configuration&lt;/h2&gt;
&lt;p&gt;The nodes in this setup utilize multiple network interfaces, a common pattern in virtualized environments like OpenStack. Here&apos;s a typical &lt;code&gt;netplan&lt;/code&gt; configuration for the control plane:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;network:
  version: 2
  ethernets:
    ens160:
      addresses:
        - &apos;172.24.61.40/24&apos;
      nameservers:
        addresses:
          - 8.8.8.8
      routes:
        - to: &apos;default&apos;
          via: &apos;172.24.61.254&apos;
    ens192:
      addresses:
        - &apos;10.0.0.4/24&apos;
    ens224:
      dhcp4: false
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ens160&lt;/code&gt;: The primary interface for external access and Ansible management (&lt;code&gt;172.24.61.0/24&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ens192&lt;/code&gt;: An internal network for cluster communication (&lt;code&gt;10.0.0.0/24&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ens224&lt;/code&gt;: An external flat network used by OpenStack.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ens256&lt;/code&gt;: An internal network used by Octavia LB communication.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The worker nodes follow a similar pattern, with an additional interface (&lt;code&gt;ens256&lt;/code&gt;) often reserved for octavia lb communication traffic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;network:
  version: 2
  ethernets:
    ens160:
      addresses:
        - &apos;172.24.61.41/24&apos;
      nameservers:
        addresses:
          - 8.8.8.8
      routes:
        - to: &apos;default&apos;
          via: &apos;172.24.61.254&apos;
    ens192:
      addresses:
        - &apos;10.0.0.1/24&apos;
    ens224:
      dhcp4: false
    ens256:
      dhcp4: false
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ansible Roles&lt;/h2&gt;
&lt;p&gt;The automation is broken down into logical Ansible roles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;common&lt;/code&gt;&lt;/strong&gt;: Configures the base OS, disables swap, installs &lt;code&gt;containerd&lt;/code&gt;, sets up required kernel modules, and installs &lt;code&gt;kubelet&lt;/code&gt;, &lt;code&gt;kubeadm&lt;/code&gt;, and &lt;code&gt;kubectl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;control_planes&lt;/code&gt;&lt;/strong&gt;: Initializes the Kubernetes control plane using &lt;code&gt;kubeadm&lt;/code&gt; and installs the Calico CNI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nodes&lt;/code&gt;&lt;/strong&gt;: Joins the worker nodes to the cluster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cluster_addons&lt;/code&gt;&lt;/strong&gt;: Deploys the entire application stack, including MetalLB, cert-manager, Rook-Ceph, FluxCD, Envoy Gateway, and the monitoring components.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;update-and-reboot&lt;/code&gt;&lt;/strong&gt;: A maintenance playbook for system-wide updates and reboots.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;h3&gt;System Requirements&lt;/h3&gt;
&lt;p&gt;|             | Control plane                      | Worker nodes                  |
| ----------- | ---------------------------------- | ----------------------------- |
| &lt;strong&gt;OS&lt;/strong&gt;      | Debian 11/12 or Ubuntu 22.04/24.04 | same                          |
| &lt;strong&gt;CPU&lt;/strong&gt;     | 2+ cores                           | 2+ cores                      |
| &lt;strong&gt;RAM&lt;/strong&gt;     | 4 GB+                              | 2 GB+                         |
| &lt;strong&gt;Disk&lt;/strong&gt;    | 30 GB+                             | 20 GB+ (+ raw disks for Ceph) |
| &lt;strong&gt;Network&lt;/strong&gt; | Static IP, internet access         | same                          |&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Ceph requires at least one raw (unformatted) block device per worker node. The Calico eBPF dataplane requires a Linux kernel version of 5.3 or higher.&lt;/p&gt;
&lt;h3&gt;Control Machine Setup&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install Ansible and the kubernetes.core collection
sudo apt update &amp;#x26;&amp;#x26; sudo apt install -y ansible python3-pip
ansible-galaxy collection install kubernetes.core

# Install python3-kubernetes
sudo apt install -y python3-pip python3-kubernetes
pip3 install kubernetes --break-system-packages

# Verify
ansible --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will also need to configure passwordless SSH access from your control machine to all cluster nodes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Generate a key pair if needed
ssh-keygen -t ed25519 -C &quot;ansible&quot;

# Copy to every node (adjust IPs to match your inventory)
ssh-copy-id root@172.24.61.40   # control plane
ssh-copy-id root@172.24.61.41   # worker 1
ssh-copy-id root@172.24.61.42   # worker 2
ssh-copy-id root@172.24.61.43   # worker 3
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;h3&gt;Step 1: Configure Inventory&lt;/h3&gt;
&lt;p&gt;Clone the project repository and edit the &lt;code&gt;inventory/dev&lt;/code&gt; file to match your node IPs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[control_planes]
k8s-control-plane1 ansible_host=172.24.61.40

[nodes]
k8s-worker1 ansible_host=172.24.61.41
k8s-worker2 ansible_host=172.24.61.42
k8s-worker3 ansible_host=172.24.61.43
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 2: Review Configuration&lt;/h3&gt;
&lt;p&gt;Key variables are located in &lt;code&gt;inventory/group_vars/all.yaml&lt;/code&gt;. Pay special attention to the networking and monitoring sections.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Networking
metallb_mode: &apos;l2&apos;
metallb_ip_range: &apos;172.24.61.20-172.24.61.30&apos;
metallb_l2_interface: &apos;ens160&apos;
gateway_ip: &apos;172.24.61.20&apos;

# Calico eBPF
calico_ebpf: true
calico_ebpf_dsr: true

# Monitoring
grafana_admin_password: &apos;ChangeMe123!&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 3: Deploy&lt;/h3&gt;
&lt;p&gt;You can deploy the entire cluster with a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ansible-playbook -i inventory/dev playbooks/k8s_all.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the first run, it&apos;s recommended to execute the playbooks one by one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. Base OS configuration on all nodes
ansible-playbook -i inventory/dev playbooks/common.yaml

# 2. Initialise control plane and install Calico
ansible-playbook -i inventory/dev playbooks/control_planes.yaml

# 3. Join worker nodes
ansible-playbook -i inventory/dev playbooks/nodes.yaml

# 4. Install the full add-on stack
ansible-playbook -i inventory/dev playbooks/cluster_addons.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Step 4: Access the Cluster&lt;/h3&gt;
&lt;p&gt;Copy the kubeconfig from the control plane to your local machine to manage the cluster with &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;scp root@172.24.61.40:~/.kube/config ~/.kube/config
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deep Dive: Calico eBPF Networking&lt;/h2&gt;
&lt;p&gt;This setup uses Calico&apos;s eBPF dataplane, which replaces &lt;code&gt;kube-proxy&lt;/code&gt; entirely. This offers several advantages over the standard iptables-based mode, including lower latency, higher throughput, and better scalability.&lt;/p&gt;
&lt;p&gt;|                             | Standard Calico (iptables)  | Calico eBPF                         |
| --------------------------- | --------------------------- | ----------------------------------- |
| &lt;strong&gt;Service routing&lt;/strong&gt;         | &lt;code&gt;kube-proxy&lt;/code&gt; iptables rules | eBPF programs in kernel             |
| &lt;strong&gt;&lt;code&gt;kube-proxy&lt;/code&gt; dependency&lt;/strong&gt; | Required                    | Eliminated                          |
| &lt;strong&gt;External traffic path&lt;/strong&gt;   | SNAT on ingress node        | Direct Server Return (no extra hop) |
| &lt;strong&gt;Connection scaling&lt;/strong&gt;      | O(n) iptables rules         | O(1) eBPF map lookups               |&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;control_planes&lt;/code&gt; role automates the configuration by patching the &lt;code&gt;FelixConfiguration&lt;/code&gt; and disabling the &lt;code&gt;kube-proxy&lt;/code&gt; DaemonSet.&lt;/p&gt;
&lt;h2&gt;Deep Dive: Monitoring Stack&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;cluster_addons&lt;/code&gt; playbook deploys a &lt;code&gt;kube-prometheus-stack&lt;/code&gt; and integrates it with Rook-Ceph for comprehensive monitoring.&lt;/p&gt;
&lt;h3&gt;Ceph Dashboard Integration&lt;/h3&gt;
&lt;p&gt;A key challenge is getting the Ceph dashboards into Grafana reliably. The solution used here is both robust and idempotent:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Ansible playbook uses &lt;code&gt;curl&lt;/code&gt; on the control plane to fetch the official dashboard JSON files directly from the Rook-Ceph GitHub repository, version-locked to the deployed Ceph version.&lt;/li&gt;
&lt;li&gt;It then creates a &lt;code&gt;ConfigMap&lt;/code&gt; in the &lt;code&gt;monitoring&lt;/code&gt; namespace for each dashboard.&lt;/li&gt;
&lt;li&gt;These &lt;code&gt;ConfigMap&lt;/code&gt;s are labeled with &lt;code&gt;grafana_dashboard: &quot;1&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A Grafana sidecar container (&lt;code&gt;k8s-sidecar&lt;/code&gt;) is configured to watch for &lt;code&gt;ConfigMap&lt;/code&gt;s with this label across all namespaces and automatically hot-loads them into Grafana.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach ensures dashboards survive Grafana restarts and are automatically updated when the Ceph version is changed in the Ansible variables.&lt;/p&gt;
&lt;p&gt;Here is the list of dashboards automatically loaded into Grafana:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./dashboard-list.png&quot; alt=&quot;Grafana Dashboard List&quot;&gt;&lt;/p&gt;
&lt;p&gt;And here is an example of the main cluster dashboard:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./cluster-dashboard.png&quot; alt=&quot;Grafana Cluster Dashboard&quot;&gt;&lt;/p&gt;
&lt;h3&gt;Accessing Dashboards&lt;/h3&gt;
&lt;p&gt;At the end of a successful deployment, the playbook prints an access summary. The services are exposed via MetalLB and Envoy Gateway using &lt;code&gt;nip.io&lt;/code&gt; for DNS.&lt;/p&gt;
&lt;p&gt;| Service              | URL                                | Credentials                                   |
| -------------------- | ---------------------------------- | --------------------------------------------- |
| Grafana              | &lt;code&gt;https://grafana.&amp;#x3C;gw-ip&gt;.nip.io&lt;/code&gt;   | &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;grafana_admin_password&lt;/code&gt;            |
| Kubernetes Dashboard | &lt;code&gt;https://dashboard.&amp;#x3C;gw-ip&gt;.nip.io&lt;/code&gt; | Bearer token (printed at run end)             |
| Rook-Ceph Dashboard  | &lt;code&gt;https://ceph.&amp;#x3C;gw-ip&gt;.nip.io&lt;/code&gt;      | &lt;code&gt;admin&lt;/code&gt; / auto-generated (printed at run end) |&lt;/p&gt;
&lt;p&gt;All services are secured with self-signed HTTPS certificates managed by &lt;code&gt;cert-manager&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Maintenance and Troubleshooting&lt;/h2&gt;
&lt;p&gt;The playbooks are idempotent, so they can be re-run safely.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Rolling OS update + reboot
ansible-playbook -i inventory/dev playbooks/update-and-reboot.yaml

# Re-run only the add-on stack
ansible-playbook -i inventory/dev playbooks/cluster_addons.yaml

# Check Ceph cluster health
kubectl exec -n rook-ceph deploy/rook-ceph-tools -- ceph status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This project provides a powerful and repeatable foundation for running Kubernetes in a production environment. The combination of Ansible&apos;s automation with best-in-class cloud-native tools creates a cluster that is both feature-rich and maintainable.&lt;/p&gt;</content:encoded><h:img src="/_astro/thumbnail.77b22cHd.jpg"/><enclosure url="/_astro/thumbnail.77b22cHd.jpg"/></item><item><title>Tips to improve concentration</title><link>https://private-site-585329.gitlab.io/blog/improve-concentration</link><guid isPermaLink="true">https://private-site-585329.gitlab.io/blog/improve-concentration</guid><description>Mindfulness, cognitive training, and a healthy lifestyle may help sharpen your focus.</description><pubDate>Sat, 10 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;You&apos;re trying to concentrate, but your mind is wandering or you&apos;re easily distracted. What happened to the laser-sharp focus you once enjoyed? As we age, we tend to have more difficulty filtering out stimuli that are not relevant to the task at hand.&lt;/p&gt;
&lt;h2&gt;What&apos;s fogging up focus?&lt;/h2&gt;
&lt;p&gt;Like a computer that slows with use, the brain accumulates wear and tear that affects processing. This can be caused by a number of physiological stressors such as inflammation, injury to blood vessels (especially if you have high blood pressure), the buildup of abnormal proteins, and naturally occurring brain shrinkage.&lt;/p&gt;
&lt;p&gt;The following factors can also affect your concentration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Underlying conditions.&lt;/strong&gt; Depression or sleep disorders (such as sleep apnea) can undermine your ability to concentrate. So can the effects of vision or hearing loss. You waste precious cognitive resources when you spend too much time trying to make out what&apos;s written on a page or just hear what someone is saying.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Medication side effects.&lt;/strong&gt; Some drugs, especially anticholinergics (such as treatments for incontinence, depression, or allergies), can slow processing speed and your ability to think clearly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Excessive drinking.&lt;/strong&gt; Having too much alcohol impairs thinking and causes interrupted sleep, which affects concentration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Information overload.&lt;/strong&gt; We are bombarded with information from TVs, computers, and messages such as texts or emails. When there&apos;s too much material, it burdens our filtering system and it&apos;s easy to get distracted.&lt;/p&gt;
&lt;h2&gt;Strategies to stay focused&lt;/h2&gt;
&lt;p&gt;To improve attention, consider the following strategies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mindfulness.&lt;/strong&gt; &quot;Mindfulness is about focusing attention on the present moment, and practicing mindfulness has been shown to rewire the brain so that attention is stronger in everyday life,&quot; says Kim Willment, a neuropsychologist with Brigham and Women&apos;s Hospital. She recommends sitting still for a few minutes each day, closing your eyes, and focusing on your breathing as well as the sounds and sensations around you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cognitive training.&lt;/strong&gt; Computerized cognitive training games aim to improve your response times and attention. Evidence that this works has been mixed. &quot;The goal of playing these games is not to get better at them, but to get better in the cognitive activities of everyday life,&quot; Willment says. &quot;But there is evidence that a person&apos;s ability to pay attention can be improved by progressively pushing the person to higher levels of performance. So if you reach a certain level of sustained attention, pushing it to the next level can help improve it, and this may translate to everyday life.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A healthier lifestyle.&lt;/strong&gt; Many aspects of a healthy lifestyle can help attention, starting with sleep and exercise. There is a direct link between exercise and cognitive ability, especially attention. When you exercise, you increase the availability of brain chemicals that promote new brain connections, reduce stress, and improve sleep. And when we sleep, we reduce stress hormones that can be harmful to the brain, and we clear out proteins that injure it.&lt;/p&gt;
&lt;p&gt;Aim for seven to eight hours of sleep each night, and 150 minutes per week of aerobic exercise, such as brisk walking.&lt;/p&gt;
&lt;p&gt;Other healthy steps to improve focus: eat a Mediterranean-style diet, which has been shown to support brain health; treat underlying conditions; and change medications that may be affecting your ability to focus.&lt;/p&gt;
&lt;p&gt;Getting older is out of your control, but healthier living is something you determine, and it may improve concentration.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Article from: &lt;a href=&quot;https://www.health.harvard.edu/mind-and-mood/tips-to-improve-concentration&quot;&gt;Harvard Health Publishing&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="/_astro/thumbnail.1GZ294Dz.jpg"/><enclosure url="/_astro/thumbnail.1GZ294Dz.jpg"/></item><item><title>Using MDX</title><link>https://private-site-585329.gitlab.io/blog/using-mdx</link><guid isPermaLink="true">https://private-site-585329.gitlab.io/blog/using-mdx</guid><description>Learning how to use MDX in Astro</description><pubDate>Sun, 01 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This theme comes with the &lt;a href=&quot;https://docs.astro.build/en/guides/integrations-guide/mdx/&quot;&gt;@astrojs/mdx&lt;/a&gt; integration installed and configured in your &lt;code&gt;astro.config.ts&lt;/code&gt; config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.&lt;/p&gt;
&lt;h2&gt;Why MDX?&lt;/h2&gt;
&lt;p&gt;MDX is a special flavor of Markdown that supports embedded JavaScript &amp;#x26; JSX syntax. This unlocks the ability to &lt;a href=&quot;https://docs.astro.build/en/guides/markdown-content/#mdx-features&quot;&gt;mix JavaScript and UI Components into your Markdown content&lt;/a&gt; for things like interactive charts or alerts.&lt;/p&gt;
&lt;p&gt;If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.&lt;/p&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;Here is how you import and use a UI component inside of MDX.&lt;br&gt;
When you open this page in the browser, you should see the clickable button below.&lt;/p&gt;
&lt;p&gt;import { Button } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;Click Me&lt;/p&gt;
&lt;h2&gt;More Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mdxjs.com/docs/what-is-mdx&quot;&gt;MDX Syntax Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages&quot;&gt;Astro Usage Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;a href=&quot;https://docs.astro.build/en/reference/directives-reference/#client-directives&quot;&gt;Client Directives&lt;/a&gt; are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Markdown Syntax Support</title><link>https://private-site-585329.gitlab.io/blog/markdown</link><guid isPermaLink="true">https://private-site-585329.gitlab.io/blog/markdown</guid><description>Markdown is a lightweight markup language.</description><pubDate>Wed, 26 Jul 2023 08:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Basic Syntax&lt;/h2&gt;
&lt;p&gt;Markdown is a lightweight and easy-to-use syntax for styling your writing.&lt;/p&gt;
&lt;h3&gt;Headers&lt;/h3&gt;
&lt;p&gt;When the content of the article is extensive, you can use headers to segment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# Header 1

## Header 2

## Large Header

### Small Header
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Header previews would disrupt the structure of the article, so they are not displayed here.&lt;/p&gt;
&lt;h3&gt;Bold and Italics&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;_Italic text_ and **Bold text**, together will be **_Bold Italic text_**
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Italic text&lt;/em&gt; and &lt;strong&gt;Bold text&lt;/strong&gt;, together will be &lt;strong&gt;&lt;em&gt;Bold Italic text&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Links&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Text link [Link Name](http://link-url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;Text link &lt;a href=&quot;http://link-url&quot;&gt;Link Name&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Inline Code&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;This is an `inline code`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;This is an &lt;code&gt;inline code&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;Code Blocks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;```js
// calculate fibonacci
function fibonacci(n) {
  if (n &amp;#x3C;= 1) return 1
  const result = fibonacci(n - 1) + fibonacci(n - 2) // [\!code --]
  return fibonacci(n - 1) + fibonacci(n - 2) // [\!code ++]
}
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// calculate fibonacci
function fibonacci(n) {
  if (n &amp;#x3C;= 1) return 1
  const result = fibonacci(n - 1) + fibonacci(n - 2) // [!code --]
  return fibonacci(n - 1) + fibonacci(n - 2) // [!code ++]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Currently using shiki as the code highlighting plugin. For supported languages, refer to &lt;a href=&quot;https://shiki.matsu.io/languages.html&quot;&gt;Shiki: Languages&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Inline Formula&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;This is an inline formula $e^{i\pi} + 1 = 0$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;This is an inline formula $e^{i\pi} + 1 = 0$&lt;/p&gt;
&lt;h3&gt;Formula Blocks&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;$$
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x) e^{-2\pi i x \xi} \, dx
$$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;$$
\hat{f}(\xi) = \int_{-\infty}^{\infty} f(x) e^{-2\pi i x \xi} , dx
$$&lt;/p&gt;
&lt;p&gt;Currently using KaTeX as the math formula plugin. For supported syntax, refer to &lt;a href=&quot;https://katex.org/docs/supported.html&quot;&gt;KaTeX Supported Functions&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Images&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![Alt text](https://placehold.co/200x200.png)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://placehold.co/200x200.png&quot; alt=&quot;Alt text&quot;&gt;&lt;/p&gt;
&lt;h4&gt;Strikethrough&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;~~Strikethrough~~
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;~~Strikethrough~~&lt;/p&gt;
&lt;h3&gt;Lists&lt;/h3&gt;
&lt;p&gt;Regular unordered list&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- 1
- 2
- 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;2&lt;/li&gt;
&lt;li&gt;3&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Regular ordered list&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;1. GPT-4
2. Claude Opus
3. LLaMa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GPT-4&lt;/li&gt;
&lt;li&gt;Claude Opus&lt;/li&gt;
&lt;li&gt;LLaMa&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can continue to nest syntax within lists.&lt;/p&gt;
&lt;h3&gt;Blockquotes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&gt; Gunshot, thunder, sword rise. A scene of flowers and blood.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Gunshot, thunder, sword rise. A scene of flowers and blood.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can continue to nest syntax within blockquotes.&lt;/p&gt;
&lt;h3&gt;Line Breaks&lt;/h3&gt;
&lt;p&gt;Markdown needs a blank line to separate paragraphs.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;If you don&apos;t leave a blank line
it will be in one paragraph

First paragraph

Second paragraph
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;If you don&apos;t leave a blank line
it will be in one paragraph&lt;/p&gt;
&lt;p&gt;First paragraph&lt;/p&gt;
&lt;p&gt;Second paragraph&lt;/p&gt;
&lt;h3&gt;Separators&lt;/h3&gt;
&lt;p&gt;If you have the habit of writing separators, you can start a new line and enter three dashes &lt;code&gt;---&lt;/code&gt; or asterisks &lt;code&gt;***&lt;/code&gt;. Leave a blank line before and after when there are paragraphs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Advanced Techniques&lt;/h2&gt;
&lt;h3&gt;Inline HTML Elements&lt;/h3&gt;
&lt;p&gt;Currently, only some inline HTML elements are supported, including &lt;code&gt;&amp;#x3C;kdb&gt; &amp;#x3C;b&gt; &amp;#x3C;i&gt; &amp;#x3C;em&gt; &amp;#x3C;sup&gt; &amp;#x3C;sub&gt; &amp;#x3C;br&gt;&lt;/code&gt;, such as&lt;/p&gt;
&lt;h4&gt;Key Display&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Use &amp;#x3C;kbd&gt;Ctrl&amp;#x3C;/kbd&gt; + &amp;#x3C;kbd&gt;Alt&amp;#x3C;/kbd&gt; + &amp;#x3C;kbd&gt;Del&amp;#x3C;/kbd&gt; to reboot the computer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;Use Ctrl + Alt + Del to reboot the computer&lt;/p&gt;
&lt;h4&gt;Bold Italics&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;#x3C;b&gt; Markdown also applies here, such as _bold_ &amp;#x3C;/b&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt; Markdown also applies here, such as &lt;em&gt;bold&lt;/em&gt; &lt;/p&gt;
&lt;h3&gt;Other HTML Writing&lt;/h3&gt;
&lt;h4&gt;Foldable Blocks&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;&amp;#x3C;details&gt;&amp;#x3C;summary&gt;Click to expand&amp;#x3C;/summary&gt;It is hidden&amp;#x3C;/details&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;h3&gt;Tables&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;| Header1  | Header2  |
| -------- | -------- |
| Content1 | Content2 |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;| Header1  | Header2  |
| -------- | -------- |
| Content1 | Content2 |&lt;/p&gt;
&lt;h3&gt;Footnotes&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;Use [^footnote] to add a footnote at the point of reference.

Then, at the end of the document, add the content of the footnote (it will be rendered at the end of the article by default).

[^footnote]: Here is the content of the footnote
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;Use [^footnote] to add a footnote at the point of reference.&lt;/p&gt;
&lt;p&gt;Then, at the end of the document, add the content of the footnote (it will be rendered at the end of the article by default).&lt;/p&gt;
&lt;p&gt;[^footnote]: Here is the content of the footnote&lt;/p&gt;
&lt;h3&gt;To-Do Lists&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;- [ ] Incomplete task
- [x] Completed task
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Incomplete task&lt;/li&gt;
&lt;li&gt;[x] Completed task&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Symbol Escaping&lt;/h3&gt;
&lt;p&gt;If you need to use markdown symbols like _ # * in your description but don&apos;t want them to be escaped, you can add a backslash before these symbols, such as &lt;code&gt;\_&lt;/code&gt; &lt;code&gt;\#&lt;/code&gt; &lt;code&gt;\*&lt;/code&gt; to avoid it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;\_Don&apos;t want the text here to be italic\_

\*\*Don&apos;t want the text here to be bold\*\*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Preview:&lt;/p&gt;
&lt;p&gt;_Don&apos;t want the text here to be italic_&lt;/p&gt;
&lt;p&gt;**Don&apos;t want the text here to be bold**&lt;/p&gt;
&lt;hr&gt;</content:encoded><h:img src="/_astro/thumbnail.HAXFr_hw.jpg"/><enclosure url="/_astro/thumbnail.HAXFr_hw.jpg"/></item></channel></rss>