diff --git a/src/Makefile b/src/Makefile index e6773198..86a763e3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -32,6 +32,7 @@ OPT=$(OPTIMIZATION) PREFIX?=/usr/local INSTALL_BIN=$(PREFIX)/bin INSTALL=install +PKG_CONFIG?=pkg-config # Default allocator defaults to Jemalloc if it's not an ARM MALLOC=libc @@ -140,6 +141,30 @@ endif # Include paths to dependencies FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src +# Determine systemd support and/or build preference (defaulting to auto-detection) +BUILD_WITH_SYSTEMD=no +# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to +# auto-detect libsystemd's presence and link accordingly. +ifneq ($(USE_SYSTEMD),no) + LIBSYSTEMD_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libsystemd && echo $$?) +# If libsystemd cannot be detected, continue building without support for it +# (unless a later check tells us otherwise) +ifeq ($(LIBSYSTEMD_PKGCONFIG),0) + BUILD_WITH_SYSTEMD=yes +endif +endif +ifeq ($(USE_SYSTEMD),yes) +ifneq ($(LIBSYSTEMD_PKGCONFIG),0) +$(error USE_SYSTEMD is set to "$(USE_SYSTEMD)", but $(PKG_CONFIG) cannot find libsystemd) +endif +# Force building with libsystemd + BUILD_WITH_SYSTEMD=yes +endif +ifeq ($(BUILD_WITH_SYSTEMD),yes) + FINAL_LIBS+=$(shell $(PKG_CONFIG) --libs libsystemd) + FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD +endif + ifeq ($(MALLOC),tcmalloc) FINAL_CFLAGS+= -DUSE_TCMALLOC FINAL_LIBS+= -ltcmalloc diff --git a/src/replication.c b/src/replication.c index c9a2e0fe..68dc77a6 100644 --- a/src/replication.c +++ b/src/replication.c @@ -1665,6 +1665,11 @@ void readSyncBulkPayload(connection *conn) { if (server.repl_backlog == NULL) createReplicationBacklog(); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success"); + if (server.supervised_mode == SUPERVISED_SYSTEMD) { + redisCommunicateSystemd("STATUS=MASTER <-> REPLICA sync: Finished with success. Ready to accept connections.\n"); + redisCommunicateSystemd("READY=1\n"); + } + /* Restart the AOF subsystem now that we finished the sync. This * will trigger an AOF rewrite, and when done will start appending * to the new file. */ diff --git a/src/server.c b/src/server.c index e38a1703..debf2f68 100644 --- a/src/server.c +++ b/src/server.c @@ -3558,6 +3558,8 @@ int prepareForShutdown(int flags) { int nosave = flags & SHUTDOWN_NOSAVE; serverLog(LL_WARNING,"User requested shutdown..."); + if (server.supervised_mode == SUPERVISED_SYSTEMD) + redisCommunicateSystemd("STOPPING=1\n"); /* Kill all the Lua debugger forked sessions. */ ldbKillForkedSessions(); @@ -3599,6 +3601,8 @@ int prepareForShutdown(int flags) { /* Create a new RDB file before exiting. */ if ((server.saveparamslen > 0 && !nosave) || save) { serverLog(LL_NOTICE,"Saving the final RDB snapshot before exiting."); + if (server.supervised_mode == SUPERVISED_SYSTEMD) + redisCommunicateSystemd("STATUS=Saving the final RDB snapshot\n"); /* Snapshotting. Perform a SYNC SAVE and exit */ rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); @@ -3609,6 +3613,8 @@ int prepareForShutdown(int flags) { * saving aborted, handling special stuff like slaves pending for * synchronization... */ serverLog(LL_WARNING,"Error trying to save the DB, can't exit."); + if (server.supervised_mode == SUPERVISED_SYSTEMD) + redisCommunicateSystemd("STATUS=Error trying to save the DB, can't exit.\n"); return C_ERR; } } @@ -4774,61 +4780,19 @@ int redisSupervisedUpstart(void) { return 1; } -int redisSupervisedSystemd(void) { +int redisCommunicateSystemd(const char *sd_notify_msg) { const char *notify_socket = getenv("NOTIFY_SOCKET"); - int fd = 1; - struct sockaddr_un su; - struct iovec iov; - struct msghdr hdr; - int sendto_flags = 0; - if (!notify_socket) { serverLog(LL_WARNING, "systemd supervision requested, but NOTIFY_SOCKET not found"); - return 0; } - if ((strchr("@/", notify_socket[0])) == NULL || strlen(notify_socket) < 2) { - return 0; - } - - serverLog(LL_NOTICE, "supervised by systemd, will signal readiness"); - if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { - serverLog(LL_WARNING, - "Can't connect to systemd socket %s", notify_socket); - return 0; - } - - memset(&su, 0, sizeof(su)); - su.sun_family = AF_UNIX; - strncpy (su.sun_path, notify_socket, sizeof(su.sun_path) -1); - su.sun_path[sizeof(su.sun_path) - 1] = '\0'; - - if (notify_socket[0] == '@') - su.sun_path[0] = '\0'; - - memset(&iov, 0, sizeof(iov)); - iov.iov_base = "READY=1"; - iov.iov_len = strlen("READY=1"); - - memset(&hdr, 0, sizeof(hdr)); - hdr.msg_name = &su; - hdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + - strlen(notify_socket); - hdr.msg_iov = &iov; - hdr.msg_iovlen = 1; - - unsetenv("NOTIFY_SOCKET"); -#ifdef HAVE_MSG_NOSIGNAL - sendto_flags |= MSG_NOSIGNAL; -#endif - if (sendmsg(fd, &hdr, sendto_flags) < 0) { - serverLog(LL_WARNING, "Can't send notification to systemd"); - close(fd); - return 0; - } - close(fd); - return 1; + #ifdef HAVE_LIBSYSTEMD + (void) sd_notify(0, sd_notify_msg); + #else + UNUSED(sd_notify_msg); + #endif + return 0; } int redisIsSupervised(int mode) { @@ -4839,12 +4803,17 @@ int redisIsSupervised(int mode) { if (upstart_job) { redisSupervisedUpstart(); } else if (notify_socket) { - redisSupervisedSystemd(); + server.supervised_mode = SUPERVISED_SYSTEMD; + serverLog(LL_WARNING, + "WARNING auto-supervised by systemd - you MUST set appropriate values for TimeoutStartSec and TimeoutStopSec in your service unit."); + return redisCommunicateSystemd("STATUS=Redis is loading...\n"); } } else if (mode == SUPERVISED_UPSTART) { return redisSupervisedUpstart(); } else if (mode == SUPERVISED_SYSTEMD) { - return redisSupervisedSystemd(); + serverLog(LL_WARNING, + "WARNING supervised by systemd - you MUST set appropriate values for TimeoutStartSec and TimeoutStopSec in your service unit."); + return redisCommunicateSystemd("STATUS=Redis is loading...\n"); } return 0; @@ -5037,6 +5006,14 @@ int main(int argc, char **argv) { serverLog(LL_NOTICE,"Ready to accept connections"); if (server.sofd > 0) serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket); + if (server.supervised_mode == SUPERVISED_SYSTEMD) { + if (!server.masterhost) { + redisCommunicateSystemd("STATUS=Ready to accept connections\n"); + redisCommunicateSystemd("READY=1\n"); + } else { + redisCommunicateSystemd("STATUS=Waiting for MASTER <-> REPLICA sync\n"); + } + } } else { InitServerLast(); sentinelIsRunning(); diff --git a/src/server.h b/src/server.h index d9f24a1f..fb967a82 100644 --- a/src/server.h +++ b/src/server.h @@ -50,6 +50,10 @@ #include #include +#ifdef HAVE_LIBSYSTEMD +#include +#endif + typedef long long mstime_t; /* millisecond time type. */ typedef long long ustime_t; /* microsecond time type. */ @@ -1544,6 +1548,7 @@ uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); void exitFromChild(int retcode); size_t redisPopcount(void *s, long count); void redisSetProcTitle(char *title); +int redisCommunicateSystemd(const char *sd_notify_msg); /* networking.c -- Networking and Client related operations */ client *createClient(connection *conn); diff --git a/utils/install_server.sh b/utils/install_server.sh index 8e5753bc..efda7da1 100755 --- a/utils/install_server.sh +++ b/utils/install_server.sh @@ -73,6 +73,16 @@ if [ "$(id -u)" -ne 0 ] ; then exit 1 fi +#bail if this system is managed by systemd +_pid_1_exe="$(readlink -f /proc/1/exe)" +if [ "${_pid_1_exe##*/}" = systemd ] +then + echo "This systems seems to use systemd." + echo "Please take a look at the provided example service unit files in this directory, and adapt and install them. Sorry!" + exit 1 +fi +unset _pid_1_exe + if ! echo $REDIS_PORT | egrep -q '^[0-9]+$' ; then _MANUAL_EXECUTION=true #Read the redis port diff --git a/utils/systemd-redis_multiple_servers@.service b/utils/systemd-redis_multiple_servers@.service new file mode 100644 index 00000000..108ccfc6 --- /dev/null +++ b/utils/systemd-redis_multiple_servers@.service @@ -0,0 +1,37 @@ +# example systemd template service unit file for multiple redis-servers +# +# You can use this file as a blueprint for your actual template service unit +# file, if you intend to run multiple independent redis-server instances in +# parallel using systemd's "template unit files" feature. If you do, you will +# want to choose a better basename for your service unit by renaming this file +# when copying it. +# +# Please take a look at the provided "systemd-redis_server.service" example +# service unit file, too, if you choose to use this approach at managing +# multiple redis-server instances via systemd. + +[Unit] +Description=Redis data structure server - instance %i +Documentation=https://redis.io/documentation +# This template unit assumes your redis-server configuration file(s) +# to live at /etc/redis/redis_server_.conf +AssertPathExists=/etc/redis/redis_server_%i.conf +#Before=your_application.service another_example_application.service +#AssertPathExists=/var/lib/redis + +[Service] +ExecStart=/usr/local/bin/redis-server /etc/redis/redis_server_%i.conf +LimitNOFILE=10032 +NoNewPrivileges=yes +#OOMScoreAdjust=-900 +#PrivateTmp=yes +Type=notify +TimeoutStartSec=infinity +TimeoutStopSec=infinity +UMask=0077 +#User=redis +#Group=redis +#WorkingDirectory=/var/lib/redis + +[Install] +WantedBy=multi-user.target diff --git a/utils/systemd-redis_server.service b/utils/systemd-redis_server.service new file mode 100644 index 00000000..addee349 --- /dev/null +++ b/utils/systemd-redis_server.service @@ -0,0 +1,41 @@ +# example systemd service unit file for redis-server +# +# In order to use this as a template for providing a redis service in your +# environment, _at the very least_ make sure to adapt the redis configuration +# file you intend to use as needed (make sure to set "supervised systemd"), and +# to set sane TimeoutStartSec and TimeoutStopSec property values in the unit's +# "[Service]" section to fit your needs. +# +# Some properties, such as User= and Group=, are highly desirable for virtually +# all deployments of redis, but cannot be provided in a manner that fits all +# expectable environments. Some of these properties have been commented out in +# this example service unit file, but you are highly encouraged to set them to +# fit your needs. +# +# Please refer to systemd.unit(5), systemd.service(5), and systemd.exec(5) for +# more information. + +[Unit] +Description=Redis data structure server +Documentation=https://redis.io/documentation +#Before=your_application.service another_example_application.service +#AssertPathExists=/var/lib/redis + +[Service] +ExecStart=/usr/local/bin/redis-server --supervised systemd --daemonize no +## Alternatively, have redis-server load a configuration file: +#ExecStart=/usr/local/bin/redis-server /path/to/your/redis.conf +LimitNOFILE=10032 +NoNewPrivileges=yes +#OOMScoreAdjust=-900 +#PrivateTmp=yes +Type=notify +TimeoutStartSec=infinity +TimeoutStopSec=infinity +UMask=0077 +#User=redis +#Group=redis +#WorkingDirectory=/var/lib/redis + +[Install] +WantedBy=multi-user.target