A Brief Analysis of Patroni in Docker Containers

A Brief Analysis of Patroni in Docker Containers

The previous article introduced the Repmgr construction process and implemented automatic switching. Today, we will introduce how to build a Patroni cluster environment under a container. As an out-of-the-box PG high-availability tool, Patroni is increasingly being used by various vendors in cloud environments.

The basic architecture of patroni is shown in the figure:

insert image description here

etcd serves as the distributed registration center and performs cluster master election; vip-manager sets the drift IP for the master node; patroni is responsible for guiding the creation, operation and management of the cluster, and can use patronictl for terminal access.

Specific process:
1. First, start the etcd cluster. In this example, the number of etcd is 3.
2. After checking that the etcd cluster is healthy, start patroni and compete to elect the leader, and other follower nodes perform data synchronization.
3. Start vip-manager, and determine whether the current node is the master node IP by accessing the specific value in the /SERVICENAME/{SERVICE_NAME}/SERVICEN​AME/{CLUSTER_NAME}/leader key in the etcd cluster. If so, set vip for the node to provide external read and write services.
Note: It is recommended to deploy etcd on a separate container in a real environment to provide external services.

Create an image

File Structure

Among them, Dockerfile is the main file of the image, and the docker service creates the image in the local warehouse through this file; entrypoint.sh is the container entry file, which is responsible for processing business logic; function is the entry file for executing business methods, which is responsible for starting etcd, monitoring the status of etcd cluster, starting patroni and vip-manager; generatefile generates the corresponding configuration file for the entire container, including etcd, patroni and vip-mananger.

The directory structure is roughly as shown in the figure:

insert image description here

Note: Please build the database installation package and patroni installation package by yourself.

DockerFile

FROM centos:7

MAINTAINER wangzhibin <wangzhibin>

ENV USER="postgresql" \
    PASSWORD=123456 \
    GROUP=postgresql 
	
RUN useradd ${USER} \
       && chown -R ${USER}:${GROUP} /home/${USER} \
       && yum -y update && yum install -y iptables sudo net-tools iproute openssh-server openssh-clients which vim sudo crontabs
#Install etcd
COPY etcd/etcd /usr/sbin
COPY etcd/etcdctl /usr/sbin

#Install database
COPY lib/ /home/${USER}/lib
COPY include/ /home/${USER}/include
COPY share/ /home/${USER}/share
COPY bin/ /home/${USER}/bin/
COPY patroni/ /home/${USER}/patroni

#Install vip-manager
COPY vip-manager/vip-manager /usr/sbin
#Install execution script COPY runtime/ /home/${USER}/runtime
COPY entrypoint.sh /sbin/entrypoint.sh

#Set the environment variable ENV LD_LIBRARY_PATH /home/${USER}/lib
ENV PATH /home/${USER}/bin:$PATH
ENV ETCDCTL_API=3

#Install Patroni
RUN yum -y install epel-release python-devel && yum -y install python-pip \
    && pip install /home/${USER}/patroni/1/pip-20.3.3.tar.gz \
    && pip install /home/${USER}/patroni/1/psycopg2-2.8.6-cp27-cp27mu-linux_x86_64.whl \
    && pip install --no-index --find-links=/home/${USER}/patroni/2/ -r /home/${USER}/patroni/2/requirements.txt \
    && pip install /home/${USER}/patroni/3/patroni-2.0.1-py2-none-any.whl

#Modify execution permissions RUN chmod 755 /sbin/entrypoint.sh \ 
&& mkdir /home/${USER}/etcddata \
&& chown -R ${USER}:${GROUP} /home/${USER} \
&& echo 'root:root123456' | chpasswd \
&& chmod 755 /sbin/etcd \
&& chmod 755 /sbin/etcdctl \
&& chmod 755 /sbin/vip-manager

#Set up Sudo
RUN chmod 777 /etc/sudoers \
       && sed -i '/## Allow root to run any commands anywhere/a '${USER}' ALL=(ALL) NOPASSWD:ALL' /etc/sudoers \
       && chmod 440 /etc/sudoers

#Switch user USER ${USER}

#Switch working directory WORKDIR /home/${USER}

#Start the entry program CMD ["/bin/bash", "/sbin/entrypoint.sh"]

entrypoint.sh

#!/bin/bash
set -e

# shellcheck source=runtime/functions
source "/home/${USER}/runtime/function"

configure_patroni

Function

#!/bin/bash

set -e
source /home/${USER}/runtime/env-defaults
source /home/${USER}/runtime/generatefile

PG_DATADIR=/home/${USER}/pgdata
PG_BINDIR=/home/${USER}/bin

configure_patroni()
{
    #Generate configuration file generate_etcd_conf
    generate_patroni_conf
    generate_vip_conf
    #Start etcd
    etcdcount=${ETCD_COUNT}
    count=0
    ip_temp=""
    array=(${HOSTLIST//,/ })
    for host in ${array[@]}
    do
        ip_temp+="http://${host}:2380,"
    done
    etcd --config-file=/home/${USER}/etcd.yml >/home/${USER}/etcddata/etcd.log 2>&1 &
    while [ $count -lt $etcdcount ]
    do
      line=(`etcdctl --endpoints=${ip_temp%?} endpoint health -w json`)
      count=`echo $line | awk -F"\"health\":true" '{print NF-1}'`
      echo "waiting etcd cluster"
      sleep 5
    done
    #Start patroni
    patroni /home/${USER}/postgresql.yml > /home/${USER}/patroni/patroni.log 2>&1 &
    #Start vip-manager
    sudo vip-manager --config /home/${USER}/vip.yml
}

generatefile

#!/bin/bash
set -e

HOSTNAME="`hostname`"
hostip=`ping ${HOSTNAME} -c 1 -w 1 | sed '1{s/[^(]*(//;s/).*//;q}'`

#generate etcd
generate_etcd_conf()
{
    echo "name : ${HOSTNAME}" >> /home/${USER}/etcd.yml
    echo "data-dir: /home/${USER}/etcddata" >> /home/${USER}/etcd.yml
    echo "listen-client-urls: http://0.0.0.0:2379" >> /home/${USER}/etcd.yml
    echo "advertise-client-urls: http://${hostip}:2379" >> /home/${USER}/etcd.yml
    echo "listen-peer-urls: http://0.0.0.0:2380" >> /home/${USER}/etcd.yml
    echo "initial-advertise-peer-urls: http://${hostip}:2380" >> /home/${USER}/etcd.yml
    ip_temp="initial-cluster: "
    array=(${HOSTLIST//,/ })  
    for host in ${array[@]}
    do
    	ip_temp+="${host}=http://${host}:2380," 
    done
    echo ${ip_temp%?} >> /home/${USER}/etcd.yml
    echo "initial-cluster-token: etcd-cluster-token" >> /home/${USER}/etcd.yml
    echo "initial-cluster-state: new" >> /home/${USER}/etcd.yml
}

#generatepatroni
generate_patroni_conf()
{
  echo "scope: ${CLUSTER_NAME}" >> /home/${USER}/postgresql.yml
  echo "namespace: /${SERVICE_NAME}/ " >> /home/${USER}/postgresql.yml
  echo "name: ${HOSTNAME} " >> /home/${USER}/postgresql.yml
  echo "restapi: " >> /home/${USER}/postgresql.yml 
  echo " listen: ${hostip}:8008 " >> /home/${USER}/postgresql.yml 
  echo " connect_address: ${hostip}:8008 " >> /home/${USER}/postgresql.yml
  echo "etcd: " >> /home/${USER}/postgresql.yml
  echo " host: ${hostip}:2379 " >> /home/${USER}/postgresql.yml
  echo " username: ${ETCD_USER} " >> /home/${USER}/postgresql.yml
  echo " password: ${ETCD_PASSWD} " >> /home/${USER}/postgresql.yml
  echo "bootstrap: " >> /home/${USER}/postgresql.yml
  echo " dcs: " >> /home/${USER}/postgresql.yml
  echo " ttl: 30 " >> /home/${USER}/postgresql.yml
  echo " loop_wait: 10 " >> /home/${USER}/postgresql.yml
  echo " retry_timeout: 10 " >> /home/${USER}/postgresql.yml
  echo " maximum_lag_on_failover: 1048576 " >> /home/${USER}/postgresql.yml
  echo " postgresql: " >> /home/${USER}/postgresql.yml
  echo " use_pg_rewind: true " >> /home/${USER}/postgresql.yml
  echo " use_slots: true " >> /home/${USER}/postgresql.yml
  echo " parameters: " >> /home/${USER}/postgresql.yml
  echo " initdb: " >> /home/${USER}/postgresql.yml
  echo " - encoding: UTF8 " >> /home/${USER}/postgresql.yml
  echo " - data-checksums " >> /home/${USER}/postgresql.yml
  echo " pg_hba: " >> /home/${USER}/postgresql.yml
  echo " - host replication ${USER} 0.0.0.0/0 md5 " >> /home/${USER}/postgresql.yml
  echo " - host all all 0.0.0.0/0 md5 " >> /home/${USER}/postgresql.yml
  echo "postgresql: " >> /home/${USER}/postgresql.yml
  echo " listen: 0.0.0.0:5432 " >> /home/${USER}/postgresql.yml
  echo " connect_address: ${hostip}:5432 " >> /home/${USER}/postgresql.yml
  echo " data_dir: ${PG_DATADIR} " >> /home/${USER}/postgresql.yml
  echo " bin_dir: ${PG_BINDIR} " >> /home/${USER}/postgresql.yml
  echo " pgpass: /tmp/pgpass " >> /home/${USER}/postgresql.yml
  echo " authentication: " >> /home/${USER}/postgresql.yml
  echo " replication: " >> /home/${USER}/postgresql.yml
  echo " username: ${USER} " >> /home/${USER}/postgresql.yml
  echo " password: ${PASSWD} " >> /home/${USER}/postgresql.yml
  echo " superuser: " >> /home/${USER}/postgresql.yml
  echo " username: ${USER} " >> /home/${USER}/postgresql.yml
  echo " password: ${PASSWD} " >> /home/${USER}/postgresql.yml
  echo " rewind: " >> /home/${USER}/postgresql.yml
  echo " username: ${USER} " >> /home/${USER}/postgresql.yml
  echo " password: ${PASSWD} " >> /home/${USER}/postgresql.yml
  echo " parameters: " >> /home/${USER}/postgresql.yml
  echo " unix_socket_directories: '.' " >> /home/${USER}/postgresql.yml
  echo " wal_level: hot_standby " >> /home/${USER}/postgresql.yml
  echo " max_wal_senders: 10 " >> /home/${USER}/postgresql.yml
  echo " max_replication_slots: 10 " >> /home/${USER}/postgresql.yml
  echo "tags: " >> /home/${USER}/postgresql.yml
  echo " nofailover: false " >> /home/${USER}/postgresql.yml
  echo " noloadbalance: false " >> /home/${USER}/postgresql.yml
  echo " clonefrom: false " >> /home/${USER}/postgresql.yml
  echo " nosync: false " >> /home/${USER}/postgresql.yml
}
#........ Omit some content

Build the image

docker build -t patroni .

Run the image

Run container node 1:
docker run --privileged --name patroni1 -itd --hostname patroni1 --net my_net3 --restart always --env 'CLUSTER_NAME=patronicluster' --env 'SERVICE_NAME=service' --env 'ETCD_USER=etcduser' --env 'ETCD_PASSWD=etcdpasswd' --env 'PASSWD=zalando' --env 'HOSTLIST=patroni1,patroni2,patroni3' --env 'VIP=172.22.1.88' --env 'NET_DEVICE=eth0' --env 'ETCD_COUNT=3' patroni
Run container node 2:
docker run --privileged --name patroni2 -itd --hostname patroni2 --net my_net3 --restart always --env 'CLUSTER_NAME=patronicluster' --env 'SERVICE_NAME=service' --env 'ETCD_USER=etcduser' --env 'ETCD_PASSWD=etcdpasswd' --env 'PASSWD=zalando' --env 'HOSTLIST=patroni1,patroni2,patroni3' --env 'VIP=172.22.1.88' --env 'NET_DEVICE=eth0' --env 'ETCD_COUNT=3' patroni
Run container node 3:
docker run --privileged --name patroni3 -itd --hostname patroni3 --net my_net3 --restart always --env 'CLUSTER_NAME=patronicluster' --env 'SERVICE_NAME=service' --env 'ETCD_USER=etcduser' --env 'ETCD_PASSWD=etcdpasswd' --env 'PASSWD=zalando' --env 'HOSTLIST=patroni1,patroni2,patroni3' --env 'VIP=172.22.1.88' --env 'NET_DEVICE=eth0' --env 'ETCD_COUNT=3' patroni

Summarize

This operation process is limited to the experimental environment, in order to demonstrate the overall containerization of etcd+patroni+vipmanager. In a real environment, etcd should be deployed in different containers to form an independent distributed cluster, and the PG storage should be mapped to the local disk or network disk. In addition, the construction of the container cluster should use orchestration tools such as docker-compose, docker-warm or Kubernetes as much as possible.

Attached photos

The etcd cluster status is as shown below:

insert image description here

The patroni cluster status is as follows:

insert image description here

The vip-manager status is as shown below:

insert image description here

insert image description here

This is the end of this article about in-depth analysis of Patroni in Docker containers. For more relevant Docker container Patroni content, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Detailed explanation of several storage methods of docker containers
  • Steps for docker container exit error code
  • Docker container data volume named mount and anonymous mount issues

<<:  Solution to the problem that the vertical centering of flex inside button is not centered

>>:  How to implement html input drop-down menu

Recommend

Detailed steps to install MySQL 8.0.27 in Linux 7.6 binary

Table of contents 1. Environmental Preparation 1....

JS realizes the automatic playback effect of pictures

This article shares the specific code of JS to ac...

Vue implements start time and end time range query

This article shares with you how to query the sta...

How to quickly clean up billions of data in MySQL database

Today I received a disk alarm exception. The 50G ...

Detailed explanation of the process of installing MySQL on Ubuntu 18.04.4

Let's take a look at the process of installin...

Example of setting up a whitelist in Nginx using the geo module

Original configuration: http { ...... limit_conn_...

Specific usage of textarea's disabled and readonly attributes

disabled definition and usage The disabled attrib...

Solve the problem of Navicat for Mysql connection error 1251 (connection failed)

Because what I wrote before was not detailed enou...

MySQL Series 7 MySQL Storage Engine

1. MyISAM storage engine shortcoming: No support ...

Semanticization of HTML tags (including H5)

introduce HTML provides the contextual structure ...

How to prevent hyperlink redirection using JavaScript (multiple ways of writing)

Through JavaScript, we can prevent hyperlinks fro...

Nginx configuration based on multiple domain names, ports, IP virtual hosts

1. Type introduction 1.1 Domain-based virtual hos...

Implementation of 2D and 3D transformation in CSS3

CSS3 implements 2D plane transformation and visua...

CSS style writing order and naming conventions and precautions

The significance of writing order Reduce browser ...