<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: fme Group</title>
    <description>The latest articles on Forem by fme Group (@fmegroup).</description>
    <link>https://forem.com/fmegroup</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F5595%2F1a54f357-3a74-4f10-91ff-9317ecb930ae.jpg</url>
      <title>Forem: fme Group</title>
      <link>https://forem.com/fmegroup</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/fmegroup"/>
    <language>en</language>
    <item>
      <title>Managing AWS Lambda Versions with AWS Step Functions: A comprehensive guide</title>
      <dc:creator>Maureen Plank</dc:creator>
      <pubDate>Fri, 19 Apr 2024 12:44:30 +0000</pubDate>
      <link>https://forem.com/fmegroup/managing-aws-lambda-versions-with-aws-step-functions-a-comprehensive-guide-2lfa</link>
      <guid>https://forem.com/fmegroup/managing-aws-lambda-versions-with-aws-step-functions-a-comprehensive-guide-2lfa</guid>
      <description>&lt;p&gt;Each AWS account has a regional default storage limit for Lambda and Lambda Layers (Zip archives) of 75 GB. This sounds a lot, and it can even be increased by demand, but many functions combined with frequent deployments might lead to storage pollution quite quickly. Therefore, it makes sense to clean up old deployment packages regularly. This blogpost introduces an AWS Step Functions state machine that is designed to automate the identification and deletion of older Lambda function versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting point
&lt;/h2&gt;

&lt;p&gt;AWS creates a new version for each lambda function deployment and keeps them all available – just in case an older one is needed in future. Every deployment package is stored and counts to the overall size limit of 75 GB. Even though this amount of storage is quite large it is advisable (and recommended by AWS) to clean up older versions which are no longer needed on a regular basis.&lt;/p&gt;

&lt;p&gt;As of now, AWS does not provide a direct solution to manage these versions, prompting developers to create custom solutions. One example of a custom solution is the use of a Lambda function like the Lambda Janitor by Yan Cui. Our idea for an implementation was a state machine – an automated and visual solution for the Lambda version management.&lt;/p&gt;

&lt;p&gt;This state machine handles all Lambda functions (optionally targeting specific ones based on predefined criteria) and deletes / removes old versions based on threshold which determines how many versions should be kept.&lt;br&gt;
A visual representation of the final workflow as well as a brief explanation of the most important steps is given below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpfkb3e0pc4d29jhtw6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpfkb3e0pc4d29jhtw6r.png" alt="Image description" width="800" height="910"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieving and filtering the function names
&lt;/h2&gt;

&lt;p&gt;The workflow begins with calling and listing the names of all existing Lambda functions within the AWS region. The “lambda:listFunctions” API call returns up to 50 items per call and a marker token in case more results are available. To take this into account the paginator pattern after the “Iterate Functions” map state is being used.&lt;/p&gt;

&lt;p&gt;For the first step we transform the result with the ResultSelector to extract only the function names. By doing that we get an output like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdonxvldv3nguksytte4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdonxvldv3nguksytte4g.png" alt="Image description" width="392" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To iterate through the array of lambda function names in the list, we use a Map state, which allows us to set a concurrency limit of 5. This limit helps to control the number of simultaneous requests and prevents throttle calls.&lt;/p&gt;

&lt;p&gt;In our case we just want to clean up versions of Lambda functions which belong to our project. Others which are managed by the platform team need to stay untouched.&lt;/p&gt;

&lt;p&gt;The Lambda functions from our project have a specific prefix in the name, that makes it easy for us to filter through all functions in the region. To do that, we added a “Choice” step to determine whether the state machine should continue the version cleanup or ignore the current Lambda function. This prefix can also be retrieved from the state machine invocation event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xpbip7hzwg6kooainvo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xpbip7hzwg6kooainvo.png" alt="Image description" width="473" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting all the available versions
&lt;/h2&gt;

&lt;p&gt;In the following step, all available versions are retrieved using the “lambda:listVersionsByFunction” API call. The outcome of this step is shown below – a list of all versions and the name of the function currently processed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ajx4ivll0tlrt2s5fck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ajx4ivll0tlrt2s5fck.png" alt="Image description" width="547" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keeping the function name in this step and adding it to the ResultPath is mandatory because we will need it again in a few steps when deleting the versions.&lt;/p&gt;

&lt;p&gt;The “lambda:listVersionsByFunction” API call returns up to 50 items and a marker token to retrieve more results. To take care of the pagination – the paginator pattern would have been required again. However, we have decided not to implement it to keep the workflow simpler.&lt;/p&gt;

&lt;p&gt;There are not that many deployments in our environment that we would reach more than 50 versions per Lambda function before the workflow runs the next time. In addition, the workflow which is triggered by an AWS EventBridge scheduler could run more often to avoid this situation – for instance, every day instead of every week – just as an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing the $LATEST version and get version count
&lt;/h2&gt;

&lt;p&gt;The Lambda API returns the versions in a way that the latest (called $LATEST 😊) one comes first followed by all other versions in ascending order – from oldest to newest. We decided not to rely on this implicit order but to exclude the $LATEST version explicitly from all further processing steps as well as to sort the remaining version numbers – just in case AWS is going to change the response format.&lt;/p&gt;

&lt;p&gt;Unfortunately, AWS Step Functions does only provide a limited set of array and JSON processing functions – so called intrinsic functions. A Lambda function is required to perform the necessary work. Hopefully, AWS adds some more power to the intrinsic function’s palette so that these kind of simple helper functions will no longer be necessary in future.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiws1sa2bpwh14sm6g42l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiws1sa2bpwh14sm6g42l.png" alt="Image description" width="541" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The outcome of this step consists of the lambda function name, the array of versions to be processed and their count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check number of available versions and remove outdated ones
&lt;/h2&gt;

&lt;p&gt;In the next step, the state machine compares the number of available versions against a threshold. This is done with a “Choice” State. In our case we wanted to always keep the three most recent versions + the $LATEST one, so the “version_count” is compared with 3:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37g2zdnyftyvma6c3mfk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37g2zdnyftyvma6c3mfk.png" alt="Image description" width="645" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the number of available versions exceeds the threshold we set, the oldest version (first position in the array) is deleted using the “lambda: deleteFunction” API call in the step “Delete oldest version”.&lt;/p&gt;

&lt;p&gt;After that, a modification of the payload is required as the deleted version number and the version count itself needs to be adapted. A “Pass” state is used to remove the oldest version (the first position in the array with the versions) and to decrease the version counter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfj3cyrjo7g3jf83q3b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbfj3cyrjo7g3jf83q3b2.png" alt="Image description" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The execution keeps checking and deleting old versions until their number has reached the threshold value (three in this example).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvcfz71x4sddmq7j61el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvcfz71x4sddmq7j61el.png" alt="Image description" width="641" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One remaining thing to consider related to Lambda Aliases&lt;/p&gt;

&lt;p&gt;A Lambda alias reference one or two function versions which cannot be deleted if the alias exists. It is possible to retrieve all aliases of a function via the “lambda:listAliases” API call. One way to make sure to take aliases into account would be to get all involved versions and to remove them from the versions array before the version deletion action. This requires some custom code in a Lambda function.&lt;/p&gt;

&lt;p&gt;Another option – which we have implemented – consists in defining an error handler for the “Delete old version” step. This one captures the “Lambda.ResourceConflictException” which is thrown when trying to delete a version which cannot be deleted for instance because of an alias reference. This error handling makes sure that the state machine does not fail in case of alias references or other unforeseen problems when trying to perform the deletion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30sq54bm8wp5z55ovisj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F30sq54bm8wp5z55ovisj.png" alt="Image description" width="574" height="895"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The state machine finishes after all Lambda functions which have been supposed to be processed do not possess more than the most recent versions – all others have been deleted. This mechanism helps to prevent deployment package pollution when used regularly.&lt;/p&gt;

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

&lt;p&gt;In this guide on managing AWS Lambda versions with AWS Step Functions, I’ve shown how developers can leverage automation to streamline the management of Lambda function versions. By automating these processes, teams can focus more on development rather than maintenance, maintaining operational efficiency and resource optimization. Overall, this solution provides a practical, visual tool for managing Lambda functions effectively, helping users navigate the complexities of cloud operations with greater ease.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>stepfunctions</category>
      <category>lambda</category>
    </item>
    <item>
      <title>AWS documentation and service quotas are your friends – do not miss them!</title>
      <dc:creator>Thomas Laue</dc:creator>
      <pubDate>Fri, 06 Oct 2023 14:31:16 +0000</pubDate>
      <link>https://forem.com/fmegroup/aws-documentation-and-service-quotas-are-your-friends-do-not-miss-them-1n43</link>
      <guid>https://forem.com/fmegroup/aws-documentation-and-service-quotas-are-your-friends-do-not-miss-them-1n43</guid>
      <description>&lt;p&gt;Who loves reading docs? Probably not that many developers and engineers. Coding and developing are so much more exciting than spending hours reading tons of documentation. However just recently I was taught again that this is one of the big misconceptions and fallacies -- probably not only for me.&lt;/p&gt;

&lt;p&gt;AWS provides an extensive documentation for each service which contains not just a general overview but, in most cases, a deep knowledge of details and specifies related to an AWS service. Most service documentations consist of hundreds of pages including a lot of examples and code snippets which are quite often helpful -- especially related to IAM policies. It is not always easy to find the relevant pieces for a specific edge case or it might be missing from time to time, but overall AWS have done a great job documenting their landscape.&lt;/p&gt;

&lt;p&gt;Service quotas which exist for every AWS service are another part which should not be missed when either starting to work with a new AWS service or to use one more extensively. Many headaches and lost hours spent to debug an issue could be avoided by taken these quotas into account right from the start. Unfortunately, this lesson is too easy to forget like&lt;br&gt;
will be shown in the following example.&lt;/p&gt;

&lt;p&gt;In a recent project, AWS DataSync was used to move about 40 million files from an AWS EFS share to a S3 bucket. The whole sync process should be repeated from time to time after the initial sync to take new and updated files into account. AWS DataSync supports this scenario by applying an incremental approach after the first run.&lt;/p&gt;

&lt;p&gt;One DataSync location was created for EFS and another one for S3 and both where sticked to gether by an DataSync task which configures among other things the sync properties. The initial run of this task went fine. All files were synced after about 9 hours.&lt;/p&gt;

&lt;p&gt;Some days later a first incremental sync was started to reflect the changes which had happened on EFS since the first run. The task went into the preparation phase but broke after about 30 minutes with a strange error message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2WTT-UGk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zka9hs2ri82cpk8gbmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2WTT-UGk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6zka9hs2ri82cpk8gbmg.png" alt="AWS Cloudtrail extract showing a strange error message related to AWS DataSync" width="800" height="143"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Cannot allocate memory" -- what are you trying to tell me? No memory setting was configured in the DataSync task definition as no agent was involved. The first hit on Google shed some light on this problem by redirecting me to the documentation of AWS DataSync&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y0HvVXSQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0n18uymki8fgbr2sx22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y0HvVXSQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w0n18uymki8fgbr2sx22.png" alt="Explanation of error message in AWS DataSync documentation" width="800" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;which contains a link to the DataSync task quotas:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BTOmCL-6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmcqpgjz15de4j9v37qi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BTOmCL-6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mmcqpgjz15de4j9v37qi.png" alt="Extract of AWS DataSync quotas" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apparently, 40 million files are way too much for one task as only 25 million are supported when transferring files between AWS Storage services. A request to the AWS support confirmed as well that problem was related to the large number of files. I have no idea why the initial run was able to run through but at least the follow up one failed. Splitting up the task into several smaller ones solved this issue so that the incremental run could finally be succeeded as well.&lt;/p&gt;

&lt;p&gt;Nevertheless, some hours were lost even though we learned something new.&lt;/p&gt;

&lt;p&gt;Lessons learned -- again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Embrace the docs -- even though they are really extensive!&lt;/li&gt;
&lt;li&gt;  Take the service quotas into account before starting to work and while working with an AWS service. They will get relevant one day -- possibly earlier than later!&lt;/li&gt;
&lt;li&gt;  AWS technical support really like to help and is quite competent. Do not hesitate to contact them (if you have a support plan available).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>cloudcomputing</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Azure CosmosDB — why technology choices matter</title>
      <dc:creator>Jens Goldhammer</dc:creator>
      <pubDate>Wed, 09 Aug 2023 05:48:03 +0000</pubDate>
      <link>https://forem.com/fmegroup/azure-cosmosdb-why-technology-choices-matter-14j1</link>
      <guid>https://forem.com/fmegroup/azure-cosmosdb-why-technology-choices-matter-14j1</guid>
      <description>&lt;p&gt;Some months ago, my colleague Florian and me joined a development team of one of our clients. We are involved as architects and engineers of the application used in their retail stores.&lt;/p&gt;

&lt;p&gt;The client currently migrates the core software from runni ng decentralized in each retail store (with its own databases) to a central solution. They heavily invested in Microsoft Azure as a Cloud provider and are moving more and more workloads to the Azure Cloud.&lt;/p&gt;

&lt;p&gt;The client currently uses MSSQL databases in combination with the open-source Firebird database and has started to migrate data into the cloud. They have decided to use Cosmos DB as standard database for all new services in the cloud some years ago as it was the cheapest choice from their point of view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2JCNcOiz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obkfadxlcenxqeeczsc2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2JCNcOiz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obkfadxlcenxqeeczsc2.png" alt="Image description" width="423" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Azure Cosmos DB?
&lt;/h1&gt;

&lt;p&gt;Azure Cosmos DB is the solution of Microsoft for fast NoSQL databases. For those who live in the AWS (Amazon Web Services) world, Cosmos DB can be compared to the service DynamoDB. You can learn more about Cosmos DB here: &lt;a href="https://azure.microsoft.com/en-us/products/cosmos-db"&gt;https://azure.microsoft.com/en-us/products/cosmos-db&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j1139z56--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz3eorhfltnxmb5oxx06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j1139z56--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz3eorhfltnxmb5oxx06.png" alt="Image description" width="800" height="429"&gt;&lt;/a&gt;&lt;br&gt;
Source: &lt;a href="https://azure.microsoft.com/en-us/products/cosmos-db"&gt;https://azure.microsoft.com/en-us/products/cosmos-db&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When working with Cosmos DB, you have to forget all the things you learned in the relational database world. To design a good data model, you need to learn how to design a data model depending on your future access patterns to your data, because the performance of Cosmos DB depends on the partitions. Therefore you must put more effort into the data modelling upfront. You can find more about here: &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/modeling-data"&gt;https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/modeling-data&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cosmos DB instances can be created on demand and can be used in many programming languages.&lt;/p&gt;

&lt;p&gt;The unique point of Cosmos DB — in comparison to traditional relational databases — is the distribution of the stored data around the world, the on-demand scalability and the effortless way to get data out of it. Due to the ensured low response times Cosmos DB allows multiple use cases in the web, mobile, gaming and IoT applications to handle many reads and writes.&lt;/p&gt;

&lt;p&gt;Further use cases can be found here: &lt;a href="https://learn.microsoft.com/en/azure/cosmos-db/use-cases"&gt;https://learn.microsoft.com/en/azure/cosmos-db/use-cases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having joined the project as an architect and engineer, I was critical of using Azure Cosmos DB from the start, as I am a big fan of relational databases, especially for transactional data. My Cosmos DB journey began with writing a centralized device service to store clients’ purchased devices. We have used Azure Functions to implement the business logic on top of Azure Cosmos DB to retrieve and store the data.&lt;/p&gt;

&lt;h1&gt;
  
  
  Structure of Azure Cosmos DB
&lt;/h1&gt;

&lt;p&gt;Microsoft allows their customers to create several Cosmos DB instances in one Azure tenant. You can compare it to a database which holds several database tables. These instances can be used to separate workloads for different teams, stages or use cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cr1S4Sfl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4q0qfsc1yfudomsgj0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cr1S4Sfl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4q0qfsc1yfudomsgj0w.png" alt="Image description" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, in the document database world you can model your data without a specific schema. You can create your own JSON structures which makes it flexible as well. Often the idea is to combine different data into one item to allow fast reads. To reference data in other domains, you can use unique identifiers like the property id in the customer object.&lt;/p&gt;

&lt;p&gt;You can find more about the structure of Azure Cosmos DB here: &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/account-databases-containers-items"&gt;https://learn.microsoft.com/en-us/azure/cosmos-db/account-databases-containers-items&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Accessing data
&lt;/h1&gt;

&lt;p&gt;Azure provides multiple ways to query data from Azure Cosmos DB.&lt;/p&gt;

&lt;p&gt;The following interfaces are possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  NoSQL API&lt;/li&gt;
&lt;li&gt;  MongoDB API&lt;/li&gt;
&lt;li&gt;  Cassandra API&lt;/li&gt;
&lt;li&gt;  Gremlin API&lt;/li&gt;
&lt;li&gt;  Table API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An overview can be found here: &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/choose-api"&gt;https://learn.microsoft.com/en-us/azure/cosmos-db/choose-api&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We started to use the NoSQL API in our project with the SQL like interface. It was an easier migration path coming from SQL based relational databases in comparison to the other interfaces. To access the data, you can also use the Azure Portal with the Data Explorer — the data explorer allows you to access your collections, query and manipulate data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SNnqWtuc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ad97nq1b88pvatz08ms1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SNnqWtuc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ad97nq1b88pvatz08ms1.png" alt="Image description" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Migration / Import mass data
&lt;/h1&gt;

&lt;p&gt;Cosmos DB allows to import data flexibly with different APIs.&lt;br&gt;
There are two options at the moment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Import via Azure Datafactory service which can be used out of the box&lt;/li&gt;
&lt;li&gt;  Import via custom CLI Tool which uses the cosmosDB API -&amp;gt; this tool needs to be developed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One way to import mass data is via Azure Data Factory which allows to pipeline data and map data from various sources and import into a Cosmos DB collection. We have used this mechanism a lot to transfer data from on-premises relational databases into the cloud and migrate data via pipelines into Cosmos DB collections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2BG2Ul3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jcxu1at219jd051tqqk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2BG2Ul3i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jcxu1at219jd051tqqk0.png" alt="Image description" width="800" height="435"&gt;&lt;/a&gt;&lt;br&gt;
Source: Azure Portal example pipeline&lt;/p&gt;

&lt;p&gt;Azure Data Factory works quite well, is fast and very flexible, but has its own drawbacks and challenges. Unfortunately, this topic is a subject in itself, so we cannot go into more detail here.&lt;/p&gt;

&lt;p&gt;In the past we have also written our own CLI tools; they are more flexible for the datamapping and can be reviewed easier by other team members. By using bulk import with parallel threads with the cosmos API you will be as fast as the importing data via Azure Datafactory.&lt;/p&gt;

&lt;p&gt;You can find a list of available SDKs here: &lt;a href="https://developer.azurecosmosdb.com/community/sdk"&gt;https://developer.azurecosmosdb.com/community/sdk&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Querying data
&lt;/h1&gt;

&lt;p&gt;Azure Cosmos DB provides several APIs to retrieve the data out of the containers. We have decided to use the SQL interface to get data out in the used Azure functions.&lt;/p&gt;

&lt;p&gt;You can for example take this SQL like query to select all devices of a customer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lOZkFA10--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sosl30hu1enrvoqqnqd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lOZkFA10--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sosl30hu1enrvoqqnqd1.png" alt="Image description" width="800" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This looks familiar, right?&lt;/p&gt;

&lt;p&gt;After some time, you notice that the SQL capabilities are limited as Azure Cosmos DB implements only a limited set of SQL specifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Cosmos DB does not allow to join items from different collections — it only allows to join the item with itself which means that you need to read data separately from different collections to join your data. The documentation says that you have to change your data model if you have needs for joining.&lt;/li&gt;
&lt;li&gt;  Cosmos DB provides functions as well, but you may know only a few of them and sometimes in a completely different way as you may know from SQL. You have to learn the cosmos specific syntax as there is no standard for querying data in NoSQL databases.&lt;/li&gt;
&lt;li&gt;  Cosmos DB has limited capabilities for the group-by with having clause. Sometimes there are workarounds, sometimes not.&lt;/li&gt;
&lt;li&gt;  Cosmos DB supports Limit and Offset, but this is very slow (as it is implemented) and you should use continuation tokens instead. Why? If you are interested to understand this, read here: &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/offset-limit"&gt;https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/offset-limit&lt;/a&gt; and &lt;a href="https://stackoverflow.com/questions/58771772/cosmos-db-paging-performance-with-offset-and-limit"&gt;https://stackoverflow.com/questions/58771772/cosmos-db-paging-performance-with-offset-and-limit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My experience was that you often found good workarounds or a complete cosmos specific way, but sometimes you didn’t find a solution which was a little bit frustrating.&lt;/p&gt;

&lt;p&gt;Nevertheless, the most painful issue was that CosmosDB only report errors with following message: “One of the input values is invalid.” in your query without a useful hint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--66Nacy9W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7p6d6qeawgt7v7193gz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--66Nacy9W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z7p6d6qeawgt7v7193gz.png" alt="Image description" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, I made the mistake of putting a semicolon at the end of the query.&lt;/p&gt;

&lt;h1&gt;
  
  
  Updating data
&lt;/h1&gt;

&lt;p&gt;Updating one or multiple rows in a relational database with one SQL statement is a common request for processing data.&lt;/p&gt;

&lt;p&gt;Azure Cosmos DB exactly allows to update one item within a container by using multiple requests.&lt;/p&gt;

&lt;p&gt;The procedure looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Retrieve the whole document you want to update&lt;/li&gt;
&lt;li&gt;  Update the fields you want to update in your application code&lt;/li&gt;
&lt;li&gt;  Write back the whole document to Cosmos DB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft provides a new SQL update API to update one item without reading it before. The syntax for updating data is driven by the JSON PATCH standard (&lt;a href="https://jsonpatch.com"&gt;https://jsonpatch.com&lt;/a&gt;). This feature was a long time in preview and now is generally available in Azure Cosmos DB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/partial-document-update-getting-started?tabs=dotnet"&gt;https://learn.microsoft.com/en-us/azure/cosmos-db/partial-document-update-getting-started?tabs=dotnet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mass updates to a larger set of documents cannot be done out of the box with one SQL statement. You must update each document separately. This limitation is a little bit surprising when you want to evolve your document schema.&lt;/p&gt;

&lt;p&gt;Yes, you can write a tool based on the bulk API. But you probably know that updating a lot of data is slow and involves much more effort instead of writing a single update query like in the relational data world.&lt;/p&gt;

&lt;h1&gt;
  
  
  Deleting data
&lt;/h1&gt;

&lt;p&gt;Deleting data in Azure Cosmos DB is generally possible by removing one item at a time via API. But there is unfortunately no support for SQL Delete-Statements!&lt;/p&gt;

&lt;p&gt;In general all limitations for mass operations in Cosmos DB may have reasons — for instance, the guaranteed response times for any action in the Cosmos DB. Operations on a bigger set of data might lead to higher execution times.&lt;/p&gt;

&lt;p&gt;But indeed this is an annoying point while writing and testing your software. Sometimes you need to remove specific data very quickly. One workaround is to drop the whole container and create your test data again, but often you have the case that you want to keep specific data in it.&lt;/p&gt;

&lt;p&gt;For example, we had to remove two million entries from a collection to repeat a migration, but wanted to keep other data in the collection. This action took half an hour by using a developed tool.&lt;/p&gt;

&lt;h1&gt;
  
  
  Transactions
&lt;/h1&gt;

&lt;p&gt;Azure Cosmos DB provides a simple transactional concept. It allows to group a set of operations in a batch operation. Unfortunately, the batch concept is not integrated nicely into the API as it does not allow to wrap your code into a transactional block like interfaces to relational databases allow.&lt;/p&gt;

&lt;p&gt;Additionally, transactions do not allow to update documents from different partitions which is understandable from a technical point of view, but this limits a lot. In our service we had the use case to update several documents at once from several partitions and we had to live without transactions in the end.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ecosystem &amp;amp; Community
&lt;/h1&gt;

&lt;p&gt;I was very surprised starting with Cosmos DB to find such limited resources, articles and tools around the platform. But I understood this situation quickly because Cosmos DB is an exclusive, commercial service of Microsoft and is not as popular as Amazon DynomoDB for example.&lt;/p&gt;

&lt;p&gt;Microsoft itself provides limited tooling only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the azure portal with the data explorer (&lt;a href="https://cosmos.azure.com"&gt;https://cosmos.azure.com&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;a visual studio code extension &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-cosmosdb"&gt;https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-cosmosdb&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;a CosmosDB emulator which runs natively under Windows &lt;a href="https://learn.microsoft.com/de-de/azure/cosmos-db/data-explorer"&gt;https://learn.microsoft.com/de-de/azure/cosmos-db/data-explorer&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The community has written some tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Cosmos DB Explorer for Windows &lt;a href="https://github.com/sachabruttin/Cosmos%20DBExplorer"&gt;https://github.com/sachabruttin/Cosmos DBExplorer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CosmicClone to clone data from one container to another &lt;a href="https://github.com/microsoft/CosmicClone"&gt;https://github.com/microsoft/CosmicClone&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately there is no big community around Cosmos DB like for example for PostgreSQL or MySQL/MariaDB. Anyways, most of the good, known database tools out there which are supporting several vendors do not support Azure Cosmos DB. Mostly because it works completely different than relational databases.&lt;/p&gt;

&lt;h1&gt;
  
  
  Advanced topics
&lt;/h1&gt;

&lt;p&gt;Additionally Azure Cosmos DB allows to use stored procedures. Wait — Are stored procedures not a thing from the last century? Why should we use it? Probably you will notice that you need to use stored procedures for some scenarios like a mass deletion of entries in your collection as this is not supported out of the box.&lt;/p&gt;

&lt;p&gt;Writing stored procedures is supported in Cosmos DB with JavaScript. As you may know, testing becomes challenging for this kind of code and besides that, most of the backend developers in our team are not familiar with JavaScript. Due to these challenges, we decided not to use them within the application apart from administrative purposes!&lt;/p&gt;

&lt;p&gt;There are many advanced topics for Cosmos DB like scaling, partition keys etc. — these topics need their own blog post. You can read more about that in the official documentation: &lt;a href="https://learn.microsoft.com/en-us/azure/cosmos-db/"&gt;https://learn.microsoft.com/en-us/azure/cosmos-db/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Using a document-based database is not a no-brainer. Document-based databases like Azure Cosmos DB are not a replacement for relational databases and it was never the intention.&lt;/p&gt;

&lt;p&gt;Yes, Azure Cosmos DB has its use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If you have the use case “write once — read many” (for example just store data with a stable structure), you can use it.&lt;/li&gt;
&lt;li&gt;  If you need global distribution of your data, you probably need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is that you do not have these requirements in general for business applications very oftem. In my opinion most of the applications do not need to scale this way (besides, you are Amazon, Microsoft, Netflix or another global player).&lt;/p&gt;

&lt;p&gt;On the other side Azure Cosmos DB has some heavy limitations when working with the data, especially if you want to evolve your schema. If you want to store relational data within Cosmos DB and have a lot of changes in the data over time, Cosmos DB makes it very complicated and is currently not a good choice from my point of view.&lt;/p&gt;

&lt;p&gt;Besides these considerations one task has become very, very important from the beginning: design how to model and to partition your data. But this is a story on its own.&lt;/p&gt;

&lt;h1&gt;
  
  
  Resources
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://medium.com/@yurexus/features-and-pitfalls-of-azure-cosmos-db-3b18c7831255"&gt;https://medium.com/@yurexus/features-and-pitfalls-of-azure-cosmos-db-3b18c7831255&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://mattruma.com/adventures-with-azure-cosmos-db-limit-query-rus/"&gt;https://mattruma.com/adventures-with-azure-cosmos-db-limit-query-rus/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://blog.scooletz.com/2019/06/04/Cosmos%20DB-and-its-limitations"&gt;https://blog.scooletz.com/2019/06/04/Cosmos DB-and-its-limitations&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>cosmosdb</category>
    </item>
    <item>
      <title>Microsoft Graph API- a practical example in python</title>
      <dc:creator>Torben Bruns</dc:creator>
      <pubDate>Thu, 23 Mar 2023 13:58:21 +0000</pubDate>
      <link>https://forem.com/fmegroup/microsoft-graph-api-a-practical-example-in-python-2eb4</link>
      <guid>https://forem.com/fmegroup/microsoft-graph-api-a-practical-example-in-python-2eb4</guid>
      <description>&lt;p&gt;Nothing is as constant as change." Following this theme, Microsoft is planning to discontinue Azure AD Graph in 2023 and introduce something new: Microsoft Graph. It will not only replace the former API but also enhance it with new capabilities. Apart from interacting with Azure AD Graph, the new API can also communicate with Microsoft 365 products. If you want a successful pipeline run to post a message in a Microsoft Teams channel, Microsoft Graph can do it. And if an application needs to send emails to users, Microsoft Graph can also handle that.&lt;/p&gt;

&lt;p&gt;To put it simply, Microsoft Graph is a REST-API and acts as gateway to numerous services Microsoft365 offers [1].&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Microsoft Graph in your environment
&lt;/h2&gt;

&lt;p&gt;To begin with, you need an active subscription for Microsoft 365. The actual plan does not matter, as even the Basic tier is sufficient. If you want to get a first look at the API's capabilities, check out Microsoft Graph Explorer. (&lt;a href="https://developer.microsoft.com/en-us/graph/graph-explorer" rel="noopener noreferrer"&gt;https://developer.microsoft.com/en-us/graph/graph-explorer&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo9kexfyouuydyjxdwl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgo9kexfyouuydyjxdwl9.png" alt="MS Graph Explorer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing our own application
&lt;/h2&gt;

&lt;p&gt;If you want to create your own application, let's get started. Let's consider an application that monitors inventory stock. As soon as the stock falls below a certain number, an email should be sent to the orders team.&lt;/p&gt;

&lt;p&gt;We will focus on the following things:&lt;br&gt;
• Registering an application in Azure AD&lt;br&gt;
• Setting up a Graph Client in Python&lt;br&gt;
• Sending an email&lt;/p&gt;

&lt;p&gt;The image below visualizes what we want to achieve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g1t9k2im6ncagfr8quk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4g1t9k2im6ncagfr8quk.jpeg" alt="Script overview"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Monitoring the stock is not covered within this article.&lt;/p&gt;
&lt;h2&gt;
  
  
  AzureAD Registration
&lt;/h2&gt;

&lt;p&gt;There are two types of permissions in AzureAD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delegated permissons&lt;/li&gt;
&lt;li&gt;Application permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With delegated permissions the application acts as a logged in user like the Graph Explorer does. Application permissions on the other hand allow the app to act as own entity rather than on behalf of a user. Downside is that for this type of permission you need administrative rights.&lt;br&gt;
After this short explanation on types of permissions in Azure let us begin with registering an application in AzureAD.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to portal.azure.com and login with your credentials&lt;/li&gt;
&lt;li&gt;Click on Azure Active Directory&lt;/li&gt;
&lt;li&gt;From the left side select App Registrations&lt;/li&gt;
&lt;li&gt;Click on New Registration and copy the configuration from below image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82nviq6sbe5krhmctqaq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82nviq6sbe5krhmctqaq.png" alt="App Registration Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The supported account types can be adjusted to your needs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click on the newly created app registration&lt;/li&gt;
&lt;li&gt;Select Authentication from the menu on the right&lt;/li&gt;
&lt;li&gt;Add a new Authentication of type Mobile and desktop application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our example to work enter below configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70rjddj9jl26njuqln3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F70rjddj9jl26njuqln3x.png" alt="Example Config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch the slider for Allow Public Client Flows to the “on” position and save&lt;/li&gt;
&lt;li&gt;From the menu select Certificates &amp;amp; Secrets&lt;/li&gt;
&lt;li&gt;Add a new client secret and remember to save it as it is only shown once&lt;/li&gt;
&lt;li&gt;Go to API permissions and select permissions like shown below&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9n38donnmleectn7imj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi9n38donnmleectn7imj.png" alt="Permissions in Azure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is all, the configuration of the application in the Azure portal is done.&lt;/p&gt;

&lt;p&gt;Save the following values for later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client id&lt;/li&gt;
&lt;li&gt;Client secret&lt;/li&gt;
&lt;li&gt;Tenant id&lt;/li&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the purpose of simplicity, I used Python. Microsoft offers SDK’s for different languages like &lt;code&gt;C#&lt;/code&gt;, &lt;code&gt;Java&lt;/code&gt;, &lt;code&gt;Go&lt;/code&gt; and &lt;code&gt;PHP&lt;/code&gt;. Still, all that is necessary is implementing HTTP-Calls. If there is no SDK for your specific language, you are only losing some comfort.&lt;/p&gt;

&lt;p&gt;Let us have a look at the source code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57jom1e6y7wt3o6iy6t0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57jom1e6y7wt3o6iy6t0.png" alt="Python Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The packages &lt;code&gt;msgraph&lt;/code&gt; and &lt;code&gt;azure&lt;/code&gt; make it relatively simple to implement a Microsoft Graph API client. First, a GraphClient is created, which then queries the API for a list of users. Then, we call the "send_mail" function, which takes a GraphClient and userlist as inputs. It sends an email with some example text on behalf of the first user found in the list using their Outlook account to the recipients listed under the keyword "toRecipients". If you want to know the exact mechanism, please refer to Microsoft’s documentation [4].&lt;/p&gt;

&lt;p&gt;A mail is not limited to plain text, it is also possible to send attachments through a call to the url&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;/users/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;userPrincipalName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;/mailFolders/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;/messages/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;/attachments&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of the above call to the API looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynp7y0yxuq3bgt3pzvdd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fynp7y0yxuq3bgt3pzvdd.png" alt="Email result"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The Graph API is a powerful gateway to the services offered by Microsoft. There are numerous applications imaginable, such as status updates on pipeline runs through Teams, email notifications like in the example, or user management within Azure AD.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/de-de/graph/overview" rel="noopener noreferrer"&gt;MS Graph Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/de-de/graph/outlook-things-to-know-about-send-mail" rel="noopener noreferrer"&gt;MS Outlook Graph API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/mail-api-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Mail API overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mattfeltonma/query-microsoft-graph" rel="noopener noreferrer"&gt;MS Graph example queries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/permissions-consent-overview" rel="noopener noreferrer"&gt;Permission overview&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>microsoftgraph</category>
      <category>python</category>
      <category>cloud</category>
    </item>
    <item>
      <title>A cross account cost overview dashboard powered by Lambda, Step Functions, S3 and Quicksight</title>
      <dc:creator>Thomas Laue</dc:creator>
      <pubDate>Fri, 17 Feb 2023 13:01:44 +0000</pubDate>
      <link>https://forem.com/fmegroup/a-cross-account-cost-overview-dashboard-powered-by-lambda-step-functons-s3-and-quicksight-22c1</link>
      <guid>https://forem.com/fmegroup/a-cross-account-cost-overview-dashboard-powered-by-lambda-step-functons-s3-and-quicksight-22c1</guid>
      <description>&lt;p&gt;Keeping an eye on cloud spendings in AWS or any other cloud service&lt;br&gt;
provider is one of the most important parts of every project team's&lt;br&gt;
work. This can be tedious when following the AWS recommendation and best&lt;br&gt;
practice to split workloads over more than one AWS account. Consolidated&lt;br&gt;
billing -- a feature of AWS Organization would help in this case -- but&lt;br&gt;
access to the main/billing account is very often not granted to project&lt;br&gt;
teams and departments for good reason.&lt;/p&gt;

&lt;p&gt;In this article, a solution is presented which allows to automatically&lt;br&gt;
collect billing information from various accounts to present them in a&lt;br&gt;
concise format in one or more AWS Quicksight dashboards. Before starting&lt;br&gt;
to go into the details, let's look shortly on the available AWS services&lt;br&gt;
for cost management and the difficulty when working with more than one&lt;br&gt;
account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Cost Management Platform and Consolidated Billing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS provides a complete toolset consisting of AWS Billing, Cost&lt;br&gt;
Explorer, and other sub services. They offer functionalities to get a&lt;br&gt;
focused overview about costs and usages as well as tools to drill down&lt;br&gt;
into the details of most services. The tooling is sufficient to get the&lt;br&gt;
job done when working with one AWS account even though the user&lt;br&gt;
experience might not be as awesome as provided by other more dedicated&lt;br&gt;
and focused 3^rd^ party tools. Special IAM permissions are needed to&lt;br&gt;
access all this information to tackle the governance part as well.&lt;/p&gt;

&lt;p&gt;AWS Organization which rules all AWS accounts associated with it&lt;br&gt;
provides some extended functionalities (aka. Consolidated billing) to&lt;br&gt;
centralize cost monitoring and management. However, in most companies&lt;br&gt;
and large enterprises only very few people are allowed to access the&lt;br&gt;
billing account. This does not help a project lead to get the required&lt;br&gt;
information easily. Depending on the number of AWS accounts used by&lt;br&gt;
team, someone who is allowed has either to login into every account&lt;br&gt;
regularly and check the costs or rely on "Cost and Usage" reports which&lt;br&gt;
can be exported automatically to S3. These reports are very detailed&lt;br&gt;
(maybe too much for simple use cases) and require some custom tooling to&lt;br&gt;
extract the required information.&lt;/p&gt;

&lt;p&gt;AWS has published a solution called &lt;a href="https://wellarchitectedlabs.com/cost/200_labs/200_cloud_intelligence/cost-usage-report-dashboards/"&gt;Cloud Intelligence&lt;br&gt;
Dashboards&lt;/a&gt; -- a collection of Quicksight dashboards which are among other data sources based on these cost and usage reports. Beside this one, company internal cost control tools -- sometimes bases on the same AWS services -- exist and can be "rented". All these solutions have their advantages and use cases -- but also drawbacks (mostly related to their costs and sometimes also due to overly large IAM permission requirements).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An approach for simple use cases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes it is fully sufficient to present some information in a&lt;br&gt;
concise manner to stay informed about the general trends and total&lt;br&gt;
amounts. In case something reveals to be strange or not to be in the&lt;br&gt;
expected range, a more detailed analysis can be performed using for&lt;br&gt;
instance the AWS tooling mentioned above.&lt;/p&gt;

&lt;p&gt;Following this idea, Step Functions, Lambda, S3 and Quicksight powers a&lt;br&gt;
small application which retrieves cost related data for every designated&lt;br&gt;
AWS account and stores it as a JSON file in S3. Quicksight which&lt;br&gt;
supports S3 as a data source directly reads this data and provides it to&lt;br&gt;
build one or more dashboards displaying various cost related diagrams&lt;br&gt;
and tables. This workflow (which is shown below) is triggered by an&lt;br&gt;
EventBridge rule regularly (e.g., once a day) so that up-to-date&lt;br&gt;
information is available.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--flgRn7YP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdaxojpy5f5b02zmpgxb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--flgRn7YP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xdaxojpy5f5b02zmpgxb.png" alt="StepFunctions workflow which coordinates the cost related data" width="427" height="684"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The „Data preparation" step provides the AWS account ids and names as input for the following &lt;em&gt;Map State&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vIfEMsvy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbm3d352zrubobplhl87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vIfEMsvy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbm3d352zrubobplhl87.png" alt="Input data for Map State" width="553" height="793"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Map State starts for every array element (= AWS account) an&lt;br&gt;
    instance of a Lambda function which assumes an IAM role in the&lt;br&gt;
    relevant account and queries the AWS Cost Center and AWS Budget APIs&lt;br&gt;
    to collect the required information: total current costs, costs per&lt;br&gt;
    service, cost forecasts...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject_lambda_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_event&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capture_lambda_handler&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
     &lt;span class="n"&gt;validate_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;assumed_role_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assume_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'arn:aws:iam::&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:role/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ROLE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"eu-central-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assumed_role_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ce"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="n"&gt;budget_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assumed_role_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"budgets"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arrow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="n"&gt;first_day_of_current_month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_start_and_end_date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;cost_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cost_and_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;TimePeriod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="s"&gt;"Start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_day_of_current_month&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
             &lt;span class="s"&gt;"End"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="p"&gt;},&lt;/span&gt;
         &lt;span class="n"&gt;Granularity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"MONTHLY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"UnblendedCost"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
     &lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="n"&gt;cost_per_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_cost_and_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;TimePeriod&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="s"&gt;"Start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;first_day_of_current_month&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
             &lt;span class="s"&gt;"End"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="p"&gt;},&lt;/span&gt;
         &lt;span class="n"&gt;Granularity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"MONTHLY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"UnblendedCost"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
         &lt;span class="n"&gt;GroupBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s"&gt;"Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"DIMENSION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"SERVICE"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
     &lt;span class="p"&gt;)&lt;/span&gt;

 &lt;span class="err"&gt;…&lt;/span&gt;

     &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="s"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
         &lt;span class="s"&gt;"current_month"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"current_year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"account_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"account_name"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"current_costs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ResultsByTime"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"Total"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"UnblendedCost"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  
         &lt;span class="s"&gt;"forecasted_costs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forecasted_cost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Budgets"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"BudgetLimit"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;"Amount"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;

     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;service_costs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;

     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The outcome of the Map State is an array consisting of the cost data retrieved from every AWS account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-02-09"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"current_month"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"current_year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2023&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"account_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"current_costs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"152.49"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"forecasted_costs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"468.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"budget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"700.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"11111111111x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Amazon DynamoDB"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;10.0002276717&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"AWS CloudTrail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.0943441333&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"AWS Lambda"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;30.24534433&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"AWS Key Management Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.8699148608&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"AWS Step Functions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5439809890&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"Amazon Relational Database Service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;16.1240975116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This data is stored as JSON file in a S3 bucket in the last workflow&lt;br&gt;
    step. Every file name contains the current date to make them unique.&lt;/p&gt;

&lt;p&gt;Apart from the StepFunctions workflow and the Lambda function which can&lt;br&gt;
for instance run in a dedicated AWS account to simplify the permission&lt;br&gt;
management, an IAM role needs to be deployed to every account whose&lt;br&gt;
costs should be retrieved. This role must trust the Lambda function and&lt;br&gt;
contain the necessary permissions to query AWS Cost Center and AWS&lt;br&gt;
Budget. An example written in Terraform is given below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"iam_assume_role_sts"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/iam/aws//modules/iam-assumable-role"&lt;/span&gt;
   &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 4.7.0"&lt;/span&gt;

   &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"query-cost-center-role"&lt;/span&gt;
   &lt;span class="nx"&gt;trusted_role_arns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query_cost_center_lambda_role_arn&lt;/span&gt;
   &lt;span class="p"&gt;]&lt;/span&gt;

   &lt;span class="nx"&gt;create_role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
   &lt;span class="nx"&gt;role_requires_mfa&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

   &lt;span class="nx"&gt;custom_role_policy_arns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query_cost_center_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
   &lt;span class="p"&gt;]&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"query_cost_center_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-aws-modules/iam/aws//modules/iam-policy"&lt;/span&gt;
   &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 4.7.0"&lt;/span&gt;

   &lt;span class="nx"&gt;name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Query-cost-center-policy"&lt;/span&gt;
   &lt;span class="nx"&gt;path&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
   &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This policy allows to query the AWS CostCenter"&lt;/span&gt;
   &lt;span class="nx"&gt;policy&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query_cost_center_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"query_cost_center_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="s2"&gt;"ce:GetCostAndUsage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="s2"&gt;"ce:GetCostForecast"&lt;/span&gt;
     &lt;span class="p"&gt;]&lt;/span&gt;

     &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="s2"&gt;"budgets:ViewBudget"&lt;/span&gt;
     &lt;span class="p"&gt;]&lt;/span&gt;

     &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
       &lt;span class="s2"&gt;"arn:aws:budgets::&lt;/span&gt;&lt;span class="k"&gt;${data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_caller_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:budget/project_budget"&lt;/span&gt;
     &lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Data visualization with Quicksight&lt;/em&gt;*&lt;/p&gt;

&lt;p&gt;Quicksight supports S3 as a direct data source -- only a manifest file&lt;br&gt;
containing a description of the data stored in the bucket is needed.&lt;br&gt;
This is quite handy for data whose structure does not change or only&lt;br&gt;
very seldom.&lt;/p&gt;

&lt;p&gt;A more involving setup including AWS Glue and AWS Athena might be&lt;br&gt;
beneficial in cases where either a lot of details (not only basic cost&lt;br&gt;
information) are queried, or a lot of different AWS services are used&lt;br&gt;
over time in the different AWS accounts. It might happen that Quicksight&lt;br&gt;
runs into problems when trying to load this kind of data as it is going&lt;br&gt;
to change constantly, and the manifest file requires a lot of updates. A&lt;br&gt;
Glue Crawler combined with an Athena table might be the better approach&lt;br&gt;
in such a scenario.&lt;/p&gt;

&lt;p&gt;As soon as a new dataset based on the S3 bucket has been created, one or&lt;br&gt;
several dashboards can be implemented. They can represent some overview&lt;br&gt;
data like it is done in the example below or go into more detail --&lt;br&gt;
depending on the specific requirements. How to create these dashboards&lt;br&gt;
is out of scope of this article but Quicksight offers enough tooling to&lt;br&gt;
start from simple to go a long way to sophisticated information display.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UZw58LUj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wr167vegkmuq3rln7pke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UZw58LUj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wr167vegkmuq3rln7pke.png" alt="Quicksight dashboard showing cost related details" width="880" height="524"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Quicksight dashboard can either be shared with individuals in need of&lt;br&gt;
this information or a scheduled email notification can be established.&lt;br&gt;
Quicksight will sent a mail to all specified recipients which can&lt;br&gt;
include an image of the dashboard as well as some data in CSV format.&lt;br&gt;
This feature helps a lot it is not always necessary to login to keep the&lt;br&gt;
costs under control. Simply by receiving an automated message for&lt;br&gt;
instance every day or just once a week can already help to stay&lt;br&gt;
informed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrapping up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cost monitoring is an important topic for every project -- from small to&lt;br&gt;
large. AWS offers various tools to stay up to date but this task is&lt;br&gt;
getting tedious when following AWS best practice and separating an&lt;br&gt;
application into different stages and AWS accounts. There are 3rd party&lt;br&gt;
or company-internal tools available which helps to overcome this&lt;br&gt;
situation, but it is not always possible to use them (especially in an&lt;br&gt;
enterprise setup) or they come with their own drawbacks.&lt;/p&gt;

&lt;p&gt;This blog post has presented a small-scale application which offers&lt;br&gt;
enough information and details to monitor the costs generated by small&lt;br&gt;
to medium size projects. It has its own limitations as it might not be&lt;br&gt;
powerful enough when dealing with tens or even hundreds of AWS accounts&lt;br&gt;
-- but this is normally not the typical setup of a project.&lt;/p&gt;

&lt;p&gt;Photo credits&lt;br&gt;
Photo of Anna Nekrashevich: &lt;a href="https://www.pexels.com/de-de/foto/lupe-oben-auf-dem-dokument-6801648/"&gt;https://www.pexels.com/de-de/foto/lupe-oben-auf-dem-dokument-6801648/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>cloud</category>
      <category>python</category>
    </item>
    <item>
      <title>How not to send all your money to AWS</title>
      <dc:creator>Thomas Laue</dc:creator>
      <pubDate>Fri, 07 Oct 2022 12:52:33 +0000</pubDate>
      <link>https://forem.com/fmegroup/how-not-to-send-all-your-money-to-aws-2kdn</link>
      <guid>https://forem.com/fmegroup/how-not-to-send-all-your-money-to-aws-2kdn</guid>
      <description>&lt;p&gt;The AWS environment has grown to a kind of universe providing more than 250 services over the last 20 years. Many applications which quite often easily use 5-10 or more of these services benefit from the rich feature set provided. Burdens which have existed for instance in local data centers like server management have been taken over by AWS to provide developers and builders more freedom to be more productive, creative and more cost effective at the end.&lt;/p&gt;

&lt;p&gt;Billing and cost management at AWS are one of the hotter topics which have been discussed and complained about throughout the internet. AWS provides tools like the &lt;a href="https://calculator.aws/#/" rel="noopener noreferrer"&gt;AWS Pricing Calculator&lt;/a&gt; which helps to create cost estimates for application infrastructure. Significant efforts have been spent over the last couple of years to improve the &lt;a href="https://aws.amazon.com/aws-cost-management/aws-billing/" rel="noopener noreferrer"&gt;AWS Billing Console&lt;/a&gt; in order to provide better insights about daily and monthly spendings. However, it still can be hard to get a clear picture as every service and quite often also every AWS region has its own pricing structure.&lt;/p&gt;

&lt;p&gt;At the same time, more and more cost saving options have been released. Depending on the characteristics and architecture of an application it can be astonishing easy to save a considerable amount of money with no or only limited invest in engineering time using some of the following tips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleanup resources and data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Probably the most obvious step to reduce costs is to delete unused resources like dangling EC2 or RDS instances, old EBS snapshots or AWS Backup files etc. S3 buckets containing huge amounts of old or unused data might as well be a good starting point to reduce costs.&lt;/p&gt;

&lt;p&gt;Beside not wasting money all these measures enhance the security at the same time: things which are no longer available cannot be hacked or leaked. AWS provides features like AWS Backup/S3 retention policies to support managing data lifecycle automatically so that not everything must be done manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Saving Plans&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Savings plans which come in different flavours were released about 3 years ago. They offer discount opportunities of up to 72 percent compared to On-Demand pricing when choosing the "EC2 Instance Savings" plan. Especially the more flexible "Compute Savings Plans" which still offers up to 66 percent discount is quite attractive as it covers not only EC2, but Lambda and Fargate as well.&lt;/p&gt;

&lt;p&gt;Workloads running 24/7 with a somehow predictable workload are mostly suited for this type of offering. Depending on the selected term length &lt;br&gt;
(1 or 3 years) and payment option (No, Partial or All Upfront) a fixed discount is granted for a commitment of a certain amount of dollars spent for compute per hour. Architectural changes like switching EC2 instance types or moving workloads from EC2 to Lambda or Fargate are possible and covered by the Compute Savings Plans.&lt;/p&gt;

&lt;p&gt;Purchasing savings plans requires a minimum of work with a considerable savings outcome especially as most workloads require some sort of compute which contribute significantly to the total costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reserved Instances and Reserved Capacity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, AWS does not offer Savings Plans for all possible scenarios or AWS services but other powerful discount options like &lt;a href="https://aws.amazon.com/rds/reserved-instances/" rel="noopener noreferrer"&gt;Amazon RDS Reserved Instances&lt;/a&gt; come to a rescue. Reserved Instances use comparable configuration options like Savings Plans and promise a similar discount rate for workloads which require continuously running database servers.&lt;/p&gt;

&lt;p&gt;The flexibility of change is however limited and depends on the database used. Nevertheless, it is worth considering Reserved Instances as a cost optimization choice with again only a minimum amount of time invest necessary.&lt;/p&gt;

&lt;p&gt;Amazon DynamoDB, the serverless NoSQL database, offers a feature called Reserved Capacity. It reserves a guaranteed amount of read and write throughput per second per table. Similar term and payment conditions as already mentioned for Savings Plans and Reserved Instances apply here as well. Predictable traffic patterns benefit from cost reduction compared to the On-Demand or Provisioned throughput modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Shutdown and Restart of EC2 and RDS Instances&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many EC2 and RDS instances are only used during specific times during a day and most often not at all during weekend. This applies mostly for development and test environments but might also be valid for production workloads. A considerable amount of money can be saved by turning off these idle instances when they are not needed.&lt;/p&gt;

&lt;p&gt;An automatic approach which initiates and manages the shutdown and restart according to a schedule can take over this task so that nearly no manual intervention is needed. AWS provides a solution called "&lt;a href="https://aws.amazon.com/solutions/implementations/instance-scheduler/" rel="noopener noreferrer"&gt;Instance Scheduler&lt;/a&gt;" which can be used to perform this work if no certain start or shutdown logic for an application has to be followed.&lt;/p&gt;

&lt;p&gt;Specific workflows which require for instance to start the databases first, prior to launching any servers can be modelled by AWS Step Functions and executed using scheduled EventBridge rules. Step Functions is extremely powerful and supports a huge range of API operations so that nearly no custom code is necessary.&lt;/p&gt;

&lt;p&gt;An example of a real-world workflow which stops an application consisting of several RDS and EC2 instances is shown in the image below. A strict sequence of shutdown steps must be followed to make sure that the application stops correctly. This workflow is triggered every evening when the environment is no longer needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvaekhjjle85abdrafhem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvaekhjjle85abdrafhem.png" alt="AWS Step Functions workflow to stop a workload" width="800" height="913"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The counter part is given in the next example. This workflow is used to launch the environment every morning before the first developer starts working.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fik51w3l81xpg0bp7rseu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fik51w3l81xpg0bp7rseu.png" alt="AWS Step Functions workflow to start a workload" width="800" height="1743"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shutting down all EC2 and RDS instances during night and over the weekend cut down the compute costs by about 50 percent in this project which is significant for a larger environment. The only caveat with this approach has been so far insufficient EC2 capacity when trying to restart the instances in the. It has happened very seldom, but took about half a day until AWS had enough resources available for a successful launch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9bzjsqrxvlkj6x369wv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9bzjsqrxvlkj6x369wv.png" alt="Error message which pops up in case AWS cannot provide enough EC2 resources of a certain type" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Up and downscale of instances in test environments&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This option might not work in all cases as test (aka. UAT) environments often mirror the production workload by design to have nearly identical conditions when performing manual or automated tests. Especially load tests, but others as well should be executed based on production like systems as their results are not reliable otherwise. In addition, not every application runs on smaller EC2 instances as smooth as on larger ones respectively changing an instance size might require additional application configuration modifications.&lt;/p&gt;

&lt;p&gt;Nevertheless, it sometimes is possible to downscale them (RDS databases might be an additional option) when load and other heavy tests are not performed on a regular basis (even though this might be recommended in theory).&lt;/p&gt;

&lt;p&gt;Infrastructure as code frameworks like Terraform or CloudFormation make it relatively easy to define two configuration sets. They can be swapped prior to running a load test to upscale the environment. EC2 supports instance size modifications on the fly (no restart necessary) and even some RDS databases can be modified without system interruption. The whole up- or downscale process requires only a small amount of time (depending on the environment size and instance types) and can save a considerable amount of money.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designing new applications with a serverless first mindset&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Serverless has become a buzzword and marketing term during the last few years (everything seems to be serverless nowadays), but in its core it is still a quite promising technological approach. Not having to deal with a whole bunch of administrative and operative tasks like provisioning and operating virtual servers or databases paired with the "pay only for what you use" model is quite appealing. Other advantages of serverless architectures should not be discussed in this article but can be found easily using your favorite search engine.&lt;/p&gt;

&lt;p&gt;Especially the "pay as you go" cost model counts towards direct cost optimization (excluding in this post topics like total cost of ownership and time to market which are important in practice as well). There is no need to shut down or restart anything when it is not needed. Serverless components do not contribute to your AWS bill when they are not used -- for instance at night in development or test environments. Even production workloads which often do not receive a constant traffic flow but more a spiky one benefit compared to an architecture based on containers or VMs.&lt;/p&gt;

&lt;p&gt;Not every application or workload is suited for a serverless design model. To be fair it should be mentioned that a serverless approach can get much more expensive than a container based one in case of very heavy traffic patterns. However, this is probably relevant for just a very small portion of all existing implementations.&lt;/p&gt;

&lt;p&gt;Quite often it is possible and beneficial to replace a VM or container by one or more Lambda function(s), a RDS database by DynamoDB or a custom REST/GraphQL API implementation by API Gateway or AppSync. The learning curve is steep and well-designed serverless architectures are not that easy to achieve at the beginning as a complete mind shift is required but believe me: this journey is worth the effort and makes a&lt;br&gt;
ton of fun after having gained some insights into this technology.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think about what should be logged and send to CloudWatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Logging has been an important part of any application development and operation since the invention of software. Useful log outputs (hopefully in a structured form) can help to identify bugs or other deficiencies and provide a useful insight into a software system. AWS provides with CloudWatch a log ingesting, storage and analyzing platform.&lt;/p&gt;

&lt;p&gt;Unfortunately, log processing is quite costly. It is not an exception that the portion of the AWS bill which is related to CloudWatch is up to 10 times higher than for instance the one of Lambda in serverless projects. The same is valid for container or VM based architectures even though the ratio might not be that high, but still not neglectable. A concept how to deal with log output is advisable and might make a considerable difference at the end of the month.&lt;/p&gt;

&lt;p&gt;Some of the following ideas help to keep CloudWatch costs under control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Change log retention time from "Never expire" to a reasonable value&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apply log sampling like described in this &lt;a href="https://dev.to/tlaue/keep-your-cloudwatch-bill-under-control-when-running-aws-lambda-at-scale-3o40"&gt;post&lt;/a&gt; and for instance provided by the &lt;a href="https://awslabs.github.io/aws-lambda-powertools-python/latest/core/logger/" rel="noopener noreferrer"&gt;AWS Lambda Powertools&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consider using a 3rd party monitoring system like     &lt;a href="https://lumigo.io/" rel="noopener noreferrer"&gt;Lumigo&lt;/a&gt; or &lt;a href="https://www.datadoghq.com/" rel="noopener noreferrer"&gt;Datadog&lt;/a&gt; instead of outputting a lot of log messages. These external systems are not for free and not always allowed to use (especially in an enterprise context) but provide a lot of additional features which can make a real difference.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In some cases, it might be possible to send logs directly to other systems (instead of ingesting them first into CloudWatch) or to store them in S3 and use Athena to get some insights.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Activate logging when needed and suitable but not always by default -- not every application requires for instance VPC flow logs or API Gateway access logs even though good reasons exist to do so in certain environments (due to security reasons or certain regulations snd company rules)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Logging is important and quite useful in most of the cases, but it makes sense to have an eye on the expenditures and to adjust the logging concept in case of sprawling costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrap up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All the cost optimization possibilities mentioned above can only scratch the surface of what is possible in the AWS universe. Things like S3 and DynamoDB storage tiers, EC2 spot instances and many others have not even been mentioned nor explained. Nevertheless, applying one or several of&lt;br&gt;
the strategies shortly discussed in this article can help to save a ton of money without having to spend weeks of engineering time. Especially Savings Plans and Reserved Instances as well as shutting down idle instances are easy and quite effective measures to reduce their contribution to the AWS bill by 30% to 50% for existing workloads. Newer ones which are suited for the serverless design model really benefit from its cost and operation model and provide a ton of fun for developers. &lt;/p&gt;

</description>
      <category>cloud</category>
      <category>serverless</category>
      <category>costs</category>
      <category>aws</category>
    </item>
    <item>
      <title>Recommender systems based on AWS Personalize</title>
      <dc:creator>Jens Goldhammer</dc:creator>
      <pubDate>Tue, 27 Sep 2022 08:47:15 +0000</pubDate>
      <link>https://forem.com/fmegroup/recommender-systems-based-on-aws-personalize-1fjk</link>
      <guid>https://forem.com/fmegroup/recommender-systems-based-on-aws-personalize-1fjk</guid>
      <description>&lt;p&gt;With its Personalize service, AWS offers a complete solution for&lt;br&gt;
building and using recommendation systems in its own solutions. The&lt;br&gt;
service, which is now also offered in the Europe/Frankfurt region, has&lt;br&gt;
been available since 2019 and is constantly being improved. Only last&lt;br&gt;
year, major improvements in the area of filters were added to the&lt;br&gt;
product.&lt;/p&gt;

&lt;p&gt;AWS Personalize allows customers to create recommendations based on an&lt;br&gt;
ML model for platform or product users. The following activities are&lt;br&gt;
abstracted and made particularly easy by AWS Personalize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Import business data into AWS Personalize&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Continuous training of models with current data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read recommendations with filtering capabilities from AWS&lt;br&gt;
Personalize&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might think that recommendation systems based on machine learning&lt;br&gt;
are old news and that you can do it all yourself anyway. Machine&lt;br&gt;
learning has been around for a few years now, and with it the&lt;br&gt;
possibility of developing such recommendation systems yourself.&lt;/p&gt;

&lt;p&gt;But the difference is: AWS Personalize takes the complete management of&lt;br&gt;
Machine Learning environments off the users' hands and allows to take&lt;br&gt;
first steps here very quickly. And we don't need the best ML experts on&lt;br&gt;
the team, because AWS Personalize takes a lot of more complex issues off&lt;br&gt;
our hands. Why it's still good to understand Machine Learning is shown&lt;br&gt;
by the challenges.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3QdDmmXp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nw7mhukj0qcj9pczz2ly.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3QdDmmXp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nw7mhukj0qcj9pczz2ly.png" alt="" width="704" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Personalize makes it easier than ever for us to create&lt;br&gt;
recommendations. From a technical perspective, everything seems easy to&lt;br&gt;
master. We find challenges mainly in the clear delineation of the use&lt;br&gt;
case and the meaningfulness of the recommendations.&lt;/p&gt;

&lt;p&gt;The recommendations should be as clearly delimited as possible for a use&lt;br&gt;
case. This influences both the selection of data and the structure of&lt;br&gt;
the data model and schema.&lt;/p&gt;

&lt;p&gt;A recommendation system lives from the meaningfulness and topicality of&lt;br&gt;
the displayed recommendations. If, for example, I make recommendations&lt;br&gt;
to a user that he already knows, are 2 years old or are not relevant at&lt;br&gt;
all, I lose his interest and trust. Recommendations are therefore first&lt;br&gt;
viewed critically and must therefore be convincing from the outset, even&lt;br&gt;
if this is of course partly viewed subjectively.&lt;/p&gt;

&lt;p&gt;Therefore, we need to ask the following questions from the beginning:&lt;/p&gt;

&lt;p&gt;It is very important to constantly validate the recommendations created&lt;br&gt;
by AWS Personalize. At the start, it is important to validate the&lt;br&gt;
recommendations manually, i.e., to check randomly whether the&lt;br&gt;
recommendations appear meaningful to a user at all. Recommendation is&lt;br&gt;
therefore to start with a recommendation system whose validity can be&lt;br&gt;
easily checked. In order to give a user recommendations that he or she&lt;br&gt;
does not yet know, it is necessary to work a lot with recommendation&lt;br&gt;
filters, so that favourites of users or content that has already been&lt;br&gt;
seen do not appear again.&lt;/p&gt;

&lt;p&gt;Now how do we make Personalize create recommendations for us? To do&lt;br&gt;
this, there are a few steps to complete.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tqz4jHtZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtoz7175f7qw4m6qr2qr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tqz4jHtZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtoz7175f7qw4m6qr2qr.png" alt="Image description" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;First, you should select a domain that best matches the use case&lt;br&gt;
(1).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In case of a user-defined use case, data models are defined&lt;br&gt;
afterwards. Importing your own data into Personalize is done once or&lt;br&gt;
continuously based on the defined data models (2).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Amazon Personalize uses the imported data to train and provide&lt;br&gt;
recommendation models (3).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To query recommendations, both an HTTP-based real-time API for one&lt;br&gt;
user and batch jobs for multiple users can be integrated (4).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's take a look at these data models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In addition to the e-commerce and video use cases, AWS Personalize&lt;br&gt;
offers the option of mapping your own use cases (domain). The bottom&lt;br&gt;
line is that it is always about the following data sets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Items&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interactions of the user with these items&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YazgG5xH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcqh7avr220qcj24l2uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YazgG5xH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lcqh7avr220qcj24l2uq.png" alt="Image description" width="751" height="255"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These datasets form a dataset group and are used as a whole in&lt;br&gt;
Personalize. Crucial here are the interactions that are necessary for&lt;br&gt;
most ML models and are used for training. A short example will&lt;br&gt;
illustrate this data model:&lt;/p&gt;

&lt;p&gt;"A fme employee reads the blog post "AWS Partnership" on the social&lt;br&gt;
intranet and writes a comment below it."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Item&lt;/strong&gt;: Blogpost "AWS Partnership&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interactions&lt;/strong&gt;: read | comment&lt;/p&gt;

&lt;p&gt;For this data a developer can define his own schema --- one schema each&lt;br&gt;
for Interactions, Users and Items.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Zd7HJZhW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/625kujsrsl6lxm2zxim2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Zd7HJZhW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/625kujsrsl6lxm2zxim2.png" alt="Image description" width="749" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following is an example schema for a user with 6 fields. These&lt;br&gt;
fields can be later used to get recommendations for content of specific&lt;br&gt;
users, e.g. users from a specific company or country.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6mejD3-o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijwibmz8kabhivbpohp5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6mejD3-o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ijwibmz8kabhivbpohp5.png" alt="Image description" width="426" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When importing data, this schema must then be followed. All three&lt;br&gt;
datasets have mandatory attributes (e.g. ID) as well as additional&lt;br&gt;
attributes that help to refine the ML model so that the recommendations&lt;br&gt;
can become even more precise. The additional attributes can be textual&lt;br&gt;
or categorical. They can also be used to filter recommendations.&lt;/p&gt;

&lt;p&gt;However, there are a few restrictions in modeling that you need to be&lt;br&gt;
aware of, such as the restrictions on 1000 characters per metadata. This&lt;br&gt;
is especially important if you want to model lists of values.&lt;/p&gt;

&lt;p&gt;Further info can be found &lt;a href="https://docs.aws.amazon.com/personalize/latest/dg/custom-datasets-and-schemas.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import data into Personalize&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The quality of recommendations are dependent on the data provided. But&lt;br&gt;
how does the data get into the system ?&lt;/p&gt;

&lt;p&gt;The import of data always takes place in these data pots, so-called&lt;br&gt;
datasets (see above) --- there is exactly 1 dataset each for Users,&lt;br&gt;
Items and Interactions. These datasets are combined in a dataset group.&lt;/p&gt;

&lt;p&gt;To be able to train the ML model, the data sets have to be imported at&lt;br&gt;
the beginning (bulk import via S3). It is also possible to update the&lt;br&gt;
data continuously (via an API), which ensures that the model can always&lt;br&gt;
be improved.&lt;/p&gt;

&lt;p&gt;When you start with AWS Personalize, you usually already have a lot of&lt;br&gt;
historical data in your own application. This is necessary because&lt;br&gt;
recommendations only "work" meaningfully once a certain amount of data&lt;br&gt;
is available ( as with any ML application).&lt;/p&gt;

&lt;p&gt;Here it is recommended to use the bulk import APIs of AWS Personalize.&lt;br&gt;
For this, the data must first be stored in S3 in CSV format and&lt;br&gt;
according to the previously defined schema. Then you can start import&lt;br&gt;
jobs ( 1 per record) via AWS Console, AWS CLI, AWS API or AWS SDKs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xuOOuBqa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o39ao0i5f62mrg744mpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xuOOuBqa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o39ao0i5f62mrg744mpu.png" alt="Image description" width="743" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For continuous updating of the Users and Items datasets, AWS Personalize&lt;br&gt;
provides REST APIs that can be easily used with the AWS Client SDKs.&lt;/p&gt;

&lt;p&gt;A so-called event tracker can be used for updating the interactions.&lt;br&gt;
This previously created tracker can be used for a large amount of events&lt;br&gt;
within a very short time to get data into the system via HTTP .&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qjrLxWfO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqwv2fjjo484adx5bu52.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qjrLxWfO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqwv2fjjo484adx5bu52.jpeg" alt="Image description" width="749" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Train models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the initial data is imported, AWS Personalize can now use this data&lt;br&gt;
in the form of the Dataset Group to train a model. To do this, you can&lt;br&gt;
first create a Solution, which is a "folder" for models. This sets the&lt;br&gt;
Recipe that Personalize should use.&lt;/p&gt;

&lt;p&gt;The recipe represents the ML model, which is then later trained (as a&lt;br&gt;
Solution version) with user-defined data. There are different types of&lt;br&gt;
recipes that offer different types of recommendations. For example,&lt;br&gt;
&lt;em&gt;USER_PERSONALIZATION&lt;/em&gt; provides personalized recommendations (from all&lt;br&gt;
items) and &lt;em&gt;PERSONALIZED_RANKING&lt;/em&gt; can provide a list of items with&lt;br&gt;
rankings for a particular user. Some recipes use all three data sets and&lt;br&gt;
some use only parts of them (e.g. SIMS does not need user data).&lt;/p&gt;

&lt;p&gt;After creating a solution, it can then be trained with the current state&lt;br&gt;
of the data sets, resulting in a solution version. Depending on the&lt;br&gt;
amount of data, this can take a little longer --- our tests showed&lt;br&gt;
runtimes of around 45 minutes. A solution version is the fully trained&lt;br&gt;
model that can be used directly for batch inference jobs or as the basis&lt;br&gt;
for a campaign --- a real-time API for recommendations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use recommendations in your own application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now it's time to integrate recommendations into our own application. AWS&lt;br&gt;
provides a REST interface that allows us to retrieve recommendations&lt;br&gt;
from AWS Personalize in real-time. This makes it easy for us to&lt;br&gt;
integrate with any system&lt;/p&gt;

&lt;p&gt;Recommendations in AWS Personalize are always user-related.&lt;br&gt;
Recommendations can therefore look different for each user --- but can also be the same for certain recipes, as in the case of "Popularity count".&lt;/p&gt;

&lt;p&gt;The response is a list of recommendations in the form of IDs of the recommended items, each with a score. The items are uniquely referenced via the ID.&lt;/p&gt;

&lt;p&gt;These recommendations can now be evaluated in your own application,&lt;br&gt;
linked with the content from your own database and then displayed to the&lt;br&gt;
user in a user interface. The performance of the query (at least for&lt;br&gt;
smaller amounts of data) is so good that this query can be done live.&lt;br&gt;
However, one can also think about keeping the results of the query for a&lt;br&gt;
while per user, so as not to have to constantly request the service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rq6FNKMW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/swywmoa5nfwh3677imse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rq6FNKMW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/swywmoa5nfwh3677imse.png" alt="Image description" width="508" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you need recommendations for a large number of users for mailings,&lt;br&gt;
batch jobs ( &lt;strong&gt;batch inference jobs&lt;/strong&gt; ) can efficiently create these&lt;br&gt;
recommendations in the background. These batch jobs can be "fed" with&lt;br&gt;
the UserIds --- the result are recommendations for each user within one&lt;br&gt;
big JSON file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z4w9YEEu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5oz7qudz18b9q4tbra7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z4w9YEEu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5oz7qudz18b9q4tbra7c.png" alt="Example for a result of the batch inference&amp;lt;br&amp;gt;
jobs" width="529" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Personalize worth the effort?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pricing model of the service can be quite demanding, so it is&lt;br&gt;
advisable to define in advance a result that you want to achieve with&lt;br&gt;
appropriate recommendations and resulting follow-up activities or repeat&lt;br&gt;
business.&lt;/p&gt;

&lt;p&gt;As a guide, to get individual recommendations for individual users in&lt;br&gt;
the Personalize Batch, we assume about 0.06 ct per recommendation for&lt;br&gt;
the user. That doesn't sound like a lot, but with several hundred&lt;br&gt;
thousand users and individual recommendations, it's part of the overall&lt;br&gt;
consideration. Depending on how often and to what extent batch runs for&lt;br&gt;
mailings etc. take place, it can get expensive. And the instances AWS&lt;br&gt;
uses for batch runs are very large and very fast. We created several&lt;br&gt;
batch jobs for mass exporting recommendations for 200k users for testing&lt;br&gt;
purposes. The batch jobs then ran overnight. We incurred costs of&lt;br&gt;
several hundred Euros --- we had probably underestimated the numbers in&lt;br&gt;
the AWS Calculator a bit.&lt;/p&gt;

&lt;p&gt;If referrals have a positive impact on the business and thus directly&lt;br&gt;
generate more sales for the customer, it can pay off very well. But what&lt;br&gt;
if my recommendations do not have a direct positive impact on my sales?&lt;br&gt;
One reason could be to bind customers more closely (subscription&lt;br&gt;
model) --- in the long term, this will in turn lead to more sales, but&lt;br&gt;
perhaps not in the short term.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AWS Personalize is a service that makes it very easy to get started with&lt;br&gt;
recommendation systems. As a development team, all you have to do is&lt;br&gt;
deliver the data in the right format and pick up the recommendations. It&lt;br&gt;
doesn't get much easier than that from a technical perspective.&lt;/p&gt;

&lt;p&gt;AWS Personalize can therefore be used well to extend existing systems&lt;br&gt;
without having to make deep changes. With the ability to create custom&lt;br&gt;
data models and tune the different ML algorithms, you can apply AWS&lt;br&gt;
Personalize to a wide variety of scenarios.&lt;/p&gt;

&lt;p&gt;The real work is in finding meaningful use cases, delineating them from&lt;br&gt;
one another, and providing the system with the right data.&lt;/p&gt;

&lt;p&gt;As always, this comes at a price. Is it worth it for them? Let's find&lt;br&gt;
out together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References and Links&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below are a few more links to help dig deeper into the topic:&lt;/p&gt;

&lt;p&gt;Official documentation:&lt;br&gt;
&lt;a href="https://aws.amazon.com/de/personalize"&gt;[https://aws.amazon.com/de/personalize]{.underline}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Blogposts:&lt;br&gt;
&lt;a href="https://aws.amazon.com/de/blogs/machine-learning/category/artificial-intelligence/amazon-personalize/"&gt;[https://aws.amazon.com/de/blogs/machine-learning/category/artificial-intelligence/amazon-personalize/]{.underline}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Personalize Best Practices:&lt;br&gt;
&lt;a href="https://github.com/aws-samples/amazon-personalize-samples/blob/master/PersonalizeCheatSheet2.0.md"&gt;[https://github.com/aws-samples/amazon-personalize-samples/blob/master/PersonalizeCheatSheet2.0.md]{.underline}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Efficiency of models:&lt;br&gt;
&lt;a href="https://aws.amazon.com/de/blogs/machine-learning/using-a-b-testing-to-measure-the-efficacy-of-recommendations-generated-by-amazon-personalize/"&gt;[https://aws.amazon.com/de/blogs/machine-learning/using-a-b-testing-to-measure-the-efficacy-of-recommendations-generated-by-amazon-personalize/]{.underline}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AWS Personalize Code Samples:&lt;br&gt;
&lt;a href="https://github.com/aws-samples/personalization-apis"&gt;[https://github.com/aws-samples/personalization-apis]{.underline}&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;br&gt;
&lt;a href="https://content.fme.de/en/blog/aws-personalize"&gt;[https://content.fme.de]{.underline}&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fortifying federated access to AWS via OIDC</title>
      <dc:creator>Konstantin Troshin</dc:creator>
      <pubDate>Fri, 12 Aug 2022 22:02:00 +0000</pubDate>
      <link>https://forem.com/fmegroup/fortifying-federated-access-to-aws-via-oidc-2995</link>
      <guid>https://forem.com/fmegroup/fortifying-federated-access-to-aws-via-oidc-2995</guid>
      <description>&lt;p&gt;In order to avoid management of numerous long-term IAM users, AWS&lt;br&gt;
provides federated access options that include &lt;a href="https://wiki.oasis-open.org/security/FrontPage"&gt;SAML2.0&lt;/a&gt; and &lt;a href="https://openid.net/connect/"&gt;OIDC&lt;/a&gt; &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html"&gt;&lt;strong&gt;identity providers&lt;/strong&gt; (IDP)&lt;/a&gt;. Whereas the SAML option is used by many of our customers and there are &lt;a href="https://www.google.com/search?q=aws+saml+access+keycloak"&gt;numerous examples&lt;/a&gt; of how to set it up , the &lt;a href="https://www.google.com/search?q=aws+oidc+access+keycloak"&gt;examples of use of OIDC&lt;/a&gt; are much scarcer. Thus, while selecting our own method of access federation, we decided to try OIDC out to get better understanding of its limits and advantages and be able to better advise our customers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Differences between SAML and OIDC identity federation
&lt;/h2&gt;

&lt;p&gt;To demonstrate the key differences between OIDC and SAML, I have created a small &lt;a href="https://github.com/fmeAG/keycloak-aws"&gt;repo&lt;/a&gt; that allows to deploy &lt;a href="https://www.keycloak.org/"&gt;Keycloak&lt;/a&gt; on an EC2 instance and then configure the SAML and OIDC clients to use with AWS. &lt;br&gt;
For those unfamiliar with Keycloak, it is an open source Identity&lt;br&gt;
and Access Management tool sponsored by RedHat and widely used by many of our customers and ourselves as an &lt;strong&gt;identity provider&lt;/strong&gt;. Among other features, Keycloak supports SAML and OIDC protocols for identity management and provides user federation via LDAP that allows to use it with an existing user base from an Active Directory. After &lt;a href="https://github.com/fmeAG/keycloak-aws#deployment"&gt;deployment&lt;/a&gt; of Keycloak and configuring the &lt;a href="https://github.com/fmeAG/keycloak-aws#access-to-aws-with-saml"&gt;SAML&lt;/a&gt; and &lt;a href="https://github.com/fmeAG/keycloak-aws#access-to-aws-via-oidc"&gt;OIDC&lt;/a&gt; clients, we can use Keycloak to login into AWS. &lt;br&gt;
The SAML login can be performed by going to &lt;code&gt;https://auth.${TF_VAR_root_dn}/realms/awsfed/protocol/saml/clients/amazon-aws&lt;/code&gt; where &lt;code&gt;${TF_VAR_root_dn}&lt;/code&gt; is the subdomain you need to create before the deployment. After entering the credentials for the user &lt;code&gt;testuser&lt;/code&gt; that is created by the deployment scripts, we get redirected to the AWS console for the AWS account to which Keycloak has been deployed. If we would have assigned multiple roles to the same Keycloak group (or multiple groups to &lt;code&gt;testuser&lt;/code&gt;), a page like the one below would appear (which would look familiar to everyone who already used SAML federation with AWS).&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cjYIQC1z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phgbuaer4bluucn6z0ls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cjYIQC1z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phgbuaer4bluucn6z0ls.png" alt="SAML choice window" width="880" height="613"&gt;&lt;/a&gt;&lt;br&gt;
If you like to experiment and have deployed everything from the &lt;a href="https://github.com/fmeAG/keycloak-aws"&gt;repo&lt;/a&gt;, you can go to the network tab of the development tools of the browser, find the &lt;code&gt;saml&lt;/code&gt; document there and copy its contents.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--abOJYkft--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnuwav514xouap5ag2ot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--abOJYkft--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xnuwav514xouap5ag2ot.png" alt="SAML assertion" width="880" height="1215"&gt;&lt;/a&gt;&lt;br&gt;
Save the contents as &lt;code&gt;aws-saml/assertion&lt;/code&gt; and run the &lt;code&gt;saml.sh&lt;/code&gt; from the same folder. If you are fast enough (per default, the SAML assertion for AWS is valid only for 5 minutes), the assuming should work for the first role but fail for the second. If you look at the trust policies for the corresponding roles (whose names should end with &lt;code&gt;_Federated_Admin-SAML&lt;/code&gt; and &lt;code&gt;_Federated_Admin-SAML2&lt;/code&gt;, respectively), you will see that those are identical and allow the &lt;em&gt;AssumeRoleWithSAML&lt;/em&gt; operation for the same SAML provider. So, why is access granted for the first and denied for the second role? This is because AWS actually checks the SAML assertion itself for the presence of the role that you try to assume. Looking at the &lt;a href="https://github.com/fmeAG/keycloak-aws/blob/main/aws-saml/kccommands.sh"&gt;script&lt;/a&gt; we ran to configure Keycloak, we can see these two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kcadm create "clients/$clientId/roles" -r ${REALM_NAME} -s "name=$(terraform output -raw role_arn),$(terraform output -raw provider_arn)" -s 'description=AWS Access'
kcadm add-roles -r ${REALM_NAME} --gname "${GROUP_NAME}" --cclientid 'urn:amazon:webservices'  --rolename "$(terraform output -raw role_arn),$(terraform output -raw provider_arn)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These lines create an entry for the first role (the one without 2) in Keycloak and map this role to a group &lt;code&gt;aws_access&lt;/code&gt; that is later assigned to our &lt;code&gt;testuser&lt;/code&gt;. Thus, this role shows up in the SAML assertion and can be assumed. Since the same thing does not happen for the second role, the access to it is denied to testuser (of course, this would change if you created the corresponding entry and mapping in Keycloak for this one too).&lt;/p&gt;

&lt;p&gt;But what about OIDC? Running the &lt;code&gt;./oidc.sh&lt;/code&gt; script from the &lt;code&gt;aws-oidc&lt;/code&gt; folder, we can see that our &lt;code&gt;testuser&lt;/code&gt; can assume the role for which our OIDC provider is listed in the trust policy. A closer look at this &lt;a href="https://github.com/fmeAG/keycloak-aws/blob/main/aws-oidc/iam.tf#L5"&gt;policy&lt;/a&gt; shows that it contains only two things: the ARN of the OIDC provider and the &lt;strong&gt;client ID&lt;/strong&gt; as &lt;em&gt;aud&lt;/em&gt;. This corresponds to what AWS Console is doing&lt;br&gt;
if a role is created there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--usFKT02M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciifa7jrokexmsszdhtr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--usFKT02M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ciifa7jrokexmsszdhtr.png" alt="AWS Console IAM Role TP OIDC" width="880" height="290"&gt;&lt;/a&gt;&lt;br&gt;
Also note that (as opposed to the SAML case), there was no need to do anything in Keycloak after running terraform scripts in the &lt;code&gt;aws-oidc&lt;/code&gt; folder. What does this mean? Well, in the case of OIDC, AWS does not check for any role or group assignments in the &lt;strong&gt;ID token&lt;/strong&gt;. The only two things that matter with the default settings are the &lt;strong&gt;IDP&lt;/strong&gt; itself (which is defined by the URL and the thumbprint as you can clearly see from the &lt;a href="https://github.com/fmeAG/keycloak-aws/blob/main/aws-oidc/openid.tf"&gt;openid.tf&lt;/a&gt; file) and the &lt;strong&gt;client ID&lt;/strong&gt; (defined in the &lt;code&gt;aud&lt;/code&gt; section of the trust policy).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "exp": 1657326250,
  "iat": 1657322650,
  "auth_time": 0,
  "jti": "a valid id must be here",
  "iss": "https://our.domain/realms/somerealm",
  "aud": "THISISWHATMATTERS",
  "sub": "typically_this_is_the_user_id",
  "typ": "ID",
  "azp": "the_same_as_aud",
  "session_state": "another id is here",
  "at_hash": "some stuff",
  "sid": "and yet another id",
  "email_verified": true,
  "groups": [
    "group1",
    "group2",
    "group3",
    "group4",
    "group5"
  ],
  "preferred_username": "some_user",
  "email": "some_user@our.domain",
  "username": "some_user"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This all means that any user that has access to the corresponding&lt;br&gt;
Keycloak realm can assume any role that trusts the &lt;strong&gt;IDP&lt;/strong&gt; which is not very granular or secure and way inferior to SAML, right? Well, that would be so if not for a very important thing - the way I used OIDC in this example is not how it is supposed to be used. Let's look at the &lt;code&gt;oidc.sh&lt;/code&gt; script more closely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function getClientSecret(){
  kcadm get -r ${REALM_NAME} "clients/$(getClientId ${1})/client-secret" | jq -r '.value'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I use &lt;code&gt;kcadm.sh&lt;/code&gt; (which is containerized and kind of hidden behind source &lt;code&gt;../kcadm.sh&lt;/code&gt;) to get the &lt;strong&gt;client secret&lt;/strong&gt; for the Keycloak OIDC client. This operation requires admin rights and would be equal to a Keycloak administrator giving a &lt;strong&gt;client secret&lt;/strong&gt; to a user in a regular context. This secret is then used together with the username and password for &lt;code&gt;testuser&lt;/code&gt; to directly get the &lt;strong&gt;ID token&lt;/strong&gt; that is in turn submitted to AWS STS. Of course, as a Keycloak admin I would never do this in the non-test environment because the &lt;strong&gt;client secret&lt;/strong&gt; (which is bound to the &lt;strong&gt;client ID&lt;/strong&gt; that is, in turn, specified in the IAM trust policy) is not meant to be available for the users. But what is it for then? Looking at the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html"&gt;AWS documentation on the OIDC topic&lt;/a&gt;, we can see that it mentions an &lt;strong&gt;identity broker&lt;/strong&gt;. And this &lt;strong&gt;identity broker&lt;/strong&gt; (which is not provided by AWS as in the case of SAML) is actually what the &lt;strong&gt;client ID and secret&lt;/strong&gt; are destined for.&lt;br&gt;
So, what is an &lt;strong&gt;identity broker&lt;/strong&gt; anyway? An &lt;strong&gt;identity broker (IB)&lt;/strong&gt; is an application that should function as a link between AWS and Keycloak and take over the management of user rights (it should know which user should be able to assume what role). A proper OIDC login flow should be started by the &lt;strong&gt;IB&lt;/strong&gt; that redirects the user to the &lt;strong&gt;IDP&lt;/strong&gt; (Keycloak in our case) which, after verifying the user credentials, provides the &lt;strong&gt;ID Token&lt;/strong&gt; for that user to the &lt;strong&gt;IB. &lt;/strong&gt;The &lt;strong&gt;IB&lt;/strong&gt; uses &lt;strong&gt;client ID and secret&lt;/strong&gt; to authenticate itself against the &lt;strong&gt;IDP&lt;/strong&gt;. As you also can see from the oidc.sh script, it would be a bad idea to provide the &lt;strong&gt;ID token&lt;/strong&gt; to the user because a combination of the role ARN and the &lt;strong&gt;ID token&lt;/strong&gt; is all you need to assume a role with OIDC.&lt;br&gt;
Instead, the &lt;strong&gt;IB&lt;/strong&gt; should check if the user has access to a requested role and then use the &lt;strong&gt;ID token&lt;/strong&gt; to get the temporary AWS credentials (by using the &lt;em&gt;AssumeRoleWithWebIdentity&lt;/em&gt; operation) and then return these credentials to the user (or use them to get the login URL for the AWS console). In my demo above, I use cURL as an &lt;strong&gt;IB&lt;/strong&gt; which is obviously a very poor choice for a production environment since it grants access to any role to any user.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hardening the OIDC-based roles
&lt;/h2&gt;

&lt;p&gt;Whereas use of a proper &lt;strong&gt;identity broker&lt;/strong&gt; minimizes the risk of the OIDC access to AWS being misused, the experiments above brought me to the question whether it is possible to get AWS STS to look at the user attributes from the &lt;strong&gt;ID token&lt;/strong&gt; and not only at the &lt;strong&gt;client ID&lt;/strong&gt; (&lt;em&gt;aud&lt;/em&gt;) and the &lt;strong&gt;IDP&lt;/strong&gt; itself. Looking at &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html"&gt;the documentation for GitHub&lt;/a&gt; (which also uses OIDC) as IDP, I saw that there is another attribute - &lt;em&gt;sub&lt;/em&gt; - that is used in trust policies. For Keycloak, the default value of &lt;em&gt;sub&lt;/em&gt; is the user ID, which is not very useful, but Keycloak has mappers that can be assigned to&lt;br&gt;
clients and can override the defaults. Experimenting with mappers, I discovered that it is indeed possible to get Keycloak to provide any LDAP user attribute (we use LDAP user federation in our environment) as &lt;em&gt;sub&lt;/em&gt; to AWS. The only caveat here is that this attribute needs to be a string, so that it is not directly possible to use group memberships (which would be arrays) to additionally secure the trust policies. It is, however, possible to use the &lt;em&gt;StringLike&lt;/em&gt; operator to match substrings. Using this operator, it is possible to check for LDAP groups with AWS STS as long as those are stringified. For instance, the following trust policy checks for a certain group (provided by terraform as &lt;code&gt;${var.group}&lt;/code&gt;) in a group string looking like this:&lt;br&gt;
&lt;code&gt;-group1-group2-group3-...&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": aws_iam_openid_connect_provider.oidc.arn
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${var.oidc_provider}:aud": var.client_id
        },
        "StringLike":{
          "${var.oidc_provider}:sub": ["*-${var.group}-*"]
        }
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what could this group string come from? One option would be to write a custom plugin for Keycloak and another would be to let the &lt;strong&gt;IB&lt;/strong&gt; (which is a custom app) handle this. My &lt;a href="https://github.com/fmeAG/keycloak-aws"&gt;repo&lt;/a&gt; actually contains such a custom mapper (next section also discusses that a bit more in detail) that should be active in your Keycloak if you deployed it as described above. To see the mapper in action, we can run the &lt;code&gt;./oidc_protected.sh&lt;/code&gt; script from the &lt;code&gt;aws-oidc&lt;/code&gt; folder. As you will see, you would be able to assume the first role but not the second one.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hzVg3WCV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr9d2fnwzqw08xvx3z89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hzVg3WCV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr9d2fnwzqw08xvx3z89.png" alt="Protected Role test" width="880" height="167"&gt;&lt;/a&gt;&lt;br&gt;
Why? Let's take a look at the &lt;a href="https://github.com/fmeAG/keycloak-aws/blob/main/aws-oidc/openid.tf"&gt;trust policies&lt;/a&gt;: the one for the first role contains the &lt;code&gt;aws_access&lt;/code&gt; group which as we know is assigned to our &lt;code&gt;testuser&lt;/code&gt;, the one for the second role refers to the &lt;code&gt;aws_access_exclusive&lt;/code&gt; group which does not even exist in Keycloak yet. So, even though our user had a valid &lt;strong&gt;ID Token&lt;/strong&gt;, it was not possible to assume a protected role because this token did not contain the correct group. If you want to verify that the access will be granted once you create the corresponding group and assign it to the &lt;code&gt;testuser&lt;/code&gt;&lt;br&gt;
and also to look at the new Keycloak UI (which is at preview for&lt;br&gt;
Keycloak 18.0.2), you can do so at &lt;code&gt;https://auth.${TF_VAR_root_dn}&lt;/code&gt;.&lt;br&gt;
In this case, you would need to use the admin credentials (admin and &lt;code&gt;${TF_VAR_keycloak_password}&lt;/code&gt;defined in &lt;code&gt;export.sh&lt;/code&gt;). Once the group is created and assigned, the access works as expected. Sweet!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nIzriMTI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4whckmy5wfop44g6zwwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nIzriMTI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4whckmy5wfop44g6zwwf.png" alt="Keycloak Admin UI" width="880" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M5mVsAJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21apods1ohzvjx8b3tku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M5mVsAJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/21apods1ohzvjx8b3tku.png" alt="Assuming Role with OIDC" width="880" height="459"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Developing custom mappers for Keycloak
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.keycloak.org/docs/latest/server_development/#_script_providers"&gt;Keycloak documentation&lt;/a&gt; mentions "JavaScript" providers which can be used to create custom mappers. As I read JavaScript, I was expecting to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function stringifyGroups(groups){ 
    return groups.reduce((current, element)=&amp;gt;{ 
        return current+"-"+element; 
    }, "")+"-"; 
} 
token.setOtherClaims("sub",stringifyGroups(token.groups));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then place this into a .jar file as described in the documentation.&lt;br&gt;
It turned out that it does not work like that at all. Firstly, the&lt;br&gt;
custom scripts are disabled by default as I found out looking at the Keycloak logs. To fix this, one needs either to activate the preview functions or enable the &lt;em&gt;scripts&lt;/em&gt; option alone as described &lt;a href="https://github.com/keycloak/keycloak-documentation/blob/main/server_installation/topics/profiles.adoc"&gt;here&lt;/a&gt;.&lt;br&gt;
Secondly, "JavaScript" turned out to be very Java-based and needs to call functions from the corresponding Java classes of Keycloak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var res="";
var forEach = Array.prototype.forEach;
forEach.call(user.getGroupsStream().toArray(), function (group) {
  res=res+"-"+group.getName();
});
res=res+"-";
exports=res;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://github.com/fmeAG/keycloak-aws"&gt;repo&lt;/a&gt; shows how it all comes together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In conclusion, both SAML2 and OIDC are great options of access federation for AWS and have their advantages and drawbacks. If you decide to use OIDC like us, you need an &lt;strong&gt;identity broker (IB)&lt;/strong&gt; that provides a link between an &lt;strong&gt;IDP&lt;/strong&gt; (such as Keycloak) and AWS. It would be unwise and potentially dangerous to provide &lt;strong&gt;ID tokens&lt;/strong&gt; directly to the federated users, because a combination of such a token with a role ARN is usually enough to be able to assume that role. Of course, it would be even more unwise to provide an AWS-trusted &lt;strong&gt;client ID&lt;/strong&gt; to the users. A combination of the StringLike operator and the Keycloak mappers can be used to increase the security of OIDC-Federated AWS accounts by restricting the access to the roles to certain user attributes such as group membership similarly to how the SAML2 federation works.&lt;/p&gt;

</description>
      <category>oidc</category>
      <category>aws</category>
      <category>saml</category>
      <category>keycloak</category>
    </item>
    <item>
      <title>Certbot as an init container for AWS ECS.</title>
      <dc:creator>Konstantin Troshin</dc:creator>
      <pubDate>Mon, 08 Aug 2022 17:00:00 +0000</pubDate>
      <link>https://forem.com/fmegroup/certbot-as-an-init-container-for-aws-ecs-2n5p</link>
      <guid>https://forem.com/fmegroup/certbot-as-an-init-container-for-aws-ecs-2n5p</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;&lt;br&gt;
Encryption in transit has become a security standard for most&lt;br&gt;
network-based applications and is requested by the majority of our&lt;br&gt;
customers for all applications we help them to build or manage. Most of the modern applications support TLS out of the box but require the certificate and the corresponding private key to be provided externally.&lt;br&gt;
In some cases (for example, for intranet apps), self-signed certificates (or certificates signed by an internal CA) are sufficient, but if the application is internet-facing and needs to be used without additional steps on the client side, a certificate signed by a commonly trusted certificate authority (CA) is required. For AWS-based applications (as you may have guessed from the title, AWS are a main focus of this post), AWS Certificate Manager (ACM) can be used in combination with a load balancer to provide an amazon-signed certificate. This simple and efficient method is not applicable, however, if the certificate and the corresponding private key need to be provided to the application directly instead of an AWS-managed load balancer. This can be the case if the application is using TLS in combination with its own protocol which would make TLS termination on the load balancer impossible. &lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; is an open CA that provides trusted certificates which can be acquired by using a tool that supports the ACME protocol. In this case, the certificate and private key can then be provided to the application directly and used also for custom TLS-based protocols. &lt;a href="https://certbot.eff.org/"&gt;Certbot&lt;/a&gt; is one of such tools and can be used to obtain the TLS credentials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s2eDXALi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvv45rzg3eijv9jvym1t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s2eDXALi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvv45rzg3eijv9jvym1t.png" alt="le" width="880" height="648"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The use case
&lt;/h2&gt;

&lt;p&gt;Recently, I have been asked to provide a publically accessible &lt;a href="https://neo4j.com/product/neo4j-graph-database"&gt;Neo4j database&lt;/a&gt; to use for development purposes. Since a Neo4j installation is available as a docker container, I chose to use AWS ECS to run it (a Kubernetes-based solution such as EKS would be quite an overkill for such a simple use case). To start things up, I deployed a &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html"&gt;Network Load Balancer&lt;/a&gt; (NLB) with three listeners and the corresponding target groups for ports 7474 (HTTP), 7473 (HTTPS), and 7687 (bolt). To improve security of the database, I decided to activate TLS for the bolt and HTTPS endpoints.&lt;br&gt;
Neo4j provides support for both &lt;a href="https://neo4j.com/developer/kb/setting-up-ssl-with-docker/"&gt;out of the box&lt;/a&gt;, but requires the certificates to be provided externally. My initial approach was to use TLS listeners in combination with an Amazon-signed ACM certificate and TLS target groups to talk to the Neo4j container. I used openssl to create a self-signed certificate and provided it via an ECS mount point to Neo4j. This worked just fine for the HTTPS endpoint but did not for bolt which is, however, crucial for the Neo4j clients. It has become clear that TLS termination would not work for this use case and that I needed to use TCP listeners and target groups and to provide the publically facing certificate directly to Neo4j. Since the request of the customer included a wish that the database can be easily accessed by the clients without much configuration on their side, I also wanted this certificate to be publically trusted. In many of our k8s-based projects, we use cert-manager which can directly obtain Let's Encrypt (LE) certificates, which brought me to the idea of using LE for my current task. Thinking about k8s and init containers, I also remembered reading some stuff about &lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/03/amazon-ecs-introduces-enhanced-container-dependency-management/?nc1=h_ls"&gt;container dependencies in ECS&lt;/a&gt;, so I came to an idea of using a certbot docker container as such an "init container" for my Neo4j database. The schematic architecture is depicted below and includes an EC2 ECS host on which three containers should run: first the &lt;strong&gt;certbot&lt;/strong&gt; container is started that can request the certificate for the corresponding domain. Once the certificate and the private key are there, the certbot container exits successfully upon which the second container (copier) is started. This container just needs a shell (I decided to use &lt;strong&gt;debian:latest&lt;/strong&gt; for this purpose) and its purpose is to copy the certificate and the private key into the folders and under the file names Neo4j expects. Upon the successful exit of this container, the &lt;strong&gt;Neo4j&lt;/strong&gt; container is finally started.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3n57HaoX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59rczy688es0v8w9w9zq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3n57HaoX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59rczy688es0v8w9w9zq.png" alt="Architechture" width="792" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve the correct order of the containers, AWS ECS supports the&lt;br&gt;
&lt;strong&gt;dependsOn&lt;/strong&gt; attribute - a list of &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDependency.html"&gt;&lt;em&gt;ContainerDependency&lt;/em&gt; objects&lt;/a&gt;&lt;br&gt;
that in turn consist of &lt;strong&gt;containerName&lt;/strong&gt; and &lt;strong&gt;condition&lt;/strong&gt;. The&lt;br&gt;
condition attribute allows to specify whether the previous container&lt;br&gt;
should have started (START), exited (COMPLETE), ran successfully&lt;br&gt;
(SUCCESS) or is passing docker health checks (HEALTHY). In the present&lt;br&gt;
use case, SUCCESS is the correct &lt;strong&gt;condition,&lt;/strong&gt; since both certificate&lt;br&gt;
retrieval and copy are crucial for the Neo4j container to work properly&lt;br&gt;
(the copier container is called debian here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  {
    "dependsOn": [
      {
        "containerName": "certbot",
        "condition": "SUCCESS"
      },
      {
        "containerName": "debian",
        "condition": "SUCCESS"
      }
    ],
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Routing to certbot
&lt;/h2&gt;

&lt;p&gt;A small challenge for the architecture above is to ensure that certbot can solve the HTTP challenge of Let's encrypt which is a part of the ACME protocol (this is needed to verify that the domain for which the certificate is requested is indeed controlled by us). The problem here is that if targets of type &lt;em&gt;instance&lt;/em&gt; are used with the load balancer (which makes sense for an ECS EC2 host), health checks are mandatory. On the other hand, since certbot is running only for a short time, it itself cannot be used for health checks on port 80. Also, LE expects the domain to be already routable to certbot requesting the certificate which means that a typical registration delay that load balancers have is not acceptable in this case. As a result, the instance should be&lt;br&gt;
registered at the corresponding target group of the NLB and already healthy &lt;em&gt;before&lt;/em&gt; the certbot container is even started. To address this issue, I decided to use a simple trick based on a small &lt;a href="https://github.com/konstl000/handshaker"&gt;handshaker app&lt;/a&gt;. This app provides a golang-based http server listening on a specified port that simply replies "OK" to any request and can be deployed as a scratch-based docker container (or a binary). Since the app cannot block the port 80 (which is required by certbot once it is ready to accept the HTTP challenge), I configured the corresponding target group (TG80) to forward to port 80 but health check on another port (6666) which I then assigned to handshaker. To ensure the correct timing, I included starting the app into the &lt;em&gt;user data&lt;/em&gt; script of the ECS EC2 instance and made terraform (with which the whole infrastructure is built) to register the auto scaling group that deploys these instances at TG80.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -d -e HEALTH_CHECK_PORT=6666 -p 6666:6666 \
${SOME_ACCOUNT_ID}.dkr.ecr.eu-central-1.amazonaws.com/handshaker:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, shortly after terraform apply, the instance was registered at TG80 and became healthy. After this, I used aws cli to scale the ECS service to 1 task (I usually initially deploy the ECS services with the task count of 0, so that the whole infrastructure such as load balancers, instances, Route53 entries, etc. is available before the containers are even started).&lt;/p&gt;

&lt;p&gt;To my delight, certbot successfully requested the certificate, passed the HTTP challenge and stored the results in a shared folder mounted via a mount point. After this, the following script ran in the copier container followed by the successful start of Neo4j.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

#The le folder will be mounted from the host and filled by certbot
cp /le/live/"${DOMAIN}"/cert.pem /home/neo4j/certificates/bolt/public.crt
cp /le/live/"${DOMAIN}"/privkey.pem /home/neo4j/certificates/bolt/private.key
#from here, we just need to create some more copies
cp /home/neo4j/certificates/bolt/private.key /home/neo4j/certificates/https/
cp /home/neo4j/certificates/bolt/public.crt /home/neo4j/certificates/https/
cp /home/neo4j/certificates/bolt/public.crt /home/neo4j/certificates/bolt/trusted/
cp /home/neo4j/certificates/bolt/public.crt /home/neo4j/certificates/https/trusted/

chown -R 7474 /home/neo4j/certificates #so that the neo4j user can read 'em
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jZgTnNZR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vqsxblsseglxzmh21ui.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jZgTnNZR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3vqsxblsseglxzmh21ui.png" alt="cert view" width="880" height="1089"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;Of course, the described approach is not the only way to get a&lt;br&gt;
certificate from LE and provide it to a Neo4j container (or another application). Some of the simple alternatives I can immediately think of would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Run certbot directly on the EC2 host instead of a container&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use k8s/k3s/k0s in combination with cert-manager&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build a custom container that has certbot inside of it&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That being said, I think that the init container approach shows a way of using ECS similar to k8s pods and can be successfully applied to other ECS-based solutions. Also, it allows to use the upstream containers which makes upgrades seamless as opposed to the "one custom container" approach. In case you wonder, how the hell I could run a custom bash script within an upstream debian container -- I just used a mount point to mount a folder from the host that has been created and filled by the &lt;em&gt;user data&lt;/em&gt; script during the EC2 deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
mkdir -p /home/xtra #prepare the xtra folder that will be attached to the debian contaner
echo "${CERT_SCRIPT}" | base64 -d &amp;gt;/home/xtra/copy_certs.sh
chmod +x /home/xtra/copy_certs.sh
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scaling
&lt;/h2&gt;

&lt;p&gt;In the current example, I used an auto scaling group with just one&lt;br&gt;
instance in it, which allows all the mount points to be folders on this instance. Of course, the local folder solution would not scale well. In this case, however, EFS can be used instead, so that the certificate and the key would be requested just once by one of the certbots (certbot exits automatically if a valid certificate is already present), but can then be used by all of the corresponding Neo4j containers. All other services used in the infrastructure above (NLB, NAT Gateway, ECS) support horizontal scaling, so that a solution based on this approach can be scaled out with ease.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;In conclusion, AWS ECS provides a nice option to include k8s-like "init containers" by using container dependencies and non-essential containers. Those can be employed for a variety of purposes including generation of TLS certificates with a certbot container. The TLS credentials can be then immediately provided to an application running in the essential container on the same host resulting in a publically trusted secured app.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>containers</category>
      <category>ecs</category>
      <category>docker</category>
    </item>
    <item>
      <title>Refactor Terraform code with Moved Blocks - a new way without manually modifying the state</title>
      <dc:creator>Thomas Laue</dc:creator>
      <pubDate>Fri, 08 Jul 2022 11:07:49 +0000</pubDate>
      <link>https://forem.com/fmegroup/refactor-terraform-code-with-moved-blocks-a-new-way-without-manually-modifying-the-state-g2c</link>
      <guid>https://forem.com/fmegroup/refactor-terraform-code-with-moved-blocks-a-new-way-without-manually-modifying-the-state-g2c</guid>
      <description>&lt;p&gt;Most software and IT infrastructure projects which have been deployed to production have to deal with requirement changes during their lifetime. User expectations change, new use cases appear, traffic patterns are different than expected or new technology becomes available. Refactoring of existing code (application code as well as infrastructure-as-code) has always been an important task but also one of the major pain points in IT. A good support of refactoring tools and patterns can make a difference for a framework like Terraform compared with its competitors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setting the stage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Terraform by HashiCorp -- one of the major players in the&lt;br&gt;
infrastructure-as-code framework world - has been around since 2014. It has been used to setup a lot of small, medium, and large projects all over the world. It provides a rich feature set to define infrastructure in a concise manner. One of its strengths is the way to create identical/similar resources using either the meta-argument &lt;code&gt;count&lt;/code&gt; or the newer version &lt;code&gt;for_each&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;count&lt;/code&gt; makes it very easy to define identical resources like shown in the listing below which defines a very basic setup for 3 EC2 instances running on AWS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webserver1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"webserver2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"webserver3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ami&lt;/span&gt;                       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0a1ee2fb28fe05df3"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform stores references to resources created by using the &lt;code&gt;count&lt;/code&gt; meta-argument in its internal state in an array using an index-based approach.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f0qk32w98fdpfcmv28m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f0qk32w98fdpfcmv28m.png" alt="Terraform state listing showing three web server instance details"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This works fine if a single instance must not be replaced or deleted. Such an action will affect all resources which are located on a higher index in the array due to the nature Terraform manages its state.&lt;/p&gt;

&lt;p&gt;Trying to remove "webserver2" in the example above&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webserver1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"webserver2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"webserver3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will result in the destruction of the EC2 instance tagged "webserver3" and a renaming of the previous named "webserver2" instance into "webserver3". The result does not correspond to the expressed intention.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl5ec2v4gifkz0dfpmg3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl5ec2v4gifkz0dfpmg3.png" alt="Terraform listing showing unintended result of refactoring a structure build upon  raw `count` endraw "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Version 0.12.6 of Terraform introduced the &lt;code&gt;for_each&lt;/code&gt; meta-argument - a more flexible way to create identical/similar resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ami&lt;/span&gt;                         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0a1ee2fb28fe05df3"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Terraform state references the resources no longer based on an index but by using a key-based approach. It is now possible to address a single resource without affecting others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstiice0glkzwlcwkxqm9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fstiice0glkzwlcwkxqm9.png" alt="Terraform listing showing correctly refactored structure based upon  raw `for_each` endraw "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The removal of "webserver2" can now be performed successfully without affecting other resources.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3ljhaic16c74yw4cswd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3ljhaic16c74yw4cswd.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Due to the greater flexibility of &lt;code&gt;for_each&lt;/code&gt; it might be helpful or even required to refactor existing code (migrate from &lt;code&gt;count&lt;/code&gt; to &lt;code&gt;for_each&lt;/code&gt;). This has been possible in the past by manipulating the Terraform state directly using the &lt;code&gt;terraform state mv&lt;/code&gt; CLI command. However, all manual state manipulations are brittle and prone to errors which make them as a kind of last resort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From imperative to explicit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HashiCorp introduced an improved refactoring experience with version 1.1 of Terraform: the &lt;a href="https://www.terraform.io/language/modules/develop/refactoring" rel="noopener noreferrer"&gt;&lt;code&gt;moved block&lt;/code&gt;&lt;/a&gt; syntax which allows to express refactoring steps in code instead of using an imperative attempt via CLI.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;moved block&lt;/code&gt; allows to specify the old and new reference of a resource like shown in the following example which has been rewritten to use &lt;code&gt;for_each&lt;/code&gt; instead of &lt;code&gt;count&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webserver1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"webserver2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"webserver3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webserver1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webserver2"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"webserver3"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;server_names&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;ami&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ami-0a1ee2fb28fe05df3"&lt;/span&gt;
  &lt;span class="nx"&gt;instance_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"t3.micro"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A following &lt;code&gt;terraform plan/apply&lt;/code&gt; reveals that no instance will be destroyed or modified in any way but only moved in the state from its old reference to its new one created by the way &lt;code&gt;for_each&lt;/code&gt; works. No need for any manual state manipulation anymore but everything can be done securely using Terraforms native way to work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vi8u35moj0xiu6gul18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vi8u35moj0xiu6gul18.png" alt="Terraform listing showing how moved blocks work"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;moved blocks&lt;/code&gt; cannot only be applied to refactor &lt;code&gt;count&lt;/code&gt; into&lt;br&gt;
&lt;code&gt;for_each&lt;/code&gt; syntax but also be used to rename resources, to move resources into modules and so on. Not everything is possible using the new language element, but many (not extremely complex) refactoring tasks can benefit from using it. Terraforms documentation contains different examples and use cases with further details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;moved blocks&lt;/code&gt; have made refactoring existing Terraform projects easier and safer to perform. No manual steps are required any longer for many use cases even though &lt;code&gt;terraform state mv&lt;/code&gt; is still there to solve problems which cannot be tackled by using the new element. It is helpful to have tooling/framework elements like this on at hand.   &lt;/p&gt;

&lt;p&gt;Depending on the type and size of the project (internal project or public module) it might make sense respectively it is even recommended by HashiCorp not to delete the blocks after having applied the changes. Not everyone using the module might already have fetched the latest version. Apart from avoiding trouble for users it might be helpful to document any significant changes on the project structure for later reviews. A short well written and dated comment combined with the &lt;code&gt;moved block&lt;/code&gt; syntax might answer your question or the one of a colleague six months down the road.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>Docker is dead?!? Podman - an alternative tool?</title>
      <dc:creator>Maxi Krone</dc:creator>
      <pubDate>Fri, 08 Jul 2022 11:04:36 +0000</pubDate>
      <link>https://forem.com/fmegroup/docker-is-dead-podman-an-alternative-tool-2iij</link>
      <guid>https://forem.com/fmegroup/docker-is-dead-podman-an-alternative-tool-2iij</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lqa2uz4n3bhbk8261ya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0lqa2uz4n3bhbk8261ya.png" alt="Docker vs Podman"&gt;&lt;/a&gt; &amp;gt; &lt;em&gt;&lt;a href="https://www.flaticon.com/free-icon/fight_1256685" rel="noopener noreferrer"&gt;https://www.flaticon.com/free-icon/fight_1256685&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You're no stranger to container images, Docker, and Kubernetes, and you may have heard it said in many places that Docker is dead? You can't explain exactly where this statement comes from, or maybe you haven't quite figured out the topic yet? Then this blog post will help you out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Updated: June, 22nd 2022:&lt;/strong&gt; This article was originally published on May 29, 2022 and blew up on hackernews.com with a broad discussion of the role of Podman and Docker in the future. This discussion also contained valid criticism and corrections of some erroneously assumed facts. We are very happy that this discussion took place and that it sparked a nice and lively discussion about containers, Docker, Podman and all the other containerization related topics between all the professionals. We want to correct the wrong facts in a transparent way that is why we decided to not change the original article, but extend it with a paragraph at the end where the wrong assumptions will be corrected. Again, thanks a lot for all your feedback! We appreciate it very much!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this blog post, I would like to briefly explain how the statement comes about, what background it has and what possible alternatives there are with my own impressions.&lt;/p&gt;

&lt;p&gt;In particular, I will discuss the alternative Podman - which does not require a background service (daemon) - including its advantages and disadvantages compared to Docker. First, I will explain the OCI and then how Podman works in general. What Podman is not, however, and a list of its advantages over Docker will follow later.&lt;/p&gt;

&lt;p&gt;To make a long story short: Container images - especially according to the OCI standard - are not dead and Docker also still has its application areas and accordingly will not die out quickly. This article is much more about the &lt;a href="https://www.docker.com/blog/updating-product-subscriptions/" rel="noopener noreferrer"&gt;licensing changes&lt;/a&gt; of Docker - especially Docker Desktop - and what consequences they entail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is Docker dead? The history behind Docker.
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Beginnings of containers and Docker
&lt;/h4&gt;

&lt;p&gt;Docker is open-source software that primarily serves to isolate&lt;br&gt;
applications with the help of container virtualization. It does this by&lt;br&gt;
using Linux techniques such as cgroups and namespaces to implement&lt;br&gt;
containers, among other things. More detailed information on this can&lt;br&gt;
also be found in Docker's official &lt;a href="https://docs.docker.com/get-started/overview/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Docker was not, as often thought, the first approach in this direction.&lt;br&gt;
The isolation of applications via process isolation - in principle, containerization is this - goes far back into the past of computers. For example, the first approaches can be traced back to the chroot system call (syscall) of the late 1970s. This advance was the beginning of process isolation: separate file access for each process.&lt;/p&gt;

&lt;p&gt;However, in March 2013, Docker Inc, initially known as dotCloud, managed to take containerization to a new level that made it usable to the general public by releasing Docker as free and open source software.&lt;br&gt;
Above all, the simple, understandable usability, high reliability and extreme added value of Docker containers led to the great success and revival of containerization in the world of IT in the following years.&lt;/p&gt;

&lt;h4&gt;
  
  
  Aquisition Mirantis
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6j64cpue16ki1x31kxpx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6j64cpue16ki1x31kxpx.jpeg" alt="Source: https://www.mirantis.com/wp-content/uploads/2017/01/mirantis-logo-2color-rgb-transparent.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Source: &lt;a href="https://www.mirantis.com/wp-content/uploads/2017/01/mirantis-logo-2color-rgb-transparent.png" rel="noopener noreferrer"&gt;https://www.mirantis.com/wp-content/uploads/2017/01/mirantis-logo-2color-rgb-transparent.png&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then, in November 2019, Mirantis acquired Docker Inc. along with the Docker suite. Shortly thereafter, Mirantis announced an adjustment to the existing licensing model. Thus, the standard use of Docker including Daemon, CLI and Docker Desktop was previously free of charge. Instead of free use of Docker Desktop until now, this software suite is now available for rent after the transition phase until the end of January 2022, starting at $5 per user/month, provided it is for professional use. Since then, only individuals, open-source communities and small businesses with up to 250 employees or $10 million in annual revenue are exempt.&lt;/p&gt;

&lt;p&gt;In addition to this, &lt;a href="https://www.docker.com/increase-rate-limits/" rel="noopener noreferrer"&gt;a rate limit was created&lt;/a&gt; in the central Docker registry - the &lt;a href="https://hub.docker.com/" rel="noopener noreferrer"&gt;DockerHub&lt;/a&gt;- so that anonymous users can only download 100 images and authenticated free users can only download 200 images in a six-hour period.&lt;/p&gt;

&lt;p&gt;These operations resulted in the departure of several major vendors and platforms, as they disagreed with the sudden change in licensing terms. With the use of Docker in popular platforms such as Kubernetes, this showed the danger of dependency to Docker Inc. If this company can continue to adjust their licensing model at will, this can result in major consequences for all companies and platforms that rely on Docker.&lt;/p&gt;

&lt;p&gt;Exemplary of this followed the &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.20.md#deprecation" rel="noopener noreferrer"&gt;announcement&lt;/a&gt; by the Kubernetes project that Docker or dockershim will be discontinued as of version 1.24.&lt;br&gt;
RedHat also decided to do so with RHEL 8.Due to these decisions and also RedHat and Kubernetes moving away from Docker, there is an increasing portion of developers moving away from Docker and looking for alternatives. An interesting report on the declining use of Docker can be found here.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker Desktop
&lt;/h4&gt;

&lt;p&gt;To understand the scope of it all, here's an explanation of the Docker Desktop application.&lt;/p&gt;

&lt;p&gt;Docker Desktop is a software suite for Mac, Windows and &lt;a href="https://www.docker.com/blog/the-magic-of-docker-desktop-is-now-available-on-linux/" rel="noopener noreferrer"&gt;now&lt;br&gt;
Linux&lt;/a&gt; that can be used to create containerized applications using Docker's own tools. Here, Docker Desktop includes the Docker Engine, docker-cli, docker-compose and a credential helper, among others.&lt;/p&gt;

&lt;p&gt;By adapting the licensing model, Docker Desktop may now only be used in its entirety by the exceptions described above, provided that licensing costs are to be avoided.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Podman?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyh4z8x4s122btp1qy6d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyh4z8x4s122btp1qy6d4.png" alt="Source: https://developers.redhat.com/sites/default/files/podman-logo.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Source: &lt;a href="https://developers.redhat.com/sites/default/files/podman-logo.png" rel="noopener noreferrer"&gt;https://developers.redhat.com/sites/default/files/podman-logo.png&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the scene, for many, Podman is following in the footsteps of Docker as free software. But what exactly Podman is and that it is not just a simple replacement is explained below.&lt;/p&gt;

&lt;p&gt;Podman is an open source and free container management tool for developing, managing and running OCI containers.&lt;/p&gt;

&lt;h4&gt;
  
  
  OCI-Images and -Runtime
&lt;/h4&gt;

&lt;p&gt;OCI stands for the &lt;a href="https://opencontainers.org/" rel="noopener noreferrer"&gt;Open Container Initiative&lt;/a&gt; and was initiated by Docker Inc. in 2015. They describe two specifications: The Runtime Specification and the Image Specification.&lt;/p&gt;

&lt;p&gt;Any software can implement the specifications and thus create&lt;br&gt;
OCI-compatible container images and container runtimes that are&lt;br&gt;
compatible with each other. In addition, there are the so-called&lt;br&gt;
&lt;strong&gt;C&lt;/strong&gt;ontainer &lt;strong&gt;R&lt;/strong&gt;untime &lt;strong&gt;I&lt;/strong&gt;nterfaces (CRI), which are based on the OCI runtimes and abstract them.&lt;/p&gt;

&lt;p&gt;Example implementations of the container runtime interfaces in this context are dockershim (OCI wrapper for the original Docker Engine implementation, see this &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/migrating-from-dockershim/check-if-dockershim-removal-affects-you/#role-of-dockershim" rel="noopener noreferrer"&gt;article&lt;/a&gt;), &lt;a href="https://containerd.io/" rel="noopener noreferrer"&gt;containerd&lt;/a&gt; (new implementation of Docker's container runtime interface (CRI)) and &lt;a href="https://github.com/cri-o/cri-o" rel="noopener noreferrer"&gt;cri-o&lt;/a&gt; (implementation of the Kubernetes container runtime interface).&lt;/p&gt;

&lt;p&gt;Implementations of the OCI images can be found in Docker,&lt;br&gt;
&lt;a href="https://buildah.io/" rel="noopener noreferrer"&gt;Buildah&lt;/a&gt;, &lt;a href="https://cloud.google.com/blog/products/containers-kubernetes/introducing-kaniko-build-container-images-in-kubernetes-and-google-container-builder-even-without-root-access" rel="noopener noreferrer"&gt;kaniko&lt;/a&gt;, and &lt;a href="https://podman.io/" rel="noopener noreferrer"&gt;Podman&lt;/a&gt;, for example. The OCI-compatible&lt;br&gt;
container images can then be executed on a &lt;strong&gt;CRI&lt;/strong&gt;-compatible runtime (containerd, cri-o), which in turn calls an OCI runtime such as runc or runsc. Runc or runsc then start the actual container on the client.&lt;/p&gt;

&lt;p&gt;This may seem a bit confusing at first glance, but can be clarified using the article from &lt;a href="https://www.tutorialworks.com/difference-docker-containerd-runc-crio-oci/" rel="noopener noreferrer"&gt;tutorialworks&lt;/a&gt; and the following figure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4nvbk7a2lr0o1kl4qed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4nvbk7a2lr0o1kl4qed.png" alt="Source: Tutorial Works - https://www.tutorialworks.com/assets/images/container-ecosystem-docker.drawio.png?ezimgfmt=rs:704x305/rscb6/ng:webp/ngcb6"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The projects involved in running a container with Docker -- Source: Tutorial Works&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Podman not?
&lt;/h2&gt;

&lt;p&gt;Because Podman - unlike Docker - is not a container runtime, but only an implementation of the OCI image specification, Podman cannot launch images itself. It requires the aforementioned CRI container runtime, which in turn uses the hardware-related OCI runtime such as runc or runsc to start the actual containers.&lt;/p&gt;

&lt;p&gt;Podman itself can only take over administration tasks of the containers including build. To execute the images Podman then uses e.g. the mentioned containerd, which in turn runs e.g. runc to actually start the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Podman
&lt;/h3&gt;

&lt;p&gt;The following is a brief explanation of the advantages of Podman over Docker.&lt;/p&gt;

&lt;h4&gt;
  
  
  Proximity to Docker
&lt;/h4&gt;

&lt;p&gt;Despite the sometimes significant differences in the implementation of Podman to Docker, the most common commands of Docker can be used one-to-one. For example, common commands like login, build, pull, push, tag, etc. work exactly as expected by the user. Also, Dockerfiles are still used to build the container images. This makes it much easier for a former Docker user to get started.&lt;/p&gt;

&lt;p&gt;Additionally, this compatibility of commands helps a necessary migration from Docker to Podman. For example, by setting an alias from podman to docker, most scripts can continue to be used.&lt;/p&gt;

&lt;h4&gt;
  
  
  Resource requirements and embedding in OS
&lt;/h4&gt;

&lt;p&gt;Podman was built as a lean and efficient solution. As a result, it has a lower overall memory footprint, is considered significantly faster and comparatively efficient. Also for these reasons, Podman has become a standard for some Linux distributions, such as Fedora's CoreOS.&lt;/p&gt;

&lt;p&gt;Additionally, Podman is now included in the default repositories of Ubuntu 22.04 LTS, see also this &lt;a href="https://podman.io/blogs/2022/04/05/ubuntu-2204-lts-kubic.html" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pods
&lt;/h4&gt;

&lt;p&gt;As mentioned earlier, Kubernetes discontinues support for Docker with version 1.24. This means that dockershim must be replaced by another container runtime such as containerd or cri-o.&lt;/p&gt;

&lt;p&gt;With Podman, however, collaboration should continue to be possible&lt;br&gt;
without any problems. Podman even supports so-called pods, as can also be derived from the name. These were originally established by Kubernetes and can combine several containers, similar to a&lt;br&gt;
Docker-compose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0jcxskt97oc31f0pre0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk0jcxskt97oc31f0pre0.png" alt="Schematic concept of a pod"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Schematic concept of a pod&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This makes it a good idea to use Podman to build and test pods locally before rolling them out to Kubernetes. Alternatively, a local Minikube installation would be a good way to test your Kubernetes manifests. &lt;br&gt;
Podman, however, is leaner and correspondingly more performant in small setups.&lt;/p&gt;

&lt;p&gt;More about Pods in Podman can also be found &lt;a href="https://developers.redhat.com/blog/2019/01/15/podman-managing-containers-pods" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rootless Mode
&lt;/h4&gt;

&lt;p&gt;Podman does not require root permissions to execute its commands unlike Docker which depends on the background process (daemon). Docker requires root privileges or requires the user to be in the Docker group. Should he not be and should sudo not be used, an appropriate error message will be thrown and it cannot be used accordingly.&lt;/p&gt;

&lt;p&gt;Why that may be a problem at all, one now asks the question? As already explained, the Docker daemon requires root privileges on the server and thus creates a potential security risk. If an attacker were to break into the container, there is a risk that he could break out onto the underlying server and then infiltrate other services in the network.&lt;/p&gt;

&lt;h4&gt;
  
  
  Auditing
&lt;/h4&gt;

&lt;p&gt;Unlike Docker, Podman follows the fork-exec model. This means that changes are recorded in the auditd system. This can be an advantage over Docker from a compliance perspective. An exploitation of the audit model in Docker versus the model in Podman can be understood thanks to the following blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations of Podman
&lt;/h2&gt;

&lt;p&gt;There are two main limitations that Podman brings with it. These will be briefly explained.&lt;/p&gt;

&lt;h4&gt;
  
  
  Linux based
&lt;/h4&gt;

&lt;p&gt;Podman currently only runs stably on Linux-based systems. Under Windows or MacOS it becomes a bit more demanding, although it is possible with detours.&lt;/p&gt;

&lt;p&gt;On Windows, Podman can be used well &lt;a href="https://opensource.com/article/21/10/podman-windows-wsl" rel="noopener noreferrer"&gt;via WSL&lt;/a&gt;, on MacOS it can be installed directly with homebrew. However, these solutions are not yet fully developed and are still considered "in development". &lt;br&gt;
More about this can be found in the official Podman &lt;a href="https://podman.io/blogs/2021/09/06/podman-on-macs.html" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Personally, I've been using Podman for some time now with a WSL 2 and the Ubuntu distribution and I'm basically very satisfied. Here and there are still minor teething problems, but these are usually quickly fixable via google search.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker-Compose
&lt;/h4&gt;

&lt;p&gt;Podman itself does not support Docker Compose to launch multiple&lt;br&gt;
containers locally. There are two alternatives for this. First, there is already a project called &lt;a href="https://github.com/containers/podman-compose" rel="noopener noreferrer"&gt;Podman-Compose&lt;/a&gt; that is supposed to fulfill the core functionalities of Docker-Compose, and second, Podman supports the pods described above. These can also be used to launch and manage multiple containers at once - even via a more Kubernetes-friendly path. &lt;/p&gt;

&lt;p&gt;Another useful alternative to Docker-Compose is, for example, the use of &lt;a href="https://minikube.sigs.k8s.io/docs/start/" rel="noopener noreferrer"&gt;minikube&lt;/a&gt; or also &lt;a href="https://k3d.io/" rel="noopener noreferrer"&gt;k3d&lt;/a&gt;. These tools can be used to easily and quickly roll out local Kubernetes clusters. These can then be used for development purposes to deploy and test local Kubernetes objects such as deployments, services or pods.&lt;/p&gt;

&lt;h3&gt;
  
  
  MacOS: Alternative Lima
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffj4ow2ewdttxceo82yhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffj4ow2ewdttxceo82yhe.png" alt="Source: https://github.com/lima-vm/lima"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Source: &lt;a href="https://github.com/lima-vm/lima" rel="noopener noreferrer"&gt;https://github.com/lima-vm/lima&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Besides Podman, there is another alternative worth mentioning:&lt;br&gt;
&lt;a href="https://github.com/lima-vm/lima" rel="noopener noreferrer"&gt;Lima&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lima - short for &lt;strong&gt;Li&lt;/strong&gt;nux virtual &lt;strong&gt;ma&lt;/strong&gt;chines - is mainly used as an alternative for MacOS in this context and comes with &lt;a href="https://www.qemu.org/" rel="noopener noreferrer"&gt;QEMU&lt;/a&gt; (a hypervisor), &lt;a href="https://containerd.io/" rel="noopener noreferrer"&gt;containerd&lt;/a&gt; and &lt;a href="https://github.com/containerd/nerdctl" rel="noopener noreferrer"&gt;nerdctl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thus, Lima uses QEMU in the background for virtualization, containerd as container runtime and nerdctl as Docker-compatible CLI for containerd.&lt;br&gt;
Lima is similar to the Windows Subsystem for Linux (WSL) in version 2.&lt;/p&gt;

&lt;p&gt;Thus, it is definitely worth a look for our Mac users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outlook Unikernel
&lt;/h3&gt;

&lt;p&gt;Another possibility to switch from Docker containers are the so-called &lt;a href="https://github.com/cetic/unikernels" rel="noopener noreferrer"&gt;Unikernels&lt;/a&gt;. These are briefly mentioned here for the sake of completeness, even though they currently have no significance in the Kubernetes context. An interesting &lt;a href="https://hackernoon.com/docker-is-dead-long-live-the-unikernel-2yn3zdr" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; on the topic can be found on Hackernoon. It is an interesting construct that may one day find its way into the world of Kubernetes if containers can be replaced by unikernels. Currently, the use of unikernels would not be feasible in my opinion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgbw48egwkkgc4g92fyi.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgbw48egwkkgc4g92fyi.jpeg" alt="Source: https://github.com/cetic/unikernels"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Source: &lt;a href="https://github.com/cetic/unikernels" rel="noopener noreferrer"&gt;https://github.com/cetic/unikernels&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Last but not least, I would like to give a summary of the usage of&lt;br&gt;
Docker and Podman in fme AG to support our point of view.&lt;/p&gt;

&lt;p&gt;Many of us have used the Docker suite for years - some continue to use it in ongoing projects, some have already switched to other&lt;br&gt;
alternatives.&lt;/p&gt;

&lt;p&gt;There is - as always - no black and white, but many shades of gray in between. Accordingly, there are still plenty of use cases for Docker Desktop, and some customers are also willing to pay the associated licensing costs.&lt;/p&gt;

&lt;p&gt;However, there are also many opposing voices to this - both in the community and among our customers. The sudden change of a  licensing model is a nuisance there and contributes to the fact that people refrain from this software.&lt;/p&gt;

&lt;p&gt;We try to use Podman, Lima or similar free software in newer projects, because we want to stay independent from vendors and licensing constraints. In my opinion, open source and free software is an important cornerstone of successful and promising IT projects. Even Microsoft has understood and lived this in recent years by investing heavily in cross-platform compatibility with products like .NET Core and WSL.&lt;/p&gt;

&lt;p&gt;For this reason, I am pro-Podman and the win - in my opinion - goes to Podman and the idea of free software.&lt;/p&gt;

&lt;h2&gt;
  
  
  Corrections
&lt;/h2&gt;

&lt;p&gt;As explained above, we want to correct the wrong facts in this article. Therefore, this paragraph contains a few corrections and statements.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mirantis acquired Docker Inc.
&lt;/h4&gt;

&lt;p&gt;This was a wrong assumption. Mirantis did not acquire Docker Inc. but Docker Enterprise which was a part of the Docker Inc. For more information have a look at &lt;a href="https://techcrunch.com/2019/11/13/mirantis-acquires-docker-enterprise/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;. Another useful article can be found [German only] &lt;a href="https://entwickler.de/kubernetes/unterm-hammer-mirantis-kauft-docker-enterprise/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Podman uses containerd
&lt;/h4&gt;

&lt;p&gt;This was a wrong assumption. Podman directly uses runC or crun instead of containerd using a technology named &lt;a href="https://github.com/containers/conmon" rel="noopener noreferrer"&gt;conmon&lt;/a&gt;. Some more useful information can be found in &lt;a href="https://iximiuz.com/en/posts/journey-from-containerization-to-orchestration-and-beyond/#container-management" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker-Compose not working with Podman
&lt;/h4&gt;

&lt;p&gt;Docker-Compose is working with Podman &lt;a href="https://www.redhat.com/sysadmin/podman-docker-compose" rel="noopener noreferrer"&gt;since version 3.0&lt;/a&gt;. Basically, you point to the Podman socket instead of Docker socket and it works just fine. This leads to the phenomenon that you can use docker-based scripts and tools with Podman as they would use docker in general. They will not see any difference and they will think that they use Docker even though they are using Podman.&lt;/p&gt;

&lt;h4&gt;
  
  
  Rate limits
&lt;/h4&gt;

&lt;p&gt;We said that rate limits in the Dockerhub are a reason why people and companies are changing from Docker to alternatives like Podman. There was a commentary that said that the Container Registry has nothing to do with the container runtime itself. That's 100% true and valid. Even for Podman the Dockerhub - next to quay.io - is the default registry to pull images.&lt;/p&gt;

&lt;p&gt;Nevertheless, the Dockerhub is part of the Docker ecosystem and therefore a rate limit of the official docker registry brought our customers to the point of changing the registry to other registry providers or implement their own container registry. Therefore they get rid of this service which is part of the plain Docker ecosystem, so its use in enterprise is decreasing.&lt;/p&gt;

&lt;h4&gt;
  
  
  RedHat replaced Docker with Podman in RHEL 8
&lt;/h4&gt;

&lt;p&gt;There was a commentary that it makes only sense that RedHat supports Podman since it comes from their own product forge. Thats definetly right!&lt;/p&gt;

&lt;p&gt;Still, there are a lot of businesses using and paying RedHat, which automatically leads to a decreased use of Docker vs. Podman. Less uses of Docker means more uses of Podman.&lt;/p&gt;

&lt;p&gt;Last but not least, RedHat would not invent Podman when there were not the need of an independent tool to Docker. Podman helps in a few manners where Docker lacks functions like support of Pods, rootless mode etc. Useful information about why RedHat invests in Podman can be found &lt;a href="https://www.redhat.com/en/blog/why-red-hat-investing-cri-o-and-podman" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>podman</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Automate DevOps Workflows using AWS StepFunctions Service Integrations</title>
      <dc:creator>Thomas Laue</dc:creator>
      <pubDate>Tue, 31 May 2022 18:48:45 +0000</pubDate>
      <link>https://forem.com/fmegroup/automate-devops-workflows-using-aws-stepfunctions-service-integrations-250c</link>
      <guid>https://forem.com/fmegroup/automate-devops-workflows-using-aws-stepfunctions-service-integrations-250c</guid>
      <description>&lt;p&gt;&lt;a href="https://aws.amazon.com/step-functions/?step-functions.sort-by=item.additionalFields.postDateTime&amp;amp;step-functions.sort-order=desc" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt;, a serverless workflow orchestration service offering by AWS, has been around since several years now. Many blog posts (like &lt;a href="https://aws.amazon.com/blogs/devops/using-aws-step-functions-state-machines-to-handle-workflow-driven-aws-codepipeline-actions/" rel="noopener noreferrer"&gt;Using AWS Step Functions State Machines to Handle Workflow-Driven AWS CodePipeline Actions&lt;/a&gt;), presentations and learning courses (e.g. &lt;a href="https://theburningmonk.thinkific.com/courses/complete-guide-to-aws-step-functions" rel="noopener noreferrer"&gt;Complete guide to AWS Step Functions&lt;/a&gt;) have been published showing the capabilities and rich feature set provided. &lt;/p&gt;

&lt;p&gt;However not many of them deal with topics related to DevOps tasks -- maybe because Step Functions only offered a limited set of direct service integrations like &lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;AWS Lambda&lt;/a&gt; until recently. Accessing an AWS API required using for instance an AWS SDK or AWS CLI commands in a script or Lambda function, but this changed a few months ago.&lt;/p&gt;

&lt;p&gt;In September 2021 AWS added support for over 200 AWS Services with AWS SDK integration resulting in over 9000 AWS API Actions available. Only a few weeks before, another major enhancement, the new Workflow Studio -- a low-code visual tool for building state machines, had been released so that it is now easier than ever to build workflows -- from simple to complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The challenge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Around the same time, we joined a migration project at a customer who was moving a large application which had been hosted on-premises so far to AWS using services like EC2, RDS, ALB.... Some of the typical operational tasks like managing the database servers are now gone as AWS takes care for the heavy lifting but new ones have arrived and others stay the same.&lt;/p&gt;

&lt;p&gt;As the project proceeded, we thought about how we could automate as many operational tasks as possible using native AWS services. AWS Step Functions Service Integrations came right around the corner to make our life much easier. We were able to handle many repeating tasks by creating state machines which are sometimes triggered by scheduled &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;Amazon EventBridge&lt;/a&gt; rules or used manually via CLI or Console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The simple one&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex7ztjkpman23lfcdeei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex7ztjkpman23lfcdeei.png" alt="Simple Workflow" width="505" height="401"&gt;&lt;/a&gt; A workflow consisting only of two steps (neglecting &lt;em&gt;Start&lt;/em&gt; and &lt;em&gt;End&lt;/em&gt;) is triggered shortly before the next EC2 maintenance window to get an overview about all security patches which will be installed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/systems-manager/" rel="noopener noreferrer"&gt;AWS Systems Manager&lt;/a&gt;`s service integration &lt;em&gt;ssm:describeInstancePatches&lt;/em&gt; is used to get the list all patches which will be sent to an AWS SNS topic in order to be delivered to an email inbox of someone who is in charge to check if there might be a conflict ahead with the application requirements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq32d1x13m801vxhc2z2h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq32d1x13m801vxhc2z2h.png" alt="API Parameter" width="586" height="450"&gt;&lt;/a&gt; The Workflow Studio editor makes it quite easy to assemble a workflow and to enrich every step with the required parameters and settings. All service integrations are based on the AWS SDK API calls so that the parameters can be retrieved from the SDK documentation (an example is shown for Systems Manager API).&lt;/p&gt;

&lt;p&gt;Workflow Editor allows exporting the state machine definition to a JSON or YAML file so that it can be included into an infrastructure as code project using for instance Terraform.&lt;/p&gt;

&lt;p&gt;Information like the EC2 instance ID or the SNS topic ARN can be derived during deploy time using for instance Terraform template variables as shown in the example JSON state machine definition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr4wdkjfxo9rlerohr41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr4wdkjfxo9rlerohr41.png" alt="State Machine Definition" width="800" height="809"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The big benefit of using Step Functions is that no custom code and no additional overhead for managing a Lambda function is required to complete this task and the best thing: the state machine is quite intuitive to create, self-documenting and easy to follow and to recap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The more complex process&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Following the sample principles, it is possible to create more complex workflows. The given example shows a workflow which is used to restart all servers belonging to the web app tier which are behind an AWS application load balancer in a rolling manner. No application downtime is required in order to restart them as only a certain number is restarted at once.&lt;/p&gt;

&lt;p&gt;In the first step, the alarm actions of some CloudWatch alarms and which should not fire during the restart process and some AWS EventBridge rules are disabled using a Lambda function as the logic to filter these resources needs some custom code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpg0gjq5r128tc09tcxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpg0gjq5r128tc09tcxz.png" alt="More Complex Workflow" width="774" height="973"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A property of the AWS Step Functions &lt;em&gt;Map&lt;/em&gt; state, the &lt;em&gt;Maximum&lt;br&gt;
Concurrency Control&lt;/em&gt;, is used to restrict the number of instances which are deregistered from the ALB target group, followed by a reboot and a final check if the application has been launched successfully before bringing it back into the target group.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhw0oslih1nxnhhwyihu1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhw0oslih1nxnhhwyihu1.png" alt="Concurrency Control Setting" width="572" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rebooting only a limited number of instances makes sure that the application stays online, and that always enough servers are available to handle user traffic without a significant influence on the user experience.&lt;/p&gt;

&lt;p&gt;The new AWS SDK service integrations help again to model the workflow as a sequence of steps must be followed in order to reboot a running instance successfully. Not only has a server to be de-/registered from the target group (among others using &lt;em&gt;elasticloadbalancingv2:registerTargets&lt;/em&gt; SDK command) but also to be rebooted (&lt;em&gt;ec2:rebootInstances&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;After a certain wait period, an application startup check is performed to make sure that everything is working correctly using a Lambda function as the whole check process requires again some custom logic. Only a healthy and working server should be put back into the ALB target group.&lt;/p&gt;

&lt;p&gt;The application requires some minutes to get everything sorted out until it is ready to serve whereby the startup time  various depending on factors like external database connections... The &lt;em&gt;Wait&lt;/em&gt; state helps in this case to pause the workflow for a certain time. Nevertheless, it can happen that the following startup check fails as the application is not yet ready and another wait period is required.&lt;/p&gt;

&lt;p&gt;An in-build "for-loop" feature for Step Functions would be quite helpful in this case to re-run the last two steps (wait + startup check) again. It is possible to model this construct using a &lt;em&gt;Choice&lt;/em&gt; state which checks the result return from startup check Lambda function and acts upon it (i.e., go back to the &lt;em&gt;Wait&lt;/em&gt; state if the application is not ready yet). &lt;/p&gt;

&lt;p&gt;However, this feels somehow clumsy and more like a workaround. Additionally, a break condition (e.g., max. number of checks is required) which introduces a stateful condition which must be passed somehow around or stored somewhere.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmphfu3g9jh3qv9pl5mz2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmphfu3g9jh3qv9pl5mz2.png" alt="Kind of For-Loop" width="682" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Custom Retry and Error Handling&lt;/em&gt; for Lambda functions, another cool feature of Step Functions, comes to our rescue. Custom errors which are thrown from a Lambda function can be handled. Depending on the use case, a &lt;em&gt;Catcher&lt;/em&gt; or a &lt;em&gt;Retrier&lt;/em&gt; for this custom error class might be defined to deal with this situation. The later one is used to simulate a "for-loop" without relying on the &lt;em&gt;Choice&lt;/em&gt; state workaround.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyz8bsj4v7myqoxfxj45.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyz8bsj4v7myqoxfxj45.png" alt="Code Snipped of Custom Error Class" width="661" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lambda raises a custom &lt;em&gt;InstanceNotYetStartedException&lt;/em&gt; in case the health check fails. This exception is handled by a specific &lt;em&gt;Retrier&lt;/em&gt; which defines a longer wait interval (120 seconds) to give the application some additional time before the next check. This whole procedure is repeated up to three times in this case until it can be assumed that something went wrong and should be handled otherwise (processing moves on to a &lt;em&gt;States.ALL Catcher&lt;/em&gt; which calls a SNS integration step for publishing an alarm).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7yjm9flexmq0ou510ol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7yjm9flexmq0ou510ol.png" alt="Error and Retry Handling Parameters" width="556" height="881"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a last note to this workflow: the &lt;em&gt;Map&lt;/em&gt; state fails as soon as one if its execution has failed. All running inner executions are aborted and all waiting once are cancelled. Care should be taken for this scenario: adding a dead later queue to the inner &lt;em&gt;Map&lt;/em&gt; state workflow would be one option, defining a &lt;em&gt;States.ALL Catcher&lt;/em&gt; on the &lt;em&gt;Map&lt;/em&gt; state level another one or even failing the complete state machine execution by purpose. The best error handling method depends on the workflow requirements. The global &lt;em&gt;Catcher&lt;/em&gt; is used in the presented case as some additional steps (putting the deactivated CloudWatch alarms back on place) must be&lt;br&gt;
executed in all cases.&lt;/p&gt;

&lt;p&gt;When not to use&lt;/p&gt;

&lt;p&gt;Step Functions has some limits like every other AWS service which might prevent one from using it in some rare cases or which requires a workaround. Furthermore, there are external API properties which might not fit to Step Functions. Two examples should shortly be discussed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Maximum input/output size for a task is 256 KB: AWS API calls might return a lot of JSON data but there are various mechanisms like the &lt;em&gt;filters&lt;/em&gt; parameter and &lt;em&gt;pagination&lt;/em&gt; support in place to narrow down the scope of a request. Additionally, Step Functions provide output processing functions to extract the data of interest so that this limitation should not be a blocker for most use cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to deal with API calls supporting pagination: many AWS API endpoints return a maximum number of items and an additional &lt;em&gt;NextToken&lt;/em&gt; value which can be used to retrieve the next batch with a following call. The clumsy &lt;em&gt;Choice&lt;/em&gt;-state construct mentioned above could be used to handle this, but this is not practical. A Lambda function is much more suited in this situation in case a lot of data must be retrieved.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wrapping up&lt;/p&gt;

&lt;p&gt;This blog presents use cases for Step Functions which might not be the most common ones out there but proved to be extremely useful. The new SDK integrations have opened a wide field of possibilities to model workflows visually without writing a lot of custom code (even though Lambda is always there if something cannot be solved by in-build mechanisms).&lt;/p&gt;

&lt;p&gt;The Step Functions Workflow Studio allows to design and build-up workflows from simple to quite complex ones in an intuitive and rapid way. The ready-to-be-used workflow can be exported to code (is JSON code?) so that a developer's heart does not need to cry and the integration into an infrastructure as code framework can be made.&lt;/p&gt;

&lt;p&gt;Some additional features like more intrinsic functions (e.g., string processing) to deal with the sometimes very large JSON results of AWS SKD calls would make working with Step Functions even easier (big point for &lt;a href="https://twitter.com/search?q=%23awswishlist" rel="noopener noreferrer"&gt;#awswishlist&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>python</category>
      <category>serverless</category>
      <category>aws</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
