diff --git a/sample-apps/docker/.config b/sample-apps/docker/.config new file mode 100644 index 0000000..d17d908 --- /dev/null +++ b/sample-apps/docker/.config @@ -0,0 +1,2 @@ +METRICS_FILE=expected_metrics +JOB_LABEL=integrations/ diff --git a/sample-apps/docker/.gitignore b/sample-apps/docker/.gitignore new file mode 100644 index 0000000..1a97878 --- /dev/null +++ b/sample-apps/docker/.gitignore @@ -0,0 +1 @@ +cloud-init.yaml diff --git a/sample-apps/docker/Makefile b/sample-apps/docker/Makefile new file mode 100644 index 0000000..62650ac --- /dev/null +++ b/sample-apps/docker/Makefile @@ -0,0 +1,41 @@ +VM_NAME ?= docker-sample-app +CONFIG_FILE_DIR := jinja/variables +CONFIG_FILE := $(CONFIG_FILE_DIR)/cloud-init.yaml +LOKI_INSTANCE ?= your-loki-instance:3100 +PROMETHEUS_INSTANCE ?= your-prometheus-instance:9090 + +# Import last to ensure expected parameters are set and available to import from this file +include ../Makefile + +.PHONY: run run-ci stop exporter fetch-prometheus-metrics render-config launch-vm clean defaultconfig + +run: launch-vm + @echo "VM $(VM_NAME) launched and configured." + +run-ci: clean defaultconfig launch-vm + @echo "Running in CI mode" + +stop: + @multipass stop $(VM_NAME) + @multipass delete $(VM_NAME) + @multipass purge + +render-config: + @docker run --rm -v $(shell pwd)/jinja/templates:/templates -v $(shell pwd)/jinja/variables:/variables dinutac/jinja2docker:latest /templates/cloud-init-template.yaml /variables/cloud-init.yaml --format=yaml > cloud-init.yaml + +launch-vm: render-config + @multipass launch -n $(VM_NAME) --cloud-init cloud-init.yaml + +clean: + @rm -f cloud-init.yaml + @rm -rf $(CONFIG_FILE_DIR) + +defaultconfig: + @mkdir -p $(CONFIG_FILE_DIR) + @echo "interval: \"10s\"" >> jinja/variables/cloud-init.yaml + @echo "loki_url: http://$(LOKI_INSTANCE)/loki/api/v1/push" >> jinja/variables/cloud-init.yaml + @echo "loki_user: your_loki_username" >> jinja/variables/cloud-init.yaml + @echo "loki_pass: your_loki_password" >> jinja/variables/cloud-init.yaml + @echo "prom_url: http://$(PROMETHEUS_INSTANCE)/api/v1/push" >> jinja/variables/cloud-init.yaml + @echo "prom_user: your_prometheus_username" >> jinja/variables/cloud-init.yaml + @echo "prom_pass: your_prometheus_password" >> jinja/variables/cloud-init.yaml diff --git a/sample-apps/docker/README.md b/sample-apps/docker/README.md new file mode 100644 index 0000000..665b9ed --- /dev/null +++ b/sample-apps/docker/README.md @@ -0,0 +1,64 @@ +# Docker sample app + +This sample application creates an Ubuntu VM integrated with Alloy for metric and log collection. This sample app utilizes cloud-init and Make commands to facilitate the setup, configuration, and monitoring of Docker using the node-exporter. + +It is largely based off of [Canonical's cloud-init for Docker](https://raw.githubusercontent.com/canonical/multipass/refs/heads/main/data/cloud-init-yaml/cloud-init-docker.yaml), which is also used for our database spinup, with added Alloy configuration steps. + +## Prerequisites + +Before you begin, ensure you have the following installed: + +- [Multipass](https://multipass.run/) +- Docker (for rendering the cloud-init configuration) +- Git (for cloning the repository) + +## Quick Start for new users + +To get started with the sample app, follow these steps: + +1. **Clone the repository**: + ```sh + git clone https://github.com/grafana/integration-sample-apps.git + cd integration-sample-apps/docker + ``` + +2. **Set up default config**: + Execute `make defaultconfig` to create a template file with default configuration variables. Modify `jinja/variables/cloud-init.yaml` to connect Alloy to an external Prometheus compatible TSDB and/or Loki server. + +3. **Render cloud-init configuration**: + Run `make render-config` to generate the `cloud-init.yaml` file based on your configuration. + +4. **Create and set up VMs**: + Use `make run` to start the Docker sample app. + +6. **Stop and clean Up**: + Use `make stop` to clean up the VM and `make clean` to remove temporary files. + +## Make commands + +- `make defaultconfig`: Initializes the configuration file with default values for cloud-init templates. +- `make render-config`: Generates the `cloud-init.yaml` configuration file using the defined variables. +- `make run`: Creates the Docker sample app. +- `make clean`: Deletes all created VMs and performs cleanup. + +## Default configuration variables + +- `prom_pass`: Your Prometheus password. +- `prom_user`: Your Prometheus username. +- `prom_url`: URL for Prometheus push endpoint (e.g., `http://your-prometheus-instance:9090/api/v1/push`). +- `loki_url`: URL for Loki push endpoint (e.g., `http://your-loki-instance:3100/loki/api/v1/push`). +- `loki_user`: Your Loki username. +- `loki_pass`: Your Loki password. + +## Validating services + +### Alloy +- **Check service status**: Confirm that Alloy is running. + ```bash + systemctl status alloy.service + ``` +- **Review configuration**: Verify the configuration in `/etc/alloy/config.alloy` is correct. +- **Check logs**: Review Alloy logs for any connectivity or configuration issues. + ```bash + journalctl -u alloy.service + ``` diff --git a/sample-apps/docker/jinja/templates/cloud-init-template.yaml b/sample-apps/docker/jinja/templates/cloud-init-template.yaml new file mode 100644 index 0000000..ed9aa00 --- /dev/null +++ b/sample-apps/docker/jinja/templates/cloud-init-template.yaml @@ -0,0 +1,279 @@ +# jinja/templates/cloud-init-template.yaml +# Cloud-init configuration for setting up Alloy and required Docker sample-app +# Largely based on https://raw.githubusercontent.com/canonical/multipass/refs/heads/main/data/cloud-init-yaml/cloud-init-docker.yaml +# With added configuration for Grafana Alloy + +apt: + sources: + docker: + source: | + deb https://download.docker.com/linux/ubuntu $RELEASE stable + key: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + + 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 + YT90qFF93M3v01BbxP+EIY2/9tiIPbrd + =0YYh + -----END PGP PUBLIC KEY BLOCK----- + grafana: + source: deb https://apt.grafana.com stable main + keyid: 963FA27710458545 + keyserver: https://apt.grafana.com/gpg.key + +packages: + - git + - gpg + - alloy + - binfmt-support + - docker-ce + - docker-ce-cli + - docker-compose + - containerd.io + - jq + - qemu-user-static + - skopeo + +snap: + commands: + - snap install yq + +runcmd: + # General setup + - sudo apt-get update + + # Configure Alloy + - sudo systemctl enable alloy.service + - sudo systemctl start alloy.service + + + # General setup + - sudo apt-get update + + - | + # add `ubuntu` to the `docker` group + adduser ubuntu docker + + - | + # disable swap + sysctl vm.swappiness=0 + echo "vm.swappiness = 0" | tee -a /etc/sysctl.conf + + - | + # disable unnecessary services + systemctl disable man-db.timer man-db.service --now + systemctl disable apport.service apport-autoreport.service --now + systemctl disable apt-daily.service apt-daily.timer --now + systemctl disable apt-daily-upgrade.service apt-daily-upgrade.timer --now + systemctl disable unattended-upgrades.service --now + systemctl disable motd-news.service motd-news.timer --now + systemctl disable bluetooth.target --now + systemctl disable ua-messaging.service ua-messaging.timer --now + + - | + # portainer, to easily manage containers + # available on http://:9000 + docker run -d \ + -p 9000:9000 \ + --name=portainer \ + --restart=always \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v portainer_data:/data \ + portainer/portainer-ce + + - | + # apt cleanup + apt-get autoremove -y + + # Configure Docker for Alloy + - sudo usermod -a -G docker alloy + - sudo sed -i 's/User=alloy/User=root/' /etc/systemd/system/alloy.service + - sudo systemctl daemon-reload + + # Configure Alloy + - sudo systemctl restart alloy.service + +write_files: +# Alloy configuration +- owner: root:root + path: /etc/alloy/config.alloy + content: | + prometheus.exporter.self "alloy_check" { } + + discovery.relabel "alloy_check" { + targets = prometheus.exporter.self.alloy_check.targets + + rule { + target_label = "instance" + replacement = constants.hostname + } + + rule { + target_label = "alloy_hostname" + replacement = constants.hostname + } + + rule { + target_label = "job" + replacement = "integrations/alloy-check" + } + } + + prometheus.scrape "alloy_check" { + targets = discovery.relabel.alloy_check.output + forward_to = [prometheus.relabel.alloy_check.receiver] + + scrape_interval = "60s" + } + + prometheus.relabel "alloy_check" { + forward_to = [prometheus.remote_write.metrics_service.receiver] + + rule { + source_labels = ["__name__"] + regex = "(prometheus_target_sync_length_seconds_sum|prometheus_target_scrapes_.*|prometheus_target_interval.*|prometheus_sd_discovered_targets|alloy_build.*|prometheus_remote_write_wal_samples_appended_total|process_start_time_seconds)" + action = "keep" + } + } + + prometheus.remote_write "metrics_service" { + endpoint { + url = "{{ prom_url }}" + + {% if prom_user and prom_pass -%} + basic_auth { + username = "{{ prom_user }}" + password = "{{ prom_pass }}" + } + {%- endif %} + } + } + + loki.write "grafana_cloud_loki" { + endpoint { + url = "{{ loki_url }}" + + {% if loki_user and loki_pass -%} + basic_auth { + username = "{{ loki_user }}" + password = "{{ loki_pass }}" + } + {%- endif %} + } + } + + prometheus.exporter.cadvisor "integrations_cadvisor" { + docker_only = true + } + + discovery.relabel "integrations_cadvisor" { + targets = prometheus.exporter.cadvisor.integrations_cadvisor.targets + + rule { + target_label = "job" + replacement = "integrations/docker" + } + + rule { + target_label = "instance" + replacement = constants.hostname + } + } + + prometheus.scrape "integrations_cadvisor" { + targets = discovery.relabel.integrations_cadvisor.output + forward_to = [prometheus.remote_write.metrics_service.receiver] + } + + discovery.docker "logs_integrations_docker" { + host = "unix:///var/run/docker.sock" + refresh_interval = "5s" + } + + discovery.relabel "logs_integrations_docker" { + targets = [] + + rule { + target_label = "job" + replacement = "integrations/docker" + } + + rule { + target_label = "instance" + replacement = constants.hostname + } + + rule { + source_labels = ["__meta_docker_container_name"] + regex = "/(.*)" + target_label = "container" + } + + rule { + source_labels = ["__meta_docker_container_log_stream"] + target_label = "stream" + } + } + + loki.source.docker "logs_integrations_docker" { + host = "unix:///var/run/docker.sock" + targets = discovery.docker.logs_integrations_docker.targets + forward_to = [loki.write.grafana_cloud_loki.receiver] + relabel_rules = discovery.relabel.logs_integrations_docker.rules + refresh_interval = "5s" + } diff --git a/sample-apps/docker/tests/configs/docker_metrics.config b/sample-apps/docker/tests/configs/docker_metrics.config new file mode 100644 index 0000000..9c2cc56 --- /dev/null +++ b/sample-apps/docker/tests/configs/docker_metrics.config @@ -0,0 +1 @@ +JOB_LABEL=integrations/docker diff --git a/sample-apps/docker/tests/metrics/docker_metrics b/sample-apps/docker/tests/metrics/docker_metrics new file mode 100644 index 0000000..5fd70df --- /dev/null +++ b/sample-apps/docker/tests/metrics/docker_metrics @@ -0,0 +1,16 @@ +container_cpu_usage_seconds_total +container_fs_reads_total +container_fs_usage_bytes +container_fs_writes_total +container_last_seen +container_memory_usage_bytes +container_network_receive_bytes_total +container_network_receive_errors_total +container_network_receive_packets_dropped_total +container_network_transmit_bytes_total +container_network_transmit_errors_total +container_network_transmit_packets_dropped_total +container_spec_memory_reservation_limit_bytes +machine_memory_bytes +machine_scrape_error +up