diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index 985e23fd9..8795335ed 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -116,8 +116,8 @@ you still need a [Host](09-object-types.md#objecttype-host) object. In case you are using the CLI commands later, you don't have to write this configuration from scratch in a text editor. -The [ApiListener](09-object-types.md#objecttype-apilistener) -object is used to load the SSL certificates and specify restrictions, e.g. +The [ApiListener](09-object-types.md#objecttype-apilistener) object is +used to load the TLS certificates and specify restrictions, e.g. for accepting configuration commands. It is also used for the [Icinga 2 REST API](12-icinga2-api.md#icinga2-api) which shares @@ -2793,3 +2793,36 @@ Add the global zone `global-templates` in case it did not exist. global = true } EOF + +## Using Multiple Environments + +In some cases it can be desired to run multiple Icinga instances on the same host. +Two potential scenarios include: + +* Different versions of the same monitoring configuration (e.g. production and testing) +* Disparate sets of checks for entirely unrelated monitoring environments (e.g. infrastructure and applications) + +The configuration is done with the global constants `ApiBindHost` and `ApiBindPort` +or the `bind_host` and `bind_port` attributes of the +[ApiListener](09-object-types.md#objecttype-apilistener) object. + +The environment must be set with the global constant `Environment` or as object attribute +of the [IcingaApplication](#objecttype-icingaapplication) object. + +In any case the constant is default value for the attribute and the direct configuration in the objects +have more precedence. The constants have been added to allow the values being set from the CLI on startup. + +When Icinga establishes a TLS connection to another cluster instance it automatically uses the [SNI extension](https://en.wikipedia.org/wiki/Server_Name_Indication) +to signal which endpoint it is attempting to connect to. On its own this can already be used to position multiple +Icinga instances behind a load balancer. + +SNI example: `icinga2-client1.localdomain` + +However, if the environment is configured to `production`, Icinga appends the environment name to the SNI hostname like this: + +SNI example with environment: `icinga2-client1.localdomain:production` + +Middleware like loadbalancers or TLS proxies can read the SNI header and route the connection to the appropriate target. +I.e., it uses a single externally-visible TCP port (usually 5665) and forwards connections to one or more Icinga +instances which are bound to a local TCP port. It does so by inspecting the environment name that is sent as part of the +SNI extension. diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 904e31b23..d04f78156 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -67,6 +67,7 @@ Configuration Attributes: access\_control\_allow\_credentials | Boolean | **Deprecated.** Indicates whether or not the actual request can be made using credentials. Defaults to `true`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Credentials) access\_control\_allow\_headers | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP headers can be used when making the actual request. Defaults to `Authorization`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers) access\_control\_allow\_methods | String | **Deprecated.** Used in response to a preflight request to indicate which HTTP methods can be used when making the actual request. Defaults to `GET, POST, PUT, DELETE`. [(MDN docs)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Methods) + environment | String | **Optional.** Used as suffix in TLS SNI extension name; default from constant `ApiEnvironment`, which is empty. The attributes `access_control_allow_credentials`, `access_control_allow_headers` and `access_control_allow_methods` are controlled by Icinga 2 and are not changeable by config any more. @@ -816,6 +817,7 @@ Configuration Attributes: enable\_service\_checks | Boolean | **Optional.** Whether active service checks are globally enabled. Defaults to true. enable\_perfdata | Boolean | **Optional.** Whether performance data processing is globally enabled. Defaults to true. vars | Dictionary | **Optional.** A dictionary containing custom attributes that are available globally. + environment | String | **Optional.** Specify the Icinga environment. This overrides the `Environment` constant specified in the configuration or on the CLI with `--define`. Defaults to empty. ## IdoMySqlConnection diff --git a/doc/16-upgrading-icinga-2.md b/doc/16-upgrading-icinga-2.md index cce8cd916..4e4db7c7f 100644 --- a/doc/16-upgrading-icinga-2.md +++ b/doc/16-upgrading-icinga-2.md @@ -41,6 +41,7 @@ has been removed and this setting has no effect. New [Icinga constants](17-language-reference.md#icinga-constants) have been added in this release. +* `Environment` for specifying the Icinga environment. Defaults to not set. * `ApiBindHost` and `ApiBindPort` to allow overriding the default ApiListener values. This will be used for an Icinga addon only. ## Upgrading to v2.9 diff --git a/doc/17-language-reference.md b/doc/17-language-reference.md index de94ba4e8..f7fae4695 100644 --- a/doc/17-language-reference.md +++ b/doc/17-language-reference.md @@ -407,10 +407,10 @@ Constant | Description --------------------|------------------- Vars |**Read-write.** Contains a dictionary with global custom attributes. Not set by default. NodeName |**Read-write.** Contains the cluster node name. Set to the local hostname by default. +Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name for outbound connections. Not set by default. RunAsUser |**Read-write.** Defines the user the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig. RunAsGroup |**Read-write.** Defines the group the Icinga 2 daemon is running as. Set in the Icinga 2 sysconfig. MaxConcurrentChecks |**Read-write.** The number of max checks run simultaneously. Defaults to `512`. -Environment |**Read-write.** The name of the Icinga environment. Included in the SNI host name when making outbound connections. Defaults to `production`. ApiBindHost |**Read-write.** Overrides the default value for the ApiListener `bind_host` attribute. Not set by default. ApiBindPort |**Read-write.** Overrides the default value for the ApiListener `bind_port` attribute. Not set by default. @@ -454,7 +454,6 @@ SysconfDir |**Read-only.** Contains the path of the sysconf directory. LocalStateDir |**Read-only.** Contains the path of the local state directory. Defaults to `PrefixDir + "/var"`. RunDir |**Read-only.** Contains the path of the run directory. Defaults to `LocalStateDir + "/run"`. - Advanced runtime constants. Please only use them if advised by support or developers. Variable | Description diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 3849ca658..26ed4cf69 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -297,8 +297,6 @@ static int Main() Application::DeclareConst("Concurrency", std::thread::hardware_concurrency()); Application::DeclareConst("MaxConcurrentChecks", Application::GetDefaultMaxConcurrentChecks()); - ScriptGlobal::Set("Environment", "production"); - ScriptGlobal::Set("AttachDebugger", false); ScriptGlobal::Set("PlatformKernel", Utility::GetPlatformKernel()); diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index c16cda028..e784a3957 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -27,7 +27,7 @@ mkclass_target(sysloglogger.ti sysloglogger-ti.cpp sysloglogger-ti.hpp) set(base_SOURCES i2-base.hpp - application.cpp application.hpp application-ti.hpp application-version.cpp + application.cpp application.hpp application-ti.hpp application-version.cpp application-environment.cpp array.cpp array.hpp array-script.cpp base64.cpp base64.hpp boolean.cpp boolean.hpp boolean-script.cpp diff --git a/lib/base/application-environment.cpp b/lib/base/application-environment.cpp new file mode 100644 index 000000000..a70ea886f --- /dev/null +++ b/lib/base/application-environment.cpp @@ -0,0 +1,34 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "base/application.hpp" +#include "base/scriptglobal.hpp" + +using namespace icinga; + +String Application::GetAppEnvironment() +{ + Value defaultValue = Empty; + return ScriptGlobal::Get("Environment", &defaultValue); +} + +void Application::SetAppEnvironment(const String& name) +{ + ScriptGlobal::Set("Environment", name); +} diff --git a/lib/base/application.hpp b/lib/base/application.hpp index dbb14c38d..88fc51ab6 100644 --- a/lib/base/application.hpp +++ b/lib/base/application.hpp @@ -107,6 +107,9 @@ public: static String GetAppVersion(); static String GetAppSpecVersion(); + static String GetAppEnvironment(); + static void SetAppEnvironment(const String& name); + static double GetStartTime(); static void SetStartTime(double ts); diff --git a/lib/icinga/icingaapplication.cpp b/lib/icinga/icingaapplication.cpp index 6ac00aa60..4db75d852 100644 --- a/lib/icinga/icingaapplication.cpp +++ b/lib/icinga/icingaapplication.cpp @@ -79,10 +79,10 @@ void IcingaApplication::StatsFunc(const Dictionary::Ptr& status, const Array::Pt { "enable_host_checks", icingaapplication->GetEnableHostChecks() }, { "enable_service_checks", icingaapplication->GetEnableServiceChecks() }, { "enable_perfdata", icingaapplication->GetEnablePerfdata() }, + { "environment", icingaapplication->GetEnvironment() }, { "pid", Utility::GetPid() }, { "program_start", Application::GetStartTime() }, - { "version", Application::GetAppVersion() }, - { "environment", ScriptGlobal::Get("Environment", &Empty) } + { "version", Application::GetAppVersion() } })); } @@ -294,6 +294,16 @@ String IcingaApplication::GetNodeName() const return ScriptGlobal::Get("NodeName"); } +String IcingaApplication::GetEnvironment() const +{ + return Application::GetAppEnvironment(); +} + +void IcingaApplication::SetEnvironment(const String& value, bool suppress_events, const Value& cookie) +{ + Application::SetAppEnvironment(value); +} + void IcingaApplication::ValidateVars(const Lazy& lvalue, const ValidationUtils& utils) { MacroProcessor::ValidateCustomVars(this, lvalue()); diff --git a/lib/icinga/icingaapplication.hpp b/lib/icinga/icingaapplication.hpp index eac7f5903..ba8b77cf3 100644 --- a/lib/icinga/icingaapplication.hpp +++ b/lib/icinga/icingaapplication.hpp @@ -50,6 +50,9 @@ public: String GetNodeName() const; + String GetEnvironment() const override; + void SetEnvironment(const String& value, bool suppress_events = false, const Value& cookie = Empty) override; + void ValidateVars(const Lazy& lvalue, const ValidationUtils& utils) override; private: diff --git a/lib/icinga/icingaapplication.ti b/lib/icinga/icingaapplication.ti index 61d4ecd4c..b9f0381b3 100644 --- a/lib/icinga/icingaapplication.ti +++ b/lib/icinga/icingaapplication.ti @@ -26,6 +26,12 @@ namespace icinga class IcingaApplication : Application { + [config, no_storage, virtual] String environment { + get; + set; + default {{{ return Application::GetAppEnvironment(); }}} + }; + [config] bool enable_notifications { default {{{ return true; }}} }; diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 428fb2df7..8c0abf570 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -388,16 +388,10 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint) TcpSocket::Ptr client = new TcpSocket(); - String serverName = endpoint->GetName(); - - String env = ScriptGlobal::Get("Environment", &Empty); - if (env != "" && env != "production") - serverName += ":" + env; - try { endpoint->SetConnecting(true); client->Connect(host, port); - NewClientHandler(client, serverName, RoleClient); + NewClientHandler(client, endpoint->GetName(), RoleClient); endpoint->SetConnecting(false); } catch (const std::exception& ex) { endpoint->SetConnecting(false); @@ -447,10 +441,17 @@ void ApiListener::NewClientHandlerInternal(const Socket::Ptr& client, const Stri TlsStream::Ptr tlsStream; + String environmentName = Application::GetAppEnvironment(); + + String serverName = hostname; + + if (!environmentName.IsEmpty()) + serverName += ":" + environmentName; + { ObjectLock olock(this); try { - tlsStream = new TlsStream(client, hostname, role, m_SSLContext); + tlsStream = new TlsStream(client, serverName, role, m_SSLContext); } catch (const std::exception&) { Log(LogCritical, "ApiListener") << "Cannot create TLS stream from client connection (" << conninfo << ")"; diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index 374c54d1c..0e1ee8954 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -568,7 +568,8 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) << "{" << std::endl; for (const Field& field : klass.Fields) { - m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl; + if (!field.PureSetAccessor) + m_Impl << "\t" << "Set" << field.GetFriendlyName() << "(" << "GetDefault" << field.GetFriendlyName() << "(), true);" << std::endl; } m_Impl << "}" << std::endl << std::endl;