<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Newerkey Notes]]></title><description><![CDATA[Short notes and journals on platform engineering, developer tooling, cloud automation, observability, and building clearer systems.]]></description><link>https://notes.newerkey.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 18 Jun 2026 22:52:25 GMT</lastBuildDate><atom:link href="https://notes.newerkey.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Setting Up a Linux User with a Non-Interactive Shell]]></title><description><![CDATA[This is Day 1 of my #100DaysOfDevOps series on KodeCloud.

The Task

Create a user named john with a non-interactive shell on App Server 2.

Simple on the surface. But before touching the server, it's]]></description><link>https://notes.newerkey.com/setting-up-a-linux-user-with-a-non-interactive-shell</link><guid isPermaLink="true">https://notes.newerkey.com/setting-up-a-linux-user-with-a-non-interactive-shell</guid><dc:creator><![CDATA[Yvette Nartey]]></dc:creator><pubDate>Thu, 18 Jun 2026 19:24:36 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><em>This is Day 1 of my #100DaysOfDevOps series on KodeCloud.</em></p>
</blockquote>
<h2>The Task</h2>
<blockquote>
<p>Create a user named <code>john</code> with a non-interactive shell on App Server 2.</p>
</blockquote>
<p>Simple on the surface. But before touching the server, it's worth understanding <em>why</em> this task exists at all.</p>
<hr />
<h2>Why Would You Ever Need a Non-Interactive User?</h2>
<p>Imagine a company runs automated backups every night at 2AM. That backup tool needs a Linux user account to operate under — it owns the processes, the temp files, the log entries. But here's the thing: <strong>the backup tool doesn't need to log in</strong>. It doesn't need a terminal prompt. It doesn't need to run arbitrary commands.</p>
<p>Think of it like giving a robot a limited keycard that only opens the filing cabinet, rather than handing it a master key to the entire building.</p>
<p>This is the <strong>principle of least privilege</strong> in action: the account gets exactly enough access to do its job — copy the data — but zero power to delete files, change system settings, or poke around where it shouldn't.</p>
<p>A non-interactive shell like <code>/sbin/nologin</code> enforces this at the OS level. If anyone (or anything) tries to SSH in as <code>john</code>, the connection is immediately terminated. No prompt. No shell. No risk.</p>
<hr />
<h2>The Infrastructure</h2>
<p>This task runs on the <a href="https://kodekloudhub.github.io/kodekloud-engineer/docs/projects/nautilus#infrastructure-details"><strong>Nautilus</strong></a> project environment from KodeCloud. App Server 2 details:</p>
<table>
<thead>
<tr>
<th>Detail</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td>Hostname</td>
<td><code>stapp02</code></td>
</tr>
<tr>
<td>SSH User</td>
<td><code>steve</code></td>
</tr>
<tr>
<td>Shell</td>
<td>Bash (steve's shell — not john's!)</td>
</tr>
</tbody></table>
<p>Connect via:</p>
<pre><code class="language-bash">ssh steve@stapp02
</code></pre>
<p>Enter the password when prompted.</p>
<hr />
<h2>Step-by-Step: What I Did</h2>
<h3>1. Connect and Check Context</h3>
<p>Before running anything, I verified who I was and what environment I landed in:</p>
<pre><code class="language-bash">whoami
# steve
</code></pre>
<p><code>steve</code> is not root. That matters — <strong>only root can create users</strong>, so I'll need <code>sudo</code>.</p>
<h3>2. Create the User — First Attempt (The Omission)</h3>
<p>My first instinct was:</p>
<pre><code class="language-bash">sudo useradd john
</code></pre>
<p>The command ran. No errors. Looked fine.</p>
<p>Then I verified in <code>/etc/passwd</code>:</p>
<pre><code class="language-bash">grep john /etc/passwd
# john:x:1001:1001::/home/john:/bin/bash
</code></pre>
<p><strong>See the issue?</strong> The last field — the shell — defaulted to <code>/bin/bash</code>. That's a fully interactive shell. The exact opposite of what was required.</p>
<blockquote>
<p>💡 <strong>Lesson</strong>: A command exiting without error doesn't mean it did what you intended. Always verify.</p>
</blockquote>
<h3>3. Fix It — Without Deleting the User</h3>
<p>I could have deleted <code>john</code> and started over, but that's the heavy-handed approach. Linux gives you <code>usermod</code> to modify existing users:</p>
<pre><code class="language-bash">sudo usermod -s /sbin/nologin john
</code></pre>
<p>Then verified again:</p>
<pre><code class="language-bash">grep john /etc/passwd
# john:x:1001:1001::/home/john:/sbin/nologin
</code></pre>
<p>The shell field now shows <code>/sbin/nologin</code>. Task complete. ✅</p>
<hr />
<h2>The Right Command from the Start</h2>
<p>For the record, the correct one-liner to create <code>john</code> with a non-interactive shell in a single step:</p>
<pre><code class="language-bash">sudo useradd -s /sbin/nologin john
</code></pre>
<p>The <code>-s</code> flag sets the shell at creation time. Simple, explicit, intentional.</p>
<hr />
<h2>Key Concepts Recap</h2>
<table>
<thead>
<tr>
<th>Concept</th>
<th>What It Means</th>
</tr>
</thead>
<tbody><tr>
<td>Non-interactive shell</td>
<td>Blocks login access; account exists but can't be used for SSH</td>
</tr>
<tr>
<td><code>/sbin/nologin</code></td>
<td>The standard shell path for non-interactive accounts</td>
</tr>
<tr>
<td><code>/etc/passwd</code></td>
<td>The file that stores all user accounts and their attributes</td>
</tr>
<tr>
<td><code>useradd -s</code></td>
<td>Creates a user with a specified shell</td>
</tr>
<tr>
<td><code>usermod -s</code></td>
<td>Modifies the shell of an existing user</td>
</tr>
<tr>
<td>Least privilege</td>
<td>Give accounts only the access they need — nothing more</td>
</tr>
</tbody></table>
<hr />
<h2>What I'd Do Differently</h2>
<p>Run the full command correctly the first time:</p>
<pre><code class="language-bash">sudo useradd -s /sbin/nologin john
</code></pre>
<p>And always verify immediately with:</p>
<pre><code class="language-bash">grep john /etc/passwd
</code></pre>
<p>Don't trust exit codes. Trust the source of truth.</p>
<hr />
<h2>Closing Thought</h2>
<p>The bigger lesson from this exercise isn't the <code>useradd</code> syntax. It's the mindset:</p>
<p><strong>Not every mistake requires deletion. Understand what you have, then modify deliberately.</strong></p>
<hr />
<p><em>Following along? Drop a comment below or connect with me on</em> <a href="https://www.linkedin.com/in/yvettenartey/"><em>LinkedIn</em></a><em>. See you on the next one</em></p>
<p><code>#100DaysOfDevOps</code> <code>#Linux</code> <code>#DevOps</code> <code>#KodeCloud</code> <code>#Beginner</code></p>
]]></content:encoded></item><item><title><![CDATA[Installing K3s and the First Pod]]></title><description><![CDATA[K3s installed in one command. Everything after that took longer. This post covers enabling cgroups on Raspberry Pi OS, fixing kubectl permissions properly via a systemd service override, and watching ]]></description><link>https://notes.newerkey.com/installing-k3s-and-the-first-pod</link><guid isPermaLink="true">https://notes.newerkey.com/installing-k3s-and-the-first-pod</guid><category><![CDATA[local k3s setup]]></category><dc:creator><![CDATA[Yvette Nartey]]></dc:creator><pubDate>Fri, 05 Jun 2026 20:39:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6011f793e8f6da1d3fdffb5a/2ead0f65-2510-4b16-998c-d6c2c05c30d0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>K3s installed in one command. Everything after that took longer. This post covers enabling cgroups on Raspberry Pi OS, fixing kubectl permissions properly via a systemd service override, and watching the pod scheduling pipeline run live in your own cluster — including what QoS classes actually mean when you change them in real time.</p>
<blockquote>
<p><strong>Previous post:</strong> <a href="https://newerkey.hashnode.dev/from-microsd-to-ssd-boot-on-raspberry-pi-4">From microSD to SSD Boot on Raspberry Pi 4</a></p>
</blockquote>
<hr />
<h2>What this covers</h2>
<p>With the Pi running cleanly from the Intenso SSD, the next goal; get a real Kubernetes cluster running and understand what's actually happening when a pod starts.</p>
<p>By the end of this post you'll have seen:</p>
<ul>
<li><p>K3s installed and running on ARM hardware</p>
</li>
<li><p>The full pod scheduling pipeline live in your own cluster</p>
</li>
<li><p>Resource requests and limits in practice</p>
</li>
<li><p>QoS classes changing in real time based on what you define</p>
</li>
</ul>
<hr />
<h2>Why K3s and not full Kubernetes</h2>
<p>K3s is a lightweight Kubernetes distribution built specifically for edge devices, ARM hardware, and resource-constrained environments. It exposes the full Kubernetes API — every <code>kubectl</code> command you learn here works identically against a production EKS or GKE cluster — but it runs in a fraction of the memory.</p>
<p>Full Kubernetes on a Raspberry Pi 4 would consume most of the available RAM before you deployed a single workload. K3s runs comfortably alongside a full application stack on the same 4GB.</p>
<p><strong>Source:</strong> <a href="https://docs.k3s.io/architecture">https://docs.k3s.io/architecture</a></p>
<hr />
<h2>Step 1 — Enable cgroups</h2>
<p>This is the step that catches almost everyone on Raspberry Pi OS. Kubernetes needs Linux <strong>cgroups</strong> (control groups) to enforce container resource limits — CPU throttling, memory limits, scheduling decisions. On Pi OS they're not fully enabled by default.</p>
<p>Edit <code>/boot/firmware/cmdline.txt</code>:</p>
<pre><code class="language-shell">sudo nano /boot/firmware/cmdline.txt
</code></pre>
<p>Add these parameters at the end of the single line:</p>
<pre><code class="language-shell">cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset
</code></pre>
<p>The full line now looks like:</p>
<pre><code class="language-shell">console=serial0,115200 console=tty1 root=PARTUUID=6692b3d6-02 rootfstype=ext4 fsck.repair=yes rootwait usb-storage.quirks=152d:0579:u cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset
</code></pre>
<p>Verify with <code>cat -A</code> — one line, one <code>$</code> at the end. Then reboot.</p>
<p>After reboot, verify cgroups are active:</p>
<pre><code class="language-shell">cat /sys/fs/cgroup/cgroup.controllers
</code></pre>
<p>Output:</p>
<pre><code class="language-shell">cpuset cpu io memory pids
</code></pre>
<p><code>memory</code> in that list confirms the memory controller is active.. I am on <strong>cgroup v2</strong> -which modern Raspberry Pi OS uses it by default. The legacy <code>cgroup_memory=1</code> syntax is harmless but the actual enablement comes from cgroup v2's memory controller being present.</p>
<p><strong>Source:</strong> <a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html">https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html</a></p>
<p><strong>Source:</strong> <a href="https://docs.k3s.io/installation/requirements?os=pi#operating-systems">https://docs.k3s.io/installation/requirements?os=pi#operating-systems</a></p>
<hr />
<h2>Step 2 — Install K3s</h2>
<p>One command:</p>
<pre><code class="language-shell">curl -sfL https://get.k3s.io | sh -
</code></pre>
<p>This downloads the K3s binary, installs it as a systemd service, generates a kubeconfig, and starts the cluster. Takes about 2 minutes on the Pi.</p>
<p>Verify the cluster started:</p>
<pre><code class="language-shell">sudo systemctl status k3s | head -5
</code></pre>
<pre><code class="language-shell">● k3s.service - Lightweight Kubernetes
     Loaded: loaded (/etc/systemd/system/k3s.service; enabled)
     Active: active (running)
</code></pre>
<p><strong>Source of truth:</strong> <a href="https://docs.k3s.io/quick-start">https://docs.k3s.io/quick-start</a></p>
<hr />
<h2>Step 3 — The kubectl permissions problem (and how it was fixed)</h2>
<p>This is where it got interesting.</p>
<p>K3s writes its kubeconfig to <code>/etc/rancher/k3s/k3s.yaml</code> with root-only permissions (600). Running <code>kubectl get nodes</code> without <code>sudo</code> fails:</p>
<pre><code class="language-shell">yvette@newerkey-lab:~ $ kubectl get nodes
WARN[0000] Unable to read /etc/rancher/k3s/k3s.yaml, please start server
with --write-kubeconfig-mode or --write-kubeconfig-group to modify kube
config permissions
error: error loading config file "/etc/rancher/k3s/k3s.yaml": open
/etc/rancher/k3s/k3s.yaml: permission denied
</code></pre>
<p>The documented fix is to create <code>/etc/rancher/k3s/config.yaml</code> with:</p>
<pre><code class="language-yaml">write-kubeconfig-mode: "0644"
</code></pre>
<p>This tells K3s to write the kubeconfig with readable permissions on every start. I verified the file was correct — <code>cat -A</code> showed clean YAML, <code>xxd</code> showed no hidden characters — but K3s kept ignoring it.</p>
<p>After tdebugging for what felt like forever, I took a more explicit approach: a <strong>systemd service override</strong>.</p>
<pre><code class="language-shell">sudo mkdir -p /etc/systemd/system/k3s.service.d
sudo nano /etc/systemd/system/k3s.service.d/override.conf
</code></pre>
<p>Content:</p>
<pre><code class="language-yaml">[Service]
ExecStart=
ExecStart=/usr/local/bin/k3s server --write-kubeconfig-mode=0644
</code></pre>
<p>The first blank <code>ExecStart=</code> clears the existing command before setting the new one — required systemd syntax for overrides.</p>
<pre><code class="language-shell">sudo systemctl daemon-reload
sudo systemctl restart k3s
</code></pre>
<p>Verify the permissions changed:</p>
<pre><code class="language-shell">ls -la /etc/rancher/k3s/k3s.yaml
</code></pre>
<pre><code class="language-shell">-rw-r--r-- 1 root root 2941 Jun  5 16:45 /etc/rancher/k3s/k3s.yaml
</code></pre>
<p><code>-rw-r--r--</code> is 644. It means that the owner can read and write to the file, while everyone else (groups and others) can only read**.** Now kubectl works without sudo and survives every restart and upgrade.</p>
<pre><code class="language-shell">yvette@newerkey-lab:~ $ kubectl get nodes
NAME           STATUS   ROLES           AGE     VERSION
newerkey-lab   Ready    control-plane   3d18h   v1.35.5+k3s1
</code></pre>
<p><strong>Why I decided on the systemd override as my preferred long-term fix:</strong></p>
<ul>
<li><p>Survives K3s upgrades (stored separately from the K3s binary)</p>
</li>
<li><p>Survives restarts (applied every time the service starts)</p>
</li>
<li><p>Explicit — you can see exactly what flag is being passed</p>
</li>
<li><p>Standard Linux pattern</p>
</li>
</ul>
<p><strong>Source:</strong> <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html">https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html</a></p>
<hr />
<h2>Step 4 — What K3s installed automatically</h2>
<pre><code class="language-shell">yvette@newerkey-lab:~ $ kubectl get pods --all-namespaces
NAMESPACE     NAME                                      READY   STATUS      RESTARTS       AGE
kube-system   coredns-8db54c48d-q6wzr                   1/1     Running     4 (147m ago)   3d21h
kube-system   helm-install-traefik-crd-2xxbj            0/1     Completed   0              3d21h
kube-system   helm-install-traefik-wf99m                0/1     Completed   2              3d21h
kube-system   local-path-provisioner-5d9d9885bc-bnddm   1/1     Running     4 (147m ago)   3d21h
kube-system   metrics-server-786d997795-twsvr           1/1     Running     4 (147m ago)   3d21h
kube-system   svclb-traefik-0ad420ed-zcg6n              2/2     Running     8 (147m ago)   3d21h
kube-system   traefik-9bcdbbd9-drlcw                    1/1     Running     4 (147m ago)   3d21h
</code></pre>
<p>What each component does:</p>
<p><strong>coredns</strong> — Cluster DNS. Every service gets a DNS name automatically. Pods resolve <code>http://my-service</code> to the right cluster IP without any manual configuration.</p>
<p><strong>helm-install-traefik (Completed)</strong> — Finished Jobs that ran once to install Traefik via Helm and exited. Completed status is expected — they're not consuming resources.</p>
<p><strong>local-path-provisioner</strong> — Creates Persistent volumes on the node's local disk. Needed from Day 3 when the private registry needs persistent storage.</p>
<p><strong>metrics-server</strong> — Collects CPU and memory usage from all pods and nodes. Powers <code>kubectl top</code> and the Horizontal Pod Autoscaler.</p>
<p><strong>svclb-traefik</strong> — K3s's built-in service load balancer. Routes external traffic into the cluster.</p>
<p><strong>traefik</strong> — The ingress controller. Routes HTTP/HTTPS requests to the right service based on hostname and path rules. Day 4 builds on this directly.</p>
<hr />
<h2>Step 5 — The pod scheduling pipeline</h2>
<p>Deploy the first pod:</p>
<pre><code class="language-shell">kubectl run hello-nginx --image=nginx:alpine
kubectl get pods --watch
</code></pre>
<p>Once <code>Running</code>, describe it:</p>
<pre><code class="language-bash">yvette@newerkey-lab:~ $ kubectl describe pod hello-nginx
</code></pre>
<p>The Events section at the bottom shows the full scheduling pipeline:</p>
<pre><code class="language-shell">Normal  Scheduled  27m   default-scheduler  Successfully assigned default/hello-nginx to newerkey-lab
Normal  Pulling    27m   kubelet            Pulling image "nginx:alpine"
Normal  Pulled     27m   kubelet            Successfully pulled image "nginx:alpine" in 4.797s
Normal  Created    27m   kubelet            Container created
Normal  Started    27m   kubelet            Container started
</code></pre>
<p>Step by step:</p>
<ol>
<li><p><code>kubectl run</code> sends a pod spec to the <strong>API server</strong></p>
</li>
<li><p>API server writes it to <strong>etcd</strong> (cluster state store)</p>
</li>
<li><p>The <strong>scheduler</strong> watches for unscheduled pods and assigns this one to <code>newerkey-lab</code></p>
</li>
<li><p>The <strong>kubelet</strong> on that node sees the assignment, pulls the image from the registry</p>
</li>
<li><p>The <strong>container runtime</strong> (containerd) creates and starts the container</p>
</li>
<li><p>Once the container passes its readiness check, the pod moves to <code>Running</code></p>
</li>
</ol>
<p>Also note: <code>QoS Class: BestEffort</code> at the bottom of the describe output. No resource requests or limits were set, so Kubernetes assigned the lowest quality of service class. These pods are first to be evicted under memory pressure.</p>
<p><strong>Source:</strong> <a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/">https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/</a></p>
<hr />
<h2>Step 6 — The QoS class change</h2>
<p>This is the thing that was insightful for me. I deleted the bare pod and redeployed it with resource requests and limits defined in a YAML manifest:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
  name: hello-nginx
  namespace: default
spec:
  containers:
  - name: hello-nginx
    image: nginx:alpine
    resources:
      requests:
        memory: "64Mi"
        cpu: "100m"
      limits:
        memory: "128Mi"
        cpu: "200m"
</code></pre>
<pre><code class="language-shell">kubectl apply -f hello-nginx.yaml
kubectl describe pod hello-nginx
</code></pre>
<p>Two things changed in the describe output:</p>
<p><strong>Requests and limits now appear:</strong></p>
<pre><code class="language-plaintext">Limits:
  cpu:     200m
  memory:  128Mi
Requests:
  cpu:        100m
  memory:     64Mi
</code></pre>
<p><strong>QoS Class changed:</strong></p>
<pre><code class="language-shell">QoS Class: Burstable
</code></pre>
<p>Reading the documentation told me this would happen. Seeing it change in my own cluster output made it real.</p>
<p>The three QoS classes:</p>
<table>
<thead>
<tr>
<th>Class</th>
<th>When assigned</th>
<th>Eviction priority</th>
</tr>
</thead>
<tbody><tr>
<td><code>Guaranteed</code></td>
<td>requests == limits for all containers</td>
<td>Last evicted</td>
</tr>
<tr>
<td><code>Burstable</code></td>
<td>requests set but lower than limits</td>
<td>Middle</td>
</tr>
<tr>
<td><code>BestEffort</code></td>
<td>no requests or limits</td>
<td>First evicted</td>
</tr>
</tbody></table>
<p>This matters for production platform work. A pod with no resource limits in a shared cluster is a neighbour problem — it can consume unbounded resources and starve other workloads. Setting limits is not optional on a platform that other teams depend on.</p>
<p><strong>Source:</strong> <a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/">https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/</a></p>
<p><strong>Source :</strong> <a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/">https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/</a></p>
<hr />
<h2>Step 7 — Checking actual resource usage</h2>
<pre><code class="language-shell">yvette@newerkey-lab:~ $ kubectl top pod hello-nginx
NAME          CPU(cores)   MEMORY(bytes)
hello-nginx   0m           4Mi

yvette@newerkey-lab:~ $ kubectl top node
NAME           CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
newerkey-lab   129m         3%       1331Mi          35%
</code></pre>
<p>The pod is using 4Mi against a 128Mi limit — well within bounds. If it hit 128Mi, the Linux kernel OOM killer would terminate the container process and Kubernetes would restart it. Repeated OOMKills produce <code>CrashLoopBackOff</code>.</p>
<div>
<div>💡</div>
<div>Mi - Mebibyte</div>
</div>

<p>The node is using 1331Mi of 4GB — 35%. That leaves roughly 2.4GB free for the rest of the work in this series.</p>
<p><strong>Source:</strong> <a href="https://kubernetes.io/docs/reference/kubectl/generated/kubectl%5C_top/">https://kubernetes.io/docs/reference/kubectl/generated/kubectl\_top/</a></p>
<hr />
<h2>The four kubectl commands to know</h2>
<p>These are the common commands I used:</p>
<pre><code class="language-shell"># List resources
kubectl get pods
kubectl get pods --all-namespaces
kubectl get pods --watch

# Detailed info — use this first when something is wrong
kubectl describe pod hello-nginx

# Application logs
kubectl logs hello-nginx

# Shell into a running container
kubectl exec -it hello-nginx -- /bin/sh
</code></pre>
<p><code>kubectl describe</code> is the most important debugging command. The Events section shows exactly what happened at each stage — whether the pod failed to schedule, whether the image pull failed, whether the container crashed on start.</p>
<p><strong>Source:</strong> <a href="https://kubernetes.io/docs/reference/kubectl/quick-reference/">https://kubernetes.io/docs/reference/kubectl/quick-reference/</a></p>
<hr />
<h2>What's running now</h2>
<pre><code class="language-shell">newerkey-lab (Raspberry Pi 4, 4GB)
└── K3s v1.35.5+k3s1
    ├── kube-system: coredns, traefik, metrics-server, local-path-provisioner
    └── default: hello-nginx (nginx:alpine, Burstable QoS, 4Mi/128Mi memory)

Node utilisation: 129m CPU (3%), 1331Mi memory (35%)
kubectl: working without sudo, permanent via systemd override
</code></pre>
<hr />
<h2>What I learned today</h2>
<p><strong>Kubernetes concepts land differently hands-on but still a lot to take in so don't rush to understand all at once.</strong> QoS classes, pod scheduling pipeline, resource requests — I'd read about all of these. Watching the QoS class change from <code>BestEffort</code> to <code>Burstable</code> in my own describe output, against a pod I just deployed, made the concept stick in a way documentation alone doesn't.</p>
<p><strong>Debugging is normal.</strong> The kubectl permissions issue took longer than the K3s install itself. The solution — a systemd service override — is more robust than the documented config.yaml approach. Sometimes the detour teaches you more than the happy path would have.</p>
<p><strong>Image caching is visible.</strong> First deployment: <code>Pulling image "nginx:alpine" in 4.797s</code>. Second deployment: <code>Container image "nginx:alpine" already present on machine</code>. That's the image cache working — and the reason CronJob pods sometimes start slowly on nodes that haven't cached the image yet.</p>
<hr />
<h2>What's next</h2>
<p><strong>Private Docker Registry.</strong> Deploy a private container registry inside the K3s cluster with persistent storage, configure K3s to trust it, and push the first image from the laptop into the cluster.</p>
<hr />
<h2>References</h2>
<ul>
<li><p><a href="https://docs.k3s.io/quick-start">K3s quick start</a></p>
</li>
<li><p><a href="https://docs.k3s.io/architecture">K3s architecture</a></p>
</li>
<li><p><a href="https://docs.k3s.io/installation/requirements">K3s installation requirements</a></p>
</li>
<li><p><a href="https://docs.k3s.io/cluster-access">K3s cluster access</a></p>
</li>
<li><p><a href="https://docs.k3s.io/installation/configuration#configuration-file">K3s configuration file</a></p>
</li>
<li><p><a href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html">systemd service overrides</a></p>
</li>
<li><p><a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/">Kubernetes pod lifecycle</a></p>
</li>
<li><p><a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/">Kubernetes QoS classes</a></p>
</li>
<li><p><a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/">Kubernetes resource management</a></p>
</li>
<li><p><a href="https://kubernetes.io/docs/reference/kubectl/quick-reference/">kubectl quick reference</a></p>
</li>
<li><p><a href="https://kubernetes.io/docs/reference/kubectl/generated/kubectl_top/">kubectl top</a></p>
</li>
<li><p><a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html">cgroup v2 documentation</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[From microSD to SSD Boot on Raspberry Pi 4]]></title><description><![CDATA[The "simple" first step — flash an OS, boot from SSD, configure SSH — turned into a four-hour debugging session involving a JMicron USB controller, a malformed kernel parameter that produced a grey sc]]></description><link>https://notes.newerkey.com/from-microsd-to-ssd-boot-on-raspberry-pi-4</link><guid isPermaLink="true">https://notes.newerkey.com/from-microsd-to-ssd-boot-on-raspberry-pi-4</guid><dc:creator><![CDATA[Yvette Nartey]]></dc:creator><pubDate>Mon, 01 Jun 2026 17:54:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6011f793e8f6da1d3fdffb5a/f211019e-2f14-4d04-a547-493740688905.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The "simple" first step — flash an OS, boot from SSD, configure SSH — turned into a four-hour debugging session involving a JMicron USB controller, a malformed kernel parameter that produced a grey screen, and a trailing newline in a config file that broke the boot entirely. This post covers the full Pi 4 SSD boot setup: the JMicron quirks fix that takes read speed from 127 kB/s to 303 MB/s, why rpi-clone silently gets the PARTUUID wrong, and why cat -A is now in my standard toolkit after every config file edit.</p>
<hr />
<h2>Why I'm doing this</h2>
<p>I'm an IT Infrastructure Support engineer transitioning into platform engineering. So I decided to put together a permanent home lab that mirrors, in miniature, the kind of infrastructure I believe platform teams actually run. This totally from a beginner's perspective.</p>
<p>The plan over the next days:</p>
<ul>
<li><p>Foundation: Pi setup, K3s, private registry, networking, Terraform with a Makefile wrapper, CI/CD</p>
</li>
<li><p>GitOps with Flux CD</p>
</li>
<li><p>OpenTelemetry observability layer aligned with the OTCA certification syllabus</p>
</li>
</ul>
<p>Every project I build from now on lives on this Pi. It's my permanent lab.</p>
<hr />
<h2>What I set out to do today</h2>
<p>The first step seemed simple: get a Raspberry Pi 4 running Raspberry Pi OS Lite, booting from a USB SSD instead of a microSD card, with SSH key authentication configured for passwordless access from my laptop.</p>
<p>What actually happened was a four-hour debugging session that taught me more about USB controllers, kernel parameters, and partition UUIDs than I expected.</p>
<hr />
<h2>The hardware</h2>
<ul>
<li><p>Raspberry Pi 4 Model B (4GB)</p>
</li>
<li><p>Intenso External SSD 256GB Premium</p>
</li>
<li><p>32GB Samsung microSD (used only for initial setup, removed at the end)</p>
</li>
<li><p>Geekworm aluminium passive case</p>
</li>
<li><p>Wired keyboard, mouse, and a portable monitor for setup</p>
</li>
<li><p>Ubuntu laptop for SSH and flashing</p>
</li>
</ul>
<p>I want to call out one decision specifically. I went back and forth between getting the Argon ONE M.2 case with an internal SSD versus using my existing Geekworm case with a USB SSD. The Argon ONE is the "proper" permanent setup — internal M.2 drive, active cooling, cleaner form factor. But it would have cost €90+ more.</p>
<p>I chose the budget path: keep the Geekworm case, use a USB SSD for now, plan to migrate to the Argon ONE in 12-18 months. The learning value is identical regardless of which case the SSD is in. <strong>Don't let perfect be the enemy of started.</strong></p>
<hr />
<h2>Phase 1: The Imager problem</h2>
<p>I started by flashing Raspberry Pi OS Lite (64-bit) to the microSD using Raspberry Pi Imager on Ubuntu.</p>
<blockquote>
<p><strong>Source:</strong> <a href="https://www.raspberrypi.com/documentation/computers/getting-started.html#raspberry-pi-imager">Raspberry Pi Imager documentation</a></p>
</blockquote>
<p>Imager's "OS customisation" feature is supposed to let you pre-configure hostname, SSH, username, and password before flashing — so the Pi boots ready to use without monitor and keyboard.</p>
<p>It didn't work for me. Twice.</p>
<p>I followed the documented flow: <strong>EDIT SETTINGS → fill in → SAVE → YES to apply → YES to overwrite</strong>. The flash completed, I put the card in the Pi, booted it — and the Pi prompted me for a new username and password as if no customisation had been applied at all.</p>
<p>I attempted a manual workaround: flash a clean image, then create <code>ssh</code> and <code>userconf.txt</code> files on the boot partition myself. That didn't work cleanly either.</p>
<p>In the end, the fastest path forward was to plug a keyboard and monitor into the Pi and just go through the first-boot wizard interactively. 30 seconds and I had a working system.</p>
<blockquote>
<p><strong>Lesson:</strong> When a tool that's supposed to save you time isn't working, sometimes the most productive thing is to abandon it. Don't burn an hour debugging a convenience feature.</p>
</blockquote>
<hr />
<h2>Phase 2: Enabling SSH</h2>
<p>After completing the first-boot wizard, I tried to SSH from my laptop and got:</p>
<pre><code class="language-plaintext">ssh: connect to host 192.168.0.249 port 22: Connection refused
</code></pre>
<p>SSH wasn't enabled by default. Easy fix from the Pi console:</p>
<pre><code class="language-bash">sudo systemctl enable ssh
sudo systemctl start ssh
sudo systemctl status ssh
</code></pre>
<blockquote>
<p><strong>Source:</strong> <a href="https://www.freedesktop.org/software/systemd/man/systemctl.html">systemd documentation on enabling services</a></p>
</blockquote>
<p>From this point on, I could disconnect the keyboard, mouse, and monitor and work entirely over SSH from my laptop. Much faster.</p>
<hr />
<h2>Phase 3: Setting up USB boot from SSD</h2>
<p>This is where the real adventure began.</p>
<p>The Pi 4 can boot from USB devices, but two things have to be in place:</p>
<ol>
<li><p>The bootloader firmware needs to be configured to look for USB before microSD</p>
</li>
<li><p>The USB device needs to be detected reliably during early boot</p>
</li>
</ol>
<p><strong>Step 1: Update the bootloader and set USB boot priority</strong></p>
<pre><code class="language-bash">sudo apt update &amp;&amp; sudo apt full-upgrade -y
sudo rpi-eeprom-update -a
sudo raspi-config
</code></pre>
<p>In <code>raspi-config</code>: <strong>Advanced Options → Boot Order → B2 USB Boot</strong>.</p>
<p>Verifying:</p>
<pre><code class="language-bash">sudo rpi-eeprom-config
</code></pre>
<p>Output included <code>BOOT_ORDER=0xf14</code>, which reads right-to-left:</p>
<ul>
<li><p><code>4</code> = USB mass storage</p>
</li>
<li><p><code>1</code> = SD card</p>
</li>
<li><p><code>f</code> = retry forever</p>
</li>
</ul>
<p>So the Pi will try USB first, then fall back to SD card, then loop. Exactly what a permanent SSD setup needs.</p>
<blockquote>
<p><strong>Source:</strong> <a href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-bootloader-configuration">Raspberry Pi bootloader configuration documentation</a></p>
</blockquote>
<hr />
<h2>Phase 4: The JMicron problem</h2>
<p>I plugged the Intenso SSD into a blue USB 3.0 port, ran <code>lsblk</code>, and saw the drive appear as <code>/dev/sda</code> with 238.5GB of capacity. So far so good.</p>
<p>Then I ran a speed test:</p>
<pre><code class="language-bash">yvette@newerkey-lab:~ $ sudo apt install -y hdparm
yvette@newerkey-lab:~ $ sudo hdparm -t /dev/sda

/dev/sda:
 Timing buffered disk reads: 4 MB in 32.19 seconds = 127.23 kB/sec
</code></pre>
<p><strong>127 kilobytes per second.</strong> That's roughly a thousand times slower than a working USB 3.0 drive should be. At that rate, copying a 4GB OS image would take about nine hours.</p>
<p>I checked <code>dmesg</code> and found the issue. The Intenso SSD uses a JMicron USB-to-SATA controller:</p>
<pre><code class="language-bash">yvette@newerkey-lab:~ $ sudo dmesg | grep -i sda
[546.122668] usb 2-2: new SuperSpeed USB device number 2 using xhci_hcd
[546.140093] usb 2-2: New USB device found, idVendor=152d, idProduct=0579
[546.140131] usb 2-2: Product: Portable SSD
[546.140142] usb 2-2: Manufacturer: Intenso
[546.685838] sd 0:0:0:0: [sda] 500118192 512-byte logical blocks: (256 GB/238 GiB)
[546.770965]  sda: sda1 sda2
[546.771605] sd 0:0:0:0: [sda] Attached SCSI disk
</code></pre>
<p>JMicron controllers have a long history of incompatibility with USB Attached SCSI (UAS) on Raspberry Pi 4. The fix is a kernel parameter that disables UAS for the specific drive ID:</p>
<pre><code class="language-plaintext">usb-storage.quirks=152d:0579:u
</code></pre>
<blockquote>
<p><strong>Source:</strong> <a href="https://jamesachambers.com/raspberry-pi-4-usb-boot-config-guide-for-ssd-flash-drives/">James A. Chambers' Raspberry Pi USB Boot Config Guide</a></p>
</blockquote>
<p>The <code>:u</code> flag tells the kernel to ignore UAS and fall back to standard USB mass storage protocol — slower theoretical maximum, but actually functional with JMicron controllers.</p>
<hr />
<h2>The first cmdline.txt mistake</h2>
<p>The quirks parameter goes into <code>/boot/firmware/cmdline.txt</code>, which is a single-line file containing all the kernel boot parameters.</p>
<p>I edited it with nano and added the quirks parameter. Saved, rebooted, and the Pi never came back online. SSH timed out with "No route to host".</p>
<p>Plugged a monitor in. <strong>Grey screen.</strong> No boot text, no kernel messages, just grey.</p>
<p>Pulled the microSD out, put it in my laptop, ran:</p>
<pre><code class="language-bash">cat /media/yvette/bootfs/cmdline.txt
</code></pre>
<p>The line read:</p>
<pre><code class="language-plaintext">usb-storage.quirks=152d:u console=serial0,115200 console=tty1 root=PARTUUID=543ddb0d-02 rootfstype=ext4 fsck.repair=yes rootwait
</code></pre>
<p>I'd typed <code>152d:u</code> instead of <code>152d:0579:u</code>. The kernel was receiving a malformed quirks parameter and bailing out during early boot before it could even display kernel messages.</p>
<p>Fixed the typo, saved, put the card back in the Pi, booted again. This time I checked the file with <code>cat -A</code> to see hidden characters:</p>
<pre><code class="language-bash">cat -A /boot/firmware/cmdline.txt
</code></pre>
<p>The <code>-A</code> flag shows newlines as <code>$</code>. Critical because:</p>
<blockquote>
<p><strong>The cmdline.txt must be exactly one line.</strong> Any line break anywhere will break boot.</p>
<p><a href="https://www.raspberrypi.com/documentation/computers/configuration.html#cmdline-txt">Raspberry Pi documentation on cmdline.txt</a></p>
</blockquote>
<p>The output showed two <code>\(</code> signs — one at the end of my line, and another on its own. nano had introduced an extra blank line at the end of the file. Removed it, verified one <code>\)</code>, rebooted.</p>
<p>This time the Pi came back up. SSH worked. Speed test:</p>
<pre><code class="language-bash">yvette@newerkey-lab:~ $ sudo hdparm -t /dev/sda

/dev/sda:
 Timing buffered disk reads: 912 MB in 3.01 seconds = 303.39 MB/sec
</code></pre>
<p><strong>303 megabytes per second.</strong> Healthy USB 3.0 performance. The quirks fix was working.</p>
<p>The before-and-after is dramatic:</p>
<table>
<thead>
<tr>
<th>State</th>
<th>Read speed</th>
<th>Ratio</th>
</tr>
</thead>
<tbody><tr>
<td>Without quirks fix</td>
<td>127.23 kB/sec</td>
<td>baseline</td>
</tr>
<tr>
<td>With quirks fix</td>
<td>303.39 MB/sec</td>
<td>~2,380× faster</td>
</tr>
</tbody></table>
<hr />
<h2>Phase 5: Cloning microSD to SSD</h2>
<p>With the SSD performing properly, I could clone the running OS from microSD onto the SSD. The community tool <code>rpi-clone</code> handles this cleanly:</p>
<pre><code class="language-bash">sudo apt install -y git
git clone https://github.com/billw2/rpi-clone.git
cd rpi-clone
sudo cp rpi-clone rpi-clone-setup /usr/local/sbin/
</code></pre>
<blockquote>
<p><strong>Source:</strong> <a href="https://github.com/billw2/rpi-clone">rpi-clone GitHub repository</a></p>
</blockquote>
<p>Then I wiped the SSD's existing partition table (it had a stale Pi OS Desktop image from an earlier failed attempt):</p>
<pre><code class="language-bash">sudo wipefs -a /dev/sda
</code></pre>
<blockquote>
<p><strong>Source:</strong> <a href="https://man7.org/linux/man-pages/man8/wipefs.8.html">wipefs manpage</a></p>
</blockquote>
<p>And cloned:</p>
<pre><code class="language-bash">sudo rpi-clone sda
</code></pre>
<p>The clone took under two minutes thanks to the 303 MB/sec USB 3.0 speeds.</p>
<hr />
<h2>The PARTUUID problem</h2>
<p>After cloning, I expected the SSD to be an exact copy of the microSD's running system. I mounted the SSD's boot partition to verify cmdline.txt:</p>
<pre><code class="language-bash">sudo mount /dev/sda1 /mnt/ssd-boot
cat /mnt/ssd-boot/cmdline.txt
</code></pre>
<p>The line still referenced <code>root=PARTUUID=543ddb0d-02</code> — the <strong>microSD's</strong> root partition PARTUUID. If I booted from the SSD with the microSD removed, the kernel would look for a partition that didn't exist and drop into initramfs.</p>
<p>I checked the SSD's actual PARTUUID with <code>blkid</code>:</p>
<pre><code class="language-bash">sudo blkid
</code></pre>
<p>Output showed the SSD's root partition was actually <code>6692b3d6-02</code>. So the cmdline.txt was pointing at the wrong device entirely.</p>
<blockquote>
<p><strong>Lesson:</strong> rpi-clone is supposed to update cmdline.txt automatically, but it doesn't always do so reliably. Always verify the PARTUUID matches the destination device after cloning.</p>
</blockquote>
<p>I edited the SSD's cmdline.txt to use the correct PARTUUID:</p>
<pre><code class="language-bash">sudo nano /mnt/ssd-boot/cmdline.txt
</code></pre>
<p>Changed <code>root=PARTUUID=543ddb0d-02</code> to <code>root=PARTUUID=6692b3d6-02</code>. Verified with <code>cat -A</code> again — one line, one <code>$</code> at the end.</p>
<p>I also checked <code>/etc/fstab</code> on the SSD's root partition. Fortunately rpi-clone had updated that one correctly — it already pointed at <code>6692b3d6-01</code> and <code>6692b3d6-02</code>.</p>
<hr />
<h2>The swap</h2>
<p>Shut down cleanly:</p>
<pre><code class="language-bash">sudo shutdown -h now
</code></pre>
<p>Waited for the green LED to stop flickering. Unplugged power. Removed the microSD card. Confirmed the SSD was still plugged into a blue USB 3.0 port. Plugged power back in.</p>
<p>60 seconds later, SSH connected from my laptop:</p>
<pre><code class="language-bash">yvette@laptop:~$ ssh yvette@ip-of-raspberrypi
yvette@newerkey-lab:~ $
</code></pre>
<p>Verification:</p>
<pre><code class="language-bash">yvette@newerkey-lab:~ $ findmnt /
TARGET SOURCE    FSTYPE OPTIONS
/      /dev/sda2 ext4   rw,noatime
</code></pre>
<p><strong>Booting from SSD.</strong> No more microSD in the system.</p>
<pre><code class="language-bash">yvette@newerkey-lab:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       234G  4.2G  218G   2% /
/dev/sda1       511M   67M  445M  14% /boot/firmware
</code></pre>
<p>234GB root filesystem, 218GB available. Three orders of magnitude more storage than the original microSD setup, with significantly more reliable continuous-write endurance.</p>
<hr />
<h2>Phase 6: SSH key authentication</h2>
<p>Final task was switching from password authentication to SSH keys on your laptop's terminal, not the raspberry-pi.</p>
<p>Already had a key pair on my laptop (<code>~/.ssh/id_ed25519</code>), so:</p>
<ul>
<li>If you do not have it, create a new one <code>ssh-keygen -t ed25519 -C "yourpreferedname"</code></li>
</ul>
<pre><code class="language-bash">ssh-copy-id -i ~/.ssh/id_ed25519.pub yvette@ip-of-raspberrypi
</code></pre>
<p>One last password prompt, then tested:</p>
<pre><code class="language-bash">ssh yvette@ip-of-raspberrypi
</code></pre>
<p>Passwordless login confirmed.</p>
<blockquote>
<p><strong>Source:</strong> <a href="https://man.openbsd.org/ssh-copy-id">ssh-copy-id manpage</a></p>
</blockquote>
<hr />
<h2>What this actually taught me</h2>
<p>The "happy path" version of this day would have taken maybe 90 minutes. Mine took four hours.</p>
<p>But every detour was an actual lesson:</p>
<p><strong>USB controllers matter.</strong> Not all SSDs work the same on Raspberry Pi. The JMicron quirks fix is one of those things you only learn by needing it. Now I know to check <code>lsusb</code> and <code>dmesg</code> for the actual controller vendor ID before assuming a drive will work.</p>
<p><strong>Source of truth beats tutorials.</strong> Every time I found myself stuck, the answer was in the official Raspberry Pi documentation, the kernel parameters reference, or the source GitHub repo of the tool I was using. Random Stack Overflow answers often gave outdated or wrong fixes. I've started keeping a <code>sources.md</code> file with bookmarked official docs for every tool in the lab.</p>
<p><strong>Verify your assumptions explicitly.</strong> I assumed <code>rpi-clone</code> would update cmdline.txt correctly. It didn't. I assumed nano would save cmdline.txt as a single line. It didn't. Now I run <code>cat -A</code> after every config file edit to verify there are no hidden characters or extra newlines.</p>
<p><strong>Hidden characters break systems.</strong> A single trailing newline in cmdline.txt is the difference between a working Pi and a grey screen. <code>cat -A</code> is now in my standard toolkit.</p>
<p><strong>The fast path forward is sometimes abandoning the tool.</strong> I burned time trying to make Imager's OS customisation work. The 30-second interactive first-boot wizard solved the same problem instantly.</p>
<hr />
<h2>What's running now</h2>
<pre><code class="language-plaintext">Raspberry Pi 4 Model B (4GB)
└── Raspberry Pi OS Lite (64-bit), fully updated
    └── Booting from /dev/sda2 (Intenso 256GB USB SSD)
        ├── Read speed: 303 MB/sec
        ├── Storage: 234GB total, 218GB available
        └── usb-storage.quirks=152d:0579:u applied
            
Hostname: newerkey-lab
SSH: key-based authentication only
</code></pre>
<p>The foundation is in place. Permanent lab ready.</p>
<h2>Sources:</h2>
<ul>
<li><p><a href="https://www.raspberrypi.com/documentation/computers/getting-started.html#raspberry-pi-imager">Raspberry Pi Imager documentation</a></p>
</li>
<li><p><a href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-bootloader-configuration">Raspberry Pi bootloader configuration</a></p>
</li>
<li><p><a href="https://www.raspberrypi.com/documentation/computers/configuration.html#cmdline-txt">Raspberry Pi cmdline.txt documentation</a></p>
</li>
<li><p><a href="https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html">Linux kernel parameters</a></p>
</li>
<li><p><a href="https://jamesachambers.com/raspberry-pi-4-usb-boot-config-guide-for-ssd-flash-drives/">James A. Chambers: Raspberry Pi USB Boot Config Guide</a></p>
</li>
<li><p><a href="https://github.com/billw2/rpi-clone">rpi-clone on GitHub</a></p>
</li>
<li><p><a href="https://www.freedesktop.org/software/systemd/man/systemctl.html">systemctl manpage</a></p>
</li>
<li><p><a href="https://man.openbsd.org/ssh-copy-id">ssh-copy-id manpage</a></p>
</li>
<li><p><a href="https://man7.org/linux/man-pages/man8/wipefs.8.html">wipefs manpage</a></p>
</li>
</ul>
<hr />
<p><em>If you're following along or doing your own Pi 4 SSD boot setup, the</em> <a href="https://www.raspberrypi.com/documentation/computers/getting-started.html#raspberry-pi-imager"><em>rpi-imager guide on the official Pi site</em></a> <em>is the right starting point. If you hit JMicron compatibility issues, check</em> <code>lsusb</code> <em>for your drive's vendor:product ID and apply the appropriate</em> <code>usb-storage.quirks</code> <em>parameter.</em></p>
]]></content:encoded></item><item><title><![CDATA[Set up Budgets and Budget Alarms]]></title><description><![CDATA[This is a quick reference guide on setting up budgets and budget alarms on AWS.
Step 1: Set up budget costs Setting up budget costs is an important part of managing your AWS costs. To set up a budget, navigate to the AWS Management Console and select...]]></description><link>https://notes.newerkey.com/set-up-budgets-and-budget-alarms</link><guid isPermaLink="true">https://notes.newerkey.com/set-up-budgets-and-budget-alarms</guid><dc:creator><![CDATA[Yvette Nartey]]></dc:creator><pubDate>Wed, 18 Feb 2026 23:27:43 GMT</pubDate><content:encoded><![CDATA[<p>This is a quick reference guide on setting up budgets and budget alarms on AWS.</p>
<p>Step 1: Set up budget costs Setting up budget costs is an important part of managing your AWS costs. To set up a budget, navigate to the AWS Management Console and select the "Billing and Cost Management" service.</p>
<p>From there, select "Budgets" from the left-hand menu and click on "Create a budget". In the budget creation screen, you'll need to set the budget type (either cost or usage), the budget amount, and the budget period.</p>
<p>You can also set up budget filters to include or exclude specific AWS services, regions, or tags. This can be useful if you want to create separate budgets for different parts of your AWS infrastructure.</p>
<p>Once you've set up your budget, click "Create" to create the budget.</p>
<p>Step 2: Set up budget alarms Budget alarms are notifications that are triggered when your AWS costs exceed a certain threshold. This can be useful for keeping track of your costs and preventing unexpected charges.</p>
<p>To set up a budget alarm, navigate to the budget you created in Step 3 and click on the "Create alarm" button. In the alarm creation screen, you'll need to set the alarm name, the threshold amount, and the notification options.</p>
<p>You can also set up alarm actions, which are actions that are triggered when the alarm is triggered. For example, you can set up an action to automatically stop EC2 instances when costs exceed a certain threshold.</p>
<p>Once you've set up your alarm, click "Create" to create the alarm.</p>
]]></content:encoded></item><item><title><![CDATA[Create and Manage an IAM user(AWS)]]></title><description><![CDATA[Creating an IAM user with security considerations is an essential task every AWS user should know how to do. In this tutorial, we'll go over how to create an IAM user and connect to the CLI.
Configuring AWS environment
This section takes you through ...]]></description><link>https://notes.newerkey.com/create-and-manage-an-iam-useraws</link><guid isPermaLink="true">https://notes.newerkey.com/create-and-manage-an-iam-useraws</guid><category><![CDATA[AWS]]></category><category><![CDATA[IAM,MFA,Access key ID,Secret access key]]></category><category><![CDATA[IAM]]></category><category><![CDATA[cloud administrator]]></category><dc:creator><![CDATA[Yvette Nartey]]></dc:creator><pubDate>Thu, 09 Mar 2023 22:27:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1678400009298/42f4e59c-b36d-4ad0-a3e0-a5a05823eb93.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Creating an IAM user with security considerations is an essential task every AWS user should know how to do. In this tutorial, we'll go over how to create an IAM user and connect to the CLI.</p>
<h2 id="heading-configuring-aws-environment">Configuring AWS environment</h2>
<p>This section takes you through installing and configuring the AWS command line(CLI). For everything that can be done on the AWS Management Console, we can use the CLI also.</p>
<p>To install AWS CLI, follow the instructions based on your operating system (<a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-version.html">https://docs.aws.amazon.com/cli/latest/userguide/getting-started-version.html</a>)</p>
<p><strong><mark>Note:</mark></strong> I am using a Unix system for configuration.</p>
<p>Verify that the CLI is properly installed by typing the command. You should see the version, in my case 2.11.1 is installed.</p>
<pre><code class="lang-bash">aws --version
</code></pre>
<h2 id="heading-create-user">Create User</h2>
<p>Let's create an IAM User and grant it permissions it needs to access the right AWS resources. It is not advised to use the root user for security reasons. Find out more about how it works <a target="_blank" href="https://aws.amazon.com/iam/">https://aws.amazon.com/iam/</a>.</p>
<p>Sign in to AWS Management Console then type <kbd>IAM</kbd> in the search bar and click on the result to take you to the IAM dashboard as shown in figure 1.0.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678392840962/fe1eb340-3fb8-4db0-b48a-dd290b1fafc9.png" alt class="image--center mx-auto" /></p>
<p>From the left menu, choose Users. Click the Add User button. Then set a name for the user and create the password depending on your preference(also select AWS Management Console access if you want the same user to have access to the console), as shown in figure 1.10.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678392793620/563e4084-4390-4904-8f14-ba64a29ad88e.png" alt class="image--center mx-auto" /></p>
<p>In the Set Permissions section, assign a policy to the user. In this case, the <em>AmazonS3FullAccess</em> policy, as shown in figure 1.11. If you already have a user group created with the same policy, you can select it instead.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678391749448/eefb6919-ccf8-4838-89c2-20e86d57fa98.png" alt class="image--center mx-auto" /></p>
<p>On the next page, you can review and create your user. You can optionally add a tag to the user.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678392906041/8940d55c-0030-46f1-a90b-f5769f8dd07d.png" alt class="image--center mx-auto" /></p>
<p>On the final page, you should see the user’s console sign-in details(figure 1.12). You can download the <code>.csv file</code> which contains the details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678393540739/02b9d61b-8e35-409d-8dfa-bc1762e3cfd4.png" alt class="image--center mx-auto" /></p>
<p>Return to the users list, the new user will appear, and click on the user to get its details. To allow access to this user from the CLI for programmatic access, we proceed to create an access key under <strong><em>Security Credentials.</em></strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678394760258/a7ca69ae-70ff-407f-b6db-e6060d389c40.png" alt class="image--center mx-auto" /></p>
<p>We can then choose how to use the access key shown in figure 1.14 and click next. In our case, we want to be able to use it via the CLI.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678395003681/0e1d3219-c44f-4eda-8807-12fc4a374adf.png" alt class="image--center mx-auto" /></p>
<p>In the last section <strong><em>Retrieve access keys</em></strong> section, you can view your access and secret access keys. The recommended way to save these keys is to download <code>.csv file</code> as we won't be able to see it anymore.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678395547379/ec81bbe7-b664-490b-b01c-16f87101babd.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-connect-to-aws-cli">Connect to AWS CLI</h2>
<p>Now that we have everything we need, let's configure the AWS CLI using the command.</p>
<pre><code class="lang-bash">aws configure
</code></pre>
<p>Enter the details as prompted in your terminal. Depending on your location, use your default region.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678396363598/1b17ab43-db9c-4024-a7b4-cd414d4202f7.png" alt class="image--center mx-auto" /></p>
<p>The CLI will store credentials specified in the preceding command in a local file under ~/.aws/credentials (or in %UserProfile%\.aws\credentials on Windows) with the following details.</p>
<p>That should be it, and we have our AWS environment set up.</p>
]]></content:encoded></item><item><title><![CDATA[Tips I learned to solve problems in Software Engineering]]></title><description><![CDATA[Introduction
Context: This was part of my early cloud and infrastructure learning journey. I’m keeping it here as a reference
Ever since I started my journey into Software Engineering, I have known ot]]></description><link>https://notes.newerkey.com/tips-i-learned-to-solve-problems-in-software-engineering</link><guid isPermaLink="true">https://notes.newerkey.com/tips-i-learned-to-solve-problems-in-software-engineering</guid><category><![CDATA[problem solving skills]]></category><category><![CDATA[Problem Solving]]></category><dc:creator><![CDATA[Yvette Nartey]]></dc:creator><pubDate>Thu, 26 Jan 2023 13:19:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1651422232168/B1eoWZ3eH.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Introduction</h1>
<p><strong>Context:</strong> This was part of my early cloud and infrastructure learning journey. I’m keeping it here as a reference</p>
<p>Ever since I started my journey into Software Engineering, I have known others who have experienced the fast, ever-changing field, which can become overwhelming. They gradually quit at some point or follow the latest trends wherever it leads them.</p>
<p>These trends, however, have the same purpose in common, to solve problems in our everyday lives and society.</p>
<p>Even though I am from another technical background(Materials Engineering), I always find myself stuck whenever I am given a problem to solve in <strong>coding</strong> and do not know what to do or where to start, hence, this article. I will explore, shortly three main approaches to problem-solving that I discovered.</p>
<blockquote>
<p><em>Question: How is it possible to build or invent a solution or discover a fact?</em></p>
</blockquote>
<h3>Mathematical method</h3>
<ul>
<li><p>Understand the problem</p>
</li>
<li><p>Find the connection between the data given and the unknown.</p>
</li>
</ul>
<h3>Turning problems into code</h3>
<ul>
<li><p>Understand the problem</p>
</li>
<li><p>Discover Inputs, Processes/methods, and Outputs</p>
</li>
<li><p>Driving Design with Tests</p>
</li>
<li><p>Writing the Algorithm in pseudocode</p>
</li>
<li><p>Writing the code</p>
</li>
</ul>
<h3>When coding: Debugging</h3>
<p>Read more on debugging written by <a href="https://hashnode.com/@GerCocca" class="user-mention" data-type="mention" title="German Cocca">German Cocca</a> 👉🏽 <a href="https://www.freecodecamp.org/news/what-is-debugging-how-to-debug-code/">here</a></p>
<p>References</p>
<ol>
<li><p>"<em>Exercises for Programmers 57 Challenges to Develop Your Coding Skills</em>*(Chapter 1)***" - Brian P. Hogan</p>
</li>
<li><p>"<em>How to Solve It</em>" - G. Polya</p>
</li>
<li><p>"<em>3D game programming for kids: create interactive worlds with JavaScript</em>*(Chapter 2)*"** - Chris Storm</p>
</li>
</ol>
]]></content:encoded></item></channel></rss>