Skip to content

Commit da5d3ab

Browse files
mgr/cephadm: Allow Ingress service to expose the metrics via HTTPS also add fields in spec to accept monitor ips/ monitor networks
Fixes: https://tracker.ceph.com/issues/71707 Signed-off-by: Shweta Bhosale <[email protected]>
1 parent 14b1dd7 commit da5d3ab

File tree

9 files changed

+543
-22
lines changed

9 files changed

+543
-22
lines changed

doc/cephadm/services/rgw.rst

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,30 @@ Service specs are YAML blocks with the following properties:
409409
use_keepalived_multicast: <bool> # optional: Default is False.
410410
vrrp_interface_network: <string>/<string> # optional: ex: 192.168.20.0/24
411411
health_check_interval: <string> # optional: Default is 2s.
412+
ssl: true
413+
certificate_source: inline # optional: Default is cephadm-signed
412414
ssl_cert: | # optional: SSL certificate and key
413415
-----BEGIN CERTIFICATE-----
414416
...
415417
-----END CERTIFICATE-----
418+
ssl_key: |
416419
-----BEGIN PRIVATE KEY-----
417420
...
418421
-----END PRIVATE KEY-----
422+
enable_stats: true
423+
monitor_ssl: <bool>
424+
monitor_cert_source: inline # optional: default is reuse_service_cert
425+
monitor_ssl_cert: | # optional: SSL certificate and key
426+
-----BEGIN CERTIFICATE-----
427+
...
428+
-----END CERTIFICATE-----
429+
monitor_ssl_key: |
430+
-----BEGIN PRIVATE KEY-----
431+
...
432+
-----END PRIVATE KEY-----
433+
monitor_networks: [..]
434+
monitor_ip_addrs:
435+
host: <ip>
419436
420437
.. code-block:: yaml
421438
@@ -467,9 +484,20 @@ where the properties of this service specification are:
467484
A list of networks to identify which ethernet interface to use for the virtual IP.
468485
* ``frontend_port``
469486
The port used to access the ingress service.
470-
* ``ssl_cert``:
471-
SSL certificate, if SSL is to be enabled. This must contain the both the certificate and
472-
private key blocks in .pem format.
487+
* ``ssl``
488+
To enable SSL for ingress service.
489+
* ``certificate_source``
490+
The certificate source can be one of the following: 'inline', 'reference', or 'cephadm-signed'.
491+
- If set to 'inline', the YAML configuration must include ssl_cert and ssl_key.
492+
- If set to 'reference', the certificate and key must already exist in the certificate store.
493+
- If set to 'cephadm-signed', Cephadm will automatically generate the certificate and key.
494+
By default, the source is set to 'cephadm-signed'.
495+
* ``ssl_cert``
496+
SSL certificate, if SSL is enabled and ``certificate_source`` is not 'cephadm-signed'.
497+
This should have the certificate .pem format.
498+
* ``ssl_key``
499+
SSL key, if SSL is enabled and ``certificate_source`` is not 'cephadm-signed'.
500+
This should have the key .pem format.
473501
* ``use_keepalived_multicast``
474502
Default is False. By default, cephadm will deploy keepalived config to use unicast IPs,
475503
using the IPs of the hosts. The IPs chosen will be the same IPs cephadm uses to connect
@@ -488,6 +516,29 @@ where the properties of this service specification are:
488516
* ``health_check_interval``
489517
Default is 2 seconds. This parameter can be used to set the interval between health checks
490518
for the haproxy with the backend servers.
519+
* ``enable_stats``
520+
Default is False, must be set to enable haproxy stats.
521+
* ``monitor_ssl``
522+
To enable ssl for monitoring. SSL for monitoring can be enabled only when service SSL is enabled.
523+
* ``monitor_cert_source``
524+
The monitor certificate source can be one of the following: 'reuse_service_cert', 'inline', 'reference', or 'cephadm-signed'.
525+
- If set to 'reuse_service_cert', then the service certs will be used.
526+
- If set to 'inline', the YAML configuration must include ssl_cert and ssl_key.
527+
- If set to 'reference', the certificate and key must already exist in the certificate store.
528+
- If set to 'cephadm-signed', Cephadm will automatically generate the certificate and key.
529+
By default, the source is set to 'reuse_service_cert'.
530+
* ``monitor_ssl_cert``
531+
Monitor SSL certificate, if monitor SSL is enabled and ``monitor_cert_source``
532+
is not 'cephadm-signed'. This should have the certificate .pem format.
533+
* ``monitor_ssl_key``
534+
Monitor SSL key, if monitor SSL is enabled and ``monitor_cert_source`` is not
535+
'cephadm-signed'. This should have the key .pem format.
536+
* ``monitor_ip_addrs``
537+
If ``monitor_ip_addrs`` is provided and the specified IP address is assigned to the host,
538+
that IP address will be used. If IP address is not present, then 'monitor_networks' will be checked.
539+
* ``monitor_networks``
540+
If ``monitor_networks`` is specified, an IP address that matches one of the specified
541+
networks will be used. If IP not present, then default host ip will be used.
491542

492543
.. _ingress-virtual-ip:
493544

src/pybind/mgr/cephadm/module.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,8 @@ def _init_cert_mgr(self) -> None:
739739

740740
self.cert_mgr.register_cert_key_pair('nvmeof', 'nvmeof_client_cert', 'nvmeof_client_key', TLSObjectScope.SERVICE)
741741
self.cert_mgr.register_cert('nvmeof', 'nvmeof_root_ca_cert', TLSObjectScope.SERVICE)
742+
# register haproxy monitor ssl cert and key
743+
self.cert_mgr.register_cert_key_pair('ingress', 'haproxy_monitor_ssl_cert', 'haproxy_monitor_ssl_key', TLSObjectScope.SERVICE)
742744

743745
self.cert_mgr.init_tlsobject_store()
744746

src/pybind/mgr/cephadm/services/ingress.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import string
55
from typing import List, Dict, Any, Tuple, cast, Optional, TYPE_CHECKING
66

7-
from ceph.deployment.service_spec import ServiceSpec, IngressSpec
7+
from ceph.deployment.service_spec import ServiceSpec, IngressSpec, MonitorCertSource
88
from mgr_util import build_url
99
from cephadm import utils
1010
from orchestrator import OrchestratorError, DaemonDescription
@@ -26,6 +26,14 @@ class IngressService(CephService):
2626
def needs_monitoring(self) -> bool:
2727
return True
2828

29+
@property
30+
def haproxy_stats_cert_name(self) -> str:
31+
return 'haproxy_monitor_ssl_cert'
32+
33+
@property
34+
def haproxy_stats_key_name(self) -> str:
35+
return 'haproxy_monitor_ssl_key'
36+
2937
@classmethod
3038
def get_dependencies(cls, mgr: "CephadmOrchestrator",
3139
spec: Optional[ServiceSpec] = None,
@@ -214,6 +222,23 @@ def haproxy_generate_config(
214222
frontend_port = daemon_spec.ports[0] if daemon_spec.ports else spec.frontend_port
215223
if ip != '[::]' and frontend_port:
216224
daemon_spec.port_ips = {str(frontend_port): ip}
225+
226+
monitor_ip, monitor_port = self.get_monitoring_details(daemon_spec.service_name, daemon_spec.host)
227+
if monitor_ip:
228+
monitor_ips = [monitor_ip]
229+
daemon_spec.port_ips.update({str(monitor_port): monitor_ip})
230+
else:
231+
monitor_ips = [ip, host_ip]
232+
233+
monitor_ssl_file = None
234+
cert_ips = [ip]
235+
if spec.monitor_ssl:
236+
if spec.monitor_cert_source == MonitorCertSource.REUSE_SERVICE_CERT.value:
237+
monitor_ssl_file = 'haproxy.pem'
238+
cert_ips.extend(monitor_ips)
239+
else:
240+
monitor_ssl_file = 'stats_haproxy.pem'
241+
217242
haproxy_conf = self.mgr.template.render(
218243
'services/ingress/haproxy.cfg.j2',
219244
{
@@ -224,12 +249,13 @@ def haproxy_generate_config(
224249
'user': spec.monitor_user or 'admin',
225250
'password': password,
226251
'ip': ip,
252+
'monitor_ips': monitor_ips,
227253
'frontend_port': frontend_port,
228-
'monitor_port': daemon_spec.ports[1] if daemon_spec.ports else spec.monitor_port,
229-
'local_host_ip': host_ip,
254+
'monitor_port': spec.monitor_port,
230255
'default_server_opts': server_opts,
231256
'health_check_interval': spec.health_check_interval or '2s',
232257
'v4v6_flag': v4v6_flag,
258+
'monitor_ssl_file': monitor_ssl_file,
233259
}
234260
)
235261
config_files = {
@@ -243,8 +269,30 @@ def haproxy_generate_config(
243269
combined_pem = tls_pair.cert + '\n' + tls_pair.key
244270
config_files['files']['haproxy.pem'] = combined_pem
245271

272+
if spec.monitor_ssl and spec.monitor_cert_source != MonitorCertSource.REUSE_SERVICE_CERT.value:
273+
stats_cert, stats_key = self.get_stats_certs(spec, daemon_spec, monitor_ips)
274+
monitor_ssl_cert = [stats_cert, stats_key]
275+
config_files['files']['stats_haproxy.pem'] = '\n'.join(monitor_ssl_cert)
276+
246277
return config_files, self.get_haproxy_dependencies(self.mgr, spec)
247278

279+
def get_stats_certs(
280+
self,
281+
svc_spec: IngressSpec,
282+
daemon_spec: CephadmDaemonDeploySpec,
283+
ips: Optional[List[str]] = None,
284+
) -> Tuple[str, str]:
285+
return self.get_certificates_generic(
286+
svc_spec=svc_spec,
287+
daemon_spec=daemon_spec,
288+
cert_attr='monitor_ssl_cert',
289+
key_attr='monitor_ssl_key',
290+
cert_source_attr='monitor_cert_source',
291+
cert_name=self.haproxy_stats_cert_name,
292+
key_name=self.haproxy_stats_key_name,
293+
ips=ips
294+
)
295+
248296
def keepalived_prepare_create(
249297
self,
250298
daemon_spec: CephadmDaemonDeploySpec,
@@ -445,3 +493,18 @@ def _get_valid_interface_and_ip(vip: str, host: str) -> Tuple[str, str]:
445493
}
446494

447495
return config_file, self.get_keepalived_dependencies(self.mgr, spec)
496+
497+
def get_monitoring_details(self, service_name: str, host: str) -> Tuple[Optional[str], Optional[int]]:
498+
spec = cast(IngressSpec, self.mgr.spec_store[service_name].spec)
499+
monitor_port = spec.monitor_port
500+
501+
# check if monitor needs to be bind on specific ip
502+
monitor_addr = spec.monitor_ip_addrs.get(host) if spec.monitor_ip_addrs else None
503+
if monitor_addr and monitor_addr not in self.mgr.cache.get_host_network_ips(host):
504+
logger.debug(f"Monitoring IP {monitor_addr} is not configured on host {host}.")
505+
monitor_addr = None
506+
if not monitor_addr and spec.monitor_networks:
507+
monitor_addr = self.mgr.get_first_matching_network_ip(host, spec, spec.monitor_networks)
508+
if not monitor_addr:
509+
logger.debug(f"No IP address found in the network {spec.monitor_networks} on host {host}.")
510+
return monitor_addr, monitor_port

src/pybind/mgr/cephadm/services/service_discovery.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ class Server: # type: ignore
1212
from mgr_util import build_url
1313
from typing import Dict, List, TYPE_CHECKING, cast, Collection, Callable, NamedTuple, Optional, IO
1414
from cephadm.services.nfs import NFSService
15+
from cephadm.services.ingress import IngressService
1516
from cephadm.services.monitoring import AlertmanagerService, NodeExporterService, PrometheusService
1617
import secrets
1718
from mgr_util import verify_tls_files
1819
import tempfile
1920

20-
from cephadm.services.ingress import IngressSpec
2121
from cephadm.services.cephadmservice import CephExporterService
2222
from cephadm.services.nvmeof import NvmeofService
2323
from cephadm.services.service_registry import service_registry
@@ -223,12 +223,13 @@ def haproxy_sd_config(self) -> List[Dict[str, Collection[str]]]:
223223
srv_entries = []
224224
for dd in self.mgr.cache.get_daemons_by_type('ingress'):
225225
if dd.service_name() in self.mgr.spec_store:
226-
spec = cast(IngressSpec, self.mgr.spec_store[dd.service_name()].spec)
227226
assert dd.hostname is not None
228227
if dd.daemon_type == 'haproxy':
229-
addr = self.mgr.inventory.get_addr(dd.hostname)
228+
ingress = cast(IngressService, service_registry.get_service('ingress'))
229+
monitor_ip, monitor_port = ingress.get_monitoring_details(dd.service_name(), dd.hostname)
230+
addr = monitor_ip or dd.ip or self.mgr.inventory.get_addr(dd.hostname)
230231
srv_entries.append({
231-
'targets': [f"{build_url(host=addr, port=spec.monitor_port).lstrip('/')}"],
232+
'targets': [f"{build_url(host=addr, port=monitor_port).lstrip('/')}"],
232233
'labels': {'ingress': dd.service_name(), 'instance': dd.hostname}
233234
})
234235
return srv_entries

src/pybind/mgr/cephadm/templates/services/ingress/haproxy.cfg.j2

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,24 @@ defaults
4545
{% endif %}
4646
maxconn 8000
4747

48+
{% if spec.enable_stats %}
4849
frontend stats
4950
mode http
50-
bind {{ ip }}:{{ monitor_port }}
51-
bind {{ local_host_ip }}:{{ monitor_port }}
51+
{% for monitor_ip in monitor_ips %}
52+
{% if spec.monitor_ssl %}
53+
bind {{ monitor_ip }}:{{ monitor_port }} ssl crt /var/lib/haproxy/{{ monitor_ssl_file }}
54+
{% else %}
55+
bind {{ monitor_ip }}:{{ monitor_port }}
56+
{% endif %}
57+
{% endfor %}
5258
stats enable
5359
stats uri /stats
5460
stats refresh 10s
5561
stats auth {{ user }}:{{ password }}
5662
http-request use-service prometheus-exporter if { path /metrics }
5763
monitor-uri /health
5864

65+
{% endif %}
5966
frontend frontend
6067
{% if spec.ssl or spec.ssl_cert %}
6168
bind {{ ip }}:{{ frontend_port }} ssl crt /var/lib/haproxy/haproxy.pem {{ v4v6_flag }}

src/pybind/mgr/cephadm/tests/test_service_discovery.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ def __init__(self, port):
6868
class FakeIngressServiceSpec:
6969
def __init__(self, port):
7070
self.monitor_port = port
71+
self.monitor_ip_addrs = {}
72+
self.monitor_networks = {}
7173

7274

7375
class FakeServiceSpec:

0 commit comments

Comments
 (0)