<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sri Sai Venkata Kasi Subbarayudu Kompella</title>
    <description>The latest articles on DEV Community by Sri Sai Venkata Kasi Subbarayudu Kompella (@kasi_subbarayudu).</description>
    <link>https://dev.to/kasi_subbarayudu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2573717%2Fe7323dc4-128a-4a5a-bcd1-cdb5c59fb941.png</url>
      <title>DEV Community: Sri Sai Venkata Kasi Subbarayudu Kompella</title>
      <link>https://dev.to/kasi_subbarayudu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kasi_subbarayudu"/>
    <language>en</language>
    <item>
      <title>Signing Container Images with Cosign</title>
      <dc:creator>Sri Sai Venkata Kasi Subbarayudu Kompella</dc:creator>
      <pubDate>Mon, 08 Jun 2026 14:50:09 +0000</pubDate>
      <link>https://dev.to/kasi_subbarayudu/signing-container-images-with-cosign-5ec9</link>
      <guid>https://dev.to/kasi_subbarayudu/signing-container-images-with-cosign-5ec9</guid>
      <description>&lt;p&gt;When you pull a container image from a registry, how do you know it actually came from a trusted source? How do you know no one tampered with it between the time it was built and the time it landed on your node? &lt;/p&gt;

&lt;p&gt;This article walks through how image signing works, from the basics of supply chain security to hands-on cosign usage, and deep into the internals of key-based, keyless signing and GitHub Actions integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Supply Chain Problem
&lt;/h2&gt;

&lt;p&gt;When customers use your container image, they're placing a lot of trust in you. They're trusting that the software components included in the image are less vulnerable, that no one injected anything malicious, and that the image they're running is the exact artifact your pipeline built.&lt;/p&gt;

&lt;p&gt;Supply chain security is the practice of giving customers confidence in the integrity and provenance of your software. Integrity means the artifact hasn't been modified. Provenance means you can prove where it came from and how it was built.&lt;/p&gt;

&lt;p&gt;Two concepts we should be aware of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SBOM (Software Bill of Materials):&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;A manifest of every software component that went into building the artifact: packages, versions, licenses. You can generate one using Trivy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image &lt;span class="nt"&gt;--format&lt;/span&gt; spdx-json kasisubbarayudu/buildkittest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a JSON file that lists every package and its version in your image. If a CVE is dropped for a specific version of a software component, you can immediately know which of your images are affected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provenance:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is the metadata describing how the artifact was built, when, and by which system. Together with an SBOM, provenance lets you trace an artifact back to its exact build pipeline run.&lt;/p&gt;

&lt;p&gt;Generating this information is one thing. Making it tamper-proof and cryptographically verifiable is another. That's where signing comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Sign Images?
&lt;/h2&gt;

&lt;p&gt;Image signing directly addresses three supply chain risks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provenance:&lt;/strong&gt; prove that an image was produced by your CI/CD system, not an attacker who got write access to your registry.&lt;br&gt;
&lt;strong&gt;Integrity:&lt;/strong&gt; detect if the image was modified after it was signed (any change to the image changes its digest, which would invalidate the signature)&lt;br&gt;
&lt;strong&gt;Policy enforcement:&lt;/strong&gt; Use tools like Kyverno/OPA Gatekeeper/Sigstore Policy Controller to block unsigned or unverified images from being deployed to your cluster at all.&lt;/p&gt;

&lt;p&gt;In this article, we will use cosign to sign the images.&lt;/p&gt;
&lt;h2&gt;
  
  
  Key-Based Signing:
&lt;/h2&gt;

&lt;p&gt;In case of key-based signing, we generate a key pair to sign the image and manage the key pair ourselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generating a Keypair:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cosign generate-key-pair
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This generates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cosign.key:&lt;/strong&gt; your private key, encrypted with a password you choose&lt;br&gt;
&lt;strong&gt;cosign.pub:&lt;/strong&gt; your public key, which you distribute freely to anyone who needs to verify your images&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Signing an Image:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build and push your image first&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; myregistry.io/myapp:v1.0 &lt;span class="nb"&gt;.&lt;/span&gt;
docker push myregistry.io/myapp:v1.0

&lt;span class="c"&gt;# Sign it (will prompt for private key password)&lt;/span&gt;
cosign sign &lt;span class="nt"&gt;--key&lt;/span&gt; cosign.key myregistry.io/myapp:v1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail worth noting: cosign doesn't modify the image itself. The signature is stored as a separate artifact in the same registry, at a tag derived from the image digest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myregistry.io/myapp:sha256-&amp;lt;digest&amp;gt;.sig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original image is untouched, and the signature lives alongside it in the registry without requiring any special infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verifying a Signature:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cosign verify &lt;span class="nt"&gt;--key&lt;/span&gt; cosign.pub myregistry.io/myapp:v1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On success, cosign confirms the signature is valid and matches the public key. If the image has been tampered with or was never signed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cosign verify &lt;span class="nt"&gt;--key&lt;/span&gt; cosign.pub myregistry.io/malicious:v1.0
&lt;span class="c"&gt;# Error: no matching signatures&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Problem with Key-Based Signing:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Key-based signing works, but it comes with an operational burden: you're now running your own PKI. You need to manage the private key securely, rotate it, distribute the public key, handle revocation if the private key is compromised, and ensure the key isn't lost. For small teams, this can be manageable, but at scale, it becomes another piece of infrastructure to worry about.&lt;br&gt;
This is the problem keyless signing was designed to solve.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keyless Signing with Sigstore:
&lt;/h2&gt;

&lt;p&gt;Keyless signing lets you sign artifacts without ever managing a long-lived private key. Instead of you holding the key, the signing key is ephemeral i.e it exists only for the duration of the signing operation and is then discarded. Identity is proven through OIDC.&lt;/p&gt;

&lt;p&gt;The Sigstore ecosystem has three components that make this work:&lt;/p&gt;

&lt;p&gt;Fulcio: a certificate authority that issues short-lived signing certificates&lt;br&gt;
Rekor: an immutable, append-only transparency log that records all signatures&lt;br&gt;
An OIDC provider: your identity source (Google, GitHub, etc.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Keyless Signing Flow (Step by Step):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example when run cosign sign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cosign sign kasisubbarayudu/buildkittest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1: Ephemeral keypair generation&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Cosign generates a keypair entirely in memory. The private key never touches disk. It exists only for this one signing operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: OIDC authentication&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cosign opens a browser and redirects you to the Sigstore OIDC broker (Dex), where you choose your identity provider, Google, GitHub, Microsoft, etc. You log in there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; cosign sign kasisubbarayudu/buildkittest
WARNING: Image reference kasisubbarayudu/buildkittest uses a tag, not a digest, to identify the image to sign.
    This can lead you to sign a different image than the intended one. Please use a
    digest &lt;span class="o"&gt;(&lt;/span&gt;example.com/ubuntu@sha256:abc123...&lt;span class="o"&gt;)&lt;/span&gt; rather than tag
    &lt;span class="o"&gt;(&lt;/span&gt;example.com/ubuntu:latest&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;the input to cosign. The ability to refer to
    images by tag will be removed &lt;span class="k"&gt;in &lt;/span&gt;a future release.

Generating ephemeral keys...

    The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
    Note that &lt;span class="k"&gt;if &lt;/span&gt;your submission includes personal data associated with this signed artifact, it will be part of an immutable record.
    This may include the email address associated with the account with which you authenticate your contractual Agreement.
    This information will be used &lt;span class="k"&gt;for &lt;/span&gt;signing this artifact and will be stored &lt;span class="k"&gt;in &lt;/span&gt;public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.

By typing &lt;span class="s1"&gt;'y'&lt;/span&gt;, you attest that &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt; you are not submitting the personal data of any other person&lt;span class="p"&gt;;&lt;/span&gt; and &lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt; you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to &lt;span class="k"&gt;continue&lt;/span&gt;? &lt;span class="o"&gt;[&lt;/span&gt;y/N] y
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type&lt;span class="o"&gt;=&lt;/span&gt;online&amp;amp;client_id&lt;span class="o"&gt;=&lt;/span&gt;sigstore&amp;amp;code_challenge&lt;span class="o"&gt;=&lt;/span&gt;plxge1Pb05zqWvd8VWJrQjKUlWK1ZrKm0hCk9BT1iZE&amp;amp;code_challenge_method&lt;span class="o"&gt;=&lt;/span&gt;S256&amp;amp;nonce&lt;span class="o"&gt;=&lt;/span&gt;TtZVuGy75XjszlKtZgAw7Q&amp;amp;redirect_uri&lt;span class="o"&gt;=&lt;/span&gt;http%3A%2F%2Flocalhost%3A39883%2Fauth%2Fcallback&amp;amp;response_type&lt;span class="o"&gt;=&lt;/span&gt;code&amp;amp;scope&lt;span class="o"&gt;=&lt;/span&gt;openid+email&amp;amp;state&lt;span class="o"&gt;=&lt;/span&gt;C4INaDJe_sTR7DOpJLWr6g
Opening &lt;span class="k"&gt;in &lt;/span&gt;existing browser session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1p8yiumyx79ywt8791w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1p8yiumyx79ywt8791w.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ephemeral keys are generated and kept in memory. As shown in the URL above, the link opens in the browser and connects to the auth endpoint of the DEX intermediate OIDC server. The response type is &lt;code&gt;code&lt;/code&gt;, which means it uses the authorization code grant type with PKCE. Since Cosign is not a server-side application and runs on the user's device, it is not secure to store or embed the client ID or client secret in the Cosign binary. That is why the authorization code grant type with PKCE is used. In OIDC terms, cosign is considered a public client. OpenID scopes are specified to get more information about the user. Because these scopes are included, an ID token is returned along with the access token.&lt;/p&gt;

&lt;p&gt;Here's something interesting: if you watch the browser URL carefully when you click "Sign in with Google," you'll notice the request goes to accounts.google.com, but the client_id belongs to Sigstore, and the redirect_uri points back to oauth2.sigstore.dev, not to your machine or cosign.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://accounts.google.com/v3/signin/accountchooser?client_id=237800849078-hri2ndt7gdafpf34kq8crd5sik9pe3so.apps.googleusercontent.com&amp;amp;redirect_uri=https%3A%2F%2Foauth2.sigstore.dev%2Fauth%2Fcallback&amp;amp;response_type=code&amp;amp;scope=openid+email&amp;amp;state=g57y3w6qy5ryk3u3q6bqekksp&amp;amp;dsh=S17075643%3A1774614974239725&amp;amp;o2v=2&amp;amp;service=lso&amp;amp;flowName=GeneralOAuthFlow&amp;amp;opparams=%253F&amp;amp;continue=https%3A%2F%2Faccounts.google.com%2Fsignin%2Foauth%2Fconsent%3Fauthuser%3Dunknown%26part%3DAJi8hANxu7AUaB6qU0RLCkwfiTl4NHKUzRTWxKZCL42fnEeigK80dX0JK9s3Uyi77N_lbNkMGEOjXsPmcD4C-3KKFFO1tgfnxveQAeqkz16cf8SWqwIrmMpMNBQieuwTqCWPgZf8SvkXEQ1c1mLhdHoarm438kgSAQINgb-GfWuvpu0DJKs9UoYyylZ2h0_h1Mk5mCZWmbEhm7Y8tAQjXe4pWfJiPZdsgld2Fuc-jADHCJnaypVIhVP4gCdSzlaOfdpl6oalYkJW5jtApJSExnjDIuuouSgksjbdw0_npecXC7hvHX5FDDjB6TCvcFo2q6SE8k8Lr2Ucj7zRWKmql3k7wdQvCZuUzaTLcM4-DucAvqDFuvbwhXwnCQzD2uSAwHb_VmRX5doPPW-aDY2juK0Y6P4lXfbwwttGaFupmCSvQIN8VjJuYdC43fTttfDD8M13CVZIddKqRicSrU5wPSpMYpRYXUysqg%26flowName%3DGeneralOAuthFlow%26as%3DS17075643%253A1774614974239725%26client_id%3D237800849078-hri2ndt7gdafpf34kq8crd5sik9pe3so.apps.googleusercontent.com%26requestPath%3D%252Fsignin%252Foauth%252Fconsent%23&amp;amp;app_domain=https%3A%2F%2Foauth2.sigstore.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;client_id=237800849078-hri2ndt7gdafpf34kq8crd5sik9pe3so.apps.googleusercontent.com&lt;/code&gt;, This is Sigstore's own Google OAuth app ID. Sigstore registered itself as an app with Google. So Google sees Sigstore as the client, not you, not cosign.&lt;/p&gt;

&lt;p&gt;After successful login:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnr8fhv8sw7yj7bazrapl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnr8fhv8sw7yj7bazrapl.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many popular identity providers don't allow arbitrary values in the aud claim. For instance, Google OIDC populates the value from a registered OAuth 2.0 application's client ID and doesn't allow user-customizable values at all. Facebook and Microsoft do the same.&lt;/p&gt;

&lt;p&gt;This is the core reason Dex exists. Fulcio needs to receive a token with aud=sigstore so it knows the token was intended for it. But Google will never let you set that audience claim, it's locked to Sigstore's own registered client ID. So Dex acts as the intermediary: your Google token goes to Dex (as Sigstore's registered Google OAuth client), Dex re-issues a new token with aud=sigstore, and that's what gets sent to Fulcio.&lt;/p&gt;

&lt;p&gt;Once you authenticate, an OIDC id_token is produced and flows through this chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google → Sigstore OAuth proxy → Cosign
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cosign also runs a small local HTTP server on localhost to receive the OAuth callback (the same pattern you'd see if you've ever implemented OIDC yourself). It uses the authorization code + PKCE flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Certificate issuance from Fulcio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cosign sends three things to Fulcio:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The OIDC id_token&lt;/li&gt;
&lt;li&gt;The ephemeral public key&lt;/li&gt;
&lt;li&gt;A signed challenge, a signature of the sub claim of the OIDC token, proving the ownership of the private key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fulcio validates the OIDC token (checking it was actually signed by the OIDC provider), then proves that Cosign holds the private key by verifying the signature of the sub claim. Once both checks pass, Fulcio issues a short-lived X.509 certificate using its own CA, and this certificate is typically valid for 10 minutes, which binds your identity (the email from the OIDC token) to the ephemeral public key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Signing and uploading&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cosign takes the image digest (the sha256 hash), signs it with the ephemeral private key, and then uploads the signature, the certificate, and the transparency log entry to Rekor. The signature is also uploaded to the image registry (.sig artifact). After this, the private key is discarded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Verification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When anyone runs cosign verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cosign fetches the .sig artifact from the registry.
Cosign fetches the list of artifacts, such as SBOM, signature, and any other attestations attached to the image. Behind the scenes, it uses the referrers api of Docker. Among the returned artifacts, it tries to find the one that matches the &lt;code&gt;artifactType: application/vnd.dev.sigstore.bundle.v0.3+json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ex: for image &lt;code&gt;dockerhub.io/kasisubbarayudu/buildkittest&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://registry-1.docker.io/v2/kasisubbarayudu/buildkittest/referrers/sha256:20436a2b1237a073a1d8035342065b767923e7b884a41b049da8ca57f0ce7df2"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/vnd.oci.image.manifest.v1+json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://auth.docker.io/token?service=registry.docker.io&amp;amp;scope=repository:kasisubbarayudu/buildkittest:pull'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nb"&gt;.&lt;/span&gt;                                                   
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"schemaVersion"&lt;/span&gt;: 2,
  &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.oci.image.index.v1+json"&lt;/span&gt;,
  &lt;span class="s2"&gt;"manifests"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.oci.image.manifest.v1+json"&lt;/span&gt;,
      &lt;span class="s2"&gt;"size"&lt;/span&gt;: 889,
      &lt;span class="s2"&gt;"digest"&lt;/span&gt;: &lt;span class="s2"&gt;"sha256:07dc49a44fb2ef30e67e2f50cf5485ae12b1b62ddb91cfc1fb24d9f604bd7689"&lt;/span&gt;,
      &lt;span class="s2"&gt;"annotations"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"dev.sigstore.bundle.content"&lt;/span&gt;: &lt;span class="s2"&gt;"dsse-envelope"&lt;/span&gt;,
        &lt;span class="s2"&gt;"dev.sigstore.bundle.predicateType"&lt;/span&gt;: &lt;span class="s2"&gt;"https://sigstore.dev/cosign/sign/v1"&lt;/span&gt;,
        &lt;span class="s2"&gt;"org.opencontainers.image.created"&lt;/span&gt;: &lt;span class="s2"&gt;"2026-06-08T07:05:26Z"&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="s2"&gt;"artifactType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.dev.sigstore.bundle.v0.3+json"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the manifests we see one entry as other attestation for ex SBOM is not attached and only signature is attached.&lt;/p&gt;

&lt;p&gt;You can use docker's manifest API, to get detailed info about the manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://registry-1.docker.io/v2/kasisubbarayudu/buildkittest/manifests/sha256:07dc49a44fb2ef30e67e2f50cf5485ae12b1b62ddb91cfc1fb24d9f604bd7689"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Accept: application/vnd.oci.image.manifest.v1+json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://auth.docker.io/token?service=registry.docker.io&amp;amp;scope=repository:kasisubbarayudu/buildkittest:pull'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"schemaVersion"&lt;/span&gt;: 2,
  &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.oci.image.manifest.v1+json"&lt;/span&gt;,
  &lt;span class="s2"&gt;"config"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.oci.empty.v1+json"&lt;/span&gt;,
    &lt;span class="s2"&gt;"size"&lt;/span&gt;: 2,
    &lt;span class="s2"&gt;"digest"&lt;/span&gt;: &lt;span class="s2"&gt;"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"&lt;/span&gt;,
    &lt;span class="s2"&gt;"artifactType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.dev.sigstore.bundle.v0.3+json"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"layers"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.dev.sigstore.bundle.v0.3+json"&lt;/span&gt;,
      &lt;span class="s2"&gt;"size"&lt;/span&gt;: 6867,
      &lt;span class="s2"&gt;"digest"&lt;/span&gt;: &lt;span class="s2"&gt;"sha256:4e5a76ee7871b1349693013c23483305239cacd9e2e9b838aa6988a39ce65e3c"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;,
  &lt;span class="s2"&gt;"annotations"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"dev.sigstore.bundle.content"&lt;/span&gt;: &lt;span class="s2"&gt;"dsse-envelope"&lt;/span&gt;,
    &lt;span class="s2"&gt;"dev.sigstore.bundle.predicateType"&lt;/span&gt;: &lt;span class="s2"&gt;"https://sigstore.dev/cosign/sign/v1"&lt;/span&gt;,
    &lt;span class="s2"&gt;"org.opencontainers.image.created"&lt;/span&gt;: &lt;span class="s2"&gt;"2026-06-08T07:05:26Z"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"subject"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.docker.distribution.manifest.v2+json"&lt;/span&gt;,
    &lt;span class="s2"&gt;"size"&lt;/span&gt;: 2406,
    &lt;span class="s2"&gt;"digest"&lt;/span&gt;: &lt;span class="s2"&gt;"sha256:20436a2b1237a073a1d8035342065b767923e7b884a41b049da8ca57f0ce7df2"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"artifactType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.dev.sigstore.bundle.v0.3+json"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the output, layers list contains one layer, whose sha value is &lt;code&gt;"sha256:4e5a76ee7871b1349693013c23483305239cacd9e2e9b838aa6988a39ce65e3c"&lt;/code&gt; and is of type &lt;code&gt;application/vnd.dev.sigstore.bundle.v0.3+json&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;And if u try to get this blob using blob api:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://auth.docker.io/token?service=registry.docker.io&amp;amp;scope=repository:kasisubbarayudu/buildkittest:pull'&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://registry-1.docker.io/v2/kasisubbarayudu/buildkittest/blobs/sha256:4e5a76ee7871b1349693013c23483305239cacd9e2e9b838aa6988a39ce65e3c"&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:-- &lt;span class="nt"&gt;--&lt;/span&gt;:--:--     0
100  6867  100  6867    0     0   6119      0  0:00:01  0:00:01 &lt;span class="nt"&gt;--&lt;/span&gt;:--:--  6119
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"mediaType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.dev.sigstore.bundle.v0.3+json"&lt;/span&gt;,
  &lt;span class="s2"&gt;"verificationMaterial"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"certificate"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"rawBytes"&lt;/span&gt;: &lt;span class="s2"&gt;"MIIDMDCCArWgAwIBAgIUDQfrQyDE+a0p1AGOeILRzKPdoFswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjA4MDcwNTI1WhcNMjYwNjA4MDcxNTI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwmo8XT1QN0I66R6shMuxGVfiS4/DKjrUB38+5OxC1fE1r/VCa2dt/hvDXO+MhmaKjooAw40Fuk5ePyvlzbeuWqOCAdQwggHQMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUN3GvpnBI8TR3+HPngQojmxxAkP4wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wJwYDVR0RAQH/BB0wG4EZa2FzaXJhbWtvbXBlbGxhQGdtYWlsLmNvbTApBgorBgEEAYO/MAEBBBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wKwYKKwYBBAGDvzABCAQdDBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wWwYKKwYBBAGDvzABGARNDEtDaFV4TURBMk16WTBPVGc1TWpReE1qQXpOVE0wT0RjU0gyaDBkSEJ6T2lVeVJpVXlSbUZqWTI5MWJuUnpMbWR2YjJkc1pTNWpiMjAwgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZ6mDOyqAAAEAwBHMEUCIQCpgLPskeYhfJtvkTZYKEDm+Wvr1wbE6r0GYxA7YAe1igIgNWMh/QYATTjbIUB35BJivXA3ptzlwZAOR9si3uhkRqswCgYIKoZIzj0EAwMDaQAwZgIxALMQKyyQXlXc1cwBbhgakQwoBgbIF0eftEEmIkzclOotX8j6v79D0jkbPfmVDAr+MAIxALya8kp3WQarAgotAasne32EQcBm8MFEI4ClIyMoyaKUkzFYqkUYfBwX6IkdXNS/HA=="&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="s2"&gt;"tlogEntries"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"logIndex"&lt;/span&gt;: &lt;span class="s2"&gt;"1754783759"&lt;/span&gt;,
        &lt;span class="s2"&gt;"logId"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"keyId"&lt;/span&gt;: &lt;span class="s2"&gt;"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"kindVersion"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"kind"&lt;/span&gt;: &lt;span class="s2"&gt;"dsse"&lt;/span&gt;,
          &lt;span class="s2"&gt;"version"&lt;/span&gt;: &lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"integratedTime"&lt;/span&gt;: &lt;span class="s2"&gt;"1780902326"&lt;/span&gt;,
        &lt;span class="s2"&gt;"inclusionPromise"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"signedEntryTimestamp"&lt;/span&gt;: &lt;span class="s2"&gt;"MEQCIEeby7uJfKo1ZqR/Z8clj8942TRqn9FeZtqWnoLZOBMtAiBYJBPc1Hkffz12y5d9l0sLQbu0ZZQ2NOB38I6gMAsWFQ=="&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"inclusionProof"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"logIndex"&lt;/span&gt;: &lt;span class="s2"&gt;"1632879497"&lt;/span&gt;,
          &lt;span class="s2"&gt;"rootHash"&lt;/span&gt;: &lt;span class="s2"&gt;"hQVgleW4096bWB5nMVA4euGOFNXspkZM7fkwOU0DRTo="&lt;/span&gt;,
          &lt;span class="s2"&gt;"treeSize"&lt;/span&gt;: &lt;span class="s2"&gt;"1632879500"&lt;/span&gt;,
          &lt;span class="s2"&gt;"hashes"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
            &lt;span class="s2"&gt;"XHIMw0aYbwSRZIDloHHhezy6eFMxDuBywnaCAEZ/hCE="&lt;/span&gt;,
            &lt;span class="s2"&gt;"47B8Brg1p3ZJ5Q20oIZDiAdFEs/Eg1bJ4T9EkyqGnwU="&lt;/span&gt;,
            &lt;span class="s2"&gt;"PCTg9y1uHHEyzk0yrF+0KIvYlf739uZWx8mLPkD4qWM="&lt;/span&gt;,
            &lt;span class="s2"&gt;"IG3DXh2vVc0tB/VXEUb49zN6VKXFgDbkYFjSbEoLnis="&lt;/span&gt;,
            &lt;span class="s2"&gt;"/6gQW6oyjPBJt9hHaloGnKe/cqwTgDP+ehCA17xJDnE="&lt;/span&gt;,
            &lt;span class="s2"&gt;"4OW3KC98wj7ppZzIz78pbAZfUPaNQOFcX/+X1RWAqEk="&lt;/span&gt;,
            &lt;span class="s2"&gt;"rrZJWna+MzeY9FZ/DOBhYRvAmyLC+usz7+CSc7D6ABA="&lt;/span&gt;,
            &lt;span class="s2"&gt;"EqM3uHc9tMy5EM7eybA0+bnEzNFb6bYf02wL+9dv7qc="&lt;/span&gt;,
            &lt;span class="s2"&gt;"AKwEP2HH5SGxOZY6n7sb0sQ8/KMJRFtSn3nT/co5fZY="&lt;/span&gt;,
            &lt;span class="s2"&gt;"RjW1XPhqoinfsobGd20S3gJis0k6WUSEpjaQ6JXQ5FI="&lt;/span&gt;,
            &lt;span class="s2"&gt;"BhdMVwa7xxwCWC//5qOnv/wqs69cjw8nHrPed7oJkAI="&lt;/span&gt;,
            &lt;span class="s2"&gt;"9jo43+euK7XyKWxBn2h5BwzuDA8PaUq2geJ8WGWycpQ="&lt;/span&gt;,
            &lt;span class="s2"&gt;"x5jI42lOBdSMG7JZOSaglWmLNIaPCZTz6pXBlb7ZgYQ="&lt;/span&gt;,
            &lt;span class="s2"&gt;"daxmZaajRpZV+JxHiOYZhJBiSKN5ucqjh2WnGbHhirw="&lt;/span&gt;,
            &lt;span class="s2"&gt;"DOCeoSMovIvLExkhIvisow9AuNXgeWs4ECkyR6EcqYU="&lt;/span&gt;
          &lt;span class="o"&gt;]&lt;/span&gt;,
          &lt;span class="s2"&gt;"checkpoint"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"envelope"&lt;/span&gt;: &lt;span class="s2"&gt;"rekor.sigstore.dev - 1193050959916656506&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;1632879500&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;hQVgleW4096bWB5nMVA4euGOFNXspkZM7fkwOU0DRTo=&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;— rekor.sigstore.dev wNI9ajBGAiEApRMPDWfsFq4rhLkDJBbREL2Wc5ZAqxWaGMylgm23WfUCIQDsyP5tohOidsES6Hb9KWdS0xzaoPzuMIPslnSS010XhA==&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
          &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"canonicalizedBody"&lt;/span&gt;: &lt;span class="s2"&gt;"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiOTE5ZjYyNzM3OWZiYWM3NjUxODc5ZWRiZTdhN2NhMDM2OTVjODA4YjFkODk2MThmNjhhNjNlM2VmNDY5OTAyZCJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImU1NDI4OTc5NGFkZGQyYWU5NDVkODc2NDY0MzRiMTMzZjE1MjU0ODFiMDUxMTAzZmIyN2YxNDQxYWU3NjY0YzkifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVVQ0lDTmFzY3N1MTNtcGRjTG5ET21KMkNEc25TYW9QbHFSR3RGOFlyMjBsVkZwQWlFQXZ1L3RCVFArWTJuOFlyYXRnUFoveE5ZSkc2RWpNaGoxckQ3VU9pdlNVRjg9IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VSTlJFTkRRWEpYWjBGM1NVSkJaMGxWUkZGbWNsRjVSRVVyWVRCd01VRkhUMlZKVEZKNlMxQmtiMFp6ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwWmQwNXFRVFJOUkdOM1RsUkpNVmRvWTA1TmFsbDNUbXBCTkUxRVkzaE9WRWt4VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVjNiVzg0V0ZReFVVNHdTVFkyVWpaemFFMTFlRWRXWm1sVE5DOUVTMnB5VlVJek9Dc0tOVTk0UXpGbVJURnlMMVpEWVRKa2RDOW9ka1JZVHl0TmFHMWhTMnB2YjBGM05EQkdkV3MxWlZCNWRteDZZbVYxVjNGUFEwRmtVWGRuWjBoUlRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVk9NMGQyQ25CdVFrazRWRkl6SzBoUWJtZFJiMnB0ZUhoQmExQTBkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMHAzV1VSV1VqQlNRVkZJTDBKQ01IZEhORVZhWVRKR2VtRllTbWhpVjNSMllsaENiR0pIZUdoUlIyUjBXVmRzYzB4dFRuWmlWRUZ3UW1kdmNncENaMFZGUVZsUEwwMUJSVUpDUW5SdlpFaFNkMk42YjNaTU1rWnFXVEk1TVdKdVVucE1iV1IyWWpKa2MxcFROV3BpTWpCM1MzZFpTMHQzV1VKQ1FVZEVDblo2UVVKRFFWRmtSRUowYjJSSVVuZGplbTkyVERKR2Fsa3lPVEZpYmxKNlRHMWtkbUl5WkhOYVV6VnFZakl3ZDFkM1dVdExkMWxDUWtGSFJIWjZRVUlLUjBGU1RrUkZkRVJoUmxZMFZGVlNRazFyTVRaWFZFSlFWa2RqTVZSWGNGSmxSVEZ4VVZod1QxWkZNSGRVTUZKcVZUQm5lV0ZFUW10VFJVbzJWREpzVmdwbFZrcHdWbGhzVTJKVlduRlhWRWsxVFZkS2RWVnVjRTFpVjFJeVdXcEthMk14Y0ZST1YzQnBUV3BCZDJkWmIwZERhWE5IUVZGUlFqRnVhME5DUVVsRkNtWkJValpCU0dkQlpHZEVaRkJVUW5GNGMyTlNUVzFOV2tob2VWcGFlbU5EYjJ0d1pYVk9ORGh5Wml0SWFXNUxRVXg1Ym5WcVowRkJRVm8yYlVSUGVYRUtRVUZCUlVGM1FraE5SVlZEU1ZGRGNHZE1VSE5yWlZsb1prcDBkbXRVV2xsTFJVUnRLMWQyY2pGM1lrVTJjakJIV1hoQk4xbEJaVEZwWjBsblRsZE5hQW92VVZsQlZGUnFZa2xWUWpNMVFrcHBkbGhCTTNCMGVteDNXa0ZQVWpsemFUTjFhR3RTY1hOM1EyZFpTVXR2V2tsNmFqQkZRWGROUkdGUlFYZGFaMGw0Q2tGTVRWRkxlWGxSV0d4WVl6RmpkMEppYUdkaGExRjNiMEpuWWtsR01HVm1kRVZGYlVscmVtTnNUMjkwV0RocU5uWTNPVVF3YW10aVVHWnRWa1JCY2lzS1RVRkplRUZNZVdFNGEzQXpWMUZoY2tGbmIzUkJZWE51WlRNeVJWRmpRbTA0VFVaRlNUUkRiRWw1VFc5NVlVdFZhM3BHV1hGclZWbG1RbmRZTmtsclpBcFlUbE12U0VFOVBRb3RMUzB0TFVWT1JDQkRSVkpVU1VaSlEwRlVSUzB0TFMwdENnPT0ifV19fQ=="&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;,
    &lt;span class="s2"&gt;"timestampVerificationData"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"rfc3161Timestamps"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"signedTimestamp"&lt;/span&gt;: &lt;span class="s2"&gt;"MIICyTADAgEAMIICwAYJKoZIhvcNAQcCoIICsTCCAq0CAQMxDTALBglghkgBZQMEAgEwgbgGCyqGSIb3DQEJEAEEoIGoBIGlMIGiAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgu2VISVNbYk+VQZDi6yCnss5LGGVpFuknPHtFsktL4OECFQD6OOcltBBcOCkve+XSgeOBgmWURBgPMjAyNjA2MDgwNzA1MjVaMAMCAQGgMqQwMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhoAAxggHaMIIB1gIBATBRMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAsGCWCGSAFlAwQCAaCB/DAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDYwODA3MDUyNVowLwYJKoZIhvcNAQkEMSIEIAZYYA1ib9CRgqXzK1RqCxC+FINsWTmm2Bd5EOH9v7n6MIGOBgsqhkiG9w0BCRACLzF/MH0wezB5BCCF+Se8B6tiysO0Q1bBDvyBssaIP9p6uebYcNnROs0FtzBVMD2kOzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQ6E1QvDJBh7rzBQy/Lio6LKiOLDDAKBggqhkjOPQQDAgRmMGQCMBspPBil7OP6OoSS1pPrreov6kqG/blObbEfqvdkcqwlet9pKNcGkrm/1AdgPu0fsAIwfUfRv0eF497BMcGmeO1SEmBxXMWj9VJQt0kp+IYAB+8wSD0n3o8wmY+sQ2FFczSL"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"dsseEnvelope"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"payload"&lt;/span&gt;: &lt;span class="s2"&gt;"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCAic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6IjIwNDM2YTJiMTIzN2EwNzNhMWQ4MDM1MzQyMDY1Yjc2NzkyM2U3Yjg4NGE0MWIwNDlkYThjYTU3ZjBjZTdkZjIifSwgImFubm90YXRpb25zIjp7fX1dLCAicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2lnc3RvcmUuZGV2L2Nvc2lnbi9zaWduL3YxIiwgInByZWRpY2F0ZSI6e319"&lt;/span&gt;,
    &lt;span class="s2"&gt;"payloadType"&lt;/span&gt;: &lt;span class="s2"&gt;"application/vnd.in-toto+json"&lt;/span&gt;,
    &lt;span class="s2"&gt;"signatures"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"sig"&lt;/span&gt;: &lt;span class="s2"&gt;"MEUCICNascsu13mpdcLnDOmJ2CDsnSaoPlqRGtF8Yr20lVFpAiEAvu/tBTP+Y2n8YratgPZ/xNYJG6EjMhj1rD7UOivSUF8="&lt;/span&gt;
      &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns the JSON response since the layer mediaType was &lt;code&gt;application/vnd.dev.sigstore.bundle.v0.3+json&lt;/code&gt; json.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;cosign verify&lt;/code&gt; is run, for ex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cosign verify kasisubbarayudu/buildkittest   &lt;span class="nt"&gt;--certificate-oidc-issuer&lt;/span&gt; https://accounts.google.com   &lt;span class="nt"&gt;--certificate-identity&lt;/span&gt;  kasisubbarayudu@gmail.com 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cosign does something similar: it fetches the JSON bundle from the signature artifact, then decodes the RAW certificate bytes, then extracts the Subject Alternative Name, checks whether it matches the --certificate-identity.&lt;/p&gt;

&lt;p&gt;Decoded cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"MIIDMDCCArWgAwIBAgIUDQfrQyDE+a0p1AGOeILRzKPdoFswCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYwNjA4MDcwNTI1WhcNMjYwNjA4MDcxNTI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwmo8XT1QN0I66R6shMuxGVfiS4/DKjrUB38+5OxC1fE1r/VCa2dt/hvDXO+MhmaKjooAw40Fuk5ePyvlzbeuWqOCAdQwggHQMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUN3GvpnBI8TR3+HPngQojmxxAkP4wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wJwYDVR0RAQH/BB0wG4EZa2FzaXJhbWtvbXBlbGxhQGdtYWlsLmNvbTApBgorBgEEAYO/MAEBBBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wKwYKKwYBBAGDvzABCAQdDBtodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20wWwYKKwYBBAGDvzABGARNDEtDaFV4TURBMk16WTBPVGc1TWpReE1qQXpOVE0wT0RjU0gyaDBkSEJ6T2lVeVJpVXlSbUZqWTI5MWJuUnpMbWR2YjJkc1pTNWpiMjAwgYoGCisGAQQB1nkCBAIEfAR6AHgAdgDdPTBqxscRMmMZHhyZZzcCokpeuN48rf+HinKALynujgAAAZ6mDOyqAAAEAwBHMEUCIQCpgLPskeYhfJtvkTZYKEDm+Wvr1wbE6r0GYxA7YAe1igIgNWMh/QYATTjbIUB35BJivXA3ptzlwZAOR9si3uhkRqswCgYIKoZIzj0EAwMDaQAwZgIxALMQKyyQXlXc1cwBbhgakQwoBgbIF0eftEEmIkzclOotX8j6v79D0jkbPfmVDAr+MAIxALya8kp3WQarAgotAasne32EQcBm8MFEI4ClIyMoyaKUkzFYqkUYfBwX6IkdXNS/HA=="&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | openssl x509 &lt;span class="nt"&gt;-inform&lt;/span&gt; DER &lt;span class="nt"&gt;-text&lt;/span&gt; &lt;span class="nt"&gt;-noout&lt;/span&gt; 
Certificate:
    Data:
        Version: 3 &lt;span class="o"&gt;(&lt;/span&gt;0x2&lt;span class="o"&gt;)&lt;/span&gt;
        Serial Number:
            0d:07:eb:43:20:c4:f9:ad:29:d4:01:8e:78:82:d1:cc:a3:dd:a0:5b
        Signature Algorithm: ecdsa-with-SHA384
        Issuer: O &lt;span class="o"&gt;=&lt;/span&gt; sigstore.dev, CN &lt;span class="o"&gt;=&lt;/span&gt; sigstore-intermediate
        Validity
            Not Before: Jun  8 07:05:25 2026 GMT
            Not After : Jun  8 07:15:25 2026 GMT
        Subject: 
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: &lt;span class="o"&gt;(&lt;/span&gt;256 bit&lt;span class="o"&gt;)&lt;/span&gt;
                pub:
                    04:c2:6a:3c:5d:3d:50:37:42:3a:e9:1e:ac:84:cb:
                    b1:19:57:e2:4b:8f:c3:2a:3a:d4:07:7f:3e:e4:ec:
                    42:d5:f1:35:af:f5:42:6b:67:6d:fe:1b:c3:5c:ef:
                    8c:86:66:8a:8e:8a:00:c3:8d:05:ba:4e:5e:3f:2b:
                    e5:cd:b7:ae:5a
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage: 
                Code Signing
            X509v3 Subject Key Identifier: 
                37:71:AF:A6:70:48:F1:34:77:F8:73:E7:81:0A:23:9B:1C:40:90:FE
            X509v3 Authority Key Identifier: 
                DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F
            X509v3 Subject Alternative Name: critical
                email:kasisubbarayudu@gmail.com
            1.3.6.1.4.1.57264.1.1: 
                https://accounts.google.com
            1.3.6.1.4.1.57264.1.8: 
                ..https://accounts.google.com
            1.3.6.1.4.1.57264.1.24: 
                .KChUxMDA2MzY0OTg5MjQxMjAzNTM0ODcSH2h0dHBzOiUyRiUyRmFjY291bnRzLmdvb2dsZS5jb20
            CT Precertificate SCTs: 
                Signed Certificate Timestamp:
                    Version   : v1 &lt;span class="o"&gt;(&lt;/span&gt;0x0&lt;span class="o"&gt;)&lt;/span&gt;
                    Log ID    : DD:3D:30:6A:C6:C7:11:32:63:19:1E:1C:99:67:37:02:
                                A2:4A:5E:B8:DE:3C:AD:FF:87:8A:72:80:2F:29:EE:8E
                    Timestamp : Jun  8 07:05:25.418 2026 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:45:02:21:00:A9:80:B3:EC:91:E6:21:7C:9B:6F:91:
                                36:58:28:40:E6:F9:6B:EB:D7:06:C4:EA:BD:06:63:10:
                                3B:60:07:B5:8A:02:20:35:63:21:FD:06:00:4D:38:DB:
                                21:40:77:E4:12:62:BD:70:37:A6:DC:E5:C1:90:0E:47:
                                DB:22:DE:E8:64:46:AB
    Signature Algorithm: ecdsa-with-SHA384
    Signature Value:
        30:66:02:31:00:b3:10:2b:2c:90:5e:55:dc:d5:cc:01:6e:18:
        1a:91:0c:28:06:06:c8:17:47:9f:b4:41:26:22:4c:dc:94:ea:
        2d:5f:c8:fa:bf:bf:43:d2:39:1b:3d:f9:95:0c:0a:fe:30:02:
        31:00:bc:9a:f2:4a:77:59:06:ab:02:0a:2d:01:ab:27:7b:7d:
        84:41:c0:66:f0:c1:44:23:80:a5:23:23:28:c9:a2:94:93:31:
        58:aa:45:18:7c:1c:17:e8:89:1d:5c:d4:bf:1c

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also checks if issuer matches &lt;code&gt;--certificate-oidc-issuer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It also verifies if the certificate is really signed by the Fulcio CA.&lt;/p&gt;

&lt;p&gt;Most importantly, it validates the signature of the image. It decodes the dsseEnvelope.payload from the JSON response. This payload contains the image digest that was signed.&lt;/p&gt;

&lt;p&gt;In our case, for ex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCAic3ViamVjdCI6W3siZGlnZXN0Ijp7InNoYTI1NiI6IjIwNDM2YTJiMTIzN2EwNzNhMWQ4MDM1MzQyMDY1Yjc2NzkyM2U3Yjg4NGE0MWIwNDlkYThjYTU3ZjBjZTdkZjIifSwgImFubm90YXRpb25zIjp7fX1dLCAicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vc2lnc3RvcmUuZGV2L2Nvc2lnbi9zaWduL3YxIiwgInByZWRpY2F0ZSI6e319"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;base&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://in-toto.io/Statement/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"20436a2b1237a073a1d8035342065b767923e7b884a41b049da8ca57f0ce7df2"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"annotations"&lt;/span&gt;&lt;span class="p"&gt;:{}}],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"predicateType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://sigstore.dev/cosign/sign/v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"predicate"&lt;/span&gt;&lt;span class="p"&gt;:{}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then it validates the signature dsseEnvelope.signatures.sig  using public key from certificate.&lt;/p&gt;

&lt;p&gt;Here, the certificate is valid for 10 minutes only. The Rekor log entry is the durable record, not the certificate's validity period.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyless Signing in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;The flow above works for human-initiated signing from a laptop. But in CI/CD pipelines, there's no browser, no human to click through an OAuth flow. This is where GitHub Actions' OIDC integration comes in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Works?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub Actions can generate OIDC tokens for workflow runs. These tokens identify the workflow itself, the repository, the ref, the workflow file path, rather than a human email address. To enable this, your workflow needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The id-token: write permission tells GitHub to inject two environment variables into the runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ACTIONS_ID_TOKEN_REQUEST_URL: the GitHub OIDC endpoint
ACTIONS_ID_TOKEN_REQUEST_TOKEN: a bearer token to authenticate with that endpoint (not the final OIDC token itself — this is a common point of confusion)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When cosign runs in the pipeline, it internally does something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET &lt;span class="nv"&gt;$ACTIONS_ID_TOKEN_REQUEST_URL&lt;/span&gt;?audience&lt;span class="o"&gt;=&lt;/span&gt;sigstore
Authorization: Bearer &lt;span class="nv"&gt;$ACTIONS_ID_TOKEN_REQUEST_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub responds with an actual OIDC id_token whose aud claim is set to sigstore. From there, the flow is identical to the keyless human flow: cosign sends this token plus the ephemeral public key to Fulcio, gets a certificate, signs the image digest, and uploads to Rekor.&lt;br&gt;
Again, since Google doesn't support setting custom aud claims, a Dex intermediate OIDC server was needed, but in the case of GitHub actions, since it allows token creation with a custom audience, a Dex server isn't needed.&lt;/p&gt;

&lt;p&gt;Sample workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, Sign, Push to DockerHub&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-sign-push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout Source Code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install Cosign&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sigstore/cosign-installer@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to DockerHub&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Push Docker Image&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-push&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./Dockerfile&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/${{ secrets.DOCKERHUB_USERNAME }}/myapp:${{ github.sha }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sign Image (Keyless)&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cosign sign --yes \&lt;/span&gt;
            &lt;span class="s"&gt;docker.io/${{ secrets.DOCKERHUB_USERNAME }}/myapp@${{ steps.build-push.outputs.digest }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify Signature&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;cosign verify \&lt;/span&gt;
            &lt;span class="s"&gt;--certificate-identity "https://github.com/${{ github.repository }}/.github/workflows/workflow.yaml@refs/heads/main" \&lt;/span&gt;
            &lt;span class="s"&gt;--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \&lt;/span&gt;
            &lt;span class="s"&gt;docker.io/${{ secrets.DOCKERHUB_USERNAME }}/myapp@${{ steps.build-push.outputs.digest }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to note here:&lt;/p&gt;

&lt;p&gt;The certificate identity is the workflow path. When verifying an Actions-signed image, the --certificate-identity isn't an email; it's the fully-qualified workflow file URL. The Fulcio certificate for a GitHub Actions signing looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Serial Number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0x05f43452ef80bc88abeda2b2495909fa1236ce61'&lt;/span&gt;
&lt;span class="na"&gt;Signature&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;O=sigstore.dev, CN=sigstore-intermediate&lt;/span&gt;
  &lt;span class="na"&gt;Validity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Not Before&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15 days ago (2026-04-02T20:06:17+05:30)&lt;/span&gt;
    &lt;span class="na"&gt;Not After&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15 days ago (2026-04-02T20:16:17+05:30)&lt;/span&gt;
  &lt;span class="na"&gt;Algorithm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ECDSA&lt;/span&gt;
    &lt;span class="na"&gt;namedCurve&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;P-256&lt;/span&gt;
  &lt;span class="na"&gt;Subject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;extraNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
    &lt;span class="na"&gt;asn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[]&lt;/span&gt;
&lt;span class="na"&gt;X509v3 extensions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Key Usage (critical)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Digital Signature&lt;/span&gt;
  &lt;span class="na"&gt;Extended Key Usage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Code Signing&lt;/span&gt;
  &lt;span class="na"&gt;Subject Key Identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;C0:B2:07:6D:68:73:B1:57:3D:DC:E1:A5:7B:4A:ED:E1:9B:8E:6C:21&lt;/span&gt;
  &lt;span class="na"&gt;Authority Key Identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;keyid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DF:D3:E9:CF:56:24:11:96:F9:A8:D8:E9:28:55:A2:C6:2E:18:64:3F&lt;/span&gt;
  &lt;span class="na"&gt;Subject Alternative Name (critical)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https://github.com/srisaikompella/pr-test/.github/workflows/workflow.yaml@refs/heads/main&lt;/span&gt;
  &lt;span class="na"&gt;OIDC Issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://token.actions.githubusercontent.com&lt;/span&gt;
  &lt;span class="na"&gt;GitHub Workflow Trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;
  &lt;span class="na"&gt;GitHub Workflow SHA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fe04ec0ce732b6be9fc1446333eceb111bfb0daf&lt;/span&gt;
  &lt;span class="na"&gt;GitHub Workflow Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, Sign, Push to DockerHub&lt;/span&gt;
  &lt;span class="na"&gt;GitHub Workflow Repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;srisaikompella/pr-test&lt;/span&gt;
  &lt;span class="na"&gt;GitHub Workflow Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main&lt;/span&gt;
  &lt;span class="na"&gt;OIDC Issuer (v2)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://token.actions.githubusercontent.com&lt;/span&gt;
  &lt;span class="na"&gt;Build Signer URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/srisaikompella/pr-test/.github/workflows/workflow.yaml@refs/heads/main&lt;/span&gt;
  &lt;span class="na"&gt;Build Signer Digest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fe04ec0ce732b6be9fc1446333eceb111bfb0daf&lt;/span&gt;
  &lt;span class="na"&gt;Runner Environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-hosted&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/srisaikompella/pr-test&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository Digest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fe04ec0ce732b6be9fc1446333eceb111bfb0daf&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository Ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;refs/heads/main&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository Identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;605870062'&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository Owner URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/srisaikompella&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository Owner Identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;69502069'&lt;/span&gt;
  &lt;span class="na"&gt;Build Config URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/srisaikompella/pr-test/.github/workflows/workflow.yaml@refs/heads/main&lt;/span&gt;
  &lt;span class="na"&gt;Build Config Digest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fe04ec0ce732b6be9fc1446333eceb111bfb0daf&lt;/span&gt;
  &lt;span class="na"&gt;Build Trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;
  &lt;span class="na"&gt;Run Invocation URI&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/srisaikompella/pr-test/actions/runs/23905840052/attempts/1&lt;/span&gt;
  &lt;span class="na"&gt;Source Repository Visibility At Signing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
  &lt;span class="na"&gt;1.3.6.1.4.1.11129.2.4.2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;04:7a:00:78:00:76:00:dd:3d:30:6a:c6:c7:11:32:63:19:1e:1c:99:67:37:02:a2:4a:5e:b8:de:3c:ad:ff:87:8a:72:80:2f:29:ee:8e:00:00:01:9d:4e:9f:a0:9d:00:00:04:03:00:47:30:45:02:20:14:74:26:99:7a:9a:07:ba:a0:e9:a6:b2:cc:bf:7a:8b:53:6b:4b:d9:c8:d1:74:ec:61:13:df:b8:9b:46:e1:5d:02:21:00:96:4e:cd:77:2d:e4:00:6c:1c:cf:a1:bd:72:d3:4a:7b:32:8f:29:94:da:f6:1b:48:f9:6e:d0:5e:5e:78:be:85&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Signing images is only half the story. The other half is making sure your cluster actually enforces it, rejecting any workload whose image wasn't signed. In the next article, we will  setup sigstore policy controller that blocks unsigned images.&lt;/p&gt;

</description>
      <category>supplychainsecurity</category>
      <category>kubernetes</category>
      <category>containers</category>
      <category>security</category>
    </item>
    <item>
      <title>Provisioning a Kubernetes Cluster on OpenStack Using Cluster API (CAPI)</title>
      <dc:creator>Sri Sai Venkata Kasi Subbarayudu Kompella</dc:creator>
      <pubDate>Sun, 29 Mar 2026 13:24:25 +0000</pubDate>
      <link>https://dev.to/kasi_subbarayudu/provisioning-a-kubernetes-cluster-on-openstack-using-cluster-api-capi-2fn8</link>
      <guid>https://dev.to/kasi_subbarayudu/provisioning-a-kubernetes-cluster-on-openstack-using-cluster-api-capi-2fn8</guid>
      <description>&lt;p&gt;This is my first blog post. I spent this weekend going deep into CAPI + CAPO + ORC. This guide covers everything - installation, configuration, manifests, and the full flow explained simply. If you want to understand CAPI, this guide is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Is Cluster API and Why Should You Care?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Managing Kubernetes clusters manually is painful. You manually create infrastructure, install Kubernetes packages, SSH into VMs, run &lt;code&gt;kubeadm init&lt;/code&gt;, and then run &lt;code&gt;kubeadm join&lt;/code&gt; to join the worker nodes, set up CNI, etc. Cluster API (CAPI) solves this by treating cluster creation as a Kubernetes-native, declarative operation. Cluster API is a Kubernetes project that lets you manage Kubernetes clusters using Kubernetes-style APIs.&lt;/p&gt;

&lt;p&gt;Instead of running commands, you write YAML manifests and apply them to a "management cluster." CAPI controllers read those manifests and provision your cluster automatically — creating VMs, configuring networking, running kubeadm — all without you touching a single VM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three core concepts to understand:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Management Cluster&lt;/strong&gt; — A small existing Kubernetes cluster that runs CAPI controllers and manages other clusters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workload Cluster&lt;/strong&gt; — The cluster CAPI creates for you, where your actual apps run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Providers&lt;/strong&gt; — Plugins that tell CAPI how to talk to specific infrastructure (OpenStack, AWS, GCP, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Role of controllers:
&lt;/h3&gt;

&lt;p&gt;CAPI installs multiple controllers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Controller&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CAPI Core&lt;/td&gt;
&lt;td&gt;Orchestrates cluster lifecycle — watches Cluster, Machine, MachineDeployment objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAPO (Cluster API Provider OpenStack)&lt;/td&gt;
&lt;td&gt;Talks to OpenStack APIs — creates Nova VMs, Octavia LBs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CABPK (Kubeadm Bootstrap Provider)&lt;/td&gt;
&lt;td&gt;Generates cloud-init scripts that run kubeadm on VMs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KCP (Kubeadm Control Plane Provider)&lt;/td&gt;
&lt;td&gt;Manages control plane machine lifecycle and scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All are installed together via clusterctl.&lt;/p&gt;

&lt;p&gt;If you have been reading about CAPO, you may have come across ORC — the OpenStack Resource Controller. ORC is a separate project and is not part of CAPO. It needs to be installed separately. ORC is a separate, optional Kubernetes controller that lets you manage OpenStack resources — Glance images, Nova keypairs, Neutron networks and routers — as Kubernetes custom resources, declaratively. The idea is that instead of manually uploading your node image to Glance and copying its UUID into your CAPI manifests, you declare an Image CRD in your management cluster, and ORC ensures it exists in OpenStack. It ensures the resources referenced in the CAPI manifest exist before creating the cluster. &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before installing CAPI, you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A running management cluster (even a single-node kind or minikube cluster works)&lt;/li&gt;
&lt;li&gt;clusterctl CLI installed&lt;/li&gt;
&lt;li&gt;OpenStack credentials (your clouds.yaml file)&lt;/li&gt;
&lt;li&gt;OpenStack environment with:

&lt;ul&gt;
&lt;li&gt;At least one available flavor (e.g., m1.medium)&lt;/li&gt;
&lt;li&gt;A Glance image with Kubernetes pre-installed (qcow image with kubeadm/kubelet/kubectl pre-installed)&lt;/li&gt;
&lt;li&gt;A provider network created&lt;/li&gt;
&lt;li&gt;Octavia (Load Balancer service) enabled&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install &lt;code&gt;clusterctl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://github.com/kubernetes-sigs/cluster-api/releases/latest/download/clusterctl-linux-amd64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; clusterctl
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x clusterctl
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;clusterctl /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clusterctl version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clusterctl version: &amp;amp;version.Info{Major:"1", Minor:"12", GitVersion:"v1.12.3", GitCommit:"1a1852c74072febc3fbf9823c9dd32f4cd6cc747", GitTreeState:"clean", BuildDate:"2026-02-17T17:46:30Z", GoVersion:"go1.24.13", Compiler:"gc", Platform:"linux/amd64"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2 — Initialize CAPI with OpenStack Provider
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clusterctl init &lt;span class="nt"&gt;--infrastructure&lt;/span&gt; openstack:v0.14.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CAPI core controllers&lt;/li&gt;
&lt;li&gt;CAPO (OpenStack infrastructure provider)&lt;/li&gt;
&lt;li&gt;CABPK (kubeadm bootstrap provider)&lt;/li&gt;
&lt;li&gt;KCP (kubeadm control plane provider)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sample Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetching providers
Installing cert-manager version="v1.19.3"
Waiting for cert-manager to be available...
spec.privateKey.rotationPolicy: In cert-manager &amp;gt;= v1.18.0, the default value changed from `Never` to `Always`.
Installing provider="cluster-api" version="v1.12.4" targetNamespace="capi-system"
spec.privateKey.rotationPolicy: In cert-manager &amp;gt;= v1.18.0, the default value changed from `Never` to `Always`.
Installing provider="bootstrap-kubeadm" version="v1.12.4" targetNamespace="capi-kubeadm-bootstrap-system"
spec.privateKey.rotationPolicy: In cert-manager &amp;gt;= v1.18.0, the default value changed from `Never` to `Always`.
Installing provider="control-plane-kubeadm" version="v1.12.4" targetNamespace="capi-kubeadm-control-plane-system"
spec.privateKey.rotationPolicy: In cert-manager &amp;gt;= v1.18.0, the default value changed from `Never` to `Always`.
Installing provider="infrastructure-openstack" version="v0.14.1" targetNamespace="capo-system"
spec.privateKey.rotationPolicy: In cert-manager &amp;gt;= v1.18.0, the default value changed from `Never` to `Always`.
Your management cluster has been initialized successfully!

You can now create your first workload cluster by running the following:

  clusterctl generate cluster [name] --kubernetes-version [version] | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify all controllers are running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;kubectl get pods -A | grep -E "capi|capo|cert-manager"
capi-kubeadm-bootstrap-system       capi-kubeadm-bootstrap-controller-manager-5b6b785789-r62sl      1/1     Running   0          42m
capi-kubeadm-control-plane-system   capi-kubeadm-control-plane-controller-manager-95df764cf-hh7km   1/1     Running   0          42m
capi-system                         capi-controller-manager-6bbbfd79d8-b2dm4                        1/1     Running   0          42m
capo-system                         capo-controller-manager-6469cf765b-xhv8s                        1/1     Running   0          42m
cert-manager                        cert-manager-77f7698dcf-76hsq                                   1/1     Running   0          43m
cert-manager                        cert-manager-cainjector-557b97dd67-52fnw                        1/1     Running   0          43m
cert-manager                        cert-manager-webhook-5b7654ff4-cmgc8                            1/1     Running   0          43m
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 — Set Environment Variables for Template Generation
&lt;/h2&gt;

&lt;p&gt;clusterctl can generate cluster manifests from a template. You need to export these environment variables first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_CLOUD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openstack &lt;span class="c"&gt;# name should match the cloudname in clouds.yaml&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_CLOUD_CACERT_B64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;export  &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_CLOUD_YAML_B64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-w0&lt;/span&gt; clouds.yaml&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# get clouds.yaml from openstack horizon dashboard&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_NODE_MACHINE_FLAVOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;m1.medium &lt;span class="c"&gt;# instance type for ctrl plane node&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_IMAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-2404-kube &lt;span class="c"&gt;# Name of the image used for kubernetes cluster.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_FAILURE_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nova  &lt;span class="c"&gt;# avail zone usually nova.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_EXTERNAL_NETWORK_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5068d3a6-f560-4d79-b834-cd6943dc5de5 &lt;span class="c"&gt;# ex: openstack network list | grep provider, get the id.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_DNS_NAMESERVERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8.8.8.8
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENSTACK_SSH_KEY_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"kashi"&lt;/span&gt; &lt;span class="c"&gt;# name of the SSH key uploaded in OpenStack and you want to be uploaded to cluster nodes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image used in CAPI is prebuilt image that contains kubeadm, kubectl, kubelet all the kubernetes packages preinstalled.&lt;/p&gt;

&lt;p&gt;You can build the image by running the following Makefile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;SHELL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; /bin/bash

&lt;span class="nv"&gt;IMAGE_BUILDER_REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://github.com/kubernetes-sigs/image-builder.git
&lt;span class="nv"&gt;IMAGE_BUILDER_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image-builder

&lt;span class="k"&gt;ifndef&lt;/span&gt; &lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;
&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ubuntu-2404-kube-v1.34.3
&lt;span class="nl"&gt;$(warning IMAGE_NAME is undefined, using default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(IMAGE_NAME))&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="k"&gt;ifndef&lt;/span&gt; &lt;span class="nv"&gt;IMAGE_FILE&lt;/span&gt;
&lt;span class="nv"&gt;IMAGE_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;output/ubuntu-2404-kube-v1.34.3/ubuntu-2404-kube-v1.34.3
&lt;span class="nl"&gt;$(warning IMAGE_FILE is undefined, using default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(IMAGE_FILE))&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="nv"&gt;OS_IMAGE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_NAME&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OS_DISK_FORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;qcow2
&lt;span class="nv"&gt;OS_CONTAINER_FORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bare
&lt;span class="nv"&gt;OS_VISIBILITY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;public
&lt;span class="k"&gt;ifndef&lt;/span&gt; &lt;span class="nv"&gt;ADMIN_RC_PATH&lt;/span&gt;
&lt;span class="nv"&gt;ADMIN_RC_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kolla/admin-openrc.sh
&lt;span class="nl"&gt;$(warning ADMIN_RC_PATH is undefined, using default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$(ADMIN_RC_PATH))&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;


&lt;span class="nl"&gt;img-builder-clone&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;IMAGE_BUILDER_DIR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        git clone &lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_BUILDER_REPO&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nl"&gt;capi-deps&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;img-builder-clone&lt;/span&gt;
    &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_BUILDER_DIR&lt;span class="p"&gt;)&lt;/span&gt;/images/capi &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make deps-qemu


&lt;span class="nl"&gt;build-capi&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;capi-deps&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;IMAGE_BUILDER_DIR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/images/capi/&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;IMAGE_FILE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
       &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_BUILDER_DIR&lt;span class="p"&gt;)&lt;/span&gt;/images/capi &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make build-qemu-ubuntu-2404 &amp;amp;&amp;gt; /dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="nl"&gt;capi-upload&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; 
     &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;ADMIN_RC_PATH&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; openstack image create &lt;span class="p"&gt;$(&lt;/span&gt;OS_IMAGE_NAME&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--file&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_BUILDER_DIR&lt;span class="p"&gt;)&lt;/span&gt;/images/capi/&lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_FILE&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--disk-format&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;OS_DISK_FORMAT&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--container-format&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;OS_CONTAINER_FORMAT&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;--public&lt;/span&gt;


&lt;span class="nl"&gt;capi-image&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build-capi capi-upload&lt;/span&gt;

&lt;span class="nl"&gt;capi-delete&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;ADMIN_RC_PATH&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; openstack image delete &lt;span class="p"&gt;$(&lt;/span&gt;OS_IMAGE_NAME&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;capi-image&lt;/span&gt;

&lt;span class="nl"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; 
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;IMAGE_BUILDER_DIR&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;$(&lt;/span&gt;DISKIMAGE_DIR&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;OCTAVIA_REPO&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the makefile: &lt;code&gt;make all&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can list the required environment variables to be set by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clusterctl generate cluster &lt;span class="nt"&gt;--infrastructure&lt;/span&gt; openstack:v0.14.1 &lt;span class="nt"&gt;--list-variables&lt;/span&gt; capi-quickstart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Required Variables:
  - KUBERNETES_VERSION
  - OPENSTACK_CLOUD
  - OPENSTACK_CLOUD_CACERT_B64
  - OPENSTACK_CLOUD_YAML_B64
  - OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR
  - OPENSTACK_DNS_NAMESERVERS
  - OPENSTACK_EXTERNAL_NETWORK_ID
  - OPENSTACK_FAILURE_DOMAIN
  - OPENSTACK_IMAGE_NAME
  - OPENSTACK_NODE_MACHINE_FLAVOR
  - OPENSTACK_SSH_KEY_NAME

Optional Variables:
  - CLUSTER_NAME                 (defaults to capi-quickstart)
  - CONTROL_PLANE_MACHINE_COUNT  (defaults to 1)
  - WORKER_MACHINE_COUNT         (defaults to 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 — Generate the Cluster Manifests&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clusterctl generate cluster hind-cluster &lt;span class="nt"&gt;--infrastructure&lt;/span&gt; openstack:v0.14.1   &lt;span class="nt"&gt;--kubernetes-version&lt;/span&gt; v1.34.3  &lt;span class="nt"&gt;--control-plane-machine-count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1   &lt;span class="nt"&gt;--worker-machine-count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; capi-quickstart.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command generates cluster manifests. Let's understand the generated manifests.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Cluster&lt;/code&gt; manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster.x-k8s.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cluster&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clusterNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;cidrBlocks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;192.168.0.0/16&lt;/span&gt;
    &lt;span class="na"&gt;serviceDomain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster.local&lt;/span&gt;
  &lt;span class="na"&gt;controlPlaneRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controlplane.cluster.x-k8s.io&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeadmControlPlane&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane&lt;/span&gt;
  &lt;span class="na"&gt;infrastructureRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure.cluster.x-k8s.io&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenStackCluster&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the main object — it ties everything together. It says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pods get IPs from 192.168.0.0/16 &lt;/li&gt;
&lt;li&gt;Control plane is defined by KubeadmControlPlane&lt;/li&gt;
&lt;li&gt;OpenStack infrastructure is defined by OpenStackCluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;OpenStackCluster&lt;/code&gt; - OpenStack Infrastructure Definition&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure.cluster.x-k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenStackCluster&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiServerLoadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;externalNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c926f5e2-830e-43b0-9d06-89641bb8e131&lt;/span&gt;
  &lt;span class="na"&gt;identityRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cloudName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openstack&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-cloud-config&lt;/span&gt;
  &lt;span class="na"&gt;managedSecurityGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;allNodesSecurityGroupRules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Created by cluster-api-provider-openstack - BGP (calico)&lt;/span&gt;
      &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress&lt;/span&gt;
      &lt;span class="na"&gt;etherType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IPv4&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BGP (Calico)&lt;/span&gt;
      &lt;span class="na"&gt;portRangeMax&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;179&lt;/span&gt;
      &lt;span class="na"&gt;portRangeMin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;179&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tcp&lt;/span&gt;
      &lt;span class="na"&gt;remoteManagedGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;controlplane&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Created by cluster-api-provider-openstack - IP-in-IP (calico)&lt;/span&gt;
      &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress&lt;/span&gt;
      &lt;span class="na"&gt;etherType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IPv4&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IP-in-IP (calico)&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4"&lt;/span&gt;
      &lt;span class="na"&gt;remoteManagedGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;controlplane&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;worker&lt;/span&gt;
  &lt;span class="na"&gt;managedSubnets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cidr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.6.0.0/24&lt;/span&gt;
    &lt;span class="na"&gt;dnsNameservers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8.8.8.8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CAPO controller in the &lt;code&gt;capo-system&lt;/code&gt; namespace reads this manifest and:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks the existence of the external network using the ID provided in the above manifest, also creates an internal network for our cluster, subnet, a router, and attaches this internal network to the router so the nodes in the cluster get internet access (outbound) and a security group that allows only the load balancer to access the backend cluster nodes. The client should be able to access the cluster only through the loadbalancer IP and the backend control plane IPs, or their identities are not exposed.&lt;/li&gt;
&lt;li&gt;Creates security groups for the control plane and workers.&lt;/li&gt;
&lt;li&gt;Creates an Octavia LoadBalancer for the Kubernetes API endpoint. This is especially useful when u have multiple control plane nodes, and the load balancer will load balance the requests across the control planes.&lt;/li&gt;
&lt;li&gt;Sets the LB IP as the cluster's controlPlaneEndpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;kubeadmControlPlane&lt;/code&gt; definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controlplane.cluster.x-k8s.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeadmControlPlane&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kubeadmConfigSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;clusterConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;controllerManager&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;extraArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud-provider&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external&lt;/span&gt;
    &lt;span class="na"&gt;initConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nodeRegistration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kubeletExtraArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud-provider&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;provider-id&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openstack:///'{{ instance_id }}'&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;local_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
    &lt;span class="na"&gt;joinConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;nodeRegistration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kubeletExtraArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud-provider&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;provider-id&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openstack:///'{{ instance_id }}'&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;local_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
  &lt;span class="na"&gt;machineTemplate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;infrastructureRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure.cluster.x-k8s.io&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenStackMachineTemplate&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.34.3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cloud-provider: external&lt;/code&gt; on kubelet: When kubelet starts, it adds a taint node.cloudprovider.kubernetes.io/uninitialized on the node. No pods schedule here until the OpenStack Cloud Controller Manager (CCM) initializes the node and removes this taint. This flag tells kubelet, "don't try to handle cloud stuff yourself, wait for external CCM." &lt;/p&gt;

&lt;p&gt;&lt;code&gt;cloud-provider: external&lt;/code&gt; on controller-manager - Disables the built-in cloud loops in kube-controller-manager (node lifecycle, service LoadBalancer, routes). These are handed off to the OpenStack CCM pod. When a load-balancer type service is created, it is the responsibility of the CCM to create an LB for you behind the scenes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;provider-id: openstack:///{{ instance_id }}&lt;/code&gt; - A unique ID linking the Kubernetes Node object to the actual Nova VM. {{ instance_id }} is a cloud-init template variable resolved to the real VM UUID at boot time. The OpenStack CCM uses this to fetch instance metadata.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;OpenStackMachineTemplate&lt;/code&gt; for Control Plane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure.cluster.x-k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenStackMachineTemplate&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-md-0&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;flavor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;m1.medium&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-2404-kube&lt;/span&gt;
      &lt;span class="na"&gt;sshKeyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kashi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells CAPO what Nova flavor and Glance image to use for control plane VMs. The CAPO looks up the image in Glance and creates the VMs using the image UID fetched from Glance.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MachineDeployment&lt;/code&gt; — Worker Node Pool&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster.x-k8s.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MachineDeployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-md-0&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clusterName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;configRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bootstrap.cluster.x-k8s.io&lt;/span&gt;
          &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeadmConfigTemplate&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-md-0&lt;/span&gt;
      &lt;span class="na"&gt;clusterName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
      &lt;span class="na"&gt;failureDomain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nova&lt;/span&gt;
      &lt;span class="na"&gt;infrastructureRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure.cluster.x-k8s.io&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenStackMachineTemplate&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-md-0&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.34.3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like a Deployment for VMs. It ensures the desired number of worker nodes exists. It references KubeadmConfigTemplate for the cloud-init script and OpenStackMachineTemplate for VM specs.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;KubeadmConfigTemplate&lt;/code&gt; — Worker Bootstrap Config&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bootstrap.cluster.x-k8s.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeadmConfigTemplate&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-md-0&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;joinConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;nodeRegistration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;kubeletExtraArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud-provider&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;provider-id&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openstack:///'{{ instance_id }}'&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;local_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workers only have joinConfiguration — they never run kubeadm init, only kubeadm join. The same &lt;code&gt;cloud-provider: external&lt;/code&gt; and &lt;code&gt;provider-id&lt;/code&gt; flags are set here for the same reasons as the control plane.&lt;/p&gt;

&lt;p&gt;So, when you apply &lt;code&gt;capi-quickstart.yaml&lt;/code&gt;, CAPI sees the cluster object, and CAPO sees the OpenstackCluster object and creates the internal network, subnet, router, attaches subnet to the router for internet access ( router will have a port from the provider network as external gw ). It also creates security groups and the loadbalancer for kubernetes API server endpoint.&lt;/p&gt;

&lt;p&gt;You should see the below events once when you describe the &lt;code&gt;OpenstackCluster&lt;/code&gt; resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events:
  Type    Reason                         Age   From                  Message
  ----    ------                         ----  ----                  -------
  Normal  Successfulcreatenetwork        54m   openstack-controller  Created network k8s-clusterapi-cluster-default-hind-cluster with id 05371ddd-a37e-43d6-ba6d-58599c4d2b7b
  Normal  Successfulcreatesubnet         54m   openstack-controller  Created subnet k8s-clusterapi-cluster-default-hind-cluster with id ea8186bc-88f7-4cc6-aa27-bf9b5a1154c5
  Normal  Successfulcreaterouter         54m   openstack-controller  Created router k8s-clusterapi-cluster-default-hind-cluster with id 8790e4f0-3b55-4d4e-acf4-8ce37e5719f4
  Normal  Successfulcreatesecuritygroup  54m   openstack-controller  Created security group k8s-cluster-default-hind-cluster-secgroup-controlplane with id e08613ca-135c-4f64-ba67-def6a1ffd7e1
  Normal  Successfulcreatesecuritygroup  54m   openstack-controller  Created security group k8s-cluster-default-hind-cluster-secgroup-worker with id 4ed61b8a-a9b0-4fb8-a3db-af6e457aea8a
  Normal  Successfulcreateloadbalancer   54m   openstack-controller  Created load balancer k8s-clusterapi-cluster-default-hind-cluster-kubeapi with id 421cd8a9-a7ba-4f41-8f03-1cdc4d958358
  Normal  Successfulcreatefloatingip     52m   openstack-controller  Created floating IP 10.31.0.101 with id 433d5f18-34dc-489d-b2ae-19ba2a167f8b
  Normal  Successfulassociatefloatingip  52m   openstack-controller  Associated floating IP 10.31.0.101 with port 6f0f82d9-bc8e-42e1-88eb-65ec33469d6e
  Normal  Successfulcreatelistener       52m   openstack-controller  Created listener k8s-clusterapi-cluster-default-hind-cluster-kubeapi-6443 with id c66b31a9-f9ee-4bfd-b61f-b402c266a8bf
  Normal  Successfulcreatepool           52m   openstack-controller  Created pool k8s-clusterapi-cluster-default-hind-cluster-kubeapi-6443 with id 465f565c-3a84-4b4d-938b-c28dfcc009af
  Normal  Successfulcreatemonitor        52m   openstack-controller  Created monitor k8s-clusterapi-cluster-default-hind-cluster-kubeapi-6443 with id 1349252f-b42b-4393-bab3-9243ff9b282e
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Also verify the network, subnet, router, security groups in your openstack cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(kolla-venv) root@control-1:~#  openstack network list
+--------------------------------------+---------------------------------------------+--------------------------------------+
| ID                                   | Name                                        | Subnets                              |
+--------------------------------------+---------------------------------------------+--------------------------------------+
| 05371ddd-a37e-43d6-ba6d-58599c4d2b7b | k8s-clusterapi-cluster-default-hind-cluster | ea8186bc-88f7-4cc6-aa27-bf9b5a1154c5 |
| 40c70b44-1c45-407f-b590-5c019814ef57 | trove-mgmt                                  | 22b30e5a-4cbb-422d-9e9b-6f0ee2736c46 |
| 75bba094-d69a-4060-a271-a5d529915f01 | private-net                                 | e2c43b43-2936-4839-b57c-8c955f44f913 |
| 7e72fa18-46b5-4bfb-96bf-5666df021dec | lb-mgmt-net                                 | 2183795b-8723-490f-aaa9-5bb49f2d9af1 |
| c926f5e2-830e-43b0-9d06-89641bb8e131 | provider                                    | 4073439c-176e-46b7-8f78-9e7debdfef02 |
+--------------------------------------+---------------------------------------------+--------------------------------------+
(kolla-venv) root@control-1:~#  openstack subnet list
 +--------------------------------------+---------------------------------------------+--------------------------------------+-----------------+
| ID                                   | Name                                        | Network                              | Subnet          |
+--------------------------------------+---------------------------------------------+--------------------------------------+-----------------+
| 2183795b-8723-490f-aaa9-5bb49f2d9af1 | lb-mgmt-subnet                              | 7e72fa18-46b5-4bfb-96bf-5666df021dec | 10.1.0.0/24     |
| 22b30e5a-4cbb-422d-9e9b-6f0ee2736c46 | trove-mgmt-subnet                           | 40c70b44-1c45-407f-b590-5c019814ef57 | 10.33.0.0/24    |
| 4073439c-176e-46b7-8f78-9e7debdfef02 | provider-subnet                             | c926f5e2-830e-43b0-9d06-89641bb8e131 | 10.31.0.0/24    |
| e2c43b43-2936-4839-b57c-8c955f44f913 | private-subnet                              | 75bba094-d69a-4060-a271-a5d529915f01 | 192.168.50.0/24 |
| ea8186bc-88f7-4cc6-aa27-bf9b5a1154c5 | k8s-clusterapi-cluster-default-hind-cluster | 05371ddd-a37e-43d6-ba6d-58599c4d2b7b | 10.6.0.0/24     |
+--------------------------------------+---------------------------------------------+--------------------------------------+-----------------+
(kolla-venv) root@control-1:~#  
(kolla-venv) root@control-1:~#  openstack router list 
+--------------------------------------+---------------------------------------------+--------+-------+----------------------------------+-------------+-------+
| ID                                   | Name                                        | Status | State | Project                          | Distributed | HA    |
+--------------------------------------+---------------------------------------------+--------+-------+----------------------------------+-------------+-------+
| 40625a37-d00e-422c-a5b4-577c4d4c2a81 | trove-mgmt-router                           | ACTIVE | UP    | 2c2343fd2bd141e8a18229afefb9c4ff | False       | False |
| 8790e4f0-3b55-4d4e-acf4-8ce37e5719f4 | k8s-clusterapi-cluster-default-hind-cluster | ACTIVE | UP    | 2c2343fd2bd141e8a18229afefb9c4ff | False       | False |
+--------------------------------------+---------------------------------------------+--------+-------+----------------------------------+-------------+-------+
(kolla-venv) root@control-1:~# 
(kolla-venv) root@control-1:~# 
(kolla-venv) root@control-1:~# 
(kolla-venv) root@control-1:~#  openstack router show k8s-clusterapi-cluster-default-hind-cluster
+-------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Field                   | Value                                                                                                                                                     |
+-------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| admin_state_up          | UP                                                                                                                                                        |
| availability_zone_hints |                                                                                                                                                           |
| availability_zones      | nova                                                                                                                                                      |
| created_at              | 2026-03-29T11:26:23Z                                                                                                                                      |
| description             | Created by cluster-api-provider-openstack cluster default-hind-cluster                                                                                    |
| distributed             | False                                                                                                                                                     |
| enable_ndp_proxy        | None                                                                                                                                                      |
| external_gateway_info   | {"network_id": "c926f5e2-830e-43b0-9d06-89641bb8e131", "external_fixed_ips": [{"subnet_id": "4073439c-176e-46b7-8f78-9e7debdfef02", "ip_address":         |
|                         | "10.31.0.246"}], "enable_snat": true}                                                                                                                     |
| flavor_id               | None                                                                                                                                                      |
| ha                      | False                                                                                                                                                     |
| id                      | 8790e4f0-3b55-4d4e-acf4-8ce37e5719f4                                                                                                                      |
| interfaces_info         | [{"port_id": "1e65d210-c1e7-4118-a847-d27b9058077b", "ip_address": "10.6.0.1", "subnet_id": "ea8186bc-88f7-4cc6-aa27-bf9b5a1154c5"}]                      |
| name                    | k8s-clusterapi-cluster-default-hind-cluster                                                                                                               |
| project_id              | 2c2343fd2bd141e8a18229afefb9c4ff                                                                                                                          |
| revision_number         | 3                                                                                                                                                         |
| routes                  |                                                                                                                                                           |
| status                  | ACTIVE                                                                                                                                                    |
| tags                    |                                                                                                                                                           |
| updated_at              | 2026-03-29T11:26:25Z                                                                                                                                      |
+-------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
(kolla-venv) root@control-1:~# 
(kolla-venv) root@control-1:~# 
(kolla-venv) root@control-1:~# 
(kolla-venv) root@control-1:~#  openstack loadbalancer list
 +-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
| id                                  | name                                | project_id                       | vip_address | provisioning_status | operating_status | provider |
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
| 421cd8a9-a7ba-4f41-8f03-            | k8s-clusterapi-cluster-default-     | 2c2343fd2bd141e8a18229afefb9c4ff | 10.6.0.107  | ACTIVE              | ONLINE           | amphora  |
| 1cdc4d958358                        | hind-cluster-kubeapi                |                                  |             |                     |                  |          |
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
(kolla-venv) root@control-1:~#  
(kolla-venv) root@control-1:~#  openstack  security group list
+--------------------------------------+--------------------------------------------------------+---------------------------+----------------------------------+------+--------+
| ID                                   | Name                                                   | Description               | Project                          | Tags | Shared |
+--------------------------------------+--------------------------------------------------------+---------------------------+----------------------------------+------+--------+
| 188e18b6-cc7e-42d6-8b56-a03cb9af5e5a | lb-421cd8a9-a7ba-4f41-8f03-1cdc4d958358                |                           | f37717d8ea254fdfb12bff38e8834f9d | []   | False  |
| 266e525f-2e15-4f72-9c11-7448c47d9abf | trove-sec-grp                                          | trove-sec-grp             | 2c2343fd2bd141e8a18229afefb9c4ff | []   | False  |
| 3a9617cd-6e92-4c7f-bad5-fd3dfb1996ed | default                                                | Default security group    | 2c2343fd2bd141e8a18229afefb9c4ff | []   | False  |
| 429b9c68-e2db-437c-b4db-bf7f8bad6d60 | default                                                | Default security group    | f37717d8ea254fdfb12bff38e8834f9d | []   | False  |
| 4ed61b8a-a9b0-4fb8-a3db-af6e457aea8a | k8s-cluster-default-hind-cluster-secgroup-worker       | Cluster API managed group | 2c2343fd2bd141e8a18229afefb9c4ff | []   | False  |
| 64ad5172-e754-416b-a228-b1bd9d98e96a | test                                                   | test                      | 2c2343fd2bd141e8a18229afefb9c4ff | []   | False  |
| 80fbc123-07f5-4b7d-bc2c-92891fc96668 | lb-mgmt-sec-grp                                        |                           | f37717d8ea254fdfb12bff38e8834f9d | []   | False  |
| e08613ca-135c-4f64-ba67-def6a1ffd7e1 | k8s-cluster-default-hind-cluster-secgroup-controlplane | Cluster API managed group | 2c2343fd2bd141e8a18229afefb9c4ff | []   | False  |
| e8fc39aa-668a-404a-afe0-98ffa118f4a1 | lb-health-mgr-sec-grp                                  |                           | f37717d8ea254fdfb12bff38e8834f9d | []   | False  |
+--------------------------------------+--------------------------------------------------------+---------------------------+----------------------------------+------+--------+
(kolla-venv) root@control-1:~# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the resources created by the clusterAPI are prefixed with &lt;code&gt;k8s-clusterapi&lt;/code&gt;, and security groups are prefixed with &lt;code&gt;k8s-cluster&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also, you can see that a load balancer has been created that will act as the frontend for our control plane nodes.&lt;/p&gt;

&lt;p&gt;Then the capi-kubeadm-control-plane-controller-manager will see the &lt;code&gt;KubeadmControlPlane&lt;/code&gt; object and create the kubeadmconfig and machine objects in the cluster.&lt;/p&gt;

&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get kubeadmconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                               CLUSTER        DATA SECRET CREATED   AGE
hind-cluster-control-plane-wggsv   hind-cluster   true                  57m
hind-cluster-md-0-xg7gb-qjlvj      hind-cluster   true                  57m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see 2 objects, one for controlplane and the other for the worker node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get machine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                               CLUSTER        NODE NAME                          READY     AVAILABLE   UP-TO-DATE   PHASE     AGE   VERSION
hind-cluster-control-plane-wggsv   hind-cluster   hind-cluster-control-plane-wggsv   Unknown   False       True         Running   57m   v1.34.3
hind-cluster-md-0-xg7gb-qjlvj      hind-cluster   hind-cluster-md-0-xg7gb-qjlvj      True      True        True         Running   57m   v1.34.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And capi-kubeadm-bootstrap-controller-manager, does a crucial job. It generates the cloud-init script that contains the actual kubeadm commands to be run during the booting process of our nodes in the cluster. This cloud-init script is actually stored in a secret.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnnh7ynggm5wnitvt3af.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnnh7ynggm5wnitvt3af.png" alt=" " width="800" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Highlighted above are the secrets created by the controller that contain the cloud-init script. These secrets will be referenced by the machine objects created by kubeadm-controlplane-mgr.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;kubectl get machine hind-cluster-control-plane-wggsv -o yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster.x-k8s.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Machine&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;pre-terminate.delete.hook.machine.cluster.x-k8s.io/kcp-cleanup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-03-29T11:28:04Z"&lt;/span&gt;
  &lt;span class="na"&gt;finalizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;machine.cluster.x-k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;generation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cluster.x-k8s.io/cluster-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
    &lt;span class="na"&gt;cluster.x-k8s.io/control-plane&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;cluster.x-k8s.io/control-plane-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane-wggsv&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;ownerReferences&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;controlplane.cluster.x-k8s.io/v1beta2&lt;/span&gt;
    &lt;span class="na"&gt;blockOwnerDeletion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;controller&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeadmControlPlane&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane&lt;/span&gt;
    &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;d25ecc23-dd2c-417a-b891-f88eb85225af&lt;/span&gt;
  &lt;span class="na"&gt;resourceVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15813"&lt;/span&gt;
  &lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4b4725ad-f1ee-4232-8c06-4cd2f14ed025&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;configRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bootstrap.cluster.x-k8s.io&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KubeadmConfig&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane-wggsv&lt;/span&gt;
    &lt;span class="na"&gt;dataSecretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane-wggsv&lt;/span&gt;
  &lt;span class="na"&gt;clusterName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster&lt;/span&gt;
  &lt;span class="na"&gt;deletion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nodeDeletionTimeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;failureDomain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nova&lt;/span&gt;
  &lt;span class="na"&gt;infrastructureRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure.cluster.x-k8s.io&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OpenStackMachine&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hind-cluster-control-plane-wggsv&lt;/span&gt;
  &lt;span class="na"&gt;providerID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;openstack:///3570638e-3d1c-4ccb-b758-86321356ea0d&lt;/span&gt;
  &lt;span class="na"&gt;readinessGates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;APIServerPodHealthy&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ControllerManagerPodHealthy&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SchedulerPodHealthy&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EtcdPodHealthy&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;conditionType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EtcdMemberHealthy&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1.34.3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the data secretName above. The value matches one of the secrets listed in the output of the &lt;code&gt;get&lt;/code&gt; secrets command.&lt;/p&gt;

&lt;p&gt;CAPO controller sees the machine object and calls the NOVA service to create the VM. &lt;/p&gt;

&lt;p&gt;Also, while &lt;code&gt;kubeadmcontrolplane&lt;/code&gt; is used for control plane nodes, &lt;code&gt;machinedeployment&lt;/code&gt; is used for worker nodes.&lt;/p&gt;

&lt;p&gt;After you applied the clusterAPI, manifests, get the kubeconfig of the workload cluster using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;clusterctl get kubeconfig hind-cluster &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hind-cluster.kubeconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Export the kubeconfig and verify the cluster status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./hind-cluster.kubeconfig
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                               STATUS     ROLES           AGE   VERSION
hind-cluster-control-plane-wggsv   NotReady   control-plane   13m   v1.34.3
hind-cluster-md-0-xg7gb-qjlvj      NotReady   &amp;lt;none&amp;gt;          11m   v1.34.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nodes are in NotReady state. And it is completely expected. Because we need to install CNI.&lt;/p&gt;

&lt;p&gt;You can install the CNI of your choice, I am going with calico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./hind-cluster.kubeconfig &lt;span class="se"&gt;\&lt;/span&gt;
  apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/projectcalico/calico/v3.26.1/manifests/calico.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After installing the CNI, nodes should transition to Ready state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get nodes
NAME                               STATUS   ROLES           AGE   VERSION
hind-cluster-control-plane-wggsv   Ready    control-plane   70m   v1.34.3
hind-cluster-md-0-xg7gb-qjlvj      Ready    &amp;lt;none&amp;gt;          68m   v1.34.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, remember that we discussed about the role of openstack cloud controller manager above. In the above capi manifests, especially for kubeadmcontrolplane under kubeadmconfigspec, and in the kubeadmconfigtemplate of machine deployment. we specified extra args. i.e cloud-provider set to external. &lt;/p&gt;

&lt;p&gt;Since these flags are set, the worker node and control plane node both will register themselves with a taint: &lt;code&gt;node.cluster.x-k8s.io/uninitialized:NoSchedule&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So unless you install Openstack cloud controller manager the nodes will stay unschedulable. Let's verify this by logging into the nodes.&lt;br&gt;
Verify the kubelet status on control plane node:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp19niedi7c1876kkzsnb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp19niedi7c1876kkzsnb.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On worker node:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73hbzatykvkrk2mvz17m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F73hbzatykvkrk2mvz17m.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You notice the following flags on both the nodes.&lt;br&gt;
On worker node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/bin/kubelet &lt;span class="nt"&gt;--bootstrap-kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kubernetes/bootstrap-kubelet.conf &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kubernetes/kubelet.conf &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/kubelet/config.yaml &lt;span class="nt"&gt;--cloud-provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;external &lt;span class="nt"&gt;--pod-infra-container-image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.k8s.io/pause:3.10.1 &lt;span class="nt"&gt;--provider-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openstack:///d9ae4dde-b281-421b-ad1b-36a2ee7db110 &lt;span class="nt"&gt;--register-with-taints&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node.cluster.x-k8s.io/uninitialized:NoSchedule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider ID here corresponds to nova VM UUID. This is how OpenStack Cloud Controller Manager maps the Kubernetes node to nova vm object in OpenStack. Also, the cloud-provider is set to &lt;code&gt;external&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On control plane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;usr/bin/kubelet &lt;span class="nt"&gt;--bootstrap-kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kubernetes/bootstrap-kubelet.conf &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kubernetes/kubelet.conf &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/lib/kubelet/config.yaml &lt;span class="nt"&gt;--cloud-provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;external &lt;span class="nt"&gt;--pod-infra-container-image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.k8s.io/pause:3.10.1 &lt;span class="nt"&gt;--provider-id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openstack:///3570638e-3d1c-4ccb-b758-86321356ea0d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check the taints.&lt;br&gt;
For control plane:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfurry7t9hq0wwe8wo1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfurry7t9hq0wwe8wo1g.png" alt="taints-on-control-plane" width="799" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For worker node:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nurff9jdhd1lcxi28ij.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nurff9jdhd1lcxi28ij.png" alt="taints-on-worker-node" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, now let's deploy OpenStack Cloud Controller Manager. Run the following steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-openstack/master/templates/env.rc &lt;span class="nt"&gt;-O&lt;/span&gt; /tmp/env.rc
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /tmp/env.rc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Removing old cloud.conf files..."&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/cloud.conf&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Generating cloud.conf file from clouds.yaml..."&lt;/span&gt;
/tmp/env.rc ./clouds.yaml openstack
&lt;span class="nb"&gt;cp&lt;/span&gt; /tmp/cloud.conf&lt;span class="k"&gt;*&lt;/span&gt; ./cloud.conf
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Creating secret in the kube-system ns..."&lt;/span&gt;
kubectl &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./hind-cluster.kubeconfig &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system create secret generic cloud-config &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud.conf
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deploying cloud controller in the workload cluster..."&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./hind-cluster.kubeconfig &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/manifests/controller-manager/cloud-controller-manager-roles.yaml
kubectl apply &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./hind-cluster.kubeconfig &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/manifests/controller-manager/cloud-controller-manager-role-bindings.yaml
kubectl apply &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./hind-cluster.kubeconfig &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/kubernetes/cloud-provider-openstack/master/manifests/controller-manager/openstack-cloud-controller-manager-ds.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tmp.rc is the script used to generate cloud.conf file from clouds.yaml. Download the clouds.yaml file from your OpenStack cloud and specify the path to this script as the first argument. And the second argument is the cloud name in the clouds.yaml. &lt;/p&gt;

&lt;p&gt;Once you install the cloud controller manager, it will remove the taint, and you should see the coredns pods move to the running state from pending.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k4ur3w9ywsx5kuztqfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k4ur3w9ywsx5kuztqfx.png" alt="status" width="800" height="151"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a workload and Test the cluster:
&lt;/h2&gt;

&lt;p&gt;Now lets create a small nginx pod and then expose it using Loadbalancer service. We will see if the openstack CCM creates an LB or not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@hind-cluster-control-plane-wggsv:~# kubectl create deploy nginx &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx
deployment.apps/nginx created
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# kubectl get pods
NAME                     READY   STATUS              RESTARTS   AGE
nginx-66686b6766-549cc   0/1     ContainerCreating   0          4s
root@hind-cluster-control-plane-wggsv:~# kubectl expose deploy nginx &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;LoadBalancer &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
service/nginx exposed
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# 
root@hind-cluster-control-plane-wggsv:~# kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
kubernetes   ClusterIP      10.96.0.1       &amp;lt;none&amp;gt;        443/TCP        90m
nginx        LoadBalancer   10.99.239.196   &amp;lt;pending&amp;gt;     80:31426/TCP   4s
root@hind-cluster-control-plane-wggsv:~# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, it will be in the pending state. Describe the service and verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@hind-cluster-control-plane-wggsv:~# kubectl describe svc nginx 
Name:                     nginx
Namespace:                default
Labels:                   app=nginx
Annotations:              &amp;lt;none&amp;gt;
Selector:                 app=nginx
Type:                     LoadBalancer
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.99.239.196
IPs:                      10.99.239.196
Port:                     &amp;lt;unset&amp;gt;  80/TCP
TargetPort:               80/TCP
NodePort:                 &amp;lt;unset&amp;gt;  31426/TCP
Endpoints:                192.168.69.193:80
Session Affinity:         None
External Traffic Policy:  Cluster
Internal Traffic Policy:  Cluster
Events:
  Type    Reason                Age   From                Message
  ----    ------                ----  ----                -------
  Normal  EnsuringLoadBalancer  12s   service-controller  Ensuring load balancer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also verify in the openstack cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(kolla-venv) root@control-1:~#  openstack loadbalancer list
  +-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
| id                                  | name                                | project_id                       | vip_address | provisioning_status | operating_status | provider |
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
| 421cd8a9-a7ba-4f41-8f03-            | k8s-clusterapi-cluster-default-     | 2c2343fd2bd141e8a18229afefb9c4ff | 10.6.0.107  | ACTIVE              | ONLINE           | amphora  |
| 1cdc4d958358                        | hind-cluster-kubeapi                |                                  |             |                     |                  |          |
| 5289ba7f-dc63-4db2-a6a4-            | kube_service_kubernetes_default_ngi | 2c2343fd2bd141e8a18229afefb9c4ff | 10.6.0.203  | PENDING_CREATE      | ONLINE           | amphora  |
| f72d2e86f33d                        | nx                                  |                                  |             |                     |                  |          |
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally after sometime, the status should change to active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(kolla-venv) root@control-1:~#   openstack loadbalancer list
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
| id                                  | name                                | project_id                       | vip_address | provisioning_status | operating_status | provider |
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
| 421cd8a9-a7ba-4f41-8f03-            | k8s-clusterapi-cluster-default-     | 2c2343fd2bd141e8a18229afefb9c4ff | 10.6.0.107  | ACTIVE              | ONLINE           | amphora  |
| 1cdc4d958358                        | hind-cluster-kubeapi                |                                  |             |                     |                  |          |
| 5289ba7f-dc63-4db2-a6a4-            | kube_service_kubernetes_default_ngi | 2c2343fd2bd141e8a18229afefb9c4ff | 10.6.0.203  | ACTIVE              | ONLINE           | amphora  |
| f72d2e86f33d                        | nx                                  |                                  |             |                     |                  |          |
+-------------------------------------+-------------------------------------+----------------------------------+-------------+---------------------+------------------+----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the status of the service should change as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@hind-cluster-control-plane-wggsv:~# kubectl describe svc nginx 
Name:                     nginx
Namespace:                default
Labels:                   app=nginx
Annotations:              loadbalancer.openstack.org/load-balancer-address: 10.31.0.48
                          loadbalancer.openstack.org/load-balancer-id: 5289ba7f-dc63-4db2-a6a4-f72d2e86f33d
Selector:                 app=nginx
Type:                     LoadBalancer
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.99.239.196
IPs:                      10.99.239.196
LoadBalancer Ingress:     10.31.0.48 (VIP)
Port:                     &amp;lt;unset&amp;gt;  80/TCP
TargetPort:               80/TCP
NodePort:                 &amp;lt;unset&amp;gt;  31426/TCP
Endpoints:                192.168.69.193:80
Session Affinity:         None
External Traffic Policy:  Cluster
Internal Traffic Policy:  Cluster
Events:
  Type    Reason                Age                 From                Message
  ----    ------                ----                ----                -------
  Normal  EnsuringLoadBalancer  23s (x2 over 117s)  service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   23s (x2 over 23s)   service-controller  Ensured load balancer

root@hind-cluster-control-plane-wggsv:~# kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       &amp;lt;none&amp;gt;        443/TCP        95m
nginx        LoadBalancer   10.99.239.196   10.31.0.48    80:31426/TCP   4m49s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can see that the service got an IP from provider network. Let's verify it through curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Welcome to nginx!&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;35em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tahoma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Verdana&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to nginx!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;If you see this page, nginx is successfully installed and working.
Further configuration is required for the web server, reverse proxy, 
API gateway, load balancer, content cache, or other features.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;For online documentation and support please refer to
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://nginx.org/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;nginx.org&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;br/&amp;gt;&lt;/span&gt;
To engage with the community please visit
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://community.nginx.org/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;community.nginx.org&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;br/&amp;gt;&lt;/span&gt;
For enterprise grade support, professional services, additional 
security features and capabilities please refer to
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://f5.com/nginx"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;f5.com/nginx&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;&lt;/span&gt;Thank you for using nginx.&lt;span class="nt"&gt;&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
root@hind-cluster-control-plane-wggsv:~# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's scale the pods to 2 replicas and see the loadbalancing in action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@hind-cluster-control-plane-wggsv:~# kubectl scale deploy nginx &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
deployment.apps/nginx scaled

&lt;span class="c"&gt;# In the first replica:&lt;/span&gt;
root@nginx-66686b6766-549cc:/usr/share/nginx/html# &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Replica-1"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; index.html 
&lt;span class="c"&gt;# In the second replica:&lt;/span&gt;
root@nginx-66686b6766-mgks5:/usr/share/nginx/html# &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Replica-2"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; index.html 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify loadbalancing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
Replica-2
root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
Replica-1
root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
Replica-1
root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
Replica-1
root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
Replica-1
root@hind-cluster-control-plane-wggsv:~# curl  10.31.0.48
Replica-2
root@hind-cluster-control-plane-wggsv:~# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;That's all for now. If you found this helpful or got stuck somewhere, drop a comment below. This stuff took me a long time to piece together — happy to help you shortcut that journey.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>clusterapi</category>
      <category>kubernetes</category>
      <category>openstack</category>
      <category>containers</category>
    </item>
  </channel>
</rss>
