Skip to content

Commit 659ebf6

Browse files
committed
Notify systemd when Elasticsearch is ready (elastic#44673)
Today our systemd service defaults to a service type of simple. This means that systemd assumes Elasticsearch is ready as soon as the ExecStart (bin/elasticsearch) process is forked off. This means that the service appears ready long before it actually is, so before it is ready to receive requests. It also means that services that want to depend on Elasticsearch being ready to start can not as there is not a reliable mechanism to determine this. This commit changes the service type to notify. This requires that Elasticsearch sends a notification message via libsystemd sd_notify method. This commit does that by using JNA to invoke this native method. Additionally, we use this integration to also notify systemd when we are stopping.
1 parent 27440b7 commit 659ebf6

File tree

8 files changed

+384
-0
lines changed

8 files changed

+384
-0
lines changed

distribution/build.gradle

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ task buildOssNoJdkNotice(type: NoticeTask) {
6868
*****************************************************************************/
6969
String ossOutputs = 'build/outputs/oss'
7070
String defaultOutputs = 'build/outputs/default'
71+
String systemdOutputs = 'build/outputs/systemd'
7172
String transportOutputs = 'build/outputs/transport-only'
7273

7374
task processOssOutputs(type: Sync) {
@@ -79,6 +80,10 @@ task processDefaultOutputs(type: Sync) {
7980
from processOssOutputs
8081
}
8182

83+
task processSystemdOutputs(type: Sync) {
84+
into systemdOutputs
85+
}
86+
8287
// Integ tests work over the rest http layer, so we need a transport included with the integ test zip.
8388
// All transport modules are included so that they may be randomized for testing
8489
task processTransportOutputs(type: Sync) {
@@ -110,6 +115,10 @@ task buildDefaultConfig {
110115
dependsOn processDefaultOutputs
111116
outputs.dir "${defaultOutputs}/config"
112117
}
118+
task buildSystemdModule {
119+
dependsOn processSystemdOutputs
120+
outputs.dir "${systemdOutputs}/modules"
121+
}
113122
task buildTransportModules {
114123
dependsOn processTransportOutputs
115124
outputs.dir "${transportOutputs}/modules"
@@ -186,6 +195,10 @@ ext.restTestExpansions = [
186195
// we create the buildOssModules task above but fill it here so we can do a single
187196
// loop over modules to also setup cross task dependencies and increment our modules counter
188197
project.rootProject.subprojects.findAll { it.parent.path == ':modules' }.each { Project module ->
198+
if (module.name == 'systemd') {
199+
// the systemd module is only included in the package distributions
200+
return
201+
}
189202
File licenses = new File(module.projectDir, 'licenses')
190203
if (licenses.exists()) {
191204
buildDefaultNotice.licensesDir licenses
@@ -218,6 +231,8 @@ xpack.subprojects.findAll { it.parent == xpack }.each { Project xpackModule ->
218231
copyLog4jProperties(buildDefaultLog4jConfig, xpackModule)
219232
}
220233

234+
copyModule(processSystemdOutputs, project(':modules:systemd'))
235+
221236
// make sure we have a clean task since we aren't a java project, but we have tasks that
222237
// put stuff in the build dir
223238
task clean(type: Delete) {
@@ -285,6 +300,9 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
285300
exclude "**/platform/${excludePlatform}-x86_64/**"
286301
}
287302
}
303+
if (project.path.startsWith(':distribution:packages')) {
304+
from(project(':distribution').buildSystemdModule)
305+
}
288306
}
289307
}
290308

distribution/packages/src/common/systemd/elasticsearch.service

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ Wants=network-online.target
55
After=network-online.target
66

77
[Service]
8+
Type=notify
89
RuntimeDirectory=elasticsearch
910
PrivateTmp=true
1011
Environment=ES_HOME=/usr/share/elasticsearch
1112
Environment=ES_PATH_CONF=${path.conf}
1213
Environment=PID_DIR=/var/run/elasticsearch
14+
Environment=ES_SD_NOTIFY=true
1315
EnvironmentFile=-${path.env}
1416

1517
WorkingDirectory=/usr/share/elasticsearch

modules/systemd/build.gradle

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
esplugin {
21+
description 'Integrates Elasticsearch with systemd'
22+
classname 'org.elasticsearch.systemd.SystemdPlugin'
23+
}
24+
25+
integTest.enabled = false
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.systemd;
21+
22+
import com.sun.jna.Native;
23+
24+
import java.security.AccessController;
25+
import java.security.PrivilegedAction;
26+
27+
/**
28+
* Provides access to the native method sd_notify from libsystemd.
29+
*/
30+
class Libsystemd {
31+
32+
static {
33+
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
34+
Native.register(Libsystemd.class, "libsystemd.so.0");
35+
return null;
36+
});
37+
}
38+
39+
/**
40+
* Notify systemd of state changes.
41+
*
42+
* @param unset_environment if non-zero, the NOTIFY_SOCKET environment variable will be unset before returning and further calls to
43+
* sd_notify will fail
44+
* @param state a new-line separated list of variable assignments; some assignments are understood directly by systemd
45+
* @return a negative error code on failure, and positive if status was successfully sent
46+
*/
47+
static native int sd_notify(int unset_environment, String state);
48+
49+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.systemd;
21+
22+
import org.apache.logging.log4j.LogManager;
23+
import org.apache.logging.log4j.Logger;
24+
import org.apache.lucene.util.Constants;
25+
import org.elasticsearch.Assertions;
26+
import org.elasticsearch.Build;
27+
import org.elasticsearch.plugins.ClusterPlugin;
28+
import org.elasticsearch.plugins.Plugin;
29+
30+
public class SystemdPlugin extends Plugin implements ClusterPlugin {
31+
32+
private static final Logger logger = LogManager.getLogger(SystemdPlugin.class);
33+
34+
private final boolean enabled;
35+
36+
final boolean isEnabled() {
37+
return enabled;
38+
}
39+
40+
@SuppressWarnings("unused")
41+
public SystemdPlugin() {
42+
this(true, Constants.LINUX, System.getenv("ES_SD_NOTIFY"));
43+
}
44+
45+
SystemdPlugin(final boolean assertIsPackageDistribution, final boolean isLinux, final String esSDNotify) {
46+
if (Assertions.ENABLED && assertIsPackageDistribution) {
47+
// our build is configured to only include this module in the package distributions
48+
assert Build.CURRENT.type() == Build.Type.DEB || Build.CURRENT.type() == Build.Type.RPM : Build.CURRENT.type();
49+
}
50+
if (isLinux == false || esSDNotify == null) {
51+
enabled = false;
52+
return;
53+
}
54+
if (Boolean.TRUE.toString().equals(esSDNotify) == false && Boolean.FALSE.toString().equals(esSDNotify) == false) {
55+
throw new RuntimeException("ES_SD_NOTIFY set to unexpected value [" + esSDNotify + "]");
56+
}
57+
enabled = Boolean.TRUE.toString().equals(esSDNotify);
58+
}
59+
60+
int sd_notify(@SuppressWarnings("SameParameterValue") final int unset_environment, final String state) {
61+
return Libsystemd.sd_notify(unset_environment, state);
62+
}
63+
64+
@Override
65+
public void onNodeStarted() {
66+
if (enabled == false) {
67+
return;
68+
}
69+
final int rc = sd_notify(0, "READY=1");
70+
logger.trace("sd_notify returned [{}]", rc);
71+
if (rc < 0) {
72+
// treat failure to notify systemd of readiness as a startup failure
73+
throw new RuntimeException("sd_notify returned error [" + rc + "]");
74+
}
75+
}
76+
77+
@Override
78+
public void close() {
79+
if (enabled == false) {
80+
return;
81+
}
82+
final int rc = sd_notify(0, "STOPPING=1");
83+
logger.trace("sd_notify returned [{}]", rc);
84+
if (rc < 0) {
85+
// do not treat failure to notify systemd of stopping as a failure
86+
logger.warn("sd_notify returned error [{}]", rc);
87+
}
88+
}
89+
90+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
grant codeBase "${codebase.systemd}" {
21+
// for registering native methods
22+
permission java.lang.RuntimePermission "accessDeclaredMembers";
23+
};

0 commit comments

Comments
 (0)