LandfallDocs
Integrations · Context sources

Connecting MySQL to Landfall

The Landfall MySQL agent is a small program you run next to your databases. It dials out to Landfall, reads each database with a read-only user whose password never leaves your network, and answers questions only for an incident the database is part of.

  • As a source: an agent token Landfall mints for the agent, plus a read-only MySQL user you create on each database. The user's password stays with the agent.
  • As a trigger: not yet. See Opening incidents from MySQL.
Agent beside your databases
docker run, or its own Helm chart
→
Landfall
a job on this agent's queue
→
Room
Beacon, your agents, widgets

How it works

  • Outbound only. Landfall never connects to a database. The agent holds a long poll on its own job queue in Landfall, runs each read it is given, and posts the answer back.
  • Quiet until involved. A database is read only for an incident it is part of. Until then the agent keeps its snapshots in its own memory and sends nothing.
  • No rows, no statement text. The agent reads metadata and performance_schema summaries only. Statements appear as MySQL's normalized digests, with literals already replaced by ?. It never reads a table row, statement samples, lock data, column defaults or comments.
  • Read-only, checked. Every hour the agent reads its user's grants. If the user holds anything beyond the published set, the database reads as degraded, naming the extra grant, and the agent refuses to read it.
  • Standard parts. The agent is an OpenTelemetry Collector distribution: the upstream MySQL receiver and SQL query receiver do the collecting.

What you'll need

  • MySQL 8.0 or later (RDS for MySQL, Aurora MySQL, or your own) with performance_schema on. On RDS it is a parameter-group setting, and the micro and small burstable classes cannot run it.
  • Somewhere to run a container that can reach the database and api.landfalls.ai over HTTPS: a VM, a container service, or any Kubernetes cluster.
  • An administrator on each database, to create the agent's user.
  • An admin on your Landfall organization.

1. Add the agent in Landfall

  1. Integrations → Context sources → MySQL → Add agent

    In the left menu, open Integrations and choose MySQL under Context sources. Under Agent name, type the name the room will use (for example orders-prod) and click Add agent. Landfall mints the agent's token and shows three things under Run the agent: the database user to create, a docker run command and a helm install command. Copy what you need now. The token is shown once. Then click I have copied it.

    app.landfalls.ai · Integrations · MySQL
    The MySQL agents panel right after adding orders-prod-eu: Run the agent with the database user SQL, a Docker command and an Or Kubernetes helm command, each with a Copy button, then the agent orders prod Connected and orders-prod-eu Waiting for the agent.
    The token lines and token endings are blurred. This picture was taken before the image and chart moved to public.ecr.aws, so its commands name an older registry. The commands Landfall shows now are the ones in step 3.

2. Create the agent's user on each database

SQL
CREATE USER 'landfall'@'%' IDENTIFIED BY '<password>'
  REQUIRE SSL WITH MAX_USER_CONNECTIONS 10;
GRANT PROCESS, REPLICATION CLIENT, REFERENCES ON *.* TO 'landfall'@'%';
GRANT SELECT ON performance_schema.* TO 'landfall'@'%';
GRANT SELECT, EXECUTE ON sys.* TO 'landfall'@'%';

-- Optional, per migration history table, so applied migrations show with their time:
GRANT SELECT ON shop.flyway_schema_history TO 'landfall'@'%';

REFERENCES lets the agent see every table's columns and indexes without reading a row. Pick the password yourself; Landfall never sees it.

3. Run the agent

With Docker, one database

Terminal
docker run -d --name landfall-mysql-agent --restart unless-stopped \
  -v landfall-mysql-agent:/var/lib/landfall \
  -e LANDFALL_API_URL=https://api.landfalls.ai \
  -e LANDFALL_CONNECTION_ID=orders-prod \
  -e LANDFALL_TOKEN=lfa_... \
  -e LANDFALL_AGENT_NAME='orders-prod' \
  -e LANDFALL_MYSQL_NAME=orders-prod \
  -e LANDFALL_MYSQL_ENDPOINT=orders.abc123.eu-west-1.rds.amazonaws.com:3306 \
  -e LANDFALL_MYSQL_PASSWORD=<the landfall user's password> \
  -e LANDFALL_MYSQL_TLS=required \
  public.ecr.aws/r9i3z7v6/landfall-mysql-agent:0.1.0

The volume keeps the agent id the token binds to, so a replaced container reconnects as the same agent. For several databases (a primary and its replica, say), set LANDFALL_MYSQL_TARGETS to a JSON list of {"name","endpoint","tls","role"} and give each password as LANDFALL_MYSQL_PASSWORD_<NAME> (upper case, dashes as underscores).

With Kubernetes

Terminal
helm install landfall-mysql-agent oci://public.ecr.aws/r9i3z7v6/charts/landfall-mysql-agent \
  --namespace landfall --create-namespace \
  --set landfall.apiUrl=https://api.landfalls.ai \
  --set landfall.connectionId=orders-prod \
  --set token=lfa_... \
  --set mysql.name=orders-prod \
  --set mysql.endpoint=<host:3306> \
  --set mysql.password=<the landfall user's password> \
  --set mysql.tls=required

The chart keeps the token, the agent id and each password in one Secret (or use existingSecret), gives the agent no Kubernetes permissions, and lists several databases under mysql.targets.

4. Watch it connect

Within a minute the agent reads Connected in the same panel, and each database shows its own state from the agent's heartbeat: Connected, Sign-in refused, Unreachable, or Degraded with the reason.

app.landfalls.ai · Integrations · MySQL
The MySQL agents panel reading 1 of 1 connected: the agent orders prod Connected, last heard from 4 s ago, agent 0.1.0, with its database orders-prod Connected, and Rotate token, Revoke token, Re-bind to a new agent and Remove.
A test agent with one database. The token ending and the agent it is bound to are blurred.

What it looks like

Once a person adds the agent to an incident, the room's chat shows a card saying MySQL name is part of this investigation, with a Remove from investigation button. On a real run, a migration that dropped orders.legacy_price while the app still read it showed up as four pieces at once: the dropped column between two snapshots, Flyway's V2 with its time, a spike of 1054 errors, and the app's own statement leading the errors ranking. The ALTER waiting behind a long transaction showed as a metadata lock wait.

app.landfalls.ai · War room
A war room's chat: a test account declared the incident checkout 500s: Unknown column in orders at SEV2, then two rows from the same account, MySQL orders prod is part of this investigation and Cluster orbstack is part of this investigation, each with a Remove from investigation button.
A test room with both agents added. The MySQL card adds that agents can read its databases for this incident only, never a table row. The author is a test account.

What Beacon can read

A database answers only for an incident its agent is part of. The room has no control for adding one yet. A member of the organization adds the agent with one call to Landfall's API, using a personal API key from Your settings → API keys. The connection id is the LANDFALL_CONNECTION_ID value in the run command. An agent's own session is refused.

Terminal
curl -X POST https://api.landfalls.ai/o/<your-org>/incidents/<incident id>/agents/mysql/<connection id>/involve \
  -H "Authorization: Bearer lf_key_..."

An agent cannot be added to a resolved incident, and adding one twice is refused. Remove from investigation on the room's card takes it out at once.

OperationWhat comes back
mysql.schema_changesTables created, altered or dropped, columns and indexes before and after, from 60 s snapshots, plus rows applied in Flyway, Liquibase, Prisma, Django, Atlas, Knex or goose history tables
mysql.error_spikesErrors raised over the window by number, for example 1054 "Unknown column", with a 30 s series
mysql.top_statementsThe statements that did the most work, as digests: calls, time, errors, rows examined
mysql.lock_waitsWho waits on whom now: row locks and metadata locks, with the statements on both sides
mysql.sessionsActive sessions: user, host, database, state, seconds running, statement
mysql.describe_tableOne table's columns and indexes, now
mysql.statusThe server's own metrics: threads, connections, operations, row locks, buffer pool
mysql.replicationA replica's threads, lag and last errors

These operations reach Beacon and every agent that joins the room through get_signal_catalog and query_signals. How the agents fit with every other integration is on Integrations architecture.

Opening incidents from MySQL

Not yet. The agent answers questions for incidents that already exist. Open incidents from the alerts you already have, for example through Alertmanager or the other pages under Triggering integrations, and add the database's agent to the incident.

Troubleshooting

Still waiting for the agent?
  • Read the agent's log with docker logs landfall-mysql-agent.
  • The agent needs outbound HTTPS to api.landfalls.ai. Egress through a proxy goes in proxy.httpsProxy on the chart.
  • A token binds to the agent id the agent first registers with. A new container without the volume gets a new id; click Re-bind to a new agent.
Connected, but Beacon finds nothing?
  • The agent is not part of this incident. Beacon's read then fails with this database is not part of this investigation. A person in the room can add it.
  • Sign-in refused: check the password and the user's host in 'landfall'@'%'.
  • Degraded, over-privileged: the database's state names the extra grant. Revoke it; the agent reads the grants again within the hour, or at its next restart.
  • Degraded with performance_schema off: the agent still reports schema changes, and says per database why the rest is unavailable.

The agent's own reads are left out of the error counts it reports: on MySQL 8, scanning information_schema without data-dictionary access raises an internal error the server would otherwise count.

Reference

FieldValue
PanelIntegrations → Context sources → MySQL, field Agent name, button Add agent
Imagepublic.ecr.aws/r9i3z7v6/landfall-mysql-agent:0.1.0
Chartoci://public.ecr.aws/r9i3z7v6/charts/landfall-mysql-agent, release landfall-mysql-agent
Agent settingsLANDFALL_API_URL (https only), LANDFALL_CONNECTION_ID, LANDFALL_TOKEN, LANDFALL_AGENT_NAME
Database settingsLANDFALL_MYSQL_NAME (default mysql), LANDFALL_MYSQL_ENDPOINT (host:port), LANDFALL_MYSQL_USERNAME (default landfall), LANDFALL_MYSQL_PASSWORD, LANDFALL_MYSQL_TLS, LANDFALL_MYSQL_CA_FILE, LANDFALL_MYSQL_ROLE (primary or replica), LANDFALL_MYSQL_TARGETS. The chart takes the same as mysql.name, mysql.endpoint, mysql.username, mysql.password, mysql.tls, mysql.caFile, mysql.role and mysql.targets
TLSrequired (the default) verifies the server's certificate; for RDS, mount the region's CA bundle and set LANDFALL_MYSQL_CA_FILE. skip-verify encrypts without verifying, for a self-signed server. With a private CA such as RDS's, the snapshot connection encrypts without verifying the certificate, because the upstream SQL query receiver cannot take a CA file. The agent's own reads and the MySQL receiver verify against the CA file
GrantsPROCESS, REPLICATION CLIENT, REFERENCES on *.*; SELECT on performance_schema.*; SELECT, EXECUTE on sys.*; optionally SELECT on migration history tables. Checked every hour
ConnectionsUp to six per database, inside the user's limit of 10
Read operationsThe eight mysql.* operations above
AccessRead-only. Landfall writes nothing to a database and never holds its password
Tokenlfa_ followed by 40 characters, shown once. Landfall stores only its SHA-256 hash. It binds to the agent id kept in the agent's volume or Secret
RotateRotate token mints a new token; the old one keeps working for 24 hours
RevokeRevoke token refuses the agent at its next call. To remove its access to a database, DROP USER 'landfall'@'%'
Trigger endpointNone yet