When configuring MySQL master-slave replication through SaltStack, a common requirement is executing certain states just once during initial setup. The specific pain point comes when you need to retrieve the master's binary log position (SHOW MASTER STATUS
) exactly once to configure the slave.
Consider this typical state declaration:
get_master_status:
module.run:
- name: mysql.query
- query: SHOW MASTER STATUS
This runs every highstate, potentially overwriting your replication coordinates. The fundamental issue is that SaltStack states are designed to be idempotent - they naturally want to enforce the declared state on every run.
Here are three battle-tested approaches:
1. The File Flag Pattern
A common solution using Salt's built-in capabilities:
# Create flag file if doesn't exist
create_flag:
file.touch:
- name: /etc/mysql/master_status_grabbed
- unless: test -f /etc/mysql/master_status_grabbed
# Only run if flag is missing
get_master_status:
module.run:
- name: mysql.query
- query: SHOW MASTER STATUS
- unless: test -f /etc/mysql/master_status_grabbed
2. Custom Execution Module
For cleaner implementation, create _modules/onetime.py
:
def execute(name, fun, **kwargs):
flag_path = f"/var/lib/salt/onetime_flags/{name}"
if __salt__['file.file_exists'](flag_path):
return {'name': name, 'result': True, 'comment': 'Already executed'}
ret = __salt__[fun](**kwargs)
if ret.get('result', False):
__salt__['file.touch'](flag_path)
return ret
Usage in SLS:
get_master_status:
onetime.execute:
- fun: mysql.query
- query: SHOW MASTER STATUS
3. Orchestrate with Reactor
For complex scenarios, use the reactor system:
# reactor/master_status.sls
execute_once:
local.state.apply:
- tgt: 'mysql-master'
- arg:
- mysql.show_master_status
- kwarg:
- queue: True
# reactor/init.sls
react_master_ready:
reactor:
- 'salt/minion/*/start':
- /srv/reactor/master_status.sls
When implementing one-time execution in production:
- Store flags in persistent storage (/var/lib/salt preferred over /tmp)
- Include cleanup logic in your teardown procedures
- Consider using Salt's mine system for master status if you need periodic updates
- For complex multi-machine sequences, orchestrate with state.sls
When automating MySQL replication setup with SaltStack, we often need to capture the master status exactly once during initial configuration. The naive approach of running SHOW MASTER STATUS
in every highstate execution creates inconsistency in replication parameters.
Many users try these problematic patterns:
# Problematic approach 1 - Runs every time
get_master_status:
mysql.query:
- query: SHOW MASTER STATUS
# Problematic approach 2 - Creates failed states
/tmp/master_status_done:
file.missing: []
get_master_status:
mysql.query:
- query: SHOW MASTER STATUS
- require:
- file: /tmp/master_status_done
Solution 1: Custom State Module with Flagging
Create a custom state module that implements atomic flag checking:
# salt/_states/myonce.py
import os
def query(name, query, flag_file=None):
if flag_file and os.path.exists(flag_file):
return {'name': name, 'result': True, 'comment': 'Already executed'}
# Your actual query logic here
result = __salt__['mysql.query'](query)
if flag_file:
with open(flag_file, 'w') as f:
f.write('executed')
return {'name': name, 'result': True, 'changes': {'data': result}}
Solution 2: Using Orchestration with Pillar
For cluster-wide one-time operations, consider orchestration:
# salt/orchestrate/mysql_replication.sls
configure_replication:
salt.state:
- tgt: 'mysql-master-*'
- sls:
- mysql.master_setup
- pillar:
first_run: True
For enterprise deployments, combine several techniques:
# salt/mysql/replication/init.sls
{% if salt['pillar.get']('mysql:initial_replication') %}
capture_master_status:
myonce.query:
- name: SHOW MASTER STATUS
- flag: /etc/mysql/master_status_captured
- require:
- sls: mysql.server_installed
configure_slave:
cmd.run:
- name: |
mysql -e "CHANGE MASTER TO
MASTER_HOST='{{ pillar.mysql.master_host }}',
MASTER_LOG_FILE='{{ salt.file.read('/etc/mysql/master_log_file') }}',
MASTER_LOG_POS={{ salt.file.read('/etc/mysql/master_log_pos') }}"
- unless: mysql -e "SHOW SLAVE STATUS" | grep -q "Waiting for master"
{% endif %}
- Using Salt's
event.send
to trigger one-time operations - Leveraging reactors for cluster-wide coordination
- Storing execution state in external systems (Vault, etcd)