DEV Community

Cover image for How to Deploy Microservices App With EKS (And Automate the Full Pipeline with AWS CloudFormation & GitHub Actions)

How to Deploy Microservices App With EKS (And Automate the Full Pipeline with AWS CloudFormation & GitHub Actions)

In production environments for SaaS platforms, manual management is a recipe for disaster. DevOps and Cloud engineering teams do not just build systems. They build the automation that manages the systems.

In this tutorial, you will build the cloud infrastructure for an online retail store using a microservice system. This retail store is split into five independent key players;

  • UI: The frontend store.
  • Catalog: The product inventory.
  • Cart: The user's shopping session.
  • Orders: The processing logic.
  • Checkout: The payment gateway interface.

You will not just click buttons. You will define the network, the cluster, and the pipelines as code using CloudFormation and GitHub Actions

Architecture Diagram

3-tier application diagram

The Cost of the Cloud: Pricing & Calculator

Before you write a single line of code, you need to estimate the cost. One of the pillars of the AWS Well-Architected Framework is Cost Optimization.

As a cloud engineer, you must predict the bill. You can use the AWS Pricing Calculator to estimate the monthly run cost of this project.

1. The Visible Costs

Open the calculator and search for Amazon EKS.

  • Control Plane: AWS charges approximately $0.10 per hour for the cluster itself. This is roughly $73.00/month.

Next, search for Amazon EC2.

  • Worker Nodes: You will need compute power for your microservices. For a small production cluster, you might select t3.medium instances. If you run 2 nodes, that is roughly $60.00/month (depending on the region).

2. The Hidden Costs (The "Gotchas")

This is where many engineers get surprised. The network is not free.

  • NAT Gateways: In this architecture, you will deploy NAT Gateways so your private nodes can download updates from the internet securely.

    • Hourly Charge: You pay for every hour the gateway exists.
    • Data Processing Fee: You pay roughly $0.045 per GB for data that passes through it. If your app pulls heavy images constantly, this scales up fast.
  • Load Balancers (ALB/NLB): To expose your UI to the world, you use an Application Load Balancer. You pay an hourly rate plus a fee for "LCU-hours" (capacity units based on traffic).

  • Elastic Network Interfaces (ENI): EKS places a specialized network interface on your nodes for every Pod. While the ENI itself is often free, the Cross-AZ traffic is not. If Service A in Zone 1 talks to Service B in Zone 2, you pay data transfer fees.

[Screenshot: The AWS Pricing Calculator summary page showing a line item for EKS, EC2, and VPC NAT Gateways]

Prerequisites

  • AWS account: If you don't have one, you can create a new account here.
  • Basic knowledge of networking on AWS: for a refresher on the core concepts of a Virtual Private Cloud (VPC) , review the official Amazon VPC documentation.
  • Beginner understanding of containerization and container orchestration: Check out the Kubernetes Basics tutorial to get up to speed.
  • GitHub Account: For your code and automation pipelines.

Phase 1: The Network Foundation

Your first task is to build the land where your city will live. This is the Virtual Private Cloud (VPC).

You will create a CloudFormation template named infrastructure.yaml.

This architecture is robust. It consists of:

  • 2 Public Subnets: One in each Availability Zone (AZ) for the Load Balancers and NAT Gateways.
  • 4 Private Subnets:
    • 2 for your Worker Nodes (where the apps live).
    • 2 for your Databases (RDS).
  • Gateways: An Internet Gateway for public traffic and 2 NAT Gateways for private outbound traffic.
  • Route Tables: Three separate tables to control traffic flow.

Defining the VPC and Subnets

Add this code to CustomVPC.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  This template provisions the CORE requirements for Project Bedrock:
  A VPC with public/private subnets, gateways, route tables,
  essential EKS IAM roles, and necessary Security Groups for the EKS cluster stack.

Parameters:
  VpcCIDR:
    Description: The CIDR block for the VPC (e.g., 10.0.0.0/16).
    Type: String
    Default: 10.0.0.0/16

  SubnetCIDR:
    Description: The CIDR block for the VPC (e.g., 10.0.0.0/16).
    Type: String
    Default: 10.0.0.0/24

  InstanceType: 
    Description: The EC2 instance type (e.g., t2.micro). 
    Type: String 
    Default: t3.micro 
    AllowedValues: 
      - t3.micro 
      - t3.small 
      - t3.medium 
    ConstraintDescription: Must be a valid EC2 instance type.

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-VPC"

# Attaching Internet Gateways
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-IGW"

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref InternetGateway

# Public Subnets
  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: 'true'
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnetA"
        # Tag for ALB discovery
        - Key: kubernetes.io/role/elb
          Value: '1'
        - Key: kubernetes.io/cluster/${AWS::StackName}-EKSCluster
          Value: shared

  PublicSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: 'true'
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnetB"
        - Key: kubernetes.io/role/elb
          Value: '1'
        - Key: kubernetes.io/cluster/${AWS::StackName}-EKSCluster
          Value: shared

# Private Subnets 
  PrivateSubnetA1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.10.0/24
      MapPublicIpOnLaunch: 'false'
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnetA1"
        # Tag for internal ALB discovery
        - Key: kubernetes.io/role/internal-elb
          Value: '1'
        - Key: kubernetes.io/cluster/${AWS::StackName}-EKSCluster
          Value: shared

  PrivateSubnetA2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.11.0/24
      MapPublicIpOnLaunch: 'false'
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnetA2"
        - Key: kubernetes.io/role/internal-elb
          Value: '1'
        - Key: kubernetes.io/cluster/${AWS::StackName}-EKSCluster
          Value: shared

  PrivateSubnetB1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.20.0/24
      MapPublicIpOnLaunch: 'false'
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnetB1"
        - Key: kubernetes.io/role/internal-elb
          Value: '1'
        - Key: kubernetes.io/cluster/${AWS::StackName}-EKSCluster
          Value: shared

  PrivateSubnetB2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.21.0/24
      MapPublicIpOnLaunch: 'false'
      AvailabilityZone: !Select [ 1, !GetAZs '' ]
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnetB2"
        - Key: kubernetes.io/role/internal-elb
          Value: '1'
        - Key: kubernetes.io/cluster/${AWS::StackName}-EKSCluster
          Value: shared
Enter fullscreen mode Exit fullscreen mode

2. Gateways and Routing

Now we append the networking logic. We need NAT Gateways for the private subnets to reach the internet securely, and Route Tables to direct the traffic.

Append this to CustomVPC.yaml:

# NAT GATEWAYS

# Elastic IPs
  MyNATGateway1EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-NATGateway1-EIP"

  MyNATGateway2EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: VPC
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-NATGateway2-EIP"

# NATGATEway Actual Resources
  MyNATGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt MyNATGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnetA
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-NATGateway1"

  MyNATGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt MyNATGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnetB
      Tags:
        - Key: Name
          Value: !Sub "${AWS::StackName}-NATGateway2"

# Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: PublicRouteTable

  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable

  PublicSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetB
      RouteTableId: !Ref PublicRouteTable

  PrivateRouteTableA:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: PrivateRouteTableA

  PrivateRouteA:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref MyNATGateway1
      RouteTableId: !Ref PrivateRouteTableA

  PrivateSubnetA1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableA
      SubnetId: !Ref PrivateSubnetA1

  PrivateSubnetA2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableA
      SubnetId: !Ref PrivateSubnetA2

  PrivateRouteTableB:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: PrivateRouteTableB

  PrivateRouteB:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref MyNATGateway2
      RouteTableId: !Ref PrivateRouteTableB

  PrivateAppSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableB
      SubnetId: !Ref PrivateSubnetB1

  PrivateDBSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableB
      SubnetId: !Ref PrivateSubnetB2
Enter fullscreen mode Exit fullscreen mode

3. Security Groups and Roles

We need to define Security Groups (firewalls) and IAM Roles (permissions). We also define two EC2 instances to act as bastions or test servers.

Append this to CustomVPC.yaml:

# ALB Security Group     
  MyALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP, HTTPS, and SSH traffic
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0 # Allows HTTP traffic
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0 # Allows HTTPS traffic
      Tags:
        - Key: Name
          Value: MyALBSecurityGroup

# SSH Security Group for Maintenance
  MySSHSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH traffic
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: MySSHSecurityGroup

# Server Security Group
  MyServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP and HTTPS traffic
      VpcId: !Ref MyVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          SourceSecurityGroupID: !Ref MySSHSecurityGroup
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupID: !Ref MyALBSecurityGroup
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          SourceSecurityGroupID: !Ref MyALBSecurityGroup
      Tags:
        - Key: Name
          Value: MySecurityGroup

# My EC2 instance
  MyEC2InstanceA:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: "ami-0a716d3f3b16d290c" # Replace with the desired AMI
      SubnetId: !Ref PublicSubnetA
      KeyName: "InnovateMart" # Replace with name of your key pair
      SecurityGroupIds:
      - !Ref MyServerSecurityGroup
      - !Ref MySSHSecurityGroup
      Tags:
        - Key: Name
          Value: MyInstance

  MyEC2InstanceB:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: "ami-0a716d3f3b16d290c" # Replace with the desired AMI
      SubnetId: !Ref PublicSubnetB
      KeyName: "InnovateMart"
      SecurityGroupIds:
      - !Ref MyServerSecurityGroup
      - !Ref MySSHSecurityGroup
      Tags:
        - Key: Name
          Value: MyInstance

# EKS Cluster Role
  EKSClusterRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${AWS::StackName}-EKSClusterRole"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: eks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEKSClusterPolicy

# EKS Node Role
  EKSNodeRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${AWS::StackName}-EKSNodeRole"
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
        - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
Enter fullscreen mode Exit fullscreen mode

4. Outputs

Finally, we export the IDs of these resources. This is crucial because our second stack (Phase 2) needs to "find" the VPC and Subnets we just created.

Append this to CustomVPC.yaml:

Outputs:
  VpcId:
    Description: The ID of the VPC
    Value: !Ref MyVPC
    Export:
      Name: !Sub ${AWS::StackName}-MyVPC

  PublicSubnetIDs:
    Description: A comma-separated list of public subnet IDs
    Value: !Join
      - ','
      - - !Ref PublicSubnetA
        - !Ref PublicSubnetB
    Export:
      Name: !Sub "${AWS::StackName}-PublicSubnetIDs"

  PrivateSubnetIDs:
    Description: A comma-separated list of private subnet IDs for EKS nodes
    Value: !Join
      - ','
      - - !Ref PrivateSubnetA1
        - !Ref PrivateSubnetA2
        - !Ref PrivateSubnetB1
        - !Ref PrivateSubnetB2
    Export:
      Name: !Sub "${AWS::StackName}-PrivateSubnetIDs"

  MyDBSecurityGroup:
    Description: The name of the DB Security Group cluster
    Value: !Ref MyServerSecurityGroup
    Export:
      Name: !Sub "${AWS::StackName}-MyDBSecurityGroup"

  MyALBSecurityGroup:
    Description: The name of the ALB Security Group cluster
    Value: !Ref MyALBSecurityGroup
    Export:
      Name: !Sub "${AWS::StackName}-MyALBSecurityGroup"

  MySSHSecurityGroup:
    Description: The name of the ALB Security Group cluster
    Value: !Ref MySSHSecurityGroup
    Export:
      Name: !Sub "${AWS::StackName}-MySSHSecurityGroup"

  MyServerSecurityGroup:
    Description: The name of the ALB Security Group cluster
    Value: !Ref MyServerSecurityGroup
    Export:
      Name: !Sub "${AWS::StackName}-MyServerSecurityGroup"

  EKSClusterRoleArn:
    Description: The ARN of the IAM role for the EKS cluster
    Value: !GetAtt EKSClusterRole.Arn
    Export:
      Name: !Sub "${AWS::StackName}-EKSClusterRoleArn"

  EKSNodeRoleArn:
    Description: The ARN of the IAM role for the EKS nodes
    Value: !GetAtt EKSNodeRole.Arn
    Export:
      Name: !Sub "${AWS::StackName}-EKSNodeRoleArn"
Enter fullscreen mode Exit fullscreen mode

To build this foundation, run the following command. We will name the stack bedrock-vpc.

aws cloudformation create-stack \
  --stack-name bedrock-vpc \
  --template-body file://CustomVPC.yaml \
  --capabilities CAPABILITY_NAMED_IAM
Enter fullscreen mode Exit fullscreen mode

Wait for this stack to reach CREATE_COMPLETE status before moving to Phase 2.

Phase 2: The Cluster and Nodes (The House)

Now that the land is prepped, we build the house. You will create a file named EKSCluster.yaml.

This template uses the Fn::ImportValue function. It looks for the exports from your bedrock-vpc stack to know where to place the servers. It creates the Control Plane (Brain), the Node Group (Workers), and a Read-Only Developer User.


File: EKSCluster.yaml

AWSTemplateFormatVersion: '2010-09-09'
Description: >
  This template provisions the EKS Cluster and a Managed Node Group
  by importing resources from the VPC stack.

Parameters:
  VPCStackName:
    Type: String
    Description: >
      The name of the CloudFormation stack that created the VPC

  ClusterVersion:
    Type: String
    Default: '1.29'
    Description: The Kubernetes version for the EKS cluster.

  InstanceType: 
    Description: The EC2 instance type for the nodes (e.g., t3.medium). 
    Type: String 
    Default: t3.medium

Resources:
  # 1. EKS Cluster (The "Brain")

  EKSCluster:
    Type: AWS::EKS::Cluster
    Properties:
      Name: !Sub "${VPCStackName}-EKSCluster"
      Version: !Ref ClusterVersion

      # This is where we import the VPC info
      RoleArn: !ImportValue
        Fn::Sub: "${VPCStackName}-EKSClusterRoleArn"
      ResourcesVpcConfig:
        SubnetIds:
          - !Select [ 0, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PublicSubnetIDs" } ] ]
          - !Select [ 1, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PublicSubnetIDs" } ] ]
          - !Select [ 0, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
          - !Select [ 1, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
          - !Select [ 2, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
          - !Select [ 3, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]

        # This tells EKS which SGs to use for its own networking
        SecurityGroupIds:
          - !ImportValue
            Fn::Sub: "${VPCStackName}-MyServerSecurityGroup"
        EndpointPublicAccess: true
        EndpointPrivateAccess: false

  # 2. EKS Node Group (The "Workers")

  EKSNodeGroup:
    Type: AWS::EKS::Nodegroup
    Properties:
      ClusterName: !Ref EKSCluster
      NodegroupName: !Sub "${VPCStackName}-NodeGroup"
      InstanceTypes:
        - !Ref InstanceType
      NodeRole: !ImportValue
        Fn::Sub: "${VPCStackName}-EKSNodeRoleArn"
      # Workers live in your private subnets
      Subnets:
        - !Select [ 0, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
        - !Select [ 1, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
        - !Select [ 2, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
        - !Select [ 3, !Split [ ',', !ImportValue { Fn::Sub: "${VPCStackName}-PrivateSubnetIDs" } ] ]
      ScalingConfig:
        MinSize: 2
        DesiredSize: 2
        MaxSize: 4
      RemoteAccess:
        Ec2SshKey: "InnovateMart"
        # This SG must allow SSH access
        SourceSecurityGroups:
          - !ImportValue
            Fn::Sub: "${VPCStackName}-MySSHSecurityGroup"

  ReadOnlyDevUser:
    Type: AWS::IAM::User
    Properties:
      UserName: dev-readonly-user

  ReadOnlyDevUserKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref ReadOnlyDevUser

Outputs:
  EKSClusterName:
    Description: The name of the EKS cluster
    Value: !Ref EKSCluster
    Export:
      Name: !Sub "${AWS::StackName}-EKSClusterName"

  EKSClusterEndpoint:
    Description: The endpoint for the EKS cluster
    Value: !GetAtt EKSCluster.Endpoint
    Export:
      Name: !Sub "${AWS::StackName}-EKSClusterEndpoint"

  ReadOnlyDevUserARN:
    Description: "ARN of the read-only developer user"
    Value: !GetAtt ReadOnlyDevUser.Arn

  ReadOnlyDevUserAccessKey:
    Description: "Access key for the read-only user"
    Value: !Ref ReadOnlyDevUserKey

  ReadOnlyDevUserSecretKey:
    Description: "Secret key for the read-only user"
    Value: !GetAtt ReadOnlyDevUserKey.SecretAccessKey
Enter fullscreen mode Exit fullscreen mode

Deploy the stack:

aws cloudformation create-stack \
  --stack-name project-bedrock \
  --template-body file://infrastructure.yaml \
  --capabilities CAPABILITY_NAMED_IAM
Enter fullscreen mode Exit fullscreen mode

Phase 3: The Handshake

Your cluster is alive, but your local machine doesn't know how to talk to it yet. You need to establish a secure handshake.

Run the update command to generate your local configuration:

aws eks update-kubeconfig \
  --region us-east-1 \
  --name RetailStoreCluster
Enter fullscreen mode Exit fullscreen mode

Test the connection. If you see the internal IP addresses of your Kubernetes services, the handshake is complete.

kubectl get svc
Enter fullscreen mode Exit fullscreen mode

Phase 4: Deploying the Application

You need the actual retail store application code.

Clone the Repository:

You will use the official AWS sample retail app. Run this command in your working directory:

git clone https://github.com/aws-containers/retail-store-sample-app.git
cd retail-store-sample-app
Enter fullscreen mode Exit fullscreen mode

Deploy with Helm:

We will use Helm to install the microservices defined in this repository.

helm install retail-app ./helm
Enter fullscreen mode Exit fullscreen mode

[Screenshot: Terminal output showing the successful Helm deployment of UI, Cart, and Catalog services]

Phase 5: Developer Access (RBAC)

In the EKSCluster.yaml stack, you created an IAM User named dev-readonly-user.

Now you must configure Kubernetes to recognize this user.

Create the Folder:

Inside the retail-store-sample-app folder you just cloned, create a new directory named k8s.

mkdir k8s
Enter fullscreen mode Exit fullscreen mode

Create the RBAC Files:

You will place your Role and RoleBinding YAML files inside this k8s folder. These files map the AWS IAM user to a Kubernetes Role with limited permissions.

Phase 6: Automation (CI/CD)

The final piece is setting up GitHub Actions to watch this repository.

Create the file:

.github/workflows/deploy.yml in your project root.

name: Deploy to EKS
on:
  push:
    branches: [ "main" ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Update Kubeconfig
        run: aws eks update-kubeconfig --name bedrock-vpc-EKSCluster

      - name: Deploy
        run: helm upgrade --install retail-app ./helm
Enter fullscreen mode Exit fullscreen mode

Phase 7: Secure Internet Access (Ingress)

Currently, your retail store works, but it's hidden inside your VPC.

There is no front door for customers to enter from the public internet.

This is where your ingress.yaml file comes in.

Install the Controller:

Before Kubernetes can understand what an Ingress is, it needs a controller. You need to install the AWS Load Balancer Controller into your cluster.

This controller listens for Ingress creation requests and automatically creates the corresponding AWS Application Load Balancer (ALB).

Create the Ingress File:

Place your ingress.yaml file inside the k8s folder created in Phase 5.

Example command to verify file placement:

ls k8s/ingress.yaml
Enter fullscreen mode Exit fullscreen mode

Apply the Configuration:

Unlike the Helm deployment, this file is applied manually (or added to your CI/CD pipeline).

kubectl apply -f k8s/ingress.yaml
Enter fullscreen mode Exit fullscreen mode

Once you apply the configuration, the Controller detects the new Ingress resource and provisions an AWS Application Load Balancer in the public subnets created in Phase 1.

You can then point your domain (e.g., store.example.com) to this Load Balancer using Route 53.

Top comments (0)