DEV Community

Nnamdi Felix Ibe
Nnamdi Felix Ibe

Posted on

100 Days of DevOps and Cloud (AWS), Day 16: A Load Balancer in Ten Lines of Nginx, and Least-Privilege IAM

You do not need a cloud load balancer to load balance. Ten lines of Nginx will spread traffic across a pool of servers, and understanding those ten lines teaches you what the managed services are quietly doing for you. Day 16 was that, plus setting up IAM the way it is meant to be done, with least privilege instead of a blank check.

One Linux task, one AWS task. Turn Nginx into a load balancer in front of three app servers, then create an IAM user, group, and read-only policy. The tasks come from the KodeKloud Engineer platform.

Nginx load balancer: an upstream block and a proxy_pass

On the load balancer host, install Nginx and get it running:

yum install -y nginx
systemctl enable nginx
systemctl start nginx
Enter fullscreen mode Exit fullscreen mode

On each app server: confirm the port Apache is running on

sudo ss -tulp | grep httpd
# Alternative: systemctl status httpd
Enter fullscreen mode Exit fullscreen mode

Then the config that turns it into a load balancer:

# On the load balancer: edit the Nginx config to set up upstream load balancing
vi /etc/nginx/nginx.conf
Enter fullscreen mode Exit fullscreen mode
# Define the pool of backend servers
upstream app_servers {
    server stapp01:5000;
    server stapp02:5000;
    server stapp03:5000;
}

server {
    listen 80;
    location / {
        proxy_pass http://app_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
Enter fullscreen mode Exit fullscreen mode

The upstream block names a group of backend servers. The proxy_pass points incoming requests at that group. That is the load balancer. By default Nginx spreads requests across the pool in round-robin order, one server after another in turn, and if you want a different strategy like least-connections or IP hash, it is a single extra directive. The proxy_set_header lines matter more than they look. Without them, every backend sees the request as coming from the load balancer itself, so these pass along the client's real IP and protocol, which is what keeps your backend logs and any IP-based logic honest.

nginx -t                 # test the config
systemctl reload nginx   # apply it without dropping connections
curl http://stlb01:80
Enter fullscreen mode Exit fullscreen mode

Same discipline as Day 15, test, then reload. Reload rather than restart here, so the new config applies without cutting live connections.

IAM: grant exactly what is needed, nothing more

The AWS task was IAM done the right way, a user, a group, and a policy that grants only what is needed:

aws iam create-user --user-name Bob
aws iam create-group --group-name Admins
Enter fullscreen mode Exit fullscreen mode

Then the policy document:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "ec2:DescribeInstances",
      "ec2:DescribeImages",
      "ec2:DescribeTags",
      "ec2:DescribeSnapshots"
    ],
    "Resource": "*"
  }]
}
Enter fullscreen mode Exit fullscreen mode
aws iam create-policy \
  --policy-name ec2-readonly-policy \
  --policy-document file://policy.json

aws iam list-policies --scope Local
Enter fullscreen mode Exit fullscreen mode

This is least privilege in practice. The policy allows four specific read-only actions, the Describe calls, and nothing else. The lazy version would be "Action": "ec2:*", which hands over full control of EC2 for what is supposed to be read-only access. Do not do that. Grant the exact actions the role needs and widen only when a real need shows up. The "Resource": "*" looks broad, but most EC2 Describe actions do not support resource-level restrictions, so it is expected here, the actions are already the tight part.

One small syntax trap. The --policy-document value is file://policy.json, with the double slash. Miss it, and the CLI treats your filename as a literal policy string and fails.

The task stopped at creating these pieces. To actually put them to work, you take two more steps, attach the policy to the group with attach-group-policy, and add the user to the group with add-user-to-group, so Bob inherits the read-only access through the group. Managing permissions at the group level instead of per user is the habit that keeps IAM sane as a team grows.

Where this leaves us

Both tasks were about doing the fundamental version properly. A load balancer you build yourself teaches you what the managed one abstracts away. An IAM policy scoped to four actions teaches you the security posture that ec2:* quietly throws away. This whole first stretch of the challenge has been exactly that, the boring, correct version of each skill, because that is the version that holds up when it counts.

So here is the Day 16 question. Would you rather reach for the broad, convenient option and hope it never bites you, or build the tight version now while the stakes are still just a lab?

Day 16 down. Eighty-four to go.

Top comments (0)