├── LICENSE
├── README.md
├── mongodb-ebs-snapshot
└── mongodb-oplog-window-size
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Daniele Valeriani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mongodb-tools
2 | Scripts that I find useful for managing MongoDB
3 |
4 | #### mongodb-ebs-snapshot
5 |
6 | Takes consistent snapshots of MongoDB data directories stored on EBS volumes.
7 | Then expires old snapshots assuming each snapshot is taken at hh:00.
8 | The expiration policy is to keep 2 days of hourly snapshots, 2 weeks of daily
9 | snapshots at 00:00 and one year of monthly snapshots taken at 00:00 on the
10 | first day of the month.
11 | The expirer is still pretty basic but it does the job if you're happy with its policy.
12 |
13 | #### mongodb-oplog-window-size
14 |
15 | Reports the MongoDB oplog window size in hours to a local statsd server.
16 |
--------------------------------------------------------------------------------
/mongodb-ebs-snapshot:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | ################################################################################
4 | # MongoDB EBS snapshot
5 | # Version 0.2
6 | #
7 | # Takes consistent snapshots of MongoDB data directories stored on EBS volumes.
8 | # Then it expires old snapshots assuming each snapshot is taken at hh:00.
9 | # The expiration policy is to keep 2 days of hourly snapshots, 2 weeks of daily
10 | # snapshots at 00:00 and one year of monthly snapshots taken at 00:00 on the
11 | # first day of the month.
12 | ################################################################################
13 |
14 | import argparse
15 | import boto.utils
16 | import fcntl
17 | import logging
18 | import os
19 | import signal
20 | import sys
21 | from boto import ec2
22 | from datetime import datetime, timedelta
23 | from dateutil.relativedelta import relativedelta
24 |
25 |
26 | OWNER_ID = 012345543210 # Insert your AWS account id here
27 | LOCK_FILE = "/var/lock/mongodb-ebs-snapshot.lock"
28 |
29 | lock_fd = open(LOCK_FILE, "w")
30 | try:
31 | fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
32 | except IOError:
33 | sys.exit("Another instance is running")
34 |
35 | def cleanup(*args):
36 | fcntl.flock(lock_fd, fcntl.LOCK_UN)
37 | # Remove the lock file
38 | if os.path.exists(LOCK_FILE):
39 | os.remove(LOCK_FILE)
40 |
41 | for sig in (
42 | signal.SIGABRT,
43 | signal.SIGILL,
44 | signal.SIGINT,
45 | signal.SIGSEGV,
46 | signal.SIGTERM
47 | ):
48 | signal.signal(sig, cleanup)
49 |
50 | def round_to_minute(dt):
51 | """
52 | This is needed because there is a slight overhead between the start of the
53 | script execution and when it gets to the expiring part.
54 | """
55 | seconds = (dt - dt.min).seconds
56 | rounding = ((seconds + 30) // 60) * 60
57 | return dt + timedelta(0, rounding - seconds, -dt.microsecond)
58 |
59 | def generate_dt_list(start, end, delta):
60 | """
61 | Generate a list of accepted datetimes to use as a mask for the expiration:
62 | everything that doesn't fall in this list will be deleted.
63 | """
64 | res = []
65 | curr = start
66 | while curr < end:
67 | res.append(curr)
68 | curr += delta
69 | return res
70 |
71 | now = datetime.utcnow()
72 |
73 | # Logging
74 | logging.basicConfig(stream=sys.stdout, level=logging.INFO)
75 |
76 | # Arguments parser
77 | parser = argparse.ArgumentParser()
78 | parser.add_argument("-d", "--device", dest="device_name",
79 | help="Device to snapshot. default: /dev/sdd",
80 | default="/dev/sdd")
81 | parser.add_argument("-e", "--env", dest="env",
82 | help="Environment. default: prod",
83 | default="prod")
84 | parser.add_argument("-f", "--force", dest="force", action="store_true",
85 | help="Perform the lock even on a primary node. default: False",
86 | default=False)
87 | parser.add_argument("-n", "--name", dest="snapshot_name",
88 | help="Snapshot name. default: mongodb-data_",
89 | default="mongo_{0}".format(datetime.now().isoformat()))
90 | parser.add_argument("-r", "--region", dest="aws_region",
91 | help="AWS region. default: us-east-1",
92 | default="us-east-1")
93 | args = parser.parse_args()
94 |
95 | timestamp_format = "%Y-%m-%dT%H:%M:%S.%fZ"
96 | instance_id = boto.utils.get_instance_metadata()["instance-id"]
97 |
98 | # Fetch the volume id mapped to the mongodb data device
99 | conn = ec2.connect_to_region(args.aws_region)
100 | for vol in conn.get_all_volumes(filters={'attachment.instance-id': instance_id}):
101 | if vol.attach_data.device == args.device_name:
102 | logging.info("Detected MongoDB volume: {0}".format(vol.id))
103 | mongo_vol = vol
104 | break
105 | else:
106 | logging.critical("No attached volume could be found for {0}".format(args.device_name))
107 | sys.exit(1)
108 |
109 | # Take a snapshot
110 | logging.info("Taking a snapshot of {vol_id} and calling it {snap_name}".format(
111 | vol_id=mongo_vol.id,
112 | snap_name=args.snapshot_name
113 | )
114 | )
115 | new_snap = conn.create_snapshot(mongo_vol.id, "MongoDB snapshot for {0} data".format(args.env))
116 | new_snap.add_tag('Name', args.snapshot_name)
117 | new_snap.add_tag('Env', args.env)
118 |
119 | # Get point-in-time markers
120 | two_days_ago = now + relativedelta(days=-2, minute=0, second=0, microsecond=0)
121 | two_weeks_ago = now + relativedelta(weeks=-2, hour=0, minute=0, second=0, microsecond=0)
122 | a_year_ago = now + relativedelta(years=-1, day=1, hour=0, minute=0, second=0, microsecond=0)
123 |
124 | # Generate a list of timetamps
125 | allowed_snapshots = []
126 | allowed_snapshots += generate_dt_list(two_days_ago, now, relativedelta(hours=1))
127 | allowed_snapshots += generate_dt_list(two_weeks_ago, two_days_ago, relativedelta(days=1))
128 | allowed_snapshots += generate_dt_list(a_year_ago, two_weeks_ago, relativedelta(months=1))
129 |
130 | # Now delete the old snapshots
131 | filters = {
132 | "owner_id": OWNER_ID,
133 | "description": "MongoDB snapshot for {0} data".format(args.env)
134 | }
135 | snapshots = conn.get_all_snapshots(filters=filters)
136 | deletion_counter = 0
137 | for snapshot in snapshots:
138 | # The snapshot just taken is too close to now, so we skip it just in case
139 | if snapshot.id == new_snap.id:
140 | continue
141 | snapshot_time = round_to_minute(datetime.strptime(snapshot.start_time, timestamp_format))
142 | if snapshot_time not in allowed_snapshots:
143 | logging.info("Deleting {0} - Unnamed from {1}".format(snapshot.id, snapshot.start_time))
144 | deletion_counter = deletion_counter + 1
145 | snapshot.delete()
146 |
147 | logging.info("Deleted {0} snapshots".format(deletion_counter))
148 | cleanup()
149 |
--------------------------------------------------------------------------------
/mongodb-oplog-window-size:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ################################################################################
4 | # MongoDB oplog window size statsd emitter
5 | # Version 0.1
6 | #
7 | # Reports the MongoDB oplog window size in hours to a local statsd server.
8 | ################################################################################
9 |
10 | # Make sure to quit when running on an arbiter
11 | if [[ "$(mongo --quiet <<< 'rs.isMaster().arbiterOnly')" == "true" ]]; then
12 | exit 0
13 | fi
14 |
15 | hours=$(mongo --quiet --eval "
16 | var ri = db.getReplicationInfo();
17 | var window = new Date(ri.tLast).valueOf() - new Date(ri.tFirst).valueOf();
18 | print(window / (60*60*1000))
19 | ")
20 |
21 | # Send the metric to localhost:8125
22 | [[ -n "$hours" ]] && echo -n "mongodb.oplog.window_size:${hours}|g|" >/dev/udp/localhost/8125
23 |
--------------------------------------------------------------------------------