How to Properly View Container Logs Without Using Tail -f Inside Pods
This article explains the correct ways to view container logs in Kubernetes, covering the underlying storage mechanism, kubectl log commands, log rotation, distributed log collection architectures like EFK and Loki, best‑practice recommendations, and detailed troubleshooting steps for common log‑related issues.
Background and Problem
In containerized environments log collection and inspection are frequent operations tasks. Using tail -f inside a container works only for very small clusters; as the number of containers grows the approach becomes inefficient and error‑prone.
1. Fundamentals of Container Logging
1.1 Log storage location
With containerd the logs are stored on the node under
/var/log/pods/<namespace>/<pod_name>/<container_name>/. Each file follows the pattern <container_name>/<restart_count>.log where restart_count starts at 0 and increments on each restart.
# List log directories on the node
ls -la /var/log/pods/
# List a specific pod's logs
ls -la /var/log/pods/default_nginx-deployment-6d9f4c8b7-x2m9n_abc123/
# List a specific container's log file
ls -la /var/log/pods/default_nginx-deployment-6d9f4c8b7-x2m9n_abc123/nginx/
# Show the log content
cat /var/log/pods/default_nginx-deployment-6d9f4c8b7-x2m9n_abc123/nginx/0.log1.2 kubelet log handling
kubelet redirects the container’s stdout and stderr to log files. It also rotates logs based on size using the defaults containerLogMaxSize: 10Mi and containerLogMaxFiles: 5.
# Show the log rotation configuration
cat /var/lib/kubelet/config.yaml | grep -A 5 "logging"
# Default values (shown as comments)
# containerLogMaxSize: 10Mi
# containerLogMaxFiles: 51.3 Role of containerd‑shim
containerd‑shimis an intermediate process that acts as the container’s parent, handles exit signals, keeps the container alive after kubelet restarts, and manages stdin/stdout streams.
# List all containerd‑shim processes
ps aux | grep containerd‑shim
# List containers managed by containerd‑shim
sudo ctr -n k8s.io containers list2. Correct Use of kubectl logs
2.1 Basic usage
# Show the latest logs of a pod
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n
# Show logs of the previous terminated container (after a restart)
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --previous
# Follow logs (similar to tail -f)
kubectl logs -f nginx-deployment-6d9f4c8b7-x2m9n
# Show the last 100 lines
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --tail=100
# Show logs from the last hour
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --since=1h
# Show logs since a specific timestamp
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --since-time="2026-01-01T00:00:00Z"2.2 Logs from multi‑container Pods
# View logs of a specific container in a multi‑container pod
kubectl logs <pod-name> -c <container-name>
# Follow logs of that container
kubectl logs -f <pod-name> -c <container-name>
# View logs of all containers in the pod
kubectl logs <pod-name> --all-containers=true
# Use a label selector to view logs from multiple pods
kubectl logs -l app=nginx --tail=1002.3 Common errors and solutions
Pod not found
# Command that fails
kubectl logs nginx
Error from server (NotFound): pods "nginx" not found
# Fix: specify the correct namespace
kubectl logs nginx -n production
kubectl logs nginx -n production --all-namespacesContainer not found
# Command that fails
kubectl logs multi-container-pod
Error from server (BadRequest): a container name must be specified for pod multi-container-pod, choose one of: [nginx sidecar]
# Fix: use -c to specify the container
kubectl logs multi-container-pod -c nginx
kubectl logs multi-container-pod -c sidecarPod in Terminating state
# Command that fails
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n
Error from server (NotFound): pods "nginx-deployment-6d9f4c8b7-x2m9n" not found
# Fix: use --previous to view the previous container's logs
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --previous
# If the pod has been deleted, view the log file directly on the node
ssh <node-name> "cat /var/log/pods/<pod-id>/<container-name>/0.log"3. Log Filtering and Formatting
3.1 Level filtering
# Show only ERROR level logs
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n | grep ERROR
# Show ERROR/FATAL/CRITICAL logs from the last hour
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --since=1h | grep -E "ERROR|FATAL|CRITICAL"
# Count logs per level
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --since=24h | awk '{print $5}' | sort | uniq -c3.2 Timestamp filtering
# Extract logs within a specific time range (awk example)
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n --since=1h | \
awk -F'[] []' '$2 " " $3 >= "2026-01-15 10:00:00" && $2 " " $3 <= "2026-01-15 11:00:00"'
# Use sed for time‑range filtering
kubectl logs nginx-deployment-6d9f4c8b7-x2m9n | \
sed -n '/2026-01-15 10:00:00/,/2026-01-15 11:00:00/p'3.3 JSON log parsing with jq
# Install jq inside the pod (Alpine example)
kubectl exec -it <pod-name> -- apk add jq
# Extract level and message fields
kubectl logs <pod-name> | jq -r '.level, .message'
# Count occurrences of each error type
kubectl logs <pod-name> --since=24h | jq -r '.error // "no-error"' | sort | uniq -c | sort -rn
# Extract timestamp and message for error level logs
kubectl logs <pod-name> | jq -r 'select(.level == "error") | .timestamp, .message'4. Node‑Level Log Access
4.1 Directly access log files
# SSH to the node
ssh <node-name>
# List pod log directories
ls -la /var/log/pods/
# View container logs with journalctl
sudo journalctl -u kubelet --since "1 hour ago" | grep <pod-name>
# Use ctr to view container information and logs
sudo ctr -n k8s.io containers list | grep <container-name>
sudo ctr -n k8s.io tasks logs <container-id>4.2 containerd logs
# View containerd service logs
sudo journalctl -u containerd --since "1 hour ago" | grep -E "error|warn" --color=never
# Check containerd service status
sudo systemctl status containerd
# Inspect containerd config for sandbox or log_level settings
cat /etc/containerd/config.toml | grep -E "sandbox|log_level"4.3 kubelet logs
# View recent kubelet logs
sudo journalctl -u kubelet --since "30 minutes ago" --no-pager
# Filter error lines
sudo journalctl -u kubelet --since "1 hour ago" | grep -i error
# Follow kubelet logs in real time
sudo journalctl -u kubelet -f5. Distributed Log‑Collection Architecture
5.1 EFK stack
EFK (Elasticsearch + Fluent Bit + Kibana) is a common solution for Kubernetes log aggregation.
Fluent Bit : runs as a DaemonSet on each node, collects container logs and forwards them to Elasticsearch.
Elasticsearch : stores and indexes log data.
Kibana : UI for querying and visualising logs.
5.1.1 Deploy Fluent Bit as a DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: kube-system
spec:
selector:
matchLabels:
k8s-app: fluent-bit
template:
metadata:
labels:
k8s-app: fluent-bit
spec:
containers:
- name: fluent-bit
image: fluent/fluent-bit:3.0.0
ports:
- name: http
containerPort: 2020
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: fluent-bit-config
mountPath: /fluent-bit/etc
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: fluent-bit-config
configMap:
name: fluent-bit-config
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: kube-system
data:
fluent-bit.conf: |
[SERVICE]
Flush 5
Log_Level info
Daemon off
Parsers_File parsers.conf
[INPUT]
Name tail
Path /var/log/containers/*.log
Parser docker
Tag kube.containers.*
Refresh_Interval 5
[OUTPUT]
Name es
Match kube.containers.*
Host elasticsearch.logging.svc
Port 9200
Index kubernetescontainers
Type flb_type
Retry_Limit 2
parsers.conf: |
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L5.1.2 Fluent Bit configuration details
# Add Kubernetes metadata filter
[FILTER]
Name kubernetes
Match kube.containers.*
Kube_URL https://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Kube_Tag_Prefix kube.containers.var.log.containers.
Merge_Log On
Keep_Log Off
K8S-Logging.Parser On
K8S-Logging.Exclude On5.2 Loki stack
Loki, developed by Grafana Labs, is a low‑resource log aggregation system with a Prometheus‑like architecture.
# Loki deployment example
apiVersion: apps/v1
kind: Deployment
metadata:
name: loki
namespace: monitoring
spec:
replicas: 2
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
containers:
- name: loki
image: grafana/loki:3.0.0
ports:
- name: http
containerPort: 3100
args:
- -config.file=/etc/loki/local-config.yaml
- -target=all
resources:
limits:
cpu: "2"
memory: 4Gi
requests:
cpu: "500m"
memory: 1Gi
---
# Promtail (log collector) deployment
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: promtail
namespace: monitoring
spec:
selector:
matchLabels:
app: promtail
template:
metadata:
labels:
app: promtail
spec:
containers:
- name: promtail
image: grafana/promtail:3.0.0
args:
- -config.file=/etc/promtail/promtail.yaml
volumeMounts:
- name: config
mountPath: /etc/promtail
- name: varlog
mountPath: /var/log
- name: pods
mountPath: /var/log/pods
volumes:
- name: config
configMap:
name: promtail-config
- name: varlog
hostPath:
path: /var/log
- name: pods
hostPath:
path: /var/log/pods5.3 Log‑collection best practices
5.3.1 Structured logs
{
"timestamp": "2026-01-15T10:30:00.123Z",
"level": "error",
"message": "Database connection failed",
"service": "user-api",
"request_id": "abc123",
"error_code": "DB_CONN_001",
"stack_trace": "..."
}5.3.2 Log level conventions
ERROR : functional impact, requires immediate attention.
WARN : potential issue or misconfiguration.
INFO : important business events or state changes.
DEBUG : development‑debug information; should be disabled in production.
5.3.3 Retention policy
Define retention based on compliance and storage cost:
Hot storage (Elasticsearch/Loki): 7‑30 days
Warm storage (object storage): 30‑90 days
Cold storage (archival): >90 days
6. Application‑level Logging Best Practices
6.1 Log output to stdout/stderr
# Python example
import logging, sys
logging.basicConfig(
level=logging.INFO,
format='{"timestamp": "%({asctime}s)", "level": "%({levelname}s)", "message": "%({message}s)"}',
handlers=[logging.StreamHandler(sys.stdout)]
)6.2 Log rotation configuration
# Logback (Java) example
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/app/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/var/log/app/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>{"timestamp":"%d{ISO8601}","level":"%level","logger":"%logger","message":"%msg"}%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</configuration>6.3 Sidecar log‑collection pattern
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-with-sidecar
spec:
template:
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/myapp
- name: log-collector
image: fluent/fluent-bit:3.0.0
volumeMounts:
- name: log-volume
mountPath: /var/log/myapp
- name: fluent-bit-config
mountPath: /fluent-bit/etc
volumes:
- name: log-volume
emptyDir: {}
- name: fluent-bit-config
configMap:
name: fluent-bit-config7. Log‑related Troubleshooting
7.1 kubectl logs hangs
# Check API server health
kubectl get componentstatuses
# Inspect API server logs
kubectl logs -n kube-system kube-apiserver-<node-name> --tail=100
# Verify kubelet status on the node
ssh <node-name> "systemctl status kubelet"
# Bypass API server and read the log file directly
ssh <node-name> "cat /var/log/pods/<namespace>_<pod-name>_<uid>/<container-name>/0.log | tail -100"7.2 Log loss
7.2.1 Container restart deletes old logs
By default logs from a restarted container are removed. Increase retention by adjusting kubelet configuration.
# Increase kubelet log retention
cat /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
containerLogMaxFiles: 10
containerLogMaxSize: 50Mi7.2.2 Fluent Bit/Fluent d not collecting logs
# Check Fluent Bit pod status
kubectl get pod -n kube-system -l k8s-app=fluent-bit
# View Fluent Bit logs
kubectl logs -n kube-system -l k8s-app=fluent-bit
# Verify ConfigMap configuration
kubectl describe configmap fluent-bit-config -n kube-system
# Test Fluent Bit configuration inside the pod
kubectl exec -it -n kube-system fluent-bit-xxxx -- fluent-bit --test --parser=parsers.conf < /var/log/containers/*.log7.3 Oversized log files
# Find log files larger than 100 MiB
find /var/log/pods -name "*.log" -size +100M -exec ls -lh {} \;
# Compress a large log file manually
sudo gzip /var/log/pods/<path>/0.log
# Delete old rotated logs (>7 days)
sudo find /var/log/pods -name "*.log.*" -mtime +7 -delete
# Monitor log directory size
du -sh /var/log/pods/*7.4 Inconsistent logs between kubectl logs and node files
# Compare kubectl output with the file on the node
kubectl logs <pod-name> > /tmp/kubectl.log
ssh <node-name> "cat /var/log/pods/<pod-id>/<container>/0.log" > /tmp/file.log
diff /tmp/kubectl.log /tmp/file.log
# Possible reasons:
# 1. Log rotation changed the underlying file.
# 2. Container restart – --previous points to a different file.
# 3. Encoding issues.8. Log Monitoring and Alerting
8.1 Error‑log monitoring with Prometheus
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: log-error-monitoring
namespace: monitoring
spec:
groups:
- name: application-logs
rules:
- alert: HighErrorRate
expr: |
sum(rate(container_log_messages_total{level="error"}[5m])) by (namespace, pod) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate in {{ $labels.namespace }}/{{ $labels.pod }}"
description: "Error rate exceeds 10% over 5 minutes"8.2 Log storage capacity alerts
- alert: LogStorageUsageHigh
expr: |
(node_filesystem_avail_bytes{mountpoint="/var/log"} / node_filesystem_size_bytes{mountpoint="/var/log"}) < 0.2
for: 10m
labels:
severity: warning
annotations:
summary: "Log storage usage is above 80%"9. Practical Command Reference
9.1 View logs from multiple Pods quickly
# Show logs of all Pods with label app=nginx (last 50 lines)
kubectl logs -l app=nginx --tail=50
# Show error logs from the last hour
kubectl logs -l app=nginx --since=1h | grep -E "ERROR|FATAL"
# Count error occurrences in the last 24 hours
kubectl logs -l app=nginx --since=24h | grep -c "ERROR"9.2 Real‑time multi‑Pod log view with stern
# Follow logs of all nginx Pods in namespace production for the last 10 minutes
stern -n production app=nginx --since=10m
# Show only lines containing ERROR
stern -n production app=nginx --filter=ERROR
# Include timestamps
stern -n production app=nginx --timestamp9.3 Common log‑analysis commands
# Distribution of log levels
kubectl logs <pod-name> | awk '{print $5}' | sort | uniq -c | sort -rn
# Top error types (JSON logs)
kubectl logs <pod-name> --since=24h | jq -r '.error // "no-error"' | sort | uniq -c | sort -rn | head -10
# Average request latency (JSON logs)
kubectl logs <pod-name> | jq -r 'select(.type=="request") | .duration' | awk '{sum+=$1; count++} END {print "Average:", sum/count, "ms"}'
# Extract logs within a specific time window
kubectl logs <pod-name> | awk -F'[] []' '$2 >= "2026-01-15T10:00:00" && $2 <= "2026-01-15T11:00:00"'Conclusion
Proper handling of container logs consists of three layers:
Fundamental layer : master kubectl logs usage, including --tail, --since, -f, --previous, and specifying container names for multi‑container Pods.
Architecture layer : understand where logs are stored ( /var/log/pods), how kubelet rotates logs, and the role of containerd‑shim.
Platform layer : build a distributed log‑collection stack (EFK, Loki) for centralized storage, search, and analysis.
Avoid exec‑ing into containers to read logs; rely on kubectl logs or a centralized logging system. Standardise log output (JSON), use clear log levels, and keep consistent field naming to enable downstream analysis and rapid issue localisation.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
