diff --git a/Dockerfile b/Dockerfile index 897de850..7ebd03a1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,45 +1,153 @@ ## This Dockerfile is meant to aid in the building and debugging patroni whilst developing on your local machine ## It has all the necessary components to play/debug with a single node appliance, running etcd -FROM postgres:10 -MAINTAINER Alexander Kukushkin +ARG PG_MAJOR=10 +ARG COMPRESS=false +ARG PGHOME=/home/postgres +ARG PGDATA=$PGHOME/data +ARG LC_ALL=C.UTF-8 +ARG LANG=C.UTF-8 -RUN export DEBIAN_FRONTEND=noninteractive \ +FROM postgres:$PG_MAJOR as builder + +ARG PGHOME +ARG PGDATA +ARG LC_ALL +ARG LANG + +ENV ETCDVERSION=2.3.8 CONFDVERSION=0.16.0 + +RUN set -ex \ + && export DEBIAN_FRONTEND=noninteractive \ && echo 'APT::Install-Recommends "0";\nAPT::Install-Suggests "0";' > /etc/apt/apt.conf.d/01norecommend \ && apt-get update -y \ - && apt-get upgrade -y \ - # postgres:10 is based on debian, which has patroni package. We will install all required dependencies - && apt-get install -s patroni | sed -n -e '/^Inst patroni /d' -e 's/^Inst \([^ ]\+\) .*$/\1/p' \ - | xargs apt-get install -y curl jq haproxy locales python3-etcd python3-kazoo \ - - ## Make sure we have a en_US.UTF-8 locale available + # postgres:10 is based on debian, which has the patroni package. We will install all required dependencies + && apt-cache depends patroni | sed -n -e 's/.*Depends: \(python3-.\+\)$/\1/p' \ + | grep -Ev '^python3-(sphinx|etcd|consul|kazoo|kubernetes)' \ + | xargs apt-get install -y vim curl less jq locales haproxy sudo \ + python3-etcd python3-kazoo python3-pip busybox \ + && pip3 install dumb-init \ +\ + # Cleanup all locales but en_US.UTF-8 + && find /usr/share/i18n/charmaps/ -type f ! -name UTF-8.gz -delete \ + && find /usr/share/i18n/locales/ -type f ! -name en_US ! -name en_GB ! -name i18n ! -name iso14651_t1 ! -name iso14651_t1_common ! -name 'translit_*' -delete \ + && echo 'en_US.UTF-8 UTF-8' > /usr/share/i18n/SUPPORTED \ +\ + # Make sure we have a en_US.UTF-8 locale available && localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 \ - - && mkdir -p /home/postgres \ - && chown postgres:postgres /home/postgres \ - - # Clean up - && apt-get purge -y libpython2.7-stdlib libpython2.7-minimal \ +\ + # haproxy dummy config + && echo 'global\n stats socket /run/haproxy/admin.sock mode 660 level admin' > /etc/haproxy/haproxy.cfg \ +\ + # Prepare postgres/patroni/haproxy environment + && mkdir -p $PGHOME/.config/patroni /patroni /run/haproxy \ + && ln -s ../../patroni.yaml $PGHOME/.config/patroni/patronictl.yaml \ + && ln -s /patronictl.py /usr/local/bin/patronictl \ + && sed -i "s|/var/lib/postgresql.*|$PGHOME:/bin/bash|" /etc/passwd \ + && chown -R postgres:postgres /var/log \ +\ + # Download etcd + && curl -sL https://github.com/coreos/etcd/releases/download/v${ETCDVERSION}/etcd-v${ETCDVERSION}-linux-amd64.tar.gz \ + | tar xz -C /usr/local/bin --strip=1 --wildcards --no-anchored etcd etcdctl \ +\ + # Download confd + && curl -sL https://github.com/kelseyhightower/confd/releases/download/v${CONFDVERSION}/confd-${CONFDVERSION}-linux-amd64 \ + > /usr/local/bin/confd && chmod +x /usr/local/bin/confd \ +\ + # Clean up all useless packages and some files + && apt-get purge -y --allow-remove-essential python3-pip gzip bzip2 util-linux e2fsprogs \ + libmagic1 bsdmainutils login ncurses-bin libmagic-mgc e2fslibs bsdutils \ + exim4-config gnupg-agent dirmngr libpython2.7-stdlib libpython2.7-minimal \ && apt-get autoremove -y \ && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* /root/.cache + && rm -rf /var/lib/apt/lists/* \ + /root/.cache \ + /var/cache/debconf/* \ + /etc/rc?.d \ + /etc/systemd \ + /docker-entrypoint* \ + /sbin/pam* \ + /sbin/swap* \ + /sbin/unix* \ + /usr/local/bin/gosu \ + /usr/sbin/[acgipr]* \ + /usr/sbin/*user* \ + /usr/share/doc* \ + /usr/share/man \ + /usr/share/info \ + /usr/share/i18n/locales/translit_hangul \ + /usr/share/locale/?? \ + /usr/share/locale/??_?? \ + /usr/share/postgresql/*/man \ + /usr/share/postgresql-common/pg_wrapper \ + /usr/share/vim/vim80/doc \ + /usr/share/vim/vim80/lang \ + /usr/share/vim/vim80/tutor \ +# /var/lib/dpkg/info/* \ + && find /usr/bin -xtype l -delete \ + && find /var/log -type f -exec truncate --size 0 {} \; \ + && find /usr/lib/python3/dist-packages -name '*test*' | xargs rm -fr \ + && find /lib/x86_64-linux-gnu/security -type f ! -name pam_env.so ! -name pam_permit.so ! -name pam_unix.so -delete -ENV ETCDVERSION 3.2.23 -RUN curl -L https://github.com/coreos/etcd/releases/download/v${ETCDVERSION}/etcd-v${ETCDVERSION}-linux-amd64.tar.gz \ - | tar xz -C /usr/local/bin --strip=1 --wildcards --no-anchored etcd etcdctl +# perform compression if it is necessary +ARG COMPRESS +RUN if [ "$COMPRESS" = "true" ]; then \ + set -ex \ + # Allow certain sudo commands from postgres + && echo 'postgres ALL=(ALL) NOPASSWD: /bin/tar xpJf /a.tar.xz -C /, /bin/rm /a.tar.xz, /bin/ln -snf dash /bin/sh' >> /etc/sudoers \ + && ln -snf busybox /bin/sh \ + && files="/bin/sh /usr/bin/sudo /usr/lib/sudo/sudoers.so /lib/x86_64-linux-gnu/security/pam_*.so" \ + && libs="$(ldd $files | awk '{print $3;}' | grep '^/' | sort -u) /lib/x86_64-linux-gnu/ld-linux-x86-64.so.* /lib/x86_64-linux-gnu/libnsl.so.* /lib/x86_64-linux-gnu/libnss_compat.so.*" \ + && (echo /var/run $files $libs | tr ' ' '\n' && realpath $files $libs) | sort -u | sed 's/^\///' > /exclude \ + && find /etc/alternatives -xtype l -delete \ + && save_dirs="usr lib var bin sbin etc/ssl etc/init.d etc/alternatives etc/apt" \ + && XZ_OPT=-e9v tar -X /exclude -cpJf a.tar.xz $save_dirs \ + # we call "cat /exclude" to avoid including files from the $save_dirs that are also among + # the exceptions listed in the /exclude, as "uniq -u" eliminates all non-unique lines. + # By calling "cat /exclude" a second time we guarantee that there will be at least two lines + # for each exception and therefore they will be excluded from the output passed to 'rm'. + && /bin/busybox sh -c "(find $save_dirs -not -type d && cat /exclude /exclude && echo exclude) | sort | uniq -u | xargs /bin/busybox rm" \ + && /bin/busybox --install -s \ + && /bin/busybox sh -c "find $save_dirs -type d -depth -exec rmdir -p {} \; 2> /dev/null"; \ + fi -ENV CONFDVERSION 0.16.0 -RUN curl -L https://github.com/kelseyhightower/confd/releases/download/v${CONFDVERSION}/confd-${CONFDVERSION}-linux-amd64 > /usr/local/bin/confd \ - && chmod +x /usr/local/bin/confd +FROM scratch +COPY --from=builder / / -ADD patronictl.py patroni.py docker/entrypoint.sh / -ADD patroni /patroni/ -ADD extras/confd /etc/confd +LABEL maintainer="Alexander Kukushkin " -RUN sed -i 's/env python/&3/' patroni*.py && ln -s /patronictl.py /usr/local/bin/patronictl && mkdir /data/ /run/haproxy \ - && touch /pgpass /patroni.yml && chown postgres:postgres -R /patroni/ /data/ /pgpass /patroni.yml /etc/haproxy /var/run/ /var/lib/ /var/log/ +ARG PG_MAJOR +ARG COMPRESS +ARG PGHOME +ARG PGDATA +ARG LC_ALL +ARG LANG -EXPOSE 2379 5432 8008 +ARG PGBIN=/usr/lib/postgresql/$PG_MAJOR/bin + +ENV LC_ALL=$LC_ALL LANG=$LANG EDITOR=/usr/bin/editor +ENV PGDATA=$PGDATA PATH=$PATH:$PGBIN + +COPY patroni /patroni/ +COPY extras/confd/conf.d/haproxy.toml /etc/confd/conf.d/ +COPY extras/confd/templates/haproxy.tmpl /etc/confd/templates/ +COPY patroni*.py docker/entrypoint.sh / +COPY postgres?.yml $PGHOME/ + +WORKDIR $PGHOME + +RUN sed -i 's/env python/&3/' /patroni*.py \ + # "fix" patroni configs + && sed -i 's/^\( connect_address:\| - host\)/#&/' postgres?.yml \ + && sed -i 's/^ listen: 127.0.0.1/ listen: 0.0.0.0/' postgres?.yml \ + && sed -i "s|^\( data_dir: \).*|\1$PGDATA|" postgres?.yml \ + && sed -i "s|^#\( bin_dir: \).*|\1$PGBIN|" postgres?.yml \ + && sed -i 's/^ - encoding: UTF8/ - locale: en_US.UTF-8\n&/' postgres?.yml \ + && sed -i 's/^\(scope\|name\|etcd\| host\| authentication\| pg_hba\| parameters\):/#&/' postgres?.yml \ + && sed -i 's/^ \(replication\|superuser\|unix_socket_directories\|\(\( \)\{0,1\}\(username\|password\)\)\):/#&/' postgres?.yml \ + && sed -i 's/^ parameters:/ pg_hba:\n - local all all trust\n - host replication all all md5\n - host all all all md5\n&\n max_connections: 100/' postgres?.yml \ + && if [ "$COMPRESS" = "true" ]; then chmod u+s /usr/bin/sudo; fi \ + && chown -R postgres:postgres $PGHOME /run /etc/haproxy -ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 -ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] USER postgres + +ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index bff610de..19a26ea2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,58 +1,68 @@ # docker compose file for running a 3-node PostgreSQL cluster -# with etcd as the SIS +# with 3-node etcd cluster as the DCS and one haproxy node +version: "2" -patroni_etcd: - container_name: patroni_etcd - image: patroni - command: --etcd +networks: + demo: -dbnode1: - image: patroni - hostname: dbnode1 - links: - - patroni_etcd:patroni_etcd - volumes: - - ./patroni:/patroni - env_file: docker/patroni-secrets.env - environment: - PATRONI_ETCD_URL: http://patroni_etcd:2379 - PATRONI_NAME: dbnode1 - PATRONI_SCOPE: testcluster +services: + etcd1: + image: patroni + networks: [ demo ] + env_file: docker/etcd.env + container_name: demo-etcd1 + hostname: etcd1 + command: etcd -name etcd1 -initial-advertise-peer-urls http://etcd1:2380 -dbnode2: - image: patroni - hostname: dbnode2 - links: - - patroni_etcd:patroni_etcd - volumes: - - ./patroni:/patroni - env_file: docker/patroni-secrets.env - environment: - PATRONI_ETCD_URL: http://patroni_etcd:2379 - PATRONI_NAME: dbnode2 - PATRONI_SCOPE: testcluster + etcd2: + image: patroni + networks: [ demo ] + env_file: docker/etcd.env + container_name: demo-etcd2 + hostname: etcd2 + command: etcd -name etcd2 -initial-advertise-peer-urls http://etcd2:2380 -dbnode3: - image: patroni - hostname: dbnode3 - links: - - patroni_etcd:patroni_etcd - volumes: - - ./patroni:/patroni - env_file: docker/patroni-secrets.env - environment: - PATRONI_ETCD_URL: http://patroni_etcd:2379 - PATRONI_NAME: dbnode3 - PATRONI_SCOPE: testcluster + etcd3: + image: patroni + networks: [ demo ] + env_file: docker/etcd.env + container_name: demo-etcd3 + hostname: etcd3 + command: etcd -name etcd3 -initial-advertise-peer-urls http://etcd3:2380 -haproxy: - image: patroni - links: - - patroni_etcd:patroni_etcd - ports: - - "5000:5000" - - "5001:5001" - environment: - PATRONI_ETCD_URL: http://patroni_etcd:2379 - PATRONI_SCOPE: testcluster - command: --confd + patroni1: + image: patroni + networks: [ demo ] + env_file: docker/patroni.env + hostname: patroni1 + container_name: demo-patroni1 + environment: + PATRONI_NAME: patroni1 + + patroni2: + image: patroni + networks: [ demo ] + env_file: docker/patroni.env + hostname: patroni2 + container_name: demo-patroni2 + environment: + PATRONI_NAME: patroni2 + + patroni3: + image: patroni + networks: [ demo ] + env_file: docker/patroni.env + hostname: patroni3 + container_name: demo-patroni3 + environment: + PATRONI_NAME: patroni3 + + haproxy: + image: patroni + networks: [ demo ] + env_file: docker/patroni.env + container_name: demo-haproxy + ports: + - "5000:5000" + - "5001:5001" + command: haproxy diff --git a/docker/README.md b/docker/README.md index afadc4b6..8e360d1a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,47 +1,117 @@ # Patroni Dockerfile -You can run Patroni in a docker container using this Dockerfile, or by using one of the Docker image at - - https://registry.opensource.zalan.do/v1/repositories/acid/patroni/tags +You can run Patroni in a docker container using this Dockerfile This Dockerfile is meant in aiding development of Patroni and quick testing of features. It is not a production-worthy Dockerfile + docker build -t patroni . + # Examples ## Standalone Patroni - docker run -d registry.opensource.zalan.do/acid/patroni:1.0-SNAPSHOT + docker run -d patroni -## Multiple Patroni's communicating with a standalone etcd inside Docker - -Basically what you would do would be: - -* Run 1 container which provides etcd - - docker run -d --etcd-only - -* Run n containers running Patroni, passing the `--etcd` option to the `docker run` command - - docker run -d --etcd= - -To automate this you can run the following script: - - dev_patroni_cluster.sh [OPTIONS] - - Options: - - --image IMAGE The Docker image to use for the cluster - --members INT The number of members for the cluster - --name NAME The name of the new cluster +## Three-node Patroni cluster with three-node etcd cluster and one haproxy container using docker-compose Example session: - $ ./dev_patroni_cluster.sh --image registry.opensource.zalan.do/acid/patroni:1.0-SNAPSHOT --members=2 --name=bravo - The etcd container is 6be871a11cb373406ca5ea1c6b39e1.0-SNAPSHOTfdde9fb1d6177212d6ad0c0d1bd9b563, ip=172.17.1.24 - Started Patroni container 67e611f2eca7c40f9e6e0e24a4a8f2cba7e3e56d22a420e15ab9240a37a9d7a4, ip=172.17.1.25 - Started Patroni container 47dd12ae635ab83b039f5889e250048b606ed5e48e3650b69e365e7e1d4acbcf, ip=172.17.1.26 + $ docker-compose up -d + Creating demo-haproxy ... + Creating demo-patroni2 ... + Creating demo-patroni1 ... + Creating demo-patroni3 ... + Creating demo-etcd2 ... + Creating demo-etcd1 ... + Creating demo-etcd3 ... + Creating demo-haproxy + Creating demo-patroni2 + Creating demo-patroni1 + Creating demo-patroni3 + Creating demo-etcd1 + Creating demo-etcd2 + Creating demo-etcd2 ... done + $ docker ps - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 47dd12ae635a registry.opensource.zalan.do/acid/patroni:1.0-SNAPSHOT "/bin/bash /entrypoi 10 seconds ago Up 8 seconds 4001/tcp, 5432/tcp, 2380/tcp bravo_OR64g8bx - 67e611f2eca7 registry.opensource.zalan.do/acid/patroni:1.0-SNAPSHOT "/bin/bash /entrypoi 11 seconds ago Up 10 seconds 2380/tcp, 4001/tcp, 5432/tcp bravo_si9no8iz - 6be871a11cb3 registry.opensource.zalan.do/acid/patroni:1.0-SNAPSHOT "/bin/bash /entrypoi 12 seconds ago Up 10 seconds 4001/tcp, 5432/tcp, 2380/tcp bravo_etcd + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 5b7a90b4cfbf patroni "/bin/sh /entrypoint…" 29 seconds ago Up 27 seconds demo-etcd2 + e30eea5222f2 patroni "/bin/sh /entrypoint…" 29 seconds ago Up 27 seconds demo-etcd1 + 83bcf3cb208f patroni "/bin/sh /entrypoint…" 29 seconds ago Up 27 seconds demo-etcd3 + 922532c56e7d patroni "/bin/sh /entrypoint…" 29 seconds ago Up 28 seconds demo-patroni3 + 14f875e445f3 patroni "/bin/sh /entrypoint…" 29 seconds ago Up 28 seconds demo-patroni2 + 110d1073b383 patroni "/bin/sh /entrypoint…" 29 seconds ago Up 28 seconds demo-patroni1 + 5af5e6e36028 patroni "/bin/sh /entrypoint…" 29 seconds ago Up 28 seconds 0.0.0.0:5000-5001->5000-5001/tcp demo-haproxy + + $ docker logs demo-patroni1 + 2019-02-20 08:19:32,714 INFO: Failed to import patroni.dcs.consul + 2019-02-20 08:19:32,737 INFO: Selected new etcd server http://etcd3:2379 + 2019-02-20 08:19:35,140 INFO: Lock owner: None; I am patroni1 + 2019-02-20 08:19:35,174 INFO: trying to bootstrap a new cluster + ... + 2019-02-20 08:19:39,310 INFO: postmaster pid=37 + 2019-02-20 08:19:39.314 UTC [37] LOG: listening on IPv4 address "0.0.0.0", port 5432 + 2019-02-20 08:19:39.321 UTC [37] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432" + 2019-02-20 08:19:39.353 UTC [39] LOG: database system was shut down at 2019-02-20 08:19:36 UTC + 2019-02-20 08:19:39.354 UTC [40] FATAL: the database system is starting up + localhost:5432 - rejecting connections + 2019-02-20 08:19:39.369 UTC [37] LOG: database system is ready to accept connections + localhost:5432 - accepting connections + 2019-02-20 08:19:39,383 INFO: establishing a new patroni connection to the postgres cluster + 2019-02-20 08:19:39,408 INFO: running post_bootstrap + 2019-02-20 08:19:39,432 WARNING: Could not activate Linux watchdog device: "Can't open watchdog device: [Errno 2] No such file or directory: '/dev/watchdog'" + 2019-02-20 08:19:39,515 INFO: initialized a new cluster + 2019-02-20 08:19:49,424 INFO: Lock owner: patroni1; I am patroni1 + 2019-02-20 08:19:49,447 INFO: Lock owner: patroni1; I am patroni1 + 2019-02-20 08:19:49,480 INFO: no action. i am the leader with the lock + 2019-02-20 08:19:59,422 INFO: Lock owner: patroni1; I am patroni1 + + $ docker exec -ti demo-patroni1 bash + postgres@patroni1:~$ patronictl list + +-------------+----------+------------+--------+---------+----+-----------+ + | Cluster | Member | Host | Role | State | TL | Lag in MB | + +-------------+----------+------------+--------+---------+----+-----------+ + | testcluster | patroni1 | 172.21.0.3 | Leader | running | 1 | 0 | + | testcluster | patroni2 | 172.21.0.4 | | running | 1 | 0 | + | testcluster | patroni3 | 172.21.0.5 | | running | 1 | 0 | + +-------------+----------+------------+--------+---------+----+-----------+ + + postgres@patroni1:~$ etcdctl ls --recursive --sort -p /service/testcluster + /service/testcluster/config + /service/testcluster/initialize + /service/testcluster/leader + /service/testcluster/members/ + /service/testcluster/members/patroni1 + /service/testcluster/members/patroni2 + /service/testcluster/members/patroni3 + /service/testcluster/optime/ + /service/testcluster/optime/leader + + postgres@patroni1:~$ etcdctl member list + 1bab629f01fa9065: name=etcd3 peerURLs=http://etcd3:2380 clientURLs=http://etcd3:2379 isLeader=false + 8ecb6af518d241cc: name=etcd2 peerURLs=http://etcd2:2380 clientURLs=http://etcd2:2379 isLeader=true + b2e169fcb8a34028: name=etcd1 peerURLs=http://etcd1:2380 clientURLs=http://etcd1:2379 isLeader=false + postgres@patroni1:~$ exit + + $ psql -h localhost -p 5000 -U postgres -W + Password: postgres + psql (11.2 (Ubuntu 11.2-1.pgdg18.04+1), server 10.7 (Debian 10.7-1.pgdg90+1)) + Type "help" for help. + + localhost/postgres=# select pg_is_in_recovery(); + pg_is_in_recovery + ─────────────────── + f + (1 row) + + localhost/postgres=# \q + + $ psql -h localhost -p 5001 -U postgres -W + Password: postgres + psql (11.2 (Ubuntu 11.2-1.pgdg18.04+1), server 10.7 (Debian 10.7-1.pgdg90+1)) + Type "help" for help. + + localhost/postgres=# select pg_is_in_recovery(); + pg_is_in_recovery + ─────────────────── + t + (1 row) diff --git a/docker/dev_patroni_cluster.sh b/docker/dev_patroni_cluster.sh deleted file mode 100755 index 2e21faac..00000000 --- a/docker/dev_patroni_cluster.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash - -DOCKER_IMAGE="registry.opensource.zalan.do/acid/patroni:1.0-SNAPSHOT" -MEMBERS=3 - - -function usage() -{ - cat <<__EOF__ -Usage: $0 - -Options: - - --image IMAGE The Docker image to use for the cluster - --members INT The number of members for the cluster - --name NAME The name of the new cluster - -Examples: - - $0 --image ${DOCKER_IMAGE} - $0 - $0 --image ${DOCKER_IMAGE} --members=2 -__EOF__ -} - - -optspec=":-:" -while getopts "$optspec" optchar; do - case "${optchar}" in - -) - case "${OPTARG}" in - help) - usage - exit 0 - ;; - name) - PATRONI_SCOPE="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) - ;; - name=*) - PATRONI_SCOPE="${OPTARG#*=}" - ;; - image) - DOCKER_IMAGE="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) - ;; - image=*) - DOCKER_IMAGE="${OPTARG#*=}" - ;; - members) - MEMBERS="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) - ;; - members=*) - MEMBERS="${OPTARG#*=}" - ;; - *) - if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then - echo "Unknown option --${OPTARG}" >&2 - fi - ;; - esac;; - *) - if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then - echo "Non-option argument: '-${OPTARG}'" >&2 - usage - exit 1 - fi - ;; - esac -done - -if [ -z ${PATRONI_SCOPE} ]; then - PATRONI_SCOPE=$(cat /dev/urandom | LC_ALL=C tr -dc 'a-z0-9' | head -c 8) -fi - -function docker_run() -{ - local name=$1 - shift - container=$(docker run -d --name=$name $*) - container_ip=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${container}) - echo "Started container ${name}, ip=${container_ip}" -} - - -ETCD_CONTAINER="${PATRONI_SCOPE}_etcd" -docker_run ${ETCD_CONTAINER} ${DOCKER_IMAGE} --etcd - -DOCKER_ARGS="--link=${ETCD_CONTAINER}:${ETCD_CONTAINER} -e PATRONI_SCOPE=${PATRONI_SCOPE} -e PATRONI_ETCD_URL=http://${ETCD_CONTAINER}:2379" -PATRONI_ENV=$(sed 's/#.*//g' docker/patroni-secrets.env | sed -n 's/^PATRONI_.*$/-e &/p' | tr '\n' ' ') -PATRONI_VOLUME="-v $(dirname $(dirname $(realpath $0)))/patroni:/patroni" - -for i in $(seq 1 "${MEMBERS}"); do - container_name=postgres${i} - docker_run "${PATRONI_SCOPE}_${container_name}" \ - $PATRONI_VOLUME \ - $DOCKER_ARGS \ - $PATRONI_ENV \ - -e PATRONI_NAME=${container_name} \ - ${DOCKER_IMAGE} -done - -docker_run "${PATRONI_SCOPE}_haproxy" \ - -p=5000 -p=5001 \ - $DOCKER_ARGS \ - ${DOCKER_IMAGE} --confd diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 1daa9c8b..898bb4b7 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,115 +1,60 @@ -#!/bin/bash +#!/bin/sh -function usage() -{ - cat <<__EOF__ -Usage: $0 +if [ -f /a.tar.xz ]; then + echo "decompressing image..." + sudo tar xpJf /a.tar.xz -C / > /dev/null 2>&1 + sudo rm /a.tar.xz + sudo ln -snf dash /bin/sh +fi -Options: +readonly PATRONI_SCOPE=${PATRONI_SCOPE:-batman} +PATRONI_NAMESPACE=${PATRONI_NAMESPACE:-/service} +readonly PATRONI_NAMESPACE=${PATRONI_NAMESPACE%/} +readonly DOCKER_IP=$(hostname --ip-address) - --etcd Do not run Patroni, run a standalone etcd - --confd Do not run Patroni, run a standalone confd - --zookeeper Do not run Patroni, run a standalone zookeeper - -Examples: - - $0 --etcd - $0 --confd - $0 --zookeeper - $0 -__EOF__ -} - -DOCKER_IP=$(hostname --ip-address) -PATRONI_SCOPE=${PATRONI_SCOPE:-batman} -ETCD_ARGS="--data-dir /tmp/etcd.data -advertise-client-urls=http://${DOCKER_IP}:2379 -listen-client-urls=http://0.0.0.0:2379" - -optspec=":vh-:" -while getopts "$optspec" optchar; do - case "${optchar}" in - -) - case "${OPTARG}" in - confd) - haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -D - CONFD="confd -prefix=${PATRONI_NAMESPACE:-/service}/$PATRONI_SCOPE -interval=10 -backend" - if [ ! -z ${PATRONI_ZOOKEEPER_HOSTS} ]; then - while ! /usr/share/zookeeper/bin/zkCli.sh -server ${PATRONI_ZOOKEEPER_HOSTS} ls /; do - sleep 1 - done - exec $CONFD zookeeper -node ${PATRONI_ZOOKEEPER_HOSTS} - else - while ! curl -s ${PATRONI_ETCD_URL}/v2/members | jq -r '.members[0].clientURLs[0]' | grep -q http; do - sleep 1 - done - exec $CONFD etcd -node $PATRONI_ETCD_URL - fi - ;; - etcd) - exec etcd $ETCD_ARGS - ;; - zookeeper) - exec /usr/share/zookeeper/bin/zkServer.sh start-foreground - ;; - cheat) - CHEAT=1 - ;; - help) - usage - exit 0 - ;; - *) - if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then - echo "Unknown option --${OPTARG}" >&2 - fi - ;; - esac;; - *) - if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then - echo "Non-option argument: '-${OPTARG}'" >&2 - usage - exit 1 - fi - ;; - esac -done +case "$1" in + haproxy) + haproxy -f /etc/haproxy/haproxy.cfg -p /var/run/haproxy.pid -D + CONFD="confd -prefix=$PATRONI_NAMESPACE/$PATRONI_SCOPE -interval=10 -backend" + if [ ! -z "$PATRONI_ZOOKEEPER_HOSTS" ]; then + while ! /usr/share/zookeeper/bin/zkCli.sh -server $PATRONI_ZOOKEEPER_HOSTS ls /; do + sleep 1 + done + exec dumb-init $CONFD zookeeper -node $PATRONI_ZOOKEEPER_HOSTS + else + while ! etcdctl cluster-health 2> /dev/null; do + sleep 1 + done + exec dumb-init $CONFD etcd -node $(echo $ETCDCTL_ENDPOINTS | sed 's/,/ -node /g') + fi + ;; + etcd) + exec "$@" -advertise-client-urls http://$DOCKER_IP:2379 + ;; + zookeeper) + exec /usr/share/zookeeper/bin/zkServer.sh start-foreground + ;; +esac ## We start an etcd -if [[ -z ${PATRONI_ETCD_URL} && -z ${PATRONI_ZOOKEEPER_HOSTS} ]]; then - etcd $ETCD_ARGS > /var/log/etcd.log 2> /var/log/etcd.err & +if [ -z "$PATRONI_ETCD_HOSTS" ] && [ -z "$PATRONI_ZOOKEEPER_HOSTS" ]; then export PATRONI_ETCD_URL="http://127.0.0.1:2379" + etcd --data-dir /tmp/etcd.data -advertise-client-urls=$PATRONI_ETCD_URL -listen-client-urls=http://0.0.0.0:2379 > /var/log/etcd.log 2> /var/log/etcd.err & fi export PATRONI_SCOPE -export PATRONI_NAME="${PATRONI_NAME:-${HOSTNAME}}" -export PATRONI_RESTAPI_CONNECT_ADDRESS="${DOCKER_IP}:8008" +export PATRONI_NAMESPACE +export PATRONI_NAME="${PATRONI_NAME:-$(hostname)}" +export PATRONI_RESTAPI_CONNECT_ADDRESS="$DOCKER_IP:8008" export PATRONI_RESTAPI_LISTEN="0.0.0.0:8008" -export PATRONI_admin_PASSWORD="${PATRONI_admin_PASSWORD:=admin}" +export PATRONI_admin_PASSWORD="${PATRONI_admin_PASSWORD:-admin}" export PATRONI_admin_OPTIONS="${PATRONI_admin_OPTIONS:-createdb, createrole}" -export PATRONI_POSTGRESQL_CONNECT_ADDRESS="${DOCKER_IP}:5432" +export PATRONI_POSTGRESQL_CONNECT_ADDRESS="$DOCKER_IP:5432" export PATRONI_POSTGRESQL_LISTEN="0.0.0.0:5432" -export PATRONI_POSTGRESQL_DATA_DIR="data/${PATRONI_SCOPE}" +export PATRONI_POSTGRESQL_DATA_DIR="${PATRONI_POSTGRESQL_DATA_DIR:-$PGDATA}" export PATRONI_REPLICATION_USERNAME="${PATRONI_REPLICATION_USERNAME:-replicator}" -export PATRONI_REPLICATION_PASSWORD="${PATRONI_REPLICATION_PASSWORD:-abcd}" +export PATRONI_REPLICATION_PASSWORD="${PATRONI_REPLICATION_PASSWORD:-replicate}" export PATRONI_SUPERUSER_USERNAME="${PATRONI_SUPERUSER_USERNAME:-postgres}" export PATRONI_SUPERUSER_PASSWORD="${PATRONI_SUPERUSER_PASSWORD:-postgres}" -export PATRONI_POSTGRESQL_PGPASS="$HOME/.pgpass" -cat > /patroni.yml <<__EOF__ -bootstrap: - dcs: - postgresql: - use_pg_rewind: true - - pg_hba: - - host all all 0.0.0.0/0 md5 - - host replication replicator ${DOCKER_IP}/16 md5 -__EOF__ - -mkdir -p "$HOME/.config/patroni" -[ -h "$HOME/.config/patroni/patronictl.yaml" ] || ln -s /patroni.yml "$HOME/.config/patroni/patronictl.yaml" - -[ -z $CHEAT ] && exec python3 /patroni.py /patroni.yml - -while true; do - sleep 60 -done +exec python3 /patroni.py postgres0.yml diff --git a/docker/etcd.env b/docker/etcd.env new file mode 100644 index 00000000..6a1af075 --- /dev/null +++ b/docker/etcd.env @@ -0,0 +1,5 @@ +ETCD_LISTEN_PEER_URLS=http://0.0.0.0:2380 +ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 +ETCD_INITIAL_CLUSTER=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380 +ETCD_INITIAL_CLUSTER_STATE=new +ETCD_INITIAL_CLUSTER_TOKEN=tutorial diff --git a/docker/patroni-secrets.env b/docker/patroni.env similarity index 62% rename from docker/patroni-secrets.env rename to docker/patroni.env index 7c0f840e..04cc06df 100644 --- a/docker/patroni-secrets.env +++ b/docker/patroni.env @@ -1,3 +1,6 @@ +PATRONI_SCOPE=testcluster +PATRONI_ETCD_HOSTS='etcd1:2379','etcd2:2379','etcd3:2379' + PATRONI_RESTAPI_USERNAME=admin PATRONI_RESTAPI_PASSWORD=admin PATRONI_SUPERUSER_USERNAME=postgres @@ -6,3 +9,6 @@ PATRONI_REPLICATION_USERNAME=replicator PATRONI_REPLICATION_PASSWORD=replicate PATRONI_admin_PASSWORD=admin PATRONI_admin_OPTIONS=createdb,createrole + +# for etcdctl +ETCDCTL_ENDPOINTS=http://etcd1:2379,http://etcd2:2379,http://etcd3:2379