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
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.mediuminstances. 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
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
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
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"
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
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
Deploy the stack:
aws cloudformation create-stack \
--stack-name project-bedrock \
--template-body file://infrastructure.yaml \
--capabilities CAPABILITY_NAMED_IAM
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
Test the connection. If you see the internal IP addresses of your Kubernetes services, the handshake is complete.
kubectl get svc
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
Deploy with Helm:
We will use Helm to install the microservices defined in this repository.
helm install retail-app ./helm
[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
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
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
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
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)