<?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>Forem: Paul Yu</title>
    <description>The latest articles on Forem by Paul Yu (@pauldotyu).</description>
    <link>https://forem.com/pauldotyu</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%2F160455%2Fbb8098fe-fd3c-4537-b40e-1351043ac2f0.jpg</url>
      <title>Forem: Paul Yu</title>
      <link>https://forem.com/pauldotyu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pauldotyu"/>
    <language>en</language>
    <item>
      <title>Installing VMWare Workstation Pro on Ubuntu Desktop</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Wed, 22 Oct 2025 13:00:00 +0000</pubDate>
      <link>https://forem.com/pauldotyu/installing-vmware-workstation-pro-on-ubuntu-desktop-4n1m</link>
      <guid>https://forem.com/pauldotyu/installing-vmware-workstation-pro-on-ubuntu-desktop-4n1m</guid>
      <description>&lt;p&gt;This is a guide that will walk you through the steps to install VMWare Workstation Pro on Ubuntu Desktop. I am currently using Ubuntu 24.10 with version 6.11.x of the Linux kernel on a UEFI-based system with secure boot enabled. This guide is based on my experience with this setup and may not apply to all systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download VMWare Workstation Pro
&lt;/h2&gt;

&lt;p&gt;To start, browse to &lt;a href="https://support.broadcom.com" rel="noopener noreferrer"&gt;https://support.broadcom.com&lt;/a&gt; and login or create an account if you don't already have one. &lt;/p&gt;

&lt;p&gt;Once you have logged in, navigate to the &lt;a href="https://support.broadcom.com/group/ecx/free-downloads" rel="noopener noreferrer"&gt;free downloads page&lt;/a&gt; and search for "VMWare Workstation Pro". &lt;/p&gt;

&lt;p&gt;In the search results, click on the &lt;a href="https://support.broadcom.com/group/ecx/productdownloads?subfamily=VMware%20Workstation%20Pro&amp;amp;freeDownloads=true" rel="noopener noreferrer"&gt;VMWare Workstation Pro&lt;/a&gt; link then click on &lt;strong&gt;VMware Workstation Pro 17.0 for Linux&lt;/strong&gt; to expand the download options.&lt;/p&gt;

&lt;p&gt;Download the &lt;a href="https://support.broadcom.com/group/ecx/productfiles?subFamily=VMware%20Workstation%20Pro&amp;amp;displayGroup=VMware%20Workstation%20Pro%2017.0%20for%20Linux&amp;amp;release=17.6.2&amp;amp;os=&amp;amp;servicePk=526673&amp;amp;language=EN&amp;amp;freeDownloads=true" rel="noopener noreferrer"&gt;17.6.2&lt;/a&gt; release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install VMWare Workstation Pro
&lt;/h2&gt;

&lt;p&gt;With the download complete, open a terminal and navigate to the directory where the installer was downloaded. &lt;/p&gt;

&lt;p&gt;Run the following command to make the installer executable.&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;sudo chmod&lt;/span&gt; +x VMware-Workstation-Full-17.6.2-24409262.x86_64.bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install some pre-requisite packages before you attempt to install VMWare Workstation Pro.&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;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;build-essential linux-headers-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to install the VMware bundle.&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;sudo&lt;/span&gt; ./VMware-Workstation-Full-17.6.2-24409262.x86_64.bundle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manually install virtual machine modules
&lt;/h3&gt;

&lt;p&gt;Run the following command to get the status of the vmware service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status vmware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see that the service is &lt;code&gt;inactive (dead)&lt;/code&gt;, you will need to install the &lt;strong&gt;vmmon&lt;/strong&gt; and &lt;strong&gt;vmnet&lt;/strong&gt; modules manually.&lt;/p&gt;

&lt;p&gt;As mentioned above, I am using a UEFI-based system with secure boot enabled. So I need to install the modules, sign the files, and enroll the MOK key.&lt;/p&gt;

&lt;p&gt;There are a few open-source repositories that provide the modules. I am using the &lt;a href="https://github.com/bytium/vm-host-modules" rel="noopener noreferrer"&gt;vmware-host-modules by @bytium&lt;/a&gt; because it offers a build for the 17.6.2 release using newer kernel versions... Thanks &lt;a href="https://github.com/bytium" rel="noopener noreferrer"&gt;@bytium&lt;/a&gt; 🎉&lt;/p&gt;

&lt;p&gt;Run the following command to clone the repository and checkout the 17.6.2 branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/bytium/vm-host-modules.git
&lt;span class="nb"&gt;cd &lt;/span&gt;vm-host-modules
git checkout 17.6.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following commands to build and install the module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make
&lt;span class="nb"&gt;sudo &lt;/span&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following commands to sign the module, enter a password for the MOK key and import it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-keyout&lt;/span&gt; MOK.priv &lt;span class="nt"&gt;-outform&lt;/span&gt; DER &lt;span class="nt"&gt;-out&lt;/span&gt; MOK.der &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 36500 &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=VMware/"&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/src/linux-headers-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/scripts/sign-file sha256 ./MOK.priv ./MOK.der &lt;span class="si"&gt;$(&lt;/span&gt;modinfo &lt;span class="nt"&gt;-n&lt;/span&gt; vmmon&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/src/linux-headers-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/scripts/sign-file sha256 ./MOK.priv ./MOK.der &lt;span class="si"&gt;$(&lt;/span&gt;modinfo &lt;span class="nt"&gt;-n&lt;/span&gt; vmnet&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mokutil &lt;span class="nt"&gt;--import&lt;/span&gt; MOK.der
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reboot your system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pay close attention to the boot process. You will see a blue screen that says "Press any key to perform MOK management". Press any key to enter the MOK management utility.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the MOK management utility, select "Enroll MOK" then select "Continue". You will be prompted to enter the password you set when signing the module.&lt;/p&gt;

&lt;p&gt;Once you have entered the password, select "Reboot" to reboot your system again.&lt;/p&gt;

&lt;p&gt;After your system has rebooted, run the following command to check the status of the vmware service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status vmware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see that the service is now &lt;code&gt;active (running)&lt;/code&gt;. At this point you should be good create virtual machines. &lt;/p&gt;

&lt;p&gt;Optionally, you can restart the vmware service to verify that all the services are running.&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;sudo&lt;/span&gt; /etc/init.d/vmware restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stopping VMware services:
   VMware Authentication Daemon                                        done
   Virtual machine monitor                                             done
Starting VMware services:
   Virtual machine monitor                                             done
   Virtual machine communication interface                             done
   VM communication interface socket family                            done
   Virtual ethernet                                                    done
   VMware Authentication Daemon                                        done
   Shared Memory Available                                             done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure VMWare Workstation Pro
&lt;/h2&gt;

&lt;p&gt;There are some additional configurations that you should make to VMWare Workstation Pro.&lt;/p&gt;

&lt;p&gt;Open a terminal and run the following command to start VMWare Workstation Pro as root.&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;sudo &lt;/span&gt;vmware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Memory settings
&lt;/h3&gt;

&lt;p&gt;In the VMWare Workstation Pro window, click on &lt;strong&gt;Edit&lt;/strong&gt; then &lt;strong&gt;Preferences&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Memory&lt;/strong&gt;, select &lt;strong&gt;Fit all virtual machine memory into reserved host RAM&lt;/strong&gt; under &lt;strong&gt;Additional Memory&lt;/strong&gt; settings, then click &lt;strong&gt;Close&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network settings
&lt;/h3&gt;

&lt;p&gt;By default, VMWare Workstation Pro uses DHCP to assign IP addresses to virtual machines. If you want to use a static IP address, you will need to know network settings for the NAT network.&lt;/p&gt;

&lt;p&gt;With VMWare Workstation Pro open as root, click on &lt;strong&gt;Edit&lt;/strong&gt; then &lt;strong&gt;Virtual Network Editor&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Select the &lt;strong&gt;NAT&lt;/strong&gt; network and click on &lt;strong&gt;NAT Settings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here you will see the Subnet IP, Subnet Mask, and Gateway IP. You will need these values to configure static IP addresses for your virtual machines.&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%2Frynqysa2f1muen2u1rh2.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%2Frynqysa2f1muen2u1rh2.png" alt="VMWare NAT Settings" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this guide, you learned how to install VMWare Workstation Pro on Ubuntu Desktop. You also learned how to install the &lt;strong&gt;vmmon&lt;/strong&gt; and &lt;strong&gt;vmnet&lt;/strong&gt; modules manually and sign them for use a UEFI-based system with secure boot enabled. The manual installation, signing, and enrollment of the MOK key may not be necessary for all systems. But if you are in a similar situation, you should be able to get VMWare Workstation Pro up and running with these steps.&lt;/p&gt;

&lt;p&gt;Hope this helps and let me know in the comments below if this guide was helpful or if you have any questions.&lt;/p&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

&lt;h2&gt;
  
  
  Uninstall VMWare Workstation Pro
&lt;/h2&gt;

&lt;p&gt;If you need to uninstall VMWare Workstation Pro, you can run 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;&lt;span class="nb"&gt;sudo &lt;/span&gt;vmware-installer &lt;span class="nt"&gt;-u&lt;/span&gt; vmware-workstation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be prompted to keep or remove the configuration files. Type &lt;code&gt;yes&lt;/code&gt; to remove the configuration files or &lt;code&gt;quit&lt;/code&gt; to cancel the uninstallation process.&lt;/p&gt;

</description>
      <category>vmware</category>
      <category>ubuntu</category>
      <category>linux</category>
      <category>secureboot</category>
    </item>
    <item>
      <title>Local LLMs: Running Ollama and Open WebUI in Docker on Ubuntu</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Wed, 08 Oct 2025 13:00:00 +0000</pubDate>
      <link>https://forem.com/pauldotyu/running-ollama-locally-with-open-webui-ol1</link>
      <guid>https://forem.com/pauldotyu/running-ollama-locally-with-open-webui-ol1</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; is a popular tool for running large language models (LLMs) locally on your machine. It provides a simple interface to interact with various models without needing an internet connection. &lt;a href="https://docs.openwebui.com" rel="noopener noreferrer"&gt;Open WebUI&lt;/a&gt; is a web-based user interface that allows you to interact with LLMs through a browser.&lt;/p&gt;

&lt;p&gt;I am working on an Ubuntu 24.04.3 LTS Desktop machine with decent hardware to run models locally, so my preference is to run Ollama as a local service rather than confining it to a container. This way, Ollama can take full advantage of my machine's capabilities, especially the GPU.&lt;/p&gt;

&lt;p&gt;Open WebUI is also an application that I'd like constantly running in the background so I can access it anytime without remembering to start it up. Naturally, running the web application in a container makes sense for this use case.&lt;/p&gt;

&lt;p&gt;Getting these two components to work together came with a few challenges. In this post, I'll walk you through the steps I took to set up Ollama as a local service with Open WebUI running in a Docker container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To get started, you'll need to have Docker installed on your machine. If you haven't done so already, you can download and install Docker Desktop by following the &lt;a href="https://docs.docker.com/get-started/get-docker/" rel="noopener noreferrer"&gt;Get Docker guide&lt;/a&gt;. However, I prefer to run Docker Engine directly so I followed this &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Install Docker Engine guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Install Ollama
&lt;/h2&gt;

&lt;p&gt;Depending on your operating system, you can follow the instructions on the &lt;a href="https://docs.ollama.com/quickstart" rel="noopener noreferrer"&gt;Ollama Quickstart page&lt;/a&gt; to install Ollama. For Ubuntu, I followed the &lt;a href="https://docs.ollama.com/linux" rel="noopener noreferrer"&gt;Linux installation instructions&lt;/a&gt;. This can take a while as it downloads the necessary files and GPU accelerators if applicable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I tried this using both AMD and NVIDIA GPUs and they both worked fine. The GPUs were detected automatically by Ollama, which is nice!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Configure Ollama
&lt;/h2&gt;

&lt;p&gt;Once Ollama is installed, it runs as a local service on your machine. You can verify that it's running by executing the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output indicating that the Ollama service is active and running.&lt;/p&gt;

&lt;p&gt;But before we can use it in a way that it can be called from a container, we need to make a small configuration change. By default, Ollama listens on &lt;code&gt;127.0.0.1&lt;/code&gt; which means it can only accept connections from the local machine.&lt;/p&gt;

&lt;p&gt;To verify this, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tuln&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tcp   LISTEN 0      4096        127.0.0.1:11434      0.0.0.0:*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To allow connections from other containers, we need to change this to listen on &lt;code&gt;0.0.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To do this, we need to modify the Ollama service configuration file. Run the following command to open the configuration file in a text editor:&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;sudo &lt;/span&gt;systemctl edit ollama.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see a file that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### Editing /etc/systemd/system/ollama.service.d/override.conf
### Anything between here and the comment below will become the contents of the drop-in file

&amp;lt;HERE_IS_WHERE_YOU_SHOULD_ADD_YOUR_CHANGES&amp;gt;

### Edits below this comment will be discarded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There will be a big blank space where you can add your changes. Add the following lines to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
Environment="OLLAMA_CONTEXT_LENGTH=32768"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note the &lt;code&gt;OLLAMA_CONTEXT_LENGTH&lt;/code&gt; setting. I plan to use models that support long context lengths, so I set this to 32k. You should adjust this value based on the models you plan to use or omit it if you want to stick with the default. You can always change this in other ways. You can find a lot more helpful tips on configuring this in the &lt;a href="https://github.com/ollama/ollama/blob/main/docs/faq.md" rel="noopener noreferrer"&gt;Ollama FAQ&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save and exit the editor. Then, reload the systemd configuration and restart the Ollama service to apply the changes:&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;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify that Ollama is now listening on all interfaces by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tuln&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tcp   LISTEN 0      4096                *:11434            *:*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Ollama is configured to accept connections on all interfaces!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: By configuring Ollama to listen on &lt;code&gt;0.0.0.0&lt;/code&gt;, it becomes accessible to your entire local network. This is intentional and beneficial for several reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows multiple containerized applications to connect to Ollama (not just Open WebUI)&lt;/li&gt;
&lt;li&gt;Enables testing AI applications from other devices on your network&lt;/li&gt;
&lt;li&gt;Supports development workflows where you might have multiple services that need LLM access&lt;/li&gt;
&lt;li&gt;Makes it easy to experiment with different AI tools and interfaces without reconfiguring Ollama each time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're concerned about security, ensure you're on a trusted network or configure your firewall to restrict access to port 11434. For production environments, consider setting up authentication or using a reverse proxy.&lt;/p&gt;

&lt;p&gt;To put it back to the way it was, just delete the file &lt;code&gt;/etc/systemd/system/ollama.service.d/override.conf&lt;/code&gt; and restart the Ollama service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, run the following command to pull a model to use with Open WebUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gpt-oss:20b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will download the &lt;code&gt;gpt-oss:20b&lt;/code&gt; model to your local machine. I chose this model because it's a capable open-source model that runs well on consumer hardware while providing good performance for general-purpose tasks. Feel free to use any other model available in the &lt;a href="https://ollama.com/models" rel="noopener noreferrer"&gt;Ollama model registry&lt;/a&gt; such as &lt;code&gt;llama3&lt;/code&gt;, &lt;code&gt;mistral&lt;/code&gt;, or &lt;code&gt;gemma&lt;/code&gt; based on your hardware capabilities and use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Run Open WebUI in a Docker Container
&lt;/h2&gt;

&lt;p&gt;Now that Ollama is set up and running the way we want, we can proceed to run Open WebUI in a Docker container.&lt;/p&gt;

&lt;p&gt;Run the following command to start the Open WebUI container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-p&lt;/span&gt; 9090:8080 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; open-webui &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--add-host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host.docker.internal:host-gateway &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.open-webui:/app/backend/data &lt;span class="se"&gt;\&lt;/span&gt;
ghcr.io/open-webui/open-webui:v0.6.33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt;: Runs the container in detached mode (in the background).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p 9090:8080&lt;/code&gt;: Maps port 8080 in the container to port 9090 on your host machine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name open-webui&lt;/code&gt;: Names the container "open-webui".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--restart unless-stopped&lt;/code&gt;: Ensures the container restarts automatically unless explicitly stopped.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--add-host=host.docker.internal:host-gateway&lt;/code&gt;: Adds a host entry to allow the container to access services running on the host.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-v $HOME/.open-webui:/app/backend/data&lt;/code&gt;: Mounts a volume to persist data so that your settings and chat history are saved even if the container is removed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ghcr.io/open-webui/open-webui:v0.6.33&lt;/code&gt;: Specifies the image to use for the container. You can check for the latest version on the &lt;a href="https://github.com/open-webui/open-webui/releases" rel="noopener noreferrer"&gt;Open WebUI releases page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After initially running the container, run the following command to check the status of the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;open-webui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait until the &lt;strong&gt;STATUS&lt;/strong&gt; column shows &lt;code&gt;Up&lt;/code&gt; and &lt;code&gt;(healthy)&lt;/code&gt; before proceeding.&lt;/p&gt;

&lt;p&gt;Once you've confirmed the container is running, you can access the Open WebUI interface by navigating to &lt;code&gt;http://localhost:9090&lt;/code&gt; in your web browser.&lt;/p&gt;

&lt;p&gt;Proceed to set up Open WebUI by following the on-screen instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Use Open WebUI with Ollama
&lt;/h2&gt;

&lt;p&gt;At this point you should be on an empty Open WebUI interface. To connect it to your local Ollama service, click the &lt;strong&gt;Select a model&lt;/strong&gt; drop down at the top left of the page. If all went well, you should see a list of models available from your local Ollama installation.&lt;/p&gt;

&lt;p&gt;Select the model you pulled earlier (e.g., &lt;code&gt;gpt-oss:20b&lt;/code&gt;) and start interacting with it through the web interface!&lt;/p&gt;

&lt;p&gt;When you get the notification from Open WebUI that a new version is available, all you need to do is stop the container, and re-run the &lt;code&gt;docker run&lt;/code&gt; command from Step 3 with the new version number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternative options
&lt;/h2&gt;

&lt;p&gt;Another option to run the Open WebUI container and avoid making changes to the Ollama service is to run the container with 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;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;OLLAMA_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:11434/ &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.open-webui:/app/backend/data &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; open-webui &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--restart&lt;/span&gt; unless-stopped &lt;span class="se"&gt;\&lt;/span&gt;
ghcr.io/open-webui/open-webui:v0.6.33
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command uses the &lt;code&gt;--network=host&lt;/code&gt; option to allow the container to share the host's network stack, making it easier to connect to services running on the host machine. The &lt;code&gt;-e OLLAMA_BASE_URL=http://127.0.0.1:11434/&lt;/code&gt; environment variable is set to point to the Ollama service running on the host.&lt;/p&gt;

&lt;p&gt;While this approach is simpler, I chose the primary method (exposing Ollama on &lt;code&gt;0.0.0.0&lt;/code&gt;) for better container isolation. With &lt;code&gt;--network=host&lt;/code&gt;, the Open WebUI container can access all network services on my host machine, including services that might be running on localhost that weren't intended to be exposed. By using bridge networking with &lt;code&gt;--add-host=host.docker.internal:host-gateway&lt;/code&gt;, the container can only access services that are explicitly listening on the network, giving me more control over what the container can reach. This follows the principle of least privilege - the container only has access to what it needs (Ollama) rather than unrestricted access to my entire host network.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we walked through the steps to set up Ollama and Open WebUI using Docker containers on a Linux machine. By configuring Ollama to listen on all interfaces and running Open WebUI in a container, we created a flexible and accessible environment for interacting with large language models locally.&lt;/p&gt;

&lt;p&gt;This Ollama setup also allows you to build and test your own AI applications against Ollama models running locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;When you want to stop and remove the Open WebUI container, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm &lt;/span&gt;open-webui &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that this removes the container but preserves your data in &lt;code&gt;$HOME/.open-webui&lt;/code&gt;. Your chat history, settings, and configurations will remain intact if you decide to run the container again later.&lt;/p&gt;

&lt;p&gt;If you want to completely remove all Open WebUI data as well, run:&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;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.open-webui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stop the Ollama service, you can run:&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;sudo &lt;/span&gt;systemctl stop ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or to disable it from starting automatically on boot:&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;sudo &lt;/span&gt;systemctl disable ollama
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to explore different models and configurations to suit your needs. Happy chatting!&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.ollama.com/" rel="noopener noreferrer"&gt;Ollama Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ollama/ollama/blob/main/docs/faq.md" rel="noopener noreferrer"&gt;Ollama FAQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.openwebui.com/" rel="noopener noreferrer"&gt;Open WebUI Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ollama</category>
      <category>openwebui</category>
      <category>containers</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Build a Weather MCP Server with Rust: Complete Tutorial for AI Integration</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Tue, 10 Jun 2025 13:00:00 +0000</pubDate>
      <link>https://forem.com/pauldotyu/build-a-weather-mcp-server-with-rust-complete-tutorial-for-ai-integration-1o3i</link>
      <guid>https://forem.com/pauldotyu/build-a-weather-mcp-server-with-rust-complete-tutorial-for-ai-integration-1o3i</guid>
      <description>&lt;p&gt;Building AI tools that can access real-time data requires bridging the gap between language models and external APIs. The Model Context Protocol (MCP) makes this possible by providing a standard way for AI assistants to interact with data sources.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll build a weather MCP server using Rust that connects to the National Weather Service API, giving any MCP-compatible AI assistant the ability to fetch live weather alerts and forecasts.&lt;/p&gt;

&lt;p&gt;This walkthrough uses the &lt;em&gt;soon-to-be-released&lt;/em&gt; &lt;a href="https://github.com/modelcontextprotocol/rust-sdk" rel="noopener noreferrer"&gt;rust-sdk&lt;/a&gt; also known as the &lt;a href="https://crates.io/crates/rmcp" rel="noopener noreferrer"&gt;rmcp&lt;/a&gt; crate, and builds on examples from the &lt;a href="https://github.com/modelcontextprotocol/rust-sdk/tree/main/examples/servers" rel="noopener noreferrer"&gt;rust-sdk MCP server examples&lt;/a&gt; and the &lt;a href="https://modelcontextprotocol.io/quickstart/server" rel="noopener noreferrer"&gt;official quickstart guides for MCP server developers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, ensure you have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.rust-lang.org/tools/install" rel="noopener noreferrer"&gt;Rust and Cargo&lt;/a&gt; installed on your machine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node.js 22+ and npm&lt;/a&gt; installed for running the &lt;a href="https://github.com/modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;MCP inspector&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you are just getting started with MCP, I recommend checking out the &lt;a href="https://aka.ms/mcp-for-beginners" rel="noopener noreferrer"&gt;Model Context Protocol (MCP) for Beginners&lt;/a&gt; course and join the community on the &lt;a href="https://aka.ms/foundry/discord%E2%80%8B" rel="noopener noreferrer"&gt;Microsoft AI Foundry Discord server&lt;/a&gt; or the &lt;a href="https://aka.ms/foundry/forum" rel="noopener noreferrer"&gt;Microsoft AI Foundry GitHub Discussions&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;Create a new Rust project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo new weather
&lt;span class="nb"&gt;cd &lt;/span&gt;weather
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add these dependencies to your &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/rmcp" rel="noopener noreferrer"&gt;rmcp&lt;/a&gt;&lt;/strong&gt;: The MCP SDK for Rust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/tokio" rel="noopener noreferrer"&gt;tokio&lt;/a&gt;&lt;/strong&gt;: For asynchronous runtime and I/O operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/serde" rel="noopener noreferrer"&gt;serde&lt;/a&gt;&lt;/strong&gt;: For serializing and deserializing data structures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/serde_json" rel="noopener noreferrer"&gt;serde_json&lt;/a&gt;&lt;/strong&gt;: For JSON serialization and deserialization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/anyhow" rel="noopener noreferrer"&gt;anyhow&lt;/a&gt;&lt;/strong&gt;: For error handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/tracing" rel="noopener noreferrer"&gt;tracing&lt;/a&gt;&lt;/strong&gt;: For logging and diagnostics.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/tracing-subscriber" rel="noopener noreferrer"&gt;tracing-subscriber&lt;/a&gt;&lt;/strong&gt;: For subscribing to tracing events and filtering logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crates.io/crates/reqwest" rel="noopener noreferrer"&gt;reqwest&lt;/a&gt;&lt;/strong&gt;: For making HTTP requests to the National Weather Service API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can add these dependencies manually to your &lt;strong&gt;Cargo.toml&lt;/strong&gt; file, but I like to use the &lt;code&gt;cargo add&lt;/code&gt; command to add them easily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo add rmcp &lt;span class="nt"&gt;--features&lt;/span&gt; server,transport-io
cargo add tokio &lt;span class="nt"&gt;--features&lt;/span&gt; macros,rt-multi-thread
cargo add serde &lt;span class="nt"&gt;--features&lt;/span&gt; derive
cargo add serde_json
cargo add anyhow
cargo add tracing
cargo add tracing-subscriber &lt;span class="nt"&gt;--features&lt;/span&gt; env-filter
cargo add reqwest &lt;span class="nt"&gt;--features&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the commands above, here is how your &lt;strong&gt;Cargo.toml&lt;/strong&gt; should look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"weather"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2024"&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;anyhow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.98"&lt;/span&gt;
&lt;span class="py"&gt;reqwest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.12.19"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;rmcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"transport-io"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.219"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"derive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;serde_json&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0.140"&lt;/span&gt;
&lt;span class="py"&gt;tokio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.45.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"macros"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rt-multi-thread"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.41"&lt;/span&gt;
&lt;span class="py"&gt;tracing-subscriber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.3.19"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"env-filter"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the project in your favorite code editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the weather server
&lt;/h2&gt;

&lt;p&gt;The MCP server will expose two tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;get_alerts&lt;/code&gt;: Returns weather alerts for a given state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_forecast&lt;/code&gt;: Returns the weather forecast for a given location (latitude and longitude coordinates).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open the &lt;strong&gt;src/main.rs&lt;/strong&gt; file and add the following code to the top. This will import the necessary crates and modules for building the MCP server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="n"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rmcp&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="n"&gt;ServerHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ServiceExt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;model&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;ServerCapabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ServerInfo&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;schemars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EnvFilter&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;NWS_API_BASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://api.weather.gov"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;USER_AGENT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"weather-app/1.0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;src/main.rs&lt;/strong&gt; file includes a &lt;code&gt;main&lt;/code&gt; function that you will implement later to run the server. Leave it as is for now and add code above the main function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Testing NWS API endpoints
&lt;/h2&gt;

&lt;p&gt;To retrieve weather data, the server will make HTTP requests to the &lt;a href="https://www.weather.gov/documentation/services-web-api" rel="noopener noreferrer"&gt;National Weather Service (NWS) API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This RESTful API has several endpoints that allow you to access weather data without requiring an API key. All that is required is to set a user agent in the HTTP request headers.&lt;/p&gt;

&lt;p&gt;The following endpoints will be used in this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alerts Endpoint&lt;/strong&gt;: &lt;code&gt;https://api.weather.gov/alerts/active?area={state}&lt;/code&gt; - This endpoint returns active weather alerts for a given state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Points Endpoint&lt;/strong&gt;: &lt;code&gt;https://api.weather.gov/points/{latitude},{longitude}&lt;/code&gt; - This endpoint returns the forecast URL for a specific latitude and longitude.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forecast Endpoint&lt;/strong&gt;: &lt;code&gt;https://api.weather.gov/gridpoints/{office}/{gridX},{gridY}/forecast&lt;/code&gt; - This endpoint returns the weather forecast for a specific grid point.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To test these endpoints manually, you can use the &lt;code&gt;curl&lt;/code&gt; command or any HTTP client to make requests to the NWS API.&lt;/p&gt;

&lt;p&gt;For example, to get weather alerts for a state, you can use the following command to make a request to the &lt;strong&gt;alerts&lt;/strong&gt; endpoint:&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="s2"&gt;"https://api.weather.gov/alerts/active?area=CA"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the weather forecast, you would need to make a request to the &lt;strong&gt;points&lt;/strong&gt; endpoint for a specific location. In the response, you will receive a &lt;strong&gt;forecast&lt;/strong&gt; URL which you can use to get the forecast data.&lt;/p&gt;

&lt;p&gt;For example, to get the forecast for Los Angeles, you can use 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;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"https://api.weather.gov/points/34.0499998,-118.249999"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The NWS API redirects precise latitude and longitude to a standardized grid point covering a larger region. Use the &lt;code&gt;-L&lt;/code&gt; flag to follow this redirect and access the normalized endpoint.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Within the points response, you will find a &lt;code&gt;forecast&lt;/code&gt; field that contains the URL for the forecast data.&lt;/p&gt;

&lt;p&gt;Use the forecast URL to get the forecast data:&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="s2"&gt;"https://api.weather.gov/gridpoints/LOX/155,45/forecast"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the forecast response, you will find a &lt;code&gt;properties&lt;/code&gt; field that contains the forecast data, which includes an array of &lt;code&gt;periods&lt;/code&gt; with details about the weather forecast for different times of the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modeling the weather data
&lt;/h2&gt;

&lt;p&gt;To work with the weather data returned by the NWS API, define &lt;a href="https://doc.rust-lang.org/book/ch05-01-defining-structs.html" rel="noopener noreferrer"&gt;Rust structs&lt;/a&gt; that match the structure of the JSON data returned by the API. This will allow the server to deserialize the JSON data into Rust types using the &lt;code&gt;serde&lt;/code&gt; crate.&lt;/p&gt;

&lt;p&gt;Add the following code to the &lt;strong&gt;src/main.rs&lt;/strong&gt; file to define the structs for the weather data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AlertResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Feature&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Feature&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;FeatureProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;FeatureProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"areaDesc"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;area_desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PointsRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[schemars(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"latitude of the location in decimal format"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[schemars(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"longitude of the location in decimal format"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PointsResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PointsProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PointsProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;GridPointsResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;GridPointsProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;GridPointsProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Period&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;serde::Deserialize,&lt;/span&gt; &lt;span class="nd"&gt;schemars::JsonSchema)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Period&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"temperatureUnit"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;temperature_unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"windSpeed"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;wind_speed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"windDirection"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;wind_direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(rename&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shortForecast"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;short_forecast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These structs represent the data returned by the NWS API for weather alerts and forecasts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding helper functions
&lt;/h2&gt;

&lt;p&gt;The server will return weather alerts and forecasts in a human-readable format. To achieve this, add the following helper functions to format the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;format_alerts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Feature&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"No active alerts found."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;alert&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alerts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Event: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Area: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Severity: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Status: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Headline: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="py"&gt;.properties.event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="py"&gt;.properties.area_desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="py"&gt;.properties.severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="py"&gt;.properties.status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="py"&gt;.properties.headline&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;format_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Period&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="nf"&gt;.is_empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"No forecast data available."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;periods&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;periods&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Name: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Temperature: {}°{}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Wind: {} {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Forecast: {}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="py"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="py"&gt;.temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="py"&gt;.temperature_unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="py"&gt;.wind_speed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="py"&gt;.wind_direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="py"&gt;.short_forecast&lt;/span&gt;
        &lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing the weather tools
&lt;/h2&gt;

&lt;p&gt;Add the following code to define a &lt;code&gt;Weather&lt;/code&gt; struct that will hold the HTTP client used to make requests to the NWS API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, implement the &lt;code&gt;Weather&lt;/code&gt; struct with a constructor that initializes the HTTP client with a user agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool(tool_box)]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[allow(dead_code)]&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.user_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USER_AGENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to create HTTP client"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code creates a new instance of the &lt;code&gt;Weather&lt;/code&gt; struct with an HTTP client that has the user agent set to &lt;code&gt;weather-app/1.0&lt;/code&gt;. The client is a reusable instance that will be used to make requests to the NWS API.&lt;/p&gt;

&lt;p&gt;As demonstrated in the section above, there will be a few HTTP requests made to the NWS API. To make the code cleaner and more reusable, create a &lt;code&gt;make_request&lt;/code&gt; function within the &lt;code&gt;Weather&lt;/code&gt; struct.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;make_request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;de&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DeserializeOwned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Making request to: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
        &lt;span class="py"&gt;.client&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.send&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received response: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="nf"&gt;.status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;reqwest&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
            &lt;span class="py"&gt;.json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;.await&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to parse response: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed with status: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will handle making HTTP GET requests and deserializing the JSON response into the specified type. It takes a URL as input, makes an HTTP GET request to that URL, and returns the deserialized response as the specified type &lt;code&gt;T&lt;/code&gt;. If the request fails or the response cannot be parsed, it returns an error message.&lt;/p&gt;

&lt;p&gt;Add the following code to implement the &lt;code&gt;get_alerts&lt;/code&gt; tool. This function retrieves weather alerts for a specified US state. It constructs the URL for the NWS API, makes the request, and formats the alerts into a human-readable string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Get weather alerts for a US state"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_alerts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[tool(param)]&lt;/span&gt;
    &lt;span class="nd"&gt;#[schemars(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"the US state to get alerts for"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received request for weather alerts in state: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/alerts/active?area={}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NWS_API_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.make_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AlertResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;format_alerts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;alerts&lt;/span&gt;&lt;span class="py"&gt;.features&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to fetch alerts: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;"No alerts found or an error occurred."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code to implement the &lt;code&gt;get_forecast&lt;/code&gt; tool. This function retrieves the weather forecast for a specific location using latitude and longitude coordinates. As demonstrated in the section above, it first makes a request to the &lt;strong&gt;points&lt;/strong&gt; endpoint to get the &lt;strong&gt;forecast&lt;/strong&gt; URL, then uses that URL to retrieve the actual forecast data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool(description&lt;/span&gt; &lt;span class="nd"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Get forecast using latitude and longitude coordinates"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[tool(aggr)]&lt;/span&gt; &lt;span class="n"&gt;PointsRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="n"&gt;PointsRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Received coordinates: latitude = {}, longitude = {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;points_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}/points/{},{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NWS_API_BASE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;points_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.make_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PointsResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;points_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;points_result&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to fetch points: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"No forecast found or an error occurred."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
        &lt;span class="py"&gt;.make_request&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GridPointsResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="py"&gt;.properties.forecast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;format_forecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;forecast&lt;/span&gt;&lt;span class="py"&gt;.properties.periods&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to fetch forecast: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="s"&gt;"No forecast found or an error occurred."&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add the following code to implement the &lt;a href="https://docs.rs/rmcp/latest/rmcp/handler/server/trait.ServerHandler.html" rel="noopener noreferrer"&gt;&lt;code&gt;ServerHandler&lt;/code&gt; trait&lt;/a&gt; for the &lt;code&gt;Weather&lt;/code&gt; struct. This &lt;a href="https://doc.rust-lang.org/book/ch10-02-traits.html" rel="noopener noreferrer"&gt;trait&lt;/a&gt; is part of the &lt;code&gt;rmcp&lt;/code&gt; crate and allows the server to define its capabilities and instructions for clients.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tool(tool_box)]&lt;/span&gt;
&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;ServerHandler&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;Weather&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ServerInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ServerInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A simple weather forecaster"&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;ServerCapabilities&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.enable_tools&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="nn"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This code provides basic server information and capabilities, indicating that the server supports tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, implement the &lt;code&gt;main&lt;/code&gt; function to run the server. Replace the existing &lt;code&gt;main&lt;/code&gt; function in &lt;strong&gt;src/main.rs&lt;/strong&gt; with the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;tracing_subscriber&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.with_env_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;EnvFilter&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_default_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.add_directive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
        &lt;span class="nf"&gt;.with_writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.with_ansi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting MCP server"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Weather&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;stdio&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.inspect_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;error!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"serving error: {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="nf"&gt;.waiting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code initializes logging, creates the &lt;code&gt;Weather&lt;/code&gt; service, and starts the MCP server using stdio transport.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A reference to the final &lt;strong&gt;src/main.rs&lt;/strong&gt; file can be found here: &lt;a href="https://github.com/pauldotyu/mcp-weather-rust/blob/main/src/main.rs" rel="noopener noreferrer"&gt;src/main.rs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following commands to format the code and build the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;fmt
&lt;/span&gt;cargo build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all goes well, you should see no errors, and the project will build successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with MCP Inspector
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;Model Context Protocol Inspector&lt;/a&gt; is handy web tool for testing MCP servers. Run the following command to start the MCP Inspector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @modelcontextprotocol/inspector cargo run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the MCP Inspector is started, navigate to &lt;a href="http://127.0.0.1:6274" rel="noopener noreferrer"&gt;http://127.0.0.1:6274&lt;/a&gt; in your web browser, connect to your MCP server, and test the tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You now have a working MCP server that provides weather data from the National Weather Service API. The server handles the multi-step API calls (points → forecast URL → actual forecast) and formats the data for AI consumption.&lt;/p&gt;

&lt;p&gt;From here, you could add error handling improvements, caching, or support for additional NWS endpoints. Check out the &lt;a href="https://github.com/modelcontextprotocol/rust-sdk/tree/main/examples/servers" rel="noopener noreferrer"&gt;Rust SDK examples&lt;/a&gt; for other patterns and features.&lt;/p&gt;

&lt;p&gt;MCP is changing the way AI assistants can access real-time data, and building servers like this one is a great way to get started. As mentioned above, if you are new to MCP, I highly recommend you work through the &lt;a href="https://aka.ms/mcp-for-beginners" rel="noopener noreferrer"&gt;Model Context Protocol (MCP) for Beginners&lt;/a&gt; course and join the community on the &lt;a href="https://aka.ms/foundry/discord%E2%80%8B" rel="noopener noreferrer"&gt;Microsoft AI Foundry Discord server&lt;/a&gt; or the &lt;a href="https://aka.ms/foundry/forum" rel="noopener noreferrer"&gt;Microsoft AI Foundry GitHub Discussions&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aka.ms/mcp-for-beginners" rel="noopener noreferrer"&gt;Model Context Protocol (MCP) for Beginners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP) official docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/rust-sdk" rel="noopener noreferrer"&gt;RMCP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/inspector" rel="noopener noreferrer"&gt;MCP Inspector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>tutorial</category>
      <category>mcp</category>
      <category>ai</category>
      <category>rust</category>
    </item>
    <item>
      <title>Deploying AKS Automatic clusters with Pulumi: A step-by-step guide</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Mon, 10 Mar 2025 13:00:00 +0000</pubDate>
      <link>https://forem.com/azure/deploying-aks-automatic-with-pulumi-a-step-by-step-guide-1o7e</link>
      <guid>https://forem.com/azure/deploying-aks-automatic-with-pulumi-a-step-by-step-guide-1o7e</guid>
      <description>&lt;p&gt;Yo! Let's build an AKS Automatic cluster with Pulumi 🚀&lt;/p&gt;

&lt;p&gt;If you don't already know, &lt;a href="https://www.pulumi.com/" rel="noopener noreferrer"&gt;Pulumi&lt;/a&gt; is a modern Infrastructure as Code (IaC) tool that allows you to use your favorite programming language to deploy and manage cloud resources. I like it because I can use &lt;a href="https://go.dev/" rel="noopener noreferrer"&gt;Go&lt;/a&gt; and the &lt;a href="https://www.pulumi.com/docs/iac/languages-sdks/go/" rel="noopener noreferrer"&gt;Pulumi Go SDK&lt;/a&gt; to write my infrastructure code. There are other languages supported like Python, TypeScript, .NET, and more so be sure to check out their &lt;a href="https://www.pulumi.com/docs/iac/languages-sdks/#pulumi-languages-sdks" rel="noopener noreferrer"&gt;docs&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;This is fairly long article, so let's dive right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, you need to have an &lt;a href="https://azure.microsoft.com/free/" rel="noopener noreferrer"&gt;Azure subscription&lt;/a&gt; and the following tools installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/cli/azure/install-azure-cli" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt; version 2.67.0 or later&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pulumi.com/docs/iac/download-install/" rel="noopener noreferrer"&gt;Pulumi CLI&lt;/a&gt; version 3.143.0 or later&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://go.dev/doc/install" rel="noopener noreferrer"&gt;Go&lt;/a&gt; version 1.23.4 or later&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://code.visualstudio.com/Download" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt; version 1.96.2 or later&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=golang.Go" rel="noopener noreferrer"&gt;Go extension for VSCode&lt;/a&gt; version 0.45.0 or later&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Login to Azure and Pulumi
&lt;/h3&gt;

&lt;p&gt;As mentioned above, I'll be using the Go SDK and using Pulumi's &lt;a href="https://www.pulumi.com/registry/packages/azure-native/" rel="noopener noreferrer"&gt;Azure Native provider&lt;/a&gt; to deploy Azure resources and will need Azure credentials to authenticate. I'll use the Azure CLI to authenticate because it's the easiest way to get started, but there are &lt;a href="https://www.pulumi.com/registry/packages/azure-native/installation-configuration/#authentication-methods" rel="noopener noreferrer"&gt;other ways to authenticate with Azure&lt;/a&gt; including OpenID Connect, Service Principal, and Managed Identity.&lt;/p&gt;

&lt;p&gt;Open a terminal and run the following command to login to Azure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login &lt;span class="nt"&gt;--use-device-code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulumi uses its own &lt;a href="https://www.pulumi.com/product/pulumi-cloud/" rel="noopener noreferrer"&gt;cloud-based service&lt;/a&gt; to store state information by default. You can setup a an account &lt;a href="https://app.pulumi.com/signup" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but if you don't want to create a Pulumi account, you can store state information locally in a file. I prefer this for quick demos and getting started as it's just easier for me and I don't have to worry about setting up or logging into an account. In a production scenario, you should consider using the cloud-based service or another backend options. Refer to the &lt;a href="https://www.pulumi.com/docs/iac/concepts/state-and-backends/" rel="noopener noreferrer"&gt;Pulumi documentation&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;Run the following command to login to Pulumi using a local file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi login file://~/.pulumi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initialize Pulumi project
&lt;/h2&gt;

&lt;p&gt;Now that we got the prerequisites out of the way, let's create a new Pulumi project for our AKS deployment.&lt;/p&gt;

&lt;p&gt;Run the following command to create a new folder for the project.&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;mkdir &lt;/span&gt;aks-pulumi-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;aks-pulumi-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to create a new Pulumi project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi new azure-go &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; aks-pulumi-demo &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"sample aks deployment with pulumi"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--stack&lt;/span&gt; dev &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--secrets-provider&lt;/span&gt; default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a breakdown of the command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pulumi new azure-go&lt;/code&gt; creates a new Pulumi project using the Azure Go template&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--name&lt;/code&gt; sets the name of the project to &lt;code&gt;aks-pulumi-demo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--description&lt;/code&gt; sets the description of the project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--stack&lt;/code&gt; sets the stack name to &lt;code&gt;dev&lt;/code&gt;; think of stack as a way to manage different environments like dev, staging, and production&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--secrets-provider&lt;/code&gt; sets the secrets provider to default which uses the local file to store secrets; you can use other providers like default, passphrase, awskms, azurekeyvault, gcpkms, or hashivault&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will be prompted to enter a passphrase to encrypt the secrets. You can leave it blank for now and hit enter to proceed.&lt;/p&gt;

&lt;p&gt;You will also be prompted to select a region to deploy the resources. Just be careful here and make sure you set the location to a region that supports all the Azure resources you intend to deploy. You can refer to the &lt;a href="https://azure.microsoft.com/explore/global-infrastructure/products-by-region/" rel="noopener noreferrer"&gt;Azure documentation&lt;/a&gt; for more information on product availability by region. I'll use &lt;code&gt;swedencentral&lt;/code&gt; for this demo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As noted in the &lt;a href="https://learn.microsoft.com/azure/aks/learn/quick-kubernetes-automatic-deploy?pivots=azure-cli" rel="noopener noreferrer"&gt;Azure docs&lt;/a&gt;, AKS Automatic tries to dynamically select a virtual machine SKU for the system node pool based on the capacity available in the subscription. Make sure your subscription has quota for 16 vCPUs of any of the following SKUs in the region you're deploying the cluster to: &lt;code&gt;Standard_D4pds_v5&lt;/code&gt;, &lt;code&gt;Standard_D4lds_v5&lt;/code&gt;, &lt;code&gt;Standard_D4ads_v5&lt;/code&gt;, &lt;code&gt;Standard_D4ds_v5&lt;/code&gt;, &lt;code&gt;Standard_D4d_v5&lt;/code&gt;, &lt;code&gt;Standard_D4d_v4&lt;/code&gt;, &lt;code&gt;Standard_DS3_v2&lt;/code&gt;, &lt;code&gt;Standard_DS12_v2&lt;/code&gt;. You can view quotas for specific VM-families and &lt;a href="https://learn.microsoft.com/azure/azure-resource-manager/management/azure-subscription-service-limits#managing-limits" rel="noopener noreferrer"&gt;submit quota increase requests through the Azure portal&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also set the location 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;pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;azure-native:location swedencentral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Setup main.go file
&lt;/h3&gt;

&lt;p&gt;At this point you should have a new Go project with a main.go file and some sample code.&lt;/p&gt;

&lt;p&gt;Here is what the directory structure should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── go.mod
├── go.sum
├── main.go
├── Pulumi.dev.yaml
└── Pulumi.yaml

1 directory, 5 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the folder with VSCode and open the &lt;code&gt;main.go&lt;/code&gt; file. Here you will see sample code to create an Azure storage account and export its keys. Delete the sample code and replace it with the code below to create an Azure resource group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/resources/v2"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/pulumi/pulumi/sdk/v3/go/pulumi"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Create an Azure Resource Group&lt;/span&gt;
        &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewResourceGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resourceGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will import the &lt;a href="https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/pulumi" rel="noopener noreferrer"&gt;Pulumi SDK&lt;/a&gt; and &lt;a href="https://pkg.go.dev/github.com/mbsulliv/pulumi-azure-native-sdk" rel="noopener noreferrer"&gt;Pulumi Azure Native SDK&lt;/a&gt; to create resource groups and sets up the main function to make a &lt;code&gt;pulumi.Run&lt;/code&gt; function call to run the Pulumi program. This function accepts a &lt;a href="https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v3/go/pulumi" rel="noopener noreferrer"&gt;RunFunc&lt;/a&gt; type which is where you define the body of the Pulumi program to run.&lt;/p&gt;

&lt;p&gt;You've just added code to create an &lt;a href="https://learn.microsoft.com/azure/azure-resource-manager/management/manage-resource-groups-portal#what-is-a-resource-group" rel="noopener noreferrer"&gt;Azure resource group&lt;/a&gt; using the &lt;code&gt;resources.NewResourceGroup&lt;/code&gt; function. This function is available via the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/resources/" rel="noopener noreferrer"&gt;resources module&lt;/a&gt; in the Azure Native SDK. The &lt;code&gt;NewResourceGroup&lt;/code&gt; function accepts a &lt;code&gt;pulumi.Context&lt;/code&gt; and a string to set the name of the resource group object.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It is important to note that the value of &lt;code&gt;resourceGroup&lt;/code&gt; in the &lt;code&gt;NewResourceGroup&lt;/code&gt; function above is not the actual name of the resource group that will be created in Azure. It's the name of the object within the Pulumi state file. More on that in a bit...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are using VSCode with the &lt;a href="https://marketplace.visualstudio.com/items?itemName=golang.Go" rel="noopener noreferrer"&gt;Go extension&lt;/a&gt;, you might notice a squiggly line under the &lt;code&gt;resourceGroup&lt;/code&gt; variable. That's because Go does not like it when variables are declared but not used. To fix this, we can export the value of the resource group by adding the following code just before the &lt;code&gt;return nil&lt;/code&gt; line inside the &lt;code&gt;pulumi.Run&lt;/code&gt; function. This will allow us to see the actual resource group name once the deployment is complete.&lt;/p&gt;

&lt;p&gt;Add the following code to the code fille.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"resourceGroupName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running Pulumi programs
&lt;/h2&gt;

&lt;p&gt;We now have a basic Pulumi program that creates an Azure resource group and exports the value of the resource group name. Let's deploy the program and see the output.&lt;/p&gt;

&lt;p&gt;Save the main.go file then open the VSCode terminal and run the following command to provision the infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I like to deploy as I go, but you can also run &lt;code&gt;pulumi preview&lt;/code&gt; to see what resources will be created without actually creating them. This is useful for checking your work before deploying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You will be prompted to enter a passphrase to decrypt the secrets. If you left it blank earlier, you can just hit enter to proceed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enter your passphrase to unlock config/secrets
    (set PULUMI_CONFIG_PASSPHRASE or PULUMI_CONFIG_PASSPHRASE_FILE to remember):
Enter your passphrase to unlock config/secrets
Previewing update (dev):
     Type                                     Name                 Plan
 +   pulumi:pulumi:Stack                      aks-pulumi-demo-dev  create
 +   └─ azure-native:resources:ResourceGroup  resourceGroup        create

Outputs:
    resourceGroupName: output&amp;lt;string&amp;gt;

Resources:
    + 2 to create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within a few seconds you will be prompted to perform the update. Use the arrow keys to select &lt;code&gt;yes&lt;/code&gt; and hit enter to confirm you want to deploy Azure resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do you want to perform this update?  [Use arrows to move, type to filter]
&amp;gt; yes
  no
  details
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the deployment is complete, you will see the outputs of the resources that were created. Here is what the output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updating (dev):
     Type                                     Name                 Status
 +   pulumi:pulumi:Stack                      aks-pulumi-demo-dev  created (1s)
 +   └─ azure-native:resources:ResourceGroup  resourceGroup        created (0.93s)

Outputs:
    resourceGroupName: "resourceGroup3f007f94"

Resources:
    + 2 created

Duration: 3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Randomizing resource names
&lt;/h2&gt;

&lt;p&gt;Did you notice that the resource group name was randomized by Pulumi? It did so by taking the name of the resourceGroup object and appending some random text to the end. If you have naming conventions that you like to follow you will want to set the actual resource group name yourself. To do this, you can pass in a &lt;code&gt;resources.ResourceGroupArgs&lt;/code&gt; struct to the &lt;code&gt;NewResourceGroup&lt;/code&gt; function. This struct has fields that you can set to control the resource group name and other properties of the resource group.&lt;/p&gt;

&lt;p&gt;I don't have strict naming conventions to follow so I often randomize resource names myself to meet global naming requirements for some Azure resources. To implement my own randomization, I'll use &lt;a href="https://www.pulumi.com/registry/packages/random/api-docs/" rel="noopener noreferrer"&gt;Pulumi's random provider&lt;/a&gt; to generate a &lt;a href="https://www.pulumi.com/registry/packages/random/api-docs/randominteger/" rel="noopener noreferrer"&gt;random integer&lt;/a&gt; then pass in the name that we want the resource group name to use.&lt;/p&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://pkg.go.dev/github.com/pulumi/pulumi-random/sdk/go/random" rel="noopener noreferrer"&gt;random&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-random/sdk/v4/go/random"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Pulumi docs are your friend. You can find the documentation for any resource and find the latest version of the provider to use by viewing the code sample on the provider's page. For example, the &lt;a href="https://www.pulumi.com/registry/packages/random/api-docs/randominteger/" rel="noopener noreferrer"&gt;RandomInteger&lt;/a&gt; resource page will show you the proper import path to use in the example code block.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next, add this code to the top of the &lt;code&gt;pulumi.Run&lt;/code&gt; function, just above the resource group creation block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a 4 digit random integer to be used for resource names&lt;/span&gt;
&lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRandomInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"randomInt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RandomIntegerArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have a random integer that we can use to generate unique resource names.&lt;/p&gt;

&lt;p&gt;Update the resource group creation block to include a new &lt;code&gt;resources.ResourceGroupArgs&lt;/code&gt; struct that will set the &lt;code&gt;ResourceGroupName&lt;/code&gt; field using the random integer to create a unique name. Here is the updated code to create the resource group.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create an Azure Resource Group&lt;/span&gt;
&lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewResourceGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resourceGroup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceGroupArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rg-akspulumidemo%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the main.go file then run the following command in a terminal to install any missing dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run the following command to update the infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can run &lt;code&gt;export PULUMI_CONFIG_PASSPHRASE=""&lt;/code&gt; to avoid entering the passphrase every time you run &lt;code&gt;pulumi up&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After you confirm the changes, you will see the output of the deployment and see that the original resource group was replaced with a new one since renaming resources is "not a thing" in Azure. Here is what the output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Previewing update (dev):
     Type                                     Name                 Plan        Info
     pulumi:pulumi:Stack                      aks-pulumi-demo-dev
 +   ├─ random:index:RandomInteger            randomInt            create
 +-  └─ azure-native:resources:ResourceGroup  resourceGroup        replace     [diff: ~resourceGroupName]

Resources:
    + 1 to create
    +-1 to replace
    2 changes. 1 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                                     Name                 Status              Info
     pulumi:pulumi:Stack                      aks-pulumi-demo-dev
 +   ├─ random:index:RandomInteger            randomInt            created (0.00s)
 +-  └─ azure-native:resources:ResourceGroup  resourceGroup        replaced (16s)      [diff: ~resourceGroupName]

Outputs:
  ~ resourceGroupName: "resourceGroup3f007f94" =&amp;gt; "rg-akspulumidemo8622"

Resources:
    + 1 created
    +-1 replaced
    2 changes. 1 unchanged

Duration: 19s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great job! We now have a resource group with a custom and unique name. Let's move on to creating an a few more resources before we deploy the AKS cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy a container registry
&lt;/h2&gt;

&lt;p&gt;With the resource group in place, we can now add other resources based on the workload requirements. I don't have any specific requirements for this demo, but I'll add an &lt;a href="https://learn.microsoft.com/azure/container-registry/container-registry-intro" rel="noopener noreferrer"&gt;Azure Container Registry&lt;/a&gt; to show you how you can attach the registry to the AKS cluster.&lt;/p&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/containerregistry/" rel="noopener noreferrer"&gt;azure-native.containerregistry&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/containerregistry/v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code in the main function to create an &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/containerregistry/registry/" rel="noopener noreferrer"&gt;Azure Container Registry&lt;/a&gt; and export the name of the registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create Azure Container Registry&lt;/span&gt;
&lt;span class="n"&gt;containerRegistry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;containerregistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"containerRegistry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerregistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RegistryArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RegistryName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"acrakspulumidemo%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Sku&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerregistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SkuArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"containerRegistry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;containerRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Whether you keep all the resource creation code together and all the export code together is up to you. I like to keep the resource creation code together and the export code together so I can easily see what resources are being created and what values are being exported. Feel free to organize the code in a way that makes sense to you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploy observability tools
&lt;/h2&gt;

&lt;p&gt;You may also want to add observability resources like &lt;a href="https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-workspace-overview" rel="noopener noreferrer"&gt;Azure Log Analytics Workspace&lt;/a&gt; for application logging, &lt;a href="https://learn.microsoft.com/azure/azure-monitor/essentials/prometheus-metrics-overview" rel="noopener noreferrer"&gt;Azure Monitor Workspace&lt;/a&gt; for Prometheus metrics, and &lt;a href="https://learn.microsoft.com/azure/managed-grafana/overview" rel="noopener noreferrer"&gt;Azure Managed Grafana&lt;/a&gt; to visualize resources in your AKS cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Azure Log Analytics Workspace
&lt;/h3&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/operationalinsights/" rel="noopener noreferrer"&gt;azure-native.operationalinsights&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/operationalinsights/v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code to create an &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/operationalinsights/workspace/" rel="noopener noreferrer"&gt;Azure Log Analytics Workspace&lt;/a&gt; and export the name of the workspace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create Azure Log Analytics Workspace for Container Insights&lt;/span&gt;
&lt;span class="n"&gt;logAnalyticsWorkspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;operationalinsights&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"logAnalyticsWorkspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;operationalinsights&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkspaceArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RetentionInDays&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Sku&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;operationalinsights&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkspaceSkuArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PerGB2018"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;WorkspaceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"law-akspulumidemo%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"logAnalyticsWorkspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logAnalyticsWorkspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will create an Azure Log Analytics Workspace with a retention period of 30 days and export the name of the workspace. We'll also randomize the name of the workspace to meet Azure's global naming requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Azure Managed Prometheus
&lt;/h3&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/monitor/" rel="noopener noreferrer"&gt;azure-native.monitor&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/monitor/v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the following code to create an &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/monitor/azuremonitorworkspace/" rel="noopener noreferrer"&gt;Azure Monitor Workspace&lt;/a&gt; and export the name of the workspace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create Azure Monitor Workspace for managed Prometheus&lt;/span&gt;
&lt;span class="n"&gt;azureMonitorWorkspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewAzureMonitorWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"azureMonitorWorkspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;monitor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AzureMonitorWorkspaceArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AzureMonitorWorkspaceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"prom-akspulumidemo%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"azureMonitorWorkspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;azureMonitorWorkspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will create an Azure Monitor Workspace for managed Prometheus and export the name of the workspace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Azure Managed Grafana
&lt;/h3&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/dashboard/" rel="noopener noreferrer"&gt;azure-native.dashboard&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/dashboard/v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the following code to create an &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/dashboard/grafana/" rel="noopener noreferrer"&gt;Azure Managed Grafana&lt;/a&gt; resource with Azure Monitor Workspace integration, and export the name of the dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create Azure Managed Grafana with Azure Monitor Workspace integration&lt;/span&gt;
&lt;span class="n"&gt;grafanaDashboard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewGrafana&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"grafanaDashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrafanaArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedServiceIdentityArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SystemAssigned"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedGrafanaPropertiesArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ApiKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;              &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;PublicNetworkAccess&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;GrafanaIntegrations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GrafanaIntegrationsArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;AzureMonitorWorkspaceIntegrations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AzureMonitorWorkspaceIntegrationArray&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AzureMonitorWorkspaceIntegrationArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;AzureMonitorWorkspaceResourceId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;azureMonitorWorkspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sku&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;dashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceSkuArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;WorkspaceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"graf-akspulumidemo%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grafanaDashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grafanaDashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will create an Azure Managed Grafana with a system-assigned managed identity and Azure Monitor Workspace integration and export the name of the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provision Azure resources
&lt;/h3&gt;

&lt;p&gt;That should do it for the observability resources. Let's save the file and run the program to deploy what we have so far.&lt;/p&gt;

&lt;p&gt;Run the following commands to update the infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi &lt;span class="nb"&gt;install
&lt;/span&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the deployment is complete, you will see the outputs of the new resources that were added. Here is what the output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Previewing update (dev):
     Type                                           Name                   Plan
     pulumi:pulumi:Stack                            aks-pulumi-demo-dev
 +   ├─ azure-native:monitor:AzureMonitorWorkspace  azureMonitorWorkspace  create
 +   ├─ azure-native:containerregistry:Registry     containerRegistry      create
 +   ├─ azure-native:dashboard:Grafana              grafanaDashboard       create
 +   └─ azure-native:operationalinsights:Workspace  logAnalyticsWorkspace  create

Outputs:
  + azureMonitorWorkspace: output&amp;lt;string&amp;gt;
  + containerRegistry    : output&amp;lt;string&amp;gt;
  + grafanaDashboard     : output&amp;lt;string&amp;gt;
  + logAnalyticsWorkspace: output&amp;lt;string&amp;gt;

Resources:
    + 4 to create
    3 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                                           Name                   Status
     pulumi:pulumi:Stack                            aks-pulumi-demo-dev
 +   ├─ azure-native:containerregistry:Registry     containerRegistry      created (11s)
 +   ├─ azure-native:operationalinsights:Workspace  logAnalyticsWorkspace  created (14s)
 +   ├─ azure-native:monitor:AzureMonitorWorkspace  azureMonitorWorkspace  created (12s)
 +   └─ azure-native:dashboard:Grafana              grafanaDashboard       created (139s)

Outputs:
  + azureMonitorWorkspace: "prom-akspulumidemo4266"
  + containerRegistry    : "acrakspulumidemo4266"
  + grafanaDashboard     : "graf-akspulumidemo4266"
  + logAnalyticsWorkspace: "law-akspulumidemo4266"
    resourceGroupName    : "rg-akspulumidemo4266"

Resources:
    + 4 created
    3 unchanged

Duration: 2m35s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy AKS Automatic cluster
&lt;/h2&gt;

&lt;p&gt;Now, the moment you've been waiting for! Let's add the AKS Automatic cluster.&lt;/p&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/containerservice/" rel="noopener noreferrer"&gt;azure-native.containerservice&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/containerservice/v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code to create an &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/containerservice/managedcluster/" rel="noopener noreferrer"&gt;Azure Kubernetes Service&lt;/a&gt; Automatic cluster and export the name of the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create AKS Automatic cluster&lt;/span&gt;
&lt;span class="n"&gt;managedCluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewManagedCluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"managedCluster"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ResourceGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ResourceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"aks-%v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;randomInt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Sku&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterSKUArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Automatic"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Tier&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Standard"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;AgentPoolProfiles&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAgentPoolProfileArray&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAgentPoolProfileArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"System"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"systempool"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;VmSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Standard_D4pds_v6"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;AddonProfiles&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAddonProfileMap&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"omsagent"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAddonProfileArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringMap&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"logAnalyticsWorkspaceResourceID"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logAnalyticsWorkspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="s"&gt;"useAADAuth"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;                      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;AzureMonitorProfile&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAzureMonitorProfileArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAzureMonitorProfileMetricsArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;KubeStateMetrics&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterAzureMonitorProfileKubeStateMetricsArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;MetricAnnotationsAllowList&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;MetricLabelsAllowlist&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManagedClusterIdentityArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;containerservice&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceIdentityTypeSystemAssigned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"aksName"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;managedCluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code will create an AKS Automatic cluster with the following configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets the SKU and Tier to Automatic and Standard respectively&lt;/li&gt;
&lt;li&gt;Adds a system pool with 3 nodes of size Standard_D4pds_v6&lt;/li&gt;
&lt;li&gt;Enables the Azure Monitor Addon with Log Analytics Workspace integration for Container Insights&lt;/li&gt;
&lt;li&gt;Enables the Azure Monitor Profile with metrics enabled for Prometheus&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;As noted above, AKS Automatic can automatically select the VM size based on its requirements and SKU availability in the region you selected. If you have specific VM requirements, you can set the &lt;code&gt;VmSize&lt;/code&gt; field to the desired SKU but it's not required for AKS Automatic.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's save the file and run the following command to update the infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi &lt;span class="nb"&gt;install
&lt;/span&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After about 10-15 minutes, the deployment should be complete and you will see the output of the AKS cluster name. Here is what the output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Previewing update (dev):
     Type                                             Name                 Plan
     pulumi:pulumi:Stack                              aks-pulumi-demo-dev
 +   └─ azure-native:containerservice:ManagedCluster  managedCluster       create

Outputs:
  + aksName              : output&amp;lt;string&amp;gt;

Resources:
    + 1 to create
    7 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                                             Name                 Status
     pulumi:pulumi:Stack                              aks-pulumi-demo-dev
 +   └─ azure-native:containerservice:ManagedCluster  managedCluster       created (767s)

Outputs:
  + aksName              : "aks-4266"
    azureMonitorWorkspace: "prom-akspulumidemo4266"
    containerRegistry    : "acrakspulumidemo4266"
    grafanaDashboard     : "graf-akspulumidemo4266"
    logAnalyticsWorkspace: "law-akspulumidemo4266"
    resourceGroupName    : "rg-akspulumidemo4266"

Resources:
    + 1 created
    7 unchanged

Duration: 12m50s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy role assignments
&lt;/h2&gt;

&lt;p&gt;At this point, all the resources are in place, but now we need to add a bunch of role permissions like AcrPull for the Azure Container Registry, Azure Monitor Reader for the Azure Monitor Workspace, and give your user access to the AKS cluster and Azure Managed Grafana.&lt;/p&gt;

&lt;p&gt;Add the following import statement to the top of the file to import the &lt;a href="https://www.pulumi.com/registry/packages/azure-native/api-docs/authorization/" rel="noopener noreferrer"&gt;azure-native.authorization&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"github.com/pulumi/pulumi-azure-native-sdk/authorization/v2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grant cluster permissions for pulling images
&lt;/h3&gt;

&lt;p&gt;Granting an AKS cluster access to an Azure Container Registry has always been a little tricky. Since the kubelet is the one pulling images from the registry, we need to assign the &lt;a href="https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/containers#acrpull" rel="noopener noreferrer"&gt;AcrPull&lt;/a&gt; role to the kubelet's principal ID which is available once the AKS cluster is created.&lt;/p&gt;

&lt;p&gt;Add the following code to retrieve the kubelet's principal ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Get the kubelet's principal ID for AcrPull role assignment&lt;/span&gt;
&lt;span class="n"&gt;kubeletPrincipalId&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;managedCluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IdentityProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MapIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kubeletidentity"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next add the following code to create the AcrPull role assignment for the kubelet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a role assignment so that the kubelet can pull images from ACR&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRoleAssignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"kubeletRoleAssignment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoleAssignmentArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;kubeletPrincipalId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToStringOutput&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;RoleDefinitionId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;containerRegistry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ServicePrincipal"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grant permissions for monitoring
&lt;/h3&gt;

&lt;p&gt;In order to view cluster metrics from the Azure Managed Grafana, the &lt;a href="https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/monitor#monitoring-reader" rel="noopener noreferrer"&gt;Monitoring Reader&lt;/a&gt; role must be assigned to the &lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview#managed-identity-types" rel="noopener noreferrer"&gt;system-assigned managed identity&lt;/a&gt; of the Azure Managed Grafana resource.&lt;/p&gt;

&lt;p&gt;Add the following code to create the Azure Monitor Reader role assignment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a role assignment so that Azure Managed Grafana can query the Azure Monitor Workspace and Log Analytics Workspace&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRoleAssignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"azureMonitorWorkspaceRoleAssignment1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoleAssignmentArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;grafanaDashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Identity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PrincipalId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;RoleDefinitionId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/providers/Microsoft.Authorization/roleDefinitions/43d0d8ad-25c7-4714-9337-8ba259a9fe05"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;resourceGroup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ServicePrincipal"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grant yourself permissions for access
&lt;/h3&gt;

&lt;p&gt;Finally, you'll need to grant yourself access to the AKS cluster and Azure Managed Grafana.&lt;/p&gt;

&lt;p&gt;Add the following code to retrieve the your user principal ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Get current user principal&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetClientConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompositeInvoke&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AKS Automatic cluster authorization is managed by Microsoft Entra ID, so we need to assign the &lt;a href="https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/containers#azure-kubernetes-service-rbac-cluster-admin" rel="noopener noreferrer"&gt;Azure Kubernetes Service RBAC Cluster Admin&lt;/a&gt; role to your user principal ID. Without this, you would not be able to run &lt;code&gt;kubectl&lt;/code&gt; commands against the AKS cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a role assignment so I can access the kubeapiserver&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRoleAssignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"managedClusterRoleAssignment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoleAssignmentArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;RoleDefinitionId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/providers/Microsoft.Authorization/roleDefinitions/b1ff04bb-8a4e-4dc4-8eb5-8693973ce19b"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;managedCluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, give yourself access to the Azure Monitor Workspace and Azure Managed Grafana.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a role assignment so I can query the Azure Monitor Workspace&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRoleAssignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"azureMonitorWorkspaceRoleAssignment2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoleAssignmentArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;RoleDefinitionId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/providers/Microsoft.Authorization/roleDefinitions/43d0d8ad-25c7-4714-9337-8ba259a9fe05"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;azureMonitorWorkspace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Create a role assignment so I can access Azure Managed Grafana dashboards&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRoleAssignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"grafanaRoleAssignment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;authorization&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RoleAssignmentArgs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;RoleDefinitionId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/providers/Microsoft.Authorization/roleDefinitions/22926164-76b3-42b3-bc55-97df8dab3e41"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;grafanaDashboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;PrincipalType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;pulumi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provision role assignments
&lt;/h3&gt;

&lt;p&gt;Now that we have all the role assignments in place, let's save the file and run the following command to update the infrastructure one last time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi &lt;span class="nb"&gt;install
&lt;/span&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few seconds, the deployment should be complete and you will see the output of the role assignments. Here is what the output should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Previewing update (dev):
     Type                                          Name                                  Plan
     pulumi:pulumi:Stack                           aks-pulumi-demo-dev
 +   ├─ azure-native:authorization:RoleAssignment  azureMonitorWorkspaceRoleAssignment1  create
 +   ├─ azure-native:authorization:RoleAssignment  kubeletRoleAssignment                 create
 +   ├─ azure-native:authorization:RoleAssignment  managedClusterRoleAssignment          create
 +   ├─ azure-native:authorization:RoleAssignment  azureMonitorWorkspaceRoleAssignment2  create
 +   └─ azure-native:authorization:RoleAssignment  grafanaRoleAssignment                 create

Resources:
    + 5 to create
    8 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                                          Name                                  Status
     pulumi:pulumi:Stack                           aks-pulumi-demo-dev
 +   ├─ azure-native:authorization:RoleAssignment  azureMonitorWorkspaceRoleAssignment1  created (3s)
 +   ├─ azure-native:authorization:RoleAssignment  kubeletRoleAssignment                 created (4s)
 +   ├─ azure-native:authorization:RoleAssignment  grafanaRoleAssignment                 created (3s)
 +   ├─ azure-native:authorization:RoleAssignment  azureMonitorWorkspaceRoleAssignment2  created (3s)
 +   └─ azure-native:authorization:RoleAssignment  managedClusterRoleAssignment          created (3s)

Outputs:
    aksName              : "aks-4266"
    azureMonitorWorkspace: "prom-akspulumidemo4266"
    containerRegistry    : "acrakspulumidemo4266"
    grafanaDashboard     : "graf-akspulumidemo4266"
    logAnalyticsWorkspace: "law-akspulumidemo4266"
    resourceGroupName    : "rg-akspulumidemo4266"

Resources:
    + 5 created
    8 unchanged

Duration: 7s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test the deployment
&lt;/h2&gt;

&lt;p&gt;Now that you've given yourself the proper permissions to the Azure resources, you can test access to the AKS cluster using the &lt;code&gt;kubectl&lt;/code&gt; command line tool.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you don't have &lt;code&gt;kubectl&lt;/code&gt; installed, you can to install it using the &lt;code&gt;az aks install-cli&lt;/code&gt; command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To configure &lt;code&gt;kubectl&lt;/code&gt; to use the AKS cluster, run 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;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PULUMI_CONFIG_PASSPHRASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="c"&gt;#&amp;lt;-- set this to whatever you set when you created the stack&lt;/span&gt;
az aks get-credentials &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pulumi stack output aksName&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;pulumi stack output resourceGroupName&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now run &lt;code&gt;kubectl&lt;/code&gt; commands against the AKS cluster. For example, you can run the following command to get the status of the nodes in the cluster.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You should be presented with a URL to authenticate with Microsoft Entra ID. Once you authenticate, you can run the following command to get the status of the nodes in the cluster.&lt;/p&gt;

&lt;p&gt;From there, feel free to browse to the &lt;a href="https://portal.azure.com" rel="noopener noreferrer"&gt;Azure Portal&lt;/a&gt; and check out the resources that were created. You can also access the Azure Managed Grafana dashboard to view the metrics of the AKS cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;This was a long but simple example of deploying an AKS Automatic cluster with Pulumi. You can extend this example by adding more resources like Prometheus data collection endpoints, rule groups, and alerts. I won't cover that in this article because it would make this article way too long, but you can view the code sample in my &lt;a href="https://github.com/pauldotyu/awesome-aks/blob/main/2024-10-09-aks-automatic-azure-pulumi/main.go#L208" rel="noopener noreferrer"&gt;GitHub repo here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean up
&lt;/h2&gt;

&lt;p&gt;After you're done with the resources, delete them 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;pulumi destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the resources are deleted, remove the stack 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;pulumi stack &lt;span class="nb"&gt;rm &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, remove the Pulumi project 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;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.pulumi/.pulumi/stacks/aks-pulumi-demo&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, I showed you how to deploy an AKS Automatic cluster with Pulumi. I used the Pulumi Go SDK and the Azure Native provider to create Azure resources like resource groups, Azure Container Registry, Azure Log Analytics Workspace, Azure Monitor Workspace, and Azure Managed Grafana.&lt;/p&gt;

&lt;p&gt;Working with Pulumi to deploy Azure resources is a good alternative to using the other IaC tools out there like Terraform and Azure Bicep. Pulumi allows you to use your favorite programming language to create resources for your workload. The pattern of creating resources and exporting their values is the same for all resources as shown in the example above.&lt;/p&gt;

&lt;p&gt;It is also worth nothing that Terraform also offers a &lt;a href="https://developer.hashicorp.com/terraform/cdktf" rel="noopener noreferrer"&gt;Cloud Development Kit (CDK)&lt;/a&gt; that allows you to use your favorite programming language to create resources. Let me know in the comments if you would like to see a similar example using Terraform CDK.&lt;/p&gt;

&lt;p&gt;Until then check out some of the resources below and let me know if you have any questions&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/get-started/azure/" rel="noopener noreferrer"&gt;Get started with Pulumi &amp;amp; Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/registry/packages/azure-native/installation-configuration/" rel="noopener noreferrer"&gt;Azure Native: Installation &amp;amp; Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/registry/packages/azure-native/how-to-guides/azure-go-aks/" rel="noopener noreferrer"&gt;Azure Kubernetes Service (AKS) Cluster using the native Azure Provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pulumi/examples" rel="noopener noreferrer"&gt;Pulumi examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/registry/packages/kubernetes/how-to-guides/aks/" rel="noopener noreferrer"&gt;Azure Kubernetes Service (AKS) - Hello World!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aks</category>
      <category>pulumi</category>
      <category>cloudnative</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Certified Argo Project Associate (CAPA) Exam Study Guide</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Thu, 06 Feb 2025 13:00:00 +0000</pubDate>
      <link>https://forem.com/pauldotyu/certified-argo-project-associate-capa-exam-study-guide-1bh1</link>
      <guid>https://forem.com/pauldotyu/certified-argo-project-associate-capa-exam-study-guide-1bh1</guid>
      <description>&lt;p&gt;I recently passed the &lt;a href="https://training.linuxfoundation.org/certification/certified-argo-project-associate-capa/" rel="noopener noreferrer"&gt;Certified Argo Project Associate (CAPA)&lt;/a&gt; exam and wanted to share a study guide to help you prepare for the exam. If you are not already aware, the &lt;a href="https://www.cncf.io/training/certification/" rel="noopener noreferrer"&gt;Cloud Native Computing Foundation (CNCF)&lt;/a&gt; offers a suite of certifications that validate your knowledge and expertise in cloud-native technologies, with &lt;a href="https://argoproj.github.io/" rel="noopener noreferrer"&gt;Argo&lt;/a&gt; being one of them.&lt;/p&gt;

&lt;p&gt;The CAPA exam is designed for beginners who are new to Argo and its suite of tools which includes Argo Workflows, Argo CD, Argo Rollouts, and Argo Events. &lt;/p&gt;

&lt;p&gt;But be warned... even though it says it's for beginners, it can be a bit intimidating if you've never worked with these tools before. So in this study guide, I'll try to cover key topics you should be familiar and include some resources and practical exercises to help you prepare for the exam &lt;/p&gt;

&lt;p&gt;Good news is the exam is multiple choice so it's not as rigorous as the Kubernetes exams where you have to perform tasks in a live server environment; however, you are expected to have a good bit of understanding on how to use these tools including detailed knowledge of some of their &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions" rel="noopener noreferrer"&gt;Custom Resource Definition (CRD)&lt;/a&gt; specifications. &lt;/p&gt;

&lt;p&gt;You have 90 minutes to complete the exam, and when you pass (I know you will ☺️), the certification is valid for 2 years. If you don't pass on the first try... no sweat, you do get one free retake 😅&lt;/p&gt;

&lt;p&gt;As documented on the &lt;a href="https://training.linuxfoundation.org/certification/certified-argo-project-associate-capa/" rel="noopener noreferrer"&gt;CAPA exam page&lt;/a&gt;, here is a breakdown of the exam topics and the percentage of questions you can expect from each section:&lt;/p&gt;

&lt;p&gt;{{% toc %}}&lt;/p&gt;

&lt;p&gt;Let's now dig into each of the topics and provide high-level summaries of what you should know for each section. I'll also include some resources and practical exercises to help you prepare for the exam 💪&lt;/p&gt;




&lt;h2&gt;
  
  
  Argo Workflows 36%
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://argoproj.github.io/argo-workflows/" rel="noopener noreferrer"&gt;Argo Workflows&lt;/a&gt; is an open-source cloud-native workflow engine for orchestrating jobs on Kubernetes. Like all the other projects in the Argo suite it's implemented as an extension to Kubernetes following the &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/" rel="noopener noreferrer"&gt;Operator pattern&lt;/a&gt; and provides a powerful way to define complex workflows using YAML. &lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Argo Workflow Fundamentals
&lt;/h3&gt;

&lt;p&gt;When you install Argo Workflows, you get a Workflow Controller which is reponsible for reconciling the desired state of the Workflow CRD and an Argo Server which is responsible for serving the API requests.&lt;/p&gt;

&lt;p&gt;Here is a high-level overview of the Argo Workflow architecture:&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%2Fvy2trwuik3quxpbnhkc4.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%2Fvy2trwuik3quxpbnhkc4.png" alt="Argo Workflow Architecture" width="800" height="1126"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://argo-workflows.readthedocs.io/en/latest/architecture/" rel="noopener noreferrer"&gt;https://argo-workflows.readthedocs.io/en/latest/architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Workflow CRD is the primary resource you will interact with to define your workflows. It allows you to define a sequence of tasks and dependencies between tasks. It also is reponsible for storing the state of the workflow.&lt;/p&gt;

&lt;p&gt;The Workflow spec contains the following key attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;entrypoint&lt;/strong&gt; is the name of the template that will be executed first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;templates&lt;/strong&gt; is a list of templates that define the tasks in the workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out this &lt;a href="https://github.com/argoproj/argo-workflows/blob/main/examples/template-defaults.yaml" rel="noopener noreferrer"&gt;example of a basic workflow with some defaults&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The template is where tasks are defined to be run in the workflow. A template can be one of the following &lt;a href="https://argo-workflows.readthedocs.io/en/latest/workflow-concepts/#template-definitions" rel="noopener noreferrer"&gt;task types&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#container" rel="noopener noreferrer"&gt;container&lt;/a&gt;&lt;/strong&gt; is a task that runs a container. This is useful when you want to run a task that is already containerized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#scripttemplate" rel="noopener noreferrer"&gt;script&lt;/a&gt;&lt;/strong&gt; is a task that gives you a bit of flexibility to run a script inside a container. This is useful when you need to run a script but don't want to create a separate container image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#resourcetemplate" rel="noopener noreferrer"&gt;resource&lt;/a&gt;&lt;/strong&gt; is a task that allows you to perform operations on cluster resources. This can be used to get, create, apply, delete, replace, or patch resources. This is useful when you need to interact with Kubernetes resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#suspendtemplate" rel="noopener noreferrer"&gt;suspend&lt;/a&gt;&lt;/strong&gt; is a task that allows you to pause the workflow until it is manually resumed. This is useful when you need to wait for a manual approval or intervention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#plugin" rel="noopener noreferrer"&gt;plugin&lt;/a&gt;&lt;/strong&gt; is a task that allows you to run an external plugin. This is useful when you need to run a task that is not supported by the built-in templates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#containersettemplate" rel="noopener noreferrer"&gt;containerset&lt;/a&gt;&lt;/strong&gt; is a task that allows you to run multiple containers within a single pod. This is useful when you need to run multiple containers that share the same network namespace, IPC namespace, and PID namespace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#http" rel="noopener noreferrer"&gt;http&lt;/a&gt;&lt;/strong&gt; is a task that allows you to make HTTP requests. This is useful when you need to interact with external services.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Be sure to read through each of the template type field references to understand how to define and take a look at some of the examples to see how they are used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can also control the order in which tasks are run with &lt;a href="https://argo-workflows.readthedocs.io/en/latest/workflow-concepts/#template-invocators" rel="noopener noreferrer"&gt;template invocators&lt;/a&gt;. This defines the execution flow of the workflow and there are two types of invocators:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#workflowstep" rel="noopener noreferrer"&gt;steps&lt;/a&gt;&lt;/strong&gt; is a of tasks that are executed in order. Steps can be nested to create a hierarchy of tasks and outer steps are run sequentially while inner steps are run in parallel. However, you can get more control over the execution of the inner steps by using the &lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#synchronization" rel="noopener noreferrer"&gt;synchronization&lt;/a&gt; field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#dagtemplate" rel="noopener noreferrer"&gt;dag&lt;/a&gt;&lt;/strong&gt; is a directed acyclic graph of templates that are executed based on dependencies. This is useful when you need to run tasks in parallel and define dependencies between tasks; especially when you tasks that rely on the output of other tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Generating and Consuming Artifacts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/walk-through/artifacts/" rel="noopener noreferrer"&gt;Artifacts&lt;/a&gt; can be consumed by tasks or be outputs of tasks which in turn can be consumed by other tasks.&lt;/p&gt;

&lt;p&gt;Check out this &lt;a href="https://github.com/argoproj/argo-workflows/blob/main/examples/artifact-passing.yaml" rel="noopener noreferrer"&gt;example of a workflow that generates and consumes artifacts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To output an artifact, you can use the &lt;code&gt;outputs.artifacts&lt;/code&gt; field in the task template. This field is a list of artifacts that includes the name of the artifact and the path to the artifact. &lt;/p&gt;

&lt;p&gt;To consume an artifact, you can use the &lt;code&gt;inputs.artifacts&lt;/code&gt; field in the task template. Like the outputs field, this field is a list of artifacts that includes the name of the artifact and the path to the artifact. If you are consuming an artifact that is an output of another task, you can reference the artifact by the name of the task that produced it along with the name of the artifact.&lt;/p&gt;

&lt;p&gt;There are also ways to reduce the amount of data that is stored in the workflow by using &lt;a href="https://argo-workflows.readthedocs.io/en/latest/configure-artifact-repository/" rel="noopener noreferrer"&gt;artifact repositories&lt;/a&gt; and &lt;a href="https://argo-workflows.readthedocs.io/en/latest/walk-through/artifacts/#artifact-garbage-collection" rel="noopener noreferrer"&gt;garbage collection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, you can get a bit more advanced control on when to output an artifact by using &lt;a href="https://argo-workflows.readthedocs.io/en/latest/conditional-artifacts-parameters/" rel="noopener noreferrer"&gt;conditional artifacts&lt;/a&gt; based on &lt;a href="https://argo-workflows.readthedocs.io/en/latest/variables/#expression" rel="noopener noreferrer"&gt;expressions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Argo Workflow Templates
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/workflow-templates/" rel="noopener noreferrer"&gt;Workflow Templates&lt;/a&gt; are reusable templates that can be used to define Workflows. They are useful when you have a common set of tasks that you want to reuse across multiple workflows typically scoped to a namespace. They build on the same concepts as Workflows but are defined as a separate resource.&lt;/p&gt;

&lt;p&gt;Check out this &lt;a href="https://github.com/argoproj/argo-workflows/blob/main/examples/workflow-template/templates.yaml" rel="noopener noreferrer"&gt;example of workflow templates&lt;/a&gt; and this &lt;a href="https://github.com/argoproj/argo-workflows/blob/main/examples/workflow-template/retry-with-steps.yaml" rel="noopener noreferrer"&gt;example of how workflow templates can be referenced in workflow definitions&lt;/a&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note how the Workflow in the sample calls the &lt;code&gt;random-fail-template&lt;/code&gt; which is defined in the templates.yaml file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is also a concept of cluster scoped workflow templates with the &lt;a href="https://argo-workflows.readthedocs.io/en/latest/cluster-workflow-templates/" rel="noopener noreferrer"&gt;ClusterWorkflowTemplate&lt;/a&gt; resource. This is useful when you want to define a template that can be used across multiple namespaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand the Argo Workflow Spec
&lt;/h3&gt;

&lt;p&gt;There will be questions on the &lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#workflowspec" rel="noopener noreferrer"&gt;Workflow spec&lt;/a&gt; so be sure to read through the documentation to understand the various fields that can be used to configure the behavior of the workflow. Some of the key fields to pay attention to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;activeDeadlineSeconds&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#arguments" rel="noopener noreferrer"&gt;arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#metrics" rel="noopener noreferrer"&gt;metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#retrystrategy" rel="noopener noreferrer"&gt;retryStrategy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#synchronization" rel="noopener noreferrer"&gt;synchronization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#template" rel="noopener noreferrer"&gt;templateDefaults&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/fields/#workflowtemplateref" rel="noopener noreferrer"&gt;workflowTemplateRef&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, be sure to understand how to configure &lt;a href="https://argo-workflows.readthedocs.io/en/latest/variables/" rel="noopener noreferrer"&gt;variables&lt;/a&gt; in the workflow spec and how to configure &lt;a href="https://argo-workflows.readthedocs.io/en/latest/metrics/" rel="noopener noreferrer"&gt;metrics&lt;/a&gt; which will be useful when you want to monitor the performance of the workflow and its tasks.&lt;/p&gt;

&lt;p&gt;Finally, it is important to understand how to configure &lt;a href="https://argo-workflows.readthedocs.io/en/latest/retries/" rel="noopener noreferrer"&gt;retries&lt;/a&gt;, &lt;a href="https://argo-workflows.readthedocs.io/en/latest/retries/#configuring-retrystrategy-in-workflowspec" rel="noopener noreferrer"&gt;retry strategy&lt;/a&gt;, and the &lt;a href="https://argo-workflows.readthedocs.io/en/latest/retries/#retry-policies" rel="noopener noreferrer"&gt;retry policies&lt;/a&gt; that are available to you to handle task failures, because they will eventually fail 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  Work with DAG (Directed-Acyclic Graphs)
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://argo-workflows.readthedocs.io/en/latest/workflow-concepts/#dag" rel="noopener noreferrer"&gt;DAG&lt;/a&gt; is important to understand as it allows you to define dependencies between tasks in a workflow. Tasks without dependencies (&lt;a href="https://argo-workflows.readthedocs.io/en/latest/workflow-concepts/#steps" rel="noopener noreferrer"&gt;steps&lt;/a&gt;) are run in parallel while tasks with dependencies are run sequentially. If you have tasks that rely on the output of other tasks, you'll want to use a DAG.&lt;/p&gt;

&lt;p&gt;Check out this &lt;a href="https://github.com/argoproj/argo-workflows/blob/main/examples/workflow-template/dag.yaml" rel="noopener noreferrer"&gt;sample workflow that uses a DAG&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pay special attention to how tasks within a DAG workflow are executed based on dependencies. You may be faced with questions on what outputs would look like based on the dependencies defined in the workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Run Data Processing Jobs with Argo Workflows
&lt;/h3&gt;

&lt;p&gt;Data processing jobs are common use cases for Argo Workflows. You can use Argo Workflows to process data from various sources, transform the data, and store the results. &lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://argo-workflows.readthedocs.io/en/latest/use-cases/data-processing/" rel="noopener noreferrer"&gt;data processing&lt;/a&gt; and &lt;a href="https://argo-workflows.readthedocs.io/en/latest/data-sourcing-and-transformation/" rel="noopener noreferrer"&gt;data sourcing and transformation&lt;/a&gt; documentation to understand how to configure workflows to process data.&lt;/p&gt;

&lt;p&gt;Data sources essentially point to artifact repositories and can be external like s3 buckets. The data fetched from these sources can be further transformed using &lt;a href="https://argo-workflows.readthedocs.io/en/latest/variables/#expression" rel="noopener noreferrer"&gt;expressions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out this &lt;a href="https://github.com/argoproj/argo-workflows/blob/main/examples/data-transformations.yaml" rel="noopener noreferrer"&gt;example of a workflow that processes data&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice
&lt;/h3&gt;

&lt;p&gt;To see Argo Workflows in action, follow the &lt;a href="https://argo-workflows.readthedocs.io/en/latest/quick-start/" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; to deploy a simple workflow that processes data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Argo CD 34%
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt; is a declarative, continuous delivery tool for Kubernetes that enables you to implement  GitOps principles. It allows you to manage the full lifecycle of applications in a Kubernetes cluster using Git repositories as the source of truth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Argo CD Fundamentals
&lt;/h3&gt;

&lt;p&gt;To understand how you can best leverage Argo CD, you need to be familiar with basic concepts around containerized applications, Kubernetes, and tools commonly used to deploy and manage applications like Helm and Kustomize.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To quickly get up to speed, make sure you read through the &lt;a href="https://argo-cd.readthedocs.io/en/stable/understand_the_basics/" rel="noopener noreferrer"&gt;Understanding The Basics&lt;/a&gt; section of the Argo CD documentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here are some of the basic &lt;a href="https://argo-cd.readthedocs.io/en/stable/core_concepts/" rel="noopener noreferrer"&gt;core concepts&lt;/a&gt; you should be familiar with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application&lt;/strong&gt; is the collection of Kubernetes resources that represent your application. It is represented by an Application CRD in Argo CD and commits to a Git repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application source type&lt;/strong&gt; is the tool used to manage the application like Helm or Kustomize&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target state&lt;/strong&gt; is the desired state of the application that is defined in the Git repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live state&lt;/strong&gt; is the actual state of the application in the Kubernetes cluster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync&lt;/strong&gt; is the action to applying the target state to the live state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync status&lt;/strong&gt; indicates whether the target state and live state are in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync operation status&lt;/strong&gt; indicates the status of the last sync operation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refresh&lt;/strong&gt; is the action of comparing the live state with the target state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/" rel="noopener noreferrer"&gt;high-level architecture&lt;/a&gt; of Argo CD. It too is implemented as an extension to Kubernetes following the Operator pattern.&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%2Ftalijgveybuek6js2cle.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%2Ftalijgveybuek6js2cle.png" alt="Argo CD Architecture" width="743" height="708"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/" rel="noopener noreferrer"&gt;https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the major components of Argo CD is the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/#api-server" rel="noopener noreferrer"&gt;API Server&lt;/a&gt; which allows various external systems to interact with it like the CLI, UI, and CI/CD systems. It also takes care of ensuing communication between Git repositories and the Kubernetes cluster and acts as the security gatekeeper for the cluster.&lt;/p&gt;

&lt;p&gt;The second major component is the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/#repository-server" rel="noopener noreferrer"&gt;Repository Server&lt;/a&gt; which is responsible for syncing the target state of the application with the live state in the Kubernetes cluster. &lt;/p&gt;

&lt;p&gt;Lastly, the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/architecture/#application-controller" rel="noopener noreferrer"&gt;Application Controller&lt;/a&gt; is responsible for managing the lifecycle of applications in the cluster. &lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronize Applications with Argo CD
&lt;/h3&gt;

&lt;p&gt;Argo CD can keep your applications in sync &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/" rel="noopener noreferrer"&gt;automatically&lt;/a&gt; with the desired state defined in the Git repository. You can control how the synchronization happens using various &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-options/" rel="noopener noreferrer"&gt;sync options&lt;/a&gt; which can be configured via resource annotations or via the &lt;code&gt;spec.syncPolicy.syncOptions&lt;/code&gt; attribute in the Application CRD. Some of the options include controlling how the sync is performed, what resources are ignored, and how to handle out-of-sync situations like pruning resources or not pruning resources, performing selective syncs, server-side apply, and more. So be sure you understand how to configure these options.&lt;/p&gt;

&lt;p&gt;If you need to control the order in which applications are synced, you can use &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/" rel="noopener noreferrer"&gt;sync waves&lt;/a&gt;. This is useful when you have dependencies between applications and need to ensure that they are synced in a specific order.&lt;/p&gt;

&lt;p&gt;If you need to control when a sync can occur, you can use &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync_windows/" rel="noopener noreferrer"&gt;sync windows&lt;/a&gt; to define a time window when a sync can or cannot occur. This is useful when you need to prevent a sync from happening during a maintenance window or when you need to ensure that a sync happens during a specific time.&lt;/p&gt;

&lt;p&gt;You can also control what resources get synced by using a &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/selective_sync/" rel="noopener noreferrer"&gt;selective sync&lt;/a&gt; or apply special annotations to resources to &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/reconcile/#ignoring-updates-for-untracked-resources" rel="noopener noreferrer"&gt;ignore them&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, synchronization can be controlled using &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/resource_hooks/" rel="noopener noreferrer"&gt;resource hooks&lt;/a&gt; which gives you the flexibility to run custom logic at various stages of the synchronization process like pre-sync, post-sync, pre-sync failure, and post-sync failure.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Knowing when to hook into the synchronization process is important so be sure to read through the documentation to understand how to use resource hooks.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Use Argo CD Application
&lt;/h3&gt;

&lt;p&gt;Before we get into the Argo CD Application, you should know that Argo &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/projects/" rel="noopener noreferrer"&gt;Projects&lt;/a&gt; are used to group applications and manage access control. This is useful when you need to manage multiple applications and control who has access to them. Argo CD comes with a default project called &lt;code&gt;default&lt;/code&gt; and most beginners will deploy applications to this project. However, as your team grows and you have more applications, you may want to create additional projects to manage access control and restrict where applications can be deployed from, what applications can be deployed, and what clusters and namespaces they can be deployed to.&lt;/p&gt;

&lt;p&gt;Check out this &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#projects" rel="noopener noreferrer"&gt;example of how to define a project&lt;/a&gt; and the various fields that can be configured.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also work with Projects and most other Argo CD resources using the &lt;a href="https://argo-cd.readthedocs.io/en/stable/cli_installation/" rel="noopener noreferrer"&gt;argocd CLI&lt;/a&gt; or the Argo CD UI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#applications" rel="noopener noreferrer"&gt;Application&lt;/a&gt; resource is the primary resource you will interact with to define your applications that Argo CD will manage. Two key attributes to pay attention to include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;source&lt;/strong&gt; is the source of the application that points to the Git repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;destination&lt;/strong&gt; is the destination of the application that points to the Kubernetes cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out this &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/" rel="noopener noreferrer"&gt;example of an application&lt;/a&gt; to see how it is defined and the various fields that can be configured and this &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/annotations-and-labels/" rel="noopener noreferrer"&gt;list of annotations and labels&lt;/a&gt; that can be used to control how the application is managed.&lt;/p&gt;

&lt;p&gt;If you need to pass parameters to your application, you can use &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/parameters/" rel="noopener noreferrer"&gt;parameters&lt;/a&gt; to define them in the application spec. This is useful when you need to pass configuration values to your application that are stored in the Git repository.&lt;/p&gt;

&lt;p&gt;Typically your application will be sourced from a single repo; however, you can also source your application from &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/multiple_sources/" rel="noopener noreferrer"&gt;multiple sources&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a &lt;a href="https://github.com/argoproj/argo-cd/blob/4dcabb933e54f21cd922e7450c5b46ae86b08793/docs/operator-manual/application.yaml#L199" rel="noopener noreferrer"&gt;sample&lt;/a&gt; of how to define multiple sources in an application.&lt;/p&gt;

&lt;p&gt;If you are managing applications that needs to be deployed across multiple clusters, you can use the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/application-set/" rel="noopener noreferrer"&gt;ApplicationSet&lt;/a&gt; resource which is managed by the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/" rel="noopener noreferrer"&gt;ApplicationSet controller&lt;/a&gt;. This feature extends the capabilities of the Application resource and focuses on cluster administration scenarios including multi-cluster application management, multi-tenancy, and more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prior to Argo CD v2.3, this did require a separate installation but now it is included in the main Argo CD installation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The ApplicationSet resource gives you powerful tools to template your applications using the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Template/" rel="noopener noreferrer"&gt;Templates&lt;/a&gt; field and the ability to use &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/GoTemplate/" rel="noopener noreferrer"&gt;Go Templates&lt;/a&gt; to generate the unique values for fields. &lt;/p&gt;

&lt;p&gt;It is also worth noting that you should be a bit familiar with the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators/" rel="noopener noreferrer"&gt;Generators&lt;/a&gt; that are available to you when using the ApplicationSet resource. This includes the List, Git, Helm, and Kustomize generators that can be used to generate the resources that will be deployed to the cluster.&lt;/p&gt;

&lt;p&gt;In some advanced scenarios like cluster bootstrapping, you may need to use an &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#app-of-apps" rel="noopener noreferrer"&gt;App of Apps pattern&lt;/a&gt; that can be used to nest applications within a parent application. This is useful when you need to create applications that in turn create other applications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Prior to Argo CD v2.5, applications were managed out of the &lt;code&gt;argocd&lt;/code&gt; namespace. Since then Argo CD administrators now have the ability to define namespaces where applications can be deployed to. To enable this &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/app-any-namespace/" rel="noopener noreferrer"&gt;app in any namespace&lt;/a&gt; feature checkout the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/notifications/#namespace-based-configuration" rel="noopener noreferrer"&gt;namespace-based configuration&lt;/a&gt; documentation to see which flags must be set.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deleting an application is a common task that you will need to perform. When you &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/" rel="noopener noreferrer"&gt;delete an application&lt;/a&gt;, you can use either the argocd CLI or kubectl and with both, you have control on whether or not you want cascading deletion of resources. This is useful when you need to control how resources are deleted when an application is deleted. Also be sure to understand how the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/#about-the-deletion-finalizer" rel="noopener noreferrer"&gt;deletion finalizer&lt;/a&gt; can be used with the App of Apps pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Argo CD with Helm and Kustomize
&lt;/h3&gt;

&lt;p&gt;In the section above, you saw how Application resources can be sourced from &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt; and &lt;a href="https://kustomize.io/" rel="noopener noreferrer"&gt;Kustomize&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When using Helm to define your application it is important to know that Argo CD &lt;a href="https://argo-cd.readthedocs.io/en/stable/faq/#after-deploying-my-helm-application-with-argo-cd-i-cannot-see-it-with-helm-ls-and-other-helm-commands" rel="noopener noreferrer"&gt;inflates the Helm chart&lt;/a&gt; and stores the resources in the Kubernetes cluster. So don't expect to see the Helm installation using the typical Helm commands. Be sure to take a look though the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/helm/" rel="noopener noreferrer"&gt;Helm guide&lt;/a&gt; to understand how to pass in parameter values, configure hooks, and even install custom Helm plugins.&lt;/p&gt;

&lt;p&gt;For Kustomize based applications, be sure to read through the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/kustomize/" rel="noopener noreferrer"&gt;Kustomize guide&lt;/a&gt; to understand how to configure your application using patches, components, and build options.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Argo CD supports multiple versions of kustomize. See this &lt;a href="https://github.com/argoproj/argo-cd/blob/4dcabb933e54f21cd922e7450c5b46ae86b08793/docs/operator-manual/application.yaml#L112" rel="noopener noreferrer"&gt;sample&lt;/a&gt; on how to set the version of kustomize to use but note that you must also have the &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/kustomize/#custom-kustomize-versions" rel="noopener noreferrer"&gt;kustomize versions set in the ConfigMap&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lastly, you can &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/kustomize/#kustomizing-helm-charts" rel="noopener noreferrer"&gt;use kustomize to customize a Helm chart&lt;/a&gt; deployment by enabling the &lt;code&gt;--enable-helm&lt;/code&gt; flag. This is useful when you need to customize a Helm chart deployment using kustomize patches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identify Common Reconciliation Patterns
&lt;/h3&gt;

&lt;p&gt;Argo CD uses a reconciliation loop to ensure that the target state of the application matches the live state in the Kubernetes cluster. This loop is responsible for detecting changes in the target state, comparing it with the live state, and taking the necessary actions to ensure that the target state is applied to the live state.&lt;/p&gt;

&lt;p&gt;Be sure to read through the &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/reconcile/" rel="noopener noreferrer"&gt;reconciliation optimization&lt;/a&gt; documentation to understand how to optimize the reconciliation process and improve the performance of Argo CD.&lt;/p&gt;

&lt;p&gt;You can also configure &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/high_availability/#rate-limiting-application-reconciliations" rel="noopener noreferrer"&gt;rate limits on application reconciliations&lt;/a&gt; to control the rate at which applications are reconciled and configure retry and backoff strategies. This is useful when you need to prevent the reconciliation loop from consuming too many resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice
&lt;/h3&gt;

&lt;p&gt;To see Argo CD in action, follow the &lt;a href="https://argoproj.github.io/argo-cd/getting_started/" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; to deploy a simple application and manage its lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Argo Rollouts 18%
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://argoproj.github.io/argo-rollouts/" rel="noopener noreferrer"&gt;Argo Rollouts&lt;/a&gt; is a tool that enables you to manage and automate the deployment of applications on Kubernetes. It takes the concept of &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Kubernetes Deployment&lt;/a&gt; to the next level by providing advanced deployment strategies and features that help you achieve more controlled and reliable application updates. &lt;/p&gt;

&lt;p&gt;Like all the other Argo projects it is implemented as a &lt;a href="https://argoproj.github.io/argo-rollouts/#controller-features" rel="noopener noreferrer"&gt;Kubernetes controller&lt;/a&gt; with its own set of CRDs that provide advanced deployment capabilities such as blue-green deployments, canary deployments, and progressive delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Argo Rollouts Fundamentals
&lt;/h3&gt;

&lt;p&gt;In the context of software development, Continuous Integration (CI), Continuous Delivery (CD), and Progressive Delivery (PD) are three key practices that help teams deliver software more efficiently and reliably.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI is the process of integrating code changes in a shared repository and making sure it can be built and tested with the intent of detecting issues early.&lt;/li&gt;
&lt;li&gt;CD is the ability to deploy apps to production, reliably, confidently, and on demand.&lt;/li&gt;
&lt;li&gt;PD is the evolution of CD that focuses on the gradual and controlled delivery of new features to users; which ultimately reduces the risk of deploying new features. In addition to gradual delivery, PD also gives you the ability to implement feature flags, A/B testing, and phased rollouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Argo Rollouts enables PD by providing a new controller called the &lt;a href="https://argoproj.github.io/argo-rollouts/architecture/#argo-rollouts-controller" rel="noopener noreferrer"&gt;Argo Rollouts Controller&lt;/a&gt; to manage the lifecycle of &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/" rel="noopener noreferrer"&gt;Pods&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/" rel="noopener noreferrer"&gt;ReplicaSets&lt;/a&gt;.&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%2F2wz79b3pxfe57op9ozex.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%2F2wz79b3pxfe57op9ozex.png" alt="High-level architecture" width="800" height="385"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://argoproj.github.io/argo-rollouts/architecture/" rel="noopener noreferrer"&gt;https://argoproj.github.io/argo-rollouts/architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://argoproj.github.io/argo-rollouts/architecture/#rollout-resource" rel="noopener noreferrer"&gt;Rollout resource&lt;/a&gt; is the primary resource that you will interact with. This can be a drop in replacement for the &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Deployment&lt;/a&gt; resource or used alongside it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://argoproj.github.io/argo-rollouts/architecture/#replica-sets-for-old-and-new-version" rel="noopener noreferrer"&gt;ReplicaSet&lt;/a&gt; is the same as the one used by the Deployment controller. The difference is that the Rollout controller will manage the ReplicaSet and its Pods.&lt;/p&gt;

&lt;p&gt;Argo Rollouts also has the ability to integrate with &lt;a href="https://argoproj.github.io/argo-rollouts/architecture/#ingressservice" rel="noopener noreferrer"&gt;Ingress, Service and/or service meshes&lt;/a&gt; to manage traffic routing and direct traffic to the new versions of an application.&lt;/p&gt;

&lt;p&gt;The traffic can be managed manually using the &lt;a href="https://argoproj.github.io/argo-rollouts/dashboard/" rel="noopener noreferrer"&gt;UI&lt;/a&gt; or &lt;a href="https://argoproj.github.io/argo-rollouts/features/kubectl-plugin/" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; or automatically using the &lt;a href="https://argoproj.github.io/argo-rollouts/features/analysis/" rel="noopener noreferrer"&gt;Analysis&lt;/a&gt; feature. This feature allows you to integrate with various &lt;a href="https://argoproj.github.io/argo-rollouts/architecture/#metric-providers" rel="noopener noreferrer"&gt;metrics providers&lt;/a&gt; to monitor and analyze the performance of the new version of an application. If the metrics do not meet the defined thresholds, the Rollout controller will automatically rollback to the previous version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Common Progressive Rollout Strategies
&lt;/h3&gt;

&lt;p&gt;The most commonly used progressive rollout strategies within Argo Rollouts Blue-Green and Canary.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://argoproj.github.io/argo-rollouts/features/bluegreen/" rel="noopener noreferrer"&gt;Blue-Green&lt;/a&gt;, both the old and new versions of an application are deployed simultaneously. Once testing is complete, traffic is switched to the new version and the old version is deleted.&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%2F92xxfv5q9xd5hwfmifg9.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%2F92xxfv5q9xd5hwfmifg9.png" alt="Blue-Green" width="600" height="776"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://argoproj.github.io/argo-rollouts/concepts/#blue-green" rel="noopener noreferrer"&gt;https://argoproj.github.io/argo-rollouts/concepts/#blue-green&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://argoproj.github.io/argo-rollouts/features/canary/" rel="noopener noreferrer"&gt;Canary&lt;/a&gt;, a new version of an application is deployed alongside the old version. Only a subset of users will be directed to the new version and monitored for any issues. If no issues are detected, the new version will be gradually rolled out to all users by increasing the percentage of traffic directed to the new version.&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%2Fq8bqvw260j2x0vnya584.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%2Fq8bqvw260j2x0vnya584.png" alt="Canary" width="600" height="776"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://argoproj.github.io/argo-rollouts/concepts/#canary" rel="noopener noreferrer"&gt;https://argoproj.github.io/argo-rollouts/concepts/#canary&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The strategy you choose will depend on your use case and requirements. Be sure to read through this &lt;a href="https://argoproj.github.io/argo-rollouts/concepts/#which-strategy-to-choose" rel="noopener noreferrer"&gt;comparison&lt;/a&gt; to understand the differences.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One important thing to consider when using Argo Rollouts is pod placement on nodes which can trigger unwanted disruptions as node autoscalers look to optimize and scale-down underutilized nodes. To prevent this, you can use &lt;a href="https://argoproj.github.io/argo-rollouts/features/anti-affinity/anti-affinity/" rel="noopener noreferrer"&gt;PodAntiAffinity&lt;/a&gt; to ensure that new versions of pods are not scheduled on the same node as old versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Describe Analysis Template and Analysis Run
&lt;/h3&gt;

&lt;p&gt;As mentioned above, Argo Rollouts can automatically manage traffic routing using the &lt;a href="https://argoproj.github.io/argo-rollouts/features/analysis/" rel="noopener noreferrer"&gt;Analysis feature&lt;/a&gt;. To do this, you will need to define AnalysisTemplate and AnalysisRun resources.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;AnalysisTemplate&lt;/strong&gt; resource defines the metrics and frequency that will be used to monitor the new version of an application. It will also include success and failure thresholds that will be used to determine whether the new version is performing as expected.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;AnalysisRun&lt;/strong&gt; resource defines the actual analysis that will be performed. Upon completion, it will return a status of Successful, Failed, or inconclusive and the Rollout controller will use this information to determine whether to continue with the rollout or rollback to the previous version.&lt;/p&gt;

&lt;p&gt;Think of AnalysisTemplate as a template and AnalysisRun as an instance of that template.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There is quite a bit to learn about this feature when it comes to resource specification, so be sure to read through the &lt;a href="https://argoproj.github.io/argo-rollouts/features/analysis/" rel="noopener noreferrer"&gt;Analysis &amp;amp; Progressive Delivery documentation&lt;/a&gt; as well as its various metric provider integrations to understand how to define and use the resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Practice
&lt;/h3&gt;

&lt;p&gt;To see Argo Rollouts in action, install the &lt;a href="https://argoproj.github.io/argo-rollouts/installation/" rel="noopener noreferrer"&gt;controller&lt;/a&gt; and &lt;a href="https://argoproj.github.io/argo-rollouts/installation/#kubectl-plugin-installation" rel="noopener noreferrer"&gt;kubectl plugin&lt;/a&gt; then follow the &lt;a href="https://argoproj.github.io/argo-rollouts/getting-started/" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; to deploy a simple application and manage its lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Argo Events 12%
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://argoproj.github.io/argo-events/" rel="noopener noreferrer"&gt;Argo Events&lt;/a&gt; enables you to implement event-driven workflow automation at scale within Kubernetes clusters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Argo Events Fundamentals
&lt;/h3&gt;

&lt;p&gt;Event-Driven Architecture (EDA) is the foundation of modern software development. It allows for the creation of highly responsive and adaptable systems that can react to real-time events and changes in the environment.&lt;/p&gt;

&lt;p&gt;Below is an overview of Argo Events:&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%2Fcrbwp9tuvd726qbzcwtc.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%2Fcrbwp9tuvd726qbzcwtc.png" alt="Overview" width="800" height="532"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://argoproj.github.io/argo-events/" rel="noopener noreferrer"&gt;https://argoproj.github.io/argo-events&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Argo Events, you can build event-driven workflows that respond to a wide range of events, such as messages in a queue, changes in a database, or even webhook notifications from external services. Currently Argo Events &lt;a href="https://argoproj.github.io/argo-events/concepts/event_source/" rel="noopener noreferrer"&gt;supports over 20 event sources&lt;/a&gt; and can trigger actions in response to those events including creation of Kubernetes resources, triggering of workflows, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Argo Event Components and Architecture
&lt;/h3&gt;

&lt;p&gt;Understanding the architecture of Argo Events is essential for grasping how it operates and how its components interact with each other.&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%2Fargoproj.github.io%2Fargo-events%2Fassets%2Fargo-events-architecture.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%2Fargoproj.github.io%2Fargo-events%2Fassets%2Fargo-events-architecture.png" alt="High-level architecture" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://argoproj.github.io/argo-events/concepts/architecture/" rel="noopener noreferrer"&gt;https://argoproj.github.io/argo-events/concepts/architecture/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's quickly go over the components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://argoproj.github.io/argo-events/concepts/event_source/" rel="noopener noreferrer"&gt;Event Source&lt;/a&gt; is the external system that generates events. &lt;/li&gt;
&lt;li&gt;
&lt;a href="https://argoproj.github.io/argo-events/concepts/sensor/" rel="noopener noreferrer"&gt;Sensor&lt;/a&gt; listens to event sources and triggers actions to respond to those events.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://argoproj.github.io/argo-events/concepts/eventbus/" rel="noopener noreferrer"&gt;EventBus&lt;/a&gt; is backbone for managing delivery of events from event sources to sensors&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://argoproj.github.io/argo-events/concepts/trigger/" rel="noopener noreferrer"&gt;Trigger&lt;/a&gt; responds to events by performing actions such as starting workflows, creating Kubernetes resources, or sending notifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Webhook event sources, you can use a simple Kubernetes Secret to store the &lt;a href="https://argoproj.github.io/argo-events/eventsources/webhook-authentication/" rel="noopener noreferrer"&gt;authentication&lt;/a&gt; token to protect the webhook endpoint.&lt;/p&gt;

&lt;p&gt;You can also further validate the event data that is received by the event source using &lt;a href="https://argoproj.github.io/argo-events/sensors/filters/intro/" rel="noopener noreferrer"&gt;filters&lt;/a&gt; and &lt;a href="https://argoproj.github.io/argo-events/sensors/filters/expr/" rel="noopener noreferrer"&gt;expressions&lt;/a&gt;. This is useful when you need to ensure that the event data meets certain criteria before triggering an action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice
&lt;/h3&gt;

&lt;p&gt;To see Argo Events in action, follow the &lt;a href="https://argoproj.github.io/argo-events/quick_start/" rel="noopener noreferrer"&gt;quick start guide&lt;/a&gt; to deploy a simple event-driven workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;That was a lot of information to digest but I hope it gives you a good starting point to prepare for the CAPA exam. Be sure to read through the official documentation for each of the projects and try out some of the examples to get a better understanding of how they work.&lt;/p&gt;

&lt;p&gt;In addition to all the URLs provided above, be sure to check out the &lt;a href="https://akuity.github.io/awesome-argo/" rel="noopener noreferrer"&gt;awesome-argo&lt;/a&gt; site maintained by friends at &lt;a href="https://akuity.io/" rel="noopener noreferrer"&gt;Akuity&lt;/a&gt; for a ton of great resources on the Argo projects.&lt;/p&gt;

&lt;p&gt;If this guide was helpful, please share it with your friends and colleagues. If you have any questions or feedback, feel free to leave a comment below or reach out on &lt;a href="https://www.linkedin.com/in/yupaul/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://bsky.app/profile/paulyu.dev" rel="noopener noreferrer"&gt;BlueSky&lt;/a&gt;, or &lt;a href="https://x.com/pauldotyu" rel="noopener noreferrer"&gt;X&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Good luck with your exam and happy learning! 🚀&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>argo</category>
      <category>cloudnative</category>
      <category>certification</category>
    </item>
    <item>
      <title>Using AKS-managed Istio External Ingress Gateway with Gateway API</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Tue, 13 Aug 2024 14:06:38 +0000</pubDate>
      <link>https://forem.com/azure/using-aks-managed-istio-external-ingress-gateway-with-gateway-api-2j</link>
      <guid>https://forem.com/azure/using-aks-managed-istio-external-ingress-gateway-with-gateway-api-2j</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Kubernetes is great at orchestrating containers, but it can be a bit tricky to manage traffic routing. There are many options and implementations that you, as a cluster operator have probably had to deal with. We have the default Service resource that can be used to expose applications, but it is limited to routing based on layer 4 (TCP/UDP) and does not support more advanced traffic routing use cases. There's also the Ingress controller, which enabled layer 7 (HTTP) routing, and securing the North-South traffic using TLS, but it was not standardized and each vendor implementation required learning a new set of resource annotations. When it comes to managing and securing East-West traffic between services, there's Service Mesh which is yet another layer of infrastructure to manage on top of Kubernetes. And we're in the same boat when it comes to resource management with each vendor having their own ways of doing things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rise of the Gateway API
&lt;/h2&gt;

&lt;p&gt;There's no doubt that Ingress and service mesh have greatly improved the way we manage and secure traffic in Kubernetes, but the subtle differences between implementations has made it difficult to standardize on a single way of doing things. This is where the &lt;a href="https://gateway-api.sigs.k8s.io/" rel="noopener noreferrer"&gt;Gateway API&lt;/a&gt; comes in. It is a Kubernetes add-on that aims to bring users an extensible, role-oriented, and protocol-aware services configuration mechanism. It is a new API that both Ingress and service mesh vendors can standardize on.&lt;/p&gt;

&lt;p&gt;The API has been designed from the ground up, taking into account the lessons learned from the Ingress and service mesh APIs. To me, it's a game-changer because it ensures that you can express traffic routing configurations that were previously only possible via custom Ingress annotations. It also offers role-oriented configuration, which means that cluster operators can configure gateways and app developers can manage routes through the gateway. This is a big win for organizations that have a separation of duties between cluster operators and app developers. And finally, the API is portable, meaning that the API specification is supported by many implementations including &lt;a href="https://istio.io/latest/" rel="noopener noreferrer"&gt;Istio&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Istio on AKS
&lt;/h2&gt;

&lt;p&gt;If you haven't heard of Istio yet, it is a &lt;a href="https://www.cncf.io/announcements/2023/07/12/cloud-native-computing-foundation-reaffirms-istio-maturity-with-project-graduation/" rel="noopener noreferrer"&gt;CNCF-graduated service mesh project&lt;/a&gt; that provides a uniform way to connect, manage, and secure microservices. Istio is a fully-featured service mesh that supports managing traffic flows between services, enforcing access policies, and aggregating telemetry data. It not only provides East-West traffic management and security but also North-South traffic management and security through its Ingress Gateway. We can also thank Istio for much of the inspiration behind the Gateway API - if you've worked with Istio, you'll see the similarities.&lt;/p&gt;

&lt;p&gt;AKS is a managed Kubernetes offering and as such many popular open-source projects are available as managed add-ons and extensions. &lt;a href="https://learn.microsoft.com/azure/aks/istio-about?WT.mc_id=containers-146848-pauyu" rel="noopener noreferrer"&gt;Istio is one of those add-ons that can be enabled on AKS&lt;/a&gt;. AKS also provides managed deployments of Istio Ingress Gateway, which can be used to route traffic into the cluster from both internal origins within a virtual network as well as external traffic from the public internet. In this article, I'll show you how you can experiment with the Gateway API to manage the external Istio Ingress Gateway on AKS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Gateway API is not fully supported yet supported for the Istio-based managed add-on on AKS and this should be considered an experiment for now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Gateway API in action with Istio on AKS
&lt;/h2&gt;

&lt;p&gt;Here's a high-level overview of what we'll be doing to leverage the Gateway API to manage the Istio external ingress gateway on AKS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision &lt;a href="https://learn.microsoft.com/azure/aks/istio-deploy-addon#install-mesh-during-cluster-creation?WT.mc_id=containers-146848-pauyu" rel="noopener noreferrer"&gt;AKS cluster with Istio enabled&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Provision &lt;a href="https://learn.microsoft.com/azure/aks/istio-deploy-ingress#enable-external-ingress-gateway?WT.mc_id=containers-146848-pauyu" rel="noopener noreferrer"&gt;AKS managed Istio external ingress gateway&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://gateway-api.sigs.k8s.io/guides/#installing-gateway-api" rel="noopener noreferrer"&gt;Gateway API CRDs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Deploy a new &lt;a href="https://gateway-api.sigs.k8s.io/concepts/api-overview/#gateway" rel="noopener noreferrer"&gt;Gateway&lt;/a&gt; resource in the &lt;code&gt;aks-istio-ingress&lt;/code&gt; namespace and configure it to program the existing AKS-managed Istio Ingress Gateway - this is the key!&lt;/li&gt;
&lt;li&gt;Deploy &lt;a href="https://gateway-api.sigs.k8s.io/concepts/api-overview/#httproute" rel="noopener noreferrer"&gt;HTTPRoute&lt;/a&gt; resource that uses the new Gateway&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before you begin, you'll need to make sure you have access to an Azure subscription and have the &lt;a href="https://learn.microsoft.com/cli/azure/install-azure-cli?WT.mc_id=containers-146848-pauyu" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt; and &lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt; installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provision AKS cluster with Istio enabled
&lt;/h3&gt;

&lt;p&gt;Start by setting some local variables.&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="nv"&gt;RG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rg-istio-gtw-demo
&lt;span class="nv"&gt;AKS_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-istio-gtw-demo
&lt;span class="nv"&gt;LOCATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;westus3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a resource group and an AKS cluster with Istio enabled. The &lt;code&gt;--enable-asm&lt;/code&gt; flag enables the managed Istio add-on and the &lt;code&gt;--revision asm-1-22&lt;/code&gt; flag specifies the version of Istio to install.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="nv"&gt;$LOCATION&lt;/span&gt;

az aks create &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--enable-asm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--kubernetes-version&lt;/span&gt; 1.30 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--revision&lt;/span&gt; asm-1-22

az aks get-credentials &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the Istio external ingress gateway with the following command. This can take a few minutes to complete.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks mesh enable-ingress-gateway &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--resource-group&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--ingress-gateway-type&lt;/span&gt; external
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy a demo application
&lt;/h3&gt;

&lt;p&gt;To demo the Gateway API, we'll deploy the &lt;a href="https://github.com/Azure-Samples/aks-store-demo" rel="noopener noreferrer"&gt;AKS store demo app&lt;/a&gt;, a simple e-commerce application which consists of two services: &lt;code&gt;store-front&lt;/code&gt; and &lt;code&gt;store-admin&lt;/code&gt;. The &lt;code&gt;store-front&lt;/code&gt; service is the customer facing e-commerce store, and the &lt;code&gt;store-admin&lt;/code&gt; service is a back-end service that serves an admin dashboard for managing the store.&lt;/p&gt;

&lt;p&gt;Create a namespace and optionally label it for Istio injection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace pets
kubectl label namespace pets istio.io/rev&lt;span class="o"&gt;=&lt;/span&gt;asm-1-22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploy the AKS store demo app into the &lt;code&gt;pets&lt;/code&gt; namespace using the &lt;strong&gt;aks-store-all-in-one&lt;/strong&gt; YAML manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; pets &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/Azure-Samples/aks-store-demo/main/aks-store-all-in-one.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/Azure-Samples/aks-store-demo/blob/44efc6edab0a811e2d8c6d8b559e066307cbbe42/aks-store-all-in-one.yaml#L413" rel="noopener noreferrer"&gt;aks-store-all-in-one.yaml&lt;/a&gt; manifest exposes the two services using &lt;code&gt;LoadBalancer&lt;/code&gt; which assigns a public IP for each service. We want to use the Istio external ingress gateway as the application's point of entry, so we can patch the &lt;code&gt;store-front&lt;/code&gt; and &lt;code&gt;store-admin&lt;/code&gt; services to use &lt;code&gt;ClusterIP&lt;/code&gt; instead of &lt;code&gt;LoadBalancer&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;kubectl patch service &lt;span class="nt"&gt;-n&lt;/span&gt; pets store-admin &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"type":"ClusterIP"}}'&lt;/span&gt;
kubectl patch service &lt;span class="nt"&gt;-n&lt;/span&gt; pets store-front &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"spec":{"type":"ClusterIP"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure all the pods and services are running before moving on.&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 all &lt;span class="nt"&gt;-n&lt;/span&gt; pets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implement Gateway API for Istio external ingress
&lt;/h3&gt;

&lt;p&gt;The Gateway API is not yet included in the Kubernetes distribution by default, so you will need to install the Gateway API CRDs and controllers before you can use it. You can always find the latest releases on the &lt;a href="https://github.com/kubernetes-sigs/gateway-api/releases" rel="noopener noreferrer"&gt;Gateway API GitHub releases page&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.1.0/standard-install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use the Gateway API, we need to create a Gateway resource in the &lt;code&gt;aks-istio-ingress&lt;/code&gt; namespace. It's important that the Gateway resource is deployed into the same namespace as the Istio external ingress gateway; otherwise, the Gateway resource will not be programmed correctly.&lt;/p&gt;

&lt;p&gt;The Gateway resource is a top-level resource that acts as a load balancer operating at the edge of the mesh. It can be used to leverage a particular &lt;a href="https://gateway-api.sigs.k8s.io/api-types/gatewayclass/" rel="noopener noreferrer"&gt;GatewayClass&lt;/a&gt;, in our case, we will be using the Istio external ingress gateway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P2CrOQqw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gateway-api.sigs.k8s.io/images/resource-model.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P2CrOQqw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gateway-api.sigs.k8s.io/images/resource-model.png" alt="Gateway API resource model" width="800" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href="https://gateway-api.sigs.k8s.io/" rel="noopener noreferrer"&gt;https://gateway-api.sigs.k8s.io&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Deploy the Gateway for our sample application in the &lt;code&gt;aks-istio-ingress&lt;/code&gt; namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; aks-istio-ingress &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: gateway
spec:
  gatewayClassName: istio
  addresses:
  - value: aks-istio-ingressgateway-external
    type: Hostname
  listeners:
  - name: default
    hostname: "*.aks.rocks"
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical implementation detail here is the &lt;code&gt;gatewayClassName&lt;/code&gt; field with a value of &lt;code&gt;istio&lt;/code&gt; and the &lt;code&gt;addresses&lt;/code&gt; field with a value of &lt;code&gt;aks-istio-ingressgateway-external&lt;/code&gt;. By default, the Gateway resource will automatically provision a Service and Deployment but we don't need in this scenario. Instead, we can &lt;a href="https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api/#manual-deployment" rel="noopener noreferrer"&gt;manually&lt;/a&gt; specify the DNS name of the Service that was deployed when AKS provisioned the Istio ingress gateway for you. Also, the &lt;code&gt;allowedRoutes&lt;/code&gt; field specifies that the Gateway will accept routes from all namespaces. This can be restricted to specific namespaces if needed.&lt;/p&gt;

&lt;p&gt;Next, deploy an HTTPRoute for the &lt;code&gt;store-admin&lt;/code&gt; and &lt;code&gt;store-front&lt;/code&gt; services in the &lt;code&gt;pets&lt;/code&gt; namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; pets &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: store-admin
spec:
  parentRefs:
  - name: gateway
    namespace: aks-istio-ingress
  hostnames: ["admin.aks.rocks"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: store-admin
      port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: store-front
spec:
  parentRefs:
  - name: gateway
    namespace: aks-istio-ingress
  hostnames: ["store.aks.rocks"]
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: store-front
      port: 80
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have two routes that will be routed through the gateway. The &lt;code&gt;store-admin&lt;/code&gt; route will route traffic to the &lt;code&gt;store-admin&lt;/code&gt; service and the &lt;code&gt;store-front&lt;/code&gt; route will route traffic to the &lt;code&gt;store-front&lt;/code&gt; service. The &lt;code&gt;hostnames&lt;/code&gt; field specifies the hostname that the route will match on and the &lt;code&gt;rules&lt;/code&gt; field specifies the path that the route will match on.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Being able to deploy HTTPRoutes in application namespaces is a powerful feature of the Gateway API because it allows app developers to manage their own routes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's validate that the Gateway and HTTPRoute resources were created successfully. Grab the public IP address of the Istio external ingress gateway and test the store app using a &lt;code&gt;curl&lt;/code&gt; command.&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="nv"&gt;INGRESS_PUBLIC_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; aks-istio-ingress aks-istio-ingressgateway-external &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].ip}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-IL&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INGRESS_PUBLIC_IP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Host: store.aks.rocks"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;-H "Host: store.aks.rocks"&lt;/code&gt; flag is used to specify the hostname in the request header. This is important because the Istio gateway is configured to route based on hostname.&lt;/p&gt;

&lt;p&gt;Optionally, you could also add an entries to your &lt;code&gt;/etc/hosts&lt;/code&gt; file.&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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INGRESS_PUBLIC_IP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; admin.aks.rocks store.aks.rocks"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the host entries updated, you can test the app in your browser. Just be sure to remove the entries from your &lt;code&gt;/etc/hosts&lt;/code&gt; file when you're done.&lt;/p&gt;

&lt;p&gt;Also, don't forget to clean up the Azure resources when you're done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group delete &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We can see that the Gateway API is a powerful new API that provides a standardized way to configure traffic routing in Kubernetes. It is a big step forward from the Ingress and service mesh APIs and provides a way to express traffic routing configurations that were previously only possible via custom Ingress annotations. The Gateway API is portable and can be used with many different implementations including Istio. In this article, you learned how you can install the Gateway API CRDs into your AKS cluster and use the Gateway API to manage the AKS-managed external Istio Ingress Gateway.&lt;/p&gt;

&lt;p&gt;Many resource types and features of Gateway API have graduated to GA with other parts of the API still evolving. If you'd like to get involved in the Gateway API project, head over to the &lt;a href="https://github.com/kubernetes-sigs/gateway-api" rel="noopener noreferrer"&gt;Gateway API GitHub repository&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;Happy traffic routing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/gateway/" rel="noopener noreferrer"&gt;Gateway API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gateway-api.sigs.k8s.io/implementations/" rel="noopener noreferrer"&gt;Gateway API implementations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://istio.io/latest/docs/tasks/traffic-management/ingress/gateway-api" rel="noopener noreferrer"&gt;Kubernetes Gateway API with Istio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/aks/istio-about?WT.mc_id=containers-146848-pauyu" rel="noopener noreferrer"&gt;Istio-based service mesh add-on for Azure Kubernetes Service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>istio</category>
      <category>gatewayapi</category>
      <category>aks</category>
    </item>
    <item>
      <title>Adding a GitHub Codespace button to your README</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Thu, 21 Mar 2024 13:00:00 +0000</pubDate>
      <link>https://forem.com/azure/adding-a-github-codespace-button-to-your-readme-5f6l</link>
      <guid>https://forem.com/azure/adding-a-github-codespace-button-to-your-readme-5f6l</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/features/codespaces" rel="noopener noreferrer"&gt;GitHub Codespaces&lt;/a&gt; is a great way to make it easier for people to contribute to your project. With a few clicks, folks can spin up a Codespace environment with all necessary tooling installed and be productive right away. But it does take a few clicks and this quick post is to show how you can save developers a click or two because every click matters 😆 &lt;/p&gt;

&lt;p&gt;With one line of markdown in your README, you can add a button that looks like this...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HNMfmY8G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.paulyu.dev/articles/2024/adding-codespace-button-to-your-github-repo/open-in-github-codespaces.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HNMfmY8G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://assets.paulyu.dev/articles/2024/adding-codespace-button-to-your-github-repo/open-in-github-codespaces.png" alt="GitHub Codespace button" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add the button, you'll first need to get your GitHub repo's ID. The GitHub repo ID is not obvious to find, but you can use the &lt;a href="https://github.com/cli/cli" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt; to retrieve it.&lt;/p&gt;

&lt;p&gt;Run a command like this from the root of your repo and make sure to replace &lt;code&gt;Azure-Samples/aks-store-demo&lt;/code&gt; with your &lt;code&gt;org/repo&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;gh api repos/Azure-Samples/aks-store-demo &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.id'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, all you need to do is add the following line to your README, like this...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;![Open in GitHub Codespaces&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/codespaces/badge.svg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;](https://github.com/codespaces/new?hide_repo_select=true&amp;amp;ref=main&amp;amp;repo=648726487)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the query parameters &lt;strong&gt;ref&lt;/strong&gt; and &lt;strong&gt;repo&lt;/strong&gt; in the URL above. Make sure you replace &lt;code&gt;648726487&lt;/code&gt; in the code above with your repo's ID and set &lt;strong&gt;ref&lt;/strong&gt; to whatever branch you want to open the Codespace in as it will currently open in the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;That's it! Now when someone visits your repo, they'll see a button at the top of the README that they can simply click to open a Codespace for your repo. 🎉&lt;/p&gt;

</description>
      <category>github</category>
      <category>codespaces</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Soaring to New Heights with Kaito: The Kubernetes AI Toolchain Operator</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Wed, 20 Mar 2024 14:00:00 +0000</pubDate>
      <link>https://forem.com/azure/soaring-to-new-heights-with-kaito-the-kubernetes-ai-toolchain-operator-24ai</link>
      <guid>https://forem.com/azure/soaring-to-new-heights-with-kaito-the-kubernetes-ai-toolchain-operator-24ai</guid>
      <description>&lt;p&gt;Earlier today at KubeCon Europe 2024, &lt;a href="https://twitter.com/jorgefpalma" rel="noopener noreferrer"&gt;Jorge Palma&lt;/a&gt; of the AKS team gave a &lt;a href="https://x.com/CloudNativeFdn/status/1770375558607647132?s=20" rel="noopener noreferrer"&gt;keynote talk on Kaito, the Kubernetes AI Toolchain Operator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1770375558607647132-590" src="https://platform.twitter.com/embed/Tweet.html?id=1770375558607647132"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1770375558607647132-590');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1770375558607647132&amp;amp;theme=dark"
  }



 &lt;/p&gt;

&lt;p&gt;This tool has been released as an &lt;a href="https://github.com/Azure/kaito" rel="noopener noreferrer"&gt;open-source project&lt;/a&gt; a few months back and you may or may not have heard of it. &lt;/p&gt;

&lt;p&gt;So if you don't know, now you know...&lt;/p&gt;

&lt;p&gt;You're probably thinking, "but what is it, and what can it do for me?" 🤔&lt;/p&gt;
&lt;h2&gt;
  
  
  What’s in a name?
&lt;/h2&gt;

&lt;p&gt;Kaito is a cool name and the word rolls off the tongue quite nicely. After doing a bit of searching I came across a definition of the name Kaito, which is a Japanese name commonly given for boys and has deep meaning and symbolism within the Japanese culture. &lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://japaneseboard.com/what-does-kaito-mean-in-japanese/" rel="noopener noreferrer"&gt;Japanese Board&lt;/a&gt;, the name is made up of two kanji characters: 海 and 斗&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;海 (kai) means "sea" or "ocean"&lt;/li&gt;
&lt;li&gt;斗 (to) has several meanings and references like a "measuring cup" or "big dipper" used in Chinese astrology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ultimately the word symbolizes guidance and direction, and aligning oneself with the stars over the vastness of the sea. This is a fitting name for a tool that aims to help you navigate through what could be a sea of complexity that is, AI and Kubernetes 😅&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Kaito?
&lt;/h2&gt;

&lt;p&gt;Okay, enough with the philosophical stuff. Let's get to the meat of it.&lt;/p&gt;

&lt;p&gt;Kaito is a Kubernetes Operator that aims to help you with your AI workloads on Kubernetes. It's a tool that in it's current form, helps you &lt;strong&gt;reduce your time to inference&lt;/strong&gt; when it comes to deploying open-source AI models with inferencing endpoints on Kubernetes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why use Kaito?
&lt;/h2&gt;

&lt;p&gt;If you're working with AI today, especially OpenAI or &lt;a href="https://azure.microsoft.com/products/ai-services/openai-service" rel="noopener noreferrer"&gt;Azure OpenAI&lt;/a&gt;, you're probably familiar with the fact the model is hosted in the cloud and you primarily interact with it via REST API. The Model-as-a-Service (MaaS) approach is great for a lot of use cases, but there are times when you need to run the model closer to the data due to a variety of reasons like data sovereignty, compliance, or latency.&lt;/p&gt;

&lt;p&gt;Also, you might not want to run OpenAI's models because they are not truly open-source and may feel like a black box when it comes to understanding how they were built and how they work. You might want to run your own models, or models from the open-source community, many of which can be found on &lt;a href="https://huggingface.co/models" rel="noopener noreferrer"&gt;Hugging Face's model hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Kaito aims to help you with this by providing a way to deploy open-source AI models as inferencing services on Kubernetes. It cuts through all the minutiae of deploying, scaling, and managing AI and GPU-based workloads on Kubernetes.&lt;/p&gt;

&lt;p&gt;More specifically it helps you with the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated GPU node provisioning and configuration&lt;/strong&gt; Kaito will automatically provision and configure GPU nodes for you. This can help reduce the operational burden of managing GPU nodes, configuring them for Kubernetes, and tuning model deployment parameters to fit GPU profiles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced cost&lt;/strong&gt;: Kaito can help you save money by splitting inferencing across lower end GPU nodes which may also be more readily available and cost less than high-end GPU nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support for popular open-source LLMs&lt;/strong&gt;: Kaito offers preset configurations for popular open-source LLMs. This can help you deploy and manage open-source LLMs on AKS and integrate them with your intelligent applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fine-grained control&lt;/strong&gt;: You can have full control over data security and privacy, model development and configuration transparency, and the ability to fine-tune the model to fit your specific use case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network and data security&lt;/strong&gt;: You can ensure these models are ring-fenced within your organization's network and/or ensure the data never leaves the Kubernetes cluster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Kaito Architecture
&lt;/h2&gt;

&lt;p&gt;Kaito is built using the &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/" rel="noopener noreferrer"&gt;Kubernetes controller design pattern&lt;/a&gt;. Here is a high-level overview of the architecture of Kaito which can be found in the &lt;a href="https://github.com/Azure/kaito" rel="noopener noreferrer"&gt;Kaito GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.paulyu.dev%2Farticles%2F2024%2Fsoaring-with-kaito%2Fkaito-arch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.paulyu.dev%2Farticles%2F2024%2Fsoaring-with-kaito%2Fkaito-arch.png" alt="Kaito architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The operator that consists of two controllers.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Workspace controller:&lt;/strong&gt; This controller reconciles the Workspace custom resource which creates a machine custom resource to trigger node auto provisioning and creates the inference workload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node provisioner controller&lt;/strong&gt;: This controller uses the machine custom resource to interact with the Workspace controller. It leverages Karpenter APIs to add new GPU nodes to the AKS cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a cluster operator, you only need to be concerned with a couple of &lt;strong&gt;helm install&lt;/strong&gt; commands to install the controllers and a &lt;strong&gt;Workspace&lt;/strong&gt; custom resource. The Workspace controller will reconcile the Workspace custom resource which creates a &lt;strong&gt;machine&lt;/strong&gt; custom resource to trigger node auto provisioning and creates the inference workload based on a preset configuration. &lt;/p&gt;

&lt;p&gt;The Kaito team has done quite a bit of work to ensure some of the most popular open-source models like &lt;a href="https://huggingface.co/meta-llama" rel="noopener noreferrer"&gt;Meta's Llama2&lt;/a&gt;, &lt;a href="https://huggingface.co/tiiuae" rel="noopener noreferrer"&gt;TII's Falcon&lt;/a&gt;, &lt;a href="https://huggingface.co/mistralai" rel="noopener noreferrer"&gt;Mistral AI's Mistral&lt;/a&gt;, &lt;a href="https://huggingface.co/microsoft/phi-2" rel="noopener noreferrer"&gt;Microsoft's Phi-2&lt;/a&gt;, and others are containerized properly and work well on Kubernetes. So they've created what they call &lt;strong&gt;presets&lt;/strong&gt; for these models. This means you can deploy these models with a few lines of YAML and Kaito will take care of the rest.&lt;/p&gt;

&lt;p&gt;Here is an example of a Workspace custom resource:&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;kaito.sh/v1alpha1&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;Workspace&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;workspace-falcon-7b-instruct&lt;/span&gt;
&lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;instanceType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Standard_NC12s_v3"&lt;/span&gt;
  &lt;span class="na"&gt;labelSelector&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="na"&gt;apps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;falcon-7b-instruct&lt;/span&gt;
&lt;span class="na"&gt;inference&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preset&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;falcon-7b-instruct"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only 12 lines of YAML 🎉&lt;/p&gt;

&lt;p&gt;If you're wondering if this works on different cloud providers, unfortunately the answer right now is "no". Kaito is currently only supported on Azure. But with this being an open-source project, you are welcome to contribute and help make it work on other cloud providers 🤝&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Getting started with Kaito is pretty straightforward. You can visit the Kaito GitHub repo and follow the &lt;a href="https://github.com/Azure/kaito/blob/main/docs/installation.md" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt; to start.&lt;/p&gt;

&lt;p&gt;I've also recorded a "Learn Live" session on &lt;a href="https://www.youtube.com/@MicrosoftReactor" rel="noopener noreferrer"&gt;@MicrosoftReactor&lt;/a&gt; YouTube channel with &lt;a href="https://www.linkedin.com/in/ishaan-sehgal/" rel="noopener noreferrer"&gt;Ishaan Seghal&lt;/a&gt; from the Kaito team where we did an in-depth walk-through on how to get started. Be sure to check that video out below.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/T33nOlRVh28"&gt;
&lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;The workshop material for the session can be found here: &lt;a href="https://aka.ms/cloudnative/learnlive/intelligent-apps-on-aks/episode-2" rel="noopener noreferrer"&gt;Open Source Models on AKS with Kaito&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with any big event like KubeCon comes a slew of announcements. One of the announcements was the release of the &lt;a href="https://azure.microsoft.com/updates/public-preview-kubernetes-ai-toolchain-operator-kaito-addon-for-aks/" rel="noopener noreferrer"&gt;Kaito add-on for AKS&lt;/a&gt; (currently in Preview) which simplifies this even more by having AKS install and manage Kaito in your AKS cluster for you. Documentation for that can be found here: &lt;a href="https://learn.microsoft.com/azure/aks/ai-toolchain-operator" rel="noopener noreferrer"&gt;Deploy an AI model on Azure Kubernetes Service (AKS) with the AI toolchain operator (preview)&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Lastly, there is a neat blog article which walks you through some of the steps listed above but includes an interesting use case of using Kaito for forecasting energy usage using the LLaMA2 model. Be sure to check that out as well. &lt;a href="https://azure.github.io/Cloud-Native/60DaysOfIA/forecasting-energy-usage-with-intelligent-apps-1/" rel="noopener noreferrer"&gt;2.1 Forecasting Energy Usage with Intelligent Apps Part 1: Laying the Groundwork with AKS, KAITO, and LLaMA&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More Presets
&lt;/h2&gt;

&lt;p&gt;The Kaito team publishes all the models they support as presets &lt;a href="https://github.com/Azure/kaito/blob/main/presets/README.md" rel="noopener noreferrer"&gt;here&lt;/a&gt; and is working on adding more for popular open-source AI models. If you have a model you'd like to see supported, you can open an issue on the Kaito GitHub repo and let the team know. They are always looking for feedback and contributions from the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By now, you should have a good understanding of what Kaito is and what it aims to help you with as a Kubernetes Operator. Being able to deploy open-source AI models as inferencing services on Kubernetes will be a game-changer for a lot of folks. But inferencing is just one part of the story and probably and that's where this Kaito journey begins. The team is also looking at how to help you with fine-tuning these open-source AI models on Kubernetes in the future, so stay tuned for that.&lt;/p&gt;

&lt;p&gt;So, leverage Kaito for your AI workloads in Kubernetes. The name says it all.&lt;/p&gt;

&lt;p&gt;If you have any questions or feedback, please feel free to leave a comment below or reach out to me on &lt;a href="https://twitter.com/pauldotyu" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/yupaul" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. Also be sure to follow the &lt;a href="https://www.youtube.com/@theakscommunity" rel="noopener noreferrer"&gt;@TheAKSCommunity&lt;/a&gt; YouTube channel for more on this project as it evolves.&lt;/p&gt;

&lt;p&gt;Until next time,&lt;/p&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

</description>
      <category>cloudnative</category>
      <category>kubernetes</category>
      <category>ai</category>
      <category>azure</category>
    </item>
    <item>
      <title>Strengthening the Secure Supply Chain</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Sun, 17 Mar 2024 13:00:00 +0000</pubDate>
      <link>https://forem.com/azure/strengthening-the-secure-supply-chain-2el4</link>
      <guid>https://forem.com/azure/strengthening-the-secure-supply-chain-2el4</guid>
      <description>&lt;p&gt;This post will walk you through a demo I presented at the &lt;a href="https://www.socallinuxexpo.org/scale/21x" rel="noopener noreferrer"&gt;SCaLE21X&lt;/a&gt; conference. The session is titled, &lt;a href="https://www.socallinuxexpo.org/scale/21x/presentations/strengthening-secure-supply-chain-project-copacetic-eraser-and-fluxcd" rel="noopener noreferrer"&gt;Strengthening the Secure Supply Chain with Project Copacetic, Eraser, and FluxCD&lt;/a&gt; and this step-by-step guide will enable you do it on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To begin, you will need to have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/get-started/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; to run a Kubernetes cluster locally&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt; to clone the demo repository&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/signup" rel="noopener noreferrer"&gt;GitHub account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will also be using the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/#installation" rel="noopener noreferrer"&gt;KIND&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;Kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cli/cli?tab=readme-ov-file#installation" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubectl.docs.kubernetes.io/installation/kustomize/" rel="noopener noreferrer"&gt;Kustomize&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.sh/docs/intro/install/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluxcd.io/flux/get-started/" rel="noopener noreferrer"&gt;FluxCD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aquasecurity.github.io/trivy/v0.18.3/installation/" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://project-copacetic.github.io/copacetic/website/installation" rel="noopener noreferrer"&gt;Copacetic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eraser-dev.github.io/eraser/docs/installation" rel="noopener noreferrer"&gt;Eraser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But don't worry about installing all of these tools right now. I will walk you through the installation process as we go. All you need to start is Docker Desktop and a Bash shell.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: I used a Mac on Apple Silicon for this demo. If you are using a different operating system, you may need to adjust the commands accordingly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Install KIND and kubectl
&lt;/h2&gt;

&lt;p&gt;First, you will need to create a Kubernetes cluster. You can use KIND which stands for Kubernetes in Docker. It is a tool for running local Kubernetes clusters using containers as “nodes”. &lt;/p&gt;

&lt;p&gt;Head over to the &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/#installation" rel="noopener noreferrer"&gt;KIND documentation&lt;/a&gt; to install KIND on your local machine. On my Mac, I used Homebrew to install KIND:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;kind
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to install kubectl to interact with the cluster. Head over to the &lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl documentation&lt;/a&gt; to install the tool on your local machine. This is how I installed kubectl on my Mac using the curl command:&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;-LO&lt;/span&gt; &lt;span class="s2"&gt;"https://dl.k8s.io/release/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; https://dl.k8s.io/release/stable.txt&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/bin/darwin/arm64/kubectl"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a local Kubernetes cluster
&lt;/h2&gt;

&lt;p&gt;Once you have KIND installed, you can use the following commands to create a single-node Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; scale21x-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the local Kubernetes cluster running and kubectl installed, run the following command to verify the cluster is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl cluster-info &lt;span class="nt"&gt;--context&lt;/span&gt; kind-scale21x-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install GitHub CLI
&lt;/h2&gt;

&lt;p&gt;We'll be working with a GitHub repository and configuring GitHub Actions. I like to use the GitHub CLI which makes it realy easy to work with GitHub using the command line. &lt;/p&gt;

&lt;p&gt;Head over to the &lt;a href="https://github.com/cli/cli?tab=readme-ov-file#installation" rel="noopener noreferrer"&gt;GitHub CLI documentation&lt;/a&gt; and install the tool on your local machine. I installed the GitHub CLI with 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;brew &lt;span class="nb"&gt;install &lt;/span&gt;gh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the GitHub CLI installed, run the following command to authenticate with GitHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh auth login &lt;span class="nt"&gt;--scopes&lt;/span&gt; repo,workflow,read:packages,write:packages,delete:packages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: The login scopes listed above are required for the demo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Export your GitHub username as an environment variable:&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;GITHUB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh api user &lt;span class="nt"&gt;--jq&lt;/span&gt; .login&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also want to be able to push container images to the GitHub Container Registry, so we need to authenticate with the GitHub Container Registry. You can use the following command to authenticate with the GitHub Container Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh auth token | docker login ghcr.io &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt; &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fork and clone sample app repo
&lt;/h2&gt;

&lt;p&gt;We will be using a sample application that I wrote for this demo. You can fork and clone the &lt;a href="https://github.com/Azure-Samples/aks-store-demo" rel="noopener noreferrer"&gt;Azure-Samples/aks-store-demo&lt;/a&gt; repository 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;gh repo fork Azure-Samples/aks-store-demo &lt;span class="nt"&gt;--clone&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;aks-store-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When working with a forked repository, we need to set the default repo, so that when we execute workflow commands it will use the forked repo and not the original. You can use the following command to set the default repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh repo set-default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: When prompted, select the forked repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Build sample app container
&lt;/h2&gt;

&lt;p&gt;The sample app contains multiple applications, but we'll only focus on the &lt;strong&gt;store-front&lt;/strong&gt; app. Let's build the &lt;strong&gt;store-front&lt;/strong&gt; container and push it to the GitHub Container Registry. &lt;/p&gt;

&lt;p&gt;Run the following command to build the container image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;--label&lt;/span&gt; &lt;span class="s2"&gt;"org.opencontainers.image.source=https://github.com/&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;&lt;span class="s2"&gt;/aks-store-demo"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; ghcr.io/&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;/aks-store-demo/store-front:1.2.0 &lt;span class="nt"&gt;-t&lt;/span&gt; ghcr.io/&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;/aks-store-demo/store-front:latest ./src/store-front
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: This may take a few minutes to complete.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following commands to push the tagged container image to the GitHub Container Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push ghcr.io/&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;/aks-store-demo/store-front:latest
docker push ghcr.io/&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;/aks-store-demo/store-front:1.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: You may need to link the package registry to the repository. You can do this by following the instructions listed &lt;a href="https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Install Kustomize CLI
&lt;/h2&gt;

&lt;p&gt;Kustomize is a neat tool for customizing Kubernetes configurations. It makes it very easy to manage and customize Kubernetes configurations including container images and tags. Head over to the &lt;a href="https://kubectl.docs.kubernetes.io/installation/kustomize/" rel="noopener noreferrer"&gt;Kustomize documentation&lt;/a&gt; to install Kustomize on your local machine. I installed Kustomize with 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;brew &lt;span class="nb"&gt;install &lt;/span&gt;kustomize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Edit store-front image source
&lt;/h2&gt;

&lt;p&gt;Using the kustomize CLI, we can update the kustomization.yaml file to use the container image you just pushed to the GitHub Container Registry. &lt;/p&gt;

&lt;p&gt;First, make sure you are in the root directory of the cloned repository then change into the directory where the kustomization.yaml file is located:&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;cd &lt;/span&gt;kustomize/overlays/dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can use the following command to update the kustomization.yaml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kustomize edit &lt;span class="nb"&gt;set &lt;/span&gt;image ghcr.io/azure-samples/aks-store-demo/store-front&lt;span class="o"&gt;=&lt;/span&gt;ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit the changes to the kustomization.yaml file and push the changes to the repository:&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;cd&lt;/span&gt; -
git add ./kustomize/overlays/dev/kustomization.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'feat: update store-front image'&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Flux CLI
&lt;/h2&gt;

&lt;p&gt;Next, you will need to install the Flux CLI so that you can bootstrap your Kubernetes cluster for GitOps. Head over to the &lt;a href="https://fluxcd.io/flux/get-started/" rel="noopener noreferrer"&gt;Flux documentation&lt;/a&gt; to install Flux on your local machine. Again, I'm using Homebrew so I installed with 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;brew &lt;span class="nb"&gt;install &lt;/span&gt;fluxcd/tap/flux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bootstrap the Kubernetes Cluster for Flux
&lt;/h2&gt;

&lt;p&gt;We're ready to bootstrap the Kubernetes cluster. But you need to ensure that you have the following environment variables set so that Flux can authenticate with GitHub on your behalf:&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;GITHUB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh api user &lt;span class="nt"&gt;--jq&lt;/span&gt; .login&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh auth token&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to bootstrap your Kubernetes cluster for GitOps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux bootstrap github create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--personal&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--private&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./kustomize/overlays/dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reconcile&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--read-write-key&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--author-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxcdbot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--author-email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxcdbot@users.noreply.github.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--components-extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image-reflector-controller,image-automation-controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don't need the GitHub token anymore, so you can unset the environment variable:&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;unset &lt;/span&gt;GITHUB_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few minutes, the cluster will be reconciled, you can run the following command to see the app running in the Kubernetes cluster:&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 pods &lt;span class="nt"&gt;-n&lt;/span&gt; pets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: If you see a status of &lt;strong&gt;ImagePullBackOff&lt;/strong&gt; for your &lt;strong&gt;store-front&lt;/strong&gt; pod, it may be due to package visibility. In which case, you may need to link the package registry to the repository. You can do this by following the instructions listed &lt;a href="https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Install Trivy CLI
&lt;/h2&gt;

&lt;p&gt;Next, you will need to install Trivy on your local machine. Trivy is a simple and comprehensive vulnerability scanner for containers. Head over to the &lt;a href="https://aquasecurity.github.io/trivy/v0.18.3/installation/" rel="noopener noreferrer"&gt;Trivy documentation&lt;/a&gt; to install Trivy on your local machine. I installed Trivy with 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;brew &lt;span class="nb"&gt;install &lt;/span&gt;aquasecurity/trivy/trivy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run a Trivy scan
&lt;/h2&gt;

&lt;p&gt;Now that you have Trivy installed, you can use the following command to run a Trivy scan on the container images you pushed to the GitHub Container Registry:&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;--vuln-type&lt;/span&gt; os &lt;span class="nt"&gt;--ignore-unfixed&lt;/span&gt; ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see Trivy output that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0)

┌──────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐
│ Library  │ Vulnerability  │ Severity │ Status │ Installed Version │ Fixed Version │                            Title                            │
├──────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
│ libexpat │ CVE-2023-52425 │ HIGH     │ fixed  │ 2.5.0-r0          │ 2.6.0-r0      │ expat: parsing large tokens can trigger a denial of service │
│          │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2023-52425                  │
│          ├────────────────┼──────────┤        │                   │               ├─────────────────────────────────────────────────────────────┤
│          │ CVE-2023-52426 │ MEDIUM   │        │                   │               │ expat: recursive XML entity expansion vulnerability         │
│          │                │          │        │                   │               │ https://avd.aquasec.com/nvd/cve-2023-52426                  │
└──────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's re-run the command to output the results in JSON format:&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;--vuln-type&lt;/span&gt; os &lt;span class="nt"&gt;--ignore-unfixed&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; json &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/store-front.1.2.0.json ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Copacetic CLI
&lt;/h2&gt;

&lt;p&gt;So we have an OS vulnerability in the container image. We can patch the vulnerability with Project Copacetic. Copacetic is a tool for automating the scanning, patching, deployment, and deletion of container images in a Kubernetes cluster. Head over to the &lt;a href="https://project-copacetic.github.io/copacetic/website/installation" rel="noopener noreferrer"&gt;Copacetic documentation&lt;/a&gt; to install Copacetic on your local machine. I installed Copacetic with 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;brew &lt;span class="nb"&gt;install &lt;/span&gt;copa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Patch the vulnerability with copa
&lt;/h2&gt;

&lt;p&gt;With Copacetic installed, you can use the following command to patch the vulnerability in the container image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;copa patch &lt;span class="nt"&gt;-i&lt;/span&gt; ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.0 &lt;span class="nt"&gt;-r&lt;/span&gt; /tmp/store-front.1.2.0.json &lt;span class="nt"&gt;-t&lt;/span&gt; 1.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you re-run the Trivy scan, you should see that the vulnerability has been patched:&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;--vuln-type&lt;/span&gt; os &lt;span class="nt"&gt;--ignore-unfixed&lt;/span&gt; ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you run the following command, you should see the history of the container image with the patched layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push the patched container image to the GitHub Container Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push ghcr.io/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/aks-store-demo/store-front:1.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Flux image update automation
&lt;/h2&gt;

&lt;p&gt;Now that you have patched the vulnerability in the container image, you can configure Flux to automatically update the container image when a new version is available. You can use the following commands to configure Flux to automatically update the container image:&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;# tells flux where the container image is stored&lt;/span&gt;
flux create image repository store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghcr.io/&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;/aks-store-demo/store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1m

&lt;span class="c"&gt;# tells flux how to find latest version of the container image&lt;/span&gt;
flux create image policy store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--select-semver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;gt;=1.0.0'&lt;/span&gt;

&lt;span class="c"&gt;# tells flux where to make edits based on new version of the container image&lt;/span&gt;
flux create image update store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--git-repo-ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;flux-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--git-repo-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./kustomize/overlays/dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--checkout-branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--author-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxcdbot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--author-email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxcdbot@users.noreply.github.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--commit-template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{range .Updated.Images}}{{println .}}{{end}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One last step is to "mark" the manifest so that Flux will be able to update the image in the right spot within the kustomization.yaml when a new version is available. You can use the following command to mark 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;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"s^newName: ghcr.io/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/aks-store-demo/store-front^newName: ghcr.io/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/aks-store-demo/store-front # {&lt;/span&gt;&lt;span class="se"&gt;\"\$&lt;/span&gt;&lt;span class="s2"&gt;imagepolicy&lt;/span&gt;&lt;span class="se"&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;flux-system:store-front:name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}^g"&lt;/span&gt; ./kustomize/overlays/dev/kustomization.yaml
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"s^newTag: 1.2.0^newTag: 1.2.0 # {&lt;/span&gt;&lt;span class="se"&gt;\"\$&lt;/span&gt;&lt;span class="s2"&gt;imagepolicy&lt;/span&gt;&lt;span class="se"&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;flux-system:store-front:tag&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}^g"&lt;/span&gt; ./kustomize/overlays/dev/kustomization.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit the changes to the kustomization.yaml file and push the changes to the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add ./kustomize/overlays/dev/kustomization.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: adding flux image update markers"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following commands to force a reconciliation of the Flux controllers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux reconcile image repository store-front
flux reconcile image update store-front
flux reconcile kustomization flux-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all went well, you should see that the image has been updated to version 1.2.1:&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 deploy store-front &lt;span class="nt"&gt;-n&lt;/span&gt; pets &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;image:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now you have Flux configured to automatically update the container image when a new version is available. Now we need to make sure the image is automatically patched when a vulnerability is found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatically patch vulnerabilities with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;You can use the following commands to configure Copacetic to automatically patch vulnerabilities with GitHub Actions. We'll rely on the &lt;a href="https://github.com/marketplace/actions/copacetic-action" rel="noopener noreferrer"&gt;copa-action&lt;/a&gt; that is available in the GitHub Marketplace.&lt;/p&gt;

&lt;p&gt;Create a new file called .github/workflows/patch-container-images.yaml.&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;touch&lt;/span&gt; .github/workflows/patch-container-images.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the file and add the following content:&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;patch-container-images&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;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&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;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&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;packages&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;test&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;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;apps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;store-front"&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;Authenticate with GitHub CLI&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;gh auth login --with-token &amp;lt;&amp;lt;&amp;lt; "${{ github.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;Get the latest tag&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;semver_tag&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;tag=$(gh api user/packages/container/aks-store-demo%2F${{ matrix.apps }}/versions --jq '.[0] | .metadata.container.tags[0]')&lt;/span&gt;
          &lt;span class="s"&gt;echo "tag=$tag" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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;Bump the tag&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;bump_tag&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;tag=$(echo ${{ steps.semver_tag.outputs.tag }} | awk -F. -v OFS=. '{$NF = $NF + 1;} 1')&lt;/span&gt;
          &lt;span class="s"&gt;echo "tag=$tag" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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;Run Trivy vulnerability scanner&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;aquasecurity/trivy-action@master&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;scan-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image"&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;table"&lt;/span&gt;
          &lt;span class="na"&gt;ignore-unfixed&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;vuln-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;os"&lt;/span&gt;
          &lt;span class="na"&gt;image-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/${{ github.repository }}/${{ matrix.apps }}:latest&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;Generate Trivy Report&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;aquasecurity/trivy-action@062f2592684a31eb3aa050cc61e7ca1451cecd3d&lt;/span&gt; &lt;span class="c1"&gt;# v0.18.0&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;scan-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image"&lt;/span&gt;
          &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json"&lt;/span&gt;
          &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.json"&lt;/span&gt;
          &lt;span class="na"&gt;ignore-unfixed&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;vuln-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;os"&lt;/span&gt;
          &lt;span class="na"&gt;image-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/${{ github.repository }}/${{ matrix.apps }}:latest&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;Check Vuln Count&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;vuln_count&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;report_file="report.json"&lt;/span&gt;
          &lt;span class="s"&gt;vuln_count=$(jq 'if .Results then [.Results[] | select(.Class=="os-pkgs" and .Vulnerabilities!=null) | .Vulnerabilities[]] | length else 0 end' "$report_file")&lt;/span&gt;
          &lt;span class="s"&gt;echo "vuln_count=$vuln_count" &amp;gt;&amp;gt; $GITHUB_OUTPUT&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;Copa Action&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.vuln_count.outputs.vuln_count != '0'&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;copa&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;project-copacetic/copa-action@3843e22efdca421adb37aa8dec103a0f1db68544&lt;/span&gt; &lt;span class="c1"&gt;# v1.2.1&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/${{ github.repository }}/${{ matrix.apps }}:latest&lt;/span&gt;
          &lt;span class="na"&gt;image-report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report.json"&lt;/span&gt;
          &lt;span class="na"&gt;patched-tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patched"&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 GHCR&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.copa.conclusion == 'success'&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;login&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@343f7c4344506bcbf9b4de18042ae17996df046d&lt;/span&gt; &lt;span class="c1"&gt;# v3.0.0&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;ghcr.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;${{ github.actor }}&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;${{ github.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;Push patched image&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.login.conclusion == 'success'&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;docker tag ${{ steps.copa.outputs.patched-image }} ghcr.io/${{ github.repository }}/${{ matrix.apps }}:${{ steps.bump_tag.outputs.tag }}&lt;/span&gt;
          &lt;span class="s"&gt;docker push ghcr.io/${{ github.repository }}/${{ matrix.apps }}:${{ steps.bump_tag.outputs.tag }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow will run every Tuesday (do you remember "Patch Tuesdays"? 😆) at 12:30 AM UTC and can also be triggered manually. It looks for the most recent image tag and bumps the semver which will be used then tagging the next container image. When scanning container images, it is important that copa does not patch from previously patched versions so we'll always patched from the &lt;strong&gt;latest&lt;/strong&gt; version for this demo. Copa will scan the container image for vulnerabilities and if any are found, it will patch and push the image to the GitHub Container Registry.&lt;/p&gt;

&lt;p&gt;Commit and push the changes to the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .github/workflows/patch-container-images.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"ci: add patch-container-images workflow"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the workflow&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh workflow run patch-container-images.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;View the workflow run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh run watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: If you run into an error with the workflow where the message states &lt;strong&gt;denied: permission_denied: write_package&lt;/strong&gt;, you will need to configure the &lt;strong&gt;store-front&lt;/strong&gt; package settings to allow Actions repository access. See &lt;a href="https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-a-registry-using-a-personal-access-token" rel="noopener noreferrer"&gt;here&lt;/a&gt; for additional information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A few minutes after the workflow completes, you should see that the container image has been patched and pushed to the GitHub Container Registry and the deployment has been updated in the Kubernetes cluster.&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 deploy store-front &lt;span class="nt"&gt;-n&lt;/span&gt; pets &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;image:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cleaning up cluster images with Eraser
&lt;/h2&gt;

&lt;p&gt;We're almost done. We just need to clean up the vulnerable container images from the Kubernetes nodes. If you run the following command, yo uwill see that the vulnerable container image is still present on the Kubernetes nodes:&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 nodes &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.items[].status.images[].names | last'&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;store-front
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where we install Eraser, a tool for automating the deletion of vulnerable container images from Kubernetes nodes, into the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Run the following command to install Eraser into the Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/eraser-dev/eraser/v1.3.1/deploy/eraser.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few minutes, you should see that the vulnerable container image has been deleted from the Kubernetes nodes:&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 nodes &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="s1"&gt;'.items[].status.images[].names | last'&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;store-front
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should only see the patched container image on the Kubernetes nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean up local machine
&lt;/h2&gt;

&lt;p&gt;When you are done, you can delete the Kubernetes cluster 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;kind delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; scale21x-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, you learned how to strengthen the secure supply chain with Trivy, Copacetic, Eraser, Flux, and GitHub Actions. It is easy to get lost in the sea of tools and technologies available to secure your supply chain, but with the right tools and processes in place, you can ensure that your container images are secure and up-to-date and in an automated fashion 🚀&lt;/p&gt;

&lt;p&gt;I hope you found this post helpful and that you are able to use the steps outlined here within in your own environments. If you have any questions or feedback, please feel free to leave a comment below or reach out to me on &lt;a href="https://twitter.com/pauldotyu" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/yupaul" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/aquasecurity/trivy%E2%80%8B" rel="noopener noreferrer"&gt;https://github.com/aquasecurity/trivy​&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cncf.io/projects/copa%E2%80%8B" rel="noopener noreferrer"&gt;https://www.cncf.io/projects/copa​&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cncf.io/projects/flux%E2%80%8B" rel="noopener noreferrer"&gt;https://www.cncf.io/projects/flux​&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cncf.io/projects/eraser" rel="noopener noreferrer"&gt;https://www.cncf.io/projects/eraser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cloudnative</category>
      <category>container</category>
      <category>security</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Bootstrap your GitOps-enabled AKS cluster with Terraform: A code sample using the Flux v2 K8s Extension</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Thu, 28 Sep 2023 13:00:00 +0000</pubDate>
      <link>https://forem.com/azure/bootstrap-your-gitops-enabled-aks-cluster-with-terraform-a-code-sample-using-the-flux-v2-k8s-extension-1l6d</link>
      <guid>https://forem.com/azure/bootstrap-your-gitops-enabled-aks-cluster-with-terraform-a-code-sample-using-the-flux-v2-k8s-extension-1l6d</guid>
      <description>&lt;p&gt;In my previous posts, we learned how to &lt;a href="https://aka.ms/cloudnative/GitGoingWithGitOps" rel="noopener noreferrer"&gt;get started with GitOps on AKS&lt;/a&gt; using the K8s extension for AKS.&lt;/p&gt;

&lt;p&gt;Then, we took a look at the Flux CLI and explored how it can be used to bootstrap your cluster and generate FluxCD manifests so that we can use GitOps to implement GitOps 🤯, and implemented Flux's &lt;a href="https://aka.ms/cloudnative/ImageAutomationWithFluxCD" rel="noopener noreferrer"&gt;image update automation&lt;/a&gt; capability.&lt;/p&gt;

&lt;p&gt;From there, we built on the concept of image update automation, and showed you how you can use Flagger to &lt;a href="https://aka.ms/cloudNative/ProgressiveDeliveryWithFlagger" rel="noopener noreferrer"&gt;automate canary deployments&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Much of this was done using the Flux CLI to generate Flux CRD manifests, but you might be thinking...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;"All that Flux CLI stuff is cool, but I want to bootstrap my AKS cluster to use GitOps, using the AKS extension!"&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to do just that... with my favorite IaC tool, Terraform!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in the box?
&lt;/h2&gt;

&lt;p&gt;To get started, head over to my &lt;a href="https://aka.ms/cloudnative/bootstrap-flux-v2-on-aks-with-terraform" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; and fork/clone it. This is where I have all the Terraform code you need to deploy and bootstrap your AKS cluster using a single &lt;code&gt;terraform apply&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;At a high-level, the Terraform code will do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a resource group&lt;/li&gt;
&lt;li&gt;Create an AKS cluster with the Istio service mesh add-on and external ingress gateway enabled&lt;/li&gt;
&lt;li&gt;Create a Kubernetes Secret in the &lt;code&gt;flux-system&lt;/code&gt; namespace to store GitHub credentials for private Git repository access&lt;/li&gt;
&lt;li&gt;Install the Flux v2 AKS extension with the following components enabled:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;image-automation-controller&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image-reflector-controller&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notification-controller&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Configure the Flux v2 AKS extension with the following Kustomizations:

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dev-app&lt;/code&gt; Kustomization deploys the &lt;code&gt;GitRepository&lt;/code&gt; resource which points to the &lt;code&gt;flux-k8s-ext&lt;/code&gt; branch in my &lt;a href="https://github.com/pauldotyu/aks-store-demo-manifests/tree/flux-k8s-ext" rel="noopener noreferrer"&gt;AKS Store Demo Manifests&lt;/a&gt; repo, and &lt;code&gt;Kustomization&lt;/code&gt; resource to deploy the &lt;a href="https://github.com/Azure-Samples/aks-store-demo" rel="noopener noreferrer"&gt;AKS Store Demo&lt;/a&gt; application&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dev-image&lt;/code&gt; Kustomization deploys the &lt;code&gt;ImageRepository&lt;/code&gt;, &lt;code&gt;ImagePolicy&lt;/code&gt; and &lt;code&gt;ImageUpdateAutomation&lt;/code&gt; resources to automate image updates for the &lt;code&gt;store-front&lt;/code&gt; application; this Kustomization has a dependency on the &lt;code&gt;dev-app&lt;/code&gt; Kustomization&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dev-flagger&lt;/code&gt; Kustomization deploys the &lt;code&gt;HelmRepository&lt;/code&gt;, &lt;code&gt;HelmChart&lt;/code&gt;, and &lt;code&gt;HelmRelease&lt;/code&gt; to install Flagger along with the &lt;code&gt;OCIRepository&lt;/code&gt; and &lt;code&gt;Kustomization&lt;/code&gt; resource to install the Flagger Loadtester; this Kustomization also has a dependency on the &lt;code&gt;dev-app&lt;/code&gt; Kustomization&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dev-canary-store-front&lt;/code&gt; Kustomization deploys the &lt;code&gt;Canary&lt;/code&gt; resource that is used to automate progressive delivery of the &lt;code&gt;store-front&lt;/code&gt; application; this Kustomization has a dependency on the &lt;code&gt;dev-flagger&lt;/code&gt; Kustomization&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;As an added bonus, I've also included Azure Managed Grafana and Azure Managed Prometheus in case you wanted to explore the &lt;a href="https://learn.microsoft.com/azure/azure-arc/kubernetes/monitor-gitops-flux-2" rel="noopener noreferrer"&gt;monitoring capabilities Flux on AKS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To deploy, simply follow the instructions found in the &lt;a href="https://aka.ms/cloudnative/bootstrap-flux-v2-on-aks-with-terraform" rel="noopener noreferrer"&gt;README&lt;/a&gt; of the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kustomizations, Kustomizations, Kustomizations...
&lt;/h2&gt;

&lt;p&gt;There are some differences between the way Flux CLI bootstraps your GitOps cluster and the way you can bootstrap your AKS cluster using the AKS extension.&lt;/p&gt;

&lt;p&gt;When Flux CLI bootstraps a cluster, it creates folders called &lt;code&gt;/clusters/dev&lt;/code&gt; and &lt;code&gt;/clusters/dev/flux-system&lt;/code&gt;. The &lt;code&gt;/clusters/dev/flux-system&lt;/code&gt; folder contains manifests that are used to install Flux and store &lt;code&gt;GitRepository&lt;/code&gt; source used for bootstrapping.&lt;/p&gt;

&lt;p&gt;Here is what the directory structure looked like when we bootstrapped using Flux CLI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── base
│   ├── kustomization.yaml
│   ├── makeline-service.yaml
│   ├── mongodb.yaml
│   ├── order-service.yaml
│   ├── product-service.yaml
│   ├── rabbitmq.yaml
│   ├── store-admin.yaml
│   ├── store-front.yaml
│   ├── virtual-customer.yaml
│   └── virtual-worker.yaml
├── clusters
│   └── dev
│       ├── aks-store-demo-kustomization.yaml
│       ├── aks-store-demo-source.yaml
│       ├── aks-store-demo-store-front-image-policy.yaml
│       ├── aks-store-demo-store-front-image-update.yaml
│       ├── aks-store-demo-store-front-image.yaml
│       ├── flagger-helmrelease.yaml
│       ├── flagger-loadtester-kustomization.yaml
│       ├── flagger-loadtester-source.yaml
│       ├── flagger-source.yaml
│       └── flux-system
│           ├── gotk-components.yaml
│           ├── gotk-sync.yaml
│           └── kustomization.yaml
└── overlays
    └── dev
        ├── kustomization.yaml
        └── namespace.yaml

7 directories, 25 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/clusters/dev/flux-system/gotk-sync.yaml&lt;/code&gt; file is the &lt;code&gt;Kustomization&lt;/code&gt; resource that Flux uses to configure the cluster. It tells Flux to deploy all resources found in the &lt;code&gt;/clusters/dev&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;With the AKS extension, the &lt;code&gt;gotk-*&lt;/code&gt; manifests are not saved or used from our repo, so we can discard them. However, in order to deploy the resources, we do need to make Flux aware that we want these resources deployed.&lt;/p&gt;

&lt;p&gt;The files in the &lt;code&gt;/clusters/dev&lt;/code&gt; directory needs to be split out and installed as separate Kustomizations.&lt;/p&gt;

&lt;p&gt;Here is what the directory structure ends up looking like to bootstrap a cluster using the AKS extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── README.md
├── base
│   ├── kustomization.yaml
│   ├── makeline-service.yaml
│   ├── mongodb.yaml
│   ├── order-service.yaml
│   ├── product-service.yaml
│   ├── rabbitmq.yaml
│   ├── store-admin.yaml
│   ├── store-front.yaml
│   ├── virtual-customer.yaml
│   └── virtual-worker.yaml
├── clusters
│   └── dev
│       ├── flagger
│       │   ├── flagger-helmrelease.yaml
│       │   ├── flagger-loadtester-kustomization.yaml
│       │   ├── flagger-loadtester-source.yaml
│       │   ├── flagger-source.yaml
│       │   └── kustomization.yaml
│       └── image-update
│           ├── aks-store-demo-store-front-image-policy.yaml
│           ├── aks-store-demo-store-front-image-update.yaml
│           ├── aks-store-demo-store-front-image.yaml
│           └── kustomization.yaml
└── overlays
    └── dev
        ├── canary
        │   ├── kustomization.yaml
        │   └── store-front-canary.yaml
        ├── ingressgateway.yaml
        ├── kustomization.yaml
        └── namespace.yaml

9 directories, 25 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the &lt;code&gt;image-update&lt;/code&gt; and &lt;code&gt;flagger&lt;/code&gt; components have been split into their own Kustomization directories. I also created a &lt;code&gt;canary&lt;/code&gt; Kustomization to deploy the &lt;code&gt;Canary&lt;/code&gt; resource and moved it down to the &lt;code&gt;overlays/dev&lt;/code&gt; directory since it is more specific to my app deployment.&lt;/p&gt;

&lt;p&gt;By taking this directory structure approach, we can link the Kustomizations together using the &lt;code&gt;dependsOn&lt;/code&gt; property and ensure things get installed and deployed in the right order.&lt;/p&gt;

&lt;p&gt;So in my &lt;a href="https://github.com/pauldotyu/awesome-aks/blob/9bd43dd94a2d2c4db23515e08ac1696adfd539d6/2023-09-28-bootstrap-flux-v2-on-aks/fluxcd.tf#L52" rel="noopener noreferrer"&gt;&lt;code&gt;fluxcd.tf&lt;/code&gt;&lt;/a&gt; I am first deploying the &lt;code&gt;dev-app&lt;/code&gt; Kustomization, then the &lt;code&gt;dev-image&lt;/code&gt; and &lt;code&gt;dev-flagger&lt;/code&gt; Kustomizations (which depends on &lt;code&gt;dev-app&lt;/code&gt;), and finally the &lt;code&gt;dev-canary-store-front&lt;/code&gt; Kustomization (which depends on &lt;code&gt;dev-flagger&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;When you run the Terraform, it will take roughly 15-20 minutes end-to-end, but at the end of it all, you'll have a fully functional AKS Store Demo app fully configured with image update automation and canary deployments 🥳&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using the Flux CLI to generate Flux manifests is a great way to get started with GitOps on AKS. It gives you a way to understand exactly what CRDs need to be deployed and how the manifests are written. However, if you are looking for an Azure native (and supported way) of bootstrapping your cluster, you'll need to plan for a few more Kustomizations and nest them using Flux dependencies. Just a little more planning but not much more.&lt;/p&gt;

&lt;p&gt;Now go and bootstrap your GitOps cluster, the Azure way! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue the conversation
&lt;/h2&gt;

&lt;p&gt;If you have any feedback or suggestions, please feel free to reach out to me on &lt;a href="https://twitter.com/pauldotyu" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/yupaul" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also find me in the &lt;a href="https://aka.ms/cloudnative/JoinOSSDiscord" rel="noopener noreferrer"&gt;Microsoft Open Source Discord&lt;/a&gt;, so feel free to DM me or drop a note in the &lt;a href="https://aka.ms/cloudnative/DiscordChannel" rel="noopener noreferrer"&gt;&lt;em&gt;cloud-native&lt;/em&gt; channel&lt;/a&gt; where my team hangs out!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aka.ms/cloudnative/JoinOSSDiscord" rel="noopener noreferrer"&gt;Join the Microsoft Open Source Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aka.ms/cloudnative/DiscordChannel" rel="noopener noreferrer"&gt;Meet us in the Cloud Native Channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>kubernetes</category>
      <category>terraform</category>
      <category>flux</category>
    </item>
    <item>
      <title>Progressive Delivery on AKS: A Step-by-Step Guide using Flagger with Istio and FluxCD</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Tue, 26 Sep 2023 13:00:00 +0000</pubDate>
      <link>https://forem.com/azure/progressive-delivery-on-aks-a-step-by-step-guide-using-flagger-with-istio-and-fluxcd-1l70</link>
      <guid>https://forem.com/azure/progressive-delivery-on-aks-a-step-by-step-guide-using-flagger-with-istio-and-fluxcd-1l70</guid>
      <description>&lt;p&gt;In my previous &lt;a href="https://aka.ms/cloudnative/ImageAutomationWithFluxCD" rel="noopener noreferrer"&gt;post&lt;/a&gt;, we setup an Azure Kubernetes Service (AKS) cluster to automatically update images based on new image tags in a container registry. As soon as a new image was pushed to the registry the image was immediately updated.&lt;/p&gt;

&lt;p&gt;But what if you don't want an agent automatically pushing out new images without some sort of testing? 🤔&lt;/p&gt;

&lt;p&gt;In this article, we'll build upon Flux's image update automation capability and add &lt;a href="https://flagger.app/" rel="noopener noreferrer"&gt;Flagger&lt;/a&gt; to implement a &lt;a href="https://docs.flagger.app/usage/deployment-strategies#canary-release" rel="noopener noreferrer"&gt;canary release strategy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Flagger is a progressive delivery tool that enables a Kubernetes operator to automate the promotion or rollback of deployments based on metrics analysis. It supports a variety of metrics including Prometheus, Datadog, and New Relic to name a few. It also works well with &lt;a href="https://istio.io/" rel="noopener noreferrer"&gt;Istio&lt;/a&gt; service mesh, and can implement progressive traffic splitting between primary and canary releases.&lt;/p&gt;

&lt;p&gt;The goal here is to harness the power of image update automation while implementing some sort of gating process around it.&lt;/p&gt;

&lt;p&gt;Here is the intended workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modify application code, then commit and push the change to the repo.&lt;/li&gt;
&lt;li&gt;Create a new release in GitHub which kicks off a release workflow to build and push an updated container image to a GitHub Container Registry.&lt;/li&gt;
&lt;li&gt;FluxCD detects the new image and updates the image tag in a YAML manifest.&lt;/li&gt;
&lt;li&gt;FluxCD rolls out the new image to the cluster.&lt;/li&gt;
&lt;li&gt;Flagger detects a new deployment revision and starts a canary deployment.&lt;/li&gt;
&lt;li&gt;Flagger progressively routes traffic to the new deployment based on metrics.&lt;/li&gt;
&lt;li&gt;Flagger promotes the new deployment to production if the metrics are within the threshold.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll move really fast through the AKS cluster provisioning, bootstrapping process, and deploying the &lt;a href="https://github.com/azure-samples/aks-store-demo" rel="noopener noreferrer"&gt;AKS Store Demo&lt;/a&gt; sample app.&lt;/p&gt;

&lt;p&gt;If you want a closer look at how image update automation is configured using FluxCD, check out my &lt;a href="https://aka.ms/cloudnative/ImageAutomationWithFluxCD" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's go!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, you need to have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/get-started/" rel="noopener noreferrer"&gt;Azure Subscription&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cli/cli#installation" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluxcd.io/flux/installation/" rel="noopener noreferrer"&gt;Flux CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create an AKS cluster and bootstrap FluxCD
&lt;/h2&gt;

&lt;p&gt;Run the following command to log into Azure and make sure you have the &lt;code&gt;AzureServiceMeshPreview&lt;/code&gt; feature enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login
az feature register &lt;span class="nt"&gt;--namespace&lt;/span&gt; &lt;span class="s2"&gt;"Microsoft.ContainerService"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"AzureServiceMeshPreview"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next run the following command to setup some variables for your deployment.&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="nv"&gt;RG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rg-flagger
&lt;span class="nv"&gt;AKS_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-flagger
&lt;span class="nv"&gt;LOC_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;westus2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll deploy an AKS cluster with the &lt;a href="https://learn.microsoft.com/azure/aks/istio-about" rel="noopener noreferrer"&gt;Istio service mesh add-on&lt;/a&gt; enabled. If you are unfamiliar with service mesh in general, check out the &lt;a href="https://istio.io/latest/docs/concepts/what-is-istio/" rel="noopener noreferrer"&gt;Istio documentation&lt;/a&gt; and my &lt;a href="https://dev.to/azure/service-mesh-considerations-2p4b"&gt;previous post on Service Mesh Considerations&lt;/a&gt; for more information.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you still have the AKS cluster from the previous post, you might want to delete it and start fresh.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following commands to create the resource group and AKS cluster with the Istio add-on enabled.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az group create &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;$LOC_NAME&lt;/span&gt;
az aks create &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="nt"&gt;--enable-azure-service-mesh&lt;/span&gt; &lt;span class="nt"&gt;--generate-ssh-keys&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; Standard_B4s_v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Istio offers internal and external ingress capabilities which controls traffic coming into the cluster. We'll use the external ingress as our entry point for the sample app.&lt;/p&gt;

&lt;p&gt;Run the following command to enable the external ingress gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks mesh enable-ingress-gateway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ingress-gateway-type&lt;/span&gt; external
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the cluster and Istio external ingress gateway are deployed, run the following command to connect to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks get-credentials &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's move on to bootstrapping FluxCD and deploying the AKS Store Demo app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap cluster using FluxCD
&lt;/h2&gt;

&lt;p&gt;We'll use the GitHub CLI to work with GitHub and Flux CLI generate new Flux manifests so be sure you have these tools installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect to GitHub using the GitHub CLI
&lt;/h3&gt;

&lt;p&gt;Run the following command to log into GitHub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh auth login &lt;span class="nt"&gt;--scopes&lt;/span&gt; repo,workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fork and clone the AKS Store Demo repo
&lt;/h3&gt;

&lt;p&gt;If you're continuing from the previous post, you should already have the AKS Store Demo repo forked and cloned. If not, run the following commands to fork and clone the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh repo fork https://github.com/azure-samples/aks-store-demo.git &lt;span class="nt"&gt;--clone&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;aks-store-demo
gh repo set-default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a release workflow
&lt;/h3&gt;

&lt;p&gt;If you're continuing from the previous post, you should already have a release workflow in the AKS Store Demo repo. If not, make sure you are in the root of the &lt;code&gt;aks-store-demo&lt;/code&gt; repository and run the following commands to create a release workflow.&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;# download the releae workflow&lt;/span&gt;
wget &lt;span class="nt"&gt;-O&lt;/span&gt; .github/workflows/release-store-front.yaml https://raw.githubusercontent.com/pauldotyu/aks-store-demo/main/.github/workflows/release-store-front.yaml

&lt;span class="c"&gt;# download the TopNav.vue file which we'll be modifying&lt;/span&gt;
wget &lt;span class="nt"&gt;-O&lt;/span&gt; src/store-front/src/components/TopNav.vue https://raw.githubusercontent.com/pauldotyu/aks-store-demo/main/src/store-front/src/components/TopNav.vue

&lt;span class="c"&gt;# commit and push&lt;/span&gt;
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add release workflow"&lt;/span&gt;
git push

&lt;span class="c"&gt;# back out to the previous directory&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fork and clone the AKS Store Demo Manifests repo
&lt;/h3&gt;

&lt;p&gt;The great thing about Flux is that it can be bootstrapped using GitOps. So we'll point to a branch in my &lt;a href="https://github.com/pauldotyu/aks-store-demo-manifests" rel="noopener noreferrer"&gt;AKS Store Demo Manifests&lt;/a&gt; repo which has everything we need to get the cluster setup quickly.&lt;/p&gt;

&lt;p&gt;If you're continuing from the previous post, you should already have the AKS Store Demo Manifests repo forked and cloned. If not, run the following commands to fork and clone it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh repo fork https://github.com/pauldotyu/aks-store-demo-manifests.git &lt;span class="nt"&gt;--clone&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;aks-store-demo-manifests
gh repo set-default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have updated manifests to include Istio resources in the &lt;code&gt;istio&lt;/code&gt; branch. We'll use this branch to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch
git checkout &lt;span class="nt"&gt;--track&lt;/span&gt; origin/istio

&lt;span class="c"&gt;# get the latest from upstream&lt;/span&gt;
git fetch upstream istio
git rebase upstream/istio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Secrets for FluxCD Image Update Automation
&lt;/h3&gt;

&lt;p&gt;As mentioned in my &lt;a href="https://aka.ms/cloudnative/ImageAutomationWithFluxCD" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, we'll need to create a Flux secret to allow Flux to write to our GitHub repo.&lt;/p&gt;

&lt;p&gt;Run the following command to create a namespace to land the Kubernetes secret into.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace flux-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following commands set your GitHub info.&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;# make sure you are in the aks-store-demo-manifests repo&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh api user &lt;span class="nt"&gt;--jq&lt;/span&gt; .login&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh auth token&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REPO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh repo view &lt;span class="nt"&gt;--json&lt;/span&gt; url | jq .url &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to create the secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create secret git aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_REPO_URL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;GitRepository&lt;/code&gt; URL in a couple of Flux manifests to point to your repo.&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;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/pauldotyu/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/g"&lt;/span&gt; clusters/dev/flux-system/gotk-sync.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tmp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;tmp clusters/dev/flux-system/gotk-sync.yaml
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/pauldotyu/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/g"&lt;/span&gt; clusters/dev/aks-store-demo-source.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tmp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;tmp clusters/dev/aks-store-demo-source.yaml
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/pauldotyu/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/g"&lt;/span&gt; clusters/dev/aks-store-demo-store-front-image.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tmp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mv &lt;/span&gt;tmp clusters/dev/aks-store-demo-store-front-image.yaml

git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'feat: update git sync url'&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now bootstrap our cluster with FluxCD using the manifests in the &lt;code&gt;istio&lt;/code&gt; branch of the AKS Store Demo Manifests repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux bootstrap github create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo-manifests &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--personal&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./clusters/dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;istio &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reconcile&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network-policy&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--components-extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image-reflector-controller,image-automation-controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a minute or two, run the following command to watch the bootstrap process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux logs &lt;span class="nt"&gt;--kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Kustomization &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;span class="c"&gt;# press ctrl-c to exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the Kustomization reconciliation process is complete, run the following command to retrieve the public IP address of the Istio ingress gateway.&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;"http://&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; aks-istio-ingress aks-istio-ingressgateway-external &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].ip}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the AKS Store Demo app running in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Flagger
&lt;/h2&gt;

&lt;p&gt;Time to install Flagger, the GitOps way!&lt;/p&gt;

&lt;p&gt;We'll use the Flux CLI to generate the Flagger manifests and commit them to our repo.&lt;/p&gt;

&lt;p&gt;Run the following command to generate a &lt;code&gt;HelmRepository&lt;/code&gt; for Flagger's Helm chart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create &lt;span class="nb"&gt;source &lt;/span&gt;helm flagger &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oci://ghcr.io/fluxcd/charts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/flagger-source.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to create a &lt;code&gt;values.yaml&lt;/code&gt; file which will be used to configure Flagger.&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; values.yaml
meshProvider: istio
prometheus:
  install: true
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we are telling Flagger to use Istio as the service mesh provider and to install Prometheus to collect metrics.&lt;/p&gt;

&lt;p&gt;Next we need to create a &lt;code&gt;HelmRelease&lt;/code&gt; resource to install Flagger and pass in the &lt;code&gt;values.yaml&lt;/code&gt; file we just created to configure Flagger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create helmrelease flagger &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;flagger-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--create-target-namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--crds&lt;/span&gt; CreateReplace &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;HelmRepository/flagger &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--chart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;flagger &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;values.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/flagger-helmrelease.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need the &lt;code&gt;values.yaml&lt;/code&gt; file anymore, so run the following command to delete it.&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;rm &lt;/span&gt;values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flagger can also run load tests against your application to generate metrics. We'll use its &lt;a href="https://docs.flagger.app/usage/webhooks#load-testing" rel="noopener noreferrer"&gt;load testing service&lt;/a&gt; to generate load against our application.&lt;/p&gt;

&lt;p&gt;Flagger's load testing service can be installed via a &lt;code&gt;Kustomization&lt;/code&gt; resource based on manifests packaged as an artifact in an &lt;a href="https://opencontainers.org/" rel="noopener noreferrer"&gt;Open Container Initiative (OCI)&lt;/a&gt; registry&lt;/p&gt;

&lt;p&gt;Run the following command to create an &lt;code&gt;OCIRepository&lt;/code&gt; pointing to an OCI registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create &lt;span class="nb"&gt;source &lt;/span&gt;oci flagger-loadtester &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oci://ghcr.io/fluxcd/flagger-manifests &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tag-semver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.x &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/flagger-loadtester-source.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to create a &lt;code&gt;Kustomization&lt;/code&gt; resource for the installation manifests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create kustomization flagger-loadtester &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prune&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6h &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./tester &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OCIRepository/flagger-loadtester &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/flagger-loadtester-kustomization.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're ready to commit our changes to our repo.&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;# pull the latest changes from the repo&lt;/span&gt;
git pull

&lt;span class="c"&gt;# add the new files and commit the changes&lt;/span&gt;
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'feat: add flagger'&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will trigger a FluxCD reconciliation and install Flagger in our cluster.&lt;/p&gt;

&lt;p&gt;After a minute or two, run any of the following commands to see the status of the new resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get &lt;span class="nb"&gt;source &lt;/span&gt;helm
flux get &lt;span class="nb"&gt;source &lt;/span&gt;chart
flux get &lt;span class="nb"&gt;source &lt;/span&gt;oci
flux get helmrelease
flux get kustomization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm that Flagger is installed and running.&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 deploy &lt;span class="nt"&gt;-n&lt;/span&gt; flagger-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy a Canary
&lt;/h2&gt;

&lt;p&gt;With Flagger installed, a Canary Custom Resource Definition (CRD) is available to us.&lt;/p&gt;

&lt;p&gt;A Canary resource will automate some of our Kubernetes resources. It will create a Service, VirtualService, and a Canary deployment for us. So we don't need to create these resources ourselves.&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;./base/store-front.yaml&lt;/code&gt; manifest file using your favorite editor and remove the &lt;code&gt;Service&lt;/code&gt; resource.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Service&lt;/code&gt; resource looks like this.&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="nn"&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;v1&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;Service&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;store-front&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;ports&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;http&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&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;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store-front&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next remove the &lt;code&gt;VirtualService&lt;/code&gt; resource.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;VirtualService&lt;/code&gt; resource looks like this.&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="nn"&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;networking.istio.io/v1alpha3&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;VirtualService&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;store-front&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;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&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;gateways&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store-front&lt;/span&gt;
  &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;store-front&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add the following &lt;code&gt;Canary&lt;/code&gt; resource to the end of the manifest file.&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="nn"&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;flagger.app/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;Canary&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;store-front&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;targetRef&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;apps/v1&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;Deployment&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;store-front&lt;/span&gt;
  &lt;span class="na"&gt;progressDeadlineSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt; &lt;span class="c1"&gt;# make sure this matches container port&lt;/span&gt;
    &lt;span class="na"&gt;portDiscovery&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;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&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;gateways&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;store-front&lt;/span&gt;
  &lt;span class="na"&gt;analysis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1m&lt;/span&gt;
    &lt;span class="na"&gt;threshold&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;maxWeight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
    &lt;span class="na"&gt;stepWeight&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;metrics&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;request-success-rate&lt;/span&gt;
        &lt;span class="na"&gt;thresholdRange&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;99&lt;/span&gt;
        &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1m&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;request-duration&lt;/span&gt;
        &lt;span class="na"&gt;thresholdRange&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;
        &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&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;error-rate&lt;/span&gt;
        &lt;span class="na"&gt;thresholdRange&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;max&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;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;
    &lt;span class="na"&gt;webhooks&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;acceptance-test&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pre-rollout&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://flagger-loadtester.dev/&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
          &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;curl&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-s&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://store-front-canary.dev"&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;load-test&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://flagger-loadtester.dev/&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&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;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hey&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-z&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1m&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-q&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-c&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;http://store-front-canary.dev"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's a lot going on here, but essentially we are telling Flagger to create a Canary deployment for our &lt;code&gt;store-front&lt;/code&gt; app. We are also telling Flagger to use the Istio ingress gateway to route traffic to our app and to use the load testing service to generate load against our app to generate metrics and analyze them to determine if the canary deployment should be promoted to production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note the &lt;code&gt;threshold&lt;/code&gt;, &lt;code&gt;maxWeight&lt;/code&gt;, and &lt;code&gt;stepWeight&lt;/code&gt; values in the Canary manifest. We are going to start with 10% traffic directed to the canary and increase by 10%. Once the test is successful at 20% traffic, the Canary will promote the canary to primary and receive 100% of traffic. 20% is a low number here an intentionally set to speed up the testing process. Normally you would set this number closer to 100.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Commit and push the changes to your repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add ./base/store-front.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'feat: add store-front canary'&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get Flux to reconcile the changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux reconcile kustomization aks-store-demo &lt;span class="nt"&gt;--with-source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Flux has finished reconciling, wait a minute or two then run the following command to watch the canary deployment and wait until the status is &lt;code&gt;Initialized&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;kubectl get canary &lt;span class="nt"&gt;-n&lt;/span&gt; dev store-front &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;span class="c"&gt;# press ctrl-c to exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view Flagger logs with 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;kubectl logs &lt;span class="nt"&gt;-n&lt;/span&gt; flagger-system deployment/flagger-system-flagger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to view important resources.&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 service,destinationrule,virtualservice &lt;span class="nt"&gt;-n&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flagger has created two &lt;code&gt;Service&lt;/code&gt; resources (one for canary and one for primary), two &lt;code&gt;DestinationRule&lt;/code&gt; resources (one to route traffic to canary and one to route to primary) and one &lt;code&gt;VirtualService&lt;/code&gt; resource that can be configured for traffic shifting between the two services. Right now, the primary service is weighted to receive 100% of the traffic and you can confirm this 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;kubectl get virtualservice &lt;span class="nt"&gt;-n&lt;/span&gt; dev store-front &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all this in place, ensure you can still access the AKS Store Demo app in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Canary
&lt;/h2&gt;

&lt;p&gt;Now we can test the progressive deployment of our canary.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Flip back to the &lt;code&gt;aks-store-demo&lt;/code&gt; repo so that we can make another change to the &lt;code&gt;TopNav.vue&lt;/code&gt; file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following commands that will update the version number in the &lt;code&gt;TopNav.vue&lt;/code&gt; file from &lt;code&gt;1.0.0&lt;/code&gt; to &lt;code&gt;2.0.0&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;&lt;span class="c"&gt;# set the version number&lt;/span&gt;
&lt;span class="nv"&gt;PREVIOUS_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.0.0
&lt;span class="nv"&gt;CURRENT_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.0.0

&lt;span class="c"&gt;# make sure you are in the aks-store-demo directory&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/Azure Pet Supplies v&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PREVIOUS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/Azure Pet Supplies v&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CURRENT_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/g"&lt;/span&gt; src/store-front/src/components/TopNav.vue &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; TempTopNav.vue
&lt;span class="nb"&gt;mv &lt;/span&gt;TempTopNav.vue src/store-front/src/components/TopNav.vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push the changes to your repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: update title again"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new release in GitHub and watch the magic happen!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh release create &lt;span class="nv"&gt;$CURRENT_VERSION&lt;/span&gt; &lt;span class="nt"&gt;--generate-notes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait a few seconds then run the following command to watch the release build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh run watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the new image built, the Flux &lt;code&gt;ImagePolicy&lt;/code&gt; resource will reconcile, detect a new image tag and trigger the &lt;code&gt;ImageUpdateAutomation&lt;/code&gt; resource reconciliation process.&lt;/p&gt;

&lt;p&gt;The new image tag will be written to the &lt;code&gt;kustomization.yaml&lt;/code&gt; manifest and the sample app's &lt;code&gt;Kustomization&lt;/code&gt; resource will reconcile and update the its &lt;code&gt;Deployment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is where Flagger picks up the baton. Flagger will detect a new deployment revision and trigger a canary deployment. It will progressively route traffic to the new deployment based on the metrics we defined in the &lt;code&gt;Canary&lt;/code&gt; resource. If the metrics are within the threshold, Flagger will promote the new deployment as the primary.&lt;/p&gt;

&lt;p&gt;You can run the following commands to watch the image update process.&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;# watch image policy&lt;/span&gt;
flux logs &lt;span class="nt"&gt;--kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ImagePolicy &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;store-front &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# watch kustomization&lt;/span&gt;
flux logs &lt;span class="nt"&gt;--kind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Kustomization &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# confirm the image tag was updated&lt;/span&gt;
kubectl get deploy &lt;span class="nt"&gt;-n&lt;/span&gt; dev store-front &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;image:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then run the following command to watch the canary deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl logs &lt;span class="nt"&gt;-n&lt;/span&gt; flagger-system deployment/flagger-system-flagger &lt;span class="nt"&gt;-f&lt;/span&gt; | jq .msg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By the end of the Canary deployment process, you should see the following messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"New revision detected! Scaling up store-front.dev"
"Starting canary analysis for store-front.dev"
"Pre-rollout check acceptance-test passed"
"Advance store-front.dev canary weight 10"
"Advance store-front.dev canary weight 20"
"Copying store-front.dev template spec to store-front-primary.dev"
"Routing all traffic to primary"
"Promotion completed! Scaling down store-front.dev"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can refresh the AKS Store Demo app in your browser and see the new version of the app 🥳&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Image update automation is cool, but it's even cooler when you can implement some sort of gating process around it. Flagger is a great tool to help you implement progressive delivery strategies in your Kubernetes cluster. It works well with Istio and can be configured to use a variety of metrics providers and if it detects an issue, it will rollback the deployment. It's a great tool to have in your GitOps tool belt and will help you automate your deployments with confidence.&lt;/p&gt;

&lt;p&gt;We've covered a lot of ground when it comes to GitOps and AKS but we have only scratched the surface. So stay tuned for more GitOps goodness!&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue the conversation
&lt;/h2&gt;

&lt;p&gt;If you have any feedback or suggestions, please feel free to reach out to me on &lt;a href="https://twitter.com/pauldotyu" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/yupaul" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also find me in the &lt;a href="https://aka.ms/cloudnative/JoinOSSDiscord" rel="noopener noreferrer"&gt;Microsoft Open Source Discord&lt;/a&gt;, so feel free to DM me or drop a note in the &lt;a href="https://aka.ms/cloudnative/DiscordChannel" rel="noopener noreferrer"&gt;&lt;em&gt;cloud-native&lt;/em&gt; channel&lt;/a&gt; where my team hangs out!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aka.ms/cloudnative/JoinOSSDiscord" rel="noopener noreferrer"&gt;Join the Microsoft Open Source Discord&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aka.ms/cloudnative/DiscordChannel" rel="noopener noreferrer"&gt;Meet us in the Cloud Native Channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.flagger.app/" rel="noopener noreferrer"&gt;Flagger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluxcd.io/flagger/install/flagger-install-with-flux/" rel="noopener noreferrer"&gt;Flagger installation on Kubernetes with Flux)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flagger.app/usage/deployment-strategies" rel="noopener noreferrer"&gt;Deployment strategies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.flagger.app/usage/webhooks#load-testing" rel="noopener noreferrer"&gt;Flagger load testing service&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>fluxcd</category>
      <category>flagger</category>
    </item>
    <item>
      <title>Automating Image Updates on AKS: A Step-by-Step Guide using open source FluxCD</title>
      <dc:creator>Paul Yu</dc:creator>
      <pubDate>Fri, 22 Sep 2023 13:00:00 +0000</pubDate>
      <link>https://forem.com/azure/automating-image-updates-with-fluxcd-on-aks-3i2d</link>
      <guid>https://forem.com/azure/automating-image-updates-with-fluxcd-on-aks-3i2d</guid>
      <description>&lt;p&gt;In my previous &lt;a href="https://aka.ms/cloudnative/GitGoingWithGitOps" rel="noopener noreferrer"&gt;post&lt;/a&gt;, we walked through the setup of FluxCD on AKS via AKS extensions. In this article, we'll go a bit deeper and take a look at how you can use FluxCD to automate image updates in your AKS cluster.&lt;/p&gt;

&lt;p&gt;The goal here is to streamline the process of updating your application deployments in your cluster.&lt;/p&gt;

&lt;p&gt;Here is our intended workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modify application code, then commit and push the change to the repo.&lt;/li&gt;
&lt;li&gt;Create a new release in GitHub which kicks off a release workflow to build and push an updated container image to a GitHub Container Registry.&lt;/li&gt;
&lt;li&gt;FluxCD detects the new image and updates the image tag in the cluster.&lt;/li&gt;
&lt;li&gt;FluxCD rolls out the new image to the cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We'll use same AKS store demo app we used in the previous post, but this time we'll go a bit faster.&lt;/p&gt;

&lt;p&gt;If you need a refresher on how to deploy the application, you can refer to the &lt;a href="https://aka.ms/cloudnative/GitGoingWithGitOps" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, you need to have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/get-started/" rel="noopener noreferrer"&gt;Azure Subscription&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest" rel="noopener noreferrer"&gt;Azure CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cli/cli#installation" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluxcd.io/flux/installation/" rel="noopener noreferrer"&gt;Flux CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create an AKS cluster
&lt;/h2&gt;

&lt;p&gt;Use the following Azure CLI commands to create a new resource group and AKS cluster.&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="nv"&gt;RG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rg-imageupdate
&lt;span class="nv"&gt;AKS_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-imageupdate
&lt;span class="nv"&gt;LOC_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;westus3

az group create &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;$LOC_NAME&lt;/span&gt;
az aks create &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the cluster is created, run the following command to connect to the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az aks get-credentials &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$AKS_NAME&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RG_NAME&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bootstrapping FluxCD
&lt;/h2&gt;

&lt;p&gt;In my previous post, I used the AKS extension to install FluxCD. This time, I'll be using the Flux CLI to bootstrap FluxCD onto the AKS cluster. This process will enable us to have a bit more control over the installation and gives us the ability to save the Flux resources to our GitHub repo.&lt;/p&gt;

&lt;p&gt;So we're going to implement GitOps even on top of our GitOps tooling 🤯&lt;/p&gt;

&lt;p&gt;There's a few things we need to do before we can bootstrap the cluster. First, we need the &lt;a href="https://fluxcd.io/flux/installation/" rel="noopener noreferrer"&gt;Flux CLI&lt;/a&gt;. Once you have the CLI installed, run the following command to ensure your cluster is ready for bootstrapping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux check &lt;span class="nt"&gt;--pre&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need a repo to land the Flux resources into. So let's fork and clone the &lt;a href="https://github.com/pauldotyu/aks-store-demo-manifests" rel="noopener noreferrer"&gt;aks-store-demo-manifests&lt;/a&gt; repository. This with be our repo for both application deployment and our GitOps configurations.&lt;/p&gt;

&lt;p&gt;We'll be doing a bunch of things within GitHub. I prefer using the &lt;a href="https://github.com/cli/cli#installation" rel="noopener noreferrer"&gt;GitHub CLI&lt;/a&gt; over clicking around in the GitHub website.&lt;/p&gt;

&lt;p&gt;Run the following commands to login to GitHub, clone the repo and prep it for our exercise.&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;# navigate to the home directory or wherever you want to clone the repo&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~

&lt;span class="c"&gt;# login to GitHub&lt;/span&gt;
gh auth login &lt;span class="nt"&gt;--scopes&lt;/span&gt; repo,workflow

&lt;span class="c"&gt;# fork and clone the repo to your local machine&lt;/span&gt;
gh repo fork https://github.com/pauldotyu/aks-store-demo-manifests.git &lt;span class="nt"&gt;--clone&lt;/span&gt;

&lt;span class="c"&gt;# make sure you are in the aks-store-demo-manifests directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;aks-store-demo-manifests

&lt;span class="c"&gt;# since we are in a forked repo, we need to set the default to be our fork&lt;/span&gt;
gh repo set-default

&lt;span class="c"&gt;# merge the "kustomize" branch into "main"&lt;/span&gt;
git merge origin/kustomize

&lt;span class="c"&gt;# push the change up to your fork&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to set some environment variables to for the Flux bootstrapping process. Run the following commands to set the environment variables.&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;# set the repo url&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REPO_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh repo view &lt;span class="nt"&gt;--json&lt;/span&gt; url | jq .url &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# set your GitHub username&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh api user &lt;span class="nt"&gt;--jq&lt;/span&gt; .login&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# set your GitHub personal access token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gh auth token&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to bootstrap FluxCD. Run the following command to bootstrap FluxCD. This command will install FluxCD with additional components to enable image automation. It will also generate the Flux resources and commit them to our Git repo in the &lt;code&gt;clusters/dev&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux bootstrap github create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo-manifests &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--personal&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./clusters/dev &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reconcile&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network-policy&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--components-extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;image-reflector-controller,image-automation-controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few minutes, run the following commands and you should see the Flux resources in the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch
git rebase

&lt;span class="c"&gt;# show the Flux resources&lt;/span&gt;
tree clusters/dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order for the &lt;code&gt;image-automation-controller&lt;/code&gt; to write commits to our repo, we need to create a Kubernetes secret to store our GitHub credentials. Run the following command to create the secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create secret git aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_REPO_URL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;This command will create Kubernetes secrets in your cluster but you could also use Azure Key Vault with the Secret Store CSI driver to store your GitHub credentials.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We don't need the GitHub PAT token anymore, so run the following command to discard it.&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;unset &lt;/span&gt;GITHUB_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to create a &lt;code&gt;GitRepository&lt;/code&gt; resource. This resource will point to our fork of the &lt;a href="https://github.com/pauldotyu/aks-store-demo-manifests" rel="noopener noreferrer"&gt;&lt;code&gt;aks-store-demo-manifests&lt;/code&gt;&lt;/a&gt; repo where we have our app deployment and GitOps manifests stored.&lt;/p&gt;

&lt;p&gt;Run the following command to create the configuration and export it to a YAML file which we'll commit to our repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create &lt;span class="nb"&gt;source &lt;/span&gt;git aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_REPO_URL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/aks-store-demo-source.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to specify the &lt;code&gt;Kustomization&lt;/code&gt; resource to tell FluxCD where to find the app deployment manifests in our repo. Run the following command to create the configuration and export it to a YAML file which we'll also commit to our repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create kustomization aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./overlays/dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prune&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retry-interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-check-timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/aks-store-demo-kustomization.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have two new Flux resource manifests. Run the following command to commit and push the files to the repo so Flux can begin the reconciliation process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add source and kustomization"&lt;/span&gt; 
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As soon as the code is pushed, Flux will do it's thing; reconcile. Run the following command to watch the &lt;code&gt;Kustomization&lt;/code&gt; reconciliation process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get kustomizations &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few minutes you should see something like this...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aks-store-demo  main@sha1:95de7bde  False   True    Applied revision: main@sha1:95de7bde
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm the &lt;code&gt;GitRepository&lt;/code&gt; and &lt;code&gt;Kustomization&lt;/code&gt; resources have been created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get sources git 
flux get kustomizations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to ensure all the Pods are running.&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 po &lt;span class="nt"&gt;-n&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you see all the Pods are running, run the following command to grab the public IP of the &lt;code&gt;store-front&lt;/code&gt; service.&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 svc store-front &lt;span class="nt"&gt;-n&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a web browser, navigate to the public IP and confirm the application is up and running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup the release workflow
&lt;/h2&gt;

&lt;p&gt;The source code for the &lt;code&gt;aks-store-demo&lt;/code&gt; application is located in a different repository than where our manifests are.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To give you a bit of a warning, we'll be flipping back and forth between the &lt;code&gt;aks-store-demo&lt;/code&gt; and &lt;code&gt;aks-store-demo-manifests&lt;/code&gt; repository directories.&lt;/p&gt;

&lt;p&gt;Hint: you can use the &lt;code&gt;cd -&lt;/code&gt; command to flip back and forth between the last two directories you were in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The application code is in the &lt;a href="https://github.com/azure-samples/aks-store-demo" rel="noopener noreferrer"&gt;aks-store-demo&lt;/a&gt; repository. Run the following commands to fork and clone the repo.&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;# navigate to the home directory or wherever you want to clone the repo&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; -

&lt;span class="c"&gt;# fork and clone the repo to your local machine&lt;/span&gt;
gh repo fork https://github.com/azure-samples/aks-store-demo.git &lt;span class="nt"&gt;--clone&lt;/span&gt;

&lt;span class="c"&gt;# make sure you are in the aks-store-demo directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;aks-store-demo

&lt;span class="c"&gt;# since we are in a forked repo, we need to set the default to be our fork&lt;/span&gt;
gh repo set-default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Currently the &lt;code&gt;aks-store-demo&lt;/code&gt; repository has Continuous Integration (CI) workflows to build and push container images using &lt;code&gt;GITHUB_SHA&lt;/code&gt; as the image tag. We need to create a new release workflow that will build and push a new container image using &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;semantic versioning&lt;/a&gt;. Flux's image update automation policy which is used to determine the most recent image tag can use various methods including numerical tags, alphabetical tags, and semver tags. So in this case, as much as I'd like to use the &lt;code&gt;GITHUB_SHA&lt;/code&gt;, it does not provide a conducive way to determine "latest".&lt;/p&gt;

&lt;p&gt;Therefore, we need to tag our releases with semantic version numbers. GitHub offers a way to &lt;a href="https://docs.github.com/repositories/releasing-projects-on-github/about-releases" rel="noopener noreferrer"&gt;create tagged releases&lt;/a&gt;. Additionaly, GitHub Actions have the ability to &lt;a href="https://docs.github.com/actions/using-workflows/events-that-trigger-workflows#release" rel="noopener noreferrer"&gt;trigger workflows when a new release is published&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run the following command to create a new workflow file.&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;# make sure you are in the aks-store-demo directory&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; .github/workflows/release-store-front.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the &lt;code&gt;release-store-front.yaml&lt;/code&gt; file using your favorite editor and paste the following code.&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;release-store-front&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;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&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;packages&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;publish-container-image&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;Set environment variables&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;set-variables&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;echo "REPOSITORY=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
          &lt;span class="s"&gt;echo "IMAGE=store-front" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
          &lt;span class="s"&gt;echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&lt;/span&gt;
          &lt;span class="s"&gt;echo "CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" &amp;gt;&amp;gt; "$GITHUB_OUTPUT"&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;Env variable output&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;test-variables&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;echo ${{ steps.set-variables.outputs.REPOSITORY }}&lt;/span&gt;
          &lt;span class="s"&gt;echo ${{ steps.set-variables.outputs.IMAGE }}&lt;/span&gt;
          &lt;span class="s"&gt;echo ${{ steps.set-variables.outputs.VERSION }}&lt;/span&gt;
          &lt;span class="s"&gt;echo ${{ steps.set-variables.outputs.CREATED }}&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 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@v2&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;Set up Docker Buildx&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/setup-buildx-action@v2&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 GitHub Container Registry&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@v1&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;ghcr.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;${{ github.actor }}&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;${{ github.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&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@v2&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;src/store-front&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;src/store-front/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="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;${{ steps.set-variables.outputs.REPOSITORY }}/${{ steps.set-variables.outputs.IMAGE }}:latest&lt;/span&gt;
            &lt;span class="s"&gt;${{ steps.set-variables.outputs.REPOSITORY }}/${{ steps.set-variables.outputs.IMAGE }}:${{ steps.set-variables.outputs.VERSION }}&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;org.opencontainers.image.source=${{ github.repositoryUrl }}&lt;/span&gt;
            &lt;span class="s"&gt;org.opencontainers.image.created=${{ steps.set-variables.outputs.CREATED }}&lt;/span&gt;
            &lt;span class="s"&gt;org.opencontainers.image.revision=${{ steps.set-variables.outputs.VERSION }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow will be triggered each time a new release is published and the &lt;code&gt;${GITHUB_REF#refs/tags/}&lt;/code&gt; bit allows us to extract the release version to use as an image tag.&lt;/p&gt;

&lt;p&gt;Save the file, then commit and push the changes to the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add .github/workflows/release-store-front.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add release workflow"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Update the application
&lt;/h2&gt;

&lt;p&gt;Since we're in the app repo, let's make a change to it.&lt;/p&gt;

&lt;p&gt;We'll make a very small edit to the &lt;code&gt;src/store-front/src/components/TopNav.vue&lt;/code&gt; file and update the title of the application to include a version number.&lt;/p&gt;

&lt;p&gt;Open the file and change the title on line 4 from &lt;code&gt;Azure Pet Supplies&lt;/code&gt; to &lt;code&gt;Azure Pet Supplies v1.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you don't want to use an editor, you can run the following command to make the change using &lt;code&gt;sed&lt;/code&gt; (I love and hate &lt;code&gt;sed&lt;/code&gt; at the same time).&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;# make sure you are in the aks-store-demo directory&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"s/Azure Pet Supplies/Azure Pet Supplies v1.0.0/g"&lt;/span&gt; src/store-front/src/components/TopNav.vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push the changes to the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add src/store-front/src/components/TopNav.vue
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: update title"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using GitHub CLI, create and publish a new release tagged as &lt;code&gt;1.0.0&lt;/code&gt;. This will trigger the release workflow we just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh release create 1.0.0 &lt;span class="nt"&gt;--generate-notes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait about 10 seconds then run the following command to watch the workflow run.&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;# you can select the release workflow from the list&lt;/span&gt;
gh run watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;store-front&lt;/code&gt; container image release workflow setup, let's configure image update automation in Flux.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure image update automation
&lt;/h2&gt;

&lt;p&gt;To setup FluxCD to listen for changes in the GitHub Container Registry, we need to create a new &lt;code&gt;ImageRepository&lt;/code&gt; resource. You need to create an &lt;code&gt;ImageRepository&lt;/code&gt; resource for each image you want to automate. In this case, we're only automating the &lt;code&gt;store-front&lt;/code&gt; image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Flip back over to the &lt;code&gt;aks-store-demo-manifests&lt;/code&gt; repo&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using the FluxCLI, run the following command to create the manifest for the &lt;code&gt;ImageRepository&lt;/code&gt; resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create image repository store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghcr.io/&lt;span class="nv"&gt;$GITHUB_USER&lt;/span&gt;/aks-store-demo/store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/aks-store-demo-store-front-image.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command to create an &lt;code&gt;ImagePolicy&lt;/code&gt; resource to tell FluxCD how to determine the newest image tags. We'll use the &lt;code&gt;semver&lt;/code&gt; filter to only allow image tags that are valid semantic versions and equal to or greater than &lt;code&gt;1.0.0&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;flux create image policy store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--select-semver&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'&amp;gt;=1.0.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/aks-store-demo-store-front-image-policy.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;There are other filters you can use as well. You can read more about them &lt;a href="https://fluxcd.io/flux/guides/image-update/#imagepolicy-examples" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, run the following command to create an &lt;code&gt;ImageUpdateAutomation&lt;/code&gt; resource which enables FluxCD to update images tags in our YAML manifests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux create image update store-front &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--interval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--git-repo-ref&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aks-store-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--git-repo-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./base"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--checkout-branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--author-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxcdbot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--author-email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxcdbot@users.noreply.github.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--commit-template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{range .Updated.Images}}{{println .}}{{end}}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--export&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ./clusters/dev/aks-store-demo-store-front-image-update.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;If you are uncomfortable with FluxCD making changes directly to the checkout branch (in this case &lt;code&gt;main&lt;/code&gt;), you can create a separate branch for FluxCD (using the &lt;code&gt;--push-branch&lt;/code&gt; parameter) to specify where commits should be pushed to. This will then enable you to follow your normal Git workflows (e.g., create a pull request to merge the changes into the main branch).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run the following commands to commit and push the new Flux resources to the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add image automation"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a few minutes, run the following command to see the status of the image update automation resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get image repository store-front
flux get image policy store-front
flux get image update store-front
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image update automation is setup but it won't do anything until we tell it which Deployments to update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update the manifest
&lt;/h2&gt;

&lt;p&gt;We're going to sick with updating the &lt;code&gt;store-front&lt;/code&gt; only. So we'll need to update the &lt;code&gt;store-front&lt;/code&gt; deployment manifest to use the &lt;code&gt;ImagePolicy&lt;/code&gt; resource we created earlier. This is done by &lt;strong&gt;marking&lt;/strong&gt; the manifest with a comment.&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;base/store-front.yaml&lt;/code&gt; file using your favorite editor.&lt;/p&gt;

&lt;p&gt;On line 19, update the image to use your GitHub Container Registry. Then at the end of the line, add a comment to include the namespace and name of your &lt;code&gt;ImagePolicy&lt;/code&gt; resource. This &lt;strong&gt;marks&lt;/strong&gt; the deployment for Flux to update.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The comment is super important because Flux will not implement the image tag policy if it doesn't have this comment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The line should look something like this:&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/&amp;lt;REPLACE_THIS_WITH_YOUR_GITHUB_USERNAME&amp;gt;/aks-store-demo/store-front:latest&lt;/span&gt; &lt;span class="c1"&gt;# {"$imagepolicy": "flux-system:store-front"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Setting the marker in the deployment manifest is fine for this demo, but ideally you'd want to set it in the kustomization manifest instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Commit and push the changes to the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add base/store-front.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: add imagepolicy to store-front manifest"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we've successfully setup image automation in our cluster. Time to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the image automation
&lt;/h2&gt;

&lt;p&gt;We're going to test the developer workflow we laid out at the beginning of this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make another change
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Flip back over to the &lt;code&gt;aks-store-demo&lt;/code&gt; repo&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Make another change to the &lt;code&gt;TopNav.vue&lt;/code&gt; file. This time, change the title to &lt;code&gt;Azure Pet Supplies v2.0.0&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;&lt;span class="c"&gt;# make sure you are in the aks-store-demo directory&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"s/Azure Pet Supplies v1.0.0/Azure Pet Supplies v2.0.0/g"&lt;/span&gt; src/store-front/src/components/TopNav.vue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commit and push the changes to the repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add src/store-front/src/components/TopNav.vue
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat: update title again"&lt;/span&gt;
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new &lt;code&gt;2.0.0&lt;/code&gt; release. This will trigger the release workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh release create 2.0.0 &lt;span class="nt"&gt;--generate-notes&lt;/span&gt;

&lt;span class="c"&gt;# wait about 5 seconds then run the following command&lt;/span&gt;
gh run watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify the image update
&lt;/h3&gt;

&lt;p&gt;After a few minutes, you should see the new image tag in the &lt;code&gt;aks-store-demo&lt;/code&gt; repo and the &lt;code&gt;store-front&lt;/code&gt; deployment being updated in the cluster.&lt;/p&gt;

&lt;p&gt;The reconcile interval for &lt;code&gt;ImagePolicy&lt;/code&gt; was set to 1 minute. So after 1 minute or so, we should see that the update was successful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get image policy store-front
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check the &lt;code&gt;store-front&lt;/code&gt; deployment to see the new image tag.&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 deploy store-front &lt;span class="nt"&gt;-n&lt;/span&gt; dev &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;image:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see that the image tag has been updated to &lt;code&gt;2.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sweet! We've successfully automated image updates in our cluster using FluxCD and GitOps 🚀&lt;/p&gt;

&lt;p&gt;Get the public IP of the &lt;code&gt;store-front&lt;/code&gt; service 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;kubectl get svc store-front &lt;span class="nt"&gt;-n&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a web browser, navigate to the public IP address of the &lt;code&gt;store-front&lt;/code&gt; service. You should see the application running with our new title that includes &lt;code&gt;v2.0.0&lt;/code&gt; 🥳&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The power of GitOps is on full display here 🤖&lt;/p&gt;

&lt;p&gt;In this article, we took a look at how you can use leverage an innovative feature of FluxCD to automate image updates in your AKS cluster. As an app developer, I can simply make my code changes and push it through a PR process. Once the PR is approved and merged and a new release is created, the image update automation kicks in and updates the image tag in the cluster for us without any manual intervention. This is a great way to streamline the deployment process and keep your cluster up to date with the latest images.&lt;/p&gt;

&lt;p&gt;As an aside, I did all this open source FluxCD. This could also be done using the AKS extension for FluxCD but you do need to enable the &lt;code&gt;image-automation-controller&lt;/code&gt; and &lt;code&gt;image-reflector-controller&lt;/code&gt; components. You can find more information on how to do that &lt;a href="https://learn.microsoft.com/azure/azure-arc/kubernetes/tutorial-use-gitops-flux2?tabs=azure-cli#control-which-controllers-are-deployed-with-the-flux-cluster-extension" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If you have any feedback or suggestions, please feel free to reach out to me on &lt;a href="https://twitter.com/pauldotyu" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://www.linkedin.com/in/yupaul" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aka.ms/cloudnative/GitGoingWithGitOps" rel="noopener noreferrer"&gt;Git going with GitOps on AKS: A Step-by-Step Guide using FluxCD AKS Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fluxcd.io/flux/guides/image-update/" rel="noopener noreferrer"&gt;Automate Image Updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>fluxcd</category>
      <category>kustomize</category>
    </item>
  </channel>
</rss>
