diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..7851d6a17 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + +jobs: + build_and_push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write # חשוב ל-GHCR + + env: + IMAGE_TAG: ${{ github.sha }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set image name (org) + run: echo "IMAGE_NAME=ghcr.io/kamatechorg/minio-bootstrap" >> $GITHUB_ENV + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR (using GITHUB_TOKEN) + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & Push + uses: docker/build-push-action@v6 + with: + context: ./storage/minio-bootstrap + file: ./storage/minio-bootstrap/Dockerfile + push: true + tags: | + ${{ env.IMAGE_NAME }}:latest + ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} + + - name: Validate compose + run: docker compose -f storage/docker-compose.yml config -q diff --git a/README.md b/README.md index f7e205ef9..168c3db4a 100644 --- a/README.md +++ b/README.md @@ -1 +1,55 @@ -# AgCloud \ No newline at end of file +# MinIO Tiering Demo (All-in-One Docker) + +Runs HOT + COLD MinIO servers in one container, with bootstrap script that: +- Creates buckets: imagery (HOT), cold-tier (COLD) +- Enables versioning on HOT +- Defines lifecycle: move objects >7 days → COLD + +## Quickstart +```bash +docker build -t minio-allinone . +docker run -d --name minio-aio -p 9000:9000 -p 9001:9001 -p 9100:9100 -p 9101:9101 minio-allinone +``` + +Consoles: HOT → http://localhost:9001 | COLD → http://localhost:9101 + +Upload test: +```bash +docker exec -it minio-aio mc cp /etc/hosts hot/imagery/test.txt +``` +# MinIO Monitoring Demo (Docker Compose) + +Runs MinIO with HOT + COLD endpoints, Prometheus scraping enabled, and Grafana dashboard for monitoring. + +- MinIO exposes metrics on ports 9000 (HOT) and 9100 (COLD) +- Prometheus scrapes MinIO metrics from both endpoints +- Grafana shows dashboards with: + - **PUT latency (p95)** + - **Bucket size** + +## Quickstart +```bash +docker compose up -d --build +``` + +## Consoles & UIs +- HOT MinIO Console → http://localhost:9001 +- COLD MinIO Console → http://localhost:9101 +- Prometheus → http://localhost:9090 +- Grafana → http://localhost:3000 (admin / admin) + +## Example PromQL +### PUT latency (p95) +```promql +histogram_quantile( + 0.95, + sum by (le, job, instance) ( + rate(minio_s3_requests_ttfb_seconds_distribution_bucket{api="putobject"}[5m]) + ) +) +``` + +### Bucket size per bucket +```promql +sum by (bucket) (minio_bucket_usage_size_bytes) +``` diff --git a/monitoring/dashbaord.png b/monitoring/dashbaord.png new file mode 100644 index 000000000..22d4f54de Binary files /dev/null and b/monitoring/dashbaord.png differ diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml new file mode 100644 index 000000000..a8f9b4bf3 --- /dev/null +++ b/monitoring/docker-compose.yml @@ -0,0 +1,46 @@ +# הערה: אפשר למחוק את ה-version בראש (היא לא נדרשת ב-compose v2) +services: + minio: + build: + context: ../storage/minio-bootstrap # <<< הנתיב לתיקייה שבה ה-Dockerfile שלך + dockerfile: Dockerfile + container_name: minio + environment: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: minio123 + MINIO_PROMETHEUS_AUTH_TYPE: public + HOT_ADDRESS: ":9000" + HOT_CONSOLE: ":9001" + COLD_ADDRESS: ":9100" + COLD_CONSOLE: ":9101" + HOT_ENDPOINT: "http://127.0.0.1:9000" + COLD_ENDPOINT: "http://127.0.0.1:9100" + MC_ALIAS_HOT: "hot" + MC_ALIAS_COLD: "cold" + MC_HOST_hot: "http://minio:minio123@127.0.0.1:9000" + MC_HOST_cold: "http://minio:minio123@127.0.0.1:9100" + ports: + - "9000:9000" + - "9001:9001" + - "9100:9100" + - "9101:9101" + + prometheus: + image: prom/prometheus:latest + container_name: prometheus + command: ["--config.file=/etc/prometheus/prometheus.yml"] + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro + ports: + - "9090:9090" + depends_on: [minio] + + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - "3000:3000" + environment: + GF_SECURITY_ADMIN_USER: admin + GF_SECURITY_ADMIN_PASSWORD: admin + depends_on: [prometheus] diff --git a/monitoring/grafana-dashboard.json b/monitoring/grafana-dashboard.json new file mode 100644 index 000000000..acea070bc --- /dev/null +++ b/monitoring/grafana-dashboard.json @@ -0,0 +1,244 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Dashboard showing MinIO bucket usage and PUT request latency\n", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "bew9cih96fx8gb" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "disableTextWrap": false, + "editorMode": "code", + "expr": "minio_s3_requests_ttfb_seconds_distribution{api=\"putobject\"}", + "fullMetaSearch": false, + "includeNullMetadata": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "PUT latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "bew9cih96fx8gb" + }, + "description": "minio_cluster_usage_total_bytes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "bew9cih96fx8gb" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "minio_cluster_usage_total_bytes", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Bucket size", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "MinIO Monitoring", + "uid": "83c84814-bdf4-4a16-b3a0-b2cf9ae50de8", + "version": 3 + } \ No newline at end of file diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml new file mode 100644 index 000000000..7af868281 --- /dev/null +++ b/monitoring/prometheus.yml @@ -0,0 +1,13 @@ +global: + scrape_interval: 5s + +scrape_configs: + - job_name: 'minio-hot' + metrics_path: /minio/v2/metrics/cluster + static_configs: + - targets: ['minio:9000'] + + - job_name: 'minio-cold' + metrics_path: /minio/v2/metrics/cluster + static_configs: + - targets: ['minio:9100'] diff --git a/monitoring/somefile.txt b/monitoring/somefile.txt new file mode 100644 index 000000000..3b18e512d --- /dev/null +++ b/monitoring/somefile.txt @@ -0,0 +1 @@ +hello world diff --git a/storage/data/cold/cold-tier/37fcaa6724dd1518/2d/43/2d436d17-48d7-462f-bea5-0336cef4b00a/xl.meta b/storage/data/cold/cold-tier/37fcaa6724dd1518/2d/43/2d436d17-48d7-462f-bea5-0336cef4b00a/xl.meta new file mode 100644 index 000000000..e9a0b670a Binary files /dev/null and b/storage/data/cold/cold-tier/37fcaa6724dd1518/2d/43/2d436d17-48d7-462f-bea5-0336cef4b00a/xl.meta differ diff --git a/storage/data/cold/cold-tier/37fcaa6724dd1518/3c/76/3c763823-b1cd-4c81-a976-edf61769a8fb/xl.meta b/storage/data/cold/cold-tier/37fcaa6724dd1518/3c/76/3c763823-b1cd-4c81-a976-edf61769a8fb/xl.meta new file mode 100644 index 000000000..dd6972e6f Binary files /dev/null and b/storage/data/cold/cold-tier/37fcaa6724dd1518/3c/76/3c763823-b1cd-4c81-a976-edf61769a8fb/xl.meta differ diff --git a/storage/data/cold/cold-tier/37fcaa6724dd1518/ad/fb/adfbf567-3f25-4984-93de-c6191b007ce4/xl.meta b/storage/data/cold/cold-tier/37fcaa6724dd1518/ad/fb/adfbf567-3f25-4984-93de-c6191b007ce4/xl.meta new file mode 100644 index 000000000..e3505cc73 Binary files /dev/null and b/storage/data/cold/cold-tier/37fcaa6724dd1518/ad/fb/adfbf567-3f25-4984-93de-c6191b007ce4/xl.meta differ diff --git a/storage/data/cold/cold-tier/37fcaa6724dd1518/c8/4a/c84a68f7-3d1d-42bd-ba45-8ac22eb99b3b/xl.meta b/storage/data/cold/cold-tier/37fcaa6724dd1518/c8/4a/c84a68f7-3d1d-42bd-ba45-8ac22eb99b3b/xl.meta new file mode 100644 index 000000000..2ce66af30 Binary files /dev/null and b/storage/data/cold/cold-tier/37fcaa6724dd1518/c8/4a/c84a68f7-3d1d-42bd-ba45-8ac22eb99b3b/xl.meta differ diff --git a/storage/data/config/init.sh b/storage/data/config/init.sh new file mode 100644 index 000000000..8f7aeb574 --- /dev/null +++ b/storage/data/config/init.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -euo pipefail + +echo "[bootstrap] setting mc aliases..." +mc alias set hot http://minio-hot:9000 minio minio123 +mc alias set cold http://minio-cold:9000 minio minio123 + +echo "[bootstrap] waiting for hot..." +until mc ls hot >/dev/null 2>&1; do sleep 2; done +echo "[bootstrap] waiting for cold..." +until mc ls cold >/dev/null 2>&1; do sleep 2; done + +echo "[bootstrap] creating buckets (idempotent)..." +mc mb --ignore-existing hot/imagery || true +mc mb --ignore-existing cold/cold-tier || true + +echo "[bootstrap] enabling versioning..." +mc version enable hot/imagery || true + +echo "[bootstrap] ensuring remote tier COLD exists..." +if ! mc ilm tier ls hot | grep -q ' COLD '; then + mc ilm tier add s3 hot COLD \ + --endpoint http://minio-cold:9000 \ + --access-key minio \ + --secret-key minio123 \ + --bucket cold-tier \ + --region us-east-1 +fi + +echo "[bootstrap] importing lifecycle policy..." +mc ilm import hot/imagery < /config/lifecycle.json + +echo "[bootstrap] current rules:" +mc ilm rule ls hot/imagery || true +echo "[bootstrap] DONE." diff --git a/storage/data/config/lifecycle.json b/storage/data/config/lifecycle.json new file mode 100644 index 000000000..9e41ad618 --- /dev/null +++ b/storage/data/config/lifecycle.json @@ -0,0 +1 @@ +{"Rules":[{"ID":"d2lhca81cpp9ns2hgsr0","Status":"Enabled","Transition":{"StorageClass":"COLD","Days":7}}]} diff --git a/storage/data/hot/imagery/dev/obj.txt/xl.meta b/storage/data/hot/imagery/dev/obj.txt/xl.meta new file mode 100644 index 000000000..ce949dd6e Binary files /dev/null and b/storage/data/hot/imagery/dev/obj.txt/xl.meta differ diff --git a/storage/docker-compose.yml b/storage/docker-compose.yml new file mode 100644 index 000000000..324fdcd8f --- /dev/null +++ b/storage/docker-compose.yml @@ -0,0 +1,63 @@ +services: + minio-hot: + image: minio/minio:latest + container_name: minio-hot + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: minio123 + ports: + - "9000:9000" + - "9001:9001" + volumes: + - ./data/hot:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/ready"] + interval: 3s + timeout: 2s + retries: 20 + networks: [minionet] + + minio-cold: + image: minio/minio:latest + container_name: minio-cold + command: server /data --console-address ":9003" + environment: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: minio123 + ports: + - "9002:9000" + - "9003:9001" + volumes: + - ./data/cold:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/ready"] + interval: 3s + timeout: 2s + retries: 20 + networks: [minionet] + + mc-bootstrap: + image: minio/mc:latest + container_name: mc-bootstrap + depends_on: + minio-hot: + condition: service_healthy + minio-cold: + condition: service_healthy + volumes: + - ./config:/config:ro + entrypoint: ["/bin/sh","-c","set -e; /bin/sh /config/init.sh"] + networks: [minionet] + restart: "no" + + mc: + image: minio/mc:latest + container_name: mc + depends_on: [minio-hot, minio-cold] + entrypoint: ["/bin/sh","-c","sleep infinity"] + networks: [minionet] + +networks: + minionet: + driver: bridge diff --git a/storage/lifecycle.md b/storage/lifecycle.md new file mode 100644 index 000000000..a12c9e968 --- /dev/null +++ b/storage/lifecycle.md @@ -0,0 +1,138 @@ +# MinIO Lifecycle — `hot/imagery` → `COLD` after 7 days + +**Owner:** Storage Team +**Scope:** Local dev (Docker Compose) and can be adapted to KinD/K8s. +**Goal:** Objects in the hot bucket (`hot/imagery`) automatically transition to the remote tier **COLD** after **7 days**. +**Why:** Reduce hot storage footprint/cost while keeping data retrievable by downstream analytics. + +--- + +## Architecture (at a glance) +- **hot** MinIO instance (source bucket): `hot/imagery` +- **cold** MinIO instance (remote tier target): bucket `cold-tier` +- **Tier name:** `COLD` (case sensitive) +- **Access pattern:** Transparent — clients keep reading `hot/imagery/...`; MinIO hot fetches the object from the remote tier when needed. + +--- + +## Prerequisites +1. **MinIO aliases** are configured in `mc`: + - `hot → http://minio-hot:9000` + - `cold → http://minio-cold:9000` +2. **Buckets created:** + - `hot/imagery` (source) + - `cold/cold-tier` (remote tier target) +3. **Versioning enabled on the source bucket:** + ```bash + mc version enable hot/imagery + mc version info hot/imagery # should show: Enabled + ``` +4. **Remote tier configured on `hot`:** + ```bash + mc ilm tier add s3 hot COLD --endpoint http://minio-cold:9000 --access-key minio --secret-key minio123 --bucket cold-tier --region us-east-1 + ``` + Verify: + ```bash + mc ilm tier ls hot + # Expect: a row with Name=COLD, Type=s3, Bucket=cold-tier + ``` + +--- + +## Policy (prod) — JSON +Put the following into `lifecycle.json` in this folder: +```json +{"Rules":[{"ID":"d2lhca81cpp9ns2hgsr0","Status":"Enabled","Transition":{"StorageClass":"COLD","Days":7}}]} +``` + +### Apply via JSON (inside the `mc` container) +```bash +# copy lifecycle.json into the mc container (from host/shell): +docker cp lifecycle.json mc:/tmp/lifecycle.json + +# then inside mc: +mc ilm import hot/imagery < /tmp/lifecycle.json +mc ilm rule ls hot/imagery +``` + +> Alternative (CLI only): +> ```bash +> mc ilm rule add hot/imagery --transition-days 7 --transition-tier COLD --prefix "" +> ``` + +--- + +## Dev Validation (fast, safe) +Use a **temporary** rule that applies only to a `dev/` prefix and transitions **immediately**. This validates the tiering without waiting 7 days. + +1) **Add the temporary rule (inside `mc`):** +```bash +mc ilm rule add hot/imagery --transition-days 0 --transition-tier COLD --prefix "dev/" +mc ilm rule ls hot/imagery +``` + +2) **Upload a test object and verify:** +```bash +echo "hello" > /tmp/obj.txt +mc cp /tmp/obj.txt hot/imagery/dev/obj.txt + +# Check on hot: +mc stat hot/imagery/dev/obj.txt +# Expect a line: X-Amz-Storage-Class: COLD +``` + +3) **(Optional) Additional checks:** +- Stop cold and try to read (should fail), then start and try again: + ```bash + # on host + docker stop minio-cold + # in mc + mc cat hot/imagery/dev/obj.txt # expect failure + # on host + docker start minio-cold + # in mc + mc cat hot/imagery/dev/obj.txt # should work again + ``` +- Watch logs while reading: + ```bash + # on host + docker logs -f minio-hot + docker logs -f minio-cold + ``` + +4) **Cleanup the temporary rule:** +```bash +# Find the ID of the dev rule (PREFIX=dev/) and remove it +mc ilm rule ls hot/imagery +mc ilm rule rm hot/imagery --id +``` + +5) **Export the final prod policy and copy it out (optional):** +```bash +# in mc +mc ilm export hot/imagery > /tmp/lifecycle.json + +# on host (Windows PowerShell) +docker cp mc:/tmp/lifecycle.json .\lifecycle.json +type .\lifecycle.json +``` + +--- + +## Troubleshooting +- **No transition happens:** ensure `mc version info hot/imagery` shows `Enabled`. +- **Wrong tier name:** `--transition-tier` must match exactly the tier shown in `mc ilm tier ls hot` (e.g., `COLD`). +- **Cannot see the object under the same key in cold:** expected. Cold stores objects under internal keys; rely on `mc stat` (`X-Amz-Storage-Class: COLD`) and logs/trace. +- **Latency on first read after transition:** expected; hot fetches from cold. +- **Permission errors:** verify access/secret keys used in `mc ilm tier add`. + +--- + +## Notes for production +- Keep the prod rule (7 days) only; remove any temporary dev rules. +- Consider bucket-level IAM: ingestion services get `s3:PutObject` only to the appropriate bucket/prefix. +- Consider monitoring: expose MinIO metrics to Prometheus; chart PUT latency and bucket size in Grafana. + +--- + +**Done.** This document + `lifecycle.json` are the required deliverables. diff --git a/storage/minio-bootstrap/Dockerfile b/storage/minio-bootstrap/Dockerfile new file mode 100644 index 000000000..1f4c28c4c --- /dev/null +++ b/storage/minio-bootstrap/Dockerfile @@ -0,0 +1,114 @@ +FROM alpine:3.19 + +# ===== Deps & Binaries ===== +RUN apk add --no-cache bash curl ca-certificates supervisor && \ + update-ca-certificates && \ + curl -fsSL https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/local/bin/minio && \ + curl -fsSL https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc && \ + chmod +x /usr/local/bin/minio /usr/local/bin/mc + +# ===== Dirs ===== +RUN mkdir -p /data/hot /data/cold /entrypoint /config /var/log/supervisor + +# ===== init.sh (Bootstrap) ===== +RUN cat > /entrypoint/init.sh <<'SH' && chmod +x /entrypoint/init.sh +#!/usr/bin/env bash +set -euo pipefail + +# Aliases via env allow mc to work even when ENTRYPOINT is bypassed +mc alias set "${MC_ALIAS_HOT}" "${HOT_ENDPOINT}" "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}" || true +mc alias set "${MC_ALIAS_COLD}" "${COLD_ENDPOINT}" "${MINIO_ROOT_USER}" "${MINIO_ROOT_PASSWORD}" || true + +echo "[bootstrap] waiting for hot (${HOT_ENDPOINT})..." +until mc ls "${MC_ALIAS_HOT}" >/dev/null 2>&1; do sleep 2; done +echo "[bootstrap] waiting for cold (${COLD_ENDPOINT})..." +until mc ls "${MC_ALIAS_COLD}" >/dev/null 2>&1; do sleep 2; done + +echo "[bootstrap] creating buckets (idempotent)..." +mc mb --ignore-existing "${MC_ALIAS_HOT}/imagery" || true +mc mb --ignore-existing "${MC_ALIAS_COLD}/cold-tier" || true + +echo "[bootstrap] enabling versioning on hot/imagery..." +mc version enable "${MC_ALIAS_HOT}/imagery" || true + +echo "[bootstrap] ensuring remote tier COLD exists on HOT..." +# Robust check using JSON; add only if missing. || true keeps script idempotent +if ! mc ilm tier ls "${MC_ALIAS_HOT}" --json | grep -q '"Name":"COLD"'; then + mc ilm tier add s3 "${MC_ALIAS_HOT}" COLD \ + --endpoint "${COLD_ENDPOINT}" \ + --access-key "${MINIO_ROOT_USER}" \ + --secret-key "${MINIO_ROOT_PASSWORD}" \ + --bucket cold-tier \ + --region us-east-1 || true +fi + +echo "[bootstrap] importing lifecycle policy..." +mc ilm import "${MC_ALIAS_HOT}/imagery" < "${LIFECYCLE_PATH}" + +echo "[bootstrap] current rules:" +mc ilm rule ls "${MC_ALIAS_HOT}/imagery" || true +echo "[bootstrap] DONE." +SH + +# ===== lifecycle.json ===== +RUN cat > /config/lifecycle.json <<'JSON' +{ + "Rules": [ + { + "ID": "d2lhca81cpp9ns2hgsr0", + "Status": "Enabled", + "Transition": { "StorageClass": "COLD", "Days": 7 } + } + ] +} +JSON + +# ===== supervisord.conf ===== +RUN cat > /etc/supervisord.conf <<'CONF' +[supervisord] +nodaemon=true +logfile=/var/log/supervisor/supervisord.log + +[program:minio_hot] +command=/usr/local/bin/minio server --address %(ENV_HOT_ADDRESS)s --console-address %(ENV_HOT_CONSOLE)s /data/hot +stdout_logfile=/var/log/supervisor/minio_hot.log +stderr_logfile=/var/log/supervisor/minio_hot.err +autorestart=true +priority=10 + +[program:minio_cold] +command=/usr/local/bin/minio server --address %(ENV_COLD_ADDRESS)s --console-address %(ENV_COLD_CONSOLE)s /data/cold +stdout_logfile=/var/log/supervisor/minio_cold.log +stderr_logfile=/var/log/supervisor/minio_cold.err +autorestart=true +priority=10 + +# Bootstrap waits for servers (init.sh itself waits for readiness) +[program:bootstrap] +command=/bin/bash -lc "/entrypoint/init.sh" +stdout_logfile=/var/log/supervisor/bootstrap.log +stderr_logfile=/var/log/supervisor/bootstrap.err +autorestart=false +startsecs=0 +priority=20 +CONF + +# ===== Sensible defaults & mc ENV aliases ===== +ENV MINIO_ROOT_USER=minio \ + MINIO_ROOT_PASSWORD=minio123 \ + HOT_ADDRESS=:9000 \ + HOT_CONSOLE=:9001 \ + COLD_ADDRESS=:9100 \ + COLD_CONSOLE=:9101 \ + HOT_ENDPOINT=http://127.0.0.1:9000 \ + COLD_ENDPOINT=http://127.0.0.1:9100 \ + MC_ALIAS_HOT=hot \ + MC_ALIAS_COLD=cold \ + LIFECYCLE_PATH=/config/lifecycle.json \ + MC_HOST_hot="http://minio:minio123@127.0.0.1:9000" \ + MC_HOST_cold="http://minio:minio123@127.0.0.1:9100" + +EXPOSE 9000 9001 9100 9101 + +# Run both MinIO servers + bootstrap in one container +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]