# Exercise 2 — Namespaces and Isolation, Observed

<!--
STEP 1: Run the two containers described in "Setup".
STEP 2: Fill in each TODO command — the point is to OBSERVE, from inside
        and outside the containers, what a PID/network namespace actually
        hides and doesn't hide.
STEP 3: Run the self-check script and get every line to PASS.
STEP 4: Answer the reasoning questions using what you observed, not guesses.
-->

## Setup

```bash
docker run -d --name ns-a --rm alpine:3.19 sleep 3600
docker run -d --name ns-b --rm alpine:3.19 sleep 3600
```

## Your Commands

```bash
# TODO 1: Inside ns-a, list processes and note the PID of the `sleep` command
#         as seen INSIDE the container's PID namespace.
docker exec ns-a ps aux

# TODO 2: On the HOST (outside any container — do not use `docker exec` here),
#         find the PID that the SAME sleep process has from the host's point
#         of view. (Hint: `docker top ns-a` shows the host-side PID mapping.)


# TODO 3: From inside ns-a, try to see or signal any process belonging to
#         ns-b by PID (e.g. `docker exec ns-a ps aux` again, or attempt
#         `docker exec ns-a kill <ns-b's host pid>`). Confirm it's not
#         visible/reachable from ns-a's own PID namespace.


# TODO 4: Confirm ns-a and ns-b each have their own network namespace by
#         checking each container's IP address and showing they differ.


# TODO 5: Confirm both containers are, in fact, ordinary processes on the
#         HOST's own process list (run this on the host, not via docker exec).
```

## Self-Check

```bash
#!/usr/bin/env bash
# Run with: bash check.sh
set -uo pipefail
pass=0
fail=0

check() {
  local desc="$1"
  local cond="$2"
  if eval "$cond"; then
    echo "PASS: $desc"; pass=$((pass + 1))
  else
    echo "FAIL: $desc"; fail=$((fail + 1))
  fi
}

check "ns-a is running" \
  "[ \"\$(docker inspect -f '{{.State.Running}}' ns-a 2>/dev/null)\" = 'true' ]"
check "ns-b is running" \
  "[ \"\$(docker inspect -f '{{.State.Running}}' ns-b 2>/dev/null)\" = 'true' ]"

ip_a=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' ns-a 2>/dev/null)
ip_b=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' ns-b 2>/dev/null)
check "ns-a and ns-b have different container IPs (separate net namespaces)" \
  "[ -n \"$ip_a\" ] && [ -n \"$ip_b\" ] && [ \"$ip_a\" != \"$ip_b\" ]"

host_pid_a=$(docker inspect -f '{{.State.Pid}}' ns-a 2>/dev/null)
check "host sees ns-a's process under a real host PID" \
  "[ -n \"$host_pid_a\" ] && kill -0 \"$host_pid_a\" 2>/dev/null"

inside_pid_a=$(docker exec ns-a sh -c "ps aux | grep '[s]leep' | awk '{print \$1}'" 2>/dev/null)
check "inside ns-a, the sleep process reports a small PID (own PID namespace, likely PID 1)" \
  "[ \"$inside_pid_a\" = \"1\" ]"

echo "---"
echo "$pass passed, $fail failed"
[ "$fail" -eq 0 ] && echo "PASS" || echo "FAIL"
```

## Cleanup

```bash
docker stop ns-a ns-b
```

(`--rm` was passed at creation, so stopping removes them automatically.)

## Reasoning Questions

1. The self-check expects the `sleep` process inside `ns-a` to report PID 1.
   Explain, in terms of the PID namespace, why a process can be PID 1
   inside its own container while simultaneously having a large, ordinary
   PID (like 48213) when listed by `docker top` or the host's own `ps aux`.
2. You confirmed ns-a and ns-b each got their own private IP address. Which
   specific namespace is responsible for that, and what Docker networking
   component (mentioned in the theory) is actually moving packets between
   these two private namespaces and the host's real network interface?
3. Nothing in this exercise limited how much CPU or memory `ns-a` or `ns-b`
   could use. Which isolation mechanism from the theory (not one used above)
   would you add, and via which `docker run` flags, to cap them?
