<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[PhiL in the Cloud]]></title><description><![CDATA[Blog about all things #kubernetes | #cloudnative | #azure]]></description><link>https://philipwelz.com</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 19:17:36 GMT</lastBuildDate><atom:link href="https://philipwelz.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[First look - GitHub Copilot code reviews]]></title><description><![CDATA[On February 26th, GitHub announced some exciting news - Copilot Code Review has officially entered its public preview phase! In this post, I’ll walk you through my first impressions of the feature and show you step-by-step how to enable it and even m...]]></description><link>https://philipwelz.com/first-look-github-copilot-code-reviews</link><guid isPermaLink="true">https://philipwelz.com/first-look-github-copilot-code-reviews</guid><category><![CDATA[GitHub]]></category><category><![CDATA[copilot]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Philip Welz]]></dc:creator><pubDate>Wed, 12 Mar 2025 00:21:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/FlPc9_VocJ4/upload/f43b41cc40dd9c88eb779dc4d64d02d4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>On February 26th, GitHub announced some exciting news - Copilot Code Review has officially entered its public preview phase! In this post, I’ll walk you through my first impressions of the feature and show you step-by-step how to enable it and even manually trigger it. Let’s dive in and explore this new addition!</p>
<h2 id="heading-what-is-github-copilot-code-review">What is GitHub Copilot code review?</h2>
<p>Having been around in our IDEs for nearly two years (or closer to three if you include the technical preview), GitHub Copilot has rolled out its latest feature: GitHub Copilot code review, our new friendly neighborhood code reviewer. This feature provides rapid, AI-generated feedback on code changes. It assists us in refining our updates by spotting issues and correcting typos, all with the aim of preparing the pull request for merging before a human reviewer gives the final approval.</p>
<p>Please note that GitHub Copilot code review does not guarantee the detection of all code issues. This caution is clearly stated in the GitHub.com <a target="_blank" href="https://docs.github.com/en/copilot/using-github-copilot/code-review/using-copilot-code-review">documentation</a>:</p>
<blockquote>
<p>Copilot isn't guaranteed to spot all problems or issues in a pull request, and sometimes it will make mistakes. Always validate Copilot's feedback carefully, and supplement Copilot's feedback with a human review.</p>
</blockquote>
<p>Currently, the new feature supports the following languages:</p>
<ul>
<li><p>C#</p>
</li>
<li><p>Go</p>
</li>
<li><p>Java</p>
</li>
<li><p>JavaScript</p>
</li>
<li><p>Markdown</p>
</li>
<li><p>Python</p>
</li>
<li><p>Ruby</p>
</li>
<li><p>TypeScript</p>
</li>
</ul>
<p>You can use GitHub Copilot code review in Visual Studio Code for an initial assessment of a highlighted section and to review your changes, or on GitHub.com for a more in-depth review of the code changes. In this discussion, I will focus exclusively on the review process available on the GitHub website.</p>
<h2 id="heading-how-to-enable-github-copilot-code-review">How to enable GitHub Copilot code review</h2>
<p>There are multiple ways to enable a code review from Copilot. The simplest method is to manually assign Copilot to the pull request, which initiates the code review process:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741735434863/87db3996-0d85-4077-848c-7fb584a31d35.png" alt class="image--center mx-auto" /></p>
<p>Manually assigning Copilot to every pull request can become tedious. Fortunately, there's a more efficient solution: enabling automatic code reviews by Copilot. This can be set up through branch rulesets on either a single GitHub repository or specific repositories on organization level. You'll find the "Request pull request review from Copilot" option within the "Require a pull request before merging" checkbox. The workflow is also documented <a target="_blank" href="https://docs.github.com/en/copilot/using-github-copilot/code-review/configuring-automatic-code-review-by-copilot#configuring-automatic-code-review-for-a-single-repository">here</a> for your convenience.</p>
<p>Another approach that I find quite effective is managing the ruleset through code and then importing it into the repository or organization. For a single repository, a ruleset that mandates a Copilot code review for every pull request on the default branch would be structured as follows:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-number">4111833</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"enable-copilot-code-review"</span>,
  <span class="hljs-attr">"target"</span>: <span class="hljs-string">"branch"</span>,
  <span class="hljs-attr">"source_type"</span>: <span class="hljs-string">"Repository"</span>,
  <span class="hljs-attr">"source"</span>: <span class="hljs-string">"philwelz/github-rulesets"</span>,
  <span class="hljs-attr">"enforcement"</span>: <span class="hljs-string">"active"</span>,
  <span class="hljs-attr">"conditions"</span>: {
    <span class="hljs-attr">"ref_name"</span>: {
      <span class="hljs-attr">"exclude"</span>: [],
      <span class="hljs-attr">"include"</span>: [
        <span class="hljs-string">"~DEFAULT_BRANCH"</span>
      ]
    }
  },
  <span class="hljs-attr">"rules"</span>: [
    {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"pull_request"</span>,
      <span class="hljs-attr">"parameters"</span>: {
        <span class="hljs-attr">"required_approving_review_count"</span>: <span class="hljs-number">0</span>,
        <span class="hljs-attr">"dismiss_stale_reviews_on_push"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">"required_reviewers"</span>: [],
        <span class="hljs-attr">"require_code_owner_review"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">"require_last_push_approval"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">"required_review_thread_resolution"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"automatic_copilot_code_review_enabled"</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">"allowed_merge_methods"</span>: [
          <span class="hljs-string">"squash"</span>
        ]
      }
    }
  ],
  <span class="hljs-attr">"bypass_actors"</span>: []
}
</code></pre>
<p><a target="_blank" href="https://github.com/philwelz/github-rulesets/blob/main/org/enable-copilot-code-review-public-repos.json">Here</a> is an example of a ruleset at the organization level that ensures every pull request on the default branch of all public repos undergoes a Copilot code review.</p>
<p>I hope that in the future, there will be an option to import rulesets using the CLI or GitHub API, allowing for a policy-as-code-like workflow, rather than having to manually import the rulesets through the UI.</p>
<h2 id="heading-code-review-in-action">Code review in action</h2>
<p>Once we've enabled Copilot code review or manually assigned Copilot to a pull request, we're ready to begin.</p>
<p>If the files changed in the pull request are in a supported language, Copilot will review them and check for potential code issues or typos. In my initial example, it identified a typo in the <a target="_blank" href="http://README.md">README.md</a> file:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741688690471/dbe69fd7-eb08-4f86-af84-c45e0c26572e.png" alt class="image--center mx-auto" /></p>
<p>I can now either commit the suggested changes directly to my branch or validate them by opening Copilot in my workspace for further testing. I chose to commit the changes, and once the conversation was resolved, I received this neat PR overview:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741737169635/5fa805bd-97ce-4410-b0c8-f5c7773a6b06.png" alt class="image--center mx-auto" /></p>
<p>If your code changes are in languages that aren't supported, such as a Terraform and Terraform provider updates, you can expect a really simplified output from Copilot:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741737337874/370ec015-7541-42eb-8cd8-962664f720eb.png" alt class="image--center mx-auto" /></p>
<p>Bonus: Although GitHub Actions aren't listed in the supported languages section, it appears that Copilot has a better understanding of changes made to them compared to something like Terraform:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741738647727/bf741621-588c-432a-b8c9-37e235470ebe.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>GitHub Copilot code review is an exciting enhancement to the developer's toolkit, offering AI-generated insights to improve code quality and streamline the review process. It provides valuable feedback on supported languages, helping to identify potential issues early and enhance the overall efficiency of code reviews. By integrating Copilot code reviews into the standard CI/CD workflow, I anticipate that the traditional "four-eyes" principle will evolve into a "four-eyes-and-AI" principle. However, it's essential to remember that it doesn't replace human judgment. I'm eager to conduct more in-depth tests with a supported language and will be sharing my findings here.</p>
]]></content:encoded></item><item><title><![CDATA[How to Deploy LibreChat (Your Own GPT) on Azure with Terraform and GitHub Actions]]></title><description><![CDATA[At the start of this year, I came across the LibreChat project, which had nearly 20,000 stars on GitHub at the time. As a long-time ChatGPT Plus user, I was intrigued by this open-source solution, which labeled itself as an “enhanced ChatGPT” clone. ...]]></description><link>https://philipwelz.com/how-to-deploy-librechat-your-own-gpt-on-azure-with-terraform-and-github-actions</link><guid isPermaLink="true">https://philipwelz.com/how-to-deploy-librechat-your-own-gpt-on-azure-with-terraform-and-github-actions</guid><category><![CDATA[Azure]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Terraform]]></category><category><![CDATA[azure-container-apps]]></category><category><![CDATA[openai]]></category><category><![CDATA[Azure OpenAI]]></category><category><![CDATA[Librechat]]></category><dc:creator><![CDATA[Philip Welz]]></dc:creator><pubDate>Tue, 11 Mar 2025 08:49:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/67l-QujB14w/upload/e2d5b84805e0671eb94d24343d5f0375.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At the start of this year, I came across the LibreChat project, which had nearly 20,000 stars on <a target="_blank" href="https://github.com/danny-avila/LibreChat">GitHub</a> at the time. As a long-time ChatGPT Plus user, I was intrigued by this open-source solution, which labeled itself as an “enhanced ChatGPT” clone. Being a staunch advocate for open-source technologies, I was curious to explore its capabilities and wondered if I could seamlessly deploy LibreChat using cloud-native technologies on the Azure platform. Join me as I walk you through my journey.</p>
<h2 id="heading-what-is-librechat"><strong>What is LibreChat?</strong></h2>
<p>LibreChat is a free and open-source AI chat platform created by <a target="_blank" href="https://www.librechat.ai/authors/danny">Danny Avila</a>. With contributions from over 200 developers, the project continues to gain momentum and has even outlined an <a target="_blank" href="https://www.librechat.ai/blog/2025-02-20_2025_roadmap">roadmap for 2025</a>. Its key design principles focus on delivering a <strong>user-friendly interface</strong>, supporting a <strong>wide range of AI providers</strong>, offering <strong>extensibility</strong>, and prioritizing <strong>security</strong>. You can find more detailed information about the project <a target="_blank" href="https://www.librechat.ai/">here</a>. For the basic setup the following components are needed:</p>
<ul>
<li><p><strong>API/UI</strong>: The core application that powers LibreChat's interface and backend.</p>
</li>
<li><p><strong>MongoDB</strong>: A database used to store and handle dynamic, flexible data.</p>
</li>
</ul>
<p>Enhanced features:</p>
<ul>
<li><p><strong>Search</strong>: Provides a fast and efficient way to explore and navigate past conversations.</p>
</li>
<li><p><strong>RAG API</strong>: Offers context-aware responses by leveraging user-uploaded files to enhance interactions.</p>
</li>
</ul>
<p>What sets LibreChat apart is its flexibility. The platform can be customized to suit your specific requirements, making it adaptable for a range of use cases and deployments.</p>
<p>For my installation, I decided to proceed with the core application, MongoDB, and optionally enable the search integration, as I currently don’t have a need for the RAG API.</p>
<h2 id="heading-choosing-the-right-azure-services">Choosing the right Azure Service(s)</h2>
<p>With this decision in mind, I knew I needed to deploy 1-2 applications along with a MongoDB instance. Azure provides a wide range of solutions for hosting web applications, but given my strong passion for cloud-native technologies, containerization was the obvious (and only, let’s be honest) choice for me. However, using Kubernetes felt like overkill for such a lightweight setup, so I choose Azure Container Apps as the home for LibreChat.</p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/container-apps/overview">Azure Container Apps</a> is a serverless platform specifically designed for scenarios like mine - just provide a container image, configure a few settings, and let the platform handle the rest. It’s a perfect match for this use case!</p>
<p>And then it hit me like a bolt of lightning striking the Hill Valley Courthouse. Azure Container Apps has another feature that’s a perfect fit for this scenario <strong>K</strong>ubernetes <strong>E</strong>vent-<strong>D</strong>riven <strong>A</strong>utoscaling <strong>(KEDA)</strong>. With <a target="_blank" href="https://keda.sh/">KEDA</a>, Azure Container Apps can automatically scale HTTP-based applications down to zero when they’re not in use. While this does introduce a brief wait time as the app spins up, it’s completely acceptable for my use case as i dont need the application to be running 24/7. An added bonus? This approach not only saves costs but also reduces environmental impact by lowering CO2 emissions.</p>
<p>The natural choice for deploying MongoDB on Azure is <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cosmos-db/mongodb/introduction">CosmosDB with the MongoAPI</a> (at least for me). While I could have opted to run MongoDB in an Azure Container App, I’m not a big fan of running databases in containers. Instead, I selected the serverless option for the CosmosDB account, allowing me to pay only for what I use—aligning perfectly with the pay-as-you-go model of the container app setup.</p>
<blockquote>
<p>CosmosDB provides a lifetime free tier for one CosmosDB account per Azure subscription, making it an appealing option for this setup. However, it's important to note that the free tier does not support the serverless capability.</p>
</blockquote>
<p>With the Azure services selected, my go-to tools for deploying this setup are Terraform and GitHub Actions.</p>
<h2 id="heading-lets-get-practical-getting-started">Let’s Get Practical: Getting Started</h2>
<p>When working with Terraform, one crucial consideration is the management of the state file. The standard practice when using Terraform with Azure is to store the state file in an Azure Storage Account.</p>
<p>The next important aspect is authentication. Historically, Terraform authentication was handled using User Principals or Machine Accounts (Service Principals). However, this approach required storing sensitive credentials and secrets, introducing potential security risks.</p>
<p>In the modern cloud-native world, the de facto standard has shifted to OIDC (OpenID Connect). By leveraging OIDC and Workload Identity Federation, we can utilize managed identities to authenticate and deploy resources with Terraform. This eliminates the need to manage secrets manually and avoids the hassle of creating additional Entra ID (formerly Azure AD) permissions typically required for service principals. It’s a much cleaner, more secure, and practical approach for modern infrastructure-as-code workflows. Check out <a target="_blank" href="https://thomasthornton.cloud/2025/02/27/deploying-to-azure-secure-your-github-workflow-with-oidc/">this blog</a> for a more in-depth exploration of the topic.</p>
<p>To begin the setup, start by cloning the repository and installing the required tools: <strong>Azure CLI</strong>, <strong>GitHub CLI</strong>, and <strong>jq</strong>.</p>
<p>For a faster setup, I’ve created a small script that takes the following inputs. These inputs can be provided either through a <code>.env</code> file or directly within the script:</p>
<ul>
<li><p><strong>name -</strong> the project name</p>
</li>
<li><p><strong>scope -</strong> the scope, example: tf for the terraform</p>
</li>
<li><p><strong>location -</strong> the Azure Region to deploy to</p>
</li>
<li><p><strong>tag -</strong> tags for the resources created by the script</p>
</li>
<li><p><strong>ghRepo -</strong> the GitHub repo from where the Terraform code is deployed</p>
</li>
</ul>
<p>Before Azure Resources are created, the script will attempt to retrieve the current Subscription ID and Tenant ID using the Azure CLI. You can customize this behavior to suit your specific requirements.</p>
<p>The script will create the following resources in Azure:</p>
<ul>
<li><p><strong>Resource Group</strong></p>
</li>
<li><p><strong>Storage Account with Blob Container</strong></p>
</li>
<li><p><strong>Managed Identity with Owner Role Assignment</strong></p>
</li>
<li><p><strong>Federated Credentials</strong></p>
</li>
<li><p><strong>Several GitHub Secrets</strong> - for the ease of use in the GitHub Actions</p>
</li>
</ul>
<blockquote>
<p>For simplicity, I assigned the managed identity the <strong>Owner</strong> role on my Azure subscription. However, this approach is not suitable for production environments. In a production-ready setup, you should follow the principle of least privilege and assign only the minimum permissions necessary for your deployment to function correctly.</p>
</blockquote>
<p>Let’s take a closer look at the federated credentials.</p>
<ul>
<li><p>The first credential grants the Managed Identity permission to deploy Terraform code, but only from the <strong>main branch</strong>. This ensures that code deployment from other branches, such as the <strong>dev branch</strong>, is blocked.</p>
</li>
<li><p>The second credential provides permission to execute Terraform validation tasks, such as running <strong>CI workflows</strong> with <code>terraform plan</code> on pull requests.</p>
</li>
</ul>
<p>This setup adds an extra layer of control and security to the deployment process.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># create federated credential for main branch</span>
az identity federated-credential create \
    --resource-group <span class="hljs-variable">$rg</span> \
    --identity-name <span class="hljs-string">"<span class="hljs-variable">$miName</span>"</span> \
    --name <span class="hljs-string">"fc-github-<span class="hljs-variable">$name</span>-<span class="hljs-variable">$scope</span>-branch"</span> \
    --issuer <span class="hljs-string">"https://token.actions.githubusercontent.com"</span> \
    --subject <span class="hljs-string">"repo:<span class="hljs-variable">$ghRepo</span>::ref:refs/heads/main"</span>\
    --audiences <span class="hljs-string">"api://AzureADTokenExchange"</span>

<span class="hljs-comment"># create federated credential for pull requests</span>
az identity federated-credential create \
    --resource-group <span class="hljs-variable">$rg</span> \
    --identity-name <span class="hljs-string">"<span class="hljs-variable">$miName</span>"</span> \
    --name <span class="hljs-string">"fc-github-<span class="hljs-variable">$name</span>-<span class="hljs-variable">$scope</span>-pr"</span> \
    --issuer <span class="hljs-string">"https://token.actions.githubusercontent.com"</span> \
    --subject <span class="hljs-string">"repo:<span class="hljs-variable">$ghRepo</span>:pull_request"</span>\
    --audiences <span class="hljs-string">"api://AzureADTokenExchange"</span>
</code></pre>
<p>Now we are set and done. You can find the complete code for the script in my GitHub repository <a target="_blank" href="https://github.com/philwelz/chat/blob/main/scripts/up.sh">here</a>.</p>
<h2 id="heading-up-amp-running-final-steps-for-your-deployment">Up &amp; Running: Final Steps for Your Deployment</h2>
<p>Before getting LibreChat up and running, you need to configure an authentication method. I opted to use GitHub as the authentication backend. You can follow the workflow <a target="_blank" href="https://www.librechat.ai/docs/configuration/authentication/OAuth2-OIDC/github">here</a> to set up LibreChat authentication using a GitHub App.</p>
<p>Once the GitHub App is created, you'll need to create the following GitHub secrets:</p>
<ul>
<li><p><code>LIBRECHAT_APP_ID</code></p>
</li>
<li><p><code>LIBRECHAT_APP_SECRET</code></p>
</li>
</ul>
<blockquote>
<p>Once the desired users have registered, it's important to disable further registrations by setting <code>ALLOW_REGISTRATION = false</code>, as permission management is not implemented. Alternatively, you can choose a different authentication method that better fits your requirements.</p>
</blockquote>
<p>Now it's time to configure the AI endpoints. LibreChat supports a wide range of AI providers. The Terraform code available in the repository enables <strong>Azure OpenAI</strong> and <strong>OpenAI</strong> by default. If you wish to disable these endpoints and adopt the config with your AI endpoints, you can do so by setting the following variables:</p>
<ul>
<li><p><code>openai_enabled = false</code></p>
</li>
<li><p><code>azure_openai_enabled = false</code></p>
</li>
</ul>
<p>The default models for both AI providers are:</p>
<ul>
<li><p>gpt-4o</p>
</li>
<li><p>gpt-4o-mini</p>
</li>
<li><p>o3-mini</p>
</li>
</ul>
<blockquote>
<p>If you keep the default settings with OpenAI enabled, you'll need to create a GitHub secret named <code>OPENAI_API_KEY</code> and provide your OpenAI Platform API key.</p>
</blockquote>
<p><strong><em>Optional:</em></strong> <em>To enable the Search integration with MeiliSearch, simply set the variable</em> <code>search_enabled</code> <em>to</em> <code>true</code><em>.</em></p>
<p><strong><em>Optional:</em></strong> <em>Custom domain integration.</em> <em>I’m using</em> <em>Cloudflare</em> <em>for managing the custom domain setup. However, you’re free to use your own domain provider or skip this configuration altogether and stick with Azure's default settings.</em></p>
<p>You can find the configuration options for LibreChat <a target="_blank" href="https://github.com/philwelz/chat/blob/main/src/_locals.tf">here</a>. Refer to the official <a target="_blank" href="https://www.librechat.ai/docs/configuration/dotenv">documentation</a> for a detailed overview of the available settings.</p>
<p>Once you’ve made the necessary adjustments to the Terraform code and LibreChat config, you’re ready to push it and trigger the deployment using GitHub Actions.</p>
<h2 id="heading-expanding-the-setup-ci-and-extras">Expanding the Setup: CI and Extras</h2>
<p>By this point, all the heavy lifting is complete, and your own GPT should be up and running. Now it’s time to ensure that your Terraform setup and providers are fully up-to-date and to add some Terraform documentation. But don’t worry - I’ve got you covered!</p>
<p>In the repository, you’ll find a <a target="_blank" href="https://github.com/philwelz/chat/blob/main/.github/workflows/tf-docs.yml">workflow</a> for <a target="_blank" href="https://github.com/terraform-docs/gh-actions">terraform-docs</a>, an action that ensures the Terraform documentation for your code is always up-to-date by automatically committing changes to an open pull request.</p>
<p>But that’s not all. The repository also includes a <a target="_blank" href="https://github.com/philwelz/chat/blob/main/.github/workflows/renovate.yml">Renovate workflow</a>, which ensures that your Terraform setup and providers stay up-to-date. <a target="_blank" href="https://docs.renovatebot.com/">Renovate-Bot</a> monitors GitHub releases for updates to these components. Additionally, it keeps MeiliSearch and LibreChat current by tracking new container image releases. The config for Renovate can be found <a target="_blank" href="https://github.com/philwelz/chat/blob/main/.github/renovate.js">here</a>.</p>
<p>To enable Renovate to monitor GitHub releases, GitHub authentication needs to be configured. This can be achieved using either a Personal Access Token (PAT) or a GitHub App. I chose to use a GitHub App. Once the GitHub App is set up, the following GitHub secrets must be created for use with GitHub Actions:</p>
<ul>
<li><p><code>RENOVATE_APP_ID</code></p>
</li>
<li><p><code>RENOVATE_PRIVATE_KEY</code></p>
</li>
</ul>
<p>Last but no least, Labeler. I like labeler because the automation helps streamline workflow organization and makes it easier to quickly identify the purpose of a pull request. As you can see in the example, based on certain changes to files, labels are attached to the pull request.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">cicd:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">changed-files:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">any-glob-to-any-file:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">.github/**</span>

<span class="hljs-attr">terraform:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">changed-files:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">any-glob-to-any-file:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">src/**</span>

<span class="hljs-attr">documentation:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">changed-files:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">any-glob-to-any-file:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.md'</span>
</code></pre>
<h2 id="heading-how-to-get-started">How to get started</h2>
<p>You can access the complete Terraform code and GitHub Actions workflows in this <a target="_blank" href="https://github.com/philwelz/chat">GitHub repository</a>. The state of the repository reflects the exact LibreChat GPT version I’m currently using. In other words, this code is live, actively maintained, and subject to regular improvements and changes. It’s a great starting point for setting up your own GPT instance and experimenting with Generative AI. Feel free to dive in, explore, and customize it to suit your needs!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Deploying LibreChat on Azure with Terraform and GitHub Actions is an exciting and practical journey, combining the best of open-source innovation with the flexibility and scalability of cloud-native technologies. By utilizing Azure Container Apps and CosmosDB, you can build a cost-efficient and sustainable setup tailored to your needs. The adoption of OIDC authentication and GitHub Actions simplifies deployment, ensuring a secure and streamlined workflow. Additionally, tools like Renovate and terraform-docs help keep your infrastructure up-to-date and well-documented. This approach not only enables you to operate your own GPT but also embraces modern cloud-deployment best practices, providing a powerful and flexible platform for AI-driven applications.</p>
]]></content:encoded></item><item><title><![CDATA[Enable Node autoprovisioning (NAP) on an existing AKS cluster]]></title><description><![CDATA[Finally announced beginning of December 2023, node autoprovisioning (NAP) enables the use of Karpenter on your AKS cluster. Usually there are functionality drawbacks when features go in public preview. One of those limitations are that NAP can only b...]]></description><link>https://philipwelz.com/enable-nap-on-an-existing-aks-cluster</link><guid isPermaLink="true">https://philipwelz.com/enable-nap-on-an-existing-aks-cluster</guid><category><![CDATA[aks]]></category><category><![CDATA[Azure]]></category><category><![CDATA[karpenter]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[cloud native]]></category><dc:creator><![CDATA[Philip Welz]]></dc:creator><pubDate>Fri, 09 Feb 2024 08:21:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/npxXWgQ33ZQ/upload/64592a8ee9712f48fd245ad6f71c7afe.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Finally <a target="_blank" href="https://azure.microsoft.com/en-us/updates/public-preview-node-autoprovision-support-in-aks/">announced</a> beginning of December 2023, node autoprovisioning (NAP) enables the use of <a target="_blank" href="https://karpenter.sh/">Karpenter</a> on your AKS cluster. Usually there are functionality drawbacks when features go in public preview. One of those limitations are that NAP can only be enabled on new clusters.</p>
<p>Due to latest improvements from the Engineering Team that is working on Karpenter at Microsoft, NAP can now be enabled on existing clusters. Follow along to see how your cluster can be migrated to NAP.</p>
<blockquote>
<p>Notice: The only supported network configuration to enable NAP on an existing cluster is Azure Overlay with Cilium data plane.</p>
</blockquote>
<h2 id="heading-requirements">Requirements</h2>
<p>As mentioned already NAP is currently public preview, so you need to have Azure CLI with the <a target="_blank" href="https://github.com/Azure/azure-cli-extensions/tree/main/src/aks-preview">aks-preview</a> extension 0.5.170 or later installed.</p>
<p>You can verify if you have the extension installed or if you have to correct version with the following commands:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># get installed version of aks-preview:</span>
az extension list | grep aks-preview -A3 -B 3

<span class="hljs-comment"># if you dont have the extension installed, you can install it with:</span>
az extension install --name aks-preview

<span class="hljs-comment"># update the extension if dont at least match 0.5.170:</span>
az extension update --name aks-preview
</code></pre>
<p>After that, we need to register the NodeAutoProvisioningPreview feature flag:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># register the NodeAutoProvisioningPreview feature flag </span>
az feature register \
  --namespace <span class="hljs-string">"Microsoft.ContainerService"</span> \
  --name <span class="hljs-string">"NodeAutoProvisioningPreview"</span>

<span class="hljs-comment"># verify the registration status</span>
az feature show \
  --namespace <span class="hljs-string">"Microsoft.ContainerService"</span> \
  --name <span class="hljs-string">"NodeAutoProvisioningPreview"</span>

<span class="hljs-comment"># re-register the Microsoft.ContainerService resource provider</span>
az provider register --namespace Microsoft.ContainerService
</code></pre>
<h2 id="heading-enable-node-autoprovision">Enable Node autoprovision</h2>
<p>To enable NAP on an existing Cluster, simply run this command:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># export this variables as we will will use them more often</span>
CLUSTER_NAME=aks-nap-test-1
CLUSTER_RG=rg-nap-test

<span class="hljs-comment"># update existing cluster and enable NAP</span>
az aks update \
  --name <span class="hljs-variable">$CLUSTER_NAME</span> \
  --resource-group <span class="hljs-variable">$CLUSTER_RG</span> \
  --node-provisioning-mode Auto
</code></pre>
<p>After a the update command is successfully finished, we can inspect the Kubernetes API-resources to verify that the Karpenter and the Karpenter Azure provider Custom Resource Definitions (CRDs) were added:</p>
<pre><code class="lang-plaintext">kubectl api-resources | grep -e aksnodeclasses -e nodeclaims -e nodepools

NAME                               SHORTNAMES          APIVERSION                             NAMESPACED   KIND
aksnodeclasses                     aksnc,aksncs        karpenter.azure.com/v1alpha2           false        AKSNodeClass
nodeclaims                                             karpenter.sh/v1beta1                   false        NodeClaim
nodepools                                              karpenter.sh/v1beta1                   false        NodePool
</code></pre>
<p>Now we are ready to go. Almost! Before disabling the VMSS node pool(s) we will do some precaution checks and verify if the default NAP resources are added to our cluster (you can also deploy your own NodePools and AksNodeClasses at this step if you dont want to stick with the standard):</p>
<pre><code class="lang-plaintext">kubectl get nodepools

NAME           NODECLASS
default        default
system-surge   system-surge


kubectl get aksnodeclasses

NAME           AGE
default        43s
system-surge   43s
</code></pre>
<h2 id="heading-disable-the-vmss-node-pools">Disable the VMSS node pool(s)</h2>
<p>Lets also record the nodes and pods that are running on the cluster. As you can see below we are running a system mode and an user mode AKS nodepool with 3 nodes each and all pods of the <a target="_blank" href="https://github.com/Azure/karpenter-provider-azure/blob/main/examples/workloads/inflate.yaml">inflate deployment</a> are scheduled to the user node pool (as the system node pool has the CriticalAddonsOnly=true:NoSchedule taint):</p>
<pre><code class="lang-plaintext">kubectl get nodes

NAME                                STATUS   ROLES   AGE     VERSION
aks-nodepool1-38290569-vmss000000   Ready    agent   13m     v1.27.7
aks-nodepool1-38290569-vmss000001   Ready    agent   13m     v1.27.7
aks-nodepool1-38290569-vmss000002   Ready    agent   13m     v1.27.7
aks-userpool1-17470761-vmss000000   Ready    agent   4m25s   v1.27.7
aks-userpool1-17470761-vmss000001   Ready    agent   4m25s   v1.27.7
aks-userpool1-17470761-vmss000002   Ready    agent   4m25s   v1.27.7

kubectl get pods -owide

NAME                       READY   STATUS    RESTARTS   AGE     IP             NODE                                NOMINATED NODE   READINESS GATES
inflate-74ccd665f4-7xj2w   1/1     Running   0          6m32s   10.244.5.136   aks-userpool1-17470761-vmss000001   &lt;none&gt;           &lt;none&gt;
inflate-74ccd665f4-bgdjh   1/1     Running   0          114s    10.244.4.212   aks-userpool1-17470761-vmss000000   &lt;none&gt;           &lt;none&gt;
inflate-74ccd665f4-k5mt8   1/1     Running   0          6m32s   10.244.3.128   aks-userpool1-17470761-vmss000002   &lt;none&gt;           &lt;none&gt;
</code></pre>
<p>Now we are ready to finally disable the user mode node pool(s) by simply scaling it to 0. We could also decide to leave this static node pool(s) as NAP and Karpenter supports this scenario but wont autoscale them. The system node pool has to stay untouched in this scenario.</p>
<blockquote>
<p>If the target node pool(s) have cluster-autoscaler enabled we have to disable it before scaling to 0</p>
</blockquote>
<pre><code class="lang-bash"><span class="hljs-comment"># variable for the VMSS node pool name</span>
NODE_POOL=userpool1

<span class="hljs-comment"># disable cluster-autoscaler before scaling to 0 if enabled</span>
az aks nodepool update \
  --name <span class="hljs-variable">$NODE_POOL</span> \
  --name <span class="hljs-variable">$CLUSTER_NAME</span> \
  --resource-group <span class="hljs-variable">$CLUSTER_RG</span> \
  --disable-cluster-autoscaler

<span class="hljs-comment"># scale user node pool to 0</span>
az aks nodepool scale \
  --name <span class="hljs-variable">$NODE_POOL</span> \
  --name <span class="hljs-variable">$CLUSTER_NAME</span> \
  --resource-group <span class="hljs-variable">$CLUSTER_RG</span> \
  --no-wait \
  --node-count 0
</code></pre>
<p>Now we can let NAP do what NAP does best, choose automatically the best suitable node size for our workload. After 1-2 minutes or workload should be up and running:</p>
<pre><code class="lang-plaintext">kubectl get events -A --field-selector source=karpenter -w

NAMESPACE   LAST SEEN   TYPE     REASON                      OBJECT                         MESSAGE
default     52s         Normal   Nominated                   pod/inflate-74ccd665f4-g2f5b   Pod should schedule on: nodeclaim/default-6hx6j
default     52s         Normal   Nominated                   pod/inflate-74ccd665f4-lt5tf   Pod should schedule on: nodeclaim/default-6hx6j
default     52s         Normal   Nominated                   pod/inflate-74ccd665f4-xbdt6   Pod should schedule on: nodeclaim/default-6hx6j
default     0s          Normal   Unconsolidatable            node/aks-default-6hx6j         Can't replace with a cheaper node

kubectl get nodeclaims

NAME            TYPE               ZONE           NODE                READY   AGE
default-6hx6j   Standard_D4ls_v5   westeurope-2   aks-default-6hx6j   True    5m12s

kubectl get pods -o wide

NAME                       READY   STATUS    RESTARTS   AGE     IP             NODE                NOMINATED NODE   READINESS GATES
inflate-74ccd665f4-g2f5b   1/1     Running   0          5m52s   10.244.3.198   aks-default-6hx6j   &lt;none&gt;           &lt;none&gt;
inflate-74ccd665f4-lt5tf   1/1     Running   0          5m53s   10.244.3.11    aks-default-6hx6j   &lt;none&gt;           &lt;none&gt;
inflate-74ccd665f4-xbdt6   1/1     Running   0          5m53s   10.244.3.2     aks-default-6hx6j   &lt;none&gt;           &lt;none&gt;
</code></pre>
<p>Voilà! We enabled Node autoprovisioning on our existing AKS cluster. The unused node pools(s) can now safely be removed if wanted:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># delete unused node pool(s) if wanted</span>
az aks nodepool delete \
  --name <span class="hljs-variable">$NODE_POOL</span> \
  --name <span class="hljs-variable">$CLUSTER_NAME</span> \
  --resource-group <span class="hljs-variable">$CLUSTER_RG</span> \
</code></pre>
<h2 id="heading-final-words">Final words</h2>
<p>Check out the official docs for NAP if you want to have a deeper overview about this new feature. Hopefully the documentation will soon remove the limitation statement that Node autoprovisioning can be only enabled on new clusters. Until that lets spread the word that is possible as of today.</p>
<p>At last I suggest to leave a star for <a target="_blank" href="https://github.com/Azure/karpenter-provider-azure">Karpenter Provider for Azure</a> on GitHub. Please also help to improve the Karpenter on Azure provider by submitting feedback of issues while using NAP or work on issues labeled with good first issue.</p>
<p>You can catch me on X or LinkedIn if you want to chat. Cheers.</p>
]]></content:encoded></item></channel></rss>