From f2156da671e43be68db263ef197ee595a3643f58 Mon Sep 17 00:00:00 2001 From: Roman Vanicek Date: Thu, 4 Apr 2024 00:41:59 +0200 Subject: [PATCH] Fix build and startup. Ping from configuration peer 9still broken) --- Dockerfile | 9 +++--- main.go | 93 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/Dockerfile b/Dockerfile index c7f5a9a..6234947 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21-bullseye AS builder +FROM golang:1.20-alpine3.18 AS builder WORKDIR /usr/local/src/pg_autopool COPY go.* ./ @@ -7,16 +7,17 @@ RUN go mod download RUN mkdir bin/ && go build -o bin/ ./... -FROM alpine +FROM alpine:3.18 #RUN ca-certificates curl ldap-utils libaudit1 libbsd0 libcap-ng0 libcom-err2 libcrypt1 libedit2 libffi8 libgcc-s1 libgmp10 libgnutls30 libgssapi-krb5-2 libhogweed6 libicu72 libidn2-0 libk5crypto3 libkeyutils1 libkrb5-3 libkrb5support0 libldap-2.5-0 libldap-common liblzma5 libmd0 libnettle8 libnss-ldapd libp11-kit0 libpam-ldapd libpam0g libpq5 libsasl2-2 libssl3 libstdc++6 libtasn1-6 libtinfo6 libunistring2 libuuid1 libxml2 libxslt1.1 nslcd procps zlib1g RUN apk add --no-cache pgpool gettext postgresql-client && \ - mkdir /var/run/pgpool/ + mkdir /var/run/pgpool/ && \ + chmod 777 /var/run/pgpool COPY --from=builder /usr/local/src/pg_autopool/bin/pg_autopool / #COPY ./conf /etc/pgpool EXPOSE 5432 -ENTRYPOINT ["/bin/sh", "/pg_autopool"] +ENTRYPOINT ["/pg_autopool"] CMD ["pgpool", "-n"] \ No newline at end of file diff --git a/main.go b/main.go index 42ef101..5832385 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "flag" "fmt" "net" "net/http" @@ -10,13 +9,15 @@ import ( "os/exec" "strconv" "strings" + "sync" log "github.com/sirupsen/logrus" ) const version = "1.0" const DefaultSocketPath = "/var/run/pg_autoconfig.sock" -const PgpoolConfigPath = "/etc/pgpool/pgpool.conf" +const WorkDir = "/var/run/pgpool" +const PgpoolConfigPath = WorkDir + "/pgpool.conf" type pgpoolConfigMessage struct { Instances []pgpoolInstance @@ -30,6 +31,10 @@ type pgpoolInstance struct { IsReadOnly bool } +type pgpoolPingMessage struct { + NeedsConfig bool +} + type pgpoolForcedKey struct { hostName string port int @@ -54,8 +59,6 @@ type pgpoolNodeInfo struct { } func main() { - flag.Parse() - // Logging var logLevelS string if logLevelS = os.Getenv("AUTOPOOL_LOG_LEVEL"); logLevelS == "" { @@ -72,13 +75,10 @@ func main() { nextIsSocket := false socketPath := DefaultSocketPath - var argsLeft []string - for i, j := range os.Args { + for _, j := range os.Args[1:] { if nextIsSocket { nextIsSocket = false socketPath = j - } else if j == "--" { - argsLeft = os.Args[i+1:] } else { switch j { case "--socket": @@ -89,24 +89,49 @@ func main() { } } - if len(argsLeft) == 0 { - log.Info("No inner command found to execute. Will execute pgpool -n") - argsLeft = []string{"pgpool", "-n"} + // Set permissions as we usually run as non-root + if err := os.Chown(WorkDir, os.Getuid(), os.Getgid()); err != nil { + log.WithFields(log.Fields{"dir": WorkDir, "uid": os.Getuid(), "gid": os.Getgid()}).Warn("Failed to set owner of the work directory to the current user.") + } + if err := os.Chmod(WorkDir, 700); err != nil { + log.WithFields(log.Fields{"dir": WorkDir, "uid": os.Getuid(), "gid": os.Getgid()}).Warn("Failed to chmod the working directory.") } // Run the configuration change listener // Note: we must remember all IP/port pairs that appeared during // the runtime of the pgpool instance so we can properly report // former nodes are down. + var intialConfigWait sync.WaitGroup + intialConfigWait.Add(1) + initialConfigDone := false nodes := make(map[pgpoolNodeKey]pgpoolNodeInfo) handler := http.NewServeMux() + handler.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) { + log.Trace("Received a ping") + + var msg pgpoolPingMessage + msg.NeedsConfig = !initialConfigDone + if msgS, err := json.Marshal(msg); err != nil { + log.WithFields(log.Fields{"msg": msg}).Warn("Failed to serializeping message") + } else { + w.Header()["Content-Type"] = []string{"application/json"} + w.Header()["Content-Length"] = []string{strconv.Itoa(len(msgS))} + w.WriteHeader(http.StatusOK) + w.Write(msgS) + } + }) handler.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) { var msg pgpoolConfigMessage if err = json.NewDecoder(r.Body).Decode(&msg); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } - configure(msg, nodes) + log.Info("Received a new configuration") + configure(msg, nodes, !initialConfigDone) + if !initialConfigDone { + initialConfigDone = true + intialConfigWait.Done() + } w.WriteHeader(http.StatusOK) w.Write([]byte("OK")) @@ -115,6 +140,7 @@ func main() { Handler: handler, } go func() { + os.Remove(socketPath) if listener, err := net.Listen("unix", socketPath); err != nil { log.WithError(err).Fatal("Failed to start config change listener") } else { @@ -123,8 +149,12 @@ func main() { }() // Start the inner executable (usually starting with "pg_autoctl create postgres" or "pg_autoctl create monitor") - log.WithFields(log.Fields{"name": argsLeft[0], "args": argsLeft[1:]}).Info("Handling over to inner process.") - cmd := exec.Command(argsLeft[0], argsLeft[1:]...) + log.Info("Waiting for the initial configuration to arrive") + intialConfigWait.Wait() + log.Info("Handling over to the inner pgpool process") + cmd := exec.Command("pgpool", "-n", "--config-file", PgpoolConfigPath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr //cmd.Env = os.Environ() cmd.Run() @@ -132,7 +162,7 @@ func main() { server.Close() } -func configure(msg pgpoolConfigMessage, nodesHistory map[pgpoolNodeKey]pgpoolNodeInfo) { +func configure(msg pgpoolConfigMessage, nodesHistory map[pgpoolNodeKey]pgpoolNodeInfo, isInitial bool) { conf := make(map[string]string) // Note: We are mostly compatible with the bitnami pgpool docker regarding env vars @@ -249,7 +279,7 @@ func configure(msg pgpoolConfigMessage, nodesHistory map[pgpoolNodeKey]pgpoolNod conf["ssl_ca_cert"] = val } conf["ssl_cert"] = getEnvOrDefault("PGPOOL_TLS_CERT_FILE", "") - conf["ssl_cert"] = getEnvOrDefault("PGPOOL_TLS_KEY_FILE", "") + conf["ssl_key"] = getEnvOrDefault("PGPOOL_TLS_KEY_FILE", "") } // @@ -326,6 +356,7 @@ func configure(msg pgpoolConfigMessage, nodesHistory map[pgpoolNodeKey]pgpoolNod // // Write the config file // + log.WithFields(log.Fields{"conf": conf}).Trace("New configuration") if configFile, err := os.Create(PgpoolConfigPath); err != nil { log.WithError(err).Fatal("Cannot open the pgpool config file.") } else { @@ -339,27 +370,29 @@ func configure(msg pgpoolConfigMessage, nodesHistory map[pgpoolNodeKey]pgpoolNod configFile.Sync() } - // Reload config - reloadCmd := exec.Command("pcp_reload_config", "-h", tmpDir, "-p", pgpoolPort, "--no-password") - if err := reloadCmd.Run(); err != nil { - log.WithError(err).Warn("Failed to force config reload.") - } + if !isInitial { + // Reload config + reloadCmd := exec.Command("pcp_reload_config", "-h", tmpDir, "-p", pgpoolPort, "--no-password") + if err := reloadCmd.Run(); err != nil { + log.WithError(err).Warn("Failed to force config reload.") + } - // Attach nodes - for _, j := range msg.Instances { - if info, exists := nodesHistory[pgpoolNodeKey{address: j.IpAddress.String(), port: j.Port}]; !exists { - log.WithFields(log.Fields{"address": j.IpAddress, "Port": j.Port}).Warn("Failed to resolve node number. Skipping attach.") - } else { - attachCmd := exec.Command("pcp_attach_node", "-h", tmpDir, "-p", pgpoolPort, "--no-password", strconv.Itoa(info.num)) - if err := attachCmd.Run(); err != nil { - log.WithError(err).WithFields(log.Fields{"address": j.IpAddress, "Port": j.Port}).Warn("Node attach failed.") + // Attach nodes + for _, j := range msg.Instances { + if info, exists := nodesHistory[pgpoolNodeKey{address: j.IpAddress.String(), port: j.Port}]; !exists { + log.WithFields(log.Fields{"address": j.IpAddress, "Port": j.Port}).Warn("Failed to resolve node number. Skipping attach.") + } else { + attachCmd := exec.Command("pcp_attach_node", "-h", tmpDir, "-p", pgpoolPort, "--no-password", strconv.Itoa(info.num)) + if err := attachCmd.Run(); err != nil { + log.WithError(err).WithFields(log.Fields{"address": j.IpAddress, "Port": j.Port}).Warn("Node attach failed.") + } } } } } func getEnvOrDefault(name string, defaultValue string) string { - if val := os.Getenv(name); name != "" { + if val := os.Getenv(name); val != "" { return val } else { return defaultValue