#Snippets

October 08, 2024

#How to survive the Infrastructure and Operations world: from servers to Kubernetes

If you’re here, most likely, you’re trying to get into the infrastructure world. First, I’m so sorry. Second, the links here are my tried and true resources to navigate the fundamentals in this jungle. Hopefully, they’ll make a difference for you too.

How to host stuff: the easy way

Go for a Platform As A Service (PaaS). These cost more, but your time is also valuable:

How to host stuff: the hard way

Grab a generic server, SSH into it, and install whatever you need. It can be an AWS EC2 or, if you’re a cheapskate like me, you can live on the edge and use Oracle’s free servers.

Cools tools you can use to help:

  • portainer: cool and convenient web UI to manage Docker containers
  • watchtower: to update your Docker containers
  • FluxCD: to set up your own CD pipeline

How to survive AWS

AWS In Plain English is a great start.

To get a feeling for cloud architecture in the real world, take a peek at this playlist with quick videos of organizations showing the problems they had and how they fixed them (it’s a bit of an advertisement of AWS products, but still super valuable):

How to GitOps

As an introduction to the topic, refer to the video What is GitOps, How GitOps works and Why it’s so useful.

When it comes to implementation, these strategies are powerful starting points:

How to deal with networks and firewalls

No easy ways out here. On AWS, you’ll have to face the dreaded Security Groups. On Oracle Cloud, this tutorial should help.

And remember the classic port numbers:

  • SSH uses port 22
  • HTTP uses port 80
  • HTTPS uses port 443

How to deal with DNS

To make sure you got the basics covered, read these:

Now, get your hands dirty by creating live hosted zones with these tools:

How to think about Docker

Containers are a deeply misunderstood piece of technology. But you don’t need to know much to get value from them. Start here:

When you feel ready, go deeper:

How to survive Kubernetes

Don’t underestimate the official Kubernetes tutorials. They’re great.

Knowing what it does under the hood is a great way to feel comfortable with a tool. That’s why this half-hour presentation can leave a strong impression.

And, as Master Yoda once said, “The greatest teacher failure is”. With this spirit, peek at a fantastic read.

For debugging applications in Kubernetes, these resources can also be very powerful:

  • WhoAmI: a tiny server that returns OS information and HTTP request headers
  • Podinfo: similar to WhoAmI, but more feature rich
  • Telepresence: a powerful tool for debugging Kubernetes-native applications; (also check out these troubleshooting tips)

Section break
April 02, 2024

#Enable Intel integrated GPUs on Linux systems

Several misconfigurations may stop a Linux machine from having access to its integrated Intel GPU devices. Let’s check the most common problems and how to fix them.

Firstly, verify that the functionality is enabled on your BIOS. You will likely find relevant settings in the “Chipset” related sections. But if that doesn’t help, it’s time to play around with the kernel.

Make sure that the kernel module i915 isn’t blacklisted. It’s responsible for activating Intel drivers.

# If this returns anything like "blacklist i915", remove the line and reboot
cat /etc/modprobe.d/* | grep i915

Next thing, check your boot Grub configuration. Look for GRUB_CMDLINE_LINUX_DEFAULT and ensure it doesn’t mention nomodeset anywhere. Be aware, however, that removing it might disable HDMI output on your system, so prepare to use SSH access just in case.

cat /etc/default/grub* | grep GRUB_CMDLINE_LINUX_DEFAULT

# If you needed to change something, update the final configuration file
grub-mkconfig -o /boot/grub/grub.cfg

Finally, it’s possible that your iGPU device is not supported by the kernel version you’re currently running with. For instance, as of the time of writing, the default kernel for Ubuntu 22.04.4 LTS Server is 5.15, which doesn’t support 12th-generation Intel iGPUs (e.g., Alder Lake N95 and N100). In such a case, you need a newer kernel.

Ubuntu users can leverage the LTS Enablement Stacks to solve the problem.

apt install --install-recommends linux-generic-hwe-20.04

Intel iGPUs show up on Linux systems under /dev/dri.

# List the available iGPU-related devices
ls -la /dev/dri

# Check iGPU usage
apt install intel-gpu-tools
intel_gpu_top

Section break
March 29, 2024 (updated at: October 31, 2024)

#Quick, dirty and effective commands for hacking your way into Kubernetes

Sometimes, we want to avoid going through the trouble of creating YAML files for resources, checking its syntax, and versioning everything. Sometimes, we only want something up and running for a POC, a quick test, or a debugging session. For those moments, these commands can help.

Spin up debug pods:

# Debug application for testing k8s features
## See docs here: https://github.com/stefanprodan/podinfo
kubectl create deployment podinfo --image=stefanprodan/podinfo

Create Services for applications:

# Basic Service (ClusterIP)
## Service will expose 'port' / target application listens on 'target-port'
kubectl expose deployment podinfo --port=80 --target-port=9898 --name=podinfo-service

# Port forward a cluster application to your local machine (access http://localhost:8080)
kubectl -n default port-forward service/podinfo-service 8080:80

# Expose the raw Kubernetes API Server on your local machine
kubectl proxy

Create and expose a Postgres database:

kubectl create deployment postgres --replicas=0 --image=postgres:16.0
kubectl set env deployment/postgres POSTGRES_PASSWORD=pass POSTGRES_USER=user
kubectl scale --replicas=1 deployment/postgres
kubectl expose deployment/postgres --port=5432 --target-port=5432

Run commands in containers:

# Latest Ubuntu container that spins up and gives you a shell 
# Container is deleted when you close the connection
kubectl run ubuntu --rm -it --image=ubuntu -- bash

# Similar to the previous one, but keeps the debug container active
kubectl create deployment ubuntu --image=ubuntu -- sleep infinity
kubectl exec -it deploy/ubuntu -- bash
kubectl delete deploy/ubuntu

Get a bird’s eye view of your cluster state:

# See all running pods on your cluster (regardless of namespace)
kubectl get pods -A

# Increase verbosity level of kubectl's output (works with all its operations)
kubectl -v=8 get pods -A

# Combine the previous command with "watch" to get an evergreen view
watch kubectl get pods -A

# List all container images running
kubectl get pods -A -o=custom-columns='DATA:spec.containers[*].image'

# Read CPU and RAM consumption of your nodes (requires metrics-server)
kubectl top nodes

# Read CPU and RAM consumption of all your pods (requires metrics-server) 
kubectl top pods -A

# Get cluster cluster state including logs from control plane applications
kubectl cluster-info dump

Copy folders to and from running pods (requires tar on both local and remote environments):

tar cf - /local-source | kubectl exec -i my-pod -- tar xf - -C /remote-destination
kubectl exec my-pod -- tar cf - /remote-source | tar xf - -C /local-destination

Force all Pods of a Deployment to be recreated:

kubectl rollout restart deployment <deployment_name>

Remove finalizers from an existing object (make sure you really know what you’re doing, though):

kubectl patch <object_name> -p '{"metadata":{"finalizers":[]}}' --type=merge

Grab logs from a pod managed by a specific deployment:

kubectl logs deploy/my-app > my-app-`date '+%Y_%m_%d__%H_%M_%S'`.log

List permission-related information for your current user:

kubectl auth whoami
kubectl auth can-i --list

Some useful Helm commands to manage chart configurations:

# See all configurable options with detailed comments (equivalent to reading the values.yml)
# Example with the Coroot Helm Chart
helm show values coroot/coroot

# See all provided versions of a Helm Chart
helm search repo coroot/coroot --versions

# Get the values used when installing a Helm release
helm get values argocd -n argocd > argocd-values.yaml
helm upgrade argocd argo/argo-cd --version 5.9.1 --namespace argocd -f argocd-values.yaml

# Upgrade an existing Helm release in-place while keeping values provided during installation
helm upgrade argocd argo/argo-cd --reuse-values --force --version 5.9.1 --namespace argocd

# List all available revisions of a given release
helm -n nginx history ingress-nginx

# Get user-supplied values of a specific revision
helm -n nginx get values ingress-nginx --revision 3

# Rollback the release to a specific revision
helm -n nginx rollback ingress-nginx 3

In order to debug your own Helm Charts, you can leverage:

helm template --debug <release_name> <chart_path> -f <values_file_path>

helm install --generate-name --dry-run --debug <chart_path>

Notable domain names available in-cluster:

https://kubernetes.default (Kubernetes API Server)
http://<service_name> (reaches a service in the same namespace as the client)
http://<service_name>.<namespace>
http://<service_name>.<namespace>.svc.cluster.local

References

Official kubectl Quick Reference

Section break
October 30, 2022

#How to emulate the x86_64 architecture for development environments on an Apple M1

First of all, let’s get some terminology out of the way. In the wild, the terms x86, x86_64, x64, and amd64 can be used to refer to the same very common Intel-based architecture we find on most PCs and servers. In the same vein, the architecture used on Apple M1 processors may be referred to as ARM, ARM64, ARMv8 64, and aarch64. It’s kind of a mess, but don’t think too much about it. Just remember the names.

The most convenient way of running foreign development environments on your M1 is using Docker or Lima, depending on your needs. Both leverage QEMU under the hood to handle the actual CPU emulation and do so with different degrees of performance penalties.

Docker

If you’re running Docker Desktop, you already have QEMU set up for you. If you’re using another platform as your Docker provider, you might need to run this:

docker run --privileged --rm tonistiigi/binfmt --install all

For a quick and dirty approach, you can use a disposable container for running your environment. For instance, the following command will spawn a Python x86_64 workspace, mount the current folder as a volume, and open a shell for you.

docker run -it --rm --platform="linux/amd64" -v ${PWD}:/app -w /app python:3.10 bash

It’s possible to reuse a container if you want to, but they’re ephemeral by nature, so don’t grow too attached to them. If you’re going down this route, remove --rm and add the --name flag to the command above to make your life easier later.

You can get to know the architecture options you have available for any container image by running:

# Get data (including supported architectures) about an image stored in a remote registry (e.g. Docker Hub)
docker manifest inspect python:3.10

# Get data on an image locally available on your machine
docker image inspect python:3.10

Pro tip: if you execute docker run without specifying a platform, the created container will use the last image pulled regardless of its architecture. This behavior is confirmed to work on Docker 20.10. See an example below:

docker pull python:3.10 
docker run python:3.10 uname -m
# aarch64

docker pull --platform="linux/amd64" python:3.10
docker run python:3.10 uname -m
# x86_64

If your local software project uses a Dockerfile to manage its image build process, you can add the --platform flag to the FROM clause.

FROM --platform="linux/amd64" python:3.10

Lima

For developers accustomed to working with Linux, Lima can prove itself a powerful addition to their toolbox. Its main use case is running containerd on MacOS, but it can also act as a Docker Desktop replacement or even do generic Virtual Machine management. We’re going to focus on this last one today.

This approach has a more significant performance impact than Docker, but it can be a good combination of compatibility and ease of use for projects that don’t run well within containers.

Let’s use Ubuntu as our base OS here.

limactl start ubuntu-lts

When prompted, select “Open an editor to review or modify the current configuration”. The arch field determines the architecture being used by our VM, so set it to x86_64. Also, feel free to modify mounts to fit your needs. You’ll likely add a relevant folder from your host machine there (try not to use your entire home folder for that). And don’t forget to add writable: true to it so the guest machine can make changes to your files. Finally, you might want to change the containerd field to false (since we won’t need it).

To better understand of all the customization options available on the file you’ve just edited, read the comments on this file.

After your VM has started, run:

# Open a shell in your new guest machine
limactl shell ubuntu-lts

# Confirm the architecture being run (you should see "x86_64" as the output)
uname -m

Now, you have a traditional Linux environment ready with volume mounting and port forwarding out of the box. To check if both are running smoothly, go to a mounted folder and run:

python3 -m http.server 9000

On the browser of your host machine, you should be able to access http://localhost:9000 and see a simple web server displaying your files.

Section break
October 23, 2022

#Create a Docker image from scratch using an OS as base

For the purposes of this example, we’ll be using Raspberry Pi OS 64-bit as the base filesystem for our new Docker image. Since this operating system lacks an official one on Docker Hub, it feels like a good fit. However, the principles described here should work well enough for any OS.

Choose an image to download here and save it locally. With unxz, uncompress the file.

unxz 2022-04-04-raspios-bullseye-arm64-lite.img.xz

Since Raspberry Pi OS is distributed as a disk image and not a partition image, we need to mount it with an offset to avoid getting data from the boot partition. Keep in mind that our goal here is to create a Docker image and, for that, we don’t need boot files.

Find the desired partition offset using fdisk.

fdisk -l 2022-04-04-raspios-bullseye-arm64-lite.img

You’re likely to see an output similar to the following:

Disk 2022-04-04-raspios-bullseye-arm64-lite.img: 1,86 GiB, 2000683008 bytes, 3907584 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x0ee3e8a8

Device                                      Boot  Start     End Sectors  Size Id Type
2022-04-04-raspios-bullseye-arm64-lite.img1        8192  532479  524288  256M  c W95 FAT32 (LBA)
2022-04-04-raspios-bullseye-arm64-lite.img2      532480 3907583 3375104  1,6G 83 Linux

From this, we can see that the block size used is 512 and the start block for the root partition is 532480. Therefore, our offset will be 532480 * 512 = 272629760.

Now, let’s mount the relevant partition, gather its contents as a tar file, generate the image and push it to a registry.

mkdir rootfs

mount -o loop,offset=272629760 2022-04-04-raspios-bullseye-arm64-lite.img rootfs

# Keep in mind that a container image is, under the hood, a tar file of tar files
tar -C rootfs -c . | docker import --platform=linux/arm64 - laury/raspberry-pi-os:bullseye-20220404
docker push laury/raspberry-pi-os:bullseye-20220404

umount rootfs

Note: as the time of writing, the --platform flag is ignored by docker import (tested on version 20.10). So, the host architecture is used to determine the platform supported by the generated image. This bug will be resolved on the 22.06 release (see this PR for more information).

Section break
October 12, 2022

#Troubleshoot CORS related issues on API requests

Cross Origin Resource Sharing (a.k.a. CORS) is a powerful, and yet misunderstood, web standard for protecting web APIs from abuse. If you’re anything like me, however, you had your fair share of wasted work hours trying to deal with it from time to time. The concept seems deceptively simple, but the devil is in the details.

First of all, its important to understand that CORS compliance is an optional standard and is mostly enforced by browsers, like Chrome, Firefox and Safari. Therefore, it’s unlikely that you will face issues with it while working with libraries like Python’s Requests or CLI tools like curl.

However, if you have a frontend application running on your browser with a domain like, let’s say, http://localhost:3000, API calls directed to different domains, like https://laury.dev, will be preceded by a preflight request. This determines if the browser will execute the main request or not.

When having problems with this process, you may see an error similar to this on your Chrome console:

Access to XMLHttpRequest at ‘https://laury.dev from origin ‘http://localhost:3000 has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

To reproduce the problem in a more isolated manner, you can use curl to make a preflight request manually. The important parts here are:

  • the HTTP method has to be OPTIONS
  • three headers must be present: Access-Control-Request-Method, Access-Control-Request-Headers and Origin. Be wary, however, that the domain stated on Origin must mention protocol, FQDN, port (if any is being used), and no trailing slash.

Here’s an example:

curl -IX OPTIONS https://google.com \
    -H "Access-Control-Request-Method: POST" \
    -H "Access-Control-Request-Headers: origin, x-requested-with" \
    -H "Origin: http://mywebsite.com:3000"

For a preflight request to be considered successful, all of the following must be true:

  • the HTTP status on the response is within 200-299
  • the response contains these headers: Access-Control-Allow-Methods, Access-Control-Max-Age and Access-Control-Allow-Origin. One extra catch here is that this last header cannot present the value * when the main request contains authentication information (like cookies). If it does, the preflight will be considered a failure by the browser. In this case, the header has to mention the origin domain explicitly.

Kubernetes users running Ingress Nginx

To manage CORS headers on Ingress Nginx, you may use these annotations on Ingress objects. Even objects that only manage paths inside domains will work with them, so there’s no need to enable CORS on the entire domain if you don’t feel like it.

Further reading

Light overview of preflight requests

Official specification of the Fetch standard for preflight requests

Section break
October 03, 2022

#Use Lima as a Docker Desktop replacement on MacOS

Lima (short for Linux on Mac) is a Virtual Machine management tool for running Linux boxes (think Vagrant, but for development tooling). It focuses on offering a convenient way to run and interact with containerd, but its high customizability allows for a lot more. The examples folder in its repository gives a good idea of its capabilities.

For running Lima as a Docker Desktop replacement, the official example provided is a great starting point with a few shortcomings:

  • mounting only a temporary folder (/tmp/lima) as writable
  • not including support for building and running multi-arch containers

To circumvent these issues, download this YAML and run:

limactl start docker.yml

If docker login gives you trouble, make sure that ~/.docker/config.json uses osxkeychain as the credsStore. Example:

{
  "auths": {},
  "credsStore": "osxkeychain"
}

Considerations about chown and chmod

Lima uses Reverse SSHFS by default to mount folders from host to guest. This strategy provides good compatibility but can cause problems in some cases. For instance, Containers that run chown on startup like Jupyter and Gogs might not be able to start up at all.

In some situations, you might need to experiment with 9p and reverse-sshfs as mount types.

Further reading

Filesystem mounts available for Lima

VM customization options

Section break
July 03, 2022 (updated at: February 02, 2024)

#Use iptables to allow only Cloudflare HTTP requests to target your server

If you’re using Cloudflare to protect HTTP endpoints, it might be worth it to block any web traffic that doesn’t come from their servers.

To do that, let’s use iptables. The following commands will do the trick, but take note that these IPs might change in the future.

# Allow IPv4 traffic
iptables -I INPUT -p tcp -m multiport --dports http,https -s 103.21.244.0/22 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 103.22.200.0/22 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 103.31.4.0/22 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 104.16.0.0/13 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 104.24.0.0/14 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 108.162.192.0/18 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 131.0.72.0/22 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 141.101.64.0/18 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 162.158.0.0/15 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 172.64.0.0/13 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 173.245.48.0/20 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 188.114.96.0/20 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 190.93.240.0/20 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 197.234.240.0/22 -j ACCEPT
iptables -I INPUT -p tcp -m multiport --dports http,https -s 198.41.128.0/17 -j ACCEPT

# Allow IPv6 traffic
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2400:cb00::/32 -j ACCEPT
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2606:4700::/32 -j ACCEPT
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2803:f800::/32 -j ACCEPT
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2405:b500::/32 -j ACCEPT
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2405:8100::/32 -j ACCEPT
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2a06:98c0::/29 -j ACCEPT
ip6tables -I INPUT -p tcp -m multiport --dports http,https -s 2c0f:f248::/32 -j ACCEPT

Now, block everything else on ports 80 and 443.

iptables -A INPUT -p tcp -m multiport --dports http,https -j DROP
ip6tables -A INPUT -p tcp -m multiport --dports http,https -j DROP

Test your new rules and save your changes to persist them across reboots.

netfilter-persistent save

Further reading

Cloudflare’s IP list

Official Cloudflare recommendations to protect your origin servers

Section break
July 02, 2022 (updated at: April 28, 2024)

#Basic survival guide and commands for iptables

iptables is a Linux firewall tool that manages packet routing and can block or allow traffic based on rules like a packet’s origin or destination.

Its settings are organized in tables which contain sets of rules, called chains, that will filter data packets.

# Lists existing rules with the "specification" syntax
iptables -S

# Delete a rule by its specification string
iptables -D <spec>

# List existing rules by chain and alongside packet matching statistics
iptables -L -v --line-numbers

# Delete a rule by its row index
iptables -D <chain> <num>

# Reset the packet counters on all chains
iptables -Z

# Accept all requests from an IP
iptables -I INPUT -s <ip> -j ACCEPT

# Drop all requests from an IP
iptables -I INPUT -s <ip> -j DROP

# Accept all incoming TCP traffic to a given port
iptables -I INPUT -m state --state NEW -p tcp --dport <port> -j ACCEPT

# Drop all requests from a range of IPs
iptables -I INPUT -m iprange --src-range <ip_range_start>-<ip_range_end> -j DROP

# Drop all traffic (must be run after acceptance rules; `-A` stands for "append")
iptables -A INPUT -j DROP

# Delete all current rules
iptables -F

# Restore iptables rules from last persisted version
iptables-restore < /etc/iptables/rules.v4

# Persist changes to disk
/sbin/iptables-save
# Depending on your setup, you may need to run another command instead
netfilter-persistent save

References

How the Iptables Firewall Works

How To List and Delete Iptables Firewall Rules

Section break
June 26, 2022 (updated at: February 03, 2024)

#Protect public URLs with ModSecurity for Nginx on a Kubernetes cluster

If you’re using Ingress Nginx to manage public endpoints, ModSecurity is already installed but disabled by default. To enable it, add the following to the ConfigMap ingress-nginx-controller:

allow-snippet-annotations: "true"
enable-modsecurity: "true"
enable-owasp-modsecurity-crs: "true"

To customize the Paranoia and Anomaly Score threshold, you must change rules 900000 and 900110. But remember that higher Paranoia levels lead to stricter rules, and a lower anomaly threshold leads to a more tolerant setup.

modsecurity-snippet: |-
    SecRuleEngine On
    SecAuditEngine Off #  Use RelevantOnly for debugging
    SecAuditLog /dev/stdout
    SecAuditLogFormat JSON
    SecAuditLogType Serial
    SecAuditLogParts ABIJDEFHZ
    SecDebugLog /dev/stdout
    SecDebugLogLevel 3
    SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=5"
    SecAction "id:900110,phase:1,nolog,pass,t:none,setvar:tx.inbound_anomaly_score_threshold=4,setvar:tx.outbound_anomaly_score_threshold=3"
    Include /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf

Customize ModSecurity rules on a per-host basis

It’s possible to adjust the ModSecurity configuration for each Ingress object on the cluster using annotations.

annotations:
  nginx.ingress.kubernetes.io/enable-modsecurity: "true"
  nginx.ingress.kubernetes.io/enable-owasp-core-rules: "false"
  nginx.ingress.kubernetes.io/modsecurity-snippet: |
    SecRuleRemoveById <rule_id>

How to completely customize modsecurity.conf

With the modsecurity-snippet option, adding custom configuration to ModSecurity is possible. However, you can override the modsecurity.conf entirely if you want.

To do so, first copy the file inside the pod.

kubectl -n ingress-nginx cp <ingress-controller-pod-name>:/etc/nginx/modsecurity/modsecurity.conf ./modsecurity.conf

Modify it as required and save the file in a ConfigMap.

kubectl -n ingress-nginx create configmap modsecurityconf --from-file=modsecurity.conf

If you need to change the file again locally, you can update the ConfigMap by doing the following.

kubectl -n ingress-nginx create configmap modsecurityconf \
  --from-file=modsecurity.conf -o yaml \
  --dry-run=client | kubectl apply -f -

To mount the ConfigMap to the controller deployment, create a patch file.

spec:
  template:
    spec:
      volumes:
      - name: modsecurityconf
        configMap:
          name: modsecurityconf
      containers:
      - name: controller
        volumeMounts:
        - name: modsecurityconf
          mountPath: "/etc/nginx/modsecurity/modsecurity.conf"
          subPath: modsecurity.conf
          readOnly: true

And then apply the patch file.

kubectl -n ingress-nginx patch deployment/ingress-nginx-controller --patch-file deployment-patch.yml

Further reading

ConfigMap options for Ingress Nginx

Annotations options for Ingress objects

Overview of Anomaly Scoring

Working with Paranoia Levels

Handling False Positives with the OWASP ModSecurity Core Rule Set

Section break
June 24, 2022

#Host publicly accessible web URLs from a home server using a remote proxy

Ideally, you would be able to open ports 80 and 443 of your public IP address and update DNS entries with solutions like ddclient or DNS-O-Matic.

However, if your ISP blocks direct traffic to those ports, using a remote server and SSH tunneling can bypass such limitations. With this setup, you can use Kubernetes ingresses as usual. From the ingress perspective, HTTP traffic will reach your home server directly. Therefore, tooling like Nginx and Cert Manager will work with no unique tweaks.

Any Linux box with a public IP can be your remote proxy, but Oracle Cloud has a generous free tier and can be a good starting option. Remember that the smallest available machine is probably enough for your needs since it will only forward traffic and not act on it.

After setting up the remote server with your provider, ensure it has an SSH server running. We’re going to use it to forward network packets for us.

On your remote instance, edit the /etc/ssh/sshd_config file and add GatewayPorts yes to it. Apply the new configuration with service sshd restart.

If you opted for an Oracle Cloud server, go to the Ingress Rules section of the Virtual Cloud Network panel and open ports 80 and 443. Also, adjust iptables in the instance to allow public traffic to ports 80 and 443.

iptables -I INPUT 6 -m state --state NEW -p tcp --dport 80 -j ACCEPT
iptables -I INPUT 6 -m state --state NEW -p tcp --dport 443 -j ACCEPT
netfilter-persistent save

From your home server, you can run a quick test, as long as you already have a HTTP server running locally. Don’t forget to generate a new SSH key for your local server and authorize it on the remote machine. If everything went smoothly until now, you can access a local web endpoint using the remote server IP as the DNS entry for your domain.

ssh -N -R 80:<local_ip>:80 root@<remote_ip>

If the port forwarding worked, you can now create deployment files so that Kubernetes can keep the tunnel running. Customize and apply the following to your local cluster.

apiVersion: v1
kind: Namespace
metadata:
  name: proxy-router

--
apiVersion: apps/v1
kind: Deployment
metadata:
  name: autossh-80
  namespace: proxy-router
  labels:
    app: autossh-80
spec:
  replicas: 1
  strategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
    type: RollingUpdate
  selector:
    matchLabels:
      app: autossh-80
  template:
    metadata:
      labels:
        app: autossh-80
    spec:
      containers:
      - name: autossh-80
        image: jnovack/autossh:2.0.1
        env:
          - name: SSH_REMOTE_USER
            value: "root"
          - name: SSH_REMOTE_HOST
            value: "<remote_server_ip>"
          - name: SSH_REMOTE_PORT
            value: "22"
          - name: SSH_TUNNEL_PORT
            value: "80"
          - name: SSH_BIND_IP
            value: "0.0.0.0"
          - name: SSH_TARGET_HOST
            value: "<local_server_ip>"
          - name: SSH_TARGET_PORT
            value: "80"
          - name: SSH_MODE
            value: "-R"
        volumeMounts:
          - name: keys
            mountPath: /id_rsa
      nodeName: <node_with_ingress_enabled>
      hostNetwork: true
      volumes:
        - name: keys
          hostPath:
            path: <node_path_for_ssh_keys>
            type: File

To redirect port 443, create another deployment using the previous one as a reference.

Any public URLs should have their DNS entries pointing to the remote server’s IP.

Section break
February 12, 2022

#Manually control a webcam using the command line on Linux

Any webcam compatible with the USB Video Class (UVC) standard should expose an API usable via v4l2-ctl.

Use the following command to check which devices are connected to your computer at the moment.

v4l2-ctl --list-devices

To check the available controllers for your device, run:

v4l2-ctl --list-ctrls-menus

For me, this feature is specially useful for managing focus, brightness and sharpness. The following commands work well with Logitech C922 at least.

# Manual focus
v4l2-ctl -d /dev/video0 --set-ctrl=focus_auto=0  # default=1
v4l2-ctl -d /dev/video0 --set-ctrl=focus_absolute=0

# Manual light exposure controls
v4l2-ctl -d /dev/video0 --set-ctrl=exposure_auto=1  # default=3 (Aperture Priority Mode)
v4l2-ctl -d /dev/video0 --set-ctrl=exposure_absolute=166  # min=12 max=664 default=166

# Brightness and other useful controls
v4l2-ctl -d /dev/video0 --set-ctrl=sharpness=170  # default=128
v4l2-ctl -d /dev/video0 --set-ctrl=brightness=150  # default=128
v4l2-ctl -d /dev/video0 --set-ctrl=backlight_compensation=1  # default=0

Further reading

v4l2-ctl manual

List of compatible USB Video Class (UVC) devices

Section break
November 24, 2021

#Pull and push container images from and to AWS ECR repositories via Docker CLI

To use the regular Docker CLI with Amazon Elastic Container Registry (Amazon ECR), you have to acquire specific login credentials for it first. Assuming you already got basic AWS credentials, run the following to pass those on to Docker. Just note that, below, AWS_ACCOUNT_ID refers to the repository owner, which may not be your own AWS account.

aws ecr get-login-password --region <AWS_REGION> | docker login -u AWS --password-stdin https://<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com

# Example for the official Sagemaker ECR repository
aws ecr get-login-password --region us-east-1 | docker login -u AWS --password-stdin https://683313688378.dkr.ecr.us-east-1.amazonaws.com

Once you’re logged in, list all available container images in a repository with the following:

aws ecr describe-images --region <AWS_REGION> --registry-id <AWS_ACCOUNT_ID> --repository-name <REPOSITORY_NAME>

# Example for the official Sagemaker ECR repository
aws ecr describe-images --region us-east-1 --registry-id 683313688378 --repository-name sagemaker-scikit-learn

Further reading

Prebuilt Amazon SageMaker Docker Images for Scikit-learn and Spark ML

Section break
November 14, 2020

#Get a Docker container's IP from the command line

When dealing with container IPs, it’s important to take note of which network each container is connected to and which network mode it’s using. The default network mode for a Docker installation is bridge, so I’ll assume you’re using it too.

Any container started with docker run will be attached to a default bridge network. To get its IP on that network, run:

docker inspect --format "{{.NetworkSettings.Networks.bridge.IPAddress}}" <container_name>

Note: the --format flag accepts Go’s template syntax. Take a look at some Docker usage examples.

For containers started with docker-compose, a new bridge network is created. Its default name will be <folder_name>_default, so the above command would look like this:

docker inspect --format "{{.NetworkSettings.Networks.<folder_name>_default.IPAddress}}" <container_name>

In both cases, the received IPs are reachable from the host and from other containers in the same bridge.

Further reading

Official docs on Docker bridge networks

Section break
November 13, 2020

#Simple Docker one-liner to run Jupyter Notebook with Python

The following command will start a Jupyter instance mounting the current folder.

docker run --rm -v ${PWD}:/home/jovyan -p 8888:8888 -e JUPYTER_ENABLE_LAB=yes -e CHOWN_HOME=yes jupyter/scipy-notebook:latest

Explanation about the chosen environment variables:

JUPYTER_ENABLE_LAB=yes  # enable the superior "lab" interface
CHOWN_HOME=yes          # ensure the home directory is writable for Jupyter

Further reading

List of all environment variables available using Docker

Official documentation about Jupyter Docker stacks

Section break
November 12, 2020

#Validate a Jenkinsfile without running it

Jenkins servers have a simple validator to check build files for basic errors without running them. Let’s use curl to send a local file named MyJenkinsfile:

curl --user "<username>:<password>" -X POST -F "jenkinsfile=<MyJenkinsfile" https://<jenkins_host_address>/pipeline-model-converter/validate

The response body will contain validation info.

Section break
September 09, 2020 (updated at: December 25, 2022)

#Install Kubernetes on Raspberry Pi OS

We will use k3s, a lightweight Kubernetes distribution, to get the most out of our hardware. This tutorial uses a Raspberry Pi 4 and the latest Raspberry Pi 32-bit (formerly known as Raspbian). The 64-bit version is pretty much the same for this step-by-step.

Flash the OS image on your SD Card and, if necessary, add Wi-Fi credentials so you can access it.

Enable cgroups support and disable IPv6 by appending the following on /boot/cmdline.txt (remember that /boot refers to the boot partition on your SD Card).

cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory ipv6.disable=1

I also recommend disabling swap.

dphys-swapfile swapoff && systemctl disable dphys-swapfile.service

If your workloads don’t require GPU, you may want to change the Memory Split to 16 using raspi-config. You’ll have a little extra RAM this way.

You can force the OS to use legacy iptables to ensure compatibility with older Kubernetes versions, but this step is optional.

iptables -F
update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy

Install and test k3s.

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable=traefik" sh -

# After a minute, you should be able to test it
kubectl get nodes

The kubeconfig yaml will be available at /etc/rancher/k3s/k3s.yaml.

If you have worker nodes to add, use the following command. Find the cluster token at /var/lib/rancher/k3s/server/node-token on the server node.

curl -sfL https://get.k3s.io | K3S_URL=https://<server_ip>:6443 K3S_TOKEN="<cluster_token>" sh -

Maintenance

Things should run smoothly from here, but if they don’t, the next few commands might help:

# See current status of k3s
systemctl status k3s

# See system logs (including k3s)
journalctl -xe

# Uninstall k3s
/usr/local/bin/k3s-uninstall.sh

Kubernetes automatically performs cleanup of unused containers and images on nodes. Still, if you’re under the impression that your filesystem is getting bloated by these, you can force a safe deletion with:

k3s crictl rmi --prune

Further reading

Configuration options for k3s

Section break
September 08, 2020 (updated at: October 09, 2022)

#Use the RGB Cooling HAT, by Yahboom, with Docker on your Raspberry Pi

This tutorial was tested on a Raspberry Pi 4 with RGB Cooling HAT model “YB-EBV02 VER1.1”, by Yahboom, on 32-bit and 64-bit OS.

The hardware creators do provide installation documentation and official code, but putting all parts together can be challenging.

If you’re willing to use Docker and run the 32-bit version of Raspberry Pi OS, you can activate everything with one command (after enabling I2C with raspi-config).

docker run -d --restart unless-stopped --network host --privileged laury/raspberry-pi-rgb-cooling-hat:latest

To run the software on a 64-bit OS, you’ll need to enable Qemu first and then run the container with the --platform flag.

docker run --privileged --rm tonistiigi/binfmt --install all

docker run -d --restart unless-stopped --platform="linux/arm/v7" --network host --privileged laury/raspberry-pi-rgb-cooling-hat:latest

The container image tagged with latest enables the default functionality of the accessory. If you’d like for the RGBs to the turned off, use the v1.1.0-noRGB image tag.

You can also build the Docker image yourself using the following Dockerfile. Just remember to target ARMv7 architecture. The easiest way to achieve that is to build in the Raspberry Pi itself.

FROM python:2.7

RUN apt update &&\
    apt install -y i2c-tools git

RUN pip install Adafruit-GPIO==1.0.3 \
                Adafruit-BBIO==1.2.0 \
                Adafruit-SSD1306==1.6.2 \
                smbus==1.1.post2 \
                image==1.5.32 \
                vcgencmd==0.1.1 \
                RPi.GPIO==0.7.1

WORKDIR /tmp
RUN git clone --depth 1 https://github.com/raspberrypi/firmware.git &&\
    cp -a firmware/hardfp/opt/vc/* /usr

RUN git clone https://github.com/YahboomTechnology/Raspberry-Pi-RGB-Cooling-HAT.git &&\
    unzip Raspberry-Pi-RGB-Cooling-HAT/4.Python\ programming/RGB_Cooling_HAT.zip &&\
    mkdir /app &&\
    cp -a RGB_Cooling_HAT/* /app &&\
    rm -rf /tmp/*

WORKDIR /app
CMD python /app/RGB_Cooling_HAT.py

Section break
August 29, 2020 (updated at: May 15, 2022)

#Connect to a headless Raspberry Pi through SSH for the first time

This will work for Raspberry Pi OS (formerly known as Raspbian) and no monitor or keyboard are needed.

After flashing the OS, create a wpa_supplicant.conf file on the boot partition of your SD Card (not the boot folder).

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=<Insert 2 letter ISO 3166-1 country code here>

network={
 ssid="<Name of your wireless LAN>"
 psk="<Password for your wireless LAN>"
}

On the same partition, create an empty file called ssh. It will instruct the OS to enable the SSH server. Also, create your SSH user by creating a file named userconf.txt.

touch ssh

echo <user>:`echo '<pass>' | openssl passwd -6 -stdin` > userconf.txt

To find the Raspberry Pi in your local network, you can use nmap. Assuming your local addresses start with 192.168.0, run:

nmap -sn 192.168.0.0/24

If your Pi connected correctly, you will see something similar to the following in the output.

Nmap scan report for raspberrypi (<IP>)
Host is up (0.11s latency).
MAC Address: <MAC> (Raspberry Pi Trading)

Further reading

Official docs on remote access and Wi-Fi settings.

Security concerns on default user and password for SSH

Section break
August 26, 2020

#Combine kubectl and JSONPath to read data from a Kubernetes cluster

To customize how data about cluster objects is presented on the terminal, its possible to use JSONPath syntax. Keep in mind that, under the hood, Kubernetes’ API already sends and receives data in JSON format (the YAMLs we’re used to see are an abstraction to ease reading and editing).

Since data hierarchy on the YAML representation may be a bit off, start by getting a pure JSON version of the data you want to filter.

In the following examples, let’s use a specific deployment.

kubectl -n <my_namespace> get deploy/<my_deployment> -o json

Get only the deployment name.

kubectl -n <my_namespace> get deploy/<my_deployment> -o jsonpath='{.metadata.name}'

range is one of the improvements to JSONPath available here. It iterates over JSON lists and can be paired with @ to make references to each item.

Using both, you can, for instance, print image names for all containers created by a deployment (with line breaks).

kubectl -n <my_namespace> get deploy/<my_deployment> -o jsonpath='{range .spec.template.spec.containers[*]}{@.image}{"\n"}{end}'

A good use case for JSONPath is to list annotations for every deployment in a cluster. Tools like Velero depend on the proper configuration of annotations to do volume backups, so it’s convenient to have a consolidated output.

The following command prints namespace, name and template annotations for every deployment. Additionally, it pipes the list to grep so we can have color highlights.

kubectl get -A deployments -o jsonpath='{range .items[*]}{@.metadata.namespace}{" / "}{@.metadata.name}{"\n"}{@.spec.template.metadata.annotations}{"\n"}{"\n"}{end}' | grep --color "backup.velero.io/backup-volumes\|$"

Note: grep is also matching line endings to ensure all lines are printed.

Further reading

Kubernetes docs on JSONPath

JSONPath evaluator

JSONPath syntax details

Section break
August 23, 2020

#Create, test and run a Java project with Gradle using Docker and no IDE

In order to create and manage Java projects, its common to employ the help of an IDE like IntelliJ IDEA or Eclipse.

However, if you would rather use only the command line, Docker can be a great help.

For convenience and reproducibility, create a Dockerfile in your local filesystem with the following contents.

FROM openjdk:14

ENV GRADLE_HOME=/opt/gradle/gradle-6.4.1
ENV PATH=${GRADLE_HOME}/bin:${PATH}

RUN yum install -y wget unzip

RUN wget https://services.gradle.org/distributions/gradle-6.4.1-bin.zip -P /tmp &&\
    unzip -d /opt/gradle /tmp/gradle-*.zip &&\
    rm -rf /tmp/*

Build the Docker image and run a container (takes a while). Again, for convenience, -v and -w are used so that file creations and modifications made inside the container are reflected outside.

docker run --rm -it -v ${PWD}:/app -w /app $(docker build -q .) bash

Now, we can use gradle to create and manage a project from the command line.

# Create a new project
# The "application" template most likely will suit your needs
gradle init

# Run unit tests (if you enabled JUnit during project creation)
gradle test

# Run your code
gradle run

Write source code in src/main/java/<your_package> and test code in src/test/java/<your_package>.

Further reading

Official Gradle guide for Java applications

Docker command line

Gradle command line

List of JUnit Assertions

Section break
August 20, 2020 (updated at: February 02, 2024)

#Issue Let's Encrypt certificates for domains in a Kubernetes cluster

We will use cert-manager to issue HTTPS certificates for domains served publicly by a Kubernetes cluster.

The application can be installed via Helm, but it’s recommended to install CRDs (Custom Resource Definitions) separately. That may come in handy if you need to delete cert-manager without losing already issued certificates. Don’t forget to change the “version” accordingly on all commands.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.1/cert-manager.crds.yaml

Add JetStack charts to your Helm client.

helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update

Install the application.

helm upgrade --install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --version v1.14.1 \
  --set ingressShim.defaultIssuerName=letsencrypt-prod \
  --set ingressShim.defaultIssuerKind=ClusterIssuer \
  --set ingressShim.defaultIssuerGroup=cert-manager.io

To issue actual certificates, you will need a production Issuer or ClusterIssuer. The following yaml defines a ClusterIssuer and assumes that you have a nginx ingress controller. If it suit your needs, apply it to your cluster.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: <your_email>
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          ingressClassName: nginx

Verify if your set up is working.

$ kubectl get clusterissuers -A
NAME               READY   AGE
letsencrypt-prod   True    30m

The cert-manager stack will act upon ingresses that comply with a couple of conditions to generate HTTPS certificates. The following example illustrates that. Take special note of the annotations and the “secretName” property under “tls”.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/tls-acme: "true"
  name: nginx-ingress
  namespace: ingress-testing
spec:
  rules:
  - host: <public_URL>
    http:
      paths:
      - backend:
          serviceName: <service_name>
          servicePort: 80
  tls:
  - hosts:
    - <public_URL>
    secretName: <secret_name_of_your_choosing>

Further reading

Official documentation on the installation process

Information about Cluster Issuers

Supported Ingress annotations

Section break
August 11, 2020

#Pretty-print JSON files using the command line

If you have either Python 2 or 3 installed, use the “json.tool” module to pretty-print.

python -m json.tool <json_file> | less

You can also stream input to Python instead of passing a file directly.

cat <json_file> | python -m json.tool | less

This may be useful for viewing large minified JSON files that could slow down GUI applications.

Section break
August 03, 2020

#Quick usage examples of "find" on the command line

Search recursively for file names using a regex pattern. Use -name for a case sensitive search.

find . -iname '*<partial_name>*'

To exclude results located in certain paths, use -not -path:

find . -iname '*<partial_name>*' -not -path "*/.venv/*" 

Execute a command with every result found.

find . -iname '*.md' -type f -exec ls -lh {} \;

List all files in the current folder (recursively) that contain every string mentioned in the search parameters. You can chain as many search terms as you want. Just remember that the last grep needs to have -l as a parameter instead of -q.

find . -type f -exec grep -q '<string1>' {} \; -exec grep -q '<string2>' {} \; -exec grep -l '<string3>' {} \;

Note: every command needs to be terminated by ; or +. But these signs may need to be escaped as in ; or \;. Outputs given by find will be placed on {}. Multiple uses of -exec are allowed.

Section break
August 02, 2020

#Read Kubernetes API data using Golang

Get the API client.

import (
  "fmt"
  "context"
  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
  metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// use the current context in kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", "<path_to_KUBECONFIG>")
if err != nil {
  panic(err.Error())
}

// create the clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
  panic(err.Error())
}

Usage examples:

  • read data about servers
nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
if err != nil {
  panic(err.Error())
}
  • read data about pods
namespace := "example"  // leave empty to get data from all namespaces
podList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
  panic(err.Error())
}

In the above examples, ListOptions can take two important strings, field selectors and label selectors. This can be used for filtering results.

metav1.ListOptions{
  LabelSelector: "labelName=labelKey",
  FieldSelector: "spec.nodeName=<node_name>",  // Example for filtering by node name
}

Field selectors and label selectors on this case operate in the same way that the CLI options for kubectl do. So, the same rules apply here.

Further reading

Official docs on Field Selectors and Labels.

Section break
August 02, 2020

#Read Kubernetes metrics-server data using Golang

First, get the metrics API client.

import (
  "fmt"
  "context"
  "k8s.io/client-go/kubernetes"
  "k8s.io/client-go/tools/clientcmd"
  metricsv "k8s.io/metrics/pkg/client/clientset/versioned"
)

// Get the config
config, err := clientcmd.BuildConfigFromFlags("", "<path_to_KUBECONFIG>")
if err != nil {
  panic(err.Error())
}

// Get the metrics client
metricsClientset, err := metricsv.NewForConfig(config)
if err != nil {
  panic(err.Error())
}

Get the data and store it in podMetricsList array.

namespace := "example"  // leave empty to get data from all namespaces
podMetricsList, err := metricsClientset.MetricsV1beta1().PodMetricses(namespace).List(context.TODO(), metav1.ListOptions{})
if err != nil {
  panic(err.Error())
}

Iterate over the results found.

for _, v := range podMetricsList.Items {
  fmt.Printf("%s\n", v.GetName())
  fmt.Printf("%s\n", v.GetNamespace())
  fmt.Printf("%vm\n", v.Containers[0].Usage.Cpu().MilliValue())
  fmt.Printf("%vMi\n", v.Containers[0].Usage.Memory().Value()/(1024*1024))
}

Note: if you’re inspecting pods that may have more than one container, you’ll need to iterate over v.Containers as well.

Section break
August 01, 2020 (updated at: October 03, 2022)

#Receive data from Kubernetes API using curl

Start by running kubectl’s proxy. With its help, we avoid dealing with authentication headers.

kubectl proxy

Test the connection with a basic call.

curl http://localhost:8001/api/

To check the API groups available, access the root.

curl http://localhost:8001/apis/

Based on the group listing, you can infer URLs for resources.

For cluster-scoped resources, use:

/apis/<GROUP>/<VERSION>/<RESOURCETYPE>
/apis/<GROUP>/<VERSION>/<RESOURCETYPE>/<NAME>

And, for namespace-scoped resources:

/apis/<GROUP>/<VERSION>/<RESOURCETYPE>
/apis/<GROUP>/<VERSION>/namespaces/<NAMESPACE>/<RESOURCETYPE>
/apis/<GROUP>/<VERSION>/namespaces/<NAMESPACE>/<RESOURCETYPE>/<NAME>

To get deployment data, for example, the API Group is “apps”, Version is “v1” and Resourcetype is “deployments”. The final request looks like this:

curl http://localhost:8001/apis/apps/v1/deployments

If your Kubernetes cluster has a metrics-server, the following request will get node resource usage data:

curl http://localhost:8001/apis/metrics.k8s.io/v1beta1/nodes

Rancher users

However, if your cluster was created using Rancher, you’ll need “Authorized Cluster Endpoint” enabled, so your requests can reach the API Server directly. Your URL paths will slightly differ from the examples above otherwise.

Using your command line, you’ll also need to select the correct context for kubectl before activating the proxy.

# List available contexts
kubectl config get-contexts

# Select the direct API context
# Its name has the format <cluster_name>-<pilot_name>
kubectl config use-context <direct_context>

Further reading

Overview of the Kubernetes API

More info about API concepts

Accessing the API without kubectl proxy

Full list of Groups, Versions and Resourcetypes

Section break
July 31, 2020

#Activate the replicaset mode in MongoDB

Start the MongoDB daemon with the --replSet flag.

mongod --replSet rs0

After first start, initialize the replica set with the following:

mongo <mongo_host>/<database> --eval "rs.initiate({_id: 'rs0', members: [ { _id: 0, host: '<mongo_host>:27017' } ]})"

Section break
Section break
July 23, 2020

#Test networking performance between two Linux machines

Let’s use iperf to measure networking performance. First, set a “server” machine.

iperf -s

From a “client” machine, run the test.

iperf -c <server_ip>

If you are having trouble getting the server’s IP, use ip addr show to find addresses used by the current machine in any connected networks.

Section break
July 23, 2020

#Wipe any filesystem data and partitions from devices on Linux

wipefs should work in most cases to clean filesystems.

# View filesystem data from device
wipefs /dev/<my_device>

# Wipe any filesystem found
wipefs --all /dev/<my_device>

If wipefs can’t detect a filesystem, you can quickly wipe the header data of the device with dd.

dd if=/dev/zero of=/dev/<my_device> bs=1M count=100 oflag=direct,dsync status=progress

You may need to restart your machine to make the filesystem deletion known to the OS, if your device is a “LVM2_member” for instance.

Also, as a last resort, you can completely wipe the device.

dd if=/dev/zero of=/dev/<my_device> bs=1M count=5000000 oflag=direct,dsync status=progress

To delete partitions, sgdisk should work well.

sgdisk --zap-all /dev/<my_device>

Section break
July 16, 2020

#Bootstrap a Go Cobra application with Go Modules

To bootstrap a Go application using Cobra Generator and Go Modules, start by installing the Cobra command line:

# Install Cobra
go get github.com/spf13/cobra/cobra

Initialize your Cobra project, but remember to use your package’s fully qualified name, as in: github.com/laurybueno/kubectl-hoggers. Don’t worry if your package is not publicly available yet. And there is no need to put your source code inside GOPATH anymore (since Go 1.11 at least):

cobra init --pkg-name <fully_qualified_package_name>

Initialize your new module:

go mod init <fully_qualified_package_name>

Get recursively all its dependencies and install it:

go get -d -v ./...
go install -v ./...

Your app’s command line is now ready to be used:

kubectl-hoggers

Further reading

Docker and Go: https://www.docker.com/blog/docker-golang/

Go Modules: https://github.com/golang/go/wiki/Modules

Section break
July 16, 2020

#Download files recursively through a FTP connection using wget

Unfortunately, SCP and Rsync aren’t always an option to get our files out of a server. When FTP is all we have, wget can still do a pretty good job and not re-download files you already have, like a primitive Rsync.

wget -nc -nH --cut-dirs=<NUMBER_REMOTE_FOLDERS_TO_CUT_FROM_PATH> --ftp-user='<FTP_USER>' --ftp-password='<FTP_PASSWORD>' -rl 0 -P <LOCAL_PATH> 'ftp://<REMOTE_HOST>/<PATH_TO_FILES>'

Explanation for the used flags:

-nc, --no-clobber                skip downloads that would download to existing files (overwriting them)
-nH, --no-host-directories       don't create host directories
--cut-dirs=NUMBER                ignore NUMBER remote directory components
-r,  --recursive                 specify recursive download
-l,  --level=NUMBER              maximum recursion depth (inf or 0 for infinite)
-P,  --directory-prefix=PREFIX   save files to PREFIX/..

Section break
June 26, 2020

#Check a site's downtime with this bash one-liner

During a site’s downtime, the following line will print the current time and try to access the given URL one time every 2 seconds. Only the received HTTP headers will be shown.

You may find it useful for monitoring downtime while you try to solve a problem.

while true; do date; curl -IX GET '<URL>'; sleep 2; done;

Section break
Section break
March 13, 2020

#How to dump and restore MySQL/MariaDB databases

Options for dumping data:

# Directly
mysqldump --databases $MYSQL_DATABASE -u$MYSQL_USER -p$MYSQL_PASSWORD > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

# Docker
docker exec -it <container_name> sh -c 'mysqldump --databases $MYSQL_DATABASE -u$MYSQL_USER -p$MYSQL_PASSWORD' > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

# Kubernetes (vulnerable to networking failures)
kubectl -n <namespace> exec deploy/<deploy_name> -- bash -c 'mysqldump --databases $MYSQL_DATABASE -u$MYSQL_USER -p$MYSQL_PASSWORD' > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

Additionally, if your database operates in a remote server (like Amazon RDS), you may still use a local Docker container for dumping the data.

# Create a local container with the desired version (in this example, MariaDB 10.4 is used)
docker run -it --rm -v ${PWD}:/dump -w /dump mariadb:10.4 bash

# Get the data
mysqldump -h <hostname> --databases <database_name> -u <database_user> --password='<password>' > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

For restoring data, you can use:

mysql -u$MYSQL_USER -p$MYSQL_PASSWORD < dump.sql

If you’re using Docker, you can also place dump files on /docker-entrypoint-initdb.d and those will be imported on the first run. Accepted files types are *.sql, *.sql.gz, and *.sh.

Section break
Section break
March 11, 2020

#How to publish Ionic apps on the Google Play Store

We’ll use Docker to avoid installing the Android SDK on our computer for this task. This is possible thanks to this ionic Docker image.

# Enter in the container command line
docker run -it --rm --net host --privileged -v /dev/bus/usb:/dev/bus/usb -v ~/.gradle:/root/.gradle -v $PWD:/Sources:rw -v /home/<your_user>/.ssh:/root/.ssh hacklab/ionic:android-28 bash

# Install javascript dependencies
npm install

# Execute Ionic build
ionic cordova platform add android
ionic cordova build android --prod --release

# Sign the generated build
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/.ssh/<your_keystore> platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk myapp
/opt/android-sdk-linux/build-tools/28.0.3/zipalign -v 4 platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk myapp-2.0.0.apk

Section break
March 10, 2020

#How to dump and restore PostgreSQL databases

Options for dumping data:

# Binary mode
pg_dump -Fc -U $POSTGRES_USER $POSTGRES_DB > dump-`date '+%Y_%m_%d__%H_%M_%S'`.psqlc

# SQL mode
pg_dump -U $POSTGRES_USER $POSTGRES_DB > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

# Docker and SQL mode
docker exec -it -u postgres <container_name> sh -c 'pg_dump -U $POSTGRES_USER $POSTGRES_DB' > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

# Kubernetes and SQL mode (vulnerable to networking failures)
kubectl -n <namespace> exec deploy/postgres -- sh -c 'pg_dump -U $POSTGRES_USER $POSTGRES_DB' > dump-`date '+%Y_%m_%d__%H_%M_%S'`.sql

Options for restoring data:

# Binary mode
pg_restore -O -U $POSTGRES_USER -c -x -n public -d $POSTGRES_DB dump.psqlc

# SQL mode
psql -d $POSTGRES_DB -a -f /backups/dump.sql

If you’re using Docker, you can also place dump files on /docker-entrypoint-initdb.d and those will be imported on the first run. Accepted files types are *.sql, *.sql.gz, and *.sh.

Section break
Section break
Section break
March 02, 2020

#Format a new SSD/HD drive for usage

# Create the partition
parted --align optimal <raw_device>
mklabel msdos
mkpart primary ext4 0% 100%
quit

# Get the new partition device path
lsblk

# Make the filesystem
mkfs.ext4 <new_partition_device_path>

# Test the partition
mkdir /tmp/partition-test
mount -t ext4 <new_partition_device_path> /tmp/partition-test

# Get the UUID
blkid

# Edit the /etc/fstab to mount partition during boot
UUID=<uuid>     /storage        ext4    defaults,discard        0       2
## OR, if using XFS
UUID=<uuid>     /storage       xfs     defaults        1       2

Section break
Section break
July 01, 2019

#Dump and restore data from MongoDB in Docker

# Create the database dump on your server
docker exec <my_mongodb_container> mongodump --archive=/backups/mongodb-`date +%Y%m%d`.gz --gzip --db <database_name>

# Copy it to your local machine, if needed
scp -r <server_user>@<server_ip>:<remote_backup_path> <local_backup_path>

# Restore it directly into a running Docker container
zcat <backup_path> | docker exec -i <my_mongodb_container> mongorestore --archive --drop

Section break
Section break
Section break
Section break
September 01, 2017

#Some useful Vagrant commands

# Bring up the system
vagrant up

# Get into the running virtual machine
vagrant ssh

# Bring the machine down and up again
# (equivalent of running a halt followed by an up)
vagrant reload

# Destroy the current created virtual machines
vagrant destroy