{"id":6,"date":"2025-01-08T15:08:44","date_gmt":"2025-01-08T14:08:44","guid":{"rendered":"https:\/\/timeforarchi-6ypwy9keq6.live-website.com\/?p=1"},"modified":"2025-01-08T15:08:44","modified_gmt":"2025-01-08T14:08:44","slug":"step-out-the-cluster","status":"publish","type":"post","link":"https:\/\/timeforarchi.com\/?p=6","title":{"rendered":"Step out of the cluster, please?"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This article has been originally published on TakeTurns blog as the first in an infrequent series of notes from TakeTurns engineering team about the technology behind TakeTurns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this interview <a href=\"https:\/\/www.linkedin.com\/in\/conradchuang\/\">Conrad Chuang<\/a> and <a href=\"https:\/\/www.linkedin.com\/in\/fbontemps\/\">Fabien Bontemps<\/a> discuss how we migrated our front-end services from our <a href=\"https:\/\/aws.amazon.com\/eks\/\">Amazon Elastic Kubernetes Service<\/a> (EKS) Cluster to <a href=\"https:\/\/aws.amazon.com\/cloudfront\/\">Amazon CloudFront<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"945\" height=\"739\" src=\"https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc1.png\" alt=\"\" class=\"wp-image-70\" srcset=\"https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc1.png 945w, https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc1-300x235.png 300w, https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc1-768x601.png 768w\" sizes=\"auto, (max-width: 945px) 100vw, 945px\" \/><\/figure>\n\n\n\n<p class=\"has-secondary-color has-text-color has-link-color has-small-font-size wp-elements-c0f233c6704738ccc1becea4363b4d70 wp-block-paragraph\"><em>A Cynical CTO has a conversation with his chief architect <a href=\"\/#1\" title=\"\">[1]<\/a><\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What can you tell me about our architecture?<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"945\" height=\"533\" src=\"https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc2.png\" alt=\"\" class=\"wp-image-71\" srcset=\"https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc2.png 945w, https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc2-300x169.png 300w, https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc2-768x433.png 768w\" sizes=\"auto, (max-width: 945px) 100vw, 945px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Like many other solutions, TakeTurns is built on a cloud-provider-enhanced implementation of Kubernetes, in our case we use <a href=\"https:\/\/aws.amazon.com\/eks\/\">Amazon Elastic Kubernetes Service<\/a> (EKS).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our architecture is event-driven <a href=\"\/#2\" title=\"\">[2]<\/a>, and the backend is structured around microservices <a href=\"\/#3\" title=\"\">[3]<\/a> corresponding to our business domains. And for the frontend, we opted for microfrontend <a href=\"\/#4\" title=\"\">[4]<\/a> architecture (again, one microfrontend by domain), and the underlying framework is <a href=\"https:\/\/react.dev\/\">React.js<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At some point, we\u2019ll provide an article around the global application architecture and the rationale but it\u2019s not the current subject of this article.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How did we end up here? <\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the first version of our architecture, we hosted our frontend in the <a href=\"https:\/\/kubernetes.io\/\">Kubernetes<\/a> cluster, using pods with containers running a <a href=\"https:\/\/nodejs.org\/en\">node.js<\/a> server, basically serving static content.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The only dynamic elements were the use of environment variables to define environment-specialized values to integrate with other layers (backend, identity provider, storage and 3rd party integration). But we could have defined it in a configuration file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One major interest in using Kubernetes for this hosting is the 24\/7 availability and <a href=\"https:\/\/docs.aws.amazon.com\/whitepapers\/latest\/overview-deployment-options\/bluegreen-deployments.html\">blue\/green deployment mechanisms<\/a>. As Amazon describes quite succinctly:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u201cA blue\/green deployment is a deployment strategy in which you create two separate, but identical environments. One environment (blue) is running the current application version and one environment (green) is running the new application version. Using a blue\/green deployment strategy increases application availability and reduces deployment risk by simplifying the rollback process if a deployment fails. Once testing has been completed on the green environment, live application traffic is directed to the green environment and the blue environment is deprecated.\u201d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Indeed, providing a new version is as smooth as possible; we don\u2019t experience any service interruptions when upgrading. The switch between version A and B is managed by Kubernetes proxy and integration with AWS load balancers, which is itself a Kubernetes-managed mechanism. The inscription into the DNS is also managed by the cluster, as are the DNS cache mechanisms. So, until today, <em>\u00abTout va pour le mieux dans le meilleur des mondes\u00bb <\/em><a href=\"\/#5\" title=\"\">[5]<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But then again, using node servers, multiplied by the number of environments, plus the number of pods to ensure regional distribution and availability, and multiple front-end components costs a lot.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"945\" height=\"637\" src=\"https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc3.png\" alt=\"\" class=\"wp-image-72\" srcset=\"https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc3.png 945w, https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc3-300x202.png 300w, https:\/\/timeforarchi.com\/wp-content\/uploads\/2024\/11\/sotc3-768x518.png 768w\" sizes=\"auto, (max-width: 945px) 100vw, 945px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Ah, given the costs \u00abIl faut cultiver notre \u201cCluster?\u201d\u00bb ?<\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Exactly. Cost optimization is always one of the key transverse objectives for many SaaS companies, especially at the beginning of the adventure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It was decided we must migrate our front-end resources from the Cluster!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What was our most cost-effective solution to reduce costs? <\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Considering what we are hosting, one of the more efficient solutions is to use a <a href=\"https:\/\/www.cloudflare.com\/learning\/cdn\/what-is-a-cdn\/\">Content Delivery Network<\/a> (CDN) <a href=\"\/#6\" title=\"\">[6]<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our cloud provider, Amazon, proposes its own implementation (<a href=\"https:\/\/aws.amazon.com\/cloudfront\/\">AWS CloudFront<\/a>), which has some advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>It is directly integrated with the DNS (<a href=\"https:\/\/aws.amazon.com\/route53\/\">Route53<\/a>) mechanism.<\/li>\n\n\n\n<li>It manages cache, advanced filtering, security, and metrics gathering (<a href=\"https:\/\/aws.amazon.com\/cloudwatch\/\">CloudWatch<\/a>)<\/li>\n\n\n\n<li>It ensures multi-region availability and accelerators for cross-region access.<\/li>\n\n\n\n<li>It can be directly mapped on a static content hosting solution (<a href=\"https:\/\/aws.amazon.com\/s3\/\">S3<\/a> bucket storage in our case) and maps a domain name (in relation with Route53, again).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">But every coin has two sides, unfortunately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Don\u2019t you mean every rose has its thorn?<\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hrm.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Anyhow, CloudFront can only define one distribution (one mapping from S3 to network) per domain name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So if we want to use CloudFront, how can we switch from one \u201cold\u201d distribution to the \u201cnew\u201d one using the Blue\/Green principle or something, ensuring no interruption for our service?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What were the options we considered? <\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We came up with a few options:<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-cb0a7ccb wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"has-small-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Option 1<\/span>:<\/strong> Use the \u201cweighted\u201d load-balancer feature. One can apply a weight (from 0 to 255, relatively) on multiple load-balancers to distribute traffic to multiple underlying targets.<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Challenge<\/span>:<\/strong> But a limitation on Route53 aliasing to CloudFront distributions forbids multiple aliases for the same domain name. Aliasing with a wildcard (*.<a href=\"http:\/\/taketurns.app\/\">taketurns.app<\/a>) is a well-known web-described hack, but it does not allow us to define multiple environments domain names (<a href=\"http:\/\/integration.taketuns.app\/\">integration.taketuns.app<\/a>, <a href=\"http:\/\/testing.taketurns.app\/\">testing.taketurns.app<\/a>, etc.) which is mandatory for us.<\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"has-small-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Option 2<\/span>: <\/strong>Use <a href=\"https:\/\/aws.amazon.com\/lambda\/edge\/\">Lambda@Edge.<\/a> Lambda@Edge is an interceptor on each request and can do a distribution\/ redirection depending on any custom rules (geographic etc.).<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Challenge<\/span>: <\/strong>But it uses cookies to remain sticky for each requester (and we do not want to use cookies) plus adds a systematic over-consumption (time + cost)<br><\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<p class=\"has-small-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Option 3<\/span>:<\/strong> CloudFront Staging distribution. AWS introduced in November 2022 non-production distribution which can co-exist with active ones. We could then prepare the next version in staging, then promote those to production ones.<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Challenge<\/span>: <\/strong>But it uses cookies to remain sticky for each requester (and we do not want to use cookies) plus adds a systematic over-consumption (time + cost)<\/p>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>How would you summarize the challenge? <\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Sure.&nbsp; If we choose to go with a CDN, what we gain on cluster consumption, we\u2019ve lost on the deployment capabilities\u2013especially the smoothness of version management.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In these kinds of cases, what I try to do is to <em>prendre de la hauteur<\/em>, or take a step back and reconsider the problem with <a href=\"https:\/\/en.wikipedia.org\/wiki\/KISS_principle\">KISS<\/a> in mind (Keep It Simple, Stupid).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We focused on what we really needed:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Folder\/file system structure representing multiple versions<\/li>\n\n\n\n<li>Structure representing the actual one<\/li>\n\n\n\n<li>Easy mechanism to switch from one version to another<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Au bon vieux temps <\/em>(in the good old days), we used to manage this with symbolic links. Does S3 (where we host our static content) provide this possibility? Nope!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>What was the path forward?<\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can manage all of this with simple mechanisms:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Copy version A and version B in a dedicated file structure (subfolders A and B, respectively) of the S3 bucket and tag the resources accordingly.<\/li>\n\n\n\n<li>If version A is the active one, the content of folder A will be copied into the root folder of the S3 bucket.<\/li>\n\n\n\n<li>CloudFront distribution always serves the content of the root folder. It uses cache mechanisms, so no further requests to S3 root content will be performed once at least one user has requested access to the static content via CloudFront distribution.<\/li>\n\n\n\n<li>If we want to enable version B, we only copy the content of version B to the root folder. Using S3 versioning, we do not lose any elements. All users keep on using version A thanks to CloudFront caching.<\/li>\n\n\n\n<li>Once the copy is done, we use the CloudFront invalidation mechanism, which will empty the cache and force reload of version B for all users.<\/li>\n\n\n\n<li>To go back to version A, either you can process the same way, redeploying version A or removing B components\/version using the tags applied and a more cherry-picked approach.<\/li>\n\n\n\n<li>You can keep as many versions as you want and define a purge mechanism (based again on applied tags) to keep only the version depth you desire.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Wow! Simple and cost-effective! <\/strong><strong><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Yes!&nbsp; In conclusion, we managed to: change the way our front end is hosted and reduce Kubernetes costs while keeping our super smooth deployment capabilities!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"has-medium-font-size wp-block-paragraph\"><strong><span style=\"text-decoration: underline;\">Notes:<\/span><\/strong><\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\" id=\"1\">[1] The artwork used in the meme is <a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:Thomas_Christian_Wink_-_Diogenes_and_Alexander.jpg\">Christian Winck:<\/a> <a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:Thomas_Christian_Wink_-_Diogenes_and_Alexander.jpg\"><em>Alexander der Gro\u00dfe und Diogenes, 1782<\/em><\/a> . The image alludes to the story of Diogenes of Sinope, a Greek philosopher, and Alexander the Great, a Macedonian king and military leader. The story goes that Alexander was quite impressed with Diogenes (one of the most prominent figures in the Cynic school of philosophy) and offered to grant him any favor or request. Diogenes, who was sunbathing, simply said, <em>\u00ab\u00a0<\/em><em>\u1f00\u03c0\u1f78<\/em><em> <\/em><em>\u03c4\u03bf\u1fe6<\/em><em> <\/em><em>\u1f21\u03bb\u03af\u03bf\u03c5<\/em><em> <\/em><em>\u03bc\u03b5\u03c4\u03ac\u03c3\u03c4\u03b7\u03b8\u03b9<\/em><em>\u00ab\u00a0, <\/em>which translates to \u201cStep out of the sun,\u201d since Alexander was blocking the sun.<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\" id=\"2\">[2] Event-driven architectures are one in which the software components communicate asynchronously through events. Events are when something changes (i.e., changes state) or something occurs. It\u2019s different from the classic request-response style, where the communications are synchronous.<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\" id=\"3\">[3] Microservices is an architectural style for developing software systems by breaking them down into small, independent, and loosely coupled components or services (they\u2019re small or micro!).&nbsp; Each microservice is responsible for specific functionality and can be deployed, updated, or scaled independently.<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\" id=\"4\">[4] Micro-front end \u2026 it\u2019s microservices for the front end!<\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\" id=\"5\">[5] <em>\u00ab\u00a0Tout va pour le mieux dans le Meilleur des mondes\u00a0\u00bb <\/em>is from Voltaire\u2019s <em>Candide (1759).<\/em> It translates to \u00ab\u00a0All is for the best in the best of all possible worlds,\u201d which is the catchphrase of Dr. Pangloss, the extreme optimist, who suggests that everything that happens, even if it\u2019s negative, is ultimately for the best and serves a greater purpose. (fwiw, our French team enjoys quoting Voltaire and to that we say:<em>Tout va pour le mieux dans le Meilleur des mondes!<\/em>). The artwork used in the meme is an illustration for <a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:Candide-L%27optimisme-Ch._01.png\"><em>Candide; ou, L&rsquo;optimisme<\/em><\/a><a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:Candide-L%27optimisme-Ch._01.png\">; 1900<\/a><\/p>\n\n\n\n<p class=\"has-small-font-size wp-block-paragraph\" id=\"6\">[6] A content delivery network (CDN) is a geographically distributed group of servers that caches content close to end users.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article has been originally published on TakeTurns blog as the first in an infrequent series of notes from TakeTurns engineering team about the technology behind TakeTurns. In this interview Conrad Chuang and Fabien Bontemps discuss how we migrated our front-end services from our Amazon Elastic Kubernetes Service (EKS) Cluster to Amazon CloudFront. A Cynical [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"categories":[2],"tags":[4,5,6,7],"class_list":["post-6","post","type-post","status-publish","format-standard","hentry","category-architecture","tag-aws","tag-cdn","tag-finops","tag-kubernetes"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/timeforarchi.com\/index.php?rest_route=\/wp\/v2\/posts\/6","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timeforarchi.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timeforarchi.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timeforarchi.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/timeforarchi.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=6"}],"version-history":[{"count":0,"href":"https:\/\/timeforarchi.com\/index.php?rest_route=\/wp\/v2\/posts\/6\/revisions"}],"wp:attachment":[{"href":"https:\/\/timeforarchi.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=6"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timeforarchi.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=6"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timeforarchi.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=6"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}