Nginx Ingress Controller 集成
编辑Nginx Ingress Controller 集成
编辑此集成从 Nginx Ingress Controller 实例收集和解析日志。它可以解析入口创建的访问和错误日志。
兼容性
编辑该集成已在 Nginx Ingress Controller v0.30.0 和 v0.40.2 上测试。日志格式在此处描述:这里。
支持的 EDOT 收集器版本 8.16.0
OpenTelemetry 收集器组件
- Filelog 接收器 v0.112.0+
- 转换处理器 v0.112.0+
- 资源检测器处理器 v0.112.0+
- (可选)GeoIP 处理器 v0.112.0+
- Elasticsearch 导出器 v0.112.0+
- 文件存储扩展 v0.112.0+
用法
编辑extensions: file_storage: receivers: filelog: include_file_path: true include: [/var/log/pods/*nginx-ingress-nginx-controller*/controller/*.log] operators: - id: container-parser type: container processors: transform/parse_nginx_ingress_error/log: error_mode: ignore log_statements: - context: log conditions: # ^[EWF]: Matches logs starting with E (Error), W (Warning), or F (Fatal). # \d{4}: Matches the four digits after the log level (representing the date, like 1215 for December 15). # .+: Matches the rest of the log line (the message part, without needing specific timestamp or file format). - IsMatch(body, "^[EWF]\\d{4} .+") statements: - set(body, ExtractGrokPatterns(body, "%{LOG_LEVEL:log.level}%{MONTHNUM}%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND}\\.%{MICROS}%{SPACE}%{NUMBER:thread_id} %{SOURCE_FILE:source.file.name}:%{NUMBER:source.line_number}\\\] %{GREEDYMULTILINE:message}", true, ["LOG_LEVEL=[A-Z]", "MONTHNUM=(0[1-9]|1[0-2])", "MONTHDAY=(0[1-9]|[12][0-9]|3[01])", "HOUR=([01][0-9]|2[0-3])", "MINUTE=[0-5][0-9]", "SECOND=[0-5][0-9]", "MICROS=[0-9]{6}", "SOURCE_FILE=[^:]+", "GREEDYMULTILINE=(.|\\n)*"])) - set(attributes["data_stream.dataset"], "nginx_ingress_controller.error") # LogRecord event: https://github.com/open-telemetry/semantic-conventions/pull/982 - set(attributes["event.name"], "nginx_ingress_controller.error") transform/parse_nginx_ingress_access/log: error_mode: ignore log_statements: - context: log conditions: # # ^([0-9a-fA-F:.]+): Matches the remote address (IPv4 or IPv6 format). # # [^ ]+: Matches the remote user (including the hyphen for missing user). # # .*[0-9a-fA-F]+$: Ensures the log line ends with a hexadecimal string (request ID). - IsMatch(body, "^([0-9a-fA-F:.]+) - [^ ]+ .*[0-9a-fA-F]+$") statements: # Log format: https://github.com/kubernetes/ingress-nginx/blob/nginx-0.30.0/docs/user-guide/nginx-configuration/log-format.md # Based on https://github.com/elastic/integrations/blob/main/packages/nginx_ingress_controller/data_stream/access/elasticsearch/ingest_pipeline/default.yml - set(body, ExtractGrokPatterns(body, "(%{NGINX_HOST} )?\"?(?:%{NGINX_ADDRESS_LIST:nginx_ingress_controller.access.remote_ip_list}|%{NOTSPACE:source.address}) - (-|%{DATA:user.name}) \\\[%{HTTPDATE:nginx_ingress_controller.access.time}\\\] \"%{DATA:nginx_ingress_controller.access.info}\" %{NUMBER:http.response.status_code:long} %{NUMBER:http.response.body.size:long} \"(-|%{DATA:http.request.referrer})\" \"(-|%{DATA:user_agent.original})\" %{NUMBER:http.request.size:long} %{NUMBER:http.request.time:double} \\\[%{DATA:upstream.name}\\\] \\\[%{DATA:upstream.alternative_name}\\\] (%{UPSTREAM_ADDRESS_LIST:upstream.address}|-) (%{UPSTREAM_RESPONSE_SIZE_LIST:upstream.response.size_list}|-) (%{UPSTREAM_RESPONSE_TIME_LIST:upstream.response.time_list}|-) (%{UPSTREAM_RESPONSE_STATUS_CODE_LIST:upstream.response.status_code_list}|-) %{GREEDYDATA:http.request.id}", true, ["NGINX_HOST=(?:%{IP:destination.ip}|%{NGINX_NOTSEPARATOR:destination.domain})(:%{NUMBER:destination.port})?", "NGINX_NOTSEPARATOR=[^\t ,:]+", "NGINX_ADDRESS_LIST=(?:%{IP}|%{WORD}) (\"?,?\\s*(?:%{IP}|%{WORD}))*", "UPSTREAM_ADDRESS_LIST=(?:%{IP}(:%{NUMBER})?)(\"?,?\\s*(?:%{IP}(:%{NUMBER})?))*", "UPSTREAM_RESPONSE_SIZE_LIST=(?:%{NUMBER})(\"?,?\\s*(?:%{NUMBER}))*", "UPSTREAM_RESPONSE_TIME_LIST=(?:%{NUMBER})(\"?,?\\s*(?:%{NUMBER}))*", "UPSTREAM_RESPONSE_STATUS_CODE_LIST=(?:%{NUMBER})(\"?,?\\s*(?:%{NUMBER}))*", "IP=(?:\\\[?%{IPV6}\\\]?|%{IPV4})"])) - merge_maps(body, ExtractGrokPatterns(body["nginx_ingress_controller.access.info"], "%{WORD:http.request.method} %{DATA:url.original} HTTP/%{NUMBER:http.version}", true), "upsert") - delete_key(body, "nginx_ingress_controller.access.info") # Extra URL parsing - merge_maps(body, URL(body["url.original"]), "upsert") - set(body["url.domain"], body["destination.domain"]) # set source.address as attribute for GeoIP processor - set(attributes["source.address"], body["source.address"]) - set(attributes["data_stream.dataset"], "nginx_ingress_controller.access") # LogRecord event: https://github.com/open-telemetry/semantic-conventions/pull/982 - set(attributes["event.name"], "nginx_ingress_controller.access") - set(attributes["event.timestamp"], String(Time(body["nginx_ingress_controller.access.time"], "%d/%b/%Y:%H:%M:%S %z"))) - delete_key(body, "nginx_ingress_controller.access.time") - context: log conditions: # Extract user agent when not empty - body["user_agent.original"] != nil statements: # Extract UserAgent # TODO: UserAgent OTTL function does not provide os specific metadata yet: https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/35458 - merge_maps(body, UserAgent(body["user_agent.original"]), "upsert") - context: log conditions: - body["upstream.response.time_list"] != nil statements: # Extract comma separated list # TODO: We would like to get the sum over all upstream.response.time_list values instead of providing a slice with all the values - set(body["upstream.response.time"], Split(body["upstream.response.time_list"], ",")) - delete_key(body, "upstream.response.time_list") - context: log conditions: - body["upstream.response.size_list"] != nil statements: # Extract comma separated list # TODO: We would like to get the Last upstream.response.size_list value instead of providing a slice with all the values # See: https://github.com/elastic/integrations/blob/main/packages/nginx_ingress_controller/data_stream/access/elasticsearch/ingest_pipeline/default.yml#L94b - set(body["upstream.response.size"], Split(body["upstream.response.size_list"], ",")) - delete_key(body, "upstream.response.size_list") - context: log conditions: - body["upstream.response.status_code_list"] != nil statements: # Extract comma separated list # TODO: We would like to get the Last upstream.response.status_code_list value instead of providing a slice with all the values - set(body["upstream.response.status_code"], Split(body["upstream.response.status_code_list"], ",")) - delete_key(body, "upstream.response.status_code_list") # TODO: add other detectors resourcedetection/system: detectors: ["system"] system: hostname_sources: [ "os" ] resource_attributes: host.name: enabled: true host.id: enabled: false host.arch: enabled: true # geoip: # context: record # providers: # maxmind: # database_path: /tmp/GeoLite2-City.mmdb exporters: elasticsearch: endpoints: - YOUR_ELASTICSEARCH_ENDPOINT api_key: YOUR_ELASTICSEARCH_API_KEY logs_dynamic_index: enabled: true mapping: mode: otel debug: verbosity: detailed service: extensions: [file_storage] pipelines: logs: receivers: [filelog] processors: [transform/parse_nginx_ingress_access/log, transform/parse_nginx_ingress_error/log, resourcedetection/system] # Uncomment the following line if geoip is configured # processors: [transform/parse_nginx_ingress_access/log, transform/parse_nginx_ingress_error/log, geoip, resourcedetection/system] exporters: [debug, elasticsearch]
不要忘记替换
-
YOUR_ELASTICSEARCH_ENDPOINT
:您的 Elasticsearch 端点(带有https://
前缀,例如:https://1234567.us-west2.gcp.elastic-cloud.com:443
)。 -
YOUR_ELASTICSEARCH_API_KEY
:您的 Elasticsearch API 密钥
GeoIP 元数据
编辑默认情况下,传入的 Nginx Ingress Controller 请求的地理 IP 元数据处于禁用状态。要启用它,您需要在处理器的配置中提供本地 GeoIP 数据库路径
- 取消注释 GeoIP 处理器配置
geoip: context: record providers: maxmind: database_path: /tmp/GeoLite2-City.mmdb
- 将处理器包含在日志管道中
processors: [transform/parse_nginx_ingress_access/log, transform/parse_nginx_ingress_error/log, geoip, resourcedetection/system]
日志
编辑访问日志
编辑access
数据流收集 Nginx Ingress Controller 访问日志。
导出的字段
字段 | 描述 | 类型 |
---|---|---|
@timestamp |
事件时间戳。 |
日期 |
data_stream.dataset |
数据流数据集。 |
constant_keyword |
data_stream.namespace |
数据流命名空间。 |
constant_keyword |
data_stream.type |
数据流类型。 |
constant_keyword |
event.name |
事件名称 |
constant_keyword |
resource.attributes.k8s.cluster.name |
keyword |
|
resource.attributes.k8s.container.name |
keyword |
|
resource.attributes.k8s.container.restart_count |
long |
|
resource.attributes.k8s.deployment.name |
keyword |
|
resource.attributes.k8s.namespace.name |
keyword |
|
resource.attributes.k8s.node.name |
keyword |
|
resource.attributes.k8s.pod.name |
keyword |
|
resource.attributes.k8s.pod.start_time |
日期 |
|
resource.attributes.cloud.account.id |
keyword |
|
resource.attributes.cloud.availability_zone |
keyword |
|
resource.attributes.cloud.instance.id |
keyword |
|
resource.attributes.cloud.platform |
keyword |
|
resource.attributes.cloud.provider |
keyword |
|
resource.attributes.deployment.environment |
keyword |
|
resource.attributes.host.arch |
keyword |
|
resource.attributes.host.cpu.cache.l2.size |
keyword |
|
resource.attributes.host.cpu.family |
keyword |
|
resource.attributes.host.cpu.model.id |
keyword |
|
resource.attributes.host.cpu.model.name |
keyword |
|
resource.attributes.host.cpu.stepping |
keyword |
|
resource.attributes.host.cpu.vendor.id |
keyword |
|
resource.attributes.host.id |
keyword |
|
resource.attributes.host.ip |
keyword |
|
resource.attributes.host.mac |
keyword |
|
resource.attributes.host.name |
keyword |
|
resource.attributes.os.type |
keyword |
|
resource.attributes.os.description |
keyword |
|
body.structured.http.request.method |
HTTP 请求方法。该值应保留其原始事件中的大小写。例如, |
keyword |
body.structured.http.request.referrer |
此 HTTP 请求的引用者。 |
keyword |
body.structured.http.response.body.size |
响应正文的大小(以字节为单位)。 |
long |
body.structured.http.response.status_code |
HTTP 响应状态代码。 |
long |
body.structured.http.version |
HTTP 版本。 |
keyword |
attribute.log.file.path |
此事件来自的日志文件的完整路径,包括文件名。它应包括驱动器号(如果适用)。如果该事件不是从日志文件中读取的,则不要填充此字段。 |
keyword |
attribute.log.iostream |
keyword |
|
body.structured.http.request.id |
请求的随机生成的 ID |
text |
body.structured.http.request.size |
请求长度(包括请求行、标头和请求正文) |
long |
body.structured.http.request.time |
自从从客户端读取第一个字节以来经过的时间 |
double |
body.structured.upstream.address |
上游服务器的 IP 地址。如果在请求处理期间联系了多个服务器,它们的地址将用逗号分隔。 |
ip |
body.structured.upstream.name |
上游的名称。 |
keyword |
body.structured.upstream.response.size |
从上游服务器获得的响应的长度 |
long |
body.structured.upstream.response.status_code |
从上游服务器获得的响应的状态代码 |
long |
body.structured.upstream.response.time |
接收来自上游服务器的响应所花费的时间,以毫秒级分辨率的秒为单位 |
double |
attribute.source.address |
某些事件源地址的定义不明确。该事件有时会列出 IP、域名或 Unix 套接字。您应该始终将原始地址存储在 |
keyword |
attribute.geo.city_name |
城市名称。 |
keyword |
attribute.geo.continent_name |
大洲名称。 |
keyword |
attributes.geo.country_iso_code |
国家/地区 ISO 代码。 |
keyword |
attributes.geo.country_name |
国家/地区名称。 |
keyword |
attributes.geo.location.lat |
纬度。 |
geo_point |
attributes.geo.location.lon |
经度。 |
geo_point |
attributes.geo.region_iso_code |
地区 ISO 代码。 |
keyword |
attributes.geo.region_name |
地区名称。 |
keyword |
body.structured.url.domain |
URL 的域,例如“https://elastic.ac.cn[www.elastic.co]”。在某些情况下,URL 可能会直接引用 IP 和/或端口,而没有域名。在这种情况下,IP 地址将转到 |
keyword |
body.structured.url.extension |
该字段包含来自原始请求 URL 的文件扩展名,不包括前导点。仅当存在文件扩展名时才设置文件扩展名,因为并非每个 URL 都有文件扩展名。不得包括前导句点。例如,该值必须是“png”,而不是“.png”。请注意,当文件名具有多个扩展名(example.tar.gz)时,应仅捕获最后一个扩展名(“gz”,而不是“tar.gz”)。 |
keyword |
body.structured.url.original |
在事件源中看到的未修改的原始 URL。请注意,在网络监控中,观察到的 URL 可能是完整的 URL,而在访问日志中,URL 通常仅表示为路径。此字段旨在表示观察到的 URL,完整与否。 |
wildcard |
body.structured.url.path |
请求的路径,例如“/search”。 |
wildcard |
body.structured.url.scheme |
请求的方案,例如“https”。注意: |
keyword |
body.structured.user.name |
用户的短名称或登录名。 |
keyword |
body.structured.user_agent.name |
用户代理的名称。 |
keyword |
body.structured.user_agent.original |
未解析的 user_agent 字符串。 |
keyword |
body.structured.user_agent.version |
用户代理的版本。 |
keyword |
错误日志
编辑error
数据流收集 Nginx Ingress Controller 错误日志。
导出的字段
字段 | 描述 | 类型 |
---|---|---|
@timestamp |
事件时间戳。 |
日期 |
data_stream.dataset |
数据流数据集。 |
constant_keyword |
data_stream.namespace |
数据流命名空间。 |
constant_keyword |
data_stream.type |
数据流类型。 |
constant_keyword |
event.name |
事件名称 |
constant_keyword |
resource.attributes.k8s.cluster.name |
keyword |
|
resource.attributes.k8s.container.name |
keyword |
|
resource.attributes.k8s.container.restart_count |
long |
|
resource.attributes.k8s.deployment.name |
keyword |
|
resource.attributes.k8s.namespace.name |
keyword |
|
resource.attributes.k8s.node.name |
keyword |
|
resource.attributes.k8s.pod.name |
keyword |
|
resource.attributes.k8s.pod.start_time |
日期 |
|
resource.attributes.cloud.account.id |
keyword |
|
resource.attributes.cloud.availability_zone |
keyword |
|
resource.attributes.cloud.instance.id |
keyword |
|
resource.attributes.cloud.platform |
keyword |
|
resource.attributes.cloud.provider |
keyword |
|
resource.attributes.deployment.environment |
keyword |
|
resource.attributes.host.arch |
keyword |
|
resource.attributes.host.cpu.cache.l2.size |
keyword |
|
resource.attributes.host.cpu.family |
keyword |
|
resource.attributes.host.cpu.model.id |
keyword |
|
resource.attributes.host.cpu.model.name |
keyword |
|
resource.attributes.host.cpu.stepping |
keyword |
|
resource.attributes.host.cpu.vendor.id |
keyword |
|
resource.attributes.host.id |
keyword |
|
resource.attributes.host.ip |
keyword |
|
resource.attributes.host.mac |
keyword |
|
resource.attributes.host.name |
keyword |
|
resource.attributes.os.type |
keyword |
|
resource.attributes.os.description |
keyword |
|
attribute.log.file.path |
此事件来自的日志文件的完整路径,包括文件名。它应包括驱动器号(如果适用)。如果该事件不是从日志文件中读取的,则不要填充此字段。 |
keyword |
attributes.log.level |
日志事件的原始日志级别。如果事件源提供日志级别或文本严重性,则此级别将放入 |
keyword |
body.structured.message |
对于日志事件,message 字段包含日志消息,该消息已针对在日志查看器中查看进行了优化。对于没有原始消息字段的结构化日志,可以连接其他字段以形成事件的人类可读摘要。如果存在多个消息,可以将它们合并为一个消息。 |
match_only_text |
body.structured.source.file.name |
源文件 |
keyword |
body.structured.source.line_number |
源行号 |
long |
body.structured.thread_id |
线程 ID |
long |