Skip to content

Commit 6ee9dc9

Browse files
committed
Merge pull request #14 from autopilotpattern/12-setup-and-demo-scripts
Improved configuration
2 parents 37825aa + 4279577 commit 6ee9dc9

File tree

11 files changed

+280
-119
lines changed

11 files changed

+280
-119
lines changed

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.DS_Store
2+
**/.DS_Store
3+
_env*

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ manta.pub
88

99
# temp
1010
python-manta/
11+
12+
# macos frustration
13+
.DS_Store

Dockerfile

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ FROM percona:5.6
22

33
RUN apt-get update \
44
&& apt-get install -y \
5-
python \
6-
python-dev \
7-
gcc \
8-
curl \
9-
percona-xtrabackup \
5+
python \
6+
python-dev \
7+
gcc \
8+
curl \
9+
percona-xtrabackup \
1010
&& rm -rf /var/lib/apt/lists/*
1111

1212
# get Python drivers MySQL, Consul, and Manta
@@ -17,22 +17,25 @@ RUN curl -Ls -o get-pip.py https://bootstrap.pypa.io/get-pip.py && \
1717
python-Consul==0.4.7 \
1818
manta==2.5.0
1919

20-
# get Containerpilot release
21-
RUN export CP_VERSION=2.0.1 &&\
22-
curl -Lo /tmp/containerpilot.tar.gz \
23-
https://github.com/joyent/containerpilot/releases/download/${CP_VERSION}/containerpilot-${CP_VERSION}.tar.gz && \
24-
tar -xzf /tmp/containerpilot.tar.gz && \
25-
mv /containerpilot /bin/
20+
# Add ContainerPilot and set its configuration file path
21+
ENV CONTAINERPILOT_VER 2.0.1
22+
ENV CONTAINERPILOT file:///etc/containerpilot.json
23+
RUN export CONTAINERPILOT_CHECKSUM=a4dd6bc001c82210b5c33ec2aa82d7ce83245154 \
24+
&& curl -Lso /tmp/containerpilot.tar.gz \
25+
"https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \
26+
&& echo "${CONTAINERPILOT_CHECKSUM} /tmp/containerpilot.tar.gz" | sha1sum -c \
27+
&& tar zxf /tmp/containerpilot.tar.gz -C /usr/local/bin \
28+
&& rm /tmp/containerpilot.tar.gz
2629

27-
# configure Containerpilot and MySQL
28-
COPY bin/* /bin/
30+
# configure ContainerPilot and MySQL
2931
COPY etc/* /etc/
32+
COPY bin/* /usr/local/bin/
3033

3134
# override the parent entrypoint
3235
ENTRYPOINT []
3336

3437
# use --console to get error logs to stderr
35-
CMD [ "/bin/containerpilot", \
38+
CMD [ "containerpilot", \
3639
"mysqld", \
3740
"--console", \
3841
"--log-bin=mysql-bin", \

README.md

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@ MySQL designed for automated operation using the [Autopilot Pattern](http://auto
1414
A running cluster includes the following components:
1515

1616
- [MySQL](https://dev.mysql.com/): we're using MySQL5.6 via [Percona Server](https://www.percona.com/software/mysql-database/percona-server), and [`xtrabackup`](https://www.percona.com/software/mysql-database/percona-xtrabackup) for running hot snapshots.
17-
- [Consul](https://www.consul.io/): used to coordinate replication and failover
17+
- [ContainerPilot](https://www.joyent.com/containerpilot): included in our MySQL containers to orchestrate bootstrap behavior and coordinate replication using keys and checks stored in Consul in the `preStart`, `health`, and `onChange` handlers.
18+
- [Consul](https://www.consul.io/): is our service catalog that works with ContainerPilot and helps coordinate service discovery, replication, and failover
1819
- [Manta](https://www.joyent.com/object-storage): the Joyent object store, for securely and durably storing our MySQL snapshots.
19-
- [ContainerPilot](http://containerpilot.io): included in our MySQL containers orchestrate bootstrap behavior and coordinate replication using keys and checks stored in Consul in the `onStart`, `health`, and `onChange` handlers.
2020
- `triton-mysql.py`: a small Python application that ContainerPilot will call into to do the heavy lifting of bootstrapping MySQL.
2121

22-
When a new MySQL node is started, ContainerPilot's `onStart` handler will call into `triton-mysql.py`.
22+
When a new MySQL node is started, ContainerPilot's `preStart` handler will call into `triton-mysql.py`.
2323

2424

25-
### Bootstrapping via `onStart` handler
25+
### Bootstrapping via `preStart` handler
26+
27+
`preStart` (formerly `onStart`) runs and must exit cleanly before the main application is started.
2628

2729
The first thing the `triton-mysql.py` application does is to ask Consul whether a primary node exists. If not, the application will atomically mark the node as primary in Consul and then bootstrap the node as a new primary. Bootstrapping a primary involves setting up users (root, default, and replication), and creating a default schema. Once the primary bootstrap process is complete, it will use `xtrabackup` to create a backup and upload it to Manta. The application then writes a TTL key to Consul which will tell us when next to run a backup, and a non-expiring key that records the path on Manta where the most recent backup was stored.
2830

2931
If a primary already exists, then the application will ask Consul for the path to the most recent backup snapshot and download it and the most recent binlog. The application will then ask Consul for the IP address of the primary and set up replication from that primary before allowing the new replica to join the cluster.
3032

31-
Replication in this architecture uses [Global Transaction Idenitifers (GTID)](https://dev.mysql.com/doc/refman/5.7/en/replication-gtids.html) so that replicas can autoconfigure their position within the binlog.
33+
Replication in this architecture uses [Global Transaction Identifiers (GTID)](https://dev.mysql.com/doc/refman/5.7/en/replication-gtids.html) so that replicas can autoconfigure their position within the binlog.
3234

3335
### Maintenance via `health` handler
3436

@@ -56,31 +58,31 @@ By default, the primary performs the backup snapshots. For deployments with high
5658

5759
## Running the cluster
5860

59-
Starting a new cluster is easy. Just run `docker-compose up -d` and in a few moments you'll have a running MySQL primary. Both the primary and replicas are described as a single `docker-compose` service. During startup, [ContainerPilot](http://containerpilot.io) will ask Consul if an existing primary has been created. If not, the node will initialize as a new primary and all future nodes will self-configure replication with the primary in their `onStart` handler.
61+
Starting a new cluster is easy once you have [your `_env` file set with the configuration details](#configuration), **just run `docker-compose up -d` and in a few moments you'll have a running MySQL primary**. Both the primary and replicas are described as a single `docker-compose` service. During startup, [ContainerPilot](http://containerpilot.io) will ask Consul if an existing primary has been created. If not, the node will initialize as a new primary and all future nodes will self-configure replication with the primary in their `preStart` handler.
6062

61-
Run `docker-compose scale mysql=2` to add a replica (or more than one!). The replicas will automatically configure themselves to to replicate from the primary and will register themselves in Consul as replicas once they're ready.
63+
**Run `docker-compose scale mysql=2` to add a replica (or more than one!)**. The replicas will automatically configure themselves to to replicate from the primary and will register themselves in Consul as replicas once they're ready.
6264

6365
### Configuration
6466

65-
Pass these variables in your environment or via an `_env` file.
67+
Pass these variables via an `_env` file. The included `setup.sh` can be used to test your Docker and Triton environment, and to encode the Manta SSH key in the `_env` file.
6668

6769
- `MYSQL_USER`: this user will be set up as the default non-root user on the node
6870
- `MYSQL_PASSWORD`: this user will be set up as the default non-root user on the node
71+
- `MANTA_URL`: the full Manta endpoint URL. (ex. `https://us-east.manta.joyent.com`)
72+
- `MANTA_USER`: the Manta account name.
73+
- `MANTA_SUBUSER`: the Manta subuser account name, if any.
74+
- `MANTA_ROLE`: the Manta role name, if any.
75+
- `MANTA_KEY_ID`: the MD5-format ssh key id for the Manta account/subuser (ex. `1a:b8:30:2e:57:ce:59:1d:16:f6:19:97:f2:60:2b:3d`); the included `setup.sh` will encode this automatically
76+
- `MANTA_PRIVATE_KEY`: the private ssh key for the Manta account/subuser; the included `setup.sh` will encode this automatically
77+
- `MANTA_BUCKET`: the path on Manta where backups will be stored. (ex. `/myaccount/stor/triton-mysql`); the bucket must already exist and be writeable by the `MANTA_USER`/`MANTA_PRIVATE_KEY`
6978

7079
These variables are optional but you most likely want them:
7180

7281
- `MYSQL_REPL_USER`: this user will be used on all instances to set up MySQL replication. If not set, then replication will not be set up on the replicas.
7382
- `MYSQL_REPL_PASSWORD`: this password will be used on all instances to set up MySQL replication. If not set, then replication will not be set up on the replicas.
7483
- `MYSQL_DATABASE`: create this database on startup if it doesn't already exist. The `MYSQL_USER` user will be granted superuser access to that DB.
75-
- `MANTA_URL`: the full Manta endpoint URL. (ex. `https://us-east.manta.joyent.com`)
76-
- `MANTA_USER`: the Manta account name.
77-
- `MANTA_SUBUSER`: the Manta subuser account name, if any.
78-
- `MANTA_ROLE`: the Manta role name, if any.
79-
- `MANTA_KEY_ID`: the MD5-format ssh key id for the Manta account/subuser (ex. `1a:b8:30:2e:57:ce:59:1d:16:f6:19:97:f2:60:2b:3d`).
80-
- `MANTA_PRIVATE_KEY`: the private ssh key for the Manta account/subuser.
81-
- `MANTA_BUCKET`: the path on Manta where backups will be stored. (ex. `/myaccount/stor/triton-mysql`)
8284
- `LOG_LEVEL`: will set the logging level of the `triton-mysql.py` application. It defaults to `DEBUG` and uses the Python stdlib [log levels](https://docs.python.org/2/library/logging.html#levels). In production you'll want this to be at `INFO` or above.
83-
- `TRITON_MYSQL_CONSUL` is the hostname for the Consul instance(s). Defaults to `consul`.
85+
- `CONSUL` is the hostname for the Consul instance(s). Defaults to `consul`.
8486
- `USE_STANDBY` tells the `triton-mysql.py` application to use a separate standby MySQL node to run backups. This might be useful if you have a very high write throughput on the primary node. Defaults to `no` (turn on with `yes` or `on`).
8587

8688
The following variables control the names of keys written to Consul. They are optional with sane defaults, but if you are using Consul for many other services you might have requirements to namespace keys:
@@ -107,7 +109,9 @@ These variables will be written to `/etc/my.cnf`.
107109

108110
### Where to store data
109111

110-
On Triton there's not need to use data volumes because the performance hit you normally take with overlay file systems in Linux doesn't happen with ZFS.
112+
This pattern automates the data management and makes container effectively stateless to the Docker daemon and schedulers. This is designed to maximize convenience and reliability by minimizing the external coordination needed to manage the database. The use of external volumes (`--volumes-from`, `-v`, etc.) is not recommended.
113+
114+
On Triton, there's no need to use data volumes because the performance hit you normally take with overlay file systems in Linux doesn't happen with ZFS.
111115

112116
### Using an existing database
113117

_env-example

Lines changed: 0 additions & 17 deletions
This file was deleted.

bin/triton-mysql.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
log = logging.getLogger('triton-mysql')
3434

35-
consul = pyconsul.Consul(host=os.environ.get('TRITON_MYSQL_CONSUL', 'consul'))
35+
consul = pyconsul.Consul(host=os.environ.get('CONSUL', 'consul'))
3636
config = None
3737

3838
# consts for node state
@@ -175,7 +175,7 @@ def __init__(self):
175175
self.user = os.environ.get('MANTA_SUBUSER', None)
176176
self.role = os.environ.get('MANTA_ROLE', None)
177177
self.key_id = os.environ.get('MANTA_KEY_ID', None)
178-
self.private_key = os.environ.get('MANTA_PRIVATE_KEY')
178+
self.private_key = os.environ.get('MANTA_PRIVATE_KEY').replace('#', '\n')
179179
self.url = os.environ.get('MANTA_URL',
180180
'https://us-east.manta.joyent.com')
181181
self.bucket = os.environ.get('MANTA_BUCKET',
@@ -203,15 +203,15 @@ def put_backup(self, backup_id, infile):
203203

204204
# ---------------------------------------------------------
205205

206-
class Containerpilot(object):
206+
class ContainerPilot(object):
207207
"""
208-
Containerpilot config is where we rewrite Containerpilot's own config
208+
ContainerPilot config is where we rewrite ContainerPilot's own config
209209
so that we can dynamically alter what service we advertise
210210
"""
211211

212212
def __init__(self, node):
213213
# TODO: we should make sure we can support JSON-in-env-var
214-
# the same as Containerpilot itself
214+
# the same as ContainerPilot itself
215215
self.node = node
216216
self.path = os.environ.get('CONTAINERPILOT').replace('file://', '')
217217
with open(self.path, 'r') as f:
@@ -231,12 +231,12 @@ def render(self):
231231
f.write(new_config)
232232

233233
def reload(self):
234-
""" force Containerpilot to reload its configuration """
235-
log.info('Reloading Containerpilot configuration.')
234+
""" force ContainerPilot to reload its configuration """
235+
log.info('Reloading ContainerPilot configuration.')
236236
os.kill(1, signal.SIGHUP)
237237

238238
# ---------------------------------------------------------
239-
# Top-level functions called by Containerpilot or forked by this program
239+
# Top-level functions called by ContainerPilot or forked by this program
240240

241241
def on_start():
242242
"""
@@ -257,24 +257,24 @@ def on_start():
257257
def health():
258258
"""
259259
Run a simple health check. Also acts as a check for whether the
260-
Containerpilot configuration needs to be reloaded (if it's been
260+
ContainerPilot configuration needs to be reloaded (if it's been
261261
changed externally), or if we need to make a backup because the
262262
backup TTL has expired.
263263
"""
264264
log.debug('health check fired.')
265265
try:
266266
node = MySQLNode()
267-
cb = Containerpilot(node)
268-
if cb.update():
269-
cb.reload()
267+
cp = ContainerPilot(node)
268+
if cp.update():
269+
cp.reload()
270270
return
271271

272-
# cb.reload() will exit early so no need to setup
272+
# cp.reload() will exit early so no need to setup
273273
# connection until this point
274274
ctx = dict(user=config.repl_user,
275275
password=config.repl_password,
276276
database=config.mysql_db,
277-
timeout=cb.config['services'][0]['ttl'])
277+
timeout=cp.config['services'][0]['ttl'])
278278
node.conn = wait_for_connection(**ctx)
279279

280280
# Update our lock on being the primary/standby.
@@ -312,15 +312,15 @@ def on_change():
312312
log.debug('on_change check fired.')
313313
try:
314314
node = MySQLNode()
315-
cb = Containerpilot(node)
316-
cb.update() # this will populate MySQLNode state correctly
315+
cp = ContainerPilot(node)
316+
cp.update() # this will populate MySQLNode state correctly
317317
if node.is_primary():
318318
return
319319

320320
ctx = dict(user=config.repl_user,
321321
password=config.repl_password,
322322
database=config.mysql_db,
323-
timeout=cb.config['services'][0]['ttl'])
323+
timeout=cp.config['services'][0]['ttl'])
324324
node.conn = wait_for_connection(**ctx)
325325

326326
# need to stop replication whether we're the new primary or not
@@ -341,8 +341,8 @@ def on_change():
341341
session_id = get_session(no_cache=True)
342342
if mark_with_session(PRIMARY_KEY, node.hostname, session_id):
343343
node.state = PRIMARY
344-
if cb.update():
345-
cb.reload()
344+
if cp.update():
345+
cp.reload()
346346
return
347347
else:
348348
# we lost the race to lock the session for ourselves
@@ -354,8 +354,8 @@ def on_change():
354354
# if it's not healthy, we'll throw an exception and start over.
355355
ip = get_primary_host(primary=primary)
356356
if ip == node.ip:
357-
if cb.update():
358-
cb.reload()
357+
if cp.update():
358+
cp.reload()
359359
return
360360

361361
set_primary_for_replica(node.conn)
@@ -791,9 +791,9 @@ def write_snapshot(conn):
791791
# we set the BACKUP_TTL before we run the backup so that we don't
792792
# have multiple health checks running concurrently. We then fork the
793793
# create_snapshot call and return. The snapshot process will be
794-
# re-parented to Containerpilot
794+
# re-parented to ContainerPilot
795795
set_backup_ttl()
796-
subprocess.Popen(['python', '/bin/triton-mysql.py', 'create_snapshot'])
796+
subprocess.Popen(['python', '/usr/local/bin/triton-mysql.py', 'create_snapshot'])
797797

798798
def set_backup_ttl():
799799
"""

common-compose.yml

Lines changed: 0 additions & 35 deletions
This file was deleted.

docker-compose.yml

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
mysql:
2-
extends:
3-
file: common-compose.yml
4-
service: mysql
5-
links:
6-
- consul:consul
2+
image: autopilotpattern/mysql:latest
3+
mem_limit: 4g
4+
restart: always
5+
# expose for linking, but each container gets a private IP for
6+
# internal use as well
7+
expose:
8+
- 3306
9+
labels:
10+
- triton.cns.services=mysql
11+
env_file: _env
12+
environment:
13+
- CONTAINERPILOT=file:///etc/containerpilot.json
714

815
consul:
9-
extends:
10-
file: common-compose.yml
11-
service: consul
16+
image: progrium/consul:latest
17+
command: -server -bootstrap -ui-dir /ui
18+
restart: always
19+
mem_limit: 128m
20+
ports:
21+
- 8500
22+
expose:
23+
- 53
24+
- 8300
25+
- 8301
26+
- 8302
27+
- 8400
28+
- 8500
29+
dns:
30+
- 127.0.0.1
31+
labels:
32+
- triton.cns.services=consul

0 commit comments

Comments
 (0)