<?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[odedia's corner]]></title><description><![CDATA[Welcome to odedia's corner.
These are my thoughts, rants, and geeky projects.]]></description><link>https://odedia.org</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1644498712642/9n9bo8Hz1.png</url><title>odedia&apos;s corner</title><link>https://odedia.org</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 11:58:37 GMT</lastBuildDate><atom:link href="https://odedia.org/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AI Meets Spring Petclinic: Implementing an AI Assistant with Spring AI]]></title><description><![CDATA[I recently added an AI sample to the famous Spring Petclinic sample application, to showcase the power of Spring AI.
I also wrote an article explaining how I achieved this. It’s been published on the spring.io blog.
The article is split into two part...]]></description><link>https://odedia.org/spring-ai-meets-spring-petclinic</link><guid isPermaLink="true">https://odedia.org/spring-ai-meets-spring-petclinic</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><category><![CDATA[AI]]></category><category><![CDATA[generative ai]]></category><category><![CDATA[openai]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Sun, 29 Sep 2024 15:02:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727622091963/9919e14e-728e-4fad-b8b1-334bf0fffc0b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently added an AI sample to the famous <a target="_blank" href="https://github.com/spring-projects/spring-petclinic/tree/spring-ai">Spring Petclinic</a> sample application, to showcase the power of Spring AI.</p>
<p>I also wrote an article explaining how I achieved this. It’s been published on the <a target="_blank" href="https://spring.io/blog">spring.io</a> blog.</p>
<p>The article is split into two parts. The first section provides an overview and shows the use of Function Calling. The second part is all about Retrieval-Augmented Generation (RAG).</p>
<p>Links below. Happy reading!</p>
<h3 id="heading-ai-meets-spring-petclinic-implementing-an-ai-assistant-with-spring-ai-part-ihttpsspringioblog20240926ai-meets-spring-petclinic-implementing-an-ai-assistant-with-spring-ai-part-i"><a target="_blank" href="https://spring.io/blog/2024/09/26/ai-meets-spring-petclinic-implementing-an-ai-assistant-with-spring-ai-part-i"><strong>AI Meets Spring Petclinic: Implementing an AI Assistant with Spring AI (Part I)</strong></a></h3>
<h3 id="heading-ai-meets-spring-petclinic-implementing-an-ai-assistant-with-spring-ai-part-iihttpsspringioblog20240927ai-meets-spring-petclinic-implementing-an-ai-assistant-with-spring-ai-part"><a target="_blank" href="https://spring.io/blog/2024/09/27/ai-meets-spring-petclinic-implementing-an-ai-assistant-with-spring-ai-part"><strong>AI Meets Spring Petclinic: Implementing an AI Assistant with Spring AI (Part II)</strong></a></h3>
]]></content:encoded></item><item><title><![CDATA[Seinfeld - The AI Episode]]></title><description><![CDATA[I recently read an article with the headline “Why AI will never write a good Seinfeld episode”.
Oh, really?
Obviously, it immediately got me interested.
The response was so convincing and hilarious that I just had to share it.
I decided to use the Se...]]></description><link>https://odedia.org/seinfeld-the-ai-episode</link><guid isPermaLink="true">https://odedia.org/seinfeld-the-ai-episode</guid><category><![CDATA[chatgpt]]></category><category><![CDATA[AI]]></category><category><![CDATA[generative ai]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Mon, 23 Sep 2024 16:49:08 GMT</pubDate><content:encoded><![CDATA[<p>I recently read an article with the headline “Why AI will never write a good Seinfeld episode”.</p>
<p>Oh, really?</p>
<p>Obviously, it immediately got me interested.</p>
<p>The response was so convincing and hilarious that I just had to share it.</p>
<p>I decided to use the Seinfeld storyline that was used on the seventh season of “Curb Your Enthusiasm״, where they wrote a script in which George became a millionaire by developing an iPhone App that finds the nearest public restroom in your area - the iToilet (😂).</p>
<p>Without further ado, I present to you ChatGPT 4o’s Seinfeld episode: <em>“The Toilet Whisperer”</em>.</p>
<hr />
<p><strong>User Prompt:</strong> Create a Seinfeld episode about George trying to create an AI chatbot that finds the nearest free toilet in your vicinity.</p>
<hr />
<p><strong>Seinfeld Episode: "The Toilet Whisperer"</strong></p>
<hr />
<p><strong>[Opening Scene: Jerry's Apartment]</strong></p>
<p><em>Jerry is standing at the kitchen counter, eating cereal. George bursts in, frantically waving his hands.</em></p>
<p><strong>George:</strong><br />Jerry! You won’t believe it. I’ve done it. I’m going to be rich!</p>
<p><strong>Jerry:</strong><br />Rich? You can’t even figure out how to split the check at Monk’s, and now you’re rich?</p>
<p><strong>George:</strong><br />I’m serious this time. I’ve created an AI chatbot that finds the nearest free toilet in your vicinity!</p>
<p><strong>Jerry:</strong><br />A toilet-finding chatbot?</p>
<p><strong>George:</strong><br />Yes! Think about it. How many times have you been out and had to... you know, <em>go</em>? But there's nowhere to go!</p>
<p><strong>Jerry:</strong><br />Never really a problem for me, George. I go before I leave the house.</p>
<p><strong>George:</strong><br />Not everyone has your precision timing, Jerry! Some of us live on the edge, always one wrong sip of coffee away from disaster!</p>
<p><strong>Jerry:</strong><br />You want an AI to find bathrooms for you?</p>
<p><strong>George:</strong><br />Not just me! For everyone! I call it "Toilet Whisperer." You just type in where you are, and boom! Nearest, free, clean toilet. It’s brilliant!</p>
<p><em>Kramer barges in, sliding across the floor with his usual flair.</em></p>
<p><strong>Kramer:</strong><br />Hey, what's up, boys?</p>
<p><strong>Jerry:</strong><br />George is revolutionizing public restroom access with artificial intelligence.</p>
<p><strong>Kramer:</strong><br />Hoo-hoo! Now you’re talkin’! You know, George, I’ve spent a fortune finding the right facilities. Some of them even charge you now! It's an epidemic.</p>
<p><strong>George:</strong><br />Exactly! That’s the whole idea! And get this—every time someone uses the app and leaves a review, it gets smarter. It knows which toilets are clean, which ones have those dreaded hand dryers instead of paper towels, which ones are... <em>occupied.</em></p>
<p><strong>Kramer:</strong><br />What about toilet paper, huh? You could rate the softness, maybe even rank the plies!</p>
<p><strong>George:</strong><br />Oh, I’m way ahead of you, Kramer. We’ll have a "paper status" feature. Two-ply? One-ply? No-ply? The people will know!</p>
<p><strong>Jerry:</strong><br />So how does this... "Toilet Whisperer" work? You think people are just gonna input bathroom reviews while they’re running for their lives?</p>
<p><strong>George:</strong><br />Of course! It’ll be like Yelp, but for... <em>relief</em>!</p>
<hr />
<p><strong>[Cut to Monk’s Café]</strong></p>
<p><em>George is fiddling with his phone while Jerry and Elaine watch him.</em></p>
<p><strong>Elaine:</strong><br />An AI chatbot for toilets? Really, George? That’s what you’re putting out into the world?</p>
<p><strong>George:</strong><br />You mock, Elaine, but you wait. One day you’ll be in desperate need, and <em>Toilet Whisperer</em> will be there for you. And you'll thank me.</p>
<p><strong>Elaine:</strong><br />So, what happens if, say, there’s a line at the nearest bathroom? Or it’s closed for cleaning?</p>
<p><strong>George:</strong><br />I’ve got contingencies! The app will redirect you to the next available toilet! It even calculates the urgency of your situation based on how long you’ve been looking.</p>
<p><em>Elaine bursts out laughing.</em></p>
<p><strong>Elaine:</strong><br />Oh, please! What, does it send out a red alert if you’ve had too much coffee?</p>
<p><strong>George:</strong><br />Why not?!</p>
<hr />
<p><strong>[Cut to Jerry’s Apartment]</strong></p>
<p><em>Kramer is sitting on Jerry’s couch, scrolling through his phone. George walks in looking disheveled.</em></p>
<p><strong>Kramer:</strong><br />Oh man, George, the app... it’s blowing up! People love it. I just got a five-star review for a bathroom in Union Square.</p>
<p><strong>George:</strong><br />(Exhausted)<br />It’s a nightmare, Kramer. I’ve had to map every single toilet in the city. I’m talking cafes, parks, those weird basement restrooms in department stores... I’m practically living in bathrooms!</p>
<p><strong>Jerry:</strong><br />So now you’re some sort of... toilet detective?</p>
<p><strong>George:</strong><br />(Frustrated)<br />It’s harder than you think! And then there’s the bots! My AI is out there searching, learning... but it’s getting confused! It thinks Starbucks is one giant public restroom! And don't get me started on those porta-potties during street fairs. The algorithm can't handle the smell index!</p>
<p><em>Kramer gets up, excited.</em></p>
<p><strong>Kramer:</strong><br />Hey, George, what if I told you about an <em>exclusive</em> bathroom only a few know about? It’s in the basement of this hotel—pristine, always empty, marble floors, a door that <em>locks</em>!</p>
<p><strong>George:</strong><br />(Interested)<br />Where?</p>
<p><strong>Kramer:</strong><br />I can’t just tell you, George. Some things... they have to be earned. You have to go through the journey, man.</p>
<p><strong>George:</strong><br />(Eager)<br />Kramer, you gotta give me the location! Toilet Whisperer needs it!</p>
<p><strong>Kramer:</strong><br />Not until you prove yourself worthy.</p>
<hr />
<p><strong>[Final Scene: Jerry’s Stand-up]</strong></p>
<p><strong>Jerry:</strong><br />What is it with public bathrooms? You walk in, it’s like entering a lottery—you don’t know what you’re going to get. One stall is pristine, the other’s a war zone. And now we’ve got AI to help us navigate this minefield. Really? A chatbot for toilets? If you’re at the point where you need technology to find a bathroom, maybe it’s time to reevaluate your day. You’re not a secret agent, you’re just a guy who had too much coffee!</p>
<p><em>Audience laughter as scene fades out.</em></p>
<hr />
<p><strong>[Closing Scene: Monk’s Café]</strong></p>
<p><em>George is looking at his phone, dejected.</em></p>
<p><strong>George:</strong><br />The app's crashing, Jerry. Everyone’s looking for toilets at the same time, and it can’t handle the load!</p>
<p><strong>Jerry:</strong><br />Sounds like it needs to... flush its system.</p>
<p><em>George glares while Jerry smirks. Fade out.</em></p>
]]></content:encoded></item><item><title><![CDATA[Spring One Tour Tel Aviv 2022]]></title><description><![CDATA[I had the pleasure to host the first-ever Spring One Tour in Tel Aviv this year, along with my colleague Kobi Shamama.
The event had a host of distinguished guests from 7 countries.
Although over 600 registered, we had to limit the attendees to only ...]]></description><link>https://odedia.org/spring-one-tour-tel-aviv-2022</link><guid isPermaLink="true">https://odedia.org/spring-one-tour-tel-aviv-2022</guid><category><![CDATA[TelAviv]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[developers]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Sun, 08 Jan 2023 12:23:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673180556306/3e7ce4ec-72dc-41f1-9e26-067aba108a69.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I had the pleasure to host the first-ever Spring One Tour in Tel Aviv this year, along with my colleague Kobi Shamama.</p>
<p>The event had a host of distinguished guests from 7 countries.</p>
<p>Although over 600 registered, we had to limit the attendees to only 250 due to the venue's capacity. Luckily, we had everything recorded for you to enjoy!</p>
<p>Here is the playlist with all talks:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/playlist?list=PLgGXSWYM2FpPj-rR278PbyoOLQNyEr-Gi">https://www.youtube.com/playlist?list=PLgGXSWYM2FpPj-rR278PbyoOLQNyEr-Gi</a></div>
<p> </p>
<p>Spring Framework founder and project lead Juergen Hoeller presented Spring Framework 6.0, which was released just in time for the event:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/mitWK_DwKGs">https://youtu.be/mitWK_DwKGs</a></div>
<p> </p>
<p>DaShaun Carter presented Spring Boot 3:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/H6HwoWZtngs">https://youtu.be/H6HwoWZtngs</a></div>
<p> </p>
<p>Cora Iberkleid presented Spring Cloud Gateway:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/HY4kTCNKwxk">https://youtu.be/HY4kTCNKwxk</a></div>
<p> </p>
<p>Spring Boot and Spring Cloud founder David Syer discussed how to run untrusted code using WebAssembley:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Tgt3VaaYTXs">https://youtu.be/Tgt3VaaYTXs</a></div>
<p> </p>
<p>Yours truly presented a talk on production considerations for Spring on Kubernetes:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/hAHXp_jQWVo">https://youtu.be/hAHXp_jQWVo</a></div>
<p> </p>
<p>Oleg Šelajev and Cora Iberkleid gave an introduction to TestContainers:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/HUZppOYDoXs">https://youtu.be/HUZppOYDoXs</a></div>
<p> </p>
<p>Director of Dev and Infrastructure at Israel's Social Security, Yuri Fux, presented his team's modernization journey to Spring Boot and Spring Cloud. This was the only session that is in Hebrew:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Ty20Mz0dwQQ">https://youtu.be/Ty20Mz0dwQQ</a></div>
<p> </p>
<p>Olga Maciaszek-Sharma discussed declarative HTTP clients in Spring:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/3NcmlrumSOc">https://youtu.be/3NcmlrumSOc</a></div>
<p> </p>
<p>Lastly, I presented along with Scott Rosenberg an end-to-end demo using Spring Boot and Tanzu Application Platform:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/3jmm5agQusM">https://youtu.be/3jmm5agQusM</a></div>
<p> </p>
<p>Overall, this was truly a fun event to organize and attend. The venue at the Peres center of innovation was beautiful, and the speakers were the best there is.</p>
<p>We're planning to have a Spring One Tour in 2023 as well. Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Trying out ChatGPT by OpenAI]]></title><description><![CDATA[ChatGPT is quickly becoming my new favorite tool. The level of deep AI it can provide, especially for Application developers, is beyond belief. Here are some of the things I've tried out with ChatGPT:
Tell me a joke
I started out with just playing ar...]]></description><link>https://odedia.org/trying-out-chatgpt-by-openai</link><guid isPermaLink="true">https://odedia.org/trying-out-chatgpt-by-openai</guid><category><![CDATA[chatgpt]]></category><category><![CDATA[openai]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Kubernetes]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Mon, 05 Dec 2022 13:33:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670247137591/MLSv4aCdJ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>ChatGPT is quickly becoming my new favorite tool. The level of deep AI it can provide, especially for Application developers, is beyond belief. Here are some of the things I've tried out with ChatGPT:</p>
<h2 id="heading-tell-me-a-joke">Tell me a joke</h2>
<p>I started out with just playing around and trying out a few things like asking it to tell jokes in certain themes. This one was just hilarious:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598272167711748096">https://twitter.com/odedia/status/1598272167711748096</a></div>
<p> </p>
<p>I then asked it to create a new plot for a Friends episode:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598307867139915776?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598307867139915776?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<p>Finally, I asked him to create a movie plot for Back to the future part 4. I would love for someone to make this!</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598286981091700736?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598286981091700736?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<h2 id="heading-lets-get-geeky">Let's get geeky</h2>
<p>Alright. This is all cute but let's get to business.</p>
<p>First, I asked him to explain Kubernetes, but for different audiences: A 5-years old, an elderly person, and a computer PHD:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598288816938569728?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598288816938569728?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<p>This one truly showed me how ChatGPT understands <em>context</em>. This is so impressive to be able to explain a complex subject to different audiences.</p>
<p>Next, I asked ChatGPT to create a lullaby on Horizontal Pod Autoscalers. It did a pretty good job:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598292087963013120?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598292087963013120?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<p>Lullabies are cool, but can it rap? Yes it can. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670263155158/xRIqDQX0u.jpeg" alt="IMG_6370.jpeg" />
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670263121541/-lqlHnlcL.jpeg" alt="IMG_6371.jpeg" /></p>
<p>I then wanted to test some assertions I have about best practices. It was detailed enough to get me rethinking some things:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598295055982370822?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598295055982370822?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<p>I think the most impressive part was getting responses for very obscure edge cases. For example - what's the recommended way to move a BOSH deployment from a single availability zone to multiple availability zones?</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/jacques_chester/status/1598315233461796866?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/jacques_chester/status/1598315233461796866?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<p>Or even, what exactly gets backed up when using BOSH backup and restore?</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598317250632716290?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598317250632716290?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<h2 id="heading-lets-get-coding">Let's get coding</h2>
<p>Ok, the interactive conversation is fun, but does this thing know how to code?</p>
<p>Why yes, yes it does.</p>
<p>Here it is creating a simple Spring Boot application that just works, along with a very nice explanation on the implementation:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598311736385445889?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598311736385445889?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<p>Here it is providing a working example for RabbitMQ streams:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/odedia/status/1598772544768593920?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA">https://twitter.com/odedia/status/1598772544768593920?s=20&amp;t=4hm5qHf-oJ_wwTwL4IRilA</a></div>
<p> </p>
<h2 id="heading-putting-it-all-together">Putting it all together</h2>
<p>Ok. Let's use ChatGPT as my co-pilot assistant when developing a new application.</p>
<p>The application takes orders for T-Shirts, written in React for the frontend and Spring for the backend. Spring writes to a Postgres database. I plan to deploy the application to a Kubernetes cluster using Knative serving, and integrate it with Github OIDC. Can it be done? It can get you 85% there in less then 10 minutes.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/sjezf3oL-AQ">https://youtu.be/sjezf3oL-AQ</a></div>
<p> </p>
<h2 id="heading-summary">Summary</h2>
<p>I truly believe ChatGPT is going to change the way we work. It feels a lot like Altavista did in the early 2000s when Google started taking over. It's just leaps and bounds better. I'm sure some teams at Google are planning to come up with good alternatives as we speak.</p>
<p>ChatGPT isn't perfect though. I already found a few examples it provided that were simply wrong. It is still AI, and it is only as good as the data sets it is modeling. If your source data is bad, your answers would be as well.</p>
<p>But these are very early days. It will get better, and possibly very quickly.</p>
<p>Fun times ahead.</p>
]]></content:encoded></item><item><title><![CDATA[Production Considerations for Spring on Kubernetes]]></title><description><![CDATA[The State of Spring on Kubernetes
The 2021 State of Spring report has two main data points:

Spring is the defacto standard for running cloud-native applications in Java.
Kubernetes is the defacto standard for running containerized workloads in produ...]]></description><link>https://odedia.org/production-considerations-for-spring-on-kubernetes</link><guid isPermaLink="true">https://odedia.org/production-considerations-for-spring-on-kubernetes</guid><category><![CDATA[Spring]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Java]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Thu, 13 Oct 2022 20:50:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665618750743/4s4Wy6nmW.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-the-state-of-spring-on-kubernetes">The State of Spring on Kubernetes</h1>
<p>The <a target="_blank" href="https://tanzu.vmware.com/content/ebooks/the-state-of-spring-2021">2021 State of Spring report</a> has two main data points:</p>
<ul>
<li>Spring is the defacto standard for running cloud-native applications in Java.</li>
<li>Kubernetes is the defacto standard for running containerized workloads in production.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662217713586/Kv8YMPSDN.png" alt="image.png" class="image--center mx-auto" /></p>
<p>This begs the question - what kind of best practices and considerations should be taken into account when running Spring on Kubernetes?</p>
<p>In this article, I shall highlight some of the learnings and best practices that have formed around running Spring (and more broadly Java) applications on Kubernetes. This document is not entirely based on first-hand experiences, but more a collection of conclusions and best practices I identified in the community and when working with customers. These are also based on my own personal observations and are not endorsed by my employer. </p>
<p>You might think that in order to run a Spring Boot app in Kubernetes, all you have to do is quickly jot down a Dockerfile and be done with it. </p>
<p>As Kramer learned the hard way, it's never that easy:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/DennisCode/status/1560079800252764163">https://twitter.com/DennisCode/status/1560079800252764163</a></div>
<p>You may consider a <a target="_blank" href="https://odedia.org/kubernetes-needs-a-developer-platform">developer platform</a> such as <a target="_blank" href="https://tanzu.vmware.com/application-platform">Tanzu Application Platform</a> to offload some of these infrastructure decisions to a platform, but even with a PaaS there are architecture and topology decisions to be made. Besides, if you build it - you own it. It's probably a good idea to know what you're owning.</p>
<h1 id="heading-build-andamp-run">Build &amp; Run</h1>
<p>There two separate yet equally important aspects of running a production-ready Spring application in Kubernetes:</p>
<ul>
<li>Building the OCI image (a.k.a., the "Docker" image)</li>
<li>Running the OCI image on Kubernetes</li>
</ul>
<p>Each step has its own gotchas and intricacies. Let's review these in details.</p>
<h1 id="heading-building-the-image">Building the image</h1>
<p>At first, this sounds fairly simple. Just Google some sample Dockerfile for a Java application and use it as reference.</p>
<p>In reality, there are many important decisions that will impact your target image and how it behaves with regards to the Java Virtual Machine it is running.</p>
<h2 id="heading-choose-a-linux-distribution">Choose a Linux distribution</h2>
<p>There are so many Linux distros out there that it gets hard to keep up. Should you go with Ubuntu? RedHat Enterprise Linux? Debian? Suse? Alpine? Distroless? Which one makes more sense for your JDK-based OCI image? </p>
<p>For containerized JDK workloads, the question should actually be more generalized. It should be focused on which Linux kernel you are going to base your container on. </p>
<p>There's a good reason why you can run a Red Hat Enterprise Linux container on an Ubuntu VM, or a Debian Linux container on a PhotonOS VM. The important pieces for a Linux-based container are:</p>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/C_standard_library">libc</a>, which is at the core of OCI images. This is practically the same programming interface, regardless of the Linux distribution you plan to use.</li>
<li>The <a target="_blank" href="https://en.wikipedia.org/wiki/Application_binary_interface">Linux kernel Application Binary Interface (ABI)</a>, which guarantees backwards compatibility between different Linux kernel versions.</li>
</ul>
<p>Based on this information, it sounds like you shouldn't really care which Linux distribution you'll use. However, the devil is in the details. </p>
<p>There are two popular implementations for the <code>libc</code> interface:</p>
<ul>
<li><strong>GNU libc</strong>: This is the standard library that you probably use every day. It is used by Ubuntu, Debian, CentOS, RHEL, SUSE and more.
Its downside is that it is a fairly large and heavy codebase (in container terms).</li>
<li><strong>Musl libc</strong>: This is a newer implementation of the library. Used by Alpine Linux, it is <a target="_blank" href="https://www.etalabs.net/compare_libcs.html">much smaller</a> in size compared to GNU libc and is meant to be lightweight, fast and simple. However, there are caveats. Musl libc actually has <a target="_blank" href="https://wiki.musl-libc.org/functional-differences-from-glibc.html">functional differences</a> compared to GNU libc - things like regular expressions, EOF and multithreading could behave differently based on the implementation. [See this detailed comparison]</li>
</ul>
<p>It sounds like the implementation details are less important when you work against an interface, but that's not the case for the Java Virtual Machine. Until fairly recent versions, the JVM relied on features that were <em>only</em> available in GNU libc, and modifications had to be made to support the Musl implementation. </p>
<p>Starting with Java 16, <a target="_blank" href="https://openjdk.org/jeps/386">both implementations are supported</a>. Since you'll probably want to stick to Java long-term support releases, in practice this means that you should only rely on Alpine-based distros starting with Java 17 and later.</p>
<blockquote>
<p>For best compatibility, use a GNU-based libc Linux implementation. At VMware Tanzu, we use Ubuntu extensively for Spring-based images and find it to be an excellent choice, but other solutions should work just as well: RHEL, Debian, PhotonOS, SUSE, CentOS etc.</p>
</blockquote>
<h2 id="heading-choose-a-jdk-distribution">Choose a JDK distribution</h2>
<p>If you ever installed <a target="_blank" href="https://sdkman.io/">SDKman</a> on your machine, you know there are a plethora of JDK distros out there:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662267621264/88o-jze6h.png" alt="image.png" class="image--center mx-auto" /></p>
<p>If that's not enough, there is also a long list of JDK base images to choose from in Docker Hub. Merely searching for <code>Java</code> returns a single official image with the label <code>DEPRECATED; use openjdk or other jdk implementations instead</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662268010706/bs5RpVezA.png" alt="image.png" class="image--center mx-auto" /></p>
<p>A search for <code>openjdk</code> returns over 6,500 results. The first two results are "Docker Official images", one for OpenJDK with over a billion downloads, and one for Eclipse Temurin with over 10 million downloads:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665651484470/07I7ZN0d1.png" alt="image.png" class="image--center mx-auto" /></p>
<p>These sounds like good choices. Just be aware that "official" doesn't necessarily mean what you think it means. It just means that the build itself is done by Docker - and sometimes this can cause <a target="_blank" href="https://www.infoq.com/news/2019/06/docker-vulnerable-java/">more harm than good</a>.</p>
<p><a target="_blank" href="https://twitter.com/mraible">Matt Raible</a> has a very good <a target="_blank" href="https://developer.okta.com/blog/2019/01/16/which-java-sdk">article</a> on the different distros available out there, although some distribution names have changed since its publishing at 2019. </p>
<p>For OCI images, there are three variants that you should consider:</p>
<ul>
<li><a target="_blank" href="https://www.oracle.com/java/technologies/downloads/">Oracle JDK</a> - requires license for production use. This is the "official" JDK. </li>
<li><a target="_blank" href="https://openjdk.org/">OpenJDK</a>, built and maintained by Oracle, and free. Oracle's OpenJDK doesn't offer support for Java 8 anymore.</li>
<li><a target="_blank" href="https://adoptium.net/">Adoptium</a> (the rebranded <a target="_blank" href="https://blog.adoptium.net/2021/03/eclipse-adoptium-announcement/">AdoptOpenJDK</a> distribution) is built and maintained by the community. It is compatible with Oracle's version while having more "friendly" licensing terms. Vendors such as Bellsoft, IBM, Amazon and others offer JDK distributions based on Adoptium.</li>
</ul>
<p>The decision on which JDK to use has many factors, so it's hard to make a single recommendation. </p>
<ul>
<li>If your company already has an Oracle license, use their JDK base images. </li>
<li>If you are running on a single public cloud provider, consider using that cloud provider's JDK release, such as <a target="_blank" href="https://aws.amazon.com/corretto">Amazon Corretto</a> or <a target="_blank" href="https://www.microsoft.com/openjdk">Microsoft's build of OpenJDK</a>.</li>
<li>If you are looking for a fair and production-ready distribution that is community-driven and can run anywhere, use the <a target="_blank" href="https://hub.docker.com/_/eclipse-temurin/tags?page=1">Adoptium-provided base images</a>, or one of the various flavors based off of it with commercial support, such as <a target="_blank" href="https://www.azul.com/downloads/?package=jdk">Azul</a> or <a target="_blank" href="https://bell-sw.com/">Bellsoft Liberica</a>. VMware Tanzu (the business unit that the Spring team is a part of) uses Bellsoft Liberia in products such as <a target="_blank" href="https://tanzu.vmware.com/build-service">Tanzu Build Service</a> and <a target="_blank" href="https://tanzu.vmware.com/application-platform">Tanzu Application Platform</a>.</li>
</ul>
<blockquote>
<p>Use the Adoptium base images such as <code>eclipse-temurin:17-jre-jammy</code>, or a supported flavor of the builds. If you run on a single public cloud, consider using that cloud provider's base image. If you have a license for the Oracle JDK, use it. </p>
</blockquote>
<h2 id="heading-choose-a-jdk-version">Choose a JDK version</h2>
<p>Oh, this one's easy. Use the latest LTS version that you can. At the time of this writing, the latest LTS version is Java 17.</p>
<p>LTS stands for Long-Term Support, and in general the industry tries to stick to those releases unless there are very important reasons to move to other versions. Non-LTS versions are released every 6 months and are supported for fairly short cycles, while LTS versions are being supported for many years (5+ based on the distribution).</p>
<p>Starting with Spring Framework 6.0, the minimum supported version will be JDK 17.</p>
<p>Support is just one aspect of the selected version. Each version also introduces new features, but more importantly - better performance, better security and improvements to the garbage collector.</p>
<p>Here's the startup time for the famous <a target="_blank" href="https://github.com/spring-projects/spring-petclinic">Spring Boot Petclinic</a> application while running in a Java 11-based container based on Bellsoft Liberica on a 4-cores machine running macOS:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662275111628/LNOUSFk7B.png" alt="Screen Shot 2022-09-04 at 9.56.15.png" class="image--center mx-auto" /></p>
<p>And here's the same application running on a Java 17 container, also based on Bellsoft Liberica, on the same 4-cores machine running macOS:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662275236865/V_H67a8Q8.png" alt="Screen Shot 2022-09-04 at 10.02.29.png" class="image--center mx-auto" /></p>
<p>That's a 1.5 seconds difference just by upgrading your JVM. It may not sound like a lot, but in a Kubernetes environment where pods go up and down at any given moment, that's a lot.</p>
<p>If you can't use Java 17 due to compatibility issues, use Java 11 - the previous LTS version.</p>
<h4 id="heading-java-8">Java 8</h4>
<p>Now, I know what some of you are thinking right about now: "What are you talking about? we're still stuck with Java 8 and we can't upgrade anytime soon". If that's you - you're not alone. According to a <a target="_blank" href="https://www.infoworld.com/article/3652408/java-8-still-dominates-but-java-17-wave-is-coming-survey.html">recent survey by JRebel</a>, more than a third of Java developers are <em>still</em> using Java 8 in production, and that's not going to change in the near future. If you still have to use Java 8, that's ok (🫤), but start planning your strategy to upgrade. Also - there are several important caveats to consider:</p>
<p>In containerized environments, <strong>Use Java 8 update 191 or later</strong>. Older versions of Java 8 are not compatible with containerized environments, and the JVM mistakenly reports the <em>host's</em> CPU and memory instead of the container's. Here's an example for running an application with Java 8 update 131 on a Linux VM with 4 CPUs and 16 GB of RAM:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662278081562/RrsGcUVhL.png" alt="Screen Shot 2022-09-04 at 10.53.22.png" class="image--center mx-auto" /></p>
<p>As you can see, in Java 8 update 131 - the container uses all available CPUs on the host although I asked the container to limit the container to just 2 CPUs. Also - the available memory is set to 4 GB although I asked for a container with just 256 MB (the max heap size is always calculated as a portion of machine's available memory, as a default that it is 1/4 of the host's memory). This can cause extremely unpredictable results, especially since many thread pools such as those used by Tomcat and thread pools use the number of available processors (<code>Runtime.getAvailableProcessors()</code>) to calculate the size of the pool, which translates to larger native memory usage, which eats more of the total available memory, which in turn, again, can cause havoc. </p>
<p>If that's not enough, the JVM also chooses the garbage collector based on available CPU and Memory. You will get different garbage collectors based on different CPU and memory configurations. </p>
<p>So, if you have to use Java 8 - use a recent version, update 191 or later. You'll also get a ton of security patches which are critical for production environments. </p>
<p>Here's the same <code>docker run</code> command on the same Linux VM, but with Java 8 update 292:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662279449914/9byYfYyfd.png" alt="Screen Shot 2022-09-04 at 11.16.47.png" class="image--center mx-auto" /></p>
<p>This time, our JVM reports a max heap size of 132 MB and no parallel garbage collection threads. Also - all thread pool using the <code>Runtime.getAvailableProcessors()</code> command will get the correct number of processors, and therefore will generate reasonable pools with reasonable native memory sizes.</p>
<p>An important note that goes without saying: there are always refinements and enhancements, and bugs are always a possibility, so use the latest patch version based on your target JDK version. Just recently, a patch was made to <a target="_blank" href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8230305">support cgroups v2</a> because the first time the feature got implemented, only cgroups v1 was available. Adding support for cgroups v2 with Java 8 is <a target="_blank" href="https://github.com/openjdk/jdk8u-dev/pull/127">still ongoing development</a> at the time of this writing.</p>
<blockquote>
<p>Use Java 17 LTS. If you can't use Java 17, use Java 11 LTS. If you can't use Java 11, use Java 8 update 191 or later. <strong>Do not use earlier versions of Java in a containerized environment such as Kubernetes</strong>.</p>
</blockquote>
<h2 id="heading-writing-a-dockerfile">Writing a Dockerfile</h2>
<p>If you'll Google "Spring Boot Dockerfile", you might run into <a target="_blank" href="https://spring.io/guides/gs/spring-boot-docker/">this article</a>. In it, the first section lists the following example:</p>
<pre><code class="lang-dockerfile">FROM eclipse-temurin:17-jre-jammy
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
</code></pre>
<p>This Dockerfile will work just fine, but I would advise you to avoid it. The above Dockerfile places the Spring Boot "fat jar" into a single OCI layer. This uber jar can easily reach 150 MB or more. What does this mean in practice? It means that for every new build, even for the simplest code change in our application:</p>
<ul>
<li>A new 150 MB layer (read: tarball) will be created</li>
<li>The layer will be uploaded to your OCI registry</li>
<li>When Kubernetes pulls the latest image to the node that runs the container,  the entire 150 MB layer will need to be pulled. </li>
</ul>
<p>This will happen on every single code change and rebuild of the image. In reality, only a very small sliver of compiled code has changed. Dockerfile treats each new line as a new layer, so it would make a lot more sense to put the third party dependencies in their own layer and have our custom code in its own layer. But how should you layer your Spring Boot image? Which app stack should be treated as its own layer?</p>
<p>Luckily, Spring Boot has you covered. Let's take the same Petclinic application, build it, and ask Spring to list what <em>it</em> thinks are the correct layers that are suitable for an OCI image. We can use a special execution mode called <code>layertools</code>:</p>
<pre><code class="lang-bash">$ ls -l target
total 105992
-rw-r--r--   1 odedia  staff  53169785 Sep  5 11:10 spring-petclinic-2.7.0-SNAPSHOT.jar
-rw-r--r--   1 odedia  staff    391665 Sep  5 11:10 spring-petclinic-2.7.0-SNAPSHOT.jar.original
drwxr-xr-x  22 odedia  staff       704 Sep  5 11:10 surefire-reports
drwxr-xr-x   3 odedia  staff        96 Sep  5 11:09 test-classes

$ java -Djarmode=layertools -jar target/spring-petclinic-2.7.0-SNAPSHOT.jar list
dependencies
spring-boot-loader
snapshot-dependencies
application
</code></pre>
<p>As you can see, instead of <em>running</em> the Spring Boot application, passing the argument <code>-Djarmode=layertools list</code> to the java command will simply list the layers that make sense for Spring Boot application. In our case, these are:</p>
<ul>
<li><strong>Dependencies</strong> - the Spring Boot and other frameworks' release-based dependencies. These only change when we upgrade a Spring Boot version or the third party framework version.</li>
<li><strong>Spring Boot Loader</strong> - this is the code that loads our Spring Boot app into the JVM to manage the bean lifecycle, among other things. This also rarely changes.</li>
<li><strong>Snapshot Dependencies</strong> - these dependencies are changing much more often. It's possible that on each new build it would require us to pull latest snapshot. As a result, this layer is the closest to our application code. </li>
<li><strong>Application</strong> - This is our application code from <code>src/main/java</code> (in the case of maven).</li>
</ul>
<p>So, Spring Boot helps us with hints on how best to layer our application.
But wouldn't it be better if it could actually <em>prepare</em> the layers for us?
Well, it can do exactly that:</p>
<pre><code class="lang-bash">$ java -Djarmode=layertools -jar target/spring-petclinic-2.7.0-SNAPSHOT.jar extract --destination target/tmp

$ ls -l target/tmp
total 0
drwxr-xr-x  4 odedia  staff  128 Sep  5 11:17 application
drwxr-xr-x  3 odedia  staff   96 Sep  5 11:17 dependencies
drwxr-xr-x  2 odedia  staff   64 Sep  5 11:17 snapshot-dependencies
drwxr-xr-x  3 odedia  staff   96 Sep  5 11:17 spring-boot-loader
</code></pre>
<p>Let's see the contents of the <code>dependencies</code> folder for example:</p>
<pre><code class="lang-bash">$ ls target/tmp/dependencies
BOOT-INF
$ ls target/tmp/dependencies/BOOT-INF
lib
$ ls target/tmp/dependencies/BOOT-INF/lib
HdrHistogram-2.1.12.jar                       jakarta.activation-1.2.2.jar                  spring-boot-actuator-autoconfigure-2.7.1.jar
HikariCP-4.0.3.jar                            jakarta.activation-api-1.2.2.jar              spring-boot-autoconfigure-2.7.1.jar
LatencyUtils-2.0.3.jar                        jakarta.annotation-api-1.3.5.jar              spring-boot-jarmode-layertools-2.7.1.jar
antlr-2.7.7.jar                               jakarta.persistence-api-2.2.3.jar             spring-context-5.3.21.jar
aspectjweaver-1.9.7.jar                       jakarta.transaction-api-1.3.3.jar             spring-context-support-5.3.21.jar
attoparser-2.0.5.RELEASE.jar                  jakarta.validation-api-2.0.2.jar              spring-core-5.3.21.jar
bootstrap-5.1.3.jar                           jakarta.xml.bind-api-2.3.3.jar                spring-data-commons-2.7.1.jar
byte-buddy-1.12.11.jar                        jandex-2.4.2.Final.jar                        spring-data-jpa-2.7.1.jar
cache-api-1.1.1.jar                           jaxb-runtime-2.3.6.jar                        spring-expression-5.3.21.jar
checker-qual-3.5.0.jar                        jboss-logging-3.4.3.Final.jar                 spring-jcl-5.3.21.jar
classgraph-4.8.139.jar                        jul-to-slf4j-1.7.36.jar                       spring-jdbc-5.3.21.jar
classmate-1.5.1.jar                           log4j-api-2.17.2.jar                          spring-orm-5.3.21.jar
ehcache-3.10.0.jar                            log4j-to-slf4j-2.17.2.jar                     spring-tx-5.3.21.jar
font-awesome-4.7.0.jar                        logback-classic-1.2.11.jar                    spring-web-5.3.21.jar
h2-2.1.214.jar                                logback-core-1.2.11.jar                       spring-webmvc-5.3.21.jar
hibernate-commons-annotations-5.1.2.Final.jar micrometer-core-1.9.1.jar                     thymeleaf-3.0.15.RELEASE.jar
hibernate-core-5.6.9.Final.jar                mysql-connector-java-8.0.29.jar               thymeleaf-extras-java8time-3.0.4.RELEASE.jar
hibernate-validator-6.2.3.Final.jar           postgresql-42.3.6.jar                         thymeleaf-spring5-3.0.15.RELEASE.jar
istack-commons-runtime-3.0.12.jar             slf4j-api-1.7.36.jar                          tomcat-embed-core-9.0.64.jar
jackson-annotations-2.13.3.jar                snakeyaml-1.30.jar                            tomcat-embed-el-9.0.64.jar
jackson-core-2.13.3.jar                       spring-aop-5.3.21.jar                         tomcat-embed-websocket-9.0.64.jar
jackson-databind-2.13.3.jar                   spring-aspects-5.3.21.jar                     txw2-2.3.6.jar
jackson-datatype-jdk8-2.13.3.jar              spring-beans-5.3.21.jar                       unbescape-1.1.6.RELEASE.jar
jackson-datatype-jsr310-2.13.3.jar            spring-boot-2.7.1.jar                         webjars-locator-core-0.50.jar
jackson-module-parameter-names-2.13.3.jar     spring-boot-actuator-2.7.1.jar
$
</code></pre>
<p>And here is our application's compiled code:</p>
<pre><code class="lang-bash">$ ls target/tmp/application/BOOT-INF/classes
application-mysql.properties    application.properties          db                              messages                        static
application-postgres.properties banner.txt                      git.properties                  org                             templates
</code></pre>
<p>As you can see - this process takes our uber jar and explodes it to make it suitable for a layered OCI image. If you've been around long enough like me and have flashbacks to <a target="_blank" href="https://docs.oracle.com/cd/E13222_01/wls/docs61/deployment/deployment.html">exploded EAR files</a>, you're not alone. Technology has a tendency to repeat itself.</p>
<p>We can now incorporate the exploded directories into our Dockerfile:</p>
<pre><code class="lang-dockerfile">FROM eclipse-temurin:17-jre-jammy as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM eclipse-temurin:17-jre-jammy
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
</code></pre>
<p>Let's see our OCI image in action:</p>
<pre><code>$ # With layered image approach

$ docker run -p <span class="hljs-number">8080</span>:<span class="hljs-number">8080</span> petclinic


              |\      _,,,--,,_
             /,<span class="hljs-string">`.-'`</span><span class="hljs-string">'   ._  \-;;,_
  _______ __|,4-  ) )_   .;.(__`'</span>-<span class="hljs-string">'__     ___ __    _ ___ _______
 |       | '</span>---<span class="hljs-string">''</span>(_/._)-<span class="hljs-string">'(_\_)   |   |   |   |  |  | |   |       |
 |    _  |    ___|_     _|       |   |   |   |   |_| |   |       | __ _ _
 |   |_| |   |___  |   | |       |   |   |   |       |   |       | \ \ \ \
 |    ___|    ___| |   | |      _|   |___|   |  _    |   |      _|  \ \ \ \
 |   |   |   |___  |   | |     |_|       |   | | |   |   |     |_    ) ) ) )
 |___|   |_______| |___| |_______|_______|___|_|  |__|___|_______|  / / / /
 ==================================================================/_/_/_/

:: Built with Spring Boot :: 2.7.1


2022-09-05 08:36:35.170  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Starting PetClinicApplication v2.7.0-SNAPSHOT using Java 17.0.4.1 on 73cac62e34cb with PID 1 (/application/BOOT-INF/classes started by root in /application)
2022-09-05 08:36:35.179  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : No active profile set, falling back to 1 default profile: "default"
2022-09-05 08:36:36.144  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-09-05 08:36:36.187  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 34 ms. Found 2 JPA repository interfaces.
2022-09-05 08:36:36.823  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-09-05 08:36:36.832  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-09-05 08:36:36.833  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.64]
2022-09-05 08:36:36.927  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-09-05 08:36:36.927  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1661 ms
2022-09-05 08:36:37.355  INFO 1 --- [           main] org.ehcache.core.EhcacheManager          : Cache '</span>vets<span class="hljs-string">' created in EhcacheManager.
2022-09-05 08:36:37.363  INFO 1 --- [           main] org.ehcache.jsr107.Eh107CacheManager     : Registering Ehcache MBean javax.cache:type=CacheStatistics,CacheManager=urn.X-ehcache.jsr107-default-config,Cache=vets
2022-09-05 08:36:37.391  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-09-05 08:36:37.602  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-09-05 08:36:37.765  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-09-05 08:36:37.807  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.9.Final
2022-09-05 08:36:37.950  INFO 1 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-09-05 08:36:38.051  INFO 1 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-09-05 08:36:38.693  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-09-05 08:36:38.701  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit '</span><span class="hljs-keyword">default</span><span class="hljs-string">'
2022-09-05 08:36:40.153  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 13 endpoint(s) beneath base path '</span>/actuator<span class="hljs-string">'
2022-09-05 08:36:40.222  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '</span><span class="hljs-string">'
2022-09-05 08:36:40.243  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Started PetClinicApplication in 5.446 seconds (JVM running for 5.782)</span>
</code></pre><p>Our exploded Spring Boot app started in just under 6 seconds, which is quite impressive! Let's compare it to the original Dockerfile that used the far jar approach:</p>
<pre><code>$ # With fat jar approach

$ docker run -p <span class="hljs-number">8080</span>:<span class="hljs-number">8080</span> petclinic


              |\      _,,,--,,_
             /,<span class="hljs-string">`.-'`</span><span class="hljs-string">'   ._  \-;;,_
  _______ __|,4-  ) )_   .;.(__`'</span>-<span class="hljs-string">'__     ___ __    _ ___ _______
 |       | '</span>---<span class="hljs-string">''</span>(_/._)-<span class="hljs-string">'(_\_)   |   |   |   |  |  | |   |       |
 |    _  |    ___|_     _|       |   |   |   |   |_| |   |       | __ _ _
 |   |_| |   |___  |   | |       |   |   |   |       |   |       | \ \ \ \
 |    ___|    ___| |   | |      _|   |___|   |  _    |   |      _|  \ \ \ \
 |   |   |   |___  |   | |     |_|       |   | | |   |   |     |_    ) ) ) )
 |___|   |_______| |___| |_______|_______|___|_|  |__|___|_______|  / / / /
 ==================================================================/_/_/_/

:: Built with Spring Boot :: 2.7.1


2022-09-05 08:40:30.802  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Starting PetClinicApplication v2.7.0-SNAPSHOT using Java 17.0.4.1 on 93195813a338 with PID 1 (/app.jar started by root in /)
2022-09-05 08:40:30.805  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : No active profile set, falling back to 1 default profile: "default"
2022-09-05 08:40:32.252  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-09-05 08:40:32.331  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 60 ms. Found 2 JPA repository interfaces.
2022-09-05 08:40:33.147  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-09-05 08:40:33.158  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-09-05 08:40:33.159  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.64]
2022-09-05 08:40:33.247  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-09-05 08:40:33.247  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2385 ms
2022-09-05 08:40:33.774  INFO 1 --- [           main] org.ehcache.core.EhcacheManager          : Cache '</span>vets<span class="hljs-string">' created in EhcacheManager.
2022-09-05 08:40:33.789  INFO 1 --- [           main] org.ehcache.jsr107.Eh107CacheManager     : Registering Ehcache MBean javax.cache:type=CacheStatistics,CacheManager=urn.X-ehcache.jsr107-default-config,Cache=vets
2022-09-05 08:40:33.837  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-09-05 08:40:34.063  INFO 1 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-09-05 08:40:34.240  INFO 1 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-09-05 08:40:34.290  INFO 1 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.9.Final
2022-09-05 08:40:34.463  INFO 1 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-09-05 08:40:34.587  INFO 1 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-09-05 08:40:35.302  INFO 1 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-09-05 08:40:35.311  INFO 1 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit '</span><span class="hljs-keyword">default</span><span class="hljs-string">'
2022-09-05 08:40:37.049  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 13 endpoint(s) beneath base path '</span>/actuator<span class="hljs-string">'
2022-09-05 08:40:37.117  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '</span><span class="hljs-string">'
2022-09-05 08:40:37.138  INFO 1 --- [           main] o.s.s.petclinic.PetClinicApplication     : Started PetClinicApplication in 6.84 seconds (JVM running for 7.272)</span>
</code></pre><p>Our application is slower by almost 1.5 seconds while running on the same machine and the same Docker engine. Add to that the overhead of pulling and storing an entire 150mb+ layer on every single change from a remote registry, and you'll quickly understand the benefits of this approach. </p>
<h3 id="heading-customizing-the-layers">Customizing the layers</h3>
<p>You might have your own set of release dependencies that are not part of the dependencies provided by Spring. For example, your company might be using an internal set of dependencies for security, logging and auditing that you need to include in every Spring Boot application. It might make sense to extract such dependencies into their own layer as well since they may change more often than the Spring dependencies. To do it, you can create a custom layers configuration:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">layers</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.springframework.org/schema/boot/layers"</span>
        <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
        <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://www.springframework.org/schema/boot/layers
                          https://www.springframework.org/schema/boot/layers/layers-2.7.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">application</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">into</span> <span class="hljs-attr">layer</span>=<span class="hljs-string">"spring-boot-loader"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">include</span>&gt;</span>org/springframework/boot/loader/**<span class="hljs-tag">&lt;/<span class="hljs-name">include</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">into</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">into</span> <span class="hljs-attr">layer</span>=<span class="hljs-string">"application"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">into</span> <span class="hljs-attr">layer</span>=<span class="hljs-string">"snapshot-dependencies"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">include</span>&gt;</span>*:*:*SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">include</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">into</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">into</span> <span class="hljs-attr">layer</span>=<span class="hljs-string">"custom-security-dependencies"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">include</span>&gt;</span>com.mycompany.security:*<span class="hljs-tag">&lt;/<span class="hljs-name">include</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">into</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">into</span> <span class="hljs-attr">layer</span>=<span class="hljs-string">"dependencies"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">layerOrder</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>spring-boot-loader<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>snapshot-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>custom-security-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">layer</span>&gt;</span>application<span class="hljs-tag">&lt;/<span class="hljs-name">layer</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">layerOrder</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">layers</span>&gt;</span>
</code></pre>
<p>To apply this configuration in your application, point the <code>spring-boot-maven-plugin</code> to the custom layer configuration: </p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">layers</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">enabled</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">enabled</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>${project.basedir}/src/layers.xml<span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">layers</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p>Don't forget to change your Dockerfile accordingly:</p>
<pre><code class="lang-dockerfile">FROM eclipse-temurin:17-jre-jammy as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM eclipse-temurin:17-jre-jammy
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/custom-security-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
</code></pre>
<blockquote>
<p>Avoid using uber jars in your Dockerfiles. Use Spring's builtin layertools to explode the jar for more efficient packaging in an OCI image.</p>
</blockquote>
<h3 id="heading-setting-bootstrap-arguments">Setting bootstrap arguments</h3>
<p>In the example above, we ran the Java application with everything as default: </p>
<pre><code>ENTRYPOINT [<span class="hljs-string">"java"</span>, <span class="hljs-string">"org.springframework.boot.loader.JarLauncher"</span>]
</code></pre><p>These defaults <em>may</em> be ok, but they're probably not. There are too many variables that are taken into consideration by the JVM to decide on things like the heap size, available memory and garbage collection algorithm. <strong>Always profile your app before production</strong> and apply settings that you feel were validated in production-like environments. At the last section of this article I will include some profiling tips that may help you.</p>
<p>There are many ways to pass arguments to the JVM. In containerized environments, it's best to use the <code>JAVA_TOOL_OPTIONS</code> environment variable since it's the <a target="_blank" href="https://circleci.com/docs/java-oom#using-java-environment-variables-to-set-memory-limits">safest choice</a> that can be read by the JVM inside a container. No, do not use <code>JAVA_OPTS</code> - this is a shell script convention and the JVM doesn't honor it. </p>
<p>Some of the more important arguments to pass to the JVM have to do with memory management. You'll need to take several things under consideration:</p>
<ul>
<li>Your JVM memory is (mostly) a combination of the heap memory and the non-heap memory (Stack Memory, Direct Memory etc.).</li>
<li>Most of your application's memory will be allocated to the heap. How much memory should you assign to it? It depends! </li>
<li>How much memory does the container has?</li>
<li>How many classes are in-memory?</li>
<li>How many threads are running at the same time (including Thread Pools)?</li>
<li><p>How much memory is taken up by the Stack memory?</p>
</li>
<li><p>Your application may also use a considerable amount of non-heap memory for things like NIO Direct Memory. For example, Kafka and many other socket-based frameworks make use of NIO for better performance. Without setting proper arguments, it might kill your JVM without you realizing what happened (as I <a target="_blank" href="https://github.com/cloudfoundry/java-buildpack-memory-calculator/issues/17">learned the hard way</a>...). Profile your application for direct memory by including the JVM arguments  <code>-XX:NativeMemoryTracking=summary-XX:+PrintNMTStatistics</code>. Add the resulting value to the total memory footprint calculations. </p>
</li>
</ul>
<p>At Pivotal (who later became VMware Tanzu), we developed a useful little tool that helped us with these memory calculations. Originally developed for Cloud Foundry, the Java Buildpack Memory Calculator was created to help developers on the platform, who really just wanted to <code>cf push</code> their Spring application and have the platform build the image for them without thinking too much on production optimizations. This tool still exists and provides a great starting point for you to consider when setting memory arguments.</p>
<p>The calculator takes as input the parameters from above like expected Stack memory, expected number of classes, expected number of concurrent threads etc. You <em>still</em> need to profile your app to get some understanding of what these values mean in your use case. The calculator would then output the recommended max heap size based on our research and real-world experience. The algorithm is <a target="_blank" href="https://github.com/cloudfoundry/java-buildpack-memory-calculator#algorithm">clearly documented</a> in the github repo. </p>
<p>For example, let's say my application has 750 classes, my container has 1G of  total memory, I expect my stack size to be around 1Mb, and I calculated the total threads used by my application to be around 100 threads, give or take. I'll run the command as follows:</p>
<pre><code>$ ./java-buildpack-memory-calculator --loaded-<span class="hljs-class"><span class="hljs-keyword">class</span>-<span class="hljs-title">count</span></span>=<span class="hljs-number">750</span> --thread-count=<span class="hljs-number">100</span> --total-memory=<span class="hljs-number">1024</span>M --jvm-options <span class="hljs-string">"-Xss1024k"</span>

-XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -XX:MaxMetaspaceSize=<span class="hljs-number">17919</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xmx672256K
</code></pre><p>The memory calculator gave a recommended max heap size of around 670 megabytes, based on the algorithm <code>total memory - (headroom + direct memory + metaspace + reserved code cache + (thread stack * thread count))</code>.
To clarify how important the thread pool is to this calculation, let's imagine our application uses Tomcat and utilizes 200 additional threads for Web requests, bumping the total number of threads to 300. The calculation is now:</p>
<pre><code>$ ./java-buildpack-memory-calculator --loaded-<span class="hljs-class"><span class="hljs-keyword">class</span>-<span class="hljs-title">count</span></span>=<span class="hljs-number">750</span> --thread-count=<span class="hljs-number">300</span> --total-memory=<span class="hljs-number">1024</span>M --jvm-options <span class="hljs-string">"-Xss1024k"</span>

-XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -XX:MaxMetaspaceSize=<span class="hljs-number">17919</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xmx467456K
</code></pre><p>Our max heap size is now down to around 470 megabytes.</p>
<p>Notice that the tool always gives a default value for <code>MaxDirectMemorySize</code> of 10M. That's because there's really no way of telling the true amount to be used without profiling the application, so it chose some sane default. Remember to check your direct memory usage as mentioned above. It could have a meaningful impact your heap size. Let's say our application uses Kafka with many concurrent consumers, and therefore requires 64 MB for direct memory. The calculation will now be:</p>
<pre><code>$ ./java-buildpack-memory-calculator --loaded-<span class="hljs-class"><span class="hljs-keyword">class</span>-<span class="hljs-title">count</span></span>=<span class="hljs-number">750</span> --thread-count=<span class="hljs-number">300</span> --total-memory=<span class="hljs-number">1024</span>M --jvm-options <span class="hljs-string">"-Xss1024k -XX:MaxDirectMemorySize=64M"</span>

-XX:MaxMetaspaceSize=<span class="hljs-number">17919</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xmx412160K
</code></pre><p>Our heap size is now down to just ~410 megabytes. Perhaps 1 gigabyte for the container is not enough at this point?</p>
<p>As mentioned before, the JVM memory arguments are entirely dependent on the target deployment environment and available container memory. You wouldn't want to hardcode these values into the Dockerfile, since they keep changing. You have three options here:</p>
<ol>
<li>Set the <code>JAVA_TOOL_OPTIONS</code> environment variable as part of your Kubernetes deployment yaml (more on this later).</li>
<li>Calculate the arguments when the container starts, but before running the actual Spring-based application. The next section shows how this is done. </li>
<li>Use a mixture of options 1 and 2. Set what you can in your deployment manifest, and let the container additional calculate arguments on startup.</li>
</ol>
<h3 id="heading-who-needs-dockerfiles">Who needs Dockerfiles?</h3>
<p>It's almost 2023. You don't compile your own Linux kernels anymore, I would hope. Why are you still writing Dockerfiles? </p>
<p>Docker images (well, OCI images, to be exact) are great. They are the golden standard for container packaging that can run anywhere. But Dockerfiles are really just scripts to build these images. It's been around since the time that OCI images became popular, so they are considered the default. We have other ways to get a working OCI image, and some of options are more focused on the <em>what</em> than on the the <em>how</em>. 
One of these tools is <a target="_blank" href="https://github.com/GoogleContainerTools/jib">jib</a>. It's developed at Google and can integrate with your build tools to automatically create a Java distroless container image for you without the hassle of writing the perfect Dockerfile. But there's also a standard that can be used for a wider context than just Java images.</p>
<h2 id="heading-cloud-native-buildpacks">Cloud Native Buildpacks</h2>
<p>Cloud Native Buildpacks (CNBs) is a specification for how to build OCI images without Dockerfiles, and I would argue it has three major advantages going for it:</p>
<ul>
<li>It's a <a target="_blank" href="https://www.cncf.io/projects/buildpacks/">CNCF project</a>.</li>
<li>It will <strong>proactively patch</strong> your base Linux distribution and your OpenJDK (or other middleware runtimes for other languages) on each new build.</li>
<li>It's directly integrated into the existing Spring build plugins!</li>
</ul>
<blockquote>
<p>TL;DR, the following will generate a production-ready OCI image for you:</p>
</blockquote>
<pre><code>mvn spring-boot:build-image
</code></pre><p>This command will use many of the things I mentioned above:</p>
<ul>
<li>It will create an optimized OCI image based on the layers listed by the <code>layertools</code> command (unless you're on Spring Boot 2.3, where it wasn't the default). </li>
<li>It will run the Java Buildpack Memory Calculator to calculate the correct heap size.</li>
<li>On container startup, it will set production-ready defaults for <code>JAVA_TOOL_OPTIONS</code>, taking into account user-provided environment variables and the memory calculation.
Here's the boot logs of a container built by <code>mvn spring-boot:build-image</code>, running on a Mac with Docker Desktop that has resource configuration of 4 CPUs and 4 GB assigned memory. Notice that <code>JAVA_TOOL_OPTIONS</code> is calculated and set before Spring even boots, based on the available container memory:</li>
</ul>
<pre><code>&gt; docker run -p <span class="hljs-number">8080</span>:<span class="hljs-number">8080</span> spring-petclinic:<span class="hljs-number">2.7</span><span class="hljs-number">.1</span>-SNAPSHOT
Setting Active Processor Count to <span class="hljs-number">4</span>
Calculating JVM memory based on <span class="hljs-number">3198728</span>K available memory
For more information on <span class="hljs-built_in">this</span> calculation, see https:<span class="hljs-comment">//paketo.io/docs/reference/java-reference/#memory-calculator</span>
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -Xmx2572558K -XX:MaxMetaspaceSize=<span class="hljs-number">114169</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xss1M (Total Memory: <span class="hljs-number">3198728</span>K, Thread Count: <span class="hljs-number">250</span>, Loaded Class Count: <span class="hljs-number">17743</span>, <span class="hljs-attr">Headroom</span>: <span class="hljs-number">0</span>%)
Enabling Java Native Memory Tracking
Adding <span class="hljs-number">127</span> container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=<span class="hljs-regexp">/layers/</span>paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=<span class="hljs-number">4</span> -XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -Xmx2572558K -XX:MaxMetaspaceSize=<span class="hljs-number">114169</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>


              |\      _,,,--,,_
             /,<span class="hljs-string">`.-'`</span><span class="hljs-string">'   ._  \-;;,_
  _______ __|,4-  ) )_   .;.(__`'</span>-<span class="hljs-string">'__     ___ __    _ ___ _______
 |       | '</span>---<span class="hljs-string">''</span>(_/._)-<span class="hljs-string">'(_\_)   |   |   |   |  |  | |   |       |
 |    _  |    ___|_     _|       |   |   |   |   |_| |   |       | __ _ _
 |   |_| |   |___  |   | |       |   |   |   |       |   |       | \ \ \ \
 |    ___|    ___| |   | |      _|   |___|   |  _    |   |      _|  \ \ \ \
 |   |   |   |___  |   | |     |_|       |   | | |   |   |     |_    ) ) ) )
 |___|   |_______| |___| |_______|_______|___|_|  |__|___|_______|  / / / /
 ==================================================================/_/_/_/

:: Built with Spring Boot :: 2.7.1
...
...</span>
</code></pre><p>Running the same image in a Linux machine with less resources results in different memory values, as it should:</p>
<pre><code># docker run -p <span class="hljs-number">8080</span>:<span class="hljs-number">8080</span> harbor.apps.cf.tanzutime.com/apps/spring-petclinic:<span class="hljs-number">2.7</span><span class="hljs-number">.1</span>-SNAPSHOT
Setting Active Processor Count to <span class="hljs-number">1</span>
JVM DNS caching disabled <span class="hljs-keyword">in</span> favor <span class="hljs-keyword">of</span> link-local DNS caching
Calculating JVM memory based on <span class="hljs-number">1221856</span>K available memory
For more information on <span class="hljs-built_in">this</span> calculation, see https:<span class="hljs-comment">//paketo.io/docs/reference/java-reference/#memory-calculator</span>
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -Xmx595686K -XX:MaxMetaspaceSize=<span class="hljs-number">114169</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xss1M (Total Memory: <span class="hljs-number">1221856</span>K, Thread Count: <span class="hljs-number">250</span>, Loaded Class Count: <span class="hljs-number">17743</span>, <span class="hljs-attr">Headroom</span>: <span class="hljs-number">0</span>%)
Enabling Java Native Memory Tracking
Adding <span class="hljs-number">127</span> container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=<span class="hljs-regexp">/layers/</span>paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=<span class="hljs-number">1</span> -XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -Xmx595686K -XX:MaxMetaspaceSize=<span class="hljs-number">114169</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>


              |\      _,,,--,,_
             /,<span class="hljs-string">`.-'`</span><span class="hljs-string">'   ._  \-;;,_
  _______ __|,4-  ) )_   .;.(__`'</span>-<span class="hljs-string">'__     ___ __    _ ___ _______
 |       | '</span>---<span class="hljs-string">''</span>(_/._)-<span class="hljs-string">'(_\_)   |   |   |   |  |  | |   |       |
 |    _  |    ___|_     _|       |   |   |   |   |_| |   |       | __ _ _
 |   |_| |   |___  |   | |       |   |   |   |       |   |       | \ \ \ \
 |    ___|    ___| |   | |      _|   |___|   |  _    |   |      _|  \ \ \ \
 |   |   |   |___  |   | |     |_|       |   | | |   |   |     |_    ) ) ) )
 |___|   |_______| |___| |_______|_______|___|_|  |__|___|_______|  / / / /
 ==================================================================/_/_/_/

:: Built with Spring Boot :: 2.7.1
...
...</span>
</code></pre><h3 id="heading-security-patching">Security Patching</h3>
<p>Cloud Native Buildpacks also have the added value of proactive security patching, which is an incredible feature to have in production. 
Every time you execute the plugin, Spring downloads the latest Paketo base builder image:</p>
<pre><code class="lang-bash">[INFO] --- spring-boot-maven-plugin:2.6.3:repackage (repackage) @ spring-petclinic ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] &lt;&lt;&lt; spring-boot-maven-plugin:2.6.3:build-image (default-cli) &lt; package @ spring-petclinic &lt;&lt;&lt;
[INFO]
[INFO]
[INFO] --- spring-boot-maven-plugin:2.6.3:build-image (default-cli) @ spring-petclinic ---
[INFO] Building image <span class="hljs-string">'docker.io/library/spring-petclinic:2.6.0-SNAPSHOT'</span>
[INFO]
[INFO]  &gt; Pulling builder image <span class="hljs-string">'docker.io/paketobuildpacks/builder:base'</span> 0%
[INFO]  &gt; Pulling builder image <span class="hljs-string">'docker.io/paketobuildpacks/builder:base'</span> 5%
[INFO]  &gt; Pulling builder image <span class="hljs-string">'docker.io/paketobuildpacks/builder:base'</span> 7%
[INFO]  &gt; Pulling builder image <span class="hljs-string">'docker.io/paketobuildpacks/builder:base'</span> 10%
...
[INFO]  &gt; Pulling builder image <span class="hljs-string">'docker.io/paketobuildpacks/builder:base'</span> 100%
</code></pre>
<p>The builder image is constantly updated with the latest security patches. Image building is done entirely inside this builder container.
Every time a new buildpack is available with the latest security patches, it will be applied to the newest build.</p>
<pre><code class="lang-bash">[INFO]  &gt; Running creator
[INFO]     [creator]     6 of 24 buildpacks participating
[INFO]     [creator]     paketo-buildpacks/ca-certificates   3.4.0
[INFO]     [creator]     paketo-buildpacks/bellsoft-liberica 9.8.0
[INFO]     [creator]     paketo-buildpacks/syft              1.20.0
[INFO]     [creator]     paketo-buildpacks/executable-jar    6.5.0
[INFO]     [creator]     paketo-buildpacks/dist-zip          5.4.0
[INFO]     [creator]     paketo-buildpacks/spring-boot       5.19.0
</code></pre>
<p>The resulting image is built from the latest <code>Run</code> base image, which is also constantly patched:</p>
<pre><code class="lang-bash">[INFO]     [creator]     Saving harbor.mysite.com/apps/spring-petclinic:2.6.0-SNAPSHOT...
[INFO]     [creator]     *** Images (179aab23f421):
[INFO]     [creator]           harbor.mysite.com/apps/spring-petclinic:2.6.0-SNAPSHOT
</code></pre>
<p>This process continues on every new rebuild. Pull the latest patched <code>Builder</code>, apply the latest patched buildpacks, use the latest patched <code>Run</code> image.</p>
<p>If you want to do this process at scale, you can run these builds directly on Kubernetes using the <a target="_blank" href="https://github.com/pivotal/kpack">kpack</a> open source project. With kpack, you will have the ability to retroactively patch the base OS without running full rebuilds, using a process called rebasing. kpack has an Enterprise-ready version called <a target="_blank" href="https://tanzu.vmware.com/build-service">Tanzu Build Service</a> that is more suited for enterprises and locations that have no internet access.</p>
<h3 id="heading-cloud-native-buildpacks-in-different-spring-boot-versions">Cloud Native Buildpacks in different Spring Boot versions.</h3>
<p>There are some differences that you should be aware of regarding the <code>build-image</code> implementation:</p>
<ol>
<li>If you're still using Spring Boot 2.3 (<em>why?</em>), the resulting image will not use the layered approach as described above. In Spring Boot 2.4 and later, this is the default. In order to gain the benefits of better OCI layering in 2.3, you'll need to provide the following configuration in your <code>pom.xml</code>:<pre><code class="lang-xml">         <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                 <span class="hljs-tag">&lt;<span class="hljs-name">layers</span>&gt;</span>
                     <span class="hljs-tag">&lt;<span class="hljs-name">enabled</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">enabled</span>&gt;</span>
                 <span class="hljs-tag">&lt;/<span class="hljs-name">layers</span>&gt;</span>
             <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
         <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
</code></pre>
</li>
<li>In Spring Boot 2.4 and above, there's an additional enhancement you get for free: The <code>build-image</code> process will also remove all the <code>spring-boot-starter</code> jars from the target image. These are really only needed at compilation time and not at runtime, so removing them results in smaller images and in better performance. </li>
<li>In Spring Boot 2.4 and above, you can configure your own custom registry for the target resulting image. Spring Boot will push the image to the remote registry for you as part of the build process - great for CI pipelines. For example:<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                 <span class="hljs-tag">&lt;<span class="hljs-name">image</span>&gt;</span>
                     <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>harbor.mysite.com/apps/spring-petclinic<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
                     <span class="hljs-tag">&lt;<span class="hljs-name">publish</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">publish</span>&gt;</span>
                 <span class="hljs-tag">&lt;/<span class="hljs-name">image</span>&gt;</span>
                 <span class="hljs-tag">&lt;<span class="hljs-name">docker</span>&gt;</span>
                     <span class="hljs-tag">&lt;<span class="hljs-name">publishRegistry</span>&gt;</span>
                         <span class="hljs-tag">&lt;<span class="hljs-name">username</span>&gt;</span>my-username<span class="hljs-tag">&lt;/<span class="hljs-name">username</span>&gt;</span>
                         <span class="hljs-tag">&lt;<span class="hljs-name">password</span>&gt;</span>my-password<span class="hljs-tag">&lt;/<span class="hljs-name">password</span>&gt;</span>
                     <span class="hljs-tag">&lt;/<span class="hljs-name">publishRegistry</span>&gt;</span>
                 <span class="hljs-tag">&lt;/<span class="hljs-name">docker</span>&gt;</span>
             <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
         <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
</li>
<li>Starting in Spring Boot 2.5 and later - you can also customize the buildpack that is used to create the resulting image. The Spring Boot plugin uses the <a target="_blank" href="https://paketo.io/docs/howto/java/">Paketo Java buildpack</a> by default. With this new feature, you can use buildpacks from other vendors such as those provided by <a target="_blank" href="https://cloud.google.com/blog/products/containers-kubernetes/google-cloud-now-supports-buildpacks">Google</a> or <a target="_blank" href="https://network.pivotal.io/products/tanzu-java-buildpack/">VMware Tanzu</a>, or even write your own. To reference a different set of buildpacks, use the following configuration:<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
             <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                 <span class="hljs-tag">&lt;<span class="hljs-name">image</span>&gt;</span>
                     <span class="hljs-tag">&lt;<span class="hljs-name">buildpacks</span>&gt;</span>
                         <span class="hljs-tag">&lt;<span class="hljs-name">buildpack</span>&gt;</span>file:///path/to/example-buildpack.tgz<span class="hljs-tag">&lt;/<span class="hljs-name">buildpack</span>&gt;</span>
                         <span class="hljs-tag">&lt;<span class="hljs-name">buildpack</span>&gt;</span>urn:cnb:builder:paketo-buildpacks/java<span class="hljs-tag">&lt;/<span class="hljs-name">buildpack</span>&gt;</span>
                     <span class="hljs-tag">&lt;/<span class="hljs-name">buildpacks</span>&gt;</span>
                 <span class="hljs-tag">&lt;/<span class="hljs-name">image</span>&gt;</span>
             <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
         <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
</li>
<li>In Spring Boot 2.5 and later, you can create additional image tags for the resulting image, to support better image management that is more suited to your organization. For more information, check out the <a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/maven-plugin/reference/htmlsingle/#build-image.customization">plugin documentation parameters</a>.</li>
</ol>
<blockquote>
<p>Use Cloud-Native Buildpacks if you can. You will get security patches, simplified workflow, and production-ready defaults.</p>
</blockquote>
<h1 id="heading-running-the-image">Running the image</h1>
<p>Finally, we have a working OCI image that we can use to run on Kubernetes.</p>
<p>But how do we run this thing?</p>
<p>There are many factors that need to go into consideration when running our image in Kubernetes. Let's review them one by one.</p>
<h2 id="heading-12-factor-apps">12 Factor apps</h2>
<p>The concept of developing <a target="_blank" href="https://12factor.net/">12 factor apps</a> is still as relevant as ever. Even if some factors seem obvious when running in containers and on Kubernetes, it's still good to have this north star. Things like dev/prod parity, scaling out via the process model (more containers!), disposability, outputting logs to the standard output, all fit perfectly into the Kubernetes model. There are those who advocate that in this day and age, we need <a target="_blank" href="https://tanzu.vmware.com/content/ebooks/beyond-the-12-factor-app">a few more of these factors</a>.
Although all of these factors are important, when it comes to Kubernetes there is one thing to consider above all else, so much so that it is sometimes referred to as the "<a target="_blank" href="https://tanzu.vmware.com/content/blog/1-factor-app-kubernetes-modernization">1-Factor app</a>": </p>
<blockquote>
<p>Can it be restarted gracefully?</p>
</blockquote>
<p>If it can, then you can let Kubernetes do a lot of things behind the scenes even if your application is not written perfectly according to all the best practices.</p>
<h2 id="heading-liveness-and-readiness-probes">Liveness and readiness probes</h2>
<p>To make sure your Spring application runs well on Kubernetes, you need understand how Kubernetes interacts with your application during various <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/">lifecycle phases</a>. This starts with having your Spring application notify the Kubernetes runtime that it is running and ready to accept traffic. </p>
<h3 id="heading-actuator-health-endpoint">Actuator health endpoint</h3>
<p>Spring Boot has has an endpoint that is designed for something like this. Adding <code>spring-boot-starter-actuator</code> to your classpath will allow you to have various production-ready endpoints that report health and metrics about of your application. <a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.exposing">By default</a>, the <code>health</code> endpoint is always exposed. In earlier versions of Spring Boot the <code>info</code> endpoint was also exposed by default, but this has since been removed. With the actuator starter in your classpath, you can now add the following to your Kubernetes deployment manifest in order to let Kubernetes know when your application is ready to accept traffic and if it is still considered "alive":</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
          <span class="hljs-string">...</span>
          <span class="hljs-attr">livenessProbe:</span>
            <span class="hljs-attr">httpGet:</span>
              <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
              <span class="hljs-attr">path:</span> <span class="hljs-string">/actuator/health</span>
          <span class="hljs-attr">readinessProbe:</span>
            <span class="hljs-attr">httpGet:</span>
              <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
              <span class="hljs-attr">path:</span> <span class="hljs-string">/actuator/health</span>
</code></pre>
<p>This is a good starting point. We're telling our application to accept traffic only when the Spring Boot actuator health endpoint returns <code>200 OK</code>. If during runtime the endpoint returns an error, we also tell Kubernetes to restart our pod.
But there are caveats... as always.</p>
<ol>
<li><p>Do you <em>really</em> want to restart your pod whenever the health endpoint returns an error? The default actuator endpoint usually returns the overall health of your application, including whether connectivity to a database or a message broker is working as expected. If you cannot access your database, restarting your application won't do much good beyond adding more strain on your (probably overloaded) database when new connection pools are created on each restart, which might actually cause a cascading failure. Perhaps implementing circuit breakers in your application and add alerting in your monitoring system is a better approach.
It does make more sense to set <em>readiness</em> probes based on external components to the application, but even then, "it depends". You might be ok with some Redis cache being inaccessible on startup, but not ok with the main database being inaccessible. </p>
</li>
<li><p>It's considered a best practice to assign a separate port to the actuator endpoints. That is because these endpoints can reveal sensitive metrics and information about your application, so moving these to a different port allows you to easily block access to this information, while keeping the main application port open to an external service or ingress. This is especially true for distributed and multi-tenant environments such as Kubernetes. To set a different management port, set the following property:</p>
<pre><code>management.server.port=<span class="hljs-number">9090</span>
</code></pre><p>However, there's a catch here - when it comes to readiness and liveness - you want these probes to exist on the main <em>server</em> application port, and not the management port. That is because probing the management port might result in false positives - the management port might be responding to your requests just fine, while the actual application is still trying to start up correctly. </p>
</li>
<li><p>Actuator health endpoints are mostly based around HTTP readiness. If your application is a batch application or a streaming application, you need to provide other verifiers to report if it is ready or not (<em><a target="_blank" href="https://www.youtube.com/watch?v=7YUE1F1Is1w&amp;ab_channel=GuiLopesBH">here I come, you can't hide...</a></em>).</p>
</li>
</ol>
<p>Luckily, Spring has you covered.</p>
<h3 id="heading-spring-liveness-and-readiness-health-groups">Spring Liveness and Readiness Health Groups</h3>
<p>Spring Boot 2.2 introduced the concept of <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.2.x/reference/html/production-ready-features.html#health-groups">Health Groups</a>, allowing you to group multiple actuator health indicators under a single group. You can then define specific properties for that particular group only.
Spring Boot 2.3 <a target="_blank" href="https://spring.io/blog/2020/03/25/liveness-and-readiness-probes-with-spring-boot">took this concept further</a> by creating two health groups out of the box: a readiness group and a liveness group. To have these two groups enabled, the following setting must be enabled:</p>
<pre><code class="lang-properties">management.endpoint.health.probes.enabled=true
</code></pre>
<p>To make your life easier, Spring sets this property to <code>true</code> automatically when it detects the application runs in Kubernetes.</p>
<p>With this new setup, you get the full-blown and detailed health information including dependent resources under <code>/actuator/health</code>, and two separate sub-groups: <code>/actuator/health/readiness</code> and <code>/actuator/health/liveness</code>. Based on the reasons explained in item #1 above, Spring Boot by default <strong>does not rely on any external state</strong> for readiness and liveness, it is only checking the internal application state. Since these are now two separate groups, you can also customize each group with its own set of checks that is suitable for your application. For example, if your application must have database connectivity before it can report it is ready to accept traffic, you can add that check to the list:</p>
<pre><code class="lang-properties">management.endpoint.health.group.readiness.include=readinessState,db
</code></pre>
<p>You might even want to write your own <a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints.kubernetes-probes.external-state">custom checks</a> if you have some unique readiness requirements that are not part of the built-in verifiers. For example, imagine that your application relies heavily on a third party SaaS API such as Twilio to be available before the application is ready to accept traffic. Write a custom verifier that checks the API endpoint and add it to the list:</p>
<pre><code class="lang-properties">management.endpoint.health.group.readiness.include=readinessState,externalTwilioVerifier
</code></pre>
<p>On the other extreme, if your application is a batch or a streaming application that doesn't accept web traffic at all, you might want to rely entirely on a specific external service such as a message broker and nothing else:</p>
<pre><code>management.endpoint.health.group.readiness.include=rabbitHealthIndicator
</code></pre><p>So, we now have better control of what exactly does it mean to have our application "ready" and "alive". </p>
<p>But we are still conflicted with the fact that most actuator endpoints should live on a separate "management port" for security reasons, while we want our readiness and liveness probes to live on the application port. The Spring documentation for version 2.3 has a <a target="_blank" href="https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/html/production-ready-features.html#production-ready-kubernetes-probes">big yellow warning</a> specifically on this:</p>
<blockquote>
<p>If your Actuator endpoints are deployed on a separate management context, be aware that endpoints are then not using the same web infrastructure (port, connection pools, framework components) as the main application. In this case, a probe check could be successful even if the main application does not work properly (for example, it cannot accept new connections).</p>
</blockquote>
<p>Luckily, starting with <a target="_blank" href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#additional-path-on-main-or-management-port">Spring Boot 2.6</a>, there is a good solution for this. You can now set a health group as an additional path to either the main application port or to the management port. For example:</p>
<pre><code>management.endpoint.health.group.readiness.additional-path=<span class="hljs-string">"server:/readyz"</span>
management.endpoint.health.group.liveness.additional-path=<span class="hljs-string">"server:/livez"</span>
</code></pre><p>In addition to whatever was configured as the management port and path, this setting will also make the readiness endpoint available on the main (<code>server:</code>) port, under the HTTP path <code>readyz</code>, and make the liveness endpoint available on the main port under the HTTP path <code>livez</code>. 
To set a health group to the management port instead of the main server port, use <code>management:</code> as the prefix.</p>
<h3 id="heading-some-more-liveness-and-readiness-settings">Some more liveness and readiness settings</h3>
<ul>
<li><p>A container is considered alive or ready if it returns <em>any</em> HTTP response code between 200-399. Kubernetes doesn't really care what is returned as long as it is not 4xx and 5xx.</p>
</li>
<li><p>Some additional settings that could be relevant when defining your liveness and readiness probes: </p>
</li>
</ul>
<pre><code class="lang-yaml">    <span class="hljs-attr">livenessProbe:</span>
      <span class="hljs-attr">failureThreshold:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">httpGet:</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/livez</span>
        <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>
      <span class="hljs-attr">timeoutSeconds:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">5</span>
    <span class="hljs-attr">readinessProbe:</span>
      <span class="hljs-attr">failureThreshold:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">httpGet:</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/readyz</span>
        <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">initialDelaySeconds:</span> <span class="hljs-number">30</span>
      <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>
      <span class="hljs-attr">timeoutSeconds:</span> <span class="hljs-number">1</span>
</code></pre>
<ul>
<li><code>periodSeconds</code> tells Kubernetes how often to poll the endpoint.</li>
<li><code>timeoutSeconds</code> tells Kubernetes when to timeout the HTTP request (in seconds) and consider it an error that requires the pod to be restarted.</li>
<li><code>initialDelaySeconds</code> indicates how long to wait before probing. This is especially important for Spring Boot applications that might require a long startup time. Profile your application to figure out the preferred values here. Beware of using an unnecessary long value, because your pod will not be available until that time has passed. Also note that the <code>initialDelaySeconds</code> for livenessProbe only begins polling <em>after</em> the application is marked as <code>Ready</code>.</li>
<li><code>failureThreshold</code> defines how many times a failed response is considered "ok". You probably don't want to immediately kill your pod if a temporary network glitch returned a bad gateway once or twice.</li>
</ul>
<blockquote>
<p>Use Spring's built-in readiness and liveness health probes. Keep the probes on the main application port, and set a separate management port for other actuator endpoints. </p>
</blockquote>
<h2 id="heading-graceful-shutdown">Graceful Shutdown</h2>
<p>Ok, we took care of the startup lifecycle. But what about shutdown? In Kubernetes, shutdowns are inevitable. Whenever a cluster node is being upgraded, removed or resized, two things happen:</p>
<ul>
<li>A process called <code>cordon</code> prevents Kubernetes from scheduling new pods on the node (it's status is changed to <code>SchedulingDisabled</code>).</li>
<li>A process called <code>drain</code> evicts all the pods from the node.</li>
<li>Maintenance on the node can now be performed (delete/upgrade/resize etc.).</li>
</ul>
<p>Another common use case for shutting down pods is when autoscaling is being utilized, such as when using a <a target="_blank" href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">Horizontal Pod Autoscaler</a>, or a runtime like <a target="_blank" href="https://knative.dev">Knative</a> that even scales to zero when there is no traffic.</p>
<p>Regardless of the reason for the shutdown, we want to make sure that our application doesn't get killed abruptly when it is still processing in-flight HTTP requests or while executing queries against our database. If it gets killed too soon, some clients will receive errors and some database transactions might not be updated (or even corrupted, depending on the database).</p>
<h3 id="heading-configuring-graceful-shutdown">Configuring graceful shutdown</h3>
<p>By default, even in the latest versions of Spring, the Web server associated with the application will be shutdown immediately. 
Setting a graceful shutdown in Spring used to be quite cumbersome and required some <a target="_blank" href="https://github.com/gesellix/graceful-shutdown-spring-boot">custom solutions</a>. Thankfully, starting with Spring Boot 2.3, graceful shutdown is supported out of the box:</p>
<pre><code class="lang-properties">server.shutdown=graceful
</code></pre>
<p>With this property set, when the container receives a <code>SIGTERM</code> to begin shutdown, Spring will block the associated Web server (Tomcat, Netty etc.) from accepting any new connections before actually shutting down. We now have a time window to handle in-flight requests without receiving new requests. </p>
<p>We now want to wait for all the in-flight requests to complete before exiting the Spring application.
For this, we have a timeout property for the shutdown phase, which defaults to 30 seconds. To override this default, change the property as follows:</p>
<pre><code class="lang-properties">spring.lifecycle.timeout-per-shutdown-phase=20s
</code></pre>
<p>It is likely that you only want to set these parameters while running in the context of Kubernetes. Luckily, this is easily doable since Spring automatically adds a profile called <code>kubernetes</code> when it detects the application runs in Kubernetes:</p>
<pre><code class="lang-properties">spring.application.name: "SpringPetclinic"
---
spring.application.name: "SpringPetclinicKubernetes"
spring.config.activate.on-cloud-platform: "kubernetes"
spring.lifecycle.timeout-per-shutdown-phase=20s
server.shutdown=graceful
</code></pre>
<p>In the example above, the shutdown properties are only applied if the application runs in Kubernetes. Outside of Kubernetes, the server will shutdown immediately. Also, just for fun, we're changing the application name.</p>
<h3 id="heading-prevent-new-requests-coming-to-a-pod-that-shuts-down">Prevent new requests coming to a pod that shuts down</h3>
<p>Kubernetes is a distributed system working in event loops. As such, eventual consistency considerations must be taken into account. One classic example of this is the fact that a pod might begin the shutdown lifecycle, while other Kubernetes resources continue to route traffic to the pod.
In order to block new requests from coming in to the pod when the shutdown flow begins, we can configure a <code>preStop</code> phase in our pod lifecycle:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">odedia/spring-petclinic</span>
    <span class="hljs-string">...</span>
    <span class="hljs-attr">lifecycle:</span>
      <span class="hljs-attr">preStop:</span>
        <span class="hljs-attr">exec:</span>
          <span class="hljs-attr">command:</span> [<span class="hljs-string">"sh"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"sleep 10"</span>]
  <span class="hljs-attr">terminationGracePeriodSeconds:</span> <span class="hljs-number">30</span>
</code></pre>
<p>In this simple use case, we're telling our application to sleep for 10 seconds once the shutdown lifecycle begins. We allow other components this time window to figure out that our pod is no longer in the game. During that time, initially new requests might still come through to the main application running in the container, but eventually new requests should not be routed. You might need to tweak this wait time based on the size of your cluster and configuration.
After this <code>preStop</code> phase completes, we are now at a stage where new requests are (hopefully) not being routed to our pod, while existing in-flight requests are being executed within the <code>timeout-per-shutdown-phase</code> window.</p>
<h3 id="heading-termination-grace-period">Termination grace period</h3>
<p>The flow of a shutdown is as follows:</p>
<ol>
<li><code>preStop</code> is being executed (in our example we're basically waiting for 10 seconds for Kubernetes to get its resources in order with our shutdown).</li>
<li>Kubernetes sends a <code>SIGTERM</code> to our pod.</li>
<li>Our pod receives the signal and begins the shutdown process. For a graceful shutdown this means blocking any new requests from our Web server and waiting for in-flight requests to complete, up to the value of <code>timeout-per-shutdown-phase</code>.</li>
</ol>
<p>Here's the caveat: the value for <code>terminationGracePeriodSeconds</code> defines how long Kubernetes will wait before violently killing your pod with a <code>SIGKILL</code> signal. The default is 30 seconds.
But when should we start counting these 30 seconds?
The answer is: from the <a target="_blank" href="https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#hook-handler-execution">beginning of the <code>preStop</code> phase</a>.
In practice, this means that you should set the <code>terminationGracePeriodSeconds</code> to a value higher than the sum of the <code>preStop</code> execution time + <code>timeout-per-shutdown-phase</code> time, otherwise your pod will always get killed abruptly. 
It also means that if you're using nothing but the defaults, you're in for a world of hurt, because the default <code>timeout-per-shutdown-phase</code> is 30 seconds and the default <code>terminationGracePeriodSeconds</code> is also 30 seconds! Great, another distributed systems race of "who dies first?".</p>
<blockquote>
<p>Use Spring's graceful shutdown setting while running in Kubernetes. Make sure timeout settings are set correctly so they do not create conflicts.</p>
</blockquote>
<h2 id="heading-cpu-requests-and-limits">CPU Requests and Limits</h2>
<p>Now that our application adheres to the "1-Factor app" - that is, it can be restarted gracefully, we turn our head to the important discussion of CPU requests and CPU limits.
As you recall, the JVM makes a lot of assumptions based on the available CPU and memory - things like which garbage collection to use, the size of Fork/Join thread pools, and various libraries will make decisions based on the value of <code>Runtime.getRuntime().availableProcessors()</code>.</p>
<p>CPU requests/limits are measured in CPU millicores. 1000 millicores translates into 1 vCPU in a virtualized environment such as the public cloud or vSphere, or a physical hyperthread if you run on bare metal. This is another plus to running Kubernetes in virtualized environments, even if you have your own hardware in a data center, because you get an additional layer of abstraction that allows you to share and over-provision resources on the same hardware.</p>
<p>A CPU <em>request</em> in Kubernetes is the minimum guaranteed amount of CPU that Kubernetes will assign to your workload.</p>
<p>A CPU <em>limit</em> measures the maximum amount of CPU that your workload can utilize before being throttled. </p>
<p>It's important to note that CPU requests are <a target="_blank" href="https://twitter.com/odedia/status/1425853578288246791">not directly related to speed or performance</a>. It may indirectly impact performance, but at its core, CPU requests are designed to help Kubernetes make better decisions on <em>resource allocation</em>. Nothing more. </p>
<p>Since CPU limits could eventually throttle your application, it could have performance implications.</p>
<p>Let's review the tradeoffs when setting various CPU requests and limits combinations:</p>
<ol>
<li><p><strong>CPU requests and limits are set to the same value:</strong> You have predictable and guaranteed CPU allocation for your pod. You might think this is exactly what you need, but if you think about how a typical Spring Boot / Java application behaves - it usually does a lot more on startup in order to create thread pools, scan for Spring beans and other startup activities. This warm-up time will greatly benefit from more CPU cores than are needed during normal operation.</p>
</li>
<li><p><strong>CPU requests and CPU limits are not set</strong>: Welcome to the wild west. You have zero predictability. Your pods can be killed at any point when new workloads that <em>do</em> have CPU requests need to allocate available capacity. <strong>Specifically for JVM workloads before JDK 19</strong>, it will actually mean that you <a target="_blank" href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8281571">limit each container to just a single vCPU</a>. For workloads on JDK 19 and later, it will try to use as many vCPUs as available on the host. Please don't use this setting.</p>
</li>
<li><p><strong>CPU requests are set to a value lower than the CPU limits</strong>: You get guarantees about the typical CPU consumption that your application needs, but can also burst up to the defined CPU limits during warmup time or if a sudden spike in traffic reaches your pod. </p>
</li>
</ol>
<p>Option 3 does seem like the best of both worlds. You have guaranteed resources that will not be taken away when other resources request CPU capacity on the worker node. In fact, requests (which are based on <a target="_blank" href="https://www.batey.info/cgroup-cpu-shares-for-docker.html#:~:text=CPU%20shares%20(cpu_share)%20are%20a,running%20in%20the%20same%20cgroup.">container CPU shares</a>) are <em>relative</em>. This means that they are only taken into consideration when there are constraints on the worker node from other competing resources. Otherwise, the container can use as much CPU as it needs up to the host limit, or the  configured value for CPU limits. So, this begs the question: what should you set as the CPU limits? </p>
<p>How about <strong>No CPU Limits</strong>?</p>
<p>As this blog post <a target="_blank" href="https://home.robusta.dev/blog/stop-using-cpu-limits/">wonderfully articulates</a>, you really don't get any meaningful benefits by assigning CPU limits. You're only preventing your containers from using the excess CPU capacity that was <strong>not</strong> assigned to other containers. Why would you ever want to artificially prevent your workloads from utilizing <em>unused CPU cycles</em>? Perhaps it makes sense to set limits in a staging/performance environments where you want have <strong>predictable results</strong> to make decisions about production, but once you're in production you really want to squeeze every bit of available CPU cycles as you could possibly get. Why limit yourself?</p>
<p>Besides, the whole mechanism that Kubernetes used to assign CPU quotas/limits is based on the amount of <em>time</em> you're getting before being throttled, relative to the total number of cores on the worker node. Do you have nodes in production with different CPU capacities, or have some worker nodes with more containers compared to others? You'll get different results on each of these worker nodes. In short - the results will be unpredictable.</p>
<p>Still not convinced? This set of blog posts [<a target="_blank" href="https://medium.com/directeam/kubernetes-resources-under-the-hood-part-1-4f2400b6bb96">1</a>] [<a target="_blank" href="https://medium.com/directeam/kubernetes-resources-under-the-hood-part-2-6eeb50197c44">2</a>] [<a target="_blank" href="https://medium.com/directeam/kubernetes-resources-under-the-hood-part-3-6ee7d6015965">3</a>] goes deep into the underpinnings of Kubernetes resources, and also reaches the same conclusion:</p>
<blockquote>
<p>"Most of us are trying to achieve the best possible performance on the cheapest infrastructure while minimizing downtime. Production workloads should be able to utilize idle CPU. Containers won’t “steal” CPU from other containers if you set your CPU requests right, if you didn’t set CPU requests or set them badly, I’m afraid that the CPU limit wouldn’t save you."</p>
</blockquote>
<p>If that was not enough, here's a recommendation from Tim Hockin, one of the original founders of the Kubernetes project at Google:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/thockin/status/1134193838841401345?s=20&amp;t=Em1V4zVj2NHmj3XOGcxeiQ">https://twitter.com/thockin/status/1134193838841401345?s=20&amp;t=Em1V4zVj2NHmj3XOGcxeiQ</a></div>
<p>The important setting is the CPU <em>requests</em> - this will tell Kubernetes how many resources to allocate for your workload at a minimum. Setting this to too low could result in bad performance if you have a lot of other workloads on the node, and will also get  you closer to the wild west scenario, since all workloads will fight for maximum resources that they should have allocated in the first place. Setting this too high could result in wasted resources and having fewer workloads running on the each node. Profile your apps before production!</p>
<p>Here's a snippet of CPU configuration from the Spring Petclinic deployment:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">spring-petclinic</span>
<span class="hljs-string">...</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">harbor.mysite.com/app/spring-petclinic:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">spring-petclinic</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"2000m"</span>
</code></pre>
<p><em>Important note</em>: if you're using a fully managed Kubernetes solution such as Google Autopilot, you don't have this level of control for your deployments. Google Autopilot specifically will set the <a target="_blank" href="https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests#resource-limits">CPU limits to the same value of the CPU requests</a>, regardless of what you configured. This <a target="_blank" href="https://twitter.com/dbradstock/status/1578173379663667200">might change</a> in the future, but the bottom line is that you don't have as much control with these managed solutions, since they prefer predictability above all else. If you want to have complete control, use an upstream vanilla Kubernetes distribution such EKS, GKE, AKS or Tanzu Kubernetes Grid.</p>
<h3 id="heading-things-get-complicated">Things get complicated</h3>
<p>If you think everything is good, I have some bad news - things keep changing between JDK versions and could have untrustworthy behaviour based on a combination of your JDK, your CPU requests/limits settings, and available resources.</p>
<p>Here's one example: a very recent bug report in JDK 19 and later <a target="_blank" href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8281571">JDK-8281571</a> suggests <strong>not</strong> to use CPU shares to compute the JVM active processor count, and instead use the CPU limits (whaaat?!). </p>
<p>To understand why that is, we need to understand how Kubernetes uses assigned CPU requests, which are translated to CPU shares per container. </p>
<p>CPU requests are the <em>guaranteed</em> value. But, as briefly mentioned previously, if nothing else runs on the node, you'll get more resources. That's why we didn't want to set a limit in the first place. Let's review this with a sample 16-core worker node:</p>
<ul>
<li>For a single container on the node requesting 4000 millicores, the container will actually get all 16 cores because nothing else runs on the node. So, each requested core is translated to 16/4 actual cores --&gt; 4 actual cores for each requested core.</li>
<li>When a second container requesting 4000 millicores joins the node (total of 8000 millicores requested on the node), each container will now receive 8 cores. Each requested core is now translated to 16/8 actual cores --&gt; 2 actual cores for each requested core.</li>
<li>When a third container requesting 2000 millicores joins the node (total 10 cores requested on the node), the first and second containers will receive 6400 millicores each, and the third container will get 3200 millicores. Each requested core is now translated to 16/10 actual cores --&gt; 1.6 actual cores for each requested core.</li>
</ul>
<p>For the JDK, the CPU shares suddenly become an unreliable value. The computed value for <code>ActiveProcessorCount</code> <em>will drift over time</em>.</p>
<p>Houston, we have a problem. </p>
<p>Actually, we have two! The bug report also provides an example that shows a very problematic setting of CPU shares: <code>1024</code>, which is the default setting if no CPUs were requested. While that should be translated to just 1 vCPU, it is actually being translated to (<em>again!</em> 🤦🏻‍♂️) the total number of available cores on the host machine:</p>
<pre><code> - docker run ... --cpu-shares=<span class="hljs-number">512</span> java ....    ==&gt; os::active_processor_count()  = <span class="hljs-number">1</span>
 - docker run ... --cpu-shares=<span class="hljs-number">1024</span> java ...   ==&gt; os::active_processor_count()  = <span class="hljs-number">32</span> (total CPUs on <span class="hljs-built_in">this</span> system)
 - docker run ... --cpu-shares=<span class="hljs-number">2048</span> java ...   ==&gt; os::active_processor_count()  = <span class="hljs-number">2</span>
</code></pre><p>This all seems like one big mess, isn't it?</p>
<p>Thankfully, there's a relatively simple solution to this: <strong>Don't let the JVM decide the number of active processors automatically</strong>. Profile the actual usage needed for your app, and just set it yourself as part of the <code>JAVA_TOOL_OPTIONS</code> parameter <code>-XX:ActiveProcessorCount</code>. You have the power, and the JVM will honor the setting regardless of how many actual vCPUs are given to it by the container. You can set the CPU requests to 2000 millicores, and the ActiveProcessorCount to 4 for bursting. It will work.</p>
<blockquote>
<p>Set CPU requests after profiling your application. Do not set CPU limits at all. Always set the active processor count explicitly.</p>
</blockquote>
<h2 id="heading-memory-requests-and-limits">Memory Requests and Limits</h2>
<p>So we have pretty good idea of what to do with CPU requests and limits. Should the same be applied to memory requests and limits?</p>
<p>Probably not. CPU constraints are a considered a <a target="_blank" href="https://cloud.google.com/blog/products/containers-kubernetes/kubernetes-best-practices-resource-requests-and-limits">compressed resource</a> in Kubernetes. This means that if needed, Kubernetes can throttle your pod to make "room" for other workloads. 
The same cannot be said for memory. If you go past your memory limit, the container will be terminated. </p>
<p>Let's review the options we have for setting memory requests and limits:</p>
<ol>
<li><strong>Memory requests and limits are set to the same value:</strong> You are guaranteed the requested amount of RAM even if the worker node is low on memory.</li>
<li><strong>Memory requests is less than memory limits:</strong> If you are using more than the memory requested but less than the memory limit, and the worker node requires memory for other workloads, your pod will be <em>terminated</em>. Once memory is given to a pod, it cannot be taken away without restarting that pod.</li>
<li><strong>Memory request and memory limits are not set:</strong> Again, this is the wild west scenario. Pods will be terminated when a worker node is low on memory.</li>
</ol>
<p>This one seems simpler:</p>
<blockquote>
<p>For Java workloads, always set the memory requests and memory limits to the same exact value.</p>
</blockquote>
<p>Here's a snippet for both the CPU and memory configuration from the Spring Petclinic deployment:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">spring-petclinic</span>
<span class="hljs-string">...</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">harbor.mysite.com/app/spring-petclinic:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">spring-petclinic</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"2000Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"2000m"</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"2000Mi"</span>
</code></pre>
<p>We now have a good setup for the container's memory. But what about the JVM's memory configuration? By default, the JVM sets the heap size to 1/4 of the available memory, which might not be the best default. What should we pass to the <code>JAVA_TOOL_OPTIONS</code> environment variable? Remember that your JVM lives in a container now, so your JVM memory should be a portion of the total available memory given to the container itself. The answer, as always, is that it depends. You could probably get away with setting the JVM memory to be a percentage of the container's memory. For example, the argument <code>-XX:MaxRAMPercentage=75.0</code> will set the JVM's Max heap memory to 75% of the container's memory, but this seems too generalized. There are different memory regions created by the JVM (Heap, Stack, DirectMemory etc.). Use something like the Java Memory Calculator mentioned in the previous section to calculate the correct values. Apply the results as an environment variable in the deployment manifest, or (if you use cloud-native buildpacks), set some values in the deployment manifest and let cloud-native buildpacks calculate the rest on startup. </p>
<h2 id="heading-putting-it-all-together">Putting it all together</h2>
<p>We now have a pretty solid understanding of what JVM arguments to pass to <code>JAVA_TOOL_OPTIONS</code> related to memory. Add to that the parameter for <code>-XX:ActiveProcessorCount</code>, and we're in a good place. </p>
<p>You may also consider explicitly setting which garbage collector to use. If you set the active processor count to a single core, it will default to SerialGC. If you have 2 cores and approximately 2 GB of RAM, you'll get ParallelGC or G1GC based on the JVM version you're using. To avoid ambiguity, set it explicitly using <code>-XX:+UseG1GC</code> or <code>-XX:-UseParallelGC</code>, and other <a target="_blank" href="https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html">related arguments</a>. If you don't have multiple cores available for parallel garbage collection, stick to SerialGC with <code>-XX:-UseSerialGC</code>. </p>
<p>When profiling your application, <a target="_blank" href="https://twitter.com/brunoborges/status/1567498812159131649">consider scaling up (CPU and RAM)</a> before scaling out. You may be surprised by the performance and even cost benefits fewer beefy containers provide compared to many small containers.</p>
<p>My colleague <a class="user-mention" href="https://hashnode.com/@asaikali">Adib Saikali</a> has a very good summary for all of this:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/asaikali/status/1578761487543173122?s=20&amp;t=XHyIVJEnKAt2odbxCZZSOQ">https://twitter.com/asaikali/status/1578761487543173122?s=20&amp;t=XHyIVJEnKAt2odbxCZZSOQ</a></div>
<h1 id="heading-spring-cloud">Spring Cloud</h1>
<p>Spring Cloud is a popular set of components that is designed to ease the development of microservices based on Spring Boot. It began with a set of components that originally came from Open Source Netflix projects such as Eureka for service discovery and Hystrix for circuit breaking (later replaced by the Spring Cloud Circuit Breaker abstraction), and evolved over the years to include projects such as Spring Cloud Stream for event-driven applications, Spring Cloud Sleuth for distributed tracing, and Spring Cloud OpenFeign for simplified HTTP clients.
While most of these Spring Cloud projects still make sense in a Kubernetes world, two of them stand out: Spring Cloud Eureka and Spring Cloud Config Server. These two solutions were developed before Kubernetes even existed, and were designed to solve two important pain points for microservices:</p>
<ul>
<li>How do I find other microservices in a distributed system, without having to manually manage a list of IP addresses or DNS records?</li>
<li>How do I externalize my configuration to a central repository so my application can remain portable between environments?</li>
</ul>
<p>Along with Spring Cloud Eureka, Spring Cloud Ribbon (and later Spring Cloud LoadBalancer) provided the ability call the available instances of a given microservice, based on load balancing rules such as simple round robin or advanced weighted-based routing.</p>
<p>Here's a typical Spring Cloud architecture without Kubernetes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665595803800/_UktZSzQr.png" alt="image.png" class="image--center mx-auto" /></p>
<p>However, in Kubernetes there is already built-in solutions for service discovery and configuration management.</p>
<p>The built-in <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">Kubernetes Service</a> resource allows you to expose a set of pods/deployments behind a single internal cluster-wide IP address. It also assigns an internal DNS record to the service so it can be easily found inside the same namespace and even across namespaces in the same cluster.</p>
<p>For external configuration, Kubernetes provides the concept of ConfigMaps to manage a set of properties, and the notion of a Kubernetes Secret to configure sensitive data such as usernames and passwords.</p>
<p><em>If you can</em>, prefer the Kubernetes-native solution. It will work interchangeably with other resources in the cluster written in other languages, and simplify your code since you don't have to use Spring Cloud dependencies at all.</p>
<ul>
<li><p>For service discovery - things are rather simple: for a given Kubernetes Service <code>my-other-service</code>, simply invoke the URL <code>http://my-other-service</code> and Kubernetes will take care of routing your Spring client application to the target server, load balancing requests based on the available number of instances. By default, you will lose the ability to perform sophisticated client-side load balancing operations such as weighted routing (unless you use Service Mesh solutions like Istio), but for the vast majority of use cases, it is more than adequate. If your service lives in another namespace, and provided you have the appropriate permissions assigned to the service account that runs your Spring application, you can make HTTP calls using <code>http://my-other-service.the-other-namespace.svc.cluster.local</code>.</p>
</li>
<li><p>For external configuration, you can fairly easily <a target="_blank" href="https://spring.io/guides/topicals/spring-on-kubernetes/">create a ConfigMap</a> from your existing <code>application.properties</code> or <code>application.yaml</code> and mount it to your deployment:</p>
</li>
</ul>
<pre><code class="lang-bash">$ kubectl create configmap my-spring-config --from-file=./k8s/application.properties
</code></pre>
<p>The result will be your <code>application.properties</code> or <code>application.yaml</code> configured as a single literal in the resulting ConfigMap:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">ConfigMap</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-spring-config</span>
<span class="hljs-attr">data:</span>
  <span class="hljs-attr">application.properties:</span> <span class="hljs-string">|
    database=h2
    management.endpoints.web.exposure.include=*
    logging.level.org.springframework=INFO</span>
</code></pre>
<p>The deployment can then be mounted to your Spring deployment manifest as follows:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">spring-petclinic</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">spring-petclinic</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">harbor.mysite.com/app/spring-petclinic:v1</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">spring-petclinic</span>
          <span class="hljs-string">...</span>
          <span class="hljs-attr">env:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">SOME_ENV_VARIABLE</span>
              <span class="hljs-attr">value:</span> <span class="hljs-string">hello</span>
          <span class="hljs-attr">volumeMounts:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">config-volume</span>
              <span class="hljs-attr">mountPath:</span> <span class="hljs-string">/workspace/config</span>
      <span class="hljs-attr">volumes:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">config-volume</span>
          <span class="hljs-attr">configMap:</span>
            <span class="hljs-attr">name:</span> <span class="hljs-string">my-spring-config</span>
</code></pre>
<p>In the deployment manifest above, we mounted the ConfigMap containing <code>application.properties</code> and also set some environment variable directly in the deployment yaml. Just because we can.</p>
<p>Here's the resulting Spring Boot application with Kubernetes-native services:
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665596067810/28qgSJ6PJ.png" alt="image.png" class="image--center mx-auto" /></p>
<p>One could argue that Spring Cloud Config is better for auditing because your configuration is usually saved to a git repo which has version control and commit history, but that should also be true for your ConfigMaps and other Kubernetes resources (except secrets which should be saved in other secured storage such as Vault). </p>
<p>The algorithm that Spring uses to load properties from git does have more advanced logic than a simple ConfigMap. It allows for interesting topologies where generic configuration resides in a default <code>application.properties</code> while a microservice-specific configuration resides at a <code>my-microservice.yaml</code> or even <code>my-microservice-forProfile.yaml</code>. Still, these things are also (mostly) possible with a mix of ConfigMaps, Secrets and deployment environment variables.</p>
<p>You do lose one thing when you opt for Kubernetes-native Configuration: if your ConfigMap changes during runtime, the volume changes immediately but your Spring application will not get the updated values until it is restarted. With Spring Cloud Config, you had the ability to invoke <code>POST /actuator/refresh</code> on each of your instances, or to register for configuration change notifications using <a target="_blank" href="https://spring.io/projects/spring-cloud-bus">Spring Cloud Bus</a> backed by a message broker. Both solutions would update your configuration at runtime without requiring a restart. </p>
<p>With Kubernetes ConfigMaps, all you can really do is restart your instances. If you have multiple instances of the application for high availability (as you should in production), you shouldn't experience downtime. You can run a rolling restart command when a ConfigMap has changed as follows:</p>
<pre><code class="lang-bash">kubectl rollout restart deployment/spring-petclinic
</code></pre>
<p>A rolling restart adheres to all the lifecycle phases we described above including the <code>preStop</code> hook and <code>gracefulShutdown</code>, so it could take some time to complete, but at least your application will not experience any downtime.</p>
<p>This all seem great for greenfield applications, but the reality is that there are a lot of applications out there that are already invested in Spring Cloud Eureka and in Spring Cloud Config. Migrating all of these brownfield applications to their Kubernetes-native counterparts could be quite the challenge, especially if your code relies heavily on annotations related to Eureka or if your git repo containing the configuration has a complex loading hierarchy.</p>
<p>Luckily, the Spring Cloud team has a solution for such applications, in the form of Spring Cloud Kubernetes. You can remove all references to <code>spring-cloud-starter-config</code> and <code>spring-cloud-starter-netflix-eureka-server</code> from your classpath, and replace them with the <code>spring-cloud-kubernetes-all</code> dependency. This dependency is a drop-in replacement that will let you easily migrate your Spring Cloud application to a Kubernetes world:</p>
<ul>
<li>The discovery client will now query the Kubernetes API server for available resources, instead of querying a Eureka server.</li>
<li>Spring Cloud Config Client will look for attached ConfigMaps for loading the configuration, instead of relying on a Config Server.</li>
</ul>
<p>These features are only enabled if you have the <code>kubernetes</code> profile configured. Luckily, when deploying a Spring boot application to Kubernetes, Spring adds this profile for you automatically.</p>
<p>However, there are things you should be aware of when deploying this sort of solution to production:</p>
<p>By default, all the instances that utilize the Spring Cloud Config Client or Spring Cloud Discovery Client <strong>will need access to the Kubernetes control plane's API server</strong>. This means that the service account used to run the Spring pod will need permissions to access the Kubernetes control plane.</p>
<p>You will also need to setup a Kubernetes NetworkPolicy that allows access to the Kubernetes API Server. My colleague <a target="_blank" href="https://twitter.com/svrc">Stuart Charlton</a> explains it as follows:</p>
<ol>
<li>The Spring Boot with the Spring Cloud Kubernetes dependency calls the Kubernetes API Server to read the ConfigMap.</li>
<li>The API endpoint Spring Cloud Kubernetes uses for this is the <a target="_blank" href="https://kubernetes.io/docs/tasks/run-application/access-api-from-pod/">internal service DNS for the control plane</a>. That is to say - <code>kubernetes.default.svc.cluster.local</code>. This is an internal, cluster-bound service with a custom endpoint, pointing at the Kubernetes API IP and port.</li>
<li>The flow is: Pod network IP --&gt; K8s Internal Service (<code>kubernetes.default.svc.cluster.local</code>) --&gt; The K8s API Server.</li>
</ol>
<p>So, your Kubernetes Egress CIDR block will need access to both the external API server IP and port, and potentially (depending on the Container Network Interface/CNI you have in your cluster), to the internal Kubernetes service <code>kubernetes.default.svc.cluster.local</code>.</p>
<p>This is a lot of headache infrastructure talk, isn't it? All of these considerations now have to go into your Spring Boot workload? Seems a bit of an "infrastructure code smell".</p>
<p>Luckily, the Spring team has some solutions in mind.</p>
<p>For the Config client part, there is a now a <a target="_blank" href="https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#spring-cloud-kubernetes-configuration-watcher">Kubernetes Configuration Watcher</a>, which is essentially a service that... well "watches" the API server for ConfigMap changes based on pre-defined labels. <em>Only this instance</em> would require permissions and NetworkPolicy access to the API server, thus significantly reducing the policy configuration surface area. This watcher can be deployed by a Kubernetes administrator to a different namespace without the developers or the Spring Boot workload instances knowing much about the Kubernetes topology. The Config Watcher is now acting more like a standard Kubernetes operator in that regard, which is more fitting for production environments. 
The watcher will track all the ConfigMaps or Secrets with a particular label (<code>spring.cloud.kubernetes.config=true</code> or <code>spring.cloud.kubernetes.secret=true</code> respectively), and if any of them changes, it will call the <code>/actuator/refresh</code> endpoint for each one of the pods that have this ConfigMap mounted as a volume. This means you get automatic refresh of the Spring Context even over HTTP without having to resort to Spring Cloud Bus backed by a message broker - a much more simplified approach. Although, if your code already depends on Spring Cloud Bus, that option is supported as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665596291312/f3JNhdYxC.png" alt="image.png" class="image--center mx-auto" /></p>
<p>If you still want to use Spring Cloud Config Server, for example since you have a complex setup of configuration in a git repo or Vault, you can <a target="_blank" href="https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#spring-cloud-kubernetes-configserver">deploy a Spring Cloud Config Server to Kubernetes</a> directly. In this use case, You will get the added benefit of being able to track both git repos <em>and</em> ConfigMaps/Secrets. Spring Cloud Kubernetes Config Server will also need permissions and NetworkPolicy access to the Kubernetes API server.</p>
<p>What about Spring Cloud Discovery Client? There's a solution for that as well. <a target="_blank" href="https://docs.spring.io/spring-cloud-kubernetes/docs/current/reference/html/#spring-cloud-kubernetes-discoveryserver">Spring Cloud Kubernetes Discovery Server</a> is a drop-in replacement for the Eureka API. Existing applications which use Spring Cloud Discovery Client can talk to this server as if it was Spring Cloud Eureka. Unlike Eureka though, they do not need to register themselves with this server, because Spring Cloud Kubernetes Discovery Server gets the service information on available Kubernetes services directly from the Kubernetes API server. Just like Spring Cloud Config Watcher and Spring Cloud Kubernetes Config Server, Spring Cloud Kubernetes Discovery Server requires permissions and NetworkPolicy access to the Kubernetes API server:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665596356660/L6MhPINaE.png" alt="image.png" class="image--center mx-auto" /></p>
<blockquote>
<p>Opt for Kubernetes-native service discovery and configuration management. For brownfield applications, use Spring Cloud Kubernetes as a drop-in replacement.</p>
</blockquote>
<h1 id="heading-profiling-your-spring-boot-application">Profiling your Spring Boot application</h1>
<p>Throughout this article, I mentioned that you need to profile your application in order to better understand  how to configure it for containers. Each application is different and has different requirements in terms of multi-threading and memory, and these settings have a tremendous impact on how your application will perform.</p>
<p>A deep dive on Spring application profiling is out of scope for this document, but here are some pointers you can use as a starting point:</p>
<ol>
<li>Consider testing your app independently of the container constraints first. Just run the JVM on a relatively powerful machine with a healthy amount of CPU and Memory. To set the desired number of cores your JVM will use, set the following command (and adjust the count as needed for different performance testing):<pre><code class="lang-bash">&gt; java -XX:ActiveProcessorCount=4 target/spring-on-k8s.jar
</code></pre>
</li>
</ol>
<p>Remember, this setting is quite important since we don't want to let the container "guess" the correct value. It's best to be explicit, especially since other factors use this value for reference - such as the selected garbage collector and the size of your thread pools. </p>
<ol>
<li><p>The JVM uses a combination of heap memory and non-heap memory. The sum of these two values will have an impact on your container configuration. In order to profile your native memory, set the following JVM arguments:</p>
<pre><code class="lang-bash">java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -jar target/myapp.jar
</code></pre>
<p>Native memory usage can be a hidden trojan horse in your memory footprint. You could have a good ratio between heap memory and your container memory, but native memory usage could be significant based on your use case, and consume all the remaining available memory. When profiling with the arguments above, the JVM will begin tracking native memory usage in your application, and will print a usage summary when the application is stopped:</p>
<pre><code class="lang-bash"> Native Memory Tracking:
 Total: reserved=10376420413, committed=245521469
 -                 Java Heap (reserved=8589934592, committed=88080384)
                             (mmap: reserved=8589934592, committed=88080384)
 ...
 -    Native Memory Tracking (reserved=3535360, committed=3535360)
                             (malloc=6016 <span class="hljs-comment">#84)</span>
                             (tracking overhead=3529344)
 ...
</code></pre>
</li>
<li><p>During profiling, include the <code>spring-boot-starter-actuator</code> dependency to expose production-ready endpoints that will help you have a better understanding about your application. For profiling, you'd want access to <em>all</em> endpoints, so set the following property during profiling, but <strong>do not</strong> keep it in production since it can pose a security risk:</p>
<pre><code class="lang-properties">management.endpoints.web.exposure.include=*
</code></pre>
<p>If you're monitoring with JMX, the endpoints are already exposed via the following default setting:</p>
<pre><code class="lang-properties">management.endpoints.jmx.exposure.include=*
</code></pre>
<p> The following endpoints will help you out with profiling:   </p>
<ul>
<li><code>/actuator/heapdump</code> will return an hprof dump file that you can load into tools such as <a target="_blank" href="https://www.ej-technologies.com/products/jprofiler/overview.html">JProfiler</a>, the <a target="_blank" href="https://blog.jetbrains.com/idea/2020/03/profiling-tools-and-intellij-idea-ultimate/">InteliJ Profiler</a> or others.</li>
<li><code>/actuator/threaddump</code> will return a thread dump that you can load into tools such as <a target="_blank" href="https://fastthread.io/">Fast Thread</a> or <a target="_blank" href="https://jstack.review/">jstack</a>.</li>
<li><code>/actuator/metrics</code> will return metrics on the JVM that can be very useful. For example, <code>/actuator/metrics/jvm.classes.loaded</code> will return the number of classes loaded by the JVM.</li>
<li><code>/actuator/prometheus</code> requires an additional dependency on <code>micrometer-registry-prometheus</code>, and will expose the same metrics, but in a format that can be scraped by Prometheus and later presented in Grafana dashboards.</li>
<li>Online SaaS services such as <a target="_blank" href="https://www.datadoghq.com">DataDog</a> or <a target="_blank" href="https://tanzu.vmware.com/aria-operations-for-applications">Tanzu Observability</a> can also provide valuable input.</li>
</ul>
</li>
<li><p>If you have the license, consider using Oracle's <a target="_blank" href="https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH170">Flight Recorder</a> diagnostic and profiling tool. Spring has built-in support for Flight Recorder, by booting your Spring App (2.4 and above) with the following main method:</p>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@ConfigurationPropertiesScan</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoApplication</span> </span>{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
     SpringApplication springApplication = <span class="hljs-keyword">new</span> SpringApplication(DemoApplication.class);
     springApplication.setApplicationStartup(<span class="hljs-keyword">new</span> FlightRecorderApplicationStartup());
     springApplication.run(args);
 }
}
</code></pre>
<p>And the following JVM arguments on startup:</p>
<pre><code class="lang-bash">java -XX:+UnlockCommercialFeatures -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar target/myapp.jar
</code></pre>
<p>You can instead use a <a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.startup-tracking">BufferingApplicationStartup</a> as follows:</p>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-meta">@ConfigurationPropertiesScan</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DemoApplication</span> </span>{
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
     SpringApplication springApplication = <span class="hljs-keyword">new</span> SpringApplication(DemoApplication.class);
     springApplication.setApplicationStartup(<span class="hljs-keyword">new</span> BufferingApplicationStartup(<span class="hljs-number">4096</span>));
     springApplication.run(args);
 }
}
</code></pre>
<p>The information collected by this solution will be exposed as a json via the actuator endpoint <code>/actuator/startup</code>.</p>
</li>
<li><p>Consider using services that offer smart code optimization and recommendations such as <a target="_blank" href="https://github.com/features/copilot">Github copilot</a>. If your Spring application uses JPA and Hibernate - consider using <a target="_blank" href="https://vladmihalcea.com/spring-petclinic-hypersistence-optimizer/">Hypersistence Optimizer</a> to optimize your JPA code path. Any code-level improvement means less wasted CPU cycles in production!</p>
</li>
<li><p>Test against "real" backing services to better simulate how your code will behave in production. In-memory databases such as H2 and HSQLDB are nice, but with tools like <a target="_blank" href="https://www.testcontainers.org/">Testcontainers</a> becoming so powerful, H2 and HSQLDB are just becoming technical debt since you're testing against an emulated database when you could have tested against the real thing.</p>
</li>
</ol>
<h1 id="heading-looking-further">Looking further</h1>
<p>Everything I described in this article is relevant for JVM applications at the end of 2022, with Spring Boot 2.x, which is probably the vast majority of the Spring workloads out there.</p>
<p>At <a target="_blank" href="https://springone.io/">SpringOne 2022</a>, the Spring team will release Spring Framework 6.0 and Spring Boot 3.0. The main feature of this release is a requirement for Java 17 or later, and GA support for GraalVM with <a target="_blank" href="https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/">Spring Native</a>. Spring Native compiles your Java code <em>Ahead of Time</em>. The Java Virtual Machine is replaced by a Substrate VM, which is specific to the target OS and architecture. Your container will require significantly less memory - probably less than 100 MB compared to 2 GB with a JVM. It also means the container starts up almost immediately - we're now talking about milliseconds instead of seconds. This makes Spring-based applications an ideal candidate for serverless solutions such as Knative and AWS Lambda, since these solutions tend to scale to zero and need immediate response time from a cold start.</p>
<p>Clearly, such a meaningful change becomes a different discussion all-together, and would require revisiting most of the recommendations in this article in the future, perhaps in a separate article.</p>
<p>Although Spring Native shows a lot of promise, the JVM isn't going away anytime soon. GraalVM-based solutions have tradeoffs as well:</p>
<ul>
<li>They boot a lot faster, but don't achieve the same <em>peak</em> performance.</li>
<li>You cannot use Java agents and some instrumentation solutions such as those used by some observability solutions.</li>
<li>There is still work to be done in the third-party frameworks community to adapt the framework to this new architecture.</li>
<li>Compilation time is longer, although it has improved considerably.</li>
</ul>
<p>Another interesting option that could gain traction in the future is the use of <a target="_blank" href="https://blog.adoptium.net/2021/10/jlink-to-produce-own-runtime/">JLink</a>. This process allows you to selectively choose only the JRE modules that are required to run your application. It can greatly reduce the container size and require less runtime memory. Unlike Spring Native - this will still result in a JVM runtime, just a more lightweight version of it. As mentioned in this very old <a target="_blank" href="https://github.com/spring-projects/spring-framework/issues/18079">Github Issue</a>, the focus appears to be shifting towards Spring Native, and there are no plans to focus more work on the complementary <a target="_blank" href="https://openjdk.org/projects/jigsaw/">Project Jigsaw</a> which was meant to automate the module building for frameworks such as Spring. Still, you can use JLink directly as demoed here by <a target="_blank" href="https://github.com/dsyer/sample-docker-microservice">Dr. Dave Syer</a> and by <a target="_blank" href="https://github.com/asaikali/modern-spring-on-kubernetes/tree/main/containerize/jlink-layers-dockerfile">Adib Saikali</a>. Compared to the images built by the Paketo Buildpacks, the JLink image can be around 40% smaller. One of the challenges for Spring is the need to still include the <code>java.desktop</code> module, which is a very large module and doesn't seem right for a server-side Spring application. In Spring Framework 6.0, this module <a target="_blank" href="https://github.com/spring-projects/spring-framework/issues/26884">would not be needed anymore</a> and would result in even smaller images.</p>
<p>A different perspective on this problem domain is <a target="_blank" href="https://openjdk.org/projects/leyden/">Project Leyden</a>, which was also mentioned as one of the paths forward for the Spring Framework. Leyden also aims to provide faster startup times and reduced memory footprint compared to a full-fledged JVM, using the concept of static images - basically a finite and closed version of the JVM for your specific application only. If the JDK is a Word document, Project Leyden is the PDF version of it.</p>
<h1 id="heading-testing-my-own-recommendations">Testing my own recommendations</h1>
<p>I set myself a challenge when I began working on this article. <a target="_blank" href="https://tanzu.vmware.com/application-platform">Tanzu Application Platform</a> is a Platform-as-a-Service that runs on any Kubernetes. You as a developer provide a simple workload yaml file containing a reference to the git repo with your source code, and in the end you have a working application in Kubernetes, powered by Knative runtime and automatic scale-to-zero. 12 lines of yaml generate thousands of lines of yaml that your team doesn't have to maintain themselves.</p>
<p>Since the platform is developed by the same team that develops the Spring framework, it has production-ready defaults and best practices applied to it based on years of research and expertise. I thought it would be a good idea to compare my recommendations in this article with the resulting Knative service and pods generated by the platform.
So, without further ado, here's the a sample Kubernetes pod, created by a <a target="_blank" href="https://github.com/odedia/tap-gitops/blob/main/config/demos/tanzu-java-web-app/delivery.yml">Knative service</a>, which was created by Tanzu Application Platform (some lines removed for readability):</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">serving.knative.dev/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">tanzu-java-web-app</span>
  <span class="hljs-string">...</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">annotations:</span>
      <span class="hljs-string">...</span>
      <span class="hljs-attr">labels:</span>
      <span class="hljs-string">...</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">env:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">JAVA_TOOL_OPTIONS</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">-Dmanagement.endpoint.health.probes.add-additional-paths="true"</span> <span class="hljs-string">-Dmanagement.endpoint.health.show-details=always</span> <span class="hljs-string">-Dmanagement.endpoints.web.base-path="/actuator"</span> <span class="hljs-string">-Dmanagement.endpoints.web.exposure.include=*</span> <span class="hljs-string">-Dmanagement.health.probes.enabled="true"</span> <span class="hljs-string">-Dmanagement.server.port="8081"</span> <span class="hljs-string">-Dserver.port="8080"</span> <span class="hljs-string">-Dserver.shutdown.grace-period="24s"</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">odedia/tanzu-java-web-app-demos@sha256:6884b8289e21b1677b91aebd40a3198d07a6951fb912efaee504945b105b682b</span>
        <span class="hljs-attr">livenessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/livez</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
            <span class="hljs-attr">scheme:</span> <span class="hljs-string">HTTP</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">workload</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
          <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
        <span class="hljs-attr">readinessProbe:</span>
          <span class="hljs-attr">httpGet:</span>
            <span class="hljs-attr">path:</span> <span class="hljs-string">/readyz</span>
            <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
            <span class="hljs-attr">scheme:</span> <span class="hljs-string">HTTP</span>
        <span class="hljs-attr">resources:</span> {}
        <span class="hljs-attr">securityContext:</span>
          <span class="hljs-attr">runAsUser:</span> <span class="hljs-number">1000</span>
      <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">default</span>
</code></pre>
<p>Here is a corresponding pod that was created by the above Knative service. The pod also has a Knative sidecar container which is not relevant for our use case, so I removed it for readability along with other labels and annotations:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">annotations:</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">labels:</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">tanzu-java-web-app-00001-deployment-7946d647f9-5gvlf</span>
  <span class="hljs-attr">namespace:</span> <span class="hljs-string">demos</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">env:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">JAVA_TOOL_OPTIONS</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">-Dmanagement.endpoint.health.probes.add-additional-paths="true"</span> <span class="hljs-string">-Dmanagement.endpoint.health.show-details=always</span>
        <span class="hljs-string">-Dmanagement.endpoints.web.base-path="/actuator"</span> <span class="hljs-string">-Dmanagement.endpoints.web.exposure.include=*</span>
        <span class="hljs-string">-Dmanagement.health.probes.enabled="true"</span> <span class="hljs-string">-Dmanagement.server.port="8081"</span>
        <span class="hljs-string">-Dserver.port="8080"</span> <span class="hljs-string">-Dserver.shutdown.grace-period="24s"</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">PORT</span>
      <span class="hljs-attr">value:</span> <span class="hljs-string">"8080"</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">harbor.mysite.com/app/tanzu-java-web-app-demos@sha256:d76d166baa827a998a4637c2771af72b1ea33240ed15107151da99b7a8eb7e39</span>
    <span class="hljs-attr">imagePullPolicy:</span> <span class="hljs-string">IfNotPresent</span>
    <span class="hljs-attr">lifecycle:</span>
      <span class="hljs-attr">preStop:</span>
        <span class="hljs-attr">httpGet:</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">/wait-for-drain</span>
          <span class="hljs-attr">port:</span> <span class="hljs-number">8022</span>
          <span class="hljs-attr">scheme:</span> <span class="hljs-string">HTTP</span>
    <span class="hljs-attr">livenessProbe:</span>
      <span class="hljs-attr">failureThreshold:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">httpGet:</span>
        <span class="hljs-attr">httpHeaders:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">K-Kubelet-Probe</span>
          <span class="hljs-attr">value:</span> <span class="hljs-string">queue</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/livez</span>
        <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
        <span class="hljs-attr">scheme:</span> <span class="hljs-string">HTTP</span>
      <span class="hljs-attr">periodSeconds:</span> <span class="hljs-number">10</span>
      <span class="hljs-attr">successThreshold:</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">timeoutSeconds:</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">workload</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">8080</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">user-port</span>
      <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
    <span class="hljs-attr">resources:</span> {}
    <span class="hljs-attr">securityContext:</span>
      <span class="hljs-attr">runAsUser:</span> <span class="hljs-number">1000</span>
    <span class="hljs-attr">terminationMessagePath:</span> <span class="hljs-string">/dev/termination-log</span>
    <span class="hljs-attr">terminationMessagePolicy:</span> <span class="hljs-string">FallbackToLogsOnError</span>
  <span class="hljs-attr">dnsPolicy:</span> <span class="hljs-string">ClusterFirst</span>
  <span class="hljs-attr">enableServiceLinks:</span> <span class="hljs-literal">false</span>
  <span class="hljs-attr">imagePullSecrets:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">registry-credentials</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">tap-registry</span>
  <span class="hljs-attr">serviceAccount:</span> <span class="hljs-string">default</span>
  <span class="hljs-attr">serviceAccountName:</span> <span class="hljs-string">default</span>
  <span class="hljs-attr">terminationGracePeriodSeconds:</span> <span class="hljs-number">300</span>
  <span class="hljs-attr">tolerations:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">effect:</span> <span class="hljs-string">NoExecute</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">node.kubernetes.io/not-ready</span>
    <span class="hljs-attr">operator:</span> <span class="hljs-string">Exists</span>
    <span class="hljs-attr">tolerationSeconds:</span> <span class="hljs-number">300</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">effect:</span> <span class="hljs-string">NoExecute</span>
    <span class="hljs-attr">key:</span> <span class="hljs-string">node.kubernetes.io/unreachable</span>
    <span class="hljs-attr">operator:</span> <span class="hljs-string">Exists</span>
    <span class="hljs-attr">tolerationSeconds:</span> <span class="hljs-number">300</span>
  <span class="hljs-string">...</span>
</code></pre>
<p>This seems very close to the conclusions in this article. Let's highlight the applied <code>JAVA_TOOL_OPTIONS</code> parameters:</p>
<pre><code>-Dmanagement.endpoint.health.probes.add-additional-paths=<span class="hljs-string">"true"</span> 
-Dmanagement.endpoint.health.show-details=always 
-Dmanagement.endpoints.web.base-path=<span class="hljs-string">"/actuator"</span> 
-Dmanagement.endpoints.web.exposure.include=* 
-Dmanagement.health.probes.enabled=<span class="hljs-string">"true"</span> 
-Dmanagement.server.port=<span class="hljs-string">"8081"</span> 
-Dserver.port=<span class="hljs-string">"8080"</span> 
-Dserver.shutdown.grace-period=<span class="hljs-string">"24s"</span>
</code></pre><p>You might be wondering: "where are the memory parameters? Where is the configuration for ActiveProcessorCount"? These are applied by the Java Memory Calculator on startup.  Nirvana :).
Here's the startup logs of the pod listed above. Notice the memory calculations applied:</p>
<pre><code>&gt; kubectl logs tanzu-java-web-app<span class="hljs-number">-00001</span>-deployment<span class="hljs-number">-7</span>f89bf96fc-cxbtb
Defaulted container <span class="hljs-string">"workload"</span> out <span class="hljs-keyword">of</span>: workload, queue-proxy
Setting Active Processor Count to <span class="hljs-number">32</span>
Calculating JVM memory based on <span class="hljs-number">110224100</span>K available memory
For more information on <span class="hljs-built_in">this</span> calculation, see https:<span class="hljs-comment">//paketo.io/docs/reference/java-reference/#memory-calculator</span>
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -Xmx109626290K -XX:MaxMetaspaceSize=<span class="hljs-number">85809</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xss1M (Total Memory: <span class="hljs-number">110224100</span>K, Thread Count: <span class="hljs-number">250</span>, Loaded Class Count: <span class="hljs-number">12736</span>, <span class="hljs-attr">Headroom</span>: <span class="hljs-number">0</span>%)
Enabling Java Native Memory Tracking
Adding <span class="hljs-number">127</span> container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Dmanagement.endpoint.health.probes.add-additional-paths=<span class="hljs-string">"true"</span> -Dmanagement.endpoint.health.show-details=always -Dmanagement.endpoints.web.base-path=<span class="hljs-string">"/actuator"</span> -Dmanagement.endpoints.web.exposure.include=* -Dmanagement.health.probes.enabled=<span class="hljs-string">"true"</span> -Dmanagement.server.port=<span class="hljs-string">"8081"</span> -Dserver.port=<span class="hljs-string">"8080"</span> -Dserver.shutdown.grace-period=<span class="hljs-string">"24s"</span> -Djava.security.properties=<span class="hljs-regexp">/layers/</span>paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=<span class="hljs-number">32</span> -XX:MaxDirectMemorySize=<span class="hljs-number">10</span>M -Xmx109626290K -XX:MaxMetaspaceSize=<span class="hljs-number">85809</span>K -XX:ReservedCodeCacheSize=<span class="hljs-number">240</span>M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>

  .   ____          _            __ _ _
 /\\ / ___<span class="hljs-string">'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '</span>_ | <span class="hljs-string">'_| | '</span>_ \/ _<span class="hljs-string">` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.4)

2022-10-11 09:12:21.357  INFO 1 --- [           main] com.example.springboot.Application       : Starting Application v0.0.1-SNAPSHOT using Java 11.0.16.1 on tanzu-java-web-app-00001-deployment-7f89bf96fc-cxbtb with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
...</span>
</code></pre><p>The only thing I seemed to have missed is related to security: It's always advisable to run your app with a service account and a non-root security context:</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">securityContext:</span>
      <span class="hljs-attr">runAsUser:</span> <span class="hljs-number">1000</span>
  <span class="hljs-string">...</span>
  <span class="hljs-attr">serviceAccount:</span> <span class="hljs-string">default</span>
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this article, I highlighted some of the challenges and considerations that developers and Devops teams need to take in to account when deploying Spring applications to Kubernetes. There are of-course many aspects that I haven't mentioned, so I would love to hear your feedback in the comments section. What tips can you share with the community? How can we achieve peak performance, consistency and reliability in production for our Spring workloads?</p>
<p>Thank you for reading.</p>
<blockquote>
<p>Some content is based on "<a target="_blank" href="https://asaikali.hashnode.dev/spring-kubernetes-best-practices">Effective Spring</a>" by <a class="user-mention" href="https://hashnode.com/@asaikali">Adib Saikali</a>. Additional feedback provided by Stuart Charlton, Dr. David Syer and Ryan Baxter.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Containers and Kubernetes: where do we go from here?]]></title><description><![CDATA[I recently had the pleasure of attending Dell Technologies' Digital Transformation podcast, where I discussed the history of Kubernetes and why developer platforms are essential for productivity.
The podcast is recorded in Hebrew:


If you’re an over...]]></description><link>https://odedia.org/containers-and-kubernetes-where-do-we-go-from-here</link><guid isPermaLink="true">https://odedia.org/containers-and-kubernetes-where-do-we-go-from-here</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[containers]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Sun, 24 Jul 2022 10:40:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658659095979/WSgZT0vLN4.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently had the pleasure of attending Dell Technologies' Digital Transformation podcast, where I discussed the history of Kubernetes and why developer platforms are essential for productivity.
The podcast is recorded in Hebrew:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/QKBETfCAvak"></iframe>

<p>If you’re an overcast.fm fan like me, you can listen to it on overcast with this link:</p>
<p>https://overcast.fm/+1LYCSCYYE</p>
]]></content:encoded></item><item><title><![CDATA[Real-world Cloud Native Buildpacks Part 1]]></title><description><![CDATA[Intro
Cloud Native Buildpacks is a CNCF specification that "helps you transform your application source code into images that can run on any cloud".
In simpler terms, it is an automated source-to-OCI-image solution that is designed to take the toil o...]]></description><link>https://odedia.org/real-world-cloud-native-buildpacks-part-1</link><guid isPermaLink="true">https://odedia.org/real-world-cloud-native-buildpacks-part-1</guid><category><![CDATA[docker images]]></category><category><![CDATA[containers]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Vue.js]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Fri, 11 Mar 2022 18:30:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1642425450166/m4RXxqStQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-intro">Intro</h1>
<p><a target="_blank" href="https://buildpacks.io/">Cloud Native Buildpacks</a> is a CNCF specification that "helps you transform your application source code into images that can run on any cloud".</p>
<p>In simpler terms, it is an automated source-to-OCI-image solution that is designed to take the toil of writing Dockerfiles from the hands of developers or devops teams. More importantly - it allows for proactive CVE patching. Since CNB is responsible for building the image, it knows how that image was built - its various layers, including the underlying base OS, which is called a <em>stack</em>, as well as runtime layers such as OpenJDK, .NET Core runtime etc.</p>
<p>Working with our customers at VMware Tanzu, I began to notice some patterns around making sure CNB work well in the "real world" - that is, with real customer codebases, airgapped environments, and multi-module repositories. These are usually much different than your typical "Hello World" demo apps, so I figured I'd list some of the caveats and best practices that I find for the most common languages.</p>
<h1 id="heading-choosing-a-platform">Choosing a platform</h1>
<p>Cloud Native Buildpacks is a specification. It does not dictate the target runtime. As a result - there are various platforms it can run on, such as:</p>
<ul>
<li>Spring  <a target="_blank" href="https://spring.io/guides/gs/spring-boot-docker/">mvn/gradle plugin</a> </li>
<li><a target="_blank" href="https://buildpacks.io/docs/tools/pack/">pack cli</a>  for local OCI image building</li>
<li><a target="_blank" href="https://azure.microsoft.com/en-us/services/spring-cloud/#overview">Azure Spring Cloud</a> </li>
<li><a target="_blank" href="https://cloud.google.com/blog/products/containers-kubernetes/google-cloud-now-supports-buildpacks">Google Cloud</a> </li>
<li><a target="_blank" href="https://github.com/pivotal/kpack">kpack</a>  as an open source automated image building service on Kubernetes</li>
<li><a target="_blank" href="https://tanzu.vmware.com/build-service">Tanzu Build Service</a>  - a commercial, enterprise supported distribution based on kpack</li>
</ul>
<p>For this article, I'm going to use Tanzu Build Service since that is what I'm most comfortable with and also because it's probably the most feature-rich distribution. It's also included as part of  <a target="_blank" href="https://odedia.org/kubernetes-needs-a-developer-platform">Tanzu Application Platform</a> . Since Tanzu Build Service is based on kpack, almost everything can be applied to kpack as well.</p>
<h1 id="heading-buildpacks-in-review">Buildpacks in review</h1>
<h3 id="heading-spring-boot-hello-world">Spring Boot "hello world!"</h3>
<p>Let's start with a simple Spring Boot <code>hello world</code> app just to get into the mood:</p>
<h4 id="heading-git-repo">Git repo</h4>
<p>The repository is available at https://github.com/odedia/spring-on-k8s.git</p>
<h4 id="heading-build-command">Build command</h4>
<p>This is as straigntforward as it gets. Our repository is based on maven, and Tanzu Build Service detects it automatically since a <code>pom.xml</code> is available at the root of the repository. The command to build the image is:</p>
<pre><code class="lang-bash">&gt; kp image save spring-hello-world \
    --tag harbor.registry.com/apps/spring-hello-world \
    --git https://github.com/odedia/spring-on-k8s.git \
    --git-revision master
</code></pre>
<h4 id="heading-build-logs-output">Build logs output</h4>
<p>Let's check the build logs:</p>
<pre><code class="lang-bash">&gt; kp build logs spring-hello-world
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  - <span class="hljs-built_in">source</span>: {}
  + <span class="hljs-built_in">source</span>:
  +   git:
  +     revision: 50c46f40baa6ac8a72498d516624fc78d95f0d2a
  +     url: https://github.com/odedia/spring-on-k8s.git
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/odedia/spring-on-k8s.git"</span> @ <span class="hljs-string">"50c46f40baa6ac8a72498d516624fc78d95f0d2a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/odedia/spring-on-k8s.git"</span> @ <span class="hljs-string">"50c46f40baa6ac8a72498d516624fc78d95f0d2a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
===&gt; DETECT
8 of 35 buildpacks participating
paketo-buildpacks/ca-certificates   3.0.2
paketo-buildpacks/bellsoft-liberica 9.0.2
paketo-buildpacks/syft              1.3.1
paketo-buildpacks/maven             6.0.2
paketo-buildpacks/executable-jar    6.0.2
paketo-buildpacks/apache-tomcat     7.0.3
paketo-buildpacks/dist-zip          5.0.2
paketo-buildpacks/spring-boot       5.3.0
===&gt; RESTORE
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/ca-certificates:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:java-security-properties"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jre"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:spring-cloud-bindings"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:web-application-type"</span> from app image
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 9.0.2
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    <span class="hljs-variable">$BP_JVM_TYPE</span>                 JRE             the JVM <span class="hljs-built_in">type</span> - JDK or JRE
    <span class="hljs-variable">$BP_JVM_VERSION</span>              11              the Java version
  Launch Configuration:
    <span class="hljs-variable">$BPL_DEBUG_ENABLED</span>           <span class="hljs-literal">false</span>           enables Java remote debugging support
    <span class="hljs-variable">$BPL_DEBUG_PORT</span>              8000            configure the remote debugging port
    <span class="hljs-variable">$BPL_DEBUG_SUSPEND</span>           <span class="hljs-literal">false</span>           configure whether to <span class="hljs-built_in">suspend</span> execution until a debugger has attached
    <span class="hljs-variable">$BPL_HEAP_DUMP_PATH</span>                          write heap dumps on error to this path
    <span class="hljs-variable">$BPL_JAVA_NMT_ENABLED</span>        <span class="hljs-literal">true</span>            enables Java Native Memory Tracking (NMT)
    <span class="hljs-variable">$BPL_JAVA_NMT_LEVEL</span>          summary         configure level of NMT, summary or detail
    <span class="hljs-variable">$BPL_JFR_ARGS</span>                                configure custom Java Flight Recording (JFR) arguments
    <span class="hljs-variable">$BPL_JFR_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Flight Recording (JFR)
    <span class="hljs-variable">$BPL_JMX_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Management Extensions (JMX)
    <span class="hljs-variable">$BPL_JMX_PORT</span>                5000            configure the JMX port
    <span class="hljs-variable">$BPL_JVM_HEAD_ROOM</span>           0               the headroom <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_LOADED_CLASS_COUNT</span>  35% of classes  the number of loaded classes <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_THREAD_COUNT</span>        250             the number of threads <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$JAVA_TOOL_OPTIONS</span>                           the JVM launch flags
  BellSoft Liberica JDK 11.0.13: Contributing to layer
    Reusing cached download from buildpack
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 128 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 11.0.13: Reusing cached layer
  Launch Helper: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Syft Buildpack 1.3.1
  https://github.com/paketo-buildpacks/syft
    Reusing cached download from buildpack
    Writing env.build/SYFT_CHECK_FOR_APP_UPDATE.default

Paketo Maven Buildpack 6.0.2
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    <span class="hljs-variable">$BP_MAVEN_BUILD_ARGUMENTS</span>  -Dmaven.test.skip=<span class="hljs-literal">true</span> package  the arguments to pass to Maven
    <span class="hljs-variable">$BP_MAVEN_BUILT_ARTIFACT</span>   target/*.[ejw]ar                the built application artifact explicitly.  Supersedes <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>
    <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>                                     the module to find application artifact <span class="hljs-keyword">in</span>
    <span class="hljs-variable">$BP_MAVEN_POM_FILE</span>         pom.xml                         the location of the main pom.xml file, relative to the application root
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=<span class="hljs-literal">true</span> package
      [INFO] Scanning <span class="hljs-keyword">for</span> projects...
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.4.3/spring-boot-starter-parent-2.4.3.pom
...
...
...
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/eclipse/sisu/org.eclipse.sisu.inject/0.3.4/org.eclipse.sisu.inject-0.3.4.jar (379 kB at 2.9 MB/s)
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/eclipse/sisu/org.eclipse.sisu.plexus/0.3.4/org.eclipse.sisu.plexus-0.3.4.jar (205 kB at 1.5 MB/s)
      [INFO] Replacing main artifact with repackaged archive
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  10.232 s
      [INFO] Finished at: 2022-01-16T10:02:48Z
      [INFO] ------------------------------------------------------------------------
  Removing <span class="hljs-built_in">source</span> code

Paketo Executable JAR Buildpack 6.0.2
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 5.3.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.8.0: Reusing cached layer
  Web Application Type: Reusing cached layer
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.version
===&gt; EXPORT
Reusing layers from image <span class="hljs-string">'harbor.registry.com/apps/spring-hello-world@sha256:e135f066c71524c6dc8343d4c803c417240c1ffc1563daa0224c865e45900d53'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/ca-certificates:helper'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:helper'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:java-security-properties'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:jre'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/executable-jar:classpath'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/spring-boot:helper'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/spring-boot:spring-cloud-bindings'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/spring-boot:web-application-type'</span>
Adding layer <span class="hljs-string">'launch.sbom'</span>
Reusing 5/5 app layer(s)
Reusing layer <span class="hljs-string">'launcher'</span>
Reusing layer <span class="hljs-string">'config'</span>
Reusing layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Adding label <span class="hljs-string">'org.opencontainers.image.title'</span>
Adding label <span class="hljs-string">'org.opencontainers.image.version'</span>
Adding label <span class="hljs-string">'org.springframework.boot.version'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/apps/spring-hello-world...
*** Images (sha256:792fee75f5627daed9e8731971d134a28112cc952359d983ea901fca5cf7f522):
      harbor.registry.com/apps/spring-hello-world
      harbor.registry.com/apps/spring-hello-world:b1.20220116.100212
Adding cache layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:jdk'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/syft:syft'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/maven:application'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/maven:cache'</span>
Adding cache layer <span class="hljs-string">'cache.sbom'</span>
===&gt; COMPLETION
Build successful
</code></pre>
<h4 id="heading-running-the-image">Running the image</h4>
<p>Let's see if our OCI image runs as expected:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/apps/spring-hello-world:b1.20220116.100212

Unable to find image <span class="hljs-string">'harbor.registry.com/apps/spring-hello-world:b1.20220116.100212'</span> locally
b1.20220116.100212: Pulling from apps/spring-hello-world
bf99a8b93828: Already exists
1da1131b1360: Pull complete
48bde579d8cc: Pull complete
1929d0623312: Pull complete
1fea93ec33be: Pull complete
fed2bcb93d83: Pull complete
d06e3ac07eba: Pull complete
a8090bf3b91d: Pull complete
204c9b3ef04c: Pull complete
95d9537f4e2e: Pull complete
40cbb147917e: Pull complete
b8e5cf599c18: Pull complete
bd74081a3b16: Pull complete
1d17a9810ba3: Pull complete
be14b062b309: Pull complete
33a1d27f7a3f: Pull complete
89732bc75041: Pull complete
96580d31d7e3: Pull complete
40d17e1b116e: Pull complete
02d76976a806: Pull complete
b39b18afe74e: Pull complete
Digest: sha256:792fee75f5627daed9e8731971d134a28112cc952359d983ea901fca5cf7f522
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/apps/spring-hello-world:b1.20220116.100212
Setting Active Processor Count to 4
WARNING: Unable to convert memory <span class="hljs-built_in">limit</span> <span class="hljs-string">"max"</span> from path <span class="hljs-string">"/sys/fs/cgroup/memory.max"</span> as int: memory size <span class="hljs-string">"max"</span> does not match pattern <span class="hljs-string">"^([\\d]+)([kmgtKMGT]?)$"</span>
Calculating JVM memory based on 1284376K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx687495K -XX:MaxMetaspaceSize=84880K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1284376K, Thread Count: 250, Loaded Class Count: 12572, Headroom: 0%)
Enabling Java Native Memory Tracking
Adding 128 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx687495K -XX:MaxMetaspaceSize=84880K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>

  .   ____          _            __ _ _
 /\\ / ___<span class="hljs-string">'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '</span>_ | <span class="hljs-string">'_| | '</span>_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  <span class="hljs-string">'  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.3)

2022-01-16 10:08:34.050  INFO 1 --- [           main] c.vmware.demos.springonk8s.Application   : Starting Application v1.0.0-SNAPSHOT using Java 11.0.13 on 3b4ec7e4ed92 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
2022-01-16 10:08:34.055  INFO 1 --- [           main] c.vmware.demos.springonk8s.Application   : No active profile set, falling back to default profiles: default
2022-01-16 10:08:35.541  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-01-16 10:08:35.560  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-01-16 10:08:35.560  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.43]
2022-01-16 10:08:35.640  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-01-16 10:08:35.641  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1499 ms
2022-01-16 10:08:36.079  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService '</span>applicationTaskExecutor<span class="hljs-string">'
2022-01-16 10:08:36.433  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '</span>/actuator<span class="hljs-string">'
2022-01-16 10:08:36.494  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '</span><span class="hljs-string">'
2022-01-16 10:08:36.517  INFO 1 --- [           main] c.vmware.demos.springonk8s.Application   : Started Application in 3.032 seconds (JVM running for 3.479)
2022-01-16 10:08:42.024  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet '</span>dispatcherServlet<span class="hljs-string">'
2022-01-16 10:08:42.026  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet '</span>dispatcherServlet<span class="hljs-string">'
2022-01-16 10:08:42.028  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms</span>
</code></pre>
<p>Let's check the application:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642328636128/jKs-5q049.png" alt="image.png" /></p>
<p>Looking good! Let's move to a more robust application.</p>
<h3 id="heading-spring-boot-pet-clinic-backend-application">Spring Boot Pet Clinic backend application</h3>
<p>This is the classic demo application for Spring Boot. I'm going to use a version of this app that only runs the backend, since I find it quite rare to run HTML+Javascript in a Spring Boot container - you'd usually run an Angular/React/VueVS application in a static code server such as NGINX. For now, lets build our REST APIs backend:</p>
<h4 id="heading-git-repo">Git Repo</h4>
<p>This application is located in https://github.com/spring-petclinic/spring-petclinic-rest.git</p>
<h4 id="heading-build-command">Build command</h4>
<p>The Java Buildpack (and Spring), is probably the most robust, so building the OCI image is still quite straightforward. Just point to the git repo:</p>
<pre><code class="lang-bash">&gt; kp image save spring-petclinic-backend \
    --tag harbor.registry.com/apps/spring-petclinic-backend \
    --git https://github.com/spring-petclinic/spring-petclinic-rest.git \
    --git-revision master
Creating Image Resource...
Image Resource <span class="hljs-string">"spring-petclinic-backend"</span> created
</code></pre>
<h4 id="heading-build-logs-output">Build logs output</h4>
<pre><code class="lang-bash">&gt; kp image save spring-petclinic-backend \
    --tag harbor.registry.com/apps/spring-petclinic-backend \
    --git https://github.com/spring-petclinic/spring-petclinic-rest.git \
    --git-revision master
Creating Image Resource...
Image Resource <span class="hljs-string">"spring-petclinic-backend"</span> created
(⎈ |tap:default)➜  ~
&gt; kp build logs spring-petclinic-backend
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  - <span class="hljs-built_in">source</span>: {}
  + <span class="hljs-built_in">source</span>:
  +   git:
  +     revision: 8da34240a5a9a04809f714a76cee82c6e27bea14
  +     url: https://github.com/spring-petclinic/spring-petclinic-rest.git
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/spring-petclinic/spring-petclinic-rest.git"</span> @ <span class="hljs-string">"8da34240a5a9a04809f714a76cee82c6e27bea14"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/spring-petclinic/spring-petclinic-rest.git"</span> @ <span class="hljs-string">"8da34240a5a9a04809f714a76cee82c6e27bea14"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
Previous image with name <span class="hljs-string">"harbor.registry.com/apps/spring-petclinic-backend"</span> not found
===&gt; DETECT
8 of 35 buildpacks participating
paketo-buildpacks/ca-certificates   3.0.2
paketo-buildpacks/bellsoft-liberica 9.0.2
paketo-buildpacks/syft              1.3.1
paketo-buildpacks/maven             6.0.2
paketo-buildpacks/executable-jar    6.0.2
paketo-buildpacks/apache-tomcat     7.0.3
paketo-buildpacks/dist-zip          5.0.2
paketo-buildpacks/spring-boot       5.3.0
===&gt; RESTORE
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper

Paketo BellSoft Liberica Buildpack 9.0.2
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    <span class="hljs-variable">$BP_JVM_TYPE</span>                 JRE             the JVM <span class="hljs-built_in">type</span> - JDK or JRE
    <span class="hljs-variable">$BP_JVM_VERSION</span>              11              the Java version
  Launch Configuration:
    <span class="hljs-variable">$BPL_DEBUG_ENABLED</span>           <span class="hljs-literal">false</span>           enables Java remote debugging support
    <span class="hljs-variable">$BPL_DEBUG_PORT</span>              8000            configure the remote debugging port
    <span class="hljs-variable">$BPL_DEBUG_SUSPEND</span>           <span class="hljs-literal">false</span>           configure whether to <span class="hljs-built_in">suspend</span> execution until a debugger has attached
    <span class="hljs-variable">$BPL_HEAP_DUMP_PATH</span>                          write heap dumps on error to this path
    <span class="hljs-variable">$BPL_JAVA_NMT_ENABLED</span>        <span class="hljs-literal">true</span>            enables Java Native Memory Tracking (NMT)
    <span class="hljs-variable">$BPL_JAVA_NMT_LEVEL</span>          summary         configure level of NMT, summary or detail
    <span class="hljs-variable">$BPL_JFR_ARGS</span>                                configure custom Java Flight Recording (JFR) arguments
    <span class="hljs-variable">$BPL_JFR_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Flight Recording (JFR)
    <span class="hljs-variable">$BPL_JMX_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Management Extensions (JMX)
    <span class="hljs-variable">$BPL_JMX_PORT</span>                5000            configure the JMX port
    <span class="hljs-variable">$BPL_JVM_HEAD_ROOM</span>           0               the headroom <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_LOADED_CLASS_COUNT</span>  35% of classes  the number of loaded classes <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_THREAD_COUNT</span>        250             the number of threads <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$JAVA_TOOL_OPTIONS</span>                           the JVM launch flags
  BellSoft Liberica JDK 11.0.13: Contributing to layer
    Reusing cached download from buildpack
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 128 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 11.0.13: Contributing to layer
    Reusing cached download from buildpack
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 128 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/active-processor-count
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/java-opts
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jvm-heap
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/link-local-dns
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/openssl-certificate-loader
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-configurer
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jmx
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/jfr
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/nmt
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/security-providers-classpath-9
    Creating /layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/debug-9
  Java Security Properties: Contributing to layer
    Writing env.launch/JAVA_SECURITY_PROPERTIES.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim

Paketo Syft Buildpack 1.3.1
  https://github.com/paketo-buildpacks/syft
    Reusing cached download from buildpack
    Writing env.build/SYFT_CHECK_FOR_APP_UPDATE.default

Paketo Maven Buildpack 6.0.2
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    <span class="hljs-variable">$BP_MAVEN_BUILD_ARGUMENTS</span>  -Dmaven.test.skip=<span class="hljs-literal">true</span> package  the arguments to pass to Maven
    <span class="hljs-variable">$BP_MAVEN_BUILT_ARTIFACT</span>   target/*.[ejw]ar                the built application artifact explicitly.  Supersedes <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>
    <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>                                     the module to find application artifact <span class="hljs-keyword">in</span>
    <span class="hljs-variable">$BP_MAVEN_POM_FILE</span>         pom.xml                         the location of the main pom.xml file, relative to the application root
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=<span class="hljs-literal">true</span> package
      [INFO] Scanning <span class="hljs-keyword">for</span> projects...
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-parent/2.6.2/spring-boot-starter-parent-2.6.2.pom
...
...
...
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-utils/3.3.0/plexus-utils-3.3.0.jar (263 kB at 16 MB/s)
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-compress/1.19/commons-compress-1.19.jar (615 kB at 23 MB/s)
      [INFO] Building jar: /workspace/target/spring-petclinic-rest-2.6.2.jar
      [INFO]
      [INFO] --- spring-boot-maven-plugin:2.6.2:repackage (repackage) @ spring-petclinic-rest ---
      [INFO] Replacing main artifact with repackaged archive
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  23.587 s
      [INFO] Finished at: 2022-01-16T10:31:48Z
      [INFO] ------------------------------------------------------------------------
  Removing <span class="hljs-built_in">source</span> code

Paketo Executable JAR Buildpack 6.0.2
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 5.3.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_spring-boot/helper/exec.d/spring-cloud-bindings
  Spring Cloud Bindings 1.8.0: Contributing to layer
    Reusing cached download from buildpack
    Copying to /layers/paketo-buildpacks_spring-boot/spring-cloud-bindings
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.version
===&gt; EXPORT
Adding layer <span class="hljs-string">'paketo-buildpacks/ca-certificates:helper'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:helper'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:java-security-properties'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:jre'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/executable-jar:classpath'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/spring-boot:helper'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/spring-boot:spring-cloud-bindings'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/spring-boot:web-application-type'</span>
Adding layer <span class="hljs-string">'launch.sbom'</span>
Adding 5/5 app layer(s)
Adding layer <span class="hljs-string">'launcher'</span>
Adding layer <span class="hljs-string">'config'</span>
Adding layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Adding label <span class="hljs-string">'org.opencontainers.image.title'</span>
Adding label <span class="hljs-string">'org.opencontainers.image.version'</span>
Adding label <span class="hljs-string">'org.springframework.boot.version'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/apps/spring-petclinic-backend...
*** Images (sha256:6cf16c4573c95f5e66e40740d9f6238377b3aaab38176e7986310c21bdd6094a):
      harbor.registry.com/apps/spring-petclinic-backend
      harbor.registry.com/apps/spring-petclinic-backend:b1.20220116.103057
Adding cache layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:jdk'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/syft:syft'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/maven:application'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/maven:cache'</span>
Adding cache layer <span class="hljs-string">'cache.sbom'</span>
===&gt; COMPLETION
Build successful
</code></pre>
<h4 id="heading-running-the-image">Running the image</h4>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/apps/spring-petclinic-backend:b1.20220116.103057
Unable to find image <span class="hljs-string">'harbor.registry.com/apps/spring-petclinic-backend:b1.20220116.103057'</span> locally
b1.20220116.103057: Pulling from apps/spring-petclinic-backend
bf99a8b93828: Already exists
1da1131b1360: Already exists
48bde579d8cc: Already exists
1929d0623312: Already exists
1fea93ec33be: Already exists
fed2bcb93d83: Already exists
d06e3ac07eba: Already exists
a8090bf3b91d: Already exists
204c9b3ef04c: Already exists
95d9537f4e2e: Already exists
40cbb147917e: Already exists
b8e5cf599c18: Already exists
bd74081a3b16: Already exists
50105f4a3af9: Pull complete
b218c24b76da: Pull complete
afd258a43f9e: Pull complete
89732bc75041: Pull complete
428c2ea77ec0: Pull complete
40d17e1b116e: Pull complete
c3e95d93d7ac: Pull complete
b39b18afe74e: Pull complete
Digest: sha256:6cf16c4573c95f5e66e40740d9f6238377b3aaab38176e7986310c21bdd6094a
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/apps/spring-petclinic-backend:b1.20220116.103057
Setting Active Processor Count to 4
WARNING: Unable to convert memory <span class="hljs-built_in">limit</span> <span class="hljs-string">"max"</span> from path <span class="hljs-string">"/sys/fs/cgroup/memory.max"</span> as int: memory size <span class="hljs-string">"max"</span> does not match pattern <span class="hljs-string">"^([\\d]+)([kmgtKMGT]?)$"</span>
Calculating JVM memory based on 1280440K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx644437K -XX:MaxMetaspaceSize=124002K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1280440K, Thread Count: 250, Loaded Class Count: 19479, Headroom: 0%)
Enabling Java Native Memory Tracking
Adding 128 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx644437K -XX:MaxMetaspaceSize=124002K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>

  .   ____          _            __ _ _
 /\\ / ___<span class="hljs-string">'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '</span>_ | <span class="hljs-string">'_| | '</span>_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  <span class="hljs-string">'  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.2)

INFO  PetClinicApplication - Starting PetClinicApplication v2.6.2 using Java 11.0.13 on 0b6241f0c523 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
DEBUG PetClinicApplication - Running with Spring Boot v2.6.2, Spring v5.3.14
INFO  PetClinicApplication - The following profiles are active: hsqldb,spring-data-jpa
INFO  Version - HV000001: Hibernate Validator 6.2.0.Final
INFO  RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
INFO  RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 97 ms. Found 7 JPA repository interfaces.
INFO  TomcatWebServer - Tomcat initialized with port(s): 9966 (http)
INFO  Http11NioProtocol - Initializing ProtocolHandler ["http-nio-9966"]
INFO  StandardService - Starting service [Tomcat]
INFO  StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.56]
INFO  [/petclinic] - Initializing Spring embedded WebApplicationContext
INFO  ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2210 ms
INFO  HikariDataSource - HikariPool-1 - Starting...
INFO  PoolBase - HikariPool-1 - Driver does not support get/set network timeout for connections. (feature not supported)
INFO  HikariDataSource - HikariPool-1 - Start completed.
INFO  LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
INFO  Version - HHH000412: Hibernate ORM core version 5.6.3.Final
INFO  Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
INFO  Dialect - HHH000400: Using dialect: org.hibernate.dialect.HSQLDialect
INFO  JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
INFO  LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit '</span>default<span class="hljs-string">'
INFO  EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '</span>/actuator<span class="hljs-string">'
INFO  UserDetailsServiceAutoConfiguration -

Using generated security password: 5b765681-8357-403d-8e73-7edc4554cccc

INFO  DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4f1072ee, org.springframework.security.web.context.SecurityContextPersistenceFilter@3fe33c59, org.springframework.security.web.header.HeaderWriterFilter@60eb9c29, org.springframework.security.web.authentication.logout.LogoutFilter@293988dc, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@34e700f4, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3bbab2c9, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@7b0a69b9, org.springframework.security.web.session.SessionManagementFilter@2f0a29f7, org.springframework.security.web.access.ExceptionTranslationFilter@63019596, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@5a513627]
INFO  Http11NioProtocol - Starting ProtocolHandler ["http-nio-9966"]
INFO  TomcatWebServer - Tomcat started on port(s): 9966 (http) with context path '</span>/petclinic<span class="hljs-string">'
INFO  PetClinicApplication - Started PetClinicApplication in 6.817 seconds (JVM running for 7.35)</span>
</code></pre>
<p>Let's check the output by running one of the APIs at http://localhost:8080/petclinic/swagger-ui/index.html:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642329468337/6mP5Bm0_t.png" alt="image.png" /></p>
<p>Hmm.... what happened here? 
If we check the logs above, it seems that our sample code starts at port 9966. It is considered a common practice (but definitely not required) to run user-facing containers such as these on port 8080, and map to whatever port we want outside the container. We could simple run the below command and it would work fine:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:9966 harbor.registry.com/apps/spring-petclinic-backend:b1.20220116.103057
</code></pre>
<p>However, I like to have repeatable practices. So, let's fork the repo and fix the <code>server.port</code> setting in <code>application.properties</code>. </p>
<h4 id="heading-making-changes">Making changes</h4>
<p>The forked repo is located at https://github.com/odedia/spring-petclinic-rest. Let's update https://github.com/odedia/spring-petclinic-rest/blob/master/src/main/resources/application.properties and change <code>server.port=9966</code> to <code>server.port=8080</code>.</p>
<p>To update our build to point to the updated repo, we simply need to update the <code>kp image save</code> command with the updated git URL:</p>
<pre><code class="lang-bash">&gt; kp image save spring-petclinic-backend \
    --tag harbor.registry.com/apps/spring-petclinic-backend \
    --git https://github.com/odedia/spring-petclinic-rest.git \
    --git-revision master
Patching Image Resource...
Image Resource <span class="hljs-string">"spring-petclinic-backend"</span> patched
</code></pre>
<p>If we'll check the build logs now, we'll find that the build is a lot faster and doesn't download all the maven dependencies again. That's because Tanzu Build Service caches layers as needed, and the maven cache layer did not change:</p>
<pre><code class="lang-bash">&gt; kp build logs spring-petclinic-backend
===&gt; PREPARE
Build reason(s): COMMIT,CONFIG
COMMIT:
  - 8da34240a5a9a04809f714a76cee82c6e27bea14
  + c31f00af9fae5ef63c323cfbf5f890f252d8669a
CONFIG:
  resources: {}
  <span class="hljs-built_in">source</span>:
    git:
  -     revision: 8da34240a5a9a04809f714a76cee82c6e27bea14
  -     url: https://github.com/spring-petclinic/spring-petclinic-rest.git
  +     revision: c31f00af9fae5ef63c323cfbf5f890f252d8669a
  +     url: https://github.com/odedia/spring-petclinic-rest.git
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/odedia/spring-petclinic-rest.git"</span> @ <span class="hljs-string">"c31f00af9fae5ef63c323cfbf5f890f252d8669a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/odedia/spring-petclinic-rest.git"</span> @ <span class="hljs-string">"c31f00af9fae5ef63c323cfbf5f890f252d8669a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
===&gt; DETECT
8 of 35 buildpacks participating
paketo-buildpacks/ca-certificates   3.0.2
paketo-buildpacks/bellsoft-liberica 9.0.2
paketo-buildpacks/syft              1.3.1
paketo-buildpacks/maven             6.0.2
paketo-buildpacks/executable-jar    6.0.2
paketo-buildpacks/apache-tomcat     7.0.3
paketo-buildpacks/dist-zip          5.0.2
paketo-buildpacks/spring-boot       5.3.0
===&gt; RESTORE
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/ca-certificates:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:java-security-properties"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jre"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jdk"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/syft:syft"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:cache"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:application"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:spring-cloud-bindings"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:web-application-type"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:helper"</span> from app image
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jdk"</span> from cache
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/syft:syft"</span> from cache
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:application"</span> from cache
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:cache"</span> from cache
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 9.0.2
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    <span class="hljs-variable">$BP_JVM_TYPE</span>                 JRE             the JVM <span class="hljs-built_in">type</span> - JDK or JRE
    <span class="hljs-variable">$BP_JVM_VERSION</span>              11              the Java version
  Launch Configuration:
    <span class="hljs-variable">$BPL_DEBUG_ENABLED</span>           <span class="hljs-literal">false</span>           enables Java remote debugging support
    <span class="hljs-variable">$BPL_DEBUG_PORT</span>              8000            configure the remote debugging port
    <span class="hljs-variable">$BPL_DEBUG_SUSPEND</span>           <span class="hljs-literal">false</span>           configure whether to <span class="hljs-built_in">suspend</span> execution until a debugger has attached
    <span class="hljs-variable">$BPL_HEAP_DUMP_PATH</span>                          write heap dumps on error to this path
    <span class="hljs-variable">$BPL_JAVA_NMT_ENABLED</span>        <span class="hljs-literal">true</span>            enables Java Native Memory Tracking (NMT)
    <span class="hljs-variable">$BPL_JAVA_NMT_LEVEL</span>          summary         configure level of NMT, summary or detail
    <span class="hljs-variable">$BPL_JFR_ARGS</span>                                configure custom Java Flight Recording (JFR) arguments
    <span class="hljs-variable">$BPL_JFR_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Flight Recording (JFR)
    <span class="hljs-variable">$BPL_JMX_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Management Extensions (JMX)
    <span class="hljs-variable">$BPL_JMX_PORT</span>                5000            configure the JMX port
    <span class="hljs-variable">$BPL_JVM_HEAD_ROOM</span>           0               the headroom <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_LOADED_CLASS_COUNT</span>  35% of classes  the number of loaded classes <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_THREAD_COUNT</span>        250             the number of threads <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$JAVA_TOOL_OPTIONS</span>                           the JVM launch flags
  BellSoft Liberica JDK 11.0.13: Reusing cached layer
  BellSoft Liberica JRE 11.0.13: Reusing cached layer
  Launch Helper: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Syft Buildpack 1.3.1
  https://github.com/paketo-buildpacks/syft

Paketo Maven Buildpack 6.0.2
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    <span class="hljs-variable">$BP_MAVEN_BUILD_ARGUMENTS</span>  -Dmaven.test.skip=<span class="hljs-literal">true</span> package  the arguments to pass to Maven
    <span class="hljs-variable">$BP_MAVEN_BUILT_ARTIFACT</span>   target/*.[ejw]ar                the built application artifact explicitly.  Supersedes <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>
    <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>                                     the module to find application artifact <span class="hljs-keyword">in</span>
    <span class="hljs-variable">$BP_MAVEN_POM_FILE</span>         pom.xml                         the location of the main pom.xml file, relative to the application root
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=<span class="hljs-literal">true</span> package
      [INFO] Scanning <span class="hljs-keyword">for</span> projects...
      [INFO]
      [INFO] ---------&lt; org.springframework.samples:spring-petclinic-rest &gt;----------
      [INFO] Building spring-petclinic-rest 2.6.2
      [INFO] --------------------------------[ jar ]---------------------------------
      [INFO]
      [INFO] --- jacoco-maven-plugin:0.8.7:prepare-agent (default) @ spring-petclinic-rest ---
      [INFO] argLine <span class="hljs-built_in">set</span> to -javaagent:/home/cnb/.m2/repository/org/jacoco/org.jacoco.agent/0.8.7/org.jacoco.agent-0.8.7-runtime.jar=destfile=/workspace/target/jacoco.exec,excludes=**/org/springframework/samples/petclinic/rest/dto/**:**/org/springframework/samples/petclinic/rest/api/**
      [INFO]
      [INFO] --- openapi-generator-maven-plugin:5.2.1:generate (default) @ spring-petclinic-rest ---
      [INFO] Generating with dryRun=<span class="hljs-literal">false</span>
      [INFO] Output directory (/workspace/target/generated-sources/openapi) does not exist, or is inaccessible. No file (.openapi-generator-ignore) will be evaluated.
      [INFO] OpenAPI Generator: spring (server)
      [INFO] Generator <span class="hljs-string">'spring'</span> is considered stable.
      [INFO] ----------------------------------
      [WARNING] java8 option has been deprecated as it<span class="hljs-string">'s set to true by default (JDK7 support has been deprecated)
      [INFO] Environment variable JAVA_POST_PROCESS_FILE not defined so the Java code may not be properly formatted. To define it, try '</span><span class="hljs-built_in">export</span> JAVA_POST_PROCESS_FILE=<span class="hljs-string">"/usr/local/bin/clang-format -i"</span><span class="hljs-string">' (Linux/Mac)
      [INFO] NOTE: To enable file post-processing, '</span>enablePostProcessFile<span class="hljs-string">' must be set to `true` (--enable-post-process-file for CLI).
      [INFO] Invoker Package Name, originally not set, is now derived from api package name: org.springframework.samples.petclinic.rest
      [INFO] Processing operation failingRequest
      [INFO] Processing operation listOwners
      [INFO] Processing operation addOwner
      [INFO] Processing operation getOwner
      [INFO] Processing operation updateOwner
      [INFO] Processing operation addPet
      [INFO] Processing operation getPet
      [INFO] Processing operation updatePet
      [INFO] Processing operation addVisit
      [INFO] Processing operation listPetTypes
      [INFO] Processing operation listVets
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/OwnerAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/OwnerDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/OwnerFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetTypeAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetTypeDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetTypeFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/RestErrorDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/RoleDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/SpecialtyDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/UserDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/ValidationMessageDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VetAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VetDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VetFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VisitAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VisitDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VisitFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/OopsApi.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/OwnersApi.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/PettypesApi.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/VetsApi.java
      [INFO] Skipped /workspace/target/generated-sources/openapi/pom.xml (Skipped by supportingFiles options supplied by user.)
      [INFO] Skipped /workspace/target/generated-sources/openapi/README.md (Skipped by supportingFiles options supplied by user.)
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/ApiUtil.java
      [INFO] Skipped /workspace/target/generated-sources/openapi/.openapi-generator-ignore (Skipped by supportingFiles options supplied by user.)
      [INFO] writing file /workspace/target/generated-sources/openapi/.openapi-generator/VERSION
      [INFO] writing file /workspace/target/generated-sources/openapi/.openapi-generator/FILES
      ################################################################################
      # Thanks for using OpenAPI Generator.                                          #
      # Please consider donation to help us maintain this project ?                 #
      # https://opencollective.com/openapi_generator/donate                          #
      ################################################################################
      [INFO]
      [INFO] --- build-helper-maven-plugin:3.2.0:add-source (add-source) @ spring-petclinic-rest ---
      [INFO] Source directory: /workspace/target/generated-sources/openapi/src/main/java added.
      [INFO]
      [INFO] --- spring-boot-maven-plugin:2.6.2:build-info (default) @ spring-petclinic-rest ---
      [INFO]
      [INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ spring-petclinic-rest ---
      [INFO] Using '</span>UTF-8<span class="hljs-string">' encoding to copy filtered resources.
      [INFO] Using '</span>UTF-8<span class="hljs-string">' encoding to copy filtered properties files.
      [INFO] Copying 4 resources
      [INFO] Copying 13 resources
      [INFO]
      [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-petclinic-rest ---
      [INFO] Changes detected - recompiling the module!
      [INFO] Compiling 108 source files to /workspace/target/classes
      [INFO]
      [INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ spring-petclinic-rest ---
      [INFO] Not copying test resources
      [INFO]
      [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ spring-petclinic-rest ---
      [INFO] Not compiling test sources
      [INFO]
      [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ spring-petclinic-rest ---
      [INFO] Tests are skipped.
      [INFO]
      [INFO] --- jacoco-maven-plugin:0.8.7:report (report) @ spring-petclinic-rest ---
      [INFO] Skipping JaCoCo execution due to missing execution data file.
      [INFO]
      [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ spring-petclinic-rest ---
      [INFO] Building jar: /workspace/target/spring-petclinic-rest-2.6.2.jar
      [INFO]
      [INFO] --- spring-boot-maven-plugin:2.6.2:repackage (repackage) @ spring-petclinic-rest ---
      [INFO] Replacing main artifact with repackaged archive
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  7.741 s
      [INFO] Finished at: 2022-01-16T10:43:45Z
      [INFO] ------------------------------------------------------------------------
  Removing source code

Paketo Executable JAR Buildpack 6.0.2
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 5.3.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.8.0: Reusing cached layer
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.version
===&gt; EXPORT
Reusing layers from image '</span>harbor.registry.com/apps/spring-petclinic-backend@sha256:6cf16c4573c95f5e66e40740d9f6238377b3aaab38176e7986310c21bdd6094a<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/ca-certificates:helper<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/bellsoft-liberica:helper<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/bellsoft-liberica:java-security-properties<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/bellsoft-liberica:jre<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/executable-jar:classpath<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/spring-boot:helper<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/spring-boot:spring-cloud-bindings<span class="hljs-string">'
Reusing layer '</span>paketo-buildpacks/spring-boot:web-application-type<span class="hljs-string">'
Adding layer '</span>launch.sbom<span class="hljs-string">'
Reusing 4/5 app layer(s)
Adding 1/5 app layer(s)
Reusing layer '</span>launcher<span class="hljs-string">'
Reusing layer '</span>config<span class="hljs-string">'
Reusing layer '</span>process-types<span class="hljs-string">'
Adding label '</span>io.buildpacks.lifecycle.metadata<span class="hljs-string">'
Adding label '</span>io.buildpacks.build.metadata<span class="hljs-string">'
Adding label '</span>io.buildpacks.project.metadata<span class="hljs-string">'
Adding label '</span>org.opencontainers.image.title<span class="hljs-string">'
Adding label '</span>org.opencontainers.image.version<span class="hljs-string">'
Adding label '</span>org.springframework.boot.version<span class="hljs-string">'
Setting default process type '</span>web<span class="hljs-string">'
Saving harbor.registry.com/apps/spring-petclinic-backend...
*** Images (sha256:d55635a51867aa722318fd405b288d865c42604ad758804bb20ef913e88e111e):
      harbor.registry.com/apps/spring-petclinic-backend
      harbor.registry.com/apps/spring-petclinic-backend:b2.20220116.104312
Reusing cache layer '</span>paketo-buildpacks/bellsoft-liberica:jdk<span class="hljs-string">'
Reusing cache layer '</span>paketo-buildpacks/syft:syft<span class="hljs-string">'
Adding cache layer '</span>paketo-buildpacks/maven:application<span class="hljs-string">'
Reusing cache layer '</span>paketo-buildpacks/maven:cache<span class="hljs-string">'
Reusing cache layer '</span>cache.sbom<span class="hljs-string">'
===&gt; COMPLETION
Build successful</span>
</code></pre>
<p>Let's run the image again:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/apps/spring-petclinic-backend:b2.20220116.104312
Unable to find image <span class="hljs-string">'harbor.registry.com/apps/spring-petclinic-backend:b2.20220116.104312'</span> locally
b2.20220116.104312: Pulling from apps/spring-petclinic-backend
bf99a8b93828: Already exists
1da1131b1360: Already exists
48bde579d8cc: Already exists
1929d0623312: Already exists
1fea93ec33be: Already exists
fed2bcb93d83: Already exists
d06e3ac07eba: Already exists
a8090bf3b91d: Already exists
204c9b3ef04c: Already exists
95d9537f4e2e: Already exists
40cbb147917e: Already exists
b8e5cf599c18: Already exists
bd74081a3b16: Already exists
1eeff7ac260d: Pull complete
b218c24b76da: Pull complete
afd258a43f9e: Pull complete
89732bc75041: Pull complete
ee556fcb0298: Pull complete
40d17e1b116e: Pull complete
c3e95d93d7ac: Pull complete
b39b18afe74e: Pull complete
Digest: sha256:d55635a51867aa722318fd405b288d865c42604ad758804bb20ef913e88e111e
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/apps/spring-petclinic-backend:b2.20220116.104312
Setting Active Processor Count to 4
WARNING: Unable to convert memory <span class="hljs-built_in">limit</span> <span class="hljs-string">"max"</span> from path <span class="hljs-string">"/sys/fs/cgroup/memory.max"</span> as int: memory size <span class="hljs-string">"max"</span> does not match pattern <span class="hljs-string">"^([\\d]+)([kmgtKMGT]?)$"</span>
Calculating JVM memory based on 1279512K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx643509K -XX:MaxMetaspaceSize=124002K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1279512K, Thread Count: 250, Loaded Class Count: 19479, Headroom: 0%)
Enabling Java Native Memory Tracking
Adding 128 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx643509K -XX:MaxMetaspaceSize=124002K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>

  .   ____          _            __ _ _
 /\\ / ___<span class="hljs-string">'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '</span>_ | <span class="hljs-string">'_| | '</span>_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  <span class="hljs-string">'  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.2)

INFO  PetClinicApplication - Starting PetClinicApplication v2.6.2 using Java 11.0.13 on 8c17a9943dd9 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
DEBUG PetClinicApplication - Running with Spring Boot v2.6.2, Spring v5.3.14
INFO  PetClinicApplication - The following profiles are active: hsqldb,spring-data-jpa
INFO  Version - HV000001: Hibernate Validator 6.2.0.Final
INFO  RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
INFO  RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 81 ms. Found 7 JPA repository interfaces.
INFO  TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
INFO  Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
INFO  StandardService - Starting service [Tomcat]
INFO  StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.56]
INFO  [/petclinic] - Initializing Spring embedded WebApplicationContext
INFO  ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2294 ms
INFO  HikariDataSource - HikariPool-1 - Starting...
INFO  PoolBase - HikariPool-1 - Driver does not support get/set network timeout for connections. (feature not supported)
INFO  HikariDataSource - HikariPool-1 - Start completed.
INFO  LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
INFO  Version - HHH000412: Hibernate ORM core version 5.6.3.Final
INFO  Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
INFO  Dialect - HHH000400: Using dialect: org.hibernate.dialect.HSQLDialect
INFO  JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
INFO  LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit '</span>default<span class="hljs-string">'
INFO  EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '</span>/actuator<span class="hljs-string">'
INFO  UserDetailsServiceAutoConfiguration -

Using generated security password: 73e3ef49-bc5c-4922-a168-570d4d8c048e

INFO  DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@62cbe18c, org.springframework.security.web.context.SecurityContextPersistenceFilter@490b8f1e, org.springframework.security.web.header.HeaderWriterFilter@dc6a186, org.springframework.security.web.authentication.logout.LogoutFilter@35ef158f, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4eb43d2f, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5c70eda4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@377b7798, org.springframework.security.web.session.SessionManagementFilter@27b9d5b7, org.springframework.security.web.access.ExceptionTranslationFilter@71cdde0c, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@6d398b6c]
INFO  Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
INFO  TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '</span>/petclinic<span class="hljs-string">'
INFO  PetClinicApplication - Started PetClinicApplication in 7.167 seconds (JVM running for 7.718)</span>
</code></pre>
<p>The result looks much better now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642332088857/1H6uHoVd1.png" alt="image.png" /></p>
<h4 id="heading-changing-the-defaults">Changing the defaults</h4>
<p>If we'll take a closer look at each buildpack stage in the logs, we'll see that there are certain defaults that are being applied to our image. For example:</p>
<pre><code class="lang-bash">  Build Configuration:
    <span class="hljs-variable">$BP_JVM_TYPE</span>                 JRE             the JVM <span class="hljs-built_in">type</span> - JDK or JRE
    <span class="hljs-variable">$BP_JVM_VERSION</span>              11              the Java version
  Build Configuration:
    <span class="hljs-variable">$BP_MAVEN_BUILD_ARGUMENTS</span>  -Dmaven.test.skip=<span class="hljs-literal">true</span> package  the arguments to pass to Maven
</code></pre>
<p>Each one of these parameters can be changed in order to customize our build. For example - I may want to use Java 17 since it's 2022. I may also opt to run all unit tests as part of my build. In that case, I could change the parameters as follows:</p>
<ul>
<li><code>BP_JVM_VERSION=17</code></li>
<li><code>BP_MAVEN_BUILD_ARGUMENTS="-Dmaven.test.skip=false package"</code>
Let's update our build accordingly:</li>
</ul>
<pre><code class="lang-bash">&gt; kp image save spring-petclinic-backend \
    --tag harbor.registry.com/apps/spring-petclinic-backend \
    --git https://github.com/odedia/spring-petclinic-rest.git \
    --git-revision master \
    --env BP_JVM_VERSION=17 \
    --env BP_MAVEN_BUILD_ARGUMENTS=<span class="hljs-string">"-Dmaven.test.skip=false package"</span>
</code></pre>
<p>If we'll check the updated logs, we'll see that we now use Java 17 and that we are now running all unit tests (logs truncated due to many lines of unit testing...):</p>
<pre><code class="lang-bash">&gt; kp build logs spring-petclinic-backend
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  + env:
  + - name: BP_JVM_VERSION
  +   value: <span class="hljs-string">"17"</span>
  + - name: BP_MAVEN_BUILD_ARGUMENTS
  +   value: -Dmaven.test.skip=<span class="hljs-literal">false</span> package
  resources: {}
  <span class="hljs-built_in">source</span>:
    git:
      revision: c31f00af9fae5ef63c323cfbf5f890f252d8669a
      url: https://github.com/odedia/spring-petclinic-rest.git
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/odedia/spring-petclinic-rest.git"</span> @ <span class="hljs-string">"c31f00af9fae5ef63c323cfbf5f890f252d8669a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/odedia/spring-petclinic-rest.git"</span> @ <span class="hljs-string">"c31f00af9fae5ef63c323cfbf5f890f252d8669a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
===&gt; DETECT
8 of 35 buildpacks participating
paketo-buildpacks/ca-certificates   3.0.2
paketo-buildpacks/bellsoft-liberica 9.0.2
paketo-buildpacks/syft              1.3.1
paketo-buildpacks/maven             6.0.2
paketo-buildpacks/executable-jar    6.0.2
paketo-buildpacks/apache-tomcat     7.0.3
paketo-buildpacks/dist-zip          5.0.2
paketo-buildpacks/spring-boot       5.3.0
===&gt; RESTORE
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/ca-certificates:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:java-security-properties"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jre"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jdk"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/syft:syft"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:application"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:cache"</span> from cache
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:web-application-type"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:helper"</span> from app image
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/spring-boot:spring-cloud-bindings"</span> from app image
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/bellsoft-liberica:jdk"</span> from cache
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/syft:syft"</span> from cache
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:application"</span> from cache
Restoring data <span class="hljs-keyword">for</span> <span class="hljs-string">"paketo-buildpacks/maven:cache"</span> from cache
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Reusing cached layer

Paketo BellSoft Liberica Buildpack 9.0.2
  https://github.com/paketo-buildpacks/bellsoft-liberica
  Build Configuration:
    <span class="hljs-variable">$BP_JVM_TYPE</span>                 JRE             the JVM <span class="hljs-built_in">type</span> - JDK or JRE
    <span class="hljs-variable">$BP_JVM_VERSION</span>              17              the Java version
  Launch Configuration:
    <span class="hljs-variable">$BPL_DEBUG_ENABLED</span>           <span class="hljs-literal">false</span>           enables Java remote debugging support
    <span class="hljs-variable">$BPL_DEBUG_PORT</span>              8000            configure the remote debugging port
    <span class="hljs-variable">$BPL_DEBUG_SUSPEND</span>           <span class="hljs-literal">false</span>           configure whether to <span class="hljs-built_in">suspend</span> execution until a debugger has attached
    <span class="hljs-variable">$BPL_HEAP_DUMP_PATH</span>                          write heap dumps on error to this path
    <span class="hljs-variable">$BPL_JAVA_NMT_ENABLED</span>        <span class="hljs-literal">true</span>            enables Java Native Memory Tracking (NMT)
    <span class="hljs-variable">$BPL_JAVA_NMT_LEVEL</span>          summary         configure level of NMT, summary or detail
    <span class="hljs-variable">$BPL_JFR_ARGS</span>                                configure custom Java Flight Recording (JFR) arguments
    <span class="hljs-variable">$BPL_JFR_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Flight Recording (JFR)
    <span class="hljs-variable">$BPL_JMX_ENABLED</span>             <span class="hljs-literal">false</span>           enables Java Management Extensions (JMX)
    <span class="hljs-variable">$BPL_JMX_PORT</span>                5000            configure the JMX port
    <span class="hljs-variable">$BPL_JVM_HEAD_ROOM</span>           0               the headroom <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_LOADED_CLASS_COUNT</span>  35% of classes  the number of loaded classes <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$BPL_JVM_THREAD_COUNT</span>        250             the number of threads <span class="hljs-keyword">in</span> memory calculation
    <span class="hljs-variable">$JAVA_TOOL_OPTIONS</span>                           the JVM launch flags
  BellSoft Liberica JDK 17.0.1: Contributing to layer
    Reusing cached download from buildpack
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jdk
    Adding 128 container CA certificates to JVM truststore
    Writing env.build/JAVA_HOME.override
    Writing env.build/JDK_HOME.override
  BellSoft Liberica JRE 17.0.1: Contributing to layer
    Reusing cached download from buildpack
    Expanding to /layers/paketo-buildpacks_bellsoft-liberica/jre
    Adding 128 container CA certificates to JVM truststore
    Writing env.launch/BPI_APPLICATION_PATH.default
    Writing env.launch/BPI_JVM_CACERTS.default
    Writing env.launch/BPI_JVM_CLASS_COUNT.default
    Writing env.launch/BPI_JVM_SECURITY_PROVIDERS.default
    Writing env.launch/JAVA_HOME.default
    Writing env.launch/JAVA_TOOL_OPTIONS.append
    Writing env.launch/JAVA_TOOL_OPTIONS.delim
    Writing env.launch/MALLOC_ARENA_MAX.default
  Launch Helper: Reusing cached layer
  Java Security Properties: Reusing cached layer

Paketo Syft Buildpack 1.3.1
  https://github.com/paketo-buildpacks/syft

Paketo Maven Buildpack 6.0.2
  https://github.com/paketo-buildpacks/maven
  Build Configuration:
    <span class="hljs-variable">$BP_MAVEN_BUILD_ARGUMENTS</span>  -Dmaven.test.skip=<span class="hljs-literal">false</span> package  the arguments to pass to Maven
    <span class="hljs-variable">$BP_MAVEN_BUILT_ARTIFACT</span>   target/*.[ejw]ar                 the built application artifact explicitly.  Supersedes <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>
    <span class="hljs-variable">$BP_MAVEN_BUILT_MODULE</span>                                      the module to find application artifact <span class="hljs-keyword">in</span>
    <span class="hljs-variable">$BP_MAVEN_POM_FILE</span>         pom.xml                          the location of the main pom.xml file, relative to the application root
    Creating cache directory /home/cnb/.m2
  Compiled Application: Contributing to layer
    Executing mvnw --batch-mode -Dmaven.test.skip=<span class="hljs-literal">false</span> package
      [INFO] Scanning <span class="hljs-keyword">for</span> projects...
      [INFO]
      [INFO] ---------&lt; org.springframework.samples:spring-petclinic-rest &gt;----------
      [INFO] Building spring-petclinic-rest 2.6.2
      [INFO] --------------------------------[ jar ]---------------------------------
      [INFO]
      [INFO] --- jacoco-maven-plugin:0.8.7:prepare-agent (default) @ spring-petclinic-rest ---
      [INFO] argLine <span class="hljs-built_in">set</span> to -javaagent:/home/cnb/.m2/repository/org/jacoco/org.jacoco.agent/0.8.7/org.jacoco.agent-0.8.7-runtime.jar=destfile=/workspace/target/jacoco.exec,excludes=**/org/springframework/samples/petclinic/rest/dto/**:**/org/springframework/samples/petclinic/rest/api/**
      [INFO]
      [INFO] --- openapi-generator-maven-plugin:5.2.1:generate (default) @ spring-petclinic-rest ---
      [INFO] Generating with dryRun=<span class="hljs-literal">false</span>
      [INFO] Output directory (/workspace/target/generated-sources/openapi) does not exist, or is inaccessible. No file (.openapi-generator-ignore) will be evaluated.
      [INFO] OpenAPI Generator: spring (server)
      [INFO] Generator <span class="hljs-string">'spring'</span> is considered stable.
      [INFO] ----------------------------------
      [WARNING] java8 option has been deprecated as it<span class="hljs-string">'s set to true by default (JDK7 support has been deprecated)
      [INFO] Environment variable JAVA_POST_PROCESS_FILE not defined so the Java code may not be properly formatted. To define it, try '</span><span class="hljs-built_in">export</span> JAVA_POST_PROCESS_FILE=<span class="hljs-string">"/usr/local/bin/clang-format -i"</span><span class="hljs-string">' (Linux/Mac)
      [INFO] NOTE: To enable file post-processing, '</span>enablePostProcessFile<span class="hljs-string">' must be set to `true` (--enable-post-process-file for CLI).
      [INFO] Invoker Package Name, originally not set, is now derived from api package name: org.springframework.samples.petclinic.rest
      [INFO] Processing operation failingRequest
      [INFO] Processing operation listOwners
      [INFO] Processing operation addOwner
      [INFO] Processing operation getOwner
      [INFO] Processing operation updateOwner
      [INFO] Processing operation addPet
      [INFO] Processing operation getPet
      [INFO] Processing operation updatePet
      [INFO] Processing operation addVisit
      [INFO] Processing operation listPetTypes
      [INFO] Processing operation listVets
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/OwnerAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/OwnerDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/OwnerFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetTypeAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetTypeDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/PetTypeFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/RestErrorDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/RoleDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/SpecialtyDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/UserDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/ValidationMessageDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VetAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VetDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VetFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VisitAllOfDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VisitDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/dto/VisitFieldsDto.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/OopsApi.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/OwnersApi.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/PettypesApi.java
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/VetsApi.java
      [INFO] Skipped /workspace/target/generated-sources/openapi/pom.xml (Skipped by supportingFiles options supplied by user.)
      [INFO] Skipped /workspace/target/generated-sources/openapi/README.md (Skipped by supportingFiles options supplied by user.)
      [INFO] writing file /workspace/target/generated-sources/openapi/src/main/java/org/springframework/samples/petclinic/rest/api/ApiUtil.java
      [INFO] Skipped /workspace/target/generated-sources/openapi/.openapi-generator-ignore (Skipped by supportingFiles options supplied by user.)
      [INFO] writing file /workspace/target/generated-sources/openapi/.openapi-generator/VERSION
      [INFO] writing file /workspace/target/generated-sources/openapi/.openapi-generator/FILES
      ################################################################################
      # Thanks for using OpenAPI Generator.                                          #
      # Please consider donation to help us maintain this project ?                 #
      # https://opencollective.com/openapi_generator/donate                          #
      ################################################################################
      [INFO]
      [INFO] --- build-helper-maven-plugin:3.2.0:add-source (add-source) @ spring-petclinic-rest ---
      [INFO] Source directory: /workspace/target/generated-sources/openapi/src/main/java added.
      [INFO]
      [INFO] --- spring-boot-maven-plugin:2.6.2:build-info (default) @ spring-petclinic-rest ---
      [INFO]
      [INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ spring-petclinic-rest ---
      [INFO] Using '</span>UTF-8<span class="hljs-string">' encoding to copy filtered resources.
      [INFO] Using '</span>UTF-8<span class="hljs-string">' encoding to copy filtered properties files.
      [INFO] Copying 4 resources
      [INFO] Copying 13 resources
      [INFO]
      [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ spring-petclinic-rest ---
      [INFO] Changes detected - recompiling the module!
      [INFO] Compiling 108 source files to /workspace/target/classes
      [INFO]
      [INFO] --- maven-resources-plugin:3.2.0:testResources (default-testResources) @ spring-petclinic-rest ---
      [INFO] Using '</span>UTF-8<span class="hljs-string">' encoding to copy filtered resources.
      [INFO] Using '</span>UTF-8<span class="hljs-string">' encoding to copy filtered properties files.
      [INFO] Copying 1 resource
      [INFO]
      [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ spring-petclinic-rest ---
      [INFO] Changes detected - recompiling the module!
      [INFO] Compiling 18 source files to /workspace/target/test-classes
      [INFO]
      [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ spring-petclinic-rest ---
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/2.22.2/surefire-junit-platform-2.22.2.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/2.22.2/surefire-junit-platform-2.22.2.pom (7.0 kB at 28 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-providers/2.22.2/surefire-providers-2.22.2.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-providers/2.22.2/surefire-providers-2.22.2.pom (2.5 kB at 113 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1.pom (2.2 kB at 222 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.pom (1.2 kB at 146 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1.pom (2.4 kB at 126 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.pom (2.0 kB at 202 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.pom
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.pom (1.7 kB at 129 kB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/2.22.2/surefire-junit-platform-2.22.2.jar
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1.jar
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.jar
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.jar
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1.jar
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apiguardian/apiguardian-api/1.0.0/apiguardian-api-1.0.0.jar (2.2 kB at 37 kB/s)
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit-platform/2.22.2/surefire-junit-platform-2.22.2.jar (66 kB at 1.0 MB/s)
      [INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.jar
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/opentest4j/opentest4j/1.1.1/opentest4j-1.1.1.jar (7.1 kB at 647 kB/s)
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-launcher/1.3.1/junit-platform-launcher-1.3.1.jar (95 kB at 1.1 MB/s)
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-commons/1.3.1/junit-platform-commons-1.3.1.jar (78 kB at 771 kB/s)
      [INFO] Downloaded from central: https://repo.maven.apache.org/maven2/org/junit/platform/junit-platform-engine/1.3.1/junit-platform-engine-1.3.1.jar (135 kB at 1.3 MB/s)
      [INFO]
      [INFO] -------------------------------------------------------
      [INFO]  T E S T S
      [INFO] -------------------------------------------------------
      [INFO] Running org.springframework.samples.petclinic.rest.controller.OwnerRestControllerTests
      INFO  AbstractContextLoader - Could not detect default resource locations for test class [org.springframework.samples.petclinic.rest.controller.OwnerRestControllerTests]: no resource found for suffixes {-context.xml, Context.groovy}.
      INFO  SpringBootTestContextBootstrapper - Found @SpringBootConfiguration org.springframework.samples.petclinic.PetClinicApplication for test class org.springframework.samples.petclinic.rest.controller.OwnerRestControllerTests
      INFO  SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.event.ApplicationEventsTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener, org.springframework.security.test.context.support.ReactorContextTestExecutionListener]
      INFO  SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@ea52184, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@3e7b65d7, org.springframework.test.context.event.ApplicationEventsTestExecutionListener@3c854752, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@3ddeaa5f, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@1a500561, org.springframework.test.context.support.DirtiesContextTestExecutionListener@7f608e21, org.springframework.test.context.transaction.TransactionalTestExecutionListener@6949e948, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@210d2a6c, org.springframework.test.context.event.EventPublishingTestExecutionListener@3bec5821, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener@4086d8fb, org.springframework.security.test.context.support.ReactorContextTestExecutionListener@66236a0a, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@2e3572e8, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@77c10a5f, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@176555c, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@7c781c42, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@795f8317, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@633fd91]

        .   ____          _            __ _ _
       /\\ / ___'</span>_ __ _ _(_)_ __  __ _ \ \ \ \
      ( ( )\___ | <span class="hljs-string">'_ | '</span>_| | <span class="hljs-string">'_ \/ _` | \ \ \ \
       \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
        '</span>  |____| .__|_| |_|_| |_\__, | / / / /
       =========|_|==============|___/=/_/_/_/
       :: Spring Boot ::                (v2.6.2)

      INFO  OwnerRestControllerTests - Starting OwnerRestControllerTests using Java 17.0.1 on spring-petclinic-backend-build-3-build-pod with PID 82 (started by cnb <span class="hljs-keyword">in</span> /workspace)
      DEBUG OwnerRestControllerTests - Running with Spring Boot v2.6.2, Spring v5.3.14
      INFO  OwnerRestControllerTests - The following profiles are active: hsqldb,spring-data-jpa
      INFO  RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories <span class="hljs-keyword">in</span> DEFAULT mode.
      INFO  RepositoryConfigurationDelegate - Finished Spring Data repository scanning <span class="hljs-keyword">in</span> 108 ms. Found 7 JPA repository interfaces.
      INFO  HikariDataSource - HikariPool-1 - Starting...
      INFO  PoolBase - HikariPool-1 - Driver does not support get/<span class="hljs-built_in">set</span> network timeout <span class="hljs-keyword">for</span> connections. (feature not supported)
      INFO  HikariDataSource - HikariPool-1 - Start completed.
      INFO  LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
      INFO  Version - HHH000412: Hibernate ORM core version 5.6.3.Final
      INFO  Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
      INFO  Dialect - HHH000400: Using dialect: org.hibernate.dialect.HSQLDialect
      INFO  Version - HV000001: Hibernate Validator 6.2.0.Final
      INFO  JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
      INFO  LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path <span class="hljs-string">'/actuator'</span>
      INFO  DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4195e7fb, org.springframework.security.web.context.SecurityContextPersistenceFilter@3999f490, org.springframework.security.web.header.HeaderWriterFilter@152a8948, org.springframework.security.web.authentication.logout.LogoutFilter@20df4edc, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@191323f5, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@47ac1aa0, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4fe30669, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@187ed1ef, org.springframework.security.web.session.SessionManagementFilter@7472bba0, org.springframework.security.web.access.ExceptionTranslationFilter@2104e947, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7a4f9db7]
      INFO  OwnerRestControllerTests - Started OwnerRestControllerTests <span class="hljs-keyword">in</span> 10.988 seconds (JVM running <span class="hljs-keyword">for</span> 13.299)
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      --&gt; newPetAsJSON={<span class="hljs-string">"name"</span>:<span class="hljs-string">"Rosy"</span>,<span class="hljs-string">"birthDate"</span>:<span class="hljs-string">"2022-01-16"</span>,<span class="hljs-string">"type"</span>:{<span class="hljs-string">"name"</span>:<span class="hljs-string">"dog"</span>,<span class="hljs-string">"id"</span>:2},<span class="hljs-string">"id"</span>:999,<span class="hljs-string">"ownerId"</span>:null,<span class="hljs-string">"visits"</span>:[]}
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 1 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms

      MockHttpServletRequest:
            HTTP Method = POST
            Request URI = /api/owners/1/pets/
             Parameters = {}
                Headers = [Content-Type:<span class="hljs-string">"application/json"</span>, Accept:<span class="hljs-string">"application/json"</span>, Content-Length:<span class="hljs-string">"104"</span>]
                   Body = &lt;no character encoding <span class="hljs-built_in">set</span>&gt;
          Session Attrs = {}

      Handler:
                   Type = org.springframework.samples.petclinic.rest.controller.OwnerRestController
                 Method = org.springframework.samples.petclinic.rest.controller.OwnerRestController<span class="hljs-comment">#addPet(Integer, PetFieldsDto)</span>

      Async:
          Async started = <span class="hljs-literal">false</span>
           Async result = null

      Resolved Exception:
                   Type = org.springframework.web.bind.MethodArgumentNotValidException

      ModelAndView:
              View name = null
                   View = null
                  Model = null

      FlashMap:
             Attributes = null

      MockHttpServletResponse:
                 Status = 400
          Error message = null
                Headers = [Vary:<span class="hljs-string">"Origin"</span>, <span class="hljs-string">"Access-Control-Request-Method"</span>, <span class="hljs-string">"Access-Control-Request-Headers"</span>, Content-Type:<span class="hljs-string">"application/json"</span>, Content-Length:<span class="hljs-string">"737"</span>]
           Content <span class="hljs-built_in">type</span> = application/json
                   Body = {<span class="hljs-string">"className"</span>:<span class="hljs-string">"org.springframework.web.bind.MethodArgumentNotValidException"</span>,<span class="hljs-string">"exMessage"</span>:<span class="hljs-string">"Validation failed for argument [1] in public org.springframework.http.ResponseEntity&lt;org.springframework.samples.petclinic.rest.dto.PetDto&gt; org.springframework.samples.petclinic.rest.controller.OwnerRestController.addPet(java.lang.Integer,org.springframework.samples.petclinic.rest.dto.PetFieldsDto): [Field error in object 'petFieldsDto' on field 'name': rejected value [null]; codes [NotNull.petFieldsDto.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [petFieldsDto.name,name]; arguments []; default message [name]]; default message [must not be null]] "</span>}
          Forwarded URL = null
         Redirected URL = null
                Cookies = []
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 5 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      newVisitAsJSON {<span class="hljs-string">"id"</span>:999,<span class="hljs-string">"date"</span>:<span class="hljs-string">"2022-01-16"</span>,<span class="hljs-string">"description"</span>:<span class="hljs-string">"rabies shot"</span>,<span class="hljs-string">"pet"</span>:null}
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 1 ms
      INFO  MockServletContext - Initializing Spring TestDispatcherServlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Initializing Servlet <span class="hljs-string">''</span>
      INFO  TestDispatcherServlet - Completed initialization <span class="hljs-keyword">in</span> 0 ms
      [INFO] Tests run: 16, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 13.43 s - <span class="hljs-keyword">in</span> org.springframework.samples.petclinic.rest.controller.OwnerRestControllerTests
      [INFO] Running org.springframework.samples.petclinic.rest.controller.SpecialtyRestControllerTests
      INFO  AbstractContextLoader - Could not detect default resource locations <span class="hljs-keyword">for</span> <span class="hljs-built_in">test</span> class [org.springframework.samples.petclinic.rest.controller.SpecialtyRestControllerTests]: no resource found <span class="hljs-keyword">for</span> suffixes {-context.xml, Context.groovy}.
      INFO  SpringBootTestContextBootstrapper - Found @SpringBootConfiguration org.springframework.samples.petclinic.PetClinicApplication <span class="hljs-keyword">for</span> <span class="hljs-built_in">test</span> class org.springframework.samples.petclinic.rest.controller.SpecialtyRestControllerTests
...
...
      INFO  DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@4af026c6, org.springframework.security.web.context.SecurityContextPersistenceFilter@2ce17213, org.springframework.security.web.header.HeaderWriterFilter@29d76031, org.springframework.security.web.authentication.logout.LogoutFilter@2a97b5a2, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@72b927c6, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5295b114, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@40d1859f, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@268433c9, org.springframework.security.web.session.SessionManagementFilter@64653709, org.springframework.security.web.access.ExceptionTranslationFilter@1dac64c7, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@a53b06d]
      INFO  SpringConfigTests - Started SpringConfigTests <span class="hljs-keyword">in</span> 1.855 seconds (JVM running <span class="hljs-keyword">for</span> 29.56)
      [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.845 s - <span class="hljs-keyword">in</span> org.springframework.samples.petclinic.SpringConfigTests
      INFO  LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  HikariDataSource - HikariPool-1 - Shutdown initiated...
      INFO  HikariDataSource - HikariPool-1 - Shutdown completed.
      INFO  LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  HikariDataSource - HikariPool-2 - Shutdown initiated...
      INFO  HikariDataSource - HikariPool-2 - Shutdown completed.
      INFO  LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  HikariDataSource - HikariPool-3 - Shutdown initiated...
      INFO  HikariDataSource - HikariPool-3 - Shutdown completed.
      INFO  LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  HikariDataSource - HikariPool-4 - Shutdown initiated...
      INFO  HikariDataSource - HikariPool-4 - Shutdown completed.
      INFO  LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  HikariDataSource - HikariPool-5 - Shutdown initiated...
      INFO  HikariDataSource - HikariPool-5 - Shutdown completed.
      INFO  LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory <span class="hljs-keyword">for</span> persistence unit <span class="hljs-string">'default'</span>
      INFO  HikariDataSource - HikariPool-6 - Shutdown initiated...
      INFO  HikariDataSource - HikariPool-6 - Shutdown completed.
      [INFO]
      [INFO] Results:
      [INFO]
      [INFO] Tests run: 172, Failures: 0, Errors: 0, Skipped: 0
      [INFO]
      [INFO]
      [INFO] --- jacoco-maven-plugin:0.8.7:report (report) @ spring-petclinic-rest ---
      [INFO] Loading execution data file /workspace/target/jacoco.exec
      [INFO] Analyzed bundle <span class="hljs-string">'spring-petclinic-rest'</span> with 65 classes
      [INFO]
      [INFO] --- maven-jar-plugin:3.2.0:jar (default-jar) @ spring-petclinic-rest ---
      [INFO] Building jar: /workspace/target/spring-petclinic-rest-2.6.2.jar
      [INFO]
      [INFO] --- spring-boot-maven-plugin:2.6.2:repackage (repackage) @ spring-petclinic-rest ---
      [INFO] Replacing main artifact with repackaged archive
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS
      [INFO] ------------------------------------------------------------------------
      [INFO] Total time:  39.657 s
      [INFO] Finished at: 2022-01-16T10:58:22Z
      [INFO] ------------------------------------------------------------------------
  Removing <span class="hljs-built_in">source</span> code

Paketo Executable JAR Buildpack 6.0.2
  https://github.com/paketo-buildpacks/executable-jar
  Class Path: Contributing to layer
    Writing env/CLASSPATH.delim
    Writing env/CLASSPATH.prepend
  Process types:
    executable-jar: java org.springframework.boot.loader.JarLauncher (direct)
    task:           java org.springframework.boot.loader.JarLauncher (direct)
    web:            java org.springframework.boot.loader.JarLauncher (direct)

Paketo Spring Boot Buildpack 5.3.0
  https://github.com/paketo-buildpacks/spring-boot
  Creating slices from layers index
    dependencies
    spring-boot-loader
    snapshot-dependencies
    application
  Launch Helper: Reusing cached layer
  Spring Cloud Bindings 1.8.0: Reusing cached layer
  Web Application Type: Contributing to layer
    Servlet web application detected
    Writing env.launch/BPL_JVM_THREAD_COUNT.default
  4 application slices
  Image labels:
    org.opencontainers.image.title
    org.opencontainers.image.version
    org.springframework.boot.version
===&gt; EXPORT
Reusing layers from image <span class="hljs-string">'harbor.registry.com/apps/spring-petclinic-backend@sha256:d55635a51867aa722318fd405b288d865c42604ad758804bb20ef913e88e111e'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/ca-certificates:helper'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:helper'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:java-security-properties'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:jre'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/executable-jar:classpath'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/spring-boot:helper'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/spring-boot:spring-cloud-bindings'</span>
Reusing layer <span class="hljs-string">'paketo-buildpacks/spring-boot:web-application-type'</span>
Adding layer <span class="hljs-string">'launch.sbom'</span>
Reusing 4/5 app layer(s)
Adding 1/5 app layer(s)
Reusing layer <span class="hljs-string">'launcher'</span>
Reusing layer <span class="hljs-string">'config'</span>
Reusing layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Adding label <span class="hljs-string">'org.opencontainers.image.title'</span>
Adding label <span class="hljs-string">'org.opencontainers.image.version'</span>
Adding label <span class="hljs-string">'org.springframework.boot.version'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/apps/spring-petclinic-backend...
*** Images (sha256:8096781a5d7236c037ef39a128a99eae397ba733d80600c515df09b93228067b):
      harbor.registry.com/apps/spring-petclinic-backend
      harbor.registry.com/apps/spring-petclinic-backend:b3.20220116.105703
Adding cache layer <span class="hljs-string">'paketo-buildpacks/bellsoft-liberica:jdk'</span>
Reusing cache layer <span class="hljs-string">'paketo-buildpacks/syft:syft'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/maven:application'</span>
Adding cache layer <span class="hljs-string">'paketo-buildpacks/maven:cache'</span>
Adding cache layer <span class="hljs-string">'cache.sbom'</span>
===&gt; COMPLETION
Build successful
</code></pre>
<p>Let's run the updated image:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/apps/spring-petclinic-backend:b3.20220116.105703
Setting Active Processor Count to 4
WARNING: Unable to convert memory <span class="hljs-built_in">limit</span> <span class="hljs-string">"max"</span> from path <span class="hljs-string">"/sys/fs/cgroup/memory.max"</span> as int: memory size <span class="hljs-string">"max"</span> does not match pattern <span class="hljs-string">"^([\\d]+)([kmgtKMGT]?)$"</span>
Calculating JVM memory based on 1283900K available memory
Calculated JVM Memory Configuration: -XX:MaxDirectMemorySize=10M -Xmx655838K -XX:MaxMetaspaceSize=116061K -XX:ReservedCodeCacheSize=240M -Xss1M (Total Memory: 1283900K, Thread Count: 250, Loaded Class Count: 18077, Headroom: 0%)
Enabling Java Native Memory Tracking
Adding 128 container CA certificates to JVM truststore
Spring Cloud Bindings Enabled
Picked up JAVA_TOOL_OPTIONS: -Djava.security.properties=/layers/paketo-buildpacks_bellsoft-liberica/java-security-properties/java-security.properties -XX:+ExitOnOutOfMemoryError -XX:ActiveProcessorCount=4 -XX:MaxDirectMemorySize=10M -Xmx655838K -XX:MaxMetaspaceSize=116061K -XX:ReservedCodeCacheSize=240M -Xss1M -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -Dorg.springframework.cloud.bindings.boot.enable=<span class="hljs-literal">true</span>

  .   ____          _            __ _ _
 /\\ / ___<span class="hljs-string">'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '</span>_ | <span class="hljs-string">'_| | '</span>_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  <span class="hljs-string">'  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.2)

INFO  Version - HV000001: Hibernate Validator 6.2.0.Final
INFO  PetClinicApplication - Starting PetClinicApplication v2.6.2 using Java 17.0.1 on de3b2736ea60 with PID 1 (/workspace/BOOT-INF/classes started by cnb in /workspace)
DEBUG PetClinicApplication - Running with Spring Boot v2.6.2, Spring v5.3.14
INFO  PetClinicApplication - The following profiles are active: hsqldb,spring-data-jpa
INFO  RepositoryConfigurationDelegate - Bootstrapping Spring Data JPA repositories in DEFAULT mode.
INFO  RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 80 ms. Found 7 JPA repository interfaces.
INFO  TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
INFO  Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
INFO  StandardService - Starting service [Tomcat]
INFO  StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.56]
INFO  [/petclinic] - Initializing Spring embedded WebApplicationContext
INFO  ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2020 ms
INFO  HikariDataSource - HikariPool-1 - Starting...
INFO  PoolBase - HikariPool-1 - Driver does not support get/set network timeout for connections. (feature not supported)
INFO  HikariDataSource - HikariPool-1 - Start completed.
INFO  LogHelper - HHH000204: Processing PersistenceUnitInfo [name: default]
INFO  Version - HHH000412: Hibernate ORM core version 5.6.3.Final
INFO  Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
INFO  Dialect - HHH000400: Using dialect: org.hibernate.dialect.HSQLDialect
INFO  JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
INFO  LocalContainerEntityManagerFactoryBean - Initialized JPA EntityManagerFactory for persistence unit '</span>default<span class="hljs-string">'
INFO  EndpointLinksResolver - Exposing 1 endpoint(s) beneath base path '</span>/actuator<span class="hljs-string">'
INFO  UserDetailsServiceAutoConfiguration -

Using generated security password: 86095fd7-2e96-4455-bf12-c85a821c51b0

INFO  DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@18206382, org.springframework.security.web.context.SecurityContextPersistenceFilter@6030eff1, org.springframework.security.web.header.HeaderWriterFilter@cec1ce2, org.springframework.security.web.authentication.logout.LogoutFilter@311eb725, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5a02fca5, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@309ac89e, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3e8c9981, org.springframework.security.web.session.SessionManagementFilter@730611a0, org.springframework.security.web.access.ExceptionTranslationFilter@38320819, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@38816a6c]
INFO  Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
INFO  TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path '</span>/petclinic<span class="hljs-string">'
INFO  PetClinicApplication - Started PetClinicApplication in 6.352 seconds (JVM running for 6.824)</span>
</code></pre>
<p>We can see from the logs that the JRE is now version 17, and also that our startup time has improved from 7.5 seconds to just under 6 seconds.
The application still works as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642331751811/fv53bJtEt.png" alt="image.png" /></p>
<h4 id="heading-some-more-tips-and-tricks">Some more tips and tricks</h4>
<ul>
<li>If you have multiple microservices in a single git repo, use the <code>--sub-path</code> parameter to point Build Service to the relevant sub-path.</li>
<li>The buildpack will honor any maven repo setting you may have in your source code. So, if you are running in an airgapped environment and target a local Nexus or Jfrog Artifactory repo, make sure your maven <code>pom.xml</code> or gradle <code>build.settings</code> are set with the correct local repo.</li>
<li>If you don't want Build Service to kick off a new image automatically on each git commit, set the <code>--git-revision</code> parameter to the current git commit id and not the desired git branch. Then, as part of your pipeline, you can update the commit ID to kick off a new build. Here is a snippet you can include in your CI pipeline:</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> COMMIT_ID=`git rev-parse HEAD`
<span class="hljs-built_in">echo</span> Commit ID is: <span class="hljs-variable">$COMMIT_ID</span>
kp image save spring-petclinic-backend \
    --tag harbor.registry.com/apps/spring-petclinic-backend \
    --git https://github.com/odedia/spring-petclinic-rest.git \
    --git-revision <span class="hljs-variable">$COMMIT_ID</span> \
    --env BP_JVM_VERSION=17 \
    --env BP_MAVEN_BUILD_ARGUMENTS=<span class="hljs-string">"-Dmaven.test.skip=false package"</span>
</code></pre>
<ul>
<li>If you want to extract the SHA256 of the last build image, you can use the below kubectl command:<pre><code>export LATEST_IMAGE_SHA<span class="hljs-operator">=</span>`kubectl get images.kpack.io spring<span class="hljs-operator">-</span>petclinic<span class="hljs-operator">-</span>backend <span class="hljs-operator">-</span>o json <span class="hljs-operator">|</span> jq .status.latestImage`
echo Latest image <span class="hljs-built_in">sha256</span> <span class="hljs-keyword">is</span>: $LATEST_IMAGE_SHA
</code></pre></li>
</ul>
<h2 id="heading-net-core-eshopweb">.NET Core eShopWeb</h2>
<p>.NET Core is the second most popular language I see in the field. It had its share of changes and turmoils with the move from .NET Framework to .NET Core 3, and now "Just .NET 5" and .NET 6. Let's try to build the popular eShopWeb .NET Core sample application.</p>
<h4 id="heading-git-repo">Git repo</h4>
<p>The git repository is available at https://github.com/dotnet-architecture/eShopOnWeb.git</p>
<h4 id="heading-build-command">Build command</h4>
<p>Let's build the image:</p>
<pre><code class="lang-bash">&gt; kp image save dotnet-eshopweb \
&gt; --tag harbor.registry.com/apps/dotnet-eshopweb \
&gt; --git https://github.com/dotnet-architecture/eShopOnWeb.git \
&gt; --git-revision main
Creating Image Resource...
Image Resource <span class="hljs-string">"dotnet-eshopweb"</span> created
</code></pre>
<h4 id="heading-build-logs-output">Build logs output</h4>
<p>Let's check the logs:</p>
<pre><code class="lang-bash">&gt; kp build logs dotnet-eshopweb
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  - <span class="hljs-built_in">source</span>: {}
  + <span class="hljs-built_in">source</span>:
  +   git:
  +     revision: 896fae150fbcec5de8a1fb923eab5dfafe09422a
  +     url: https://github.com/dotnet-architecture/eShopOnWeb.git
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
Previous image with name <span class="hljs-string">"harbor.registry.com/apps/dotnet-eshopweb"</span> not found
===&gt; DETECT
ERROR: No buildpack groups passed detection.
ERROR: Please check that you are running against the correct path.
ERROR: failed to detect: no buildpacks participating
</code></pre>
<p>Well that didn't work... What happened here?</p>
<p>It seems that, like many .NET Core projects, the source code is nested inside our main repo. Let's try to build the ApplicationCore module by pointing at the sub path:</p>
<pre><code class="lang-bash">&gt; kp image save dotnet-eshopweb \
--tag harbor.registry.com/apps/dotnet-eshopweb \
--git https://github.com/dotnet-architecture/eShopOnWeb.git \
--git-revision main \
--sub-path ./src/Web
</code></pre>
<p>Let's check the updated logs:</p>
<pre><code class="lang-bash">&gt; kp build logs dotnet-eshopweb
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  - <span class="hljs-built_in">source</span>: {}
  + <span class="hljs-built_in">source</span>:
  +   git:
  +     revision: 896fae150fbcec5de8a1fb923eab5dfafe09422a
  +     url: https://github.com/dotnet-architecture/eShopOnWeb.git
  +   subPath: ./src/Web
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
Previous image with name <span class="hljs-string">"harbor.registry.com/apps/dotnet-eshopweb"</span> not found
===&gt; DETECT
7 of 13 buildpacks participating
paketo-buildpacks/ca-certificates    3.0.2
tanzu-buildpacks/dotnet-core-runtime 0.3.1
tanzu-buildpacks/dotnet-core-aspnet  0.3.2
tanzu-buildpacks/dotnet-core-sdk     0.3.1
tanzu-buildpacks/icu                 0.0.13
tanzu-buildpacks/dotnet-publish      0.5.1
tanzu-buildpacks/dotnet-execute      0.4.1
===&gt; RESTORE
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Tanzu .NET Core Runtime Buildpack 0.3.1
  Resolving Dotnet Core Runtime version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      Web.csproj -&gt; <span class="hljs-string">"6.0.0"</span>
      &lt;unknown&gt;  -&gt; <span class="hljs-string">""</span>

    Selected dotnet-runtime version (using Web.csproj): 6.0.0

  Executing build process
    Installing Dotnet Core Runtime 6.0.0
      Completed <span class="hljs-keyword">in</span> 4.228s

  Configuring environment <span class="hljs-keyword">for</span> build and launch
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>

  Configuring environment <span class="hljs-keyword">for</span> build
    RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.0"</span>

Tanzu ASP.NET Core Buildpack 0.3.2
  Resolving Dotnet Core ASPNet version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.0"</span>
      Web.csproj      -&gt; <span class="hljs-string">"6.0.0"</span>
      &lt;unknown&gt;       -&gt; <span class="hljs-string">""</span>

    Selected dotnet-aspnetcore version (using RUNTIME_VERSION): 6.0.0

  Executing build process
    Installing Dotnet Core ASPNet 6.0.0
      Completed <span class="hljs-keyword">in</span> 1.292s

  Configuring environment
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>

Tanzu .NET Core SDK Buildpack 0.3.1
  Resolving .NET Core SDK version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.100"</span>
      &lt;unknown&gt;       -&gt; <span class="hljs-string">"*"</span>
      Web.csproj      -&gt; <span class="hljs-string">"6.0.*"</span>

    Selected .NET Core SDK version (using RUNTIME_VERSION): 6.0.100

  Executing build process
    Installing .NET Core SDK 6.0.100
      Completed <span class="hljs-keyword">in</span> 16.038s

  Configuring environment
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>
    PATH        -&gt; <span class="hljs-string">"/workspace/.dotnet_root:<span class="hljs-variable">$PATH</span>"</span>

Tanzu ICU Buildpack 0.0.13
  Executing build process
    Installing ICU
      Completed <span class="hljs-keyword">in</span> 480ms

Tanzu .Net Publish Buildpack 0.5.1
  Executing build process
    Running <span class="hljs-string">'dotnet publish /workspace --configuration Release --runtime ubuntu.18.04-x64 --self-contained false --output /tmp/dotnet-publish-output2505033169'</span>
      Failed after 20.780801107s
failed to execute <span class="hljs-string">'dotnet publish'</span>: <span class="hljs-built_in">exit</span> status 1

        Welcome to .NET 6.0!
        ---------------------
        SDK Version: 6.0.100

        Telemetry
        ---------
        The .NET tools collect usage data <span class="hljs-keyword">in</span> order to <span class="hljs-built_in">help</span> us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to <span class="hljs-string">'1'</span> or <span class="hljs-string">'true'</span> using your favorite shell.

        Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

        ----------------
        Installed an ASP.NET Core HTTPS development certificate.
        To trust the certificate run <span class="hljs-string">'dotnet dev-certs https --trust'</span> (Windows and macOS only).
        Learn about HTTPS: https://aka.ms/dotnet-https
        ----------------
        Write your first app: https://aka.ms/dotnet-hello-world
        Find out what<span class="hljs-string">'s new: https://aka.ms/dotnet-whats-new
        Explore documentation: https://aka.ms/dotnet-docs
        Report issues and find source on GitHub: https://github.com/dotnet/core
        Use '</span>dotnet --<span class="hljs-built_in">help</span><span class="hljs-string">' to see available commands or visit: https://aka.ms/dotnet-cli
        --------------------------------------------------------------------------------------
        Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
        Copyright (C) Microsoft Corporation. All rights reserved.

          Determining projects to restore...
          Skipping project "/ApplicationCore/ApplicationCore.csproj" because it was not found.
          Skipping project "/BlazorAdmin/BlazorAdmin.csproj" because it was not found.
          Skipping project "/BlazorShared/BlazorShared.csproj" because it was not found.
          Skipping project "/Infrastructure/Infrastructure.csproj" because it was not found.
          Skipping project "/ApplicationCore/ApplicationCore.csproj" because it was not found.
          Skipping project "/BlazorAdmin/BlazorAdmin.csproj" because it was not found.
          Skipping project "/BlazorShared/BlazorShared.csproj" because it was not found.
          Skipping project "/Infrastructure/Infrastructure.csproj" because it was not found.
          Restored /workspace/Web.csproj (in 14.4 sec).

          Restore operation started...

          6 libraries restored in 0.56 seconds

        /layers/tanzu-buildpacks_dotnet-core-sdk/dotnet-core-sdk/sdk/6.0.100/Microsoft.Common.CurrentVersion.targets(2068,5): warning : The referenced project '</span>../ApplicationCore/ApplicationCore.csproj<span class="hljs-string">' does not exist. [/workspace/Web.csproj]
        /layers/tanzu-buildpacks_dotnet-core-sdk/dotnet-core-sdk/sdk/6.0.100/Microsoft.Common.CurrentVersion.targets(2068,5): warning : The referenced project '</span>../BlazorAdmin/BlazorAdmin.csproj<span class="hljs-string">' does not exist. [/workspace/Web.csproj]
        /layers/tanzu-buildpacks_dotnet-core-sdk/dotnet-core-sdk/sdk/6.0.100/Microsoft.Common.CurrentVersion.targets(2068,5): warning : The referenced project '</span>../BlazorShared/BlazorShared.csproj<span class="hljs-string">' does not exist. [/workspace/Web.csproj]
        /layers/tanzu-buildpacks_dotnet-core-sdk/dotnet-core-sdk/sdk/6.0.100/Microsoft.Common.CurrentVersion.targets(2068,5): warning : The referenced project '</span>../Infrastructure/Infrastructure.csproj<span class="hljs-string">' does not exist. [/workspace/Web.csproj]

          Bundler: Begin processing bundleconfig.json
          Bundler: Done processing bundleconfig.json
        /workspace/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs(9,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Login.cshtml.cs(12,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Login.cshtml.cs(13,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Logout.cshtml.cs(10,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(11,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Configuration/ConfigureCoreServices.cs(1,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Configuration/ConfigureCoreServices.cs(2,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Configuration/ConfigureCoreServices.cs(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Configuration/ConfigureCoreServices.cs(4,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Configuration/ConfigureCoreServices.cs(5,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Configuration/ConfigureCoreServices.cs(6,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(10,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(11,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/UserController.cs(5,7): error CS0246: The type or namespace name '</span>BlazorShared<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/UserController.cs(8,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Extensions/EmailSenderExtensions.cs(3,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(6,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(7,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(8,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(5,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(6,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(7,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/HealthChecks/ApiHealthCheck.cs(4,7): error CS0246: The type or namespace name '</span>BlazorShared<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Interfaces/IBasketViewModelService.cs(1,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Admin/EditCatalogItem.cshtml.cs(5,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Admin/Index.cshtml.cs(4,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(11,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(12,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(13,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(14,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Index.cshtml.cs(8,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Shared/Components/BasketComponent/Basket.cs(5,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(3,7): error CS0246: The type or namespace name '</span>BlazorAdmin<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(4,7): error CS0246: The type or namespace name '</span>BlazorAdmin<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(5,7): error CS0246: The type or namespace name '</span>Blazored<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(6,7): error CS0246: The type or namespace name '</span>BlazorShared<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(13,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(14,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Program.cs(15,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(4,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(5,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(6,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(7,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogItemViewModelService.cs(2,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogItemViewModelService.cs(3,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(6,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(7,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(8,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/ViewModels/OrderViewModel.cs(3,26): error CS0234: The type or namespace name '</span>ApplicationCore<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/_ViewImports.cshtml(3,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs(16,34): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs(18,42): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(528,55): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(22,34): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(23,36): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(24,22): error CS0246: The type or namespace name '</span>IEmailSender<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(25,22): error CS0246: The type or namespace name '</span>IAppLogger&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(32,19): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(33,21): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(34,7): error CS0246: The type or namespace name '</span>IEmailSender<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/ManageController.cs(35,7): error CS0246: The type or namespace name '</span>IAppLogger&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(15,22): error CS0246: The type or namespace name '</span>IReadRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(15,38): error CS0246: The type or namespace name '</span>Order<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(17,31): error CS0246: The type or namespace name '</span>IReadRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/MyOrders/GetMyOrdersHandler.cs(17,47): error CS0246: The type or namespace name '</span>Order<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(14,22): error CS0246: The type or namespace name '</span>IReadRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(14,38): error CS0246: The type or namespace name '</span>Order<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Login.cshtml.cs(21,36): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/UserController.cs(29,24): error CS0246: The type or namespace name '</span>UserInfo<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(16,35): error CS0246: The type or namespace name '</span>IReadRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Features/OrderDetails/GetOrderDetailsHandler.cs(16,51): error CS0246: The type or namespace name '</span>Order<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/UserController.cs(16,22): error CS0246: The type or namespace name '</span>ITokenClaimsService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Login.cshtml.cs(23,22): error CS0246: The type or namespace name '</span>IBasketService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Controllers/UserController.cs(18,27): error CS0246: The type or namespace name '</span>ITokenClaimsService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/HealthChecks/ApiHealthCheck.cs(12,22): error CS0246: The type or namespace name '</span>BaseUrlConfiguration<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/HealthChecks/ApiHealthCheck.cs(14,36): error CS0246: The type or namespace name '</span>BaseUrlConfiguration<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Extensions/EmailSenderExtensions.cs(9,56): error CS0246: The type or namespace name '</span>IEmailSender<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Login.cshtml.cs(25,37): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Login.cshtml.cs(25,97): error CS0246: The type or namespace name '</span>IBasketService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(56,86): error CS0246: The type or namespace name '</span>BasketItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(80,44): error CS0246: The type or namespace name '</span>Basket<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Interfaces/IBasketViewModelService.cs(10,31): error CS0246: The type or namespace name '</span>Basket<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Logout.cshtml.cs(20,36): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(15,22): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(15,34): error CS0246: The type or namespace name '</span>Basket<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Logout.cshtml.cs(24,38): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Logout.cshtml.cs(35,64): warning CS8625: Cannot convert null literal to non-nullable reference type. [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(16,22): error CS0246: The type or namespace name '</span>IUriComposer<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Admin/Index.cshtml(2,7): error CS0246: The type or namespace name '</span>BlazorAdmin<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Admin/EditCatalogItem.cshtml.cs(11,20): error CS0103: The name '</span>BlazorShared<span class="hljs-string">' does not exist in the current context [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(17,22): error CS0246: The type or namespace name '</span>IBasketQueryService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Admin/Index.cshtml.cs(12,20): error CS0103: The name '</span>BlazorShared<span class="hljs-string">' does not exist in the current context [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(19,36): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(18,22): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(18,34): error CS0246: The type or namespace name '</span>CatalogItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(20,34): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(25,21): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(26,23): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(60,42): warning CS8625: Cannot convert null literal to non-nullable reference type. [/workspace/Web.csproj]
        /workspace/Areas/Identity/Pages/Account/Register.cshtml.cs(65,69): warning CS8625: Cannot convert null literal to non-nullable reference type. [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(20,35): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(20,47): error CS0246: The type or namespace name '</span>Basket<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(21,9): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(21,21): error CS0246: The type or namespace name '</span>CatalogItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(22,9): error CS0246: The type or namespace name '</span>IUriComposer<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/BasketViewModelService.cs(23,9): error CS0246: The type or namespace name '</span>IBasketQueryService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(22,22): error CS0246: The type or namespace name '</span>IBasketService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(23,36): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(24,22): error CS0246: The type or namespace name '</span>IOrderService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogItemViewModelService.cs(11,22): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogItemViewModelService.cs(11,34): error CS0246: The type or namespace name '</span>CatalogItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(27,22): error CS0246: The type or namespace name '</span>IAppLogger&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogItemViewModelService.cs(13,40): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogItemViewModelService.cs(13,52): error CS0246: The type or namespace name '</span>CatalogItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(21,22): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(21,34): error CS0246: The type or namespace name '</span>CatalogItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(29,26): error CS0246: The type or namespace name '</span>IBasketService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(31,23): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(32,9): error CS0246: The type or namespace name '</span>IOrderService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Checkout.cshtml.cs(33,9): error CS0246: The type or namespace name '</span>IAppLogger&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(22,22): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(22,34): error CS0246: The type or namespace name '</span>CatalogBrand<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Index.cshtml.cs(16,22): error CS0246: The type or namespace name '</span>IBasketService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Basket/Index.cshtml.cs(19,23): error CS0246: The type or namespace name '</span>IBasketService<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(23,22): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(23,34): error CS0246: The type or namespace name '</span>CatalogType<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(24,22): error CS0246: The type or namespace name '</span>IUriComposer<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(28,9): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(28,21): error CS0246: The type or namespace name '</span>CatalogItem<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(29,9): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(29,21): error CS0246: The type or namespace name '</span>CatalogBrand<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(30,9): error CS0246: The type or namespace name '</span>IRepository&lt;&gt;<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(30,21): error CS0246: The type or namespace name '</span>CatalogType<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Services/CatalogViewModelService.cs(31,9): error CS0246: The type or namespace name '</span>IUriComposer<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Shared/Components/BasketComponent/Basket.cs(14,36): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Pages/Shared/Components/BasketComponent/Basket.cs(17,35): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Microsoft.NET.Sdk.Razor.SourceGenerators/Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator/Views_Manage__ManageNav_cshtml.g.cs(236,30): error CS0246: The type or namespace name '</span>ApplicationUser<span class="hljs-string">' could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/Views/_ViewImports.cshtml(7,26): error CS0234: The type or namespace name '</span>Infrastructure<span class="hljs-string">' does not exist in the namespace '</span>Microsoft.eShopWeb<span class="hljs-string">' (are you missing an assembly reference?) [/workspace/Web.csproj]
        /workspace/SlugifyParameterTransformer.cs(8,19): warning CS8767: Nullability of reference types in type of parameter '</span>value<span class="hljs-string">' of '</span>string SlugifyParameterTransformer.TransformOutbound(object value)<span class="hljs-string">' doesn'</span>t match implicitly implemented member <span class="hljs-string">'string? IOutboundParameterTransformer.TransformOutbound(object? value)'</span> (possibly because of nullability attributes). [/workspace/Web.csproj]
        /workspace/ViewModels/OrderViewModel.cs(15,12): error CS0246: The <span class="hljs-built_in">type</span> or namespace name <span class="hljs-string">'Address'</span> could not be found (are you missing a using directive or an assembly reference?) [/workspace/Web.csproj]
ERROR: failed to build: <span class="hljs-built_in">exit</span> status 1
</code></pre>
<p>It seems that we are building our project in the wrong context. .NET usually has multiple modules and a shared project root. We can tell Build Service to use the <code>src</code> folder as the project root (since it has our <code>.cs_proj</code> file), and also point to a specific .NET project path that we'd like to build:</p>
<pre><code class="lang-bash">&gt; kp image save dotnet-eshopweb \
--tag harbor.registry.com/apps/dotnet-eshopweb \
--git https://github.com/dotnet-architecture/eShopOnWeb.git \
--git-revision main \
--sub-path=./src \
--env BP_DOTNET_PROJECT_PATH=<span class="hljs-string">"./Web"</span>
Patching Image Resource...
Image Resource <span class="hljs-string">"dotnet-eshopweb"</span> patched
</code></pre>
<p>Let's check out the updated logs:</p>
<pre><code class="lang-bash">&gt; kp build logs dotnet-eshopweb
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  + env:
  + - name: BP_DOTNET_PROJECT_PATH
  +   value: ./Web
  resources: {}
  <span class="hljs-built_in">source</span>:
    git:
      revision: 896fae150fbcec5de8a1fb923eab5dfafe09422a
      url: https://github.com/dotnet-architecture/eShopOnWeb.git
  -   subPath: ./src/Web
  +   subPath: ./src
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
Previous image with name <span class="hljs-string">"harbor.registry.com/apps/dotnet-eshopweb"</span> not found
===&gt; DETECT
7 of 13 buildpacks participating
paketo-buildpacks/ca-certificates    3.0.2
tanzu-buildpacks/dotnet-core-runtime 0.3.1
tanzu-buildpacks/dotnet-core-aspnet  0.3.2
tanzu-buildpacks/dotnet-core-sdk     0.3.1
tanzu-buildpacks/icu                 0.0.13
tanzu-buildpacks/dotnet-publish      0.5.1
tanzu-buildpacks/dotnet-execute      0.4.1
===&gt; RESTORE
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Tanzu .NET Core Runtime Buildpack 0.3.1
  Resolving Dotnet Core Runtime version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      Web.csproj -&gt; <span class="hljs-string">"6.0.0"</span>
      &lt;unknown&gt;  -&gt; <span class="hljs-string">""</span>

    Selected dotnet-runtime version (using Web.csproj): 6.0.0

  Executing build process
    Installing Dotnet Core Runtime 6.0.0
      Completed <span class="hljs-keyword">in</span> 4.383s

  Configuring environment <span class="hljs-keyword">for</span> build and launch
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>

  Configuring environment <span class="hljs-keyword">for</span> build
    RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.0"</span>

Tanzu ASP.NET Core Buildpack 0.3.2
  Resolving Dotnet Core ASPNet version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.0"</span>
      Web.csproj      -&gt; <span class="hljs-string">"6.0.0"</span>
      &lt;unknown&gt;       -&gt; <span class="hljs-string">""</span>

    Selected dotnet-aspnetcore version (using RUNTIME_VERSION): 6.0.0

  Executing build process
    Installing Dotnet Core ASPNet 6.0.0
      Completed <span class="hljs-keyword">in</span> 1.253s

  Configuring environment
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>

Tanzu .NET Core SDK Buildpack 0.3.1
  Resolving .NET Core SDK version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.100"</span>
      &lt;unknown&gt;       -&gt; <span class="hljs-string">"*"</span>
      Web.csproj      -&gt; <span class="hljs-string">"6.0.*"</span>

    Selected .NET Core SDK version (using RUNTIME_VERSION): 6.0.100

  Executing build process
    Installing .NET Core SDK 6.0.100
      Completed <span class="hljs-keyword">in</span> 16.334s

  Configuring environment
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>
    PATH        -&gt; <span class="hljs-string">"/workspace/.dotnet_root:<span class="hljs-variable">$PATH</span>"</span>

Tanzu ICU Buildpack 0.0.13
  Executing build process
    Installing ICU
      Completed <span class="hljs-keyword">in</span> 485ms

Tanzu .Net Publish Buildpack 0.5.1
  Executing build process
    Running <span class="hljs-string">'dotnet publish /workspace/Web --configuration Release --runtime ubuntu.18.04-x64 --self-contained false --output /tmp/dotnet-publish-output4061578451'</span>
failed to execute <span class="hljs-string">'dotnet publish'</span>: <span class="hljs-built_in">exit</span> status 1
      Failed after 37.399735604s

        Welcome to .NET 6.0!
        ---------------------
        SDK Version: 6.0.100

        Telemetry
        ---------
        The .NET tools collect usage data <span class="hljs-keyword">in</span> order to <span class="hljs-built_in">help</span> us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to <span class="hljs-string">'1'</span> or <span class="hljs-string">'true'</span> using your favorite shell.

        Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

        ----------------
        Installed an ASP.NET Core HTTPS development certificate.
        To trust the certificate run <span class="hljs-string">'dotnet dev-certs https --trust'</span> (Windows and macOS only).
        Learn about HTTPS: https://aka.ms/dotnet-https
        ----------------
        Write your first app: https://aka.ms/dotnet-hello-world
        Find out what<span class="hljs-string">'s new: https://aka.ms/dotnet-whats-new
        Explore documentation: https://aka.ms/dotnet-docs
        Report issues and find source on GitHub: https://github.com/dotnet/core
        Use '</span>dotnet --<span class="hljs-built_in">help</span><span class="hljs-string">' to see available commands or visit: https://aka.ms/dotnet-cli
        --------------------------------------------------------------------------------------
        Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
        Copyright (C) Microsoft Corporation. All rights reserved.

          Determining projects to restore...
          Restored /workspace/BlazorShared/BlazorShared.csproj (in 2.82 sec).
          Restored /workspace/BlazorAdmin/BlazorAdmin.csproj (in 4.12 sec).
          Restored /workspace/ApplicationCore/ApplicationCore.csproj (in 7.8 sec).
          Restored /workspace/Infrastructure/Infrastructure.csproj (in 4.28 sec).
          Restored /workspace/Web/Web.csproj (in 17.21 sec).

          Restore operation started...

          6 libraries restored in 0.96 seconds

          BlazorShared -&gt; /workspace/BlazorShared/bin/Release/net6.0/BlazorShared.dll
          ApplicationCore -&gt; /workspace/ApplicationCore/bin/Release/net6.0/ApplicationCore.dll
          Infrastructure -&gt; /workspace/Infrastructure/bin/Release/net6.0/Infrastructure.dll
          BlazorAdmin -&gt; /workspace/BlazorAdmin/bin/Release/net6.0/BlazorAdmin.dll
          BlazorAdmin (Blazor output) -&gt; /workspace/BlazorAdmin/bin/Release/net6.0/wwwroot

          Bundler: Begin processing bundleconfig.json
          Bundler: Done processing bundleconfig.json
        /workspace/Web/SlugifyParameterTransformer.cs(8,19): warning CS8767: Nullability of reference types in type of parameter '</span>value<span class="hljs-string">' of '</span>string SlugifyParameterTransformer.TransformOutbound(object value)<span class="hljs-string">' doesn'</span>t match implicitly implemented member <span class="hljs-string">'string? IOutboundParameterTransformer.TransformOutbound(object? value)'</span> (possibly because of nullability attributes). [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Logout.cshtml.cs(35,64): warning CS8625: Cannot convert null literal to non-nullable reference <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(60,42): warning CS8625: Cannot convert null literal to non-nullable reference <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(65,69): warning CS8625: Cannot convert null literal to non-nullable reference <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/ManageController.cs(31,12): warning CS8618: Non-nullable property <span class="hljs-string">'StatusMessage'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Extensions/UrlHelperExtensions.cs(7,16): warning CS8603: Possible null reference <span class="hljs-built_in">return</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/UserController.cs(27,12): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/HealthChecks/HomePageHealthCheck.cs(22,23): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Manage/ManageNavPages.cs(30,16): warning CS8603: Possible null reference <span class="hljs-built_in">return</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Services/CatalogItemViewModelService.cs(21,9): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Configuration/RevokeAuthenticationEvents.cs(25,22): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Features/OrderDetails/GetOrderDetailsHandler.cs(29,20): warning CS8603: Possible null reference <span class="hljs-built_in">return</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Features/MyOrders/GetMyOrdersHandler.cs(31,26): warning CS8601: Possible null reference assignment. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/UserController.cs(31,14): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/ManageController.cs(383,29): warning CS8600: Converting null literal or possible null value to non-nullable <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/PaginationInfoViewModel.cs(9,19): warning CS8618: Non-nullable property <span class="hljs-string">'Previous'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/PaginationInfoViewModel.cs(10,19): warning CS8618: Non-nullable property <span class="hljs-string">'Next'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/OrderViewModel.cs(15,20): warning CS8618: Non-nullable property <span class="hljs-string">'ShippingAddress'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/OrderItemViewModel.cs(6,19): warning CS8618: Non-nullable property <span class="hljs-string">'ProductName'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/OrderItemViewModel.cs(10,19): warning CS8618: Non-nullable property <span class="hljs-string">'PictureUrl'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ShowRecoveryCodesViewModel.cs(5,21): warning CS8618: Non-nullable property <span class="hljs-string">'RecoveryCodes'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/SetPasswordViewModel.cs(11,19): warning CS8618: Non-nullable property <span class="hljs-string">'NewPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/SetPasswordViewModel.cs(16,19): warning CS8618: Non-nullable property <span class="hljs-string">'ConfirmPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/SetPasswordViewModel.cs(18,19): warning CS8618: Non-nullable property <span class="hljs-string">'StatusMessage'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/RemoveLoginViewModel.cs(5,19): warning CS8618: Non-nullable property <span class="hljs-string">'LoginProvider'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/RemoveLoginViewModel.cs(6,19): warning CS8618: Non-nullable property <span class="hljs-string">'ProviderKey'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/IndexViewModel.cs(7,19): warning CS8618: Non-nullable property <span class="hljs-string">'Username'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/IndexViewModel.cs(13,19): warning CS8618: Non-nullable property <span class="hljs-string">'Email'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/IndexViewModel.cs(17,19): warning CS8618: Non-nullable property <span class="hljs-string">'PhoneNumber'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/IndexViewModel.cs(19,19): warning CS8618: Non-nullable property <span class="hljs-string">'StatusMessage'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/SlugifyParameterTransformer.cs(10,37): warning CS8603: Possible null reference <span class="hljs-built_in">return</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/SlugifyParameterTransformer.cs(13,30): warning CS8604: Possible null reference argument <span class="hljs-keyword">for</span> parameter <span class="hljs-string">'input'</span> <span class="hljs-keyword">in</span> <span class="hljs-string">'string Regex.Replace(string input, string pattern, string replacement)'</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ExternalLoginsViewModel.cs(9,33): warning CS8618: Non-nullable property <span class="hljs-string">'CurrentLogins'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ExternalLoginsViewModel.cs(10,40): warning CS8618: Non-nullable property <span class="hljs-string">'OtherLogins'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ExternalLoginsViewModel.cs(12,19): warning CS8618: Non-nullable property <span class="hljs-string">'StatusMessage'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/EnableAuthenticatorViewModel.cs(13,19): warning CS8618: Non-nullable property <span class="hljs-string">'Code'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/EnableAuthenticatorViewModel.cs(16,19): warning CS8618: Non-nullable property <span class="hljs-string">'SharedKey'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/EnableAuthenticatorViewModel.cs(19,19): warning CS8618: Non-nullable property <span class="hljs-string">'AuthenticatorUri'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ChangePasswordViewModel.cs(10,19): warning CS8618: Non-nullable property <span class="hljs-string">'OldPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ChangePasswordViewModel.cs(16,19): warning CS8618: Non-nullable property <span class="hljs-string">'NewPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ChangePasswordViewModel.cs(21,19): warning CS8618: Non-nullable property <span class="hljs-string">'ConfirmPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Manage/ChangePasswordViewModel.cs(23,19): warning CS8618: Non-nullable property <span class="hljs-string">'StatusMessage'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/File/FileViewModel.cs(5,19): warning CS8618: Non-nullable property <span class="hljs-string">'FileName'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/File/FileViewModel.cs(6,19): warning CS8618: Non-nullable property <span class="hljs-string">'Url'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/File/FileViewModel.cs(7,19): warning CS8618: Non-nullable property <span class="hljs-string">'DataBase64'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/CatalogItemViewModel.cs(6,19): warning CS8618: Non-nullable property <span class="hljs-string">'Name'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/CatalogItemViewModel.cs(7,19): warning CS8618: Non-nullable property <span class="hljs-string">'PictureUri'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/CatalogIndexViewModel.cs(8,39): warning CS8618: Non-nullable property <span class="hljs-string">'CatalogItems'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/CatalogIndexViewModel.cs(9,33): warning CS8618: Non-nullable property <span class="hljs-string">'Brands'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/CatalogIndexViewModel.cs(10,33): warning CS8618: Non-nullable property <span class="hljs-string">'Types'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/CatalogIndexViewModel.cs(13,36): warning CS8618: Non-nullable property <span class="hljs-string">'PaginationInfo'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/ResetPasswordViewModel.cs(9,19): warning CS8618: Non-nullable property <span class="hljs-string">'Email'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/ResetPasswordViewModel.cs(14,19): warning CS8618: Non-nullable property <span class="hljs-string">'Password'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/ResetPasswordViewModel.cs(19,19): warning CS8618: Non-nullable property <span class="hljs-string">'ConfirmPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/ResetPasswordViewModel.cs(21,19): warning CS8618: Non-nullable property <span class="hljs-string">'Code'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/RegisterViewModel.cs(10,19): warning CS8618: Non-nullable property <span class="hljs-string">'Email'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/RegisterViewModel.cs(16,19): warning CS8618: Non-nullable property <span class="hljs-string">'Password'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/RegisterViewModel.cs(21,19): warning CS8618: Non-nullable property <span class="hljs-string">'ConfirmPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/LoginWith2faViewModel.cs(11,19): warning CS8618: Non-nullable property <span class="hljs-string">'TwoFactorCode'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/OrderController.cs(25,62): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/OrderController.cs(25,62): warning CS8604: Possible null reference argument <span class="hljs-keyword">for</span> parameter <span class="hljs-string">'userName'</span> <span class="hljs-keyword">in</span> <span class="hljs-string">'GetMyOrders.GetMyOrders(string userName)'</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/OrderController.cs(33,66): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Controllers/OrderController.cs(33,66): warning CS8604: Possible null reference argument <span class="hljs-keyword">for</span> parameter <span class="hljs-string">'userName'</span> <span class="hljs-keyword">in</span> <span class="hljs-string">'GetOrderDetails.GetOrderDetails(string userName, int orderId)'</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/LoginViewModel.cs(9,19): warning CS8618: Non-nullable property <span class="hljs-string">'Email'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/ViewModels/Account/LoginViewModel.cs(13,19): warning CS8618: Non-nullable property <span class="hljs-string">'Password'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Order/Detail.cshtml(20,68): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Login.cshtml.cs(25,12): warning CS8618: Non-nullable property <span class="hljs-string">'Input'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Login.cshtml.cs(25,12): warning CS8618: Non-nullable property <span class="hljs-string">'ExternalLogins'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Login.cshtml.cs(25,12): warning CS8618: Non-nullable property <span class="hljs-string">'ReturnUrl'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Login.cshtml.cs(25,12): warning CS8618: Non-nullable property <span class="hljs-string">'ErrorMessage'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Manage/TwoFactorAuthentication.cshtml(8,6): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Login.cshtml.cs(46,23): warning CS8618: Non-nullable property <span class="hljs-string">'Email'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Login.cshtml.cs(50,23): warning CS8618: Non-nullable property <span class="hljs-string">'Password'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(24,12): warning CS8618: Non-nullable property <span class="hljs-string">'Input'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(24,12): warning CS8618: Non-nullable property <span class="hljs-string">'ReturnUrl'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/BasketItemViewModel.cs(9,19): warning CS8618: Non-nullable property <span class="hljs-string">'ProductName'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/BasketItemViewModel.cs(16,19): warning CS8618: Non-nullable property <span class="hljs-string">'PictureUrl'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Checkout.cshtml.cs(25,32): warning CS8625: Cannot convert null literal to non-nullable reference <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(46,23): warning CS8618: Non-nullable property <span class="hljs-string">'Email'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(52,23): warning CS8618: Non-nullable property <span class="hljs-string">'Password'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(57,23): warning CS8618: Non-nullable property <span class="hljs-string">'ConfirmPassword'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Error.cshtml.cs(10,19): warning CS8618: Non-nullable property <span class="hljs-string">'RequestId'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/BasketViewModel.cs(11,19): warning CS8618: Non-nullable property <span class="hljs-string">'BuyerId'</span> must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Checkout.cshtml.cs(79,82): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Checkout.cshtml.cs(79,82): warning CS8604: Possible null reference argument <span class="hljs-keyword">for</span> parameter <span class="hljs-string">'userName'</span> <span class="hljs-keyword">in</span> <span class="hljs-string">'Task&lt;BasketViewModel&gt; IBasketViewModelService.GetOrCreateBasketForUser(string userName)'</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Shared/Components/Basket/Default.cshtml(12,15): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Areas/Identity/Pages/Account/Register.cshtml.cs(84,91): warning CS8604: Possible null reference argument <span class="hljs-keyword">for</span> parameter <span class="hljs-string">'value'</span> <span class="hljs-keyword">in</span> <span class="hljs-string">'string TextEncoder.Encode(string value)'</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Manage/ShowRecoverCodes.cshtml(19,34): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Checkout.cshtml.cs(92,25): warning CS8601: Possible null reference assignment. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Index.cshtml.cs(64,27): warning CS8600: Converting null literal or possible null value to non-nullable <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Index.cshtml.cs(66,13): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Index.cshtml.cs(68,20): warning CS8603: Possible null reference <span class="hljs-built_in">return</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Index.cshtml.cs(73,24): warning CS8600: Converting null literal or possible null value to non-nullable <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Basket/Index.cshtml.cs(79,32): warning CS8600: Converting null literal or possible null value to non-nullable <span class="hljs-built_in">type</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/Components/BasketComponent/Default.cshtml(11,11): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/_editCatalog.cshtml(5,50): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/Components/BasketComponent/Basket.cs(36,63): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/Components/BasketComponent/Basket.cs(36,63): warning CS8604: Possible null reference argument <span class="hljs-keyword">for</span> parameter <span class="hljs-string">'username'</span> <span class="hljs-keyword">in</span> <span class="hljs-string">'Task&lt;int&gt; IBasketViewModelService.CountTotalBasketItems(string username)'</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/Components/BasketComponent/Basket.cs(57,16): warning CS8603: Possible null reference <span class="hljs-built_in">return</span>. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Shared/_LoginPartial.cshtml(1,6): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Manage/EnableAuthenticator.cshtml(23,58): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Manage/ExternalLogins.cshtml(7,6): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/_pagination.cshtml(6,34): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Pages/Shared/_product.cshtml(4,46): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
        /workspace/Web/Views/Manage/MyAccount.cshtml(19,22): warning CS8602: Dereference of a possibly null reference. [/workspace/Web/Web.csproj]
          Web -&gt; /workspace/Web/bin/Release/net6.0/ubuntu.18.04-x64/Web.dll
        /layers/tanzu-buildpacks_dotnet-core-sdk/dotnet-core-sdk/sdk/6.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ILLink.targets(213,5): error NETSDK1102: Optimizing assemblies <span class="hljs-keyword">for</span> size is not supported <span class="hljs-keyword">for</span> the selected publish configuration. Please ensure that you are publishing a self-contained app. [/workspace/BlazorAdmin/BlazorAdmin.csproj]
ERROR: failed to build: <span class="hljs-built_in">exit</span> status 1
</code></pre>
<p>Our build is still failing, but at least the <code>dotnet publish</code> command gives us a very clear hint as to what is going on. In this particular application, we need to set our application to be  <a target="_blank" href="https://andrewlock.net/should-i-use-self-contained-or-framework-dependent-publishing-in-docker-images/">self-contained</a>. Let's set the <a target="_blank" href="https://paketo.io/docs/howto/dotnet-core/#using-bp_dotnet_publish_flags">.NET build flags</a> to the desired setting. While we're at it, we'll change the verbosity level to normal, so that we'll get more information during the build:</p>
<pre><code class="lang-bash">&gt; kp image save dotnet-eshopweb \
--tag harbor.registry.com/apps/dotnet-eshopweb \
--git https://github.com/dotnet-architecture/eShopOnWeb.git \
--git-revision main \
--sub-path=./src \
--env BP_DOTNET_PROJECT_PATH=<span class="hljs-string">"./Web"</span> \
--env BP_DOTNET_PUBLISH_FLAGS=<span class="hljs-string">"--verbosity=normal --self-contained=true"</span>
</code></pre>
<p>Let's check the updated logs:</p>
<pre><code class="lang-bash">&gt; kp build logs dotnet-eshopweb
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  env:
  - name: BP_DOTNET_PROJECT_PATH
    value: ./Web
  + - name: BP_DOTNET_PUBLISH_FLAGS
  +   value: --verbosity=normal --self-contained=<span class="hljs-literal">true</span>
  resources: {}
  <span class="hljs-built_in">source</span>:
    git:
      revision: 896fae150fbcec5de8a1fb923eab5dfafe09422a
      url: https://github.com/dotnet-architecture/eShopOnWeb.git
    subPath: ./src
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/dotnet-architecture/eShopOnWeb.git"</span> @ <span class="hljs-string">"896fae150fbcec5de8a1fb923eab5dfafe09422a"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
Previous image with name <span class="hljs-string">"harbor.registry.com/apps/dotnet-eshopweb"</span> not found
===&gt; DETECT
7 of 13 buildpacks participating
paketo-buildpacks/ca-certificates    3.0.2
tanzu-buildpacks/dotnet-core-runtime 0.3.1
tanzu-buildpacks/dotnet-core-aspnet  0.3.2
tanzu-buildpacks/dotnet-core-sdk     0.3.1
tanzu-buildpacks/icu                 0.0.13
tanzu-buildpacks/dotnet-publish      0.5.1
tanzu-buildpacks/dotnet-execute      0.4.1
===&gt; RESTORE
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Tanzu .NET Core Runtime Buildpack 0.3.1
  Resolving Dotnet Core Runtime version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      Web.csproj -&gt; <span class="hljs-string">"6.0.0"</span>
      &lt;unknown&gt;  -&gt; <span class="hljs-string">""</span>

    Selected dotnet-runtime version (using Web.csproj): 6.0.0

  Executing build process
    Installing Dotnet Core Runtime 6.0.0
      Completed <span class="hljs-keyword">in</span> 4.814s

  Configuring environment <span class="hljs-keyword">for</span> build and launch
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>

  Configuring environment <span class="hljs-keyword">for</span> build
    RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.0"</span>

Tanzu ASP.NET Core Buildpack 0.3.2
  Resolving Dotnet Core ASPNet version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.0"</span>
      Web.csproj      -&gt; <span class="hljs-string">"6.0.0"</span>
      &lt;unknown&gt;       -&gt; <span class="hljs-string">""</span>

    Selected dotnet-aspnetcore version (using RUNTIME_VERSION): 6.0.0

  Executing build process
    Installing Dotnet Core ASPNet 6.0.0
      Completed <span class="hljs-keyword">in</span> 1.262s

  Configuring environment
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>

Tanzu .NET Core SDK Buildpack 0.3.1
  Resolving .NET Core SDK version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      RUNTIME_VERSION -&gt; <span class="hljs-string">"6.0.100"</span>
      &lt;unknown&gt;       -&gt; <span class="hljs-string">"*"</span>
      Web.csproj      -&gt; <span class="hljs-string">"6.0.*"</span>

    Selected .NET Core SDK version (using RUNTIME_VERSION): 6.0.100

  Executing build process
    Installing .NET Core SDK 6.0.100
      Completed <span class="hljs-keyword">in</span> 16.227s

  Configuring environment
    DOTNET_ROOT -&gt; <span class="hljs-string">"/workspace/.dotnet_root"</span>
    PATH        -&gt; <span class="hljs-string">"/workspace/.dotnet_root:<span class="hljs-variable">$PATH</span>"</span>

Tanzu ICU Buildpack 0.0.13
  Executing build process
    Installing ICU
      Completed <span class="hljs-keyword">in</span> 471ms

Tanzu .Net Publish Buildpack 0.5.1
  Executing build process
    Running <span class="hljs-string">'dotnet publish /workspace/Web --configuration Release --runtime ubuntu.18.04-x64 --output /tmp/dotnet-publish-output1039265788 --verbosity=normal --self-contained=true'</span>
      Completed <span class="hljs-keyword">in</span> 1m3.68120334s

  Removing <span class="hljs-built_in">source</span> code

Tanzu .NET Execute Buildpack 0.4.1
  Assigning launch processes
    web: /workspace/Web --urls http://0.0.0.0:<span class="hljs-variable">${PORT:-8080}</span>

===&gt; EXPORT
Adding layer <span class="hljs-string">'paketo-buildpacks/ca-certificates:helper'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/dotnet-core-runtime:dotnet-core-runtime'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/dotnet-core-aspnet:dotnet-core-aspnet'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/dotnet-core-sdk:dotnet-env-var'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/icu:icu'</span>
Adding layer <span class="hljs-string">'launch.sbom'</span>
Adding 1/1 app layer(s)
Adding layer <span class="hljs-string">'launcher'</span>
Adding layer <span class="hljs-string">'config'</span>
Adding layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/apps/dotnet-eshopweb...
*** Images (sha256:48ac5c34be35b15589edb25a186978b19c70c397c08f618795799b3d65a454a0):
      harbor.registry.com/apps/dotnet-eshopweb
      harbor.registry.com/apps/dotnet-eshopweb:b3.20220116.122508
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/dotnet-core-runtime:dotnet-core-runtime'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/dotnet-core-aspnet:dotnet-core-aspnet'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/dotnet-core-sdk:dotnet-core-sdk'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/icu:icu'</span>
===&gt; COMPLETION
Build successful
</code></pre>
<p>This looks A LOT better.</p>
<h4 id="heading-running-the-image">Running the image</h4>
<p>Let's see if the image runs as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642337498061/AK1a77Q5Q.png" alt="image.png" /></p>
<p>We still see some errors, but if we'll enable the development mode as hinted on screen, we'll see that the error is as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1644316381565/l3tzmPrSv.png" alt="image.png" /></p>
<p>After some googling, we can see that SQL Server Express is <a target="_blank" href="https://stackoverflow.com/questions/45860851/localdb-is-not-supported-on-this-platform">only supported on Windows</a>. This will require us to make some architectural code changes in our app to adapt to a linux world, but that's standard development effort and out of scope for Cloud Native Buildpacks themselves.</p>
<h3 id="heading-additional-tips-and-tricks">Additional tips and tricks</h3>
<ul>
<li><p>If your application fails on error NU1605, make sure you update your project file with the following XML entry to force it to use the correct runtime for your desired runtime:</p>
<pre><code class="lang-bash">&lt;PackageReference Include=<span class="hljs-string">"Microsoft.NETCore.Targets"</span> Version=<span class="hljs-string">"5.0.0"</span> PrivateAssets=<span class="hljs-string">"all"</span> /&gt;
</code></pre>
</li>
<li><p>If you're working at an airgapped environment, you may need to clear the default nuget repositories to force the build to use your private registry only. In your <code>nuget.config</code>:</p>
<pre><code><span class="hljs-operator">&lt;</span>configuration<span class="hljs-operator">&gt;</span>
<span class="hljs-operator">&lt;</span>packageSources<span class="hljs-operator">&gt;</span>
      <span class="hljs-operator">&lt;</span>clear <span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span> <span class="hljs-operator">&lt;</span><span class="hljs-operator">!</span><span class="hljs-operator">-</span><span class="hljs-operator">-</span> ensure only the sources defined below are used <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>
      <span class="hljs-operator">&lt;</span>add key<span class="hljs-operator">=</span><span class="hljs-string">"MyPrivateRepo - ES"</span> value<span class="hljs-operator">=</span><span class="hljs-string">"https://MyPrivateRepo/ES/nuget"</span> <span class="hljs-operator">/</span><span class="hljs-operator">&gt;</span>
  <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>packageSources<span class="hljs-operator">&gt;</span>
<span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>configuration<span class="hljs-operator">&gt;</span>
</code></pre></li>
<li>If you need to set environment variables for the application runtime, add the env varible to the <code>kp image save</code> command, prefixed with <code>BPE_</code>. For example - <code>--env BPE_MY_RUNTIME_VAR=value</code>. Any env variable that starts with <code>BPE</code> will automatically add the <a target="_blank" href="https://github.com/paketo-buildpacks/environment-variables">environment variables</a> buildpack to the image.</li>
</ul>
<h2 id="heading-frontend-vuejs-application">Frontend VueJS Application</h2>
<p>We'll begin with a UI application that is using the popular VueJS library. This application presents a frontend for a Todo application. We won't be focusing on the backend so we'll see some "error loading details" in the UI, but the idea is just to see if the image works as expected.</p>
<h4 id="heading-git-repo">Git repo</h4>
<p>The git repo for this application is available at https://github.com/odedia/todo-ui.git</p>
<h4 id="heading-build-command">Build command</h4>
<p>Let's try to build the code:</p>
<pre><code class="lang-bash">&gt; kp image save todo-vuejs \
--tag harbor.registry.com/tap/todo-vuejs \
--git https://github.com/odedia/todo-ui \
--git-revision main
Creating Image Resource...
Image Resource <span class="hljs-string">"todo-vuejs"</span> created
</code></pre>
<h4 id="heading-build-logs-output">Build logs output</h4>
<p>Here's the output from the build logs:</p>
<pre><code class="lang-bash">&gt; kp build logs todo-vuejs
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  - <span class="hljs-built_in">source</span>: {}
  + <span class="hljs-built_in">source</span>:
  +   git:
  +     revision: 0236707eb0481fce9d3d292f0a1787a7ced5e706
  +     url: https://github.com/odedia/todo-ui
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/odedia/todo-ui"</span> @ <span class="hljs-string">"0236707eb0481fce9d3d292f0a1787a7ced5e706"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/odedia/todo-ui"</span> @ <span class="hljs-string">"0236707eb0481fce9d3d292f0a1787a7ced5e706"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
Previous image with name <span class="hljs-string">"harbor.registry.com/tap/todo-vuejs"</span> not found
===&gt; DETECT
5 of 17 buildpacks participating
paketo-buildpacks/ca-certificates 3.0.2
tanzu-buildpacks/node-engine      0.8.3
tanzu-buildpacks/npm-install      0.5.3
tanzu-buildpacks/node-module-bom  0.1.4
tanzu-buildpacks/npm-start        0.2.3
===&gt; RESTORE
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Tanzu Node Engine Buildpack 0.8.3
  Resolving Node Engine version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      package.json -&gt; <span class="hljs-string">"&gt;=10.12.0"</span>
      .nvmrc       -&gt; <span class="hljs-string">"11.6.0"</span>
      &lt;unknown&gt;    -&gt; <span class="hljs-string">""</span>

    Selected Node Engine version (using package.json): 16.13.1

  Executing build process
    Installing Node Engine 16.13.1
      Completed <span class="hljs-keyword">in</span> 1.043s

  Configuring build environment
    NODE_ENV     -&gt; <span class="hljs-string">"production"</span>
    NODE_HOME    -&gt; <span class="hljs-string">"/layers/tanzu-buildpacks_node-engine/node"</span>
    NODE_VERBOSE -&gt; <span class="hljs-string">"false"</span>

  Configuring launch environment
    NODE_ENV     -&gt; <span class="hljs-string">"production"</span>
    NODE_HOME    -&gt; <span class="hljs-string">"/layers/tanzu-buildpacks_node-engine/node"</span>
    NODE_VERBOSE -&gt; <span class="hljs-string">"false"</span>

    Writing profile.d/0_memory_available.sh
      Calculates available memory based on container limits at launch time.
      Made available <span class="hljs-keyword">in</span> the MEMORY_AVAILABLE environment variable.

Tanzu NPM Install Buildpack 0.5.3
  Resolving installation process
    Process inputs:
      node_modules      -&gt; <span class="hljs-string">"Not found"</span>
      npm-cache         -&gt; <span class="hljs-string">"Not found"</span>
      package-lock.json -&gt; <span class="hljs-string">"Found"</span>

    Selected NPM build process: <span class="hljs-string">'npm ci'</span>

  Executing build process
    Running <span class="hljs-string">'npm ci --unsafe-perm --cache /layers/tanzu-buildpacks_npm-install/npm-cache'</span>
      Completed <span class="hljs-keyword">in</span> 7.182s

  Configuring launch environment
    NPM_CONFIG_LOGLEVEL -&gt; <span class="hljs-string">"error"</span>

  Configuring environment shared by build and launch
    PATH -&gt; <span class="hljs-string">"<span class="hljs-variable">$PATH</span>:/layers/tanzu-buildpacks_npm-install/modules/node_modules/.bin"</span>


Tanzu Node Module Bill of Materials Generator Buildpack 0.1.4
  Resolving CycloneDX Node.js Module version
    Selected CycloneDX Node.js Module version: 3.0.7

  Executing build process
    Installing CycloneDX Node.js Module 3.0.7
      Completed <span class="hljs-keyword">in</span> 42ms

  Configuring environment
    Appending CycloneDX Node.js Module onto PATH

  Running CycloneDX Node.js Module
    Running <span class="hljs-string">'cyclonedx-bom -o bom.json'</span>
      Completed <span class="hljs-keyword">in</span> 501ms

Tanzu NPM Start Buildpack 0.2.3
  Assigning launch processes
    web: node server.js

===&gt; EXPORT
Adding layer <span class="hljs-string">'paketo-buildpacks/ca-certificates:helper'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/node-engine:node'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/npm-install:modules'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/npm-install:npm-cache'</span>
Adding layer <span class="hljs-string">'launch.sbom'</span>
Adding 1/1 app layer(s)
Adding layer <span class="hljs-string">'launcher'</span>
Adding layer <span class="hljs-string">'config'</span>
Adding layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/tap/todo-vuejs...
*** Images (sha256:493cb51b0cd480eb68228da37332a06112141284ee55266faca2047503b99cab):
      harbor.registry.com/tap/todo-vuejs
      harbor.registry.com/tap/todo-vuejs:b1.20220117.104712
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/node-engine:node'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/npm-install:modules'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/npm-install:npm-cache'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/node-module-bom:cyclonedx-node-module'</span>
===&gt; COMPLETION
Build successful
</code></pre>
<h4 id="heading-running-the-image">Running the image</h4>
<p>Here's the command to run the image:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/tap/todo-vuejs:b1.20220117.104712
Unable to find image <span class="hljs-string">'harbor.registry.com/tap/todo-vuejs:b1.20220117.104712'</span> locally
b1.20220117.104712: Pulling from tap/todo-vuejs
bf99a8b93828: Already exists
1da1131b1360: Already exists
48bde579d8cc: Already exists
1929d0623312: Already exists
1fea93ec33be: Already exists
fed2bcb93d83: Already exists
eb2e57672edf: Already exists
03e598551527: Pull complete
00da406126de: Pull complete
841d6c40c392: Pull complete
b3e12a218902: Pull complete
40d17e1b116e: Pull complete
5baaabcf4fd8: Pull complete
5a44e4f7b58d: Pull complete
Digest: sha256:493cb51b0cd480eb68228da37332a06112141284ee55266faca2047503b99cab
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/tap/todo-vuejs:b1.20220117.104712
cat: /sys/fs/cgroup/memory/memory.limit_in_bytes: No such file or directory
/layers/tanzu-buildpacks_node-engine/node/profile.d/0_memory_available.sh: line 3: / ( 1024 * 1024 ) : syntax error: operand expected (error token is <span class="hljs-string">"/ ( 1024 * 1024 ) "</span>)
server started 5000
</code></pre>
<p>Oops - we immediatly see that the container boots on default node port of 5000. This means we can't access the application since nothing maps to 8080. We could just map intenral port 5000 to external port 8080, but conventions are good. We can change the source code, but it's possible we want to keep port 5000 as the default for local development and debugging. The alternative is to set the PORT env varible. The Node buildpack supports embedding launch-time environment varialbes by utilizing the  <a target="_blank" href="https://github.com/paketo-buildpacks/environment-variables/blob/main/README.md">Environment Variables</a> buildpack. Following the documentation, it appears we need to set a value called BPE_PORT:</p>
<pre><code><span class="hljs-operator">&gt;</span> kp image save todo<span class="hljs-operator">-</span>vuejs \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>tag harbor.registry.com/tap<span class="hljs-operator">/</span>todo<span class="hljs-operator">-</span>vuejs \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>git https:<span class="hljs-comment">//github.com/odedia/todo-ui \</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>git<span class="hljs-operator">-</span>revision main \
<span class="hljs-operator">&gt;</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span>env BPE_PORT<span class="hljs-operator">=</span><span class="hljs-number">8080</span>
Patching Image Resource...
Image Resource <span class="hljs-string">"todo-vuejs"</span> patched
</code></pre><p>We can confirm in the logs that the DETECT phase now contributes the Environment Variables buildpack, which is a good sign:</p>
<pre><code class="lang-bash">===&gt; DETECT
6 of 17 buildpacks participating
paketo-buildpacks/ca-certificates       3.0.2
tanzu-buildpacks/node-engine            0.8.3
tanzu-buildpacks/npm-install            0.5.3
tanzu-buildpacks/node-module-bom        0.1.4
tanzu-buildpacks/npm-start              0.2.3
paketo-buildpacks/environment-variables 4.0.1
</code></pre>
<p>And indeed, while running the image again, we see the application listening on port 8080:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/tap/todo-vuejs:b2.20220117.105600
Unable to find image <span class="hljs-string">'harbor.registry.com/tap/todo-vuejs:b2.20220117.105600'</span> locally
b2.20220117.105600: Pulling from tap/todo-vuejs
bf99a8b93828: Already exists
1da1131b1360: Already exists
48bde579d8cc: Already exists
1929d0623312: Already exists
1fea93ec33be: Already exists
fed2bcb93d83: Already exists
eb2e57672edf: Already exists
03e598551527: Already exists
00da406126de: Already exists
f9724edbdfed: Pull complete
f4702cd992a7: Pull complete
40d17e1b116e: Pull complete
0dc2cd914cc8: Pull complete
5a44e4f7b58d: Pull complete
Digest: sha256:cdd54580ea9554927a80f7c4ccdbd97046624a6001c137f9c0cecc9487300389
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/tap/todo-vuejs:b2.20220117.105600
cat: /sys/fs/cgroup/memory/memory.limit_in_bytes: No such file or directory
/layers/tanzu-buildpacks_node-engine/node/profile.d/0_memory_available.sh: line 3: / ( 1024 * 1024 ) : syntax error: operand expected (error token is <span class="hljs-string">"/ ( 1024 * 1024 ) "</span>)
server started 8080
</code></pre>
<p>Our application is now shown as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642418747861/UjyhR6ANo.png" alt="image.png" /></p>
<h4 id="heading-changing-the-defaults">Changing the defaults</h4>
<p>Does this buildpack really does what we want it to do? Node is a language that can run as a standard backend server, for example to host your APIs, but for frontend applications we really just use it to compile our libraries into standard HTML+Javascript or Typescript. We want Node when doing local development and debugging, but once that part is done - we really just want to take these artifacts and serve them over a web server such as HTTPD or Nginx. Let's see what really happens in our container at the moment:</p>
<pre><code class="lang-bash">&gt; docker <span class="hljs-built_in">exec</span> -it b4db4162a405 /bin/bash
cnb@b4db4162a405:/workspace$ ls -l
total 1308
-rwxr-xr-x 1 cnb cnb     521 Jan  1  1980 Dockerfile
-rwxr-xr-x 1 cnb cnb     364 Jan  1  1980 README.md
-rw-r--r-- 1 cnb cnb     716 Jan  1  1980 Tiltfile
-rwxr-xr-x 1 cnb cnb      53 Jan  1  1980 babel.config.js
-rwxr-xr-x 1 cnb cnb      86 Jan  1  1980 build.sh
-rwxr-xr-x 1 cnb cnb      97 Jan  1  1980 buildAndPush.sh
drwxr-sr-x 2 cnb cnb    4096 Jan  1  1980 ci
drwxr-sr-x 2 cnb cnb    4096 Jan  1  1980 config
drwxr-sr-x 4 cnb cnb    4096 Jan  1  1980 dist
-rwxr-xr-x 1 cnb cnb     310 Jan  1  1980 manifest-docker.yml
-rwxr-xr-x 1 cnb cnb     120 Jan  1  1980 manifest-tas4vms.yml
-rwxr-xr-x 1 cnb cnb     120 Jan  1  1980 manifest.yml
lrwxrwxrwx 1 cnb cnb      57 Jan  1  1980 node_modules -&gt; /layers/tanzu-buildpacks_npm-install/modules/node_modules
-rwxr-xr-x 1 cnb cnb 1257671 Jan  1  1980 package-lock.json
-rwxr-xr-x 1 cnb cnb    2508 Jan  1  1980 package.json
drwxr-sr-x 2 cnb cnb    4096 Jan  1  1980 public
-rwxr-xr-x 1 cnb cnb     271 Jan  1  1980 server.js
drwxr-sr-x 3 cnb cnb    4096 Jan  1  1980 src
-rwxr-xr-x 1 cnb cnb      46 Jan  1  1980 vue.config.js
</code></pre>
<p>We seem to have a <code>dist</code> folder that contains our desired HTML+Javascript/Typescript, but that's not what the container is currently serving. Checking the <code>package.json</code> file, we can see that the available scripts are:</p>
<pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"vue-cli-service serve"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"vue-cli-service build"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"vue-cli-service lint"</span>
  }
</code></pre>
<p>If we'll take a closer look at the build logs from above, the NPM start buildpack contributed the start command as listed in the json:</p>
<pre><code class="lang-bash">Tanzu NPM Start Buildpack 0.2.3
  Assigning launch processes
    web: node server.js
</code></pre>
<p>So, we're at an odd place - the application is running, but it's probably not optimized as we'd like.</p>
<p>What we really want to do is to take the artifacts in the <code>dist</code> folder and host that as a web server. Sadly, there is no OOTB solution for this yet with existing buildpacks. This is something that would probably change in the near future. In order to solve this issue, we can solve it in two approaches, both are not perfect at the moment:</p>
<h4 id="heading-option-1-compile-npm-externally">Option 1: Compile NPM externally</h4>
<p>This option would build the source code into the dist folder before building the final image witth TBS. In order to do that, we first need to create two files for NGINX: <code>nginx.conf</code> and <code>mime.types</code>. Both of these files should exist at the root of the repository. <code>mime.types</code> is a standard file that can be replicated wherever needed. A sample file is available <a target="_blank" href="https://github.com/odedia/todo-ui/blob/main/mime.types">here</a>. You may want to tweak <code>nginx.conf</code> per your own use cases, but here's a good starting point:</p>
<pre><code class="lang-bash">worker_processes 1;
daemon off;

error_log stderr;
events { worker_connections 1024; }

http {
  charset utf-8;
  log_format cloudfoundry <span class="hljs-string">'NginxLog "$request" $status $body_bytes_sent'</span>;
  access_log /dev/stdout cloudfoundry;
  default_type application/octet-stream;
  include mime.types;
  sendfile on;

  tcp_nopush on;
  keepalive_timeout 30;
  port_in_redirect off; <span class="hljs-comment"># Ensure that redirects don't include the internal container PORT - 8080</span>

  server {
    listen {{ 8080 }};
    root .;
    index index.html;
  }
}
</code></pre>
<p>Note that the root folder to serve is the root of the target workspace folder, since we only use the <code>dist</code> folder as the root of our container. Also, We again use port 8080 to host the application. </p>
<p>To get this working, we first need to update the build command in <code>package.json</code> to also copy the two NGINX files to the <code>dist</code> folder:</p>
<pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"vue-cli-service serve"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"vue-cli-service build &amp;&amp; cp nginx.conf ./dist &amp;&amp; cp mime.types ./dist"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"vue-cli-service lint"</span>
  }
</code></pre>
<p>Let's try to run the flow:</p>
<pre><code class="lang-bash">npm install &amp;&amp; npm run build &amp;&amp; kp image save todo-vuejs --tag harbor.registry.com/tap/todo-vuejs --local-path ./dist
</code></pre>
<p>The build logs are a lot simpler now - they just server static content behind NGINX:</p>
<pre><code class="lang-bash">&gt; kp build logs todo-vuejs
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  - <span class="hljs-built_in">source</span>: {}
  + <span class="hljs-built_in">source</span>:
  +   registry:
  +     image: harbor.registry.com/tap/todo-vuejs-source@sha256:772c0bb4faa607fde8f6139135dd27814cf0a38b4d27e8abdcb68d1f528ab1d5
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Pulling harbor.registry.com/tap/todo-vuejs-source@sha256:772c0bb4faa607fde8f6139135dd27814cf0a38b4d27e8abdcb68d1f528ab1d5...
Successfully pulled harbor.registry.com/tap/todo-vuejs-source@sha256:772c0bb4faa607fde8f6139135dd27814cf0a38b4d27e8abdcb68d1f528ab1d5 <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
===&gt; DETECT
tanzu-buildpacks/nginx 0.4.4
===&gt; RESTORE
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"tanzu-buildpacks/nginx:nginx"</span> from app image
===&gt; BUILD
Tanzu Nginx Server Buildpack 0.4.4
  Resolving Nginx Server version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      buildpack.toml -&gt; <span class="hljs-string">"1.21.*"</span>

    Selected Nginx Server version (using buildpack.toml): 1.21.4

===&gt; EXPORT
Reusing layers from image <span class="hljs-string">'harbor.registry.com/tap/todo-vuejs@sha256:cc57796966f948dbec6531f399cdfbb5a2840730405810efd70eeb205f79a23a'</span>
Reusing layer <span class="hljs-string">'tanzu-buildpacks/nginx:nginx'</span>
Reusing 1/1 app layer(s)
Reusing layer <span class="hljs-string">'launcher'</span>
Reusing layer <span class="hljs-string">'config'</span>
Reusing layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/tap/todo-vuejs...
*** Images (sha256:cc57796966f948dbec6531f399cdfbb5a2840730405810efd70eeb205f79a23a):
      harbor.registry.com/tap/todo-vuejs
      harbor.registry.com/tap/todo-vuejs:b1.20220117.112114
===&gt; COMPLETION
Build successful
</code></pre>
<p>Let's see if the application is still running as expected:</p>
<pre><code class="lang-bash">&gt; docker run -p 8080:8080 harbor.registry.com/tap/todo-vuejs:b1.20220117.112114
Unable to find image <span class="hljs-string">'harbor.registry.com/tap/todo-vuejs:b1.20220117.112114'</span> locally
b1.20220117.112114: Pulling from tap/todo-vuejs
bf99a8b93828: Already exists
1da1131b1360: Already exists
48bde579d8cc: Already exists
1929d0623312: Already exists
1fea93ec33be: Already exists
5ad05d7a8092: Pull complete
3499fa7eadde: Pull complete
40d17e1b116e: Pull complete
274f229843b4: Pull complete
5a44e4f7b58d: Pull complete
Digest: sha256:cc57796966f948dbec6531f399cdfbb5a2840730405810efd70eeb205f79a23a
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/tap/todo-vuejs:b1.20220117.112114
</code></pre>
<p>Our application is exposed as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642418747861/UjyhR6ANo.png" alt="image.png" /></p>
<p>So things work, great! But, is this a good practice? probably not.</p>
<ul>
<li>We need to compile our code outside the container.</li>
<li>The NPM version that runs on the target system can impact our build. Changes to the hosted environment can produce different results.</li>
<li>We need to send source code from a local path to Build Service. This means that we are basically decoupled from our git repository. Changes in git will not be reflected in the build pipeline.</li>
<li>We create dependencies between Node buildpack and the NGINX buildpack - our build command needs to copy files to the <code>dist</code> folder.</li>
<li>In general, this feels very "scripty" and you might as well just revert to a Dockerfile at this point (although you always gain the benefits of proactive CVE patching for your base OS stack and middleware, even with this approach).</li>
</ul>
<h4 id="heading-option-2-create-a-custom-buildpack">Option 2: Create a custom buildpack</h4>
<p>We can take a different approach and just tell Build Service to run the Node Buildpack followed by the NGINX buildpack. This way we'll get better automation and can still target a git repository and not a local folder. </p>
<p>The first thing we need is to know how our buildpacks are named. We can use the following command to get information about our default <code>clusterbuilder</code>, which is the one we used up to this point:</p>
<pre><code class="lang-bash">&gt; kp clusterbuilder status default
Status:       Ready
Image:        harbor.registry.com/build-service/tbs:clusterbuilder-default@sha256:e2f4db390a617d11a475a5ebbed4bd17ddd9375a5e1b9f489621480ae58dcc43
Stack ID:     io.buildpacks.stacks.bionic
Run Image:    harbor.registry.com/build-service/tbs@sha256:b69b4f654e6a2d2d69d1cca1a51638401127b6fb3556faa6304cab1575ef7c16

Stack Ref:
  Name:       base
  Kind:       ClusterStack
Store Ref:
  Name:       default
  Kind:       ClusterStore

BUILDPACK ID                                    VERSION    HOMEPAGE
paketo-buildpacks/bellsoft-liberica             9.0.2      https://github.com/paketo-buildpacks/bellsoft-liberica
tanzu-buildpacks/go-dist                        0.6.5
tanzu-buildpacks/cpython                        0.2.0      https://github.com/pivotal-cf/tanzu-cpython
tanzu-buildpacks/dotnet-core-sdk                0.3.1
tanzu-buildpacks/node-engine                    0.8.3
tanzu-buildpacks/apache-skywalking              4.1.2      https://github.com/pivotal-cf/tanzu-apache-skywalking
tanzu-buildpacks/dotnet-core-runtime            0.3.1
tanzu-buildpacks/appdynamics                    4.1.4      https://github.com/pivotal-cf/tanzu-appdynamics
paketo-buildpacks/gradle                        6.0.2      https://github.com/paketo-buildpacks/gradle
tanzu-buildpacks/jprofiler                      4.0.2      https://github.com/pivotal-cf/tanzu-jprofiler
tanzu-buildpacks/miniconda                      0.1.4      https://github.com/pivotal-cf/tanzu-miniconda
tanzu-buildpacks/new-relic                      4.0.4      https://github.com/pivotal-cf/tanzu-new-relic
tanzu-buildpacks/overops                        4.0.3      https://github.com/pivotal-cf/tanzu-overops
tanzu-buildpacks/pipenv                         0.2.4      https://github.com/pivotal-cf/tanzu-pipenv
tanzu-buildpacks/dotnet-core-aspnet             0.3.2
paketo-buildpacks/maven                         6.1.0      https://github.com/paketo-buildpacks/maven
paketo-buildpacks/apache-tomcat                 7.0.3      https://github.com/paketo-buildpacks/apache-tomcat
tanzu-buildpacks/icu                            0.0.13
paketo-buildpacks/azure-application-insights    5.1.2      https://github.com/paketo-buildpacks/azure-application-insights
tanzu-buildpacks/jrebel                         4.0.3      https://github.com/pivotal-cf/tanzu-jrebel
paketo-buildpacks/sbt                           6.1.0      https://github.com/paketo-buildpacks/sbt
paketo-buildpacks/sbt                           6.0.4      https://github.com/paketo-buildpacks/sbt
tanzu-buildpacks/riverbed                       4.0.2      https://github.com/pivotal-cf/tanzu-riverbed
tanzu-buildpacks/yourkit                        4.0.3      https://github.com/pivotal-cf/tanzu-yourkit
tanzu-buildpacks/nginx                          0.4.4      https://docs.pivotal.io/tanzu-buildpacks/nginx/nginx-buildpack.html
tanzu-buildpacks/pip                            0.6.3      https://github.com/pivotal-cf/tanzu-pip
tanzu-buildpacks/contrast-security              4.1.1      https://github.com/pivotal-cf/tanzu-contrast-security
tanzu-buildpacks/elastic-apm                    4.1.2      https://github.com/pivotal-cf/tanzu-elastic-apm
paketo-buildpacks/maven                         6.0.2      https://github.com/paketo-buildpacks/maven
paketo-buildpacks/google-stackdriver            5.1.2      https://github.com/paketo-buildpacks/google-stackdriver
tanzu-buildpacks/elastic-apm                    4.1.3      https://github.com/pivotal-cf/tanzu-elastic-apm
tanzu-buildpacks/dep                            0.1.4
paketo-buildpacks/syft                          1.3.1      https://github.com/paketo-buildpacks/syft
tanzu-buildpacks/yarn                           0.3.3
tanzu-buildpacks/dynatrace                      4.0.2      https://github.com/pivotal-cf/tanzu-dynatrace
paketo-buildpacks/spring-boot                   5.3.0      https://github.com/paketo-buildpacks/spring-boot
tanzu-buildpacks/jacoco                         4.0.2      https://github.com/pivotal-cf/tanzu-jacoco
tanzu-buildpacks/node-module-bom                0.1.4
tanzu-buildpacks/checkmarx                      4.0.2      https://github.com/pivotal-cf/tanzu-checkmarx
paketo-buildpacks/watchexec                     2.0.2      https://github.com/paketo-buildpacks/watchexec
paketo-buildpacks/upx                           3.0.1      https://github.com/paketo-buildpacks/upx
tanzu-buildpacks/synopsys                       4.0.2      https://github.com/pivotal-cf/tanzu-synopsys
paketo-buildpacks/ca-certificates               3.0.2      https://github.com/paketo-buildpacks/ca-certificates
paketo-buildpacks/procfile                      5.0.2      https://github.com/paketo-buildpacks/procfile
paketo-buildpacks/encrypt-at-rest               4.0.2      https://github.com/paketo-buildpacks/encrypt-at-rest
paketo-buildpacks/leiningen                     4.0.2      https://github.com/paketo-buildpacks/leiningen
paketo-buildpacks/clojure-tools                 2.0.2      https://github.com/paketo-buildpacks/clojure-tools
paketo-buildpacks/syft                          1.4.0      https://github.com/paketo-buildpacks/syft
tanzu-buildpacks/snyk                           4.0.2      https://github.com/pivotal-cf/tanzu-snyk
tanzu-buildpacks/go-build                       0.3.2
tanzu-buildpacks/dotnet-publish                 0.5.1
tanzu-buildpacks/dotnet-execute                 0.4.1
tanzu-buildpacks/pipenv-install                 0.1.6      https://github.com/pivotal-cf/tanzu-pipenv-install
tanzu-buildpacks/npm-install                    0.5.3
tanzu-buildpacks/yarn-install                   0.3.2
tanzu-buildpacks/dep-ensure                     0.1.3
tanzu-buildpacks/go-mod-vendor                  0.2.3
tanzu-buildpacks/pip-install                    0.2.2      https://github.com/pivotal-cf/tanzu-pip-install
tanzu-buildpacks/conda-env-update               0.1.4      https://github.com/pivotal-cf/tanzu-conda-env-update
tanzu-buildpacks/node-run-script                0.2.2
tanzu-buildpacks/npm-start                      0.2.3
tanzu-buildpacks/yarn-start                     0.2.3
tanzu-buildpacks/python-start                   0.4.2      https://github.com/pivotal-cf/tanzu-python-start
tanzu-buildpacks/node-start                     0.3.3
paketo-buildpacks/native-image                  5.0.2      https://github.com/paketo-buildpacks/native-image
paketo-buildpacks/executable-jar                6.0.2      https://github.com/paketo-buildpacks/executable-jar
tanzu-buildpacks/aspectj                        4.0.2      https://github.com/pivotal-cf/tanzu-aspectj
paketo-buildpacks/dist-zip                      5.0.2      https://github.com/paketo-buildpacks/dist-zip
paketo-buildpacks/environment-variables         4.0.1      https://github.com/paketo-buildpacks/environment-variables
paketo-buildpacks/environment-variables         4.0.2      https://github.com/paketo-buildpacks/environment-variables
paketo-buildpacks/image-labels                  4.0.2      https://github.com/paketo-buildpacks/image-labels
tanzu-buildpacks/java                           6.4.0      https://github.com/pivotal-cf/tanzu-java
tanzu-buildpacks/java-native-image              6.5.0      https://github.com/pivotal-cf/tanzu-java-native-image
tanzu-buildpacks/nodejs                         1.13.5     https://docs.pivotal.io/tanzu-buildpacks/nodejs/nodejs-buildpack.html
tanzu-buildpacks/dotnet-core                    1.11.1     https://docs.pivotal.io/tanzu-buildpacks/dotnet-core/dotnet-core-buildpack.html
tanzu-buildpacks/go                             1.10.4     https://docs.pivotal.io/tanzu-buildpacks/go/go-buildpack.html
tanzu-buildpacks/python                         1.1.4


DETECTION ORDER
Group <span class="hljs-comment">#1</span>
  tanzu-buildpacks/dotnet-core@1.11.1
Group <span class="hljs-comment">#2</span>
  tanzu-buildpacks/nodejs@1.13.5
Group <span class="hljs-comment">#3</span>
  tanzu-buildpacks/go@1.10.4
Group <span class="hljs-comment">#4</span>
  tanzu-buildpacks/python@1.1.4
Group <span class="hljs-comment">#5</span>
  tanzu-buildpacks/nginx@0.4.4
Group <span class="hljs-comment">#6</span>
  tanzu-buildpacks/java-native-image@6.5.0
Group <span class="hljs-comment">#7</span>
  tanzu-buildpacks/java@6.4.0
Group <span class="hljs-comment">#8</span>
  paketo-buildpacks/procfile@5.0.2
</code></pre>
<p>We see that the buildpacks we need are <code>tanzu-buildpacks/nodejs</code> and <code>tanzu-buildpacks/nginx</code></p>
<p>Now, we need to define a new builder. Let's call it <code>webserver</code>:</p>
<pre><code class="lang-bash">&gt; kp builder create webserver --tag harbor.registry.com/apps/webserver --buildpack tanzu-buildpacks/nodejs --buildpack tanzu-buildpacks/nginx
Builder <span class="hljs-string">"webserver"</span> created
</code></pre>
<p>Before we can update our image, we need to setup the source code to be more aligned with our new folder structure. Our resulting image will have all the NodeJS artifacts and also the target <code>dist</code> folder. Because of that, we need to do the following:</p>
<ul>
<li><p>The npm build command doesn't need to copy the two Nginx artifacts to the <code>dist</code> folder anymore:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"vue-cli-service serve"</span>,
  <span class="hljs-attr">"build"</span>: <span class="hljs-string">"vue-cli-service build"</span>,
  <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,
  <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"vue-cli-service lint"</span>
}
</code></pre>
</li>
<li><p>The root of <code>nginx.conf</code> should now target the <code>dist</code> folder:</p>
</li>
</ul>
<pre><code class="lang-bash">worker_processes 1;
daemon off;

error_log stderr;
events { worker_connections 1024; }

http {
  charset utf-8;
  log_format cloudfoundry <span class="hljs-string">'NginxLog "$request" $status $body_bytes_sent'</span>;
  access_log /dev/stdout cloudfoundry;
  default_type application/octet-stream;
  include mime.types;
  sendfile on;

  tcp_nopush on;
  keepalive_timeout 30;
  port_in_redirect off; <span class="hljs-comment"># Ensure that redirects don't include the internal container PORT - 8080</span>

  server {
    listen {{ 8080 }};
    root dist;
    index index.html;
  }
}
</code></pre>
<p>With our source code setup as we want, we can initiate the build command:</p>
<pre><code class="lang-bash">&gt; kp image save todo-vuejs \
--tag harbor.registry.com/tap/todo-vuejs \
--git https://github.com/odedia/todo-ui \
--git-revision main \
--builder webserver
Patching Image Resource...
Image Resource <span class="hljs-string">"todo-vuejs"</span> patched
</code></pre>
<p>Let's check the updated logs. We now see that the code is first being built with NPM, and then served via NGINX, which is exactly what we want:</p>
<pre><code class="lang-bash">&gt; kp build logs todo-vuejs
===&gt; PREPARE
Build reason(s): CONFIG
CONFIG:
  resources: {}
  <span class="hljs-built_in">source</span>:
  -   registry:
  -     image: harbor.registry.com/tap/todo-vuejs-source@sha256:8783abc3754540ca6c5cb0cca9b92ab5a1bca98a539d7a7473abf3bd53e9a328
  +   git:
  +     revision: 988e7e70f27a2fcb9f9edf2025f1e517cfdef86d
  +     url: https://github.com/odedia/todo-ui
Loading secret <span class="hljs-keyword">for</span> <span class="hljs-string">"harbor.registry.com"</span> from secret <span class="hljs-string">"registry-credentials"</span> at location <span class="hljs-string">"/var/build-secrets/registry-credentials"</span>
Cloning <span class="hljs-string">"https://github.com/odedia/todo-ui"</span> @ <span class="hljs-string">"988e7e70f27a2fcb9f9edf2025f1e517cfdef86d"</span>...
Successfully cloned <span class="hljs-string">"https://github.com/odedia/todo-ui"</span> @ <span class="hljs-string">"988e7e70f27a2fcb9f9edf2025f1e517cfdef86d"</span> <span class="hljs-keyword">in</span> path <span class="hljs-string">"/workspace"</span>
===&gt; ANALYZE
===&gt; DETECT
6 of 18 buildpacks participating
paketo-buildpacks/ca-certificates 3.0.2
tanzu-buildpacks/node-engine      0.8.3
tanzu-buildpacks/npm-install      0.5.3
tanzu-buildpacks/node-module-bom  0.1.4
tanzu-buildpacks/npm-start        0.2.3
tanzu-buildpacks/nginx            0.4.4
===&gt; RESTORE
Restoring metadata <span class="hljs-keyword">for</span> <span class="hljs-string">"tanzu-buildpacks/nginx:nginx"</span> from app image
===&gt; BUILD

Paketo CA Certificates Buildpack 3.0.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Tanzu Node Engine Buildpack 0.8.3
  Resolving Node Engine version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      package.json -&gt; <span class="hljs-string">"&gt;=10.12.0"</span>
      .nvmrc       -&gt; <span class="hljs-string">"11.6.0"</span>
      &lt;unknown&gt;    -&gt; <span class="hljs-string">""</span>

    Selected Node Engine version (using package.json): 16.13.1

  Executing build process
    Installing Node Engine 16.13.1
      Completed <span class="hljs-keyword">in</span> 1.076s

  Configuring build environment
    NODE_ENV     -&gt; <span class="hljs-string">"production"</span>
    NODE_HOME    -&gt; <span class="hljs-string">"/layers/tanzu-buildpacks_node-engine/node"</span>
    NODE_VERBOSE -&gt; <span class="hljs-string">"false"</span>

  Configuring launch environment
    NODE_ENV     -&gt; <span class="hljs-string">"production"</span>
    NODE_HOME    -&gt; <span class="hljs-string">"/layers/tanzu-buildpacks_node-engine/node"</span>
    NODE_VERBOSE -&gt; <span class="hljs-string">"false"</span>

    Writing profile.d/0_memory_available.sh
      Calculates available memory based on container limits at launch time.
      Made available <span class="hljs-keyword">in</span> the MEMORY_AVAILABLE environment variable.

Tanzu NPM Install Buildpack 0.5.3
  Resolving installation process
    Process inputs:
      node_modules      -&gt; <span class="hljs-string">"Not found"</span>
      npm-cache         -&gt; <span class="hljs-string">"Not found"</span>
      package-lock.json -&gt; <span class="hljs-string">"Found"</span>

    Selected NPM build process: <span class="hljs-string">'npm ci'</span>

  Executing build process
    Running <span class="hljs-string">'npm ci --unsafe-perm --cache /layers/tanzu-buildpacks_npm-install/npm-cache'</span>
      Completed <span class="hljs-keyword">in</span> 6.354s

  Configuring launch environment
    NPM_CONFIG_LOGLEVEL -&gt; <span class="hljs-string">"error"</span>

  Configuring environment shared by build and launch
    PATH -&gt; <span class="hljs-string">"<span class="hljs-variable">$PATH</span>:/layers/tanzu-buildpacks_npm-install/modules/node_modules/.bin"</span>


Tanzu Node Module Bill of Materials Generator Buildpack 0.1.4
  Resolving CycloneDX Node.js Module version
    Selected CycloneDX Node.js Module version: 3.0.7

  Executing build process
    Installing CycloneDX Node.js Module 3.0.7
      Completed <span class="hljs-keyword">in</span> 46ms

  Configuring environment
    Appending CycloneDX Node.js Module onto PATH

  Running CycloneDX Node.js Module
    Running <span class="hljs-string">'cyclonedx-bom -o bom.json'</span>
      Completed <span class="hljs-keyword">in</span> 496ms

Tanzu NPM Start Buildpack 0.2.3
  Assigning launch processes
    web: node server.js

Tanzu Nginx Server Buildpack 0.4.4
  Resolving Nginx Server version
    Candidate version sources (<span class="hljs-keyword">in</span> priority order):
      buildpack.toml -&gt; <span class="hljs-string">"1.21.*"</span>

    Selected Nginx Server version (using buildpack.toml): 1.21.4

===&gt; EXPORT
Reusing layers from image <span class="hljs-string">'harbor.registry.com/tap/todo-vuejs@sha256:6f435c808b0b7b413b7314aeb24bb01f23c859aec27d482c8574bce2435115a3'</span>
Adding layer <span class="hljs-string">'paketo-buildpacks/ca-certificates:helper'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/node-engine:node'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/npm-install:modules'</span>
Adding layer <span class="hljs-string">'tanzu-buildpacks/npm-install:npm-cache'</span>
Reusing layer <span class="hljs-string">'tanzu-buildpacks/nginx:nginx'</span>
Adding layer <span class="hljs-string">'launch.sbom'</span>
Adding 1/1 app layer(s)
Reusing layer <span class="hljs-string">'launcher'</span>
Adding layer <span class="hljs-string">'config'</span>
Reusing layer <span class="hljs-string">'process-types'</span>
Adding label <span class="hljs-string">'io.buildpacks.lifecycle.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.build.metadata'</span>
Adding label <span class="hljs-string">'io.buildpacks.project.metadata'</span>
Setting default process <span class="hljs-built_in">type</span> <span class="hljs-string">'web'</span>
Saving harbor.registry.com/tap/todo-vuejs...
*** Images (sha256:c8201b2c55727cd0a6ae238a9bb3f889b5cf1247b75b7b1e029c5547a8237db9):
      harbor.registry.com/tap/todo-vuejs
      harbor.registry.com/tap/todo-vuejs:b3.20220117.113942
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/node-engine:node'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/npm-install:modules'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/npm-install:npm-cache'</span>
Adding cache layer <span class="hljs-string">'tanzu-buildpacks/node-module-bom:cyclonedx-node-module'</span>
===&gt; COMPLETION
Build successful
</code></pre>
<p>Let's verify everything works as expected:</p>
<pre><code class="lang-bash">&gt; docker run harbor.registry.com/tap/todo-vuejs:b3.20220117.113942
Unable to find image <span class="hljs-string">'harbor.registry.com/tap/todo-vuejs:b3.20220117.113942'</span> locally
b3.20220117.113942: Pulling from tap/todo-vuejs
bf99a8b93828: Already exists
1da1131b1360: Already exists
48bde579d8cc: Already exists
1929d0623312: Already exists
1fea93ec33be: Already exists
fed2bcb93d83: Already exists
eb2e57672edf: Already exists
03e598551527: Already exists
f76b3557931f: Pull complete
5ad05d7a8092: Pull complete
841d6c40c392: Pull complete
697988c1248a: Pull complete
40d17e1b116e: Pull complete
239731ce13f5: Pull complete
5a44e4f7b58d: Pull complete
Digest: sha256:c8201b2c55727cd0a6ae238a9bb3f889b5cf1247b75b7b1e029c5547a8237db9
Status: Downloaded newer image <span class="hljs-keyword">for</span> harbor.registry.com/tap/todo-vuejs:b3.20220117.113942
cat: /sys/fs/cgroup/memory/memory.limit_in_bytes: No such file or directory
/layers/tanzu-buildpacks_node-engine/node/profile.d/0_memory_available.sh: line 3: / ( 1024 * 1024 ) : syntax error: operand expected (error token is <span class="hljs-string">"/ ( 1024 * 1024 ) "</span>)
</code></pre>
<p>The application still works as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1642419962422/PrSmVz6b4.png" alt="image.png" /></p>
<p>In the docker logs, we can confirm that it's NGINX hosting the content:</p>
<pre><code class="lang-bash">NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET /js/app.8278f7ef.js HTTP/1.1"</span> 200 10484
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
NginxLog <span class="hljs-string">"GET / HTTP/1.1"</span> 200 771
</code></pre>
<p>Great! Everything works. However, there is still a small thing that bugs me: Do I really need the entire NPM stack in the target image? This includes the bloated <code>node_modules</code> folder and other artifacts. The size of this new image is around 210mb, while the size of the NGINX-only image is 102mb. </p>
<p>Still, this is probably the recommended way to build frontend applications with Build Service, and I believe this area will improve significantly soon.</p>
<p>This concludes part 1 of this tutorial. In the next one, we'll dive deep into other buildpacks such as Spring Native (Powered by GraalVM), Python applications and some other extreme use cases. Stay tuned!</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes needs a developer platform]]></title><description><![CDATA[The container runtime landscape

Kubernetes is the new IaaS.

Whether you’re developing custom applications or purchasing off-the-shelf software from a vendor, the new, modern target runtime are containers, and the container runtime of choice is Kube...]]></description><link>https://odedia.org/kubernetes-needs-a-developer-platform</link><guid isPermaLink="true">https://odedia.org/kubernetes-needs-a-developer-platform</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Devops]]></category><category><![CDATA[PaaS]]></category><category><![CDATA[Developer Tools]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Mon, 03 Jan 2022 16:50:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640994023169/HZJYeIdOD.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-the-container-runtime-landscape">The container runtime landscape</h3>
<blockquote>
<p>Kubernetes is the new IaaS.</p>
</blockquote>
<p>Whether you’re developing custom applications or purchasing off-the-shelf software from a vendor, the new, modern target runtime are containers, and the container runtime of choice is Kubernetes.</p>
<p>In a rare alignment of the stars for the tech industry, virtually all vendors, private cloud and public cloud providers, are in agreement on a spec and on an API. That's really something. It doesn't have to be the best API in world, or even the most readable, and it doesn’t matter that interaction is mostly done through a wall of YAML. The important takeaway is that it's a language and syntax that almost <em>everyone</em> agreed upon.</p>
<p>But just because the industry agrees on a fundamental, low-level API, doesn't mean that developers should use it. In fact, it probably means the opposite. </p>
<h3 id="heading-the-abstraction-gap">The abstraction gap</h3>
<p>How often do you develop cloud-based, server side software in C these days? When was the last time you've written something in assembly, if ever? Do you use low-level networking APIs or do you opt for HTTP or gRPC?</p>
<p>We write at higher-level abstractions because they provide us with meaningful benefits:</p>
<ul>
<li>Our code is more readable</li>
<li>We stand on the shoulders of giants who simplified and encapsulated very complicated systems for us</li>
<li>An abstraction usually provides guardrails. How many times did the type system of Java, or its automated garbage collection, saved you from disaster?</li>
<li>Underlying low-level APIs may change for better optimization, while our code remains the same</li>
</ul>
<p>The same can and should be said for Kubernetes. If we truly treat Kubernetes as commodity infrastructure, why do we use it directly?
How many developers do you know that have ever seen a vCenter console, or got full admin rights to an AWS console? Hardly any, and for a very good reason.</p>
<p>Developers want to write their business logic and have it run on a Kubernetes cluster. </p>
<p><em>How</em>? Good question. </p>
<p>Sure, developers need to understand how things work in Kubernetes, and may need to dig deeper into an environment when issues arise, but in their day to day they have so many other things to worry about - business logic, code path optimizations, writing unit tests, merge conflicts, evaluating sources and targets of data - that having to worry about infrastructure is just too much of an overhead. Non-functional requirements can take up a considerable part of a software engineer's time. That is time that could have been spent providing value to the organization with new features, that ultimately provide a better user experience, and eventually more revenue.</p>
<p>A platform that is focused on developer productivity, if done correctly, can remove much of that toil from the day-to-day of a software engineer, otherwise they may spend a large portion of their time  building an internal DIY platform.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/kelseyhightower/status/935252923721793536?s=20">https://twitter.com/kelseyhightower/status/935252923721793536?s=20</a></div>
<h3 id="heading-lets-build-an-app">Let's build an app</h3>
<p>In order to fully understand a problem domain, I always like to use a real example. Let's test the hypothesis with a very simple hello world application, written in Java using Spring Boot:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloController</span> </span>{
    <span class="hljs-meta">@Value("${app.message:Hello world!}")</span>
    <span class="hljs-keyword">private</span> String message;

    <span class="hljs-meta">@GetMapping(value = "/", produces = MediaType.TEXT_PLAIN_VALUE)</span>
    <span class="hljs-function">String <span class="hljs-title">greeting</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-comment">// Just return a simple String.</span>
        <span class="hljs-keyword">return</span> message;
    }
}
</code></pre>
<p>Simple enough. </p>
<p>Let's add a unit test to make sure our application behaves as expected:</p>
<pre><code class="lang-java"><span class="hljs-meta">@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationTests</span> </span>{
    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> TestRestTemplate webClient;

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">testGreeting</span><span class="hljs-params">()</span> </span>{
        assertThat(webClient.getForObject(<span class="hljs-string">"/"</span>, String.class)).isEqualTo(<span class="hljs-string">"Hello world!"</span>);
    }
}
</code></pre>
<p>Great! Our application is now production ready. 😉</p>
<p>Well, not really. We want to run this application in a production-ready environment, preferably as a container so we can gain better isolation than just a JVM runtime, and also to easily scale out horizontally when needed.</p>
<h3 id="heading-building-the-oci-image">Building the OCI image</h3>
<p>First of all, we need to containerize our application. But how? The most obvious way to achieve this is to write a Dockerfile. After some googling, we may end up with this example from the  <a target="_blank" href="https://spring.io/guides/gs/spring-boot-docker/">Spring guides website</a>:</p>
<pre><code class="lang-dockerfile">FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
</code></pre>
<p>Now, this will work, but only if we compile and package our code in advance. Let's deconstruct this Dockerfile line by line:</p>
<ul>
<li><code>FROM openjdk:8-jdk-alpine</code> uses a base image from Dockerhub that appears to be based on Alpine Linux and OpenJDK 8. Being bound to JDK 8 is pretty old here at the dawn of 2022, so we may need to google for a better baseline that supports Java 11 or 17.</li>
<li><code>ARG JAR_FILE=target/*.jar</code> declares an env variable that contains all the jar files in the <code>target</code> folder. We assume there's only one, or else the next command will not work properly.</li>
<li><code>COPY ${JAR_FILE} app.jar</code> copies the jar file to the root of the container folder structure and renames it to <code>app.jar</code>.</li>
<li><code>ENTRYPOINT ["java","-jar","/app.jar"]</code> sets the start command for this container - basically run the uber jar using Java. Note the utter lack of any <code>JAVA_OPTS</code>, tweaking for production or changes to the garbage collection settings. We’re using whatever the defaults are, which are probably not optimized for container runtimes. </li>
</ul>
<p>Let's build the image:</p>
<pre><code class="lang-bash">&gt; docker build -t hello-world:v1 .
...
...
 =&gt; [stage-1 1/5] FROM docker.io/library/adoptopenjdk:11-jre-hotspot@sha256:ad6431b2e2a6f8016aa6d79c3f588783af9fdc06cafe131fd0d3faf560914b13         9.8s
 =&gt; [internal] load build context                                                                                                                    0.0s
 =&gt; =&gt; transferring context: 232.93kB                                                                                                                0.0s
 =&gt; [stage-1 2/5] RUN useradd -m -s /bin/bash app                                                                                                    0.5s
 =&gt; [builder 2/6] RUN mkdir /build                                                                                                                   0.4s
 =&gt; [builder 3/6] ADD . /build                                                                                                                       0.1s
 =&gt; [builder 4/6] WORKDIR /build                                                                                                                     0.0s
 =&gt; [builder 5/6] RUN ./mvnw -B clean package                                                                                                      101.9s
 =&gt; [builder 6/6] RUN mkdir -p target/dependency &amp;&amp; (<span class="hljs-built_in">cd</span> target/dependency; jar -xf ../*.jar)                                                         0.6s
 =&gt; [stage-1 3/5] COPY --from=BUILDER /build/target/dependency/BOOT-INF/lib /app/lib                                                                 0.1s
 =&gt; [stage-1 4/5] COPY --from=BUILDER /build/target/dependency/META-INF /app/META-INF                                                                0.0s
 =&gt; [stage-1 5/5] COPY --from=BUILDER /build/target/dependency/BOOT-INF/classes /app                                                                 0.0s
 =&gt; exporting to image                                                                                                                               0.1s
 =&gt; =&gt; exporting layers                                                                                                                              0.1s
 =&gt; =&gt; writing image sha256:1433c6a737757449f2697cee31806f69cb0ffac1ca9ea53acae7153e1868d3f2                                                         0.0s
 =&gt; =&gt; naming to docker.io/library/hello-world:v1                                                                                                    0.0s
</code></pre>
<p>So, this was fairly simple, but also quite script-driven. We basically described a step by step of the <em>how</em>, not the <em>what</em>.</p>
<p>As developers, we are now also responsible for 100% of that  <em>how</em>. We need to start thinking about security implications. For example, we probably only want to run our container with user privileges. The same Spring blog post recommends to add a custom user and group and run the container with that user:</p>
<pre><code class="lang-bash">FROM openjdk:8-jdk-alpine
RUN addgroup -S spring &amp;&amp; adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY <span class="hljs-variable">${JAR_FILE}</span> app.jar
ENTRYPOINT [<span class="hljs-string">"java"</span>,<span class="hljs-string">"-jar"</span>,<span class="hljs-string">"/app.jar"</span>]
</code></pre>
<p>Once again, since we as developers are now responsible for 100% of the <em>how</em>, we need to start thinking about optimizations. 
The sample above takes our large uber jar that can easily reach 150mb or more, and put it in a single layer in the OCI image. What does this mean in practice? It means that for every new build, even for the simplest code change in our custom code, a new 150mb layer (read: <em>tarball</em>) will be created and uploaded to our OCI registry. Later, when pulling the image to our local machines or to a Kubernetes cluster, a 150mb layer will need to be pulled as well. This will happen on every single code change and rebuild of the image.
In reality, only a very small sliver of compiled code has changed. Dockerfile treats every new line as its own layer, so it would make a lot more sense to put the third party dependencies in their own layer and have our custom code in its own layer. That way, only a very small layer will need to be uploaded and later pulled. The Spring blog presents the following approach to basically "unzip" the jar file:</p>
<pre><code class="lang-bash">FROM openjdk:8-jdk-alpine
RUN addgroup -S spring &amp;&amp; adduser -S spring -G spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY <span class="hljs-variable">${DEPENDENCY}</span>/BOOT-INF/lib /app/lib
COPY <span class="hljs-variable">${DEPENDENCY}</span>/META-INF /app/META-INF
COPY <span class="hljs-variable">${DEPENDENCY}</span>/BOOT-INF/classes /app
ENTRYPOINT [<span class="hljs-string">"java"</span>,<span class="hljs-string">"-cp"</span>,<span class="hljs-string">"app:app/lib/*"</span>,<span class="hljs-string">"hello.HelloController"</span>]
</code></pre>
<p>Of course, this sort of Dockerfile is only good if you have a previous step that already compiled and packaged the jar for you, and that's usually not what you want. It would be ideal that the compilation of the source code would be done in a containerized environment as well, to remove any risk of an unknown (and potentially insecure) build environment. </p>
<p>We also took some time to build our image with what we consider good security best practices <em>today</em>, but new CVEs are discovered every day, and patches are released to various Linux distributions, to the JDK and to many frameworks you may be using in your code (hi there,  <a target="_blank" href="https://arstechnica.com/information-technology/2021/12/minecraft-and-other-apps-face-serious-threat-from-new-code-execution-bug/">log4j 👋</a>). Since we as developers now need to care about the <em>how</em>, it is our responsibility (or a specific person in the organization) to keep things patched continuously. </p>
<h3 id="heading-cloud-native-buildpacks-to-the-rescue">Cloud Native Buildpacks to the rescue</h3>
<p>Luckily, at least in the world of Spring Boot, we have you covered.</p>
<p>Thanks to <a target="_blank" href="https://buildpacks.io">Cloud-Native Buildpacks CNCF project</a>, we can create images from source code just by saying <em>what</em> we want, without having to explain the how. Cloud Native Buildpacks are available for various modern languages, and of course for Spring Boot. In fact, the Spring Boot maven plugin provides a command just for that:</p>
<pre><code>mvn spring<span class="hljs-operator">-</span>boot:build<span class="hljs-operator">-</span>image
</code></pre><p>This simple command will build an optimized, production-ready, secure image. I may not care how, although I could dive into the spec if I want to understand how it performs the  <a target="_blank" href="https://buildpacks.io/docs/concepts/components/lifecycle/">build lifecycle</a>. I can also override the recommended defaults if I wanted to (for example to change the JDK version from the current default of JDK 11 to an older JDK 8 or a newer JDK 17). Lastly - the generated image is created with sensible defaults such as proper segregation of layers in the OCI image so it would be optimized for storage and pulls later on, and an optimized java start command that is recommend for running in a containerized environment. The important piece here is that I delegated some of the responsibility of my non-functional requirements <em>to a platform</em>. In the case of a simple <code>mvn spring-boot:build-image</code> command, it will go out and build the image using the latest patched layers that are available for the OS and OpenJDK. In case of other platforms that support Cloud Native Buildpacks, such as  <a target="_blank" href="https://github.com/pivotal/kpack">kpack</a>  or its commercial flavor   <a target="_blank" href="https://tanzu.vmware.com/build-service">Tanzu Build Service</a>, the builds would be triggered automatically on git commit or when an operator uploads updated buildpacks or OS updates (called:  <a target="_blank" href="https://buildpacks.io/docs/concepts/components/stack/">stacks</a> ) to the environment. </p>
<p>So, we have a production-ready image. Great! We can now just deploy to Kubernetes and forget about it. </p>
<p>Well... I guess we still have some things we need to do. </p>
<p>Let's try to get as far as we can with just barebones Kubernetes. </p>
<h3 id="heading-creating-a-deployment">Creating a deployment</h3>
<p>We'll begin by tagging our built image and pushing it to a docker registry.</p>
<pre><code class="lang-bash">docker tag hello-world my-registry.com/apps/hello-world:v1
docker push my-registry.com/apps/hello-world:v1
</code></pre>
<p>Now we can go ahead and create a deployment in our Kubernetes cluster. It is considered a best practice to make changes in Kubernetes in a declarative way, so we can keep our configuration in a versioned repository, thus promoting <a target="_blank" href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Infrastructure as Code</a>. Let's use imperative commands to generate the initial declarative yaml skeleton we will need:</p>
<pre><code class="lang-bash">kubectl create deployment hello-world --image=my-registry.com/apps/hello-world:v1 --dry-run=client -o yaml &gt; deployment.yaml
</code></pre>
<p>The result is a good starting point to describe our deployment:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">creationTimestamp:</span> <span class="hljs-literal">null</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">strategy:</span> {}
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">creationTimestamp:</span> <span class="hljs-literal">null</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">my-registry.com/apps/hello-world:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">resources:</span> {}
<span class="hljs-attr">status:</span> {}
</code></pre>
<p>We would probably want to change some settings here though. For example, set replica count to 3. We would also need to understand the CPU and memory requirements of our application and set proper Kubernetes resource requests and limits (however, it's best to understand what these settings  <a target="_blank" href="https://twitter.com/odedia/status/1425853578288246791">actually mean</a>). Let's start with an initial settings of 1 core and 384mb of memory, and a max of 1.5 cores and 512mb of memory:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">creationTimestamp:</span> <span class="hljs-literal">null</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">strategy:</span> {}
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">creationTimestamp:</span> <span class="hljs-literal">null</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">my-registry.com/apps/hello-world:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"384Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1000m"</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"512Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1500m"</span>
</code></pre>
<p>You would probably also want to define the deployment strategy, as this could have implications on the availability of your app. How many replicas can be unavailable at any given time? How many replicas can be updated simultaneously?</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">strategy:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">RollingUpdate</span>
    <span class="hljs-attr">rollingUpdate:</span>
       <span class="hljs-attr">maxSurge:</span> <span class="hljs-number">25</span><span class="hljs-string">%</span>
       <span class="hljs-attr">maxUnavailable:</span> <span class="hljs-number">25</span><span class="hljs-string">%</span>  
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">my-registry.com/apps/hello-world:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"384Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1000m"</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"512Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1500m"</span>
</code></pre>
<p>We're almost there!</p>
<p>Oh wait. How do I tell Kubernetes where to place each replica? I would like to have a nice spread between available worker nodes for better availability. The below example adds an anti-affinity rule to our deployment:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">strategy:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">RollingUpdate</span>
    <span class="hljs-attr">rollingUpdate:</span>
       <span class="hljs-attr">maxSurge:</span> <span class="hljs-number">25</span><span class="hljs-string">%</span>
       <span class="hljs-attr">maxUnavailable:</span> <span class="hljs-number">25</span><span class="hljs-string">%</span>  
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">affinity:</span>
        <span class="hljs-attr">podAntiAffinity:</span>
          <span class="hljs-attr">requiredDuringSchedulingIgnoredDuringExecution:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">labelSelector:</span>
              <span class="hljs-attr">matchExpressions:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">app</span>
                <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
                <span class="hljs-attr">values:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">hello-world</span>
            <span class="hljs-attr">topologyKey:</span> <span class="hljs-string">"kubernetes.io/hostname"</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">my-registry.com/apps/hello-world:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"384Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1000m"</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"512Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1500m"</span>
</code></pre>
<p>This is getting quite long, isn't it? Oh well, whatever it takes to run our <strong> <a target="_blank" href="https://twitter.com/kelseyhightower/status/1304045435971989505">simple hello world app on Kubernetes</a> </strong> 🤦🏻‍♂️</p>
<p>We can finally deploy our application:</p>
<pre><code class="lang-bash">&gt; kubectl apply -f deployment.yaml
</code></pre>
<p>Let's make sure our app is working as expected:</p>
<pre><code class="lang-bash">&gt; kubectl get pods
NAME                           READY   STATUS             RESTARTS   AGE
hello-world-64bccd8549-fqmxv   0/1     CrashLoopBackOff   3          79s
hello-world-64bccd8549-fxgrr   0/1     CrashLoopBackOff   3          79s
hello-world-64bccd8549-rbw2b   0/1     CrashLoopBackOff   3          79s
</code></pre>
<p>Oops. What happened? Let's check the logs:</p>
<pre><code class="lang-bash">&gt; kubectl logs --selector app=hello-world
Setting Active Processor Count to 4
unable to calculate memory configuration
fixed memory regions require 596880K <span class="hljs-built_in">which</span> is greater than 512M available <span class="hljs-keyword">for</span> allocation: -XX:MaxDirectMemorySize=10M, -XX:MaxMetaspaceSize=84880K, -XX:ReservedCodeCacheSize=240M, -Xss1M * 250 threads
ERROR: failed to launch: exec.d: failed to execute exec.d file at path <span class="hljs-string">'/layers/paketo-buildpacks_bellsoft-liberica/helper/exec.d/memory-calculator'</span>: <span class="hljs-built_in">exit</span> status 1
</code></pre>
<p>Seems like our resource requests and limits were too low. Until  <a target="_blank" href="https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/">Spring Native</a> is officially GAed with much lower memory footprint requirements and faster startup times, we're still bound to our memory-hungry JVM. Still, this is a reminder that it is our responsibility to tweak the deployment configuration to a production-ready state. After changing the deployment yaml to a limit of 768Mi, we can try again:</p>
<pre><code class="lang-bash">&gt; kubectl apply -f deployment.yaml
deployment.apps/hello-world configured


&gt; kubectl get pods
NAME                           READY   STATUS             RESTARTS   AGE
hello-world-5ff7bfb9ff-wgnj6   0/1     Pending            0          38s
hello-world-64bccd8549-fqmxv   0/1     CrashLoopBackOff   5          5m20s
hello-world-64bccd8549-fxgrr   0/1     CrashLoopBackOff   5          5m20s
hello-world-64bccd8549-rbw2b   0/1     CrashLoopBackOff   5          5m20s
</code></pre>
<p>Hmm... seems like our rollout is stuck in Pending state. We probably didn't take into consideration the correct relationship between our RollingUpdate strategy, the Node Anti-Affinity rules and the number of available worker nodes, so we are stuck in a form of deadlock. Again, it is <em>your</em> responsibility to take these matters into consideration.</p>
<p>Let's change our <code>deployment.yaml</code> to allow for up to 50% of the replicas to be unavailable. This may or may not be an acceptable solution in your production environment. The final <code>deployment.yaml</code> now looks like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">strategy:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">RollingUpdate</span>
    <span class="hljs-attr">rollingUpdate:</span>
       <span class="hljs-attr">maxSurge:</span> <span class="hljs-number">25</span><span class="hljs-string">%</span>
       <span class="hljs-attr">maxUnavailable:</span> <span class="hljs-number">50</span><span class="hljs-string">%</span>  
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">affinity:</span>
        <span class="hljs-attr">podAntiAffinity:</span>
          <span class="hljs-attr">requiredDuringSchedulingIgnoredDuringExecution:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">labelSelector:</span>
              <span class="hljs-attr">matchExpressions:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">app</span>
                <span class="hljs-attr">operator:</span> <span class="hljs-string">In</span>
                <span class="hljs-attr">values:</span>
                <span class="hljs-bullet">-</span> <span class="hljs-string">hello-world</span>
            <span class="hljs-attr">topologyKey:</span> <span class="hljs-string">"kubernetes.io/hostname"</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">my-registry.com/apps/hello-world:v1</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
        <span class="hljs-attr">resources:</span>
          <span class="hljs-attr">requests:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"384Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1000m"</span>
          <span class="hljs-attr">limits:</span>
            <span class="hljs-attr">memory:</span> <span class="hljs-string">"768Mi"</span>
            <span class="hljs-attr">cpu:</span> <span class="hljs-string">"1500m"</span>
</code></pre>
<p>Let's check the results:</p>
<pre><code class="lang-bash">&gt; kubectl apply -f deployment.yaml 
deployment.apps/hello-world configured

&gt; kubectl get pods
NAME                           READY   STATUS    RESTARTS   AGE
hello-world-5ff7bfb9ff-mph2v   1/1     Running   0          99s
hello-world-5ff7bfb9ff-pmkxx   1/1     Running   0          95s
hello-world-5ff7bfb9ff-xrtdx   1/1     Running   0          99s
</code></pre>
<p><strong>Finally</strong>. </p>
<h3 id="heading-exposing-the-deployment">Exposing the deployment</h3>
<p>Now that our app is deployed, we need to expose it to the world. We can again use the imperative route to generate our declarative yaml:</p>
<pre><code class="lang-bash">kubectl expose deployment/hello-world --port 8080 --target-port 8080 --dry-run=client -o yaml &gt; service.yaml
</code></pre>
<p>Our <code>service.yaml</code> looks like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">creationTimestamp:</span> <span class="hljs-literal">null</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
    <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">8080</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">hello-world</span>
<span class="hljs-attr">status:</span>
  <span class="hljs-attr">loadBalancer:</span> {}
</code></pre>
<p>Let's apply it:</p>
<pre><code class="lang-bash">&gt; kubectl apply -f service.yaml
service/hello-world created

&gt; kubectl get svc
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
hello-world   ClusterIP   10.100.25.211   &lt;none&gt;        8080/TCP   2m5s
</code></pre>
<p>Our service is exposed with type <code>ClusterIP</code>, which means it is only accessible inside the cluster. We did not expose using a service type LoadBalancer. Does our Kubernetes cluster even support a service type of LoadBalancer? It is very IaaS specific. In public cloud providers such as EKS/AKS/GKE, you get it out of the box. In VMware Tanzu distributions you get several options to choose from. In a local Minikube or kind cluster, the answer is probably "nope". In other distros - YMMV.</p>
<p>At any case - we don't want to expose our application using an external IP address. We're not animals. We want a nice, beautiful URL. </p>
<p>For that, we need an to use an Ingress.</p>
<h3 id="heading-adding-an-ingress">Adding an Ingress</h3>
<p>In order to create ingresses in Kubernetes, we need an  <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/">Ingress Controller</a> .</p>
<p>Who installs <em>that</em>? There are quite a few choices available, such as NGINX, Contour, HAProxy and many others. The choice of ingress is probably a decision that should not be made solely by the development team.</p>
<p>In our case, let's install Contour, since it's fast and lightweight:</p>
<pre><code class="lang-bash">kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
</code></pre>
<p>We now need to map our public or corporate DNS to point to the <code>External IP</code> that the ingress controller exposed. In case of Contour, we can find it with the following command:</p>
<pre><code class="lang-bash">&gt; kubectl get svc -n projectcontour
NAMESPACE             NAME     TYPE           CLUSTER-IP       EXTERNAL-IP                                                                        PORT(S)                      AGE
tanzu-system-ingress  envoy    LoadBalancer   10.100.101.15    ab915484edcec4e5ea4b532aa8a5340c-62fc50737cb73a64.elb.eu-west-2.amazonaws.com      80:30656/TCP,443:30358/TCP   2d4h
</code></pre>
<p>The External IP in my case is a host name since I'm running on AWS. For other providers, you'll probably get a real IP address.</p>
<p>We now need to tell our corporate domain or external domain registrar to map all traffic of certain addresses to the new ingress endpoint. We can have some fun and install an External DNS service in our cluster, which would even register the URLs for us, but for now we'll keep it simple. Let's map all *.apps.odedia.org to the hostname provided by AWS. I configured my domain registrar as follows:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640961490241/ifxgfkKF7.png" alt="image.png" /></p>
<p>Finally, we can create an <code>ingress.yaml</code> to describe our entry point. It's kind of a pain to use imperative commands with ingress so it's better to write the yaml ourselves. Contour also has a Custom Resource Definition called <code>HTTPProxy</code> which makes setting up routes easier, but we'll stick to the "standard" Ingress configuration for now:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world-ingress</span>
  <span class="hljs-attr">annotations:</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">rules:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">hello.apps.odedia.org</span>
    <span class="hljs-attr">http:</span>
      <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
        <span class="hljs-attr">backend:</span>
          <span class="hljs-attr">service:</span>
            <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
            <span class="hljs-attr">port:</span>
              <span class="hljs-attr">number:</span> <span class="hljs-number">8080</span>
</code></pre>
<p>We're telling the ingress that any request to <code>hello.apps.odedia.org</code> should be routed to the internal service <code>hello-world</code> that we created in the previous step.</p>
<p>Let's apply it:</p>
<pre><code class="lang-bash">&gt; kubectl apply -f ingress.yaml 
ingress.networking.k8s.io/hello-world-ingress created

&gt; kubectl get ingress    
NAME                  CLASS    HOSTS                   ADDRESS                                                                         PORTS   AGE
hello-world-ingress   &lt;none&gt;   hello.apps.odedia.org   ab915484edcec4e5ea4b532aa8a5340c-62fc50737cb73a64.elb.eu-west-2.amazonaws.com   80      8m59s
</code></pre>
<p>We <em>finally</em> have our application accessible through a web browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640968283035/r02vV3mrL.png" alt="image.png" /></p>
<p>Although... I don't like this <code>Not Secure</code> part in the address bar. I would love to have it served as <code>https://</code>, since the greeting is top secret and should probably be encrypted in transit. I need to setup a TLS secret to make my application run as expected. </p>
<p>Creating free TLS certificates can be done via  <a target="_blank" href="https://letsencrypt.org/">Let's Encrypt</a>. Obtaining the certificate is beyond the scope of this article, but suffice to say that you can use anything from a TXT record in your domain registrar, to automating the process by installing  <a target="_blank" href="https://cert-manager.io">cert-manager</a>  in your Kubernetes cluster.</p>
<h3 id="heading-reality-check">Reality check</h3>
<p>Remember that part in the beginning where we implemented our "Hello world" as a RestController? That was the only business logic we actually implemented so far. Everything else is just getting the plumbing to work.</p>
<h3 id="heading-securing-the-ingress-endpoint">Securing the ingress endpoint</h3>
<p>Ok, so we have a signed certificate, somehow. We have our private key and public cert.
Let's install it in our Kubernetes cluster:</p>
<pre><code class="lang-bash">&gt; kubectl create secret tls hello-world-tls --cert=fullchain.pem --key=privkey.pem
secret/hello-world-tls created
</code></pre>
<p>We need to update our ingress with the TLS secret reference. I'm also adding an annotation to force SSL redirection:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Ingress</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world-ingress</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">ingress.kubernetes.io/force-ssl-redirect:</span> <span class="hljs-string">"true"</span>
    <span class="hljs-attr">kubernetes.io/ingress.class:</span> <span class="hljs-string">contour</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">tls:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">secretName:</span> <span class="hljs-string">hello-world-tls</span>
    <span class="hljs-attr">hosts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">'hello.apps.odedia.org'</span>
  <span class="hljs-attr">rules:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">host:</span> <span class="hljs-string">hello.apps.odedia.org</span>
    <span class="hljs-attr">http:</span>
      <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">pathType:</span> <span class="hljs-string">Prefix</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">/</span>
        <span class="hljs-attr">backend:</span>
          <span class="hljs-attr">service:</span>
            <span class="hljs-attr">name:</span> <span class="hljs-string">hello-world</span>
            <span class="hljs-attr">port:</span>
              <span class="hljs-attr">number:</span> <span class="hljs-number">8080</span>
</code></pre>
<p>Et voilà!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640968066701/QfsC7r3re.png" alt="image.png" /></p>
<p>Just to be sure - we can see our connection is secure.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640968107202/r43BRHk5E.png" alt="image.png" /></p>
<h3 id="heading-were-just-getting-started">We're just getting started</h3>
<p>We finally did it. We deployed our application to Kubernetes. We're done!</p>
<p>Well, not really... Turns out that developing new features is a never-ending task.
We just got a new feature request to change the greeting to "Hello, everybody!". 
We now need to:</p>
<ul>
<li>Update our code</li>
<li>Build, tag and push a new image</li>
<li>Update and apply the deployment yaml</li>
</ul>
<p>Is that all?</p>
<p>Of course not. There are still <strong>a lot</strong> of things we need to consider. To name just a few:</p>
<ul>
<li>How do new deployments get triggered? Will it be on every new push to the OCI registry? </li>
<li>How will you reference the new image in your deployment? You need to have some sort of process to update the image reference with the new tag or its sha256 signature (please, <strong> <a target="_blank" href="https://vsupalov.com/docker-latest-tag/">don't use the <code>latest</code> tag</a> </strong>). Will you use a templating solution such as helm charts? Perhaps other solutions such as <code>ytt</code> and <code>kapp</code> from the  <a target="_blank" href="https://carvel.dev/">carvel</a>  toolset?</li>
<li>Come to think of it, what are we going to use for CI/CD? We have Jenkins, Azure Devops, Concourse, ArgoCD, Spinnaker, Tekton and many more. Each has its advantages and disadvantages. Tools like Jenkins and Concourse seem better at the CI part, since they are stateless and are not "Kubernetes aware". Tools like ArgoCD and Spinnaker make more sense for the CD part, since they are aware of what is currently in your clusters.</li>
<li>What happens when you have a sudden spike in traffic? Can your deployment autoscale? We hardly considered it with our current, simple deployment. Will it only autoscale based on CPU/Memory using a  <a target="_blank" href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">Horizontal Pod Autoscaler</a>, or will it be based on end-user experience and latency, using solutions such as  <a target="_blank" href="https://knative.dev">Knative</a>?</li>
<li>Our application is ridiculously simple. A real app will probably at the very least connect to a database, a cache or a message broker. How do you intend to connect to such backing services?  Who installs these backing services? How will you keep the credentials to those services safe?</li>
<li>How will you perform blue-green deployments? What happens if a release has bugs, do you have a rollback strategy?</li>
<li>How on earth are you going to debug this thing? Your applications should probably expose a debug port when testing against a local Kubernetes cluster during  <a target="_blank" href="https://thenewstack.io/kubernetes-infrastructure-know-the-inner-dev-loop/">inner loop development</a>, but disable it completely in other (pre-prod/production) scenarios.</li>
<li>While on the subject of inner loop - how fast can you iterate on your code changes? If you'll have to do the whole docker build-tag-push + update deployment dance for each code change, you won't get much coding done. You may be looking at tools such as  <a target="_blank" href="https://skaffold.dev/">Skaffold</a> ,  <a target="_blank" href="https://tilt.dev/">Tilt</a>  and/or  <a target="_blank" href="https://kustomize.io/">Kustomize</a> to improve iteration times, but you're still the one who has to write the configuration.</li>
<li>What is your multi-cluster strategy? Moving from dev environment, to test, to pre-prod and eventually production - each is probably a separate Kubernetes environment, perhaps on different cloud providers. How will you make sure everything is setup correctly to accept the same application (Ingress controllers, TLS certificates, service accounts, Knative(?) etc.)</li>
<li>How will you monitor the application? Will you use Prometheus and Grafana? Who's responsible to operate this service? Will you use a SaaS solution instead? How will you monitor application-specific KPIs such as HTTP Requests, logging levels, app-level environment variables etc.? These are not automatically exposed at a pod level.</li>
<li>Here's a good one: <strong>Who handles security patching</strong>? Imagine you have 500 microservices running in production, and suddenly a new  log4j-level exploit is released. How will you patch things as quickly and safely as possible? Moreover - how will you even <em>know</em> that your app is vulnerable?</li>
</ul>
<p>There's just... too many stuff. Things that must be done, but are really just "glue" to get some code out the door.</p>
<h3 id="heading-kubernetes-needs-a-developer-platform">Kubernetes needs a developer platform</h3>
<ul>
<li>Developers should <em>develop</em></li>
<li>They should focus on writing code, and thinking about their code logic</li>
<li>They care about connecting to third party services - their database, cache layer, message broker, legacy environment etc.</li>
<li>They don’t really care about infrastructure, until things stop working</li>
<li>They don’t really care about SSL certs, networking and all that jazz</li>
</ul>
<p>Obviously, this is nothing new. Many companies and open source projects are trying to solve the developer crisis on Kubernetes. There are solutions from  <a target="_blank" href="https://www.weave.works/">Weaveworks</a> ,  <a target="_blank" href="https://okteto.com/">Okteko</a> ,  <a target="_blank" href="https://www.getambassador.io/">Ambassador</a> ,  <a target="_blank" href="https://www.digitalocean.com/">Digital Ocean</a>,  <a target="_blank" href="https://docs.openshift.com/">Red Hat</a>  and many more. </p>
<p>At Pivotal (now  <a target="_blank" href="https://tanzu.vmware.com/tanzu">VMware Tanzu</a> ), our approach is somewhat unique. As the creators of Spring, we love the idea of  <a target="_blank" href="https://en.wikipedia.org/wiki/Inversion_of_control">inversion of control</a> . That principle is what allows Spring to autoconfigure itself to your needs without you having to tell it every single little tidbit. It's often referred to as the Hollywood principle: don't call us, we'll call you. 
<em>Convention over configuration</em> is a powerful feature, because most of the time, the default established conventions are exactly what you want. Wouldn't it be great to apply the same principals to a developer platform on Kubernetes?</p>
<p>The fact is, we <em>already solved</em> many of these challenges years ago by building a beloved, production-ready, enterprise-ready Platform-as-a-Service that is trusted by many Fortune 500 organizations. Pivotal Cloud Foundry (now  <a target="_blank" href="https://tanzu.vmware.com/application-service">Tanzu Application Service</a>) allows developers to focus on their code, just <code>cf push</code> their application to an automated environment, easily connect to backing services, and enjoy automatic security patching whenever the platform operator upgrades the platform to a newer version. Back in the day, our haiku was:</p>
<blockquote>
<p>Here’s my code</p>
<p>Run it on the cloud for me</p>
<p>I don’t care how</p>
</blockquote>
<p>I  <a target="_blank" href="https://odedia.org/comparing-kubernetes-to-pivotal-cloud-foundry-a-developers-perspective">compared the benefits</a>  of using Cloud Foundry instead of Kubernetes four years ago (that's <em>exactly</em> four years ago). </p>
<p>However, the landscape in our ever-changing industry has changed quite a bit during those four years:</p>
<ul>
<li>Kubernetes is the new IaaS. This was thought of as the future in 2017, but in 2022, it is just the present. Any container solution that doesn't use Kubernetes is losing points. Cloud Foundry is using  <a target="_blank" href="https://bosh.io">BOSH</a>  and runs an (excellent) container runtime called Diego. Customers want the new IaaS.</li>
<li>The reality is - sometimes, we <em>do</em> care how. Sometimes we want to dig deeper and make changes. If our company invested in a particular solution, we want to be able to integrate it easily into our developer platform. I'd say Cloud Foundry is 80% opinionated, 20% customizable, which is mind-blowing spectacular when you agree with those opinions. However, sometimes customers want a ratio that is skewed more towards the customizable part. </li>
<li>In Kubernetes - everything is Infrastructure as Code. Some aspects in the Cloud Foundry ecosystem are imperative only, such as creating a backing service with <code>cf create-service</code>. This requires a bit of scripting to get a new Cloud Foundry org/space to the desired state. You also can't <code>cf push</code> a git repo - you can only push a local directory.</li>
<li>IDE integration is good, but not spectacular. In order to debug a container running on the platform, developers needed to setup ssh tunneling using the <code>cf ssh</code> command. </li>
<li>Setting up a local Cloud Foundry environment for a developer requires booting a resource-intensive virtual machine, and it lacks 1:1 parity with a full-blown Cloud Foundry installation.</li>
<li>Working with multiple Cloud Foundry environments ("Foundations") is not a first-class feature. Developers basically need to login to env a, <code>cf push</code>, then login to env b, and <code>cf push</code> again. The full footprint of a Cloud Foundry installation is also rather resource-intensive, reaching around 10 virtual machines for a minimal installation.</li>
</ul>
<p>Still, nothing can compete with the magic of giving just source code to a platform, and see an application served as a secured URL at the other side of that command. The result is basically what we did in this article <em>and much more</em>, with just a single command - <code>cf push</code>.</p>
<h3 id="heading-looking-forward">Looking forward</h3>
<p>At Tanzu, we're building a developer platform for Kubernetes that tries to solve all these challenges and more. <a target="_blank" href="https://tanzu.vmware.com/application-platform">Tanzu Application Platform</a> is our modern, cloud-native, Kubernetes-native <em>Platform as a Service</em>. Note: TAP is currently beta, not all features described below will be available at GA.</p>
<p>In the past year, I've had the privilege to contribute my thoughts (and rants...) to a group of very smart people inside Tanzu from R&amp;D and the field, in order to make Tanzu Application Platform better and more aligned with real-world customer needs. </p>
<p>The idea is to take the same Inversion of Control concepts we love in Spring, and apply it to a developer platform for Kubernetes. </p>
<p>Also, if Kubernetes is indeed the new IaaS, we should be able to install the platform on any Kubernetes. This would remove the need for things like BOSH, for example.  <a target="_blank" href="https://tanzu.vmware.com/content/blog/the-value-of-bosh">BOSH exists</a>  because no common multi-cloud infrastructure API existed when Cloud Foundry was built, so we had to build it ourselves. Now that we have such API with Kubernetes, TAP can technically run on <em>any</em> Kubernetes distro - EKS, GKE, AKS, Tanzu Kubernetes Grid, vSphere with Tanzu, OpenShift, Rancher, <a target="_blank" href="https://tanzucommunityedition.io/">Tanzu Community Edition</a> on AWS/Azure/Docker, Minikube, kind etc. (<em>Disclaimer: not all Kubernetes distros are officially certified by VMware Tanzu</em>).</p>
<p>TAP relies heavily on the notion of <a target="_blank" href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/">Custom Resource Definitions</a>. This allows it to extend the Kubernetes API with additional features that would enable developers to interact with the platform at a higher level of abstraction. 
A platform operator only needs to install the platform once with a simple install command on a given Kubernetes cluster. The installation would take care of cross-cutting concerns such as configuring the Ingress, installing a container runtime such as Knative, setup automatic image building, scanning and signing, setup automatic build triggers on git commit, enable autoscaling of instances based on user activity, enable application monitoring and much more.
Moreover, since we're using standard Kubernetes resources here, a platform operator can take almost any of the opinionated components in the platform and replace them with something else. The operator gets to choose how much he or she wants to move that slider of 80% opinionated, 20% customizable. If I were to create a Haiku for TAP, it would probably be something like:</p>
<blockquote>
<p>Here’s my code</p>
<p>Run it on Kubernetes for me</p>
<p>Although my platform operator really cares how, I just want it to work</p>
</blockquote>
<p>Ok, maybe I need to work on it some more 😄. But you get the idea. You get to choose the level of exposure to the underpinnings of the platform as it fits your needs.</p>
<p>Here's a good example for this: TAP uses Knative as its runtime. You may decide you couldn't care less and just use the equivalent of <code>cf push</code> in TAP (which is <code>tanzu apps workload create</code>, by the way), but you may also decide you do care, and use the <code>kn</code> Knative cli to see what goes on with the Knative revisions under the hood. You can always just use <code>kubectl</code> to see the primitives themselves (Pods, ReplicaSets etc.), and TAP has a very nice UI to see all runtime resources. Also, if for some reason your organization chooses to avoid Knative, your platform operator may choose something else, such as a deployment yaml like we used in this article.</p>
<p>Here are some of the capabilities that TAP solves as a development platform for Kubernetes:</p>
<ul>
<li>Developers start with production-ready templates for their <em>source code</em>. An architect or project manager can expose to his team templates provided by Tanzu, or create his own, based on specific corporate requirements. You can also choose just how much customizations you want to expose to the developers. This removes a lot of overhead for "getting started", and also applies company-wide policies for what makes a good starting point for development.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640997533190/WvpYSNRCZ.png" alt="image.png" /></p>
<ul>
<li>Go from source to URL in 90 seconds. That's basically everything we implemented in this article. We took all the learnings from Cloud Foundry and implemented it in a Kubernetes-native paradigm. Cloud Native Buildpacks are still the secret sauce of open source that builds our production-ready images. </li>
<li>IDE integration. You can continue development against a live container running in a local or a remote Kubernetes cluster, including live updates based on any code change, and even remote debugging.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640998255067/i14z_n_Gib.png" alt="image.png" /></p>
<ul>
<li>Application-specific monitoring - HTTP requests, env variables, deployed configuration and Beans (in Spring), even change the logging level of a particular class at runtime if you want to.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640997801045/ALr4aoVUz.png" alt="image.png" /></p>
<ul>
<li>Multi-cluster support. Take an artifact from source to production, while moving the deployable artifact across different clusters. </li>
<li>Image security scanning, patching and signing. Cloud Native Buildpacks will patch the OS level and the middleware. Scanning and signing will also alert you on CVEs in your own code or dependencies, and optionally block deployment to the cluster if the CVE is critical.</li>
<li>The ability to perform service bindings to backing services - declare a desired access to a service such as RabbitMQ based on its name, and the platform will bind the service to your application. For some popular services, it will even auto-configure Spring to connect to the service with zero configuration. This makes your app 100% portable since no IPs or passwords are hanging around your source code (and potentially end up in your git repo by mistake).</li>
<li>Service binding can also be cross-cluster. Setup a services cluster for your message brokers and databases, and a workload cluster for your apps.</li>
<li>UI dashboards that display information about your apps, their runtime state, and even allow you to write wiki-style documentation for your microservices.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640997878761/YoL2ORXkH.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640997936233/8M_qgEjwj.png" alt="image.png" /></p>
<ul>
<li>Access to workshops developed in-house or by third parties, using a web-based workshop environment that gives every student access to a Kubernetes namespace and even web-based VSCode (Just bring a Chrome browser and an Internet connection).</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640998106440/7bShBnU3w.png" alt="image.png" /></p>
<p>I plan to create several getting started guides, tutorials and videos around TAP in the coming months, so be sure to subscribe for updates if you're interested.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Kubernetes was not my first choice as a developer. I always felt it is too low level for me to get things done. 
It seems that even the co-founder of Kubernetes agrees with this.  At Spring One 2021, <a target="_blank" href="https://youtu.be/2Qhj5u2bct0?t=493">Craig McLuckie said</a> that they never meant for developers to interact directly with the wall of YAML:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/2Qhj5u2bct0?start=493"></iframe>

<p>That is why I love Cloud Foundry so much. It allows me to focus on my code, and have the infrastructure get out of the way.</p>
<p>But there is a lot to be said on the benefits of using a ubiquitous infrastructure API. The benefits are massive. Vendor lock-in is basically non existent. Just use whatever fits your needs and your budget.</p>
<p>Creating a platform that is focused on developer productivity on Kubernetes lets you enjoy the best of both worlds. Developers can keep focusing on their code, platform operators can choose the best tech stack that matches their requirements, IT can operate low-level Kubernetes, and the security team can be alerted whenever a security issue may arise (and in many cases, automatically patch it).</p>
<h3 id="heading-epilogue">Epilogue</h3>
<p>So, how would I get the same results (and much more - for example automatic autoscaling based on HTTP traffic) as our example above, using Tanzu Application Platform? Here it is (The yaml is auto-generated and it not a part of the command):</p>
<pre><code class="lang-bash">&gt; tanzu apps workload create tanzu-java-web-app \
--git-repo https://github.com/odedia/spring-on-k8s \
--git-branch main --<span class="hljs-built_in">type</span> web --yes

Create workload:
      1 + |---
      2 + |apiVersion: carto.run/v1alpha1
      3 + |kind: Workload
      4 + |metadata:
      5 + |  labels:
      6 + |    apps.tanzu.vmware.com/workload-type: web
      7 + |  name: tanzu-java-web-app
      8 + |  namespace: default
      9 + |spec:
     10 + |  <span class="hljs-built_in">source</span>:
     11 + |    git:
     12 + |      ref:
     13 + |        branch: main
     14 + |      url: https://github.com/odedia/spring-on-k8s 

Created workload <span class="hljs-string">"tanzu-java-web-app"</span>
</code></pre>
<p>To get the URL, just ask for it:</p>
<pre><code class="lang-bash">&gt; tanzu apps workload get tanzu-java-web-app     
<span class="hljs-comment"># tanzu-java-web-app: Ready</span>
---
lastTransitionTime: <span class="hljs-string">"2022-01-01T01:56:14Z"</span>
message: <span class="hljs-string">""</span>
reason: Ready
status: <span class="hljs-string">"True"</span>
<span class="hljs-built_in">type</span>: Ready

Workload pods
NAME                                                   STATE       AGE
tanzu-java-web-app-00002-deployment-6f4cd7cb8c-w9shd   Running     66s
tanzu-java-web-app-build-1-build-pod                   Succeeded   2m16s
tanzu-java-web-app-config-writer-6wllp-pod             Succeeded   92s

Workload Knative Services
NAME                 READY     URL
tanzu-java-web-app   Ready   http://tanzu-java-web-app-default.apps.odedia.org
</code></pre>
<p>For more information on Tanzu Application Platform, check out the  <a target="_blank" href="https://network.pivotal.io/products/tanzu-application-platform/">download page</a>  and the  <a target="_blank" href="https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/0.4/tap/GUID-overview.html">docs</a>. </p>
<p><em>Special thanks to Thomas Risberg, Timo Salm and Lekha Pillai for providing feedback on this article</em></p>
]]></content:encoded></item><item><title><![CDATA[About Me]]></title><description><![CDATA[Hi there! I’m Oded.
I work for VMware Tanzu by Broadcom as a principal solutions architect and advocate for Spring, focused on cloud native platform such as Cloud Foundry and Tanzu Platform for Kubernetes.
I helped design large-scale, cloud-native so...]]></description><link>https://odedia.org/about-me</link><guid isPermaLink="true">https://odedia.org/about-me</guid><category><![CDATA[introduction]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Fri, 17 Dec 2021 20:15:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639776509689/eigFf0ENx.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi there! I’m Oded.</p>
<p>I work for VMware Tanzu by Broadcom as a principal solutions architect and advocate for Spring, focused on cloud native platform such as Cloud Foundry and Tanzu Platform for Kubernetes.</p>
<p>I helped design large-scale, cloud-native solutions running on Cloud Foundry and Kubernetes, using Spring Boot and Spring Cloud.</p>
<p>I’m highly skilled in Kubernetes, Java, Spring Boot, Spring Cloud, Apache Kafka, Event Sourcing and DevOps practices.</p>
<p>I enjoy photography, video editing, and the music of the King of Pop.</p>
]]></content:encoded></item><item><title><![CDATA[CNCF TLV Meetup on Buildpacks: Continuously patched images in your OCI registry [Hebrew]]]></title><description><![CDATA[Check out this talk I gave with my colleague Kobi Shamama on Cloud Native Buildpacks and how they can enable a secured software supply chain.]]></description><link>https://odedia.org/cncf-tlv-meetup-on-buildpacks-continuously-patched-images-in-your-oci-registry-hebrew</link><guid isPermaLink="true">https://odedia.org/cncf-tlv-meetup-on-buildpacks-continuously-patched-images-in-your-oci-registry-hebrew</guid><category><![CDATA[Docker]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Wed, 02 Sep 2020 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639828366839/uDNoXMlmY.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Check out this talk I gave with my colleague Kobi Shamama on Cloud Native Buildpacks and how they can enable a secured software supply chain. </p>
<p><a target="_blank" href="https://youtu.be/qYu-U7NxIN0/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639773197124/C4LAMi3lC.jpeg" alt="CNCF Talk" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Go OSS 2019 Presentation - Concept to production in 20 minutes [Hebrew]]]></title><description><![CDATA[I gave this talk at the Go OSS conference in 2019, where I showed how Pivotal Cloud Foundry can get you from code to production in a matter of minutes.]]></description><link>https://odedia.org/go-oss-2019-presentation-concept-to-production-in-20-minutes-hebrew</link><guid isPermaLink="true">https://odedia.org/go-oss-2019-presentation-concept-to-production-in-20-minutes-hebrew</guid><category><![CDATA[PaaS]]></category><category><![CDATA[containers]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Fri, 19 Jun 2020 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639775100501/25gbeGb3W.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I gave this talk at the Go OSS conference in 2019, where I showed how Pivotal Cloud Foundry can get you from code to production in a matter of minutes.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/n82T-s8gPyk"></iframe>
]]></content:encoded></item><item><title><![CDATA[Running Tanzu Application Service on Tanzu Kubernetes Grid]]></title><description><![CDATA[In this video, I walk you through the process of setting up Tanzu Kubernetes Grid and installing a (now deprecated) public beta of Tanzu Application Service for Kubernetes]]></description><link>https://odedia.org/running-tanzu-application-service-on-tanzu-kubernetes-grid</link><guid isPermaLink="true">https://odedia.org/running-tanzu-application-service-on-tanzu-kubernetes-grid</guid><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[containers]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Fri, 08 May 2020 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639773670597/lklJW1kyZ.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this video, I walk you through the process of setting up Tanzu Kubernetes Grid and installing a (now deprecated) public beta of Tanzu Application Service for Kubernetes</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/66K6MkXdNoE"></iframe>]]></content:encoded></item><item><title><![CDATA[Deliver a modern software supply chain with VMware Tanzu]]></title><description><![CDATA[Checkout an overview of the Tanzu Portfolio, presented by myself and my colleague, Kobi Shamama.


Here is a version of this video in Hebrew:]]></description><link>https://odedia.org/deliver-a-modern-software-supply-chain-with-vmware-tanzu</link><guid isPermaLink="true">https://odedia.org/deliver-a-modern-software-supply-chain-with-vmware-tanzu</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Tue, 28 Jan 2020 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639772577959/eXfC5LzzL.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Checkout an overview of the Tanzu Portfolio, presented by myself and my colleague, Kobi Shamama.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/rkCh5s8RdzY"></iframe>

<p>Here is a version of this video in Hebrew:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ghmWfVJZLj0"></iframe>

]]></content:encoded></item><item><title><![CDATA[Microservices are for humans, not machines]]></title><description><![CDATA[I gave this talk at Spring One Platform 2019 in Austin, TX.]]></description><link>https://odedia.org/microservices-are-for-humans-not-machines</link><guid isPermaLink="true">https://odedia.org/microservices-are-for-humans-not-machines</guid><category><![CDATA[Microservices]]></category><category><![CDATA[Devops]]></category><category><![CDATA[DDD]]></category><category><![CDATA[Cloud Computing]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Tue, 15 Oct 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639773813657/_RwC_mTeE.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I gave this talk at Spring One Platform 2019 in Austin, TX. </p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/D7uk1PJqk1o"></iframe>]]></content:encoded></item><item><title><![CDATA[From Concept to Production in 30 Minutes]]></title><description><![CDATA[I gave this talk at a meetup we had with Harel Insurance in Israel, where I showed how one can take an idea from concept to production in a very short timeframe when using a Platform as a Service. 
Our special guest was Spring Boot and Spring Cloud c...]]></description><link>https://odedia.org/from-concept-to-production-in-30-minutes</link><guid isPermaLink="true">https://odedia.org/from-concept-to-production-in-30-minutes</guid><category><![CDATA[serverless]]></category><category><![CDATA[PaaS]]></category><category><![CDATA[containers]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Wed, 31 Jul 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639774189888/n0DvGTL_o.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I gave this talk at a meetup we had with Harel Insurance in Israel, where I showed how one can take an idea from concept to production in a very short timeframe when using a Platform as a Service. </p>
<p>Our special guest was Spring Boot and Spring Cloud creator Dave Syer. He discussed Spring Cloud Function and Serverless spring.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/VGcbi8PaC1g"></iframe>

<p>Dr. Syer's talk is available here:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/BV-YjxNsIIw"></iframe>]]></content:encoded></item><item><title><![CDATA[Restoring an Entire PCF Platform with Automation]]></title><description><![CDATA[In this video I show how one can completely repave an entire Pivotal Cloud Foundry environment using Concourse automation. I even use Siri to kick a new upgrade to the platform!]]></description><link>https://odedia.org/restoring-an-entire-pcf-platform-with-automation</link><guid isPermaLink="true">https://odedia.org/restoring-an-entire-pcf-platform-with-automation</guid><category><![CDATA[automation]]></category><category><![CDATA[Devops]]></category><category><![CDATA[infrastructure]]></category><category><![CDATA[PaaS]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Wed, 22 May 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639775331169/jh-_3kAEW.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this video I show how one can completely repave an entire Pivotal Cloud Foundry environment using Concourse automation. I even use Siri to kick a new upgrade to the platform!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/B1FQYTedAEM"></iframe>]]></content:encoded></item><item><title><![CDATA[Pivotal Cloud Foundry on Azure - the Easy Way]]></title><description><![CDATA[In this tutorial I'll show you the fastest way to get an evaluation of Pivotal Cloud Foundry up and running.
If you want get a deeper understanding of how the Pivotal Cloud Foundry installation works, including the use of BOSH, Ops Manager and the ne...]]></description><link>https://odedia.org/pivotal-cloud-foundry-on-azure-the-easy-way</link><guid isPermaLink="true">https://odedia.org/pivotal-cloud-foundry-on-azure-the-easy-way</guid><category><![CDATA[Azure]]></category><category><![CDATA[PaaS]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Thu, 25 Apr 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771373750/QjbSH_Hxr.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this tutorial I'll show you the fastest way to get an evaluation of Pivotal Cloud Foundry up and running.</p>
<p>If you want get a deeper understanding of how the Pivotal Cloud Foundry installation works, including the use of BOSH, Ops Manager and the networking you need to setup, have a look at <a target="_blank" href="{% post_url /articles/2019-4-1-pcf-on-azure-the-hard-way %}">PCF on Azure - the Hard Way</a>.</p>
<p>Go to the <a target="_blank" href="https://azuremarketplace.microsoft.com/en-us/marketplace/apps/pivotal.pivotal-cloud-foundry?tab=Overview">Pivotal Cloud Foundry on Microsoft Azure</a> page and click <code>Get it now</code> followed by <code>Continue</code> and then <code>Create</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771373750/QjbSH_Hxr.png" alt="get-it-now.png" />
p&gt;</p><p></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771389154/Y0si-JTFS.png" alt="pcf-on-azure.png" /></p>
<p></p>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771400488/uam8VrNT0.png" alt="create-pcf-on-azure.png" /></p>
<p>You need to provide 5 parameters for the installation:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771417133/v9mTjsjt1.png" alt="pcf-on-azure-params.png" /></p>
<p>Run the following command on a linux machine:</p>
<pre><code>mkdir <span class="hljs-operator">~</span><span class="hljs-operator">/</span>certs
ssh<span class="hljs-operator">-</span>keygen <span class="hljs-operator">-</span>t rsa <span class="hljs-operator">-</span>b <span class="hljs-number">2048</span> <span class="hljs-operator">-</span>f <span class="hljs-operator">~</span><span class="hljs-operator">/</span>certs<span class="hljs-operator">/</span>pcf<span class="hljs-operator">-</span>on<span class="hljs-operator">-</span>azure <span class="hljs-operator">-</span>P <span class="hljs-string">""</span>
</code></pre><p>Copy the contents of <code>~/certs/pcf-on-azure.pub</code> to the field <code>SSH public key</code>. Keep the file <code>~/certs/pcf-on-azure</code> in a secured place for future reference.</p>
<p>You will need a Service Principal JSON file for the <code>Service Principal</code> field. The fields in the JSON are as follows:</p>
<pre><code>{
  <span class="hljs-attr">"subscriptionID"</span>: <span class="hljs-string">"SUBSCRIPTION-ID"</span>,
  <span class="hljs-attr">"tenantID"</span>: <span class="hljs-string">"TENANT-ID"</span>,
  <span class="hljs-attr">"clientID"</span>: <span class="hljs-string">"SERVICE-PRINCIPAL-NAME"</span>,
  <span class="hljs-attr">"clientSecret"</span>: <span class="hljs-string">"SERVICE-PRINCIPAL-PASSWORD"</span>
}
</code></pre><p>If you don't have it yet, install the Azure CLI from https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest</p>
<p>Login with the <code>az</code> cli:</p>
<pre><code>az <span class="hljs-keyword">login</span>
</code></pre><p>Confirm the login in the pop-up web page. Once done you will see a list fo accounts in the command prompt.</p>
<p>Review the list of accounts by running:</p>
<pre><code>az account <span class="hljs-keyword">list</span>
</code></pre><p>Important: If you have multiple accounts, make sure you set the default to the account you want to use for the installation! The default account is marked as <code>isDefault</code> . You can change it using:</p>
<pre><code>az account set <span class="hljs-operator">-</span>s <span class="hljs-operator">&lt;</span>id<span class="hljs-operator">-</span>field<span class="hljs-operator">-</span><span class="hljs-keyword">from</span><span class="hljs-operator">-</span>response<span class="hljs-operator">-</span>json<span class="hljs-operator">&gt;</span>
</code></pre><p>You now have two out of the four required fields:</p>
<ul>
<li>The <code>id</code> from <code>az account list</code> is the <code>subscriptionID</code>. Don't ask me why, I'm sure Microsoft had a good reason :)</li>
<li>The <code>tenantId</code> from <code>az account list</code> is the <code>tenantID</code> field. Make sure that in the latter it is <code>ID</code>, not <code>Id</code>.</li>
</ul>
<p>We now need to create a service principal for the installation.</p>
<p>First, save some environment variables for later use:</p>
<pre><code>CLIENT_SECRET<span class="hljs-operator">=</span><span class="hljs-operator">&lt;</span>choose a secure secret<span class="hljs-operator">&gt;</span>
USER_ID<span class="hljs-operator">=</span><span class="hljs-operator">&lt;</span>choose a application user id, <span class="hljs-keyword">for</span> example Oded<span class="hljs-operator">&gt;</span>
SUBSCRIPTION_ID<span class="hljs-operator">=</span><span class="hljs-operator">&lt;</span>id field <span class="hljs-keyword">from</span> response json above <span class="hljs-keyword">while</span> running az account list<span class="hljs-operator">&gt;</span>
TENANT_ID<span class="hljs-operator">=</span><span class="hljs-operator">&lt;</span>tenantId field <span class="hljs-keyword">from</span> response json above <span class="hljs-keyword">while</span> running az account list<span class="hljs-operator">&gt;</span>
</code></pre><p><strong>Important</strong>: Choose a CLIENT_SECRET that is at least 40 characters long and contains alphanumeric characters, digits and at least one underscore. Otherwise the deployment will later fail on password validation.</p>
<p>Now, create an active directory application:</p>
<pre><code>az ad app create \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>display<span class="hljs-operator">-</span>name <span class="hljs-string">"Service Principal for BOSH"</span> \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>password $CLIENT_SECRET \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>homepage <span class="hljs-string">"http://BOSHAzureCPI"</span> \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>identifier<span class="hljs-operator">-</span>uris <span class="hljs-string">"http://${USER_ID}BOSHAzureCPI"</span>
</code></pre><blockquote>
<p>(Tip: if you ever need to delete the AAD use this: <code>az ad app delete --id "http://${USER_ID}BOSHAzureCPI"</code>)</p>
</blockquote>
<p>Create a service principal from the AAD:</p>
<pre><code>az ad sp create <span class="hljs-operator">-</span><span class="hljs-operator">-</span>id `az ad app show <span class="hljs-operator">-</span><span class="hljs-operator">-</span>id http:<span class="hljs-comment">//${USER_ID}BOSHAzureCPI | jq -r .appId`</span>
</code></pre><p>Wait for about 30 seconds for Azure active directory updates to progapate, then add the contributor role:</p>
<pre><code><span class="hljs-attribute">az</span> role assignment create --assignee http://<span class="hljs-variable">${USER_ID}</span>BOSHAzureCPI --role <span class="hljs-string">"Contributor"</span> --scope /subscriptions/<span class="hljs-variable">${SUBSCRIPTION_ID}</span>
</code></pre><p>Login with the Application user:</p>
<pre><code>az login \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>username `az ad app show <span class="hljs-operator">-</span><span class="hljs-operator">-</span>id http:<span class="hljs-comment">//${USER_ID}BOSHAzureCPI | jq -r .appId` \</span>
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>password $CLIENT_SECRET \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>service<span class="hljs-operator">-</span>principal \
<span class="hljs-operator">-</span><span class="hljs-operator">-</span>tenant ${TENANT_ID}
</code></pre><p>Enable compute, netowrk and storage access:</p>
<pre><code>az provider register <span class="hljs-operator">-</span><span class="hljs-operator">-</span>namespace Microsoft.Storage
az provider register <span class="hljs-operator">-</span><span class="hljs-operator">-</span>namespace Microsoft.Network
az provider register <span class="hljs-operator">-</span><span class="hljs-operator">-</span>namespace Microsoft.Compute
</code></pre><p>Logout of the application user and login again to your regular azure account:</p>
<pre><code>az logout
az <span class="hljs-keyword">login</span>
</code></pre><p>You now have the two remaining fields:</p>
<ul>
<li>The ClientID can be extracted by running <code>az ad app show --id http://${USER_ID}BOSHAzureCPI | jq -r .appId</code></li>
<li>The ClientSecret is the value of $CLIENT_SECRET you set above.</li>
</ul>
<p>A final JSON might look like this:</p>
<pre><code>{
  <span class="hljs-attr">"subscriptionID"</span>: <span class="hljs-string">"24A7CC81-402C-44EB-8DFE-96A8DB670E37"</span>,
  <span class="hljs-attr">"tenantID"</span>: <span class="hljs-string">"BF7500C3-A3D4-411F-AA5B-606CBE903FC4"</span>,
  <span class="hljs-attr">"clientID"</span>: <span class="hljs-string">"23536697-C5F6-49DD-9E2F-68D36FFDCA22"</span>,
  <span class="hljs-attr">"clientSecret"</span>: <span class="hljs-string">"My-Super-Secret-Secret"</span>
}
</code></pre><p>Save this file as <code>azure-creds.json</code>, then upload it to the web page under the <code>Service Principal</code> field.</p>
<p>Under "Resource group", choose "Create new" and name the resource group "pcf".</p>
<p>Choose a Location you want to deploy to.</p>
<p>Until <code>Pivotal Network Token</code>, enter your Pivotal Network token from Pivnet at <a target="_blank" href="network.pivotal.io">network.pivotal.io</a>. You can find (once logged in) under your <code>User Name --&gt; Edit Profiles --&gt; Request New Refresh Token</code>. It's best to keep this token in a secured place like LastPass and not recreate it every time you need to.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771428878/tc-aOyQhy.png" alt="pivnet-token.png" /></p>
<p>Before continuing, make sure you accepted the EULAs for the links below at Pivnet. In each of the following links, you will be prompted to accept an End-User License Agreement. Once you accepted the EULA, a download will begin, but you can immediatly cancel it. You account is now linked to accepting the EULA and so the installation from Azure can download the files.</p>
<p>https://network.pivotal.io/products/ops-manager/ (Choose 
latest 2.4 version and then download <code>Pivotal Cloud Foundry Ops Manager YAML for Azure</code>. Do the same for the latest 2.5 version)
https://network.pivotal.io/products/elastic-runtime/ (Choose latest 2.4 version and then download Small Footprint PAS. Do the same for the latest 2.5)
https://network.pivotal.io/products/azure-service-broker/ (Choose latest version and then download Microsoft Azure Service Broker)</p>
<p>You now have all required data for installation:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771443836/dYznPDbrr.png" alt="final-screen.png" /></p>
<p>Click "OK". In the next page, wait for the validation to complete and click "OK" again. In the last screen, click "Create".</p>
<p>Your deployment will now begin. You can track the progress under <code>Resource Groups --&gt; pcf --&gt; Deployments</code>. You will receive a notification in the Azure portal when the Deployment was successful, however note that there are additional installations done after the deployment is complete.</p>
<p>When the deployment is done, click on the deployment <code>pivotal.pivotal-cloud-foundry-....</code> and then <code>Outputs</code>. You will see the login URL and password for Ops Manager:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771457466/tMQTSrRTn.png" alt="outputs.png" /></p>
<p>Go to the URL listed under <code>opsMan-FQDN</code>. The username is <code>admin</code> and the password is located under <code>Outputs--&gt;password</code>.</p>
<p>You can now track the progress of the installation in Ops Manager.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771466543/8btLFDkrs.png" alt="apply-changes.png" /></p>
<p></p>



<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771477743/-3PHuMKDq.png" alt="progress.png" /></p>
<p>When the BOSH Direcor installation completes, it might take up to 1/2 an hour for the PAS installation to continue. You can track the progress under <code>Resource Group --&gt; Activity Log</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771486466/9gItcWkxl.png" alt="activity-log.png" /></p>
]]></content:encoded></item><item><title><![CDATA[Get KUBECONFIG access token in PKS from Jenkins]]></title><description><![CDATA[In many enterprises, the idea of letting developers get access to kubectl is blasphemy. kubectl is a powerful CLI and if you don't have proper RBAC setup, you can do some serious damage. The path many enterprises take is to have pipelines do all the ...]]></description><link>https://odedia.org/get-kubeconfig-access-token-in-pks-from-jenkins</link><guid isPermaLink="true">https://odedia.org/get-kubeconfig-access-token-in-pks-from-jenkins</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Jenkins]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Tue, 23 Apr 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771199469/-hXXFLGsk.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In many enterprises, the idea of letting developers get access to <code>kubectl</code> is blasphemy. <code>kubectl</code> is a powerful CLI and if you don't have proper RBAC setup, you can do some serious damage. The path many enterprises take is to have pipelines do all the deployment to the kubernetes cluster.</p>
<p>Still, developers do require access to some aspects of the Kubernetes lifecycle, mainly the Kubernetes dashboard. However, that dashboard requires an access token that is found in the KUBECONFIG file.</p>
<p>In PKS, it is possible to get the KUBECONFIG file that includes the access token by using the <code>pks</code> cli, but if <code>kubectl</code> is off limits, the <code>pks</code> cli is <strong>definetly</strong> off limits. </p>
<p>The solution? Run a script that talks to the UAA backend to get the access token. Pivotal and VMware provide a script under this <a target="_blank" href="https://community.pivotal.io/s/article/script-to-automate-generation-of-the-kubeconfig-for-the-kubernetes-user">community support article</a>.</p>
<p>However, even that can be a challenge for many enterprises. Windows is still king, and running shell scripts is not possible, let alone installation of additional utilities such as <code>jq</code>.</p>
<p>The solution? Get the access token from a Jenkins job that developers already have access to in their daily work.</p>
<p>Let's create a new Jenkins Freestyle job:</p>
<p><img src="{{page.base_url}}/assets/images/jenkins-access-token/new-project.png" alt /></p>
<p>Check the box to <code>Discard old builds</code> and set <code>Max # of builds to keep</code> to 0. You don't want to keep a history of the access tokens.</p>
<p>Check <code>This project is parameterized</code> and add the following parameters:</p>
<p><img src="{{page.base_url}}/assets/images/jenkins-access-token/parameters.png" alt /></p>
<p>Make sure that <code>PKS_PASSWORD_RAW</code> is set to type <code>Password Parameter</code>.</p>
<p>Under <code>Build</code> choose <code>Execute shell</code>:</p>
<p><img src="{{page.base_url}}/assets/images/jenkins-access-token/shell.png" alt /></p>
<p>Paste the following script. It has been modified from the original so it would not require <code>jq</code>:</p>
<pre><code><span class="hljs-meta">#!/bin/bash -e</span>
<span class="hljs-comment"># v 0.0.5.1</span>

<span class="hljs-comment"># get-pks-k8s-config.sh</span>
<span class="hljs-comment"># gmerlin@vmware.com</span>
<span class="hljs-comment"># adapted for Jenkins by Oded Shopen (odedia.org)</span>

<span class="hljs-function"><span class="hljs-title">urlencode</span></span>() {
    <span class="hljs-built_in">local</span> l=<span class="hljs-variable">${#1}</span>
    <span class="hljs-keyword">for</span> (( i = 0 ; i &lt; l ; i++ )); <span class="hljs-keyword">do</span>
        <span class="hljs-built_in">local</span> c=<span class="hljs-variable">${1:i:1}</span>
        <span class="hljs-keyword">case</span> <span class="hljs-string">"<span class="hljs-variable">$c</span>"</span> <span class="hljs-keyword">in</span>
            [a-zA-Z0-9.~_-]) <span class="hljs-built_in">printf</span> <span class="hljs-string">"<span class="hljs-variable">$c</span>"</span> ;;
            <span class="hljs-string">' '</span>) <span class="hljs-built_in">printf</span> + ;;
            *) <span class="hljs-built_in">printf</span> <span class="hljs-string">'%%%.2X'</span> <span class="hljs-string">"'<span class="hljs-variable">$c</span>"</span>
        <span class="hljs-keyword">esac</span>
    <span class="hljs-keyword">done</span>
}


PKS_PASSWORD=$(urlencode <span class="hljs-variable">$PKS_PASSWORD_RAW</span>)

<span class="hljs-comment"># Collect Tokens from UAA</span>
CURL_CMD=<span class="hljs-string">"curl 'https://<span class="hljs-variable">${PKS_API}</span>:8443/oauth/token' -sk -X POST -H 'Accept: application/json' -d \"client_id=pks_cluster_client&amp;client_secret=\"\"&amp;grant_type=password&amp;username=<span class="hljs-variable">${PKS_USER}</span>&amp;password=\"<span class="hljs-variable">${PKS_PASSWORD}</span>\"&amp;response_type=id_token\""</span>

TOKENS=$(<span class="hljs-built_in">eval</span> <span class="hljs-variable">$CURL_CMD</span> | python -c <span class="hljs-string">"import sys, json; print json.load(sys.stdin)['id_token']"</span> ) 
<span class="hljs-built_in">echo</span> -e <span class="hljs-string">"\n"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-variable">$TOKENS</span>
<span class="hljs-built_in">echo</span> -e  <span class="hljs-string">"\n"</span>
</code></pre><p>Save the job.</p>
<p>While running the job, provide all required parameters:</p>
<p><img src="{{page.base_url}}/assets/images/jenkins-access-token/build.png" alt /></p>
<p>The output will present your access token:</p>
<p><img src="{{page.base_url}}/assets/images/jenkins-access-token/console.png" alt /></p>
<p>Good luck!</p>
]]></content:encoded></item><item><title><![CDATA[CIFS/SMB FlexVolume driver for PKS]]></title><description><![CDATA[I've recently had a customer request to access an SMB server from their PKS Kubernetes cluster. SMB is still incredibly popular in Enterprises, often used as part of a "whitelisting" process.
Still, being Kubernetes, it would be nice to treat an SMB ...]]></description><link>https://odedia.org/cifssmb-flexvolume-driver-for-pks</link><guid isPermaLink="true">https://odedia.org/cifssmb-flexvolume-driver-for-pks</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[networking]]></category><category><![CDATA[Cloud Computing]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Tue, 16 Apr 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771007557/EFQuthsmt.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've recently had a customer request to access an SMB server from their PKS Kubernetes cluster. SMB is still incredibly popular in Enterprises, often used as part of a "whitelisting" process.</p>
<p>Still, being Kubernetes, it would be nice to treat an SMB share as yet another Persistent Volume and abstract away the protocol being used.</p>
<p>I've found a few solutions for this online, but the only one that seemed valuable and from a well known vendor was the <a target="_blank" href="https://github.com/Azure/kubernetes-volume-drivers">FlexVolume for Azure by Microsoft</a>. This project exposes several Azure services as PersistentVolumes to Kubernetes.</p>
<p>The drawback of this project is that it was developed specifically for Azure and AKS (Azure Kubernetes Service).</p>
<p>The biggest issue was that the installation required you to install specific binaries on the worker nodes, specifically <code>jq</code>. While that may be a feasible outcome on AKS, it was not a realistic approach for PKS. In PKS, BOSH manages the Worker nodes, patches them, upgrades then or just repaves (i.e. resets) them to a known state. Therefore, any "snowflake" binary installation on the working nodes would disappear on the next upgrade cycle.</p>
<p>I've been working with my collegue Stuart Charlton (<a target="_blank" href="https://twitter.com/svrc?lang=en">@svrc</a>) to come up with a solution, and we came up with <a target="_blank" href="https://github.com/odedia/kubernetes-volume-drivers/tree/master/flexvolume/smb">this fork</a>.</p>
<p>It differs from the upstream Azure project in the following ways:</p>
<ol>
<li><code>hostPath</code> settings are set to match their expected location on PKS.</li>
<li><code>jq</code> is being installed as part of the DameonSet deployment to Kubernetes, so no prior installation is required. Upon upgrade/resize of the cluster, each new/upgraded node would run the DameonSet again and install <code>jq</code> again. Microsoft states that  <code>cifs-utils</code> is another required binary but it is included in the PKS distribution so it already exists on the worker nodes.</li>
</ol>
<p>I tested this on a PKS 1.3 cluster. I was able to resize, repave and upgrade the cluster, and the SMB persistent volume was still there waiting for me.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You will need a Kubernetes clusters where <code>Privileged Containers</code> are enabled. Under your Plan settings in Ops Manager, check the box for <code>Enable Privileged Containers - Use with caution</code> and <code>Disable DenyEscalatingExec</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639770917905/nFGCHVO4W.png" alt="privileged-containers.png" /></p>
<p>You will need an Egress network policy in your Kubernetes Cluster to allow outbound traffic on port 445 (SMB). If you're running PKS on-premise with NSX-T, it's as easy as applying a Network Policy yaml. For example:</p>
<pre><code><span class="hljs-meta">---</span>
<span class="hljs-attr">apiVersion:</span> <span class="hljs-string">networking.k8s.io/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">NetworkPolicy</span>
<span class="hljs-attr">metadata:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">smb-network-policy</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">default</span>
<span class="hljs-attr">spec:</span>
    <span class="hljs-attr">podSelector:</span>
      <span class="hljs-attr">matchLabels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">nginx-flex-smb</span>
  <span class="hljs-attr">policyTypes:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Egress</span>
  <span class="hljs-attr">egress:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">to:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">ipBlock:</span>
        <span class="hljs-attr">cidr:</span> <span class="hljs-string">&lt;your</span> <span class="hljs-string">target</span> <span class="hljs-string">IP</span> <span class="hljs-string">Block&gt;/32</span>
    <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">protocol:</span> <span class="hljs-string">TCP</span>
      <span class="hljs-attr">port:</span> <span class="hljs-number">445</span>
</code></pre><p>Persistent Volumes also require a default storage class. If you're running PKS on vSphere, you can create the default storage class with the yaml below:</p>
<pre><code>  kind: StorageClass
  apiVersion: <span class="hljs-keyword">storage</span>.k8s.io/v1
  metadata:
    name: thin
    annotations:
      storageclass.kubernetes.io/<span class="hljs-keyword">is</span><span class="hljs-operator">-</span>default<span class="hljs-operator">-</span>class: <span class="hljs-string">"true"</span>
  provisioner: kubernetes.io/vsphere<span class="hljs-operator">-</span>volume
  parameters:
      diskformat: thin
</code></pre><p>Apply the Storage Class:</p>
<pre><code>kubectl apply <span class="hljs-operator">-</span>f <span class="hljs-keyword">storage</span><span class="hljs-operator">-</span>class<span class="hljs-operator">-</span>vsphere.yml
</code></pre><h2 id="heading-how-to-install">How to Install</h2>
<p>Create a DaemonSet with the FlexVolume driver. A DaemonSet is a pod that runs on each one of the worker nodes in Kubernetes.</p>
<pre><code>kubectl apply -f <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/raw.githubusercontent.com/odedia</span><span class="hljs-regexp">/kubernetes-volume-drivers/master</span><span class="hljs-regexp">/flexvolume/smb</span><span class="hljs-regexp">/deployment/smb</span>-flexvol-installer.yaml
</code></pre><p>Check DaemonSet status:</p>
<pre><code>watch kubectl describe daemonset smb<span class="hljs-operator">-</span>flexvol<span class="hljs-operator">-</span>installer <span class="hljs-operator">-</span><span class="hljs-operator">-</span>namespace<span class="hljs-operator">=</span>kube<span class="hljs-operator">-</span>system
watch kubectl get po <span class="hljs-operator">-</span><span class="hljs-operator">-</span>namespace<span class="hljs-operator">=</span>kube<span class="hljs-operator">-</span>system <span class="hljs-operator">-</span>o wide
</code></pre><p>Once you see your pods in a <code>Running</code> state, you can continue.</p>
<p>Create a Kubernetes <code>secret</code> to hold your SMB credentials:</p>
<pre><code>kubectl create secret generic smbcreds <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-keyword">from</span><span class="hljs-operator">-</span>literal username<span class="hljs-operator">=</span>USERNAME <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-keyword">from</span><span class="hljs-operator">-</span>literal password<span class="hljs-operator">=</span><span class="hljs-string">"PASSWORD"</span> <span class="hljs-operator">-</span><span class="hljs-operator">-</span><span class="hljs-keyword">type</span><span class="hljs-operator">=</span><span class="hljs-string">"microsoft.com/smb"</span>
</code></pre><p>Replace USERNAME and PASSWORD with your credentials.</p>
<h2 id="heading-running-a-pod-with-direct-mount">Running a pod with direct mount</h2>
<p>You can define the SMB access on the pod definition itself, or via a persistent volume claim. For direct configuration, you will need to define the <code>volumes</code> section of your <code>Pod</code> definition. Here's  a sample Pod you can use for testing:</p>
<pre><code><span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/raw.githubusercontent.com/odedia</span><span class="hljs-regexp">/kubernetes-volume-drivers/master</span><span class="hljs-regexp">/flexvolume/smb</span><span class="hljs-regexp">/nginx-flex-smb.yaml</span>
</code></pre><p>Edit the yaml contents and replace the <code>source</code> line with the address of your SMB server and shared folder.</p>
<p>Once done, apply the yaml:</p>
<pre><code>kubectl apply <span class="hljs-operator">-</span>f nginx<span class="hljs-operator">-</span>flex<span class="hljs-operator">-</span>smb.yaml
</code></pre><h2 id="heading-running-a-pod-with-mount-via-persistent-volume-claim">Running a pod with mount via Persistent Volume Claim</h2>
<p>You can setup the SMB share via a Persistent Volume Claim (PVC). </p>
<p>Download the Persistent Volume (PV) yaml:</p>
<pre><code>wget <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/raw.githubusercontent.com/odedia</span><span class="hljs-regexp">/kubernetes-volume-drivers/master</span><span class="hljs-regexp">/flexvolume/smb</span><span class="hljs-regexp">/pv-smb-flexvol.yaml</span>
</code></pre><p>Edit the yaml contents and replace the <code>source</code> line with the address of your SMB server and shared folder. Apply the changes:</p>
<pre><code>kubectl apply <span class="hljs-operator">-</span>f pv<span class="hljs-operator">-</span>smb<span class="hljs-operator">-</span>flexvol.yaml
</code></pre><p>Create a PVC that is bound to the newly created persistent volume:</p>
<pre><code> kubectl apply -f <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/raw.githubusercontent.com/odedia</span><span class="hljs-regexp">/kubernetes-volume-drivers/master</span><span class="hljs-regexp">/flexvolume/smb</span><span class="hljs-regexp">/pvc-smb-flexvol.yaml</span>
</code></pre><p>Check the status of the PV and the PVC:</p>
<pre><code>kubectl <span class="hljs-keyword">get</span> pv
kubectl <span class="hljs-keyword">get</span> pvc
</code></pre><p>Run a test pod with this PVC:</p>
<pre><code> kubectl apply -f <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/raw.githubusercontent.com/odedia</span><span class="hljs-regexp">/kubernetes-volume-drivers/master</span><span class="hljs-regexp">/flexvolume/smb</span><span class="hljs-regexp">/nginx-flex-smb-pvc.yaml</span>
</code></pre><h2 id="heading-verify-the-volume-mount">Verify the volume mount</h2>
<p>SSH into the pod:</p>
<pre><code>kubectl exec <span class="hljs-operator">-</span>it nginx<span class="hljs-operator">-</span>flex<span class="hljs-operator">-</span>smb <span class="hljs-operator">-</span><span class="hljs-operator">-</span> bash
</code></pre><p>Run <code>df -h</code>. You should see your SMB share mounted under <code>/data</code>:</p>
<pre><code><span class="hljs-string">root@nginx-flex-smb:/#</span> <span class="hljs-string">df</span> <span class="hljs-string">-h</span>
<span class="hljs-string">Filesystem</span>                                 <span class="hljs-string">Size</span>  <span class="hljs-string">Used</span> <span class="hljs-string">Avail</span> <span class="hljs-string">Use%</span> <span class="hljs-string">Mounted</span> <span class="hljs-string">on</span>
<span class="hljs-string">overlay</span>                                    <span class="hljs-string">291G</span>  <span class="hljs-number">3.</span><span class="hljs-string">2G</span>  <span class="hljs-string">288G</span>   <span class="hljs-number">2</span><span class="hljs-string">%</span> <span class="hljs-string">/</span>
<span class="hljs-string">tmpfs</span>                                      <span class="hljs-number">3.</span><span class="hljs-string">4G</span>     <span class="hljs-number">0</span>  <span class="hljs-number">3.</span><span class="hljs-string">4G</span>   <span class="hljs-number">0</span><span class="hljs-string">%</span> <span class="hljs-string">/dev</span>
<span class="hljs-string">tmpfs</span>                                      <span class="hljs-number">3.</span><span class="hljs-string">4G</span>     <span class="hljs-number">0</span>  <span class="hljs-number">3.</span><span class="hljs-string">4G</span>   <span class="hljs-number">0</span><span class="hljs-string">%</span> <span class="hljs-string">/sys/fs/cgroup</span>
<span class="hljs-string">//myfileserver.com/my-shared-folder</span>           <span class="hljs-string">25G</span>   <span class="hljs-string">64K</span>   <span class="hljs-string">25G</span>   <span class="hljs-number">1</span><span class="hljs-string">%</span> <span class="hljs-string">/data</span>
<span class="hljs-string">/dev/sda1</span>                                  <span class="hljs-string">291G</span>  <span class="hljs-number">3.</span><span class="hljs-string">2G</span>  <span class="hljs-string">288G</span>   <span class="hljs-number">2</span><span class="hljs-string">%</span> <span class="hljs-string">/etc/hosts</span>
<span class="hljs-string">shm</span>                                         <span class="hljs-string">64M</span>     <span class="hljs-number">0</span>   <span class="hljs-string">64M</span>   <span class="hljs-number">0</span><span class="hljs-string">%</span> <span class="hljs-string">/dev/shm</span>
<span class="hljs-string">tmpfs</span>                                      <span class="hljs-number">3.</span><span class="hljs-string">4G</span>   <span class="hljs-string">12K</span>  <span class="hljs-number">3.</span><span class="hljs-string">4G</span>   <span class="hljs-number">1</span><span class="hljs-string">%</span> <span class="hljs-string">/run/secrets/kubernetes.io/serviceaccount</span>
<span class="hljs-string">tmpfs</span>                                      <span class="hljs-number">3.</span><span class="hljs-string">4G</span>     <span class="hljs-number">0</span>  <span class="hljs-number">3.</span><span class="hljs-string">4G</span>   <span class="hljs-number">0</span><span class="hljs-string">%</span> <span class="hljs-string">/sys/firmware</span>
</code></pre>]]></content:encoded></item><item><title><![CDATA[Platform Automation for PAS and PKS on GCP]]></title><description><![CDATA[What is this?
Credits: This tutorial is largly based on the excellent work by Alan McGinlay on github, my small contributions were:

One terraform for both PAS and PKS since it’s now supported on the same ops manager.
Added terraform entries for VMwa...]]></description><link>https://odedia.org/platform-automation-for-pas-and-pks-on-gcp</link><guid isPermaLink="true">https://odedia.org/platform-automation-for-pas-and-pks-on-gcp</guid><category><![CDATA[GCP]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[infrastructure]]></category><dc:creator><![CDATA[Oded Shopen]]></dc:creator><pubDate>Tue, 02 Apr 2019 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639771110863/UOoCclTJf.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-this">What is this?</h2>
<p><strong>Credits</strong>: This tutorial is largly based on the excellent work by Alan McGinlay <a target="_blank" href="https://github.com/amcginlay/ops-manager-automation-cc">on github</a>, my small contributions were:</p>
<ul>
<li>One terraform for both PAS and PKS since it’s now supported on the same ops manager.</li>
<li>Added terraform entries for VMware harbor (it’s not included in the official PKS terraform for some reason)</li>
<li>Let’s encrypt certificates for concourse and PCF.</li>
<li>Separation of main pipeline and “fetch artifacts”</li>
<li>Additional marketplace services</li>
<li>Some minor fixes and customizations such as triggers of upload-and-configure when new artifacts are downloaded.</li>
</ul>
<p>The following steps use <a target="_blank" href="https://github.com/EngineerBetter/control-tower">Control Tower</a> to build a <a target="_blank" href="https://concourse-ci.org/">Concourse</a> instance on <a target="_blank" href="https://cloud.google.com/">Google Cloud Platform</a>, then uses a combination of <a target="_blank" href="https://cloud.google.com/storage/">GCS</a> buckets, <a target="_blank" href="https://docs.cloudfoundry.org/credhub/">Credhub</a>, a suite of <a target="_blank" href="http://docs.pivotal.io/platform-automation">Platform Automation</a> tools and a single Concourse pipeline to deploy (and upgrade) the entire OpsMan and PCF product stack directly from the <a target="_blank" href="https://network.pivotal.io">Pivotal Network</a>.</p>
<p>This fork was adapted to use a terraform script that paves both <a target="_blank" href="https://pivotal.io/platform/pivotal-application-service">Pivotal Application Service</a> and <a target="_blank" href="https://pivotal.io/platform/pivotal-container-service">Pivotal Container Service</a>, and an updated pipeline that runs both products on the same Ops Manager.</p>
<h2 id="heading-setup-proper-quotas">Setup proper quotas</h2>
<p>You will need quite a bit of resources for the entire installation of both Pivotal Application Service, Pivotal Container Service and all the included marketplace services in this installation. You can remove services you don't need from the final pipeline below (under <code>ops-manager-automation-cc/ci/pivotal-cloud-foundry/pipeline.yml</code>). The following quotas are required for the full installation in a given region:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639770641529/H7rWIOwBJ.png" alt="quotas.png" /></p>
<h2 id="heading-setup-dns-namespaces">Setup DNS namespaces</h2>
<p>You need to setup two NS records at your domain registrar for this installation. One will be for concourse and one would be for the platform. You want to seperate the two into different subdomains so there will not be any dependency between them. 
Create a new NS record for concourse, for example <code>devops</code>, and give it an NS. Give a different name for the platform, for example <code>cf</code>. Here's an example of setting it on Google domains:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639770656897/OkCijNffo.png" alt="cf-domain.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639770666553/G34R4XP3Z.png" alt="devops-domain.png" /></p>
<p>You need to provide all the nameserver records from Google Cloud for each of the NS records. The list is:</p>
<pre><code class="lang-bash">ns-cloud-a1.googledomains.com.
ns-cloud-b1.googledomains.com.
ns-cloud-c1.googledomains.com.
ns-cloud-d1.googledomains.com.
ns-cloud-e1.googledomains.com.
ns-cloud-a2.googledomains.com.
ns-cloud-b2.googledomains.com.
ns-cloud-c2.googledomains.com.
ns-cloud-d2.googledomains.com.
ns-cloud-e2.googledomains.com.
ns-cloud-a3.googledomains.com.
ns-cloud-b3.googledomains.com.
ns-cloud-c3.googledomains.com.
ns-cloud-d3.googledomains.com.
ns-cloud-e3.googledomains.com.
ns-cloud-a4.googledomains.com.
ns-cloud-b4.googledomains.com.
ns-cloud-c4.googledomains.com.
ns-cloud-d4.googledomains.com.
ns-cloud-e4.googledomains.com.
</code></pre>
<h2 id="heading-fork-this-repository">Fork this repository</h2>
<p>I recommend forking this repository so you can:</p>
<ul>
<li>Make modifications to suit your own requirements</li>
<li>Protect your active pipelines from config changes made here</li>
</ul>
<h2 id="heading-recycling-gcp-projects">Recycling GCP projects</h2>
<p>If you wish to re-use an existing GCP project for this exercise, it is often useful to clean up any existing resources beforehand.
For guidance, follow <a target="_blank" href="https://github.com/amcginlay/gcp-cleanup">these instructions</a>.</p>
<h2 id="heading-create-your-jumpbox-from-your-local-machine-or-google-cloud-shell">Create your jumpbox from your local machine or Google Cloud Shell</h2>
<pre><code class="lang-bash">GCP_PROJECT_ID=&lt;TARGET_GCP_PROJECT_ID&gt;
GCP_REGION=&lt;TARGET_REGION&gt;
gcloud auth login --project <span class="hljs-variable">${GCP_PROJECT_ID}</span> --quiet <span class="hljs-comment"># ... if necessary</span>

gcloud services <span class="hljs-built_in">enable</span> compute.googleapis.com \
  --project <span class="hljs-string">"<span class="hljs-variable">${GCP_PROJECT_ID}</span>"</span>

gcloud compute instances create <span class="hljs-string">"jbox-cc"</span> \
  --image-project <span class="hljs-string">"ubuntu-os-cloud"</span> \
  --image-family <span class="hljs-string">"ubuntu-1804-lts"</span> \
  --boot-disk-size <span class="hljs-string">"200"</span> \
  --machine-type=g1-small \
  --project <span class="hljs-string">"<span class="hljs-variable">${GCP_PROJECT_ID}</span>"</span> \
  --zone <span class="hljs-string">"<span class="hljs-variable">${GCP_REGION}</span>"</span>-b
</code></pre>
<h2 id="heading-move-to-the-jumpbox-and-log-in-to-gcp">Move to the jumpbox and log in to GCP</h2>
<pre><code class="lang-bash">gcloud compute ssh ubuntu@jbox-cc \
  --project <span class="hljs-string">"<span class="hljs-variable">${GCP_PROJECT_ID}</span>"</span> \
  --zone <span class="hljs-string">"<span class="hljs-variable">${GCP_REGION}</span>"</span>-b
</code></pre>
<pre><code class="lang-bash">gcloud auth login --quiet
</code></pre>
<p>All following commands should be executed from the jumpbox unless otherwsie instructed.</p>
<h2 id="heading-prepare-your-environment-file">Prepare your environment file</h2>
<pre><code class="lang-bash">cat &gt; ~/.env &lt;&lt; EOF
<span class="hljs-comment"># *** your environment-specific variables will go here ***</span>
PIVNET_UAA_REFRESH_TOKEN=CHANGE_ME_PIVNET_UAA_REFRESH_TOKEN  <span class="hljs-comment"># e.g. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-r</span>
PCF_DOMAIN_NAME=CHANGE_ME_DOMAIN_NAME                        <span class="hljs-comment"># e.g. "mydomain.com", "pal.pivotal.io", "pivotaledu.io", etc.</span>
PCF_SUBDOMAIN_NAME=CHANGE_ME_SUBDOMAIN_NAME                  <span class="hljs-comment"># e.g. "mypks", "mypas", "cls66env99", "maroon", etc.</span>
GITHUB_PUBLIC_REPO=CHANGE_ME_GITHUB_PUBLIC_REPO              <span class="hljs-comment"># e.g. https://github.com/odedia/pivotal-platform-automation-gcp.git</span>
GCP_REGION=CHANGE_ME_GCP_REGION                              <span class="hljs-comment"># e.g. europe-west2, us-central1 etc.</span>
CONCOURSE_SUBDOMAIN_NAME=CHANGE_ME_CONCOURSE_SUBDOMAIN       <span class="hljs-comment">#e.g. "devops"</span>
GCP_AZ1=CHANGE_ME_GCP_AZ1                              <span class="hljs-comment"># e.g. europe-west2-a, us-central1-a etc.</span>
GCP_AZ2=CHANGE_ME_GCP_AZ2                              <span class="hljs-comment"># e.g. europe-west2-b, us-central1-b etc.</span>
GCP_AZ3=CHANGE_ME_GCP_AZ3                              <span class="hljs-comment"># e.g. europe-west2-c, us-central1-c etc.</span>
PRODUCT_SLUG=pivotal-cloud-foundry
MY_EMAIL=CHANGE_ME_EMAIL_ADDRESS                      <span class="hljs-comment">#email address for Lets Encrypt</span>

<span class="hljs-built_in">export</span> OM_TARGET=https://opsman.\<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>.\<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>
<span class="hljs-built_in">export</span> OM_USERNAME=admin
<span class="hljs-built_in">export</span> OM_PASSWORD=$(uuidgen)
<span class="hljs-built_in">export</span> RABBITMQ_MULTITENANT_ADMIN_PASSWORD=$(uuidgen)
<span class="hljs-built_in">export</span> OM_DECRYPTION_PASSPHRASE=\<span class="hljs-variable">${OM_PASSWORD}</span>
<span class="hljs-built_in">export</span> OM_SKIP_SSL_VALIDATION=<span class="hljs-literal">true</span>
EOF
</code></pre>
<p><strong>Before</strong> continuing, open the <code>.env</code> file and update the <code>CHANGE_ME</code> values accordingly.</p>
<p>Ensure these variables get set into the shell every time the ubuntu user connects to the jumpbox:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"source ~/.env"</span> &gt;&gt; ~/.bashrc
</code></pre>
<p>Load the variables into your shell with the source command so we can use them immediately:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> ~/.env
</code></pre>
<h2 id="heading-prepare-jumpbox-and-generate-service-account">Prepare jumpbox and generate service account</h2>
<pre><code class="lang-bash">gcloud services <span class="hljs-built_in">enable</span> iam.googleapis.com --async
gcloud services <span class="hljs-built_in">enable</span> cloudresourcemanager.googleapis.com --async
gcloud services <span class="hljs-built_in">enable</span> dns.googleapis.com --async
gcloud services <span class="hljs-built_in">enable</span> sqladmin.googleapis.com --async

sudo apt update --yes &amp;&amp; \
sudo apt install --yes jq &amp;&amp; \
sudo apt install --yes build-essential &amp;&amp; \
sudo apt install --yes ruby-dev &amp;&amp; \
sudo apt-get install software-properties-common --yes &amp;&amp; \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -  &amp;&amp; \
sudo apt-get update --yes &amp;&amp; \
sudo apt-get install software-properties-common  --yes &amp;&amp; \
sudo add-apt-repository ppa:certbot/certbot --yes &amp;&amp; \
sudo apt-get update --yes &amp;&amp; \
sudo apt-get install certbot --yes &amp;&amp; \
sudo apt-get install python-minimal --yes &amp;&amp; \
sudo apt-get install python-pip python-dev build-essential --yes &amp;&amp; \
sudo apt-get install python-setuptools --yes &amp;&amp; \
sudo pip install cryptography &amp;&amp; \
sudo pip install certbot-dns-google
</code></pre>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~

FLY_VERSION=5.6.0
wget -O fly.tgz https://github.com/concourse/concourse/releases/download/v<span class="hljs-variable">${FLY_VERSION}</span>/fly-<span class="hljs-variable">${FLY_VERSION}</span>-linux-amd64.tgz &amp;&amp; \
  tar -xvf fly.tgz &amp;&amp; \
  sudo mv fly /usr/<span class="hljs-built_in">local</span>/bin &amp;&amp; \
  rm fly.tgz

CT_VERSION=0.8.3
wget -O control-tower https://github.com/EngineerBetter/control-tower/releases/download/<span class="hljs-variable">${CT_VERSION}</span>/control-tower-linux-amd64 &amp;&amp; \
  chmod +x control-tower &amp;&amp; \
  sudo mv control-tower /usr/<span class="hljs-built_in">local</span>/bin/

OM_VERSION=0.51.0
wget -O om https://github.com/pivotal-cf/om/releases/download/<span class="hljs-variable">${OM_VERSION}</span>/om-linux &amp;&amp; \
  chmod +x om &amp;&amp; \
  sudo mv om /usr/<span class="hljs-built_in">local</span>/bin/

PN_VERSION=0.0.55
wget -O pivnet https://github.com/pivotal-cf/pivnet-cli/releases/download/v<span class="hljs-variable">${PN_VERSION}</span>/pivnet-linux-amd64-<span class="hljs-variable">${PN_VERSION}</span> &amp;&amp; \
  chmod +x pivnet &amp;&amp; \
  sudo mv pivnet /usr/<span class="hljs-built_in">local</span>/bin/

BOSH_VERSION=5.4.0
wget -O bosh https://s3.amazonaws.com/bosh-cli-artifacts/bosh-cli-<span class="hljs-variable">${BOSH_VERSION}</span>-linux-amd64 &amp;&amp; \
  chmod +x bosh &amp;&amp; \
  sudo mv bosh /usr/<span class="hljs-built_in">local</span>/bin/

CHUB_VERSION=2.6.1
wget -O credhub.tgz https://github.com/cloudfoundry-incubator/credhub-cli/releases/download/<span class="hljs-variable">${CHUB_VERSION}</span>/credhub-linux-<span class="hljs-variable">${CHUB_VERSION}</span>.tgz &amp;&amp; \
  tar -xvf credhub.tgz &amp;&amp; \
  sudo mv credhub /usr/<span class="hljs-built_in">local</span>/bin &amp;&amp; \
  rm credhub.tgz

BBL_VERSION=7.6.0
wget -O bbl https://github.com/cloudfoundry/bosh-bootloader/releases/download/v7.6.0/bbl-v<span class="hljs-variable">${BBL_VERSION}</span>_linux_x86-64 &amp;&amp; \
  chmod +x bbl &amp;&amp; \
  sudo mv bbl /usr/<span class="hljs-built_in">local</span>/bin

TF_VERSION=0.11.13
wget -O terraform.zip https://releases.hashicorp.com/terraform/<span class="hljs-variable">${TF_VERSION}</span>/terraform_<span class="hljs-variable">${TF_VERSION}</span>_linux_amd64.zip &amp;&amp; \
  unzip terraform.zip &amp;&amp; \
  sudo mv terraform /usr/<span class="hljs-built_in">local</span>/bin &amp;&amp; \
  rm terraform.zip

git <span class="hljs-built_in">clone</span> https://github.com/odedia/terraforming-gcp &amp;&amp; \
  mv terraforming-gcp terraforming
</code></pre>
<pre><code class="lang-bash">gcloud iam service-accounts create p-service --display-name <span class="hljs-string">"Pivotal Service Account"</span>

gcloud projects add-iam-policy-binding $(gcloud config get-value core/project) \
  --member <span class="hljs-string">"serviceAccount:p-service@<span class="hljs-subst">$(gcloud config get-value core/project)</span>.iam.gserviceaccount.com"</span> \
  --role <span class="hljs-string">'roles/owner'</span>

<span class="hljs-built_in">cd</span> ~
gcloud iam service-accounts keys create <span class="hljs-string">'gcp_credentials.json'</span> \
  --iam-account <span class="hljs-string">"p-service@<span class="hljs-subst">$(gcloud config get-value core/project)</span>.iam.gserviceaccount.com"</span>
</code></pre>
<h2 id="heading-clone-this-repo">Clone this repo</h2>
<p>The scripts, pipelines and config you need to complete the following steps are inside this repo, so clone it to your jumpbox:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> <span class="hljs-variable">${GITHUB_PUBLIC_REPO}</span> ~/ops-manager-automation-cc
</code></pre>
<h2 id="heading-create-a-certificate-signed-by-lets-encrypt">Create a certificate signed by Let's Encrypt</h2>
<p>Create a Google Cloud Zone DNS entry:</p>
<p>Run the following script to create a certificate and key for the installation:</p>
<pre><code class="lang-bash">gcloud dns managed-zones create <span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-zone --dns-name <span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>.<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>. --description <span class="hljs-string">"PCF ZONE"</span>
DOMAIN=<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>.<span class="hljs-variable">${PCF_DOMAIN_NAME}</span> EMAIL=<span class="hljs-variable">${MY_EMAIL}</span> ~/ops-manager-automation-cc/bin/certbot.sh
gcloud dns managed-zones delete <span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-zone
</code></pre>
<p>Note: If you face issues with <code>AttributeError: 'module' object has no attribute 'Locale'</code>, please remove <code>parsedatetime</code> 2.6 by running <code>pip uninstall parsedatetime</code> and then run <code>pip install parsedatetime==2.5</code>.</p>
<h2 id="heading-configure-terraform">Configure Terraform</h2>
<pre><code class="lang-bash">cat &gt; ~/terraform.tfvars &lt;&lt;-EOF
dns_suffix             = <span class="hljs-string">"<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>"</span>
env_name               = <span class="hljs-string">"<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>"</span>
region                 = <span class="hljs-string">"<span class="hljs-variable">${GCP_REGION}</span>"</span>
zones                  = [<span class="hljs-string">"<span class="hljs-variable">${GCP_AZ1}</span>"</span>, <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ2}</span>"</span>, <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ3}</span>"</span>]
project                = <span class="hljs-string">"<span class="hljs-subst">$(gcloud config get-value core/project)</span>"</span>
opsman_image_url       = <span class="hljs-string">""</span>
opsman_vm              = 0
create_gcs_buckets     = <span class="hljs-string">"false"</span>
external_database      = 0
isolation_segment      = 0
ssl_cert            = &lt;&lt;SSL_CERT
$(sudo cat /etc/letsencrypt/live/<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>.<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>/fullchain.pem)
SSL_CERT
ssl_private_key     = &lt;&lt;SSL_KEY
$(sudo cat /etc/letsencrypt/live/<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>.<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>/privkey.pem)
SSL_KEY
service_account_key = &lt;&lt;SERVICE_ACCOUNT_KEY
$(cat ~/gcp_credentials.json)
SERVICE_ACCOUNT_KEY
EOF
</code></pre>
<p>Note the <code>opsman_image_url == ""</code> setting which prohibits Terraform from downloading and deploying the Ops Manager VM.
The Concourse pipelines will take responsibility for this.</p>
<h2 id="heading-terraform-the-infrastructure">Terraform the infrastructure</h2>
<p>The PKS and PAS platforms have different baseline infrastructure requirements which are configured from separate dedicated directories.
Terraform is directory-sensitive and needs local access to your customized <code>terraform.tfvars</code> files so symlink it in from the home directory.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/terraforming/terraforming-pcf
ln -s ~/terraform.tfvars .
</code></pre>
<h3 id="heading-launch-terraform">Launch Terraform</h3>
<p>Confirm you're in the correct directory for your chosen platform and <code>terraform.tfvars</code> is present, then execute the following:</p>
<pre><code class="lang-bash">terraform init
terraform apply --auto-approve
</code></pre>
<p>This will take about 2 mins to complete.</p>
<h2 id="heading-install-concourse">Install Concourse</h2>
<p>We use Control Tower to install Concourse, as follows:</p>
<pre><code class="lang-bash">gcloud dns managed-zones create <span class="hljs-variable">${CONCOURSE_SUBDOMAIN_NAME}</span>-zone --dns-name <span class="hljs-variable">${CONCOURSE_SUBDOMAIN_NAME}</span>.<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>. --description <span class="hljs-string">"Devops zone"</span>
GOOGLE_APPLICATION_CREDENTIALS=~/gcp_credentials.json \
  control-tower deploy \
    --region <span class="hljs-variable">${GCP_REGION}</span> \
    --iaas gcp \
    --workers 2 \
    --domain concourse.<span class="hljs-variable">${CONCOURSE_SUBDOMAIN_NAME}</span>.<span class="hljs-variable">${PCF_DOMAIN_NAME}</span> \
    pautomation
</code></pre>
<p>This will take about 20 mins to complete.</p>
<h2 id="heading-persist-a-few-credentials">Persist a few credentials</h2>
<pre><code class="lang-bash">INFO=$(GOOGLE_APPLICATION_CREDENTIALS=~/gcp_credentials.json \
  control-tower info \
    --region <span class="hljs-variable">${GCP_REGION}</span> \
    --iaas gcp \
    --json \
    pautomation
)

<span class="hljs-built_in">echo</span> <span class="hljs-string">"CC_ADMIN_PASSWD=<span class="hljs-subst">$(echo ${INFO} | jq --raw-output .config.concourse_password)</span>"</span> &gt;&gt; ~/.env
<span class="hljs-built_in">echo</span> <span class="hljs-string">"CREDHUB_CA_CERT='<span class="hljs-subst">$(echo ${INFO} | jq --raw-output .config.credhub_ca_cert)</span>'"</span> &gt;&gt; ~/.env
<span class="hljs-built_in">echo</span> <span class="hljs-string">"CREDHUB_CLIENT=credhub_admin"</span> &gt;&gt; ~/.env
<span class="hljs-built_in">echo</span> <span class="hljs-string">"CREDHUB_SECRET=<span class="hljs-subst">$(echo ${INFO} | jq --raw-output .config.credhub_admin_client_secret)</span>"</span> &gt;&gt; ~/.env
<span class="hljs-built_in">echo</span> <span class="hljs-string">"CREDHUB_SERVER=<span class="hljs-subst">$(echo ${INFO} | jq --raw-output .config.credhub_url)</span>"</span> &gt;&gt; ~/.env
<span class="hljs-built_in">echo</span> <span class="hljs-string">'eval "$(GOOGLE_APPLICATION_CREDENTIALS=~/gcp_credentials.json \
  control-tower info \
    --region ${GCP_REGION} \
    --iaas gcp \
    --env pautomation)"'</span> &gt;&gt; ~/.env

<span class="hljs-built_in">source</span> ~/.env
</code></pre>
<h2 id="heading-verify-bosh-and-credhub-connectivity">Verify BOSH and Credhub connectivity</h2>
<pre><code class="lang-bash">bosh env
credhub --version
</code></pre>
<h2 id="heading-check-concourse-targets-and-check-the-pre-configured-pipeline">Check Concourse targets and check the pre-configured pipeline:</h2>
<pre><code class="lang-bash">fly targets
fly -t control-tower-pautomation pipelines
</code></pre>
<p>Navigate to the <code>url</code> shown for <code>fly targets</code>.</p>
<p>Use <code>admin</code> user and the value of <code>CC_ADMIN_PASSWD</code> to login and see the pre-configured pipeline.</p>
<p><strong>Note</strong> <code>control-tower</code> will log you in but valid access tokens will expire every 24 hours. The command to log back in is:</p>
<pre><code class="lang-bash">fly -t control-tower-pautomation login --username admin --password <span class="hljs-variable">${CC_ADMIN_PASSWD}</span>
</code></pre>
<h2 id="heading-set-up-dedicated-gcs-bucket-for-downloads">Set up dedicated GCS bucket for downloads</h2>
<pre><code class="lang-bash">gsutil mb -c regional -l <span class="hljs-variable">${GCP_REGION}</span> gs://<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-concourse-resources
gsutil versioning <span class="hljs-built_in">set</span> on gs://<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-concourse-resources
</code></pre>
<h2 id="heading-add-a-dummy-state-file">Add a dummy state file</h2>
<p>The <code>state.yml</code> file is produced by the <code>create-vm</code> platform automation task and serves as a flag to indicate that an Ops Manager exists.
We currently store the <code>state.yml</code> file in GCS.
The <code>install-opsman</code> job also consumes this file so it can short-circuit the <code>create-vm</code> task if an Ops Manager does exist.
This is a mandatory input and does not exist by default so we create a dummy <code>state.yml</code> file to kick off proceedings.
Storing the <code>state.yml</code> file in git may work around this edge case but, arguably, GCS/S3 is a more appropriate home.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">"---"</span> &gt; ~/state.yml
gsutil cp ~/state.yml gs://<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-concourse-resources/
</code></pre>
<p>If required, be aware that versioned buckets require you to use <code>gsutil rm -a</code> to take files fully out of view.</p>
<h2 id="heading-store-secrets-in-credhub">Store secrets in Credhub</h2>
<pre><code class="lang-bash">credhub <span class="hljs-built_in">set</span> -n pivnet-api-token -t value -v <span class="hljs-string">"<span class="hljs-variable">${PIVNET_UAA_REFRESH_TOKEN}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n domain-name -t value -v <span class="hljs-string">"<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n subdomain-name -t value -v <span class="hljs-string">"<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n gcp-project-id -t value -v <span class="hljs-string">"<span class="hljs-subst">$(gcloud config get-value core/project)</span>"</span>
credhub <span class="hljs-built_in">set</span> -n opsman-public-ip -t value -v <span class="hljs-string">"<span class="hljs-subst">$(dig +short opsman.${PCF_SUBDOMAIN_NAME}.${PCF_DOMAIN_NAME})</span>"</span>
credhub <span class="hljs-built_in">set</span> -n gcp-credentials -t value -v <span class="hljs-string">"<span class="hljs-subst">$(cat ~/gcp_credentials.json)</span>"</span>
credhub <span class="hljs-built_in">set</span> -n om-target -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_TARGET}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n om-skip-ssl-validation -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_SKIP_SSL_VALIDATION}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n om-username -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_USERNAME}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n om-password -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_PASSWORD}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n om-decryption-passphrase -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_DECRYPTION_PASSPHRASE}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n domain-crt -t value -v <span class="hljs-string">"<span class="hljs-subst">$(sudo cat /etc/letsencrypt/live/${PCF_SUBDOMAIN_NAME}.${PCF_DOMAIN_NAME}/fullchain.pem)</span>"</span>
credhub <span class="hljs-built_in">set</span> -n domain-key -t value -v <span class="hljs-string">"<span class="hljs-subst">$(sudo cat /etc/letsencrypt/live/${PCF_SUBDOMAIN_NAME}.${PCF_DOMAIN_NAME}/privkey.pem)</span>"</span>
credhub <span class="hljs-built_in">set</span> -n region -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_REGION}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n az1 -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ1}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n az2 -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ2}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n az3 -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ3}</span>"</span>
credhub <span class="hljs-built_in">set</span> -n rabbitmq-multitenant-password -t value -v <span class="hljs-string">"<span class="hljs-variable">${RABBITMQ_MULTITENANT_ADMIN_PASSWORD}</span>"</span>
</code></pre>
<p>Take a moment to review these settings with <code>credhub get -n &lt;NAME&gt;</code>.</p>
<h2 id="heading-build-the-pipeline">Build the pipeline</h2>
<p>Create a <code>private.yml</code> to contain the secrets required by <code>pipeline.yml</code>:</p>
<pre><code class="lang-bash">cat &gt; ~/private.yml &lt;&lt; EOF
---
product-slug: <span class="hljs-variable">${PRODUCT_SLUG}</span>
config-uri: <span class="hljs-variable">${GITHUB_PUBLIC_REPO}</span>
gcp-credentials: |
$(cat ~/gcp_credentials.json | sed <span class="hljs-string">'s/^/  /'</span>)
gcs-bucket: <span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-concourse-resources
pivnet-token: <span class="hljs-variable">${PIVNET_UAA_REFRESH_TOKEN}</span>
credhub-ca-cert: |
$(<span class="hljs-built_in">echo</span> <span class="hljs-variable">$CREDHUB_CA_CERT</span> | sed <span class="hljs-string">'s/- /-\n/g; s/ -/\n-/g'</span> | sed <span class="hljs-string">'/CERTIFICATE/! s/ /\n/g'</span> | sed <span class="hljs-string">'s/^/  /'</span>)
credhub-client: <span class="hljs-variable">${CREDHUB_CLIENT}</span>
credhub-secret: <span class="hljs-variable">${CREDHUB_SECRET}</span>
credhub-server: <span class="hljs-variable">${CREDHUB_SERVER}</span>
EOF
</code></pre>
<h2 id="heading-set-and-unpause-the-pipelines">Set and unpause the pipelines:</h2>
<p>Create the "fetch artifacts" pipeline. You want this pipeline separate from your main pipeline because you are less likely to destroy it and it can work in parallel to the master pipeline</p>
<pre><code class="lang-bash">fly -t control-tower-pautomation set-pipeline -p fetch-artifacts -n   -c ~/ops-manager-automation-cc/ci/fetch-artifacts/pipeline.yml -l ~/private.yml 

fly -t control-tower-pautomation  unpause-pipeline -p fetch-artifacts
</code></pre>
<p>Run the master pipeline with the commands below:</p>
<pre><code class="lang-bash">fly -t control-tower-pautomation set-pipeline -p <span class="hljs-variable">${PRODUCT_SLUG}</span> -n \
  -c ~/ops-manager-automation-cc/ci/<span class="hljs-variable">${PRODUCT_SLUG}</span>/pipeline.yml \
  -l ~/private.yml

fly -t control-tower-pautomation unpause-pipeline -p <span class="hljs-variable">${PRODUCT_SLUG}</span>
</code></pre>
<p>This should begin to execute in ~60 seconds.</p>
<p>Be aware that you may be required to manually accept the PivNet EULAs before a product can be downloaded
so watch for pipeline failures which contain the necessary URLs to follow.</p>
<p>You may also observe that on the first run, the <code>export-installation</code> job will fail because the Ops Manager
is missing.
Run this job manually once the <code>install-opsman</code> job has run successfully.</p>
<h1 id="heading-teardown">Teardown</h1>
<p>You have a "delete-installation" pipline that can be setup with this command:</p>
<pre><code class="lang-bash">fly -t control-tower-pautomation set-pipeline -p delete-installation -n \
  -c ~/ops-manager-automation-cc/ci/<span class="hljs-variable">${PRODUCT_SLUG}</span>/delete-installation.yml \
  -l ~/private.yml

fly -t control-tower-pautomation unpause-pipeline -p delete-installation
</code></pre>
<p>You will need to manually run this pipeline.</p>
<p>The following steps will help you when you're ready to dispose of everything.</p>
<p>Use the <code>om</code> tool to delete the installation (be careful, you will <strong>not</strong> be asked to confirm this operation):</p>
<pre><code class="lang-bash">om delete-installation
</code></pre>
<p>Delete the Ops Manager VM:</p>
<pre><code class="lang-bash">gcloud compute instances delete <span class="hljs-string">"ops-manager-vm"</span> --zone <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ1}</span>"</span> --quiet
</code></pre>
<p>Unwind the remaining PCF infrastructure:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ~/terraforming/terraforming-pcf
terraform destroy --auto-approve
</code></pre>
<p>Uninstall Concourse with <code>control-tower</code>:</p>
<pre><code class="lang-bash">GOOGLE_APPLICATION_CREDENTIALS=~/gcp_credentials.json \
  control-tower destroy \
    --region <span class="hljs-variable">${GCP_REGION}</span> \
    --iaas gcp \
    pautomation
</code></pre>
<p>If you just want to destroy the current installation and "start clean" you can run this command that would teardown the entire PCF environment and reset the pipeline:</p>
<pre><code class="lang-bash">om delete-installation &amp;&amp; \
gcloud compute instances delete ops-manager-vm --delete-disks all --zone <span class="hljs-variable">${GCP_AZ1}</span> --quiet &amp;&amp; \
<span class="hljs-built_in">cd</span> ~/terraforming/terraforming-pcf &amp;&amp; \
terraform destroy --auto-approve &amp;&amp; \
terraform init &amp;&amp; \
terraform apply --auto-approve &amp;&amp; \
sleep 70 &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n pivnet-api-token -t value -v <span class="hljs-string">"<span class="hljs-variable">${PIVNET_UAA_REFRESH_TOKEN}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n domain-name -t value -v <span class="hljs-string">"<span class="hljs-variable">${PCF_DOMAIN_NAME}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n subdomain-name -t value -v <span class="hljs-string">"<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n gcp-project-id -t value -v <span class="hljs-string">"<span class="hljs-subst">$(gcloud config get-value core/project)</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n opsman-public-ip -t value -v <span class="hljs-string">"<span class="hljs-subst">$(dig +short opsman.${PCF_SUBDOMAIN_NAME}.${PCF_DOMAIN_NAME})</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n gcp-credentials -t value -v <span class="hljs-string">"<span class="hljs-subst">$(cat ~/gcp_credentials.json)</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n om-target -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_TARGET}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n om-skip-ssl-validation -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_SKIP_SSL_VALIDATION}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n om-username -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_USERNAME}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n om-password -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_PASSWORD}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n om-decryption-passphrase -t value -v <span class="hljs-string">"<span class="hljs-variable">${OM_DECRYPTION_PASSPHRASE}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n domain-crt -t value -v <span class="hljs-string">"<span class="hljs-subst">$(sudo cat /etc/letsencrypt/live/${PCF_SUBDOMAIN_NAME}.${PCF_DOMAIN_NAME}/fullchain.pem)</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n domain-key -t value -v <span class="hljs-string">"<span class="hljs-subst">$(sudo cat /etc/letsencrypt/live/${PCF_SUBDOMAIN_NAME}.${PCF_DOMAIN_NAME}/privkey.pem)</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n region -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_REGION}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n az1 -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ1}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n az2 -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ2}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n az3 -t value -v <span class="hljs-string">"<span class="hljs-variable">${GCP_AZ3}</span>"</span> &amp;&amp; \
credhub <span class="hljs-built_in">set</span> -n rabbitmq-multitenant-password -t value -v <span class="hljs-string">"<span class="hljs-variable">${RABBITMQ_MULTITENANT_ADMIN_PASSWORD}</span>"</span> &amp;&amp; \
<span class="hljs-built_in">echo</span> <span class="hljs-string">"---"</span> &gt; ~/state.yml &amp;&amp; \
gsutil cp ~/state.yml gs://<span class="hljs-variable">${PCF_SUBDOMAIN_NAME}</span>-concourse-resources/ &amp;&amp; \
fly -t control-tower-pautomation destroy-pipeline -n -p <span class="hljs-variable">${PRODUCT_SLUG}</span> &amp;&amp; \
fly -t control-tower-pautomation set-pipeline -p <span class="hljs-variable">${PRODUCT_SLUG}</span> -n -c ~/ops-manager-automation-cc/ci/<span class="hljs-variable">${PRODUCT_SLUG}</span>/pipeline.yml  -l ~/private.yml

fly -t control-tower-pautomation unpause-pipeline -p <span class="hljs-variable">${PRODUCT_SLUG}</span>
</code></pre>
<p>If you have some leftover resources, you can clean them up (interactively) with the command below.</p>
<h2 id="heading-warning-this-is-a-dangerous-command-that-can-wipe-out-all-the-resources-on-your-gcp-project">WARNING: This is a dangerous command that can wipe out all the resources on your GCP project!</h2>
<pre><code class="lang-bash">bbl cleanup-leftovers \
  --gcp-service-account-key=<span class="hljs-string">"<span class="hljs-subst">$(cat ~/gcp_credentials.json)</span>"</span> \
  --gcp-region=<span class="hljs-variable">${GCP_REGION}</span> \
  --iaas gcp
</code></pre>
]]></content:encoded></item></channel></rss>