Containers Trend Report. Explore the current state of containers, containerization strategies, and modernizing architecture.
Securing Your Software Supply Chain with JFrog and Azure. Leave with a roadmap for keeping your company and customers safe.
Containers allow applications to run quicker across many different development environments, and a single container encapsulates everything needed to run an application. Container technologies have exploded in popularity in recent years, leading to diverse use cases as well as new and unexpected challenges. This Zone offers insights into how teams can solve these challenges through its coverage of container performance, Kubernetes, testing, container orchestration, microservices usage to build and deploy containers, and more.
Kubernetes is an open-source container orchestration platform that is used to manage and automate the deployment, scaling, and management of containerized applications. Azure DevOps is a cloud-based DevOps service that provides a complete CI/CD pipeline for building, testing, and deploying applications. In this article, I will discuss how to deploy a Kubernetes application using Azure DevOps. Prerequisites An Azure subscription An Azure DevOps account A Kubernetes cluster A Docker image Step 1: Create a Kubernetes Deployment File Create a Kubernetes deployment file (deployment.yaml) in your source code repository. This file should contain the specifications of your Kubernetes deployment, including the container image, replicas, and ports. Here is an example of a deployment file: YAML apiVersion: apps/v1 kind: Deployment metadata: name: my-app-deployment spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: my-app-container image: my-app-image:latest ports: - containerPort: 8080 Step 2: Create an Azure DevOps Pipeline In your Azure DevOps account, create a new pipeline and select the source code repository where your deployment file is located. Choose the appropriate repository type (e.g., Git) and select the branch to use for the deployment. Next, choose the appropriate template for your pipeline. For Kubernetes deployments, we can use the “Deploy to Kubernetes” template, which is available in the Azure DevOps marketplace. Step 3: Configure the Azure Kubernetes Service (AKS) Connection In the pipeline, add a new task for configuring the AKS connection. This task will authenticate your pipeline to your AKS cluster. To add this task, search for “Kubernetes” in the task search bar and select the “Configure Kubernetes connection” task. In the task configuration window, select the appropriate Azure subscription and AKS cluster. Also, provide the Kubernetes namespace and service account information. Step 4: Add the Kubernetes Deployment Task After configuring the AKS connection, add the Kubernetes deployment task. Search for “Kubernetes” in the task search bar and select the “Deploy to Kubernetes” task. In the task configuration window, provide the path to your deployment file, select the appropriate image registry, and provide the container image name and tag. Step 5: Save and Run the Pipeline Save your pipeline and run it. The pipeline will build the Docker image, push it to the image registry, and deploy it to the Kubernetes cluster. Conclusion Kubernetes is a powerful tool for managing containerized applications. Azure DevOps provides a complete CI/CD pipeline for building, testing, and deploying applications. By using these tools together, we can easily deploy applications to Kubernetes clusters. With Azure DevOps, you can automate your deployment process and reduce manual errors, which can improve your application’s reliability and scalability. We covered the steps for creating a Kubernetes deployment file, creating an Azure DevOps pipeline, configuring the AKS connection, adding the Kubernetes deployment task, and running the pipeline. By following these steps, you can deploy your Kubernetes application using Azure DevOps. Kubernetes has become the de facto standard for container orchestration and management and with good reason. It is highly scalable, portable, and resilient, making it a great choice for deploying and managing containerized applications.
When it comes to Container Orchestration, there’s a good chance you may already be aware of Kubernetes, an open-source solution for automating the deployment, scaling, and management of containerized applications that aggregate the containers comprising an application into logical units allowing for simple management and discovery. You may also know of the AWS Elastic Kubernetes Service (AWS EKS), a managed Kubernetes service that enables you to run Kubernetes on AWS easily. Knowing about these things is not enough to leverage the best out of Kubernetes using AWS EKS; you must know about AWS EKS Best Practices. In this blog, we will be looking at 10 AWS EKS Best Practices that will help you configure, deploy, use, and manage the Kubernetes Cluster on AWS for high security, reliability, availability, and more. We will also explain how and why you should save your EKS cluster as code. What Features Does EKS Include? We won’t get into the details of what Kubernetes is and how it works, as you probably already know. That said, it’s worth addressing a few of the features that AWS EKS provides, as the best practices we will present in this article revolve around these features. You can use Amazon EKS to execute your Kubernetes apps on AWS Fargate and Amazon EC2. A scalable and highly available Kubernetes control plane running across multiple AWS Availability Zones is provided by Amazon EKS. For Kubernetes clusters, EKS offers an integrated console. With just one command, Amazon EKS enables you to create, update, scale and terminate nodes for your cluster. Cluster management and operations, including managing nodes and add-ons, can be made simpler with eksctl. AWS CloudTrail and Amazon EKS have been connected to give users visibility into EKS administration activities, including audit history. These are just a few of the features that AWS EKS provides. What Are the Prerequisites for Setting up an EKS Cluster? Before you go ahead and try out these AWS EKS Best Practices on your own, you need to meet the following requirements. If you don’t meet these requirements, we still recommend reading through the article to get a theoretical understanding of these AWS EKS Best Practices to have them in place when you set up your EKS cluster. AWS Account:Since we are looking at AWS EKS Best practices, it’s no surprise that you will need an AWS Account. If you already have one, that’s great! If not, click here to register for a free tier. AWS-CLI Command:You will need this command to configure kubectl so you can connect to your AWS EKS cluster from the CLI. Click here to learn how to install or update AWS-CLI on your machine. Kubectl Command:You will need kubectl to run commands against your AWS EKS clusters. Click here to learn how to install it on your machine. Terraform Command:In order to use Terraform to create your AWS EKS clusters, you will need to install it. Click here to install and use it. Now, without further ado, let’s go ahead and dive into the top 10 AWS EKS Best Practices. What Are the AWS EKS Best Practices? Use a Version Control System When you are just starting with EKS or any other AWS service, you usually immediately go to the AWS Console to create the required resources. This is how infrastructure was managed traditionally on-cloud or on-premise, and it works fine when you are just getting started; however, this is no longer the best way of going about things. Nowadays, the Infrastructure as Code has taken over the manual process of creating the required resources and infrastructure. The same applies to AWS EKS clusters. In the same way, you store configuration files related to your Kubernetes deployments, ingresses, services, and other objects in a Version Control System before pushing them to a cluster; you should also save the Infrastructure as Code (IaC) files used to create your EKS cluster to a Version Control System in order to benefit from the advantages of Infrastructure as Code (IaC). This helps you keep track of who made what changes to your EKS cluster and when such changes were made. E.g., If you want to create an EKS cluster, you can use Terraform, Cloudformation, or any other Infrastructure provisioning tool. That said, you must also save the files or code that you used to create the EKS cluster to a Version Control System, like GitHub, in order to have a complete history of the changes you made to your EKS cluster. Additionally, IaC guarantees that you will always provide the same environment, i.e., your EKS cluster. Furthermore, IaC facilitates configuration management and allows you to prevent ad hoc, undocumented configuration changes by codifying and documenting your configuration standards. The configuration files you used to create your EKS cluster should be subject to source control just like any other software source code file, seeing as version control is a crucial component of IaC. By deploying your EKS as code, it may also break it into modular components that can be automatedly joined in various ways. Tip 1: We recommend creating your EKS cluster using an IaC approach and storing its Infrastructure as Code (IaC) files in a Version Control System as a first AWS EKS Best Practice. Kubernetes Cluster Version Kubernetes is a frequently updated open-source project that is continually expanding. Because Kubernetes doesn’t adhere to the Long Term Support (LTS) philosophy, Kubernetes-adopting enterprises must conduct routine upgrades. The deployed components are simply one part of the Kubernetes upgrading process. The versioning of the Kubernetes API must also be monitored. In order to take advantage of the most recent security, new features, and bug fixes, it’s important to update the cluster and deployed solutions. In the case of EKS, launching new API server nodes with the upgraded Kubernetes version to replace the outdated ones is part of the upgrading process. You can’t directly update to the newest version of Kubernetes, seeing as the procedure is gradual. We strongly advise that you select the most recent version of Kubernetes supported by AWS for your EKS clusters in order to take advantage of new features and enhancements unless your containerized applications require a specific version of Kubernetes. As of August 26, 2022, 1.23 is the latest version supported by EKS.Tip 2: The next AWS EKS Best Practice we have on our list for you is to keep your EKS cluster up to date and take advantage of the benefits Kubernetes provides in its new releases.Sample Terraform Snippet: XML resource “aws_eks_cluster” “my_eks_cluster” { . . . name = “sample-eks-cluster” version = <Unless your application requires a specific version of Kubernetes for your workloads, we recommend you to choose the latest available Kubernetes version supported by Amazon EKS for your cluster.> . . . } Configuration in the EKS Cluster Configuration Console: You will find this configuration under EKS -> Add Cluster -> Create -> Configure cluster-> Cluster configuration -> Kubernetes version Enable Envelope Encryption for EKS Kubernetes Secrets Using the Kubernetes API, you can store and manage sensitive data such as passwords, docker registry credentials, and TLS keys, thanks to Kubernetes secrets. In order to safeguard your data from unauthorized access and satisfy compliance requirements for data-at-rest encryption within your business, we strongly advise that you enable encryption of Kubernetes secrets when working with security-critical data. Kubernetes store all secret object data in etcd, and all etcd volumes used by Amazon EKS are encrypted on the disk using encryption keys managed by AWS. AWS Key Management Service (KMS) keys can provide envelope encryption for Kubernetes secrets stored in Amazon Elastic Kubernetes Service (EKS). Implementing envelope encryption is regarded as an AWS EKS Security Best Practice for applications that store sensitive data. Without needing to install or administer extra software, you can utilize KMS keys that you generate or import keys generated by another system to AWS KMS in order to encrypt Kubernetes secrets and use them with the cluster. Envelope encryption for secrets is available for Amazon EKS clusters using Kubernetes versions 1.13 and later. Keep in mind that there is no way to restore the cluster if you enable secret encryption for your EKS cluster and the KMS key you used is removed. Indeed, the cluster becomes irreversibly damaged if the KMS key is deleted. Tip 3: To safeguard your data from illegal access and to satisfy compliance standards for data-at-rest encryption, we highly recommend that you enable secret encryption of Kubernetes secrets in your EKS cluster. Sample Terraform Snippet: XML resource “aws_eks_cluster” “my_eks_cluster” { . . . name = “sample-eks-cluster” version = “1.23” #use this block you enable Envelope encryption encryption_config { resources = [ “secrets” ] provider { key_arn = <ARN of the Key Management Service (KMS) customer master key (CMK)> } } . . . } Configuration in the EKS Cluster Configuration Console: You will find this configuration under EKS -> Add Cluster -> Create -> Configure cluster -> Cluster configuration -> Secrets encryption VPC Layout For each cluster, Amazon EKS runs a single-tenant Kubernetes control plane. In no event do clusters or AWS accounts share the control plane infrastructure. A minimum of two API server instances, three etcd instances, and three Availability Zones inside an AWS Region make up the control plane fully managed by AWS. That said, when it comes to worker nodes, the user is responsible for taking care of its networking, and VPC is the first component required for building the EKS cluster. Worker nodes and Kubernetes Pods require networking capabilities; therefore, when building a cluster, you must define a different VPC for each cluster with at least two subnets in different Availability Zones allowing for high availability. You can create an EKS Cluster using the default VPC, Subnet, and Security Groups. However, in order to stay on the safe side of things, we recommend always creating your own VPC, Subnet, and SGs with particular IP ranges for Ingress and Egress as a best practice. To ensure zone-independent architecture while deploying worker nodes on private subnets, consider setting up a NAT Gateway in each Availability Zone. The Amazon VPC Container Network Interface (CNI) plugin for Kubernetes provides native VPC networking capabilities for Amazon EKS. The CNI plugin enables Kubernetes Pods to share an IP address between their internal and VPC networks. Hence, the subnets you’ll use for pod networking should be sized for expansion, seeing as your pods won’t obtain an IP address if the subnet that the CNI uses doesn’t have enough IP addresses available. Furthermore, until an IP address becomes available, the pods will stay pending, potentially affecting application autoscaling and compromising its availability. Tip 4: We recommend using different VPCs with subnets that span multiple Availability Zones for different clusters. When choosing the appropriate CIDR, consider the number of IPs required for your pods in the cluster. As a best practice, think of a VPC that will provide high availability for your EKS cluster. Sample Terraform Snippet: XML resource “aws_eks_cluster” “my_eks_cluster” { . . . name = “sample-eks-cluster” version = “1.23” encryption_config { resources = [ “secrets” ] provider { key_arn = <ARN of the Key Management Service (KMS) customer master key (CMK)> } } vpc_config { . . subnet_ids = [<private_subnet_1_id>,<private_subnet_2_id>,<private_subnet_3_id>] #Add you subnets here . . } . . . } Configuration in the EKS Cluster Configuration Console: You will find this configuration under EKS -> Add Cluster -> Create -> Next -> Specify networking -> Networking -> VPC and Subnets EKS Security Groups It is not recommended to open all of the ports inside your Amazon EKS security groups. Indeed, doing so will enable attackers to use port scanners and other probing techniques to identify the services and apps operating on your EKS clusters and exploit their vulnerabilities. To get around this, make sure that the security groups connected to your Amazon Elastic Kubernetes Service (EKS) clusters are set up to only allow inbound traffic on TCP port 443 (HTTPS). This will shield your clusters from malicious actions like brute-force attacks and help you adhere to organizational compliance standards. Please be aware that you may limit the rules. We, therefore, advise that you test every one of your pods properly before implementing your modified rules in a production cluster. E.g. The ports that you anticipate that your nodes will use for inter-node communication, outbound internet access (so that nodes can access the Amazon EKS APIs for cluster introspection and node registration at launch time), and node access to pull images from registries (to pull container images from the Amazon ECR or other container registry APIs needed to pull images from, such as DockerHub), must all be added. Tip 5: According to EKS Security Best Practices, only the ports needed by your EKS cluster must be opened. Do not unnecessarily open ports if you are unsure of their function. Sample Terraform Snippet: XML resource “aws_eks_cluster” “my_eks_cluster” { . . . name = “sample-eks-cluster” version = “1.23” encryption_config { resources = [ “secrets” ] provider { key_arn = <ARN of the Key Management Service (KMS) customer master key (CMK)> } } vpc_config { . . subnet_ids = [<private_subnet_1_id>,<private_subnet_2_id>,<private_subnet_3_id>] security_group_ids = [<security_group_1_id>,<security_group_n_id>] #Add your Security Groups here . . } . . . } Configuration in the EKS Cluster Configuration Console: You will find this configuration under EKS -> Add Cluster -> Create -> Next -> Specify networking -> Networking -> Security groups EKS Cluster Endpoint Private Access After each cluster launch, you can utilize the managed Kubernetes API server endpoint that Amazon EKS generates for you to communicate with your newly established cluster. Every machine on the Internet can reach your EKS cluster through its public endpoint, seeing as this API server endpoint (managed by AWS EKS) can be directly accessed by default outside of a Virtual Private Cloud (VPC). This can potentially increase the risk of malicious activities and attacks. In order to reduce security risks and protect sensitive information, ensure that the Kubernetes API server endpoint for your Amazon EKS cluster is not publicly accessible via the Internet. Your EKS application use cases will determine the extent of access to your Kubernetes API server endpoints. However, for most use cases, we recommend that the API server endpoints only be accessible from within your AWS Virtual Private Cloud (VPC). In such a case, to run kubectl commands, you can launch an Amazon EC2 instance into a public subnet in the VPC of your cluster. To accept ingress traffic on port 443 from your bastion host, you first need to ensure that your Amazon EKS control plane security group contains the required rules. Tip 6: In accordance with EKS Security Best Practices, entirely disable public access to your API server endpoint, making it unreachable from the Internet, and use a bastion host to run kubectl commands. Sample Terraform Snippet: XML resource “aws_eks_cluster” “my_eks_cluster” { . . . name = “sample-eks-cluster” version = “1.23” encryption_config { resources = [ “secrets” ] provider { key_arn = <ARN of the Key Management Service (KMS) customer master key (CMK)> } } vpc_config { . . subnet_ids = [<private_subnet_1_id>,<private_subnet_2_id>,<private_subnet_3_id>] security_group_ids = [<security_group_1_id>, <security_group_n_id>] endpoint_private_access = “true” #Make this true so as to have the cluster endpoint accessible only through your VPC. . . } . . . } Configuration in the EKS Cluster Configuration Console: You will find this configuration under EKS -> Add Cluster -> Create -> Next -> Specify networking -> Cluster endpoint access -> Private Kubernetes Cluster Logging The Kubernetes control plane is a collection of parts that controls Kubernetes clusters and generates logs for auditing and troubleshooting. Logs for various control plane components can be enabled using Amazon EKS and then sent to CloudWatch. In order to publish API, audit, controller manager, scheduler, or authenticator logs to AWS CloudWatch Logs, control plane logs must be enabled in your AWS EKS clusters. Amazon EKS transmits audit and diagnostic logs straight to AWS CloudWatch Logs after the EKS Control Plane Logging capability is activated. You can use these logs to operate your EKS clusters securely and effectively. Cluster control plane logs are not usually transmitted to CloudWatch Logs by default. Therefore, we advise that you enable each log type separately when submitting in order to submit logs for your cluster. You should also be aware that if you utilize Amazon EKS control plane logging, you will be charged the usual Amazon EKS price for each cluster you run, in addition to the standard CloudWatch Logs data ingestion and storage charges you will need to pay for any logs transmitted to CloudWatch Logs from your clusters. Tip 7: Logs are usually ignored, but we highly recommend enabling logs for your EKS cluster as an AWS EKS Best Practice. Sample Terraform Snippet: XML resource “aws_eks_cluster” “my_eks_cluster” { . . . name = “sample-eks-cluster” version = “1.23” encryption_config { resources = [ “secrets” ] provider { key_arn = <ARN of the Key Management Service (KMS) customer master key (CMK)> } } vpc_config { . . subnet_ids = [<private_subnet_1_id>,<private_subnet_2_id>,<private_subnet_3_id>] security_group_ids = [<security_group_1_id>, <security_group_n_id>] endpoint_private_access = “true” enabled_cluster_log_types = [“api”, “audit”, “authenticator”, “controllerManager”, “scheduler”] #Enable required logs using this configuration . . } . . . } Configuration in the EKS Cluster Configuration Console: You will find this configuration under EKS -> Add Cluster -> Create -> Next -> Next -> Configure logging -> Control plane logging -> API server, Audit, Authenticator, Controller manager and Scheduler Control SSH Access to Node There should never be a necessity for the workloads operating in the cluster’s pods to SSH directly to the nodes. By preventing pods from using the SSH port, it becomes harder for a malicious pod to gain direct access to the node. SSH access shouldn’t be enabled for node instances. In rare instances, not being able to SSH to the node may make troubleshooting more challenging. However, system and EKS logs typically provide sufficient information for problem diagnosis. When logging into a host, utilize SSM Session Manager rather than activating SSH access. Session Manager may use IAM to restrict access to EC2 instances, unlike SSH keys, which can be misplaced, copied, or distributed. Additionally, IAM offers an audit trail and commands log, which are very useful. In light of the above, the bottom line is that a crucial safeguard for your cluster is to isolate the nodes to the fullest extent possible from the containers they host and limit SSH access to the node from outside sources. Tip 8: Do not enable SSH access to your nodes. Instead, use AWS Systems Manager Session Manager to gain access to the nodes from the AWS Console. Sample Terraform Snippet: XML resource “aws_eks_node_group” “my_eks_node_group” { cluster_name = “sample-eks-cluster” node_group_name = “sample-eks-cluster-node-group-1” remote_access { ec2_ssh_key = “<your-key>” source_security_group_ids = “<your-sg-that-does-not-allow-connection-on-port-22>” } } Please note that if you specify ec2_ssh_key, but forget to specify source_security_group_ids when you create your EKS Node Group using Terraform, port 22 on the worker nodes will be opened to the Internet (0.0.0.0/0). Hence, always explicitly restrict access to port 22 if you need to use the remote_access block in your node group configuration. Identity and Access Management Your Kubernetes cluster’s authentication is handled by Amazon EKS using IAM, while authorization is still handled by the native Kubernetes Role-Based Access Control (RBAC). As a result, IAM is only used to authenticate legitimate IAM entities. The native Kubernetes RBAC system controls all permissions with respect to your Amazon EKS cluster’s Kubernetes API. AWS Identity and Access Management (IAM) is a free AWS solution that enables administrators to securely manage access to your AWS EKS cluster and other AWS services. After being authenticated and granted access, IAM administrators control who can utilize Amazon EKS resources. By default, Amazon EKS resources cannot be created or modified by IAM users or roles. They can also not use the AWS Management Console, AWS CLI, or AWS API to perform tasks. In order to allow users and roles to execute particular API actions on the designated resources they require, you, as an IAM administrator, must create IAM policies. Once the IAM policies are created, you must associate the policies with the IAM users or groups that require permissions. It’s important to note that the IAM user or role that builds the cluster is automatically granted system:masters permissions in the cluster’s RBAC setup when you create an Amazon EKS cluster. Therefore, it’s wise to designate a specific IAM role when creating the cluster and periodically check who is authorized to take on it. Also, to access the Kubernetes API, an IAM User does not need access rights to AWS resources. Hence, if you need to provide an IAM user access to an EKS cluster, create an entry in the aws-auth ConfigMap for the user that corresponds to a particular Kubernetes RBAC group. You must also be prepared to routinely audit the aws-auth ConfigMap in order to determine who has been granted access and the rights they have been given because the list of people who need access is likely to change over time. Additionally, as the service account token is a persistent, static credential, we advise against using it. An attacker may be enabled to carry out all of the operations connected to a particular token up until the service account is terminated if it is hacked, lost, or stolen. Tip 9: Avoid using service account tokens, provide the least privileged AWS Resources access to the user that requires access to the EKS cluster and create the cluster with a dedicated IAM user/role. These are a few of our recommendations that constitute our EKS Best Practices. Sample Read-Only Access: The following configuration provides read only access to arn:aws:iam::AWSAccountID:user/IAMUserName cluster-role.yaml XML kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: read-only-cluster-role rules:- apiGroups: [“”] resources: [“pods/log”] verbs: [“get”, “watch”, “list”] – apiGroups: [“”] resources: [“pods”] verbs: [“get”, “watch”, “list”] cluster-role-binding.yaml XML kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: read-only-cluster-role-binding subjects: – kind: Group name: “read-only-group” apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: read-only-cluster-role apiGroup: rbac.authorization.k8s.io aws-auth configmap kubectl edit configmap aws-auth -n kube-system XML apiVersion: v1 kind: ConfigMap metadata: name: aws-auth namespace: kube-system data: mapUsers: | – groups: – read-only-group userarn: arn:aws:iam::AWSAccountID:user/IAMUserName username: IAMUserName Reference: Click here to learn how to enable IAM user and role access to your cluster. Cluster Autoscaling Thanks to the auto-scaling capability, your resources can be dynamically scaled up or down to meet shifting demands. This is a crucial Kubernetes operation that, if done manually, requires a lot of labor. Autoscaling ensures that your EKS cluster has enough nodes to schedule your pods without wasting resources. Cluster autoscaling is essential in a Kubernetes cluster as it ensures that sufficient computing resources are available by adding nodes to a cluster and reduces infrastructure expenses by eliminating nodes. An environment’s total availability and uptime are maintained through Cluster Autoscaling. Cluster Autoscaling should be thought of as a great initial step to maintaining optimal sizing on your EKS cluster in order to minimize expenses and preserve the environment’s uptime. There are two products for autoscaling that Amazon EKS supports: The open source autoscaling initiatives Karpenter and the Kubernetes Cluster Autoscaler. While Karpenter works directly with the Amazon EC2 fleet, the Cluster Autoscaler uses AWS scaling groups. The Kubernetes Cluster Autoscaler looks out for unscheduled pods and underutilized nodes. Before implementing the actual modification in your cluster, it simulates the addition or deletion of nodes. Usually, the Cluster Autoscaler is implemented as a Deployment in your cluster. Karpenter is a versatile, high-performance Kubernetes cluster autoscaler that boosts cluster effectiveness and application availability. Karpenter can provide just-in-time compute resources that precisely match the demands of your workload by connecting Kubernetes with AWS. Based on the particular needs of your cluster workloads, Karpenter automatically creates additional compute resources. Tip 10: Depending on the requirements of your organization, you can choose to go with Karpenter or the Kubernetes Cluster Autoscaler. That said, choosing an autoscaling product is a must and that’s why it’s one of our AWS EKS Best Practices. Reference: Click here to learn more about Autoscaling on AWS EKS. Conclusion New security obligations arise when workloads are run on AWS EKS or any Kubernetes cluster. Creating an AWS EKS cluster is as simple as clicking a few clicks on the AWS Console. However, it’s important that it be created optimally. In this article, we presented you with 10 AWS EKS Best Practices, which will help you create your EKS cluster in accordance with standard practices. We strongly recommend that you follow these AWS EKS Best Practices right from the start, i.e., from the moment you start creating your cluster. These AWS EKS Best Practices cover a broad range of topics, including IaC, Networking, Security, Access Control, Accessibility, and more. Each section begins with an introduction of the main ideas, followed by specific suggestions based on AWS EKS Best Practices that have been implemented and approved by our Kubernetes experts and the Kubernetes technical teams. EKS Best Practices Summary EKS Best Practices #1 Use a Version Control System to store your cluster’s IaC files. #2 Keep your EKS up to date and take advantage of Kubernetes’ new releases. #3 Enable secret encryption of Kubernetes secrets in your EKS cluster. #4 Use different VPCs that provide high availability for your cluster. #5 For EKS Security, only open the ports needed by your EKS. #6 Disable public access to your EKS Cluster Endpoint for more security. #7 Enable logs for your EKS cluster. #8 Use AWS Systems Manager Session Manager to gain access to the nodes from the AWS Console. #9 Avoid using service account tokens, provide the least privileged AWS Resources access to the user that requires access to the EKS cluster and create the cluster with a dedicated IAM user/role. #10 Depending on the requirements of your organization, choose the best autoscaling product (Karpenter or Kubernetes Cluster Autoscaler).
With the rise of Docker came a new focus for engineers: optimizing the build to reach the smallest image size possible. A couple of options are available. Multi-stage builds: A Dockerfile can consist of multiple steps, each having a different Docker base image. Each step can copy files from any of the previous build steps. Only the last one will receive a tag; the others will be left untagged.This approach separates one or more build steps and a run step. On the JVM, it means that the first step includes compiling and packaging, based on a JDK, and the second step comprises running, based on a JRE. Choosing the smallest base image size: The smaller the base image, the smaller the resulting image. In this post, I'm going to focus on the second point. Minimal Base Images Three approaches are available for base images. Here they are: FROM scratch The following is taken from "Create a simple parent image using scratch": You can use Docker’s reserved, minimal image, scratch, as a starting point for building containers. Using the scratch "image" signals to the build process that you want the next command in the Dockerfile to be the first filesystem layer in your image. While scratch appears in Docker’s repository on the hub, you can’t pull it, run it, or tag any image with the name scratch. Instead, you can refer to it in your Dockerfile. For example, to create a minimal container using scratch: Dockerfile FROM scratch COPY hello / CMD ["/hello"] scratch is the smallest possible parent image. It works well if the final image is independent of any system tool. Alpine Alpine Linux is a tiny distribution based on musl, BusyBox, and OpenRC. It's designed to be secure and small. For example, the 3.17 Docker image is only 3.22 MB. On the flip side, I already encountered issues because of Alpine's usage of musl instead of the more widespread glibc. Just last week, I heard about Alpaquita Linux, which is meant to solve this exact issue. The stream-glibc-230404 tag is 8.4 MB. It's twice bigger as Alpine but is still very respectable compared to regular Linux distros, e.g., Red Hat's 75.41 MB. Distroless Last but far from least comes Distroless. Since this post focuses on Distroless, I'll dive into it in a dedicated section. Distroless I first learned about Distroless because it was the default option in Google's Jib. Jib is a Maven plugin to create Docker containers without dependency on Docker. Note that the default has changed now. Distroless has its own GitHub project: "Distroless" images contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution. [...] Restricting what's in your runtime container to precisely what's necessary for your app is a best practice employed by Google and other tech giants that have used containers in production for many years. It improves the signal to noise of scanners (e.g. CVE) and reduces the burden of establishing provenance to just what you need. - "Distroless" Container Images The statement above hints at what Distroless is and why you should use it. Just like "Serverless," "Distroless" is a misleading term. The most important fact is that Distroless provides neither a package manager nor a shell. For this reason, the size of a Distroless image is limited. Also, Distroless images are considered more secure: the attack surface is reduced compared to other regular images, and they lack package managers and shells - common attack vectors. Note that some articles dispute this benefit. Distroless images come with four standardized tags: latest nonroot: The image doesn't run as root, so it's more secure debug: The image contains a shell for debugging purposes debug-nonroot Distroless Debugging I love the idea of Distroless, but it has a big issue. Something happens during development and sometimes during production, and one needs to log into the container to understand the problem. In general, one uses docker exec or kubect exec to run a shell: it's then possible to run commands interactively from inside the running container. However, Distroless images don't offer a shell by design. Hence, one needs to run every command from outside; it could be a better developer experience. During development, one can switch the base image to a debug one. Then, you rebuild and run again, and then solve the problem. Yet, you must remember to roll back to the non-debug base image. The more issues you encounter, the more chances you'll finally ship a debug image to production. Worse, you cannot do the same trick in production at all. Kubernetes to the Rescue At the latest JavaLand conference, I attended a talk by my good friend Matthias Häussler. In the talk, he made me aware of the kubectl debug command, introduced in Kubernetes 1.25: Ephemeral containers are useful for interactive troubleshooting when kubectl exec is insufficient because a container has crashed or a container image doesn't include debugging utilities, such as with distroless images. You can use the kubectl debug command to add ephemeral containers to a running Pod. - Debugging with an ephemeral debug container Let's see how it works by running a Distroless container: Shell kubectl run node --image=gcr.io/distroless/nodejs18-debian11:latest --command -- /nodejs/bin/node -e "while(true) { console.log('hello') }" The container starts an infinite Node.js loop. We can check the logs with the expected results: Shell kubectl logs node Plain Text hello hello hello hello Imagine that we need to check what is happening inside the container. Shell kubectl exec -it node -- sh Because the container has no shell, the following error happens: Plain Text OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown command terminated with exit code 126 We can use use kubectl debug magic to achieve it anyway: Shell kubectl debug -it \ --image=bash \ #1 --target=node \ #2 node #3 Image to attach. As we want a shell, we are using bash. Name of the container to attach to For some reason I don't understand, we must repeat it. The result is precisely what we expect: Plain Text Targeting container "node". If you don't see processes from this container it may be because the container runtime doesn't support this feature. Defaulting debug container name to debugger-tkkdf. If you don't see a command prompt, try pressing enter. bash-5.2# We can now use the shell to type whatever command we want: Shell ps The result confirms that we "share" the same container: Plain Text PID USER TIME COMMAND 1 root 12:18 /nodejs/bin/node -e while(true) { console.log('hello') } 27 root 0:00 bash 33 root 0:00 ps After we finish the session, we can reattach it to the container by following the instructions: Plain Text bash-5.2# Session ended, the ephemeral container will not be restarted but may be reattached using 'kubectl attach node -c debugger-tkkdf -i -t' if it is still running Conclusion Distroless images are an exciting solution to reduce your image's size and improve its security. They achieve these advantages by providing neither a package manager nor a shell. The lack of a shell is a huge issue when one needs to debug what happens inside a container. The new kubectl debug command offers a clean way to fix this issue by attaching an external container that shares the same context as the original one. Danke nochmal dafür, Matthias! To Go Further: "What's Inside Of a Distroless Container Image: Taking a Deeper Look" "In Pursuit of Better Container Images: Alpine, Distroless, Apko, Chisel, DockerSlim, oh my!" How To Debug Distroless And Slim Containers
Flask is a popular web framework for building web applications in Python. Docker is a platform that allows developers to package and deploy applications in containers. In this tutorial, we'll walk through the steps to build a Flask web application using Docker. Prerequisites Before we begin, you must have Docker installed on your machine. You can download the appropriate version for your operating system from the official Docker website. Additionally, it would help if you had a basic understanding of Flask and Python. Creating a Flask Application The first step is to create a Flask application. We'll create a simple "Hello, World!" application for this tutorial. Create a new file called app.py and add the following code: Python from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello, World!' Save the file and navigate to its directory in a terminal. Creating a Dockerfile The next step is to create a Dockerfile. A Dockerfile is a script that describes the environment in which the application will run. We'll use the official Python 3.8 image as the base image for our Docker container. FROM python:3.8-slim-buster: This sets the base image for our Docker container to the official Python 3.8 image. WORKDIR /app: This sets the working directory inside the container to /app. COPY requirements.txt .: This copies the requirements.txt file from our local machine to the /app directory inside the container. RUN pip install --no-cache-dir -r requirements.txt: This installs the dependencies listed in requirements.txt. COPY . .: This copies the entire local directory to the /app directory inside the container. CMD [ "python", "app.py" ]: This sets the command to run when the container starts to python app.py. Create a new file called Dockerfile and add the following code: Dockerfile FROM python:3.8-slim-buster # Set the working directory WORKDIR /app # Install dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy the application code COPY . . # Run the application CMD [ "python", "app.py" ] Save the Dockerfile and navigate to its directory in a terminal. Building the Docker Image The next step is to build a Docker image from the Dockerfile. Run the following command to build the image: Python docker build -t my-flask-app . This command builds an image named my-flask-app from the Dockerfile in the current directory. The . at the end of the command specifies that the build context is the current directory. Starting the Docker Container Now that we have a Docker image, we can start a container from it. Run the following command to start a new container from the my-flask-app image and map port 5000 on the host to port 5000 in the container: Python docker run -p 5000:5000 my-flask-app This command starts a new container from the my-flask-app image and maps port 5000 on the host to port 5000 in the container. Testing the Flask Application Finally, open your web browser and navigate to http://localhost:5000. You should see the "Hello, World!" message displayed in your browser, indicating that the Flask application is running inside the docker application. Customizing the Flask Application You can customize the Flask application by modifying the app.py file and rebuilding the Docker image. For example, you could modify the hello function to return a different message: Python @app.route('/') def hello(): return 'Welcome to my Flask application!' Save the app.py file and rebuild the Docker image using the docker build command from earlier. Once the image is built, start a new container using the docker run command from earlier. When you navigate to http://localhost:5000, you should see the updated message displayed in your browser. Advantages Docker simplifies the process of building and deploying Flask applications, as it provides a consistent and reproducible environment across different machines and operating systems. Docker allows for easy management of dependencies and versions, as everything needed to run the application is contained within the Docker image. Docker facilitates scaling and deployment of the Flask application, allowing for the quick and easy creation of new containers. Disadvantages Docker adds an additional layer of complexity to the development and deployment process, which may require additional time and effort to learn and configure. Docker may not be necessary for small or simple Flask applications, as the benefits may not outweigh the additional overhead and configuration. Docker images and containers can take up significant disk space, which may concern applications with large dependencies or machines with limited storage capacity. Conclusion In this tutorial, we've walked through the steps to build a Flask web application using Docker. We've created a simple Flask application, written a Dockerfile to describe the environment in which the application will run, built a Docker image from the Dockerfile, started a Docker container from the image, and tested the Flask application inside the container. With Docker, you can easily package and deploy your Flask application in a consistent and reproducible manner, making it easier to manage and scale your application.
This article is based on an interview with Kubernetes co-founder Brendan Burns who appeared on the Dev Interrupted podcast. The success of Kubernetes was never preordained — it took years of work. While today it has grown to be one of the largest and most popular open source projects and become the go-to API for building cloud-native applications worldwide — it almost wasn’t even open source. Since its start in 2014, Kubernetes (an open-source orchestrator for deploying containerized applications) has transformed from a tiny project co-founded by three people, Joe Beda, Craig McLuckie, and Brendan Burns, into a production-grade infrastructure that powers large-scale production applications in various fields, from machine learning to online services. Created with the intent to simplify the task of building, deploying, and maintaining distributed systems, it allows people the world over to achieve new levels of velocity, agility, and reliability. Over the years, Kubernetes has grown into a large, successful open-source community — but it was a long journey getting there. What Is a Container? Before jumping into the history of Kubernetes, let’s first define a “container” because it often has a broad set of meanings. Fundamentally, a container is a process of taking an application and packaging it, building a binary representation of the pieces that make up that application, like the software, configuration files, etc., and having a protocol for distributing it around the world. There are three pillars of what became known as containers: Packaging everything so it's exactly the same, for instance, on a laptop as it is out in the cloud The distribution of step 1 needs to occur easily and around the world Creating an environment where a bug in one component of the process doesn’t affect another component How It Started When we interviewed Kubernetes co-founder Brendan Burns on the Dev Interrupted podcast, he told us that as an engineer, he found himself gravitating towards spaces with opportunity. While working at Google, he noticed that Cloud had a lot of white space and thought it would be an attractive space to work in. At the time, he led a small team of about seven engineers and decided to transition his team over to Cloud. At the same time, his eventual Kubernetes co-founders Joe and Craig created Compute Engine, the virtual machine product for Google Cloud, and the three of them began to work together in Google’s Cloud division. While Joe and Craig focused on compute, Brendan worked in config management on things like CloudFormation and Terraform. Ideas were starting to coalesce, and the three of them were witnessing the challenges people faced trying to adopt Cloud — a far too difficult process. There were also some internal systems at Google, in particular a system called Borg, a cluster manager that’s still used today, that served as the initial inspiration for the three developers as they dreamt up Kubernetes. However, none of it would have been a reality without Docker. Docker Changes Everything As a prerequisite to the functionality of Kubernetes, people needed to be motivated to build container images and run containers. Docker was the startup that came along and convinced people why they should care about containment. All of a sudden, a use case existed, and an amazing opportunity presented itself. Docker didn’t have a lot of experience at scale, and they were focused on one machine, with a container and daemon on that machine — what they were lacking was orchestration. If a system was built that could act as a container-orchestrator, it represented not only a massive opportunity to change the market but to change the entire cloud landscape. When you think about what it takes to deploy an application up to the cloud or even to an on-premise collection of machines, it’s a lengthy process. It requires you to package up an application, distribute it, keep it running, have load-balanced traffic between the various pieces of the application, and an API to tie it all together. Prior to Kubernetes, some of these systems were in place, but nothing like today. Kubernetes is responsible for mainstreaming the idea of a distributed systems application environment for building and constructing distributed systems that span machines far and wide. With the need for orchestration realized, the next step was selling the idea to executives. Selling Open Source Convincing people that it was possible and a good idea was pretty straightforward. There were folks at Google who understood what Joe, Craig, and Brendan were trying to do. The real battle was fighting to make Kubernetes open source. As Brendan shared in our interview, they had a lot of internal arguments at Google about it being open source. Mostly it came down to money and control. From a business perspective, if a product or system is massively successful and you’re the only one who can sell it, then you’re in a great position. Conversely, Brendan told us that he always felt that Kubernetes would only be massively successful if it had an ecosystem, and the best way to foster an ecosystem was to make it open source. This viewpoint is centered around the community that comes together to build the software. An amazing community formed early on of people who helped build docs, who helped build tutorials, who would talk about their work at conferences, and then an ecosystem of companies that were betting their whole business on the success of Kubernetes. Startups began popping up, saying things like, “Well, the prerequisite for using my monitoring software is that you have a Kubernetes cluster.” All of the attention and goodwill formed a sort of virtuous cycle around Kubernetes. Success Has a Way of Looking Easy Soon enough, Kelsey Hightower, principal engineer for Google Cloud and Brendan’s co-author of the book Kubernetes: Up and Running: Dive into the Future of Infrastructure, came along and started doing a ton of evangelism and driving attention towards Kubernetes. It can be easy to look back and assume that it was easy because Kubernetes just took over. It's present in every major public cloud at this point. People expect it to be in new systems. But the truth is that in those early years, it took a lot of hard work to build and evangelize Kubernetes. Brendan shared with us that his hope for the future is that the bits of Kubernetes sort of fade into the background. It’ll be there, and it’ll be important, but it won’t be talked about or thought about from day to day because, as he puts it, “There’s so much more that needs to be built.”
In the initial "How to Move IBM App Connect Enterprise to Containers" post, a single MQ queue was used in place of an actual MQ-based back-end system used by an HTTP Input/Reply flow, which allowed for a clean example of moving from local MQ connections to remote client connections. In this post, we will look at what happens when an actual back-end is used with multiple client containers and explore solutions to the key challenge: how do we ensure that reply messages return to the correct server when all the containers have identical starting configurations? For a summary of the use of MQ correlation IDs; see Correlation ID Solution below. Issues With Multiple Containers and Reply Messages Consider the following scenario, which involves an HTTP-based flow that calls an MQ back-end service using one queue for requests and another for replies (some nodes not shown): The HTTP input message is sent to the MQ service by the top branch of the flow using the input queue, and the other branch of the flow receives the replies and sends them back to the HTTP client.The picture is complicated slightly by HTTPReply nodes requiring a “reply identifier” in order to know which HTTP client should receive a reply (there could be many clients connected simultaneously), with the identifier being provided by the HTTPInput node. The reply identifier can be saved in the flow (using ESQL shared variables or Java static variables) in the outbound branch and restored in the reply branch based on MQ-assigned message IDs, or else sent to the MQ service as a message or correlation ID to be passed back to the reply branch, or possibly sent as part of the message body; various solutions will work in this case.This behaves well with only one copy of the flow running, as all replies go through one server and back to the calling client. If the ACE container is scaled up, then there will be a second copy of the flow running with an identical configuration, and it might inadvertently pick up a message intended for the original server. At that point, it will attempt to reply but discover that the TCPIP socket is connected to the original server: This situation can arise even with only a single copy of the container deployed: a Kubernetes rolling update will create the new container before stopping the old one, leading to the situation shown above due to both containers running at the same time. While Kubernetes does have a “Recreate” deploy strategy that eliminates the overlap, it would clearly be better to solve the problem itself rather than restricting solutions to only one container. Containers present extra challenges when migrating from on-prem integration nodes: the scaling and restarts in the container world are often automated and not directly performed by administrators, and all of the replica containers have the same flows with the same nodes and options. There is also no “per-broker listener” in the container case, as each server has a separate listener. Solutions The underlying problem comes down to MQInput nodes picking up messages intended for other servers, and the solutions come in two general categories: Use correlation IDs for reply messages so that the MQInput nodes only get the messages for their server.Each server has a specific correlation ID, the messages sent from the MQOutput node are specific to that correlation ID, and the back-end service copies the correlation ID into the response message. The MQInput node only listens for messages with that ID, and so no messages for other servers will be picked up. This solution requires some way of preserving the HTTP reply identifier, which can be achieved in various ways. Create a separate queue for each server and configure the MQInput nodes for each container to use a separate queue.In this case, there is no danger of messages going back to the wrong server, as each server has a distinct queue for replies. These queues need to be created and the flows configured for this to work with ACE. The second category requires custom scripting in the current ACE v12 releases and so will not be covered in this blog post, but ACE v12 does have built-in support for the first category, with several options for implementing solutions that will allow for scaling and redeploying without messages going to the wrong server. Variations in the first category include the “message ID to correlation ID” pattern and synchronous flows, but the idea is the same. While containers show this problem (and therefore the solutions) nicely, the examples described here can be run on a local server also, and do not need to be run in containers. Scaling integration solutions to use multiple servers is much easier with containers, however, and so the examples focus on those scenarios. Example Solution Scenario Overview Building on the previous blog post, the examples we shall be using are at this GitHub repo and follow this pattern: The previous blog post showed how to create the MQ container and the ACE container for the simple flow used in that example, and these examples follow the same approach but with different queues and ACE applications. Two additional queues are needed, with backend-queues.mqsc showing the definitions: DEFINE QLOCAL(BACKEND.SHARED.INPUT) REPLACE DEFINE QLOCAL(BACKEND.SHARED.REPLY) REPLACE Also, the MQSC ConfigMap shown in a previous MQ blog post can be adjusted to include these definitions. The same back-end is used for all of the client flows, so it should be deployed once using the “MQBackend” application. A pre-built BAR file is available that can be used in place of the BAR file used in the post, "From IBM Integration Bus to IBM App Connect Enterprise in Containers (Part 4b)," and the IS-github-bar-MQBackend.yaml file can be used to deploy the backend service. See the README.md for details of how the flow works. Correlation ID Solution The CorrelIdClient example shows one way to use correlation IDs: This client flow relies on: The back-end flow honoring the MQMD Report settings A unique per-server correlation ID being available as a user variable The HTTP RequestIdentifier being usable as an MQ message ID The MQBackend application uses an MQReply node, which satisfies requirement 1, and requirement 3 is satisfied by the design of the ACE product itself: the request identifier is 24 bytes (which is the size of MQ’s MsgId and CorrelId) and is unique for a particular server. Requirement 2 is met in this case by setting a user variable to the SHA-1 sum of the HOSTNAME environment variable. SHA-1 is not being used in this case for cryptographic purposes but rather to ensure that the user variable is a valid hex number (with only letters from A-F and numbers) that is 24 bytes or less (20 in this case). The sha1sum command is run from a server startup script (supported from ACE 12.0.5) using the following server.conf.yaml setting (note that the spaces are very important due to it being YAML): YAML StartupScripts: EncodedHostScript: command: 'export ENCODED_VAR=`echo $HOSTNAME | sha1sum | tr -d "-" | tr -d " "` && echo UserVariables: && /bin/echo -e " script-encoded-hostname: \\x27$ENCODED_VAR\\x27"' readVariablesFromOutput: true This server.conf.yaml setting will cause the server to run the script and output the results: YAML 2022-11-10 13:04:57.226548: BIP9560I: Script 'EncodedHostScript' is about to run using command 'export ENCODED_VAR=`echo $HOSTNAME | sha1sum | tr -d "-" | tr -d " "` && echo UserVariables: && /bin/echo -e " script-encoded-hostname: \\x27$ENCODED_VAR\\x27"'. UserVariables: script-encoded-hostname:'adc83b19e793491b1c6ea0fd8b46cd9f32e592fc' 2022-11-10 13:04:57.229552: BIP9567I: Setting user variable 'script-encoded-hostname'. 2022-11-10 13:04:57.229588: BIP9565I: Script 'EncodedHostScript' has run successfully. Once the user variable is set, it can be used for the MQInput node (to ensure only matching messages are received) and the MQOutput node (to provide the correct ID for the outgoing message). The MQInput node can accept user variable references in the correlation ID field (ignore the red X): The MQOutput node will send the contents of the MQMD parser from the flow, and so this is set in the “Create Outbound Message” Compute node using ESQL, converting the string in the user variable into a binary CorrelId: SQL -- Set the CorrelId of the outgoing message to match the MQInput node. DECLARE encHost BLOB CAST("script-encoded-hostname" AS BLOB); SET OutputRoot.MQMD.CorrelId = OVERLAY(X'000000000000000000000000000000000000000000000000' PLACING encHost FROM 1); SET OutputRoot.Properties.ReplyIdentifier = OutputRoot.MQMD.CorrelId; The ReplyIdentifier field in the Properties parser overwrites the MQMD CorrelId in some cases, so both are set to ensure the ID is picked up. The “script-encoded-hostname” reference is the name of the user variable, declared as EXTERNAL in the ESQL to cause the server to read the user variable when the ESQL is loaded: DECLARE "script-encoded-hostname" EXTERNAL CHARACTER; Other sections of the ESQL set the Report options, the HTTP request identifier, and the ReplyToQ: SQL -- Store the HTTP reply identifier in the MsgId of the outgoing message. -- This works because HTTP reply identifiers are the same size as an MQ -- correlation/message ID (by design). SET OutputRoot.MQMD.MsgId = InputLocalEnvironment.Destination.HTTP.RequestIdentifier; -- Tell the backend flow to send us the MsgId and CorrelId we send it. SET OutputRoot.MQMD.Report = MQRO_PASS_CORREL_ID + MQRO_PASS_MSG_ID; -- Tell the backend flow to use the queue for our MQInput node. SET OutputRoot.MQMD.ReplyToQ = 'BACKEND.SHARED.REPLY'; Deploying the flow requires a configuration for the server.conf.yaml mentioned above, and this must include the remote default queue manager setting as well as the hostname encoding script due to only one server.conf.yaml configuration being allowed by the operator. See this combined file and an encoded form ready for CP4i deployment. Once the configurations are in place, the flow itself can be deployed using the IS-github-bar-CorrelIdClient.yaml file to the desired namespace (“cp4i” in this case): kubectl apply -n cp4i -f IS-github-bar-CorrelIdClient.yaml (or using a direct HTTP URL to the Git repo). This will create two replicas, and once the servers are running then curl can be used to verify the flows operating successfully, and the CorrelId field should alternate between requests to show both servers sending and receiving messages correctly: YAML $ curl http://http-mq-correlidclient-http-cp4i.apps.cp4i-domain/CorrelIdClient {"originalMessage": {"MsgId":"X'455648540000000000000000c6700e78d900000000000000'","CorrelId":"X'20fa1e68cb59a328f559cc306aa52df3e58ffd3200000000'","ReplyToQ":"BACKEND.SHARED.REPLY ","jsonData":{"Data":{"test":"CorrelIdClient message"}},"backendFlow":{"application":"MQBackend"} $ curl http://http-mq-correlidclient-http-cp4i.apps.cp4i-domain/CorrelIdClient {"originalMessage": {"MsgId":"X'455648540000000000000000be900e78d900000000000000'","CorrelId":"X'd7cb7b30c4775d27aabbb4997020ebafd14f775700000000'","ReplyToQ":"BACKEND.SHARED.REPLY ","jsonData":{"Data":{"test":"CorrelIdClient message"}},"backendFlow":{"application":"MQBackend"} $ curl http://http-mq-correlidclient-http-cp4i.apps.cp4i-domain/CorrelIdClient {"originalMessage":{"MsgId":"X'455648540000000000000000c6700e78d900000000000000'","CorrelId":"X'20fa1e68cb59a328f559cc306aa52df3e58ffd3200000000'","ReplyToQ":"BACKEND.SHARED.REPLY ","jsonData":{"Data":{"test":"CorrelIdClient message"}},"backendFlow":{"application":"MQBackend"} Variations Other possible solutions exist: CorrelIdClientUsingBody shows a solution storing the HTTP reply identifier in the body of the message rather than using the MsgId field of the MQMD. This avoids a situation where the same MsgId is used twice (once for the request and the second time for the reply), but requires the back-end service to copy the identifier from the request to the reply message, and not all real-life services will do this. Similar flows can be built using RFH2 headers to contain the HTTP reply identifier, with the same requirement that the back-end service copies the RFH2 information. This flow uses a separate CorrelId padding value of “11111111” instead of “00000000” used by CorrelIdClient to ensure that the flows do not collide when using the same reply queue. Like the CorrelIdClient, this solution relies on startup scripts supported in ACE 12.0.5 and later. Sha1HostnameClient is a variant of CorrelIdClient that uses a predefined user variable called “sha1sum-hostname” provided by the server in ACE 12.0.6 and later fixpacks; this eliminates the need for startup scripts to set user variables. SyncClient shows a different approach, using a single flow that waits for the reply message. No user variables are needed, and the back-end service needs only to copy the request MsgId to the CorrelId in the reply (which is the default Report option), but the client flow will block in the MQGet until the reply message is received. This is potentially a very resource-intensive way to implement request-reply messaging, as every message will block a thread for as long as it takes the back-end to reply, and each thread will consume storage while running. Summary Existing integration solutions using MQ request/reply patterns may work unchanged in the scalable container world if they implement mechanisms similar to those described above, but many solutions will need to be modified to ensure correct operation. This is especially true for integrations that rely on the per-broker listener to handle the matching of replies to clients, but solutions are possible as is shown in the example above. Further conversation in the comments or elsewhere is always welcome! Acknowledgments: Thanks to Amar Shah for creating the original ACE-with-MQ blog post on which this is based, and for editorial help.
As organizations increasingly adopt containerization for their applications, container orchestration platforms have become essential tools for managing and scaling containerized workloads. Two of the most popular container orchestration platforms today are Docker Swarm and Kubernetes.While both platforms share some similarities, they differ in architecture, scalability, high availability, container management, and learning curve. The question of which of these platforms wins the container war is a common one among developers, architects, and IT teams. This article will discuss Docker Swarm vs. Kubernetes and compare their features, strengths, and weaknesses to help you choose the best platform for your specific needs. What Is Docker Swarm? Docker Swarm is a container orchestration platform that allows you to manage a cluster of Docker hosts and containers as a single virtual system. It is built on top of Docker and provides additional features for managing a large number of containers and hosts. Using Docker Swarm, you can create a cluster of Docker hosts that work together to run and manage your containerized applications. You can easily deploy and scale applications across the cluster. Docker Swarm will automatically handle load balancing, container scheduling, and container health monitoring. Docker Swarm also provides features for rolling updates, service discovery, and network segmentation, making it a powerful tool for managing complex containerized applications. In addition, Docker Swarm simplifies the process of managing a large number of containers and hosts. It provides a centralized way to manage your entire container infrastructure. Exploring the Benefits of Docker Swarm Docker Swarm is a container orchestration docker platform that allows you to manage and scale containerized workloads. Here are some of the key benefits of using Docker Container Swarm: Simplicity: Docker Swarm is a simpler and more lightweight platform than Kubernetes, making it easier to set up and manage for smaller-scale deployments or teams with limited resources or expertise. Docker Integration: Docker Swarm integrates seamlessly with the Docker ecosystem, providing a smooth and streamlined workflow for organizations already using Docker. High Availability: Docker Swarm provides built-in features for automatic failover and self-healing, ensuring that your applications are always available and minimizing downtime. Resource Optimization: Docker Swarm allows you to optimize resource usage by allocating and managing resources more efficiently. This ensures that your applications run smoothly and cost-effectively. Portability: Docker Swarm can run on any cloud provider or on-premises infrastructure, providing you with flexibility and portability for your applications. Automation: Docker Container Swarm automates many of the tasks involved in managing microservices and scaling containerized applications, making it easier to manage large and complex deployments. Open-Source: Docker Swarm is open-source and community-driven. It is a transparent and collaborative container orchestration docker platform that is continuously evolving and improving. Docker Swarm provides a simpler and more lightweight platform to manage and scale containerized applications. This makes Docker Swarm an ideal choice for smaller-scale deployments or teams with limited resources or expertise. Its seamless integration with the Docker ecosystem and automation features make it a popular choice for organizations already using Docker. What Is Kubernetes? Kubernetes is an open-source container orchestration platform that allows teams to automate the deployment, scaling, and management of containerized applications. Kubernetes provides a platform-agnostic way to manage containerized applications. Kubernetes enables teams to deploy and manage applications across various environments, including on-premise data centers, public cloud providers, and hybrid cloud environments. Using Kubernetes, you can define and deploy containerized applications using declarative configuration files. In addition, Kubernetes will automatically handle scheduling, load balancing, and scaling of containers based on application needs and resource availability. Kubernetes also provides features such as self-healing, rolling updates, and service discovery, ensuring that your applications are always available and up-to-date. As a result, Kubernetes is a powerful tool for managing containerized applications at scale and is widely used in production environments by organizations of all sizes. Unleashing the Power of Kubernetes Kubernetes is a powerful and feature-rich container orchestration platform that offers several benefits for managing and scaling containerized workloads. Here are some of the key benefits of Kubernetes: Scalability: Kubernetes provides powerful scaling features that allow you to automatically scale your containerized applications based on demand. This confirms that your application can handle heavy traffic and workload spikes. High Availability: Kubernetes provides built-in features for automatic failover and self-healing. This ensures that your DevOps services and applications are always available, thus minimizing downtime. Resource Optimization: Kubernetes allows you to optimize resource usage by allocating and managing resources more efficiently, ensuring that your applications run smoothly and cost-effectively. Portability: Kubernetes is a platform-agnostic platform that can run on any cloud provider or on-premises infrastructure, providing you with flexibility and portability for your applications. Extensibility: Kubernetes has a large and active community that has developed a wide range of plugins, add-ons, and extensions, providing you with a rich ecosystem of tools and integrations to extend and enhance your Kubernetes deployments. Automation: Kubernetes automates many of the tasks involved in managing and scaling containerized applications, making it easier to manage large and complex deployments. Open-Source: Kubernetes is open-source and community-driven, providing you with a transparent and collaborative platform that is continuously evolving and improving. Kubernetes provides a robust and flexible platform for managing containerized applications, helping organizations to optimize resource usage, reduce downtime, streamline DevOps services, and improve scalability and portability. Docker Swarm vs. Kubernetes: Which One Fits Your Business Needs Best? Kubernetes and Docker Swarm are both popular container orchestration platforms, but there are some key differences between the two. Let’s read further on Docker Swarm vs. Kubernetes here. Architecture: Kubernetes has a more complex architecture than Docker Swarm. Kubernetes uses a master node and worker nodes to manage and run containers, while Docker Swarm uses a simpler approach with a manager node and worker nodes. Scalability: Both platforms can scale applications horizontally, but Kubernetes has more advanced scaling features, such as autoscaling, which allows for automatic scaling based on resource utilization. Docker Swarm, on the other hand, uses a more manual approach to scaling. High Availability: Kubernetes has more advanced high availability features than Docker Swarm. Kubernetes provides automatic failover and self-healing capabilities, ensuring that containers are always running and available. Docker Swarm has fewer built-in features for high availability. Container Management: Both platforms offer similar capabilities for managing containers, but Kubernetes has a more advanced feature set, including more granular control over container networking and more options for storage management. Ecosystem: Kubernetes has a larger ecosystem and community than Docker Swarm, with a wider range of third-party tools and integrations available. Learning Curve: Kubernetes has a steeper learning curve than Docker Swarm due to its more complex architecture and advanced feature set. Kubernetes is a more powerful and complex platform than Docker Swarm, with more advanced features for scalability, high availability, and container management. However, it also has a steeper learning curve and requires more configuration and setup. Docker Swarm is a simpler and easier-to-use platform but may not be as suitable for complex, large-scale deployments. Key Takeaways The choice between Docker Swarm and Kubernetes ultimately depends on the specific needs of your application and organization. It also depends on the level of expertise of the development team. Both platforms offer unique benefits and trade-offs. Docker Swarm is a more lightweight and simpler platform, while Kubernetes is a more powerful and feature-rich platform. Docker Swarm may better fit smaller-scale deployments or teams with limited resources or expertise. At the same time, Kubernetes may be more suitable for larger-scale deployments and complex applications. Regardless of which platform you choose, it is important to carefully evaluate your needs and choose the platform that best meets your requirements. By considering the strengths and weaknesses of Docker Swarm and Kubernetes, you can make an informed decision and successfully manage and scale your containerized workloads.
AWS Fargate is a serverless computing engine for containers that allows developers to run Docker containers without having to manage the underlying infrastructure. Fargate provides a scalable, secure, and cost-effective way to run containers on the cloud, making it a popular choice for modern application architectures. In this blog, we will explore the key concepts of Fargate and how they can help you build and manage containerized applications on AWS. Introduction Fargate is a compute engine for Amazon Elastic Container Service (ECS) and Amazon Elastic Kubernetes Service (EKS) that allows you to run containers without managing the underlying infrastructure. Fargate abstracts away the complexity of managing servers, clusters, and infrastructure scaling, allowing you to focus on your application code. AWS Fargate is a fully-managed container orchestration service that automates the deployment and scaling of containerized applications. It removes the need for manual infrastructure management and allows you to deploy applications faster and more efficiently. Architecture of AWS Fargate AWS Fargate is built on top of Amazon Elastic Container Service (ECS). The architecture of AWS Fargate can be divided into the following components: Container definition: A container definition is a blueprint that describes how a container should be run. It includes information such as the Docker image, CPU and memory requirements, and the command to run. Task definition: A task definition is a blueprint that describes how to run one or more containers together as a single unit. It includes the container definition, networking, and other parameters. Task: A task is an instance of a task definition that is running on AWS Fargate. It includes one or more containers and the resources required to run them. Cluster: A cluster is a logical grouping of tasks and services. It includes the AWS Fargate resources required to run them. Service: A service is a set of tasks that are running together and can be scaled up or down based on demand. How Fargate Works Fargate works by launching containers on a shared infrastructure that is managed by AWS. When you launch a container using Fargate, you specify the resources that the container needs, such as CPU and memory, and Fargate provisions the necessary resources to run the container. Fargate automatically manages the infrastructure scaling and availability of the containers, ensuring that they are always available to handle incoming traffic. It scales the infrastructure up or down based on demand and automatically replaces unhealthy containers to maintain high availability. Fargate integrates with ECS and EKS, allowing you to launch and manage your containers using the same APIs and management tools. You can use ECS or EKS to create task definitions that describe your containerized application, and Fargate takes care of launching the containers and managing the underlying infrastructure. Fargate also integrates with other AWS services, such as Amazon Elastic Container Registry (ECR) for storing and managing container images, AWS CloudFormation for infrastructure as code, and AWS Identity and Access Management (IAM) for role-based access control. Benefits of Fargate Fargate provides several benefits for running containers on AWS: No infrastructure management: With Fargate, you do not have to manage servers, clusters, or infrastructure scaling. Fargate handles all of this automatically, allowing you to focus on your application code. Scalable and flexible: Fargate can scale your containers automatically based on your application’s needs, allowing you to handle sudden spikes in traffic without manual intervention. It also provides the flexibility to run different types of applications, such as stateless, stateful, and batch-processing workloads. Secure: Fargate provides a secure environment for running your containers, isolating them from other containers running on the same infrastructure. Fargate also integrates with AWS Identity and Access Management (IAM) and Amazon Virtual Private Cloud (VPC) to provide you with additional security features. Cost-effective: With Fargate, you only pay for the resources you use, making it a cost-effective way to run containers on AWS. Fargate also eliminates the need for over-provisioning of infrastructure, reducing your operational costs. Use Cases for Fargate Fargate can be used in a variety of scenarios, including: Modern application architectures: Fargate is ideal for building modern application architectures, such as microservices and serverless applications, that require scalable and flexible infrastructure. Continuous integration and delivery (CI/CD): Fargate can be used in a CI/CD pipeline to build and deploy containerized applications automatically. Machine learning and data processing: Fargate can be used to run containerized machine learning workloads and data processing tasks that require scalable infrastructure. IoT and edge computing: Fargate can be used to run containerized workloads at the edge, providing a scalable and flexible way to process and analyze data. Hybrid cloud deployments: Fargate can be used to deploy containerized applications in hybrid cloud environments, providing a consistent way to manage containers across on-premises and cloud environments. Getting Started With Fargate To get started with Fargate, you need to create an ECS or EKS cluster and launch a task definition that describes your containerized application. AWS Fargate can be managed through the AWS Management Console or the AWS CLI. Create an Amazon Elastic Container Registry (ECR) repository: Before you can deploy containers to AWS Fargate, you need to create an Amazon ECR repository to store your Docker images. Create a task definition: A task definition is a blueprint that describes how to run one or more containers together as a single unit. Create a cluster: A cluster is a logical grouping of tasks and services. Create a service: A service is a set of tasks that are running together and can be scaled up or down based on demand. Deploy the service: To deploy the service, either use console or use the AWS CLI and run the following command: Shell aws ecs create-service --cluster [cluster-name] --service-name [service-name] --task-definition [task-definition-arn] --desired-count [desired-count] Conclusion AWS Fargate is a powerful tool for building and managing containerized applications on AWS. Its serverless approach to container orchestration eliminates the need for manual infrastructure management, making it a popular choice for modern application architectures. With Fargate, you can deploy and scale your containers quickly and easily while also maintaining a secure and cost-effective environment.
In this blog post, you will be using AWS Controllers for Kubernetes on an Amazon EKS cluster to put together a solution where HTTP requests sent to a REST endpoint exposed by Amazon API Gateway are processed by a 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 API Gateway, 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 the 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 the Getting Started with Amazon EKS – eksctl documentation. Clone this GitHub repository and change to the right directory: git clone https://github.com/abhirockzz/k8s-ack-apigw-lambda cd k8s-ack-apigw-lambda Ok, let's get started! Setup the ACK Service Controllers for AWS Lambda, API Gateway, 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 API Gateway using the apigatewayv2-chart Helm chart: RELEASE_VERSION_APIGWV2_ACK=$(curl -sL "https://api.github.com/repos/aws-controllers-k8s/apigatewayv2-controller/releases/latest" | grep '"tag_name":' | cut -d'"' -f4) helm install --create-namespace -n ack-system oci://public.ecr.aws/aws-controllers-k8s/apigatewayv2-chart "--version=${RELEASE_VERSION_APIGWV2_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 API Gateway. 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 Your Lambda, API Gateway, 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. Make sure to update the ecr-permissions.json file with the AWS_ACCOUNT_ID and AWS_REGION variables. 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 API Gateway controller. ACK API Gateway Controller Set the following environment variables: ACK_K8S_SERVICE_ACCOUNT_NAME=ack-apigatewayv2-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_apigatewayv2.json Create the IAM role: ACK_CONTROLLER_IAM_ROLE="ack-apigatewayv2-controller" ACK_CONTROLLER_IAM_ROLE_DESCRIPTION="IRSA role for ACK apigatewayv2 controller deployment on EKS cluster using Helm charts" aws iam create-role --role-name "${ACK_CONTROLLER_IAM_ROLE}" --assume-role-policy-document file://trust_apigatewayv2.json --description "${ACK_CONTROLLER_IAM_ROLE_DESCRIPTION}" Attach managed IAM policies to the IAM role: aws iam attach-role-policy --role-name "${ACK_CONTROLLER_IAM_ROLE}" --policy-arn arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator aws iam attach-role-policy --role-name "${ACK_CONTROLLER_IAM_ROLE}" --policy-arn arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess 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 the service controller Pods with IRSA environment variables. Get the list of ACK service controller deployments: export ACK_K8S_NAMESPACE=ack-system kubectl get deployments -n $ACK_K8S_NAMESPACE Restart Lambda, API Gateway, and DynamoDB Deployments: DEPLOYMENT_NAME_LAMBDA=<enter deployment name for lambda controller> kubectl -n $ACK_K8S_NAMESPACE rollout restart deployment $DEPLOYMENT_NAME_LAMBDA DEPLOYMENT_NAME_APIGW=<enter deployment name for apigw controller> kubectl -n $ACK_K8S_NAMESPACE rollout restart deployment $DEPLOYMENT_NAME_APIGW 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_" APIGW_POD_NAME=<enter Pod name for apigw controller> kubectl describe pod -n $ACK_K8S_NAMESPACE $APIGW_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 API Gateway Resources, DynamoDB table, and Deploy the Lambda Function Create API Gateway Resources In the file apigw-resources.yaml, replace the AWS account ID in the integrationURI attribute for the Integration resource. This is what the ACK manifest for API Gateway resources (API, Integration, and Stage) looks like: apiVersion: apigatewayv2.services.k8s.aws/v1alpha1 kind: API metadata: name: ack-demo-apigw-httpapi spec: name: ack-demo-apigw-httpapi protocolType: HTTP --- apiVersion: apigatewayv2.services.k8s.aws/v1alpha1 kind: Integration metadata: name: ack-demo-apigw-integration spec: apiRef: from: name: ack-demo-apigw-httpapi integrationType: AWS_PROXY integrationMethod: POST integrationURI: arn:aws:lambda:us-east-1:AWS_ACCOUNT_ID:function:demo-dynamodb-func-ack payloadFormatVersion: "2.0" --- apiVersion: apigatewayv2.services.k8s.aws/v1alpha1 kind: Stage metadata: name: demo-stage spec: apiRef: from: name: ack-demo-apigw-httpapi stageName: demo-stage autoDeploy: true description: "demo stage for http api" Create the API Gateway resources (API, Integration, and Stage) using the following command: kubectl apply -f apigw-resources.yaml 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: user annotations: services.k8s.aws/region: us-east-1 spec: attributeDefinitions: - attributeName: email attributeType: S billingMode: PAY_PER_REQUEST keySchema: - attributeName: email keyType: HASH tableName: user You can replace the us-east-1 region with your preferred region. Create a table (named user) 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-apigw-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-apigw-dynamodb-func-ack --region us-east-1 docker tag demo-apigw-dynamodb-func-ack:latest $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/demo-apigw-dynamodb-func-ack:latest docker push $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/demo-apigw-dynamodb-func-ack:latest Create an IAM execution Role for the Lambda function and attach the required policies: export ROLE_NAME=demo-apigw-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, let's add the following policy to the IAM role: aws iam put-role-policy \ --role-name "${ROLE_NAME}" \ --policy-name "dynamodb-put" \ --policy-document file://dynamodb-put.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-apigw-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-apigw-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-apigw-dynamodb-func-ack annotations: services.k8s.aws/region: us-east-1 spec: architectures: - x86_64 name: demo-apigw-dynamodb-func-ack packageType: Image code: imageURI: AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/demo-apigw-dynamodb-func-ack:latest environment: variables: TABLE_NAME: user role: arn:aws:iam::AWS_ACCOUNT_ID:role/demo-apigw-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 API Gateway Trigger Configuration Here is an example using AWS Console. Open the Lambda function in the AWS Console and click on the Add trigger button. Select API Gateway as the trigger source, select the existing API, and click on the Add button. Now you are ready to try out the end-to-end solution! Test the Application Get the API Gateway endpoint: export API_NAME=ack-demo-apigw-httpapi export STAGE_NAME=demo-stage export URL=$(kubectl get api/"${API_NAME}" -o=jsonpath='{.status.apiEndpoint}')/"${STAGE_NAME}"/demo-apigw-dynamodb-func-ack" Invoke the API Gateway endpoint: curl -i -X POST -H 'Content-Type: application/json' -d '{"email":"user1@foo.com","name":"user1"}' $URL curl -i -X POST -H 'Content-Type: application/json' -d '{"email":"user2@foo.com","name":"user2"}' $URL curl -i -X POST -H 'Content-Type: application/json' -d '{"email":"user3@foo.com","name":"user3"}' $URL curl -i -X POST -H 'Content-Type: application/json' -d '{"email":"user4@foo.com","name":"user4"}' $URL 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 user Clean Up After you have explored the solution, you can clean up the resources by running the following commands: Delete API Gateway resources, DynamoDB table, and the Lambda function: kubectl delete -f apigw-resources.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 apigw 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, API Gateway integration, 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!
Do you still write lengthy Dockerfiles describing every step necessary to build a container image? Then, buildpacks come to your rescue! Developers simply feed them an application, buildpacks do their magic, and turn it into a fully functional container ready to be deployed on any cloud. But how exactly does the magic happen? And what should you do if the resulting container performance doesn’t meet the business requirements? This article will look under the hood of buildpacks to see how they operate and give tips on optimizing the default settings to reach better performance outcomes. What Are Buildpacks? A buildpack turns the application source code into a runnable production-ready container image. Buildpacks save time and effort for developers because there’s no need to configure the image and manually manage dependencies through a Dockerfile. Heroku was the first company to develop buildpacks in 2011. Since then, many other companies (Cloud Foundry, Google, etc.) have adopted the concept. In 2018, Heroku partnered with Pivotal to create the Cloud Native Buildpacks project, encompassing modern standards and specifications for container images, such as the OCI format. The project is part of the Cloud Native Computing Foundation (CNCF). Paketo buildpacks, which we will use in this article, is an open-source project backed by Cloud Foundry and sponsored by VMware. It implements Cloud Native Buildpacks specifications and supports the most popular languages, including Java. Containers produced with Paketo buildpacks can run on any cloud. How Buildpacks Work Buildpacks operate in two phases: detect and build. 1. The Detect Phase During the detection phase, the buildpack analyzes the source code looking for indicators of whether or not it should be applied to the application. In other words, a group of buildpacks is tested against the source code, and the first group deemed fit for the code is selected for building the app. After the buildpack detects the necessary indicators, it returns a contract of what is required for creating an image and proceeds to the build phase. 2. The Build Phase During the build phase, the buildpack transforms the codebase, fulfilling the contract requirements composed earlier. It provides the build-time and runtime environment, downloads necessary dependencies, compiles the code if needed, and sets the entry points and startup scripts. Builders A builder is a combination of components required for building a container image: Buildpacks, sets of executables that analyze the code and provide a plan for building and running the app; Stack consists of two images: the build image and the run image. The build image provides the built environment (a containerized environment where build packs are executed), the run image offers the environment for the application image during runtime; Lifecycle manages the buildpack execution and assembles the resulting artifact into a final image. Therefore, one builder can automatically detect and build different applications. Buildpacks Offer a Variety of JVMs — How to Choose? Paketo buildpacks use Liberica JVM by default. Liberica is a HotSpot-based Java runtime supported by a major OpenJDK contributor and recommended by Spring. It provides JDK and JRE for all LTS versions (8, 11, 17), the current version, and Liberica Native Image Kit (NIK), a GraalVM-based utility for converting JVM-based apps into native images with an accelerated startup. Native images are beneficial when you need to avoid cold starts in AWS. But the buildpacks support several Java distributions, which can be used instead of the default JVM: Adoptium Alibaba Dragonwell Amazon Corretto Azul Zulu BellSoft Liberica (default) Eclipse OpenJ9 GraalVM Oracle JDK Microsoft OpenJDK SapMachine If you want to switch JVMs, you have to keep in mind several nuances: Alibaba Dragonwell, Amazon Corretto, GraalVM, Oracle JDK, and Microsoft OpenJDK offer only JDK. The resulting container will be twice as big as the JRE-based one; Adoptium provides JDK and JRE for Java 8 and 11 and only JDK for Java 16+; Oracle JDK provides only Java 17. Another important consideration: buildpacks facilitate and accelerate deployment, but if you are dissatisfied with container performance or seek to improve essential KPIs (throughput, latency, or memory consumption), you have to tune the JVM yourself. For more details, see the section Configuring the JVM below. For instance, Eclipse OpenJ9 based on the OpenJ9 JVM may demonstrate better performance than HotSpot in some cases because HotSpot comes with default settings, and OpenJ9 is already tuned. Adding a few simple parameters will give you equal or superior performance with HotSpot. How to Use Paketo Buildpacks Let’s build a Java container utilizing a Paketo buildpack. First, make sure Docker is up and running. If you don’t have it, follow these instructions to install Docker Desktop for your system. The next step is to install pack CLI, a Command Line Interface maintained by Cloud Native Buildpack that can be used to work with buildpacks. Follow the guide to complete the installation for your platform (macOS, Linux, and Windows are supported). Pack is one of the several available tools. Spring Boot developers, for instance, can look into Spring Boot Maven Plugin or Spring Boot Gradle Plugin. We will use Paketo sample applications, so run the following command: git clone https://github.com/paketo-buildpacks/samples && cd samples Alternatively, utilize your own demo app. Make Paketo Base builder the default builder: pack config default-builder paketobuildpacks/builder:base To build an image from source with Maven, run pack build samples/java \ --path java/maven --env BP_JVM_VERSION=17 Java example images should return {"status":"UP"} from the actuator health endpoint: docker run --rm --tty --publish 8080:8080 samples/java curl -s http://localhost:8080/actuator/health | jq . It is also possible to build an image from a compiled artifact. The following archive formats are supported: executable JAR, WAR, or distribution ZIP. To compile an executable JAR and build an image using pack, run cd java/maven ./mvnw package pack build samples/java \ --path ./target/demo-0.0.1-SNAPSHOT.jar Extracting a Software Bill of Materials Software supply chains consist of numerous libraries, tools, and processes used to develop and run applications. It is often hard to trace the origin of all software components in a software product, increasing the risk of nested vulnerabilities. A software bill of materials (SBOM) lists all library dependencies utilized to build a software artifact. It is similar to a traditional bill of materials, which summarizes the raw materials, parts, components, and exact quantities required to manufacture a product. SBOMs enable the developers to monitor the version of software components, integrate security patches promptly, and keep vulnerable libraries out. Buildpacks also enable the developers to see an SBOM for their image. Run the following command to extract the SBOM for the samples/java image built previously: pack sbom download samples/java --output-dir /tmp/samples-java-sbom After that, you can browse the folder. SBOMs are presented in JSON format. To list all .json files in the folder, run the following: find /tmp/samples-java-sbom -name "*.json" /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_executable-jar/sbom.cdx.json /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_executable-jar/sbom.syft.json /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_spring-boot/helper/sbom.syft.json /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_spring-boot/spring-cloud-bindings/sbom.syft.json /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_bellsoft-liberica/jre/sbom.syft.json /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_bellsoft-liberica/helper/sbom.syft.json /tmp/samples-java-sbom/layers/sbom/launch/sbom.legacy.json /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_ca-certificates/helper/sbom.syft.json Now, you can open the file with any text editor. For instance, if you have Visual Studio Code installed, run the following: code /tmp/samples-java-sbom/layers/sbom/launch/paketo-buildpacks_bellsoft-liberica/jre/sbom.syft.json You will get the following output: { "Artifacts": [ { "ID": "1f2d01eeb13b5894", "Name": "BellSoft Liberica JRE", "Version": "17.0.6", "Type": "UnknownPackage", "FoundBy": "libpak", "Locations": [ { "Path": "buildpack.toml" } ], "Licenses": [ "GPL-2.0 WITH Classpath-exception-2.0" ], "Language": "", "CPEs": [ "cpe:2.3:a:oracle:jre:17.0.6:*:*:*:*:*:*:*" ], "PURL": "pkg:generic/bellsoft-jre@17.0.6?arch=amd64" } ], "Source": { "Type": "directory", "Target": "/layers/paketo-buildpacks_bellsoft-liberica/jre" }, "Descriptor": { "Name": "syft", "Version": "0.32.0" }, "Schema": { "Version": "1.1.0", "URL": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-1.1.0.json" } } Configuring the JVM The BellSoft Liberica Buildpack provides the newest patch updates of Java versions supported in the buildpack. The buildpack uses the latest LTS version by default. If you want to use another Java version, use the BP_JVM_VERSION environment variable. For instance, BP_JVM_VERSION=11 will install the newest release of Liberica JDK and JRE 11. In addition, you can change the JDK type. The buildpack uses JDK at build-time and JRE at runtime. Specifying the BP_JVM_TYPE=JDK option will force the buildpack to use JDK at runtime. The BP_JVM_JLINK_ENABLED option runs the jlink tool with Java 9+, which cuts out a custom JRE. If you deploy a Java application to an application server, the buildpack uses Apache Tomcat by default. You can select another server (TomEE or Open Liberty). For instance, run the following command to switch to TomEE: pack build samples/war -e BP_JAVA_APP_SERVER=tomee You can configure JVM at runtime by using the JAVA_TOOL_OPTIONS environment variable. For instance, you can configure garbage collection, number of threads, memory limits, etc., to reach optimal performance for your specific needs: docker run --rm --tty \ --env JAVA_TOOL_OPTIONS='-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40' \ --env BPL_JVM_THREAD_COUNT=100 \ samples/java The whole list of JVM configuration options can be found on the Liberica Buildpack page. Conclusion As you can see, buildpacks are great automation tools saving developers time. But it would help if you used them wisely, or there’s a risk you will get a cat in the sack. Our general recommendation is to define the KPIs and adjust JVM settings accordingly. What can you do if you are not happy with the size of the resulting image? After all, it’s not possible to change the base OS image utilized by buildpacks. One option is to migrate to the Native Image to optimize resource consumption. Another alternative is to manually build containers and switch to a smaller OS image, such as Alpine or Alpaquita Linux. The latter supports two libc implementations (optimized musl and glibc) and comes with numerous performance and security enhancements.
Yitaek Hwang
Software Engineer,
NYDIG
Abhishek Gupta
Principal Developer Advocate,
AWS
Alan Hohn
Director, Software Strategy,
Lockheed Martin
Marija Naumovska
Product Manager,
Microtica