Almost everything useful in infrastructure is a layer of indirection. Something points at something else, and the pointing is what buys you the flexibility. Day 24 was two versions of that idea: a Git branch pointing at a commit, and a load balancer pointing at a pool of servers.
One Git task, one AWS task. Create a branch and publish it with tracking, then put an Application Load Balancer in front of two EC2 instances. The tasks come from the KodeKloud Engineer platform.
Branches: 41 bytes, not a copy
The mental model most people arrive with is that a branch is a copy of the codebase. It is not, and once that clicks, a lot of Git stops feeling expensive.
A branch is a file containing one commit hash. That is the whole thing. Creating a branch writes forty-odd bytes to disk, which is why it is instant on a repository of any size, and why branching freely is normal practice rather than something you ration.
git branch # local branches, * marks the current one
git branch -a # include remote-tracking branches
git branch -v # show the last commit on each
Creating one has a catch that gets everybody once:
# Creates the branch — and leaves you exactly where you were
git branch feature-x
# Creates AND switches
git checkout -b feature-x # classic
git switch -c feature-x # modern, Git 2.23 and later
git branch <name> creates without switching. You make the branch, carry on working, and every commit lands on the old branch. Nothing warns you. git branch --show-current before you commit is a cheap habit that catches it.
The switch and restore commands were split out of checkout in Git 2.23 for a good reason: checkout did two completely unrelated jobs, moving between branches and discarding local file changes. One typo could destroy work. The newer commands each do one thing, and there is no reason to keep reaching for the old one.
Then publish it:
git push -u origin feature-x
The -u sets the upstream tracking reference. Skip it and every later git push on that branch stops to tell you it does not know where to go.
One last thing worth knowing before you need it: git branch -d refuses to delete a branch with unmerged commits, while -D forces it. That refusal is a safety net. If -d complains, go and look at why before reaching for the capital letter.
The ALB: three parts, and all three have to exist
An Application Load Balancer is not one resource. It is three, and the most common first failure is building two of them and wondering why nothing responds.
The target group is the pool of backends plus the health check that decides who is in it. The load balancer is the thing that accepts traffic. The listener is the rule that connects them, saying which port and protocol to accept and what to do with what arrives.
TG_ARN=$(aws elbv2 create-target-group \
--name nautilus-tg --protocol HTTP --port 80 \
--vpc-id vpc-xxxxxxxx --target-type instance \
--health-check-path /health \
--query 'TargetGroups[0].TargetGroupArn' --output text)
aws elbv2 register-targets --target-group-arn $TG_ARN \
--targets Id=i-1111111111111111 Id=i-2222222222222222
LB_ARN=$(aws elbv2 create-load-balancer \
--name nautilus-alb --type application --scheme internet-facing \
--subnets subnet-aaaaaaaa subnet-bbbbbbbb --security-groups sg-xxxxxxxx \
--query 'LoadBalancers[0].LoadBalancerArn' --output text)
aws elbv2 create-listener --load-balancer-arn $LB_ARN \
--protocol HTTP --port 80 \
--default-actions Type=forward,TargetGroupArn=$TG_ARN
A load balancer with no listener accepts nothing. It exists, it has a DNS name, and it does absolutely nothing with your traffic.
The requirement that catches people on the first attempt is in the --subnets line: an ALB needs at least two subnets in two different availability zones. AWS enforces it, and it is not a suggestion. The load balancer is meant to survive an AZ failure, so it refuses to be built somewhere it could not.
Then:
aws elbv2 describe-target-health --target-group-arn $TG_ARN \
--query 'TargetHealthDescriptions[*].{Target:Target.Id,State:TargetHealth.State,Reason:TargetHealth.Reason}'
If every target is unhealthy, the cause is almost always security groups, and specifically the second one nobody thinks about. The ALB's security group has to allow inbound from the internet. The instances' security group has to allow inbound from the ALB's security group on the target port. Get the first and miss the second and you have a working load balancer pointing at servers it cannot reach. The reason code tells you which: Target.Timeout means the traffic is being blocked, Target.ResponseCodeMismatch means it got through and the health check path returned the wrong status.
One thing to internalise: an ALB has a DNS name, never a static IP. Its addresses change underneath you. Point DNS at the name with a CNAME or a Route 53 alias and never write one of those IPs down.
Both are pointers
A branch points at a commit, so moving work around costs nothing. A target group points at instances, so replacing a server costs nothing either. In both cases, the indirection is the feature, and in both cases the thing being pointed at can change without anything upstream noticing.
So here is the Day 24 question. When something in your setup is expensive to change, is it actually expensive, or is it just missing the layer of indirection that would make it cheap?
Day 24 down. Seventy-six to go.
Top comments (0)