--- date: "2025-05-28T22:07:23Z" title: 'Case Study: Minio S3 - Part 1' --- {{< image float="right" src="/assets/minio/minio-logo.png" alt="MinIO Logo" width="6em" >}} # Introduction Amazon Simple Storage Service (Amazon S3) is an object storage service offering industry-leading scalability, data availability, security, and performance. Millions of customers of all sizes and industries store, manage, analyze, and protect any amount of data for virtually any use case, such as data lakes, cloud-native applications, and mobile apps. With cost-effective storage classes and easy-to-use management features, you can optimize costs, organize and analyze data, and configure fine-tuned access controls to meet specific business and compliance requirements. Amazon's S3 became the _de facto_ standard object storage system, and there exist several fully open source implementations of the protocol. One of them is MinIO: designed to allow enterprises to consolidate all of their data on a single, private cloud namespace. Architected using the same principles as the hyperscalers, AIStor delivers performance at scale at a fraction of the cost compared to the public cloud. IPng Networks is an Internet Service Provider, but I also dabble in self-hosting things, for example [[PeerTube](https://video.ipng.ch/)], [[Mastodon](https://ublog.tech/)], [[Immich](https://photos.ipng.ch/)], [[Pixelfed](https://pix.ublog.tech/)] and of course [[Hugo](https://ipng/ch/)]. These services all have one thing in common: they tend to use lots of storage when they grow. At IPng Networks, all hypervisors ship with enterprise SAS flash drives, mostly 1.92TB and 3.84TB. Scaling up each of these services, and backing them up safely, can be quite the headache. This article is for the storage-buffs. I'll set up a set of distributed MinIO nodes from scatch. ## Physical {{< image float="right" src="/assets/minio/disks.png" alt="MinIO Disks" width="16em" >}} I'll start with the basics. I still have a few Dell R720 servers laying around, they are getting a bit older but still have 24 cores and 64GB of memory. First I need to get me some disks. I order 36pcs of 16TB SATA enterprise disk, a mixture of Seagate EXOS and Toshiba MG series disks. I've once learned (the hard way), that buying a big stack of disks from one production run is a risk - so I'll mix and match the drives. Three trays of caddies and a melted credit card later, I have 576TB of SATA disks safely in hand. Each machine will carry 192TB of raw storage. The nice thing about this chassis is that Dell can ship them with 12x 3.5" SAS slots in the front, and 2x 2.5" SAS slots in the rear of the chassis. So I'll install Debian Bookworm on one small 480G SSD in software RAID1. ### Cloning an install I have three identical machines so in total I'll want six of these SSDs. I temporarily screw the other five in 3.5" drive caddies and plug them into the first installed Dell, which I've called `minio-proto`: ``` pim@minio-proto:~$ for i in b c d e f; do sudo dd if=/dev/sda of=/dev/sd${i} bs=512 count=1; sudo mdadm --manage /dev/md0 --add /dev/md${i}1 done pim@minio-proto:~$ sudo mdadm --manage /dev/md0 --grow 6 pim@minio-proto:~$ watch cat /proc/mdstat pim@minio-proto:~$ for i in a b c d e f; do sudo grub-install /dev/sd$i done ``` {{< image float="right" src="/assets/minio/rack.png" alt="MinIO Rack" width="16em" >}} The first command takes my installed disk, `/dev/sda`, and copies the first sector over to the other five. This will give them the same partition table. Next, I'll add the first partition of each disk to the raidset. Then, I'll expand the raidset to have six members, after which the kernel starts a recovery process that syncs the newly added paritions to `/dev/md0` (by copying from `/dev/sda` to all other disks at once). Finally, I'll watch this exciting movie and grab a cup of tea. Once the disks are fully copied, I'll shut down the machine and distribute the disks to their respective Dell R720, two each. Once they boot they will all be identical. I'll need to make sure their hostnames, and machine/host-id are unique, otherwise things like bridges will have overlapping MAC addresses - ask me how I know: ``` pim@minio-proto:~$ sudo mdadm --manage /dev/md0 --grow -n 2 pim@minio-proto:~$ sudo rm /etc/ssh/ssh_host* pim@minio-proto:~$ sudo hostname minio0-chbtl0 pim@minio-proto:~$ sudo dpkg-reconfigure openssh-server pim@minio-proto:~$ sudo dd if=/dev/random of=/etc/hostid bs=4 count=1 pim@minio-proto:~$ sudo /usr/bin/dbus-uuidgen > /etc/machine-id pim@minio-proto:~$ sudo reboot ``` After which I have three beautiful and unique machines: * `minio0.chbtl0.net.ipng.ch`: which will go into my server rack at the IPng office. * `minio0.ddln0.net.ipng.ch`: which will go to [[Daedalean]({{< ref 2022-02-24-colo >}})], doing AI since before it was all about vibe coding. * `minio0.chrma0.net.ipng.ch`: which will go to [[IP-Max](https://ip-max.net/)], one of the best ISPs on the planet. 🥰 ## Deploying Minio The user guide that MinIO provides [[ref](https://min.io/docs/minio/linux/operations/installation.html)] is super good, arguably one of the best documented open source projects I've ever seen. it shows me that I can do three types of install. A 'Standalone' with one disk, a 'Standalone Multi-Drive', and a 'Distributed' deployment. I decide to make three independent standalone multi-drive installs. This way, I have less shared fate, and will be immune to network partitions (as these are going to be in three different physical locations). I've also read about per-bucket _replication_, which will be an excellent way to get geographical distribution and active/active instances to work together. I feel good about the single-machine multi-drive decision. I follow the install guide [[ref](https://min.io/docs/minio/linux/operations/install-deploy-manage/deploy-minio-single-node-multi-drive.html#minio-snmd)] for this deployment type. ### IPng Frontends At IPng I use a private IPv4/IPv6/MPLS network that is not connected to the internet. I call this network [[IPng Site Local]({{< ref 2023-03-11-mpls-core.md >}})]. But how will users reach my Minio install? I have four redundantly and geographically deployed frontends, two in the Netherlands and two in Switzerland. I've described the frontend setup in a [[previous article]({{< ref 2023-03-17-ipng-frontends >}})] and the certificate management in [[this article]({{< ref 2023-03-24-lego-dns01 >}})]. I've decided to run the service on these three regionalized endpoints: 1. `s3.chbtl0.ipng.ch` which will back into `minio0.chbtl0.net.ipng.ch` 1. `s3.ddln0.ipng.ch` which will back into `minio0.ddln0.net.ipng.ch` 1. `s3.chrma0.ipng.ch` which will back into `minio0.chrma0.net.ipng.ch` The first thing I take note of is that S3 buckets can be either addressed _by path_, in other words something like `s3.chbtl0.ipng.ch/my-bucket/README.md`, but they can also be addressed by virtual host, like so: `my-bucket.s3.chbtl0.ipng.ch/README.md`. A subtle difference, but from the docs I understand that Minio needs to have control of the whole space under its main domain. There's a small implication to this requirement -- the Web Console that ships with MinIO (eh, well, maybe that's going to change, more on that later), will want to have its own domain-name, so I choose something simple: `cons0-s3.chbtl0.ipng.ch` and so on. This way, somebody might still be able to have a bucket name called `cons0` :) #### Let's Encrypt Certificates Alright, so I will be neading nine domains into this new certificate which I'll simply call `s3.ipng.ch`. I configure it in Ansible: ``` certbot: certs: ... s3.ipng.ch: groups: [ 'nginx', 'minio' ] altnames: - 's3.chbtl0.ipng.ch' - 'cons0-s3.chbtl0.ipng.ch' - '*.s3.chbtl0.ipng.ch' - 's3.ddln0.ipng.ch' - 'cons0-s3.ddln0.ipng.ch' - '*.s3.ddln0.ipng.ch' - 's3.chrma0.ipng.ch' - 'cons0-s3.chrma0.ipng.ch' - '*.s3.chrma0.ipng.ch' ``` I run the `certbot` playbook and it does two things: 1. On the machines from group `nginx` and `minio`, it will ensure there exists a user `lego` with an SSH key and write permissions to `/etc/lego/`; this is where the automation will write (and update) the certificate keys. 1. On the `lego` machine, it'll create two files. One is the certificate requestor, and the other is a certificate distribution script that will copy the cert to the right machine(s) when it renews. On the `lego` machine, I'll run the cert request for the first time: ``` lego@lego:~$ bin/certbot:s3.ipng.ch lego@lego:~$ RENEWED_LINEAGE=/home/lego/acme-dns/live/s3.ipng.ch bin/certbot-distribute ``` The first script asks me to add the _acme-challenge DNS entries, which I'll do, for example on the `s3.chbtl0.ipng.ch` instance (and similar for the `ddln0` and `chrma0` ones: ``` $ORIGIN chbtl0.ipng.ch. _acme-challenge.s3 CNAME 51f16fd0-8eb6-455c-b5cd-96fad12ef8fd.auth.ipng.ch. _acme-challenge.cons0-s3 CNAME 450477b8-74c9-4b9e-bbeb-de49c3f95379.auth.ipng.ch. s3 CNAME nginx0.ipng.ch. *.s3 CNAME nginx0.ipng.ch. cons0-s3 CNAME nginx0.ipng.ch. ``` I push and reload the `ipng.ch` zonefile with these changes after which the certificate gets requested and a cronjob added to check for renewals. The second script will copy the newly created cert to all three `minio` machines, and all four `nginx` machines. From now on, every 90 days, a new cert will be automatically generated and distributed. Slick! #### NGINX Configs With the LE wildcard certs in hand, I can create an NGINX frontend for these minio deployments. First, a simple redirector service that punts people on port 80 to port 443: ``` server { listen [::]:80; listen 0.0.0.0:80; server_name cons0-s3.chbtl0.ipng.ch s3.chbtl0.ipng.ch *.s3.chbtl0.ipng.ch; access_log /var/log/nginx/s3.chbtl0.ipng.ch-access.log; include /etc/nginx/conf.d/ipng-headers.inc; location / { return 301 https://$server_name$request_uri; } } ``` Next, the Minio API service itself which runs on port 9000, with a configuration snippet inspired by the MinIO [[docs](https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html)]: ``` server { listen [::]:443 ssl http2; listen 0.0.0.0:443 ssl http2; ssl_certificate /etc/certs/s3.ipng.ch/fullchain.pem; ssl_certificate_key /etc/certs/s3.ipng.ch/privkey.pem; include /etc/nginx/conf.d/options-ssl-nginx.inc; ssl_dhparam /etc/nginx/conf.d/ssl-dhparams.inc; server_name s3.chbtl0.ipng.ch *.s3.chbtl0.ipng.ch; access_log /var/log/nginx/s3.chbtl0.ipng.ch-access.log upstream; include /etc/nginx/conf.d/ipng-headers.inc; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; ignore_invalid_headers off; client_max_body_size 0; # Disable buffering proxy_buffering off; proxy_request_buffering off; location / { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 300; proxy_http_version 1.1; proxy_set_header Connection ""; chunked_transfer_encoding off; proxy_pass http://minio0.chbtl0.net.ipng.ch:9000; } } ``` Finally, the Minio Console service which runs on port 9090: ``` include /etc/nginx/conf.d/geo-ipng-trusted.inc; server { listen [::]:443 ssl http2; listen 0.0.0.0:443 ssl http2; ssl_certificate /etc/certs/s3.ipng.ch/fullchain.pem; ssl_certificate_key /etc/certs/s3.ipng.ch/privkey.pem; include /etc/nginx/conf.d/options-ssl-nginx.inc; ssl_dhparam /etc/nginx/conf.d/ssl-dhparams.inc; server_name cons0-s3.chbtl0.ipng.ch; access_log /var/log/nginx/cons0-s3.chbtl0.ipng.ch-access.log upstream; include /etc/nginx/conf.d/ipng-headers.inc; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; ignore_invalid_headers off; client_max_body_size 0; # Disable buffering proxy_buffering off; proxy_request_buffering off; location / { if ($geo_ipng_trusted = 0) { rewrite ^ https://ipng.ch/ break; } proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-NginX-Proxy true; real_ip_header X-Real-IP; proxy_connect_timeout 300; chunked_transfer_encoding off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://minio0.chbtl0.net.ipng.ch:9090; } } ``` This last one has an NGINX trick. It will only allow users in if they are in the map called `geo_ipng_trusted`, which contains a set of IPv4 and IPv6 prefixes. Visitors who are not in this map will receive an HTTP redirect back to the [[IPng.ch](https://ipng.ch/)] homepage instead. I run the Ansible Playbook which contains the NGINX changes to all frontends, but of course nothing runs yet, because I haven't yet started MinIO backends. ### MinIO Backends The first thing I need to do is get those disks mounted. MinIO likes using XFS, so I'll install that and prepare the disks as follows: ``` pim@minio0-chbtl0:~$ sudo apt install xfsprogs pim@minio0-chbtl0:~$ sudo modprobe xfs pim@minio0-chbtl0:~$ echo xfs | sudo tee -a /etc/modules pim@minio0-chbtl0:~$ sudo update-initramfs -k all -u pim@minio0-chbtl0:~$ for i in a b c d e f g h i j k l; do sudo mkfs.xfs /dev/sd$i; done pim@minio0-chbtl0:~$ blkid | awk 'BEGIN {i=1} /TYPE="xfs"/ { printf "%s /minio/disk%d xfs defaults 0 2\n",$2,i; i++; }' | sudo tee -a /etc/fstab pim@minio0-chbtl0:~$ for i in `seq 1 12`; do sudo mkdir -p /minio/disk$i; done pim@minio0-chbtl0:~$ sudo mount -t xfs -a pim@minio0-chbtl0:~$ sudo chown -R minio-user: /minio/ ``` From the top: I'll install `xfsprogs` which contains the things I need to manipulate XFS filesystems in Debian. Then I'll install the `xfs` kernel module, and make sure it gets inserted upon subsequent startup by adding it to `/etc/modules` and regenerating the initrd for the installed kernels. Next, I'll format all twelve 16TB disks (which are `/dev/sda` - `/dev/sdl` on these machines), and add their resulting blockdevice id's to `/etc/fstab` so they get persistently mounted on reboot. Finally, I'll create their mountpoints, mount all XFS filesystems, and chown them to the user that MinIO is running as. End result: ``` pim@minio0-chbtl0:~$ df -T Filesystem Type 1K-blocks Used Available Use% Mounted on udev devtmpfs 32950856 0 32950856 0% /dev tmpfs tmpfs 6595340 1508 6593832 1% /run /dev/md0 ext4 114695308 5423976 103398948 5% / tmpfs tmpfs 32976680 0 32976680 0% /dev/shm tmpfs tmpfs 5120 4 5116 1% /run/lock /dev/sda xfs 15623792640 121505936 15502286704 1% /minio/disk1 /dev/sde xfs 15623792640 121505968 15502286672 1% /minio/disk12 /dev/sdi xfs 15623792640 121505968 15502286672 1% /minio/disk11 /dev/sdl xfs 15623792640 121505904 15502286736 1% /minio/disk10 /dev/sdd xfs 15623792640 121505936 15502286704 1% /minio/disk4 /dev/sdb xfs 15623792640 121505968 15502286672 1% /minio/disk3 /dev/sdk xfs 15623792640 121505936 15502286704 1% /minio/disk5 /dev/sdc xfs 15623792640 121505936 15502286704 1% /minio/disk9 /dev/sdf xfs 15623792640 121506000 15502286640 1% /minio/disk2 /dev/sdj xfs 15623792640 121505968 15502286672 1% /minio/disk7 /dev/sdg xfs 15623792640 121506000 15502286640 1% /minio/disk8 /dev/sdh xfs 15623792640 121505968 15502286672 1% /minio/disk6 tmpfs tmpfs 6595336 0 6595336 0% /run/user/0 ``` MinIO likes to be configured using environment variables - and this is likely because it's a popular thing to run in a containerized environment like Kubernetes. The maintainers ship it also as a Debian package, which will read its environment from `/etc/default/minio`, and I'll prepare that file as follows: ``` pim@minio0-chbtl0:~$ cat << EOF | sudo tee /etc/default/minio MINIO_DOMAIN="s3.chbtl0.ipng.ch,minio0.chbtl0.net.ipng.ch" MINIO_ROOT_USER="XXX" MINIO_ROOT_PASSWORD="YYY" MINIO_VOLUMES="/minio/disk{1...12}" MINIO_OPTS="--console-address :9001" EOF pim@minio0-chbtl0:~$ sudo systemctl enable --now minio pim@minio0-chbtl0:~$ sudo journalctl -u minio May 31 10:44:11 minio0-chbtl0 minio[690420]: MinIO Object Storage Server May 31 10:44:11 minio0-chbtl0 minio[690420]: Copyright: 2015-2025 MinIO, Inc. May 31 10:44:11 minio0-chbtl0 minio[690420]: License: GNU AGPLv3 - https://www.gnu.org/licenses/agpl-3.0.html May 31 10:44:11 minio0-chbtl0 minio[690420]: Version: RELEASE.2025-05-24T17-08-30Z (go1.24.3 linux/amd64) May 31 10:44:11 minio0-chbtl0 minio[690420]: API: http://198.19.4.11:9000 http://127.0.0.1:9000 May 31 10:44:11 minio0-chbtl0 minio[690420]: WebUI: https://cons0-s3.chbtl0.ipng.ch/ May 31 10:44:11 minio0-chbtl0 minio[690420]: Docs: https://docs.min.io pim@minio0-chbtl0:~$ sudo ipmitool sensor | grep Watts Pwr Consumption | 154.000 | Watts ``` Incidentally - I am pretty pleased with this 192TB disk tank, sporting 24 cores, 64GB memory and 2x10G network, casually hanging out at 154 Watts of power all up. Slick! {{< image float="right" src="/assets/minio/minio-ec.svg" alt="MinIO Erasure Coding" width="22em" >}} MinIO implements _erasure coding_ as a core component in providing availability and resiliency during drive or node-level failure events. MinIO partitions each object into data and parity shards and distributes those shards across a single so-called _erasure set_. Under the hood, it uses [[Reed-Solomon](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)] erasure coding implementation and partitions the object for distribution. From the MinIO website, I'll borrow a diagram to show how it looks like on a single node like mine to the right. Anyway, MinIO detects 12 disks and installs an erasure set with 8 data disks and 4 parity disks, which it calls `EC:4` encoding, also known in the industry as `RS8.4`. Just like that, the thing shoots to life. Awesome! ### MinIO Client On Summer, I'll install the MinIO Client called `mc`. This is easy because the maintainers ship a Linux binary which I can just download. On OpenBSD, they don't do that. Not a problem though, on Squanchy, Pencilvester and Glootie, I will just `go install` the client. Using the `mc` commandline, I can all any of the S3 APIs on my new MinIO instance: ``` pim@summer:~$ set +o history pim@summer:~$ mc alias set chbtl0 https://s3.chbtl0.ipng.ch/ pim@summer:~$ set -o history pim@summer:~$ mc admin info chbtl0/ ● s3.chbtl0.ipng.ch Uptime: 22 hours Version: 2025-05-24T17:08:30Z Network: 1/1 OK Drives: 12/12 OK Pool: 1 ┌──────┬───────────────────────┬─────────────────────┬──────────────┐ │ Pool │ Drives Usage │ Erasure stripe size │ Erasure sets │ │ 1st │ 0.8% (total: 116 TiB) │ 12 │ 1 │ └──────┴───────────────────────┴─────────────────────┴──────────────┘ 95 GiB Used, 5 Buckets, 5,859 Objects, 318 Versions, 1 Delete Marker 12 drives online, 0 drives offline, EC:4 ``` Cool beans. I think I should get rid of this root account though, I've installed those credentials into the `/etc/default/minio` environment file, but I don't want to keep them out in the open. So I'll make an account for myself and assign me reasonable privileges, called `consoleAdmin` in the default install: ``` pim@summer:~$ set +o history pim@summer:~$ mc admin user add chbtl0/ pim@summer:~$ mc admin policy info chbtl0 consoleAdmin pim@summer:~$ mc admin policy attach chbtl0 consoleAdmin --user= pim@summer:~$ mc alias set chbtl0 https://s3.chbtl0.ipng.ch/ pim@summer:~$ set -o history ``` OK, I feel less gross now that I'm not operating as root on the MinIO deployment. Using my new user-powers, let me set some metadata on my new minio server: ``` pim@summer:~$ mc admin config set chbtl0/ site name=chbtl0 region=switzerland Successfully applied new settings. Please restart your server 'mc admin service restart chbtl0/'. pim@summer:~$ mc admin service restart chbtl0/ Service status: ▰▰▱ [DONE] Summary: ┌───────────────┬─────────────────────────────┐ │ Servers: │ 1 online, 0 offline, 0 hung │ │ Restart Time: │ 61.322886ms │ └───────────────┴─────────────────────────────┘ pim@summer:~$ mc admin config get chbtl0/ site site name=chbtl0 region=switzerland ``` By the way, what's really cool about these open standards is that both the Amazon `aws` client works with MinIO, but `mc` also works with AWS! ### MinIO Console Although I'm pretty good with APIs and command line tools, there's some benefit also in using a Graphical User Interface. MinIO ships with one, but there was a bit of a kerfuffle in the MinIO community. Unfortunately, these are pretty common -- Redis (an open source key/value storage system) changed their offering abruptly. Terraform (an open source infrastructure-as-code tool) changed their licensing at some point. Ansible (an open source machine management tool) changed their offering also. MinIO developers decided to strip their console of ~all features recently. The gnarly bits are discussed on [[reddit](https://www.reddit.com/r/selfhosted/comments/1kva3pw/avoid_minio_developers_introduce_trojan_horse/)]. but suffice to say: the same thing that happened in literally 100% of the other cases, also happened here. Somebody decided to simply fork the code from before it was changed. Enter OpenMaxIO. A cringe worthy name, but it gets the job done. Reading up on the [[GitHub](https://github.com/OpenMaxIO/openmaxio-object-browser/issues/5)], reviving the fully working console is pretty straight forward -- that is, once somebody spent a few days figuring it out. Thank you `icesvz` for this excellent pointer. With this, I can create a systemd service for the console and start it: ``` pim@minio0-chbtl0:~$ cat << EOF | sudo tee -a /etc/default/minio ## NOTE(pim): For openmaxio console service CONSOLE_MINIO_SERVER="http://localhost:9000" MINIO_BROWSER_REDIRECT_URL="https://cons0-s3.chbtl0.ipng.ch/" EOF pim@minio0-chbtl0:~$ cat << EOF | sudo tee /lib/systemd/system/minio-console.service [Unit] Description=OpenMaxIO Console Service Wants=network-online.target After=network-online.target AssertFileIsExecutable=/usr/local/bin/minio-console [Service] Type=simple WorkingDirectory=/usr/local User=minio-user Group=minio-user ProtectProc=invisible EnvironmentFile=-/etc/default/minio ExecStart=/usr/local/bin/minio-console server Restart=always LimitNOFILE=1048576 MemoryAccounting=no TasksMax=infinity TimeoutSec=infinity OOMScoreAdjust=-1000 SendSIGKILL=no [Install] WantedBy=multi-user.target EOF pim@minio0-chbtl0:~$ sudo systemctl enable --now minio-console pim@minio0-chbtl0:~$ sudo systemctl restart minio ``` The first snippet is an update to the MinIO configuration that instructs it to redirect users who are not trying to use the API to the console endpoint on `cons0-s3.chbtl0.ipng.ch`, and then the console-server needs to know where to find the API, which from its vantage point is running on `localhost:9000`. Hello, beautiful fully featured console: {{< image src="/assets/minio/console-1.png" alt="MinIO Console" >}} ### MinIO Prometheus MinIO ships with a prometheus metrics endpoint, and I notice on its console that it has a nice metrics tab, which is fully greyed out. This is most likely because, well, I don't have a Prometheus install here yet. I decide to keep the storage nodes self-contained and start a Prometheus server on the local machine. I can always plumb that to IPng's Grafana instance later. For now, I'll install Prometheus as follows: ``` pim@minio0-chbtl0:~$ cat << EOF | sudo tee -a /etc/default/minio ## NOTE(pim): Metrics for minio-console MINIO_PROMETHEUS_AUTH_TYPE="public" CONSOLE_PROMETHEUS_URL="http://localhost:19090/" CONSOLE_PROMETHEUS_JOB_ID="minio-job" EOF pim@minio0-chbtl0:~$ sudo apt install prometheus pim@minio0-chbtl0:~$ cat << EOF | sudo tee /etc/default/prometheus ARGS="--web.listen-address='[::]:19090' --storage.tsdb.retention.size=16GB" EOF pim@minio0-chbtl0:~$ cat << EOF | sudo tee /etc/prometheus/prometheus.yml global: scrape_interval: 60s scrape_configs: - job_name: minio-job metrics_path: /minio/v2/metrics/cluster static_configs: - targets: ['localhost:9000'] labels: cluster: minio0-chbtl0 - job_name: minio-job-node metrics_path: /minio/v2/metrics/node static_configs: - targets: ['localhost:9000'] labels: cluster: minio0-chbtl0 - job_name: minio-job-bucket metrics_path: /minio/v2/metrics/bucket static_configs: - targets: ['localhost:9000'] labels: cluster: minio0-chbtl0 - job_name: minio-job-resource metrics_path: /minio/v2/metrics/resource static_configs: - targets: ['localhost:9000'] labels: cluster: minio0-chbtl0 - job_name: node static_configs: - targets: ['localhost:9100'] labels: cluster: minio0-chbtl0 pim@minio0-chbtl0:~$ sudo systemctl restart minio prometheus ``` In the first snippet, I'll tell MinIO where it should find its Prometheus instance. Since the MinIO console service is running on port 9090, and this is also the default port for Prometheus, I will run Promtheus on port 19090 instead. From reading the MinIO docs, I can see that normally MinIO will want prometheus to authenticate to it before it'll allow the endpoints to be scraped. I'll turn that off by making these public. On the IPng Frontends, I can always remove access to /minio/v2 and simply use the IPng Site Local access for local Prometheus scrapers instead. After telling Prometheus its runtime arguments (in `/etc/default/prometheus`) and its scraping endpoints (in `/etc/prometheus/prometheus.yml`), I can restart minio and prometheus. A few minutes later, I can see the _Metrics_ tab in the console come to life. But now that I have this prometheus running on the MinIO node, I can also add it to IPng's Grafana configuration, by adding a new data source on `minio0.chbtl0.net.ipng.ch:19090` and pointing the default Grafana [[Dashboard](https://grafana.com/grafana/dashboards/13502-minio-dashboard/)] at it: {{< image src="/assets/minio/console-2.png" alt="Grafana Dashboard" >}} A two-for-one: I will both be able to see metrics directly in the console, but also I will be able to hook up these per-node prometheus instances into IPng's alertmanager also, and I've read some [[docs](https://min.io/docs/minio/linux/operations/monitoring/collect-minio-metrics-using-prometheus.html)] on the concepts. I'm really liking the experience so far! ### MinIO Nagios Prometheus is fancy and all, but at IPng Networks, I've been doing monitoring for a while now. As a dinosaur, I still have an active [[Nagios](https://www.nagios.org/)] install, which autogenerates all of its configuration using the Ansible repository I have. So for the new Ansible group called `minio`, I will autogenerate the following snippet: ``` define command { command_name ipng_check_minio command_line $USER1$/check_http -E -H $HOSTALIAS$ -I $ARG1$ -p $ARG2$ -u $ARG3$ -r '$ARG4$' } define service { hostgroup_name ipng:minio:ipv6 service_description minio6:api check_command ipng_check_minio!$_HOSTADDRESS6$!9000!/minio/health/cluster! use ipng-service-fast notification_interval 0 ; set > 0 if you want to be renotified } define service { hostgroup_name ipng:minio:ipv6 service_description minio6:prom check_command ipng_check_minio!$_HOSTADDRESS6$!19090!/classic/targets!minio-job use ipng-service-fast notification_interval 0 ; set > 0 if you want to be renotified } define service { hostgroup_name ipng:minio:ipv6 service_description minio6:console check_command ipng_check_minio!$_HOSTADDRESS6$!9090!/!MinIO Console use ipng-service-fast notification_interval 0 ; set > 0 if you want to be renotified } ``` I've shown the snippet for IPv6 but I also have three services defined for legacy IP in the hostgroup `ipng:minio:ipv4`. The check command here uses `-I` which has the IPv4 or IPv6 address to talk to, `-p` for the port to consule, `-u` for the URI to hit and an option `-r` for a regular expression to expect in the output. For the Nagios afficianados out there: my Ansible `groups` correspond one to one with autogenerated Nagios `hostgroups`. This allows me to add arbitrary checks by group-type, like above in the `ipng:minio` group for IPv4 and IPv6. In the MinIO [[docs](https://min.io/docs/minio/linux/operations/monitoring/healthcheck-probe.html)] I read up on the Healthcheck API. I choose to monitor the _Cluster Write Quorum_ on my minio deployments. For Prometheus, I decide to hit the `targets` endpoint and expect the `minio-job` to be among them. Finally, for the MinIO Console, I expect to see a login screen with the words `MinIO Console` in the returned page. I guessed right, because Nagios is all green: {{< image src="/assets/minio/nagios.png" alt="Nagios Dashboard" >}} ## My First Bucket The IPng website is a statically generated Hugo site, and when-ever I submit a change to my Git repo, a CI/CD runner (called [[Drone](https://www.drone.io/)]), picks up the change. It re-builds the static website, and copies it to four redundant NGINX servers. But IPng's website has amassed quite a bit of extra files (like VM images and VPP packages that I publish), which are copied separately using a simple push script I have in my home directory. This avoids all those big media files from cluttering the Git repository. I decide to move this stuff into S3: ``` pim@summer:~/src/ipng-web-assets$ echo 'Gruezi World.' > ipng.ch/media/README.md pim@summer:~/src/ipng-web-assets$ mc mb chbtl0/ipng-web-assets pim@summer:~/src/ipng-web-assets$ mc mirror . chbtl0/ipng-web-assets/ ...ch/media/README.md: 6.50 GiB / 6.50 GiB ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 236.38 MiB/s 28s pim@summer:~/src/ipng-web-assets$ mc anonymous set download chbtl0/ipng-web-assets/ ``` OK, two things that immediately jump out at me. This stuff is **fast**: Summer is connected with a 2.5GbE network card, and she's running hard, copying the 6.5GB of data that are in these web assets essentially at line rate. It doesn't really surprise me because Summer is running off of Gen4 NVME, while MinIO has 12 spinning disks which each can write about 160MB/s or so sustained [[ref](https://www.seagate.com/www-content/datasheets/pdfs/exos-x16-DS2011-1-1904US-en_US.pdf)], with 24 CPUs to tend to the NIC (2x10G) and disks (2x SSD, 12x LFF). Should be plenty! The second is that MinIO allows for buckets to be publicly shared in three ways: 1) read-only by setting `download`; 2) write-only by setting `upload`, and 3) read-write by setting `public`. I set `download` here, which means I should be able to fetch an asset now publicly: ``` pim@summer:~$ curl https://s3.chbtl0.ipng.ch/ipng-web-assets/ipng.ch/media/README.md Gruezi World. pim@summer:~$ curl https://ipng-web-assets.s3.chbtl0.ipng.ch/ipng.ch/media/README.md Gruezi World. ``` The first `curl` here shows the path-based access, while the second one shows an equivalent virtual-host based access. Both retrieve the file I just pushed via the public Internet. Whoot! # What's Next I'm going to be moving [[Restic](https://restic.net/)] backups from IPng's ZFS storage pool to this S3 service over the next few days. I'll also migrate PeerTube and possibly Mastodon from NVME based storage to replicated S3 buckets as well. Finally, the IPng website media that I mentioned above, should make for a nice followup article. Stay tuned!