Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ce87806

Browse files
authored
Merge pull request autopilotpattern#3 from cjihrig/master
initial implementation
2 parents 3171f14 + 4fbb245 commit ce87806

File tree

9 files changed

+364
-2
lines changed

9 files changed

+364
-2
lines changed

Dockerfile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
FROM postgres:9.6.4-alpine
2+
3+
RUN apk add --update bash curl nodejs
4+
5+
# Install Consul
6+
# Releases at https://releases.hashicorp.com/consul
7+
RUN set -ex \
8+
&& export CONSUL_VERSION=0.7.5 \
9+
&& export CONSUL_CHECKSUM=40ce7175535551882ecdff21fdd276cef6eaab96be8a8260e0599fadb6f1f5b8 \
10+
&& curl --retry 7 --fail -vo /tmp/consul.zip "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip" \
11+
&& echo "${CONSUL_CHECKSUM} /tmp/consul.zip" | sha256sum -c \
12+
&& unzip /tmp/consul -d /usr/local/bin \
13+
&& rm /tmp/consul.zip \
14+
# Create empty directories for Consul config and data \
15+
&& mkdir -p /etc/consul \
16+
&& mkdir -p /var/lib/consul \
17+
&& mkdir /config
18+
19+
# Install Consul template
20+
# Releases at https://releases.hashicorp.com/consul-template/
21+
RUN set -ex \
22+
&& export CONSUL_TEMPLATE_VERSION=0.19.0 \
23+
&& export CONSUL_TEMPLATE_CHECKSUM=31dda6ebc7bd7712598c6ac0337ce8fd8c533229887bd58e825757af879c5f9f \
24+
&& curl --retry 7 --fail -Lso /tmp/consul-template.zip "https://releases.hashicorp.com/consul-template/${CONSUL_TEMPLATE_VERSION}/consul-template_${CONSUL_TEMPLATE_VERSION}_linux_amd64.zip" \
25+
&& echo "${CONSUL_TEMPLATE_CHECKSUM} /tmp/consul-template.zip" | sha256sum -c \
26+
&& unzip /tmp/consul-template.zip -d /usr/local/bin \
27+
&& rm /tmp/consul-template.zip
28+
29+
# Add Containerpilot and set its configuration
30+
COPY etc/containerpilot.json5 /etc
31+
ENV CONTAINERPILOT /etc/containerpilot.json5
32+
ENV CONTAINERPILOT_VERSION 3.3.3
33+
34+
RUN export CONTAINERPILOT_CHECKSUM=8d680939a8a5c8b27e764d55a78f5e3ae7b42ef4 \
35+
&& export archive=containerpilot-${CONTAINERPILOT_VERSION}.tar.gz \
36+
&& curl -Lso /tmp/${archive} \
37+
"https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VERSION}/${archive}" \
38+
&& echo "${CONTAINERPILOT_CHECKSUM} /tmp/${archive}" | sha1sum -c \
39+
&& tar zxf /tmp/${archive} -C /usr/local/bin \
40+
&& rm /tmp/${archive}
41+
42+
# Add Postgres templates
43+
COPY etc/pg_hba.conf /etc
44+
COPY etc/postgresql.conf.ctmpl /etc
45+
COPY etc/recovery.conf.ctmpl /etc
46+
47+
# Set up Node.js app
48+
COPY bin /bin
49+
RUN cd /bin && npm install && npm run lint
50+
51+
ENTRYPOINT []
52+
CMD ["/usr/local/bin/containerpilot"]

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,31 @@
1-
# postgres
2-
Work in progress, not stable, expect force pushes of this repo
1+
# Autopilot Pattern Postgres
2+
3+
This repo serves as a blueprint demonstrating [Postgres](https://www.postgresql.org/) designed for automated operation using the [Autopilot Pattern](http://autopilotpattern.io/).
4+
5+
[![DockerPulls](https://img.shields.io/docker/pulls/autopilotpattern/postgres.svg)](https://registry.hub.docker.com/u/autopilotpattern/postgres/)
6+
[![DockerStars](https://img.shields.io/docker/stars/autopilotpattern/postgres.svg)](https://registry.hub.docker.com/u/autopilotpattern/postgres/)
7+
8+
## Environment Variables
9+
10+
- `CONSUL`: consul hostname
11+
- `CONSUL_AGENT`: determines if the consul agent is executed in the container
12+
- `DEBUG`: used to control the Node.js `debug` module
13+
- `LOG_LEVEL`: control the amount of logging from ContainerPilot
14+
- `PGDATA`: sets the location of the Postgres database files
15+
- `POSTGRES_DB`: the name of the Postgres database
16+
- `POSTGRES_PASSWORD`: the password used to access the Postgres database
17+
- `POSTGRES_USER`: the username used to access the Postgres database
18+
19+
## Example Usage
20+
21+
```
22+
$ cd examples/compose
23+
$ docker-compose up -d
24+
$ docker-compose scale db=3
25+
```
26+
27+
The first instance creates a Postgres master. All additional instances create read-only replicas.
28+
29+
## Modifying this Pattern
30+
31+
This image extends the [official `postgres:9.6.4-alpine` image](https://hub.docker.com/_/postgres/). Postgres can be configured as needed by modifying `etc/pg_hba.conf`, `etc/postgresql.conf.ctmpl`, and `etc/recovery.conf.ctmpl`, and then rebuilding the image.

bin/config-postgres.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use strict';
2+
const Cp = require('child_process');
3+
const Path = require('path');
4+
const Consulite = require('consulite');
5+
const consulHost = `${process.env.CONSUL}:8500`;
6+
const debug = require('debug')('config-postgres');
7+
const isPreStart = process.argv[2] === 'preStart';
8+
const isOnChange = process.argv[2] === 'onChange';
9+
10+
debug('running node script. preStart=%s, onChange=%s, env=%j',
11+
isPreStart, isOnChange, process.env);
12+
13+
if (!isPreStart && !isOnChange) {
14+
throw new Error('run in an unexpected mode');
15+
}
16+
17+
lookupService((err, service) => {
18+
if (err) {
19+
throw err;
20+
}
21+
22+
debug('service = %j', service);
23+
24+
createPgConfFile(service);
25+
26+
if (isPreStart && !service.isMaster) {
27+
createBaseBackup(service);
28+
createRecoveryFile(service);
29+
}
30+
});
31+
32+
33+
function createPgConfFile (service) {
34+
const template = '/etc/postgresql.conf.ctmpl';
35+
const conf = '/etc/postgresql.conf';
36+
37+
debug('creating config file (%s) from template (%s).', conf, template);
38+
39+
const cmd = 'consul-template -once' +
40+
` -consul-addr ${consulHost}` +
41+
` -template "${template}:${conf}` +
42+
(isOnChange ? ':pkill -SIGHUP postgres' : '') + '"';
43+
const env = service.isMaster ?
44+
Object.assign({}, process.env, { POSTGRES_MASTER: '1' }) :
45+
process.env;
46+
47+
Cp.execSync(cmd, { env });
48+
}
49+
50+
51+
function createRecoveryFile (service) {
52+
const template = '/etc/recovery.conf.ctmpl';
53+
const conf = Path.join(process.env.PGDATA, 'recovery.conf');
54+
55+
debug('creating config file (%s) from template (%s).', conf, template);
56+
57+
if (service.isMaster) {
58+
throw new Error('attempting to create recovery file for master');
59+
}
60+
61+
const cmd = 'consul-template -once' +
62+
` -consul-addr ${consulHost}` +
63+
` -template "${template}:${conf}"`;
64+
const env = Object.assign({}, process.env, {
65+
POSTGRES_MASTER_IP: service.address
66+
});
67+
68+
Cp.execSync(cmd, { env });
69+
}
70+
71+
72+
function createBaseBackup (service) {
73+
debug('creating base backup.');
74+
75+
if (service.isMaster) {
76+
throw new Error('attempting to create backup for master');
77+
}
78+
79+
const cmd = `pg_basebackup -h ${service.address} -D ${process.env.PGDATA}` +
80+
` -U ${process.env.POSTGRES_USER} -vP -w --xlog-method=stream`;
81+
const env = Object.assign({}, process.env, {
82+
PGPASSWORD: process.env.POSTGRES_PASSWORD
83+
});
84+
85+
Cp.execSync(cmd, { env });
86+
}
87+
88+
89+
function lookupService (callback) {
90+
const consul = new Consulite({ consul: consulHost });
91+
const ip = process.env.CONTAINERPILOT_POSTGRES_IP;
92+
93+
debug('looking up service in consul (%s)', consulHost);
94+
consul.getService('postgres', (err, service) => {
95+
if (err) {
96+
// If the service wasn't found, then this is the master.
97+
if (/postgres couldn't be found/.test(err.message)) {
98+
return callback(null, {
99+
isMaster: true,
100+
address: ip,
101+
port: 5432,
102+
executed: false
103+
});
104+
} else {
105+
return callback(err);
106+
}
107+
} else {
108+
service.isMaster = service.address === ip;
109+
callback(null, service);
110+
}
111+
});
112+
}

bin/package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "config-postgres",
3+
"version": "1.0.0",
4+
"private": "true",
5+
"dependencies": {
6+
"consulite": "2.0.0",
7+
"debug": "3.0.0"
8+
},
9+
"devDependencies": {
10+
"belly-button": "4.x.x"
11+
},
12+
"scripts": {
13+
"lint": "belly-button"
14+
}
15+
}

etc/containerpilot.json5

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
{
2+
consul: '{{ if .CONSUL_AGENT }}localhost{{ else }}{{ .CONSUL | default "consul" }}{{ end }}:8500',
3+
logging: {
4+
level: '{{ .LOG_LEVEL | default "INFO" }}'
5+
},
6+
jobs: [
7+
{
8+
name: 'preStart',
9+
exec: 'node /bin/config-postgres.js preStart'
10+
{{ if .CONSUL_AGENT }},
11+
when: {
12+
source: 'consul-agent',
13+
once: 'healthy'
14+
}{{ end }}
15+
},
16+
{
17+
name: 'postgres',
18+
port: 5432,
19+
exec: [
20+
'docker-entrypoint.sh',
21+
'postgres',
22+
'-c', 'config_file=/etc/postgresql.conf',
23+
'-c', 'hba_file=/etc/pg_hba.conf'
24+
],
25+
health: {
26+
exec: 'pgrep postgres',
27+
interval: 10,
28+
ttl: 25
29+
},
30+
when: {
31+
source: 'preStart',
32+
once: 'exitSuccess'
33+
},
34+
restarts: 'unlimited'
35+
},{{ if .CONSUL_AGENT }}
36+
{
37+
name: 'consul-agent',
38+
exec: ['/usr/local/bin/consul', 'agent',
39+
'-data-dir=/data',
40+
'-config-dir=/config',
41+
'-log-level=err',
42+
'-rejoin',
43+
'-retry-join', '{{ .CONSUL | default "consul" }}',
44+
'-retry-max', '10',
45+
'-retry-interval', '10s'],
46+
health: {
47+
exec: 'curl -so /dev/null http://localhost:8500',
48+
interval: 10,
49+
ttl: 25
50+
},
51+
restarts: 'unlimited'
52+
},
53+
{{ end }}
54+
{
55+
name: 'postgres-onchange',
56+
exec: 'node /bin/config-postgres.js onChange',
57+
when: {
58+
source: 'watch.postgres',
59+
once: 'changed'
60+
}
61+
}
62+
],
63+
watches: [
64+
{
65+
name: 'postgres',
66+
interval: 5
67+
}
68+
]
69+
}

etc/pg_hba.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
host replication all 0.0.0.0/0 md5
2+
host all all 0.0.0.0/0 md5

etc/postgresql.conf.ctmpl

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# - Connection Settings -
2+
listen_addresses = '*'
3+
port = 5432
4+
max_connections = 100
5+
6+
# - Memory -
7+
shared_buffers = 128MB # min 128kB
8+
dynamic_shared_memory_type = posix
9+
10+
# - What to Log -
11+
log_timezone = 'US/Eastern'
12+
13+
# - Locale and Formatting -
14+
datestyle = 'iso, mdy'
15+
timezone = 'US/Eastern'
16+
17+
# These settings are initialized by initdb, but they can be changed.
18+
lc_messages = 'en_US.UTF-8' # locale for system error message
19+
lc_monetary = 'en_US.UTF-8' # locale for monetary formatting
20+
lc_numeric = 'en_US.UTF-8' # locale for number formatting
21+
lc_time = 'en_US.UTF-8' # locale for time formatting
22+
23+
# default configuration for text search
24+
default_text_search_config = 'pg_catalog.english'
25+
26+
# Replication
27+
hot_standby = on
28+
29+
{{ $postgresMaster := env "POSTGRES_MASTER" }}
30+
{{ if $postgresMaster }}
31+
# Master Server Replication Settings
32+
wal_level = hot_standby
33+
max_wal_senders = 8
34+
wal_keep_segments = 100
35+
archive_mode = on
36+
archive_command = 'cd .'
37+
{{ end }}

etc/recovery.conf.ctmpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{ $masterIp := env "POSTGRES_MASTER_IP" }}
2+
{{ $user := env "POSTGRES_USER" }}
3+
{{ $password := env "POSTGRES_PASSWORD" }}
4+
5+
standby_mode = 'on'
6+
primary_conninfo = 'host={{$masterIp}} port=5432 user={{$user}} password={{$password}}'
7+
trigger_file = '/etc/postgresql.trigger.5432'
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
version: '2.1'
2+
# Postgres demonstration of the Autopilot pattern
3+
4+
services:
5+
db:
6+
build: ../../
7+
mem_limit: 1g
8+
restart: always
9+
dns:
10+
- 127.0.0.1
11+
ports:
12+
- 5432
13+
environment:
14+
- CONSUL=consul
15+
- CONSUL_AGENT=1
16+
- POSTGRES_USER=postgres
17+
- POSTGRES_PASSWORD=password
18+
- POSTGRES_DB=postgres
19+
- PGDATA=/var/lib/postgresql/data
20+
links:
21+
- consul:consul
22+
23+
# Start with a single host which will bootstrap the cluster.
24+
# In production we'll want to use an HA cluster.
25+
consul:
26+
image: autopilotpattern/consul:0.7.2-r0.8
27+
restart: always
28+
mem_limit: 128m
29+
ports:
30+
- 8500:8500
31+
dns:
32+
- 127.0.0.1
33+
command: >
34+
/usr/local/bin/containerpilot
35+
/bin/consul agent -server
36+
-config-dir=/etc/consul
37+
-log-level=err
38+
-bootstrap-expect 1
39+
-ui-dir /ui

0 commit comments

Comments
 (0)