Cloud architecture refers to how technologies and components are built in a cloud environment. A cloud environment comprises a network of servers that are located in various places globally, and each serves a specific purpose. With the growth of cloud computing and cloud-native development, modern development practices are constantly changing to adapt to this rapid evolution. This Zone offers the latest information on cloud architecture, covering topics such as builds and deployments to cloud-native environments, Kubernetes practices, cloud databases, hybrid and multi-cloud environments, cloud computing, and more!
Many applications are now being hosted on public cloud platforms, and it becomes imperative to leverage the cloud to store their data and application parameters. And of the most popular cloud providers, Amazon Web Services (AWS) is the most widely used. While AWS offers many solutions for storing application parameters, understanding which option best fits your application and use case can be difficult. In this article, we’ll dive into the best ways to store your application parameters in AWS. Overview of Application Properties Storage Let us take the example of AWS Lambda — a popular compute service that allows developers to run code without provisioning or managing servers. When writing code for Lambda functions, it's common to use configuration properties to define how the function should operate. Configuration properties can include things like environment variables, database connection strings, and other settings that are specific to your application. One option for configuration properties in AWS is to use the Lambda environment variables feature. This allows you to define key-value pairs that are passed to your function at runtime. Another way to store configuration properties for Lambda functions is to use the AWS Systems Manager Parameter Store. This service provides a centralized location to store and manage your configuration data, making it easy to retrieve and update properties as needed. You can use the Parameter Store API or the AWS Command Line Interface (CLI) to interact with the service programmatically. Another option for managing properties in AWS is the Secrets Manager service. Similar to AWS Parameter Store, Secrets Manager provides a secure and centralized location to store and manage secrets such as database credentials, API keys, and other sensitive information. However, Secrets Manager offers additional features such as automatic rotation of secrets, integration with other AWS services like RDS, and the ability to retrieve secrets directly from your code without having to call an API or CLI. Overall, there are many ways to store your application parameters in AWS, and the decision should be based on your application's specific use case and requirements. With the right approach, you can take advantage of the scalability, security, and cost-effectiveness that the cloud provides. If you're ready to get started with AWS and need help deciding how to store your application parameters, contact us today to get the guidance you need. Benefits and Limitations Let us then look at the benefits and limitations of each of the options. AWS Lambda lets you configure environment variables for your function, which can be used to store configuration data or other sensitive information. This is probably the simplest way to configure parameters. However, there are some limitations to keep in mind when using environment variables with Lambda. First, the total size of all environment variables for a single function cannot exceed 4 KB. This means that if you need to store a large amount of data, you may want to consider using another service, such as AWS Parameter Store or AWS Secrets Manager. Another limitation of Lambda environment variables is that they are static and cannot be changed during runtime. If you need to update an environment variable value, you will need to redeploy your function with the new value. Also, these values are not available to other functions. Finally, it's important to note that environment variables are not encrypted by default. If you are storing sensitive information in an environment variable, you should consider encrypting it using a service like AWS KMS. AWS Parameter Store is a good choice for storing configuration data and secrets that are not frequently changed and do not require advanced features like automatic secret rotation. It can be easily accessed programmatically using the Parameter Store API or AWS CLI. AWS Parameter Store has certain limits that should be kept in mind when using the service. The maximum size of a parameter value is 4KB, which means that larger data sets will need to be broken up into smaller pieces. Additionally, there is a limit on the number of parameters that can be stored in the Parameter Store, which varies based on the AWS region and can be increased through a support request. It's also worth noting that Parameter Store has a maximum request rate of 100 transactions per second. If your application requires a higher request rate, you may want to consider using a different service or architecture. On the other hand, Secrets Manager is specifically designed for secret management, providing additional features such as automatic secret rotation and integration with other AWS services. It is a better option when advanced management of secrets is required, such as when you need to handle many secrets or rotate them frequently. Secrets Manager can be configured for auto rotation of credentials. However, it needs to be considered that once this is set up, Secrets Manager will immediately rotate the secrets, and code with hard-coded credentials will start failing. Cost and Security Considerations When considering which service to use, it's important to take into account both cost and security considerations. AWS Parameter Store is generally the most cost-effective option for storing configuration data and secrets, as it has a free tier and low pricing for additional usage. However, it may not be the most secure option if you need advanced security features like encryption or access control. AWS Parameter Store provides multiple options for encrypting parameter values, ensuring that sensitive information is kept secure. All parameters can be encrypted using AWS Key Management Service (KMS), which provides a high level of security and control over encryption keys. Additionally, Parameter Store supports customer-managed KMS keys, allowing you to have even greater control over the encryption process. Secrets Manager, on the other hand, offers more advanced security features and can help ensure compliance with security best practices. However, it may be more expensive than Parameter Store, depending on your usage. When Do You Implement Parameter Store vs. Secrets Manager vs. Lambda Environment Variable? Deciding when to implement AWS Parameter Store, Secrets Manager, or environment variables in Lambda depends on the specific requirements and use case of your application. AWS Parameter Store is a good choice for storing configuration data and secrets that are not frequently changed and do not require advanced features like automatic secret rotation. It can be easily accessed programmatically using the Parameter Store API or AWS CLI. On the other hand, Secrets Manager is specifically designed for secret management, providing additional features such as automatic secret rotation and integration with other AWS services. It is a better option when advanced management of secrets is required, such as when you need to handle many secrets or rotate them frequently. Environment variables in Lambda are best suited for storing simple configuration values that do not contain sensitive information. They can be easily accessed within the function code but do not provide any additional security features. In summary, AWS Parameter Store is suitable for general-purpose configuration data and lightweight secret management. Secrets Manager is a better fit when advanced secret management capabilities are required. Environment variables are best used for simple configuration values that are not sensitive.
This article describes the steps involved in setting up Lambda Functions for accessing AWS S3 in a Cross Account scenario. The configuration process for this integration will be explained in detail, providing a comprehensive guide. By following these instructions, readers can gain an understanding of the requirements for Cross Account access to S3 using Lambda Functions. This article is aimed at AWS developers who are looking to improve their knowledge of Cross Account S3 access and want to learn how to configure Lambda Functions for this purpose. A high-level illustration of AWS Cross Account S3 access through Lamda. Prerequisites To create this solution, you must have the following prerequisites: An AWS account. Python 3, preferably the latest version. Basic knowledge of AWS SDK for Python (boto3). Basic knowledge of S3 and KMS Services. Basic knowledge of Lambda and IAM Roles. Setup in Account A In AWS Account A, create the following resources step by step. Create S3 Bucket Setup S3 bucket policy Setup KMS key Create S3 Bucket Let's begin by creating an S3 bucket in AWS Account A. Firstly, log in to the AWS console and then navigate to the S3 dashboard. S3Dashboard Please enter the bucket name and region, and then click on the "Create" button. Successfully created bucket. Setup S3 Bucket Policy It is important to set up a bucket policy that allows a cross-account lambda role to access an S3 bucket and perform S3 operations. JSON { "Version": "2012-10-17", "Statement": [ { "Sid": "Enforce HTTPS Connections", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::role/lambda-s3-access-role" }, "Action": "s3:*", "Resource": "arn:aws:s3:::demo-s3-cross-account-bucket/*" } ] } Please navigate to the permission tab and then proceed to edit the bucket policy. Replace the existing policy with the one provided above. Setup KMS Key This step is crucial for enabling encryption and decryption of S3 bucket data using the KMS key. It's important to remember that if the bucket is associated with the KMS key, a cross-account lambda role must be authorized to avoid a 401 unauthorized error. To achieve this, we need to modify the existing KMS key policy to permit cross-region lambda roles to perform encryption or decryption operations. JSON { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*", "kms:ReEncrypt*" ], "Resource": [ "arn:aws:iam:::role/lambda-s3-access-role", ] } ] } To enable the cross-account lambda role, go to the KMS dashboard and choose the key that's linked to the S3 bucket. Then, select the "Key Policy" tab and add the above key policy. Setup in Account B In AWS Account B, create the following resources step by step. Create IAM Policy and Role Create Lambda function Test Lambda and Verify Create IAM Policy and Role Let's move on and begin creating an IAM role in AWS Account B. Log in to the AWS Account B console and navigate to the IAM dashboard. IAM Dashboard Choose "AWS service" as the trusted identity type and "Lambda" as the use case, then click on the "Next" button. Click on the "Create policy" button to add the role policy. This role policy enables Lambda to access a cross-account S3 bucket, which requires specifying the S3 bucket name and KMS key. JSON { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:PutObjectAcl", "s3:List*" ], "Resource": [ "arn:aws:s3:::demo-s3-cross-account-bucket", "arn:aws:s3:::demo-s3-cross-account-bucket/*" ] }, { "Effect": "Allow", "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:DescribeKey", "kms:GenerateDataKey*" ], "Resource": [ "arn:aws:kms:::key/demo-s3-cross-account-kms-key", ] } ] ] } Add the above policy on the JSON tab and then click on the "Next" button. Please provide a policy name and click the "Create policy" button. Once the role policy is created successfully, map it to the role. Select the policy and click the "Next" button. Provide the role name and click the "Create role" button. The role is created successfully; then, we will use this role for lambda in the coming step. Create Lambda Function Now, we'll start creating a Lambda function in AWS Account B. Please log in to the AWS Account B console and go to the Lambda dashboard. Python import logging import boto3 from botocore.exceptions import ClientError import os def lambda_handler(event, context): upload_file(event['file_name'], event['bucket'], event['object_name']): def upload_file(file_name, bucket, object_name=None): """Upload a file to an S3 bucket :param file_name: File to upload :param bucket: Bucket to upload to :param object_name: S3 object name. If not specified then file_name is used :return: True if file was uploaded, else False """ # If S3 object_name was not specified, use file_name if object_name is None: object_name = os.path.basename(file_name) # Upload the file s3_client = boto3.client('s3') try: response = s3_client.upload_file(file_name, bucket, object_name) except ClientError as e: logging.error(e) return False return True Create a Lambda function using Python 3.10 runtime, and select the previously created role. Then, add the Python code mentioned above and create the Lambda function. Test Lambda Function and Validate Finally, let's go through the steps we provisioned to test and validate the process and ensure that it's working properly. To execute the Lambda function, provide the necessary input in the event JSON, as shown in the illustration below, and click on the "Test" button. After successful execution of the Lambda function in AWS account B, verify the S3 bucket in AWS account A. You will see the uploaded file as illustrated below. Conclusion In conclusion, this article has provided a detailed guide on how to set up Lambda Functions for accessing AWS S3 in a Cross Account scenario. By following the step-by-step instructions outlined in this article, AWS developers can enhance their knowledge of Cross Account S3 access and gain the skills needed to configure Lambda Functions for this purpose. This guide is designed to help readers understand the requirements for Cross Account access to S3 using Lambda Functions and to provide them with the knowledge and tools necessary to successfully complete the configuration process. With the information provided in this article, readers can confidently set up Lambda Functions for Cross Account S3 access and take advantage of the benefits that this integration offers.
A few years in software development do not equate to a linear amount of knowledge and information. So is true for the .NET ecosystem. The first release of .NET 1.0 saw the light of day in January 2002. The significance of the event was not the palindrome year. The new paradigm is offered to the traditional C++ MFC, VB, and classic ASP developers, supporting two main new languages, C#, and VB.NET. It started as a proprietary Windows technology, closed source language, primarily appealing to the companies on Microsoft Windows stack, tied to the sole IDE, Visual Studio. While .NET was a step forward, the pace and the options outside the designated use were abysmal. The situation took a sharp turn, with a substantial change in 2016 with the introduction of .NET Core. Starting from the heart of the .NET ecosystem, ASP.NET, the change has spread through the entire platform, leading to a complete re-imagination and makeover of runtime and language. Open source, cross-platform, and free for commercial use, .NET and C# became viable options for many projects that traditionally would go with another platform and languages. From web to mobile, from desktop to backend systems, in any cloud environment, .NET is a solid and viable option with outstanding experience and rich offerings. Common Challenges When Uncle Ben told Pete, "With great power comes great responsibility" in the 2002 Spiderman movie, he was not referring to the newly emerging .NET platform. This phrase could be applied to the challenges any developer on any platform using any language will eventually face. And .NET is not an exception. Here are just a few things that can go wrong. Cross-platform software development is often taking place on a single platform. It could be Windows, Linux, or Mac operating systems. The software then needs to be deployed and executed on a platform that might not be a one-to-one match with the development or testing environment. And while technologies such as Docker containers have made it simpler to "ship your development environment to production," it is still not a bulletproof solution. The differences between environments still pose a risk of running into environmental discrepancies, leading to bugs. Cloud environments pose even more significant challenges, with infrastructure and code executed remotely on the environment outside our reach and control. It could be Azure App Service, AWS Fargate, or GCP Cloud Functions. These services provide foundational troubleshooting but cannot cater to the specifics of the application and its use cases, usually involving additional intrusive services required for troubleshooting. Troubleshooting Options .NET developers are offered a few options to troubleshoot the problems faced in production running .NET-based systems. Here are a few of those: Crash dumps. Analyzing crash dumps is a skill that only a few possess. A more significant challenge is that the analysis can pinpoint why the code was crushed but not what led to that critical moment. Metric and counters. Emitting metrics and counter values from the code, collected and visualized, allows better insights into the remotely executing code. But it lacks the necessary dials and knobs to dynamically adjust the scope of focus. For example, emitting the value of a specific one or more variables within a particular method is not an option. Logs. Logging is one of the oldest and the most common techniques to help the developer identify issues in the running code. The code is decorated with additional instructions that emit information into a sink, such as a file, a remote service, or any other destination where the logs are retained for a defined period. This option provides a lot of information, but it also has drawbacks. Unnecessary amounts of data irrelevant to the outage or the bug being investigated are stored and muddy the water. In a scaled-out solution, these logs are multiplied. And when consolidated into a single service, such as Application Insights, it requires accurate filtering to separate the wheat from the chaff. Not to mention the price tag associated with processing and storing those logs. But one of the most significant drawbacks of static logging is the inability to adjust what is logged and the duration of the logging session. Snapshot debugging. Ability to debug remotely executed code within the IDE. Upon exceptions thrown, a debug snapshot is collected from the running application and sent to Application Insights. The stored information includes the stack trace. In addition, a minidump can be obtained in Visual Studio to enhance visibility into why an exception occurred in the first place. But this is still a reactive solution to a problem that requires a more proactive approach. Dynamic Logging and Observability Meet dynamic logging with Lightrun. With dynamic logging, the code does not have to be instrumented at every spot. Instead, we want to gain visibility at run time. So instead, the code is left as-is. The magic ingredient is the agent enabling dynamic logging and observability that is added to the solution in the form of a NuGet package. Once included, it is initialized once in your application starting code. And that is it. From there, the Lightrun agent takes care of everything else. And by that, it means connecting to your application at any time to retrieve logs on an ad-hoc basis, without the unnecessary code modification/instrumentation, going through the rigorous code changes approval process, testing, and deployment. Logs, metrics, and snapshots can be collected and presented on demand, all without wracking a substantial bill otherwise incurred with static logging and metrics storage or leaving the comfort of the preferable .NET IDE of your choice — VS Code, Rider, or Visual Studio (coming soon). The results are immediately available in the IDE or streamed to your observability services such as Sentry, New Relic, DataDog, and many other services in the application performance monitoring space. Summary .NET offers a range of software development options with rich support for libraries and platforms. With extra help from Lightrun, a symbiotic relationship takes code troubleshooting to the next level, where investigating and resolving code deficiencies does not have to be a lengthy and costly saga.
In this blog post, you will be using AWS Controllers for Kubernetes on an Amazon EKS cluster to put together a solution wherein data from an Amazon SQS queue is processed by an AWS Lambda function and persisted to a DynamoDB table. AWS Controllers for Kubernetes (also known as ACK) leverage Kubernetes Custom Resource and Custom Resource Definitions and give you the ability to manage and use AWS services directly from Kubernetes without needing to define resources outside of the cluster. The idea behind ACK is to enable Kubernetes users to describe the desired state of AWS resources using the Kubernetes API and configuration language. ACK will then take care of provisioning and managing the AWS resources to match the desired state. This is achieved by using Service controllers that are responsible for managing the lifecycle of a particular AWS service. Each ACK service controller is packaged into a separate container image that is published in a public repository corresponding to an individual ACK service controller. There is no single ACK container image. Instead, there are container images for each individual ACK service controller that manages resources for a particular AWS API. This blog post will walk you through how to use the SQS, DynamoDB, and Lambda service controllers for ACK. Prerequisites To follow along step-by-step, in addition to an AWS account, you will need to have AWS CLI, kubectl, and Helm installed. There are a variety of ways in which you can create an Amazon EKS cluster. I prefer using eksctl CLI because of the convenience it offers. Creating an EKS cluster using eksctl can be as easy as this: eksctl create cluster --name my-cluster --region region-code For details, refer to Getting started with Amazon EKS – eksctl. Clone this GitHub repository and change it to the right directory: git clone https://github.com/abhirockzz/k8s-ack-sqs-lambda cd k8s-ack-sqs-lambda Ok, let's get started! Setup the ACK Service Controllers for AWS Lambda, SQS, and DynamoDB Install ACK Controllers Log into the Helm registry that stores the ACK charts: aws ecr-public get-login-password --region us-east-1 | helm registry login --username AWS --password-stdin public.ecr.aws Deploy the ACK service controller for Amazon Lambda using the lambda-chart Helm chart: RELEASE_VERSION_LAMBDA_ACK=$(curl -sL "https://api.github.com/repos/aws-controllers-k8s/lambda-controller/releases/latest" | grep '"tag_name":' | cut -d'"' -f4) helm install --create-namespace -n ack-system oci://public.ecr.aws/aws-controllers-k8s/lambda-chart "--version=${RELEASE_VERSION_LAMBDA_ACK}" --generate-name --set=aws.region=us-east-1 Deploy the ACK service controller for SQS using the sqs-chart Helm chart: RELEASE_VERSION_SQS_ACK=$(curl -sL "https://api.github.com/repos/aws-controllers-k8s/sqs-controller/releases/latest" | grep '"tag_name":' | cut -d'"' -f4) helm install --create-namespace -n ack-system oci://public.ecr.aws/aws-controllers-k8s/sqs-chart "--version=${RELEASE_VERSION_SQS_ACK}" --generate-name --set=aws.region=us-east-1 Deploy the ACK service controller for DynamoDB using the dynamodb-chart Helm chart: RELEASE_VERSION_DYNAMODB_ACK=$(curl -sL "https://api.github.com/repos/aws-controllers-k8s/dynamodb-controller/releases/latest" | grep '"tag_name":' | cut -d'"' -f4) helm install --create-namespace -n ack-system oci://public.ecr.aws/aws-controllers-k8s/dynamodb-chart "--version=${RELEASE_VERSION_DYNAMODB_ACK}" --generate-name --set=aws.region=us-east-1 Now, it's time to configure the IAM permissions for the controller to invoke Lambda, DynamoDB, and SQS. Configure IAM Permissions Create an OIDC Identity Provider for Your Cluster For the steps below, replace the EKS_CLUSTER_NAME and AWS_REGION variables with your cluster name and region. export EKS_CLUSTER_NAME=demo-eks-cluster export AWS_REGION=us-east-1 eksctl utils associate-iam-oidc-provider --cluster $EKS_CLUSTER_NAME --region $AWS_REGION --approve OIDC_PROVIDER=$(aws eks describe-cluster --name $EKS_CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text | cut -d '/' -f2- | cut -d '/' -f2-) Create IAM Roles for Lambda, SQS, and DynamoDB ACK Service Controllers ACK Lambda Controller Set the following environment variables: ACK_K8S_SERVICE_ACCOUNT_NAME=ack-lambda-controller ACK_K8S_NAMESPACE=ack-system AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) Create the trust policy for the IAM role: read -r -d '' TRUST_RELATIONSHIP <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OIDC_PROVIDER}:sub": "system:serviceaccount:${ACK_K8S_NAMESPACE}:${ACK_K8S_SERVICE_ACCOUNT_NAME}" } } } ] } EOF echo "${TRUST_RELATIONSHIP}" > trust_lambda.json Create the IAM role: ACK_CONTROLLER_IAM_ROLE="ack-lambda-controller" ACK_CONTROLLER_IAM_ROLE_DESCRIPTION="IRSA role for ACK lambda controller deployment on EKS cluster using Helm charts" aws iam create-role --role-name "${ACK_CONTROLLER_IAM_ROLE}" --assume-role-policy-document file://trust_lambda.json --description "${ACK_CONTROLLER_IAM_ROLE_DESCRIPTION}" Attach IAM policy to the IAM role: # we are getting the policy directly from the ACK repo INLINE_POLICY="$(curl https://raw.githubusercontent.com/aws-controllers-k8s/lambda-controller/main/config/iam/recommended-inline-policy)" aws iam put-role-policy \ --role-name "${ACK_CONTROLLER_IAM_ROLE}" \ --policy-name "ack-recommended-policy" \ --policy-document "${INLINE_POLICY}" Attach ECR permissions to the controller IAM role. These are required since Lambda functions will be pulling images from ECR. aws iam put-role-policy \ --role-name "${ACK_CONTROLLER_IAM_ROLE}" \ --policy-name "ecr-permissions" \ --policy-document file://ecr-permissions.json Associate the IAM role to a Kubernetes service account: ACK_CONTROLLER_IAM_ROLE_ARN=$(aws iam get-role --role-name=$ACK_CONTROLLER_IAM_ROLE --query Role.Arn --output text) export IRSA_ROLE_ARN=eks.amazonaws.com/role-arn=$ACK_CONTROLLER_IAM_ROLE_ARN kubectl annotate serviceaccount -n $ACK_K8S_NAMESPACE $ACK_K8S_SERVICE_ACCOUNT_NAME $IRSA_ROLE_ARN Repeat the steps for the SQS controller. ACK SQS Controller Set the following environment variables: ACK_K8S_SERVICE_ACCOUNT_NAME=ack-sqs-controller ACK_K8S_NAMESPACE=ack-system AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) Create the trust policy for the IAM role: read -r -d '' TRUST_RELATIONSHIP <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OIDC_PROVIDER}:sub": "system:serviceaccount:${ACK_K8S_NAMESPACE}:${ACK_K8S_SERVICE_ACCOUNT_NAME}" } } } ] } EOF echo "${TRUST_RELATIONSHIP}" > trust_sqs.json Create the IAM role: ACK_CONTROLLER_IAM_ROLE="ack-sqs-controller" ACK_CONTROLLER_IAM_ROLE_DESCRIPTION="IRSA role for ACK sqs controller deployment on EKS cluster using Helm charts" aws iam create-role --role-name "${ACK_CONTROLLER_IAM_ROLE}" --assume-role-policy-document file://trust_sqs.json --description "${ACK_CONTROLLER_IAM_ROLE_DESCRIPTION}" Attach IAM policy to the IAM role: # for sqs controller, we use the managed policy ARN instead of the inline policy (unlike the Lambda controller) POLICY_ARN="$(curl https://raw.githubusercontent.com/aws-controllers-k8s/sqs-controller/main/config/iam/recommended-policy-arn)" aws iam attach-role-policy --role-name "${ACK_CONTROLLER_IAM_ROLE}" --policy-arn "${POLICY_ARN}" Associate the IAM role to a Kubernetes service account: ACK_CONTROLLER_IAM_ROLE_ARN=$(aws iam get-role --role-name=$ACK_CONTROLLER_IAM_ROLE --query Role.Arn --output text) export IRSA_ROLE_ARN=eks.amazonaws.com/role-arn=$ACK_CONTROLLER_IAM_ROLE_ARN kubectl annotate serviceaccount -n $ACK_K8S_NAMESPACE $ACK_K8S_SERVICE_ACCOUNT_NAME $IRSA_ROLE_ARN Repeat the steps for the DynamoDB controller. ACK DynamoDB Controller Set the following environment variables: ACK_K8S_SERVICE_ACCOUNT_NAME=ack-dynamodb-controller ACK_K8S_NAMESPACE=ack-system AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) Create the trust policy for the IAM role: read -r -d '' TRUST_RELATIONSHIP <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OIDC_PROVIDER}:sub": "system:serviceaccount:${ACK_K8S_NAMESPACE}:${ACK_K8S_SERVICE_ACCOUNT_NAME}" } } } ] } EOF echo "${TRUST_RELATIONSHIP}" > trust_dynamodb.json Create the IAM role: ACK_CONTROLLER_IAM_ROLE="ack-dynamodb-controller" ACK_CONTROLLER_IAM_ROLE_DESCRIPTION="IRSA role for ACK dynamodb controller deployment on EKS cluster using Helm charts" aws iam create-role --role-name "${ACK_CONTROLLER_IAM_ROLE}" --assume-role-policy-document file://trust_dynamodb.json --description "${ACK_CONTROLLER_IAM_ROLE_DESCRIPTION}" Attach IAM policy to the IAM role: # for dynamodb controller, we use the managed policy ARN instead of the inline policy (like we did for Lambda controller) POLICY_ARN="$(curl https://raw.githubusercontent.com/aws-controllers-k8s/dynamodb-controller/main/config/iam/recommended-policy-arn)" aws iam attach-role-policy --role-name "${ACK_CONTROLLER_IAM_ROLE}" --policy-arn "${POLICY_ARN}" Associate the IAM role to a Kubernetes service account: ACK_CONTROLLER_IAM_ROLE_ARN=$(aws iam get-role --role-name=$ACK_CONTROLLER_IAM_ROLE --query Role.Arn --output text) export IRSA_ROLE_ARN=eks.amazonaws.com/role-arn=$ACK_CONTROLLER_IAM_ROLE_ARN kubectl annotate serviceaccount -n $ACK_K8S_NAMESPACE $ACK_K8S_SERVICE_ACCOUNT_NAME $IRSA_ROLE_ARN Restart ACK Controller Deployments and Verify the Setup Restart the ACK service controller Deployment using the following commands. It will update service controller Pods with IRSA environment variables. Get list of ACK service controller deployments: export ACK_K8S_NAMESPACE=ack-system kubectl get deployments -n $ACK_K8S_NAMESPACE Restart Lambda, SQS, and DynamoDB controller Deployments: DEPLOYMENT_NAME_LAMBDA=<enter deployment name for lambda controller> kubectl -n $ACK_K8S_NAMESPACE rollout restart deployment $DEPLOYMENT_NAME_LAMBDA DEPLOYMENT_NAME_SQS=<enter deployment name for sqs controller> kubectl -n $ACK_K8S_NAMESPACE rollout restart deployment $DEPLOYMENT_NAME_SQS DEPLOYMENT_NAME_DYNAMODB=<enter deployment name for dynamodb controller> kubectl -n $ACK_K8S_NAMESPACE rollout restart deployment $DEPLOYMENT_NAME_DYNAMODB List Pods for these Deployments. Verify that the AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_ARN environment variables exist for your Kubernetes Pod using the following commands: kubectl get pods -n $ACK_K8S_NAMESPACE LAMBDA_POD_NAME=<enter Pod name for lambda controller> kubectl describe pod -n $ACK_K8S_NAMESPACE $LAMBDA_POD_NAME | grep "^\s*AWS_" SQS_POD_NAME=<enter Pod name for sqs controller> kubectl describe pod -n $ACK_K8S_NAMESPACE $SQS_POD_NAME | grep "^\s*AWS_" DYNAMODB_POD_NAME=<enter Pod name for dynamodb controller> kubectl describe pod -n $ACK_K8S_NAMESPACE $DYNAMODB_POD_NAME | grep "^\s*AWS_" Now that the ACK service controller has been set up and configured, you can create AWS resources! Create SQS Queue, DynamoDB Table, and Deploy the Lambda Function Create SQS Queue In the file sqs-queue.yaml, replace the us-east-1 region with your preferred region as well as the AWS account ID. This is what the ACK manifest for the SQS queue looks like: apiVersion: sqs.services.k8s.aws/v1alpha1 kind: Queue metadata: name: sqs-queue-demo-ack annotations: services.k8s.aws/region: us-east-1 spec: queueName: sqs-queue-demo-ack policy: | { "Statement": [{ "Sid": "__owner_statement", "Effect": "Allow", "Principal": { "AWS": "AWS_ACCOUNT_ID" }, "Action": "sqs:SendMessage", "Resource": "arn:aws:sqs:us-east-1:AWS_ACCOUNT_ID:sqs-queue-demo-ack" }] } Create the queue using the following command: kubectl apply -f sqs-queue.yaml # list the queue kubectl get queue Create DynamoDB Table This is what the ACK manifest for the DynamoDB table looks like: apiVersion: dynamodb.services.k8s.aws/v1alpha1 kind: Table metadata: name: customer annotations: services.k8s.aws/region: us-east-1 spec: attributeDefinitions: - attributeName: email attributeType: S billingMode: PAY_PER_REQUEST keySchema: - attributeName: email keyType: HASH tableName: customer You can replace the us-east-1 region with your preferred region. Create a table (named customer) using the following command: kubectl apply -f dynamodb-table.yaml # list the tables kubectl get tables Build Function Binary and Create Docker Image GOARCH=amd64 GOOS=linux go build -o main main.go aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws docker build -t demo-sqs-dynamodb-func-ack . Create a private ECR repository, tag and push the Docker image to ECR: AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com aws ecr create-repository --repository-name demo-sqs-dynamodb-func-ack --region us-east-1 docker tag demo-sqs-dynamodb-func-ack:latest $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/demo-sqs-dynamodb-func-ack:latest docker push $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/demo-sqs-dynamodb-func-ack:latest Create an IAM execution Role for the Lambda function and attach the required policies: export ROLE_NAME=demo-sqs-dynamodb-func-ack-role ROLE_ARN=$(aws iam create-role \ --role-name $ROLE_NAME \ --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' \ --query 'Role.[Arn]' --output text) aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Since the Lambda function needs to write data to DynamoDB and invoke SQS, let's add the following policies to the IAM role: aws iam put-role-policy \ --role-name "${ROLE_NAME}" \ --policy-name "dynamodb-put" \ --policy-document file://dynamodb-put.json aws iam put-role-policy \ --role-name "${ROLE_NAME}" \ --policy-name "sqs-permissions" \ --policy-document file://sqs-permissions.json Create the Lambda Function Update function.yaml file with the following info: imageURI - The URI of the Docker image that you pushed to ECR, e.g., <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/demo-sqs-dynamodb-func-ack:latest role - The ARN of the IAM role that you created for the Lambda function, e.g., arn:aws:iam::<AWS_ACCOUNT_ID>:role/demo-sqs-dynamodb-func-ack-role This is what the ACK manifest for the Lambda function looks like: apiVersion: lambda.services.k8s.aws/v1alpha1 kind: Function metadata: name: demo-sqs-dynamodb-func-ack annotations: services.k8s.aws/region: us-east-1 spec: architectures: - x86_64 name: demo-sqs-dynamodb-func-ack packageType: Image code: imageURI: AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/demo-sqs-dynamodb-func-ack:latest environment: variables: TABLE_NAME: customer role: arn:aws:iam::AWS_ACCOUNT_ID:role/demo-sqs-dynamodb-func-ack-role description: A function created by ACK lambda-controller To create the Lambda function, run the following command: kubectl create -f function.yaml # list the function kubectl get functions Add SQS Trigger Configuration Add SQS trigger which will invoke the Lambda function when an event is sent to the SQS queue. Here is an example using AWS Console: Open the Lambda function in the AWS Console and click on the Add trigger button. Select SQS as the trigger source, select the SQS queue, and click on the Add button. Now you are ready to try out the end-to-end solution! Test the Application Send a few messages to the SQS queue. For the purposes of this demo, you can use the AWS CLI: export SQS_QUEUE_URL=$(kubectl get queues/sqs-queue-demo-ack -o jsonpath='{.status.queueURL}') aws sqs send-message --queue-url $SQS_QUEUE_URL --message-body user1@foo.com --message-attributes 'name={DataType=String, StringValue="user1"}, city={DataType=String,StringValue="seattle"}' aws sqs send-message --queue-url $SQS_QUEUE_URL --message-body user2@foo.com --message-attributes 'name={DataType=String, StringValue="user2"}, city={DataType=String,StringValue="tel aviv"}' aws sqs send-message --queue-url $SQS_QUEUE_URL --message-body user3@foo.com --message-attributes 'name={DataType=String, StringValue="user3"}, city={DataType=String,StringValue="new delhi"}' aws sqs send-message --queue-url $SQS_QUEUE_URL --message-body user4@foo.com --message-attributes 'name={DataType=String, StringValue="user4"}, city={DataType=String,StringValue="new york"}' The Lambda function should be invoked and the data should be written to the DynamoDB table. Check the DynamoDB table using the CLI (or AWS console): aws dynamodb scan --table-name customer Clean Up After you have explored the solution, you can clean up the resources by running the following commands: Delete SQS queue, DynamoDB table and the Lambda function: kubectl delete -f sqs-queue.yaml kubectl delete -f function.yaml kubectl delete -f dynamodb-table.yaml To uninstall the ACK service controllers, run the following commands: export ACK_SYSTEM_NAMESPACE=ack-system helm ls -n $ACK_SYSTEM_NAMESPACE helm uninstall -n $ACK_SYSTEM_NAMESPACE <enter name of the sqs chart> helm uninstall -n $ACK_SYSTEM_NAMESPACE <enter name of the lambda chart> helm uninstall -n $ACK_SYSTEM_NAMESPACE <enter name of the dynamodb chart> Conclusion and Next Steps In this post, we have seen how to use AWS Controllers for Kubernetes to create a Lambda function, SQS, and DynamoDB table and wire them together to deploy a solution. All of this (almost) was done using Kubernetes! I encourage you to try out other AWS services supported by ACK. Here is a complete list. Happy building!
As organizations migrate to the cloud, they desire to exploit this on-demand infrastructure to scale their applications. But such migrations are usually complex and need established patterns and control points to manage. In my previous blog posts, I covered a few of the proven designs for cloud applications. In this article, I’ll introduce the Orchestration Pattern (also known as the Orchestrator Pattern) to add to the list. This technique allows the creation of scalable, reliable, and fault-tolerant systems. The approach can help us manage the flow and coordination among components of a distributed system, predominantly in a microservices architecture. Let’s dive into a problem statement to see how this pattern works. Problem Context Consider a legacy monolith retail e-commerce website. This complex monolith consists of multiple subdomains such as shopping baskets, inventory, payments etc. When a client sends a request, the website performs a sequence of operations to fulfil the request. In this traditional architecture, each operation can be described as a method call. The biggest challenge for the application is scaling with the demand. So, the organisation decided to migrate this application to the cloud. However, the monolithic approach that the application uses is too restricted and would limit scaling even in the cloud. Adopting a lift and shift approach to perform migration would not reap the real benefits of the cloud. Thus, a better migration would be to refactor the entire application and break it down by subdomains. The new services must be deployed and managed individually. The new system comes with all the improvements of distributed architecture. These distributed and potentially stateless services are responsible for their own sub-domains. But the immediate question is how to manage a complete workflow in this distributed architecture. Let us try to address this question in the next section and explore more about Orchestration Patterns. Monolithic application migration to the cloud What Is Orchestration Pattern We have designed an appropriate architecture where all services operate within their bounded context. However, we still need a component that is aware of the entire business workflow. The missing element is responsible for generating the final response by communicating with all of the services. Think of it like an orchestra with musicians playing their instruments. In an orchestra, a central conductor coordinates and aligns the members to produce a final performance. The Orchestration Pattern also introduces a centralized controller or service known as the orchestrator, similar to a central conductor. The orchestrator does not perform business logic but manages complex business flows by calling independently deployed services, handling exceptions, retrying requests, maintaining state, and returning the final response. Orchestrator Pattern The figure above illustrates the pattern. It has three components: the orchestrator or central service, business services that need coordination, and the communication channel between them. It is an extension of the Scatter Gather pattern but involves a sequence of operations instead of executing a single task in parallel. Let’s examine a use case to understand how the pattern works. Use Case Many industries, such as e-commerce, finance, healthcare, telecommunications, and entertainment, widely use the orchestrator pattern with microservices. By now, we also have a good understanding of the pattern. In this section, I will talk about payment processing, which is relevant in many contexts, to detail the pattern in action. Consider a payment gateway system that mediates between a merchant and a customer bank. The payment gateway aims to facilitate secure transactions by managing and coordinating multiple participating services. When the orchestrator service receives a payment request, it triggers a sequence of service calls in the following order: Firstly, it calls the payment authorization service to verify the customer’s payment card, the amount going out, and bank details. The service also confirms the merchant’s bank and its status. Next, the orchestrator invokes the Risk Management Service to retrieve the transaction history of the customer and merchant to detect and prevent fraud. After this, the orchestrator checks for Payment Card Industry (PCI) Compliance by calling the PCI Compliance Service. This service enforces the mandated security standards and requirements for cardholder data. Credit card companies need all online transactions to comply with these security standards. Finally, the orchestrator calls another microservice, the Transaction Service. This service converts the payment to the merchant’s preferred currency if needed. The service then transfers funds to the merchant’s account to settle the payment transaction. Payment Gateway System Flow After completing all the essential steps, the Orchestrator Service responds with a transaction completion status. At this point, the calling service may send a confirmation email to the buyer. The complete flow is depicted in the above diagram. It is important to note that this orchestration service is not just a simple API gateway that calls the APIs of different services. Instead, it is the only service with the complete context and manages all the steps necessary to finish the transaction. If we want to add another step, for example, the introduction of new compliance by the government, all we need to do is create a new service that ensures compliance and add this to the orchestration service. It’s worth noting that the new addition may not affect the other services, and they may not even be aware of it. Implementation Details The previous section has demonstrated a practical use case for managing service using an orchestrator. However, below are a few tactics that can be used while implementing the pattern: Services vs ServerlessMostly following this pattern means having a business logic that spreads across many services. However, there are specific situations when not all the business steps require execution or only a few steps are necessary. Should these steps be deployed as functions instead of services in these scenarios? Events usually trigger functions, which shut down once they complete their job. Such an infrastructure can save us money compared to a service that remains active continuously and performs minimal tasks. Recovery from Transient FailuresThe orchestration pattern implementation can be challenging because it involves coordinating multiple services and workflows, which requires a different approach to designing and managing software systems than traditional monolithic architectures. The implementation must be able to handle potential transient failures, such as network failure, service failure, or database failure. Below are a few ways to cater to such issues: Retry MechanismImplementing a retry mechanism can improve resiliency when a service operation fails. The retry mechanism should configure the number of retries allowed, the delay between retries, and the conditions to attempt retries. Circuit Breaker PatternIn case a service fails, the orchestrator must detect the failure, isolate the failed service, and give it a chance to recover. It can help the service heal without disruption and avoid complete system failure. Graceful DegradationIf a service fails and becomes unavailable, the rest of the services should continue to operate. The orchestrator should look for fallback options to minimize the impact on end-users, such as previously cached results or an alternate service. Monitoring and AlertingThe entire business flow is distributed among various services when we operate with the Orchestration Pattern. Therefore, an effective monitoring and alerting solution is mandatory to trace and debug any failures. The solution must be capable of detecting any issues in real-time and taking appropriate actions to mitigate the impact. It includes implementing auto-recovery strategies, such as restarting failed services or switching to a backup service, and setting up alerts to notify the operations team when exceptions occur. The logs generated by the orchestrator are also valuable for the operations team to troubleshoot errors. We can operate smoothly and meet user needs by proactively identifying and resolving issues. Orchestration Service FailureFinally, we must prepare for scenarios where the orchestrator fails itself while processing requests. For instance, in our payment gateway example, imagine a scenario where the orchestrator calls the Transaction service to transfer the funds but crashes or loses connection before getting a successful response for the occurred transaction. It could lead to a frustrating user experience, with the risk of the customer being charged twice for the same product. To prevent such failure scenarios, we can adopt one of the following solutions: Service ReplicationReplicate the orchestration service across multiple nodes. The service can automatically fail over to the backup node when needed. With a load balancer that can detect and switch to the available node, the replication guarantees seamless service and prevents disruptions to the user. Data ReplicationNot only should we replicate the service, but we should also replicate the data to ensure data consistency. It enables the backup node to take over seamlessly without any data loss. Request QueuesImplementing queues like a buffer for requests when the orchestration service is down. The queue can hold incoming requests until the service is available again. Once the backup node is up and running, it can retrieve the data from the queue buffer and process them in the correct order. Why Use Orchestration Pattern The pattern comes with the following advantages: Orchestration makes it easier to understand, monitor and observe the application, resulting in a better understanding of the core part of the system with less effort. The pattern promotes loose coupling. Each downstream service exposes an API interface and is self-contained, without any need to know about the other services. The pattern simplifies the business workflows and improves the separation of concerns. Each service participates in a long-running transaction without any need to know about it. The orchestrator service can decide what to do in case of failure making the system fault-tolerant and reliable. Important Considerations The primary goal of this architectural pattern is to decompose the entire business workflow into multiple services, making it more flexible and scalable. And due to this, it’s crucial to analyse and comprehend the business processes in detail before implementation. A poorly defined and overly complicated business process will lead to a system that would be hard to maintain and scale. Secondly, it’s easy to fall into the trap of adding business logic into the orchestration service. Sometimes it’s inevitable because certain functionalities are too small to create their separate service. But the risk here is that if the orchestration service becomes too intelligent and performs too much business logic, it can evolve into a monolithic application that also happens to talk to microservices. So, it’s crucial to keep track of every addition to the orchestration service and ensure that its work remains within the boundaries of orchestration. Maintaining the scope of the orchestration service will prevent it from becoming a burden on the system, leading to decreased scalability and flexibility. Summary Numerous organizations are adopting microservice patterns to handle their complex distributed systems. The orchestration pattern plays a vital role in designing and managing these systems. By centralizing control and coordination, the orchestration pattern enhances agility, scalability, and resilience, making it an essential tool for organizations looking to modernize their infrastructure.
Docker has revolutionized the way we build and deploy applications. It provides a platform-independent environment that allows developers to package their applications and dependencies into a single container. This container can then be easily deployed across different environments, making it an ideal solution for building and deploying applications at scale. Building Docker images from scratch is a must skill that any DevOps engineer needs to acquire for working with Docker. It allows you to create custom images tailored to your application's specific needs, making your deployments more efficient and reliable. Here, in this blog, we'll explore Docker images, its benefits, the process of building Docker images from scratch, and the best practices for building a Docker image. What Is a Docker Image? A Docker image is a lightweight, standalone, executable package that includes everything needed to run the software, including code, libraries, system tools, and settings. Docker images are built using a Dockerfile, which is a text file that contains a set of instructions for building the image. These instructions specify the base image to use, the packages and dependencies to install, and the configuration settings for the application. Docker images are designed to be portable and can be run on any system that supports Docker. They are stored in a central registry, such as Docker Hub, where others can easily share and download. By using Docker images, developers can quickly and easily deploy their applications in a consistent and reproducible manner, regardless of the underlying infrastructure. This makes Docker images an essential tool for modern software development and deployment. Benefits of Building a Docker Image By building image Docker, you can improve the consistency, reliability, and security of your applications. In addition, Docker images make it easy to deploy and manage applications, which helps to reduce the time and effort required to maintain your infrastructure. Here are some major benefits of building a Docker image: Portability: Docker images are portable and can run on any platform that supports Docker. This makes moving applications between development, testing, and production environments easy. Consistency: Docker images provide a consistent environment for running applications. This ensures that the application behaves the same way across different environments. Reproducibility: Docker images are reproducible, which means you can recreate the same environment every time you run the image. Scalability: Docker images are designed to be scalable, which means that you can easily spin up multiple instances of an application to handle increased traffic. Security: Docker images provide a secure way to package and distribute applications. They allow you to isolate your application from the host system and other applications running on the same system. Efficiency: Docker images are lightweight and take up minimal disk space. This makes it easy to distribute and deploy applications quickly. Versioning: Docker images can be versioned, which allows you to track changes and roll back to previous versions if necessary. Structure of a Docker Image A Docker image is a read-only template that contains the instructions for creating a Docker container. Before you learn how to build a Docker image, let's read about its structure first. The structure of a Docker image includes the following components: Base Image A Docker image is built on top of a base image, which is the starting point for the image. The base image can be an official image from the Docker Hub registry or a custom image created by another user. Filesystem The filesystem of a Docker image is a series of layers that represent the changes made to the base image. Each layer contains a set of files and directories that represent the differences from the previous layer. Metadata Docker images also include metadata that provides information about the image, such as its name, version, author, and description. This metadata is stored in a file called the manifest. Dockerfile The Dockerfile is a text file that contains the instructions for building the Docker image. It specifies the base image, the commands to run in the image, and any additional configuration needed to create the image. Before learning how to build the docker image using the Docker build command from Dockerfile, knowing how dockerfile works will be helpful. Configuration Files Docker images may also include configuration files that are used to customize the image at runtime. These files can be mounted as volumes in the container to provide configuration data or environment variables. Runtime Environment Finally, Docker images may include a runtime environment that specifies the software and libraries needed to run the application in the container. This can include language runtimes such as Python or Node.js or application servers such as Apache or Nginx. The structure of a Docker image is designed to be modular and flexible, allowing technology teams to create images tailored to their specific needs while maintaining consistency and compatibility across different environments. How to Build a Docker Image? To build a Docker image, you need to follow these steps: Create a Dockerfile A Dockerfile is a script that contains instructions on how to build your Docker image. The Dockerfile specifies the base image, dependencies, and application code that are required to build the image. After creating a Dockerfile and understanding how Dockerfile works, move to the next step. Define the Dockerfile Instructions In the Dockerfile, you need to define the instructions for building the Docker image. These instructions include defining the base image, installing dependencies, copying files, and configuring the application. Build the Docker Image To build a Docker image, you need to use the Docker build command. This command takes the Dockerfile as input and builds the Docker image. After using the Docker build command with Dockerfile, you can also specify the name and tag for the image using the -t option. Test the Docker Image Once the Docker image is built, you can test it locally using the docker run command. This command runs a container from the Docker image and allows you to test the application. Push the Docker Image to a Registry Once you have tested the Docker image, you can push it to a Docker registry such as Docker Hub or a private registry. This makes it easy to share the Docker image with others and deploy it to other environments. Let's see this Docker build command example. Once you've created your Dockerfile, you can use the "docker build" command to build the image. Here's the basic syntax for the docker build command with dockerfile: (php) docker build -t <image-name> <path-to-Dockerfile> Here, in this Docker build command example, if your Dockerfile is located in the current directory and you want to name your image "my-app," you can use the following Docker build command from dockerfile. (perl) docker build -t my-app This Docker builds command builds the Docker image using the current directory as the build context and sets the name and tag of the image to "my-app." Best Practices for Building a Docker Image Here are some best practices to follow when building a Docker image: First, use a small base image: Use a small base image such as Alpine Linux or BusyBox while building an image Docker. This helps to reduce the size of your final Docker image and improves security by minimizing the attack surface. Use a .dockerignore file: Use a .dockerignore file to exclude files and directories that are not needed in the Docker image. This helps to reduce the size of the context sent to the Docker daemon during the build process. Use multistage builds: Use multistage builds to optimize your Docker image size. Multistage builds allow you to build multiple images in a single Dockerfile, which can help reduce the number of layers in your final image. Minimize the number of layers: Minimize the number of layers in your Docker image to reduce the build time and image size. Each layer in a Docker image adds overhead, so it's important to combine multiple commands into a single layer. Use specific tags: Use specific tags for your Docker image instead of the latest tag. This helps to ensure that you have a consistent and reproducible environment. Avoid installing unnecessary packages: Avoid installing unnecessary packages in your Docker image to reduce the image size and improve security. Use COPY instead of ADD: Use the COPY command instead of ADD to copy files into your Docker image. The COPY command is more predictable and has fewer side effects than the ADD command. Avoid using root user: Avoid using the root user in your Docker image to improve security. Instead, create a non-root user and use that user in your Docker image. Docker Images: The Key to Seamless Container Management By following these steps and practices outlined in this blog, you can create custom Docker images tailored to your application's specific needs. This will not only make your deployments more efficient and reliable, but it will also help you to save time and resources. With these skills, you can take your Docker knowledge to the next level and build more efficient and scalable applications. Docker is a powerful tool for building and deploying applications, but it can also be complex and challenging to manage. Whether you're facing issues with image compatibility, security vulnerabilities, or performance problems, it's important to have a plan in place for resolving these issues quickly and effectively.
IBM App Connect Enterprise (ACE) has provided support for the concept of “shared classes” for many releases, enabling various use cases including providing supporting Java classes for JMS providers and also for caching data in Java static variables to make it available across whole servers (plus other scenarios). Some of these scenarios are less critical in a containerized server, and others might be handled by using shared libraries instead, but for the remaining scenarios there is still a need for the shared classes capability in containers. What Is the Equivalent of /var/mqsi/shared-classes in Containers? Adding JARs to shared classes is relatively simple when running ACE in a virtual machine: copying the JAR files into a specific directory such as /var/mqsi/shared-classes allows all flows in all servers to make use of the Java code. There are other locations that apply only to certain integration nodes or servers, but the basic principle is the same, and only needs to be performed once for a given version of supporting JAR as the copy action is persistent across redeploys and reboots. The container world is different, in that it starts with a fixed image every time, so copying files into a specific location must either be done when building the container image, or else done every time the container starts (because changes to running containers are generally non-persistent). Further complicating matters is the way flow redeploy works with containers: the new flow is run in a new container, and the old container with the old flow is deleted, so any changes to the old container are lost. Two main categories of solution exist in the container world: Copy the shared classes JARs into the container image during the container build, and Deploy the shared classes JARs in a BAR file or configuration in IBM Cloud Pak for Integration (CP4i) and configure the server to look for them. There is also a modified form of the second category that uses persistent volumes to hold the supporting JARs, but from an ACE point of view it is very similar to the CP4i configuration method. The following discussion uses an example application from the GitHub repo at https://github.com/trevor-dolby-at-ibm-com/ace-shared-classes to illustrate the question and some of the answers. Original Behavior With ACE in a Virtual Machine Copying the supporting JAR file into /var/mqsi/shared-classes was sufficient when running in a virtual machine, as the application would be able to use the classes without further configuration: The application would start and run successfully, and other applications would also be able to use the same shared classes across all servers. Container Solution 1: Copy the Shared Classes JARs in While Building the Container Image This solution has several variants, but they all result in the container starting up with the support JAR already in place. ACE servers will automatically look in the “shared-classes” directory within the work directory, and so it is possible to simply copy the JARs into the correct location; the following example from the Dockerfile in the repo mentioned above shows this: # Copy the pre-built shared JAR file into placeRUN mkdir /home/aceuser/ace-server/shared-classesCOPY SharedJava.jar /home/aceuser/ace-server/shared-classes/ and the server in the container will load the JAR into the shared classloader: Note that this solution also works for servers running locally during development in a virtual machine. It also means that any change to the supporting JAR requires a rebuild of the container image, but this may not be a problem if a CI/CD pipeline is used to build application-specific container images. The server may also be configured to look elsewhere for shared classes by setting the additionalSharedClassesDirectories parameter in server.conf.yaml. This parameter can be set to a list of directories to use, and then the supporting JAR files can be placed anywhere in the container. The following example shows the JAR file in the “/git/ace-shared-classes” directory: This solution would be most useful for cases where the needed JAR files are already present in the image, possibly as part of another application installation. Container Solution 2: Deploy the Shared Classes JARs in a BAR File or Configuration in CP4i For many CP4i use cases, the certified container image will be used unmodified, so the previous solution will not work as it requires modification of the container image. In these cases, the supporting JAR files can be deployed either as a BAR file or else as a “generic files” configuration. In both cases, the server must be configured to look for shared classes in the desired location. If the JAR files are small enough or if the shared artifacts are just properties files, then using a “generic files” configuration is a possible solution, as that type of configuration is a ZIP file that can contain arbitrary contents. The repo linked above shows an example of this, where the supporting JAR file is placed in a ZIP file in a subdirectory called “extra-classes” and additionalSharedClassesDirectories is set to “/home/aceuser/generic/extra-classes”: (If a persistent volume is used instead, then the “generic files” configuration is not needed and the additionalSharedClassesDirectories setting should point to the PV location; note that this requires the PV to be populated separately and managed appropriately (including allowing multiple simultaneous versions of the JARs in many cases)). The JAR file can also be placed in a shared library and deployed in a BAR file, which allows the supporting JARs to be any size and also allows a specific version of the supporting JARs to be used with a given application. In this case, the supporting JARs must be copied into a shared library and then additionalSharedClassesDirectories must be set to point the server at the shared library to tell it to use it as shared classes. This example uses a shared library called SharedJavaLibrary and so additionalSharedClassesDirectories is set to “{SharedJavaLibrary}”: Shared libraries used this way cannot also be used by applications in the server. Summary Existing solutions that require the use of shared classes can be migrated to containers without needing to be rewritten, with two categories of solution that allow this. The first category would be preferred if building container images is possible, while the second would be preferred if a certified container image is used as-is. For further reading on container image deployment strategies, see Comparing Styles of Container-Based Deployment for IBM App Connect Enterprise; ACE servers can be configured to work with shared classes regardless of which strategy is chosen.
This is an article from DZone's 2023 Software Integration Trend Report.For more: Read the Report Multi-cloud integration strategies may sound like buzzwords and marketing slang, but in this article, I will demystify them. I will also dive deeply into on-premises and legacy systems and how we can integrate with them. Before we jump into the topic, I would like to define what integration means in a cloud context. Cloud integration is a process that allows organizations' applications, infrastructure, data, and components to properly work together within one or several cloud providers. It also includes connecting on-premises data centers to the cloud if migration can be done across the organization. Cloud Integrations An important part of cloud integration is understanding the strategies. Many medium- and enterprise-level companies choose multi-cloud and hybrid-cloud approaches. Why is successful integration important for companies? Most companies building solutions have to exchange data with on-premises or out-of-support solutions. Properly designed integration solutions will save a lot of time and money. We can see it in the example of a bank multi-cloud application at the end of the article. Hybrid vs. Multi-Cloud Below is a comparison table describing both strategies' pros and cons. Before we jump in, keep the differences between public and private clouds in mind. Remember that public clouds provide computing power, SaaS, and PaaS services for organizations that don't have (or where it is difficult to have) their data centers. A private cloud (on-premises) is an infrastructure the company maintains internally. HYBRID VS. MULTI-CLOUD PROS AND CONS Hybrid Cloud Multi-Cloud Description Hybrid clouds combine private clouds/on-prem data centers with a public cloud, an approach that companies usually take. For example, banks have secure on-prem environments that they won't move to the cloud. Meanwhile, they have other, less secure solutions that can be easily moved to a public cloud and have fewer connections to on-premises. Multi-cloud combines several public clouds without using a private cloud. Usually, companies choose a multi-cloud strategy to avoid vendor lock-in. Pros Flexibility to connect infrastructure that can't be moved to the public cloud. Increased security thanks to the on-prem component. Flexibility between using a legacy system and modern public cloud services. Flexible and scalable environments. You can choose the services in each cloud that work best for your company. Freedom to implement the solution across several clouds. Cons It can be difficult to maintain legacy, on-prem environments. Additional cost for companies because they need to maintain their hardware. The cost of maintaining different services on several cloud providers can be prohibitive. Complexity in managing and separating different services. Securing network communication between clouds can be difficult. Cloud Integration Patterns and Best Practices Applying a good integration strategy also requires knowing some integration best practices and patterns. Cloud Integration Patterns Understanding the main set of integration patterns is key to using existing integration solutions or designing a new one from scratch. Also, having the knowledge of these patterns provides a massive benefit during the integration of cloud applications and enterprise, on-premises infrastructure. Asynchronous Messaging Asynchronous messaging allows components and services to process data without waiting for each other. It also allows components to be decoupled from each other. Figure 1 Shared Databases This pattern uses a shared database to communicate and exchange data between enterprise applications and services. As part of a shared database and communication bus, we can also use an enterprise service bus that can save and exchange data between several components. Figure 2 Remote Procedure Call Remote procedure call (RPC) is an abstraction layer or protocol that allows one network component to communicate with another without knowing the whole network's complete functionality. Figure 3 File Transfer The file transfer pattern provides an interface to share files between cloud or application components. For example, file transfer is useful if an application produces CSV or XML reports — the integration service should adapt this file for other applications. Figure 4 Recommended Practices for Cloud Integration Here are three of the most important best practices for cloud integration: Use native SaaS tools that cloud providers offer. This approach always provides the best integration options between applications and components. There are even "no-code" tools for non-technical people. We will get into native Azure, AWS, and Google Cloud Services in the next section. Use an Integration Platform as a Service (iPaaS). Some services and components provide integration capabilities and are hosted as cloud services. For example, triggermesh and cenit.io are open-source integration platforms that allow building event-driven applications in Kubernetes, orchestrating data flow, and providing API management capabilities in cloud providers and on-premises Use a Function Platform as Service (FPaaS). These platforms provide huge customization levels of integration options, from which some organizations can benefit. This approach is intended for cloud solution architects and requires a knowledge of cloud architecture patterns and function-oriented software development skills. FPaaS tools include AWS Lambda, Azure Functions, Google Cloud Functions, and Apache OpenWhisk. Common Integration Services Knowing the general cloud integration best practices and patterns is crucial. However, knowing what exactly each cloud provider offers is also important. In this section, we will briefly touch upon common cloud integration services from providers such as AWS, Azure, and Google Cloud. Keep in mind: This section contains — but is not limited to — some of the most ubiquitous open-source integration services available. To learn more about the list below, common benefits, and drawbacks associated with each, check out this platform breakdown for more information. AWS AWS has several integration services that provide powerful features alongside simplicity. This list includes SNS (Simple Notification Service), SQS (Simple Queue Service), SWF (Simple Workflow Service), and AWS step functions. To learn more, visit the AWS Application Integration services page. Google Cloud Google Cloud has a vast integration ecosystem, also commonly referred to as Integration Platform as a Service (iPaaS). This provides a set of tools and services to manage and connect applications. The Google Cloud iPaaS contains the following core services: Integration designer, triggers, and tasks. Learn more about each Google Cloud integration service here. Azure Azure offers an Azure integration service set (also commonly referred to as Azure Integration Platform as a Service). This contains a variety of services set up to provide strong integration between applications. Some of the most powerful integration services Azure offers include API Management, Logic Apps, Service Bus, Event Grid, and Azure Arc. If you are interested in reading more on the various Azure integration services, check out this page to learn more. A Bank Multi-Cloud Application As mentioned, banking applications require a massive security layer. Also, many banks contain their own highly secure data centers, and migrating all secured data to the cloud may not even be an option. Figure 5: A banking multi-cloud integration application example In this example, we selected Azure as the cloud for the main application. The application is based on a microservices architecture and is deployed to several Kubernetes clusters. Azure stores secrets, a configuration in a Cosmos DB, and some files in Blob Storage. Azure also provides an observability platform with a service mesh. All secured data is stored on the on-premises data center, and the AWS Cloud part contains a workflow for the call center. Conclusion In this article, we've reviewed top cloud integration patterns and services that start the integration process from scratch or that consider an existing environment. Designing integrations of software solutions in the cloud requires knowledge of best practices and patterns. Furthermore, it requires a deep understanding of the toolsets, services, and components each cloud and framework offer. For example, alongside Azure Arc, AWS offers services like Systems Manager. Before I start an integration project, I'm using the following algorithm: Keep in mind the KISS principle Have a look at existing integration patterns Check on what integration components and services other clouds provide Therefore, multi-cloud integration means to make solutions and components of one cloud provider work with others using existing integration cloud components and patterns. This is an article from DZone's 2023 Software Integration Trend Report.For more: Read the Report
API Gateway is the AWS service that allows interfacing an application's back-end with its front-end. The figure below shows an example of such an application, consisting of a web/mobile-based front-end and a back-end residing in a REST API, implemented as a set of serverless Lambda functions, as well as a number of legacy services. The figure above illustrates the so-called design pattern Legacy API Proxy, as described by Peter Sbarski, Yan Cui, and Ajay Nair in their excellent book Serverless Architectures on AWS (Manning, 2022). This pattern refers to a use case where Amazon API Gateway and Lambda are employed together, in order to create a new API layer over legacy APIs and services, such that to adapt and reuse them. In this design, the API Gateway exposes a REST interface invoking Lambda functions which, in turn, modify the requests and the responses or transform data to legacy-specific formats. This way, legacy services may be consumed by modern clients that don't support older protocols. This can be done, of course, using the AWS Console, by selecting the API Gateway service and, on the behalf of the proposed GUI (Graphical User Interface), by browsing among the dozens of possible options such that, about one hour later, to come to a functional skeleton. And when our API specifications are changing, i.e., several times per month, we need to start again, from the beginning. We shall not proceed accordingly. We will rather adopt an IaC (Infrastructure as Code) approach consisting in defining our API in a repeatable and deterministic manner. This could be done in several ways, via a script-based automation process using, for example, AWS CLI (Command Line Interpreter), CloudFormation, or Terraform. But there is another interesting alternative that most developers prefer: OpenAPI. And it's this alternative that we chose to use here, as shown further. Designing the REST Interface With OpenAPI In 2011, SmartBear Software, a small company specializing in testing and monitoring tools, developed Swagger, a set of utilities dedicated to the creation and documentation of RESTful services. Several years later in November 2015 under the auspices of the Linux Foundation, this same company was announcing the creation of a new organization, named OpenAPI Initiative. Other majors, like Google, IBM, etc., got committed as founding members. In January 2016, Swagger changed its name and became OpenAPI. OpenAPI is a formalism based on the YAML notation, which could also be expressed in JSON. It aims at defining REST APIs in a language-agnostic manner. There are currently a lot of tools around OpenAPI and our goal here isn't to extensively look at all the possibilities which are open to us, as far as these tools and their utilization is concerned. One of the most common use cases is probably to login to the SwaggerHub online service, create a new API project, export the resulted YAML file, and use it in conjunction with the SAM (Serverless Application Model) tool in order to expose the given API via Amazon API Gateway. And since we need to illustrate the modus operandi described above, let's consider the use case of a money transfer service, named send-money. This service, as its name clearly shows it, is responsible to perform bank account transfers. It exposes a REST API whose specifications are presented in the table below: Resource HTTP Request Action Java Class /orders GET Get the full list of the currently registered orders GetMoneyTransferOrders /orders POST Create a new money transfer order CreateMoneyTransferOrder /orders PUT Update an existing money transfer order UpdateMoneyTransferOrder /orders/{ref} GET Get the money transfer order identified by itsreference passed as an argument GetMoneyTransferOrder /orders/{ref} DELETE Remove the money transfer order identified by its reference passed as an argument RemoveMoneyTransferOrder This simple use case, consisting of a CRUD (Create, Read, Update, Delete) and exposed as a REST API, is the one that we chose to implement here, such that to illustrate the scenario described above and here are the required steps: Go to the Send Money API on SwaggerHub. Here you'll find an already prepared project showing the OpenAPI specification of the REST API defined in the table above. This is a public project and, in order to get access, one doesn't need to register and log in. You'll be presented with a screen similar to the one in the figure below: This screen shows in its left pane the OpenAPI description of our API. Once again, the full explanation of the OpenAPI notation is out of our scope here, as this topic might make the subject of an entire book, like the excellent one of Joshua S. Ponelat and Lukas L. Rosenstock, titled Designing APIs with Swagger and OpenAPI (Manning 2022). The right pane of the screen presents schematically the HTTP requests of our API and allows, among others, to test it. You may spend some time browsing in this part of the screen, by clicking the button labeled with an HTTP request and then selecting Try it out. Notice that these tests are simulated, of course, as there is no concrete implementation behind them. However, they allow you to make sure that the API is correctly defined, from a syntactical and a semantic point of view. Now that you finished playing with the test interface, you can use the Export -> Download API -> YAML Resolved function located in the screen's rightmost upper corner to download our API OpenAPI definition in YAML format. In fact, you don't really have to do that because you can find this same file in the Maven project used to exemplify this blog ticket. Let's have now a quick look at this YAML file. The first thing we notice is the declaration openapi: which defines the version of the notation that we're using: in this case, 3.0.0. The section labeled info identifies general information like the API name, its author, and the associated contact details, etc. The next element, labeled servers: defines the auto-mocking function. It allows us to run the simulated tests outside the SwagerHub site. Just copy the URL declared here and use it with your preferred browser. Last but not least, we have the element labeled paths: where our API endpoints are defined. There are two such endpoints: /orders and /orders/{ref}. For each one, we define the associated HTTP requests, their parameters as well as the responses, including the HTTP headers. OpenAPI is an agnostic notation and, consequently, it isn't bound to any specific technology, framework, or programming language. However, AWS-specific extensions are available. One of these extensions is x-amazon-apigateway-integration which allows a REST endpoint to connect to the API Gateway. As you can see looking at the OpenAPI YAML definition, each endpoint includes an element labeled x-amazon-apigateway-integration which declares, among others, the URL of the Lambda function where the call will be forwarded. The Project Ok, we have an OpenAPI specification of our API. In order to generate an API Gateway stack out of it and deploy it on AWS, we will use SAM, as explained above. For more details on SAM and how to use it, please don't hesitate to have a look here. Our Java project containing all the required elements may be found here. Once you cloned it from GitHub, open the file template.yaml. We reproduce it below: YAML AWSTemplateFormatVersion: '2010-09-09' Transform: 'AWS::Serverless-2016-10-31' Description: Send Money SAM Template Globals: Function: Runtime: java11 MemorySize: 512 Timeout: 10 Tracing: Active Parameters: BucketName: Type: String Description: The name of the S3 bucket in which the OpenAPI specification is stored Resources: SendMoneyRestAPI: Type: AWS::Serverless::Api Properties: Name: send-money-api StageName: dev DefinitionBody: Fn::Transform: Name: AWS::Include Parameters: Location: Fn::Join: - '' - - 's3://' - Ref: BucketName - '/openapi.yaml' MoneyTransferOrderFunction: Type: AWS::Serverless::Function Properties: FunctionName: MoneyTransferOrderFunction CodeUri: send-money-lambda/target/send-money.jar Handler: fr.simplex_software.aws.lambda.send_money.functions.MoneyTransferOrder::handleRequest Events: GetAll: Type: Api Properties: RestApiId: Ref: SendMoneyRestAPI Path: /orders Method: GET Get: Type: Api Properties: RestApiId: Ref: SendMoneyRestAPI Path: /orders Method: GET Create: Type: Api Properties: RestApiId: Ref: SendMoneyRestAPI Path: /orders Method: POST Update: Type: Api Properties: RestApiId: Ref: SendMoneyRestAPI Path: /orders Method: PUT Delete: Type: Api Properties: RestApiId: Ref: SendMoneyRestAPI Path: /orders Method: DELETE ConfigLambdaPermissionForMoneyTransferOrderFunction: Type: "AWS::Lambda::Permission" DependsOn: - SendMoneyRestAPI Properties: Action: lambda:InvokeFunction FunctionName: !Ref MoneyTransferOrderFunction Principal: apigateway.amazonaws.com Our template.yaml file will create an AWS CloudFormation stack containing an API Gateway. This API Gateway will be generated from the OpenAPI specification that we just discussed. The DefinitionBody element in the SendMoneyAPI resource says that the API's endpoints are described by the file named openapi.yaml located in an S3 bucket, which name is passed as an input parameter. The idea here is that we need to create a new S3 bucket, copy into it our OpenAPI specifications in the form of an yaml file, and use this bucket as an input source for the AWS CloudFormation stack containing the API Gateway. A Lambda function, named MoneyTransferOrderFunction, is defined in this same SAM template as well. The CodeUri parameter configures the location of the Java archive which contains the associated code, while the Handler one declares the name of the Java method implementing the AWS Lambda Request Handler. Last but not least, the Event paragraph sets the HTTP requests that our Lambda function is serving. As you can see, there are 5 endpoints, labeled as follows (each defined in the OpenAPI specification): GetAll mapped to the GET /orders operation Get mapped to the GET /orders/{ref} operation Create mapped to the POST /orders operation Update mapped to the PUT /orders operation Delete mapped to the DELETE /orders/{ref} operation To build and deploy the project, proceed as shown in the listing below: Shell $ mkdir test-aws $ cd test-aws $ git clone https://github.com/nicolasduminil/aws-showcase ... $mvn package ... $ ./deploy.sh ... make_bucket: bucketname-3454 upload: ./open-api.yaml to s3://bucketname-3454/openapi.yaml Uploading to 73e5d262c96743505970ad88159b929b 2938384 / 2938384 (100.00%) Deploying with following values =============================== Stack name : money-transfer-stack Region : eu-west-3 Confirm changeset : False Disable rollback : False Deployment s3 bucket : bucketname-3454 Capabilities : ["CAPABILITY_IAM"] Parameter overrides : {"BucketName": "bucketname-3454"} Signing Profiles : {} Initiating deployment ===================== Uploading to b0cf548da696c5a94419a83c5088de48.template 2350 / 2350 (100.00%) Waiting for changeset to be created.. CloudFormation stack changeset ... Successfully created/updated stack - money-transfer-stack in eu-west-3 Your API with ID mtr6ryktjk is deployed and ready to be tested at https://mtr6ryktjk.execute-api.eu-west-3.amazonaws.com/dev In this listing, we start by cloning the Git repository containing the project. Then, we execute a Maven build, which will package the Java archive named send-money-lambda.jar, after having performed some unit tests. The script deploy.sh, like its name implies, is effectively responsible to fulfill the deployment operation. Its code is reproduced below: Shell #!/bin/bash RANDOM=$$ BUCKET_NAME=bucketname-$RANDOM STAGE_NAME=dev AWS_REGION=$(aws configure list | grep region | awk '{print $2}') aws s3 mb s3://$BUCKET_NAME echo $BUCKET_NAME > bucket-name.txt aws s3 cp open-api.yaml s3://$BUCKET_NAME/openapi.yaml sam deploy --s3-bucket $BUCKET_NAME --stack-name money-transfer-stack --capabilities CAPABILITY_IAM --parameter-overrides BucketName=$BUCKET_NAME aws cloudformation wait stack-create-complete --stack-name money-transfer-stack API_ID=$(aws apigateway get-rest-apis --query "items[?name=='send-money-api'].id" --output text) aws apigateway create-deployment --rest-api-id $API_ID --stage-name $STAGE_NAME >/dev/null 2>&1 echo "Your API with ID $API_ID is deployed and ready to be tested at https://$API_ID.execute-api.$AWS_REGION.amazonaws.com/$STAGE_NAME" We're using here the $$ Linux command which generates a random number. By appending this randomly generated number to the S3 bucket name that will be used in order to store the OpenAPI specification file, we satisfy its region-wide uniqueness condition. This bucket name is further stored in a local file, such that it can be later retrieved and cleaned up. Notice also the aws configure command used in order to get the current AWS region. The command aws s3 mb is creating the S3 bucket. Here mb states for make bucket. Once the bucket is created, we'll be using it in order to store inside the open-api.yaml file, containing the API specifications. This is done on the behalf of the command aws s3 cp. Now, we are ready to start the deployment process. This is done through the sam deploy command. Since this operation might take a while, we need to wait until the AWS CloudFormation stack is completely created before continuing. This is done by the statement aws cloudformation wait, as shown in the listing above. The last operation is the deployment of the previously created API Gateway, done by running the aws apigateway create-deployment command. Here we need to pass, as an input parameter, the API Gateway identifier, retrieved on the behalf of the command aws apigateway get-rest-api, which returns information about all the current API Gateways. Then, using the --query option, we filter among the JSON payload, in order to find ours, named send-money-api. At the end of its execution, the script displays the URL of the newly created API Gateways. This is the URL that can be used for testing purposes. For example, you may use Postman, if you have it installed, or simply the AWS Console, which benefits a nice and intuitive test interface. If you decide to use the AWS Console, you need to select the API Gateway service and you'll be presented with the list of all current existent ones. Clicking on the one named send-money-api will display the list of the endpoint to be tested. For that, you need to start, of course, by creating a new money transfer order. You can do this by pasting the JSON payload below in the request body: JSON { "amount": 200, "reference": "reference", "sourceAccount": { "accountID": "accountId", "accountNumber": "accountNumber", "accountType": "CHECKING", "bank": { "bankAddresses": [ { "cityName": "poBox", "countryName": "countryName", "poBox": "cityName", "streetName": "streetName", "streetNumber": "10", "zipCode": "zipCode" } ], "bankName": "bankName" }, "sortCode": "sortCode", "transCode": "transCode" }, "targetAccount": { "accountID": "accountId", "accountNumber": "accountNumber", "accountType": "CHECKING", "bank": { "bankAddresses": [ { "cityName": "poBox", "countryName": "countryName", "poBox": "cityName", "streetName": "streetName", "streetNumber": "10", "zipCode": "zipCode" } ], "bankName": "bankName" }, "sortCode": "sortCode", "transCode": "transCode" } } If the status code appearing in the AWS Console is 200, then the operation has succeeded and now you can test the two GET operations, the one retrieving all the existent money transfer orders and the one getting the money transfer order identified by its reference. For this last one, you need to initialize the input parameter of the HTTP GET request with the value of the money transfer order reference which, in our test, is simply "reference". In order to test the PUT operation, just paste in its body the same JSON payload used previously to test the POST, and slightly modify it. For example, modify the amount to 500 instead of 200. Test again now the two GET operations and they should retrieve a newly updated money transfer order, this time having an amount of 500. When you finished playing with the AWS Console interface, test the DELETE operation and paste the same reference in its input parameter. After that, the two GET operations should return an empty result set. If you're tired to use the AWS Console, you can switch to the provided integration test. First, you need to open the FunctionsIT class in the send-money-lambda Maven module. Here, you need to make sure that the static constant named AWS_GATEWAY_URL matches the URL displayed by the deploy.sh script. Then compile and run the integration tests as follows: Shell mvn test-compile failsafe:integration-test You should see statistics showing that all the integration tests have succeeded. Have fun!
The advent of the Internet has brought revolutionary changes in the IT world. One of the notable changes is that virtualization has advanced with the Internet to become an integral part of the IT infrastructure of modern organizations. As a result, companies are now relying on the virtual online entity housing data and services, commonly referred to as the cloud. The switch to the cloud was brought on by the exponential data growth in the last couple of decades. In fact, studies predict that by 2025, the cloud will be storing up to 100 zettabytes of data. What Is the Cloud? The cloud refers to a global network of remote servers, each with a unique function that are connected and work together as a unitary ecosystem. In simple terms, the cloud describes what we commonly know as the “internet.” This remote network of servers is designed to either store and manage data, run applications, or deliver content or a service such as streaming videos or accessing social media networks for anyone with an internet connection. What Is Cloud Computing? It is the provision of computing resources such as servers, storage, databases, networking, software, analytics, and intelligence over the cloud (internet). Cloud computing eliminates the need for enterprises to acquire, configure, or manage resources themselves, and instead, only pay for what they use. Virtual computers gained popularity in the 1990s when the IT industry started to rent virtual private networks. Their use sped up the development of the cloud computing infrastructure that organizations use today. Cloud computing offers a variety of benefits for businesses with some of the key ones being: Flexible resources Cost savings Scalability with growing business needs Data recovery Security With that being said, there are three main types of cloud computing deployments: Public Cloud - An open infrastructure for general public usage. Private Cloud - Computing infrastructure that’s exclusively used by a single organization. Hybrid Cloud - A combination of private and public cloud infrastructures. Community Cloud - A collaborative cloud infrastructure shared by a community of organizations with similar requirements and regulations. Single and multi-cloud concepts come from employing these deployment types from either one or numerous vendors. What Is a Single Cloud? Single cloud is a cloud computing model where organizations rely on a single third-party vendor for their cloud computing services. The provider maintains on-premise servers to provide either of the following cloud services in the single-cloud environment: Software-as-a-Service (SaaS) - a software on-demand service allowing users to utilize cloud-based applications such as email. Infrastructure-as-a-Service (IaaS) - provides computing resources hosted on the cloud. Amazon Web Services (AWS) is a famous IaaS example. Platform-as-a-Service (PaaS) - offers a development and deployment environment hosted on a provider's cloud infrastructure. A good example in this category is Google App Engine. Single Cloud Use Cases The single cloud strategy is suitable for companies with the following use cases: Strict organizational regulations are in place for data and workload governance. Insufficiency of skilled cloud engineers for efficient cloud workload management. Less cloud workload that a single provider can manage. Single Cloud Strategy Advantages It is easier to manage as it does not require workload migration between multiple cloud providers. Privacy and control are maintained. Needs limited resources in terms of cloud engineering staffing as well as managing vendor relationships. Faster workload handling with a single provider. Reduced risk of data inconsistencies. Easier to hold a single vendor accountable in case of any cloud issues. Single Cloud Strategy Disadvantages Hard to avoid vendor lock-in with single platform dependencies. It costs more to have all workloads managed by a single vendor. Choosing the right vendor is difficult as a single provider has limited cloud resources and flexibility in design. Risk of cloud resource unavailability due to any cloud issues that result in a single point of failure. What Is Multi-Cloud? Multi-cloud describes a cloud computing model where organizations use multiple cloud providers for their infrastructure requirements. The name multi-cloud refers to the use of multiple cloud providers, accounts, availability zones, premises, or a combination of them. Multi-Cloud Use Cases The multi-cloud strategy is suitable for companies with the following use cases: You are unable to fulfill business requirements with a single cloud. Multi-cloud meets the proximity requirements of your globally distributed users and service requirements in different regions. When the workload is big, varying, and needs to be distributed, which calls for specific cloud services. The regulations you are subject to require some data in private clouds for security reasons. Multi-Cloud Strategy Advantages Organizations consider a multi-cloud environment for the following benefits: It is a creative approach to simultaneously executing disparate workloads that offers customizable and flexible cloud services. Organizations spend less time by moving workloads between multiple clouds offering the required services at the best prices. You can switch vendors to ensure data availability by reducing vulnerabilities to cloud issues. Having multiple vendors reduces vendor dependencies and saves you from being locked into a single vendor. Multiple cloud providers in different deployment regions enable you to meet data sovereignty requirements for global cloud services. This minimizes concerns about non-compliance with government regulations. Multi-Cloud Strategy Disadvantages The multi-cloud model comes with the following disadvantages: Multi-cloud management can get complicated due to issues such as multi-vendor management, cloud computing inconsistencies, and inefficiencies, as well as task redundancies. Data migration between multiple cloud vendors can have cost overheads and slow down performance. Workload implementation can be inconsistent due to distribution among multiple clouds. Companies require excessive cloud engineering expertise to manage multi-cloud computing. Single Cloud vs. Multi-Cloud: The Key Differences This table gives you a side-by-side comparison of the single cloud vs multi-cloud strategies: Differences Single Cloud Multi-Cloud Vendors Single vendor dependency Multiple vendors offering more control Cost Payment to one provider Payment to multiple providers Purpose Provides single service Handles multiple services with multiple solutions Required Skillset Fewer cloud engineersrequired to manage the cloud Require extensive cloud engineering teamswith strong multi-cloud expertise Security Easier to ensure data compliance Less secure with distributed sensitive data Disaster Recovery Single point of failure making it vulnerable to disasters Easier disaster recovery Management Easier management Complex management The Cloud Portability Myth Under the Multi-Cloud Model and Potential Workarounds Migrating cloud services in a multi-cloud environment is always vulnerable to disruption. Cloud portability potentially reduces this vulnerability by facilitating the transfer of services between cloud environments with minimal disruption. While cloud portability may seem practical, some underlying complexities render this concept mythical. Essentially, cloud environments are migrated in compiled containers that make an entire cloud environment portable. However, while the containers may be portable, other public clouds cannot execute them without the underlying cloud-native services. Consequently, migrating this way defeats the purpose of employing a multi-cloud strategy. Achieving cloud portability may be complex, but companies still opt for the multi-cloud strategy to keep up with their competitors. The key is to find out how to work around this myth to run your multi-cloud models successfully. A trial-and-error approach would be to make multiple copies of compiled containers for each cloud environment. The container copy that offers the correct solution passes for deployment in other cloud platforms. Alternatively, you can use a Platform-as-a-Service option to provide portable services that are not dependent on specific cloud platforms. This aspect makes migrating such an application platform achievable for organizations. Single Cloud vs. Multi-Cloud Strategy: Which Is Better? When it comes to single cloud vs. multi-cloud strategies, businesses are increasingly adopting the multi-cloud model. This strategy is favored as it allows you to work globally with data and applications spread across various cloud servers and data centers. However, such a model only suits large organizations because setting up and maintaining a multi-cloud environment is a costly and complex task. Additionally, they require excessive resources and robust strategies to optimize cloud migration. It is important to note that despite the use of optimized strategies, cloud portability still remains a myth for multi-cloud organizations. Primarily, at some point, your cloud portability workarounds are bound to become too complex to manage. These complexities include: Lack of knowledgeable staff Absence of holistic disaster management Security gaps Are all these complexities worth investing in a multi-cloud strategy? The answer depends on your company’s use cases. However, another key consideration, in this case, is focusing on choosing the "right vendor" on top of debating the single cloud vs. multi-cloud strategies, as it is vital to finding the best solution for your business. Conclusion Depending on your use case, being locked to a single vendor does more good to an organization than delving into multi-vendor complexities. The opposite is also true. To sum it up, instead of working around a myth, cloud optionality gives you a better chance to adopt a successful cloud strategy. While it may prolong the vendor selection process, if either a single cloud or a multi-cloud strategy is right for your business, you can save your company from costly cloud expenses.
Boris Zaikin
Senior Software Cloud Architect,
Nordcloud GmBH
Ranga Karanam
Best Selling Instructor on Udemy with 1 MILLION Students,
in28Minutes.com
Samir Behara
Senior Cloud Infrastructure Architect,
AWS
Pratik Prakash
Master Software Engineer (SDE-IV),
Capital One