<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yuri Burger</title>
    <description>The latest articles on DEV Community by Yuri Burger (@yuriburger).</description>
    <link>https://dev.to/yuriburger</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%2F379301%2F2d9fe769-80bb-4e46-9814-a840a92c6e94.png</url>
      <title>DEV Community: Yuri Burger</title>
      <link>https://dev.to/yuriburger</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuriburger"/>
    <language>en</language>
    <item>
      <title>Azure Active Directory B2C With PKCE for Your Angular App</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Mon, 09 Nov 2020 21:08:39 +0000</pubDate>
      <link>https://dev.to/yuriburger/azure-active-directory-b2c-with-pkce-for-your-angular-app-1dcg</link>
      <guid>https://dev.to/yuriburger/azure-active-directory-b2c-with-pkce-for-your-angular-app-1dcg</guid>
      <description>&lt;p&gt;Let's create and integrate an Angular app with Azure Active Directory Business to Consumers using the Authorization Code with Proof Key of Code Exchange flow.&lt;/p&gt;

&lt;p&gt;Although this post works with an Angular App, the concepts (including the twists and tweaks) needed to make it work for Azure AD B2C are universal for Single Page Applications. So even if you have Vue or React, this post could be useful. &lt;/p&gt;

&lt;p&gt;Why this post (and why not just use the well known Implicit flow)? The browser based Implicit flow is old and The OAuth Working Group has published a document recommending replacing the Implicit flow with the newer Authorization Code flow with Proof Key for Code Exchange (we like to simply refer to it as the PKCE flow). &lt;/p&gt;

&lt;p&gt;Azure AD B2C still supports the Implicit flow (as it has for a long time) but it recently began to recommend the PKCE based flow when creating new apps. So now seems like the perfect time to go along with this and start using it too. This blogpost should get you up and running for new apps, but refactoring apps that worked with the Implicit flow shouldn't be too hard. If you happen to use one of the OpenID Connect certified libraries, the changes to your apps codebase are minimal!&lt;/p&gt;

&lt;p&gt;But what is this new PKCE flow? It is basically an enhanced version of the Authorization Code flow. To illustrate, follow me through steps in the diagram. This flow is not complex, but understanding this will benefit you if you ever need to troubleshoot login issues.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F01xyu727y6h0nj9e9jur.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F01xyu727y6h0nj9e9jur.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The user clicks a login link or button. The app generates a random code_verifier and derives a code_challenge from that verifier.&lt;br&gt;
The app then redirects the user to the Azure AD B2C Authorize endpoint with the code_challenge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user is redirected to the login page. After supplying the correct credentials the user is redirected back to the app with a authorization code. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The app receives the code and posts this code along with the code_verifier (from step 1) to the Azure AD B2C Token endpoint to request an access and id token. After validation Azure AD B2C sends both these tokens back to the app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The user can now request data from the API and the app will send the access token with the request.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Setting up the stage (on Azure AD B2C)
&lt;/h3&gt;

&lt;p&gt;This is a complete walkthrough, so contains a lot of steps. If you already have a working Azure AD B2C setup, skip to the next part. &lt;/p&gt;

&lt;p&gt;First we register a new application. Two things are important, the rest can be left with the defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supported account types must be the option that enables the user flows&lt;/li&gt;
&lt;li&gt;The Redirect URI must be of type Single-page-application (SPA) otherwise we would not have PKCE enabled and instead need to fallback on the Implicit flow.&lt;/li&gt;
&lt;/ul&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frzw5orvmm3uvrma5zg9l.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frzw5orvmm3uvrma5zg9l.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After we create the application, we need to enter any additional Redirect URIs we require. In case of this demo, we add &lt;a href="http://localhost:4200/index.html" rel="noopener noreferrer"&gt;http://localhost:4200/index.html&lt;/a&gt; as this matches our Angular development setup.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs1vf666bq1kqx86ifehl.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs1vf666bq1kqx86ifehl.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To be able to request access tokens, we need to setup and expose an API using a scope. Start by "Exposing an API" and setting a App ID URI. This needs only to be done once and the URI must be unique within your Azure AD B2C tenant. &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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F54t6k55xa5yjjxqpyaik.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F54t6k55xa5yjjxqpyaik.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the URI we can continue adding API scope(s).&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvf32n9mfpb1by8lsungh.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fvf32n9mfpb1by8lsungh.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before we can actually request an API scope, the permissions must be added. API Permissions, Add a permission, My APIs&lt;br&gt;
And, because we want to skip the consent forms, we grant admin consent for this permission.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwkg3rhdytnx0ko5kixvv.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwkg3rhdytnx0ko5kixvv.png" alt="Alt Text"&gt;&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwa216xctxdnfc6obfqyu.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fwa216xctxdnfc6obfqyu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally we take note of the Application (client) ID from the overview page. We need this value later configuring our Angular app.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F15e8cq0wm7q9o0k7iipg.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F15e8cq0wm7q9o0k7iipg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up the User Flows (on Azure AD B2C)
&lt;/h3&gt;

&lt;p&gt;User Flows are configurable login/logout/reset experiences. They are (somewhat) customizable and provide us with ready to go multi-language templates for our users. So we set up two of them:&lt;/p&gt;

&lt;p&gt;First a flow for signing up (registration) and signing in (login). This flow enables both in one universal form. &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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn89wifz7tf0u7j2w8l9a.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn89wifz7tf0u7j2w8l9a.png" alt="Alt Text"&gt;&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3gcix30uhxf8fff5xzck.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3gcix30uhxf8fff5xzck.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case I enable the Local Accounts, so the user objects will be stored in my Azure AD B2C tenant.&lt;/p&gt;

&lt;p&gt;The second flow enables self-service password reset. This flow requires some tweaking in our app, but that is covered in the last part. &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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhlbkbtiqy7ctchv16cec.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fhlbkbtiqy7ctchv16cec.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since we have Local Accounts, we enable that option. &lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up your app (with Angular)
&lt;/h3&gt;

&lt;p&gt;There are a few OAuth/OpenID Connect Angular libraries out there, but for my projects (including this demo) I have picked the excellent library from Manfred Steyer. Just follow the "Getting Started" documentation or take a look at the demo app. &lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://manfredsteyer.github.io/angular-oauth2-oidc/docs/index.html" rel="noopener noreferrer"&gt;https://manfredsteyer.github.io/angular-oauth2-oidc/docs/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Couple of things are important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need the clientid from the new Azure AD B2C app that was created earlier;&lt;/li&gt;
&lt;li&gt;You also need the custom scope from that was created together with the app;&lt;/li&gt;
&lt;li&gt;We need a additional steps to be able to succesfully login with PKCE. See the next section for this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The twist and tweaks with Azure AD B2C
&lt;/h3&gt;

&lt;p&gt;Up until this point, things are pretty straightforwared. And if you were to run this example on any of the other well known Identity Service Providers, you would be finished after completing the previous part. For Azure AD B2C we need to do some additional configuration and coding to make things work well.&lt;/p&gt;

&lt;h4&gt;
  
  
  Issue 1: disable strict document validation
&lt;/h4&gt;

&lt;p&gt;The mentioned library uses a feature called strictDiscoveryDocumentValidation by default. This ensures that all of the endpoints provided via the Identity Provider discovery document share the same base URL as the issuer parameter. Azure AD B2C provides different domains or paths for various endpoints and this makes the library fail validation. To use this library with Azure AD B2C we need to disable this document validation. &lt;/p&gt;

&lt;p&gt;There is a property for this in the AuthConfig, just set the "strictDiscoveryDocumentValidation: to "false"&lt;/p&gt;

&lt;h4&gt;
  
  
  Issue 2: support the password reset flow
&lt;/h4&gt;

&lt;p&gt;This one ended up being pretty ugly, especially for the PKCE flow. So what's the deal?&lt;/p&gt;

&lt;p&gt;Microsoft uses a feature called Linking User Flows. What happens is, that if you click the "Forgot password" option in the login form, Microsoft will redirect the user back to your app with a special error code. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A sign-up or sign-in user flow with local accounts includes a Forgot password? link on the first page of the experience. &lt;br&gt;
Clicking this link doesn't automatically trigger a password reset user flow. Instead, the error code AADB2C90118 is returned to your application. Your application needs to handle this error code by running a specific user flow that resets the password.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read more about this here:  &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#linking-user-flows" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#linking-user-flows&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we need to ensure that if a user has clicked the Forgot Password link, we send them on the right path back to Azure AD B2C. Ok, that is where the second flow we created comes into play. This flow has exactly the same base URL but uses a different profile. In our case "b2c_1_passwordreset" instead of "b2c_1_signupandsignin". We do this by noticing the error code and overriding the authorize endpoint:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

if (this.userHasRequestedPasswordReset(err)) {
    // In this case we need to enter a different flow on the Azure AD B2C side.
    // This is still a valid Code + PKCE flow, but uses a different form to support self service password reset
    this.oauthService.loginUrl = this.oauthService.loginUrl.replace(
        'b2c_1_signupandsignin',
        'b2c_1_passwordreset'
    );

    this.oauthService.initCodeFlow();
}

private userHasRequestedPasswordReset(err: OAuthErrorEvent): boolean {
    return (err.params['error_description'] as string).startsWith(
      'AADB2C90118'
    );
}


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

&lt;/div&gt;

&lt;p&gt;This will make sure a user gets directed back to Azure and into the correct flow. If a user now resets their password, they get directed back to your app with the code and our app can fetch the access token and id token. &lt;/p&gt;

&lt;p&gt;But our app breaks. :'( &lt;/p&gt;

&lt;p&gt;I will leave out most of the gory details, but what happens is that our app "sees" the code coming in and starts the code exchange part of the flow (see step 3 in the diagram above). It does that using the default AuthConfig and performs a POST to the default/configured 'b2c_1_signupandsignin' profile endpoint. But our code challenge was done on the 'b2c_1_passwordreset' endpoint and thus Azure throws a "HTTP4xx you screwed up" error. To fix that, we need to make sure that in the case of reset-password, we override the profile on the token endpoint (like we did on the authorize endpoint earlier). This is not that difficult, because we can send a "state" along with our requests. On the way back we will pick up this state and if it is present, we fix the token endpoint:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

this.oauthService
  .loadDiscoveryDocument(url)
  .then((_) =&amp;gt; {
    if (this.userHasEnteredPasswordResetFlow()) {
      // We need to change to token endpoint to match the reset-password flow
      this.oauthService.tokenEndpoint.replace(
        'b2c_1_signupandsignin',
        'b2c_1_passwordreset'
      );
    }

    return this.oauthService.tryLoginCodeFlow();
  })
  .then((_) =&amp;gt; {
    if (!this.oauthService.hasValidAccessToken()) {
      this.oauthService.initCodeFlow();
    }
  })
  .catch((err) =&amp;gt; {
    if (this.userHasRequestedPasswordReset(err)) {
      // In this case we need to enter a different flow on the Azure AD B2C side.
      // This is still a valid Code + PKCE flow, but uses a different form to support self service password reset
      this.oauthService.loginUrl = this.oauthService.loginUrl.replace(
        'b2c_1_signupandsignin',
        'b2c_1_passwordreset'
      );
      // Add this to the state as we need it on our way back
      this.oauthService.initCodeFlow('PASSWORD_RESET');
    } else {
      // Another error has occurred, e.g. the user cancelled the reset-password flow.
      // In that case, simply retry the login.
      this.oauthService.initCodeFlow();
    }
  });

  private userHasEnteredPasswordResetFlow(): boolean {
    return window.location.search.indexOf('PASSWORD_RESET') &amp;gt; -1;
  }

  private userHasRequestedPasswordReset(err: OAuthErrorEvent): boolean {
    return (err.params['error_description'] as string).startsWith(
      'AADB2C90118'
    );
  }


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

&lt;/div&gt;

&lt;p&gt;You can find a fully working example app here (just update the config): &lt;a href="https://github.com/yuriburger/ng-azureb2c-pkce-demo" rel="noopener noreferrer"&gt;https://github.com/yuriburger/ng-azureb2c-pkce-demo&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Thanks &lt;a href="https://daanstolp.nl/" rel="noopener noreferrer"&gt;Daan Stolp&lt;/a&gt; for working with me on the Azure tweaks! &lt;/p&gt;

&lt;p&gt;/Y.&lt;/p&gt;

&lt;h4&gt;
  
  
  More info:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;You will find the code here: &lt;a href="https://github.com/yuriburger/ng-azureb2c-pkce-demo" rel="noopener noreferrer"&gt;https://github.com/yuriburger/ng-azureb2c-pkce-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;User Flows: &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The angular-oauth2-oidc library: &lt;a href="https://manfredsteyer.github.io/angular-oauth2-oidc/docs" rel="noopener noreferrer"&gt;https://manfredsteyer.github.io/angular-oauth2-oidc/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The RfC: &lt;a href="https://tools.ietf.org/html/rfc7636" rel="noopener noreferrer"&gt;https://tools.ietf.org/html/rfc7636&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The news on the Implicit flow: &lt;a href="https://oauth.net/2/grant-types/implicit" rel="noopener noreferrer"&gt;https://oauth.net/2/grant-types/implicit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>angular</category>
      <category>azure</category>
      <category>oauth</category>
      <category>pkce</category>
    </item>
    <item>
      <title>A Better RSS for Hugo</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Fri, 21 Aug 2020 11:26:38 +0000</pubDate>
      <link>https://dev.to/yuriburger/a-better-rss-for-hugo-dm5</link>
      <guid>https://dev.to/yuriburger/a-better-rss-for-hugo-dm5</guid>
      <description>&lt;p&gt;Hugo. Best static blogging platform. See my other post about &lt;a href="https://www.yuriburger.net/2020/06/23/migrating-to-hugo/"&gt;migrating to Hugo&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Missing content
&lt;/h4&gt;

&lt;p&gt;Hugo ships with a default RSS 2.0 template for basic feed functionality. For most feed readers this is probably ok, but for Syndication not so much. The thing is, that the feed XML only contains the basic fields, like link, title, date, description, etc. But what is missing, is the posts content. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DjykxoKX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gj7wx9ebu25gnqoyhzfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DjykxoKX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gj7wx9ebu25gnqoyhzfu.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  A better template
&lt;/h4&gt;

&lt;p&gt;To create a better RSS template, we start with creating a rss.xml file in layouts/_default. You could use other locations and filenames, please see the Hugo documentation for more info on this. The default template is a good starting point, so make sure you copy that content into the rss.xml file first. You can find the default file here: &lt;a href="https://gohugo.io/templates/rss/#the-embedded-rssxml"&gt;https://gohugo.io/templates/rss/#the-embedded-rssxml&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can add our article content to the RSS items by adding a "content" tag (also notice the change to the "description" tag to allow for formatted summaries):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.....
    &amp;lt;item&amp;gt;
      &amp;lt;title&amp;gt;{{ .Title }}&amp;lt;/title&amp;gt;
      &amp;lt;link&amp;gt;{{ .Permalink }}&amp;lt;/link&amp;gt;
      &amp;lt;pubDate&amp;gt;{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}&amp;lt;/pubDate&amp;gt;
      {{ with .Site.Author.email }}&amp;lt;author&amp;gt;{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}&amp;lt;/author&amp;gt;{{end}}
      &amp;lt;guid&amp;gt;{{ .Permalink }}&amp;lt;/guid&amp;gt;

      &amp;lt;description&amp;gt;{{ "&amp;lt;![CDATA[" | safeHTML }} {{ .Summary }}]]&amp;gt;&amp;lt;/description&amp;gt;
      &amp;lt;content:encoded&amp;gt;{{ "&amp;lt;![CDATA[" | safeHTML }} {{ .Content }}]]&amp;gt;&amp;lt;/content:encoded&amp;gt;

    &amp;lt;/item&amp;gt;
.....
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To enable this change, the header needs to change too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;rss version="2.0" 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"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  W3C Validation
&lt;/h4&gt;

&lt;p&gt;After these changes, you need to make sure you end up with a valid RSS feed, so let's check this with the W3C Validation Service: &lt;a href="https://validator.w3.org/feed/:"&gt;https://validator.w3.org/feed/:&lt;/a&gt;&lt;br&gt;
Open your your blog RSS feed (usually located at /blog/index.xml) and paste the raw xml content in the Validation Field, press "Validate" and see if it al works out. I ended up with valid RSS but with an important recommendation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4D_1N2ip--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4zgrkida15m67ixq92ah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4D_1N2ip--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4zgrkida15m67ixq92ah.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is of course bad for syndication, because we need an absolute URL to render the images directly from the source location.&lt;/p&gt;
&lt;h4&gt;
  
  
  Absolute links
&lt;/h4&gt;

&lt;p&gt;So how to fix the "content:encoded should not contain relative URL references"?&lt;/p&gt;

&lt;p&gt;I found some code on Hugo's Discourse that worked almost instantly. Thank you "hacdias" (&lt;a href="https://discourse.gohugo.io/u/hacdias"&gt;https://discourse.gohugo.io/u/hacdias&lt;/a&gt;)!&lt;br&gt;
Create a partial rss.html (in "layouts/partials") with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $html := .Content | safeHTML }}

{{ $hrefs := findRE "href=\"([^\"]*)\"" $html }}
{{ range $href := $hrefs}}
  {{ $absHref := strings.TrimPrefix "href=\"" $href  }}
  {{ $absHref = strings.TrimSuffix "\"" $absHref  }}
  {{ $absHref = printf "href=\"%s\"" ($absHref | absURL) }}
  {{ $html = replace $html $href $absHref }}
{{ end }}

{{ $srcs := findRE "src=\"([^\"]*)\"" $html }}
{{ range $src := $srcs}}
  {{ $absSrc := strings.TrimPrefix "src=\"" $src  }}
  {{ $absSrc = strings.TrimSuffix "\"" $absSrc  }}
  {{ $absSrc = printf "src=\"%s\"" ($absSrc | absURL) }}
  {{ $html = replace $html $src $absSrc }}
{{ end }}

{{ $srcset := findRE "srcset=\"([^\"]*)\"" $html }}
{{ range $set := $srcset}}
  {{ $parts := strings.TrimPrefix "srcset=\"" $set  }}
  {{ $parts = strings.TrimSuffix "\"" $parts  }}
  {{ $parts = split $parts "," }}
  {{ $newSrcset := slice }}
  {{ range $part := $parts }}
    {{ $part = $part | replaceRE "^\\s*(.*)\\s*$" "$1" }}
    {{ $lg := split $part " " }}
    {{ $href := index $lg 0 | absURL }}
    {{ $size := index $lg 1 }}
    {{ $newSrcset = $newSrcset | append (printf "%s %s" $href $size) }}
  {{ end }}
  {{ $newSrcset = delimit $newSrcset ", " }}
  {{ $newSrcset = printf "srcset=\"%s\"" $newSrcset }}
  {{ $html = replace $html $set $newSrcset }}
{{ end }}

{{ return $html }}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;And make sure we call the partial when rendering our feed XML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;content:encoded&amp;gt;{{ "&amp;lt;![CDATA[" | safeHTML }} {{ partial "rss.html" . | safeHTML }}]]&amp;gt;&amp;lt;/content:encoded&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Another option is providing a "xml:base" URI and set it to the base URL of the site. Although not officially supported by W3C RSS 2.0, many readers and aggregators (reportedly) support it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0"?&amp;gt;
  &amp;lt;rss version="2.0" xml:base="http://example.com/sub"&amp;gt;
    &amp;lt;channel&amp;gt;
    .....
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;/Y.&lt;/p&gt;

</description>
      <category>blog</category>
      <category>staticsite</category>
    </item>
    <item>
      <title>Tips for keeping Azure Kubernetes Service costs down</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Thu, 20 Aug 2020 14:48:04 +0000</pubDate>
      <link>https://dev.to/yuriburger/tips-for-keeping-azure-kubernetes-service-costs-down-5cg9</link>
      <guid>https://dev.to/yuriburger/tips-for-keeping-azure-kubernetes-service-costs-down-5cg9</guid>
      <description>&lt;p&gt;Kubernetes is awesome, but can be a costly service to run. In many cases it pays off to take a look at the various components and settings to see if there are ways to save money. &lt;/p&gt;

&lt;h3&gt;
  
  
  How much can I save?
&lt;/h3&gt;

&lt;p&gt;The amount of money you can save depends on a lot of things: type of environment (staging, production), workload, application architecture, infrastructure design, etc. But before you dive into any of the tips, you will need to start with a baseline and document at least a couple of things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Last months bill&lt;/li&gt;
&lt;li&gt;Current billing period forecast&lt;/li&gt;
&lt;li&gt;Size of the Kubernetes cluster&lt;/li&gt;
&lt;li&gt;Current/ base load of the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any Cloud Vendor has tools for cost analysis, Azure has Cost Management and Billing providing accumulated reports and breakdowns by resource. This way you can get some insight in the total costs of your Kubernetes applications. You can use filters to include all resources related to your AKS cluster (any container registries, storage accounts, Log Analytics, dependent services like KeyVault, etc.).&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzuus3cffeo1jjngmza6v.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzuus3cffeo1jjngmza6v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you have some cool reports ready to show off the fruits of your cost saving labor :).&lt;/p&gt;

&lt;h3&gt;
  
  
  Tips
&lt;/h3&gt;

&lt;p&gt;Virtual Machines are the foundation the Azure Kubernetes Service runs on. Every AKS cluster node is a virtual machine in a "behind-the-scenes" scale set. And you get billed for every single one of them. If it is running that is. So the first three tips relate to the Virtual Machine Scale Set.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tip #1: Scale down
&lt;/h4&gt;

&lt;p&gt;Maybe you won't need the full power of the application 24/7? In that case, you can scale down or even completely shut down the Virtual Machine scaleset outside of operating hours. If you don't scale down completely, you will need to make sure you scale down the Kubernetes resources too. Otherwise Kubernetes will try to keep all the parts running on fewer nodes and you will most likely be flouded with Out Of Memory and deployment errors. &lt;/p&gt;

&lt;p&gt;Tip within a tip: instead of manually stopping a VM Scale Set, use Azure Automation or a (scheduled) Azure Function to automate this.&lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://docs.microsoft.com/en-us/azure/automation/automation-solution-vm-management" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/azure/automation/automation-solution-vm-management&lt;/a&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Tip #2: Reserved Savings
&lt;/h4&gt;

&lt;p&gt;Reserved Savings are available for certain VM series, so if you are going to run a cluster for a year or longer this could save a lot! You can check the Azure Calculator and see if reserved savings are applicable: &lt;a href="https://azure.microsoft.com/en-us/pricing/calculator" rel="noopener noreferrer"&gt;https://azure.microsoft.com/en-us/pricing/calculator&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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn6g63hmm1yp9l3yggp9d.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%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn6g63hmm1yp9l3yggp9d.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Tip #3: Mix 'n Match
&lt;/h4&gt;

&lt;p&gt;Maybe you don't need premium disks or compute optimized machines? Or specific GPU optimized nodes for certain Kubernetes applications or services but not all of them? In that case, multiple nodepools allow you to mix and match different VM types. Kubernetes "taints" and "tolerations" allow you to schedule the correct workload on the correct nodes. &lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://docs.microsoft.com/en-us/azure/aks/use-multiple-node-pools" rel="noopener noreferrer"&gt;https://docs.microsoft.com/en-us/azure/aks/use-multiple-node-pools&lt;/a&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Tip #4: Logging
&lt;/h4&gt;

&lt;p&gt;In many cases Kubernetes apps are based on distributed architectures and logging is done somewhere central. Maybe using Application Insights or a 3rd party solution. But handling large volumes of log entries requires substantial infrastructure and can become quite expensive. I have seen cases where the cost of distributed logging almost equals the cost of running the software! &lt;/p&gt;

&lt;p&gt;So make sure every loglevel is configurable (runtime), so you can silence most of the noise during normal operation. When troubleshooting, levels can be cranked up for short periods of time to enable all the detail needed. Also, log entries should be useful and meaningful. In distributed scenarios this means that you need to be able to correlate entries to allow for real event tracing.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tip #5: Memory usage
&lt;/h4&gt;

&lt;p&gt;Usually Kubernetes applications are cloud (native) applications. Maybe even full microservices. Yes, containers are more lightweight than VMs (they share the OS), but making sure applications run memory optimized is still very important and requires careful planning and tuning. In case of Kubernetes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick a Kubernetes optimized container image based on a container friendly OS;&lt;/li&gt;
&lt;li&gt;Define resource requests and limits for your Kubernetes deployments. If you don't do this, Kubernetes has no way to schedule them efficiently;&lt;/li&gt;
&lt;li&gt;If you run .NET Core, make sure you build the image using the SDK image, but run the application using the Runtime image (multi stage Docker build FTW);&lt;/li&gt;
&lt;li&gt;If you run Java, make sure you configure the JVM or try a container optimized JVM like OpenJ9;&lt;/li&gt;
&lt;li&gt;Or run your Java workload using a container optimized stack like Quarkus.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Migrating to Hugo</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Tue, 23 Jun 2020 07:32:35 +0000</pubDate>
      <link>https://dev.to/yuriburger/migrating-to-hugo-fm9</link>
      <guid>https://dev.to/yuriburger/migrating-to-hugo-fm9</guid>
      <description>&lt;p&gt;(Cover image credit: Kenneth Canning/Getty Images)&lt;/p&gt;

&lt;p&gt;So I finally decided to do it, after thinking about it at least a dozen times: I switched from using Wordpress to Hugo for my blog. I watched most of my online friends do it too the last couple of years. And all but a few ended up with a static generated site (with Jekyll, GitHub Pages or Hugo) but were pretty excited about the results. &lt;/p&gt;

&lt;p&gt;I also thought of all the great things a static generated site would have to offer, things like writing articles in MarkDown using my favorite editor (Visual Studio Code) and total control over the generated HTML output. The reason I hesitated was, that I thought it would be a lot of work not to mention all the great features I would be missing out on (the Wordpress plugins).&lt;/p&gt;

&lt;p&gt;It turned out to be pretty easy to do and can now also join my friends in excitement. Static sites rock.&lt;br&gt;
The only thing left for me to do is, making sure everything works as expected. And that is why sharing my migration notes on my new Hugo powered platform seems like the perfect thing to do right now. &lt;/p&gt;
&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;p&gt;Wanted to move from Wordpress to a static website. Picked Hugo and Azure Static Web Apps. Migrating content turned out easier than I thought, DNS a bit tougher. I can now use Visual Studio Code and Markdown for writing articles. Ended up with a blazingly fast website behind Cloudflare.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a static website framework&lt;/li&gt;
&lt;li&gt;Select a hosting provider&lt;/li&gt;
&lt;li&gt;Migrate content (text and media)&lt;/li&gt;
&lt;li&gt;Fix the links (permalinks and custom 404)&lt;/li&gt;
&lt;li&gt;Adjust DNS&lt;/li&gt;
&lt;li&gt;Enable Search&lt;/li&gt;
&lt;li&gt;Configure a new content workflow&lt;/li&gt;
&lt;li&gt;Trigger social media&lt;/li&gt;
&lt;li&gt;Configure analytics&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Static website framework
&lt;/h3&gt;

&lt;p&gt;The reason for me to look beyond Wordpress for my blogging site was not because I wanted to join my friends screaming with excitement. Well not the only reason. My basic needs were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improved workflow: I never liked writing my articles in the browser and usually prepared them in Microsoft Word or Notepad++. To be able to work with any editor and Markdown as the underlying language seemed real nice. Content as Code!&lt;/li&gt;
&lt;li&gt;Markdown: To be able to use it as a markup language with its plain-text formatting syntax. The reason is simply because of exit scenarios. As we will see later on, getting your own content out of a CMS can be quite challenging. Because of the minimum formatting, Markdown enables you to migrate your blog content with ease.&lt;/li&gt;
&lt;li&gt;Speed: I have a simple blogsite, nothing special. And somedays WP was just super slow and it took seconds to load my site. &lt;/li&gt;
&lt;li&gt;Theming: I am no CSS warrior and prefer to build on the great work of those who are.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Picking a framework can be hard, so I just evaluated a couple that seemed to fit the basic needs. My shortlist was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gatsbyjs.org"&gt;Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vuepress.vuejs.org"&gt;VuePress&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many articles on the web comparing these frameworks so if you want to know more about the differences, I suggest checking those out. My evaluation was simple: can I get a basic site up and running in under 10 minutes. As it turns out, all of them qualify :)&lt;/p&gt;

&lt;p&gt;In short: I went with Hugo, but mainly because of the available &lt;a href="https://themes.gohugo.io/"&gt;themes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rlGk0CWL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fxcd7lmuod7djxsfi6fa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rlGk0CWL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fxcd7lmuod7djxsfi6fa.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My next step was, getting to know the new platform. There is probably no better way than writing my first post (this one) to see if I can find my way around. But because I was also hosting my blog on wordpress.com, I also needed to look for a new provider.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hosting
&lt;/h3&gt;

&lt;p&gt;Not surprisingly hosting a static website is the most basic thing to do and every provider has support for HTML files and images. There are so many options out there, just pick one. The only thing to watch out for is how you can "upload" your content. If you only have legacy FTP, you might be missing out on all the great features other hosting options have.&lt;/p&gt;

&lt;p&gt;I picked Azure Static Web Apps, a new and shiny (also very much in preview) service from Microsoft.&lt;/p&gt;

&lt;p&gt;A note on Azure Static Web Apps:&lt;br&gt;
There is currently no support yet for "naked" (APEX) domains. This means that &lt;a href="https://www.yuriburger.net"&gt;https://www.yuriburger.net&lt;/a&gt; works out of the box, but &lt;a href="https://yuriburger.net"&gt;https://yuriburger.net&lt;/a&gt; does not. A workaround is available for the time being, but this involves Cloudflare. See later on this article for the details on that.&lt;/p&gt;
&lt;h3&gt;
  
  
  Content (the most important part of course)
&lt;/h3&gt;

&lt;p&gt;I had not that many blogposts to migrate, maybe 80 or so. And I choose to leave a big part of that content behind, because I considered them outdated and/or not relevant anymore. Still what was left, needed some form of automated migration. Luckily the Hugo website has some links on tools that can help you with this. As it turns out, these tools focus on exporting WP content to Markdown and can be used with other blog platforms as well.&lt;/p&gt;

&lt;p&gt;I choose to export the WP content (articles and media) and converted the text using the exitwp-for-hugo Python script.&lt;/p&gt;

&lt;p&gt;Next, I migrated (actually just copied) the converted files to the Hugo file structure. This was the part that I "feared" the most and it took only minutes.&lt;/p&gt;

&lt;p&gt;More info on the WP export scripts: &lt;a href="https://gohugo.io/tools/migrations/#wordpress"&gt;https://gohugo.io/tools/migrations/#wordpress&lt;/a&gt; &lt;/p&gt;
&lt;h3&gt;
  
  
  Perma links (fix all the old links)
&lt;/h3&gt;

&lt;p&gt;All that "old" links pointing to the old site? Usually not a problem, because most static site frameworks have a way to generate the same permalinks. In my case, I just had to make sure my articles have the correct (original) publish date.&lt;/p&gt;

&lt;p&gt;In Hugo, this is accomplished by adjusting the config.toml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[permalinks]
  blog = "/:year/:month/:day/:title/"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Boom, easy fix. Now I needed to write a custom 404 (Not Found) for the cases someone used an old link pointing to content that I left behind. In the case of static websites, it is not the framework (i.e. Hugo) that handles this, but the hosting platform. In my case, I needed to provide a custom 404.html and adding a "routes.json" to the generated output and make sure it was uploaded to my Azure Static Web App.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "routes": [
  ],
  "platformErrorOverrides": [
    {
      "errorType": "NotFound",
      "serve": "/404.html"
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;More info: &lt;a href="https://gohugo.io/templates/404/"&gt;https://gohugo.io/templates/404/&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS (downtime about to happen)
&lt;/h3&gt;

&lt;p&gt;I also hosted my DNS at Wordpress and that needed to change for various reasons. This was actually the hardest past, because it is tough to do without downtime. I wanted to move away from WP entirely so I transferred my domain to a new Registrar.&lt;/p&gt;

&lt;p&gt;AS I mentioned in the beginning, Azure Static Web Apps do not (yet) support "naked" (or APEX or root) domains. A fix is not that hard, but requires a little bit of DNS trickery. All written up very well, so if you are like me and just want to use Azure, check this out &lt;a href="https://burkeholland.github.io/posts/static-app-root-domain/"&gt;https://burkeholland.github.io/posts/static-app-root-domain/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;As a bonus, you will end up with a Cloudflare instance in front of your website. It speeds up your already fast website!&lt;/p&gt;

&lt;h3&gt;
  
  
  Search (yes, static sites can have search)
&lt;/h3&gt;

&lt;p&gt;Search was on my checklist, but I decided I could live without it. Most of my stuff can be found using categories and tags. Turns out, there are Hugo themes that support search. Even on a static web site and that is pretty neat! If you also picked Hugo, then check out this theme &lt;a href="https://github.com/pacollins/hugo-future-imperfect-slim"&gt;https://github.com/pacollins/hugo-future-imperfect-slim&lt;/a&gt; for an example.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2T4sGJL9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5v5pss6e9h7jp2f6ygfe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2T4sGJL9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5v5pss6e9h7jp2f6ygfe.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  New content workflow (GitHub Actions FTW!)
&lt;/h3&gt;

&lt;p&gt;Content as Code. This also means you can store your content in Azure DevOps, Bitbucket or GitHub. I choose the latter and setting up your content publishing workflow is now as easy as configuring a build pipeline. &lt;/p&gt;

&lt;p&gt;As it turns out, Azure Static Web Apps has an even easier option. It can configure a GitHub Action directly from the Azure Portal. It recognizes the Hugo filestructure and configures the build and release automatically. Very impressive!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FlSCiC_Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eugdqj8df05l2d4f486q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FlSCiC_Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/eugdqj8df05l2d4f486q.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More info here: &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/github-actions-workflow"&gt;https://docs.microsoft.com/en-us/azure/static-web-apps/github-actions-workflow&lt;/a&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger social media on new posts
&lt;/h3&gt;

&lt;p&gt;Turns out, the only WP plugin I used was for triggering social media on new posts. And Hugo has no built in support for this (because it is a static generated website). It requires daisy-chaining a couple of (free) tools to accomplish, but someone shared the struggle for us to copy: &lt;a href="https://dereckcurry.com/posts/automatically-tweeting-new-hugo-posts/"&gt;https://dereckcurry.com/posts/automatically-tweeting-new-hugo-posts/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;I decided not to use this for now, as I don't mind drafting up a tweet every now and then.&lt;/p&gt;

&lt;h3&gt;
  
  
  Analytics
&lt;/h3&gt;

&lt;p&gt;Wordpress Analytics --&amp;gt; Google Analytics. Not that hard, because Google Analytics have way more detail and features so no harm in switching Google for Wordpress. The frameworks I checked all had configurable support for Google Analytics.&lt;/p&gt;

&lt;p&gt;Thats it, at least it was for me. On the whole, it took me an evening and I think it was totally worth it. &lt;/p&gt;

&lt;p&gt;/Y.&lt;/p&gt;

&lt;p&gt;More info on Azure Static Web Apps (be careful, in preview):&lt;br&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/custom-domain"&gt;https://docs.microsoft.com/en-us/azure/static-web-apps/custom-domain&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blog</category>
      <category>staticsite</category>
    </item>
    <item>
      <title>A quick intro: Application Insights for your Angular Java stack</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Tue, 17 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/yuriburger/a-quick-intro-application-insights-for-your-angular-java-stack-6hk</link>
      <guid>https://dev.to/yuriburger/a-quick-intro-application-insights-for-your-angular-java-stack-6hk</guid>
      <description>&lt;p&gt;Finding out about performance issues and collecting metrics on the usage of your app is important. Add Application Insights to your apps and let the reports tell you what is wrong and allow you to track all kinds of dimensions.&lt;/p&gt;

&lt;p&gt;Microsoft provides several SDK’s for this, for all types of frameworks and languages. In this post, we will look at how to set this up for a typical Java (Spring Boot) API and an Angular Single Page Application. Although you can easily monitor each component separately, we also show how to enable distributed telemetry correlation. This allows you to aggegrate frontend and backend metrics in a single query, see next section!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/17/a-quick-intro-application-insights-for-your-angular-java-stack/images/image.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rvLVu-mz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/17/a-quick-intro-application-insights-for-your-angular-java-stack/images/image.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Correlation
&lt;/h4&gt;

&lt;p&gt;A special note on correlation. From the docs:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Application Insights defines a &lt;a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/data-model"&gt;data model&lt;/a&gt; for distributed telemetry correlation. To associate telemetry with a logical operation, every telemetry item has a context field called &lt;code&gt;operation_Id&lt;/code&gt;. This identifier is shared by every telemetry item in the distributed trace. So even if you lose telemetry from a single layer, you can still associate telemetry reported by other components.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A distributed logical operation typically consists of a set of smaller operations that are requests processed by one of the components. These operations are defined by &lt;a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/data-model-request-telemetry"&gt;request telemetry&lt;/a&gt;. Every request telemetry item has its own &lt;code&gt;id&lt;/code&gt; that identifies it uniquely and globally. And all telemetry items (such as traces and exceptions) that are associated with the request should set the &lt;code&gt;operation_parentId&lt;/code&gt; to the value of the request &lt;code&gt;id&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This means, that if you enable distributed tracing correctly, you should always be able to correlate telemetry from different sources and to navigate from a distributed logical operation to the individual operations. This is going to save us a lot of time, no more plowing through logfiles!&lt;/p&gt;

&lt;h4&gt;
  
  
  Our sample scenario
&lt;/h4&gt;

&lt;p&gt;Our scenario is pretty straightforward: an Angular frontend application that connects to a Spring Boot REST backend application. Although we do not include any backend microservices or other complex parts, it is still considered a distributed environment. The approach we are going to use can easily be extended to more complex architectures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/17/a-quick-intro-application-insights-for-your-angular-java-stack/images/image-1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BNzwWeOR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/17/a-quick-intro-application-insights-for-your-angular-java-stack/images/image-1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we want to send both exceptions and telemetry to Application Insights.&lt;/p&gt;

&lt;h4&gt;
  
  
  The app
&lt;/h4&gt;

&lt;p&gt;Starting with the Angular app, we need to add the Javascript SDK. You can just follow the instructions from the GitHub guide: &lt;a href="https://github.com/microsoft/ApplicationInsights-JS"&gt;https://github.com/microsoft/ApplicationInsights-JS&lt;/a&gt; but for our sample app the instructions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the library: npm i –save @microsoft/applicationinsights-web&lt;/li&gt;
&lt;li&gt;Add the instrumentation key from Azure Application Insights to the config file /assets/config/config.develop.json&lt;/li&gt;
&lt;li&gt;configure the distributed tracing mode&lt;/li&gt;
&lt;li&gt;set the cloud role tag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this is a Single Page App, it relies on Angular routing for the navigation. To be able to pickup the route changes, the Application Insights integration is done with the help of a service that subscribes to the routers ResolveEnd event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private appInsights = new ApplicationInsights({
    config: {
      instrumentationKey: AppConfig.settings.instrumentation,
      distributedTracingMode: DistributedTracingModes.W3C,
      disableCorrelationHeaders: false,
      enableCorsCorrelation: true
    }
  });

constructor(private router: Router) {
    this.appInsights.loadAppInsights();

    this.appInsights.addTelemetryInitializer(envelope =&amp;gt; {
      envelope.tags\["ai.cloud.role"\] = "app";
    });

    this.router.events
      .pipe(filter(event =&amp;gt; event instanceof ResolveEnd))
      .subscribe((event: ResolveEnd) =&amp;gt; {
        const activatedComponent = this.getActivatedComponent(event.state.root);
        if (activatedComponent) {
          this.trackPageView(
            \`${activatedComponent.name} ${this.getRouteTemplate(
              event.state.root
            )}\`,
            event.urlAfterRedirects
          );
          this.appInsights.flush(); // Debug -&amp;gt; don't wait for browser to close
        }
      });
  }

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



&lt;p&gt;Full source code: &lt;a href="https://github.com/yuriburger/monitoring-demo-app"&gt;https://github.com/yuriburger/monitoring-demo-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A couple of notes on this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This code subscribes to the ResolveEnd event and uses some private methods to get the activated component and path that was used to match (see this blogpost &lt;a href="https://dev.to/azure/using-azure-application-insights-with-angular-5-7-4kej"&gt;https://dev.to/azure/using-azure-application-insights-with-angular-5-7-4kej&lt;/a&gt; for more information)&lt;/li&gt;
&lt;li&gt;The distributed tracing method is set to W3C. The Applications Insights team is switching from the MS specific format (AI) to the more standardized W3C Trace Context ( more info on this &lt;a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/correlation#correlation-headers"&gt;https://docs.microsoft.com/en-us/azure/azure-monitor/app/correlation#correlation-headers&lt;/a&gt; )&lt;/li&gt;
&lt;li&gt;The Instrumentation Key is gathered from the configuration service, please see the full source code for more info on this.&lt;/li&gt;
&lt;li&gt;Normally the App Insights data is sent to MS when the browser closes. We added the appInsights.flush() to do this on every route change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The backend
&lt;/h4&gt;

&lt;p&gt;The backend is based on a simple Sprint Boot application with a REST controller. In the dependencies we enable Application Insights with the help of a starter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.microsoft.azure&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;applicationinsights-spring-boot-starter&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;2.5.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

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



&lt;p&gt;Set the correct properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;azure.application-insights.instrumentation-key=#{INSTRUMENTATION\_KEY}#
spring.application.name=api

#Enable W3C distributed tracing support for Java apps
azure.application-insights.web.enable-W3C=true
azure.application-insights.web.enable-W3C-backcompat-mode=false

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



&lt;p&gt;And we enable some telemetry in one of the Controller classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@RestController
@CrossOrigin(origins = "\*")
@RequestMapping(path = "/info")
public class InfoController {
    @Autowired
    TelemetryClient telemetryClient;

    @GetMapping(path = "", produces = "application/json")
    public ResponseEntity&amp;lt;Object&amp;gt; getInfo() {
        telemetryClient.trackEvent("Info requested...");
        return new ResponseEntity&amp;lt;&amp;gt;("{\\"version\\": \\"1.0\\"}", HttpStatus.OK);
    }
}

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



&lt;p&gt;Full source code: &lt;a href="https://github.com/yuriburger/monitoring-demo-api"&gt;https://github.com/yuriburger/monitoring-demo-api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the properties file to include the correct Instrumentation Key (/src/main/resources/application.properties)&lt;/li&gt;
&lt;li&gt;We send some custom data to App Insights with the trackEvent, but we can add other metrics as well&lt;/li&gt;
&lt;li&gt;We distributed tracing method is set to W3C without compatibility (see &lt;a href="https://docs.microsoft.com/en-us/azure/azure-monitor/app/correlation#correlation-headers"&gt;https://docs.microsoft.com/en-us/azure/azure-monitor/app/correlation#correlation-headers&lt;/a&gt; for more info)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The result
&lt;/h4&gt;

&lt;p&gt;When we run the app and the backend, logs should accumulate in Application Insights. You need to give it a few minutes, but this should be the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(requests
| union dependencies
| union pageViews
| union customEvents)
| where operation\_Id == "76ee5b17521b4635b0f745f2993fa60d"
| project timestamp, itemType, name, id, operation\_ParentId, operation\_Id

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



&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/17/a-quick-intro-application-insights-for-your-angular-java-stack/images/image-3.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3h0X0fE0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/17/a-quick-intro-application-insights-for-your-angular-java-stack/images/image-3.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we now query on operation_Id, we should get all the info in one result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pageView is from the Angular app, specifically the routing event.&lt;/li&gt;
&lt;li&gt;The dependency is from the “microsoft/applicationinsights-web” npm package, it auto-collected the outgoing XHR request.&lt;/li&gt;
&lt;li&gt;Request is coming from the Spring Boot metrics, also auto-collected and correlated using the W3C tracing headers.&lt;/li&gt;
&lt;li&gt;The Custom Event is from the InfoController in Spring Boot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;/Y.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>applicationinsights</category>
      <category>azure</category>
      <category>java</category>
    </item>
    <item>
      <title>Canary deployments with just Azure DevOps</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Mon, 09 Dec 2019 12:48:00 +0000</pubDate>
      <link>https://dev.to/yuriburger/canary-deployments-with-just-azure-devops-2gg6</link>
      <guid>https://dev.to/yuriburger/canary-deployments-with-just-azure-devops-2gg6</guid>
      <description>&lt;p&gt;Canary deployments with just Azure DevOps. Well, with Azure DevOps and Azure Kubernetes Service. But that’s it, no additional software required.&lt;/p&gt;

&lt;p&gt;Although there are plenty 3rd party solutions out there that address canary deployments, you might just get it done using “just” Azure DevOps. And in some cases, less is actually more! But to be fair, these other solutions provide a more holistic approach when it comes to deploying cloud native apps. If you are interested in those, these are the ones I know/ worked with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Spinnaker: &lt;a href="https://www.spinnaker.io"&gt;https://www.spinnaker.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Harness: &lt;a href="https://harness.io"&gt;https://harness.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Istio: &lt;a href="https://istio.io"&gt;https://istio.io&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you just want to get some control on releasing new functionality in a modern cloud-native fashion, you can read on 🙂&lt;/p&gt;

&lt;p&gt;Canary releases allow you to expose new code to a small group within the app’s “population”. It requires you to examine the canary carefully and look for unexpected behavior. If nothing hints at the app malfunctioning, you can decide to cut-over and roll out the new code to the entire group.&lt;/p&gt;

&lt;h3&gt;
  
  
  The backstory on the bird
&lt;/h3&gt;

&lt;p&gt;For those interested, canary releases get their name from a technique that was used in the mining industry. Miners would carry a canary in a small cage to spot for mine-gas (mostly Methane). If the bird dropped, they would evacuate in fear of explosions and “air” the mine shafts before returning to their job.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/canary1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yejEJCRb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/canary1.png" alt="Resuscitation device for the canary"&gt;&lt;/a&gt;Resuscitation device for the canary&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment scenario
&lt;/h3&gt;

&lt;p&gt;The concept is simple: expose new code to a small percentage of the users. If the new code doesn’t fail, expose all users. With deployments (and in our case docker images, pods and Kubernetes in mind) it could look something like this:&lt;/p&gt;

&lt;p&gt;First, all users are on the “left”, then a canary is deployed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/picture1-2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u-nWSEGE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/picture1-2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After canary analyses, the group is switched-over:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/picture3-4.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B_MirKTb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/picture3-4.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our app will be deployed as a docker image on a managed Kubernetes cluster. So we need an Azure Container Registry and an Azure Kubernetes Service. These components don’t require any special setup, but for the deployment of our app we need to make sure we have them in place.&lt;/p&gt;

&lt;p&gt;Within Kubernetes we usually end up with an Ingress type of setup. In the following example, we have both an app and a backend api running behind an Ingress Controller:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-4.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uvit1zZS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-4.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup ensures that incoming traffic for the App and Api gets routed through Kubernetes services to the pods running the containers. The way this works is that the Ingress rules match an incoming Request URI (a.k.a. path) on a Service name and the Service selects the pods based on the labels:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-2.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6pZe9hQk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we now create two deployments, one for the stabel release and one for the canary release, we can have Azure DevOps control both from a DevOps Release Pipeline. We start with one replica on the stable track (in real life there would probably be multiple replicas) and zero replicas on the canary track. Because both deployments have the “app=api” label, both are considered valid endpoints for the api-service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-3.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DZ4Fg3FZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-3.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Azure DevOps Release Pipeline&lt;/p&gt;

&lt;p&gt;The multi-stage release pipeline can now control our Kubernetes deployments. We create 2 stages: Canary and Stable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-5.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rnq93pzx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-5.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both stages have 2 tasks: one for setting the correct image and one for scaling the deployment. These are standard Azure DevOps Kubernetes Tasks:&lt;/p&gt;

&lt;p&gt;Canary stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: Kubernetes@0
  displayName: 'Set Canary Image'
  inputs:
    kubernetesServiceConnection: 'wherefore-aks'
    namespace: default
    command: set
    arguments: 'image deployments/api-canary api=wherefore.azurecr.io/wherefore-art-thou-api:$(Release.Artifacts._api.BuildId) --record'

- task: Kubernetes@0
  displayName: 'Scale Canary 1'
  inputs:
    kubernetesServiceConnection: 'wherefore-aks'
    namespace: default
    command: scale
    arguments: 'deployments/api-canary --replicas=1'

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



&lt;p&gt;As you can see, we set the correct image for our api-canary deployment (in this example the artifact build ID is used for the image tag) and scale the canary deployment (api-canary) to 1 replica.&lt;/p&gt;

&lt;p&gt;The Stable stage is just the opposite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: Kubernetes@0
  displayName: 'Set Deployment Image'
  inputs:
    kubernetesServiceConnection: 'wherefore-aks'
    namespace: default
    command: set
    arguments: 'image deployments/api-deployment api=wherefore.azurecr.io/wherefore-art-thou-api:$(Release.Artifacts._api.BuildId) --record'

- task: Kubernetes@0
  displayName: 'Scale Canary 0'
  inputs:
    kubernetesServiceConnection: 'wherefore-aks'
    namespace: default
    command: scale
    arguments: 'deployments/api-canary --replicas=0'

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



&lt;p&gt;We set the correct image for our api-deployment and scale the canary deployment (api-canary) down to 0 replicas. One important missing piece is the pre-deployment approval for the Stable stage. If we do not enable this, the Canary deployment would be practically useless as the Release would immediately go through to the Stable stage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-6.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pjZn89VS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/afbeelding-6.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we now create a new release, things should work as expected. The release is waiting on our Stable stage pre-deployment and in Kubernetes we have two deployments for our Api: one api-deployment and one api-canary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/release1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5sGKn72w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2019/12/09/canary-deployments-with-just-azure-devops/images/release1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we approve the deployment, the api-deployment would be upgraded with the new image and the api-canary would be scaled down again: Canary deployment with just Azure DevOps 😉&lt;/p&gt;

&lt;p&gt;/Y.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>release</category>
    </item>
    <item>
      <title>Developing Java on Azure</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Mon, 03 Dec 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/yuriburger/developing-java-on-azure-ggc</link>
      <guid>https://dev.to/yuriburger/developing-java-on-azure-ggc</guid>
      <description>&lt;p&gt;Part 2! In a &lt;a href="https://www.yuriburger.net/2018/11/05/running-java-on-azure/"&gt;previous post&lt;/a&gt;, I discussed the options for running Java workloads on Azure. Now it is time to discover some of the choices Azure offers to our software engineers!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/javalovesazure11.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2LP6CTxs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/javalovesazure11.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Developing Java applications
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Sourcecode
&lt;/h4&gt;

&lt;p&gt;Application consist of source code that needs to be centrally stored and managed. Azure has many options for storing sourcecode, but also for working with external repositories. Although not something very specific for Java projects, still good to know Azure supports your favourite repository flavour!&lt;/p&gt;

&lt;p&gt;Azure DevOps Git Repos&lt;/p&gt;

&lt;p&gt;Azure DevOps (yes, this is the new name for VSTS f.k.a. Visual Studio Online) supports Git for your projects code repositories. Upon creation of a new Azure DevOps project, you are presented with some options for configuring the included Git repo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/git.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G6gaF0PI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/git.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Integration with GitHub, Gitlabs and others&lt;/p&gt;

&lt;p&gt;Besides its own repositories, Azure has some great support for other flavours. If you configure a build pipeline for instance, you can pick from popular choices:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/repos.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KO5osce5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/repos.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  MongoDB
&lt;/h4&gt;

&lt;p&gt;Our favourite document based database is also available on Azure. You can of course run your own VM or docker image, but Azure also offers a MongoDB as a Service. This service is called Azure Cosmos DB and is Microsoft’s globally distributed, multi-model database service. Azure Cosmos DB provides global distribution, elastic scaling and very low latencies with very high availability. And it comes with 5 different APIs including a MongoDB API.&lt;/p&gt;

&lt;h4&gt;
  
  
  MySQL
&lt;/h4&gt;

&lt;p&gt;The popular relational database. Again, we can run this with our own VM or docker image, but it is also available as a managed service.&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure DevOps
&lt;/h4&gt;

&lt;p&gt;Azure DevOps is Microsofts collection of developer tools, cloud services and CI/CD pipelines. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/devops.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lkmtccbG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/devops.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Boards: Work Item Tracking&lt;/li&gt;
&lt;li&gt;Azure Repos: the Git repos mentioned above&lt;/li&gt;
&lt;li&gt;Azure Pipelines: build, test, deploy with CI/CD&lt;/li&gt;
&lt;li&gt;Azure Test Plans: test tools&lt;/li&gt;
&lt;li&gt;Azure Artifacts: package, share and ship your software&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although (again) not very specific for our Java projects, it is perfectly suitable. It supports continuous integration (CI) and continuous delivery (CD) pipelines for our Java apps in Azure out of the box.&lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://azure.microsoft.com/en-us/services/devops/"&gt;https://azure.microsoft.com/en-us/services/devops/&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Jenkins
&lt;/h4&gt;

&lt;p&gt;If Jenkins is your CI tool of choice, you can easily integrate it with Azure DevOps and still gain alot of benefits from the Azure tools. This way you can keep Jenkins for your CI builds and use Azure for the (continuous) deployments!&lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/integrate-jenkins-vsts-cicd?view=vsts"&gt;https://docs.microsoft.com/en-us/azure/devops/pipelines/release/integrate-jenkins-vsts-cicd?view=vsts &lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Maven
&lt;/h4&gt;

&lt;p&gt;Microsoft provides a Maven Plugin for the Azure App Service. It provides seamless integration into Maven projects, and makes it easier for developers to deploy to different kinds of Azure Web Apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-web-overview"&gt;Web App on Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/azure/app-service-web/app-service-linux-intro"&gt;Web App on Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/app-service/containers/tutorial-custom-docker-image"&gt;Web App for Containers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More info: &lt;a href="https://github.com/Microsoft/azure-maven-plugins/tree/develop/azure-webapp-maven-plugin"&gt;https://github.com/Microsoft/azure-maven-plugins/tree/develop/azure-webapp-maven-plugin&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Pipelines
&lt;/h4&gt;

&lt;p&gt;Azure Pipelines is part of the Azure DevOps tools, but also available standalone if that is all you need. &lt;a href="https://go.microsoft.com/fwlink/?linkid=2021416"&gt;Azure Pipelines&lt;/a&gt; offers cloud-hosted pipelines for Linux, macOS, and Windows with 10 free parallel jobs and unlimited minutes for open source projects.&lt;/p&gt;

&lt;p&gt;If your source code resides on GitHub, it is even easier to get started with CD/CD! If you browse the GitHub CI Marketplace, you will find a plan to integrate with Azure in just a few clicks! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/githubmarketplace.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u0XAF7MK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/12/03/developing-java-on-azure/images/githubmarketplace.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://github.com/marketplace/azure-pipelines"&gt;https://github.com/marketplace/azure-pipelines&lt;/a&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Plugins
&lt;/h4&gt;

&lt;p&gt;There are several plugins available for IntelliJ and Eclipse that can really boost your Azure workflow: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Azure Toolkit for IntelliJ provides templates and functionality that allow you to easily create, develop, test, and deploy cloud application to Azure.&lt;/li&gt;
&lt;li&gt;Azure Toolkit for Eclipse provides the same functionality, but for the Eclipse IDE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See &lt;a href="https://github.com/microsoft/azure-tools-for-java"&gt;https://github.com/microsoft/azure-tools-for-java&lt;/a&gt; for more information on how to set this up.&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Functions
&lt;/h4&gt;

&lt;p&gt;And to end this post, a Azure Service that natively supports Java: Azure Functions. Azure Functions are an easy way to run small pieces of code, or “functions,” in the cloud (think “AWS Lambda”). Functions is a solution for processing data, integrating systems, working with the internet-of-things (IoT), and building simple APIs and microservices. You can run them through triggers or on a schedule. It includes support for many languages, including Java!&lt;/p&gt;

&lt;p&gt;More info: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-java"&gt;https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-java&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  But wait, there’s still more!
&lt;/h4&gt;

&lt;p&gt;Microsoft has a lot of options for running and developing Java based workloads on Azure, but this is still not not the entire story ;). There are many, many more services that support Java in one way or another, but we will leave them for another time. In one more follow up post I will be reviewing options for operating your custom Java solutions on Azure.&lt;/p&gt;

&lt;p&gt;/Y.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>java</category>
    </item>
    <item>
      <title>Running Java on Azure</title>
      <dc:creator>Yuri Burger</dc:creator>
      <pubDate>Mon, 05 Nov 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/yuriburger/running-java-on-azure-1jn0</link>
      <guid>https://dev.to/yuriburger/running-java-on-azure-1jn0</guid>
      <description>&lt;p&gt;Azure is Microsofts cloud platform. It is the home of Service Apps, Logic Apps, cloud storage, Kubernetes Service and provides the foundation for VSTS (now Azure DevOps), Office 365 and loads of other services and tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/11/05/running-java-on-azure/images/javalovesazure1.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QuFfc1sr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/11/05/running-java-on-azure/images/javalovesazure1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But not only for .NET based services and applications. Todays Microsoft provides options for Linux developers, OSX teams, Docker containers, Python code, Node.js and many more different workloads. In this article series I will explore some of the Azure services and options for Java based solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running Java applications
&lt;/h3&gt;

&lt;p&gt;The best part about Azure (or any other cloud platform) is the capability for running your workloads. For many different reasons the cloud has pushed companies away from running their own datacenters and in to the cloud. So what are your options for running Java applications on Microsoft Azure?&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Web Apps
&lt;/h4&gt;

&lt;p&gt;Officially known by its full name Azure App Service Web Apps  is a service for hosting web applications, REST APIs, and mobile back ends. You can develop in your favorite language, be it .NET, .NET Core, Java, Ruby, Node.js, PHP, or Python. This platform comes in two flavours: Windows based and Linux based. You use App Service on Linux to host web apps natively on Linux for Java (currently 8.0), Spring based and Apache Tomcat  applications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/11/05/running-java-on-azure/images/java-hello-world-in-browser.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_JeBWGC3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/11/05/running-java-on-azure/images/java-hello-world-in-browser.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is a lot of information on this and Microsoft even created different walkthroughs for both &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-web-get-started-java"&gt;Eclipse&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/java/azure/intellij/azure-toolkit-for-intellij-create-hello-world-web-app?view=azure-java-stable"&gt;IntelliJ&lt;/a&gt;!&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker with Web Apps for containers
&lt;/h4&gt;

&lt;p&gt;If your application runtime dependencies are not supported on the default App Service for Linux, you can also deploy/ create one using your custom built Docker image. You can pull your container images from Docker Hub or a private Azure Container Registry and Web App for Containers will deploy the containerized app with your preferred dependencies to production in seconds. The platform automatically takes care of OS patching, capacity provisioning, and load balancing. More information, try this &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/containers/quickstart-java"&gt;Quickstart for Java&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Container Instances
&lt;/h4&gt;

&lt;p&gt;Azure Container Instances offer a fast and simple way of running single containers in Azure. No virtual machine hassle, no OS to manage and no orchestration. This service is best suited for small applications. You can deploy your container using bash (CLI), the Azure portal or using PowerShell. More information, try this &lt;a href="https://docs.microsoft.com/en-us/azure/container-instances/container-instances-tutorial-prepare-app"&gt;tutorial&lt;/a&gt; on creating a container for deployment.&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Kubernetes Service
&lt;/h4&gt;

&lt;p&gt;Full blown managed hosted Kubernetes. As a hosted Kubernetes service, Azure handles critical tasks like health monitoring and maintenance for you. The Kubernetes masters are managed by Azure. You only manage and maintain the agent nodes. This option is suited for all types of applications: single node, multi node, clustered, mission critical, micro service based, auto scaling, etc. More info on the &lt;a href="https://docs.microsoft.com/en-us/azure/aks/"&gt;product page&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Market Place
&lt;/h4&gt;

&lt;p&gt;A quick search on the Java based products available through the &lt;a href="https://azuremarketplace.microsoft.com/en-us/marketplace/apps?search=java&amp;amp;page=1"&gt;Azure Market Place&lt;/a&gt; reveals a lot of options for running Java on Azure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.yuriburger.net/2018/11/05/running-java-on-azure/images/javaazuremarketplace.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_c3RxgMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.yuriburger.net/2018/11/05/running-java-on-azure/images/javaazuremarketplace.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Azure Databricks using Data Factory
&lt;/h4&gt;

&lt;p&gt;Ok, this is something completely different. Azure Data Factory is Microsofts cloud solution for orchestrating data pipelines. Consider it your data ETL as a service :) Azure Databricks is Microsofts Apache Spark–based analytics service. &lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b5bhZG5n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://allthingssharepoint.files.wordpress.com/2018/11/azure-databricks-overview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b5bhZG5n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://allthingssharepoint.files.wordpress.com/2018/11/azure-databricks-overview.png" alt="azure-databricks-overview"&gt;&lt;/a&gt; So, lets say you have Java code compiled as a .jar for certain tasks in your data pipeline (e.g. data classification), you can deploy this using Azure Data Factory on Azure Databricks. If you are into large-scale data processing, this is a great addition to your toolset. More info: &lt;a href="https://docs.microsoft.com/en-us/azure/azure-databricks/what-is-azure-databricks"&gt;https://docs.microsoft.com/en-us/azure/azure-databricks/what-is-azure-databricks &lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  But wait, there’s more!
&lt;/h4&gt;

&lt;p&gt;Microsoft has a lot of options for running Java based workloads on Azure, but this is certainly not the entire story. In two follow up posts I will be reviewing options for developing and operating Java solutions on Azure.&lt;/p&gt;

&lt;p&gt;/Y.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>java</category>
    </item>
  </channel>
</rss>
