# Monitoring MySQL, JVM, AMQ with Prometheus
:::danger
**Just read the drill section!**
:::
[TOC]
## VM setup
Create a CentOS 8 VM with 4 vcpus, 8G memory, and 60G disk.
```bash
virt-install \
--name prom \
--memory 8192 \
--vcpus 4 \
--os-variant centos8 \
--disk size=60,cache=none,io=native,bus=virtio \
--disk vol=iso/centos8.iso,device=cdrom \
--boot hd,cdrom,useserial=on \
--network network=default,model=virtio \
--graphics none \
--autostart \
--noreboot \
--noautoconsole
virsh start prom --console
```
1. Press `TAB` on boot menu, and append the parameter `console=ttyS0`.
2. Don't create user; just set the password for root.
3. Configure network.
4. Use `LVM`.
5. NTP: `tw.pool.ntp.org`.
6. Repo URL: `http://mirror01.idc.hinet.net/centos/8/BaseOS/x86_64/os/`.
7. Choose *minimal install* with *container management*.
### Post-installation
Resize *root* partition.
```bash
umount /dev/mapper/cl_prom-home
lvremove /dev/mapper/cl_prom-home
lvresize /dev/mapper/cl_prom-root /dev/vda2
xfs_growfs /dev/mapper/cl_prom-root
sed -i '/\/home/d' /etc/fstab
```
Upgrade system.
```bash
dnf upgrade --refresh && reboot
dnf system-upgrade reboot
```
## Serivce Installation
### MySQL 5.7.26 community
[Install MySQL 5.7 on CentOS 8 / RHEL 8 Linux](https://computingforgeeks.com/install-mysql-5-7-on-centos-rhel-linux/)
> There is no MySQL repository for EL 8, so we’ll use EL 7 repository instead. Create a new repository file.
[Installing MySQL on Linux Using the MySQL Yum Repository](https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html)
[MySQL Yum Repository](https://dev.mysql.com/downloads/repo/yum/)
#### TL;DR
```bash
dnf module disable mysql
dnf install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
dnf config-manager --disable mysql80-community
dnf config-manager --enable mysql57-community
dnf install 'dnf-command(versionlock)' mysql-community-server-5.7.26
dnf versionlock add mysql-community-server mysql-community-common
```
#### Detail
```bash
# Disable MySQL default AppStream repository
dnf module disable mysql
# Install MySQL community repo for CentOS 7, as that of CentOS 8 is deprived of MySQL 5.7
dnf install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
# Removal of the mysql repo
dnf remove $(rpm -aq | grep mysql)
# Check the installation
dnf repolist all | grep mysql
# Switch to 5.7
dnf config-manager --disable mysql80-community
dnf config-manager --enable mysql57-community
# Check for minor version 5.7.26
dnf repoquery --showduplicates mysql-community-server | grep 5.7.26
dnf install mysql-community-server-5.7.26
# Lock MySQL package version
dnf install 'dnf-command(versionlock)'
dnf versionlock add mysql-community-server mysql-community-common
```
```
podman pull mysql:5.7.26
dnf module disable mysql
```
#### Post-installation
```bash
systemctl --now enable mysqld
grep 'temporary password' /var/log/mysqld.log
mysql -uroot -p
```
```sql
ALTER USER 'root'@'localhost' IDENTIFIED BY '<password>';
exit
```
Create `~/.my.cnf` with content:
```
[client]
user=root
password=<passwd>
```
Running the command `mysql` in the future will automatically log you in with the MySQL *root* account.
### OpenJDK 1.8.0_232
Use [AdoptOpenJDK](https://adoptopenjdk.net/installation.html?variant=openjdk8&jvmVariant=hotspot#linux-pkg-rpm).
Create `/etc/yum.repos.d/adoptopenjdk.repo` with content:
```
[AdoptOpenJDK]
name=AdoptOpenJDK
baseurl=http://adoptopenjdk.jfrog.io/adoptopenjdk/rpm/centos/$releasever/$basearch
enabled=1
gpgcheck=1
gpgkey=https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public
```
```bash
dnf repoquery --showduplicates adoptopenjdk-8-hotspot | grep 232
dnf install adoptopenjdk-8-hotspot-0:8u232_b09-1
dnf versionlock add adoptopenjdk-8-hotspot
# Check java version
java -version
```
### ActiveMQ 5.11.0
- [Release page](https://activemq.apache.org/activemq-5011000-release)
- [How to install ActiveMQ on RHEL 8](https://linuxconfig.org/how-to-install-activemq-on-redhat-8)
```bash
# Create a non-privileged user to run amq
useradd activemq
mkdir /opt/activemq
chown activemq:activemq /opt/activemq
su activemq
cd /tmp
curl -LO http://archive.apache.org/dist/activemq/5.11.0/apache-activemq-5.11.0-bin.tar.gz
tar zxvf apache-activemq-5.11.0-bin.tar.gz -C /opt/activemq
/opt/activemq/bin/activemq start
```
:::warning
- http://karaf.apache.org/
- https://activemq.apache.org/osgi-integration
- https://activemq.apache.org/examples
:::
## Monitoring
- [Monitoring broker runtime data using Prometheus](https://access.redhat.com/documentation/en-us/red_hat_amq/7.6/html/deploying_amq_broker_on_openshift/assembly_br-broker-monitoring_broker-ocp)
- [JMX Exporter](https://github.com/prometheus/jmx_exporter)
<!-- - [Monitoring Java applications with the Prometheus JMX exporter and Grafana](https://grafana.com/blog/2020/06/25/monitoring-java-applications-with-the-prometheus-jmx-exporter-and-grafana/) -->
- [How to Monitor MySQL Deployments with Prometheus & Grafana at ScaleGrid](https://scalegrid.io/blog/how-to-monitor-mysql-deployments-with-prometheus-and-grafana-at-scalegrid/)
### jmx_exporter
- [Architecture and Monitoring Apache ActiveMQ with Grafana](https://www.metricfire.com/blog/architecture-and-monitoring-apache-activemq-with-grafana/)
- [How to Use Prometheus Monitoring With Java to Gather Data](https://www.openlogic.com/blog/prometheus-java-monitoring-and-gathering-data)
- [Github: prometheus/jmx_exporter](https://github.com/prometheus/jmx_exporter)
### mysqld_exporter
```sql
CREATE USER 'exporter'@'localhost' IDENTIFIED BY '<password>' WITH MAX_USER_CONNECTIONS 5;
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'exporter'@'localhost';
GRANT SELECT ON performance_schema.* TO 'exporter'@'localhost';
SHOW VARIABLES WHERE Variable_name = 'port';
```
The typical result of *port* is **3306**.
I'll denote this number by *<port>*.
The [GitHub Readme page](https://github.com/prometheus/mysqld_exporter/blob/master/README.md) instructs that we compile the exporter from source:
```bash
git clone https://github.com/prometheus/mysqld_exporter.git\
mv mysqld_exporter
make
```
This requires that *go* exists on the host; if not, run `dnf install golang`, and *make* again.
If you face the following message among *assertions*, check the existence of the executable `mysqld_exporter`.
```!
.level=debug msg="Error querying version" err="Error 1045: Access denied for user 'root'@'localhost' (using password: NO)"
```
If `mysqld_exporter` is found, don't worry about the error message; it's the part of the Makefile that queries the version of *mysqld* via `'root'@'localhost'`.
As long as `mysqld_exporter` is successfully compiled, we are good.
We test it as follows:
```bash
export DATA_SOURCE_NAME='exporter:<password>@(localhost:<port>)/'
./mysqld_exporter --web.listen-address localhost:8081 &
curl -s localhost:8081/metrics | grep "mysql_up 1"
# Kill the job
```
:::warning
By default, *mysqld_exporter* exposes the metrics for *prometheus* on port **9104**.
We intentionally changed it to 8081 with the *web.listen-address* flag.
:::
If the output of *grep* is empty, something is wrong, as it should be `mysql_up 1`; consider the following:
1. Check if *<password>* and *<port>* is correct.
2. Check if the *sql grants* are correct.
Then, one would have to write a systemd unit file for *mysqld_exporter*.
This could be quite a hassle.
I suggest using *podman* on CentOS 8.
:::info
Podman is a closely-compatible *docker* alternative.
Podman's is singular in its *daemonless* container runtime and *rootless* containers.
However, its default rootless containers are painful when it comes to networking, so we will be running *rootfull* containers, i.e., running `sudo podman` instead of `podman`.
The pain comes from having to deal with:
```!
rootless CNI infra image not present - please build image from https://github.com/containers/podman/blob/v2.2.1-rhel/contrib/rootless-cni-infra/ and tag as "rootless-cni-infra"
```
:::
:::warning
The *docker* package of CentOS 8 is, in fact, a facade of *podman*.
Running *docker* in CentOS 8 prints the following message.
```!
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
```
To hide this message, simply do:
```bash
touch /etc/containers/nodocker
```
:::
To install the containerized mysqld_exporter, the *mysql grants* have to updated.
First, find out subnet and gateway associated with podman's default network -- *podman*.
:::danger
1. Podman or docker in the rest of this section must be run as root.
2. If the docker package is installed, the command docker can be used and is synonymous with podman.
:::
```bash
sudo docker network inspect podman
```
In my case, I have:
```json
{
"gateway": "10.88.0.1",
"subnet": "10.88.0.0/16"
}
```
According to the [offical documentation](https://dev.mysql.com/doc/refman/5.7/en/account-names.html), it seems that access to mysqld can be granted to a whole subnet but the netmask must be spelled out in full, instead of just the netmask length, e.g., `10.88.0.0/255.255.0.0`.
I'll refer to `10.88.0.1` and `10.88.0.0/255.255.0.0` as *<gateway>* and *<subnet>*, respectively.
```sql
CREATE USER 'exporter'@'<subnet>' IDENTIFIED BY '<password>' WITH MAX_USER_CONNECTIONS 5;
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'exporter'@'<subnet>';
GRANT SELECT ON performance_schema.* TO 'exporter'@'<subnet>';
```
:::info
I grant access to whole subnet as, I won't configure static IPs for containers.
:::
```bash
docker run -d \
--name sql_exporter \
-p 9104:9104 \
-e DATA_SOURCE_NAME="exporter:<password>@(<gateway>:<port>)/" \
prom/mysqld-exporter
```
Similarly, we test with the following, where the output should read `mysql_up 1`, precisely.
```bash
curl -s localhost:8081/metrics | grep "mysql_up 1"
```
### ActiveMQ Pub/Sub
[GitHub: tomitribe/JMS-1.1-PubSub-Queue-Example-with-ActiveMQ](https://www.tomitribe.com/blog/5-minutes-or-less-activemq-with-jms-queues-and-topics/)
## Prometheus
```bash
chown nobody:nobody <prom_data>
# In case that nobody is not defined:
# chown 65534:65534 <prom_data>
docker run \
-p 9090:9090 \
-v <prom_data>:/prometheus \
-v <prom_config.yml>:/etc/prometheus/prometheus.yml \
prom/prometheus
```
# Drill
## Docker
With a fresh install, I can confirm that CentOS 8 uses an emulated docker by podman.
To prevent networking pitfalls, prepend *sudo* to every *docker* command on CentOS 8; CentOS 7 is not subject to this problem.
## Prometheus
Create the prometheus config file `prom_config.yml` with content:
```yaml
global:
scrape_interval: 15s # default 60s
evaluation_interval: 15s # default 60s
scrape_timeout: 10s # default 10s
scrape_configs:
- job_name: 'activemq'
static_configs:
- targets: ['<docker_bridge_ip>:<jmx_exporter_port>']
- job_name: 'mysql'
static_configs:
- targets: ['<docker_bridge_ip>:<mysqld_exporter_port>']
```
Start prometheus with:
```bash
sudo docker run -d \
--restart unless-stopped \
--name prom \
-p 9090:<prometheus_port> \
-v prom_config.yml:/etc/prometheus/prometheus.yml:z \
prom/prometheus
```
## MySQL exporter
We will run the mysqld_exporter as a container.
The exporter will log into the MySQL database to be monitored and read metrics.
Hence, we have to create a special user in the database for this occasion.
### Identify container network configuration
Identify the network configuration of the default bridge maintained by the container-runtime.
#### For podman
```bash
sudo docker network inspect podman
```
You should see something like:
```json
{
"gateway": "10.88.0.1",
"subnet": "10.88.0.0/16"
}
```
#### For docker
```bash
ip a show docker0
```
This should result in something like:
```
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:3aff:fe13:cb14/64 scope link
valid_lft forever preferred_lft forever
```
### Add a SQL user
According to the [offical documentation](https://dev.mysql.com/doc/refman/5.7/en/account-names.html), it seems that access to mysqld can be granted to a whole subnet but the netmask must be spelled out in full, instead of just the netmask length.
To assist discussion, I'll define variables *<gateway>* and *<subnet>*.
Taking the two examples above:
- *<gateway>*:
- `10.88.0.1` for podman.
- `172.17.0.1` for docker.
- *<subnet>*:
- `10.88.0.0/255.255.0.0` for podman.
- `172.17.0.0/255.255.0.0` for docker.
The SQL user will be called *<user>* whose password will be *<password>*.
With MySQL 5.7, the SQL variable `MAX_USER_CONNECTIONS` can be set to mitigate the load on the database due to metric-scraping; I'll denote this number by *<connections>* which is typically 3.
Log in the database as the root user:
```bash
mysql -u root -p
```
Then, run these commands:
```sql
CREATE USER '<user>'@'<subnet>' IDENTIFIED BY '<password>' WITH MAX_USER_CONNECTIONS <connections>;
GRANT PROCESS, REPLICATION CLIENT ON *.* TO '<user>'@'<subnet>';
GRANT SELECT ON performance_schema.* TO '<user>'@'<subnet>';
```
### Start the exporter
```bash
sudo docker run -d \
--restart unless-stopped \
--name mysqld_exporter \
-p 9104:<mysqld_exporter_port> \
-e DATA_SOURCE_NAME="<user>:<password>@(<gateway>:3306)/" \
prom/mysqld-exporter
```
Test with:
```bash
curl localhost:<mysqld_exporter_port>/metrics -s | grep "mysql_up 1"
```
On success, you should see the string "mysql_up 1" being echoed; if the output is empty, something is wrong.
## JMX exporter
This will monitor ActiveMQ and its associated JVM.
I can't find a way to run the JMX exporter without restarting ActiveMQ.
:::spoiler
JMX exporter is meant to be run as a javaagent, e.g.,
```bash
java -javaagent:<agent> -jar <program>
```
I read about dynamically loaded javaagent.
With this in mind, I experimented with [jattach](https://github.com/apangin/jattach).
```bash
jattach <pid> load jmx_prometheus_javaagent-0.15.0.jar true <jmx_exporter_port> jmx_exporter/example_configs/activemq.yml
```
*<pid>* is determined by:
```bash
${ACTIVEMQ_HOME}/bin/activemq status
```
Unfortunately, this failed with:
```!
Connected to remote JVM
Response code = -1
```
The content of `jmx_exporter/example_configs/activemq.yml` is:
```yaml
lowercaseOutputName: true
lowercaseOutputLabelNames: true
blacklistObjectNames:
- "org.apache.activemq:clientId=*,*"
whitelistObjectNames:
- "org.apache.activemq:destinationType=Queue,*"
- "org.apache.activemq:destinationType=Topic,*"
- "org.apache.activemq:type=Broker,brokerName=*"
- "org.apache.activemq:type=Topic,brokerName=*"
rules:
- pattern: org.apache.activemq<type=Broker, brokerName=(\S*), destinationType=Queue, destinationName=(\S*)><>(\w+)
name: activemq_queue_$3
attrNameSnakeCase: true
labels:
destination: $2
- pattern: org.apache.activemq<type=Broker, brokerName=(\S*), destinationType=Topic, destinationName=(\S*)><>(\w+)
name: activemq_topic_$3
attrNameSnakeCase: true
labels:
destination: $2
- pattern: org.apache.activemq<type=Broker, brokerName=(\S*)><>CurrentConnectionsCount
name: activemq_connections
type: GAUGE
- pattern: org.apache.activemq<type=Broker, brokerName=(\S*)><>Total(.*)Count
name: activemq_$2_total
type: COUNTER
- pattern: org.apache.activemq<type=Broker, brokerName=(\S*)><>(.*)PercentUsage
name: activemq_$2_usage_ratio
type: GAUGE
valueFactor: 0.01
```
:::
I will fix the version to 0.15.0.
First, clone the GitHub repo to get the configuration file for the JMX exporter.
Then, download the the JMX exporter (a jar file).
```bash
git clone --depth=1 -b parent-0.15.0 https://github.com/prometheus/jmx_exporter.git
curl -LO https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.15.0/jmx_prometheus_javaagent-0.15.0.jar
```
Suppose the path to the ActiveMQ directory is `apache-activemq-5.11.0`.
First, stop the ActiveMQ broker, e.g.,
```bash
apache-activemq-5.11.0/bin/activemq stop
```
Edit the file `apache-activemq-5.11.0/bin/env` by changing
```bash
ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS_MEMORY -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=$ACTIVEMQ_CONF/login.config"
```
to
```bash
ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS_MEMORY -javaagent:/path/to/jmx_prometheus_javaagent-0.15.0.jar=8080:/path/to/jmx_exporter/example_configs/activemq.yml -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=$ACTIVEMQ_CONF/login.config"
```
Test with `curl localhost:8080/metrics`.