diff --git a/userspace/libsinsp/container.cpp b/userspace/libsinsp/container.cpp index 9273b59a2b..f7fb7edc22 100644 --- a/userspace/libsinsp/container.cpp +++ b/userspace/libsinsp/container.cpp @@ -17,6 +17,8 @@ limitations under the License. */ +#include + #include "container_engine/cri.h" #include "container_engine/docker.h" #include "container_engine/rkt.h" @@ -131,8 +133,8 @@ bool sinsp_container_manager::resolve_container(sinsp_threadinfo* tinfo, bool qu } } - // Also identify if this thread is part of a container healthcheck - identify_healthcheck(tinfo); + // Also possibly set the category for the threadinfo + identify_category(tinfo); return matches; } @@ -169,10 +171,7 @@ string sinsp_container_manager::container_to_json(const sinsp_container_info& co container["Mounts"] = mounts; - if(!container_info.m_healthcheck_obj.isNull()) - { - container["Healthcheck"] = container_info.m_healthcheck_obj; - } + sinsp_container_info::container_health_probe::add_health_probes(container_info.m_health_probes, container); char addrbuff[100]; uint32_t iph = htonl(container_info.m_container_ip); @@ -356,15 +355,10 @@ string sinsp_container_manager::get_container_name(sinsp_threadinfo* tinfo) return res; } -void sinsp_container_manager::identify_healthcheck(sinsp_threadinfo *tinfo) +void sinsp_container_manager::identify_category(sinsp_threadinfo *tinfo) { - // This thread is a part of a container healthcheck if its - // parent thread is part of a health check. - sinsp_threadinfo* ptinfo = tinfo->get_parent_thread(); - - if(ptinfo && ptinfo->m_is_container_healthcheck) + if(tinfo->m_container_id.empty()) { - tinfo->m_is_container_healthcheck = true; return; } @@ -375,23 +369,65 @@ void sinsp_container_manager::identify_healthcheck(sinsp_threadinfo *tinfo) return; } - // Otherwise, the thread is a part of a container healthcheck if: + if(tinfo->m_vpid == 1) + { + if(g_logger.get_severity() >= sinsp_logger::SEV_DEBUG) + { + g_logger.format(sinsp_logger::SEV_DEBUG, + "identify_category (%ld) (%s): initial process for container, assigning CAT_CONTAINER", + tinfo->m_tid, tinfo->m_comm.c_str()); + } + + tinfo->m_category = sinsp_threadinfo::CAT_CONTAINER; + + return; + } + + // Categories are passed from parent to child threads + sinsp_threadinfo* ptinfo = tinfo->get_parent_thread(); + + if(ptinfo && ptinfo->m_category != sinsp_threadinfo::CAT_NONE) + { + if(g_logger.get_severity() >= sinsp_logger::SEV_DEBUG) + { + g_logger.format(sinsp_logger::SEV_DEBUG, + "identify_category (%ld) (%s): taking parent category %d", + tinfo->m_tid, tinfo->m_comm.c_str(), ptinfo->m_category); + } + + tinfo->m_category = ptinfo->m_category; + return; + } + + if(!cinfo->m_metadata_complete) + { + if(g_logger.get_severity() >= sinsp_logger::SEV_DEBUG) + { + g_logger.format(sinsp_logger::SEV_DEBUG, + "identify_category (%ld) (%s): container metadata incomplete", + tinfo->m_tid, tinfo->m_comm.c_str()); + } + + return; + } + + // Otherwise, the thread is a part of a container health probe if: // - // 1. the comm and args match the container's healthcheck + // 1. the comm and args match one of the container's health probes // 2. we traverse the parent state and do *not* find vpid=1, // or find a process not in a container // - // This indicates the initial process of the healthcheck. + // This indicates the initial process of the health probe. - if(!cinfo->m_has_healthcheck || - cinfo->m_healthcheck_exe != tinfo->m_exe || - cinfo->m_healthcheck_args != tinfo->m_args) - { - return; - } + sinsp_container_info::container_health_probe::probe_type ptype = + cinfo->match_health_probe(tinfo); - if(tinfo->m_vpid == 1) + if(ptype == sinsp_container_info::container_health_probe::PT_NONE) { + g_logger.format(sinsp_logger::SEV_DEBUG, + "identify_category (%ld) (%s): container health probe PT_NONE", + tinfo->m_tid, tinfo->m_comm.c_str()); + return; } @@ -413,7 +449,27 @@ void sinsp_container_manager::identify_healthcheck(sinsp_threadinfo *tinfo) if(!found_container_init) { - tinfo->m_is_container_healthcheck = true; + g_logger.format(sinsp_logger::SEV_DEBUG, + "identify_category (%ld) (%s): not under container init, assigning category %s", + tinfo->m_tid, tinfo->m_comm.c_str(), + sinsp_container_info::container_health_probe::probe_type_names[ptype].c_str()); + + // Each health probe type maps to a command category + switch(ptype) + { + case sinsp_container_info::container_health_probe::PT_NONE: + case sinsp_container_info::container_health_probe::PT_END: + break; + case sinsp_container_info::container_health_probe::PT_HEALTHCHECK: + tinfo->m_category = sinsp_threadinfo::CAT_HEALTHCHECK; + break; + case sinsp_container_info::container_health_probe::PT_LIVENESS_PROBE: + tinfo->m_category = sinsp_threadinfo::CAT_LIVENESS_PROBE; + break; + case sinsp_container_info::container_health_probe::PT_READINESS_PROBE: + tinfo->m_category = sinsp_threadinfo::CAT_READINESS_PROBE; + break; + } } } diff --git a/userspace/libsinsp/container.h b/userspace/libsinsp/container.h index fdd7c4ad5d..fb7d1a6d6b 100644 --- a/userspace/libsinsp/container.h +++ b/userspace/libsinsp/container.h @@ -48,12 +48,11 @@ class sinsp_container_manager void dump_containers(scap_dumper_t* dumper); string get_container_name(sinsp_threadinfo* tinfo); - // Set tinfo's is_container_healthcheck attribute to true if - // it is identified as a container healthcheck. It will *not* - // set it to false by default, so a threadinfo that is - // initially identified as a health check will remain one + // Set tinfo's m_category based on the container context. It + // will *not* change any category to NONE, so a threadinfo + // that initially has a category will retain its category // across execs e.g. "sh -c /bin/true" execing /bin/true. - void identify_healthcheck(sinsp_threadinfo *tinfo); + void identify_category(sinsp_threadinfo *tinfo); bool container_exists(const string& container_id) const { return m_containers.find(container_id) != m_containers.end(); diff --git a/userspace/libsinsp/container_engine/docker.h b/userspace/libsinsp/container_engine/docker.h index b7627f4e50..c4aef83e25 100644 --- a/userspace/libsinsp/container_engine/docker.h +++ b/userspace/libsinsp/container_engine/docker.h @@ -79,6 +79,30 @@ class docker_async_source : public sysdig::async_key_value_sourcem_id.c_str(), Json::FastWriter().write(healthcheck_obj).c_str()); + + if(healthcheck_obj.isNull() || + !healthcheck_obj.isMember("Test")) + { + g_logger.format(sinsp_logger::SEV_WARNING, "Could not parse health check from %s", + Json::FastWriter().write(healthcheck_obj).c_str()); + + return; + } + + const Json::Value &test_obj = healthcheck_obj["Test"]; + + if(!test_obj.isArray() || test_obj.size() < 2) + { + g_logger.format(sinsp_logger::SEV_WARNING, "Could not parse health check from %s", + Json::FastWriter().write(healthcheck_obj).c_str()); + return; + } + + if(test_obj[0].asString() == "CMD") + { + std::string exe = normalize_arg(test_obj[1].asString()); + std::vector args; + + for(uint32_t i = 2; i < test_obj.size(); i++) + { + args.push_back(normalize_arg(test_obj[i].asString())); + } + + g_logger.format(sinsp_logger::SEV_DEBUG, + "docker (%s): Setting PT_HEALTHCHECK exe=%s nargs=%d", + container->m_id.c_str(), exe.c_str(), args.size()); + + container->m_health_probes.emplace_back(sinsp_container_info::container_health_probe::PT_HEALTHCHECK, + std::move(exe), + std::move(args)); + } + else if(test_obj[0].asString() == "CMD-SHELL") + { + std::string exe = "/bin/sh"; + std::vector args; + + args.push_back("-c"); + args.push_back(test_obj[1].asString()); + + g_logger.format(sinsp_logger::SEV_DEBUG, + "docker (%s): Setting PT_HEALTHCHECK exe=%s nargs=%d", + container->m_id.c_str(), exe.c_str(), args.size()); + + container->m_health_probes.emplace_back(sinsp_container_info::container_health_probe::PT_HEALTHCHECK, + std::move(exe), + std::move(args)); + } + + // This occurs when HEALTHCHECK is NONE. No warning log in this case. +} + +bool docker_async_source::parse_liveness_readiness_probe(const Json::Value &probe_obj, + sinsp_container_info::container_health_probe::probe_type ptype, + sinsp_container_info *container) +{ + if(probe_obj.isNull() || + !probe_obj.isMember("exec") || + !probe_obj["exec"].isMember("command")) + { + g_logger.format(sinsp_logger::SEV_WARNING, "Could not parse liveness/readiness probe from %s", + Json::FastWriter().write(probe_obj).c_str()); + return false; + } + + const Json::Value command_obj = probe_obj["exec"]["command"]; + + if(!command_obj.isNull() && command_obj.isArray()) + { + std::string exe; + std::vector args; + + exe = normalize_arg(command_obj[0].asString()); + for(uint32_t i = 1; i < command_obj.size(); i++) + { + args.push_back(normalize_arg(command_obj[i].asString())); + } + + g_logger.format(sinsp_logger::SEV_DEBUG, + "docker (%s): Setting %s exe=%s nargs=%d", + container->m_id.c_str(), + sinsp_container_info::container_health_probe::probe_type_names[ptype].c_str(), + exe.c_str(), args.size()); + + container->m_health_probes.emplace_back(ptype, std::move(exe), std::move(args)); + } + + return true; +} + +void docker_async_source::parse_health_probes(const Json::Value &config_obj, + sinsp_container_info *container) +{ + Json::Value spec; + bool liveness_readiness_added = false; + + // When parsing the full container json for live containers, a label contains stringified json that + // contains the probes. + if (get_k8s_pod_spec(config_obj, spec)) + { + if(spec.isMember("livenessProbe")) + { + if(parse_liveness_readiness_probe(spec["livenessProbe"], + sinsp_container_info::container_health_probe::PT_LIVENESS_PROBE, + container)) + { + liveness_readiness_added = true; + } + } + else if(spec.isMember("readinessProbe")) + { + if(parse_liveness_readiness_probe(spec["readinessProbe"], + sinsp_container_info::container_health_probe::PT_READINESS_PROBE, + container)) + { + liveness_readiness_added = true; + } + } + } + + // To avoid any confusion about containers that both refer to + // a healthcheck and liveness/readiness probe, we only + // consider a healthcheck if no liveness/readiness was added. + if(!liveness_readiness_added && config_obj.isMember("Healthcheck")) + { + parse_healthcheck(config_obj["Healthcheck"], container); + } +} + void docker_async_source::set_query_image_info(bool query_image_info) { g_logger.format(sinsp_logger::SEV_DEBUG, @@ -273,19 +477,16 @@ bool docker_async_source::parse_docker(std::string &container_id, sinsp_containe container->m_imageid = imgstr.substr(cpos + 1); } - container->parse_healthcheck(config_obj["Healthcheck"]); - - // Saving full healthcheck for container event parsing/writing - container->m_healthcheck_obj = config_obj["Healthcheck"]; + parse_health_probes(config_obj, container); // containers can be spawned using just the imageID as image name, // with or without the hash prefix (e.g. sha256:) bool no_name = !container->m_imageid.empty() && - strncmp(container->m_image.c_str(), container->m_imageid.c_str(), - MIN(container->m_image.length(), container->m_imageid.length())) == 0; + strncmp(container->m_image.c_str(), container->m_imageid.c_str(), + MIN(container->m_image.length(), container->m_imageid.length())) == 0; no_name |= !imgstr.empty() && - strncmp(container->m_image.c_str(), imgstr.c_str(), - MIN(container->m_image.length(), imgstr.length())) == 0; + strncmp(container->m_image.c_str(), imgstr.c_str(), + MIN(container->m_image.length(), imgstr.length())) == 0; if(!no_name || !m_query_image_info) { @@ -520,3 +721,4 @@ bool docker_async_source::parse_docker(std::string &container_id, sinsp_containe container_id.c_str()); return true; } + diff --git a/userspace/libsinsp/container_info.cpp b/userspace/libsinsp/container_info.cpp index bf15f9452b..90575024e1 100644 --- a/userspace/libsinsp/container_info.cpp +++ b/userspace/libsinsp/container_info.cpp @@ -17,10 +17,97 @@ limitations under the License. */ +#include + #include "container_info.h" #include "sinsp.h" #include "sinsp_int.h" +std::vector sinsp_container_info::container_health_probe::probe_type_names = { + "None", + "Healthcheck", + "LivenessProbe", + "ReadinessProbe", + "End" +}; + +sinsp_container_info::container_health_probe::container_health_probe() +{ +} + +sinsp_container_info::container_health_probe::container_health_probe(const probe_type ptype, + const std::string &&exe, + const std::vector &&args) + : m_probe_type(ptype), + m_health_probe_exe(exe), + m_health_probe_args(args) +{ +} + +sinsp_container_info::container_health_probe::~container_health_probe() +{ +} + +void sinsp_container_info::container_health_probe::parse_health_probes(const Json::Value &config_obj, + std::list &probes) +{ + // Add any health checks described in the container config/labels. + for(int i=PT_NONE; i != PT_END; i++) + { + string key = probe_type_names[i]; + const Json::Value& probe_obj = config_obj[key]; + + if(!probe_obj.isNull() && probe_obj.isObject()) + { + const Json::Value& probe_exe_obj = probe_obj["exe"]; + + if(!probe_exe_obj.isNull() && probe_exe_obj.isConvertibleTo(Json::stringValue)) + { + const Json::Value& probe_args_obj = probe_obj["args"]; + + std::string probe_exe = probe_exe_obj.asString(); + std::vector probe_args; + + if(!probe_args_obj.isNull() && probe_args_obj.isArray()) + { + for(const auto &item : probe_args_obj) + { + if(item.isConvertibleTo(Json::stringValue)) + { + probe_args.push_back(item.asString()); + } + } + } + g_logger.format(sinsp_logger::SEV_DEBUG, + "add_health_probes: adding %s %s %d", + probe_type_names[i].c_str(), + probe_exe.c_str(), + probe_args.size()); + + probes.emplace_back(static_cast(i), std::move(probe_exe), std::move(probe_args)); + } + } + } +} + +void sinsp_container_info::container_health_probe::add_health_probes(const std::list &probes, + Json::Value &config_obj) +{ + for(auto &probe : probes) + { + string key = probe_type_names[probe.m_probe_type]; + Json::Value args; + + config_obj[key]["exe"] = probe.m_health_probe_exe; + for(auto &arg : probe.m_health_probe_args) + { + args.append(arg); + } + + config_obj[key]["args"] = args; + } +} + const sinsp_container_info::container_mount_info *sinsp_container_info::mount_by_idx(uint32_t idx) const { if (idx >= m_mounts.size()) @@ -59,57 +146,6 @@ const sinsp_container_info::container_mount_info *sinsp_container_info::mount_by return NULL; } -std::string sinsp_container_info::normalize_healthcheck_arg(const std::string &arg) -{ - std::string ret = arg; - - if(ret.empty()) - { - return ret; - } - - // Remove pairs of leading/trailing " or ' chars, if present - while(ret.front() == '"' || ret.front() == '\'') - { - if(ret.back() == ret.front()) - { - ret.pop_back(); - ret.erase(0, 1); - } - } - - return ret; -} - -void sinsp_container_info::parse_healthcheck(const Json::Value &healthcheck_obj) -{ - if(!healthcheck_obj.isNull()) - { - const Json::Value &test_obj = healthcheck_obj["Test"]; - - if(!test_obj.isNull() && test_obj.isArray() && test_obj.size() >= 2) - { - if(test_obj[0].asString() == "CMD") - { - m_has_healthcheck = true; - m_healthcheck_exe = normalize_healthcheck_arg(test_obj[1].asString()); - - for(uint32_t i = 2; i < test_obj.size(); i++) - { - m_healthcheck_args.push_back(normalize_healthcheck_arg(test_obj[i].asString())); - } - } - else if(test_obj[0].asString() == "CMD-SHELL") - { - m_has_healthcheck = true; - m_healthcheck_exe = "/bin/sh"; - m_healthcheck_args.push_back("-c"); - m_healthcheck_args.push_back(test_obj[1].asString()); - } - } - } -} - std::shared_ptr sinsp_container_info::get_tinfo(sinsp* inspector) const { auto tinfo = make_shared(inspector); @@ -123,3 +159,31 @@ std::shared_ptr sinsp_container_info::get_tinfo(sinsp* inspect return tinfo; } +sinsp_container_info::container_health_probe::probe_type sinsp_container_info::match_health_probe(sinsp_threadinfo *tinfo) +{ + g_logger.format(sinsp_logger::SEV_DEBUG, + "match_health_probe (%s): %u health probes to consider", + m_id.c_str(), m_health_probes.size()); + + auto pred = [&] (container_health_probe &p) { + g_logger.format(sinsp_logger::SEV_DEBUG, + "match_health_probe (%s): Matching tinfo %s %d against %s %d", + m_id.c_str(), + tinfo->m_exe.c_str(), tinfo->m_args.size(), + p.m_health_probe_exe.c_str(), p.m_health_probe_args.size()); + + return (p.m_health_probe_exe == tinfo->m_exe && + p.m_health_probe_args == tinfo->m_args); + }; + + auto match = std::find_if(m_health_probes.begin(), + m_health_probes.end(), + pred); + + if(match == m_health_probes.end()) + { + return container_health_probe::PT_NONE; + } + + return match->m_probe_type; +} diff --git a/userspace/libsinsp/container_info.h b/userspace/libsinsp/container_info.h index cbf27e5c01..6550f5301a 100644 --- a/userspace/libsinsp/container_info.h +++ b/userspace/libsinsp/container_info.h @@ -22,6 +22,7 @@ limitations under the License. #include #include #include +#include #include #include @@ -44,6 +45,8 @@ enum sinsp_container_type CT_BPM = 9, }; +class sinsp_threadinfo; + // Docker and CRI-compatible runtimes are very similar static inline bool is_docker_compatible(sinsp_container_type t) { @@ -129,6 +132,48 @@ class sinsp_container_info std::string m_propagation; }; + class container_health_probe + { + public: + + // The type of health probe + enum probe_type { + PT_NONE = 0, + PT_HEALTHCHECK, + PT_LIVENESS_PROBE, + PT_READINESS_PROBE, + PT_END + }; + + // String representations of the above, suitable for + // parsing to/from json. Should be kept in sync with + // probe_type enum. + static std::vector probe_type_names; + + // Parse any health probes out of the provided + // container json, updating the list of probes. + static void parse_health_probes(const Json::Value &config_obj, + std::list &probes); + + // Serialize the list of health probes, adding to the provided json object + static void add_health_probes(const std::list &probes, + Json::Value &config_obj); + + container_health_probe(); + container_health_probe(const probe_type probe_type, + const std::string &&exe, + const std::vector &&args); + virtual ~container_health_probe(); + + // The probe_type that should be used for commands + // matching this health probe. + probe_type m_probe_type; + + // The actual health probe exe and args. + std::string m_health_probe_exe; + std::vector m_health_probe_args; + }; + sinsp_container_info(): m_container_ip(0), m_privileged(false), @@ -137,17 +182,12 @@ class sinsp_container_info m_cpu_shares(1024), m_cpu_quota(0), m_cpu_period(100000), - m_has_healthcheck(false), - m_healthcheck_exe(""), m_is_pod_sandbox(false), m_metadata_complete(true), m_metadata_deadline(0) { } - std::string normalize_healthcheck_arg(const std::string &arg); - void parse_healthcheck(const Json::Value &config_obj); - const std::vector& get_env() const { return m_env; } const container_mount_info *mount_by_idx(uint32_t idx) const; @@ -160,6 +200,9 @@ class sinsp_container_info std::shared_ptr get_tinfo(sinsp* inspector) const; + // Match a process against the set of health probes + container_health_probe::probe_type match_health_probe(sinsp_threadinfo *tinfo); + std::string m_id; sinsp_container_type m_type; std::string m_name; @@ -180,10 +223,8 @@ class sinsp_container_info int64_t m_cpu_shares; int64_t m_cpu_quota; int64_t m_cpu_period; - Json::Value m_healthcheck_obj; - bool m_has_healthcheck; - std::string m_healthcheck_exe; - std::vector m_healthcheck_args; + std::list m_health_probes; + bool m_is_pod_sandbox; // If false, this represents incomplete information about the diff --git a/userspace/libsinsp/filterchecks.cpp b/userspace/libsinsp/filterchecks.cpp index 7912ffcc69..2022164edc 100644 --- a/userspace/libsinsp/filterchecks.cpp +++ b/userspace/libsinsp/filterchecks.cpp @@ -1853,6 +1853,8 @@ const filtercheck_field_info sinsp_filter_check_thread_fields[] = {PT_CHARBUF, EPF_TABLE_ONLY, PF_NA, "thread.nametid", "this field chains the process name and tid of a thread and can be used as a specific identifier of a thread for a specific execve."}, {PT_INT64, EPF_NONE, PF_ID, "proc.vpgid", "the process group id of the process generating the event, as seen from its current PID namespace."}, {PT_BOOL, EPF_NONE, PF_NA, "proc.is_container_healthcheck", "true if this process is running as a part of the container's health check."}, + {PT_BOOL, EPF_NONE, PF_NA, "proc.is_container_liveness_probe", "true if this process is running as a part of the container's liveness probe."}, + {PT_BOOL, EPF_NONE, PF_NA, "proc.is_container_readiness_probe", "true if this process is running as a part of the container's readiness probe."}, }; sinsp_filter_check_thread::sinsp_filter_check_thread() @@ -2642,7 +2644,13 @@ uint8_t* sinsp_filter_check_thread::extract(sinsp_evt *evt, OUT uint32_t* len, b m_tstr = tinfo->get_comm() + to_string(evt->get_tid()); RETURN_EXTRACT_STRING(m_tstr); case TYPE_IS_CONTAINER_HEALTHCHECK: - m_tbool = tinfo->m_is_container_healthcheck; + m_tbool = (tinfo->m_category == sinsp_threadinfo::CAT_HEALTHCHECK); + RETURN_EXTRACT_VAR(m_tbool); + case TYPE_IS_CONTAINER_LIVENESS_PROBE: + m_tbool = (tinfo->m_category == sinsp_threadinfo::CAT_LIVENESS_PROBE); + RETURN_EXTRACT_VAR(m_tbool); + case TYPE_IS_CONTAINER_READINESS_PROBE: + m_tbool = (tinfo->m_category == sinsp_threadinfo::CAT_READINESS_PROBE); RETURN_EXTRACT_VAR(m_tbool); default: ASSERT(false); @@ -5962,7 +5970,9 @@ const filtercheck_field_info sinsp_filter_check_container_fields[] = {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.repository", "the container image repository (e.g. sysdig/sysdig)."}, {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.tag", "the container image tag (e.g. stable, latest)."}, {PT_CHARBUF, EPF_NONE, PF_NA, "container.image.digest", "the container image registry digest (e.g. sha256:d977378f890d445c15e51795296e4e5062f109ce6da83e0a355fc4ad8699d27)."}, - {PT_CHARBUF, EPF_NONE, PF_NA, "container.healthcheck", "The container's health check. Will be the null value (\"N/A\") if no healthcheck configured, \"NONE\" if configured but explicitly not created, and the healthcheck command line otherwise"} + {PT_CHARBUF, EPF_NONE, PF_NA, "container.healthcheck", "The container's health check. Will be the null value (\"N/A\") if no healthcheck configured, \"NONE\" if configured but explicitly not created, and the healthcheck command line otherwise"}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.liveness_probe", "The container's liveness probe. Will be the null value (\"N/A\") if no liveness probe configured, the liveness probe command line otherwise"}, + {PT_CHARBUF, EPF_NONE, PF_NA, "container.readiness_probe", "The container's readiness probe. Will be the null value (\"N/A\") if no readiness probe configured, the readiness probe command line otherwise"} }; sinsp_filter_check_container::sinsp_filter_check_container() @@ -6393,6 +6403,8 @@ uint8_t* sinsp_filter_check_container::extract(sinsp_evt *evt, OUT uint32_t* len } break; case TYPE_CONTAINER_HEALTHCHECK: + case TYPE_CONTAINER_LIVENESS_PROBE: + case TYPE_CONTAINER_READINESS_PROBE: if(tinfo->m_container_id.empty()) { return NULL; @@ -6406,26 +6418,31 @@ uint8_t* sinsp_filter_check_container::extract(sinsp_evt *evt, OUT uint32_t* len return NULL; } - if(container_info->m_healthcheck_obj.isNull()) - { - return NULL; - } - - if(!container_info->m_has_healthcheck) + for(auto &probe : container_info->m_health_probes) { - m_tstr = "NONE"; - - RETURN_EXTRACT_STRING(m_tstr); - } + if((m_field_id == TYPE_CONTAINER_HEALTHCHECK && + probe.m_probe_type == sinsp_container_info::container_health_probe::PT_HEALTHCHECK) || + (m_field_id == TYPE_CONTAINER_LIVENESS_PROBE && + probe.m_probe_type == sinsp_container_info::container_health_probe::PT_LIVENESS_PROBE) || + (m_field_id == TYPE_CONTAINER_READINESS_PROBE && + probe.m_probe_type == sinsp_container_info::container_health_probe::PT_READINESS_PROBE)) + { + m_tstr = probe.m_health_probe_exe; - m_tstr = container_info->m_healthcheck_exe; + for(auto &arg : probe.m_health_probe_args) + { + m_tstr += " "; + m_tstr += arg; + } - for(auto &arg : container_info->m_healthcheck_args) - { - m_tstr += " "; - m_tstr += arg; + RETURN_EXTRACT_STRING(m_tstr); + } } + // If here, then the container didn't have any + // health probe matching the filtercheck + // field. + m_tstr = "NONE"; RETURN_EXTRACT_STRING(m_tstr); } diff --git a/userspace/libsinsp/filterchecks.h b/userspace/libsinsp/filterchecks.h index 9b4c90cc07..e74172f9b2 100644 --- a/userspace/libsinsp/filterchecks.h +++ b/userspace/libsinsp/filterchecks.h @@ -342,6 +342,8 @@ class sinsp_filter_check_thread : public sinsp_filter_check TYPE_NAMETID = 44, TYPE_VPGID = 45, TYPE_IS_CONTAINER_HEALTHCHECK = 46, + TYPE_IS_CONTAINER_LIVENESS_PROBE = 47, + TYPE_IS_CONTAINER_READINESS_PROBE = 48, }; sinsp_filter_check_thread(); @@ -728,6 +730,8 @@ class sinsp_filter_check_container : public sinsp_filter_check TYPE_CONTAINER_IMAGE_TAG, TYPE_CONTAINER_IMAGE_DIGEST, TYPE_CONTAINER_HEALTHCHECK, + TYPE_CONTAINER_LIVENESS_PROBE, + TYPE_CONTAINER_READINESS_PROBE, }; sinsp_filter_check_container(); diff --git a/userspace/libsinsp/parsers.cpp b/userspace/libsinsp/parsers.cpp index 9ebaae72ec..b94ec0a942 100644 --- a/userspace/libsinsp/parsers.cpp +++ b/userspace/libsinsp/parsers.cpp @@ -4600,7 +4600,8 @@ void sinsp_parser::parse_container_json_evt(sinsp_evt *evt) libsinsp::container_engine::docker::parse_json_mounts(container["Mounts"], container_info.m_mounts); - container_info.parse_healthcheck(container["Healthcheck"]); + sinsp_container_info::container_health_probe::parse_health_probes(container, container_info.m_health_probes); + const Json::Value& contip = container["ip"]; if(check_json_val_is_convertible(contip, Json::stringValue, "ip")) { diff --git a/userspace/libsinsp/threadinfo.cpp b/userspace/libsinsp/threadinfo.cpp index cf9615c737..245c6ab766 100644 --- a/userspace/libsinsp/threadinfo.cpp +++ b/userspace/libsinsp/threadinfo.cpp @@ -90,7 +90,7 @@ void sinsp_threadinfo::init() m_lastevent_data = NULL; m_parent_loop_detected = false; m_tty = 0; - m_is_container_healthcheck = false; + m_category = CAT_NONE; m_blprogram = NULL; m_loginuid = 0; } @@ -425,7 +425,7 @@ void sinsp_threadinfo::init(scap_threadinfo* pi) m_clone_ts = pi->clone_ts; m_tty = pi->tty; m_loginuid = pi->loginuid; - m_is_container_healthcheck = false; + m_category = CAT_NONE; set_cgroups(pi->cgroups, pi->cgroups_len); m_root = pi->root; @@ -955,6 +955,13 @@ void sinsp_threadinfo::populate_cmdline(string &cmdline, sinsp_threadinfo *tinfo } } +bool sinsp_threadinfo::is_health_probe() +{ + return (m_category == sinsp_threadinfo::CAT_HEALTHCHECK || + m_category == sinsp_threadinfo::CAT_LIVENESS_PROBE || + m_category == sinsp_threadinfo::CAT_READINESS_PROBE); +} + shared_ptr sinsp_threadinfo::lookup_thread() { return m_inspector->get_thread_ref(m_pid, true, true); diff --git a/userspace/libsinsp/threadinfo.h b/userspace/libsinsp/threadinfo.h index 6244e2393c..10078db7c3 100644 --- a/userspace/libsinsp/threadinfo.h +++ b/userspace/libsinsp/threadinfo.h @@ -236,6 +236,10 @@ class SINSP_PUBLIC sinsp_threadinfo static void populate_cmdline(std::string &cmdline, sinsp_threadinfo *tinfo); + // Return true if this thread is a part of a healthcheck, + // readiness probe, or liveness probe. + bool is_health_probe(); + // // Core state // @@ -269,8 +273,23 @@ class SINSP_PUBLIC sinsp_threadinfo int32_t m_tty; int32_t m_loginuid; ///< loginuid (auid) - // If true, this thread is part of a container health check - bool m_is_container_healthcheck; + // In some cases, a threadinfo has a category that identfies + // why it was run. Descriptions: + // CAT_NONE: no specific category + // CAT_CONTAINER: a process run in a container and *not* any + // of the following more specific categories. + // CAT_HEALTHCHECK: part of a container healthcheck + // CAT_LIVENESS_PROBE: part of a k8s liveness probe + // CAT_READINESS_PROBE: part of a k8s readiness probe + enum command_category { + CAT_NONE = 0, + CAT_CONTAINER, + CAT_HEALTHCHECK, + CAT_LIVENESS_PROBE, + CAT_READINESS_PROBE + }; + + command_category m_category; // // State for multi-event processing